290 lines
9.7 KiB
Go
290 lines
9.7 KiB
Go
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
|
||
}
|
||
}
|