756d57d818
部署到群晖 / deploy (push) Successful in 44s
- typeList 从 5 项简化到 4 项:生日 / 结婚纪念日 / 订婚纪念日 / 其他纪念日 - TYPE_NAMES / TYPE_ICONS 中 lunar_birthday 保留兼容映射(也映射到「生日」+ 🎂), 让线上历史数据自然回显,无需数据库迁移(方案 A) - getTypeIndex('lunar_birthday') = 0,老数据编辑时正确回显「生日」 - index.js 列表筛选和 wxml 图标判断本来已含 lunar_birthday 兼容,无需改 老数据自然淘汰:用户重新保存时新数据写 type='birthday',老数据 type 保留 直到下次编辑保存才升级。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
244 lines
14 KiB
Markdown
244 lines
14 KiB
Markdown
---
|
||
name: ""
|
||
description: 生日提醒小程序自建后端的完整运维 playbook:维护场景操作步骤、故障排查表、关键资源清单和踩坑记录
|
||
metadata:
|
||
node_type: memory
|
||
type: reference
|
||
originSessionId: b9f9c436-1656-4989-aa1d-6b9f009fd1f2
|
||
---
|
||
|
||
## 项目速查表
|
||
|
||
| 资源 | 值 |
|
||
|---|---|
|
||
| 小程序前端 | `/Users/gaotu/WebstormProjects/myself/生日提醒小程序` |
|
||
| 后端代码 | 同上目录下 `server/` |
|
||
| Gitea 仓库 | https://git.ymxixi.space/adminym/wxserver.git |
|
||
| 对外 HTTPS | https://wxserver.ymxixi.space |
|
||
| 群晖内网 | http://192.168.1.66:15002 |
|
||
| 小程序 AppID | `wxe72882e2072d141a` |
|
||
| 数据库文件 | 群晖 `/volume1/docker/apps/birthday-server/data/birthday.db` |
|
||
| Docker 容器名 | `birthday-server` |
|
||
| FRP 公网入口 | 47.238.152.174(阿里云香港) |
|
||
| frpc 子域名 | `wxserver`,type=http,软路由 `192.168.1.119` 上 |
|
||
| NPM 反代配置 | NPM 容器 `/data/nginx/proxy_host/33.conf` |
|
||
| 订阅消息模板 ID | `6J7Stt-lu7DKU6jblJ0nZGq_D81z5glnksf7qWfy5Yw` |
|
||
|
||
## 链路图(一图流)
|
||
|
||
```
|
||
微信小程序 (envVersion 切换 BASE_URL)
|
||
↓ HTTPS 443
|
||
wxserver.ymxixi.space (DNS A 记录 → 47.238.152.174)
|
||
↓
|
||
Nginx Proxy Manager (Docker 容器 nginx-proxy-manager-zh-app-1,监听 80/443/81)
|
||
↓ proxy_pass http://127.0.0.1:8080,Host 透传
|
||
frps (vhost_http_port: 8080;公开 vhost_https_port: 4430 但本项目不用)
|
||
↓ frp 隧道(subdomain=wxserver)
|
||
frpc (软路由 192.168.1.119,UCI `/etc/config/frpc`)
|
||
↓ TCP 转发到 192.168.1.66:15002
|
||
群晖 Docker 容器 birthday-server (Node 20 + Express + SQLite + node-cron)
|
||
↓
|
||
/volume1/docker/apps/birthday-server/data/birthday.db
|
||
```
|
||
|
||
## 关键文件清单
|
||
|
||
**前端**
|
||
- `app.js` — 启动初始化 + 调 /api/login 拿 openid,加缓存复用
|
||
- `utils/api.js` — 请求封装;**BASE_URL 自动切换**(develop → localhost:3000,trial/release → 线上 HTTPS)
|
||
- `pages/add-anniversary/add-anniversary.js` — 添加纪念日时调 syncToCloud
|
||
|
||
**后端**
|
||
- `server/src/index.js` — Express 入口 + 4 个接口(/api/login、/api/anniversary、/api/reminder/run、/api/health)
|
||
- `server/src/db.js` — SQLite 表结构(anniversaries / remind_logs)
|
||
- `server/src/wx.js` — 微信 API(access_token 缓存、code2session、sendSubscribeMessage)
|
||
- `server/src/reminder.js` — node-cron 定时任务 + 推送逻辑
|
||
|
||
**部署**
|
||
- `.gitea/workflows/deploy.yml` — Gitea Actions 自动构建并部署
|
||
- `server/Dockerfile` — Node 20 + apt 装 python3/make/g++ + npm install
|
||
- `server/docker-compose.yml` — 本地开发用,生产走 Gitea Actions 直接 docker run
|
||
|
||
**配置**
|
||
- `server/.env.example` — 配置模板(已入库)
|
||
- `server/.env` — 本地真实配置(已 gitignore)
|
||
- Gitea Secrets:`WX_APPID` / `WX_APPSECRET` / `WX_MINIPROGRAM_STATE`(可选 `WX_TEMPLATE_ID` / `REMINDER_CRON`)
|
||
|
||
## 维护场景 Playbook
|
||
|
||
### 场景 1:改后端代码(最常见)
|
||
|
||
```
|
||
# 在本地改 server/ 下文件
|
||
git add server/...
|
||
git commit -m "..."
|
||
git push origin master
|
||
```
|
||
|
||
Gitea Actions 会自动构建并部署(约 3-8 分钟)。验证:
|
||
```
|
||
curl https://wxserver.ymxixi.space/api/health
|
||
```
|
||
|
||
后端无需审核,push 即生效。**关键纪律**:新接口字段要保持向后兼容(默认值 / 可选字段),否则线上用户的旧版小程序会崩。
|
||
|
||
### 场景 2:迭代小程序前端(页面 / 样式 / 交互改动)
|
||
|
||
```
|
||
1. 本地改 pages/ utils/ app.* 等
|
||
2. 开发者工具点「编译」预览,自测
|
||
3. 右上角「上传」→ 版本号递增(如 2.0.1 → 2.0.2)
|
||
- 改 bug:选「修订补丁」
|
||
- 加小功能:选「特性更新」
|
||
- 重大变更:选「版本升级」
|
||
4. mp.weixin.qq.com → 管理 → 版本管理 → 找到刚上传的「开发版本」
|
||
5. 两条路:
|
||
a) 点「体验版」→ 自己/体验成员扫码先试,没问题再回来走 b
|
||
b) 点「提交审核」→ 审核通过(1-2 天)→ 点「发布」正式上线
|
||
6. 用户重新打开小程序自动拉新代码(强制更新可能要 24h)
|
||
```
|
||
|
||
**注意**:微信对小程序代码强制审核,**没有"热替换跳过审核"的办法**。紧急修线上 bug 可用「加急审核」(账号每年 2 次额度)。
|
||
|
||
### 场景 3:前端 + 后端一起改
|
||
|
||
为避免线上旧前端遇到新后端接口出错,**严格按这个顺序**:
|
||
|
||
```
|
||
① 改后端 → git push → CI 部署 → 验证 /api/health
|
||
② 验证后端对旧版本前端仍兼容(关键,否则线上炸锅)
|
||
③ 再改前端 → 上传 → 审核 → 发布
|
||
```
|
||
|
||
后端无审核延迟,可以随时回滚;前端审核+发布有延迟。所以"后端先行 + 向后兼容"是铁律。
|
||
|
||
### 场景 4:改环境变量 / 凭据
|
||
|
||
1. 浏览器登 `https://git.ymxixi.space/adminym/wxserver/settings/actions/secrets`
|
||
2. 编辑对应密钥(如 `WX_APPSECRET`)
|
||
3. 进 Actions 页面找到最近一次 workflow → 右上角 **Re-run** 重跑
|
||
4. **不要靠 push 触发**——光改 Secrets 不会触发新流水线
|
||
|
||
### 场景 5:从体验版上线到正式版
|
||
|
||
1. 开发者工具点上传 → 选「版本升级」(或对应 semver)→ 上传
|
||
2. mp.weixin.qq.com → 管理 → 版本管理 → 提交审核
|
||
3. 审核通过后点「发布」
|
||
4. **关键**:去 Gitea Secrets 把 `WX_MINIPROGRAM_STATE` 改成 `formal`,re-run workflow
|
||
(不改的话推送跳转的是开发版小程序,体验差)
|
||
|
||
### 场景 6:拉新人加体验
|
||
|
||
1. mp.weixin.qq.com → 管理 → 成员管理 → 体验成员 → 添加(最多 15 人,填对方微信号)
|
||
2. 把体验版二维码图发给对方,让他扫码
|
||
|
||
### 场景 7:备份数据库
|
||
|
||
```
|
||
ssh 群晖
|
||
cp /volume1/docker/apps/birthday-server/data/birthday.db \
|
||
/volume1/backup/birthday-$(date +%F).db
|
||
```
|
||
|
||
或从 Mac 拉到本地:
|
||
```
|
||
scp 群晖:/volume1/docker/apps/birthday-server/data/birthday.db ~/Downloads/
|
||
```
|
||
|
||
### 场景 8:看后端日志
|
||
|
||
```
|
||
ssh 群晖
|
||
docker logs -f birthday-server # 实时
|
||
docker logs --tail 200 birthday-server # 最近 200 行
|
||
```
|
||
|
||
### 场景 9:本地起后端调试
|
||
|
||
```
|
||
cd server
|
||
cp .env.example .env
|
||
# 编辑 .env 填 AppID/AppSecret
|
||
docker compose up -d
|
||
docker logs -f birthday-server
|
||
```
|
||
|
||
开发者工具勾选「不校验合法域名」,BASE_URL 会自动走 localhost:3000。
|
||
|
||
### 场景 10:扩展新接口
|
||
|
||
1. `server/src/index.js` 加新的 `app.post('/api/...', ...)` 路由
|
||
2. 数据库变动改 `server/src/db.js`(注意 SQLite `CREATE TABLE IF NOT EXISTS` 不能增列,需手动 ALTER 或重建)
|
||
3. 前端 `utils/api.js` 加调用方法
|
||
4. push → 自动部署 → 开发者工具上传新版 → 设体验版
|
||
|
||
### 场景 11:DB Schema 升级
|
||
|
||
SQLite 重启不会自动改表结构。如果加字段:
|
||
- 临时操作:进容器 `docker exec -it birthday-server sqlite3 /app/data/birthday.db`,`ALTER TABLE anniversaries ADD COLUMN xxx`
|
||
- 永久方案:在 `db.js` 启动逻辑里加迁移 SQL,参考 PRAGMA user_version 做版本号管理
|
||
|
||
### 场景 12:回滚到旧版本
|
||
|
||
Gitea Actions 每次 push 都会构建新镜像但 tag 都是 `:latest`,旧镜像在群晖 Docker 里残留但没标签。最稳的回滚:
|
||
```
|
||
git revert <提交哈希>
|
||
git push
|
||
```
|
||
让 CI 重新构建旧代码版本,比 `docker run 旧镜像 ID` 干净。
|
||
|
||
## 故障排查表
|
||
|
||
| 症状 | 可能原因 | 排查命令 / 处理 |
|
||
|---|---|---|
|
||
| `https://wxserver.ymxixi.space/api/health` 502 | NPM 反代目标配错 | 进 NPM Web UI 检查:Forward Scheme=http,IP=127.0.0.1,Port=8080 |
|
||
| 502 但 NPM 配置看着对 | nginx 没传 Host header / frps 找不到 vhost | NPM 默认会传,自建 nginx 要 `proxy_set_header Host $host` |
|
||
| TLS 报 `unrecognized name` | nginx 上没有该 server_name | NPM 里新加 Proxy Host 或修改 server_name |
|
||
| frpc 改了配置但不生效 | 改的是 `/var/etc/frpc.ini`(临时) | 通过 LuCI / `/etc/config/frpc` 改 UCI 配置,再 `/etc/init.d/frpc restart` |
|
||
| Actions 卡「等待中」不跑 | Runner 离线 | Container Manager 重启 `gitea-runner` 容器 |
|
||
| 构建镜像超时 / 拉不下来 | docker.io 国际网络慢 | 重试,或群晖 Container Manager 加镜像加速 |
|
||
| 改了 .env 但容器没生效 | `docker compose restart` 不重读 .env | 用 `docker compose up -d --force-recreate` 或重建容器 |
|
||
| 部署成功但 `docker run` 启动 1s 失败 | bind mount 目录不存在 | 通过 `docker run --rm -v /宿主路径:/x <image> mkdir -p /x/...` 在宿主上建 |
|
||
| 微信小程序请求失败「不在合法域名列表」 | 服务器域名没配 / 改后没生效 | mp.weixin.qq.com → 开发管理 → 服务器域名添加并扫码确认 |
|
||
| 体验版能收推送但点击跳错版本 | `WX_MINIPROGRAM_STATE` 与发布状态不匹配 | developer/trial/formal 对应小程序版本,改 Secrets re-run |
|
||
| 用户看到旧数据 | 小程序本地 storage 共享 | wx.clearStorageSync() 或重装小程序 |
|
||
| code2session 报 `invalid appid` (40013) | AppID/AppSecret 不匹配 或 .env 没生效 | 容器内 `printenv WX_APPID` 检查;改 Secrets 需重建容器 |
|
||
| 订阅消息发送返回 errcode≠0 | 用户未授权 / 模板 ID 错 / 配额耗尽 | 查 docker logs;用户每次添加才有一次订阅机会 |
|
||
|
||
## 产品决策记录(避免反复纠结)
|
||
|
||
### 闰月生日的提醒策略(采用 A 方案)
|
||
|
||
农历闰月(如闰二月、闰六月)只在某些年份存在。当用户的农历生日落在闰月(例如「闰二月初一」),遇到当年没有该闰月的年份,按**对应普通月份的同日**提醒(即「二月初一」)。这是民间最常见的过法。
|
||
|
||
**数据字段**:`anniversaries.isLeapMonth`(0/1),保存时由 `solarToLunar(...).isLeap` 决定。
|
||
**算法实现**:`lunar.lunarToSolar(year, month, day, isLeap)` 在 isLeap=true 但当年无对应闰月时,会自动按普通月份返回——所以代码上无需显式 fallback。
|
||
**UI 提示**:用户在添加纪念日页面勾选「农历」+ 选择公历日期后,会实时显示「对应农历:闰X月X日」并标黄警示,说明无闰月年按普通月份提醒。
|
||
|
||
如未来需要改为 B 方案(按下一个月初过)或 C 方案(跳过不提醒),改 `reminder.js` 的 `getThisYearDate` 即可,无需动数据。
|
||
|
||
## 已知"踩过的坑"(避免重蹈)
|
||
|
||
1. **`docker compose restart` 不会重读 `.env`** — 必须 `up -d --force-recreate`。光 restart 会保留旧环境变量。
|
||
2. **Gitea Runner 容器内 `mkdir`** 创建的是 runner 容器内的路径,不是群晖宿主路径。在宿主上建持久化目录要走 `docker run -v 宿主路径:/x <镜像> mkdir -p /x/子目录`,让 docker daemon 用宿主路径处理 bind mount。
|
||
3. **frp `type=https` 是 TLS 透传**,要求本地服务自己监听 HTTPS 并带证书。HTTP 后端用 `type=http`,配合 frps 的 `vhost_http_port`,由 frps 按 `Host` header 路由。
|
||
4. **NPM 反代默认配置容易写成「自己的公网 IP + frps 公开端口」**(比如 47.238.152.174:4430)。正确写法是 `127.0.0.1:8080`,让流量在本机经 frps vhost_http_port 进入隧道。
|
||
5. **frpc 配置 `/var/etc/frpc.ini` 是临时文件**,是从 `/etc/config/frpc`(UCI)启动时生成的。直接改临时文件下次重启就丢,必须改 UCI(LuCI 界面或 `uci set`)。
|
||
6. **微信小程序的「服务器域名」修改有次数限制**(个人小程序约 5 次/月),改前最好确认无误。
|
||
7. **微信小程序的开发版/体验版/正式版共享同一份本地 `wx.setStorageSync`**(同一台手机、同一 AppID)。看到"莫名其妙的旧数据"先怀疑这个。
|
||
8. **`miniprogram_state` 三个值**:`developer` 只能推到开发版,`trial` 推体验版,`formal` 推正式版。配错就收不到。
|
||
9. **Docker build 阶段不会自动用宿主代理**。代理走 docker-compose `build.args` 传 `HTTP_PROXY`/`HTTPS_PROXY`(保留 build-arg,不会写入运行时 ENV)。容器内访问宿主用 `host.docker.internal`。
|
||
10. **Gitea Actions 的 Secrets 改完不会自动触发流水线**——必须手动 re-run,或者推一个新 commit。
|
||
11. **农历 `lunarInfo` 数据表来源要可信**。早期版本数据有错位(多个年份的闰月信息错),导致 solarToLunar 算的春节日期跟实际差 1-2 个月。已替换为权威源 `github.com/jjonline/calendar.js` 的版本。如未来要扩展年份范围(>2100),务必同样从该源取。
|
||
12. **农历存字段必须含 `isLeapMonth`**。只存 `lunarMonth`/`lunarDay` 会丢闰月信息,闰月生日的人每年被错误提醒到普通月份。修复需同时改:数据库列、后端 FIELDS、前端 formData、`lunar.lunarToSolar` 第 4 参数透传。
|
||
13. **`solarToLunar` 月循环必须保留 `temp` 变量**。早期版本在闰月分支里 `offset -= leapDays`、非闰分支 `offset -= monthDays`,循环外的 `if (offset < 0)` 又根据 isLeap 重新判断要加哪个——闰月期间的"非初一日期"会被错算到下一个普通月(例如「闰二月初二」算成「三月初二」)。权威实现(jjonline/calendar.js)让循环内统一 `offset -= temp` 并把 `temp` 保留到循环外用,是更稳的写法。修改算法时一定要用 *闰月连续日期*(如 2023-03-22 到 2023-04-19)做端到端验证,不能只测闰月初一。
|
||
14. **`lunarToSolar` 的 BASE 时间戳必须用 UTC**。早期版用 `new Date(1900, 0, 31)`(本地时间)作为基准,在 1900 年早期日期上某些 V8 版本有时区偏差,让每个农历月初对应公历都少 1 天(系统性误差,不容易发现,只在测「正常日期」时才暴露——闰月那次只测了初一,恰好正确)。改用 `Date.UTC(1900, 0, 31)` 算时间戳,再用 `getUTCFullYear/Month/Date` 提取后重新构造本地 Date 对象。**测算法务必跑「公历→农历→公历」互逆**,发现不互逆就是有 bug。
|
||
|
||
## 与其他记忆的关系
|
||
|
||
- 群晖 CI/CD 通用规范:见 [[reference_synology_cicd_playbook]]
|
||
- 软路由(frpc 所在)基本信息:见 [[project_home_router]]
|
||
- Docker 镜像拉取代理优先级:见 [[feedback_docker_mirror]]
|
||
- 项目当前架构和接口设计:见 [[project_birthday]]
|