开发环境
名称 | 版本 |
---|---|
操作系统 | Windows 10 X64 |
JDK | JDK1.8(jdk-8u151-windows-x64) |
IntelliJ IDEA | IntelliJ IDEA 2018.3 |
Maven | Maven 3.6.0 |
lombok | 1.18.6 |
迁移说明
本文的内容出现了很多多余的内容,不利于快速阅读
快速了解,请参考
代码说明
本文代码基于 4.Spring Cloud-Hystrix Dashboard监控数据聚合(Turbine) 项目代码进行修改
文件结构
新增/修改下面选中的文件
test-invoice-web
test-invoice-common
pom.xml(test-invoice-clound)
新加入下面内容
<properties>
<lombok.version>1.18.6</lombok.version>
<gson.version>2.8.5</gson.version>
<hutool.version>4.5.1</hutool.version>
</properties>
<dependencies>
<!--lombok start-->
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!--lombok end-->
<!-- 用于日志切面中,以 json 格式打印出入参(本来使用阿里的 FASTJSON, 但是对于文件上传的接口,打印参数会报错,换为 Gson) -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
</dependencies>
test-invoice-common
新增公共类-HttpUtils
package com.test.invoice.util;
import cn.hutool.http.Header;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpServletRequest;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* HTTP 代理工具类
* 无业务需要不要使用此代理类,直接使用hutool工具包中的httputil
*
* @author:
* @version:
* @date: 2019-10-09 16:29
*/
@Slf4j
public class HttpUtils {
/**
* send post
*
* @param agentHost 代理地址
* @param agentPort 代理端口
* @param params 包体
* @return java.lang.String
* @author
* @description //TODO
* @date 15:42 2019/4/18
* @params * @param url
**/
public static String post(String agentUrl, String agentHost, Integer agentPort, Object params) {
return HttpRequest.post(agentUrl)
.header(Header.USER_AGENT,
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36")
.setProxy(getProxy(agentHost, agentPort)).body(JSON.toJSONString(params)).execute().body();
}
/**
* send post
*
* @param agentHost 代理地址
* @param agentPort 代理端口
* @return java.lang.String
* @author
* @description //TODO
* @date 15:43 2019/4/18
* @params * @param url
**/
public static String post(String agentUrl, String agentHost, Integer agentPort) {
return post(agentUrl, agentHost, agentPort, null);
}
/**
* send get
*
* @param agentHost 代理地址
* @param agentPort 代理端口
* @return java.lang.String
* @author
* @description //TODO
* @date 15:43 2019/4/18
* @params * @param url
**/
public static String get(String agentUrl, String agentHost, Integer agentPort, Object params) {
return HttpRequest.get(agentUrl)
.header(Header.USER_AGENT,
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36")
.setProxy(getProxy(agentHost, agentPort)).body(JSON.toJSONString(params)).execute().body();
}
/**
* send get
*
* @param agentHost 代理地址
* @param agentPort 代理端口
* @return java.lang.String
* @author
* @description //TODO
* @date 15:43 2019/4/18
* @params * @param url
**/
public static String get(String agentUrl, String agentHost, Integer agentPort) {
return get(agentUrl, agentHost, agentPort, null);
}
private static Proxy getProxy(String agentHost, Integer agentPort) {
InetSocketAddress addr = new InetSocketAddress(agentHost, agentPort);
return new Proxy(Proxy.Type.HTTP, addr);
}
/**
* 获取用户真实IP地址,不使用request.getRemoteAddr()的原因是有可能用户使用了代理软件方式避免真实IP地址,
* 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值
*
* @return ip
*/
public static String getIpAddr(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
log.info("x-forwarded-for ip: " + ip);
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
if (ip.indexOf(",") != -1) {
ip = ip.split(",")[0];
}
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
log.info("Proxy-Client-IP ip: " + ip);
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
log.info("WL-Proxy-Client-IP ip: " + ip);
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
log.info("HTTP_CLIENT_IP ip: " + ip);
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
log.info("HTTP_X_FORWARDED_FOR ip: " + ip);
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
log.info("X-Real-IP ip: " + ip);
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
log.info("getRemoteAddr ip: " + ip);
}
log.info("获取客户端ip: " + ip);
return ip;
}
public static Map<Object, Object> getHeaders(HttpServletRequest request) {
Map<Object, Object> headers = new HashMap();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
Enumeration<String> values = request.getHeaders(name);
while (values.hasMoreElements()) {
String value = values.nextElement();
headers.put(name, value);
}
}
}
return headers;
}
public static void main(String[] args) {
String url = "https://www.baidu.com";
String rtn = HttpUtil.get(url);//get(url, "web-proxy.tencent.com", 8080);
System.out.println("########" + rtn);
}
}
新增公共类-MapperUtils
package com.test.invoice.util;
import org.dozer.DozerBeanMapperBuilder;
import org.dozer.Mapper;
import org.springframework.util.Assert;
import java.util.ArrayList;
import java.util.List;
/**
* 对象转换类
*
* @author: v_weboyang
* @version:
* @date: 2019/3/13 18:13
*/
public class MapperUtils {
private static final Mapper MAPPER = DozerBeanMapperBuilder.buildDefault();
public static <T, S> T convert(S source, T target) {
if (source == null) {
return null;
}
Assert.notNull(target, "target instance required");
MAPPER.map(source, target);
return target;
}
public static <T, S> T convert(S source, Class<T> targetClass) {
if (source == null) {
return null;
}
Assert.notNull(targetClass, "targetClass required");
return MAPPER.map(source, targetClass);
}
public static <T, S> List<T> convert(List<S> source, Class<T> targetClass) {
if (source == null) {
return null;
}
List<T> targetList = new ArrayList<>();
for (S s : source) {
T target = MAPPER.map(s, targetClass);
targetList.add(target);
}
return targetList;
}
}
test-invoice-web
application.yml
添加以下配置
logging:
config: classpath:logback-dev.xml
新增文件-logback-dev.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<contextName>logback</contextName>
<property name="log.path" value="/v_hwhao/test-invoice-cloud/logs/test-invoice-web/logs"/>
<!--输出到控制台 -->
<appender name="console"
class="ch.qos.logback.core.ConsoleAppender">
<!-- <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level>
</filter> -->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%X{requestId}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- info日志 -->
<appender name="fileInfoLog"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤日志 -->
<file>${log.path}/blockchain-invoice-api.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>DENY</onMatch> <!-- 如果命中就禁止这条日志 -->
<onMismatch>ACCEPT</onMismatch> <!-- 如果没有命中就使用这条规则 -->
</filter>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%X{requestId}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<!-- 滚动策略 -->
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 设置info日志路径 -->
<fileNamePattern>${log.path}/blockchain-invoice-api.logback.%d{yyyy-MM-dd}.log
</fileNamePattern>
</rollingPolicy>
</appender>
<!-- error日志 -->
<appender name="fileErrorLog"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/blockchain-invoice-api.error.log</file>
<!-- 过滤日志 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%X{requestId}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<!-- 滚动策略 -->
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 设置error日志路径 -->
<fileNamePattern>${log.path}/blockchain-invoice-api.error.logback.%d{yyyy-MM-dd}.log
</fileNamePattern>
</rollingPolicy>
</appender>
<!-- all日志 -->
<appender name="fileAllLog"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 过滤日志 -->
<file>${log.path}/blockchain-invoice-api.all.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%X{requestId}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<!-- 滚动策略 -->
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 设置info日志路径 -->
<fileNamePattern>${log.path}/blockchain-invoice-api.all.logback.%d{yyyy-MM-dd}.log
</fileNamePattern>
</rollingPolicy>
</appender>
<root level="info">
<appender-ref ref="console"/>
<appender-ref ref="fileInfoLog"/>
<appender-ref ref="fileErrorLog"/>
<appender-ref ref="fileAllLog"/>
</root>
</configuration>
TRbtTestController-Get()方法修改
@Slf4j
public class TRbtTestController{
@PostMapping("/Test/Get")
@ApiOperation(value = "系统框架测试-获取单个数据", httpMethod = "POST", response = ResponseVO.class, notes = "系统框架测试-获取单个数据")
public ResponseVO Get(@RequestBody TRbtTestData data){
GsonBuilder builder = new GsonBuilder();
Gson gson = builder.create();
log.info("请求方法 Get(),请求参数:"+ gson.toJson(data));
ResponseVO result = testConsumer.Get(data);
log.info("请求方法 Get(),返回数据:"+ gson.toJson(result));
return result;
}
}
新增类-WebLogAspect
package com.test.invoice.aspect;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.test.invoice.util.HttpUtils;
import com.test.invoice.util.Serializer;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.asm.Advice;
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;
/**
* web 请求,响应日志记录类
*
* @author: v_hwhao
* @version:
* @date: 2019-10-09 16:25
*/
@Aspect
@Component
@Slf4j
public class WebLogAspect {
ThreadLocal<Long> startTime = new ThreadLocal<>();
Map params = Maps.newLinkedHashMap();
/**
* 以 controller 包下定义的所有请求为切入点
*/
@Pointcut("execution(public * com.test.invoice.controller..*.*(..))")
public void webLog() {
}
/**
* 在切点之前织入
*
* @param joinPoint
* @throws Throwable
*/
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 开始打印请求日志
startTime.set(System.currentTimeMillis());
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
params.put("【url】", request.getRequestURL()); // 获取请求的url
params.put("【method】", request.getMethod()); // 获取请求的方式
params.put("【ip】", HttpUtils.getIpAddr(request)); // 获取请求的ip地址
params.put("【className】", joinPoint.getSignature().getDeclaringTypeName()); // 获取类名
params.put("【classMethod】", joinPoint.getSignature().getName()); // 获取类方法
params.put("【request args】", Serializer.serialize(joinPoint.getArgs())); // 请求参数
}
/**
* 在切点之后织入
*
* @throws Throwable
*/
@After("webLog()")
public void doAfter() throws Throwable {
// 输出格式化后的json字符串
Gson gson = new GsonBuilder().setPrettyPrinting().create();
log.info(gson.toJson(params));
//清空每次内容
params.clear();
// 每个请求之间空一行
log.info("");
}
/**
* 环绕
*
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around("webLog()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object result = proceedingJoinPoint.proceed();
params.put("【response args】", result); // 响应回包参数
params.put("【spend time】", (System.currentTimeMillis() - startTime.get()) + "ms"); // 响应回包参数
return result;
}
@AfterReturning(value = "webLog()", returning = "returnValue")
public void doRetrun(JoinPoint point, Object returnValue){
}
}
测试
使用 Postman
访问 Get
接口
地址:http://localhost:8080/Inv/Api/Test/Get
Body raw 参数:"id":"","name":"测试数据-菲克-1","version":""}
在目录 D:\v_hwhao\test-invoice-cloud\logs\test-invoice-web\logs
生成了下面文件
blockchain-invoice-api.all.log
blockchain-invoice-api.error.log
blockchain-invoice-api.log
打开 blockchain-invoice-api.log
,部分内容如下
2019-10-09 17:09:37.396 logback [] INFO c.t.i.controller.TRbtTestController - 请求方法 Get(),请求参数:{"id":"","name":"测试数据-菲克-1","version":""}
2019-10-09 17:09:37.436 logback [] INFO c.t.i.controller.TRbtTestController - 返回数据:{"code":2000,"msg":"Success","data":{"id":"","name":"测试数据-菲克-1","version":"1.0.1"},"timestamp":1570612177430}
2019-10-09 16:49:26.429 logback [] INFO com.test.invoice.aspect.WebLogAspect - {
"【url】": "http://localhost:8080/Inv/Api/Test/Get",
"【method】": "POST",
"【ip】": "127.0.0.1",
"【className】": "com.test.invoice.controller.TRbtTestController",
"【classMethod】": "Get",
"【request args】": "[{\"id\":\"\",\"name\":\"测试数据-菲克-1\",\"version\":\"\"}]",
"【response args】": {
"code": 2000,
"msg": "Success",
"data": {
"id": "",
"name": "测试数据-菲克-1",
"version": "1.0.1"
},
"timestamp": 1570610966375
},
"【spend time】": "131ms"
}
可以看到自定义和自动生成的日志都在里面进行记录