first commit
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// QuotaService 额度业务服务。
|
||||
type QuotaService struct {
|
||||
quotaDao *mysqldao.QuotaDao
|
||||
flowLogDao *mysqldao.QuotaFlowLogDao
|
||||
}
|
||||
|
||||
// NewQuotaService 创建额度服务。
|
||||
func NewQuotaService() *QuotaService {
|
||||
return &QuotaService{
|
||||
quotaDao: mysqldao.NewQuotaDao(),
|
||||
flowLogDao: mysqldao.NewQuotaFlowLogDao(),
|
||||
}
|
||||
}
|
||||
|
||||
// InfoGetBySelf 获取当前用户各类额度概览。
|
||||
// Cos 返回永久总额度/累计已用/剩余;AI/Outfit/Closet 返回每日总额度/当日已用/当日剩余。
|
||||
func (s *QuotaService) InfoGetBySelf(ctx context.Context, userID int64) (*dto.QuotaInfoGetBySelfResponse, *errcode.Error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "svc.QuotaInfoGetBySelf")
|
||||
defer span.End()
|
||||
|
||||
types := []int8{QuotaTypeCos, QuotaTypeAI, QuotaTypeOutfitShare, QuotaTypeClosetShare}
|
||||
items := make([]dto.QuotaItemResponse, 0, len(types))
|
||||
for _, quotaType := range types {
|
||||
eff, err := getEffectiveQuota(ctx, userID, quotaType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, dto.QuotaItemResponse{
|
||||
QuotaType: quotaType,
|
||||
Total: eff.Total,
|
||||
Used: eff.Used,
|
||||
Remain: eff.Remain,
|
||||
})
|
||||
}
|
||||
|
||||
return &dto.QuotaInfoGetBySelfResponse{Quotas: items}, nil
|
||||
}
|
||||
|
||||
// FlowList 分页查询当前用户的额度流水。
|
||||
func (s *QuotaService) FlowList(ctx context.Context, userID int64, request *dto.QuotaFlowListRequest) (*dto.QuotaFlowListResponse, *errcode.Error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "svc.QuotaFlowList")
|
||||
defer span.End()
|
||||
|
||||
page, pageSize := normalizePage(request.Page, request.PageSize)
|
||||
|
||||
list, total, err := s.flowLogDao.QuotaFlowLogListByUserID(ctx, userID, request.QuotaType, page, pageSize)
|
||||
if err != nil {
|
||||
return nil, errcode.DatabaseError
|
||||
}
|
||||
|
||||
items := make([]dto.QuotaFlowItemResponse, 0, len(list))
|
||||
for _, f := range list {
|
||||
items = append(items, dto.QuotaFlowItemResponse{
|
||||
QuotaFlowUUID: f.QuotaFlowUUID,
|
||||
QuotaType: f.QuotaType,
|
||||
ChangeTarget: f.ChangeTarget,
|
||||
ChangeType: f.ChangeType,
|
||||
ChangeNum: f.ChangeNum,
|
||||
NewValue: f.NewValue,
|
||||
Reason: f.Reason,
|
||||
OrderNo: f.OrderNo,
|
||||
CreatedAt: f.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
return &dto.QuotaFlowListResponse{
|
||||
PageResponse: dto.PageResponse{Total: total, Page: page, PageSize: pageSize},
|
||||
List: items,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ensureDefaultQuota 为新用户初始化 Cos 永久免费额度底值。
|
||||
// AI/Outfit/Closet 为每日额度,不落库,由 getEffectiveQuota 动态计算,无需初始化。
|
||||
// 事务内「创建 + 写流水」强一致,依赖 uk_user_quota 唯一索引实现并发安全幂等;
|
||||
// best-effort:失败仅告警,不阻断登录主流程。
|
||||
func ensureDefaultQuota(ctx context.Context, userID int64) {
|
||||
quotaDao := mysqldao.NewQuotaDao()
|
||||
total := defaultQuotaValue(QuotaTypeCos)
|
||||
|
||||
flow := &mysqlmodel.QuotaFlowLog{
|
||||
QuotaFlowUUID: genUUID(),
|
||||
UserID: userID,
|
||||
ChangeTarget: QuotaChangeTargetAdjust,
|
||||
ChangeType: QuotaChangeTypeActivity,
|
||||
ChangeNum: total,
|
||||
QuotaType: QuotaTypeCos,
|
||||
Reason: "新用户默认上传额度",
|
||||
}
|
||||
if err := quotaDao.QuotaInitWithFlow(ctx, userID, QuotaTypeCos, total, flow); err != nil {
|
||||
slog.WarnContext(ctx, "init default cos quota failed", "user_id", userID, "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// defaultQuotaValue 返回某类额度的新用户默认值:
|
||||
// 优先取 global.UserDefaultQuotaSetting 的配置值,配置为 0 时同样生效(表示不赠送该类免费额度);
|
||||
// 未配置(整段缺失或该项缺失,指针为 nil)或配置为负数时,回落内置常量。
|
||||
func defaultQuotaValue(quotaType int8) int64 {
|
||||
cfg := global.UserDefaultQuotaSetting
|
||||
|
||||
switch quotaType {
|
||||
case QuotaTypeCos:
|
||||
if cfg != nil && cfg.Cos != nil && *cfg.Cos >= 0 {
|
||||
return *cfg.Cos
|
||||
}
|
||||
return DefaultQuotaCos
|
||||
case QuotaTypeAI:
|
||||
if cfg != nil && cfg.AI != nil && *cfg.AI >= 0 {
|
||||
return *cfg.AI
|
||||
}
|
||||
return DefaultQuotaAI
|
||||
case QuotaTypeOutfitShare:
|
||||
if cfg != nil && cfg.OutfitShare != nil && *cfg.OutfitShare >= 0 {
|
||||
return *cfg.OutfitShare
|
||||
}
|
||||
return DefaultQuotaOutfitShare
|
||||
case QuotaTypeClosetShare:
|
||||
if cfg != nil && cfg.ClosetShare != nil && *cfg.ClosetShare >= 0 {
|
||||
return *cfg.ClosetShare
|
||||
}
|
||||
return DefaultQuotaClosetShare
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user