前言
现在很多业务会基于微信公众号实现。笔者做这部分开发的时候,项目不允许再引入外部jar包,故做的相当蛋疼。这里是总结时写的demo节选。
如何将xml消息转换成json对象?
xstream
maven依赖
1 2 3 4 5
| <dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.12</version> </dependency>
|
消息原型(xmlString)
1 2 3 4 5 6 7 8
| <xml> <ToUserName><![CDATA[gh_d0c6b73cc08e]]></ToUserName> <FromUserName><![CDATA[oHOLJw-r6lBxSXU4pRDKpoDyqWI0]]></FromUserName> <CreateTime>1587459486</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[subscribe]]></Event> <EventKey><![CDATA[]]></EventKey> </xml>
|
对应的java对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| @Data @XStreamAlias("xml") public class XmlMessage implements Serializable {
@XStreamAlias("ToUserName") @XStreamConverter(XStreamCDataConverter.class) private String toUser; @XStreamAlias("FromUserName") @XStreamConverter(XStreamCDataConverter.class) private String fromUser; @XStreamAlias("CreateTime") private Long createTime; @XStreamAlias("MsgType") @XStreamConverter(XStreamCDataConverter.class) private String msgType; @XStreamAlias("Event") @XStreamConverter(XStreamCDataConverter.class) private String event; @XStreamAlias("EventKey") @XStreamConverter(XStreamCDataConverter.class) private String eventKey; @XStreamAlias("Ticket") @XStreamConverter(XStreamCDataConverter.class) private String ticket; @XStreamAlias("MsgID") private Long msgId; @XStreamAlias("Status") @XStreamConverter(value = XStreamCDataConverter.class) private String status; }
|
1 2 3 4 5 6 7 8 9
| public class XStreamCDataConverter extends StringConverter {
public XStreamCDataConverter() { }
public String toString(Object obj) { return "<![CDATA[" + super.toString(obj) + "]]>"; } }
|
类型转换
1 2 3 4 5 6 7 8 9 10 11
| @Test void xml2ObjectByXStream() {
XStream xStream = new XStream(); XStream.setupDefaultSecurity(xStream); xStream.allowTypes(new Class[]{XmlMessage.class}); xStream.processAnnotations(XmlMessage.class);
XmlMessage message = (XmlMessage)xStream.fromXML(xmlString); log.info(message.toString()); }
|
w3c.Dom
类型转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Test void xml2ObjectByDom() throws Exception{ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); InputSource is = new InputSource(new StringReader(xmlString)); Document document = builder.parse(is);
Element rootElement = document.getDocumentElement(); NodeList childNodes = rootElement.getChildNodes(); Map<String, Object> map = new HashMap<>(16); for (int i = 0; i < childNodes.getLength(); i++) { Node node = childNodes.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { Element child = (Element) node;
String nodeName = child.getNodeName(); String textContent = child.getTextContent(); map.put(nodeName, textContent); } }
log.info(map.toString()); }
|
dom4j
maven依赖
1 2 3 4 5
| <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency>
|
类型转换
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Test void xml2ObjectByDom4j() throws Exception{ Map<String, String> map = new HashMap<>(16); SAXReader saxReader = new SAXReader(); org.dom4j.Document doc = saxReader.read(new StringReader(xmlString)); org.dom4j.Element root = doc.getRootElement(); List<org.dom4j.Element> elements = root.elements(); for (org.dom4j.Element element : elements) { map.put(element.getName(), element.getTextTrim()); }
log.info(map.toString()); }
|
Json对象如何适配字段格式?
微信公众号默认的模板消息结构如下
1 2 3 4 5 6 7 8 9 10 11
| { "touser": "oHOLJw-r6lBxSXU4pRDKpoDyqWI0", "template_id": "7BsKKP1MsDxbNmfYfTaNVm9guRvgwH8l2PmFbDCMip0", "url": "http://idea360.cn", "data": { "name": { "value": "登高射太阳!", "color": "#173177" } } }
|
消息占位符字典的形式很难扩展,所以我们想办法把它变为List集合处理
maven依赖
1 2 3 4 5
| <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.6</version> </dependency>
|
消息结构定义
1 2 3 4 5 6 7
| @Data public class WeixinTemplateMessage implements Serializable { private String toUser; private String templateId; private String url; private List<WeixinTemplateData> data; }
|
1 2 3 4 5 6
| @Data public class WeixinTemplateData implements Serializable { private String key; private String value; private String color; }
|
类型转换适配器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class TemplateMessageGsonAdapter implements JsonSerializer<WeixinTemplateMessage> { @Override public JsonElement serialize(WeixinTemplateMessage message, Type typeOfSrc, JsonSerializationContext context) { JsonObject messageJson = new JsonObject(); messageJson.addProperty("touser", message.getToUser()); messageJson.addProperty("template_id", message.getTemplateId()); messageJson.addProperty("url", message.getUrl());
JsonObject data = new JsonObject(); messageJson.add("data", data);
WeixinTemplateData item; JsonObject dataJson; for(Iterator itemData = message.getData().iterator(); itemData.hasNext(); data.add(item.getKey(), dataJson)) { item = (WeixinTemplateData)itemData.next(); dataJson = new JsonObject(); dataJson.addProperty("value", item.getValue()); dataJson.addProperty("color", item.getColor()); }
return messageJson; } }
|
gson注册转换器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class WeixinGsonBuilder {
private static final GsonBuilder INSTANCE = new com.google.gson.GsonBuilder();
public WeixinGsonBuilder() { }
public static Gson create() { return INSTANCE.create(); }
static { INSTANCE.registerTypeAdapter(WeixinTemplateMessage.class, new TemplateMessageGsonAdapter()); } }
|
格式转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Test void gsonAdapter() {
WeixinTemplateMessage weixinTemplateMessage = new WeixinTemplateMessage(); weixinTemplateMessage.setTemplateId("7BsKKP1MsDxbNmfYfTaNVm9guRvgwH8l2PmFbDCMip0"); weixinTemplateMessage.setToUser("oHOLJw-r6lBxSXU4pRDKpoDyqWI0"); weixinTemplateMessage.setUrl("http://idea360.cn");
WeixinTemplateData weixinTemplateData = new WeixinTemplateData(); weixinTemplateData.setKey("name"); weixinTemplateData.setValue("当我遇上你"); weixinTemplateData.setColor("#ff0000");
weixinTemplateMessage.setData(Arrays.asList(weixinTemplateData));
String message = WeixinGsonBuilder.create().toJson(weixinTemplateMessage); log.info(message); }
|
结果输出发现就是前边我们需要的 json
格式。
消息推送
根据上述 gson
对模板消息的处理。参照 微信公众号接口学习 一文,即可轻松实现模板消息的推送。
模板没有创建接口,在公众号后台配置即可。
关注与取关
关注与取关的实现都是基于微信的事件推送来实现的。上述关于 xml
消息的处理即是为这里做铺垫。微信默认的推送消息是xml格式的。这里需要注意的是: 认证后的公众号有EncodingAESKey, 即可实现消息的加密选项。
微信事件推送接口和签名校验接口地址相同,区别在于时间推送接口基于 POST
。
对于事件推送的处理的伪代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| @Override public void handleEvent(String encType, String timestamp, String nonce, String signature, String xmlMessage, String msgSignature) throws WxException {
logger.info("收到微信推送消息:encType={}, timestamp={}, nonce={}, signature={}, xmlMessage={}", encType, timestamp, nonce, signature, xmlMessage);
WxMpXmlMessage message = null; XStream xStream = new XStream(); XStream.setupDefaultSecurity(xStream); xStream.allowTypes(new Class[]{WxMpXmlMessage.class}); xStream.processAnnotations(WxMpXmlMessage.class); if (encType == null) { message = (WxMpXmlMessage)xStream.fromXML(xmlMessage);
} else if ("aes".equalsIgnoreCase(encType)) { WxMpCryptUtil cryptUtil = new WxMpCryptUtil(wxMpService.getWxMpConfigStorage()); String plainText = cryptUtil.decrypt(msgSignature, timestamp, nonce, xmlMessage); logger.debug("解密后的原始xml消息内容:{}", plainText);
message = (WxMpXmlMessage)xStream.fromXML(plainText);
}
if (message.getMsgType().equals("event")) { wxMpEventHandlerFactory.handler(message.getEvent(), message); }
}
|
这里由于事件比较多,我们可以基于 工厂+策略
的模式来做业务处理。
- 定义handler接口
1 2 3 4 5 6
| public interface WxMpEventHandler {
String event();
void handler(WxMpXmlMessage message) throws WxException; }
|
- handler实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Component public class SubscribeHandler implements WxMpEventHandler {
private static final Logger logger = LoggerFactory.getLogger(SubscribeHandler.class);
@Override public String event() { return "subscribe"; }
@Transactional(rollbackFor = Exception.class) @Override public void handler(WxMpXmlMessage message) throws WxException { logger.info("有新用户关注:{}", userInfo);
} }
|
- 工厂类实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Component public class WxMpEventHandlerFactory implements ApplicationContextAware {
private static final Logger logger = LoggerFactory.getLogger(WxMpEventHandlerFactory.class); private static Map<String, WxMpEventHandler> handlerMap = new HashMap<>();
public WxMpEventHandlerFactory() { }
public void handler(String event, WxMpXmlMessage message) throws WxException { WxMpEventHandler eventHandler = handlerMap.get(event); if (null != eventHandler) { eventHandler.handler(message); } else { logger.error("暂无该类型消息处理器"); } }
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { Map<String, WxMpEventHandler> beansOfType = applicationContext.getBeansOfType(WxMpEventHandler.class); for (WxMpEventHandler bean : beansOfType.values()) { handlerMap.put(bean.event(), bean); } } }
|
- 事件处理
1 2 3
| if (message.getMsgType().equals("event")) { wxMpEventHandlerFactory.handler(message.getEvent(), message); }
|
最后
本篇到此结束。代码比较多,节选了关键代码。大家如有疑问可公众号【当我遇上你】后台留言。感谢阅读。