2026-06-11 01:02:16 +08:00

290 lines
9.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package prettyid
import (
"strconv"
"strings"
"hyapp-admin-server/internal/integration/userclient"
"hyapp-admin-server/internal/middleware"
"hyapp-admin-server/internal/modules/shared"
"hyapp-admin-server/internal/response"
"github.com/gin-gonic/gin"
)
type Handler struct {
user userclient.Client
audit shared.OperationLogger
}
func New(user userclient.Client, audit shared.OperationLogger) *Handler {
return &Handler{user: user, audit: audit}
}
type poolRequest struct {
Name string `json:"name"`
LevelTrack string `json:"level_track"`
MinLevel int32 `json:"min_level"`
MaxLevel int32 `json:"max_level"`
RuleType string `json:"rule_type"`
RuleConfigJSON string `json:"rule_config_json"`
Status string `json:"status"`
SortOrder int32 `json:"sort_order"`
}
type generateRequest struct {
RuleType string `json:"rule_type"`
RuleConfigJSON string `json:"rule_config_json"`
Count int32 `json:"count"`
}
type grantRequest struct {
TargetUserID any `json:"target_user_id"`
DisplayUserID string `json:"display_user_id"`
DurationMs int64 `json:"duration_ms"`
Reason string `json:"reason"`
}
type statusRequest struct {
Status string `json:"status"`
Reason string `json:"reason"`
}
func (h *Handler) ListPools(c *gin.Context) {
page, pageSize, ok := pageQuery(c)
if !ok {
response.BadRequest(c, "分页参数不正确")
return
}
// admin-server 只解析 HTTP 查询参数和当前管理员身份,池状态、等级轨道合法性由 user-service 统一校验。
items, total, err := h.user.ListPrettyDisplayIDPools(c.Request.Context(), userclient.ListPrettyDisplayIDPoolsRequest{
RequestID: middleware.CurrentRequestID(c),
Caller: "admin-server",
Status: strings.TrimSpace(c.Query("status")),
LevelTrack: strings.TrimSpace(c.Query("level_track")),
Page: page,
PageSize: pageSize,
})
if err != nil {
response.BadRequest(c, err.Error())
return
}
response.OK(c, map[string]any{"items": items, "total": total, "page": page, "page_size": pageSize})
}
func (h *Handler) CreatePool(c *gin.Context) {
var req poolRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "靓号池参数不正确")
return
}
// 创建池时操作者从后台登录态读取,不能信任请求体里的管理员 ID。
pool, err := h.user.CreatePrettyDisplayIDPool(c.Request.Context(), userclient.PrettyDisplayIDPoolRequest{
RequestID: middleware.CurrentRequestID(c),
Caller: "admin-server",
Name: req.Name,
LevelTrack: req.LevelTrack,
MinLevel: req.MinLevel,
MaxLevel: req.MaxLevel,
RuleType: req.RuleType,
RuleConfigJSON: req.RuleConfigJSON,
Status: req.Status,
SortOrder: req.SortOrder,
OperatorAdminID: int64(middleware.CurrentUserID(c)),
})
if err != nil {
response.BadRequest(c, err.Error())
return
}
shared.OperationLogWithResourceID(c, h.audit, "create-pretty-id-pool", "pretty_display_id_pools", pool.PoolID, "success", "name="+pool.Name)
response.Created(c, pool)
}
func (h *Handler) UpdatePool(c *gin.Context) {
var req poolRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "靓号池参数不正确")
return
}
// pool_id 来自路径body 只承载可编辑字段,避免前端误传 ID 导致更新目标不一致。
poolID := strings.TrimSpace(c.Param("pool_id"))
pool, err := h.user.UpdatePrettyDisplayIDPool(c.Request.Context(), userclient.PrettyDisplayIDPoolRequest{
RequestID: middleware.CurrentRequestID(c),
Caller: "admin-server",
PoolID: poolID,
Name: req.Name,
LevelTrack: req.LevelTrack,
MinLevel: req.MinLevel,
MaxLevel: req.MaxLevel,
RuleType: req.RuleType,
RuleConfigJSON: req.RuleConfigJSON,
Status: req.Status,
SortOrder: req.SortOrder,
OperatorAdminID: int64(middleware.CurrentUserID(c)),
})
if err != nil {
response.BadRequest(c, err.Error())
return
}
shared.OperationLogWithResourceID(c, h.audit, "update-pretty-id-pool", "pretty_display_id_pools", pool.PoolID, "success", "status="+pool.Status)
response.OK(c, pool)
}
func (h *Handler) Generate(c *gin.Context) {
var req generateRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "生成参数不正确")
return
}
// 生成只从路径读取 pool_id请求体里的规则和数量会继续交给 user-service 校验并写批次审计。
poolID := strings.TrimSpace(c.Param("pool_id"))
batch, err := h.user.GeneratePrettyDisplayIDs(c.Request.Context(), userclient.GeneratePrettyDisplayIDsRequest{
RequestID: middleware.CurrentRequestID(c),
Caller: "admin-server",
PoolID: poolID,
RuleType: req.RuleType,
RuleConfigJSON: req.RuleConfigJSON,
Count: req.Count,
OperatorAdminID: int64(middleware.CurrentUserID(c)),
})
if err != nil {
response.BadRequest(c, err.Error())
return
}
shared.OperationLogWithResourceID(c, h.audit, "generate-pretty-ids", "pretty_display_id_generation_batches", batch.BatchID, "success", "pool_id="+poolID)
response.Created(c, batch)
}
func (h *Handler) ListIDs(c *gin.Context) {
page, pageSize, ok := pageQuery(c)
if !ok {
response.BadRequest(c, "分页参数不正确")
return
}
assignedUserID, ok := optionalInt64Query(c, "assigned_user_id")
if !ok {
response.BadRequest(c, "assigned_user_id 参数不正确")
return
}
// 列表筛选保持轻量透传空字符串表示不过滤assigned_user_id 只在正整数时作为精确条件。
items, total, err := h.user.ListPrettyDisplayIDs(c.Request.Context(), userclient.ListPrettyDisplayIDsRequest{
RequestID: middleware.CurrentRequestID(c),
Caller: "admin-server",
PoolID: strings.TrimSpace(c.Query("pool_id")),
Source: strings.TrimSpace(c.Query("source")),
Status: strings.TrimSpace(c.Query("status")),
Keyword: strings.TrimSpace(c.Query("keyword")),
AssignedUserID: assignedUserID,
Page: page,
PageSize: pageSize,
})
if err != nil {
response.BadRequest(c, err.Error())
return
}
response.OK(c, map[string]any{"items": items, "total": total, "page": page, "page_size": pageSize})
}
func (h *Handler) Grant(c *gin.Context) {
var req grantRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "发放参数不正确")
return
}
targetUserID, ok := parseFlexibleInt64(req.TargetUserID)
if !ok {
response.BadRequest(c, "target_user_id 参数不正确")
return
}
// 后台发放允许 target_user_id 由 JS 以数字或字符串提交,这里转成 int64 后把业务校验交给 user-service。
result, err := h.user.AdminGrantPrettyDisplayID(c.Request.Context(), userclient.AdminGrantPrettyDisplayIDRequest{
RequestID: middleware.CurrentRequestID(c),
Caller: "admin-server",
TargetUserID: targetUserID,
DisplayUserID: req.DisplayUserID,
DurationMs: req.DurationMs,
Reason: req.Reason,
OperatorAdminID: int64(middleware.CurrentUserID(c)),
})
if err != nil {
response.BadRequest(c, err.Error())
return
}
shared.OperationLogWithResourceID(c, h.audit, "grant-pretty-id", "pretty_display_ids", result.PrettyID, "success", "target_user_id="+strconv.FormatInt(targetUserID, 10))
response.Created(c, result)
}
func (h *Handler) SetStatus(c *gin.Context) {
var req statusRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "状态参数不正确")
return
}
// 状态修改以路径 pretty_id 为准,请求体只允许目标状态和原因,防止改错号码。
prettyID := strings.TrimSpace(c.Param("pretty_id"))
item, err := h.user.SetPrettyDisplayIDStatus(c.Request.Context(), userclient.SetPrettyDisplayIDStatusRequest{
RequestID: middleware.CurrentRequestID(c),
Caller: "admin-server",
PrettyID: prettyID,
Status: req.Status,
Reason: req.Reason,
OperatorAdminID: int64(middleware.CurrentUserID(c)),
})
if err != nil {
response.BadRequest(c, err.Error())
return
}
shared.OperationLogWithResourceID(c, h.audit, "set-pretty-id-status", "pretty_display_ids", prettyID, "success", "status="+item.Status)
response.OK(c, item)
}
func pageQuery(c *gin.Context) (int32, int32, bool) {
// 分页参数在 admin-server 先收敛到 1..100,避免后台页面传错值造成 user-service 大分页查询。
page := int32(1)
pageSize := int32(20)
if raw := strings.TrimSpace(c.Query("page")); raw != "" {
value, err := strconv.ParseInt(raw, 10, 32)
if err != nil || value <= 0 {
return 0, 0, false
}
page = int32(value)
}
if raw := strings.TrimSpace(c.Query("page_size")); raw != "" {
value, err := strconv.ParseInt(raw, 10, 32)
if err != nil || value <= 0 {
return 0, 0, false
}
pageSize = int32(value)
}
if pageSize > 100 {
pageSize = 100
}
return page, pageSize, true
}
func optionalInt64Query(c *gin.Context, name string) (int64, bool) {
// 空查询表示不筛选,非空必须是正整数,避免把 0 当成真实用户 ID 传给列表接口。
raw := strings.TrimSpace(c.Query(name))
if raw == "" {
return 0, true
}
value, err := strconv.ParseInt(raw, 10, 64)
return value, err == nil && value > 0
}
func parseFlexibleInt64(raw any) (int64, bool) {
// JSON 大整数在前端经常以字符串提交,数字提交时也必须是精确整数,不能接受小数或负数。
switch value := raw.(type) {
case float64:
if value <= 0 || value != float64(int64(value)) {
return 0, false
}
return int64(value), true
case string:
parsed, err := strconv.ParseInt(strings.TrimSpace(value), 10, 64)
return parsed, err == nil && parsed > 0
default:
return 0, false
}
}