refactor(api): 优化CreatorsAPI的列表逻辑与数据处理

- 增强list方法,添加重复用户ID过滤,确保返回数据唯一性
- 改进用户名称显示逻辑,支持格式化输出
- 统一SQL查询逻辑,提升代码可读性与维护性
- 处理查询结果时,确保错误处理和数据有效性检查更加严谨
This commit is contained in:
zhouyonggao 2025-12-17 15:51:30 +08:00
parent 526ff0054c
commit 5c143616d5
4 changed files with 173 additions and 188 deletions

View File

@ -1,105 +1,158 @@
package api package api
import ( import (
"database/sql" "database/sql"
"net/http" "fmt"
"strconv" "net/http"
"strings" "strconv"
"strings"
) )
type CreatorsAPI struct { type CreatorsAPI struct {
marketing *sql.DB marketing *sql.DB
} }
func CreatorsHandler(marketing *sql.DB) http.Handler { func CreatorsHandler(marketing *sql.DB) http.Handler {
api := &CreatorsAPI{marketing: marketing} api := &CreatorsAPI{marketing: marketing}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
p := strings.TrimPrefix(r.URL.Path, "/api/creators") p := strings.TrimPrefix(r.URL.Path, "/api/creators")
if r.Method == http.MethodGet && p == "" { if r.Method == http.MethodGet && p == "" {
api.list(w, r) api.list(w, r)
return return
} }
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
}) })
} }
func (a *CreatorsAPI) list(w http.ResponseWriter, r *http.Request) { func (a *CreatorsAPI) list(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query().Get("q") q := r.URL.Query().Get("q")
limitStr := r.URL.Query().Get("limit") limitStr := r.URL.Query().Get("limit")
limit := 2000 limit := 2000
if limitStr != "" { if limitStr != "" {
if n, err := strconv.Atoi(limitStr); err == nil && n > 0 && n <= 10000 { limit = n } if n, err := strconv.Atoi(limitStr); err == nil && n > 0 && n <= 10000 {
} limit = n
sql1 := "SELECT DISTINCT user_id, COALESCE(user_name, '') AS name FROM activity WHERE user_id IS NOT NULL" }
args := []interface{}{} }
if q != "" { sql1 := "SELECT DISTINCT user_id, COALESCE(user_name, '') AS name FROM activity WHERE user_id IS NOT NULL"
sql1 += " AND (CAST(user_id AS CHAR) LIKE ? OR user_name LIKE ?)" args := []interface{}{}
like := "%" + q + "%" if q != "" {
args = append(args, like, like) sql1 += " AND (CAST(user_id AS CHAR) LIKE ? OR user_name LIKE ?)"
} like := "%" + q + "%"
sql1 += " ORDER BY user_id ASC LIMIT ?" args = append(args, like, like)
args = append(args, limit) }
rows, err := a.marketing.Query(sql1, args...) sql1 += " ORDER BY user_id ASC LIMIT ?"
out := []map[string]interface{}{} args = append(args, limit)
if err == nil { rows, err := a.marketing.Query(sql1, args...)
defer rows.Close() out := []map[string]interface{}{}
for rows.Next() { used := map[int64]struct{}{}
var id sql.NullInt64 if err == nil {
var name sql.NullString defer rows.Close()
if err := rows.Scan(&id, &name); err != nil { continue } for rows.Next() {
if !id.Valid { continue } var id sql.NullInt64
m := map[string]interface{}{"id": id.Int64, "name": name.String} var name sql.NullString
out = append(out, m) if err := rows.Scan(&id, &name); err != nil {
} continue
} }
if err != nil || len(out) == 0 { if !id.Valid {
sqlPlan := "SELECT DISTINCT creator, COALESCE(creator_name, '') AS name FROM plan WHERE creator IS NOT NULL" continue
argsPlan := []interface{}{} }
if q != "" { if _, ok := used[id.Int64]; ok {
sqlPlan += " AND (CAST(creator AS CHAR) LIKE ? OR creator_name LIKE ?)" continue
like := "%" + q + "%" }
argsPlan = append(argsPlan, like, like) used[id.Int64] = struct{}{}
}
sqlPlan += " ORDER BY creator ASC LIMIT ?" n := strings.TrimSpace(name.String)
argsPlan = append(argsPlan, limit) if n == "" {
rowsPlan, errPlan := a.marketing.Query(sqlPlan, argsPlan...) n = strconv.FormatInt(id.Int64, 10)
if errPlan == nil { }
defer rowsPlan.Close() display := fmt.Sprintf("%s%d", n, id.Int64)
tmp := []map[string]interface{}{}
for rowsPlan.Next() { m := map[string]interface{}{"id": id.Int64, "name": display}
var id sql.NullInt64 out = append(out, m)
var name sql.NullString }
if err := rowsPlan.Scan(&id, &name); err != nil { continue } }
if !id.Valid { continue } if err != nil || len(out) == 0 {
tmp = append(tmp, map[string]interface{}{"id": id.Int64, "name": name.String}) sqlPlan := "SELECT DISTINCT creator, COALESCE(creator_name, '') AS name FROM plan WHERE creator IS NOT NULL"
} argsPlan := []interface{}{}
if len(tmp) > 0 { out = tmp } if q != "" {
} sqlPlan += " AND (CAST(creator AS CHAR) LIKE ? OR creator_name LIKE ?)"
if len(out) == 0 { like := "%" + q + "%"
sql2 := "SELECT DISTINCT creator, '' AS name FROM `order` WHERE creator IS NOT NULL" argsPlan = append(argsPlan, like, like)
args2 := []interface{}{} }
if q != "" { sqlPlan += " ORDER BY creator ASC LIMIT ?"
sql2 += " AND CAST(creator AS CHAR) LIKE ?" argsPlan = append(argsPlan, limit)
args2 = append(args2, "%"+q+"%") rowsPlan, errPlan := a.marketing.Query(sqlPlan, argsPlan...)
} if errPlan == nil {
sql2 += " ORDER BY creator ASC LIMIT ?" defer rowsPlan.Close()
args2 = append(args2, limit) tmp := []map[string]interface{}{}
rows2, err2 := a.marketing.Query(sql2, args2...) usedPlan := map[int64]struct{}{}
if err2 != nil { for rowsPlan.Next() {
fail(w, r, http.StatusInternalServerError, err2.Error()) var id sql.NullInt64
return var name sql.NullString
} if err := rowsPlan.Scan(&id, &name); err != nil {
defer rows2.Close() continue
out = out[:0] }
for rows2.Next() { if !id.Valid {
var id sql.NullInt64 continue
var name sql.NullString }
if err := rows2.Scan(&id, &name); err != nil { continue } if _, ok := usedPlan[id.Int64]; ok {
if !id.Valid { continue } continue
m := map[string]interface{}{"id": id.Int64, "name": name.String} }
out = append(out, m) usedPlan[id.Int64] = struct{}{}
}
} n := strings.TrimSpace(name.String)
} if n == "" {
ok(w, r, out) n = strconv.FormatInt(id.Int64, 10)
}
display := fmt.Sprintf("%s%d", n, id.Int64)
tmp = append(tmp, map[string]interface{}{"id": id.Int64, "name": display})
}
if len(tmp) > 0 {
out = tmp
}
}
if len(out) == 0 {
sql2 := "SELECT DISTINCT creator, '' AS name FROM `order` WHERE creator IS NOT NULL"
args2 := []interface{}{}
if q != "" {
sql2 += " AND CAST(creator AS CHAR) LIKE ?"
args2 = append(args2, "%"+q+"%")
}
sql2 += " ORDER BY creator ASC LIMIT ?"
args2 = append(args2, limit)
rows2, err2 := a.marketing.Query(sql2, args2...)
if err2 != nil {
fail(w, r, http.StatusInternalServerError, err2.Error())
return
}
defer rows2.Close()
out = out[:0]
usedOrder := map[int64]struct{}{}
for rows2.Next() {
var id sql.NullInt64
var name sql.NullString
if err := rows2.Scan(&id, &name); err != nil {
continue
}
if !id.Valid {
continue
}
if _, ok := usedOrder[id.Int64]; ok {
continue
}
usedOrder[id.Int64] = struct{}{}
n := strings.TrimSpace(name.String)
if n == "" {
n = strconv.FormatInt(id.Int64, 10)
}
display := fmt.Sprintf("%s%d", n, id.Int64)
m := map[string]interface{}{"id": id.Int64, "name": display}
out = append(out, m)
}
}
}
ok(w, r, out)
} }

