Commit Graph

14 Commits

Author SHA1 Message Date
Blizzard 8b1b4cde02 refactor(be): 今日任务并进计划节点,养护方案改成可选、按重复规则配
## 三个问题,一起解决

你问「模版要不要做成可选」,摸下来发现底下有三个更硬的:

**① 根本没有多套模版可选。** 模板表唯一索引是 (species, stage) ——
同一个「猫 × 幼年期」只能有一套。「让用户选模版」在数据模型上做不到。
去掉唯一索引,加 Name / Summary / Sort / Enabled。

**② 今日任务和计划节点是两套,而且是我上一轮改出来的。**
  DailyTask  模板 kind=task 生成,ensureDayTasks 每天把昨天那份复制到今天
  PlanTask   模板 kind=plan 生成,Day 偏移
计划改成月度管理 + 加了「应用到每天」之后,两者做的是同一件事,
但首页读 DailyTask、计划页管 PlanTask —— 用户在计划页加一条「每天喂药」,
首页今日任务里看不到。

DailyTask 退役。task.go 重写成 PlanTask 的一层「按日期取」的门面,
/pets/:id/tasks 路径不变,小程序端不用为这次合并改调用。
PlanTask 加 Priority(首页那份要能标出哪条重要)。

顺手删掉重复的 CreateTask/UpdateTask/DeleteTask —— /plan-tasks 那套已经
覆盖了,留着两套会出现「首页管理弹层加的任务」和「计划页加的节点」进不同的表。

**③ 模板的 Day 偏移和月度计划对不上。** 月底建档的话 day=14/21/30 全落到
下个月,用户建完档打开计划页发现本月是空的。

改成重复规则:once(偏移 N 天,疫苗序列要用)/ daily / weekly(星期几)
/ monthly(几号)。套用 = 展开成目标月份的具体日期。
「每月 31 号」在 2 月按月末算,不然那个月直接没有。

列名用 repeat_rule 不用 repeat —— repeat 是 MySQL 保留字(和 group_key 同理)。

## 展开成实体行,不是「存规则读时展开」
勾选状态是按天的。存规则得再开一张表记「哪天打过勾」,而且用户把
「每周二梳毛」这周挪到周三就要引入例外记录。一次展开成行,增删改和打勾全免费。

代价是套用后模板和计划断开:后台改模板不影响已套用的宠物。这是有意的——
用户已经在自己的计划上勾了几十个勾,后台一改就被覆盖才是灾难。

## 建档不再自动铺
seedPetDefaults 只建必要提醒(疫苗、驱虫)。漏针是真会出事的,而且它们是
「到期提醒」不占计划页的格子。其余交给用户挑方案。

自动铺的问题是:用户还没看懂这个 App 是干什么的,首页已经躺着十几条他没
答应过的任务,第一反应是「这些是什么」而不是「我该做什么」。

**改完发现还有第二条自动铺的路径**:newStagePlan 在建计划壳子时顺手把模板
铺成节点。所以我改完 seedPetDefaults 之后,建档后首页仍然躺着 5 条任务。
惰性生成尤其难查——不是建档时发生的,是第一次读计划才发生的。

## 方案摘要按重复方式分开报数
一开始只报一个总数,「10 条」套用一整月展开成 130 条,用户会以为出 bug。
现在分成 每天 4 · 每周 0 · 每月 0 · 一次性 6 · 提醒 4。

## 数据迁移(预生产库,已执行)
  模板明细 kind=task → plan + repeat=daily     28 条
  模板明细 kind=plan 补 repeat=once            44 条
  8 套方案补名字和摘要
  DailyTask → PlanTask                        34 条,跳过 0
DailyTask 表的行**没删**,先留着当备份。

迁移第一版又踩了「列还没 AutoMigrate 出来就跑」那个坑:Count 静默返回 0,
我以为「没有需要补的」。第二版加了 HasColumn 前置检查才发现 44 条没转。
和上次 record_types.fields 回填是同一个错。

