# 腾讯-百度追踪回传系统 (tencent-baidu-tracking) 测试文档 ## 一、项目概述 本系统是腾讯广告平台与百度ADX之间的追踪和转化回传中转服务器,主要功能: 1. 接收腾讯曝光/点击监测请求 2. 向百度ADX发起竞价请求并上报追踪事件 3. 定时从百度拉取转化数据并回传给腾讯 4. 失败回调自动重试 **技术栈**:Java 17 / Spring Boot 3.2.5 / MyBatis 3.0.3 / Redis (Lettuce) / MySQL(TiDB) --- ## 二、测试环境要求 | 组件 | 版本/配置 | |------|-----------| | JDK | 17+ | | Redis | 6.0+ (需支持 Stream) | | MySQL/TiDB | 5.7+ / TiDB 6.0+ | | Maven | 3.8+ | | Profile | `application-test.yml` | **环境准备**: - Redis 地址: 配置在 `application-test.yml` 的 `adx.redis-addr` - MySQL/TiDB: 配置在 `adx.tidb-url`,需预先创建数据库 `adx_tencent` - 系统自动建表(`TiDBColdStore.migrate()`) --- ## 三、API 接口测试 ### 3.1 健康检查 | 项目 | 说明 | |------|------| | 端点 | `GET /health` | | 预期响应 | `200 OK`,Body: `{"ok": true}` | **测试用例**: | 编号 | 场景 | 请求 | 预期结果 | |------|------|------|----------| | HC-01 | 正常健康检查 | `GET /health` | 200, `{"ok":true}` | | HC-02 | 服务启动后立即检查 | `GET /health` | 200, `{"ok":true}` | --- ### 3.2 曝光监测接口 | 项目 | 说明 | |------|------| | 端点 | `GET /tencent/impression` | | 必需参数 | 至少一个: `click_id`, `trace_id`, `callback`, `request_id`, `oaid`, `imei`, `idfa`, `caid` | | 成功响应 | `204 No Content` | **测试用例**: | 编号 | 场景 | 请求参数 | 预期结果 | |------|------|----------|----------| | IMP-01 | 正常曝光(click_id) | `?click_id=test123&platform=android` | 204 No Content | | IMP-02 | 正常曝光(oaid) | `?oaid=abc123&platform=android` | 204 No Content | | IMP-03 | 正常曝光(imei) | `?imei=def456&platform=android` | 204 No Content | | IMP-04 | 正常曝光(idfa+ios) | `?idfa=guid123&platform=ios` | 204 No Content | | IMP-05 | 缺少 trace id | `?platform=android` | 400, `{"error":"missing media trace id..."}` | | IMP-06 | 空 trace id | `?click_id=&platform=android` | 400, `{"error":"missing media trace id..."}` | | IMP-07 | 无 platform 且有默认配置 | `?click_id=test123` | 204(使用默认 baidu_app_id/tag_id) | | IMP-08 | 无 platform 且无默认配置 | `?click_id=test123`(未配置默认) | 400, `{"error":"missing baidu_tag_id or platform"}` | | IMP-09 | 指定 baidu_tag_id | `?click_id=x&baidu_tag_id=tag001` | 204 | | IMP-10 | 无效的 baidu_tag_id | `?click_id=x&baidu_tag_id=invalid` | 400, `{"error":"unknown baidu tag id: invalid"}` | | IMP-11 | 百度 ADX 返回空响应 | 模拟百度返回空 | 502, `{"error":"empty baidu ADX response"}` | | IMP-12 | 百度 ADX 网络超时 | 模拟网络超时 | 502, `{"error":"..."}` | **验证点**: - Redis 中应存在 bid 缓存键:`adx:tencent:bid:{qk}` 和 `adx:tencent:media:tencent:{traceId}` - Redis Stream `adx:tencent:events` 中应有 type=bid 的事件 - 若有 showUrls,应能看到 impression tracking 的 Stream 事件 --- ### 3.3 点击监测接口 | 项目 | 说明 | |------|------| | 端点 | `GET /tencent/click` | | 必需参数 | 至少一个: `click_id`, `trace_id`, `callback`, `request_id`, `oaid`, `imei`, `idfa`, `caid` | | 成功响应 | `302 Redirect`(重定向到落地页/应用商店) | **测试用例**: | 编号 | 场景 | 请求参数 | 预期结果 | |------|------|----------|----------| | CLK-01 | 已有 bid 记录(重定向到 landingPage) | `?click_id=test123&platform=android` | 302, Location=landingPage | | CLK-02 | 已有 bid 记录(重定向到 appStoreLink) | `?click_id=test123&platform=ios` | 302, Location=appStoreLink | | CLK-03 | 无 bid 记录(临时竞价) | `?click_id=newId&platform=android` | 302 或 JSON | | CLK-04 | 缺少 trace id | `?platform=android` | 400, `{"error":"missing media trace id..."}` | | CLK-05 | 无重定向目标 | `?click_id=noDest&platform=android` | 200 JSON: `{"qk":"...","traceId":"...","tracking":[...]}` | | CLK-06 | 百度点击上报失败(不影响重定向) | 模拟点击 URL 返回 500 | 仍然 302 重定向 | | CLK-07 | 百度 ADX 竞价失败(无已有 bid) | 模拟 ADX 返回错误 | 502, `{"error":"..."}` | **验证点**: - 点击上报后 Redis Stream 应有 type=tracking, kind=click 的事件 - 302 重定向的 Location 头应为有效 URL --- ### 3.4 管理接口 #### 3.4.1 查询百度转化 | 项目 | 说明 | |------|------| | 端点 | `POST /admin/conversions/query` | | Content-Type | `application/json` | | 请求体 | `{"date":"20260702","pageSize":1,"acts":[1,2]}` | **测试用例**: | 编号 | 场景 | 请求体 | 预期结果 | |------|------|--------|----------| | AQ-01 | 正常查询 | `{"date":"20260702","pageSize":1}` | 200, ConversionResponse | | AQ-02 | 缺少 date | `{"pageSize":1}` | 400, `{"error":"date is required"}` | | AQ-03 | pageSize 无效 | `{"date":"20260702","pageSize":0}` | 400, `{"error":"pageSize must be > 0"}` | | AQ-04 | 百度 API 返回错误码 | 模拟百度返回 code!=200 | 502, `{"error":"Baidu conversion API failed: ..."}` | #### 3.4.2 手动触发转化同步 | 项目 | 说明 | |------|------| | 端点 | `POST /admin/conversions/sync` | | Content-Type | `application/json` | **测试用例**: | 编号 | 场景 | 请求体 | 预期结果 | |------|------|--------|----------| | AS-01 | 正常同步 | `{"date":"20260702","pageSize":1,"acts":[1]}` | 200, SyncResult | | AS-02 | syncer 未配置 | (TiDB 未配置时) | 503, `{"error":"conversion syncer is not configured"}` | | AS-03 | 百度无数据 | `{"date":"20260101","pageSize":1}` | 200, `fetched=0` | #### 3.4.3 手动触发回调重试 | 项目 | 说明 | |------|------| | 端点 | `POST /admin/callbacks/retry` | | Content-Type | `application/json` | **测试用例**: | 编号 | 场景 | 请求体 | 预期结果 | |------|------|--------|----------| | AR-01 | 正常重试 | `{"limit":10}` | 200, RetryResult | | AR-02 | retryService 未配置 | (TiDB 未配置时) | 503, `{"error":"callback retryer is not configured"}` | | AR-03 | limit=0(使用默认值) | `{"limit":0}` | 200, RetryResult | | AR-04 | 无待重试记录 | `{"limit":10}` | 200, `{"fetched":0,"sent":0,"failed":0}` | --- ## 四、核心业务逻辑测试 ### 4.1 竞价价格编码 (AuctionPriceEncoder) | 编号 | 模式 | 输入 | 预期 | |------|------|------|------| | PE-01 | none | price=1000 | URL encode 的 "1000" | | PE-02 | aes-ecb | price=1000,eKey=16字节 | Base64 编码的 AES-ECB 加密值(URL encoded) | | PE-03 | aes-cbc | price=1000,eKey+iKey | Base64 编码的 AES-CBC 加密值(URL encoded) | | PE-04 | aes-cbc iKey 不足16字节 | eKey=xxx, iKey=short | 抛出 IllegalArgumentException | | PE-05 | 未知模式 | mode="unknown" | 抛出 IllegalArgumentException | | PE-06 | 自动推断(有 eKey+iKey) | mode="",提供 eKey+iKey | 使用 aes-cbc | | PE-07 | 自动推断(仅 eKey) | mode="",仅提供 eKey | 使用 aes-ecb | | PE-08 | 自动推断(无 key) | mode="",无 key | 使用 none | ### 4.2 百度转化签名 (ConversionClient) | 编号 | 场景 | 验证 | |------|------|------| | CS-01 | 签名正确性 | `md5(timestamp + secret + randomStr)` 结果为32位十六进制 | | CS-02 | 相同输入产生相同签名 | 固定 timestamp/secret/randomStr 验证一致性 | | CS-03 | 随机字符串唯一性 | 两次 `newRandomString()` 结果不同 | ### 4.3 MediaPlacement 解析 | 编号 | 场景 | 输入 params | 预期结果 | |------|------|-------------|----------| | MP-01 | 指定 baidu_tag_id(匹配 platform 配置) | `{baidu_tag_id: "tag1"}` | Resolved(platform, appId, "tag1") | | MP-02 | 指定 platform=android | `{platform: "android"}` | Resolved("android", androidAppId, androidTagId) | | MP-03 | 指定 platform=ios | `{platform: "ios"}` | Resolved("ios", iosAppId, iosTagId) | | MP-04 | platform 别名("安卓") | `{platform: "安卓"}` | 归一化为 "android" | | MP-05 | platform 别名("1"=ios) | `{platform: "1"}` | 归一化为 "ios" | | MP-06 | 无 tag/platform 但有默认值 | `{}` | 使用默认 baiduAppId/baiduTagId | | MP-07 | 多 tagId 未指定具体 tag | `{platform: "android"}`(配置多个 tagId) | 抛出 IllegalArgumentException | | MP-08 | 未知 tag_id | `{baidu_tag_id: "unknown"}` | 抛出 IllegalArgumentException | ### 4.4 腾讯 Act 映射 (TencentClient) | 编号 | 百度 Act | 预期腾讯 ActionType | |------|----------|---------------------| | AM-01 | 1 | ACTIVATE_APP | | AM-02 | 2 | PURCHASE | | AM-03 | 3 | REGISTER | | AM-04 | 4 | ONE_DAY_RETENTION | | AM-05 | 5 | COMPLETE_ORDER | | AM-06 | 6 | START_APP | | AM-07 | 7 | CUSTOM (custom_action=key_behavior) | | AM-08 | 8 | CUSTOM (custom_action=7day_retention) | | AM-09 | 9 | CUSTOM (custom_action=3day_retention) | | AM-10 | 99(未映射) | 抛出 IllegalArgumentException | ### 4.5 设备标识 MD5 处理 (TencentClient.ensureMd5) | 编号 | 输入 | 预期 | |------|------|------| | MD-01 | 已是32位十六进制 "abcdef1234567890abcdef1234567890" | 原值(小写化) | | MD-02 | 原始 IMEI "123456789012345" | 计算 MD5 | | MD-03 | null | null | | MD-04 | 空字符串 | null | | MD-05 | 大写32位十六进制 | 转小写返回 | --- ## 五、数据存储测试 ### 5.1 Redis 热存储 (RedisHotStore) | 编号 | 场景 | 操作 | 验证 | |------|------|------|------| | RH-01 | 记录 Bid | `recordBid(record)` | 1. `adx:tencent:bid:{qk}` 存在 2. `adx:tencent:media:tencent:{traceId}` 存在 3. TTL=24h 4. Stream 有 type=bid 事件 | | RH-02 | 按 qk 查找 Bid | `findBidByQk(qk)` | 返回正确的 BidRecord | | RH-03 | 按 mediaTrace 查找 Bid | `findBidByMediaTrace("tencent", traceId)` | 返回正确的 BidRecord | | RH-04 | 查找不存在的 Bid | `findBidByQk("nonexist")` | 返回 null | | RH-05 | Bid TTL 过期 | 等待 TTL 后查询 | 返回 null | | RH-06 | 记录 Tracking | `recordTracking(record)` | Stream 有 type=tracking 事件 | | RH-07 | 确保消费者组 | `ensureGroup("test-group")` | 不抛异常,幂等 | | RH-08 | 读取 Stream 消息 | `read(group, consumer, 10)` | 返回待消费的事件列表 | | RH-09 | ACK 消息 | `ack(group, ids)` | 消息从 pending 列表移除 | | RH-10 | Trim Stream | `trim(maxLen)` | Stream 长度不超过 maxLen | | RH-11 | ClaimStale | 模拟超时未 ACK 消息 | `claimStale` 能获取到超时消息 | ### 5.2 TiDB 冷存储 (TiDBColdStore) | 编号 | 场景 | 操作 | 验证 | |------|------|------|------| | TC-01 | 自动建表 | `migrate()` | 4张表创建成功 | | TC-02 | 保存 Bid(新记录) | `saveBid(record)` | ad_bid_events 表有记录 | | TC-03 | 保存 Bid(重复 qk) | 两次 `saveBid(sameQk)` | 更新而非报错(UPSERT) | | TC-04 | qk 为空抛异常 | `saveBid(qk=null)` | 抛出 IllegalArgumentException | | TC-05 | 保存 Tracking | `saveTracking(record)` | tracking_reports 表有记录 | | TC-06 | 保存 Conversion(新记录) | `saveConversion(record)` | baidu_conversions 表有记录 | | TC-07 | 保存 Conversion(重复 dedupeKey) | 两次相同 dedupeKey | 更新而非报错 | | TC-08 | 保存 MediaCallback | `saveMediaCallback(record)` | media_callbacks 表有记录 | | TC-09 | 查询成功回调存在 | `successfulCallbackExists(key)` | 有 ok=1 记录返回 true | | TC-10 | 查询成功回调不存在 | `successfulCallbackExists(key)` | 无 ok=1 记录返回 false | | TC-11 | 查询待重试回调 | `pendingMediaCallbacks("tencent", 10)` | 返回 ok=0 且无对应 ok=1 的记录 | | TC-12 | 空 dedupeKey 不查询 | `successfulCallbackExists("")` | 返回 false | --- ## 六、后台任务测试 ### 6.1 ColdWorker(冷路径数据持久化) | 编号 | 场景 | 验证 | |------|------|------| | CW-01 | 正常消费 bid 事件 | Stream 中 bid 事件被消费,TiDB ad_bid_events 有新记录 | | CW-02 | 正常消费 tracking 事件 | Stream 中 tracking 事件被消费,TiDB tracking_reports 有新记录 | | CW-03 | 未知事件类型 | Stream 中有 type=unknown 事件 → 抛异常,该批次不 ACK | | CW-04 | TiDB 写入失败 | 模拟 TiDB 不可用 → 抛异常,消息不 ACK(可重试) | | CW-05 | 空 Stream | 无消息时返回 0 | | CW-06 | 超时消息重新领取 | 模拟消费者崩溃 → 超过 pendingIdle 后被其他 worker claim | | CW-07 | Stream Trim | 消费后 Stream 长度被裁剪 | ### 6.2 ConversionSyncRunner(转化同步) | 编号 | 场景 | 验证 | |------|------|------| | SR-01 | 正常同步(有匹配的 tencent bid) | fetched > 0, matched > 0, sent > 0 | | SR-02 | 无匹配 bid | fetched > 0, skipped > 0, matched=0 | | SR-03 | bid 不属于 tencent | 记录到 baidu_conversions 但 skipped | | SR-04 | 幂等 - 已成功发送过 | alreadySent > 0,不重复调用腾讯 | | SR-05 | 腾讯回传失败 | failed > 0,media_callbacks 记录 ok=0 | | SR-06 | 分页拉取多页 | 正确遍历多页数据 | | SR-07 | 最大页数限制(100页) | 超过100页时停止 | | SR-08 | dateOffsetDays 配置 | offset=0 查当天,offset=-1 查昨天 | ### 6.3 RetryService(回调重试) | 编号 | 场景 | 验证 | |------|------|------| | RS-01 | 正常重试成功 | fetched > 0, sent > 0, media_callbacks 新增 ok=1 记录 | | RS-02 | 重试仍失败 | failed > 0, attempt 递增, 新记录 ok=0 | | RS-03 | 无待重试记录 | fetched=0, sent=0, failed=0 | | RS-04 | 使用默认 limit | limit=0 时使用配置的默认值 | | RS-05 | 网络异常 | failed > 0, errorMessage 记录异常信息 | ### 6.4 Leader Election(分布式选举) | 编号 | 场景 | 验证 | |------|------|------| | LE-01 | 单实例获取锁 | 成功获取锁并执行任务 | | LE-02 | 多实例竞选 | 仅一个实例获得锁,其他等待 | | LE-03 | 锁续约成功 | 任务执行期间锁不过期 | | LE-04 | 续约失败(锁丢失) | jobStop.isStopped() = true,任务停止 | | LE-05 | 任务完成释放锁 | 任务结束后其他实例可获得锁 | | LE-06 | 外部停止信号 | stopped=true 时退出循环 | | LE-07 | skip-leader-election=true | 跳过选举直接运行任务 | --- ## 七、异常处理测试 ### 7.1 全局异常处理器 (GlobalExceptionHandler) | 编号 | 异常类型 | 预期 HTTP 状态码 | 预期响应格式 | |------|----------|------------------|--------------| | EH-01 | ResponseStatusException(400, "bad") | 400 | `{"error":"bad"}` | | EH-02 | ResponseStatusException(503, "unavailable") | 503 | `{"error":"unavailable"}` | | EH-03 | IllegalArgumentException("invalid param") | 400 | `{"error":"invalid param"}` | | EH-04 | IOException("timeout") | 502 | `{"error":"timeout"}` | | EH-05 | InterruptedException | 502 | `{"error":"..."}` | | EH-06 | RuntimeException("unexpected") | 502 | `{"error":"unexpected"}` | | EH-07 | NullPointerException(message=null) | 502 | `{"error":"internal error"}` | --- ## 八、数据库表结构验证 ### 8.1 ad_bid_events | 字段 | 类型 | 约束 | 验证 | |------|------|------|------| | qk | VARCHAR(128) | UNIQUE NOT NULL | 唯一键,重复插入应更新 | | media | VARCHAR(64) | - | 值为 "tencent" | | media_trace_id | VARCHAR(255) | 索引 | 与 media 联合索引 | | req_id | VARCHAR(512) | - | 竞价请求 ID | | price | BIGINT UNSIGNED | - | 竞价价格(分) | | show_urls | JSON | - | JSON 数组 | | click_urls | JSON | - | JSON 数组 | | created_at | TIMESTAMP(3) | 索引 | 毫秒精度 | ### 8.2 tracking_reports | 字段 | 类型 | 约束 | 验证 | |------|------|------|------| | qk | VARCHAR(128) | 联合索引 | 关联 bid | | kind | VARCHAR(32) | NOT NULL | "impression" 或 "click" | | ok | TINYINT(1) | NOT NULL | 0 或 1 | | tracking_url | TEXT | NOT NULL | 上报的 URL | ### 8.3 baidu_conversions | 字段 | 类型 | 约束 | 验证 | |------|------|------|------| | dedupe_key | VARCHAR(255) | UNIQUE | 幂等键,格式: `{media}:{qk}:{date}:{deviceId}:{act}` | | act | INT | 联合索引 | 百度行为类型码 | | payment | DOUBLE | - | 支付金额 | ### 8.4 media_callbacks | 字段 | 类型 | 约束 | 验证 | |------|------|------|------| | dedupe_key | VARCHAR(255) | 索引 | 回调去重键 | | ok | TINYINT(1) | NOT NULL | 回传是否成功 | | attempt | INT | DEFAULT 1 | 重试次数递增 | | event_type | INT | NOT NULL | 对应百度 act | | error_message | TEXT | - | 失败时记录错误信息 | --- ## 九、端到端集成测试 ### 9.1 完整曝光 → 点击 → 转化回传流程 ``` 步骤 1: 发送曝光请求 GET /tencent/impression?click_id=e2e_test_001&platform=android&oaid=test_oaid_123 验证: ✓ 返回 204 ✓ Redis 存在 bid 缓存 ✓ Redis Stream 有 bid 事件 步骤 2: 发送点击请求 GET /tencent/click?click_id=e2e_test_001&platform=android&oaid=test_oaid_123 验证: ✓ 返回 302 重定向 ✓ Redis Stream 有 click tracking 事件 步骤 3: 等待 ColdWorker 消费 验证: ✓ ad_bid_events 表有记录 ✓ tracking_reports 表有 impression 和 click 记录 步骤 4: 触发转化同步 POST /admin/conversions/sync Body: {"date":"20260702","pageSize":1,"acts":[1]} 验证: ✓ 百度转化数据被拉取 ✓ 匹配到腾讯 bid 后回传成功 ✓ baidu_conversions 表有记录 ✓ media_callbacks 表有 ok=1 的记录 步骤 5: 幂等验证 - 再次同步 POST /admin/conversions/sync (相同参数) 验证: ✓ alreadySent > 0,不重复回传 ``` ### 9.2 回调失败 → 自动重试流程 ``` 步骤 1: 模拟腾讯回传失败 构造一条 media_callbacks 记录 (ok=0, attempt=1) 步骤 2: 触发回调重试 POST /admin/callbacks/retry Body: {"limit":10} 验证: ✓ 获取到待重试记录 ✓ 重试后生成新的 callback 记录 ✓ attempt = 2 ``` --- ## 十、性能与压力测试 ### 10.1 API 性能基线 | 接口 | 目标 QPS | 目标 P99 延迟 | 备注 | |------|----------|--------------|------| | GET /health | >10000 | <10ms | 无外部依赖 | | GET /tencent/impression | >500 | <200ms | 含百度竞价+Redis 写入 | | GET /tencent/click | >500 | <200ms | 含 Redis 读取+百度上报 | ### 10.2 后台任务性能 | 任务 | 指标 | 目标 | |------|------|------| | ColdWorker | 消费速率 | >1000 events/s | | ConversionSync | 处理速率 | >100 records/batch(并发=10) | | RetryService | 重试速率 | >50 records/batch | ### 10.3 Redis 压力 - bid 缓存 key 数量: 验证 TTL 自动清理 - Stream 长度: 验证 XTRIM 保持在配置的 maxLen 以内 - 内存使用: 监控 Redis 内存占用稳定性 --- ## 十一、安全测试 | 编号 | 场景 | 验证 | |------|------|------| | SEC-01 | 百度签名校验 | MD5 签名格式正确、不可伪造 | | SEC-02 | Access Token 保护 | 生产环境 token 从环境变量注入,不硬编码 | | SEC-03 | Redis 锁安全性 | Lua 脚本保证仅持有者能释放/续约 | | SEC-04 | SQL 注入防护 | MyBatis 参数化查询,无 SQL 拼接 | | SEC-05 | 设备标识脱敏 | 腾讯回传使用 MD5 哈希而非明文 | --- ## 十二、配置测试 | 编号 | 场景 | 验证 | |------|------|------| | CFG-01 | dev 环境启动 | skip-leader-election=true,sync/retry 禁用 | | CFG-02 | test 环境启动 | sync/retry 启用,短间隔 | | CFG-03 | prod 环境启动 | 环境变量注入,leader election 启用 | | CFG-04 | TiDB 未配置 | ColdStore=null,相关功能降级但不崩溃 | | CFG-05 | Redis 连接失败 | 启动失败,打印明确错误信息 | | CFG-06 | 百度 ADX endpoint 为空 | 竞价时抛 IllegalStateException | | CFG-07 | bid TTL 配置 | 默认 24h,可自定义 | --- ## 十三、测试执行方式 ### 单元测试(推荐 Mock) ```bash # 运行全部测试 mvn test -Ptest # 运行指定类 mvn test -Dtest=AuctionPriceEncoderTest # 运行指定方法 mvn test -Dtest=MediaPlacementTest#testResolvePlatformAndroid ``` ### 集成测试(需要外部依赖) ```bash # 需要 Redis + MySQL 环境 mvn verify -Ptest -Dspring.profiles.active=test ``` ### 手动接口测试(cURL) ```bash # 健康检查 curl -s http://localhost:8787/health # 曝光监测 curl -s -o /dev/null -w "%{http_code}" \ "http://localhost:8787/tencent/impression?click_id=test001&platform=android&oaid=test_oaid" # 点击监测 curl -s -o /dev/null -w "%{http_code}" -L \ "http://localhost:8787/tencent/click?click_id=test001&platform=android" # 查询转化 curl -s -X POST http://localhost:8787/admin/conversions/query \ -H "Content-Type: application/json" \ -d '{"date":"20260702","pageSize":1,"acts":[1,2]}' # 触发同步 curl -s -X POST http://localhost:8787/admin/conversions/sync \ -H "Content-Type: application/json" \ -d '{"date":"20260702","pageSize":1,"acts":[1]}' # 触发重试 curl -s -X POST http://localhost:8787/admin/callbacks/retry \ -H "Content-Type: application/json" \ -d '{"limit":10}' ``` --- ## 十四、测试覆盖率目标 | 模块 | 目标覆盖率 | 重点 | |------|-----------|------| | httpapi (Controller) | ≥80% | 参数校验、异常处理 | | baidu (AdxClient, ConversionClient) | ≥75% | 签名、加密、HTTP 调用 | | tencent (TencentClient) | ≥80% | act 映射、设备标识处理、回传逻辑 | | conversionsync | ≥85% | 核心业务逻辑 | | storage (Redis/TiDB) | ≥70% | CRUD、幂等性 | | leader | ≥60% | 锁获取/释放/续约 | | worker | ≥75% | 事件消费、错误处理 | --- ## 十五、已知限制与注意事项 1. **重试限制**:RetryService 当前实现中,重试使用原始 callbackUrl 发送空 body,正式环境需验证腾讯 API 是否接受此方式 2. **并发安全**:ConversionSyncService 使用 10 并发线程处理支付记录,需确保 Redis/TiDB 连接池够用 3. **时钟依赖**:转化同步的 `dateOffsetDays` 依赖系统时钟,需确保服务器时间同步 4. **百度 ADX 模拟**:单元测试需 Mock 百度 ADX HTTP 响应(推荐 WireMock 或 MockWebServer) 5. **数据清理**:集成测试后需清理测试数据,避免影响后续测试 6. **Adm 字段拼写**:百度原始字段 `landdingPage`(双 d),需注意 JSON 序列化/反序列化一致性