【微信公众号开发】【2】注册搭建属于自己的公众号

前言:

1,正式的公众号需要在微信公众平台申请(https://mp.weixin.qq.com/);

也可以先申请测试号进行开发,测试帐号申请(https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login);

微信接口调用都是有次数限制的

我这边是先申请的测试号(测试号的功能全,但接口调用的次数少,且不能被搜索到,会过期(但我的目前还没过期))

2,公众号有两种模式,两种模式不能并存

编辑模式:针对非编程人员及信息发布类公众帐号使用。公众平台就相当于该公众号的管理后台。

开发模式:针对具备开发能力的人使用。相关的功能是通过调用微信的接口来实现,公众平台就像是负责授权的接口中心。

3,我的开发环境为:Eclipse + Java + Spring MVC + Spring Boot + Mybatis + Redis + Oracle + Maven

4,请先申请测试号,再看接下来的内容

正文:

1,测试号管理

进入上面的测试号网址,可得到测试号相关的信息,

其中的较为重要的是:appID,appsecret

其中的需要我们处理的有:

(1)接口配置信息

URL :外部能访问到的处理微信交互的地址(例:http://xxx.com/coreServlet,访问该页面报500错误即为调通)

Token:自己指定,保持程序中与该页面一致即可

(2)JS接口安全域

域名 :在该域名下通过验证方可调用JSSDK(例:xxx.com)

(3)OAuth2.0网页授权

体验接口权限表-网页服务-网页帐号-网页授权获取用户基本信息

授权回调页面域名:与JS接口安全域名一致即可(例:xxx.com)(使用OAuth2.0的url时带的参数要和这里一致)

2,基本要素

公众号调用接口并不是无限制的!(认证帐号可以对实时调用量清零;测试账号次数少很多)

接口调用视情况会需要两个参数(access_token,jsapi_ticket)(有效时间为7200秒,且新获取的会使旧的失效)

 解决方案:

(1)可存储到数据库或Redis

(2)将参数存到properties文件中,使用时直接读取,重启服务及每两小时更新一次文件

注:fileName为properties文件的名称(如:tt.properties) ;Config.AppId()为上文的appID

//重启后加载  
@PostConstruct    
public void init() {  
    refreshProperties();    
}   
  
//更新access_token及jsapi_ticket  
@Scheduled(cron = "0 0 0/2 * * ?")  //每两小时  
public void refreshProperties() {     
  try {  
    writeProperties(fileName, Config.AppId(), Config.AppSecret());  
  } catch (Exception e) {  
       logger.info("update access_token & jsapi_ticket error:" + e);  
   }  
}  

更新properties文件中的数据

public static void writeProperties(String fileName, String appId, String appSecret) throws Exception   
{  
  String url = "https://api.weixin.qq.com/cgi-bin/token";  
  String ticketUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket";  
  
  String jsonStrToken = HttpRequest.sendGet(url, "grant_type=client_credential&appid="+ appId + "&secret=" + appSecret);         
  JSONObject json = JSONObject.fromObject(jsonStrToken);  
  String access_token = (String) json.getString("access_token");  
  logger.info("access_token:"+access_token);  
  
  String jsonStrTicket = HttpRequest.sendGet(ticketUrl, "access_token=" + access_token + "&type=jsapi");  
  JSONObject ticketJson = JSONObject.fromObject(jsonStrTicket);  
  String ticket = (String) ticketJson.get("ticket");   
  logger.info("ticket:"+ticket);        
  
  PropertiesUtil.writeData(fileName, ACCESS_TOKEN, access_token);          
  PropertiesUtil.writeData(fileName, JSAPI_TICKET, ticket);       
}  
数据写入properties文件
    /** 
     * 修改或添加键值对 如果key存在,修改, 反之,添加。 
     * @param filePath 文件路径,即文件所在包的路径,例如:java/util/config.properties 
     * @param key 键 
     * @param value 键对应的值 
     */ 
    public  synchronized static void writeData(String filePath, String key, String value) {     	
        //获取绝对路径  
        OutputStream fos = null;        
        Properties prop = new Properties();  
        try {  
           logger.info("begin writeData");
            File file = new File(filePath);   
            InputStream fis = new FileInputStream(file);  
            prop.load(fis);  
            //一定要在修改值之前关闭fis  
            fis.close();  
            fos = new FileOutputStream(filePath);  
            prop.setProperty(key, value);  
            //保存,并加入注释  
            prop.store(fos, "Update '" + key + "' value");  
           // fos.close();
            
        } catch (IOException e) {        
        	logger.error("写token文件异常", e);
        }  
        
        finally
        {
        	 try {
				fos.close();
			  } catch (IOException e) {
				  logger.info("close:" + filePath + "——error:"+e);
			 } 
        }       
    }  
上文中的HttpRequest.sendGet方法(第三章有更优化的httpRequest方法,但这里这样已经可以满足需求了)
//向指定URL发送GET方法的请求  
public static String sendGet(String url, String param) 
{   String result
= "";   BufferedReader in = null;   try {     String urlNameString = url + "?" + param;     URL realUrl = new URL(urlNameString);     // 打开和URL之间的连接     URLConnection connection = realUrl.openConnection();     // 设置通用的请求属性     connection.setRequestProperty("accept", "*/*");     connection.setRequestProperty("connection", "Keep-Alive");     // 建立实际的连接     connection.connect();     // 获取所有响应头字段     Map<String, List<String>> map = connection.getHeaderFields();     // 遍历所有的响应头字段     for (String key : map.keySet()) {       logger.info(key + "--->" + map.get(key));     }     // 定义 BufferedReader输入流来读取URL的响应     in = new BufferedReader(new InputStreamReader(connection.getInputStream()));     String line;     while ((line = in.readLine()) != null) {       result += line;     }   } catch (Exception e) {     logger.info("发送GET请求出现异常!" + e);     e.printStackTrace();   }   // 使用finally块来关闭输入流   finally {     try {   if (in != null) {in.close();}     } catch (Exception e2) {       e2.printStackTrace(); } }   return result; }

读取: 

public static String getAccessToken(String fileName, String appId, String appSecret)   
    {  
        String access_token = PropertiesUtil.readData(fileName, ACCESS_TOKEN);  
  
        return access_token;  
    }  
  
    public static String getWeiXinTicket(String fileName, String appId,String appSecret)  
    {  
        String ticket = PropertiesUtil.readData(fileName, JSAPI_TICKET);  
  
        return ticket;  
    }  

从properties文件中读取数据

public static String readData(String fileName, String key)   
{    
  filePath = PropertiesUtil.class.getResource("/" + fileName).toString();  //获取绝对路径             
  filePath = filePath.substring(6);  //截掉路径的"file:"前缀   
  Properties props = new Properties();  
          
  try {    
    InputStream in = new BufferedInputStream(new FileInputStream(fileName));    
    props.load(in);    
    in.close();    
    String value = props.getProperty(key);    
    return value;    
  } catch (Exception e) {    
     e.printStackTrace();    
    return null;    
  }    
}

tt.properties

jsapi_ticket=XXXXXX
access_token=XXXX

检验授权凭证(access_token)是否有效

//请求方法
http:GET(请使用https协议) https://api.weixin.qq.com/sns/auth?access_token=ACCESS_TOKEN&openid=OPENID 

正确的JSON返回结果:{ "errcode":0,"errmsg":"ok"} 

错误时的JSON返回示例:{ "errcode":40003,"errmsg":"invalid openid"}

3,代码(Java)

(1)接口配置信息  URL  需经过微信验证  

@ResponseBody  
@RequestMapping(value = "coreServlet", method = RequestMethod.GET)  
public void coreServletGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException  
{     
  //微信验证地址  
  String signature = request.getParameter("signature"); //加密签名   
  String timestamp = request.getParameter("timestamp"); // 时间戳              
  String nonce = request.getParameter("nonce"); // 随机数            
  String echostr = request.getParameter("echostr"); // 随机字符串    
    
  PrintWriter out = response.getWriter();    
  // 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败    
  if (Sign.checkSignature(Config.token(), signature, timestamp, nonce)) {    
    out.print(echostr);    
  }    
  out.close();    
  out = null;  
}

验证签名  上文中的Sign.checkSignature

public static boolean checkSignature(String token, 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 = byteToStr(digest);    
  } catch (NoSuchAlgorithmException e) {    
    e.printStackTrace();    
  }    
    
  content = null;    
  // 将sha1加密后的字符串可与signature对比,标识该请求来源于微信    
  return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;    
}
//将字节数组转换为十六进制字符串   
private static String byteToStr(byte[] byteArray)   
{    
  String strDigest = "";    
  for (int i = 0; i < byteArray.length; i++) {    
    strDigest += byteToHexStr(byteArray[i]);    
  }    
  return strDigest;    
}    
    
private static String byteToHexStr(byte mByte)   
{    
  char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };    
  char[] tempArr = new char[2];    
  tempArr[0] = Digit[(mByte >>> 4) & 0X0F];    
  tempArr[1] = Digit[mByte & 0X0F];    
   
  String s = new String(tempArr);    
  return s;    
}  

发布后在浏览器里访问CoreServlet,如报500则表示通过

(2)JS接口安全域名 JS发送请求到后台进行验证

前台JS: 要用JSSDK都需要先引用JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.2.0.js

var wxUrl= location.href.split('#')[0];
$.ajax({   type :
"post",   url : "/getTicket",   data : {     "url":wxUrl   },   dataType : "json",   success : function(data){     var obj = data;     wx.config({       debug: false, //开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。       appId: obj.appId, //必填,公众号的唯一标识       timestamp: obj.timestamp, //必填,生成签名的时间戳       nonceStr: obj.nonceStr, //必填,生成签名的随机串       signature: obj.signature,//必填,签名,见微信开发文档附录1       jsApiList: ['chooseImage','previewImage','uploadImage','downloadImage'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2     });   } });

后台验证:  

//调用JSJDK前的验证  
@ResponseBody  
@RequestMapping(value = "getTicket", method = RequestMethod.POST)  
public Map<String, String> getTicket(String url) throws Exception   
{     
  //获取jsapi_ticket,是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,通过access_token来获取。     
  String jsapi_ticket = WeixinUtil.getWeiXinTicket(Config.joinTokenFileName, Config.appId(), Config.appSecret());  
  //URL为要调用接口的页面路径  
  Map<String, String> ret = Sign.sign(Config.appId(), jsapi_ticket, url);  
  
  return ret;  
}  

WeixinUtil.getWeiXinTicket(见上文2中的获取jsapi_ticket方法)

Sign.sign

public static Map<String, String> sign(String appId, String jsapi_ticket, String url)   
{  
  Map<String, String> ret = new HashMap<String, String>();  
  String nonce_str = create_nonce_str();//签名的随机串  
  String timestamp = create_timestamp();//生成签名的时间戳  
  String string1;  
  String signature = "";//签名  
  
  //注意这里参数名必须全部小写,且必须有序  
  string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str + "×tamp=" + timestamp + "&url=" + url;  
  
  try {  
    MessageDigest crypt = MessageDigest.getInstance("SHA-1");  
    crypt.reset();  
    crypt.update(string1.getBytes("UTF-8"));  
    signature = byteToHex(crypt.digest());    
  } catch (NoSuchAlgorithmException e) {  
    e.printStackTrace();  
  } catch (UnsupportedEncodingException e) {  
    e.printStackTrace();  
  }    
  ret.put("url", url);  
  ret.put("jsapi_ticket", jsapi_ticket);  
  ret.put("nonceStr", nonce_str);  
  ret.put("timestamp", timestamp);  
  ret.put("signature", signature);  
  ret.put("appId", appId);  
          
  return ret;  
}  
  
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;  
}  
  
private static String create_nonce_str()   
{  
  return UUID.randomUUID().toString();  
}  
  
private static String create_timestamp()   
{  
  return Long.toString(System.currentTimeMillis() / 1000);  
}  

 

posted @ 2017-10-20 12:01  花生喂龙  阅读(763)  评论(1编辑  收藏  举报