324 lines
9.8 KiB
Go
324 lines
9.8 KiB
Go
package sevendaycheckin
|
|
|
|
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"`
|
|
Rewards []rewardDTO `json:"rewards"`
|
|
}
|
|
|
|
type configDTO struct {
|
|
AppCode string `json:"app_code"`
|
|
Enabled bool `json:"enabled"`
|
|
ConfigVersion int64 `json:"config_version"`
|
|
Rewards []rewardDTO `json:"rewards"`
|
|
UpdatedByAdminID int64 `json:"updated_by_admin_id"`
|
|
CreatedAtMS int64 `json:"created_at_ms"`
|
|
UpdatedAtMS int64 `json:"updated_at_ms"`
|
|
}
|
|
|
|
type rewardDTO struct {
|
|
DayIndex int32 `json:"day_index"`
|
|
ResourceGroupID int64 `json:"resource_group_id"`
|
|
}
|
|
|
|
type claimDTO struct {
|
|
ClaimID string `json:"claim_id"`
|
|
CommandID string `json:"command_id"`
|
|
UserID int64 `json:"user_id,string"`
|
|
User *userDTO `json:"user,omitempty"`
|
|
CycleNo int64 `json:"cycle_no"`
|
|
CheckinDay string `json:"checkin_day"`
|
|
DayIndex int32 `json:"day_index"`
|
|
ConfigVersion int64 `json:"config_version"`
|
|
ResourceGroupID int64 `json:"resource_group_id"`
|
|
WalletCommandID string `json:"wallet_command_id"`
|
|
WalletGrantID string `json:"wallet_grant_id"`
|
|
Status string `json:"status"`
|
|
FailureReason string `json:"failure_reason"`
|
|
SignedAtMS int64 `json:"signed_at_ms"`
|
|
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) GetSevenDayCheckInConfig(c *gin.Context) {
|
|
resp, err := h.activity.GetSevenDayCheckInConfig(c.Request.Context(), &activityv1.GetSevenDayCheckInConfigRequest{Meta: h.meta(c)})
|
|
if err != nil {
|
|
response.ServerError(c, "获取七日签到配置失败")
|
|
return
|
|
}
|
|
response.OK(c, configFromProto(resp.GetConfig()))
|
|
}
|
|
|
|
func (h *Handler) UpdateSevenDayCheckInConfig(c *gin.Context) {
|
|
var req configRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
response.BadRequest(c, "七日签到配置参数不正确")
|
|
return
|
|
}
|
|
rewards := make([]*activityv1.SevenDayCheckInReward, 0, len(req.Rewards))
|
|
for _, reward := range req.Rewards {
|
|
rewards = append(rewards, &activityv1.SevenDayCheckInReward{
|
|
DayIndex: reward.DayIndex,
|
|
ResourceGroupId: reward.ResourceGroupID,
|
|
})
|
|
}
|
|
resp, err := h.activity.UpdateSevenDayCheckInConfig(c.Request.Context(), &activityv1.UpdateSevenDayCheckInConfigRequest{
|
|
Meta: h.meta(c),
|
|
Enabled: req.Enabled,
|
|
Rewards: rewards,
|
|
OperatorAdminId: int64(middleware.CurrentUserID(c)),
|
|
})
|
|
if err != nil {
|
|
response.BadRequest(c, err.Error())
|
|
return
|
|
}
|
|
item := configFromProto(resp.GetConfig())
|
|
shared.OperationLogWithResourceID(c, h.audit, "update-seven-day-checkin", "seven_day_checkin_configs", item.AppCode, "success", "")
|
|
response.OK(c, item)
|
|
}
|
|
|
|
func (h *Handler) ListSevenDayCheckInClaims(c *gin.Context) {
|
|
options := shared.ListOptions(c)
|
|
userFilter := shared.UserIdentityFilterFromQuery(c, "")
|
|
userID, matched, ok := h.resolveClaimUserID(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: []claimDTO{}, Page: options.Page, PageSize: options.PageSize, Total: 0})
|
|
return
|
|
}
|
|
dayIndex := parseOptionalInt(c.Query("day_index"))
|
|
if dayIndex < 0 || dayIndex > 7 {
|
|
response.BadRequest(c, "签到天数参数不正确")
|
|
return
|
|
}
|
|
resp, err := h.activity.ListSevenDayCheckInClaims(c.Request.Context(), &activityv1.ListSevenDayCheckInClaimsRequest{
|
|
Meta: h.meta(c),
|
|
Status: strings.TrimSpace(options.Status),
|
|
UserId: userID,
|
|
CheckinDay: strings.TrimSpace(firstQuery(c, "checkin_day", "checkinDay")),
|
|
DayIndex: int32(dayIndex),
|
|
Page: int32(options.Page),
|
|
PageSize: int32(options.PageSize),
|
|
})
|
|
if err != nil {
|
|
response.ServerError(c, "获取七日签到领取记录失败")
|
|
return
|
|
}
|
|
items := make([]claimDTO, 0, len(resp.GetClaims()))
|
|
for _, claim := range resp.GetClaims() {
|
|
items = append(items, claimFromProto(claim))
|
|
}
|
|
if err := h.fillClaimUsers(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) resolveClaimUserID(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 查;短号、靓号和昵称先在 admin 侧解析,避免下游误用展示 ID。
|
|
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) fillClaimUsers(ctx context.Context, claims []claimDTO) error {
|
|
if h.userDB == nil || len(claims) == 0 {
|
|
return nil
|
|
}
|
|
ids := collectClaimUserIDs(claims)
|
|
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 claims {
|
|
if user, ok := users[claims[index].UserID]; ok {
|
|
claims[index].User = &user
|
|
continue
|
|
}
|
|
claims[index].User = &userDTO{UserID: claims[index].UserID}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func configFromProto(config *activityv1.SevenDayCheckInConfig) configDTO {
|
|
if config == nil {
|
|
return configDTO{Rewards: defaultRewards()}
|
|
}
|
|
rewards := make([]rewardDTO, 0, len(config.GetRewards()))
|
|
for _, reward := range config.GetRewards() {
|
|
rewards = append(rewards, rewardDTO{DayIndex: reward.GetDayIndex(), ResourceGroupID: reward.GetResourceGroupId()})
|
|
}
|
|
return configDTO{
|
|
AppCode: config.GetAppCode(),
|
|
Enabled: config.GetEnabled(),
|
|
ConfigVersion: config.GetConfigVersion(),
|
|
Rewards: rewards,
|
|
UpdatedByAdminID: config.GetUpdatedByAdminId(),
|
|
CreatedAtMS: config.GetCreatedAtMs(),
|
|
UpdatedAtMS: config.GetUpdatedAtMs(),
|
|
}
|
|
}
|
|
|
|
func claimFromProto(claim *activityv1.SevenDayCheckInClaim) claimDTO {
|
|
if claim == nil {
|
|
return claimDTO{}
|
|
}
|
|
return claimDTO{
|
|
ClaimID: claim.GetClaimId(),
|
|
CommandID: claim.GetCommandId(),
|
|
UserID: claim.GetUserId(),
|
|
CycleNo: claim.GetCycleNo(),
|
|
CheckinDay: claim.GetCheckinDay(),
|
|
DayIndex: claim.GetDayIndex(),
|
|
ConfigVersion: claim.GetConfigVersion(),
|
|
ResourceGroupID: claim.GetResourceGroupId(),
|
|
WalletCommandID: claim.GetWalletCommandId(),
|
|
WalletGrantID: claim.GetWalletGrantId(),
|
|
Status: claim.GetStatus(),
|
|
FailureReason: claim.GetFailureReason(),
|
|
SignedAtMS: claim.GetSignedAtMs(),
|
|
GrantedAtMS: claim.GetGrantedAtMs(),
|
|
CreatedAtMS: claim.GetCreatedAtMs(),
|
|
UpdatedAtMS: claim.GetUpdatedAtMs(),
|
|
}
|
|
}
|
|
|
|
func defaultRewards() []rewardDTO {
|
|
items := make([]rewardDTO, 0, 7)
|
|
for day := int32(1); day <= 7; day++ {
|
|
items = append(items, rewardDTO{DayIndex: day})
|
|
}
|
|
return items
|
|
}
|
|
|
|
func firstQuery(c *gin.Context, keys ...string) string {
|
|
for _, key := range keys {
|
|
if value := c.Query(key); value != "" {
|
|
return value
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func parseOptionalInt(value string) int {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return 0
|
|
}
|
|
var result int
|
|
for _, char := range value {
|
|
if char < '0' || char > '9' {
|
|
return -1
|
|
}
|
|
result = result*10 + int(char-'0')
|
|
}
|
|
return result
|
|
}
|
|
|
|
func collectClaimUserIDs(claims []claimDTO) []int64 {
|
|
seen := make(map[int64]struct{}, len(claims))
|
|
ids := make([]int64, 0, len(claims))
|
|
for _, claim := range claims {
|
|
if claim.UserID <= 0 {
|
|
continue
|
|
}
|
|
if _, ok := seen[claim.UserID]; ok {
|
|
continue
|
|
}
|
|
seen[claim.UserID] = struct{}{}
|
|
ids = append(ids, claim.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 ""
|
|
}
|
|
return strings.TrimRight(strings.Repeat("?,", count), ",")
|
|
}
|