activity-framework
SKILL.md
活动基础框架开发指南
帮助你理解和使用活动系统框架,快速创建和开发新活动。
🤖 AI 执行指令
当用户调用此 skill 时,根据用户需求提供活动框架相关的指导和代码示例。
使用场景判断
- 创建新活动 → 提供完整的活动创建流程和模板代码
- 理解架构 → 解释框架分层和核心概念
- 实现功能 → 提供具体的 API 用法和代码示例
- 排查问题 → 结合生命周期和初始化流程分析问题
框架总览
活动系统基于 Go + Gin + Redis 的插件化框架。核心思路:活动通过实现 ActModel 接口接入框架,通过 Widget 插件系统复用通用功能,通过 Hook 机制注入自定义行为,通过事件系统实现异步解耦。
架构分层
┌─────────────────────────────────────────┐
│ 路由层 (Gin Router) │
├─────────────────────────────────────────┤
│ Widget路由 (40+) │ 活动自定义路由 (150+) │
├─────────────────────────────────────────┤
│ Service 业务逻辑层 │
├──────────┬──────────┬───────────────────┤
│ Hook系统 │ 事件系统 │ 定时任务系统 │
├──────────┴──────────┴───────────────────┤
│ Redis 存储层 (actstore/actredis) │
├─────────────────────────────────────────┤
│ 通用基础设施 (common/) │
│ 错误处理 · 奖励 · 追踪 · 消息 · i18n │
└─────────────────────────────────────────┘
ActModel 核心接口
每个活动必须实现 ActModel 接口(/common/actmodel/actmodel.go):
type ActModel interface {
GetActInfo() *weactivity.Activity // 活动元数据
GetActId() int // 唯一活动ID
GetRouter() Router // API端点
GetEvents() []Event // 事件处理器
GetDeferTasks() []DeferTask // 延迟任务
GetNotifyEvents() []NotifyEvent // 通知事件
GetCronTasks() []*cronext.ActJob // 定时任务
GetHookers() ActHookerList // Hook扩展
GetRedisCmds() map[int][]RedisCmd // Redis命令注册
GetRegions() []string // 部署区域
NeedCheckAct() bool // 是否校验配置
}
默认实现
| 基类 | 特点 |
|---|---|
DefaultModel |
空实现,所有方法返回零值,适合简单活动 |
DefaultModelV2 |
继承 DefaultModel,NeedCheckAct() 返回 true(配置不存在时跳过注册) |
活动创建标准流程
步骤 1:定义配置
文件:/{year}/{activity}/conf/conf.go
const ActId = 1001 // 唯一活动ID
type Config struct {
// 活动特有配置字段
}
func GetConfig() *Config {
// 通过 ParseSpecialInfo 从 weconfig 加载
}
步骤 2:注册活动
文件:/{year}/{activity}/register/register.go
func init() {
registry.Register(&MyActivity{})
}
type MyActivity struct {
actmodel.DefaultModelV2
}
func (a *MyActivity) GetActId() int { return conf.ActId }
func (a *MyActivity) GetActInfo() *weactivity.Activity { ... }
func (a *MyActivity) GetRouter() actmodel.Router { ... }
func (a *MyActivity) GetEvents() []actmodel.Event { ... }
func (a *MyActivity) GetRegions() []string { return []string{"C"} }
步骤 3:设置路由
文件:/{year}/{activity}/route/route.go
func SetupRouter(r *gin.RouterGroup) {
r.GET("/info", handler.GetInfo)
r.POST("/claim", handler.ClaimReward)
}
步骤 4:注册事件和任务
在 GetEvents() / GetCronTasks() 中声明式注册。
活动生命周期
状态流转
| 状态 | 条件 | 可见性 |
|---|---|---|
| 未开始 | 当前时间 < StartTime | 仅白名单用户可见 |
| 进行中 | StartTime ≤ 当前时间 ≤ EndTime | 所有符合条件用户 |
| 已结束 | 当前时间 > EndTime | 已归档,数据仍可访问 |
核心访问函数
| 函数 | 用途 | 时间限制 |
|---|---|---|
GetActById(actId) |
获取活动信息 | 无 |
GetValidActById(actId) |
获取进行中活动 | 仅进行中 |
GetValidActByUid(actId, uid) |
获取用户可参与的活动 | 仅进行中 + 参与校验 |
GetValidActByUidWithExtend() |
带延长时间的获取 | 仅进行中(含延长) |
CanParticipateAct(actId, uid) |
用户参与检查 | 仅进行中 |
参与检查流程
- 检查活动是否在时间窗口内
- 调用
RegisterIsParticipateUidFunc(actId, fn)自定义检查 - 检查
actCommonFilterList全局过滤器 - 返回过滤后的活动
路由系统
路由类型
| 类型 | 值 | 说明 |
|---|---|---|
| RouterTypeNormal | 0 | 需登录的普通端点 |
| RouterTypeInner | 1 | 内部/管理员端点 |
| RouterTypeNoLogin | 2 | 无需登录的公开端点 |
主路由初始化顺序
router.SetupRouter(r)
├── registry.RegisterActivity() // 初始化活动注册
├── hook.RegisterAllFunc() // 初始化所有 Hook
├── setupRouter(r) // 设置普通端点(Widget + 活动路由)
├── setupInnerRouter(r) // 设置内部端点
├── setupNoLoginRouter(r) // 设置公开端点
├── event.RegisterEvent() // 注册事件处理
├── event.RegisterRiskEvent() // 注册风控事件
└── event.RegisterDeferTasks() // 注册延迟任务
注册机制
活动注册文件 /registry/act_model_register/register.go 由 register.sh 脚本自动生成,通过 Go 的 init() 副作用导入实现自动注册(150+ 活动)。
通用基础设施 (common/)
请求/响应
// 标准请求
type CommonReq struct { ActId, Uid int; Lang string }
// 标准响应
actutil.SuccessResponse(c, data)
actutil.BadParamsResponse(c, msg)
actutil.ServerErrorResponse(c, msg, data)
actutil.GinResponseWithError(c, err, data) // 自动处理 CustomError
错误处理
// 自定义错误(支持 i18n)
err := acterror.NewCustomError(actmsg.KeyXXX)
err := acterror.NewErrorWithLang(key, lang)
// 预定义错误
acterror.GetDefaultErr()
acterror.GetCoinNotEnoughErr()
acterror.GetChipNotEnoughErr()
奖励系统
rewards := actreward.GenerateRewards(uid, genderId, config)
err := actreward.SendRewards(uid, rewards, &actreward.ExtraInfo{
ActId: actId, ActName: name,
PropChangeType: def.PropChangeTypeActivityTask,
ChipSource: "widget_name",
})
消息系统
支持卡片消息、文本消息、背景卡片,通过 NPC 或私聊发送,自带深链接和消息后缀(活动链接、背包、商店、房间等)。
时间周期
| 周期类型 | 说明 |
|---|---|
| PeriodTypeTotal | 活动整个周期 |
| PeriodTypeDaily | 每日 |
| PeriodTypeNatureWeekly | 自然周 |
| PeriodTypeMonthly | 每月 |
| PeriodTypeHourly | 每小时 |
多区域部署
每个活动通过 GetRegions() 指定部署区域(如 "C" = 中国, "A" = 东南亚),registry.GetAllActModels() 根据当前服务器区域自动过滤。
应用初始化流程
main()
├── db.ConnectRedis() // 连接 Redis
├── db.ConnectDB() // 连接数据库
├── db.ConnectMongoDB() // 连接 MongoDB
├── cache.InitMemoryCache() // 初始化内存缓存
├── wevent.Init() // 初始化全局事件
└── setupActivityRouter()
├── r := gin.New()
├── r.Use(middleware...) // 全局中间件
└── router.SetupRouter(r)
├── registry.RegisterActivity()
├── hook.RegisterAllFunc()
├── setupRouter(r) // Widget + 活动路由
├── event.RegisterEvent()
└── event.RegisterDeferTasks()
关键文件速查
| 文件路径 | 用途 |
|---|---|
/common/actmodel/actmodel.go |
ActModel 接口定义 |
/registry/registry.go |
活动注册与查找 |
/router/router.go |
主路由编排 |
/common/actinfo/act.go |
活动元数据与生命周期 |
/common/actstore/ |
存储层高级抽象 |
/common/actredis/ |
Redis 客户端框架 |
/common/actevent/ |
事件系统 |
/hook/register.go |
Hook 注册入口 |
/event/register.go |
事件注册中心 |
/{year}/{activity}/register/ |
活动注册 |
/{year}/{activity}/conf/ |
活动配置 |
/{year}/{activity}/route/ |
活动路由 |
典型使用场景
场景 1:创建新活动
/activity-framework 我要创建一个新的春节活动
我会提供完整的项目结构、注册代码模板和配置示例。
场景 2:理解初始化流程
/activity-framework 活动是怎么被加载的?
我会解释从 main() 到活动注册的完整初始化链路。
场景 3:使用基础设施
/activity-framework 怎么发送奖励给用户?
我会提供 actreward 的使用方法和完整代码示例。