携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第8天,点击查看活动详情
一、前言
在上一章中我对个人博客项目完成了登录接口,通过登录接口来整合redis和jwt,同时为创建UtilController,将一些公共接口放置在UtilController中
本章主要是完善上一章没有做完的功能, 主要是用户模块和完善登录接口, 包括用户的增删改查细节处理, 和登录接口的待完善内容(用户登录次数, 最后登录时间, 登录ip)
二, 用户模块增删改查
新增用户的注册时间,登录次数填充, 注册地ip, 信息判断, 密码加密
密码加密新增pom依赖和PasswordUtils工具类
<!-- 实现SHA265加密-->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version> 1.15</version>
</dependency>
/**
* 密码工具类
* @Author ningxuan
* @Date 2022/8/8 20:30
*/
public class PasswordUtils {
/***
* 利用Apache的工具类实现SHA-256加密
*
* @param str 加密后的报文
* @return
*/
public static String getSHA256Str(String str) {
MessageDigest messageDigest;
String encdeStr = "";
try {
messageDigest = MessageDigest.getInstance("SHA-256");
byte[] hash = messageDigest.digest(str.getBytes("UTF-8"));
encdeStr = Hex.encodeHexString(hash);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return encdeStr;
}
}
SysUserController
@PostMapping
@ApiOperation("新增")
public ResultVo insert(@RequestBody SysUserInsertDto userDto, HttpServletRequest request){
// 判断username是否重复 true:null
boolean b = sysUserService.userIsOnly(userDto.getUsername());
if (!b){
throw new BlogException(ErrorEnum.USER_EXIST_ERROR);
}
// 新增用户
sysUserService.insert(userDto,request);
return new ResultVo();
}
SysUserServiceImpl
// 新增
@Override
public void insert(SysUserInsertDto userDto, HttpServletRequest request) {
SysUser user = new SysUser();
BeanUtils.copyProperties(userDto, user);
user.setNum(1);
user.setStatus("1");
user.setCreateTime(LocalDateTime.now());
// 获取用户注册ip
String ipAddr = IpUtils.getIpAddr(request);
user.setIpconfig(ipAddr);
// 如果用户未填昵称则默认为username
if (StringUtils.isEmpty(userDto.getNickname())){
user.setNickname(userDto.getUsername());
}
// 密码加密
user.setPassword(PasswordUtils.getSHA256Str(user.getPassword()));
this.save(user);
}
// 判断username是否唯一
@Override
public boolean userIsOnly(String username) {
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysUser::getUsername, username);
SysUser user = this.getOne(wrapper);
return user == null;
}
新建IPUtil类,用到了Common-lang3 jar包
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8</version>
</dependency>
用到了里面的StringUtils.isEmpty(final CharSequence cs)方法, 用来判断字符串是否为null
IpUtils工具类来自于人人开源,具体如下
public class IpUtils {
private static Logger logger = LoggerFactory.getLogger(IpUtils.class);
/**
* 获取IP地址
*
* 使用Nginx等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
* 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非unknown的有效IP字符串,则为真实IP地址
*/
public static String getIpAddr(HttpServletRequest request) {
String unknown = "unknown";
String ip = null;
try {
ip = request.getHeader("x-forwarded-for");
if (StringUtils.isEmpty(ip) || unknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || unknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (StringUtils.isEmpty(ip) || unknown.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (StringUtils.isEmpty(ip) || unknown.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
} catch (Exception e) {
logger.error("IPUtils ERROR ", e);
}
return ip;
}
}
逻辑删除
使用了mybatis-plus的逻辑删除
修改时间填充, username不允许修改等
@Override
public void updateUser(SysUserUpdateDto userDto) {
SysUser byId = this.getById(userDto.getBlid());
if (byId == null){
throw new BlogException(ErrorEnum.NOT_USER_ERROR);
}
SysUser user= new SysUser();
BeanUtils.copyProperties(userDto, user);
// 更新修改时间
user.setUpdateTime(LocalDateTime.now());
this.updateById(user);
}
查看列表
@Override
public Page<SysUser> getList(PageVo page) {
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
// 可以根据keyword进行用户名的模糊查询
wrapper.like(page.getKeyword() != null, SysUser::getNickname, page.getKeyword())
// 只查询用户id, 昵称, 头像地址, 用户名, 最后登录时间
.select(SysUser::getBlid,SysUser::getNickname,SysUser::getAvatar, SysUser::getUsername, SysUser::getLastLoginTime)
// 查询账户状态正常的用户
.eq(SysUser::getStatus, "1");
Page<SysUser> pageList = new Page<>(page.getPage(), page.getSize());
Page<SysUser> list = this.page(pageList, wrapper);
return list;
}
查看详情
@GetMapping
@ApiOperation("查看详情")
public ResultVo getInfo(Long blid){
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysUser::getBlid, blid);
SysUser one = sysUserService.getOne(wrapper);
one.setPassword(null);
return new ResultVo(one);
}
三、完善登录接口
用户不存在和密码错误使用自定义异常
@Override
public SysUser login(LoginDto loginDto, HttpServletResponse response) {
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysUser::getUsername, loginDto.getUsername());
SysUser one = this.getOne(wrapper);
if (one == null){
// 自定义异常
log.error("用户不存在");
throw new BlogException(ErrorEnum.USER_OR_PASSWORD_ERROR);
}
boolean equals = one.getPassword().equals(PasswordUtils.getSHA256Str(loginDto.getPassword()));
if (!equals){
// 自定义异常
//TODO 密码错误之后生成token判断错误次数,超过五次锁用户
log.error("账号或密码错误");
throw new BlogException(ErrorEnum.USER_OR_PASSWORD_ERROR);
}
// 使用jwt生成token
String token = JwtUtils.generateToken(one.getBlid());
// 使用redis保存token 默认8小时
redisUtils.set(RedisKeys.getLoginKey(token), one, CommonConstant.REDIS_TIME_TOKEN);
// token存入cookie
CookieUtils.setUpCookie(response, CommonConstant.COOKIE_TOKEN_KEY, token);
return one;
}
完善之前留下的CookieUtil工具类中不能获取到application.yml文件的配置
pom依赖下载
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
类上添加@Component注解,主要是上面的依赖,不下载的话我添加注解也不好使
四、泛谈密码加密
明文存储密码毕竟有点不好, 本项目中采用了SHA265的形式存储密码
几种加密
由易到难可以分为
- 前端加密
- 前端、后端两至多次加密
- 前端、后端(密码+加密盐)加密
- 前端、后端(密码+加密盐+用户名)加密
- 加密盐随登录随机生成
五、测试
修改
这里我只按照id查询, 但是后面自动添加了status=“0”是因为我们之前设置逻辑删除了,具体可以看图
mybatis-plus逻辑删除注解 @TableLogic
删除
同样的因为我们设置了mybatis-plus 的逻辑删除,那么删除的时候是默认把我们设置的逻辑删除字段改为“1”
六、总结
详情具体的查SQL直接写到了controller层了, 感觉不太对, 但是目前对于Controller, Service我感觉对其还是了解的不够彻底,如果有大佬希望可以在评论区留下一些自己的见解, 谢谢