JAVA實(shí)現(xiàn) SpringMVC方式的微信接入、實(shí)現(xiàn)簡(jiǎn)單的自動(dòng)回復(fù)功能
前端時(shí)間小忙了一陣,微信公眾號(hào)的開(kāi)發(fā),從零開(kāi)始看文檔,踩了不少坑,也算是熬過(guò)來(lái)了,最近考慮做一些總結(jié),方便以后再開(kāi)發(fā)的時(shí)候回顧,也給正在做相關(guān)項(xiàng)目的同學(xué)做個(gè)參考。
其實(shí)做過(guò)一遍之后會(huì)發(fā)現(xiàn)也不難,大致思路:用戶消息和開(kāi)發(fā)者需要的事件推送都會(huì)通過(guò)微信方服務(wù)器發(fā)起一個(gè)請(qǐng)求,轉(zhuǎn)發(fā)到你在公眾平臺(tái)配置的服務(wù)器url地址,微信方將帶上signature,timestamp,nonce,echostr四個(gè)參數(shù),我們自己服務(wù)器通過(guò)拼接公眾平臺(tái)配置的token,以及傳上來(lái)的timestamp,nonce進(jìn)行SHA1加密后匹配signature,返回ture說(shuō)明接入成功。
1.公眾平臺(tái)配置