## 验证(预生产库实跑)
  今日任务/计划页今天  4 条 vs 4 条,一致
  建档后             0 任务 / 0 计划节点 / 4 条必要提醒
  套用本月           从今天到月末,4 条(7/30-7/31 两天)
  重复套用           0 条新增(按「日期+标题」去重)
  套用下月           130 条覆盖 31 天,每天 4.2 条
  全部完成           5 → 0 未做

## 还没做(下一步)
前端:建档第 4 步选方案、计划页空月份的套用入口、首页「管理」改跳计划页、
弹层删 manageTasks;后台模板页要支持多套 + 重复规则编辑。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 11:50:54 +08:00
Blizzard aefb1bc103 feat: 建档改成三步向导页,品种改成后台可配的选择器
## pages/petform 三步向导
  ① 名字 + 传头像
  ② 猫还是狗 → 选品种
  ③ 性别 / 生日 / 到家日期 / 体重 / 毛色 / 阶段

**编辑时不分步**,一屏全放开:用户是来改某一项的,不该被按顺序走一遍。
同一个页面两种形态,靠 ?id= 区分。

每一步只校验这一步的东西。三步全填完才校验的话,用户在第三步才被告知
第一步没填名字,还得翻回去。

进度条没用 seg-tabs:那是「可以随意切」的语义,这里有顺序,
点第三步跳过前两步会拿到一份没名字的档案。

## 生日和到家日期分开
原来是一个「生日 / 到家时间」字段。领养的成年猫狗生日常常是不知道的,
而「一起生活了多少天」要按到家日算——合成一个字段两件事都说不准。
验过:到家 4 个月前 → 第 121 天;按生日算会是 801 天。

模型里 Birthday 和 ArrivedAt 本来就是两个字段,只是表单没露出来。

## 品种:自由文本 → 后台可配的选择器
原来品种是个输入框,用户手打「柯基」「柯基犬」「威尔士柯基」算三个品种,
以后想按品种做体重基准、常见病提示这类事就没法做。

新增 breeds 表,预置 34 种猫 + 45 种狗,按首字母分组、右侧 A-Z 索引跳转、
首字母吸顶。留了「不确定」——领养的串串确实说不出品种,逼着选一个只会
得到假数据。换物种会清掉已选品种(原来那个一定不对了)。

**首字母存字段不运行时算**:Go 没有标准拼音库,而多音字自动转常出错——
「藏獒」按 cang 还是 zang 分组、「柴犬」的柴,人来定比库来猜靠谱。
后台新增品种时手填这一位,服务端校验必须是单个 A-Z。

已有档案在用的品种不许删(删了那些档案的品种就变成查不到的字符串),
想隐藏用 enabled=false。同物种下不许重名——品种是统计口径,重名会把
同一种猫劈成两半。

预置数据自己填错了两条,实跑时看出来的:「苏格兰柯利犬」我按「柯利」
填了 K,应该按「苏格兰」算 S;「双血统边牧」根本不是品种,是血统描述。
seed 源和库里都改了。

## 弹层继续瘦
addPet/editPet 两支删掉,连带 10 个方法、ageFromBirthday/GENDERS/STAGES
和 5 个 add* 字段。累计(从记录表单搬走那次算起):
  js   1110 → 734 行
  wxml  485 → 307 行

多宠管理里「点某只去编辑」原来是 setType('editPet'),那支没了会开一个
空白弹层——改成关弹层再跳页。8 个页面的入口全换完,grep 无残留。

## 验证(预生产库实跑)
  品种接口   猫 34 种 16 个首字母、狗 45 种 17 个首字母;非法 species 返回 []
  建档      生日 / 到家日期 分别落库,品种毛色阶段都对
  编辑      只改名字,到家日期没被冲掉
  修正      柯利犬 K→S、双血统边牧已删

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 11:26:51 +08:00
Blizzard 3ea50efb7d feat: 添加记录独立成页,表单字段由后端配置驱动
## 弹层里塞 24 个表单是走不通的
弹层高度受 .sheet-scroll 的 78vh 限制,字段一多就变成内滚;24 项的选择器
挤在弹层里更难看(用户原话「很丑」)。整体搬成页面。

