100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > java微信公众号自动回复文字加图片

java微信公众号自动回复文字加图片

时间:2022-05-16 15:46:18

相关推荐

java微信公众号自动回复文字加图片

java微信公众号自动回复文字加图片

开发流程详细流程,附上代码:第一步服务器(url)接口配置服务器(url)接口配置,此步骤就是微信授权接口的过程,如果域名都不改变,微信只会校验一次。此请求微信文档中说明了是get请求。第二步发送文字我这边的需求是发送的文字中要带上微信名称,所以还需要调用微信的获取accessToken接口跟获取用户信息接口。获取用户信息接口中需要openId,在用户点击关注微信公众号时,微信会自动发送过来。微信为什么会知道咱们的接口呢,因为咱们已经在第一步授权了接口,微信会以默认post请求的方式调用相同的路由,所以我们还得写一个post请求的接口。注:finally中是发送图片的动作。调用事件中运用了抽象工厂的设计模式,此处还有待优化。第三步组合图片与文字信息并返回3、4、5步骤是在一个代码块中,所以此处进行详细分解。注:调用方式已在第二步说明

前提:第一次发布文章,如有写的不好的地方请指正或代码上可以优化的地方也请各路大神多多指教。顺带想吐槽下微信开发文档真的太恶心了,第一次接触下来让人头晕眼花,通过查阅各种资料后理清了思路,在此写上总结。

开发流程

服务器(url)接口配置发送文字(根据微信公众号文档“接收事件推送”事件接口编写代码)生成二维码(账号管理下的“生成带参数的二维码”事件接口编写代码)根据UI的图组合微信头像、微信名字、公众号二维码并调用素材管理下的“新增临时素材”接口上传图片发送图片(调用消息管理下的客服消息下的客服接口-发消息(图片)接口)进行裂变(调用“扫描带参数二维码事件”)(此步骤还未开发完,需根据业务需求变动)

详细流程,附上代码:

第一步服务器(url)接口配置

服务器(url)接口配置,此步骤就是微信授权接口的过程,如果域名都不改变,微信只会校验一次。此请求微信文档中说明了是get请求。

