feat: 添加阿里云短信服务支持并优化验证码逻辑

- 引入 alibabacloud/dysmsapi-20170525 依赖,集成阿里云短信服务
- 将短信验证码从6位改为4位,优化用户体验
- 添加聚水潭商品按时间范围同步功能,支持分页拉取
- 增强Cron任务异常处理,添加错误日志记录
- 修复系统附件服务中图片路径处理逻辑
- 更新阿里云SDK相关组件至最新版本
- 添加think-trace调试扩展,便于问题排查
- 优化跨域中间件,使用str_contains替代strpos
This commit is contained in:
2026-04-30 15:19:40 +08:00
parent 0334fef6c1
commit c7f85ffbda
565 changed files with 59742 additions and 3391 deletions

View File

@@ -0,0 +1,5 @@
- [ ] `route/admin.php` 中正确添加了 `sync_jst_product_by_time` 路由。
- [ ] `StoreProduct::sync_jst_product_by_time` 能够正确接收并校验 `start_time` 和 `end_time` 参数。
- [ ] `HaichaoJstServices::getProductListByTime` 能够正确构建请求参数(包括 `modified_begin` 和 `modified_end`)并返回数据。
- [ ] `ProductSyncErp` 中的商品处理逻辑成功抽离为 `doProcessProductList`,且原有的按 SPU 同步功能未受影响。
- [ ] `ProductSyncErp::productFromErpByTime` 能够正确处理聚水潭的分页返回,直到所有页数处理完毕。

View File

@@ -0,0 +1,31 @@
# 增加聚水潭商品按时间范围同步功能 Spec
## Why
当前系统仅支持通过手动输入 SPU 编码列表的方式来同步聚水潭商品。在实际运营中,管理员往往需要批量同步某段时间内(如今天、本周)在聚水潭中新增或修改的商品。增加按时间范围同步的功能可以大大提升操作效率,避免遗漏。
## What Changes
- 在后台路由配置文件中新增 `sync_jst_product_by_time` 路由。
- 在 `StoreProduct` 控制器中新增 `sync_jst_product_by_time` 方法,用于接收 `start_time`、`end_time` 和 `sync` 参数,并分发同步任务。
- 在 `HaichaoJstServices` 服务类中新增 `getProductListByTime` 方法,封装调用聚水潭 `open/mall/item/query` 接口的时间范围查询逻辑。
- 在 `ProductSyncErp` 任务类中新增 `productFromErpByTime` 方法,支持按页拉取时间范围内的商品。
- **重构**: 将 `ProductSyncErp::productFromErp` 中核心的商品数据转换和保存逻辑抽离为 `doProcessProductList` 方法,以便按 SPU 同步和按时间同步复用相同的处理逻辑。
## Impact
- Affected specs: 聚水潭商品手动同步功能。
- Affected code:
- `route/admin.php`
- `app/controller/admin/v1/product/StoreProduct.php`
- `app/haichao/HaichaoJstServices.php`
- `app/jobs/product/ProductSyncErp.php`
## ADDED Requirements
### Requirement: 按时间范围同步商品
系统 SHALL 提供一个接口,允许管理员传入开始时间和结束时间,自动拉取聚水潭在该时间范围内修改过的商品,并同步到商城系统中。
#### Scenario: 成功发起异步同步任务
- **WHEN** 管理员调用 `/adminapi/product/sync_jst_product_by_time` 接口,传入有效的 `start_time` 和 `end_time`,且 `sync=0`
- **THEN** 系统返回成功响应,并在后台队列中创建对应的同步任务,按页拉取并更新商品数据。
## MODIFIED Requirements
### Requirement: 重构商品同步核心逻辑
为了避免代码重复,现有的按 SPU 同步商品的逻辑 SHALL 被重构。商品数据的映射、价格计算、SKU 组装和入库逻辑 SHALL 抽取为独立的内部方法,供所有同步入口调用。

View File

