微信开发平台对接流程(Java版本)2

在 微信开发平台对接流程(Java版本)1

我们已经准备好了第三方开发平台的相关准备。接下来,就需要对接代码了。

主要是第三方授权技术流程   

思路:

     微信第三方会每隔一段时间(10分钟)会验证第三方票据。所以需要先接收第三方票据。接着。需要获取第三方的compent_access_token。然后根据第三方的compent_access_token来定时获取授权公众号的access_token.

代码:

验证票据 

   对应的表

    

CREATE TABLE `wx_open_account` (
  `id` varchar(36) NOT NULL COMMENT '主键',
  `appid` varchar(200) DEFAULT NULL COMMENT '第三方公众号的appId',
  `appsecret` varchar(500) DEFAULT NULL COMMENT '第三方公众号的appSecret',
  `ticket` varchar(2000) DEFAULT NULL COMMENT '第三方平台推送 :票据 ticket',
  `get_ticket_time` datetime DEFAULT NULL COMMENT '票据接收时间',
  `component_access_token` varchar(2000) DEFAULT NULL COMMENT '平台方access_token',
  `get_access_token_time` datetime DEFAULT NULL COMMENT '平台方获取access_token时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='微信第三方平台账号表';

  我们需要把在表里创建一条第三方的数据。只需要appid(下图1)和appSecret(下图2)就行。

   

                                                   图1

 ThirdController

   /**
     * 接收微信服务器发送的component_verify_ticket
     */
    @PostMapping("/getComponentVerifyTicket")
    @CrossOrigin
    public String getComponentVerifyTicket(HttpServletRequest request, HttpServletResponse response) throws Exception {
        return thirdService.getComponentVerifyTicket(request,response);
    }

 ThirdService

@Override
    public String getComponentVerifyTicket(HttpServletRequest request, HttpServletResponse response) {
        logger.info("接收微信服务器发送的component_verify_ticket  begin");
        try {
            request.setCharacterEncoding("UTF-8");
            response.setCharacterEncoding("UTF-8");
            // 微信加密签名
            String msg_signature = request.getParameter("msg_signature");
            // 时间戳
            String timestamp = request.getParameter("timestamp");
            // 随机数
            String nonce = request.getParameter("nonce");
            // 从请求中读取整个post数据
            InputStream inputStream;
            String postData = null;
            inputStream = request.getInputStream();
            //获取接收到消息里的XML密文,存放在postData中
            postData = IOUtils.toString(inputStream, "UTF-8");
            logger.info("postData:" + postData);


            WxBizMsgCrypt wxcpt = null;
            logger.info("postData:" + postData);
            logger.info("msg_signature:" + msg_signature);
            logger.info("timestamp:" + timestamp);
            logger.info("nonce:" + nonce);
            logger.info("getServetoken:" + environment.getProperty("wx.component_token"));
            logger.info("getServeencodingaeskey:" + environment.getProperty("wx.component_encodingaeskey"));
            logger.info("getServeappid:" + environment.getProperty("wx.component_appid"));

            //从XML中获取<Encrypt></Encrypt>标签内的密文文本
            //加密处理
            Map map2 = XmlUtil.xmlToMap(postData);
            String encrypt = map2.get("Encrypt").toString();
            logger.info("Encrypt:" + encrypt);
            //格式化密文文本,否则没有<ToUserName>标签,会解密失败,参考官方的加解密代码JAVA版本
            String format = "<xml><ToUserName><![CDATA[toUser]]></ToUserName><Encrypt><![CDATA[%1$s]]></Encrypt></xml>";
            String fromXML = String.format(format, encrypt);
            //解密后的明文
            String msg = "";
            if (StringUtils.isEmpty(encrypt)) {
                msg = fromXML;
            } else {
                wxcpt = new WxBizMsgCrypt(environment.getProperty("wx.component_token"), environment.getProperty("wx.component_encodingaeskey"), environment.getProperty("wx.component_appid"));
                // 解密消息
                msg = wxcpt.decryptMsg(msg_signature, timestamp, nonce, fromXML);
            }
            logger.info("msg:" + msg);
            //将XML格式字符串转为Map类型
            Map<String, Object> msgMap = XmlUtil.xmlToMap(msg);
            //获取infotype,注:微信开放平台文档中标明固定为:"component_verify_ticket",但参考其他代码,还包含authorized???
            String infotype = msgMap.get("InfoType").toString();
            logger.info(infotype);
            switch (infotype) {
                //验证票据
                case "component_verify_ticket":
                     //查询库中的第三方信息,并且准备存储ticket   
                    WxOpenAccount weixinOpenAccount = openAccountMapperExt.queryOneByAppid(environment.getProperty("wx.component_appid"));
                    String ComponentVerifyTicket = msgMap.get("ComponentVerifyTicket").toString();
                    logger.info("component_verify_ticket:" + ComponentVerifyTicket);
                    weixinOpenAccount.setTicket(ComponentVerifyTicket);
                    weixinOpenAccount.setGetTicketTime(new Date());
                    openAccountMapperExt.updateByPrimaryKeySelective(weixinOpenAccount);
                    //将票据值写入Redis缓存中
                    // webChatCatchUtil.setWeixinOpenComponentVerifyTicket(ComponentVerifyTicket);

                    break;
                case "unauthorized"://用户取消授权
                    break;
            }
        } catch (Exception e) {
            logger.error("Exception", e);
        }
        return "success";
    }
wx.component_token:消息校验Token(在1中)
wx.component_encodingaeskey:消息加密解密key

                                                 图2

 wx.component_appid:图1 的appId(也就是1)

 一些工具类 

WxBizMsgCrypt
package com.iflytek.util.wx.aes;
/**
 * 提供接收和推送给公众平台消息的加解密接口(UTF8编码的字符串).
 * <ol>
 * 	<li>第三方回复加密消息给公众平台</li>
 * 	<li>第三方收到公众平台发送的消息,验证消息的安全性,并对消息进行解密。</li>
 * </ol>
 * 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案
 * <ol>
 * 	<li>在官方网站下载JCE无限制权限策略文件(JDK7的下载地址:
 *      http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html</li>
 * 	<li>下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt</li>
 * 	<li>如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件</li>
 * 	<li>如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件</li>
 * </ol>
 */
import com.iflytek.exception.AesException;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.util.Arrays;

/**
 * @desc 微信消息体加密解密工具类
 */
public class WxBizMsgCrypt {

    static Charset CHARSET = Charset.forName("utf-8");
    Base64 base64 = new Base64();
    byte[] aesKey;
    String token;
    String appId;

    /**
     * 构造函数
     * @param token 公众平台上,开发者设置的token
     * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey
     * @param appId 公众平台appid
     *
     * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
     */
    public WxBizMsgCrypt(String token, String encodingAesKey, String appId) throws AesException {
        if (encodingAesKey.length() != 43) {
            throw new AesException(AesException.IllegalAesKey);
        }

        this.token = token;
        this.appId = appId;
        aesKey = Base64.decodeBase64(encodingAesKey + "=");
    }

    /**
     * 生成4个字节的网络字节序
     * @param sourceNumber 源数字
     * @return 返回字节数组
     */
    byte[] getNetworkBytesOrder(int sourceNumber) {
        byte[] orderBytes = new byte[4];
        orderBytes[3] = (byte) (sourceNumber & 0xFF);
        orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF);
        orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF);
        orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF);
        return orderBytes;
    }

    /**
     * 还原4个字节的网络字节序
     * @param orderBytes 需要还原的字节数组
     * @return 返回数字
     */
    int recoverNetworkBytesOrder(byte[] orderBytes) {
        int sourceNumber = 0;
        for (int i = 0; i < 4; i++) {
            sourceNumber <<= 8;
            sourceNumber |= orderBytes[i] & 0xff;
        }
        return sourceNumber;
    }

    /**
     * 随机生成16位字符串
     * @return 返回随机字符串
     */
    String getRandomStr() {
        String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        SecureRandom random = new SecureRandom();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 16; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

    /**
     * 对明文进行加密.
     *
     * @param text 需要加密的明文
     * @return 加密后base64编码的字符串
     * @throws AesException aes加密失败
     */
    String encrypt(String randomStr, String text) throws AesException {
        ByteGroup byteCollector = new ByteGroup();
        byte[] randomStrBytes = randomStr.getBytes(CHARSET);
        byte[] textBytes = text.getBytes(CHARSET);
        byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length);
        byte[] appidBytes = appId.getBytes(CHARSET);

        // randomStr + networkBytesOrder + text + appid
        byteCollector.addBytes(randomStrBytes);
        byteCollector.addBytes(networkBytesOrder);
        byteCollector.addBytes(textBytes);
        byteCollector.addBytes(appidBytes);

        // ... + pad: 使用自定义的填充方式对明文进行补位填充
        byte[] padBytes = PkCs7Encoder.encode(byteCollector.size());
        byteCollector.addBytes(padBytes);

        // 获得最终的字节流, 未加密
        byte[] unencrypted = byteCollector.toBytes();

        try {
            // 设置加密模式为AES的CBC模式
            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
            IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);

            // 加密
            byte[] encrypted = cipher.doFinal(unencrypted);

            // 使用BASE64对加密后的字符串进行编码
            String base64Encrypted = base64.encodeToString(encrypted);

            return base64Encrypted;
        } catch (Exception e) {
            e.printStackTrace();
            throw new AesException(AesException.EncryptAESError);
        }
    }

    /**
     * 对密文进行解密.
     *
     * @param text 需要解密的密文
     * @return 解密得到的明文
     * @throws AesException aes解密失败
     */
    String decrypt(String text) throws AesException {
        byte[] original;
        try {
            // 设置解密模式为AES的CBC模式
            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES");
            IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
            cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);

            // 使用BASE64对密文进行解码
            byte[] encrypted = Base64.decodeBase64(text);

            // 解密
            original = cipher.doFinal(encrypted);
        } catch (Exception e) {
            e.printStackTrace();
            throw new AesException(AesException.DecryptAESError);
        }

        String xmlContent, from_appid;
        try {
            // 去除补位字符
            byte[] bytes = PkCs7Encoder.decode(original);

            // 分离16位随机字符串,网络字节序和AppId
            byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);

            int xmlLength = recoverNetworkBytesOrder(networkOrder);

            xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET);
            from_appid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length),
                    CHARSET);
        } catch (Exception e) {
            e.printStackTrace();
            throw new AesException(AesException.IllegalBuffer);
        }

        // appid不相同的情况
        if (!from_appid.equals(appId)) {
            throw new AesException(AesException.ValidateAppidError);
        }
        return xmlContent;

    }

    /**
     * 将公众平台回复用户的消息加密打包.
     * <ol>
     * 	<li>对要发送的消息进行AES-CBC加密</li>
     * 	<li>生成安全签名</li>
     * 	<li>将消息密文和安全签名打包成xml格式</li>
     * </ol>
     *
     * @param replyMsg 公众平台待回复用户的消息,xml格式的字符串
     * @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp
     * @param nonce 随机串,可以自己生成,也可以用URL参数的nonce
     *
     * @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串
     * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
     */
    public String encryptMsg(String replyMsg, String timeStamp, String nonce) throws AesException {
        // 加密
        String encrypt = encrypt(getRandomStr(), replyMsg);

        // 生成安全签名
        if (timeStamp == "") {
            timeStamp = Long.toString(System.currentTimeMillis());
        }

        String signature = Sha1Util.getSha1Token(token, timeStamp, nonce, encrypt);
        // 生成发送的xml
        String result = XmlParse.generate(encrypt, signature, timeStamp, nonce);
        return result;
    }

    /**
     * 检验消息的真实性,并且获取解密后的明文.
     * <ol>
     * 	<li>利用收到的密文生成安全签名,进行签名验证</li>
     * 	<li>若验证通过,则提取xml中的加密消息</li>
     * 	<li>对消息进行解密</li>
     * </ol>
     *
     * @param msgSignature 签名串,对应URL参数的msg_signature
     * @param timeStamp 时间戳,对应URL参数的timestamp
     * @param nonce 随机串,对应URL参数的nonce
     * @param postData 密文,对应POST请求的数据
     *
     * @return 解密后的原文
     * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
     */
    public String decryptMsg(String msgSignature, String timeStamp, String nonce, String postData)
            throws AesException {

        // 密钥,公众账号的app secret
        // 提取密文
        Object[] encrypt = XmlParse.extract(postData);

        // 验证安全签名
        String signature = Sha1Util.getSha1Token(token, timeStamp, nonce, encrypt[1].toString());

        // 和URL中的签名比较是否相等
        // System.out.println("第三方收到URL中的签名:" + msg_sign);
        // System.out.println("第三方校验签名:" + signature);
        if (!signature.equals(msgSignature)) {
            throw new AesException(AesException.ValidateSignatureError);
        }

        // 解密
        String result = decrypt(encrypt[1].toString());
        return result;
    }

    /**
     * 验证URL
     * @param msgSignature 签名串,对应URL参数的msg_signature
     * @param timeStamp 时间戳,对应URL参数的timestamp
     * @param nonce 随机串,对应URL参数的nonce
     * @param echoStr 随机串,对应URL参数的echostr
     *
     * @return 解密之后的echostr
     * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
     */
    public String verifyUrl(String msgSignature, String timeStamp, String nonce, String echoStr)
            throws AesException {
        String signature = Sha1Util.getSha1Token(token, timeStamp, nonce, echoStr);

        if (!signature.equals(msgSignature)) {
            throw new AesException(AesException.ValidateSignatureError);
        }

        String result = decrypt(echoStr);
        return result;
    }
}

