前言
现在很多业务会基于微信公众号实现。笔者做这部分开发的时候,项目不允许再引入外部 jar 包,故做的相当蛋疼。这里是总结时写的 demo 节选。
如何将 xml 消息转换成 json 对象?
xstream
maven 依赖
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.12</version>
</dependency>
消息原型(xmlString)
<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 对象
@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;
}
public class XStreamCDataConverter extends StringConverter {
public XStreamCDataConverter() {
}
public String toString(Object obj) {
return "<![CDATA[" + super.toString(obj) + "]]>";
}
}
类型转换
@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
类型转换
@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);
// xml标签
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 依赖
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
类型转换
@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 对象如何适配字段格式?
微信公众号默认的模板消息结构如下
{
"touser": "oHOLJw-r6lBxSXU4pRDKpoDyqWI0",
"template_id": "7BsKKP1MsDxbNmfYfTaNVm9guRvgwH8l2PmFbDCMip0",
"url": "http://idea360.cn",
"data": {
"name": {
"value": "登高射太阳!",
"color": "#173177"
}
}
}
消息占位符字典的形式很难扩展,所以我们想办法把它变为 List 集合处理
maven 依赖
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
消息结构定义
@Data
public class WeixinTemplateMessage implements Serializable {
private String toUser;
private String templateId;
private String url;
private List<WeixinTemplateData> data;
}
@Data
public class WeixinTemplateData implements Serializable {
private String key;
private String value;
private String color;
}
类型转换适配器
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 注册转换器
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());
}
}
格式转换
@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。
对于事件推送的处理的伪代码如下:
@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)) {
// aes加密的消息
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 接口
public interface WxMpEventHandler {
String event();
void handler(WxMpXmlMessage message) throws WxException;
}
- handler 实现
@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);
}
}
- 工厂类实现
@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);
}
}
}
- 事件处理
if (message.getMsgType().equals("event")) {
wxMpEventHandlerFactory.handler(message.getEvent(), message);
}
最后
本篇到此结束。代码比较多,节选了关键代码。大家如有疑问可公众号【当我遇上你】后台留言。感谢阅读。