- 3.1 bcrypt 移出事务 (Register): repository.HashPassword 前移到 db.Transaction 之前。 - 3.2 Login 消除用户枚举 + 限流 + timing 抹平: pkg/errors 加 ErrInvalidCredential / ErrTooManyLoginAttempts; user-not-found 跑 dummy bcrypt 抹平 ~100ms 时序差; mobile 5次/ip 20次 per 15min 限流 (Redis, fail-open 降级)。 - 3.3 MQ streams adapter 停用 → stub: 0 业务调用方, noop EventProducer.Publish; Init 不再装配 streams; 11 处硬编码 'gallery'/'default' 抽常量到 pkg/queue/consts (值不变, 消漂移)。 - 3.5 JWT 密钥治理: pkg/jwt MustInit fail-fast + atomic.Value, 50-goroutine race_test 零告警; MustInit 调用点 gateway main + auth_provider + loadgen 同步更新。 - 3.6 aiChat 健壮性: SaveContext 用 persona.ID(非 req.PersonaId); Redis/memory 错误 记 WARN 不静默; Dify err 映射稳定用户文案。 - 3.7 statistic.Client 重构: TrackEvent 改 buffered channel (cap 1024) + dispatchLoop worker。 - 3.8 网关聚合: StarCache (60s TTL, singleflight) 替换 GetFanIdentities 链式调用; DeleteAccount 改网关直调 userService.DeleteAccount(避免改 hand-written triple.go); 铸造双写改异步 channel+consumer (3 retry)。 - 大量单测: 各子项 TDD (RED→GREEN), 关键并发 race_test (50 goroutine)。 - .env.example JWT_SECRET 改为 ≥32 字节 base64 示例(原为空, 被 MustInit 立即拒)。 Co-Authored-By: Claude <noreply@anthropic.com>
393 lines
15 KiB
Go
393 lines
15 KiB
Go
package repository
|
|
|
|
import (
|
|
"errors"
|
|
|
|
appErrors "github.com/topfans/backend/pkg/errors"
|
|
"github.com/topfans/backend/pkg/models"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// AssetRegistryRepository 资产统一索引Repository接口
|
|
//
|
|
// 历史:曾位于 github.com/topfans/backend/services/starbookService/repository,
|
|
// starbookService 已被决断删除(批次 4.2)。assetService 是 AssetRegistry
|
|
// 数据的真正写入方(GetMyAssets、铸造流程等都在 assetService),
|
|
// 故该仓储下沉为 assetService 内置模块。
|
|
type AssetRegistryRepository interface {
|
|
// Create 创建索引记录
|
|
Create(registry *models.AssetRegistry) error
|
|
|
|
// GetByID 根据ID查询
|
|
GetByID(id int64) (*models.AssetRegistry, error)
|
|
|
|
// GetByAssetID 根据asset_id查询
|
|
GetByAssetID(assetID int64) (*models.AssetRegistry, error)
|
|
|
|
// GetByAssetTypeAndID 根据类型和asset_id查询
|
|
GetByAssetTypeAndID(assetType string, assetID int64) (*models.AssetRegistry, error)
|
|
|
|
// GetByOwner 查询用户的所有索引记录
|
|
GetByOwner(ownerUID, starID int64) ([]*models.AssetRegistry, error)
|
|
|
|
// GetByOwnerAndType 查询用户指定类型的索引记录
|
|
GetByOwnerAndType(ownerUID, starID int64, assetType string, limit, offset int) ([]*models.AssetRegistry, error)
|
|
|
|
// GetByOwnerAndTypeAndGrade 查询用户指定类型和等级的索引记录
|
|
GetByOwnerAndTypeAndGrade(ownerUID, starID int64, assetType string, grade int32, limit, offset int) ([]*models.AssetRegistry, error)
|
|
|
|
// GetByOwnerAndTypeAndCategory 查询用户指定类型和分类的索引记录
|
|
GetByOwnerAndTypeAndCategory(ownerUID, starID int64, assetType string, category string, limit, offset int) ([]*models.AssetRegistry, error)
|
|
|
|
// GetByOwnerAndTypeAndActivity 查询用户指定类型和活动的索引记录
|
|
GetByOwnerAndTypeAndActivity(ownerUID, starID int64, assetType string, activityID int64, limit, offset int) ([]*models.AssetRegistry, error)
|
|
|
|
// CountByOwner 统计用户的索引记录数量
|
|
CountByOwner(ownerUID, starID int64) (int64, error)
|
|
|
|
// CountByOwnerAndType 统计用户指定类型的索引记录数量
|
|
CountByOwnerAndType(ownerUID, starID int64, assetType string) (int64, error)
|
|
|
|
// CountByOwnerAndTypeAndGrade 统计用户指定类型和等级的索引记录数量
|
|
CountByOwnerAndTypeAndGrade(ownerUID, starID int64, assetType string, grade int32) (int64, error)
|
|
|
|
// CountByOwnerAndTypeAndCategory 统计用户指定类型和分类的索引记录数量
|
|
CountByOwnerAndTypeAndCategory(ownerUID, starID int64, assetType string, category string) (int64, error)
|
|
|
|
// CountByOwnerAndTypeAndActivity 统计用户指定类型和活动的索引记录数量
|
|
CountByOwnerAndTypeAndActivity(ownerUID, starID int64, assetType string, activityID int64) (int64, error)
|
|
|
|
// UpdateLikeCount 更新点赞数
|
|
UpdateLikeCount(assetID int64, likeCount int32) error
|
|
|
|
// UpdateGrade 更新等级
|
|
UpdateGrade(assetID int64, grade int32) error
|
|
|
|
// Delete 删除索引记录
|
|
Delete(assetID int64) error
|
|
|
|
// DeleteByAssetType 删除指定类型的索引记录
|
|
DeleteByAssetType(assetType string, assetID int64) error
|
|
}
|
|
|
|
// assetRegistryRepository 资产统一索引Repository实现
|
|
type assetRegistryRepository struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewAssetRegistryRepository 创建资产统一索引Repository实例
|
|
func NewAssetRegistryRepository(db *gorm.DB) AssetRegistryRepository {
|
|
return &assetRegistryRepository{db: db}
|
|
}
|
|
|
|
// Create 创建索引记录
|
|
func (r *assetRegistryRepository) Create(registry *models.AssetRegistry) error {
|
|
if registry == nil {
|
|
return errors.New("registry cannot be nil")
|
|
}
|
|
if registry.OwnerUID <= 0 {
|
|
return errors.New("owner_uid must be greater than 0")
|
|
}
|
|
if registry.StarID <= 0 {
|
|
return errors.New("star_id must be greater than 0")
|
|
}
|
|
return r.db.Create(registry).Error
|
|
}
|
|
|
|
// GetByID 根据ID查询
|
|
func (r *assetRegistryRepository) GetByID(id int64) (*models.AssetRegistry, error) {
|
|
if id <= 0 {
|
|
return nil, errors.New("id must be greater than 0")
|
|
}
|
|
var registry models.AssetRegistry
|
|
if err := r.db.Where("id = ?", id).First(®istry).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, appErrors.ErrAssetRegistryNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
return ®istry, nil
|
|
}
|
|
|
|
// GetByAssetID 根据asset_id查询
|
|
func (r *assetRegistryRepository) GetByAssetID(assetID int64) (*models.AssetRegistry, error) {
|
|
if assetID <= 0 {
|
|
return nil, errors.New("asset_id must be greater than 0")
|
|
}
|
|
var registry models.AssetRegistry
|
|
if err := r.db.Where("asset_id = ?", assetID).First(®istry).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, appErrors.ErrAssetRegistryNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
return ®istry, nil
|
|
}
|
|
|
|
// GetByAssetTypeAndID 根据类型和asset_id查询
|
|
func (r *assetRegistryRepository) GetByAssetTypeAndID(assetType string, assetID int64) (*models.AssetRegistry, error) {
|
|
if assetType == "" {
|
|
return nil, errors.New("asset_type must not be empty")
|
|
}
|
|
if assetID <= 0 {
|
|
return nil, errors.New("asset_id must be greater than 0")
|
|
}
|
|
var registry models.AssetRegistry
|
|
if err := r.db.Where("asset_type = ? AND asset_id = ?", assetType, assetID).First(®istry).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, appErrors.ErrAssetRegistryNotFound
|
|
}
|
|
return nil, err
|
|
}
|
|
return ®istry, nil
|
|
}
|
|
|
|
// GetByOwner 查询用户的所有索引记录
|
|
func (r *assetRegistryRepository) GetByOwner(ownerUID, starID int64) ([]*models.AssetRegistry, error) {
|
|
if ownerUID <= 0 {
|
|
return nil, errors.New("owner_uid must be greater than 0")
|
|
}
|
|
if starID <= 0 {
|
|
return nil, errors.New("star_id must be greater than 0")
|
|
}
|
|
var registries []*models.AssetRegistry
|
|
if err := r.db.
|
|
Joins("JOIN assets ON assets.id = asset_registry.asset_id AND assets.deleted_at IS NULL").
|
|
Where("asset_registry.owner_uid = ? AND asset_registry.star_id = ?", ownerUID, starID).
|
|
Order("asset_registry.created_at DESC").
|
|
Find(®istries).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return registries, nil
|
|
}
|
|
|
|
// GetByOwnerAndType 查询用户指定类型的索引记录
|
|
func (r *assetRegistryRepository) GetByOwnerAndType(ownerUID, starID int64, assetType string, limit, offset int) ([]*models.AssetRegistry, error) {
|
|
if ownerUID <= 0 {
|
|
return nil, errors.New("owner_uid must be greater than 0")
|
|
}
|
|
if starID <= 0 {
|
|
return nil, errors.New("star_id must be greater than 0")
|
|
}
|
|
var registries []*models.AssetRegistry
|
|
query := r.db.
|
|
Joins("JOIN assets ON assets.id = asset_registry.asset_id AND assets.deleted_at IS NULL").
|
|
Where("asset_registry.owner_uid = ? AND asset_registry.star_id = ? AND asset_registry.asset_type = ?", ownerUID, starID, assetType).
|
|
Order("asset_registry.created_at DESC")
|
|
if limit > 0 {
|
|
query = query.Limit(limit)
|
|
}
|
|
if offset > 0 {
|
|
query = query.Offset(offset)
|
|
}
|
|
if err := query.Find(®istries).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return registries, nil
|
|
}
|
|
|
|
// GetByOwnerAndTypeAndGrade 查询用户指定类型和等级的索引记录
|
|
func (r *assetRegistryRepository) GetByOwnerAndTypeAndGrade(ownerUID, starID int64, assetType string, grade int32, limit, offset int) ([]*models.AssetRegistry, error) {
|
|
if ownerUID <= 0 {
|
|
return nil, errors.New("owner_uid must be greater than 0")
|
|
}
|
|
if starID <= 0 {
|
|
return nil, errors.New("star_id must be greater than 0")
|
|
}
|
|
var registries []*models.AssetRegistry
|
|
query := r.db.
|
|
Joins("JOIN assets ON assets.id = asset_registry.asset_id AND assets.deleted_at IS NULL").
|
|
Where("asset_registry.owner_uid = ? AND asset_registry.star_id = ? AND asset_registry.asset_type = ? AND asset_registry.grade = ?", ownerUID, starID, assetType, grade).
|
|
Order("asset_registry.created_at DESC")
|
|
if limit > 0 {
|
|
query = query.Limit(limit)
|
|
}
|
|
if offset > 0 {
|
|
query = query.Offset(offset)
|
|
}
|
|
if err := query.Find(®istries).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return registries, nil
|
|
}
|
|
|
|
// GetByOwnerAndTypeAndCategory 查询用户指定类型和分类的索引记录
|
|
func (r *assetRegistryRepository) GetByOwnerAndTypeAndCategory(ownerUID, starID int64, assetType string, category string, limit, offset int) ([]*models.AssetRegistry, error) {
|
|
if ownerUID <= 0 {
|
|
return nil, errors.New("owner_uid must be greater than 0")
|
|
}
|
|
if starID <= 0 {
|
|
return nil, errors.New("star_id must be greater than 0")
|
|
}
|
|
var registries []*models.AssetRegistry
|
|
query := r.db.
|
|
Joins("JOIN assets ON assets.id = asset_registry.asset_id AND assets.deleted_at IS NULL").
|
|
Where("asset_registry.owner_uid = ? AND asset_registry.star_id = ? AND asset_registry.asset_type = ? AND asset_registry.collection_category = ?", ownerUID, starID, assetType, category).
|
|
Order("asset_registry.created_at DESC")
|
|
if limit > 0 {
|
|
query = query.Limit(limit)
|
|
}
|
|
if offset > 0 {
|
|
query = query.Offset(offset)
|
|
}
|
|
if err := query.Find(®istries).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return registries, nil
|
|
}
|
|
|
|
// GetByOwnerAndTypeAndActivity 查询用户指定类型和活动的索引记录
|
|
func (r *assetRegistryRepository) GetByOwnerAndTypeAndActivity(ownerUID, starID int64, assetType string, activityID int64, limit, offset int) ([]*models.AssetRegistry, error) {
|
|
if ownerUID <= 0 {
|
|
return nil, errors.New("owner_uid must be greater than 0")
|
|
}
|
|
if starID <= 0 {
|
|
return nil, errors.New("star_id must be greater than 0")
|
|
}
|
|
var registries []*models.AssetRegistry
|
|
query := r.db.
|
|
Joins("JOIN assets ON assets.id = asset_registry.asset_id AND assets.deleted_at IS NULL").
|
|
Where("asset_registry.owner_uid = ? AND asset_registry.star_id = ? AND asset_registry.asset_type = ? AND asset_registry.activity_id = ?", ownerUID, starID, assetType, activityID).
|
|
Order("asset_registry.created_at DESC")
|
|
if limit > 0 {
|
|
query = query.Limit(limit)
|
|
}
|
|
if offset > 0 {
|
|
query = query.Offset(offset)
|
|
}
|
|
if err := query.Find(®istries).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return registries, nil
|
|
}
|
|
|
|
// CountByOwner 统计用户的索引记录数量
|
|
func (r *assetRegistryRepository) CountByOwner(ownerUID, starID int64) (int64, error) {
|
|
if ownerUID <= 0 {
|
|
return 0, errors.New("owner_uid must be greater than 0")
|
|
}
|
|
if starID <= 0 {
|
|
return 0, errors.New("star_id must be greater than 0")
|
|
}
|
|
var count int64
|
|
if err := r.db.Model(&models.AssetRegistry{}).
|
|
Joins("JOIN assets ON assets.id = asset_registry.asset_id AND assets.deleted_at IS NULL").
|
|
Where("asset_registry.owner_uid = ? AND asset_registry.star_id = ?", ownerUID, starID).
|
|
Count(&count).Error; err != nil {
|
|
return 0, err
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
// CountByOwnerAndType 统计用户指定类型的索引记录数量
|
|
func (r *assetRegistryRepository) CountByOwnerAndType(ownerUID, starID int64, assetType string) (int64, error) {
|
|
if ownerUID <= 0 {
|
|
return 0, errors.New("owner_uid must be greater than 0")
|
|
}
|
|
if starID <= 0 {
|
|
return 0, errors.New("star_id must be greater than 0")
|
|
}
|
|
var count int64
|
|
if err := r.db.Model(&models.AssetRegistry{}).
|
|
Joins("JOIN assets ON assets.id = asset_registry.asset_id AND assets.deleted_at IS NULL").
|
|
Where("asset_registry.owner_uid = ? AND asset_registry.star_id = ? AND asset_registry.asset_type = ?", ownerUID, starID, assetType).
|
|
Count(&count).Error; err != nil {
|
|
return 0, err
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
// CountByOwnerAndTypeAndGrade 统计用户指定类型和等级的索引记录数量
|
|
func (r *assetRegistryRepository) CountByOwnerAndTypeAndGrade(ownerUID, starID int64, assetType string, grade int32) (int64, error) {
|
|
if ownerUID <= 0 {
|
|
return 0, errors.New("owner_uid must be greater than 0")
|
|
}
|
|
if starID <= 0 {
|
|
return 0, errors.New("star_id must be greater than 0")
|
|
}
|
|
var count int64
|
|
if err := r.db.Model(&models.AssetRegistry{}).
|
|
Joins("JOIN assets ON assets.id = asset_registry.asset_id AND assets.deleted_at IS NULL").
|
|
Where("asset_registry.owner_uid = ? AND asset_registry.star_id = ? AND asset_registry.asset_type = ? AND asset_registry.grade = ?", ownerUID, starID, assetType, grade).
|
|
Count(&count).Error; err != nil {
|
|
return 0, err
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
// CountByOwnerAndTypeAndCategory 统计用户指定类型和分类的索引记录数量
|
|
func (r *assetRegistryRepository) CountByOwnerAndTypeAndCategory(ownerUID, starID int64, assetType string, category string) (int64, error) {
|
|
if ownerUID <= 0 {
|
|
return 0, errors.New("owner_uid must be greater than 0")
|
|
}
|
|
if starID <= 0 {
|
|
return 0, errors.New("star_id must be greater than 0")
|
|
}
|
|
var count int64
|
|
if err := r.db.Model(&models.AssetRegistry{}).
|
|
Joins("JOIN assets ON assets.id = asset_registry.asset_id AND assets.deleted_at IS NULL").
|
|
Where("asset_registry.owner_uid = ? AND asset_registry.star_id = ? AND asset_registry.asset_type = ? AND asset_registry.collection_category = ?", ownerUID, starID, assetType, category).
|
|
Count(&count).Error; err != nil {
|
|
return 0, err
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
// CountByOwnerAndTypeAndActivity 统计用户指定类型和活动的索引记录数量
|
|
func (r *assetRegistryRepository) CountByOwnerAndTypeAndActivity(ownerUID, starID int64, assetType string, activityID int64) (int64, error) {
|
|
if ownerUID <= 0 {
|
|
return 0, errors.New("owner_uid must be greater than 0")
|
|
}
|
|
if starID <= 0 {
|
|
return 0, errors.New("star_id must be greater than 0")
|
|
}
|
|
var count int64
|
|
if err := r.db.Model(&models.AssetRegistry{}).
|
|
Joins("JOIN assets ON assets.id = asset_registry.asset_id AND assets.deleted_at IS NULL").
|
|
Where("asset_registry.owner_uid = ? AND asset_registry.star_id = ? AND asset_registry.asset_type = ? AND asset_registry.activity_id = ?", ownerUID, starID, assetType, activityID).
|
|
Count(&count).Error; err != nil {
|
|
return 0, err
|
|
}
|
|
return count, nil
|
|
}
|
|
|
|
// UpdateLikeCount 更新点赞数
|
|
func (r *assetRegistryRepository) UpdateLikeCount(assetID int64, likeCount int32) error {
|
|
if assetID <= 0 {
|
|
return errors.New("asset_id must be greater than 0")
|
|
}
|
|
return r.db.Model(&models.AssetRegistry{}).
|
|
Where("asset_id = ?", assetID).
|
|
Update("like_count", likeCount).Error
|
|
}
|
|
|
|
// UpdateGrade 更新等级
|
|
func (r *assetRegistryRepository) UpdateGrade(assetID int64, grade int32) error {
|
|
if assetID <= 0 {
|
|
return errors.New("asset_id must be greater than 0")
|
|
}
|
|
return r.db.Model(&models.AssetRegistry{}).
|
|
Where("asset_id = ?", assetID).
|
|
Update("grade", grade).Error
|
|
}
|
|
|
|
// Delete 删除索引记录
|
|
func (r *assetRegistryRepository) Delete(assetID int64) error {
|
|
if assetID <= 0 {
|
|
return errors.New("asset_id must be greater than 0")
|
|
}
|
|
return r.db.Where("asset_id = ?", assetID).Delete(&models.AssetRegistry{}).Error
|
|
}
|
|
|
|
// DeleteByAssetType 删除指定类型的索引记录
|
|
func (r *assetRegistryRepository) DeleteByAssetType(assetType string, assetID int64) error {
|
|
if assetType == "" {
|
|
return errors.New("asset_type must not be empty")
|
|
}
|
|
if assetID <= 0 {
|
|
return errors.New("asset_id must be greater than 0")
|
|
}
|
|
return r.db.Where("asset_type = ? AND asset_id = ?", assetType, assetID).
|
|
Delete(&models.AssetRegistry{}).Error
|
|
}
|