Процесс внутренней разработки апплета WeChat

задняя часть

Согласно официальному сайту, внутренний процесс разработки апплета WeChat состоит из двух этапов.

1. Внешний интерфейс вызывает wx.login для возврата кода, а затем вызывает wx.getUserInfo для получения никнейма и аватара пользователя. 2. Сервер обращается к WeChat для получения openid по коду, а адрес интерфейса такой:Developers.WeChat.QQ.com/mini программа…

Разработка внутреннего интерфейса апплета WeChat

слой контроллера

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);
    }
}

Резюме: внутренняя разработка мини-программ WeChat в основном предназначена для авторизации пользователей. 1. Внешний интерфейс вызывает wx.login для возврата кода, а затем вызывает wx.getUserInfo для получения никнейма и аватара пользователя. 2. Сначала замените js_code на openId через авторизацию WeChat, чтобы получить openId, и передайте поле кода параметра WeChat во внешний интерфейс. 3. Затем расшифруйте, чтобы получить номер мобильного телефона.Внешнему интерфейсу необходимо передать такие поля, как openId зашифрованные данные iv, чтобы получить авторизованный номер мобильного телефона пользователя.

После того, как вся эта информация получена, следующим шагом будет вызов интерфейса входа в систему. обязательные поля.

图片.png

Основной шаг — получить sessionKey по openId внешнего интерфейса, а затем расшифровать его по sessionKey и другим параметрам, чтобы получить номер мобильного телефона пользователя.

Получите номер мобильного телефона, авторизованный для входа в систему, путем расшифровки, а затем обработайте его в соответствии с нашей собственной бизнес-логикой, чтобы мы могли выполнить авторизованный вход в соответствии с авторизованным номером мобильного телефона.

图片.png