如何在修改表时候做变更的历史记录,以及日志发送给前端,还有ThreadLocal属性记录时间

针对这个需求,我建议你可以通过以下方式来解决:

1. 在数据库中为每一张数据表添加两张表,一张用于存储原始数据,一张用于存储变更历史记录。例如:我们有表A,那么我们为表A创建A_Orignal和A_ChangeLog表。

2. 在A_Orignal表中对应需要记录的字段增加记录时间(create_time)和更新时间(update_time)两个字段。当插入或更新数据时,将当前的时间戳记录到create_time和update_time中。

3. 当发生数据更新时,通过对比A_Orignal表中的和新值,筛选出修改了的字段并记录在A_ChangeLog表中。例如,我们可以设计A_ChangeLog为id, ref_id, field_name, old_value, new_value, change_time,其中ref_id为A_Orignal表中的主键,即记录的是哪个数据的修改历史。field_name记录修改的字段名,old_value记录旧值,new_value记录新值。change_time记录修改的时间。

4. 当需要查询某条数据的修改历史时,只需要根据主键id,在A_ChangeLog表中查询出对应该条记录在何时修改了哪些字段即可。

5. 如果需要查询历史记录的一些统计信息,例如当天有多少条数据被修改了,某个字段在一段时间内被修改的次数等,可以通过SQL语句来实现。

复用性的解决方案,给我一个范例,方案如下:框架是springboot,写一个自定义注解,然后去拦截所有打到自定义注解的方法,在保存之前查询原值,保存之后把原值和新值做对比,最后去保存这个变更记录

以下是一个范例:

  1. 首先,我们在项目的代码中创建一个自定义注解@MyLog,用于标注需要记录操作日志的方法。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
    String value() default "";
}
  1. 创建一个拦截器实现类LogInterceptor, 拦截所有打了@MyLog注解的方法,在方法执行之前和之后进行操作,获取要记录日志的类、方法名称和参数,以及执行前后的数据变化信息,并将这些信息保存到日志表中。