@@ -0,0 +1,13 @@
# Tasks
- [ ] Task 1: 增加路由和控制器方法
- [ ] SubTask 1.1: 在 `route/admin.php` 中增加 `POST sync_jst_product_by_time` 路由。
- [ ] SubTask 1.2: 在 `app/controller/admin/v1/product/StoreProduct.php` 中实现 `sync_jst_product_by_time` 方法,验证时间参数,并根据 `sync` 参数决定是同步执行还是投递异步队列。
- [ ] Task 2: 在服务类中增加 API 调用方法
- [ ] SubTask 2.1: 在 `app/haichao/HaichaoJstServices.php` 中新增 `getProductListByTime(string $startTime, string $endTime, int $pageIndex = 1, int $pageSize = 50)` 方法。
- [ ] Task 3: 重构同步任务类并实现时间范围同步
- [ ] SubTask 3.1: 在 `app/jobs/product/ProductSyncErp.php` 中,将 `productFromErp` 方法内的商品列表遍历处理逻辑提取为受保护的 `doProcessProductList(array $productList, int $supplierId)` 方法。
- [ ] SubTask 3.2: 修改原 `productFromErp` 方法以调用新抽离的 `doProcessProductList`。
- [ ] SubTask 3.3: 在 `ProductSyncErp.php` 中新增 `productFromErpByTime(string $startTime, string $endTime, int $supplierId = 0)` 方法,实现分页拉取逻辑,并在每页数据返回后调用 `doProcessProductList`。

View File

@@ -0,0 +1,7 @@
- [ ] 已列出回调入口路由与调用链路:`/api/work/serve` -> `Wechat::work()` -> `WechatServices::workServe()` -> `Work::serve()`
- [ ] 已列出 Work 基础配置项与系统配置键映射:`corp_id/token/aes_key` -> `wechat_work_corpid/wechat_work_token/wechat_work_aes_key`
- [ ] 已列出 Work 应用配置项(通讯录/客户联系/自建应用)与系统配置键:`wechat_work_address_secret`、`wechat_work_user_secret`、`wechat_work_build_agent_id`、`wechat_work_build_secret`
- [ ] 已说明 `Failed to decode request contents.` 的触发原因与正确校验方式(企业微信 GET 校验、POST 加密回调)
- [ ] 已给出“域名无法配置”的替代接入方案,并明确透明转发必须保持 query/body 不变
- [ ] 已给出“无法接入回调”的降级方案:使用后台同步成员能力,并标注其删除/全量重建的副作用
- [ ](可选增强)若实现 `wechat_work_callback_url`,后台表单能正确展示并可复制,空值时回退到 `site_url . '/api/work/serve'`

View File

