diff --git a/README.md b/README.md
index fddc5d8..16da43f 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Spring Boot Demo
-spring boot demo 是一个用来学习 spring boot 的项目,已经集成 actuator(监控)、logback(日志)、JPA(ORM 框架)、mybatis(ORM 框架)、redis-cache(缓存)、swagger(API 接口管理测试)、ureport2(中国式报表)模块,后续会集成activemq,email, freemarker,shiro,websocket,quartz,netty等模块。
+spring boot demo 是一个用来学习 spring boot 的项目,已经集成 actuator(监控)、admin(可视化监控)、logback(日志)、aopLog(通过 AOP 记录 web 请求日志)、JPA(ORM 框架)、mybatis(ORM 框架)、redis-cache(缓存)、swagger(API 接口管理测试)、ureport2(中国式报表)模块,后续会集成activemq,email, freemarker,shiro,websocket,quartz,netty等模块。
依赖的 Spring Boot 版本:
@@ -50,6 +50,7 @@ spring boot demo 是一个用来学习 spring boot 的项目,已经集成 actu
../spring-boot-demo-actuator
../spring-boot-demo-admin
../spring-boot-demo-logback
+ ../spring-boot-demo-aoplog
../spring-boot-demo-orm-jpa
../spring-boot-demo-orm-mybatis
../spring-boot-demo-cache-redis
@@ -146,6 +147,7 @@ spring boot demo 是一个用来学习 spring boot 的项目,已经集成 actu
| [spring-boot-demo-actuator](./spring-boot-demo-actuator) | spring-boot 集成 spring-boot-starter-actuator 用于监控 spring-boot 的启动和运行状态 |
| [spring-boot-demo-admin](./spring-boot-demo-admin) | spring-boot 集成 spring-boot-admin 来可视化的监控 spring-boot 程序的运行状态,可以与 actuator 互相搭配使用 |
| [spring-boot-demo-logback](./spring-boot-demo-logback) | spring-boot 集成 logback 日志 |
+| [spring-boot-demo-aoplog](./spring-boot-demo-aoplog) | spring-boot 使用 AOP 切面的方式记录 web 请求日志 |
| [spring-boot-demo-orm-jpa](./spring-boot-demo-orm-jpa) | spring-boot 集成 spring-boot-starter-data-jpa 操作数据库 |
| [spring-boot-demo-orm-mybatis](./spring-boot-demo-orm-mybatis) | spring-boot 集成 [mybatis-spring-boot-starter](https://github.com/mybatis/spring-boot-starter)、[mybatis-spring-boot-starter](https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter) |
| [spring-boot-demo-cache-redis](./spring-boot-demo-cache-redis) | spring-boot 使用 Redis 做缓存 |
diff --git a/TODO.md b/TODO.md
index 935142c..09a5913 100644
--- a/TODO.md
+++ b/TODO.md
@@ -7,7 +7,7 @@
- [x] ~~spring-boot-demo-actuator(对 Spring boot 的端点监控)~~
- [x] ~~spring-boot-demo-admin(对 Spring boot 可视化管控)~~
- [x] ~~spring-boot-demo-logback(集成 logback 日志)~~
-- [ ] spring-boot-demo-aopLog(使用 AOP 拦截请求日志信息)
+- [ ] spring-boot-demo-aoplog(使用 AOP 拦截请求日志信息)
- [ ] spring-boot-demo-exceptionHandler(统一异常处理)
- [ ] spring-boot-demo-orm-jdbcTemplate(操作 SQL 关系型数据库 - JdbcTemplate)
- [x] ~~spring-boot-demo-orm-jpa(操作 SQL 关系型数据库 - JPA)~~
diff --git a/spring-boot-demo-aoplog/README.md b/spring-boot-demo-aoplog/README.md
new file mode 100644
index 0000000..42af1b0
--- /dev/null
+++ b/spring-boot-demo-aoplog/README.md
@@ -0,0 +1,168 @@
+# spring-boot-demo-aoplog
+
+依赖[spring-boot-demo-parent](../spring-boot-demo-parent)、`spring-boot-starter-aop`
+
+### pom.xml
+
+```xml
+
+
+ 4.0.0
+
+ spring-boot-demo-aoplog
+ 0.0.1-SNAPSHOT
+ jar
+
+ spring-boot-demo-aoplog
+ Demo project for Spring Boot
+
+
+ com.xkcoding
+ spring-boot-demo-parent
+ 0.0.1-SNAPSHOT
+ ../spring-boot-demo-parent/pom.xml
+
+
+
+ 1.20
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
+
+ eu.bitwalker
+ UserAgentUtils
+ ${useragent.version}
+
+
+
+
+ spring-boot-demo-aoplog
+
+
+
+```
+
+### AopLog.java
+
+```java
+/**
+ * aop 切面记录请求日志
+ *
+ * @package: com.xkcoding.springbootdemoaoplog.aspectj
+ * @description:aop 切面记录请求日志
+ * @author: yangkai.shen
+ * @date: Created in 2017/11/24 上午9:43
+ * @copyright: Copyright (c) 2017
+ * @version: 0.0.1
+ * @modified: yangkai.shen
+ */
+@Aspect
+@Component
+@Slf4j
+public class AopLog {
+ private static final String START_TIME = "start-request";
+
+ @Pointcut("execution(public * com.xkcoding.springbootdemoaoplog.controller.*Controller.*(..))")
+ public void log() {
+ }
+
+ @Before("log()")
+ public void beforeLog(JoinPoint joinPoint) {
+ ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+ HttpServletRequest request = attributes.getRequest();
+
+ log.info("【请求 URL】:{}", request.getRequestURL());
+ log.info("【请求 IP】:{}", request.getRemoteAddr());
+ log.info("【请求类名】:{},【请求方法名】:{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
+ Map parameterMap = request.getParameterMap();
+ log.info("【请求参数】:{},", JsonMapper.obj2Str(parameterMap));
+ Long start = System.currentTimeMillis();
+ request.setAttribute(START_TIME, start);
+ }
+
+ @Around("log()")
+ public Object arroundLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
+ Object result = proceedingJoinPoint.proceed();
+ log.info("【返回值】:{}", JsonMapper.obj2Str(result));
+ return result;
+ }
+
+ @AfterReturning("log()")
+ public void afterReturning(JoinPoint joinPoint) {
+ ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+ HttpServletRequest request = attributes.getRequest();
+ Long start = (Long) request.getAttribute(START_TIME);
+ Long end = System.currentTimeMillis();
+ log.info("【请求耗时】:{}毫秒", end - start);
+ String header = request.getHeader("User-Agent");
+ UserAgent userAgent = UserAgent.parseUserAgentString(header);
+ log.info("【浏览器类型】:{},【操作系统】:{},【原始User-Agent】:{}", userAgent.getBrowser().toString(), userAgent.getOperatingSystem().toString(), header);
+ }
+}
+```
+
+### JsonMapper.java
+
+```java
+/**
+ * Json 转化工具类
+ *
+ * @package: com.xkcoding.springbootdemoaoplog.util
+ * @description:Json 转化工具类
+ * @author: yangkai.shen
+ * @date: Created in 2017/11/24 上午9:36
+ * @copyright: Copyright (c) 2017
+ * @version: 0.0.1
+ * @modified: yangkai.shen
+ */
+@Slf4j
+public class JsonMapper {
+ private static ObjectMapper objectMapper = new ObjectMapper();
+
+ /**
+ * 对象转 json 字符串
+ *
+ * @param src 元对象
+ * @param 类型
+ * @return json 字符串
+ */
+ public static String obj2Str(T src) {
+ if (src == null) {
+ return null;
+ }
+ try {
+ return src instanceof String ? (String) src : objectMapper.writeValueAsString(src);
+ } catch (IOException e) {
+ log.error("【JSON 转换:对象 --> 字符串】,异常堆栈:{}", e);
+ return null;
+ }
+ }
+
+ /**
+ * json 字符串转化为对象
+ *
+ * @param src 源 json 字符串
+ * @param typeReference 转化后的类型
+ * @param 类型
+ * @return 返回转化后的对象
+ */
+ public static T str2Obj(String src, TypeReference typeReference) {
+ if (src == null || typeReference == null) {
+ return null;
+ }
+ try {
+ return (T) (typeReference.getType().equals(String.class) ? src : objectMapper.readValue(src, typeReference));
+ } catch (Exception e) {
+ log.error("【JSON 转换:字符串 --> 对象】,异常堆栈:{}", e);
+ return null;
+ }
+ }
+}
+```
+
diff --git a/spring-boot-demo-aoplog/pom.xml b/spring-boot-demo-aoplog/pom.xml
new file mode 100644
index 0000000..cc363f9
--- /dev/null
+++ b/spring-boot-demo-aoplog/pom.xml
@@ -0,0 +1,41 @@
+
+
+ 4.0.0
+
+ spring-boot-demo-aoplog
+ 0.0.1-SNAPSHOT
+ jar
+
+ spring-boot-demo-aoplog
+ Demo project for Spring Boot
+
+
+ com.xkcoding
+ spring-boot-demo-parent
+ 0.0.1-SNAPSHOT
+ ../spring-boot-demo-parent/pom.xml
+
+
+
+ 1.20
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
+
+ eu.bitwalker
+ UserAgentUtils
+ ${useragent.version}
+
+
+
+
+ spring-boot-demo-aoplog
+
+
+
\ No newline at end of file
diff --git a/spring-boot-demo-aoplog/src/main/java/com/xkcoding/springbootdemoaoplog/SpringBootDemoAoplogApplication.java b/spring-boot-demo-aoplog/src/main/java/com/xkcoding/springbootdemoaoplog/SpringBootDemoAoplogApplication.java
new file mode 100644
index 0000000..3be9089
--- /dev/null
+++ b/spring-boot-demo-aoplog/src/main/java/com/xkcoding/springbootdemoaoplog/SpringBootDemoAoplogApplication.java
@@ -0,0 +1,12 @@
+package com.xkcoding.springbootdemoaoplog;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringBootDemoAoplogApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringBootDemoAoplogApplication.class, args);
+ }
+}
diff --git a/spring-boot-demo-aoplog/src/main/java/com/xkcoding/springbootdemoaoplog/aspectj/AopLog.java b/spring-boot-demo-aoplog/src/main/java/com/xkcoding/springbootdemoaoplog/aspectj/AopLog.java
new file mode 100644
index 0000000..41b7551
--- /dev/null
+++ b/spring-boot-demo-aoplog/src/main/java/com/xkcoding/springbootdemoaoplog/aspectj/AopLog.java
@@ -0,0 +1,69 @@
+package com.xkcoding.springbootdemoaoplog.aspectj;
+
+import com.xkcoding.springbootdemoaoplog.util.JsonMapper;
+import eu.bitwalker.useragentutils.UserAgent;
+import lombok.extern.slf4j.Slf4j;
+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.util.Map;
+
+/**
+ * aop 切面记录请求日志
+ *
+ * @package: com.xkcoding.springbootdemoaoplog.aspectj
+ * @description:aop 切面记录请求日志
+ * @author: yangkai.shen
+ * @date: Created in 2017/11/24 上午9:43
+ * @copyright: Copyright (c) 2017
+ * @version: 0.0.1
+ * @modified: yangkai.shen
+ */
+@Aspect
+@Component
+@Slf4j
+public class AopLog {
+ private static final String START_TIME = "start-request";
+
+ @Pointcut("execution(public * com.xkcoding.springbootdemoaoplog.controller.*Controller.*(..))")
+ public void log() {
+ }
+
+ @Before("log()")
+ public void beforeLog(JoinPoint joinPoint) {
+ ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+ HttpServletRequest request = attributes.getRequest();
+
+ log.info("【请求 URL】:{}", request.getRequestURL());
+ log.info("【请求 IP】:{}", request.getRemoteAddr());
+ log.info("【请求类名】:{},【请求方法名】:{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
+ Map parameterMap = request.getParameterMap();
+ log.info("【请求参数】:{},", JsonMapper.obj2Str(parameterMap));
+ Long start = System.currentTimeMillis();
+ request.setAttribute(START_TIME, start);
+ }
+
+ @Around("log()")
+ public Object arroundLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
+ Object result = proceedingJoinPoint.proceed();
+ log.info("【返回值】:{}", JsonMapper.obj2Str(result));
+ return result;
+ }
+
+ @AfterReturning("log()")
+ public void afterReturning(JoinPoint joinPoint) {
+ ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+ HttpServletRequest request = attributes.getRequest();
+ Long start = (Long) request.getAttribute(START_TIME);
+ Long end = System.currentTimeMillis();
+ log.info("【请求耗时】:{}毫秒", end - start);
+ String header = request.getHeader("User-Agent");
+ UserAgent userAgent = UserAgent.parseUserAgentString(header);
+ log.info("【浏览器类型】:{},【操作系统】:{},【原始User-Agent】:{}", userAgent.getBrowser().toString(), userAgent.getOperatingSystem().toString(), header);
+ }
+}
diff --git a/spring-boot-demo-aoplog/src/main/java/com/xkcoding/springbootdemoaoplog/controller/IndexController.java b/spring-boot-demo-aoplog/src/main/java/com/xkcoding/springbootdemoaoplog/controller/IndexController.java
new file mode 100644
index 0000000..70a0dc8
--- /dev/null
+++ b/spring-boot-demo-aoplog/src/main/java/com/xkcoding/springbootdemoaoplog/controller/IndexController.java
@@ -0,0 +1,38 @@
+package com.xkcoding.springbootdemoaoplog.controller;
+
+import com.google.common.collect.Maps;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * IndexController
+ *
+ * @package: com.xkcoding.springbootdemoaoplog.controller
+ * @description:IndexController
+ * @author: yangkai.shen
+ * @date: Created in 2017/11/24 上午9:36
+ * @copyright: Copyright (c) 2017
+ * @version: 0.0.1
+ * @modified: yangkai.shen
+ */
+@Slf4j
+@RestController
+public class IndexController {
+
+ @GetMapping({"", ""})
+ public String index() {
+ return "index";
+ }
+
+ @GetMapping({"/test"})
+ public Map test(@RequestParam String name) {
+ ConcurrentMap ret = Maps.newConcurrentMap();
+ ret.put("name", name);
+ return ret;
+ }
+}
diff --git a/spring-boot-demo-aoplog/src/main/java/com/xkcoding/springbootdemoaoplog/util/JsonMapper.java b/spring-boot-demo-aoplog/src/main/java/com/xkcoding/springbootdemoaoplog/util/JsonMapper.java
new file mode 100644
index 0000000..a4f12c8
--- /dev/null
+++ b/spring-boot-demo-aoplog/src/main/java/com/xkcoding/springbootdemoaoplog/util/JsonMapper.java
@@ -0,0 +1,62 @@
+package com.xkcoding.springbootdemoaoplog.util;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+
+/**
+ * Json 转化工具类
+ *
+ * @package: com.xkcoding.springbootdemoaoplog.util
+ * @description:Json 转化工具类
+ * @author: yangkai.shen
+ * @date: Created in 2017/11/24 上午9:36
+ * @copyright: Copyright (c) 2017
+ * @version: 0.0.1
+ * @modified: yangkai.shen
+ */
+@Slf4j
+public class JsonMapper {
+ private static ObjectMapper objectMapper = new ObjectMapper();
+
+ /**
+ * 对象转 json 字符串
+ *
+ * @param src 元对象
+ * @param 类型
+ * @return json 字符串
+ */
+ public static String obj2Str(T src) {
+ if (src == null) {
+ return null;
+ }
+ try {
+ return src instanceof String ? (String) src : objectMapper.writeValueAsString(src);
+ } catch (IOException e) {
+ log.error("【JSON 转换:对象 --> 字符串】,异常堆栈:{}", e);
+ return null;
+ }
+ }
+
+ /**
+ * json 字符串转化为对象
+ *
+ * @param src 源 json 字符串
+ * @param typeReference 转化后的类型
+ * @param 类型
+ * @return 返回转化后的对象
+ */
+ public static T str2Obj(String src, TypeReference typeReference) {
+ if (src == null || typeReference == null) {
+ return null;
+ }
+ try {
+ return (T) (typeReference.getType().equals(String.class) ? src : objectMapper.readValue(src, typeReference));
+ } catch (Exception e) {
+ log.error("【JSON 转换:字符串 --> 对象】,异常堆栈:{}", e);
+ return null;
+ }
+ }
+}
diff --git a/spring-boot-demo-aoplog/src/main/resources/application.yml b/spring-boot-demo-aoplog/src/main/resources/application.yml
new file mode 100644
index 0000000..3cae10b
--- /dev/null
+++ b/spring-boot-demo-aoplog/src/main/resources/application.yml
@@ -0,0 +1,3 @@
+server:
+ port: 8080
+ context-path: /demo
\ No newline at end of file
diff --git a/spring-boot-demo-aoplog/src/test/java/com/xkcoding/springbootdemoaoplog/SpringBootDemoAoplogApplicationTests.java b/spring-boot-demo-aoplog/src/test/java/com/xkcoding/springbootdemoaoplog/SpringBootDemoAoplogApplicationTests.java
new file mode 100644
index 0000000..56b91a5
--- /dev/null
+++ b/spring-boot-demo-aoplog/src/test/java/com/xkcoding/springbootdemoaoplog/SpringBootDemoAoplogApplicationTests.java
@@ -0,0 +1,16 @@
+package com.xkcoding.springbootdemoaoplog;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class SpringBootDemoAoplogApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}
diff --git a/spring-boot-demo-parent/pom.xml b/spring-boot-demo-parent/pom.xml
index 22e187b..f08e451 100644
--- a/spring-boot-demo-parent/pom.xml
+++ b/spring-boot-demo-parent/pom.xml
@@ -17,6 +17,7 @@
../spring-boot-demo-actuator
../spring-boot-demo-admin
../spring-boot-demo-logback
+ ../spring-boot-demo-aoplog
../spring-boot-demo-orm-jpa
../spring-boot-demo-orm-mybatis
../spring-boot-demo-cache-redis