java--微信公众号开发

1、前言html

最近一段时间都在进行公众号的开发,以前在实训的时候也开发过一次,可是过得过久早就忘记了如何作了,此次开发也是至关于从新开始,其实开发的步骤微信公众号开发文档上面写的也很清楚,因此整体来讲开发的步骤也不是特别复杂。只要配置好须要的URL、TOKEN、和回调的域名就能够了。java

2、开发过程git

 (1)配置服务器信息数据库

在测试上面有两个重要信息。分别是appID 和 appsecret。这是开启公众号两个重要秘钥,一个公众号对应一个appID。有了这两个东西你就能够随心所欲随心所欲猥琐欲为了。json

而后配置你的URL地址和token,其中URL是开发者用来接收微信消息和事件的接口URL。Token可由开发者能够任意填写,用做生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)。api

URL做为开发接口,主要告诉公众号,这是属于本身的服务器,之后公众号接收到的信息都会给个人服务器。配置这个信息时,公众号会给这个接口发送一个GET请求,并要接受四个参数安全

开发者经过检验signature对请求进行校验(下面有校验方式)。若确认这次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,不然接入失败。服务器

此代码段是开放给公众号的接口。在这里接受GET请求的四个参数。微信

package com.itlink.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.junit.internal.runners.TestMethod;

import com.itlink.domain.TextMessage;
import com.itlink.utils.MessageUtil;
import com.itlink.utils.SignUtil;




public class wx extends HttpServlet {

    //private static final Logger logger = Logger.get
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        
        String signature = request.getParameter("signature");
        String timestamp = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        String echostr = request.getParameter("echostr");
        
        if(echostr != null && SignUtil.checkSignature(signature, timestamp, nonce)){
            System.out.println("[signature: "+signature + "]<-->[timestamp: "+ timestamp+"]<-->[nonce: "+nonce+"]<-->[echostr: "+echostr+"]");
            response.getOutputStream().println(echostr);
        }
    }

    
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
     
    }

}

加密/校验流程以下:app

1)将token、timestamp、nonce三个参数进行字典序排序

2)将三个参数字符串拼接成一个字符串进行sha1加密

3)开发者得到加密后的字符串可与signature对比,标识该请求来源于微信

package com.itlink.utils;

import java.security.MessageDigest;
import java.util.Arrays;

public class SignUtil {

    private static String token = "activty";
    
    public static boolean checkSignature(String signature, String timestamp,
            String nonce) {
        
        String checktext = null;
        String[] params = new String[]{token, timestamp, nonce};
        Arrays.sort(params);
        
        String content = params[0].concat(params[1]).concat(params[2]);
        try{
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] digest = md.digest(content.toString().getBytes());
            checktext = bytetostr(digest);
        }catch(Exception e){
            e.printStackTrace();
        }
        
        
         return checktext !=null ? checktext.equals(signature.toUpperCase()) : false;
    }

    private static String bytetostr(byte[] digest) {
        String str = "";
        for (int i = 0; i < digest.length; i++) {
            str += byteToHexStr(digest[i]);
        }
        return str;
    }
    private static String byteToHexStr(byte myByte) {
        char[] Digit = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
        char[] tampArr = new char[2];
        tampArr[0] = Digit[(myByte >>> 4) & 0X0F];
        tampArr[1] = Digit[myByte & 0X0F];
        String str = new String(tampArr);
        return str;
    }

}

此时提交的配置信息就会成功。(这是真的,若是仍是不行的话,那是不存在的)

(2)获取网页权限。

打开第三方网页时须要获取到用户信息,就须要用户受权。全部操做的权限是经过微信公众号开放的接口获取的。

在微信公众号文档中也明确提到。在用户受权以后进行网页跳转时须要配置用户的回调地址。这是一个关键信息。若是没有的话,在网页跳转的时候就会报错,至关于找不到回家的路。

 

关于网页受权的两种scope的区别说明

一、以snsapi_base为scope发起的网页受权,是用来获取进入页面的用户的openid的,而且是静默受权并自动跳转到回调页的。这种方式不用用户手动受权。

二、以snsapi_userinfo为scope发起的网页受权,是用来获取用户的基本信息的。但这种受权须要用户手动赞成,而且因为用户赞成过,因此无须关注,就可在受权后获取该用户的基本信息。

三、用户管理类接口中的“获取用户基本信息接口”,是在用户和公众号产生消息交互或关注后事件推送后,才能根据用户OpenID来获取用户基本信息。这个接口,包括其余微信接口,都是须要该用户(即openid)关注了公众号后,才能调用成功的。

关于网页受权access_token和普通access_token的区别

