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
+37 -40
View File
@@ -46,10 +46,10 @@ type RevenueStats struct {
}
// TodoStats 待办统计。
// 穿搭相关指标(公开展示中数量、已封禁数量)由 ContentStatsGet 一并聚合,此处不重复查询。
type TodoStats struct {
RefundPending int64 // 待处理退款数
OutfitPendingAudit int64 // 待审核穿搭数
GoodsOnShelf int64 // 上架商品数
RefundPending int64 // 待处理退款数
GoodsOnShelf int64 // 上架商品数
}
// UserStatsGet 聚合用户总数与今日新增
@@ -169,12 +169,6 @@ func (d *StatsDao) TodoStatsGet(ctx context.Context) (*TodoStats, error) {
span.RecordError(err)
return nil, err
}
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Outfit{}).
Where("is_public = ? AND audit_status = ?", int8(2), int8(1)).
Count(&s.OutfitPendingAudit).Error; err != nil {
span.RecordError(err)
return nil, err
}
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Goods{}).
Where("status = ?", int8(1)).
Count(&s.GoodsOnShelf).Error; err != nil {
@@ -184,6 +178,13 @@ func (d *StatsDao) TodoStatsGet(ctx context.Context) (*TodoStats, error) {
return s, nil
}
// outfitDistRow 穿搭 (is_public, status) 组合分布行。
type outfitDistRow struct {
IsPublic int8
Status int8
Cnt int64
}
// countByInt8 通用分组计数行(小整型枚举 + 计数)。
type countByInt8 struct {
K int8
@@ -205,20 +206,19 @@ type dateRevenue struct {
// ContentStats 内容审核统计。
type ContentStats struct {
OutfitTotal int64
OutfitPending int64
OutfitPassed int64
OutfitRejected int64
OutfitBanned int64
ClothesTotal int64
ClothesProcessing int64
ClothesSuccess int64
ClothesFailed int64
ShareTotal int64
ShareNormal int64
ShareCancelled int64
ShareExpired int64
ShareBanned int64
OutfitTotal int64 // 穿搭总数
OutfitPublic int64 // 公开展示中(is_public=2 且未封禁)
OutfitPrivate int64 // 私密(is_public=1 且未封禁)
OutfitBanned int64 // 已封禁(status=2)
ClothesTotal int64 // 衣物总数
ClothesProcessing int64 // 处理中(抠图/分类/排队)
ClothesSuccess int64 // 处理成功
ClothesFailed int64 // 处理失败
ShareTotal int64 // 衣柜分享总数
ShareNormal int64 // 正常
ShareCancelled int64 // 用户取消
ShareExpired int64 // 已过期
ShareBanned int64 // 管理员封禁
}
// VIPStats 会员统计。
@@ -258,32 +258,29 @@ func (d *StatsDao) ContentStatsGet(ctx context.Context) (*ContentStats, error) {
s := &ContentStats{}
// 穿搭:audit_status 分布 + 封禁数
outfitRows := []countByInt8{}
// 穿搭:一次 group by 取回 (is_public, status) 组合分布。
// 业务侧「公开穿搭」会在同一次更新中写入 is_public=2 与 audit_status=2(自动过审),
// 因此审核状态不再作为公开的前置条件,内容治理以封禁为准;这里按 is_public 统计公开/私密,
// 并把已封禁(status=2)单独成列、从公开/私密中剔除,使三者互不重叠、饼图不会重复计数。
outfitRows := []outfitDistRow{}
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Outfit{}).
Select("audit_status AS k, COUNT(*) AS cnt").
Group("audit_status").
Select("is_public, status, COUNT(*) AS cnt").
Group("is_public, status").
Scan(&outfitRows).Error; err != nil {
span.RecordError(err)
return nil, err
}
for _, r := range outfitRows {
s.OutfitTotal += r.Cnt
switch r.K {
case 1:
s.OutfitPending = r.Cnt
case 2:
s.OutfitPassed = r.Cnt
case 3:
s.OutfitRejected = r.Cnt
switch {
case r.Status == int8(2): // 已封禁
s.OutfitBanned += r.Cnt
case r.IsPublic == int8(2): // 公开
s.OutfitPublic += r.Cnt
case r.IsPublic == int8(1): // 私密
s.OutfitPrivate += r.Cnt
}
}
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Outfit{}).
Where("status = ?", int8(2)).
Count(&s.OutfitBanned).Error; err != nil {
span.RecordError(err)
return nil, err
}
// 衣物:image_process_status 分布(1/2/3 处理中,4 成功,5 失败)
clothesRows := []countByInt8{}