企業微信第三方應用(三)基於springboot開發(獲取Ticket,auth_code)


一、構建spring boot項目
1、新建項目
新建一個模塊(module):enterprise-wechat
新建一個子模塊(module):wechat
目錄結構如下:

結構描述:
common
-> WeChatConstants:存放企業微信一些常量,公用參數
-> WeChatUtils:存放企業微信第三方應用api
controller
-> SystemController:控制層,接收請求
entity
-> aes:目錄下文件企業微信加解密包
service
-> IConfigService:調用企業微信服務層
pom.xml
-> 導入所需要的jar包

pom.xml中需要導入commons.codec包

<dependency>
   <groupId>commons-codec</groupId>
   <artifactId>commons-codec</artifactId>
   <version>1.9</version>
</dependency>

2、方法描述
1)doGetCallback:
① 接收驗證請求,用於驗證通用開發參數系統事件接收URL、數據回調URL、指令回調URL。
② 企業微信后台錄入回調URL點擊保存時,微信服務器會立即發送一條GET請求到對應URL,該函數就對URL的signature進行驗證。

2)doPostCallback:
① 用於獲取 suite_ticket,安裝應用時企業微信傳遞過來的auth_code:指令回調URL
② 當刷新ticket傳遞【SuitID】:指令回調URL
③ 當打開應用時傳遞【CorpID】:數據回調URL

3、代碼編寫
1)企業微信配置類:WeChatConstants

package com.wechat.common;

/**
 * 企業微信
 */
public class WeChatConstants {

    // 企業微信授權碼獲取時間
    public static final Long EXPIRES_IN = 24 * 60 * 60 * 1000L;
    //24 * 60 * 60 * 1000L 7200L * 1000

    /**
     * 服務商CorpID
     */
    public static final String CORP_ID = "ww14438c6c07a317f2";
    /**
     * 服務商身份的調用憑證
     */
    public static final String PROVIDER_SECRET = "RH7PehRJX3LIcw4axad_H2T9HSUG1finOBEpnLTVIioBrP-zgZrGsqJ9pHVw5vVj";

    /**
     * 應用的唯一身份標識
     */
    public static final String SUITE_ID = "ww4f66fa544a32f920";
    /**
     * 應用的調用身份密鑰
     */
    public static final String SUITE_SECRET = "vVv8JzaBlEVCTQkHKqmr57EAMs65AILWiI_4ANc25T4";

    /**
     * 應用的ticket
     */
    public static final String SUITE_TICKET = "SUITE_TICKET";

    /**
     * 應用的auth_code
     */
    public static final String AUTH_CODE = "AUTH_CODE";

    /**
     * 第三方應用憑證token
     */
    public static final String SUITE_TOKEN = "suiteToken";

    /**
     * 授權方(企業)token
     */
    public static final String ACCESS_TOKEN = "ACCESS_TOKEN";

    /**
     * 提供商 授權方服務token
     */
    public static final String PROVIDER_ACCESS_TOKEN = "PROVIDER_ACCESS_TOKEN";

    /**
     * 應用企業corpid
     */
    public static final String AUTH_CORPID = "AUTH_CORPID";

    /**
     * 企業名稱
     */
    public static final String CORP_NAME = "CORPNAME";

    /**
     * 授權方的網頁應用ID,在具體的網頁應用中查看
     */
    public static final String AGENT_ID = "AGENTID";

    /**
     * 用戶id
     */
    public static final String USER_ID = "userId";

    // 回調相關
    /**
     * 回調/通用開發參數Token, 兩者解密算法一樣,所以為方便設為一樣
     */
    public static final String TOKENS = "E0sOXx4LqeE5BmDvMTAz3x";

    /**
     * 回調/通用開發參數EncodingAESKey, 兩者解密算法一樣,所以為方便設為一樣
     */
    public static final String ENCODING_AES_KEY = "IESLPSyW4vyBB90jkzfwfYRtcMky6LIOevr4SVefz7I";

    public static final String REDIRECT_URI = "REDIRECT_URI";

    /**
     * 重定向地址,自己設置
     */
    public static final String REDIRECT_URL = "www.baidu.com";

    // 第三方應用id(即ww或wx開頭的suite_id)
    public static final String APP_ID= "APPID";

    public static final String PERMANENT_CODE = "PERMANENT_CODE";

}


2)企業微信api:WeChatUtils

package com.wechat.common;

/**
 * 企業微信工具類
 */
public class WeChatUtils {
    /**
     * 第三方應用api start
     */
    // 獲取第三方應用憑證
    public final static String THIRD_BUS_WECHAT_SUITE_TOKEN = "https://qyapi.weixin.qq.com/cgi-bin/service/get_suite_token";

    // 獲取企業永久授權碼
    public final static String THIRD_BUS_WECHAT_ACCESS_TOKEN = "https://qyapi.weixin.qq.com/cgi-bin/service/get_permanent_code?suite_access_token=SUITE_ACCESS_TOKEN";

    // 第三方 構造掃碼登錄鏈接
    public final static String THIRD_BUS_WECHAT_LOGIN = "https://open.work.weixin.qq.com/wwopen/sso/3rd_qrConnect?appid=CORPID&redirect_uri=REDIRECT_URI&state=web_login&usertype=member";

    // 第三方 獲取登錄用戶信息 POST
    public final static String THIRD_BUS_WECHAT_GET_LOGIN_INFO = "https://qyapi.weixin.qq.com/cgi-bin/service/get_login_info?access_token=PROVIDER_ACCESS_TOKEN";

    // 第三方 構造網頁授權鏈接
    public final static String THIRD_BUS_WECHAT_AUTHORIZE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_privateinfo&state=STATE#wechat_redirect";

    // 第三方 獲取訪問用戶身份 GET
    public final static String THIRD_BUS_WECHAT_GET_USER_INFO = "https://qyapi.weixin.qq.com/cgi-bin/service/getuserinfo3rd?suite_access_token=SUITE_TOKEN&code=CODE";

    // 第三方 獲取訪問用戶敏感信息 post
    public final static String THIRD_BUS_WECHAT_GET_USER_DETAIL3RD = "https://qyapi.weixin.qq.com/cgi-bin/service/getuserdetail3rd?suite_access_token=SUITE_ACCESS_TOKEN";

    // 第三方 獲取部門列表
    public final static String THIRD_BUS_WECHAT_DEPART_LIST = "https://qyapi.weixin.qq.com/cgi-bin/department/list?access_token=ACCESS_TOKEN&id=ID";

    // 第三方 獲取部門成員
    public final static String THIRD_BUS_WECHAT_DEPART_USER = "https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID&fetch_child=FETCH_CHILD";

    // 第三方 獲取部門成員詳情
    public final static String THIRD_BUS_WECHAT_DEPART_USER_DETAIL = "https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID&fetch_child=FETCH_CHILD";

    // 第三方 讀取成員 GET
    public final static String THIRD_BUS_WECHAT_GET_USER = "https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&userid=USERID";

    // 服務商的token
    public final static String THIRD_BUS_WECHAT_GET_PROVIDER_TOKEN = "https://qyapi.weixin.qq.com/cgi-bin/service/get_provider_token";

    // 獲取企業憑證
    public final static String THIRD_BUS_WECHAT_GET_CORP_TOKEN = "https://qyapi.weixin.qq.com/cgi-bin/service/get_corp_token?suite_access_token=SUITE_ACCESS_TOKEN";

    // 發送應用消息
    public final static String THIRD_BUS_WECHAT_SEND = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN";

    // 獲取應用的jsapi_ticket
    public final static String THIRD_BUS_GET_JSAPI_TICKET = "https://qyapi.weixin.qq.com/cgi-bin/ticket/get?access_token=ACCESS_TOKEN&type=agent_config";

    // 獲取企業的jsapi_ticket
    public final static String THIRD_BUS_GET_JSAPI_TICKET_BUS = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=ACCESS_TOKEN";
    /**
     * 第三方應用api end
     */

}

3)controller層:SystemController

package com.wechat.controller;

import com.wechat.service.IConfigService;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

/**
 * 控制層
 */
@Slf4j
@RestController
@RequestMapping(value = "system")
public class SystemController {

    @Autowired
    private IConfigService configService;

    /**
     * 驗證通用開發參數及應用回調
     * @param: request
     * @param: response
     * @returns: void
     */
    @ApiOperation(value = "驗證通用開發參數及應用回調")
    @GetMapping(value = "getEchostr")
    public void doGetCallback(HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 微信加密簽名
        String msgSignature = request.getParameter("msg_signature");
        // 時間戳
        String timestamp = request.getParameter("timestamp");
        // 隨機數
        String nonce = request.getParameter("nonce");
        // 隨機字符串
        // 如果是刷新,需返回原echostr
        String echoStr = request.getParameter("echostr");
        String sEchoStr=  "";
        PrintWriter out;
        log.debug("msgSignature: " + msgSignature+"timestamp="+timestamp+"nonce="+nonce+"echoStr="+echoStr);
        try {
            sEchoStr = configService.doGetCallback(msgSignature,timestamp,nonce,echoStr); //需要返回的明文;
            log.debug("doGetCallback-> echostr: " + sEchoStr);
            // 驗證URL成功,將sEchoStr返回
            out = response.getWriter();
            out.print(sEchoStr);
        } catch (Exception e) {
            //驗證URL失敗,錯誤原因請查看異常
            e.printStackTrace();
        }
    }