微信网页受权是经过OAuth2.0机制实现的,在用户受权给公众号后,公众号能够获取到一个网页受权特有的接口调用凭证(网页受权access_token),经过网页受权access_token能够进行受权后接口调用,如获取用户基本信息;其余微信接口,须要经过基础支持中的“获取access_token”接口来获取到的普通access_token调用。

 获取用户信息的基本步骤:

一、引导用户进入受权页面赞成受权,获取code

二、经过code换取网页受权access_token

三、若是须要,开发者能够刷新网页受权access_token,避免过时

四、经过网页受权access_token和openid获取用户基本信息

跳转

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
REDIRECT_URI:是用户须要跳转的路径(个人代码中是一个servlet)
 

经过一个回调的路径跳转到一个页面并获取到code。

//String code = request.getParameter("code");//从受权页面获取到code,用于回去用户的openid

		//code做为换取access_token的票据,每次用户受权带上的code将不同,code只能使用一次,5分钟未被使用自动过时。
		//经过code获取到token_access
		//AccessToken access_token = WeixinUtil.getAccessToken(WeixinUtil.appid, WeixinUtil.appsecret);
		//JSONObject json1 = WeixinUtil.getOAuthAccessToken(WeixinUtil.appid, WeixinUtil.appsecret, code);
		
		String access_token =  dao.getAccessToken().getAccess_token();
		response.sendRedirect("activity/index.html?user="+openid);
//		JSONObject user_json = WeixinUtil.getUserinfo(access_token, openid);

  微信工具类。

package com.itlink.utils;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.URL;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;

import org.junit.Test;

import com.itlink.domain.AccessToken;
import com.itlink.domain.Menu;



import net.sf.json.JSONObject;


public class WeixinUtil {  
    public static String appid = "";//本身公众号的APPID和appsecret
    public static String appsecret = "";
    
  //处理请求
    public static JSONObject httpsRequest(String requestUrl, String requestMethod, String outputStr) {  
        JSONObject jsonObject = null;  
        StringBuffer buffer = new StringBuffer();  
        try {  

            TrustManager[] tm = { new MyX509TrustManager() };  
            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");  
            sslContext.init(null, tm, new java.security.SecureRandom());  

            SSLSocketFactory ssf = sslContext.getSocketFactory();  
  
            URL url = new URL(requestUrl);  
            HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();  
            httpUrlConn.setSSLSocketFactory(ssf);  
  
            httpUrlConn.setDoOutput(true);  
            httpUrlConn.setDoInput(true);  
            httpUrlConn.setUseCaches(false);  

            httpUrlConn.setRequestMethod(requestMethod);  
  
            if ("GET".equalsIgnoreCase(requestMethod))  
                httpUrlConn.connect();  

            if (null != outputStr) {  
                OutputStream outputStream = httpUrlConn.getOutputStream();  
                outputStream.write(outputStr.getBytes("UTF-8"));  
                outputStream.close();  
            }  

            InputStream inputStream = httpUrlConn.getInputStream();  
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");  
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);  
  
            String str = null;  
            while ((str = bufferedReader.readLine()) != null) {  
                buffer.append(str);  
            }  
            bufferedReader.close();  
            inputStreamReader.close();    
            inputStream.close();  
            inputStream = null;  
            httpUrlConn.disconnect();  
            jsonObject = JSONObject.fromObject(buffer.toString());  
        } catch (ConnectException ce) {  
           // log.info("Weixin server connection timed out.");  
        } catch (Exception e) {  
           // log.info("https request error:"+e);  
        }  
        return jsonObject;  
    }  

//获取access_token接口
    public final static String access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";

    public static AccessToken getAccessToken(String appid, String appsecret){
        String url = access_token_url.replace("APPID", appid).replace("APPSECRET", appsecret);
        JSONObject jsonObject = httpsRequest(url, "GET", null);
        
        AccessToken accessToken = new AccessToken();
  
        accessToken.setExpires_in((Integer)jsonObject.get("expires_in"));
        accessToken.setAccess_token(jsonObject.getString("access_token"));
        
        return accessToken;
    }
    
  //获取用户基本信息(UnionID机制)
    public final static String userinfo_url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
    
    public static JSONObject getUserinfo(String access_token,String openid){
        String url = userinfo_url.replace("ACCESS_TOKEN", access_token).replace("OPENID", openid);
        JSONObject jsonObject = httpsRequest(url, "GET", null);
        
        return jsonObject;
    }
  //获取用户权限
    public final static String access_token_oauth_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";

    public static JSONObject getOAuthAccessToken(String appid,String appsecret,String code){
        String url = access_token_oauth_url.replace("APPID", appid).replace("SECRET", appsecret).replace("CODE", code);
        JSONObject jsonObject = httpsRequest(url, "GET", null);
        
        return jsonObject;
    }
  

  //拉取用户信息
