e9c7330f31
部署到群晖 / deploy (push) Successful in 57s
后端(server/src/index.js)——两处同步幂等: 1. deleteAnniversary 删除不存在的记录时返回 success:true。 原先返回 false,被前端 sync.js:34 当作失败重新入队,那条删除会永远留在 pending_sync_queue 里每次启动重试且永远清不掉。 2. updateAnniversary / updatePerson 在记录不存在时退化为插入(upsert)。 同一类问题:本地才是主真相源,若某条记录当初的 add 没同步成功, 之后所有 update 都会失败并无限重试。 前端 —— 新增 utils/migrate.js,在 app.js onLaunch 执行: 3. 补齐老数据缺失的农历字段。早期 lunar_birthday 类型只记公历日期, 没有 lunarMonth/lunarDay,这类记录会被安全降级成按公历算——不崩,但每年日期是错的。 用它存的公历日期反算回农历补齐。 4. 为孤儿纪念日重建人员。部分纪念日的 personId 指向已不存在的人(历史上删人没级联干净), 首页按 persons 遍历所以隐形,日历页按 anniversaries 遍历会显示成「未知」。 用记录自带的 personName 重建,并沿用原 personId,纪念日无需改动。 迁移放在客户端而非后端跑 SQL:本地 wx.Storage 是主真相源,改服务端会被客户端同步覆盖。 每次启动都跑而非记版本号:函数是纯检测式的,无坏数据时零写入零请求, 还能顺带覆盖「从云端拉回坏数据」的情况。顺便把原本是死代码的 initData() 替换掉。 ⚠️ 本次后端有改动,需要重新部署;部署顺序应为先后端、后小程序。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3.9 KiB
3.9 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
项目简介
微信小程序「生日提醒」+ 自建 Node.js 后端。小程序管理人员和纪念日,后端负责 openid 换取、数据云端备份、定时推送订阅消息。
常用命令
# 后端本地开发(进 server 目录)
cd server
npm install
npm run dev # node --watch 热重载,监听 3000
# 后端生产启动
npm start
# Docker 部署
docker compose up -d --build
# 手动触发一次提醒任务(调试用)
curl -X POST http://localhost:3000/api/reminder/run
小程序侧无构建命令,用微信开发者工具直接打开项目根目录。
架构总览
两个独立部分
| 部分 | 路径 | 说明 |
|---|---|---|
| 微信小程序 | pages/ utils/ app.js |
原生微信小程序,无框架 |
| 自建后端 | server/ |
Express + better-sqlite3,Node ≥18 |
cloudfunctions/目录是早期云开发遗留,已被server/取代,不再使用。
小程序数据流
本地 wx.Storage(主存储)
↕ 写时 fire-and-forget
自建后端 SQLite(云端备份)
- 本地是主真相源:所有读写先操作 wx.Storage 内存缓存(
utils/storage.js),再异步同步后端。 - 失败入队:
utils/sync.js维护pending_sync_queue,同步失败的操作下次启动时flush()。 - 新设备恢复:
app.js启动时,若本地为空则调用storage.pullFromCloudIfEmpty()从后端拉取全量数据。
后端 API 协议
所有接口用统一的 action 字段区分操作,openid 通过 x-openid 请求头传递:
POST /api/login # wx.login code → openid
POST /api/person # action: add / update / delete / sync / get
POST /api/anniversary # action: add / update / delete / sync / get
POST /api/reminder/run # 手动触发定时任务
前端 URL 自动选择:开发者工具 → http://localhost:3000;体验版/正式版 → https://wxserver.ymxixi.space(见 utils/api.js:resolveBaseUrl)。
后端主要模块
| 文件 | 职责 |
|---|---|
server/src/index.js |
Express 路由 + 所有 CRUD 函数 |
server/src/db.js |
better-sqlite3 初始化、建表、旧库 ALTER 迁移 |
server/src/reminder.js |
cron 定时任务,扫描 remindEnabled=1 的纪念日并发微信订阅消息 |
server/src/wx.js |
微信接口封装(code2session、sendSubscribeMessage) |
server/src/lunar.js |
农历转公历,与前端 utils/lunar.js 算法一致 |
农历处理
utils/lunar.js(小程序)和 server/src/lunar.js(后端)是同一套寿星万年历算法,覆盖 1900–2100 年。纪念日记录同时存储公历字段(solarMonth/solarDay)和农历字段(lunarMonth/lunarDay/isLeapMonth),isLunar 标志决定展示和计算逻辑用哪套。
纪念日数据结构关键字段
{
id, personId, personName,
type, // 'birthday' | 'wedding' | 'engagement' | 'other'
customTypeName, // type='other' 时的自定义名称
isLunar, // true = 农历生日
solarMonth, solarDay, // 公历月日(必填,农历纪念日也存转换后的公历供排序)
lunarMonth, lunarDay, isLeapMonth, // 农历字段(isLunar=true 时有效)
importance, // 'high' | 'medium' | 'low'
remindEnabled, remindDays
}
lunar_birthday 是老数据兼容类型,与 birthday 等效,由 isLunar 决定是否农历,常量和后端均有兼容映射。
后端环境变量
参考 server/.env.example,关键变量:
WX_APPID # 微信小程序 AppID
WX_APPSECRET # 微信小程序 AppSecret
WX_TEMPLATE_ID # 订阅消息模板 ID
REMINDER_CRON # 默认 "0 9 * * *"(每天9点,Asia/Shanghai)
DB_PATH # SQLite 路径,Docker 中挂载到 ./data/birthday.db