# nowen-heic **Repository Path**: slamkk/nowen-heic ## Basic Information - **Project Name**: nowen-heic - **Description**: nowen-heic是nowen note项目的https://github.com/cropflre/nowen-note.git,heic heif图片增强支持外部插件 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-10 - **Last Updated**: 2026-09-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Nowen-Note HEIC/HEIF 兼容方案 > 在 Synology NAS 上自托管 Nowen-Note,解决 HEIC 图片无法上传和预览的问题。 ## 架构总览 ``` 浏览器(手机/PC) ↓ HTTPS :8040 nginx-proxy(SSL 反代) ↓ nowen-transcoder(HEIC→JPEG 转码 + JWT 鉴权) ↓ nowen-note(官方镜像,bind-mount 补丁覆盖) ``` ### 三容器服务 | 容器 | 端口 | 角色 | |------|------|------| | `nowen-note` | 3001(内部) | 核心应用,bind-mount 后端+前端补丁 | | `nowen-transcoder` | 3002(内部) | HEIC/HEIF → JPEG 转码,JWT 鉴权代理 | | `nowen-nginx` | 8040(外部) | SSL 反向代理(仅监听 8040,容器不占用 80/443) | ### 数据流 ``` 上传: 浏览器 → nginx:8040 → nowen-note:3001(直接存储) 预览: 浏览器 → nginx:8040 → transcoder:3002 → nowen-note:3001(JWT 鉴权) ↓ HEIC? → heic-convert WASM → JPEG → 缓存到 .transcoded/.jpg JPEG? → 直接透传 ↓ 返回 JPEG 给浏览器 ``` --- ## 快速部署 ### 前置条件 - Synology NAS(DS416play 或同等) - Docker + docker-compose v1 已安装 - SSH 访问 NAS - SSL 证书(`.cer` + `.key`) - 域名解析到 NAS ### 第一步:克隆仓库到 NAS ```bash # 通过 SSH 进入 NAS ssh @ -p # 创建目录结构并克隆仓库 cd /volume1/docker/nowen-note mkdir -p patches/{routes,frontend/assets} data certs git clone https://gitee.com/slamkk/nowen-heic.git . ``` > **为什么用 git 而不是 scp?** > - 便于后续在 Mac 上修改配置后 `git push` → NAS `git pull` > - 保留变更历史,便于回滚 > - 支持大文件(前端 bundle 通常 >1MB) > - 避免每次更新都重新上传全部文件 ### 第二步:配置环境变量 复制环境变量模板并填入实际值: ```bash cp .env.example .env nano .env ``` 编辑 `/volume1/docker/nowen-note/.env`: ```bash # 管理员 userId(从 DB 查询,见下方获取方法) ADMIN_USER_ID= # 管理员 tokenVersion(改密后 +1) ADMIN_TOKEN_VERSION= # Token 有效期 TOKEN_EXPIRES_IN=30d # 镜像版本 NOWEN_IMAGE_TAG=latest # 时区 TZ=Asia/Shanghai # 公开 Web Origin(可选,留空禁用) PUBLIC_WEB_ORIGIN= ``` **获取 ADMIN_USER_ID:** ```bash docker exec nowen-note node -e " const db = require('/app/backend/node_modules/better-sqlite3')('/app/data/nowen-note.db'); console.log(db.prepare(\"SELECT id, username, role, tokenVersion FROM users WHERE role='admin'\").get()); " ``` ### 第三步:放置 SSL 证书 ```bash # 证书放在 data 目录(会被 nginx-proxy 容器挂载到 /etc/nginx/ssl) cp /path/to/fullchain.cer /volume1/docker/nowen-note/data/ cp /path/to/huaxuntech.top.key /volume1/docker/nowen-note/data/ ``` **证书路径说明:** - 宿主机路径:`/volume1/docker/nowen-note/data/fullchain.cer` - 容器内路径:`/etc/nginx/ssl/fullchain.cer`(通过卷挂载自动映射) ### 第四步:启动服务 **通过 Synology Container Manager GUI(推荐):** 1. 打开 DSM → Container Manager 2. 项目 → 选择 `nowen-note` 3. 点击「操作」→「重新启动」或「启动」 **或通过 SSH(如有 sudo 权限):** ```bash cd /volume1/docker/nowen-note sudo docker-compose up -d ``` ### 第五步:验证 ```bash # 检查容器状态 sudo docker ps | grep nowen # 测试 HEIC 预览(替换为你的附件 ID) curl -sk https://:8040/api/attachments/ \ -o /dev/null -w "HTTP:%{http_code} TYPE:%{content_type}" # 测试首页 curl -sk https://:8040/ -w "HTTP:%{http_code}" -o /dev/null # 测试 HTTP→HTTPS 重定向 curl -sk http://:8040/ -w "HTTP:%{http_code}" -o /dev/null # 期望返回 301 ``` --- ## SSL 证书管理 ### 证书存放位置 | 位置 | 用途 | |------|------| | `/volume1/docker/nowen-note/data/fullchain.cer` | 证书链文件(证书 + 中间证书) | | `/volume1/docker/nowen-note/data/huaxuntech.top.key` | 私钥文件 | | `/etc/nginx/ssl/` (容器内) | nginx 读取证书的挂载路径 | ### 证书续期 建议在 NAS 上配置自动续期: ```bash # 添加到 crontab(每日 3:00 AM 检查续期) crontab -e # 添加以下行: 0 3 * * * certbot renew --quiet && docker restart nowen-nginx ``` 或使用 Synology 自带的证书管理工具。 ### 证书验证 ```bash # 检查证书有效期 openssl x509 -in /volume1/docker/nowen-note/data/fullchain.cer -text -noout | grep "Not After" # 测试证书配置 docker exec nowen-nginx nginx -t ``` --- ## 文件说明 ### 核心文件 | 文件 | 说明 | |------|------| | `transcoder.js` | HEIC→JPEG 转码代理服务,含 JWT 鉴权 | | `docker-compose.yml` | 三容器编排配置 | | `nginx.conf` | SSL 8040 反向代理配置 | | `nginx-http-redirect.conf` | 备用 HTTP 重定向配置(仅重定向,无 SSL) | | `.env.example` | 环境变量模板 | | `.env` | 实际环境变量(勿提交到 git) | | `cleanup-cache.sh` | 定时清理 30 天以上转码缓存 | ### 补丁文件 | 文件 | 说明 | |------|------| | `patches/routes/diary.js` | 后端:MIME 白名单 + 扩展名回退检测 | | `patches/routes/attachments-core.js` | 后端:HEIC/HEIF 加入 MIME 白名单 | | `patches/frontend/assets/index-8JpuPwP-.js` | 前端主 bundle:修复 HEIC 类型识别 | | `patches/frontend/assets/DiaryCenter-BF6P-bJy.js` | 前端日记中心 bundle | | `patches/frontend/assets/NavRail-BtZqIfk4.js` | 前端导航栏 bundle | | `patches/frontend/assets/apply-frontend-patch.sh` | 前端补丁一键部署脚本 | ### 报告 | 文件 | 说明 | |------|------| | `reports/auth-analysis.md` | JWT 鉴权机制深度分析 | | `reports/review.md` | 方案评审报告 | | `README_HEIC_FIX.md` | HEIC 修复总结 | | `用户指南.md` | 面向最终用户的操作指南 | --- ## 关键修复点 ### 1. JWT 鉴权注入(transcoder.js) 官方镜像升级后 `ATTACHMENT_LEGACY_PUBLIC_URL` 默认关闭,附件接口需要 Bearer JWT Token。 transcoder 启动时读取 `/app/data/.jwt_secret`,用管理员账户签发 `typ=login` Token,代理请求时自动注入 `Authorization` header。Token 剩余 <5 分钟时自动重签。 ### 2. nginx HTTP→HTTPS 重定向(nginx.conf) 添加端口 8040 的 HTTP server block,返回 301 重定向到 HTTPS: ```nginx server { listen 8040; server_name my.huaxuntech.top; return 301 https://my.huaxuntech.top:8040$request_uri; } ``` ### 3. 前端 bundle 修复 官方镜像前端代码中 `hT` 对象和 `Set` 包含孤立字符串 `"image/heic","image/heif"`,缺少 key,导致 JavaScript 语法错误,前端无法识别 HEIC 类型为图片。 补丁将孤立字符串替换为正确的 `heic:"image/heic",heif:"image/heif"` 键值对。 ### 4. Ka() 函数修复 — 错误 MIME 类型处理 Harmony OS WebView 给 HEIC 文件分配错误的 MIME 类型(如 `application/octet-stream`), 原代码 `if(t.type)return t;` 直接返回不修正,导致后续校验失败。 修复:只返回已知正确的 MIME 类型: ```js // 修复前 if(t.type)return t; // 修复后 if(t.type&&/^(image|video)\//.test(t.type))return t; ``` ### 5. heic-convert API 修复 `heic-convert` 导出的是 async 函数(返回 Promise),不是回调函数。原代码用回调方式调用,导致转码永远不会完成。 修复:`const out = await converter({ buffer, format: 'JPEG', quality: 0.8 })` --- ## 维护指南 ### 官方镜像升级后 每次升级官方镜像需要重新应用补丁: ```bash cd /volume1/docker/nowen-note # 1. 备份当前数据 sudo docker cp nowen-note:/app/data . # 2. 重新拉取镜像 sudo docker pull cropflre/nowen-note:latest # 3. 重新生成后端补丁(从新镜像提取原文件) sudo docker run --rm --entrypoint sh cropflre/nowen-note:latest \ -c 'cat /app/backend/dist/routes/attachments-core.js' \ > original-attachments-core.js # 4. 注入 heic/heif 到白名单 sed -i 's/"image\/bmp",/"image\/bmp",\n "image\/heic",\n "image\/heif",/' original-attachments-core.js sed -i 's/"image\/bmp": "bmp",/"image\/bmp": "bmp",\n "image\/heic": "heic",\n "image\/heif": "heif",/' original-attachments-core.js cp original-attachments-core.js patches/routes/attachments-core.js # 5. 重新提取并修复前端 bundle sudo docker run --rm --entrypoint sh cropflre/nowen-note:latest \ -c 'cat /app/frontend/dist/assets/index-8JpuPwP-.js' \ > patches/frontend/assets/index-8JpuPwP-.js # 用 python 修复孤立字符串(参考 patches 目录中的脚本) # 6. 重启 sudo docker-compose up -d ``` ### 管理员改密后 管理员修改密码后 `tokenVersion` 递增,transcoder 签发的 JWT 失效(返回 401)。 ```bash # 1. 查询新的 tokenVersion docker exec nowen-note node -e " const db = require('/app/backend/node_modules/better-sqlite3')('/app/data/nowen-note.db'); console.log(db.prepare(\"SELECT tokenVersion FROM users WHERE id='' \").get().tokenVersion); " # 2. 更新 .env 中的 ADMIN_TOKEN_VERSION # 3. 重启 transcoder docker-compose restart nowen-transcoder ``` ### 缓存清理 ```bash # 手动清理 bash cleanup-cache.sh # 预览模式(不实际删除) bash cleanup-cache.sh --dry-run # 添加 crontab(每日 3:00 AM) crontab -e # 0 3 * * * /volume1/docker/nowen-note/cleanup-cache.sh >> /volume1/docker/nowen-note/cleanup.log 2>&1 ``` --- ## 故障排查 ### 端口 8040 无法访问 **症状:** curl 返回 "Connection refused" **排查步骤:** ```bash # 1. 检查容器状态 sudo docker ps --filter name=nowen # 2. 检查 nginx 日志 sudo docker logs nowen-nginx # 3. 验证端口监听 sudo ss -tlnp | grep 8040 # 4. 测试容器内 nginx 配置 sudo docker exec nowen-nginx nginx -t ``` **常见原因:** - 容器未启动:通过 Container Manager GUI 或 `docker-compose up -d` 启动 - 端口冲突:检查是否有其他服务占用 8040 端口 - SSL 证书缺失:确认 `/etc/nginx/ssl/` 下存在证书文件 ### nowen-nginx 容器卡在 Created / 无法启动(80 端口冲突) **症状:** `docker ps -a` 显示 `nowen-nginx | Created`(而非 `Up`),`https://:8040` 无法访问。 **根因:** `docker-compose.yml` 的 `nginx-proxy` 服务里多了一行 `- "80:80"` 端口映射。Synology 的 **80 端口已被系统自带 nginx(DSM)占用**,容器无法绑定,docker 直接拒绝启动: ``` Error starting userland proxy: listen tcp4 0.0.0.0:80: listen: address already in use ``` 本方案的对外入口是 **8040**,`80:80` 映射完全多余(`nginx.conf` 里即使写了 `listen 80` 的跳转 server 块,外部也走不到容器的 80)。 **修复:** ```bash cd /volume1/docker/nowen-note cp -a docker-compose.yml docker-compose.yml.bak-$(date +%Y%m%d-%H%M%S) sed -i '/- "80:80"/d' docker-compose.yml # 删掉多余的 80 映射 docker rm -f nowen-nginx # 删掉卡住的旧容器 docker-compose up -d nginx-proxy # 重建 ``` **校验:** ```bash docker ps --filter name=nowen-nginx # 应为 Up,端口 0.0.0.0:8040->8040/tcp curl -sk https://localhost:8040/ -o /dev/null -w 'HTTP:%{http_code}\n' # 期望 200 ``` > ⚠️ **部署铁律:`nginx-proxy` 的 `ports` 只能有 `"8040:8040"`,绝不要加 `"80:80"`。** 80/443 归 DSM 系统 nginx,容器不要碰。 ### 端口归属速查 | 端口 | 归属 | 能否被容器占用 | |------|------|----------------| | 80 / 443 | DSM 系统 nginx | ❌ 不可 | | 8040 | nowen-nginx(本方案) | ✅ 仅此一个 | ### transcoder 返回 404 ```bash # 检查 JWT token 是否有效 docker logs nowen-transcoder | grep "JWT token signed" # 检查 nowen-note 是否返回 200(直连) docker exec nowen-transcoder node -e " const http=require('http'); const fs=require('fs'); const jwt=require('/app/backend/node_modules/jsonwebtoken'); const secret=fs.readFileSync('/app/data/.jwt_secret','utf8').trim(); const token=jwt.sign({typ:'login',userId:'',username:'transcoder-service',tver:},secret,{expiresIn:'30d'}); http.get({hostname:'nowen-note',port:3001,path:'/api/attachments/',headers:{'Authorization':'Bearer '+token}},r=>{ console.log(r.statusCode, r.headers['content-type']); }); " ``` ### 上传失败 "Failed to fetch" 检查 nginx 是否返回 301: ```bash curl -sk -X POST https://:8040/api/attachments -w "HTTP:%{http_code}" -o /dev/null # 期望: 200 或 401(需要认证),不期望 301 ``` ### 上传成功但预览空白 1. 检查 MIME 白名单是否生效: ```bash docker exec nowen-note node -e " const {ALLOWED_IMAGE_MIMES} = require('/app/backend/dist/routes/attachments-core'); console.log('heic in white:', ALLOWED_IMAGE_MIMES.has('image/heic')); " ``` 2. 检查前端 bundle 是否有语法错误: ```bash docker exec nowen-note node -e " require('/app/frontend/dist/assets/index-8JpuPwP-.js'); console.log('OK'); " ``` ### nowen-note 启动崩溃 检查 attachments-core.js 补丁是否正确(不应替换整个文件): ```bash docker exec nowen-note sh -c 'wc -l /app/backend/dist/routes/attachments-core.js' # 应该是 1514+ 行,不是 68 行 ``` --- ## 技术细节 ### JWT Token 字段 ```json { "typ": "login", "userId": "", "username": "transcoder-service", "tver": 1, "iat": <签发时间>, "exp": <过期时间> } ``` ### 转码缓存路径 ``` /app/data/attachments/.transcoded/.jpg ``` 缓存由 transcoder 自动管理:首次请求转码后写入,后续请求直接返回。 ### 网络拓扑 ``` nowen-network (bridge) ├── nowen-note:3001 ├── nowen-transcoder:3002 └── nowen-nginx:8040 (→ host 8040) ``` ### SSL 证书挂载 ```yaml # docker-compose.yml nginx-proxy: volumes: - /volume1/docker/nowen-note/data:/etc/nginx/ssl:ro ``` 证书文件放置在 `/volume1/docker/nowen-note/data/` 目录下,容器内自动映射到 `/etc/nginx/ssl/`。 --- ## 许可与声明 本方案基于官方 Nowen-Note 镜像(`cropflre/nowen-note`),仅通过 bind-mount 方式覆盖部分文件,不修改镜像本身。所有补丁文件和配置均保存在宿主机 `/volume1/docker/nowen-note/` 目录下。 --- ## 变更记录 ### 2026-09-10 — 修复 80 端口冲突导致 nginx 无法启动 - **问题**:NAS 上的 `docker-compose.yml` 被改动,给 `nginx-proxy` 加了 `"80:80"` 映射,与 DSM 系统 nginx 冲突,容器卡在 `Created`,`https://my.huaxuntech.top:8040` 无法访问。 - **修复**:删除 `"80:80"`,只保留 `"8040:8040"`;`docker rm -f nowen-nginx` 后 `docker-compose up -d nginx-proxy` 重建。 - **结果**:三容器 Up,8040 外部访问恢复 HTTP 200。 - **说明**:仓库源码本身一直是正确的(只有 8040),故障来自 NAS 上被手工改动的部署文件。 > 提醒:如需修改配置,请改仓库源码后整体部署,避免仓库与线上不一致。