feat: 实现起卦、设置与积分系统
This commit is contained in:
@@ -0,0 +1,505 @@
|
||||
# Eryao 用户档案/积分/会话数据模型设计
|
||||
|
||||
日期:2026-04-03
|
||||
状态:已确认(待实现)
|
||||
|
||||
## 1. 目标与范围
|
||||
|
||||
本设计用于 Eryao 后端新增并对齐以下 5 张表:
|
||||
|
||||
1. 用户档案表:`profiles`
|
||||
2. 用户积分表:`user_points`
|
||||
3. 积分流水表:`points_ledger`
|
||||
4. 会话表:`sessions`
|
||||
5. 对话历史表:`messages`
|
||||
|
||||
来源原则:
|
||||
|
||||
- `profiles`、`sessions`、`messages` 参考并吸收 `social-app` 现有设计。
|
||||
- 会话能力按“结构完整复制,但业务先停用 automation”执行。
|
||||
- 本文档为设计方案,不包含迁移脚本与代码实现。
|
||||
|
||||
## 2. 关键确认项
|
||||
|
||||
### 2.1 profiles.username 不做唯一约束
|
||||
|
||||
已确认:`profiles.username` **不需要唯一**。
|
||||
|
||||
设计落地:
|
||||
|
||||
- 不创建 `UNIQUE(username)` 约束。
|
||||
- 可保留普通索引 `ix_profiles_username` 以支持检索。
|
||||
- 若后续产品要支持“唯一用户名登录/提及”,另行引入唯一标识字段(例如 `handle`)。
|
||||
|
||||
### 2.2 settings 需要 JSONB 模板
|
||||
|
||||
`profiles.settings` 使用 `jsonb not null default '{}'::jsonb`,并约定版本化模板:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"preferences": {
|
||||
"interface_language": "zh-CN",
|
||||
"ai_language": "zh-CN",
|
||||
"timezone": "Asia/Shanghai",
|
||||
"country": "CN"
|
||||
},
|
||||
"privacy": {
|
||||
"profile_visibility": "public",
|
||||
},
|
||||
"notification": {
|
||||
"push_enabled": true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `version` 为配置结构版本,后续结构升级通过版本迁移处理。
|
||||
- `timezone` 作为运行时时区回退来源之一。
|
||||
- `default_runtime_mode` 当前仅允许 `chat` 生效。
|
||||
|
||||
## 3. 表结构设计
|
||||
|
||||
## 3.1 profiles(吸收 social-app)
|
||||
|
||||
核心字段:
|
||||
|
||||
- `id uuid primary key`(外键指向 `auth.users(id)`,`on delete cascade`)
|
||||
- `username varchar(30) not null`(非唯一)
|
||||
- `avatar_url text null`
|
||||
- `bio varchar(200) null`
|
||||
- `settings jsonb not null default '{}'::jsonb`
|
||||
- `created_at timestamptz not null default now()`
|
||||
- `updated_at timestamptz not null default now()`
|
||||
- `deleted_at timestamptz null`
|
||||
|
||||
索引建议:
|
||||
|
||||
- `ix_profiles_username (username)`
|
||||
- `ix_profiles_settings_gin using gin(settings)`
|
||||
|
||||
初始化建议:
|
||||
|
||||
- 与 `auth.users` 建立注册触发器,自动插入 profile 默认记录。
|
||||
- `settings` 初始化值应写入上述模板(而非空对象)。
|
||||
|
||||
## 3.2 user_points(用户积分账户)
|
||||
|
||||
职责:保存用户积分余额与累计统计,1 用户 1 行。
|
||||
|
||||
核心字段:
|
||||
|
||||
- `user_id uuid primary key`(FK `auth.users(id)`)
|
||||
- `balance bigint not null default 0`
|
||||
- `frozen_balance bigint not null default 0`
|
||||
- `lifetime_earned bigint not null default 0`
|
||||
- `lifetime_spent bigint not null default 0`
|
||||
- `version int not null default 0`
|
||||
- `updated_at timestamptz not null default now()`
|
||||
|
||||
约束建议:
|
||||
|
||||
- `check (balance >= 0)`
|
||||
- `check (frozen_balance >= 0)`
|
||||
- `check (lifetime_earned >= 0)`
|
||||
- `check (lifetime_spent >= 0)`
|
||||
|
||||
## 3.3 points_ledger(积分流水)
|
||||
|
||||
职责:记录每次积分变更,支持审计、对账、幂等。
|
||||
|
||||
核心字段:
|
||||
|
||||
- `id uuid primary key`
|
||||
- `user_id uuid not null`(FK `auth.users(id)`)
|
||||
- `direction smallint not null`(1 增加,-1 减少)
|
||||
- `amount bigint not null`
|
||||
- `balance_after bigint not null`
|
||||
- `change_type varchar(16) not null`(约束:`register/consume/grant/adjust`)
|
||||
- `biz_type varchar(16) not null`(约束:当前仅 `chat`)
|
||||
- `biz_id uuid not null`(当前语义:指向 `sessions.id`)
|
||||
- `event_id varchar(64) not null`
|
||||
- `operator_id uuid null`
|
||||
- `metadata jsonb not null default '{}'::jsonb`
|
||||
- `created_at timestamptz not null default now()`
|
||||
|
||||
约束与索引建议:
|
||||
|
||||
- `check (amount > 0)`
|
||||
- `check (direction in (1, -1))`
|
||||
- `check (change_type in ('register', 'consume', 'grant', 'adjust'))`
|
||||
- `check (biz_type = 'chat')`
|
||||
- `foreign key (biz_id) references sessions(id)`
|
||||
- `unique (user_id, event_id)`(用户维度幂等)
|
||||
- `index (user_id, created_at desc)`
|
||||
- `index (biz_type, biz_id)`
|
||||
|
||||
## 3.4 sessions(完整复制结构,先停用 automation)
|
||||
|
||||
来源:`social-app` 的 `sessions` 表结构。
|
||||
|
||||
核心字段:
|
||||
|
||||
- `id uuid primary key`
|
||||
- `user_id uuid not null`
|
||||
- `session_type varchar(20) not null`(结构保留 `chat/automation`)
|
||||
- `job_id uuid null`
|
||||
- `title varchar(255) null`
|
||||
- `status varchar(20) not null`
|
||||
- `last_activity_at timestamptz not null default now()`
|
||||
- `message_count int not null default 0`
|
||||
- `total_tokens int not null default 0`
|
||||
- `total_cost numeric(12,6) not null default 0`
|
||||
- `state_snapshot jsonb null`
|
||||
- `created_at/updated_at/deleted_at`
|
||||
|
||||
业务启用策略(当前阶段):
|
||||
|
||||
- 应用层仅允许 `session_type='chat'`。
|
||||
- 应用层要求 `job_id is null`。
|
||||
- 数据结构不删减,保留未来 automation 扩展能力。
|
||||
|
||||
## 3.5 messages(完整复制结构)
|
||||
|
||||
来源:`social-app` 的 `messages` 表结构。
|
||||
|
||||
核心字段:
|
||||
|
||||
- `id uuid primary key`
|
||||
- `session_id uuid not null`(FK `sessions(id)`,`on delete cascade`)
|
||||
- `seq int not null`
|
||||
- `role varchar(20) not null`(`user/assistant/system/tool`)
|
||||
- `content text not null`
|
||||
- `model_code varchar(50) null`
|
||||
- `tool_name varchar(100) null`
|
||||
- `input_tokens int not null default 0`
|
||||
- `output_tokens int not null default 0`
|
||||
- `cost numeric(12,6) not null default 0`
|
||||
- `latency_ms int null`
|
||||
- `visibility_mask bigint not null default 0`
|
||||
- `metadata jsonb null`
|
||||
- `created_at/updated_at/deleted_at`
|
||||
|
||||
约束与索引建议:
|
||||
|
||||
- `unique (session_id, seq)`
|
||||
- `index (session_id)`
|
||||
- `index (session_id, seq, visibility_mask)`
|
||||
|
||||
## 4. 一致性与事务约定
|
||||
|
||||
- 积分变更必须在单事务内同时更新:`user_points` + `points_ledger`。
|
||||
- 通过 `event_id` 做幂等写保护,避免重试导致重复扣发。
|
||||
- `sessions.total_tokens/total_cost/message_count` 作为聚合字段,由写消息流程维护。
|
||||
|
||||
## 5. 安全与权限
|
||||
|
||||
- 所有业务写入走后端服务层,不信任客户端传入 `owner_id/user_id`。
|
||||
- 表级策略沿用项目约定(RLS + 服务端授权控制)。
|
||||
- `metadata/settings` 禁止写入密钥类敏感信息。
|
||||
|
||||
## 6. 兼容与演进
|
||||
|
||||
- 本期兼容策略:新增表/字段为主,不做破坏式变更。
|
||||
- automation 能力延后启用,仅在业务层放开,不需变更当前 DDL。
|
||||
- 若后续需要唯一用户名,应新增独立唯一字段,不直接改造 `username` 历史数据。
|
||||
|
||||
## 7. 关于“用户实际成本核算表”的结论
|
||||
|
||||
结论:建议二期引入,不阻塞本期 5 张表上线。
|
||||
|
||||
理由:
|
||||
|
||||
- 本期已有 `messages.cost` 与 `sessions.total_cost`,可支持展示级统计。
|
||||
- 若进入财务对账、补贴结算、重算审计场景,需要独立不可变成本流水表。
|
||||
|
||||
建议二期最小表:`user_cost_ledger`,记录 provider/model/tokens/raw_cost/billable_cost/event_id。
|
||||
|
||||
## 8. 字段释义(5 张表逐字段)
|
||||
|
||||
本节作为实施、联调、排障时的字段字典,避免同名字段被不同团队误读。
|
||||
|
||||
### 8.1 profiles
|
||||
|
||||
- `id`:用户主键,直接对应 `auth.users.id`,生命周期与认证用户绑定。
|
||||
- `username`:展示名/昵称,不承担唯一身份语义。
|
||||
- `avatar_url`:头像地址。
|
||||
- `bio`:用户简介。
|
||||
- `settings`:用户配置 JSON,承载语言、时区、隐私、通知等可扩展偏好。
|
||||
- `created_at`:记录创建时间。
|
||||
- `updated_at`:最近一次更新记录时间。
|
||||
- `deleted_at`:软删除时间,`null` 表示有效。
|
||||
|
||||
### 8.2 user_points
|
||||
|
||||
- `user_id`:积分账户所属用户,1:1 对应 `auth.users.id`。
|
||||
- `balance`:当前可计入总余额的积分值(含可用与冻结)。
|
||||
- `frozen_balance`:冻结中的积分,暂不可消费。
|
||||
- `lifetime_earned`:历史累计获得积分(单调递增)。
|
||||
- `lifetime_spent`:历史累计消费积分(单调递增)。
|
||||
- `version`:乐观锁版本号,用于并发更新防冲突。
|
||||
- `updated_at`:积分账户最近一次变更时间。
|
||||
|
||||
### 8.3 points_ledger
|
||||
|
||||
- `id`:流水主键。
|
||||
- `user_id`:该条积分流水所属用户。
|
||||
- `direction`:变更方向,`1` 表示加分,`-1` 表示扣分。
|
||||
- `amount`:变更绝对值,始终为正数。
|
||||
- `balance_after`:本次变更完成后的账户余额快照。
|
||||
- `change_type`:变更分类,仅允许 `register/consume/grant/adjust`。
|
||||
- `biz_type`:业务域类型,当前固定 `chat`。
|
||||
- `biz_id`:业务侧引用 ID,当前固定引用 `sessions.id`。
|
||||
- `event_id`:幂等事件 ID,同一用户下不可重复。
|
||||
- `operator_id`:操作人(系统/管理员/服务账号)用户 ID,可空。
|
||||
- `metadata`:扩展信息 JSON(上下文参数、备注、来源等)。
|
||||
- `created_at`:流水写入时间。
|
||||
|
||||
### 8.4 sessions
|
||||
|
||||
- `id`:会话主键。
|
||||
- `user_id`:会话所属用户。
|
||||
- `session_type`:会话类型,当前只启用 `chat`,结构保留 `automation`。
|
||||
- `job_id`:自动化任务 ID(当前阶段应为 `null`)。
|
||||
- `title`:会话标题。
|
||||
- `status`:会话状态(如 active/archived/closed)。
|
||||
- `last_activity_at`:最近活动时间,用于排序与回收策略。
|
||||
- `message_count`:消息总数聚合值。
|
||||
- `total_tokens`:会话累计 token 聚合值。
|
||||
- `total_cost`:会话累计成本聚合值。
|
||||
- `state_snapshot`:会话状态快照(用于上下文恢复/调试)。
|
||||
- `created_at`:创建时间。
|
||||
- `updated_at`:更新时间。
|
||||
- `deleted_at`:软删除时间。
|
||||
|
||||
### 8.5 messages
|
||||
|
||||
- `id`:消息主键。
|
||||
- `session_id`:所属会话 ID,级联删除。
|
||||
- `seq`:会话内消息序号(从小到大单调)。
|
||||
- `role`:消息角色(`user/assistant/system/tool`)。
|
||||
- `content`:消息主体文本。
|
||||
- `model_code`:生成该消息的模型标识。
|
||||
- `tool_name`:工具消息对应工具名。
|
||||
- `input_tokens`:本条请求输入 token。
|
||||
- `output_tokens`:本条响应输出 token。
|
||||
- `cost`:本条消息成本。
|
||||
- `latency_ms`:本条消息处理耗时(毫秒)。
|
||||
- `visibility_mask`:可见性位掩码,用于多视图过滤。
|
||||
- `metadata`:扩展信息 JSON。
|
||||
- `created_at`:创建时间。
|
||||
- `updated_at`:更新时间。
|
||||
- `deleted_at`:软删除时间。
|
||||
|
||||
## 9. 审查结论(重点:user_points / points_ledger)
|
||||
|
||||
结论:当前字段集可支撑一期上线,但若目标是“高并发 + 强审计 + 低误用”,建议在 DDL 层补 4 项硬约束、1 项审计字段,能显著降低后续事故概率。
|
||||
|
||||
### 9.1 user_points 审查
|
||||
|
||||
现状可用点:
|
||||
|
||||
- 账户余额、冻结、累计收支、版本号齐全,满足账户模型最小闭环。
|
||||
- 非负约束已覆盖核心数值字段,能防止明显脏数据。
|
||||
|
||||
主要风险与建议:
|
||||
|
||||
1. 缺少 `frozen_balance <= balance` 约束。
|
||||
- 风险:可能出现“冻结金额大于总余额”的不合法状态。
|
||||
- 建议:新增 `check (frozen_balance <= balance)`。
|
||||
|
||||
2. 缺少 `created_at`。
|
||||
- 风险:无法直接追溯账户初始化时间,审计链不完整。
|
||||
- 建议:新增 `created_at timestamptz not null default now()`。
|
||||
|
||||
3. 并发写依赖应用层版本控制,需明确 SQL 写法。
|
||||
- 风险:若更新语句未携带 `version` 条件,可能发生覆盖写。
|
||||
- 建议:约定更新模板 `... where user_id=? and version=?`,成功后 `version=version+1`。
|
||||
|
||||
### 9.2 points_ledger 审查
|
||||
|
||||
现状可用点:
|
||||
|
||||
- `direction + amount + balance_after + event_id` 组合,已具备审计、幂等、对账基础能力。
|
||||
- `(user_id, event_id)` 唯一约束符合“同一用户维度幂等”场景。
|
||||
|
||||
主要风险与建议:
|
||||
|
||||
1. 缺少 `balance_after >= 0` 约束。
|
||||
- 风险:极端并发或逻辑 bug 时可能落负余额快照。
|
||||
- 建议:新增 `check (balance_after >= 0)`。
|
||||
|
||||
2. `operator_id` 未声明外键语义。
|
||||
- 风险:排障时难确认操作者主体是否存在。
|
||||
- 建议:若业务允许,增加 FK `operator_id -> auth.users(id)`(可 `on delete set null`)。
|
||||
|
||||
3. `change_type/biz_type` 为自由文本。
|
||||
- 风险:枚举漂移(同义不同写)导致统计口径分裂。
|
||||
- 建议:通过 `check in (...)` 或字典表约束可选值。
|
||||
|
||||
4. 缺少“业务发生时间”字段。
|
||||
- 风险:`created_at` 仅表示入库时间,异步补偿场景下难对齐业务时序。
|
||||
- 建议:二期可加 `occurred_at timestamptz`。
|
||||
|
||||
### 9.3 一期最低增强清单(建议)
|
||||
|
||||
若只做最小改动,优先加以下 5 项:
|
||||
|
||||
1. `user_points`: `check (frozen_balance <= balance)`。
|
||||
2. `user_points`: `created_at timestamptz not null default now()`。
|
||||
3. `points_ledger`: `check (balance_after >= 0)`。
|
||||
4. `points_ledger`: 明确 `operator_id` 外键策略。
|
||||
5. 统一 `change_type/biz_type` 枚举口径(约束或字典表)。
|
||||
|
||||
## 10. points_ledger 约束模型(定稿草案)
|
||||
|
||||
本节将 `change_type`、`biz_type`、`metadata` 固化为可执行约束,作为后续 DDL 实现依据。
|
||||
|
||||
### 10.1 change_type / biz_type / biz_id 约束
|
||||
|
||||
- `change_type`:`register | consume | grant | adjust`
|
||||
- `biz_type`:当前仅允许 `chat`
|
||||
- `biz_id`:`uuid not null`,并 `FK -> sessions(id)`
|
||||
|
||||
配套业务约束建议:
|
||||
|
||||
- `register/grant` 必须 `direction = 1`
|
||||
- `consume` 必须 `direction = -1`
|
||||
- `adjust` 允许 `direction in (1, -1)`
|
||||
|
||||
建议 SQL(可直接迁移化):
|
||||
|
||||
```sql
|
||||
alter table points_ledger
|
||||
add constraint ck_points_ledger_change_type
|
||||
check (change_type in ('register', 'consume', 'grant', 'adjust')),
|
||||
add constraint ck_points_ledger_biz_type
|
||||
check (biz_type = 'chat'),
|
||||
add constraint ck_points_ledger_direction_by_change_type
|
||||
check (
|
||||
(change_type in ('register', 'grant') and direction = 1)
|
||||
or (change_type = 'consume' and direction = -1)
|
||||
or (change_type = 'adjust' and direction in (1, -1))
|
||||
),
|
||||
add constraint fk_points_ledger_biz_session
|
||||
foreign key (biz_id) references sessions(id);
|
||||
```
|
||||
|
||||
### 10.2 metadata 结构(基于现有 chat 数据的定制模型)
|
||||
|
||||
设计依据(来自当前代码里的真实字段):
|
||||
|
||||
- `messages.metadata` 已稳定存在 `run_id`(见 `AgentChatMessageMetadata.run_id`)。
|
||||
- `messages` 表已有计费上下文列:`id/seq/model_code/input_tokens/output_tokens/cost`。
|
||||
- chat 业务主键是 `session_id`,本设计里已对应 `points_ledger.biz_id`。
|
||||
|
||||
因此,`points_ledger.metadata` 不再使用泛化字段,直接锚定现有运行时和消息数据:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema_version": 1,
|
||||
"reason_code": "REGISTER_WELCOME|CHAT_CONSUME|CHAT_GRANT|CHAT_ADJUST",
|
||||
"operator_type": "user|system|admin",
|
||||
"run_id": "string",
|
||||
"request_id": "string|null",
|
||||
"charge": {
|
||||
"message_id": "uuid",
|
||||
"message_seq": 1,
|
||||
"model_code": "string",
|
||||
"input_tokens": 0,
|
||||
"output_tokens": 0,
|
||||
"cost": "0.000000"
|
||||
},
|
||||
"ext": {}
|
||||
}
|
||||
```
|
||||
|
||||
字段说明(按现有数据来源):
|
||||
|
||||
- `schema_version`:固定 `1`。
|
||||
- `reason_code`:固定业务原因码,不允许自由文本。
|
||||
- `operator_type`:与 `operator_id` 搭配使用,表达操作者身份类型。
|
||||
- `run_id`:来自 agent 运行主键(`messages.metadata.run_id` 同源)。
|
||||
- `request_id`:来自 `X-Request-ID`(可空,排障用)。
|
||||
- `charge`:消费/赠金/调整时的“消息快照”,字段全部来自 `messages` 现有列。
|
||||
- `ext`:仅允许对象,承载少量扩展审计信息(如工单号)。
|
||||
|
||||
按 `change_type` 的必填规则(不是通用模板,直接按你当前业务):
|
||||
|
||||
- `register`:必须有 `reason_code/operator_type/run_id`,`charge` 必须不存在。
|
||||
- `consume`:必须有 `reason_code/operator_type/run_id/charge`,且 `charge.message_id/message_seq/model_code/input_tokens/output_tokens/cost` 全必填。
|
||||
- `grant`:必须有 `reason_code/operator_type/run_id`;若是“按会话补偿赠金”,允许并建议带 `charge`。
|
||||
- `adjust`:必须有 `reason_code/operator_type/run_id` 与 `ext.ticket_id`;`charge` 可选。
|
||||
|
||||
建议 SQL(JSON 约束可执行最小集):
|
||||
|
||||
```sql
|
||||
alter table points_ledger
|
||||
add constraint ck_points_ledger_metadata_object
|
||||
check (jsonb_typeof(metadata) = 'object'),
|
||||
add constraint ck_points_ledger_metadata_common
|
||||
check (
|
||||
metadata->>'schema_version' = '1'
|
||||
and metadata->>'reason_code' in ('REGISTER_WELCOME', 'CHAT_CONSUME', 'CHAT_GRANT', 'CHAT_ADJUST')
|
||||
and metadata->>'operator_type' in ('user', 'system', 'admin')
|
||||
and coalesce(metadata->>'run_id', '') <> ''
|
||||
and (not (metadata ? 'ext') or jsonb_typeof(metadata->'ext') = 'object')
|
||||
),
|
||||
add constraint ck_points_ledger_metadata_register_shape
|
||||
check (
|
||||
change_type <> 'register'
|
||||
or (
|
||||
metadata->>'reason_code' = 'REGISTER_WELCOME'
|
||||
and not (metadata ? 'charge')
|
||||
)
|
||||
),
|
||||
add constraint ck_points_ledger_metadata_consume_shape
|
||||
check (
|
||||
change_type <> 'consume'
|
||||
or (
|
||||
metadata->>'reason_code' = 'CHAT_CONSUME'
|
||||
and (metadata ? 'charge')
|
||||
and jsonb_typeof(metadata->'charge') = 'object'
|
||||
and (metadata->'charge' ? 'message_id')
|
||||
and (metadata->'charge' ? 'message_seq')
|
||||
and (metadata->'charge' ? 'model_code')
|
||||
and (metadata->'charge' ? 'input_tokens')
|
||||
and (metadata->'charge' ? 'output_tokens')
|
||||
and (metadata->'charge' ? 'cost')
|
||||
)
|
||||
),
|
||||
add constraint ck_points_ledger_metadata_adjust_shape
|
||||
check (
|
||||
change_type <> 'adjust'
|
||||
or (
|
||||
metadata->>'reason_code' = 'CHAT_ADJUST'
|
||||
and (metadata ? 'ext')
|
||||
and (metadata->'ext' ? 'ticket_id')
|
||||
and coalesce(metadata #>> '{ext,ticket_id}', '') <> ''
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
可选强化(建议二期加触发器,而不是只靠 CHECK):
|
||||
|
||||
- 校验 `metadata.charge.message_id` 真正存在于 `messages.id`,且 `messages.session_id = points_ledger.biz_id`。
|
||||
- 校验 `metadata.charge.message_seq` 与该 `message_id` 的真实 `seq` 一致。
|
||||
|
||||
### 10.3 operator_id 与 created_by/updated_by 是否重复
|
||||
|
||||
不重复,语义不同:
|
||||
|
||||
- `operator_id`:业务操作者(“谁触发了积分变更”),是业务审计字段。
|
||||
- `created_by/updated_by`:数据行审计字段(“谁写了这条数据库记录”)。
|
||||
|
||||
对 `points_ledger`(不可变流水)而言:
|
||||
|
||||
- `updated_by` 基本无意义(流水不应更新)。
|
||||
- `created_by` 常等于服务账号,无法表达真实业务操作者。
|
||||
- 因此保留 `operator_id` 是必要的,且建议允许空值(纯系统任务)。
|
||||
|
||||
推荐实践:
|
||||
|
||||
- `points_ledger`:保留 `operator_id`,不强制引入 `created_by/updated_by`。
|
||||
- `user_points`:如项目需要统一审计基类,可在账户表引入 `updated_by`,但不替代流水里的 `operator_id`。
|
||||
@@ -13,6 +13,18 @@ This document is the source of truth for backend RFC7807 `code` values consumed
|
||||
| `AUTH_REFRESH_TOKEN_MISSING` | 401 | Refresh token missing on logout | Treat as local logout and clear session |
|
||||
| `AUTH_USER_NOT_FOUND` | 404 | User not found | Show not-found message where applicable |
|
||||
|
||||
## Agent Points
|
||||
|
||||
| code | status | meaning | frontend handling |
|
||||
|---|---:|---|---|
|
||||
| `POINTS_INSUFFICIENT_BALANCE` | 402 | Not enough points to start this run | Show recharge/insufficient-points prompt |
|
||||
|
||||
## Agent Session
|
||||
|
||||
| code | status | meaning | frontend handling |
|
||||
|---|---:|---|---|
|
||||
| `AGENT_SESSION_RUN_LIMIT_EXCEEDED` | 409 | Session already reached max run count (start + 3 follow-ups) | Show run-limit message and require starting a new session |
|
||||
|
||||
Compatibility strategy:
|
||||
|
||||
- Additive changes only for new codes.
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
# User Points & Chat Data Protocol
|
||||
|
||||
This protocol defines the canonical data contract for user profile, points account, points ledger, chat session, and chat messages.
|
||||
|
||||
Protocol verification status:
|
||||
|
||||
- Last audited migration: `backend/alembic/versions/20260403_0004_remove_points_reason_code.py`
|
||||
- Last audited models: `backend/src/models/profile.py`, `backend/src/models/user_points.py`, `backend/src/models/points_ledger.py`, `backend/src/models/agent_chat_session.py`, `backend/src/models/agent_chat_message.py`
|
||||
- Current status: aligned
|
||||
|
||||
## Scope
|
||||
|
||||
- `profiles`
|
||||
- `user_points`
|
||||
- `points_ledger`
|
||||
- `sessions`
|
||||
- `messages`
|
||||
|
||||
## Compatibility strategy
|
||||
|
||||
- Current strategy: additive evolution.
|
||||
- Breaking changes (drop/rename/type change on core fields) require explicit migration + rollback notes.
|
||||
- `points_ledger.metadata.schema_version` is mandatory and current value is `1`.
|
||||
|
||||
## Runtime charging policy (chat)
|
||||
|
||||
- Charge unit: `20` points per successful run.
|
||||
- Charge timing: deduct after worker run succeeds (`RUN_FINISHED` path).
|
||||
- Failure behavior: failed/canceled runs do not deduct points.
|
||||
- Precheck: before accepting a run, backend must verify `available = balance - frozen_balance >= 20`.
|
||||
- Session follow-up cap: one session allows at most 4 user runs total (initial divination + 3 follow-ups).
|
||||
- Billing idempotency key for per-run consume: `chat.run.success:{session_id}:{run_id}`.
|
||||
|
||||
## Table contract
|
||||
|
||||
### profiles
|
||||
|
||||
- PK: `id` (`auth.users.id`, `on delete cascade`)
|
||||
- Core fields: `username`, `avatar_url`, `bio`, `settings`, `created_at`, `updated_at`, `deleted_at`
|
||||
- Constraints:
|
||||
- `username` not empty
|
||||
- Indexes:
|
||||
- `ix_profiles_username`
|
||||
- `ix_profiles_settings_gin`
|
||||
|
||||
### user_points
|
||||
|
||||
- PK: `user_id` (`auth.users.id`, `on delete cascade`)
|
||||
- Core fields: `balance`, `frozen_balance`, `lifetime_earned`, `lifetime_spent`, `version`, `created_at`, `updated_at`
|
||||
- Constraints:
|
||||
- all numeric totals must be non-negative
|
||||
- `frozen_balance <= balance`
|
||||
|
||||
### points_ledger
|
||||
|
||||
- PK: `id`
|
||||
- FK:
|
||||
- `user_id -> auth.users.id` (`on delete cascade`)
|
||||
- `biz_id -> sessions.id` (`on delete restrict`, nullable)
|
||||
- `operator_id -> auth.users.id` (`on delete set null`)
|
||||
- Core fields: `direction`, `amount`, `balance_after`, `change_type`, `biz_type`, `biz_id`, `event_id`, `operator_id`, `metadata`, `created_at`, `updated_at`
|
||||
- Constraints:
|
||||
- `amount > 0`
|
||||
- `direction in (1, -1)`
|
||||
- `balance_after >= 0`
|
||||
- `change_type in ('register', 'consume', 'grant', 'adjust')`
|
||||
- `biz_type is null or biz_type='chat'`
|
||||
- biz binding:
|
||||
- `register => biz_type is null and biz_id is null`
|
||||
- `consume/grant/adjust => biz_type='chat' and biz_id not null`
|
||||
- direction and change_type coupling:
|
||||
- `register/grant => direction = 1`
|
||||
- `consume => direction = -1`
|
||||
- `adjust => direction in (1, -1)`
|
||||
- idempotency: `unique (user_id, event_id)`
|
||||
|
||||
#### points_ledger.metadata (schema_version=1)
|
||||
|
||||
Canonical shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema_version": 1,
|
||||
"operator_type": "user|system|admin",
|
||||
"run_id": "string",
|
||||
"request_id": "string|null",
|
||||
"charge": {
|
||||
"message_id": "uuid",
|
||||
"message_seq": 1,
|
||||
"model_code": "string",
|
||||
"input_tokens": 0,
|
||||
"output_tokens": 0,
|
||||
"cost": "0.000000"
|
||||
},
|
||||
"ext": {}
|
||||
}
|
||||
```
|
||||
|
||||
JSON constraints:
|
||||
|
||||
- Common:
|
||||
- must be object
|
||||
- `schema_version = 1`
|
||||
- `operator_type in (user, system, admin)`
|
||||
- `run_id` non-empty
|
||||
- if present, `ext` must be object
|
||||
- Per `change_type`:
|
||||
- `register`: no `charge`, and no chat binding (`biz_type/biz_id` both null)
|
||||
- `consume`: requires `charge` object with required fields
|
||||
- `grant`: no extra metadata shape requirement
|
||||
- `adjust`: requires `ext.ticket_id` non-empty
|
||||
|
||||
## Signup initialization contract
|
||||
|
||||
- Trigger: `auth.users` after insert
|
||||
- Function: `public.initialize_profile_and_points_on_signup()`
|
||||
- Side effects:
|
||||
- create `profiles` row with default settings
|
||||
- username format: `user_xxxxxx` (`x` = 6 chars from `[a-z0-9]`)
|
||||
- create `user_points` row with initial `balance=100`, `lifetime_earned=100`
|
||||
- create `points_ledger` register row:
|
||||
- `change_type='register'`
|
||||
- `biz_type=null`, `biz_id=null`
|
||||
- `amount=100`, `direction=1`, `balance_after=100`
|
||||
|
||||
### sessions
|
||||
|
||||
- PK: `id`
|
||||
- FK: `user_id -> auth.users.id`
|
||||
- Core fields: `session_type`, `job_id`, `title`, `status`, `last_activity_at`, `message_count`, `total_tokens`, `total_cost`, `state_snapshot`, `created_at`, `updated_at`, `deleted_at`
|
||||
- Constraints:
|
||||
- `session_type in ('chat', 'automation')`
|
||||
- `status in ('pending', 'running', 'completed', 'failed')`
|
||||
- `message_count/total_tokens/total_cost` non-negative
|
||||
|
||||
### messages
|
||||
|
||||
- PK: `id`
|
||||
- FK: `session_id -> sessions.id` (`on delete cascade`)
|
||||
- Core fields: `seq`, `role`, `content`, `model_code`, `tool_name`, `input_tokens`, `output_tokens`, `cost`, `latency_ms`, `visibility_mask`, `metadata`, `created_at`, `updated_at`, `deleted_at`
|
||||
- Constraints:
|
||||
- `unique (session_id, seq)`
|
||||
- `seq > 0`
|
||||
- `role in ('user', 'assistant', 'system', 'tool')`
|
||||
- token/cost non-negative
|
||||
- `latency_ms` null or non-negative
|
||||
|
||||
## Security and ownership
|
||||
|
||||
- Backend service must derive owner identity from verified auth context.
|
||||
- Client must not be trusted for `user_id`/`operator_id` ownership semantics.
|
||||
- `metadata` and `settings` must not include secrets.
|
||||
@@ -0,0 +1,202 @@
|
||||
# 算卦 Agent API Reference
|
||||
|
||||
## 1. API Endpoint
|
||||
|
||||
- **URL**: `POST https://meeyao.com.cn/api/deepseek/chat`
|
||||
- **认证**: 需要通过 `AuthInterceptor` 注入用户 token
|
||||
|
||||
---
|
||||
|
||||
## 2. 请求结构
|
||||
|
||||
### 2.1 DeepSeekRequest (请求体)
|
||||
|
||||
```kotlin
|
||||
data class DeepSeekRequest(
|
||||
val model: String = "deepseek-chat",
|
||||
val messages: List<DeepSeekMessage>,
|
||||
val temperature: Double = 0.7,
|
||||
val max_tokens: Int = 2048,
|
||||
val stream: Boolean = false
|
||||
)
|
||||
```
|
||||
|
||||
### 2.2 DeepSeekMessage
|
||||
|
||||
```kotlin
|
||||
data class DeepSeekMessage(
|
||||
val role: String, // "system" 或 "user"
|
||||
val content: String // 系统提示词或用户提示词(含卦象JSON)
|
||||
)
|
||||
```
|
||||
|
||||
### 2.3 DivinationInfo (卦象信息 JSON)
|
||||
|
||||
```kotlin
|
||||
data class DivinationInfo(
|
||||
// 用户信息
|
||||
val question: String, // 用户问题
|
||||
val questionType: String, // 问题类型 (如"事业"、"感情"、"健康")
|
||||
|
||||
// 起卦时间信息
|
||||
val divinationTime: String, // 起卦时间 "2024年06月01日 12:00"
|
||||
val yearGanZhi: String, // 年干支 "甲子"
|
||||
val monthGanZhi: String, // 月干支
|
||||
val dayGanZhi: String, // 日干支
|
||||
val timeGanZhi: String, // 时干支
|
||||
|
||||
// 干支空亡信息
|
||||
val yearKongWang: String, // 年空亡 "戌亥"
|
||||
val monthKongWang: String, // 月空亡
|
||||
val dayKongWang: String, // 日空亡
|
||||
val timeKongWang: String, // 时空亡
|
||||
|
||||
// 月建日辰信息
|
||||
val yueJian: String, // 月建 "寅木"
|
||||
val riChen: String, // 日辰 "午火"
|
||||
val yuePo: String, // 月破
|
||||
val riChong: String, // 日冲
|
||||
|
||||
// 五行旺衰
|
||||
val wuXingStatuses: Map<String, String>, // 五行旺相休囚死状态
|
||||
|
||||
// 本卦信息
|
||||
val guaName: String, // 卦名 "坤为地"
|
||||
val upperName: String, // 上卦名称
|
||||
val lowerName: String, // 下卦名称
|
||||
val worldPosition: Int, // 世爻位置 (1-6)
|
||||
val responsePosition: Int, // 应爻位置 (1-6)
|
||||
|
||||
// 六爻信息
|
||||
val yaoInfoList: List<YaoDetailInfo>,
|
||||
|
||||
// 变卦信息
|
||||
val hasChangingYao: Boolean, // 是否有动爻
|
||||
val targetGuaName: String, // 变卦名称
|
||||
val targetYaoInfoList: List<YaoDetailInfo>
|
||||
)
|
||||
```
|
||||
|
||||
### 2.4 YaoDetailInfo (爻详细信息)
|
||||
|
||||
```kotlin
|
||||
data class YaoDetailInfo(
|
||||
val position: Int, // 爻位置 (1-6: 初爻到上爻)
|
||||
val spiritName: String, // 神煞 (龙/雀/勾/蛇/虎/玄)
|
||||
val relationName: String, // 六亲 (兄弟/父母/官鬼/妻财/子孙)
|
||||
val tiganName: String, // 地支 (子/丑/寅...)
|
||||
val elementName: String, // 五行 (金/木/水/火/土)
|
||||
val isYang: Boolean, // 阴阳属性
|
||||
val isChanging: Boolean, // 是否为动爻
|
||||
val specialMark: String // 特殊标记 (世/应/"")
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 响应结构
|
||||
|
||||
### 3.1 DeepSeekResponse
|
||||
|
||||
```kotlin
|
||||
data class DeepSeekResponse(
|
||||
val id: String,
|
||||
val choices: List<DeepSeekChoice>
|
||||
)
|
||||
|
||||
data class DeepSeekChoice(
|
||||
val message: DeepSeekMessage?, // 包含 AI 回复内容
|
||||
val finish_reason: String?
|
||||
)
|
||||
|
||||
data class DeepSeekMessage(
|
||||
val role: String,
|
||||
val content: String // AI 返回的解卦结果
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 系统提示词 (System Prompt)
|
||||
|
||||
```
|
||||
## 角色
|
||||
你是一个六爻解卦专家,熟悉六爻解卦步骤以及给出对应的解卦结果...
|
||||
|
||||
## 输出格式要求
|
||||
- 单独在最开头输出一句话概括卦象的吉凶
|
||||
- 输出顺序:解卦结论、卦象重点、卦象建议、关键词
|
||||
- 格式:解卦结论:1、… 2、…;卦象重点:1、… 2、…;卦象建议:1、… 2、…;关键词:…
|
||||
- 关键词:三个四字成语
|
||||
```
|
||||
|
||||
### 4.1 吉凶等级
|
||||
|
||||
| 等级 | 描述 |
|
||||
|------|------|
|
||||
| 上上签 | 卦象结果较好,完成某事容易或最终结果好 |
|
||||
| 中上签 | 卦象结果一般,需很努力才能完成或效果一般 |
|
||||
| 中下签 | 卦象结果较差,即使很努力也无法完成或结果不好 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 字段说明
|
||||
|
||||
### 5.1 字段名与含义对照表
|
||||
|
||||
| 字段名 | 含义 | 示例 |
|
||||
|--------|------|------|
|
||||
| `divinationTime` | 起卦时间 | "2024年06月01日 12:00" |
|
||||
| `yearGanZhi` | 年柱天干地支 | "甲子" |
|
||||
| `monthGanZhi` | 月柱天干地支 | "丙寅" |
|
||||
| `dayGanZhi` | 日柱天干地支 | "戊午" |
|
||||
| `timeGanZhi` | 时柱天干地支 | "庚子" |
|
||||
| `yearKongWang` | 年柱空亡地支 | "戌亥" |
|
||||
| `yueJian` | 月建 | "寅木" |
|
||||
| `riChen` | 日辰 | "午火" |
|
||||
| `yuePo` | 月破 | "申金" |
|
||||
| `riChong` | 日冲 | "子水" |
|
||||
| `guaName` | 本卦卦名 | "坤为地" |
|
||||
| `upperName` | 上卦名称 | |
|
||||
| `lowerName` | 下卦名称 | |
|
||||
| `worldPosition` | 世爻位置 | 1-6 |
|
||||
| `responsePosition` | 应爻位置 | 1-6 |
|
||||
| `hasChangingYao` | 是否有动爻 | true/false |
|
||||
| `targetGuaName` | 变卦卦名 | |
|
||||
|
||||
### 5.2 六神 (spiritName)
|
||||
|
||||
| 神煞 | 含义 |
|
||||
|------|------|
|
||||
| 龙 | 青龙 |
|
||||
| 雀 | 朱雀 |
|
||||
| 勾 | 勾陈 |
|
||||
| 蛇 | 螣蛇 |
|
||||
| 虎 | 白虎 |
|
||||
| 玄 | 玄武 |
|
||||
|
||||
### 5.3 六亲 (relationName)
|
||||
|
||||
| 六亲 | 含义 |
|
||||
|------|------|
|
||||
| 兄弟 | |
|
||||
| 父母 | |
|
||||
| 官鬼 | |
|
||||
| 妻财 | |
|
||||
| 子孙 | |
|
||||
|
||||
### 5.4 地支 (tiganName)
|
||||
|
||||
子、丑、寅、卯、辰、巳、午、未、申、酉、戌、亥
|
||||
|
||||
### 5.5 五行 (elementName)
|
||||
|
||||
金、木、水、火、土
|
||||
|
||||
---
|
||||
|
||||
## 6. Source
|
||||
|
||||
- Android App: `old/app/src/main/java/com/example/eryaoapp/api/DivinationRepository.kt`
|
||||
- Request Models: `old/app/src/main/java/com/example/eryaoapp/api/model/DivinationRequest.kt`
|
||||
- API Service: `old/app/src/main/java/com/example/eryaoapp/api/DeepSeekApiService.kt`
|
||||
@@ -0,0 +1,350 @@
|
||||
# Old 项目数据库表结构参考
|
||||
|
||||
本文档记录 `old` 文件夹中历史项目的数据库表结构定义。
|
||||
|
||||
---
|
||||
|
||||
## 一、login-service (后端服务)
|
||||
|
||||
### 1. users - 用户表
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 主键ID |
|
||||
| phone_number | VARCHAR(20) | UNIQUE, NOT NULL | 手机号 |
|
||||
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
|
||||
|
||||
**索引:**
|
||||
- `idx_phone_number` ON `phone_number`
|
||||
|
||||
---
|
||||
|
||||
### 2. verification_codes - 验证码表
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 主键ID |
|
||||
| phone_number | VARCHAR(20) | NOT NULL | 手机号 |
|
||||
| code | VARCHAR(6) | NOT NULL | 验证码 |
|
||||
| expiration_time | TIMESTAMP | NOT NULL | 过期时间 |
|
||||
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
|
||||
**索引:**
|
||||
- `idx_vc_phone_number` ON `phone_number`
|
||||
- `idx_vc_expiration` ON `expiration_time`
|
||||
|
||||
---
|
||||
|
||||
### 3. user_profile - 用户资料表
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 主键ID |
|
||||
| phone_number | VARCHAR(20) | UNIQUE, NOT NULL | 手机号 |
|
||||
| nickname | VARCHAR(50) | | 昵称 |
|
||||
| avatar_url | VARCHAR(500) | | 头像URL |
|
||||
| signature | VARCHAR(200) | | 个性签名 |
|
||||
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
|
||||
|
||||
---
|
||||
|
||||
### 4. user_tokens - 用户令牌表
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 主键ID |
|
||||
| user_id | BIGINT | NOT NULL | 用户ID |
|
||||
| token | VARCHAR(255) | NOT NULL | 访问令牌 |
|
||||
| refresh_token | VARCHAR(255) | | 刷新令牌 |
|
||||
| expires_at | TIMESTAMP | | 过期时间 |
|
||||
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
|
||||
---
|
||||
|
||||
### 5. user_feedback - 用户反馈表
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 主键ID |
|
||||
| user_id | BIGINT | NOT NULL | 用户ID |
|
||||
| content | TEXT | NOT NULL | 反馈内容 |
|
||||
| contact | VARCHAR(100) | | 联系方式 |
|
||||
| status | VARCHAR(20) | DEFAULT 'PENDING' | 处理状态 |
|
||||
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
|
||||
|
||||
---
|
||||
|
||||
### 6. user_coin - 用户金币表
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 主键ID |
|
||||
| user_id | BIGINT | UNIQUE, NOT NULL | 用户ID |
|
||||
| coin_count | BIGINT | DEFAULT 0 | 金币数量 |
|
||||
| total_charged | BIGINT | DEFAULT 0 | 累计充值金币 |
|
||||
| total_consumed | BIGINT | DEFAULT 0 | 累计消费金币 |
|
||||
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
|
||||
|
||||
---
|
||||
|
||||
### 7. notification - 通知表
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 主键ID |
|
||||
| user_id | BIGINT | NOT NULL | 用户ID |
|
||||
| title | VARCHAR(100) | NOT NULL | 通知标题 |
|
||||
| content | TEXT | | 通知内容 |
|
||||
| type | VARCHAR(20) | | 通知类型 |
|
||||
| is_read | BOOLEAN | DEFAULT FALSE | 是否已读 |
|
||||
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
|
||||
---
|
||||
|
||||
### 8. payment_record - 支付记录表
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 主键ID |
|
||||
| user_id | BIGINT | NOT NULL | 用户ID |
|
||||
| order_id | VARCHAR(64) | NOT NULL | 订单ID |
|
||||
| amount | DECIMAL(10,2) | NOT NULL | 支付金额 |
|
||||
| coin_amount | BIGINT | NOT NULL | 购买金币数量 |
|
||||
| payment_method | VARCHAR(20) | | 支付方式 |
|
||||
| status | VARCHAR(20) | NOT NULL | 支付状态 |
|
||||
| transaction_id | VARCHAR(100) | | 第三方交易号 |
|
||||
| paid_at | TIMESTAMP | | 支付时间 |
|
||||
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
|
||||
---
|
||||
|
||||
### 9. payment_order - 支付订单表
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 主键ID |
|
||||
| order_no | VARCHAR(64) | UNIQUE, NOT NULL | 订单号 |
|
||||
| user_id | BIGINT | NOT NULL | 用户ID |
|
||||
| product_id | VARCHAR(50) | NOT NULL | 商品ID |
|
||||
| product_name | VARCHAR(100) | NOT NULL | 商品名称 |
|
||||
| amount | DECIMAL(10,2) | NOT NULL | 订单金额 |
|
||||
| status | VARCHAR(20) | NOT NULL | 订单状态 |
|
||||
| pay_url | TEXT | | 支付链接 |
|
||||
| expire_time | TIMESTAMP | | 过期时间 |
|
||||
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
|
||||
|
||||
---
|
||||
|
||||
### 10. sensitive_word_violations - 敏感词违规记录表
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 主键ID |
|
||||
| user_id | BIGINT | NOT NULL, FK | 用户ID |
|
||||
| content_type | VARCHAR(20) | NOT NULL | 内容类型:NICKNAME, SIGNATURE |
|
||||
| violation_type | VARCHAR(30) | NOT NULL | 违规类型:POLITICAL, ILLEGAL, VULGAR, ADVERTISING, PERSONAL_ATTACK |
|
||||
| detection_service | VARCHAR(20) | DEFAULT 'LOCAL' | 检测服务类型:LOCAL, ALIYUN |
|
||||
| risk_level | VARCHAR(50) | | 阿里云风险等级 |
|
||||
| confidence | DOUBLE | | 阿里云置信度(0-1) |
|
||||
| original_content | TEXT | NOT NULL | 原始内容 |
|
||||
| matched_words | TEXT | NOT NULL | 匹配到的敏感词(JSON) |
|
||||
| aliyun_response | TEXT | | 阿里云完整响应 |
|
||||
| client_ip | VARCHAR(45) | | 客户端IP |
|
||||
| user_agent | TEXT | | 用户代理 |
|
||||
| violation_time | DATETIME | NOT NULL | 违规时间 |
|
||||
| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
|
||||
**索引:**
|
||||
- `idx_user_id` ON `user_id`
|
||||
- `idx_content_type` ON `content_type`
|
||||
- `idx_violation_type` ON `violation_type`
|
||||
- `idx_violation_time` ON `violation_time`
|
||||
- `idx_user_violation_time` ON `(user_id, violation_time)`
|
||||
- `idx_client_ip` ON `client_ip`
|
||||
- `idx_detection_service` ON `detection_service`
|
||||
- `idx_risk_level` ON `risk_level`
|
||||
- `idx_confidence` ON `confidence`
|
||||
|
||||
**外键:**
|
||||
- `user_id` REFERENCES `user_profile(id)` ON DELETE CASCADE
|
||||
|
||||
---
|
||||
|
||||
### 11. user_divination_records - 用户解卦记录表
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 主键ID |
|
||||
| user_id | BIGINT | NOT NULL | 用户ID |
|
||||
| trace_id | VARCHAR(64) | NOT NULL | 请求追踪ID |
|
||||
| question | TEXT | NOT NULL | 用户问题 |
|
||||
| question_type | VARCHAR(50) | NOT NULL | 问题类型 |
|
||||
| divination_data | LONGTEXT | NOT NULL | 卦象详情JSON |
|
||||
| deepseek_request | LONGTEXT | NOT NULL | 发送给DeepSeek的请求JSON |
|
||||
| deepseek_response | LONGTEXT | | DeepSeek响应JSON |
|
||||
| interpretation_result | LONGTEXT | | 解卦结果文本 |
|
||||
| api_success | BOOLEAN | NOT NULL, DEFAULT FALSE | API调用是否成功 |
|
||||
| error_message | TEXT | | 错误信息 |
|
||||
| api_duration_ms | BIGINT | | API调用耗时(毫秒) |
|
||||
| phone_number | VARCHAR(20) | | 用户手机号(冗余) |
|
||||
| created_at | DATETIME | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
|
||||
**索引:**
|
||||
- `idx_user_id` ON `user_id`
|
||||
- `idx_trace_id` ON `trace_id`
|
||||
- `idx_phone_number` ON `phone_number`
|
||||
- `idx_created_at` ON `created_at`
|
||||
- `idx_api_success` ON `api_success`
|
||||
- `idx_question_type` ON `question_type`
|
||||
- `idx_user_created` ON `(user_id, created_at)`
|
||||
|
||||
---
|
||||
|
||||
### 12. user_divination_history - 用户卦象历史同步表
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 主键ID |
|
||||
| user_id | BIGINT | NOT NULL, FK | 用户ID |
|
||||
| phone_number | VARCHAR(20) | NOT NULL | 用户手机号 |
|
||||
| local_record_id | BIGINT | | 本地记录ID |
|
||||
| json_data | LONGTEXT | NOT NULL | 卦象详情JSON |
|
||||
| ai_result | LONGTEXT | NOT NULL | AI解卦结果 |
|
||||
| question_type | VARCHAR(50) | NOT NULL | 问题类型 |
|
||||
| question | TEXT | NOT NULL | 用户问题 |
|
||||
| timestamp | BIGINT | NOT NULL | 创建时间戳(毫秒) |
|
||||
| is_active | BOOLEAN | NOT NULL, DEFAULT TRUE | 是否有效 |
|
||||
| sync_time | DATETIME | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 同步时间 |
|
||||
| updated_at | DATETIME | NOT NULL, DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
|
||||
|
||||
**索引:**
|
||||
- `idx_user_phone` ON `(user_id, phone_number)`
|
||||
- `idx_phone_active` ON `(phone_number, is_active)`
|
||||
- `idx_user_active_time` ON `(user_id, is_active, timestamp)`
|
||||
- `idx_local_record` ON `local_record_id`
|
||||
- `idx_sync_time` ON `sync_time`
|
||||
- `idx_question_type` ON `question_type`
|
||||
|
||||
**外键:**
|
||||
- `user_id` REFERENCES `user_profile(id)`
|
||||
|
||||
---
|
||||
|
||||
### 13. network_access_logs - 网络访问日志表
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 主键ID |
|
||||
| user_id | BIGINT | NULL | 用户ID |
|
||||
| phone_number | VARCHAR(20) | NULL | 用户手机号 |
|
||||
| client_ip | VARCHAR(45) | NOT NULL | 客户端IP |
|
||||
| client_port | INT | NULL | 客户端端口 |
|
||||
| server_ip | VARCHAR(45) | NOT NULL | 服务器IP |
|
||||
| server_port | INT | NOT NULL | 服务器端口 |
|
||||
| http_method | VARCHAR(10) | NOT NULL | 请求方法 |
|
||||
| request_path | VARCHAR(500) | NOT NULL | 请求路径 |
|
||||
| request_url | VARCHAR(1000) | NOT NULL | 完整请求URL |
|
||||
| user_agent | VARCHAR(1000) | NULL | User-Agent |
|
||||
| device_info | TEXT | NULL | 设备信息JSON |
|
||||
| response_status | INT | NULL | HTTP响应状态码 |
|
||||
| processing_time_ms | BIGINT | NULL | 处理耗时(毫秒) |
|
||||
| request_size | BIGINT | NULL | 请求体大小(字节) |
|
||||
| response_size | BIGINT | NULL | 响应体大小(字节) |
|
||||
| x_forwarded_for | VARCHAR(500) | NULL | X-Forwarded-For |
|
||||
| x_real_ip | VARCHAR(45) | NULL | X-Real-IP |
|
||||
| referer | VARCHAR(1000) | NULL | Referer |
|
||||
| operation_type | VARCHAR(50) | NULL | 操作类型 |
|
||||
| operation_result | VARCHAR(20) | NULL | 操作结果 |
|
||||
| error_message | TEXT | NULL | 错误信息 |
|
||||
| session_id | VARCHAR(100) | NULL | 会话ID |
|
||||
| access_time | DATETIME | NOT NULL | 访问时间 |
|
||||
| created_at | DATETIME | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
|
||||
**索引:**
|
||||
- `idx_user_id` ON `user_id`
|
||||
- `idx_phone_number` ON `phone_number`
|
||||
- `idx_client_ip` ON `client_ip`
|
||||
- `idx_access_time` ON `access_time`
|
||||
- `idx_operation_type` ON `operation_type`
|
||||
- `idx_operation_result` ON `operation_result`
|
||||
- `idx_client_ip_access_time` ON `(client_ip, access_time)`
|
||||
- `idx_user_id_access_time` ON `(user_id, access_time)`
|
||||
|
||||
---
|
||||
|
||||
### 14. app_version - 应用版本管理表
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 主键ID |
|
||||
| version_name | VARCHAR(20) | UNIQUE, NOT NULL | 版本名称(如v1.06) |
|
||||
| version_code | INT | UNIQUE, NOT NULL | 版本号(如106) |
|
||||
| min_supported_version | VARCHAR(20) | NOT NULL | 最低支持版本 |
|
||||
| min_supported_code | INT | NOT NULL | 最低支持版本号 |
|
||||
| is_force_update | BOOLEAN | NOT NULL, DEFAULT FALSE | 是否强制更新 |
|
||||
| update_message | TEXT | | 更新提示信息 |
|
||||
| download_url | VARCHAR(500) | | 下载链接 |
|
||||
| is_active | BOOLEAN | NOT NULL, DEFAULT TRUE | 是否启用 |
|
||||
| created_at | DATETIME | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
| updated_at | DATETIME | NOT NULL, DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 更新时间 |
|
||||
|
||||
**索引:**
|
||||
- `uk_version_name` UNIQUE ON `version_name`
|
||||
- `uk_version_code` UNIQUE ON `version_code`
|
||||
- `idx_is_active` ON `is_active`
|
||||
- `idx_created_at` ON `created_at`
|
||||
|
||||
---
|
||||
|
||||
## 二、app (Android 客户端本地 Room 数据库)
|
||||
|
||||
### 1. divination_record - 解卦记录表
|
||||
|
||||
| 字段 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 主键ID |
|
||||
| question_type | VARCHAR(50) | NOT NULL | 问题类型 |
|
||||
| question | TEXT | NOT NULL | 用户问题 |
|
||||
| hexagram_data | TEXT | NOT NULL | 卦象数据JSON |
|
||||
| ai_result | TEXT | NOT NULL | AI解卦结果 |
|
||||
| timestamp | BIGINT | NOT NULL | 创建时间戳 |
|
||||
| is_synced | BOOLEAN | DEFAULT FALSE | 是否已同步到云端 |
|
||||
| is_deleted | BOOLEAN | DEFAULT FALSE | 是否已删除 |
|
||||
|
||||
---
|
||||
|
||||
## 三、数据库初始化文件位置
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `login-service/src/main/resources/db/init.sql` | 初始化表结构 |
|
||||
| `login-service/src/main/resources/db/migration.sql` | 迁移脚本 |
|
||||
| `login-service/src/main/resources/db/migration/V1_4__Create_sensitive_word_violations_table.sql` | 敏感词表创建 |
|
||||
| `login-service/src/main/resources/db/migration/V1_5__Enhance_sensitive_word_violations_table.sql` | 敏感词表增强 |
|
||||
|
||||
---
|
||||
|
||||
## 四、Entity 类位置
|
||||
|
||||
| Entity 类 | 表名 | 位置 |
|
||||
|-----------|------|------|
|
||||
| UsersEntity | users | `login-service/src/main/kotlin/com/eryao/login/entity/UsersEntity.kt` |
|
||||
| VerificationCode | verification_codes | `login-service/src/main/kotlin/com/eryao/login/entity/VerificationCode.kt` |
|
||||
| User | user_profile | `login-service/src/main/kotlin/com/eryao/login/entity/User.kt` |
|
||||
| UserToken | user_tokens | `login-service/src/main/kotlin/com/eryao/login/entity/UserToken.kt` |
|
||||
| UserFeedback | user_feedback | `login-service/src/main/kotlin/com/eryao/login/entity/UserFeedback.kt` |
|
||||
| UserCoin | user_coin | `login-service/src/main/kotlin/com/eryao/login/entity/UserCoin.kt` |
|
||||
| Notification | notification | `login-service/src/main/kotlin/com/eryao/login/entity/Notification.kt` |
|
||||
| PaymentRecord | payment_record | `login-service/src/main/kotlin/com/eryao/login/entity/PaymentRecord.kt` |
|
||||
| PaymentOrder | payment_order | `login-service/src/main/kotlin/com/eryao/login/entity/PaymentOrder.kt` |
|
||||
| SensitiveWordViolation | sensitive_word_violations | `login-service/src/main/kotlin/com/eryao/login/entity/SensitiveWordViolation.kt` |
|
||||
| DivinationRecord | user_divination_records | `login-service/src/main/kotlin/com/eryao/login/entity/DivinationRecord.kt` |
|
||||
| DivinationHistory | user_divination_history | `login-service/src/main/kotlin/com/eryao/login/entity/DivinationRecord.kt` |
|
||||
| NetworkAccessLog | network_access_logs | `login-service/src/main/kotlin/com/eryao/login/entity/NetworkAccessLog.kt` |
|
||||
| AppVersion | app_version | `login-service/src/main/kotlin/com/eryao/login/entity/AppVersion.kt` |
|
||||
| DivinationRecord (Room) | divination_record | `app/src/main/java/com/example/eryaoapp/database/DivinationRecord.kt` |
|
||||
Reference in New Issue
Block a user