feat(ECR-012–016): 合规、题库、时辰刷新、头像、MBTI OEJTS 与埋点

落地输入合规、探索题库、报告日/时辰刷新、账号头像、OEJTS 量表,并补齐 H5 埋点与 Admin 漏斗;同步 ESS 工件、切至自建 Git、清理 GitHub Actions。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-13 01:27:58 +08:00
co-authored by Cursor
parent 13860bf1ef
commit 89756f65b4
227 changed files with 7405 additions and 1407 deletions
+1
View File
@@ -25,6 +25,7 @@
| [ops-content.md](ops-content.md) | 运营内容(宫格+测评上下架) | §7 | `admin-h5` `/content` · `GET /home/tools` | Ops-C · ECR-008 Implemented |
| [ops-commerce.md](ops-commerce.md) | 运营商业加深(订单筛选+展示价+退款只读) | §7 | `admin-h5` `/orders` `/pricing` | Ops-D · ECR-009 |
| [ops-system.md](ops-system.md) | 运营系统(RBAC+封禁+推送占位) | §7 | `admin-h5` `/push` `/admins` | Ops-E · ECR-010 |
| [input-compliance.md](input-compliance.md) | 用户文字输入合规 | 横切 · 安全 | 各写接口 · code 40060 | P2 · ECR-012 |
新功能:复制 `_TEMPLATE.md` → 填满 → 在本表登记 → 再编码。
+26 -4
View File
@@ -84,14 +84,16 @@ Device 绑定到账号 User
| R5 | 登出作废 session;设备可再登录其他账号 |
| R6 | 正式环境恢复校验前须改回本 Spec |
| R7 | 新账号未填昵称时,服务端用「意象词+场景词」词库(100×100)随机生成,如心语岛、微光谷 |
| R8 | 账号昵称可在个人档案页修改(PATCH `/auth/me`);首页展示昵称而非「自己」 |
| R8 | 账号昵称可在个人档案页修改(PATCH `/auth/me`);首页与各页「自己」展示位统一用昵称(合盘/问答/星座/档案卡) |
| R9 | 账号头像可在「我的」页更换:相册或拍照上传;服务端持久化;GET `/auth/me` 返回 `avatar_url`;未设置时 UI 用愈心小人正面默认头像(`public/default-avatar.png`,源图 `public/logo/愈心小人1.png` |
---
## 8. 数据模型影响
- `users.phone` UNIQUE · `users.password_hash` · `users.nickname`
- `users.phone` UNIQUE · `users.password_hash` · `users.nickname` · `users.avatar_url`
- `user_sessions(token, user_id, expires_at)`
- 本地文件:`data/avatars/{user_id}.{jpg|png|webp}`(进程相对路径)
---
@@ -102,8 +104,10 @@ Device 绑定到账号 User
| POST | `/api/v1/auth/register` | 注册(可带 nickname |
| POST | `/api/v1/auth/login` | 登录 |
| POST | `/api/v1/auth/logout` | 登出 |
| GET | `/api/v1/auth/me` | 当前账号 |
| GET | `/api/v1/auth/me` | 当前账号(含 avatar_url |
| PATCH | `/api/v1/auth/me` | 更新昵称 `{ nickname }` |
| POST | `/api/v1/auth/me/avatar` | multipart `file`jpeg/png/webp;≤2MB → 更新 avatar_url |
| GET | `/api/v1/media/avatars/:file` | 公开读头像文件 |
---
@@ -113,12 +117,30 @@ Device 绑定到账号 User
|---|---|---|
| register/login | ✓ | ✓ |
| profiles / reports 写读 | ✗ | ✓ |
| 上传头像 | ✗ | ✓ |
| 读头像媒体 | ✓ | ✓ |
---
## 11. 埋点
`auth_register` · `auth_login` · `auth_logout`
经统一 `track()`(见 [analytics.md](analytics.md) · [ops-analytics.md](ops-analytics.md))。
**非** P1 核心漏斗 5 事件;接入后走自有 `POST /analytics/events`(及可选 GA)。
禁止参数含手机号、明文密码、头像绝对 URL / `user_id`
| Event | 触发 | 可选 params |
|---|---|---|
| `auth_register` | 账号新建成功(含 OpenLogin 自动建号,`is_new=true` | `source`: login_page |
| `auth_login` | 已有账号登录成功(`is_new=false` | `source`: login_page |
| `auth_logout` | 用户在「我的」点退出且本地 session 已清 | `surface`: mine |
| `avatar_sheet_opened` | 「我的」打开更换头像 sheet | `surface`: mine |
| `avatar_upload_succeeded` | 头像上传 API 成功且 UI 刷新为新图 | `source`: album \| camera |
| `avatar_upload_failed` | 头像上传失败(校验/网络/服务端) | `source`: album \| camera · `reason`: too_large \| bad_type \| network \| server \| unknown |
| `nickname_updated` | PATCH 昵称成功 | `surface`: profile |
`page_view``/login` · `/mine` 由路由自动采集,本 Spec 不重复定义。
**实现状态:** 事件名以本表为准;H5 已挂 Login / Mine / Profile`track` → 自有 ingest + 可选 GA)。
---
+2
View File
@@ -65,6 +65,8 @@
```text
/ask
├── 愈心 AI / 顾问 Tab
├── 顾问列表 → 顾问专属对话(?advisor=key · 顶栏返回回列表)
├── Empty(无档案)
├── Loading(发送中)
├── Normal(历史 + 输入)
+9 -4
View File
@@ -111,10 +111,15 @@ Tab 陪伴 → /companion
## 11. 埋点
| Event | 触发 |
|---|---|
| `companion_viewed` | 打开 |
| `mood_saved` | P2 |
| Event | 触发 | 可选 params |
|---|---|---|
| `companion_viewed` | 打开 `/companion` | — |
| `mood_saved` | 心情保存成功 | `score`(整数分,非正文) |
| `growth_plan_viewed` | 打开 `/growth-plan` 且列表加载结束 | — |
| `growth_plan_created` | 创建计划成功 | — |
| `growth_plan_checkin` | 今日打卡成功 | — |
禁止参数含计划 title/focus/note 明文。
---
+48 -17
View File
@@ -45,9 +45,9 @@
进入 /scales/:slug
加载题目
逐题作答(须全部完成)
已有结果? → 直接展示最近结果 +「重新测试」
(无结果 / 点重测)
加载题目 · 逐题作答(须全部完成)
无 Self → 引导建档
@@ -60,10 +60,13 @@ POST result → 展示丰富结果
| 子能力 | P1 |
|---|---|
| 测试入口(探索 Tab | ✓ |
| 测试列表 | ✓ |
| 测试入口(探索 Tab · 首页宫格) | ✓ · 产品向 `mbti-lite`;探索主路径为 curated 题库(A 集 |
| 测试列表 | ✓ · 探索「题库精选」宫格(每类 1~2)+「题库分类」;分类页 `/explore/bank/:key` |
| 题库范围(A) | ✓ 人格 / 情绪能力 / 智能 / 生活与关系;**不含**抑郁焦虑强迫躁狂双相等临床筛查 |
| 问题流程 | ✓ |
| 答题保存(服务端结果落库) | ✓ |
| 最近结果回看 | ✓ GET `/scales/{slug}/result`;有结果进页不重复作答 |
| 重新测试 | ✓ 结果页按钮 → 清空草稿再答;新结果落库并覆盖展示 |
| 中断恢复 | ✓ localStorage 草稿(`scaleDraft`);提交后清除 |
| 结果生成(丰富结构) | ✓ |
| 免费层 | ✓ 全量 result |
@@ -79,8 +82,10 @@ POST result → 展示丰富结果
| 路由 | 页面 |
|---|---|
| `/explore` | ExplorePage |
| `/scales/:slug` | ScalePage |
| `/explore` | ExplorePage(题库精选宫格 + 题库分类 + 其它工具) |
| `/explore/bank/:category` | ExploreBankCategoryPage · curated 题库分类列表 |
| `/explore/:category` | ExploreCategoryPage · 产品工具分类(解码/星座等) |
| `/scales/:slug` | ScalePage(含 bank slug |
```text
/scales/:slug
@@ -116,6 +121,13 @@ POST result → 展示丰富结果
| R5 | result 含 label/summary/overview/dimensions/tips/scripts/growth_plan/faq |
| R6 | 结果「不是固定标签」需在文案中提示 |
| R7 | 作答过程写入本地草稿(按 slug);提交成功后清除;刷新可恢复未提交答案 |
| R8 | 同一用户同一 slug(含 curated 题库 slug)若已有 `scale_results`,再次进入直接展示**最近一次**结果,并提供「重新测试」;未点重测不得自动进入答题 |
| R9 | `mbti-lite` 产品展示名统一为「MBTI测试」(首页宫格 / 探索入口 / 量表 title) |
| R10 | **标准版 `mbti-lite`**:开源 OEJTS 1.2 · **32 题**(四维各 8)· 双极 1–5 Likert;按维累加,阈值判出 E/I·S/N·T/F·J/P 一侧,合成四字母类型;**免费** |
| R11 | **完整版 `mbti-full`**:60 题(四维各 15)· 须**成长会员**;未开通时 GET 返回 `locked=true` 且无题目;提交返回 403 |
| R12 | 禁止使用官方 MBTI® 版权题干;结果须提示「不是固定标签」;题库重大更新须 soft-delete 旧 `scale_results` |
| R13 | curated 题库(`scalebank`)仅含 A 集非临床探索量表;文案须声明「非诊断」;临床类(抑郁/焦虑/强迫等)不得进入 C 端 |
| R14 | curated 题库作答走同一 `/scales/:slug`;完成后再次点击入口须遵守 R8(只看结果或显式重测),不得每次从头作答 |
---
@@ -134,6 +146,7 @@ POST result → 展示丰富结果
|---|---|---|
| GET | `/api/v1/scales` | 列表 |
| GET | `/api/v1/scales/{slug}` | 题目 |
| GET | `/api/v1/scales/{slug}/result` | 当前用户该量表最近一次结果;无则 404 |
| POST | `/api/v1/scales/{slug}/result` | 提交计分 |
---
@@ -144,18 +157,27 @@ POST result → 展示丰富结果
|---|---|---|
| 看列表/题目 | ✓ | ✓ |
| 提交结果 | ✗ | ✓ |
| `mbti-full` 作答 | ✗(须成长会员) | ✓(会员 active) |
---
## 11. 埋点
| Event | 触发 |
|---|---|
| `scale_list_viewed` | 探索列表 |
| `scale_started` | 进入作答 |
| `scale_completed` | 出结果 |
| `scale_share_clicked` | 分享 |
| `scale_cta_relation` / `scale_cta_ask` | 导流 |
经统一 `track()`;非 P1 核心 5 事件。禁止参数含题干全文、答案明文、档案 ID。
| Event | 触发 | 可选 params |
|---|---|---|
| `scale_list_viewed` | 打开 `/explore` 且题库 catalog 加载成功 | — |
| `scale_bank_category_viewed` | 打开 `/explore/bank/:category` 且分类加载成功 | `category`key |
| `scale_started` | 用户点开始进入作答(含「重新测试」后再开始) | `slug` · `access`: free\|member |
| `scale_completed` | 提交计分成功并展示结果 | `slug` · `access`: free\|member |
| `scale_result_reopened` | 再次进入已有结果直接展示(R8,未重测) | `slug` |
| `scale_retake_clicked` | 点「重新测试」 | `slug` |
| `scale_locked_viewed` | 会员锁页展示(如 mbti-full | `slug` |
| `scale_share_clicked` | 结果页打开分享 | `slug` |
| `scale_cta_relation` / `scale_cta_ask` | 结果导流(若 UI 有) | `slug` |
`page_view``/explore` · `/explore/bank/*` · `/scales/*` 由路由自动采集。
---
@@ -165,6 +187,14 @@ POST result → 展示丰富结果
**When** 答完提交
**Then** 返回 label 与 overview/tips 等丰富字段
**Given** 已提交过结果
**When** 再次进入 `/scales/{slug}`
**Then** 直接展示最近结果与「重新测试」;不进入答题
**Given** 结果页点「重新测试」并完成提交
**When** 再次进入
**Then** 展示新结果
**Given** 未答完
**When** 提交
**Then** 前端拦截;不成功落库
@@ -186,7 +216,8 @@ POST result → 展示丰富结果
| 项 | 内容 |
|---|---|
| Packages | `internal/scale` · `service/scale` · `ExplorePage` · `ScalePage` |
| Seeds | communication-style · emotion-pattern |
| Seeds | communication-style · emotion-pattern · **mbti-liteOEJTS 32** · **mbti-full60·会员)** |
| Gaps | 结果付费墙若要做须先改 Spec;全量 §11 埋点未挂(核心漏斗见 analytics) |
| Tests | score/result L1`scaleDraft.spec`Scale 流程组件测 |
| Process Review | 草稿恢复已实现 · P1 收口 |
| Tests | score/result L1`ScoreJungian``scaleDraft.spec`Scale 流程组件测 |
| Process Review | OEJTS 标准/完整双轨 · 最近结果回看 · P1 收口 |
| Credit | OEJTS / Open Psychometrics · openjung MIT |
+12 -6
View File
@@ -62,7 +62,7 @@
```text
/
├── Hero / 品牌
├── 全局顶栏:透明愈心谷 Logo(AppHeader,全站)
├── 「自己」卡片标题展示账号昵称(默认意象词+场景词;档案页可改)
├── 穿衣指数 · 颜色搭配 · 养生推荐
├── 功能入口(4 列固定网格 · 多行换行 · 禁止横向滑动)
@@ -91,14 +91,16 @@ Empty:首页本身是入口,不因无档案空白死页。
| R2 | 不展示内部运维信息(API 连接状态等) |
| R3 | 入口文案符合 lexicon(个人画像/关系理解/成长会员…) |
| R4 | Tab 五名冻结:首页·探索·问答·陪伴·我的 |
| R5 | 「自己」卡片:穿衣指数 / 颜色搭配 / 养生推荐;有 Self 档案生日时按生日+本地时刻易经卦象种子调大模型生成;无档案或 LLM 不可用则本地回退 |
| R5 | 「自己」卡片:穿衣指数 / 颜色搭配 / 养生推荐;有 Self 生日时按生日+**本地日历**+**十二时辰**易经种子生成;**每一时辰准点切换**;无档案或 LLM 不可用则本地回退 |
| R6 | 文案禁止占卜/算命恐吓与医疗疗效承诺;卦象仅作节律意象 |
| R7 | 贴士按 `user × 时辰起点` 持久缓存;GET 优先读缓存**即时返回**;缓存未命中时先回退/旧值,后台异步生成写入,禁止每次打开同步等 LLM |
| R8 | 响应含 `shichen` / `shichen_name` / `valid_until`(下一时辰起点);前端在 `valid_until` 自动静默刷新 |
---
## 8. 数据模型影响
经首页建档时写 `profiles`(见 profile Spec)。每日贴士可缓存(同用户约 45 分钟 / 按时段刷新)
经首页建档时写 `profiles``home_daily_tips(user_id, shichen_start, tips jsonb)` 缓存时辰贴士
---
@@ -107,7 +109,7 @@ Empty:首页本身是入口,不因无档案空白死页。
| Method | Path | 说明 |
|---|---|---|
| GET | `/api/v1/home/tools` | 宫格(已有) |
| GET | `/api/v1/home/daily-tips` | 自己卡片贴士(DeviceAuth;有 birthday 则 LLM |
| GET | `/api/v1/home/daily-tips` | 自己卡片贴士(读缓存;含 valid_until |
| — | 复用 | `POST /profiles``POST /reports/portrait` |
---
@@ -142,9 +144,13 @@ Empty:首页本身是入口,不因无档案空白死页。
**When** 提交主 CTA
**Then** 进入画像基础结果路径
**Given** 已登录且有 Self 生日、已配 deepseek
**Given** 已登录且有 Self 生日、该时辰已有缓存
**When** GET `/home/daily-tips`
**Then** `source=llm`含 clothing_index / palette / wellness
**Then** 立即返回缓存(含 clothing/palette/wellness / valid_until),不阻塞 LLM
**Given** 时辰切换到点
**When** 前端到达 `valid_until`
**Then** 静默刷新;穿衣/颜色/养生随新时辰更新
**Given** 无档案或无 LLM
**When** GET `/home/daily-tips`
@@ -0,0 +1,123 @@
# Feature Spec: 用户文字输入合规校验
> Status: `Active` · Map: `横切 · 安全/合规` · Phase: `P2`
> 关联:[lexicon.md](../lexicon.md) · [.ai/security.md](../../security.md) · account-auth / profile / ask / companion
---
## 1. 功能定义
| 字段 | 内容 |
|---|---|
| Name | 文字输入合规(Text Compliance |
| Purpose | 所有用户提交的**自由文字**在服务端统一做合法性/合规校验;前端可做即时提示,**以后端拒绝为准** |
| Business Goal | 降低 XSS/注入、恐吓与医疗疗效话术、垃圾刷屏进入 DB 与模型上下文的风险 |
| In | Out |
|---|---|
| 昵称 · 档案显示名 · 问答 content · 心情/打卡 note · 成长计划 title/focus · 邀请 display_name · 意象场景自填 | 纯数字字段(生日年月日、分数、经纬度) |
| 长度 · 格式清洗 · XSS/控制字符 · lexicon 硬禁止 · 简易刷屏特征 | 完整内容审核平台 / 人工审 / 语音 |
| 错误码统一 | 改开放登录策略本身 |
---
## 2. 用户价值
1. 避免有害/违规文案入库与进模型。
2. 提交失败时得到可读中文原因。
3. N/A(安全横切,非付费面)。
---
## 3. 用户角色
Visitor / User / Admin 凡提交自由文字均适用(Admin 运营文案可后续加强;本 Spec **先 C 端用户 API**)。
---
## 4. 用户流程
```text
用户输入文字 →(可选)前端即时校验
→ API Handler/Service 调用 textsafe.Check(kind, text)
→ OK:规范化后入库/进模型
→ 拒绝:HTTP 400 · code 40060 · message 可读原因
```
---
## 56. 页面 / 状态
各既有表单;Error 态展示 `message`。无新页面。
---
## 7. Business Rules
| ID | Rule |
|---|---|
| R1 | **后端必须校验**所有用户自由文字;前端校验不得替代后端 |
| R2 | 种类与上限(rune):`nickname` 116`display_name` 124`ask_content` 12000`note` 0200`title` 140`focus` 040`scene` 180 |
| R3 | 拒绝控制字符(允许消息内 `\n` `\t`);拒绝 HTML/脚本片段(如 `<script``javascript:` |
| R4 | 命中 lexicon 硬禁止子串(占卜/算命/改命恐吓/疗效承诺/「测测」品牌自称等)→ 拒绝 |
| R5 | 刷屏特征:连续相同字 ≥8,或可见字符全相同且长度 ≥6 → 拒绝 |
| R6 | 密码字段不做 lexicon,仅拒空字节;手机号走既有数字规则 |
| R7 | 校验通过后写库的是 **Normalize** 后的文本(Trim;压缩首尾空白) |
| R8 | 错误码:`40060`;文案过 lexicon(勿用恐吓语气) |
---
## 8. 数据模型
无新表。进程内可选短时计数(本版刷屏以内容特征为主,不做跨请求配额表)。
---
## 9. API
既有写接口;拒绝时统一:
```json
{ "code": 40060, "message": "文案不合规:…", "data": null }
```
---
## 10. 权限
同各业务接口鉴权;校验在鉴权之后、写库之前。
---
## 11. 埋点
可选:`text_rejected{kind}`(本版可不接)。
---
## 12. 验收
| Given | When | Then |
|---|---|---|
| nickname 含「算命」 | PATCH /auth/me | 40060 |
| ask content 含 `<script` | POST messages | 40060 |
| display_name 正常「小愈」 | POST profile | 200 |
| note=`啊啊啊啊啊啊啊啊` | POST mood | 40060 |
---
## 13. AI 检查
- [x] lexicon
- [x] 后端权威
- [x] 不扩开放登录
---
## 14. Implementation
| 项 | 内容 |
|---|---|
| Package | `apps/api/internal/textsafe` |
| Mirror | `@yuxingu/utils` `validateUserText`(即时提示) |
| Wire | auth · profile · ask · companion · growth · synastry accept |
+13
View File
@@ -98,6 +98,10 @@ CTA:节气陪伴 / 问答「生活节奏」/ 分享
| R5 | 推荐用语:体质倾向、生活建议、节律、平衡 |
| R6 | 可展示五行隐喻,须标注探索/非医疗 |
| R7 | 与星象性格、个人画像内容可互相链,不互相覆盖主路径 |
| R8 | **体质/五行倾向**随生日确定;**今日/本周生活建议**按**自然日(CST)**刷新(本周焦点随星期变) |
| R9 | GET `/reports/latest` 与 GET `/reports/{id}`type=rhythm):`as_of` ≠ 当日则**原 id 原地更新**时效段并落库;同日即时返回 |
| R10 | `summary``as_of` · `valid_until`(次日 00:00 CST);前端到点静默刷新;**不按十二时辰** |
| R11 | `summary.wuxing` 必含五行分布 `bars`(与愈心解码同源算法)及主/辅倾向;缺字段时 GET 原地补全 |
---
@@ -108,6 +112,7 @@ CTA:节气陪伴 / 问答「生活节奏」/ 分享
| `growth_reports.type` | `rhythm` / `life_rhythm` |
| 或 Constitution 专用表 | 若需独立聚合,实现时 ADR |
| 禁止 | `luck_score`、病名诊断字段 |
| 报告 JSON | `as_of` · `valid_until` · `today_tip` · `week_focus` |
---
@@ -155,6 +160,14 @@ CTA:节气陪伴 / 问答「生活节奏」/ 分享
**When** 扫 summary+detail
**Then** 符合 lexicon
**Given** 已有 rhythm 报告且 `as_of` 为昨日
**When** GET latest
**Then** `today_tip` / `week_focus` 按今日更新;报告 `id` 不变
**Given** 用户停留在 `/rhythm` 跨自然日
**When** 到达 `valid_until`
**Then** 前端静默刷新建议文案
---
## 13. AI 开发前检查
+1 -1
View File
@@ -118,7 +118,7 @@ analytics_daily_page(可选汇总)
- 卡片:DAU、新增、会话数、人均会话时长
- 图:日活趋势;页面均停留 TOP
- 表:页面明细、退出页 TOP、点击 TOP
- 漏斗:画像完成 → 深度 CTA → 支付成功
- 漏斗:探索列表 → 开始作答 → 量表完成 → 画像完成 → 深度 CTA → 支付成功
视觉:愈心谷色系;品牌仅 logo,无「运营后台」字样。
@@ -16,7 +16,7 @@
| In | Out |
|---|---|
| Self / Other 档案创建与生日变更触发 | 无档案凭空出合盘 |
| 单人:portrait · star · rhythm | 意象卡片抽牌(配额玩法,不预生成) |
| 单人:portrait · star · rhythmstar/rhythm 运势与今日建议按自然日在 GET 时原地刷新,见各 Spec) | 意象卡片抽牌(配额玩法,不预生成) |
| 有 Self+Otherrelation + synastry 成对覆盖 | 附近的人合盘(进页再算) |
---
+13 -2
View File
@@ -107,6 +107,9 @@
| R9 | 附近的人默认 `geo_visible=false`;仅可见已开启位置的 self 档案 |
| R10 | 合盘星历走 Swiss Ephemeris(默认 Moshier);宫位制 Whole Sign |
| R11 | 合盘交叉校验:档案 A 为本账号 Self;档案 B 须为本账号 `other` 或附近可见他人 Self;拒绝 self×self / 同 id |
| R12 | **本命层**(轮盘/行星/相位/太阳月亮上升)随生日确定,不因打开时间重算;**运势层**(日/周/月/年 outlook · 行运)按**自然日(CST**刷新 |
| R13 | GET `/reports/latest` 与 GET `/reports/{id}`type=star):若 `summary.as_of` ≠ 当日则**原报告 id 原地更新**时效段并落库;同日再读即时返回;禁止为换日运新建报告 id(以免深度权益丢失) |
| R14 | `summary``as_of`YYYY-MM-DD)与 `valid_until`(次日 00:00 CST RFC3339);前端到点静默刷新;**不按十二时辰**切换 |
---
@@ -119,7 +122,7 @@
| `profiles.birth_place` | 可选;常用城市表解析经纬度 |
| `profiles.geo_lat/lng/visible` | 附近的人 |
| `synastry_invites` | 邀请 token · host/guest · report_id |
| 报告 JSON | `charts.*`(五主盘+推运)· `as_of` · 三指数 · fortune/transits |
| 报告 JSON | `charts.*`(五主盘+推运)· `as_of` · `valid_until` · 三指数 · fortune/transits |
详见 erd;实现切片同步 OpenAPI。
@@ -153,7 +156,7 @@
| Event | 触发 |
|---|---|
| `star_completed` / portrait_completed source=star | 基础结果展示 |
| `star_completed` | 基础结果展示 |
| `star_wheel_viewed` | 点选轮盘行星 |
| `synastry_completed` | 合盘结果展示 |
| `synastry_invite_created` / `synastry_invite_accepted` | 邀请链路 |
@@ -184,6 +187,14 @@
**When** 打开 `/star`
**Then** 页内生日表单可生成
**Given** 已有 star 报告且 `as_of` 为昨日
**When** GET latest
**Then** 日运等 outlook 按今日更新;报告 `id` 不变;响应含当日 `as_of``valid_until`
**Given** 用户停留在 `/star` 跨自然日
**When** 到达 `valid_until`
**Then** 前端静默刷新运势 Tab 内容
---
## 13. AI 开发前检查
+2 -1
View File
@@ -7,7 +7,8 @@
- DB: PostgreSQL
- Migrations: goose (or golang-migrate) under `apps/api/migrations/`
- Log: structured (zap or slog wrapper). No `fmt.Println` in business paths.
- Module path: `github.com/yuxingu/digital-psychology/apps/api`
- Module path: `github.com/yuxingu/digital-psychology/apps/api`Go import 身份;**不**表示托管在 GitHub)
- Git host: `https://git.jackyu66.com/jack/digital-psychology.git`(唯一远程 `origin`;无 GitHub Actions
## User H5