popiart-server
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

5.8 KiB

popiartServer 薄膜持久化设计

这份文档描述当前的轻量化持久化方案。目标不是把 popiartServer 做成第二个模型平台,而是让它只保存 PopiArt 自己的产品语义。

设计原则

PopiNewAPI 已经负责:

  • 上游 provider key
  • 模型请求
  • 原始用量
  • 一部分任务型能力的 task 状态

所以 popiartServer 不再重复持有:

  • provider 侧原始产物数据库
  • 本地 job_logs table

它当前保留五类本地状态:

  • sessions
  • jobs(更准确说是 job refs)
  • artifacts
  • media_records
  • skill_routes
  • media blobs

当前关系

session -> jobs
jobs    -> result_refs_json
jobs    -> artifacts
artifacts -> media_records
project -> skill_routes
media   -> local files

其中:

  • session 保存 PopiArt 登录态和对应的 PopiNewAPI token
  • jobs 保存 skill 语义、用户归属、上游引用和同步结果引用
  • result_refs_json 仍作为 job 级兼容引用保留
  • artifacts 保存用户可查询的 artifact 元数据与溯源快照
  • media_records 保存稳定 URL 所需的 media 元数据
  • skill_routes 保存项目级路由覆盖
  • media blobs 保存真实文件内容

存储位置

默认配置来自这些环境变量:

  • POPIART_DATA_DIR
  • POPIART_SQLITE_PATH
  • POPIART_SESSION_SECRET

默认值:

POPIART_DATA_DIR=./data
POPIART_SQLITE_PATH=./data/popiart.db

Repository 接口

代码位置:

  • internal/server/repository.go

当前 repository 分成五层:

  1. SessionRepository
  2. JobRepository
  3. RouteRepository
  4. ArtifactRepository
  5. MediaRepository

本地开发版没有对象存储依赖,但现在有一个轻量 media 存储层:

  • blob 文件保存在 POPIART_DATA_DIR/media/blobs/
  • 兼容期 metadata JSON 保存在 POPIART_DATA_DIR/media/meta/
  • SQLite 中的 media_records / artifacts 是当前主元数据存储

SQLite Schema

sessions

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

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)
);

media_records

CREATE TABLE media_records (
  media_id TEXT PRIMARY KEY,
  user_id TEXT NOT NULL,
  artifact_id TEXT,
  project_id TEXT,
  filename TEXT NOT NULL,
  content_type TEXT NOT NULL,
  size_bytes INTEGER NOT NULL,
  created_at TEXT NOT NULL,
  url TEXT NOT NULL,
  visibility TEXT,
  sha256 TEXT,
  local_path TEXT NOT NULL
);

artifacts

CREATE TABLE artifacts (
  artifact_id TEXT PRIMARY KEY,
  user_id TEXT NOT NULL,
  job_id TEXT NOT NULL,
  project_id TEXT,
  result_index INTEGER NOT NULL,
  media_id TEXT,
  filename TEXT NOT NULL,
  content_type TEXT NOT NULL,
  size_bytes INTEGER NOT NULL,
  created_at TEXT NOT NULL,
  expires_at TEXT,
  visibility TEXT,
  sha256 TEXT,
  storage_status TEXT,
  source_skill_id TEXT,
  source_model_id TEXT,
  source_route_key TEXT,
  source_input_json TEXT,
  prompt_text TEXT,
  usage_json TEXT,
  ref_json TEXT NOT NULL
);

skill_routes

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_jsonurl
  2. popiartServer 会先把结果 re-host 到本地 media store
  3. 再把本地 local_path + media_id + stable url 写进 result_refs_json
  4. 同时 upsert 到 media_recordsartifacts
  5. GET /jobs/:id/artifacts / GET /v1/artifacts 优先从 SQLite 读取

拉取 artifact

  1. server 从 artifact_id 反推出 job_id + result index
  2. 优先读取 artifacts 表中的 ref_json
  3. 如果 SQLite 尚未命中,则从 jobs.result_refs_json 回填并重试
  4. 如果是本地 local_path,直接读取本地文件并流式返回
  5. 如果是旧的 data_url,直接解码并流式返回
  6. 如果是旧的远端 url,server 代理下载并返回

读取 media

  1. POST /v1/media/upload 会把本地文件写入 media/blobs/
  2. server 同时写 SQLite media_records,并保留兼容 JSON meta
  3. GET /v1/media/:id 优先读 SQLite,JSON 作为 fallback
  4. GET /v1/media/:id/contentlocal_path 读取内容

当前边界

  • server 重启后:
    • session 会保留
    • job ref 会保留
    • artifact 通过 SQLite 主路径继续可读
    • 旧数据仍可通过 result_refs_json / JSON meta 懒回填
    • media 文件和 metadata 会继续可读
  • 已存在的 pending/running job 仍然不会自动恢复执行
  • 视频类 upstream_task 路径只预留了字段,后续再接 PopiNewAPI task 查询
  • 旧数据里仍可能存在 data_url 或上游 url
  • media/meta/*.json 仍保留作为兼容层,不再是主元数据来源
  • 新写入路径优先落 SQLite + 本地 media store,从而给 artifact 补出稳定 url