Files
huangjingfen/docs/license-commercial-auth-compliant-license-dependency-replacement.plan.md
2026-04-30 17:57:03 +08:00

450 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: compliant-license-dependency-replacement
overview: 合规剥离 CRMEB 商业授权基础依赖,使用项目自有基础服务逐步替换 BaseAuth/BaseController/版权接口依赖,不修改、不替换、不绕过商业授权校验文件。
isProject: false
---
# 合规替换方案:剥离商业授权基础依赖
## 目标
在不破解、不绕过、不伪造 CRMEB 商业授权的前提下,把项目运行时对商业加密基础类的依赖逐步迁移到自有实现。迁移完成后:
- 自有业务模块不再依赖 `crmeb\basic\BaseAuth`
- 控制器逐步改为继承自有 `AppBaseController`
- 授权/版权接口只保留合法展示或系统版本能力,不伪造原厂授权状态。
- 商业 CRMEB PRO 代码仅在已购买授权时保留使用;未授权模块冻结或替换。
## 禁止事项
- 不替换 `config/auth.php` 为空文件。
- 不重写 `crmeb/basic/BaseAuth.php` 来模拟原厂授权类。
- 不 patch `Model::maker` 授权闭包。
- 不返回伪造的 `AUTHORIZED`、授权天数、版权购买状态。
- 不删除原厂商业文件来规避授权检查。
## 当前依赖边界
### BaseAuth 依赖点
1. 应用配置
- `config/app.php`
- 当前有 `use crmeb\basic\BaseAuth``'auth_crmeb' => BaseAuth::AUTH_CRMEB`
- 迁移方向:改为自有配置项或删除无业务用途配置。
2. DAO 通用搜索
- `crmeb/traits/SearchDaoTrait.php`
- `BaseAuth::________(array_keys($where), $this->setModel())`
- 迁移方向:替换为自有 `SearchConditionBuilder`,根据模型搜索器方法过滤可用搜索字段。
3. 企业微信相关 DAO 搜索
- `app/dao/work/WorkMemberDao.php`
- `app/dao/work/WorkWelcomeDao.php`
- `app/dao/work/WorkGroupMsgSendResultDao.php`
- `app/dao/work/WorkClientDao.php`
- `app/dao/work/WorkGroupMsgTaskDao.php`
- 当前项目暂不使用该模块功能。
- 迁移方向:放到最后阶段处理,同样使用 `SearchConditionBuilder`
4. 库存扣减/回滚
- `app/dao/BaseDao.php`
- `decStockIncSales()` 调用 `BaseAuth::_____`
- `incStockDecSales()` 调用 `BaseAuth::___`
- 当前项目暂不使用相关下单/库存链路。
- 迁移方向:放到最后阶段处理,新建 `StockMutationService`,用事务和条件更新实现原子库存变更。
5. token 解析
- `app/services/kefu/LoginServices.php`
- `app/services/out/OutAccountServices.php`
- 当前项目暂不使用客服模块,`kefu` 相关 token 解析放到最后阶段处理。
- 迁移方向:复用现有 `crmeb\utils\JwtAuth``CacheService` 模式,封装自有 `AccessTokenService`
### BaseController 依赖点
直接继承 `crmeb\basic\BaseController` 的入口:
- `app/controller/admin/AuthController.php`
- `app/controller/supplier/AuthController.php`
- `app/controller/kefu/AuthController.php`
- `app/controller/kefu/Login.php`
- `app/controller/kefu/Common.php`
- `app/controller/api/v1/Common.php`
- `app/controller/out/OutAccount.php`
间接影响很大:`admin``supplier``kefu` 下大量控制器继承各自 `AuthController`。因此迁移必须先做兼容基类,再切换顶层继承。
当前项目暂不使用客服模块,`kefu` 相关控制器迁移放到最后阶段处理。
### 版权/授权接口
当前涉及:
- `route/admin.php`
- `check_auth`
- `auth_apply`
- `auth`
- `crmeb_copyright`
- `crmeb_verify`
- `crmeb_login`
- `crmeb_order`
- `crmeb_pay`
- `crmeb_product`
- `copyright`
- `app/controller/admin/Common.php`
- `check_auth()`
- `auth()`
- `auth_apply()`
- `saveCopyright()`
- `getCopyright()`
- `crmeb_copyright()`
- `app/controller/api/v1/Common.php`
- `app/controller/kefu/Common.php`
- `app/controller/supplier/Common.php`
迁移方向:删除“购买/申请原厂授权”业务入口,保留自有系统信息接口,例如版本、备案、页脚版权配置。不要返回伪造授权。
## 目标架构
新增自有基础层,避免继续向 `crmeb\basic\BaseAuth``crmeb\basic\BaseController` 扩散:
- `app/common/controller/AppBaseController.php`
- 持有 `Request`
- 提供 `success()` / `fail()`
- 提供 `validate()`
- 调用 `initialize()`
- 不包含原厂授权、版权、远程校验逻辑
- `app/services/auth/AccessTokenService.php`
- `createToken(int $id, string $type, string $authHash, array $extra = [])`
- `parseToken(string $token, string $type, callable $resolver, callable $authHashResolver)`
- 复用 `JwtAuth` 生成与校验 JWT
- 复用 `CacheService` token bucket
- `app/services/dao/SearchConditionBuilder.php`
- 输入:模型类、请求 where keys
- 输出:`[$with, $whereKey]`
- 策略:只允许模型中存在对应 `searchXxxAttr` 搜索器的字段进入 `withSearch`,普通字段进入直接 `where`
- `app/services/product/StockMutationService.php`
- `decreaseStockIncreaseSales($model, array $where, int $num, string $stock, string $sales): bool`
- `increaseStockDecreaseSales($model, array $where, int $num, string $stock, string $sales): bool`
- 要求库存扣减必须带 `$stock >= $num` 条件,防止负库存
- 在订单链路上由外层事务包裹,或内部提供事务版本
- `app/services/system/LocalCopyrightService.php`
- 仅管理本项目自有版权文案和图片
- 来源可以是 `system_config` 或独立配置
- 不表达 CRMEB 原厂授权状态
## 分阶段实施
### 阶段执行规则
每个阶段必须独立完成“修改 → 自动化/手工测试 → 提交”闭环:
1. 阶段内只修改该阶段范围内的文件,不夹带后续阶段改动。
2. 阶段修改完成后先跑自动化检查,再按该阶段 checklist 做手工接口回归。
3. 阶段验收 checklist 全部通过后,单独创建一个或多个语义清晰的 git commit。
4. 未通过测试的阶段不得提交、不得进入下一阶段,不得把多个阶段混在同一个提交里。
5. 若阶段需要拆成多个提交,每个提交都必须保持 `php think` 可启动,核心 smoke 测试不退化。
6. 每个阶段提交前必须在提交说明或 PR 描述中记录测试结果:执行时间、环境、接口、期望结果、实际结果。
每个阶段统一测试记录格式:
- 自动化检查:命令、结果、失败项处理结论。
- 手工接口回归:接口、请求方式、登录身份、关键参数、响应码、关键字段、是否通过。
- 兼容确认:前端页面是否可正常打开,已有 token/session 是否仍可用,失败场景是否返回明确错误。
- 提交确认:本阶段测试全部通过后再提交;提交只包含本阶段文件。
### 阶段 0基线固定
目的:先把现状记录清楚,避免迁移过程中误把授权绕过当成功。
任务:
1. 恢复或保留原始商业加密文件,建立只读备份。
2. 记录当前 `BaseAuth``BaseController`、版权方法引用清单。
3. 给核心接口建 smoke 测试清单:
- admin 登录
- `adminapi/home/header`
- `adminapi/jnotice`
- 商品列表
- 外部接口 token 获取和刷新
4. 客服、企业微信相关 DAO 和库存扣减/回滚链路标记为暂不使用模块,最后阶段迁移前再补充对应回归清单。
验收:
- 清单完整。
- 没有新增绕过授权的改动。
- `php think` 可正常启动或当前已知授权问题被明确记录为基线问题。
- 手工确认当前可访问接口的原始响应,至少记录状态码和关键字段,作为后续阶段对比基线。
- 阶段 0 测试 checklist 通过后单独提交基线记录或测试清单变更。
### 阶段 1替换 token 解析依赖
优先级最高,风险中等,影响边界小。客服模块当前项目暂不使用,客服 token 解析移到最后阶段。
改动:
1. 新建 `app/services/auth/AccessTokenService.php`
2.`app/services/out/OutAccountServices.php::parseToken()``BaseAuth::parseToken()` 改为 `AccessTokenService`
3. 保持 token 格式和缓存 key 与现有 `JwtAuth` / `CacheService` 兼容,避免外部接口调用方重新登录。
4. 仅记录 `app/services/kefu/LoginServices.php::parseToken()` 依赖,最后阶段统一处理。
验收:
- 外部账号可获取 token、刷新 token、访问受保护接口。
- 过期 token、伪造 token、密码变更后的旧 token 均被拒绝。
- 手工回归外部账号接口:登录/获取 token、刷新 token、访问受保护资源、退出或禁用后访问失败。
- 确认 `kefu` token 解析未在本阶段修改,只记录为最后阶段遗留项。
- 后台核心 smoke 接口不受影响。
- 阶段 1 测试 checklist 通过后单独提交。
### 阶段 2替换通用搜索条件构建依赖
优先级高,影响常用列表查询。企业微信相关 DAO 当前项目暂不使用,延后到最后阶段。
改动:
1. 新建 `app/services/dao/SearchConditionBuilder.php`
2. 修改 `crmeb/traits/SearchDaoTrait.php`,移除 `BaseAuth` 调用。
3. 规则保持:
- 模型存在搜索器的字段进入 `$with`
- 无搜索器但 where 值有效的字段进入普通 where
- `timeKey` 等特殊字段保持原逻辑
验收:
- admin 商品、订单、用户、财务列表可按原筛选条件查询。
- 对非法字段不拼接 SQL。
- 手工回归列表筛选:关键词、状态、时间范围、分页、排序、空结果、非法字段请求。
- 企业微信相关 DAO 不在本阶段修改,只记录为最后阶段遗留项。
- 后台核心 smoke 接口不受影响。
- 阶段 2 测试 checklist 通过后单独提交。
### 阶段 3引入自有 AppBaseController
优先级中高,影响面大,必须分入口切换。
改动:
1. 新建 `app/common/controller/AppBaseController.php`
2. 先切换低风险入口:
- `app/controller/out/OutAccount.php`
3. 再切换中风险入口:
- `app/controller/supplier/AuthController.php`
4. 最后切换 admin
- `app/controller/admin/AuthController.php`
5. `app/controller/kefu/AuthController.php``app/controller/kefu/Login.php``app/controller/kefu/Common.php` 当前项目暂不使用,放到最后阶段统一切换。
6. 保持 `success()` / `fail()` 响应结构与 `app('json')` 一致。
7. 保持 `validate()` 行为兼容 ThinkPHP 校验器。
验收:
- admin/supplier/out 三类入口均能正常返回 JSON。
- 中间件注入的 request macro 仍能读取。
- 表单校验错误格式不破坏前端。
- 手工回归 `out` 入口:登录/鉴权、token 失效、核心受保护接口。
- 手工回归 `supplier` 入口:登录/鉴权、订单列表、商品列表、上传。
- 手工回归 `admin` 入口:登录信息、菜单、首页统计、通知、表单校验失败返回。
- 导出、上传、表单生成接口单独回归并记录响应结构。
- 客服控制器不在本阶段切换,只记录为最后阶段遗留项。
- 后台核心 smoke 接口不受影响。
- 阶段 3 测试 checklist 通过后按入口拆分提交,至少 `out``supplier``admin` 分开提交。
### 阶段 4清理版权/授权接口
优先级中,主要是合规清理。
改动:
1. 删除或隐藏 CRMEB 原厂授权申请、授权支付、授权订单、授权产品接口入口。
2. `check_auth` 不再返回伪造授权状态。
3. `auth()` 改为本系统许可状态接口,例如:
- `edition: "custom"`
- `license_source: "self-owned"`
- `crm_pro_authorized: false|true`,仅在确有合法授权凭证时为 true
4. `getCopyright()` 改由 `LocalCopyrightService` 读取本地配置。
5. `saveCopyright()` 只保存自有版权文案/图片。
验收:
- 前端不再展示“申请 CRMEB 授权/购买版权”的入口。
- 页脚版权、系统版本展示正常。
- 不再出现伪造原厂授权字段。
- 手工回归授权/版权相关接口:确认隐藏或返回自有系统信息,不返回伪造原厂授权成功状态。
- 手工回归后台系统设置页、版权保存页、前台页脚展示。
- 后台核心 smoke 接口不受影响。
- 阶段 4 测试 checklist 通过后单独提交。
### 阶段 5移除配置依赖
改动:
1. 修改 `config/app.php`,去掉 `use crmeb\basic\BaseAuth`
2. 删除或替换 `'auth_crmeb' => BaseAuth::AUTH_CRMEB`
3. 全库 `rg "BaseAuth|BaseController|__z6uxy|__qsG|auth_crmeb"`,确认只剩已授权保留模块或无结果。
验收:
- `php think` 能正常启动。
- 后台核心接口 smoke 测试通过。
- 常用自有代码不再引用 `crmeb\basic\BaseAuth`;客服、企业微信 DAO 和库存链路引用记录为最后阶段遗留项。
- 手工回归 admin/supplier/out 核心接口,确认移除配置依赖后不触发基础类授权异常。
- 全库搜索结果必须附在阶段测试记录中,明确剩余引用是否全部属于最后阶段暂不使用模块。
- 阶段 5 测试 checklist 通过后单独提交。
### 阶段 6最后迁移暂不使用模块
当前项目暂不使用客服、企业微信相关 DAO 和库存扣减/回滚链路,因此放到最后处理,避免把高风险低收益改动放进前期上线范围。
#### 6.1 客服模块
当前项目没有使用客服模块,因此该小阶段排在最后阶段内优先级最低。只有当前面所有常用模块迁移、测试、提交完成后,再处理客服相关依赖。
改动:
1.`app/services/kefu/LoginServices.php::parseToken()``BaseAuth::parseToken()` 改为 `AccessTokenService`
2.`app/controller/kefu/AuthController.php``app/controller/kefu/Login.php``app/controller/kefu/Common.php` 切换为自有 `AppBaseController`
3. 保持客服登录、会话、订单、上传图片接口的响应结构兼容现有前端。
验收:
- 客服登录成功后可访问会话列表、订单查询、用户聊天记录、上传图片接口。
- 过期 token、伪造 token、禁用客服账号均被拒绝。
- 未启用客服模块时接口返回明确错误,不触发基础类授权依赖。
- 手工回归客服模块前先确认业务方是否启用;若仍未启用,只验证未启用状态的错误返回和无授权依赖异常。
- 阶段 6.1 测试 checklist 通过后单独提交。
#### 6.2 企业微信相关 DAO 搜索
改动:
1. 修改 5 个 `app/dao/work/*Dao.php` 中的 `BaseAuth::________` 调用。
2. 统一改为 `SearchConditionBuilder`
3. 保持字段过滤策略与阶段 2 一致。
验收:
- 企业微信客户、成员、群发、欢迎语列表可查询。
- 非法字段不拼接 SQL。
- 未启用企业微信配置时接口返回明确错误,不触发基础类授权依赖。
- 阶段 6.2 测试 checklist 通过后单独提交。
#### 6.3 库存扣减/回滚
改动:
1. 新建 `app/services/product/StockMutationService.php`
2. 修改 `app/dao/BaseDao.php::decStockIncSales()``incStockDecSales()`
3. 扣库存逻辑必须满足:
- `$num > 0`
- `where($where)`
- 扣减时追加 `where($stock, '>=', $num)`
- 同一条 SQL 完成 `dec($stock, $num)->inc($sales, $num)->update()`
- 返回更新行数是否大于 0
4. 回滚逻辑必须满足:
- `$num > 0`
- `inc($stock, $num)->dec($sales, $num)`
- 如需防止销量为负,追加 `$sales >= $num`
验收:
- 并发下单不会产生负库存。
- 库存不足返回失败,不创建异常订单。
- 退款/取消订单能正确回滚库存和销量。
- 规格库存、商品总库存、活动库存链路分别验证。
- 阶段 6.3 测试 checklist 通过后单独提交。
## 回滚策略
- 每个阶段单独提交。
- 阶段 1、2、3 可按文件级回滚。
- 阶段 4 是接口/前端合规清理,若前端依赖未同步,先保留兼容字段但不得返回伪造原厂授权。
- 阶段 6 库存链路必须在预发压测通过后上线;失败时回滚 `BaseDao.php``StockMutationService` 引用。
## 测试清单
### 自动化测试建议
- `AccessTokenServiceTest`
- 正常 token 解析
- 过期 token
- 缓存不存在 token
- 密码/secret 变更后旧 token 失效
- `SearchConditionBuilderTest`
- 搜索器字段识别
- 普通 where 字段保留
- 非法字段过滤
- `timeKey` 兼容
- `StockMutationServiceTest`
- 正常扣库存加销量
- 库存不足失败
- 回滚库存减销量
- 并发扣减不负库存
### 手工回归接口
每次阶段提交前至少完成本阶段相关接口回归并把结果记录到阶段测试记录中。记录必须包含接口、请求方式、登录身份、关键参数、HTTP 状态码、业务 `status`、关键响应字段、是否通过。
#### 核心后台 smoke
- `GET /adminapi/login/info`:已登录管理员返回管理员信息;未登录返回明确鉴权失败。
- `GET /adminapi/menusList`:返回菜单列表,菜单结构和权限过滤正常。
- `GET /adminapi/home/header`:返回首页统计卡片,关键字段不缺失。
- `GET /adminapi/home/order`:返回订单统计,时间筛选正常。
- `GET /adminapi/home/user`:返回用户统计,时间筛选正常。
- `GET /adminapi/jnotice`:返回通知列表,不触发授权基础类异常。
#### 列表和搜索回归
- 商品列表:关键词、分类、上下架状态、分页、空结果。
- 订单列表:订单号、用户、状态、时间范围、分页。
- 用户列表:手机号/昵称、标签或等级、状态、分页。
- 财务/资金列表:时间范围、类型、分页、空结果。
- 非法字段请求:不会拼接 SQL不返回 500。
#### 表单、上传和导出
- 商品编辑:读取详情、保存基础信息、保存规格库存。
- 上传接口:图片上传成功,非法文件类型失败。
- 表单生成接口:返回结构兼容前端渲染。
- 导出接口:触发导出任务或返回下载信息,不返回 500。
#### 外部账号和供应商
- 外部账号:获取 token、刷新 token、访问受保护接口、伪造 token 失败、过期 token 失败。
- 供应商:登录、登录信息、商品列表、订单列表、上传图片、无权限访问失败。
#### 暂不使用模块最后回归
- 客服模块:当前项目未使用,最后阶段处理;未启用时确认返回明确错误,不触发基础类授权依赖。
- 企业微信:当前项目未使用,最后阶段处理;未配置时确认返回明确错误,不触发基础类授权依赖。
- 库存链路:最后阶段处理;创建订单、支付成功、取消订单、退款、规格库存、活动库存分别验证。
## 建议提交拆分
1. `feat(auth): add local access token service`
2. `refactor(out): remove BaseAuth token parsing dependency`
3. `feat(dao): add local search condition builder`
4. `refactor(dao): remove BaseAuth search dependency`
5. `feat(controller): add app base controller`
6. `refactor(out): migrate out controller to app base controller`
7. `refactor(supplier): migrate supplier auth controller to app base controller`
8. `refactor(admin): migrate admin auth controller to app base controller`
9. `refactor(license): replace copyright endpoints with local system metadata`
10. `chore(config): remove BaseAuth app config dependency`
11. `refactor(work): remove BaseAuth enterprise wechat dao dependency`
12. `feat(stock): add local stock mutation service`
13. `refactor(dao): remove BaseAuth stock mutation dependency`
14. `refactor(kefu): remove BaseAuth dependencies from unused kefu module`
## 风险点
- `BaseController` 影响面很大,不能一次性全量替换所有控制器文件,只切换顶层入口。
- 库存扣减必须保持原子更新,不能先查库存再保存。
- 搜索条件构建如果放宽字段,可能引入非法查询或 SQL 风险。
- token 解析必须保持缓存令牌桶逻辑,否则会导致登出、过期、禁用账号语义变化。
- 如果继续使用 CRMEB PRO 商业模块,仍需要合法授权;本方案只负责自有业务脱离商业基础依赖。