最近在做一个saas模式的产品开发。微信官方账号只有一个,但是客户有很多种,每个客户都有自己的用户。现在有一个需求,当用户扫描某客户提供的微信官方账号二维码时,会出现相应的客户欢迎信息,并显示该客户的LOGO界面。前提是每个客户的LOGO都不一样。是不是有点绕弯?说白了,就像你是一个微信官方账号,你需要被多个商家使用,每个商家都有自己的用户群。那个用户是扫码关注微信官方账号,进入微信官方账号需要显示每个商家自己唯一的LOGO。

  正常关注微信官方账号二维码图片可在微信官方账号开发者后台下载。但这是统一的二维码,无法区分商家。这时候就需要我们自己生成微信官方账号的关注二维码了。这个二维码和网上自动生成的功能不一样。毕竟你扫码之后,还要连接第三方腾讯。

  首先,JAVA编辑器生成QR码界面

  参数微信公众平台接口https://mp.weixin.qq.com/wiki? t=resource/RES _ mainid=MP 1443433542

  我们生成一个带有永久字符串的二维码。我们只需要通过一个商家的ID来识别用户在关注时扫描了哪个二维码,从而显示对应的商家LOGO。

  控制器层

   @ApiOperation(value=创建微信官方账号二维码)@ responsebody公众结果创建二维码(@ apiparam (name= type ,value= type (1:临时二维码;2:带数字永久参数的二维码;3:永久参数为字符串的二维码))@ requestparam () IntegerType,@ apiparam (name= validtime ,value=临时二维码的有效时间(秒,最多2592000秒(30天)))@ requestparam (required=false)整数有效时间,@ apiparam (name= intparameter ,Value= number parameter )@ request param(required=false)整数int参数,@ apiparam (name= strparameter ,Value= string}业务逻辑层

   //获取微信官方账号二维码私有最终静态字符串Get _ Perpetual _ QR Code _ URL= https://API . weixin . QQ.com/CGI-bin/QR Code/create?“访问令牌=访问令牌”;//获取车票私有最终静态字符串对应的二维码映射Get _ TICKET _ QR Code _ URL= https://MP . weixin . QQ.com/CGI-bin/show QR Code?ticket=;public Result create QR code(Integer type,Integer validTime,Integer IntParameter,String strParameter,String userId){ String access token=微信pushservice . getgzhaccesstokendefaultcfg();//获取微信官方账号令牌字符串请求URL=Get _ persistent _ QR code _ URL . replace( access _ token ,访问令牌);//替换URL的参数JSON object JSON=new JSON object();JSON object actioninfo JSON=new JSON object();JSON object sceneJson=new JSON object();string fileName=/sys/QR code/ str parameter 。jpg ;//图片的下载路径if(type==3){//永久生成带字符串参数的二维码json.put (action _ name , QR _ limit _ str _ scene );//固定值scenejon.put (scene _ str ,str parameter);//strParameter是商家ID的参数,也是要和二维码一起生成的参数ActionInfoJSON.put (scene ,scene JSON);json.put(action_info ,actionInfoJson);//{ action _ name 3360 QR _ limit _ str _ scene , action _ info 3360 { scene 3360 { scene _ str 3360 test } }调用微信官方账号接口的参数格式,json值mapstring,objectmap=}}

  uestUtils.json(requestUrl, json);//POST方法调用第三方公众号接口 String ticket = map.containsKey("ticket")?map.get("ticket").toString():"";//从返回参数中获取二维码ticket值 if(org.apache.commons.lang.StringUtils.isNotEmpty(ticket)){//使用ticket的值再去调用另外一个接口,下载二维码图片 File file1 = new File(filePath+"sys/QRCode/");//自己要把图片下载的路径 if(!file1.exists()){//判断文件路径是否存在,不存在就创建 file1.mkdirs(); } downloadPicture(GET_TICKET_QRCODE_URL+ URLEncoder.encode(ticket),filePath+fileName);//下载图片 } } return ResultUtil.success(fileName);}/** * 获取默认公众号访问令牌 */public String getGzhAccessTokenDefaultCfg() { if (StringUtils.isEmpty(defaultGzhAppId)

   StringUtils.isEmpty(defaultGzhSecret)) { initialize();//读取配置文件里公众号的值(appId和appSecret),这两个值在公众号里有,公众号的接口大多需要这两个参数去获取Token } return getGzhAccessToken(defaultGzhAppId,defaultGzhSecret);}// 获取企业号access_tokenprivate final static String company_access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=CORPID&secret=CORPSECRET";// 获取开放平台的access_token、openid等认证信息private final static String GET_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";/** * 获取微信公众号访问令牌 * @param appId appId * @param appSecret appSecret */public String getGzhAccessToken(String appId, String appSecret) { String accessToken = ""; try { accessToken = redisService.getDataService("WX_AccessToken").getData().toString();//从Redis缓存中获取Token, } catch (Exception e) { log.error("从缓存微信公众号token失败"); } if (StringUtils.isEmpty(accessToken)) {//如果缓存没有Token,或过期了,将重新去获取一次 String requestUrl = company_access_token_url.replace("CORPID", appId).replace("CORPSECRET", appSecret);//替换参数 Map<String, Object> map = RequestUtils.json(requestUrl, null);//POST接口调用第三方接口 // 如果请求成功 if (null != map) { System.out.print("###############################" + map.toString()); try { accessToken = (String) map.get("access_token"); redisService.strAdd("WX_AccessToken", accessToken, 700);// (存到缓存中,避免经常去拿token,将近两小时) } catch (Exception e) { log.error("获取微信公众号token失败,或token保存至缓存失败 ####accessToken" + accessToken); } } } return accessToken;}

  POST和GET请求工具类,在获取图片时,需要使用GET

  import org.springframework.http.MediaType;import java.io.*;import java.net.HttpURLConnection;import java.net.URL;import java.net.URLConnection;import java.util.Map;/** * 工具类 */public class RequestUtils { @SuppressWarnings("unchecked") public static Map<String, Object> json(String url, Map<String, Object> params){ String content = null; if(params != null){ content = JsonUtils.convert(params); } String result = post(url, content, MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON); if(result != null){ return JsonUtils.convert(result, Map.class); } return null; } public static void main(String[] args) { String post = post("http://www.baidu.com", "", MediaType.APPLICATION_JSON); System.out.println(post); } public static String post(String strURL, String content) { return post(strURL, content, null); } public static String post(String strURL, String content, MediaType mediaType) { return post(strURL, content, mediaType, mediaType); } public static String post(String strURL, String content, MediaType sendMediaType, MediaType receiveMediaType) { try { URL url = new URL(strURL);// 创建连接 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setDoOutput(content != null); connection.setDoInput(true); connection.setUseCaches(false); connection.setInstanceFollowRedirects(true); connection.setRequestMethod("POST"); // 设置请求方式 if(sendMediaType != null) { connection.setRequestProperty("Accept", receiveMediaType.toString()); // 设置接收数据的格式 } if(sendMediaType != null) { connection.setRequestProperty("Content-Type", sendMediaType.toString()); // 设置发送数据的格式 } connection.connect(); if(content != null) { OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream(), "UTF-8"); // utf-8编码 out.write(content); out.flush(); out.close(); } int code = connection.getResponseCode(); System.out.println(code); InputStream is = connection.getInputStream(); if (is == null) { is = connection.getErrorStream(); } // 读取响应 int length = (int) connection.getContentLength();// 获取长度 if (length != -1) { byte[] data = new byte[length]; byte[] temp = new byte[1024]; int readLen = 0; int destPos = 0; while ((readLen = is.read(temp)) > 0) { System.arraycopy(temp, 0, data, destPos, readLen); destPos += readLen; } String result = new String(data, "UTF-8"); // utf-8编码 return result; } } catch (IOException e) { e.printStackTrace(); } return null; // 自定义错误信息 } public static String get(String url) { BufferedReader in = null; try { URL realUrl = new URL(url); // 打开和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.setConnectTimeout(5000); connection.setReadTimeout(5000); // 建立实际的连接 connection.connect(); // 定义 BufferedReader输入流来读取URL的响应 in = new BufferedReader(new InputStreamReader(connection.getInputStream())); StringBuffer sb = new StringBuffer(); String line; while ((line = in.readLine()) != null) { sb.append(line); } return sb.toString(); } catch (Exception e) { e.printStackTrace(); }finally { try { if (in != null) { in.close(); } } catch (Exception e2) { e2.printStackTrace(); } } return null; }}

  接口写完后,你可以单元测试调用一下,生成的二维码就可以扫一下,是不是会跳到你对应的公众号界面。自定义二维码已经生成了,但现在跟普通的二维码没区别,因为没有触发事件。接下来,编写一个能让公众号调用你的方法的接口。让公众号告诉你,有人关注或取消关注。

  二、事件触发接口

  controller层

  //微信推送事件 url@RequestMapping("/openwx/getticket")public void getTicketMessage(HttpServletRequest request, HttpServletResponse response) throws Exception { wechatPushService.getTicketMessage(request,response);}

  事件触发逻辑层,看下面代码时,先看官方的文档,这样更能理解返回参数:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140543

   public String getTicketMessage(HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("1.收到微信服务器消息"); Map<String, String> wxdata=parseXml(request); if(null != wxdata){ String key = wxdata.get("FromUserName")+ "__" + wxdata.get("ToUserName")+ "__" + wxdata.get("MsgId") + "__" + wxdata.get("CreateTime"); Result keyRedisResult = redisService.getDataService(key); System.out.println(keyRedisResult.getStatus()); if(keyRedisResult.getStatus() == 200){//防止公众重复推送消息,所以第一次把消息送缓存中,如果存在了就不处理 return null; } redisService.strAdd(key,"1",3600);//不存在的话,放缓存里,记得加一个失效时间,避免一直存在,占用资源 String Event = wxdata.get("Event"); System.out.println("Event"+Event); if(Event.equals("subscribe")

   Event.equals("SCAN")){//扫码带参数的二维码进入的 String EventKey = wxdata.get("EventKey");//获取参数 String FromUserName = wxdata.get("FromUserName");//OpenID if(EventKey.indexOf("_") != -1){//初次关注 EventKey = EventKey.substring(EventKey.indexOf("_")+1); } System.out.println("EventKey:"+EventKey); Map map = (Map)result.getData(); TextMessage textMessage=new TextMessage(); textMessage.setToUserName(wxdata.get("FromUserName")); //这里的ToUserName 是刚才接收xml中的FromUserName textMessage.setFromUserName(wxdata.get("ToUserName")); //这里的FromUserName 是刚才接收xml中的ToUserName 这里一定要注意,否则会出错 textMessage.setCreateTime(new Date().getTime()); textMessage.setMsgType("text"); textMessage.setContent("欢迎您关注"+map.get("departmentTopName")+"电子送达"); MessageUtil messageUtil = MessageUtil.getInstance(); String xml=messageUtil.textMessageToXml(textMessage); System.out.println("xml:"+xml); response.setCharacterEncoding("UTF-8"); PrintWriter out = response.getWriter(); out.print(xml);//用户关注时,发一个欢迎语给用户 out.close(); } } return null; }

  三、触发接口写完后,需要去公众号后台去设置你的接口服务器,让公众号知道你的接口地址。

  修改配置,服务器地址为你部署的地址,必须对方能连上,而且需要80端口(如果80端口被占用,可以使用Nginx做转发),在配置的时候,公众号会尝试调用,调用不到你的接口,会直接提醒你。

  配置完后,点击启动。这个时候你再去关注你刚才生成的参数二维码,就会有反映了。记得在事件触发接口中,增加你的业务。用户关注或取消关注时,你要做什么。

  另外,在启动配置后,你会发现,你的公众号自定义菜单不见了,这个时候不要慌。接下往下看。

  启动菜单

  这个时候公众号上的小菜单就有了。但公众号后台自定义菜单还是看不到?那怎么修改菜单呢?

  很简单,先把前面开启的服务器配置给停止了,然后再改你的菜单,修改完菜单后,你再开始服务器。到此就完成了生成及事件监听的过程