1.首先对小程序的登录流程简单讲解
小程序用户分类:
- 游客:只要进入小程序就默认为游客身份,就算是游客也需要获取用户唯一标识 OpenID ,存入用户表
- 学生:学生信息由学校导入,学生进入后可以通过学号、手机号、姓名和身份证后6位进行绑定
- 导师:导师信息由学校导入,导师进入后可以通过手机号、工号、姓名进行绑定
登录流程:
《微信开放文档》:
简述一下步骤:
- 用户第一次进入小程序,前端调用 wx.login() 获取临时登录凭证code ,将code传给后端
- 后端通过code请求微信接口服务获取用户的OpenID,用OpenID来创建用户存入用户表
- 再通过用户编号、用户类型(会员/管理员)、客户端编号来创建Token令牌并返回给前端
- Token令牌有过期时间,在有效期内用户可以直接进入
- 当过了有效期后,就会再次通过code得到OpenID(同一个微信用户OpenID是不变的),当用户表中有OpenID时,不用再次创建,直接返回该用户,创建Token令牌返回前端
2.登录流程代码
1.weixinLogin
通过code获取openid
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AppAuthWxLoginReqVO {
@Schema(description = "登录 code,小程序通过 wx.login 方法获得", required = true, example = "word")
@NotEmpty(message = "登录 code 不能为空")
private String code;
}
@Override
public AppAuthLoginRespVO weixinLogin(AppAuthWxLoginReqVO reqVO) {
String openid;
try{
WxMaJscode2SessionResult sessionInfo = wxMaService.getUserService().getSessionInfo(reqVO.getCode());
openid = sessionInfo.getOpenid();
// 获得获得注册用户
MemberUserDO user = userService.createUserIfAbsent(openid, getClientIP());
Assert.notNull(user, "获取用户失败,结果为空");
return createTokenAfterLoginSuccess(user, user.getOpenid(), LoginLogTypeEnum.LOGIN_SOCIAL);
}catch (Exception exception){
throw exception(AUTH_WEIXIN_CODE_ERROR);
}
}
2.createUserIfAbsent
通过openid创建用户(如果不存在的话)
@Override
public MemberUserDO createUserIfAbsent(String openid, String registerIp) {
// 用户已经存在
MemberUserDO user = memberUserMapper.selectByOpenid(openid);
if (user != null) {
return user;
}
// 用户不存在,则进行创建
return this.createUser(openid, registerIp);
}
3.createTokenAfterLoginSuccess
通过用户编号、用户类型(会员/管理员)、客户端编号来创建Token令牌
private AppAuthLoginRespVO createTokenAfterLoginSuccess(MemberUserDO user, String openid, LoginLogTypeEnum logType) {
// 插入登陆日志
createLoginLog(user.getId(), openid, logType, LoginResultEnum.SUCCESS);
// 创建 Token 令牌
OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.createAccessToken(new OAuth2AccessTokenCreateReqDTO()
.setUserId(user.getId())
.setUserType(getUserType().getValue())
.setClientId(OAuth2ClientConstants.CLIENT_ID_DEFAULT));
// 构建返回结果
System.out.println(accessTokenRespDTO);
return AuthConvert.INSTANCE.convert(accessTokenRespDTO);
}
@Data
public class OAuth2AccessTokenCreateReqDTO implements Serializable {
/**
* 用户编号
*/
@NotNull(message = "用户编号不能为空")
private Long userId;
/**
* 用户类型
*/
@NotNull(message = "用户类型不能为空")
@InEnum(value = UserTypeEnum.class, message = "用户类型必须是 {value}")
private Integer userType;
/**
* 客户端编号
*/
@NotNull(message = "客户端编号不能为空")
private String clientId;
/**
* 授权范围
*/
private List<String> scopes;
}
4.Token令牌
@Data
@Accessors(chain = true)
public class OAuth2AccessTokenRespDTO implements Serializable {
/**
* 访问令牌
*/
private String accessToken;
/**
* 刷新令牌
*/
private String refreshToken;
/**
* 用户编号
*/
private Long userId;
/**
* 用户类型
*/
private Integer userType;
/**
* 过期时间
*/
private LocalDateTime expiresTime;
}
3.学生导师身份的绑定(以学生为例)
绑定的流程:
通过前端传入的学号、姓名、手机号以及身份证后6位在学生表中查询(在studentService中),如果匹配不到,则返回学生不存在;
如果存在,则根据前端传入的用户编号,完善用户信息(将游客身份改为学生,添加手机号)(在userService中)
AppUserBindVO
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AppUserBindVO {
@Schema(description = "编号", required = true, example = "1024")
private Long userId;
@Schema(description = "学生学号/导师工号", required = true, example = "202052116534")
private String number;
@Schema(description = "姓名", required = true, example = "芋艿")
private String name;
@Schema(description = "身份证后6位", required = true, example = "051711")
@Length(min = 6, max = 6, message = "身份证后6位")
private String idLastSix;
@Schema(description = "用户手机号", required = true, example = "15601691300")
@Length(min = 8, max = 11, message = "手机号码长度为 8-11 位")
private String mobile;
@Schema(description = "角色", required = true, example = "1")
private Integer role;
}
AppUserController层接口
@PreAuthenticated是Spring Security框架提供的一个注解,它可以应用于Spring MVC控制器的方法上,用于指定在方法执行之前要进行的身份验证操作。
在用户未登录的情况下进行api测试时,需要将它注释。
@PostMapping("/bind")
@Operation(summary = "用户信息绑定")
// @PreAuthenticated
public CommonResult<Boolean> bind(@RequestBody AppUserBindVO appUserBindVO) {
if(appUserBindVO.getRole() == 1){
if(studentService.bindStudent(appUserBindVO)){
userService.bind(appUserBindVO);
}else throw exception(STUDENT_NOT_EXISTS);
}
if(appUserBindVO.getRole() == 2){
if(mentorService.bindMentor(appUserBindVO)){
userService.bind(appUserBindVO);
}else throw exception(MENTOR_NOT_EXISTS);
}
return success(true);
}
StudentMapper
来判断该信息是否存在学生表中
注意Mybatis Plus中 likeLeft 的用法
可以参考《MyBatisPlus中的likeLeft和likeRight》
@Mapper
public interface StudentMapper extends BaseMapperX<StudentDO> {
default boolean bindStudent(AppUserBindVO appUserBindVO){
String idLastSix = appUserBindVO.getIdLastSix(); // 获取 appUserBindVO 对象的后六位字符串
return exists(new LambdaQueryWrapperX<StudentDO>()
.eqIfPresent(StudentDO::getStudentNumber,appUserBindVO.getNumber())
.eqIfPresent(StudentDO::getName,appUserBindVO.getName())
.eqIfPresent(StudentDO::getMobile,appUserBindVO.getMobile())
.likeLeft(StudentDO::getIdCard, idLastSix)
);
}
}
MemberUserServiceImpl
@Override
public void bind(AppUserBindVO appUserBindVO) {
LambdaUpdateWrapper<MemberUserDO> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(MemberUserDO::getId,appUserBindVO.getUserId())
.set(MemberUserDO::getMobile,appUserBindVO.getMobile())
.set(MemberUserDO::getRole,appUserBindVO.getRole());
memberUserMapper.update(null,updateWrapper);
}