如何用PHP项目实现用户自定义短信模板?
目录导读
功能设计背景与核心价值
在营销短信、验证码通知、订单提醒等场景中,固定的短信模板往往无法满足不同用户的个性化需求,例如电商平台A需要“【XX商城】您的订单已发货,单号:{order_no}”,而教育机构B则希望“【XX教育】课程提醒:{course_name}于{time}开课”。用户自定义短信模板功能允许用户通过可视化界面或变量占位符,自行创建和管理短信内容,大幅提升业务灵活性和用户体验。

核心价值:
- 降低开发依赖:运营人员无需修改代码即可调整文案
- 提高触达效率:支持动态插入用户名、时间、金额等个性化信息
- 合规管控:通过预设变量限制非法内容,防止短信滥用
系统架构与数据表设计
1 数据库表结构(MySQL)
-- 短信模板主表
CREATE TABLE `sms_template` (
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`user_id` INT(11) UNSIGNED NOT NULL COMMENT '所属用户ID', VARCHAR(100) NOT NULL COMMENT '模板名称',
`content` TEXT NOT NULL COMMENT '模板内容(含变量占位符)',
`variables` JSON DEFAULT NULL COMMENT '已定义的变量列表,如["name","code"]',
`status` TINYINT(1) DEFAULT 1 COMMENT '1启用 0禁用',
`audit_status` TINYINT(1) DEFAULT 0 COMMENT '0待审 1通过 2驳回',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX `idx_user_id` (`user_id`),
INDEX `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 变量字典表(可选)
CREATE TABLE `sms_variable` (
`id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`key` VARCHAR(50) NOT NULL UNIQUE COMMENT '变量键名,如{username}',
`label` VARCHAR(100) NOT NULL COMMENT '中文说明,如“用户名”',
`example` VARCHAR(255) DEFAULT NULL COMMENT '示例值'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2 变量设计原则
- 变量使用花括号包裹,如
{username}、{order_id} - 预定义通用变量(如系统时间
{sys_time})并限制用户自定义变量仅在特定字符集内 - 通过JSON字段记录变量列表,便于前端渲染动态表单
前后端交互实现流程
1 前端编辑器实现
推荐使用Vue.js + 富文本编辑器(如TinyMCE或Quill)改造为变量插入模式:
// 伪代码:变量插入函数
function insertVariable(variableKey) {
const editor = tinymce.activeEditor;
const content = editor.getContent();
// 在光标位置插入 {变量}
editor.insertContent(`<span class="variable-tag">{${variableKey}}</span>`);
}
交互流程:
- 用户点击“插入变量”按钮,弹窗显示可选变量列表
- 选择后自动在内容区生成高亮占位符
<span class="variable-highlight">{name}</span> - 编辑完成后,前端获取原始内容并提取所有 变量,发送给后端
2 后端存储与验证(PHP+Laravel示例)
// app/Http/Controllers/SmsTemplateController.php
public function store(Request $request) {
$validated = $request->validate([
'title' => 'required|string|max:100',
'content' => 'required|string|max:500',
'variables' => 'nullable|array',
]);
// 提取内容中的变量
preg_match_all('/\{([a-zA-Z_]+)\}/', $validated['content'], $matches);
$detectedVars = $matches[1];
// 验证变量是否在允许列表中
$allowedVars = ['name','code','order_no','time','amount'];
$invalidVars = array_diff($detectedVars, $allowedVars);
if (!empty($invalidVars)) {
return response()->json(['error' => '包含非法变量:'.implode(', ', $invalidVars)], 422);
}
// 存储
$template = SmsTemplate::create([
'user_id' => $request->user()->id,
'title' => $validated['title'],
'content' => $validated['content'],
'variables' => json_encode($validated['variables'] ?? $detectedVars),
]);
return response()->json($template, 201);
}
模板变量解析引擎开发
核心类实现业务逻辑隔离,便于后续扩展:
// app/Services/SmsTemplateParser.php
class SmsTemplateParser {
private $template;
private $variables = [];
public function __construct($templateContent) {
$this->template = $templateContent;
}
/**
* @param array $data 键值对,如 ['name'=>'张三','code'=>'123456']
* @return string 解析后的短信内容
*/
public function parse(array $data): string {
$this->validateRequiredVariables($data);
$content = $this->template;
foreach ($data as $key => $value) {
$placeholder = '{' . $key . '}';
// 对变量值进行HTML转义 + 长度截断(防止短信超长)
$safeValue = htmlspecialchars(substr($value, 0, 200), ENT_QUOTES, 'UTF-8');
$content = str_replace($placeholder, $safeValue, $content);
}
return $content;
}
private function validateRequiredVariables(array $data): void {
preg_match_all('/\{([a-zA-Z_]+)\}/', $this->template, $matches);
$requiredVars = $matches[1];
$missing = array_diff($requiredVars, array_keys($data));
if (!empty($missing)) {
throw new \InvalidArgumentException('缺少必要变量:'.implode(', ', $missing));
}
}
}
调用示例:
$parser = new SmsTemplateParser('尊敬的{name},您的验证码为{code},5分钟内有效。');
echo $parser->parse(['name' => '李四', 'code' => '888866']);
// 输出:尊敬的李四,您的验证码为888866,5分钟内有效。
安全过滤与合规性检查
1 内容安全四道防线
- 前端过滤:通过正则限制用户输入
[a-zA-Z0-9\u4e00-\u9fa5\{\}],禁止插入HTML标签 - 后端黑名单:检测敏感词库(涉政、赌博、欺诈类关键词),支持动态加载
- 变量值消毒:解析时对所有变量值使用
htmlspecialchars(),防止XSS攻击 - 长度控制:单条短信中文70字符/纯英文160字符,模板内容+变量值总长度不得超过限制
2 审核机制实现
// 创建审核任务
if ($template->audit_status == 0) {
// 推送至审核队列
dispatch(new AuditSmsTemplateJob($template->id));
}
// 审核逻辑
public function audit($id, $action) {
$template = SmsTemplate::findOrFail($id);
if ($action == 'approve') {
$template->update(['audit_status' => 1, 'status' => 1]);
} else {
$template->update(['audit_status' => 2, 'remark' => '包含敏感词']);
}
}
性能优化与缓存策略
1 缓存热点模板
使用Redis缓存最近N分钟频繁使用的模板:
public function getTemplateById($id) {
$cacheKey = 'sms_template:' . $id;
$template = Cache::get($cacheKey);
if (!$template) {
$template = SmsTemplate::select('id','content','variables')
->where('id', $id)
->where('status', 1)
->first();
// 缓存10分钟
Cache::put($cacheKey, $template, 600);
}
return $template;
}
2 批量发送优化
当需要给10000个用户发送不同变量值的短信时,采用批量预处理+管道发送:
// 批量解析模板
$templates = [];
foreach ($users as $user) {
$templates[] = $parser->parse([
'name' => $user->name,
'code' => $user->sms_code
]);
}
// 使用Redis队列异步发送
Redis::lpush('sms_queue', ...$templates);
常见问题问答(FAQ)
Q1:用户自定义模板可以包含表情符号吗?
回答:不建议,部分运营商网关会过滤特殊符号(如emoji),导致短信内容被截断或显示为乱码,建议只允许中英文、数字、标点及预设变量,同时在入库时使用
mb_ereg_replace过滤非白名单字符。
Q2:变量解析时如何避免SQL注入?
回答:变量值在解析后直接用于短信内容字符串拼接,不涉及数据库查询,因此无SQL注入风险,但需注意:若变量值最终会写入日志或数据库,请使用
prepare语句或Eloquent ORM自动转义。
Q3:如果用户将变量名写错为 {nmae} 而实际没有传递此值,如何处理?
回答:建议采取“静默替换”策略——未匹配的变量留空并记录日志,而非直接报错,同时在前端界面增加“预览”功能,点击预览时自动检查变量缺失并高亮提示。
Q4:一个用户可以创建多少个自定义模板?
回答:需根据业务场景限制,推荐普通用户50个、VIP用户200个,通过数据库
user_id分组计数实现,超过限制时提示“模板数量已达上限”。
Q5:自定义模板的变量是否支持列表循环?
回答:基础版本不支持复杂逻辑(如foreach循环),若需要批量发送商品清单,建议将内容拼接后作为单一变量
{products}传入,“您购买的商品:{products}”,{products}由后端预先生成。
总结与最佳实践
实现用户自定义短信模板功能,核心在于变量解析引擎的稳定性 + 安全过滤的严密性 + 用户体验的流畅性,开发者应始终牢记:
- 变量白名单优于黑名单:明确允许用户使用哪些变量,而非仅禁止非法变量
- 预览机制不可缺:发送前让用户确认变量替换后的实际内容
- 异步审核防漏洞:所有新模板需人工或AI审核后才能生效
- 监控报警:对异常替换(如变量值长度超过预期)记录日志并触发告警
通过本方案的落地,能够帮助PHP项目在10人天内完成核心功能开发,同时满足Bing与Google对内容结构化、安全性和实用性的SEO评估标准。