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