WeixinComponent - TuPengXiong/TuPengXiong.github.io GitHub Wiki

maven Groovy语言

<!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>4.3.3.RELEASE</version>
		</dependency>

相关代码

package xyz.kingsilk.qh.service.service

import grails.converters.JSON
import grails.transaction.Transactional
import org.apache.commons.codec.binary.Base64
import org.apache.commons.codec.digest.DigestUtils
import org.codehaus.groovy.grails.web.servlet.mvc.GrailsParameterMap
import org.springframework.http.HttpEntity
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpMethod
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.client.RestTemplate
import org.springframework.web.util.UriComponentsBuilder

import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
import java.nio.charset.Charset

/**
 * 微信第三方平台
 */
@Transactional
class WeixinComponentService {


    public static final Charset CHARSET = Charset.forName("utf-8");

    RestTemplate wxRestTemplate

    /** API URL : 获取第三方平台component_access_token */
    public static final String COMPONENT_TOKEN = "https://api.weixin.qq.com/cgi-bin/component/api_component_token"

    /** API URL : 获取预授权码pre_auth_code */
    public static final String CREATE_PREAUTHCODE = "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode"

    /** API URL : 使用授权码换取公众号的接口调用凭据和授权信息 */
    public static final String QUERY_AUTH = "https://api.weixin.qq.com/cgi-bin/component/api_query_auth"

    /** API URL : 使用授权码换取公众号的接口调用凭据和授权信息 */
    public static final String AUTHORIZER_TOKEN = "https://api.weixin.qq.com /cgi-bin/component/api_authorizer_token"

    /** API URL : 获取授权方的公众号帐号基本信息 */
    public static
    final String GET_AUTHORIZER_INFO = "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_info"

    /** API URL : 获取授权方的选项设置信息 */
    public static
    final String GET_AUTHORIZER_OPTION = "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_option"

    /** API URL : 设置授权方的选项设置信息 */
    public static
    final String SET_AUTHORIZER_OPTION = "https://api.weixin.qq.com/cgi-bin/component/ api_set_authorizer_option"

    /**
     * 获取第三方平台component_access_token
     * https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1453779503&token=c921fd115eecdb869ad572e9f0e2668b29e4f495&lang=zh_CN
     * @param component_appid ;第三方平台方appid
     * @param component_appsecret ;第三方平台appsecret
     * @param component_verify_ticket ;微信后台推送的ticket,此ticket会定时推送,具体请见本页的推送说明
     * @return
     返回结果示例{
     "component_access_token":"61W3mEpU66027wgNZ_MhGHNQDHnFATkDa9-2llqrMBjUwxRSNPbVsMmyD-yq8wZETSoE5NQgecigDrSHkPtIYA",
     "expires_in":7200
     }
     */
    public String api_component_token(String component_appid, String component_appsecret, String component_verify_ticket) {
        URI uri = UriComponentsBuilder.fromHttpUrl(COMPONENT_TOKEN)
                .build()
                .encode("UTF-8")
                .toUri()
        HttpHeaders headers = new HttpHeaders()
        headers.setContentType(MediaType.APPLICATION_JSON)
        def json = ([
                component_appid        : component_appid,
                component_appsecret    : component_appsecret,
                component_verify_ticket: component_verify_ticket
        ] as JSON)
        HttpEntity<String> reqEntity = new HttpEntity<String>(json.toString(), headers)
        ResponseEntity respEntity = wxRestTemplate.exchange(uri, HttpMethod.POST, reqEntity, String.class)
        return respEntity?.body
    }

    /**
     * 获取预授权码pre_auth_code
     * https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1453779503&token=c921fd115eecdb869ad572e9f0e2668b29e4f495&lang=zh_CN
     * @param component_appid ;第三方平台appid
     * @param component_access_token
     * @return{
     "pre_auth_code":"Cx_Dk6qiBE0Dmx4EmlT3oRfArPvwSQ-oa3NL_fwHM7VI08r52wazoZX2Rhpz1dEw",
     "expires_in":600
     }
     */
    public String api_create_preauthcode(String component_access_token, String component_appid) {
        URI uri = UriComponentsBuilder.fromHttpUrl(CREATE_PREAUTHCODE)
                .queryParam("component_access_token", component_access_token)
                .build()
                .encode("UTF-8")
                .toUri()
        HttpHeaders headers = new HttpHeaders()
        headers.setContentType(MediaType.APPLICATION_JSON)
        def json = ([
                component_appid: component_appid,
        ] as JSON)
        HttpEntity<String> reqEntity = new HttpEntity<String>(json.toString(), headers)
        ResponseEntity respEntity = wxRestTemplate.exchange(uri, HttpMethod.POST, reqEntity, String.class)
        return respEntity?.body
    }

