Files
smt/docs/api.md
T

19 KiB
Raw Blame History

戒烟/抽烟记录 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=0num=0(会在 fa_smoke_log 中展示为一条记录,但不会影响看板的支数累加)。

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/:id

curl 示例:

curl -X GET 'http://127.0.0.1:8080/api/v1/smoke/logs/5202' \
  -H 'Authorization: Bearer wx-session-key'

3) 列表查询(分页)

GET /api/v1/smoke/logs?page=1&page_size=20&start=2025-12-01&end=2025-12-31

参数:

  • page:页码,默认 1
  • page_size:每页数量,默认 20,最大 200
  • start/end:可选,按 smoke_time 过滤(格式 YYYY-MM-DD

成功响应示例:

{
  "code": 200,
  "message": "success",
  "data": {
    "items": [],
    "total": 0,
    "page": 1,
    "page_size": 20
  }
}

4) 获取看板概览

GET /api/v1/smoke/dashboard?start=2026-01-01&end=2026-01-07

参数:

  • start:起始日期(含,格式 YYYY-MM-DD),默认“本周一”
  • end:截止日期(含,格式 YYYY-MM-DD),默认“本周日”。若只传 startend 默认为 start + 6 天

成功响应示例:

{
  "code": 200,
  "message": "success",
  "data": {
    "today_count": 6,
    "minutes_since_last": 42,
    "weekly": [
      { "date": "2026-01-01", "count": 2, "is_today": false },
      { "date": "2026-01-02", "count": 1, "is_today": false },
      { "date": "2026-01-03", "count": 0, "is_today": false },
      { "date": "2026-01-04", "count": 0, "is_today": false },
      { "date": "2026-01-05", "count": 3, "is_today": true },
      { "date": "2026-01-06", "count": 0, "is_today": false },
      { "date": "2026-01-07", "count": 0, "is_today": false }
    ]
  }
}

字段说明:

  • today_count:当天吸烟总支数(累加 num
  • minutes_since_last:距最后一次“实际抽烟”(忽略 level=0 && num=0 的忍住记录)的分钟数,通过最近一条 smoke_at/smoke_time/createtime 计算;若历史为空则字段不存在
  • weekly:起止日期内每日汇总,count 为当日总支数,is_today 标记当前日期(即便不在 start/end 范围内也会标记为 false

5) 最近记录列表(轻量版)

GET /api/v1/smoke/logs/latest?limit=20

参数:

  • limit:返回条数,默认 20,最大 100

成功响应示例:

{
  "code": 200,
  "message": "success",
  "data": {
    "items": [
      {
        "id": 123,
        "smoke_time": "2026-01-05T00:00:00+08:00",
        "smoke_at": "2026-01-05T09:12:00+08:00",
        "remark": "压力大",
        "level": 3,
        "num": 2,
        "createtime": 1736049120
      }
    ]
  }
}

6) 更新记录

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,后端会认为你没有修改该字段。

7) 删除记录(软删除)

DELETE /api/v1/smoke/logs/:id

成功响应:

{
  "code": 200,
  "message": "success",
  "data": {
    "deleted": true
  }
}

8) 获取 AI 戒烟建议(会员 + 广告解锁并行)

GET /api/v1/smoke/ai/advice?date=2026-01-02

说明:

  • date 可选,默认“昨天”(建议针对哪一天的数据)。
  • 权限:会员用户直接可用;非会员需要先对该 date 完成“看广告解锁”(见下一个接口)。
  • 建议结果会按 uid + date + prompt_version 缓存(表:fa_smoke_ai_advice)。

未满足权限时的建议响应(示例):

{
  "code": 403,
  "message": "需要会员或观看广告解锁后才可生成建议",
  "data": {
    "date": "2026-01-02",
    "need": "vip_or_ad"
  }
}

成功响应(示例):

{
  "code": 200,
  "message": "success",
  "data": {
    "date": "2026-01-02",
    "advice": "..."
  }
}