@@ -0,0 +1,76 @@
# 企业微信通讯录配置项使用逻辑与回调域名替代方案 Spec
## Why
现网企业微信通讯录/客户联系能力依赖“接收事件服务器”回调地址(如 `https://shop.douba.top/api/work/serve`)来接收成员/部门/客户等变更事件。实际部署中可能遇到企业微信后台无法填写或无法通过校验(域名限制、HTTPS 证书、网络可达性、网关/WAF 等)导致无法上线。
本 Spec 的目标是:
- 梳理后端企业微信通讯录相关配置项在代码中的读取、组装与使用路径,便于定位配置错误。
- 给出当企业微信侧无法配置项目域名时的可行替代配置方式(运维/网络层方案 + 业务层降级方案)。
- (可选增强)提供独立的“企业微信回调地址”配置项,仅用于展示与复制,避免必须修改全站 `site_url`。
## Current Behavior(现状)
### 1) 回调路由入口
- 企业微信回调入口固定为 `/api/work/serve`,路由映射到 `app\controller\api\v1\wechat\Wechat::work()`,最终调用 `crmeb\services\wechat\Work::serve()`。
- 后台配置表单中“服务器地址”字段为只读展示:`sys_config('site_url') . '/api/work/serve'`。
### 2) 配置项来源与组装
企业微信配置通过两层组装后交给 EasyWeChat:
- **基础配置**(用于验签/解密/加密回复):`corp_id`、`token`、`aes_key`
- 映射到系统配置键:`wechat_work_corpid`、`wechat_work_token`、`wechat_work_aes_key`
- **应用配置**(用于调用不同能力 API):按类型注入 `secret` / `agent_id`
- 通讯录同步:`wechat_work_address_secret`
- 客户联系:`wechat_work_user_secret`
- 自建应用:`wechat_work_build_agent_id` + `wechat_work_build_secret`
### 3) 回调验签与事件处理
- `Work::serve()` 调用 EasyWeChat Work Server 完成 URL 校验与消息解密;解密后消息交给 `app\listener\wechat\WorkListener` 分发。
- `WorkListener` 处理的关键事件包含:
- 通讯录事件:`change_contact`(成员/部门增删改)
- 客户事件:`change_external_contact`
- 客户群事件:`change_external_chat`
- 客户标签事件:`change_external_tag`
## Problems(常见问题)
- 直接在浏览器访问回调 URL 或用普通 HTTP 客户端随意请求,会触发 EasyWeChat 的解析失败,出现 `Failed to decode request contents.`;这不代表系统异常,只是请求不是企业微信回调协议(缺少 `echostr/msg_signature` 或正文不是企业微信 XML/加密格式)。
- 企业微信后台“接收事件服务器”校验失败,常见原因:
- URL 不可公网访问(内网/防火墙/白名单/运营商限制)
- HTTPS 证书链不完整/过期/不被信任
- 反向代理/WAF 改写了 query/body(导致 `msg_signature` 校验失败)
- 回调域名无法在企业微信侧配置(企业侧安全策略或域名限制)
## What Changes(拟新增/调整)
### A. 文档化与定位指引(必做)
- 输出一份“配置项—代码使用点—故障排查清单”的说明(本 Spec + checklist)。
- 明确哪些配置影响“回调校验”,哪些配置只影响“API 调用”。
### B. 回调域名替代方案(不改代码即可落地)
- 允许使用“可在企业微信侧配置的域名/网关”作为回调入口,通过透明转发(反向代理/网关/隧道)转发到实际项目的 `/api/work/serve`。
- 允许在无法接入回调时,使用后台已有的“同步企业微信成员”能力做业务降级(非实时)。
### C. 可选增强:增加独立的回调地址配置项(需要改代码)
新增系统配置键(示例):`wechat_work_callback_url`
- 用途:仅用于后台表单展示/复制,默认值仍为 `site_url . '/api/work/serve'`。
- 不改变实际回调逻辑(仍然由路由 `/api/work/serve` 接收);目的是让运维能把“实际对外可用的回调域名”记录在系统里,减少误配。
## Requirements
### Requirement 1: 配置项可追溯
系统 SHALL 明确以下配置项的用途与作用范围:
- `wechat_work_corpid` / `wechat_work_token` / `wechat_work_aes_key`:影响回调验签解密与企业微信身份
- `wechat_work_address_secret`:影响通讯录同步相关 API(拉部门、拉成员等)
- `wechat_work_user_secret`:影响客户联系相关 API
- `wechat_work_build_agent_id` + `wechat_work_build_secret`:影响自建应用相关 API
### Requirement 2: 当域名无法配置时可替代接入
在企业微信后台无法配置项目域名时,运维 SHALL 能选择以下一种方式完成回调接入:
- 方案 A:配置一个企业微信可接受的域名作为回调入口,并反向代理透明转发到本项目 `/api/work/serve`
- 方案 B:无法接入回调时,使用后台“同步企业微信成员”接口进行人工/定时全量同步作为降级(承认非实时)
### Requirement 3(可选): 回调地址可配置展示
系统 SHOULD 提供 `wechat_work_callback_url` 配置项用于展示与复制,默认回退到 `site_url . '/api/work/serve'`。
## Impact(影响面)
### 不改代码即可采用
- 仅影响运维部署方式与企业微信后台填写的回调 URL。
### 若做可选增强
- 影响后台“企业微信配置”表单展示字段。

View File

@@ -0,0 +1,19 @@
# Tasks
- [ ] Task 1: 梳理配置项与使用链路(文档输出)
- [ ] SubTask 1.1: 汇总企业微信回调入口、路由、控制器与服务调用链路。
- [ ] SubTask 1.2: 汇总企业微信配置项(sys_config 键)与代码读取点、作用范围(回调验签 vs API 调用)。
- [ ] SubTask 1.3: 汇总回调事件类型与本地处理服务(通讯录/客户/客户群/标签)。
- [ ] Task 2: 给出“无法配置域名”的替代接入方式(运维/网络层)
- [ ] SubTask 2.1: 反向代理/网关透明转发方案(保持 query/body 不变,避免验签失败)。
- [ ] SubTask 2.2: 备用公网域名/子域名方案(同服务器多域名证书)。
- [ ] SubTask 2.3: 隧道映射方案(frp/ngrok 类,仅建议测试或临时)。
- [ ] Task 3: 给出“无法接入回调”的业务降级方式(不改代码)
- [ ] SubTask 3.1: 使用后台“同步企业微信成员”接口触发全量同步(说明副作用与适用场景)。
- [ ] SubTask 3.2: 建议定时执行策略(如低频全量同步,或按部门增量同步的可拓展方向)。
- [ ] Task 4(可选增强): 增加独立的回调地址展示配置项
- [ ] SubTask 4.1: 增加系统配置键 `wechat_work_callback_url`(若已有配置表/初始化机制则按既有方式接入)。
- [ ] SubTask 4.2: 修改企业微信配置表单展示字段,优先展示 `wechat_work_callback_url`,空则回退 `site_url . '/api/work/serve'`。