Compare commits

...

17 Commits

Author SHA1 Message Date
周敏
8683437d64 Merge remote-tracking branch 'origin/test' 2025-02-18 10:15:28 +08:00
周敏
c98c52e48b feat(common): PageUtils,增加对PageReqV2参数的快捷支持 2025-01-16 11:13:19 +08:00
周敏
f5250beda8 Merge remote-tracking branch 'origin/test' 2025-01-09 21:55:33 +08:00
周敏
0affbf379f feat(common): 新增RequestLogV2Aspect,支持@RequestMapping定义在api里面,controller继承feignApi的情况。 2025-01-08 18:01:52 +08:00
周敏
18e2230439 feat(common): 新脚手架,兼容一下 ctxLogId 这个traceId 2025-01-08 16:58:06 +08:00
周敏
a0d46ae8e5 Merge remote-tracking branch 'origin/test' 2025-01-06 13:37:45 +08:00
周敏
dcd4bd7412 feat(dao-support、common): IPageReq,提供PageReqV2分页参数基类。
* 1、支持builder方式构建参数。
 * 2、默认分页大小为1000。
 * 3、支持指定不分页。searchCount = false
2024-12-23 11:39:56 +08:00
周敏
4cbad7cc24 Revert "feat(dao-support、common): IPageReq,支持指定不查询分页count。step2"
This reverts commit f2428ffe31.
2024-12-23 11:24:05 +08:00
周敏
f2428ffe31 feat(dao-support、common): IPageReq,支持指定不查询分页count。step2 2024-12-23 10:29:26 +08:00
周敏
54f135f94f feat(dao-support、common): IPageReq,支持指定不查询分页count。 2024-12-19 10:52:03 +08:00
zengxiaobo
c819af4a3c feat: 重构代码 2024-12-10 17:50:32 +08:00
zengxiaobo
d942b58fb9 Merge branch 'test' 2024-10-12 11:49:11 +08:00
zengxiaobo
5530f0321b Merge branch 'test' 2024-10-10 11:08:50 +08:00
zengxiaobo
291a040c82 Merge branch 'test' 2024-07-26 11:43:15 +08:00
zengxiaobo
ef9ed1e697 Merge branch 'test' 2024-07-24 18:43:39 +08:00
zengxiaobo
ded88b3a60 Merge branch 'test' 2024-07-17 18:05:29 +08:00
zengxiaobo
0ae5b0d20c feat: 增加live profile 2024-07-11 16:43:04 +08:00
9 changed files with 261 additions and 5 deletions

View File

@ -25,4 +25,13 @@ public interface IPageReq {
default List<String> getSort() { default List<String> getSort() {
return ImmutableList.of(); return ImmutableList.of();
} }
/**
* 部分分页不需要查询count支持指定是否查询count
*
* @return
*/
default Boolean isSearchCount() {
return true;
}
} }

View File

@ -7,10 +7,14 @@ import lombok.NoArgsConstructor;
import java.util.List; import java.util.List;
/**
* 如果作为接口调用的参数使用建议使用{@link PageReqV2}支持builder 模式构建参数对参数构造更友好
*/
@Data @Data
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Deprecated
public class PageReq implements IPageReq { public class PageReq implements IPageReq {
Integer page; Integer page;
Integer pageSize; Integer pageSize;

View File

@ -0,0 +1,47 @@
package cn.axzo.foundation.page;
import cn.axzo.foundation.dao.support.wrapper.CriteriaField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.apache.commons.lang3.BooleanUtils;
import java.util.List;
/**
* 默认分页请求基类
* 1支持builder方式构建参数
* 2默认分页大小为1000
* 3支持指定不分页searchCount = false
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class PageReqV2 implements IPageReq {
private static final Integer DEFAULT_PAGE_SIZE_V2 = 1000;
@CriteriaField(ignore = true)
Integer page;
@CriteriaField(ignore = true)
Integer pageSize;
@CriteriaField(ignore = true)
Boolean searchCount;
@CriteriaField(ignore = true)
List<String> sort;
@Override
public Integer getPage() {
return page == null || page < 1 ? 1 : page;
}
@Override
public Integer getPageSize() {
return pageSize == null || pageSize < 1 ? DEFAULT_PAGE_SIZE_V2 : pageSize;
}
public Boolean isSearchCount() {
// 只要不明确指定 searchCount = false都要查count信息
return BooleanUtils.isNotFalse(searchCount);
}
}

View File

@ -1,6 +1,7 @@
package cn.axzo.foundation.util; package cn.axzo.foundation.util;
import cn.axzo.foundation.page.IPageReq; import cn.axzo.foundation.page.IPageReq;
import cn.axzo.foundation.page.PageReqV2;
import cn.axzo.foundation.page.PageResp; import cn.axzo.foundation.page.PageResp;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
@ -10,6 +11,40 @@ import java.util.function.Function;
public class PageUtils { public class PageUtils {
/**
* <pre>
* 快捷拉取所有分页数据默认从第一页开始拉取直到返回的记录行数小于 预期的行数
* 分页大小 pageReq.getPageSize()可通过该参数调整
* </pre>
* XXX: 注意判断数据查询的完成条件 当前也页的记录行数 小于 分页大小所以对于会破坏查询条件的请求参数该方法不保证会返回所有数据
*
*
* <pre>
* 示例代码
* import cn.axzo.foundation.util.PageUtils;
* // case 1. nodeUserService定义了以下方法
* PageResp<OrgNodeUserDTO> list(ListOrgNodeUserReq req); // ListOrgNodeUserReq extends PageReqV2
* // 获取所有分页数据
* List<OrgNodeUserDTO> nodeUsers = PageUtil.drainAll(req, nodeUserService::list);
*
* // case 2. nodeUserApi定义了以下方法
* ApiResult<PageResp<OrgNodeUserDTO>> list(ListOrgNodeUserReq req); // ListOrgNodeUserReq extends PageReqV2
* List<OrgNodeUserDTO> nodeUsers = PageUtil.drainAll(req, r -> nodeUserApi.list(r).getData());
* </pre>
*
* @param pageReq
* @param function
* @param <T>
* @param <R>
* @return
*/
public static <T, R extends PageReqV2> List<T> drainAll(R pageReq, Function<R, PageResp<T>> function) {
return PageUtils.drainAll(pageNumber -> {
pageReq.setPage(pageNumber);
return function.apply(pageReq);
});
}
/** /**
* 将所有的数据通过page接口写入到list. 并返回 * 将所有的数据通过page接口写入到list. 并返回
* function中需要参数为新的pageNum, 默认从第一页开始加载. 直到返回的记录行数小于 预期的行数 * function中需要参数为新的pageNum, 默认从第一页开始加载. 直到返回的记录行数小于 预期的行数

View File

@ -2,6 +2,7 @@ package cn.axzo.foundation.util;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC; import org.slf4j.MDC;
@UtilityClass @UtilityClass
@ -12,27 +13,35 @@ public class TraceUtils {
*/ */
public static final String TRACE_ID_IN_MDC = "TraceId"; public static final String TRACE_ID_IN_MDC = "TraceId";
/**
* 老服务用的是 ctxLogId这里兼容一下老服务
*/
public static final String TRACE_ID_CTX_LOG_ID = "ctxLogId";
public String getOrCreateTraceId() { public String getOrCreateTraceId() {
String res = MDC.get(TRACE_ID); String res = getTraceId();
if (Strings.isNullOrEmpty(res)) { if (Strings.isNullOrEmpty(res)) {
res = UUIDBuilder.generateShortUuid(); res = UUIDBuilder.generateShortUuid();
MDC.put(TRACE_ID, res); MDC.put(TRACE_ID, res);
MDC.put(TRACE_ID_IN_MDC, res); MDC.put(TRACE_ID_IN_MDC, res);
MDC.put(TRACE_ID_CTX_LOG_ID, res);
} }
return res; return res;
} }
public String getTraceId() { public String getTraceId() {
return MDC.get(TRACE_ID); return StringUtils.firstNonBlank(MDC.get(TRACE_ID_CTX_LOG_ID), MDC.get(TRACE_ID));
} }
public void putTraceId(String traceId) { public void putTraceId(String traceId) {
MDC.put(TRACE_ID, traceId); MDC.put(TRACE_ID, traceId);
MDC.put(TRACE_ID_IN_MDC, traceId); MDC.put(TRACE_ID_IN_MDC, traceId);
MDC.put(TRACE_ID_CTX_LOG_ID, traceId);
} }
public void removeTraceId() { public void removeTraceId() {
MDC.remove(TRACE_ID); MDC.remove(TRACE_ID);
MDC.remove(TRACE_ID_IN_MDC); MDC.remove(TRACE_ID_IN_MDC);
MDC.remove(TRACE_ID_CTX_LOG_ID);
} }
} }

