系统日志

This commit is contained in:
cwchen 2025-09-05 14:22:54 +08:00
parent 4712851918
commit 15b6bef566
14 changed files with 763 additions and 8 deletions

View File

@ -3,6 +3,9 @@ package com.bonus.web.controller.system;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import com.bonus.common.annotation.SysLog;
import com.bonus.common.enums.OperaType;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
@ -58,6 +61,7 @@ public class SysUserController extends BaseController
*/
@PreAuthorize("@ss.hasPermi('system:user:list')")
@GetMapping("/list")
@SysLog(title = "用户管理", businessType = OperaType.QUERY, logType = 0, module = "系统管理->用户管理", details = "查询用户列表")
public TableDataInfo list(SysUser user)
{
startPage();

View File

@ -6,12 +6,9 @@ spring:
druid:
# 主库数据源
master:
# url: jdbc:mysql://192.168.0.56:13306/smart_archives?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
# username: root
# password: qd_mysql@2025
url: jdbc:mysql://127.0.0.1:3306/smart_archives?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
url: jdbc:mysql://192.168.0.56:13306/smart_archives_dev?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: ccw1998@yyt1999
password: qd_mysql@2025
# 从库数据源
slave:
# 从数据源开关/默认关闭

View File

@ -141,6 +141,12 @@
<version>1.0.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,76 @@
package com.bonus.common.annotation;
import com.bonus.common.enums.OperaType;
import com.bonus.common.enums.OperatorType;
import java.lang.annotation.*;
/**
* 自定义操作日志记录注解
*
* @author bonus
*
*/
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog
{
/**
* 模块
*/
public String title() default "";
/**
* 业务类型 默认 0 系统日志 1 业务日志 2 异常日志
* @return
*/
public int logType() default 1;
/**
* 操作模块
* @return
*/
public String module();
/**
* 功能 ->新增修改删除
*/
public String businessType() default OperaType.OTHER;
public String details() default "";
/**
* 操作人类别
*/
public OperatorType operatorType() default OperatorType.MANAGE;
/**
* 是否保存请求的参数
*/
public boolean isSaveRequestData() default true;
/**
* 是否保存响应的参数
*/
public boolean isSaveResponseData() default true;
/**
* 排除指定的请求参数
*/
public String[] excludeParamNames() default {};
/**
* 操作类型
*
* @return
*/
String grade() default OperaType.QUERY;
/**
* 日志类型
*/
String type() default "业务日志";
}

View File

@ -0,0 +1,28 @@
package com.bonus.common.enums;
/**
* 操作人类别
*
* @author bonus
*/
public enum LogType
{
/**
* 日志类型 1 业务日志 0 系统日志 2异常日志
*/
/**
* 系统日志
*/
SYSTEM_LOG,
/**
* 业务日志
*/
BUSINESS_LOG,
/**
* 2异常日志
*/
EXCEPTION_LOG
}

View File

@ -0,0 +1,18 @@
package com.bonus.common.enums;
/**
* 操作类型
* @author bonus
*/
public class OperaResult {
/**
* 备份
*/
public final static String SUCCESS="成功";
/**
* 查询
*/
public final static String FAIL="失败";
}

View File

@ -0,0 +1,62 @@
package com.bonus.common.enums;
/**
* 操作类型
* @author bonus
*/
public class OperaType {
/**
* 备份
*/
public final static String COPY_LOG="备份";
/**
* 查询
*/
public final static String QUERY="查询";
/**
* 修改
*/
public final static String UPDATE="修改";
/**
* 新增"
*/
public final static String INSERT="新增";
/**
* 删除
*/
public final static String DELETE="删除";
/**
* 删除
*/
public final static String DOWNLOAD="下载";
/**
* 导出
*/
public final static String EXPORT="导出";
public final static String GRANT="授权";
/**
* 下载
*/
public final static String IMPORT="导入";
/**
* 其他
*/
public final static String OTHER="其他";
/**
* 其他
*/
public final static String FLASH="刷新";
public final static String LOGIN="登录";
public final static String LOGOUT="登出";
public final static String REGISTER="注册";
}

View File

@ -0,0 +1,278 @@
package com.bonus.framework.aspectj;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.bonus.common.annotation.SysLog;
import com.bonus.common.enums.OperaResult;
import com.bonus.common.filter.PropertyPreExcludeFilter;
import com.bonus.common.utils.DateUtils;
import com.bonus.common.utils.SecurityUtils;
import com.bonus.common.utils.ServletUtils;
import com.bonus.common.utils.global.SystemGlobal;
import com.bonus.common.utils.ip.IpUtils;
import com.bonus.framework.manager.AsyncManager;
import com.bonus.framework.manager.factory.AsyncFactory;
import com.bonus.system.domain.SysLogsVo;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
/**
* 操作日志记录处理
*
* @author bonus
*/
@Aspect
@Component
public class LogAspect2
{
private static final Logger log = LoggerFactory.getLogger(LogAspect2.class);
/** 排除敏感属性字段 */
public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" };
/** 计算操作消耗时间 */
private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");
/**
* 处理请求前执行
*/
@Before(value = "@annotation(controllerLog)")
public void boBefore(JoinPoint joinPoint, SysLog controllerLog)
{
TIME_THREADLOCAL.set(System.currentTimeMillis());
}
/**
* 处理完请求后执行
*
* @param joinPoint 切点
*/
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, SysLog controllerLog, Object jsonResult)
{
handleLog(joinPoint, controllerLog, null, jsonResult);
}
/**
* 拦截异常操作
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, SysLog controllerLog, Exception e)
{
handleLog(joinPoint, controllerLog, e, null);
}
protected void handleLog(final JoinPoint joinPoint, SysLog controllerLog, final Exception e, Object jsonResult) {
try
{
// *========数据库日志=========*//
SysLogsVo sysLogsVo=new SysLogsVo();
sysLogsVo.setOperTime(DateUtils.getTime());
String uuid= UUID.randomUUID().toString().replace("-","").toUpperCase();
sysLogsVo.setLogId(uuid);
if(jsonResult!=null){
String result = JSON.toJSONString(jsonResult);
JSONObject jsonObject = JSON.parseObject(result);
String code= jsonObject.getString("code");
if (SystemGlobal.SUCCESS_CODE_STR.contains(code)){
sysLogsVo.setResult(OperaResult.SUCCESS);
sysLogsVo.setFailureReason("操作成功");
}else{
sysLogsVo.setFailureReason(jsonObject.getString("msg"));
sysLogsVo.setResult(OperaResult.FAIL);
}
}else{
//void->无参数返回的
sysLogsVo.setResult(OperaResult.SUCCESS);
sysLogsVo.setFailureReason("操作成功");
}
//操作模块及路径
sysLogsVo.setModel(controllerLog.module());
sysLogsVo.setOperType(controllerLog.businessType());
sysLogsVo.setLogType(controllerLog.logType());
if(StringUtils.isEmpty(controllerLog.details())){
sysLogsVo.setOperateDetail("进行了数据的"+controllerLog.businessType());
}else{
sysLogsVo.setOperateDetail(controllerLog.details());
}
sysLogsVo.setIp(IpUtils.getIpAddr());
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
//方法
sysLogsVo.setMethod(className + "." + methodName + "()");
//请求方法
sysLogsVo.setMethodType(ServletUtils.getRequest().getMethod());
// 请求的地址
String username = SecurityUtils.getUsername();
if (StringUtils.isNotBlank(username)) {
sysLogsVo.setOperaUserName(username);
}
Long userId = SecurityUtils.getUserId();
if (userId!=null && userId!=0L) {
sysLogsVo.setUserId(userId.toString());
}
sysLogsVo.setOperUri(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
sysLogsVo.setTimes(System.currentTimeMillis() - TIME_THREADLOCAL.get()+"");
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, sysLogsVo, jsonResult);
// 保存数据库
// asyncLogService.addLogs(sysLogsVo);
AsyncManager.me().execute(AsyncFactory.addSysOperLog(sysLogsVo));
}
catch (Exception exp)
{
// 记录本地异常日志
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
finally
{
TIME_THREADLOCAL.remove();
}
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param log 日志
* @param sysLogsVo 操作日志
* @throws Exception
*/
public void getControllerMethodDescription(JoinPoint joinPoint, SysLog log, SysLogsVo sysLogsVo, Object jsonResult) throws Exception
{
// 设置action动作
// 设置标题
sysLogsVo.setTitle(log.title());
// 是否需要保存request参数和值
if (log.isSaveRequestData())
{
// 获取参数的信息传入到数据库中
setRequestValue(joinPoint, sysLogsVo, log.excludeParamNames());
}
// 是否需要保存response参数和值
if (log.isSaveResponseData() && Objects.nonNull(jsonResult))
{
sysLogsVo.setResultData(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 3000));
}
}
/**
* 获取请求的参数放到log中
*
* @param operLog 操作日志
* @throws Exception 异常
*/
private void setRequestValue(JoinPoint joinPoint, SysLogsVo operLog, String[] excludeParamNames) throws Exception
{
String requestMethod = operLog.getMethod();
Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
boolean bResult = HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod);
if (Objects.isNull(paramsMap) && bResult)
{
String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
operLog.setParams(StringUtils.substring(params, 0, 2000));
}
else {
operLog.setParams(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000));
}
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames)
{
String params = "";
if (paramsArray != null && paramsArray.length > 0)
{
for (Object o : paramsArray)
{
if (Objects.nonNull(o) && !isFilterObject(o))
{
try
{
String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames));
params += jsonObj.toString() + " ";
}
catch (Exception e)
{
}
}
}
}
return params.trim();
}
/**
* 忽略敏感属性
*/
public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames)
{
return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames));
}
/**
* 判断是否需要过滤的对象
*
* @param o 对象信息
* @return 如果是需要过滤的对象则返回true否则返回false
*/
@SuppressWarnings("rawtypes")
public boolean isFilterObject(final Object o)
{
Class<?> clazz = o.getClass();
if (clazz.isArray())
{
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
}
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.entrySet())
{
Map.Entry entry = (Map.Entry) value;
return entry.getValue() instanceof MultipartFile;
}
}
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|| o instanceof BindingResult;
}
public static void main(String[] args) {
String uuid= UUID.randomUUID().toString().replace("-","").toUpperCase();
System.err.println(uuid);
}
}

