一、问题的发现:为什么审计日志会丢数据?

1.1 踩坑的现场

前阵子公司的OA后台系统出了个小问题,管理员操作审批的时候填了很长的操作说明,结果后来查审计日志,发现那行备注只显示了前面一小段,后半截全没了。管理员回忆说当时写的内容是“这个审批涉及跨部门协调,需要等市场部确认物料到货时间,还要和财务部核对预算,预计本周内完成,请相关人员先别催促,有问题直接找我”,但日志里只显示到“这个审批涉及跨部门协调”后面就没了,这就像你发微博超过140字被强制截断一样,关键信息丢了,排查问题的时候根本不知道当时的具体决策,差点耽误了事。

1.2 常见的触发场景

这种情况不止OA系统会遇到,只要是用ABP框架做的后台管理系统,比如电商的订单操作日志、CRM的客户跟进记录,都可能踩这个坑。因为ABP框架默认的审计日志字段长度设置得比较保守,大概率是几百个字符,而很多业务场景下需要留的操作说明、错误详情、业务备注都超过这个长度,只要写入的内容一超长,数据库就会直接咔嚓掉后半截,不会报错,只会默默丢数据,这种隐性问题特别难发现。

二、问题的根因分析:到底是谁在偷偷丢数据?

2.1 ABP框架的默认设置

ABP框架自带的审计日志功能是用来记录所有用户操作的,方便排查问题和留痕。框架的默认配置里,对应审计日志实体的“Comments”字段(就是存操作备注的地方),数据库里的列定义一般是长度500的varchar或者nvarchar,应用层也会限制这个字段最多存500个字符,只要超过这个长度,写入数据库的时候就会被截断。这个设置是框架的通用默认,适合大部分简单场景,但对需要长备注的业务来说就不够用了。

2.2 重现问题的示例

技术栈:.NET 6 + ABP vNext 5.3 + SQL Server 先写一段模拟代码,演示如果直接写超长内容到审计日志会发生什么:

using Volo.Abp.Auditing;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Uow;

// 模拟一个后台服务,用来写审计日志
public class AuditTestService : ITransientDependency
{
    private readonly IAuditingManager _auditingManager;

    public AuditTestService(IAuditingManager auditingManager)
    {
        _auditingManager = auditingManager;
    }

    [UnitOfWork]
    public async Task WriteLongCommentAsync()
    {
        // 模拟超长的操作备注,大概1500个字符
        var longComment = new string('A', 1500);

        // 开始审计日志会话
        using (var scope = _auditingManager.BeginScope())
        {
            // 获取当前审计日志
            var auditLog = scope.AuditLog;
            // 给Comments字段赋值超长内容
            auditLog.Comments = longComment;

            // 提交会话,写入数据库
            await scope.CompleteAsync();
        }
    }
}

这段代码里,我们故意给Comments字段写了1500个A,而ABP默认的长度是500,写入数据库后,查询出来的Comments就只有前500个A,后面的都丢了,这就是问题的核心原因:字段长度不匹配。

三、解决方法:怎么调整字段才不会丢数据?

3.1 调整ABP的审计日志实体配置

不用改ABP的源码,只需要在我们自己的项目里重写审计日志的配置,覆盖默认的设置就行,这样框架升级的时候不会被覆盖。示例代码如下: 技术栈:.NET 6 + ABP vNext 5.3 + SQL Server

using Volo.Abp.AuditLogging;
using Volo.Abp.EntityFrameworkCore.Modeling;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

// 自定义审计日志配置类,替换ABP的默认配置
public class CustomAuditLogConfig : EntityTypeConfiguration<AuditLog>
{
    public override void Configure(EntityTypeBuilder<AuditLog> builder)
    {
        // 先调用ABP的默认配置,保证其他字段的设置不变
        base.Configure(builder);

        // 重点:调整Comments字段的长度,从默认的500改成2000,足够存下长备注
        builder.Property(log => log.Comments)
               .HasMaxLength(2000) // 这里可以根据业务需要调整,比如最多要1000就设1000
               .IsRequired(false); // 这个字段不是必填的,所以设成false

        // 如果业务需要存更长的内容(比如超过2000),也可以用max类型,注意max类型的限制
        // builder.Property(log => log.Comments).HasColumnType("nvarchar(max)");
    }
}

3.2 同步修改数据库结构

光改应用层的配置还不够,还要把数据库里的AuditLogs表的对应字段长度改了,不然还是存不下。这里用EF Core的迁移工具来同步,避免手动改数据库出错,命令如下:

# 创建迁移脚本,记录这次字段长度的修改
dotnet ef migrations add UpdateAuditLogCommentsLength
# 应用迁移到本地数据库,同步结构
dotnet ef database update

3.3 调整后的效果

改完之后,再用刚才的测试代码写1500个A,查询出来的Comments就是完整的1500个,不会被截断了,关键信息终于能完整存下来了。

四、技术的优缺点:这么调整有什么好坏?

4.1 优点

第一个是数据不会丢,不管业务备注多长,只要不超过设置的长度(或者用max),都能完整存下来,留痕的信息更全面,排查问题的时候不会因为丢了半截备注而卡壳;第二个是不需要改ABP的源码,升级框架的时候不会出问题,符合ABP的扩展原则;第三个是可以灵活调整长度,根据自己业务的需求设合适的值,不是一概而论。

4.2 缺点

如果把字段改成太长的,比如直接用nvarchar(max),会带来两个问题:一个是数据库的存储空间会变大,存大字段比存小字段占更多空间;另一个是数据库的查询性能会下降,因为大字段不适合建索引,要是经常按Comments字段搜索的话,效率会很低。所以不能随便改太长,要平衡需求和性能。

五、注意事项:调整的时候要避开这些坑

5.1 不要盲目改成max长度

之前说的,要是业务不需要那么长的话,就设刚好够用的长度,比如一般的操作备注最多1000字,就设1000,不用设max,不然浪费空间还影响性能。比如我们公司的OA系统,操作备注最多用到800字,所以设1000就够了。

5.2 先备份再改数据库

如果数据库里已经有旧的审计日志数据,改字段长度之前一定要先备份数据库,万一改的时候出问题,比如旧数据超过新的长度,会导致迁移失败,备份了就可以回滚。另外,最好在测试环境先测一遍,没问题再上生产。

5.3 检查其他关联配置

比如ABP的其他模块有没有用到审计日志的字段,有没有其他地方限制了长度,比如前端的输入框,要是前端也限制了最多输入500字,就算后端改了,用户还是写不了太长的,所以要前后端配合。还有,其他审计字段比如Exception(存错误详情的),如果也有长内容的需求,也要单独调整,不要只改Comments字段。

六、总结

这次踩的坑,其实就是框架默认设置和实际业务需求不匹配的常见问题。遇到这种问题,不用慌,只要找到对应的实体配置,调整字段长度,再同步数据库,就能解决。核心是要平衡数据完整性和数据库性能,不能为了存下所有内容就盲目用大字段,也不能因为怕麻烦就用默认设置导致数据丢了。以后再遇到类似的问题,比如其他框架的默认字段长度不够,思路也是一样的:先找根因(字段不匹配),再调整配置,再同步数据库,最后测试效果。