角色:Model / 持久化(拥有文章/句子/媒体/反馈/每周推荐)

端口 8014 · 仅内部调用 · 控制器与 collection_server 的数据后端
data_server 是核心存储服务,拥有文章、句子、媒体对象、反馈与每周推荐。路由通过 Repository / ObjectStore / FileStore Protocol 访问存储。关系存储现在只支持 PostgreSQL,运行时没有 SQLite fallback、没有存储后端开关,并拆成两个物理隔离的库rakull_data(文章 / 句子 / 媒体 / 反馈 / 每周推荐 / 情景旅程等内容库,58 张表)与 rakull_collection(收藏域 collections / collection_items,2 张表)。两库由同一进程持有,但绝不能跨库 JOINOBJECT_STORE 只切换对象 / 文件存储:local(默认,媒体字节在 PostgreSQL media_objects.data BYTEA + 本地 uploads 目录)或腾讯云 COS(OBJECT_STORE=cos),上层调用方式不变。仓储实现为 PgArticleRepositoryPgSentenceRepositoryPgArticleFavoriteRepositoryPgWeeklyRecommendationRepositoryPgFeedbackRepositoryPgCollectionRepositoryPgUnitOfWork 等(位于 app/storage/pg/)。它不调用任何其他服务

接口 /v1/data/*

文章

方法 / 路径用途
POST /v1/data/articles创建,返回 id
GET /v1/data/articles?user_id=&role=&genre=列表(可见性入参由控制器给出)
GET /v1/data/admin/articlesroot 全量列表(筛选、排序、标题/作者搜索,并返回 analysis_models
PUT /v1/data/admin/articles/{id}/visibilityroot 显式设为公开/私有
GET /v1/data/articles/{id}详情(含句子与 analysis_models
GET /v1/data/articles/{id}/stats成功率统计
PATCH /v1/data/articles/{id}改元信息
PATCH /v1/data/articles/{id}/visibility改可见性
PATCH /v1/data/articles/{id}/status改状态/进度
PATCH /v1/data/articles/{id}/raw-content改原文
PATCH /v1/data/articles/{id}/summary改文章总结
DELETE /v1/data/articles/{id}删除文章;级联句子并清理不再被引用的媒体行/COS 对象

句子

方法 / 路径用途
POST /v1/data/articles/{id}/sentences批量创建
GET /v1/data/articles/{id}/sentences读取全部
GET /v1/data/articles/{id}/sentences/{idx}读取单句
PUT .../sentences/{idx}/result写入翻译 + 讲解 + 状态
PUT .../sentences/{idx}/translation仅写翻译
PUT .../sentences/{idx}/explanation仅写讲解
PUT .../sentences/{idx}/furigana仅写假名
PUT .../sentences/{idx}/note写笔记
GET / PUT .../sentences/{idx}/audio读/写音频字节(经 media_objects)
PUT .../sentences/{idx}/tts-error持久化或清除最近一次逐句 TTS 错误

编辑与对象/反馈

方法 / 路径用途
POST /v1/data/articles/{id}/diff-preview非破坏性地预览句子差异
POST /v1/data/articles/{id}/apply-edit索引重映射,插入新增/修改句
POST /v1/data/files · GET /v1/data/files/{handle}流式上传与按用户鉴权读取
POST /v1/data/files/resolve返回本地路径或 10 分钟 COS 签名 URL(内部转写使用)
POST /v1/data/files/promote · DELETE /v1/data/files/{handle}临时转写对象转永久或删除
POST /v1/data/feedback持久化反馈
GET /v1/data/usage-records/routing-cost-profiles仅 Controller:按 operation/model 汇总最近 1,000 次成功完整 Token 样本,不返回文章内容或密钥
GET/POST/PUT/DELETE /v1/data/weekly-recommendations...读取或管理每周推荐顺序
/v1/data/admin/publish-requests · .../approve · .../reject审核 publish_request 反馈
GET/POST/PUT /v1/data/admin/cos...root 查看、探测、保存并热应用 COS 配置
GET/POST/DELETE /v1/data/admin/cos/objects...root 按前缀浏览(含可读归属与精确生命周期状态)、生成下载链接或安全删除 COS 对象
成本画像只用于同一个 model_id 的 Endpoint 路由估价。模型样本不足 20 条时回退同 operation 的全局样本;仍无数据时由 Controller 使用输入/输出 1:1。接口同时要求用户 Bearer 与短期 Controller service assertion。 文章详情与 root 文章列表中的 analysis_models 从不可变调用记录派生:只保留成功的 article_analysis 调用,去重后按最近成功使用优先排列。失败尝试、文章摘要和单句重新生成不会进入该字段;没有记录时返回空数组。 写入端点校验 Bearer;部分文章、文件与推荐读取允许可选身份。文章可见性查询使用控制器提供的 user_id / role

媒体与上传文件

句子与情景 NPC 音频统一通过 ObjectStore 写入 media_objects 表,句子行保留 audio_object_idaudio_voice 与最近一次失败的 tts_errorlocal 模式下字节存于 PostgreSQL media_objects.data 的 BYTEA 列(上传文件另走本地 uploads 目录),cos 模式下 data=NULL 而字节存于远端对象存储,由 storage_key 指向。句子响应继续提供 has_audio,并派生 tts_status=ready|missing|failedtts_error、空的 tts_task_id;成功写入音频会自动清除旧错误。 音频采用内容寻址缓存:相同 (text_hash, voice, content_type) 三元组 → 同一个远端对象,跨文章/会话/情景 turn 自动复用,零重复生成、零重复上传。/v1/data/files 则由 FileStore 流式写入,不创建 media_objects 行。普通上传进入 audio|video|image|files/u{uid}/uploads/...;转写源先进入 tmp/u{uid}/transcription/...,成功保留时转入 audio|video/u{uid}/sources/...。所有读取、签名、转永久和删除操作都从 handle 中校验用户归属。 COS 对象列表以批量 SQL 解析当前页 key 的真实归属:TTS 返回文章 ID/标题/句子数,情景音频返回场景、模板和 variant/session,响应同时区分 media_record_countusage_count,并给出 lifecycle_statusdeletable 和删除阻止原因。兼容字段 reference_count 等于媒体行数,而不是业务使用数。用户上传没有权威引用表,因此标记为 untracked,不会仅因媒体行数为零就在管理端开放删除。 删除文章时,文章与句子先在同一个数据库事务中删除;仅当相应 media_objects 行不再被其他句子或情景音频引用时才删除媒体行。COS 对象还会再次按 storage_key 检查共享引用,数据库提交后才尝试删除远端对象;远端清理失败会记录在响应的 cleanup_failures 中,不会回滚已经成功的数据库删除。模型调用用量记录保留用于审计,其 article_id 会按外键规则置空。 对象存储的 COS 配置、key 架构与数据迁移见对象存储(COS);数据归属与物理字段分别见领域模型与数据归属数据库表字段参考;上传、编辑和假名补全的写入差异见文章处理的持久化流程

diff-preview 与 apply-edit

  • diff-preview:只计算并返回 {unchanged, modified, added, deleted}不改动任何数据,用于给用户看编辑前后的差异。
  • apply-edit:真正落库——保留未变的句子行、重映射索引、插入新增/修改的句子,并返回需要重新处理的索引集合。

环境变量

下表变量同样是该服务根目录下的 YAML 配置键(键名一致):config.yaml 存非敏感默认值(已提交),config.local.yaml 存密钥与本机覆盖(已 gitignore,可从 config.example.yaml 复制)。优先级(高 → 低):环境变量 > config.local.yaml > config.yaml > 代码默认值。JWT_SECRET 等密钥与机器相关的数据库连接串放进 config.local.yaml
变量说明默认
JWT_SECRET校验 Bearer 的共享密钥change-me-in-config-local
DATA_DATABASE_URL内容库 PostgreSQL 连接串(文章 / 句子 / 媒体 / 反馈 / 每周推荐 / 情景旅程等,58 张表)postgresql://rakull:rakull@localhost:5432/rakull_data
COLLECTION_DATABASE_URL收藏库 PostgreSQL 连接串:独立物理库 rakull_collectioncollections / collection_items + SM-2 调度列,2 张表),由 data_server 唯一持有,与 rakull_data 物理隔离、不能跨库 JOINpostgresql://rakull:rakull@localhost:5432/rakull_collection
CONTROLLER_ASSERTION_SECRET校验 study/collection 内部调用 X-Rakull-Service-Assertion 的共享密钥;生产/预发必须显式提供仅开发环境缺失时生成临时值
UPLOAD_DIROBJECT_STORE=local 时一般上传文件的本地目录服务目录下 uploads/
OBJECT_STORE对象/文件存储后端:local(媒体字节存于 PG media_objects.data BYTEA,上传文件在本地目录)或 cos(腾讯云 COS)local
COS_SECRET_ID腾讯云 CAM 子账户 SecretId(管理后台标准入口;此处为部署回退)
COS_SECRET_KEY腾讯云 CAM 子账户 SecretKey(管理后台标准入口;此处为部署回退)
COS_REGIONCOS 桶所在地域,例如 ap-shanghaiap-shanghai
COS_BUCKETCOS 桶名(含 APPID 后缀,例如 bucket-appid
COS_RUNTIME_CONFIG_PATH管理后台 COS 覆盖文件路径;应位于持久化且仅服务账号可读的位置服务目录下 .data/cos-config.json
通过 COS storage 保存的五个字段使用:后台运行时设置 > 环境变量 > config.local.yaml > config.yaml > 默认值。测试当前表单不会写入;保存会直接覆盖运行时配置,空凭证按清空处理。SecretId/SecretKey 只显示“已配置”状态,绝不回传明文。更多安全边界与六步探测见对象存储(COS) 本地开发先运行仓库根目录的 scripts/setup_postgres.sh 备好 PostgreSQL(Homebrew postgresql@18,幂等安装 / initdb / 启动,创建 rakull 登录角色与 rakull_datarakull_collection 两库,可用 PG_MAJOR / PG_HOST / PG_PORT / PG_ROLE / PG_PASSWORD 覆盖;scripts/init_all_dbs.sh 会自动调用它)。bootstrap 按 app/db/schema.sqlapp/db/collection_schema.sql 在两个库幂等建 schema,这两个 SQL 文件是 DDL 的事实来源。旧 SQLite 数据的一次性迁移见安装与快速开始

运行

cd rakull_server/data_server
JWT_SECRET=dev-secret uv run uvicorn app.main:app --port 8014