Commit 8c5e70484476f809ebdd0363254653312db0e920
1 parent
88fd7b68
feat: 完善 GaussDB、MogDB 等国产数据库可选接入
Showing
12 changed files
with
509 additions
and
8 deletions
README.md
| @@ -67,12 +67,12 @@ | @@ -67,12 +67,12 @@ | ||
| 67 | |----------|-----------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| | 67 | |----------|-----------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| |
| 68 | | 国产操作系统 | 银河麒麟、统信 UOS 等 Linux 系统 | 参考 [《Linux 部署》](https://doc.iocoder.cn/deployment-linux/),选择与 CPU 架构匹配的 JDK | | 68 | | 国产操作系统 | 银河麒麟、统信 UOS 等 Linux 系统 | 参考 [《Linux 部署》](https://doc.iocoder.cn/deployment-linux/),选择与 CPU 架构匹配的 JDK | |
| 69 | | 关系型数据库 | MySQL、MariaDB、Oracle、PostgreSQL、SQL Server | 默认使用 MySQL | | 69 | | 关系型数据库 | MySQL、MariaDB、Oracle、PostgreSQL、SQL Server | 默认使用 MySQL | |
| 70 | -| 国产关系型数据库 | 达梦 DM、人大金仓 KingbaseES、openGauss、瀚高 HighGo、OceanBase、TiDB | 参考 [《国产信创数据库》](https://doc.iocoder.cn/xinchuang-db/) | | 70 | +| 国产关系型数据库 | 达梦 DM、人大金仓 KingbaseES、openGauss、瀚高 HighGo、OceanBase、TiDB、华为 GaussDB、云和恩墨 MogDB、海量 Vastbase G100、PolarDB PostgreSQL 版 | 参考 [《国产信创数据库》](https://doc.iocoder.cn/xinchuang-db/)。GaussDB、MogDB、Vastbase G100 使用 PG 兼容模式;神通数据库提供实测差异说明,待专门适配 | |
| 71 | | 国产缓存 | 东方通 TongRDS、宝兰德 BES CacheDB | 替换 Redis,参考 [《国产信创数据库》](https://doc.iocoder.cn/xinchuang-db/)。BES CacheDB 提供接入说明,待介质验证 | | 71 | | 国产缓存 | 东方通 TongRDS、宝兰德 BES CacheDB | 替换 Redis,参考 [《国产信创数据库》](https://doc.iocoder.cn/xinchuang-db/)。BES CacheDB 提供接入说明,待介质验证 | |
| 72 | | 国产应用服务器 | 东方通 TongWeb、宝兰德 BES AppServer | 替换 Tomcat,参考 [《东方通服务器部署》](https://doc.iocoder.cn/deployment-tong/)、[《宝兰德服务器部署》](https://doc.iocoder.cn/deployment-bes/)。BES AppServer 提供接入说明,待介质验证 | | 72 | | 国产应用服务器 | 东方通 TongWeb、宝兰德 BES AppServer | 替换 Tomcat,参考 [《东方通服务器部署》](https://doc.iocoder.cn/deployment-tong/)、[《宝兰德服务器部署》](https://doc.iocoder.cn/deployment-bes/)。BES AppServer 提供接入说明,待介质验证 | |
| 73 | | 国产 Web 服务器 | 东方通 TongHttpServer | 替换 Nginx,提供静态资源、反向代理,参考 [《东方通服务器部署》](https://doc.iocoder.cn/deployment-tong/) | | 73 | | 国产 Web 服务器 | 东方通 TongHttpServer | 替换 Nginx,提供静态资源、反向代理,参考 [《东方通服务器部署》](https://doc.iocoder.cn/deployment-tong/) | |
| 74 | 74 | ||
| 75 | -* 数据库脚本:[`sql`](sql/) 目录提供 MySQL、Oracle、PostgreSQL、SQL Server、达梦、人大金仓、openGauss、瀚高、OceanBase 的项目初始化脚本(含 Quartz),优先使用对应目录;MariaDB、TiDB 直接使用 MySQL 脚本 | 75 | +* 数据库脚本:[`sql`](sql/) 目录提供 MySQL、Oracle、PostgreSQL、SQL Server、达梦、人大金仓、openGauss、瀚高、OceanBase 的项目初始化脚本(含 Quartz),优先使用对应目录;MariaDB、TiDB 直接使用 MySQL 脚本;GaussDB、MogDB、Vastbase G100 基于 openGauss 脚本导入,PolarDB PostgreSQL 版直接使用 PostgreSQL 脚本 |
| 76 | * 其它数据库、后续表结构变更,可参考 [`sql/tools`](sql/tools/README.md) 的转换工具及 [《表结构变更(版本升级)》](https://doc.iocoder.cn/sql-update/#_3-数据库转换),转换后仍需按实际数据库调整、测试 | 76 | * 其它数据库、后续表结构变更,可参考 [`sql/tools`](sql/tools/README.md) 的转换工具及 [《表结构变更(版本升级)》](https://doc.iocoder.cn/sql-update/#_3-数据库转换),转换后仍需按实际数据库调整、测试 |
| 77 | 77 | ||
| 78 | ## 🐳 项目关系 | 78 | ## 🐳 项目关系 |
sql/tools/README.md
| @@ -6,7 +6,7 @@ | @@ -6,7 +6,7 @@ | ||
| 6 | 6 | ||
| 7 | ## 1. 测试数据库的快速启动 | 7 | ## 1. 测试数据库的快速启动 |
| 8 | 8 | ||
| 9 | -基于 Docker Compose,快速启动 MySQL、Oracle、PostgreSQL、SQL Server、OceanBase、TiDB 等数据库。 | 9 | +基于 Docker Compose,快速启动 MySQL、Oracle、PostgreSQL、SQL Server、OceanBase、TiDB、MogDB 等数据库。 |
| 10 | 10 | ||
| 11 | 注意!使用 Docker Compose 启动完测试数据后,因为会自动导入项目的 SQL 脚本,所以可能需要等待 1-2 分钟。 | 11 | 注意!使用 Docker Compose 启动完测试数据后,因为会自动导入项目的 SQL 脚本,所以可能需要等待 1-2 分钟。 |
| 12 | 12 | ||
| @@ -175,6 +175,50 @@ password: 123456 | @@ -175,6 +175,50 @@ password: 123456 | ||
| 175 | 175 | ||
| 176 | > 注意:端口只绑定本机 `127.0.0.1:4000`。数据保存在 `tidb_pd`、`tidb_tikv` 数据卷中,重启不需要重新导入 SQL。如需重建,执行 `docker compose down tidb tidb-tikv tidb-pd` 后,删除 `ruoyi-vue-pro_tidb_pd`、`ruoyi-vue-pro_tidb_tikv` 数据卷。该环境仅用于开发测试,生产部署见 [TiDB 官方文档](https://docs.pingcap.com/zh/tidb/v8.5/production-deployment-using-tiup/)。 | 176 | > 注意:端口只绑定本机 `127.0.0.1:4000`。数据保存在 `tidb_pd`、`tidb_tikv` 数据卷中,重启不需要重新导入 SQL。如需重建,执行 `docker compose down tidb tidb-tikv tidb-pd` 后,删除 `ruoyi-vue-pro_tidb_pd`、`ruoyi-vue-pro_tidb_tikv` 数据卷。该环境仅用于开发测试,生产部署见 [TiDB 官方文档](https://docs.pingcap.com/zh/tidb/v8.5/production-deployment-using-tiup/)。 |
| 177 | 177 | ||
| 178 | +### 1.11 华为 GaussDB | ||
| 179 | + | ||
| 180 | +GaussDB 没有官方 Docker 镜像,需要自行安装,例如华为云官网的 [GaussDB 混合云开发者版](https://www.huaweicloud.com/intl/zh-cn/product/gaussdb/download.html)(官方声明仅供个人学习、适配验证或测试,不得用于商业用途)。项目复用 `sql/opengauss` 脚本,导入到 PG 兼容模式的数据库中。 | ||
| 181 | + | ||
| 182 | +① 初始化数据库:在安装了 GaussDB `gsql` 客户端的机器上(例如以数据库安装用户登录 GaussDB 服务器,并复制项目的 `sql` 目录),使用具有 CREATEDB 权限的账号执行: | ||
| 183 | + | ||
| 184 | +```Bash | ||
| 185 | +GAUSSDB_PORT='数据库端口' GAUSSDB_USER='数据库账号' GAUSSDB_PASSWORD='数据库密码' bash gaussdb/create_schema.sh | ||
| 186 | +``` | ||
| 187 | + | ||
| 188 | +> 注意:脚本创建 PG 兼容模式(`DBCOMPATIBILITY = 'PG'`)、UTF8 编码的 `ruoyi-vue-pro` 数据库,依次导入 `sql/opengauss/quartz.sql`、`sql/opengauss/ruoyi-vue-pro.sql`。GaussDB 默认创建的是 A(Oracle)兼容模式的数据库,项目脚本需要使用 PG 兼容模式。GaussDB 已内置 `dual` 视图,原脚本开头的 `DROP TABLE IF EXISTS dual` 会报 `"dual" is not a table` 错误,所以脚本只跳过创建 `dual` 表的一段,不修改内置视图,其余 SQL 保持不变;该段格式与预期不一致,或者未检测到内置 `dual` 视图时,拒绝导入。初始化只允许创建全新的数据库:数据库已存在(包括空库)或重复执行时,脚本拒绝导入并返回非零状态;导入失败时保留数据库便于排查。 | ||
| 189 | + | ||
| 190 | +> 注意:`GAUSSDB_HOST` 为空时,使用本机 Unix Socket 连接;远程连接时,需要在 `gs_hba.conf`(不是 `pg_hba.conf`)中为该账号、客户端地址配置密码认证(例如 sha256),不要使用 trust。GaussDB 的初始用户不允许远程连接,项目请使用单独创建的账号。 | ||
| 191 | + | ||
| 192 | +② 项目使用 GaussDB 原生 JDBC 驱动(`com.huawei.gaussdb.jdbc.Driver`,`jdbc:gaussdb://127.0.0.1:端口/ruoyi-vue-pro`),按 PostgreSQL 处理序列主键、Quartz、工作流等差异,驱动需要引入到 `yudao-server/pom.xml` 的 `<dependencies>` 中。详见 [《国产信创数据库》](https://doc.iocoder.cn/xinchuang-db/#_7-华为-gaussdb-数据库) 文档。 | ||
| 193 | + | ||
| 194 | +### 1.12 MogDB | ||
| 195 | + | ||
| 196 | +使用云和恩墨官方 MogDB 5.0.0 容器镜像(官方说明仅用于测试),支持 x86_64 和 MacBook Apple Silicon。`mogdb` 服务属于 `mogdb` profile,执行 `docker compose up -d` 时不会启动。 | ||
| 197 | + | ||
| 198 | +① 启动数据库: | ||
| 199 | + | ||
| 200 | +```Bash | ||
| 201 | +docker compose up -d mogdb | ||
| 202 | +``` | ||
| 203 | + | ||
| 204 | +② 初始化数据库:创建 PG 兼容模式(`DBCOMPATIBILITY = 'PG'`)、UTF8 编码的 `ruoyi-vue-pro` 数据库,所有者为 `mogdb` 用户,并依次导入 `sql/opengauss/quartz.sql`、`sql/opengauss/ruoyi-vue-pro.sql`: | ||
| 205 | + | ||
| 206 | +```Bash | ||
| 207 | +docker compose exec -u omm mogdb bash /tmp/create_schema.sh | ||
| 208 | +``` | ||
| 209 | + | ||
| 210 | +> 注意:MogDB 默认创建的是 A(Oracle)兼容模式的数据库,`varchar(n)` 按字节计算长度,导入项目脚本时会在中文数据处报 `value too long for type character varying(32)` 错误,所以需要使用 PG 兼容模式。初始化只允许创建全新的数据库:数据库已存在(包括空库)或重复执行时,脚本拒绝导入并返回非零状态;导入失败时保留数据库便于排查,重新初始化需要执行 `docker compose down mogdb` 后,删除 `ruoyi-vue-pro_mogdb` 数据卷。 | ||
| 211 | + | ||
| 212 | +③ 项目使用 openGauss JDBC 驱动(`org.opengauss.Driver`),将数据源配置中的地址、账号和密码修改为: | ||
| 213 | + | ||
| 214 | +```yaml | ||
| 215 | +url: jdbc:opengauss://127.0.0.1:15432/ruoyi-vue-pro | ||
| 216 | +username: mogdb | ||
| 217 | +password: Yudao@2024 | ||
| 218 | +``` | ||
| 219 | + | ||
| 220 | +> 注意:端口只绑定本机 `127.0.0.1:15432`;`omm` 初始用户不允许远程连接,项目使用 `mogdb` 用户。`jdbc:opengauss:` 按 PostgreSQL 处理序列主键、Quartz 等差异,驱动需要引入到 `yudao-server/pom.xml` 的 `<dependencies>` 中。详见 [《国产信创数据库》](https://doc.iocoder.cn/xinchuang-db/#_11-云和恩墨-mogdb-数据库) 文档。 | ||
| 221 | + | ||
| 178 | ## 1.X 容器的销毁重建 | 222 | ## 1.X 容器的销毁重建 |
| 179 | 223 | ||
| 180 | 开发测试过程中,有时候需要创建全新干净的数据库。由于测试数据 Docker 容器采用数据卷 Volume 挂载数据库实例的数据目录,因此销毁数据需要停止容器后,删除数据卷,然后再重新创建容器。 | 224 | 开发测试过程中,有时候需要创建全新干净的数据库。由于测试数据 Docker 容器采用数据卷 Volume 挂载数据库实例的数据目录,因此销毁数据需要停止容器后,删除数据卷,然后再重新创建容器。 |
sql/tools/docker-compose.yaml
| @@ -12,6 +12,7 @@ volumes: | @@ -12,6 +12,7 @@ volumes: | ||
| 12 | oceanbase_config: { } | 12 | oceanbase_config: { } |
| 13 | tidb_pd: { } | 13 | tidb_pd: { } |
| 14 | tidb_tikv: { } | 14 | tidb_tikv: { } |
| 15 | + mogdb: { } | ||
| 15 | 16 | ||
| 16 | services: | 17 | services: |
| 17 | mysql: | 18 | mysql: |
| @@ -272,3 +273,23 @@ services: | @@ -272,3 +273,23 @@ services: | ||
| 272 | - ../mysql/ruoyi-vue-pro.sql:/tmp/schema.sql:ro | 273 | - ../mysql/ruoyi-vue-pro.sql:/tmp/schema.sql:ro |
| 273 | - ../mysql/quartz.sql:/tmp/quartz.sql:ro | 274 | - ../mysql/quartz.sql:/tmp/quartz.sql:ro |
| 274 | - ./tidb/create_schema.sh:/tmp/create_schema.sh:ro | 275 | - ./tidb/create_schema.sh:/tmp/create_schema.sh:ro |
| 276 | + | ||
| 277 | + # MogDB 测试库:云和恩墨官方容器镜像(仅用于测试),属于 mogdb profile,默认不启动 | ||
| 278 | + # 启动:docker compose up -d mogdb | ||
| 279 | + # 初始化:docker compose exec -u omm mogdb bash /tmp/create_schema.sh(创建 PG 兼容模式的 ruoyi-vue-pro 数据库,并导入 sql/opengauss 脚本) | ||
| 280 | + mogdb: | ||
| 281 | + image: swr.cn-north-4.myhuaweicloud.com/mogdb/mogdb:5.0.0 | ||
| 282 | + profiles: ["mogdb"] | ||
| 283 | + restart: unless-stopped | ||
| 284 | + shm_size: 1g | ||
| 285 | + environment: | ||
| 286 | + # omm 超级用户、mogdb 用户的密码:至少 8 位,包含大小写字母、数字及特殊字符 | ||
| 287 | + GS_PASSWORD: Yudao@2024 | ||
| 288 | + ports: | ||
| 289 | + # 只绑定本机,应用使用 mogdb 用户连接(omm 初始用户不允许远程连接) | ||
| 290 | + - "127.0.0.1:15432:5432" | ||
| 291 | + volumes: | ||
| 292 | + - mogdb:/var/lib/mogdb | ||
| 293 | + - ../opengauss/quartz.sql:/tmp/quartz.sql:ro | ||
| 294 | + - ../opengauss/ruoyi-vue-pro.sql:/tmp/schema.sql:ro | ||
| 295 | + - ./mogdb/create_schema.sh:/tmp/create_schema.sh:ro |
sql/tools/gaussdb/create_schema.sh
0 → 100644
| 1 | +#!/bin/bash | ||
| 2 | +# 初始化 GaussDB 数据库:使用 GaussDB 的 gsql 客户端,创建 PG 兼容模式(DBCOMPATIBILITY = 'PG')、UTF8 编码的全新数据库,并导入 sql/opengauss 脚本 | ||
| 3 | +# GaussDB 已内置 dual 视图(sys.dual、pg_catalog.dual),导入时只跳过 sql/opengauss/ruoyi-vue-pro.sql 开头创建 dual 表的一段,不修改内置视图,其余 SQL 保持不变 | ||
| 4 | +# 数据库已存在(包括空库)时拒绝导入;任一步骤失败都以非零状态退出 | ||
| 5 | +# 执行:GAUSSDB_PORT=8000 GAUSSDB_USER='数据库账号' GAUSSDB_PASSWORD='数据库密码' bash sql/tools/gaussdb/create_schema.sh | ||
| 6 | +set -euo pipefail | ||
| 7 | + | ||
| 8 | +host="${GAUSSDB_HOST:-}" # 为空时,使用本机的 Unix Socket 连接 | ||
| 9 | +port="${GAUSSDB_PORT:?GAUSSDB_PORT 未设置,请设置为 GaussDB 的端口}" | ||
| 10 | +user="${GAUSSDB_USER:?GAUSSDB_USER 未设置,请设置为具有 CREATEDB 权限的数据库账号}" | ||
| 11 | +password="${GAUSSDB_PASSWORD:?GAUSSDB_PASSWORD 未设置,请设置为数据库账号的实际密码}" | ||
| 12 | +maintenance_db="${GAUSSDB_MAINTENANCE_DB:-postgres}" | ||
| 13 | +database="${GAUSSDB_DATABASE:-ruoyi-vue-pro}" | ||
| 14 | +sql_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../opengauss" && pwd)" | ||
| 15 | + | ||
| 16 | +if [[ ! "${database}" =~ ^[A-Za-z0-9_-]+$ ]]; then | ||
| 17 | + echo "数据库名 ${database} 不合法,只允许字母、数字、下划线和中划线" >&2 | ||
| 18 | + exit 1 | ||
| 19 | +fi | ||
| 20 | +if ! command -v gsql >/dev/null 2>&1; then | ||
| 21 | + echo "未找到 gsql 客户端:请在安装了 GaussDB gsql 客户端的机器上执行,例如以数据库安装用户登录 GaussDB 服务器" >&2 | ||
| 22 | + exit 1 | ||
| 23 | +fi | ||
| 24 | + | ||
| 25 | +client=(gsql -p "${port}" -U "${user}" -W "${password}" -X -q -v ON_ERROR_STOP=1) | ||
| 26 | +if [ -n "${host}" ]; then | ||
| 27 | + client+=(-h "${host}") | ||
| 28 | +fi | ||
| 29 | + | ||
| 30 | +# 1. 生成跳过 dual 段的临时脚本:只允许跳过「-- Table structure for dual」到其后第一个「-- @formatter:on」之间的 dual 建表、注释及插入语句 | ||
| 31 | +schema_file="$(mktemp)" | ||
| 32 | +trap 'rm -f "${schema_file}"' EXIT | ||
| 33 | +# 注释语句只允许单个字符串常量(内部的单引号写作 ''),字符串后只能是行尾的分号,避免同一行中的其它 SQL 被一起跳过 | ||
| 34 | +if ! awk -v q="'" ' | ||
| 35 | + BEGIN { comment = "^COMMENT ON TABLE dual IS " q "([^" q "]|" q q ")*" q ";$" } | ||
| 36 | + /^-- Table structure for dual$/ { skipping = 1; blocks++ } | ||
| 37 | + skipping { | ||
| 38 | + if ($0 ~ /^(DROP TABLE IF EXISTS dual;|CREATE TABLE dual|INSERT INTO dual VALUES \(1\);)$/) { | ||
| 39 | + statements++ | ||
| 40 | + } else if ($0 !~ /^(--.*|\(|[[:space:]]*id int2|\);)?$/ && $0 !~ comment) { | ||
| 41 | + unexpected = 1 | ||
| 42 | + } | ||
| 43 | + if ($0 ~ /^-- @formatter:on$/) { | ||
| 44 | + skipping = 0 | ||
| 45 | + } | ||
| 46 | + next | ||
| 47 | + } | ||
| 48 | + { print } | ||
| 49 | + END { exit (blocks == 1 && statements == 3 && !skipping && !unexpected) ? 0 : 1 } | ||
| 50 | +' "${sql_dir}/ruoyi-vue-pro.sql" > "${schema_file}" \ | ||
| 51 | + || grep -Eq '^(DROP TABLE IF EXISTS dual;|CREATE TABLE dual|INSERT INTO dual )' "${schema_file}"; then | ||
| 52 | + echo "${sql_dir}/ruoyi-vue-pro.sql 中 dual 表一段的格式与预期不一致,拒绝导入;请检查脚本是否由 sql/tools/convertor.py 生成" >&2 | ||
| 53 | + exit 1 | ||
| 54 | +fi | ||
| 55 | + | ||
| 56 | +# 2. 校验账号、密码及认证方式 | ||
| 57 | +if ! "${client[@]}" -d "${maintenance_db}" -c 'SELECT 1' >/dev/null; then | ||
| 58 | + echo "无法使用账号 ${user} 登录 ${maintenance_db} 数据库:请检查 GAUSSDB_HOST、GAUSSDB_PORT、GAUSSDB_USER、GAUSSDB_PASSWORD,以及 gs_hba.conf 中该账号、客户端地址的认证配置" >&2 | ||
| 59 | + exit 1 | ||
| 60 | +fi | ||
| 61 | + | ||
| 62 | +# 3. 校验 GaussDB 内置的 dual 视图:openGauss、MogDB 没有该视图,请直接导入 sql/opengauss 脚本 | ||
| 63 | +dual_count=$("${client[@]}" -d "${maintenance_db}" -At -c "SELECT COUNT(*) FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = 'dual' AND c.relkind = 'v' AND n.nspname IN ('sys', 'pg_catalog');") | ||
| 64 | +if [ "${dual_count}" = "0" ]; then | ||
| 65 | + echo "未检测到 GaussDB 内置的 dual 视图,拒绝导入:该脚本只适用于 GaussDB,openGauss、MogDB 请直接导入 sql/opengauss 脚本" >&2 | ||
| 66 | + exit 1 | ||
| 67 | +fi | ||
| 68 | + | ||
| 69 | +# 4. 创建全新数据库:不判断是否存在,数据库已存在时直接失败,避免项目 SQL 中的 DROP TABLE 重建已有的同名表 | ||
| 70 | +# 注意:GaussDB 默认创建的是 A(Oracle)兼容模式的数据库,项目脚本需要使用 PG 兼容模式 | ||
| 71 | +if ! "${client[@]}" -d "${maintenance_db}" -c "CREATE DATABASE \"${database}\" ENCODING 'UTF8' DBCOMPATIBILITY = 'PG';"; then | ||
| 72 | + echo "创建数据库 ${database} 失败:只允许初始化全新的数据库,数据库已存在(包括空库)时拒绝导入;如需重新初始化,请确认数据可以删除后,手动删除该数据库" >&2 | ||
| 73 | + exit 1 | ||
| 74 | +fi | ||
| 75 | +compatibility=$("${client[@]}" -d "${database}" -At -c 'SHOW sql_compatibility;') | ||
| 76 | +if [ "${compatibility}" != "PG" ]; then | ||
| 77 | + echo "数据库 ${database} 的兼容模式为 ${compatibility},不是 PG,拒绝导入" >&2 | ||
| 78 | + exit 1 | ||
| 79 | +fi | ||
| 80 | + | ||
| 81 | +# 5. 按 quartz、ruoyi-vue-pro 的顺序导入脚本(ON_ERROR_STOP 遇到错误立即退出,失败时保留数据库,便于排查) | ||
| 82 | +"${client[@]}" -d "${database}" -f "${sql_dir}/quartz.sql" | ||
| 83 | +"${client[@]}" -d "${database}" -f "${schema_file}" | ||
| 84 | + | ||
| 85 | +# 只统计普通表、分区表;dual 使用 GaussDB 内置视图,不计入 | ||
| 86 | +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');") | ||
| 87 | +echo "GaussDB 初始化完成:数据库 ${database} 共 ${table_count} 张表" |
sql/tools/mogdb/create_schema.sh
0 → 100644
| 1 | +#!/bin/bash | ||
| 2 | +# 初始化 MogDB 测试库:在 mogdb 容器内以 omm 用户执行,创建 PG 兼容模式(DBCOMPATIBILITY = 'PG')、UTF8 编码的全新数据库,并导入 sql/opengauss 脚本 | ||
| 3 | +# 数据库已存在(包括空库)时拒绝导入;任一步骤失败都以非零状态退出 | ||
| 4 | +# 执行:docker compose exec -u omm mogdb bash /tmp/create_schema.sh | ||
| 5 | +set -euo pipefail | ||
| 6 | + | ||
| 7 | +# 官方镜像只在 omm 用户的 .profile 中设置 gsql 的 PATH,这里显式设置 | ||
| 8 | +export GAUSSHOME="${GAUSSHOME:-/usr/local/mogdb}" | ||
| 9 | +export PATH="${GAUSSHOME}/bin:${PATH}" | ||
| 10 | +export LD_LIBRARY_PATH="${GAUSSHOME}/lib:${LD_LIBRARY_PATH:-}" | ||
| 11 | + | ||
| 12 | +port="${GS_PORT:-5432}" | ||
| 13 | +owner="${MOGDB_OWNER:-mogdb}" # 应用连接使用的账号,官方镜像默认创建 mogdb 用户;omm 初始用户不允许远程连接 | ||
| 14 | +password="${GS_PASSWORD:?GS_PASSWORD 未设置,请检查 docker-compose.yaml 中 mogdb 服务的环境变量}" | ||
| 15 | +database="${MOGDB_DATABASE:-ruoyi-vue-pro}" | ||
| 16 | + | ||
| 17 | +if [[ ! "${database}" =~ ^[A-Za-z0-9_-]+$ ]]; then | ||
| 18 | + echo "数据库名 ${database} 不合法,只允许字母、数字、下划线和中划线" >&2 | ||
| 19 | + exit 1 | ||
| 20 | +fi | ||
| 21 | +if [[ ! "${owner}" =~ ^[A-Za-z0-9_]+$ ]]; then | ||
| 22 | + echo "用户名 ${owner} 不合法,只允许字母、数字和下划线" >&2 | ||
| 23 | + exit 1 | ||
| 24 | +fi | ||
| 25 | +if [ "$(id -un)" != "omm" ]; then | ||
| 26 | + echo "请使用 omm 用户执行:docker compose exec -u omm mogdb bash /tmp/create_schema.sh" >&2 | ||
| 27 | + exit 1 | ||
| 28 | +fi | ||
| 29 | + | ||
| 30 | +client=(gsql -p "${port}" -X -q -v ON_ERROR_STOP=1) | ||
| 31 | + | ||
| 32 | +# 1. 等待 MogDB 可连接(首次启动时需要初始化数据目录) | ||
| 33 | +for i in $(seq 1 60); do | ||
| 34 | + if "${client[@]}" -d postgres -c 'SELECT 1' >/dev/null 2>&1; then | ||
| 35 | + break | ||
| 36 | + fi | ||
| 37 | + if [ "${i}" -eq 60 ]; then | ||
| 38 | + echo "等待 MogDB 启动超时,请执行 docker compose logs mogdb 检查启动日志" >&2 | ||
| 39 | + exit 1 | ||
| 40 | + fi | ||
| 41 | + sleep 5 | ||
| 42 | +done | ||
| 43 | + | ||
| 44 | +# 2. 校验数据库所有者 | ||
| 45 | +if [ "$("${client[@]}" -d postgres -At -c "SELECT COUNT(*) FROM pg_catalog.pg_roles WHERE rolname = '${owner}';")" != "1" ]; then | ||
| 46 | + echo "用户 ${owner} 不存在:请通过 MOGDB_OWNER 指定应用连接使用的账号" >&2 | ||
| 47 | + exit 1 | ||
| 48 | +fi | ||
| 49 | + | ||
| 50 | +# 3. 创建全新数据库:不判断是否存在,数据库已存在时直接失败,避免项目 SQL 中的 DROP TABLE 重建已有的同名表 | ||
| 51 | +# 注意:MogDB 默认创建的是 A(Oracle)兼容模式的数据库,导入项目脚本会失败,需要使用 PG 兼容模式 | ||
| 52 | +if ! "${client[@]}" -d postgres -c "CREATE DATABASE \"${database}\" OWNER \"${owner}\" ENCODING 'UTF8' DBCOMPATIBILITY = 'PG';"; then | ||
| 53 | + echo "创建数据库 ${database} 失败:只允许初始化全新的数据库,数据库已存在(包括空库)时拒绝导入;如需重新初始化,请删除 MogDB 数据卷后重建" >&2 | ||
| 54 | + exit 1 | ||
| 55 | +fi | ||
| 56 | + | ||
| 57 | +# 4. 使用数据库所有者,按 quartz、ruoyi-vue-pro 的顺序导入脚本(ON_ERROR_STOP 遇到错误立即退出,失败时保留数据库,便于排查) | ||
| 58 | +owner_client=("${client[@]}" -U "${owner}" -W "${password}" -d "${database}") | ||
| 59 | +"${owner_client[@]}" -f /tmp/quartz.sql | ||
| 60 | +"${owner_client[@]}" -f /tmp/schema.sql | ||
| 61 | + | ||
| 62 | +# 只统计普通表、分区表 | ||
| 63 | +table_count=$("${owner_client[@]}" -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');") | ||
| 64 | +echo "MogDB 初始化完成:数据库 ${database} 共 ${table_count} 张表" |
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnum.java
| @@ -36,7 +36,7 @@ public enum DbTypeEnum { | @@ -36,7 +36,7 @@ public enum DbTypeEnum { | ||
| 36 | /** | 36 | /** |
| 37 | * PostgreSQL | 37 | * PostgreSQL |
| 38 | * | 38 | * |
| 39 | - * 华为 openGauss 使用 ProductName 与 PostgreSQL 相同 | 39 | + * 华为 openGauss(PostgreSQL JDBC 驱动)、MogDB(io.mogdb 的 openGauss JDBC 驱动)使用 ProductName 与 PostgreSQL 相同 |
| 40 | */ | 40 | */ |
| 41 | POSTGRE_SQL(DbType.POSTGRE_SQL, "PostgreSQL", "POSITION(',' || CAST(#{value} AS VARCHAR) || ',' IN ',' || #{column} || ',') > 0"), | 41 | POSTGRE_SQL(DbType.POSTGRE_SQL, "PostgreSQL", "POSITION(',' || CAST(#{value} AS VARCHAR) || ',' IN ',' || #{column} || ',') > 0"), |
| 42 | 42 | ||
| @@ -62,7 +62,22 @@ public enum DbTypeEnum { | @@ -62,7 +62,22 @@ public enum DbTypeEnum { | ||
| 62 | /** | 62 | /** |
| 63 | * OceanBase | 63 | * OceanBase |
| 64 | */ | 64 | */ |
| 65 | - OCEAN_BASE(DbType.OCEAN_BASE, "OceanBase", "FIND_IN_SET(#{value}, #{column}) <> 0") | 65 | + OCEAN_BASE(DbType.OCEAN_BASE, "OceanBase", "FIND_IN_SET(#{value}, #{column}) <> 0"), |
| 66 | + | ||
| 67 | + /** | ||
| 68 | + * 华为 GaussDB | ||
| 69 | + * | ||
| 70 | + * 原生驱动(jdbc:gaussdb:)使用的 ProductName 为 GaussDB,需要使用 PG 兼容模式(DBCOMPATIBILITY = 'PG')创建的数据库 | ||
| 71 | + */ | ||
| 72 | + GAUSS_DB(DbType.GAUSS_DB, "GaussDB", "POSITION(',' || CAST(#{value} AS VARCHAR) || ',' IN ',' || #{column} || ',') > 0"), | ||
| 73 | + | ||
| 74 | + /** | ||
| 75 | + * openGauss | ||
| 76 | + * | ||
| 77 | + * org.opengauss 的 openGauss JDBC 驱动(jdbc:opengauss:)使用的 ProductName 为 openGauss,连接 MogDB 时也是如此; | ||
| 78 | + * 需要使用 PG 兼容模式(DBCOMPATIBILITY = 'PG')创建的数据库 | ||
| 79 | + */ | ||
| 80 | + OPENGAUSS(DbType.OPENGAUSS, "openGauss", "POSITION(',' || CAST(#{value} AS VARCHAR) || ',' IN ',' || #{column} || ',') > 0") | ||
| 66 | 81 | ||
| 67 | ; | 82 | ; |
| 68 | 83 |
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/JdbcUtils.java
| @@ -38,14 +38,20 @@ public class JdbcUtils { | @@ -38,14 +38,20 @@ public class JdbcUtils { | ||
| 38 | /** | 38 | /** |
| 39 | * 获得 URL 对应的 DB 类型 | 39 | * 获得 URL 对应的 DB 类型 |
| 40 | * | 40 | * |
| 41 | - * 特殊:瀚高 HighGo 原生驱动(jdbc:highgo:)兼容 PostgreSQL,按 PostgreSQL 处理,复用其序列主键、Quartz 等适配 | 41 | + * 特殊:以下原生驱动连接的数据库兼容 PostgreSQL,按 PostgreSQL 处理,复用其序列主键、Quartz、代码生成等适配: |
| 42 | + * 1. 瀚高 HighGo(jdbc:highgo:) | ||
| 43 | + * 2. openGauss、MogDB(jdbc:opengauss:),需要使用 PG 兼容模式(DBCOMPATIBILITY = 'PG')创建的数据库 | ||
| 44 | + * 3. 华为 GaussDB(jdbc:gaussdb:),需要使用 PG 兼容模式(DBCOMPATIBILITY = 'PG')创建的数据库 | ||
| 45 | + * | ||
| 46 | + * 注意:MyBatis Plus 的分页仍按原生 URL 识别,例如说 GaussDB 使用 GaussDBDialect | ||
| 42 | * | 47 | * |
| 43 | * @param url URL | 48 | * @param url URL |
| 44 | * @return DB 类型 | 49 | * @return DB 类型 |
| 45 | */ | 50 | */ |
| 46 | public static DbType getDbType(String url) { | 51 | public static DbType getDbType(String url) { |
| 47 | DbType dbType = com.baomidou.mybatisplus.extension.toolkit.JdbcUtils.getDbType(url); | 52 | DbType dbType = com.baomidou.mybatisplus.extension.toolkit.JdbcUtils.getDbType(url); |
| 48 | - return dbType == DbType.HIGH_GO ? DbType.POSTGRE_SQL : dbType; | 53 | + return ObjectUtils.equalsAny(dbType, DbType.HIGH_GO, DbType.OPENGAUSS, DbType.GAUSS_DB) |
| 54 | + ? DbType.POSTGRE_SQL : dbType; | ||
| 49 | } | 55 | } |
| 50 | 56 | ||
| 51 | /** | 57 | /** |
yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessorTest.java
| @@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test; | @@ -9,6 +9,7 @@ import org.junit.jupiter.api.Test; | ||
| 9 | import org.springframework.core.env.MapPropertySource; | 9 | import org.springframework.core.env.MapPropertySource; |
| 10 | import org.springframework.core.env.StandardEnvironment; | 10 | import org.springframework.core.env.StandardEnvironment; |
| 11 | 11 | ||
| 12 | +import java.util.Arrays; | ||
| 12 | import java.util.HashMap; | 13 | import java.util.HashMap; |
| 13 | import java.util.Map; | 14 | import java.util.Map; |
| 14 | 15 | ||
| @@ -51,6 +52,51 @@ public class IdTypeEnvironmentPostProcessorTest { | @@ -51,6 +52,51 @@ public class IdTypeEnvironmentPostProcessorTest { | ||
| 51 | } | 52 | } |
| 52 | 53 | ||
| 53 | @Test | 54 | @Test |
| 55 | + public void testPostProcessEnvironment_gaussDBNativeUrl() { | ||
| 56 | + // 准备参数 | ||
| 57 | + StandardEnvironment environment = buildEnvironment("jdbc:gaussdb://127.0.0.1:8000/ruoyi-vue-pro", null); | ||
| 58 | + | ||
| 59 | + // 调用 | ||
| 60 | + processor.postProcessEnvironment(environment, null); | ||
| 61 | + | ||
| 62 | + // 断言:GaussDB(PG 兼容模式)与 PostgreSQL 一致,使用序列主键及 PostgreSQLDelegate | ||
| 63 | + assertEquals("INPUT", environment.getProperty(ID_TYPE_KEY)); | ||
| 64 | + assertEquals(POSTGRESQL_DELEGATE, environment.getProperty(QUARTZ_DELEGATE_KEY)); | ||
| 65 | + assertInstanceOf(PostgreKeyGenerator.class, new YudaoMybatisAutoConfiguration().keyGenerator(environment)); | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + @Test | ||
| 69 | + public void testPostProcessEnvironment_openGaussNativeUrl() { | ||
| 70 | + // 准备参数:openGauss、MogDB 使用 openGauss JDBC 驱动 | ||
| 71 | + StandardEnvironment environment = buildEnvironment("jdbc:opengauss://127.0.0.1:15432/ruoyi-vue-pro", null); | ||
| 72 | + | ||
| 73 | + // 调用 | ||
| 74 | + processor.postProcessEnvironment(environment, null); | ||
| 75 | + | ||
| 76 | + // 断言:与 PostgreSQL 一致,使用序列主键及 PostgreSQLDelegate | ||
| 77 | + assertEquals("INPUT", environment.getProperty(ID_TYPE_KEY)); | ||
| 78 | + assertEquals(POSTGRESQL_DELEGATE, environment.getProperty(QUARTZ_DELEGATE_KEY)); | ||
| 79 | + assertInstanceOf(PostgreKeyGenerator.class, new YudaoMybatisAutoConfiguration().keyGenerator(environment)); | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + @Test | ||
| 83 | + public void testPostProcessEnvironment_notNormalizedNativeUrls() { | ||
| 84 | + // 海量 Vastbase 原生驱动、神通、PolarDB(兼容 Oracle)未按 PostgreSQL 处理:保持自增主键,且不设置 Quartz Delegate | ||
| 85 | + for (String url : Arrays.asList("jdbc:vastbase://127.0.0.1:5432/ruoyi-vue-pro", | ||
| 86 | + "jdbc:oscar://127.0.0.1:2003/OSRDB", "jdbc:polardb://127.0.0.1:1521/ruoyi-vue-pro")) { | ||
| 87 | + // 准备参数 | ||
| 88 | + StandardEnvironment environment = buildEnvironment(url, null); | ||
| 89 | + | ||
| 90 | + // 调用 | ||
| 91 | + processor.postProcessEnvironment(environment, null); | ||
| 92 | + | ||
| 93 | + // 断言 | ||
| 94 | + assertEquals("AUTO", environment.getProperty(ID_TYPE_KEY), url); | ||
| 95 | + assertNull(environment.getProperty(QUARTZ_DELEGATE_KEY), url); | ||
| 96 | + } | ||
| 97 | + } | ||
| 98 | + | ||
| 99 | + @Test | ||
| 54 | public void testPostProcessEnvironment_postgresqlUrl() { | 100 | public void testPostProcessEnvironment_postgresqlUrl() { |
| 55 | // 准备参数 | 101 | // 准备参数 |
| 56 | StandardEnvironment environment = buildEnvironment("jdbc:postgresql://127.0.0.1:5866/ruoyi-vue-pro", null); | 102 | StandardEnvironment environment = buildEnvironment("jdbc:postgresql://127.0.0.1:5866/ruoyi-vue-pro", null); |
| @@ -95,12 +141,19 @@ public class IdTypeEnvironmentPostProcessorTest { | @@ -95,12 +141,19 @@ public class IdTypeEnvironmentPostProcessorTest { | ||
| 95 | } | 141 | } |
| 96 | 142 | ||
| 97 | @Test | 143 | @Test |
| 98 | - public void testGetDbType_onlyNormalizeHighGo() { | 144 | + public void testGetDbType_normalizePostgreSQLCompatible() { |
| 145 | + // 按 PostgreSQL 处理:瀚高、openGauss(MogDB)、GaussDB 的原生驱动 | ||
| 99 | assertEquals(DbType.POSTGRE_SQL, JdbcUtils.getDbType("jdbc:highgo://127.0.0.1:5866/ruoyi-vue-pro")); | 146 | assertEquals(DbType.POSTGRE_SQL, JdbcUtils.getDbType("jdbc:highgo://127.0.0.1:5866/ruoyi-vue-pro")); |
| 147 | + assertEquals(DbType.POSTGRE_SQL, JdbcUtils.getDbType("jdbc:opengauss://127.0.0.1:15432/ruoyi-vue-pro")); | ||
| 148 | + assertEquals(DbType.POSTGRE_SQL, JdbcUtils.getDbType("jdbc:gaussdb://127.0.0.1:8000/ruoyi-vue-pro")); | ||
| 100 | assertEquals(DbType.POSTGRE_SQL, JdbcUtils.getDbType("jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro")); | 149 | assertEquals(DbType.POSTGRE_SQL, JdbcUtils.getDbType("jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro")); |
| 150 | + // 保持 MyBatis Plus 的识别结果 | ||
| 101 | assertEquals(DbType.MYSQL, JdbcUtils.getDbType("jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro")); | 151 | assertEquals(DbType.MYSQL, JdbcUtils.getDbType("jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro")); |
| 102 | assertEquals(DbType.KINGBASE_ES, JdbcUtils.getDbType("jdbc:kingbase8://127.0.0.1:54321/test")); | 152 | assertEquals(DbType.KINGBASE_ES, JdbcUtils.getDbType("jdbc:kingbase8://127.0.0.1:54321/test")); |
| 103 | assertEquals(DbType.DM, JdbcUtils.getDbType("jdbc:dm://127.0.0.1:5236?schema=RUOYI_VUE_PRO")); | 153 | assertEquals(DbType.DM, JdbcUtils.getDbType("jdbc:dm://127.0.0.1:5236?schema=RUOYI_VUE_PRO")); |
| 154 | + assertEquals(DbType.VASTBASE, JdbcUtils.getDbType("jdbc:vastbase://127.0.0.1:5432/ruoyi-vue-pro")); | ||
| 155 | + assertEquals(DbType.OSCAR, JdbcUtils.getDbType("jdbc:oscar://127.0.0.1:2003/OSRDB")); | ||
| 156 | + assertEquals(DbType.OTHER, JdbcUtils.getDbType("jdbc:polardb://127.0.0.1:1521/ruoyi-vue-pro")); | ||
| 104 | } | 157 | } |
| 105 | 158 | ||
| 106 | private static StandardEnvironment buildEnvironment(String url, Map<String, Object> extra) { | 159 | private static StandardEnvironment buildEnvironment(String url, Map<String, Object> extra) { |
yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/enums/DbTypeEnumTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.mybatis.core.enums; | ||
| 2 | + | ||
| 3 | +import com.baomidou.mybatisplus.annotation.DbType; | ||
| 4 | +import org.junit.jupiter.api.Test; | ||
| 5 | + | ||
| 6 | +import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| 7 | +import static org.junit.jupiter.api.Assertions.assertNull; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * {@link DbTypeEnum} 的单元测试 | ||
| 11 | + * | ||
| 12 | + * @author 芋道源码 | ||
| 13 | + */ | ||
| 14 | +public class DbTypeEnumTest { | ||
| 15 | + | ||
| 16 | + @Test | ||
| 17 | + public void testFind() { | ||
| 18 | + // 华为 GaussDB 原生驱动(jdbc:gaussdb:)返回的 ProductName | ||
| 19 | + assertEquals(DbType.GAUSS_DB, DbTypeEnum.find("GaussDB")); | ||
| 20 | + // org.opengauss 的 openGauss JDBC 驱动(jdbc:opengauss:)连接 MogDB 时返回的 ProductName | ||
| 21 | + assertEquals(DbType.OPENGAUSS, DbTypeEnum.find("openGauss")); | ||
| 22 | + // io.mogdb 的 openGauss JDBC 驱动连接 MogDB 时,返回的 ProductName 与 PostgreSQL 相同 | ||
| 23 | + assertEquals(DbType.POSTGRE_SQL, DbTypeEnum.find("PostgreSQL")); | ||
| 24 | + assertEquals(DbType.KINGBASE_ES, DbTypeEnum.find("KingbaseES")); | ||
| 25 | + assertNull(DbTypeEnum.find("")); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + @Test | ||
| 29 | + public void testGetFindInSetTemplate_postgreSQLCompatible() { | ||
| 30 | + // GaussDB、openGauss(PG 兼容模式)与 PostgreSQL 使用相同的 POSITION 模板 | ||
| 31 | + assertEquals(DbTypeEnum.getFindInSetTemplate(DbType.POSTGRE_SQL), DbTypeEnum.getFindInSetTemplate(DbType.GAUSS_DB)); | ||
| 32 | + assertEquals(DbTypeEnum.getFindInSetTemplate(DbType.POSTGRE_SQL), DbTypeEnum.getFindInSetTemplate(DbType.OPENGAUSS)); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | +} |
yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtilsTest.java
| @@ -113,6 +113,10 @@ public class MyBatisUtilsTest { | @@ -113,6 +113,10 @@ public class MyBatisUtilsTest { | ||
| 113 | MyBatisUtils.findInSet(DbType.POSTGRE_SQL, "websites", 1)); | 113 | MyBatisUtils.findInSet(DbType.POSTGRE_SQL, "websites", 1)); |
| 114 | assertEquals("CHARINDEX(',' + CAST({2} AS varchar(255)) + ',', ',' + websites + ',') > 0", | 114 | assertEquals("CHARINDEX(',' + CAST({2} AS varchar(255)) + ',', ',' + websites + ',') > 0", |
| 115 | MyBatisUtils.findInSet(DbType.SQL_SERVER, "websites", 2)); | 115 | MyBatisUtils.findInSet(DbType.SQL_SERVER, "websites", 2)); |
| 116 | + assertEquals("POSITION(',' || CAST({0} AS VARCHAR) || ',' IN ',' || websites || ',') > 0", | ||
| 117 | + MyBatisUtils.findInSet(DbType.GAUSS_DB, "websites", 0)); | ||
| 118 | + assertEquals("POSITION(',' || CAST({0} AS VARCHAR) || ',' IN ',' || websites || ',') > 0", | ||
| 119 | + MyBatisUtils.findInSet(DbType.OPENGAUSS, "websites", 0)); | ||
| 116 | } | 120 | } |
| 117 | 121 | ||
| 118 | @Test | 122 | @Test |
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/config/BpmFlowableConfiguration.java
| 1 | package cn.iocoder.yudao.module.bpm.framework.flowable.config; | 1 | package cn.iocoder.yudao.module.bpm.framework.flowable.config; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.collection.ListUtil; | 3 | import cn.hutool.core.collection.ListUtil; |
| 4 | +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | ||
| 5 | +import cn.iocoder.yudao.framework.mybatis.core.enums.DbTypeEnum; | ||
| 4 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.BpmActivityBehaviorFactory; | 6 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.BpmActivityBehaviorFactory; |
| 5 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; | 7 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; |
| 6 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; | 8 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; |
| 7 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; | 9 | import cn.iocoder.yudao.module.bpm.framework.flowable.core.event.BpmProcessInstanceEventPublisher; |
| 8 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | 10 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 11 | +import lombok.extern.slf4j.Slf4j; | ||
| 9 | import org.flowable.common.engine.api.delegate.FlowableFunctionDelegate; | 12 | import org.flowable.common.engine.api.delegate.FlowableFunctionDelegate; |
| 10 | import org.flowable.common.engine.api.delegate.event.FlowableEventListener; | 13 | import org.flowable.common.engine.api.delegate.event.FlowableEventListener; |
| 14 | +import org.flowable.common.engine.impl.AbstractEngineConfiguration; | ||
| 11 | import org.flowable.spring.SpringProcessEngineConfiguration; | 15 | import org.flowable.spring.SpringProcessEngineConfiguration; |
| 12 | import org.flowable.spring.boot.EngineConfigurationConfigurer; | 16 | import org.flowable.spring.boot.EngineConfigurationConfigurer; |
| 13 | import org.springframework.beans.factory.ObjectProvider; | 17 | import org.springframework.beans.factory.ObjectProvider; |
| @@ -18,6 +22,8 @@ import org.springframework.context.annotation.Configuration; | @@ -18,6 +22,8 @@ import org.springframework.context.annotation.Configuration; | ||
| 18 | import org.springframework.core.task.AsyncTaskExecutor; | 22 | import org.springframework.core.task.AsyncTaskExecutor; |
| 19 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | 23 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; |
| 20 | 24 | ||
| 25 | +import java.sql.Connection; | ||
| 26 | +import java.sql.SQLException; | ||
| 21 | import java.util.List; | 27 | import java.util.List; |
| 22 | 28 | ||
| 23 | /** | 29 | /** |
| @@ -26,6 +32,7 @@ import java.util.List; | @@ -26,6 +32,7 @@ import java.util.List; | ||
| 26 | * @author jason | 32 | * @author jason |
| 27 | */ | 33 | */ |
| 28 | @Configuration(proxyBeanMethods = false) | 34 | @Configuration(proxyBeanMethods = false) |
| 35 | +@Slf4j | ||
| 29 | public class BpmFlowableConfiguration { | 36 | public class BpmFlowableConfiguration { |
| 30 | 37 | ||
| 31 | /** | 38 | /** |
| @@ -53,6 +60,7 @@ public class BpmFlowableConfiguration { | @@ -53,6 +60,7 @@ public class BpmFlowableConfiguration { | ||
| 53 | * | 60 | * |
| 54 | * 1. 设置各种监听器 | 61 | * 1. 设置各种监听器 |
| 55 | * 2. 设置自定义的 ActivityBehaviorFactory 实现 | 62 | * 2. 设置自定义的 ActivityBehaviorFactory 实现 |
| 63 | + * 3. 设置华为 GaussDB、openGauss(含 MogDB)的数据库类型 | ||
| 56 | */ | 64 | */ |
| 57 | @Bean | 65 | @Bean |
| 58 | public EngineConfigurationConfigurer<SpringProcessEngineConfiguration> bpmProcessEngineConfigurationConfigurer( | 66 | public EngineConfigurationConfigurer<SpringProcessEngineConfiguration> bpmProcessEngineConfigurationConfigurer( |
| @@ -66,9 +74,36 @@ public class BpmFlowableConfiguration { | @@ -66,9 +74,36 @@ public class BpmFlowableConfiguration { | ||
| 66 | configuration.setActivityBehaviorFactory(bpmActivityBehaviorFactory); | 74 | configuration.setActivityBehaviorFactory(bpmActivityBehaviorFactory); |
| 67 | // 设置自定义的函数 | 75 | // 设置自定义的函数 |
| 68 | configuration.setCustomFlowableFunctionDelegates(ListUtil.toList(customFlowableFunctionDelegates.stream().iterator())); | 76 | configuration.setCustomFlowableFunctionDelegates(ListUtil.toList(customFlowableFunctionDelegates.stream().iterator())); |
| 77 | + // 设置华为 GaussDB、openGauss(含 MogDB)的数据库类型 | ||
| 78 | + initPostgreSQLCompatibleDatabaseType(configuration); | ||
| 69 | }; | 79 | }; |
| 70 | } | 80 | } |
| 71 | 81 | ||
| 82 | + /** | ||
| 83 | + * 以下原生驱动返回的 ProductName 不是 PostgreSQL,Flowable 无法识别,启动时会报错: | ||
| 84 | + * 1. 华为 GaussDB 的原生驱动(jdbc:gaussdb:),返回 GaussDB | ||
| 85 | + * 2. org.opengauss 的 openGauss JDBC 驱动(jdbc:opengauss:),返回 openGauss,连接 MogDB 时也是如此 | ||
| 86 | + * | ||
| 87 | + * 因此,在未显式设置 databaseType 时,按 PostgreSQL 处理(需要使用 PG 兼容模式创建的数据库)。 | ||
| 88 | + * 只读取 Flowable 自身数据源的元数据,其它数据库不做处理,仍由 Flowable 自动识别 | ||
| 89 | + * | ||
| 90 | + * @param configuration Flowable 配置 | ||
| 91 | + */ | ||
| 92 | + private static void initPostgreSQLCompatibleDatabaseType(SpringProcessEngineConfiguration configuration) { | ||
| 93 | + if (configuration.getDatabaseType() != null || configuration.getDataSource() == null) { | ||
| 94 | + return; | ||
| 95 | + } | ||
| 96 | + try (Connection connection = configuration.getDataSource().getConnection()) { | ||
| 97 | + if (ObjectUtils.equalsAny(connection.getMetaData().getDatabaseProductName(), | ||
| 98 | + DbTypeEnum.GAUSS_DB.getProductName(), DbTypeEnum.OPENGAUSS.getProductName())) { | ||
| 99 | + configuration.setDatabaseType(AbstractEngineConfiguration.DATABASE_TYPE_POSTGRES); | ||
| 100 | + } | ||
| 101 | + } catch (SQLException ex) { | ||
| 102 | + // 获取失败时,不做处理,由 Flowable 自动识别时报错 | ||
| 103 | + log.warn("[initPostgreSQLCompatibleDatabaseType][获取数据库类型失败]", ex); | ||
| 104 | + } | ||
| 105 | + } | ||
| 106 | + | ||
| 72 | // =========== 审批人相关的 Bean ========== | 107 | // =========== 审批人相关的 Bean ========== |
| 73 | 108 | ||
| 74 | @Bean | 109 | @Bean |
yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/config/BpmFlowableConfigurationTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.bpm.framework.flowable.config; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; | ||
| 4 | +import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.BpmActivityBehaviorFactory; | ||
| 5 | +import org.flowable.common.engine.api.delegate.FlowableFunctionDelegate; | ||
| 6 | +import org.flowable.common.engine.api.delegate.event.FlowableEventListener; | ||
| 7 | +import org.flowable.common.engine.impl.AbstractEngineConfiguration; | ||
| 8 | +import org.flowable.spring.SpringProcessEngineConfiguration; | ||
| 9 | +import org.junit.jupiter.api.Test; | ||
| 10 | +import org.mockito.Mock; | ||
| 11 | +import org.springframework.beans.factory.ObjectProvider; | ||
| 12 | + | ||
| 13 | +import javax.sql.DataSource; | ||
| 14 | +import java.sql.Connection; | ||
| 15 | +import java.sql.DatabaseMetaData; | ||
| 16 | +import java.sql.SQLException; | ||
| 17 | +import java.util.Collections; | ||
| 18 | +import java.util.stream.Stream; | ||
| 19 | + | ||
| 20 | +import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| 21 | +import static org.junit.jupiter.api.Assertions.assertNull; | ||
| 22 | +import static org.mockito.Mockito.never; | ||
| 23 | +import static org.mockito.Mockito.verify; | ||
| 24 | +import static org.mockito.Mockito.when; | ||
| 25 | + | ||
| 26 | +/** | ||
| 27 | + * {@link BpmFlowableConfiguration} 的单元测试 | ||
| 28 | + * | ||
| 29 | + * @author 芋道源码 | ||
| 30 | + */ | ||
| 31 | +public class BpmFlowableConfigurationTest extends BaseMockitoUnitTest { | ||
| 32 | + | ||
| 33 | + @Mock | ||
| 34 | + private ObjectProvider<FlowableEventListener> listeners; | ||
| 35 | + @Mock | ||
| 36 | + private ObjectProvider<FlowableFunctionDelegate> customFlowableFunctionDelegates; | ||
| 37 | + | ||
| 38 | + @Mock | ||
| 39 | + private DataSource dataSource; | ||
| 40 | + @Mock | ||
| 41 | + private Connection connection; | ||
| 42 | + @Mock | ||
| 43 | + private DatabaseMetaData metaData; | ||
| 44 | + | ||
| 45 | + @Test | ||
| 46 | + public void testBpmProcessEngineConfigurationConfigurer_gaussDB() throws SQLException { | ||
| 47 | + // mock 方法:GaussDB 原生驱动返回的 ProductName | ||
| 48 | + mockDatabaseProductName("GaussDB"); | ||
| 49 | + // 准备参数 | ||
| 50 | + SpringProcessEngineConfiguration configuration = buildConfiguration(null); | ||
| 51 | + | ||
| 52 | + // 调用 | ||
| 53 | + configure(configuration); | ||
| 54 | + | ||
| 55 | + // 断言:按 PostgreSQL 处理,并关闭连接 | ||
| 56 | + assertEquals(AbstractEngineConfiguration.DATABASE_TYPE_POSTGRES, configuration.getDatabaseType()); | ||
| 57 | + verify(connection).close(); | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + @Test | ||
| 61 | + public void testBpmProcessEngineConfigurationConfigurer_openGauss() throws SQLException { | ||
| 62 | + // mock 方法:org.opengauss 的 openGauss JDBC 驱动(jdbc:opengauss:)连接 MogDB 时返回的 ProductName | ||
| 63 | + mockDatabaseProductName("openGauss"); | ||
| 64 | + // 准备参数 | ||
| 65 | + SpringProcessEngineConfiguration configuration = buildConfiguration(null); | ||
| 66 | + | ||
| 67 | + // 调用 | ||
| 68 | + configure(configuration); | ||
| 69 | + | ||
| 70 | + // 断言:按 PostgreSQL 处理,并关闭连接 | ||
| 71 | + assertEquals(AbstractEngineConfiguration.DATABASE_TYPE_POSTGRES, configuration.getDatabaseType()); | ||
| 72 | + verify(connection).close(); | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + @Test | ||
| 76 | + public void testBpmProcessEngineConfigurationConfigurer_postgreSQL() throws SQLException { | ||
| 77 | + // mock 方法:PostgreSQL,以及 io.mogdb 的 openGauss JDBC 驱动连接 MogDB 时返回的 ProductName | ||
| 78 | + mockDatabaseProductName("PostgreSQL"); | ||
| 79 | + // 准备参数 | ||
| 80 | + SpringProcessEngineConfiguration configuration = buildConfiguration(null); | ||
| 81 | + | ||
| 82 | + // 调用 | ||
| 83 | + configure(configuration); | ||
| 84 | + | ||
| 85 | + // 断言:不处理,仍由 Flowable 自动识别 | ||
| 86 | + assertNull(configuration.getDatabaseType()); | ||
| 87 | + verify(connection).close(); | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + @Test | ||
| 91 | + public void testBpmProcessEngineConfigurationConfigurer_explicitDatabaseType() throws SQLException { | ||
| 92 | + // 准备参数:用户显式设置 databaseType | ||
| 93 | + SpringProcessEngineConfiguration configuration = buildConfiguration(AbstractEngineConfiguration.DATABASE_TYPE_MYSQL); | ||
| 94 | + | ||
| 95 | + // 调用 | ||
| 96 | + configure(configuration); | ||
| 97 | + | ||
| 98 | + // 断言:不覆盖,且不获取连接 | ||
| 99 | + assertEquals(AbstractEngineConfiguration.DATABASE_TYPE_MYSQL, configuration.getDatabaseType()); | ||
| 100 | + verify(dataSource, never()).getConnection(); | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + @Test | ||
| 104 | + public void testBpmProcessEngineConfigurationConfigurer_connectionError() throws SQLException { | ||
| 105 | + // mock 方法:获取连接失败 | ||
| 106 | + when(dataSource.getConnection()).thenThrow(new SQLException("connection refused")); | ||
| 107 | + // 准备参数 | ||
| 108 | + SpringProcessEngineConfiguration configuration = buildConfiguration(null); | ||
| 109 | + | ||
| 110 | + // 调用 | ||
| 111 | + configure(configuration); | ||
| 112 | + | ||
| 113 | + // 断言:不处理,由 Flowable 自动识别时报错 | ||
| 114 | + assertNull(configuration.getDatabaseType()); | ||
| 115 | + } | ||
| 116 | + | ||
| 117 | + private void mockDatabaseProductName(String productName) throws SQLException { | ||
| 118 | + when(dataSource.getConnection()).thenReturn(connection); | ||
| 119 | + when(connection.getMetaData()).thenReturn(metaData); | ||
| 120 | + when(metaData.getDatabaseProductName()).thenReturn(productName); | ||
| 121 | + } | ||
| 122 | + | ||
| 123 | + private SpringProcessEngineConfiguration buildConfiguration(String databaseType) { | ||
| 124 | + SpringProcessEngineConfiguration configuration = new SpringProcessEngineConfiguration(); | ||
| 125 | + configuration.setDataSource(dataSource); | ||
| 126 | + configuration.setDatabaseType(databaseType); | ||
| 127 | + return configuration; | ||
| 128 | + } | ||
| 129 | + | ||
| 130 | + private void configure(SpringProcessEngineConfiguration configuration) { | ||
| 131 | + when(listeners.iterator()).thenReturn(Collections.emptyIterator()); | ||
| 132 | + when(customFlowableFunctionDelegates.stream()).thenReturn(Stream.empty()); | ||
| 133 | + new BpmFlowableConfiguration().bpmProcessEngineConfigurationConfigurer(listeners, | ||
| 134 | + customFlowableFunctionDelegates, new BpmActivityBehaviorFactory()).configure(configuration); | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | +} |