Template Starter
受众:开发者 摘要:统一文本模板引擎 Facade。simple engine +
{{var}}/${var}双语法,Repository SPI,Notification / Mail / SMS 集成,后台 CRUD + 预览。
何时使用
| 场景 | 建议 |
|---|---|
| 通知 / 站内信内容 | Notification.send().template("order.paid").variable(...) |
| 邮件模板渲染 | Mail.send(MailMessage.builder().template("mail.welcome")...) |
| 短信框架本地模板 | Sms.send(SmsMessage.builder().template("sms.verify-code")) |
| 短信厂商审核模板 | SmsMessage.templateCode("xxx")(不是 template) |
| 后台运营可维护文案 | template 表 + /api/v1/admin/template/templates |
| 直接渲染一段文本 | Template.renderText(text, vars) |
| Validation / 异常 / 框架提示 | 用 i18n.md,不用 Template |
业务代码用 Template Facade / TemplateManager,不手写 String.replace。
快速开始
spring:
open:
template:
enabled: true
default-engine: simple直接渲染:
String text = Template.renderText(
"Hello {{user.name}}, your code is ${code}.",
Map.of(
"user", Map.of("name", "Alice"),
"code", "9527"));按 code 渲染(从 template 找模板):
TemplateRenderResult result = Template.render(TemplateRenderRequest.builder()
.code("mail.welcome")
.channel(TemplateConstants.CHANNEL_MAIL)
.locale(Locale.SIMPLIFIED_CHINESE)
.variable("username", "Alice")
.build());通道值优先使用 TemplateConstants.CHANNEL_MAIL / CHANNEL_SMS / CHANNEL_DATABASE / CHANNEL_ALL 等常量,避免在业务代码中散落 "mail"、"sms" 这类固定字符串。
Facade API
// 直接渲染文本
String text = Template.renderText(template, variables);
// 按 code 渲染
TemplateRenderResult result = Template.render(TemplateRenderRequest.builder()
.code("mail.welcome")
.channel(TemplateConstants.CHANNEL_MAIL) // 通道常量(mail / sms / database / all)
.locale(Locale.x) // 语言
.variable("k", "v") // 单个变量
.variables(Map.of(...)) // 批量变量
.build());TemplateRenderResult 字段
| 字段 | 说明 |
|---|---|
subject | 渲染后的标题 |
content | 渲染后的正文 |
placeholders | 模板中发现的所有变量名 |
missingVariables | 未提供值的变量名 |
matched | 是否命中仓储模板 |
占位符语法
内置 simple engine 支持两种写法:
{{username}}
${code}简单嵌套变量:
{{user.name}}
${order.no}Map.of(
"user", Map.of("name", "Alice"),
"order", Map.of("no", "SO202605200001"));缺失变量保留原占位符,不删除:渲染结果仍含 {{username}},并在 missingVariables 返回 username。
配置项
spring:
open:
template:
enabled: true
default-engine: simple
default-channel: all # 兜底通道
default-type: text
default-locale: default
database:
enabled: true # Template 后台模板管理| 配置 | 默认值 | 说明 |
|---|---|---|
enabled | true | 启用开关 |
default-engine | simple | 默认引擎 |
default-channel | all | 默认通道 |
default-type | text | 默认类型 |
default-locale | default | 兜底语言 |
spring.open.template.database.enabled | true | 后台管理入口 |
Provider
| Engine | 说明 | 扩展方式 |
|---|---|---|
simple | 内置,支持 {{var}} / ${var} / 简单嵌套 | 默认 |
| 自定义 | 实现 TemplateEngine SPI 注册 Bean | 例如 Pebble / Freemarker |
Repository 扩展:实现 TemplateRepository SPI 注册 Bean。Template Manager 从所有 TemplateRepository 中查找模板。
Template 核心模块默认提供 template 仓储。业务模块优先复用 template,只有独立模板来源才新增 TemplateRepository。
扩展点与源码骨架
模板 starter 的核心对象保持中性,不绑定邮件、短信或通知:
| 类型 | 职责 |
|---|---|
TemplateManager | 统一渲染入口,负责编排模板查找、引擎选择、渲染和变量诊断 |
TemplateRenderRequest | 调用方传入的渲染请求,可按 code 查仓储模板,也可只传内联 subject / content |
TemplateLookup | 传给仓储的查找条件,包含 code、channel、type、原始 locale 和语言候选链 |
TemplateDefinition | 仓储返回的模板快照,屏蔽数据库或第三方模板系统细节 |
TemplateRepository | 模板来源扩展点,Template 核心模块默认接入 template |
TemplateEngine | 渲染引擎扩展点,默认 simple 支持 {{var}} 和 ${var} |
DefaultTemplateManager 的匹配流程是:
- 请求带
code时,构造TemplateLookup并按仓储 Bean 顺序查找模板。 - 语言候选链来自请求 locale、语言短码、配置默认语言和
default。 - 仓储命中后优先使用仓储模板;仓储字段为空时使用请求中的
subject/content兜底。 - 未命中仓储但请求自带内联文本时,仍可直接渲染。
- 未命中仓储且没有内联文本时,抛出模板不存在异常。
- 渲染结果会返回
placeholders和missingVariables,用于后台预览和测试断言。
新增业务模块通常不需要实现自己的仓储;只有模板来源独立于 template 时,才注册 TemplateRepository。
后台模板(template)
| 字段 | 说明 |
|---|---|
code | 模板编码(mail.welcome / order.paid) |
name | 模板名称 |
channel | all / mail / sms / database 等 |
template_type | 默认 text |
engine | 默认 simple |
locale | zh-CN / zh-TW / en-US / default |
subject | 标题(邮件 / 通知用) |
content | 正文 |
variables | 变量说明(JSON 数组) |
enabled | 启用状态 |
version_no | 业务版本号 |
sort_order | 排序 |
唯一约束:code + channel + locale。同一编码可按通道和语言维护多份。
模板匹配规则
按 code 查找时优先级:
code必须相同enabled=true且未删除channel优先匹配请求通道,找不到时用alllocale按请求语言 → 语言码 → 默认语言 →default兜底- 同等条件下优先
version_no更高、sort_order更小、ID 更新
示例(请求 code=order.paid, channel=mail, locale=zh-CN):
1. order.paid + mail + zh-CN
2. order.paid + mail + zh
3. order.paid + mail + default
4. order.paid + all + zh-CN
5. order.paid + all + default后台 API
Template 核心模块提供:
| 方法 | 路径 | 说明 | 权限 |
|---|---|---|---|
GET | /api/v1/admin/template/templates | 分页查询 | template:template:view |
GET | /api/v1/admin/template/templates/{id} | 详情 | template:template:view |
POST | /api/v1/admin/template/templates | 创建 | template:template:create |
PUT | /api/v1/admin/template/templates/{id} | 修改 | template:template:update |
PUT | /api/v1/admin/template/templates/{id}/enabled | 启停 | template:template:update |
DELETE | /api/v1/admin/template/templates/{id} | 删除 | template:template:delete |
DELETE | /api/v1/admin/template/templates | 批量删除(body 传 ID) | template:template:delete |
POST | /api/v1/admin/template/templates/render | 预览渲染 | template:template:view |
分页过滤:code / channel / type / engine / locale / enabled / keyword。
Display 字段
模板分页和详情返回展示字段(按当前 locale 解析):
| 字段 | 说明 |
|---|---|
displayName | 模板名称 |
displaySubject | 模板标题 |
displayContent | 模板正文 |
displayDescription | 模板说明 |
name / subject / content / description 仍是原始定义;后台编辑表单用原始字段,列表 / 预览 / 运营展示用 display 字段。未命中翻译时 display 回退到原文。
创建示例
POST /api/v1/admin/template/templates
Content-Type: application/json
{
"code": "mail.welcome",
"name": "欢迎邮件",
"channel": "mail",
"type": "text",
"engine": "simple",
"locale": "zh-CN",
"subject": "欢迎 {{username}}",
"content": "你好 {{username}},欢迎加入 {{app.name}}。",
"variables": "[{\"name\":\"username\",\"label\":\"用户名\",\"type\":\"string\",\"required\":true}]",
"enabled": true,
"versionNo": 1,
"sortOrder": 0
}预览渲染
POST /api/v1/admin/template/templates/render
Content-Type: application/json
{
"code": "mail.welcome",
"channel": "mail",
"locale": "zh-CN",
"variables": {
"username": "Alice",
"app": { "name": "Sample" }
}
}返回 valid=true 表示无缺失变量。
变量说明(variables 字段)
推荐 JSON 数组格式:
[
{
"name": "username",
"label": "用户名",
"type": "string",
"required": true,
"description": "接收人的显示名称",
"example": "Alice"
},
{
"name": "order.no",
"label": "订单号",
"type": "string",
"required": true,
"example": "SO202605200001"
}
]框架不强制解析 variables schema。它服务后台页面、发送前预览和模板治理。
Notification 集成
Notification.send("database", NotificationMessage.builder()
.template("order.paid")
.variable("orderNo", "SO202605200001")
.recipient(NotificationRecipient.user(1L))
.build());发送前自动渲染。占位符和缺失变量写入消息属性 spring.open.template.missing-variables。
默认不因缺失变量阻断。需要严格模式:
spring:
open:
notification:
template:
fail-on-missing-variables: true通知专用预览:
POST /api/v1/notifications/template-previewMail 集成
存在 TemplateManager Bean 时 Mail manager 在 Provider 发送前渲染标题和正文:
Mail.send(MailMessage.builder()
.to(MailAddress.of("user@example.com"))
.template("mail.welcome")
.variable("username", "Alice")
.build());SMS 集成
重要区分:
| 字段 | 含义 |
|---|---|
SmsMessage.template | 框架本地模板编码,走 template |
SmsMessage.templateCode | 服务商审核后的模板 ID(腾讯云 / 阿里云) |
两者不要混用。
Sms.send(SmsMessage.builder()
.mobile("13800138000")
.template("sms.verify-code") // 框架本地
.templateCode("SMS_123456") // 服务商
.variable("code", "9527")
.build());使用服务商模板时 content 通常不是最终短信正文,由厂商根据 templateCode + templateParam 渲染。框架本地模板适合统一发送前预览、站内信、邮件或不依赖厂商模板的短信场景。
模块边界
| 模块 | 职责 |
|---|---|
spring-open-starter-template | Template Facade / Manager / Engine SPI / Repository SPI / simple engine |
spring-open-core-template | template 后台 CRUD / 权限 / MyBatis-Plus 仓储 / 渲染预览 |
spring-open-starter-notification | 通知发送前模板渲染和缺失变量校验 |
spring-open-starter-mail | 邮件发送前模板渲染 |
spring-open-starter-sms | 短信发送前模板渲染,保留服务商模板编码语义 |
Template 不负责语言资源文件和异常提示,多语言消息用 i18n.md。
开发约定
- 不在业务 Service 中手写
String.replace做模板替换 - 通知 / 邮件 / 短信优先用
template + variables - 新增模板时先在后台
/api/v1/admin/template/templates/render预览 - 变量名保持稳定,避免随意重命名破坏历史模板
- 密钥、验证码明文、token、私钥不要写入模板定义
- 模板内容动态值用变量,不拼接字符串
- 缺失变量在开发期尽早修复;生产严格校验按通道风险决定是否开启
验证命令
./mvnw -pl spring-open-starters/spring-open-starter-template -am test -DskipITs
./mvnw -pl spring-open-core/spring-open-core-template -am -Dtest='Template*Test,DatabaseTemplateRepositoryTest' -Dsurefire.failIfNoSpecifiedTests=false test -DskipITs
./mvnw -pl spring-open-application -am test -DskipITs -DskipFrontend相关
- I18n(多语言文案):i18n.md
- Notification(通知通道):notification.md
- Mail:mail.md
- SMS:sms.md
- 动态业务元数据本地化:
.ai/decisions/adr-029-dynamic-metadata-i18n.md - Provider 扩展模型:provider.md