用户操作日志记录字段修改前后值

你可能遇到这样的需求,要详细记录用户的操作日志,像下面这样:

用户张三将年龄从“20”改为“21”
用户张三将爱好从“篮球”改为“足球”

 通常,用户可以一次改多个字段,然后一次性保存,这些字段的数据修改记录要分别保存。

这样的日志需要知道以下数据:

  1. 用户修改了数据库中什么字段
  2. 这个字段对应的中文名是什么
  3. 这个字段原来的值是什么
  4. 这个字段新值是什么

这几个问题里,1和4是闭着眼都能搞明白的。

问题2,字段的中文名可以用一个枚举类把英文和中文对应起来,但是如果字段很多的话,很繁琐,然后想起了注解,对字段添加注解,是否可以获取字段的中文意思呢?

问题3,这个字段原来的值我们也是知道的,但如何保存呢?我首先想到的是:用户修改时将修改前的旧值和新值从前端一起传过来,但是这样有安全问题,因为用户可以按F12在浏览器修改页面上的旧值,所以需要在修改前从数据库先查询出所有字段的旧值,然后与新值依次对比,从中找到用户修改了哪些字段。

网上搜索一番,看到这篇文章,得到启示,下面按文章思路实现,并加以改进。

自定义一个注解类来标注字段对应的中文。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 字段中文别名
 * @author test
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldAlias {
    String value() default "";
}

然后,类需要记录日志的字段上加注解:

public class User {
	
	private int userId;
	
	@FieldAlias("姓名")
	private String name;
	
	@FieldAlias("年龄")
	private int age;
	
	@FieldAlias("爱好")
	private String hobby;
	
	public User(){
		
	}
	
	public User(int userId, String name, int age) {
		this.userId = userId;
		this.name = name;
		this.age = age;
	}

    // 这里省略get set 方法
}
/**
 * 两个对象差异-字段新旧值
 * @author test
 */
public class FieldDiff {

	/**
	 * 字段英文名
	 */
	private String fieldENName;
	
	/**
	 * 字段中文名
	 */
	private String fieldCNName;
	
	/**
	 * 旧值
	 */
	private Object oldValue;
	
	/**
	 * 新值
	 */
	private Object newValue;
	
	
	public FieldDiff(String fieldENName, String fieldCNName, Object oldValue, Object newValue) {
		this.fieldENName = fieldENName;
		this.fieldCNName = fieldCNName;
		this.oldValue = oldValue;
		this.newValue = newValue;
	}

    // 这里省略get set 方法

	@Override
	public String toString() {
		String oldVal = this.oldValue == null ? "" : this.oldValue.toString();
        String newVal = this.newValue == null ? "" : this.newValue.toString();
		return "将 " + this.fieldCNName + " 从“" + oldVal + "” 修改为 “" + newVal + "”";
	}
	
}
import java.util.ArrayList;
import java.util.List;

/**
 * 两个对象差异
 * @author test
 */
public class BeanDiff {

	/**
	 * 所有差异字段list
	 */
	private List<FieldDiff> fieldDiffList = new ArrayList<>();
	
	public void addFieldDiff(FieldDiff fieldDiff) {
		this.fieldDiffList.add(fieldDiff);
	}

	public List<FieldDiff> getFieldDiffList() {
		return fieldDiffList;
	}

	public void setFieldDiffList(List<FieldDiff> fieldDiffList) {
		this.fieldDiffList = fieldDiffList;
	}
	
}
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


/**
 * 实例字段差异比较工具类
 * @author test
 */
public class BeanCompareUtils {
	
	private static final String INCLUDE = "INCLUDE";
	private static final String EXCLUDE = "EXCLUDE";
	private static final String FILTER_TYPE = "FILTER_TYPE";
	private static final String FILTER_ARRAY = "FILTER_ARRAY";
	
	// 存放过滤类型及过滤字段数组
	private static ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<>();
	
	public static void main(String[] args) {
		
		User oldUser = new User(1, "张三", 20);
		User newUser = new User(2, "李四", 21);
		oldUser.setMoney(1100.1);
		newUser.setMoney(2200.52);
		
		String[] fieldArray = new String[]{"age","name","hobby"};
		
		BeanDiff beanDiff = compareInclude(oldUser, newUser,fieldArray);
		
		List<FieldDiff> list = beanDiff.getFieldDiffList();
		if (list != null && list.size() > 0) {
			for (int i = 0; i< list.size(); i ++) {
				FieldDiff fieldDiff = list.get(i);
				System.out.println(fieldDiff.toString());
			}
		}
	}
	
	/**
	 * bean比较
	 * @param oldBean
	 * @param newBean
	 * @return
	 */
	public static BeanDiff compare(Object oldBean, Object newBean) {
		BeanDiff beanDiff = new BeanDiff();
		
		Class oldClass = oldBean.getClass();
		Class newClass = newBean.getClass();
		
		if (oldClass.equals(newClass)) {
			List<Field> fieldList = new ArrayList<>();
			fieldList = getCompareFieldList(fieldList, newClass);
			
			Map<String, Object> map = threadLocal.get();
			
			boolean needInclude = false;
			boolean needExclude = false;
			boolean hasArray = false;
			String[] fieldArray = null;
			
			if(map != null) {
				fieldArray = (String[])map.get(FILTER_ARRAY);
				String type = (String)map.get(FILTER_TYPE);
				
				if (fieldArray != null && fieldArray.length > 0) {
					// 数组排序
					Arrays.sort(fieldArray);
					hasArray = true;
					
					if (INCLUDE.equals(type)) {
						needInclude = true;
					} else if (EXCLUDE.equals(type)) {
						needExclude = true;
					}
				}
			}
			
			for (int i = 0; i < fieldList.size(); i ++) {
				Field field = fieldList.get(i);
				field.setAccessible(true);
				FieldAlias alias = field.getAnnotation(FieldAlias.class);
				
				try {
					Object oldValue = field.get(oldBean);
					Object newValue = field.get(newBean);
					
					if (hasArray) {
						// 二分法查找该字段是否被排除或包含
						int idx = Arrays.binarySearch(fieldArray, field.getName());
						
						// 该字段被指定排除或没有指定包含
						if ((needExclude && idx > -1) || (needInclude && idx < 0)) {
							continue;
						}
					}
					
					if (nullableNotEquals(oldValue, newValue)) {
						FieldDiff fieldDiff = new FieldDiff(field.getName(), alias.value(), oldValue, newValue);
						
						// 打印
						System.out.println(fieldDiff.toString());
						
						beanDiff.addFieldDiff(fieldDiff);
					}
					
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
			}
		}
		
		return beanDiff;
	}
	
	/**
	 * bean比较
	 * @param oldBean
	 * @param newBean
	 * @param includeFieldArray 需要包含的字段
	 * @return
	 */
	public static BeanDiff compareInclude(Object oldBean, Object newBean, String[] includeFieldArray) {
		Map<String, Object> map = new HashMap<>();
		map.put(FILTER_TYPE, INCLUDE);
		map.put(FILTER_ARRAY, includeFieldArray);
		threadLocal.set(map);
		
		return compare(oldBean, newBean);
	}
	
	/**
	 * bean比较
	 * @param oldBean
	 * @param newBean
	 * @param excludeFieldArray 需要排除的字段
	 * @return
	 */
	public static BeanDiff compareExclude(Object oldBean, Object newBean, String[] excludeFieldArray) {
		Map<String, Object> map = new HashMap<>();
		map.put(FILTER_TYPE, EXCLUDE);
		map.put(FILTER_ARRAY, excludeFieldArray);
		threadLocal.set(map);
		
		return compare(oldBean, newBean);
	}
	
	
	/**
	 * 获取需要比较的字段list
	 * @param fieldList
	 * @param clazz
	 * @return
	 */
	private static List<Field> getCompareFieldList(List<Field> fieldList, Class clazz) {
		Field[] fieldArray = clazz.getDeclaredFields();
		
		List<Field> list = Arrays.asList(fieldArray);
		
		for (int i = 0; i < list.size(); i ++) {
			Field field = list.get(i);
			FieldAlias alias = field.getAnnotation(FieldAlias.class);
			if (alias != null) {
				fieldList.add(field);
			}
		}
		
		Class superClass = clazz.getSuperclass();
		if (superClass != null) {
			getCompareFieldList(fieldList, superClass);
		}
		return fieldList;
	}
	
	
	/**
	 * 比较值是否不相等
	 * @param oldValue
	 * @param newValue
	 * @return
	 */
	private static boolean nullableNotEquals(Object oldValue, Object newValue) {
		
		if (oldValue == null && newValue == null) {
			return false;
		}

		if (oldValue != null && oldValue.equals(newValue)) {
			return false;
		}
		
		if (("".equals(oldValue) && newValue == null) || ("".equals(newValue) && oldValue == null)) {
			return false;
		}
		
		return true;
	}
	
}