2.Controller
@Controller
@RequestMapping("/wechat")
publicclass WechatController {
@Value("${DNBX_TOKEN}")
private String DNBX_TOKEN;
private static final Logger LOGGER = LoggerFactory.getLogger(WechatController.class);
@Resource
WechatService wechatService;
/**
* 微信接入
* @param wc
* @return
* @throws IOException
*/
@RequestMapping(value="/connect",method = {RequestMethod.GET, RequestMethod.POST})
@ResponseBody
publicvoid connectWeixin(HttpServletRequest request, HttpServletResponse response) throws IOException{
// 將請(qǐng)求、響應(yīng)的編碼均設(shè)置為UTF-8(防止中文亂碼)
request.setCharacterEncoding("UTF-8"); //微信服務(wù)器POST消息時(shí)用的是UTF-8編碼,在接收時(shí)也要用同樣的編碼,否則中文會(huì)亂碼;
response.setCharacterEncoding("UTF-8"); //在響應(yīng)消息(回復(fù)消息給用戶)時(shí),也將編碼方式設(shè)置為UTF-8,原理同上;boolean isGet = request.getMethod().toLowerCase().equals("get");
PrintWriter out = response.getWriter();
try {
if (isGet) {
String signature = request.getParameter("signature");// 微信加密簽名
String timestamp = request.getParameter("timestamp");// 時(shí)間戳
String nonce = request.getParameter("nonce");// 隨機(jī)數(shù)
String echostr = request.getParameter("echostr");//隨機(jī)字符串
// 通過(guò)檢驗(yàn)signature對(duì)請(qǐng)求進(jìn)行校驗(yàn),若校驗(yàn)成功則原樣返回echostr,表示接入成功,否則接入失敗 if (SignUtil.checkSignature(DNBX_TOKEN, signature, timestamp, nonce)) {
LOGGER.info("Connect the weixin server is successful.");
response.getWriter().write(echostr);
} else {
LOGGER.error("Failed to verify the signature!");
}
}else{
String respMessage = "異常消息!";
try {
respMessage = wechatService.weixinPost(request);
out.write(respMessage);
LOGGER.info("The request completed successfully");
LOGGER.info("to weixin server "+respMessage);
} catch (Exception e) {
LOGGER.error("Failed to convert the message from weixin!");
}
}
} catch (Exception e) {
LOGGER.error("Connect the weixin server is error.");
}finally{
out.close();
}
}
}
3.簽名驗(yàn)證 checkSignature
從上面的controller我們可以看到,我封裝了一個(gè)工具類SignUtil,調(diào)用了里面的一個(gè)叫checkSignature,傳入了四個(gè)值,DNBX_TOKEN, signature, timestamp, nonce。這個(gè)過(guò)程非常重要,其實(shí)我們可以理解為將微信傳過(guò)來(lái)的值進(jìn)行一個(gè)加解密的過(guò)程,很多大型的項(xiàng)目所有的接口為保證安全性都會(huì)有這樣一個(gè)驗(yàn)證的過(guò)程。DNBX_TOKEN我們?cè)谖⑿殴娖脚_(tái)配置的一個(gè)token字符串,主意保密哦!其他三個(gè)都是微信服務(wù)器發(fā)送get請(qǐng)求傳過(guò)來(lái)的參數(shù),我們進(jìn)行一層sha1加密:
public class SignUtil {
/**
* 驗(yàn)證簽名
*
* @param token 微信服務(wù)器token,在env.properties文件中配置的和在開(kāi)發(fā)者中心配置的必須一致
* @param signature 微信服務(wù)器傳過(guò)來(lái)sha1加密的證書簽名
* @param timestamp 時(shí)間戳
* @param nonce 隨機(jī)數(shù)
* @return
*/
public static boolean checkSignature(String token,String signature, String timestamp, String nonce) {
String[] arr = new String[] { token, timestamp, nonce };
// 將token、timestamp、nonce三個(gè)參數(shù)進(jìn)行字典序排序
Arrays.sort(arr);
// 將三個(gè)參數(shù)字符串拼接成一個(gè)字符串進(jìn)行sha1加密
String tmpStr = SHA1.encode(arr[0] + arr[1] + arr[2]);
// 將sha1加密后的字符串可與signature對(duì)比,標(biāo)識(shí)該請(qǐng)求來(lái)源于微信
return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
}
}
SHA1:
/**
* 微信公眾平臺(tái)(JAVA) SDK
*
* SHA1算法
*
* @author helijun 2016/06/15 19:49
*/
public final class SHA1 {
private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
/**
* Takes the raw bytes from the digest and formats them correct.
*
* @param bytes the raw bytes from the digest.
* @return the formatted bytes.
*/
private static String getFormattedText(byte[] bytes) {
int len = bytes.length;
StringBuilder buf = new StringBuilder(len * 2);
// 把密文轉(zhuǎn)換成十六進(jìn)制的字符串形式
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);
}
}
}
當(dāng)你在公眾平臺(tái)提交保存,并且看到綠色的提示“接入成功”之后,恭喜你已經(jīng)完成微信接入。這個(gè)過(guò)程需要細(xì)心一點(diǎn),注意加密算法里的大小寫,如果接入不成功,大多數(shù)情況都是加密算法的問(wèn)題,多檢查檢查。
4. 實(shí)現(xiàn)消息自動(dòng)回復(fù)service
/**
* 處理微信發(fā)來(lái)的請(qǐng)求
*
* @param request
* @return
*/
public String weixinPost(HttpServletRequest request) {
String respMessage = null;
try {
// xml請(qǐng)求解析
Map<String, String> requestMap = MessageUtil.xmlToMap(request);
// 發(fā)送方帳號(hào)(open_id)
String fromUserName = requestMap.get("FromUserName");
// 公眾帳號(hào)
String toUserName = requestMap.get("ToUserName");
// 消息類型
String msgType = requestMap.get("MsgType");
// 消息內(nèi)容
String content = requestMap.get("Content");
LOGGER.info("FromUserName is:" + fromUserName + ", ToUserName is:" + toUserName + ", MsgType is:" + msgType);
// 文本消息
if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) {
//這里根據(jù)關(guān)鍵字執(zhí)行相應(yīng)的邏輯,只有你想不到的,沒(méi)有做不到的
if(content.equals("xxx")){
}
//自動(dòng)回復(fù)
TextMessage text = new TextMessage();
text.setContent("the text is" + content);
text.setToUserName(fromUserName);
text.setFromUserName(toUserName);
text.setCreateTime(new Date().getTime() + "");
text.setMsgType(msgType);
respMessage = MessageUtil.textMessageToXml(text);
} /*else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {// 事件推送
String eventType = requestMap.get("Event");// 事件類型
if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {// 訂閱
respContent = "歡迎關(guān)注xxx公眾號(hào)!";
return MessageResponse.getTextMessage(fromUserName , toUserName , respContent);
} else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) {// 自定義菜單點(diǎn)擊事件
String eventKey = requestMap.get("EventKey");// 事件KEY值,與創(chuàng)建自定義菜單時(shí)指定的KEY值對(duì)應(yīng)
logger.info("eventKey is:" +eventKey);
return xxx;
}
}
//開(kāi)啟微信聲音識(shí)別測(cè)試 2015-3-30
else if(msgType.equals("voice"))
{
String recvMessage = requestMap.get("Recognition");
//respContent = "收到的語(yǔ)音解析結(jié)果:"+recvMessage;
if(recvMessage!=null){
respContent = TulingApiProcess.getTulingResult(recvMessage);
}else{
respContent = "您說(shuō)的太模糊了,能不能重新說(shuō)下呢?";
}
return MessageResponse.getTextMessage(fromUserName , toUserName , respContent);
}
//拍照功能
else if(msgType.equals("pic_sysphoto"))
{
}
else
{
return MessageResponse.getTextMessage(fromUserName , toUserName , "返回為空");
}*/
// 事件推送
else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {
String eventType = requestMap.get("Event");// 事件類型
// 訂閱
if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {
TextMessage text = new TextMessage();
text.setContent("歡迎關(guān)注,xxx");
text.setToUserName(fromUserName);
text.setFromUserName(toUserName);
text.setCreateTime(new Date().getTime() + "");
text.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
respMessage = MessageUtil.textMessageToXml(text);
}
// TODO 取消訂閱后用戶再收不到公眾號(hào)發(fā)送的消息,因此不需要回復(fù)消息
else if (eventType.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {// 取消訂閱
}
// 自定義菜單點(diǎn)擊事件
else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) {
String eventKey = requestMap.get("EventKey");// 事件KEY值,與創(chuàng)建自定義菜單時(shí)指定的KEY值對(duì)應(yīng)
if (eventKey.equals("customer_telephone")) {
TextMessage text = new TextMessage();
text.setContent("0755-86671980");
text.setToUserName(fromUserName);
text.setFromUserName(toUserName);
text.setCreateTime(new Date().getTime() + "");
text.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
respMessage = MessageUtil.textMessageToXml(text);
}
}
}
}
catch (Exception e) {
Logger.error("error......")
}
return respMessage;
}
先貼代碼如上,大多都有注釋,讀一遍基本語(yǔ)義也懂了不需要多解釋。
簡(jiǎn)要思路:當(dāng)用戶給公眾號(hào)發(fā)送消息時(shí),微信服務(wù)器會(huì)將用戶消息以xml格式通過(guò)POST請(qǐng)求到我們配置好的服務(wù)器對(duì)應(yīng)的接口,而我們要做的事情就是根據(jù)消息類型等做相應(yīng)的邏輯處理,并將最后的返回結(jié)果也通過(guò)xml格式return給微信服務(wù)器,微信方再傳達(dá)給用戶的這樣一個(gè)過(guò)程。
有一個(gè)地方格外需要注意:
上面標(biāo)紅的fromUserName和toUserName剛好相反,這也是坑之一,還記得我當(dāng)時(shí)調(diào)了很久,明明都沒(méi)有問(wèn)題就是不通,最后把這兩個(gè)一換消息就收到了!其實(shí)回過(guò)頭想也對(duì),返回給微信服務(wù)器這時(shí)本身角色就變了,所以發(fā)送和接收方也肯定是相反的。
5.MessageUtil
public class MessageUtil {
/**
* 返回消息類型:文本
*/
public static final String RESP_MESSAGE_TYPE_TEXT = "text";
/**
* 返回消息類型:音樂(lè)
*/
public static final String RESP_MESSAGE_TYPE_MUSIC = "music";
/**
* 返回消息類型:圖文
*/
public static final String RESP_MESSAGE_TYPE_NEWS = "news";
/**
* 請(qǐng)求消息類型:文本
*/
public static final String REQ_MESSAGE_TYPE_TEXT = "text";
/**
* 請(qǐng)求消息類型:圖片
*/
public static final String REQ_MESSAGE_TYPE_IMAGE = "image";
/**
* 請(qǐng)求消息類型:鏈接
*/
public static final String REQ_MESSAGE_TYPE_LINK = "link";
/**
* 請(qǐng)求消息類型:地理位置
*/
public static final String REQ_MESSAGE_TYPE_LOCATION = "location";
/**
* 請(qǐng)求消息類型:音頻
*/
public static final String REQ_MESSAGE_TYPE_VOICE = "voice";
/**
* 請(qǐng)求消息類型:推送
*/
public static final String REQ_MESSAGE_TYPE_EVENT = "event";
/**
* 事件類型:subscribe(訂閱)
*/
public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
/**
* 事件類型:unsubscribe(取消訂閱)
*/
public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
/**
* 事件類型:CLICK(自定義菜單點(diǎn)擊事件)
*/
public static final String EVENT_TYPE_CLICK = "CLICK";
}
這里為了程序可讀性、擴(kuò)展性更好一點(diǎn),我做了一些封裝,定義了幾個(gè)常量,以及將微信傳過(guò)來(lái)的一些參數(shù)封裝成java bean持久化對(duì)象,核心代碼如上。重點(diǎn)講下xml和map之間的轉(zhuǎn)換
其實(shí)這個(gè)問(wèn)題要?dú)w咎于微信是用xml通訊,而我們平時(shí)一般是用json,所以可能短時(shí)間內(nèi)會(huì)有點(diǎn)不適應(yīng)
1.引入jar包
<!-- 解析xml --> <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.9</version> </dependency>
2.xml轉(zhuǎn)map集合對(duì)象
/**
* xml轉(zhuǎn)換為map
* @param request
* @return
* @throws IOException
*/
@SuppressWarnings("unchecked")
public static Map<String, String> xmlToMap(HttpServletRequest request) throws IOException{
Map<String, String> map = new HashMap<String, String>();
SAXReader reader = new SAXReader();
InputStream ins = null;
try {
ins = request.getInputStream();
} catch (IOException e1) {
e1.printStackTrace();
}
Document doc = null;
try {
doc = reader.read(ins);
Element root = doc.getRootElement();
List<Element> list = root.elements();
for (Element e : list) {
map.put(e.getName(), e.getText());
}
return map;
} catch (DocumentException e1) {
e1.printStackTrace();
}finally{
ins.close();
}
return null;
}
3.文本消息對(duì)象轉(zhuǎn)換成xml
/**
* 文本消息對(duì)象轉(zhuǎn)換成xml
*
* @param textMessage 文本消息對(duì)象
* @return xml
*/
public static String textMessageToXml(TextMessage textMessage){
XStream xstream = new XStream();
xstream.alias("xml", textMessage.getClass());
return xstream.toXML(textMessage);
}
以上所述是小編給大家介紹的JAVA實(shí)現(xiàn) SpringMVC方式的微信接入、實(shí)現(xiàn)簡(jiǎn)單的自動(dòng)回復(fù)功能,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Spring Boot 深入分析AutoConfigurationImportFilter自動(dòng)化條件
這篇文章主要分析了Spring Boot AutoConfigurationImportFilter自動(dòng)化條件配置源碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-07-07
Java開(kāi)發(fā)中synchronized的定義及用法詳解
這篇文章主要介紹了Java開(kāi)發(fā)中synchronized的定義及用法詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
Java錯(cuò)誤問(wèn)題:找不到或無(wú)法加載主類的解決
這篇文章主要介紹了Java錯(cuò)誤問(wèn)題:找不到或無(wú)法加載主類的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
Java中統(tǒng)計(jì)字符個(gè)數(shù)以及反序非相同字符的方法詳解
本篇文章是對(duì)Java中統(tǒng)計(jì)字符個(gè)數(shù)以及反序非相同字符的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
Intellij IDEA實(shí)現(xiàn)springboot熱部署過(guò)程解析
這篇文章主要介紹了Intellij IDEA實(shí)現(xiàn)springboot熱部署過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
詳解java開(kāi)發(fā)webservice的幾種方式
webservice的應(yīng)用已經(jīng)越來(lái)越廣泛了,下面介紹幾種在Java體系中開(kāi)發(fā)webservice的方式,有興趣的可以了解一下。2016-11-11
Java堆空間爆滿導(dǎo)致宕機(jī)的問(wèn)題分析及解決
團(tuán)隊(duì)有一個(gè)服務(wù),一直運(yùn)行的好好的,突然訪問(wèn)異常了,先是請(qǐng)求超時(shí),然后直接無(wú)法訪問(wèn),本文將給大家介紹Java堆空間爆滿導(dǎo)致宕機(jī)的問(wèn)題分析及解決,需要的朋友可以參考下2024-02-02