## pages/addrecord —— 一个页面吃掉全部 24 种
形态:记录宠物 / 记录时间 / 类型专属字段 / 描述 + 照片 / 底部固定保存键。

关键在「类型专属字段」整段是后端驱动的:前端不认识「体重」「金额」这些业务词,
只认识 number / options / text 三种渲染方式,以及每个字段的值该落到哪儿。

record_types 加了 fields 列,后台用紧凑写法配(一行一个):

  number:体重:kg                    数值 → num_value
  options:状态:正常|软便|拉稀          单选 → category
  options:症状:呕吐|拉稀|精神差:-      末尾 - 表示不落 category,只进标题
  text:吃了什么                      文本 → 只进标题
  (空)                            只要 时间+描述+照片

解析放服务端不放前端:配错了要在后台保存时就报出来(第几行、错在哪),
不能等用户点开表单才发现渲染不出东西。读取时解析失败只让这一种事项没有额外
字段并打 warn,不让一条烂配置把整个记录页打空。

## 那个 - 开关不是过度设计
异常观察有两个单选(症状 + 严重程度),而 category 只有一个坑,
周报的高风险数按 category='高' 统计(report.go:46)——必须能指定谁占这个坑。
同理账单按 cost 的 category 分组聚合(report.go:134),
食便相关性排除 poop 的 category='正常'。这三处口径都得严丝合缝对上。

## 记录页瘦成纯列表
只留分组宫格。健康洞察 / 体重趋势 / 健康时间轴整页搬到 pages/history,
从报告页「记录与趋势」进——报告页才是看数据的地方,而一个「我要记一笔」的
页面上摆三块只读图表,用户每次都得先滚过去才能找到要点的东西。

**没有直接删掉那三块**:时间轴是唯一能看和删历史记录的地方,报告页只有
周报/账单这些聚合。删了用户就再也看不到自己记过什么。搬页面用的是 git 里
改动前的完整版复制,比往 report.js 里合并代码安全。

## 弹层瘦了一圈
9 个记录分支删掉,连带 17 个方法、SIMPLE_HINT、buildRecord 的 7 个 case
和一批只有它们在用的 data 字段。
  js   1089 → 857 行
  wxml  480 → 335 行
弹层现在只管「不是记一笔」的事:提醒、海报、档案、发帖、评论、导出、反馈。
buildRecord 只剩 vetSummary 一个分支(它写一条 note 留痕,不是用户填的表单)。

## 验证(预生产库实跑)
24 种的字段配置逐个核对解析结果,然后照 addrecord 的 buildBody 组装落库:
  体重  title='体重:4.6kg'        num=4.6            → 宠物档案回写成 4.6kg ✓
  记账  title='记账:128元 医疗'     num=128 cat=医疗    → 账单 total=128 分类[(医疗,128)] ✓
  异常  title='异常:呕吐 高'        cat=高             → 周报 high_risk_count=1 ✓
  排便  cat=软便                                    食便相关性口径保住
  饮食  title='饮食:幼猫粮 45g 偏少' cat=偏少
  喝水  num=180(新类型带数值,老代码没有它的分支也不影响)
  洗澡  title='洗澡'(无额外字段)
  看病  num=320(两个字段:文本+数值)
体重趋势 1 个点、值 4.6,没被其他 7 条污染。

回填踩过一次坑:fields 列是服务启动时 AutoMigrate 才建的,我在启动前就跑了
回填,Update 报错被忽略、13 行静默没写进去。第二版加了 HasColumn 前置检查
和逐行错误统计才发现。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 10:25:42 +08:00
Blizzard 60ee9d4bbc feat(be): 记录类型挪进数据库,后台可配 + 15 种新类型
## 为什么
9 种记录类型全是低频医疗和记账(疫苗一年一次、驱虫一季一次、体检更少),
用户今天打开 App 大概率没什么可记,时间轴是空的。竞品 35 种里有 26 种是
高频日常(洗澡/剪指甲/换猫砂/喝水/洗食盆),那才是每天打开的理由。

