| 项目 | 说明 |
|---|---|
| 接口路径 | 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_call(system固定为AI药典,url为相对路径)。因此:
- 调用本后端 API 的
api_call已被自动覆盖,前端不要再拦截 fetch 重复上报,否则同一请求会产生两份记录(client 绝对 URL + server 相对路径,难以去重)。- 下文「自动拦截 fetch」示例仅适用于追踪「调用接入方自家后端 / 外部资源」的请求;对本后端 API 不要启用。
埋点按 system 字段区分来源系统,便于多业务系统共用一套埋点时分别统计。
| 传入值 | 含义 |
|---|---|
| 不传 / 空 | 默认归入 AI药典(当前业务系统) |
AI药典 |
本系统 |
中医 |
中医系统(示例,按接入方实际系统名传值) |
| 其它字符串 | 接入方自定义系统名,后端原样存储,自动截断至 64 字符 |
服务端自动埋点(
api_call)统一记为AI药典,无需接入方处理。
// 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' });
(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;
});
};
})();
keepalive: true,确保页面关闭/跳转时也能发出track() 必须放在 try/catch 里,埋点失败不能影响正常功能Authorization: Bearer xxx),后端自动提取用户标识;不传则记为 anonymous{} 而非 null,方便后端统一处理埋点写入接口按来源 IP 限流,默认 600 次/分钟(后端配置项 rate-limit.analytics-per-minute,可调整)。超出阈值返回 429,响应体不是 {code, message, data} 形状:
{
"error": "请求过于频繁,请稍后再试",
"detail": "请求过于频繁,请稍后再试",
"code": 429
}
接入方需单独处理 429:埋点丢失不应影响业务,建议失败指数退避重试,并控制上报频率在阈值内;如需集中回补大量历史事件,请联系后端临时调参。
本接口定位为「多业务系统共用的行为埋点 sink」。第三方接入前请知悉以下限制,部分为当前未实现、属待办(见末尾清单):
user_key 由后端从本系统 JWT 提取;埋点接口走 permitAll,不经过鉴权,因此第三方传入的 Token 不会被校验或解析,第三方事件会记为 anonymous。如需按用户分群,请把用户标识放进 event_data(如 {user_id: "xxx"})——但该标识不会进入 dimension=user 维度统计(该维度基于服务端提取的 user_key)。
system 为自由文本,仅截断至 64 字符,后端不做取值校验。任意调用方都能以 system:"中医" 等名义写入事件,存在被冒名灌脏数据的风险。建议接入方约定稳定系统名并知悉该风险。
埋点接口 permitAll + CORS 回射任意 Origin,无 API Key / 共享密钥,任何网络方都能写入任意事件。请勿在 event_data 中放置敏感个人信息(PII)、密钥、token 等。
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 维度统计)