first commit
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ClosetAuditLogDao 衣柜分享审核日志数据访问
|
||||
type ClosetAuditLogDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewClosetAuditLogDao 创建衣柜分享审核日志 DAO
|
||||
func NewClosetAuditLogDao() *ClosetAuditLogDao {
|
||||
return &ClosetAuditLogDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// ClosetAuditLogCreate 创建衣柜分享审核日志
|
||||
func (d *ClosetAuditLogDao) ClosetAuditLogCreate(ctx context.Context, log *mysqlmodel.ClosetAuditLog) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClosetAuditLogCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(log).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClosetAuditLogListByTargetID 查询目标衣柜分享的审核日志
|
||||
func (d *ClosetAuditLogDao) ClosetAuditLogListByTargetID(ctx context.Context, targetID int64) ([]mysqlmodel.ClosetAuditLog, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClosetAuditLogListByTargetID")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.ClosetAuditLog
|
||||
if err := d.db.WithContext(ctx).Where("target_id = ?", targetID).Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// OutfitAuditLogDao 穿搭审核日志数据访问
|
||||
type OutfitAuditLogDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewOutfitAuditLogDao 创建穿搭审核日志 DAO
|
||||
func NewOutfitAuditLogDao() *OutfitAuditLogDao {
|
||||
return &OutfitAuditLogDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// OutfitAuditLogCreate 创建穿搭审核日志
|
||||
func (d *OutfitAuditLogDao) OutfitAuditLogCreate(ctx context.Context, log *mysqlmodel.OutfitAuditLog) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OutfitAuditLogCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(log).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OutfitAuditLogListByTargetID 查询目标穿搭的审核日志
|
||||
func (d *OutfitAuditLogDao) OutfitAuditLogListByTargetID(ctx context.Context, targetID int64) ([]mysqlmodel.OutfitAuditLog, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OutfitAuditLogListByTargetID")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.OutfitAuditLog
|
||||
if err := d.db.WithContext(ctx).Where("target_id = ?", targetID).Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// BenefitGrantTaskDao 权益发放补偿任务数据访问
|
||||
type BenefitGrantTaskDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewBenefitGrantTaskDao 创建补偿任务 DAO
|
||||
func NewBenefitGrantTaskDao() *BenefitGrantTaskDao {
|
||||
return &BenefitGrantTaskDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// BenefitGrantTaskCreate 创建补偿任务,依赖唯一索引 (order_no, benefit_type) 防重复发放。
|
||||
func (d *BenefitGrantTaskDao) BenefitGrantTaskCreate(ctx context.Context, task *mysqlmodel.BenefitGrantTask) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.BenefitGrantTaskCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(task).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BenefitGrantTaskGetByOrderNoAndType 按订单号+权益类型查询补偿任务。
|
||||
func (d *BenefitGrantTaskDao) BenefitGrantTaskGetByOrderNoAndType(ctx context.Context, orderNo string, benefitType int8) (*mysqlmodel.BenefitGrantTask, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.BenefitGrantTaskGetByOrderNoAndType")
|
||||
defer span.End()
|
||||
|
||||
var task mysqlmodel.BenefitGrantTask
|
||||
if err := d.db.WithContext(ctx).
|
||||
Where("order_no = ? AND benefit_type = ?", orderNo, benefitType).
|
||||
First(&task).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &task, nil
|
||||
}
|
||||
|
||||
// BenefitGrantTaskUpdateByID 按主键更新补偿任务字段。
|
||||
func (d *BenefitGrantTaskDao) BenefitGrantTaskUpdateByID(ctx context.Context, id int64, updates map[string]any) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.BenefitGrantTaskUpdateByID")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.BenefitGrantTask{}).Where("id = ?", id).Updates(updates).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BenefitGrantTaskListPending 查询待发放的补偿任务(status=0),按主键升序。
|
||||
func (d *BenefitGrantTaskDao) BenefitGrantTaskListPending(ctx context.Context, limit int) ([]mysqlmodel.BenefitGrantTask, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.BenefitGrantTaskListPending")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.BenefitGrantTask
|
||||
if err := d.db.WithContext(ctx).
|
||||
Where("status = ?", 0).
|
||||
Order("id ASC").
|
||||
Limit(limit).
|
||||
Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// BenefitGrantTaskReset 将失败任务重置为待发放(首次发放遇到 failed 记录时复用)。
|
||||
// 保留 retry_count:不归零,保证重试上限与人工介入机制不被重置绕过;
|
||||
// 调用方应配合冷却期使用,避免高频回调无限重置。
|
||||
func (d *BenefitGrantTaskDao) BenefitGrantTaskReset(ctx context.Context, id int64) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.BenefitGrantTaskReset")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.BenefitGrantTask{}).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]any{"status": 0, "last_error": ""}).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BenefitGrantTaskClaim 条件认领任务(待发放/失败 -> 发放中),返回是否认领成功,防并发重复发放。
|
||||
func (d *BenefitGrantTaskDao) BenefitGrantTaskClaim(ctx context.Context, id int64) (bool, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.BenefitGrantTaskClaim")
|
||||
defer span.End()
|
||||
|
||||
res := d.db.WithContext(ctx).Model(&mysqlmodel.BenefitGrantTask{}).
|
||||
Where("id = ? AND status IN (?, ?)", id, 0, 2).
|
||||
Update("status", 3)
|
||||
if res.Error != nil {
|
||||
span.RecordError(res.Error)
|
||||
return false, res.Error
|
||||
}
|
||||
return res.RowsAffected > 0, nil
|
||||
}
|
||||
|
||||
// BenefitGrantTaskRecoverStale 将「发放中」且超过一定时间未完成的任务重置为待发放(认领后崩溃的恢复)。
|
||||
func (d *BenefitGrantTaskDao) BenefitGrantTaskRecoverStale(ctx context.Context, beforeUpdatedAt int64) (int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.BenefitGrantTaskRecoverStale")
|
||||
defer span.End()
|
||||
|
||||
res := d.db.WithContext(ctx).Model(&mysqlmodel.BenefitGrantTask{}).
|
||||
Where("status = ? AND updated_at < ?", 3, beforeUpdatedAt).
|
||||
Update("status", 0)
|
||||
if res.Error != nil {
|
||||
span.RecordError(res.Error)
|
||||
return 0, res.Error
|
||||
}
|
||||
return res.RowsAffected, nil
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// ClosetShareDao 衣柜分享数据访问
|
||||
type ClosetShareDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewClosetShareDao 创建衣柜分享 DAO
|
||||
func NewClosetShareDao() *ClosetShareDao {
|
||||
return &ClosetShareDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// ClosetShareCreate 创建分享
|
||||
func (d *ClosetShareDao) ClosetShareCreate(ctx context.Context, share *mysqlmodel.ClosetShare) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClosetShareCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(share).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// ClosetShareListByUserID 分页查询我的分享(status 为 0 不筛选)
|
||||
func (d *ClosetShareDao) ClosetShareListByUserID(ctx context.Context, userID int64, status int8, page, pageSize int) ([]mysqlmodel.ClosetShare, int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClosetShareListByUserID")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.ClosetShare
|
||||
var total int64
|
||||
q := d.db.WithContext(ctx).Model(&mysqlmodel.ClosetShare{}).Where("user_id = ?", userID)
|
||||
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
|
||||
}
|
||||
|
||||
// ClosetShareItemDao 分享明细数据访问
|
||||
type ClosetShareItemDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewClosetShareItemDao 创建分享明细 DAO
|
||||
func NewClosetShareItemDao() *ClosetShareItemDao {
|
||||
return &ClosetShareItemDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// ClosetShareItemCreateBatch 批量创建分享明细
|
||||
func (d *ClosetShareItemDao) ClosetShareItemCreateBatch(ctx context.Context, items []mysqlmodel.ClosetShareItem) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClosetShareItemCreateBatch")
|
||||
defer span.End()
|
||||
|
||||
if len(items) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := d.db.WithContext(ctx).Create(&items).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClosetShareItemDeleteByShareUUID 删除分享明细
|
||||
func (d *ClosetShareItemDao) ClosetShareItemDeleteByShareUUID(ctx context.Context, shareUUID string) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClosetShareItemDeleteByShareUUID")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Where("share_uuid = ?", shareUUID).Delete(&mysqlmodel.ClosetShareItem{}).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClosetShareItemListByShareUUID 查询分享明细
|
||||
func (d *ClosetShareItemDao) ClosetShareItemListByShareUUID(ctx context.Context, shareUUID string) ([]mysqlmodel.ClosetShareItem, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClosetShareItemListByShareUUID")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.ClosetShareItem
|
||||
if err := d.db.WithContext(ctx).Where("share_uuid = ?", shareUUID).Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// ClosetShareItemListByShareUUIDs 批量查询分享明细
|
||||
func (d *ClosetShareItemDao) ClosetShareItemListByShareUUIDs(ctx context.Context, shareUUIDs []string) ([]mysqlmodel.ClosetShareItem, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClosetShareItemListByShareUUIDs")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.ClosetShareItem
|
||||
if len(shareUUIDs) == 0 {
|
||||
return list, nil
|
||||
}
|
||||
if err := d.db.WithContext(ctx).Where("share_uuid IN ?", shareUUIDs).Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// ClosetShareReceiverDao 衣柜分享接收人数据访问
|
||||
type ClosetShareReceiverDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewClosetShareReceiverDao 创建衣柜分享接收人 DAO
|
||||
func NewClosetShareReceiverDao() *ClosetShareReceiverDao {
|
||||
return &ClosetShareReceiverDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// ClosetShareReceiverUpsert 幂等记录接收关系:唯一索引冲突时不做任何事,避免重复记录。
|
||||
func (d *ClosetShareReceiverDao) ClosetShareReceiverUpsert(ctx context.Context, shareUUID string, receiverUserID int64) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClosetShareReceiverUpsert")
|
||||
defer span.End()
|
||||
|
||||
receiver := &mysqlmodel.ClosetShareReceiver{
|
||||
ShareUUID: shareUUID,
|
||||
ReceiverUserID: receiverUserID,
|
||||
}
|
||||
if err := d.db.WithContext(ctx).Clauses(clause.OnConflict{DoNothing: true}).Create(receiver).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClosetShareReceivedList 联表分页查询某接收人收到的分享(status 为 0 不筛选)。
|
||||
func (d *ClosetShareReceiverDao) ClosetShareReceivedList(ctx context.Context, receiverUserID int64, status int8, page, pageSize int) ([]mysqlmodel.ClosetShare, int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClosetShareReceivedList")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.ClosetShare
|
||||
var total int64
|
||||
|
||||
q := d.db.WithContext(ctx).
|
||||
Model(&mysqlmodel.ClosetShare{}).
|
||||
Joins("JOIN closet_share_receivers r ON r.share_uuid = closet_shares.share_uuid").
|
||||
Where("r.receiver_user_id = ?", receiverUserID)
|
||||
if status != 0 {
|
||||
q = q.Where("closet_shares.status = ?", status)
|
||||
}
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := q.Select("closet_shares.*").
|
||||
Order("r.id DESC").
|
||||
Offset((page - 1) * pageSize).
|
||||
Limit(pageSize).
|
||||
Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return list, total, nil
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ClothesDao 衣物数据访问
|
||||
type ClothesDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewClothesDao 创建衣物 DAO
|
||||
func NewClothesDao() *ClothesDao {
|
||||
return &ClothesDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// ClothesCreate 创建衣物
|
||||
func (d *ClothesDao) ClothesCreate(ctx context.Context, clothes *mysqlmodel.Clothes) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClothesCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(clothes).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClothesDeleteByUUID 按 UUID 软删除衣物
|
||||
func (d *ClothesDao) ClothesDeleteByUUID(ctx context.Context, clothesUUID string) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClothesDeleteByUUID")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Where("clothes_uuid = ?", clothesUUID).Delete(&mysqlmodel.Clothes{}).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClothesUpdateByUUID 按 UUID 更新衣物字段
|
||||
func (d *ClothesDao) ClothesUpdateByUUID(ctx context.Context, clothesUUID string, updates map[string]any) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClothesUpdateByUUID")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Clothes{}).Where("clothes_uuid = ?", clothesUUID).Updates(updates).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return 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
|
||||
}
|
||||
|
||||
// ClothesGetByID 按主键 ID 查询衣物
|
||||
func (d *ClothesDao) ClothesGetByID(ctx context.Context, id int64) (*mysqlmodel.Clothes, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClothesGetByID")
|
||||
defer span.End()
|
||||
|
||||
var clothes mysqlmodel.Clothes
|
||||
if err := d.db.WithContext(ctx).First(&clothes, id).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &clothes, nil
|
||||
}
|
||||
|
||||
// ClothesGetByUUIDs 按 UUID 批量查询衣物
|
||||
func (d *ClothesDao) ClothesGetByUUIDs(ctx context.Context, uuids []string) ([]mysqlmodel.Clothes, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClothesGetByUUIDs")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.Clothes
|
||||
if len(uuids) == 0 {
|
||||
return list, nil
|
||||
}
|
||||
if err := d.db.WithContext(ctx).Where("clothes_uuid IN ?", uuids).Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// ClothesGetByIDs 按主键 ID 批量查询衣物
|
||||
func (d *ClothesDao) ClothesGetByIDs(ctx context.Context, ids []int64) ([]mysqlmodel.Clothes, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClothesGetByIDs")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.Clothes
|
||||
if len(ids) == 0 {
|
||||
return list, nil
|
||||
}
|
||||
if err := d.db.WithContext(ctx).Where("id IN ?", ids).Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// ClothesListByUserID 分页查询用户衣物(categoryMain/categorySub 为空不筛选)
|
||||
func (d *ClothesDao) ClothesListByUserID(ctx context.Context, userID int64, categoryMain, categorySub string, page, pageSize int) ([]mysqlmodel.Clothes, int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.ClothesListByUserID")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.Clothes
|
||||
var total int64
|
||||
q := d.db.WithContext(ctx).Model(&mysqlmodel.Clothes{}).Where("user_id = ?", userID)
|
||||
if categoryMain != "" {
|
||||
q = q.Where("category_main = ?", categoryMain)
|
||||
}
|
||||
if categorySub != "" {
|
||||
q = q.Where("category_sub = ?", categorySub)
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// CollectDao 收藏数据访问
|
||||
type CollectDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewCollectDao 创建收藏 DAO
|
||||
func NewCollectDao() *CollectDao {
|
||||
return &CollectDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// CollectCreate 创建收藏记录
|
||||
func (d *CollectDao) CollectCreate(ctx context.Context, collect *mysqlmodel.Collect) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.CollectCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(collect).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CollectUpsertOn 收藏幂等写入:唯一索引冲突时仅置 status=1,防并发重复插入。
|
||||
func (d *CollectDao) CollectUpsertOn(ctx context.Context, collect *mysqlmodel.Collect) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.CollectUpsertOn")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "collect_type"}, {Name: "target_id"}, {Name: "user_id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"status"}),
|
||||
}).Create(collect).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CollectUpdateStatus 更新收藏状态(1=已收藏 0=已取消)
|
||||
func (d *CollectDao) CollectUpdateStatus(ctx context.Context, id int64, status int8) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.CollectUpdateStatus")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Collect{}).Where("id = ?", id).UpdateColumn("status", status).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CollectGetByTargetAndUser 查询用户在目标上的收藏记录
|
||||
func (d *CollectDao) CollectGetByTargetAndUser(ctx context.Context, collectType int8, targetID, userID int64) (*mysqlmodel.Collect, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.CollectGetByTargetAndUser")
|
||||
defer span.End()
|
||||
|
||||
var collect mysqlmodel.Collect
|
||||
if err := d.db.WithContext(ctx).Where("collect_type = ? AND target_id = ? AND user_id = ?", collectType, targetID, userID).First(&collect).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &collect, nil
|
||||
}
|
||||
|
||||
// CollectCountByTarget 统计目标收藏数(仅 status=1)
|
||||
func (d *CollectDao) CollectCountByTarget(ctx context.Context, collectType int8, targetID int64) (int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.CollectCountByTarget")
|
||||
defer span.End()
|
||||
|
||||
var count int64
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Collect{}).
|
||||
Where("collect_type = ? AND target_id = ? AND status = ?", collectType, targetID, int8(1)).
|
||||
Count(&count).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// CollectListTargetIDsByUserPage 分页查询用户收藏的目标ID列表(仅 status=1),返回 ids 与总数。
|
||||
func (d *CollectDao) CollectListTargetIDsByUserPage(ctx context.Context, collectType int8, userID int64, page, pageSize int) ([]int64, int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.CollectListTargetIDsByUserPage")
|
||||
defer span.End()
|
||||
|
||||
q := d.db.WithContext(ctx).Model(&mysqlmodel.Collect{}).
|
||||
Where("collect_type = ? AND user_id = ? AND status = ?", collectType, userID, int8(1))
|
||||
|
||||
var total int64
|
||||
if err := q.Count(&total).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
var ids []int64
|
||||
if err := q.Order("id DESC").Offset((page - 1) * pageSize).Limit(pageSize).Pluck("target_id", &ids).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, 0, err
|
||||
}
|
||||
return ids, total, nil
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GoodsDao 商品数据访问
|
||||
type GoodsDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewGoodsDao 创建商品 DAO
|
||||
func NewGoodsDao() *GoodsDao {
|
||||
return &GoodsDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// 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).Where("goods_code = ?", code).First(&goods).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &goods, 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
|
||||
}
|
||||
|
||||
// GoodsList 分页查询上架商品(goodsType 为 0 不筛选)
|
||||
func (d *GoodsDao) GoodsList(ctx context.Context, goodsType int8, page, pageSize int) ([]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{}).Where("status = ?", int8(1))
|
||||
if goodsType != 0 {
|
||||
q = q.Where("goods_type = ?", goodsType)
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// HoroscopeDao 运势数据访问
|
||||
type HoroscopeDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewHoroscopeDao 创建运势 DAO
|
||||
func NewHoroscopeDao() *HoroscopeDao {
|
||||
return &HoroscopeDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// HoroscopeCreate 创建运势记录
|
||||
func (d *HoroscopeDao) HoroscopeCreate(ctx context.Context, horoscope *mysqlmodel.Horoscope) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.HoroscopeCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(horoscope).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HoroscopeGetByZodiacAndDate 按星座和日期查询运势(幂等)
|
||||
func (d *HoroscopeDao) HoroscopeGetByZodiacAndDate(ctx context.Context, zodiac string, statDate int64) (*mysqlmodel.Horoscope, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.HoroscopeGetByZodiacAndDate")
|
||||
defer span.End()
|
||||
|
||||
var horoscope mysqlmodel.Horoscope
|
||||
if err := d.db.WithContext(ctx).Where("zodiac = ? AND stat_date = ?", zodiac, statDate).First(&horoscope).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &horoscope, nil
|
||||
}
|
||||
|
||||
// HoroscopeGetByDate 按日期查询当日全部星座运势。
|
||||
func (d *HoroscopeDao) HoroscopeGetByDate(ctx context.Context, statDate int64) ([]mysqlmodel.Horoscope, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.HoroscopeGetByDate")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.Horoscope
|
||||
if err := d.db.WithContext(ctx).Where("stat_date = ?", statDate).Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// AIHoroscopeFlowLogDao 星座运势 AI 流水数据访问
|
||||
type AIHoroscopeFlowLogDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewAIHoroscopeFlowLogDao 创建星座运势 AI 流水 DAO
|
||||
func NewAIHoroscopeFlowLogDao() *AIHoroscopeFlowLogDao {
|
||||
return &AIHoroscopeFlowLogDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// AIHoroscopeFlowLogCreate 创建星座运势 AI 流水
|
||||
func (d *AIHoroscopeFlowLogDao) AIHoroscopeFlowLogCreate(ctx context.Context, log *mysqlmodel.AIHoroscopeFlowLog) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.AIHoroscopeFlowLogCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(log).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AIHoroscopeFlowLogGetByRequestID 按请求ID查询流水(幂等)
|
||||
func (d *AIHoroscopeFlowLogDao) AIHoroscopeFlowLogGetByRequestID(ctx context.Context, requestID string) (*mysqlmodel.AIHoroscopeFlowLog, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.AIHoroscopeFlowLogGetByRequestID")
|
||||
defer span.End()
|
||||
|
||||
var log mysqlmodel.AIHoroscopeFlowLog
|
||||
if err := d.db.WithContext(ctx).Where("request_id = ?", requestID).First(&log).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &log, nil
|
||||
}
|
||||
|
||||
// AIRecommendFlowLogDao AI 推荐流水数据访问
|
||||
type AIRecommendFlowLogDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewAIRecommendFlowLogDao 创建 AI 推荐流水 DAO
|
||||
func NewAIRecommendFlowLogDao() *AIRecommendFlowLogDao {
|
||||
return &AIRecommendFlowLogDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// AIRecommendFlowLogCreate 创建 AI 推荐流水
|
||||
func (d *AIRecommendFlowLogDao) AIRecommendFlowLogCreate(ctx context.Context, log *mysqlmodel.AIRecommendFlowLog) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.AIRecommendFlowLogCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(log).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AIRecommendFlowLogGetByRequestID 按请求ID查询流水(幂等)
|
||||
func (d *AIRecommendFlowLogDao) AIRecommendFlowLogGetByRequestID(ctx context.Context, requestID string) (*mysqlmodel.AIRecommendFlowLog, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.AIRecommendFlowLogGetByRequestID")
|
||||
defer span.End()
|
||||
|
||||
var log mysqlmodel.AIRecommendFlowLog
|
||||
if err := d.db.WithContext(ctx).Where("request_id = ?", requestID).First(&log).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &log, nil
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// LikeDao 点赞数据访问
|
||||
type LikeDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewLikeDao 创建点赞 DAO
|
||||
func NewLikeDao() *LikeDao {
|
||||
return &LikeDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// LikeCreate 创建点赞记录
|
||||
func (d *LikeDao) LikeCreate(ctx context.Context, like *mysqlmodel.Like) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.LikeCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(like).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LikeUpsertOn 点赞幂等写入:唯一索引冲突时仅置 status=1,防并发重复插入。
|
||||
func (d *LikeDao) LikeUpsertOn(ctx context.Context, like *mysqlmodel.Like) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.LikeUpsertOn")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "like_type"}, {Name: "target_id"}, {Name: "user_id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"status"}),
|
||||
}).Create(like).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LikeUpdateStatus 更新点赞状态(1=已点赞 0=已取消)
|
||||
func (d *LikeDao) LikeUpdateStatus(ctx context.Context, id int64, status int8) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.LikeUpdateStatus")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Like{}).Where("id = ?", id).UpdateColumn("status", status).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LikeGetByTargetAndUser 查询用户在目标上的点赞记录
|
||||
func (d *LikeDao) LikeGetByTargetAndUser(ctx context.Context, likeType int8, targetID, userID int64) (*mysqlmodel.Like, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.LikeGetByTargetAndUser")
|
||||
defer span.End()
|
||||
|
||||
var like mysqlmodel.Like
|
||||
if err := d.db.WithContext(ctx).Where("like_type = ? AND target_id = ? AND user_id = ?", likeType, targetID, userID).First(&like).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &like, nil
|
||||
}
|
||||
|
||||
// LikeCountByTarget 统计目标点赞数(仅 status=1)
|
||||
func (d *LikeDao) LikeCountByTarget(ctx context.Context, likeType int8, targetID int64) (int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.LikeCountByTarget")
|
||||
defer span.End()
|
||||
|
||||
var count int64
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Like{}).
|
||||
Where("like_type = ? AND target_id = ? AND status = ?", likeType, targetID, int8(1)).
|
||||
Count(&count).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// LikeListUserIDsByTarget 查询点赞用户ID列表(仅 status=1)
|
||||
func (d *LikeDao) LikeListUserIDsByTarget(ctx context.Context, likeType int8, targetID int64) ([]int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.LikeListUserIDsByTarget")
|
||||
defer span.End()
|
||||
|
||||
var ids []int64
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Like{}).
|
||||
Where("like_type = ? AND target_id = ? AND status = ?", likeType, targetID, int8(1)).
|
||||
Pluck("user_id", &ids).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// NotificationDao 用户通知数据访问
|
||||
type NotificationDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewNotificationDao 创建通知 DAO
|
||||
func NewNotificationDao() *NotificationDao {
|
||||
return &NotificationDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// NotificationCreate 创建通知
|
||||
func (d *NotificationDao) NotificationCreate(ctx context.Context, notification *mysqlmodel.UserNotification) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.NotificationCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(notification).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NotificationUpdateReadByUUID 标记通知已读
|
||||
func (d *NotificationDao) NotificationUpdateReadByUUID(ctx context.Context, notificationUUID string) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.NotificationUpdateReadByUUID")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.UserNotification{}).
|
||||
Where("notification_uuid = ?", notificationUUID).
|
||||
Update("is_read", int8(2)).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NotificationUpdateReadByUUIDIfUnread 条件标记已读(仅未读 -> 已读),返回是否更新成功,
|
||||
// 用于防并发重复扣减未读数。
|
||||
func (d *NotificationDao) NotificationUpdateReadByUUIDIfUnread(ctx context.Context, notificationUUID string) (bool, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.NotificationUpdateReadByUUIDIfUnread")
|
||||
defer span.End()
|
||||
|
||||
res := d.db.WithContext(ctx).Model(&mysqlmodel.UserNotification{}).
|
||||
Where("notification_uuid = ? AND is_read = ?", notificationUUID, int8(1)).
|
||||
Update("is_read", int8(2))
|
||||
if res.Error != nil {
|
||||
span.RecordError(res.Error)
|
||||
return false, res.Error
|
||||
}
|
||||
return res.RowsAffected > 0, nil
|
||||
}
|
||||
|
||||
// NotificationGetByUUID 按 UUID 查询通知
|
||||
func (d *NotificationDao) NotificationGetByUUID(ctx context.Context, notificationUUID string) (*mysqlmodel.UserNotification, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.NotificationGetByUUID")
|
||||
defer span.End()
|
||||
|
||||
var notification mysqlmodel.UserNotification
|
||||
if err := d.db.WithContext(ctx).Where("notification_uuid = ?", notificationUUID).First(¬ification).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return ¬ification, nil
|
||||
}
|
||||
|
||||
// NotificationCountUnread 统计用户未读数
|
||||
func (d *NotificationDao) NotificationCountUnread(ctx context.Context, userID int64) (int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.NotificationCountUnread")
|
||||
defer span.End()
|
||||
|
||||
var count int64
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.UserNotification{}).
|
||||
Where("user_id = ? AND is_read = ?", userID, int8(1)).
|
||||
Count(&count).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// NotificationListByUserID 分页查询用户通知(isRead/msgType 为 0 不筛选)
|
||||
func (d *NotificationDao) NotificationListByUserID(ctx context.Context, userID int64, isRead, msgType int8, page, pageSize int) ([]mysqlmodel.UserNotification, int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.NotificationListByUserID")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.UserNotification
|
||||
var total int64
|
||||
q := d.db.WithContext(ctx).Model(&mysqlmodel.UserNotification{}).Where("user_id = ?", userID)
|
||||
if isRead != 0 {
|
||||
q = q.Where("is_read = ?", isRead)
|
||||
}
|
||||
if msgType != 0 {
|
||||
q = q.Where("msg_type = ?", msgType)
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// OrderDao 订单数据访问
|
||||
type OrderDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewOrderDao 创建订单 DAO
|
||||
func NewOrderDao() *OrderDao {
|
||||
return &OrderDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// OrderCreate 创建订单
|
||||
func (d *OrderDao) OrderCreate(ctx context.Context, order *mysqlmodel.Order) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OrderCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(order).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OrderUpdateByOrderNo 按订单号更新订单字段
|
||||
func (d *OrderDao) OrderUpdateByOrderNo(ctx context.Context, orderNo string, updates map[string]any) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OrderUpdateByOrderNo")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Order{}).Where("order_no = ?", orderNo).Updates(updates).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OrderUpdateStatusByOrderNo 条件流转订单状态(防重复回调),返回是否更新成功
|
||||
func (d *OrderDao) OrderUpdateStatusByOrderNo(ctx context.Context, orderNo string, fromStatus, toStatus int8) (bool, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OrderUpdateStatusByOrderNo")
|
||||
defer span.End()
|
||||
|
||||
res := d.db.WithContext(ctx).Model(&mysqlmodel.Order{}).
|
||||
Where("order_no = ? AND order_status = ?", orderNo, fromStatus).
|
||||
Update("order_status", toStatus)
|
||||
if res.Error != nil {
|
||||
span.RecordError(res.Error)
|
||||
return false, res.Error
|
||||
}
|
||||
return res.RowsAffected > 0, nil
|
||||
}
|
||||
|
||||
// OrderExpireByTime 将超过支付有效期的待支付订单批量置为已过期,返回更新条数。
|
||||
// 状态字面量:1=待支付 4=已过期(与 service 层 OrderStatusPending/OrderStatusExpired 一致)。
|
||||
func (d *OrderDao) OrderExpireByTime(ctx context.Context, now int64) (int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OrderExpireByTime")
|
||||
defer span.End()
|
||||
|
||||
res := d.db.WithContext(ctx).Model(&mysqlmodel.Order{}).
|
||||
Where("order_status = ? AND expire_time < ?", 1, now).
|
||||
Update("order_status", 4)
|
||||
if res.Error != nil {
|
||||
span.RecordError(res.Error)
|
||||
return 0, res.Error
|
||||
}
|
||||
return res.RowsAffected, 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// OrderListByUserID 分页查询用户订单(status 为 0 不筛选)
|
||||
func (d *OrderDao) OrderListByUserID(ctx context.Context, userID int64, status int8, page, pageSize int) ([]mysqlmodel.Order, int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OrderListByUserID")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.Order
|
||||
var total int64
|
||||
q := d.db.WithContext(ctx).Model(&mysqlmodel.Order{}).Where("user_id = ?", userID)
|
||||
if status != 0 {
|
||||
q = q.Where("order_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
|
||||
}
|
||||
|
||||
// RefundFlowLogDao 退款流水数据访问
|
||||
type RefundFlowLogDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewRefundFlowLogDao 创建退款流水 DAO
|
||||
func NewRefundFlowLogDao() *RefundFlowLogDao {
|
||||
return &RefundFlowLogDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// RefundFlowLogCreate 创建退款流水
|
||||
func (d *RefundFlowLogDao) RefundFlowLogCreate(ctx context.Context, log *mysqlmodel.RefundFlowLog) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.RefundFlowLogCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(log).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RefundFlowLogUpdateStatus 按退款单号更新退款流水字段
|
||||
func (d *RefundFlowLogDao) RefundFlowLogUpdateStatus(ctx context.Context, refundNo string, updates map[string]any) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.RefundFlowLogUpdateStatus")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.RefundFlowLog{}).Where("refund_no = ?", refundNo).Updates(updates).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RefundFlowLogUpdateStatusByFrom 条件流转退款流水状态(防并发/乱序回调覆盖),返回是否更新成功。
|
||||
func (d *RefundFlowLogDao) RefundFlowLogUpdateStatusByFrom(ctx context.Context, refundNo string, fromStatus int8, updates map[string]any) (bool, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.RefundFlowLogUpdateStatusByFrom")
|
||||
defer span.End()
|
||||
|
||||
res := d.db.WithContext(ctx).Model(&mysqlmodel.RefundFlowLog{}).
|
||||
Where("refund_no = ? AND status = ?", refundNo, fromStatus).
|
||||
Updates(updates)
|
||||
if res.Error != nil {
|
||||
span.RecordError(res.Error)
|
||||
return false, res.Error
|
||||
}
|
||||
return res.RowsAffected > 0, nil
|
||||
}
|
||||
|
||||
// RefundFlowLogGetByRefundNo 按退款单号查询退款流水
|
||||
func (d *RefundFlowLogDao) RefundFlowLogGetByRefundNo(ctx context.Context, refundNo string) (*mysqlmodel.RefundFlowLog, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.RefundFlowLogGetByRefundNo")
|
||||
defer span.End()
|
||||
|
||||
var log mysqlmodel.RefundFlowLog
|
||||
if err := d.db.WithContext(ctx).Where("refund_no = ?", refundNo).First(&log).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &log, nil
|
||||
}
|
||||
|
||||
// RefundFlowLogGetByOrderNo 按商户订单号查询退款流水
|
||||
func (d *RefundFlowLogDao) RefundFlowLogGetByOrderNo(ctx context.Context, orderNo string) (*mysqlmodel.RefundFlowLog, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.RefundFlowLogGetByOrderNo")
|
||||
defer span.End()
|
||||
|
||||
var log mysqlmodel.RefundFlowLog
|
||||
if err := d.db.WithContext(ctx).Where("order_no = ?", orderNo).First(&log).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &log, nil
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// OutfitDao 穿搭数据访问
|
||||
type OutfitDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewOutfitDao 创建穿搭 DAO
|
||||
func NewOutfitDao() *OutfitDao {
|
||||
return &OutfitDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// OutfitCreate 创建穿搭
|
||||
func (d *OutfitDao) OutfitCreate(ctx context.Context, outfit *mysqlmodel.Outfit) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OutfitCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(outfit).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OutfitDeleteByUUID 按 UUID 软删除穿搭
|
||||
func (d *OutfitDao) OutfitDeleteByUUID(ctx context.Context, outfitUUID string) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OutfitDeleteByUUID")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Where("outfit_uuid = ?", outfitUUID).Delete(&mysqlmodel.Outfit{}).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OutfitUpdateByUUID 按 UUID 更新穿搭字段
|
||||
func (d *OutfitDao) OutfitUpdateByUUID(ctx context.Context, outfitUUID string, updates map[string]any) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OutfitUpdateByUUID")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Outfit{}).Where("outfit_uuid = ?", outfitUUID).Updates(updates).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OutfitUpdateIsPublic 设置穿搭公开状态
|
||||
func (d *OutfitDao) OutfitUpdateIsPublic(ctx context.Context, outfitUUID string, isPublic int8) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OutfitUpdateIsPublic")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.Outfit{}).Where("outfit_uuid = ?", outfitUUID).UpdateColumn("is_public", isPublic).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OutfitPublish 条件公开穿搭(仅私密 -> 公开 + 审核通过),返回是否更新成功,防并发重复消耗额度。
|
||||
// 状态字面量:is_public 1=私密 2=公开;audit_status 2=审核通过。
|
||||
func (d *OutfitDao) OutfitPublish(ctx context.Context, outfitUUID string) (bool, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OutfitPublish")
|
||||
defer span.End()
|
||||
|
||||
res := d.db.WithContext(ctx).Model(&mysqlmodel.Outfit{}).
|
||||
Where("outfit_uuid = ? AND is_public = ?", outfitUUID, int8(1)).
|
||||
Updates(map[string]any{"is_public": int8(2), "audit_status": int8(2)})
|
||||
if res.Error != nil {
|
||||
span.RecordError(res.Error)
|
||||
return false, res.Error
|
||||
}
|
||||
return res.RowsAffected > 0, 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
|
||||
}
|
||||
|
||||
// OutfitGetByID 按主键 ID 查询穿搭
|
||||
func (d *OutfitDao) OutfitGetByID(ctx context.Context, id int64) (*mysqlmodel.Outfit, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OutfitGetByID")
|
||||
defer span.End()
|
||||
|
||||
var outfit mysqlmodel.Outfit
|
||||
if err := d.db.WithContext(ctx).First(&outfit, id).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &outfit, nil
|
||||
}
|
||||
|
||||
// OutfitGetByUUIDs 按 UUID 批量查询穿搭
|
||||
func (d *OutfitDao) OutfitGetByUUIDs(ctx context.Context, uuids []string) ([]mysqlmodel.Outfit, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OutfitGetByUUIDs")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.Outfit
|
||||
if len(uuids) == 0 {
|
||||
return list, nil
|
||||
}
|
||||
if err := d.db.WithContext(ctx).Where("outfit_uuid IN ?", uuids).Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// OutfitListByUserID 分页查询用户穿搭(isPublic/source 为 0 不筛选)
|
||||
func (d *OutfitDao) OutfitListByUserID(ctx context.Context, userID int64, isPublic, source int8, page, pageSize int) ([]mysqlmodel.Outfit, int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OutfitListByUserID")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.Outfit
|
||||
var total int64
|
||||
q := d.db.WithContext(ctx).Model(&mysqlmodel.Outfit{}).Where("user_id = ?", userID)
|
||||
if isPublic != 0 {
|
||||
q = q.Where("is_public = ?", isPublic)
|
||||
}
|
||||
if source != 0 {
|
||||
q = q.Where("source = ?", source)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// OutfitItemDao 穿搭明细数据访问
|
||||
type OutfitItemDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewOutfitItemDao 创建穿搭明细 DAO
|
||||
func NewOutfitItemDao() *OutfitItemDao {
|
||||
return &OutfitItemDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// OutfitItemCreateBatch 批量创建穿搭明细
|
||||
func (d *OutfitItemDao) OutfitItemCreateBatch(ctx context.Context, items []mysqlmodel.OutfitItem) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OutfitItemCreateBatch")
|
||||
defer span.End()
|
||||
|
||||
if len(items) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := d.db.WithContext(ctx).Create(&items).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OutfitItemDeleteByOutfitID 删除穿搭明细
|
||||
func (d *OutfitItemDao) OutfitItemDeleteByOutfitID(ctx context.Context, outfitID int64) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OutfitItemDeleteByOutfitID")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Where("outfit_id = ?", outfitID).Delete(&mysqlmodel.OutfitItem{}).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OutfitItemListByOutfitID 查询穿搭明细
|
||||
func (d *OutfitItemDao) OutfitItemListByOutfitID(ctx context.Context, outfitID int64) ([]mysqlmodel.OutfitItem, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OutfitItemListByOutfitID")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.OutfitItem
|
||||
if err := d.db.WithContext(ctx).Where("outfit_id = ?", outfitID).Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// OutfitItemListByOutfitIDs 批量查询穿搭明细
|
||||
func (d *OutfitItemDao) OutfitItemListByOutfitIDs(ctx context.Context, outfitIDs []int64) ([]mysqlmodel.OutfitItem, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.OutfitItemListByOutfitIDs")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.OutfitItem
|
||||
if len(outfitIDs) == 0 {
|
||||
return list, nil
|
||||
}
|
||||
if err := d.db.WithContext(ctx).Where("outfit_id IN ?", outfitIDs).Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
@@ -0,0 +1,502 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// QuotaDao 用户配额数据访问
|
||||
type QuotaDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewQuotaDao 创建配额 DAO
|
||||
func NewQuotaDao() *QuotaDao {
|
||||
return &QuotaDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// QuotaCreate 创建配额记录
|
||||
func (d *QuotaDao) QuotaCreate(ctx context.Context, quota *mysqlmodel.Quota) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(quota).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// QuotaIncreaseUsed 原子增加已用量(扣减配额),剩余不足时返回 false。
|
||||
// 已废弃:仅改数值不写流水,存在账实不符风险;新代码请使用 QuotaConsumeWithFlow。
|
||||
func (d *QuotaDao) QuotaIncreaseUsed(ctx context.Context, userID int64, quotaType int8, n int64) (bool, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaIncreaseUsed")
|
||||
defer span.End()
|
||||
|
||||
res := d.db.WithContext(ctx).Model(&mysqlmodel.Quota{}).
|
||||
Where("user_id = ? AND quota_type = ? AND used + ? <= total", userID, quotaType, n).
|
||||
UpdateColumn("used", gorm.Expr("used + ?", n))
|
||||
if res.Error != nil {
|
||||
span.RecordError(res.Error)
|
||||
return false, res.Error
|
||||
}
|
||||
return res.RowsAffected > 0, nil
|
||||
}
|
||||
|
||||
// QuotaDecreaseUsed 原子减少已用量(回退配额),已用量不足时返回 false。
|
||||
// 已废弃:仅改数值不写流水,存在账实不符风险;新代码请使用 QuotaRefundWithFlow。
|
||||
func (d *QuotaDao) QuotaDecreaseUsed(ctx context.Context, userID int64, quotaType int8, n int64) (bool, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaDecreaseUsed")
|
||||
defer span.End()
|
||||
|
||||
res := d.db.WithContext(ctx).Model(&mysqlmodel.Quota{}).
|
||||
Where("user_id = ? AND quota_type = ? AND used >= ?", userID, quotaType, n).
|
||||
UpdateColumn("used", gorm.Expr("used - ?", n))
|
||||
if res.Error != nil {
|
||||
span.RecordError(res.Error)
|
||||
return false, res.Error
|
||||
}
|
||||
return res.RowsAffected > 0, nil
|
||||
}
|
||||
|
||||
// QuotaConsumeWithFlow 事务内扣减配额并写流水,保证数值变更与流水强一致。
|
||||
// 条件更新不满足(额度不足)返回 (false, nil);其余错误(含流水写入失败整体回滚)返回 error。
|
||||
func (d *QuotaDao) QuotaConsumeWithFlow(ctx context.Context, userID int64, quotaType int8, n int64, flow *mysqlmodel.QuotaFlowLog) (bool, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaConsumeWithFlow")
|
||||
defer span.End()
|
||||
|
||||
var ok bool
|
||||
err := d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
res := tx.Model(&mysqlmodel.Quota{}).
|
||||
Where("user_id = ? AND quota_type = ? AND used + ? <= total", userID, quotaType, n).
|
||||
UpdateColumn("used", gorm.Expr("used + ?", n))
|
||||
if res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return nil // 额度不足,无变更。
|
||||
}
|
||||
ok = true
|
||||
|
||||
// 回读真实值填充流水 old/new。
|
||||
var cur mysqlmodel.Quota
|
||||
if err := tx.Where("user_id = ? AND quota_type = ?", userID, quotaType).First(&cur).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
remain := cur.Total - cur.Used
|
||||
flow.OldValue = remain + n
|
||||
flow.NewValue = remain
|
||||
|
||||
// 写流水(失败则整个事务回滚,扣减一并撤销)。
|
||||
ensureFlowOrderNo(flow)
|
||||
return tx.Create(flow).Error
|
||||
})
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
return false, err
|
||||
}
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
// QuotaRefundWithFlow 事务内回退配额并写流水,保证数值变更与流水强一致。
|
||||
// 条件更新不满足(已用量不足/重复回退)返回 (false, nil);其余错误(含流水失败回滚)返回 error。
|
||||
func (d *QuotaDao) QuotaRefundWithFlow(ctx context.Context, userID int64, quotaType int8, n int64, flow *mysqlmodel.QuotaFlowLog) (bool, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaRefundWithFlow")
|
||||
defer span.End()
|
||||
|
||||
var ok bool
|
||||
err := d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
res := tx.Model(&mysqlmodel.Quota{}).
|
||||
Where("user_id = ? AND quota_type = ? AND used >= ?", userID, quotaType, n).
|
||||
UpdateColumn("used", gorm.Expr("used - ?", n))
|
||||
if res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return nil // 已用量不足,无变更。
|
||||
}
|
||||
ok = true
|
||||
|
||||
// 回读真实值填充流水 old/new。
|
||||
var cur mysqlmodel.Quota
|
||||
if err := tx.Where("user_id = ? AND quota_type = ?", userID, quotaType).First(&cur).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
remain := cur.Total - cur.Used
|
||||
flow.OldValue = remain - n
|
||||
flow.NewValue = remain
|
||||
|
||||
// 写流水(失败则整个事务回滚,回退一并撤销)。
|
||||
ensureFlowOrderNo(flow)
|
||||
return tx.Create(flow).Error
|
||||
})
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
return false, err
|
||||
}
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
// QuotaInitWithFlow 事务内初始化配额并写流水。
|
||||
// 依赖 uk_user_quota 唯一索引(OnConflict DoNothing)实现并发安全幂等:已存在则跳过且不写流水。
|
||||
func (d *QuotaDao) QuotaInitWithFlow(ctx context.Context, userID int64, quotaType int8, total int64, flow *mysqlmodel.QuotaFlowLog) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaInitWithFlow")
|
||||
defer span.End()
|
||||
|
||||
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
q := &mysqlmodel.Quota{
|
||||
UserID: userID,
|
||||
QuotaType: quotaType,
|
||||
Total: total,
|
||||
Used: 0,
|
||||
}
|
||||
res := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "user_id"}, {Name: "quota_type"}},
|
||||
DoNothing: true,
|
||||
}).Create(q)
|
||||
if res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return nil // 已存在,幂等跳过。
|
||||
}
|
||||
|
||||
// 回读真实值填充流水 old/new。
|
||||
var cur mysqlmodel.Quota
|
||||
if err := tx.Where("user_id = ? AND quota_type = ?", userID, quotaType).First(&cur).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
remain := cur.Total - cur.Used
|
||||
flow.OldValue = remain - total
|
||||
flow.NewValue = remain
|
||||
|
||||
// 写流水(失败则整个事务回滚,初始化一并撤销)。
|
||||
ensureFlowOrderNo(flow)
|
||||
return tx.Create(flow).Error
|
||||
})
|
||||
}
|
||||
|
||||
// ensureFlowOrderNo 流水联合唯一索引 (order_no, quota_type, change_type) 的兜底:
|
||||
// 消费/回退/初始化流水无业务订单号时(order_no 为空串),MySQL 唯一索引将空串视为相同值,
|
||||
// 第二条同类流水会 1062 冲突。此处用流水自身 UUID 填充 order_no 保证唯一,不影响
|
||||
// 购买/退款类幂等流水(其 order_no 非空,直接沿用业务订单号)。
|
||||
func ensureFlowOrderNo(flow *mysqlmodel.QuotaFlowLog) {
|
||||
if flow != nil && flow.OrderNo == "" {
|
||||
flow.OrderNo = flow.QuotaFlowUUID
|
||||
}
|
||||
}
|
||||
|
||||
// QuotaGrantIdempotent 幂等发放额度:事务内先累加 total(不存在则创建,存在则累加),
|
||||
// 回读真实值后写流水;流水唯一索引(order_no+quota_type+change_type)冲突时事务回滚,
|
||||
// 调用方据此识别为「已发放」从而幂等。
|
||||
func (d *QuotaDao) QuotaGrantIdempotent(ctx context.Context, userID int64, quotaType int8, n int64, flow *mysqlmodel.QuotaFlowLog) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaGrantIdempotent")
|
||||
defer span.End()
|
||||
|
||||
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
// 1. 累加 total(记录不存在则创建,存在则原子累加)。
|
||||
q := &mysqlmodel.Quota{
|
||||
UserID: userID,
|
||||
QuotaType: quotaType,
|
||||
Total: n,
|
||||
Used: 0,
|
||||
}
|
||||
if err := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "user_id"}, {Name: "quota_type"}},
|
||||
DoUpdates: clause.Assignments(map[string]interface{}{"total": gorm.Expr("total + ?", n)}),
|
||||
}).Create(q).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 回读真实值填充流水 old/new。
|
||||
var cur mysqlmodel.Quota
|
||||
if err := tx.Where("user_id = ? AND quota_type = ?", userID, quotaType).First(&cur).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
remain := cur.Total - cur.Used
|
||||
flow.OldValue = remain - n
|
||||
flow.NewValue = remain
|
||||
|
||||
// 3. 写流水(唯一索引冲突会回滚整个事务,保证不重复发放)。
|
||||
return tx.Create(flow).Error
|
||||
})
|
||||
}
|
||||
|
||||
// QuotaRevokeIdempotent 幂等回退额度(退款回收):事务内先扣减 total(GREATEST 保证 total 不低于 used,
|
||||
// 即已用部分不回退),回读真实值后写回退流水;流水唯一索引(order_no+quota_type+change_type)冲突时
|
||||
// 事务回滚,调用方据此识别为「已回退」从而幂等。
|
||||
func (d *QuotaDao) QuotaRevokeIdempotent(ctx context.Context, userID int64, quotaType int8, n int64, flow *mysqlmodel.QuotaFlowLog) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaRevokeIdempotent")
|
||||
defer span.End()
|
||||
|
||||
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
// 1. 扣减 total(GREATEST(used, total - n) 保证 total >= used)。
|
||||
res := tx.Model(&mysqlmodel.Quota{}).
|
||||
Where("user_id = ? AND quota_type = ?", userID, quotaType).
|
||||
UpdateColumn("total", gorm.Expr("GREATEST(used, total - ?)", n))
|
||||
if res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
|
||||
// 2. 回读真实值填充流水 old/new。
|
||||
var cur mysqlmodel.Quota
|
||||
if err := tx.Where("user_id = ? AND quota_type = ?", userID, quotaType).First(&cur).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
remain := cur.Total - cur.Used
|
||||
flow.OldValue = remain + n
|
||||
flow.NewValue = remain
|
||||
|
||||
// 3. 写回退流水(唯一索引冲突回滚整个事务,保证不重复回退)。
|
||||
return tx.Create(flow).Error
|
||||
})
|
||||
}
|
||||
|
||||
// QuotaGetByUserIDAndType 查询用户某类配额
|
||||
func (d *QuotaDao) 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
|
||||
}
|
||||
|
||||
// QuotaListByUserID 查询用户的所有配额
|
||||
func (d *QuotaDao) 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
|
||||
}
|
||||
|
||||
// UserDailyUsageDao 每日使用计数数据访问
|
||||
type UserDailyUsageDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewUserDailyUsageDao 创建每日使用计数 DAO
|
||||
func NewUserDailyUsageDao() *UserDailyUsageDao {
|
||||
return &UserDailyUsageDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// UserDailyUsageCreate 创建每日使用计数
|
||||
func (d *UserDailyUsageDao) UserDailyUsageCreate(ctx context.Context, usage *mysqlmodel.UserDailyUsage) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.UserDailyUsageCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(usage).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserDailyUsageIncrementAI 当日AI推荐次数 +1
|
||||
func (d *UserDailyUsageDao) UserDailyUsageIncrementAI(ctx context.Context, userID int64, statDate int64) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.UserDailyUsageIncrementAI")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.UserDailyUsage{}).
|
||||
Where("user_id = ? AND stat_date = ?", userID, statDate).
|
||||
UpdateColumn("ai_recommend_count", gorm.Expr("ai_recommend_count + ?", 1)).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserDailyUsageIncrementOutfitShare 当日社区分享次数 +1
|
||||
func (d *UserDailyUsageDao) UserDailyUsageIncrementOutfitShare(ctx context.Context, userID int64, statDate int64) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.UserDailyUsageIncrementOutfitShare")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.UserDailyUsage{}).
|
||||
Where("user_id = ? AND stat_date = ?", userID, statDate).
|
||||
UpdateColumn("outfit_share_count", gorm.Expr("outfit_share_count + ?", 1)).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserDailyUsageIncrementClosetShare 当日衣柜分享次数 +1
|
||||
func (d *UserDailyUsageDao) UserDailyUsageIncrementClosetShare(ctx context.Context, userID int64, statDate int64) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.UserDailyUsageIncrementClosetShare")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.UserDailyUsage{}).
|
||||
Where("user_id = ? AND stat_date = ?", userID, statDate).
|
||||
UpdateColumn("closet_share_count", gorm.Expr("closet_share_count + ?", 1)).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserDailyUsageGetByUserIDAndDate 查询用户某日使用计数
|
||||
func (d *UserDailyUsageDao) UserDailyUsageGetByUserIDAndDate(ctx context.Context, userID int64, 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
|
||||
}
|
||||
|
||||
// QuotaFlowLogDao 配额流水数据访问
|
||||
type QuotaFlowLogDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewQuotaFlowLogDao 创建配额流水 DAO
|
||||
func NewQuotaFlowLogDao() *QuotaFlowLogDao {
|
||||
return &QuotaFlowLogDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// QuotaFlowLogCreate 创建配额流水
|
||||
func (d *QuotaFlowLogDao) QuotaFlowLogCreate(ctx context.Context, log *mysqlmodel.QuotaFlowLog) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaFlowLogCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(log).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// QuotaFlowLogExistsByOrderNoAndType 判断指定订单+额度类型是否已有发放流水(补偿重试幂等判断)。
|
||||
func (d *QuotaFlowLogDao) QuotaFlowLogExistsByOrderNoAndType(ctx context.Context, orderNo string, quotaType int8) (bool, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaFlowLogExistsByOrderNoAndType")
|
||||
defer span.End()
|
||||
|
||||
var count int64
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.QuotaFlowLog{}).
|
||||
Where("order_no = ? AND quota_type = ?", orderNo, quotaType).
|
||||
Count(&count).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// QuotaFlowLogListByUserID 分页查询用户配额流水(quotaType 为 0 不筛选)
|
||||
func (d *QuotaFlowLogDao) QuotaFlowLogListByUserID(ctx context.Context, userID int64, quotaType int8, page, pageSize int) ([]mysqlmodel.QuotaFlowLog, int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaFlowLogListByUserID")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.QuotaFlowLog
|
||||
var total int64
|
||||
q := d.db.WithContext(ctx).Model(&mysqlmodel.QuotaFlowLog{}).Where("user_id = ?", userID)
|
||||
if quotaType != 0 {
|
||||
q = q.Where("quota_type = ?", quotaType)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// QuotaGrantListByUserIDAndType 查询用户某类型全部凭证(未删除)。
|
||||
// 按到期时间升序(expire_time=0 表示跟随 VIP,排最后),配合「先到期先用」扣减策略。
|
||||
func (d *QuotaGrantDao) QuotaGrantListByUserIDAndType(ctx context.Context, userID int64, quotaType int8) ([]mysqlmodel.QuotaGrant, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaGrantListByUserIDAndType")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.QuotaGrant
|
||||
if err := d.db.WithContext(ctx).
|
||||
Where("user_id = ? AND quota_type = ?", userID, quotaType).
|
||||
Order("CASE WHEN expire_time = 0 THEN 1 ELSE 0 END ASC").
|
||||
Order("expire_time ASC").
|
||||
Find(&list).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// QuotaGrantConsume 条件扣减单条凭证已用量(used + n <= amount),返回是否扣减成功。
|
||||
// 用于 Cos 永久额度凭证维度的扣减,避免并发超扣。
|
||||
func (d *QuotaGrantDao) QuotaGrantConsume(ctx context.Context, grantID int64, n int64) (bool, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaGrantConsume")
|
||||
defer span.End()
|
||||
|
||||
res := d.db.WithContext(ctx).Model(&mysqlmodel.QuotaGrant{}).
|
||||
Where("id = ? AND used + ? <= amount", grantID, n).
|
||||
UpdateColumn("used", gorm.Expr("used + ?", n))
|
||||
if res.Error != nil {
|
||||
span.RecordError(res.Error)
|
||||
return false, res.Error
|
||||
}
|
||||
return res.RowsAffected > 0, nil
|
||||
}
|
||||
|
||||
// QuotaGrantInvalidateByRefID 按关联ID软删除凭证(退款回收时使赠送额度失效)。
|
||||
func (d *QuotaGrantDao) QuotaGrantInvalidateByRefID(ctx context.Context, refID string) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaGrantInvalidateByRefID")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Where("ref_id = ?", refID).Delete(&mysqlmodel.QuotaGrant{}).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// QuotaGrantExistsByRefIDAndType 判断凭证是否已存在(广告激励防重复领取)。
|
||||
func (d *QuotaGrantDao) QuotaGrantExistsByRefIDAndType(ctx context.Context, refID string, quotaType int8) (bool, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.QuotaGrantExistsByRefIDAndType")
|
||||
defer span.End()
|
||||
|
||||
var count int64
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.QuotaGrant{}).
|
||||
Where("ref_id = ? AND quota_type = ?", refID, quotaType).
|
||||
Count(&count).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UserDao 用户数据访问
|
||||
type UserDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewUserDao 创建用户 DAO
|
||||
func NewUserDao() *UserDao {
|
||||
return &UserDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// UserCreate 创建用户
|
||||
func (d *UserDao) UserCreate(ctx context.Context, user *mysqlmodel.User) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.UserCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(user).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return 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
|
||||
}
|
||||
|
||||
// VIPGrantIdempotent 幂等续费会员:事务内先原子累加 vip_expire(CASE 表达式避免并发丢失天数),
|
||||
// 回读真实到期时间后推导会员类型并写 VIP 流水;流水唯一索引(order_no+change_type)冲突时事务回滚,
|
||||
// 调用方据此识别为「已发放」从而幂等。
|
||||
//
|
||||
// 注意:续费 SQL 使用原生语句显式控制 SET 顺序(vip_start 先于 vip_expire 求值),
|
||||
// 使 vip_start 读到更新前的旧 vip_expire,保证「已过期续费时重置 vip_start」语义正确。
|
||||
//
|
||||
// 会员类型按「续费后最终到期时长」推导:>=365 天为年度会员,否则月度会员,
|
||||
// 避免「年度会员续月包被错误降级为月度」;体验会员等其它类型保持传入值不参与推导。
|
||||
func (d *UserDao) VIPGrantIdempotent(ctx context.Context, userID int64, now int64, memberDays int64, vipType int8, flow *mysqlmodel.VIPFlowLog) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.VIPGrantIdempotent")
|
||||
defer span.End()
|
||||
|
||||
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
sql := `UPDATE users SET
|
||||
vip_start = CASE WHEN vip_type = 1 OR vip_expire <= ? THEN ? ELSE vip_start END,
|
||||
vip_expire = (CASE WHEN vip_expire > ? THEN vip_expire ELSE ? END) + ?
|
||||
WHERE id = ? AND deleted_at = 0`
|
||||
if res := tx.Exec(sql, now, now, now, now, memberDays*86400, userID); res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
|
||||
// 回读续费后的真实到期时间,用于推导会员类型。
|
||||
var renewed mysqlmodel.User
|
||||
if err := tx.Where("id = ?", userID).First(&renewed).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 字面量 2=月度会员 3=年度会员(与 service 层 VIPTypeMonthly/VIPTypeYearly 一致)。
|
||||
// 月度/年度续费按最终到期时长推导;体验会员(4)等其它类型保持传入值。
|
||||
finalType := vipType
|
||||
if vipType == 2 || vipType == 3 {
|
||||
if renewed.VIPExpire-now >= 365*86400 {
|
||||
finalType = 3
|
||||
} else {
|
||||
finalType = 2
|
||||
}
|
||||
}
|
||||
if err := tx.Model(&mysqlmodel.User{}).Where("id = ?", userID).
|
||||
UpdateColumn("vip_type", finalType).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
flow.NewVIPExpire = renewed.VIPExpire
|
||||
flow.NewVIPType = finalType
|
||||
|
||||
return tx.Create(flow).Error
|
||||
})
|
||||
}
|
||||
|
||||
// VIPRevokeIdempotent 幂等回退会员(退款回收):事务内先扣减 vip_expire(GREATEST 保证不早于 now),
|
||||
// 回读真实值后写回退流水;流水唯一索引(order_no+change_type)冲突时事务回滚,调用方据此识别为「已回退」。
|
||||
func (d *UserDao) VIPRevokeIdempotent(ctx context.Context, userID int64, now int64, memberDays int64, flow *mysqlmodel.VIPFlowLog) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.VIPRevokeIdempotent")
|
||||
defer span.End()
|
||||
|
||||
return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
// 1. 扣减 vip_expire(GREATEST(now, vip_expire - days) 保证不倒退到过去)。
|
||||
res := tx.Model(&mysqlmodel.User{}).
|
||||
Where("id = ? AND vip_expire > ?", userID, now).
|
||||
UpdateColumn("vip_expire", gorm.Expr("GREATEST(?, vip_expire - ?)", now, memberDays*86400))
|
||||
if res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
|
||||
// 2. 回读真实值填充流水。
|
||||
var renewed mysqlmodel.User
|
||||
if err := tx.Where("id = ?", userID).First(&renewed).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
flow.NewVIPExpire = renewed.VIPExpire
|
||||
flow.NewVIPType = renewed.VIPType
|
||||
|
||||
// 3. 写回退流水(唯一索引冲突回滚整个事务,保证不重复回退)。
|
||||
return tx.Create(flow).Error
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// UserGetByIDs 按主键 ID 批量查询用户(空 ids 直接返回空切片)
|
||||
func (d *UserDao) UserGetByIDs(ctx context.Context, ids []int64) ([]mysqlmodel.User, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.UserGetByIDs")
|
||||
defer span.End()
|
||||
|
||||
var users []mysqlmodel.User
|
||||
if len(ids) == 0 {
|
||||
return users, nil
|
||||
}
|
||||
if err := d.db.WithContext(ctx).Where("id IN ?", ids).Find(&users).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return users, 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
|
||||
}
|
||||
|
||||
// UserGetByOpenID 按微信 openid 查询用户
|
||||
func (d *UserDao) UserGetByOpenID(ctx context.Context, openID string) (*mysqlmodel.User, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.UserGetByOpenID")
|
||||
defer span.End()
|
||||
|
||||
var user mysqlmodel.User
|
||||
if err := d.db.WithContext(ctx).Where("wx_open_id = ?", openID).First(&user).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// VIPFlowLogDao VIP 流水数据访问
|
||||
type VIPFlowLogDao struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewVIPFlowLogDao 创建 VIP 流水 DAO
|
||||
func NewVIPFlowLogDao() *VIPFlowLogDao {
|
||||
return &VIPFlowLogDao{db: global.MysqlEngine}
|
||||
}
|
||||
|
||||
// VIPFlowLogCreate 创建 VIP 流水
|
||||
func (d *VIPFlowLogDao) VIPFlowLogCreate(ctx context.Context, log *mysqlmodel.VIPFlowLog) error {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.VIPFlowLogCreate")
|
||||
defer span.End()
|
||||
|
||||
if err := d.db.WithContext(ctx).Create(log).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VIPFlowLogExistsByOrderNo 判断指定订单是否已有 VIP 发放流水(补偿重试幂等判断)。
|
||||
func (d *VIPFlowLogDao) VIPFlowLogExistsByOrderNo(ctx context.Context, orderNo string) (bool, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.VIPFlowLogExistsByOrderNo")
|
||||
defer span.End()
|
||||
|
||||
var count int64
|
||||
if err := d.db.WithContext(ctx).Model(&mysqlmodel.VIPFlowLog{}).
|
||||
Where("order_no = ?", orderNo).
|
||||
Count(&count).Error; err != nil {
|
||||
span.RecordError(err)
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// VIPFlowLogListByUserID 分页查询用户 VIP 流水
|
||||
func (d *VIPFlowLogDao) VIPFlowLogListByUserID(ctx context.Context, userID int64, page, pageSize int) ([]mysqlmodel.VIPFlowLog, int64, error) {
|
||||
ctx, span := global.Tracer.Start(ctx, "mysqldao.VIPFlowLogListByUserID")
|
||||
defer span.End()
|
||||
|
||||
var list []mysqlmodel.VIPFlowLog
|
||||
var total int64
|
||||
q := d.db.WithContext(ctx).Model(&mysqlmodel.VIPFlowLog{}).Where("user_id = ?", userID)
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user