微信小程序後端開發流程

微信小程序後端開發流程根據官網總結爲兩個步驟

一、前端調用 wx.login 返回了code,而後調用wx.getUserInfo獲取到用戶的暱稱 頭像 二、服務端根據code去微信獲取openid, 接口地址:developers.weixin.qq.com/miniprogram…html

微信小程序後端接口開發

controller層前端

public class OauthController {

    @Autowired
    private WeChatService weChatService;

    /**
     * 微信受權用js_code換取openId
     * @param code
     * @return
     */
    @GetMapping("/code2Session")
    public BaseResponse code2Session(String code) {
        log.info("code2Session,code={}", code);
        if (StringUtil.isEmpty(code)) {
            return BaseResponse.buildFail("參數異常");
        }
        Code2SessionResponse res = weChatService.code2Session(code);
        log.info("code2Session,res={}", res);
        if (!res.isSuccess()) {
            return BaseResponse.buildFail(res.getErrCode(), res.getErrMsg());
        }
        return BaseResponse.buildSuccess(res);
    }


 /**
     * 解密獲取手機號
     * @param request
     * @param response
     * @param param
     * @return
     */
    public BaseResponse decryptGetPhone(HttpServletRequest request, HttpServletResponse response,
                                    @RequestBody OauthParam param) {
   
            if (!StringUtil.isEmpty(param.getOpenId())) {//微信受權登陸
                String sessionKey = weChatService.getSessionKey(param.getOpenId());
                if (StringUtil.isEmpty(sessionKey)) {
                    return BaseResponse.buildFail("會話不存在");
                }
                Sha1Utils sha = new Sha1Utils();
                // 獲取用戶信息
                log.debug("微信登錄 sessionKey = {}", sessionKey);
                String userInfoStr = sha.decryptWXAppletInfo(sessionKey, param.getEncryptedData(), param.getIv());
                if (StringUtil.isEmpty(userInfoStr)) {
                    return BaseResponse.buildFail("沒法獲取用戶信息");
                }
                JSONObject json = JSONObject.parseObject(userInfoStr);
                //綁定微信的手機號
                String tel = json.getString("purePhoneNumber");
                Assert.isTrue(!StringUtils.isEmpty(tel), "沒法獲取用戶手機號");
                BaseResponse baseResponse=new BaseResponse();
                baseResponse.setResultInfo(tel);
                baseResponse.setState(0);
                return baseResponse;
            }

    }
}
複製代碼

接口

public interface WeChatService {


    /**
     * 用code換取openid
     *
     * @param code
     * @return
     */
    Code2SessionResponse code2Session(String code);


    /**
     * 獲取憑證
     *
     * @return
     */
    String getAccessToken();


    /**
     * 獲取憑證
     *
     * @param isForce
     * @return
     */
    String getAccessToken(boolean isForce);


    String getSessionKey(String openId);

}
複製代碼

實現類

public class WeChatServiceImpl implements WeChatService {

    //獲取配置文件數據
    @Value("${wechat.miniprogram.id}")
    private String appId;

    @Value("${wechat.miniprogram.secret}")
    private String appSecret;

    @Reference
    private SysUserService sysUserService;


    @Override
    public Code2SessionResponse code2Session(String code) {
        String rawResponse = HttpClientUtil
                .get(String.format(WechatConstant.URL_CODE2SESSION, appId, appSecret, code));
        log.info("rawResponse====={}", rawResponse);
        Code2SessionResponse response = JSON.parseObject(rawResponse, Code2SessionResponse.class);
        if (response.isSuccess()) {
            cacheSessionKey(response);
        }
        return response;
    }

    private void cacheSessionKey(Code2SessionResponse response) {
        RedisCache redisCache = RedisCache.getInstance();
        String key = RedisCacheKeys.getWxSessionKeyKey(response.getOpenId());
        redisCache.setCache(key, 2147483647, response.getSessionKey());
    }

    @Override
    public String getAccessToken() {
        return getAccessToken(false);
    }

    @Override
    public String getAccessToken(boolean isForce) {
        RedisCache redisCache = RedisCache.getInstance();
        String accessToken = null;
        if (!isForce) {
            accessToken = redisCache.getCache(RedisCacheKeys.getWxAccessTokenKey(appId));
        }
        if (StringUtil.isNotEmpty(accessToken)) {
            return accessToken;
        }
        String rawResponse = HttpClientUtil
                .get(String.format(WechatConstant.URL_GET_ACCESS_TOKEN, appId, appSecret));
        AccessTokenResponse response = JSON.parseObject(rawResponse, AccessTokenResponse.class);
        log.info("getAccessToken:response={}", response);
        if (response.isSuccess()) {
            redisCache.setCache(RedisCacheKeys.getWxAccessTokenKey(appId), 7000, response.getAcessToken());
            return response.getAcessToken();
        }
        return null;
    }


