diff --git a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/configuration/DialectProviders.java b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/configuration/DialectProviders.java index 71fc92844..894e9c444 100644 --- a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/configuration/DialectProviders.java +++ b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/configuration/DialectProviders.java @@ -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 allSupportedDialect = new HashMap<>(); @@ -19,6 +17,10 @@ public class DialectProviders { } } + public static List all(){ + return new ArrayList<>(allSupportedDialect.values()); + } + @SneakyThrows public static DialectProvider lookup(String dialect) { DialectProvider provider = allSupportedDialect.get(dialect); diff --git a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/configuration/EasyormConfiguration.java b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/configuration/EasyormConfiguration.java index 5be675b1f..43069c441 100644 --- a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/configuration/EasyormConfiguration.java +++ b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/configuration/EasyormConfiguration.java @@ -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; diff --git a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/exception/DatabaseExceptionAnalyzerReporter.java b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/exception/DatabaseExceptionAnalyzerReporter.java new file mode 100644 index 000000000..2aacd127e --- /dev/null +++ b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/exception/DatabaseExceptionAnalyzerReporter.java @@ -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)); + } +} diff --git a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/sql/DefaultR2dbcExecutor.java b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/sql/DefaultR2dbcExecutor.java index 1d0c5180f..f1247026b 100644 --- a/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/sql/DefaultR2dbcExecutor.java +++ b/hsweb-commons/hsweb-commons-crud/src/main/java/org/hswebframework/web/crud/sql/DefaultR2dbcExecutor.java @@ -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 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 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> select(String sql, Object... args) { - return super.select(sql,args); + return super.select(sql, args); } @Override @Transactional(readOnly = true, transactionManager = TransactionManagers.reactiveTransactionManager) public Flux select(String sql, ResultWrapper wrapper) { - return super.select(sql,wrapper); + return super.select(sql, wrapper); } @Override @Transactional(readOnly = true, transactionManager = TransactionManagers.reactiveTransactionManager) public Flux select(SqlRequest sqlRequest, ResultWrapper wrapper) { - return super.select(sqlRequest,wrapper); + return super.select(sqlRequest, wrapper); } } diff --git a/hsweb-commons/hsweb-commons-crud/src/main/resources/META-INF/services/org.hswebframework.web.exception.analyzer.ExceptionAnalyzer b/hsweb-commons/hsweb-commons-crud/src/main/resources/META-INF/services/org.hswebframework.web.exception.analyzer.ExceptionAnalyzer new file mode 100644 index 000000000..bc8746972 --- /dev/null +++ b/hsweb-commons/hsweb-commons-crud/src/main/resources/META-INF/services/org.hswebframework.web.exception.analyzer.ExceptionAnalyzer @@ -0,0 +1 @@ +org.hswebframework.web.crud.exception.DatabaseExceptionAnalyzerReporter \ No newline at end of file diff --git a/hsweb-commons/hsweb-commons-crud/src/main/resources/i18n/commons/messages_en.properties b/hsweb-commons/hsweb-commons-crud/src/main/resources/i18n/commons/messages_en.properties index eeeec4f3e..e8142ce63 100644 --- a/hsweb-commons/hsweb-commons-crud/src/main/resources/i18n/commons/messages_en.properties +++ b/hsweb-commons/hsweb-commons-crud/src/main/resources/i18n/commons/messages_en.properties @@ -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 \ No newline at end of file +error.data.find.not_found=Data not found +error.sql.prepare.failed.IndexOutOfBoundsException=Execute SQL failed, try check config: `easyorm.dialect`. \ No newline at end of file diff --git a/hsweb-commons/hsweb-commons-crud/src/main/resources/i18n/commons/messages_zh.properties b/hsweb-commons/hsweb-commons-crud/src/main/resources/i18n/commons/messages_zh.properties index cfcbf08c2..28b2e85ae 100644 --- a/hsweb-commons/hsweb-commons-crud/src/main/resources/i18n/commons/messages_zh.properties +++ b/hsweb-commons/hsweb-commons-crud/src/main/resources/i18n/commons/messages_zh.properties @@ -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 \ No newline at end of file +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. \ No newline at end of file diff --git a/hsweb-commons/hsweb-commons-crud/src/test/java/org/hswebframework/web/crud/exception/DatabaseExceptionAnalyzerReporterTest.java b/hsweb-commons/hsweb-commons-crud/src/test/java/org/hswebframework/web/crud/exception/DatabaseExceptionAnalyzerReporterTest.java new file mode 100644 index 000000000..cb4ab1f18 --- /dev/null +++ b/hsweb-commons/hsweb-commons-crud/src/test/java/org/hswebframework/web/crud/exception/DatabaseExceptionAnalyzerReporterTest.java @@ -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") + )); + } +} \ No newline at end of file diff --git a/hsweb-core/src/main/java/org/hswebframework/web/exception/analyzer/ExceptionAnalyzer.java b/hsweb-core/src/main/java/org/hswebframework/web/exception/analyzer/ExceptionAnalyzer.java new file mode 100644 index 000000000..f7dcfd60c --- /dev/null +++ b/hsweb-core/src/main/java/org/hswebframework/web/exception/analyzer/ExceptionAnalyzer.java @@ -0,0 +1,27 @@ +package org.hswebframework.web.exception.analyzer; + +/** + * 异常分析器,用于分析异常信息. 实现此接口,并使用SPI进行拓展. + * + *
{@code
+ *
+ *  META-INF/services/org.hswebframework.web.exception.analyzer.ExceptionAnalyzer
+ *
+ * }
+ * + * @author zhouhao + * @since 4.0.18 + * @see ExceptionAnalyzerReporter + */ +public interface ExceptionAnalyzer { + + /** + * 执行分析 + * + * @param error 异常信息 + * @return 是否被处理 + */ + boolean analyze(Throwable error); + + +} diff --git a/hsweb-core/src/main/java/org/hswebframework/web/exception/analyzer/ExceptionAnalyzerReporter.java b/hsweb-core/src/main/java/org/hswebframework/web/exception/analyzer/ExceptionAnalyzerReporter.java new file mode 100644 index 000000000..42602aa41 --- /dev/null +++ b/hsweb-core/src/main/java/org/hswebframework/web/exception/analyzer/ExceptionAnalyzerReporter.java @@ -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 = 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 predicate, + Consumer 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 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); + + } +} diff --git a/hsweb-core/src/main/java/org/hswebframework/web/exception/analyzer/ExceptionAnalyzers.java b/hsweb-core/src/main/java/org/hswebframework/web/exception/analyzer/ExceptionAnalyzers.java new file mode 100644 index 000000000..626b3b753 --- /dev/null +++ b/hsweb-core/src/main/java/org/hswebframework/web/exception/analyzer/ExceptionAnalyzers.java @@ -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 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; + } + +} diff --git a/hsweb-starter/src/main/java/org/hswebframework/web/starter/reporter/GenericExceptionReport.java b/hsweb-starter/src/main/java/org/hswebframework/web/starter/reporter/GenericExceptionReport.java new file mode 100644 index 000000000..c9c34af98 --- /dev/null +++ b/hsweb-starter/src/main/java/org/hswebframework/web/starter/reporter/GenericExceptionReport.java @@ -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); + } + +} diff --git a/hsweb-starter/src/main/resources/META-INF/spring.factories b/hsweb-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..5ad0657ab --- /dev/null +++ b/hsweb-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.SpringBootExceptionReporter=\ +org.hswebframework.web.starter.reporter.GenericExceptionReport \ No newline at end of file diff --git a/hsweb-starter/src/test/java/org/hswebframework/web/starter/reporter/GenericExceptionReportTest.java b/hsweb-starter/src/test/java/org/hswebframework/web/starter/reporter/GenericExceptionReportTest.java new file mode 100644 index 000000000..1776c54e1 --- /dev/null +++ b/hsweb-starter/src/test/java/org/hswebframework/web/starter/reporter/GenericExceptionReportTest.java @@ -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 ")) + ); + + } + +} \ No newline at end of file