短信管理开发文档
1. 概述
短信管理模块是系统中负责短信发送、配置和管理的核心功能,支持多种短信服务商(牛云短信、阿里云短信、腾讯云短信),提供短信配置管理、模板管理、发送记录查询等功能。
2. 功能模块
2.1 短信配置管理
2.1.1 支持的短信服务商
- 牛云短信:系统内置的短信服务
- 阿里云短信:阿里云提供的短信服务
- 腾讯云短信:腾讯云提供的短信服务
2.1.2 配置参数
| 短信服务商 | 配置参数 | 说明 |
|---|---|---|
| 阿里云短信 | sign | 短信签名 |
| 阿里云短信 | app_key | APP_KEY |
| 阿里云短信 | secret_key | SECRET_KEY |
| 腾讯云短信 | sign | 短信签名 |
| 腾讯云短信 | app_id | APP_ID |
| 腾讯云短信 | secret_id | SECRET_ID |
| 腾讯云短信 | secret_key | SECRET_KEY |
| 牛云短信 | - | 无需配置参数 |
2.1.3 配置流程
- 进入短信配置页面
- 选择要配置的短信服务商
- 填写相应的配置参数
- 保存配置并启用
2.2 短信模板管理
2.2.1 模板列表
- 显示所有短信通知模板
- 支持查看模板详情
- 支持启用/禁用模板
2.2.2 模板类型
- 登录验证
- 注册验证
- 绑定手机
- 找回密码
2.2.3 模板编辑
- 设置模板状态(启用/禁用)
- 配置短信模板ID
- 编辑短信内容
2.3 短信发送记录
2.3.1 发送记录列表
- 显示所有短信发送记录
- 支持按手机号、状态、时间等条件筛选
- 支持查看记录详情
2.3.2 记录详情
- 短信内容
- 发送状态
- 发送时间
- 接收手机号
2.4 牛云短信特殊功能
2.4.1 账户管理
- 注册牛云短信账户
- 登录牛云短信账户
- 修改账户信息
- 重置密码
2.4.2 订单管理
- 查看订单列表
- 计算短信费用
- 创建订单
- 查看订单详情
- 订单状态查询
- 获取支付信息
2.4.3 签名管理
- 查看签名列表
- 查看签名详情
- 创建签名
- 删除签名
3. 代码实现
3.1 路由配置
php
// 短信模块路由配置
Route::group('sms', function () {
// 短信配置列表
Route::get('config', 'sms.Sms/config');
// 短信配置详情
Route::get('config/:sms_type', 'sms.Sms/configDetail');
// 短信配置修改
Route::put('config/:sms_type', 'sms.Sms/editConfig');
// 短信通知消息模版管理
Route::get('notice', 'sms.SmsNotice/lists');
Route::get('notice/:key', 'sms.SmsNotice/info');
Route::post('notice/edit', 'sms.SmsNotice/edit');
Route::post('notice/editstatus', 'sms.SmsNotice/editStatus');
// 短信发送记录
Route::get('log', 'sms.SmsLog/lists');
Route::get('log/:id', 'sms.SmsLog/info');
// 牛云特殊业务
Route::group('niusms', function () {
Route::get('packages', 'sms.NiuSms/getSmsPackageList');
// 发送验证短信
Route::post('send', 'sms.NiuSms/sendMobileCode');
Route::get('captcha', 'sms.NiuSms/captcha');
Route::get('config', 'sms.NiuSms/getConfig');
Route::put('enable', 'sms.NiuSms/enable');
Route::group('account', function () {
Route::post('register', 'sms.NiuSms/registerAccount');
Route::post('login', 'sms.NiuSms/loginAccount');
Route::post('edit/:username', 'sms.NiuSms/editAccount');
Route::post('reset/password/:username', 'sms.NiuSms/resetPassword');
Route::post('forget/password/:username', 'sms.NiuSms/forgetPassword');
Route::get('info/:username', 'sms.NiuSms/accountInfo');
});
Route::group('order', function () {
Route::get('list/:username', 'sms.NiuSms/orderList');
Route::post('calculate/:username', 'sms.NiuSms/calculate');
Route::post('create/:username', 'sms.NiuSms/createOrder');
Route::get('info/:username', 'sms.NiuSms/orderInfo');
Route::get('status/:username', 'sms.NiuSms/orderStatus');
Route::get('pay/:username', 'sms.NiuSms/getPayInfo');
});
Route::group('sign', function () {
Route::get('list/:username', 'sms.NiuSms/signList');
Route::get('info/:username', 'sms.NiuSms/signInfo');
Route::get('report/config', 'sms.NiuSms/signCreateConfig');
Route::post('report/:username', 'sms.NiuSms/signCreate');
Route::post('delete/:username', 'sms.NiuSms/signDelete');
});
});
});3.2 短信配置控制器
php
<?php
namespace app\adminapi\controller\sms;
use app\service\admin\sms\SmsService;
use core\base\BaseAdminController;
use think\Response;
/**
* 短信配置控制器
*/
class Sms extends BaseAdminController
{
/**
* 获取短信配置列表
* @return Response
*/
public function config()
{
$data = (new SmsService())->getList();
return success($data);
}
/**
* 获取短信配置详情
* @param string $sms_type
* @return Response
*/
public function configDetail(string $sms_type)
{
$data = (new SmsService())->getConfig($sms_type);
return success($data);
}
/**
* 短信配置修改
* @param string $sms_type
* @return Response
*/
public function editConfig(string $sms_type)
{
$data = $this->request->only(['is_use', 'params'], 'post');
(new SmsService())->setConfig($sms_type, $data);
return success('SUCCESS');
}
}3.3 短信配置服务
php
<?php
namespace app\service\admin\sms;
use app\dict\common\CommonDict;
use app\dict\sys\ConfigKeyDict;
use app\dict\sys\SmsDict;
use app\service\core\sys\CoreConfigService;
use core\base\BaseAdminService;
use core\exception\AdminException;
/**
* 短信配置服务层
*/
class SmsService extends BaseAdminService
{
public function __construct()
{
parent::__construct();
}
/**
* 获取短信配置列表(平台设置)
*/
public function getList()
{
$sms_type_list = SmsDict::getType();
$info = (new CoreConfigService())->getConfig(ConfigKeyDict::SMS);
if(empty($info) || !isset($info['value']['default']))
{
$config_type = ['default' => ''];//初始化
}else
$config_type = $info['value'];
$list = [];
foreach($sms_type_list as $k => $v)
{
$data = [];
$data['sms_type'] = $k;
$data['is_use'] = $k == $config_type['default'] ? 1 : 0;
$data['name'] = $v['name'];
foreach ($v['params'] as $k_param => $v_param)
{
$value = $config_type[$k][$k_param] ?? '';
$encrypt_params = $sms_type_list[$k]['encrypt_params'] ?? [];
if ($value !== '' && in_array($k_param, $encrypt_params)) $value = CommonDict::ENCRYPT_STR;
$data['params'][$k_param] = [
'name' => $v_param,
'value' => $value
];
}
$data['component'] = $v['component'] ?? '';
$list[] = $data;
}
return $list;
}
/**
* 获取短信配置
* @param string $sms_type
* @return array
*/
public function getConfig(string $sms_type)
{
$sms_type_list = SmsDict::getType();
if(!array_key_exists($sms_type, $sms_type_list)) throw new AdminException('SMS_TYPE_NOT_EXIST');
$info = (new CoreConfigService())->getConfig(ConfigKeyDict::SMS);
if(empty($info) || !isset($info['value']['default']))
{
$config_type = ['default' => ''];//初始化
}else
$config_type = $info['value'];
$data = [
'sms_type' => $sms_type,
'is_use' => $sms_type == $config_type['default'] ? 1 : 0,
'name' => $sms_type_list[$sms_type]['name'],
];
foreach ($sms_type_list[$sms_type]['params'] as $k_param => $v_param)
{
$value = $config_type[$sms_type][$k_param] ?? '';
$encrypt_params = $sms_type_list[$sms_type]['encrypt_params'] ?? [];
if ($value !== '' && in_array($k_param, $encrypt_params)) $value = CommonDict::ENCRYPT_STR;
$data['params'][$k_param] = [
'name' => $v_param,
'value' => $value
];
}
return $data;
}
/**
* 短信配置
* @param string $sms_type
* @param array $data
* @return bool
*/
public function setConfig(string $sms_type, array $data)
{
$sms_type_list = SmsDict::getType();
if(!array_key_exists($sms_type, $sms_type_list)) throw new AdminException('SMS_TYPE_NOT_EXIST');
$info = (new CoreConfigService())->getConfig(ConfigKeyDict::SMS);
if(empty($info) || !isset($info['value']['default']))
{
$config['default'] = '';
}else{
$config = $info['value'];
}
//初始化数据
if($data['is_use'])
{
$config['default'] = $sms_type;
}else{
$config['default'] = $config['default'] == $sms_type ? '' : $config['default'];
}
foreach ($sms_type_list[$sms_type]['params'] as $k_param => $v_param)
{
$value = $data['params'][$k_param] ?? '';
if ($value == CommonDict::ENCRYPT_STR) $value = isset($config[$sms_type]) ? ($config[$sms_type][$k_param] ?? '') : '';
$config[$sms_type][$k_param] = $value;
}
return (new CoreConfigService())->setConfig(ConfigKeyDict::SMS, $config);
}
}3.4 短信通知控制器
php
<?php
namespace app\adminapi\controller\sms;
use app\service\admin\sms\SmsNoticeService;
use core\base\BaseAdminController;
use think\Response;
/**
* 短信通知管理
*/
class SmsNotice extends BaseAdminController
{
/**
* 短信通知列表
* @description 短信通知列表
* @return Response
*/
public function lists()
{
$res = (new SmsNoticeService())->getList();
return success($res);
}
/**
* 短信通知详情
* @description 短信通知详情
* @param $key
* @return Response
*/
public function info($key)
{
$res = (new SmsNoticeService())->getInfo($key);
return success($res);
}
/**
* 短信通知编辑
* @description 短信通知编辑
* @return Response
*/
public function edit()
{
$data = $this->request->params([
['key', ''],
['status', 0],
['sms_id', ''],
['sms_content', ''],
]);
(new SmsNoticeService())->edit($data['key'], $data);
return success();
}
/**
* 短信通知状态修改
* @description 短信通知状态修改
* @return Response
*/
public function editStatus()
{
$data = $this->request->params([
['key', ''],
['status', 0],
]);
(new SmsNoticeService())->editStatus($data['key'], $data['status']);
return success();
}
}3.5 短信通知服务
php
<?php
namespace app\service\admin\sms;
use app\model\sys\SysSmsNotice;
use app\service\core\sms\CoreSmsNoticeService;
use app\service\core\sms\CoreSmsTemplateData;
use core\base\BaseAdminService;
use core\exception\AdminException;
/**
* 短信通知管理服务层
*/
class SmsNoticeService extends BaseAdminService
{
public function __construct()
{
parent::__construct();
$this->model = new SysSmsNotice();
}
/**
* 获取短信通知列表
* @return array
*/
public function getList()
{
return (new CoreSmsNoticeService())->getList();
}
/**
* 获取短信通知详情
* @param string $key
* @return array
*/
public function getInfo(string $key)
{
return (new CoreSmsNoticeService())->getInfo($key);
}
/**
* 修改短信通知状态
* @param string $key
* @param int $status
*/
public function editStatus(string $key, int $status)
{
return (new CoreSmsNoticeService())->edit($key, ['is_sms' => $status]);
}
/**
* 短信通知编辑
* @param string $key
* @param array $data
*/
public function edit(string $key, array $data)
{
$save_data = ['is_sms' => $data['status']];
$save_data['sms_id'] = $data['sms_id'] ?? '';
$save_data['sms_content'] = $data['sms_content'] ?? '';
// 从 CoreSmsTemplateData 获取 param_json 的值
$templates = CoreSmsTemplateData::getSmsTemplates();
if (isset($templates[$key]['param_json'])) {
$save_data['param_json'] = $templates[$key]['param_json'];
// 如果 param_json 是数组,转换为 JSON 字符串
if (is_array($save_data['param_json'])) {
$save_data['param_json'] = json_encode($save_data['param_json'], JSON_UNESCAPED_UNICODE);
}
}
return (new CoreSmsNoticeService())->edit($key, $save_data);
}
}3.6 核心短信通知服务
php
<?php
namespace app\service\core\sms;
use app\model\sys\SysSmsNotice;
use core\base\BaseCoreService;
use app\service\core\sms\CoreSmsTemplateData;
/**
* 核心短信通知服务层
*/
class CoreSmsNoticeService extends BaseCoreService
{
public function __construct()
{
parent::__construct();
$this->model = new SysSmsNotice();
}
/**
* 获取短信通知配置
* @param string $key
* @return array
*/
private function getNotice(string $key = '')
{
$notice = CoreSmsTemplateData::getSmsTemplates();
if (!empty($key)) {
return $notice[$key] ?? [];
}
return $notice;
}
/**
* 获取短信通知列表
* @return array
*/
public function getList()
{
$list = $this->model->select()->toArray();
if (!empty($list)) {
$list_key = array_column($list, 'key');
$list = array_combine($list_key, $list);
}
$notice = $this->getNotice();
$result = [];
foreach ($notice as $k => $v) {
if (array_key_exists($k, $list)) {
$result[] = array_merge($v, $list[$k]);
} else {
$data = [
'is_sms' => 0,
'sms_content' => '',
'sms_id' => '',
'param_json' => ''
];
$result[] = array_merge($v, $data);
}
}
return $result;
}
/**
* 获取短信通知详情
* @param string $key
* @return array
*/
public function getInfo(string $key)
{
$info = $this->model->where([['key', '=', $key]])->find();
if (empty($info)) {
$data = [
'is_sms' => 0,
'sms_content' => '',
'sms_id' => '',
'param_json' => ''
];
return array_merge($this->getNotice($key), $data);
}
return array_merge($this->getNotice($key), $info->toArray());
}
/**
* 编辑短信通知
* @param string $key
* @param array $data
* @return bool
*/
public function edit(string $key, array $data)
{
// 如果 param_json 是数组,转换为 JSON 字符串
if (isset($data['param_json']) && is_array($data['param_json'])) {
$data['param_json'] = json_encode($data['param_json'], JSON_UNESCAPED_UNICODE);
}
$info = $this->model->where([['key', '=', $key]])->find();
if (empty($info)) {
$data['key'] = $key;
$data['create_time'] = time();
$this->model->create($data);
} else {
$this->model->where([['key', '=', $key]])->update($data);
}
return true;
}
/**
* 根据key获取短信通知配置
* @param string $key
* @return array
*/
public function find(string $key)
{
return $this->getInfo($key);
}
}3.7 短信字典
php
<?php
namespace app\dict\sys;
/**
* 短信枚举类
*/
class SmsDict
{
// 阿里云短信
public const ALISMS = 'aliyun';
public const NIUSMS = 'niuyun';
// 腾讯云短信
public const TENCENTSMS = 'tencent';
public const SENDING = 'sending';
public const SUCCESS = 'success';
public const FAIL = 'fail';
public const LOGIN = 'login';
public const REGISTER = 'register';
public const BIND_MOBILE = 'bind_mobile';
public const FIND_PASS = 'find_pass';
public const SCENE_TYPE = [
self::LOGIN,
self::REGISTER,
self::BIND_MOBILE,
self::FIND_PASS
];
public static function getType()
{
$system = [
self::NIUSMS => [
'name' => '牛云短信',
// 配置参数
'params' => [],
'encrypt_params' => [],
'show_type'=>'view',
'view' => '/src/views/setting/sms_niu.vue',
'component' => '',
],
self::ALISMS => [
'name' => '阿里云短信',
// 配置参数
'params' => [
'sign' => '短信签名',
'app_key' => 'APP_KEY',
'secret_key' => 'SECRET_KEY'
],
'encrypt_params' => ['secret_key'],
'show_type'=>'component',
'component' => '/src/views/setting/components/sms-ali.vue',
],
self::TENCENTSMS => [
'name' => '腾讯云短信',
// 配置参数
'params' => [
'sign' => '短信签名',
'app_id' => 'APP_ID',
'secret_id' => 'SECRET_ID',
'secret_key' => 'SECRET_KEY'
],
'encrypt_params' => ['secret_key'],
'show_type'=>'component',
'component' => '/src/views/setting/components/sms-tencent.vue',
],
];
$extend = event('SmsType');
return array_merge($system, ...$extend);
}
// 支持的短信场景
public static function getStatusType()
{
return [
self::SENDING => 'dict_sms.status_sending',
self::SUCCESS => 'dict_sms.status_success',
self::FAIL => 'dict_sms.status_fail',
];
}
// 牛云短信特有的字典方法
// 签名审核状态
public const API_AUDIT_RESULT_WAIT = 1;
public const API_AUDIT_RESULT_PASS = 2;
public const API_AUDIT_RESULT_REFUSE = 3;
// 余额分配类型
public const BALANCE_RECHARGE_ADD = 1;
public const BALANCE_RECHARGE_REDUCE = 2;
// 参数类型
public const PARAMS_TYPE_VALID_CODE = 'valid_code';
public const PARAMS_TYPE_MOBILE_NUMBER = 'mobile_number';
public const PARAMS_TYPE_OTHER_NUMBER = 'other_number';
public const PARAMS_TYPE_AMOUNT = 'amount';
public const PARAMS_TYPE_DATE = 'date';
public const PARAMS_TYPE_CHINESE = 'chinese';
public const PARAMS_TYPE_OTHERS = 'others';
/**
* 获取签名审核状态
* @param string $type
* @return array|string
*/
public static function getSignAuditType(string $type = '')
{
$data = [
self::API_AUDIT_RESULT_WAIT => get_lang('dict_sms_api.sign_audit_status_wait'),
self::API_AUDIT_RESULT_PASS => get_lang('dict_sms_api.sign_audit_status_pass'),
self::API_AUDIT_RESULT_REFUSE => get_lang('dict_sms_api.sign_audit_status_refuse'),
];
return $type ? $data[$type] : $data;
}
/**
* 获取余额分配类型
* @param string $type
* @return array|string
*/
public static function getBalanceAllocateType(string $type = '')
{
$data = [
self::BALANCE_RECHARGE_ADD => get_lang('dict_sms_api.balance_add'),
self::BALANCE_RECHARGE_REDUCE => get_lang('dict_sms_api.balance_reduce'),
];
return $type ? $data[$type] : $data;
}
/**
* 获取签名来源
* @param string $source
* @return array
*/
public static function getSignSource(string $source = '')
{
$data = [
['type' => 1, 'name' => '企业名称'],
['type' => 2, 'name' => '事业单位'],
['type' => 3, 'name' => '商标'],
['type' => 4, 'name' => 'APP'],
['type' => 5, 'name' => '小程序'],
];
return $source ? [] : $data;
}
/**
* 获取签名类型
* @param string $type
* @return array
*/
public static function getSignType(string $type = '')
{
$data = [
['type' => 0, 'name' => '全称'],
['type' => 1, 'name' => '简称'],
];
return $type ? [] : $data;
}
/**
* 获取签名默认设置
* @param string $type
* @return array
*/
public static function getSignDefault(string $type = '')
{
$data = [
['type' => 0, 'name' => '否'],
['type' => 1, 'name' => '是'],
];
return $type ? [] : $data;
}
/**
* 获取API参数类型
* @return array
*/
public static function getApiParamsType()
{
return [
[
'name' => '验证码',
'type' => self::PARAMS_TYPE_VALID_CODE,
'desc' => '4-6位纯数字',
'rule' => '/^\d$/',
'min'=>4,
'max'=>6
],
[
'name' => '手机号',
'type' => self::PARAMS_TYPE_MOBILE_NUMBER,
'desc' => '1-15位纯数字',
'rule' => '/^\d$/',
'min'=>1,
'max'=>15
],
[
'name' => '其他号码',
'type' => self::PARAMS_TYPE_OTHER_NUMBER,
'desc' => '1-32位字母+数字组合',
'rule'=>'/^[a-zA-Z0-9]$/',
'min'=>1,
'max'=>32
],
[
'name' => '金额',
'type' => self::PARAMS_TYPE_AMOUNT,
'desc' => '支持数字或数字的中文 (壹贰叁肆伍陆柒捌玖拾佰仟万亿 圆元整角分厘毫)',
'rule' => "/^(?:\d+|(?:[零壹贰叁肆伍陆柒捌玖拾佰仟万亿圆角分厘毫]+|圆|元|整)+)$/u"
],
[
'name' => '日期',
'type' => self::PARAMS_TYPE_DATE,
'desc' => '符合时间的表达方式 也支持中文:2019年9月3日16时24分35秒',
'rule' => ''
],
[
'name' => '中文',
'type' => self::PARAMS_TYPE_CHINESE,
'desc' => '1-32中文,支持中文园括号()',
'rule' => '/^[\p{Han}()()]$/u',
'min'=>1,
'max'=>32
],
[
'name' => '其他',
'type' => self::PARAMS_TYPE_OTHERS,
'desc' => ' 1-35个中文数字字母组合,支持中文符号和空格',
'rule' => '/^[\p{Han}\p{N}\p{L}\p{P}\p{S}\s]$/u',
'min'=>1,
'max'=>35
],
];
}
}4. 短信发送流程
4.1 配置流程
- 登录后台管理系统
- 进入系统设置 -> 短信设置
- 选择短信服务商并填写配置参数
- 保存配置并启用
4.2 模板配置流程
- 进入短信通知模板管理
- 选择要配置的模板类型
- 填写短信模板ID和内容
- 启用模板
4.3 短信发送流程
- 系统触发短信发送事件(如用户注册、登录验证等)
- 根据事件类型获取对应的短信模板
- 替换模板中的变量(如验证码、用户名等)
- 调用短信服务商的API发送短信
- 记录短信发送状态和结果
5. 数据模型
5.1 短信通知模型
- 表名:
sys_sms_notice - 主要字段:
key:模板键名is_sms:是否启用短信通知sms_id:短信模板IDsms_content:短信内容param_json:参数JSONcreate_time:创建时间update_time:更新时间
5.2 短信发送记录模型
- 表名:
sys_sms_log - 主要字段:
id:记录IDmobile:手机号content:短信内容status:发送状态error_msg:错误信息create_time:发送时间
6. 开发注意事项
6.1 安全考虑
- 短信配置中的密钥信息应加密存储
- 短信验证码应设置有效期
- 防止短信轰炸攻击,限制同一手机号的发送频率
6.2 性能优化
- 短信发送应采用异步处理,避免阻塞主流程
- 对短信发送结果进行缓存,减少重复查询
6.3 错误处理
- 短信发送失败时应进行重试机制
- 记录详细的错误信息,便于排查问题
6.4 最佳实践
- 根据业务场景选择合适的短信服务商
- 合理设置短信模板,确保内容清晰明了
- 定期清理短信发送记录,避免数据量过大
7. 常见问题
7.1 短信发送失败
- 检查短信配置是否正确
- 确认短信模板是否已审核通过
- 检查账户余额是否充足
- 查看发送记录中的错误信息
7.2 短信接收延迟
- 可能是短信服务商的网络延迟
- 检查手机信号是否良好
- 确认短信是否被手机安全软件拦截
7.3 短信验证码不生效
- 检查验证码是否正确
- 确认验证码是否在有效期内
- 检查短信模板中的变量替换是否正确
8. 总结
短信管理模块是系统中重要的通信工具,通过本文档的指导,开发人员可以了解短信管理的核心功能和实现逻辑,从而更好地集成和扩展短信功能。系统支持多种短信服务商,提供了完整的配置管理、模板管理和发送记录功能,满足不同业务场景的需求。