## 一个比预想好得多的发现
本来担心加类型会踩到分析逻辑。把那 9 个常量的引用全捞了一遍,结论是不会:

  weight       record.go:83 回写宠物体重;home/insight/report 的体重趋势与基准
  food + poop  insight.go:130 食便相关性
  symptom      insight.go:160 症状聚类;report 高风险计数
  cost         report.go:99 消费统计
  vaccine      report.go:137 疫苗完成度
  deworm/medicine/photo  没有任何特殊分支

这些分支全都是「按某个具体常量过滤」,没有一处是 switch 全枚举、也没有
default 兜底。CreateRecord 更是完全没有 type 白名单校验。所以新增类型
不可能碰到任何现存逻辑——一行分析代码都不用改。

于是分工是:
  record_types 表  负责「展示什么、能记什么」
  那 9 个常量      负责「哪几种参与分析」
两者各管一段,常量一个都没删。

## 表设计
code / label / icon / group_key / sort / enabled / form / locked。

group_key 而不是 group:group 是 MySQL 保留字,GORM 会加反引号所以能跑,
但手写 SQL 排查时很容易踩。

form 区分 full(弹层里有专用 wx:elif 分支)和 simple(时间+备注+照片)。
15 种新类型全是 simple——下一步在弹层加一个通用分支吃掉它们,不然 28 个
分支再加 15 个会把 bottom-sheet 写到 1800 行。

locked 给那 9 种:有代码分支依赖、且被 care_template_items.sheet_type 引用。

## 两条硬拦在 service 层的规则
1. locked 的删不掉
2. 已经有记录的删不掉(删了历史记录会变孤儿)
想让它从记录页消失应该用 enabled=false,历史记录仍能正常显示。

都在 service 层拦,不是靠前端隐藏按钮。改 code 同样受 2 的约束——
code 是落库值,改了历史记录会对不上任何类型。

## bootstrap 的取舍
文章和养护模板当初按你的要求从代码里挪走了,这次 24 种类型仍然放在 seed 里,
因为它不是内容而是结构:表空着记录页一个可记事项都没有,App 直接不能用。
和管理员账号一样,只在表**完全为空**时铺一次,之后一切以数据库为准。
表非空就完全不碰,否则后台删掉的类型会每次重启自己长回来。

ListRecordTypes 在表为空时返回空数组 + 打 warn,不硬编码兜底一份——
那等于把「可配置」又变回代码常量,下次改配置的人会发现改了没用。

## 图标
新增 15 个(iconfont.wxss 40 → 55),全部取自本地那份 tabler-icons-3.46.0
真源,不是凭记忆画路径。已解码抽检确认路径非空。

## 验证(生产库实跑)
  建表 + 24 条 bootstrap 一次到位,分组 daily6/health7/care6/clean5
  locked 9 个、simple 表单 15 个
  规则 1  删 weight → 「有统计和分析逻辑依赖它,不能删」
  规则 2  新增 nose 成功 → 重复 code 报错 → 删除成功
  规则 3  造 1 条 tmptest 记录 → 删类型被拦;删掉记录后再删 → 成功;残留 0
  规则 4  停用「美容」→ 小程序端洗护组只剩 5 项;恢复后回到 6 项

规则 3 一开始没真验到:库里唯一的记录类型是 weight,locked 先拦住了,
「已有记录」那个分支根本没走到。补了个造真记录的测试才算验过。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 09:24:24 +08:00
Blizzard 8606c1dfc8 feat(be): Phase C —— 个人主页装扮 + 养宠数据
## User 加四个装扮字段
bio / bg_file_id / theme / hide_pets。

hide_pets 存的是「隐藏」而不是「展示」,为的是零值即默认:AutoMigrate
给存量行填 0,正好等于「展示宠物墙」,不用给老用户跑一次刷值。对外
API 仍然用 show_pets(UI 上是个「展示我的宠物」开关,正着说更自然),
只在 handler 边界取一次反。

