在做项目的时候遇到了这个问题,表结构如下
当前的时间
插入之后显示的时间
在写后端接口时并没有对create_time和update_time进行字段插入
因为在设计表结构时候,把这两个时间的属性设置为了timestamp类型并,设置了默认值CURRENT_TIMESTAMP,这样在对数据进行插入或修改时就可以直接默认插入当前时间。
但问题出现了,当我进行前后端联调测试的时候发现并未插入当前系统时间,问题无非就出现在前端,后端,数据库这三个方面,前端和后端均为设计这两个字段的插入操作,然后我就在navicate里面进行插入结果发现时间不一致,然后就想到了时区的问题,会不会是因为我使用navicate连接虚拟机的mysql时并未设置当前时区,然后修改为东八区之后再进行插入,解决了该问题
执行以下sql命令进行修改:
#修改全局为东八区
set global time_zone = '+8:00';
#修改当前会话
set time_zone = '+8:00';
#刷新权限 重新加载授权表并使更改生效的命令
flush privileges;
当然除了使用命令修改之外还可以再配置文件中修改,在虚拟机中找到配置文件位置进行修改保存
[mysqld]
default-time_zone = '+8:00'
如果修改未生效则可以重启mysql服务
systemctl stop mysqld.service
systemctl start mysqld.service
最最最重要的是,当你按上面的方法修改完前端界面时间还没修改为正确的时间,有以下问题
给前端响应的数据
idea控制台待响应数据
可以看出时间查了8个小时,这是因为JSON配置导致的,因为要返回JSON数据,这里差八个小时,在配置文件中进行下面配置即可:
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
当然如果不想在mysql里面进行修改这里可以使用公共字段自动填充在后端代码里面进行插入
实现思路:使用AOP切面、反射、注解进行解决
- 自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法
/**
* 自定义注解 , 用于标识某个方法需要进行功能字段自动填充处理
*/
//指定注解位置 , 指定注解只能加在方法上面
//在这里,@Target(ElementType.METHOD) 表示被注解修饰的注解只能用于方法上
//具体来说,它告诉编译器和开发者,这个注解只能应用在方法级别的元素上,而不能应用在类、字段或其他元素上。
@Target(ElementType.METHOD)
//@Retention(RetentionPolicy.RUNTIME)
// 是 Java 中的一个元注解,用于指定被修饰的注解在运行时仍然可用。@Retention 注解有三个枚举值:
//RetentionPolicy.RUNTIME: 注解被保留到运行时,因此可以通过反射机制在运行时获取注解信息。
// 这允许在程序运行时动态地读取和处理注解。
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
//指定当前数据库操作类型: update , insert
OperationType value();
}
- 自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值
/**
* 自定义切面 , 实现公共字段自动填充处理逻辑
*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* 切入点
* 返回值是所有的*
* .*.*(..) mapper下面所有的类所有的方法
* 还要面子定义的注解
*/
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}
/**
* 前置通知
* 需要匹配切点表达式
*/
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint){
log.info("开始进行公共字段的填充...");
//获取当前被拦截方法数据库操作类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法签名对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获取方法上的注解对象
OperationType operationType = autoFill.value();//获取数据库操作类型 @AutoFill(value = OperationType.INSERT)
//获取当前被拦截方法的参数--实体对象 void isnert(Employee employee); 获取参数employee
Object[] args = joinPoint.getArgs();
//防止空指针异常
if(args == null || args.length == 0){
return;
}
//获取参数 实体对象
Object entity = args[0];
//准备赋值数据
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
//根据当前不同的操作类型 , 为对应的属性通过反射进行赋值
if(operationType == OperationType.INSERT){
//为4个公共字段赋值
//为2个公共字段赋值
try {
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射进行赋值
setCreateTime.invoke(entity , now);
setCreateUser.invoke(entity , currentId);
setUpdateTime.invoke(entity , now);
setUpdateUser.invoke(entity , currentId);
} catch (Exception e) {
throw new RuntimeException(e);
}
//通过反射进行赋值
}else if(operationType == OperationType.UPDATE){
//为2个公共字段赋值
try {
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射进行赋值
setUpdateTime.invoke(entity , now);
setUpdateUser.invoke(entity , currentId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}else {
}
}
}
- 在 Mapper 的方法上加入 AutoFill 注解
@AutoFill(value = OperationType.UPDATE)
void update(Dish dish);