一、 概念
code
code是用户登录凭证
,个人理解为用户的授权码
(需要用户本人授权给小程序,小程序才有权力获取到你这个用户的数据),code需要由小程序向微信服务器获取。
注意: 每个code只能使用一次,且有效期为5分钟。因此,在使用code进行登录时,需要及时将其转换成用户的openid和session_key等信息,以免出现code过期的情况
openid
openid是用来唯一标识用户的一个字符串。在微信小程序中,每个用户的openid都是唯一的。通过openid,小程序可以获取用户的基本信息,如头像、昵称等。
大概就是,不同的用户,在不同的小程序上会有不同的openid,后台可以通过openid+session_key来对使用本小程序的用户进行标识(某个openid就是某个用户)
注意: 同一个用户在不同的小程序中拥有不同的openid。因此,在开发小程序时,不能使用openid来进行用户的唯一性判断。
二、 流程
流程图
步骤分析:
- 小程序端,调用wx.login()获取
code
授权码。调用wx.request()发送请求并携带code,请求开发者服务器(自己编写的后端服务)。 - 开发者服务端,通过
HttpClient
向微信接口服务发送请求
,并携带appId
+appsecret
+code
三个参数。 - 开发者服务端,接收微信接口服务返回的数据,
session_key
+opendId
等。opendId是微信用户的唯一标识。 - 开发者服务端,自定义登录态,生成令牌(token)和openid等数据返回给小程序端,方便后绪请求身份校验。
- 小程序端,收到自定义登录态(token),存储storage。
- 小程序端,后绪通过wx.request()发起业务请求时,携带token。
- 开发者服务端,收到请求后,通过携带的token,解析当前登录用户的id。
- 开发者服务端,身份校验通过后,继续相关的业务逻辑处理,最终返回业务数据。
三、 服务端代码
配置文件
yml
项目:wechat:appid: ${sky.wechat.appid}secret: ${sky.wechat.secret}
#**配置为微信用户生成jwt令牌时使用的配置项:**user-secret-key: itheimauser-ttl: 7200000user-token-name: authentication
DTO传code
@Data
public class UserLoginDTO implements Serializable {private String code;}
VO类
返回用户的id , openid,token给前端;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserLoginVO implements Serializable {private Long id;private String openid;private String token;}
逻辑梳理
controller层
- 接收参数,获取
code
,校验数据; - 给用户生成jwt令牌;
service层
- 通过
code
,向微信服务器发送请求获取openid
; - 判断获取不获取得到(openid == null)
2.1 为空 抛出业务异常(code
可能是错误的); - 判断是否为新用户(openid是否在数据库中已经存在)
不存在 : 自动注册
存在: 直接返回;
Controller
public class UserController {@Autowiredprivate UserService userService;@Autowiredprivate 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);}}
ServiceImpl
public class UserServiceImpl implements UserService {@Autowiredprivate WeChatProperties weChatProperties;@Autowiredprivate 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){//调用微信接口服务,获得当前微信用户的openidMap<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;}}
mapper 和 service接口就省略啦
mapper的insert记得键入主键的返回useGeneratedKeys=“true”
接着就是设置用户的 jwt令牌拦截器
@Component
@Slf4j
public class JwtTokenUserInterceptor implements HandlerInterceptor {@Autowiredprivate 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;}}
WebMvcConfiguration配置拦截器;
protected void addInterceptors(InterceptorRegistry registry) {log.info("开始注册自定义拦截器...");registry.addInterceptor(jwtTokenUserInterceptor).addPathPatterns("/user/**").excludePathPatterns("/user/user/login").excludePathPatterns("/user/shop/status");}
参考链接:https://juejin.cn/post/7212074532340908091