XmlUtil是下面包的工具类
    <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.5.0</version>
        </dependency>

获取compent_access_token 

         这个是定时任务获取。因此,需要我们存储到redis中。因为它的有效期是7200s.。接着,我们在更新下该第三方下的公众号token.

需要的表。

  

CREATE TABLE `wx_token` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `jwid` varchar(64) DEFAULT NULL COMMENT '公众号',
  `name` varchar(64) NOT NULL COMMENT '名称',
  `application_type` varchar(10) DEFAULT NULL COMMENT '应用类型',
  `qrcodeimg` varchar(255) DEFAULT NULL COMMENT '微信二维码图片',
  `weixin_number` varchar(64) DEFAULT NULL COMMENT '微信号',
  `weixin_appid` varchar(50) DEFAULT NULL COMMENT '微信AppId',
  `weixin_appsecret` varchar(255) DEFAULT NULL COMMENT '微信AppSecret',
  `account_type` varchar(255) DEFAULT NULL COMMENT '公众号类型',
  `auth_status` varchar(255) DEFAULT NULL COMMENT '是否认证',
  `access_token` varchar(2000) DEFAULT NULL COMMENT 'Access_Token',
  `token_gettime` datetime DEFAULT NULL COMMENT 'token获取的时间',
  `apiticket` varchar(255) DEFAULT NULL COMMENT 'api凭证',
  `apiticket_gettime` datetime DEFAULT NULL COMMENT 'apiticket获取时间',
  `jsapiticket` varchar(255) DEFAULT NULL COMMENT 'jsapiticket',
  `jsapiticket_gettime` datetime DEFAULT NULL COMMENT 'jsapiticket获取时间',
  `auth_type` varchar(10) DEFAULT NULL COMMENT '类型:1手动授权,2扫码授权',
  `business_info` varchar(5000) DEFAULT NULL COMMENT '功能的开通状况:1代表已开通',
  `func_info` varchar(5000) DEFAULT NULL COMMENT '公众号授权给开发者的权限集',
  `nick_name` varchar(200) DEFAULT NULL COMMENT '授权方昵称',
  `headimgurl` varchar(1000) DEFAULT NULL COMMENT '授权方头像',
  `authorization_info` varchar(5000) DEFAULT NULL COMMENT '授权信息',
  `authorizer_refresh_token` varchar(500) DEFAULT NULL COMMENT '刷新token',
  `token` varchar(32) DEFAULT NULL COMMENT '令牌',
  `authorization_status` varchar(10) DEFAULT NULL COMMENT '授权状态(1正常,2已取消)',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `create_user` varchar(255) DEFAULT NULL COMMENT '创建人',
  `update_time` datetime DEFAULT NULL COMMENT '修改时间',
  `update_user` varchar(255) DEFAULT NULL COMMENT '修改人',
  `delete_flag` int(2) DEFAULT '0' COMMENT '逻辑删除 0未删除 1已删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='系统公众号表';

