CRMEB/backend/driver/README.md
2026-09-23 15:15:02 +08:00

127 lines
6.9 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.

# 第三方驱动底座(driver/)
## 一、定位
`driver/` 是三层架构的最底层,只封装**与第三方服务的通信细节**(协议、鉴权、SDK 适配),不承载任何业务决策:
```
modules/(业务扩展层)→ app/(统一管理中台)→ crmeb/(核心底层)→ driver/(第三方驱动底座)
```
- **只能被 `crmeb/services` 调用**,不可反向引用 `app/`、`modules/`
- 驱动包内禁止访问数据库、禁止调用业务 Services,配置由上层注入(`initialize(array $config)`)
- 每个驱动包 = 一个自包含目录(`Driver.php` + `plugin.json`,可选 `Installer.php` + `extend/` SDK 子目录)
## 二、驱动类型总览
| 类型 | 目录 | 职责 | 基类 | 开发指南 |
|------|------|------|------|---------|
| 短信 | `driver/sms/` | 短信发送/模板/记录 | `crmeb\services\sms\BaseSms` | [sms/DEVELOPMENT.md](sms/DEVELOPMENT.md) |
| 支付 | `driver/pay/` | 收款/退款/对账 | `crmeb\services\pay\BasePay` + `PayInterface` | [pay/DEVELOPMENT.md](pay/DEVELOPMENT.md) |
| 上传存储 | `driver/upload/` | 文件存储/域名/区域 | `crmeb\services\upload\BaseUpload` | [upload/DEVELOPMENT.md](upload/DEVELOPMENT.md) |
| 邮件 | `driver/email/` | 邮件发送 | `crmeb\services\email\BaseEmail` + `EmailInterface` | [email/DEVELOPMENT.md](email/DEVELOPMENT.md) |
| 物流查询 | `driver/logistics/` | 快递轨迹查询 | `crmeb\services\logistics\BaseExpress` | [logistics/DEVELOPMENT.md](logistics/DEVELOPMENT.md) |
| 电子面单 | `driver/express/` | 面单打印 | `crmeb\services\express\BaseExpress` | [express/DEVELOPMENT.md](express/DEVELOPMENT.md) |
| 发票 | `driver/invoice/` | 发票开具/下载 | `crmeb\services\invoice\BaseInvoice` | [invoice/DEVELOPMENT.md](invoice/DEVELOPMENT.md) |
| 机器人通知 | `driver/bot/` | 群机器人消息推送 | `crmeb\services\bot\BaseBot` | [bot/DEVELOPMENT.md](bot/DEVELOPMENT.md) |
| 打印 | `driver/printer/` | 云打印机打印 | `crmeb\services\printer\BasePrinter` | [printer/DEVELOPMENT.md](printer/DEVELOPMENT.md) |
| 商品采集 | `driver/copyproduct/` | 第三方平台商品采集 | `crmeb\services\copyproduct\CopyProduct` | [copyproduct/DEVELOPMENT.md](copyproduct/DEVELOPMENT.md) |
## 三、统一扩展框架
### 3.1 驱动注册中心
所有驱动类型通过 `crmeb\services\driver\DriverRegistry` 统一注册(收口原先分散在各 Services 中的重复扫描逻辑):
```php
use crmeb\services\driver\DriverRegistry;
$drivers = DriverRegistry::scan('sms'); // [driver_key => 驱动信息数组]
$driver = DriverRegistry::get('sms', 'aliyun'); // 单驱动信息,不存在返回 null
$class = DriverRegistry::getDriverClass('sms', 'aliyun'); // driver\sms\Aliyun\Driver
```
`scan()` 返回的驱动信息数组包含:`plugin.json` 的 `driver` 子对象全量字段 + 归一化默认值 + `dir_name`(目录名)+ `namespace`(完整命名空间)。同请求内有缓存,不重复扫描磁盘。
### 3.2 plugin.json 清单规范
每个驱动包必须携带 `plugin.json`(双层结构):
```json
{
"key": "driver_key",
"name": "驱动名称",
"description": "驱动功能简述",
"version": "1.0.0",
"author": "CRMEB",
"type": "driver",
"path": "driver/{type}/{DriverName}",
"require": { "crmeb": "^6.0", "php": "^8.0" },
"driver": {
"name": "驱动显示名称",
"driver_key": "driver_key",
"class_name": "Driver",
"icon": "icon-xxx",
"is_builtin": true,
"sort": 100,
"capabilities": [],
"config_fields": [],
"help_doc": {}
}
}
```
- **顶层**:插件元信息,由 Installer 生命周期消费(安装/卸载/升级展示)
- **`driver` 子对象**:驱动全量描述字段,Registry 全量透传不做白名单;标准键(`name`/`class_name`/`icon`/`capabilities`/`config_fields`/`help_doc`/`sort`/`is_builtin`)缺省时由注册中心补默认值;各类型可有扩展字段(如 pay 的 `channel_key`/`pay_type_map`,upload 的 `domain_template`/`bucket_fields`,copyproduct 的 `platforms`)
- `key`(顶层)与 `driver_key`(driver 子对象)必须一致
- 完整字段说明见各类型 DEVELOPMENT.md,通用顶层字段见[短信驱动开发指南](sms/DEVELOPMENT.md#pluginjson-字段说明)
### 3.3 Installer 生命周期(可选)
驱动包需要执行安装态动作(建表、写入字典/配置)时,在包内提供 `Installer.php`,**原位生效**,系统不复制驱动文件到其他目录。
## 四、选型标准
新增第三方对接能力时,按以下顺序判定落位:
### 判定 1:是否属于既有 10 类驱动类型?
**是** → 在对应 `driver/{type}/` 下新增驱动包(如新增聚合支付通道 → `driver/pay/NewPay/`)。
判定特征:
- 能力本质是「换一个服务商做同一件事」(换短信通道、换 OSS 厂商、换支付渠道)
- 上层业务流程不变,只有通信细节变化
- 配置形态为「后台可切换 + 动态表单」(`config_fields` 渲染)
### 判定 2:不属于既有类型,是纯通信对接吗?
**是** → 评估新增驱动类型目录 `driver/{type}/`:
- 仅当它是对接「外部服务协议」且被多个业务方复用时才值得开新类型(如未来的 OCR、风控)
- 单一业务专用、无复用场景 → 不开新类型,放到 `crmeb/services/` 对应服务内实现
**否(涉及业务规则/流程编排)** → 不放 driver 层,改用业务扩展点:
| 需求本质 | 扩展点 | 落位 |
|---------|--------|------|
| 卖什么/库存/运费/履约流程 | 产品类型 | `modules/product/{type}/` |
| 怎么卖/定价/验证/售后规则 | 交易模式 | `modules/trade/{type}/` |
| 用户从哪来/如何识别登录 | 用户渠道 | `modules/channel/{端}/` |
| 抽奖/直播/签到等运营活动 | 营销玩法 | `modules/marketing/{type}/` |
> 简单判据:**写的是"怎么和第三方说话" → driver 层;写的是"业务上怎么办" → modules 扩展点。**
### 判定 3:驱动包内部的取舍
- **SDK 体积大**(如 COS/OBS 扩展)→ 放驱动包内 `extend/` 子目录,与主工程隔离
- **需要安装态动作** → 提供 `Installer.php`(原位生效,禁止文件复制)
- **配置字段** → 全部声明在 `plugin.json` 的 `config_fields`,后台动态渲染;密钥类字段用 `password` 类型;从其他配置自动读取的字段标记 `readonly`
- **能力差异化** → 通过 `capabilities` 声明(如短信的 `template`/`record`,机器人的 `send_markdown`/`send_card`),上层按能力降级而非硬编码判断驱动
## 五、开发流程
1. 在 `driver/{type}/{DriverName}/` 创建目录
2. 编写 `plugin.json`(先抄同类型现有驱动的结构再改)
3. 继承对应基类实现 `Driver.php`
4. 用 `DriverRegistry::scan()` 验证注册
5. 参考各类型 [DEVELOPMENT.md](#二驱动类型总览) 的接口规范与参考实现