feat: migrate workbench services

This commit is contained in:
richjohnrambo
2026-10-10 23:34:06 +08:00
parent c13fb72849
commit 81e3cf1d03
88 changed files with 7358 additions and 15333 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-230
View File
@@ -1,230 +0,0 @@
-- IM 工作台 AI 建表 SQL
-- Run on the shared meeting_im database.
CREATE TABLE IF NOT EXISTS wb_ai_capability_config (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
tenant_code VARCHAR(64) NOT NULL DEFAULT '',
feature VARCHAR(64) NOT NULL,
enabled TINYINT(1) NOT NULL DEFAULT 0,
model VARCHAR(64) NOT NULL DEFAULT 'deepseek-flash',
daily_limit INT NOT NULL DEFAULT 0,
concurrency_limit INT NOT NULL DEFAULT 1,
max_input_chars INT NOT NULL DEFAULT 0,
max_file_size_mb INT NOT NULL DEFAULT 0,
config_json JSON NULL,
created_at DATETIME(3) NOT NULL,
updated_at DATETIME(3) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_tenant_feature (tenant_id, feature)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE IF NOT EXISTS wb_ai_task (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
task_id VARCHAR(64) NOT NULL,
tenant_id BIGINT UNSIGNED NOT NULL,
tenant_code VARCHAR(64) NOT NULL,
user_id VARCHAR(64) NOT NULL,
feature VARCHAR(64) NOT NULL,
status VARCHAR(32) NOT NULL,
progress INT NOT NULL DEFAULT 0,
source_type VARCHAR(32) NOT NULL DEFAULT '',
source_ref_id VARCHAR(128) NOT NULL DEFAULT '',
input_hash VARCHAR(128) NOT NULL DEFAULT '',
input_snapshot JSON NULL,
prompt_version VARCHAR(64) NOT NULL DEFAULT '',
model VARCHAR(64) NOT NULL DEFAULT '',
result_ref_id VARCHAR(64) NOT NULL DEFAULT '',
result_json JSON NULL,
error_reason VARCHAR(64) NOT NULL DEFAULT '',
error_message VARCHAR(255) NOT NULL DEFAULT '',
retry_count INT NOT NULL DEFAULT 0,
started_at DATETIME(3) NULL,
finished_at DATETIME(3) NULL,
created_at DATETIME(3) NOT NULL,
updated_at DATETIME(3) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_task_id (task_id),
KEY idx_user_feature_time (tenant_id, user_id, feature, created_at),
KEY idx_status_time (status, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE IF NOT EXISTS wb_ai_task_step (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
task_id VARCHAR(64) NOT NULL,
step VARCHAR(64) NOT NULL,
title VARCHAR(128) NOT NULL,
status VARCHAR(32) NOT NULL,
progress INT NOT NULL DEFAULT 0,
error_reason VARCHAR(64) NOT NULL DEFAULT '',
started_at DATETIME(3) NULL,
finished_at DATETIME(3) NULL,
created_at DATETIME(3) NOT NULL,
updated_at DATETIME(3) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_task_step (task_id, step)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE IF NOT EXISTS wb_ai_record (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
record_id VARCHAR(64) NOT NULL,
tenant_id BIGINT UNSIGNED NOT NULL,
tenant_code VARCHAR(64) NOT NULL,
user_id VARCHAR(64) NOT NULL,
record_type VARCHAR(64) NOT NULL,
title VARCHAR(128) NOT NULL,
summary VARCHAR(512) NOT NULL DEFAULT '',
status VARCHAR(32) NOT NULL,
detail_id VARCHAR(64) NOT NULL DEFAULT '',
task_id VARCHAR(64) NOT NULL DEFAULT '',
created_at DATETIME(3) NOT NULL,
updated_at DATETIME(3) NOT NULL,
deleted_at DATETIME(3) NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_record_id (record_id),
KEY idx_user_type_time (tenant_id, user_id, record_type, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE IF NOT EXISTS wb_ai_document (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
record_id VARCHAR(64) NOT NULL,
tenant_id BIGINT UNSIGNED NOT NULL,
user_id VARCHAR(64) NOT NULL,
document_type VARCHAR(64) NOT NULL,
title VARCHAR(128) NOT NULL,
content_markdown MEDIUMTEXT NOT NULL,
content_plain MEDIUMTEXT NULL,
sections_json JSON NULL,
source_excerpt VARCHAR(1024) NOT NULL DEFAULT '',
prompt_version VARCHAR(64) NOT NULL DEFAULT '',
model VARCHAR(64) NOT NULL DEFAULT '',
created_at DATETIME(3) NOT NULL,
updated_at DATETIME(3) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_record_id (record_id),
KEY idx_tenant_user_time (tenant_id, user_id, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE IF NOT EXISTS wb_ai_table_analysis (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
record_id VARCHAR(64) NOT NULL,
tenant_id BIGINT UNSIGNED NOT NULL,
user_id VARCHAR(64) NOT NULL,
file_id VARCHAR(128) NOT NULL,
file_name VARCHAR(255) NOT NULL,
file_type VARCHAR(16) NOT NULL,
file_size BIGINT UNSIGNED NOT NULL DEFAULT 0,
topic VARCHAR(128) NOT NULL,
metrics_json JSON NULL,
preview_json JSON NULL,
alerts_json JSON NULL,
interpretation MEDIUMTEXT NULL,
export_file_id VARCHAR(128) NOT NULL DEFAULT '',
prompt_version VARCHAR(64) NOT NULL DEFAULT '',
model VARCHAR(64) NOT NULL DEFAULT '',
created_at DATETIME(3) NOT NULL,
updated_at DATETIME(3) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_record_id (record_id),
KEY idx_file_id (tenant_id, file_id),
KEY idx_tenant_user_time (tenant_id, user_id, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE IF NOT EXISTS wb_ai_meeting_summary (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
summary_id VARCHAR(64) NOT NULL,
tenant_id BIGINT UNSIGNED NOT NULL,
tenant_code VARCHAR(64) NOT NULL,
meeting_id VARCHAR(128) NOT NULL,
user_id VARCHAR(64) NOT NULL,
task_id VARCHAR(64) NOT NULL DEFAULT '',
conclusions_json JSON NULL,
decisions_json JSON NULL,
action_items_json JSON NULL,
source_file_ids_json JSON NULL,
prompt_version VARCHAR(64) NOT NULL DEFAULT '',
model VARCHAR(64) NOT NULL DEFAULT '',
edited_by VARCHAR(64) NOT NULL DEFAULT '',
edited_at DATETIME(3) NULL,
version INT NOT NULL DEFAULT 1,
created_at DATETIME(3) NOT NULL,
updated_at DATETIME(3) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_summary_id (summary_id),
UNIQUE KEY uk_tenant_meeting (tenant_id, meeting_id),
KEY idx_user_time (tenant_id, user_id, updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE IF NOT EXISTS wb_ai_usage_log (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
tenant_id BIGINT UNSIGNED NOT NULL,
tenant_code VARCHAR(64) NOT NULL,
user_id VARCHAR(64) NOT NULL,
feature VARCHAR(64) NOT NULL,
task_id VARCHAR(64) NOT NULL DEFAULT '',
provider VARCHAR(32) NOT NULL,
model VARCHAR(64) NOT NULL,
provider_request_id VARCHAR(128) NOT NULL DEFAULT '',
prompt_tokens INT NOT NULL DEFAULT 0,
completion_tokens INT NOT NULL DEFAULT 0,
total_tokens INT NOT NULL DEFAULT 0,
latency_ms INT NOT NULL DEFAULT 0,
status VARCHAR(32) NOT NULL,
error_reason VARCHAR(64) NOT NULL DEFAULT '',
input_hash VARCHAR(128) NOT NULL DEFAULT '',
output_hash VARCHAR(128) NOT NULL DEFAULT '',
created_at DATETIME(3) NOT NULL,
PRIMARY KEY (id),
KEY idx_user_feature_day (tenant_id, user_id, feature, created_at),
KEY idx_provider_time (provider, model, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE IF NOT EXISTS wb_ai_idempotency_record (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
tenant_id BIGINT UNSIGNED NOT NULL,
user_id VARCHAR(64) NOT NULL,
idempotency_key VARCHAR(128) NOT NULL,
request_hash VARCHAR(128) NOT NULL,
response_code INT NOT NULL DEFAULT 0,
response_body JSON NULL,
status VARCHAR(32) NOT NULL,
created_at DATETIME(3) NOT NULL,
updated_at DATETIME(3) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_idempotency (tenant_id, user_id, idempotency_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE IF NOT EXISTS wb_ai_prompt_template (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
template_key VARCHAR(64) NOT NULL,
version VARCHAR(64) NOT NULL,
provider VARCHAR(32) NOT NULL DEFAULT 'deepseek',
model VARCHAR(64) NOT NULL DEFAULT 'deepseek-flash',
system_prompt MEDIUMTEXT NOT NULL,
user_prompt_template MEDIUMTEXT NOT NULL,
response_schema JSON NULL,
enabled TINYINT(1) NOT NULL DEFAULT 1,
created_at DATETIME(3) NOT NULL,
updated_at DATETIME(3) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_template_version (template_key, version),
KEY idx_template_enabled (template_key, enabled)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO wb_ai_capability_config
(tenant_id, tenant_code, feature, enabled, model, daily_limit, concurrency_limit, max_input_chars, max_file_size_mb, config_json, created_at, updated_at)
VALUES
(0, '', 'smart_document', 0, 'deepseek-flash', 20, 1, 1000, 0, JSON_OBJECT('entry_title', '智能文档'), NOW(3), NOW(3)),
(0, '', 'smart_table', 0, 'deepseek-v4-pro', 10, 1, 100, 20, JSON_OBJECT('entry_title', '智能表格', 'allowed_file_types', JSON_ARRAY('xlsx', 'xls', 'csv')), NOW(3), NOW(3)),
(0, '', 'text_polish', 0, 'deepseek-flash', 50, 3, 300, 0, JSON_OBJECT('entry_title', 'AI美文'), NOW(3), NOW(3)),
(0, '', 'daily_report_polish', 0, 'deepseek-flash', 50, 3, 300, 0, JSON_OBJECT('entry_title', 'AI美文'), NOW(3), NOW(3)),
(0, '', 'monthly_report_polish', 0, 'deepseek-flash', 30, 3, 1000, 0, JSON_OBJECT('entry_title', 'AI美文'), NOW(3), NOW(3)),
(0, '', 'meeting_summary', 0, 'deepseek-v4-pro', 10, 1, 0, 0, JSON_OBJECT('entry_title', 'AI总结'), NOW(3), NOW(3))
ON DUPLICATE KEY UPDATE
model = VALUES(model),
daily_limit = VALUES(daily_limit),
concurrency_limit = VALUES(concurrency_limit),
max_input_chars = VALUES(max_input_chars),
max_file_size_mb = VALUES(max_file_size_mb),
config_json = VALUES(config_json),
updated_at = NOW(3);
@@ -1,442 +0,0 @@
# 工作台与 Manager 内部配置运维说明
更新时间:2026-09-17
## 1. 背景
`im_workbench` 需要从 `im_manager` 读取系统配置,例如:
- `im.uid_encryption_key`
读取链路如下:
```text
im_workbench
-> GET https://admin.118im.com/internal/im-config/third-party/im?keys=uid_encryption_key,UID_ENCRYPTION_KEY,uid_key
-> im_manager 校验 HMAC 签名
-> 返回系统配置
```
这个接口是服务间内部接口,不是普通前端接口。调用方必须带 HMAC 请求头:
```http
X-Service-Name: im-workbench
X-Timestamp: <unix seconds>
X-Nonce: <random nonce>
X-Signature: <hmac-sha256 hex>
```
签名使用的密钥来自 `im_manager` 测试环境 Secret 中的:
```text
manager-system / im-manager-secrets / IM_CONFIG_API_SECRET
```
注意:不要把真实密钥写进 Git 文档或代码。
## 2. 为什么 .env 不生效
测试环境运行在 Kubernetes Pod 中,服务启动时不会自动读取仓库里的 `.env` 文件。
当前代码和启动方式是:
- `im_workbench` 只读取进程环境变量和 `IM_WORKBENCH_CONFIG` 指向的 JSON 配置文件。
- `im_manager` 的 entrypoint 使用 Pod 环境变量做 `envsubst` 生成 `config.yaml`。
- 两个服务都没有 `godotenv.Load()`、`source .env` 或 docker compose `env_file` 逻辑。
所以测试环境真正生效的是:
```text
Kubernetes Secret / Deployment 环境变量
```
不是仓库中的 `.env`。
## 3. 当前 Kubernetes 配置关系
### im_workbench
命名空间:
```text
workbench-system
```
Deployment:
```text
im-workbench
```
当前 Pod 里只显式注入:
```text
IM_WORKBENCH_CONFIG=/etc/im-workbench/workbench.json
```
`/etc/im-workbench/workbench.json` 来自:
```text
workbench-system / im-workbench-secrets / workbench.json
```
因此 workbench 的运行时配置要改这个 Secret 里的 `workbench.json`,或在 Deployment 里显式加环境变量。
### im_manager
命名空间:
```text
manager-system
```
Deployment:
```text
im-manager
```
Secret:
```text
manager-system / im-manager-secrets
```
其中和内部配置接口相关的 key:
```text
IM_CONFIG_API_SERVICE_NAME
IM_CONFIG_API_SECRET
```
## 4. 必须配置什么
### 4.1 im_manager 需要允许 im_workbench 调配置接口
`im_manager` 的 `IM_CONFIG_API_SERVICE_NAME` 需要支持两个调用方:
```text
im-server,im-workbench
```
原因:
- `im_server` 也需要读取 manager 配置。
- `im_workbench` 也需要读取 manager 配置。
- 如果只配置 `im-workbench`,会影响 `im_server`。
- 如果只配置 `im-server`,`im_workbench` 用正确 service name 调用会 401。
目标配置:
```text
IM_CONFIG_API_SERVICE_NAME=im-server,im-workbench
```
`IM_CONFIG_API_SECRET` 保持现有值,不要改成空,也不要泄露到日志或文档。
### 4.2 im_workbench 需要配置 manager 连接信息
`workbench.json` 中需要有:
```json
{
"manager": {
"base_url": "https://admin.118im.com",
"service_name": "im-workbench",
"secret": "<填 manager-system/im-manager-secrets 中的 IM_CONFIG_API_SECRET>",
"uid_key_group": "im",
"uid_key_keys": [
"uid_encryption_key",
"UID_ENCRYPTION_KEY",
"uid_key"
],
"timeout_ms": 3000
}
}
```
也可以不用改 JSON,改成 Deployment 环境变量:
```text
IM_WORKBENCH_MANAGER_BASE_URL=https://admin.118im.com
IM_WORKBENCH_MANAGER_SERVICE_NAME=im-workbench
IM_WORKBENCH_MANAGER_SECRET=<填 manager-system/im-manager-secrets 中的 IM_CONFIG_API_SECRET>
```
建议优先统一放到 `workbench.json`,因为当前 workbench 已经通过 Secret 挂载 JSON 配置。
## 5. 推荐操作流程
### 步骤 1:更新 manager Secret
将:
```text
manager-system / im-manager-secrets / IM_CONFIG_API_SERVICE_NAME
```
改为:
```text
im-server,im-workbench
```
`IM_CONFIG_API_SECRET` 保持现有值。
### 步骤 2:更新 workbench Secret
从:
```text
manager-system / im-manager-secrets / IM_CONFIG_API_SECRET
```
取值,写入:
```text
workbench-system / im-workbench-secrets / workbench.json
```
目标字段:
```json
{
"manager": {
"base_url": "https://admin.118im.com",
"service_name": "im-workbench",
"secret": "<IM_CONFIG_API_SECRET>"
}
}
```
### 步骤 3:重启服务
更新 Secret 后需要重启 Pod,确保进程重新读取配置。
建议顺序:
```text
1. 重启 im-manager
2. 重启 im-workbench
```
原因:
- 先让 manager 支持 `im-workbench` service name。
- 再让 workbench 使用 `im-workbench` service name 去调用 manager。
## 6. 验证方法
### 6.1 验证 manager 是否接受 im-workbench
用 `IM_CONFIG_API_SECRET` 对请求签名,请求:
```text
GET https://admin.118im.com/internal/im-config/third-party/im?keys=uid_encryption_key,UID_ENCRYPTION_KEY,uid_key
X-Service-Name: im-workbench
```
期望结果:
```json
{
"code": 0,
"data": {
"values": {
"uid_encryption_key": "..."
}
}
}
```
如果返回 401,重点检查:
- `IM_CONFIG_API_SERVICE_NAME` 是否包含 `im-workbench`
- 请求里的 `X-Service-Name` 是否是 `im-workbench`
- `IM_CONFIG_API_SECRET` 是否和 manager 当前 Secret 一致
- 签名文本是否为:
```text
METHOD
RequestURI
BodySHA256
Timestamp
Nonce
```
### 6.2 验证 workbench 是否加载到 manager 配置
进入 workbench Pod 看环境和配置摘要,注意不要打印真实 secret:
```bash
kubectl -n workbench-system exec <im-workbench-pod> -- sh -c 'env | grep IM_WORKBENCH_MANAGER'
```
或查看挂载配置:
```bash
kubectl -n workbench-system exec <im-workbench-pod> -- sh -c 'cat /etc/im-workbench/workbench.json'
```
检查:
```text
manager.base_url = https://admin.118im.com
manager.service_name = im-workbench
manager.secret 非空
```
### 6.3 验证业务接口
从 IM 登录获取 token 后,请求 workbench 接口:
```http
GET https://api.118im.com/v1/workbench
Authorization: Bearer <IM token>
X-MeetingIM-APIKey: <api key>
X-IM-Schema: 1
X-Tenant-Code: <tenant code>
```
期望:
```json
{
"code": 0
}
```
再创建会议,确认 user id 编解码和展示名正常:
```http
POST https://api.118im.com/v1/workbench/meetings
Idempotency-Key: <unique id>
Authorization: Bearer <IM token>
X-MeetingIM-APIKey: <api key>
X-IM-Schema: 1
X-Tenant-Code: <tenant code>
Content-Type: application/json
```
请求体示例:
```json
{
"title": "运维验证会议",
"start_at": "2026-09-18T08:00:00Z",
"end_at": "2026-09-18T09:00:00Z",
"location": "",
"recorder_user_id": "usr...",
"attendee_user_ids": ["usr..."],
"reminder_minutes": []
}
```
期望:
```json
{
"code": 0,
"data": {
"state": "scheduled",
"organizer": {
"user_id": "usr...",
"display_name": "..."
},
"participant_names": ["..."]
}
}
```
## 7. 常见问题
### 7.1 为什么不能从 manager 读取 manager secret
因为 `IM_CONFIG_API_SECRET` 是调用 manager 配置接口的 HMAC 引导凭证。
如果要读取 manager 配置接口,必须先有这个 secret 才能签名。
所以它不能从同一个 manager 配置接口中获取,只能来自部署环境,例如 Kubernetes Secret。
### 7.2 为什么 service name 不是 im-server
`X-Service-Name` 表示调用方服务名。
- `im_server` 调 manager 时,应使用 `im-server`
- `im_workbench` 调 manager 时,应使用 `im-workbench`
manager 侧应该允许多个合法调用方:
```text
im-server,im-workbench
```
### 7.3 为什么配置了 .env 还是没用
因为 Kubernetes 不会自动读取仓库 `.env`。
`.env` 只有在以下情况才会生效:
- 程序代码主动 `godotenv.Load()`
- 启动脚本 `source .env`
- docker compose 配了 `env_file`
- CI/CD 将 `.env` 内容写进 Deployment 或 Secret
当前测试环境都不是这些方式。
### 7.4 只改 workbench 会怎样
如果只把 workbench 改成:
```text
X-Service-Name: im-workbench
```
但 manager 仍然只允许:
```text
IM_CONFIG_API_SERVICE_NAME=im-server
```
那么 workbench 调 manager 会返回 401。
所以需要 manager 和 workbench 一起配置。
## 8. 最终配置清单
manager:
```text
Namespace: manager-system
Secret: im-manager-secrets
IM_CONFIG_API_SERVICE_NAME=im-server,im-workbench
IM_CONFIG_API_SECRET=<保持现有值>
```
workbench:
```text
Namespace: workbench-system
Secret: im-workbench-secrets
Key: workbench.json
```
`workbench.json`:
```json
{
"manager": {
"base_url": "https://admin.118im.com",
"service_name": "im-workbench",
"secret": "<manager 的 IM_CONFIG_API_SECRET>",
"uid_key_group": "im",
"uid_key_keys": [
"uid_encryption_key",
"UID_ENCRYPTION_KEY",
"uid_key"
],
"timeout_ms": 3000
}
}
```
-351
View File
@@ -1,351 +0,0 @@
# 工作台迁移对接总结文档
> 更新时间:2026-09-14。本文用于后续开启新的 `im_workbench` 工作树时快速接手,覆盖当前迁移边界、接口、鉴权、中间件配置、Kafka 事件、数据库现状和验收步骤。
## 1. 当前结论
工作台已从 `im_server` 拆到独立仓库 `im_workbench`,当前阶段只拆服务和接口边界,数据库暂不迁移。工作台接口统一走 `/v1/workbench/*`,由网关分流到 `im_workbench`;登录仍由 `im_server` 负责,登录成功后把 Token 会话写入 Redis,`im_workbench` 从 Redis 校验登录态。
当前已完成:
| 项目 | 状态 |
|---|---|
| 独立 Go 工程 | 已完成,仓库:`/Users/youshunlai/go_workspace/im_workbench` |
| 独立启动 | 已完成,默认监听 `:6061` |
| 新路由 `/v1/workbench/*` | 已完成 |
| Redis Token 鉴权 | 已完成 |
| 工作台 Kafka 事件生产 | 已完成 |
| 测试环境中间件配置 | 已更新 |
| 数据库迁移 | 暂不迁移,仍使用租户库 `tinode` |
| `im_server` 旧工作台代码 | 暂保留,等新服务稳定后再删 |
最近关键提交:
| 仓库 | Commit | 说明 |
|---|---|---|
| `im_workbench` | `8b5c5e3` | 兼容当前工作台表结构 |
| `im_workbench` | `c1c1156` | 更新新测试环境中间件配置,修复 Kafka 发布 |
## 2. 服务边界
### 2.1 im_workbench 负责
| 模块 | 接口范围 |
|---|---|
| 工作台首页 | `GET /v1/workbench` |
| 组织架构 | `/v1/workbench/organization/*` |
| 考勤 | `/v1/workbench/attendance/*` |
| 会议 | `/v1/workbench/meetings*` |
| 会议笔记 | `/v1/workbench/meeting-notes`、`/v1/workbench/meetings/{id}/notes*` |
| 日报 | `/v1/workbench/reports/daily*` |
| 月报 | `/v1/workbench/reports/monthly*` |
| 总经理信箱 | `/v1/workbench/executive-mailbox/*` |
| 工作台业务事件 | 生产 Kafka 事件到 `im.workbench.event` |
### 2.2 im_server 继续负责
| 能力 | 说明 |
|---|---|
| 登录、注册、Token 签发 | C 端登录仍走 `im_server` |
| Redis 登录态写入 | 登录成功后写 `im:session:token:{sha256(token)}` |
| 文件上传 | 暂由 `/v0/file/u/` 处理,工作台只保存 `file_id` |
| WebSocket 实时下发 | 工作台事件由 `im_server` 消费后转 C 端通知 |
| IM 消息、群、好友、在线状态 | 仍全部留在 `im_server` |
## 3. 网关路由
客户端对外仍访问统一域名,由 Nginx/API Gateway 按路径分流:
```text
/v0/channels -> im_server
/v0/file/u/ -> im_server
/v1/im/* -> im_server
/v1/workbench/* -> im_workbench
```
测试环境域名:
| 域名 | 用途 |
|---|---|
| `https://api.118im.com` | 后端 API,包含 `/v1/workbench/*` |
| `https://api-test.118im.com` | 测试 API 备用域名 |
| `https://web.118im.com` | Web 前端同源代理 |
注意:不要再让 `im_server` 代理工作台新接口。新接口应直接进入 `im_workbench`。
## 4. 请求鉴权
工作台接口和 IM 产品 HTTP 保持同一套公共 Header:
| Header | 必填 | 说明 |
|---|---|---|
| `Authorization` | 是 | `Bearer {token}` |
| `X-MeetingIM-APIKey` | 是 | 客户端 API Key |
| `X-Tenant-Code` | 是 | 当前租户编码,例如 `default` |
| `X-IM-Schema` | 是 | 当前固定 `1` |
| `X-Request-ID` | 建议必填 | 请求链路 ID |
| `Idempotency-Key` | 写接口必填 | 写操作幂等键 |
登录态 Redis Key:
```text
im:session:token:{sha256(token)}
```
Redis Value:
```json
{
"user_id": 1572927143048908800,
"tenant_id": 1,
"tenant_code": "default",
"device_id": "ios-device-1",
"auth_level": "auth",
"state": "active",
"issued_at": "2026-09-14T10:00:00Z",
"expires_at": "2026-09-14T12:00:00Z"
}
```
鉴权流程:
1. `im_workbench` 校验 `X-MeetingIM-APIKey` 非空或命中配置白名单。
2. 从 `Authorization` 读取 Bearer Token。
3. 对 Token 做 `sha256`,查 Redis 登录态。
4. 校验 `state=active`、未过期。
5. 校验 Header 的 `X-Tenant-Code` 与登录态 `tenant_code` 一致。
测试环境当前可用 API Key 以部署文档为准,最近验证可用的是新测试环境文档中的 `TINODE_API_KEY`。
## 5. 测试环境配置
配置文件:
```text
/Users/youshunlai/go_workspace/im_workbench/configs/workbench.test.json
```
当前关键配置:
| 配置 | 值 |
|---|---|
| 服务监听 | `:6061` |
| MySQL | `mysql.118im.com:30306` |
| MySQL 业务库 | `tinode` |
| Redis 数据端口 | `redis.118im.com:30637` |
| Redis Sentinel | `redis.118im.com:30638` |
| Kafka Bootstrap | `kafka.118im.com:30835` |
| Kafka 协议 | `PLAINTEXT` |
| Kafka Topic | `im.workbench.event` |
说明:
1. Kafka 测试环境是明文直连,不配置 SASL/TLS。
2. `im.workbench.event` 已在新 Kafka 集群创建。
3. Redis 数据 NodePort 可能落到主节点或副本。服务正常读登录态没问题;如果本地要写临时测试 session,可能遇到 `READONLY`,需要通过真正主库入口或先走真实登录链路。
4. MySQL、Redis 密码不写入本文,按 `im_server/docs/problems/新测试环境部署与中间件连接信息.md` 或部署 Secret 获取。
## 6. 本地启动
本地开发配置:
```bash
IM_WORKBENCH_CONFIG=configs/workbench.local.json go run ./cmd/workbench
```
连接测试环境配置:
```bash
IM_WORKBENCH_CONFIG=configs/workbench.test.json go run ./cmd/workbench
```
如果只做本地接口调试,可以临时打开开发 Header 鉴权:
```bash
IM_WORKBENCH_CONFIG=configs/workbench.test.json \
IM_WORKBENCH_ALLOW_DEV_HEADER_AUTH=true \
go run ./cmd/workbench
```
开发 Header:
```text
X-User-ID: 1572927143048908800
X-Tenant-ID: 1
X-Tenant-Code: default
X-MeetingIM-APIKey: x
X-IM-Schema: 1
X-Request-ID: {uuid}
```
注意:`IM_WORKBENCH_ALLOW_DEV_HEADER_AUTH=true` 只能本地调试使用,测试/生产部署不要开启。
## 7. Kafka 事件
工作台写操作统一投递:
```text
Topic: im.workbench.event
```
事件体:
```json
{
"event_id": "wbe_xxx",
"trace_id": "request-id",
"tenant_id": 1,
"tenant_code": "default",
"actor_user_id": 42,
"module": "meeting",
"action": "meeting.created",
"resource_id": "mtg_xxx",
"target_user_ids": [42, 43],
"title": "会议通知",
"summary": "项目周会",
"occurred_at": "2026-09-14T06:00:00Z",
"data": {}
}
```
当前事件范围:
| 模块 | Action | 触发场景 |
|---|---|---|
| 考勤 | `attendance.record.updated` | 打卡成功 |
| 考勤 | `attendance.appeal.created` | 提交考勤申诉 |
| 会议 | `meeting.created` | 创建会议 |
| 会议 | `meeting.updated` | 修改会议 |
| 会议 | `meeting.canceled` | 取消会议 |
| 会议 | `meeting.response.updated` | 更新参会回执 |
| 会议笔记 | `meeting_note.file.created` | 新增会议笔记文件 |
| 会议笔记 | `meeting_note.file.replaced` | 替换会议笔记文件 |
| 会议笔记 | `meeting_note.file.deleted` | 删除会议笔记文件 |
| 日报 | `daily_report.draft.created` | 创建日报草稿 |
| 日报 | `daily_report.draft.saved` | 保存日报草稿 |
| 日报 | `daily_report.submitted` | 提交日报 |
| 日报 | `daily_report.reminded` | 日报提醒 |
| 月报 | `monthly_report.draft.created` | 创建月报草稿 |
| 月报 | `monthly_report.draft.saved` | 保存月报草稿 |
| 月报 | `monthly_report.submitted` | 提交月报 |
| 月报 | `monthly_report.reopened` | 重开月报 |
| 信箱 | `mailbox.submitted` | 提交总经理信箱 |
已修复问题:`kafka.Writer` 设置了默认 Topic 后,单条 `kafka.Message` 不再重复设置 Topic,否则 `kafka-go` 会报 `Topic must not be specified for both Writer and Message`。
## 8. 数据库现状
当前数据库不迁移,`im_workbench` 仍连接租户库 `tinode`。
主要表:
| 表 | 用途 |
|---|---|
| `im_workbench_apps` | 工作台模块 |
| `im_org_departments` | 组织部门 |
| `im_org_members` | 组织成员 |
| `im_org_visibility_overrides` | 组织可见范围 |
| `im_attendance_rules` | 考勤规则 |
| `im_attendance_shifts` | 考勤班次 |
| `im_attendance_records` | 考勤记录 |
| `im_attendance_checkins` | 打卡流水 |
| `im_attendance_appeals` | 考勤申诉 |
| `im_meetings` | 会议 |
| `im_meeting_participants` | 会议参与人 |
| `im_meeting_note_files` | 会议笔记文件引用 |
| `im_daily_reports` | 日报 |
| `im_monthly_reports` | 月报 |
| `im_mailbox_config` | 总经理信箱配置 |
| `im_mailbox_submissions` | 总经理信箱提交 |
已兼容当前表结构:
| 表 | 当前结构注意点 | 代码处理 |
|---|---|---|
| `im_org_members` | 成员状态字段是 `employment_state` | 查询时返回为 `status` |
| `im_attendance_rules` | 没有 `appeal_deadline` 字段 | 查询返回 `NULL AS appeal_deadline` |
| `im_mailbox_config` | 没有 `version` 字段 | 用 `notice_updated_at` 派生版本 |
服务启动不自动建库建表,不执行 DDL。所有建表、改字段、初始化数据都走人工 SQL 审核和发布。
## 9. 已验证接口
使用新测试环境配置启动本地 `im_workbench`,并开启本地开发 Header 鉴权后,已验证:
| 接口 | 结果 |
|---|---|
| `GET /v1/workbench` | 200 |
| `GET /v1/workbench/organization/departments` | 200 |
| `GET /v1/workbench/attendance/config` | 200 |
| `GET /v1/workbench/executive-mailbox/config` | 200 |
| `POST /v1/workbench/executive-mailbox/submissions` | 200,Kafka 发布成功 |
同时执行:
```bash
go test ./...
```
结果通过。
## 10. 新工作树接手步骤
1. 拉取 `im_workbench` 的 `dev` 分支。
2. 确认包含提交 `c1c1156` 或更新的提交。
3. 查看 `configs/workbench.test.json`,确认中间件地址是新测试环境域名,不是旧 VM `64.118.137.182`。
4. 执行 `go test ./...`。
5. 本地启动:
```bash
IM_WORKBENCH_CONFIG=configs/workbench.test.json \
IM_WORKBENCH_ALLOW_DEV_HEADER_AUTH=true \
go run ./cmd/workbench
```
6. 用开发 Header 调 `GET /v1/workbench`、组织、考勤、信箱配置。
7. 调一个写接口,例如信箱提交,确认返回 200,并在数据库清理测试数据。
8. 关闭开发 Header 鉴权,走真实登录 Token 验证网关链路。
9. 验证公网 `/v1/workbench/*` 路由已经进入 `im_workbench`,并确认网关透传 `Authorization`、`X-MeetingIM-APIKey`、`X-Tenant-Code`、`X-IM-Schema`、`X-Request-ID`。
## 11. 待后续处理
| 事项 | 说明 |
|---|---|
| 真实登录态联调 | 需要通过 `im_server` 登录后生成 Redis session,再从公网 `/v1/workbench/*` 验证 |
| 网关 Header 透传 | 必须确认 `Authorization` 没被 Nginx/API Gateway 清掉 |
| Redis 写入口 | 数据 NodePort 可能落副本,本地造 session 可能遇到 `READONLY` |
| im_server 消费工作台事件 | `im_server` 消费 `im.workbench.event` 后转系统通知/WS 下发需要继续联调 |
| 工作台数据库迁移 | 现阶段不迁,接口稳定后单独做工作台库迁移 |
| 删除 im_server 旧工作台代码 | 等新服务、网关、前端、通知链路全部稳定后再删 |
## 12. 常见问题
### 12.1 `/v1/workbench` 返回 `AUTH_REQUIRED`
优先检查:
1. 是否带 `Authorization: Bearer {token}`。
2. Redis 是否有 `im:session:token:{sha256(token)}`。
3. session 是否 `state=active` 且未过期。
4. `X-Tenant-Code` 是否和 session 中的 `tenant_code` 一致。
5. 网关是否透传 `Authorization`。
6. `im_workbench` 是否连到了和 `im_server` 相同的 Redis。
### 12.2 写接口返回 `workbench event publish failed`
优先检查:
1. Kafka broker 是否为 `kafka.118im.com:30835`。
2. Kafka 是否为 `PLAINTEXT`,不要配置 SASL。
3. Topic `im.workbench.event` 是否存在。
4. 服务代码是否已经包含 Kafka 双 Topic 修复。
### 12.3 组织或考勤接口 500
优先检查当前表结构是否和代码兼容:
1. `im_org_members` 使用 `employment_state`,不是 `status`。
2. `im_attendance_rules` 当前没有 `appeal_deadline`。
3. `im_mailbox_config` 当前没有 `version`。
这些兼容已在当前代码中处理;如果新工作树仍 500,先确认是否拉到最新提交。
+101
View File
@@ -0,0 +1,101 @@
# 注册登录对接文档
接口公共前缀为 `/v1`,旧的应用名路径前缀不再提供兼容路由。
## 1 数据模型
账户以 `olons_admin.hg_app_member` 为主档:
- 手机号写入 `mobile_cipher`、`mobile_hash` 和 `mobile_mask`。
- 登录密码使用 bcrypt 写入 `password_hash`。
- 推荐码通过上级会员的 `invite_code` 建立 `parent_id`、`level` 和 `tree`。
- 注册事务同时创建 `hg_app_member_profile` 和 CNY `hg_wallet_account`。
## 2 公共 Header
```http
X-API-Key: <api key>
X-Request-ID: <request id>
X-Device-ID: <optional device id>
```
## 3 注册
```http
POST /v1/auth/register
Content-Type: application/json
```
```json
{
"username": "alice01",
"phone": "13800138000",
"password": "Secret123",
"referral_code": "ABCD2345",
"privacy_accepted": true
}
```
`referral_code` 可选;传入时必须匹配当前租户下正常会员的 `invite_code`。
## 4 登录
```http
POST /v1/auth/login
Content-Type: application/json
```
```json
{
"phone": "13800138000",
"password": "Secret123"
}
```
界面的“记住密码”由客户端安全存储,不作为接口字段上传。
## 5 成功响应
```json
{
"code": 0,
"data": {
"member_id": 9,
"member_no": "MABCDEFGHIJKLMNOPQRST",
"username": "alice01",
"phone_mask": "138****8000",
"invite_code": "EFGH6789",
"token": "...",
"token_type": "Bearer",
"expires_at": "2026-10-23T10:00:00Z"
},
"request_id": "uuid",
"server_time": "2026-10-09T10:00:00Z"
}
```
Token 是 32 字节随机会话凭证的 Base64URL 编码。Redis 只使用 Token SHA-256 摘要作为 Key,不存储原始 Token。
## 6 退出登录
```http
POST /v1/auth/logout
Authorization: Bearer <token>
```
退出成功后删除当前 Redis 会话,`data` 为 `null`。缺少、格式错误或已失效的 Token 返回 HTTP 401 和 `AUTH_REQUIRED`。
## 7 错误 reason
| reason | HTTP | 说明 |
| --- | --- | --- |
| `USERNAME_INVALID_FORMAT` | 400 | 账户名格式错误 |
| `PHONE_INVALID_FORMAT` | 400 | 手机号格式错误 |
| `PASSWORD_INVALID_FORMAT` | 400 | 密码格式错误 |
| `REFERRAL_CODE_INVALID` | 400 | 推荐码无效 |
| `PRIVACY_POLICY_NOT_ACCEPTED` | 400 | 未同意隐私政策 |
| `ACCOUNT_ALREADY_REGISTERED` | 409 | 账户名或手机号已注册 |
| `INVALID_CREDENTIALS` | 401 | 手机号或密码错误 |
| `AUTH_REQUIRED` | 401 | 缺少或无效的登录会话 |
| `RATE_LIMITED` | 429 | 15 分钟内登录失败次数过多 |
| `SERVICE_UNAVAILABLE` | 503 | Redis 或敏感数据密钥未正确配置 |
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.
@@ -0,0 +1,368 @@
# 移动端任务与账户平台需求文档
**参考站点功能拆解与重构建议**
目标:在不复制品牌素材、敏感数据与高风险业务表述的前提下,复刻参考站点的信息架构和交互骨架,并形成可开发、可验收、可审计的产品需求基线。
主要读者:产品负责人、交互设计师、前端开发、后端开发、测试、风控与运营。
观察日期:2026-10-07 参考环境:移动端网页 文档版本:V1.0
结论:参考站点由账户、任务订单、余额台账、储入取出、会员等级、内容展示和在线客服七个域构成。建议复刻页面结构与业务闭环,但重写资金、隐私、安全和合规机制。
# 1 文档范围与方法
本需求基于登录后的页面逐项浏览、按钮与导航行为检查、页面状态观察,以及浏览器中可见网络请求和前端资源分析。本文把结论分为三类:实测行为、前端可见实现、建议需求。敏感手机号、邀请码、银行卡等信息已脱敏,不作为原型内容。
## 1.1 已覆盖范围
- 登录页现状、注册页字段、会话与路由保护。
- 首页、荣誉榜、会员等级、平台简介、规则说明、代理合作、公司资质。
- 订单记录筛选、领取中心、订单资金变化、手工商城。
- 储入、取出、收款信息绑定、储入记录、取出记录、账户明细。
- 个人信息、头像上传、登录密码、交易密码、通知记录、在线客服。
- 可观察接口、响应结构、关键数据字段与前端安全实现。
## 1.2 未执行操作
为避免生成真实订单、变更资金、上传个人文件、修改密码或退出当前会话,本文未点击自动领取、订单提交、储入提交、取出确认、收款信息提交、头像上传、密码提交和退出登录。对应需求由页面字段、前端接口定义与上下游数据状态推导,并标记为待联调确认。
# 2 产品定位与角色
产品可定位为移动端会员任务与佣金账户平台。用户通过注册、储入余额、领取任务订单、完成任务、获得本金返还与佣金,并可查看台账或发起取出。平台通过等级、任务数量和费率管理用户权益,通过客服处理储入、异常订单和账户问题。
| **角色** | **主要目标** | **关键权限** |
| --- | --- | --- |
| 游客 | 了解平台并注册 | 登录、注册、查看隐私政策 |
| 会员 | 领取任务并查看收益 | 首页、任务、订单、余额、客服、个人设置 |
| 高级会员 | 按等级获得更多任务和更高费率 | 等级权益、额度与次数受配置控制 |
| 客服 | 处理储入、取出、异常任务 | 会话、工单、用户与交易查询 |
| 运营 | 管理内容、等级、任务和公告 | 配置、发布、审核、数据看板 |
| 财务与风控 | 审核资金和异常行为 | 充值审核、提现审核、台账、风控规则 |
| 管理员 | 维护权限与系统参数 | RBAC、审计日志、系统配置 |
# 3 信息架构与页面路由
| **一级模块** | **页面或路由** | **核心内容** | **入口** |
| --- | --- | --- | --- |
| 认证 | /login /register | 手机号登录、账户名、推荐码、验证码 | 未登录访问 |
| 首页 | /home | 活动图、快捷入口、余额、荣誉榜、会员等级、公司介绍 | 底部首页 |
| 任务 | /receive /orderDetail | 领取、等待、任务详情、提交完成 | 底部领取 |
| 记录 | 记录页 | 全部、待处理、已完成订单 | 底部记录 |
| 手工 | /mall | 手工任务卡片并引导客服 | 首页快捷入口 |
| 资金 | /recharge /withdrawal | 储入、取出、余额、交易密码 | 首页与个人中心 |
| 账户 | /mine /personalInfo | 用户资料、信用分、邀请码、设置 | 底部我的 |
| 明细 | /depositRecord /withdrawalRecord /bill | 储入、取出、余额台账 | 我的 |
| 客服 | /customerService /iframeCustom | 客服列表与第三方会话 | 底部客服 |
| 内容 | /html/{id} | 平台简介、规则、代理、资质 | 首页公司简介 |
## 3.1 建议导航原则
- 底部五栏固定为首页、记录、领取、客服、我的;详情页隐藏底部栏并保留明确返回。
- 资金页面必须显示币种、可用余额、冻结余额、预计到账和手续费,不使用含义不明的“人民币编码”。
- 所有金额变动都能跳转至同一业务单据,并在台账中保持可解释、可追溯。
- 客服 iframe 需明确第三方来源、隐私边界和会话状态,失败时提供备用联系方式。
# 4 核心业务闭环
## 4.1 任务订单闭环
1. 用户进入领取中心,系统校验登录态、营业时间、会员等级、最低可用余额、未完成任务与风控状态。
1. 用户点击领取后,系统创建或匹配一笔任务订单,返回任务编号、商品、金额、佣金、截止时间与状态。
1. 任务确认时,台账先记录交易扣款;完成后记录本金返还和佣金入账。参考数据中同一业务单号出现 -298、+298、+14.9 三条流水。
1. 订单必须支持待领取、匹配中、待处理、处理中、已完成、超时、冻结、取消与失败,并记录每次状态变化。
1. 任何补差额、冻结或信用分变化都必须有明确原因、复核入口和申诉渠道;不得依赖口头客服指令。
## 4.2 储入与取出闭环
储入页面按等级展示金额、赠送额、每日单数和佣金比例,用户填写金额与姓名后提交。参考站点还要求联系客服获取收款方式。新系统应改为平台内生成可核验的支付订单,禁止客服通过聊天发送临时个人账户。
取出前需绑定姓名、银行名、银行账户与收款码,随后输入金额和交易密码。新系统应只保留与实际支付通道必要的字段,敏感数据加密存储,收款码上传须做病毒扫描、内容校验与访问控制。
## 4.3 会员与代理闭环
参考站点设置普通、白银、黄金、铂金、钻石、星耀、至尊七档,充值门槛、赠送额、每日单数和佣金比例逐级上升,并存在三级推荐奖励。建议将等级规则做成版本化配置,禁止前端写死;代理奖励必须在合规评估通过后再启用。
# 5 页面级功能需求
## 5.1 登录与注册
| **页面** | **字段与控件** | **校验与行为** | **验收要点** |
| --- | --- | --- | --- |
| 登录 | 手机号、密码、记住密码、登录、去注册、隐私政策 | 手机号格式;密码非空;错误次数限制;登录成功进入首页 | 不在本地明文保存密码;支持会话过期提示 |
| 注册 | 账户名、11 位手机号、密码、推荐码、图片验证码、隐私政策 | 账户名必填;手机号唯一;验证码校验;推荐码可选或按运营配置 | 提交前明确同意协议;防批量注册;注册成功自动登录或返回登录 |
| 会话 | Authorization 令牌 | 401 清除会话并返回登录;403 显示可解释错误 | 支持刷新、撤销、设备管理与退出所有设备 |
## 5.2 首页
- 展示轮播图或单图运营位,支持后台排序、上下线、跳转类型和有效期。
- 快捷入口为领取、储入、取出、手工;按后台开关显示,关闭时给出原因而不是静默失效。
- 账户卡展示昵称、可用余额与币种;点击可进入账户明细。
- 荣誉榜为脱敏昵称、积分和结果状态,要求数据真实性标识、刷新频率与隐私策略。
- 会员等级展示当前等级、费率和下一等级条件;等级图不得只是一张不可访问的图片。
- 公司简介包括平台简介、规则说明、代理合作和公司资质,内容由 CMS 管理并保留版本。
## 5.3 记录与订单详情
记录页显示可用资产,提供全部、待处理、已完成三种筛选。列表项包括创建时间、商品图、商品名、商品价格、佣金与状态。分页默认 10 条,支持下拉刷新、空状态、加载失败重试和详情跳转。
实测发现先切换待处理、再切换已完成时曾短暂出现空列表,而重新加载全部后可见已完成记录。验收时需覆盖筛选参数、缓存状态和并发请求覆盖问题。
| **状态** | **用户可见文案** | **允许操作** | **系统约束** |
| --- | --- | --- | --- |
| 匹配中 | 正在匹配任务 | 取消等待 | 只允许一个匹配请求 |
| 待处理 | 请在截止时间前完成 | 查看详情、提交 | 倒计时与服务端时间一致 |
| 处理中 | 已提交,等待确认 | 查看详情、联系客服 | 防重复提交 |
| 已完成 | 本金与佣金已入账 | 查看流水 | 三笔或组合流水必须可核对 |
| 超时或冻结 | 显示原因与处理路径 | 申诉、联系客服 | 需要人工复核和审计 |
| 失败或取消 | 显示失败原因 | 重新领取 | 不得产生不一致余额 |
## 5.4 领取中心
领取中心包含会员等级、佣金费率、主领取按钮、匹配进度、今日战果和规则说明。点击主按钮前需完成资格预检;按钮点击后立即进入不可重复提交的 loading 状态;匹配成功跳转订单详情,失败显示可恢复原因。
- 预检项:营业时间、账户状态、信用或风控状态、最低余额、当日次数、未完成订单、等级商品范围。
- 统计项:总资产、昨日佣金、今日佣金、今日领取数;所有统计口径需要后台统一定义。
- 规则文案需要 CMS 管理,禁止把风控与资金规则硬编码在页面。
## 5.5 手工商城
页面以双列卡片展示图片、任务名称、份数、工资与已领取数;点击卡片实测直接进入客服页。建议增加任务详情、适用人群、交付方式、预计周期、库存、审核标准和风险提示,再由用户主动发起咨询。
参考站点一条商品名称中可见脚本标签文本,说明内容过滤不足。新系统必须对运营内容做 HTML 转义、富文本白名单、图片域名白名单和持久型 XSS 检测。
## 5.6 客服
客服页展示多个客服入口与工作时间,点击后在站内 iframe 打开第三方聊天。聊天页含历史会话、系统消息、文本框、图片与视频上传。需求上应增加服务状态、排队人数、会话编号、隐私声明、附件大小与类型限制、敏感信息提醒和会话评价。
## 5.7 我的与个人信息
| **功能** | **需求** | **边界或风险** |
| --- | --- | --- |
| 资料概览 | 头像、昵称、邀请码、会员等级、信用状态、余额 | 手机号等敏感字段默认脱敏 |
| 储入与取出 | 提供余额入口与状态提示 | 资金操作必须二次确认与风控 |
| 收款信息 | 姓名、银行、账户、可选支付通道 | 加密、最小化收集、修改留痕 |
| 头像 | 上传、裁剪、预览、替换 | 文件扫描、大小和格式校验 |
| 密码 | 旧密码、新密码、确认密码 | 强度、频控、成功后令牌失效 |
| 交易密码 | 独立于登录密码 | 不得用无盐 MD5;失败次数锁定 |
| 通知 | 公告列表、未读、已读、详情 | 重要规则变更需留存阅读证据 |
| 退出 | 清除令牌和本地用户数据 | 保留确认,避免误触 |
## 5.8 储入
页面展示等级规则表和金额、姓名输入框。参考规则为普通 500、白银 1888、黄金 10000、铂金 30000、钻石 50000、星耀 88888、至尊 188888;费率从 1.8% 到 5%。规则说明和表格存在个别口径差异,必须以后台版本化配置为唯一事实源。
- 提交前展示收款主体、订单号、有效期、金额、手续费和风险提示。
- 储入订单状态:待支付、待审核、成功、失败、过期、取消;失败必须提供原因。
- 若需要上传付款凭证,必须经过用户明确选择,并限制为图片或 PDF。
## 5.9 取出与收款绑定
取出页面展示余额、金额、已绑定收款信息摘要和交易密码。若未绑定,弹窗提示并引导至绑定页。绑定页实测字段为姓名、银行名、银行账户、人民币编码、支付宝收款码和微信收款码。建议删除含义不清或非必要字段,并根据支付渠道动态展示。
- 金额校验:大于 0、不超过可用余额、符合单笔最小值和最大值,并显示手续费与到账金额。
- 状态:待审核、审核中、处理中、成功、失败、驳回、取消;用户可查看原因和预计时间。
- 交易密码只在提交时使用;服务端采用强哈希校验并配合频率限制、设备风控和二次验证。
## 5.10 账户明细与记录
账户明细需按业务单号聚合展示变动类型、变动金额、变动前余额、变动后余额、时间、状态和关联单据。储入记录显示订单号、状态、金额、创建时间;取出记录字段同构,并增加手续费、到账金额与失败原因。
# 6 数据模型与状态机
| **实体** | **关键字段** | **说明** |
| --- | --- | --- |
| User | id, username, mobile, avatar, status, inviteCode | 用户主档,手机号单独加密与索引 |
| Membership | levelId, rate, dailyLimit, withdrawLimits, effectiveAt | 版本化等级权益 |
| Wallet | available, frozen, currency, version | 使用乐观锁或账户序列保证一致性 |
| LedgerEntry | bizNo, type, direction, amount, before, after | 不可变台账,支持幂等与冲正 |
| TaskOrder | orderNo, productId, amount, commission, status, deadline | 任务生命周期 |
| DepositOrder | depositNo, amount, channel, status, proofUrl | 储入生命周期 |
| WithdrawalOrder | withdrawNo, amount, fee, netAmount, status | 取出生命周期 |
| PayoutProfile | realName, bankName, maskedAccount, qrAssetIds | 敏感字段加密与脱敏 |
| Content | type, title, body, version, status, publishedAt | 公告、规则与公司资料 |
| SupportSession | sessionNo, provider, status, openedAt, closedAt | 第三方客服映射 |
| AuditLog | actor, action, target, before, after, ip, device | 资金与权限操作强制留痕 |
## 6.1 幂等与一致性
- 领取、提交订单、储入回调、取出申请和密码修改都必须使用幂等键。
- 钱包余额只能由服务端台账事务修改;前端显示值不参与结算。
- 订单状态转换由服务端状态机控制,不允许客户端任意设置。
- 重复回调、网络重试和并发点击不得重复扣款、返款或发佣金。
- 所有涉及金额的接口返回业务单号和最新余额版本。
# 7 接口需求
下表分为实测请求与前端资源中可见接口。实测接口在浏览器中返回 HTTP 200,响应外层为 Base64 字符串,解码后为 JSON,统一包含 success、code、data、msg。建议新系统直接使用标准 JSON,不把 Base64 当作安全措施。
| **方法** | **接口** | **用途** | **主要请求或返回字段** | **证据** |
| --- | --- | --- | --- | --- |
| POST | /wap-api/doLogin | 登录 | username, password;token, users | 前端实现与已发生请求 |
| POST | /wap-api/doRegister | 注册 | nickName, username, password, inviteCode, code, uuid | 前端实现 |
| GET | /wap-api/captchaImage | 图片验证码 | uuid, img, captchaBol | 前端实现 |
| GET | /wap-api/profile | 用户概要 | tel, username, headpic, inviteCode, level, levelRate, balance, xyf | 实测 |
| GET | /wap-api/banner/list | 首页横幅 | 图片地址数组 | 实测 |
| GET | /wap-api/home | 首页开关与内容编号 | SHOP_STATUS, TASK, POP_MESSAGE, 内容 ID | 实测 |
| GET | /wap-api/user/data/home | 荣誉榜 | name, avatar, balance | 实测 |
| GET | /wap-api/ctrl/level-list | 等级配置 | name, orderNum, num, giveNum, bili, 提现限制 | 实测 |
| GET | /wap-api/support | 客服列表 | qq, url, workTime, 分页 | 实测 |
| GET | /wap-api/convey/page | 订单分页 | pageNo, pageSize, status;订单与商品字段 | 实测 |
| POST | /wap-api/convey/order | 领取或匹配订单 | 无显式参数;返回订单 | 前端实现,未执行 |
| POST | /wap-api/convey/order/submit/{id} | 提交任务 | 订单 id | 前端实现,未执行 |
| GET | /wap-api/convey/count | 领取统计 | balance, countToday, countNumToday, countNumYesterday, content | 实测 |
| GET | /wap-api/convey/unpay | 未完成订单检查 | djs 等状态 | 实测 |
| GET | /wap-api/product/page | 手工商品分页 | 分页条件;商品卡片 | 前端实现 |
| GET | /wap-api/platform-config/trade-wait-time | 任务等待时间 | 秒数或分钟数 | 实测 |
| GET | /wap-api/bank/current/info | 当前收款信息 | 收款资料摘要 | 前端实现 |
| POST | /wap-api/bank/save | 保存收款信息 | 姓名、银行、账户、收款码 | 前端实现,未执行 |
| POST | /wap-api/recharge/recharge | 创建储入申请 | amount, name 等 | 前端实现,未执行 |
| GET | /wap-api/recharge/page | 储入记录分页 | 分页与状态 | 前端实现 |
| POST | /wap-api/deposit/do_deposit | 创建取出申请 | amount, paypassword 等 | 前端实现,未执行 |
| GET | /wap-api/deposit/page | 取出记录分页 | 分页与状态 | 前端实现 |
| GET | /wap-api/balance-log/page | 账户流水 | 分页;类型、金额、业务单号 | 前端实现与页面实测 |
| POST | /wap-api/users/setPwd | 修改登录密码 | oldPwd, newPwd | 前端实现,未执行 |
| POST | /wap-api/users/setPwd2 | 修改交易密码 | oldPwd, newPwd | 前端实现,未执行 |
| POST | /wap-api/local/upload | 文件上传 | multipart file | 前端实现,未执行 |
| GET/POST | /wap-api/user-tc/select /read/{id} /page | 弹窗通知 | 公告、已读、分页 | 实测与前端实现 |
## 7.1 新系统接口规范
- 统一使用 HTTPS、JSON、明确的 HTTP 状态码、traceId、错误码和可本地化 message。
- Authorization 使用短期访问令牌与可撤销刷新令牌;敏感操作增加一次性挑战或二次验证。
- 列表接口统一 page、pageSize、sort、status,返回 items、total、hasNext。
- 金额使用最小货币单位整数或高精度 Decimal,禁止浮点结算。
- 上传接口先申请受限上传凭证,服务端扫描后再绑定业务。
- 所有写接口要求 Idempotency-Key、设备信息与审计上下文。
# 8 管理后台需求
| **模块** | **主要能力** | **审批与审计** |
| --- | --- | --- |
| 用户管理 | 查询、状态、等级、信用、设备、邀请关系 | 冻结与解冻需理由和双人复核 |
| 等级配置 | 门槛、赠送、次数、费率、提现限制、商品范围 | 版本化发布,可回滚 |
| 任务商品 | 商品、图片、金额、佣金、库存、上下线 | 内容过滤和审核流 |
| 任务订单 | 状态、倒计时、资金流水、异常处理 | 人工改状态必须双人审批 |
| 储入管理 | 支付订单、回调、凭证、审核、对账 | 每日对账与差错处理 |
| 取出管理 | 审核、通道、手续费、失败重试 | 大额与异常规则 |
| 内容管理 | 横幅、规则、简介、资质、公告 | 预览、定时、版本与发布人 |
| 客服管理 | 客服入口、工作时间、会话映射、工单 | 第三方访问最小权限 |
| 风控 | 设备、IP、频次、关联账户、异常金额 | 命中规则可解释并可申诉 |
| 权限审计 | RBAC、操作日志、导出日志 | 关键权限定期复核 |
# 9 非功能需求
## 9.1 安全
- 密码使用 Argon2id 或 bcrypt 加盐存储。参考站点在浏览器端对密码做 MD5,这不能替代服务端强哈希。
- 输入与富文本做上下文相关转义;运营内容采用白名单;CSP 禁止任意外部脚本。
- 银行卡和手机号加密存储,日志与埋点禁止记录完整值;展示统一脱敏。
- 接口启用 CSRF 或同站策略、速率限制、风控挑战、签名回调和重放保护。
- 第三方客服 iframe 设置最小 sandbox 权限、allowlist 域名和清晰的数据传输告知。
- 上传文件限制 MIME、扩展名、体积与像素,执行恶意内容扫描,使用隔离存储和短期访问 URL。
## 9.2 性能与可用性
- 首屏 LCP 目标小于 2.5 秒,核心 API P95 小于 800 毫秒,写操作 P95 小于 1.5 秒。
- 按钮防抖、请求取消、重试与离线提示;长列表分页和图片懒加载。
- 资金写入接口可用性目标 99.95%,客服与内容接口目标 99.9%。
- 客户端时间仅用于展示,截止时间与状态以服务端为准。
## 9.3 可访问性与国际化
按钮和导航必须有语义标签与至少 44×44 像素触控区域;颜色不作为唯一状态标识;支持字体放大。简体、繁体和术语需统一,避免同一页面混用“储入/充值”“取出/提现”“领取/抢单”。
# 10 合规与业务风险
参考站点涉及先储入、任务扣款、返本付佣、推荐奖励、信用分和提现限制。此类模式在不同司法辖区可能触及支付、消费者保护、反洗钱、网络营销或其他监管要求。上线前需由法务和持牌支付机构评估,不应直接复制其资金规则、免责条款或客服收款模式。
| **风险** | **参考站点表现** | **新系统要求** | **优先级** |
| --- | --- | --- | --- |
| 资金真实性 | 客服提供收款方式,失败责任不清 | 使用合规支付通道、订单化收款与自动对账 | P0 |
| 诱导与不透明 | 高等级对应更高储入和收益 | 明确费用、风险、无保证收益,禁止误导 | P0 |
| 推荐奖励 | 三级佣金 16% 8% 4% | 法务审查后决定是否启用 | P0 |
| 账户冻结 | 规则含冻结、补差额、信用分 | 冻结需合法依据、通知、申诉和复核 | P0 |
| 内容安全 | 商品名出现脚本标签文本 | CMS 白名单与持久型 XSS 防护 | P0 |
| 密码安全 | 前端无盐 MD5 | 服务端强哈希、MFA、频控 | P0 |
| 隐私 | 收集银行账户和收款码 | 最小化、加密、授权、删除与访问审计 | P0 |
| 数据一致性 | 筛选短暂空列表、规则口径差异 | 单一配置源、请求竞态处理、自动化测试 | P1 |
# 11 埋点与运营指标
| **事件** | **触发时机** | **关键属性** |
| --- | --- | --- |
| auth_login_result | 登录成功或失败 | result, errorCode, deviceRisk |
| home_shortcut_click | 点击首页快捷入口 | target, userLevel |
| order_claim_click | 点击领取 | eligibilityResult, balanceBucket, level |
| order_state_change | 订单状态变化 | orderNo, from, to, reason |
| deposit_submit | 提交储入申请 | amountBucket, channel, result |
| withdraw_submit | 提交取出申请 | amountBucket, fee, result |
| support_open | 打开客服会话 | provider, entry, workStatus |
| content_view | 查看规则或公告 | contentId, version, readDuration |
| security_challenge | 触发安全验证 | scene, result, ruleId |
核心指标包括注册转化、首个任务完成率、领取成功率、订单超时率、储入成功率、取出成功率与处理时长、客服转人工率、异常账户率、账务差错率和投诉率。涉及金额与身份的数据只做区间化或匿名化统计。
# 12 验收标准
1. 未登录用户只能访问登录、注册和公开协议;受保护路由统一拦截。
2. 同一用户重复点击领取或提交订单,不产生重复业务单和重复资金流水。
3. 订单完成后,订单状态、钱包余额、三类流水和统计数据在一次事务或可验证最终一致性中完成。
4. 储入与取出所有状态均可在记录页查询,失败和驳回有明确原因。
5. 等级规则由后台发布后前台一致生效,表格、领取校验与提现限制不存在口径差异。
6. 手机号、银行卡、邀请码和交易密码不出现在客户端日志、服务端普通日志或分析平台。
7. 富文本、商品名、公告和客服消息中的脚本或危险 HTML 不能执行。
8. 接口 401、403、429、5xx、超时和断网场景均有清晰恢复路径。
9. 移动端 320 至 430 像素宽度可用,底部导航不遮挡内容,键盘弹出时表单按钮可达。
10. 关键资金和权限操作在后台可按用户、业务单号和时间完整审计。
# 13 交付优先级与里程碑
| **阶段** | **范围** | **退出条件** |
| --- | --- | --- |
| MVP 认证与账户 | 登录注册、资料、钱包、台账、内容、客服 | 权限、安全、审计基线通过 |
| MVP 任务闭环 | 任务列表、领取、详情、提交、状态机、佣金 | 幂等和账务一致性测试通过 |
| 资金闭环 | 储入、取出、收款绑定、支付通道、对账 | 支付与法务合规评审通过 |
| 运营能力 | 等级、公告、横幅、商品、通知、后台 | 配置版本和发布审核可用 |
| 增长与优化 | 邀请关系、数据看板、体验优化 | 合规批准且核心指标稳定 |
# 14 低保真原型
以下原型根据实测页面重新绘制,用于确认页面层级、组件位置和交互入口,不复制原站品牌素材、头像或敏感数据。视觉设计阶段应重新建立色彩、字体、图标和组件规范。
![低保真原型图 1](移动端任务与账户平台需求文档.assets/figure-01.png)
图 1 参考站点功能复刻原型
![低保真原型图 2](移动端任务与账户平台需求文档.assets/figure-02.png)
图 2 参考站点功能复刻原型
![低保真原型图 3](移动端任务与账户平台需求文档.assets/figure-03.png)
图 3 参考站点功能复刻原型
![低保真原型图 4](移动端任务与账户平台需求文档.assets/figure-04.png)
图 4 参考站点功能复刻原型
# 15 待确认事项
- 目标产品是否真实涉及用户资金储入与取出,还是仅做演示积分。
- 任务来源、商品真实性、结算主体和佣金承担方。
- 目标市场与适用法律、支付通道和实名要求。
- 是否保留会员等级、推荐奖励、信用分和冻结机制。
- 客服使用自建系统还是第三方服务,允许传输哪些用户数据。
- 管理后台的组织结构、审批层级和财务对账责任人。
- 品牌名、语言版本、视觉方向与是否需要 App 壳或 PWA。
在上述事项明确前,可以先完成无资金风险的认证、内容、任务展示、客服、账户明细和后台配置原型;资金接口只做沙箱与模拟数据。
-56
View File
@@ -1,56 +0,0 @@
# 工作台服务迁移说明
本仓库承接 `im_server` 中的工作台接口,第一阶段目标是让工作台服务独立编译、独立启动、独立鉴权,并由网关把 `/v1/workbench/*` 分流到本服务。
## 已迁移接口
| 模块 | 新路径 |
|---|---|
| 工作台入口 | `GET /v1/workbench` |
| 组织架构 | `/v1/workbench/organization/*` |
| 考勤 | `/v1/workbench/attendance/*` |
| 会议 | `/v1/workbench/meetings*` |
| 会议笔记 | `/v1/workbench/meeting-notes`、`/v1/workbench/meetings/{id}/notes*` |
| 日报 | `/v1/workbench/reports/daily*` |
| 月报 | `/v1/workbench/reports/monthly*` |
| 总经理信箱 | `/v1/workbench/executive-mailbox/*` |
## 当前边界
1. 数据库暂不迁移,仍访问现有租户库工作台表。
2. 文件上传仍由 `im_server` 的 `/v0/file/u/` 处理,工作台只保存 `file_id` 引用。
3. C 端实时通知仍由 `im_server` 下发;工作台后续只投 Kafka 业务事件。
4. `im_server` 原工作台代码暂不删除,待本服务联调稳定后再清理。
5. 服务启动不自动创建或修改表结构。
## 待联调项
1. `im_server` 登录后写入 Redis session:`im:session:token:{sha256(token)}`。
2. Nginx/API Gateway 路由:`/v1/workbench/* -> im_workbench:6061`。
3. 工作台业务事件 Kafka 化:会议、考勤、日报、月报、信箱事件投递给 `im_server`。
4. 数据库专项迁移:接口稳定后再从租户库拆到工作台库。
## Kafka 事件范围
当前工作台服务生产端统一投递到 `im.workbench.event`,由 `im_server` 后续接入消费组并转为系统通知、未读数或 WS 实时下发。
| 模块 | Action | 触发场景 |
|---|---|---|
| 考勤 | `attendance.record.updated` | 打卡成功 |
| 考勤 | `attendance.appeal.created` | 提交考勤申诉 |
| 会议 | `meeting.created` | 创建会议 |
| 会议 | `meeting.updated` | 修改会议 |
| 会议 | `meeting.canceled` | 取消会议 |
| 会议 | `meeting.response.updated` | 参会人更新回执 |
| 会议笔记 | `meeting_note.file.created` | 新增会议笔记文件 |
| 会议笔记 | `meeting_note.file.replaced` | 替换会议笔记文件 |
| 会议笔记 | `meeting_note.file.deleted` | 删除会议笔记文件 |
| 日报 | `daily_report.draft.created` | 创建日报草稿 |
| 日报 | `daily_report.draft.saved` | 保存日报草稿 |
| 日报 | `daily_report.submitted` | 提交日报 |
| 日报 | `daily_report.reminded` | 日报提醒 |
| 月报 | `monthly_report.draft.created` | 创建月报草稿 |
| 月报 | `monthly_report.draft.saved` | 保存月报草稿 |
| 月报 | `monthly_report.submitted` | 提交月报 |
| 月报 | `monthly_report.reopened` | 重开月报 |
| 信箱 | `mailbox.submitted` | 提交总经理信箱 |