theme 存 key 不存色值。色值属于前端表现层,存进库以后想调色就得改数据;
存 key 还能保证前端只渲染已知的那几种。非法 key 走 40000,老用户的空
theme 在读取时兜底成 warm,前端不会拿空 key 去查表。

## GET /users/:id/profile 扩展
原来只有昵称头像和三个计数,现在带上 bio / bg_url / theme / show_pets
+ 宠物名片墙 + 养宠数据。自己看和别人看走同一个接口,省得两套渲染慢慢长歪。

## 养宠数据
days     养宠天数,按最早那只的建档日算——这里要表达的是「你在这照顾它
         多久了」,不是宠物活了多久
records  累计记录条数
act_days 有记录的天数
streak   连续打卡

streak 有个必须处理的边界:今天还没记录不能算断签,否则用户每天早上打开
都会看到连续天数归零。实现是先看今天有没有记,没有就从昨天起往回数。

## PUT /api/user/decoration
四个字段全用指针,区分「没传」和「传了空串」——清空个性签名和不改个性
签名是两件事。bio 服务端截到 60 字。

联调验证(本地 9090):
  老用户读取     theme 兜底 warm、bio ''、hide_pets false
  改装扮        theme=mint、bio 落库
  theme=rainbow 40000 参数不合法
  bio 100 字     截断到 60
  show_pets     false → pets 字段消失,stats.pet_count 仍为 1
  别人视角       is_self=false、followed=false、装扮和宠物墙都能看到
  streak        1/2/3/5 天前各记一条:act_days=4 streak=3(第 4 天空档正确断开)
                今天再补一条:act_days=5 streak=4

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 17:54:16 +08:00
Blizzard 9fc61e878e feat: 评论支持回复(两级 + @某人)
不做无限层级——手机屏幕撑不住层层缩进,读到第四层就没法看了。
微信、小红书、B站清一色是两级:一级评论 + 其下的回复列表,回复里
用「回复 @某人」表达指向谁。

数据结构:comments 加 parent_id + reply_to_name。回复的回复会被
压平到同一条一级评论下(parent_id 取爷爷的),同时把被回复人的名字
记进 reply_to_name —— 这样既保住两级,又不丢「在跟谁说话」的信息。

几个容易做错的地方:
- 一次查完这一页所有一级评论的回复,不是每条一次查询(N+1)
- 一级评论默认只带 3 条回复,其余点「展开全部 N 条」再拉,避免热门
  评论一次返回几百条
- 删一级评论要连它下面的回复一起删,否则回复变成挂在空处的孤儿;
  帖子的 comment_count 也要按实际删除条数减,不是减 1
- total 统计含回复,和帖子上显示的数字对得上
- replies 为空时返回 [],不是 null

实测四条评论(含一条回复的回复):
  ▸ 甲:一级评论   (2 条回复)
      └ 乙:多久了?
      └ 甲 回复 乙:三天了
  ▸ 乙:另一条一级评论   (0 条回复)
删掉第一条一级评论后 total 4→1,comment_count 同步。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 16:33:50 +08:00
Blizzard 5b2f8e50bc fix: 带 date 查任务时区错位导致重复生成 + 评论弹窗重做 + AI 每日次数上限
## 首页添加的任务不显示(根因比表象严重)

time.Parse("2006-01-02", q) 返回的是 UTC 时间,dayStart 又保留了
t.Location(),于是带 ?date= 查询时:
  查询区间 = 07-29 00:00 UTC ~ 次日 = CST 的 08:00 ~ 次日 08:00
  已有任务的 task_date 是 07-29 00:00 CST,落在区间外

后果不只是「新任务不显示」——ensureDayTasks 因此认为今天没有任务,
每刷一次首页就重新生成一批。用户手动加的那条(00:00 CST)永远不在
那个错位窗口里,所以管理页看得到、首页看不到。

