|
1 | 1 | ```text |
2 | 2 | 在Spring框架中,使用AOP配合自定义注解可以方便的实现用户操作的监控。 |
3 | 3 | 1.引入mybatis依赖、mysql驱动依赖、druid数据源驱动依赖和aop依赖 |
4 | | - <dependency> |
5 | | - <groupId>org.mybatis.spring.boot</groupId> |
6 | | - <artifactId>mybatis-spring-boot-starter</artifactId> |
7 | | - <version>1.3.2</version> |
8 | | - </dependency> |
9 | | - <dependency> |
10 | | - <groupId>mysql</groupId> |
11 | | - <artifactId>mysql-connector-java</artifactId> |
12 | | - </dependency> |
13 | | - <dependency> |
14 | | - <groupId>com.alibaba</groupId> |
15 | | - <artifactId>druid-spring-boot-starter</artifactId> |
16 | | - <version>1.1.10</version> |
17 | | - </dependency> |
18 | | - <dependency> |
19 | | - <groupId>org.springframework.boot</groupId> |
20 | | - <artifactId>spring-boot-starter-aop</artifactId> |
21 | | - </dependency> |
| 4 | + <dependency> |
| 5 | + <groupId>org.mybatis.spring.boot</groupId> |
| 6 | + <artifactId>mybatis-spring-boot-starter</artifactId> |
| 7 | + <version>1.3.2</version> |
| 8 | + </dependency> |
| 9 | + <dependency> |
| 10 | + <groupId>mysql</groupId> |
| 11 | + <artifactId>mysql-connector-java</artifactId> |
| 12 | + </dependency> |
| 13 | + <dependency> |
| 14 | + <groupId>com.alibaba</groupId> |
| 15 | + <artifactId>druid-spring-boot-starter</artifactId> |
| 16 | + <version>1.1.10</version> |
| 17 | + </dependency> |
| 18 | + <dependency> |
| 19 | + <groupId>org.springframework.boot</groupId> |
| 20 | + <artifactId>spring-boot-starter-aop</artifactId> |
| 21 | + </dependency> |
22 | 22 | 2.在application.yml文件中配置Druid数据源连接池及监控 |
23 | | - server: |
24 | | - servlet: |
25 | | - context-path: /web |
26 | | - spring: |
27 | | - datasource: |
28 | | - druid: |
29 | | - # 数据库访问配置, 使用druid数据源, 该MySQL驱动类名为新的 |
30 | | - driver-class-name: com.mysql.cj.jdbc.Driver |
31 | | - # 设置URL时,如果是MySQL可能出现时区的问题,查看springboot设置时区 |
32 | | - url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8 |
33 | | - username: root |
34 | | - password: root |
35 | | - # 连接池配置 |
36 | | - initial-size: 5 |
37 | | - min-idle: 5 |
38 | | - max-active: 20 |
39 | | - # 连接等待超时时间 |
40 | | - max-wait: 30000 |
41 | | - # 配置检测可以关闭的空闲连接间隔时间 |
42 | | - time-between-eviction-runs-millis: 60000 |
43 | | - # 配置连接在池中的最小生存时间 |
44 | | - min-evictable-idle-time-millis: 300000 |
45 | | - validation-query: select '1' from dual |
46 | | - test-while-idle: true |
47 | | - test-on-borrow: false |
48 | | - test-on-return: false |
49 | | - # 打开PSCache,并且指定每个连接上PSCache的大小 |
50 | | - pool-prepared-statements: true |
51 | | - max-open-prepared-statements: 20 |
52 | | - max-pool-prepared-statement-per-connection-size: 20 |
53 | | - # 配置监控统计拦截的filters, 去掉后监控界面sql无法统计, 'wall'用于防火墙 |
54 | | - filters: stat,wall |
55 | | - # Spring监控AOP切入点,如x.y.z.service.*,配置多个英文逗号分隔 |
56 | | - aop-patterns: com.springboot.servie.* |
57 | | - |
58 | | - # WebStatFilter配置 |
59 | | - web-stat-filter: |
60 | | - enabled: true |
61 | | - # 添加过滤规则 |
62 | | - url-pattern: /* |
63 | | - # 忽略过滤的格式 |
64 | | - exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' |
65 | | - # StatViewServlet配置 |
66 | | - stat-view-servlet: |
67 | | - enabled: true |
68 | | - # 访问路径为/druid时,跳转到StatViewServlet |
69 | | - url-pattern: /druid/* |
70 | | - # 是否能够重置数据 |
71 | | - reset-enable: false |
72 | | - # 需要账号密码才能访问控制台 |
73 | | - #login-username: admin |
74 | | - #login-password: admin |
75 | | - # IP白名单 |
76 | | - # allow: 127.0.0.1 |
77 | | - # IP黑名单(共同存在时,deny优先于allow) |
78 | | - # deny: 192.168.1.218 |
79 | | - # 配置StatFilter |
80 | | - filter: |
81 | | - stat: |
82 | | - log-slow-sql: true |
83 | | - mybatis: |
84 | | - # type-aliases扫描路径 |
85 | | - # type-aliases-package: |
86 | | - # mapper xml实现扫描路径 |
87 | | - mapper-locations: classpath:mapper/*.xml |
| 23 | + server: |
| 24 | + servlet: |
| 25 | + context-path: /web |
| 26 | + spring: |
| 27 | + datasource: |
| 28 | + druid: |
| 29 | + # 数据库访问配置, 使用druid数据源, 该MySQL驱动类名为新的 |
| 30 | + driver-class-name: com.mysql.cj.jdbc.Driver |
| 31 | + # 设置URL时,如果是MySQL可能出现时区的问题,查看springboot设置时区 |
| 32 | + url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8 |
| 33 | + username: root |
| 34 | + password: root |
| 35 | + # 连接池配置 |
| 36 | + initial-size: 5 |
| 37 | + min-idle: 5 |
| 38 | + max-active: 20 |
| 39 | + # 连接等待超时时间 |
| 40 | + max-wait: 30000 |
| 41 | + # 配置检测可以关闭的空闲连接间隔时间 |
| 42 | + time-between-eviction-runs-millis: 60000 |
| 43 | + # 配置连接在池中的最小生存时间 |
| 44 | + min-evictable-idle-time-millis: 300000 |
| 45 | + validation-query: select '1' from dual |
| 46 | + test-while-idle: true |
| 47 | + test-on-borrow: false |
| 48 | + test-on-return: false |
| 49 | + # 打开PSCache,并且指定每个连接上PSCache的大小 |
| 50 | + pool-prepared-statements: true |
| 51 | + max-open-prepared-statements: 20 |
| 52 | + max-pool-prepared-statement-per-connection-size: 20 |
| 53 | + # 配置监控统计拦截的filters, 去掉后监控界面sql无法统计, 'wall'用于防火墙 |
| 54 | + filters: stat,wall |
| 55 | + # Spring监控AOP切入点,如x.y.z.service.*,配置多个英文逗号分隔 |
| 56 | + aop-patterns: com.springboot.servie.* |
| 57 | + # WebStatFilter配置 |
| 58 | + web-stat-filter: |
| 59 | + enabled: true |
| 60 | + # 添加过滤规则 |
| 61 | + url-pattern: /* |
| 62 | + # 忽略过滤的格式 |
| 63 | + exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' |
| 64 | + # StatViewServlet配置 |
| 65 | + stat-view-servlet: |
| 66 | + enabled: true |
| 67 | + # 访问路径为/druid时,跳转到StatViewServlet |
| 68 | + url-pattern: /druid/* |
| 69 | + # 是否能够重置数据 |
| 70 | + reset-enable: false |
| 71 | + # 需要账号密码才能访问控制台 |
| 72 | + #login-username: admin |
| 73 | + #login-password: admin |
| 74 | + # IP白名单 |
| 75 | + # allow: 127.0.0.1 |
| 76 | + # IP黑名单(共同存在时,deny优先于allow) |
| 77 | + # deny: 192.168.1.218 |
| 78 | + # 配置StatFilter |
| 79 | + filter: |
| 80 | + stat: |
| 81 | + log-slow-sql: true |
| 82 | + mybatis: |
| 83 | + # type-aliases扫描路径 |
| 84 | + # type-aliases-package: |
| 85 | + # mapper xml实现扫描路径 |
| 86 | + mapper-locations: classpath:mapper/*.xml |
88 | 87 | 3.自定义注解(标注要监控的方法) |
89 | | - @Target(ElementType.METHOD) |
90 | | - @Retention(RetentionPolicy.RUNTIME) |
91 | | - public @interface Log { |
92 | | - String value() default ""; |
93 | | - } |
| 88 | + @Target(ElementType.METHOD) |
| 89 | + @Retention(RetentionPolicy.RUNTIME) |
| 90 | + public @interface Log { |
| 91 | + String value() default ""; |
| 92 | + } |
94 | 93 | 4.创建保存操作日志的库表: |
95 | | - CREATE TABLE SYS_LOG ( |
96 | | - ID INTEGER(20) NOT NULL COMMENT 'ID', |
97 | | - USERNAME VARCHAR(50) NULL COMMENT '用户名', |
98 | | - OPERATION VARCHAR(50) NULL COMMENT '用户操作', |
99 | | - TIME INTEGER(11) NULL COMMENT '响应时间', |
100 | | - METHOD VARCHAR(200) NULL COMMENT '请求方法', |
101 | | - PARAMS VARCHAR(500) NULL COMMENT '请求参数', |
102 | | - IP VARCHAR(64) NULL COMMENT 'IP地址', |
103 | | - CREATETIME DATE NULL COMMENT '创建时间' |
104 | | - ); |
105 | | - ALTER TABLE SYS_LOG MODIFY ID INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY; |
| 94 | + CREATE TABLE SYS_LOG ( |
| 95 | + ID INTEGER(20) NOT NULL COMMENT 'ID', |
| 96 | + USERNAME VARCHAR(50) NULL COMMENT '用户名', |
| 97 | + OPERATION VARCHAR(50) NULL COMMENT '用户操作', |
| 98 | + TIME INTEGER(11) NULL COMMENT '响应时间', |
| 99 | + METHOD VARCHAR(200) NULL COMMENT '请求方法', |
| 100 | + PARAMS VARCHAR(500) NULL COMMENT '请求参数', |
| 101 | + IP VARCHAR(64) NULL COMMENT 'IP地址', |
| 102 | + CREATETIME DATE NULL COMMENT '创建时间' |
| 103 | + ); |
| 104 | + ALTER TABLE SYS_LOG MODIFY ID INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY; |
106 | 105 | 5.库表对应的实体JavaBean: |
107 | | - public class SysLog implements Serializable{ |
108 | | - private static final long serialVersionUID = -6309732882044872298L; |
109 | | - private Integer id; |
110 | | - private String username; |
111 | | - private String operation; |
112 | | - private Integer time; |
113 | | - private String method; |
114 | | - private String params; |
115 | | - private String ip; |
116 | | - private Date createTime; |
117 | | - // get,set略 |
118 | | - } |
| 106 | + public class SysLog implements Serializable{ |
| 107 | + private static final long serialVersionUID = -6309732882044872298L; |
| 108 | + private Integer id; |
| 109 | + private String username; |
| 110 | + private String operation; |
| 111 | + private Integer time; |
| 112 | + private String method; |
| 113 | + private String params; |
| 114 | + private String ip; |
| 115 | + private Date createTime; |
| 116 | + // get,set略 |
| 117 | + } |
119 | 118 | 6.编写Mapper接口: |
120 | 119 | @Repository |
121 | 120 | @Mapper |
122 | | - public interface SysLogMapper { |
123 | | - void saveSysLog(SysLog syslog); |
124 | | - } |
| 121 | + public interface SysLogMapper { |
| 122 | + void saveSysLog(SysLog syslog); |
| 123 | + } |
125 | 124 | 7.编写Mapper的实现: |
126 | | - <?xml version="1.0" encoding="UTF-8" ?> |
127 | | - <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" |
128 | | - "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
129 | | - <mapper namespace="com.example.aoplog.dao.SysLogMapper"> |
130 | | - <insert id="saveSysLog" > |
131 | | - insert into SYS_LOG(id,userName,operation,time,method,params,ip,createTime) |
132 | | - values(#{id},#{userName},#{operation},#{time},#{method},#{params},#{ip},#{createTime}) |
133 | | - </insert> |
134 | | - </mapper> |
| 125 | + <?xml version="1.0" encoding="UTF-8" ?> |
| 126 | + <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" |
| 127 | + "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
| 128 | + <mapper namespace="com.example.aoplog.dao.SysLogMapper"> |
| 129 | + <insert id="saveSysLog" > |
| 130 | + insert into SYS_LOG(id,userName,operation,time,method,params,ip,createTime) |
| 131 | + values(#{id},#{userName},#{operation},#{time},#{method},#{params},#{ip},#{createTime}) |
| 132 | + </insert> |
| 133 | + </mapper> |
135 | 134 | 8.编写切面类定义切点: (注意添加两个工具类:HttpContextUtils,IPUtils) |
136 | | - @Aspect |
137 | | - @Component |
138 | | - public class LogAspect { |
139 | | - @Autowired |
140 | | - private SysLogMapper sysLogMapper; |
141 | | - @Pointcut("@annotation(com.example.aoplog.annotation.Log)") |
142 | | - public void poincut(){} |
143 | | - @Around("poincut()") |
144 | | - public Object around(ProceedingJoinPoint point){ |
145 | | - Object result =null; |
146 | | - long start = System.currentTimeMillis(); |
147 | | - try { |
148 | | - // 执行方法 |
149 | | - result = point.proceed(); |
150 | | - } catch (Throwable e) { |
151 | | - e.printStackTrace(); |
152 | | - } |
153 | | - long time = System.currentTimeMillis() - start; |
154 | | - saveLog(point,time); |
155 | | - return result; |
156 | | - } |
157 | | - private void saveLog(ProceedingJoinPoint point, long time) { |
158 | | - MethodSignature signature = (MethodSignature) point.getSignature(); |
159 | | - Method method = signature.getMethod(); |
160 | | - SysLog sysLog = new SysLog(); |
161 | | - Log annotation = method.getAnnotation(Log.class); |
162 | | - if (annotation != null){ |
163 | | - sysLog.setOperation(annotation.value()); |
164 | | - } |
165 | | - String className = point.getTarget().getClass().getName(); |
166 | | - String methodName = signature.getName(); |
167 | | - sysLog.setMethod(className+"."+methodName); |
168 | | - |
169 | | - Object[] args = point.getArgs(); |
170 | | - LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer(); |
171 | | - String[] paramNames = u.getParameterNames(method); |
172 | | - if(args != null && paramNames != null){ |
173 | | - StringBuffer params = new StringBuffer(); |
174 | | - for (int i = 0; i < args.length; i++) { |
175 | | - params.append(" "+paramNames[i]+": "+args[i]); |
176 | | - } |
177 | | - sysLog.setParams(params.toString()); |
178 | | - } |
179 | | - HttpServletRequest request = HttpContextUtils.getHttpServletRequest(); |
180 | | - sysLog.setIp(IPUtils.getIpAddr(request)); |
181 | | - sysLog.setUserName("admin"); |
182 | | - sysLog.setTime(time); |
183 | | - sysLog.setCreateTime(new Date()); |
184 | | - sysLogMapper.saveSysLog(sysLog); |
185 | | - } |
186 | | - } |
| 135 | + @Aspect |
| 136 | + @Component |
| 137 | + public class LogAspect { |
| 138 | + @Autowired |
| 139 | + private SysLogMapper sysLogMapper; |
| 140 | + @Pointcut("@annotation(com.example.aoplog.annotation.Log)") |
| 141 | + public void poincut(){} |
| 142 | + @Around("poincut()") |
| 143 | + public Object around(ProceedingJoinPoint point){ |
| 144 | + Object result =null; |
| 145 | + long start = System.currentTimeMillis(); |
| 146 | + try { |
| 147 | + // 执行方法 |
| 148 | + result = point.proceed(); |
| 149 | + } catch (Throwable e) { |
| 150 | + e.printStackTrace(); |
| 151 | + } |
| 152 | + long time = System.currentTimeMillis() - start; |
| 153 | + saveLog(point,time); |
| 154 | + return result; |
| 155 | + } |
| 156 | + private void saveLog(ProceedingJoinPoint point, long time) { |
| 157 | + MethodSignature signature = (MethodSignature) point.getSignature(); |
| 158 | + Method method = signature.getMethod(); |
| 159 | + SysLog sysLog = new SysLog(); |
| 160 | + Log annotation = method.getAnnotation(Log.class); |
| 161 | + if (annotation != null){ |
| 162 | + sysLog.setOperation(annotation.value()); |
| 163 | + } |
| 164 | + String className = point.getTarget().getClass().getName(); |
| 165 | + String methodName = signature.getName(); |
| 166 | + sysLog.setMethod(className+"."+methodName); |
| 167 | + |
| 168 | + Object[] args = point.getArgs(); |
| 169 | + LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer(); |
| 170 | + String[] paramNames = u.getParameterNames(method); |
| 171 | + if(args != null && paramNames != null){ |
| 172 | + StringBuffer params = new StringBuffer(); |
| 173 | + for (int i = 0; i < args.length; i++) { |
| 174 | + params.append(" "+paramNames[i]+": "+args[i]); |
| 175 | + } |
| 176 | + sysLog.setParams(params.toString()); |
| 177 | + } |
| 178 | + HttpServletRequest request = HttpContextUtils.getHttpServletRequest(); |
| 179 | + sysLog.setIp(IPUtils.getIpAddr(request)); |
| 180 | + sysLog.setUserName("admin"); |
| 181 | + sysLog.setTime(time); |
| 182 | + sysLog.setCreateTime(new Date()); |
| 183 | + sysLogMapper.saveSysLog(sysLog); |
| 184 | + } |
| 185 | + } |
187 | 186 | 9.编写Controller测试: |
188 | | - @RestController |
189 | | - public class TestController { |
190 | | - @Log("查询的方法") |
191 | | - @GetMapping("/query") |
192 | | - public void query() throws InterruptedException { Thread.sleep(200); } |
193 | | - |
194 | | - @Log("新增的方法") |
195 | | - @PostMapping("/add") |
196 | | - public void add(String name,int age,char sex) throws InterruptedException { |
197 | | - Thread.sleep(500); |
198 | | - } |
199 | | - @Log("删除的方法") |
200 | | - @DeleteMapping("/delete") |
201 | | - public void delete(String name) throws InterruptedException { Thread.sleep(100); } |
202 | | - } |
| 187 | + @RestController |
| 188 | + public class TestController { |
| 189 | + @Log("查询的方法") |
| 190 | + @GetMapping("/query") |
| 191 | + public void query() throws InterruptedException { Thread.sleep(200); } |
| 192 | + @Log("新增的方法") |
| 193 | + @PostMapping("/add") |
| 194 | + public void add(String name,int age,char sex) throws InterruptedException { |
| 195 | + Thread.sleep(500); |
| 196 | + } |
| 197 | + @Log("删除的方法") |
| 198 | + @DeleteMapping("/delete") |
| 199 | + public void delete(String name) throws InterruptedException { Thread.sleep(100); } |
| 200 | + } |
203 | 201 | 10.测试并查看数据库表: http://localhost:8080/web/XXX |
204 | 202 | ``` |
0 commit comments