涉及的知识点:接收微信消息事件、生成唯一二维码(产品地址+埋码)、图片上传sftp、两张图片的合并、图片上传微信服务、推送客服消息、记录埋码信息等等。
一、前言:
事情是这样的: 前段时间,周五下午,我正在幻想美好的周末的时候,突然收到我们业务发送的一条微信消息,有个需求需要沟通下。
需求是这样的:业务部门要做一个活动,用来推广一款年金产品。
活动的具体要求是这样的:公司员工在公众号输入的关键字,生成一个唯一带有二维码的推广图片(推广图片+二维码)。客户扫描二维码跳转到产品页面,出单以后要定位是谁推广的。
活动的上线时间:周一上午。还能怎么办?周末加班的命~。心里默念“加班使我快乐”“我爱加班”。
二、需求分析
明白了业务要做什么,接下来就是怎么实现的问题了。需求主要有如下几个问题:
1,我怎么可以定为是哪个员工发送的消息呢?
用户在微信后台发送一条消息,在后台,我可以知道用户发送了什么,也可以知道用户的唯一openid,但是openid和员工并没有关联关系。
当时想的是,做一个绑定关系页面,让每一个想推广的用户先把个人信息和微信进行绑定,这样我就有了openid和用户的绑定关系。当然对于用户体验不好。
第二种方案就是通过关键字来定义。
2,关键字怎么定义呢?
获取到用户发送的消息以后,要判断出,用户是谁?用户发送消息是要干嘛?不能用户随便发送一个消息我都给推送一个图片。
当时想的是,活动名称+手机号,因为手机号是唯一的。业务部门需要有员工和手机号的数据。
最后业务确定用活动名称+姓名,确定公司暂时没有重名的员工。
三、具体实现:
1、获取微信推送的消息事件,在后台做分析。
前提条件是微信必须是开发者模式,需要在微信后台配置回到接口地址
2、接收微信推送的消息事件
微信通过post方式推送报文格式如下:
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>1348831860</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[this is a test]]></Content> <MsgId>1234567890123456</MsgId> </xml>
报文中Content就是用户发送的消息,如果用户发送的是“某某活动+用户名”,我们就处理这条消息;如果不是,我们可以做固定字段回复,或者转的客服平台,或者对接智能机器人也可以。
处理用用户消息的时候,需要识别是这次活动,还要考虑扩展性,后期同样类型的活动就不需要开发了,数据库配置下就可以实现。
源码如下:
//获取请求参数 Map<String, String> map = WeChatResplyUtil.parseXmlToMap(request.getInputStream()); logger.info("微信请求的数据:"+JSONObject.fromObject(map)); String msgType = map.get("MsgType"); String userName = map.get("ToUserName");//公众号 String fromUserName = map.get("FromUserName");//用户openid //文本消息类型 if(WeChatResplyUtil.MESSAGE_TEXT.equals(msgType)){ //获取用户回复的文本消息 String content = map.get("Content"); //判断 if(StringUtils.isNotBlank(content)&&content.contains("+")){ //获取配置信息,配置活动关键字,活动图片地址,产品地址 List<NsType> nsType = nsTypeDao.findByTypeSortAndLikeTypeCode("PRODUCT_URL_QR", keyWord.split("\\+")[0]); if(nsType!=null||nsType.size()>=0){ //多线程处理业务 threadpool.execute(....); return ""; } } //如果不是活动,可以处理关键字回复,人工客服,智能机器人等
微信有个要求,获取到用户消息以后,必须5s内响应。确定是活动的关键字,直接响应微信""或者"success"。开启多线程处理业务。
2、根据用户发送的消息生成唯一的二维码,并上传服务器。
获取活动的产品地址productUrl 和唯一的标识randomString 生成二维码(不是微信接口生成的二维码),并上传服务器。然后活动图片和二维码合并。
//获取产品地址 String productUrl = nsType.get(0).getRelationtypecode(); //获取推广图片 String tgImageUrl = nsType.get(0).getTypeDesc(); //获取随机字符串 String randomString = "rh_ryb_"+UUID.randomUUID(); //图片上传途径(别的需求配置过的) String filePath = sysConfigService.getConfig("IDNO_IMAGE_URL") + "QRCode/"; File file = new File(filePath); if (!file.exists()) { file.mkdirs(); } //生成二维码 String qrUrl = filePath+randomString+".png"; boolean qrStatus = QRCodeUtil.generateQRCodeImage(productUrl+randomString, 350, 350, qrUrl); if(qrStatus){ WXControllerLog.INFO("生成二维码成功,二维码地址qrUrl:"+qrUrl); } //合并图片 BufferedImage b = ImageUtil.loadImageLocal(qrUrl); BufferedImage d = ImageUtil.loadImageLocal(tgImageUrl); String img_suc = filePath+randomString+"_suc.jpg"; ImageUtil.writeImageLocal(img_suc,ImageUtil.modifyImagetogeter(b, d)); WXControllerLog.INFO("合并图片,活动图片的地址tgImageUrl:"+tgImageUrl); WXControllerLog.INFO("合并图片,合并后的地址img_suc:"+img_suc);
合成后的图片如下:
3、推广图片和二维码合并生成一个图片工具类。
public class ImageUtil { /** * 把两张图片合并 */ private static Font font = new Font("宋体", Font.PLAIN, 12); // 添加字体的属性设置 private static Graphics2D g = null; private static int fontsize = 0; private static int x = 0; private static int y = 0; /** * 导入本地图片到缓冲区 */ public static BufferedImage loadImageLocal(String imgName) { try { return ImageIO.read(new File(imgName)); } catch (IOException e) { System.out.println(e.getMessage()); } return null; } /** * 导入网络图片到缓冲区 */ public static BufferedImage loadImageUrl(String imgName) { try { URL url = new URL(imgName); return ImageIO.read(url); } catch (IOException e) { System.out.println(e.getMessage()); } return null; } /** * 生成新图片到本地 */ public static void writeImageLocal(String newImage, BufferedImage img) { if (newImage != null && img != null) { try { File outputfile = new File(newImage); ImageIO.write(img, "jpg", outputfile); } catch (IOException e) { System.out.println(e.getMessage()); } } } /** * 设定文字的字体等 */ public static void setFont(String fontStyle, int fontSize) { fontsize = fontSize; font = new Font(fontStyle, Font.PLAIN, fontSize); } /** * 修改图片,返回修改后的图片缓冲区(只输出一行文本) */ public static BufferedImage modifyImage(BufferedImage img, Object content, int x, int y) { try { int w = img.getWidth(); int h = img.getHeight(); g = img.createGraphics(); g.setBackground(Color.WHITE); g.setColor(Color.orange);//设置字体颜色 if (font != null) g.setFont(font); // 验证输出位置的纵坐标和横坐标 if (x >= h || y >= w) { x = h - fontsize + 2; y = w; } else { x = x; y = y; } if (content != null) { g.drawString(content.toString(), x, y); } g.dispose(); } catch (Exception e) { System.out.println(e.getMessage()); } return img; } /** * 修改图片,返回修改后的图片缓冲区(输出多个文本段) xory:true表示将内容在一行中输出;false表示将内容多行输出 */ public BufferedImage modifyImage(BufferedImage img, Object[] contentArr, int x, int y, boolean xory) { try { int w = img.getWidth(); int h = img.getHeight(); g = img.createGraphics(); g.setBackground(Color.WHITE); g.setColor(Color.RED); if (this.font != null) g.setFont(this.font); // 验证输出位置的纵坐标和横坐标 if (x >= h || y >= w) { this.x = h - this.fontsize + 2; this.y = w; } else { this.x = x; this.y = y; } if (contentArr != null) { int arrlen = contentArr.length; if (xory) { for (int i = 0; i < arrlen; i++) { g.drawString(contentArr[i].toString(), this.x, this.y); this.x += contentArr[i].toString().length() * this.fontsize / 2 + 5;// 重新计算文本输出位置 } } else { for (int i = 0; i < arrlen; i++) { g.drawString(contentArr[i].toString(), this.x, this.y); this.y += this.fontsize + 2;// 重新计算文本输出位置 } } } g.dispose(); } catch (Exception e) { System.out.println(e.getMessage()); } return img; } /** * 修改图片,返回修改后的图片缓冲区(只输出一行文本) */ public BufferedImage modifyImageYe(BufferedImage img) { try { int w = img.getWidth(); int h = img.getHeight(); g = img.createGraphics(); g.setBackground(Color.WHITE); g.setColor(Color.blue);//设置字体颜色 if (this.font != null) g.setFont(this.font); g.drawString("www.hi.baidu.com?xia_mingjian", w - 85, h - 5); g.dispose(); } catch (Exception e) { System.out.println(e.getMessage()); } return img; } public static BufferedImage modifyImagetogeter(BufferedImage b, BufferedImage d) { try { int w = 110;//b.getWidth(); int h = 110;//b.getHeight(); g = d.createGraphics(); g.drawImage(b, 515, 830, w, h, null); g.dispose(); } catch (Exception e) { System.out.println(e.getMessage()); } return d; } }
4,生成的图片上传微信服务器,获取media_id,数据库记录信息。
推送微信图片消息,需要获取到图片的media_id,所以需要把合成的图片上传微信获取media_id。并保存信息,再次推送图片消息的时候可以直接数据库查询。数据库记录唯一标识、medis_id、用户消息、公众号标识等
//获取token WxConfig wxConfig = wxConfigDao.queryByType("accessToken"); String token =wxConfig.getExtends0(); //图片上传至微信服务器 String uploadMeida = WXUtil.Uploadmaterial( "image", img_suc, token); WXControllerLog.INFO("图片上传至微信服务器,微信返回信息:"+uploadMeida); JSONObject parseObject = JSONObject.parseObject(uploadMeida); try { WxAutoReply wr = wxAutoReplyDao.findAutoReplyByOpenid(keyWord, tencent,fromUserName); if(wr==null){//保存信息 wr = new WxAutoReply(); wr.setKeyword(keyWord); wr.setKeyValue(parseObject.getString("media_id")); wr.setTencent(tencent); wr.setMsgtype("image"); wr.setExtends3(keyWord.split("\\+")[1]); wr.setExtends4(randomString); wr.setExtends6(fromUserName); wxAutoReplyDao.saveWxAutoReply(wr); }else{ wr.setKeyword(keyWord); wr.setKeyValue(parseObject.getString("media_id")); wr.setTencent(tencent); wr.setMsgtype("image"); wr.setExtends3(keyWord.split("\\+")[1]); wr.setExtends4(randomString); wr.setExtends6(fromUserName); wxAutoReplyDao.updateWxAutoReply(wr); } } catch (Exception e) { e.printStackTrace(); }
5、根据media_id给用户退送推广图片。
调用微信接口推送消息
//推送信息 String sendMsg ="{\"touser\":\""+fromUserName+"\",\"msgtype\": \"image\",\"image\":{\"media_id\":\""+parseObject.getString("media_id")+"\"}}"; String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token="+token; String responseJson = HttpUtil.postXmlRequest(url, sendMsg,"utf-8"); WXControllerLog.INFO("SendWxMessageThread发送微信消息,返回信息:"+responseJson);
6、最后的效果,如图:
7、用户出单以后,数据库记录埋码标识。
只要在用户出单的时候,记录唯一标识randomString就可以了
注:其实需要处理的问题还是挺多的,篇幅有限就只粘贴关键代码了,我觉得上面的工具栏贼好用就直接全部分享了。
微信开发中遇到的相关问题可以留言或者私信,有问必答。
本文暂时没有评论,来添加一个吧(●'◡'●)