# 本文最先发表于我的个人博客 ,CSDN 为同步发布,如有需要,请访问 Brath 的个人博客 获取更多内容
# 背景
最近准备搭建自己的博客系统,有些软件或资料的下载链接放在网盘中,为了方便下载,同时可以将用户导流到公众号上,因此准备用 Java 实现微信公众号业务支持公众号自动回复的功能
# 准备工作
首先当然是需要注册一个微信公众号业务支持公众号,具体步骤就不在这里赘述了,注册地址:微信公众号业务支持公众平台
注册完毕后需要完成认证操作
# 代码
依赖引入,主要为 xml 相关依赖, 因为微信公众号业务支持公众号采用的 xml 消息格式进行交互
1 2 3 4 5 6 7 8 9 10 11
| <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.19</version> </dependency>
|
自动回复内容一共需要两个接口(两个接口路由完全一致,一个为 GET 请求,一个为 POST 请求)
此接口用于微信公众号业务支持公众号后台服务器认证使用,GET 请求
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
|
@GetMapping("callback") public void callback(String signature, String timestamp, String nonce, String echostr, HttpServletResponse response) { PrintWriter out = null; log.info("微信公众号业务支持校验消息,signature:{},timestamp:{},nonce:{},echostr:{}", signature, timestamp, nonce, echostr); List<WechatConfigPO> configPOList = wechatConfigDao.selectAll(); try { out = response.getWriter(); out.write(echostr); } catch (Throwable e) { log.error("微信公众号业务支持校验失败", e); } finally { if (out != null) { out.close(); } } }
|
此接口用于接收公众号消息回调,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
|
@PostMapping("callback") public void callback(HttpServletRequest request, HttpServletResponse response) { PrintWriter out = null;
try { String respMessage = wechatService.callback(request); if (StringUtils.isBlank(respMessage)) { log.info("不回复消息"); return; } response.setCharacterEncoding("UTF-8"); out = response.getWriter(); out.write(respMessage); } catch (Throwable e) { log.error("微信公众号业务支持发送消息失败", e); } finally { if (out != null) { out.close(); } } }
|
消息回复 service
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
@Slf4j @Service public class WechatService {
@Autowired private TextReplyService textReplyService;
public String callback(HttpServletRequest request) throws UnsupportedEncodingException { request.setCharacterEncoding("UTF-8");
try { Map<String, String> requestMap = WechatMessageUtils.parseXml(request); log.info("微信公众号业务支持接收到消息:{}", GsonUtils.toJson(requestMap)); String msgType = requestMap.get("MsgType");
switch (msgType) { case WechatMsgTypeConstant.MESSAGE_TYPE_TEXT: return textReplyService.reply(requestMap); default: return textReplyService.reply(requestMap); } } catch (Throwable e) { log.error("回复消息错误", e); } return null; }
}
|
文本回复 service
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
|
@Service public class TextReplyService {
private static final String FROM_USER_NAME = "FromUserName"; private static final String TO_USER_NAME = "ToUserName"; private static final String CONTENT = "Content";
@Autowired private WechatKeywordDao wechatKeywordDao;
@Autowired private WechatMsgRecordDao wechatMsgRecordDao;
public String reply(Map<String, String> requestMap) { String wechatId = requestMap.get(FROM_USER_NAME); String gongzhonghaoId = requestMap.get(TO_USER_NAME);
TextMessage textMessage = WechatMessageUtils.getDefaultTextMessage(wechatId, gongzhonghaoId);
String content = requestMap.get(CONTENT); if (content == null) { textMessage.setContent(WechatConstants.DEFAULT_MSG); } else { Example example = new Example(WechatKeywordPO.class); example.createCriteria().andEqualTo("wechatId", gongzhonghaoId).andEqualTo("keyword", content); List<WechatKeywordPO> keywordPOList = wechatKeywordDao.selectByExample(example); if (CollectionUtils.isEmpty(keywordPOList)) { textMessage.setContent(WechatConstants.DEFAULT_MSG); } else { textMessage.setContent(keywordPOList.get(0).getReplyContent()); } } wechatMsgRecordDao.insertSelective(WechatMsgRecordPO.builder() .fromUser(wechatId) .wechatId(gongzhonghaoId) .content(content) .replyContent(textMessage.getContent()) .build() );
return WechatMessageUtils.textMessageToXml(textMessage); }
}
|
文本消息 model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
@Data public class TextMessage extends BaseMessage {
private String Content;
} 1234567891011121314
|
基础消息 model
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 31 32
|
@Data public class BaseMessage {
private String ToUserName;
private String FromUserName;
private long CreateTime;
private String MsgType;
private int FuncFlag;
}
|
消息工具
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
|
public class WechatMessageUtils {
public static Map<String, String> parseXml(HttpServletRequest request) throws Exception { Map<String, String> map = new HashMap<>();
InputStream inputStream = request.getInputStream(); try { SAXReader reader = new SAXReader(); Document document = reader.read(inputStream); Element root = document.getRootElement();
List<Element> elementList = root.elements();
for (Element e : elementList) { map.put(e.getName(), e.getText()); } } finally { if (inputStream != null) { inputStream.close(); } }
return map; }
public static String textMessageToXml(TextMessage textMessage) { XSTREAM.alias("xml", textMessage.getClass()); return XSTREAM.toXML(textMessage); }
public static String musicMessageToXml(MusicMessage musicMessage) { XSTREAM.alias("xml", musicMessage.getClass()); return XSTREAM.toXML(musicMessage); }
public static String newsMessageToXml(NewsMessage newsMessage) { XSTREAM.alias("xml", newsMessage.getClass()); XSTREAM.alias("item", Article.class); return XSTREAM.toXML(newsMessage); }
private static final XStream XSTREAM = new XStream(new XppDriver() { @Override public HierarchicalStreamWriter createWriter(Writer out) { return new PrettyPrintWriter(out) { final boolean cdata = true;
@Override protected void writeText(QuickWriter writer, String text) { if (cdata) { writer.write("<![CDATA["); writer.write(text); writer.write("]]>"); } else { writer.write(text); } } }; } });
public static TextMessage getDefaultTextMessage(String receiver, String officialWxid) { TextMessage textMessage = new TextMessage(); textMessage.setToUserName(receiver); textMessage.setFromUserName(officialWxid); textMessage.setCreateTime(System.currentTimeMillis()); textMessage.setMsgType(WechatMsgTypeConstant.MESSAGE_TYPE_TEXT); textMessage.setFuncFlag(0);
return textMessage; }
}
|
消息类型枚举
1 2 3 4 5 6 7 8 9 10 11 12 13
|
public class WechatMsgTypeConstant {
public static final String MESSAGE_TYPE_TEXT = "text";
}
|
其他内容为一些数据库相关操作,此处不再列出,仅为:查询关键词及其回复内容,存储消息记录
# 公众号配置
公众号后台 -> 设置与开发 -> 基本配置 -> 服务器配置
填写你的服务器回调接口地址 (需要为公网地址,否则微信公众号业务支持无法调通)
- 生成或者自定义你的令牌 Token,后台需要配置这个 Token,一定要记住
Token 需要记住,一般在微信公众号业务支持验证接口处会校验相关信息是否是自己的公众号
验证方法
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
@Slf4j public class WechatUtils {
private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
public static boolean checkSignature(String signature, String timestamp, String nonce, String token) { String[] str = new String[]{token, timestamp, nonce}; Arrays.sort(str); StringBuffer buffer = new StringBuffer(); for (int i = 0; i < str.length; i++) { buffer.append(str[i]); } String temp = encode(buffer.toString()); return signature.equals(temp); }
private static String getFormattedText(byte[] bytes) { int len = bytes.length; StringBuilder buf = new StringBuilder(len * 2); for (int j = 0; j < len; j++) { buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]); buf.append(HEX_DIGITS[bytes[j] & 0x0f]); } return buf.toString(); }
public static String encode(String str) { if (str == null) { return null; } try { MessageDigest messageDigest = MessageDigest.getInstance("SHA1"); messageDigest.update(str.getBytes()); return getFormattedText(messageDigest.digest()); } catch (Exception e) { throw new RuntimeException(e); } }
} 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
|
可以搜索公众号 InterviewCoder) 或扫码关注公众号回复关键词《chatGPT》查看效果
# 关于我
Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!
非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!