feat(backend): 对齐业务服务额度与内容治理口径

仪表盘待办与穿搭概况改为公开/私密/已封禁口径,移除恒为零的待审核统计;补齐额度凭证来源 source=4 与流水 change_type=7 枚举及订单商品类型、额度有效期快照字段;管理员发放与回收额度凭证在同一事务内写入人工调整流水,回收流水使用独立 order_no 避免撞唯一索引,并增加凭证归属校验;对齐 Quota 配置免费额度值,同步更新 README 与配置说明。
This commit is contained in:
2026-09-16 02:58:56 +08:00
parent 5d6109bb52
commit 0863234bc5
13 changed files with 460 additions and 110 deletions
+7 -2
View File
@@ -33,14 +33,19 @@ const (
QuotaTypeClosetShare int8 = 4 // 衣柜分享
// 额度凭证来源(quota_grants.source)
QuotaGrantSourceMember int8 = 1 // 会员包(跟随 VIP 有效期)
QuotaGrantSourceMember int8 = 1 // 会员包(跟随 VIP 有效期,续期自动延长)
QuotaGrantSourceAddon int8 = 2 // 加量包(独立有效期)
QuotaGrantSourceAdReward int8 = 3 // 广告激励(当天有效)
QuotaGrantSourceTrial int8 = 4 // 体验会员赠送(新用户注册;固定有效期=体验期结束,不随续费延长)
QuotaChangeTargetAdjust int8 = 1 // 配额调整
QuotaChangeTargetUse int8 = 2 // 使用
QuotaChangeTypeManual int8 = 5 // 人工调整
// 额度流水变更类型(quota_flow_logs.change_type)。
// 购买获赠(1)/活动赠送(2)/上传消耗(3)/删除回退(4)/退款回收(6)/到期失效(7) 由业务服务写入,
// 管理后台仅写入人工调整(5)。
QuotaChangeTypeManual int8 = 5 // 人工调整(管理后台发放/回收额度时写入)
QuotaChangeTypeExpire int8 = 7 // 到期失效(固定有效期凭证过期,由业务服务定时任务补记)
)
// 免费额度内置兜底值(与业务服务 DefaultQuota* 对齐;仅当 Quota 配置段未配置时使用)。
+9 -8
View File
@@ -92,17 +92,18 @@ func (s *DashboardService) Summary(ctx context.Context) (*dto.DashboardResponse,
RefundTotal: revenueStats.RefundTotal,
},
Todo: dto.DashboardTodo{
RefundPending: todoStats.RefundPending,
OutfitPendingAudit: todoStats.OutfitPendingAudit,
GoodsOnShelf: todoStats.GoodsOnShelf,
RefundPending: todoStats.RefundPending,
// 穿搭口径复用内容概况聚合结果,避免对 outfits 表重复计数。
OutfitPublicTotal: contentStats.OutfitPublic,
OutfitBanned: contentStats.OutfitBanned,
GoodsOnShelf: todoStats.GoodsOnShelf,
},
Content: dto.DashboardContent{
Outfit: dto.DashboardOutfitStats{
Total: contentStats.OutfitTotal,
Pending: contentStats.OutfitPending,
Passed: contentStats.OutfitPassed,
Rejected: contentStats.OutfitRejected,
Banned: contentStats.OutfitBanned,
Total: contentStats.OutfitTotal,
Public: contentStats.OutfitPublic,
Private: contentStats.OutfitPrivate,
Banned: contentStats.OutfitBanned,
},
Clothes: dto.DashboardClothesStats{
Total: contentStats.ClothesTotal,
+2
View File
@@ -164,6 +164,8 @@ func buildOrderItem(o *mysqlmodel.Order) dto.OrderItem {
OrderStatus: o.OrderStatus,
TransactionID: o.TransactionID,
MemberDays: o.MemberDays,
GoodsType: o.GoodsType,
QuotaValidDays: o.QuotaValidDays,
UploadQuota: o.CosQuota,
AIRecommendQuota: o.AIRecommendQuota,
ClosetShareQuota: o.ClosetShareQuota,
+36 -3
View File
@@ -233,7 +233,18 @@ func (s *UserService) QuotaAdjust(ctx context.Context, adminID int64, userUUID s
grant.CreatedBy = adminID
grant.UpdatedBy = adminID
if err := s.quotaGrantDao.QuotaGrantCreate(ctx, grant); err != nil {
// 凭证与「人工调整」流水同事务写入:小程序额度明细中能看到对应的增加记录,
// 避免业务侧到期失效任务补记了扣减、却查不到发放来源的账实不符。
if err := s.quotaGrantDao.QuotaGrantCreateWithFlow(ctx, grant, mysqldao.QuotaFlowParams{
UserID: user.ID,
VipExpire: user.VIPExpire,
QuotaType: request.QuotaType,
Delta: request.Amount,
FreeTotal: defaultQuotaValue(request.QuotaType),
StatDate: todayStatDate(),
OrderNo: grant.RefID, // 与业务侧到期失效流水按 ref_id 关联的口径保持一致
Reason: "管理员发放额度凭证",
}); err != nil {
return errcode.DatabaseError
}
return nil
@@ -252,14 +263,36 @@ func (s *UserService) GrantRevoke(ctx context.Context, adminID int64, userUUID s
return errcode.DatabaseError
}
if _, err := s.quotaGrantDao.QuotaGrantGetByID(ctx, grantID); err != nil {
grant, err := s.quotaGrantDao.QuotaGrantGetByID(ctx, grantID)
if err != nil {
if isNotFound(err) {
return errcode.UserGrantNotFound
}
return errcode.DatabaseError
}
// 归属校验:凭证必须属于该用户,防止越权回收他人凭证。
if grant.UserID != user.ID {
return errcode.UserGrantNotFound
}
if err := s.quotaGrantDao.QuotaGrantSoftDelete(ctx, grantID, user.ID); err != nil {
// 回收使「未用完的部分」失效:Cos 为 Amount-Used,每日额度 Used 恒为 0(口径与业务侧到期失效一致)。
revoke := grant.Amount - grant.Used
if revoke < 0 {
revoke = 0
}
// 回收流水使用独立 order_no:同一凭证的发放流水同为 change_type=5,
// 复用 ref_id 会撞 uk_quota_flow_idem(order_no, quota_type, change_type) 唯一索引。
if err := s.quotaGrantDao.QuotaGrantRevokeWithFlow(ctx, mysqldao.QuotaFlowParams{
UserID: user.ID,
VipExpire: user.VIPExpire,
QuotaType: grant.QuotaType,
Delta: -revoke,
FreeTotal: defaultQuotaValue(grant.QuotaType),
StatDate: todayStatDate(),
OrderNo: "revoke_" + genUUID(),
RelatedID: grant.ID,
Reason: "管理员回收额度凭证",
}); err != nil {
return errcode.DatabaseError
}
return nil