基于SqlSugar的开发框架循序渐进介绍(8)-- 在基类函数封装实现用户操作日志记录
在我们对数据进行重要修改调整的时候,往往需要跟踪记录好用户操作日志。一般来说,如对重要表记录的插入、修改、删除都需要记录下来,由于用户操作日志会带来一定的额外消耗,因此我们通过配置的方式来决定记录那些业务数据的重要调整。本篇随笔介绍如何在基于SqlSugar的开发框架中,实现对用户操作日志记录的配置设置,以及根据配置信息自动实现用户操作日志记录。
1、用户操作日志记录的配置处理
前面提到,由于用户操作日志会带来一定的额外消耗,因此我们通过配置的方式来决定记录那些业务数据的重要调整。
首先我们在系统中定义一个用户操作日志记录表和一个操作日志配置信息表,系统根据配置进行记录重要的修改调整信息。
列表展示信息如下所示
有了这些信息记录,我们可以在操作基类函数中,通过判断SqlSugar实体类信息中的是否插入、更新、删除的重要设置,可以决定记录它们那些操作日志信息。
下面列表记录了对一些表的增加、修改、删除、以及一些重要的系统操作日志信息,如“密码重置”、“密码修改”、“用户过期设置”等操作日志。
2、在基类中实现用户操作日志记录处理
上面界面展示了如何通过配置,自动记录用户对某业务的相关重要操作记录的界面。系统之所以能够进行相关的信息记录,是在基类函数中定义了相关的逻辑,根据配置逻辑,把插入对象的详细信息、修改对象的变化比记录、删除对象的详细信息进行写入,以及对一些重要的处理,如重置密码等,进行自定义的信息记录的。
下面我们来看看如何在基类中处理这些操作。
例如,我们在删除记录的时候,有时候接收的是实体类的ID,有时候接收的是实体类,那么对于这些条件,我们相应的进行日志处理,如下代码所示。
/// <summary>
/// 删除指定ID的对象
/// </summary>
/// <param name="id">记录ID</param>
/// <returns></returns>
public virtual async Task<bool> DeleteAsync(TEntity input)
{
await OnOperationLog(input, OperationLogTypeEnum.删除);
return await EntityDb.DeleteAsync(input);
} /// <summary>
/// 删除指定ID的对象
/// </summary>
/// <param name="id">记录ID</param>
/// <returns></returns>
public virtual async Task<bool> DeleteAsync(TKey id)
{
await OnOperationLog(id, OperationLogTypeEnum.删除);
return await EntityDb.DeleteByIdAsync(id);
}
其中我们根据日志的操作,定义一个枚举的对象,如下所示。
/// <summary>
/// 操作日志的枚举类型
/// </summary>
public enum OperationLogTypeEnum
{
增加,
删除,
修改
}
对于删除记录的Id,我们需要把它转换为对应的实体类,然后进行记录的。
/// <summary>
/// 统一处理实体类的日志记录
/// </summary>
/// <param name="id">实体对象Id</param>
/// <param name="logType">记录类型</param>
/// <returns></returns>
protected override async Task OnOperationLog(TKey id, OperationLogTypeEnum logType)
{
var enableLog = await CheckOperationLogEnable(logType);
if (enableLog)
{
var input = await this.EntityDb.GetByIdAsync(id);
if (input != null)
{
string note = JsonConvert.SerializeObject(input, Formatting.Indented);
await AddOperationLog(logType.ToString(), note);
}
}
await Task.CompletedTask;//结束处理
}
其中 CheckOperationLogEnable 就是用来判断是否存在指定操作类型的配置信息的,如果存在,那么就记录操作日志。
我们是根据实体类的全名进行判断,如果存在指定的操作设置,就返回True,如下所示。(刚好基类中可以判断泛型约束TEntity的全名)
/// <summary>
/// 判断指定的类型(增加、删除、修改)是否配置启用
/// </summary>
/// <param name="logType">指定的类型(增加、删除、修改)</param>
/// <returns></returns>
protected async Task<bool> CheckOperationLogEnable(OperationLogTypeEnum logType)
{
var result = false; string tableName = typeof(TEntity).FullName;//表名称或者实体类全名
var settingInfo = await this._logService.GetOperationLogSetting(tableName);
if (settingInfo != null)
{
if (logType == OperationLogTypeEnum.修改)
{
result = settingInfo.UpdateLog > 0;
}
else if (logType == OperationLogTypeEnum.增加)
{
result = settingInfo.InsertLog > 0;
}
else if (logType == OperationLogTypeEnum.删除)
{
result = settingInfo.DeleteLog > 0;
}
}
return result;
}
对于插入记录,我们也可以同时进行判断并处理日志信息。
/// <summary>
/// 创建对象
/// </summary>
/// <param name="input">实体对象</param>
/// <returns></returns>
public virtual async Task<bool> InsertAsync(TEntity input)
{
SetIdForGuids(input);//如果Id为空,设置有序的GUID值 await OnOperationLog(input, OperationLogTypeEnum.增加);//判断并记录日志
return await EntityDb.InsertAsync(input);
}
对于更新原有记录,它也只需要接收更新前的对象,然后进行判断处理即可。
/// <summary>
/// 更新对象
/// </summary>
/// <param name="input">实体对象</param>
/// <returns></returns>
public virtual async Task<bool> UpdateAsync(TEntity input)
{
SetIdForGuids(input);//如果Id为空,设置有序的GUID值 await OnOperationLog(input, OperationLogTypeEnum.修改);//判断并记录日志
return await EntityDb.UpdateAsync(input);
}
比较两者,我们需要提供一个操作日志方法重载用于记录信息即可。
由于修改的信息,我们需要对比两个不同记录之间的差异信息,这样我们才能友好的判断那些信息变化了。也就是更新前后两个实体对象之间的属性差异信息,需要获取出来。
/// <summary>
/// 统一处理实体类的日志记录
/// </summary>
/// <param name="input">实体对象</param>
/// <param name="logType">记录类型</param>
/// <returns></returns>
protected override async Task OnOperationLog(TEntity input, OperationLogTypeEnum logType)
{
var enableLog = await CheckOperationLogEnable(logType);
if (enableLog && input != null)
{
if (logType == OperationLogTypeEnum.修改)
{
var oldInput = await this.EntityDb.GetByIdAsync(input.Id);
//对于更新记录,需要判断更新前后两个对象的差异信息
var changeNote = oldInput.GetChangedNote(input); //计算差异的部分
if (!string.IsNullOrEmpty(changeNote))
{
await AddOperationLog(logType.ToString(), changeNote);
}
}
else
{
//对于插入、删除的操作,只需要记录对象的信息
var note = JsonConvert.SerializeObject(input, Formatting.Indented);
await AddOperationLog(logType.ToString(), note);
}
}
await Task.CompletedTask;//结束处理
}
而对于差异信息,我能定义一个扩展函数来处理他们的差异信息,如下所示。
/// <summary>
/// 对象属性的处理操作
/// </summary>
public static class ObjectExtensions
{
/// <summary>
/// 对比两个属性的差异信息
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <param name="val1">对象实例1</param>
/// <param name="val2">对象实例2</param>
/// <returns></returns>
public static List<Variance> DetailedCompare<T>(this T val1, T val2)
{
var propertyInfo = val1.GetType().GetProperties();
return propertyInfo.Select(f => new Variance
{
Property = f.Name,
ValueA = (f.GetValue(val1, null)?.ToString()) ?? "", //确保不为null
ValueB = (f.GetValue(val2, null)?.ToString()) ?? ""
})
.Where(v => !v.ValueA.Equals(v.ValueB)) //调用内置的Equals判断
.ToList();
} /// <summary>
/// 把两个对象的差异信息转换为JSON格式
/// </summary>
/// <typeparam name="T">对象类型</typeparam>
/// <param name="val1">对象实例1</param>
/// <param name="val2">对象实例2</param>
/// <returns></returns>
public static string GetChangedNote<T>(this T oldVal, T newVal)
{
var specialList = new List<string> { "edittime", "createtime", "lastupdated" };
var list = DetailedCompare<T>(oldVal, newVal);
var newList = list.Select(s => new { Property = s.Property, OldValue = s.ValueA, NewValue = s.ValueB })
.Where(s=> !specialList.Contains(s.Property.ToLower())).ToList();//排除某些属性 string note = null;
if (newList?.Count > 0)
{
//增加一个ID属性记录显示
var id = EntityHelper.GetEntityId(oldVal)?.ToString();
newList.Add(new { Property = "Id", OldValue = id, NewValue = id }); note = JsonConvert.SerializeObject(newList, Formatting.Indented);
}
return note;
} public class Variance
{
public string Property { get; set; }
public string ValueA { get; set; }
public string ValueB { get; set; }
}
}
这样我们通过LINQ把两个对象的差异信息生成,就可以用来记录变更操作的信息了,最终可以获得类似下面界面提示的差异信息。
也就是获得类似字符串的差异信息。
[
{
"Property": "PID",
"OldValue": "-1",
"NewValue": "0"
},
{
"Property": "OfficePhone",
"OldValue": "",
"NewValue": "18620292076"
},
{
"Property": "WorkAddr",
"OldValue": "广州市白云区同和路**小区**号",
"NewValue": "广州市白云区同和路330号君立公寓B栋1803房"
},
{
"Property": "Id",
"OldValue": "1",
"NewValue": "1"
}
]
最后的属性Id,是我们强行加到变化列表中的,因为不记录Id的话,不清楚那个记录变更了。
这样我们就实现了增删改的重要操作的记录,并且由于是基类实现,我们只需要在系统中配置决定哪些业务类需要记录即可自动实现重要日志的记录。
另外,我们在类别中还发现了其他一些不同类别的重要操作日志,如重置密码、修改密码、用户过期设置等,这些操作我们提供接口给这些处理调用即可。
/// <summary>
/// 设置用户的过期与否
/// </summary>
/// <param name="userId">用户ID</param>
/// <param name="expired">是否禁用,true为禁用,否则为启用</param>
public async Task<bool> SetExpire(int userId, bool expired)
{
bool result = false;
var info = await this.GetAsync(userId);
if (info != null)
{
info.IsExpire = expired;
result = await this.UpdateAsync(info);
if (result)
{
//记录用户修改密码日志
string note = string.Format("{0} {1}了用户【{2}】的账号", this.CurrentApiUser.FullName, expired ? "禁用" : "启用", info.Name);
await base.AddOperationLog("用户过期设置", note);
}
}
return result;
}
其中 AddOperationLog 就是我们调用基类插入指定类型和日志信息的记录的,通过自定义类型和自定义日志信息,可以让我们弹性化的处理一些重要日志记录。
系列文章:
《基于SqlSugar的开发框架的循序渐进介绍(1)--框架基础类的设计和使用》
《基于SqlSugar的开发框架循序渐进介绍(2)-- 基于中间表的查询处理》
《基于SqlSugar的开发框架循序渐进介绍(3)-- 实现代码生成工具Database2Sharp的整合开发》
《基于SqlSugar的开发框架循序渐进介绍(4)-- 在数据访问基类中对GUID主键进行自动赋值处理》
《基于SqlSugar的开发框架循序渐进介绍(5)-- 在服务层使用接口注入方式实现IOC控制反转》
《基于SqlSugar的开发框架循序渐进介绍(6)-- 在基类接口中注入用户身份信息接口》
《基于SqlSugar的开发框架循序渐进介绍(7)-- 在文件上传模块中采用选项模式【Options】处理常规上传和FTP文件上传》
《基于SqlSugar的开发框架循序渐进介绍(8)-- 在基类函数封装实现用户操作日志记录》
基于SqlSugar的开发框架循序渐进介绍(8)-- 在基类函数封装实现用户操作日志记录的更多相关文章
- 基于SqlSugar的开发框架循序渐进介绍(12)-- 拆分页面模块内容为组件,实现分而治之的处理
在早期的随笔就介绍过,把常规页面的内容拆分为几个不同的组件,如普通的页面,包括列表查询.详细资料查看.新增资料.编辑资料.导入资料等页面场景,这些内容相对比较独立,而有一定的代码量,本篇随笔介绍基于V ...
- 基于SqlSugar的开发框架循序渐进介绍(13)-- 基于ElementPlus的上传组件进行封装,便于项目使用
在我们实际项目开发过程中,往往需要根据实际情况,对组件进行封装,以更简便的在界面代码中使用,在实际的前端应用中,适当的组件封装,可以减少很多重复的界面代码,并且能够非常简便的使用,本篇随笔介绍基于El ...
- 基于SqlSugar的开发框架循序渐进介绍(14)-- 基于Vue3+TypeScript的全局对象的注入和使用
刚完成一些前端项目的开发,腾出精力来总结一些前端开发的技术点,以及继续完善基于SqlSugar的开发框架循序渐进介绍的系列文章,本篇随笔主要介绍一下基于Vue3+TypeScript的全局对象的注入和 ...
- 基于SqlSugar的开发框架循序渐进介绍(17)-- 基于CSRedis实现缓存的处理
在一个应用系统的开发框架中,往往很多地方需要用到缓存的处理,有些地方是为了便于记录用户的数据,有些地方是为了提高系统的响应速度,如有时候我们在发送一个短信验证码的时候,可以在缓存中设置几分钟的过期时间 ...
- 基于SqlSugar的开发框架循序渐进介绍(20)-- 在基于UniApp+Vue的移动端实现多条件查询的处理
在做一些常规应用的时候,我们往往需要确定条件的内容,以便在后台进行区分的进行精确查询,在移动端,由于受限于屏幕界面的情况,一般会对多个指定的条件进行模糊的搜索,而这个搜索的处理,也是和前者强类型的条件 ...
- 基于SqlSugar的开发框架循序渐进介绍(21)-- 在工作流列表页面中增加一些转义信息的输出,在后端进行内容转换
有时候,为了给前端页面输出内容,有时候我们需要准备和数据库不一样的实体信息,因为数据库可能记录的是一些引用的ID或者特殊字符,那么我们为了避免前端单独的进行转义处理,我们可以在后端进行统一的格式化后再 ...
- 基于SqlSugar的开发框架循序渐进介绍(3)-- 实现代码生成工具Database2Sharp的整合开发
我喜欢在一个项目开发模式成熟的时候,使用代码生成工具Database2Sharp来配套相关的代码生成,对于我介绍的基于SqlSugar的开发框架,从整体架构确定下来后,我就着手为它们量身定做相关的代码 ...
- 基于SqlSugar的开发框架循序渐进介绍(4)-- 在数据访问基类中对GUID主键进行自动赋值处理
我们在设计数据库表的时候,往往为了方便,主键ID一般采用字符串类型或者GUID类型,这样对于数据库表记录的迁移非常方便,而且有时候可以在处理关联记录的时候,提前对应的ID值.但有时候进行数据记录插入的 ...
- 基于SqlSugar的开发框架循序渐进介绍(5)-- 在服务层使用接口注入方式实现IOC控制反转
在前面随笔,我们介绍过这个基于SqlSugar的开发框架,我们区分Interface.Modal.Service三个目录来放置不同的内容,其中Modal是SqlSugar的映射实体,Interface ...
随机推荐
- 什么是3D建模?
一.3D建模是什么 将所见所想用立体三维的方式通过计算机技术表现出来,这就是3D建模,比如你看到了一个可爱呆萌的卡通人物又或是华丽酷炫的变形金刚,总之就是你认为美好的立体实物 你想将它用计算机技术完美 ...
- ubuntu下Docker配置阿里云镜像加速
1.确认正确安装好docker,登录阿里云,打开如下界面 https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors 2.复制下面那段代码, ...
- python---用顺序表实现双端队列
class Dqueue(object): """双端队列""" def __init__(self): self.__list = [] ...
- oracle三个连接配置文件 listener.ora、sqlnet.ora、tnsnames.ora
关于PLSQL连接ORACLE配置字符串 首先要讲一下下面的一些知识 1.ORACLE_SID:(ORACLE SYSTEM IDENTIFIER) Oracle实例是由SGA和一组后台进程组成的,实 ...
- java实现MD5文件加密
package me.zhengjie.modules.logdump.util; import java.io.FileInputStream; import java.io.IOException ...
- Model, HttpServletRequest, ModelMap区别
看了spring mvc的底层会发现,model数据最终还是写到HttpServletRequest属性中,只是model的写法更体现了MVC思想减少各层间耦合 写法: 1.request.setAt ...
- maven jar包冲突问题
之前好端端的项目没做任何改动maven依赖就报红?jar包冲突?不要慌,问题不大. idea file里面点击invalidate Caches/Restart清空项目缓存并重启,ok解决问题.
- ADO访问Excel
需要安装驱动:Microsoft Access Database Engine,可搜索下载,有64位和32位之分. 随便新建一个后缀名为udl的文件,双击打开.注意,现如今一般都是64位系统,双击打开 ...
- 记一次MySql重置root密码无效
说起这个事情吧也相对来说比较尴尬,对于一个技术来说忘记密码然后找回密码都是相当简单的一个事情,但是在生产环境中没有保存记录只能是自己的失职,尴尬就尴尬在明明重置成功了却没有生效,弄得好几个工程师在哪里 ...
- 记一次docker安装成功,启动失败的原因
问题 按照错误提示,先查看docker的状态: systemctl status docker 可以看到,非常明显的一行大红字:Failed to start Docker Application C ...