说明:
直接上效果图
系统日志列表
一个成熟的系统,应对用户的某些增删改操作,特别是管理员的增删改操作进行日志持久化处理。这些功能基本包括了用户的操作日志。那么我们要对一个完整的操作记录,其单位就是方法。通过AOP的环绕通知可以把切点的记录在内,得到日志并持久化处理。那么就不废话直接上设计了。
pojo
import lombok.Data;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Date;
/**
* 系统日志实体定义
*
*/
@Entity
@Table(name = "ABC_SYSTEM_LOG")
@Data
public class SystemLog
{
public static final int STATUS_NORMAL = 0;
public static final int STATUS_PAUSE = 1;
public static final String DEFAULT_OWNER_TYPE = "SYS";
public static final String DEFAULT_OWNER = "0";
/**
* 标识
*/
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
/**
* 应用端
*/
@Column(name = "app_name")
@Size(max = 255)
private String appName;
/**
* 日志类型:0为操作日志,1为异常日志
*/
@Column(name = "log_type")
private int logType;
/**
* 访问者/请求者
*/
@Column(name = "user")
@Size(max = 255)
private String user;
/**
* 账号类型
*/
@Column(name = "user_type")
@Size(max = 255)
private String userType;
/**
* 方法名称
*/
@Column(name = "method_name")
@Size(max = 255)
private String methodName;
/**
* 请求参数
*/
@Lob
@Column(name = "request_params")
private String requestParams;
/**
* 方法描述
*/
@Column(name="method_description")
@Size(max = 255)
private String methodDescription;
/**
* 访问ip
*/
@Column(name="request_ip")
@Size(max = 255)
private String requestIp;
/**
* 请求URL
*/
@Column(name="request_uri")
@Size(max = 255)
private String requestUri;
/**
* 请求PATH
*/
@Column(name="request_path")
@Size(max = 255)
private String requestPath;
/**
* 异常码
*/
@Column(name="exception_code")
@Size(max = 255)
private String exceptionCode;
/**
* 异常描述
*/
@Column(name="exception_detail")
@Size(max = 255)
private String exceptionDetail;
/**
* d
*/
@Column(name="request_status")
private int requestStatus;
/**
* 备注
*/
@Lob
@Column(name = "remark")
private String remark;
/**
* 状态
*/
@Column(name = "status")
private int status = STATUS_NORMAL;
/**
* 所有者
*/
@NotNull
@Size(max = 32)
@Column(name = "owner", updatable = false)
private String owner = DEFAULT_OWNER;
/**
* 所有者类型
*/
@Size(max = 32)
@Column(name = "owner_type", updatable = false)
private String ownerType = DEFAULT_OWNER_TYPE;
/**
* 创建人/时间,更新人/时间
*/
@Column(name = "creator", nullable = false, updatable = false)
private String creator;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "create_date", nullable = false, updatable = false)
private Date creationDate = new Date();
@Column(name = "modifier", insertable = false, updatable = true)
private String modifier;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "modify_date", insertable = false, updatable = true)
private Date modifyDate;
}
注解
import java.lang.annotation.*;
/**
*自定义注解 拦截Controller
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemControllerLog {
String description() default "";
}
切点
import com.geek.abc.core.systemLog.SystemLog;
import com.geek.abc.core.systemLog.SystemLogManager;
import com.geek.abc.core.util.DateHelper;
import com.jade.bss.organization.account.AdminAccount;
import com.jade.bss.organization.customer.Customer;
import com.jade.framework.base.context.ApplicationContextUtils;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
/**
* 系统日志 切面
* optimized by_liaoqian 20191212
*/
@Aspect
@Component
@SuppressAjWarnings("all")
@Slf4j
public class SystemLogAspect {
private SystemLogManager getSystemLogManager() {
return ApplicationContextUtils.getBean("bss_systemLog_manager");
}
/**
* Service层切点
*/
@Pointcut("@annotation(com.geek.abc.core.systemLog.aopHandle.SystemServiceLog)")
public void serviceAspect() {
}
/**
* Controller层切点
*/
@Pointcut("@annotation(com.geek.abc.core.systemLog.aopHandle.SystemControllerLog)")
public void controllerAspect() {
}
/**
* 环绕通知 用于拦截controller层记录用户的操作
* @param proceeding 切点
*/
@Around("controllerAspect()")
@SuppressAjWarnings("all")
public Object aroundAdvice(ProceedingJoinPoint proceeding) throws Exception {
Object[] args=proceeding.getArgs();
Object result=null;
String userName = "未识别";
String userType = "未识别";
String appName = "";
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
if (SecurityUtils.getSubject() != null) {
if (SecurityUtils.getSubject().getPrincipal() != null) {
String userClassStr = SecurityUtils.getSubject().getPrincipal().toString();
if (userClassStr.contains("com.jade.bss.organization.account.AdminAccount")) {
AdminAccount adminAccount = (AdminAccount) SecurityUtils.getSubject().getPrincipal();
userName = adminAccount.getName();
userType = adminAccount.getType();
} else if (SecurityUtils.getSubject().getPrincipal().toString().contains("com.jade.bss.organization.customer.Customer")) {
Customer customer = (Customer) SecurityUtils.getSubject().getPrincipal();
userName = customer.getName();
userType = customer.getOwnerType();
}
}
}
if (userType.equals("MEMBER")) {
appName = "client";
} else {
appName = "web";
}
String params = "";
System.out.println(proceeding.getArgs());
if (proceeding.getArgs() != null && proceeding.getArgs().length > 0) {
for (int i = 0; i < proceeding.getArgs().length; i++) {
if (proceeding.getArgs()[i] != null) {
params += proceeding.getArgs()[i].toString() + ";";
if (proceeding.getArgs()[i].toString().contains("com.geek") || proceeding.getArgs()[i].toString().contains("com.jade.abc")) {
Object object = proceeding.getArgs()[i];
JSONObject json = JSONObject.fromObject(object);
params += json.toString();
}
}
}
}
SystemLog systemLog = new SystemLog();
try {
log.info("=====前置通知开始=====");
System.out.println("请求方法:" + (proceeding.getTarget().getClass().getName() + "." + proceeding.getSignature().getName() + "()"));
System.out.println("方法描述:" + getControllerMethodDescription(proceeding));
System.out.println("请求路径:" + request.getRequestURI());
System.out.println("请求参数:" + request.getQueryString());
System.out.println("请求人:" + userName);
System.out.println("请求人类型:" + userType);
System.out.println("请求IP:" + request.getRemoteAddr());
System.out.println("日期:" + DateHelper.getCurDateTime());
// tip:执行连接点的方法 获取返回值 必不可少!!!
result=proceeding.proceed(args);
systemLog.setAppName(appName);
systemLog.setCreator(userName);
systemLog.setLogType(0);
systemLog.setUser(userName);
systemLog.setUserType(userType);
systemLog.setMethodDescription(getControllerMethodDescription(proceeding));
systemLog.setMethodName(proceeding.getTarget().getClass().getName() + "." + proceeding.getSignature().getName() + "()");
systemLog.setExceptionCode(null);
systemLog.setExceptionDetail(null);
systemLog.setRequestIp(request.getRemoteAddr());
systemLog.setRequestParams(request.getQueryString());
systemLog.setRequestPath(request.getServletPath());
systemLog.setRequestUri(request.getRequestURI());
systemLog.setRequestStatus(200);
systemLog.setRemark(params);
// 初次登录,无法识别,所以通过截取参数获取,保留
if ("用户登录".equals(systemLog.getMethodDescription())
&& "未识别".equals(userName) && "未识别".equals(userType)) {
String name = request.getQueryString().split("=")[2];
systemLog.setAppName("client");
systemLog.setCreator(name);
systemLog.setUser(name);
systemLog.setUserType("MEMBER");
}
log.info("=====后置通知结束=====");
} catch (Throwable e) {
log.error("=====异常通知开始=====");
systemLog.setAppName(appName);
systemLog.setCreator(userName);
systemLog.setLogType(1);
systemLog.setUser(userName);
systemLog.setUserType(userType);
systemLog.setMethodDescription(getControllerMethodDescription(proceeding));
systemLog.setMethodName(proceeding.getTarget().getClass().getName() + "." + proceeding.getSignature().getName() + "()");
systemLog.setExceptionCode(e.getClass().getName());
systemLog.setExceptionDetail(e.getMessage());
systemLog.setRequestIp(request.getRemoteAddr());
systemLog.setRequestParams(request.getQueryString());
systemLog.setRequestPath(request.getServletPath());
systemLog.setRequestUri(request.getRequestURI());
systemLog.setRequestStatus(500);
systemLog.setRemark(params);
log.error("=====异常通知结束=====");
} finally {
log.info("=====最终通知开始=====");
getSystemLogManager().addSystemLog(systemLog);
}
return result;
}
/**
* 获取注解中对方法的描述信息 用于service层注解
*
* @param joinPoint 切点
* @return 方法描述
* @throws Exception
*/
public static String getServiceMthodDescription(JoinPoint joinPoint)
throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
String description = "";
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
description = method.getAnnotation(SystemServiceLog.class).description();
break;
}
}
}
return description;
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param joinPoint 切点
* @return 方法描述
* @throws Exception
*/
public static String getControllerMethodDescription(JoinPoint joinPoint) throws Exception {
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
String description = "";
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
description = method.getAnnotation(SystemControllerLog.class).description();
break;
}
}
}
return description;
}
}
重点在于切点的环绕通知写法,在切点的aroundAdvice方法内,由于采用的是远古的ssh项目,一些非必要的dao层和service层就没有写出来,那么controller的实现方式也特别简单。
事例
/**
* 删除会员
* @param id
* @param response
* @throws Exception
*/
@RequestMapping(value = "/remove")
@RequiresAuthentication
@CheckPermission(permission = "member_remove")
@SystemControllerLog(description = "删除会员信息")
public void removeMember(@RequestParam(value = "id") long[] id, HttpServletResponse response) throws Exception {
try {
getMemberManager().removeMember( id );
ResponseUtil.writeSuccessResult( response );
} catch (BssException e) {
e.printStackTrace();
ResponseUtil.writeErrorResult( response, STATUS_ERROR, e.getMessage() );
}
}
就是一个简单的注解开发
@SystemControllerLog(description = "方法描述")
那么看一下实际运行图
操作正常下:
标题
操作异常:
标题
日志列表:
标题