跳到正文
☰
UA
评论中间件现状
重构前事实基线
审计日期 2026-08-12
◐
正在渲染文档...
此文档需要启用 JavaScript 才能渲染 Markdown 和 Mermaid 图。
# 业务功能与端到端流程(现状) > 审计日期:2026-08-12 > 审计范围:`console` 下的命令控制器、队列 Job、渠道 Client、ActiveRecord Model、Console 配置与 `console/runtime/logs/app.log`。 > 证据状态:静态代码可确认设计路径;历史日志可确认部分路径曾被人工执行或由 Worker 消费。未连接生产数据库、Redis、Cron/Supervisor,也未调用外部平台,因此真实数据量、当前启停状态、调度频率和外部平台最终结果均未验证。 > 安全说明:本文不记录真实密钥、Token、评论正文、手机号或完整第三方响应。历史日志自身含敏感环境变量,引用时仅给出脱敏后的事件摘要与行号。 ## 1. 业务定位与边界 该工程当前不是面向业务人员的评论管理 UI,而是一个通过 Yii Console 命令和 Redis Queue 运行的多渠道评论中间件。它维护店铺渠道凭据,按店铺拉取京东、天猫/淘宝、抖音评论,将渠道原始 JSON 落入各自表;京东和天猫/淘宝还会读取待回复表并调用平台回复接口。 业务边界可概括为: ```mermaid flowchart LR OP["人工或未知调度器"] --> CMD["Yii Console 命令"] CMD --> DB1[("app_info / comments_limitation / 回复表")] CMD --> RQ["Redis Queue"] RQ --> W["queue/run 或 queue/exec Worker"] W --> API["京东中间件 / 淘宝开放平台 / 抖店开放平台"] W --> DB2[("渠道商品、评论、回复结果表")] API -->|"评论和商品数据"| W W -->|"回复评论:外部副作用"| API DB2 --> DOWN["下游消费者:Unknown"] ``` 证据:Console 注册 `douyin`、`tmall`、`jd` 三个模块且启动 Redis Queue(`console/config/main.php:13-50`);历史日志出现 `queue/run`(`console/runtime/logs/app.log:2321`)、`queue/exec`(`console/runtime/logs/app.log:13178`)及渠道命令(`console/runtime/logs/app.log:7150`、`7368`、`12189`)。前后台代码没有发现本业务的页面或 API 路由。 ## 2. 功能清单 | 能力 | 当前行为 | 业务输入 | 数据/副作用 | 状态 | 证据 | | --- | --- | --- | --- | --- | --- | | 账户/店铺配置 | 按 `channel_type`、可选 `shop_code` 查询店铺;凭据和访问令牌来自 `app_info` | 渠道类型、店铺编码 | 供渠道 Client 鉴权;京东还需店铺到中间件 `appId` 的映射 | Confirmed | `console/models/AppInfo.php:19-23,27-40,71-97`;`console/modules/jd/HuFuClient.php:51-59` | | 同步任务标识 | 将 `渠道名>渠道类型>店铺编码` 编入 `limit_type`,Job 再拆解定位账户 | `AppInfo` 或 `limitId` | 关联 `app_info` 与 `comments_limitation`,无显式外键 | Confirmed | `console/models/AppInfo.php:71-83` | | 京东评论抓取 | 最近 180 天按配置天数切片,每片分页拉取评论并写入 `jd_comments` | 可选店铺、或已有时间片 ID | 调用京东中间件 `/comments`;新增/更新评论原始 JSON | Confirmed | `console/modules/jd/controllers/CommentsController.php:20-82`;`console/modules/jd/CommentJob.php:27-79`;`console/modules/jd/HuFuClient.php:61-89` | | 天猫评论抓取 | 最近 180 天按当前仓库配置的 30 天切片(代码 fallback 为 3 天),分页拉取基础评价并写入 `tmall_comments` | 天猫店铺、或已有时间片 ID | 调用淘宝 `TaobaoTraderatesGet`;每页再派发详情任务 | Confirmed | `console/modules/tmall/controllers/CommentsController.php:42-97`;`console/config/params-local.php:14`;`console/modules/tmall/CommentJob.php:30-91` | | 淘宝评论抓取 | 与天猫共用 Controller、Job、Client、评论表,账户类型不同 | 淘宝店铺 | 与天猫相同的基础评价接口和落库路径 | Confirmed | `console/modules/tmall/controllers/CommentsController.php:28-39`;`console/models/AppInfo.php:19-22` | | 天猫/淘宝评论详情补全 | 按基础评价页的 `oid` 列表逐单查询详情并回写 `detail` | `limitId`、`oids[]` | 调用 `TmallTraderateFeedsGet`;逐条更新 `tmall_comments.detail` | Hidden capability | `console/modules/tmall/CommentJob.php:46-84`;`console/modules/tmall/CommenDetailtJob.php:31-59`;`console/modules/tmall/Client.php:64-69` | | 抖音商品枚举 | 单个 180 天时间片内分页查询普通商品并登记商品 ID | 可选店铺 | 调用抖店商品列表;写 `douyin_products`;每商品页派发评论任务 | Confirmed,但查询范围受硬编码过滤 | `console/modules/douyin/controllers/CommentsController.php:42-80`;`console/modules/douyin/CommentJob.php:31-69`;`console/modules/douyin/Client.php:96-129` | | 抖音商品评论抓取 | 按商品逐个、按 offset 分页拉取时间片开始后的评论 | `limitId`、`productIds[]` | 调用抖店评论列表;新增/更新 `douyin_comments` | Hidden capability | `console/modules/douyin/ProductCommentJob.php:12-83`;`console/modules/douyin/Client.php:26-55` | | 京东评论回复 | 顺序扫描 `jd_comments_reply` 中的新记录,入队后最多尝试 5 次 | 可选店铺;回复表记录;人工重启 ID | 调用京东中间件 `/comments/reply`,为不可逆外部副作用;保存响应/异常 | Confirmed | `console/modules/jd/controllers/ReplyController.php:21-59`;`console/modules/jd/CommentReplyJob.php:23-66`;`console/modules/jd/HuFuClient.php:91-109` | | 天猫/淘宝评论回复 | 顺序扫描共享回复表,入队后最多尝试 5 次 | 可选店铺;回复表记录;人工重启 ID | 调用 `TaobaoTraderateExplainAdd`;保存响应/异常 | Confirmed | `console/modules/tmall/controllers/ReplyController.php:21-63`;`console/modules/tmall/CommentReplyJob.php:26-67`;`console/modules/tmall/Client.php:71-75` | | 时间切片 | 京东、天猫/淘宝把最近 180 天拆成多个窗口;抖音建立单个 180 天窗口 | 当前时间、配置间隔 | 创建 `comments_limitation(new)`;只以“是否已有任意 new”阻止再次创建 | Hidden capability | `console/modules/jd/controllers/CommentsController.php:47-82`;`console/modules/tmall/controllers/CommentsController.php:61-97`;`console/modules/douyin/controllers/CommentsController.php:61-80` | | 队列节流 | 京东各时间片按序增加 120 秒延迟;天猫/淘宝、抖音主任务立即入队 | 时间片序号 | Redis 延迟消息;Queue TTR 为 4 小时 | Confirmed | `console/modules/jd/controllers/CommentsController.php:35-38`;`console/config/main.php:26-37` | | 业务级回复重试 | 回复失败且累计次数小于 5 时,120 秒后再次派发自己 | `retry_count`、`limit=5`、`delay=120` | 重复调用外部回复接口;最终 `success` 或 `error` | Confirmed | `console/modules/jd/CommentReplyJob.php:47-65`;`console/modules/tmall/CommentReplyJob.php:49-66` | | 按 ID 重新派发同步 | 三渠道均可读取现有时间片 ID并重新推送主 Job | 时间片 ID | 不重置时间片状态,直接入队 | Hidden capability | `console/modules/jd/controllers/CommentsController.php:20-25`;`console/modules/tmall/controllers/CommentsController.php:20-25`;`console/modules/douyin/controllers/CommentsController.php:34-39` | | 按 ID 人工重启回复 | 京东和天猫/淘宝可按回复主键再次入队 | 回复 ID | 不重置状态和次数;不检查当前状态 | Hidden capability | `console/modules/jd/controllers/ReplyController.php:63-92`;`console/modules/tmall/controllers/ReplyController.php:65-94` | | 日志与命令行诊断 | Job 输出进度到 stdout,异常写 Yii FileTarget;Queue 加载 LogBehavior | 页码、窗口、商品/回复 ID、异常 | `console/runtime/logs/app.log`;可能记录完整响应和运行环境 | Confirmed | `console/config/main.php:34-44`;`console/modules/jd/CommentJob.php:42-43,81-85`;日志错误示例 `console/runtime/logs/app.log:10743` | | 抖音 SDK 调试入口 | 固定店铺和商品调用 SKU 列表并打印结果,不属于批量同步链路 | 代码内固定标识 | 直接调用外部接口并输出结果 | Hidden/debug capability | `console/modules/douyin/controllers/CommentsController.php:21-30` | ## 3. 通用调度与时间片流程 ### 3.1 正常路径 ```mermaid sequenceDiagram actor Trigger as 人工或未知调度器 participant Controller as 渠道 CommentsController participant AppDB as app_info participant LimitDB as comments_limitation participant Queue as Redis Queue participant Worker as Queue Worker Trigger->>Controller: 执行 fetch/tmall/tao-bao,可选 shopCode Controller->>AppDB: 按 channel_type + shop_code 查询账户 loop 每个账户 Controller->>LimitDB: 查询同 limit_type 且 status=new alt 已存在 new Controller-->>Trigger: 本店跳过,不创建新一批 else 不存在 new Controller->>LimitDB: 创建最近 180 天时间片,status=new Controller->>Queue: 推送 {limitId} end end Worker->>Queue: queue/run 或 queue/exec 消费 Queue->>Worker: 反序列化渠道 Job Worker->>LimitDB: 通过 limitId 读取窗口与账户标识 ``` 京东参数名为 `jd_interval_days`,当前仓库没有发现覆盖值,因此使用代码 fallback 的 30 天/片;天猫/淘宝参数名为 `tmall_interval_days`,Controller 的 fallback 是 3 天,但当前 `console/config/params-local.php` 明确覆盖为 30 天/片。Console 配置会依次合并公共参数、公共本地参数、Console 参数和 Console 本地参数,所以当前仓库实际口径是京东 30 天、天猫/淘宝 30 天;部署环境若使用不同的本地配置,实际值仍可能不同。抖音不分片,整段 180 天为一片(`console/config/main.php:3-8`;`console/config/params-local.php:14`;`console/modules/jd/controllers/CommentsController.php:56-79`;`console/modules/tmall/controllers/CommentsController.php:70-94`;`console/modules/douyin/controllers/CommentsController.php:70-78`)。 ### 3.2 当前时间片状态机 ```mermaid stateDiagram-v2 [*] --> new: Controller 创建记录 new --> success: 主 Job 自行结束 new --> error: 主 Job 捕获异常 success --> success: actionStart 再派发且再次成功 success --> error: actionStart 再派发但失败 error --> success: actionStart 再派发且成功 error --> error: actionStart 再派发仍失败 ``` 不存在 `running` 状态、领取时间、Worker ID、心跳、租约、分页游标、子任务计数或错误消息字段写入。`Limitation::end()` 只更新 `status` 并忽略传入的 `$message`(`console/models/Limitation.php:19-21,74-78`)。Controller 只检查是否存在 `new`;一旦旧批次全部成为 `success/error`,下一次执行会再次创建覆盖“当时最近 180 天”的整批窗口,而不是从 checkpoint 增量前进。 ## 4. 京东评论抓取流程 ```mermaid sequenceDiagram actor Trigger as 人工或未知调度器 participant C as jd/comments/fetch participant DB as MySQL participant Q as Redis Queue participant J as JD CommentJob participant API as 京东中间件 Trigger->>C: 可选 shopCode C->>DB: 读取京东 app_info C->>DB: 最近 180 天按 30 天 fallback 创建多个 Limitation(new) loop 每个时间片 k C->>Q: delay(k × 120秒), {limitId} end Q->>J: 执行主任务 J->>DB: 读取 Limitation 和 AppInfo loop page=1..N, pageSize=50 J->>API: POST /comments(appId), beginTime/endTime/page API-->>J: comments[] loop 每条评论 J->>DB: 按 shop_code + comment_id 查询 alt 不存在 J->>DB: INSERT jd_comments else 原始 JSON 字符串变化 J->>DB: UPDATE comment, updated_at end end end J->>DB: Limitation.status=success ``` 输入是店铺配置和北京时间格式化的窗口起止时间;API 页面大小固定为 50,直到 `comments` 为空停止(`console/modules/jd/CommentJob.php:27-43`)。京东中间件请求还依赖 `shop_code -> appId` 映射,否则抛错(`console/modules/jd/HuFuClient.php:51-59`;历史日志曾出现“店铺未配置中间件映射”,`console/runtime/logs/app.log:7048`)。评论幂等查询键在代码层是 `shop_code + comment_id`,但仓库迁移没有该唯一索引的证据。 **`success` 的真实语义:** 当前京东主 Job 没有子 Job;在单 Worker、不被中断且每次数据库保存都成功的前提下,它表示该时间窗分页循环已经遇到空页并完成落库。它仍不证明平台数据从此不会变化,也不证明没有并发重复任务。 ## 5. 天猫/淘宝评论与详情流程 ```mermaid sequenceDiagram actor Trigger as 人工或未知调度器 participant C as tmall/comments participant DB as MySQL participant Q as Redis Queue participant Main as Tmall CommentJob participant Detail as CommenDetailtJob participant TOP as 淘宝开放平台 Trigger->>C: tmall 或 tao-bao,可选 shopCode C->>DB: 读取对应 channel_type 的 app_info C->>DB: 最近 180 天按当前配置 30 天创建 Limitation(new) C->>Q: 每片立即推送 {limitId} Q->>Main: 执行主任务 loop page=1..N, pageSize=100 Main->>TOP: TaobaoTraderatesGet(start,end,page) TOP-->>Main: trade_rates + has_next loop 每条基础评价 Main->>DB: 按 shop_code + oid INSERT/UPDATE tmall_comments.comment end Main->>Q: 推送详情消息 {limitId, oids[]} end Main->>DB: Limitation.status=success Note over Main,Detail: success 写入时,详情任务可能尚未开始 Q->>Detail: 执行每页详情任务 loop 每个 oid Detail->>TOP: TmallTraderateFeedsGet(oid) TOP-->>Detail: tmall_rate_info Detail->>DB: UPDATE tmall_comments.detail end ``` 天猫与淘宝使用相同流程,仅入口查询的 `channel_type` 不同(`console/modules/tmall/controllers/CommentsController.php:28-54`)。主任务以 `has_next` 判断分页终止,每页基础评价落库后派发一个详情 Job(`console/modules/tmall/CommentJob.php:37-88`)。详情 Job 对每个 `oid` 单独捕获异常并继续下一个,失败不会反馈给父任务或时间片(`console/modules/tmall/CommenDetailtJob.php:36-65`)。 **`success` 的真实语义:** 仅表示基础评论分页完成且详情消息已尝试入队;不表示所有详情已抓取成功。详情 Job 没有自身状态或重试计数,异常又被吞掉,所以 `success` 时间片可以对应 `detail='[]'`、部分详情或完整详情三种数据状态。代码证据见 `console/modules/tmall/CommentJob.php:81-91` 与 `console/modules/tmall/CommenDetailtJob.php:36-59`。 ## 6. 抖音商品与评论流程 ```mermaid sequenceDiagram actor Trigger as 人工或未知调度器 participant C as douyin/comments/fetch participant DB as MySQL participant Q as Redis Queue participant Main as DouYin CommentJob participant Child as ProductCommentJob participant DY as 抖店开放平台 Trigger->>C: 可选 shopCode C->>DB: 读取抖音 app_info C->>DB: 创建单个最近 180 天 Limitation(new) C->>Q: 推送 {limitId} Q->>Main: 执行商品主任务 loop page=1..N, pageSize=100 Main->>DY: ProductListV2(page,size,普通商品,固定名称过滤) DY-->>Main: products[] Main->>DB: 批量查已存在 product_id Main->>DB: 新商品逐条 INSERT douyin_products(offset=0) Main->>Q: 推送 {limitId, productIds[]} end Main->>DB: Limitation.status=success Note over Main,Child: success 写入时,商品评论任务可能尚未开始 Q->>Child: 执行每页商品评论任务 loop 每个 productId loop offset=0..N, pageSize=100 Child->>DY: ProductCommentList(productId,startTime,offset) DY-->>Child: comments + count + has_more loop 每条评论 Child->>DB: 按 shop_code + product_id + comment_id INSERT/UPDATE end end end ``` 商品列表调用固定 `product_type=0`,且代码硬编码了商品名称过滤 `1379758`,因此当前路径不是全店普通商品同步(`console/modules/douyin/Client.php:101-113`)。商品评论只传时间片 `start_time`,未传 `end_time`(`console/modules/douyin/ProductCommentJob.php:26`;`console/modules/douyin/Client.php:26-40`)。`douyin_products.offset` 创建为 0 后,在现有业务代码中既未读取也未更新;每次子任务都从 offset 0 开始(`console/modules/douyin/CommentJob.php:47-55`;`console/modules/douyin/ProductCommentJob.php:20-26`)。 **`success` 的真实语义:** 仅表示商品列表分页完成且每页商品评论消息已尝试入队;不表示商品评论抓取完成。每个商品内部异常被捕获后继续,整个子任务异常也被捕获且不抛出(`console/modules/douyin/ProductCommentJob.php:24-88`),因此队列层可能将实际部分失败视作正常结束。 ## 7. 京东与天猫/淘宝回复流程 ### 7.1 扫描、发送与业务级重试 ```mermaid sequenceDiagram actor Trigger as 人工或未知调度器 participant C as 渠道 ReplyController participant DB as MySQL participant Q as Redis Queue participant J as CommentReplyJob participant API as 渠道回复接口 Trigger->>C: reply/index,可选 shopCode C->>DB: 读取 app_info 和 replay_... Limitation 游标 loop id 升序扫描 id>cursor 且 submit_status=new C->>DB: reply.submit_status=processing C->>Q: 推送 {id, delay=120, limit=5} C->>DB: Limitation.end_time=id, status=success end Q->>J: 执行回复任务 J->>DB: 按 id 读取回复内容和店铺 J->>API: 提交回复(不可逆外部副作用) alt 平台结果被判定成功 J->>DB: retry_count+1, result=响应, submit_status=success else 失败且 retry_count<5 J->>DB: retry_count+1, result=异常/响应, submit_status=processing J->>Q: delay 120 秒后再次推送同一 id else 已达 5 次 J->>DB: retry_count+1, result=异常/响应, submit_status=error end ``` 京东成功条件是响应 `resultCode == 200`,调用参数包括 `commentId`、内容以及可选 `replyId`(`console/modules/jd/CommentReplyJob.php:31-55`;`console/modules/jd/HuFuClient.php:91-109`)。天猫/淘宝成功条件是响应 `is_success` 非空,调用 `TaobaoTraderateExplainAdd(oid, reply)`(`console/modules/tmall/CommentReplyJob.php:33-57`;`console/modules/tmall/Client.php:71-75`)。 回复游标借用 `comments_limitation.end_time` 保存“已扫描到的回复表主键”,`limit_type` 前缀在代码中拼成 `replay_`(不是 `reply_`)(`console/modules/jd/controllers/ReplyController.php:99-118`;天猫同构代码在 `console/modules/tmall/controllers/ReplyController.php:101-120`)。此记录的 `status=success` 仅表示扫描游标已推进,不表示该 ID 回复成功。 ### 7.2 回复记录状态机 ```mermaid stateDiagram-v2 [*] --> new: 上游创建记录(本仓库无创建代码) new --> processing: Controller 入队前先保存 processing --> success: 平台响应满足成功条件 processing --> processing: 失败且 retry_count < 5,延迟再入队 processing --> error: 第 5 次仍失败 success --> success: 人工 restart 可再次发送 success --> processing: 人工 restart 失败且仍可重试 success --> error: 人工 restart 失败且达到上限 error --> success: 人工 restart 成功 error --> error: 人工 restart 失败/次数已达上限 ``` `actionRestart($id)` 只是重新推送 Job,不会把 `submit_status` 改回 `processing`,不会清零 `retry_count`,也不会阻止 `success` 记录再次发送(`console/modules/jd/controllers/ReplyController.php:63-92`;`console/modules/tmall/controllers/ReplyController.php:65-94`)。如果一个 `error` 记录的 `retry_count` 已经达到 5,重启后仅有当前这一次尝试,不会继续延迟重试。 ## 8. 异常流程与当前处理语义 ### 8.1 评论同步异常 ```mermaid flowchart TD A["Job 开始"] --> B{"Limitation / AppInfo / API / DB 是否异常"} B -->|"否"| C["继续分页或派发子任务"] C --> D["主任务写 success"] B -->|"主 Job 异常"| E["catch 输出堆栈并 Yii::error"] E --> F["尝试写 Limitation=error"] F --> G["异常不重新抛出"] G --> H["Queue 可能视作执行完成,不做框架重试"] B -->|"天猫详情/抖音商品子项异常"| I["子 Job catch 后继续或返回"] I --> J["无子任务失败状态、无业务重试"] ``` - 三个评论主 Job 均捕获 `Throwable` 后不重新抛出,通常只把时间片设为 `error`,所以 Yii Queue 自身的失败重试语义没有被业务层利用(`console/modules/jd/CommentJob.php:81-86`;`console/modules/tmall/CommentJob.php:93-98`;`console/modules/douyin/CommentJob.php:71-76`)。 - 如果异常发生在 `$limit` 成功赋值之前,catch 中再次执行 `$limit->end()` 会产生二次错误。历史日志显示一批已不存在的天猫 `limitId` 在消费时触发 `Attempt to read property "limit_type" on null`,随后 `Call to a member function end() on null`(`console/runtime/logs/app.log:19772-19773`)。 - 天猫详情对每个 `oid` 吞掉异常,抖音商品评论对子商品与最外层都吞掉异常;两者都不更新父时间片,也不创建错误记录(`console/modules/tmall/CommenDetailtJob.php:36-65`;`console/modules/douyin/ProductCommentJob.php:24-88`)。 - 历史日志证明 Redis 不可用会在 Controller 推送时直接失败,例如天猫回复入队 `Connection refused`(`console/runtime/logs/app.log:17973`)。评论 Controller 通常已先创建所有时间片,因此可能留下 `new` 记录;回复 Controller 则已先把当前回复保存为 `processing`,见下一节。 ### 8.2 回复异常 - 回复 Job 会将平台异常转为 `{err, trace}`,增加 `retry_count`,并由业务逻辑最多尝试 5 次;不会把异常重新交给 Yii Queue(`console/modules/jd/CommentReplyJob.php:31-65`;`console/modules/tmall/CommentReplyJob.php:33-66`)。 - Controller 的“改为 processing”“推入 Redis”“推进游标”是三个独立操作,没有数据库事务或 outbox。若第一步成功而 Redis 推送失败,记录会卡在 `processing`,后续扫描只找 `new`;日志确有 Redis 推送失败事件(代码:`console/modules/tmall/controllers/ReplyController.php:47-60`;日志:`console/runtime/logs/app.log:17973`)。 - Job 在读取 `$reply` 和账户时未判空且位于 try/catch 之外。遗留队列消息引用已删除记录时直接报错;日志已出现 `Attempt to read property "reply" on null`(`console/modules/tmall/CommentReplyJob.php:28-33`;`console/runtime/logs/app.log:18077`)。 - 天猫历史日志出现响应保存时 `Array to string conversion`,队列将 Job 判为错误(`console/runtime/logs/app.log:15017-15029`)。当前代码已对 `$result` 做 `json_encode`,说明日志反映过较旧代码版本;它仍证明 Redis 中曾存在与代码/表结构演进不同步的任务。 - 京东历史日志记录回复接口 404、500、DNS/响应体异常等故障(脱敏摘要:`console/runtime/logs/app.log:13187`、`16079`、`16185`)。 ## 9. 重试、幂等与并发现状 | 维度 | 当前实现 | 实际语义/缺口 | 证据 | | --- | --- | --- | --- | | 评论框架重试 | 主/子 Job 多数吞异常 | Queue 看不到异常时不会按框架失败机制重试;代码也未实现 `RetryableJobInterface` | `console/modules/jd/CommentJob.php:81-86`;`console/modules/douyin/ProductCommentJob.php:78-88` | | 回复业务重试 | 120 秒固定延迟,累计最多 5 次 | 不是指数退避;错误类型不分类;人工 restart 不重置计数 | `console/modules/jd/CommentReplyJob.php:47-65` | | 评论幂等 | 查询后新增/更新;京东键为店铺+评论,天猫键为店铺+OID,抖音键为店铺+商品+评论 | 这是应用层 check-then-write;仓库无唯一索引迁移证据,并发 Job 仍可能同时插入 | `console/modules/jd/CommentJob.php:45-75`;`console/modules/tmall/CommentJob.php:46-78`;`console/modules/douyin/ProductCommentJob.php:34-70` | | 内容变更判断 | 部分渠道先对顶层数组 `ksort` 再比较 JSON 字符串;天猫未排序 | 嵌套字段顺序或序列化形式变化也可能触发更新;不是领域字段级比较 | `console/modules/jd/CommentJob.php:45-48,65-69`;`console/modules/tmall/CommentJob.php:50,68-72` | | 回复幂等 | 无平台幂等键、发送锁或“平台成功但本地未保存”的恢复协议 | 外部调用成功后进程在 DB 保存前退出,重跑可能重复回复;人工 restart 也可重发成功记录 | `console/modules/jd/CommentReplyJob.php:31-58`;`console/modules/jd/controllers/ReplyController.php:63-84` | | 同步并发互斥 | 创建新批次前仅查询是否已有 `new` | 查询与批量插入无事务/锁;两个调度进程可同时通过检查并创建重叠窗口 | `console/modules/jd/controllers/CommentsController.php:47-79` | | 回复并发抢占 | 先 SELECT 一条 `new`,再 UPDATE `processing` | 无 `FOR UPDATE`、条件更新或事务;多个扫描进程可拿到同一记录并重复入队 | `console/modules/jd/controllers/ReplyController.php:30-58` | | 父子任务一致性 | 父 Job 派发子 Job 后自行 `success` | 子任务失败、丢失或尚未运行不影响父状态 | `console/modules/tmall/CommentJob.php:81-91`;`console/modules/douyin/CommentJob.php:63-69` | | 消息与 DB 生命周期 | 消息只持有数据库主键 | 记录删除或数据库换代后,旧消息无法执行;日志已验证该现象 | `console/modules/tmall/CommentReplyJob.php:28-29`;`console/runtime/logs/app.log:18077` | | 回复扫描游标 | 只扫描 `id > end_time` 的 `new` 记录 | 游标前移后,旧 ID 若被重置为 `new` 不会被自动扫描;只能尝试人工 restart | `console/modules/jd/controllers/ReplyController.php:28-39,53-58` | ## 10. 人工运维与恢复路径 当前可见的运维面全部是 Console 命令,没有管理页面: | 目的 | 代码对应命令 | 当前效果 | 限制/风险 | | --- | --- | --- | --- | | 创建京东同步批次 | `yii jd/comments/fetch [shopCode]` | 创建窗口并延迟入队 | 日志也曾使用过旧命令 `jd/comments`,代码版本可能已变化(`console/runtime/logs/app.log:7150`) | | 创建天猫同步批次 | `yii tmall/comments/tmall [shopCode]` | 创建窗口并立即入队 | 历史日志确认该命令被执行(`console/runtime/logs/app.log:7368`) | | 创建淘宝同步批次 | `yii tmall/comments/tao-bao [shopCode]` | 与天猫同流程,不同账户类型 | 未在日志中确认正式执行记录 | | 创建抖音同步批次 | `yii douyin/comments/fetch [shopCode]` | 创建单窗口并入队 | 历史日志主要是旧/调试命令,正式调度未确认 | | 重派评论时间片 | `yii <channel>/comments/start <limitId>` | 不改状态,直接重新推送主 Job | 缺失 ID 时 Controller 自身会空引用;无防重 | | 扫描京东待回复 | `yii jd/reply [shopCode]` | 读取新记录、置 processing、入队、推进游标 | 历史日志确认有人执行 `jd/reply`(`console/runtime/logs/app.log:12189`) | | 扫描天猫/淘宝待回复 | `yii tmall/reply [shopCode]` | 同上 | 历史日志确认有人执行 `tmall/reply`(`console/runtime/logs/app.log:17973`) | | 重派单条回复 | `yii jd/reply/restart <id>` / `yii tmall/reply/restart <id>` | 找到记录后直接入队 | 不清次数、不检查状态;日志中的京东命令曾因当时代码路由未加载而失败(`console/runtime/logs/app.log:20563`) | | 消费队列 | `yii queue/run` 或由队列内部拉起 `queue/exec` | 执行 Redis 消息 | 仓库无常驻 Worker 管理配置;日志只证明曾人工/外部拉起 | 注意:上述命令是由当前 Controller/action 名称推导的 Yii 路由,不代表生产 Cron 已这样配置。历史日志跨越了多个代码版本,旧命令和旧类名不能作为当前唯一操作手册。 ## 11. 渠道能力对比矩阵 | 维度 | 京东 | 天猫 | 淘宝 | 抖音 | | --- | --- | --- | --- | --- | | 账户类型 | `CHANNEL_JD=2` | `CHANNEL_TMALL=1` | `CHANNEL_TAOBAO=3` | `CHANNEL_DOUYIN=4` | | 评论数据入口 | 京东自建/云中间件 | 淘宝开放平台 | 与天猫共用淘宝开放平台 | 抖店开放平台 | | 同步窗口 | 最近 180 天;当前无覆盖,fallback 30 天/片 | 最近 180 天;当前配置 30 天/片,fallback 3 天/片 | 同天猫 | 最近 180 天;单片 | | 主分页 | page/pageSize=50 | page/pageSize=100 + `has_next` | 同天猫 | 商品 page/pageSize=100 | | 二阶段任务 | 无 | 每页 `oids[]` 补详情 | 同天猫 | 每页 `productIds[]` 抓评论 | | 评论子分页 | 不适用 | 详情逐 OID,无分页 | 同天猫 | offset/pageSize=100 + `has_more` | | 当前抓取范围异常 | 依赖店铺到中间件 appId 映射 | 无额外代码过滤 | 无额外代码过滤 | 商品名称被硬编码过滤;非全店 | | 落库表 | `jd_comments` | `tmall_comments` | 与天猫共表,以 shop_code 区分 | `douyin_products`、`douyin_comments` | | 应用层评论键 | 店铺+comment_id | 店铺+oid | 同天猫 | 店铺+product_id+comment_id | | 回复 | 有 | 有 | 与天猫共用 | 无实现证据 | | 回复表 | `jd_comments_reply` | `tmall_comments_reply` | 与天猫共表 | 无 | | 自动回复重试 | 固定 120 秒,最多 5 次 | 同京东 | 同天猫 | 无 | | 父任务 `success` | 分页完成且评论保存未抛错 | 基础评论完成、详情仅已派发 | 同天猫 | 商品枚举完成、评论仅已派发 | | 子任务结果汇总 | 不适用 | 无 | 无 | 无 | | 人工重派 | 时间片与单回复 | 时间片与单回复 | 时间片与单回复 | 时间片 | ## 12. 已观察到的运行现象 这些是历史证据,不等同于当前仍在发生: - Redis `127.0.0.1:6379 database=1` 曾拒绝连接,导致命令在推送阶段失败(`console/runtime/logs/app.log:7260-7261`、`17973`)。 - 天猫旧队列消息曾因 `limit_type` 格式或字段名不兼容失败;当前 `getAppByLimitId()` 仍无格式校验(日志:`console/runtime/logs/app.log:1`;代码:`console/models/AppInfo.php:80-83`)。 - 抖音曾出现 SDK 返回 `stdClass` 而业务按数组访问、以及平台域名解析失败(`console/runtime/logs/app.log:5315`、`5413`)。 - 京东拉取曾出现 400、403、500、DNS 和非 JSON 200 响应(`console/runtime/logs/app.log:9699`、`10441`、`10743`、`10841`、`11043`)。 - 京东回复曾连续收到 404/500;当时业务任务继续按自身逻辑或旧 Job 逻辑重派(`console/runtime/logs/app.log:13187`、`16079`)。 - 2025-11-28 消费了一批 DB 中已找不到时间片/回复记录的遗留消息(`console/runtime/logs/app.log:18077`、`18191-18193`、`19772-19773`)。这说明队列消息与数据库记录生命周期曾失配。 ## 13. Unknown / Open Questions 以下信息不能从当前仓库可靠确认,重构前需要生产环境、DB 或业务方补证: 1. **正式调度来源与频率:** 仓库无 Cron、Supervisor、systemd、容器编排或 CI 调度定义。谁执行各渠道 fetch/reply 命令、多久一次、是否允许并行,Unknown。 2. **Worker 部署:** 日志证明 `queue/run/exec` 曾执行,但 Worker 数量、进程管理、失败重启策略、Redis 持久化与队列清理规则,Unknown。 3. **回复记录生产方:** 本仓库没有向 `jd_comments_reply` / `tmall_comments_reply` 插入记录的代码。上游系统、字段契约、是否会更新/删除记录、是否预先防重,Unknown。 4. **评论数据消费者:** 本仓库只写渠道表,未发现读取业务评论表的代码。UA PIM 或其他系统如何读取、字段依赖、延迟和完整性 SLA,Unknown。 5. **生产表 DDL:** ActiveRecord 只说明应用预期字段,仓库没有 8 张核心业务表(账户、时间片、三张评论/商品表、两张回复表)的创建迁移。主键、唯一索引、字段实际类型、外键、字符集和现存脏数据,Unknown。 6. **渠道主键语义:** 天猫/淘宝将 `oid` 当评论唯一标识使用;需要确认一个订单行是否可能存在追评、多条评价或字段更新,否则可能覆盖业务对象。 7. **平台分页边界:** 京东以空页结束,未读取总数/hasMore;平台在同步期间新增或排序变化是否导致漏页/重复,Unknown。 8. **时间边界定义:** 窗口首尾是否均包含、连续窗口 `[start,end]` 是否重复边界数据,需依据三方 API 文档或响应样本确认。 9. **抖音硬编码过滤意图:** 商品名称 `1379758` 是临时联调条件还是正式业务范围,Unknown;这直接决定当前抖音数据是否应被认为不完整。 10. **抖音 `offset` 设计:** `douyin_products.offset` 当前未使用。它原本是否用于断点、最后同步位置或平台分页标记,Unknown。 11. **回复成功后的平台语义:** 平台是否支持客户端幂等键、能否查询已回复状态、重复提交会返回什么,Unknown。这决定人工重启和进程崩溃后的重复回复风险能否消解。 12. **历史队列处理:** 2025 日志显示旧类名、旧字段和已删除 DB 记录共存。当前 Redis 是否仍保留旧消息、升级/回滚时如何排空或兼容,Unknown。 13. **异常通知与人工闭环:** 只发现文件日志,没有告警、监控面板、死信表或处理工单。是否有仓库外监控,Unknown。 14. **账号维护入口:** 只发现 `app_info` 读取模型,没有新增/编辑/刷新 Token 的业务代码。账号由谁维护、Token 如何续期,Unknown。 ## 14. 结论:现状中的业务完成边界 当前系统有两套不同的“成功”概念,重构调研时不能混用: | 状态位置 | `success` 当前实际表示 | 不保证什么 | | --- | --- | --- | | 京东评论 `comments_limitation` | 主 Job 的评论分页和逐条保存走完 | 无重复任务、API 时间边界完整、后续数据不变化 | | 天猫/淘宝评论 `comments_limitation` | 基础评论走完,详情 Job 已尝试派发 | 详情任务执行或全部成功 | | 抖音评论 `comments_limitation` | 商品枚举走完,商品评论 Job 已尝试派发 | 评论任务执行或全部成功;更不代表全店,因为存在商品名过滤 | | 回复扫描用 `comments_limitation` | Controller 已将扫描游标推进到该回复 ID | 回复 Job 已执行或平台回复成功 | | `*_comments_reply.submit_status=success` | 某次 Job 收到符合代码判断的成功响应并保存 | 平台幂等、不会被人工再次发送 | 因此,以现有数据判断同步完整性时,只有京东评论时间片的 `success` 相对接近端到端完成;天猫/淘宝、抖音的 `success` 是父任务完成,回复游标的 `success` 是派发进度。当前数据库中没有能够统一证明“所有子任务均成功、所有外部副作用与本地状态一致”的记录。