146 lines
4.4 KiB
Go
146 lines
4.4 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
|
|
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/dto"
|
|
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqldao"
|
|
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
|
|
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/pkg/errcode"
|
|
)
|
|
|
|
// AdRewardService 广告激励额度服务。
|
|
type AdRewardService struct{}
|
|
|
|
// NewAdRewardService 创建广告激励额度服务。
|
|
func NewAdRewardService() *AdRewardService {
|
|
return &AdRewardService{}
|
|
}
|
|
|
|
// Grant 发放广告激励额度:校验开关后按服务端配置的固定金额创建当天有效凭证。
|
|
// ref_id+quota_type 唯一索引兜底幂等,重复领取返回 AdRewardClaimed。
|
|
func (s *AdRewardService) Grant(ctx context.Context, userID int64, request *dto.AdRewardGrantRequest) (*dto.AdRewardGrantResponse, *errcode.Error) {
|
|
ctx, span := global.Tracer.Start(ctx, "svc.AdRewardGrant")
|
|
defer span.End()
|
|
|
|
quotaType, ok := parseAdRewardType(request.RewardType)
|
|
if !ok {
|
|
return nil, errcode.AdRewardInvalid
|
|
}
|
|
|
|
// 开关校验:广告激励未开启时拒绝发放。
|
|
if global.AdRewardSetting == nil || !global.AdRewardSetting.Enable {
|
|
return nil, errcode.AdRewardDisabled
|
|
}
|
|
|
|
// 服务端定价:发放金额由配置固定,前端仅传类型与广告记录 ID,杜绝刷额度。
|
|
amount := adRewardAmount(quotaType)
|
|
if amount <= 0 {
|
|
return nil, errcode.AdRewardInvalid.WithDetails("该激励类型未配置发放数量")
|
|
}
|
|
|
|
// 变更前后取「有效剩余」,仅用于流水展示;权威数值以额度凭证表为准。
|
|
before := int64(0)
|
|
if eff, qerr := getEffectiveQuota(ctx, userID, quotaType); qerr == nil {
|
|
before = eff.Remain
|
|
}
|
|
|
|
grant := &mysqlmodel.QuotaGrant{
|
|
UserID: userID,
|
|
QuotaType: quotaType,
|
|
Amount: amount,
|
|
Used: 0,
|
|
Source: QuotaGrantSourceAdReward,
|
|
ExpireTime: todayEndUnix(), // 当天 23:59:59 到期
|
|
RefID: request.RefID,
|
|
}
|
|
// 流水 order_no 复用广告记录ID(与凭证 ref_id 一致,便于回溯;非空故不影响唯一索引幂等)。
|
|
flow := &mysqlmodel.QuotaFlowLog{
|
|
QuotaFlowUUID: genUUID(),
|
|
UserID: userID,
|
|
ChangeTarget: QuotaChangeTargetAdjust,
|
|
ChangeType: QuotaChangeTypeActivity,
|
|
ChangeNum: amount,
|
|
OldValue: before,
|
|
NewValue: before + amount,
|
|
OrderNo: request.RefID,
|
|
QuotaType: quotaType,
|
|
Reason: "广告激励赠送",
|
|
}
|
|
maxClaims := global.AdRewardSetting.MaxClaimsPerDay
|
|
if maxClaims <= 0 {
|
|
maxClaims = 1
|
|
}
|
|
dayStart, dayEnd := todayRangeUnix()
|
|
created, err := mysqldao.NewQuotaGrantDao().AdRewardGrantCreateWithFlow(ctx, grant, flow, dayStart, dayEnd, maxClaims)
|
|
if err != nil {
|
|
return nil, errcode.DatabaseError
|
|
}
|
|
if !created {
|
|
return nil, errcode.AdRewardClaimed // 凭证已存在:重复领取。
|
|
}
|
|
|
|
return &dto.AdRewardGrantResponse{
|
|
RefID: request.RefID,
|
|
QuotaType: quotaType,
|
|
Amount: amount,
|
|
}, nil
|
|
}
|
|
|
|
// adRewardAmount 返回指定额度类型的广告激励固定金额(服务端配置)。
|
|
// 未配置(指针为 nil)或配置为非正数时返回 0,表示不发放该类型激励。
|
|
func adRewardAmount(quotaType int8) int64 {
|
|
cfg := global.AdRewardSetting
|
|
if cfg == nil {
|
|
return 0
|
|
}
|
|
var p *int64
|
|
switch quotaType {
|
|
case QuotaTypeCos:
|
|
p = cfg.Cos
|
|
case QuotaTypeAI:
|
|
p = cfg.AI
|
|
case QuotaTypeOutfitShare:
|
|
p = cfg.OutfitShare
|
|
case QuotaTypeClosetShare:
|
|
p = cfg.ClosetShare
|
|
default:
|
|
return 0
|
|
}
|
|
if p == nil || *p <= 0 {
|
|
return 0
|
|
}
|
|
return *p
|
|
}
|
|
|
|
// parseAdRewardType 将 reward_type 字符串映射到额度类型。
|
|
func parseAdRewardType(rewardType string) (int8, bool) {
|
|
switch rewardType {
|
|
case "cos":
|
|
return QuotaTypeCos, true
|
|
case "ai":
|
|
return QuotaTypeAI, true
|
|
case "outfit_share":
|
|
return QuotaTypeOutfitShare, true
|
|
case "closet_share":
|
|
return QuotaTypeClosetShare, true
|
|
default:
|
|
return 0, false
|
|
}
|
|
}
|
|
|
|
// todayRangeUnix 返回本地时区当天的左闭右开时间范围。
|
|
func todayRangeUnix() (int64, int64) {
|
|
now := time.Now()
|
|
start := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
|
return start.Unix(), start.AddDate(0, 0, 1).Unix()
|
|
}
|
|
|
|
// todayEndUnix 返回当天 23:59:59 的秒级时间戳(广告激励当天有效)。
|
|
func todayEndUnix() int64 {
|
|
now := time.Now()
|
|
return time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location()).Unix()
|
|
}
|