spring boot整合公众号笔记
1. 域名验证
1.1 WechatController 与 CheckUtil 验证
@Controller @RequestMapping("certification/access") public class WechatController { private Logger logger = LoggerFactory.getLogger(WechatController.class); /** * @Description 这是微信回调的验证接口 */ @RequestMapping(method = RequestMethod.GET) protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String signature = req.getParameter("signature"); String timestamp = req.getParameter("timestamp"); String nonce = req.getParameter("nonce"); String echostr = req.getParameter("echostr"); PrintWriter out = resp.getWriter(); if(CheckUtil.checkSignature(signature, timestamp, nonce)){ out.print(echostr); } } /** * 消息的接收与响应 */ @RequestMapping(method = RequestMethod.POST) protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException{ req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); PrintWriter out = resp.getWriter(); try { Map<String, String> map = MessageUtil.xmlToMap(req); String fromUserName = map.get("FromUserName");//发送者 String toUserName = map.get("ToUserName");//接收者 String msgType = map.get("MsgType");//消息类型 String content = map.get("Content");//消息内容 String message = null; if(MessageUtil.MESSAGE_TEXT.equals(msgType)){//文本消息 }else if(MessageUtil.MESSAGE_EVNET.equals(msgType)){//事件消息 String eventType = map.get("Event"); if(MessageUtil.MESSAGE_SUBSCRIBE.equals(eventType)){//用户未关注时,进行关注后的事件推送 String eventKey = map.get("EventKey"); if(!StringUtil.isEmpty(eventKey)){ eventKey = eventKey.replace("qrscene_",""); //自己的逻辑 } message = MessageUtil.initText(toUserName, fromUserName, MessageUtil.menuText()); }else if(MessageUtil.FOLLOW_OFFICIAL_ACCOUNT.equals(eventType)){//用户已关注时的事件推送 String eventKey = map.get("EventKey"); if(!StringUtil.isEmpty(eventKey)){ //自己的逻辑 } message = MessageUtil.initText(toUserName, fromUserName, MessageUtil.menuText()); }else if(MessageUtil.MESSAGE_CLICK.equals(eventType)){//点击事件 message = MessageUtil.initText(toUserName, fromUserName, MessageUtil.menuText()); }else if(MessageUtil.MESSAGE_VIEW.equals(eventType)){//查看事件 }else if(MessageUtil.MESSAGE_SCANCODE.equals(eventType)){//扫描码推送 } }else if(MessageUtil.MESSAGE_LOCATION.equals(msgType)){ } logger.info(message); out.print(message); } catch (DocumentException e) { e.printStackTrace(); logger.error("消息与响应失败:"+e); out.print(""); }finally{ out.close(); } } }
1.2 CheckUtil 验证类
package com.syx.modules.system.wechat; import java.security.MessageDigest; import java.util.Arrays; public class CheckUtil { private static final String token = "imooc"; public static boolean checkSignature(String signature,String timestamp,String nonce){ String[] arr = new String[]{token,timestamp,nonce}; //排序 Arrays.sort(arr); //生成字符串 StringBuffer content = new StringBuffer(); for(int i=0;i<arr.length;i++){ content.append(arr[i]); } //sha1加密 String temp = getSha1(content.toString()); return temp.equals(signature); } /** * Sha1加密方法 * @param str * @return */ public static String getSha1(String str) { if (str == null || str.length() == 0) { return null; } char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; try { MessageDigest mdTemp = MessageDigest.getInstance("SHA1"); mdTemp.update(str.getBytes("UTF-8")); byte[] md = mdTemp.digest(); int j = md.length; char buf[] = new char[j * 2]; int k = 0; for (int i = 0; i < j; i++) { byte byte0 = md[i]; buf[k++] = hexDigits[byte0 >>> 4 & 0xf]; buf[k++] = hexDigits[byte0 & 0xf]; } return new String(buf); } catch (Exception e) { return null; } } }
1.3 微信配置
1.4 如果配置验证就能通过
2. 获取access_token 方法getAccess_token()
现在缓存中获取token 没有在去重新获取,获取到放到缓存中设置过期时间
@Service public class WechatService { private Logger logger = LoggerFactory.getLogger(WechatService.class); @Autowired private RedisUtil redisUtil; @Autowired private RestTemplate restTemplate; @Value("${wechat.w_appid}") private String appid; @Value("${wechat.w_appsecret}") String appsecret; @Value("${wechat.w_access_token_url}") String access_toekn_url; @Value("${wechat.w_template_url}") private String template_url; @Value("${wechat.w_ticket}") private String ticket_url; @Value("${wechat.w_qrcode}") private String w_qrcode; /** * @Description 获取token */ public String getAccess_token(){ // 这个是在redis中取token 如果没有 重新获取 String token = (String) redisUtil.get("access_token_app"); if (token==null||"".equals(token)) { String acc_token = access_toekn_url; String appId = appid; acc_token = acc_token.replace("APPID",appId).replace("APPSECRET",appsecret); System.out.println(acc_token); try { /* * 这里用的是RestTemplate 方式发送http请求 */ WechatResponseEntity wechatResponseEntity = restTemplate.getForObject(acc_token, WechatResponseEntity.class); System.out.println(wechatResponseEntity.toString()); token = wechatResponseEntity.getAccess_token(); redisUtil.set("access_token_app",token,7100); } catch (Exception e) { e.printStackTrace(); } } return token; } /** * @Description 推送的模板消息公共接口 * @param appid 接收人的appid * @param template_id 使用过的公众号的模板id * @param mapD 组装的推送数据 (包含key:模板配置的{{xxx.DATA}}的xxx,value:是的对应的内容) */ public boolean pushMessageWechat(String appid,String template_id,Map<String,String> mapD){ String token = getAccess_token(); Map<String,Object> map = assembleTemplateMap(appid,template_id,mapD); String temp_url = template_url.replace("ACCESS_TOKEN",token); /* * 这里用的是RestTemplate 方式发送http请求 */ WechatResponseEntity wechatResponseEntity = restTemplate.postForObject(temp_url,map, WechatResponseEntity.class); if ("0".equals(wechatResponseEntity.getErrcode())){ return true; }else { logger.error("***** 公众号模板消息发送异常 *****",wechatResponseEntity.toString()); } return false; } /** * @Description 组装发送消息内容 */ public Map<String,Object> assembleTemplateMap(String appid,String template_id,Map<String,String> mapData){ Map<String,Object> map = new HashMap<>(); map.put("touser",appid); map.put("template_id",template_id); Map<String,Object> map1 = new HashMap<>(); /* *遍历mapDATA 组装模板消息的data */ for(String key:mapData.keySet()){ Map<String,String> mapNew = new HashMap<>(); mapNew.put("value",mapData.get(key)); mapNew.put("color","#173177"); map1.put(key,mapNew); } map.put("data",map1); return map; } /** * @Description 获取公众号的临时二维码,带参数当前用户的userId */ public String getQRCodeURL (String userId){ String token = getAccess_token(); String url = ticket_url.replace("TOKEN",token); Map<String,Object> map = new HashMap<>(); map.put("expire_seconds",86400); //字符串的参数类型 map.put("action_name","QR_STR_SCENE"); Map<String,Object> map2 = new HashMap<>(); //字符串的参数类型 map2.put("scene_str",userId); Map<String,Object> map1 = new HashMap<>(); map1.put("scene",map2); map.put("action_info",map1); WechatResponseEntity we = restTemplate.postForObject(url,map, WechatResponseEntity.class); System.out.println(we); return w_qrcode.replace("TICKET", URLEncoder.encode(we.getTicket())); } }
接收实体类
@Data public class WechatResponseEntity { private String access_token; private String expires_in; private String errcode; private String errmsg; private String msgid; private String ticket; private Integer expire_seconds; private String url; }
3.生成公众号二维码url 调用 getQRCodeURL() 方法可以获取去二维码url
4.关注成功会有WechatController.dopsot()方法回调,回调还需要增加几个基础方法
添加的jar包 一个是RestTemplate 的包 一个转xml的包
<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency> <dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.10</version> </dependency>
/** * 消息封装类 * @author Stephen * */ public class MessageUtil { public static final String MESSAGE_TEXT = "text"; public static final String MESSAGE_NEWS = "news"; public static final String MESSAGE_IMAGE = "image"; public static final String MESSAGE_VOICE = "voice"; public static final String MESSAGE_MUSIC = "music"; public static final String MESSAGE_VIDEO = "video"; public static final String MESSAGE_LINK = "link"; public static final String MESSAGE_LOCATION = "location"; public static final String MESSAGE_EVNET = "event"; public static final String MESSAGE_SUBSCRIBE = "subscribe";//用户未关注时,进行关注后的事件推送 public static final String MESSAGE_UNSUBSCRIBE = "unsubscribe"; public static final String MESSAGE_CLICK = "CLICK"; public static final String MESSAGE_VIEW = "VIEW"; public static final String MESSAGE_SCANCODE= "scancode_push"; public static final String FOLLOW_OFFICIAL_ACCOUNT = "SCAN";//用户已关注时的事件推送 /** * xml转为map集合 * @param request * @return * @throws IOException * @throws DocumentException */ public static Map<String, String> xmlToMap(HttpServletRequest request) throws IOException, DocumentException{ Map<String, String> map = new HashMap<String, String>(); SAXReader reader = new SAXReader(); InputStream ins = request.getInputStream(); Document doc = reader.read(ins); Element root = doc.getRootElement(); List<Element> list = root.elements(); for(Element e : list){ map.put(e.getName(), e.getText()); } ins.close(); return map; } /** * 将文本消息对象转为xml * @param textMessage * @return */ public static String textMessageToXml(TextMessage textMessage){ XStream xstream = new XStream(); xstream.alias("xml", textMessage.getClass()); return xstream.toXML(textMessage); } /** * 组装文本消息 * @param toUserName * @param fromUserName * @param content * @return */ public static String initText(String toUserName,String fromUserName,String content){ TextMessage text = new TextMessage(); text.setFromUserName(toUserName); text.setToUserName(fromUserName); text.setMsgType(MessageUtil.MESSAGE_TEXT); text.setCreateTime(System.currentTimeMillis()); text.setContent(content); return textMessageToXml(text); } /** * 主菜单 * @return */ public static String menuText(){ StringBuffer sb = new StringBuffer(); sb.append("欢迎您的关注,你已经可以正常接收到消息提醒"); return sb.toString(); } public static String firstMenu(){ StringBuffer sb = new StringBuffer(); sb.append("本套课程介绍微信公众号开发,主要涉及公众号介绍、编辑模式介绍、开发模式介绍等"); return sb.toString(); } public static String secondMenu(){ StringBuffer sb = new StringBuffer(); sb.append("慕课网是垂直的互联网IT技能免费学习网站。以独家视频教程、在线编程工具、学习计划、问答社区为核心特色。在这里,你可以找到最好的互联网技术牛人,也可以通过免费的在线公开视频课程学习国内领先的互联网IT技术。"); sb.append("慕课网课程涵盖前端开发、PHP、Html5、Android、iOS、Swift等IT前沿技术语言,包括基础课程、实用案例、高级分享三大类型,适合不同阶段的学习人群。以纯干货、短视频的形式为平台特点,为在校学生、职场白领提供了一个迅速提升技能、共同分享进步的学习平台。"); return sb.toString(); } public static String threeMenu(){ StringBuffer sb = new StringBuffer(); sb.append("词组翻译使用指南\n\n"); sb.append("使用示例:\n"); sb.append("翻译足球\n"); sb.append("翻译中国足球\n"); sb.append("翻译football\n\n"); sb.append("回复?显示主菜单。"); return sb.toString(); } // /** // * 图文消息转为xml // * @param newsMessage // * @return // */ // public static String newsMessageToXml(NewsMessage newsMessage){ // XStream xstream = new XStream(); // xstream.alias("xml", newsMessage.getClass()); // xstream.alias("item", new News().getClass()); // return xstream.toXML(newsMessage); // } // /** // * 图片消息转为xml // * @param imageMessage // * @return // */ // public static String imageMessageToXml(ImageMessage imageMessage){ // XStream xstream = new XStream(); // xstream.alias("xml", imageMessage.getClass()); // return xstream.toXML(imageMessage); // } // /** // * 音乐消息转为xml // * @param musicMessage // * @return // */ // public static String musicMessageToXml(MusicMessage musicMessage){ // XStream xstream = new XStream(); // xstream.alias("xml", musicMessage.getClass()); // return xstream.toXML(musicMessage); // } // /** // * 图文消息的组装 // * @param toUserName // * @param fromUserName // * @return // */ // public static String initNewsMessage(String toUserName,String fromUserName){ // String message = null; // List<News> newsList = new ArrayList<News>(); // NewsMessage newsMessage = new NewsMessage(); // // News news = new News(); // news.setTitle("慕课网介绍"); // news.setDescription("慕课网是垂直的互联网IT技能免费学习网站。以独家视频教程、在线编程工具、学习计划、问答社区为核心特色。在这里,你可以找到最好的互联网技术牛人,也可以通过免费的在线公开视频课程学习国内领先的互联网IT技术。慕课网课程涵盖前端开发、PHP、Html5、Android、iOS、Swift等IT前沿技术语言,包括基础课程、实用案例、高级分享三大类型,适合不同阶段的学习人群。"); // news.setPicUrl("http://zapper.tunnel.mobi/Weixin/image/imooc.jpg"); // news.setUrl("www.imooc.com"); // // newsList.add(news); // // newsMessage.setToUserName(fromUserName); // newsMessage.setFromUserName(toUserName); // newsMessage.setCreateTime(System.currentTimeMillis()); // newsMessage.setMsgType(MESSAGE_NEWS); // newsMessage.setArticles(newsList); // newsMessage.setArticleCount(newsList.size()); // // message = newsMessageToXml(newsMessage); // return message; // } // /** // * 组装图片消息 // * @param toUserName // * @param fromUserName // * @return // */ // public static String initImageMessage(String toUserName,String fromUserName){ // String message = null; // Image image = new Image(); // image.setMediaId("JTH8vBl0zDRlrrn2bBnMleySuHjVbMhyAo0U2x7kQyd1ciydhhsVPONbnRrKGp8m"); // ImageMessage imageMessage = new ImageMessage(); // imageMessage.setFromUserName(toUserName); // imageMessage.setToUserName(fromUserName); // imageMessage.setMsgType(MESSAGE_IMAGE); // imageMessage.setCreateTime(System.currentTimeMillis()); // imageMessage.setImage(image); // message = imageMessageToXml(imageMessage); // return message; // } // /** // * 组装音乐消息 // * @param toUserName // * @param fromUserName // * @return // */ // public static String initMusicMessage(String toUserName,String fromUserName){ // String message = null; // Music music = new Music(); // music.setThumbMediaId("WsHCQr1ftJQwmGUGhCP8gZ13a77XVg5Ah_uHPHVEAQuRE5FEjn-DsZJzFZqZFeFk"); // music.setTitle("see you again"); // music.setDescription("速7片尾曲"); // music.setMusicUrl("http://zapper.tunnel.mobi/Weixin/resource/See You Again.mp3"); // music.setHQMusicUrl("http://zapper.tunnel.mobi/Weixin/resource/See You Again.mp3"); // // MusicMessage musicMessage = new MusicMessage(); // musicMessage.setFromUserName(toUserName); // musicMessage.setToUserName(fromUserName); // musicMessage.setMsgType(MESSAGE_MUSIC); // musicMessage.setCreateTime(System.currentTimeMillis()); // musicMessage.setMusic(music); // message = musicMessageToXml(musicMessage); // return message; // } }
public class TextMessage extends BaseMessage { private String Content; private String MsgId; public String getContent() { return Content; } public void setContent(String content) { Content = content; } public String getMsgId() { return MsgId; } public void setMsgId(String msgId) { MsgId = msgId; } }
/** * 消息父类 * @author Stephen * */ public class BaseMessage { //接收方微信号 private String ToUserName; //发送方微信号 private String FromUserName; //创建时间 private long CreateTime; //消息类型 private String MsgType; public String getToUserName() { return ToUserName; } public void setToUserName(String toUserName) { ToUserName = toUserName; } public String getFromUserName() { return FromUserName; } public void setFromUserName(String fromUserName) { FromUserName = fromUserName; } public long getCreateTime() { return CreateTime; } public void setCreateTime(long createTime) { CreateTime = createTime; } public String getMsgType() { return MsgType; } public void setMsgType(String msgType) { MsgType = msgType; } }
applicat.yml配置文件
wechat: #公众号的appid w_appid: # 公众号的appsecret w_appsecret: #获取token的url w_access_token_url: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET #模板id w_template_id: #推送消息的接口url w_template_url: https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN #获取二维码ticket的url w_ticket: https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN #通过ticket换取二维码 的url(TICKET记得进行UrlEncode) w_qrcode: https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET http: #最大连接数 maxTotal: 100 #并发数 defaultMaxPerRoute: 20 #创建连接的最长时间 connectTimeout: 3000 #从连接池中获取到连接的最长时间 connectionRequestTimeout: 1000 #数据传输的最长时间 socketTimeout: 10000 #提交请求前测试连接是否可用 staleConnectionCheckEnabled: true #可用空闲连接过期时间,重用空闲连接时会先检查是否空闲时间超过这个时间,如果超过,释放socket重新建立 validateAfterInactivity: 3000000
5.推送模板消息 WechatService.pushMessageWechat()方法发送模板消息
微信模板配置
{{first.DATA}}
工单类型:{{typeName.DATA}}
工单标题:{{typeTitle.DATA}}
流程编号:{{orderNO.DATA}}
操作时间: {{updateTime.DATA}}
{{remark.DATA}}
6.RestTemplate 集成
博客地址:https://blog.csdn.net/yu1xue1fei/article/details/111610455