    /**
     * 使用授权码换取公众号的接口调用凭据和授权信息
     * https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1453779503&token=c921fd115eecdb869ad572e9f0e2668b29e4f495&lang=zh_CN
     * @param component_appid ;授权code,会在授权成功时返回给第三方平台,详见第三方平台授权流程说明
     * @param component_access_token
     * @param authorization_code ;授权code,会在授权成功时返回给第三方平台,详见第三方平台授权流程说明
     * @return{{
     "authorization_info": {
     "authorizer_appid": "wxf8b4f85f3a794e77",
     "authorizer_access_token": "QXjUqNqfYVH0yBE1iI_7vuN_9gQbpjfK7hYwJ3P7xOa88a89-Aga5x1NMYJyB8G2yKt1KCl0nPC3W9GJzw0Zzq_dBxc8pxIGUNi_bFes0qM",
     "expires_in": 7200,
     "authorizer_refresh_token": "dTo-YCXPL4llX-u1W1pPpnp8Hgm4wpJtlR6iV0doKdY",
     "func_info": [{
     "funcscope_category": {
     "id": 1
     }
     },{
     "funcscope_category": {
     "id": 2
     }
     },{
     "funcscope_category": {
     "id": 3
     }
     }]
     }}
     */
    public String api_query_auth(String component_access_token, String component_appid, String authorization_code) {
        URI uri = UriComponentsBuilder.fromHttpUrl(QUERY_AUTH)
                .queryParam("component_access_token", component_access_token)
                .build()
                .encode("UTF-8")
                .toUri()
        HttpHeaders headers = new HttpHeaders()
        headers.setContentType(MediaType.APPLICATION_JSON)
        def json = ([
                component_appid   : component_appid,
                authorization_code: authorization_code
        ] as JSON)
        HttpEntity<String> reqEntity = new HttpEntity<String>(json.toString(), headers)
        ResponseEntity respEntity = wxRestTemplate.exchange(uri, HttpMethod.POST, reqEntity, String.class)
        return respEntity?.body
    }

    /**
     * 获取(刷新)授权公众号的接口调用凭据(令牌)
     * https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1453779503&token=c921fd115eecdb869ad572e9f0e2668b29e4f495&lang=zh_CN
     * @param component_appid ;第三方平台appid
     * @param component_access_token
     * @param authorizer_appid ;授权方appid
     * @param authorizer_refresh_token ;授权方的刷新令牌
     * @return {
     "authorizer_access_token": "aaUl5s6kAByLwgV0BhXNuIFFUqfrR8vTATsoSHukcIGqJgrc4KmMJ-JlKoC_-NKCLBvuU1cWPv4vDcLN8Z0pn5I45mpATruU0b51hzeT1f8",
     "expires_in": 7200,
     "authorizer_refresh_token": "BstnRqgTJBXb9N2aJq6L5hzfJwP406tpfahQeLNxX0w"
     }
     */
    public String api_authorizer_token(String component_access_token, String component_appid, String authorizer_appid, String authorizer_refresh_token) {
        URI uri = UriComponentsBuilder.fromHttpUrl(AUTHORIZER_TOKEN)
                .queryParam("component_access_token", component_access_token)
                .build()
                .encode("UTF-8")
                .toUri()
        HttpHeaders headers = new HttpHeaders()
        headers.setContentType(MediaType.APPLICATION_JSON)
        def json = ([
                component_appid         : component_appid,
                authorizer_appid        : authorizer_appid,
                authorizer_refresh_token: authorizer_refresh_token
        ] as JSON)
        HttpEntity<String> reqEntity = new HttpEntity<String>(json.toString(), headers)
        ResponseEntity respEntity = wxRestTemplate.exchange(uri, HttpMethod.POST, reqEntity, String.class)
        return respEntity?.body
    }

