package resource import ( "context" "strings" "time" "hyapp-admin-server/internal/appctx" "hyapp-admin-server/internal/model" "hyapp-admin-server/internal/modules/shared" ) const ( grantSourceAdmin = "admin" grantSourceManagerCenter = "manager_center" ) type adminOperator struct { UserID uint Username string Name string } type managerOperator struct { UserID int64 DisplayUserID string Username string Avatar string } func (h *Handler) enrichGrantTargets(ctx context.Context, grants []grantDTO) error { ids := collectGrantTargetIDs(grants) if len(ids) == 0 { return nil } users := make(map[int64]grantUserDTO, len(ids)) if h.userDB != nil { var err error users, err = h.queryGrantTargetUsers(ctx, ids) if err != nil { return err } } applyGrantTargetUsers(grants, users) return nil } func (h *Handler) queryGrantTargetUsers(ctx context.Context, ids []int64) (map[int64]grantUserDTO, error) { out := make(map[int64]grantUserDTO, len(ids)) if len(ids) == 0 || h.userDB == nil { return out, nil } nowMs := time.Now().UnixMilli() args := append([]any{nowMs, nowMs, appctx.FromContext(ctx)}, int64Args(ids)...) rows, err := h.userDB.QueryContext(ctx, ` SELECT user_id, current_display_user_id, COALESCE(default_display_user_id, ''), COALESCE(( SELECT lease.display_user_id FROM pretty_display_user_id_leases lease WHERE lease.app_code = users.app_code AND lease.user_id = users.user_id AND lease.status = 'active' AND (COALESCE(lease.expires_at_ms, 0) = 0 OR lease.expires_at_ms > ?) ORDER BY lease.created_at_ms DESC, lease.lease_id DESC LIMIT 1 ), ''), COALESCE(( SELECT pdi.pretty_id FROM pretty_display_user_id_leases lease JOIN pretty_display_ids pdi ON pdi.app_code = lease.app_code AND pdi.assigned_lease_id = lease.lease_id AND pdi.status = 'assigned' WHERE lease.app_code = users.app_code AND lease.user_id = users.user_id AND lease.status = 'active' AND (COALESCE(lease.expires_at_ms, 0) = 0 OR lease.expires_at_ms > ?) ORDER BY lease.created_at_ms DESC, lease.lease_id DESC LIMIT 1 ), ''), COALESCE(username, ''), COALESCE(avatar, '') FROM users WHERE app_code = ? AND user_id IN (`+placeholders(len(ids))+`) `, args...) if err != nil { return nil, err } defer rows.Close() for rows.Next() { var user grantUserDTO if err := rows.Scan(&user.UserID, &user.DisplayUserID, &user.DefaultDisplayUserID, &user.PrettyDisplayUserID, &user.PrettyID, &user.Username, &user.Avatar); err != nil { return nil, err } out[user.UserID] = user } return out, rows.Err() } func applyGrantTargetUsers(grants []grantDTO, users map[int64]grantUserDTO) { for index := range grants { targetUserID := grants[index].TargetUserID if targetUserID <= 0 { continue } if user, ok := users[targetUserID]; ok { grants[index].TargetUser = &user continue } grants[index].TargetUser = &grantUserDTO{UserID: targetUserID} } } func (h *Handler) enrichResourceShopPurchaseUsers(ctx context.Context, orders []resourceShopPurchaseOrderDTO) error { ids := collectResourceShopPurchaseUserIDs(orders) if len(ids) == 0 { return nil } users := map[int64]grantUserDTO{} if h.userDB != nil { var err error users, err = h.queryGrantTargetUsers(ctx, ids) if err != nil { return err } } for index := range orders { userID := orders[index].UserID if userID <= 0 { continue } if user, ok := users[userID]; ok { orders[index].User = &user continue } orders[index].User = &grantUserDTO{UserID: userID} } return nil } func collectResourceShopPurchaseUserIDs(orders []resourceShopPurchaseOrderDTO) []int64 { seen := make(map[int64]struct{}, len(orders)) ids := make([]int64, 0, len(orders)) for _, order := range orders { if order.UserID <= 0 { continue } if _, exists := seen[order.UserID]; exists { continue } seen[order.UserID] = struct{}{} ids = append(ids, order.UserID) } return ids } func (h *Handler) resolveResourceShopPurchaseUserID(ctx context.Context, appCode string, keyword string) (int64, bool, error) { keyword = strings.TrimSpace(keyword) if keyword == "" { return 0, false, nil } if h == nil { return 0, false, nil } // 资源商店购买记录同样由 wallet-service 按内部 user_id 过滤;后台输入允许短号、靓号和昵称,先解析再下传。 return shared.ResolveExactUserID(ctx, h.userDB, appCode, keyword, time.Now().UnixMilli()) } func (h *Handler) enrichGrantOperators(ctx context.Context, grants []grantDTO) error { // operator_user_id 在 wallet 发放事实里只保存稳定 ID;后台列表按来源补展示资料,避免把 admin 用户和 app 用户混成同一张表。 adminIDs, managerIDs := collectGrantOperatorIDs(grants) admins, err := h.queryAdminOperators(adminIDs) if err != nil { return err } managers, err := h.queryManagerOperators(ctx, managerIDs) if err != nil { return err } applyGrantOperators(grants, admins, managers) return nil } func (h *Handler) queryAdminOperators(ids []uint) (map[uint]adminOperator, error) { out := make(map[uint]adminOperator, len(ids)) if len(ids) == 0 || h.store == nil { return out, nil } users, err := h.store.UsersByIDs(ids) if err != nil { return nil, err } for id, user := range users { out[id] = adminOperatorFromModel(user) } return out, nil } func (h *Handler) queryManagerOperators(ctx context.Context, ids []int64) (map[int64]managerOperator, error) { out := make(map[int64]managerOperator, len(ids)) if len(ids) == 0 || h.userDB == nil { return out, nil } // 经理中心发放人是 app 用户,只读 user-service 的用户库投影;资源发放事实仍然以 wallet-service 返回为准。 args := append([]any{appctx.FromContext(ctx)}, int64Args(ids)...) rows, err := h.userDB.QueryContext(ctx, ` SELECT user_id, current_display_user_id, COALESCE(username, ''), COALESCE(avatar, '') FROM users WHERE app_code = ? AND user_id IN (`+placeholders(len(ids))+`) `, args...) if err != nil { return nil, err } defer rows.Close() for rows.Next() { var operator managerOperator if err := rows.Scan(&operator.UserID, &operator.DisplayUserID, &operator.Username, &operator.Avatar); err != nil { return nil, err } out[operator.UserID] = operator } return out, rows.Err() } func applyGrantOperators(grants []grantDTO, admins map[uint]adminOperator, managers map[int64]managerOperator) { for index := range grants { source := normalizeGrantSource(grants[index].GrantSource) operatorID := grants[index].OperatorUserID if operatorID <= 0 { continue } // 即使资料缺失也返回 source + user_id,前端仍能展示可追溯的操作人线索。 operator := &operatorDTO{ Source: source, UserID: operatorID, } switch source { case grantSourceAdmin: if admin, ok := admins[uint(operatorID)]; ok { operator.Username = admin.Username operator.Name = admin.Name } case grantSourceManagerCenter: if manager, ok := managers[operatorID]; ok { operator.DisplayUserID = manager.DisplayUserID operator.Username = manager.Username operator.Avatar = manager.Avatar } } grants[index].Operator = operator } } func collectGrantOperatorIDs(grants []grantDTO) ([]uint, []int64) { adminSet := make(map[uint]struct{}) managerSet := make(map[int64]struct{}) for _, grant := range grants { if grant.OperatorUserID <= 0 { continue } switch normalizeGrantSource(grant.GrantSource) { case grantSourceAdmin: adminSet[uint(grant.OperatorUserID)] = struct{}{} case grantSourceManagerCenter: managerSet[grant.OperatorUserID] = struct{}{} } } adminIDs := make([]uint, 0, len(adminSet)) for id := range adminSet { adminIDs = append(adminIDs, id) } managerIDs := make([]int64, 0, len(managerSet)) for id := range managerSet { managerIDs = append(managerIDs, id) } return adminIDs, managerIDs } func collectGrantTargetIDs(grants []grantDTO) []int64 { targetSet := make(map[int64]struct{}) for _, grant := range grants { if grant.TargetUserID <= 0 { continue } targetSet[grant.TargetUserID] = struct{}{} } targetIDs := make([]int64, 0, len(targetSet)) for id := range targetSet { targetIDs = append(targetIDs, id) } return targetIDs } func adminOperatorFromModel(user model.User) adminOperator { return adminOperator{ UserID: user.ID, Username: user.Username, Name: user.Name, } } func normalizeGrantSource(source string) string { return strings.ToLower(strings.TrimSpace(source)) } func placeholders(count int) string { if count <= 0 { return "" } items := make([]string, count) for index := range items { items[index] = "?" } return strings.Join(items, ",") } func int64Args(values []int64) []any { args := make([]any, 0, len(values)) for _, value := range values { args = append(args, value) } return args }