9) 看广告解锁(用于非会员)

POST /api/v1/smoke/ai/advice_unlocks

请求体:

{
  "date": "2026-01-02",
  "ad_watched_at": "2026-01-03 09:00:00"
}

说明:

  • 该接口用于记录“已完成观看广告”,落库到 fa_smoke_ai_advice_unlocksuid + unlock_date 唯一)。
  • ad_watched_at 可由后端取当前时间;如需审计/对账可保留前端上报并做校验。
  • 解锁是“按天”的:观看一次广告解锁一天内的 AI 生成功能(可用于「每日 AI 建议」以及「AI 下次抽烟时间节点」)。
  • 如果你要生成“明天”的 AI 时间节点,请把 date 传为明天日期(例如 2026-01-06)。

10) 获取用户基础信息(首次进入:判断是否需要补全)

GET /api/v1/smoke/profile

说明:

  • 首次进入小程序建议先调用该接口:若 exists=falseis_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",
      "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(作息时间):用于自动规避睡眠时间,防止在用户睡觉时提醒其“坚持”。

11) 补全/更新用户基础信息(Upsert)

POST /api/v1/smoke/profile

说明:

  • 字段按需传;首次进入建议一次性补全。
  • 作息时间格式:HH:MM24 小时制),例如 07:3023: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"
}

成功响应:同 GET /api/v1/smoke/profile(返回最新 profile + is_completed + baseline_interval_minutes)。

12) 想抽但忍住了(写入一条 level=0,num=0 的记录)

POST /api/v1/smoke/logs/resisted

请求体(示例):

{
  "smoke_time": "2026-01-05",
  "smoke_at": "2026-01-05 10:20:00",
  "remark": "压力大,想抽但忍住了"
}

说明:

  • 该接口会在 fa_smoke_log 中新增一条记录:level=0num=0,用于更直观记录“想抽/忍住”的过程。
  • 这类记录不会影响 today_count/weekly.count 的支数统计(因为 num=0)。

13) 获取“下次抽烟记录时间”(默认 + AI 自动切换)

GET /api/v1/smoke/next_smoke_time

说明:

  • 用于首页展示“建议的下次记录时间”。
  • 已整合首页所需汇总字段(上次抽烟时间/今日抽烟支数/今日克制次数/较昨日减少支数)。
  • 如果指定日期存在 AI 给出的时间节点(time_nodes 不为空),则优先使用 AI 的建议;否则使用默认策略。
  • 可选参数:
    • date:计划日期(默认今天),支持 YYYY-MM-DDtoday/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": "default",
    "not_before_at": "2026-01-05T10:18:00+08:00",
    "suggested_at": "2026-01-05T10:18:00+08:00",
    "last_smoke_at": "2026-01-05T09:30:00+08:00",
    "today_count": 3,
    "resisted_count": 1,
    "reduced_from_yesterday": 2,
    "exceeded_yesterday": false,
    "default": {
      "last_smoke_at": "2026-01-05T09:30:00+08:00",
      "next_smoke_at": "2026-01-05T10:18:00+08:00",
      "base_interval_minutes": 48,
      "interval_minutes": 48,
      "stage": 0,
      "resisted_7d": 3,
      "sleep_adjusted": false,
      "algorithm": "staircase_delay_v1",
      "as_of": "2026-01-05T10:00:00+08:00"
    }
  }
}

当存在 AI 建议且包含 time_nodes 时,响应会是(示例):

