• 微信小程序登录后端


    一、 概念

    code

    code是用户登录凭证,个人理解为用户的授权码(需要用户本人授权给小程序,小程序才有权力获取到你这个用户的数据),code需要由小程序向微信服务器获取。

    注意: 每个code只能使用一次,且有效期为5分钟。因此,在使用code进行登录时,需要及时将其转换成用户的openid和session_key等信息,以免出现code过期的情况

    openid

    openid是用来唯一标识用户的一个字符串。在微信小程序中,每个用户的openid都是唯一的。通过openid,小程序可以获取用户的基本信息,如头像、昵称等。
    大概就是,不同的用户,在不同的小程序上会有不同的openid,后台可以通过openid+session_key来对使用本小程序的用户进行标识(某个openid就是某个用户)

    注意: 同一个用户在不同的小程序中拥有不同的openid。因此,在开发小程序时,不能使用openid来进行用户的唯一性判断。

    二、 流程

    流程图

    在这里插入图片描述
    步骤分析:

    1. 小程序端,调用wx.login()获取code授权码。调用wx.request()发送请求并携带code,请求开发者服务器(自己编写的后端服务)。
    2. 开发者服务端,通过HttpClient向微信接口服务发送请求,并携带appId+appsecret+code三个参数。
    3. 开发者服务端,接收微信接口服务返回的数据,session_key+opendId等。opendId是微信用户的唯一标识
    4. 开发者服务端,自定义登录态,生成令牌(token)和openid等数据返回给小程序端,方便后绪请求身份校验
    5. 小程序端,收到自定义登录态(token),存储storage。
    6. 小程序端,后绪通过wx.request()发起业务请求时,携带token。
    7. 开发者服务端,收到请求后,通过携带的token,解析当前登录用户的id。
    8. 开发者服务端,身份校验通过后,继续相关的业务逻辑处理,最终返回业务数据。

    三、 服务端代码

    配置文件

    yml

    项目:
    	wechat:
    	    appid: ${sky.wechat.appid}
    	    secret: ${sky.wechat.secret}
    #**配置为微信用户生成jwt令牌时使用的配置项:**
     	user-secret-key: itheima
        user-ttl: 7200000
        user-token-name: authentication
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    DTO传code

    @Data
    public class UserLoginDTO implements Serializable {
    
        private String code;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    VO类

    返回用户的id , openid,token给前端;

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class UserLoginVO implements Serializable {
    
        private Long id;
        private String openid;
        private String token;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    逻辑梳理

    controller层

    1. 接收参数,获取code,校验数据;
    2. 给用户生成jwt令牌;

    service层

    1. 通过code,向微信服务器发送请求获取openid
    2. 判断获取不获取得到(openid == null)
      2.1 为空 抛出业务异常(code可能是错误的);
    3. 判断是否为新用户(openid是否在数据库中已经存在)
      不存在 : 自动注册
      存在: 直接返回;

    Controller

    public class UserController {
        @Autowired
        private UserService userService;
        @Autowired
        private JwtProperties jwtProperties;
    
        @PostMapping("/login")
        @ApiOperation("微信登录")
        public Result<UserLoginVO> login(@PathVariable UserLoginDTO userLoginDTO){
            log.info("微信用户登录:{}",userLoginDTO.getCode());
            User user = userService.wxLogin(userLoginDTO);
    
            //登录成功后 为微信用户生成jwt令牌
            Map<String, Object> claims = new HashMap<>();
            claims.put(JwtClaimsConstant.USER_ID, user.getId());
            String token = JwtUtil.createJWT(
                    jwtProperties.getUserSecretKey(),
                    jwtProperties.getUserTtl(),
                    claims);
    
            UserLoginVO userLoginVO = UserLoginVO.builder()
                    .id(user.getId())
                    .openid(user.getOpenid())
                    .token(token)
                    .build();
            return Result.success(userLoginVO);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    ServiceImpl

    public class UserServiceImpl implements UserService {
        @Autowired
        private WeChatProperties weChatProperties;
        @Autowired
        private UserMapper userMapper;
    
        //微信服务接口地址
        public static final String WX_LOGIN = "https://api.weixin.qq.com/sns/jscode2session";
    
        /**
         * 微信登录
         * @param userLoginDTO
         * @return
         */
        public User wxLogin(UserLoginDTO userLoginDTO) {
    
                String openid = getOpenid(userLoginDTO.getCode());
    
                //1、 判断openid是否为空,如果为空表示登录失败,抛出业务异常
                if (openid == null) {
                    throw new LoginFailedException(MessageConstant.LOGIN_FAILED);
                }
    
                //2、 判断当前用户是否为新用户
                User user = userMapper.getByOpenid(openid);
    
                //3、 如果是新用户,自动完成注册
                if (user == null) {
                    user = User.builder()
                            .openid(openid)
                            .createTime(LocalDateTime.now())
                            .build();
                    userMapper.insert(user);
                }
    
                //4、返回这个用户对象
                return user;
            }
    
        /**
             * 调用微信接口服务,获取微信用户的openid
             * @param code
             * @return
             */
            private String getOpenid (String code){
                //调用微信接口服务,获得当前微信用户的openid
                Map<String, String> map = new HashMap<>();
                map.put("appid", weChatProperties.getAppid());
                map.put("secret", weChatProperties.getSecret());
                map.put("js_code", code);
                map.put("grant_type", "authorization_code");
                String json = HttpClientUtil.doGet(WX_LOGIN, map);
    
                JSONObject jsonObject = JSON.parseObject(json);
                String openid = jsonObject.getString("openid");
                return openid;
            }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    mapper 和 service接口就省略啦
    mapper的insert记得键入主键的返回useGeneratedKeys=“true”

    接着就是设置用户的 jwt令牌拦截器

    @Component
    @Slf4j
    public class JwtTokenUserInterceptor implements HandlerInterceptor {
    
        @Autowired
        private JwtProperties jwtProperties;
    
        /**
         * 校验jwt
         *
         * @param request
         * @param response
         * @param handler   目标方法
         * @return true ,代表放行, false,表示禁止放行
         * @throws Exception
         */
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            //判断当前拦截到的是Controller的方法还是其他资源
            if (!(handler instanceof HandlerMethod)) {
                //当前拦截到的不是动态方法,直接放行
                return true;
            }
    
            //1、从请求头中获取令牌   因为token存放在请求头中,
            String token = request.getHeader(jwtProperties.getUserTokenName());
    
            //2、校验令牌
            try {
                log.info("jwt校验:{}", token);
                Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);
                Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
                log.info("当前用户id:", userId);
                BaseContext.setCurrentId(userId); //通过LocalThread,把id存到该线程的存储空间中;
                //3、通过,放行
                return true;
            } catch (Exception ex) {
                //4、不通过,响应401状态码
                response.setStatus(401);
                return false;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    WebMvcConfiguration配置拦截器;

     protected void addInterceptors(InterceptorRegistry registry) {
            log.info("开始注册自定义拦截器...");
            registry.addInterceptor(jwtTokenUserInterceptor)
                    .addPathPatterns("/user/**")
                    .excludePathPatterns("/user/user/login")
                    .excludePathPatterns("/user/shop/status");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    参考链接:https://juejin.cn/post/7212074532340908091

  • 相关阅读:
    Android:DMB-T/H,开发记录
    左值引用与右值引用疑点总结
    MySQL主从同步
    数据分析 -- numpy
    SQL不同类型分组排序
    多御安全浏览器chromium95内核更新:上网速度更快
    29.Nacos的简介与安装(springcloud)
    Web大学生网页作业成品——游戏主题HTM5网页设计作业成品 (HTML+CSS王者荣耀8页)
    springboot整合redis
    .netcore6.0自己配置swagger
  • 原文地址:https://blog.csdn.net/m0_60708917/article/details/134272141