【公众号开发】公众号技术分享-公众号模板消息-模板模式实战分享

【公众号开发】公众号技术分享-公众号模板消息-模板模式实战分享

由于文章内容较干,请允许Brath打一波广告…

面试记APP

Github:https://github.com/Guoqing815/interview

安卓APP下载:https://www.pgyer.com/interview_app_release

Brath的个人博客:https://brath.top

面试记官方公众号,定期分享有趣的编程知识:https://mp.weixin.qq.com/s/jWs6lLHl5L-atXJhHc4YvA

前言

​ 大家好啊,我是Brath,一名激进的全栈开发者,从这期专栏开始,我会逐渐分享面试记项目中的部分优质源码给到大家。还希望大家多多关注!

​ 上期我们介绍了微信模板消息的配置方式,以及Java代码示例,接下来我们要用更优雅的方式来实现发送模板消息!

注意:这是实战代码,不是Demo!

模板模式介绍:

​ 模板模式是一种行为型设计模式,它定义了一个操作中的算法骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。

​ 在模板模式中,我们定义一个抽象类,其中包含一个模板方法,该方法定义了算法的骨架,以及一些抽象的方法,这些方法需要子类去实现。子类可以继承该抽象类,并实现其中的抽象方法,从而完成具体的业务逻辑。

如何使用模板设计模式优雅的实现发送微信公众号模板消息?

一、设计模板架构

​ 我们的业务就是发送模板消息,最开始我使用了switch分支来判断不同的模板消息,起初它是好用的,但是随着消息类型的增加,慢慢的代码开始冗余了起来,所以我想到了利用模板模式来彻底优化这个业务。

​ 这次设计的模板架构分为五个模块:

1.模板:定义每个模板的规范,抽取共性方法,凝练重要功能
2.消息类型:分类管理,为不同的消息类型赋能
3.类型配置:便携管理消息类型,摒弃了if与switch判断
4.数据支撑:为模板消息提供基础数据支撑,例如公众号消息推送业务
5.统一校验:在抽象的层面去校验模板合法性,校验通过发送消息
6.消息注入:接口层面通过反射实例化消息,并将参数集合注入消息体

二、结构预览

messageData包,定义消息体,规范消息格式

1.IMessage为模板通用定义,实现了两个方法,getMessageName用于获取模板自己的名称,getWxMaTemplateData用于获取模板的参数配置

2.LoginSuccessTemplateImpl为登录成功模板消息的实例定义,实现了IMessage接口,并且重写getMessageName和getWxMaTemplateData方法,即能提供自己的名称和参数列表到接口定义中。

【公众号开发】公众号技术分享-公众号模板消息-模板模式实战分享_第1张图片

messageModule包,定义模板配置,发送流程

1.WechatMessageConfig为模板配置,用于获取所有模板映射的集合,方便我们通过名称获取模板配置实例,摒弃了ifelse的判断模式。

2.WechatMessageSupport为基础数据支撑,用于提供基础的微信模板发送消息的方法,以及模板消息注入方法的实现。

3.IWechatMessageExec为发送模板的接口,用于实现发送模板消息的规范。

4.AbstractWechatMessageBase为抽象的微信模板消息,用于抽象化管理所有模板消息,在这里做参数的校验,以及unionId的获取。

5.IWechatMessageExecImpl为发送消息的实现,在抽象层面获取到fromOpenId后即可通过抽象层继承的Support层的微信模板发送消息的方法来发送微信模板消息。

【公众号开发】公众号技术分享-公众号模板消息-模板模式实战分享_第2张图片

预览了结构之后,可以更好的理解下面的代码实现

三、模板消息以及实现类型

现在开始实现代码的部分,首先我们来定义一个消息体的抽象接口,用来表示统一的模板

抽象接口:
/**
 * @author: Brath
 * @date: 2023-06-5 18:10
 * @github: https://github.com/Guoqing815
 * @Copyright: 公众号:InterviewCoder | 博客:https://brath.top - 为了更好的你,也为了更好的世界。
 * @description: 消息体的抽象接口
 */
public interface IMessage {

    /**
     * 获取消息名称
     *
     * @return
     */
    String getMessageName();

