目前网上有很多,一分钟微信公众平台教程,但很少有快速搭建微信公众平台服务器教程。
本文以fastweixin框架作为基础,以微信公众平台企业号作为目标,快速搭建微信公众平台服务器。
有关JAVA微信公众平台服务器框架介绍可以参考:http://my.oschina.net/ywbrj042/blog/402049
本文目标:
1.极其方便的实现微信公众平台服务端开发
2、完成服务器绑定
3、实现用户消息监听
本文技术要求:要求大家熟悉JAVA基本语言和servlet
很多大学同学可能对servlet不太熟悉,推荐大家学习书:《head first servlets and jsp》,除此之外不要求swing awt这些知识。
首先这个框架已经为我们实现基本结构,但本人从实践中发现里面有很多需要自己扩充修改BUG的地方,本人使用完全自己实现自己的WeixinServletSupport方式。
0、修改JDK:
微信企业号均使用消息安全模式,安全模式都是AES加密,AES是米国政府采用的加密,所以强加密算法在早期是出口限制,同时在 jdk中有所限制,导致想使用安全模式,必须修改jdk内部的jar包替换为 JCE Unlimited Strength。
否则你会收到异常java.security.InvalidKeyException:illegal Key Size
下面代码修改自WeixinServletSupport
说明:
1、里面有个MyMessageUtil,后续文章会讲述,修改适应weblogic的,如果不需要可以换回原框架MessageUtil
2、用户发来所有信息都可以处理,可以自行修改代码。
3、bindServer进行修改处理判断没有参数调用情况。
package com.luozhuang; import com.github.sd4324530.fastweixin.company.handle.QYEventHandle; import com.github.sd4324530.fastweixin.company.handle.QYMessageHandle; import com.github.sd4324530.fastweixin.company.message.req.QYBaseEvent; import com.github.sd4324530.fastweixin.company.message.req.QYBaseReq; import com.github.sd4324530.fastweixin.company.message.req.QYBaseReqMsg; import com.github.sd4324530.fastweixin.company.message.req.QYBatchJobEvent; import com.github.sd4324530.fastweixin.company.message.req.QYEnterAgentEvent; import com.github.sd4324530.fastweixin.company.message.req.QYEventType; import com.github.sd4324530.fastweixin.company.message.req.QYImageReqMsg; import com.github.sd4324530.fastweixin.company.message.req.QYLocationEvent; import com.github.sd4324530.fastweixin.company.message.req.QYLocationReqMsg; import com.github.sd4324530.fastweixin.company.message.req.QYMenuEvent; import com.github.sd4324530.fastweixin.company.message.req.QYReqType; import com.github.sd4324530.fastweixin.company.message.req.QYScanCodeEvent; import com.github.sd4324530.fastweixin.company.message.req.QYSendPicInfoEvent; import com.github.sd4324530.fastweixin.company.message.req.QYTextReqMsg; import com.github.sd4324530.fastweixin.company.message.req.QYVideoReqMsg; import com.github.sd4324530.fastweixin.company.message.req.QYVoiceReqMsg; import com.github.sd4324530.fastweixin.company.message.resp.QYBaseRespMsg; import com.github.sd4324530.fastweixin.company.message.resp.QYTextRespMsg; import com.qq.weixin.mp.aes.AesException; import com.qq.weixin.mp.aes.WXBizMsgCrypt; import com.github.sd4324530.fastweixin.servlet.QYWeixinSupport; import com.github.sd4324530.fastweixin.util.BeanUtil; import com.github.sd4324530.fastweixin.util.CollectionUtil; import com.github.sd4324530.fastweixin.util.StrUtil; import com.fastwixinextend.MyMessageUtil; import java.io.IOException; import java.io.PrintWriter; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MainServernSupport { private static final Logger LOG = LoggerFactory.getLogger(QYWeixinSupport.class); private static final Object LOCK = new Object(); protected String fromUserName, toUserName; /** * 微信消息处理器列表 */ private static List<QYMessageHandle> messageHandles; /** * 子类重写,加入自定义的微信消息处理器,细化消息的处理 * * @return 微信消息处理器列表 */ protected List<QYMessageHandle> initMessageHandles() { return null; } /** * 子类重写,加入自定义的微信事件处理器,细化消息的处理 * * @return 微信事件处理器列表 */ protected List<QYEventHandle> initEventHandles() { return null; } /** * 微信事件处理器列表 */ private static List<QYEventHandle> eventHandles; public MainServernSupport() { super(); } /** * @return 你的回调token */ public static String getToken() { return "luozhuang"; } /** * @return "这里填写你的企业corpid" */ public static String getCropId() { return "luozhuang"; } public static String getAgentID() { return "luozhuang"; } /** * @return "你的回调encodingAesKey " */ public static String getAESKey() { return "luozhuang"; } public static String getAPPSecret() { return "luozhuang"; } /** * 绑定服务器的方法 * @param request * @param response */ public void bindServer(HttpServletRequest request, HttpServletResponse response) { PrintWriter pw = null; try { pw = response.getWriter(); } catch (IOException e) { LOG.debug(e); } if (StrUtil.isBlank(getToken()) || StrUtil.isBlank(getAESKey()) || StrUtil.isBlank(getCropId())) { pw.write("成功"); pw.flush(); pw.close(); return; } if (StrUtil.isBlank(request.getParameter("echostr")) || StrUtil.isBlank(request.getParameter("msg_signature")) || StrUtil.isBlank(request.getParameter("timestamp")) || StrUtil.isBlank(request.getParameter("nonce"))) { pw.write("成功"); pw.flush(); pw.close(); return; } try { WXBizMsgCrypt pc = new WXBizMsgCrypt(getToken(), getAESKey(), getCropId()); String echoStr = pc.VerifyURL(request.getParameter("msg_signature"), request.getParameter("timestamp"), request.getParameter("nonce"), request.getParameter("echostr")); pw.write(echoStr); pw.flush(); pw.close(); } catch (AesException e) { if (StrUtil.isNotBlank(e.OrginexceptionMessage)) { LOG.debug(e.OrginexceptionMessage); } LOG.debug("msg_signature",request.getParameter("msg_signature")); LOG.debug("timestamp",request.getParameter("timestamp")); LOG.debug("nonce",request.getParameter("nonce")); LOG.debug("echostr",request.getParameter("echostr")); LOG.debug(e); } finally { pw.write(""); pw.flush(); pw.close(); } } /** * 处理微信服务器发来的请求方法 * * @param request * http请求对象 * @return 处理消息的结果,已经是接口要求的XML的报文了 */ public String processRequest(HttpServletRequest request) { try { Map<String, Object> reqMap = MyMessageUtil.parseXml(request, getToken(), getCropId(), getAESKey()); fromUserName = (String) reqMap.get("FromUserName"); toUserName = (String) reqMap.get("ToUserName"); String result = ProcessMessage(request, reqMap); return result; } catch (Exception e) { LOG.debug(e); } return null; } /** * 处理微信传来信息 * * @param request * @param reqMap * @return */ public String ProcessMessage(HttpServletRequest request, Map<String, Object> reqMap) { String msgType = (String) reqMap.get("MsgType"); LOG.debug("收到消息,消息类型:{}", msgType); QYBaseRespMsg msg = null; /// BUG修改:equalsIgnoreCase代替equals,因为微信传来的Type不分大小写 if (msgType.equalsIgnoreCase(QYReqType.EVENT)) { String eventType = (String) reqMap.get("Event"); if (QYEventType.SUBSCRIBE.equals(eventType)) { QYBaseEvent event = new QYBaseEvent(); buildBasicEvent(reqMap, event); msg = handleSubScribe(event); if (BeanUtil.isNull(msg)) { msg = processEventHandle(event); } } else if (QYEventType.UNSUBSCRIBE.equalsIgnoreCase(eventType)) { QYBaseEvent event = new QYBaseEvent(); buildBasicEvent(reqMap, event); msg = handleUnsubscribe(event); if (BeanUtil.isNull(msg)) { msg = processEventHandle(event); } } else if (QYEventType.CLICK.equalsIgnoreCase(eventType)) { String eventKey = (String) reqMap.get("EventKey"); LOG.debug("eventKey:{}", eventKey); QYMenuEvent event = new QYMenuEvent(eventKey); buildBasicEvent(reqMap, event); msg = handleMenuClickEvent(event); if (BeanUtil.isNull(msg)) { msg = processEventHandle(event); } } else if (QYEventType.VIEW.equalsIgnoreCase(eventType)) { String eventKey = (String) reqMap.get("EventKey"); LOG.debug("eventKey:{}", eventKey); QYMenuEvent event = new QYMenuEvent(eventKey); buildBasicEvent(reqMap, event); msg = handleMenuViewEvent(event); if (BeanUtil.isNull(msg)) { msg = processEventHandle(event); } } else if (QYEventType.LOCATION.equalsIgnoreCase(eventType)) { double latitude = Double.parseDouble((String) reqMap.get("Latitude")); double longitude = Double.parseDouble((String) reqMap.get("Longitude")); double precision = Double.parseDouble((String) reqMap.get("Precision")); QYLocationEvent event = new QYLocationEvent(latitude, longitude, precision); buildBasicEvent(reqMap, event); msg = handleLocationEvent(event); if (BeanUtil.isNull(msg)) { msg = processEventHandle(event); } } else if (QYEventType.SCANCODEPUSH.equalsIgnoreCase(eventType) || QYEventType.SCANCODEWAITMSG.equalsIgnoreCase(eventType)) { String eventKey = (String) reqMap.get("EventKey"); Map<String, Object> scanCodeInfo = (Map<String, Object>) reqMap.get("ScanCodeInfo"); String scanType = (String) scanCodeInfo.get("ScanType"); String scanResult = (String) scanCodeInfo.get("ScanResult"); QYScanCodeEvent event = new QYScanCodeEvent(eventKey, scanType, scanResult); buildBasicEvent(reqMap, event); msg = handleScanCodeEvent(event); if (BeanUtil.isNull(msg)) { msg = processEventHandle(event); } } else if (QYEventType.PICPHOTOORALBUM.equalsIgnoreCase(eventType) || QYEventType.PICSYSPHOTO.equalsIgnoreCase(eventType) || QYEventType.PICWEIXIN.equalsIgnoreCase(eventType)) { String eventKey = (String) reqMap.get("EventKey"); Map<String, Object> sendPicsInfo = (Map<String, Object>) reqMap.get("SendPicsInfo"); int count = Integer.parseInt((String) sendPicsInfo.get("Count")); List<Map> picList = (List) sendPicsInfo.get("PicList"); QYSendPicInfoEvent event = new QYSendPicInfoEvent(eventKey, count, picList); buildBasicEvent(reqMap, event); msg = handleSendPicsInfoEvent(event); if (BeanUtil.isNull(msg)) { msg = processEventHandle(event); } } else if (QYEventType.ENTERAGENT.equalsIgnoreCase(eventType)) { QYEnterAgentEvent event = new QYEnterAgentEvent(); buildBasicEvent(reqMap, event); msg = handleEnterAgentEvent(event); if (BeanUtil.isNull(msg)) { msg = processEventHandle(event); } } else if (QYEventType.BATCHJOBRESULT.equalsIgnoreCase(eventType)) { Map<String, Object> batchJob = (Map<String, Object>) reqMap.get("BatchJob"); String jobId = (String) batchJob.get("JobId"); String jobType = (String) batchJob.get("JobType"); int errCode = (Integer) batchJob.get("ErrCode"); String errMsg = (String) batchJob.get("ErrMsg"); QYBatchJobEvent event = new QYBatchJobEvent(jobId, jobType, errCode, errMsg); buildBasicEvent(reqMap, event); msg = handleBatchJobEvent(event); if (BeanUtil.isNull(msg)) { msg = processEventHandle(event); } } } else { if (QYReqType.TEXT.equalsIgnoreCase(msgType)) { String content = (String) reqMap.get("Content"); LOG.debug("文本消息内容:{}", content); QYTextReqMsg textReqMsg = new QYTextReqMsg(content); buildBasicReqMsg(reqMap, textReqMsg); msg = handleTextMsg(textReqMsg); if (BeanUtil.isNull(msg)) { msg = processMessageHandle(textReqMsg); } } else if (QYReqType.IMAGE.equalsIgnoreCase(msgType)) { String picUrl = (String) reqMap.get("PicUrl"); String mediaId = (String) reqMap.get("MediaId"); QYImageReqMsg imageReqMsg = new QYImageReqMsg(picUrl, mediaId); buildBasicReqMsg(reqMap, imageReqMsg); msg = handleImageMsg(imageReqMsg); if (BeanUtil.isNull(msg)) { msg = processMessageHandle(imageReqMsg); } } else if (QYReqType.VOICE.equalsIgnoreCase(msgType)) { String format = (String) reqMap.get("Format"); String mediaId = (String) reqMap.get("MediaId"); QYVoiceReqMsg voiceReqMsg = new QYVoiceReqMsg(mediaId, format); buildBasicReqMsg(reqMap, voiceReqMsg); msg = handleVoiceMsg(voiceReqMsg); if (BeanUtil.isNull(msg)) { msg = processMessageHandle(voiceReqMsg); } } else if (QYReqType.VIDEO.equalsIgnoreCase(msgType)) { String thumbMediaId = (String) reqMap.get("ThumbMediaId"); String mediaId = (String) reqMap.get("MediaId"); QYVideoReqMsg videoReqMsg = new QYVideoReqMsg(mediaId, thumbMediaId); buildBasicReqMsg(reqMap, videoReqMsg); msg = handleVideoMsg(videoReqMsg); if (BeanUtil.isNull(msg)) { msg = processMessageHandle(videoReqMsg); } } else if (QYReqType.SHORT_VIDEO.equalsIgnoreCase(msgType)) { String thumbMediaId = (String) reqMap.get("ThumbMediaId"); String mediaId = (String) reqMap.get("MediaId"); QYVideoReqMsg videoReqMsg = new QYVideoReqMsg(mediaId, thumbMediaId); buildBasicReqMsg(reqMap, videoReqMsg); msg = handleShortVideoMsg(videoReqMsg); if (BeanUtil.isNull(msg)) { msg = processMessageHandle(videoReqMsg); } } else if (QYReqType.LOCATION.equalsIgnoreCase(msgType)) { double locationX = Double.parseDouble((String) reqMap.get("Location_X")); double locationY = Double.parseDouble((String) reqMap.get("Location_Y")); // BUG:Scale是首字母大写 int scale = Integer.parseInt((String) reqMap.get("Scale")); String label = (String) reqMap.get("Label"); // BUG:locationY 写成locationX QYLocationReqMsg locationReqMsg = new QYLocationReqMsg(locationX, locationY, scale, label); buildBasicReqMsg(reqMap, locationReqMsg); msg = handleLocationMsg(locationReqMsg); if (BeanUtil.isNull(msg)) { msg = processMessageHandle(locationReqMsg); } } } String result = ""; if (BeanUtil.nonNull(msg)) { msg.setFromUserName(toUserName); msg.setToUserName(fromUserName); result = msg.toXml(); /// BUG:缺少这个标签 result = "<xml>" + result + "</xml>"; LOG.debug("回复内容:{}", result); try { WXBizMsgCrypt pc = new WXBizMsgCrypt(getToken(), getAESKey(), getCropId()); result = pc.EncryptMsg(result, request.getParameter("timestamp"), request.getParameter("nonce")); LOG.debug("加密后密文:{}", result); } catch (AesException e) { LOG.error("加密异常", e); } } return result; } /** * 处理文本消息,有需要时子类重写 * * @param msg * 请求消息对象 * @return 响应消息对象 */ protected QYBaseRespMsg handleTextMsg(QYTextReqMsg msg) { return new QYTextRespMsg("感谢您发送文本"); } /** * 处理图片消息,有需要时子类重写 * * @param msg * 请求消息对象 * @return 响应消息对象 */ protected QYBaseRespMsg handleImageMsg(QYImageReqMsg msg) { return new QYTextRespMsg("感谢您上报图片"); } /** * 处理语音消息,有需要时子类重写 * * @param msg * 请求消息对象 * @return 响应消息对象 */ protected QYBaseRespMsg handleVoiceMsg(QYVoiceReqMsg msg) { return new QYTextRespMsg("感谢您上报语音"); } /** * 处理视频消息,有需要时子类重写 * * @param msg * 请求消息对象 * @return 响应消息对象 */ protected QYBaseRespMsg handleVideoMsg(QYVideoReqMsg msg) { return new QYTextRespMsg("感谢您上报视频"); } /** * 处理小视频消息,有需要时子类重写 * * @param msg * 请求消息对象 * @return 响应消息对象 */ protected QYBaseRespMsg handleShortVideoMsg(QYVideoReqMsg msg) { return new QYTextRespMsg("感谢您上报视频"); } /** * 处理地理位置消息,有需要时子类重写 * * @param msg * 请求消息对象 * @return 响应消息对象 */ protected QYBaseRespMsg handleLocationMsg(QYLocationReqMsg msg) { return new QYTextRespMsg("感谢您上报地理位置"); } /** * 处理地理位置事件,有需要时子类重写 * * @param event * 地理位置事件对象 * @return 响应消息对象 */ protected QYBaseRespMsg handleLocationEvent(QYLocationEvent event) { return new QYTextRespMsg("感谢您上报地理位置"); } /** * 处理菜单点击事件,有需要时子类重写 * * @param event * 菜单点击事件对象 * @return 响应消息对象 */ protected QYBaseRespMsg handleMenuClickEvent(QYMenuEvent event) { String eventKey = event.getEventKey(); QYTextRespMsg textMessage = new QYTextRespMsg(); // 根据key值判断用户点击的按钮 if (eventKey.equals("登录")) { textMessage .setContentBuilder("登录"); } return textMessage; } /** * 处理菜单跳转事件,有需要时子类重写 * * @param event * 菜单跳转事件对象 * @return 响应消息对象 */ protected QYBaseRespMsg handleMenuViewEvent(QYMenuEvent event) { return new QYTextRespMsg("感谢菜单跳转"); } /** * 处理菜单扫描推事件,有需要时子类重写 * * @param event * @return */ protected QYBaseRespMsg handleScanCodeEvent(QYScanCodeEvent event) { return new QYTextRespMsg("感谢您扫描二维码"); } /** * 处理菜单弹出相册事件,有需要时子类重写 * * @param event * 菜单弹出相册事件 * @return 响应的消息对象 */ protected QYBaseRespMsg handleSendPicsInfoEvent(QYSendPicInfoEvent event) { return handleDefaultEvent(event); } /** * 处理用户进入应用事件,有需要时子类重写 * * @param event * 添加应用事件对象 * @return 响应的消息对象 */ protected QYBaseRespMsg handleEnterAgentEvent(QYEnterAgentEvent event) { return new QYTextRespMsg("感谢进入应用"); } /** * 处理异步任务通知事件,有需要时子类重写 * * @param event * 添加通知事件对象 * @return 响应的消息对象 */ protected QYBaseRespMsg handleBatchJobEvent(QYBatchJobEvent event) { return handleDefaultEvent(event); } /** * 处理添加关注事件,有需要时子类重写 * * @param event * 添加关注事件对象 * @return 响应消息对象 */ protected QYBaseRespMsg handleSubScribe(QYBaseEvent event) { return new QYTextRespMsg("感谢您的关注"); } /** * 处理取消关注事件,有需要时子类重写 * * @param event * 取消关注事件对象 * @return 响应消息对象 */ protected QYBaseRespMsg handleUnsubscribe(QYBaseEvent event) { return null; } protected QYBaseRespMsg handleDefaultMsg(QYBaseReqMsg msg) { return null; } protected QYBaseRespMsg handleDefaultEvent(QYBaseEvent event) { return null; } /** * 自定义的消息事件处理 * * @param msg * 微信消息 * @return */ private QYBaseRespMsg processMessageHandle(QYBaseReqMsg msg) { if (CollectionUtil.isEmpty(messageHandles)) { synchronized (LOCK) { messageHandles = this.initMessageHandles(); } } if (CollectionUtil.isNotEmpty(messageHandles)) { for (QYMessageHandle messageHandle : messageHandles) { QYBaseRespMsg resultMsg = null; boolean result; try { result = messageHandle.beforeHandle(msg); } catch (Exception e) { result = false; } if (result) { resultMsg = messageHandle.handle(msg); } if (BeanUtil.nonNull(resultMsg)) { return resultMsg; } } } return null; } /** * 自定义的消息事件处理 * * @param event * @return */ private QYBaseRespMsg processEventHandle(QYBaseEvent event) { if (CollectionUtil.isEmpty(eventHandles)) { synchronized (LOCK) { eventHandles = this.initEventHandles(); } } if (CollectionUtil.isNotEmpty(eventHandles)) { for (QYEventHandle eventHandle : eventHandles) { QYBaseRespMsg resultMsg = null; boolean result; try { result = eventHandle.beforeHandle(event); } catch (Exception e) { result = false; } if (result) { resultMsg = eventHandle.handle(event); } if (BeanUtil.nonNull(resultMsg)) { return resultMsg; } } } return null; } private void buildBasicReqMsg(Map<String, Object> reqMap, QYBaseReqMsg reqMsg) { addBasicReqParams(reqMap, reqMsg); reqMsg.setMsgId((String) reqMap.get("MsgId")); } private void buildBasicEvent(Map<String, Object> reqMap, QYBaseEvent event) { addBasicReqParams(reqMap, event); event.setEvent((String) reqMap.get("Event")); } private void addBasicReqParams(Map<String, Object> reqMap, QYBaseReq req) { req.setMsgType((String) reqMap.get("MsgType")); req.setFromUserName((String) reqMap.get("FromUserName")); req.setToUserName((String) reqMap.get("ToUserName")); req.setCreateTime(Long.parseLong((String) reqMap.get("CreateTime"))); } }
package com.luozhuang; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 请求处理的核心类 * */ public class CoreServletDemo extends HttpServlet { private static final long serialVersionUID = 4440739483644821986L; MainServernSupport support = new MainServernSupport(); public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); support.bindServer(request, response); } /** * 重写servlet中的post方法,用于接收微信服务器发来的消息,置为final方法,用户已经无需重写这个方法啦 * * @param request http请求对象 * @param response http响应对象 * @throws ServletException servlet异常 * @throws IOException IO异常 */ public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 将请求、响应的编码均设置为UTF-8(防止中文乱码) request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); String resp = support.processRequest(request); PrintWriter out = response.getWriter(); out.print(resp); out.close(); } }