学习地址:https://www.bilibili.com/video/BV1TP411v7v6?p=6&spm_id_from=pageDriver&vd_source=a6f7db332f104aff6fadf5b3542e5875
后端环境搭建
- Entity 实体,通常和数据库的表对应
- DTO 数据传输对象,用于程序中各层之间传递数据 (前端发过来)
- VO 视图对象,为前端展示数据提供的对象,返回前端
- POJO 普通java对象,只有属性和对应的getter和setter
Swagger使用
常用注解
一些优雅的方案
-
BeanUtils.copyProperties(a,b)
将a对象上的值复制给b对象。 -
Budiler
在定义数据类的时候加上该注解,在创建实例的时候可以使用链式调用。
@Data
@Builder
@ApiOperation(value = "查询返回数据")
public class EmpPageVO {
Integer count;
List<Employee> data;
}
// 使用
EmpPageVO empPageVO = EmpPageVO.builder().count(allCount).data(returnData).build();
ThreadLocal
,每个线程都会创建一个,互不影响,可以将每次请求的用户信息存储起来,后面需要用到直接取出来,因为每个请求都是一个单独的线程,互不影响。
定义类
// spring boot 每个请求都由一个线程处理
public class UserContext {
public static ThreadLocal<UserContextDTO> threadLocal = new ThreadLocal();
public static void setCurrentUser(UserContextDTO user){
threadLocal.set(user);
}
public static UserContextDTO getCurrentUser(){
return threadLocal.get();
}
public static void removeCurrentUser(){
threadLocal.remove();
}
}
// 将用户信息存储
@Slf4j
@Component("AuthInterceptor")
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {
String url = req.getRequestURL().toString();
log.info("处理请求....{}", url);
// TODO 前期不需要校验
if(url != null){
// 登陆接口不需要校验
return true;
}
String token = req.getHeader("token");
try {
if(token != null){
Claims data = JwtUtils.parseToken(token);
UserContext.setCurrentUser((UserContextDTO) data); //将登陆用户存储起来
return true;
}else {
throw new UserNotLoginException("token is not exists or expire");
}
} catch (Exception e){
Result error = Result.error(e.getMessage());
res.addHeader("Content-Type", "application/json");
// 转位json
res.getWriter().write(JSONObject.toJSONString(error));
return false;
}
};
}
//在后续service层可直接使用
emp.setUpdateUser(UserContext.getCurrentUser().getUserId());
公共字段自动填充
自动填充time,user
像每个表的createTime createUser updateTime updateUser,在每一次增加或者修改的时候都会赋值,为了避免冗余,方便后期维护,可以使用切面AOP,精准拦截对应的方法,然后加上对应的操作,这样原本的create update方法就不用写setCreateTime, setUpdateTime这些了。
自定义注解
package com.sky.demo.annotaion;
import com.sky.demo.constant.SqlActionType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AutoFill {
SqlActionType value();
}
package com.sky.demo.constant;
public enum SqlActionType {
Update,
Insert
}
在需要用到的mapper上添注解
自定义切面类
package com.sky.demo.ascept;
import com.sky.demo.constant.AutoFillConstant;
import com.sky.demo.constant.SqlActionType;
import com.sky.demo.utils.DateTimeFormat;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import com.sky.demo.annotaion.AutoFill;
import java.lang.reflect.Method;
/**
* aop,用于给数据库创建 插入 自动注入 时间,用户
*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* 切入点
*/
@Pointcut("execution(* com.sky.demo.mapper.*.*(..)) && @annotation(com.sky.demo.annotaion.AutoFill)")
public void autoFillPointCut(){}
/**
* 前置通知
*/
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint){
log.info("开始进行公共字段填充");
// 获取当前被拦截的方法上的数据库操作类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取拦截mapper的注解
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
// 获取注解的value
SqlActionType sqlActionType = autoFill.value();
// 获取到当前被拦截方法的参数-实体对象(categoryMapper.update(data))中的data,修改其参数可以反馈到sql语句中
Object[] args = joinPoint.getArgs();
if(args == null || args.length == 0){
return;
}
Object entity = args[0];
// 准备赋值的数据
String currentTime = DateTimeFormat.getCurrentTime();
if(sqlActionType == SqlActionType.Insert){
try {
Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.setCreateUser, Integer.class);
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.setCreateTime, String.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.setUpdateUser, Integer.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.setUpdateTime, String.class);
// 通过反射进行赋值
setCreateUser.invoke(entity, 0);
setCreateTime.invoke(entity, currentTime);
setUpdateUser.invoke(entity, 0);
setUpdateTime.invoke(entity, currentTime);
}catch (Exception e){
e.printStackTrace();
}
}else if(sqlActionType == SqlActionType.Update) {
try {
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.setUpdateUser, Integer.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.setUpdateTime, String.class);
setUpdateUser.invoke(entity, 0);
setUpdateTime.invoke(entity, currentTime);
}catch (Exception e){
e.printStackTrace();
}
}
// 根据不同的操作类型,为对应的属性赋值
}
}
通过反射拿到每个实例对象(约定每个方法的第一个参数为实例对象)的setCreateUser等方法,然后注入数据,这样就完成对公共字段的自动填充。
自动处理page,pageSize
基础此思路,继续开发一个拦截分页查询方法的切面类,使其自动实现对page,pageSize等处理。
定义自定义注解
package com.sky.demo.annotaion;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AutoPage {
}
在需要用到的地方使用注解
实现切面类
package com.sky.demo.ascept;
import com.sky.demo.annotaion.AutoFill;
import com.sky.demo.constant.AutoFillConstant;
import com.sky.demo.constant.SqlActionType;
import com.sky.demo.dto.PageDTO;
import com.sky.demo.utils.DateTimeFormat;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
@Slf4j
public class AutoPageAspect {
/**
* 切入点
*/
@Pointcut("execution(* com.sky.demo.mapper.*.*(..)) && @annotation(com.sky.demo.annotaion.AutoPage)")
public void autoFillPointCut() {
}
/**
* 前置通知
*/
@Before("autoFillPointCut()")
public void autoPage(JoinPoint joinPoint) {
log.info("分页查询开始进行公共字段填充");
Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0) {
return;
}
PageDTO entity = (PageDTO)args[0];
try {
Method setPage = entity.getClass().getDeclaredMethod(AutoFillConstant.setPage, Integer.class);
Method setPageSize = entity.getClass().getDeclaredMethod(AutoFillConstant.setPageSize, Integer.class);
Integer page = entity.getPage() != null ? (entity.getPage() == 0 ? 1 : entity.getPage()) : 1;
Integer pageSize = entity.getPageSize() != null ? entity.getPageSize() : 20;
Integer start = (page - 1) * pageSize;
Integer end = page * pageSize;
// 通过反射进行赋值
setPage.invoke(entity, start);
setPageSize.invoke(entity, end);
} catch (Exception e) {
e.printStackTrace();
}
}
}
如上,通过强制转换,将实例对象转为PageDTO,约定每个分页的DTO都得继承该类,然后通过反射注入即可。
这样就能自动将page,pageSize转为sql中limit正确的参数。