View File

@ -7,6 +7,7 @@ import cn.axzo.foundation.util.PageUtils;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.core.metadata.OrderItem;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import org.apache.commons.lang3.BooleanUtils;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -34,6 +35,7 @@ public class PageConverter {
= new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(current, pageSize); = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(current, pageSize);
List<OrderItem> orderItems = MybatisPlusConverterUtils.convertOrderItems(page.getSort(), entityClz); List<OrderItem> orderItems = MybatisPlusConverterUtils.convertOrderItems(page.getSort(), entityClz);
myBatisPage.setOrders(orderItems); myBatisPage.setOrders(orderItems);
myBatisPage.setSearchCount(BooleanUtils.isNotFalse(page.isSearchCount()));
return myBatisPage; return myBatisPage;
} }

View File

@ -2,6 +2,7 @@ package cn.axzo.foundation.web.support.interceptors;
import cn.axzo.foundation.util.TraceUtils; import cn.axzo.foundation.util.TraceUtils;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -14,7 +15,7 @@ public class TraceInterceptor implements HandlerInterceptor {
@Override @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String upstreamTraceId = request.getHeader(TraceUtils.TRACE_ID); String upstreamTraceId = StringUtils.firstNonBlank(request.getHeader(TraceUtils.TRACE_ID_CTX_LOG_ID), request.getHeader(TraceUtils.TRACE_ID));
if (!Strings.isNullOrEmpty(upstreamTraceId)) { if (!Strings.isNullOrEmpty(upstreamTraceId)) {
// 优先使用上游的 traceId // 优先使用上游的 traceId
TraceUtils.putTraceId(upstreamTraceId); TraceUtils.putTraceId(upstreamTraceId);
@ -22,6 +23,7 @@ public class TraceInterceptor implements HandlerInterceptor {
String traceId = TraceUtils.getOrCreateTraceId(); String traceId = TraceUtils.getOrCreateTraceId();
request.setAttribute(TraceUtils.TRACE_ID_IN_MDC, traceId); request.setAttribute(TraceUtils.TRACE_ID_IN_MDC, traceId);
request.setAttribute(TraceUtils.TRACE_ID_CTX_LOG_ID, traceId);
return true; return true;
} }

View File

@ -27,10 +27,14 @@ import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
* 统一打印日志(支持忽略此aop, 采用@IgnoreRequestLog注解) * 统一打印日志(支持忽略此aop, 采用@IgnoreRequestLog注解)<br>
* @Deprecated 改类不会处理 @RequestMapping没有在controller上定义的情况<br>
* 建议使用{@link RequestLogV2Aspect}
* @see RequestLogV2Aspect
**/ **/
@Aspect @Aspect
@Slf4j @Slf4j
@Deprecated
public class RequestLogAspect { public class RequestLogAspect {
private final static Integer DEFAULT_LOG_SIZE = 2048; private final static Integer DEFAULT_LOG_SIZE = 2048;
private Integer logSize; private Integer logSize;

View File

@ -0,0 +1,144 @@
package cn.axzo.foundation.web.support.log;
import cn.axzo.foundation.exception.BusinessException;
import cn.axzo.foundation.result.ApiResult;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableSet;
import lombok.Builder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 统一打印日志(支持忽略此aop, 采用@IgnoreRequestLog注解)
**/
@Aspect
@Slf4j
public class RequestLogV2Aspect {
private final static Integer DEFAULT_LOG_SIZE = 2048;
private Integer logSize;
@Builder
public RequestLogV2Aspect(Integer logSize) {
this.logSize = Optional.ofNullable(logSize).orElse(DEFAULT_LOG_SIZE);
}
private static Set<Class<?>> EXCLUDE_CLASSES = ImmutableSet.of(ServletRequest.class, ServletResponse.class,
MultipartFile.class, BindingResult.class);
@Around("@within(restController)||@annotation(restController)")
public Object doAround(ProceedingJoinPoint joinPoint, RestController restController) throws Throwable {
//避免拦截非http请求. 如feign
if (RequestContextHolder.getRequestAttributes() == null) {
return joinPoint.proceed();
}
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
if (request != null) {
//用于在AbstractExceptionHandler中为dumpRequest, AbstractExceptionHandler无法直接获取body
request.setAttribute("params", this.getUserArgs(joinPoint.getArgs()));
}
IgnoreRequestLog annotation = AnnotationUtils.findAnnotation(((MethodSignature) joinPoint.getSignature()).getMethod(),
IgnoreRequestLog.class);
// 获取注解中的ignoreTypes
ImmutableSet<IgnoreRequestLog.IgnoreType> ignoreTypes = Optional.ofNullable(annotation)
.map(a -> ImmutableSet.copyOf(a.types())).orElse(ImmutableSet.of());
Stopwatch stopwatch = Stopwatch.createStarted();
String requestLog = buildRequestLog(joinPoint, ignoreTypes);
String userSession = StringUtils.EMPTY;
String employeeSession = StringUtils.EMPTY;
String appClient = StringUtils.EMPTY;
Object proceed;
try {
proceed = joinPoint.proceed(joinPoint.getArgs());
} catch (BusinessException e) {
//stein会收集error级别的异常并告警. 对BizException. 不期望收到告警邮件. 因此将BizException对应的日志级别调整为warn
log.warn("api log, process error, caught BizException, url = {}, params = {}, userSession = {}, employeeSession = {}, appClient = {}, time cost = {} ms, BizException:",
request.getRequestURI(), requestLog, userSession, employeeSession, appClient, stopwatch.elapsed(TimeUnit.MILLISECONDS), e);
throw e;
} catch (Exception e) {
log.error("api log, process error, uncaught exception, url = {}, params = {}, userSession = {}, employeeSession = {}, appClient = {}, time cost = {} ms, exception:",
request.getRequestURI(), requestLog, userSession, employeeSession, appClient, stopwatch.elapsed(TimeUnit.MILLISECONDS), e);
throw e;
} catch (Error error) {
log.error("api log, process error, uncaught error, url = {}, params = {}, userSession = {}, employeeSession = {}, appClient = {}, time cost = {} ms, error:",
request.getRequestURI(), requestLog, userSession, employeeSession, appClient, stopwatch.elapsed(TimeUnit.MILLISECONDS), error);
throw error;
}
// 如果该接口定义的类型不是SILENT的, 则打印日志
if (!ignoreTypes.contains(IgnoreRequestLog.IgnoreType.ALL)) {
String responseLog;
try {
responseLog = buildResponseLog(proceed, ignoreTypes);
} catch (Exception ex) {
log.error("api log, process error, resp serialize error, url = {}, params = {}, userSession = {}, employeeSession = {}, appClient = {}, time cost = {} ms, error:",
request.getRequestURI(), requestLog, userSession, employeeSession, appClient, stopwatch.elapsed(TimeUnit.MILLISECONDS), ex);
throw ex;
}
log.info("api log, url = {}, params = {}, userSession = {}, employeeSession = {}, appClient = {}, result = {}, time cost = {} ms",
request.getRequestURI(),
StringUtils.left(requestLog, logSize),
userSession,
employeeSession,
appClient,
responseLog,
stopwatch.elapsed(TimeUnit.MILLISECONDS));
}
return proceed;
}
private String buildRequestLog(ProceedingJoinPoint joinPoint, Set<IgnoreRequestLog.IgnoreType> ignoreTypes) {
if (ignoreTypes.contains(IgnoreRequestLog.IgnoreType.REQUEST)) {
return "IGNORED";
}
return JSONObject.toJSONString(this.getUserArgs(joinPoint.getArgs()));
}
private String buildResponseLog(Object proceed, Set<IgnoreRequestLog.IgnoreType> ignoreTypes) {
if (proceed == null || !(ApiResult.class.isAssignableFrom(proceed.getClass()))) {
return StringUtils.EMPTY;
}
if (ignoreTypes.contains(IgnoreRequestLog.IgnoreType.RESPONSE)) {
return "IGNORED";
}
return StringUtils.left(JSONObject.toJSONString(proceed), logSize);
}
protected List<Object> getUserArgs(Object[] args) {
return Arrays.stream(args)
.filter((p) ->
Objects.nonNull(p) && EXCLUDE_CLASSES.stream().noneMatch(clz -> clz.isAssignableFrom(p.getClass()))
).collect(Collectors.toList());
}
}