ANALYTICS_API.md 9.8 KB

用户行为埋点 API 文档

基本信息

项目 说明
接口路径 POST /api/v1/analytics/events
请求方式 POST
Content-Type application/json
返回格式 {code, message, data}
认证 无需认证(有 Token 时自动记录用户标识,无 Token 记录为 anonymous)

一、上报行为埋点

请求

POST /api/v1/analytics/events
Content-Type: application/json
Authorization: Bearer <token>   (可选)

请求体

字段 类型 必填 说明
event_type string 事件类型,不传则默认为 unknown
system string 来源系统,用于区分不同业务系统的埋点。不传默认 AI药典;接入方按自身系统传值即可,如 中医
event_data object 事件附加数据,JSON 对象
page_url string 触发事件的页面 URL(建议传完整地址)
referrer string 来源页面 URL

服务端自动记录

以下字段由服务端从请求中自动提取,无需前端传入

字段 来源
user_key JWT Token 中的用户标识,未登录为 anonymous
ip 请求来源 IP
user_agent User-Agent 请求头
system_source 取自请求体 system,缺省记为 AI药典,超长自动截断至 64 字符

响应

{
  "code": 202,
  "message": "accepted",
  "data": {
    "status": "accepted"
  }
}

注意:接口返回 202,写入是异步的(fire-and-forget),前端无需等待或处理结果。

示例

curl -X POST https://pharmacopoeia.kailin.com.cn/api/v1/analytics/events \
  -H "Content-Type: application/json" \
  -d '{
    "event_type": "page_view",
    "system": "AI药典",
    "event_data": {},
    "page_url": "https://pharmacopoeia.kailin.com.cn/?token=xxx"
  }'

二、标准事件类型定义

以下为推荐约定,接入方也可使用任意自定义 event_type(后端原样存储,超长截断至 64 字符)。自定义类型会出现在 GET /event-types 与事件列表中,可正常查询;但概览看板的 page_views / searches / ai_qa 等聚合指标按固定类型统计,自定义类型不纳入。

页面行为

event_type 触发时机 event_data
page_view 页面加载/进入 {}{referrer: "来源URL"}

用户交互

event_type 触发时机 event_data
click 点击快捷提问标签 {label: "标签名称"}
switch_tab 切换导航标签页 {tab: "chat" \| "drugs"}

对话行为

event_type 触发时机 event_data
chat 用户发送提问 {query: "用户输入的问题", has_media: true \| false}

搜索行为

event_type 触发时机 event_data
search 搜索药品 {keyword: "搜索关键词"}

浏览行为

event_type 触发时机 event_data
drug_detail 查看药品详情 {drug_id: "H20000001", drug_name: "阿莫西林"}

品牌推荐

event_type 触发时机 event_data
brand_recommend 品牌推荐匹配命中 {recommendations: [{brand_name, matched_keyword, tier}]}

品牌推荐匹配由后端在 SSE 流中自动注入 brand_recommend 事件,前端收到后渲染推荐卡片。事件数据详见 HANDOVER.md 第十六章节。

接口调用(自动埋点)

event_type 触发时机 event_data
api_call 任意 API 调用完成 {url: "/api/v1/chat/stream", method: "POST", status: 200, elapsed_ms: 342}
api_call API 调用失败 {url: "/api/v1/drug/search", method: "GET", status: 0, elapsed_ms: 5000, error: "network_error"}