    /**
     * 获取授权方的公众号帐号基本信息
     * https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1453779503&token=c921fd115eecdb869ad572e9f0e2668b29e4f495&lang=zh_CN
     * @param component_appid ;第三方平台appid
     * @param component_access_token
     * @param authorizer_appid ;授权方appid
     * @return{
     "authorizer_info": {
     "nick_name": "微信SDK Demo Special",
     "head_img": "http://wx.qlogo.cn/mmopen/GPyw0pGicibl5Eda4GmSSbTguhjg9LZjumHmVjybjiaQXnE9XrXEts6ny9Uv4Fk6hOScWRDibq1fI0WOkSaAjaecNTict3n6EjJaC/0",
     "service_type_info": { "id": 2 },
     "verify_type_info": { "id": 0 },
     "user_name":"gh_eb5e3a772040",
     "business_info": {"open_store": 0, "open_scan": 0, "open_pay": 0, "open_card": 0, "open_shake": 0},
     "alias":"paytest01"
     },
     "qrcode_url":"URL",
     "authorization_info": {
     "appid": "wxf8b4f85f3a794e77",
     "func_info": [{ "funcscope_category": { "id": 1 }},{ "funcscope_category": { "id": 2 }},{ "funcscope_category": { "id": 3 }}]
     }
     }
     */
    public String api_get_authorizer_info(String component_access_token, String component_appid, String authorizer_appid) {
        URI uri = UriComponentsBuilder.fromHttpUrl(GET_AUTHORIZER_INFO)
                .queryParam("component_access_token", component_access_token)
                .build()
                .encode("UTF-8")
                .toUri()
        HttpHeaders headers = new HttpHeaders()
        headers.setContentType(MediaType.APPLICATION_JSON)
        def json = ([
                component_appid : component_appid,
                authorizer_appid: authorizer_appid,
        ] as JSON)
        HttpEntity<String> reqEntity = new HttpEntity<String>(json.toString(), headers)
        ResponseEntity respEntity = wxRestTemplate.exchange(uri, HttpMethod.POST, reqEntity, String.class)
        return respEntity?.body
    }

    /**
     * 获取授权方的选项设置信息
     * 该API用于获取授权方的公众号的选项设置信息,如:地理位置上报,语音识别开关,多客服开关。注意,获取各项选项设置信息,需要有授权方的授权
     * https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1453779503&token=c921fd115eecdb869ad572e9f0e2668b29e4f495&lang=zh_CN
     * @param component_appid ;第三方平台appid
     * @param component_access_token
     * @param authorizer_appid ;授权方appid
     * @param option_name ;选项名称
     * @return {
     "authorizer_appid":"wx7bc5ba58cabd00f4",
     "option_name":"voice_recognize",
     "option_value":"1"
     }
     */
    public String api_get_authorizer_option(String component_access_token, String component_appid, String authorizer_appid, String option_name) {
        URI uri = UriComponentsBuilder.fromHttpUrl(GET_AUTHORIZER_OPTION)
                .queryParam("component_access_token", component_access_token)
                .build()
                .encode("UTF-8")
                .toUri()
        HttpHeaders headers = new HttpHeaders()
        headers.setContentType(MediaType.APPLICATION_JSON)
        def json = ([
                component_appid : component_appid,
                authorizer_appid: authorizer_appid,
                option_name     : option_name
        ] as JSON)
        HttpEntity<String> reqEntity = new HttpEntity<String>(json.toString(), headers)
        ResponseEntity respEntity = wxRestTemplate.exchange(uri, HttpMethod.POST, reqEntity, String.class)
        return respEntity?.body
    }

    /**
     *  设置授权方的选项信息
     * 该API用于获取授权方的公众号的选项设置信息,如:地理位置上报,语音识别开关,多客服开关。注意,获取各项选项设置信息,需要有授权方的授权
     * https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1453779503&token=c921fd115eecdb869ad572e9f0e2668b29e4f495&lang=zh_CN
     * @param component_appid ;第三方平台appid
     * @param component_access_token
     * @param authorizer_appid ;授权方appid
     * @param option_name ;选项名称
     * @param option_value ;设置的选项值
     * @return {
     "errcode":0,
     "errmsg":"ok"
     }
     */
    public String api_set_authorizer_option(String component_access_token, String component_appid, String authorizer_appid, String option_name, String option_value) {
        URI uri = UriComponentsBuilder.fromHttpUrl(SET_AUTHORIZER_OPTION)
                .queryParam("component_access_token", component_access_token)
                .build()
                .encode("UTF-8")
                .toUri()
        HttpHeaders headers = new HttpHeaders()
        headers.setContentType(MediaType.APPLICATION_JSON)
        def json = ([
                component_appid : component_appid,
                authorizer_appid: authorizer_appid,
                option_name     : option_name,
                option_value    : option_value
        ] as JSON)
        HttpEntity<String> reqEntity = new HttpEntity<String>(json.toString(), headers)
        ResponseEntity respEntity = wxRestTemplate.exchange(uri, HttpMethod.POST, reqEntity, String.class)
        return respEntity?.body
    }


