1. 概述
短信管理模块是系统中负责短信发送、配置和管理的核心功能,支持多种短信服务商(牛云短信、阿里云短信、腾讯云短信),提供短信配置管理、模板管理、发送记录查询等功能。
2. 功能模块
2.1 短信配置管理
2.1.1 支持的短信服务商
- 牛云短信:系统内置的短信服务
- 阿里云短信:阿里云提供的短信服务
- 腾讯云短信:腾讯云提供的短信服务
2.1.2 配置参数
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 路由配置
// 短信模块路由配置 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 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 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 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 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 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 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. 总结
短信管理模块是系统中重要的通信工具,通过本文档的指导,开发人员可以了解短信管理的核心功能和实现逻辑,从而更好地集成和扩展短信功能。系统支持多种短信服务商,提供了完整的配置管理、模板管理和发送记录功能,满足不同业务场景的需求。