尚融宝20-实现用户注册和用户认证
目录
一、需求
二、前端整合发送验证码
三、实现用户注册
1、创建VO对象
2、定义常量
3、引入MD5工具类
4、Controller
5、Service
6、前端整合
四、实现用户登录
1、后端整合JWT
2、前端整合
五、校验用户登录
1、后端
2、前端
一、需求
二、前端整合发送验证码
点击获取验证码后先对手机号进行验证是否为空,其次禁用发送验证码按钮防止重复提交,然后显示倒计时,调用后端的阿里云微服务发送验证码,下面这篇文章提到如何整合阿里云短信微服务整合阿里云短信服务https://blog.csdn.net/m0_62946761/article/details/129625885?spm=1001.2014.3001.5501
methods: {//发短信 send() {if (!this.userInfo.mobile) {this.$message.error('请输入手机号')return}//防止重复提交if (this.sending) returnthis.sending = true//倒计时this.timeDown()//远程调用发送短信的接口this.$axios.$get('/api/sms/send/' + this.userInfo.mobile).then((response) => {this.$message.success(response.message)})},//倒计时timeDown() {console.log('进入倒计时')this.leftSecond = this.second//创建定时器const timmer = setInterval(() => {//计数器减一this.leftSecond--if (this.leftSecond <= 0) {//停止定时器clearInterval(timmer)//还原计数器this.leftSecond = this.second//还原按钮状态this.sending = false}}, 1000)},},
三、实现用户注册
1、创建VO对象
service-core中创建vo(value object),根据表单创建出来的对象,这里其实可以用复用原本的userInfo对象,它已经包含了这些属性
@Data
@ApiModel(description="注册对象")
public class RegisterVO {@ApiModelProperty(value = "用户类型")private Integer userType;@ApiModelProperty(value = "手机号")private String mobile;@ApiModelProperty(value = "验证码")private String code;@ApiModelProperty(value = "密码")private String password;
}
2、定义常量
UserInfo中添加常量
public static final Integer STATUS_NORMAL = 1;
public static final Integer STATUS_LOCKED = 0;
3、引入MD5工具类
guigu-common中util包,引入工具类:
MD5.java:MD5加密,对用户密码加密后再存入数据库
4、Controller
先对手机,密码,验证码进行校验判断,最后再调用service的注册方法
@Api(tags = "会员接口")
@RestController
@RequestMapping("/api/core/userInfo")
@Slf4j
@CrossOrigin
public class UserInfoController {@Resourceprivate UserInfoService userInfoService;@Resourceprivate RedisTemplate redisTemplate;@ApiOperation("会员注册")@PostMapping("/register")public R register(@RequestBody RegisterVO registerVO){String mobile = registerVO.getMobile();String password = registerVO.getPassword();String code = registerVO.getCode();//MOBILE_NULL_ERROR(-202, "手机号不能为空"),Assert.notEmpty(mobile, ResponseEnum.MOBILE_NULL_ERROR);//MOBILE_ERROR(-203, "手机号不正确"),Assert.isTrue(RegexValidateUtils.checkCellphone(mobile), ResponseEnum.MOBILE_ERROR);//PASSWORD_NULL_ERROR(-204, "密码不能为空"),Assert.notEmpty(password, ResponseEnum.PASSWORD_NULL_ERROR);//CODE_NULL_ERROR(-205, "验证码不能为空"),Assert.notEmpty(code, ResponseEnum.CODE_NULL_ERROR);//校验验证码String codeGen = (String)redisTemplate.opsForValue().get("srb:sms:code:" + mobile);//CODE_ERROR(-206, "验证码不正确"),Assert.equals(code, codeGen, ResponseEnum.CODE_ERROR);//注册userInfoService.register(registerVO);return R.ok().message("注册成功");}
}
5、Service
接口:UserInfoService
public interface UserInfoService extends IService<UserInfo> {void register(RegisterVO registerVO);
}
实现:UserInfoServiceImpl
先判断用户表是否已经注册过该用户,如果没有就插入用户信息表和用户账户表,新建账户0块钱,这里涉及两张表的操作,在类上加上事务回滚
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {@Resourceprivate UserAccountMapper userAccountMapper;@Transactional(rollbackFor = {Exception.class})@Overridepublic void register(RegisterVO registerVO) {//判断用户是否被注册QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();queryWrapper.eq("mobile", registerVO.getMobile());Integer count = baseMapper.selectCount(queryWrapper);//MOBILE_EXIST_ERROR(-207, "手机号已被注册"),Assert.isTrue(count == 0, ResponseEnum.MOBILE_EXIST_ERROR);//插入用户基本信息UserInfo userInfo = new UserInfo();userInfo.setUserType(registerVO.getUserType());userInfo.setNickName(registerVO.getMobile());userInfo.setName(registerVO.getMobile());userInfo.setMobile(registerVO.getMobile());userInfo.setPassword(MD5.encrypt(registerVO.getPassword()));userInfo.setStatus(UserInfo.STATUS_NORMAL); //正常//设置一张静态资源服务器上的头像图片userInfo.setHeadImg("https://srb-file.oss-cn-beijing.aliyuncs.com/avatar/07.jpg");baseMapper.insert(userInfo);//创建会员账户UserAccount userAccount = new UserAccount();userAccount.setUserId(userInfo.getId());userAccountMapper.insert(userAccount);}
}
6、前端整合
pages/register.vue
//注册
register() {this.$axios.$post('/api/core/userInfo/register', this.userInfo).then((response) => {this.step = 2})
},
四、实现用户登录
1、后端整合JWT
这里使用JWT令牌来校验用户的登录,下面这篇文章讲解了JWT令牌尚融宝18-JWT令牌和测试_zoeil的博客-CSDN博客https://blog.csdn.net/m0_62946761/article/details/129962876?spm=1001.2014.3001.5502&ydreferer=aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzYyOTQ2NzYxP3R5cGU9YmxvZw%3D%3D
导入依赖
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId>
</dependency>
JWT工具
service-base中添加util包
添加JwtUtils类,包括生成token,校验token等主要功能
public class JwtUtils {private static long tokenExpiration = 24*60*60*1000;private static String tokenSignKey = "A1t2g3uigu123456";private static Key getKeyInstance(){SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;byte[] bytes = DatatypeConverter.parseBase64Binary(tokenSignKey);return new SecretKeySpec(bytes,signatureAlgorithm.getJcaName());}public static String createToken(Long userId, String userName) {String token = Jwts.builder().setSubject("SRB-USER").setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)).claim("userId", userId).claim("userName", userName).signWith(SignatureAlgorithm.HS512, getKeyInstance()).compressWith(CompressionCodecs.GZIP).compact();return token;}/* 判断token是否有效* @param token* @return*/public static boolean checkToken(String token) {if(StringUtils.isEmpty(token)) {return false;}try {Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(token);return true;} catch (Exception e) {return false;}}public static Long getUserId(String token) {Claims claims = getClaims(token);Integer userId = (Integer)claims.get("userId");return userId.longValue();}public static String getUserName(String token) {Claims claims = getClaims(token);return (String)claims.get("userName");}public static void removeToken(String token) {//jwttoken无需删除,客户端扔掉即可。}/* 校验token并返回Claims* @param token* @return*/private static Claims getClaims(String token) {if(StringUtils.isEmpty(token)) {// LOGIN_AUTH_ERROR(-211, "未登录"),throw new BusinessException(ResponseEnum.LOGIN_AUTH_ERROR);}try {Jws<Claims> claimsJws = Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(token);Claims claims = claimsJws.getBody();return claims;} catch (Exception e) {throw new BusinessException(ResponseEnum.LOGIN_AUTH_ERROR);}}
}
创建VO对象
service-core中创建登录对象
@Data
@ApiModel(description="登录对象")
public class LoginVO {@ApiModelProperty(value = "用户类型")private Integer userType;@ApiModelProperty(value = "手机号")private String mobile;@ApiModelProperty(value = "密码")private String password;
}
用户信息对象
@Data
@ApiModel(description="用户信息对象")
public class UserInfoVO {@ApiModelProperty(value = "用户姓名")private String name;@ApiModelProperty(value = "用户昵称")private String nickName;@ApiModelProperty(value = "头像")private String headImg;@ApiModelProperty(value = "手机号")private String mobile;@ApiModelProperty(value = "1:出借人 2:借款人")private Integer userType;@ApiModelProperty(value = "JWT访问令牌")private String token;
}
UserInfoController,返回给前端用户信息对象供展示用
@ApiOperation("会员登录")
@PostMapping("/login")
public R login(@RequestBody LoginVO loginVO, HttpServletRequest request) {String mobile = loginVO.getMobile();String password = loginVO.getPassword();Assert.notEmpty(mobile, ResponseEnum.MOBILE_NULL_ERROR);Assert.notEmpty(password, ResponseEnum.PASSWORD_NULL_ERROR);String ip = request.getRemoteAddr();UserInfoVO userInfoVO = userInfoService.login(loginVO, ip);return R.ok().data("userInfo", userInfoVO);
}
接口:UserInfoService
UserInfoVO login(LoginVO loginVO, String ip);
实现:UserInfoServiceImpl
@Resource
private UserLoginRecordMapper userLoginRecordMapper;@Transactional( rollbackFor = {Exception.class})
@Override
public UserInfoVO login(LoginVO loginVO, String ip) {String mobile = loginVO.getMobile();String password = loginVO.getPassword();Integer userType = loginVO.getUserType();//获取会员QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();queryWrapper.eq("mobile", mobile);queryWrapper.eq("user_type", userType);UserInfo userInfo = baseMapper.selectOne(queryWrapper);//用户不存在//LOGIN_MOBILE_ERROR(-208, "用户不存在"),Assert.notNull(userInfo, ResponseEnum.LOGIN_MOBILE_ERROR);//校验密码//LOGIN_PASSWORD_ERROR(-209, "密码不正确"),Assert.equals(MD5.encrypt(password), userInfo.getPassword(), ResponseEnum.LOGIN_PASSWORD_ERROR);//用户是否被禁用//LOGIN_DISABLED_ERROR(-210, "用户已被禁用"),Assert.equals(userInfo.getStatus(), UserInfo.STATUS_NORMAL, ResponseEnum.LOGIN_LOKED_ERROR);//记录登录日志UserLoginRecord userLoginRecord = new UserLoginRecord();userLoginRecord.setUserId(userInfo.getId());userLoginRecord.setIp(ip);userLoginRecordMapper.insert(userLoginRecord);//生成tokenString token = JwtUtils.createToken(userInfo.getId(), userInfo.getName());UserInfoVO userInfoVO = new UserInfoVO();userInfoVO.setToken(token);userInfoVO.setName(userInfo.getName());userInfoVO.setNickName(userInfo.getNickName());userInfoVO.setHeadImg(userInfo.getHeadImg());userInfoVO.setMobile(userInfo.getMobile());userInfoVO.setUserType(userType);return userInfoVO;
}
2、前端整合
登录脚本pages/login.vue,按下登录按钮后请求后台获取用户信息对象,跳转到主页
methods: {//登录login() {this.$axios.$post('/api/core/userInfo/login', this.userInfo).then((response) => {// 把用户信息存在cookie中cookie.set('userInfo', response.data.userInfo)window.location.href = '/user'})},
},
登陆后,免费注册这里需要换成用户的电话以证明登录
页面头信息
components/AppHeader.vue
脚本
<script>
import cookie from 'js-cookie'export default {data() {return {userInfo: null,}},mounted() {this.showInfo()},methods: {//显示用户信息 showInfo() {// debuggerlet userInfo = cookie.get('userInfo')if (!userInfo) {console.log('cookie不存在')this.userInfo = nullreturn} userInfo = JSON.parse(userInfo) this.userInfo = userInfo},//退出 logout() {cookie.set('userInfo', '')//跳转页面window.location.href = '/login'},},
}
</script>
五、校验用户登录
1、后端
显示用户信息时需要先进行token的校验,service-core 中 UserInfoController添加令牌校验接口
@ApiOperation("校验令牌")
@GetMapping("/checkToken")
public R checkToken(HttpServletRequest request) {String token = request.getHeader("token");boolean result = JwtUtils.checkToken(token);if(result){return R.ok();}else{//LOGIN_AUTH_ERROR(-211, "未登录"),return R.setResult(ResponseEnum.LOGIN_AUTH_ERROR);}
}
这里我们原本测试使用的是基本的swagger2无法在请求头中添加token字段,可以使用postman,也可以使用民间加强版的swagger2,只需要以下几个步骤:
step1:service-base导入以下依赖
<dependency><groupId>com.github.xiaoymin</groupId><artifactId>swagger-bootstrap-ui</artifactId><version>1.9.2</version>
</dependency>
step2:访问
http://localhost:8110/doc.html
step3:添加全局参数
2、前端
对于原本的显示用户信息的函数showInfo我们需要优化一下,在拿到cookie中的用户信息后,先请求后端服务器校验token,成功才显示信息
showInfo() {// debuggerlet userInfo = cookie.get('userInfo')if (!userInfo) {console.log('cookie不存在')this.userInfo = nullreturn}userInfo = JSON.parse(userInfo)//先在服务器端校验tokenthis.$axios({url: '/api/core/userInfo/checkToken',method: 'get',headers: {//如果token校验成功,再展示user信息token: userInfo.token,},}).then((response) => {console.log('校验成功')this.userInfo = userInfo})
},
可能你会有疑问为什么上面拿到响应数据进入then就代表着成功,因为axios响应拦截器中已经帮我们处理了未登录的情况
$axios.onResponse((response) => {console.log('Reciving resposne', response)if (response.data.code === 0) {return response // 后端对于未登录的情况返回的状态码为-211} else if (response.data.code === -211) {console.log('token校验失败')cookie.set('userInfo', '')//debugger//跳转到登录页面window.location.href = '/login'} else {Message({message: response.data.message,type: 'error',duration: 5 * 1000,})return Promise.reject(response)}})