全项目 7 处 time.Parse 日期解析统一换成 ParseInLocation + time.Local
(任务、日计划、生日、到家日期、提醒到期日都受影响)。
实测:加一条后带 date 查得到 4 条,连查 4 次仍是 4 条不再增长。

## 评论弹窗

- 改成居中弹出。评论以输入为主,贴底弹层会被键盘顶掉大半屏
- 单行 input 换成自动撑高的 textarea,最多 500 字,带字数
- 支持配图,最多 3 张(评论表加 images 字段)
- 空评论原来会直接把弹层关掉,看起来像发成功了,改成明确提示
- 发完就地刷新列表,不再关闭弹层——连着回复更顺

## AI 每日次数上限

AI 调用是真金白银,不设上限等于把钱包交给用户。新增按「用户 + 自然日
+ 功能」计的额度,后台「社区运营」页可配问问 AI / 异常评估 / AI 计划
三档,改完立即生效。

两个刻意的设计:
- 扣额度在请求大模型之前,失败也算用掉一次。否则刷接口空转照样烧钱
- 配置读不到时回落到保守默认值,绝不「读不到就不限制」——那正是配置
  出问题时最不该发生的事

超限返回业务码 42900,小程序端明确说明「明天 0 点恢复」,不当成网络
错误让用户反复重试。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 15:50:38 +08:00
Blizzard 7159930d9c feat(admin): 随机昵称 + 建档数统计 + 帖子发布时间 + 16 篇新文章
## 新用户昵称
微信登录拿不到昵称(要用户主动授权),原来一律叫「微信用户」,
社区里一屏全是同名,后台也没法区分。改成随机生成「铲屎官12138」
这种形式:10 个前缀 × 5 位数字,落库前查重,撞了重试。

新增 users.nickname_auto 标记这个昵称是系统生成的。原来判断该不该
用微信授权昵称覆盖,靠的是「昵称是不是等于『微信用户』」这个字符串
比较,改成随机昵称后这招就失效了。有标记位才能安全覆盖,同时保证
用户自己改过的昵称不会被冲掉(改昵称时会把标记置 false)。

## 用户管理
加「宠物档案」列,显示每个用户建了几个档。用一次 GROUP BY 查完
当页所有人,不是每行一次 count——那会是 N+1。

## 帖子审核
加发布时间列。今天的显示「今天 14:48」,昨天的显示「昨天」,
更早的显示日期——审核时关心的是「多久以前发的」而不是完整时间戳。

## 文章
新增 16 篇,连同原有 4 篇共 20 篇,每篇 400-570 字:
猫 5 篇(新猫到家第一周 / 猫砂盆 / 公猫尿闭 / 掉毛毛球 / 老年猫信号)
狗 5 篇(社会化关键期 / 定点排便 / 遛狗时长 / 刷牙 / 中暑防护)
通用 6 篇(体重趋势 / 驱虫周期 / 绝育 / 急诊清单 / 就医准备 / 养宠预算)

沿用和 AI 助手一样的安全护栏:不做诊断、不给药物剂量,凡是可能
延误病情的都写清「什么时候必须去医院」。犬用驱虫药对猫的致死风险
这类真实事故也写进去了。

seedArticles 从「库空才插」改成「按标题补缺失」,以后往内置清单加
文章,老库启动也能拿到。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 12:55:22 +08:00
Blizzard d9d7e128ff feat: 引导页阶段按生日推断 + 8 套养护模板按临床共识重配
## 引导页三个逻辑漏洞

1. **阶段和生日脱节**:用户填了 3 个月的生日,却能手选「成年期」,
   而阶段决定疫苗针次、驱虫周期和每天要做的事,选错整套计划都是偏的。
   现在按生日自动推断(猫狗同:12 月龄成年、7 岁进老年),仍可手改,
   但改成矛盾值时会弹框说明代价;服务端 NormalizeStage 兜底。

2. **`goals` 是假选择**:6 个勾选框收上来只存进 pets.goals,一处没用过。
   现在真正生效:体重提醒、月度报告按勾选生成,勾了「想控制养宠开销」
   会多一条记账任务。疫苗和驱虫不给关——狂犬是法定要求,漏驱虫真会出事。

