介绍
我们在日常开发工作中,肯定逃不开与日志接触,一些比较严谨的后台管理系统里面会涉及到一些比较重要的资料,有些公司为了知道有哪些人登录了系统,是谁在什么时候修改了用户信息或者资料,所以就有了操作日志这么个需求。
此文章介绍的是SpringBoot下如何通过注解的形式实现操作日志,仅供学习参考,不喜勿喷。
具体实现
pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--aspectj依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.25.RELEASE</version>
</dependency>
<!--swagger依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
<!--工具依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.32</version>
</dependency>
代码实现
- 系统日志注解
/**
* 系统日志注解
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysOperateLog {
//用户操作
String value();
//保存入参
boolean isSaveParams() default true;
//保存返回结果
boolean isSaveResult() default false;
//异常信息
boolean isRecordError() default false;
//忽略属性
String [] ignoreFields() default {};
}
- 切面处理类
/**
* 系统日志,切面处理类
*/
@Component
@Aspect
public class SysLogAspect {
private static final Logger logger = LoggerFactory.getLogger(SysLogAspect.class);
@Resource
private SysLogService sysLogService;
// 定义切点,被@SysOperateLog注解标注的方法作为切点
@Pointcut("@annotation(com.ou.common.annotation.SysOperateLog)")
public void logPointCut() {
}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
//执行方法
Object result = null;
try {
result = point.proceed();
} catch (Throwable throwable) {
//异常日志记录
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
SysOperateLog syslog = method.getAnnotation(SysOperateLog.class);
if(syslog != null && syslog.isRecordError()){
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
result = "异常返回," + throwable.getMessage();
sysLogService.aopTransactionalSysLog(point,"error!!", time);
}
throw throwable;
}
//执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
//保存日志
sysLogService.aopSysLog(point,result, time);
return result;
}
}
- 过滤器
public class FastJsonByteArrayValueFilter implements ValueFilter {
public FastJsonByteArrayValueFilter() {
}
public Object process(Object object, String name, Object value) {
try {
if (value == null) {
return value;
}
Field declaredField = object.getClass().getDeclaredField(name);
Class<?> type = declaredField.getType();
if (type.toString().equals("class [B")) {
return null;
}
} catch (NoSuchFieldException var6) {
}
return value;
}
}
- 日志实体类
/**
* 系统操作日志实体类
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value = "SysLog对象", description = "系统操作日志")
public class SysLog implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "日志主键ID")
private String id;
@ApiModelProperty(value = "用户ID")
private String userId;
private String userName;
@ApiModelProperty(value = "用户操作")
private String operation;
@ApiModelProperty(value = "请求方法")
private String method;
private String params;
private String result;
@ApiModelProperty(value = "执行时长")
private Long time;
@ApiModelProperty(value = "IP地址")
private String ip;
@ApiModelProperty(value = "逻辑删除标志,0:正常,1:删除")
private Integer delFlag;
@ApiModelProperty(value = "创建人")
private String createUser;
@ApiModelProperty(value = "创建时间")
private Date createTime;
@ApiModelProperty(value = "更新人")
private String updateUser;
@ApiModelProperty(value = "更新时间")
private Date updateTime;
}
- 业务层接口
/**
* 系统操作日志
*/
public interface SysLogService{
void aopTransactionalSysLog(ProceedingJoinPoint joinPoint, Object result, long time);
void aopSysLog(ProceedingJoinPoint joinPoint, Object result, long time);
}
- 业务层实现类
/**
* 系统操作日志 服务实现类
*/
@Service
public class SysLogServiceImpl implements SysLogService {
private static final Logger logger = LoggerFactory.getLogger(SysLogServiceImpl.class);
//MediumText
private static final int MAX_BTYE_LENGTH = 16777215;
//@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
@Override
public void aopTransactionalSysLog(ProceedingJoinPoint joinPoint, Object result, long time){
aopSysLog(joinPoint,result,time);
}
/**
* ProceedingJoinPoint继承了JoinPoint
* JoinPoint.getSignature() = 获取到了方法的【修饰符 + 包名 + 组件名(类名) +方法】
*/
@Override
public void aopSysLog(ProceedingJoinPoint joinPoint, Object result, long time) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SysLog sysLog = new SysLog();
//这里我是用UUID来模仿ID,可自行设置
//sysLog.setId(UUIDCreater.create());
SysOperateLog sysOperateLog = method.getAnnotation(SysOperateLog.class);
boolean isSaveResult = false;
boolean isSaveParams = false;
if(sysOperateLog != null){
//注解上的描述
sysLog.setOperation(sysOperateLog.value());
isSaveResult = sysOperateLog.isSaveResult();
isSaveParams = sysOperateLog.isSaveParams();
}
//请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
String[] parameterNames = signature.getParameterNames();
// 设置 com.ou.controller.SysLogController.testParam(map)
sysLog.setMethod(className + "." + methodName + "("+ StringUtils.join(parameterNames,",")+")");
try{
//保存参数信息
if(isSaveParams){
Object[] args = joinPoint.getArgs();
StringBuffer params = new StringBuffer();
for(int i = 0 ; i < args.length; i++){
if(args[i] instanceof ServletResponse){
continue;
}else if(args[i] instanceof Class){
continue;
}else if(args[i] instanceof byte[]){
continue;
} else if(args[i] instanceof ServletRequest){
ServletRequest request = (ServletRequest)args[i];
params.append(JSON.toJSONString(request.getParameterMap()));
}
else {
Object json = JSON.toJSON(args[i]);
String jsonString = JSON.toJSONString(json, new ValueFilter[]{new FastJsonByteArrayValueFilter()});
params.append(jsonString);
}
if(i < args.length - 1){
params.append("; ");
}
}
byte[] paramsBytes = params.toString().getBytes();
if(paramsBytes.length > MAX_BTYE_LENGTH){
byte[] subBytes = new byte[MAX_BTYE_LENGTH];
System.arraycopy(paramsBytes, 0, subBytes, 0, MAX_BTYE_LENGTH);
sysLog.setParams(new String(subBytes));
}else{
sysLog.setParams(params.toString());
}
}
//保存返回结果
if(isSaveResult && result != null){
Object json = JSON.toJSON(result);
String[] ignoreFields = sysOperateLog.ignoreFields();
if(ignoreFields.length > 0){
if(json instanceof JSONObject){
JSONObject jsonObject = (JSONObject) json;
for(String field : ignoreFields){
jsonObject.remove(field);
}
}else if(json instanceof JSONArray){
JSONArray jsonArray = (JSONArray) json;
for(Object obj : jsonArray){
if(obj instanceof JSONObject){
JSONObject jsonObject = (JSONObject) obj;
for(String field : ignoreFields){
jsonObject.remove(field);
}
}
}
}
}
String transferResult = JSON.toJSONString(json, new ValueFilter[]{new FastJsonByteArrayValueFilter()});
byte[] transferResultBytes = transferResult.toString().getBytes();
if(transferResultBytes.length > MAX_BTYE_LENGTH){
byte[] subBytes = new byte[MAX_BTYE_LENGTH];
System.arraycopy(transferResultBytes, 0, subBytes, 0, MAX_BTYE_LENGTH);
sysLog.setResult(new String(subBytes));
}else{
sysLog.setResult(transferResult);
}
}
try {
//获取request
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
//设置IP地址
sysLog.setIp(IPUtils.getIpAddr(request));
} catch (Exception e) {
}
sysLog.setTime(time);
//保存系统日志--这里是打印
logger.info("系统日志:{}",JSONObject.toJSONString(sysLog));
}catch (Exception e){
logger.error("系统日志记录失败:",e);
}
}
}
- 工具类
public class HttpContextUtils {
public static HttpServletRequest getHttpServletRequest() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
}
/**
* 获取IP地址的工具类
*/
public class IPUtils {
private static Logger logger = LoggerFactory.getLogger(IPUtils.class);
/**
* 获取IP地址
*/
public static String getIpAddr(HttpServletRequest request) {
String ip = null;
try {
ip = request.getHeader("x-forwarded-for");
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
} catch (Exception e) {
logger.error("IPUtils ERROR ", e);
}
return ip;
}
}
- Controller接口
@RestController
@RequestMapping("/sys/log")
public class SysLogController {
/**
* 触发操作日志
*/
@SysOperateLog("触发-操作日志接口-不带参数")
@RequestMapping("/test")
public String test() {
return "OK";
}
/**
* 触发操作日志
*/
@SysOperateLog("触发-操作日志接口-带参数")
@RequestMapping("/testParam")
public String testParam(@RequestBody Map map) {
return "OK";
}
}
测试
控制台输出:
系统日志:{"id":"9e088286efe64183aa5992f30a8aa34b","ip":"127.0.0.1","method":"com.ou.controller.SysLogController.testParam(map)","operation":"触发-操作日志接口-带参数","params":"{\"sex\":\"男\",\"name\":\"张三\",\"age\":18,\"desc\":\"描述啊啊啊\"}","time":10}
我这里是直接输出日志,具体怎么存库还是由各自的业务来进行修改。
--------END