13 KiB
戒烟/抽烟记录 API
所有接口前缀:/api/v1/smoke
除登录外都需要:Authorization: Bearer <session_key>(见:docs/common/auth.md)
1) 新增记录
POST /api/v1/smoke/logs
请求体:
{
"smoke_time": "2025-12-31",
"smoke_at": "2025-12-31 08:30:00",
"remark": "压力大",
"level": 2,
"num": 3
}
说明:
smoke_time可选;不传则默认“当天”。smoke_at可选;真实抽烟时间(格式YYYY-MM-DD HH:MM:SS)。用于“按时间节点分析/AI 建议”;不传则可用createtime近似。level/num可选;不传时后端会按1处理。- “想抽但忍住了”请传
level=0且num=0,系统以num=0作为“忍住”的判断条件(计入忍住次数,但不计入抽烟支数)。
curl 示例:
curl -X POST 'http://127.0.0.1:8080/api/v1/smoke/logs' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer wx-session-key' \
-d '{"smoke_time":"2025-12-31","smoke_at":"2025-12-31 08:30:00","remark":"压力大","level":2,"num":3}'
成功响应示例(字段以实际为准):
{
"code": 200,
"message": "success",
"data": {
"id": 5202,
"smoke_time": "2025-12-31T00:00:00+08:00",
"smoke_at": "2025-12-31T08:30:00+08:00",
"remark": "压力大",
"createtime": 1735600000,
"updatetime": 1735600000,
"deletetime": null,
"level": 2,
"num": 3
}
}
2) 列表查询(分页)
GET /api/v1/smoke/logs?page=1&page_size=20&start=2025-12-01&end=2025-12-31&type=all
参数:
page:页码,默认1page_size:每页数量,默认20,最大200start/end:可选,按smoke_time过滤(格式YYYY-MM-DD)type:可选,默认all;smoke表示抽烟记录(num>0),resisted表示忍住记录(num=0)
说明:
- 列表按时间倒序返回(优先
smoke_at,其次createtime,最后smoke_time)。
成功响应示例:
{
"code": 200,
"message": "success",
"data": {
"items": [],
"total": 0,
"page": 1,
"page_size": 20
}
}
3) 更新记录
POST /api/v1/smoke/logs/:id
请求体(字段可选,按需传):
{
"smoke_time": "2026-01-01",
"smoke_at": "2026-01-01 21:10:00",
"remark": "聚会",
"level": 3,
"num": 1
}
注意:
- 如果你想“清空 smoke_time”,请传空字符串:
{"smoke_time":""}。 - 如果你想“清空 smoke_at”,请传空字符串:
{"smoke_at":""}。 - 如果传
null或者不传smoke_time,后端会认为你没有修改该字段。
4) 删除记录(软删除)
DELETE /api/v1/smoke/logs/:id
成功响应:
{
"code": 200,
"message": "success",
"data": {
"deleted": true
}
}
5) 首页整合接口(Home)
GET /api/v1/smoke/home
此接口只返回首页、AI 时间页和 AI 日总结页正在消费的核心字段,避免生成或传递无用模块。返回示例:
{
"code": 200,
"message": "success",
"data": {
"timer": {
"seconds_since_last": 9900,
"next_suggested_at": "2026-01-05T10:30:00+08:00",
"next_suggested_clock": "10:30",
"suggestion_source": "default"
},
"summary": {
"today_count": 3,
"daily_target": 10,
"reduced_from_yesterday": 2,
"exceeded_yesterday": false
},
"motivation": {
"message": "太棒了!你刚刚成功抵抗了一次烟瘾",
"type": "praise"
}
}
}
字段说明:
timer.seconds_since_last:距上次抽烟的秒数(无记录返回-1)。timer.next_suggested_at:建议下次抽烟时间(RFC3339)。timer.next_suggested_clock:仅时分显示(如“16:30”)。timer.suggestion_source:建议来源(default/ai)。summary.today_count:今日吸烟支数累加。summary.daily_target:每日目标。summary.reduced_from_yesterday:与昨日的绝对差值(非负)。summary.exceeded_yesterday:是否比昨天多。daily_summary:当天已缓存的 AI 日总结;无缓存时为null。motivation.message:激励语文案。motivation.type:激励语类型。
如需生成 AI 时间节点,请调用 GET /api/v1/smoke/ai/next_smoke_time;首页接口只读取缓存,不主动生成 AI 建议,避免额外性能成本。
10) 看广告解锁(用于非会员)
POST /api/v1/smoke/ai/advice_unlocks
请求体:
{
"date": "2026-01-02",
"ad_watched_at": "2026-01-03 09:00:00"
}
说明:
- 该接口用于记录“已完成观看广告”,落库到
fa_smoke_ai_advice_unlocks(uid + unlock_date唯一)。 ad_watched_at可由后端取当前时间;如需审计/对账可保留前端上报并做校验。- 解锁是“按天”的:观看一次广告解锁一天内的 AI 生成功能(可用于「每日 AI 建议」以及「AI 下次抽烟时间节点」)。
- 如果你要生成“明天”的 AI 时间节点,请把
date传为明天日期(例如2026-01-06)。
11) 获取用户基础信息(首次进入:判断是否需要补全)
GET /api/v1/smoke/profile
说明:
- 首次进入小程序建议先调用该接口:若
exists=false或is_completed=false,前端进入“信息补全”流程。 baseline_interval_minutes用于建立初始基准:在用户清醒时段内的“平均间隔(分钟)”。计算:awake_minutes / baseline_cigs_per_day。- 若未提供作息时间(起床/入睡),后端会用默认清醒时长
16*60=960分钟参与计算。
成功响应(示例):
{
"code": 200,
"message": "success",
"data": {
"exists": true,
"profile": {
"id": 1,
"created_at": "2026-01-05T10:00:00+08:00",
"updated_at": "2026-01-05T10:00:00+08:00",
"baseline_cigs_per_day": 20,
"smoking_years": 8,
"pack_price_cent": 2500,
"smoke_motivations": ["压力大", "社交"],
"quit_motivations": ["身体健康", "省钱"],
"wake_up_time": "07:30",
"sleep_time": "23:30",
"quit_date": "2026-02-28T00:00:00+08:00",
"onboarding_completed_at": "2026-01-05T10:00:00+08:00"
},
"is_completed": true,
"awake_minutes": 960,
"baseline_interval_minutes": 48
}
}
当 exists=false(尚未补全)时,响应示例:
{
"code": 200,
"message": "success",
"data": {
"exists": false,
"is_completed": false,
"awake_minutes": 960,
"baseline_interval_minutes": 0
}
}
字段用途(补全页面可参考):
baseline_cigs_per_day(基础烟量/日均抽烟支数):建立初始基准,计算初始建议间隔时长。smoking_years(烟龄/年)+pack_price_cent(单包价格/分):用于看板计算“已省金额”和“恢复时长”等指标(公式可在看板端实现)。smoke_motivations(抽烟动机):如压力大/无聊/社交/提神,用于 AI 在分析 remark 时更有针对性。quit_motivations(戒烟动力):如身体健康/家人孩子/省钱,当用户产生动摇时 AI 可用这些信息做“情感阻断/自我提醒”。wake_up_time+sleep_time(作息时间):用于自动规避睡眠时间,防止在用户睡觉时提醒其“坚持”。quit_date(目标戒烟日期):用于阶段规划或到期提醒。
12) 补全/更新用户基础信息(Upsert)
POST /api/v1/smoke/profile
说明:
- 字段按需传;首次进入建议一次性补全。
- 作息时间格式:
HH:MM(24 小时制),例如07:30、23:10。 pack_price_cent为“分”;若前端用“元”,请乘以 100。
请求体(示例):
{
"baseline_cigs_per_day": 20,
"smoking_years": 8,
"pack_price_cent": 2500,
"smoke_motivations": ["压力大", "社交"],
"quit_motivations": ["身体健康", "省钱"],
"wake_up_time": "07:30",
"sleep_time": "23:30",
"quit_date": "2026-02-28"
}
成功响应:同 GET /api/v1/smoke/profile(返回最新 profile + is_completed + baseline_interval_minutes)。
13) 获取 AI 下次抽烟建议
GET /api/v1/smoke/ai/next_smoke_time
说明:
- 用于 AI 建议页生成当天时间节点。
- 首页只通过
GET /smoke/home读取已缓存的 AI 结果,不主动生成 AI,避免首页加载时产生额外性能成本。 - 可选参数:
date:计划日期(默认今天),支持YYYY-MM-DD或today/tomorrow。mode(默认auto)auto:只在已存在 AI 时间节点时使用 AI(不主动生成)ai:生成该date的 AI 时间节点(需要先看广告解锁;生成一次缓存一天)default:永远返回默认策略
默认策略(不使用 AI):
- 基础间隔:优先使用
GET /api/v1/smoke/profile返回的baseline_interval_minutes;若不存在则默认60分钟。 - 阶梯式延时:最近 7 天内每累计
5条“忍住记录(level=0,num=0)”,在基础间隔上+5分钟(最多+60分钟)。 - 间隔兜底:最终间隔会限制在
5~240分钟之间。 - 若用户已补全作息时间,会自动规避睡眠区间:若计算出的时间落在睡眠区间,顺延到下一次起床时间。
AI 生成说明:
- 当
mode=ai时,会把最近 3 天的抽烟数据(含“忍住记录”)作为输入提供给 AI,用于更贴合近期模式生成时间节点。 - 未解锁时会返回
403:提示需要观看广告解锁。
成功响应(示例):
{
"code": 200,
"message": "success",
"data": {
"source": "ai",
"suggested_at": "2026-01-05T10:28:00+08:00",
"time_nodes": ["10:30", "11:10", "14:00", "16:30"],
"advice": "先把这次冲动延后到10:28,期间做一次5分钟快走+喝水,压力场景用深呼吸替代。"
}
}
14) 数据统计分析(趋势 + 健康 + 省钱)
GET /api/v1/smoke/stats?range=week|month|year&date=2026-01-07
参数:
range:week|month|year,默认weekdate:锚点日期(YYYY-MM-DD),默认今天
说明:
- 用于“统计页”一屏数据整合(趋势、均值、环比、健康恢复、省钱、连续记录、已拒绝次数)。
trend_unit:day或month,用于前端图表横轴显示。
成功响应(示例):
{
"code": 200,
"message": "success",
"data": {
"range": "week",
"start": "2026-01-01",
"end": "2026-01-07",
"trend_unit": "day",
"trend": [
{ "label": "2026-01-01", "count": 2 },
{ "label": "2026-01-02", "count": 1 },
{ "label": "2026-01-03", "count": 0 },
{ "label": "2026-01-04", "count": 0 },
{ "label": "2026-01-05", "count": 3 },
{ "label": "2026-01-06", "count": 0 },
{ "label": "2026-01-07", "count": 0 }
],
"daily_average": 4,
"change_percent": -20,
"money": {
"available": true,
"pack_price_cent": 2500,
"cigs_per_pack": 20,
"expected_total": 140,
"actual_total": 92,
"saved_cent": 6000
},
"health": {
"available": true,
"smoke_free_minutes": 420,
"lung_recovery_percent": 12,
"milestones": [
{ "name": "心率血压恢复正常", "minutes": 20, "reached": true },
{ "name": "血氧水平恢复", "minutes": 480, "reached": false }
]
},
"streak_days": 12,
"resisted_total": 24
}
}
字段说明:
change_percent:与上一个同周期对比的变化比例(可为负)。money.available=false:表示缺少baseline_cigs_per_day或pack_price_cent。money.expected_total:按“统计周期内有记录的天数”×baseline_cigs_per_day计算;不统计无日志的天数。money.saved_cent:按max(expected_total - actual_total, 0)计算,避免出现负值。health.available=false:表示无历史记录。
15) 成就主题与当前称号
GET /api/v1/smoke/achievement/themes
返回 onboarding 可选择的称号主题。每个主题包含 icon/name/key/levels,前端在“重填问卷”中展示。
GET /api/v1/smoke/achievement
返回当前用户所选主题下的当前等级、下一等级和进度。记录抽烟模式使用“少抽积分”进阶;戒烟打卡模式仍使用连续记录天数进阶。
记录模式少抽积分算法:
- 统计窗口:从
onboarding_completed_at到今天,最多回看最近 90 天。 - 只统计有记录的日期,避免无记录日期被误判为 0 根。
- 每日基础分:
max(0, baseline_cigs_per_day - 当日抽烟支数)。 - 当日少抽比例
>=30%额外+1分。 - 当日少抽比例
>=60%额外+2分。 - 当日抽烟支数为
0额外+3分。 - 总积分用于匹配所选主题的等级阈值;
fa_achievement_level.required_days在记录模式下作为required_score使用。
成功响应示例:
{
"code": 200,
"message": "success",
"data": {
"achievement": {
"theme_id": 1,
"theme_name": "意志骑士",
"theme_key": "knight",
"theme_icon": "🛡️",
"metric_type": "score",
"score": 48,
"metrics": {
"baseline_cigs_per_day": 10,
"scored_days": 7,
"total_reduced_cigs": 36,
"today_reduced_cigs": 4,
"today_reduce_percent": 40,
"stable_days": 6
},
"current": {
"name": "见习骑士",
"icon": "🛡️",
"required_score": 30
},
"next": {
"name": "白银骑士",
"icon": "⚔️",
"required_score": 80
},
"progress": 0.36
}
}
}