3. **「刚到家 0-30 天」和年龄阶段互斥**:挤在一个四选一里,刚接回家的
   成年猫只能二选一。现在拆开:stage 只存年龄阶段,新增 pets.arrived_at,
   到家不满 30 天时把安置期那套任务和计划叠加上去(同标题去重)。

顺带:生日/到家日期不能选未来(前后端各拦一道)、体重范围校验。

## 疫苗提醒改为按月龄算

原来不管多大都排「14 天后首针」,一只 8 月龄才接回家的猫会被安排去打首免。
现在 dueDaysFor 按真实月龄推:<2 月龄等到 8 周龄、2-5 月龄间隔 21 天、
5-12 月龄两周内补齐、成年走年度加强。标题也跟着改写,否则用户照着
「年度疫苗加强」去打错针。

年度疫苗不再编一个假的到期日(原来写 30 天后),改用频率描述。

## 8 套模板按临床共识重配

依据 WSAVA 疫苗指南、ESCCAP/CAPC 驱虫建议、AAHA 生命阶段指南整理:
猫三联 3 针间隔 3-4 周末针不早于 16 周龄、犬联苗 2-3 针 + 3 月龄狂犬、
体内驱虫 6 月龄前每月一次之后每 3 个月、幼崽 3-6 月龄每天 3-4 餐、
成年犬每天 2 次共 30-60 分钟遛狗、老年建议半年一次体检。
每套从 11 条扩到 10-14 条,描述写成能直接照做的具体动作。

后台新增「恢复默认」:启动 seed 改为只补缺失、不覆盖已有(管理员可能
改过),内置模板更新后必须从后台显式推一次。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 11:58:00 +08:00
Blizzard 9adafb1354 feat(auth): access token 缩到 2 小时 + refresh token 机制
后台登录框原来预填了 sundynix/sundynix,等于把凭证写在页面上,去掉。

令牌改为 access(2h) + refresh 两段式:
- 新表 sundynix_refresh_tokens,只存 sha256,明文只在签发时返回一次
- 有效期:小程序 30 天、后台 7 天
- 新接口 /api/auth/refresh|logout、/api/admin/refresh|logout

安全约定:
- 每次续期都轮换刷新令牌,旧的立即作废
- 作废后 60 秒内再到达算并发重试放行,超过则判定泄露、吊销该账号全部会话
- 主动退出与被连坐吊销的令牌不吃宽限期,否则「吊销全部」形同虚设
- 禁用用户时一并吊销刷新令牌,最多 2 小时彻底失去访问

两端请求层都做了单飞续期:并发请求同时 401 只发一次 refresh,
否则刷新令牌会被并发轮换掉互相打架。小程序续期失败回退 wx.login,
登录/续期请求标 noAuthRetry,避免登录失败(同样返回 40100)触发自我套娃。

顺带修掉 vite 代理仍指向 8080 的遗留(端口早已改 9090)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 09:38:19 +08:00
Blizzard 2e3345cdfc feat: 记录删除与筛选、用户侧内容删除、AI 聊天历史
修复雪花 ID 精度丢失(真 bug)
- bottom-sheet 的 postId 声明为 Number,而 ID 是 18 位雪花字符串,
  超出 JS 安全整数范围:339346584095428608 会被转成 ...600,
  评论弹层实际查的是不存在的帖子。改为 String

记录
- 后端 DELETE /records/:id 早已就绪但前端从未调用,记错了删不掉。
  时间轴支持长按删除
- 时间轴加类型筛选(体重/便便/饮食/异常/用药/疫苗/消费/照片),
  后端 ?type= 本就支持

用户侧内容删除(UGC 合规:用户应能删除自己发布的内容)
- 新增 DELETE /posts/:id 与 DELETE /comments/:id,仅限本人,
  越权返回 40300;帖子软删除,评论删除同步递减 comment_count
- 评论列表返回 is_self;社区长按自己的帖子、长按自己的评论可删