{
  "code": 200,
  "message": "success",
  "data": {
    "source": "ai",
    "not_before_at": "2026-01-05T10:18:00+08:00",
    "suggested_at": "2026-01-05T10:28:00+08:00",
    "last_smoke_at": "2026-01-05T09:30:00+08:00",
    "today_count": 3,
    "resisted_count": 1,
    "reduced_from_yesterday": 2,
    "exceeded_yesterday": false,
    "time_nodes": ["10:30", "11:10", "14:00", "16:30"],
    "advice": "先把这次冲动延后到10:28,期间做一次5分钟快走+喝水,压力场景用深呼吸替代。",
    "default": { "algorithm": "staircase_delay_v1" },
    "ai": {
      "plan_date": "2026-01-05",
      "not_before_at": "2026-01-05T10:18:00+08:00",
      "suggested_at": "2026-01-05T10:28:00+08:00",
      "time_nodes": ["10:30", "11:10", "14:00", "16:30"],
      "advice": "先把这次冲动延后到10:28,期间做一次5分钟快走+喝水,压力场景用深呼吸替代。",
      "prompt_version": "v1",
      "model": "gpt-4.1-mini",
      "provider": "openai-compatible"
    }
  }
}

字段说明(新增首页字段):

  • last_smoke_at:上次“实际抽烟”时间(忽略忍住记录),格式 RFC3339(含时区)。
  • today_count:今日抽烟支数(累加 num)。
  • resisted_count:今日克制次数(level=0 && num=0)。
  • reduced_from_yesterday:较昨日减少的支数(允许为负数;为负时表示“今天超出昨日”)。
  • exceeded_yesterday:是否超出昨日(true 表示今天超出昨日,前端可用作单独标识)。

14) 获取健康与储蓄统计(合并接口)

GET /api/v1/smoke/health_savings?period=week&start=2026-01-01&end=2026-01-07

说明:

  • 合并了"肺部功能恢复"和"节省金额"两个指标,专用于统计页面展示。
  • 支持按周/月/年查询,通过 period 参数指定时间范围类型。

参数:

  • period(必填):时间范围类型,可选值:
    • week:周(默认本周一至本周日)
    • month:月(默认本月1日至本月最后一日)
    • year:年(默认本年1月1日至本年12月31日)
  • start(可选):起始日期(格式 YYYY-MM-DD),不传则根据 period 自动计算
  • end(可选):截止日期(格式 YYYY-MM-DD),不传则根据 period 自动计算