@Aspect
@Component
public class LogInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);

    private ThreadLocal<Long> time = new ThreadLocal<>();

    @Autowired
    private LogService logService;

    @Pointcut("@annotation(com.example.demo.annotation.MyLog)")
    public void pointcut() {}

    @Before("pointcut()")
    public void before(JoinPoint joinPoint) {
        time.set(System.currentTimeMillis());
        logger.info("================== Start ==================");
        logger.info("Class Method      : {}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        logger.info("Request Args      : {}", Arrays.toString(joinPoint.getArgs()));
    }

    @AfterReturning(returning = "ret", pointcut = "pointcut()")
    public void afterReturning(Object ret) {
        logger.info("Response          : {}", ret);
        logger.info("================== End, Time Used: {}ms ==================", System.currentTimeMillis()-time.get());
    }

    @AfterThrowing(pointcut = "pointcut()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Throwable e) {
        logger.info("================== Error ==================");
        logger.info("Class Method      : {}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        logger.info("Request Args      : {}", Arrays.toString(joinPoint.getArgs()));
        logger.error("Error Message     : {}", e.getMessage());
        logger.info("================== End ==================");
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) {
        try {
            long startTime = System.currentTimeMillis();
            Object result = pjp.proceed();
            long elapsedTime = System.currentTimeMillis() - startTime;
            saveLog(pjp, elapsedTime, result);
            return result;
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    private void saveLog(ProceedingJoinPoint jp, long elapsedTime, Object result) throws Exception {
        MethodSignature signature = (MethodSignature) jp.getSignature();
        String methodName = signature.getName();
        String className = jp.getTarget().getClass().getSimpleName();
        Object[] args = jp.getArgs();

        // 获得业务实体类型
        if (args.length > 0) {
            Class<?> clazz = args[0].getClass();

            // 查询原始数据
            String originalData = "";
            String primaryKey = "";
            // 这里需要根据实际业务情况查询原始数据, 例如:
            // primaryKey = ((Entity)args[0]).getId();
            // originalData = queryOriginalData(primaryKey, clazz);

            // 执行新数据
            String newData = JSON.toJSONString(args[0]);

            // 记录操作日志
            LogEntity logEntity = new LogEntity(className, methodName, originalData, newData, primaryKey);
            logEntity.setTime(new Date());
            // 这里需要保存记录到数据库表
            // logService.save(logEntity);
        }
    }
}  
  1. 在需要记录日志的方法前加上@MyLog注解即可。
@MyLog
public void update(User user) {
    // 更新用户信息到数据库表
    userDao.update(user);
}

这个方案的实现:在AOP的方法拦截器中,获取到请求的类名、方法名称和参数列表。在执行业务前,从数据库中查询实体原始数据,在执行业务后,获取实体修改后的数据,并将它们进行对比,记录变更历史并保存到数据库表中。

如果我在业务数据保存成功了,但是在对比或者保存变更记录的时候出现了异常,这个时候怎么办要不要进行重试,要不要进行回滚?

如果在保存变更记录时出现异常,可以尝试进行重试操作,如果重试多次后还是无法保存成功,建议进行回滚操作。具体的实现方法可以通过try-catch块加事务来实现。以下是一个简单的相关代码示例:

@Service
@Transactional(rollbackFor = Exception.class)
public class LogService {

    @Autowired
    private LogDao logDao;

    public void save(LogEntity logEntity) {
        int retryTimes = 3;
        boolean isSuccess = false;
        Exception exception = null;
        for (int i = 0; i < retryTimes; i++) {
            try {
                logDao.save(logEntity);
                isSuccess = true;
                break;
            } catch (Exception e) {
                logger.error("Save log failed, retry: {}, error message: {}", i+1, e.getMessage());
                exception = e;
            }
        }
        if (!isSuccess) {
            throw new RuntimeException("Save log failed after retrying " + retryTimes + " times.", exception);
        }
    }
}

在这个示例中,我们定义了一个重试次数的变量retryTimes,然后在循环中进行多次保存操作。如果保存成功了,直接退出循环;如果保存失败,则记录错误信息,进行重试操作,重试操作未成功,则抛出运行时异常并回滚事务。

==============================================================

小知识

private static final Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);
这个属性什么意思,有什么作用?有用吗

这个属性定义了一个静态Logger对象,用于记录系统日志信息。Logger对象是Slf4j框架提供的,可以使用其它日志框架的实现(如Logback、Log4j)。

在代码中使用Logger对象可以记录系统的日志信息,例如记录系统错误信息、调试信息等,以便于后续的查看和分析。通常我们会将不同的日志信息记录到不同的文件中,以便于独立维护和查看。另外,Slf4j框架可以根据不同的日志输出级别(如调试、信息、警告、错误)来对日志信息进行分类输出,从而方便程序员进行日志分析和问题排查。

在开发中,Logger对象是非常常用的,可以对系统运行状态进行全面监控和评估,对于应用程序的稳定性、性能优化以及问题排查都非常有用。需要注意的是,在记录日志信息时,应该遵循日志级别化、减少日志量、格式统一等良好习惯,以提高日志信息的可读性和性能。

记录日志通常使用Logger的不同方法,可以根据级别的不同选择不同的方法,常用的方法有:

  • logger.trace(String msg): 记录跟踪信息,一般用于诊断问题,这个级别的日志一般不会送入生产环境。
  • logger.debug(String msg): 记录调试信息,记录一些运行时的调试信息,一般用于开发和测试阶段,正式发布的版本中这个日志级别一般不会记录。
  • logger.info(String msg): 记录一般性信息,记录一些非错误性质的信息,如系统启动信息、访问日志等。
  • logger.warn(String msg): 记录警告信息,表示可能会出现一些问题,但并不影响程序的运行,可以通过修改程序避免出现更严重的问题。
  • logger.error(String msg): 记录错误信息,表示出现了一些错误,需要相关人员进一步排查问题。

下面给出一个范例:

public class DemoController {
    
    private static final Logger logger = LoggerFactory.getLogger(DemoController.class);

    @RequestMapping("/demo")
    public String demo() {
        logger.info("进入demo方法");
        try {
            // do something
            logger.info("执行成功,返回结果");
            return "success";
        } catch (Exception e) {
            logger.error("执行失败,抛出异常", e);
            return "error";
        }
    }
}

在这个例子中,我们在DemoController中定义了一个静态Logger对象,并在demo方法中记录了info和error级别的日志信息。logger.info方法记录了“进入demo方法”和“执行成功,返回结果”两个信息,表示进入方法和方法结束时的状态;logger.error方法记录了“执行失败,抛出异常”,并将异常信息e作为参数输出,表示方法执行发生错误的原因。通过这种方式,我们可以了解系统的运行状态,以便于进行调试和排查问题。

这个记录我可以通过属性在程序里面调出来打印吗?

可以,通过设置日志级别和输出目标,可以将日志信息输出到不同的地方,如控制台、文件、数据库等。Slf4j框架提供了多种输出日志信息的方式,常用的有以下几种:

  1. 控制台输出

将日志信息输出到控制台,可以通过以下配置来实现:

# 配置日志级别
logging.level.com.example=info

# 配置控制台输出
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) [%thread] %logger{50} - %msg%n

这里的配置是基于Logback实现的,logging.pattern.console表示控制台输出的格式,%d、%highlight、%level等是占位符,详细的占位符可以在Logback的官网上查看。

  1. 文件输出

将日志信息输出到文件中,可以通过以下配置来实现:

# 配置日志级别
logging.level.com.example=info

# 配置文件输出
logging.file.name=mylog.log
logging.file.path=/var/log/myapp

这个配置表示将日志信息输出到/var/log/myapp/mylog.log文件中。

  1. 使用注解输出日志

在类或方法上添加注解,使其在运行时输出日志信息,可以通过以下方式实现:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/demo")
public class DemoController {

    private static final Logger logger = LoggerFactory.getLogger(DemoController.class);

    @RequestMapping("/hello")
    @Loggable(LogLevel.INFO) // 添加注解
    public String hello() {
        logger.info("进入hello方法");
        return "hello";
    }
}

在这里,我们定义了一个@Loggable注解,并在DemoController的hello方法上添加该注解,当运行到这个方法时,会输出日志信息。@Loggable注解的实现可以参考相关的框架或自己编写。

我可以将日志以json形式打给前端吗?

重试

复制

可以,将日志以JSON格式输出,可以更方便地让前端解析和展示日志信息。在Java中,需要引入Jackson或Gson等JSON处理库,将日志信息转换为JSON格式后输出即可。

以下是一个使用Jackson的示例:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/log")
public class LogController {

    private static final Logger logger = LoggerFactory.getLogger(LogController.class);

    @GetMapping("/json")
    public String jsonLog() throws JsonProcessingException {
        // 模拟系统运行时产生的日志信息
        logger.info("系统运行正常");
        logger.warn("警告信息:磁盘空间不足");
        logger.error("错误信息:数据读取失败");

        // 将日志信息转换为JSON格式输出
        ObjectMapper mapper = new ObjectMapper();
        String jsonLog = mapper.writeValueAsString(logger);

        return jsonLog;
    }
}

这个例子中我们定义了一个LogController来处理日志相关的请求,其中的jsonLog方法将系统运行时的日志信息转换为JSON格式的字符串并返回给前端。需要注意的是,在实际生产中,为了避免日志信息过大导致前端无法正常显示,可以根据机器性能和网络带宽等因素调整日志输出的级别和格式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值