    /**
     * 刷新ticket,AuthCode
     */
    @ApiOperation(value = "刷新ticket,AuthCode")
    @PostMapping(value = "getEchostr")
    public String doPostCallback(HttpServletRequest request) throws Exception {
        // 微信加密簽名
        String msgSignature = request.getParameter("msg_signature");
        // 時間戳
        String timestamp = request.getParameter("timestamp");
        // 隨機數
        String nonce = request.getParameter("nonce");
        // 類型
        String type = request.getParameter("type");
        // 企業id
        String corpId = request.getParameter("corpid");
        ServletInputStream in = request.getInputStream();
        // 刷新ticket,AuthCode
        String success = configService.doPostCallback(msgSignature, timestamp, nonce, type, corpId, in);
        return success;
    }
}

4)Service層:IConfigService

package com.wechat.service;

import javax.servlet.ServletInputStream;

/**
 * 企業微信第三方服務service
 */
public interface IConfigService {

    /**
     * 驗證通用開發參數及應用回調
     * @returns: java.lang.String
     */
    String doGetCallback(String msgSignature, String timestamp, String nonce, String echoStr);

    /**
     * 獲取SuiteTicket,AuthCode
     */
    String doPostCallback(String msgSignature, String timestamp, String nonce, String type, String corpId, ServletInputStream in);
}

5)service實現類:ConfigServiceImpl

package com.wechat.service.impl;

import com.alibaba.druid.support.json.JSONUtils;
import com.wechat.common.StringUtils;
import com.wechat.common.WeChatConstants;
import com.wechat.common.WxUtil;
import com.wechat.common.cache.CacheData;
import com.wechat.entity.aes.AesException;
import com.wechat.entity.aes.WXBizMsgCrypt;
import com.wechat.service.IConfigService;
import com.wechat.service.IWeChatService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.ServletInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Map;

/**
 * 回調service
 */
@Slf4j
@Service
public class ConfigServiceImpl implements IConfigService {

    @Autowired
    private IWeChatService weChatService;

    /**
     * 驗證通用開發參數及應用回調
     * @returns: java.lang.String
     */
    @Override
    public String doGetCallback(String msgSignature, String timestamp, String nonce, String echoStr) {
        //需要返回的明文
        String sEchoStr="";
        try {
            log.debug(WeChatConstants.TOKENS, WeChatConstants.ENCODING_AES_KEY, WeChatConstants.CORP_ID);
            WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(WeChatConstants.TOKENS, WeChatConstants.ENCODING_AES_KEY, WeChatConstants.CORP_ID);
            sEchoStr = wxcpt.VerifyURL(msgSignature, timestamp, nonce, echoStr);
        } catch (AesException e) {
            e.printStackTrace();
        }
        return sEchoStr;
    }