    /**
     * 获取模板消息集合
     *
     * @return
     */
    List<WxMaTemplateData> getWxMaTemplateData();
}
实现类:
/**
 * @author: Brath
 * @date: 2023-06-5 18:13
 * @github: https://github.com/Guoqing815
 * @Copyright: 公众号:InterviewCoder | 博客:https://brath.top - 为了更好的你,也为了更好的世界。
 * @description: 登录成功模板消息
 */
@Component("loginSuccessTemplateMessage")
@ApiModel(value = "登录成功模板消息")
@Data
@Accessors(chain = true)
public class LoginSuccessTemplateImpl implements IMessage {

    @NotBlank(message = "登录用户不能为空")
    private String loginUser;

    @NotBlank(message = "登录地址不能为空")
    private String loginAddr;

    @NotBlank(message = "登录IP不能为空")
    private String loginIp;

    @Override
    public String getMessageName() {
        return NotifyType.LOGIN_SUCCESS_TEMPLATE_MESSAGE.getType();
    }

    @Override
    public List<WxMaTemplateData> getWxMaTemplateData() {
        List<WxMaTemplateData> params = new ArrayList<>();
        params.add(new WxMaTemplateData("keyword1", loginUser + ",您的账号刚刚在" + loginAddr + "登录。请关注本次登录时间和地点,若是您本人登录,请忽略本提醒,若非本人登录,请点击修改密码,并检查账号最近登录和操作行为是否有问题。", "#173177"));
        params.add(new WxMaTemplateData("keyword2", new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date()), "#173177"));
        params.add(new WxMaTemplateData("keyword3", loginIp, "#173177"));
        return params;
    }
}

注意:NotifyType表示消息的类型,你可以自己定义常量来代替这段代码

这两段代码实现了一个抽象的消息模板接口,并创建了登录成功模板消息类型,实现了对应的方法

四、数据支撑以及数据配置

数据支撑:
/**
 * @author: Brath
 * @date: 2023-06-5 19:11
 * @github: https://github.com/Guoqing815
 * @Copyright: 公众号:InterviewCoder | 博客:https://brath.top - 为了更好的你,也为了更好的世界。
 * @description: WechatMessageSupport
 */
@Service
public class WechatMessageSupport extends WechatMessageConfig {
    
    private static final Logger logger = LoggerFactory.getLogger(WechatMessageSupport.class);

    /**
     * Information service interface
     */
    protected static NotifyService notifyService;

    /**
     * 注入消息服务接口
     *
     * @param notifyService
     */
    @Resource
    public void setNotifyService(NotifyService notifyService) {
        WechatMessageSupport.notifyService = notifyService;
    }

    /**
     * 注入消息体
     *
     * @param paramMap
     * @param iMessage
     */
    public static IMessage injectMessage(HashMap<String, Object> paramMap, IMessage iMessage) {
        // 获取class模板
        Class<?> clazz = iMessage.getClass();

        // 缓存setter方法
        Map<String, Method> setterMap = new HashMap<>(16);
        for (Method method : clazz.getMethods()) {
            if (method.getName().startsWith(Consts.SET)) setterMap.put(method.getName(), method);
        }

        // 注入参数到字段中
        for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
            String paramName = entry.getKey();
            Object paramValue = entry.getValue();
            String setterName = generateSetterName(paramName);
            Method method = setterMap.get(setterName);
            if (method != null) {
                // 检查参数类型是否匹配
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length != 1 || !parameterTypes[0].isInstance(paramValue)) {
                    logger.warn("无法为消息对象设置参数 {} = {}, 参数类型不匹配", paramName, paramValue);
                    continue;
                }

                // 检查方法是否可访问
                if (!method.isAccessible()) {
                    method.setAccessible(true);
                }

                try {
                    method.invoke(iMessage, paramValue);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    logger.error("无法为消息对象设置参数 {} = {}", paramName, paramValue, e);
                }
            } else {
                logger.warn("无法为消息对象设置参数 {} = {}", paramName, paramValue);
            }
        }

        return iMessage;
    }

    /**
     * 生成setter方法名称
     *
     * @param propertyName
     * @return
     */
    private static String generateSetterName(String propertyName) {
        return Optional.ofNullable(propertyName).filter(name -> !name.isEmpty()).map(name -> "set" + Character.toUpperCase(name.charAt(0)) + name.substring(1)).orElse(null);
    }
}
数据配置:
/**
 * @author: Brath
 * @date: 2023-06-5 19:39
 * @github: https://github.com/Guoqing815
 * @Copyright: 公众号:InterviewCoder | 博客:https://brath.top - 为了更好的你,也为了更好的世界。
 * @description: WechatMessageConfig
 */
