From a45271141343767f0e6931c256dca16e29af3e71 Mon Sep 17 00:00:00 2001 From: yanglin Date: Tue, 2 Apr 2024 14:43:05 +0800 Subject: [PATCH] =?UTF-8?q?REQ-2135:=20=E9=80=9A=E8=BF=87=E5=88=86?= =?UTF-8?q?=E6=9E=90=E5=AE=9E=E9=99=85=E6=89=A7=E8=A1=8C=E7=9A=84SQL?= =?UTF-8?q?=E6=9D=A5=E5=88=86=E6=9E=90=E5=BE=85=E5=8A=9E=E4=B8=BA=E4=BD=95?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E6=9F=A5=E8=AF=A2=E5=87=BA=E6=9D=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- inside-notices/pom.xml | 5 +- .../service/todo/TodoRangeQueryService.java | 85 ++++++---- .../todo/queryanalyze/AnalyzeResult.java | 46 +++++ .../todo/queryanalyze/AnalyzeVisitor.java | 49 ++++++ .../todo/queryanalyze/EvalVisitor.java | 157 ++++++++++++++++++ .../service/todo/queryanalyze/Record.java | 52 ++++++ .../queryanalyze/SQLCollectInterceptor.java | 91 ++++++++++ .../todo/queryanalyze/SetTypeVisitor.java | 69 ++++++++ .../todo/queryanalyze/SimpleAnalyzer.java | 56 +++++++ .../queryanalyze/SqlInterceptorConfig.java | 16 ++ .../request/PendingMessagePageRequest.java | 4 +- .../pending/response/AnalysisPage.java | 10 +- .../java/cn/axzo/msg/center/dal/TodoDao.java | 6 +- .../MybatisInterceptorAnalyzeProcessor.java | 2 +- 14 files changed, 606 insertions(+), 42 deletions(-) create mode 100644 inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/AnalyzeResult.java create mode 100644 inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/AnalyzeVisitor.java create mode 100644 inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/EvalVisitor.java create mode 100644 inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/Record.java create mode 100644 inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/SQLCollectInterceptor.java create mode 100644 inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/SetTypeVisitor.java create mode 100644 inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/SimpleAnalyzer.java create mode 100644 inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/SqlInterceptorConfig.java diff --git a/inside-notices/pom.xml b/inside-notices/pom.xml index 7b751bef..c5063d08 100644 --- a/inside-notices/pom.xml +++ b/inside-notices/pom.xml @@ -21,6 +21,10 @@ + + com.alibaba + druid + cn.axzo.basics basics-profiles-api @@ -81,7 +85,6 @@ mysql mysql-connector-java - runtime org.mapstruct diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/TodoRangeQueryService.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/TodoRangeQueryService.java index 047f5453..1c904a19 100644 --- a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/TodoRangeQueryService.java +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/TodoRangeQueryService.java @@ -3,21 +3,20 @@ package cn.axzo.msg.center.message.service.todo; import cn.axzo.maokai.api.util.Ref; import cn.axzo.maokai.api.vo.response.tree.ValueNode; import cn.axzo.msg.center.common.enums.TableIsDeleteEnum; +import cn.axzo.msg.center.dal.TodoDao; import cn.axzo.msg.center.dal.mapper.TodoBusinessMapper; -import cn.axzo.msg.center.dal.mapper.TodoMapper; import cn.axzo.msg.center.domain.entity.MessageGroupNode; import cn.axzo.msg.center.domain.entity.PendingRecordAdapter; import cn.axzo.msg.center.domain.entity.Todo; import cn.axzo.msg.center.domain.entity.TodoBusiness; import cn.axzo.msg.center.domain.persistence.BaseEntityExt; -import cn.axzo.msg.center.message.domain.dto.MessageTemplateDTO; import cn.axzo.msg.center.message.domain.param.MessageGroupNodeStatisticParam; -import cn.axzo.msg.center.message.service.MessageTemplateNewService; import cn.axzo.msg.center.message.service.group.GroupTemplateService; import cn.axzo.msg.center.message.service.group.NodeWrapper; import cn.axzo.msg.center.message.service.impl.PendingMessageNewServiceImpl; -import cn.axzo.msg.center.message.service.todo.pagequery.PageQueryResult; import cn.axzo.msg.center.message.service.todo.pagequery.PageQuerySort; +import cn.axzo.msg.center.message.service.todo.queryanalyze.SQLCollectInterceptor; +import cn.axzo.msg.center.message.service.todo.queryanalyze.SimpleAnalyzer; import cn.axzo.msg.center.service.enums.PendingMessageRoleCategoryEnum; import cn.axzo.msg.center.service.enums.PendingMessageStateEnum; import cn.axzo.msg.center.service.enums.TodoType; @@ -32,11 +31,11 @@ import cn.axzo.msg.center.utils.DateFormatUtil; import cn.axzo.msg.center.utils.QueryFormatter; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; -import com.google.common.collect.ImmutableMap; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Component; import javax.annotation.Nullable; @@ -48,7 +47,6 @@ import java.util.List; import java.util.function.Function; import static cn.axzo.msg.center.inside.notices.utils.Queries.query; -import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; /** @@ -62,9 +60,8 @@ import static java.util.stream.Collectors.toSet; public class TodoRangeQueryService { private final TodoBusinessMapper todoBusinessMapper; - private final TodoMapper todoMapper; + private final TodoDao todoDao; private final PendingMessageNewServiceImpl pendingMessageNewServiceImpl; - private final MessageTemplateNewService messageTemplateNewService; private final TodoRespBuilder todoRespBuilder; private final AnalysisConfig analysisConfig; private final GroupTemplateService groupTemplateService; @@ -75,7 +72,7 @@ public class TodoRangeQueryService { List templateCodes = determineVisibleTemplateCodes(request); if (CollectionUtils.isEmpty(templateCodes)) return AnalysisPage.emptyPage(request.getPage(), request.getPageSize()); - PageQueryResult queryResult; + AnalysisPage queryResult; if (request.determineToDoType() == TodoType.EXECUTABLE && request.getRoleCategory() == PendingMessageRoleCategoryEnum.PROMOTER) // 我发起的 @@ -84,27 +81,20 @@ public class TodoRangeQueryService { // 可执行的/抄送我的 queryResult = pageQueryTodo(request, templateCodes); List messages = todoRespBuilder - .convertAdapter2MessageResponse(queryResult.getAdapters(), request.getTerminalType()); - AnalysisPage pageResult = new AnalysisPage<>( - request.getPage(), request.getPageSize(), queryResult.getTotal(), messages); + .convertAdapter2MessageResponse(queryResult.getList(), request.getTerminalType()); + AnalysisPage pageResult = createAnalysisPage( + request, queryResult.getTotalElements(), messages); pageResult.setEnableAnalysis(analysisConfig.determineAnalysisEnable(request.getAnalysisToken())); pageResult.addAnalysis(queryResult.getAnalysis()); pageResult.addAnalysis("request", request); pageResult.addAnalysis("terminals", pendingMessageNewServiceImpl::getTerminalConfigInfo); - pageResult.addAnalysis("queryTemplates", () -> { - List messageTemplates = messageTemplateNewService - .listByTemplateCodes(templateCodes); - return messageTemplates.stream() - .map(t -> String.format("%s:%s", t.getCode(), t.getName())) - .collect(toList()); - }); return pageResult; } /** * 业务视角 */ - private PageQueryResult pageQueryBusiness(PendingMessagePageRequest request, List templateCodes) { + private AnalysisPage pageQueryBusiness(PendingMessagePageRequest request, List templateCodes) { Date startingAt = DateFormatUtil.toDate(LocalDateTime.now().minusDays(90)); LambdaQueryWrapper query = businessQuery(request.getTitle()) .eq(TodoBusiness::getPromoterPersonId, request.getPersonId()) @@ -115,20 +105,22 @@ public class TodoRangeQueryService { PageQuerySort.TODO_BUSINESS.appendSortExpr(request, query); IPage pageData = todoBusinessMapper.selectPage(request.toPage(), query); List messages = todoRespBuilder.buildBusinessAdapters(pageData.getRecords()); - return new PageQueryResult(pageData.getTotal(), messages, ImmutableMap.of( - "query", QueryFormatter.format(query))); + AnalysisPage pageResult = createAnalysisPage(request, pageData.getTotal(), messages); + pageResult.addAnalysis("query", QueryFormatter.format(query)); + return pageResult; } /** * 接收者视角 */ - private PageQueryResult pageQueryTodo(PendingMessagePageRequest request, List templateCodes) { + private AnalysisPage pageQueryTodo( + PendingMessagePageRequest request, List templateCodes) { Collection businessIdsByLike = Collections.emptySet(); // 模糊匹配标题/内容 if (StringUtils.isNotBlank(request.getTitle())) { List businesses = todoBusinessMapper.selectList(businessQuery(request.getTitle())); if (businesses.isEmpty()) - return PageQueryResult.empty(); + return AnalysisPage.emptyPage(request.getPage(), request.getPageSize()); businessIdsByLike = businesses.stream() .map(BaseEntityExt::getId) .collect(toSet()); @@ -160,11 +152,45 @@ public class TodoRangeQueryService { // 用于查询的模版会通过一个单独的字段打印出来, 用于排查问题 .in(Todo::getTemplateCode, templateCodes); PageQuerySort.TODO.appendSortExpr(request, query); - IPage pageData = todoMapper.selectPage(request.toPage(), query); + Todo analyzeTodo = getAnalyzeTodo(request); + if (analyzeTodo != null) + SQLCollectInterceptor.enableCollectSQL(); + IPage pageData; + Ref runnableSQL = Ref.create(); + try { + pageData = todoDao.page(request.toPage(), query); + runnableSQL.set(SQLCollectInterceptor.getSQL().orElse(null)); + } finally { + SQLCollectInterceptor.disableCollectSQL(); + } List messages = todoRespBuilder.buildTodoAdapters(pageData.getRecords()); - return new PageQueryResult(pageData.getTotal(), messages, ImmutableMap.of( - "determinedOuIds", ouCollector.get(), - "query", QueryFormatter.format(query))); + AnalysisPage pageResult = createAnalysisPage(request, pageData.getTotal(), messages); + pageResult.addAnalysis("analyzeTodo", analyzeTodo == null ? "Can't find analyze todo" : analyzeTodo); + pageResult.addAnalysis("query", () -> runnableSQL.isNull() + ? QueryFormatter.format(query) : runnableSQL.get()); + pageResult.addAnalysis("eval", () -> { + String sql = runnableSQL.get(); + if (sql == null) return null; + return new SimpleAnalyzer().analyze(sql, analyzeTodo); + }); + return pageResult; + } + + @NotNull + private AnalysisPage createAnalysisPage( + PendingMessagePageRequest request, long total, List messages) { + AnalysisPage pageResult = new AnalysisPage<>( + request.getPage(), request.getPageSize(), total, messages); + pageResult.setEnableAnalysis(analysisConfig.determineAnalysisEnable(request.getAnalysisToken())); + return pageResult; + } + + private Todo getAnalyzeTodo(PendingMessagePageRequest request) { + if (request.getTodoId() != null && request.getTodoId() > 0) + return todoDao.getById(request.getTodoId()); + else if (StringUtils.isNotBlank(request.getTodoCode())) + return todoDao.findTodoByCode(request.getTodoCode()).orElse(null); + return null; } /** @@ -227,8 +253,7 @@ public class TodoRangeQueryService { .and(todoType == TodoType.COPIED_TO_ME, nested -> nested .eq(Todo::getType, TodoType.COPIED_TO_ME) .eq(Todo::getState, PendingMessageStateEnum.CREATED)); - Integer count = todoMapper.selectCount(query); - return count == null ? 0 : count; + return todoDao.count(query); } public List> determineStatNodes(MessageGroupNodeStatisticParam request) { diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/AnalyzeResult.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/AnalyzeResult.java new file mode 100644 index 00000000..6f930de6 --- /dev/null +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/AnalyzeResult.java @@ -0,0 +1,46 @@ +package cn.axzo.msg.center.message.service.todo.queryanalyze; + +import cn.axzo.msg.center.utils.DateFormatUtil; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLObject; +import com.alibaba.fastjson.JSON; +import lombok.Data; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * @author yanglin + */ +@Data +public class AnalyzeResult { + + private String warn; + private List failMatches = new ArrayList<>(); + + void addFailMatch(FailMatch failMatch) { + failMatches.add(failMatch); + } + + @Getter + public static class FailMatch { + + private final String expected; + private final String actual; + + FailMatch(SQLObject expr, String column, Object actualValue) { + Object value = actualValue; + if (actualValue instanceof Date) + value = DateFormatUtil.toReadableString((Date) actualValue); + this.expected = SQLUtils.toMySqlString(expr); + this.actual = String.format("%s = %s", column, value); + } + } + + @Override + public String toString() { + return JSON.toJSONString(this); + } +} \ No newline at end of file diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/AnalyzeVisitor.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/AnalyzeVisitor.java new file mode 100644 index 00000000..c9ade581 --- /dev/null +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/AnalyzeVisitor.java @@ -0,0 +1,49 @@ +package cn.axzo.msg.center.message.service.todo.queryanalyze; + +import cn.axzo.msg.center.message.service.todo.queryanalyze.AnalyzeResult.FailMatch; +import com.alibaba.druid.sql.ast.SQLObject; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLInListExpr; +import com.alibaba.druid.sql.visitor.SQLASTVisitor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * @author yanglin + */ +@Getter +@RequiredArgsConstructor +class AnalyzeVisitor implements SQLASTVisitor { + + private final Record record; + private final AnalyzeResult analyzeResult = new AnalyzeResult(); + + @Override + public void endVisit(SQLBinaryOpExpr x) { + String column = EvalVisitor.findColumn(x.getLeft()); + if (column == null) return; + if (shouldCollectAsFailMatch(x)) { + Object value = record.getValue(column, SetTypeVisitor.getType(x.getParent())); + analyzeResult.addFailMatch(new FailMatch(x, column, value)); + } + } + + @Override + public void endVisit(SQLInListExpr x) { + String column = EvalVisitor.findColumn(x.getExpr()); + if (column == null) return; + if (shouldCollectAsFailMatch(x)) { + Object value = record.getValue(column, SetTypeVisitor.getType(x)); + analyzeResult.addFailMatch(new FailMatch(x, column, value)); + } + } + + private boolean shouldCollectAsFailMatch(SQLObject x) { + Object value = EvalVisitor.getValue(x); + if (value instanceof Boolean && (Boolean) value) + return false; + SQLObject parent = x.getParent(); + return parent == null || shouldCollectAsFailMatch(parent); + } + +} \ No newline at end of file diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/EvalVisitor.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/EvalVisitor.java new file mode 100644 index 00000000..543b90d8 --- /dev/null +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/EvalVisitor.java @@ -0,0 +1,157 @@ +package cn.axzo.msg.center.message.service.todo.queryanalyze; + +import cn.axzo.maokai.api.util.Ref; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLObject; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr; +import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator; +import com.alibaba.druid.sql.ast.expr.SQLCharExpr; +import com.alibaba.druid.sql.ast.expr.SQLDoubleExpr; +import com.alibaba.druid.sql.ast.expr.SQLFloatExpr; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.expr.SQLInListExpr; +import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr; +import com.alibaba.druid.sql.ast.expr.SQLNullExpr; +import com.alibaba.druid.sql.ast.expr.SQLNumberExpr; +import com.alibaba.druid.sql.ast.expr.SQLSmallIntExpr; +import com.alibaba.druid.sql.visitor.SQLASTVisitor; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; + +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Supplier; + +import static java.util.stream.Collectors.toSet; + +/** + * @author yanglin + */ +@Slf4j +@RequiredArgsConstructor +class EvalVisitor implements SQLASTVisitor { + + private static final String VALUE = "value"; + + private final Record record; + + // !! eval + + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public boolean visit(SQLBinaryOpExpr x) { + x.getLeft().accept(this); + Object leftValue = x.getLeft().getAttribute(VALUE); + Ref visitRight = Ref.create(true); + Supplier rightValue = () -> { + x.getRight().accept(this); + visitRight.set(false); + return x.getRight().getAttribute(VALUE); + }; + SQLBinaryOperator operator = x.getOperator(); + Object value = false; + if (operator == SQLBinaryOperator.Equality) + value = Objects.equals(leftValue, rightValue.get()); + else if (operator == SQLBinaryOperator.NotEqual) + value = !Objects.equals(leftValue, rightValue.get()); + else if (operator == SQLBinaryOperator.BooleanOr) { + if ((boolean) leftValue) + visitRight.set(false); + value = (boolean) leftValue || (boolean) rightValue.get(); + } else if (operator == SQLBinaryOperator.BooleanAnd) + value = (boolean) leftValue && (boolean) rightValue.get(); + else if (operator == SQLBinaryOperator.LessThan) + value = ((Comparable) leftValue).compareTo(rightValue.get()) < 0; + else if (operator == SQLBinaryOperator.LessThanOrEqual) + value = ((Comparable) leftValue).compareTo(rightValue.get()) <= 0; + else if (operator == SQLBinaryOperator.GreaterThan) + value = ((Comparable) leftValue).compareTo(rightValue.get()) > 0; + else if (operator == SQLBinaryOperator.GreaterThanOrEqual) + value = ((Comparable) leftValue).compareTo(rightValue.get()) >= 0; + else if (x.getRight() instanceof SQLNullExpr) { + if (operator == SQLBinaryOperator.Is) + value = leftValue == null; + else if (operator == SQLBinaryOperator.IsNot) + value = leftValue != null; + } else + log.warn("Unsupported bin expr: {}", SQLUtils.toMySqlString(x)); + x.putAttribute(VALUE, value); + return visitRight.get(); + } + + @Override + public void endVisit(SQLInListExpr x) { + List targetList = x.getTargetList(); + if (CollectionUtils.isEmpty(targetList)) { + x.putAttribute(VALUE, false); + return; + } + String column = findColumn(x.getExpr()); + Object value = column != null + ? record.getValue(column, SetTypeVisitor.getType(x)) + : x.getExpr().getAttribute(VALUE); + Set listValues = targetList.stream() + .map(t -> t.getAttribute(VALUE)) + .filter(Objects::nonNull) + .collect(toSet()); + x.putAttribute(VALUE, listValues.contains(value)); + } + + // !! simple + + @Override + public void endVisit(SQLNumberExpr x) { + x.putAttribute(VALUE, x.getNumber()); + } + + @Override + public void endVisit(SQLSmallIntExpr x) { + x.putAttribute(VALUE, x.getNumber()); + } + + @Override + public void endVisit(SQLIntegerExpr x) { + x.putAttribute(VALUE, x.getNumber()); + } + + @Override + public void endVisit(SQLDoubleExpr x) { + x.putAttribute(VALUE, x.getValue()); + } + + @Override + public void endVisit(SQLFloatExpr x) { + x.putAttribute(VALUE, x.getValue()); + } + + @Override + public void endVisit(SQLCharExpr x) { + Date date = SetTypeVisitor.asDate(x); + if (date != null) + x.putAttribute(VALUE, date); + else + x.putAttribute(VALUE, x.getValue()); + } + + @Override + public void endVisit(SQLIdentifierExpr x) { + String column = findColumn(x); + Object value = record.getValue(column, SetTypeVisitor.getType(x.getParent())); + x.putAttribute(VALUE, value); + } + + static String findColumn(SQLExpr x) { + if (!(x instanceof SQLIdentifierExpr)) + return null; + return ((SQLIdentifierExpr) x).getName(); + } + + static Object getValue(SQLObject x) { + return x.getAttribute(VALUE); + } + +} \ No newline at end of file diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/Record.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/Record.java new file mode 100644 index 00000000..27f25e01 --- /dev/null +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/Record.java @@ -0,0 +1,52 @@ +package cn.axzo.msg.center.message.service.todo.queryanalyze; + +import com.google.common.base.CaseFormat; +import lombok.RequiredArgsConstructor; +import org.springframework.util.ReflectionUtils; + +import javax.annotation.Nullable; +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.util.Date; + +/** + * @author yanglin + */ +@RequiredArgsConstructor +class Record { + + private final Object value; + + Object getValue(String column, @Nullable Class type) { + if (value == null) return null; + String fieldName = CaseFormat.LOWER_UNDERSCORE.to( + CaseFormat.LOWER_CAMEL, column); + Field field = ReflectionUtils.findField(value.getClass(), fieldName); + if (field == null) return null; + field.setAccessible(true); + Object fieldValue = ReflectionUtils.getField(field, value); + if (type == null) return fieldValue; + String strValue = String.valueOf(fieldValue); + if (type == String.class) return strValue; + if (type == Long.class && fieldValue instanceof Date) + return ((Date) fieldValue).getTime(); + if (Number.class.isAssignableFrom(type)) { + if (type == Long.class) + return fieldValue instanceof Long ? (Long) fieldValue : Long.parseLong(strValue); + else if (type == Integer.class) + return fieldValue instanceof Integer ? (Integer) fieldValue : Integer.parseInt(strValue); + else if (type == Double.class) + return fieldValue instanceof Double ? (Double) fieldValue : Double.parseDouble(strValue); + else if (type == Float.class) + return fieldValue instanceof Float ? (Float) fieldValue : Float.parseFloat(strValue); + else if (type == Short.class) + return fieldValue instanceof Short ? (Short) fieldValue : Short.parseShort(strValue); + else if (type == Byte.class) + return fieldValue instanceof Byte ? (Byte) fieldValue : Byte.parseByte(strValue); + else if (type == BigDecimal.class) + return fieldValue instanceof BigDecimal ? (BigDecimal) fieldValue : new BigDecimal(strValue); + } + return fieldValue; + } + +} \ No newline at end of file diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/SQLCollectInterceptor.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/SQLCollectInterceptor.java new file mode 100644 index 00000000..3fddf4df --- /dev/null +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/SQLCollectInterceptor.java @@ -0,0 +1,91 @@ +package cn.axzo.msg.center.message.service.todo.queryanalyze; + +import com.mysql.cj.jdbc.ClientPreparedStatement; +import com.zaxxer.hikari.pool.ProxyStatement; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.executor.statement.StatementHandler; +import org.apache.ibatis.logging.jdbc.PreparedStatementLogger; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Signature; +import org.springframework.util.ReflectionUtils; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.sql.Statement; +import java.util.Optional; + +/** + * @author yanglin + */ +@Slf4j +@Intercepts(value = { + @Signature(type = StatementHandler.class, method = "parameterize", args = {Statement.class}) +}) +public class SQLCollectInterceptor implements Interceptor { + + private static final ThreadLocal LOCAL = new ThreadLocal<>(); + + public static void enableCollectSQL() { + LOCAL.set(new SQLInfo()); + } + + public static void disableCollectSQL() { + LOCAL.remove(); + } + + public static Optional getSQL() { + SQLInfo info = LOCAL.get(); + return info == null ? Optional.empty() : Optional.ofNullable(info.sql); + } + + @Override + public Object intercept(Invocation invocation) throws Throwable { + Object result = invocation.proceed(); + Object arg = invocation.getArgs()[0]; + try { + SQLInfo info = LOCAL.get(); + if (info != null) + collect(arg, info); + } catch (Exception e) { + log.warn("Error collecting SQL", e); + } + return result; + } + + private void collect(Object arg, SQLInfo info) throws Exception { + Statement stmt = null; + // logger is enabled + if (Proxy.isProxyClass(arg.getClass())) { + InvocationHandler invocationHandler = Proxy.getInvocationHandler(arg); + if (!(invocationHandler instanceof PreparedStatementLogger)) + return; + Statement proxiedStmt = ((PreparedStatementLogger) invocationHandler).getPreparedStatement(); + if (proxiedStmt instanceof ProxyStatement) + stmt = getStmtFromProxy((ProxyStatement) proxiedStmt); + } else if (arg instanceof ProxyStatement) { + stmt = getStmtFromProxy((ProxyStatement) arg); + } + if (stmt == null) + return; + if (!(stmt instanceof ClientPreparedStatement)) + return; + info.sql = ((ClientPreparedStatement) stmt).asSql(); + } + + private Statement getStmtFromProxy(ProxyStatement proxy) { + Field delegateField = ReflectionUtils.findField(proxy.getClass(), "delegate"); + if (delegateField == null) return null; + delegateField.setAccessible(true); + return (Statement) ReflectionUtils.getField(delegateField, proxy); + } + + @RequiredArgsConstructor + private static class SQLInfo { + private String sql; + } + +} diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/SetTypeVisitor.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/SetTypeVisitor.java new file mode 100644 index 00000000..1d989602 --- /dev/null +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/SetTypeVisitor.java @@ -0,0 +1,69 @@ +package cn.axzo.msg.center.message.service.todo.queryanalyze; + +import com.alibaba.druid.sql.ast.SQLObject; +import com.alibaba.druid.sql.ast.expr.SQLCharExpr; +import com.alibaba.druid.sql.ast.expr.SQLDoubleExpr; +import com.alibaba.druid.sql.ast.expr.SQLFloatExpr; +import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr; +import com.alibaba.druid.sql.ast.expr.SQLNumberExpr; +import com.alibaba.druid.sql.ast.expr.SQLSmallIntExpr; +import com.alibaba.druid.sql.visitor.SQLASTVisitor; + +import javax.annotation.Nullable; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * @author yanglin + */ +class SetTypeVisitor implements SQLASTVisitor { + + private static final String TYPE = "type"; + private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.Ss"); + static final SetTypeVisitor INSTANCE = new SetTypeVisitor(); + + @Override + public void endVisit(SQLNumberExpr x) { + x.getParent().putAttribute(TYPE, x.getNumber().getClass()); + } + + @Override + public void endVisit(SQLSmallIntExpr x) { + x.getParent().putAttribute(TYPE, x.getNumber().getClass()); + } + + @Override + public void endVisit(SQLIntegerExpr x) { + x.getParent().putAttribute(TYPE, x.getNumber().getClass()); + } + + @Override + public void endVisit(SQLDoubleExpr x) { + x.getParent().putAttribute(TYPE, Double.class); + } + + @Override + public void endVisit(SQLFloatExpr x) { + x.getParent().putAttribute(TYPE, Float.class); + } + + @Override + public void endVisit(SQLCharExpr x) { + Date date = asDate(x); + x.getParent().putAttribute(TYPE, date == null ? String.class : Date.class); + } + + @Nullable + static Date asDate(SQLCharExpr x) { + try { + return SDF.parse((String) x.getValue()); + } catch (ParseException e) { + return null; + } + } + + static Class getType(SQLObject x) { + return (Class)x.getAttribute(TYPE); + } +} \ No newline at end of file diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/SimpleAnalyzer.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/SimpleAnalyzer.java new file mode 100644 index 00000000..5321296b --- /dev/null +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/SimpleAnalyzer.java @@ -0,0 +1,56 @@ +package cn.axzo.msg.center.message.service.todo.queryanalyze; + +import com.alibaba.druid.sql.ast.SQLExpr; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLSelect; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock; +import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser; +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + +/** + * @author yanglin + */ +@Slf4j +public class SimpleAnalyzer { + + public AnalyzeResult analyze(String sql, Object value) { + try { + return analyzeImpl(sql, value); + } catch (Exception e) { + log.warn("Analyze error", e); + AnalyzeResult result = new AnalyzeResult(); + result.setWarn("Analyze error"); + return result; + } + } + + private AnalyzeResult analyzeImpl(String sql, Object value) { + SQLStatement stmt = new MySqlStatementParser(sql).parseSelect(); + AnalyzeResult result = new AnalyzeResult(); + SQLExpr whereExpr = findWhereExpr(stmt); + if (whereExpr == null) { + result.setWarn("Can't find where expr!"); + return result; + } + Record record = new Record(value); + whereExpr.accept(SetTypeVisitor.INSTANCE); + whereExpr.accept(new EvalVisitor(record)); + AnalyzeVisitor analyzeVisitor = new AnalyzeVisitor(record); + whereExpr.accept(analyzeVisitor); + return analyzeVisitor.getAnalyzeResult(); + } + + @Nullable + private SQLExpr findWhereExpr(SQLStatement stmt) { + if (!(stmt instanceof SQLSelectStatement)) return null; + SQLSelect select = ((SQLSelectStatement) stmt).getSelect(); + if (select == null) return null; + MySqlSelectQueryBlock query = (MySqlSelectQueryBlock) select.getQuery(); + if (query == null) return null; + return query.getWhere(); + } + +} \ No newline at end of file diff --git a/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/SqlInterceptorConfig.java b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/SqlInterceptorConfig.java new file mode 100644 index 00000000..56959e16 --- /dev/null +++ b/inside-notices/src/main/java/cn/axzo/msg/center/message/service/todo/queryanalyze/SqlInterceptorConfig.java @@ -0,0 +1,16 @@ +package cn.axzo.msg.center.message.service.todo.queryanalyze; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author yanglin + */ +@Configuration +public class SqlInterceptorConfig { + + @Bean + public SQLCollectInterceptor sqlCollectInterceptor() { + return new SQLCollectInterceptor(); + } +} diff --git a/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/request/PendingMessagePageRequest.java b/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/request/PendingMessagePageRequest.java index ee5c279c..07bb7705 100644 --- a/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/request/PendingMessagePageRequest.java +++ b/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/request/PendingMessagePageRequest.java @@ -127,9 +127,9 @@ public class PendingMessagePageRequest extends PageRequest implements Serializab private CopiedToMeParam copiedToMeParam; // !! 用于排查问题 - private String fetchTemplateCode; - private int fetchLimit = 5; private String analysisToken; + private String todoCode; + private Long todoId; public PendingMessageRoleCategoryEnum determineRoleCategory() { return roleCategory != null ? roleCategory : PendingMessageRoleCategoryEnum.EXECUTOR; diff --git a/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/response/AnalysisPage.java b/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/response/AnalysisPage.java index fa818c67..76829feb 100644 --- a/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/response/AnalysisPage.java +++ b/msg-center-api/src/main/java/cn/axzo/msg/center/service/pending/response/AnalysisPage.java @@ -34,15 +34,13 @@ public class AnalysisPage extends Page { } public void addAnalysis(Map analysis) { - if (enableAnalysis) { + if (enableAnalysis) this.analysis.putAll(analysis); - } } public void addAnalysis(String key, Object value) { - if (enableAnalysis) { + if (enableAnalysis && key != null && value != null) analysis.put(key, value); - } } /** @@ -50,7 +48,9 @@ public class AnalysisPage extends Page { */ public void addAnalysis(String key, Supplier provider) { if (enableAnalysis) { - analysis.put(key, provider.get()); + Object value = provider.get(); + if (value != null) + analysis.put(key, value); } } diff --git a/msg-center-dal/src/main/java/cn/axzo/msg/center/dal/TodoDao.java b/msg-center-dal/src/main/java/cn/axzo/msg/center/dal/TodoDao.java index f729c4c8..f2d6c32f 100644 --- a/msg-center-dal/src/main/java/cn/axzo/msg/center/dal/TodoDao.java +++ b/msg-center-dal/src/main/java/cn/axzo/msg/center/dal/TodoDao.java @@ -29,11 +29,11 @@ import static java.util.stream.Collectors.toSet; @Component public class TodoDao extends ServiceImpl { - public Optional findTodoByCode(String idCode) { - if (StringUtils.isBlank(idCode)) + public Optional findTodoByCode(String todoCode) { + if (StringUtils.isBlank(todoCode)) return Optional.empty(); Todo todo = lambdaQuery() - .eq(Todo::getIdentityCode, idCode) + .eq(Todo::getIdentityCode, todoCode) .eq(Todo::getIsDelete, TableIsDeleteEnum.NORMAL.value) .last("LIMIT 1") .one(); diff --git a/msg-notices/msg-notices-client/src/main/java/cn/axzo/msg/center/notices/client/config/MybatisInterceptorAnalyzeProcessor.java b/msg-notices/msg-notices-client/src/main/java/cn/axzo/msg/center/notices/client/config/MybatisInterceptorAnalyzeProcessor.java index 583321db..5f774040 100644 --- a/msg-notices/msg-notices-client/src/main/java/cn/axzo/msg/center/notices/client/config/MybatisInterceptorAnalyzeProcessor.java +++ b/msg-notices/msg-notices-client/src/main/java/cn/axzo/msg/center/notices/client/config/MybatisInterceptorAnalyzeProcessor.java @@ -21,7 +21,7 @@ import java.util.Set; * @author yanglin */ @Slf4j -@Component +//@Component public class MybatisInterceptorAnalyzeProcessor implements BeanPostProcessor { private static final Set> EXPECTED_INTERCEPTORS = Sets.newHashSet(