aop+自定义注解实现日志功能(参数前后变化)
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.18</version>
</dependency>
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.21</version>
</dependency>
<!-- SpringBoot 核心包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
package com.aisino.project.monitor.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.aisino.framework.aspectj.lang.annotation.Excel;
import com.aisino.framework.aspectj.lang.annotation.Excel.ColumnType;
import com.aisino.framework.web.domain.BaseEntity;
/**
* 操作日志记录表 oper_log
*
* @author aisino
*/
public class SysOperLog extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 日志主键
*/
@Excel(name = "操作序号" , cellType = ColumnType.NUMERIC)
private Long operId;
/**
* 操作模块
*/
@Excel(name = "操作模块")
private String title;
/**
* 业务类型(0其它 1新增 2修改 3删除)
*/
@Excel(name = "业务类型" , readConverterExp = "0=其它,1=新增,2=修改,3=删除,4=授权,5=导出,6=导入,7=强退,8=生成代码,9=清空数据")
private Integer businessType;
/**
* 业务类型数组
*/
private Integer[] businessTypes;
/**
* 请求方法
*/
@Excel(name = "请求方法")
private String method;
/**
* 请求方式
*/
@Excel(name = "请求方式")
private String requestMethod;
/**
* 操作类别(0其它 1后台用户 2手机端用户)
*/
@Excel(name = "操作类别" , readConverterExp = "0=其它,1=后台用户,2=手机端用户")
private Integer operatorType;
/**
* 操作人员
*/
@Excel(name = "操作人员")
private String operName;
/**
* 部门名称
*/
@Excel(name = "部门名称")
private String deptName;
/**
* 请求url
*/
@Excel(name = "请求地址")
private String operUrl;
/**
* 操作地址
*/
@Excel(name = "操作地址")
private String operIp;
/**
* 操作地点
*/
@Excel(name = "操作地点")
private String operLocation;
/**
* 请求参数
*/
@Excel(name = "请求参数")
private String operParam;
/**
* 原有请求参数
*/
@Excel(name = "原有请求参数")
private String operInitParam;
/**
* 返回参数
*/
@Excel(name = "返回参数")
private String jsonResult;
/**
* 操作状态(0正常 1异常)
*/
@Excel(name = "状态" , readConverterExp = "0=正常,1=异常")
private Integer status;
/**
* 错误消息
*/
@Excel(name = "错误消息")
private String errorMsg;
/**
* 操作时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Excel(name = "操作时间" , width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date operTime;
public String getOperInitParam() {
return operInitParam;
}
public void setOperInitParam(String operInitParam) {
this.operInitParam = operInitParam;
}
public Long getOperId() {
return operId;
}
public void setOperId(Long operId) {
this.operId = operId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Integer getBusinessType() {
return businessType;
}
public void setBusinessType(Integer businessType) {
this.businessType = businessType;
}
public Integer[] getBusinessTypes() {
return businessTypes;
}
public void setBusinessTypes(Integer[] businessTypes) {
this.businessTypes = businessTypes;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getRequestMethod() {
return requestMethod;
}
public void setRequestMethod(String requestMethod) {
this.requestMethod = requestMethod;
}
public Integer getOperatorType() {
return operatorType;
}
public void setOperatorType(Integer operatorType) {
this.operatorType = operatorType;
}
public String getOperName() {
return operName;
}
public void setOperName(String operName) {
this.operName = operName;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public String getOperUrl() {
return operUrl;
}
public void setOperUrl(String operUrl) {
this.operUrl = operUrl;
}
public String getOperIp() {
return operIp;
}
public void setOperIp(String operIp) {
this.operIp = operIp;
}
public String getOperLocation() {
return operLocation;
}
public void setOperLocation(String operLocation) {
this.operLocation = operLocation;
}
public String getOperParam() {
return operParam;
}
public void setOperParam(String operParam) {
this.operParam = operParam;
}
public String getJsonResult() {
return jsonResult;
}
public void setJsonResult(String jsonResult) {
this.jsonResult = jsonResult;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
public Date getOperTime() {
return operTime;
}
public void setOperTime(Date operTime) {
this.operTime = operTime;
}
}
自定义注解
package com.aisino.framework.aspectj.lang.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.aisino.framework.aspectj.lang.enums.BusinessType;
import com.aisino.framework.aspectj.lang.enums.OperatorType;
/**
* 自定义操作日志记录注解
*
* @author aisino
*
*/
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log
{
/**
* 模块
*/
public String title() default "";
/**
* 功能
*/
public BusinessType businessType() default BusinessType.OTHER;
/**
* 操作人类别
*/
public OperatorType operatorType() default OperatorType.MANAGE;
/**
* 是否保存请求的参数
*/
public boolean isSaveRequestData() default true;
/**
* 是否保存响应的参数
*/
public boolean isSaveResponseData() default true;
/**查询的bean名称*/
String serviceClass() default "";
/**查询单个详情的bean的方法*/
String queryMethod() default "";
/**查询详情的参数类型*/
String parameterType() default "";
/**从页面参数中解析出要查询的id,
* 如域名修改中要从参数中获取customerDomainId的值进行查询
*/
String parameterKey() default "";
}
获取springBean容器
package com.aisino.framework.aspectj;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* 获取spring容器,以访问容器中定义的其他bean
* xiang
* MOSTsView 3.0 2009-11-16
*/
@Component
public class SpringContextUtil implements ApplicationContextAware{
private static ApplicationContext applicationContext;
/**
* 实现ApplicationContextAware接口的回调方法,设置上下文环境
*/
public void setApplicationContext(ApplicationContext applicationContext){
SpringContextUtil.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext(){
return applicationContext;
}
/**
* 获取对象
* @return Object 一个以所给名字注册的bean的实例 (service注解方式,自动生成以首字母小写的类名为bean name)
*/
public static Object getBean(String name) throws BeansException{
return applicationContext.getBean(name);
}
}
编写aop前置 后置通知 (里面细节不全 需要自己填写)
package com.aisino.framework.aspectj;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.ObjectUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.aisino.common.utils.StringUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;
import com.alibaba.fastjson.JSON;
import com.aisino.common.enums.HttpMethod;
import com.aisino.common.utils.SecurityUtils;
import com.aisino.common.utils.ServletUtils;
import com.aisino.common.utils.ip.IpUtils;
import com.aisino.framework.aspectj.lang.annotation.Log;
import com.aisino.framework.aspectj.lang.enums.BusinessStatus;
import com.aisino.framework.manager.AsyncManager;
import com.aisino.framework.manager.factory.AsyncFactory;
import com.aisino.framework.security.LoginUser;
import com.aisino.project.monitor.domain.SysOperLog;
/**
* 操作日志记录处理
*
* @author aisino
*/
@Aspect
@Component
public class LogAspect
{
private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
private static String ORDERJSON =null;
/**
* @desc : 处理前执行
* @Return :
* @Author : Jerry
* @Date : Created in 2022-9-21 9:27
**/
//前置通知, 在方法执行之前执行
@Before(value = "@annotation(controllerLog)")
public void before(JoinPoint joinPoint, Log controllerLog) {
//常见日志实体对象
// 拦截的实体类,就是当前正在执行的controller
Object target = joinPoint.getTarget();
// 拦截的方法名称。当前正在执行的方法
String methodName = joinPoint.getSignature().getName();
// 拦截的放参数类型
Signature sig = joinPoint.getSignature();
MethodSignature msig = null;
if (!(sig instanceof MethodSignature)) {
throw new IllegalArgumentException("该注解只能用于方法");
}
// 判断请求类型
// 判断参数类型格式-对应取出里面key值
msig = (MethodSignature) sig;
Class[] parameterTypes = msig.getMethod().getParameterTypes();
// 获得被拦截的方法
Method method = null;
try {
method = target.getClass().getMethod(methodName, parameterTypes);
} catch (NoSuchMethodException e1) {
System.out.println(e1);
} catch (SecurityException e1) {
System.out.println(e1);
}
Log systemlog = method.getAnnotation(Log.class);
String queryMethod = systemlog.queryMethod();
//查询
if (methodName.equals("query")){
}
//新增
if (methodName.equals("add")){
ORDERJSON = "新增方法暂无旧数据";
}
//删除
if (methodName.equals("remove")){
Object[] args = joinPoint.getArgs();
for (int i = 0; i < args.length; i++) {
Object paramsObj = args[i];
String param = judgeType(paramsObj);
//批量删除
if (param.indexOf("[]")!=-1){
if (param.equals("Long[]")){
Long[] longs = (Long[])paramsObj;
for (Long l : longs){
Object data = getOperateBeforeData(controllerLog.parameterType(), controllerLog.serviceClass(), queryMethod, l.toString());
JSONObject jsonObject = (JSONObject) JSON.toJSON(data);
ORDERJSON +=jsonObject;
}
}
}else{
//单次删除
Object data = getOperateBeforeData(controllerLog.parameterType(), controllerLog.serviceClass(), queryMethod, paramsObj.toString());
JSONObject jsonObject = (JSONObject) JSON.toJSON(data);
ORDERJSON = jsonObject.toString();
}
}
}
//导入
if (methodName.equals("export")){
ORDERJSON = "导入方法暂无旧数据";
}
//编辑
if (methodName.equals("edit")){
Object[] args = joinPoint.getArgs();
Object outprint ="";
for (int i = 0; i < args.length; i++) {
Object paramsObj = args[i];
Map<String,Object> map = JSONObject.parseObject(JSON.toJSONString(paramsObj));
outprint = map.get(controllerLog.parameterKey());
}
if (!ObjectUtils.isEmpty(outprint)){
Object data = getOperateBeforeData(controllerLog.parameterType(), controllerLog.serviceClass(), queryMethod, outprint.toString());
JSONObject jsonObject = (JSONObject) JSON.toJSON(data);
ORDERJSON = jsonObject.toString();
}
}
}
/**
* @param obj 需要判断类型的值
*/
public String judgeType(Object obj){
if (obj instanceof Boolean){
//进行你的逻辑处理
return "Boolean";
}else if (obj instanceof Byte){
return "Byte";
}else if (obj instanceof Character){
return "Character";
}else if (obj instanceof Short){
return "Short";
}else if (obj instanceof Integer){
return "Integer";
}else if (obj instanceof Long){
return "Long";
}else if (obj instanceof Float){
return "Float";
}else if (obj instanceof Double){
return "Double";
}else if (obj instanceof String){
return "String";
}else if (obj instanceof Date){
return "Date";
}else if (obj instanceof Array){
return "Array";
}else if (obj instanceof Map){
return "Map";
}else if (obj instanceof List){
return "List";
}else if (obj instanceof BigDecimal){
return "BigDecimal";
}else if (obj instanceof BigInteger){
return "BigInteger";
}else if (obj instanceof Stack){
return "Stack";
}else if (obj instanceof Long[]){
return "Long[]";
}else {
return "未知类型--"+obj.getClass();
}
}
/**
*
* 功能描述: <br>
* 〈功能详细描述〉
*
* @param paramType:参数类型
* @param serviceClass:bean名称
* @param queryMethod:查询method
* @param value:查询id的value
* @return
* @see [相关类/方法](可选)
* @since [产品/模块版本](可选)
*/
public Object getOperateBeforeData(String paramType,String serviceClass,String queryMethod,String value){
Object obj = new Object();
//在此处解析请求的参数类型,根据id查询数据,id类型有四种:int,Integer,long,Long
if(paramType.equals("int")){
int id = Integer.parseInt(value);
Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), queryMethod,Long.class );
//传入的id类型与bean里面的参数类型需要保持一致
obj = ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(serviceClass),id);
}else if(paramType.equals("Integer")){
Integer id = Integer.valueOf(value);
Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), queryMethod,Long.class );
//传入的id类型与bean里面的参数类型需要保持一致
obj = ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(serviceClass),id);
}else if(paramType.equals("long")){
long id = Long.parseLong(value);
Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), queryMethod,Long.class );
//传入的id类型与bean里面的参数类型需要保持一致
obj = ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(serviceClass),id);
}else if(paramType.equals("Long")){
Long id = Long.valueOf(value);
Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), queryMethod,Long.class );
//传入的id类型与bean里面的参数类型需要保持一致
obj = ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(serviceClass),id);
}
return obj;
}
/**
* 获取当前执行的方法
*
* @param joinPoint 连接点
* @param methodName 方法名称
* @return 方法
*/
private Method currentMethod(JoinPoint joinPoint, String methodName) {
/**
* 获取目标类的所有方法,找到当前要执行的方法
*/
Method[] methods = joinPoint.getTarget().getClass().getMethods();
Method resultMethod = null;
for (Method method : methods) {
if (method.getName().equals(methodName)) {
resultMethod = method;
break;
}
}
return resultMethod;
}
/**
* 处理完请求后执行
*
* @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
{
// 获取当前的用户
LoginUser loginUser = SecurityUtils.getLoginUser();
// *========数据库日志=========*//
SysOperLog operLog = new SysOperLog();
operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址
String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
operLog.setOperIp(ip);
operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
if (loginUser != null)
{
operLog.setOperName(loginUser.getUsername());
}
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());
operLog.setOperInitParam(ORDERJSON);
// 处理设置注解上的参数
getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
// 保存数据库
AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
ORDERJSON = "";
}
catch (Exception exp)
{
// 记录本地异常日志
log.error("==前置通知异常==");
log.error("异常信息:{}", exp.getMessage());
exp.printStackTrace();
}
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param log 日志
* @param operLog 操作日志
* @throws Exception
*/
public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog 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);
}
// 是否需要保存response,参数和值
if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult))
{
operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
}
}
/**
* 获取请求的参数,放到log中
*
* @param operLog 操作日志
* @throws Exception 异常
*/
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception
{
String requestMethod = operLog.getRequestMethod();
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))
{
String params = argsArrayToString(joinPoint.getArgs());
Map maps = (Map)JSON.parse(params);
Map hashMap = new HashMap<>();
for (Object map : maps.entrySet()){
if (((Map.Entry)map).getKey().equals("objJson")){
operLog.setOperInitParam(((Map.Entry)map).getValue().toString());
}else{
hashMap.put(((Map.Entry)map).getKey(),((Map.Entry)map).getValue());
}
}
String json = JSONObject.toJSONString(hashMap);
operLog.setOperParam(StringUtils.substring(json, 0, 2000));
}
else
{
Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
}
}
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray)
{
String params = "";
if (paramsArray != null && paramsArray.length > 0)
{
for (Object o : paramsArray)
{
if (StringUtils.isNotNull(o) && !isFilterObject(o))
{
try
{
Object jsonObj = JSON.toJSON(o);
params += jsonObj.toString() + " ";
}
catch (Exception e)
{
}
}
}
}
return params.trim();
}
/**
* 判断是否需要过滤的对象。
*
* @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;
}
}
controller层调用
serviceClass:填写自己在修改、删除之前查询 需要调用的 service
queryMethod:填写自己 对应serve层根据id查询的方法
parameterType:填写 自己 主键 id 类型
parameterKey:填写 自己 主键昵称
另外 需要在自己的实现层标明 service名称,不然找不到
@Service(“ISysOrganService”)
/**
* 删除组织管理
*/
@Log(title = "组织管理", businessType = BusinessType.DELETE,
serviceClass="ISysOrganService",
queryMethod="selectSysOrganById",
parameterType="Long",
parameterKey="id")
@DeleteMapping("/{id}")
public AjaxResult remove(@PathVariable Long id)
{
return sysOrganService.deleteSysOrganById(id);
}