集合:心情随笔(journals)
1. 目的与使用场景
- 存储心情随笔与 REBT 结构化记录,支撑心情随笔页、护心周报、微建议生成以及从心情随笔转化任务的流程。由
/api/journals、周报生成云函数、情绪趋势分析使用。
枚举值:
- entry_type:
text(文本)、voice(语音)、image(图片)、mixed(混合)、template(模板) - share_scope:
private(私密)、guardian(守护者)、shared_link(分享链接) - risk_level:
L1(一般)、L2(关注)、L3(需要干预)、L4(紧急危机) - source:
journal_page(随笔页)、quick_note(快速记录)、mood_card(情绪卡片)
2. Schema 定义
| 字段 | 类型 | 必填 | 默认值 | 约束/校验 | 说明 | 隐私分级 |
|---|---|---|---|---|---|---|
| _id | string | 是 | - | TCB 自动 | 主键 | P3 |
| _openid | string | 是 | - | TCB 自动 | 创建人 openid | P3 |
| user_id | 指针id | 是 | - | 指向 users._id | 用户指针 | P3 |
| entry_type | string | 是 | text | 枚举(text/voice/image/mixed/template) | 记录类型 | P2 |
| title | string | 否 | - | 长度 ≤ 40 | 可选标题 | P1 |
| body_text | string | 否 | - | 长度 ≤ 4000 | 文本正文或语音转写 | P3 |
| mood_score | number | 否 | - | 0-100 | 情绪分值 | P3 |
| mood_tags | array | 否 | [] | 元素长度 ≤ 20 | 标签云来源 | P3 |
| scene_tags | array | 否 | [] | 元素长度 ≤ 20 | 场景标签 | P3 |
| media_refs | array | 否 | [] | 元素为 object {type,url,metadata} | 附件引用(支持语音/图片/手写OCR) | P3 |
| rebt_struct | object | 否 | - | A/B/C/D/E 字段 | REBT 结构化内容 | P3 |
| ocr_text | string | 否 | - | 长度 ≤ 2000 | OCR识别文本 | P3 |
| derived_summary | object | 否 | - | {title, bullet_points[], keywords[]} | AI 摘要(包含关键词) | P2 |
| generated_task_ids | array | 否 | [] | 指向 tasks._id | 转任务引用 | P2 |
| share_scope | string | 是 | private | 枚举(private/guardian/shared_link) | 分享范围 | P2 |
| risk_level | string | 否 | L1 | 枚举(L1/L2/L3/L4) | 风险等级 | P3 |
| allow_suggestion_training | bool | 否 | false | - | 是否用于建议模型训练 | P2 |
| source | string | 是 | journal_page | 枚举(journal_page,quick_note,mood_card) | 创建入口 | P1 |
| is_favorited | bool | 否 | false | - | 收藏标记 | P1 |
| audio_duration | number | 否 | - | ≥0 | 音频时长(秒) | P2 |
| word_count | number | 否 | - | ≥0 | 文本字数统计 | P1 |
| createdAt | date | 是 | - | 服务端时间 | 创建时间 | P1 |
| updatedAt | date | 是 | - | 服务端时间 | 更新时间 | P1 |
| deletedAt | date | 否 | - | - | 软删时间 | P2 |
| is_deleted | bool | 否 | false | - | 软删标记 | P2 |
3. 索引与唯一约束
- 单字段索引:
user_id、createdAt、share_scope、risk_level - 复合索引:
[user_id, createdAt desc]、[user_id, is_deleted, share_scope]、[user_id, risk_level, createdAt desc] - 唯一性约束:无(按 _id)
- 设计理由:心情随笔列表按时间倒序;分享过滤需按 share_scope;软删过滤避免返回删除记录;风险等级用于连续负向情绪检测。
4. 访问控制(TCB 权限)
{
"read": "_openid == auth.openid || (share_scope == 'guardian' && auth.role == 'guardian') || auth.role in ['counselor']",
"write": "_openid == auth.openid || auth.role in ['counselor']"
}角色说明:用户可读写自身;授权守护者可读 share_scope=guardian 的记录;辅导员在紧急情况下可写入备注。
- 关系与级联
user_id→usersgenerated_task_ids→tasks- 与
checkins通过日期关联,用于情绪趋势 - 删除策略:软删保留审计;删除时清空
derived_summary并解除任务关联。 - 反范式:
derived_summary缓存周报摘要;更新正文时需刷新。
- 数据生命周期与合规
- 留存:用户主动删除即软删,默认保留 2 年;过期后提供导出备份后删除正文。
- 匿名化:导出时可生成去标识摘要;模型训练前需脱敏处理。
- 导出流程:用户在“我的-数据导出”发起 → 云函数压缩 JSON → COS 临时链接。
- 审计字段:
createdAt、updatedAt、deletedAt、allow_suggestion_training。
API/云函数契约映射 | 接口/函数 | 读/写 | 使用字段 | 过滤条件 | 排序/分页 | 备注 | |
/api/journalsGET | 读 | title, body_text, mood_score, mood_tags, scene_tags, createdAt, rebt_struct, risk_level |user_id = auth.uid,is_deleted=false|createdAt desc, 分页 | 心情随笔列表 | |/api/journalsPOST | 写 | entry_type, body_text, mood_score, mood_tags, scene_tags, rebt_struct, share_scope |_openid = auth.openid| - | 创建记录 | |/api/journalsPATCH | 写 | title, body_text, mood_tags, share_scope |_id匹配 | - | 编辑 | |/api/moods/trend| 读 | mood_score, mood_tags, createdAt |user_id = auth.uid| - | 标签云 | | 周报云函数generateWeeklyReport| 读 | mood_score, mood_tags, derived_summary | 周期内user_id| - | 护心周报 | | 连续负向检测consecutiveNegativeCheck| 读 | mood_score, risk_level, createdAt |user_id = auth.uid, 最近2条 |createdAt desc| 负向情绪预警 |示例文档(≥3条)
{
"_id": "jr_20240406_01",
"_openid": "oAbcd123",
"user_id": "usr_001",
"entry_type": "text",
"title": "与室友调解",
"body_text": "今天和室友聊开了噪音问题,心情平稳不少。",
"mood_score": 68,
"mood_tags": ["室友", "沟通"],
"scene_tags": ["宿舍"],
"rebt_struct": {"A": "室友打游戏很吵", "B": "他们不尊重我", "C": "焦虑", "D": "尝试约定时间", "E": "得到了理解"},
"derived_summary": {"title": "成功沟通解决噪音问题", "bullet_points": ["主动沟通", "得到理解"], "keywords": ["沟通", "解决"]},
"risk_level": "L1",
"generated_task_ids": ["task_305"],
"share_scope": "private",
"word_count": 28,
"createdAt": "2024-04-06T13:20:00Z",
"updatedAt": "2024-04-06T13:20:00Z"
}
{
"_id": "jr_20240405_02",
"_openid": "oAbcd456",
"user_id": "usr_002",
"entry_type": "voice",
"body_text": "语音转写:今天状态不佳,准备尝试冥想。",
"mood_score": 42,
"mood_tags": ["焦虑", "学习"],
"scene_tags": ["自习室"],
"share_scope": "guardian",
"risk_level": "L2",
"audio_duration": 15,
"allow_suggestion_training": false,
"createdAt": "2024-04-05T21:05:00Z",
"updatedAt": "2024-04-05T21:06:00Z"
}
{
"_id": "jr_20240404_ocr",
"_openid": "oAbcd123",
"user_id": "usr_001",
"entry_type": "mixed",
"source": "quick_note",
"body_text": "今天手写记录了感恩日记,感觉心情变好了。",
"ocr_text": "感恩日记:1. 好天气 2. 同学帮助 3. 健康身体",
"media_refs": [{"type": "image", "url": "cos://journal-img-001.jpg", "metadata": {"width": 1080, "height": 1920}}],
"mood_score": 75,
"risk_level": "L1",
"share_scope": "private",
"word_count": 26,
"createdAt": "2024-04-04T10:10:00Z",
"updatedAt": "2024-04-04T10:10:00Z"
}- 常用查询样例(≥3条)
// 心情随笔列表(分页)
const page = await db.collection('journals')
.where({ user_id: auth.uid, is_deleted: false })
.orderBy('createdAt', 'desc')
.skip(20 * pageIndex)
.limit(20)
.get();
// 统计近一月情绪标签
const tags = await db.collection('journals').where({
user_id: auth.uid,
createdAt: db.command.gte(new Date(Date.now() - 30 * 86400000).toISOString())
}).field({ mood_tags: true }).get();
// 守护者获取可见心情随笔
const guardianView = await db.collection('journals').where({
share_scope: 'guardian',
user_id: targetUserId,
is_deleted: false
}).orderBy('createdAt', 'desc').get();- 边界与错误码
- 超长正文:超过 4000 字返回
E_JOURNAL_TOO_LONG。 - 并发编辑:检测
updatedAt,冲突返回E_VERSION_MISMATCH。 - 越权:未授权守护者访问返回
E_FORBIDDEN并记录日志。
- 变更影响评估
- 新增字段需同步
/api/journalsDTO、护心周报模板、周报推送通知内容。 - 索引调整影响心情随笔搜索性能及埋点
journal_entry_submit的查询报表。
- 假设与待确认
- 假设
share_scope='guardian'通过守护者列表控制,待确认是否存在多守护者差异化权限。 - 待确认 REBT 结构是否需要单独拆表用于 AI 训练。