计算逻辑

  1. 肺部功能恢复百分比

    • 基于用户最后一次实际抽烟时间(忽略 level=0 && num=0 的忍住记录)
    • 计算无烟时长(分钟)
    • 根据医学研究恢复时间线计算恢复百分比(详见 docs/ALGORITHM.md
    • 公式:
      无烟天数 = (当前时间 - 最后抽烟时间) / (24 * 60) 分钟
      
      恢复百分比计算:
      - < 14天: (天数 / 14) * 15%
      - 14-30天: 15% + ((天数 - 14) / 16) * 15%
      - 30-90天: 30% + ((天数 - 30) / 60) * 20%
      - > 90天: 50% + ((天数 - 90) / 275) * 50%,最高 100%
      
  2. 节省金额

    • 基于用户在统计周期内的实际抽烟量与基线对比
    • 计算公式:
      预期总支数 = baseline_cigs_per_day * 周期天数
      实际总支数 = 周期内所有记录的 num 累加(排除 level=0 && num=0
      节省支数 = 预期总支数 - 实际总支数
      节省包数 = 节省支数 / 20(假设每包20支)
      节省金额(分)= 节省包数 * pack_price_cent
      
    • 若实际总支数 > 预期总支数,则节省金额为 0(不显示负数)
  3. 目标金额

    • 默认目标:baseline_cigs_per_day * 周期天数 / 20 * pack_price_cent(即完全戒烟节省的金额)
    • 或使用用户设定的目标金额(如有)

成功响应示例:

{
  "code": 200,
  "message": "success",
  "data": {
    "period": "week",
    "start": "2026-01-01",
    "end": "2026-01-07",
    "days": 7,
    "health": {
      "lung_recovery_percent": 40.5,
      "smoke_free_minutes": 11520,
      "smoke_free_days": 8,
      "last_smoke_at": "2025-12-24T14:30:00+08:00",
      "recovery_stage": "early",  // early(0-14天) / mid(14-30天) / late(30-90天) / advanced(>90天)
      "next_milestone": {
        "days_until": 6,
        "milestone": "2周 - 肺功能提升15%",
        "percent_at_milestone": 15
      }
    },
    "savings": {
      "saved_amount_cent": 14500,  // 已节省金额(分)
      "saved_amount_yuan": 145.00, // 已节省金额(元,前端展示用)
      "target_amount_cent": 20000, // 目标金额(分)
      "target_amount_yuan": 200.00, // 目标金额(元)
      "saved_cigs": 58,             // 节省支数
      "expected_cigs": 140,          // 预期总支数
      "actual_cigs": 82,             // 实际总支数
      "progress_percent": 72.5,      // 完成进度百分比
      "pack_price_cent": 2500,      // 单包价格(分)
      "cigs_per_pack": 20            // 每包支数
    },
    "profile": {
      "baseline_cigs_per_day": 20,
      "pack_price_cent": 2500,
      "smoking_years": 8
    },
    "calculated_at": "2026-01-08T10:00:00+08:00"
  }
}

字段说明:

health(健康数据)

  • lung_recovery_percent:肺部功能恢复百分比(0-100
  • smoke_free_minutes:无烟时长(分钟)
  • smoke_free_days:无烟天数(保留1位小数)
  • last_smoke_at:最后一次实际抽烟时间(RFC3339格式)
  • recovery_stage:恢复阶段标识
  • next_milestone:下一个里程碑信息

savings(储蓄数据)

  • saved_amount_cent:已节省金额(分,用于计算)
  • saved_amount_yuan:已节省金额(元,用于前端展示)
  • target_amount_cent:目标金额(分)
  • target_amount_yuan:目标金额(元)
  • saved_cigs:节省的支数
  • expected_cigs:预期总支数(基线 × 天数)
  • actual_cigs:实际总支数
  • progress_percent:完成进度百分比(saved_amount / target_amount * 100
  • pack_price_cent:单包价格(分)
  • cigs_per_pack:每包支数(默认20

period 参数说明

  • period=week(周):

    • 默认:本周一 00:00:00 至 本周日 23:59:59
    • 若传 start,则 end = start + 6 天
  • period=month(月):

    • 默认:本月1日 00:00:00 至 本月最后一日 23:59:59
    • 若传 start,则 end = start 所在月的最后一日
  • period=year(年):

    • 默认:本年1月1日 00:00:00 至 本年12月31日 23:59:59
    • 若传 start,则 end = start 所在年的12月31日

curl 示例:

# 查询本周数据
curl -X GET 'http://127.0.0.1:8080/api/v1/smoke/health_savings?period=week' \
  -H 'Authorization: Bearer wx-session-key'

# 查询本月数据
curl -X GET 'http://127.0.0.1:8080/api/v1/smoke/health_savings?period=month' \
  -H 'Authorization: Bearer wx-session-key'

# 查询本年数据
curl -X GET 'http://127.0.0.1:8080/api/v1/smoke/health_savings?period=year' \
  -H 'Authorization: Bearer wx-session-key'

# 查询指定日期范围
curl -X GET 'http://127.0.0.1:8080/api/v1/smoke/health_savings?period=week&start=2026-01-01&end=2026-01-07' \
  -H 'Authorization: Bearer wx-session-key'

注意事项

  1. 若用户尚未补全 profile(缺少 baseline_cigs_per_daypack_price_cent),相关计算字段可能为 0 或使用默认值
  2. 若用户从未抽烟(无历史记录),last_smoke_at 可能不存在,肺部恢复百分比按最大恢复计算
  3. 节省金额计算时,若实际支数 > 预期支数,saved_amount_cent 为 0(不显示负数)
  4. 时间范围计算时,使用用户所在时区(后端需根据用户配置或默认使用 UTC+8)