提灯喵 Rust 后端使用说明
环境要求
- Rust 1.75+
- MySQL 8(库名
tdm) - Docker(可选,测试库)
配置
复制 config/dev.toml,或通过环境变量覆盖:
| 变量 | 说明 |
|---|---|
DATABASE_URL |
MySQL 连接串 |
DB_USER / DB_PASSWORD |
数据库账号 |
SECRET_ID |
腾讯云 SecretId(默认 dev.toml,TDM OSS 用 manga-trans 密钥对) |
SECRET_KEY |
腾讯云 SecretKey(见 keychain_20260427/keys.txt,勿用 backup 密钥);写入 TdmServerRust/.env 即可,若 shell 残留 ci-placeholder 会自动被 .env 覆盖 |
CDN_KEY |
CDN 签名密钥(ossdev.yuriful.top) |
启动测试数据库
cd TdmServerRust
docker compose up -d
迁移脚本位于 migrations/(自 Java Flyway 复制)。
CI/本地 Docker 基础库结构见 docker/mysql/00_schema.sql(同步自 WebBack-end local/tdm.sql)。
同步远程 dev 数据到本地
- Xshell 连接 dev,隧道:
本地 13307→127.0.0.1:3306 - 启动本地库:
docker compose up -d mysql - 同步:
cd TdmServerRust
$env:REMOTE_DATABASE_URL = "mysql://tdm:密码@127.0.0.1:13307/tdm?ssl-mode=disabled"
.\scripts\sync_remote_db.ps1
.env改本地连接:
DATABASE_URL=mysql://root:root@127.0.0.1:3307/tdm
| 参数 | 说明 |
|---|---|
-SkipImport |
仅导出到 tmp/tdm_remote_dump.sql |
LOCAL_DATABASE_URL |
覆盖本地连接串,默认 root:root@127.0.0.1:3307/tdm |
启动服务
cargo run -- --profile dev
默认端口 8090,与 Java 一致。
HTTP/2(本地 / 生产)
| 场景 | 做法 |
|---|---|
| 本地 dev | cargo run -- --profile dev-h2(TLS + ALPN 协商 h2;首次自动生成 config/certs/dev/*.pem) |
| RustRover | Cargo 配置文件选 dev-h2;程序实参填 --profile dev-h2(前者是编译 profile,后者是应用配置档) |
| 前端 | .env.local-development 改为 VITE_BASE_API=https://localhost:8090,浏览器信任自签证书 |
| 生产 | nginx listen 443 ssl http2,示例见 deploy/nginx/back-end.http2.conf.example |
Chrome Network 协议列应显示 h2;dev 控制台 [api-timing] 的 queue 应明显下降。
OSS 下载
| 场景 | 接口 | 说明 |
|---|---|---|
| 单文件 | GET /api/oss/downloadCredential + CDN 直链,或 GET /api/oss/downloadFile 302(非浏览器客户端) |
|
| ZIP/CORS 回退 | GET /api/oss/downloadFile/proxy |
服务端代理,需连接池 |
| CDN CORS | 见 deploy/cdn-cors.md |
配好后 ZIP 直拉 CDN |
开发期双端对比:Java 8090 / Rust 8091(修改 config/dev.toml 中 port)。
dev 模式自动记录每个 HTTP 请求/响应(body 超 64KB 截断),并在控制台输出调用栈耗时树(prof │ 前缀)。
调用栈耗时分析(仅 dev)
--profile dev 时,每个 API 请求结束后在 stderr 打印各层方法 self(自身)与 incl(含子调用)耗时。
| 环境变量 | 说明 |
|---|---|
RUST_LOG |
默认 tdm_server_rust=debug |
DEV_PROFILE_MIN_MS |
隐藏 self 低于该阈值的 span(毫秒),默认 0.1 |
DEV_PROFILE_NO_LINKS |
设为任意值则禁用源码链接 |
DEV_PROFILE_LINK_SCHEME |
jetbrains(默认)/cursor/vscode/file/none |
DEV_PROFILE_JB_STYLE |
rustc(默认)/stack |
DEV_PROFILE_JSONL |
每请求追加一行 JSON profile(如 tmp/member_profile.jsonl) |
DEV_SERVER_TIMING |
设为 0 关闭 Server-Timing 响应头(dev 默认开启) |
MANGA_NAME_CACHE_SECS |
译名/原名列表缓存 TTL(秒),默认 60 |
MEMBER_ALL_CACHE_SECS |
全量组员列表缓存 TTL(秒),默认 60 |
RustRover Run 窗口:
- Run 配置 取消「在输出控制台中模拟终端」
- Ctrl+点击独立行
--> src/repository/member_repo.rs:75:1
- Cursor:
DEV_PROFILE_LINK_SCHEME=cursor - self 最大 的行加粗黄色,末尾
◀ hot;报告末尾有hotspot摘要行
输出示例:
prof │ ├─ repository::episode_repo::page_list self 23.3ms ...
--> src/repository/member_repo.rs:75:1
Server-Timing(前后端联调)
--profile dev 时响应头携带 Server-Timing:total(墙钟)、hot(self 最大 span)、s1…(其余热点)。
- 跨域 dev 已暴露
Access-Control-Expose-Headers: server-timing - 关闭:
DEV_SERVER_TIMING=0 - 前端 dev 控制台
[api-timing]合并 Network Timing 与server=[...]
JSON 性能
- 请求/响应 JSON 使用 simd-json(x86 AVX2 / aarch64),否则自动回退 serde_json
- Controller 请求体提取器:
AppJson<T>
新增 service/repository/web handler 的 async fn 后:
python scripts/add_profile_instrument.py
dev 控制台(日志与错误检查)
仅 dev / dev-h2 profile 注册,不经 /api 鉴权。
| URL | 说明 |
|---|---|
/dev/console.html |
Web 控制台(健康 / 错误 / 应用日志) |
/dev/api/health |
JSON 健康检查 |
/dev/api/logs/errors?limit=100 |
内存错误列表 |
/dev/api/logs/app?lines=200 |
tail app.log |
本地:http://localhost:8090/dev/console.html
远程 dev:https://back.dev.yuriful.top/dev/console.html
配置 config/dev.toml [dev_console];部署时 log_dir / app_log 指向 /data/TdmServerRust/。环境变量 DEV_LOG_DIR、DEV_APP_LOG 可覆盖。
RSS 订阅(事件驱动,无轮询)
输出目录:{folder.base}/src(dev 部署常为 /data/WebBack-end/local/tdm/src),由 Caddy rss.dev.yuriful.top 静态托管。
| Feed | 内容 |
|---|---|
rssManga.xml |
新开坑 + 漫画信息更新 + 最新 5 话 |
rssEpisode.xml |
最新 5 话新单话提醒 |
rssEpisode_{post}.xml |
各岗位交稿提醒(bot 订上一工序 feed) |
rssMember.xml |
三月未交稿组员 |
rssPublishLink.xml |
发布链接 |
业务状态变更后异步写盘;rss_file_lock 按文件名互斥。
| 事件 | 刷新 |
|---|---|
| 增/删/改话、接稿 | EpisodePipeline |
| 交稿 | 对应岗位 + rssMember.xml |
| 新发/更新漫画 | rssManga.xml |
| 发布链接 | rssPublishLink.xml |
| 启动 / 每日 12:00 | 全量 / rssMember.xml |
dev 验证:curl -sI https://rss.dev.yuriful.top/rssManga.xml 交稿/改漫画后 Last-Modified 即时变化。
测试:cargo test rss_service;集成 cargo test --test rss_event_test(需 DB)。
单元测试
cargo test
集成测试(需数据库)
PowerShell:
$env:RUN_INTEGRATION_TESTS = "1"
$env:DATABASE_URL = "mysql://root:root@127.0.0.1:3307/tdm"
cargo test --test episode_api_test
cargo test --test member_api_test
cargo test --test manga_api_test
cargo test --test manga_card_repo_test
cargo test --test oss_api_test
cargo test --test task_tracking_api_test
cargo test --test cos_presign_test --test cos_sts_test --test oss_entity_test
/task-tracking 页面 API 冒烟(需后端 8090 + admin 账号):
powershell -File scripts/test_task_tracking_apis.ps1
Git Bash / WSL:
export RUN_INTEGRATION_TESTS=1
export DATABASE_URL=mysql://root:root@127.0.0.1:3307/tdm
cargo test --test episode_api_test
cargo test --test member_api_test
cargo test --test manga_api_test
cargo test --test station_episode_test
cargo test --test oss_api_test
exe 被占用导致 link 失败时:
$env:CARGO_TARGET_DIR = "target/test-build"
cargo test --test episode_api_test
常驻组员填充/清空单话
| 接口 | 行为 |
|---|---|
POST /api/mangas/station/admin |
fillEpisodes: true 时,将该漫画该岗位所有 空位且未交稿 的单话指派给新组员,并写入 *SetupTime |
DELETE /api/mangas/station/:stationId |
删除常驻前,清空该组员在该漫画、该岗位上 未交稿 的单话分配(已交稿不动) |
岗位支持:翻译/校对/嵌字/审稿/时轴(post 1–4、6);图源(post 0)仅按空位填充,无 detail 交稿字段。
集成测试:cargo test --test station_episode_test
公共断言见 tests/common/mod.rs(assert_manga_card_fields、assert_episode_list_fields 等)。
curl 接口测试
依赖:curl、jq、Git Bash 或 WSL(Windows 原生 PowerShell 无 bash 时需 WSL)。
# 单模块 smoke
bash tests/curl/login/01_login.sh
# 字段契约(需 TOKEN)
TOKEN=your_jwt bash tests/curl/manga/02_manga_fields.sh
TOKEN=your_jwt bash tests/curl/episode/02_episode_fields.sh
TOKEN=your_jwt bash tests/curl/member/02_stationed.sh
TOKEN=your_jwt bash tests/curl/reward/02_reward_fields.sh
bash tests/curl/oss/02_oss_credential.sh
# 全量(01 + 02)
TOKEN=your_jwt bash tests/curl/run_all.sh
# Java vs Rust 双端 key 对比
JAVA_URL=http://127.0.0.1:8090 RUST_URL=http://127.0.0.1:8091 \
TOKEN=xxx MANGA_ID=1 MEMBER_ID=1 bash tests/curl/compare_module.sh manga
bash tests/curl/compare_module.sh episode
bash tests/curl/compare_module.sh glossary
字段断言库:tests/curl/lib/assert_json.sh(assert_field_exists、assert_no_field)。
OpenAPI 文档(dev / dev-h2)
| 路径 | 说明 |
|---|---|
/doc/openapi.json |
OpenAPI 3.1 契约,与 Java static/doc/openapi.json 一致 |
/swagger-ui.html |
Swagger UI,url=/doc/openapi.json,operationsSorter=method |
契约源:assets/doc/openapi.json(由 Java smart-doc 同步)。pro 环境不暴露上述路由。
OpenAPI parity 全量验收
契约:tests/fixtures/openapi.json(与 https://back-docs.dev.yuriful.top/ 一致;Java 基准 API 为 https://back.dev.yuriful.top)
用例:tests/fixtures/parity_cases.yaml(101 operation,含 json / multipart / form-urlencoded)
前置:数据库
默认读取 .env / 环境变量中的 DATABASE_URL。如需严格对比业务数据值,请先同步 dev 库:
cd TdmServerRust
$env:REMOTE_DATABASE_URL = "mysql://tdm:密码@127.0.0.1:13307/tdm?ssl-mode=disabled"
.\scripts\sync_remote_db.ps1
$env:DATABASE_URL = "mysql://root:root@127.0.0.1:3307/tdm"
启动 Rust(hotspot 采集必选)
$env:DEV_PROFILE_JSONL = "tmp/openapi_profile.jsonl"
cargo run -- --profile dev
run_openapi_parity.ps1 会在 benchmark 前清空 JSONL;Rust 服务端必须带 DEV_PROFILE_JSONL 启动,否则 hotspot 列显示 none。
启动本地 Java(与 Rust 共用 DATABASE_URL)
$env:DATABASE_URL = "mysql://root:root@127.0.0.1:3307/tdm"
.\scripts\start_local_java_backend.ps1 -Port 8091
跑 Java vs Rust parity + 性能报告
$env:RUN_INTEGRATION_TESTS = "1"
$env:JAVA_BASE_URL = "http://127.0.0.1:8091"
.\scripts\run_openapi_parity.ps1
# 远程 Java 基准:$env:JAVA_BASE_URL = "https://back.dev.yuriful.top"
# 仅 Rust 单测(不调 Java):$env:SKIP_JAVA_PARITY = "1"
# 严格对比业务数据值:$env:STRICT_OPENAPI_PARITY = "1"
单独跑集成测试
cargo test --test openapi_parity_test -- --nocapture
cargo test --test evaluation_api_test
cargo test --test questionnaire_api_test
cargo test --test reward_api_test
cargo test --test task_tracking_api_test
重新生成 parity_cases.yaml
python scripts/generate_parity_cases.py
报告输出:docs/reports/api_performance_YYYY-MM-DD.md
| 列 | 含义 |
|---|---|
| hotspot | 最耗时 span 标签 |
| self_ms | hotspot 自身耗时(ms) |
| pct | hotspot 占 wall 比例 |
| source | jsonl / server-timing / none |
parity 集成测试覆盖全部 37 个 POST/PUT(default_body 自动补请求体);reg 为 Rust-only(skip_java)。
账号:gum979 / 123456(member);Gum979 / 123456(admin task-tracking)
Member 性能巡检
一键(自动启停 dev 服务 + curl + hotspot 表):
cd TdmServerRust
.\scripts\run_member_profile.ps1
# 保持服务运行:.\scripts\run_member_profile.ps1 -KeepServer
手动分步:
$env:DEV_PROFILE_JSONL = "tmp/member_profile.jsonl"
$env:DATABASE_URL = "mysql://root:root@127.0.0.1:3307/tdm"
cargo run -- --profile dev
.\scripts\profile_member_api.ps1
# 或 Git Bash:bash tests/curl/member/03_profile_all.sh
python scripts/parse_profile_report.py tmp/member_profile.jsonl
环境变量:DEV_USER/DEV_PWD(默认 gum979/123456,memberId=18)、MEMBER_ID、BASE_URL。
写接口默认不自动跑。
API 响应格式
{"code":200,"msg":"成功!","data":...}
分页:{"total":100,"rows":[...]}(仅一层 Result 包裹,勿再嵌套 code/data)
列表项主键 JSON 字段为 id(小写,对齐 Java/OpenAPI)
鉴权头:token 或 Authorization(JWT,密钥 yuri.tdm)
Query 与路由约定
- 空 query 字符串(如
email=、mangaOriName=)视为未传参,不参与过滤(对齐 Java/Spring) NaN/undefined的 query 数字参数返回{"code":500,...}业务 JSON,非 axum 400- 动态路径参数使用 axum 0.7 语法
/:id(如/api/members/:id、/api/members/stationedMangas/:id)
CI/CD
| 工作流 | 触发 | 说明 |
|---|---|---|
CI |
push/PR → dev;PR → master | check、clippy、单元测试;push dev 额外跑集成测试 |
Deploy |
push → dev / 手动 | 编译 release → SSH 部署到 /data/TdmServerRust → Flyway → 重启 |
分支:日常开发用 dev,不直接提交 master;任务完成后开 PR。
GitHub Environments(与 WebBack-end 同名):development、production
| 类型 | 变量/密钥 |
|---|---|
| Variables | SSH_HOST、SSH_USERNAME、MYSQL_APP_USER、SENTRY_DSN、SECRET_ID |
| Secrets | SSH_KEY(完整私钥,含 BEGIN/END 行,无 passphrase)、MYSQL_APP_PASSWORD、SECRET_KEY、CDN_KEY |
SSH_KEY 配置(Windows 用文件导入,勿手粘贴):
gh secret set SSH_KEY --env development --repo Tideng-Cat/TdmServerRust < C:\path\to\deploy_key
服务器 ~/.ssh/authorized_keys 需有对应公钥。
手动部署:Actions → Deploy → Run workflow → 选择 development / production。
模块迁移状态
| 模块 | 端点 | 状态 |
|---|---|---|
| Login | 5 | 已迁移 |
| Member | 12 | 已迁移 |
| Author | 7 | 已迁移 |
| Magazine | 7 | 已迁移 |
| Evaluation | 5 | 已迁移 |
| Questionnaire | 3 | 已迁移 |
| Manga | 27 | 已迁移 |
| Episode | 14 | 已迁移 |
| MangaBenefit | 4 | 已迁移 |
| Reward | 10 | 已迁移 |
| OSS | 4 | 已迁移 |
| TaskTracking | 3 | 已迁移 |