移动开发:SpringBoot整合微信小程序登录

Java
276
0
0
2023-08-25

作者 | 邓维-java

来源 | urlify.cn/aEB3Qj

66套java从入门到精通实战课程分享

微信小程序登录流程

微信小程序登录流程涉及到三个角色:小程序、开发者服务器、微信服务器

三者交互步骤如下:

第一步:小程序通过wx.login()获取code。

第二步:小程序通过wx.request()发送code到开发者服务器。

第三步:开发者服务器接收小程序发送的code,并携带appid、appsecret(这两个需要到微信小程序后台查看)、code发送到微信服务器。

第四步:微信服务器接收开发者服务器发送的appid、appsecret、code进行校验。校验通过后向开发者服务器发送session_key、openid。

第五步:开发者服务器自己生成一个skey(自定义登录状态)与openid、session_key进行关联,并存到数据库中(mysql、redis等)。

第六步:开发者服务器返回生成skey(自定义登录状态)到小程序。

第七步:小程序存储skey(自定义登录状态)到本地。

第八步:小程序通过wx.request()发起业务请求到开发者服务器,同时携带skey(自定义登录状态)。

第九步:开发者服务器接收小程序发送的skey(自定义登录状态),查询skey在数据库中是否有对应的openid、session_key。

第十步:开发者服务器返回业务数据到小程序。

yml:

 <!--hutool具包-->   <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>.4.0</version>
     </dependency>
<!--简化代码的工具包-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
<!-- mybatis-plus-spring-boot-starter--><dependency>    <groupId>com.baomidou</groupId>    <artifactId>mybatis-plus-boot-starter</artifactId>    <version>.3.2</version></dependency> 


wx返回的用户信息:

 /**
 * @Author dw
 * @ClassName WeChatUserInfo
 * @Description 微信用户信息
 * @Date /8/28 14:14
 * @Version .0
 */@Data
