微信小程序后端(java)开发流程的详细步骤


Posted in Javascript onNovember 13, 2019

微信小程序后端开发流程根据官网总结为两个步骤

1、前端调用 wx.login 返回了code,然后调用wx.getUserInfo获取到用户的昵称 头像

 2、服务端根据code去微信获取openid, 接口地址: https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html%EF%BC%9B%E5%90%8C%E6%97%B6%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%94%A8%E6%88%B7%E6%98%B5%E7%A7%B0%E5%A4%B4%E5%83%8F%E7%AD%89%E8%B5%84%E6%96%99

微信小程序后端接口开发

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

总结:微信小程序的后端开发主要就是对用户进行授权 , 1、前端调用 wx.login 返回了code,然后调用wx.getUserInfo获取到用户的昵称 头像 2.首先通过微信授权用js_code换取openId,来获取openId,前端传微信的参数 code字段 3.然后解密获取手机号 前端需要传openId encryptedData iv 等字段来获取用户的的授权手机号

这些信息都获取后 接着就是调用后端的登陆接口,登陆接口如果只有授权登录就是我们将接口参数为下图最后三个字段为前端必填字段

微信小程序后端(java)开发流程的详细步骤 

主要步骤是根据前端的openId获取sessionKey 然后根据sessionKey 和其他参数进行解密获取用户手机号

通过解密获取授权登录的手机号,然后根据自己的业务逻辑处理即可,这样我们就可以根据授权的手机号进行授权登录

微信小程序后端(java)开发流程的详细步骤

Javascript 相关文章推荐
json简单介绍
Jun 10 Javascript
基于Jquery的开发个代阴影的对话框效果代码
Jul 28 Javascript
Jquery加载时从后台读取数据绑定到dropdownList实例
Jun 09 Javascript
用Jquery实现滚动新闻
Feb 12 Javascript
node.js 使用ejs模板引擎时后缀换成.html
Apr 22 Javascript
基于JavaScript实现div层跟随滚动条滑动
Jan 12 Javascript
JSON简介以及用法汇总
Feb 21 Javascript
dropload.js插件下拉刷新和上拉加载使用详解
Oct 20 Javascript
详解Require.js与Sea.js的区别
Aug 05 Javascript
react用Redux中央仓库实现一个todolist
Sep 29 Javascript
JavaScript的console命令使用实例
Dec 03 Javascript
解决ant-design-vue中menu菜单无法默认展开的问题
Oct 31 Javascript
jQuery实现滑动星星评分效果(每日分享)
Nov 13 #jQuery
vue+导航锚点联动-滚动监听和点击平滑滚动跳转实例
Nov 13 #Javascript
jquery获取input输入框中的值
Nov 13 #jQuery
Vue 实现CLI 3.0 + momentjs + lodash打包时优化
Nov 13 #Javascript
在vue中使用防抖和节流,防止重复点击或重复上拉加载实例
Nov 13 #Javascript
在vue-cli中引入lodash.js并使用详解
Nov 13 #Javascript
vue 解除鼠标的监听事件的方法
Nov 13 #Javascript
You might like
PHP生成带有雪花背景的验证码
2008/09/28 PHP
有关PHP性能优化的介绍
2013/06/20 PHP
启用Csrf后POST数据时出现的400错误
2015/07/05 PHP
PHP读取大文件的几种方法介绍
2016/10/27 PHP
DOM 中的事件处理介绍
2012/01/18 Javascript
JavaScript高级程序设计 阅读笔记(二十) js错误处理
2012/08/14 Javascript
javascript避免数字计算精度误差的方法详解
2014/03/05 Javascript
jQuery对val和atrr(&quot;value&quot;)赋值的区别介绍
2014/09/26 Javascript
Javascript学习指南
2014/12/01 Javascript
node.js中的http.response.writeHead方法使用说明
2014/12/14 Javascript
如何使用jquery修改css中带有!important的样式属性
2016/04/28 Javascript
分享bootstrap学习笔记心得(组件及其属性)
2017/01/11 Javascript
vue使用原生js实现滚动页面跟踪导航高亮的示例代码
2018/10/25 Javascript
vue选项卡切换登录方式小案例
2019/09/27 Javascript
微信小程序使用 vant Dialog组件的正确方式
2020/02/21 Javascript
JS Web Flex弹性盒子模型代码实例
2020/03/10 Javascript
VsCode里的Vue模板的实现
2020/08/12 Javascript
Django实战之用户认证(初始配置)
2018/07/16 Python
如何安装多版本python python2和python3共存以及pip共存
2018/09/18 Python
Python实现PyPDF2处理PDF文件的方法示例
2019/09/25 Python
Python如何实现大型数组运算(使用NumPy)
2020/07/24 Python
Python如何批量生成和调用变量
2020/11/21 Python
css3中transition属性详解
2014/09/02 HTML / CSS
详解CSS3选择器:nth-child和:nth-of-type之间的差异
2017/09/18 HTML / CSS
全球知名旅游社区巴西站点:TripAdvisor巴西
2016/07/21 全球购物
俄罗斯购买内衣网站:Trusiki
2020/08/22 全球购物
应届行政管理专业个人自我评价
2013/12/28 职场文书
医学生职业规划范文
2014/01/05 职场文书
物业招聘计划书
2014/01/10 职场文书
《一本男孩子必读的书》教学反思
2014/02/19 职场文书
大学优秀班主任事迹材料
2014/05/02 职场文书
涉密人员保密承诺书
2014/05/28 职场文书
党员个人总结范文
2015/02/14 职场文书
班主任工作经验交流会总结
2015/11/02 职场文书
大学毕业生自我鉴定范文
2019/06/21 职场文书
Ruby序列化和持久化存储 Marshal和Pstore介绍
2022/04/18 Ruby