前言
昨天说的会分享方案的具体实现,今天就来分享想核心代码吧。
一、思路
我们用的是springboot组服务,每个链路通过http请求。所以要实现全链路的日志记录,也都是围绕sprinboot做的处理。
这里用的就是过滤器Filter、拦截器Interceptor、配合重写RequestWrapper、统一分发处理、redis缓存机制实现。
二、使用步骤
1.引入库
引库就不说了,都是springboot的,没有第三方依赖,也就是redis、mysql要引入依赖。
2.重写Request
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.util.*;
@Slf4j
public class MyRequestWrapper extends HttpServletRequestWrapper {
private Map<String, String[]> params = new HashMap<String, String[]>();
private Map<String, String> headerMap = new HashMap<>();
private String body;
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public MyRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = new String(readBytes(request.getInputStream()));
}
public MyRequestWrapper(HttpServletRequest request, byte[] encodeBody) throws IOException {
super(request);
body = new String(encodeBody);
}
public MyRequestWrapper(HttpServletRequest request, Map<String, String[]> extendParams) throws IOException {
super(request);
body = new String(readBytes(request.getInputStream()));
addAllParameters(extendParams);
}
public void MyRequestWrapper(HttpServletRequest request) {
this.params.putAll(request.getParameterMap());
}
private String getRequestBody(InputStream stream) {
String line = "";
StringBuilder body = new StringBuilder();
int counter = 0;
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
try {
while ((line = reader.readLine()) != null) {
if (counter > 0) {
body.append("rn");
}
body.append(line);
counter++;
}
} catch (IOException e) {
e.printStackTrace();
}
return body.toString();
}
private HashMap<String, String[]> getParamMapFromPost(HttpServletRequest request) {
String body = "";
try {
body = getRequestBody(request.getInputStream());
} catch (IOException e) {
e.printStackTrace();
}
HashMap<String, String[]> result = new HashMap<String, String[]>();
if (null == body || 0 == body.length()) {
return result;
}
return parseQueryString(body);
}
public HashMap<String, String[]> parseQueryString(String s) {
String valArray[] = null;
if (s == null) {
throw new IllegalArgumentException();
}
HashMap<String, String[]> ht = new HashMap<String, String[]>();
StringTokenizer st = new StringTokenizer(s, "&");
while (st.hasMoreTokens()) {
String pair = (String) st.nextToken();
int pos = pair.indexOf('=');
if (pos == -1) {
continue;
}
String key = pair.substring(0, pos);
String val = pair.substring(pos + 1, pair.length());
if (ht.containsKey(key)) {
String oldVals[] = (String[]) ht.get(key);
valArray = new String[oldVals.length + 1];
for (int i = 0; i < oldVals.length; i++) {
valArray[i] = oldVals[i];
}
} else {
valArray = new String[1];
}
ht.put(key, valArray);
}
return ht;
}
private Map<String, String[]> getParamMapFromGet(HttpServletRequest request) {
return parseQueryString(request.getQueryString());
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes());
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener arg0) {
}
};
}
private static byte[] readBytes(InputStream in) throws IOException {
BufferedInputStream bufin = new BufferedInputStream(in);
int buffSize = 1024;
ByteArrayOutputStream out = new ByteArrayOutputStream(buffSize);
byte[] temp = new byte[buffSize];
int size = 0;
while ((size = bufin.read(temp)) != -1) {
out.write(temp, 0, size);
}
bufin.close();
byte[] content = out.toByteArray();
return content;
}
public void addHeader(String name, String value) {
headerMap.put(name, value);
}
@Override
public String getHeader(String name) {
log.info("getHeader --->{}", name);
String headerValue = super.getHeader(name);
if (headerMap.containsKey(name)) {
headerValue = headerMap.get(name);
}
return headerValue;
}
@Override
public Enumeration<String> getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
for (String name : headerMap.keySet()) {
names.add(name);
}
return Collections.enumeration(names);
}
@Override
public Enumeration<String> getHeaders(String name) {
log.info("getHeaders --->>>>>>{}", name);
List<String> values = Collections.list(super.getHeaders(name));
log.info("getHeaders --->>>>>>{}", values);
if (headerMap.containsKey(name)) {
log.info("getHeaders --->{}", headerMap.get(name));
values = Arrays.asList(headerMap.get(name));
}
return Collections.enumeration(values);
}
@SuppressWarnings("unchecked")
@Override
public String getParameter(String name) {
String[] values = params.get(name);
if (values == null || values.length == 0) {
return null;
}
return values[0];
}
public String[] getParameterValues(String name) {
return params.get(name);
}
public void addAllParameters(Map<String, String[]> otherParams) {
for (Map.Entry<String, String[]> entry : otherParams.entrySet()) {
addParameter(entry.getKey(), entry.getValue());
}
}
public void addParameter(String name, Object value) {
if (value != null) {
if (value instanceof ArrayList) {
String value1 = String.valueOf(value).substring(1, String.valueOf(value).length());
value = value1.substring(0, value1.length() - 1);
params.put(name, new String[]{(String) value});
} else if (value instanceof String[]) {
params.put(name, (String[]) value);
} else if (value instanceof String) {
params.put(name, new String[]{(String) value});
} else {
params.put(name, new String[]{String.valueOf(value)});
}
}
}
}
3.自定义过滤器
import com.xx.MyRequestWrapper;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;
@WebFilter
public class MyWebFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
ServletRequest request = null;
if (servletRequest instanceof HttpServletRequest) {
String url = servletRequest.getParameter("url");
Map<String, String[]> parameterMap = servletRequest.getParameterMap();
request = new MyRequestWrapper((HttpServletRequest) servletRequest, parameterMap);
}
if (request == null) {
chain.doFilter(servletRequest, servletResponse);
} else {
chain.doFilter(request, servletResponse);
}
}
@Override
public void destroy() {
}
}
过滤器里的核心就是将原先的servletRequest替换为自定义的RequestWrapper,执行chain.doFilter(request, servletResponse);继续走下去。
4.拦截器配置
import cn.hutool.core.thread.ThreadUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.core.JsonParser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.service.spi.ServiceException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
@Configuration
@Slf4j
public class MyWebMvcConfigurer implements WebMvcConfigurer {
@Value("${spring.profiles.active:dev}")
private String env;
@Value("${base_service.interface.profix:/api}")
private String baseInterfaceProfix;
@Resource
RedisUtil redisUtil;
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(stringHttpMessageConverter());
converters.add(mappingJackson2HttpMessageConverter());
}
@Bean
StringHttpMessageConverter stringHttpMessageConverter() {
return new StringHttpMessageConverter(Charset.forName("UTF-8"));
}
@Bean
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
return new MappingJackson2HttpMessageConverter(customObjectMapper());
}
@Bean
CustomObjectMapper customObjectMapper() {
CustomObjectMapper objectMapper = new CustomObjectMapper();
objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
return objectMapper;
}
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
exceptionResolvers.add(new HandlerExceptionResolver() {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
RestMessage restMessage = null;
if (e instanceof ServiceException) {
restMessage = RestBuilders.failureMessage().setCode(ResponseCodeI18n.SERVICE_EXCEPTION.getCode()).setMessage(ResponseCodeI18n.SERVICE_EXCEPTION.getMsg());
log.error(e.getMessage());
} else if (e instanceof NoHandlerFoundException) {
restMessage = RestBuilders.failureMessage().setCode(ResponseCodeI18n.NOT_FOND.getCode()).setMessage("接口 [" + request.getRequestURI() + "] 不存在");
}else if (e instanceof IllegalArgumentException){
restMessage = RestBuilders.failureMessage().setCode(ResponseCodeI18n.ILLEGAL_ARGUMENT.getCode()).setMessage(e.getMessage());
}else {
String respError = "接口 [" + request.getRequestURI() + "] 内部错误,请联系管理员。";
if (StringUtils.isNotBlank(e.getMessage())) {
respError += "错误原因[" + e.getMessage() + "]";
}
restMessage = RestBuilders.failureMessage().setCode(ResponseCodeI18n.SERVICE_EXCEPTION.getCode()).setMessage(respError);
String message;
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
message = String.format("接口 [%s] 出现异常,方法:%s.%s,异常摘要:%s",
request.getRequestURI(),
handlerMethod.getBean().getClass().getName(),
handlerMethod.getMethod().getName(),
e.getMessage());
} else {
message = e.getMessage();
}
log.error(message, e);
}
RestMessage finalRestMessage = restMessage;
ThreadUtil.execAsync(new Runnable() {
@Override
public void run() {
try {
RequestUtil.updateInterfaceLogException(request, finalRestMessage,redisUtil);
} catch (IOException e) {
log.error("开启线程保存接口请求日志,发生异常,原因:{}",e.getMessage());
}
}
});
responseResult(response, restMessage);
return new ModelAndView();
}
});
}
private void responseResult(HttpServletResponse response, RestMessage restMessage) {
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-type", "application/json;charset=UTF-8");
response.setHeader("Access-Control-Allow-Headers", "Content-Type,x-requested-with,X-Token");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
response.setHeader("Access-Control-Allow-Origin", "*");
response.setStatus(200);
try {
response.getWriter().write(JSON.toJSONString(restMessage, SerializerFeature.WriteNullStringAsEmpty));
} catch (IOException ex) {
log.error(ex.getMessage());
}
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/templates/")
.addResourceLocations("classpath:/static/")
.addResourceLocations("classpath:/resources/");
WebMvcConfigurer.super.addResourceHandlers(registry);
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
WebMvcConfigurer.super.addViewControllers(registry);
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*")
.allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE")
.allowedHeaders("*")
.maxAge(3600);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptorAdapter() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("---------------------preHandle---------------------");
MyRequestWrapper requestWrapper = null;
if (request instanceof MyRequestWrapper){
requestWrapper = (MyRequestWrapper) request;
}
BaseInterfaceInfo interfaceInfo = RequestUtil.getInterfaceInfoFrom(baseInterfaceProfix,requestWrapper,redisUtil);
RequestUtil.initInterfaceRequestLogAndCache(requestWrapper, interfaceInfo,redisUtil);
boolean isPass = true;
return isPass;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("---------------------postHandle---------------------");
}
});
}
private boolean isOptionRequest(HttpServletRequest request) {
String method = request.getMethod();
if ("OPTIONS".equals(method)) {
return true;
}
return false;
}
}
注释很清楚,可以仔细看
5.拦截Response
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ControllerAdvice
public class InterceptResponse implements ResponseBodyAdvice<Object> {
@Resource
private RedisUtil redisUtil;
@Resource
private BaseInterfaceLogMapper baseInterfaceLogMapper;
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@Nullable
@Override
public Object beforeBodyWrite(@Nullable Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
ServletServerHttpResponse responseTemp = (ServletServerHttpResponse) serverHttpResponse;
HttpServletResponse response = responseTemp.getServletResponse();
String requestId = null;
Object requestIdObj = null;
if (serverHttpRequest instanceof ServletServerHttpRequest) {
ServletServerHttpRequest sshr = (ServletServerHttpRequest) serverHttpRequest;
HttpServletRequest req = sshr.getServletRequest();
requestIdObj = req.getAttribute("requestId");
}
if (serverHttpRequest instanceof MyRequestWrapper) {
MyRequestWrapper sshr = (MyRequestWrapper) serverHttpRequest;
requestIdObj = sshr.getAttribute("requestId");
}
if (requestIdObj != null) {
requestId = "requestId-" + requestIdObj.toString();
}
String finalRequestId = requestId;
ThreadUtil.execAsync(() -> {
if (body instanceof SimpleRestMessage) {
SimpleRestMessage result = (SimpleRestMessage) body;
if (result != null) {
String dataStr = RequestUtil.formatJsonStr(JSONUtil.toJsonStr(result.getData()));
if(dataStr.length() > 8000){
result.setData(null);
result.setMessage("data数据过长,已抛弃存储");
}
String resStr = JSONUtil.toJsonStr(result);
if (StringUtils.isNotBlank(resStr)) {
JSONObject resJson = JSONUtil.parseObj(resStr);
String code = resJson.getStr("code");
boolean success = resJson.getBool("success");
Object interfaceLogObj = redisUtil.get(finalRequestId);
if (interfaceLogObj != null) {
BaseInterfaceLog interfaceLog = null;
if (interfaceLogObj instanceof BaseInterfaceLog) {
interfaceLog = (BaseInterfaceLog) interfaceLogObj;
} else {
interfaceLog = JSONUtil.toBean(JSONUtil.toJsonStr(interfaceLogObj.toString()), BaseInterfaceLog.class);
}
if (interfaceLog != null) {
interfaceLog.setRequestOutTime(DateUtil.date());
interfaceLog.setResponseResult(resStr);
interfaceLog.setResponseCode(code);
interfaceLog.setRequestState(success ? 1 : 0);
long useMs = DateUtil.betweenMs(interfaceLog.getRequestInTime(), interfaceLog.getRequestOutTime());
interfaceLog.setUseTime(useMs);
interfaceLog.setCreateTime(DateUtil.date());
baseInterfaceLogMapper.insert(interfaceLog);
redisUtil.del(finalRequestId);
}
}
}
}
}
});
return body;
}
}
6.RequestUtil工具类
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Component
public class RequestUtil {
public static String getIpAddress(HttpServletRequest request) {
if (request == null) {
return null;
}
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
if (ip != null && ip.indexOf(",") != -1) {
ip = ip.substring(0, ip.indexOf(",")).trim();
}
return ip;
}
public static BaseInterfaceInfo getInterfaceInfoFrom(String baseInterfaceProfix, MyRequestWrapper request, RedisUtil redisUtil) throws Exception {
if (request == null) {
return null;
}
String url = null;
BaseInterfaceInfo baseInterfaceInfo = null;
String requestURI = request.getRequestURI();
String method = request.getMethod();
String path = request.getServletPath();
String interfaceKey = path + "-" + method;
Map<String, String[]> paramsMap = request.getParameterMap();
if(CollectionUtil.isNotEmpty(paramsMap) && paramsMap.containsKey("url")){
url = paramsMap.get("url")[0];
}
boolean isOutInterface = false;
String interfaceUrl = "";
if (StringUtils.isNotBlank(url) && !"null".equals(url)) {
if (url.startsWith("/")) {
interfaceUrl = baseInterfaceProfix + url.trim().substring(1);
interfaceKey = interfaceUrl + "-" + method;
} else {
interfaceUrl = baseInterfaceProfix + "/" + url.trim();
interfaceKey = interfaceUrl + "-" + method;
}
isOutInterface = true;
} else {
if (!requestURI.equals(path)) {
String requestUrl = request.getRequestURL().toString();
if (requestUrl.contains("?")) {
requestUrl = requestUrl.substring(0, requestUrl.indexOf("?") - 1);
}
if(StringUtils.isBlank(url)){
interfaceKey = requestURI + "-" + method;
}else{
interfaceKey = url.trim() + "-" + method;
}
}
}
Object interfaceInfoObj = redisUtil.get(interfaceKey);
if (interfaceInfoObj != null) {
baseInterfaceInfo = JSONUtil.toBean(interfaceInfoObj.toString(), BaseInterfaceInfo.class);
} else {
if (isOutInterface) {
QueryWrapper<BaseInterfaceInfo> qw = new QueryWrapper<>();
qw.eq("interface_url", interfaceUrl);
qw.eq("method_type", method.trim().toUpperCase());
qw.eq("is_deleted", 1);
qw.orderByDesc("id");
baseInterfaceInfo = SpringUtil.getBean(BaseInterfaceInfoMapper.class).selectOne(qw);
if (baseInterfaceInfo != null) {
redisUtil.set(interfaceKey, JSONUtil.toJsonStr(baseInterfaceInfo));
}
}
}
return baseInterfaceInfo;
}
public static BaseInterfaceLog initInterfaceRequestLogAndCache(MyRequestWrapper requestWrapper, BaseInterfaceInfo interfaceInfo, RedisUtil redisUtil) throws Exception {
Snowflake snowflake = IdUtil.getSnowflake(1, 1);
long requestId = snowflake.nextId();
BaseInterfaceLog baseInterfaceLog = new BaseInterfaceLog();
baseInterfaceLog.setRequestId(String.valueOf(requestId));
baseInterfaceLog.setRequestIdCode(baseInterfaceLog.getRequestId());
baseInterfaceLog.setRequestTime(DateUtil.date());
if (interfaceInfo != null) {
baseInterfaceLog.setInterfaceUrl(interfaceInfo.getInterfaceUrl());
baseInterfaceLog.setInterfaceId(interfaceInfo.getId());
baseInterfaceLog.setApplicationId(interfaceInfo.getApplicationId());
baseInterfaceLog.setApplicationCode(interfaceInfo.getApplicationCode());
}
baseInterfaceLog.setRequestIp(RequestUtil.getIpAddress(requestWrapper));
baseInterfaceLog.setRequestInTime(baseInterfaceLog.getRequestTime());
baseInterfaceLog.setIsDeleted(1);
repairRequestInfo(requestWrapper, requestId, baseInterfaceLog);
if (baseInterfaceLog != null) {
redisUtil.set("requestId-" + requestId, baseInterfaceLog, 60 * 3);
}
return baseInterfaceLog;
}
private static void repairRequestInfo(MyRequestWrapper requestWrapper, long requestId, BaseInterfaceLog baseInterfaceLog) {
if (requestWrapper == null) {
return;
}
String body = requestWrapper.getBody();
String params = requestWrapper.getQueryString();
if (StringUtils.isNotBlank(body)) {
if (JSONUtil.isJson(body)) {
JSONObject bodyJson = JSONUtil.parseObj(body);
if (bodyJson.containsKey("customQueryParams")) {
JSONObject customQueryParamsJson = bodyJson.getJSONObject("customQueryParams");
if (customQueryParamsJson != null) {
customQueryParamsJson.putOpt("requestId", requestId);
customQueryParamsJson.putOpt("requestApplicationId", baseInterfaceLog.getApplicationId());
customQueryParamsJson.putOpt("requestApplicationCode", baseInterfaceLog.getApplicationCode());
bodyJson.putOpt("customQueryParams", customQueryParamsJson);
}
} else {
bodyJson.putOpt("requestId", requestId);
bodyJson.putOpt("requestApplicationId", baseInterfaceLog.getApplicationId());
bodyJson.putOpt("requestApplicationCode", baseInterfaceLog.getApplicationCode());
}
String bodyJsonStr = JSONUtil.toJsonStr(bodyJson);
requestWrapper.setBody(bodyJsonStr);
}
} else {
params = JSONUtil.toJsonStr(requestWrapper.getParameterMap());
requestWrapper.addParameter("requestId", requestId);
requestWrapper.addParameter("requestApplicationId", baseInterfaceLog.getApplicationId());
requestWrapper.addParameter("requestApplicationCode", baseInterfaceLog.getApplicationCode());
}
requestWrapper.setAttribute("requestId", requestId);
requestWrapper.setAttribute("requestApplicationId", baseInterfaceLog.getApplicationId());
requestWrapper.setAttribute("requestApplicationCode", baseInterfaceLog.getApplicationCode());
baseInterfaceLog.setRequestParam(params);
String fmtBodyStr = formatJsonStr(body);
baseInterfaceLog.setRequestBody(fmtBodyStr);
}
public static void updateInterfaceLogException(HttpServletRequest request, RestMessage finalRestMessage, RedisUtil redisUtil) throws IOException {
MyRequestWrapper requestWrapper = null;
if (request instanceof MyRequestWrapper) {
requestWrapper = (MyRequestWrapper) request;
} else {
requestWrapper = new MyRequestWrapper(request);
}
Object requestIdObj = requestWrapper.getAttribute("requestId");
if (requestIdObj != null) {
String finalRequestId = "requestId-" + requestIdObj.toString();
Object interfaceLogObj = redisUtil.get(finalRequestId);
if (interfaceLogObj != null) {
BaseInterfaceLog interfaceLog = null;
if (interfaceLogObj instanceof BaseInterfaceLog) {
interfaceLog = (BaseInterfaceLog) interfaceLogObj;
} else {
interfaceLog = JSONUtil.toBean(JSONUtil.toJsonStr(interfaceLogObj), BaseInterfaceLog.class);
}
if (interfaceLog != null) {
interfaceLog.setRequestOutTime(DateUtil.date());
interfaceLog.setResponseCode(finalRestMessage.getCode());
String dataStr = formatJsonStr(JSONUtil.toJsonStr(finalRestMessage.getData()));
if (dataStr.length() > 8000) {
finalRestMessage.setData(null);
finalRestMessage.setMessage("data数据过长,已抛弃存储");
}
String fmtMsgStr = formatJsonStr(JSONUtil.toJsonStr(finalRestMessage));
interfaceLog.setResponseResult(fmtMsgStr);
interfaceLog.setRequestException(finalRestMessage.getMessage());
interfaceLog.setRequestOutTime(DateUtil.date());
interfaceLog.setRequestState(0);
long useMs = DateUtil.betweenMs(interfaceLog.getRequestInTime(), interfaceLog.getRequestOutTime());
interfaceLog.setUseTime(useMs);
interfaceLog.setCreateTime(DateUtil.date());
BaseInterfaceLogMapper baseInterfaceLogMapper = SpringUtil.getBean(BaseInterfaceLogMapper.class);
baseInterfaceLogMapper.insert(interfaceLog);
redisUtil.del(finalRequestId);
}
}
}
}
public static String formatJsonStr(String jsonStr) {
String result = null;
if (StringUtils.isNotBlank(jsonStr)) {
jsonStr = StringEscapeUtils.unescapeJavaScript(jsonStr);
String regex = "\\r\\n[ ]+|\\r|\\n|\\t";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(jsonStr);
result = matcher.replaceAll("");
}
return result;
}
}
- 接口信息、接口日志等常规的CRUD一套不用我提供吧,呵呵
3.表结构
接口信息
CREATE TABLE `base_interface_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`interfact_name` varchar(200) DEFAULT NULL COMMENT '接口名称',
`interface_url` varchar(500) DEFAULT NULL COMMENT '接口url',
`application_id` bigint(20) DEFAULT NULL COMMENT '接口提供方,来源application主键',
`application_code` varchar(50) DEFAULT NULL COMMENT '应用编码,来源base_application编码,冗余,但是必填',
`controller_name` varchar(100) DEFAULT NULL COMMENT 'controller名称',
`method_name` varchar(100) DEFAULT NULL COMMENT '方法名称',
`method_type` varchar(10) DEFAULT NULL COMMENT '方法类型,get、post、delete、put',
`business_type` int(1) DEFAULT NULL COMMENT '业务类型,1新增、2删除、3修改、4查询',
`interface_state` int(1) DEFAULT NULL COMMENT '接口状态,0停用、1可用',
`memo` varchar(2000) DEFAULT NULL COMMENT '接口描述',
`open_time` datetime DEFAULT NULL COMMENT '开放时间',
`close_time` datetime DEFAULT NULL COMMENT '关闭时间',
`limit_rate` int(11) DEFAULT NULL COMMENT '请求频率,1s次',
`check_auth` int(1) DEFAULT NULL COMMENT '校验授权,0否,1是',
`check_flow` int(1) DEFAULT NULL COMMENT '校验限流,0否,1是',
`check_black_list` int(1) DEFAULT NULL COMMENT '校验黑名单,0否,1是',
`check_idempotent` int(1) DEFAULT NULL COMMENT '校验幂等,0否,1是',
`check_safe` int(1) DEFAULT NULL COMMENT '校验安全,0否,1是',
`creator` varchar(100) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`modifier` varchar(100) DEFAULT NULL COMMENT '修改人',
`modify_time` datetime DEFAULT NULL COMMENT '修改时间',
`is_deleted` int(1) DEFAULT NULL COMMENT '是否删除,0删除,1存在',
PRIMARY KEY (`id`),
KEY `base_interface_info_application_id_IDX` (`application_id`) USING BTREE
) ENGINE=InnoDB COMMENT='接口信息';
接口日志
CREATE TABLE `base_interface_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`request_id_code` varchar(100) DEFAULT NULL COMMENT '请求id码',
`request_time` datetime DEFAULT NULL COMMENT '请求时间',
`interface_url` varchar(100) DEFAULT NULL COMMENT '接口url',
`interface_id` bigint(20) DEFAULT NULL COMMENT '接口id',
`request_ip` varchar(100) DEFAULT NULL COMMENT '请求ip',
`request_body` longtext COMMENT '请求入参body',
`request_param` longtext COMMENT '请求入参param',
`response_result` longtext COMMENT '请求出参',
`response_code` varchar(100) DEFAULT NULL COMMENT '响应码',
`request_state` int(1) DEFAULT NULL COMMENT '请求状态,0失败,1成功',
`request_exception` varchar(2000) DEFAULT NULL COMMENT '请求异常',
`business_exception` varchar(2000) DEFAULT NULL COMMENT '业务异常',
`use_time` bigint(20) DEFAULT NULL COMMENT '用时,单位毫秒',
`client_id` bigint(20) DEFAULT NULL COMMENT '客户端id,来源客户端信息主键',
`business_msg` longtext COMMENT '业务接口补充信息',
`request_in_time` datetime DEFAULT NULL COMMENT '请求进时间',
`request_out_time` datetime DEFAULT NULL COMMENT '请求出时间',
`application_id` bigint(20) DEFAULT NULL COMMENT '应用id,来源base_application主键,冗余',
`application_code` varchar(50) DEFAULT NULL COMMENT '应用编码,来源base_application编码,冗余',
`creator` varchar(100) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`modifier` varchar(100) DEFAULT NULL COMMENT '修改人',
`modify_time` datetime DEFAULT NULL COMMENT '修改时间',
`is_deleted` int(1) DEFAULT NULL COMMENT '是否删除,0删除,1存在',
PRIMARY KEY (`id`),
KEY `NewTable_interface_id_IDX` (`interface_id`) USING BTREE,
KEY `NewTable_cleint_id_IDX` (`client_id`) USING BTREE,
KEY `NewTable_create_time_IDX` (`create_time`) USING BTREE,
KEY `base_interface_log_request_time_IDX` (`request_time`) USING BTREE,
KEY `base_interface_log_application_id_IDX` (`application_id`) USING BTREE
) ENGINE=InnoDB COMMENT='接口请求日志';
客户端信息
CREATE TABLE `base_client_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`client_name` varchar(50) DEFAULT NULL COMMENT '客户端名称',
`client_logo` varchar(100) DEFAULT NULL COMMENT '客户端标识',
`api_key` varchar(100) DEFAULT NULL COMMENT '授权apikey',
`api_secret` varchar(100) DEFAULT NULL COMMENT '授权apisecret',
`auth_start_time` datetime DEFAULT NULL COMMENT '授权有效开始时间',
`auth_end_time` datetime DEFAULT NULL COMMENT '授权有效结束时间',
`auth_state` int(11) DEFAULT NULL COMMENT '授权状态,0停用,1启用',
`super_name` varchar(50) DEFAULT NULL COMMENT '超管账号,冗余',
`password_salt` varchar(30) DEFAULT NULL COMMENT '超管账号密码盐值',
`creator` varchar(100) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`modifier` varchar(100) DEFAULT NULL COMMENT '修改人',
`modify_time` datetime DEFAULT NULL COMMENT '修改人时间',
`is_deleted` int(11) DEFAULT NULL COMMENT '是否删除,0删除,1存在',
PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='客户端信息';
接口服务应用信息
CREATE TABLE `base_application` (
`id_` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键,自增',
`name_` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT '应用名称',
`code_` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT '应用代码(应用在base的唯一标识)',
`url_` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '应用访问地址(预留字段,也可以配置在环境变量中)',
`api_url_` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '应用接口访问地址(预留字段,也可以配置在环境变量中)',
`type_` int(10) DEFAULT NULL COMMENT '类别(1内部服务 2外部服务)',
`remark_` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id_`) USING BTREE
) ENGINE=InnoDB COLLATE=utf8_bin;
总结
各个核心代码注释的很清楚,大家可以仔细看几遍,其实核心思想就是:
- preHandle、postHandle处理
- pre里处理将请求日志(校验不通过也记录),统一请求标识作为key放redis缓存
- 统一请求标识想办法塞入请求参数
- 具体执行Controller分方法,从请求参数拿到日志,补充业务日志
- postHandle做出参记录处理(需要重写Response,懒得写,我这里就直接用的spring提供的ResponseBodyAdvice处理的,自定义一个实现它就行)
- 开启线程异步去记录日志
其实还有一套Controller统一分发到其他子应用的请求没有分享,没有子系统的服务的其实也不需要(要是没有子系统其实日志可以直接用aop就够了),我先说下思路: - controller的post、get、等常规方法统一都从传参取到url(对于子应用的接口url),
- 通过http+url请求子应用
- 对子应用的返回做统一转换,(不然我们这边的ResponseBodyAdvice怕不认识出参对象)
- 子系统里也是可以取到统一请求标识的,根据这个标识取redis可以取出日志记录补充日志,再放回缓存就行
好,就分享到这里吧,至于各个验证机制,大家自己根据业务实现吧,我这里其实也还没写实现呢,写好了可以交流下。希望能帮到大家,uping
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)