服务端已自动埋点,前端勿重复拦截:本后端对所有 /api/** 入站请求(/api/v1/analytics/events 自身除外)已由服务端 ApiTrackingFilter 自动记录一条 api_callsystem 固定为 AI药典url 为相对路径)。因此:

  • 调用本后端 APIapi_call 已被自动覆盖,前端不要再拦截 fetch 重复上报,否则同一请求会产生两份记录(client 绝对 URL + server 相对路径,难以去重)。
  • 下文「自动拦截 fetch」示例仅适用于追踪「调用接入方自家后端 / 外部资源」的请求;对本后端 API 不要启用。

三、来源系统(system)

埋点按 system 字段区分来源系统,便于多业务系统共用一套埋点时分别统计。

取值约定

传入值 含义
不传 / 空 默认归入 AI药典(当前业务系统)
AI药典 本系统
中医 中医系统(示例,按接入方实际系统名传值)
其它字符串 接入方自定义系统名,后端原样存储,自动截断至 64 字符

服务端自动埋点(api_call)统一记为 AI药典,无需接入方处理。


四、前端接入示例

最小接入(推荐封装 track 函数)

// SYSTEM 为当前接入方系统名,不传后端默认记为 AI药典
var SYSTEM = '中医';

function track(eventType, eventData) {
  var payload = {
    event_type: eventType,
    system: SYSTEM,
    event_data: eventData || {},
    page_url: window.location.href,
    referrer: document.referrer || ''
  };
  try {
    fetch('https://pharmacopoeia.kailin.com.cn/api/v1/analytics/events', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
      keepalive: true   // 页面关闭时也能发出
    }).catch(function(){});
  } catch(e) {}
}

使用示例

// 页面浏览
track('page_view');

// 搜索
track('search', { keyword: '阿莫西林' });

// 点击
track('click', { label: '感冒药推荐', position: 1 });

// 提问
track('chat', { query: '布洛芬怎么吃', has_media: false });

// 自定义事件
track('custom_event', { key1: 'value1', key2: 'value2' });

自动拦截 fetch(可选,自动记录所有 API 调用)

(function(){
  var _fetch = window.fetch;
  window.fetch = function(url, opts){
    var t0 = Date.now();
    var method = (opts && opts.method) || 'GET';
    return _fetch.apply(this, arguments).then(function(r){
      track('api_call', {
        url: typeof url === 'string' ? url : '',
        method: method,
        status: r.status,
        elapsed_ms: Date.now() - t0
      });
      return r;
    }).catch(function(e){
      track('api_call', {
        url: typeof url === 'string' ? url : '',
        method: method,
        status: 0,
        elapsed_ms: Date.now() - t0,
        error: e.message || 'network_error'
      });
      throw e;
    });
  };
})();

五、注意事项

  1. fire-and-forget — 埋点接口返回 202,异步写入,前端不要 await,不要阻塞主流程
  2. keepalive — fetch 带上 keepalive: true,确保页面关闭/跳转时也能发出
  3. 容错track() 必须放在 try/catch 里,埋点失败不能影响正常功能
  4. user_key — 建议前端传 JWT Token(Authorization: Bearer xxx),后端自动提取用户标识;不传则记为 anonymous
  5. event_data — 建议始终传 {} 而非 null,方便后端统一处理
  6. page_url — 建议传完整 URL,方便分析不同页面的行为分布

六、限流与错误响应

埋点写入接口按来源 IP 限流,默认 600 次/分钟(后端配置项 rate-limit.analytics-per-minute,可调整)。超出阈值返回 429,响应体不是 {code, message, data} 形状:

{
  "error": "请求过于频繁,请稍后再试",
  "detail": "请求过于频繁,请稍后再试",
  "code": 429
}

接入方需单独处理 429:埋点丢失不应影响业务,建议失败指数退避重试,并控制上报频率在阈值内;如需集中回补大量历史事件,请联系后端临时调参。


七、第三方接入须知

本接口定位为「多业务系统共用的行为埋点 sink」。第三方接入前请知悉以下限制,部分为当前未实现、属待办(见末尾清单):

1. 用户标识(user_key)

user_key 由后端从本系统 JWT 提取;埋点接口走 permitAll不经过鉴权,因此第三方传入的 Token 不会被校验或解析,第三方事件会记为 anonymous。如需按用户分群,请把用户标识放进 event_data(如 {user_id: "xxx"})——但该标识不会进入 dimension=user 维度统计(该维度基于服务端提取的 user_key)。

2. 来源系统(system)无白名单

system 为自由文本,仅截断至 64 字符,后端不做取值校验。任意调用方都能以 system:"中医" 等名义写入事件,存在被冒名灌脏数据的风险。建议接入方约定稳定系统名并知悉该风险。

3. 接口无鉴权(安全风险)

埋点接口 permitAll + CORS 回射任意 Origin,无 API Key / 共享密钥,任何网络方都能写入任意事件。请勿在 event_data 中放置敏感个人信息(PII)、密钥、token 等。

4. event_data 体积

event_data 整块 JSON 原样存储(JSONB),后端不做体积截断(仅 page_url / referrer / user_agent / system / event_type / user_key / ip 等标量字段截断)。请接入方自行控制单条 event_data 大小,避免超大 payload。

待办(需后端改造,未含在本文档承诺内)

  • 接入方密钥 / 签名鉴权
  • system 取值白名单
  • event_data 体积上限校验
  • 第三方 external_user_id 字段(接入方自填,进入 user 维度统计)