跳到内容

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

快速开始

yaml
spring:
  open:
    template:
      enabled: true
      default-engine: simple

直接渲染:

java
String text = Template.renderText(
    "Hello {{user.name}}, your code is ${code}.",
    Map.of(
        "user", Map.of("name", "Alice"),
        "code", "9527"));

按 code 渲染(从 template 找模板):

java
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

java
// 直接渲染文本
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 支持两种写法:

text
{{username}}
${code}

简单嵌套变量:

text
{{user.name}}
${order.no}
java
Map.of(
    "user", Map.of("name", "Alice"),
    "order", Map.of("no", "SO202605200001"));

缺失变量保留原占位符,不删除:渲染结果仍含 {{username}},并在 missingVariables 返回 username

配置项

yaml
spring:
  open:
    template:
      enabled: true
      default-engine: simple
      default-channel: all              # 兜底通道
      default-type: text
      default-locale: default
      database:
        enabled: true                   # Template 后台模板管理
配置默认值说明
enabledtrue启用开关
default-enginesimple默认引擎
default-channelall默认通道
default-typetext默认类型
default-localedefault兜底语言
spring.open.template.database.enabledtrue后台管理入口

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 的匹配流程是:

  1. 请求带 code 时,构造 TemplateLookup 并按仓储 Bean 顺序查找模板。
  2. 语言候选链来自请求 locale、语言短码、配置默认语言和 default
  3. 仓储命中后优先使用仓储模板;仓储字段为空时使用请求中的 subject / content 兜底。
  4. 未命中仓储但请求自带内联文本时,仍可直接渲染。
  5. 未命中仓储且没有内联文本时,抛出模板不存在异常。
  6. 渲染结果会返回 placeholdersmissingVariables,用于后台预览和测试断言。

新增业务模块通常不需要实现自己的仓储;只有模板来源独立于 template 时,才注册 TemplateRepository

后台模板(template

字段说明
code模板编码(mail.welcome / order.paid
name模板名称
channelall / mail / sms / database
template_type默认 text
engine默认 simple
localezh-CN / zh-TW / en-US / default
subject标题(邮件 / 通知用)
content正文
variables变量说明(JSON 数组)
enabled启用状态
version_no业务版本号
sort_order排序

唯一约束code + channel + locale。同一编码可按通道和语言维护多份。

模板匹配规则

code 查找时优先级:

  1. code 必须相同
  2. enabled=true 且未删除
  3. channel 优先匹配请求通道,找不到时用 all
  4. locale 按请求语言 → 语言码 → 默认语言 → default 兜底
  5. 同等条件下优先 version_no 更高、sort_order 更小、ID 更新

示例(请求 code=order.paid, channel=mail, locale=zh-CN):

text
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 回退到原文。

创建示例

http
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
}

预览渲染

http
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 数组格式:

json
[
  {
    "name": "username",
    "label": "用户名",
    "type": "string",
    "required": true,
    "description": "接收人的显示名称",
    "example": "Alice"
  },
  {
    "name": "order.no",
    "label": "订单号",
    "type": "string",
    "required": true,
    "example": "SO202605200001"
  }
]

框架强制解析 variables schema。它服务后台页面、发送前预览和模板治理。

Notification 集成

java
Notification.send("database", NotificationMessage.builder()
    .template("order.paid")
    .variable("orderNo", "SO202605200001")
    .recipient(NotificationRecipient.user(1L))
    .build());

发送前自动渲染。占位符和缺失变量写入消息属性 spring.open.template.missing-variables

默认因缺失变量阻断。需要严格模式:

yaml
spring:
  open:
    notification:
      template:
        fail-on-missing-variables: true

通知专用预览:

http
POST /api/v1/notifications/template-preview

Mail 集成

存在 TemplateManager Bean 时 Mail manager 在 Provider 发送前渲染标题和正文:

java
Mail.send(MailMessage.builder()
    .to(MailAddress.of("user@example.com"))
    .template("mail.welcome")
    .variable("username", "Alice")
    .build());

SMS 集成

重要区分

字段含义
SmsMessage.template框架本地模板编码,走 template
SmsMessage.templateCode服务商审核后的模板 ID(腾讯云 / 阿里云)

两者不要混用

java
Sms.send(SmsMessage.builder()
    .mobile("13800138000")
    .template("sms.verify-code")              // 框架本地
    .templateCode("SMS_123456")               // 服务商
    .variable("code", "9527")
    .build());

使用服务商模板时 content 通常不是最终短信正文,由厂商根据 templateCode + templateParam 渲染。框架本地模板适合统一发送前预览、站内信、邮件或不依赖厂商模板的短信场景。

模块边界

模块职责
spring-open-starter-templateTemplate Facade / Manager / Engine SPI / Repository SPI / simple engine
spring-open-core-templatetemplate 后台 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、私钥不要写入模板定义
  • 模板内容动态值用变量,拼接字符串
  • 缺失变量在开发期尽早修复;生产严格校验按通道风险决定是否开启

验证命令

bash
./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

相关

Released under the Apache License 2.0.