微信小程序后端(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 相关文章推荐
Javascript 面向对象之重载
May 04 Javascript
javascript 运算数的求值顺序
Aug 23 Javascript
jquery禁止回车触发表单提交
Dec 12 Javascript
jQuery前端分页示例分享
Feb 10 Javascript
jquery实现右键菜单插件
Mar 29 Javascript
JS的框架Polymer中的dom-if和is属性使用说明
Jul 29 Javascript
jQuery模仿单选按钮选中效果
Jun 24 Javascript
基于jquery实现的鼠标悬停提示案例
Dec 11 Javascript
vue单页应用加百度统计代码(亲测有效)
Jan 31 Javascript
vue-devtools的安装步骤
Apr 23 Javascript
vue实现微信二次分享以及自定义分享的示例
Mar 20 Javascript
微信小程序实现菜单左右联动
May 19 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连接sql server 2005环境配置及问题解决
2014/08/08 PHP
php缩放gif和png图透明背景变成黑色的解决方法
2014/10/14 PHP
php生成网页桌面快捷方式
2017/05/05 PHP
javascript之锁定表格栏位
2007/06/29 Javascript
javascript StringBuilder类实现
2008/12/22 Javascript
一个简单的js动画效果代码
2010/07/20 Javascript
jQuery动态添加 input type=file的实现代码
2012/06/14 Javascript
JQuery页面地址处理插件jqURL详解
2015/05/03 Javascript
javascript删除元素节点removeChild()用法实例
2015/05/26 Javascript
jQuery检测某个元素是否存在代码分享
2015/07/09 Javascript
JavaScript实现横向滑出的多级菜单效果
2015/10/09 Javascript
jQuery实现图片轮播效果代码
2016/09/27 Javascript
自动适应iframe右边的高度
2016/12/22 Javascript
js图片加载效果实例代码(延迟加载+瀑布流加载)
2017/05/12 Javascript
JS实现自动轮播图效果(自适应屏幕宽度+手机触屏滑动)
2017/06/19 Javascript
关于laydate.js加载laydate.css路径错误问题解决
2017/12/27 Javascript
echarts实现地图定时切换散点与多图表级联联动详解
2018/08/07 Javascript
vue 公共列表选择组件,引用Vant-UI的样式方式
2020/11/02 Javascript
js+html+css实现手动轮播和自动轮播
2020/12/30 Javascript
python xlsxwriter创建excel图表的方法
2018/06/11 Python
Python+OpenCV图片局部区域像素值处理详解
2019/01/23 Python
对pyqt5之menu和action的使用详解
2019/06/20 Python
详解background属性的8个属性值(面试题)
2020/11/02 HTML / CSS
CSS3中的弹性布局em运用入门详解 1em等于多少像素
2021/02/08 HTML / CSS
迪卡侬英国官网:Decathlon英国
2017/04/08 全球购物
建筑装饰学院室内设计专业个人自我评价
2013/12/07 职场文书
会展策划与管理专业大学生职业生涯规划
2014/02/07 职场文书
2016元旦晚会主持词开场白和结束语
2015/12/04 职场文书
2016年社区“我们的节日·中秋节”活动总结
2016/04/05 职场文书
jquery插件实现搜索历史
2021/04/24 jQuery
使用golang编写一个并发工作队列
2021/05/08 Golang
日本动漫十大公认神作:第五现已全网禁播,《死亡笔记》在榜
2022/03/18 日漫
人物搭配车车超萌联名预备中 【咒术迴战】 ⨯ 【天竺鼠车车】 展开合作
2022/04/11 日漫
Android Studio实现简易进制转换计算器
2022/05/20 Java/Android
SQL Server携程核心系统无感迁移到MySQL实战
2022/06/01 SQL Server
使用CSS自定义属性实现骨架屏效果
2022/06/21 HTML / CSS