前言
- 只是一个笔记,肯定有不足的地方,麻烦指出来一起进步.
- 因为是多模块的内部项目,所以不会有高并发.
- 所以是在一个线程内进行的
一.枚举
- 操作状态
/**
* 操作状态
*/
public enum BusinessStatus {
/**
* 成功
*/
SUCCESS,
/**
* 失败
*/
FAIL,
}
- 业务操作类型
/**
* 业务操作类型
*/
public enum BusinessType {
/**
* 其它
*/
OTHER,
/**
* 新增
*/
INSERT,
/**
* 修改
*/
UPDATE,
/**
* 删除
*/
DELETE,
/**
* 授权
*/
GRANT,
/**
* 导出
*/
EXPORT,
/**
* 导入
*/
IMPORT,
/**
* 强退
*/
FORCE,
/**
* 生成代码
*/
GENCODE,
/**
* 清空数据
*/
CLEAN,
}
- 操作人类别
/**
* 操作人类别
*/
public enum OperatorType {
/**
* 其它
*/
OTHER,
/**
* 后台用户
*/
MANAGE,
/**
* 手机端用户
*/
MOBILE
}
二.工具类
- ThreadLocalIdUtils
/**
* ThreadLocalIdUtils 是一个工具类,用于在每个线程内管理唯一的 ID。
* 通过 ThreadLocal 存储 ID,确保每个线程都有自己独立的 ID 副本,
* 避免线程间的变量冲突。
*
* @author 鲁子狄
**/
public class ThreadLocalIdUtils {
/**
* ThreadLocal 变量用于存储当前线程的 ID。
*/
private static final ThreadLocal<Long> threadId = new ThreadLocal<>();
/**
* 私有构造函数,防止外部实例化此类。
* 此类仅包含静态方法,不应被实例化。
*
* @throws UnsupportedOperationException 如果试图实例化此工具类时抛出此异常。
*/
private ThreadLocalIdUtils() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
/**
* 设置当前线程的 ID。
*
* @param id 要设置的 ID 值。
*/
public static void setId(Long id) {
ThreadLocalIdUtils.threadId.set(id);
}
/**
* 获取当前线程的 ID。
*
* @return 当前线程的 ID,如果没有设置则返回默认值。
*/
public static Long getId() {
return ThreadLocalIdUtils.threadId.get();
}
/**
* 清除当前线程的 ID。
* 通常在请求结束后调用此方法,释放 ThreadLocal 占用的资源。
*/
public static void clearId() {
ThreadLocalIdUtils.threadId.remove();
}
}
- FieldUtils
/**
* 字段工具类
*
* @author 鲁子狄
**/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings("unchecked")
public class FieldUtils {
/**
* 获取指定对象的指定字段的值。
*
* @param obj 对象
* @param fieldName 字段名
* @param entityType 实体类型
* @param specialFields 特殊处理的字段集合
* @param <T> 返回类型的泛型
* @return 字段值
*/
public static <T> T getField(Object obj, String fieldName, Class<?> entityType, Set<String> specialFields) {
Class<?> clazz = obj.getClass();
// 如果对象类型不匹配,进行类型转换
if (!clazz.equals(entityType)) {
obj = MapstructUtils.convert(obj, entityType);
clazz = entityType;
}
while (clazz != null) {
try {
// 获取字段
Field field = clazz.getDeclaredField(fieldName);
// 设置访问权限
field.setAccessible(true);
// 获取字段值
return (T) field.get(obj);
} catch (NoSuchFieldException e) {
// 如果字段在特殊处理字段集合中,则返回 null
if (specialFields != null && specialFields.contains(fieldName)) {
return null;
}
// 继续向上级类中查找
clazz = clazz.getSuperclass();
} catch (IllegalAccessException e) {
throw new RuntimeException("无法访问字段 " + fieldName + ":" + e);
}
}
// 如果到达这里,说明没有找到字段
throw new NoSuchElementException("无法找到字段 " + fieldName);
}
}
- JsonUtils
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class JsonUtils {
/**
* compareJsonFields 比较两个 JSON 对象的字段,并返回差异
*
* @param oldValuesJson 旧值
* @param newValuesJson 新值
* @param excludedFields 排除字段列表
* @return {@link java.lang.String}
*/
public static String compareJsonFields(String oldValuesJson, String newValuesJson,Set<String> excludedFields) {
ObjectMapper mapper = new ObjectMapper();
try {
JsonNode oldNode = mapper.readTree(oldValuesJson);
JsonNode newNode = mapper.readTree(newValuesJson);
ObjectNode differences = mapper.createObjectNode();
// 遍历旧的 JSON 节点
oldNode.fieldNames().forEachRemaining(fieldName -> {
// 如果字段在排除列表中,则跳过
if (excludedFields!=null && excludedFields.contains(fieldName)){
return;
}
JsonNode oldValue = oldNode.get(fieldName);
JsonNode newValue = newNode.get(fieldName);
// 如果字段值不同,添加到 differences 中
if (!oldValue.equals(newValue)) {
ObjectNode fieldDifferences = mapper.createObjectNode();
fieldDifferences.putPOJO("old", oldValue);
fieldDifferences.putPOJO("new", newValue);
differences.set(fieldName, fieldDifferences);
}
});
// 检查新 JSON 中独有的字段
newNode.fieldNames().forEachRemaining(fieldName -> {
if (!oldNode.has(fieldName)) {
ObjectNode fieldDifferences = mapper.createObjectNode();
fieldDifferences.set("old", null);
fieldDifferences.set("new", newNode.get(fieldName));
differences.set(fieldName, fieldDifferences);
}
});
// 返回差异的 JSON 字符串
return differences.toString();
} catch (IOException e) {
JsonUtils.log.error("解析JSON出错", e);
return null;
}
}
}
三.操作日志
1.表结构
create table sys_oper_log
(
oper_id bigint not null comment '日志主键'
primary key,
tenant_id varchar(20) default '000000' null comment '租户编号',
title varchar(50) default '' null comment '模块标题',
business_type int default 0 null comment '业务类型(0其它 1新增 2修改 3删除)',
method varchar(100) default '' null comment '方法名称',
request_method varchar(10) default '' null comment '请求方式',
operator_type int default 0 null comment '操作类别(0其它 1后台用户 2手机端用户)',
oper_name varchar(50) default '' null comment '操作人员',
dept_name varchar(50) default '' null comment '部门名称',
oper_url varchar(255) default '' null comment '请求URL',
oper_ip varchar(128) default '' null comment '主机地址',
oper_location varchar(255) default '' null comment '操作地点',
oper_param varchar(2000) default '' null comment '请求参数',
json_result varchar(2000) default '' null comment '返回参数',
status int default 0 null comment '操作状态(0正常 1异常)',
error_msg varchar(2000) default '' null comment '错误消息',
oper_time datetime null comment '操作时间',
cost_time bigint default 0 null comment '消耗时间'
)
comment '操作日志记录';
2.注解
/**
* 自定义操作日志记录注解
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
/**
* 模块
*/
String title() default "";
/**
* 功能
*/
BusinessType businessType() default BusinessType.OTHER;
/**
* 操作人类别
*/
OperatorType operatorType() default OperatorType.MANAGE;
/**
* 是否保存请求的参数
*/
boolean isSaveRequestData() default true;
/**
* 是否保存响应的参数
*/
boolean isSaveResponseData() default true;
/**
* 排除指定的请求参数
*/
String[] excludeParamNames() default {};
}
3.日志事件
- 操作日志事件
/**
* 操作日志事件
*/
@Data
public class OperLogEvent implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 日志主键
*/
private Long operId;
/**
* 租户ID
*/
private String tenantId;
/**
* 操作模块
*/
private String title;
/**
* 业务类型(0其它 1新增 2修改 3删除)
*/
private Integer businessType;
/**
* 业务类型数组
*/
private Integer[] businessTypes;
/**
* 请求方法
*/
private String method;
/**
* 请求方式
*/
private String requestMethod;
/**
* 操作类别(0其它 1后台用户 2手机端用户)
*/
private Integer operatorType;
/**
* 操作人员
*/
private String operName;
/**
* 部门名称
*/
private String deptName;
/**
* 请求url
*/
private String operUrl;
/**
* 操作地址
*/
private String operIp;
/**
* 操作地点
*/
private String operLocation;
/**
* 请求参数
*/
private String operParam;
/**
* 返回参数
*/
private String jsonResult;
/**
* 操作状态(0正常 1异常)
*/
private Integer status;
/**
* 错误消息
*/
private String errorMsg;
/**
* 操作时间
*/
private Date operTime;
/**
* 消耗时间
*/
private Long costTime;
}
- 登录事件
/**
* 登录事件
*/
@Data
public class LogininforEvent implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 租户ID
*/
private String tenantId;
/**
* 用户账号
*/
private String username;
/**
* 登录状态 0成功 1失败
*/
private String status;
/**
* 提示消息
*/
private String message;
/**
* 请求体
*/
private HttpServletRequest request;
/**
* 其他参数
*/
private Object[] args;
}
4.切面
/**
* 操作日志记录处理
*/
@Slf4j
@Aspect
@AutoConfiguration
public class LogAspect {
/**
* 排除敏感属性字段
*/
public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };
/**
* 计时 key
*/
private static final ThreadLocal<StopWatch> KEY_CACHE = new ThreadLocal<>();
/**
* 处理请求前执行
*/
@Before(value = "@annotation(controllerLog)")
public void doBefore(JoinPoint joinPoint, Log controllerLog) {
ThreadLocalIdUtils.setId(IdWorker.getId());
StopWatch stopWatch = new StopWatch();
KEY_CACHE.set(stopWatch);
stopWatch.start();
}
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
handleLog(joinPoint, controllerLog, null, jsonResult);
}
/**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) {
handleLog(joinPoint, controllerLog, e, null);
}
protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {
try {
// *========数据库日志=========*//
OperLogEvent operLog = new OperLogEvent();
operLog.setOperId(ThreadLocalIdUtils.getId());
operLog.setTenantId(LoginHelper.getTenantId());
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址
String ip = ServletUtils.getClientIP();
operLog.setOperIp(ip);
operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
LoginUser loginUser = LoginHelper.getLoginUser();
operLog.setOperName(loginUser.getUsername());
operLog.setDeptName(loginUser.getDeptName());
if (e != null) {
operLog.setStatus(BusinessStatus.FAIL.ordinal());
operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
}
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
operLog.setMethod(className + "." + methodName + "()");
// 设置请求方式
operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
// 设置消耗时间
StopWatch stopWatch = KEY_CACHE.get();
stopWatch.stop();
operLog.setCostTime(stopWatch.getTime());
// 发布事件保存数据库
SpringUtils.context().publishEvent(operLog);
} catch (Exception exp) {
// 记录本地异常日志
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
} finally {
KEY_CACHE.remove();
}
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param log 日志
* @param operLog 操作日志
* @throws Exception
*/
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, OperLogEvent operLog, Object jsonResult) throws Exception {
// 设置action动作
operLog.setBusinessType(log.businessType().ordinal());
// 设置标题
operLog.setTitle(log.title());
// 设置操作人类别
operLog.setOperatorType(log.operatorType().ordinal());
// 是否需要保存request,参数和值
if (log.isSaveRequestData()) {
// 获取参数的信息,传入到数据库中。
setRequestValue(joinPoint, operLog, log.excludeParamNames());
}
// 是否需要保存response,参数和值
if (log.isSaveResponseData() && ObjectUtil.isNotNull(jsonResult)) {
operLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0, 2000));
}
}
/**
* 获取请求的参数,放到log中
*
* @param operLog 操作日志
* @throws Exception 异常
*/
private void setRequestValue(JoinPoint joinPoint, OperLogEvent operLog, String[] excludeParamNames) throws Exception {
Map<String, String> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
String requestMethod = operLog.getRequestMethod();
if (MapUtil.isEmpty(paramsMap)
&& HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
} else {
MapUtil.removeAny(paramsMap, EXCLUDE_PROPERTIES);
MapUtil.removeAny(paramsMap, excludeParamNames);
operLog.setOperParam(StringUtils.substring(JsonUtils.toJsonString(paramsMap), 0, 2000));
}
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) {
StringJoiner params = new StringJoiner(" ");
if (ArrayUtil.isEmpty(paramsArray)) {
return params.toString();
}
for (Object o : paramsArray) {
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
String str = JsonUtils.toJsonString(o);
Dict dict = JsonUtils.parseMap(str);
if (MapUtil.isNotEmpty(dict)) {
MapUtil.removeAny(dict, EXCLUDE_PROPERTIES);
MapUtil.removeAny(dict, excludeParamNames);
str = JsonUtils.toJsonString(dict);
}
params.add(str);
}
}
return params.toString();
}
/**
* 判断是否需要过滤的对象。
*
* @param o 对象信息。
* @return 如果是需要过滤的对象,则返回true;否则返回false。
*/
@SuppressWarnings("rawtypes")
public boolean isFilterObject(final Object o) {
Class<?> clazz = o.getClass();
if (clazz.isArray()) {
return MultipartFile.class.isAssignableFrom(clazz.getComponentType());
} else if (Collection.class.isAssignableFrom(clazz)) {
Collection collection = (Collection) o;
for (Object value : collection) {
return value instanceof MultipartFile;
}
} else if (Map.class.isAssignableFrom(clazz)) {
Map map = (Map) o;
for (Object value : map.values()) {
return value instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|| o instanceof BindingResult;
}
}
5. 事件监听器
/**
* 操作日志记录
*
* @param operLogEvent 操作日志事件
*/
@Async
@EventListener
public void recordOper(OperLogEvent operLogEvent) {
SysOperLogBo operLog = MapstructUtils.convert(operLogEvent, SysOperLogBo.class);
// 远程查询操作地点
operLog.setOperLocation(AddressUtils.getRealAddressByIp(operLog.getOperIp()));
insertOperlog(operLog);
}
/**
* 新增操作日志
*
* @param bo 操作日志对象
*/
@Override
public void insertOperlog(SysOperLogBo bo) {
SysOperLog operLog = MapstructUtils.convert(bo, SysOperLog.class);
operLog.setOperTime(new Date());
baseMapper.insert(operLog);
}
四. 数据日志
1.表结构
create table sys_data_log
(
data_id bigint not null comment '数据日志ID'
primary key,
tenant_id varchar(20) default '000000' null comment '租户编号',
oper_id bigint null comment '关联的操作日志ID',
table_name varchar(255) default '' null comment '受影响的数据表名',
row_id varchar(255) default '' null comment '受影响的数据行ID或主键',
old_values text null comment '变更前的值(JSON格式)',
new_values text null comment '变更后的值(JSON格式)',
change_columns text null comment '变更的列(JSON格式)',
change_type int default 0 null comment '变更类型(如:0其它 1插入 2更新 3删除)',
oper_time datetime null comment '操作时间',
oper_name varchar(50) default '' null comment '操作人员',
dept_name varchar(50) default '' null comment '部门名称'
)
comment '数据日志记录';
2.注解
/**
* 自定义数据日志注解
*
* @author 鲁子狄
**/
@Target({ ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataLog {
/**
* 功能
*/
BusinessType businessType() default BusinessType.OTHER;
/**
* mapper class
*/
Class<? extends BaseMapperPlus> baseMapper() default BaseMapperPlus.class;
}
3.日志事件
/**
* 数据日志事件
*
* @author 鲁子狄
**/
@Data
public class DataLogEvent implements Serializable {
@Serial
private static final long serialVersionUID = 8640083616912539107L;
/**
* 数据日志ID
*/
private Long dataId;
/**
* 关联的操作日志ID
*/
private Long operId;
/**
* 受影响的数据表名
*/
private String tableName;
/**
* 受影响的数据行ID或主键
*/
private String rowId;
/**
* 变更前的值(JSON格式)
*/
private String oldValues;
/**
* 变更后的值(JSON格式)
*/
private String newValues;
/**
* 变更的列(JSON格式)
*/
private String changeColumns;
/**
* 变更类型(如:0其它 1插入 2更新 3删除)
*/
private Integer changeType;
/**
* 操作人员
*/
private String operName;
/**
* 部门名称
*/
private String deptName;
}
4.切面
/**
* 数据日志处理
*
* @author 鲁子狄
**/
@Slf4j
@Aspect
@AutoConfiguration
@SuppressWarnings("unchecked")
public class DataLogAspect {
/**
* 创建一个 Set 来存储需要排除的字段名称
*/
private static final Set<String> EXCLUDED_FIELDS =
Set.of("version", "createDept", "createTime", "createBy",
"updateTime", "updateBy", "delFlag", "tenantId");
/**
* 环绕拦截
*
* @param joinPoint 切点
* @param serviceLog 注解
* @return Object
* @throws Throwable 错误
*/
@Around("@annotation(serviceLog)")
public static Object doAround(ProceedingJoinPoint joinPoint, DataLog serviceLog) throws Throwable {
// 获取对应的 BaseMapperPlus 实例
BaseMapperPlus<?, ?> baseMapper = SpringUtil.getBean(serviceLog.baseMapper());
// 获取实体类类型
Class<?> entityType = baseMapper.currentModelClass();
// 根据业务类型处理不同的操作
return switch (serviceLog.businessType()) {
case INSERT ->
// 插入操作
DataLogAspect.handleInsert(joinPoint, entityType);
case UPDATE ->
// 更新操作
DataLogAspect.handleUpdate(joinPoint, entityType, baseMapper);
case DELETE ->
// 删除操作
DataLogAspect.handleDelete(joinPoint, entityType, baseMapper);
default ->
// 默认情况下执行原方法
joinPoint.proceed();
};
}
/**
* 处理插入操作
*
* @param joinPoint 切点
* @return Object
* @throws Throwable 错误
*/
private static Object handleInsert(ProceedingJoinPoint joinPoint, Class<?> entityType) throws Throwable {
// 执行原方法
Object result = joinPoint.proceed();
// 记录插入日志
DataLogAspect.handInsert(joinPoint, result, entityType);
return result;
}
/**
* 处理更新操作
*
* @param joinPoint 切点
* @return Object
* @throws Throwable 错误
*/
private static Object handleUpdate(ProceedingJoinPoint joinPoint, Class<?> entityType, BaseMapperPlus<?, ?> baseMapper) throws Throwable {
// 获取方法参数
Object arg = joinPoint.getArgs()[0];
// 获取主键ID
Long id = DataLogAspect.getIdField(arg, entityType);
// 获取旧数据的JSON表示
String oldValuesJson = DataLogAspect.getOldValuesJson(id, entityType, baseMapper);
// 执行原方法
Object result = joinPoint.proceed();
// 获取新数据的JSON表示
String newValuesJson = DataLogAspect.getValuesJson(arg, entityType);
// 比较新旧数据
String compareJson = JsonUtils.compareJsonFields(oldValuesJson, newValuesJson, DataLogAspect.EXCLUDED_FIELDS);
// 如果数据有变化
if (StringUtils.isNotBlank(compareJson) && !"{}".equals(compareJson)) {
// 记录更新日志
DataLogAspect.handUpdate(oldValuesJson, newValuesJson, compareJson, id, entityType);
}
return result;
}
/**
* 处理删除操作
*
* @param joinPoint 切点
* @return Object
* @throws Throwable 错误
*/
private static Object handleDelete(ProceedingJoinPoint joinPoint, Class<?> entityType, BaseMapperPlus<?, ?> baseMapper) throws Throwable {
// 执行原方法
Object result = joinPoint.proceed();
// 记录删除日志
DataLogAspect.handDelete((Collection<Long>) joinPoint.getArgs()[0], entityType, baseMapper);
return result;
}
/**
* 处理插入日志
*
* @param joinPoint 切点
* @param id 插入后的主键ID
*/
private static void handInsert(ProceedingJoinPoint joinPoint, Object id, Class<?> entityType) {
// 创建日志事件
DataLogEvent dataLog = DataLogAspect.createDataLogEvent(BusinessType.INSERT, entityType);
// 设置新数据
dataLog.setNewValues(DataLogAspect.getValuesJson(joinPoint.getArgs()[0], entityType));
// 设置主键ID字符串
dataLog.setRowId(String.valueOf(id));
// 发布事件
DataLogAspect.publishEvent(dataLog);
}
/**
* 处理更新日志
*
* @param oldValuesJson 旧数据的JSON表示
* @param newValuesJson 新数据的JSON表示
* @param compareJson 比较结果的JSON表示
* @param id 主键ID
*/
private static void handUpdate(String oldValuesJson, String newValuesJson, String compareJson, Long id, Class<?> entityType) {
// 创建日志事件
DataLogEvent dataLog = DataLogAspect.createDataLogEvent(BusinessType.UPDATE, entityType);
// 设置旧数据
dataLog.setOldValues(oldValuesJson);
// 设置新数据
dataLog.setNewValues(newValuesJson);
// 设置变更列
dataLog.setChangeColumns(compareJson);
// 设置主键ID字符串
dataLog.setRowId(String.valueOf(id));
// 发布事件
DataLogAspect.publishEvent(dataLog);
}
/**
* 处理删除日志
*
* @param ids 被删除的数据的主键ID集合
*/
private static void handDelete(Collection<Long> ids, Class<?> entityType, BaseMapperPlus<?, ?> baseMapper) {
if (ids.isEmpty()){
return;
}
// 创建日志事件
DataLogEvent dataLog = DataLogAspect.createDataLogEvent(BusinessType.DELETE, entityType);
// 设置主键ID字符串
dataLog.setRowId(StringUtils.join(ids, ", "));
// 设置数据
dataLog.setOldValues(JsonUtils.toJsonString(DataLogAspect.getDeletedList(ids, baseMapper)));
// 发布事件
DataLogAspect.publishEvent(dataLog);
}
/**
* getDeletedList 获取已删除的数据
*
* @param ids 主键id集合
* @return {@link java.util.List<?>}
*/
private static List<?> getDeletedList(Collection<Long> ids, BaseMapperPlus<?, ?> baseMapper) {
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.in("id", ids);
String sql = String.format(
"OR (id IN (%s) AND del_flag = 2)",
StringUtils.join(ids, ", ")
);
queryWrapper.last(sql);
return baseMapper.selectList(queryWrapper);
}
/**
* 创建 DataLogEvent 对象
*
* @param businessType 业务类型
* @return DataLogEvent
*/
private static DataLogEvent createDataLogEvent(BusinessType businessType, Class<?> entityType) {
DataLogEvent dataLog = new DataLogEvent();
// 设置操作者ID
dataLog.setOperId(ThreadLocalIdUtils.getId());
// 设置表名
dataLog.setTableName(DataLogAspect.getTableName(entityType));
// 设置变更类型
dataLog.setChangeType(businessType.ordinal());
// 获取登录用户信息
LoginUser loginUser = LoginHelper.getLoginUser();
// 设置操作者用户名
dataLog.setOperName(loginUser.getUsername());
// 设置部门名
dataLog.setDeptName(loginUser.getDeptName());
return dataLog;
}
/**
* 获取表名
*
* @return 表名
*/
private static String getTableName(Class<?> entityType) {
// 检查类是否具有 @TableName 注解
if (entityType.isAnnotationPresent(TableName.class)) {
// 获取注解的 value 属性值
return entityType.getAnnotation(TableName.class).value();
} else {
// 抛出异常
throw new RuntimeException("实体类上没有 @TableName 注解");
}
}
/**
* 获取旧数据的 JSON 表示
*
* @param id 主键 ID
* @return JSON 字符串
*/
private static String getOldValuesJson(Long id, Class<?> entityType, BaseMapperPlus<?, ?> baseMapper) {
// 通过主键获取旧数据
Object result = baseMapper.selectById(id);
// 将旧数据转换为 JSON 字符串
return DataLogAspect.getValuesJson(result, entityType);
}
/**
* 获取对象的 JSON 表示
*
* @param obj 对象
* @return JSON 字符串
*/
private static String getValuesJson(Object obj, Class<?> entityType) {
// 如果对象类型不匹配
if (!obj.getClass().equals(entityType)) {
// 转换对象类型
obj = MapstructUtils.convert(obj, entityType);
}
// 将对象转换为 JSON 字符串
return JsonUtils.toJsonString(obj);
}
/**
* getIdField 获取指定对象的 id 字段的值
*
* @param obj 对象
* @return {@link T}
*/
private static <T> T getIdField(Object obj, Class<?> entityType) {
// 获取指定对象的指定字段的值
return FieldUtils.getField(obj, "id", entityType, null);
}
/**
* 发布事件保存数据库
*
* @param dataLog 数据日志事件
*/
private static void publishEvent(DataLogEvent dataLog) {
// 发布事件
SpringUtils.context().publishEvent(dataLog);
}
}
5. 事件监听器
/**
* 操作日志记录
*
* @param dataLogEvent 数据日志事件
*/
@Async
@EventListener
public void recordData(DataLogEvent dataLogEvent) {
insertByBo(MapstructUtils.convert(dataLogEvent, SysDataLogBo.class));
}
/**
* insertByBo 新增数据日志记录
*
* @param bo 数据日志记录
* @return {@link Long} 主键
*/
@Override
public Long insertByBo(SysDataLogBo bo) {
SysDataLog add = MapstructUtils.convert(bo, SysDataLog.class);
add.setOperTime(LocalDateTime.now());
return baseMapper.insert(add) > 0 ? add.getDataId() : null;
}
五.使用
1.操作日志
/**
* 基础单据信息表(base_document)表控制层
*
* @author 鲁子狄
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/base/document")
public class BaseDocumentController extends BaseController {
private final IBaseDocumentService baseDocumentService;
/**
* 分页查询基础单据信息列表
*/
@SaCheckPermission("base:document:page")
@GetMapping("/page")
public TableDataInfo<BaseDocumentVo> page(BaseDocumentBo bo, PageQuery pageQuery) {
return baseDocumentService.queryPageList(bo, pageQuery);
}
/**
* 查询基础单据信息列表
*/
@SaCheckPermission("base:document:list")
@GetMapping("/list")
public R<List<BaseDocumentVo>> list(BaseDocumentBo bo) {
return R.ok(baseDocumentService.queryList(bo));
}
/**
* 导出基础单据信息列表
*/
@SaCheckPermission("base:document:export")
@Log(title = "单据管理", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(BaseDocumentBo bo, HttpServletResponse response) {
List<BaseDocumentVo> list = baseDocumentService.queryList(bo);
ExcelUtil.exportExcel(list, "基础单据信息", BaseDocumentVo.class, response);
}
/**
* 获取基础单据信息详细信息
*/
@SaCheckPermission("base:document:query")
@GetMapping("/{id}")
public R<BaseDocumentVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(baseDocumentService.queryById(id));
}
/**
* 新增基础单据信息
*/
@SaCheckPermission("base:document:add")
@Log(title = "单据管理", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Long> add(@Validated(AddGroup.class) @RequestBody BaseDocumentBo bo) {
return R.ok(baseDocumentService.insertByBo(bo));
}
/**
* 修改基础单据信息
*/
@SaCheckPermission("base:document:edit")
@Log(title = "单据管理", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody BaseDocumentBo bo) {
return toAjax(baseDocumentService.updateByBo(bo));
}
/**
* 删除基础单据信息
*/
@SaCheckPermission("base:document:remove")
@Log(title = "单据管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(baseDocumentService.deleteWithValidByIds(List.of(ids), true));
}
}
2.数据日志
/**
* 基础单据信息 Service业务层处理
*
* @author 鲁子狄
*/
@RequiredArgsConstructor
@Service
public class BaseDocumentServiceImpl extends ServiceImpl<BaseDocumentMapper, BaseDocument> implements IBaseDocumentService {
private final IBaseDocAssocService baseDocAssocService;
/**
* buildQueryWrapper 构建条件构造器
*
* @param bo bo
* @return {@link LambdaQueryWrapper<BaseDocument>} 条件构造器
*/
private static LambdaQueryWrapper<BaseDocument> buildQueryWrapper(BaseDocumentBo bo) {
LambdaQueryWrapper<BaseDocument> lqw = Wrappers.lambdaQuery();
lqw.like(StringUtils.isNotBlank(bo.getDocumentName()), BaseDocument::getDocumentName, bo.getDocumentName());
lqw.like(StringUtils.isNotBlank(bo.getModuleName()), BaseDocument::getModuleName, bo.getModuleName());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), BaseDocument::getStatus, bo.getStatus());
return lqw;
}
/**
* queryById 查询基础单据信息
*
* @param id 主键
* @return {@link BaseDocumentVo} 基础单据信息
*/
@Override
public BaseDocumentVo queryById(Long id) {
return baseMapper.selectVoById(id);
}
/**
* queryPageList 分页查询基础单据信息列表
*
* @param bo 查询条件
* @param pageQuery 分页参数
* @return {@link TableDataInfo<BaseDocumentVo>} 基础单据信息分页列表
*/
@Override
public TableDataInfo<BaseDocumentVo> queryPageList(BaseDocumentBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<BaseDocument> lqw = buildQueryWrapper(bo);
Page<BaseDocumentVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
* queryList 查询符合条件的基础单据信息列表
*
* @param bo 查询条件
* @return {@link List<BaseDocumentVo>} 基础单据信息列表
*/
@Override
public List<BaseDocumentVo> queryList(BaseDocumentBo bo) {
LambdaQueryWrapper<BaseDocument> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
/**
* insertByBo 新增基础单据信息
*
* @param bo 基础单据信息
* @return {@link Long} 主键
*/
@Override
@DataLog(businessType= BusinessType.INSERT, baseMapper = BaseDocumentMapper.class)
@Transactional(rollbackFor = Exception.class)
public Long insertByBo(BaseDocumentBo bo) {
BaseDocument add = MapstructUtils.convert(bo, BaseDocument.class);
validEntityBeforeSave(add);
return baseMapper.insert(add) > 0 ? add.getId() : null;
}
/**
* updateByBo 修改基础单据信息
*
* @param bo 基础单据信息
* @return {@link Boolean} 是否修改成功
*/
@Override
@DataLog(businessType= BusinessType.UPDATE, baseMapper = BaseDocumentMapper.class)
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(BaseDocumentBo bo) {
BaseDocument update = MapstructUtils.convert(bo, BaseDocument.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* validEntityBeforeSave 保存前的数据校验
*
* @param entity entity
*/
private void validEntityBeforeSave(BaseDocument entity) {
LambdaQueryWrapper<BaseDocument> lqw = Wrappers.lambdaQuery();
lqw.eq(BaseDocument::getDocumentName, entity.getDocumentName());
if (ObjectUtils.isNotEmpty(entity.getId()) && !entity.getId().equals(0L)) {
lqw.ne(BaseDocument::getId, entity.getId());
}
if (baseMapper.selectCount(lqw) > 0) {
throw new ServiceException("单据名称已存在.");
}
}
/**
* deleteWithValidByIds 校验并批量删除基础单据信息信息
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return {@link Boolean} 是否删除成功
*/
@Override
@DataLog(businessType= BusinessType.DELETE, baseMapper = BaseDocumentMapper.class)
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
baseDocAssocService.remove(new LambdaQueryWrapper<BaseDocAssoc>().in(BaseDocAssoc::getDocId, ids));
return baseMapper.deleteByIds(ids) > 0;
}
}
标签:null,return,String,private,切面,AOP,日志,param,public
From: https://blog.csdn.net/LuChangQiu/article/details/142921371