REQ-2135: 通过分析实际执行的SQL来分析待办为何没有查询出来
This commit is contained in:
parent
4499aae8f0
commit
a452711413
@ -21,6 +21,10 @@
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.axzo.basics</groupId>
|
||||
<artifactId>basics-profiles-api</artifactId>
|
||||
@ -81,7 +85,6 @@
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
|
||||
@ -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<String> templateCodes = determineVisibleTemplateCodes(request);
|
||||
if (CollectionUtils.isEmpty(templateCodes))
|
||||
return AnalysisPage.emptyPage(request.getPage(), request.getPageSize());
|
||||
PageQueryResult queryResult;
|
||||
AnalysisPage<PendingRecordAdapter> queryResult;
|
||||
if (request.determineToDoType() == TodoType.EXECUTABLE
|
||||
&& request.getRoleCategory() == PendingMessageRoleCategoryEnum.PROMOTER)
|
||||
// 我发起的
|
||||
@ -84,27 +81,20 @@ public class TodoRangeQueryService {
|
||||
// 可执行的/抄送我的
|
||||
queryResult = pageQueryTodo(request, templateCodes);
|
||||
List<PendingMessageResponse> messages = todoRespBuilder
|
||||
.convertAdapter2MessageResponse(queryResult.getAdapters(), request.getTerminalType());
|
||||
AnalysisPage<PendingMessageResponse> pageResult = new AnalysisPage<>(
|
||||
request.getPage(), request.getPageSize(), queryResult.getTotal(), messages);
|
||||
.convertAdapter2MessageResponse(queryResult.getList(), request.getTerminalType());
|
||||
AnalysisPage<PendingMessageResponse> 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<MessageTemplateDTO> 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<String> templateCodes) {
|
||||
private AnalysisPage<PendingRecordAdapter> pageQueryBusiness(PendingMessagePageRequest request, List<String> templateCodes) {
|
||||
Date startingAt = DateFormatUtil.toDate(LocalDateTime.now().minusDays(90));
|
||||
LambdaQueryWrapper<TodoBusiness> query = businessQuery(request.getTitle())
|
||||
.eq(TodoBusiness::getPromoterPersonId, request.getPersonId())
|
||||
@ -115,20 +105,22 @@ public class TodoRangeQueryService {
|
||||
PageQuerySort.TODO_BUSINESS.appendSortExpr(request, query);
|
||||
IPage<TodoBusiness> pageData = todoBusinessMapper.selectPage(request.toPage(), query);
|
||||
List<PendingRecordAdapter> messages = todoRespBuilder.buildBusinessAdapters(pageData.getRecords());
|
||||
return new PageQueryResult(pageData.getTotal(), messages, ImmutableMap.of(
|
||||
"query", QueryFormatter.format(query)));
|
||||
AnalysisPage<PendingRecordAdapter> pageResult = createAnalysisPage(request, pageData.getTotal(), messages);
|
||||
pageResult.addAnalysis("query", QueryFormatter.format(query));
|
||||
return pageResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收者视角
|
||||
*/
|
||||
private PageQueryResult pageQueryTodo(PendingMessagePageRequest request, List<String> templateCodes) {
|
||||
private AnalysisPage<PendingRecordAdapter> pageQueryTodo(
|
||||
PendingMessagePageRequest request, List<String> templateCodes) {
|
||||
Collection<Long> businessIdsByLike = Collections.emptySet();
|
||||
// 模糊匹配标题/内容
|
||||
if (StringUtils.isNotBlank(request.getTitle())) {
|
||||
List<TodoBusiness> 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<Todo> pageData = todoMapper.selectPage(request.toPage(), query);
|
||||
Todo analyzeTodo = getAnalyzeTodo(request);
|
||||
if (analyzeTodo != null)
|
||||
SQLCollectInterceptor.enableCollectSQL();
|
||||
IPage<Todo> pageData;
|
||||
Ref<String> runnableSQL = Ref.create();
|
||||
try {
|
||||
pageData = todoDao.page(request.toPage(), query);
|
||||
runnableSQL.set(SQLCollectInterceptor.getSQL().orElse(null));
|
||||
} finally {
|
||||
SQLCollectInterceptor.disableCollectSQL();
|
||||
}
|
||||
List<PendingRecordAdapter> messages = todoRespBuilder.buildTodoAdapters(pageData.getRecords());
|
||||
return new PageQueryResult(pageData.getTotal(), messages, ImmutableMap.of(
|
||||
"determinedOuIds", ouCollector.get(),
|
||||
"query", QueryFormatter.format(query)));
|
||||
AnalysisPage<PendingRecordAdapter> 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 <T> AnalysisPage<T> createAnalysisPage(
|
||||
PendingMessagePageRequest request, long total, List<T> messages) {
|
||||
AnalysisPage<T> 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<ValueNode<NodeWrapper>> determineStatNodes(MessageGroupNodeStatisticParam request) {
|
||||
|
||||
@ -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<FailMatch> 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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<Boolean> visitRight = Ref.create(true);
|
||||
Supplier<Object> 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<SQLExpr> 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<Object> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<SQLInfo> LOCAL = new ThreadLocal<>();
|
||||
|
||||
public static void enableCollectSQL() {
|
||||
LOCAL.set(new SQLInfo());
|
||||
}
|
||||
|
||||
public static void disableCollectSQL() {
|
||||
LOCAL.remove();
|
||||
}
|
||||
|
||||
public static Optional<String> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -34,23 +34,23 @@ public class AnalysisPage<T> extends Page<T> {
|
||||
}
|
||||
|
||||
public void addAnalysis(Map<String, Object> 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加查询成本较高的分析数据
|
||||
*/
|
||||
public void addAnalysis(String key, Supplier<Object> provider) {
|
||||
if (enableAnalysis) {
|
||||
analysis.put(key, provider.get());
|
||||
Object value = provider.get();
|
||||
if (value != null)
|
||||
analysis.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -29,11 +29,11 @@ import static java.util.stream.Collectors.toSet;
|
||||
@Component
|
||||
public class TodoDao extends ServiceImpl<TodoMapper, Todo> {
|
||||
|
||||
public Optional<Todo> findTodoByCode(String idCode) {
|
||||
if (StringUtils.isBlank(idCode))
|
||||
public Optional<Todo> 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();
|
||||
|
||||
@ -21,7 +21,7 @@ import java.util.Set;
|
||||
* @author yanglin
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
//@Component
|
||||
public class MybatisInterceptorAnalyzeProcessor implements BeanPostProcessor {
|
||||
|
||||
private static final Set<Class<?>> EXPECTED_INTERCEPTORS = Sets.newHashSet(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user