用户操作系统日志 记录用户操作记录日志持久化 springAOP切点实现方式 可以填加用户的任何类型的日志 正常+异常记录

springAOP实现操作日志记录,并记录请求参数与编辑前后字段的具体改变
原文
参考

说明:

直接上效果图

列表效果展示
系统日志列表

一个成熟的系统,应对用户的某些增删改操作,特别是管理员的增删改操作进行日志持久化处理。这些功能基本包括了用户的操作日志。那么我们要对一个完整的操作记录,其单位就是方法。通过AOP的环绕通知可以把切点的记录在内,得到日志并持久化处理。那么就不废话直接上设计了。

pojo

  1. import lombok.Data;
  2. import javax.persistence.*;
  3. import javax.validation.constraints.NotNull;
  4. import javax.validation.constraints.Size;
  5. import java.util.Date;
  6. /**
  7. * 系统日志实体定义
  8. *
  9. */
  10. @Entity
  11. @Table(name = "ABC_SYSTEM_LOG")
  12. @Data
  13. public class SystemLog
  14. {
  15. public static final int STATUS_NORMAL = 0;
  16. public static final int STATUS_PAUSE = 1;
  17. public static final String DEFAULT_OWNER_TYPE = "SYS";
  18. public static final String DEFAULT_OWNER = "0";
  19. /**
  20. * 标识
  21. */
  22. @Id
  23. @GeneratedValue(strategy = GenerationType.AUTO)
  24. private int id;
  25. /**
  26. * 应用端
  27. */
  28. @Column(name = "app_name")
  29. @Size(max = 255)
  30. private String appName;
  31. /**
  32. * 日志类型:0为操作日志,1为异常日志
  33. */
  34. @Column(name = "log_type")
  35. private int logType;
  36. /**
  37. * 访问者/请求者
  38. */
  39. @Column(name = "user")
  40. @Size(max = 255)
  41. private String user;
  42. /**
  43. * 账号类型
  44. */
  45. @Column(name = "user_type")
  46. @Size(max = 255)
  47. private String userType;
  48. /**
  49. * 方法名称
  50. */
  51. @Column(name = "method_name")
  52. @Size(max = 255)
  53. private String methodName;
  54. /**
  55. * 请求参数
  56. */
  57. @Lob
  58. @Column(name = "request_params")
  59. private String requestParams;
  60. /**
  61. * 方法描述
  62. */
  63. @Column(name="method_description")
  64. @Size(max = 255)
  65. private String methodDescription;
  66. /**
  67. * 访问ip
  68. */
  69. @Column(name="request_ip")
  70. @Size(max = 255)
  71. private String requestIp;
  72. /**
  73. * 请求URL
  74. */
  75. @Column(name="request_uri")
  76. @Size(max = 255)
  77. private String requestUri;
  78. /**
  79. * 请求PATH
  80. */
  81. @Column(name="request_path")
  82. @Size(max = 255)
  83. private String requestPath;
  84. /**
  85. * 异常码
  86. */
  87. @Column(name="exception_code")
  88. @Size(max = 255)
  89. private String exceptionCode;
  90. /**
  91. * 异常描述
  92. */
  93. @Column(name="exception_detail")
  94. @Size(max = 255)
  95. private String exceptionDetail;
  96. /**
  97. * d
  98. */
  99. @Column(name="request_status")
  100. private int requestStatus;
  101. /**
  102. * 备注
  103. */
  104. @Lob
  105. @Column(name = "remark")
  106. private String remark;
  107. /**
  108. * 状态
  109. */
  110. @Column(name = "status")
  111. private int status = STATUS_NORMAL;
  112. /**
  113. * 所有者
  114. */
  115. @NotNull
  116. @Size(max = 32)
  117. @Column(name = "owner", updatable = false)
  118. private String owner = DEFAULT_OWNER;
  119. /**
  120. * 所有者类型
  121. */
  122. @Size(max = 32)
  123. @Column(name = "owner_type", updatable = false)
  124. private String ownerType = DEFAULT_OWNER_TYPE;
  125. /**
  126. * 创建人/时间,更新人/时间
  127. */
  128. @Column(name = "creator", nullable = false, updatable = false)
  129. private String creator;
  130. @Temporal(TemporalType.TIMESTAMP)
  131. @Column(name = "create_date", nullable = false, updatable = false)
  132. private Date creationDate = new Date();
  133. @Column(name = "modifier", insertable = false, updatable = true)
  134. private String modifier;
  135. @Temporal(TemporalType.TIMESTAMP)
  136. @Column(name = "modify_date", insertable = false, updatable = true)
  137. private Date modifyDate;
  138. }

