first commit
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AdminDao 管理员账号数据访问
|
||||
type AdminDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewAdminDao 创建管理员 DAO
|
||||
func NewAdminDao() *AdminDao {
|
||||
return &AdminDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// AdminGetByID 按主键 ID 查询管理员
|
||||
func (d *AdminDao) AdminGetByID(ctx context.Context, id int64) (*mysqlmodel.AdminUser, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.AdminGetByID")
|
||||
defer span.End()
|
||||
|
||||
var admin mysqlmodel.AdminUser
|
||||
if err := d.db.WithContext(ctx).First(&admin, id).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &admin, nil
|
||||
}
|
||||
|
||||
// AdminGetByUsername 按登录账号查询管理员(含软删除)
|
||||
func (d *AdminDao) AdminGetByUsername(ctx context.Context, username string) (*mysqlmodel.AdminUser, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.AdminGetByUsername")
|
||||
defer span.End()
|
||||
|
||||
var admin mysqlmodel.AdminUser
|
||||
if err := d.db.WithContext(ctx).Where("username = ?", username).First(&admin).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &admin, nil
|
||||
}
|
||||
|
||||
// AdminList 分页查询管理员列表(status 为 0 不筛选)
|
||||
func (d *AdminDao) AdminList(ctx context.Context, status int8, page, pageSize int) ([]mysqlmodel.AdminUser, int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.AdminList")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.AdminUser
|
||||
var total int64
|
||||
q := d.db.WithContext(ctx).Model(&mysqlmodel.AdminUser{})
|
||||
if status != 0 {
|
||||
q = q.Where("status = ?", status)
|
||||
}
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := q.Offset((page - 1) * pageSize).Limit(pageSize).Order("id DESC").Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
// AdminCreate 创建管理员账号
|
||||
func (d *AdminDao) AdminCreate(ctx context.Context, admin *mysqlmodel.AdminUser) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.AdminCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(admin).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AdminUpdate 按主键更新管理员字段
|
||||
func (d *AdminDao) AdminUpdate(ctx context.Context, id int64, updates map[string]any) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.AdminUpdate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.AdminUser{}).Where("id = ?", id).Updates(updates).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AdminSoftDelete 软删除管理员账号
|
||||
func (d *AdminDao) AdminSoftDelete(ctx context.Context, id int64) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.AdminSoftDelete")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Where("id = ?", id).Delete(&mysqlmodel.AdminUser{}).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ClosetShareDao 衣柜分享数据访问
|
||||
type ClosetShareDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewClosetShareDao 创建衣柜分享 DAO
|
||||
func NewClosetShareDao() *ClosetShareDao {
|
||||
return &ClosetShareDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// ClosetShareListParams 分享分页列表筛选参数。
|
||||
type ClosetShareListParams struct {
|
||||
UserID int64 // 用户 ID,0 表示全部
|
||||
Status int8 // 分享状态,0 表示全部
|
||||
Page int
|
||||
Size int
|
||||
}
|
||||
|
||||
// ClosetShareList 分页查询衣柜分享
|
||||
func (d *ClosetShareDao) ClosetShareList(ctx context.Context, p ClosetShareListParams) ([]mysqlmodel.ClosetShare, int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClosetShareList")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.ClosetShare
|
||||
var total int64
|
||||
q := d.db.WithContext(ctx).Model(&mysqlmodel.ClosetShare{})
|
||||
if p.UserID != 0 {
|
||||
q = q.Where("user_id = ?", p.UserID)
|
||||
}
|
||||
if p.Status != 0 {
|
||||
q = q.Where("status = ?", p.Status)
|
||||
}
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := q.Offset((p.Page - 1) * p.Size).Limit(p.Size).Order("id DESC").Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
// ClosetShareGetByUUID 按分享 UUID 查询
|
||||
func (d *ClosetShareDao) ClosetShareGetByUUID(ctx context.Context, shareUUID string) (*mysqlmodel.ClosetShare, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClosetShareGetByUUID")
|
||||
defer span.End()
|
||||
|
||||
var share mysqlmodel.ClosetShare
|
||||
if err := d.db.WithContext(ctx).Where("share_uuid = ?", shareUUID).First(&share).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &share, nil
|
||||
}
|
||||
|
||||
// ClosetShareUpdateStatus 更新分享状态(封禁/解封)
|
||||
func (d *ClosetShareDao) ClosetShareUpdateStatus(ctx context.Context, shareUUID string, status int8) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClosetShareUpdateStatus")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.ClosetShare{}).
|
||||
Where("share_uuid = ?", shareUUID).
|
||||
UpdateColumn("status", status).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ClothesDao 衣物数据访问
|
||||
type ClothesDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewClothesDao 创建衣物 DAO
|
||||
func NewClothesDao() *ClothesDao {
|
||||
return &ClothesDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// ClothesListParams 衣物分页列表筛选参数。
|
||||
type ClothesListParams struct {
|
||||
UserID int64 // 用户 ID,0 表示全部
|
||||
ImageProcessStatus int8 // AI 处理状态,0 表示全部
|
||||
CategoryMain string // 大类筛选
|
||||
Keyword string // 模糊匹配名称
|
||||
Page int
|
||||
Size int
|
||||
}
|
||||
|
||||
// ClothesList 分页查询衣物
|
||||
func (d *ClothesDao) ClothesList(ctx context.Context, p ClothesListParams) ([]mysqlmodel.Clothes, int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClothesList")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.Clothes
|
||||
var total int64
|
||||
q := d.db.WithContext(ctx).Model(&mysqlmodel.Clothes{})
|
||||
if p.UserID != 0 {
|
||||
q = q.Where("user_id = ?", p.UserID)
|
||||
}
|
||||
if p.ImageProcessStatus != 0 {
|
||||
q = q.Where("image_process_status = ?", p.ImageProcessStatus)
|
||||
}
|
||||
if p.CategoryMain != "" {
|
||||
q = q.Where("category_main = ?", p.CategoryMain)
|
||||
}
|
||||
if p.Keyword != "" {
|
||||
q = q.Where("name LIKE ?", "%"+p.Keyword+"%")
|
||||
}
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := q.Offset((p.Page - 1) * p.Size).Limit(p.Size).Order("id DESC").Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
// ClothesGetByUUID 按衣物 UUID 查询
|
||||
func (d *ClothesDao) ClothesGetByUUID(ctx context.Context, clothesUUID string) (*mysqlmodel.Clothes, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClothesGetByUUID")
|
||||
defer span.End()
|
||||
|
||||
var clothes mysqlmodel.Clothes
|
||||
if err := d.db.WithContext(ctx).Where("clothes_uuid = ?", clothesUUID).First(&clothes).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &clothes, nil
|
||||
}
|
||||
|
||||
// ClothesListByIDs 按主键 ID 批量查询衣物(穿搭详情聚合用)
|
||||
func (d *ClothesDao) ClothesListByIDs(ctx context.Context, ids []int64) ([]mysqlmodel.Clothes, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClothesListByIDs")
|
||||
defer span.End()
|
||||
|
||||
if len(ids) == 0 {
|
||||
return []mysqlmodel.Clothes{}, nil
|
||||
}
|
||||
var list []mysqlmodel.Clothes
|
||||
if err := d.db.WithContext(ctx).Where("id IN ?", ids).Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// genFlowUUID 生成流水 UUID(人工调整流水 order_no 兜底用)。
|
||||
func genFlowUUID() string {
|
||||
return uuid.NewString()
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GoodsDao 商品数据访问
|
||||
type GoodsDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewGoodsDao 创建商品 DAO
|
||||
func NewGoodsDao() *GoodsDao {
|
||||
return &GoodsDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// GoodsListParams 商品分页列表筛选参数。
|
||||
type GoodsListParams struct {
|
||||
GoodsType int8 // 商品类型筛选,0 表示全部
|
||||
Status int8 // 状态筛选,0 表示全部(含下架)
|
||||
Keyword string // 模糊匹配商品名称/编码
|
||||
Page int
|
||||
Size int
|
||||
}
|
||||
|
||||
// GoodsList 分页查询商品(管理端含下架商品)
|
||||
func (d *GoodsDao) GoodsList(ctx context.Context, p GoodsListParams) ([]mysqlmodel.Goods, int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.GoodsList")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.Goods
|
||||
var total int64
|
||||
q := d.db.WithContext(ctx).Model(&mysqlmodel.Goods{})
|
||||
if p.GoodsType != 0 {
|
||||
q = q.Where("goods_type = ?", p.GoodsType)
|
||||
}
|
||||
if p.Status != 0 {
|
||||
q = q.Where("status = ?", p.Status)
|
||||
}
|
||||
if p.Keyword != "" {
|
||||
kw := "%" + p.Keyword + "%"
|
||||
q = q.Where("goods_name LIKE ? OR goods_code LIKE ?", kw, kw)
|
||||
}
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := q.Offset((p.Page - 1) * p.Size).Limit(p.Size).Order("id DESC").Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
// GoodsGetByID 按主键 ID 查询商品
|
||||
func (d *GoodsDao) GoodsGetByID(ctx context.Context, id int64) (*mysqlmodel.Goods, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.GoodsGetByID")
|
||||
defer span.End()
|
||||
|
||||
var goods mysqlmodel.Goods
|
||||
if err := d.db.WithContext(ctx).First(&goods, id).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &goods, nil
|
||||
}
|
||||
|
||||
// GoodsGetByCode 按商品编码查询商品(含软删,用于编码唯一性校验)
|
||||
func (d *GoodsDao) GoodsGetByCode(ctx context.Context, code string) (*mysqlmodel.Goods, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.GoodsGetByCode")
|
||||
defer span.End()
|
||||
|
||||
var goods mysqlmodel.Goods
|
||||
if err := d.db.WithContext(ctx).Unscoped().Where("goods_code = ?", code).First(&goods).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &goods, nil
|
||||
}
|
||||
|
||||
// GoodsCreate 创建商品
|
||||
func (d *GoodsDao) GoodsCreate(ctx context.Context, goods *mysqlmodel.Goods) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.GoodsCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(goods).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GoodsUpdate 按主键更新商品字段
|
||||
func (d *GoodsDao) GoodsUpdate(ctx context.Context, id int64, updates map[string]any) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.GoodsUpdate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Goods{}).Where("id = ?", id).Updates(updates).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GoodsSoftDelete 软删除商品
|
||||
func (d *GoodsDao) GoodsSoftDelete(ctx context.Context, id int64) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.GoodsSoftDelete")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Where("id = ?", id).Delete(&mysqlmodel.Goods{}).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// NotificationDao 通知数据访问
|
||||
type NotificationDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewNotificationDao 创建通知 DAO
|
||||
func NewNotificationDao() *NotificationDao {
|
||||
return &NotificationDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// NotificationBatchCreate 批量插入通知(单用户/分片广播共用)
|
||||
func (d *NotificationDao) NotificationBatchCreate(ctx context.Context, notifications []mysqlmodel.UserNotification) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.NotificationBatchCreate")
|
||||
defer span.End()
|
||||
|
||||
if len(notifications) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := d.db.WithContext(ctx).CreateInBatches(notifications, 500).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// OrderDao 订单数据访问
|
||||
type OrderDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewOrderDao 创建订单 DAO
|
||||
func NewOrderDao() *OrderDao {
|
||||
return &OrderDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// OrderListParams 订单分页列表筛选参数。
|
||||
type OrderListParams struct {
|
||||
OrderNo string // 订单号(精确)
|
||||
UserID int64 // 用户 ID,0 表示全部
|
||||
Status int8 // 订单状态,0 表示全部
|
||||
Page int
|
||||
Size int
|
||||
}
|
||||
|
||||
// OrderList 分页查询订单
|
||||
func (d *OrderDao) OrderList(ctx context.Context, p OrderListParams) ([]mysqlmodel.Order, int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OrderList")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.Order
|
||||
var total int64
|
||||
q := d.db.WithContext(ctx).Model(&mysqlmodel.Order{})
|
||||
if p.OrderNo != "" {
|
||||
q = q.Where("order_no = ?", p.OrderNo)
|
||||
}
|
||||
if p.UserID != 0 {
|
||||
q = q.Where("user_id = ?", p.UserID)
|
||||
}
|
||||
if p.Status != 0 {
|
||||
q = q.Where("order_status = ?", p.Status)
|
||||
}
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := q.Offset((p.Page - 1) * p.Size).Limit(p.Size).Order("id DESC").Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
// OrderGetByOrderNo 按订单号查询订单
|
||||
func (d *OrderDao) OrderGetByOrderNo(ctx context.Context, orderNo string) (*mysqlmodel.Order, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OrderGetByOrderNo")
|
||||
defer span.End()
|
||||
|
||||
var order mysqlmodel.Order
|
||||
if err := d.db.WithContext(ctx).Where("order_no = ?", orderNo).First(&order).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &order, nil
|
||||
}
|
||||
|
||||
// OrderGetByID 按主键 ID 查询订单
|
||||
func (d *OrderDao) OrderGetByID(ctx context.Context, id int64) (*mysqlmodel.Order, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OrderGetByID")
|
||||
defer span.End()
|
||||
|
||||
var order mysqlmodel.Order
|
||||
if err := d.db.WithContext(ctx).First(&order, id).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &order, nil
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// OutfitDao 穿搭数据访问
|
||||
type OutfitDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewOutfitDao 创建穿搭 DAO
|
||||
func NewOutfitDao() *OutfitDao {
|
||||
return &OutfitDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// OutfitListParams 穿搭分页列表筛选参数。
|
||||
type OutfitListParams struct {
|
||||
UserID int64 // 用户 ID,0 表示全部
|
||||
IsPublic int8 // 是否公开,0 表示全部
|
||||
AuditStatus int8 // 审核状态,0 表示全部
|
||||
Status int8 // 穿搭状态,0 表示全部
|
||||
Keyword string // 模糊匹配穿搭名称
|
||||
Page int
|
||||
Size int
|
||||
}
|
||||
|
||||
// OutfitList 分页查询穿搭(管理端全量视角)
|
||||
func (d *OutfitDao) OutfitList(ctx context.Context, p OutfitListParams) ([]mysqlmodel.Outfit, int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OutfitList")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.Outfit
|
||||
var total int64
|
||||
q := d.db.WithContext(ctx).Model(&mysqlmodel.Outfit{})
|
||||
if p.UserID != 0 {
|
||||
q = q.Where("user_id = ?", p.UserID)
|
||||
}
|
||||
if p.IsPublic != 0 {
|
||||
q = q.Where("is_public = ?", p.IsPublic)
|
||||
}
|
||||
if p.AuditStatus != 0 {
|
||||
q = q.Where("audit_status = ?", p.AuditStatus)
|
||||
}
|
||||
if p.Status != 0 {
|
||||
q = q.Where("status = ?", p.Status)
|
||||
}
|
||||
if p.Keyword != "" {
|
||||
q = q.Where("outfit_name LIKE ?", "%"+p.Keyword+"%")
|
||||
}
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := q.Offset((p.Page - 1) * p.Size).Limit(p.Size).Order("id DESC").Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
// OutfitGetByUUID 按穿搭 UUID 查询
|
||||
func (d *OutfitDao) OutfitGetByUUID(ctx context.Context, outfitUUID string) (*mysqlmodel.Outfit, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OutfitGetByUUID")
|
||||
defer span.End()
|
||||
|
||||
var outfit mysqlmodel.Outfit
|
||||
if err := d.db.WithContext(ctx).Where("outfit_uuid = ?", outfitUUID).First(&outfit).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &outfit, nil
|
||||
}
|
||||
|
||||
// OutfitItemListByOutfitID 查询穿搭衣物明细
|
||||
func (d *OutfitDao) OutfitItemListByOutfitID(ctx context.Context, outfitID int64) ([]mysqlmodel.OutfitItem, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OutfitItemListByOutfitID")
|
||||
defer span.End()
|
||||
|
||||
var items []mysqlmodel.OutfitItem
|
||||
if err := d.db.WithContext(ctx).Where("outfit_id = ?", outfitID).Find(&items).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// OutfitUpdateByID 按主键更新穿搭字段
|
||||
func (d *OutfitDao) OutfitUpdateByID(ctx context.Context, id int64, updates map[string]any) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OutfitUpdateByID")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Outfit{}).Where("id = ?", id).Updates(updates).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OutfitAuditWithLog 事务内更新穿搭审核状态并写审核日志。
|
||||
func (d *OutfitDao) OutfitAuditWithLog(ctx context.Context, outfitID int64, auditResult int8, rejectReason string, log *mysqlmodel.OutfitAuditLog) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OutfitAuditWithLog")
|
||||
defer span.End()
|
||||
|
||||
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Model(&mysqlmodel.Outfit{}).
|
||||
Where("id = ?", outfitID).
|
||||
UpdateColumn("audit_status", auditResult).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
log.TargetID = outfitID
|
||||
log.AuditResult = auditResult
|
||||
log.RejectReason = rejectReason
|
||||
return tx.Create(log).Error
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// QuotaGrantDao 额度凭证数据访问(有有效期赠送额度)。
|
||||
type QuotaGrantDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewQuotaGrantDao 创建额度凭证 DAO。
|
||||
func NewQuotaGrantDao() *QuotaGrantDao {
|
||||
return &QuotaGrantDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// QuotaGrantCreate 创建额度凭证(ref_id+quota_type 唯一索引兜底幂等,冲突返回 1062)。
|
||||
func (d *QuotaGrantDao) QuotaGrantCreate(ctx context.Context, grant *mysqlmodel.QuotaGrant) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaGrantCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(grant).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// QuotaGrantListByUserID 查询用户全部凭证(未删除),按 id 倒序。
|
||||
func (d *QuotaGrantDao) QuotaGrantListByUserID(ctx context.Context, userID int64) ([]mysqlmodel.QuotaGrant, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaGrantListByUserID")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.QuotaGrant
|
||||
if err := d.db.WithContext(ctx).Where("user_id = ?", userID).Order("id DESC").Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// QuotaGrantGetByID 按主键查询凭证(未删除)。
|
||||
func (d *QuotaGrantDao) QuotaGrantGetByID(ctx context.Context, id int64) (*mysqlmodel.QuotaGrant, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaGrantGetByID")
|
||||
defer span.End()
|
||||
|
||||
var grant mysqlmodel.QuotaGrant
|
||||
if err := d.db.WithContext(ctx).First(&grant, id).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &grant, nil
|
||||
}
|
||||
|
||||
// QuotaGrantSoftDelete 软删除凭证(按 id + user_id 归属校验)。
|
||||
func (d *QuotaGrantDao) QuotaGrantSoftDelete(ctx context.Context, id, userID int64) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaGrantSoftDelete")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).
|
||||
Where("id = ? AND user_id = ?", id, userID).
|
||||
Delete(&mysqlmodel.QuotaGrant{}).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// QuotaGetByUserIDAndType 查询用户某类配额(Cos 永久额度的已用/底值)。
|
||||
func (d *QuotaGrantDao) QuotaGetByUserIDAndType(ctx context.Context, userID int64, quotaType int8) (*mysqlmodel.Quota, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaGetByUserIDAndType")
|
||||
defer span.End()
|
||||
|
||||
var quota mysqlmodel.Quota
|
||||
if err := d.db.WithContext(ctx).
|
||||
Where("user_id = ? AND quota_type = ?", userID, quotaType).
|
||||
First("a).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return "a, nil
|
||||
}
|
||||
|
||||
// UserDailyUsageGetByUserIDAndDate 查询用户某日使用计数(当日额度已用,Redis 异常时 MySQL 兜底)。
|
||||
func (d *QuotaGrantDao) UserDailyUsageGetByUserIDAndDate(ctx context.Context, userID, statDate int64) (*mysqlmodel.UserDailyUsage, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.UserDailyUsageGetByUserIDAndDate")
|
||||
defer span.End()
|
||||
|
||||
var usage mysqlmodel.UserDailyUsage
|
||||
if err := d.db.WithContext(ctx).
|
||||
Where("user_id = ? AND stat_date = ?", userID, statDate).
|
||||
First(&usage).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &usage, nil
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// RefundDao 退款单数据访问
|
||||
type RefundDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewRefundDao 创建退款 DAO
|
||||
func NewRefundDao() *RefundDao {
|
||||
return &RefundDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// RefundListParams 退款单分页列表筛选参数。
|
||||
type RefundListParams struct {
|
||||
Status int8 // 退款状态(WithStatus 为 true 时生效)
|
||||
WithStatus bool // 是否按状态筛选(退款状态 0=待处理 是合法值,需显式区分「不过滤」)
|
||||
OrderNo string // 订单号(精确)
|
||||
Page int
|
||||
Size int
|
||||
}
|
||||
|
||||
// RefundList 分页查询退款单
|
||||
func (d *RefundDao) RefundList(ctx context.Context, p RefundListParams) ([]mysqlmodel.RefundFlowLog, int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.RefundList")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.RefundFlowLog
|
||||
var total int64
|
||||
q := d.db.WithContext(ctx).Model(&mysqlmodel.RefundFlowLog{})
|
||||
if p.WithStatus {
|
||||
q = q.Where("status = ?", p.Status)
|
||||
}
|
||||
if p.OrderNo != "" {
|
||||
q = q.Where("order_no = ?", p.OrderNo)
|
||||
}
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := q.Offset((p.Page - 1) * p.Size).Limit(p.Size).Order("id DESC").Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
// RefundGetByRefundNo 按退款单号查询
|
||||
func (d *RefundDao) RefundGetByRefundNo(ctx context.Context, refundNo string) (*mysqlmodel.RefundFlowLog, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.RefundGetByRefundNo")
|
||||
defer span.End()
|
||||
|
||||
var refund mysqlmodel.RefundFlowLog
|
||||
if err := d.db.WithContext(ctx).Where("refund_no = ?", refundNo).First(&refund).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &refund, nil
|
||||
}
|
||||
|
||||
// RefundTransition 条件更新退款状态(仅 from 状态可流转到 to,行锁防并发重复审核)。
|
||||
func (d *RefundDao) RefundTransition(ctx context.Context, refundNo string, from, to int8, failReason string) (bool, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.RefundTransition")
|
||||
defer span.End()
|
||||
|
||||
updates := map[string]any{"status": to}
|
||||
if failReason != "" {
|
||||
updates["fail_reason"] = failReason
|
||||
}
|
||||
res := d.db.WithContext(ctx).Model(&mysqlmodel.RefundFlowLog{}).
|
||||
Where("refund_no = ? AND status = ?", refundNo, from).
|
||||
Updates(updates)
|
||||
if res.Error != nil {
|
||||
span.RecordError(res.Error)
|
||||
return false, res.Error
|
||||
}
|
||||
return res.RowsAffected > 0, nil
|
||||
}
|
||||
@@ -0,0 +1,518 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// StatsDao 仪表盘聚合统计数据访问
|
||||
type StatsDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewStatsDao 创建统计 DAO
|
||||
func NewStatsDao() *StatsDao {
|
||||
return &StatsDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// UserStats 用户统计。
|
||||
type UserStats struct {
|
||||
Total int64 // 用户总数
|
||||
TodayNew int64 // 今日新增
|
||||
Normal int64 // 正常
|
||||
TempBan int64 // 临时禁用
|
||||
PermBan int64 // 永久禁用
|
||||
Deactivated int64 // 已注销
|
||||
}
|
||||
|
||||
// OrderStats 订单统计。
|
||||
type OrderStats struct {
|
||||
Total int64 // 订单总数
|
||||
Pending int64 // 待支付
|
||||
Paid int64 // 已支付
|
||||
Cancelled int64 // 已取消
|
||||
Expired int64 // 已过期
|
||||
Refunded int64 // 已退款
|
||||
}
|
||||
|
||||
// RevenueStats 营收统计(单位分)。
|
||||
type RevenueStats struct {
|
||||
TotalRevenue int64 // 累计营收(已支付+已退款订单实付金额)
|
||||
TodayRevenue int64 // 今日营收(今日支付订单实付金额)
|
||||
RefundTotal int64 // 累计退款金额
|
||||
}
|
||||
|
||||
// TodoStats 待办统计。
|
||||
type TodoStats struct {
|
||||
RefundPending int64 // 待处理退款数
|
||||
OutfitPendingAudit int64 // 待审核穿搭数
|
||||
GoodsOnShelf int64 // 上架商品数
|
||||
}
|
||||
|
||||
// UserStatsGet 聚合用户总数与今日新增
|
||||
func (d *StatsDao) UserStatsGet(ctx context.Context, todayStart int64) (*UserStats, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.UserStatsGet")
|
||||
defer span.End()
|
||||
|
||||
s := &UserStats{}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.User{}).Count(&s.Total).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.User{}).Where("created_at >= ?", todayStart).Count(&s.TodayNew).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// UserStatsGetByStatus 按状态统计用户数(单次 group by)
|
||||
func (d *StatsDao) UserStatsGetByStatus(ctx context.Context) (map[int8]int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.UserStatsGetByStatus")
|
||||
defer span.End()
|
||||
|
||||
rows := []struct {
|
||||
Status int8
|
||||
Cnt int64
|
||||
}{}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.User{}).
|
||||
Select("status, COUNT(*) AS cnt").
|
||||
Group("status").
|
||||
Scan(&rows).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
m := make(map[int8]int64, len(rows))
|
||||
for _, r := range rows {
|
||||
m[r.Status] = r.Cnt
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// OrderStatsGet 聚合订单状态统计(单次 group by)
|
||||
func (d *StatsDao) OrderStatsGet(ctx context.Context) (*OrderStats, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OrderStatsGet")
|
||||
defer span.End()
|
||||
|
||||
rows := []struct {
|
||||
OrderStatus int8
|
||||
Cnt int64
|
||||
}{}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Order{}).
|
||||
Select("order_status, COUNT(*) AS cnt").
|
||||
Group("order_status").
|
||||
Scan(&rows).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
s := &OrderStats{}
|
||||
for _, r := range rows {
|
||||
s.Total += r.Cnt
|
||||
switch r.OrderStatus {
|
||||
case 1:
|
||||
s.Pending = r.Cnt
|
||||
case 2:
|
||||
s.Paid = r.Cnt
|
||||
case 3:
|
||||
s.Cancelled = r.Cnt
|
||||
case 4:
|
||||
s.Expired = r.Cnt
|
||||
case 5:
|
||||
s.Refunded = r.Cnt
|
||||
}
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// RevenueStatsGet 聚合营收统计(已支付与已退款订单均计营收;今日按支付时间统计)
|
||||
func (d *StatsDao) RevenueStatsGet(ctx context.Context, todayStart int64) (*RevenueStats, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.RevenueStatsGet")
|
||||
defer span.End()
|
||||
|
||||
s := &RevenueStats{}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Order{}).
|
||||
Where("order_status IN ?", []int8{2, 5}).
|
||||
Select("COALESCE(SUM(pay_amount), 0)").
|
||||
Scan(&s.TotalRevenue).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Order{}).
|
||||
Where("order_status IN ? AND pay_time >= ?", []int8{2, 5}, todayStart).
|
||||
Select("COALESCE(SUM(pay_amount), 0)").
|
||||
Scan(&s.TodayRevenue).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Order{}).
|
||||
Where("order_status = ?", int8(5)).
|
||||
Select("COALESCE(SUM(refund_amount), 0)").
|
||||
Scan(&s.RefundTotal).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// TodoStatsGet 聚合待办统计
|
||||
func (d *StatsDao) TodoStatsGet(ctx context.Context) (*TodoStats, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.TodoStatsGet")
|
||||
defer span.End()
|
||||
|
||||
s := &TodoStats{}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.RefundFlowLog{}).
|
||||
Where("status = ?", int8(0)).
|
||||
Count(&s.RefundPending).Error; err != nil {
|
||||
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 {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// countByInt8 通用分组计数行(小整型枚举 + 计数)。
|
||||
type countByInt8 struct {
|
||||
K int8
|
||||
Cnt int64
|
||||
}
|
||||
|
||||
// dateCount 按日分组计数行。
|
||||
type dateCount struct {
|
||||
D string
|
||||
Cnt int64
|
||||
}
|
||||
|
||||
// dateRevenue 按日分组营收行。
|
||||
type dateRevenue struct {
|
||||
D string
|
||||
Cnt int64
|
||||
Rev int64
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// VIPStats 会员统计。
|
||||
type VIPStats struct {
|
||||
MemberTotal int64
|
||||
Monthly int64
|
||||
Yearly int64
|
||||
Trial int64
|
||||
ExpiringSoon int64
|
||||
}
|
||||
|
||||
// GoodsTypeCount 商品类型数量。
|
||||
type GoodsTypeCount struct {
|
||||
Type int8
|
||||
Count int64
|
||||
}
|
||||
|
||||
// GoodsStats 商品统计。
|
||||
type GoodsStats struct {
|
||||
Total int64
|
||||
OnShelf int64
|
||||
OffShelf int64
|
||||
ByType []GoodsTypeCount
|
||||
}
|
||||
|
||||
// TrendStats 趋势原始聚合(按 YYYY-MM-DD 分组)。
|
||||
type TrendStats struct {
|
||||
NewUsers map[string]int64 // 新增用户
|
||||
Orders map[string]int64 // 已支付订单量
|
||||
Revenue map[string]int64 // 营收(分)
|
||||
}
|
||||
|
||||
// ContentStatsGet 聚合内容审核概况:穿搭/衣物/衣柜分享。
|
||||
func (d *StatsDao) ContentStatsGet(ctx context.Context) (*ContentStats, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ContentStatsGet")
|
||||
defer span.End()
|
||||
|
||||
s := &ContentStats{}
|
||||
|
||||
// 穿搭:audit_status 分布 + 封禁数
|
||||
outfitRows := []countByInt8{}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Outfit{}).
|
||||
Select("audit_status AS k, COUNT(*) AS cnt").
|
||||
Group("audit_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
|
||||
}
|
||||
}
|
||||
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{}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Clothes{}).
|
||||
Select("image_process_status AS k, COUNT(*) AS cnt").
|
||||
Group("image_process_status").
|
||||
Scan(&clothesRows).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range clothesRows {
|
||||
s.ClothesTotal += r.Cnt
|
||||
switch r.K {
|
||||
case 1, 2, 3:
|
||||
s.ClothesProcessing += r.Cnt
|
||||
case 4:
|
||||
s.ClothesSuccess = r.Cnt
|
||||
case 5:
|
||||
s.ClothesFailed = r.Cnt
|
||||
}
|
||||
}
|
||||
|
||||
// 衣柜分享:status 分布
|
||||
shareRows := []countByInt8{}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.ClosetShare{}).
|
||||
Select("status AS k, COUNT(*) AS cnt").
|
||||
Group("status").
|
||||
Scan(&shareRows).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range shareRows {
|
||||
s.ShareTotal += r.Cnt
|
||||
switch r.K {
|
||||
case 1:
|
||||
s.ShareNormal = r.Cnt
|
||||
case 2:
|
||||
s.ShareCancelled = r.Cnt
|
||||
case 3:
|
||||
s.ShareExpired = r.Cnt
|
||||
case 4:
|
||||
s.ShareBanned = r.Cnt
|
||||
}
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// VIPStatsGet 聚合会员概况(有效会员 + 即将到期)。
|
||||
func (d *StatsDao) VIPStatsGet(ctx context.Context, now, soonThreshold int64) (*VIPStats, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.VIPStatsGet")
|
||||
defer span.End()
|
||||
|
||||
s := &VIPStats{}
|
||||
activeVIP := []int8{2, 3, 4}
|
||||
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.User{}).
|
||||
Where("vip_type IN ? AND vip_expire > ?", activeVIP, now).
|
||||
Count(&s.MemberTotal).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.User{}).
|
||||
Where("vip_type = ? AND vip_expire > ?", int8(2), now).
|
||||
Count(&s.Monthly).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.User{}).
|
||||
Where("vip_type = ? AND vip_expire > ?", int8(3), now).
|
||||
Count(&s.Yearly).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.User{}).
|
||||
Where("vip_type = ? AND vip_expire > ?", int8(4), now).
|
||||
Count(&s.Trial).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.User{}).
|
||||
Where("vip_type IN ? AND vip_expire >= ? AND vip_expire <= ?", activeVIP, now, soonThreshold).
|
||||
Count(&s.ExpiringSoon).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// GoodsStatsGet 聚合商品统计(上架/下架 + 类型分布)。
|
||||
func (d *StatsDao) GoodsStatsGet(ctx context.Context) (*GoodsStats, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.GoodsStatsGet")
|
||||
defer span.End()
|
||||
|
||||
s := &GoodsStats{}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Goods{}).Count(&s.Total).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Goods{}).
|
||||
Where("status = ?", int8(1)).
|
||||
Count(&s.OnShelf).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Goods{}).
|
||||
Where("status = ?", int8(2)).
|
||||
Count(&s.OffShelf).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
rows := []countByInt8{}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Goods{}).
|
||||
Select("goods_type AS k, COUNT(*) AS cnt").
|
||||
Group("goods_type").
|
||||
Scan(&rows).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range rows {
|
||||
s.ByType = append(s.ByType, GoodsTypeCount{Type: r.K, Count: r.Cnt})
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// TrendStatsGet 聚合近 N 天趋势(新增用户/已支付订单量/营收,按天分组)。
|
||||
func (d *StatsDao) TrendStatsGet(ctx context.Context, startTs int64) (*TrendStats, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.TrendStatsGet")
|
||||
defer span.End()
|
||||
|
||||
s := &TrendStats{
|
||||
NewUsers: map[string]int64{},
|
||||
Orders: map[string]int64{},
|
||||
Revenue: map[string]int64{},
|
||||
}
|
||||
|
||||
// 新增用户:按创建时间
|
||||
userRows := []dateCount{}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.User{}).
|
||||
Where("created_at >= ?", startTs).
|
||||
Select("DATE_FORMAT(FROM_UNIXTIME(created_at), '%Y-%m-%d') AS d, COUNT(*) AS cnt").
|
||||
Group("d").
|
||||
Scan(&userRows).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range userRows {
|
||||
s.NewUsers[r.D] = r.Cnt
|
||||
}
|
||||
|
||||
// 已支付/已退款订单量 + 营收:按支付时间
|
||||
orderRows := []dateRevenue{}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Order{}).
|
||||
Where("order_status IN ? AND pay_time >= ?", []int8{2, 5}, startTs).
|
||||
Select("DATE_FORMAT(FROM_UNIXTIME(pay_time), '%Y-%m-%d') AS d, COUNT(*) AS cnt, COALESCE(SUM(pay_amount), 0) AS rev").
|
||||
Group("d").
|
||||
Scan(&orderRows).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range orderRows {
|
||||
s.Orders[r.D] = r.Cnt
|
||||
s.Revenue[r.D] = r.Rev
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// UsageStats 今日消耗统计。
|
||||
type UsageStats struct {
|
||||
AIRecommend int64 // 今日AI推荐消耗次数
|
||||
ClosetShare int64 // 今日衣柜分享消耗次数
|
||||
OutfitShare int64 // 今日社区分享消耗次数
|
||||
}
|
||||
|
||||
// SocialStats 内容互动统计。
|
||||
type SocialStats struct {
|
||||
CosImageCount int64 // COS总图数
|
||||
LikeCount int64 // 点赞数
|
||||
CollectCount int64 // 收藏数
|
||||
}
|
||||
|
||||
// UsageStatsGet 聚合今日消耗次数(AI推荐/衣柜分享/社区分享,按 stat_date 单次求和)。
|
||||
func (d *StatsDao) UsageStatsGet(ctx context.Context, statDate int64) (*UsageStats, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.UsageStatsGet")
|
||||
defer span.End()
|
||||
|
||||
s := &UsageStats{}
|
||||
row := struct {
|
||||
AIRecommend int64
|
||||
ClosetShare int64
|
||||
OutfitShare int64
|
||||
}{}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.UserDailyUsage{}).
|
||||
Where("stat_date = ?", statDate).
|
||||
Select("COALESCE(SUM(ai_recommend_count), 0) AS ai_recommend, COALESCE(SUM(closet_share_count), 0) AS closet_share, COALESCE(SUM(outfit_share_count), 0) AS outfit_share").
|
||||
Scan(&row).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
s.AIRecommend = row.AIRecommend
|
||||
s.ClosetShare = row.ClosetShare
|
||||
s.OutfitShare = row.OutfitShare
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// SocialStatsGet 聚合内容互动统计(COS 总图数/点赞数/收藏数)。
|
||||
func (d *StatsDao) SocialStatsGet(ctx context.Context) (*SocialStats, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.SocialStatsGet")
|
||||
defer span.End()
|
||||
|
||||
s := &SocialStats{}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Clothes{}).
|
||||
Where("processed_image <> ''").
|
||||
Count(&s.CosImageCount).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Like{}).
|
||||
Where("status = ?", int8(1)).
|
||||
Count(&s.LikeCount).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Collect{}).
|
||||
Where("status = ?", int8(1)).
|
||||
Count(&s.CollectCount).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UserDao 用户数据访问
|
||||
type UserDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewUserDao 创建用户 DAO
|
||||
func NewUserDao() *UserDao {
|
||||
return &UserDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// UserListParams 用户分页列表筛选参数。
|
||||
type UserListParams struct {
|
||||
Keyword string // 模糊匹配昵称/手机号/OpenID(任一命中)
|
||||
Status int8 // 状态筛选,0 表示全部
|
||||
Page int
|
||||
Size int
|
||||
}
|
||||
|
||||
// UserList 分页查询用户(关键词 + 状态筛选)
|
||||
func (d *UserDao) UserList(ctx context.Context, p UserListParams) ([]mysqlmodel.User, int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.UserList")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.User
|
||||
var total int64
|
||||
q := d.db.WithContext(ctx).Model(&mysqlmodel.User{})
|
||||
if p.Keyword != "" {
|
||||
kw := "%" + p.Keyword + "%"
|
||||
q = q.Where("username LIKE ? OR phone LIKE ? OR wx_open_id LIKE ?", kw, kw, kw)
|
||||
}
|
||||
if p.Status != 0 {
|
||||
q = q.Where("status = ?", p.Status)
|
||||
}
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := q.Offset((p.Page - 1) * p.Size).Limit(p.Size).Order("id DESC").Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
// UserGetByID 按主键 ID 查询用户
|
||||
func (d *UserDao) UserGetByID(ctx context.Context, userID int64) (*mysqlmodel.User, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.UserGetByID")
|
||||
defer span.End()
|
||||
|
||||
var user mysqlmodel.User
|
||||
if err := d.db.WithContext(ctx).First(&user, userID).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// UserGetByUUID 按 UUID 查询用户
|
||||
func (d *UserDao) UserGetByUUID(ctx context.Context, userUUID string) (*mysqlmodel.User, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.UserGetByUUID")
|
||||
defer span.End()
|
||||
|
||||
var user mysqlmodel.User
|
||||
if err := d.db.WithContext(ctx).Where("user_uuid = ?", userUUID).First(&user).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// UserUpdateByID 按主键 ID 更新用户字段
|
||||
func (d *UserDao) UserUpdateByID(ctx context.Context, userID int64, updates map[string]any) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.UserUpdateByID")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.User{}).Where("id = ?", userID).Updates(updates).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// QuotaListByUserID 查询用户的所有配额
|
||||
func (d *UserDao) QuotaListByUserID(ctx context.Context, userID int64) ([]mysqlmodel.Quota, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaListByUserID")
|
||||
defer span.End()
|
||||
|
||||
var quotas []mysqlmodel.Quota
|
||||
if err := d.db.WithContext(ctx).Where("user_id = ?", userID).Find("as).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return quotas, nil
|
||||
}
|
||||
|
||||
// VIPAdjustWithFlow 事务内人工调整用户 VIP 并写流水。
|
||||
func (d *UserDao) VIPAdjustWithFlow(ctx context.Context, user *mysqlmodel.User, flow *mysqlmodel.VIPFlowLog) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.VIPAdjustWithFlow")
|
||||
defer span.End()
|
||||
|
||||
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Model(&mysqlmodel.User{}).
|
||||
Where("id = ?", user.ID).
|
||||
Updates(map[string]any{
|
||||
"vip_type": user.VIPType,
|
||||
"vip_start": user.VIPStart,
|
||||
"vip_expire": user.VIPExpire,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 人工调整无订单号,用流水 UUID 填充 order_no 满足联合唯一索引。
|
||||
if flow.OrderNo == "" {
|
||||
flow.OrderNo = genFlowUUID()
|
||||
}
|
||||
return tx.Create(flow).Error
|
||||
})
|
||||
}
|
||||
|
||||
// UserIDListAll 分批遍历全部用户 ID(通知广播用)。
|
||||
func (d *UserDao) UserIDListAll(ctx context.Context, lastID int64, limit int) ([]int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.UserIDListAll")
|
||||
defer span.End()
|
||||
|
||||
var ids []int64
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.User{}).
|
||||
Where("id > ?", lastID).
|
||||
Order("id ASC").
|
||||
Limit(limit).
|
||||
Pluck("id", &ids).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
Reference in New Issue
Block a user