View File

@ -1,6 +1,8 @@
package com.bonus.framework.manager.factory;
import java.util.TimerTask;
import com.bonus.system.domain.SysLogsVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bonus.common.constant.Constants;
@ -99,4 +101,24 @@ public class AsyncFactory
}
};
}
/**
* 操作日志记录
*
* @param sysLogsVo 操作日志信息
* @return 任务task
*/
public static TimerTask addSysOperLog(final SysLogsVo sysLogsVo)
{
return new TimerTask()
{
@Override
public void run()
{
// 远程查询操作地点
// sysLogsVo.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
SpringUtils.getBean(ISysOperLogService.class).addSysOperLog(sysLogsVo);
}
};
}
}

View File

@ -0,0 +1,176 @@
package com.bonus.system.domain;
import com.bonus.common.core.domain.model.LoginUser;
import com.bonus.common.utils.DateUtils;
import com.bonus.common.utils.ServletUtils;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.type.Alias;
import org.aspectj.lang.ProceedingJoinPoint;
import java.util.UUID;
/**
* 日志实体
*
* @author bonus
*/
@Data
@Alias("SysLogsVo")
public class SysLogsVo {
/**
* 日志ID
*/
private String logId;
/**
* 操作人名称
*/
private String operaUserName;
/**
* 操作ip
*/
private String ip;
/**
* 操作人ID
*/
private String userId;
/**
* 操作模块
*/
private String model;
/**
* 操作时间
*/
private String operTime;
/**
* 操作详情
*/
private String operateDetail;
/**
*操作类型 增删改查 登录 登出
*/
private String operType;
/**
* 执行方法
*/
private String method;
/**
*操作页面路径URI
*/
private String operUri;
/**
* 日志类型 1 业务日志 0 系统日志 2异常日志
*/
private int logType;
/**
* 执行结果(1.成功/2.失败)
*/
private String result;
// private String fruit;
/**
* 执行时间(/ms)
*/
private String times;
/**
* 失败原因
*/
private String failureReason;
/**
* 异常事件等级(
*/
private String grade;
/**
* 异常类型(ip异常/越权)
*/
private String errType;
/**
* 方法类型
*/
private String methodType;
/**
* 模块名称
*/
private String title;
/**
* 操作参数
* */
private String params;
/**
* 返回的数据
*/
private String resultData;
private int num;
/**
* 排序字段 1,2,3,4,5,6,7,8,
*/
private String logSort;
/**
* 倒序 1 正序 2
*/
private String logDesc;
private String startTime;
private String endTime;
//查询类型 1 按日志类型 2 按操作类型 3 按操作人
private String type;
private String capacity;
/**
* 0未处理1已处理
*/
private String warningStatus;
/**
* 越权记录
* @param loginUser
* @return
*/
public static SysLogsVo getExceedAuthorithSysLogsVo(LoginUser loginUser, ProceedingJoinPoint joinPoint) {
SysLogsVo vo=new SysLogsVo();
try{
String uuid= UUID.randomUUID().toString().replace("-","").toUpperCase();
vo.setLogId(uuid);
String ip = loginUser.getIpaddr();
vo.setIp(ip);
// 设置方法名称
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
//方法
vo.setMethod(className + "." + methodName + "()");
//请求方法
vo.setMethodType(ServletUtils.getRequest().getMethod());
//void->无参数返回的
vo.setResult("成功");
vo.setFailureReason("操作未授权");
vo.setGrade("");
vo.setErrType("越权访问");
vo.setOperUri(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
vo.setOperTime(DateUtils.getTime());
vo.setLogType(2);
//
if (StringUtils.isNotBlank(loginUser.getUsername())){
vo.setOperaUserName(loginUser.getUsername());
}
Long userId=loginUser.getUserId();
if (userId!=null && userId!=0L) {
vo.setUserId(userId.toString());
}
return vo;
}catch (Exception e){
System.err.println("越权记录");
}
return vo;
}
}

View File

@ -1,6 +1,8 @@
package com.bonus.system.mapper;
import java.util.List;
import com.bonus.system.domain.SysLogsVo;
import com.bonus.system.domain.SysOperLog;
/**
@ -45,4 +47,22 @@ public interface SysOperLogMapper
* 清空操作日志
*/
public void cleanOperLog();
/**
* 添加系统操作日志
* @param sysLogsVo
* @return void
* @author cwchen
* @date 2025/9/5 13:16
*/
void addSysOperLog(SysLogsVo sysLogsVo);
/**
* 查询业务模块
* @param operUri
* @return SysLogsVo
* @author cwchen
* @date 2025/9/5 13:41
*/
SysLogsVo getModule(String operUri);
}

