在模板模式(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"
            }
        }
    }
}

其中PersonnelTypeInspectorBusinessSysIDUserID必填,而且SendTemplateModelPersonnelTypetemplate_id等必填不能为空

腐败代码的味道

image.png
在上面代码里面看出
1、大量使用了Map,一方面导致代码的可读性差
2、调用方法不能复用,复用性较差

流程图

image.png

定义模板方法

参考发现的模板信息中,data部分则是业务需要实现的模块,其中First
为必填,Remark选填,KeyWord 部分不确定

  1. 我们定义了一个抽象类,其中 getFirst(),getRemark(),getKeyWordData(),为抽象方法,需要业务具体实现
  2. 重写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 Speed setting from 0 to 255.
     * @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);

然后收到信息
image.png

使用模板方法模式,我们可以控制主体业务的逻辑,比如校验业务数据、调用发送的接口,灵活变动的模块则让业务自己动态实现,一般这种动静结合的方式最适合使用模板方法模式。