在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
最近,在项目中,需要调用用户提供的接口发送微信模板信息,然后提供了信息模板
{
"PersonnelType": "Inspector",
"BusinessSysID": "0002",
"UserID": "1111",
"Mobile":"",
"UnionID":"",
"SendTemplateModel": {
"touser": "",
"template_id": "MVSBZ77wG2Ml-Qa6IEqks8kRhc48E8MsjzO7P8j36HU",
"url": "http://www.baidu.com",
"miniProgram": "",
"data": {
"first": {
"value": "您有【1】台【压力容器】【定期(全面)检验】任务",
"color": "#ff0"
},
"keyword1": {
"value": "杭州****有限公司",
"color": "blue"
},
"keyword2": {
"value": "黄平\n联系时间:2019-01-30",
"color": ""
},
"keyword3": {
"value": "*******",
"color": ""
},
"keyword4": {
"value": "*******",
"color": ""
},
"remark": {
"value": "详情请见业务系统中的我的任务。",
"color": "#c2c2c2"
}
}
}
}
其中PersonnelType
为Inspector
,BusinessSysID
、UserID
必填,而且SendTemplateModel
、PersonnelType
、template_id
等必填不能为空
腐败代码的味道
在上面代码里面看出
1、大量使用了Map,一方面导致代码的可读性差
2、调用方法不能复用,复用性较差
流程图
定义模板方法
参考发现的模板信息中,data部分则是业务需要实现的模块,其中First
为必填,Remark
选填,KeyWord
部分不确定
- 我们定义了一个抽象类,其中 getFirst(),getRemark(),getKeyWordData(),为抽象方法,需要业务具体实现
- 重写SendTemplateModel中data参数的set方法,必须传入BaseTemplateDataInfo类
public void setData(BaseTemplateDataInfo templateDataInfo) {
if (this.data == null) {
this.data = new HashMap(16);
}
//对first 模块进行校验
WeChatUniformTemplate first = templateDataInfo.getFirst();
if (first == null || StringUtils.isEmpty(first.getValue())) {
throw new JeecgBootException("微信公众号模板消息中first的模板内容(必填)");
}
data.put("first", templateDataInfo.getFirst());
data.put("remark", templateDataInfo.getRemark());
data.putAll(templateDataInfo.getKeyWordData());
}
源码内容
public abstract class BaseTemplateDataInfo {
/**
* BaseTemplateDataInfo::getFirst
* <p>TO:获取信息模板中first模板的信息
* <p>HISTORY: 2020/12/3 liuha : Created.
*
* @return WeChatUniformTemplate 模板信息内容
*/
public abstract WeChatUniformTemplate getFirst();
/**
* BaseTemplateDataInfo::getRemark
* <p>TO:获取信息模板中remark部分的信息
* <p>HISTORY: 2020/12/3 liuha : Created.
* @return WeChatUniformTemplate 模板信息内容
*/
public abstract WeChatUniformTemplate getRemark();
/**
* BaseTemplateDataInfo::getKeyWordData
* <p>TO:获取信息模板中keyword部分的信息字段
* <p>HISTORY: 2020/12/3 liuha : Created.
* @return Map<String, WeChatUniformTemplate> key 为信息关键词 WeChatUniformTemplate 模板信息内容
*/
public abstract Map<String, WeChatUniformTemplate> getKeyWordData();
/**
* SendTemplateModel:: setSendDateWithColor
* <p>TO:赋值发送的信息的data
* <p>HISTORY: 2020/12/3 liuha : Created.
*
* @param value 发送值
* @param color 字体颜色
*/
public WeChatUniformTemplate setSendDateWithColor(String value, String color) {
return new WeChatUniformTemplate(value, color);
}
/**
* SendTemplateModel:: setSendDate
* <p>TO:赋值发送的信息的data,不带有字体颜色
* <p>HISTORY: 2020/12/3 liuha : Created.
*
* @param value 发送值
*/
public WeChatUniformTemplate setSendDate(String value) {
return new WeChatUniformTemplate(value, "");
}
}
定义模板类
微信模板的data部分的属性
@Data
public class WeChatUniformTemplate implements Serializable {
/**
* 信息内容
*/
private final String value;
/**
* 字体颜色
*/
private final String color;
public WeChatUniformTemplate(String value, String color) {
this.value = value;
this.color = color;
}
}
微信类型model类
@Data
public class WeChatTemplateModel implements Serializable {
/**
* 接收模板消息的人员类型(必填,客户:Customer,检验员:Inspector,二选一)
* 发送“业务受理通知”模板时填Customer,
* 发送“任务指派通知”、“检验流程审批提醒”、“审核驳回提醒”、“流程待办通知”、“检验安排通知”模板时填Inspector
*/
@NotBlank(message = "人员类型不能为空", groups = {InspectorCheck.class, CustomerCheck.class})
private String PersonnelType;
/**
* 业务系统编号(PersonnelType为Inspector时必填,这里固定填写为0002)
* PersonnelType为Customer时可以不用填写
*/
@NotBlank(message = "检验员模式业务系统编号不能为空", groups = {InspectorCheck.class})
private String BusinessSysID;
/**
* 检验员在检验系统中的UserID(PersonnelType为Inspector时必填,多个UserID则用英文的逗号隔开,例如:1,2,3)
* PersonnelType为Customer时可以不用填写
*/
@NotBlank(message = "检验员UserID不能为空", groups = {InspectorCheck.class})
private String UserID;
/**
* 报检客户的手机号(PersonnelType为Customer时,该项和UnionID这两项,至少填写一项)
* PersonnelType为Inspector时可以不用填写
*/
private String Mobile;
/**
* 报检客户的UnionID(PersonnelType为Customer时,该项和Mobile这两项,至少填写一项)
* PersonnelType为Inspector时可以不用填写
*/
private String UnionID;
/**
* 微信公众号模板消息实体(必填)
*/
@NotNull(message = "模板消息实体不能为空", groups = {InspectorCheck.class, CustomerCheck.class})
private SendTemplateModel SendTemplateModel;
/**
* WeChatTemplateModel:: getInspectorModel
* <p>TO:得到接收模板消息的人员为检验员时的Model对象
* <p>DO:默认赋值PersonnelType为Inspector;默认赋值BusinessSysID为0002
* <p>HISTORY: 2020/12/3 liuha : Created.
*
* @return WeChatTemplateModel 收模板消息的人员格式模板
*/
public static WeChatTemplateModel getInspectorModel() {
WeChatTemplateModel weChatTemplateModel = new WeChatTemplateModel();
weChatTemplateModel.setBusinessSysID(WeChatConstant.BUSINESSSYSID_INSPECTOR);
weChatTemplateModel.setPersonnelType(WeChatConstant.PERSONNELTYPE_INSPECTOR);
return weChatTemplateModel;
}
}
微信模板中发送数据的类
@Data
public class SendTemplateModel implements Serializable {
/**
* 微信公众号模板消息接收者openid(此处固定为空字符串)
*/
private String touser;
/**
* 微信公众号模板消息ID(必填)
*/
@NotBlank(message = "微信公众号模板消息ID不能为空", groups = {InspectorCheck.class, CustomerCheck.class})
private String template_id;
/**
* 微信公众号模板消息跳转链接(选填)
*/
private String url;
/**
* 微信公众号模板消息跳小程序所需数据,不需跳小程序可不用传该数据(选填)
*/
private String miniProgram;
/**
* 微信公众号模板消息数据(必填)
*/
@NotNull(message = "微信公众号模板消息数据不能为空", groups = {InspectorCheck.class, CustomerCheck.class})
private Map<String, WeChatUniformTemplate> data;
public void setData(BaseTemplateDataInfo templateDataInfo) {
if (this.data == null) {
this.data = new HashMap(16);
}
WeChatUniformTemplate first = templateDataInfo.getFirst();
if (first == null || StringUtils.isEmpty(first.getValue())) {
throw new JeecgBootException("微信公众号模板消息中first的模板内容(必填)");
}
data.put("first", templateDataInfo.getFirst());
data.put("remark", templateDataInfo.getRemark());
data.putAll(templateDataInfo.getKeyWordData());
}
}
这里我使用了validate按照 groups分组进行入参的校验,这里我们需要分成两种情况进行校验必填项,InspectorCheck和CustomerCheck,并且在发送方法的中,我们也分别定义了两个发送方法sendCustomerTemplateMsg和sendInspectorTemplateMsg,在业务上进行区分
校验方法
public class ValidationUtil {
private static Validator VALIDATOR;
private static Validator getValidatorOrCreate() {
if (VALIDATOR == null) {
synchronized (ValidationUtil.class) {
// Init the validation
VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();
}
}
return VALIDATOR;
}
public static void validate(Object obj, Class<?>... groups) {
Validator validator = getValidatorOrCreate();
// Validate the object
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(obj, groups);
if (!CollectionUtils.isEmpty(constraintViolations)) {
// If contain some errors then throw constraint violation exception
throw new ConstraintViolationException(constraintViolations);
}
}
public static void validateList(List<?> objList, Class<?>... groups) {
for (Object obj : objList) {
validate(obj, groups);
}
}
}
分组的接口类
public interface CustomerCheck {
}
public interface InspectorCheck {
}
发送模板信息方法
@Component
@Slf4j
public class WeChatSendTemplateMsgUtil {
@Autowired
private WxApiService wxApiService;
private static WeChatSendTemplateMsgUtil sndTemplateMsg;
@PostConstruct
public void init() {
sndTemplateMsg = this;
sndTemplateMsg.wxApiService = this.wxApiService;
}
/**
* WeChatSendTemplateMsg:: sendCustomerTemplateMsg
* <p>TO:发送给客户微信模板信息
* <p>DO:校验选填项目;发送模板信息
* <p>HISTORY: 2020/12/3 liuha : Created.
*
* @param templateModel 模板信息内容
* @return String 发送微信信返回
*/
public static String sendCustomerTemplateMsg(WeChatTemplateModel templateModel) {
//校验选填项
String unionID = templateModel.getUnionID();
String mobile = templateModel.getMobile();
if (StringUtils.isEmpty(mobile) && StringUtils.isEmpty(unionID)) {
throw new JeecgBootException("PersonnelType为Customer时,该项和UnionID这两项,至少填写一项");
}
return sendTemplateMsg(templateModel, CustomerCheck.class);
}
/**
* WeChatSendTemplateMsg:: sendInspectorTemplateMsg
* <p>TO:发送微信信息给检验员
* <p>HISTORY: 2020/12/3 liuha : Created.
*
* @param templateModel 信息内容模板
* @return String 发送微信信返回
*/
public static String sendInspectorTemplateMsg(WeChatTemplateModel templateModel) {
return sendTemplateMsg(templateModel, InspectorCheck.class);
}
/**
* WeChatSendTemplateMsg::
* <p>TO:发送模板信息
* <p>DO:校验必填项;发送微信模板信息
* <p>HISTORY: 2020/12/3 liuha : Created.
*
* @param templateModel 发送模板信息
* @param check 检验参数的类
* @return String 发送微信信返回
*/
private static String sendTemplateMsg(WeChatTemplateModel templateModel, Class check) {
//校验必填项
ValidationUtil.validate(templateModel, check);
//校验数据的必填
SendTemplateModel sendTemplateModel = templateModel.getSendTemplateModel();
ValidationUtil.validate(sendTemplateModel, check);
//发送信息
JSONObject jsonObj = JSON.parseObject(JSON.toJSONString(templateModel));
String result;
try {
result = sndTemplateMsg.wxApiService.SendTemplateMsg(jsonObj);
} catch (Exception e) {
throw new JeecgBootException("发送微信模板信息失败" + e.getMessage());
}
return result;
}
}
由于静态方法无法注入spring的bean,但是个人觉得发送微信使用静态方法方式比较优雅,所以使用了@PostConstruc标签
@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行
测试方法
WeChatTemplateModel templateModel =WeChatTemplateModel.getInspectorModel();
templateModel.setUserID("1111");
SendTemplateModel sendTemplateModel = new SendTemplateModel();
BaseTemplateDataInfo TemplateDataInfo = new BaseTemplateDataInfo() {
@Override
public WeChatUniformTemplate getFirst() {
return new WeChatUniformTemplate("您检验】任务", "#ff0");
}
@Override
public WeChatUniformTemplate getRemark() {
return super.setSendDate("新系统");
}
@Override
public Map<String, WeChatUniformTemplate> getKeyWordData() {
Map<String, WeChatUniformTemplate> data = new HashMap(4);
data.put("keyword1", super.setSendDateWithColor("杭州******blue"));
data.put("keyword2", super.setSendDate("黄平n联系时间:2019-01-30"));
data.put("keyword3", super.setSendDate("******"));
data.put("keyword4", super.setSendDate("*******"));
data.put("keyword5", super.setSendDate("1"));
return data;
}
};
sendTemplateModel.setTemplate_id("pMNf2j1Nqf4sb87KQyjyglm-hGmBQkZND5DevgPgz8s");
sendTemplateModel.setData(TemplateDataInfo);
templateModel.setSendTemplateModel(sendTemplateModel);
//发送信息
WeChatSendTemplateMsgUtil.sendInspectorTemplateMsg(templateModel);
然后收到信息
使用模板方法模式,我们可以控制主体业务的逻辑,比如校验业务数据、调用发送的接口,灵活变动的模块则让业务自己动态实现,一般这种动静结合的方式最适合使用模板方法模式。