# 七日签到奖励架构 本文定义 App 七日签到奖励的产品规则、服务边界、后台管理、接口契约、数据模型和落地顺序。七日签到属于活动事实,签到状态和发奖状态由 `activity-service` 拥有;奖励内容统一引用 `wallet-service` 资源组,不能把资源明细复制进签到配置。 ## 目标 - 后台 `活动管理` 下新增二级菜单 `七日签到`。 - 后台可以配置 1 到 7 天的奖励资源组,每天必须选择一个 `resource_group_id`。 - 后台可以打开或关闭七日签到;关闭后 App 只读状态,不允许新签到。 - App 提供七日签到面板查询接口和签到接口。 - 每个用户按 UTC 自然日每日最多签到一次。 - 连续签到按 7 天为一轮:第 7 天签到后,下一次签到开启新一轮第 1 天。 - 中断连续签到后下一次签到从第 1 天重新开始。 - 签到奖励发放必须通过 `wallet-service.GrantResourceGroup`,资源组中可以包含金币、钻石和装扮权益。 - 签到事实、奖励发放状态和幂等命令必须可审计、可恢复、可重试。 ## 非目标 - 首版不做补签、签到卡、广告补领或客户端本地时区签到。 - 首版不支持多套并行签到活动;每个 `app_code` 同一时间只有一套七日签到配置。 - 首版不做人群定向、地区定向、新老用户差异奖励。 - 首版不让客户端上报连续天数、奖励内容、资源组明细或服务端时间。 - 首版不把七日签到建成每日任务。每日任务依赖事实事件进度,签到是用户主动命令和每日幂等发奖。 - 首版不把签到奖励拆成金币字段;所有奖励都只表达为资源组。 ## 服务边界 | 模块 | 责任 | 不负责 | | --- | --- | --- | | `gateway-service` | App HTTP 入口、鉴权、业务 envelope、生成并透传 `request_id`、调用 activity gRPC、按需向 wallet 查询资源组展示信息 | 不判断连续天数,不写签到表,不发资源组 | | `activity-service` | 七日签到配置、配置版本、用户连续签到状态、签到 claim、发奖幂等、失败重试状态 | 不拥有资源组内容,不直接修改钱包余额或用户权益 | | `wallet-service` | 资源组 catalog、资源组有效性校验、`GrantResourceGroup` 原子展开资源和钱包资产、钱包/资源 outbox | 不判断用户今天能否签到,不保存签到连续天数 | | `server/admin` | 后台菜单、权限、后台 HTTP API、操作审计、调用 activity 管理 RPC,并复用资源组选择能力 | 不直接写 activity/wallet 业务库,不复制 app 服务内部包 | | Admin 前端 | `活动管理 > 七日签到` 页面、开关、7 行资源组选择、保存发布、领取记录列表 | 不自行拼资源组明细,不绕过后台权限 | | `notice-service` | 后续如需私有通知,从 activity outbox 消费签到奖励事实 | 不参与签到主链路 | 核心原则: - `activity-service` 是七日签到状态 owner。 - 资源组内容永远以 `wallet-service` 为准;签到配置只保存 `resource_group_id` 和发放时必要快照。 - 发奖是房间外副作用,必须通过幂等命令调用 wallet-service。 - 所有日期、周期、刷新时间使用 UTC,不读取服务器本地时区、用户手机时区或 IP 推断时区。 ## 总体架构 ```mermaid flowchart LR AdminFE["admin 前端\n活动管理 > 七日签到"] --> AdminServer["server/admin\n七日签到后台 API"] AdminServer --> AdminCheckIn["activity-service\nAdminSevenDayCheckInService"] AdminServer --> WalletRead["wallet-service\n资源组查询"] App["App 七日签到页"] --> Gateway["gateway-service\n/api/v1"] Gateway --> CheckInSvc["activity-service\nSevenDayCheckInService"] Gateway --> WalletGroup["wallet-service\n资源组展示查询"] CheckInSvc --> ActivityDB[("hyapp_activity\ncheckin tables")] CheckInSvc --> WalletGrant["wallet-service\nGrantResourceGroup"] WalletGrant --> WalletDB[("hyapp_wallet\nresource grants / wallet ledger")] CheckInSvc --> ActivityOutbox[("activity_outbox\noptional reward event")] ``` ## 产品规则 ### 签到周期 签到日使用 UTC 日期字符串 `yyyy-mm-dd`: | 场景 | 结果 | | --- | --- | | 用户从未签到 | 今天签到为第 1 天 | | 上次签到日是今天 | 返回已签到,不重复发奖 | | 上次签到日是昨天,且上次是第 1 到第 6 天 | 今天签到为下一天 | | 上次签到日是昨天,且上次是第 7 天 | 今天开启新一轮第 1 天 | | 上次签到日早于昨天 | 连续中断,今天重新从第 1 天开始 | 第 7 天之后不保持在第 7 天;下一次有效签到必须进入新一轮第 1 天,避免一个用户无限领取第 7 天奖励。 ### 配置版本 后台每次保存 7 天奖励或开关都生成新的 `config_version`: - App 查询总是展示当前配置版本。 - 用户开始一轮签到时绑定当时的 `config_version`。 - 同一轮后续签到继续使用该轮绑定版本,避免运营改配置后用户第 3 天突然看到另一套奖励。 - 第 7 天完成后下一轮读取最新 enabled 配置版本。 - 如果旧版本对应资源组后来被 wallet 下架,发奖时以 wallet-service 校验结果为准,失败 claim 进入可重试/待处理状态。 配置版本不是历史兼容负担,而是发奖审计和用户体验一致性的最小事实。 ### 开关 `enabled=false` 时: - App 查询返回当前配置和 `can_sign=false, reason=disabled`。 - 新的签到命令被拒绝。 - 已经创建的 pending/failed claim 仍允许后台或服务端补偿重试,避免开关关闭导致已接受签到无法收尾。 - 后台可以继续编辑 7 天奖励并保存新版本。 ### 奖励 - 每天奖励只允许一个资源组 ID。 - 资源组必须由后台从资源组列表选择;不允许手写资源 ID、金币数量、钻石数量或有效期。 - 资源组内容展示由 wallet-service 查询并返回,activity-service 只保存 ID 和发放时返回的 `grant_id`。 - 发放时调用 `wallet-service.GrantResourceGroup`,`grant_source=seven_day_checkin`,`reason=seven_day_checkin`。 - wallet-service 在发放事务内重新校验资源组 active、资源 active、钱包资产合法;gateway 和 admin 的前端筛选不能替代 owner 校验。 ## 后台管理 后台菜单: ```text 活动管理 - 每日任务 - 注册奖励 - 成就配置 - 七日签到 ``` 建议新增权限: | Permission | Kind | Usage | | --- | --- | --- | | `seven-day-checkin:view` | `menu` | 查看七日签到配置和领取记录 | | `seven-day-checkin:update` | `button` | 更新开关和 7 天奖励资源组 | 建议菜单: | 字段 | 值 | | --- | --- | | title | `七日签到` | | code | `seven-day-checkin` | | path | `/activities/seven-day-checkin` | | icon | `event_available` | | permission_code | `seven-day-checkin:view` | | sort | `72` | ### 页面结构 页面保留两个 Tab: | Tab | 内容 | | --- | --- | | `配置` | 顶部开关、7 行奖励配置、当前版本、更新时间、保存按钮 | | `领取记录` | 用户、签到日、第几天、轮次、配置版本、资源组、状态、wallet grant id、失败原因 | 配置 Tab 字段: | 字段 | 必填 | 说明 | | --- | --- | --- | | 开关 | 是 | 保存到 `enabled` | | 第 1 天资源组 | 是 | `resource_group_id > 0` | | 第 2 天资源组 | 是 | `resource_group_id > 0` | | 第 3 天资源组 | 是 | `resource_group_id > 0` | | 第 4 天资源组 | 是 | `resource_group_id > 0` | | 第 5 天资源组 | 是 | `resource_group_id > 0` | | 第 6 天资源组 | 是 | `resource_group_id > 0` | | 第 7 天资源组 | 是 | `resource_group_id > 0` | 领取记录筛选: | 字段 | 说明 | | --- | --- | | `keyword` | 用户 ID、展示 ID 或昵称,由 `server/admin` 解析成 user_id | | `status` | `pending` / `granted` / `failed` | | `checkin_day` | UTC 日期 | | `day_index` | 1 到 7 | ### 后台 API `server/admin` 对前端暴露: ```http GET /api/v1/admin/activity/seven-day-checkin/config PUT /api/v1/admin/activity/seven-day-checkin/config GET /api/v1/admin/activity/seven-day-checkin/claims ``` 更新配置请求: ```json { "enabled": true, "rewards": [ {"day_index": 1, "resource_group_id": 101}, {"day_index": 2, "resource_group_id": 102}, {"day_index": 3, "resource_group_id": 103}, {"day_index": 4, "resource_group_id": 104}, {"day_index": 5, "resource_group_id": 105}, {"day_index": 6, "resource_group_id": 106}, {"day_index": 7, "resource_group_id": 107} ] } ``` 规则: - `rewards` 必须刚好包含 1 到 7 天,不能缺失、重复或越界。 - `resource_group_id` 必须大于 0。 - Admin 前端资源组选择器复用现有资源组列表能力,只展示 active 资源组。 - `server/admin` 必须写操作审计,记录 `before_json`、`after_json`、操作人、request_id。 - `activity-service` 保存配置时只保存资源组 ID;资源组是否 active 由 wallet-service 在选择展示和最终发放时确认。 ## App 接口 App 只通过 gateway HTTP 访问: ```http GET /api/v1/activities/seven-day-checkin POST /api/v1/activities/seven-day-checkin/sign ``` ### 查询签到面板 ```http GET /api/v1/activities/seven-day-checkin Authorization: Bearer ``` 响应 data: ```json { "enabled": true, "can_sign": true, "reason": "eligible", "cycle_no": 3, "current_day_index": 2, "next_day_index": 3, "checkin_day": "2026-05-16", "last_checkin_day": "2026-05-15", "config_version": 8, "server_time_ms": 1778889600000, "next_refresh_at_ms": 1778976000000, "rewards": [ { "day_index": 1, "resource_group_id": 101, "status": "granted", "resource_group": {} }, { "day_index": 2, "resource_group_id": 102, "status": "granted", "resource_group": {} }, { "day_index": 3, "resource_group_id": 103, "status": "available", "resource_group": {} } ] } ``` `resource_group` 由 gateway 按 `resource_group_id` 调 wallet-service 查询后嵌入,结构沿用现有资源组 HTTP DTO。activity-service gRPC 只需要返回资源组 ID 和用户签到状态。 `current_day_index` 表示本轮最近已完成的天数,从未签到时返回 `0`。`next_day_index` 表示今天如果签到会领取第几天奖励。 `reason` 建议枚举: | reason | 含义 | | --- | --- | | `eligible` | 今天可签到 | | `disabled` | 活动关闭 | | `not_configured` | 未配置完整 7 天奖励 | | `already_signed` | 今天已签到 | | `pending_reward` | 今天签到已接受,奖励仍在处理中 | | `invalid_config` | 配置不可发放 | ### 执行签到 ```http POST /api/v1/activities/seven-day-checkin/sign Authorization: Bearer ``` 请求: ```json { "command_id": "cmd_checkin_550e8400" } ``` 规则: - `command_id` 由客户端按本次点击动作生成,同一次重试必须复用。 - gateway 不补 `command_id`,只裁剪空白并透传当前用户 ID。 - activity-service 决定今天是第几天、是否已签到、应发哪个资源组。 - 同一用户同一 UTC 日期最多一个签到 claim。 - 同一 `command_id` 重试且 payload 一致,返回同一 claim 结果。 响应 data: ```json { "claim_id": "checkin_claim_123", "cycle_no": 3, "checkin_day": "2026-05-16", "day_index": 3, "resource_group_id": 103, "status": "granted", "wallet_grant_id": "resource_grant_456", "granted_at_ms": 1778918500000, "server_time_ms": 1778918500000 } ``` ## 内部 gRPC 契约 建议在 `api/proto/activity/v1/activity.proto` 增加: ```proto message SevenDayCheckInReward { int32 day_index = 1; int64 resource_group_id = 2; } message SevenDayCheckInConfig { string app_code = 1; bool enabled = 2; int64 config_version = 3; repeated SevenDayCheckInReward rewards = 4; int64 updated_by_admin_id = 5; int64 created_at_ms = 6; int64 updated_at_ms = 7; } message SevenDayCheckInRewardStatus { int32 day_index = 1; int64 resource_group_id = 2; string status = 3; string claim_id = 4; string wallet_grant_id = 5; int64 granted_at_ms = 6; } message GetSevenDayCheckInStatusRequest { RequestMeta meta = 1; int64 user_id = 2; } message GetSevenDayCheckInStatusResponse { bool enabled = 1; bool can_sign = 2; string reason = 3; int64 cycle_no = 4; int32 current_day_index = 5; int32 next_day_index = 6; string checkin_day = 7; string last_checkin_day = 8; int64 config_version = 9; repeated SevenDayCheckInRewardStatus rewards = 10; int64 server_time_ms = 11; int64 next_refresh_at_ms = 12; } message SignSevenDayCheckInRequest { RequestMeta meta = 1; int64 user_id = 2; string command_id = 3; } message SignSevenDayCheckInResponse { string claim_id = 1; int64 cycle_no = 2; string checkin_day = 3; int32 day_index = 4; int64 resource_group_id = 5; string status = 6; string wallet_grant_id = 7; int64 granted_at_ms = 8; int64 server_time_ms = 9; } service SevenDayCheckInService { rpc GetSevenDayCheckInStatus(GetSevenDayCheckInStatusRequest) returns (GetSevenDayCheckInStatusResponse); rpc SignSevenDayCheckIn(SignSevenDayCheckInRequest) returns (SignSevenDayCheckInResponse); } ``` 后台管理 RPC: ```proto message GetSevenDayCheckInConfigRequest { RequestMeta meta = 1; } message GetSevenDayCheckInConfigResponse { SevenDayCheckInConfig config = 1; } message UpdateSevenDayCheckInConfigRequest { RequestMeta meta = 1; bool enabled = 2; repeated SevenDayCheckInReward rewards = 3; int64 operator_admin_id = 4; } message UpdateSevenDayCheckInConfigResponse { SevenDayCheckInConfig config = 1; } message SevenDayCheckInClaim { string claim_id = 1; string command_id = 2; int64 user_id = 3; int64 cycle_no = 4; string checkin_day = 5; int32 day_index = 6; int64 config_version = 7; int64 resource_group_id = 8; string wallet_command_id = 9; string wallet_grant_id = 10; string status = 11; string failure_reason = 12; int64 signed_at_ms = 13; int64 granted_at_ms = 14; int64 created_at_ms = 15; int64 updated_at_ms = 16; } message ListSevenDayCheckInClaimsRequest { RequestMeta meta = 1; string status = 2; int64 user_id = 3; string checkin_day = 4; int32 day_index = 5; int32 page = 6; int32 page_size = 7; } message ListSevenDayCheckInClaimsResponse { repeated SevenDayCheckInClaim claims = 1; int64 total = 2; } service AdminSevenDayCheckInService { rpc GetSevenDayCheckInConfig(GetSevenDayCheckInConfigRequest) returns (GetSevenDayCheckInConfigResponse); rpc UpdateSevenDayCheckInConfig(UpdateSevenDayCheckInConfigRequest) returns (UpdateSevenDayCheckInConfigResponse); rpc ListSevenDayCheckInClaims(ListSevenDayCheckInClaimsRequest) returns (ListSevenDayCheckInClaimsResponse); } ``` 修改 proto 后必须运行: ```bash make proto go test ./... ``` ## 数据模型 所有表位于 `hyapp_activity`,字段使用 UTC epoch milliseconds 和 UTC 日期字符串。 ### `seven_day_checkin_configs` 当前配置入口。 | 字段 | 说明 | | --- | --- | | `app_code` | App 租户,主键 | | `enabled` | 活动开关 | | `config_version` | 当前配置版本 | | `updated_by_admin_id` | 最近操作人 | | `created_at_ms` | 创建时间 | | `updated_at_ms` | 更新时间 | ### `seven_day_checkin_rewards` 当前版本 7 天奖励读模型。 | 字段 | 说明 | | --- | --- | | `app_code` | App 租户 | | `day_index` | 1 到 7 | | `config_version` | 当前版本 | | `resource_group_id` | wallet-service 资源组 ID | | `created_at_ms` | 创建时间 | | `updated_at_ms` | 更新时间 | 唯一键: ```text PRIMARY KEY(app_code, day_index) ``` ### `seven_day_checkin_config_versions` 配置版本快照。 | 字段 | 说明 | | --- | --- | | `app_code` | App 租户 | | `config_version` | 版本号 | | `enabled` | 当时开关 | | `snapshot_json` | 7 天奖励资源组快照 | | `created_by_admin_id` | 操作人 | | `created_at_ms` | 创建时间 | 唯一键: ```text PRIMARY KEY(app_code, config_version) ``` ### `user_seven_day_checkin_accounts` 用户连续签到账户。 | 字段 | 说明 | | --- | --- | | `app_code` | App 租户 | | `user_id` | 用户 ID | | `cycle_no` | 当前轮次,从 1 开始 | | `cycle_start_day` | 本轮第 1 天 UTC 日期 | | `last_checkin_day` | 最近成功签到 UTC 日期 | | `last_day_index` | 最近成功签到是第几天 | | `config_version` | 本轮绑定配置版本 | | `updated_at_ms` | 更新时间 | 唯一键: ```text PRIMARY KEY(app_code, user_id) ``` ### `seven_day_checkin_claims` 每次签到和资源组发放事实。 | 字段 | 说明 | | --- | --- | | `claim_id` | 主键 | | `command_id` | 客户端签到动作幂等键 | | `app_code` | App 租户 | | `user_id` | 用户 ID | | `cycle_no` | 轮次 | | `checkin_day` | UTC 签到日 | | `day_index` | 1 到 7 | | `config_version` | 发奖使用的配置版本 | | `resource_group_id` | 发奖资源组快照 | | `wallet_command_id` | 调用 wallet-service 的幂等键 | | `wallet_grant_id` | wallet-service 返回的资源组 grant ID | | `status` | `pending` / `granted` / `failed` | | `failure_reason` | 失败原因 | | `signed_at_ms` | 签到命令接受时间 | | `granted_at_ms` | 奖励发放完成时间 | | `created_at_ms` | 创建时间 | | `updated_at_ms` | 更新时间 | 唯一键: ```text PRIMARY KEY(app_code, claim_id) UNIQUE(app_code, command_id) UNIQUE(app_code, user_id, checkin_day) UNIQUE(app_code, user_id, cycle_no, day_index) ``` `UNIQUE(app_code, user_id, checkin_day)` 保证自然日最多一次;`UNIQUE(app_code, user_id, cycle_no, day_index)` 保证同一轮同一天位不会被并发重复发放。 ## 签到流程 ```mermaid sequenceDiagram participant App participant Gateway participant Activity as activity-service participant DB as activity MySQL participant Wallet as wallet-service App->>Gateway: POST /activities/seven-day-checkin/sign(command_id) Gateway->>Activity: SignSevenDayCheckIn(user_id, command_id) Activity->>DB: 锁 config + user account,计算 checkin_day/day_index Activity->>DB: INSERT claim pending(wallet_command_id) Activity->>Wallet: GrantResourceGroup(wallet_command_id, resource_group_id) Wallet-->>Activity: grant_id Activity->>DB: 锁 claim + account,更新 account 和 claim=granted Activity-->>Gateway: granted Gateway-->>App: OK ``` 关键事务边界: 1. Prepare 事务只创建或复用 `pending` claim,不更新连续签到账户。 2. wallet-service 发放资源组使用稳定 `wallet_command_id`,重复调用必须幂等。 3. Finalize 事务在 wallet 成功后更新 `user_seven_day_checkin_accounts` 和 claim 状态。 4. 如果 wallet 成功但 finalize 失败,用户重试同一 `command_id` 时 wallet 返回幂等结果,activity 再完成 finalize。 5. 如果 wallet 失败,claim 标记 `failed`,账户不前进;同一 `command_id` 重试应重新尝试发放该 claim,后台补偿也只能重试同一个 claim。 这样做的原因是签到连续天数必须和奖励成功保持一致。不能出现 activity 已经把用户推进到第 3 天,但第 3 天资源组没有发出的状态。 ## 查询流程 ```mermaid sequenceDiagram participant App participant Gateway participant Activity as activity-service participant Wallet as wallet-service App->>Gateway: GET /activities/seven-day-checkin Gateway->>Activity: GetSevenDayCheckInStatus(user_id) Activity-->>Gateway: config ids + user status Gateway->>Wallet: GetResourceGroup for visible reward ids Wallet-->>Gateway: resource group DTOs Gateway-->>App: envelope(data) ``` 查询只读 activity 配置、用户账户和最近 claims,不实时扫描钱包流水。资源组展示信息由 gateway 使用 wallet 查询结果补齐。 ## 幂等和并发 - `command_id` 是客户端点击动作幂等键。 - `wallet_command_id` 建议格式:`seven_day_checkin:{claim_id}`。 - 同一 `command_id` 重试返回同一 claim。 - 同一用户同一 `checkin_day` 并发请求只能创建一个 claim。 - 同一用户同一 `cycle_no + day_index` 只能有一个 granted/pending claim。 - claim payload 冲突时返回 `CONFLICT`,不能静默重写历史 claim。 - 查询接口不产生签到事实。 ## 时间规则 - `checkin_day` 按 UTC 日期计算。 - `next_refresh_at_ms` 是下一天 UTC 00:00:00.000 的 epoch milliseconds。 - 所有业务时间字段使用 `*_ms`。 - 领取记录筛选的日期字段是 UTC 日期,不接受本地时区字符串。 - MySQL DSN、Docker `TZ` 和数据库默认时区继续保持 UTC。 ## 错误和状态 | 状态或错误 | 场景 | 处理 | | --- | --- | --- | | `disabled` | 活动关闭 | 查询可见,签到拒绝 | | `not_configured` | 未配置完整 7 天奖励 | 查询提示不可签,签到拒绝 | | `already_signed` | 当天已有 granted/pending claim | 返回已有状态 | | `pending` | claim 已创建,奖励未完成 | 可重试或后台补偿 | | `failed` | wallet 发放失败或配置不可发放 | 记录原因,账户不前进 | | `CONFLICT` | 同一 command_id payload 不一致 | 返回冲突 | | `INVALID_ARGUMENT` | command_id 空、day_index 越界、资源组 ID 无效 | 返回参数错误 | ## Outbox 和通知 签到发奖成功后,activity-service 可以写 `activity_outbox`: | Event | Payload | | --- | --- | | `SevenDayCheckInRewardGranted` | `claim_id`、`user_id`、`cycle_no`、`checkin_day`、`day_index`、`resource_group_id`、`wallet_grant_id` | | `SevenDayCheckInRewardFailed` | `claim_id`、`user_id`、`checkin_day`、`failure_reason` | 首版不要求实时私信通知。后续如果产品需要奖励到账通知,由 `notice-service` 消费 activity outbox 并投递,不让 activity-service 直接耦合私有通知实现。 ## 实现顺序 1. 文档和契约:确认本架构后修改 `api/proto/activity/v1/activity.proto`,运行 `make proto`。 2. activity-service:增加 domain/service/repository/gRPC server 和 MySQL initdb 表。 3. wallet 集成:复用 `GrantResourceGroup`,补齐七日签到发放 reason/source 和测试。 4. gateway-service:增加签到查询和签到 HTTP,接入 activity client,并按资源组 ID 补齐展示 DTO。 5. server/admin:增加后台模块、路由、权限、菜单 migration、审计和 activity 管理 client。 6. Admin 前端:增加 `/activities/seven-day-checkin` 页面,复用资源组选择器。 7. 补偿:增加 pending/failed claim 重试批处理;由 cron-service 触发 activity-service owner 批处理。 8. 验证:补 UTC 边界、并发幂等、wallet 失败、配置版本切换和后台权限测试。 ## 测试要求 至少覆盖: | 场景 | 验证 | | --- | --- | | UTC 当天首次签到 | 创建 claim,发资源组,账户推进到第 1 天 | | 同一天重复签到 | 不重复发奖,返回已有 claim | | 连续 7 天签到 | day_index 从 1 到 7,下一天回到第 1 天并 cycle_no + 1 | | 中断一天后签到 | day_index 重置为 1,新 cycle_no | | UTC 边界 | `23:59:59.999` 和 `00:00:00.000` 分属不同 checkin_day | | 开关关闭 | 查询可见但不能签到 | | 配置缺失 | 不能打开或不能签到 | | 修改配置版本 | 已开始轮次继续旧版本,新轮次使用新版本 | | wallet GrantResourceGroup 失败 | claim failed,账户不前进,可重试 | | finalize 失败后重试 | wallet 幂等返回后 activity 完成 finalize | | admin 权限 | 无 `seven-day-checkin:update` 不能保存配置 | 涉及 proto 和代码实现后运行: ```bash make proto make test go test ./... ``` 涉及后台菜单 migration 或配置时额外运行: ```bash docker compose config ``` ## 待确认产品项 1. 是否需要补签。当前架构明确首版不支持。 2. 第 7 天之后是否循环回第 1 天。当前架构按循环处理。 3. 中断后是否重置到第 1 天。当前架构按重置处理。 4. 是否需要领取记录导出。当前只设计分页查询。 5. 是否需要发奖成功后的 App 活动消息。当前只预留 outbox,不默认投递。