update
This commit is contained in:
172
internal/repository/admin_repository.go
Normal file
172
internal/repository/admin_repository.go
Normal file
@ -0,0 +1,172 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AdminRecordCard 是「文章管理」混合列表的统一卡片:跨 brand_runways 与 street_snaps
|
||||
// 两张表,字段取两者并集的最小可读子集(类型由 Kind 区分)。
|
||||
//
|
||||
// 为什么是 UNION 而非分别查两张表再在 Go 里拼:混合列表需要按(待审优先 + id 倒序)
|
||||
// 做跨表统一排序与分页,分别查两表再分页会得到错误的边界;一条 UNION 查询在数据库侧
|
||||
// 完成排序与 LIMIT/OFFSET,分页语义才正确,也避免把整张表拉进内存。
|
||||
type AdminRecordCard struct {
|
||||
Kind string // "runway" | "street"
|
||||
ID uint32 // 内部数值主键(供通过/驳回/批量接口使用)
|
||||
Title string
|
||||
Subtitle string // 走秀=品牌名;街拍=城市
|
||||
ImageCount int
|
||||
Status string // pending / published / rejected
|
||||
}
|
||||
|
||||
// DupStat 某记录的图片去重统计(仅统计 is_deleted=0 的存活行)。
|
||||
type DupStat struct {
|
||||
Total int // 存活图片总数(含主图与细节图)
|
||||
Dup int // is_duplicate=1 的张数
|
||||
}
|
||||
|
||||
// AdminRepository 文章管理页所需的跨表只读查询。
|
||||
type AdminRepository interface {
|
||||
// ListMixed 按状态 + 类型过滤(status 为空表示全部;kind 为空表示走秀+街拍,
|
||||
// "runway"/"street" 则只取对应一类),跨表统一排序后分页返回。
|
||||
// 排序:pending 优先,其次 id 倒序(最新待审排最前)。
|
||||
ListMixed(ctx context.Context, status, kind string, page, size int) ([]AdminRecordCard, int64, error)
|
||||
// CountMixed 统计某状态 + 类型下的记录总数(用于 tab 角标)。
|
||||
CountMixed(ctx context.Context, status, kind string) (int64, error)
|
||||
// DupRateByRecords 批量统计若干记录的图片去重情况(仅存活行)。
|
||||
// 返回 owner id → {Total 存活图片总数, Dup is_duplicate=1 张数};不存在的 id 不出现在 map 中。
|
||||
DupRateByRecords(ctx context.Context, kind string, ids []uint32) (map[uint32]DupStat, error)
|
||||
}
|
||||
|
||||
type adminRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewAdminRepository 创建文章管理仓储。
|
||||
func NewAdminRepository(db *gorm.DB) AdminRepository {
|
||||
return &adminRepository{db: db}
|
||||
}
|
||||
|
||||
// legWhere 单条腿(带表别名)的过滤条件:is_deleted=0 是后台可见性的硬约束,
|
||||
// status 过滤可选。别名必须显式指定——两表 LEFT JOIN 时 is_deleted 会歧义。
|
||||
func legWhere(alias, status string) (string, []interface{}) {
|
||||
where := alias + ".is_deleted = 0"
|
||||
args := []interface{}{}
|
||||
if status != "" {
|
||||
where += " AND " + alias + ".status = ?"
|
||||
args = append(args, status)
|
||||
}
|
||||
return where, args
|
||||
}
|
||||
|
||||
func (r *adminRepository) ListMixed(ctx context.Context, status, kind string, page, size int) ([]AdminRecordCard, int64, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if size < 1 {
|
||||
size = 24
|
||||
}
|
||||
rw, rargs := legWhere("r", status)
|
||||
sw, sargs := legWhere("s", status)
|
||||
|
||||
// kind 决定本次跨表 UNION 取哪几腿:""=两表都取,"street"=只取街拍,"runway"=只取走秀。
|
||||
var legs []string
|
||||
var args []interface{}
|
||||
if kind != "street" {
|
||||
legs = append(legs, `
|
||||
SELECT 'runway' AS kind, r.id, COALESCE(r.title_en, '') AS title, COALESCE(b.name_en, '') AS subtitle,
|
||||
CAST(r.image_count AS BIGINT) AS image_count, r.status
|
||||
FROM brand_runways r
|
||||
LEFT JOIN brands b ON b.id = r.brand_id
|
||||
WHERE `+rw)
|
||||
args = append(args, rargs...)
|
||||
}
|
||||
if kind != "runway" {
|
||||
legs = append(legs, `
|
||||
SELECT 'street' AS kind, s.id, COALESCE(s.title, '') AS title, COALESCE(s.city, '') AS subtitle,
|
||||
CAST(s.image_count AS BIGINT) AS image_count, s.status
|
||||
FROM street_snaps s
|
||||
WHERE `+sw)
|
||||
args = append(args, sargs...)
|
||||
}
|
||||
|
||||
q := `
|
||||
SELECT * FROM (` + strings.Join(legs, " UNION ALL ") + `) sub
|
||||
ORDER BY (status = 'pending') DESC, id DESC
|
||||
LIMIT ? OFFSET ?`
|
||||
listArgs := append(append([]interface{}{}, args...), size, (page-1)*size)
|
||||
|
||||
var cards []AdminRecordCard
|
||||
if err := r.db.WithContext(ctx).Raw(q, listArgs...).Scan(&cards).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
total, err := r.CountMixed(ctx, status, kind)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return cards, total, nil
|
||||
}
|
||||
|
||||
// DupRateByRecords 批量统计若干记录的图片去重情况。
|
||||
// kind=runway 查 brand_runway_images(外键 runway_id);kind=street 查 street_snap_images(外键 snap_id)。
|
||||
// 只统计 is_deleted=0 的存活行;命中阈值为 phash.DefaultThreshold(当前 4)。
|
||||
func (r *adminRepository) DupRateByRecords(ctx context.Context, kind string, ids []uint32) (map[uint32]DupStat, error) {
|
||||
out := make(map[uint32]DupStat)
|
||||
if len(ids) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
var table, fk string
|
||||
switch kind {
|
||||
case "runway":
|
||||
table, fk = "brand_runway_images", "runway_id"
|
||||
case "street":
|
||||
table, fk = "street_snap_images", "snap_id"
|
||||
default:
|
||||
return out, nil
|
||||
}
|
||||
type agg struct {
|
||||
Owner uint32 `gorm:"column:owner"`
|
||||
Total int `gorm:"column:total"`
|
||||
Dup int `gorm:"column:dup"`
|
||||
}
|
||||
var rows []agg
|
||||
if err := r.db.WithContext(ctx).
|
||||
Table(table).
|
||||
Select(fk + " AS owner, COUNT(*) AS total, COALESCE(SUM(CASE WHEN is_duplicate = 1 THEN 1 ELSE 0 END), 0) AS dup").
|
||||
Where(fk+" IN ? AND is_deleted = 0", ids).
|
||||
Group(fk).
|
||||
Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, row := range rows {
|
||||
out[row.Owner] = DupStat{Total: row.Total, Dup: row.Dup}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *adminRepository) CountMixed(ctx context.Context, status, kind string) (int64, error) {
|
||||
rw, rargs := legWhere("r", status)
|
||||
sw, sargs := legWhere("s", status)
|
||||
|
||||
var legs []string
|
||||
var args []interface{}
|
||||
if kind != "street" {
|
||||
legs = append(legs, `SELECT COUNT(*) AS c FROM brand_runways r WHERE `+rw)
|
||||
args = append(args, rargs...)
|
||||
}
|
||||
if kind != "runway" {
|
||||
legs = append(legs, `SELECT COUNT(*) AS c FROM street_snaps s WHERE `+sw)
|
||||
args = append(args, sargs...)
|
||||
}
|
||||
q := `
|
||||
SELECT COALESCE(SUM(c), 0) FROM (` + strings.Join(legs, " UNION ALL ") + `) t`
|
||||
var total int64
|
||||
if err := r.db.WithContext(ctx).Raw(q, args...).Scan(&total).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
Reference in New Issue
Block a user