feat: 实现用户画像、占卜历史与后端用户管理模块

This commit is contained in:
ZL-Q
2026-04-06 01:28:10 +08:00
parent d87b2e1e3a
commit 8a18b3528b
77 changed files with 5850 additions and 2604 deletions
+17
View File
@@ -25,6 +25,23 @@ This document is the source of truth for backend RFC7807 `code` values consumed
|---|---:|---|---|
| `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 |
| `AGENT_DIVINATION_PAYLOAD_REQUIRED` | 422 | Missing required `forwardedProps.divinationPayload` in run request | Prompt user to restart casting flow and resubmit |
| `AGENT_OUTPUT_DIVINATION_INVALID` | 422 | Worker output contains invalid `divination_derived` payload shape | Show generic history parse error and suggest retrying latest run |
## Profile
| code | status | meaning | frontend handling |
|---|---:|---|---|
| `PROFILE_PAYLOAD_INVALID` | 422 | Profile update payload invalid (length/type/empty constraints) | Highlight invalid fields and block submit |
| `PROFILE_NOT_FOUND` | 404 | User profile row missing | Show retry and optionally trigger profile bootstrap |
## Avatar
| code | status | meaning | frontend handling |
|---|---:|---|---|
| `AVATAR_FILE_INVALID` | 422 | Avatar mime type or size is invalid | Show file validation hint and ask user to pick another image |
| `AVATAR_PATH_SCOPE_INVALID` | 422 | Avatar path does not belong to current user scope | Show generic security error and force refresh |
| `AVATAR_SIGNED_URL_FAILED` | 502 | Backend failed to generate avatar signed upload URL | Show retry toast and keep previous avatar |
| `AVATAR_UPLOAD_FAILED` | 502 | Backend failed to upload avatar bytes to storage | Show retry toast and keep previous avatar |
Compatibility strategy:
@@ -18,6 +18,7 @@ Protocol verification status:
- Submit run: `POST /api/v1/agent/runs`
- Stream events: `GET /api/v1/agent/runs/{threadId}/events?runId=...`
- History snapshot: `GET /api/v1/agent/history`
## Run request contract
@@ -166,6 +167,73 @@ Frontend should combine:
- structural divination data from `DIVINATION_DERIVED`
- interpretation text from `TEXT_MESSAGE_END`
## History snapshot contract
`GET /api/v1/agent/history` is the canonical replay source for frontend history list and result reconstruction.
### Required response shape
```json
{
"scope": "history_day",
"threadId": "uuid|null",
"day": "2026-04-05|null",
"hasMore": false,
"messages": [
{
"id": "uuid",
"seq": 12,
"role": "assistant",
"content": "...",
"timestamp": "2026-04-05T12:34:56+00:00",
"agent_output": {
"status": "success",
"sign_level": "中上签",
"summary": "...",
"conclusion": ["..."],
"focus_points": ["..."],
"advice": ["..."],
"keywords": ["..."],
"answer": "...",
"key_points": ["..."],
"result_type": "structured_payload",
"suggested_actions": ["..."],
"divination_derived": {
"binaryCode": "101001",
"changedBinaryCode": "100001",
"guaName": "山火贲"
}
}
},
{
"id": "uuid",
"seq": 11,
"role": "user",
"content": "我最近换工作是否合适?",
"timestamp": "2026-04-05T12:34:12+00:00",
"attachments": [
{
"mimeType": "image/png",
"url": "https://...signed..."
}
]
}
]
}
```
Rules:
- `assistant` message MUST provide `agent_output` when backend has valid worker output metadata.
- `agent_output.divination_derived` uses the same shape as `DIVINATION_DERIVED.divination` payload.
- Frontend reconstructs divination result page from `agent_output` data, not from local mock data.
- `agent_output.sign_level` allowed values: `上上签` / `中上签` / `中下签` / `下下签`.
### Breaking change note
- `ui_schema` is removed from history response and is no longer part of this project protocol.
- This repository currently accepts non-backward-compatible protocol evolution (no production compatibility burden).
## Error contract linkage
- All errors use RFC7807 with extension `code` and optional `params`.
+143
View File
@@ -0,0 +1,143 @@
# Profile Protocol (Frontend <-> Backend)
This document defines the canonical backend contract for user profile read/write and avatar upload signing.
Protocol verification status:
- Backend model source: `backend/src/models/profile.py`
- Storage config source: `backend/src/core/config/settings.py`
- Current status: planned
## Compatibility strategy
- Current strategy: breaking changes allowed during implementation phase (no production compatibility burden).
- Once production compatibility is required, switch to additive-only evolution.
## Route overview
- Get profile: `GET /api/v1/users/me/profile`
- Update profile: `PATCH /api/v1/users/me/profile`
- Create avatar upload url: `POST /api/v1/users/me/avatar/upload-url`
- Upload avatar directly: `POST /api/v1/users/me/avatar` (multipart)
## Auth and trust boundary
- All routes require authenticated user context.
- `user_id` is derived from verified JWT `sub`; never accepted from client payload.
## Profile read contract
### `GET /api/v1/users/me/profile`
Response:
```json
{
"user_id": "uuid",
"display_name": "string",
"bio": "string|null",
"avatar_path": "avatars/{user_id}/{file}",
"avatar_url": "https://...signed-or-public...",
"settings": {
"version": 1,
"preferences": {
"interface_language": "zh-CN",
"ai_language": "zh-CN",
"timezone": "Asia/Shanghai",
"country": "CN"
},
"privacy": {},
"notification": {}
},
"updated_at": "2026-04-05T12:34:56+00:00"
}
```
Mapping note:
- `display_name` maps to `profiles.username`.
- `avatar_path` is stored in profile layer.
- `avatar_url` is render-ready URL generated from storage strategy.
## Profile update contract
### `PATCH /api/v1/users/me/profile`
Request:
```json
{
"display_name": "string(1..30)",
"bio": "string(0..200)",
"avatar_path": "avatars/{user_id}/{file}"
}
```
Rules:
- At least one field must be provided.
- `display_name` must be non-empty after trim.
- `bio` can be empty string and should be normalized to `null` only if agreed by API implementation.
- `avatar_path` must stay in current user prefix: `avatars/{current_user.id}/`.
Response:
- Returns the same shape as `GET /users/me/profile`.
## Avatar upload signing contract
### `POST /api/v1/users/me/avatar/upload-url`
Request:
```json
{
"mime_type": "image/png|image/jpeg|image/webp",
"file_size": 123456,
"ext": "png|jpg|jpeg|webp"
}
```
Response:
```json
{
"bucket": "avatars",
"path": "avatars/{user_id}/{uuid}.png",
"upload_url": "https://...signed...",
"expires_in": 600
}
```
Validation rules:
- `bucket` must equal `config.storage.avatar.bucket`.
- `file_size` must be `>0` and `<= config.storage.avatar.max_size_mb`.
- Only image mime types are allowed.
- Path must be server-generated and never trusted from client.
## Direct avatar upload contract
### `POST /api/v1/users/me/avatar`
Request:
- `multipart/form-data`
- field name: `file`
Validation rules:
- extension must be one of `png|jpg|jpeg|webp`
- mime must map to image type (`image/png|image/jpeg|image/webp`)
- payload size must be `<= config.storage.avatar.max_size_mb`
Behavior:
- backend writes avatar bytes to `bucket=config.storage.avatar.bucket`
- backend stores canonical path in profile
- response returns latest profile payload (`ProfileResponse`)
## Error contract linkage
- All errors must follow RFC7807 `application/problem+json`.
- `code` values must be registered in `docs/protocols/common/http-error-codes.md`.