用户相关接口实现
一、用户分页数据
1.配置mybatis分页插件
利用mybatis本身的拦截器机制对beforeQuery方法进行了实现,根据指定的方言类型拼接上分页sql
语句和对应的参数映射
。
java
代码解读
复制代码
@Configuration @EnableTransactionManagement//激活spring事务管理器 public class MybatisConfig { /** * 分页插件和数据权限插件 */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); //数据权限拦截器 interceptor.addInnerInterceptor(new DataPermissionInterceptor(new MyDataPermissionHandler())); //分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } /** * 自动填充数据库创建人、创建时间、更新人、更新时间 */ @Bean public GlobalConfig globalConfig() { GlobalConfig globalConfig = new GlobalConfig(); globalConfig.setMetaObjectHandler(new MyMetaObjectHandler()); return globalConfig; } }
数据权限以及自动填充数据库某些公有字段后续再进行解释
2.定义分页响应结构体PageResult以及视图对象UserPageVO
- 因为定义的是分页的响应结构,所以还需要在PageResult中添加一个构造。接收
IPage<T>
类型的参数。并将前端所需要的total以及list封装给PageResult对象
整理了一份Java面试题。包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记的【点击此处】即可免费获取
java
代码解读
复制代码
@Data public class PageResult<T> implements Serializable { private String code; private Data<T> data; private String msg; public static <T> PageResult<T> success(IPage<T> page) { PageResult<T> result = new PageResult<>(); result.setCode(ResultCode.SUCCESS.getCode()); Data data = new Data<T>(); data.setList(page.getRecords()); data.setTotal(page.getTotal()) result.setData(data); result.setMsg(ResultCode.SUCCESS.getMsg()); return result; } @Data public static class Data<T> { private List<T> list; private long total; } }
- 定义返回给前端的VO对象 (代码省略)需要注意的是可以添加
@Schema
注解,为接口文档添加描述。 - 定义接收前端传入的query参数对象 UserPageQuery 注意添加
@DateTimeFormat(pattern = "yyyy-MM-dd")
注解以及接口文档的解释注解 (如果传入的是Json 还需要注意使用@JsonFormat
注解) - SysUserController--->SysUserService--->SysUserServiceImpl
- 根据前端传入的query参数封装成mybatis-plus的Page对象
- 格式化为数据库的
日期格式
,避免日期比较格式化函数导致索引失效 - 传入
Page
对象以及query
参数调用mapper层进行查询 - 查询出来的对象一般封装为BO
- 利用
MapStruct
实现对象之间赋值(过于复杂的 直接set就好 推荐博客文章 blog.csdn.net/yangshangwe…) - 返回page给前端
java
代码解读
复制代码
@Operation(summary = "用户分页列表") @GetMapping("/page") //返回一个分页查询对象 泛型为UserPageVO public PageResult<UserPageVO> listPagedUsers( UserPageQuery queryParams ) { //返回mybatis-plus分页插件类型的接口 IPage<UserPageVO> result = userService.listPagedUsers(queryParams); return PageResult.success(result); }
service部分代码
java
代码解读
复制代码
@Override public IPage<UserPageVO> listPagedUsers(UserPageQuery queryParams) { // 参数构建 int pageNum = queryParams.getPageNum(); int pageSize = queryParams.getPageSize(); Page<UserBO> page = new Page<>(pageNum, pageSize); // 格式化为数据库日期格式,避免日期比较使用格式化函数导致索引失效 DateUtils.toDatabaseFormat(queryParams, "startTime", "endTime"); // 查询数据 Page<UserBO> userPage = this.baseMapper.listPagedUsers(page, queryParams); // 实体转换 return userConverter.toPageVo(userPage); }
二、用户信息的增删改操作
新增与修改
mybatis-plus提供了类似于saveOrUpdate这种方法 所以一般service层写到一起进行判断
新增与修改返��值都是boolean,表单提交上来的参数也都要进行校验@Valid
(@NotNull @NotBlank等)。
1.逻辑实现
- 根据前端传入的
user_id
判断是新增还是修改 - 数据库查询用户名是否唯一
- 新增时添加密码编码器对明文密码进行
加密
- 使用mapstruct将表单对象转换为entity
- 调用
saveOrUpdate
方法 插入到sys_user表 - 保存用户与角色相关的信息 插入到sys_user_role
- 如果是修改 还需要
删除已经不生效
的关联信息
java
代码解读
复制代码
@Override @Transactional(rollbackFor = Exception.class) public boolean saveUserRoles(Long userId, List<Long> roleIds) { if (userId == null || CollectionUtil.isEmpty(roleIds)) { return false; } // 用户原角色ID集合 List<Long> userRoleIds = this.list(new LambdaQueryWrapper<SysUserRole>() .eq(SysUserRole::getUserId, userId)) .stream() .map(SysUserRole::getRoleId) .collect(Collectors.toList()); // 新增用户角色 List<Long> saveRoleIds; if (CollectionUtil.isEmpty(userRoleIds)) { saveRoleIds = roleIds; } else { saveRoleIds = roleIds.stream() .filter(roleId -> !userRoleIds.contains(roleId)) .collect(Collectors.toList()); } List<SysUserRole> saveUserRoles = saveRoleIds .stream() .map(roleId -> new SysUserRole(userId, roleId)) .collect(Collectors.toList()); this.saveBatch(saveUserRoles); // 删除用户角色 if (CollectionUtil.isNotEmpty(userRoleIds)) { List<Long> removeRoleIds = userRoleIds.stream() .filter(roleId -> !roleIds.contains(roleId)) .collect(Collectors.toList()); if (CollectionUtil.isNotEmpty(removeRoleIds)) { this.remove(new LambdaQueryWrapper<SysUserRole>() .eq(SysUserRole::getUserId, userId) .in(SysUserRole::getRoleId, removeRoleIds) ); } } return true; }
2.防止重复提交
利用redisson
的分布式锁以及aop
实现的防止重复提交
DuplicateSubmitAspect
切面通过在被PreventRepeatSubmit注解标记
的方法执行前尝试获取一个基于请求信息和JWT Token的分布式锁
,有效地防止了重复提交。如果锁获取失败,表明有其他实例正在处理相同请求,从而阻止了重复执行,确保了操作的原子性和一致性
java
代码解读
复制代码
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface PreventRepeatSubmit { /** * 锁过期时间(秒) * <p> * 默认5秒内不允许重复提交 */ int expire() default 5; }
java
代码解读
复制代码
@Aspect @Component @Slf4j @RequiredArgsConstructor public class DuplicateSubmitAspect { private final RedissonClient redissonClient; private static final String RESUBMIT_LOCK_PREFIX = "LOCK:RESUBMIT:"; /** * 防重复提交切点 */ @Pointcut("@annotation(preventRepeatSubmit)") public void preventDuplicateSubmitPointCut(PreventRepeatSubmit preventRepeatSubmit) { log.info("定义防重复提交切点"); } @Around("preventDuplicateSubmitPointCut(preventRepeatSubmit)") public Object doAround(ProceedingJoinPoint pjp, PreventRepeatSubmit preventRepeatSubmit) throws Throwable { String resubmitLockKey = generateResubmitLockKey(); if (resubmitLockKey != null) { int expire = preventRepeatSubmit.expire(); // 防重提交锁过期时间 RLock lock = redissonClient.getLock(resubmitLockKey); boolean lockResult = lock.tryLock(0, expire, TimeUnit.SECONDS); // 立刻尝试获取锁;失败,直接返回 false if (!lockResult) { throw new BusinessException(ResultCode.REPEAT_SUBMIT_ERROR); // 抛出重复提交提示信息 } } return pjp.proceed(); } /** * 获取重复提交锁的 key */ private String generateResubmitLockKey() { String resubmitLockKey = null; HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String token = request.getHeader(HttpHeaders.AUTHORIZATION); if (StrUtil.isNotBlank(token) && token.startsWith(SecurityConstants.JWT_TOKEN_PREFIX)) { token = token.substring(SecurityConstants.JWT_TOKEN_PREFIX.length()); // 从 JWT Token 中获取 jti String jti = (String) JWTUtil.parseToken(token).getPayload(RegisteredPayload.JWT_ID); resubmitLockKey = RESUBMIT_LOCK_PREFIX + jti + ":" + request.getMethod() + "-" + request.getRequestURI(); } return resubmitLockKey; } }