topfans/backend/services/assetService/repository/asset_registry_repository.go
zerosaturation 8a767fb400 fix(backend): service stability — batch 3 accumulated (bcrypt off-txn / login anti-enum / MQ stub / aiChat / event reliability / gateway aggregate)
- 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>
2026-07-24 14:03:21 +08:00

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(&registry).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, appErrors.ErrAssetRegistryNotFound
}
return nil, err
}
return &registry, 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(&registry).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, appErrors.ErrAssetRegistryNotFound
}
return nil, err
}
return &registry, 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(&registry).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, appErrors.ErrAssetRegistryNotFound
}
return nil, err
}
return &registry, 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(&registries).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(&registries).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(&registries).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(&registries).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(&registries).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
}