1、用户服务
(1)UserService接口
public interface UserService {
/**
* 登录
*
* @param loginRequest
* @param request
* @return
*/
JsonData login(UserLoginReq loginRequest, HttpServletRequest request);
/**
* 查询用户详情
*
* @return
*/
UserVO findUserInfo(int userId);
/**
* 新增或修改
*
* @param userReq
* @return
*/
JsonData saveOrUpdate(UserReq userReq);
/**
* 删除
*
* @param username
*/
void delete(String username);
/**
* 密码重置
*
* @param userId
*/
void reset(Integer userId);
/**
* 分页列表
*
* @param params
* @return
*/
PageResult pageList(Map<String, Object> params);
/**
* 修改密码
*
* @param oldPwd
* @param newPwd
* @param userId
*/
void modify(String oldPwd, String newPwd, int userId);
/**
* 退出
*/
void logout();
/**
* 分配角色
*
* @param userRoleReq
*/
void addUserRoles(UserRoleReq userRoleReq);
}
@Data
@ApiModel(value = "用户", description = "用户请求对象")
public class UserReq {
@ApiModelProperty(value = "用户id", example = "1")
private Integer id;
@ApiModelProperty(value = "用户名", example = "admin", required = true)
private String username;
@ApiModelProperty(value = "昵称", example = "管理员", required = true)
private String nickname;
}
@Data
@ApiModel(value = "用户登录", description = "用户登录请求对象")
public class UserLoginReq {
@ApiModelProperty(value = "用户名", example = "admin")
private String username;
@ApiModelProperty(value = "密码", example = "123456")
private String password;
@ApiModelProperty(value = "验证码", example = "123456")
private String captcha;
}
@Data
@ApiModel(value = "用户角色", description = "用户分配角色请求对象")
public class UserRoleReq {
@ApiModelProperty(value = "用户id", example = "1")
@JsonProperty("user_id")
private int userId;
@ApiModelProperty(value = "角色列表", example = "[1,2]")
@JsonProperty("role_ids")
private List<Integer> roleIds;
}
(2)接口实现UserServiceImpl
@Slf4j
@Service
public class UserServiceImpl implements UserService {
private static final String INIT_PASSWORD = "123456";
@Value("${user.head.img}")
private String defaultHeadImg;
@Autowired
private UserMapper userMapper;
@Autowired
private RoleMapper roleMapper;
@Autowired
private MenuMapper menuMapper;
@Autowired
private PermissionMapper permissionMapper;
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 用户登录
*
* @param loginRequest
* @return
*/
@Override
public JsonData login(UserLoginReq loginRequest, HttpServletRequest request) {
String key = getCaptchaKey(request);
String cacheCaptcha = stringRedisTemplate.opsForValue().get(key);
// 校验图形验证码
if (StringUtils.isNoneBlank(cacheCaptcha)) {
if (loginRequest.getCaptcha() != null
&& Objects.requireNonNull(cacheCaptcha).equalsIgnoreCase(loginRequest.getCaptcha())) {
stringRedisTemplate.delete(key);
} else {
return JsonData.buildResult(BizCodeEnum.CODE_CAPTCHA_ERROR);
}
} else {
return JsonData.buildResult(BizCodeEnum.CODE_CAPTCHA_NOT_EXIST);
}
// 通过username找数据库记录
SysUserDO sysUserDO = userMapper.selectOne(new QueryWrapper<SysUserDO>().eq("username", loginRequest.getUsername()));
if (sysUserDO != null) {
// 获取盐和当前传递的密码就行,加密后匹配
String cryptPwd = Md5Crypt.md5Crypt(loginRequest.getPassword().getBytes(), sysUserDO.getSecret());
if (cryptPwd.equals(sysUserDO.getPwd())) {
// 登录成功,生成token
String token = geneUserToken(sysUserDO);
return JsonData.buildSuccess(token);
} else {
return JsonData.buildResult(BizCodeEnum.ACCOUNT_PWD_ERROR);
}
} else {
// 为了混淆,一种常用的安全防护
return JsonData.buildResult(BizCodeEnum.ACCOUNT_PWD_ERROR);
}
}
/**
* 查询当前用户详情
*
* @return
*/
@Override
public UserVO findUserInfo(int userId) {
if (userId < -1L) {
throw new BizException(BizCodeEnum.ACCOUNT_UNREGISTER);
}
int id = userId;
if (userId == -1L) {
LoginUser loginUser = LoginInterceptor.threadLocal.get();
id = loginUser.getId();
}
SysUserDO sysUserDO = userMapper.selectOne(new QueryWrapper<SysUserDO>().eq("id", id));
UserVO userVO = new UserVO();
BeanUtils.copyProperties(sysUserDO, userVO);
/*
这里是可以直接写在RoleMapper中的,但是需要在RoleDO中增加两个list字段,会导致后面使用工具类中的方法时,
RoleDO的字段对应不上。数据表中并没有两个list字段。现在这种处理方式也没什么问题
*/
// 获取用户角色列表,一般就1到2个角色
List<SysRoleDO> sysRoleDOList = roleMapper.findRoleListByUserId(id);
userVO.setRoleList(sysRoleDOList.stream().map(roleDo -> {
// 查询角色菜单列表
List<SysMenuDO> sysMenuDOList = menuMapper.findMenuListByRoleId(roleDo.getId());
// 查询角色权限列表
List<SysPermissionDO> sysPermissionDOList = permissionMapper.findPermissionListByRoleId(roleDo.getId());
RoleVO roleVO = new RoleVO();
BeanUtils.copyProperties(roleDo, roleVO);
roleVO.setMenuList(sysMenuDOList.stream().map(m -> ConvertBean.getProcess(m, MenuVO.class)).collect(Collectors.toList()));
roleVO.setPermissionList(sysPermissionDOList.stream().map(m -> ConvertBean.getProcess(m, PermissionVO.class)).collect(Collectors.toList()));
return roleVO;
}).collect(Collectors.toList()));
return userVO;
}
/**
* 用户新增或修改
*
* @param userReq
* @return
*/
@Override
public JsonData saveOrUpdate(UserReq userReq) {
String username = userReq.getUsername();
String nickname = userReq.getNickname();
if (userReq.getId() != null && userReq.getId() > 0) {
// 修改
SysUserDO sysUserDO = userMapper.selectById(userReq.getId());
if (sysUserDO != null) {
// 不允许修改管理员的账号
if (sysUserDO.getUsername().equals("admin") && !userReq.getUsername().equals("admin")) {
return JsonData.buildResult(BizCodeEnum.ACCOUNT_REJECT_UPDATE);
}
if (checkUserUnique(username, userReq.getId())) {
sysUserDO.setUsername(username);
sysUserDO.setNickname(nickname);
userMapper.updateById(sysUserDO);
geneUserToken(sysUserDO);
} else {
return JsonData.buildResult(BizCodeEnum.ACCOUNT_REPEAT);
}
} else {
return JsonData.buildResult(BizCodeEnum.ACCOUNT_UNREGISTER);
}
} else {
if (StringUtils.isBlank(username)) {
return JsonData.buildError("用户名不能为空");
}
if (StringUtils.isBlank(nickname)) {
return JsonData.buildError("昵称不能为空");
}
// 新增
synchronized (this) {
if (checkUserUnique(username, -1)) {
SysUserDO sysUserDO = new SysUserDO();
sysUserDO.setCreateTime(CommonUtil.getCurrentDate());
sysUserDO.setSecret("$1$" + CommonUtil.getStringNumRandom(8));
sysUserDO.setPwd(Md5Crypt.md5Crypt(INIT_PASSWORD.getBytes(), sysUserDO.getSecret()));
sysUserDO.setUsername(username);
sysUserDO.setNickname(nickname);
// 从数据表查一下已有的头像
String headImg = userMapper.selectUserHeadImg();
sysUserDO.setHeadImg(StringUtils.isNotBlank(headImg) ? headImg : defaultHeadImg); // 用户头像
userMapper.insert(sysUserDO);
} else {
return JsonData.buildResult(BizCodeEnum.ACCOUNT_REPEAT);
}
}
}
return JsonData.buildSuccess();
}
private boolean checkUserUnique(String username, int userId) {
QueryWrapper<SysUserDO> queryWrapper = new QueryWrapper<SysUserDO>().eq("username", username).ne("id", userId);
SysUserDO sysUserDO = userMapper.selectOne(queryWrapper);
return sysUserDO == null;
}
/**
* 用户分页列表
*
* @param params
* @return
*/
@Override
public PageResult pageList(Map<String, Object> params) {
if (CommonUtil.validatePageSize(params)) {
throw new BizException(BizCodeEnum.REQUEST_PARAMS_ERROR);
}
int page = MapUtils.getInteger(params, "page");
int size = MapUtils.getInteger(params, "size");
String username = (String) params.get("username");
String nickname = (String) params.get("nickname");
Page<SysUserDO> pageInfo = new Page<>(page, size);
IPage<SysUserDO> userDOIPage = userMapper.selectPage(pageInfo, new QueryWrapper<SysUserDO>().orderByDesc("id")
.like(StringUtils.isNotBlank(username), "username", username)
.like(StringUtils.isNotBlank(nickname), "nickname", nickname));
long total = userDOIPage.getTotal();
long pages = userDOIPage.getPages();
Object data = userDOIPage.getRecords().stream().map(m -> ConvertBean.getProcess(m, UserVO.class)).collect(Collectors.toList());
return PageResult.builder().totalRecord(total).totalPage(pages).currentData(data).build();
}
/**
* 用户删除
*
* @param username
*/
@Override
public void delete(String username) {
if (username.equals("admin")) {
throw new BizException(BizCodeEnum.ACCOUNT_REJECT_DELETE);
}
QueryWrapper queryWrapper = new QueryWrapper<SysUserDO>().eq("username", username);
SysUserDO sysUserDO = userMapper.selectOne(queryWrapper);
if (sysUserDO != null) {
stringRedisTemplate.delete(TOKEN_KEY + sysUserDO.getId());
userMapper.delete(queryWrapper);
}
}
/**
* 重置密码
*
* @param userId
*/
@Override
public void reset(Integer userId) {
SysUserDO sysUserDO = userMapper.selectById(userId);
if (sysUserDO != null) {
if (sysUserDO.getUsername().equals("admin")) {
throw new BizException(BizCodeEnum.ACCOUNT_REJECT_RESET);
}
sysUserDO.setSecret("$1$" + CommonUtil.getStringNumRandom(8));
sysUserDO.setPwd(Md5Crypt.md5Crypt(INIT_PASSWORD.getBytes(), sysUserDO.getSecret()));
geneUserToken(sysUserDO);
userMapper.updateById(sysUserDO);
} else {
throw new BizException(BizCodeEnum.ACCOUNT_UNREGISTER);
}
}
/**
* 修改当前用户的密码
*
* @param oldPwd
* @param newPwd
* @param userId
*/
@Override
public void modify(String oldPwd, String newPwd, int userId) {
if (oldPwd.equals(newPwd)) {
throw new BizException(BizCodeEnum.ACCOUNT_PWD_SAME);
}
if (newPwd.length() < 6) {
throw new BizException(BizCodeEnum.ACCOUNT_PWD_LESS);
}
SysUserDO sysUserDO = userMapper.selectById(userId);
if (sysUserDO != null) {
if (Md5Crypt.md5Crypt(oldPwd.getBytes(), sysUserDO.getSecret()).equals(sysUserDO.getPwd())) {
sysUserDO.setPwd(Md5Crypt.md5Crypt(newPwd.getBytes(), sysUserDO.getSecret()));
// 重新生成token
geneUserToken(sysUserDO);
userMapper.updateById(sysUserDO);
} else {
throw new BizException(BizCodeEnum.ACCOUNT_OLD_PWD_ERROR);
}
} else {
throw new BizException(BizCodeEnum.ACCOUNT_UNREGISTER);
}
}
/**
* 用户退出
*/
@Override
public void logout() {
LoginUser loginUser = LoginInterceptor.threadLocal.get();
stringRedisTemplate.delete(TOKEN_KEY + loginUser.getId());
LoginInterceptor.threadLocal.remove();
}
/**
* 分配用户角色
*
* @param userRoleReq
*/
@Transactional(rollbackFor = BizException.class)
@Override
public void addUserRoles(UserRoleReq userRoleReq) {
userMapper.deleteUserRole(userRoleReq.getUserId());
userMapper.addUserRoles(userRoleReq.getUserId(), userRoleReq.getRoleIds());
}
private String geneUserToken(SysUserDO sysUserDO) {
LoginUser loginUserDTO = LoginUser.builder().build();
BeanUtils.copyProperties(sysUserDO, loginUserDTO);
String token = JWTUtil.geneJsonWebToken(loginUserDTO, sysUserDO.getPwd());
stringRedisTemplate.opsForValue().set(TOKEN_KEY + sysUserDO.getId(), token, 7, TimeUnit.DAYS);
return token;
}
}
@Data
public class UserVO {
private Integer id;
/**
* 昵称
*/
private String nickname;
/**
* 用户名
*/
private String username;
/**
* 头像
*/
@JsonProperty("head_img")
private String headImg;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", locale = "zh", timezone = "GMT+8")
@JsonProperty("create_time")
private Date createTime;
/**
* 用户角色列表
*/
@JsonProperty("role_list")
private List<RoleVO> roleList;
}
(3)UserMapper
public interface UserMapper extends BaseMapper<SysUserDO> {
void updateHeadImg(String headImg);
void deleteUserRole(int userId);
void addUserRoles(int userId, @Param("list") List<Integer> roleIds);
String selectUserHeadImg();
}
<insert id="addUserRoles">
insert into user_role(user_id, role_id) values
<foreach collection="list" item="item" index="index" separator=",">
(#{arg0},#{item})
</foreach>
</insert>
<update id="updateHeadImg" parameterType="java.lang.String">
update sys_user
set head_img = #{arg0}
</update>
<delete id="deleteUserRole">
delete
from user_role
where user_id = #{arg0}
</delete>
<select id="selectUserHeadImg" resultType="java.lang.String">
select head_img
from sys_user
where head_img != '' limit 1
</select>
(4)阿里云OSS配置
@Configuration
public class AliyunOSSConfig {
@Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.access-key}")
private String accessKeyId;
@Value("${aliyun.oss.accessKeySecret}")
private String accessKeySecret;
@Bean
@ConditionalOnProperty(name = "aliyun.oss.access-key", matchIfMissing = true)
public OSS ossClient() {
return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
}
}
(5)拦截器配置
/**
* 拦截器配置
*/
@Configuration
@Slf4j
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor(stringRedisTemplate))
// 拦截的路径
.addPathPatterns("/api/user/*/**", "/api/menu/*/**", "/api/permission/*/**", "/api/role/*/**")
// 排查不拦截的路径
.excludePathPatterns("/api/user/*/login");
}
@Bean("requestInterceptor")
public RequestInterceptor requestInterceptor() {
return template -> {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
template.header("token", request.getHeader("token"));
} else {
log.warn("requestInterceptor() 获取Header空指针异常");
}
};
}
}
(6)Controller
@Api(tags = "用户模块")
@RestController
@RequestMapping("/api/user/v1")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private FileService fileService;
@ApiOperation("用户头像上传")
@PostMapping(value = "upload")
public JsonData uploadHeaderImg(@ApiParam(value = "文件上传", required = true) @RequestPart("file") MultipartFile file) {
String result = fileService.uploadUserHeadImg(file);
return result != null ? JsonData.buildSuccess(result) : JsonData.buildResult(BizCodeEnum.FILE_UPLOAD_USER_IMG_FAIL);
}
@ApiOperation("用户登录")
@PostMapping("login")
public JsonData login(@RequestBody UserLoginReq loginRequest, HttpServletRequest request) {
return userService.login(loginRequest, request);
}
@ApiOperation("用户退出")
@PostMapping("logout")
public JsonData logout() {
userService.logout();
return JsonData.buildSuccess();
}
@ApiOperation("查询用户信息")
@GetMapping("info")
public JsonData info(@ApiParam(name = "user_id", value = "用户id,为-1时查询当前登录的用户信息") @RequestParam("user_id") int userId) {
return JsonData.buildSuccess(userService.findUserInfo(userId));
}
@ApiOperation("用户新增或修改")
@PostMapping("saveOrUpdate")
public JsonData saveOrUpdate(@RequestBody UserReq userReq) {
return userService.saveOrUpdate(userReq);
}
@ApiOperation("用户分页列表")
@GetMapping("page_list")
public JsonData pageList(@RequestParam Map<String, Object> params) {
return JsonData.buildSuccess(userService.pageList(params));
}
@ApiOperation("删除用户")
@DeleteMapping("delete/{username}")
public JsonData delete(@PathVariable("username") String username) {
try {
userService.delete(username);
return JsonData.buildSuccess();
} catch (Exception e) {
return JsonData.buildError("删除失败:" + e.getMessage());
}
}
@ApiOperation("重置密码")
@PutMapping("pwd/reset")
public JsonData resetPwd(@RequestParam("user_id") Integer userId) {
try {
userService.reset(userId);
return JsonData.buildSuccess();
} catch (BizException e) {
return JsonData.buildError("密码重置失败:" + e.getMessage());
}
}
@ApiOperation("修改密码")
public JsonData modifyPwd(@RequestParam("old_pwd") String oldPwd, @RequestParam("new_pwd") String newPwd, @RequestParam("user_id") int userId) {
try {
userService.modify(oldPwd, newPwd, userId);
return JsonData.buildSuccess();
} catch (BizException e) {
return JsonData.buildError("密码修改失败:" + e.getMessage());
}
}
@ApiOperation("用户分配角色")
@PostMapping("addUserRoles")
public JsonData addUserRoles(@RequestBody UserRoleReq request) {
try {
userService.addUserRoles(request);
return JsonData.buildSuccess();
} catch (Exception e) {
return JsonData.buildError("操作失败:" + e.getMessage());
}
}
}
2、图形验证码Captcha
(1)配置
在config包中新增验证码配置,之前已添加过依赖了
@Configuration
public class CaptchaConfig {
/**
* 验证码配置
* Kaptcha配置类名
*/
@Bean
@Qualifier("captchaProducer")
public DefaultKaptcha kaptcha() {
DefaultKaptcha kaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 验证码个数
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
// 字体间隔
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "8");
// 干扰实现类
properties.setProperty(Constants.KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise");
// 图片样式
properties.setProperty(Constants.KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.WaterRipple");
// 文字来源
properties.setProperty(Constants.KAPTCHA_TEXTPRODUCER_CHAR_STRING, "0123456789");
Config config = new Config(properties);
kaptcha.setConfig(config);
return kaptcha;
}
}
(2)Controller
@Api(tags = "图形验证码模块")
@RestController
@RequestMapping(("/api/validate/v1"))
@Slf4j
public class ValidateCodeController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private Producer captchaProducer;
/**
* 1分钟过期
*/
private static final long CAPTCHA_CODE_EXPIRED = 60 * 1000;
@ApiOperation("获取图形验证码")
@GetMapping("captcha")
public void getCaptcha(HttpServletRequest request, HttpServletResponse response) {
String cacheKey = getCaptchaKey(request);
String capText = captchaProducer.createText();
// 存储
stringRedisTemplate.opsForValue().set(cacheKey, capText, CAPTCHA_CODE_EXPIRED, TimeUnit.MILLISECONDS);
BufferedImage bi = captchaProducer.createImage(capText);
ServletOutputStream out;
try {
response.setDateHeader("Expires", 0);
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
response.addHeader("Cache-Control", "create_date-check=0, pre-check=0");
response.setHeader("Pragma", "no-cache");
out = response.getOutputStream();
ImageIO.write(bi, "jpg", out);
out.flush();
out.close();
} catch (IOException e) {
log.error("获取验证码失败:{}", e);
}
}
}
下一次,我们来使用Postman测试接口功能!获取验证码->登录->访问接口!