2026-06-29 14:26:06 +08:00

332 lines
11 KiB
Go
Raw Permalink 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 cumulativerechargereward
import (
"context"
"database/sql"
"strings"
"time"
"hyapp-admin-server/internal/appctx"
"hyapp-admin-server/internal/integration/activityclient"
"hyapp-admin-server/internal/middleware"
"hyapp-admin-server/internal/modules/shared"
"hyapp-admin-server/internal/response"
activityv1 "hyapp.local/api/proto/activity/v1"
"github.com/gin-gonic/gin"
)
type Handler struct {
activity activityclient.Client
userDB *sql.DB
audit shared.OperationLogger
}
func New(activity activityclient.Client, userDB *sql.DB, audit shared.OperationLogger) *Handler {
return &Handler{activity: activity, userDB: userDB, audit: audit}
}
type configRequest struct {
Enabled bool `json:"enabled"`
Tiers []tierDTO `json:"tiers"`
}
type configDTO struct {
AppCode string `json:"app_code"`
Enabled bool `json:"enabled"`
Tiers []tierDTO `json:"tiers"`
UpdatedByAdminID int64 `json:"updated_by_admin_id"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
}
type tierDTO struct {
TierID int64 `json:"tier_id"`
TierCode string `json:"tier_code"`
TierName string `json:"tier_name"`
ThresholdUSDMinor int64 `json:"threshold_usd_minor"`
ResourceGroupID int64 `json:"resource_group_id"`
Status string `json:"status"`
SortOrder int32 `json:"sort_order"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
}
type grantDTO struct {
GrantID string `json:"grant_id"`
AppCode string `json:"app_code"`
CycleKey string `json:"cycle_key"`
UserID int64 `json:"user_id,string"`
User *userDTO `json:"user,omitempty"`
EventID string `json:"event_id"`
TransactionID string `json:"transaction_id"`
CommandID string `json:"command_id"`
TierID int64 `json:"tier_id"`
TierCode string `json:"tier_code"`
ThresholdUSDMinor int64 `json:"threshold_usd_minor"`
ResourceGroupID int64 `json:"resource_group_id"`
ReachedUSDMinor int64 `json:"reached_usd_minor"`
QualifyingUSDMinor int64 `json:"qualifying_usd_minor"`
RechargeCoinAmount int64 `json:"recharge_coin_amount"`
RechargeType string `json:"recharge_type"`
Status string `json:"status"`
WalletCommandID string `json:"wallet_command_id"`
WalletGrantID string `json:"wallet_grant_id"`
FailureReason string `json:"failure_reason"`
GrantedAtMS int64 `json:"granted_at_ms"`
CreatedAtMS int64 `json:"created_at_ms"`
UpdatedAtMS int64 `json:"updated_at_ms"`
}
type userDTO struct {
UserID int64 `json:"user_id,string"`
DisplayUserID string `json:"display_user_id"`
Username string `json:"username"`
Avatar string `json:"avatar"`
}
func (h *Handler) GetConfig(c *gin.Context) {
resp, err := h.activity.GetCumulativeRechargeRewardConfig(c.Request.Context(), &activityv1.GetCumulativeRechargeRewardConfigRequest{Meta: h.meta(c)})
if err != nil {
response.ServerError(c, "获取累充奖励配置失败")
return
}
response.OK(c, configFromProto(resp.GetConfig()))
}
func (h *Handler) UpdateConfig(c *gin.Context) {
var req configRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "累充奖励配置参数不正确")
return
}
// admin-server 只做输入透传和操作人注入,档位有效性、重复门槛和启用规则统一交给 activity-service 校验。
tiers := make([]*activityv1.CumulativeRechargeRewardTier, 0, len(req.Tiers))
for _, tier := range req.Tiers {
tiers = append(tiers, &activityv1.CumulativeRechargeRewardTier{
TierId: tier.TierID,
TierCode: strings.TrimSpace(tier.TierCode),
TierName: strings.TrimSpace(tier.TierName),
ThresholdUsdMinor: tier.ThresholdUSDMinor,
ResourceGroupId: tier.ResourceGroupID,
Status: strings.TrimSpace(tier.Status),
SortOrder: tier.SortOrder,
})
}
resp, err := h.activity.UpdateCumulativeRechargeRewardConfig(c.Request.Context(), &activityv1.UpdateCumulativeRechargeRewardConfigRequest{
Meta: h.meta(c),
Enabled: req.Enabled,
Tiers: tiers,
OperatorAdminId: int64(middleware.CurrentUserID(c)),
})
if err != nil {
response.BadRequest(c, err.Error())
return
}
item := configFromProto(resp.GetConfig())
shared.OperationLogWithResourceID(c, h.audit, "update-cumulative-recharge-reward", "cumulative_recharge_reward_configs", item.AppCode, "success", "")
response.OK(c, item)
}
func (h *Handler) ListGrants(c *gin.Context) {
options := shared.ListOptions(c)
// 发放记录主表只存 user_id后台 keyword 先在 users 表解析成精确用户,再下推给 activity-service 查询。
userFilter := shared.UserIdentityFilterFromQuery(c, "")
userID, matched, ok := h.resolveGrantUserID(c.Request.Context(), strings.TrimSpace(options.Keyword), userFilter)
if !ok {
response.ServerError(c, "查询用户信息失败")
return
}
if (options.Keyword != "" || !userFilter.IsEmpty()) && !matched {
response.OK(c, response.Page{Items: []grantDTO{}, Page: options.Page, PageSize: options.PageSize, Total: 0})
return
}
resp, err := h.activity.ListCumulativeRechargeRewardGrants(c.Request.Context(), &activityv1.ListCumulativeRechargeRewardGrantsRequest{
Meta: h.meta(c),
Status: strings.TrimSpace(options.Status),
UserId: userID,
CycleKey: strings.TrimSpace(c.Query("cycle_key")),
Page: int32(options.Page),
PageSize: int32(options.PageSize),
})
if err != nil {
response.ServerError(c, "获取累充奖励发放记录失败")
return
}
items := make([]grantDTO, 0, len(resp.GetGrants()))
for _, grant := range resp.GetGrants() {
items = append(items, grantFromProto(grant))
}
if err := h.fillGrantUsers(c.Request.Context(), items); err != nil {
response.ServerError(c, "补全用户信息失败")
return
}
response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()})
}
func (h *Handler) meta(c *gin.Context) *activityv1.RequestMeta {
return &activityv1.RequestMeta{
RequestId: middleware.CurrentRequestID(c),
Caller: "admin-server",
AppCode: appctx.FromContext(c.Request.Context()),
SentAtMs: time.Now().UnixMilli(),
}
}
func (h *Handler) resolveGrantUserID(ctx context.Context, keyword string, filter shared.UserIdentityFilter) (int64, bool, bool) {
if filter.IsEmpty() && keyword == "" {
return 0, false, true
}
if !filter.IsEmpty() {
userID, matched, err := shared.ResolveUserIdentityFilter(ctx, h.userDB, appctx.FromContext(ctx), filter, time.Now().UnixMilli())
return userID, matched, err == nil
}
// 累计充值奖励按 activity-service 的内部 user_id 过滤keyword 的长 ID、短号、靓号、昵称优先级由 shared 统一维护。
userID, matched, err := shared.ResolveExactUserID(ctx, h.userDB, appctx.FromContext(ctx), keyword, time.Now().UnixMilli())
if err != nil {
return 0, false, false
}
return userID, matched, true
}
func (h *Handler) fillGrantUsers(ctx context.Context, grants []grantDTO) error {
if h.userDB == nil || len(grants) == 0 {
return nil
}
// 列表补用户资料是展示增强,找不到用户时仍返回 grant 原始 user_id避免审计记录因为用户资料缺失而不可见。
ids := collectGrantUserIDs(grants)
if len(ids) == 0 {
return nil
}
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 err
}
defer rows.Close()
users := make(map[int64]userDTO, len(ids))
for rows.Next() {
var user userDTO
if err := rows.Scan(&user.UserID, &user.DisplayUserID, &user.Username, &user.Avatar); err != nil {
return err
}
users[user.UserID] = user
}
if err := rows.Err(); err != nil {
return err
}
for index := range grants {
if user, ok := users[grants[index].UserID]; ok {
grants[index].User = &user
continue
}
grants[index].User = &userDTO{UserID: grants[index].UserID}
}
return nil
}
func configFromProto(config *activityv1.CumulativeRechargeRewardConfig) configDTO {
if config == nil {
return configDTO{Tiers: []tierDTO{}}
}
tiers := make([]tierDTO, 0, len(config.GetTiers()))
for _, tier := range config.GetTiers() {
tiers = append(tiers, tierFromProto(tier))
}
return configDTO{
AppCode: config.GetAppCode(),
Enabled: config.GetEnabled(),
Tiers: tiers,
UpdatedByAdminID: config.GetUpdatedByAdminId(),
CreatedAtMS: config.GetCreatedAtMs(),
UpdatedAtMS: config.GetUpdatedAtMs(),
}
}
func tierFromProto(tier *activityv1.CumulativeRechargeRewardTier) tierDTO {
if tier == nil {
return tierDTO{}
}
return tierDTO{
TierID: tier.GetTierId(),
TierCode: tier.GetTierCode(),
TierName: tier.GetTierName(),
ThresholdUSDMinor: tier.GetThresholdUsdMinor(),
ResourceGroupID: tier.GetResourceGroupId(),
Status: tier.GetStatus(),
SortOrder: tier.GetSortOrder(),
CreatedAtMS: tier.GetCreatedAtMs(),
UpdatedAtMS: tier.GetUpdatedAtMs(),
}
}
func grantFromProto(grant *activityv1.CumulativeRechargeRewardGrant) grantDTO {
if grant == nil {
return grantDTO{}
}
return grantDTO{
GrantID: grant.GetGrantId(),
AppCode: grant.GetAppCode(),
CycleKey: grant.GetCycleKey(),
UserID: grant.GetUserId(),
EventID: grant.GetEventId(),
TransactionID: grant.GetTransactionId(),
CommandID: grant.GetCommandId(),
TierID: grant.GetTierId(),
TierCode: grant.GetTierCode(),
ThresholdUSDMinor: grant.GetThresholdUsdMinor(),
ResourceGroupID: grant.GetResourceGroupId(),
ReachedUSDMinor: grant.GetReachedUsdMinor(),
QualifyingUSDMinor: grant.GetQualifyingUsdMinor(),
RechargeCoinAmount: grant.GetRechargeCoinAmount(),
RechargeType: grant.GetRechargeType(),
Status: grant.GetStatus(),
WalletCommandID: grant.GetWalletCommandId(),
WalletGrantID: grant.GetWalletGrantId(),
FailureReason: grant.GetFailureReason(),
GrantedAtMS: grant.GetGrantedAtMs(),
CreatedAtMS: grant.GetCreatedAtMs(),
UpdatedAtMS: grant.GetUpdatedAtMs(),
}
}
func collectGrantUserIDs(grants []grantDTO) []int64 {
seen := make(map[int64]struct{}, len(grants))
ids := make([]int64, 0, len(grants))
for _, grant := range grants {
if grant.UserID <= 0 {
continue
}
if _, ok := seen[grant.UserID]; ok {
continue
}
seen[grant.UserID] = struct{}{}
ids = append(ids, grant.UserID)
}
return ids
}
func int64Args(ids []int64) []any {
args := make([]any, 0, len(ids))
for _, id := range ids {
args = append(args, id)
}
return args
}
func placeholders(count int) string {
if count <= 0 {
return ""
}
parts := make([]string, count)
for i := range parts {
parts[i] = "?"
}
return strings.Join(parts, ",")
}