From 602b5b7c246093f4b2fbdd4b39ea62d4ea6299b0 Mon Sep 17 00:00:00 2001 From: "Leon.yan" Date: Thu, 6 Aug 2026 20:45:49 +0900 Subject: [PATCH 1/2] feat: support searching users by name - PublicUserInfoByUserId falls back to name lookup when user_id does not match, only when the name is unique, so redeem can accept a nickname - add GET /v1/user/search (access token required) returning at most 20 users with id, name, avatar and user_id --- manage/user.go | 42 ++++++++++++++++++++++++++++++++++++++---- modelexts/exts.go | 6 ++++++ route/v1.go | 7 +++++++ route/v1/user.go | 23 +++++++++++++++++++++++ 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/manage/user.go b/manage/user.go index bce2e03..31987be 100644 --- a/manage/user.go +++ b/manage/user.go @@ -1657,13 +1657,35 @@ func (m *Manager) PublicUsersInfo(plutoId string) (map[string]interface{}, *perr } func (m *Manager) PublicUserInfoByUserId(userId string) (*modelexts.User, *perror.PlutoError) { - user, err := models.Users(qm.Where("user_id = ?", userId)).One(m.db) - if err != nil && err == sql.ErrNoRows { - return nil, perror.UserNotExist - } else if err != nil { + users, err := models.Users(qm.Where("user_id = ? OR name = ?", userId, userId)).All(m.db) + if err != nil { return nil, perror.ServerError.Wrapper(err) } + // 优先精确匹配 user_id + var user *models.User + for _, u := range users { + if u.UserID == userId { + user = u + break + } + } + if user == nil { + // name 不保证唯一,仅当昵称唯一匹配时才按昵称查找,避免定位到错误用户 + for _, u := range users { + if u.Name != userId { + continue + } + if user != nil { + return nil, perror.UserNotExist + } + user = u + } + } + if user == nil { + return nil, perror.UserNotExist + } + bindings, err := models.Bindings(qm.Where("user_id = ?", userId)).All(m.db) if err != nil { @@ -1677,3 +1699,15 @@ func (m *Manager) PublicUserInfoByUserId(userId string) (*modelexts.User, *perro return userExt, nil } + +func (m *Manager) SearchUsersByName(name string) ([]*models.User, *perror.PlutoError) { + users, err := models.Users( + qm.Where("name LIKE ?", "%"+name+"%"), + qm.Limit(20), + ).All(m.db) + if err != nil { + return nil, perror.ServerError.Wrapper(err) + } + + return users, nil +} diff --git a/modelexts/exts.go b/modelexts/exts.go index cce6f75..b06d2d2 100644 --- a/modelexts/exts.go +++ b/modelexts/exts.go @@ -46,6 +46,12 @@ func (u User) PublicInfo() map[string]interface{} { return res } +func (u User) SearchInfo() map[string]interface{} { + res := u.PublicInfo() + res["user_id"] = u.User.UserID + return res +} + type Scopes struct { Application *models.Application `json:"application"` Scopes []*models.RbacScope `json:"scopes"` diff --git a/route/v1.go b/route/v1.go index f63cdae..ef07b2e 100644 --- a/route/v1.go +++ b/route/v1.go @@ -120,6 +120,13 @@ func (r *Router) registerUserV1Routes(prefix string) { middle: middleware.NoAuthMiddleware, handler: r.v1.PublicUserInfoByUserId, }, + { + path: "/search", + description: "Search users by name", + method: "GET", + middle: middleware.AccessTokenAuthMiddleware, + handler: r.v1.SearchUsers, + }, { path: "/delete", description: "Delete user", diff --git a/route/v1/user.go b/route/v1/user.go index 8f21504..fb23c67 100644 --- a/route/v1/user.go +++ b/route/v1/user.go @@ -12,6 +12,7 @@ import ( perror "github.com/MuShare/pluto/datatype/pluto_error" "github.com/MuShare/pluto/datatype/request" "github.com/MuShare/pluto/manage" + "github.com/MuShare/pluto/modelexts" "github.com/MuShare/pluto/utils/general" "github.com/MuShare/pluto/utils/mail" routeUtils "github.com/MuShare/pluto/utils/route" @@ -405,6 +406,28 @@ func (router *Router) PublicUserInfoByUserId(w http.ResponseWriter, r *http.Requ return nil } +func (router *Router) SearchUsers(w http.ResponseWriter, r *http.Request) *perror.PlutoError { + name := r.URL.Query().Get("name") + if name == "" { + return perror.BadRequest + } + + users, perr := router.manager.SearchUsersByName(name) + + if perr != nil { + return perr + } + + res := make([]map[string]interface{}, 0) + for _, u := range users { + res = append(res, (&modelexts.User{User: u}).SearchInfo()) + } + + routeUtils.ResponseOK(res, w) + + return nil +} + func (router *Router) DeleteUser(w http.ResponseWriter, r *http.Request) *perror.PlutoError { payload, perr := routeUtils.GetAccessPayload(r) if perr != nil { From 8af1266e0bf509159012041ec487ac5e0b001cee Mon Sep 17 00:00:00 2001 From: "Leon.yan" Date: Thu, 6 Aug 2026 21:03:37 +0900 Subject: [PATCH 2/2] refactor: split user_id-or-name lookup into its own endpoint - revert PublicUserInfoByUserId to match user_id only - add GET /v1/user/info/identity/{userIdOrName}/public: exact user_id match first, then unique name match --- manage/user.go | 30 ++++++++++++++++++++++++++---- route/v1.go | 7 +++++++ route/v1/user.go | 14 ++++++++++++++ 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/manage/user.go b/manage/user.go index 31987be..ae273ab 100644 --- a/manage/user.go +++ b/manage/user.go @@ -1657,7 +1657,29 @@ func (m *Manager) PublicUsersInfo(plutoId string) (map[string]interface{}, *perr } func (m *Manager) PublicUserInfoByUserId(userId string) (*modelexts.User, *perror.PlutoError) { - users, err := models.Users(qm.Where("user_id = ? OR name = ?", userId, userId)).All(m.db) + user, err := models.Users(qm.Where("user_id = ?", userId)).One(m.db) + if err != nil && err == sql.ErrNoRows { + return nil, perror.UserNotExist + } else if err != nil { + return nil, perror.ServerError.Wrapper(err) + } + + bindings, err := models.Bindings(qm.Where("user_id = ?", userId)).All(m.db) + + if err != nil { + return nil, perror.ServerError.Wrapper(err) + } + + userExt := &modelexts.User{ + User: user, + Bindings: bindings, + } + + return userExt, nil +} + +func (m *Manager) PublicUserInfoByUserIdOrName(userIdOrName string) (*modelexts.User, *perror.PlutoError) { + users, err := models.Users(qm.Where("user_id = ? OR name = ?", userIdOrName, userIdOrName)).All(m.db) if err != nil { return nil, perror.ServerError.Wrapper(err) } @@ -1665,7 +1687,7 @@ func (m *Manager) PublicUserInfoByUserId(userId string) (*modelexts.User, *perro // 优先精确匹配 user_id var user *models.User for _, u := range users { - if u.UserID == userId { + if u.UserID == userIdOrName { user = u break } @@ -1673,7 +1695,7 @@ func (m *Manager) PublicUserInfoByUserId(userId string) (*modelexts.User, *perro if user == nil { // name 不保证唯一,仅当昵称唯一匹配时才按昵称查找,避免定位到错误用户 for _, u := range users { - if u.Name != userId { + if u.Name != userIdOrName { continue } if user != nil { @@ -1686,7 +1708,7 @@ func (m *Manager) PublicUserInfoByUserId(userId string) (*modelexts.User, *perro return nil, perror.UserNotExist } - bindings, err := models.Bindings(qm.Where("user_id = ?", userId)).All(m.db) + bindings, err := models.Bindings(qm.Where("user_id = ?", user.ID)).All(m.db) if err != nil { return nil, perror.ServerError.Wrapper(err) diff --git a/route/v1.go b/route/v1.go index ef07b2e..388c2d7 100644 --- a/route/v1.go +++ b/route/v1.go @@ -120,6 +120,13 @@ func (r *Router) registerUserV1Routes(prefix string) { middle: middleware.NoAuthMiddleware, handler: r.v1.PublicUserInfoByUserId, }, + { + path: "/info/identity/{userIdOrName}/public", + description: "Get user public info like avatar, name by userId or unique name", + method: "GET", + middle: middleware.NoAuthMiddleware, + handler: r.v1.PublicUserInfoByUserIdOrName, + }, { path: "/search", description: "Search users by name", diff --git a/route/v1/user.go b/route/v1/user.go index fb23c67..46ee459 100644 --- a/route/v1/user.go +++ b/route/v1/user.go @@ -406,6 +406,20 @@ func (router *Router) PublicUserInfoByUserId(w http.ResponseWriter, r *http.Requ return nil } +func (router *Router) PublicUserInfoByUserIdOrName(w http.ResponseWriter, r *http.Request) *perror.PlutoError { + vars := mux.Vars(r) + userIdOrName := vars["userIdOrName"] + res, perr := router.manager.PublicUserInfoByUserIdOrName(userIdOrName) + + if perr != nil { + return perr + } + + routeUtils.ResponseOK(res.PublicInfo(), w) + + return nil +} + func (router *Router) SearchUsers(w http.ResponseWriter, r *http.Request) *perror.PlutoError { name := r.URL.Query().Get("name") if name == "" {