开发流程
该业务基于rouyi生成好了mapper和service的代码,现在需要在controller层写接口
实际操作流程:
看接口文档一>controller里定义函数一>看给出的工具类一>补全controller里的函数一>运行测试
接口文档
在登录模块有登录和登出方法
登录分出登录成功、账号密码错误、密码多次错误三种情况
登出点了一定登出成功
controller里定义
在注解框架里加上login和logout两个函数
@RestController
@RequestMapping(value = "/api")
@Api(tags = {"登录模块"})
public class LoginController {
@Autowired
private CardUserService userService;
@Autowired
private RedisUtil redisUtil;
@PostMapping("/login")
@ApiOperation(value = "登录")
@ApiImplicitParams({
@ApiImplicitParam(name="account",value = "用户名",required = true),
@ApiImplicitParam(name="password",value = "密码",required = true)
})
public ApiResult login(HttpServletRequest request, @RequestParam String account,@RequestParam String password) {
//TODO
return null;
}
@GetMapping("/logout")
@ApiOperation(value = "退出")
public ApiResult logout(HttpServletRequest request) {
//TODO
return null;
}
}
看给出的工具类
找该模块附近的common和util包
补全controller里的函数
login部分
首先拿到用户(基于rouyi使用的mybatis-plus)
QueryWrapper<CardUser> wrapper = new QueryWrapper<>();
wrapper.eq("uname",account).eq("passwd",PasswordUtil.encodePassword(password));
List<CardUser> users = userService.list(wrapper);
-
创建一个 QueryWrapper 对象 wrapper,用于构建查询条件。QueryWrapper<CardUser> wrapper = new QueryWrapper<>();
-
使用 wrapper 对象的 eq 方法添加查询条件,这里表示查询条件为用户名("uname" 字段)等于 account 变量的值,并且密码("passwd" 字段)等于经过加密后的 password 变量的值。wrapper.eq("uname",account).eq("passwd",PasswordUtil.encodePassword(password));
-
通过 UserService 的 list 方法传入包含查询条件的 wrapper 对象,执行数据库查询操作,返回符合条件的 CardUser 对象列表 users。这里为什么要用wrapper获取满足条件的用户列表,再取第一个呢?可以直接CardUser user = userService.getOne(wrapper);吗?这是一种保护措施。万一数据有多条不会出错。getOne的话就报错了。用getOne然后捕获异常也可以的。List<CardUser> users = userService.list(wrapper);
然后处理登录成功的情况
if (users != null && users.size() > 0) {
CardUser user = users.get(0);
user.setIdcard(null);
user.setPasswd(null);
HttpSession session = request.getSession();
session.setAttribute("user", user);
return new ApiResult(1, "登录成功", user);
}
if (users != null && users.size() > 0)
-
users != null: 这一部分是为了确保 users 不为 null,即确保查询返回的用户列表对象不为空。如果 users 为 null,那么调用 users.size() 方法将会导致空指针异常。因此,这一部分判断是为了避免空指针异常。
-
users.size() > 0: 这一部分判断是为了确保 users 列表中至少包含一个用户。即使 users 不为 null,但如果其中没有任何用户,也不应该继续处理和返回登录成功的信息,因为没有符合条件的用户。
-
从查询到的用户列表中获取第一个用户对象,并赋值给 user 变量。CardUser user = users.get(0);
-
将用户对象的身份证号字段设置为 null,出于安全考虑避免将密码信息传递到前端。user.setIdcard(null);
-
将用户对象的密码字段设置为 null,同样可能是出于安全考虑。user.setPasswd(null);
-
通过 request 对象获取 HttpSession 对象,用于存储用户的会话信息。HttpSession session = request.getSession();
-
将处理后的用户对象存储到 HttpSession 中,以键值对 "user"-"user" 的形式保存。session.setAttribute("user", user);
-
最后返回一个 ApiResult 对象,其中包含了状态码、消息和用户对象,用于向前端传递登录成功的信息和用户数据。return new ApiResult(1, "登录成功", user);
接下来处理登录失败的情况
对于密码错误5次的处理
Integer errortimes = (Integer) redisUtil.get(RedisKeys.USERLOGINTIMES + account);
if (errortimes != null && errortimes >= 5) {
return new ApiResult(0, "密码错误5次,请5分钟后再登录", null);
}
从 Redis 中获取用户登录错误次数 errortimes,然后判断如果 errortimes 不为 null 而且大于等于 5,则返回一个包含错误提示信息的 ApiResult 对象,告知用户密码错误达到5次,需要等待5分钟后再尝试登录。
redisUtil.get(RedisKeys.USERLOGINTIMES + account) 表示根据用户账号构建的键名从 Redis 中获取该用户的登录错误次数信息。如果 Redis 中存储了该信息,则返回相应的错误次数;如果不存在或者 Redis 出现问题,则可能返回 null。。
- RedisKeys.USERLOGINTIMES: 这是一个 Redis Key 的前缀,用于标识存储用户登录错误次数的键名。
- account: 这是用户的账号信息,用于构建特定用户的登录错误次数的键名。
然后在判断流程增加密码错误的处理
else {
redisUtil.incr(RedisKeys.USERLOGINTIMES + account, 1);
redisUtil.expire(RedisKeys.USERLOGINTIMES + account, 60 * 5);
return new ApiResult(0, "账户名或密码错误", null);
}
如果用户登录失败(且不满足之前判断的密码错误次数达到5次的条件),则会将该用户的登录错误次数加1,并设置该键的过期时间为5分钟。然后返回一个包含账户名或密码错误信息的 ApiResult 对象。
完整代码
@Autowired
private CardUserService userService;
@Autowired
private RedisUtil redisUtil;
@PostMapping("/login")
@ApiOperation(value = "登录")
@ApiImplicitParams({
@ApiImplicitParam(name="account",value = "用户名",required = true),
@ApiImplicitParam(name="password",value = "密码",required = true)
})
public ApiResult login(HttpServletRequest request, @RequestParam String account, @RequestParam String password) {
Integer errortimes = (Integer) redisUtil.get(RedisKeys.USERLOGINTIMES + account);
if (errortimes != null && errortimes >= 5) {
return new ApiResult(0, "密码错误5次,请5分钟后再登录", null);
}
QueryWrapper<CardUser> wrapper = new QueryWrapper<>();
wrapper.eq("uname", account).eq("passwd", PasswordUtil.encodePassword(password));
List<CardUser> users = userService.list(wrapper);
if (users != null && users.size() > 0) {
CardUser user = users.get(0);
user.setIdcard(null);
user.setPasswd(null);
HttpSession session = request.getSession();
session.setAttribute("user", user);
return new ApiResult(1, "登录成功", user);
} else {
redisUtil.incr(RedisKeys.USERLOGINTIMES + account, 1);
redisUtil.expire(RedisKeys.USERLOGINTIMES + account, 60 * 5);
return new ApiResult(0, "账户名或密码错误", null);
}
}
logout部分
非常简单粗暴
HttpSession session = request.getSession();
if (session != null) {
session.invalidate();
}
return new ApiResult(1, "退出登录", null);
首先通过HttpServletRequest获取HttpSession对象,然后检查session是否为null,如果不为null,则调用invalidate()方法使session失效。最后返回一个表示成功退出登录的ApiResult对象。
让session失效是为了确保用户在退出登录后,其会话信息得到及时清理,从而避免可能存在的安全风险和信息泄漏问题。一旦会话失效,用户在该会话中的所有状态和数据都会被清除,包括登录凭证、用户信息等,以确保用户的隐私和安全。
完整代码
@GetMapping("/logout")
@ApiOperation(value = "退出")
public ApiResult logout(HttpServletRequest request) {
HttpSession session = request.getSession();
if (session != null) {
session.invalidate();
}
return new ApiResult(1, "退出登录", null);
}
运行测试
运行springboot项目后访问http://localhost:9001/doc.html
密码正确的情况
密码错误的情况
密码错5次及以上的情况
使用nginx部署前端后,进行前后端联调:
登录成功还没跳转
登录失败: