笔者最近刚开始玩微信小程序,对其中的一些原理还不是很清楚,欢迎各位批评指正。

    在网络中传输明文会存在一定的风险,因此想要与后台数据库结合,笔者认为不能直接调用微信认证的接口,并且将用户信息传递到后台,而应该先调用微信的方法获取加密的encrypedData、iv、code等,在后台将用户数据进行解析,并存入数据库。

    总体的思路时,当启动小程序的时候,从微信服务器请求到加密数据以及code等,并携带这些数据向服务器发请求(是自己的服务器,不是微信服务器),服务器先根据code获取用户的openid(一个唯一标识,如果不清楚可以参考笔者的另外一篇文章: java实现微信登录)从缓存中查询是否有此用户对应的信息(openid),如果有则直接从缓存中将数据返回,如果没有则调用微信授权的接口,获取用户信息存入数据库和缓存,并将用户信息返回给小程序。

该项目后台为springboot+mybatisplus,下面是核心代码:

一、小程序启动的js

//app.js
App({
  onLaunch: function () {
    // 展示本地存储能力
    var logs = wx.getStorageSync('logs') || []
    logs.unshift(Date.now())
    wx.setStorageSync('logs', logs)
  },

  denglu: function () {
    var that = this;
    // 登录
    wx.login({
      success: res => {
        // 发送 res.code 到后台换取 openId, sessionKey, unionId
        wx.getUserInfo({
          success: result => {
            // 获取用户信息
            wx.request({
              url: '/user/start',
              method: 'GET',
              data: {
                code: res.code,
                encryptedData: result.encryptedData,
                iv: result.iv
              },
              success: function (e) {
                //console.log(e.data.data);
                that.globalData.userInfo = e.data.data;
                if (this.userInfoReadyCallback) {
                  this.userInfoReadyCallback(e.data.data);                
                  }
              }
            })
          }
        })
      }
    })
  },

  globalData: {
    userInfo: null
  }
})

二、Controller层

@GetMapping("/start")
    public BabyResult startApp(@RequestParam("encryptedData") String encrypted_data,
                               @RequestParam("iv") String iv,
                               @RequestParam("code") String code) {

        try {
            BabyResult result =  userService.startApp(encrypted_data, iv, code);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            return BabyResult.build(500, "启动错误");
        }

    }

三、Service层

@Override
    public BabyResult startApp(String encrypted_data, String iv, String code) {

        try {
            //获取session_key
            String accessJson = this.getSessionKey(code);
            if ("".equals(accessJson)) {
                return BabyResult.build(401, "获取session_key失败");
            }
            JsonObject accessJsonObject = new JsonParser().parse(accessJson).getAsJsonObject();
            String openid = accessJsonObject.get("openid").getAsString();

            if(!userMap.containsKey(openid)) {
                /**
                 * 第一次授权
                 */
                //解析用户微信信息
                String session_key = accessJsonObject.get("session_key").getAsString();;
                String userWxInfoJson = WxDataUtil.DecryptDataGetUserInfoJson(encrypted_data, session_key, iv);
                if (null == userWxInfoJson || "".equals(userWxInfoJson)) {
                    logger.debug("获取用户微信信息失败");
                    return BabyResult.build(401, "获取用户微信信息失败");
                }
                //保存用户信息
                User user = this.saveUserInfo(userWxInfoJson, openid);
                //将用户信息放入缓存
                userMap.put(openid, user);
                //将用户信息返回给客户端
                return BabyResult.ok(user);
            } else {
                /**
                 * 开启小程序,之前已经授权
                 */
                User user = (User) userMap.get(openid);
                return BabyResult.ok(user);
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.debug("拉取登录信息失败");
            return BabyResult.build(500, "拉取登录信息失败");
        }

    }

四、解密微信数据

public static String DecryptDataGetUserInfoJson(String encryptedData,String sessionkey,String iv){
        // 被加密的数据
        byte[] dataByte = Base64.decode(encryptedData);
        // 加密秘钥
        byte[] keyByte = Base64.decode(sessionkey);
        // 偏移量
        byte[] ivByte = Base64.decode(iv);
        try {
            // 如果密钥不足16位,那么就补足.  这个if 中的内容很重要
            int base = 16;
            if (keyByte.length % base != 0) {
                int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
                byte[] temp = new byte[groups * base];
                Arrays.fill(temp, (byte) 0);
                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
                keyByte = temp;
            }
            // 初始化
            Security.addProvider(new BouncyCastleProvider());
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding","BC");
            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
            parameters.init(new IvParameterSpec(ivByte));
            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
            byte[] resultByte = cipher.doFinal(dataByte);
            if (null != resultByte && resultByte.length > 0) {
                String result = new String(resultByte, "UTF-8");
                return result;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } 
        return null;
    }

五、总结

    微信程序现在比较火,但是微信开发工具还是有很多的bug。而且大部分都是用php做后台服务,笔者也用过,确实php开发比较简单,因为小程序毕竟不会像传统应用那样具有特别复杂的逻辑;笔者主要是做java开发,因此用java写了一套服务。其实,在这个webservice的时代,大家都遵循Restful风格的API,即使不同的编程语言,也都可以相互调用。