跳到正文
☰
UA
评论中间件现状
重构前事实基线
审计日期 2026-08-12
◐
正在渲染文档...
此文档需要启用 JavaScript 才能渲染 Markdown 和 Mermaid 图。
# 现状梳理:代码分层与依赖 > 审计日期:2026-08-12 > 证据状态:**源码确认**。结论来自当前目录内源码、配置、迁移、依赖锁文件和测试;未连接运行中的 MySQL、Redis 或第三方平台,因此部署方式、实际调度命令及生产数据约束仍待运行环境确认。 > 统计口径:业务代码统计仅计 `console/modules/{jd,tmall,douyin}` 下非 `sdk/` 的 PHP 文件;工程代码统计排除 `vendor/`、各模块 `sdk/`、`runtime/` 和生成产物。SDK 同时列出全部文件数、PHP 文件数和 PHP 行数,`vendor` 另行统计;行数含注释和空行。 > 安全说明:本文不展示配置中的凭据值、令牌、店铺标识或日志内业务数据。 ## 1. 结论摘要 当前仓库沿用了 Yii 2 Advanced Template 的 `common/frontend/backend/console` 名义多应用结构,但可由源码确认的评论业务全部集中于 `console/modules`。`frontend`、`common` 仍是模板账户、登录和展示代码;`backend` 当前副本缺少基础应用配置与 Controller,无法证实任何可运行的评论后台能力。因此这些目录不能直接视为评论系统的前台、后台或领域共享层。模板来源与 frontend 默认页面可由 `README.md:5-16`、`frontend/views/site/index.php:5-14` 确认,backend 交付不完整则可由其入口仍要求缺失的配置文件确认(`backend/web/index.php:11-18`)。 业务代码有渠道目录边界和 `Controller -> Queue Job -> Client / ActiveRecord` 的基本形状,但没有独立应用服务、领域模型、Repository、统一渠道契约或消息契约。Controller 负责编排和持久化同步窗口,Job 同时负责用例编排、分页、数据转换、幂等查重、写库、状态推进、异常处理和再次投递,Client 同时负责凭据绑定、SDK/HTTP 请求构造及平台响应解释。因而当前更准确的描述是“按渠道分包的脚本式队列应用”,不是严格的分层架构。 最重要的依赖特征是:几乎所有业务对象都可直接触达 Yii 全局容器、默认 DB、Redis Queue、其他 ActiveRecord 和第三方 API。单个 Job 无法在不启动 Yii、准备数据库并处理 SDK 全局类的情况下进行纯单元测试。 ## 2. 仓库目录与业务占比 ### 2.1 目录职责 | 目录 | 当前实际职责 | 规模(文件 / 代码行) | 评论业务占比判断 | 证据 | | --- | --- | ---: | --- | --- | | 根目录 | Yii 启动脚本、Composer 依赖声明、环境初始化入口;README 仍为原始模板说明 | 不与子目录重复计数 | 业务入口仅 console CLI;无领域说明或运维编排 | `yii:1-24`、`composer.json:2-4`、`README.md:24-60` | | `common/` | Yii 别名、共享缓存、模板用户/登录/邮件及其测试 | 19 文件 / 657 行 | 未发现评论业务代码 | `common/config/bootstrap.php:2-5`、`common/config/main.php:7-11`、`common/models/User.php:26` | | `frontend/` | Yii 模板站点:注册、登录、联系、密码重置、模板页面 | 47 文件 / 2,193 行 | 0%;未调用三渠道模块 | `frontend/controllers/SiteController.php:22-102`、`frontend/views/site/index.php:5-14` | | `backend/` | 当前副本仅见 Web 入口、测试骨架和部分静态/模板残留;入口要求的 `backend/config/main.php`、`main-local.php` 及业务 Controller 均不在当前副本中 | 11 代码文件 / 422 行 | 评论业务能力不可证实;属于交付不完整状态,不应据此推断生产后台 | `backend/web/index.php:11-18`、`README.md:39-47` | | `console/` | 唯一真实业务应用:渠道命令、Redis Queue、同步/回复 Job、渠道 Client、ActiveRecord、迁移和日志 | 排除 SDK/runtime 后 37 文件 / 2,677 行 | 核心业务所在;其中渠道业务 24 文件 / 1,922 行,占该口径约 71.8% | `console/config/main.php:10-51` | | `environments/` | Yii 初始化时复制 dev/prod local 配置、入口文件并设置权限 | 38 文件 / 406 行 | 部署模板,不承载业务逻辑 | `environments/index.php:31-67` | | `vendor/` | Composer 安装的 Yii、Queue、Redis、Guzzle、测试等第三方包;当前被直接放入项目目录 | 5,679 文件 / 约 208 万行 | 非业务代码,不应按业务规模计入 | `common/config/main.php:7`、`composer.json:16-38` | 说明:文件统计包含目录内所有普通文件,代码行统计为 PHP/JS/CSS/JSON/YAML/Shell 等文本代码。`frontend` 行数较多不代表其具有评论业务能力,源码仍是 Yii 模板。 ### 2.2 三渠道业务与 SDK | 渠道 | 自有业务代码 | 自有代码占三渠道比例 | 内嵌 SDK(全部文件 / PHP 文件 / PHP 行) | SDK 接入方式 | 现有能力 | | --- | ---: | ---: | ---: | --- | --- | | 京东 | 8 文件 / 684 行 | 35.6% | 14 / 10 / 1,918 | 签名 SDK 通过 Composer `psr-4` 和 `files` 注册 | 评论同步、评论回复,经京东云中间件 HTTP API | | 天猫/淘宝 | 9 文件 / 699 行 | 36.4% | 1,150 / 1,149 / 91,689 | 业务文件直接 `require_once sdk/TopSdk.php`,使用全局类 | 评论列表、评论详情、评论回复 | | 抖音 | 7 文件 / 539 行 | 28.0% | 859 / 859 / 26,567 | `Client.php` 直接 `require_once sdk/autoload.php` | 商品列表、按商品评论同步;无回复链 | 证据:京东自动加载配置见 `composer.json:40-46`;天猫直接加载 SDK 见 `console/modules/tmall/CommentJob.php:3-11` 和 `console/modules/tmall/Client.php:3-6`;抖音直接加载 SDK 见 `console/modules/douyin/Client.php:1-8`。三套 SDK 合计 2,023 个全部文件,其中 2,018 个 PHP 文件、120,174 行 PHP;PHP 行数约为三渠道自有 PHP 代码的 62.5 倍。该比例反映维护/审计负担,不代表业务价值。 ## 3. 当前代码层次与依赖 ### 3.1 真实分层图 ```mermaid flowchart TB trigger["人工或外部 Cron 调用 yii 命令"] controllers["入口与部分编排层<br/>渠道 Console Controllers"] jobs["队列任务兼编排层<br/>Comment / Detail / Product / Reply Jobs"] clients["渠道适配层<br/>JD HuFuClient / Tmall Client / Douyin Client"] models["ActiveRecord 持久化模型<br/>AppInfo / Limitation / 渠道评论与回复"] queue["Yii Redis Queue"] db["Yii 默认 DB / MySQL"] sdk["内嵌渠道 SDK / Guzzle / 签名 SDK"] api["京东中间件 / 淘宝开放平台 / 抖店开放平台"] global["Yii 全局应用与容器<br/>Yii::$app / Yii::$container / params / log"] trigger --> controllers controllers --> models controllers --> queue queue --> jobs jobs --> models jobs --> clients jobs --> queue clients --> models clients --> sdk sdk --> api clients --> api models --> db controllers -.隐式依赖.-> global jobs -.隐式依赖.-> global clients -.隐式依赖.-> global global --> queue global --> db ``` Queue、日志和三个渠道模块均注册在 console 应用配置中(`console/config/main.php:10-51`)。Controller 从 `Yii::$app->queue` 取 Queue(如 `console/modules/jd/controllers/CommentsController.php:15-18`);Job 从 `Yii::$container` 取 Client(如 `console/modules/jd/CommentJob.php:18-21`);ActiveRecord 隐式使用 Yii 默认 DB。京东 Client 还直接从全局 `params` 读取基础地址和签名配置(`console/modules/jd/HuFuClient.php:25-35`)。 ### 3.2 核心调用链 | 用例 | 当前调用链 | 状态与副作用落点 | 证据 | | --- | --- | --- | --- | | 京东评论同步 | `jd/comments/fetch` -> `CommentsController::createLimit` -> Redis `CommentJob` -> `HuFuClient::getComments` -> `jd_comments` | Controller 创建 `comments_limitation`;Job 分页逐条 upsert 并置 success/error | `console/modules/jd/controllers/CommentsController.php:28-82`、`console/modules/jd/CommentJob.php:23-86` | | 天猫/淘宝评论同步 | `tmall/comments/tmall` 或 `tao-bao` -> 创建多个窗口 -> `CommentJob` -> `Client::getComments` -> `tmall_comments` -> 为每页派发 `CommenDetailtJob` -> `Client::getCommentDetail` -> 回写 detail | 父 Job 在子详情 Job 完成前就将窗口置 success;详情 Job 逐 oid 自行吞异常 | `console/modules/tmall/controllers/CommentsController.php:28-96`、`console/modules/tmall/CommentJob.php:37-97`、`console/modules/tmall/CommenDetailtJob.php:27-65` | | 抖音评论同步 | `douyin/comments/fetch` -> 创建单窗口 -> `CommentJob` -> `Client::getProducts` -> `douyin_products` -> 每页派发 `ProductCommentJob` -> `Client::getProductComments` -> `douyin_comments` | 父 Job 派发完商品评论子 Job 后即置 success;子 Job 对每个商品独立吞异常 | `console/modules/douyin/controllers/CommentsController.php:42-80`、`console/modules/douyin/CommentJob.php:22-75`、`console/modules/douyin/ProductCommentJob.php:12-88` | | 京东回复 | `jd/reply/index` -> 扫描 `jd_comments_reply(new)` -> 先置 processing -> Redis `CommentReplyJob` -> `HuFuClient::replyComment` -> 更新结果/重试次数/状态 -> 失败则延迟自投递 | `comments_limitation.end_time` 被复用作回复表 ID 游标;Controller 与投递间无事务 | `console/modules/jd/controllers/ReplyController.php:21-59`、`console/modules/jd/CommentReplyJob.php:23-66` | | 天猫/淘宝回复 | `tmall/reply/index` -> 扫描 `tmall_comments_reply(new)` -> 先置 processing -> Redis `CommentReplyJob` -> `Client::replyComment` -> 更新结果/重试次数/状态 -> 失败则延迟自投递 | 与京东回复结构近乎复制,共用 `replay_` 前缀的窗口游标方案 | `console/modules/tmall/controllers/ReplyController.php:21-61`、`console/modules/tmall/CommentReplyJob.php:26-67` | | 抖音调试调用 | `douyin/comments/index` -> 容器取 Client -> 固定店铺/商品参数 -> SDK | 直接请求平台并输出结果;属于生产模块中的调试入口 | `console/modules/douyin/controllers/CommentsController.php:21-31` | ## 4. 按逻辑层检查实际职责 | 逻辑层 | 已有实现 | 实际承担的职责 | 混入/越层情况 | | --- | --- | --- | --- | | 入口层 | 三渠道 console Controller | 解析命令参数、查店铺、创建同步窗口、拆时间片、扫描回复、写回复状态/游标、投递队列、人工重启 | Controller 已承担应用编排和部分数据一致性职责;JD/Tmall 的窗口创建几乎重复(`console/modules/jd/controllers/CommentsController.php:47-82`、`console/modules/tmall/controllers/CommentsController.php:61-96`) | | 编排层 | 没有独立类;散落在 Controller 和 Job | 决定时间范围、分页、父子任务、重试、完成状态 | 父子任务完成语义由 Job 临时决定;没有一个对象拥有完整用例状态 | | 队列任务层 | 6 个 Job | 消费消息,同时查询账户和窗口、调用平台、规范化 JSON、查重、增改记录、记日志、改状态、自我重投 | Job 是最重的一层。比如 JD `CommentJob::execute` 从查询上下文一直做到逐条持久化和终态更新(`console/modules/jd/CommentJob.php:23-86`) | | 渠道适配层 | `HuFuClient`、Tmall `Client`、Douyin `Client` | 绑定 `AppInfo` 凭据、构造请求、调用 SDK/HTTP、解释各平台成功码和返回结构 | Client 接受 ActiveRecord 而非独立配置;京东直接读全局 params;错误返回类型/结构没有统一契约(`console/modules/jd/HuFuClient.php:25-45`、`console/modules/tmall/Client.php:22-41`、`console/modules/douyin/Client.php:18-55`) | | 持久化层 | 8 个业务 ActiveRecord 类可由源码确认 | 表映射、验证规则;`AppInfo` 还负责渠道枚举、复合 ID 编解码和账户查询;`Limitation` 还负责格式化时间及终态写入 | ActiveRecord 同时扮演实体、查询服务、配置载体和状态对象(`console/models/AppInfo.php:17-97`、`console/models/Limitation.php:17-77`) | | 基础设施层 | Yii Application、Redis Queue、默认 DB、FileTarget、Composer vendor、内嵌 SDK | 对象创建、持久化、异步执行、日志、HTTP/签名/平台协议 | 通过静态全局入口直接暴露给上层;基础设施接口未被隔离(`console/config/main.php:13-45`) | ## 5. 模块依赖矩阵 图例:`D` 直接依赖;`I` 通过框架/ActiveRecord 隐式依赖;`-` 未发现。这里的“其他业务模型”指模块自身主模型之外的 `AppInfo`、`Limitation` 或另一业务表模型。 | 模块/类组 | Yii 全局应用/容器 | DB | Redis Queue | 渠道 SDK/HTTP 库 | 外部 API | 其他业务模型 | 关键证据 | | --- | :---: | :---: | :---: | :---: | :---: | :---: | --- | | JD `CommentsController` | D | I | D | - | - | D | `console/modules/jd/controllers/CommentsController.php:15-38` | | JD `ReplyController` | D | I | D | - | - | D | `console/modules/jd/controllers/ReplyController.php:16-58` | | JD `CommentJob` | D | I | 间接消费 | - | 经 Client | D | `console/modules/jd/CommentJob.php:18-32`、`console/modules/jd/CommentJob.php:49-85` | | JD `CommentReplyJob` | D | I | D(失败再投递) | - | 经 Client | D | `console/modules/jd/CommentReplyJob.php:18-32`、`console/modules/jd/CommentReplyJob.php:47-65` | | JD `HuFuClient` | D | - | - | D(Guzzle + JdcloudSign) | D(中间件) | D(AppInfo) | `console/modules/jd/HuFuClient.php:25-45`、`console/modules/jd/HuFuClient.php:112-146` | | Tmall `CommentsController` | D | I | D | - | - | D | `console/modules/tmall/controllers/CommentsController.php:15-50`、`console/modules/tmall/controllers/CommentsController.php:61-90` | | Tmall `ReplyController` | D | I | D | - | - | D | `console/modules/tmall/controllers/ReplyController.php:16-60` | | Tmall `CommentJob` | D | I | D(子任务) | D(静态加载) | 经 Client | D | `console/modules/tmall/CommentJob.php:3-24`、`console/modules/tmall/CommentJob.php:33-97` | | Tmall `CommenDetailtJob` | D | I | 间接消费 | D(静态加载) | 经 Client | D | `console/modules/tmall/CommenDetailtJob.php:3-24`、`console/modules/tmall/CommenDetailtJob.php:31-64` | | Tmall `CommentReplyJob` | D | I | D(失败再投递) | D(静态加载) | 经 Client | D | `console/modules/tmall/CommentReplyJob.php:3-34`、`console/modules/tmall/CommentReplyJob.php:49-66` | | Tmall `Client` | - | - | - | D(Top SDK 全局类) | D | D(AppInfo) | `console/modules/tmall/Client.php:3-28`、`console/modules/tmall/Client.php:31-100` | | Douyin `CommentsController` | D | I | D | 经调试 Client | 经调试 Client | D | `console/modules/douyin/controllers/CommentsController.php:16-53` | | Douyin `CommentJob` | D | I | D(子任务) | - | 经 Client | D | `console/modules/douyin/CommentJob.php:17-29`、`console/modules/douyin/CommentJob.php:41-75` | | Douyin `ProductCommentJob` | 继承获得 Client;D 日志 | I | 间接消费 | - | 经 Client | D | `console/modules/douyin/ProductCommentJob.php:8-17`、`console/modules/douyin/ProductCommentJob.php:34-87` | | Douyin `Client` | - | - | - | D(抖店 SDK 全局类) | D | D(AppInfo) | `console/modules/douyin/Client.php:6-23`、`console/modules/douyin/Client.php:26-55`、`console/modules/douyin/Client.php:96-144` | | `AppInfo` / `Limitation` / 渠道 AR | I | I | - | - | - | `AppInfo`/`Limitation` 含额外领域逻辑 | `console/models/AppInfo.php:17-97`、`console/models/Limitation.php:17-77` | ## 6. 主要分层问题 ### 6.1 跨层耦合与隐式全局依赖 - Controller、Job、Client 均通过 `Yii::$app`、`Yii::$container`、`Yii::error` 获取依赖或产生副作用,构造函数和方法签名不能表达完整依赖。例:`console/modules/jd/CommentJob.php:18-21`、`console/modules/jd/HuFuClient.php:25-35`。 - Job 消息只携带可变数据库记录 ID,执行时再解析 `Limitation.limit_type` 并查询 `AppInfo`(`console/modules/jd/CommentJob.php:16-32`)。消息协议依赖数据库记录仍存在、复合字符串格式稳定、对应账户仍可查到。 - `AppInfo::getLimitId()` 用 `Channel>channel_type>shop_code` 字符串编码跨层上下文,`getAppByLimitId()` 用无校验 `explode` 反解(`console/models/AppInfo.php:71-83`);数据库字段同时成了消息契约。 - Client 接收完整 `AppInfo` ActiveRecord 并保留可变账户状态(JD:`console/modules/jd/HuFuClient.php:37-45`;Tmall:`console/modules/tmall/Client.php:18-28`;Douyin:`console/modules/douyin/Client.php:14-23`)。若容器复用实例,渠道上下文由调用顺序隐式决定。 ### 6.2 复制代码 - JD 与 Tmall `CommentsController::createLimit()` 均实现“检测 new -> 回溯 180 天 -> 按配置切片 -> 逐条保存”,只有配置键、默认天数和投递节奏不同(`console/modules/jd/controllers/CommentsController.php:47-82`、`console/modules/tmall/controllers/CommentsController.php:61-96`)。 - JD 与 Tmall `ReplyController` 的扫描、游标、processing 标记、投递、restart 和 `getLimit()` 结构几乎相同(`console/modules/jd/controllers/ReplyController.php:21-121`、`console/modules/tmall/controllers/ReplyController.php:21-123`)。 - JD、Tmall、Douyin 评论 Job 均重复逐条 `findOne -> new/setAttributes/save 或比较 JSON/update`,只是业务键和响应字段不同(`console/modules/jd/CommentJob.php:45-75`、`console/modules/tmall/CommentJob.php:46-78`、`console/modules/douyin/ProductCommentJob.php:34-70`)。 - JD 与 Tmall 回复 Job 重复“调用 -> 解释成功 -> 记录完整异常和 trace -> retry_count -> 状态 -> 自投递”流程(`console/modules/jd/CommentReplyJob.php:31-65`、`console/modules/tmall/CommentReplyJob.php:33-66`)。 ### 6.3 命名与类型不一致 - `CommenDetailtJob` 同时缺少 `t` 且多出 `t`,类名已经进入序列化队列消息,简单改名会影响旧消息反序列化(`console/modules/tmall/CommenDetailtJob.php:13`、`console/modules/tmall/CommentJob.php:81-84`)。 - 回复窗口前缀拼为 `replay_` 而业务含义应是 reply;该字符串已经存入 `comments_limitation.limit_type`(`console/modules/jd/controllers/ReplyController.php:99-105`、`console/modules/tmall/controllers/ReplyController.php:101-107`)。 - `AppInfo::getApps()` 文档和实现按单个 `$channelType` 描述,但 Tmall 回复传入数组。Yii 查询可能接受数组值,但方法契约没有表达这一点(`console/models/AppInfo.php:86-97`、`console/modules/tmall/controllers/ReplyController.php:21-24`)。 - Tmall `Comments` 注释把 `shop_code`、`product_id` 描述为整数,规则却将 `shop_code` 作为 string,实际表名又是 `tmall_comments` 而类注释写 `tm_comments`(`console/modules/tmall/models/Comments.php:7-16`、`console/modules/tmall/models/Comments.php:24-37`)。 - Douyin 的 `appSecrete` 拼写与其他渠道 `appSecret` 不一致(`console/modules/douyin/Client.php:10-22`);`getproductComments` 调用与声明的 `getProductComments` 大小写不一致(`console/modules/douyin/ProductCommentJob.php:26`、`console/modules/douyin/Client.php:26`)。PHP 当前方法名大小写不敏感,但静态分析和跨语言迁移会暴露问题。 - ID 类型不统一:JD 评论 ID 按最长 64 的字符串验证,Douyin 评论/商品 ID 按 integer,Tmall oid 也按 integer(`console/modules/jd/models/Comments.php:30-37`、`console/modules/douyin/models/Comments.php:30-35`、`console/modules/tmall/models/Comments.php:32-37`)。平台长 ID 的跨环境宽度和前导零语义未被统一约束。 ### 6.4 可测试性 - 业务类没有接口边界,Client 在 Job `init()` 内从全局容器解析,DB 查询与保存直接写在 `execute()` 内,时间来自 `time()`,队列重投直接使用传入 Queue。这要求测试同时替换全局容器、ActiveRecord/DB、时钟、Queue 和 SDK,无法做轻量纯单元测试。 - Tmall 与 Douyin SDK 通过 `require_once` 暴露全局类,而不是 Composer PSR-4 类型边界(`console/modules/tmall/Client.php:5`、`console/modules/douyin/Client.php:6`),Mock 和类加载隔离更困难。 - 仓库测试只覆盖 Yii 模板账户/表单;搜索未发现对 `AppInfo`、`Limitation`、三渠道 Controller/Job/Client 或队列流程的业务测试。现有渠道目录中的测试属于 SDK 自带测试,而非本项目契约测试。 - 业务表没有完整迁移:现有首个迁移只建模板 `user` 表(`console/migrations/m130524_201442_init.php:15-26`),评论相关迁移只向两张既存回复表加字段(`console/migrations/m251114_094210_add_column_comment_reply_table.php:13-18`)。测试环境无法由仓库稳定重建业务 schema。 ### 6.5 版本、构建与运行现状 - `composer.json` 仍声明项目为 Yii Advanced Template,PHP 下限为 `>=7.0`,依赖 Yii 2、SwiftMailer、Queue、Redis 和 Guzzle(`composer.json:2-4`、`composer.json:16-27`)。锁文件实际版本为 Yii 2.0.45、Queue 2.3.7、Redis 2.0.20、Guzzle 7.4.5、SwiftMailer bridge 2.1.3(`composer.lock:328-329`、`composer.lock:1858-1859`、`composer.lock:2135-2136`、`composer.lock:2252-2253`、`composer.lock:2326-2327`)。 - 唯一 Dockerfile 使用 PHP 7.4 Apache 并只调整 frontend document root(`frontend/Dockerfile:1-3`);它没有表达 console Worker、Redis、DB 或调度器的完整运行拓扑。 - Queue 在 console 配置中以 Redis 驱动注册并设置 4 小时 TTR;基础配置中的 Redis 连接块被注释,当前 local 配置才给出 localhost/database 1(`console/config/main.php:25-37`、`console/config/main-local.php:5-16`)。部署正确性依赖未文档化的 local 文件覆盖顺序。 - 未发现仓库级 CI、Supervisor/systemd、Cron、docker-compose 或 Makefile。console 启动脚本默认 `YII_ENV=dev` 且 `YII_DEBUG=true`(`yii:7-8`)。 - 当前工作目录不是 Git 仓库,无法从这里确认版本历史、变更归属、分支策略或 SDK 来源版本。 ## 7. 已有、名义与缺失层次 | 分类 | 层次 | 现状判断 | | --- | --- | --- | | 已有层次 | Console 命令入口 | 三渠道均有可调用 Controller;JD/Tmall 另有回复入口 | | 已有层次 | Redis 异步任务 | 已有父任务、部分子任务、延迟重投和 Queue 日志行为 | | 已有层次 | 渠道 API 封装 | 每渠道有 Client,将具体 SDK/HTTP 细节从大部分 Job 中抽离 | | 已有层次 | ActiveRecord 表映射 | 已对账户、窗口、评论、商品、回复表建立类映射和基础规则 | | 名义层次 | `frontend/backend/common` 多应用分层 | 源自 Yii 模板;frontend/common 未承载评论业务,backend 当前交付不完整、业务能力不可证实 | | 名义层次 | `console/modules/{channel}` 模块边界 | 有目录和命名空间隔离,但通过共享 AR、全局容器、默认 DB 和 Queue 横向贯穿 | | 名义层次 | `models` 持久化层 | 不仅映射表,还负责查询服务、标识编解码、状态变更和时间格式化 | | 缺失层次 | 应用服务/用例编排 | 没有统一拥有“一次同步/一次回复”完整生命周期的对象 | | 缺失层次 | 领域模型与统一数据契约 | 没有渠道无关 Comment、Reply、SyncRun、Page/Cursor、Result/Error 类型 | | 缺失层次 | Repository / Unit of Work | 数据访问、事务、批量 upsert、并发抢占均直接散落在 Controller/Job | | 缺失层次 | 渠道 Gateway 接口 | 三个 Client 没有共同接口,成功码、分页和错误语义直接泄漏到 Job | | 缺失层次 | 明确消息契约 | Queue 任务类公共属性即消息结构,没有版本、校验或旧消息兼容层 | | 缺失层次 | 可观测性与运行层 | 没有结构化业务指标、追踪 ID、Worker/调度声明、死信与运行手册 | | 缺失层次 | 业务测试层 | 没有同步、分页、幂等、重试、父子任务或平台响应契约测试 | ## 8. 对后续重构的约束(非详细方案) 1. **先保护现有外部与数据契约。** Queue 中序列化的是具体 Job 类名和公共属性,数据库又保存复合 `limit_type`;类名、命名空间、字段和编码格式不能无兼容策略直接修改。证据见 `console/modules/tmall/CommentJob.php:81-84`、`console/models/AppInfo.php:71-83`。 2. **按业务调用链切分,而不是按 Yii 目录搬文件。** 评论同步、详情补全、商品枚举、回复发送具有不同完成语义;现有 `frontend/backend/common` 不能直接当作目标层次依据。 3. **保持渠道差异可见。** JD 经自建中间件,Tmall/淘宝有列表加详情两阶段,Douyin 是商品加评论两阶段;抽取共性时不能抹平分页、成功码和父子任务差异。 4. **先建立可重放证据再移动职责。** 当前没有业务测试且 schema 不完整,重构前至少需要固定典型响应、数据库前后状态、任务消息及失败重试行为,否则无法判断“等价”。 5. **把全局依赖视为迁移风险。** `Yii::$app`、`Yii::$container`、ActiveRecord 默认 DB、静态 SDK 全局类及 `time()` 都需要在迁移过程中逐步显式化,不能假设现有类可直接单测。 6. **SDK 与业务代码分开治理。** 三渠道 SDK 规模远大于业务代码、加载方式又不一致;依赖升级、来源确认和安全扫描应有独立清单,避免与业务重写混成一次不可验证的大变更。 7. **数据库迁移是重构前置证据。** 当前源码只能说明 ActiveRecord 期待哪些字段,不能证明生产表索引、唯一约束、默认值和字符集;必须先从真实库导出并脱敏核对,本文不据模型注释推断完整 schema。 ## 9. 待确认项 - 生产环境实际由什么 Cron/调度平台调用哪些 console 命令、频率和并发度如何。 - Redis Queue 的 Worker 启动参数、进程数、失败/重试/清理策略,以及是否仍有旧类名消息。 - 8 张业务表的真实 DDL、索引、唯一键、数据量与关联完整性;当前迁移不足以证明这些信息。 - `backend` 缺少基础配置/Controller 是否为不完整交付,还是生产部署中从仓库外注入。 - 内嵌 Tmall/Douyin SDK 的确切来源、版本、授权和生成方式。 - `douyin/comments/index` 固定参数调试入口是否仍被运维使用;在确认前不能直接删除。