• 【微信公众号开发】【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);  
    }  
  • 相关阅读:
    Junit连接oracle数据库
    java判断字符串是否由数字组成
    Hibernate各种主键生成策略与配置详解
    一对多映射关系
    one-to-one 一对一映射关系(转 wq群)
    工厂模式
    struts2
    创建JUtil
    jdbc
    压缩数据
  • 原文地址:https://www.cnblogs.com/huashengweilong/p/7698818.html
Copyright © 2020-2023  润新知