角色:Model / 持久化(拥有文章/句子/媒体/反馈/每周推荐)
端口
8014 · 仅内部调用 · 控制器与 collection_server 的数据后端data_server 是核心存储服务,拥有文章、句子、媒体对象、反馈与每周推荐。路由通过 Repository / ObjectStore / FileStore Protocol 访问存储。关系存储现在只支持 PostgreSQL,运行时没有 SQLite fallback、没有存储后端开关,并拆成两个物理隔离的库:rakull_data(文章 / 句子 / 媒体 / 反馈 / 每周推荐 / 情景旅程等内容库,58 张表)与 rakull_collection(收藏域 collections / collection_items,2 张表)。两库由同一进程持有,但绝不能跨库 JOIN。OBJECT_STORE 只切换对象 / 文件存储:local(默认,媒体字节在 PostgreSQL media_objects.data BYTEA + 本地 uploads 目录)或腾讯云 COS(OBJECT_STORE=cos),上层调用方式不变。仓储实现为 PgArticleRepository、PgSentenceRepository、PgArticleFavoriteRepository、PgWeeklyRecommendationRepository、PgFeedbackRepository、PgCollectionRepository、PgUnitOfWork 等(位于 app/storage/pg/)。它不调用任何其他服务。
接口 /v1/data/*
文章
| 方法 / 路径 | 用途 |
|---|---|
POST /v1/data/articles | 创建,返回 id |
GET /v1/data/articles?user_id=&role=&genre= | 列表(可见性入参由控制器给出) |
GET /v1/data/admin/articles | root 全量列表(筛选、排序、标题/作者搜索,并返回 analysis_models) |
PUT /v1/data/admin/articles/{id}/visibility | root 显式设为公开/私有 |
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_id、audio_voice 与最近一次失败的 tts_error;local 模式下字节存于 PostgreSQL media_objects.data 的 BYTEA 列(上传文件另走本地 uploads 目录),cos 模式下 data=NULL 而字节存于远端对象存储,由 storage_key 指向。句子响应继续提供 has_audio,并派生 tts_status=ready|missing|failed、tts_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_count 与 usage_count,并给出 lifecycle_status、deletable 和删除阻止原因。兼容字段 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_collection(collections / collection_items + SM-2 调度列,2 张表),由 data_server 唯一持有,与 rakull_data 物理隔离、不能跨库 JOIN | postgresql://rakull:rakull@localhost:5432/rakull_collection |
CONTROLLER_ASSERTION_SECRET | 校验 study/collection 内部调用 X-Rakull-Service-Assertion 的共享密钥;生产/预发必须显式提供 | 仅开发环境缺失时生成临时值 |
UPLOAD_DIR | OBJECT_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_REGION | COS 桶所在地域,例如 ap-shanghai | ap-shanghai |
COS_BUCKET | COS 桶名(含 APPID 后缀,例如 bucket-appid) | 空 |
COS_RUNTIME_CONFIG_PATH | 管理后台 COS 覆盖文件路径;应位于持久化且仅服务账号可读的位置 | 服务目录下 .data/cos-config.json |
config.local.yaml > config.yaml > 默认值。测试当前表单不会写入;保存会直接覆盖运行时配置,空凭证按清空处理。SecretId/SecretKey 只显示“已配置”状态,绝不回传明文。更多安全边界与六步探测见对象存储(COS)。
本地开发先运行仓库根目录的 scripts/setup_postgres.sh 备好 PostgreSQL(Homebrew postgresql@18,幂等安装 / initdb / 启动,创建 rakull 登录角色与 rakull_data、rakull_collection 两库,可用 PG_MAJOR / PG_HOST / PG_PORT / PG_ROLE / PG_PASSWORD 覆盖;scripts/init_all_dbs.sh 会自动调用它)。bootstrap 按 app/db/schema.sql 与 app/db/collection_schema.sql 在两个库幂等建 schema,这两个 SQL 文件是 DDL 的事实来源。旧 SQLite 数据的一次性迁移见安装与快速开始。