    /**
     * 消息解密
     * @param content //加密的内容
     * @param encodingAesKey //aes的key值
     * @param result //存放 公众号来自的appid 和 消息解密后的明文 msg_decrypt
     * @return
     */
    void decrypt(String content, String encodingAesKey, def result) {
        byte[] aesKey = Base64.decodeBase64(encodingAesKey + "=")
        byte[] original;

        // 设置解密模式为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(content);
        // 解密
        original = cipher.doFinal(encrypted);
        // 去除补位字符
        int pad = (int) original[original.length - 1];
        if (pad < 1 || pad > 32) {
            pad = 0;
        }
        byte[] bytes = Arrays.copyOfRange(original, 0, original.length - pad);
        // 分离16位随机字符串,网络字节序和AppId
        byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
        //// 还原4个字节的网络字节序
        int sourceNumber = 0;
        for (int i = 0; i < 4; i++) {
            sourceNumber <<= 8;
            sourceNumber |= networkOrder[i] & 0xff;
        }
        int xmlLength = sourceNumber;

        String xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), Charset.forName("utf-8"));

        String from_appid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), Charset.forName("utf-8"));

        log.debug("from_appid:" + from_appid)
        // appid不相同的情况
        if (!from_appid.equals(result.appid)) {
            log.debug("appid不相同")
            result.appid = from_appid
        }
        result.msg_decrypt = xmlContent
    }



    /**
     *  对明文进行加密.
     * @param text 要加密的明文
     * @param targetAppid //appid
     * @param encodingAesKey //aes加密的key
     * @return
     *  加密的字符串
     */
    String encrypt(String text, String targetAppid, String encodingAesKey) {
        String randomStr = getRandomStr();
        byte[] aesKey = Base64.decodeBase64(encodingAesKey + "=")
        ArrayList<Byte> byteCollector = new ArrayList<Byte>();
        byte[] randomStrBytes = randomStr.getBytes(CHARSET);
        byte[] textBytes = text.getBytes(CHARSET);
        byte[] orderBytes = new byte[4];

        // 生成4个字节的网络字节序
        orderBytes[3] = (byte) (textBytes.length & 0xFF);
        orderBytes[2] = (byte) (textBytes.length >> 8 & 0xFF);
        orderBytes[1] = (byte) (textBytes.length >> 16 & 0xFF);
        orderBytes[0] = (byte) (textBytes.length >> 24 & 0xFF);
        byte[] networkBytesOrder = orderBytes
        byte[] appidBytes = targetAppid.getBytes(CHARSET);

        // randomStr + networkBytesOrder + text + appid
        for (byte b : randomStrBytes) {
            byteCollector.add(b);
        }
        for (byte b : networkBytesOrder) {
            byteCollector.add(b);
        }
        for (byte b : textBytes) {
            byteCollector.add(b);
        }
        for (byte b : appidBytes) {
            byteCollector.add(b);
        }

        // ... + pad: 使用自定义的填充方式对明文进行补位填充
        int amountToPad = 32 - (byteCollector.size() % 32);
        if (amountToPad == 0) {
            amountToPad = 32;
        }
        // 获得补位所用的字符
        byte target = (byte) (amountToPad & 0xFF);
        char padChr = (char) target
        String tmp = new String();
        for (int index = 0; index < amountToPad; index++) {
            tmp += padChr;
        }
        byte[] padBytes = tmp.getBytes(CHARSET);

        for (byte b : padBytes) {
            byteCollector.add(b);
        }

        // 获得最终的字节流, 未加密
        byte[] bytes = new byte[byteCollector.size()];
        log.debug(byteCollector.size())
        for (int i = 0; i < byteCollector.size(); i++) {

            bytes[i] = byteCollector.get(i);
        }
        byte[] unencrypted = bytes;

        // 设置加密模式为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 = new Base64().encodeToString(encrypted);

        return base64Encrypted;

    }

    // 随机生成16位字符串
    String getRandomStr() {
        String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        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 params
     * @return
     */
    public boolean checkSignature(GrailsParameterMap params, String token) throws Exception {
        String newSignature = getSignature(params, token)
        if (params.msg_encrypt && params.msg_signature) {
            return newSignature == params.msg_signature
        } else {
            return newSignature == params.signature
        }
    }

    /**
     * 验证签名/消息验证
     * @param params
     * @return
     */
    public String getSignature(GrailsParameterMap params, String token) throws Exception {
        String signature = params.signature
        String msg_signature = params.msg_signature
        String timestamp = params.timestamp
        String nonce = params.nonce
        //1)将token、timestamp、nonce三个参数进行字典序排序
        ArrayList<String> list = new ArrayList<String>();
        /**消息加密**/
        if (params.msg_encrypt && params.msg_signature) {
            list.add(token);
            list.add(timestamp);
            list.add(nonce);
            list.add(params.msg_encrypt);
        } else {
            //签名验证
            list.add(nonce);
            list.add(timestamp);
            list.add(token);
        }
        Collections.sort(list);
        //2)将三个参数字符串拼接成一个字符串进行sha1加密
        String newSignature
        if (list.size() == 3) {
            newSignature = DigestUtils.shaHex(list.get(0) + list.get(1) + list.get(2))
            //3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
            return newSignature
        } else {
            newSignature = DigestUtils.shaHex(list.get(0) + list.get(1) + list.get(2) + list.get(3))
            //3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
            return newSignature
        }

    }

}

⚠️ **GitHub.com Fallback** ⚠️