public class WeChatUserInfo {
        /**
         * 微信返回的code
         */    private String code;
        /**
         * 非敏感的用户信息
         */    private String rawData;
        /**
         * 签名信息
         */    private String signature;
        /**
         * 加密的数据
         */    private String encrypteData;
        /**
         * 加密密钥
         */    private String iv;

} 


 WeChatUtil工具: 
 /**
 * @Author dw
 * @ClassName WeChatUtil
 * @Description
 * @Date/8/28 10:56
 * @Version.0
 */public class WeChatUtil {

        public static JSONObject getSessionKeyOrOpenId(String code) {
                String requestUrl = "#;;
                HashMap<String, Object> requestUrlParam = new HashMap<>();
                //小程序appId
                requestUrlParam.put("appid", "小程序appId");
                //小程序secret
                requestUrlParam.put("secret", "小程序secret");
                //小程序端返回的code
                requestUrlParam.put("js_code", code);
                //默认参数
                requestUrlParam.put("grant_type", "authorization_code");
                //发送post请求读取调用微信接口获取openid用户唯一标识
                String result = HttpUtil.get(requestUrl, requestUrlParam);
                JSONObject jsonObject = JSONUtil.parseObj(result);
                return jsonObject;
        }

        public static JSONObject getUserInfo(String encryptedData, String sessionKey, String iv) throws BaseDecodingException {
                // 被加密的数据
                byte[] dataByte = Base.decode(encryptedData);
                // 加密秘钥
                byte[] keyByte = Base.decode(sessionKey);
                // 偏移量
                byte[] ivByte = Base.decode(iv);
                try {
                        // 如果密钥不足位,那么就补足. 这个if 中的内容很重要
                        int base = ;
                        if (keyByte.length % base != ) {
                                int groups = keyByte.length / base + (keyByte.length % base !=  ? 1 : 0);
                                byte[] temp = new byte[groups * base];
                                Arrays.fill(temp, (byte) );
                                System.arraycopy(keyByte, , temp, 0, keyByte.length);
                                keyByte = temp;
                        }
                        // 初始化
                        Security.addProvider(new BouncyCastleProvider());
                        Cipher cipher = Cipher.getInstance("AES/CBC/PKCSPadding""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 > ) {
                                String result = new String(resultByte, "UTF-");
                                return JSONUtil.parseObj(result);
                        }
                } catch (Exception e) {
                }
                return null;
        } 


登录controller:

 /**
 * @Author dw
 * @ClassName WeChatUserLoginController
 * @Description
 * @Date/8/28 14:12
 * @Version.0
 */@RestController
public class WeChatUserLoginController {

        @Resource
        private IUserService userService;

        /**
         * 微信用户登录详情
         */    @PostMapping("wx/login")
        public ResultInfo user_login(@RequestBody WeChatUserInfo weChatUserInfo) throws BaseDecodingException {
                //.开发者服务器 登录凭证校验接口 appId + appSecret + 接收小程序发送的code
                JSONObject SessionKeyOpenId = WeChatUtil.getSessionKeyOrOpenId(weChatUserInfo.getCode());
                //.接收微信接口服务 获取返回的参数
                String openid = SessionKeyOpenId.get("openid", String.class);
                String sessionKey = SessionKeyOpenId.get("session_key", String.class);
                // 用户非敏感信息:rawData
                // 签名:signature
                JSONObject rawDataJson = JSONUtil.parseObj(weChatUserInfo.getRawData());
                //.校验签名 小程序发送的签名signature与服务器端生成的签名signature2 = sha1(rawData + sessionKey)
                String signature = DigestUtils.sha1Hex(weChatUserInfo.getRawData() + sessionKey);
                if (!weChatUserInfo.getSignature().equals(signature)) {
                        return ResultInfo.error( "签名校验失败");
                }
                //encrypteData比rowData多了appid和openid
                JSONObject userInfo = WeChatUtil.getUserInfo(weChatUserInfo.getEncrypteData(),
                        sessionKey, weChatUserInfo.getIv());
                //.根据返回的User实体类,判断用户是否是新用户,是的话,将用户信息存到数据库;不是的话,更新最新登录时间
                QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
                userQueryWrapper.lambda().eq(User::getLoginName, openid);
                int userCount = userService.count(userQueryWrapper);
                // uuid生成唯一key,用于维护微信小程序用户与服务端的会话(或者生成Token)
                String skey = UUID.randomUUID().toString();
                if (userCount <= ) {
                        // 用户信息入库
                        String nickName = rawDataJson.get("nickName",String.class);
                        String avatarUrl = rawDataJson.get("avatarUrl",String.class);
                        String gender = rawDataJson.get("gender",String.class);
                        String city = rawDataJson.get("city",String.class);
                        String country = rawDataJson.get("country",String.class);
                        String province = rawDataJson.get("province",String.class);
                       // 新增用户到数据库
                } else {
                        // 已存在,更新用户登录时间

                }
                //. 把新的skey返回给小程序
                return ResultInfo.success();
        }


} 

全局返回结果:

 public class ResultInfo {
        /**
         * 响应代码
         */    private String code;

        /**
         * 响应消息
         */    private String message;

        /**
         * 响应结果
         */    private Object result;

        public ResultInfo() {
        }

        public ResultInfo(BaseErrorInfoInterface errorInfo) {
                this.code = errorInfo.getResultCode();
                this.message = errorInfo.getResultMsg();
        }

        public String getCode() {
                return code;
        }

        public void setCode(String code) {
                this.code = code;
        }

        public String getMessage() {
                return message;
        }

        public void setMessage(String message) {
                this.message = message;
        }

        public Object getResult() {
                return result;
        }

        public void setResult(Object result) {
                this.result = result;
        }

        /**
         * 成功
         *
         * @return
         */    public static ResultInfo success() {
                return success(null);
        }

        /**
         * 成功
         * @param data
         * @return
         */    public static ResultInfo success(Object data) {
                ResultInfo rb = new ResultInfo();
                rb.setCode(CommonEnum.SUCCESS.getResultCode());
                rb.setMessage(CommonEnum.SUCCESS.getResultMsg());
                rb.setResult(data);
                return rb;
        }

        /**
         * 失败
         */    public static ResultInfo error(BaseErrorInfoInterface errorInfo) {
                ResultInfo rb = new ResultInfo();
                rb.setCode(errorInfo.getResultCode());
                rb.setMessage(errorInfo.getResultMsg());
                rb.setResult(null);
                return rb;
        }

        /**
         * 失败
         */    public static ResultInfo error(String code, String message) {
                ResultInfo rb = new ResultInfo();
                rb.setCode(code);
                rb.setMessage(message);
                rb.setResult(null);
                return rb;
        }

        /**
         * 失败
         */    public static ResultInfo error(String message) {
                ResultInfo rb = new ResultInfo();
                rb.setCode("-");
                rb.setMessage(message);
                rb.setResult(null);
                return rb;
        }} 


微信小程序

项目结构:

项目结构

1 初始配置

初始配置

2 me.wxml

 <view class="container">
  <!-- 登录组件  -->  
  <button wx:if="{{!hasUserInfo}}" open-type="getUserInfo" bind:getuserinfo="onGetUserInfo">授权登录</button>
  <!-- 登录后使用open-data -->
  <view class="avatar-container avatar-position">
      <image src="{{userInfo.avatarUrl}}" wx:if="{{hasUserInfo}}" class="avatar" />
      <open-data wx:if="{{hasUserInfo}}" type="userNickName"></open-data>
  </view>
</view> 

3 me.wxss

4 me.json

 {
      
} 

5 me.js

 // pages/me/me.js
Page({

      /**
       * 页面的初始数据
       */  data: {
            hasUserInfofalse,
            userInfonull
      },

      onLoad: function() {
            // 页面加载时使用用户授权逻辑,弹出确认的框
            this.userAuthorized()
      },
      
      userAuthorized() {
            wx.getSetting({
                  success: data => {
                        if (data.authSetting['scope.userInfo']) {
                              wx.getUserInfo({
                                    success: data => {
                                          this.setData({
                                                hasUserInfotrue,
                                                userInfo: data.userInfo
                                          })
                                    }
                              })
                        } else {
                              this.setData({
                                    hasUserInfofalse
                              })
                        }
                  }
            })
      },

      onGetUserInfo(e) {
            const userInfo = e.detail.userInfo
            if (userInfo) {
                  //. 小程序通过wx.login()获取code
                  wx.login({
                        success: function(login_res) {
                              //获取用户信息
                              wx.getUserInfo({
                                    success: function(info_res) {
                                          //. 小程序通过wx.request()发送code到开发者服务器
                                          wx.request({
                                                url'#;,
                                                method: 'POST',
                                                header: {
                                                     'content-type': 'application/json'
                                                },
                                                data: {
                                                      code: login_res.code, //临时登录凭证
                                                      rawData: info_res.rawData, //用户非敏感信息
                                                      signature: info_res.signature, //签名
                                                      encrypteData: info_res.encryptedData, //用户敏感信息
                                                      iv: info_res.iv //解密算法的向量
                                                },
                                                success: function(res) {
                                                      if (res.data.status == ) {
                                                            //.小程序存储skey(自定义登录状态)到本地
                                                            wx.setStorageSync('userInfo', userInfo);
                                                            wx.setStorageSync('skey', res.data.data);
                                                      } else{
                                                            console.log('服务器异常');
                                                      }
                                                },
                                                fail: function(error) {
                                                      //调用服务端登录接口失败
                                                      console.log(error);
                                                }
                                          })
                                    }
                              })
                        }
                  })
                  this.setData({
                        hasUserInfo: true,
                        userInfo: userInfo
                  })
            }
      }

}) 

6 app.json

 设置app.json的pages

{
      "pages":[
        "pages/me/me"
      ],
      "window":{
            "backgroundTextStyle":"light",
            "navigationBarBackgroundColor""#fff",
            "navigationBarTitleText""WeChat",
            "navigationBarTextStyle":"black"
      },
      "debug":true
} 

测试

启动开发者服务器,启动SpringBoot的main方法。

打开微信小程序开发者工具

清空缓存

点击授权登录,并允许。

授权登录

登录成功

登录成功

查看数据库,openid、skey以及用户信息等存入了数据库。

用户信息入库

同时微信小程序将skey等存储到本地,每次发起请求时都可以携带上。

skey存储本地