public final static String user_info_oauth_url = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN"; public static JSONObject getUserInfoByOAuth(String access_token,String openid){ String url = user_info_oauth_url.replace("ACCESS_TOKEN", access_token).replace("OPENID", openid); JSONObject jsonObject = httpsRequest(url, "GET", null); return jsonObject; } }
MyX509TrustManager类证书信任管理器(用于https请求) 
package com.itlink.utils;

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.X509TrustManager;


 
public class MyX509TrustManager implements X509TrustManager {  
  
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {  
    }  
  
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {  
    }  
  
    public X509Certificate[] getAcceptedIssuers() {  
        return null;  
    }  
}  

 网页受权方式基本上就能够获取到用户的信息了。

(三)开发过程当中遇到的坑

(1)遇到access_token过时。在开发过程当中过一点时间就会没法访问到本身的网站,后面才发现微信受权调用的凭票过一段时间就会过去,就须要从新获取。access_token保存的时长是7200s,也就是两个小时,因此须要通过段时间就要去从新获取access_token。这个时间就须要作一个定时器定时的去执行任务。并将凭票保存起来,能够放在数据库中,也能够用一个文件保存起来,每次须要用到的时候就去获取。

package com.itlink.domain;

public class AccessToken {
    private String access_token;
    private int expires_in;
    
    public String getAccess_token() {
        return access_token;
    }
    public void setAccess_token(String access_token) {
        this.access_token = access_token;
    }
    public int getExpires_in() {
        return expires_in;
    }
    public void setExpires_in(int expires_in) {
        this.expires_in = expires_in;
    }
    @Override
    public String toString() {
        return "AccessToken [access_token=" + access_token + ", expires_in="
                + expires_in + "]";
    }
    
     
}

将access_token保存起来

package com.itlink.dao;

import org.dom4j.Document;
import org.dom4j.Element;
import org.junit.Test;

import com.itlink.domain.AccessToken;
import com.itlink.utils.WeixinUtil;
import com.itlink.utils.dom4jUtil;

public class AccessTokenDao {
    AccessToken accessToken = null;
    public AccessToken getAccessToken(){
        //获取保存的信息
    }
    
    public void  setAccessToken(AccessToken accesstoken){
        //this.accessToken = new AccessToken();
        //保存信息

    }
  
}

开启一个线程,每隔一段时间就获取信息。

package com.itlink.main;

import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;

import org.junit.Test;

import com.itlink.dao.AccessTokenDao;
import com.itlink.domain.AccessToken;
import com.itlink.utils.WeixinUtil;

public class TokenThread implements Runnable {

    public static AccessToken accessToken = null;
    @Override
    public void run() {
        accessToken = WeixinUtil.getAccessToken(WeixinUtil.appid, WeixinUtil.appsecret);
        System.out.println("开始获取");
        try {
            if(accessToken != null){
                
                System.out.println("accessToken获取成功:"+ accessToken.getExpires_in());
                //将access——token存放起来    
                new AccessTokenDao().setAccessToken(accessToken);
                
                //7000秒后从新获取
                
                Thread.sleep((accessToken.getExpires_in()-200)*1000);
                    
                
            }else{
                System.out.println("获取失败");
                Thread.sleep(60*1000);
            }
        
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
    

}

 

(2)启用开发者模式后,原先定义的菜单栏会被停用。要使用自定义菜单只能经过公众号给你提供的接口设置。

public final static String create_menu_url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";

    public static Integer createMenu(String access_token, Menu menu){
        String url = create_menu_url.replace("ACCESS_TOKEN", access_token);
        
        String outputStr = JSONObject.fromObject(menu).toString();
        System.out.println(outputStr);
        
        JSONObject jsonObject = httpsRequest(url, "POST", outputStr);
        Integer result = null;
        if(null!=jsonObject){
            result = jsonObject.getInt("errcode");
        }
        return result;
    }

本身定义一个Menu类,将你须要的定义的菜单以json类型的格式以post请求的方式传递过去。

规定格式:

 {
     "button":[
     {    
          "type":"click",
          "name":"今日歌曲",
          "key":"V1001_TODAY_MUSIC"
      },
      {
           "name":"菜单",
           "sub_button":[
           {    
               "type":"view",
               "name":"搜索",
               "url":"http://www.soso.com/"
            },
            {
                 "type":"miniprogram",
                 "name":"wxa",
                 "url":"http://mp.weixin.qq.com",
                 "appid":"wx286b93c14bbf93aa",
                 "pagepath":"pages/lunar/index"
             },
            {
               "type":"click",
               "name":"赞一下咱们",
               "key":"V1001_GOOD"
            }]
       }]
 }

(四)最后

基本配置的格式就差很少是这样了。这个项目是在测试号上面进行开发的,测试号上面全部的权限都提供给了开发者,在实际开发中不一样类型的公众号的权限是不同的,因此在开发时候仍是须要根据实际状况进行业务的调整。

相关文章
相关标签/搜索