# trans_images_ftp **Repository Path**: rymaker/trans_images_ftp ## Basic Information - **Project Name**: trans_images_ftp - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-10 - **Last Updated**: 2026-08-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # trans_image — FTP 图片递归转 WebP 系统 基于 Nuxt 4 + TypeORM + MySQL + basic-ftp + sharp 的 FTP 图片管理与 WebP 批量转换系统。 设计文档见 `PLAN.md`、`TASKS.md`、`docs/`。 ## 环境 - Node.js v22(开发基线 v22.22.1),包管理器统一 **npm** - MySQL 8(utf8mb4) - 依赖版本基线:nuxt 4.5.2 / typeorm 1.1.0 / mysql2 3.23.2 / basic-ftp 6.2.0 / sharp 0.35.3 / zod 4.4.3 / pinia 4.0.2 - 注意:TypeScript 固定 5.9.x(vue-tsc 3.x 与 TypeScript 7 不兼容,勿升级) ## 导入规则(强制) | 别名 | 指向 | 用途 | | --- | --- | --- | | `#server` | `server/` | 仅服务端代码内部使用 | | `~` / `~~` | `app/` / 项目根 | 前端代码 / 根目录资源 | - 服务端代码禁止 `~/server/...` 导入,统一 `import ... from '#server/...'`。 - 前端不得导入 `server/`;服务端不得导入 Vue 页面、组件、Pinia Store。 - 前后端共享类型与常量放 `shared/`。 ## 常用命令 ```bash npm install # 安装依赖 npm run dev # 开发服务器 http://localhost:3000 npx nuxi typecheck # 类型检查 npm run build # 生产构建(nitro preset: node-server,产物 .output/) npm run preview # 本地预览生产构建 ``` ## 启动方式(必须同时启动 Web 与 Worker 两个进程) 系统是**双进程架构**:Web 负责页面与接口,Worker 负责领取并执行转换任务。 **只启动 Web 时,创建的转换任务会一直卡在「等待 Worker 领取」(`pending`)**——Worker 启动后才会自动领取并执行。 开发环境(开两个终端): ```bash npm run dev # 终端 1:Nuxt 开发服务器 http://localhost:3000 npm run worker # 终端 2:图片转换 Worker(健康检查 http://127.0.0.1:3101/health) ``` 生产环境:pm2 一条命令拉起双进程(`nuxt-web` + `image-convert-worker`),见下文「生产部署」; 无 pm2 时等价直接启动:`npm run start`(Web,需先 `npm run build`)与 `npm run worker`(Worker)各开一个进程。 Worker 健康检查 `http://127.0.0.1:3101/health` 用 **HTTP 状态码**表达健康与否,探针须按状态码判定而非只看响应体: | HTTP | `status` | 含义 | | --- | --- | --- | | 200 | `ok` | 正常 | | 503 | `degraded` | 数据库不可达 / 领取任务持续失败(`degradedReason`、`degradedSeconds` 给出来源与持续时长) | | 503 | `shutting_down` | 正在优雅退出,应摘除流量 | | 连接拒绝 | — | 进程已死 | ## 环境变量 复制 `.env.example` 为 `.env` 并填写(数据库、会话密钥等;FTP 连接配置在管理页面入库 `ftp_config` 表,无环境变量)。 DB 密码与 FTP 密码(入库密文)只允许出现在服务端,禁止进入 `public` 与前端产物。 ## 用户默认 FTP 目录 每个用户登录后的起始浏览路径为**用户级配置**(`user.default_path`),不同用户可不同: ```bash # 设置(路径须位于管理页面 FTP 配置中的允许根目录内) npm run user:set-path -- --username <用户名> --default-path '/backup/chunk/hanvon/hanvon/static/input/2021MGZS/JPG' # 清除(回退到允许根目录) npm run user:set-path -- --username <用户名> --clear # 新建管理员时一并指定 npm run seed:admin -- --username <用户名> --password '<强口令>' --default-path '/backup/...' ``` 修改对用户重新登录后生效(默认目录随会话固化)。 > **Windows Git Bash 注意**:以 `/` 开头的参数会被 Git Bash 自动转成 Windows 盘符路径, > 需在命令前加 `MSYS_NO_PATHCONV=1`(如 `MSYS_NO_PATHCONV=1 npm run user:set-path -- ...`); > PowerShell/CMD 无此问题。 ## 生产部署 最短路径(权威文档见 `docs/DEPLOYMENT.md`): ```bash npm ci # 完整安装依赖(Worker 依赖 devDependency 中的 tsx,禁止 --omit=dev) npm run build # 产出 .output/server(nitro node-server preset) npm run migration:run # 结构迁移;npm run migration:show 应全部 [x] npm run seed:admin -- --username <用户名> --password '<强口令>' # 仅首次部署:创建管理员 pm2 start ecosystem.config.cjs # 启动 nuxt-web + image-convert-worker 双进程 pm2 save npm run smoke # 冒烟检查:配置/构建产物/数据库/Worker 配置逐项 PASS curl http://127.0.0.1:3000/api/health # Web 健康 curl http://127.0.0.1:3101/health # Worker 健康 ``` - 无 pm2 环境可用等价直接启动:`npm run start`(Web)与 `npm run worker`(Worker)各开一个进程。 - Windows 开机自启:`pm2-windows-service` 或 NSSM 包装上述两条命令(见 `docs/DEPLOYMENT.md` §4)。 - `NODE_ENV=production` 启动时强制校验 FTPS、证书校验、≥32 字符会话密钥与 FTP 凭证,缺失即拒绝启动并指明变量名。 ## Docker 部署 镜像与 compose 编排:`Dockerfile`、`docker-compose.yml`、`docker/entrypoint.sh`。 **MySQL 是外部实例,不在编排内**——容器里只有 `web` 和 `worker` 两个常驻服务,外加一个一次性迁移任务 `migrate`。 Web 与 Worker **共用同一个镜像**,只是入口角色不同。 配置直接读项目根目录的 `.env`,和 pm2 部署用的是同一份,无需另建 docker 专用配置: ```bash # 确保 .env 已填好(数据库地址/账号/口令、NUXT_SESSION_SECRET ≥32 字符) docker compose up -d --build # 迁移 → 起 web + worker docker compose run --rm web seed-admin --username admin --password '<强口令>' # 仅首次 ``` 随后打开 `http://127.0.0.1:3000` 登录,**在管理页面完成 FTP 配置**(FTP 连接信息唯一来源是数据库 `ftp_config` 表,不走环境变量)。 其他常用操作(入口脚本已封装角色,见 `docker/entrypoint.sh`): ```bash docker compose ps # 含健康状态 docker compose logs -f worker docker compose run --rm web migrate show # 迁移状态应全部 [x] docker compose run --rm web smoke # 冒烟检查 docker compose run --rm web set-path --username <用户名> --default-path '/backup/...' docker compose restart worker docker compose down # 停止(外部数据库不受影响) ``` ### 几个必须知道的点 - **`.env` 被容器原样复用**,只有两项被 compose 显式覆盖:`NITRO_HOST=0.0.0.0`(容器内不监听 0.0.0.0 则端口映射打不通)和 `NUXT_WORKER_TEMP_DIR=/app/.tmp/convert`(换成挂了命名卷的绝对路径)。其余照搬。 - **外部 MySQL 必须从容器网络可达**。`.env` 里若填的是 LAN 地址(如 `192.168.1.111:53306`),bridge 网络经 NAT 出去即可,无需额外配置。但若数据库就在 Docker 宿主机上、地址写成 `127.0.0.1`,容器里的 `127.0.0.1` 指的是容器自己——要改成 `host.docker.internal`,Linux 宿主还需放开 `docker-compose.yml` 里注释掉的 `extra_hosts: ["host.docker.internal:host-gateway"]`(Docker Desktop 该域名开箱可用)。同一条注意事项对 FTP 服务器地址同样适用。 - **数据库不可达时 `migrate` 会直接失败,`web`/`worker` 随之不启动**,这是刻意的:宁可起不来,也不要一堆进程连着一个连不上的库空转。 - **Worker 在 FTP 配置好之前会反复重启**,这是设计如此的 fail fast:`ftp_config` 表没有完整配置行时 Worker 打印「FTP 未配置」并以退出码 1 结束。管理页面配好后它会自行恢复,无需手动干预。 - **Worker 健康端口只绑 `127.0.0.1`**,所以不对外映射,探针在容器内跑(`docker compose ps` 的 health 列即为其结果)。200=`ok`,503=`degraded`(数据库不可达/领取持续失败)或 `shutting_down`。 - **Web 默认只发布到宿主机回环** `127.0.0.1:3000`,前面挂反向代理。要直接对外访问,在 `.env` 里加 `WEB_PUBLISH_ADDR=0.0.0.0`(可选项还有 `WEB_PUBLISH_PORT`、`WORKER_MEM_LIMIT`,都有默认值)。 - **镜像偏大(含完整 `node_modules`)**,这是有意的:Worker 不是打包产物,而是 tsx 直跑 `worker/index.ts` 源码,依赖 `.nuxt/tsconfig.server.json` 提供的 `#server`/`#shared` 别名和位于 devDependencies 的 tsx——与部署文档「禁止 `npm ci --omit=dev`」是同一条约束。 - **停机留了 120s**(`stop_grace_period`):Worker 收到 `SIGTERM` 后要把在途文件写完、任务回 `queued`,强杀会留下 `.uploading-*` 残留(下次启动会清理,但没必要)。 ## 已有部署升级 Worker 转换流水线做过一轮优化,其中重试退避改为落库表达,**新增了 `image_convert_task_item.next_attempt_at` 列**: ```bash npm run migration:run # 必须先跑;npm run migration:show 应全部 [x] pm2 restart ecosystem.config.cjs ``` > 漏跑迁移会让 Worker 在拉取待处理明细时因缺列直接报错。升级前建议等在跑的任务收尾,或依赖优雅退出—— > Worker 收到 `SIGTERM` 后会在检查点停止领取新文件,在途文件写完即把任务回 `queued` 等待接管,不会产生半截文件。 优化要点(细节见 `docs/ARCHITECTURE.md` §5.5/§6.2/§7): - **重试退避不再占用文件并发槽位**:临时错误失败后立即把明细置回 `pending` 并写 `next_attempt_at`, 槽位当场交还给健康文件;原实现在并发池协程里 `sleep(2s/5s/15s)`,FTP 抖动时会拖垮整批吞吐。 - **取消 / 超时 / 优雅退出下沉为批内检查点**,不再只在每 100 条的批次边界判断,`SIGTERM` 响应从分钟级回到秒级。 - **每批一次性领取明细**(1 条 `UPDATE ... WHERE id IN (...)` 代替 100 次往返); 取消标记检查加 1 秒 TTL 缓存与并发合并,代价是取消最多延迟 1 秒被感知。 - **`overwrite=true` 时跳过目标存在性探测**(结论用不上),每个文件省一次 FTP 往返。 - **任务计数一律以明细表对齐**:扫描收口与进度落库失败后都走 `realignTaskCountersWithItems`, 避免恢复重排导致的重复累计与计数丢失。 多分辨率输出功能**新增了 `image_convert_task.extra_resolutions` 列**(迁移 1700000009000;同一条迁移还合并了两处索引变更:任务表补 `idx_task_status_id` / `idx_task_created_by_id`,明细表删除冗余的 `idx_item_task_status`): ```bash npm run migration:run # 必须先跑;npm run migration:show 应全部 [x] pm2 restart ecosystem.config.cjs ``` > 漏跑迁移会让 Web/Worker 在读写任务时因缺 `extra_resolutions` 列直接报错。老任务迁移后该列为 `NULL`, > 行为与升级前完全一致(只输出原始分辨率,不产生任何变体文件)。