diff --git a/.gitignore b/.gitignore index 5f9cda9..830a563 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ node_modules/ dist/ coverage/ +/test-results/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e5fd01e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,151 @@ +# huike-back 项目指南 + +## 服务器标识 + +| 环境 | 服务器 | SSH 用户 | 说明 | +|------|--------|----------|------| +| **正式服 (production)** | `alchemy-studio.cn` (152.136.103.69, CentOS 8) | `alchemy`(`sudo -u alchemy` 或 `sudo su - alchemy`) | 线上生产环境 | +| **测试服 (test)** | `moicen.com` (101.43.244.164, Ubuntu 24.04) | `weli` | 测试验证环境 | + +**正式服 = alchemy,测试服 = moicen,不要搞混。** + +### 正式服 (alchemy) 部署要点 +- 所有服务以 `alchemy` 用户运行(`sudo -u alchemy -H bash`) +- 代码路径:`/mnt/huiwing/huike-back/`(新版本拆分仓库,非旧 monorepo) +- AuthCore 路径:`/mnt/huiwing/AuthCore/` +- 旧 monorepo (`/mnt/huiwing/huiwing/`) 已由拆分仓库替代,Java 服务已全部下线 + +### 测试服 (moicen) 部署要点 +- 代码路径:`/home/weli/works/huike-back/` +- AuthCore 路径:`/home/weli/works/AuthCore/` + +--- + +## 部署原则 + +**禁止使用 scp/rsync 部署代码。** 所有部署必须走 GitHub push → 服务器 git pull 的流程。 + +```bash +# 本地 +git push + +# 服务器 +ssh +cd /path/to/repo +git pull --ff-only +cargo build --release # 后端 +# 或 sh cp_dist_moicen.sh / cp_dist_huiwing.sh # 前端 +``` + +如果服务器缺少对应仓库,先在服务器上用 `git clone` 克隆,而不是从本机传文件。 + +**Why:** 二进制/构建产物拷贝绕过构建系统,会引入静默的架构或链接不匹配问题。git 工作流确保二进制与代码一致、依赖已解析、构建可重复。 + +--- + + + +## htyproc (Task Processor) + +### 启动流程 + +```bash +cd /path/to/huike-back/htyproc +bash start.sh +``` + +启动后 proc server 默认处于 **PENDING** 状态,不会处理任务。需要调用 API 切换为 RUNNING: + +```bash +# proc server 默认 PENDING,必须手动 start +curl -s 'http://127.0.0.1:3004/api/v1/proc/start' + +# 检查状态 +curl -s 'http://127.0.0.1:3004/api/v1/proc/status' +# → {"r":true,"d":"RUNNING",...} + +# 停止 +curl -s 'http://127.0.0.1:3004/api/v1/proc/stop' +``` + +**注意**:`/api/v1/proc/start` 是 GET 方法(不是 POST),路由定义在 `htyproc/src/proc_api.rs`。 + +重启后必须手动调用 start 才能恢复任务处理。 + +### 重置 FAILED 任务为 PENDING + +若任务因临时故障(如 AI API 不可用)标记为 FAILED,可手动重置重新处理: + +```bash +ssh moicen +sudo -u postgres psql -d htytask_moicen -c "UPDATE dbtack SET task_status='PENDING', updated_at=NOW(), updated_by='manual_reset' WHERE task_id='';" +``` + +确保 proc 为 RUNNING 状态后,等待 WAIT_SEC(默认 5s)即可自动拾取。 + +### NGX_URL 配置陷阱 + +`.env` 中 `NGX_URL` 不要包含 `/api/ngx` 后缀 — 代码 `clients.rs:98` 会拼接 `/api/ngx/audio/convert`,两者叠加会变成双 `/api/ngx/api/ngx/` 路径,导致 OpenResty 返回 405。 + +```bash +# 错误 +NGX_URL=https://admin.huiwings.cn/api/ngx + +# 正确 +NGX_URL=https://admin.huiwings.cn +``` + +### 日志 + +日志文件:`htyproc/htyproc.nohup.log`,受 logrotate 管理。 + +--- + +## AI API 网络架构 + +### 架构说明 + +AI API(Flask,端口 5000)部署在独立的 AI 服务器上(Mac mini 192.168.0.115),Flask 运行在 **Podman 容器** 内,通过 NPS + SSH 隧道穿透访问: + +``` +AI Server Podman container(port 5000) + → Podman gvproxy(port 5000) + → Mac host(port 5000) + → NPS client → NPS server alchemy:10001 + → alchemy(port 5000 via SSH -L) + → moicen(port 5000 via SSH -L) +``` + +- **穿透链路**:本机 → `ssh weli@alchemy-studio.cn` → `ssh -p 10001 weli@localhost`(经 NPS 隧道)→ Mac → `/usr/local/bin/podman exec ai-api` +- **容器信息**:`localhost/ai-api:latest`,端口映射 `5000→5000/tcp`、`5909→5901/tcp`、`2222→22/tcp` +- **便捷工具**:`plan_skills/tools/huiwing-ai-api`(一键执行容器命令,无需逐层 SSH) +- **详细文档**:见 `plan_skills/moicen/moicen-access-ai-api-container.md` + +### SSH 隧道命令 + +```bash +# alchemy 上的隧道(由 tmux ai-tunnel 守护,自动重连) +tmux new-session -d -s ai-tunnel 'while true; do sleep 3; ssh -L 5909:localhost:5909 -L 5000:localhost:5000 weli@localhost -p 10001; done' + +# moicen 到 alchemy 转发 +ssh -L 5000:localhost:5000 weli@alchemy-studio.cn +``` + +### OpenResty 反向代理 + +两台服务器的 OpenResty 都配置了 `ai.conf`,将 `https://ai./api/v1/ai/*` 代理到 `http://127.0.0.1:5000/`(注意尾部 / 会剥离 `/api/v1/ai` 前缀)。 + +Flask 路由实际路径(无前缀): +- `/form_image_compress_audit` — 图片压缩审计 +- `/compare` — AI 评分对比 +- 等 + +### 验证 AI API 可用性 + +```bash +curl -s 'https://ai.moicen.com/api/v1/ai/form_image_compress_audit' \ + -X POST -H 'Content-Type: application/json' \ + -H 'HtySudoerToken: test' -H 'HtyHost: ts.moicen.com' \ + -d '{"url":"https://test.com/test.jpg"}' +# → {"d":{"acknowledged":true},"e":null,"r":true} +``` diff --git a/envs/huiwings/htyproc.env b/envs/huiwings/htyproc.env index 9cf841a..833d1dd 100644 --- a/envs/huiwings/htyproc.env +++ b/envs/huiwings/htyproc.env @@ -7,7 +7,7 @@ HTYUC_URL=http://127.0.0.1:3000 HTYWS_URL=http://127.0.0.1:3001 AI_URL=https://ai.alchemy-studio.cn TS_DOMAIN=ts.huiwings.cn -NGX_URL=https://admin.huiwings.cn/api/ngx +NGX_URL=https://admin.huiwings.cn REDIS_HOST=localhost REDIS_PORT=6379 diff --git a/htyts_models/migrations/2026-04-30-151213_add_dbtask_indexes/down.sql b/htyts_models/migrations/2026-04-30-151213_add_dbtask_indexes/down.sql new file mode 100644 index 0000000..9b370fe --- /dev/null +++ b/htyts_models/migrations/2026-04-30-151213_add_dbtask_indexes/down.sql @@ -0,0 +1,2 @@ +DROP INDEX IF EXISTS idx_dbtask_status_created; +DROP INDEX IF EXISTS idx_dbtask_updated_at; diff --git a/htyts_models/migrations/2026-04-30-151213_add_dbtask_indexes/up.sql b/htyts_models/migrations/2026-04-30-151213_add_dbtask_indexes/up.sql new file mode 100644 index 0000000..1e1fb9f --- /dev/null +++ b/htyts_models/migrations/2026-04-30-151213_add_dbtask_indexes/up.sql @@ -0,0 +1,10 @@ +-- BTREE index on (task_status, created_at DESC) for one_pending_task query: +-- SELECT ... FROM dbtask WHERE task_status = 'PENDING' ORDER BY created_at DESC LIMIT 1 +-- Also benefits all_tasks_page and zombie detection filtered by status. +CREATE INDEX IF NOT EXISTS idx_dbtask_status_created + ON dbtask (task_status, created_at DESC); + +-- BTREE index on updated_at for zombie detection: +-- SELECT ... FROM dbtask WHERE updated_at < cutoff AND task_status IN (...) +CREATE INDEX IF NOT EXISTS idx_dbtask_updated_at + ON dbtask (updated_at); diff --git a/htyts_models/src/payloads/ai_score.rs b/htyts_models/src/payloads/ai_score.rs index 27e2cab..c498de7 100644 --- a/htyts_models/src/payloads/ai_score.rs +++ b/htyts_models/src/payloads/ai_score.rs @@ -9,6 +9,9 @@ pub struct AiScorePayload { /// 现网部分 Redis 叶子缺该字段;Java 侧可为 null,反序列化缺省为空串。 #[serde(default)] pub reference_url: String, + /// 对于 AUDIO_FILE_AI_SCORE,compare_url 在 pipeline 中(音频转换后)才赋值, + /// 前端创建任务时不传此字段,故设为 default。 + #[serde(default)] pub compare_url: String, pub audio_type: String, pub ref_id: String,