    @Override
    public String getSessionKey(String openId) {
        RedisCache redisCache = RedisCache.getInstance();
        String key = RedisCacheKeys.getWxSessionKeyKey(openId);
        String sessionKey = redisCache.getCache(key);
        return sessionKey;
    }
}
複製代碼

用到的解密工具類

public class Sha1Utils {
    public static String decryptWXAppletInfo(String sessionKey, String encryptedData, String iv) {
        String result = null;
        try {
            byte[] encrypData = Base64.decodeBase64(encryptedData);
            byte[] ivData = Base64.decodeBase64(iv);
            byte[] sessionKeyB = Base64.decodeBase64(sessionKey);

            AlgorithmParameterSpec ivSpec = new IvParameterSpec(ivData);
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKeySpec keySpec = new SecretKeySpec(sessionKeyB, "AES");
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
            byte[] doFinal = cipher.doFinal(encrypData);
            result = new String(doFinal);
            return result;
        } catch (Exception e) {
            //e.printStackTrace();
            log.error("decryptWXAppletInfo error",e);
        }
        return null;
    }

}
複製代碼

網絡請求工具類

public class HttpClientUtil {

    // utf-8字符編碼
    public static final String                        CHARSET_UTF_8          = "utf-8";

    // HTTP內容類型。
    public static final String                        CONTENT_TYPE_TEXT_HTML = "text/xml";

    // HTTP內容類型。至關於form表單的形式,提交數據
    public static final String                        CONTENT_TYPE_FORM_URL  = "application/x-www-form-urlencoded";

    // HTTP內容類型。至關於form表單的形式,提交數據
    public static final String                        CONTENT_TYPE_JSON_URL  = "application/json;charset=utf-8";

    // 鏈接管理器
    private static PoolingHttpClientConnectionManager pool;

    // 請求配置
    private static volatile RequestConfig requestConfig;

    private static CloseableHttpClient getNewHttpClient() {

        CloseableHttpClient httpClient = HttpClients.custom()
            // 設置鏈接池管理
            .setConnectionManager(pool)
            // 設置請求配置
            .setDefaultRequestConfig(getRequestConfig())
            // 設置重試次數
            .setRetryHandler(new DefaultHttpRequestRetryHandler(0, false)).build();

        return httpClient;
    }

    /**
     * 發送 post請求
     *
     * @param httpUrl
     *            地址
     */
    public static String post(String httpUrl) {
        // 建立httpPost
        HttpPost httpPost = new HttpPost(httpUrl);
        return request(httpPost);
    }

    public static byte[] postRaw(String httpUrl) {
        // 建立httpPost
        HttpPost httpPost = new HttpPost(httpUrl);
        return requestRaw(httpPost);
    }

    /**
     * 發送 get請求
     *
     * @param httpUrl
     */
    public static String get(String httpUrl) {
        // 建立get請求
        HttpGet httpGet = new HttpGet(httpUrl);
        return request(httpGet);
    }

    /**
     * 發送 post請求(帶文件)
     *
     * @param httpUrl
     *            地址
     * @param maps
     *            參數
     * @param fileLists
     *            附件
     */
    public static String post(String httpUrl, Map<String, String> maps, List<File> fileLists,
                              String fileName) {
        HttpPost httpPost = new HttpPost(httpUrl);// 建立httpPost
        MultipartEntityBuilder meBuilder = MultipartEntityBuilder.create();
        if (maps != null) {
            for (String key : maps.keySet()) {
                meBuilder.addPart(key, new StringBody(maps.get(key), ContentType.TEXT_PLAIN));
            }
        }
        if (fileLists != null) {
            for (File file : fileLists) {
                FileBody fileBody = new FileBody(file);
                meBuilder.addPart(fileName, fileBody);
            }
        }
        HttpEntity reqEntity = meBuilder.build();
        httpPost.setEntity(reqEntity);
        return request(httpPost);
    }

    public static String post(String httpUrl, Map<String, String> maps, List<File> fileLists) {
        return post(httpUrl, maps, fileLists, "file");
    }

    public static String post(String httpUrl, List<File> fileLists) {
        return post(httpUrl, Collections.emptyMap(), fileLists, "file");
    }

