CRMEB/backend/app/services/driver/sms/SmsDriverServices.php
2026-09-23 15:15:02 +08:00

349 lines
12 KiB
PHP
Raw Permalink 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.

<?php
// +----------------------------------------------------------------------
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
// +----------------------------------------------------------------------
// | Author: CRMEB Team <admin@crmeb.com>
// +----------------------------------------------------------------------
declare(strict_types=1);
namespace app\services\driver\sms;
use app\dao\driver\sms\SmsDriverDao;
use crmeb\basic\BaseService;
use crmeb\exceptions\AdminException;
use crmeb\services\driver\DriverRegistry;
/**
* 短信驱动管理服务
*
* 短信驱动为互斥模式:同一时间只允许一个驱动启用
* 配置值存储在 config_fields JSON 的 value 属性中
*
* Class SmsDriverServices
* @package app\services\driver\sms
* @method array|object|null get($id, ?array $field = []) 获取一条数据
* @method update($id, array $data, ?string $key = null) 修改数据
*/
class SmsDriverServices extends BaseService
{
/**
* 初始化
* @param SmsDriverDao $dao
*/
public function __construct(SmsDriverDao $dao)
{
$this->dao = $dao;
}
/**
* 获取驱动列表(含扫描状态)
* @param array $where
* @return array
*/
public function getList(array $where = []): array
{
$list = $this->dao->selectList($where, '*', 0, 0, 'sort desc,id asc')->toArray();
$scannedDrivers = $this->scanDrivers();
// 为每条记录附加驱动扫描状态
foreach ($list as &$item) {
$driverKey = $item['driver_key'] ?? '';
$item['driver_exists'] = isset($scannedDrivers[$driverKey]);
$item['driver_info'] = $scannedDrivers[$driverKey] ?? null;
}
// 附加数据库中不存在但目录中存在的驱动(未同步的)
$existingKeys = array_column($list, 'driver_key');
foreach ($scannedDrivers as $key => $driver) {
if (!in_array($key, $existingKeys)) {
$list[] = [
'id' => 0,
'driver_key' => $key,
'driver_name' => $driver['name'],
'icon' => $driver['icon'],
'is_builtin' => $driver['is_builtin'],
'config_fields' => $driver['config_fields'],
'sort' => $driver['sort'],
'status' => 0,
'driver_exists' => true,
'driver_info' => $driver,
'not_synced' => true,
];
}
}
return $list;
}
/**
* 获取单条详情(含当前配置值)
* @param int $id
* @return array
*/
public function getDetail(int $id): array
{
$driver = $this->dao->get($id);
if (!$driver) {
throw new AdminException('短信驱动不存在');
}
$data = $driver->toArray();
// 如果数据库中没有 config_fields,从 plugin.json 读取
if (empty($data['config_fields']) && $data['driver_key']) {
$scannedDrivers = $this->scanDrivers();
if (isset($scannedDrivers[$data['driver_key']])) {
$data['config_fields'] = $scannedDrivers[$data['driver_key']]['config_fields'];
}
}
// 一号通驱动:账号/密钥从系统配置自动读取,标记为只读
if ($data['driver_key'] === 'yihaotong') {
foreach ($data['config_fields'] as &$field) {
if (in_array($field['field'], ['yihaotong_appid', 'yihaotong_appsecret'])) {
$field['readonly'] = true;
if ($field['field'] === 'yihaotong_appid') {
$field['value'] = sys_config('yihaotong_appid', '');
} else {
$field['value'] = sys_config('yihaotong_appsecret', '');
}
}
}
}
// 附加驱动扫描信息
$scannedDrivers = $this->scanDrivers();
$data['driver_info'] = $scannedDrivers[$data['driver_key']] ?? null;
return $data;
}
/**
* 同步驱动信息:扫描 driver/sms/ 目录,将 plugin.json 信息写入数据库
* @return array ['added' => int, 'updated' => int]
*/
public function syncDrivers(): array
{
$scannedDrivers = $this->scanDrivers();
$added = 0;
$updated = 0;
$deleted = 0;
foreach ($scannedDrivers as $driverKey => $info) {
$existing = $this->dao->getByDriverKey($driverKey);
if ($existing) {
// 已存在:更新基本信息但保留 config_fields 中的配置值和状态
$configFields = is_array($existing->config_fields) ? $existing->config_fields : [];
// 用 plugin.json 的字段定义更新,但保留已有的 value
$newConfigFields = $info['config_fields'];
foreach ($newConfigFields as &$newField) {
$fieldName = $newField['field'];
foreach ($configFields as $oldField) {
if (($oldField['field'] ?? '') === $fieldName && isset($oldField['value'])) {
$newField['value'] = $oldField['value'];
break;
}
}
}
$this->dao->update($existing->id, [
'driver_name' => $info['name'],
'icon' => $info['icon'],
'is_builtin' => $info['is_builtin'] ? 1 : 0,
'sort' => $info['sort'],
'config_fields' => $newConfigFields,
]);
$updated++;
} else {
// 新增:内置驱动默认禁用
$this->dao->save([
'driver_key' => $driverKey,
'driver_name' => $info['name'],
'icon' => $info['icon'],
'is_builtin' => $info['is_builtin'] ? 1 : 0,
'sort' => $info['sort'],
'config_fields' => $info['config_fields'],
'status' => 0,
]);
$added++;
}
}
// 删除已不存在的驱动(目录已被移除)
$dbKeys = $this->dao->getColumn([], 'driver_key');
foreach ($dbKeys as $key) {
if (!isset($scannedDrivers[$key])) {
$this->dao->delete(['driver_key' => $key]);
$deleted++;
}
}
return ['added' => $added, 'updated' => $updated, 'deleted' => $deleted];
}
/**
* 修改驱动状态(互斥模式:启用一个驱动时禁用其他所有驱动)
* @param int $id
* @param int $status
* @return bool
*/
public function updateStatus(int $id, int $status): bool
{
$driver = $this->dao->get($id);
if (!$driver) {
throw new AdminException('短信驱动不存在');
}
if ($status === 1) {
// 启用当前驱动前,先禁用所有其他驱动(互斥模式)
$this->dao->disableAllExcept($id);
}
$this->dao->update($id, ['status' => $status]);
return true;
}
/**
* 保存驱动配置
* @param int $id
* @param array $data
* @return bool
*/
public function saveConfig(int $id, array $data): bool
{
$driver = $this->dao->get($id);
if (!$driver) {
throw new AdminException('短信驱动不存在');
}
// 获取当前 config_fields 定义
$configFields = is_array($driver->config_fields) ? $driver->config_fields : [];
if (empty($configFields)) {
$scannedDrivers = $this->scanDrivers();
$configFields = $scannedDrivers[$driver->driver_key]['config_fields'] ?? [];
}
// 更新配置值到 config_fields 的 value 属性
foreach ($configFields as &$field) {
$fieldName = $field['field'];
if (isset($data[$fieldName])) {
// 一号通 appid/appsecret 为系统全局配置,不允许通过驱动编辑修改
if ($driver->driver_key === 'yihaotong' && in_array($fieldName, ['yihaotong_appid', 'yihaotong_appsecret'])) {
continue;
}
$field['value'] = $data[$fieldName];
}
}
// 配置值存储到 eb_driver 表的 config_fields JSON 中
$this->dao->update($id, [
'config_fields' => $configFields,
]);
return true;
}
/**
* 批量排序
* @param array $sortData
* @return bool
*/
public function setSort(array $sortData): bool
{
foreach ($sortData as $item) {
if (isset($item['id']) && isset($item['sort'])) {
$this->dao->update((int)$item['id'], ['sort' => (int)$item['sort']]);
}
}
return true;
}
/**
* 获取已扫描的驱动列表
* @return array
*/
public function getDrivers(): array
{
return array_values($this->scanDrivers());
}
/**
* 获取驱动配置字段定义
* @param string $driverKey
* @return array
*/
public function getConfigFields(string $driverKey): array
{
$scannedDrivers = $this->scanDrivers();
return $scannedDrivers[$driverKey]['config_fields'] ?? [];
}
/**
* 获取当前启用的驱动
* @return array|null
*/
public function getEnabledDriver(): ?array
{
$driver = $this->dao->getActiveDriver();
if (!$driver) {
return null;
}
$data = $driver->toArray();
// 一号通驱动:yihaotong_appid/yihaotong_appsecret 存储在系统配置表 sys_config 中
// 但 getActiveDriver() 返回的 config_fields.value 为空(只读字段不允许通过驱动编辑保存)
// 因此需要从 sys_config() 补充填充
if ($data['driver_key'] === 'yihaotong') {
$configFields = is_array($data['config_fields']) ? $data['config_fields'] : [];
foreach ($configFields as &$field) {
if (in_array($field['field'], ['yihaotong_appid', 'yihaotong_appsecret'])) {
if ($field['field'] === 'yihaotong_appid') {
$field['value'] = sys_config('yihaotong_appid', '');
} else {
$field['value'] = sys_config('yihaotong_appsecret', '');
}
}
}
$data['config_fields'] = $configFields;
}
return $data;
}
/**
* 获取用户端可用的短信驱动列表
* @return array
*/
public function getAvailableDrivers(): array
{
$list = $this->dao->getAllEnabled();
$result = [];
foreach ($list as $driver) {
$data = $driver->toArray();
$result[] = [
'driver_key' => $data['driver_key'],
'name' => $data['driver_name'],
'icon' => $data['icon'],
];
}
return $result;
}
/**
* 扫描 driver/sms/(第三方扩展 + 内置驱动 + 插件安装的驱动)
* @return array
*/
protected function scanDrivers(): array
{
// 统一走驱动注册中心(读取 plugin.json 元数据,带缓存与标准键归一化)
return DriverRegistry::scan('sms');
}
}