daily_browse_asset (id=2) / daily_login (id=1) 之前永远不触发,根因:
2026_07_21_004 迁移只 backfill 了 id=3/4 的 trigger_event=task_key,
id=1/2 留 NULL。但 ProcessTaskEvent 引擎按 trigger_event = ? 严格匹配
(spec §4 单一隔离单元),NULL 不会命中,前端 reportEvent 后引擎跑空
循环、daily-tasks 任务永远 pending。
修复:
- 新增 2026_07_27_005 迁移:回填 id=1/2 的 trigger_event=task_key(幂等)
- 修订原 004 迁移的错误注释('trigger_event 留 NULL'误导后人)
- model/task_models.go:删除 'NULL 仍走 def.TaskKey 兜底' 不实承诺
- impl plan §37/39:修订错描述('暂不依赖 trigger_event 列')
- docker/init-db.sql:124668/124671:重建 dev DB 时也带 trigger_event
- daily_task_repo_test.go:补 2 个回归测试
- NullTriggerEvent_NotMatched:trigger_event=NULL 时查不到
- TriggerEventEqualsTaskKey_Matches:trigger_event=task_key 时能命中
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
147 lines
8.6 KiB
Go
147 lines
8.6 KiB
Go
package model
|
||
|
||
// TaskDefinition 任务定义表
|
||
type TaskDefinition struct {
|
||
ID int64 `gorm:"primaryKey;column:id;autoIncrement"`
|
||
StarID *int64 `gorm:"column:star_id;index"` // NULL=全局默认
|
||
TaskKey string `gorm:"column:task_key;size:50;not null"`
|
||
TaskType string `gorm:"column:task_type;size:20;not null"` // daily/onboarding
|
||
Name string `gorm:"column:name;size:100;not null"`
|
||
Description string `gorm:"column:description;type:text"`
|
||
CrystalReward int64 `gorm:"column:crystal_reward;default:0"`
|
||
SortOrder int `gorm:"column:sort_order;default:0"`
|
||
IsActive bool `gorm:"column:is_active;default:true"`
|
||
CreatedAt int64 `gorm:"column:created_at"`
|
||
UpdatedAt int64 `gorm:"column:updated_at"`
|
||
// 方案 A(spec §2.1 / §4):trigger_event + target_count 取代内联 def.TaskKey 匹配
|
||
// ★ trigger_event 必须填;ProcessTaskEvent 引擎按 trigger_event = ? 严格匹配
|
||
// (spec §4 单一隔离单元),NULL 不会命中。daily 任务的 trigger_event 由
|
||
// 2026_07_27_005 迁移回填(与 task_key 一致);onboarding 任务 trigger_event 留 NULL。
|
||
TriggerEvent string `gorm:"column:trigger_event;size:64"`
|
||
TargetCount int `gorm:"column:target_count;default:1"` // =1 等价"首次",>1 为计数型
|
||
}
|
||
|
||
func (TaskDefinition) TableName() string { return "task_definitions" }
|
||
|
||
// UserDailyTaskProgress 每日任务进度表
|
||
type UserDailyTaskProgress struct {
|
||
ID int64 `gorm:"primaryKey;column:id;autoIncrement"`
|
||
UserID int64 `gorm:"column:user_id;not null;index:idx_daily_user_star_key"`
|
||
StarID int64 `gorm:"column:star_id;not null;index:idx_daily_user_star_key"`
|
||
TaskKey string `gorm:"column:task_key;size:50;not null;index:idx_daily_user_star_key"`
|
||
Status string `gorm:"column:status;size:20;default:pending"` // pending/completed/claimed
|
||
CompletedAt *int64 `gorm:"column:completed_at"`
|
||
ClaimedAt *int64 `gorm:"column:claimed_at"`
|
||
CreatedAt int64 `gorm:"column:created_at"`
|
||
UpdatedAt int64 `gorm:"column:updated_at"`
|
||
// 方案 A(spec §2.2 / §4):累计次数;progress >= target_count → status=completed
|
||
Progress int `gorm:"column:progress;default:0"`
|
||
}
|
||
|
||
func (UserDailyTaskProgress) TableName() string { return "user_daily_task_progress" }
|
||
|
||
// UserOnboardingProgress 引导任务进度表
|
||
type UserOnboardingProgress struct {
|
||
ID int64 `gorm:"primaryKey;column:id;autoIncrement"`
|
||
UserID int64 `gorm:"column:user_id;not null;index:idx_onboard_user_key"`
|
||
TaskKey string `gorm:"column:task_key;size:50;not null;index:idx_onboard_user_key"`
|
||
Status string `gorm:"column:status;size:20;default:pending"`
|
||
CompletedAt *int64 `gorm:"column:completed_at"`
|
||
ClaimedAt *int64 `gorm:"column:claimed_at"`
|
||
CreatedAt int64 `gorm:"column:created_at"`
|
||
UpdatedAt int64 `gorm:"column:updated_at"`
|
||
}
|
||
|
||
func (UserOnboardingProgress) TableName() string { return "user_onboarding_progress" }
|
||
|
||
// UserOnboardingStatus 引导流程状态表(per-user per-star)
|
||
type UserOnboardingStatus struct {
|
||
ID int64 `gorm:"primaryKey;column:id;autoIncrement"`
|
||
UserID int64 `gorm:"column:user_id;not null;uniqueIndex:uk_user_star_onboarding"`
|
||
StarID int64 `gorm:"column:star_id;not null;uniqueIndex:uk_user_star_onboarding"`
|
||
IsOnboardingCompleted bool `gorm:"column:is_onboarding_completed;default:false"`
|
||
IsOnboardingClaimed bool `gorm:"column:is_onboarding_claimed;default:false"`
|
||
HasFriendDisplayBonus bool `gorm:"column:has_friend_display_bonus;default:false"`
|
||
OnboardingCompletedAt *int64 `gorm:"column:onboarding_completed_at"`
|
||
OnboardingClaimedAt *int64 `gorm:"column:onboarding_claimed_at"`
|
||
ClaimedStages []int64 `gorm:"column:claimed_stages;type:text;serializer:json"` // 已领取奖励的阶段列表
|
||
CreatedAt int64 `gorm:"column:created_at"`
|
||
UpdatedAt int64 `gorm:"column:updated_at"`
|
||
CurrentStage int64 `gorm:"column:current_stage;default:0"`
|
||
Status string `gorm:"column:status;size:20;default:pending"`
|
||
IsFirstLoginBonusClaimed bool `gorm:"column:is_first_login_bonus_claimed;default:false"` // 废弃字段
|
||
CompletedAt *int64 `gorm:"column:completed_at"` // 废弃字段
|
||
ClaimedAt *int64 `gorm:"column:claimed_at"` // 废弃字段
|
||
}
|
||
|
||
func (UserOnboardingStatus) TableName() string { return "user_onboarding_status" }
|
||
|
||
// OnboardingStageConfig 引导阶段配置表
|
||
type OnboardingStageConfig struct {
|
||
ID int64 `gorm:"primaryKey;column:id;autoIncrement"`
|
||
Stage int `gorm:"column:stage;not null;uniqueIndex"`
|
||
Name string `gorm:"column:name;size:100;not null"`
|
||
Description string `gorm:"column:description;type:text"`
|
||
RequiredTaskKeys []string `gorm:"column:required_task_keys;type:text;serializer:json"` // 存储为 JSON 字符串
|
||
CrystalReward int64 `gorm:"column:crystal_reward;default:0"`
|
||
SortOrder int `gorm:"column:sort_order;default:0"`
|
||
IsActive bool `gorm:"column:is_active;default:true"`
|
||
CreatedAt int64 `gorm:"column:created_at"`
|
||
UpdatedAt int64 `gorm:"column:updated_at"`
|
||
}
|
||
|
||
func (OnboardingStageConfig) TableName() string { return "onboarding_stage_config" }
|
||
|
||
// ExhibitionRevenueRecord 展示收益记录表
|
||
type ExhibitionRevenueRecord struct {
|
||
ID int64 `gorm:"primaryKey;column:id;autoIncrement"`
|
||
UserID int64 `gorm:"column:user_id;not null;index:idx_revenue_user_star"`
|
||
StarID int64 `gorm:"column:star_id;not null;index:idx_revenue_user_star"`
|
||
ExhibitionID int64 `gorm:"column:exhibition_id;not null"`
|
||
AssetID int64 `gorm:"column:asset_id;not null"`
|
||
SlotID int64 `gorm:"column:slot_id;not null"`
|
||
SlotOwnerUID int64 `gorm:"column:slot_owner_uid;not null"`
|
||
SlotType string `gorm:"column:slot_type;size:20;not null"` // own/friend
|
||
CrystalAmount int64 `gorm:"column:crystal_amount;not null"`
|
||
CycleStartTime int64 `gorm:"column:cycle_start_time;not null"`
|
||
CycleEndTime int64 `gorm:"column:cycle_end_time;not null"`
|
||
Status string `gorm:"column:status;size:20;default:claimable"` // claimable/claimed/failed
|
||
ClaimedAt *int64 `gorm:"column:claimed_at"`
|
||
CreatedAt int64 `gorm:"column:created_at"`
|
||
}
|
||
|
||
func (ExhibitionRevenueRecord) TableName() string { return "exhibition_revenue_records" }
|
||
|
||
// LikeBetRevenueRecord 点赞押注收益记录表
|
||
// 业务场景:用户点赞别人作品 → 展品到期时根据点赞排名(bet_order)发放水晶奖励。
|
||
// 数据写入方:galleryService.cleanup_worker 调用 taskService.RecordLikeBetRevenue(内部 RPC)
|
||
// 幂等保证:DB 唯一约束 uk_like_bet_unique(exhibition_id, like_id) —— cleanup_worker 重跑不会重复发奖
|
||
type LikeBetRevenueRecord struct {
|
||
ID int64 `gorm:"primaryKey;column:id;autoIncrement"`
|
||
UserID int64 `gorm:"column:user_id;not null;index:idx_like_bet_user_star"` // 点赞者
|
||
StarID int64 `gorm:"column:star_id;not null;index:idx_like_bet_user_star"` // 点赞者粉丝身份
|
||
ExhibitionID int64 `gorm:"column:exhibition_id;not null"`
|
||
AssetID int64 `gorm:"column:asset_id;not null"`
|
||
LikeID int64 `gorm:"column:like_id;not null"` // 关联 asset_likes.id(幂等键)
|
||
BetOrder int `gorm:"column:bet_order;not null"` // 第几位点赞(按 asset_likes.created_at ASC, id ASC)
|
||
TotalLikes int `gorm:"column:total_likes;not null"` // 该展览内的总押注数(审计字段),NOT 藏品全局点赞数
|
||
CrystalAmount int64 `gorm:"column:crystal_amount;not null"`
|
||
CycleStartTime int64 `gorm:"column:cycle_start_time;not null"`
|
||
CycleEndTime int64 `gorm:"column:cycle_end_time;not null"`
|
||
Status string `gorm:"column:status;size:20;default:claimable"` // claimable/claimed/failed
|
||
ClaimedAt *int64 `gorm:"column:claimed_at"`
|
||
CreatedAt int64 `gorm:"column:created_at"`
|
||
}
|
||
|
||
func (LikeBetRevenueRecord) TableName() string { return "like_bet_revenue_records" }
|
||
|
||
// TaskResetLog 重置日志表
|
||
type TaskResetLog struct {
|
||
ID int64 `gorm:"primaryKey;column:id;autoIncrement"`
|
||
ResetType string `gorm:"column:reset_type;size:20;not null"` // daily
|
||
LastResetAt int64 `gorm:"column:last_reset_at;not null"`
|
||
CreatedAt int64 `gorm:"column:created_at"`
|
||
}
|
||
|
||
func (TaskResetLog) TableName() string { return "task_reset_log" }
|