2026-06-25 17:07:01 +08:00

331 lines
9.0 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 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
}