  • 3
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
停车场管理系统 浙江农林大学地理信息科学171班 HMc、FZ、HHb、XHc、ZLl、CC小组 模块 模块名 命名空间 介绍 完成度 模型模块 Park.Model 提供数据库模型 99% 服务模块 Park.Service 提供业务处理方法 99% 管理模块 Park.Admin 提供管理页面供管理员对各种信息进行管理、查看和汇总 80% 用户模块 Park.Mobile 提供手机网站,供用户进行查看账户信息和停车场信息 80% 接口模块 Park.API 为停车场硬件设施和Park.Mobile提供API 80% 车位设计模块 Park.Designer 用于设计停车位地图。由于技术不够,故使用WPF作为设计器框架。 99% 测试模块 Park.Test 用于测试,非单元测试 - 日志 20200329 设计器 基本完成车位设计器的主要功能: 画板网格显示 鼠标绘制停车位、通道 停车区的选择 停车区的新增、删除、重命名 使用鼠标浏览画板 使用鼠标选取对象 配置文件的导入导出 配置文件的自动保存和恢复 20200330 设计器 将通道从矩形改为线 通道支持了非横纵方向 通道支持了朝左上方拉伸 通道支持按Shift进行约束 停车位新增支持旋转 鼠标悬浮样式改为图形显示阴影 支持了删除功能 20200331 管理端 搭建了对车主管理表格的基本页面 核心 增加了停车场业务处理相关方法 测试 增加了测试类 20200401 核心 增加了交易充业务处理相关方法 测试 增加了非会员进出、会员进出的测试方法 20200402 核心 为数据库添加了显式外键声明 管理端 基本完成车主管理表格 基本完成车位管理表格 20200403 核心 支持了从Json文件(设计器导出)导入停车位的功能 基本完成停车区地图的显示(To Bitmap) 20200404 管理端 显示了车主拥有的车辆数量、交易订单数量,点击车辆数量可以跳转到车辆表 提升了”表格模型“基类和js方法,方便之后的编写 基本完成了停车记录表格 20200406 管理端 完成交易记录表格 重新分离编写停车区和停车位管理页面 核心 增加了”墙体“模型 设计器 支持了”墙体“模型 设备接口 做好准备 20200407 管理端 基本完成模拟界面和逻辑 设备接口 基本完成门卫接口、车位传感器接口 20200410 管理端 新增了一个图表 修改数据库初始化代码,符合本系统 基本完成权限角色的分配 新增数据库管理页面 模型 新增了车主的注册时间、最后登录时间字段,新增车辆的准入字段 服务 分离了数据库创建与生成测试数据 进入停车场返回复合类型 20200501 手机 修改为前后端分离项目,前端使用Vue进行编写,后端使用Park.API 20200502 手机 基本完成主页 完成账户验证 完成车辆管理界面和查看停车记录 20200503 模型 新增Config类型,删除了定价策略的停车区字段 服务 将某些服务的参数CarOwner改为CarOwnerID 手机 完成了车辆的删除和新增功能 新增“充”页,支持了充和充月卡 20200504 服务 修复了数据库生成的一些时序问题和没有应用定价策略的问题 手机 基本完成交易记录界面 完成停车场地图的显示 完成账户的注销 20200505 服务 修复了数据库生成的一些时序问题,停车记录时间为1-01-01 8:05的问题 手机 修复了一些小BUG,例如格式化代码不正确、宽度过窄等问题 新增点击标题栏文字返回主页 20200515 管理端 完成了导入停车场设计器生成的JSON的功能 20200605 服务 将用户初始化时的用户名改为非随机、顺序的。 修复了设置密码时,有些加了盐有些没加的BUG 管理端 修改初始化密码为1234而非123456 手机 修复了充金额可以为负数、非数字、很大的数字的BUG 新增修改密码界面 20200610 服务 优化测试数据创建流程,修改了数量、停车记录生成的时间 管理端 完成了首页的数据大屏 20200611 服务 优化测试数据创建流程,支持设置添加用户数量,支持了停车时修改停车位状态,修改模拟当前时间 管理端 数据大屏新增停车场名称标题、新增每个停车区的车位状态饼状图 新增停车场设置界面 数据库管理界面增加了生成测试数据的用户数量输入框 20200616 管理端 删除了车主界面的新建按钮(没写功能)和停车记录、交易记录页面的删除按钮(防止外键出错) 修改了登录页和菜单的图标 设置强制设置指定的主题,删除主题按钮 删除了一些搜索框 20200616 管理端 在车主管理界面新增了管理按钮,可以一键打开用户手机端界面,并修改用户的一些数据 出自 © 2021 GitHub, Inc.
J2eeFAST是一个Java EE企业级快速开发平台,基于经典技术组合(Spring Boot、Spring MVC、Apache Shiro、MyBatis-Plus、Freemarker、Bootstrap、AdminLTE)采用经典开发模式,让初学者能够更快的入门并投入到团队开发中去。在线代码生成功能,包括核心模块如:组织机构、角色用户、菜单及按钮授权、数据权限、系统参数、内容管理、license认证,BPM工作流等。采用松耦合设计;界面无刷新,一键换肤;众多账号安全设置,密码策略;在线定时任务配置;支持多数据源;支持读写分离、分库分表。 J2eeFAST功能: 1、用户管理:用户系统操作者,该功能主要完成系统用户配置。 2、部门管理:配置系统组织机构(公司、部门),树结构展现支持数据权限。 3、岗位管理:配置系统用户所属担任职务。 4、菜单管理:配置系统菜单,操作权限,按钮权限标识等。 5、角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。 6、字典管理:对系统中经常使用的一些较为固定的数据进行维护。 7、参数管理:对系统动态配置常用参数。 8、操作日志系统正常操作日志记录和查询;系统异常信息日志记录和查询。 9、登录日志系统登录日志记录查询包含登录异常。 10、在线用户:当前系统中活跃用户状态监控。 11、定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。 12、代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。 13、服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。 14、在线构建器:拖动表单元素生成相应的HTML代码。 15、连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。 16、产品许可: 对项目进行许可证书控制,防止程序拷贝运行。 17、公告通知: 针对项目升级重要情况出通知公告直接生成静态页面,可以实现延迟推送,设置时效性,实时。 J2eeFAST软件架构: 核心框架:Spring Boot 2.2.5.RELEASE 安全框架:Apache Shiro 1.4.2 模板引擎:Freemarker 前端:AdminLTE 2.3.8, Bootstrap 3.3.7, Bootstrap-Table 1.11.0, JQuery 3.3.1 持久层框架:MyBatis-Plus 3.3.1 定时任务: Quartz 数据库连接池:Druid 1.10.1 数据库: Mysql5.7 分布式缓存数据库: Redis 4.0.9 工具类:Hutool 4.5.8 工作流引擎:flowable 6.4.2 J2eeFAST安装教程: 1、需要准备环境Mysql5.7以上、JDK1.8、Maven3.3、Redis4.X以上、开发工具eclipse或者IEDA。 2、下载源码git clone https://gitee.com/zhouhuanOGP/J2EEFAST.git。 3、编译代码找到根目录下pom.xml,执行mvn clean install命令编译一键打包。一般来说不会有什么问题,如果还是编译不成功,可以按照优先级逐个编译试一试。 4、导入数据库db目录里initDb.sql有建库建表语句按步骤执行即可。 5、将代码导入开发工具fast-admin启动模块- 执行FastApplication类即可 注意:fast-admin模块,资源目录application-Test.yml中修改连接数据库和链接地址;如果你是按照initDb.sql建库,test.sql导入初始数据,则账号密码用户名都不需要修改。 6、搭建文档。 最大管理员账号:admin 密码:admin。其他账户密码都是123456 演示地址的账号 :admin 密码:admin   J2eeFAST企业级快速开发平台 v2.2.1 更新日志: 升级mybatisplus到最新版本3.4.2 新增数据库脚本自动升级 适配恶心的IE浏览器最低适配IE10 修复添加SQLServer数据库源问题 修复多源数据JTA事务问题 新增多源数据支持Mapper接口注解 修复表格显示图片点击事件问题 优化适配移动端 linux环境升级、启动脚本备份文件无目录问题 修复多源数据库代码生成切换数据库问题 修复oracle兼容问题 修复代码生成器生成头像问题 修复适配数据库执行语句问题 新增金额转换中文通用方法 修复代码生成div标签问题 调整文件上传考虑事务问题、不删除文件 移除项目pom文件多余配置 新增系统默认配置文件 简化配置文件 移除代码默认密码字段 新增base64字符判断公用方法 优化图片上传支持直接Base64字符 新增表格通过id获取行
RuoYi若依管理系统是一个基于SpringBoot的权限管理系统,代码易读易懂、界面简洁美观。 核心技术采用Spring、MyBatis、Shiro没有任何其它重度依赖。直接运行即可用。 RuoYi若依管理系统功能: 1、用户管理:用户系统操作者,该功能主要完成系统用户配置。 2、部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持权限。 3、岗位管理:配置系统用户所属担任职务。 4、菜单管理:配置系统菜单,操作权限,按钮权限标识等。 5、角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。 6、字典管理:对系统中经常使用的一些较为固定的数据进行维护。 7、参数管理:对系统动态配置常用参数。 8、通知公告:系统通知公告信息发布维护。 9、操作日志系统正常操作日志记录和查询;系统异常信息日志记录和查询。 10、登录日志系统登录日志记录查询包含登录异常。 11、在线用户:当前系统中活跃用户状态监控。 12、定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。 13、代码生成:前后端代码的生成(java、html、xml、sql)支持CRUD下载 。 14、系统接口:根据业务代码自动生成相关的api接口文档。 15、服务监控:监视当前系统CPU、内存、磁盘、堆栈等相关信息。 16、在线构建器:拖动表单元素生成相应的HTML代码。 17、连接池监视:监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。   RuoYi若依管理系统 更新日志: v4.6.1 新增IE浏览器版本过低提示页面 新增详细信息tab页签方式 新增解锁屏幕打开上次页签 数据监控默认账户密码防止越权访问 新增表格示例(导出选择列) 个人信息添加手机&邮箱重复验证 个人中心刷新后样式问题 操作日志返回参数添加非空验证 velocity剔除commons-collections版本,防止3.2.1版本的反序列化漏洞 子表模板默认日期格式化 代码生成预览语言根据后缀名高亮显示 代码生成主子表相同字段导致数据问题 升级SpringBoot到最新版本2.2.13 升级shiro到最新版1.7.1 阻止身份认证绕过漏洞 升级bootstrapTable到最新版本v1.18.2 升级bootstrapTable相关组件到最新版本v1.18.2 升级fastjson到最新版1.2.75 升级druid到最新版本v1.2.4 升级oshi到最新版本v5.6.0 修改ip字段长度防止ipv6地址长度不够 搜索建议示例选择后隐藏列表 主子表示例增加初始化数据 优化Excel导入增加空行判断 修复横向菜单无法打开页签问题 修复导入数据为负浮点数时,导入结果会丢失精度问题 优化更多操作按钮左侧移入内容闪现消失情况 修复主子表提交中列隐藏后出现列偏移问题 单据打印网页时通过hidden-print隐藏元素 表格销毁清除记住选择数据 增加表格动态列示例 代码生成选择主子表关联元素必填 tree根据Id和Name选中指定节点增加空判断 其他细节优化

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值