微信开发相关知识点
由于个人公众中竟然需要用到微信相关开发,故整理下,以便后期查阅。
个人开发过程中积累的工具类Utilities
package com.project.wx.util; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import javax.servlet.http.HttpServletRequest; import java.math.BigDecimal; import java.net.InetAddress; import java.net.URL; import java.net.UnknownHostException; import java.security.MessageDigest; import java.text.NumberFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Created by zhu on 2017/4/19. */ public class Utilities { public static void main(String[] args) { } public static ResponseEntity returnResponseEntity(String result){ return new ResponseEntity(result,Utilities.setAccessControl(), HttpStatus.OK); } public static String filter(String str) { if(str.trim().isEmpty()){ return str; } String pattern="[\ud83c\udc00-\ud83c\udfff]|[\ud83d\udc00-\ud83d\udfff]|[\u2600-\u27ff]"; String reStr=""; Pattern emoji=Pattern.compile(pattern); Matcher emojiMatcher=emoji.matcher(str); str=emojiMatcher.replaceAll(reStr); return str; } public static long getCurrentTimeStamp(){ long time = System.currentTimeMillis(); return time; } public static long date2TimeStamp(String date){ try { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.parse(date).getTime(); } catch (Exception e) { e.printStackTrace(); } return 0; } public static boolean timeCompare(String beginTime,String endTime){ boolean isBefore=false; SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); try { Date bt = sdf.parse(beginTime); Date et=sdf.parse(endTime); if (bt.before(et)){ //表示bt小于et isBefore=true; } } catch (ParseException e) { e.printStackTrace(); } return isBefore; } public static String getTimeTransform(String time){ long create_time=date2TimeStamp(time); long currentTimeStamp=getCurrentTimeStamp(); //得到距离当前时间 long show_hour=(currentTimeStamp-create_time)/1000/60/60; String show_time=show_hour+"小时前"; if(show_hour==0){ show_time="刚刚"; } if(show_hour>=24&&show_hour<24*7){ //大于一天,小于一周 show_hour=show_hour/24; show_time=show_hour+"天前"; } if(show_hour>=24*7&&show_hour<24*30){ //大于一周,小于一月 show_hour=show_hour/24/7; show_time=show_hour+"周前"; } if(show_hour>=24*30&&show_hour<24*30*12){ //大于一月,小于一年 show_hour=show_hour/24/30; show_time=show_hour+"月前"; } if(show_hour>=24*30*12){ //大于一年 show_hour=show_hour/24/30/12; show_time=show_hour+"年前"; } return show_time; } public static String timeStamp2Date(String timestamp){ try { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date=new Date(Long.parseLong(timestamp)); return sdf.format(date); } catch (Exception e) { e.printStackTrace(); } return ""; } public static String getCurrentTime() { Date inputDate = new Date(); SimpleDateFormat outputFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String datetime = outputFormat.format(inputDate); return datetime; } public static String checkNull(String input){ String result=""; if(input==null||input.trim().equals("")||input.trim().equalsIgnoreCase("null")){ result=""; }else { result=input.trim(); } return result; } public static boolean checkEmpty(String input){ if(input==null||input.trim().equals("")||input.trim().equalsIgnoreCase("null")){ return true; }else { return false; } } public static String getCurrentTimeSubOrAddHour(int hour) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Calendar nowTime = Calendar.getInstance(); nowTime.add(Calendar.HOUR, hour); String datetime = sdf.format(nowTime.getTime()); return datetime; } public static int getDefaultValue(HttpServletRequest request, String paraName, int defaultValue){ int result; try { result=Integer.valueOf(request.getParameter(paraName)); } catch (NumberFormatException e) { result=defaultValue; } return result; } public static String getTimestamp() { Long timestamp = System.currentTimeMillis(); return timestamp.toString(); } /** * 加密算法 */ public static String encode( String str) { String ALGORITHM = "MD5"; char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; if (str == null) { return null; } try { MessageDigest messageDigest = MessageDigest.getInstance(ALGORITHM); messageDigest.update(str.getBytes("utf-8")); return getFormattedText(messageDigest.digest()); } catch (Exception e) { throw new RuntimeException(e); } } private static String getFormattedText(byte[] bytes) { int len = bytes.length; StringBuilder buf = new StringBuilder(len * 2); char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; // 把密文转换成十六进制的字符串形式 for (int j = 0; j < len; j++) { buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]); buf.append(HEX_DIGITS[bytes[j] & 0x0f]); } return buf.toString(); } /** * 产生8位随机数 * @return */ public static String random8(){ int[] i=new int[8]; int count=0; String randomNum=""; while(count<8){ //抽取的数值小于char类型的“z”(122) int t=(int)(Math.random()*122); if((t>=0&t<=9)|(t>=65&t<=90)|(t>=97&t<=122)){ i[count]=t; count++; } }for(int k=0;k<8;k++){ if(i[k]>=0&i[k]<=9){ randomNum=randomNum+i[k]; } else{ randomNum=randomNum+(char)i[k]; } } return randomNum; } public static HttpHeaders setAccessControl(){ HttpHeaders httpHeaders=new HttpHeaders(); httpHeaders.set("Content-Type", "application/json;charset=UTF-8"); httpHeaders.set("Access-Control-Allow-Origin","*"); return httpHeaders; } public static String getIpAddr(HttpServletRequest request){ String ipAddress = request.getHeader("x-forwarded-for"); if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("Proxy-Client-IP"); } if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("WL-Proxy-Client-IP"); } if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){ //根据网卡取本机配置的IP InetAddress inet=null; try { inet = InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); } ipAddress= inet.getHostAddress(); } } //对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15 if(ipAddress.indexOf(",")>0){ ipAddress = ipAddress.substring(0,ipAddress.indexOf(",")); } } return ipAddress; } public static void getTimeSub(){ SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); format.setLenient(false); Date date1 = null; try { date1 = format.parse("2018-04-16 10:00:00"); } catch (ParseException e) { e.printStackTrace(); } Date date2 = new Date(); //计算差值,分钟数 long minutes=(date2.getTime()-date1.getTime())/(1000*60); System.out.println(minutes); //计算差值,天数 long days=(date2.getTime()-date1.getTime())/(1000*60*60*24); System.out.println(days); } /** * 比较两个字符串的大小,按字母的ASCII码比较 * @param pre * @param next * @return * */ public static boolean isMoreThan(String pre, String next){ if(null == pre || null == next || "".equals(pre) || "".equals(next)){ System.out.println("字符串比较数据不能为空!"); return false; } char[] c_pre = pre.toCharArray(); char[] c_next = next.toCharArray(); int minSize = Math.min(c_pre.length, c_next.length); for (int i = 0; i < minSize; i++) { if((int)c_pre[i] > (int)c_next[i]){ return true; }else if((int)c_pre[i] < (int)c_next[i]){ return false; } } if(c_pre.length > c_next.length){ return true; } return false; } public static String deciMal(int top, int below) { double result = new BigDecimal((float)top / below).setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue(); //获取格式化对象 NumberFormat nt = NumberFormat.getPercentInstance(); //设置百分数精确度2即保留两位小数 nt.setMinimumFractionDigits(0); return nt.format(result); } public static Set<Integer> getRandomInt(int limit,int size){ Set<Integer> resultSet=new HashSet<>(); Random random = new Random(); while (resultSet.size()<size){ resultSet.add(random.nextInt(limit)); } /** * set直接转array * Integer [] arr=resultSet.toArray(new Integer[resultSet.size()]); */ /** * set遍历 * Iterator it = resultSet.iterator(); while(it.hasNext()){ System.out.println(it.next()); } */ return resultSet; } /** * 生成指定范围的随机数 */ public static void getLimitRandom() { int max=20; int min=10; Random random = new Random(); int s = random.nextInt(max)%(max-min+1) + min; System.out.println(s); } public static JSONArray treeMenuList(JSONArray menuList, int parentId) { JSONArray childMenu = new JSONArray(); for (Object object : menuList) { JSONObject jsonMenu = (JSONObject)object; int menuId = jsonMenu.getIntValue("thr_gz_id"); int pid = jsonMenu.getIntValue("thr_p_id"); if (parentId == pid) { JSONArray c_node = treeMenuList(menuList, menuId); jsonMenu.put("childNode", c_node); childMenu.add(jsonMenu); } } return childMenu; } //求两个数组的交集 public static String[] intersect(String[] arr1, String[] arr2) { Map<String, Boolean> map = new HashMap<>(); LinkedList<String> list = new LinkedList<String>(); for (String str : arr1) { if (!map.containsKey(str)) { map.put(str, Boolean.FALSE); } } for (String str : arr2) { if (map.containsKey(str)) { map.put(str, Boolean.TRUE); } } for (Map.Entry<String, Boolean> e : map.entrySet()) { if (e.getValue().equals(Boolean.TRUE)) { list.add(e.getKey()); } } String[] result = {}; return list.toArray(result); } //求两个数组的差集 public static String[] minus(String[] arr1, String[] arr2) { LinkedList<String> list = new LinkedList<String>(); LinkedList<String> history = new LinkedList<String>(); String[] longerArr = arr1; String[] shorterArr = arr2; //找出较长的数组来减较短的数组 if (arr1.length > arr2.length) { longerArr = arr2; shorterArr = arr1; } for (String str : longerArr) { if (!list.contains(str)) { list.add(str); } } for (String str : shorterArr) { if (list.contains(str)) { history.add(str); list.remove(str); } else { if (!history.contains(str)) { list.add(str); } } } String[] result = {}; return list.toArray(result); } //求两个数组的差集 public static List<String> getDelValue(String[] targetArray, String[] currentArray) { List<String> result = new ArrayList<>(); for (String t : targetArray) { boolean isOK=true; for (String c : currentArray) { if(t.equals(c)){ isOK=false; break; } } if(isOK){ result.add(t); } } return result; } }
接口返回值封装类Result
package com.ddyg.wx.domain; public class Result { private String message="操作成功"; private Object data; private int count; private boolean status=true; private int code=0; public Result(){ } public Result(Object data, int count){ this.data=data; this.count=count; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } public boolean isStatus() { return status; } public void setStatus(boolean status) { this.status = status; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } }
一、获取accesstoken(微信开发最基础要求,几乎所有接口都要用到这个参数)
请求链接:https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
accesstoken每日获取总量有上限(2000次),所以这个accesstoken值应该存储起来,不能随意开火。个人一般把这个值存储在ServletContext(域)中,核心代码如下:
public static Result getAccessToken(HttpServletRequest request) { Result result = new Result(); try { //先看一眼全局变量里有没有 String token = (String) request.getServletContext().getAttribute("access_token"); String expiresString = (String) request.getServletContext().getAttribute("expires_time"); LocalDateTime nowTime = LocalDateTime.now(); LocalDateTime expiresTime; //如果全局变量里没有的话,要从新取 if (Utilities.checkNull(token).isEmpty()) { result = getNewWxToken(result, request); } else { //如果全局变量里面有的话,要判断过期了没 expiresTime = LocalDateTime.parse(expiresString, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); if (nowTime.isBefore(expiresTime)) { JSONObject resultJO = new JSONObject(); resultJO.put("access_token", token); resultJO.put("expires_time", expiresString); result.setData(resultJO); result.setMessage("请求成功,直接从全局变量里取的值,没有请求微信API"); } else { result = getNewWxToken(result, request,type); } } } catch (Exception e) { result.setMessage(e.getMessage()); result.setStatus(false); } return result; } private static Result getNewWxToken(Result result, HttpServletRequest request) { JSONObject jo = getWxTokenApi(); String token = jo.getString("access_token"); //获取成功了 if (!Utilities.checkNull(token).isEmpty()) { long expiresIn = jo.getLong("expires_in"); LocalDateTime expiresTime = LocalDateTime.now().plusSeconds(expiresIn - 60); String expiresTimeString = expiresTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); request.getServletContext().setAttribute("access_token"+type, token); request.getServletContext().setAttribute("expires_time"+type, expiresTimeString); JSONObject resultJO = new JSONObject(); resultJO.put("access_token", token); resultJO.put("expires_time", expiresTimeString); result.setData(resultJO); result.setMessage("请求成功,从微信API新请求了一个access_token"); } else { //获取没成功 String errmsg = jo.getString("errmsg"); //看上一步的网络请求成功了没 if (!Utilities.checkNull(errmsg).isEmpty()) { result.setStatus(false); result.setMessage("errcode:" + jo.get("errcode") + " errmsg:" + errmsg); } else { result.setStatus(false); result.setMessage("getWxTokenApi()接口返回数据为空,可能是向微信请求token时网络超时或请求次数达到上限"); } } return result; } private static JSONObject getWxTokenApi() { JSONObject jo = new JSONObject(); String appID = ""; String appSecret =""; String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appID + "&secret=" + appSecret; Document document= null; try { document = Jsoup.connect(url).ignoreContentType(true).get(); String data=document.select("body").text(); if (!Utilities.checkNull(data).isEmpty()) { jo = JSON.parseObject(data); } } catch (IOException e) { e.printStackTrace(); } return jo; }
二、获取用户openid
该过程分为三种情况(微信客户端网页授权,PC端微信登陆,微信小程序授权),每种情况都是两步,首先获取code,其次获取openid
微信客户端网页授权
2.1 获取code(微信公众平台配置授权回调域,我这里配置的是http://wx.pinche.com/login?mode=wx,这里的scope值为snsapi_base和snsapi_userinfo)
用户访问链接:
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=http%3A%2F%2Fwx.pinche.com%2Flogin?mode=wx&response_type=code&scope=snsapi_base&state=dac24d03f848ce899f28ad787eba74e2&connect_redirect=1#wechat_redirect
2.2 获取openid
后端代码发送get请求
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
获取openid
PC端微信授权登陆(注意,这里的appid来自于微信开放平台)
2.1 获取code(配置域名回调域,我这里配置的是http://wx.pinche.com/login?mode=wx,这里的scope值为snsapi_base和snsapi_userinfo)
用户访问链接:
https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=http%3A%2F%2Fwx.pinche.com%2Flogin? mode=wx&response_type=code&scope=snsapi_login&state=dac24d03f848ce899f28ad787eba74e2&connect_redirect=1#wechat_redirect
2.2 获取openid
同上
微信小程序授权
2.1 获取code(配置域名回调域,我这里配置的是http://wx.pinche.com/login?mode=wx)
小程序调用方法
wx.login()获取code,发送后端
2.2 获取openid
后端代码发送get请求
https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=APPSECRET&js_code=CODE&grant_type=authorization_code
获取openid
三、根据openid获取用户基本信息,如果用户已经关注公众号,则返回数据包含用户unionid(这个很简单,不赘述了)
https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
四、获取已关注公众号列表,默认一次最多返回1000条数据(第一次不需要next_openid参数),根据NEXT_OPENID可进行翻页
https://api.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&next_openid=NEXT_OPENID
五、生成带参数微信二维码(生成临时二维码时,有没有自定义参数sence_str无所谓,生成永久二维码时这个参数不能为空)
public Result getNewWxQRTicket(HttpServletRequest request, int fixed,String code){ Result finalResult=new Result();
//获取access_token Result result = WxQRUtil.getAccessToken(request); JSONObject jo= (JSONObject) result.getData(); String access_token= null; try { access_token = jo.getString("access_token"); } catch (Exception e) { e.printStackTrace(); } try { //发送post请求获取二维码 String qrCode_url="https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token="+access_token; JSONObject postDataJson=new JSONObject(); JSONObject sceneJson=new JSONObject(); JSONObject sceneDataJson=new JSONObject(); //临时ticket有效期,单位为秒,最多为2592000s(即30天),当前默认为7天 postDataJson.put("expire_seconds",day*3600*24); //临时二维码 QR_SCENE (对应scene_id),QR_STR_SCENE(对应scene_str) //永久二维码 QR_LIMIT_SCENE (对应scene_id),QR_LIMIT_STR_SCENE(对应scene_str) String action_name="QR_STR_SCENE"; if(fixed==1){ //永久二维码 action_name="QR_LIMIT_STR_SCENE"; } postDataJson.put("action_name",action_name); //业务场景下属性id sceneDataJson.put("scene_str",code); sceneJson.put("scene",sceneDataJson); postDataJson.put("action_info",sceneJson); String postData=postDataJson.toJSONString(); HttpClient httpClient = new DefaultHttpClient(); HttpPost post = new HttpPost(qrCode_url); StringEntity postingString = new StringEntity(postData,"utf-8");// json传递 post.setEntity(postingString); HttpResponse response = httpClient.execute(post); String get_content = EntityUtils.toString(response.getEntity()); JSONObject resultData= JSONObject.parseObject(get_content); String ticket=resultData.getString("ticket"); String final_code_url="https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket="+ticket; finalResult.setData(final_code_url); } catch (Exception e) { finalResult.setStatus(false); finalResult.setMessage(e.getMessage()); e.printStackTrace(); } return finalResult; }
六、微信模板消息推送(注:模板消息换行使用"\\n")
七、获取用户基本信息的几种方式
移动端网页授权链接,官方文档(https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419317851&token=&lang=zh_CN)
https://open.weixin.qq.com/connect/oauth2/authorize?appid=&redirect_uri=http%3A%2F%2F" + redirect_uri + "%2Fapi%2Fitem%2Fuser%2Fwx_info?active_index=1" + para + "&response_type=code&scope=snsapi_userinfo&state=dac24d03f848ce899f28ad787eba74e2&connect_redirect=1#wechat_redirect
redirect_uri即授权回调域名,如www.baidu.com
para参数格式为(%26mode=wx%26key=value,即把'&'替换为'%26'),用于授权回调后携带多个参数
7.1 只获取用户openid (可采用网页静默授权,即scope=snsapi_base)
7.2 获取用户个人基本信息有两种方式
7.2.1 常规方式:同7.1相近,只需把scope改为 snsapi_userinfo,然后用户允许授权后即可通过code获取到用户信息
7.2.2 已关注公众号用户获取个人信息:这个就比较简单,可以无视用户是否允许授权,先通过7.1获取用户openid,再获取公众号access_token(非7.2.1,通过code获取access_token),及可以通过下面链接直接获取
https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
PC端生成二维码,用户扫码授权登陆,官方文档(https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN)
授权链接
https://open.weixin.qq.com/connect/qrconnect?appid=&redirect_uri=http%3A%2F%2F" + redirect_uri + "%2Fapi%2Fitem%2Fuser%2Fwx_info?active_index=1&response_type=code&scope=snsapi_login&state=dac24d03f848ce899f28ad787eba74e2#wechat_redirect
redirect_uri即授权回调域名,如www.baidu.com
剩下操作同上
qrconnect