    /**
     * 發送 post請求
     *
     * @param httpUrl
     *            地址
     * @param params
     *            參數(格式:key1=value1&key2=value2)
     *
     */
    public static String post(String httpUrl, String params) {
        HttpPost httpPost = new HttpPost(httpUrl);// 建立httpPost
        try {
            // 設置參數
            if (params != null && params.trim().length() > 0) {
                StringEntity stringEntity = new StringEntity(params, "UTF-8");
                stringEntity.setContentType(CONTENT_TYPE_FORM_URL);
                httpPost.setEntity(stringEntity);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return request(httpPost);
    }

    /**
     * 發送 post請求
     *
     * @param maps
     *            參數
     */
    public static String post(String httpUrl, Map<String, String> maps) {
        String param = convertStringParamter(maps);
        return post(httpUrl, param);
    }



    /**
     * 發送 post請求 發送json數據
     *
     * @param httpUrl
     *            地址
     * @param content
     *
     *
     */
    public static String post(String httpUrl, String content, String contentType) {
        //        HttpPost httpPost = new HttpPost(httpUrl);// 建立httpPost
        //        try {
        //            // 設置參數
        //            if (StringUtils.isNotEmpty(content)) {
        //                StringEntity stringEntity = new StringEntity(content, "UTF-8");
        //                stringEntity.setContentType(contentType);
        //                httpPost.setEntity(stringEntity);
        //            }
        //        } catch (Exception e) {
        //            e.printStackTrace();
        //        }
        //        return request(httpPost);
        return new String(postRaw(httpUrl, content, contentType), StandardCharsets.UTF_8);
    }

    public static byte[] postRaw(String httpUrl, String content, String contentType) {
        HttpPost httpPost = new HttpPost(httpUrl);// 建立httpPost
        try {
            // 設置參數
            if (StringUtils.isNotEmpty(content)) {
                StringEntity stringEntity = new StringEntity(content, "UTF-8");
                stringEntity.setContentType(contentType);
                httpPost.setEntity(stringEntity);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return requestRaw(httpPost);
    }

    /**
     * 發送 post請求 發送json數據
     *
     * @param httpUrl
     *            地址
     * @param paramsJson
     *            參數(格式 json)
     *
     */
    public static String postJson(String httpUrl, String paramsJson) {
        return post(httpUrl, paramsJson, CONTENT_TYPE_JSON_URL);
    }

    public static byte[] postJsonRaw(String httpUrl, String paramsJson) {
        return postRaw(httpUrl, paramsJson, CONTENT_TYPE_JSON_URL);
    }

    /**
     * 發送 post請求 發送xml數據
     *
     * @param url   地址
     * @param paramsXml  參數(格式 Xml)
     *
     */
    public static String postXml(String url, String paramsXml) {
        return post(url, paramsXml, CONTENT_TYPE_TEXT_HTML);
    }

    /**
     * 將map集合的鍵值對轉化成:key1=value1&key2=value2 的形式
     *
     * @param parameterMap
     *            須要轉化的鍵值對集合
     * @return 字符串
     */
    public static String convertStringParamter(Map parameterMap) {
        StringBuilder parameterBuffer = new StringBuilder();
        if (parameterMap != null) {
            Iterator iterator = parameterMap.keySet().iterator();
            String key = null;
            String value = null;
            while (iterator.hasNext()) {
                key = (String) iterator.next();
                if (parameterMap.get(key) != null) {
                    value = (String) parameterMap.get(key);
                } else {
                    value = "";
                }
                parameterBuffer.append(key).append("=").append(value);
                if (iterator.hasNext()) {
                    parameterBuffer.append("&");
                }
            }
        }
        return parameterBuffer.toString();
    }

    /**
     * 發送請求
     *
     * @param request
     * @return
     */
    public static byte[] requestRaw(HttpRequestBase request) {

        CloseableHttpClient httpClient;
        CloseableHttpResponse response = null;
        // 響應內容
        //        String responseContent = null;
        byte[] rawResponse = null;
        try {
            // 建立默認的httpClient實例.
            httpClient = getNewHttpClient();
            // 配置請求信息
            request.setConfig(requestConfig);
            // 執行請求
            response = httpClient.execute(request);
            // 獲得響應實例
            HttpEntity entity = response.getEntity();

            // 能夠得到響應頭
            // Header[] headers = response.getHeaders(HttpHeaders.CONTENT_TYPE);
            // for (Header header : headers) {
            // System.out.println(header.getName());
            // }

            // 獲得響應類型
            // System.out.println(ContentType.getOrDefault(response.getEntity()).getMimeType());

            // 判斷響應狀態
            if (response.getStatusLine().getStatusCode() >= 300) {
                throw new Exception("HTTP Request is not success, Response code is "
                                    + response.getStatusLine().getStatusCode());
            }

            if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {
                rawResponse = EntityUtils.toByteArray(entity);
                //                responseContent = EntityUtils.toString(entity, CHARSET_UTF_8);
                EntityUtils.consume(entity);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                // 釋放資源
                if (response != null) {
                    response.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return rawResponse;
    }

    private static String request(HttpRequestBase req) {
        return new String(requestRaw(req), StandardCharsets.UTF_8);
    }

    private static RequestConfig getRequestConfig() {

        if (requestConfig == null) {
            synchronized (HttpClientUtil.class) {
                if (requestConfig == null) {
                    try {
                        //System.out.println("初始化HttpClientTest~~~開始");
                        SSLContextBuilder builder = new SSLContextBuilder();
                        builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
                        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                            builder.build());
                        // 配置同時支持 HTTP 和 HTPPS
                        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder
                            .<ConnectionSocketFactory> create()
                            .register("http", PlainConnectionSocketFactory.getSocketFactory())
                            .register("https", sslsf).build();
                        // 初始化鏈接管理器
                        pool = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
                        // 將最大鏈接數增長到200,實際項目最好從配置文件中讀取這個值
                        pool.setMaxTotal(200);
                        // 設置最大路由
                        pool.setDefaultMaxPerRoute(2);
                        // 根據默認超時限制初始化requestConfig
                        int socketTimeout = 10000;
                        int connectTimeout = 10000;
                        int connectionRequestTimeout = 10000;
                        requestConfig = RequestConfig.custom()
                            .setConnectionRequestTimeout(connectionRequestTimeout)
                            .setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout)
                            .build();

                    } catch (NoSuchAlgorithmException e) {
                        e.printStackTrace();
                    } catch (KeyStoreException e) {
                        e.printStackTrace();
                    } catch (KeyManagementException e) {
                        e.printStackTrace();
                    }

                    // 設置請求超時時間
                    requestConfig = RequestConfig.custom().setSocketTimeout(50000)
                        .setConnectTimeout(50000).setConnectionRequestTimeout(50000).build();
                }
            }
        }
        return requestConfig;
    }
}
複製代碼

常量

public interface WechatConstant {
    Integer OK_STATUS            = 0;
    String  URL_CODE2SESSION     = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code";


    String  URL_GET_ACCESS_TOKEN     = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";


    String URL_GET_IMAGE = "http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s";
    
    
    /**
     * 給公衆號發送信息。參考https://mp.weixin.qq.com/advanced/tmplmsg?action=faq&token=708366329&lang=zh_CN
     */
    String  URL_SEND_TO_CHANNEL  = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s";
    String  URL_SEND_MESSAGE     = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=%s";
    
    /**
     * 發送模板消息。參考https://developers.weixin.qq.com/miniprogram/dev/api-backend/sendMiniTemplateMessage.html
     */
    String URL_SEND_TEMPLATE_MESSAGE = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=%s";

    String  URL_QR_CODE_UNLIMTED = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s";
    
    String  URL_QR_CODE = "https://api.weixin.qq.com/wxa/getwxacode?access_token=%s";

    /**
     * 獲取標籤下粉絲列表
     */
    String URL_ALL_FANS_OPENID = "https://api.weixin.qq.com/cgi-bin/user/tag/get?access_token=%s";
    /**
     * 獲取公衆號已建立的標籤
     */
    String URL_ALL_TAGS = "https://api.weixin.qq.com/cgi-bin/tags/get?access_token=%s";

}
複製代碼

使用到的實體類

public class Code2SessionResponse implements Serializable {
    public static Integer RESPONSE_OK = 0;

    @JSONField(name = "openid")
    private String       openId;
    @JSONField(name = "session_key")
    private String       sessionKey;
    @JSONField(name = "unionid")
    private String       unionId;
    @JSONField(name = "errcode")
    private Integer      errCode;
    @JSONField(name = "errmsg")
    private String      errMsg;



    public boolean isSuccess() {
        return this.errCode == null || RESPONSE_OK.equals(this.errCode);
    }
}
複製代碼

總結:微信小程序的後端開發主要就是對用戶進行受權 , 一、前端調用 wx.login 返回了code,而後調用wx.getUserInfo獲取到用戶的暱稱 頭像 2.首先經過微信受權用js_code換取openId,來獲取openId,前端傳微信的參數 code字段 3.而後解密獲取手機號 前端須要傳openId encryptedData iv 等字段來獲取用戶的的受權手機號redis

這些信息都獲取後 接着就是調用後端的登錄接口,登錄接口若是隻有受權登陸就是咱們將接口參數爲下圖最後三個字段爲前端必填字段 json

圖片.png

主要步驟是根據前端的openId獲取sessionKey 而後根據sessionKey 和其餘參數進行解密獲取用戶手機號

經過解密獲取受權登陸的手機號,而後根據本身的業務邏輯處理便可,這樣咱們就能夠根據受權的手機號進行受權登陸 小程序

圖片.png
相關文章
相關標籤/搜索