时间:2022-11-06 09:20:41 | 栏目:JAVA代码 | 点击:次
本次就是记录一下我的开发过程,不是教程,纯属自己做个笔记。
现在项目有个需求,需要用户在公众号发送图片消息的时候,我后台程序能接收到这个图片,并用ai处理图片并返回信息。
这个url配置了以后,所以微信公众号的消息都会被推送到这个url对应的接口上,Token感觉没啥用,随便写一个完事,加密随机生成,不加密消息的话也没用。
最坑爹的是在提交配置的时候,微信要根据你填的这个url验证一下token认证,而这个url实际是后台处理消息的接口,搞不清楚咋肥四,我就先把这个接口写成验证token的,等提交完配置再改回我的处理消息接口代码。验证token这里随便找了段代码,亲测有效。
@RequestMapping(value = "/testToken") public void testToken(HttpServletRequest request, HttpServletResponse response) throws Exception { String token="tokenxxx"; logger.error("WechatController ---- WechatController"); System.out.println("========WechatController========= "); logger.info("请求进来了..."); Enumeration pNames = request.getParameterNames(); while (pNames.hasMoreElements()) { String name = (String) pNames.nextElement(); String value = request.getParameter(name); // out.print(name + "=" + value); String log = "name =" + name + " value =" + value; logger.error(log); } String signature = request.getParameter("signature");/// 微信加密签名 String timestamp = request.getParameter("timestamp");/// 时间戳 String nonce = request.getParameter("nonce"); /// 随机数 String echostr = request.getParameter("echostr"); // 随机字符串 PrintWriter out = response.getWriter(); out.print(echostr); out.close(); }
官方文档在这里:文本消息 | 微信开放文档
也就是说微信会给你发送xml格式的消息,后台需要能接收这个消息
要接收xml消息和以后发送消息啥的,需要先引入一些依赖:
<dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.10.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.10.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.10.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.jaxrs</groupId> <artifactId>jackson-jaxrs-xml-provider</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!--httpUtil--> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpcore</artifactId> <version>4.4.10</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.6</version> </dependency> <!--解析微信的消息--> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6</version> </dependency> <dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.4</version> </dependency>
以为对应的图标消息是这样的:
所以写个消息的实体类:
package com.bomc.recordLife.entry; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; @Data @NoArgsConstructor @AllArgsConstructor @JacksonXmlRootElement(localName = "xml") public class WxMessageImg { @JacksonXmlProperty(localName = "ToUserName") private String ToUserName; @JacksonXmlProperty(localName = "FromUserName") private String FromUserName; @JacksonXmlProperty(localName = "CreateTime") private long CreateTime; @JacksonXmlProperty(localName = "MsgType") private String MsgType; @JacksonXmlProperty(localName = "Event") private String Event; @JacksonXmlProperty(localName = "PicUrl") private String PicUrl; @JacksonXmlProperty(localName = "MediaId") private String MediaId; @JacksonXmlProperty(localName = "MsgId") private long MsgId; @JacksonXmlProperty(localName = "Content") private String Content; }
还是先记录一下如果不怕超时直接给用户返回消息的情况:
@PostMapping(value = "analyzeImg2",consumes = "text/xml", produces = "text/xml;charset=utf-8") @ResponseBody public Object analyzeImg2(@RequestBody WxMessageImg wxMessageImg){ //拼一下要返回的信息对象 WxMessageImg resultMessage=new WxMessageImg(); try { //忽略图片逻辑,直接闹个结果 String resultStr="处理完图片返回的信息"; String openid = wxMessageImg.getFromUserName(); //用户 openid String mpid = wxMessageImg.getToUserName(); //公众号原始 ID resultMessage.setToUserName(openid); resultMessage.setFromUserName(mpid); resultMessage.setCreateTime(new Date().getTime()); resultMessage.setContent(resultStr); resultMessage.setMsgType("text"); //用这个工具类处理出一串玩意直接返回 String outMesStr = WxMessageUtil.textMessageToXml(resultMessage); System.out.println(outMesStr); return outMesStr; } catch (Exception e) { e.printStackTrace(); } return null; }
工具类:
package com.bomc.recordLife.util; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.bomc.recordLife.entry.WxMessageImg; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.DomDriver; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @Description: 消息工具类 * @Author: lst * @Date 2020-08-19 */ public class WxMessageUtil { /** * 返回消息类型:文本 */ public static final String RESP_MESSAGE_TYPE_TEXT = "text"; /** * 返回消息类型:音乐 */ public static final String RESP_MESSAGE_TYPE_MUSIC = "music"; /** * 返回消息类型:图文 */ public static final String RESP_MESSAGE_TYPE_NEWS = "news"; /** * 返回消息类型:图片 */ public static final String RESP_MESSAGE_TYPE_Image = "image"; /** * 返回消息类型:语音 */ public static final String RESP_MESSAGE_TYPE_Voice = "voice"; /** * 返回消息类型:视频 */ public static final String RESP_MESSAGE_TYPE_Video = "video"; /** * 请求消息类型:文本 */ public static final String REQ_MESSAGE_TYPE_TEXT = "text"; /** * 请求消息类型:图片 */ public static final String REQ_MESSAGE_TYPE_IMAGE = "image"; /** * 请求消息类型:链接 */ public static final String REQ_MESSAGE_TYPE_LINK = "link"; /** * 请求消息类型:地理位置 */ public static final String REQ_MESSAGE_TYPE_LOCATION = "location"; /** * 请求消息类型:音频 */ public static final String REQ_MESSAGE_TYPE_VOICE = "voice"; /** * 请求消息类型:视频 */ public static final String REQ_MESSAGE_TYPE_VIDEO = "video"; /** * 请求消息类型:推送 */ 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(自定义菜单点击事件) */ public static final String EVENT_TYPE_CLICK = "CLICK"; /** * 事件类型:VIEW(自定义菜单URl视图) */ public static final String EVENT_TYPE_VIEW = "VIEW"; /** * 事件类型:LOCATION(上报地理位置事件) */ public static final String EVENT_TYPE_LOCATION = "LOCATION"; /** * 事件类型:LOCATION(上报地理位置事件) */ public static final String EVENT_TYPE_SCAN = "SCAN"; /** * @Description: 解析微信发来的请求(XML) * @param @param request * @param @return * @param @throws Exception * @author dapengniao * @date 2016年3月7日 上午10:04:02 */ public static Map<String, String> parseXml(HttpServletRequest request) { // 将解析结果存储在HashMap中 Map<String, String> map = new HashMap<String, String>(); // 读取输入流 SAXReader reader = new SAXReader(); Document document = null; InputStream inputStream = null; try { // 从request中取得输入流 inputStream = request.getInputStream(); document = reader.read(inputStream); // 得到xml根元素 Element root = document.getRootElement(); // 得到根元素的所有子节点 List<Element> elementList = root.elements(); // 遍历所有子节点 elementList.stream().forEach(element -> { map.put(element.getName(), element.getStringValue()); }); } catch (DocumentException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { // 释放资源 if(null != inputStream){ try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } return map; } /** * @Description: 文本消息对象转换成xml * @param @param textMessage * @param @return * @author dapengniao * @date 2016年3月8日 下午4:13:22 */ public static String textMessageToXml(WxMessageImg textMessage) { XStream xStream = new XStream(new DomDriver("UTF-8")); //XStream xStream = new XStream(); xStream.alias("xml", textMessage.getClass()); return xStream.toXML(textMessage); } }
如果用postman调用的需要这样:
以上就是接收消息和被动回复消息,但是有个大坑,一开始我想着处理完消息在直接返回信息回去,但是处理时间总是超过5秒,每次超过5秒它就报一下服务出现故障,一共请求三次,三次都超时就给你报三次故障。
后来客户不愿意报这玩意儿,就只好改成直接返回success再异步调用处理图片方法,处理完再用客服消息主动给用户发消息。
因为要处理图片花费的时间比较多,所以开个线程搞成异步调用处理图片再推送消息,这样的话直接返回字符串success
在controller里面写个方法接收一下,用@RequestBody 直接把发来的xml变成对象
@RequestMapping(value = "analyzeImg") @ResponseBody public String analyzeImg(@RequestBody WxMessageImg wxMessageImg) throws Exception { //异步调用,先直接返回success,不然会显示程序异常 new Thread(new Runnable() { public void run() { getImgData(wxMessageImg); } }).start(); return "SUCCESS"; //return null; }
public Object getImgData(WxMessageImg wxMessageImg){ try { //忽略图片逻辑,直接闹个结果 String resultStr="处理完图片返回的信息"; String openid = wxMessageImg.getFromUserName(); //用户 openid postMessage(openid,resultStr); } catch (Exception e) { e.printStackTrace(); } return null; }
微信的破文档找个推送客户消息太费劲了,网上都到的基本都是发送模板消息,但我只是想发个文本消息呀!!
后来才找到发送客服消息的url是:https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=
发送模板消息的url是:https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=
官方接口介绍
发送文本信息
所以我们需要的就是这个url和发送的文本格式,就这么简单几个值而已:
public String postMessage(String openid,String content) throws Exception { //String access_token=WxMessageUtil.obtainAccessToken(); //appid和appsecret为公众号里面的 String tokenData = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+appId+"&secret="+appSecret; // 返回的用户信息json字符串 String result=HttpUtil.doGet(tokenData); System.out.println(result); JSONObject jsonObject = JSON.parseObject(result); //先获取access_token String access_token=String.valueOf(jsonObject.get("access_token")); System.out.println(access_token); //消息推送接口 String path = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=" + access_token; JSONObject jsonData = new JSONObject(); jsonData.put("touser", openid); jsonData.put("msgtype", "text"); JSONObject text = new JSONObject(); text.put("content",content); jsonData.put("text",text); System.out.println(jsonData); System.out.println(path); //HttpUtil.doPostJson(path, jsonData.toJSONString()); HttpRequest.sendPost(path, jsonData.toJSONString()); return "SUCCESS"; //return null; }
这样就能成功给用户异步回复消息,不会担心超过5秒报异常的问题了
package com.bomc.recordLife.util; import java.io.*; import java.net.URL; import java.net.URLConnection; import java.util.List; import java.util.Map; public class HttpRequest { /** * 向指定URL发送GET方法的请求 * * @param url * 发送请求的URL * @param param * 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @return URL 所代表远程资源的响应结果 */ public static String sendGet(String url, String param) { String result = ""; BufferedReader in = null; try { String urlNameString = url + "?" + param; URL realUrl = new URL(urlNameString); // 打开和URL之间的连接 URLConnection connection = realUrl.openConnection(); // 设置通用的请求属性 connection.setRequestProperty("accept", "*/*"); connection.setRequestProperty("connection", "Keep-Alive"); connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); // 建立实际的连接 connection.connect(); // 获取所有响应头字段 Map<String, List<String>> map = connection.getHeaderFields(); // 遍历所有的响应头字段 for (String key : map.keySet()) { System.out.println(key + "--->" + map.get(key)); } // 定义 BufferedReader输入流来读取URL的响应 in = new BufferedReader(new InputStreamReader( connection.getInputStream())); String line; while ((line = in.readLine()) != null) { result += line; } } catch (Exception e) { System.out.println("发送GET请求出现异常!" + e); e.printStackTrace(); } // 使用finally块来关闭输入流 finally { try { if (in != null) { in.close(); } } catch (Exception e2) { e2.printStackTrace(); } } return result; } /** * 向指定 URL 发送POST方法的请求 * * @param url * 发送请求的 URL * @param param * 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @return 所代表远程资源的响应结果 */ public static String sendPost(String url, String param) { PrintWriter out = null; BufferedReader in = null; String result = ""; try { URL realUrl = new URL(url); // 打开和URL之间的连接 URLConnection conn = realUrl.openConnection(); // 设置通用的请求属性 conn.setRequestProperty("accept", "application/json, text/javascript, */*; q=0.01"); conn.setRequestProperty("Accept-Encoding", "gzip, deflate"); conn.setRequestProperty("Connection", "keep-alive"); conn.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.8"); conn.setRequestProperty("Content-Length", "80"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); // 发送POST请求必须设置如下两行 conn.setDoOutput(true); conn.setDoInput(true); // 获取URLConnection对象对应的输出流 OutputStreamWriter outWriter = new OutputStreamWriter(conn.getOutputStream(), "utf-8"); out = new PrintWriter(outWriter); // 发送请求参数 out.print(param); // flush输出流的缓冲 out.flush(); // 定义BufferedReader输入流来读取URL的响应 in = new BufferedReader( new InputStreamReader(conn.getInputStream(),"UTF-8")); String line; while ((line = in.readLine()) != null) { result += line; } } catch (Exception e) { System.out.println("发送 POST 请求出现异常!"+e); e.printStackTrace(); } //使用finally块来关闭输出流、输入流 finally{ try{ if(out!=null){ out.close(); } if(in!=null){ in.close(); } } catch(IOException ex){ ex.printStackTrace(); } } return result; } public static void main(String[] args) { //发送 GET 请求 /* String s=HttpRequest.sendGet("http://localhost:6144/Home/RequestString", "key=123&v=456"); System.out.println(s);*/ //发送 POST 请求 /* String sr=HttpRequest.sendPost("http://www.cheshouye.com/api/weizhang/get_all_config",""); JSONObject jsStr = JSONObject.fromObject(sr); JSONArray jsonarray = jsStr.getJSONArray("configs"); for(int i=0;i<jsonarray.size();i++){ JSONObject ob1=(JSONObject)jsonarray.get(i); Integer provinceId=ob1.getInt("provice_id"); String provinceName=ob1.getString("provice_name"); String provinceShortName=ob1.getString("provice_short_name"); JSONArray jsonarray2 = ob1.getJSONArray("citys"); } System.out.println(jsonarray);*/ //(JSONObject)jsonarray[i] } }