SoringCloud(四) - 微信獲取用戶資訊

1、項目介紹

2、微信公眾平台 和 微信開放文檔

2.1 微信公眾平台

2.1.1 網址鏈接

//mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index

2.1.2 測試號資訊

2.1.3 微信掃描關注測試公眾號

2.1.4 授權回調頁面域名

2.1.4.1 網頁服務->網頁帳號->修改

2.1.4.2 填寫 授權回調頁面域名

2.1.4.3 內網穿透 NATAPP
2.1.4.3.1 使用教程
NATAPP1分鐘快速新手圖文教程: //natapp.cn/article/natapp_newbie

下載: //natapp.cn/#download

使用本地配置文件config.ini: //natapp.cn/article/config_ini
2.1.4.3.2 authtoken

2.1.4.3.3 授權回調頁面域名

2.2 微信開放文檔

2.2.1 網址鏈接

//developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#2

2.2.2 官方 基本步驟教程

1 第一步:用戶同意授權,獲取code

2 第二步:通過 code 換取網頁授權access_token

3 第三步:刷新access_token(如果需要)

4 第四步:拉取用戶資訊(需 scope 為 snsapi_userinfo)

5 附:檢驗授權憑證(access_token)是否有效

3、http請求工具類 HttpClient4Util

HttpClient4Util 用來發http請求;

//www.cnblogs.com/xiaoqigui/p/16839536.html

4、配置文件 和 配置類

4.1 配置文件

application.yml

#埠
server:
  port: 8096

# 自定義微信授權資訊
wechat:
  auth:
    app-id: wxd4e20add67******   #  appID
    app-secret: a21e97d21d0d6ce408b7a6c******  # appsecret
    code-uri: //open.weixin.qq.com/connect/oauth2/authorize  # 請求微信官方獲取用戶授權code 的請求地址
    redirect-uri: //******.natappfree.cc/wechat/auth/codeBack # 微信官方返回  用戶授權code 的回調地址
    access-token-uri: //api.weixin.qq.com/sns/oauth2/access_token # 根據微信回調的code值,請求微信官方獲取用戶access_token  的請求地址
    user-info-uri: //api.weixin.qq.com/sns/userinfo #根據用戶的 accessToken 和  openId 拉取用戶資訊  的請求地址

4.2 配置類

//自定義微信授權參數資訊配置類
@Data
@Component
@ConfigurationProperties(prefix = "wechat.auth")
public class WeChatAuthConfig {
    /*
        應用id
     */
    private String appId;
    /*
        應用密鑰
     */
    private String appSecret;
    /*
        請求獲取code的地址
     */
    private String codeUri;
    /*
        微信官方回調code的地址
     */
    private String redirectUri;
    /**
     * 微信官方獲取access_token地址
     */
    private String accessTokenUri;
    /*
        微信官方獲取userInfo地址
     */
    private String userInfoUri;

}

5、server 層

5.1 介面

/**
 * Created On : 28/10/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: 微信授權的業務介面
 */
public interface WeChatAuthService {

    /**
     * @author : huayu
     * @date   : 28/10/2022
     * @param  : []
     * @return : java.lang.String
     * @description : 生成請求微信官方獲取用戶授權code的請求地址
     */

    String generateWeChatAuthCodeUrl();

    /**
     * @author : huayu
     * @date   : 28/10/2022
     * @param  : [wechatAuthCode]
     * @return : java.lang.String
     * @description : 根據微信回調的code值,請求微信官方獲取用戶access_token
     */
    String getAccessTokenFromWechatUseCode(String wechatAuthCode);


    /**
     * @author : huayu
     * @date   : 28/10/2022
     * @param  : [accessToken, openId]
     * @return : java.lang.String
     * @description : 根據用戶的 accessToken 和  openId 拉取用戶資訊
     */
    String getUserInfoFromWechatUseAccessToken(String accessToken,String openId);

}

5.2 實現類

/**
 * Created On : 28/10/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: 微信授權的業務介面 實現類
 */
@Service
@Slf4j
public class WeChatAuthServiceImpl implements WeChatAuthService{

    @Autowired
    private WeChatAuthConfig weChatAuthConfig;