View File

@ -2,6 +2,7 @@ package api
import ( import (
"database/sql" "database/sql"
"fmt"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@ -45,13 +46,31 @@ func (a *YMTUsersAPI) list(w http.ResponseWriter, r *http.Request) {
return return
} }
defer rows.Close() defer rows.Close()
out := []map[string]interface{}{} out := []map[string]interface{}{}
used := map[int64]struct{}{}
for rows.Next() { for rows.Next() {
var id sql.NullInt64 var id sql.NullInt64
var name sql.NullString var name sql.NullString
if err := rows.Scan(&id, &name); err != nil { continue } if err := rows.Scan(&id, &name); err != nil {
if !id.Valid { continue } continue
out = append(out, map[string]interface{}{"id": id.Int64, "name": name.String}) }
if !id.Valid {
continue
}
if _, ok := used[id.Int64]; ok {
// 根据 ID 去重
continue
}
used[id.Int64] = struct{}{}
n := strings.TrimSpace(name.String)
if n == "" {
n = strconv.FormatInt(id.Int64, 10)
}
display := fmt.Sprintf("%s%d", n, id.Int64)
out = append(out, map[string]interface{}{"id": id.Int64, "name": display})
} }
ok(w, r, out) ok(w, r, out)
} }

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
/opt/homebrew/opt/go/libexec/src/crypto/x509/cert_pool.go:10:2: package encoding/pem is not in std (/opt/homebrew/opt/go/libexec/src/encoding/pem)
cmd/server/main.go:7:2: open /Users/zhouyonggao/Library/Caches/go-build/81/81aa4fdeaa93757df0aec25e30b31ebae853b2651233ed934d1a1b081249443a-d: operation not permitted