AI 聊天历史
- ai_messages 一直在写但没有查询接口,每次打开弹层都是空白。
  新增 GET /api/ai/messages,弹层打开时接在开场白后加载

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 17:42:14 +08:00
Blizzard 3fde90e97b feat: 补齐五块缺失功能(记录丢数据/文章正文/提醒管理/我的页入口/分享/关注)
P0 修数据问题
- 记录弹层有 5 个输入框从未绑定,用户填的便便备注、饮食内容、药品名称、
  剂量、消费备注全部丢弃;用药记录因此只存下「用药记录」四个字。已全部绑定并入库
- 异常观察保存时写死「风险中」,AI 实际判定的等级被丢掉。改为使用 riskData
  的真实 risk_level,并把成因与建议存进备注

文章正文
- 早期 seed 的 4 篇文章 Content 全空,点开只是打开关联记录弹层,内容运营等于白做
- 补写 4 篇真实正文(疫苗驱虫/软便判断/换粮方案/养宠花销),遵循不诊断、
  异常必提就医的护栏;对已存在的空正文做幂等回填
- 新增文章详情页,学习页与首页卡片改为打开正文

提醒管理
- 后端 CRUD 早已具备但小程序只读,用户改不了疫苗驱虫日期。提醒弹层支持增删改
- 修复后端 UpdateReminder 漏掉 type 字段导致改类型不生效

「我的」页 7 个死入口接活
- 提醒设置、健康记录、多宠物管理、会员权益分别落到对应功能
- 数据导出:汇总档案与记录为文本,可复制发给医生
- 意见反馈:新增 feedback 表与提交接口,后台加「意见反馈」管理页
- 关于我们:应用说明与免责声明

分享
- 各页面补 onShareAppMessage/onShareTimeline
- 帖子分享按钮改用微信原生 open-type="share",删除原来点了没反应的空壳弹层

社区关注
- 新增 follows 表与关注/取关接口,「关注」tab 此前等同「推荐」,
  现只显示已关注用户的帖子;帖子头部可直接关注,标记 followed / is_self

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 18:11:08 +08:00
Blizzard 2f5dd6eefc feat: 养护模板/社区运营/体重趋势/计划日历 + 雪花ID重构 + 文件表与图片上传
后端
- 主键改雪花字符串 ID(pkg/idgen + Base.BeforeCreate),全表外键/JWT/中间件随之调整
- 新增 files 表:/api/upload 按 MD5 去重,返回 {id,url,md5},服务端只收图片
- 头像/记录附图/帖子图改 file_id 关联,读取解析为 URL
- 养护模板(物种×阶段)后台可配 + AI 生成草稿;建档按模板生成任务/计划/提醒
- 社区 AI 运营:虚拟账号池 + 每日定时/手动生成,帖子带 AI 标
- 计划路线图节点带真实日期;首页周历与计划日历同源;新增 day-plan 当日安排
- 体重趋势接口带备注;记录列表分页

小程序
- 公共图片上传 utils/upload.js(仅图片);记录拍照/我的头像/社区发图三处接入
- 首页日历点选查当日任务;记录页体重趋势可点看备注 + 时间轴分页折叠
- 计划页日历点选查当日计划;自定义 tabBar 高度调整

后台(React)
- 新增「养护模板」「社区运营」页;用户管理加机器人筛选

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 08:48:15 +08:00
Blizzard 609f7d06cf init: 毛孩子计划 小程序 + Go 后端 + 内嵌后台
- pets-fe: 微信原生小程序(首页/计划/记录/报告/社区/引导),
  服务端驱动、无假数据;弹层改用 scroll-view,打开时隐藏自定义 tabBar
- pets-be: Gin + GORM(MySQL, sundynix_ 前缀) + MinIO,统一响应/分页,
  微信 code2session 登录,provider-neutral AI(DeepSeek),go:embed React 后台
- 修复:分段选择类型不匹配(字符串 vs 数字)导致选不中

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 15:36:55 +08:00