    /**
     * @author : huayu
     * @date   : 29/10/2022
     * @param  : []
     * @return : java.lang.String
     * @description : 生成請求微信官方獲取用戶授權code的請求地址
     */
    @Override
    public String generateWeChatAuthCodeUrl() {

        //微信官方引導用戶打開授權頁面,獲取code的完整路徑
        ////open.weixin.qq.com/connect/oauth2/authorize
        // ?appid=APPID
        // &redirect_uri=REDIRECT_URI
        // &response_type=code
        // &scope=SCOPE
        // &state=STATE
        // #wechat_redirect
        //尤其注意:由於授權操作安全等級較高,所以在發起授權請求時,微信會對授權鏈接做正則強匹配校驗,如果鏈接的參數順序不對,授權頁面將無法正常訪問

        //生成請求衛星官方獲取用戶code的完整地址
        StringBuilder weCharAuthCodeUrl = new StringBuilder(weChatAuthConfig.getCodeUri());
        weCharAuthCodeUrl.append("?appid=").append(weChatAuthConfig.getAppId())
                .append("&redirect_uri=").append(weChatAuthConfig.getRedirectUri())
                .append("&response_type=code")
                //&scope=snsapi_userinfo&state=STATE
                .append("&scope=").append("snsapi_userinfo")
                .append("&state=").append("kh96_wechat_auth")
                .append("#wechat_redirect");

        log.info("------ 請求微信官方授權網站地址:{}  ------",weCharAuthCodeUrl.toString());

        //返貨完整的請求地址
        return weCharAuthCodeUrl.toString();

    }

    /**
     * @author : huayu
     * @date   : 29/10/2022
     * @param  : [wechatAuthCode]
     * @return : java.lang.String
     * @description : 根據微信回調的code值,請求微信官方獲取用戶access_token
     */
    @Override
    public String getAccessTokenFromWechatUseCode(String wechatAuthCode) {
        // 尤其注意:由於公眾號的 secret 和獲取到的access_token安全級別都非常高,必須只保存在伺服器,不允許傳給客戶端。
        // 請求方法:獲取 code 後,請求以下鏈接獲取access_token:
        // //api.weixin.qq.com/sns/oauth2/access_token
        // ?appid=APPID
        // &secret=SECRET
        // &code=CODE
        // &grant_type=authorization_code

        // 封裝根據code,請求微信官方獲取access_token的完整地址
        StringBuilder accessTokenUrl = new StringBuilder(weChatAuthConfig.getAccessTokenUri());
        accessTokenUrl.append("?appid=").append(weChatAuthConfig.getAppId())
                .append("&secret=").append(weChatAuthConfig.getAppSecret())
                .append("&code=").append(wechatAuthCode)
                .append("&grant_type=authorization_code");

        log.info("------ 根據code,請求微信官方獲取access_token的完整地址:{} ------", accessTokenUrl.toString());

        // 根據code,請求微信官方獲取access_token,返回結果是同步返回的,不再是非同步回調
        // 請求是伺服器內部發起的,也就是說:在程式中,要根據上面完整的請求地址,主動發送請求到微信官方,介面同步會返回一個json格式的字元串結果,程式內要解析獲取的結果

        // 程式內主動發起http請求,獲取access_token
        return HttpClient4Util.getResponse4GetAsString(accessTokenUrl.toString(), "utf-8");
    }

    /**
     * @author : huayu
     * @date   : 29/10/2022
     * @param  : [accessToken, openId]
     * @return : java.lang.String
     * @description : 根據用戶的 accessToken 和  openId 拉取用戶資訊
     */
    @Override
    public String getUserInfoFromWechatUseAccessToken(String accessToken, String openId) {

        // 如果網頁授權作用域為snsapi_userinfo,則此時開發者可以通過access_token和 openid 拉取用戶資訊了。
        // http:GET(請使用 https 協議):
        // //api.weixin.qq.com/sns/userinfo
        // ?access_token=ACCESS_TOKEN
        // &openid=OPENID
        // &lang=zh_CN

        // 封裝根據accessToken和openId,請求微信官方獲取用戶資訊詳情地址
        StringBuilder userInfoUrl = new StringBuilder(weChatAuthConfig.getUserInfoUri());
        userInfoUrl.append("?access_token=").append(accessToken)
                .append("&openid=").append(openId)
                .append("&lang=zh_CN");
        log.info("------ 根據access_token,請求微信官方獲取userinfo的完整地址:{} ------", userInfoUrl.toString());

        // 程式內主動發起http請求,獲取用戶詳情
        return HttpClient4Util.getResponse4GetAsString(userInfoUrl.toString(), "utf-8");
    }

}

6、controller 層

/**
 * Created On : 28/10/2022.
 * <p>
 * Author : huayu
 * <p>
 * Description: 測試微信授權登錄操作入口
 */
//@SuppressWarnings("all")
@Slf4j
@RestController
@RequestMapping("/wechat/auth")
public class WeChatAuthController {