上代码。

/**
     * 定时刷新TOKEN
     */
    @Scheduled(cron = "${wx.open.task}")
    public void run() {
        logger.info("===================重置公众号AccseeToken定时任务开启==========================");
        long start = System.currentTimeMillis();
        try {

            //1.重置第三方平台AccessTOKEN
            resetComponentAccessToken();

            //2.重置公众号的Token
            WxTokenExample example = new WxTokenExample();
            WxTokenExample.Criteria crt = example.createCriteria();
            //授权状态(1正常,2已取消)
            crt.andAuthorizationStatusEqualTo("1");
            crt.andDeleteFlagEqualTo(0);
            Date date = new Date();
            crt.andTokenGettimeLessThan(new Date(date.getTime() - 60 * 60 * 1000));
            List<WxToken> tokenList = tokenMapperExt.selectByExample(example);
            for (WxToken myJwWebJwid : tokenList) {
                try {
                    if ("2".equals(myJwWebJwid.getAuthType())) {
                        //第三方平台
                        resetAccessTokenByType2(myJwWebJwid);
                    } else {
                        resetAccessTokenByType1(myJwWebJwid);
                    }
                } catch (Exception e) {
                    logger.info("重置AccseeToken定时任务异常e={}", new Object[]{e});
                }
            }
        } catch (Exception e) {
            logger.info("重置AccseeToken定时任务异常e={}", new Object[]{e});
        }
        logger.info("===================重置AccseeToken定时任务结束,用时={}ms.==========================", new Object[]{System.currentTimeMillis() - start});
    }

    /**
     * 重置第三方平台AccessTOKEN
     *
     * @param
     * @return
     */
    private void resetComponentAccessToken() {
        try {
            Date date = new Date();
            long time = date.getTime() - 1000 * 60 * 90;
            Date refDate = new Date(time);
            //根据APPID从数据库中获取基本信息(包括秘钥)
            if (StringUtils.isEmpty(environment.getProperty("wx.component_appid"))) {
                return;
            }
            WxOpenAccount weixinOpenAccount = openAccountMapperExt.queryOneByAppid(environment.getProperty("wx.component_appid"));
            logger.info("微信第三方账号信息:"+ JSON.toJSONString(weixinOpenAccount));
            //第三方平台账号上次获取token时间超过1个半小时,则进行重置
            if (weixinOpenAccount != null && weixinOpenAccount.getGetAccessTokenTime().before(refDate)) {
                logger.info("时间匹配:"+weixinOpenAccount.getGetAccessTokenTime().before(refDate));
                Map<String, Object> param = new HashMap<String, Object>(8);
                param.put("component_appid", environment.getProperty("wx.component_appid"));
                //从数据库中获取秘钥
                param.put("component_appsecret", weixinOpenAccount.getAppsecret());
                param.put("component_verify_ticket", weixinOpenAccount.getTicket());
                JSONObject jsonObject = new JSONObject(param);
                JSONObject jsonObj = WxHttpUtil.postForJson(environment.getProperty("wx.baseUrl") +CommonWeixinProperties.GET_COMPONENT_ACCESS_ACTOKEN_URL, jsonObject.toString());
                logger.info("重置第三方平台ACCESSTOKEN时返回的报文" + jsonObj);
                if (jsonObj != null && jsonObj.containsKey("component_access_token")) {
                    weixinOpenAccount.setComponentAccessToken(jsonObj.getString("component_access_token"));
                    weixinOpenAccount.setGetAccessTokenTime(new Date());
                    openAccountMapperExt.updateByPrimaryKeySelective(weixinOpenAccount);

                    //---------------------第三方平台账号重置token后写入redis缓存-----------------------------
                    try {
                        redisUtil.setKey(Constant.WX_THIRD_INFO, weixinOpenAccount, 5400);
                    } catch (Exception e) {
                        logger.error("----------第三方平台账号重置TOKEN错误-------------" + e.toString());
                        e.printStackTrace();
                    }
                    //---------------------第三方平台账号重置token后写入redis缓存-----------------------------
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据APPID和秘钥 获取ACCESSTOKEN
     *
     * @param wxToken
     * @return
     */
    private void resetAccessTokenByType1(WxToken wxToken) {
        //根据APPID和秘钥 获取ACCESSTOKEN,并获取apiticket和jsapiticket
        Map<String, Object> data = AccessTokenUtil.getAccseeToken(wxToken.getWeixinAppid(), wxToken.getWeixinAppsecret());
        logger.info("resetAccessTokenByType1重置第三方平台下公众号信息时返回的报文" + JSONObject.toJSONString(data));

        if (data != null && "success".equals(data.get("status").toString())) {
            wxToken.setAccessToken(data.get("accessToken").toString());
            wxToken.setTokenGettime(new Date());
            wxToken.setApiticket(data.get("apiTicket").toString());
            wxToken.setApiticketGettime(new Date());
            wxToken.setJsapiticket(data.get("jsApiTicket").toString());
            wxToken.setJsapiticketGettime(new Date());
            tokenMapperExt.updateByPrimaryKeySelective(wxToken);

            //-------H5平台独立公众号,重置redis缓存-------------------------------------------
            try {
                redisUtil.setKey(Constant.WX_ACCOUNT+wxToken.getId(), wxToken, 5400);
            } catch (Exception e) {
                logger.error("定时任务:独立公众号,重置redis缓存token失败,{}", e);
            }
            //--------独立公众号,重置redis缓存---------------------------------------
        } else if ("responseErr".equals(data.get("status").toString())) {
            logger.error(data.get("msg").toString());
        } else {
            logger.error("根据APPID和秘钥 获取ACCESSTOKEN,并获取apiticket和jsapiticket 失败");
        }
    }

    /**
     * 获取ACCESSTOKEN
     *获取/刷新接口调用令牌
     * @param wxToken
     * @return
     */
    private String resetAccessTokenByType2(WxToken wxToken) {
        try {
            WxOpenAccount weixinOpenAccount = openAccountMapperExt.queryOneByAppid(environment.getProperty("wx.component_appid"));
            if (weixinOpenAccount == null) {
                logger.error("重置accessToken时获取WEIXINOPENACCOUNT为空");
                return "重置accessToken时获取WEIXINOPENACCOUNT为空";
            }
            //重置授权公众号或小程序的接口调用凭据
            String getAuthorizerTokenUrl = environment.getProperty("wx.baseUrl") +CommonWeixinProperties.GET_AUTHORIZER_ACCESS_TOKEN_URL;

            getAuthorizerTokenUrl = getAuthorizerTokenUrl.replace("COMPONENT_ACCESS_TOKEN", weixinOpenAccount.getComponentAccessToken());
            // 拼装参数
            JSONObject js = new JSONObject();
            // 第三方平台appid
            js.put("component_appid", environment.getProperty("wx.component_appid"));
            // 授权用户的appid
            js.put("authorizer_appid", wxToken.getWeixinAppid());
            // 刷新令牌
            js.put("authorizer_refresh_token", wxToken.getAuthorizerRefreshToken());
            JSONObject jsonObj = WxHttpUtil.postForJson(getAuthorizerTokenUrl, js.toString());
            logger.info("resetAccessTokenByType2重置第三方平台下公众号信息时返回的报文" + jsonObj);
            if (jsonObj != null && !jsonObj.containsKey("errcode")) {
                String authorizerAccessToken = jsonObj.getString("authorizer_access_token");
                String authorizerRefreshToken = jsonObj.getString("authorizer_refresh_token");
                wxToken.setAccessToken(authorizerAccessToken);
                wxToken.setTokenGettime(new Date());
                wxToken.setAuthorizerRefreshToken(authorizerRefreshToken);
                //update jsapiticket
                Map<String, String> apiTicket = AccessTokenUtil.getApiTicket(wxToken.getAccessToken());
                if(apiTicket!=null){
                    if ("true".equals(apiTicket.get("status"))) {
                        wxToken.setApiticket(apiTicket.get("apiTicket"));
                        wxToken.setApiticketGettime(new Date());
                        wxToken.setJsapiticket(apiTicket.get("jsApiTicket"));
                        wxToken.setJsapiticketGettime(new Date());
                    }
                }
                tokenMapperExt.updateByPrimaryKeySelective(wxToken);

                //-------H5平台独立公众号,重置redis缓存-------------------------------------------
                try {
                    redisUtil.setKey(wxToken.getWeixinAppid(), wxToken.getAccessToken(), 5400);
                } catch (Exception e) {
                    logger.error("----------定时任务:独立公众号,重置redis缓存token失败-------------" + e.toString());
                }
                //--------H5平台独立公众号,重置redis缓存---------------------------------------
            } else {
                logger.error("重置Token失败");
            }
            return "success";
        } catch (Exception e) {
            e.printStackTrace();
            return "重置accessToken时发生异常:" + e.getMessage();
        }
    }

授权

       是第三方生成一个二维码,公众号管理员,扫描授权后。我们就可以存储到表中。这样就可以管理公众号了。

    扫码授权

     controller

   

 /**
     * 授权二维码页面
     */
    @RequestMapping(value = "/getAuthhorization", method = RequestMethod.GET)
    @CrossOrigin
    public Result getAuthhorization(HttpServletRequest request) {
        return thirdService.getAuthhorization(request);
    }

service

public Result getAuthhorization(HttpServletRequest request) {
        try {

            WxOpenAccount weixinOpenAccount = openAccountMapperExt.queryOneByAppid(environment.getProperty("wx.component_appid"));
            if (weixinOpenAccount == null) {
                return Result.sendFailure("通过APPID获取WEIXINOPENACCOUNT为空!");
            }
            //获取ACCESSTOKEN
            if (StringUtils.isEmpty(weixinOpenAccount.getComponentAccessToken())) {
                return Result.sendFailure("未获取到第三方平台的ACCESSTOKEN!");
            }

            String redirect_uri = URLEncoder.encode(environment.getProperty("wx.authhorizationCallBackUrl") + "?userId=" + loginCache.getUserId(), "UTF-8");


            String authhorizationUrl = null;

            //获取预授权码
            String preAuthCode = getPreAuthCode(environment.getProperty("wx.component_appid"), weixinOpenAccount.getComponentAccessToken());
            if (preAuthCode == null) {
                return Result.sendFailure("获取预授权码失败");
            } else {
                authhorizationUrl = environment.getProperty("wx.authhorizationUrl").replace("PRE_AUTH_CODE", preAuthCode);
            }
            authhorizationUrl = authhorizationUrl.replace("REDIRECT_URI", redirect_uri).replace("COMPONENT_APPID", environment.getProperty("wx.component_appid"));
            logger.info("扫描的二维码地址是" + authhorizationUrl);
            return Result.sendSuccess("获取授权二维码成功", authhorizationUrl);
        } catch (Exception e) {
            logger.error("扫描授权失败,{}", e);
            return Result.sendFailure("扫描授权失败!");
        }


    }



    public String getPreAuthCode(String component_appid, String component_access_token) {
        String pre_auth_code = null;
        String requestUrl = environment.getProperty("wx.preUrl").replace("COMPONENT_ACCESS_TOKEN", component_access_token);
        GetPreAuthCodeParam getPreAuthCodeParam = new GetPreAuthCodeParam();
        getPreAuthCodeParam.setComponent_appid(component_appid);
        String obj = JSONObject.toJSONString(getPreAuthCodeParam);
        HttpResponse response = WxHttpUtil.post(requestUrl, obj);
        logger.info("getPreAuthCode Body:" + response.body());
        JSONObject jsonObject = JSONObject.parseObject(response.body());
        if (jsonObject.containsKey("errcode")) {
            logger.error("获取权限令牌信息!errcode=" + jsonObject.getString("errcode") + ",errmsg = " + jsonObject.getString("errmsg"));
            return pre_auth_code;
        } else {
            pre_auth_code = jsonObject.getString("pre_auth_code");
            return pre_auth_code;
        }
    }
wx.authhorizationUrl:微信授权二维的地址需要(https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=COMPONENT_APPID&pre_auth_code=PRE_AUTH_CODE&auth_type=1&redirect_uri=REDIRECT_URI)

wx.preUrl:获取预授权的地址(https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=COMPONENT_ACCESS_TOKEN

wx.authhorizationCallBackUrl:回调地址(用户授权后,微信回调的地址。xxx/callback)

授权回调

微信公众号授权后回调的地址

Cotroller

 /**
     * 授权回调地址
     *
     * @param request
     * @return
     */
    @RequestMapping(value = "/callback", method = RequestMethod.GET)
    @CrossOrigin
    public void callback(HttpServletRequest request, HttpServletResponse response) throws Exception {
         thirdService.callBack(request, response);
    }

Service

@Override
    public void callBack(HttpServletRequest request, HttpServletResponse response) {
        try {
            String authCode = request.getParameter("auth_code");
            //回调的时候添加的userId
            String userId = request.getParameter("userId");

            WxOpenAccount weixinOpenAccount = openAccountMapperExt.queryOneByAppid(environment.getProperty("wx.component_appid"));

            //调取接口获取平台ACCESSTOKEN
            String componentAccessToken = weixinOpenAccount.getComponentAccessToken();
            if (StringUtils.isEmpty(componentAccessToken)) {
                logger.error("授权公共号回调时获取ACCESSTOKEN为空!");
            }

            //调取接口 使用授权码获取授权信息
            String urlFormat = environment.getProperty("wx.apiQueryAuth").replace("COMPONENT_ACCESS_TOKEN", componentAccessToken);
            JSONObject json = new JSONObject();
            json.put("component_appid", environment.getProperty("wx.component_appid"));
            json.put("authorization_code", authCode);
            logger.info("授权公共号回调后调取接口请求参数为:{}", new Object[]{json.toString()});
            JSONObject jsonObject = WxHttpUtil.postForJson(urlFormat, JSONObject.toJSONString(json));
            logger.info("授权公共号回调后调取接口返回参数为:{}", new Object[]{jsonObject});
            if (jsonObject != null && !jsonObject.containsKey("errcode")) {
                WxToken wxToken = new WxToken();
                // 保存授权公众号的部分信息
                wxToken.setCreateUser(userId);
                save(jsonObject, wxToken);
                // 通过第三方token获取公众号信息
                String getAuthorizerInfoUrl = environment.getProperty("wx.baseUrl") + CommonWeixinProperties.getAuthorizerInfo.replace("COMPONENT_ACCESS_TOKEN", componentAccessToken);

                JSONObject j = new JSONObject();
                // 第三方平台appid
                j.put("component_appid", environment.getProperty("wx.component_appid"));
                // 授权用户的appid
                j.put("authorizer_appid", wxToken.getWeixinAppid());
                JSONObject jsonObj = WxHttpUtil.postForJson(getAuthorizerInfoUrl, j.toString());
                logger.info("===========授权回调方法===获取授权公众号详细Info===" + jsonObj.toString() + "===========");
                if (jsonObj != null && !jsonObj.containsKey("errcode")) {
                    update(jsonObj, wxToken);
                }

                //给用户绑定相应的微信公众号
                if (userId != null) {
                        
                    }

                }
                //存储到redis中。
                redisUtil.setKey(wxToken.getWeixinAppid(), wxToken.getAccessToken(), 5400);
            }
            logger.info("授权成功!");
            String redirectUrl=environment.getProperty("wx.oAuthDomain");
            response.sendRedirect(redirectUrl);
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("授权信息回调方法中,发生错误,错误信息={}", e);
            logger.error("授权失败");
        }

    }

wx.apiQueryAuth:获取授权方的帐号基本信息(https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_info?component_access_token=COMPONENT_ACCESS_TOKEN)
 

这样你以为就结束了。但是不是,还有一个接收信息的方法。

接收公众号信息

Controller

/**
     * 接收微信服务器
     */
    @RequestMapping("/{APPID}/message")
    public void message(HttpServletRequest request, HttpServletResponse response,@PathVariable("APPID") String APPID) throws Exception {
         thirdService.getMessage(request,response,APPID);
    }

Service

public void getMessage(HttpServletRequest req, HttpServletResponse resp, String appid) {
        // 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
        String signature = req.getParameter("msg_signature");
        logger.info("第三方平台全网发布-------------" + appid + "-----------验证开始。。。。msg_signature=" + signature);

        setHeaderParam(req, resp);


        if (!StringUtils.isNotBlank(signature)) {
            return;// 微信推送给第三方开放平台的消息一定是加过密的,无消息加密无法解密消息
        }

        //时间戳
        String timestamp = req.getParameter("timestamp");
        // 随机数
        String nonce = req.getParameter("nonce");

        PrintWriter out = null;
        Map<String, Object> receiveMap = null;
        try {
            out = resp.getWriter();
            //兼容加密与不加密
            String resultMsg = null;
            if (StringUtils.isEmpty(signature)) {
                //不加密处理
                receiveMap = MessageUtil.parseXml(req);
                logger.info("接收的微信推送消息为:{}", receiveMap.toString());
            } else {
                //加密处理
                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
                dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
                DocumentBuilder db = dbf.newDocumentBuilder();
                Document document = db.parse(req.getInputStream());
                Element root = document.getDocumentElement();
                String encrypt = root.getElementsByTagName("Encrypt").item(0).getTextContent();
                String format = "<xml><ToUserName><![CDATA[toUser]]></ToUserName><Encrypt><![CDATA[%1$s]]></Encrypt></xml>";
                String fromXml = String.format(format, encrypt);


                //解密密文
                WxBizMsgCrypt pc = new WxBizMsgCrypt(environment.getProperty("wx.component_token"), environment.getProperty("wx.component_encodingaeskey"), environment.getProperty("wx.component_appid"));
                resultMsg = pc.decryptMsg(signature, timestamp, nonce, fromXml);
                logger.info("接收的微信推送消息为:{}", resultMsg);
            }
           
              //处理接收到的信息。  
              wxMessageService.processMessage(resultMsg, receiveMap, appid);


            out.println("");
        } catch (Exception e) {
            logger.error(e.getLocalizedMessage(), e);
            if (out != null) {
                out.println("");
            }
        } finally {
            if (out != null) {
                out.close();
            }
            out = null;
        }
        logger.info("全网发布接入检测消息反馈开始---------------APPID=" + appid);
    }

这样就完成了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值