From 509e2f1f4d89264d2afd04cccf972ddd50dfca15 Mon Sep 17 00:00:00 2001 From: ruying408 <1877972603@qq.com> Date: Sun, 11 May 2025 21:04:45 +0800 Subject: [PATCH] =?UTF-8?q?upgrade:=201=E3=80=81R=20=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E5=80=BC=E5=8E=BB=E6=8E=89=20dataMap=202=E3=80=81basecontrolle?= =?UTF-8?q?r=20=E6=8F=92=E5=85=A5=E5=92=8C=20=E6=89=B9=E9=87=8F=E6=8F=92?= =?UTF-8?q?=E5=85=A5=E9=83=BD=E4=BD=BF=E7=94=A8=20id=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E5=80=BC=203=E3=80=81=E4=BC=98=E5=8C=96postgres=E6=94=AF?= =?UTF-8?q?=E6=8C=81=20=E4=BF=AE=E6=94=B9=20com.mybatisflex.core.handler?= =?UTF-8?q?=20=E4=B8=BA=20com.cool.core.mybatis.handler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 7 +- .../com/cool/core/base/BaseController.java | 2 +- .../com/cool/core/init/DBFromJsonInit.java | 33 +++- .../mybatis/handler/BaseJsonTypeHandler.java | 48 ++++++ .../mybatis/handler/Fastjson2TypeHandler.java | 73 ++++++++ .../mybatis/handler/JacksonTypeHandler.java | 68 ++++++++ .../pg/PostgresSequenceSyncService.java | 66 ++++++++ src/main/java/com/cool/core/request/R.java | 20 +-- .../com/cool/core/util/AutoTypeConverter.java | 27 +++ .../cool/core/util/DatabaseDialectUtils.java | 17 +- .../base/entity/sys/BaseSysLogEntity.java | 2 +- .../base/entity/sys/BaseSysRoleEntity.java | 2 +- .../sys/impl/BaseSysUserServiceImpl.java | 6 +- .../plugin/entity/PluginInfoEntity.java | 4 +- .../recycle/entity/RecycleDataEntity.java | 2 +- .../cool/modules/task/run/ScheduleJob.java | 3 +- .../pgsql/builder/ColumnSqlBuilder.java | 53 ++++++ .../quartz/QuartzAutoConfiguration.java | 159 ++++++++++++++++++ 18 files changed, 552 insertions(+), 40 deletions(-) create mode 100644 src/main/java/com/cool/core/mybatis/handler/BaseJsonTypeHandler.java create mode 100644 src/main/java/com/cool/core/mybatis/handler/Fastjson2TypeHandler.java create mode 100644 src/main/java/com/cool/core/mybatis/handler/JacksonTypeHandler.java create mode 100644 src/main/java/com/cool/core/mybatis/pg/PostgresSequenceSyncService.java create mode 100644 src/main/java/com/cool/core/util/AutoTypeConverter.java create mode 100644 src/main/java/org/dromara/autotable/core/strategy/pgsql/builder/ColumnSqlBuilder.java create mode 100644 src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java diff --git a/pom.xml b/pom.xml index cc402cf..bb33e1a 100644 --- a/pom.xml +++ b/pom.xml @@ -18,14 +18,14 @@ 17 1.18.34 - 1.10.8 - 1.10.8.122 + 1.10.9 + 1.10.9.125 5.8.26 3.3.2 2.0.51 2.5.0 0.9.16 - 4.6.3.B + 4.7.0 @@ -84,7 +84,6 @@ org.postgresql postgresql - runtime com.zaxxer diff --git a/src/main/java/com/cool/core/base/BaseController.java b/src/main/java/com/cool/core/base/BaseController.java index c16dfd5..32fc3f2 100644 --- a/src/main/java/com/cool/core/base/BaseController.java +++ b/src/main/java/com/cool/core/base/BaseController.java @@ -119,7 +119,7 @@ public abstract class BaseController, T extends BaseEnt if (JSONUtil.isTypeJSONArray(body)) { JSONArray array = JSONUtil.parseArray(body); return R.ok(Dict.create() - .set("ids", service.addBatch(requestParams, array.toList(currentEntityClass())))); + .set("id", service.addBatch(requestParams, array.toList(currentEntityClass())))); } else { return R.ok(Dict.create().set("id", service.add(requestParams, requestParams.toBean(currentEntityClass())))); diff --git a/src/main/java/com/cool/core/init/DBFromJsonInit.java b/src/main/java/com/cool/core/init/DBFromJsonInit.java index 14fc10f..9f01048 100644 --- a/src/main/java/com/cool/core/init/DBFromJsonInit.java +++ b/src/main/java/com/cool/core/init/DBFromJsonInit.java @@ -9,6 +9,8 @@ import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.cool.core.base.service.MapperProviderService; +import com.cool.core.mybatis.pg.PostgresSequenceSyncService; +import com.cool.core.util.DatabaseDialectUtils; import com.cool.core.util.EntityUtils; import com.cool.modules.base.entity.sys.BaseSysConfEntity; import com.cool.modules.base.entity.sys.BaseSysMenuEntity; @@ -49,6 +51,8 @@ public class DBFromJsonInit { final private ApplicationEventPublisher eventPublisher; + final private PostgresSequenceSyncService postgresSequenceSyncService; + @Value("${cool.initData}") private boolean initData; @@ -58,14 +62,24 @@ public class DBFromJsonInit { return; } // 初始化自定义的数据 - extractedDb(); + boolean initFlag = extractedDb(); // 初始化菜单数据 - extractedMenu(); + initFlag = extractedMenu() || initFlag; // 发送数据库初始化完成事件 eventPublisher.publishEvent(new DbInitCompleteEvent(this)); + if (initFlag) { + // 如果是postgresql,同步序列 + syncIdentitySequences(); + } log.info("数据初始化完成!"); } + private void syncIdentitySequences() { + if (DatabaseDialectUtils.isPostgresql()) { + postgresSequenceSyncService.syncIdentitySequences(); + } + } + @Getter public static class DbInitCompleteEvent { private final Object source; @@ -79,21 +93,23 @@ public class DBFromJsonInit { /** * 解析插入业务数据 */ - private void extractedDb() { + private boolean extractedDb() { try { // 加载 JSON 文件 PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resources = resolver.getResources("classpath:cool/data/db/*.json"); // 遍历所有.json文件 - analysisResources(resources); + return analysisResources(resources); } catch (Exception e) { log.error("Failed to initialize data", e); } + return false; } - private void analysisResources(Resource[] resources) + private boolean analysisResources(Resource[] resources) throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { String prefix = "db_"; + boolean isInit = false; for (Resource resource : resources) { File resourceFile = new File(resource.getURL().getFile()); String fileName = prefix + resourceFile.getName(); @@ -112,8 +128,10 @@ public class DBFromJsonInit { baseSysUserEntity.setCValue("success"); // 当前文件已加载 baseSysConfService.add(baseSysUserEntity); + isInit = true; log.info("{} 业务数据初始化成功...", fileName); } + return isInit; } private void analysisJson(JSONObject jsonObject) @@ -158,7 +176,8 @@ public class DBFromJsonInit { /** * 解析插入菜单数据 */ - public void extractedMenu() { + public boolean extractedMenu() { + boolean initFlag = false; try { String prefix = "menu_"; PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); @@ -173,10 +192,12 @@ public class DBFromJsonInit { continue; } analysisResources(resource, fileName); + initFlag = true; } } catch (Exception e) { log.error("Failed to initialize data", e); } + return initFlag; } private void analysisResources(Resource resource, String fileName) throws IOException { diff --git a/src/main/java/com/cool/core/mybatis/handler/BaseJsonTypeHandler.java b/src/main/java/com/cool/core/mybatis/handler/BaseJsonTypeHandler.java new file mode 100644 index 0000000..a403c46 --- /dev/null +++ b/src/main/java/com/cool/core/mybatis/handler/BaseJsonTypeHandler.java @@ -0,0 +1,48 @@ +package com.cool.core.mybatis.handler; + +import com.cool.core.util.DatabaseDialectUtils; +import com.mybatisflex.core.util.StringUtil; +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.postgresql.util.PGobject; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +public abstract class BaseJsonTypeHandler extends BaseTypeHandler { + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { + if (DatabaseDialectUtils.isPostgresql()) { + PGobject jsonObject = new PGobject(); + jsonObject.setType("json"); + jsonObject.setValue(toJson(parameter)); + ps.setObject(i, jsonObject); + } else { + ps.setString(i, toJson(parameter)); + } + } + + @Override + public T getNullableResult(ResultSet rs, String columnName) throws SQLException { + final String json = rs.getString(columnName); + return StringUtil.noText(json) ? null : parseJson(json); + } + + @Override + public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException { + final String json = rs.getString(columnIndex); + return StringUtil.noText(json) ? null : parseJson(json); + } + + @Override + public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { + final String json = cs.getString(columnIndex); + return StringUtil.noText(json) ? null : parseJson(json); + } + + protected abstract T parseJson(String json); + + protected abstract String toJson(T object); + +} diff --git a/src/main/java/com/cool/core/mybatis/handler/Fastjson2TypeHandler.java b/src/main/java/com/cool/core/mybatis/handler/Fastjson2TypeHandler.java new file mode 100644 index 0000000..aad475e --- /dev/null +++ b/src/main/java/com/cool/core/mybatis/handler/Fastjson2TypeHandler.java @@ -0,0 +1,73 @@ +package com.cool.core.mybatis.handler; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.JSONWriter; +import com.alibaba.fastjson2.TypeReference; + +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collection; + +public class Fastjson2TypeHandler extends BaseJsonTypeHandler { + + private final Class propertyType; + private Class genericType; + private Type type; + + private boolean supportAutoType = false; + + public Fastjson2TypeHandler(Class propertyType) { + this.propertyType = propertyType; + this.supportAutoType = propertyType.isInterface() || Modifier.isAbstract(propertyType.getModifiers()); + } + + + public Fastjson2TypeHandler(Class propertyType, Class genericType) { + this.propertyType = propertyType; + this.genericType = genericType; + this.type = TypeReference.collectionType((Class) propertyType, genericType); + + Type actualTypeArgument = ((ParameterizedType) type).getActualTypeArguments()[0]; + if (actualTypeArgument instanceof Class) { + this.supportAutoType = ((Class) actualTypeArgument).isInterface() + || Modifier.isAbstract(((Class) actualTypeArgument).getModifiers()); + } + } + + @Override + protected Object parseJson(String json) { + if (genericType != null && Collection.class.isAssignableFrom(propertyType)) { + if (supportAutoType) { + return JSON.parseArray(json, Object.class, JSONReader.Feature.SupportAutoType); + } else { + return JSON.parseObject(json, type); + } + + } else { + if (supportAutoType) { + return JSON.parseObject(json, Object.class, JSONReader.Feature.SupportAutoType); + } else { + return JSON.parseObject(json, propertyType); + } + } + } + + @Override + protected String toJson(Object object) { + if (supportAutoType) { + return JSON.toJSONString(object + , JSONWriter.Feature.WriteMapNullValue + , JSONWriter.Feature.WriteNullListAsEmpty + , JSONWriter.Feature.WriteNullStringAsEmpty, JSONWriter.Feature.WriteClassName + ); + } else { + return JSON.toJSONString(object + , JSONWriter.Feature.WriteMapNullValue + , JSONWriter.Feature.WriteNullListAsEmpty + , JSONWriter.Feature.WriteNullStringAsEmpty + ); + } + } +} diff --git a/src/main/java/com/cool/core/mybatis/handler/JacksonTypeHandler.java b/src/main/java/com/cool/core/mybatis/handler/JacksonTypeHandler.java new file mode 100644 index 0000000..e92d096 --- /dev/null +++ b/src/main/java/com/cool/core/mybatis/handler/JacksonTypeHandler.java @@ -0,0 +1,68 @@ +package com.cool.core.mybatis.handler; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.mybatisflex.core.exception.FlexExceptions; + +import java.io.IOException; +import java.util.Collection; + +public class JacksonTypeHandler extends BaseJsonTypeHandler { + + private static ObjectMapper objectMapper; + private final Class propertyType; + private Class genericType; + private JavaType javaType; + + public JacksonTypeHandler(Class propertyType) { + this.propertyType = propertyType; + } + + public JacksonTypeHandler(Class propertyType, Class genericType) { + this.propertyType = propertyType; + this.genericType = genericType; + } + + @Override + protected Object parseJson(String json) { + try { + if (genericType != null && Collection.class.isAssignableFrom(propertyType)) { + return getObjectMapper().readValue(json, getJavaType()); + } else { + return getObjectMapper().readValue(json, propertyType); + } + } catch (IOException e) { + throw FlexExceptions.wrap(e, "Can not parseJson by JacksonTypeHandler: " + json); + } + } + + @Override + protected String toJson(Object object) { + try { + return getObjectMapper().writeValueAsString(object); + } catch (JsonProcessingException e) { + throw FlexExceptions.wrap(e, "Can not convert object to Json by JacksonTypeHandler: " + object); + } + } + + + public JavaType getJavaType() { + if (javaType == null){ + javaType = getObjectMapper().getTypeFactory().constructCollectionType((Class) propertyType, genericType); + } + return javaType; + } + + public static ObjectMapper getObjectMapper() { + if (null == objectMapper) { + objectMapper = new ObjectMapper(); + } + return objectMapper; + } + + public static void setObjectMapper(ObjectMapper objectMapper) { + JacksonTypeHandler.objectMapper = objectMapper; + } + +} \ No newline at end of file diff --git a/src/main/java/com/cool/core/mybatis/pg/PostgresSequenceSyncService.java b/src/main/java/com/cool/core/mybatis/pg/PostgresSequenceSyncService.java new file mode 100644 index 0000000..d54b34c --- /dev/null +++ b/src/main/java/com/cool/core/mybatis/pg/PostgresSequenceSyncService.java @@ -0,0 +1,66 @@ +package com.cool.core.mybatis.pg; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.jdbc.core.JdbcTemplate; + +import java.util.List; +import java.util.Map; +/** + * PostgreSQL Identity 序列同步服务 + * 解决PostgreSQL 默认的序列机制,序列会自动递增,当手动插入指定id时需调用同步接口,否则id会重复。 + */ +@Slf4j +@Service +public class PostgresSequenceSyncService { + + private final JdbcTemplate jdbcTemplate; + + public PostgresSequenceSyncService(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public void syncIdentitySequences() { + log.info("⏳ 开始同步 PostgreSQL Identity 序列..."); + + // 查询所有 identity 字段 + String identityColumnQuery = """ + SELECT table_schema, table_name, column_name + FROM information_schema.columns + WHERE is_identity = 'YES' + AND table_schema = 'public' + """; + + List> identityColumns = jdbcTemplate.queryForList(identityColumnQuery); + + for (Map col : identityColumns) { + String schema = (String) col.get("table_schema"); + String table = (String) col.get("table_name"); + String column = (String) col.get("column_name"); + + String fullTable = schema + "." + table; + + // 获取对应的序列名 + String seqNameSql = "SELECT pg_get_serial_sequence(?, ?)"; + String seqName = jdbcTemplate.queryForObject(seqNameSql, String.class, fullTable, column); + + if (seqName == null) { + log.warn("⚠️ 无法获取序列:{}.{}", table, column); + continue; + } + + // 获取当前最大 ID + Long maxId = jdbcTemplate.queryForObject( + String.format("SELECT COALESCE(MAX(%s), 0) FROM %s", column, fullTable), + Long.class + ); + + if (maxId != null && maxId > 0) { // 正确的:setval 有返回值,必须用 queryForObject + String setvalSql = "SELECT setval(?, ?)"; + Long newVal = jdbcTemplate.queryForObject(setvalSql, Long.class, seqName, maxId); + log.info("✅ 同步序列 [{}] -> 当前最大 ID: {}", seqName, newVal); + } + } + + log.info("✅ PostgreSQL Identity 序列同步完成。"); + } +} \ No newline at end of file diff --git a/src/main/java/com/cool/core/request/R.java b/src/main/java/com/cool/core/request/R.java index 35ecd2a..c152ff9 100644 --- a/src/main/java/com/cool/core/request/R.java +++ b/src/main/java/com/cool/core/request/R.java @@ -4,8 +4,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; /** * 返回信息 @@ -24,11 +22,7 @@ public class R implements Serializable { @Schema(title = "响应数据") private T data; - - @Schema(title = "响应数据") - private Map dataMap = new HashMap(); - - + public R() { } @@ -70,14 +64,10 @@ public class R implements Serializable { public R put(String key, Object value) { - if ( key.equals( "code") ) { - this.code = (int)value; - } else if ( key.equals( "message") ) { - this.message = (String)value; - } else if ( key.equals( "data") ) { - this.data = (T) value; - } else { - dataMap.put(key, value); + switch (key) { + case "code" -> this.code = (int) value; + case "message" -> this.message = (String) value; + case "data" -> this.data = (T) value; } return this; } diff --git a/src/main/java/com/cool/core/util/AutoTypeConverter.java b/src/main/java/com/cool/core/util/AutoTypeConverter.java new file mode 100644 index 0000000..45ed11c --- /dev/null +++ b/src/main/java/com/cool/core/util/AutoTypeConverter.java @@ -0,0 +1,27 @@ +package com.cool.core.util; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.util.NumberUtil; + +import java.io.Serializable; + +public class AutoTypeConverter { + /** + * 将字符串自动转换为数字或保留为字符串 + * + * @param input 输入字符串 + * @return Integer / Long / String + */ + public static Serializable autoConvert(Object input) { + if (input == null) { + return null; + } + if (NumberUtil.isInteger(input.toString())) { + return Convert.convert(Integer.class, input); + } else if (NumberUtil.isLong(input.toString())) { + return Convert.convert(Long.class, input); + } else { + return (Serializable) input; + } + } +} diff --git a/src/main/java/com/cool/core/util/DatabaseDialectUtils.java b/src/main/java/com/cool/core/util/DatabaseDialectUtils.java index 6cdeb80..f4f2554 100644 --- a/src/main/java/com/cool/core/util/DatabaseDialectUtils.java +++ b/src/main/java/com/cool/core/util/DatabaseDialectUtils.java @@ -12,16 +12,25 @@ import javax.sql.DataSource; public class DatabaseDialectUtils { private static String dialect; - public static String getDatabaseDialect() { + public static String getDatabaseDialect(DataSource dataSource) { if (dialect == null) { - dialect = determineDatabaseType(); + dialect = determineDatabaseType(dataSource); } return dialect; } - private static String determineDatabaseType() { - // 从 DataSource 获取连接 + public static boolean isPostgresql() { DataSource dataSource = SpringContextUtils.getBean(DataSource.class); + return DatabaseDialect.PostgreSQL.equals(getDatabaseDialect(dataSource)); + } + + public static boolean isPostgresql(DataSource dataSource) { + return DatabaseDialect.PostgreSQL.equals(getDatabaseDialect(dataSource)); + } + + + private static String determineDatabaseType(DataSource dataSource) { + // 从 DataSource 获取连接 try (Connection connection = dataSource.getConnection()) { // 获取元数据 DatabaseMetaData metaData = connection.getMetaData(); diff --git a/src/main/java/com/cool/modules/base/entity/sys/BaseSysLogEntity.java b/src/main/java/com/cool/modules/base/entity/sys/BaseSysLogEntity.java index 9c9dc18..450960c 100644 --- a/src/main/java/com/cool/modules/base/entity/sys/BaseSysLogEntity.java +++ b/src/main/java/com/cool/modules/base/entity/sys/BaseSysLogEntity.java @@ -3,7 +3,7 @@ package com.cool.modules.base.entity.sys; import com.cool.core.base.BaseEntity; import com.mybatisflex.annotation.Column; import com.mybatisflex.annotation.Table; -import com.mybatisflex.core.handler.Fastjson2TypeHandler; +import com.cool.core.mybatis.handler.Fastjson2TypeHandler; import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine; import lombok.Getter; import lombok.Setter; diff --git a/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleEntity.java b/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleEntity.java index 5119e90..8102e2d 100644 --- a/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleEntity.java +++ b/src/main/java/com/cool/modules/base/entity/sys/BaseSysRoleEntity.java @@ -3,7 +3,7 @@ package com.cool.modules.base.entity.sys; import com.cool.core.base.BaseEntity; import com.mybatisflex.annotation.Column; import com.mybatisflex.annotation.Table; -import com.mybatisflex.core.handler.Fastjson2TypeHandler; +import com.cool.core.mybatis.handler.Fastjson2TypeHandler; import com.tangzc.mybatisflex.autotable.annotation.ColumnDefine; import java.util.List; import lombok.Getter; diff --git a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysUserServiceImpl.java b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysUserServiceImpl.java index e333529..7ba615c 100644 --- a/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysUserServiceImpl.java +++ b/src/main/java/com/cool/modules/base/service/sys/impl/BaseSysUserServiceImpl.java @@ -27,7 +27,6 @@ import com.mybatisflex.core.paginate.Page; import com.mybatisflex.core.query.QueryWrapper; import com.mybatisflex.core.update.UpdateChain; import lombok.RequiredArgsConstructor; -import org.dromara.autotable.core.constants.DatabaseDialect; import org.springframework.stereotype.Service; import java.util.List; @@ -55,8 +54,7 @@ public class BaseSysUserServiceImpl extends BaseServiceImpl columnMetadata.getType().getDefaultFullType()); + + // 如果是自增列,则使用GENERATED ALWAYS AS IDENTITY, 忽略not null和默认值的配置 + if (columnMetadata.isAutoIncrement()) { + return sql.replace("{null} {default}", "GENERATED BY DEFAULT AS IDENTITY").toString(); + } + + return sql.replace("{null}", columnMetadata.isNotNull() ? "NOT NULL" : "") + .replace("{default}", () -> { + // 指定NULL + DefaultValueEnum defaultValueType = columnMetadata.getDefaultValueType(); + if (defaultValueType == DefaultValueEnum.NULL) { + return "DEFAULT NULL"; + } + // 指定空字符串 + if (defaultValueType == DefaultValueEnum.EMPTY_STRING) { + return "DEFAULT ''"; + } + // 自定义 + String defaultValue = columnMetadata.getDefaultValue(); + if (DefaultValueEnum.isCustom(defaultValueType) && StringUtils.hasText(defaultValue)) { + return "DEFAULT " + defaultValue; + } + return ""; + }) + .toString(); + } +} diff --git a/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java b/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java new file mode 100644 index 0000000..2d01e23 --- /dev/null +++ b/src/main/java/org/springframework/boot/autoconfigure/quartz/QuartzAutoConfiguration.java @@ -0,0 +1,159 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.quartz; + +import java.util.Map; +import java.util.Properties; + +import javax.sql.DataSource; + +import com.cool.core.util.DatabaseDialectUtils; +import org.quartz.Calendar; +import org.quartz.JobDetail; +import org.quartz.Scheduler; +import org.quartz.Trigger; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.boot.autoconfigure.sql.init.OnDatabaseInitializationCondition; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.core.annotation.Order; +import org.springframework.scheduling.quartz.SchedulerFactoryBean; +import org.springframework.scheduling.quartz.SpringBeanJobFactory; +import org.springframework.transaction.PlatformTransactionManager; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Quartz Scheduler. + * 覆盖原始的QuartzAutoConfiguration,兼容postgres数据库 + * properties.getProperties().put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate"); + * @author Vedran Pavic + * @author Stephane Nicoll + * @since 2.0.0 + */ +@AutoConfiguration(after = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class, + LiquibaseAutoConfiguration.class, FlywayAutoConfiguration.class }) +@ConditionalOnClass({ Scheduler.class, SchedulerFactoryBean.class, PlatformTransactionManager.class }) +@EnableConfigurationProperties(QuartzProperties.class) +public class QuartzAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public SchedulerFactoryBean quartzScheduler(QuartzProperties properties, + ObjectProvider customizers, ObjectProvider jobDetails, + Map calendars, ObjectProvider triggers, ApplicationContext applicationContext) { + SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); + SpringBeanJobFactory jobFactory = new SpringBeanJobFactory(); + jobFactory.setApplicationContext(applicationContext); + schedulerFactoryBean.setJobFactory(jobFactory); + if (properties.getSchedulerName() != null) { + schedulerFactoryBean.setSchedulerName(properties.getSchedulerName()); + } + schedulerFactoryBean.setAutoStartup(properties.isAutoStartup()); + schedulerFactoryBean.setStartupDelay((int) properties.getStartupDelay().getSeconds()); + schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(properties.isWaitForJobsToCompleteOnShutdown()); + schedulerFactoryBean.setOverwriteExistingJobs(properties.isOverwriteExistingJobs()); + if (!properties.getProperties().isEmpty()) { + schedulerFactoryBean.setQuartzProperties(asProperties(properties.getProperties())); + } + schedulerFactoryBean.setJobDetails(jobDetails.orderedStream().toArray(JobDetail[]::new)); + schedulerFactoryBean.setCalendars(calendars); + schedulerFactoryBean.setTriggers(triggers.orderedStream().toArray(Trigger[]::new)); + customizers.orderedStream().forEach((customizer) -> customizer.customize(schedulerFactoryBean)); + return schedulerFactoryBean; + } + + private Properties asProperties(Map source) { + Properties properties = new Properties(); + properties.putAll(source); + return properties; + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnSingleCandidate(DataSource.class) + @ConditionalOnProperty(prefix = "spring.quartz", name = "job-store-type", havingValue = "jdbc") + @Import(DatabaseInitializationDependencyConfigurer.class) + protected static class JdbcStoreTypeConfiguration { + + @Bean + @Order(0) + public SchedulerFactoryBeanCustomizer dataSourceCustomizer(QuartzProperties properties, DataSource dataSource, + @QuartzDataSource ObjectProvider quartzDataSource, + ObjectProvider transactionManager, + @QuartzTransactionManager ObjectProvider quartzTransactionManager) { + return (schedulerFactoryBean) -> { + DataSource dataSourceToUse = getDataSource(dataSource, quartzDataSource); + schedulerFactoryBean.setDataSource(dataSourceToUse); + PlatformTransactionManager txManager = getTransactionManager(transactionManager, + quartzTransactionManager); + if (txManager != null) { + schedulerFactoryBean.setTransactionManager(txManager); + } + }; + } + + private DataSource getDataSource(DataSource dataSource, ObjectProvider quartzDataSource) { + DataSource dataSourceIfAvailable = quartzDataSource.getIfAvailable(); + return (dataSourceIfAvailable != null) ? dataSourceIfAvailable : dataSource; + } + + private PlatformTransactionManager getTransactionManager( + ObjectProvider transactionManager, + ObjectProvider quartzTransactionManager) { + PlatformTransactionManager transactionManagerIfAvailable = quartzTransactionManager.getIfAvailable(); + return (transactionManagerIfAvailable != null) ? transactionManagerIfAvailable + : transactionManager.getIfUnique(); + } + + @Bean + @ConditionalOnMissingBean(QuartzDataSourceScriptDatabaseInitializer.class) + @Conditional(OnQuartzDatasourceInitializationCondition.class) + public QuartzDataSourceScriptDatabaseInitializer quartzDataSourceScriptDatabaseInitializer( + DataSource dataSource, @QuartzDataSource ObjectProvider quartzDataSource, + QuartzProperties properties) { + DataSource dataSourceToUse = getDataSource(dataSource, quartzDataSource); + if (DatabaseDialectUtils.isPostgresql(dataSource)) { + properties.getProperties().put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate"); + } + return new QuartzDataSourceScriptDatabaseInitializer(dataSourceToUse, properties); + } + + static class OnQuartzDatasourceInitializationCondition extends OnDatabaseInitializationCondition { + + OnQuartzDatasourceInitializationCondition() { + super("Quartz", "spring.quartz.jdbc.initialize-schema"); + } + + } + + } + +}