View File

@ -1,6 +1,8 @@
package com.bonus.system.service;
import java.util.List;
import com.bonus.system.domain.SysLogsVo;
import com.bonus.system.domain.SysOperLog;
/**
@ -45,4 +47,13 @@ public interface ISysOperLogService
* 清空操作日志
*/
public void cleanOperLog();
/**
* 添加操作日志
* @param sysLogsVo
* @return void
* @author cwchen
* @date 2025/9/5 13:14
*/
void addSysOperLog(SysLogsVo sysLogsVo);
}

View File

@ -1,11 +1,18 @@
package com.bonus.system.service.impl;
import java.util.List;
import com.bonus.common.utils.global.SystemGlobal;
import com.bonus.system.domain.SysLogsVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.bonus.system.domain.SysOperLog;
import com.bonus.system.mapper.SysOperLogMapper;
import com.bonus.system.service.ISysOperLogService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
/**
* 操作日志 服务层处理
@ -13,6 +20,7 @@ import com.bonus.system.service.ISysOperLogService;
* @author bonus
*/
@Service
@Slf4j
public class SysOperLogServiceImpl implements ISysOperLogService
{
@Autowired
@ -73,4 +81,26 @@ public class SysOperLogServiceImpl implements ISysOperLogService
{
operLogMapper.cleanOperLog();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void addSysOperLog(SysLogsVo sysLogsVo) {
try {
if (sysLogsVo.getLogType() == 2) {
sysLogsVo.setWarningStatus("0");
}
if (sysLogsVo.getOperaUserName() != null) {
String str = sysLogsVo.getOperaUserName().replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_");
sysLogsVo.setOperaUserName(str);
}
if (sysLogsVo.getIp() != null) {
String str = sysLogsVo.getIp().replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_");
sysLogsVo.setIp(str);
}
operLogMapper.addSysOperLog(sysLogsVo);
} catch (Exception e) {
log.error(e.toString(),e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
}

View File

@ -33,8 +33,26 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
insert into da_ky_sys_oper_log(title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, cost_time, oper_time)
values (#{title}, #{businessType}, #{method}, #{requestMethod}, #{operatorType}, #{operName}, #{deptName}, #{operUrl}, #{operIp}, #{operLocation}, #{operParam}, #{jsonResult}, #{status}, #{errorMsg}, #{costTime}, sysdate())
</insert>
<select id="selectOperLogList" parameterType="SysOperLog" resultMap="SysOperLogResult">
<!--添加操作日志-->
<insert id="addSysOperLog">
INSERT INTO da_ky_sys_logs(
log_id,opera_user_name,ip,user_id,
model,oper_time,method,params,
operate_detail,oper_type,oper_uri,
log_type,result,times,
failure_reason,grade,err_type,
method_type,title,result_data
)values (
#{logId},#{operaUserName},#{ip},#{userId},
#{model},#{operTime},#{method},#{params},
#{operateDetail},#{operType},#{operUri},
#{logType},#{result},#{times},
#{failureReason},#{grade},#{errType},
#{methodType},#{title},#{resultData}
)
</insert>
<select id="selectOperLogList" parameterType="SysOperLog" resultMap="SysOperLogResult">
<include refid="selectOperLogVo"/>
<where>
<if test="operIp != null and operIp != ''">
@ -79,7 +97,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<include refid="selectOperLogVo"/>
where oper_id = #{operId}
</select>
<!--查询业务模块-->
<select id="getModule" resultType="com.bonus.system.domain.SysLogsVo">
select CONCAT(IFNULL(sm.menu_name,''),'->',IFNULL(sm2.menu_name,'')) module ,sm3.menu_name operateDetail
from da_ky_sys_menu sm
left join da_ky_sys_menu sm2 on sm2.parent_id=sm.menu_id and sm2.menu_type=1
left join da_ky_sys_menu sm3 on sm3.parent_id=sm2.menu_id and sm3.menu_type=2
where sm3.component=#{module}
limit 1
</select>
<update id="cleanOperLog">
truncate table da_ky_sys_oper_log
</update>