activity-integration-doc
活动前后端联调文档生成器
根据 活动配置 + Go 代码 + 需求文档,自动生成一份面向前端开发的联调文档,减少沟通成本、避免参数填错。
适用场景
- 活动后端开发完成,需要给前端一份接口对接文档
- 新活动启动联调前,整理碎片 ID、接口列表和参数含义
- 活动复用旧玩法,需确认当前活动中组件接口应填什么参数
- 活动临近上线,补充联调文档用于测试和验收
输入参数
用户只需提供活动 ID 和区服,其余信息自动获取:
| 参数 | 说明 | 必须 | 示例 |
|---|---|---|---|
act_id |
活动 ID | 是 | 6553 |
region |
区服代号 | 是 | O(Jackaroo) |
code_dir |
活动代码目录 | 否(自动搜索) | app/activity/2026/jk/jk_fire_song |
env |
配置环境 | 否 | dev(默认)或 prod |
飞书需求文档链接无需用户提供:从活动配置 JSON 的
doc_url字段自动获取。
区服代号对照表
| 代号 | 区服 | 代号 | 区服 |
|---|---|---|---|
| C | 华语服 | K | 韩服 |
| T | 泰服 | A | 阿语服 |
| M | 马来服 | Q | 土语服 |
| P | 菲律宾服 | O | Jackaroo |
| V | 越南服 | G | 德语服 |
| U | 美服 | I | 印度服 |
| B | 葡语服 | N | 巴基斯坦服 |
| S | 西语服 | R | 俄语服 |
| Y | 意大利服 | J | 日服 |
执行步骤
步骤 0:前置条件检查(硬门控,必须通过才能继续)
活动配置是生成联调文档的唯一数据基础,任何原因导致配置拉取失败,立即停止执行,不进入步骤 1。
调用 fetch-game-config-clean skill(namespace=activity,region=用户提供的区服代号,key=活动 ID,env=dev(默认,用户未指定时使用)或 prod),根据结果分三种情况处理:
情况 A:ConfigSecret 未设置
立即停止,输出以下信息后不再继续任何步骤:
无法拉取活动配置:环境变量 ConfigSecret 未设置。
请在终端执行以下命令后重试:
export ConfigSecret=<你的配置中心密钥>
ConfigSecret 可从团队配置文档或后端同学处获取。
情况 B:网络错误或其他拉取失败
立即停止,报告具体错误信息,提示用户检查网络后重试,不继续执行。
情况 C:配置拉取成功
保存返回的 JSON,继续执行步骤 1。
禁止跳过:不接受以本地 JSON 文件、手动粘贴配置或纯代码推导作为替代。配置数值(碎片档位、奖池类型、task_id、rank_type 等)是文档的核心内容,缺失配置会导致文档严重不完整,误导前端开发。
步骤 1:收集三方数据源
1.1 解析活动配置(步骤 0 已拉取)
从步骤 0 拉取的 JSON 中提取:
doc_url→ 飞书需求文档链接(用于步骤 1.3 自动获取需求文档)- 组件配置:
collect_chip→ 碎片定义lottery→ 抽奖配置tasks→ 任务配置rank→ 排行榜配置exchange_store→ 兑换商店配置direct_purchase_store→ 直购商店配置collect_gifts→ 收送礼配置extra_gifts→ 爆礼物配置charge_coupon→ 充值优惠配置gift_pkg→ 礼包配置special_info→ 活动特殊配置
1.2 定位并读取活动代码
1.2.1 定位活动代码目录
活动代码仓库路径通过以下优先级确定:
- 用户直接提供(最优先):用户说"代码在
app/activity/2026/jk/jk_cruise_holiday"时,直接使用该目录,跳过自动搜索 - 环境变量:
$ACTIVITY_DEV_CODE_DIR(指向 wespy-http-go 项目本地仓库,将自动切换到 dev 分支) - 固定路径:
/usr/local/wejoy/src/wespy-http-go-dev/ - 相对路径兜底:当前工作目录下的
../wespy-http-go
如果都找不到,提示用户设置环境变量或提供路径:
export ACTIVITY_DEV_CODE_DIR=/path/to/wespy-http-go
拉取最新 dev 代码(定位到仓库后、搜索活动子目录前执行):
定位到代码仓库根目录后,自动切换到 dev 分支并拉取最新代码:
cd $ACTIVITY_DEV_CODE_DIR && git checkout dev && git pull
- 若
git checkout dev失败(本地有未提交修改等),报告警告并询问用户是否继续使用当前分支代码 - 若
git pull失败(网络问题等),报告警告并询问用户是否继续使用本地已有代码 - 两步均成功后,继续搜索活动子目录
当用户未提供代码目录时,自动搜索:
- 根据活动 ID 搜索(推荐,活动 ID 通常定义在
*_model.go或conf/下):
grep -r "<活动ID>" $ACTIVITY_DEV_CODE_DIR/app/activity/ --include="*.go" -l
- 根据活动名称搜索目录:
find $ACTIVITY_DEV_CODE_DIR/app/activity -type d -maxdepth 3 | grep -i "<活动关键词>"
定位确认步骤(必须执行):找到代码目录后,向用户展示定位结果并确认:
已定位到活动代码目录:
- 活动目录:app/activity/2026/jk/jk_cruise_holiday
- ActId 定义:internal/conf/lottery.go → ActIdLottery = 6601
请确认这是正确的目录,还是需要指定其他路径?
用户确认后才继续后续步骤。
1.2.2 项目代码结构
wespy-http-go/ # 项目根目录
├── app/activity/ # 活动服务
│ ├── {year}/ # 按年份分目录
│ │ └── {region_or_name}/ # 按区域或活动名称分目录
│ │ ├── *_model.go # 路由和 Hook 注册 ★
│ │ ├── register/ # 活动注册入口
│ │ ├── internal/conf/ # 活动配置常量
│ │ ├── internal/route/ # 路由处理器
│ │ ├── internal/service/ # 业务逻辑
│ │ ├── internal/store/ # 数据结构(req/rsp/redis)
│ │ ├── internal/hook/ # Hook 实现
│ │ └── internal/event/ # 事件处理
│ ├── widget/ # 公共 Widget 组件(80+)
│ ├── common/ # 通用框架(actmodel、actutil 等)
│ ├── hook/ # 全局 Hook
│ ├── event/ # 全局事件处理
│ └── router/ # 路由层
├── pkg/weconfig/ # 配置中心 SDK
├── service/ # 全局 service 层
├── store/ # 全局 store 层
└── def/ # 全局常量定义
注意:活动代码目录结构有两种风格:
- 新版(2026+):
internal/conf/、internal/route/、internal/service/、internal/store/、internal/hook/- 旧版:
conf/、route/、service/、store/、hook/(直接在活动目录下,不带internal/) 两种都需要兼容。
1.2.3 重点提取文件
确认目录后,读取以下关键文件:
*_model.go(如sale_model.go、lottery_model.go)→GetRouter()获取 Domain 和 SubRouters,GetHookers()获取 Hook 注册。文件名不固定,搜索包含GetRouter()的.go文件- route/*.go →
c.ShouldBind/c.ShouldBindJSON获取请求参数结构体 - store/req.go → 请求参数的
form/jsontag 和binding约束 - store/rsp.go 或 route 中的
SuccessResponse→ 响应结构 - conf/conf.go → ActId、SpecialInfo 解析结构体、常量定义(如 GiftId、ChipId)
- hook/*.go → Hooker 接口实现
- register/register.go → 活动注册入口
1.3 获取需求文档(飞书凭证为强配置)
从步骤 0 拉取的活动配置中读取 doc_url 字段,该字段即飞书需求文档链接。
情况一:doc_url 存在
调用 feishu2stdout skill 读取文档内容。FEISHU_APP_ID 和 FEISHU_APP_SECRET 是此时的必须条件。
- 若飞书凭证未设置,立即停止执行,向用户输出以下引导后不再继续任何步骤:
活动配置中存在飞书需求文档(doc_url 已配置),必须读取后才能生成联调文档。 请设置飞书凭证后重试: export FEISHU_APP_ID=your_app_id export FEISHU_APP_SECRET=your_app_secret - 若凭证已设置但读取失败(权限不足、网络等),同样停止执行,报告具体错误并引导用户解决后重试。
- 读取成功后,从文档中提取:活动玩法说明、数值设计(概率、保底、阈值等)、各玩法的业务流程和触发条件。
情况二:doc_url 为空或不存在
向用户输出以下提示并等待确认,不得自行跳过:
活动配置中未配置 doc_url(飞书需求文档链接)。
需求文档包含玩法说明和数值设计,对联调文档质量有重要影响。
建议在配置中心补充 doc_url 字段,否则联调文档中的活动概览和业务流程将无法填写。
请选择:
A) 我已在配置中心补充了 doc_url,重新执行
B) 确认该活动没有飞书需求文档,跳过此步骤,继续生成
- 用户选 A → 重新执行步骤 0 拉取配置,再进入本步骤。
- 用户选 B(或明确表示无文档)→ 跳过需求文档,继续执行后续步骤,文档中活动概览标注"无需求文档,请手动补充"。
feishu2stdout 输出说明:
- stdout:Markdown 内容(直接使用)
- stderr:调试信息(文档标题、块数量、转换进度)
- 支持 docx 和 wiki 类型链接
步骤 2:分析与整合
2.1 碎片分析
从活动配置的 collect_chip 数组中提取每个碎片的:
chip_id— 碎片 ID(前端调用接口时需传入)name— 碎片名称price— 单价(金币)sell_conf— 售卖档位(number、price、extra_number)chip_discount— 折扣配置record_total— 是否记录累计值
分析碎片的用途(结合代码和配置):
- 被哪个
lottery消耗(cost_chip_meta_list中引用了哪个 chip_id) - 被哪个
exchange_store消耗(cost_map或cost_chip_meta中引用了哪个 chip_id) - 被哪个
task监听(Trigger 类型为collect_chip且 rules 匹配 chip_id) - 是否作为标志位/中转使用(price=0 或无 sell_conf)
2.2 组件接口分析
核心规则:
- Widget 组件接口注册在各自独立的路由 Group 下,不在活动的 Domain 路径下
- 活动的 Domain 路径(如
/activity_v2/jk_cruise_holiday/)下仅有GetRouter().SubRouters中定义的自定义接口 - 前端通过
act_id参数区分调用的是哪个活动 - 目录名 ≠ 路由组名,必须以代码中
Group("xxx")的实际值为准
动态发现步骤(禁止硬编码路径):
- 判断活动用了哪些组件:检查活动配置 JSON 中哪些组件字段非空
- 读取 Widget 路由代码:对每个用到的组件,读取
app/activity/widget/{组件}/route/route.go(或router.go),从Group("xxx")和.POST("yyy"...)提取真实的路由组名和接口 Action - 输出真实路径:
/activity_v2/{路由组名}/{action}
配置字段与 Widget 目录映射表(参考 act-config-review,完整 70+ 项):
| 配置字段 | Widget 目录 | 说明 |
|---|---|---|
collect_chip |
widget/collect_chip/ |
碎片收集 |
lottery |
widget/lottery/ |
抽奖 |
tasks |
widget/task/ |
任务 |
task_groups |
widget/task_group/ |
任务分组 |
rank |
widget/rank/ |
榜单 |
reward_pool |
widget/reward_pool/ |
奖池 |
exchange_store |
widget/exchange_store/ |
兑换商店 |
direct_purchase_store |
widget/direct_purchase_store/ |
直购商店 |
gift_pkg |
widget/gift_pkg/ |
礼包 |
coin_pkg |
widget/coin_pkg/ |
金币礼包 |
charge_coupon |
widget/charge_coupon/ |
充值优惠 |
collect_gifts |
widget/collect_gift/ |
礼物收集 |
extra_gifts |
widget/extra_gift/ |
爆礼物 |
sign |
widget/sign/ |
签到 |
sign_discontinue |
widget/sign_discontinue/ |
不连续签到 |
mining |
widget/mining/ |
储值返利 |
level_task |
widget/level_task/ |
多层级任务 |
daily_collect_chip |
widget/daily_collect_chip/ |
每日碎片 |
daily_task_repeat |
widget/daily_task_repeat/ |
每日重复任务 |
team |
widget/team/ |
组队 |
family |
widget/family/ |
家族 |
invite_user |
widget/invite_user/ |
邀请用户 |
blind_box |
widget/blind_box_lucky_value/ |
盲盒幸运值 |
flip_card |
widget/flip_card/ |
翻牌 |
claw_machine |
widget/claw_machine/ |
抓娃娃机 |
chess |
widget/chess/ |
棋盘 |
farm |
widget/farm/ |
农场 |
sea_bingo |
widget/sea_bingo/ |
Bingo |
bingo |
widget/bingo/ |
Bingo(旧版) |
red_packet |
widget/redpacket/ |
红包雨 |
gift_charge |
widget/gift_charge/ |
礼物充值 |
gift_unlock |
widget/gift_unlock/ |
礼物解锁 |
gift_slot_machines |
widget/gift_slot_machines/ |
礼物老虎机 |
gift_box_star |
widget/gift_box_star/ |
礼盒星级 |
gift_love_cube |
widget/gift_love_cube/ |
爱心魔方 |
vip_discount |
widget/vip_discount/ |
VIP 折扣 |
renew_vip |
widget/renew_vip/ |
VIP 续费 |
coin_pool |
widget/coin_pool/ |
金币池 |
prop_rain |
widget/prop_rain/ |
道具雨 |
workshop |
widget/workshop/ |
工坊 |
knock_glass |
widget/knock_glass/ |
敲杯子 |
treasure_pavilion |
widget/treasure_pavilion/ |
藏宝阁 |
simple_vote |
widget/simple_vote/ |
简单投票 |
act_vote |
widget/act_vote/ |
活动投票 |
act_discover |
widget/act_discover/ |
活动发现页 |
act_cdk |
widget/act_cdk/ |
CDK 兑换码 |
act_ai_chat |
widget/act_ai_chat/ |
AI 聊天 |
npc |
widget/npc/ |
NPC 互动 |
question |
widget/question/ |
答题 |
room_gift_top |
widget/room_gift_top/ |
房间礼物排行 |
voice_statistics |
widget/voice_statistics/ |
语音统计 |
use_ring |
widget/use_ring/ |
使用戒指 |
注意:配置字段名和 Widget 目录名大部分一致,少数有差异(如
tasks→widget/task/,red_packet→widget/redpacket/)。 遇到未在表中的字段,直接在app/activity/widget/下搜索匹配的目录。
获取 Widget 接口路径的步骤(禁止硬编码,必须动态读取):
- 根据上表定位 Widget 目录
- 读取该目录下的 route 文件(
route/route.go、router.go、route/router.go或router/router.go) - 从
Group("xxx")提取路由组名,从.POST("yyy"...)提取接口 Action - 拼接真实路径:
/activity_v2/{路由组名}/{action}
特别注意目录名与路由组名不同的组件:
direct_purchase_store→ 组名direct_storefamily→ 组名family_widgetact_vote→ 组名voteact_ai_chat→ 组名ai_chatgift_pkg额外注册了chain_gift_pkg子组
对于每个组件接口,说明在当前活动中应该填什么参数值:
act_id— 填conf.ActId(从 conf.go 读取)type(lottery)— 填 lottery 配置中的type值chip_id— 填具体的碎片 IDtype(rank)— 填 rank 配置中的type值task_id— 填 task 配置中的id值store_name— 填 exchange_store 配置中的store_name值- 其他参数根据具体 Widget 的请求结构体确定
2.3 活动特定接口分析
从 *_model.go 的 GetRouter().SubRouters 提取活动自定义接口,结合 route 和 store 代码分析每个接口的:
- 完整路径:
/activity_v2/{domain}/{action} - HTTP 方法
- 请求参数(从
ShouldBind绑定的结构体提取) - 响应结构(从
SuccessResponse返回的数据提取) - 业务说明(从 service 层逻辑推导)
2.4 Hook 影响分析
从 GetHookers() 中识别注册的 Hook,说明它们对标准组件接口的影响:
LotteryHooker的BeforeSendRewards→ 可能替换/拦截抽奖奖励TaskHooker的CanReceiveReward→ 可能限制任务领取条件SendGiftHooker→ 送特定礼物时的额外处理逻辑- 其他 Hook 对前端展示或流程的影响
步骤 3:生成联调文档
输出一份结构化的 Markdown 文档,保存到活动代码目录下:
<code_dir>/INTEGRATION.md
文档结构如下:
# {活动名称} 前后端联调文档
> 自动生成时间:{datetime}
> 活动 ID:{act_id}
> Domain:{domain}
> 区服:{region}
> 活动时间:{start_time} ~ {end_time}
---
## 一、活动概览
{从需求文档或配置中提取的活动简要说明}
---
## 二、碎片(Collect Chip)定义
{从活动配置 `collect_chip` 数组提取,如果无法获取配置则从代码常量推导}
| chip_id | 名称 | 单价(金币) | 用途 | 备注 |
|---------|------|-----------|------|------|
| {从配置/代码提取} | {name} | {price} | {分析用途} | {sell_conf 为空则标注"不可购买"} |
### 碎片购买档位(从 sell_conf.sell_infos 提取,无 sell_conf 则省略本节)
| 数量 | 价格(金币) | 额外赠送 |
|------|-----------|---------|
| {number} | {price} | {extra_number} |
### 碎片折扣(从 chip_discount 提取,无折扣则省略本节)
| discount_id | 折扣率 | 有效期(秒) | 首次折扣 | 抽奖可用 |
|------------|--------|-----------|---------|---------|
| {discount_id} | {discount_rate} | {expire_time} | {is_first_rate} | {lottery_can_enjoy} |
---
## 三、组件接口(Widget API)参数速查
> **Widget 接口不在活动 Domain 路径下**,它们注册在各自独立的路由 Group 下。
> 前端通过 `act_id` 参数区分调用的是哪个活动。
> 以下仅列出**本活动实际用到的组件**及其接口路径和参数。
> 接口路径通过读取各 Widget 的 route 文件中 `Group("xxx")` 和 `.POST("yyy")` 的真实注册值获得。
>
> **根据活动配置动态判断用了哪些组件**(配置字段非空即表示使用了该组件),
> 然后对每个用到的组件,读取 `app/activity/widget/{组件目录}/route/route.go` 获取真实接口列表。
> **禁止猜测接口路径**,目录名 ≠ 路由组名的情况很常见。
{根据活动实际使用的组件,从「步骤 2.2 组件接口分析」的完整对照表中选取对应组件,
读取 route 文件后填入真实路径和参数。
如果活动自定义路由覆盖了组件同名 action,需标注说明。}
---
## 四、活动特定接口
> 以下为本活动自定义的接口(非通用组件接口)。
### 4.1 {接口名称}
- **路径**:`POST /activity_v2/{domain}/{action}`
- **说明**:{业务说明}
**请求参数**:
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| act_id | int | 是 | 活动 ID |
| uid | int | 是 | 用户 ID |
| {字段} | {类型} | {是/否} | {说明} |
**响应结构**:
```json
{
"code": 200,
"msg": "ok",
"result": {
// 响应字段说明
}
}
```
{... 其他自定义接口 ...}
---
## 五、Hook 对接口的影响
{列出注册的 Hook 及其对标准接口行为的影响}
| Hook 类型 | 影响的接口 | 影响说明 |
|----------|----------|---------|
| LotteryHooker.BeforeSendRewards | do_lottery | 每日限额控制,超出后替换为空奖励 |
| TaskHooker.CanReceiveReward | recv_task_reward | 同设备限制3个用户领取 |
---
## 六、关键业务流程
{从需求文档和代码中梳理的核心用户操作流程}
```
用户充值 → 购买碎片(buy_chip) → 执行抽奖(do_lottery) → 获得奖励
↓
获得兑换币(chip_id=2)
↓
兑换商店兑换(do_exchange)
```
---
## 七、注意事项
{从配置和代码中发现的需要前端注意的事项}
- {时间窗口限制}
- {特殊状态处理}
- {错误码说明}
- {并发/频率限制}
---
## 八、配置速查
### SpecialInfo 关键字段
| 字段 | 值 | 说明 |
|------|---|------|
| {key} | {value} | {说明} |
### 关键常量
| 常量 | 值 | 说明 |
|------|---|------|
| ActId | {act_id} | 活动 ID |
| GiftId | {gift_id} | 活动礼物 ID |
| {其他常量} | {值} | {说明} |
步骤 3.5:生成 OpenAPI 文档
基于步骤 2.2 中已分析出的 Widget 组件接口 和步骤 2.3 中已分析出的活动特定接口(GetRouter().SubRouters 中的自定义接口),按照 activity-openapi-from-code skill 的规范生成 OpenAPI 3.0 文档。
范围说明:此 OpenAPI 文档覆盖两类接口:
- Widget 组件接口:本活动实际使用的所有 Widget 组件的接口(路径为
/activity_v2/{路由组名}/{action})- 活动自定义接口:
GetRouter().SubRouters中定义的活动专属接口(路径为/activity_v2/{Domain}/{action})
Part A:活动自定义接口 — 代码溯源顺序(与 activity-openapi-from-code 一致)
| 步骤 | 文件/位置 | 提取内容 |
|---|---|---|
| 1 | <活动>/*_model.go |
GetActId()、GetRouter() 的 Domain、SubRouters[].Action |
| 2 | SubRouters[].Handler 指向的 internal/route/*.go |
HTTP 方法、绑定类型 c.ShouldBind、成功/失败响应封装 |
| 3 | internal/store/req.go(及同包其它 req) |
form/json/uri 标签 → OpenAPI 属性名;binding 与嵌入的 actutil.CommonReq |
| 4 | internal/store/rsp.go 或 route 中 SuccessResponse 的变量类型 |
响应 result 的结构与 json 标签 |
| 5 | internal/service/*.go 中对应函数 |
summary/description 的补充语义(状态机、时间窗、错误分支、枚举含义) |
| 6 | internal/conf/conf.go |
ActID、域名等客观配置 |
| 7 | 公共层 app/activity/common/actutil/http_req.go(CommonReq)、http_rsp.go |
统一请求/响应外层 |
Part B:Widget 组件接口 — 代码溯源顺序
对步骤 2.2 中已识别的每个 Widget 组件,按以下顺序读取代码:
| 步骤 | 文件/位置 | 提取内容 |
|---|---|---|
| 1 | app/activity/widget/{组件}/route/route.go(或 router.go) |
Group("xxx") → 路由组名;.POST("yyy", handler) → action 列表;handler 函数名 |
| 2 | handler 函数体中 c.ShouldBind / c.ShouldBindJSON 绑定的结构体类型 |
绑定方式(form/json)及请求参数结构体名 |
| 3 | app/activity/widget/{组件}/store/req.go |
form/json 标签 → 属性名;binding:"required" → required 字段 |
| 4 | app/activity/widget/{组件}/store/rsp.go 或 handler 中 SuccessResponse 的变量类型 |
响应 result 结构 |
| 5 | app/activity/widget/{组件}/service/*.go 对应函数(可选) |
summary/description 语义补充 |
在 OpenAPI 路径中,Widget 接口格式为 /activity_v2/{路由组名}/{action},并在 description 中注明:
- 该组件的用途(如"抽奖组件")
- 本活动对应的
act_id参数值(从conf.ActId读取) - 活动特有的参数枚举值(如
type、chip_id、task_id等,来自活动配置)
核心原则
- 一切有据:每个 path、每个字段都能在代码中找到定义或推导来源
- 不 Mock:不写虚构的
example值;未覆盖的逻辑写「以代码为准」并指向文件 - 路径与网关一致:Widget 接口使用
/activity_v2/{路由组名}/{action};自定义接口使用/activity_v2/{Domain}/{action} - 分组标注:OpenAPI
tags中使用widget标记组件接口,使用custom标记活动自定义接口,便于前端区分
请求体 Content-Type
ShouldBind+form标签 →application/x-www-form-urlencodedShouldBindJSON→application/json- 同一活动混用时按接口分别声明
输出文件
保存到 <code_dir>/openapi.yaml,格式参考 activity-openapi-from-code skill 中定义的 OpenAPI 文件结构。
步骤 4:输出与确认
- 将生成的联调文档保存到
<code_dir>/INTEGRATION.md - 将生成的 OpenAPI 文档保存到
<code_dir>/openapi.yaml - 向用户展示文档摘要(碎片数量、Widget 组件数、Widget 接口总数、自定义接口数、OpenAPI 接口总数)
- 询问是否需要调整或补充
步骤 5:飞书文档分享
文档生成完成后,无需询问用户,直接执行以下全部操作。任何步骤失败则报告错误并继续尝试后续步骤,不阻塞整体流程。
5.1 导入飞书在线文档:
- 读取生成的
INTEGRATION.md文件内容 - 调用 lark-cli 创建飞书文档:
lark-cli docs +create --title "{活动名称} 联调文档" --markdown "<INTEGRATION.md 内容>" - 记录返回的
doc_url(飞书在线文档链接)
5.2 获取当前应用下的 open_id:
重要:飞书 open_id 是应用级别隔离的,同一用户在不同应用下 open_id 不同。 消息中的
sender_id(ou_xxx)属于消息来源应用,不可直接用于 lark-cli 配置的应用发送消息(会报open_id cross app)。 必须通过user_id(全局唯一,非ou_开头,如cf122be1)转换为当前应用下的 open_id。
从对话消息中提取用户的 user_id(非 ou_ 开头的全局用户 ID),然后查询当前应用下的 open_id:
lark-cli contact +get-user --user-id <user_id> --user-id-type user_id
从返回 JSON 的 data.open_id 字段提取当前应用可用的 open_id。若查询失败则跳过私聊发送步骤。
5.3 私聊发送文件和链接:
- 若已创建飞书在线文档,发送链接和摘要:
lark-cli im +messages-send --user-id <open_id> --markdown "**{活动名称} 联调文档**\n\n在线文档:{doc_url}\n\n碎片数:{N},Widget 组件数:{N}(接口 {N} 条),自定义接口数:{N}" - 发送本地文件(注意须使用相对路径,先 cd 到文件所在目录):
cd <code_dir> && lark-cli im +messages-send --user-id <open_id> --file ./INTEGRATION.md cd <code_dir> && lark-cli im +messages-send --user-id <open_id> --file ./openapi.yaml
每个 lark-cli 命令独立执行,某一步失败不影响后续步骤。全部失败时报告错误,不影响已生成的文档。
输出质量要求
必须满足的条件
- 每个碎片的
chip_id和用途必须列出 - 组件接口的参数值必须来自实际配置(不可编造)
- 自定义接口的请求/响应必须来自代码中的结构体定义(不可编造)
- 所有
{domain}必须替换为实际的 Domain 值 - 所有
{act_id}必须替换为实际的活动 ID - Hook 影响说明必须基于代码实际注册的 Hook
- OpenAPI 的每个
paths条目均能在GetRouter+ route 中找到 Handler 与 Action - OpenAPI Request schema 字段与
form/json标签名一致(含下划线命名) - OpenAPI Response
result与SuccessResponse(c, result)的类型一致 - OpenAPI 中的 Widget 组件接口:每个配置中启用的组件必须对应至少一条
paths条目 - OpenAPI 中的 Widget 接口路径由 Widget
route.go中Group("xxx")+.POST("yyy")动态读取,禁止猜测 - OpenAPI 中的 Widget 接口请求 schema 来自该 Widget 的
store/req.go,禁止凭印象填写 - OpenAPI Widget 接口用
tags: [widget]标注,自定义接口用tags: [custom]标注
禁止行为
- 禁止编造不存在的接口或参数
- 禁止编造 example 值(可以标注"需确认")
- 禁止遗漏
sale_model.go中定义的自定义接口 - 禁止遗漏配置中启用的组件
- 禁止在 OpenAPI 中添加未在代码中出现的枚举值或字段
- 禁止在不读取 Widget route 文件的情况下填写 Widget 接口路径(目录名 ≠ 路由组名)
依赖说明
Skill 依赖
| 依赖 Skill | 路径 | 用途 | 必须 |
|---|---|---|---|
fetch-game-config-clean |
skills/activity/backend/fetch-game-config-clean |
拉取活动配置 JSON | 是 |
feishu2stdout |
skills/activity/backend/feishu2stdout |
读取飞书需求文档(输出到 stdout) | 否(提供需求文档时需要) |
activity-components |
skills/activity/backend/activity-components |
组件知识库(辅助理解组件用法) | 否(内置知识) |
activity-openapi-from-code |
skills/activity/backend/activity-openapi-from-code |
OpenAPI 文档生成规范 | 是(生成 openapi.yaml 时需要) |
lark-cli |
全局安装(npm install -g @larksuite/cli) |
飞书文档分享(导入在线文档 + 私聊发送) | 否(未安装则跳过分享步骤) |
环境变量
| 变量 | 用途 | 必须 |
|---|---|---|
ConfigSecret |
配置中心鉴权密钥(fetch-game-config-clean 需要) | 是 |
FEISHU_APP_ID |
飞书应用 ID | 条件必须(活动配置有 doc_url 时必须设置) |
FEISHU_APP_SECRET |
飞书应用密钥 | 条件必须(活动配置有 doc_url 时必须设置) |
ACTIVITY_DEV_CODE_DIR |
业务代码仓库路径(wespy-http-go),自动切换 dev 分支并拉取最新代码 | 否(未提供时使用 ../wespy-http-go 兜底) |
Python 依赖
fetch-game-config-clean 和 feishu2stdout 各自有 requirements.txt,安装方式见 README.md。
典型使用场景
场景 1:最简用法(只需活动 ID + 区服)
用户:帮我生成联调文档,活动 ID 6601,Jk 区服
AI 执行:
- 拉取 act_id=6601, region=O 的活动配置
- 从配置中读取
doc_url,自动获取飞书需求文档 - 根据活动 ID 自动搜索代码目录,定位后确认
- 整合三方数据,生成
INTEGRATION.md
场景 2:指定代码目录
用户:生成联调文档,活动 6601,O 服,代码在 app/activity/2026/jk/jk_cruise_holiday
AI 执行:
- 拉取配置 + 从
doc_url获取需求文档 - 直接使用用户指定的代码目录
- 生成文档
场景 3:配置中有 doc_url 但飞书凭证未设置
用户:生成联调文档,活动 6601,O 服
AI 执行:
- 拉取配置,发现
doc_url非空 - 尝试调用 feishu2stdout,发现 FEISHU_APP_ID / FEISHU_APP_SECRET 未设置
- 立即停止,提示用户设置凭证后重试,不继续生成文档
场景 4:配置中无 doc_url,用户确认没有文档
用户:生成联调文档,活动 6601,O 服
AI 执行:
- 拉取配置,发现
doc_url为空 - 提示用户:"未配置 doc_url,请确认是否真的没有需求文档?"
- 用户回复"确认没有"→ 跳过需求文档,继续生成(活动概览标注"无需求文档,请手动补充")
- 用户回复"有,我去补充"→ 等待用户补充后重新执行
场景 6:更新已有文档
用户:更新一下 jk_cruise_holiday 的联调文档,新增了一个接口
AI 执行:
- 读取已有的
INTEGRATION.md - 重新扫描代码,发现新增接口
- 增量更新文档
常见问题
Q: 配置拉取失败怎么办?
A: ConfigSecret 是必须的前置条件。若提示未设置,执行 export ConfigSecret=<你的密钥> 后重试。网络问题则检查网络并重试。不接受以本地 JSON 文件或纯代码推导作为替代,配置数值必须从配置中心获取。
Q: 飞书凭证未设置怎么办?
A: 若活动配置有 doc_url,飞书凭证是必须的。执行 export FEISHU_APP_ID=xxx 和 export FEISHU_APP_SECRET=xxx 后重试。若活动配置无 doc_url,skill 会主动询问用户是否真的没有需求文档,用户确认后可跳过。
Q: 代码目录找不到怎么办?
A: 让用户提供准确的活动代码目录路径。通常在 app/activity/{year}/{region_group}/{activity_name} 下。
Q: 组件接口路径不确定?
A: 禁止猜测路径。必须读取对应 Widget 目录下的 route 文件,从 Group("xxx") 获取路由组名,从 .POST("yyy") 获取接口 Action。组件接口路径是 /activity_v2/{路由组名}/{action},其中路由组名由 Widget 的 route 代码决定,不是活动的 Domain。特别注意以下目录名 ≠ 路由组名的情况:direct_purchase_store → direct_store、family → family_widget、act_vote → vote、act_ai_chat → ai_chat。
Q: 如何判断活动用了哪些组件? A: 检查活动配置中哪些组件字段非空:
collect_chip非空 → 碎片组件(路由组:collect_chip)lottery非空 → 抽奖组件(路由组:lottery)tasks非空 → 任务组件(路由组:task_group)rank非空 → 排行榜组件(路由组:rank)exchange_store非空 → 兑换商店(路由组:exchange_store)direct_purchase_store非空 → 直购商店(路由组:direct_store)gift_pkg非空 → 礼包(路由组:gift_pkg,另有chain_gift_pkg)coin_pkg非空 → 金币礼包(路由组:coin_pkg)charge_coupon非空 → 充值优惠(路由组:charge_coupon)collect_gifts非空 → 收送礼(路由组:collect_gift)extra_gifts非空 → 爆礼物(路由组:extra_gift)- sign 相关 → 签到(路由组:
sign)/ 不连续签到(路由组:sign_discontinue) level_tasks非空 → 多层级任务(路由组:level_task)- 此外还需检查代码中是否引用了 bingo、farm、mining、team、family、invite_user 等游戏/社交类组件