注解

  1. import java.lang.annotation.*;
  2. /**
  3. *自定义注解 拦截Controller
  4. */
  5. @Target({ElementType.PARAMETER, ElementType.METHOD})
  6. @Retention(RetentionPolicy.RUNTIME)
  7. @Documented
  8. public @interface SystemControllerLog {
  9. String description() default "";
  10. }

切点

  1. import com.geek.abc.core.systemLog.SystemLog;
  2. import com.geek.abc.core.systemLog.SystemLogManager;
  3. import com.geek.abc.core.util.DateHelper;
  4. import com.jade.bss.organization.account.AdminAccount;
  5. import com.jade.bss.organization.customer.Customer;
  6. import com.jade.framework.base.context.ApplicationContextUtils;
  7. import lombok.extern.slf4j.Slf4j;
  8. import net.sf.json.JSONObject;
  9. import org.apache.shiro.SecurityUtils;
  10. import org.aspectj.lang.JoinPoint;
  11. import org.aspectj.lang.ProceedingJoinPoint;
  12. import org.aspectj.lang.annotation.*;
  13. import org.springframework.stereotype.Component;
  14. import org.springframework.web.context.request.RequestContextHolder;
  15. import org.springframework.web.context.request.ServletRequestAttributes;
  16. import javax.servlet.http.HttpServletRequest;
  17. import java.lang.reflect.Method;
  18. /**
  19. * 系统日志 切面
  20. * optimized by_liaoqian 20191212
  21. */
  22. @Aspect
  23. @Component
  24. @SuppressAjWarnings("all")
  25. @Slf4j
  26. public class SystemLogAspect {
  27. private SystemLogManager getSystemLogManager() {
  28. return ApplicationContextUtils.getBean("bss_systemLog_manager");
  29. }
  30. /**
  31. * Service层切点
  32. */
  33. @Pointcut("@annotation(com.geek.abc.core.systemLog.aopHandle.SystemServiceLog)")
  34. public void serviceAspect() {
  35. }
  36. /**
  37. * Controller层切点
  38. */
  39. @Pointcut("@annotation(com.geek.abc.core.systemLog.aopHandle.SystemControllerLog)")
  40. public void controllerAspect() {
  41. }
  42. /**
  43. * 环绕通知 用于拦截controller层记录用户的操作
  44. * @param proceeding 切点
  45. */
  46. @Around("controllerAspect()")
  47. @SuppressAjWarnings("all")
  48. public Object aroundAdvice(ProceedingJoinPoint proceeding) throws Exception {
  49. Object[] args=proceeding.getArgs();
  50. Object result=null;
  51. String userName = "未识别";
  52. String userType = "未识别";
  53. String appName = "";
  54. HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
  55. if (SecurityUtils.getSubject() != null) {
  56. if (SecurityUtils.getSubject().getPrincipal() != null) {
  57. String userClassStr = SecurityUtils.getSubject().getPrincipal().toString();
  58. if (userClassStr.contains("com.jade.bss.organization.account.AdminAccount")) {
  59. AdminAccount adminAccount = (AdminAccount) SecurityUtils.getSubject().getPrincipal();
  60. userName = adminAccount.getName();
  61. userType = adminAccount.getType();
  62. } else if (SecurityUtils.getSubject().getPrincipal().toString().contains("com.jade.bss.organization.customer.Customer")) {
  63. Customer customer = (Customer) SecurityUtils.getSubject().getPrincipal();
  64. userName = customer.getName();
  65. userType = customer.getOwnerType();
  66. }
  67. }
  68. }
  69. if (userType.equals("MEMBER")) {
  70. appName = "client";
  71. } else {
  72. appName = "web";
  73. }
  74. String params = "";
  75. System.out.println(proceeding.getArgs());
  76. if (proceeding.getArgs() != null && proceeding.getArgs().length > 0) {
  77. for (int i = 0; i < proceeding.getArgs().length; i++) {
  78. if (proceeding.getArgs()[i] != null) {
  79. params += proceeding.getArgs()[i].toString() + ";";
  80. if (proceeding.getArgs()[i].toString().contains("com.geek") || proceeding.getArgs()[i].toString().contains("com.jade.abc")) {
  81. Object object = proceeding.getArgs()[i];
  82. JSONObject json = JSONObject.fromObject(object);
  83. params += json.toString();
  84. }
  85. }
  86. }
  87. }
  88. SystemLog systemLog = new SystemLog();
  89. try {
  90. log.info("=====前置通知开始=====");
  91. System.out.println("请求方法:" + (proceeding.getTarget().getClass().getName() + "." + proceeding.getSignature().getName() + "()"));
  92. System.out.println("方法描述:" + getControllerMethodDescription(proceeding));
  93. System.out.println("请求路径:" + request.getRequestURI());
  94. System.out.println("请求参数:" + request.getQueryString());
  95. System.out.println("请求人:" + userName);
  96. System.out.println("请求人类型:" + userType);
  97. System.out.println("请求IP:" + request.getRemoteAddr());
  98. System.out.println("日期:" + DateHelper.getCurDateTime());
  99. // tip:执行连接点的方法 获取返回值 必不可少!!!
  100. result=proceeding.proceed(args);
  101. systemLog.setAppName(appName);
  102. systemLog.setCreator(userName);
  103. systemLog.setLogType(0);
  104. systemLog.setUser(userName);
  105. systemLog.setUserType(userType);
  106. systemLog.setMethodDescription(getControllerMethodDescription(proceeding));
  107. systemLog.setMethodName(proceeding.getTarget().getClass().getName() + "." + proceeding.getSignature().getName() + "()");
  108. systemLog.setExceptionCode(null);
  109. systemLog.setExceptionDetail(null);
  110. systemLog.setRequestIp(request.getRemoteAddr());
  111. systemLog.setRequestParams(request.getQueryString());
  112. systemLog.setRequestPath(request.getServletPath());
  113. systemLog.setRequestUri(request.getRequestURI());
  114. systemLog.setRequestStatus(200);
  115. systemLog.setRemark(params);
  116. // 初次登录,无法识别,所以通过截取参数获取,保留
  117. if ("用户登录".equals(systemLog.getMethodDescription())
  118. && "未识别".equals(userName) && "未识别".equals(userType)) {
  119. String name = request.getQueryString().split("=")[2];
  120. systemLog.setAppName("client");
  121. systemLog.setCreator(name);
  122. systemLog.setUser(name);
  123. systemLog.setUserType("MEMBER");
  124. }
  125. log.info("=====后置通知结束=====");
  126. } catch (Throwable e) {
  127. log.error("=====异常通知开始=====");
  128. systemLog.setAppName(appName);
  129. systemLog.setCreator(userName);
  130. systemLog.setLogType(1);
  131. systemLog.setUser(userName);
  132. systemLog.setUserType(userType);
  133. systemLog.setMethodDescription(getControllerMethodDescription(proceeding));
  134. systemLog.setMethodName(proceeding.getTarget().getClass().getName() + "." + proceeding.getSignature().getName() + "()");
  135. systemLog.setExceptionCode(e.getClass().getName());
  136. systemLog.setExceptionDetail(e.getMessage());
  137. systemLog.setRequestIp(request.getRemoteAddr());
  138. systemLog.setRequestParams(request.getQueryString());
  139. systemLog.setRequestPath(request.getServletPath());
  140. systemLog.setRequestUri(request.getRequestURI());
  141. systemLog.setRequestStatus(500);
  142. systemLog.setRemark(params);
  143. log.error("=====异常通知结束=====");
  144. } finally {
  145. log.info("=====最终通知开始=====");
  146. getSystemLogManager().addSystemLog(systemLog);
  147. }
  148. return result;
  149. }
  150. /**
  151. * 获取注解中对方法的描述信息 用于service层注解
  152. *
  153. * @param joinPoint 切点
  154. * @return 方法描述
  155. * @throws Exception
  156. */
  157. public static String getServiceMthodDescription(JoinPoint joinPoint)
  158. throws Exception {
  159. String targetName = joinPoint.getTarget().getClass().getName();
  160. String methodName = joinPoint.getSignature().getName();
  161. Object[] arguments = joinPoint.getArgs();
  162. Class targetClass = Class.forName(targetName);
  163. Method[] methods = targetClass.getMethods();
  164. String description = "";
  165. for (Method method : methods) {
  166. if (method.getName().equals(methodName)) {
  167. Class[] clazzs = method.getParameterTypes();
  168. if (clazzs.length == arguments.length) {
  169. description = method.getAnnotation(SystemServiceLog.class).description();
  170. break;
  171. }
  172. }
  173. }
  174. return description;
  175. }
  176. /**
  177. * 获取注解中对方法的描述信息 用于Controller层注解
  178. *
  179. * @param joinPoint 切点
  180. * @return 方法描述
  181. * @throws Exception
  182. */
  183. public static String getControllerMethodDescription(JoinPoint joinPoint) throws Exception {
  184. String targetName = joinPoint.getTarget().getClass().getName();
  185. String methodName = joinPoint.getSignature().getName();
  186. Object[] arguments = joinPoint.getArgs();
  187. Class targetClass = Class.forName(targetName);
  188. Method[] methods = targetClass.getMethods();
  189. String description = "";
  190. for (Method method : methods) {
  191. if (method.getName().equals(methodName)) {
  192. Class[] clazzs = method.getParameterTypes();
  193. if (clazzs.length == arguments.length) {
  194. description = method.getAnnotation(SystemControllerLog.class).description();
  195. break;
  196. }
  197. }
  198. }
  199. return description;
  200. }
  201. }

重点在于切点的环绕通知写法,在切点的aroundAdvice方法内,由于采用的是远古的ssh项目,一些非必要的dao层和service层就没有写出来,那么controller的实现方式也特别简单。

事例

  1. /**
  2. * 删除会员
  3. * @param id
  4. * @param response
  5. * @throws Exception
  6. */
  7. @RequestMapping(value = "/remove")
  8. @RequiresAuthentication
  9. @CheckPermission(permission = "member_remove")
  10. @SystemControllerLog(description = "删除会员信息")
  11. public void removeMember(@RequestParam(value = "id") long[] id, HttpServletResponse response) throws Exception {
  12. try {
  13. getMemberManager().removeMember( id );
  14. ResponseUtil.writeSuccessResult( response );
  15. } catch (BssException e) {
  16. e.printStackTrace();
  17. ResponseUtil.writeErrorResult( response, STATUS_ERROR, e.getMessage() );
  18. }
  19. }

就是一个简单的注解开发

@SystemControllerLog(description = "方法描述")

那么看一下实际运行图

操作正常下:

正常
标题

操作异常:

异常
标题

日志列表:

日志
标题

 

posted @ 2020-11-11 15:32  edda_huang  阅读(565)  评论(0)    收藏  举报