    /**
     * 獲取SuiteTicket,AuthCode
     * @param: msgSignature 微信加密簽名
     * @param: timestamp 時間戳
     * @param: nonce  隨機數
     * @param: type 類型
     * @param: corpId 企業id
     * @param: in
     * @returns: java.lang.String
     */
    @Override
    public String doPostCallback(String msgSignature, String timestamp, String nonce, String type, String corpId, ServletInputStream in) {
        String id = "";
        // 訪問應用和企業回調傳不同的ID
        if(!StringUtils.isNull(type) && type.equals("data")){
            id = corpId;
            log.debug("======corpId==="+id);
        } else {
            id = WeChatConstants.SUITE_ID;
            log.debug("======SuiteId===" + id);
        }
        try {
            WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(WeChatConstants.TOKENS, WeChatConstants.ENCODING_AES_KEY, id);
            String postData="";   // 密文,對應POST請求的數據
            //1.獲取加密的請求消息:使用輸入流獲得加密請求消息postData
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            String tempStr = "";   //作為輸出字符串的臨時串,用於判斷是否讀取完畢
            while(null != (tempStr=reader.readLine())){
                postData+=tempStr;
            }
            log.debug("====msg_signature===="+msgSignature+"====timestamp==="+timestamp+"====nonce==="+nonce+"====postData==="+postData);
            String suiteXml = wxcpt.DecryptMsg(msgSignature, timestamp, nonce, postData);
            log.debug("suiteXml: " + suiteXml);
            Map suiteMap = WxUtil.parseXml(suiteXml);
            log.debug("==suiteMap=="+ JSONUtils.toJSONString(suiteMap));
            if(suiteMap.get("SuiteTicket") != null) {
                String suiteTicket = (String) suiteMap.get("SuiteTicket");
                CacheData.put(WeChatConstants.SUITE_TICKET, suiteTicket);
                log.debug("====SuiteTicket=====" + suiteTicket);
            } else if(suiteMap.get("AuthCode") != null){
                String authCode = (String) suiteMap.get("AuthCode");
                log.debug("doPostValid->AuthCode:" + authCode);
                //根據authcode獲取企業永久授權碼
                weChatService.getPermanentCode(authCode);
                CacheData.put(WeChatConstants.AUTH_CODE, authCode);

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "success";
    }
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!--<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.5</version>
        <relativePath/>

    </parent>-->
    <parent>
        <groupId>org.example</groupId>
        <artifactId>third-wechat</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <groupId>com.wechat</groupId>
    <artifactId>wechat</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>wechat</name>
    <description>wechat</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.0.0</version>
        </dependency>

        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.9</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.10</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.4</version>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.5.24</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.5</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.6</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- 日志存放路徑 -->
	<property name="log.path" value="logs/wechat" />
   <!-- 日志輸出格式 -->
	<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />

    <!-- 控制台輸出 -->
	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<pattern>${log.pattern}</pattern>
		</encoder>
	</appender>

    <!-- 系統日志輸出 -->
	<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
	    <file>${log.path}/info.log</file>
        <!-- 循環政策:基於時間創建日志文件 -->
		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志文件名格式 -->
			<fileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
			<!-- 日志最大的歷史 60天 -->
			<maxHistory>60</maxHistory>
		</rollingPolicy>
		<encoder>
			<pattern>${log.pattern}</pattern>
		</encoder>
		<filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 過濾的級別 -->
            <level>INFO</level>
            <!-- 匹配時的操作:接收(記錄) -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配時的操作:拒絕(不記錄) -->
            <onMismatch>DENY</onMismatch>
        </filter>
	</appender>

    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
	    <file>${log.path}/error.log</file>
        <!-- 循環政策:基於時間創建日志文件 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志文件名格式 -->
            <fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
			<!-- 日志最大的歷史 60天 -->
			<maxHistory>60</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 過濾的級別 -->
            <level>ERROR</level>
			<!-- 匹配時的操作:接收(記錄) -->
            <onMatch>ACCEPT</onMatch>
			<!-- 不匹配時的操作:拒絕(不記錄) -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!-- 系統模塊日志級別控制  -->
	<logger name="com.wechat" level="debug" />
	<!-- Spring日志級別控制  -->
	<logger name="org.springframework" level="warn" />

	<!--<root level="info">
		<appender-ref ref="console" />
	</root>-->

    <root level="debug">
        <appender-ref ref="console" />
    </root>
	
	<!--系統操作日志-->
    <root level="info">
        <appender-ref ref="file_info" />
        <appender-ref ref="file_error" />
    </root>
</configuration>

4、驗證
以上代碼編寫完成后,就可以打包到環境上面進行測試驗證:
①:echostr驗證

返回結果:返回 echostr,並顯示已驗證

16:11:46.940 [http-nio-9205-exec-7] INFO  c.q.w.s.c.SystemController - [doGetValid,94] - doGetCallback->echostr: 577115934236344259
16:11:46.969 [http-nio-9205-exec-3] INFO  c.q.w.s.c.SystemController - [doGetValid,94] - doGetCallback->echostr: 5267604771365158379

②:刷新Ticket:獲取Ticket有兩種方式,一是點擊按鈕獲取,二是企業微信每15分鍾會調用回調接口獲取一次

點擊“刷新Ticket” 會彈出如下圖,然后點擊確定

Ticket 有效期為30分鍾;建議把Ticket放到數據庫或者redis中

③:獲取auth_code
安裝第三方應用的時候,會獲取auth_code

④:安裝測試流程


通過企業微信掃碼進行安裝


上面就是驗證通過,及獲取Ticket和auth_code

5、總結
在第三方應用開發中,主要圍繞三種類型的access_token(見企業微信地方應用(二)https://www.cnblogs.com/why0703/p/15983925.html)

provider_access_token:服務商的token
suite_access_token:獲取第三方應用憑證
access_token:授權方(企業)access_token

通過上面的代碼及配置,我們獲取到了suiteTicket和auth_code。
接下來我們要通過這些值獲取到上面token,通過springboot開發實現“企業微信第三方應用(二)api使用測試”


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM