573 lines
18 KiB
Markdown
573 lines
18 KiB
Markdown
# 每日任务系统架构方案
|
||
|
||
本文定义 App 每日任务和专属任务的产品边界、服务归属、事件来源、数据模型、后台管理和关键异常处理。
|
||
|
||
## 目标
|
||
|
||
- 后台增加 `活动管理` 一级菜单,`每日任务` 作为活动管理下的二级菜单。
|
||
- `每日任务` 页面包含两个 Tab:`每日任务`、`专属任务`。
|
||
- 每日任务按 UTC 自然日刷新,当天只能领取一次,超额完成不结转到明天。
|
||
- 专属任务是用户维度终身只能完成并领取一次的任务。
|
||
- 任务奖励统一发金币,金币发放必须走 `wallet-service` 账本。
|
||
- 任务进度由服务端事实事件驱动,客户端不能上报任务进度。
|
||
|
||
## 非目标
|
||
|
||
- 首版不做客户端自定义任务排序。
|
||
- 首版不做多奖励类型;奖励只支持金币。
|
||
- 首版不做任务进度回滚;退款、撤销、CP 解除等后续事件进入风控和补偿,不直接减已完成任务。
|
||
- 首版不让 room-service、wallet-service、user-service 同步调用任务系统更新进度。
|
||
|
||
## 服务边界
|
||
|
||
| 模块 | 责任 | 不负责 |
|
||
| --- | --- | --- |
|
||
| `server/admin` | 菜单、后台 HTTP API、任务配置管理、启停、排序、查询统计 | 不直接写 app 业务库;通过 protobuf client 或明确集成接口访问后端 |
|
||
| `activity-service` | 任务定义、任务版本、任务进度、完成状态、领奖状态、任务事件消费幂等 | 不直接改钱包余额,不拥有房间/钱包/用户基础事实 |
|
||
| `wallet-service` | 金币奖励入账、账本幂等、奖励流水 | 不判断任务是否完成 |
|
||
| `user-service` | CP 关系事实、用户麦上时长事实 | 不判断任务奖励 |
|
||
| `room-service` | 房间和麦位状态 owner,产生房间 outbox | 不同步更新任务 |
|
||
|
||
建议首版把任务系统放在 `activity-service`,因为它已经承接活动、消息、fanout 等低频活动能力。后续如果任务规模独立膨胀,再拆 `task-service`。
|
||
|
||
## 总体架构
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
Admin["admin 活动管理 > 每日任务"] --> AdminAPI["admin-server task APIs"]
|
||
AdminAPI --> ActivityAdmin["activity-service AdminTaskService"]
|
||
|
||
Room["room-service outbox"] --> UserMic["user-service mic time consumer"]
|
||
UserMic --> UserOutbox["user-service user_outbox"]
|
||
Wallet["wallet-service outbox"] --> TaskConsumer["activity-service task consumers"]
|
||
UserOutbox --> TaskConsumer
|
||
CP["user-service CP outbox"] --> TaskConsumer
|
||
|
||
TaskConsumer --> TaskDB["activity MySQL task tables"]
|
||
App["App task page"] --> Gateway["gateway-service"]
|
||
Gateway --> ActivityQuery["activity-service TaskQueryService"]
|
||
Gateway --> ActivityClaim["activity-service TaskClaimService"]
|
||
ActivityClaim --> WalletGrant["wallet-service CreditTaskReward"]
|
||
```
|
||
|
||
核心原则:
|
||
|
||
- 任务系统只消费事实事件,不参与房间、钱包、CP 主链路。
|
||
- 金币奖励只通过钱包账本发放。
|
||
- 每个事件必须有稳定 `event_id`,任务消费必须幂等。
|
||
- 任务完成和领奖是两个状态,完成不等于已经发奖。
|
||
|
||
## 任务类型
|
||
|
||
| 分类 | 进度指标 | 事件来源 | 口径 |
|
||
| --- | --- | --- | --- |
|
||
| CP 任务 | 组成 CP 次数 | user-service CP 关系创建事件 | 以 `CpRelationshipCreated` 为准,解除 CP 不回滚 |
|
||
| 上麦任务 | 麦上有效分钟数 | user-service 麦位时长聚合事件 | 使用 `mic_online_ms`,不是单纯占麦时间 |
|
||
| 游戏任务 | 游戏消耗金币数 | wallet-service 游戏扣费事件 | 只统计成功扣费的 COIN |
|
||
| 送礼任务 | 送礼消耗金币数 | wallet-service 送礼扣费事件 | 使用钱包扣费金额,不能用客户端礼物价格 |
|
||
| 幸运礼物任务 | 幸运礼物消耗金币数或中奖金币数 | wallet-service 幸运礼物结算事件 | 首版一个任务只配置一种指标:`spend_coin` 或 `win_coin` |
|
||
| 充值任务 | 充值金币数 | wallet-service 充值成功事件 | Google、Apple、三方支付、币商转普通金币都可以按配置纳入口径 |
|
||
|
||
### 上麦任务口径
|
||
|
||
上麦任务默认读取 `user_mic_daily_stats.mic_online_ms` 或消费 `UserMicSessionClosed` 派生事件。`mic_online_ms` 只从确认发流开始累计,未发声、只占麦、pending_publish 不计入有效麦上时间。
|
||
|
||
跨天上麦必须按 UTC 自然日拆分到不同 `task_day`,不能把整段时长归到下麦当天。
|
||
|
||
### 幸运礼物任务口径
|
||
|
||
幸运礼物任务需要避免把“消耗”和“中奖”混在同一个字段:
|
||
|
||
| 指标 | 含义 |
|
||
| --- | --- |
|
||
| `lucky_gift_spend_coin` | 用户购买/发送幸运礼物实际消耗金币 |
|
||
| `lucky_gift_win_coin` | 用户因幸运礼物中奖获得的金币价值 |
|
||
|
||
如果运营需要“消耗 n 金币且中奖 n 金币”,应在第二阶段支持多条件 AND。首版建议一个任务一个指标,减少配置歧义。
|
||
|
||
## 任务周期
|
||
|
||
### 每日任务
|
||
|
||
- 周期 key 使用 UTC 自然日计算,例如 UTC 下的 `2026-05-09`。
|
||
- 任务周期不读取机器本地时区、用户手机时区或请求 IP 所在时区。
|
||
- 事件归属日按 `occurred_at_ms` 转 UTC 计算,不按消费时间。
|
||
- 用户当天完成后只能领取一次。
|
||
- 当天超额完成只保留当天进度,不结转到明天。
|
||
- 每日刷新不需要批量清空用户数据;查询时按当前 `task_day` 读取或懒创建进度。
|
||
|
||
### 专属任务
|
||
|
||
- 周期 key 固定为 `lifetime`。
|
||
- 用户终身只能完成一次、领取一次。
|
||
- 首版可作为全体用户可见的 lifetime 任务。
|
||
- 如果后续要“指定用户专属”,再增加任务分配表,不要把用户列表塞进任务定义 JSON。
|
||
|
||
## 后台管理
|
||
|
||
后台菜单:
|
||
|
||
```text
|
||
活动管理
|
||
- 每日任务
|
||
Tab 1: 每日任务
|
||
Tab 2: 专属任务
|
||
```
|
||
|
||
### 列表字段
|
||
|
||
| 字段 | 说明 |
|
||
| --- | --- |
|
||
| 任务 ID | 系统生成 |
|
||
| 任务名称 | App 展示名 |
|
||
| 任务分类 | CP / 上麦 / 游戏 / 送礼 / 幸运礼物 / 充值 |
|
||
| 任务类型 | 每日任务 / 专属任务 |
|
||
| 目标值 | n 次、n 分钟、n 金币 |
|
||
| 奖励金币 | 完成后领取的金币数 |
|
||
| 状态 | draft / active / paused / archived |
|
||
| 生效时间 | 可选 |
|
||
| 结束时间 | 可选 |
|
||
| 排序值 | App 列表排序 |
|
||
| 当前版本 | 配置版本 |
|
||
| 创建人/更新时间 | 审计 |
|
||
|
||
### 新增/编辑任务
|
||
|
||
后台表单:
|
||
|
||
| 字段 | 必填 | 说明 |
|
||
| --- | --- | --- |
|
||
| 任务类型 | 是 | daily / exclusive |
|
||
| 任务分类 | 是 | 固定枚举 |
|
||
| 任务名称 | 是 | App 展示 |
|
||
| 任务描述 | 否 | App 展示 |
|
||
| 指标类型 | 是 | 按分类收敛,例如 `mic_online_minutes`、`gift_spend_coin` |
|
||
| 目标值 | 是 | 必须大于 0 |
|
||
| 奖励金币 | 是 | 必须大于 0 |
|
||
| 排序值 | 是 | 小值靠前 |
|
||
| 生效/结束时间 | 否 | 控制任务上下架窗口 |
|
||
| 状态 | 是 | draft 保存草稿,active 对 App 生效 |
|
||
|
||
编辑 active 任务时建议生成新版本。每日任务的新版本默认从下一个 `task_day` 生效,避免当天用户看到目标值突然变化。
|
||
|
||
## App 接口
|
||
|
||
### 查询任务列表
|
||
|
||
```text
|
||
GET /api/v1/tasks/tabs
|
||
```
|
||
|
||
返回两个分区:
|
||
|
||
- `daily`
|
||
- `exclusive`
|
||
|
||
每个任务返回:
|
||
|
||
| 字段 | 说明 |
|
||
| --- | --- |
|
||
| `task_id` | 任务 ID |
|
||
| `task_type` | daily / exclusive |
|
||
| `category` | 任务分类 |
|
||
| `title` | 展示名称 |
|
||
| `description` | 展示描述 |
|
||
| `target_value` | 目标值 |
|
||
| `progress_value` | 当前进度 |
|
||
| `reward_coin_amount` | 奖励金币 |
|
||
| `status` | in_progress / completed / claimed / expired |
|
||
| `claimable` | 是否可领取 |
|
||
| `task_day` | daily 任务返回当前日期 |
|
||
| `server_time_ms` | 服务端时间 |
|
||
| `next_refresh_at_ms` | 下次刷新时间 |
|
||
|
||
### 领取奖励
|
||
|
||
```text
|
||
POST /api/v1/tasks/claim
|
||
```
|
||
|
||
请求:
|
||
|
||
```json
|
||
{
|
||
"task_id": "123",
|
||
"task_type": "daily",
|
||
"task_day": "2026-05-09",
|
||
"command_id": "cmd_task_claim_550e8400"
|
||
}
|
||
```
|
||
|
||
规则:
|
||
|
||
- 必须已完成。
|
||
- 必须未领取。
|
||
- daily 只能领取当前 `task_day` 的任务。
|
||
- exclusive 只能领取 `lifetime` 任务。
|
||
- `command_id` 由客户端按领取动作生成,同一次动作重试必须复用,重复请求返回同一结果。
|
||
|
||
可选第二阶段增加:
|
||
|
||
```text
|
||
POST /api/v1/tasks/claim-all
|
||
```
|
||
|
||
## 数据模型
|
||
|
||
### `task_definitions`
|
||
|
||
任务定义当前版本的读模型。
|
||
|
||
| 字段 | 说明 |
|
||
| --- | --- |
|
||
| `task_id` | 主键 |
|
||
| `app_code` | App 租户 |
|
||
| `task_type` | daily / exclusive |
|
||
| `category` | cp / mic / game / gift / lucky_gift / recharge |
|
||
| `metric_type` | 具体指标 |
|
||
| `title` | 展示名称 |
|
||
| `description` | 展示描述 |
|
||
| `target_value` | 目标值 |
|
||
| `target_unit` | count / minute / coin |
|
||
| `reward_coin_amount` | 奖励金币 |
|
||
| `status` | draft / active / paused / archived |
|
||
| `sort_order` | 排序 |
|
||
| `version` | 当前版本 |
|
||
| `effective_from_ms` | 生效时间 |
|
||
| `effective_to_ms` | 结束时间 |
|
||
| `created_by_admin_id` | 创建人 |
|
||
| `updated_by_admin_id` | 更新人 |
|
||
| `created_at_ms` | 创建时间 |
|
||
| `updated_at_ms` | 更新时间 |
|
||
|
||
### `task_definition_versions`
|
||
|
||
保存历史版本,保证事件和进度可以追溯。
|
||
|
||
| 字段 | 说明 |
|
||
| --- | --- |
|
||
| `task_version_id` | 主键 |
|
||
| `task_id` | 任务 ID |
|
||
| `version` | 版本号 |
|
||
| `snapshot_json` | 任务配置快照 |
|
||
| `effective_from_cycle` | 从哪个 daily cycle 或 lifetime 生效 |
|
||
| `created_at_ms` | 创建时间 |
|
||
|
||
### `user_task_progress`
|
||
|
||
用户任务进度。
|
||
|
||
| 字段 | 说明 |
|
||
| --- | --- |
|
||
| `app_code` | App 租户 |
|
||
| `user_id` | 用户 ID |
|
||
| `task_id` | 任务 ID |
|
||
| `task_version_id` | 任务版本 |
|
||
| `cycle_key` | daily 使用 `yyyy-mm-dd`,exclusive 使用 `lifetime` |
|
||
| `progress_value` | 当前进度,可超过目标值 |
|
||
| `target_value` | 进度创建时的目标快照 |
|
||
| `status` | in_progress / completed / claimed / expired |
|
||
| `completed_at_ms` | 完成时间 |
|
||
| `claimed_at_ms` | 领取时间 |
|
||
| `updated_at_ms` | 更新时间 |
|
||
|
||
唯一键:
|
||
|
||
```text
|
||
UNIQUE(app_code, user_id, task_id, cycle_key)
|
||
```
|
||
|
||
### `task_event_consumption`
|
||
|
||
事件幂等表。
|
||
|
||
| 字段 | 说明 |
|
||
| --- | --- |
|
||
| `app_code` | App 租户 |
|
||
| `event_id` | 来源事件 ID |
|
||
| `event_type` | 来源事件类型 |
|
||
| `source_service` | room / wallet / user |
|
||
| `user_id` | 事件归属用户 |
|
||
| `task_day` | UTC 日期 |
|
||
| `status` | consumed / skipped / failed |
|
||
| `skip_reason` | 跳过原因 |
|
||
| `created_at_ms` | 创建时间 |
|
||
| `consumed_at_ms` | 消费时间 |
|
||
|
||
主键:
|
||
|
||
```text
|
||
PRIMARY KEY(app_code, event_id)
|
||
```
|
||
|
||
### `task_reward_claims`
|
||
|
||
领奖幂等和钱包发奖状态。
|
||
|
||
| 字段 | 说明 |
|
||
| --- | --- |
|
||
| `claim_id` | 主键 |
|
||
| `app_code` | App 租户 |
|
||
| `command_id` | 领奖幂等键 |
|
||
| `user_id` | 用户 ID |
|
||
| `task_id` | 任务 ID |
|
||
| `cycle_key` | daily 日期或 lifetime |
|
||
| `reward_coin_amount` | 奖励金币快照 |
|
||
| `wallet_command_id` | 钱包发奖幂等键 |
|
||
| `status` | pending / granted / failed |
|
||
| `failure_reason` | 失败原因 |
|
||
| `created_at_ms` | 创建时间 |
|
||
| `updated_at_ms` | 更新时间 |
|
||
|
||
唯一键:
|
||
|
||
```text
|
||
UNIQUE(app_code, command_id)
|
||
UNIQUE(app_code, user_id, task_id, cycle_key)
|
||
```
|
||
|
||
## 事件消费流程
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Source as Source outbox
|
||
participant Task as activity task consumer
|
||
participant DB as activity MySQL
|
||
|
||
Source->>Task: event(event_id, user_id, metric, value, occurred_at_ms)
|
||
Task->>DB: INSERT task_event_consumption(event_id)
|
||
alt event_id 已存在
|
||
DB-->>Task: duplicate
|
||
Task-->>Source: ack
|
||
else 新事件
|
||
Task->>DB: 查 active task definitions
|
||
Task->>DB: upsert user_task_progress
|
||
Task->>DB: progress >= target 时标记 completed
|
||
Task->>DB: task_event_consumption = consumed
|
||
Task-->>Source: ack
|
||
end
|
||
```
|
||
|
||
进度更新必须在一个数据库事务内完成:
|
||
|
||
1. 写入 `task_event_consumption`。
|
||
2. 找到事件命中的 active 任务。
|
||
3. 按 `user_id + task_id + cycle_key` 锁定或创建进度行。
|
||
4. 增加进度。
|
||
5. 如果达到目标,标记 `completed`。
|
||
6. 标记事件已消费。
|
||
|
||
如果事件没有匹配任何任务,写 `skipped`,避免重复消费同一个无效事件。
|
||
|
||
## 领奖流程
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant App
|
||
participant Gateway
|
||
participant Activity as activity-service
|
||
participant DB as activity MySQL
|
||
participant Wallet as wallet-service
|
||
|
||
App->>Gateway: POST /tasks/claim
|
||
Gateway->>Activity: ClaimTaskReward
|
||
Activity->>DB: 锁 user_task_progress
|
||
DB-->>Activity: completed and not claimed
|
||
Activity->>DB: 创建 task_reward_claims(pending)
|
||
Activity->>Wallet: CreditTaskReward(wallet_command_id)
|
||
Wallet-->>Activity: granted
|
||
Activity->>DB: progress=claimed, claim=granted
|
||
Activity-->>Gateway: reward granted
|
||
Gateway-->>App: OK
|
||
```
|
||
|
||
钱包调用失败时:
|
||
|
||
- `task_reward_claims` 保持 `pending` 或 `failed`。
|
||
- `user_task_progress` 不应标记 `claimed`。
|
||
- 用户重试同一 `command_id` 返回当前状态。
|
||
- 后台可以扫描 `pending/failed` claim 重试。
|
||
|
||
## 边界和补充规则
|
||
|
||
### 1. UTC 周期
|
||
|
||
所有 daily 周期都按 UTC 计算:
|
||
|
||
- `task_day`
|
||
- `cycle_start_ms`
|
||
- `cycle_end_ms`
|
||
- `next_refresh_at_ms`
|
||
|
||
不要使用用户手机时区、请求 IP 所在时区或服务器部署所在地时区。
|
||
|
||
### 2. 任务定义修改
|
||
|
||
active 任务修改目标值或奖励时,必须生成新版本。
|
||
|
||
建议规则:
|
||
|
||
- 文案、排序可以立即生效。
|
||
- 目标值、奖励金币、指标类型从下一个 daily cycle 生效。
|
||
- 专属任务修改后只影响未开始用户,已完成或已领取用户不重算。
|
||
|
||
### 3. 延迟事件
|
||
|
||
事件按 `occurred_at_ms` 归属日期。
|
||
|
||
建议首版:
|
||
|
||
- 当前 task_day 之前的迟到事件可以记录到历史进度。
|
||
- App 只展示当前 daily 任务。
|
||
- daily 奖励只允许在当前 task_day 领取。
|
||
- 是否允许“昨日补领”不要默认支持,后续作为后台补偿能力单独设计。
|
||
|
||
### 4. 超额完成
|
||
|
||
`progress_value` 可以超过 `target_value`,便于展示真实进度,例如 `120/100`。
|
||
|
||
但:
|
||
|
||
- daily 超额不结转。
|
||
- reward 仍只发一次。
|
||
- exclusive 完成后不再重复累计领取。
|
||
|
||
### 5. 任务可见性
|
||
|
||
首版所有 active 任务对当前 App 全体用户可见。
|
||
|
||
后续可扩展:
|
||
|
||
- 地区可见。
|
||
- 新用户可见。
|
||
- VIP 可见。
|
||
- 指定用户可见。
|
||
- 主播/Agency/BD 身份可见。
|
||
|
||
这些应通过 `task_audience_rules` 扩展,不要把复杂人群条件塞进基础任务表。
|
||
|
||
### 6. CP 任务解除关系
|
||
|
||
CP 任务以成功组成 CP 的事件为准。后续解除 CP 不回滚已完成进度,避免用户完成后被动丢奖励。
|
||
|
||
如果运营需要“当前仍保持 CP”,应该设计状态型任务,不要复用事件计数任务。
|
||
|
||
### 7. 上麦任务跨天
|
||
|
||
跨天麦位 session 必须拆分到不同 `task_day`。
|
||
|
||
例如 UTC 23:50 上麦,次日 UTC 00:10 下麦:
|
||
|
||
```text
|
||
前一天 +10 分钟
|
||
后一天 +10 分钟
|
||
```
|
||
|
||
### 8. 充值退款和拒付
|
||
|
||
充值任务按成功充值事件完成。Google/Apple 拒付、退款、风控追回不直接回滚已领任务奖励。
|
||
|
||
如果后续需要严格财务反作弊,应新增:
|
||
|
||
- 充值奖励冻结期。
|
||
- 领奖后拒付追扣。
|
||
- 风控黑名单。
|
||
|
||
首版不建议把这些复杂规则放进每日任务主链路。
|
||
|
||
### 9. 金币奖励发放
|
||
|
||
activity-service 不能直接写钱包余额。
|
||
|
||
钱包发奖命令必须包含:
|
||
|
||
| 字段 | 说明 |
|
||
| --- | --- |
|
||
| `wallet_command_id` | 幂等键 |
|
||
| `user_id` | 领取用户 |
|
||
| `asset_type` | COIN |
|
||
| `amount` | 奖励金币 |
|
||
| `reason` | `daily_task_reward` / `exclusive_task_reward` |
|
||
| `biz_id` | `task_id + cycle_key` |
|
||
|
||
### 10. 幂等
|
||
|
||
必须有三层幂等:
|
||
|
||
- 来源事件:`task_event_consumption(event_id)`。
|
||
- 用户进度:`user_task_progress(user_id, task_id, cycle_key)`。
|
||
- 领奖:`task_reward_claims(command_id)` 和 `task_reward_claims(user_id, task_id, cycle_key)`。
|
||
|
||
### 11. 删除任务
|
||
|
||
后台不做物理删除。
|
||
|
||
状态流转:
|
||
|
||
```text
|
||
draft -> active -> paused -> archived
|
||
```
|
||
|
||
`archived` 后 App 不展示,但历史进度、领奖记录仍可审计。
|
||
|
||
### 12. 任务排序
|
||
|
||
建议排序:
|
||
|
||
1. 可领取任务。
|
||
2. 已完成未领取任务。
|
||
3. 进行中任务。
|
||
4. 已领取任务。
|
||
5. 同状态按 `sort_order`。
|
||
|
||
### 13. 任务进度查询性能
|
||
|
||
App 查询任务列表时不要实时扫钱包流水或房间事件。
|
||
|
||
查询路径只读:
|
||
|
||
```text
|
||
task_definitions + user_task_progress
|
||
```
|
||
|
||
没有进度行时返回 0,不必提前为所有用户初始化每日任务。
|
||
|
||
### 14. 多 App 隔离
|
||
|
||
所有任务定义、进度、事件消费、领奖记录都必须带 `app_code`。
|
||
|
||
任务 ID 不能跨 App 复用语义。
|
||
|
||
### 15. 管理端审计
|
||
|
||
后台每次创建、启停、修改任务都要写审计:
|
||
|
||
- operator_admin_id
|
||
- request_id
|
||
- before_json
|
||
- after_json
|
||
- created_at_ms
|
||
|
||
任务配置影响发奖,必须可追溯。
|
||
|
||
## 推荐实现顺序
|
||
|
||
1. Admin 菜单和权限:`活动管理` 一级菜单下增加 `每日任务` 二级菜单。
|
||
2. activity-service 增加任务定义表、版本表、进度表、事件消费表、领奖表。
|
||
3. Admin API:每日任务/专属任务列表、新增、编辑、启停、排序。
|
||
4. App 查询接口:返回 daily/exclusive 两个分区和用户进度。
|
||
5. 钱包金币奖励接口:`CreditTaskReward` 幂等入账。
|
||
6. 领奖接口:完成校验、claim 幂等、调用钱包发奖。
|
||
7. 接入充值、送礼、游戏、幸运礼物事件。
|
||
8. 接入上麦时长事件或 user-service 麦时长 read model。
|
||
9. 接入 CP 创建事件。
|
||
10. 增加补偿任务:扫描 pending claim、failed event、消费延迟。
|
||
|
||
## 需要提前确认的问题
|
||
|
||
1. 幸运礼物任务首版是“消耗金币”或“中奖金币”二选一,还是需要 AND 条件。
|
||
2. 充值任务是否包含币商转金币、后台补偿、活动赠币;建议首版只包含真实充值和币商转普通金币。
|
||
3. daily 任务是否允许次日补领;建议首版不允许。
|
||
4. 专属任务是否全体用户可见,还是需要后台指定用户或人群。
|
||
5. 游戏消耗金币的事件来源和业务类型枚举需要先统一,否则任务无法区分普通扣费和游戏扣费。
|