/*** 微信消息接收和token验证* @param request* @param response*/@RequestMapping(value = "/weChatToken", method = RequestMethod.GET)public void weChatToken(HttpServletRequest request, HttpServletResponse response){boolean isGet = request.getMethod().toLowerCase().equals("get");if (isGet) {// 微信加密签名String signature = request.getParameter("signature");// 时间戳String timestamp = request.getParameter("timestamp");// 随机数String nonce = request.getParameter("nonce");// 随机字符串String echostr = request.getParameter("echostr");// 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败if (signature != null && WeChatUtil.checkSignature(signature, timestamp, nonce)) {try {PrintWriter print = response.getWriter();print.write(echostr);print.flush();} catch (IOException e) {log.error("微信消息接收和token验证异常",e);}}}}

第二步发送文字

我这边的需求是发送的文字中要带上微信名称,所以还需要调用微信的获取accessToken接口跟获取用户信息接口。获取用户信息接口中需要openId,在用户点击关注微信公众号时,微信会自动发送过来。微信为什么会知道咱们的接口呢,因为咱们已经在第一步授权了接口,微信会以默认post请求的方式调用相同的路由,所以我们还得写一个post请求的接口。注:finally中是发送图片的动作。调用事件中运用了抽象工厂的设计模式,此处还有待优化。

2.1 接口详解

/*** 微信公众号 -->* 消息管理 -->*接收普通消息、接收事件推送、被动回复用户消息** @param request* @return*/@ResponseBody@RequestMapping(value = "/weChatToken", method = RequestMethod.POST)public String responseEvent(HttpServletRequest request){WeChatMsgService weChatMsgService = null;Map<String, String> requestMessageMap = null;try {// 获取到请求参数requestMessageMap = XMLUtil.xmlToMap(request);log.info("获取到参数为:{}",requestMessageMap);// 判断请求参数中 MsgType的值或判断Event值String returnMessage = "";if (WeChatUtil.REQ_MESSAGE_TYPE_EVENT.equals(requestMessageMap.get("MsgType"))) {// 如果后期有取消订阅事件,此处方法可以去除ifif (WeChatUtil.EVENT_TYPE_SUBSCRIBE.equals(requestMessageMap.get("Event"))) {weChatMsgService = getType(requestMessageMap.get("Event"));returnMessage = weChatMsgService.getReturnMessage(requestMessageMap);}}return returnMessage;} catch (Exception e) {log.error("微信公众号自动回复异常",e);}finally {if (WeChatUtil.EVENT_TYPE_SUBSCRIBE.equals(requestMessageMap.get("Event"))) {weChatMsgService = weChatCustomerServiceImgMessageServiceImpl;weChatMsgService.getReturnMessage(requestMessageMap);}}return "";}

2.1.2 使用设计模式调用不同的业务逻辑(default有待修改)

此处注入了业务层

@Autowired

private WeChatMsgService weChatTextMessageServiceImpl;

/*** 根据类型选择所需的业务处理逻辑* @param type*/private WeChatMsgService getType(String type){switch (type) {case WeChatUtil.EVENT_TYPE_SUBSCRIBE:return weChatTextMessageServiceImpl;default:return null;}}

2.1.3 调用业务层组合返回的信息(微信文档要求返回的信息为xml的形式)

@Service@Slf4jpublic class WeChatTextMessageServiceImpl implements WeChatMsgService {@Autowiredprivate WeiXinService weiXinService;@Overridepublic String getReturnMessage(Map<String,String> requestMessageMap) {// 获取access_tokenWeiXinAccessTokenDto accessToken = weiXinService.getAccessToken();//获取用户信息WeiXinUserInfoDto userInfo = null;if (accessToken.getAccess_token() != null) {userInfo = weiXinService.getUnionID(accessToken.getAccess_token(), requestMessageMap.get("FromUserName"));}TextMessageResponseVo textMessageResponseVo = new TextMessageResponseVo();textMessageResponseVo.setFromUserName(requestMessageMap.get("ToUserName"));textMessageResponseVo.setToUserName(requestMessageMap.get("FromUserName"));textMessageResponseVo.setMsgType("text");textMessageResponseVo.setCreateTime(System.currentTimeMillis());textMessageResponseVo.setContent("欢迎" + userInfo.getNickname() + "11111");String result = WeChatUtil.messageToXml(textMessageResponseVo);return result;}}

2.2 调用微信获取accessToken 的接口(业务层涉及到其他逻辑,所以就不全部粘出来了)

@Overridepublic WeiXinAccessTokenDto getAccessToken() {Map<String,String> reqParams = new HashMap<>();reqParams.put("appid","appid");reqParams.put("secret","secret");reqParams.put("grant_type","client_credential");String result = HttpUtil.sendGetJoint(accessTokenUrl, reqParams);// 将返回的字符串转为对象WeiXinAccessTokenDto weiXinAccessTokenDto = JSON.parseObject(result, WeiXinAccessTokenDto.class);return weiXinAccessTokenDto;}

2.3 通过微信接口获取用户信息(微信获取用户信息的接口有三种,此处只举例一种)

@Overridepublic WeiXinUserInfoDto getUnionID(String accessToken, String openId) {Map<String,String> reqParams = new HashMap<>();reqParams.put("lang","zh_CN");reqParams.put("access_token",accessToken);reqParams.put("openid",openId);String result = HttpUtil.sendGetJoint(unionIDUrl, reqParams);log.info("获取微信用户信息为:{}",result);// 将返回的字符串转为对象WeiXinUserInfoDto weiXinUserInfoDto = JSON.parseObject(result, WeiXinUserInfoDto.class);return weiXinUserInfoDto;}

2.4 返回信息实体类(此处用了Swagger,不用的童鞋可以删除注解。还要引入lombok)

/*** @author: hugh_lin* @date: /06/26 18:53* @Description: 微信公众号返回消息公共实体类* @Modifed:**/@Getter@Setterpublic class BaseWeChatResponseVo {@ApiModelProperty("开发者微信号")private String ToUserName;@ApiModelProperty("发送方帐号(一个OpenID)")private String FromUserName;@ApiModelProperty("消息创建时间 (整型)")private Long CreateTime;@ApiModelProperty("消息类型,event或text、image、voice、video、music、news")private String MsgType;}/*** @author: hugh_lin* @date: /06/26 18:58* @Description: 微信公众号返回消息实体* @Modifed:**/@Getter@Setterpublic class TextMessageResponseVo extends BaseWeChatResponseVo{@ApiModelProperty("文本消息内容")private String Content;}

2.5 微信工具类

import com.thoughtworks.xstream.XStream;import com.thoughtworks.xstream.core.util.QuickWriter;import com.thoughtworks.xstream.io.HierarchicalStreamWriter;import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;import com.thoughtworks.xstream.io.xml.XppDriver;import com.youchengjvhe.weishangketang.model.vo.wechat.TextMessageResponseVo;import lombok.extern.slf4j.Slf4j;import java.io.*;import .URL;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import java.util.Arrays;import java.util.Formatter;/*** @author: hugh_lin* @date: /06/26 15:45* @Description: 微信消息处理工具类* @Modifed:**/@Slf4jpublic class WeChatUtil {//请求消息的类型------------------------------------------/*** 文本*/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_VOICE = "voice";/*** 视频*/public static final String REQ_MESSAGE_TYPE_VIDEO = "video";/*** 小视频*/public static final String REQ_MESSAGE_TYPE_SHORTVIDEO = "shortvideo";/*** 地理位置*/public static final String REQ_MESSAGE_TYPE_LOCATION = "location";/*** 链接*/public static final String REQ_MESSAGE_TYPE_LINK = "link";/*** 事件推送*/public static final String REQ_MESSAGE_TYPE_EVENT = "event";/*** 事件类型--订阅*/public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";/*** 取消订阅*/public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";/*** 已关注用户扫描带参数二维码*/public static final String EVENT_TYPE_SCAN = "SCAN";/*** 上报地理位置*/public static final String EVENT_TYPE_LOCATION = "LOCATION";/*** 自定义菜单事件*/public static final String EVENT_TYPE_CLICK = "CLICK";//响应消息类型/*** 文本*/public static final String RESP_MESSAGE_TYPE_TEXT = "text";/*** 图片*/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 RESP_MESSAGE_TYPE_MUSIC = "music";/*** 图文*/public static final String RESP_MESSAGE_TYPE_ARTICLE = "news";//---------------------------------------------------------------------/*** 与测试账号接口配置信息中的Token要一致* 测试token,微信公众号内可自行配置*/private static String token = "******";/*** 验证签名* @param signature* @param timestamp* @param nonce* @return*/public static boolean checkSignature(String signature, String timestamp, String nonce) {String[] arr = new String[] {token, timestamp, nonce };// 将token、timestamp、nonce三个参数进行字典序排序Arrays.sort(arr);StringBuilder content = new StringBuilder();for (int i = 0; i < arr.length; i++) {content.append(arr[i]);}MessageDigest md = null;String tmpStr = null;try {md = MessageDigest.getInstance("SHA-1");// 将三个参数字符串拼接成一个字符串进行sha1加密byte[] digest = md.digest(content.toString().getBytes());tmpStr = byteToHex(digest );} catch (NoSuchAlgorithmException e) {e.printStackTrace();}// 将sha1加密后的字符串可与signature对比,标识该请求来源于微信return tmpStr != null ? tmpStr.equals(signature) : false;}/*** 十六进制字节数组转为字符串* @param hash* @return*/private static String byteToHex(final byte[] hash) {Formatter formatter = new Formatter();for (byte b : hash) {formatter.format("%02x", b);}String result = formatter.toString();formatter.close();return result;}/*** 扩展xstream使其支持CDATA*/private static XStream xstream = new XStream(new XppDriver() {@Overridepublic HierarchicalStreamWriter createWriter(Writer out) {return new PrettyPrintWriter(out) {// 对所有xml节点的转换都增加CDATA标记boolean cdata = true;@Override@SuppressWarnings("unchecked")public void startNode(String name, Class clazz) {super.startNode(name, clazz);}@Overrideprotected void writeText(QuickWriter writer, String text) {if (cdata) {writer.write("<![CDATA[");writer.write(text);writer.write("]]>");} else {writer.write(text);}}};}});/*** 文本消息转换成xml*/public static String messageToXml(TextMessageResponseVo textMessageResponseVo) {xstream.alias("xml", textMessageResponseVo.getClass());return xstream.toXML(textMessageResponseVo);}/*** 链接url下载图片* @param urlList* @param path*/public static void downloadPicture(String urlList,String path) {URL url = null;try {url = new URL(urlList);DataInputStream dataInputStream = new DataInputStream(url.openStream());FileOutputStream fileOutputStream = new FileOutputStream(new File(path));ByteArrayOutputStream output = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int length;while ((length = dataInputStream.read(buffer)) > 0) {output.write(buffer, 0, length);}fileOutputStream.write(output.toByteArray());dataInputStream.close();fileOutputStream.close();} catch (Exception e) {log.error("根据url下载图片异常",e);}}}

第三步组合图片与文字信息并返回

3、4、5步骤是在一个代码块中,所以此处进行详细分解。注:调用方式已在第二步说明

3.1 整体方法(一些注入的路径,可以自行查阅微信公众号文档)

/*** @author: hugh_lin* @date: /06/28 16:37* @Description: 公众号客服消息(回复图片)* @Modifed:**/@Servicepublic class WeChatCustomerServiceImgMessageServiceImpl implements WeChatMsgService {/*** 图片请求路径*/@Value("${test.imgFileUrl}")private String imgFileUrl;/*** 图片生成路径*/@Value("${test.imgFilePath}")private String imgFilePath;@Value("${weixin.getDownloadQrCodeUrl}")private String downloadQrCodeUrl;@Autowiredprivate WeiXinService weiXinService;@Overridepublic String getReturnMessage(Map<String, String> requestMessageMap) {// 获取accessTokenWeiXinAccessTokenDto accessToken = weiXinService.getAccessToken();//获取用户信息WeiXinUserInfoDto userInfo = null;if (accessToken.getAccess_token() != null) {userInfo = weiXinService.getUnionID(accessToken.getAccess_token(), requestMessageMap.get("FromUserName"));}// 生成微信公众号二维码WeChatQrCodeDto qrCode = weiXinService.getQrCode(accessToken.getAccess_token(), userInfo.getOpenid());/* 下载二维码图片 */// 1.生成下载地址StringBuffer qrCodeUrl = new StringBuffer();qrCodeUrl.append(downloadQrCodeUrl);qrCodeUrl.append("?ticket=");qrCodeUrl.append(qrCode.getTicket());// 2.生成文件名并生成下载到本地的路径StringBuffer imgPath = new StringBuffer();imgPath.append("D:\\");imgPath.append(UUID.randomUUID());imgPath.append(".jpg");WeChatUtil.downloadPicture(qrCodeUrl.toString(),imgPath.toString());// 合成图片ImageUtil.generateCompositeImages("D:\\aaa.jpg",userInfo.getHeadimgurl(),imgPath.toString(),userInfo.getNickname());// 上传到微信公众号素材库,将数据转成json格式并取出需要的数据String weChatMedia = weiXinService.getWeChatMediaId(accessToken.getAccess_token(), imgPath.toString(), "image");JSONObject media = JSONObject.parseObject(weChatMedia);// 发送客服消息weiXinService.sendCustomerServiceMessage(accessToken.getAccess_token(),userInfo.getOpenid(),"image","media_id",media.getString("media_id"));return null;}}

3.2 生成公众号二维码(此处为什么要调用微信接口生成呢,因为如果进行裂变的话,需要调用微信扫描二维码事件,所以更方便)

@Overridepublic WeChatQrCodeDto getQrCode(String accessToken, String content) {// 拼接urlStringBuffer sb = new StringBuffer();sb.append(qrCodeUrl);sb.append("?access_token=");sb.append(accessToken);// 生成json数据进行请求JSONObject sceneStrJson = new JSONObject();sceneStrJson.put("scene_str",content);JSONObject sceneJson = new JSONObject();sceneJson.put("scene",sceneStrJson);JSONObject reqJson = new JSONObject();reqJson.put("action_name","QR_LIMIT_STR_SCENE");reqJson.put("action_info",sceneJson);String result = HttpUtil.sendPost(sb.toString(), reqJson.toString());log.info("获取微信公众号二维码信息为:{}",result);// 将返回的字符串转为对象WeChatQrCodeDto weChatQrCodeDto = JSON.parseObject(result, WeChatQrCodeDto.class);return weChatQrCodeDto;}

3.3下载二维码图片到本地(因为要组合图片,需要用到。如何获取微信开发文档中有提及,此处就不多说了。此处调用的是WeChatUtil,在第二步中已写上)

3.4合成图片(因为调用了Graphics画图工具,组合的图片大小及位置可自行根据UI图进行调整)

/*** @author: hugh_lin* @date: /06/28 09:52* @Description: 图片工具类* @Modifed:**/@Slf4jpublic class ImageUtil {/*** 生成组合图片(此处根据需求需要组成三张图片)* @param args* args[0] backgroundImage 背景图路径* args[1] weChatAvatar 微信头像路径* args[2] qrCode 二维码路径* args[3] 微信名*/public static void generateCompositeImages(String... args){try {//创建初始图片BufferedImage img = new BufferedImage(1242, 2208, BufferedImage.TYPE_INT_RGB);//读取本地图片BufferedImage backgroundImage = ImageIO.read(new File(args[0]));//读取微信头像图片BufferedImage weChatAvatar = ImageIO.read(new URL(args[1]));//读取本地图片BufferedImage qrCode = ImageIO.read(new File(args[2]));//开启画图Graphics g = img.getGraphics();//将微信头像跟二维码设置为圆角(根据需求变更)weChatAvatar = setClip(weChatAvatar,100);qrCode = setClip(qrCode, 100);// 绘制缩小后的图g.drawImage(backgroundImage.getScaledInstance(1242,2208, Image.SCALE_DEFAULT), 0, 0, null);g.drawImage(weChatAvatar.getScaledInstance(85, 85, Image.SCALE_DEFAULT), 93, 35, null);g.drawImage(qrCode.getScaledInstance(370, 370, Image.SCALE_DEFAULT), 203, 1800, null);g.setColor(Color.black);g.setFont(new Font("微软雅黑", Font.PLAIN, 24));//绘制文字g.drawString(args[3], 214, 58);g.dispose();ImageIO.write(img, "jpg", new File(args[2]));} catch (Exception e) {log.error("生成组合图片异常",e);}}/*** 图片设置圆角* @param srcImage* @return* @throws IOException*/private static BufferedImage setRadius(BufferedImage srcImage) throws IOException{int radius = (srcImage.getWidth() + srcImage.getHeight()) / 6;return setRadius(srcImage, radius, 2, 5);}/*** 图片设置圆角* @param srcImage* @param radius* @param border* @param padding* @return* @throws IOException*/private static BufferedImage setRadius(BufferedImage srcImage, int radius, int border, int padding) throws IOException{int width = srcImage.getWidth();int height = srcImage.getHeight();int canvasWidth = width + padding * 2;int canvasHeight = height + padding * 2;BufferedImage image = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_ARGB);Graphics2D gs = image.createGraphics();gs.setComposite(AlphaComposite.Src);gs.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);gs.setColor(Color.WHITE);gs.fill(new RoundRectangle2D.Float(0, 0, canvasWidth, canvasHeight, radius, radius));gs.setComposite(AlphaComposite.SrcAtop);gs.drawImage(setClip(srcImage, radius), padding, padding, null);if(border !=0){gs.setColor(Color.GRAY);gs.setStroke(new BasicStroke(border));gs.drawRoundRect(padding, padding, canvasWidth - 2 * padding, canvasHeight - 2 * padding, radius, radius);}gs.dispose();return image;}/*** 图片切圆角* @param srcImage* @param radius* @return*/private static BufferedImage setClip(BufferedImage srcImage, int radius){int width = srcImage.getWidth();int height = srcImage.getHeight();BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);Graphics2D gs = image.createGraphics();gs.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);gs.setClip(new RoundRectangle2D.Double(0, 0, width, height, radius, radius));gs.drawImage(srcImage, 0, 0, null);gs.dispose();return image;}}

3.5上传到微信公众号素材库(因为调用发送客服图片信息接口时需要用到参数,所以只能先上传到素材库)

@Overridepublic String getWeChatMediaId(String accessToken, String imgPath, String type) {try {File file=new File(imgPath);if (!file.exists() || !file.isFile()) {return null;}// 拼接请求地址StringBuffer sb = new StringBuffer();sb.append(mediaUrl);sb.append("?access_token=");sb.append(accessToken);sb.append("&type=");sb.append(type);String result = HttpUtil.weChatFodderPost(sb.toString(), file);return result;} catch (Exception e) {log.error("素材流关闭异常",e);}return null;}

3.6发送客服图片消息

/*** 客服接口-发消息(此方法目前只支持:* 发送文本消息、发送图片消息、发送语音消息、发送视频消息、发送音乐消息)* @param args* args[0] accessToken* args[1] OPENID* args[2] msgtype* args[3] content、media_id.....* args[4] 内容*/@Overridepublic void sendCustomerServiceMessage(String... args) {// 拼接urlStringBuffer sb = new StringBuffer();sb.append(customerServiceUrl);sb.append("?access_token=");sb.append(args[0]);// 生成json数据进行请求JSONObject contentJson = new JSONObject();contentJson.put(args[3],args[4]);JSONObject reqJson = new JSONObject();reqJson.put("touser",args[1]);reqJson.put("msgtype",args[2]);reqJson.put(args[2],contentJson);HttpUtil.sendPost(sb.toString(), reqJson.toString());}

最后,遇到个bug,因为需求是先返回文字再返回图片。测试的时候,偶然发生了先返回图片再返回文字的情况。从我的认知角度讲,会先执行finally块,再执行return,也是先返回return的消息,再返回finally中的消息。遇到此问题是否由网络延迟或等原因造成的还有待考究。

最后附上个人微信:a373011739 ,谢谢各位大佬的观看,鞠躬。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。