179 lines
4.4 KiB
Markdown
179 lines
4.4 KiB
Markdown
# popiartServer 薄膜持久化设计
|
||
|
||
这份文档描述当前的轻量化持久化方案。目标不是把 `popiartServer`
|
||
做成第二个模型平台,而是让它只保存 PopiArt 自己的产品语义。
|
||
|
||
## 设计原则
|
||
|
||
`PopiNewAPI` 已经负责:
|
||
|
||
- 上游 provider key
|
||
- 模型请求
|
||
- 原始用量
|
||
- 一部分任务型能力的 task 状态
|
||
|
||
所以 `popiartServer` 不再重复持有:
|
||
|
||
- 本地 artifact metadata table
|
||
- 本地 job_logs table
|
||
|
||
它只保留三类本地状态:
|
||
|
||
- `sessions`
|
||
- `jobs`(更准确说是 job refs)
|
||
- `skill_routes`
|
||
- `media blobs + metadata`
|
||
|
||
## 当前关系
|
||
|
||
```text
|
||
session -> jobs
|
||
jobs -> result_refs_json
|
||
project -> skill_routes
|
||
media -> local files + metadata json
|
||
```
|
||
|
||
其中:
|
||
|
||
- `session` 保存 PopiArt 登录态和对应的 `PopiNewAPI token`
|
||
- `jobs` 保存 skill 语义、用户归属、上游引用和同步结果引用
|
||
- `result_refs_json` 保存同步能力的结果引用;新结果会优先 re-host 到本地 media store
|
||
- `skill_routes` 保存项目级路由覆盖
|
||
- `media` 保存稳定 URL 所需的本地文件与元数据
|
||
|
||
## 存储位置
|
||
|
||
默认配置来自这些环境变量:
|
||
|
||
- `POPIART_DATA_DIR`
|
||
- `POPIART_SQLITE_PATH`
|
||
- `POPIART_SESSION_SECRET`
|
||
|
||
默认值:
|
||
|
||
```text
|
||
POPIART_DATA_DIR=./data
|
||
POPIART_SQLITE_PATH=./data/popiart.db
|
||
```
|
||
|
||
## Repository 接口
|
||
|
||
代码位置:
|
||
|
||
- `internal/server/repository.go`
|
||
|
||
当前只保留三层 repository:
|
||
|
||
1. `SessionRepository`
|
||
2. `JobRepository`
|
||
3. `RouteRepository`
|
||
|
||
本地开发版没有对象存储依赖,但现在有一个轻量 media 存储层:
|
||
|
||
- blob 文件保存在 `POPIART_DATA_DIR/media/blobs/`
|
||
- metadata JSON 保存在 `POPIART_DATA_DIR/media/meta/`
|
||
|
||
## SQLite Schema
|
||
|
||
### sessions
|
||
|
||
```sql
|
||
CREATE TABLE sessions (
|
||
session_id TEXT PRIMARY KEY,
|
||
user_id TEXT NOT NULL,
|
||
user_json TEXT NOT NULL,
|
||
token_enc BLOB NOT NULL,
|
||
token_masked TEXT NOT NULL,
|
||
created_at TEXT NOT NULL,
|
||
expires_at TEXT NOT NULL
|
||
);
|
||
```
|
||
|
||
### jobs
|
||
|
||
```sql
|
||
CREATE TABLE jobs (
|
||
job_id TEXT PRIMARY KEY,
|
||
user_id TEXT NOT NULL,
|
||
session_id TEXT,
|
||
project_id TEXT,
|
||
skill_id TEXT,
|
||
route_key TEXT,
|
||
model_id TEXT,
|
||
exec_mode TEXT NOT NULL,
|
||
newapi_task_id TEXT,
|
||
status TEXT NOT NULL,
|
||
input_json TEXT NOT NULL,
|
||
result_refs_json TEXT,
|
||
usage_json TEXT,
|
||
error_json TEXT,
|
||
idempotency_key TEXT,
|
||
created_at TEXT NOT NULL,
|
||
started_at TEXT,
|
||
finished_at TEXT,
|
||
UNIQUE(user_id, idempotency_key)
|
||
);
|
||
```
|
||
|
||
### skill_routes
|
||
|
||
```sql
|
||
CREATE TABLE skill_routes (
|
||
route_key TEXT NOT NULL,
|
||
scope_key TEXT NOT NULL,
|
||
model_id TEXT NOT NULL,
|
||
options_json TEXT,
|
||
updated_at TEXT NOT NULL,
|
||
PRIMARY KEY(route_key, scope_key)
|
||
);
|
||
```
|
||
|
||
## 运行时流程
|
||
|
||
### 登录
|
||
|
||
1. CLI 把 `PopiNewAPI token` 交给 `popiartServer`
|
||
2. `popiartServer` 用 `/v1/models` 验证 token
|
||
3. server 签发本地 `sess_xxx`
|
||
4. 本地 session 持久化到 SQLite,重启后仍可继续使用
|
||
|
||
### 创建 job
|
||
|
||
1. server 解析 `skill_id -> route_key -> model_id`
|
||
2. `jobs` 表写入一条本地 job ref
|
||
3. goroutine 开始向 `PopiNewAPI` 发请求
|
||
|
||
### 完成同步结果 job
|
||
|
||
1. 如果 `PopiNewAPI` 直接返回同步结果,例如 `b64_json` 或 `url`
|
||
2. `popiartServer` 会先把结果 re-host 到本地 media store
|
||
3. 再把本地 `local_path + media_id + stable url` 写进 `result_refs_json`
|
||
4. `GET /jobs/:id/artifacts` 时再从 `result_refs_json` 派生 artifact 列表
|
||
|
||
### 拉取 artifact
|
||
|
||
1. server 从 `artifact_id` 反推出 `job_id + result index`
|
||
2. 读取 `jobs.result_refs_json`
|
||
3. 如果是本地 `local_path`,直接读取本地文件并流式返回
|
||
4. 如果是旧的 `data_url`,直接解码并流式返回
|
||
5. 如果是旧的远端 `url`,server 代理下载并返回
|
||
|
||
### 读取 media
|
||
|
||
1. `POST /v1/media/upload` 会把本地文件写入 `media/blobs/`
|
||
2. server 同时写一份 metadata JSON 到 `media/meta/`
|
||
3. `GET /v1/media/:id` 返回 media 元数据
|
||
4. `GET /v1/media/:id/content` 返回可供模型或客户端直接 fetch 的稳定内容路径
|
||
|
||
## 当前边界
|
||
|
||
- server 重启后:
|
||
- `session` 会保留
|
||
- `job ref` 会保留
|
||
- `artifact` 通过 `result_refs_json` 继续可读
|
||
- 新 `media` 文件和 metadata 会继续可读
|
||
- 已存在的 `pending/running` job 仍然不会自动恢复执行
|
||
- 视频类 `upstream_task` 路径只预留了字段,后续再接 `PopiNewAPI` task 查询
|
||
- 旧数据里仍可能存在 `data_url` 或上游 `url`
|
||
- 新写入路径优先落本地 media store,从而给 artifact 补出稳定 `url`
|