    @Autowired
    private WeChatAuthService weChatAuthService;

    /**
     * @author : huayu
     * @date   : 28/10/2022
     * @param  : []
     * @return : com.kgc.scd.uitl.RequestResult<java.lang.String>
     * @description :  獲取請求微信官方貨物code的完整地址,用戶訪問該地址,可以進行授權操作(把地址交給前端生成二維碼給用戶掃碼,或者後端生成)
     */
    @GetMapping("/codeUrl")
    public RequestResult<String> codeUrl(){

        //調用業務介面,獲取完整用戶授權訪問的地址
        return ResultBuildUtil.success(weChatAuthService.generateWeChatAuthCodeUrl());

    }

    /**
     * @author : huayu
     * @date   : 28/10/2022
     * @param  : []
     * @return : com.kgc.scd.uitl.RequestResult<java.util.Map<java.lang.String,java.lang.Object>>
     * @description : 接收微信官方一步回調請求,獲取用戶授權的code
     * 流程:用戶先根據上一步返回請求地址,進行授權操作,如果用戶統一授權,微信官方自動根據上一步請求帶過去的回調地址redirectUri,進行結果回調
     */
    @RequestMapping("/codeBack")
    public RequestResult<Map<String, Object>> codeBack(HttpServletRequest request){

        // 用戶同意授權後,如果用戶同意授權,頁面將跳轉至 redirect_uri/?code=CODE&state=STATE。
        // code說明:code作為換取access_token的票據,每次用戶授權帶上的 code 將不一樣,code只能使用一次,5分鐘未被使用自動過期。

        // 從官方回調的請求中,獲取用戶授權後的code參數值
        String wechatAuthCode = request.getParameter("code");

        // 從官方回調的請求中,獲取用戶授權時的自定義參數state
        String wechatAuthState = request.getParameter("state");

        log.info("------ 微信授權後,官方非同步回調結果:code={},state={} ------", wechatAuthCode, wechatAuthState);

        // 定義介面返回集合對象
        Map<String, Object> resultMap = new HashMap<>();

        // 參數非空校驗
        if(StringUtils.isBlank(wechatAuthCode)){
            resultMap.put("msg", "授權code為空!");
            return ResultBuildUtil.fail(resultMap);
        }

        //  調用業務介面,通過 code 換取網頁授權access_token
        String accessTokenJson = weChatAuthService.getAccessTokenFromWechatUseCode(wechatAuthCode);
        log.info("------ 通過 code 換取網頁授權access_token返回結果:{} ------", accessTokenJson);

        // 正確時返回的 JSON 數據包如下:
        // {"access_token":"ACCESS_TOKEN","expires_in":7200,"refresh_token":"REFRESH_TOKEN","openid":"OPENID","scope":"SCOPE"}
        // 錯誤時返回的 JSON 數據包如下:
        // {"errcode":40029,"errmsg":"invalid code"}

        // 解析返回的json數據
        JSONObject accessTokenJsonObj = JSON.parseObject(accessTokenJson);

        // 判斷獲取access_token結果是否正確,如果錯誤,直接結束,如果正確,獲取對應的access_token
        if(StringUtils.isNotBlank(accessTokenJsonObj.getString("errcode"))){
            resultMap.put("wxCode", accessTokenJsonObj.getString("errcode"));
            resultMap.put("wxMsg", accessTokenJsonObj.getString("errmsg"));
            return ResultBuildUtil.fail(resultMap);
        }

        // 拉取用戶資訊(需 scope 為 snsapi_userinfo)
        // 根據上一步返回json,獲取拉取用戶資訊憑證-access_token和用戶唯一標識-openid
        String accessToken = accessTokenJsonObj.getString("access_token");
        String openId = accessTokenJsonObj.getString("openid");

        //  調用業務介面,通過access_token和openId,拉取用戶詳情
        String userInfoJson = weChatAuthService.getUserInfoFromWechatUseAccessToken(accessToken, openId);
        log.info("------ 通過access_token和openId,拉取用戶詳情:{} ------", userInfoJson);

        // 介面返回用戶詳情資訊
        resultMap.put("userInfo", userInfoJson);

        // TODO 獲取成功用戶資訊後,系統要完成靜默註冊-把用戶資訊註冊到系統數據中,存儲用戶的頭像,昵稱,openId資訊,並給系統用戶表增加其它的基本資訊

        //返回用戶詳情
        return ResultBuildUtil.success(resultMap);
    }

}

7、測試

7.1 生成請求微信官方獲取用戶授權code的請求地址

7.2 獲取用戶資訊