@Service
public class WechatMessageConfig {

    /**
     * 消息策略组映射表
     * 疑:为什么初始值会设置成8?而不是6呢?
     * 答:因为 HashMap 的实现可能会自动将 initcp 调整为接近 2 的幂次方的值,以便更好地处理哈希冲突
     */
    public static Map<String, IMessage> messageStrategyGroup = new ConcurrentHashMap<>(8);

    /**
     * 初始化消息组
     *
     * @param properties
     */
    @Resource
    private void initService(IMessage[] properties) {
        Arrays.stream(properties).forEach(
                property -> messageStrategyGroup.put(property.getMessageName(), property)
        );
    }
}

这两段代码我们实现了数据的基础支撑,以及类型模式配置。

五、注入抽象消息到模板,并且实现发送流程

抽象消息接口:定义发送模板消息的接口
/**
 * @author: Brath
 * @date: 2023-06-5 20:23
 * @github: https://github.com/Guoqing815
 * @Copyright: 公众号:InterviewCoder | 博客:https://brath.top - 为了更好的你,也为了更好的世界。
 * @description: IWechatMessageExec
 */
public interface IWechatMessageExec {

    /**
     * 发送模板消息
     */
    Object sendMessage(IMessage message, String unionId);

}
抽象消息实现:在抽象消息层面去判断消息体的参数合法性,并且将unionId转换为fromOpenId
/**
 * @author: Brath
 * @date: 2023-06-6 07:12
 * @github: https://github.com/Guoqing815
 * @Copyright: 公众号:InterviewCoder | 博客:https://brath.top - 为了更好的你,也为了更好的世界。
 * @description: AbstractWechatMessage
 */
public abstract class AbstractWechatMessageBase extends WechatMessageSupport implements IWechatMessageExec {

    private static final Logger logger = LoggerFactory.getLogger(AbstractWechatMessageBase.class);

    /**
     * 发送抽象消息,准备消息体,转换FromOpenID
     */
    @Override
    public Object sendMessage(IMessage message, String unionId) {
        ValidatorUtils.validateEntity(message);

        if (AssertUtil.isEmpty(unionId)) {
            logger.error(DEFALUT_FAIL, ResponseCode.DATA_DOES_NOT_EXIST.desc());
            return ResponseUtil.fail(ResponseCode.DATA_DOES_NOT_EXIST.desc());
        }
        //这段代码原本在WechatMessageSupport层实现,被我剔除掉了,这个需要大家去自己实现收集公众号关注人的fromOpenId,不是微信的openId,是关注了公众号后,产生的openId。
        String fromOpenId = wxservFollowService.getFromOpenIdByUnionId(unionId);
        if (AssertUtil.isEmpty(fromOpenId)) {
            logger.error(DEFALUT_FAIL, ResponseCode.DATA_DOES_NOT_EXIST.desc());
            return ResponseUtil.fail(ResponseCode.DATA_DOES_NOT_EXIST.desc());
        }
        //总之fromOpenId就是关注了公众号后产生的唯一ID
        return this.sendMessage(message, fromOpenId, message.getMessageName());
    }

    /**
     * 发送抽象消息
     *
     * @param message
     * @param openId
     * @param messageType
     * @return
     */
    protected abstract Object sendMessage(IMessage message, String openId, String messageType);
}
具体消息实现:在抽象层确保消息体完整性后,调用抽象层面的具体实现类,调用数据支撑层的notifyService服务完成发送模板消息
/**
 * @author: Brath
 * @date: 2023-06-6 07:40
 * @github: https://github.com/Guoqing815
 * @Copyright: 公众号:InterviewCoder | 博客:https://brath.top - 为了更好的你,也为了更好的世界。
 * @description: 模板消息推送实现
 */
@Component("wechatMessageExecImpl")
public class IWechatMessageExecImpl extends AbstractWechatMessageBase {

    private static final Logger logger = LoggerFactory.getLogger(IWechatMessageExecImpl.class);

    @Override
    protected Object sendMessage(IMessage message, String openId, String messageType) {
        logger.info("【公众号模板消息推送】:开始 --  messageType:{},openId:{}", messageType, openId);
        try {
            notifyService.notifyWxTemplate(
                    openId,
                    NotifyType.of(messageType),
                    message.getWxMaTemplateData()
            );
            logger.info("【公众号模板消息推送】:结束 -- messageType:{},openId:{}", messageType, openId);
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("【公众号模板消息推送】:异常 -- {}", e.getMessage());
            return ResponseUtil.fail(e.getMessage());

        }
        return ResponseUtil.ok();
    }
}

SpringBoot配置文件、消息类型、NotifyService实现:

pom文件配置依赖:这里我们使用大神binarywang的miniapp库来实现微信支持
<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-miniapp</artifactId>
    <version>3.3.0</version>
</dependency>
<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-pay</artifactId>
    <version>3.3.0</version>
</dependency>
yaml配置:实现自定义配置类,配置模板集合,以及是否开启
system:
  notify:
    wx:
      enable: true #启动模板消息
      template:
        - name: loginSuccessTemplateMessage #登录成功模板消息
          templateId: qDHo-UXXXXXXXXXXXXXXXXXXXXX #公众平台模板ID
配置信息类:使用静态内部类实现子配置
/**
 * @author: Brath
 * @date: 2023-06-6 07:40
 * @github: https://github.com/Guoqing815
 * @Copyright: 公众号:InterviewCoder | 博客:https://brath.top - 为了更好的你,也为了更好的世界。
 * @description: NotifyProperties
 */
@Data
@ConfigurationProperties(prefix = "system.notify")
public class NotifyProperties {

	private Wx wx;

	@Data
	public static class Wx {
		private boolean enable;
		private List<Map<String, String>> template = new ArrayList<>();
	}
}
自动配置类:使用EnableConfigurationProperties注解注入NotifyProperties配置并实现notifyService配置注入
/**
 * @author: Brath
 * @date: 2023-06-6 07:40
 * @github: https://github.com/Guoqing815
 * @Copyright: 公众号:InterviewCoder | 博客:https://brath.top - 为了更好的你,也为了更好的世界。
 * @description:  挂载NotifyProperties类,获取配置信息并自动加载
 */
@Configuration
@EnableConfigurationProperties(NotifyProperties.class)
public class NotifyAutoConfiguration {

    private final NotifyProperties properties;

    public NotifyAutoConfiguration(NotifyProperties properties) {
        this.properties = properties;
    }

    @Bean(name = "NotifyService")
    public NotifyService notifyService() {
        NotifyService notifyService = new NotifyService();

        NotifyProperties.Wx wxConfig = properties.getWx();
        if (wxConfig.isEnable()) {
            notifyService.setWxTemplateSender(wxTemplateSender());
            NotifyService.wxTemplate = wxConfig.getTemplate();
        }
        return notifyService;
    }

    @Bean
    public WxTemplateSender wxTemplateSender() {
        WxTemplateSender wxTemplateSender = new WxTemplateSender();
        return wxTemplateSender;
    }
}
消息类型:配置模板消息类型
/**
 * @author: Brath
 * @date: 2023-05-25 08:49
 * @github: https://github.com/Guoqing815
 * @Copyright: 公众号:InterviewCoder | 博客:https://brath.top - 为了更好的你,也为了更好的世界。
 * @description: NotifyType
 */
public enum NotifyType {

    //登录成功模板消息
    LOGIN_SUCCESS_TEMPLATE_MESSAGE("loginSuccessTemplateMessage");


    private static final EnumMap<NotifyType, String> typeMap = new EnumMap<>(NotifyType.class);

    static {
        for (NotifyType notifyType : NotifyType.values()) {
            typeMap.put(notifyType, notifyType.type);
        }
    }

    private String type;

    NotifyType(String type) {
        this.type = type;
    }

    public static NotifyType of(String messageType) {
        for (NotifyType notifyType : typeMap.keySet()) {
            if (typeMap.get(notifyType).equals(messageType)) {
                return notifyType;
            }
        }
        throw new IllegalArgumentException("No such enum object for the given messageType");
    }

    public String getType() {
        return this.type;
    }
}
NotifyService服务实现:实现发送消息的服务类
/**
 * @author: Brath
 * @date: 2023-06-6 07:40
 * @github: https://github.com/Guoqing815
 * @Copyright: 公众号:InterviewCoder | 博客:https://brath.top - 为了更好的你,也为了更好的世界。
 * @description: 公众号模板消息服务
 */
@Data
@NoArgsConstructor
public class NotifyService {
    private MailSender mailSender;
    private String sendFrom;
    private String sendTo;

    private WxTemplateSender wxTemplateSender;
    public static List<Map<String, String>> wxTemplate = new ArrayList<>();

    public boolean isWxEnable() {
        return wxTemplateSender != null;
    }

  
    /**
     * 微信模版消息通知,不跳转
     * 

* 该方法会尝试从数据库获取缓存的FormId去发送消息 * * @param touser 接收者openId * @param notifyType 通知类别,通过该枚举值在配置文件中获取相应的模版ID * @param params 通知模版内容里的参数,类似"您的验证码为{1}"中{1}的值 */ @Async public void notifyWxTemplate(String touser, NotifyType notifyType, List<WxMaTemplateData> params) { if (wxTemplateSender == null) return; String templateId = getTemplateId(notifyType, wxTemplate); wxTemplateSender.sendWechatMsg(touser, templateId, params); } /** * 微信模版消息通知,带跳转 *

* 该方法会尝试从数据库获取缓存的FormId去发送消息 * * @param touser 接收者openId * @param notifyType 通知类别,通过该枚举值在配置文件中获取相应的模版ID * @param params 通知模版内容里的参数,类似"您的验证码为{1}"中{1}的值 * @param page 点击消息跳转的页面 */ @Async public void notifyWxTemplate(String touser, NotifyType notifyType, List<WxMaTemplateData> params, String page) { if (wxTemplateSender == null) return; String templateId = getTemplateId(notifyType, wxTemplate); wxTemplateSender.sendWechatMsg(touser, templateId, params, page); } /** * 获取模板ID * * @param notifyType * @param values * @return */ private String getTemplateId(NotifyType notifyType, List<Map<String, String>> values) { for (Map<String, String> item : values) { String notifyTypeStr = notifyType.getType(); if (item.get("name").equals(notifyTypeStr)) return item.get("templateId"); } return null; } }

WxTemplateSender发送者实现:实现发送消息的基础类,使用WxMaService库发送消息
/**
 * @author: Brath
 * @date: 2023-06-6 07:40
 * @github: https://github.com/Guoqing815
 * @Copyright: 公众号:InterviewCoder | 博客:https://brath.top - 为了更好的你,也为了更好的世界。
 * @description: 微信模版消息通知
 */
public class WxTemplateSender {
    private final Log logger = LogFactory.getLog(WxTemplateSender.class);

    @Autowired
    private WxMaService wxMaService;

    /**
     * 发送微信消息(模板消息),不带跳转
     *
     * @param touser
     *            用户 OpenID
     * @param templatId
     *            模板消息ID
     * @param parms
     *            详细内容
     */
    public void sendWechatMsg(String touser, String templatId, String[] parms) {
        sendMsg(touser, templatId, parms, "", "", "");
    }

    /**
     * 发送微信消息(模板消息),不带跳转
     *
     * @param touser
     *            用户 OpenID
     * @param templatId
     *            模板消息ID
     * @param parms
     *            详细内容
     */
    public void sendWechatMsg(String touser, String templatId, List<WxMaTemplateData> parms) {
        sendMsg(touser, templatId, parms, "", "", "");
    }

    /**
     * 发送微信消息(模板消息),带跳转
     *
     * @param touser
     *            用户 OpenID
     * @param templatId
     *            模板消息ID
     * @param parms
     *            详细内容
     * @param page
     *            跳转页面
     */
    public void sendWechatMsg(String touser, String templatId, List<WxMaTemplateData> parms, String page) {
        sendMsg(touser, templatId, parms, page, "", "");
    }

