REQ-2135: 通过分析实际执行的SQL来分析待办为何没有查询出来

This commit is contained in:
yanglin 2024-04-02 14:43:05 +08:00
parent 4499aae8f0
commit a452711413
14 changed files with 606 additions and 42 deletions

View File

@ -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>

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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(