From 88fd7b685a34f79500d3717af0a42f86d4b70006 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 25 Oct 2026 12:25:48 +0800 Subject: [PATCH] feat: 完善瀚高可选接入与信创兼容说明 --- README.md | 18 +++++++++++++++++- sql/tools/README.md | 12 ++++++++++-- sql/tools/docker-compose.yaml | 21 +++++++++++++++++++++ sql/tools/highgo/create_schema.sh | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/JdbcUtils.java | 5 ++++- yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessorTest.java | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 223 insertions(+), 4 deletions(-) create mode 100644 sql/tools/highgo/create_schema.sh create mode 100644 yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessorTest.java diff --git a/README.md b/README.md index b74eb83..3770fbb 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ * 管理后台的电脑端:Vue3 提供 `element-plus`、`vben(ant-design-vue)` 两个版本,Vue2 提供 `element-ui` 版本 * 管理后台的移动端:采用 `uni-app` 方案,一份代码多终端适配,同时支持 APP、小程序、H5! * 后端采用 Spring Boot 多模块架构、MySQL + MyBatis Plus、Redis + Redisson -* 数据库可使用 MySQL、Oracle、PostgreSQL、SQL Server、MariaDB、国产达梦 DM、TiDB 等 +* 数据库可使用 MySQL、Oracle、PostgreSQL、SQL Server、MariaDB,以及达梦、人大金仓、瀚高等国产数据库,详见 [信创兼容与数据库支持](#-信创兼容与数据库支持) * 消息队列可使用 Event、Redis、RabbitMQ、Kafka、RocketMQ 等 * 权限认证使用 Spring Security & Token & Redis,支持多终端、多种用户的认证系统,支持 SSO 单点登录 * 支持加载动态权限菜单,按钮级别权限控制,Redis 缓存提升性能 @@ -59,6 +59,22 @@ * 集成阿里云、腾讯云等短信渠道,集成 MinIO、阿里云、腾讯云、七牛云等云存储服务 * 集成报表设计器、大屏设计器,通过拖拽即可生成酷炫的报表与大屏 +## 🐲 信创兼容与数据库支持 + +支持国产操作系统、数据库和中间件,满足信创环境的部署需要: + +| 类别 | 支持 | 说明 | +|----------|-----------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 国产操作系统 | 银河麒麟、统信 UOS 等 Linux 系统 | 参考 [《Linux 部署》](https://doc.iocoder.cn/deployment-linux/),选择与 CPU 架构匹配的 JDK | +| 关系型数据库 | MySQL、MariaDB、Oracle、PostgreSQL、SQL Server | 默认使用 MySQL | +| 国产关系型数据库 | 达梦 DM、人大金仓 KingbaseES、openGauss、瀚高 HighGo、OceanBase、TiDB | 参考 [《国产信创数据库》](https://doc.iocoder.cn/xinchuang-db/) | +| 国产缓存 | 东方通 TongRDS、宝兰德 BES CacheDB | 替换 Redis,参考 [《国产信创数据库》](https://doc.iocoder.cn/xinchuang-db/)。BES CacheDB 提供接入说明,待介质验证 | +| 国产应用服务器 | 东方通 TongWeb、宝兰德 BES AppServer | 替换 Tomcat,参考 [《东方通服务器部署》](https://doc.iocoder.cn/deployment-tong/)、[《宝兰德服务器部署》](https://doc.iocoder.cn/deployment-bes/)。BES AppServer 提供接入说明,待介质验证 | +| 国产 Web 服务器 | 东方通 TongHttpServer | 替换 Nginx,提供静态资源、反向代理,参考 [《东方通服务器部署》](https://doc.iocoder.cn/deployment-tong/) | + +* 数据库脚本:[`sql`](sql/) 目录提供 MySQL、Oracle、PostgreSQL、SQL Server、达梦、人大金仓、openGauss、瀚高、OceanBase 的项目初始化脚本(含 Quartz),优先使用对应目录;MariaDB、TiDB 直接使用 MySQL 脚本 +* 其它数据库、后续表结构变更,可参考 [`sql/tools`](sql/tools/README.md) 的转换工具及 [《表结构变更(版本升级)》](https://doc.iocoder.cn/sql-update/#_3-数据库转换),转换后仍需按实际数据库调整、测试 + ## 🐳 项目关系 ![架构演进](/.image/common/yudao-roadmap.png) diff --git a/sql/tools/README.md b/sql/tools/README.md index b2eb325..02469f7 100644 --- a/sql/tools/README.md +++ b/sql/tools/README.md @@ -109,9 +109,17 @@ docker tag : highgo:local docker compose up -d highgo ``` -> 注意:不同瀚高镜像的数据目录可能不同,如果容器无法启动,请按镜像实际 `PGDATA` 修改 `docker-compose.yaml` 中的 `highgo` 数据卷挂载目录。 +> 注意:`highgo` 服务属于 `highgo` profile,执行 `docker compose up -d` 时不会启动,需要像上面一样指定服务名。不同瀚高镜像的数据目录可能不同,如果容器无法启动,请按镜像实际 `PGDATA` 修改 `docker-compose.yaml` 中的 `highgo` 数据卷挂载目录。 -③ 启动完成后,需要手动导入 Quartz 和项目 SQL。瀚高兼容 PostgreSQL,具体客户端命令以当前镜像为准,可使用 `psql` 或瀚高镜像内置的兼容客户端执行 `/tmp/quartz.sql`、`/tmp/schema.sql`。 +③ 初始化数据库:使用 `postgres:14.2` 镜像中的 `psql` 客户端,创建 `ruoyi-vue-pro` 数据库,并依次导入 `sql/highgo/quartz.sql`、`sql/highgo/ruoyi-vue-pro.sql`: + +```Bash +HIGHGO_PASSWORD='数据库密码' docker compose run --rm highgo-init +``` + +> 注意:默认使用瀚高的 `highgo` 账号连接 `highgo` 数据库执行建库,可以通过 `HIGHGO_USER`、`HIGHGO_MAINTENANCE_DB` 环境变量修改;密码以镜像或安装时的实际设置为准,脚本不会修改数据库的认证配置。初始化只允许创建全新的数据库:数据库已存在(包括空库)或重复执行时,脚本拒绝导入并返回非零状态;导入失败时保留数据库便于排查。该入口只适用于 PostgreSQL 客户端支持的认证方式(例如 md5、scram-sha-256),数据库开启 SM3 等专有认证时,请使用瀚高客户端手动执行上述两个 SQL 文件。 + +④ 项目可以使用 PostgreSQL JDBC 驱动(`jdbc:postgresql://127.0.0.1:5866/ruoyi-vue-pro`),也可以使用瀚高 JDBC 驱动(`jdbc:highgo://127.0.0.1:5866/ruoyi-vue-pro`),两者都按 PostgreSQL 处理序列主键、Quartz 等差异,`url` 需要与 `driver-class-name` 配对。驱动需要引入到 `yudao-server/pom.xml` 的 `` 中:`yudao-spring-boot-starter-mybatis` 中的 `org.postgresql:postgresql` 是 `optional` 依赖,只在 `yudao-dependencies` 中声明版本也不会引入 jar 包。详见 [《国产信创数据库》](https://doc.iocoder.cn/xinchuang-db/#_4-highgo-瀚高数据库) 文档。 ### 1.9 OceanBase diff --git a/sql/tools/docker-compose.yaml b/sql/tools/docker-compose.yaml index bb8bf46..84c83bd 100644 --- a/sql/tools/docker-compose.yaml +++ b/sql/tools/docker-compose.yaml @@ -142,6 +142,7 @@ services: # 使用瀚高官方提供的 Docker 镜像,加载后打成本地标签: # docker tag : highgo:local image: highgo:local + profiles: ["highgo"] restart: unless-stopped ports: - "5866:5866" @@ -150,6 +151,26 @@ services: - ../highgo/quartz.sql:/tmp/quartz.sql:ro - ../highgo/ruoyi-vue-pro.sql:/tmp/schema.sql:ro + # 瀚高初始化:使用 PostgreSQL 客户端创建 ruoyi-vue-pro 数据库并导入 sql/highgo 脚本,只允许创建全新数据库 + # 只支持 PostgreSQL 客户端可用的认证方式(例如 md5、scram-sha-256),SM3 等专有认证请使用瀚高客户端手动导入 + # 执行:HIGHGO_PASSWORD='数据库密码' docker compose run --rm highgo-init + highgo-init: + image: postgres:14.2 + profiles: ["highgo-init"] + restart: "no" + environment: + HIGHGO_HOST: ${HIGHGO_HOST:-highgo} + HIGHGO_PORT: ${HIGHGO_PORT:-5866} + HIGHGO_USER: ${HIGHGO_USER:-highgo} + HIGHGO_PASSWORD: ${HIGHGO_PASSWORD:-} + HIGHGO_MAINTENANCE_DB: ${HIGHGO_MAINTENANCE_DB:-highgo} + HIGHGO_DATABASE: ruoyi-vue-pro + entrypoint: ["bash", "/tmp/create_schema.sh"] + volumes: + - ../highgo/quartz.sql:/tmp/quartz.sql:ro + - ../highgo/ruoyi-vue-pro.sql:/tmp/schema.sql:ro + - ./highgo/create_schema.sh:/tmp/create_schema.sh:ro + oceanbase: image: oceanbase/oceanbase-ce:4.4.2-lts restart: unless-stopped diff --git a/sql/tools/highgo/create_schema.sh b/sql/tools/highgo/create_schema.sh new file mode 100644 index 0000000..d5658e2 --- /dev/null +++ b/sql/tools/highgo/create_schema.sh @@ -0,0 +1,52 @@ +#!/bin/bash +# 初始化瀚高测试库:使用 PostgreSQL 客户端 psql 创建全新的数据库并导入 sql/highgo 脚本,数据库已存在(包括空库)时拒绝导入;任一步骤失败都以非零状态退出 +# 只支持 PostgreSQL 客户端可用的认证方式(例如 md5、scram-sha-256);数据库开启 SM3 等瀚高专有认证时,请使用瀚高客户端手动导入 +set -euo pipefail + +host="${HIGHGO_HOST:-highgo}" +port="${HIGHGO_PORT:-5866}" +user="${HIGHGO_USER:-highgo}" +maintenance_db="${HIGHGO_MAINTENANCE_DB:-highgo}" +database="${HIGHGO_DATABASE:-ruoyi-vue-pro}" +export PGPASSWORD="${HIGHGO_PASSWORD:?HIGHGO_PASSWORD 未设置,请设置为瀚高数据库账号的实际密码}" +export PGCONNECT_TIMEOUT=5 +export PGOPTIONS="-c client_min_messages=warning" + +if [[ ! "${database}" =~ ^[A-Za-z0-9_-]+$ ]]; then + echo "数据库名 ${database} 不合法,只允许字母、数字、下划线和中划线" >&2 + exit 1 +fi + +client=(psql -h "${host}" -p "${port}" -U "${user}" -X -q -v ON_ERROR_STOP=1) + +# 1. 等待瀚高可连接 +for i in $(seq 1 60); do + if pg_isready -h "${host}" -p "${port}" -U "${user}" -d "${maintenance_db}" -q; then + break + fi + if [ "${i}" -eq 60 ]; then + echo "等待瀚高 ${host}:${port} 超时,请执行 docker compose logs highgo 检查启动日志" >&2 + exit 1 + fi + sleep 5 +done + +# 2. 校验账号、密码及认证方式 +if ! "${client[@]}" -d "${maintenance_db}" -c 'SELECT 1' >/dev/null; then + echo "无法使用账号 ${user} 登录 ${maintenance_db} 数据库:请检查 HIGHGO_USER、HIGHGO_PASSWORD、HIGHGO_MAINTENANCE_DB;如果数据库使用 SM3 等专有认证,请使用瀚高客户端手动导入" >&2 + exit 1 +fi + +# 3. 创建全新数据库:不判断是否存在,数据库已存在时直接失败,避免项目 SQL 中的 DROP TABLE 重建已有的同名表 +if ! "${client[@]}" -d "${maintenance_db}" -c "CREATE DATABASE \"${database}\";"; then + echo "创建数据库 ${database} 失败:只允许初始化全新的数据库,数据库已存在(包括空库)时拒绝导入;如需重新初始化,请删除瀚高数据卷后重建" >&2 + exit 1 +fi + +# 4. 按 quartz、ruoyi-vue-pro 的顺序导入脚本(ON_ERROR_STOP 遇到错误立即退出,失败时保留数据库,便于排查) +"${client[@]}" -d "${database}" -f /tmp/quartz.sql +"${client[@]}" -d "${database}" -f /tmp/schema.sql + +# 注意:瀚高的 information_schema.tables 对同名表可能返回多行,这里直接查 pg_class(每张表一行),只统计普通表、分区表 +table_count=$("${client[@]}" -d "${database}" -At -c "SELECT COUNT(*) FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = current_schema() AND c.relkind IN ('r', 'p');") +echo "瀚高初始化完成:数据库 ${database} 共 ${table_count} 张表" diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/JdbcUtils.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/JdbcUtils.java index 0ee22db..8f92428 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/JdbcUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/JdbcUtils.java @@ -38,11 +38,14 @@ public class JdbcUtils { /** * 获得 URL 对应的 DB 类型 * + * 特殊:瀚高 HighGo 原生驱动(jdbc:highgo:)兼容 PostgreSQL,按 PostgreSQL 处理,复用其序列主键、Quartz 等适配 + * * @param url URL * @return DB 类型 */ public static DbType getDbType(String url) { - return com.baomidou.mybatisplus.extension.toolkit.JdbcUtils.getDbType(url); + DbType dbType = com.baomidou.mybatisplus.extension.toolkit.JdbcUtils.getDbType(url); + return dbType == DbType.HIGH_GO ? DbType.POSTGRE_SQL : dbType; } /** diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessorTest.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessorTest.java new file mode 100644 index 0000000..63c73fa --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessorTest.java @@ -0,0 +1,119 @@ +package cn.iocoder.yudao.framework.mybatis.config; + +import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils; +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.incrementer.PostgreKeyGenerator; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.core.env.MapPropertySource; +import org.springframework.core.env.StandardEnvironment; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * {@link IdTypeEnvironmentPostProcessor} 的单元测试 + * + * @author 芋道源码 + */ +public class IdTypeEnvironmentPostProcessorTest { + + private static final String ID_TYPE_KEY = "mybatis-plus.global-config.db-config.id-type"; + private static final String QUARTZ_DELEGATE_KEY = "spring.quartz.properties.org.quartz.jobStore.driverDelegateClass"; + private static final String POSTGRESQL_DELEGATE = "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate"; + + private final IdTypeEnvironmentPostProcessor processor = new IdTypeEnvironmentPostProcessor(); + + @BeforeEach + @AfterEach + public void clearQuartzDelegate() { + // Quartz Delegate 写入的是 JVM 系统属性,需要清理,避免影响其它用例 + System.clearProperty(QUARTZ_DELEGATE_KEY); + } + + @Test + public void testPostProcessEnvironment_highGoNativeUrl() { + // 准备参数 + StandardEnvironment environment = buildEnvironment("jdbc:highgo://127.0.0.1:5866/ruoyi-vue-pro", null); + + // 调用 + processor.postProcessEnvironment(environment, null); + + // 断言:与 PostgreSQL 一致,使用序列主键及 PostgreSQLDelegate + assertEquals("INPUT", environment.getProperty(ID_TYPE_KEY)); + assertEquals(POSTGRESQL_DELEGATE, environment.getProperty(QUARTZ_DELEGATE_KEY)); + assertInstanceOf(PostgreKeyGenerator.class, new YudaoMybatisAutoConfiguration().keyGenerator(environment)); + } + + @Test + public void testPostProcessEnvironment_postgresqlUrl() { + // 准备参数 + StandardEnvironment environment = buildEnvironment("jdbc:postgresql://127.0.0.1:5866/ruoyi-vue-pro", null); + + // 调用 + processor.postProcessEnvironment(environment, null); + + // 断言 + assertEquals("INPUT", environment.getProperty(ID_TYPE_KEY)); + assertEquals(POSTGRESQL_DELEGATE, environment.getProperty(QUARTZ_DELEGATE_KEY)); + assertInstanceOf(PostgreKeyGenerator.class, new YudaoMybatisAutoConfiguration().keyGenerator(environment)); + } + + @Test + public void testPostProcessEnvironment_mysqlUrl() { + // 准备参数 + StandardEnvironment environment = buildEnvironment("jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro", null); + + // 调用 + processor.postProcessEnvironment(environment, null); + + // 断言:MySQL 保持自增主键,且不设置 Quartz Delegate + assertEquals("AUTO", environment.getProperty(ID_TYPE_KEY)); + assertNull(environment.getProperty(QUARTZ_DELEGATE_KEY)); + } + + @Test + public void testPostProcessEnvironment_highGoNativeUrlWithExplicitConfig() { + // 准备参数:用户显式配置主键策略及 Quartz Delegate + Map explicit = new HashMap<>(); + explicit.put(ID_TYPE_KEY, "ASSIGN_ID"); + explicit.put(QUARTZ_DELEGATE_KEY, "org.quartz.impl.jdbcjobstore.StdJDBCDelegate"); + StandardEnvironment environment = buildEnvironment("jdbc:highgo://127.0.0.1:5866/ruoyi-vue-pro", explicit); + + // 调用 + processor.postProcessEnvironment(environment, null); + + // 断言:不覆盖用户配置 + assertEquals("ASSIGN_ID", environment.getProperty(ID_TYPE_KEY)); + assertEquals("org.quartz.impl.jdbcjobstore.StdJDBCDelegate", environment.getProperty(QUARTZ_DELEGATE_KEY)); + assertNull(System.getProperty(QUARTZ_DELEGATE_KEY)); + } + + @Test + public void testGetDbType_onlyNormalizeHighGo() { + assertEquals(DbType.POSTGRE_SQL, JdbcUtils.getDbType("jdbc:highgo://127.0.0.1:5866/ruoyi-vue-pro")); + assertEquals(DbType.POSTGRE_SQL, JdbcUtils.getDbType("jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro")); + assertEquals(DbType.MYSQL, JdbcUtils.getDbType("jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro")); + assertEquals(DbType.KINGBASE_ES, JdbcUtils.getDbType("jdbc:kingbase8://127.0.0.1:54321/test")); + assertEquals(DbType.DM, JdbcUtils.getDbType("jdbc:dm://127.0.0.1:5236?schema=RUOYI_VUE_PRO")); + } + + private static StandardEnvironment buildEnvironment(String url, Map extra) { + Map properties = new HashMap<>(); + properties.put("spring.datasource.dynamic.primary", "master"); + properties.put("spring.datasource.dynamic.datasource.master.url", url); + properties.put(ID_TYPE_KEY, "NONE"); + if (extra != null) { + properties.putAll(extra); + } + StandardEnvironment environment = new StandardEnvironment(); + environment.getPropertySources().addLast(new MapPropertySource("test", properties)); + return environment; + } + +} -- libgit2 0.21.4