diff --git a/controller/user.go b/controller/user.go index 1fc83c99e..9e09c51da 100644 --- a/controller/user.go +++ b/controller/user.go @@ -287,11 +287,37 @@ func GetAllUsers(c *gin.Context) { return } +// SearchUsers handles a request to find users by keyword, group, or external identifier filters and returns paginated results. +// It requires at least one search parameter (keyword, group, or any of the external ID/email filters) and responds with an error if none are provided. +// On success it populates the page query with matching users and total count and returns that page info. func SearchUsers(c *gin.Context) { keyword := c.Query("keyword") group := c.Query("group") + filters := map[string]string{ + "github_id": c.Query("github_id"), + "discord_id": c.Query("discord_id"), + "oidc_id": c.Query("oidc_id"), + "wechat_id": c.Query("wechat_id"), + "email": c.Query("email"), + "telegram_id": c.Query("telegram_id"), + "linux_do_id": c.Query("linux_do_id"), + } + + // 检查是否至少有一个搜索条件 + hasFilter := keyword != "" || group != "" + for _, v := range filters { + if v != "" { + hasFilter = true + break + } + } + if !hasFilter { + common.ApiErrorMsg(c, "at least one search parameter is required") + return + } + pageInfo := common.GetPageQuery(c) - users, total, err := model.SearchUsers(keyword, group, pageInfo.GetStartIdx(), pageInfo.GetPageSize()) + users, total, err := model.SearchUsers(keyword, group, filters, pageInfo.GetStartIdx(), pageInfo.GetPageSize()) if err != nil { common.ApiError(c, err) return @@ -1293,4 +1319,4 @@ func UpdateUserSetting(c *gin.Context) { "success": true, "message": "设置已更新", }) -} +} \ No newline at end of file diff --git a/model/user.go b/model/user.go index 395daa0b5..f76cc4d63 100644 --- a/model/user.go +++ b/model/user.go @@ -185,6 +185,11 @@ func GetMaxUserId() int { return user.Id } +// GetAllUsers retrieves a paginated list of users and the total number of users. +// It returns results that include soft-deleted records, ordered by id descending, +// and omits the password field from the returned user objects. +// The pageInfo parameter provides the page size and start index for pagination. +// Returns the slice of users, the total user count, and any error encountered. func GetAllUsers(pageInfo *common.PageInfo) (users []*User, total int64, err error) { // Start transaction tx := DB.Begin() @@ -219,7 +224,10 @@ func GetAllUsers(pageInfo *common.PageInfo) (users []*User, total int64, err err return users, total, nil } -func SearchUsers(keyword string, group string, startIdx int, num int) ([]*User, int64, error) { +// SearchUsers searches for users matching the provided keyword, group, and exact-match filters. +// The function accepts a keyword (performs LIKE match on username, email, and display_name; if the keyword is numeric it also matches id), a group to scope results, a map of exact-match filters (allowed keys: "github_id", "discord_id", "oidc_id", "wechat_id", "email", "telegram_id", "linux_do_id"), and pagination parameters startIdx and num. +// It returns the matched users, the total number of records matching the query (ignoring pagination), and an error if the operation fails. +func SearchUsers(keyword string, group string, filters map[string]string, startIdx int, num int) ([]*User, int64, error) { var users []*User var total int64 var err error @@ -238,32 +246,46 @@ func SearchUsers(keyword string, group string, startIdx int, num int) ([]*User, // 构建基础查询 query := tx.Unscoped().Model(&User{}) - // 构建搜索条件 - likeCondition := "username LIKE ? OR email LIKE ? OR display_name LIKE ?" + // 允许的过滤字段白名单 + allowedFields := map[string]bool{ + "github_id": true, + "discord_id": true, + "oidc_id": true, + "wechat_id": true, + "email": true, + "telegram_id": true, + "linux_do_id": true, + } - // 尝试将关键字转换为整数ID - keywordInt, err := strconv.Atoi(keyword) - if err == nil { - // 如果是数字,同时搜索ID和其他字段 - likeCondition = "id = ? OR " + likeCondition - if group != "" { - query = query.Where("("+likeCondition+") AND "+commonGroupCol+" = ?", - keywordInt, "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%", group) - } else { + // 应用精确匹配过滤器 + for field, value := range filters { + if value != "" && allowedFields[field] { + query = query.Where(field+" = ?", value) + } + } + + // 构建搜索条件 + if keyword != "" { + likeCondition := "username LIKE ? OR email LIKE ? OR display_name LIKE ?" + + // 尝试将关键字转换为整数ID + keywordInt, err := strconv.Atoi(keyword) + if err == nil { + // 如果是数字,同时搜索ID和其他字段 + likeCondition = "id = ? OR " + likeCondition query = query.Where(likeCondition, keywordInt, "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%") - } - } else { - // 非数字关键字,只搜索字符串字段 - if group != "" { - query = query.Where("("+likeCondition+") AND "+commonGroupCol+" = ?", - "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%", group) } else { + // 非数字关键字,只搜索字符串字段 query = query.Where(likeCondition, "%"+keyword+"%", "%"+keyword+"%", "%"+keyword+"%") } } + if group != "" { + query = query.Where(commonGroupCol+" = ?", group) + } + // 获取总数 err = query.Count(&total).Error if err != nil { @@ -928,4 +950,4 @@ func RootUserExists() bool { return false } return true -} +} \ No newline at end of file