mirror of
https://github.com/hs-web/hsweb-framework.git
synced 2026-09-03 06:35:25 +08:00
feat: 增加异常分析功能
通过实现ExceptionAnalyzer拓展异常分析
This commit is contained in:
@@ -2,9 +2,7 @@ package org.hswebframework.web.crud.configuration;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.*;
|
||||
|
||||
public class DialectProviders {
|
||||
private static final Map<String, DialectProvider> allSupportedDialect = new HashMap<>();
|
||||
@@ -19,6 +17,10 @@ public class DialectProviders {
|
||||
}
|
||||
}
|
||||
|
||||
public static List<DialectProvider> all(){
|
||||
return new ArrayList<>(allSupportedDialect.values());
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static DialectProvider lookup(String dialect) {
|
||||
DialectProvider provider = allSupportedDialect.get(dialect);
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.hswebframework.web.crud.generator.*;
|
||||
import org.hswebframework.web.crud.query.DefaultQueryHelper;
|
||||
import org.hswebframework.web.crud.query.QueryHelper;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.hswebframework.web.crud.exception;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.hswebframework.web.crud.configuration.DialectProvider;
|
||||
import org.hswebframework.web.crud.configuration.DialectProviders;
|
||||
import org.hswebframework.web.exception.analyzer.ExceptionAnalyzerReporter;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
public class DatabaseExceptionAnalyzerReporter extends ExceptionAnalyzerReporter {
|
||||
|
||||
public DatabaseExceptionAnalyzerReporter() {
|
||||
init();
|
||||
}
|
||||
|
||||
void init() {
|
||||
addSimpleReporter(
|
||||
Pattern.compile("^Binding.*"),
|
||||
error -> log
|
||||
.warn(wrapLog("请在application.yml中正确配置`easyorm.dialect`,可选项为:{}"),
|
||||
DialectProviders
|
||||
.all()
|
||||
.stream()
|
||||
.map(DialectProvider::name)
|
||||
.collect(Collectors.toList())
|
||||
, error));
|
||||
|
||||
addSimpleReporter(
|
||||
Pattern.compile("^Unknown database.*"),
|
||||
error -> log
|
||||
.warn(wrapLog("请先手动创建数据库或者配置`easyorm.default-schema`,数据库名不能包含只能由`数字字母下划线`组成."), error));
|
||||
|
||||
initForPgsql();
|
||||
}
|
||||
|
||||
void initForPgsql() {
|
||||
addSimpleReporter(
|
||||
Pattern.compile(".*\\[3D000].*"),
|
||||
error -> log
|
||||
.warn(wrapLog("请先手动创建数据库,数据库名不能包含只能由`数字字母下划线`组成."), error));
|
||||
addSimpleReporter(
|
||||
Pattern.compile(".*\\[3F000].*"),
|
||||
error -> log
|
||||
.warn(wrapLog("请正确配置`easyorm.default-schema`为pgsql数据库中对应的schema."), error));
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import org.hswebframework.ezorm.rdb.executor.wrapper.ResultWrapper;
|
||||
import org.hswebframework.web.api.crud.entity.TransactionManagers;
|
||||
import org.hswebframework.web.datasource.DataSourceHolder;
|
||||
import org.hswebframework.web.datasource.R2dbcDataSource;
|
||||
import org.hswebframework.web.exception.I18nSupportException;
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.r2dbc.connection.ConnectionFactoryUtils;
|
||||
@@ -48,6 +49,17 @@ public class DefaultR2dbcExecutor extends R2dbcReactiveSqlExecutor {
|
||||
return sqlRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Statement prepareStatement(Statement statement, SqlRequest request) {
|
||||
try {
|
||||
return super.prepareStatement(statement, request);
|
||||
} catch (Throwable e) {
|
||||
throw new I18nSupportException
|
||||
.NoStackTrace("error.sql.prepare", e)
|
||||
.withSource("sql.prepare", request);
|
||||
}
|
||||
}
|
||||
|
||||
protected void bindNull(Statement statement, int index, Class type) {
|
||||
if (type == Date.class) {
|
||||
type = LocalDateTime.class;
|
||||
@@ -60,11 +72,12 @@ public class DefaultR2dbcExecutor extends R2dbcReactiveSqlExecutor {
|
||||
}
|
||||
|
||||
protected void bind(Statement statement, int index, Object value) {
|
||||
|
||||
if (value instanceof Date) {
|
||||
value = ((Date) value)
|
||||
.toInstant()
|
||||
.atZone(ZoneOffset.systemDefault())
|
||||
.toLocalDateTime();
|
||||
.toInstant()
|
||||
.atZone(ZoneOffset.systemDefault())
|
||||
.toLocalDateTime();
|
||||
}
|
||||
if (bindCustomSymbol) {
|
||||
statement.bind(getBindSymbol() + (index + getBindFirstIndex()), value);
|
||||
@@ -77,8 +90,8 @@ public class DefaultR2dbcExecutor extends R2dbcReactiveSqlExecutor {
|
||||
protected Mono<Connection> getConnection() {
|
||||
if (DataSourceHolder.isDynamicDataSourceReady()) {
|
||||
return DataSourceHolder.currentR2dbc()
|
||||
.flatMap(R2dbcDataSource::getNative)
|
||||
.flatMap(ConnectionFactoryUtils::getConnection);
|
||||
.flatMap(R2dbcDataSource::getNative)
|
||||
.flatMap(ConnectionFactoryUtils::getConnection);
|
||||
} else {
|
||||
return ConnectionFactoryUtils.getConnection(defaultFactory);
|
||||
}
|
||||
@@ -116,7 +129,7 @@ public class DefaultR2dbcExecutor extends R2dbcReactiveSqlExecutor {
|
||||
@Override
|
||||
@Transactional(transactionManager = TransactionManagers.reactiveTransactionManager)
|
||||
public Mono<Integer> update(String sql, Object... args) {
|
||||
return super.update(sql,args);
|
||||
return super.update(sql, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -128,18 +141,18 @@ public class DefaultR2dbcExecutor extends R2dbcReactiveSqlExecutor {
|
||||
@Override
|
||||
@Transactional(readOnly = true, transactionManager = TransactionManagers.reactiveTransactionManager)
|
||||
public Flux<Map<String, Object>> select(String sql, Object... args) {
|
||||
return super.select(sql,args);
|
||||
return super.select(sql, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true, transactionManager = TransactionManagers.reactiveTransactionManager)
|
||||
public <E> Flux<E> select(String sql, ResultWrapper<E, ?> wrapper) {
|
||||
return super.select(sql,wrapper);
|
||||
return super.select(sql, wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true, transactionManager = TransactionManagers.reactiveTransactionManager)
|
||||
public <E> Flux<E> select(SqlRequest sqlRequest, ResultWrapper<E, ?> wrapper) {
|
||||
return super.select(sqlRequest,wrapper);
|
||||
return super.select(sqlRequest, wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
org.hswebframework.web.crud.exception.DatabaseExceptionAnalyzerReporter
|
||||
@@ -6,4 +6,5 @@ error.data_error=Data error
|
||||
error.internal_server_error = Internal server error
|
||||
error.tree_entity_cyclic_dependency=Cannot modify parent node as oneself or one's own child node
|
||||
error.tree_entity_parent_id_not_exist=Parent node does not exist or has been deleted
|
||||
error.data.find.not_found=Data not found
|
||||
error.data.find.not_found=Data not found
|
||||
error.sql.prepare.failed.IndexOutOfBoundsException=Execute SQL failed, try check config: `easyorm.dialect`.
|
||||
@@ -6,4 +6,5 @@ error.data_error=\u6570\u636E\u9519\u8BEF
|
||||
error.internal_server_error=\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF
|
||||
error.tree_entity_cyclic_dependency=\u4E0D\u80FD\u4FEE\u6539\u7236\u8282\u70B9\u4E3A\u81EA\u5DF1\u6216\u8005\u81EA\u5DF1\u7684\u5B50\u8282\u70B9
|
||||
error.tree_entity_parent_id_not_exist=\u7236\u8282\u70B9\u4E0D\u5B58\u5728\u6216\u5DF2\u88AB\u5220\u9664
|
||||
error.data.find.not_found=\u6570\u636E\u4E0D\u5B58\u5728
|
||||
error.data.find.not_found=\u6570\u636E\u4E0D\u5B58\u5728
|
||||
error.sql.prepare.failed.IndexOutOfBoundsException=SQL\u6267\u884C\u5931\u8D25,\u8BF7\u5C1D\u8BD5\u68C0\u67E5`easyorm.dialect`\u914D\u7F6E.
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.hswebframework.web.crud.exception;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class DatabaseExceptionAnalyzerReporterTest {
|
||||
|
||||
DatabaseExceptionAnalyzerReporter reporter=new DatabaseExceptionAnalyzerReporter();
|
||||
@Test
|
||||
void testBinding(){
|
||||
Assertions.assertTrue(reporter.doReportException(
|
||||
new IndexOutOfBoundsException("Binding index 0 when only 0 parameters are expected ")
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testUnknownDatabase(){
|
||||
Assertions.assertTrue(reporter.doReportException(
|
||||
new IndexOutOfBoundsException("Unknown database 'jetlinks' ")
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testPgsqlUnknownDatabase(){
|
||||
Assertions.assertTrue(reporter.doReportException(
|
||||
new IndexOutOfBoundsException("[3D000] database \"jetlinks22\" does not exist")
|
||||
));
|
||||
}
|
||||
@Test
|
||||
void testPgsqlUnknownSchema(){
|
||||
Assertions.assertTrue(reporter.doReportException(
|
||||
new IndexOutOfBoundsException("[3F000] schema \"jetlinks22\" does not exist")
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.hswebframework.web.exception.analyzer;
|
||||
|
||||
/**
|
||||
* 异常分析器,用于分析异常信息. 实现此接口,并使用SPI进行拓展.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* META-INF/services/org.hswebframework.web.exception.analyzer.ExceptionAnalyzer
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @author zhouhao
|
||||
* @since 4.0.18
|
||||
* @see ExceptionAnalyzerReporter
|
||||
*/
|
||||
public interface ExceptionAnalyzer {
|
||||
|
||||
/**
|
||||
* 执行分析
|
||||
*
|
||||
* @param error 异常信息
|
||||
* @return 是否被处理
|
||||
*/
|
||||
boolean analyze(Throwable error);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.hswebframework.web.exception.analyzer;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 提供基础的异常分析器实现
|
||||
*
|
||||
* @author zhouhao
|
||||
* @since 4.0.18
|
||||
*/
|
||||
@Slf4j
|
||||
public class ExceptionAnalyzerReporter implements ExceptionAnalyzer {
|
||||
|
||||
private final List<Reporter> reporter = new CopyOnWriteArrayList<>();
|
||||
|
||||
|
||||
public static String wrapLog(String message) {
|
||||
char[] arr = new char[message.length() + 2];
|
||||
Arrays.fill(arr, '=');
|
||||
arr[0] = '\n';
|
||||
arr[arr.length - 1] = '\n';
|
||||
String line = new String(arr);
|
||||
return line + message + line;
|
||||
}
|
||||
|
||||
protected void addReporter(Predicate<Throwable> predicate,
|
||||
Consumer<Throwable> reporter) {
|
||||
this.reporter.add(new Reporter() {
|
||||
@Override
|
||||
public boolean predicate(Throwable error) {
|
||||
return predicate.test(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void report(Throwable error) {
|
||||
reporter.accept(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void addSimpleReporter(Pattern pattern, Consumer<Throwable> reporter) {
|
||||
|
||||
addReporter((error) -> error.getMessage() != null
|
||||
&& pattern.matcher(error.getMessage()).matches(),
|
||||
reporter);
|
||||
}
|
||||
|
||||
public boolean doReportException(Throwable failure) {
|
||||
Throwable cause = failure;
|
||||
while (cause != null) {
|
||||
for (Reporter _reporter : this.reporter) {
|
||||
if (_reporter.predicate(cause)) {
|
||||
_reporter.report(cause);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
cause = cause.getCause();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean analyze(Throwable error) {
|
||||
return doReportException(error);
|
||||
}
|
||||
|
||||
interface Reporter {
|
||||
|
||||
boolean predicate(Throwable error);
|
||||
|
||||
void report(Throwable error);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.hswebframework.web.exception.analyzer;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* 异常分析器,用于分析异常信息.使用{@link ExceptionAnalyzer}进行分析拓展.
|
||||
*
|
||||
* @author zhouhao
|
||||
* @see ExceptionAnalyzer
|
||||
* @since 4.0.18
|
||||
*/
|
||||
@Slf4j
|
||||
public class ExceptionAnalyzers {
|
||||
|
||||
private static final List<ExceptionAnalyzer> ANALYZER = new CopyOnWriteArrayList<>();
|
||||
|
||||
private ExceptionAnalyzers() {
|
||||
|
||||
}
|
||||
|
||||
static {
|
||||
ServiceLoader.load(ExceptionAnalyzer.class).forEach(ANALYZER::add);
|
||||
}
|
||||
|
||||
public static void addAnalyzer(ExceptionAnalyzer analyzer) {
|
||||
log.debug("add ExceptionAnalyzer:{}", analyzer);
|
||||
ANALYZER.add(analyzer);
|
||||
}
|
||||
|
||||
public static boolean analyze(Throwable failure) {
|
||||
Throwable cause = failure;
|
||||
while (cause != null) {
|
||||
for (ExceptionAnalyzer _analyzer : ANALYZER) {
|
||||
if (_analyzer.analyze(cause)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
cause = cause.getCause();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.hswebframework.web.starter.reporter;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.hswebframework.web.exception.analyzer.ExceptionAnalyzers;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.boot.SpringBootExceptionReporter;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
@Slf4j
|
||||
public class GenericExceptionReport implements SpringBootExceptionReporter {
|
||||
|
||||
|
||||
public GenericExceptionReport(ConfigurableApplicationContext context) {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean reportException(Throwable failure) {
|
||||
return ExceptionAnalyzers.analyze(failure);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.SpringBootExceptionReporter=\
|
||||
org.hswebframework.web.starter.reporter.GenericExceptionReport
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.hswebframework.web.starter.reporter;
|
||||
|
||||
import org.hswebframework.web.exception.analyzer.ExceptionAnalyzers;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
|
||||
|
||||
public class GenericExceptionReportTest {
|
||||
|
||||
|
||||
@Test
|
||||
void test(){
|
||||
GenericExceptionReport report = new GenericExceptionReport(
|
||||
new GenericApplicationContext()
|
||||
);
|
||||
|
||||
Assertions.assertTrue(
|
||||
report.reportException(new IndexOutOfBoundsException("Binding index 0 when only 0 parameters are expected "))
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user