    /**
     * 发送消息基类
     *
     * @param touser
     * @param templatId
     * @param parms
     * @param page
     * @param color
     * @param emphasisKeyword
     */
    private void sendMsg(String touser, String templatId, String[] parms, String page, String color,
                         String emphasisKeyword) {
        if (touser == null)
            return;

        WxMaTemplateMessage msg = new WxMaTemplateMessage();
        msg.setTemplateId(templatId);
        msg.setToUser(touser);
        msg.setFormId(touser);
        msg.setPage(page);
        msg.setColor(color);
        msg.setEmphasisKeyword(emphasisKeyword);
        msg.setData(createMsgData(parms));

        try {
            wxMaService.getMsgService().sendTemplateMsg(msg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static final JsonParser JSON_PARSER = new JsonParser();
    private static final String SEND_MSG_API = "https://api.weixin.qq.com/cgi-bin/message/template/send";

    private void sendMsg(String touser, String templatId, List<WxMaTemplateData> parms, String page, String color,
                         String emphasisKeyword) {
        if (touser == null)
            return;

        WxMaTemplateMessage msg = new WxMaTemplateMessage();
        msg.setTemplateId(templatId);
        msg.setToUser(touser);
        msg.setFormId(touser);
        msg.setPage(page);
        msg.setColor(color);
        msg.setEmphasisKeyword(emphasisKeyword);
        msg.setData(parms);

        try {
            String responseContent = this.wxMaService.post(SEND_MSG_API, msg.toJson());
            JsonObject jsonObject = JSON_PARSER.parse(responseContent).getAsJsonObject();
            if (jsonObject.get(WxMaConstants.ERRCODE).getAsInt() != 0) {
                throw new WxErrorException(WxError.fromJson(responseContent));
            }
        } catch (WxErrorException e) {
            logger.error("【微信消息模板】:服务端异常,原因可能是:{}", e);
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();

        }
    }

    private List<WxMaTemplateData> createMsgData(String[] parms) {
        List<WxMaTemplateData> dataList = new ArrayList<WxMaTemplateData>();
        for (int i = 1; i <= parms.length; i++) {
            dataList.add(new WxMaTemplateData("keyword" + i, parms[i - 1]));
        }

        return dataList;
    }
}
在配置完如上代码后,我们的代码就完成了,接下来即可通过单元测试来测试模板消息!

六、使用抽象模板发送消息

单元测试发送消息:

/**
 * @author: Brath
 * @date: 2023-06-6 08:21
 * @github: https://github.com/Guoqing815
 * @Copyright: 公众号:InterviewCoder | 博客:https://brath.top - 为了更好的你,也为了更好的世界。
 * @description: 单测
 */
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SpringRunnerTest {

    private Logger logger = LoggerFactory.getLogger(SpringRunnerTest.class);

    @Resource
    private IWechatMessageExec messageExec;

    @Test
    public void test_sendMessage() {
        //产品提测消息
        ProductTestTemplateImpl productTestTemplate = new ProductTestTemplateImpl()
                .setCompanyName("测试公司")
                .setUserName("Brath")
                .setVersion("2.0.6");
        //注册成功消息
        RegistTemplateImpl registTemplate = new RegistTemplateImpl();

        //登录成功消息
        LoginSuccessTemplateImpl loginSuccessTemplate = new LoginSuccessTemplateImpl()
                .setLoginUser("Brath")
                .setLoginAddr("JVM")
                .setLoginIp("0.0.0.0");
        messageExec.sendMessage(loginSuccessTemplate, "o-KKr6Qwsxxxxxxxxxxxxxxx");
    }
}
测试成功~

【公众号开发】公众号技术分享-公众号模板消息-模板模式实战分享_第3张图片

tips:如果你觉得Brath分享的代码还可以的话,请将我分享给更多需要帮助的人~
到此为止,公众号发送模板消息的知识分享就结束啦,还请同学们多多关注InterviewCoder,做一个激进的开发者,为了更好的你,也为了更好的世界!

完结撒花❀

关于我

Brath是一个热爱技术的Java程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

你可能感兴趣的:(Java,IDEA,java,开发语言)