This commit is contained in:
xsl
2025-11-18 16:38:49 +08:00
commit e20989e133
4419 changed files with 383263 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
######################################################################
# Build Tools
.gradle
/build/
!gradle/wrapper/gradle-wrapper.jar
target/
!.mvn/wrapper/maven-wrapper.jar
.flattened-pom.xml
######################################################################
# IDE
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
nbproject/private/
build/*
nbbuild/
dist/
nbdist/
.nb-gradle/
######################################################################
# Others
*.log
*.xml.versionsBackup
*.swp
!*/build/*.java
!*/build/*.html
!*/build/*.xml
### JRebel ###
rebel.xml
application-my.yaml
/yudao-ui-app/unpackage/
**/.DS_Store
+20
View File
@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2021 ruoyi-vue-pro
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+391
View File
@@ -0,0 +1,391 @@
<p align="center">
<img src="https://img.shields.io/badge/Spring%20Boot-2.7.18-blue.svg" alt="Downloads">
<img src="https://img.shields.io/badge/Vue-3.2-blue.svg" alt="Downloads">
<img src="https://img.shields.io/github/license/YunaiV/ruoyi-vue-pro"/>
</p>
**严肃声明:现在、未来都不会有商业版本,所有代码全部开源!!**
**「我喜欢写代码,乐此不疲」**
**「我喜欢做开源,以此为乐」**
我 🐶 在上海艰苦奋斗,早中晚在 top3 大厂认真搬砖,夜里为开源做贡献。
如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。
## 🐶 新手必读
* 演示地址【Vue3 + element-plus】:<http://dashboard-vue3.yudao.iocoder.cn>
* 演示地址【Vue3 + vben(ant-design-vue)】:<http://dashboard-vben.yudao.iocoder.cn>
* 演示地址【Vue2 + element-ui】:<http://dashboard.yudao.iocoder.cn>
* 启动文档:<https://doc.iocoder.cn/quick-start/>
* 视频教程:<https://doc.iocoder.cn/video/>
## 🐰 版本说明
| 版本 | JDK 8 + Spring Boot 2.7 | JDK 17/21 + Spring Boot 3.2 |
|---------------------------------------------------------------------|---------------------------------------------------------------------------|---------------------------------------------------------------------------------------|
| 【完整版】[ruoyi-vue-pro](https://gitee.com/zhijiantianya/ruoyi-vue-pro) | [`master`](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/) 分支 | [`master-jdk17`](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master-jdk17/) 分支 |
| 【精简版】[yudao-boot-mini](https://gitee.com/yudaocode/yudao-boot-mini) | [`master`](https://gitee.com/yudaocode/yudao-boot-mini/tree/master/) 分支 | [`master-jdk17`](https://gitee.com/yudaocode/yudao-boot-mini/tree/master-jdk17/) 分支 |
* 【完整版】:包括系统功能、基础设施、会员中心、数据报表、工作流程、商城系统、微信公众号、CRM、ERP 等功能
* 【精简版】:只包括系统功能、基础设施功能,不包括会员中心、数据报表、工作流程、商城系统、微信公众号、CRM、ERP 等功能
可参考 [《迁移文档》](https://doc.iocoder.cn/migrate-module/) ,只需要 5-10 分钟,即可将【完整版】按需迁移到【精简版】
## 🐯 平台简介
**芋道**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
> 有任何问题,或者想要的功能,可以在 _Issues_ 中提给艿艿。
>
> 😜 给项目点点 Star 吧,这对我们真的很重要!
![架构图](/.image/common/ruoyi-vue-pro-architecture.png)
* Java 后端:`master` 分支为 JDK 8 + Spring Boot 2.7`master-jdk17` 分支为 JDK 17/21 + Spring Boot 3.2
* 管理后台的电脑端: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 等
* 消息队列可使用 Event、Redis、RabbitMQ、Kafka、RocketMQ 等
* 权限认证使用 Spring Security & Token & Redis,支持多终端、多种用户的认证系统,支持 SSO 单点登录
* 支持加载动态权限菜单,按钮级别权限控制,Redis 缓存提升性能
* 支持 SaaS 多租户,可自定义每个租户的权限,提供透明化的多租户底层封装
* 工作流使用 Flowable,支持动态表单、在线设计流程、会签 / 或签、多种任务分配方式
* 高效率开发,使用代码生成器可以一键生成 Java、Vue 前后端代码、SQL 脚本、接口文档,支持单表、树表、主子表
* 实时通信,采用 Spring WebSocket 实现,内置 Token 身份校验,支持 WebSocket 集群
* 集成微信小程序、微信公众号、企业微信、钉钉等三方登陆,集成支付宝、微信等支付与退款
* 集成阿里云、腾讯云等短信渠道,集成 MinIO、阿里云、腾讯云、七牛云等云存储服务
* 集成报表设计器、大屏设计器,通过拖拽即可生成酷炫的报表与大屏
## 🐳 项目关系
![架构演进](/.image/common/yudao-roadmap.png)
三个项目的功能对比,可见社区共同整理的 [国产开源项目对比](https://www.yuque.com/xiatian-bsgny/lm0ec1/wqf8mn) 表格。
### 后端项目
| 项目 | Star | 简介 |
|-----------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
| [ruoyi-vue-pro](https://gitee.com/zhijiantianya/ruoyi-vue-pro) | [![Gitee star](https://gitee.com/zhijiantianya/ruoyi-vue-pro/badge/star.svg?theme=white)](https://gitee.com/zhijiantianya/ruoyi-vue-pro) [![GitHub stars](https://img.shields.io/github/stars/YunaiV/ruoyi-vue-pro.svg?style=social&label=Stars)](https://github.com/YunaiV/ruoyi-vue-pro) | 基于 Spring Boot 多模块架构 |
| [yudao-cloud](https://gitee.com/zhijiantianya/yudao-cloud) | [![Gitee star](https://gitee.com/zhijiantianya/yudao-cloud/badge/star.svg?theme=white)](https://gitee.com/zhijiantianya/yudao-cloud) [![GitHub stars](https://img.shields.io/github/stars/YunaiV/yudao-cloud.svg?style=social&label=Stars)](https://github.com/YunaiV/yudao-cloud) | 基于 Spring Cloud 微服务架构 |
| [Spring-Boot-Labs](https://gitee.com/yudaocode/SpringBoot-Labs) | [![Gitee star](https://gitee.com/yudaocode/SpringBoot-Labs/badge/star.svg?theme=white)](https://gitee.com/zhijiantianya/yudao-cloud) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/SpringBoot-Labs.svg?style=social&label=Stars)](https://github.com/yudaocode/SpringBoot-Labs) | 系统学习 Spring Boot & Cloud 专栏 |
### 前端项目
| 项目 | Star | 简介 |
|----------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------|
| [yudao-ui-admin-vue3](https://gitee.com/yudaocode/yudao-ui-admin-vue3) | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-admin-vue3/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-admin-vue3) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-admin-vue3.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-admin-vue3) | 基于 Vue3 + element-plus 实现的管理后台 |
| [yudao-ui-admin-vben](https://gitee.com/yudaocode/yudao-ui-admin-vben) | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-admin-vben/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-admin-vben) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-admin-vben.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-admin-vben) | 基于 Vue3 + vben(ant-design-vue) 实现的管理后台 |
| [yudao-mall-uniapp](https://gitee.com/yudaocode/yudao-mall-uniapp) | [![Gitee star](https://gitee.com/yudaocode/yudao-mall-uniapp/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-mall-uniapp) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-mall-uniapp.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-mall-uniapp) | 基于 uni-app 实现的商城小程序 |
| [yudao-ui-admin-vue2](https://gitee.com/yudaocode/yudao-ui-admin-vue2) | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-admin-vue2/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-admin-vue2) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-admin-vue2.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-admin-vue2) | 基于 Vue2 + element-ui 实现的管理后台 |
| [yudao-ui-admin-uniapp](https://gitee.com/yudaocode/yudao-ui-admin-uniapp) | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-admin-uniapp/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-admin-uniapp) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-admin-uniapp.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-admin-uniapp) | 基于 Vue2 + element-ui 实现的管理后台 |
| [yudao-ui-go-view](https://gitee.com/yudaocode/yudao-ui-go-view) | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-go-view/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-go-view) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-go-view.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-go-view) | 基于 Vue3 + naive-ui 实现的大屏报表 |
## 😎 开源协议
**为什么推荐使用本项目?**
① 本项目采用比 Apache 2.0 更宽松的 [MIT License](https://gitee.com/zhijiantianya/ruoyi-vue-pro/blob/master/LICENSE) 开源协议,个人与企业可 100% 免费使用,不用保留类作者、Copyright 信息。
② 代码全部开源,不会像其他项目一样,只开源部分代码,让你无法了解整个项目的架构设计。[国产开源项目对比](https://www.yuque.com/xiatian-bsgny/lm0ec1/wqf8mn)
![开源项目对比](/.image/common/project-vs.png)
③ 代码整洁、架构整洁,遵循《阿里巴巴 Java 开发手册》规范,代码注释详细,113770 行 Java 代码,42462 行代码注释。
## 🤝 项目外包
我们也是接外包滴,如果你有项目想要外包,可以微信联系【**Aix9975**】。
团队包含专业的项目经理、架构师、前端工程师、后端工程师、测试工程师、运维工程师,可以提供全流程的外包服务。
项目可以是商城、SCRM 系统、OA 系统、物流系统、ERP 系统、CMS 系统、HIS 系统、支付系统、IM 聊天、微信公众号、微信小程序等等。
## 🐼 内置功能
系统内置多种多种业务功能,可以用于快速你的业务系统:
![功能分层](/.image/common/ruoyi-vue-pro-biz.png)
* 通用模块(必选):系统功能、基础设施
* 通用模块(可选):工作流程、支付系统、数据报表、会员中心
* 业务系统(按需):ERP 系统、CRM 系统、商城系统、微信公众号、AI 大模型
> 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。
>
> * 额外新增的功能,我们使用 🚀 标记。
> * 重新实现的功能,我们使用 ⭐️ 标记。
🙂 所有功能,都通过 **单元测试** 保证高质量。
### 系统功能
| | 功能 | 描述 |
|-----|-------|---------------------------------|
| | 用户管理 | 用户是系统操作者,该功能主要完成系统用户配置 |
| ⭐️ | 在线用户 | 当前系统中活跃用户状态监控,支持手动踢下线 |
| | 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 |
| | 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等,本地缓存提供性能 |
| | 部门管理 | 配置系统组织机构(公司、部门、小组),树结构展现支持数据权限 |
| | 岗位管理 | 配置系统用户所属担任职务 |
| 🚀 | 租户管理 | 配置系统租户,支持 SaaS 场景下的多租户功能 |
| 🚀 | 租户套餐 | 配置租户套餐,自定每个租户的菜单、操作、按钮的权限 |
| | 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 |
| 🚀 | 短信管理 | 短信渠道、短息模板、短信日志,对接阿里云、腾讯云等主流短信平台 |
| 🚀 | 邮件管理 | 邮箱账号、邮件模版、邮件发送日志,支持所有邮件平台 |
| 🚀 | 站内信 | 系统内的消息通知,提供站内信模版、站内信消息 |
| 🚀 | 操作日志 | 系统正常操作日志记录和查询,集成 Swagger 生成日志内容 |
| ⭐️ | 登录日志 | 系统登录日志记录查询,包含登录异常 |
| 🚀 | 错误码管理 | 系统所有错误码的管理,可在线修改错误提示,无需重启服务 |
| | 通知公告 | 系统通知公告信息发布维护 |
| 🚀 | 敏感词 | 配置系统敏感词,支持标签分组 |
| 🚀 | 应用管理 | 管理 SSO 单点登录的应用,支持多种 OAuth2 授权方式 |
| 🚀 | 地区管理 | 展示省份、城市、区镇等城市信息,支持 IP 对应城市 |
![功能图](/.image/common/system-feature.png)
### 工作流程
![功能图](/.image/common/bpm-feature.png)
基于 Flowable 构建,可支持信创(国产)数据库,满足中国特色流程操作:
| BPMN 设计器 | 钉钉/飞书设计器 |
|------------------------------|--------------------------------|
| ![](/.image/工作流设计器-bpmn.jpg) | ![](/.image/工作流设计器-simple.jpg) |
> 历经头部企业生产验证,工作流引擎须标配仿钉钉/飞书 + BPMN 双设计器!!!
>
> 前者支持轻量配置简单流程,后者实现复杂场景深度编排
| 功能列表 | 功能描述 | 是否完成 |
|------------|-------------------------------------------------------------------------------------|------|
| SIMPLE 设计器 | 仿钉钉/飞书设计器,支持拖拽搭建表单流程,10 分钟快速完成审批流程配置 | ✅ |
| BPMN 设计器 | 基于 BPMN 标准开发,适配复杂业务场景,满足多层级审批及流程自动化需求 | ✅ |
| 会签 | 同一个审批节点设置多个人(如 A、B、C 三人,三人会同时收到待办任务),需全部同意之后,审批才可到下一审批节点 | ✅ |
| 或签 | 同一个审批节点设置多个人,任意一个人处理后,就能进入下一个节点 | ✅ |
| 依次审批 | (顺序会签)同一个审批节点设置多个人(如 A、B、C 三人),三人按顺序依次收到待办,即 A 先审批,A 提交后 B 才能审批,需全部同意之后,审批才可到下一审批节点 | ✅ |
| 抄送 | 将审批结果通知给抄送人,同一个审批默认排重,不重复抄送给同一人 | ✅ |
| 驳回 | (退回)将审批重置发送给某节点,重新审批。可驳回至发起人、上一节点、任意节点 | ✅ |
| 转办 | A 转给其 B 审批,B 审批后,进入下一节点 | ✅ |
| 委派 | A 转给其 B 审批,B 审批后,转给 A,A 继续审批后进入下一节点 | ✅ |
| 加签 | 允许当前审批人根据需要,自行增加当前节点的审批人,支持向前、向后加签 | ✅ |
| 减签 | (取消加签)在当前审批人操作之前,减少审批人 | ✅ |
| 撤销 | (取消流程)流程发起人,可以对流程进行撤销处理 | ✅ |
| 终止 | 系统管理员,在任意节点终止流程实例 | ✅ |
| 表单权限 | 支持拖拉拽配置表单,每个审批节点可配置只读、编辑、隐藏权限 | ✅ |
| 超时审批 | 配置超时审批时间,超时后自动触发审批通过、不通过、驳回等操作 | ✅ |
| 自动提醒 | 配置提醒时间,到达时间后自动触发短信、邮箱、站内信等通知提醒,支持自定义重复提醒频次 | ✅ |
| 父子流程 | 主流程设置子流程节点,子流程节点会自动触发子流程。子流程结束后,主流程才会执行(继续往下下执行),支持同步子流程、异步子流程 | ✅ |
| 条件分支 | (排它分支)用于在流程中实现决策,即根据条件选择一个分支执行 | ✅ |
| 并行分支 | 允许将流程分成多条分支,不进行条件判断,所有分支都会执行 | ✅ |
| 包容分支 | (条件分支 + 并行分支的结合体)允许基于条件选择多条分支执行,但如果没有任何一个分支满足条件,则可以选择默认分支 | ✅ |
| 路由分支 | 根据条件选择一个分支执行(重定向到指定配置节点),也可以选择默认分支执行(继续往下执行) | ✅ |
| 触发节点 | 执行到该节点,触发 HTTP 请求、HTTP 回调、更新数据、删除数据等 | ✅ |
| 延迟节点 | 执行到该节点,审批等待一段时间再执行,支持固定时长、固定日期等 | ✅ |
| 拓展设置 | 流程前置/后置通知,节点(任务)前置、后置通知,流程报表,自动审批去重,自定流程编号、标题、摘要,流程报表等 | ✅ |
### 支付系统
| | 功能 | 描述 |
|-----|------|---------------------------|
| 🚀 | 应用信息 | 配置商户的应用信息,对接支付宝、微信等多个支付渠道 |
| 🚀 | 支付订单 | 查看用户发起的支付宝、微信等的【支付】订单 |
| 🚀 | 退款订单 | 查看用户发起的支付宝、微信等的【退款】订单 |
| 🚀 | 回调通知 | 查看支付回调业务的【支付】【退款】的通知结果 |
| 🚀 | 接入示例 | 提供接入支付系统的【支付】【退款】的功能实战 |
### 基础设施
| | 功能 | 描述 |
|-----|-----------|----------------------------------------------|
| 🚀 | 代码生成 | 前后端代码的生成(Java、Vue、SQL、单元测试),支持 CRUD 下载 |
| 🚀 | 系统接口 | 基于 Swagger 自动生成相关的 RESTful API 接口文档 |
| 🚀 | 数据库文档 | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式 |
| | 表单构建 | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件 |
| 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 |
| ⭐️ | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 |
| 🚀 | 文件服务 | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、FTP、数据库等 |
| 🚀 | WebSocket | 提供 WebSocket 接入示例,支持一对一、一对多发送方式 |
| 🚀 | API 日志 | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题 |
| | MySQL 监控 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈 |
| | Redis 监控 | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理 |
| 🚀 | 消息队列 | 基于 Redis 实现消息队列,Stream 提供集群消费,Pub/Sub 提供广播消费 |
| 🚀 | Java 监控 | 基于 Spring Boot Admin 实现 Java 应用的监控 |
| 🚀 | 链路追踪 | 接入 SkyWalking 组件,实现链路追踪 |
| 🚀 | 日志中心 | 接入 SkyWalking 组件,实现日志中心 |
| 🚀 | 服务保障 | 基于 Redis 实现分布式锁、幂等、限流功能,满足高并发场景 |
| 🚀 | 日志服务 | 轻量级日志中心,查看远程服务器的日志 |
| 🚀 | 单元测试 | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等 |
![功能图](/.image/common/infra-feature.png)
### 数据报表
| | 功能 | 描述 |
|-----|-------|--------------------|
| 🚀 | 报表设计器 | 支持数据报表、图形报表、打印设计等 |
| 🚀 | 大屏设计器 | 拖拽生成数据大屏,内置几十种图表组件 |
### 微信公众号
| | 功能 | 描述 |
|-----|--------|-------------------------------|
| 🚀 | 账号管理 | 配置接入的微信公众号,可支持多个公众号 |
| 🚀 | 数据统计 | 统计公众号的用户增减、累计用户、消息概况、接口分析等数据 |
| 🚀 | 粉丝管理 | 查看已关注、取关的粉丝列表,可对粉丝进行同步、打标签等操作 |
| 🚀 | 消息管理 | 查看粉丝发送的消息列表,可主动回复粉丝消息 |
| 🚀 | 自动回复 | 自动回复粉丝发送的消息,支持关注回复、消息回复、关键字回复 |
| 🚀 | 标签管理 | 对公众号的标签进行创建、查询、修改、删除等操作 |
| 🚀 | 菜单管理 | 自定义公众号的菜单,也可以从公众号同步菜单 |
| 🚀 | 素材管理 | 管理公众号的图片、语音、视频等素材,支持在线播放语音、视频 |
| 🚀 | 图文草稿箱 | 新增常用的图文素材到草稿箱,可发布到公众号 |
| 🚀 | 图文发表记录 | 查看已发布成功的图文素材,支持删除操作 |
### 商城系统
演示地址:<https://doc.iocoder.cn/mall-preview/>
![功能图](/.image/common/mall-feature.png)
![功能图](/.image/common/mall-preview.png)
### 会员中心
| | 功能 | 描述 |
|-----|------|----------------------------------|
| 🚀 | 会员管理 | 会员是 C 端的消费者,该功能用于会员的搜索与管理 |
| 🚀 | 会员标签 | 对会员的标签进行创建、查询、修改、删除等操作 |
| 🚀 | 会员等级 | 对会员的等级、成长值进行管理,可用于订单折扣等会员权益 |
| 🚀 | 会员分组 | 对会员进行分组,用于用户画像、内容推送等运营手段 |
| 🚀 | 积分签到 | 回馈给签到、消费等行为的积分,会员可订单抵现、积分兑换等途径消耗 |
### ERP 系统
演示地址:<https://doc.iocoder.cn/erp-preview/>
![功能图](/.image/common/erp-feature.png)
### CRM 系统
演示地址:<https://doc.iocoder.cn/crm-preview/>
![功能图](/.image/common/crm-feature.png)
### AI 大模型
演示地址:<https://doc.iocoder.cn/ai-preview/>
![功能图](/.image/common/ai-feature.png)
![功能图](/.image/common/ai-preview.gif)
## 🐨 技术栈
### 模块
| 项目 | 说明 |
|-----------------------|--------------------|
| `yudao-dependencies` | Maven 依赖版本管理 |
| `yudao-framework` | Java 框架拓展 |
| `yudao-server` | 管理后台 + 用户 APP 的服务端 |
| `yudao-module-system` | 系统功能的 Module 模块 |
| `yudao-module-member` | 会员中心的 Module 模块 |
| `yudao-module-infra` | 基础设施的 Module 模块 |
| `yudao-module-bpm` | 工作流程的 Module 模块 |
| `yudao-module-pay` | 支付系统的 Module 模块 |
| `yudao-module-mall` | 商城系统的 Module 模块 |
| `yudao-module-erp` | ERP 系统的 Module 模块 |
| `yudao-module-crm` | CRM 系统的 Module 模块 |
| `yudao-module-ai` | AI 大模型的 Module 模块 |
| `yudao-module-mp` | 微信公众号的 Module 模块 |
| `yudao-module-report` | 大屏报表 Module 模块 |
### 框架
| 框架 | 说明 | 版本 | 学习指南 |
|---------------------------------------------------------------------------------------------|------------------|----------------|----------------------------------------------------------------|
| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.7.18 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
| [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 / 8.0+ | |
| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.23 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.7 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.6.1 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
| [Redis](https://redis.io/) | key-value 数据库 | 5.0 / 6.0 /7.0 | |
| [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.32.0 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) |
| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.24 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |
| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.7.11 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.5 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |
| [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 6.8.0 | [文档](https://doc.iocoder.cn/bpm/) |
| [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) |
| [Springdoc](https://springdoc.org/) | Swagger 文档 | 1.7.0 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) |
| [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 8.12.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) |
| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 2.7.10 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) |
| [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.13.5 | |
| [MapStruct](https://mapstruct.org/) | Java Bean 转换 | 1.6.3 | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao) |
| [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码 | 1.18.34 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) |
| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.8.2 | - |
| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 4.8.0 | - |
## 🐷 演示图
### 系统功能
| 模块 | biu | biu | biu |
|----------|-----------------------------|---------------------------|--------------------------|
| 登录 & 首页 | ![登录](/.image/登录.jpg) | ![首页](/.image/首页.jpg) | ![个人中心](/.image/个人中心.jpg) |
| 用户 & 应用 | ![用户管理](/.image/用户管理.jpg) | ![令牌管理](/.image/令牌管理.jpg) | ![应用管理](/.image/应用管理.jpg) |
| 租户 & 套餐 | ![租户管理](/.image/租户管理.jpg) | ![租户套餐](/.image/租户套餐.png) | - |
| 部门 & 岗位 | ![部门管理](/.image/部门管理.jpg) | ![岗位管理](/.image/岗位管理.jpg) | - |
| 菜单 & 角色 | ![菜单管理](/.image/菜单管理.jpg) | ![角色管理](/.image/角色管理.jpg) | - |
| 审计日志 | ![操作日志](/.image/操作日志.jpg) | ![登录日志](/.image/登录日志.jpg) | - |
| 短信 | ![短信渠道](/.image/短信渠道.jpg) | ![短信模板](/.image/短信模板.jpg) | ![短信日志](/.image/短信日志.jpg) |
| 字典 & 敏感词 | ![字典类型](/.image/字典类型.jpg) | ![字典数据](/.image/字典数据.jpg) | ![敏感词](/.image/敏感词.jpg) |
| 错误码 & 通知 | ![错误码管理](/.image/错误码管理.jpg) | ![通知公告](/.image/通知公告.jpg) | - |
### 工作流程
| 模块 | biu | biu | biu |
|---------|---------------------------------|---------------------------------|---------------------------------|
| 流程模型 | ![流程模型-列表](/.image/流程模型-列表.jpg) | ![流程模型-设计](/.image/流程模型-设计.jpg) | ![流程模型-定义](/.image/流程模型-定义.jpg) |
| 表单 & 分组 | ![流程表单](/.image/流程表单.jpg) | ![用户分组](/.image/用户分组.jpg) | - |
| 我的流程 | ![我的流程-列表](/.image/我的流程-列表.jpg) | ![我的流程-发起](/.image/我的流程-发起.jpg) | ![我的流程-详情](/.image/我的流程-详情.jpg) |
| 待办 & 已办 | ![任务列表-审批](/.image/任务列表-审批.jpg) | ![任务列表-待办](/.image/任务列表-待办.jpg) | ![任务列表-已办](/.image/任务列表-已办.jpg) |
| OA 请假 | ![OA请假-列表](/.image/OA请假-列表.jpg) | ![OA请假-发起](/.image/OA请假-发起.jpg) | ![OA请假-详情](/.image/OA请假-详情.jpg) |
### 基础设施
| 模块 | biu | biu | biu |
|---------------|-------------------------------|-----------------------------|---------------------------|
| 代码生成 | ![代码生成](/.image/代码生成.jpg) | ![生成效果](/.image/生成效果.jpg) | - |
| 文档 | ![系统接口](/.image/系统接口.jpg) | ![数据库文档](/.image/数据库文档.jpg) | - |
| 文件 & 配置 | ![文件配置](/.image/文件配置.jpg) | ![文件管理](/.image/文件管理2.jpg) | ![配置管理](/.image/配置管理.jpg) |
| 定时任务 | ![定时任务](/.image/定时任务.jpg) | ![任务日志](/.image/任务日志.jpg) | - |
| API 日志 | ![访问日志](/.image/访问日志.jpg) | ![错误日志](/.image/错误日志.jpg) | - |
| MySQL & Redis | ![MySQL](/.image/MySQL.jpg) | ![Redis](/.image/Redis.jpg) | - |
| 监控平台 | ![Java监控](/.image/Java监控.jpg) | ![链路追踪](/.image/链路追踪.jpg) | ![日志中心](/.image/日志中心.jpg) |
### 支付系统
| 模块 | biu | biu | biu |
|---------|---------------------------|---------------------------------|---------------------------------|
| 商家 & 应用 | ![商户信息](/.image/商户信息.jpg) | ![应用信息-列表](/.image/应用信息-列表.jpg) | ![应用信息-编辑](/.image/应用信息-编辑.jpg) |
| 支付 & 退款 | ![支付订单](/.image/支付订单.jpg) | ![退款订单](/.image/退款订单.jpg) | --- |
### 数据报表
| 模块 | biu | biu | biu |
|-------|---------------------------------|---------------------------------|---------------------------------------|
| 报表设计器 | ![数据报表](/.image/报表设计器-数据报表.jpg) | ![图形报表](/.image/报表设计器-图形报表.jpg) | ![报表设计器-打印设计](/.image/报表设计器-打印设计.jpg) |
| 大屏设计器 | ![大屏列表](/.image/大屏设计器-列表.jpg) | ![大屏预览](/.image/大屏设计器-预览.jpg) | ![大屏编辑](/.image/大屏设计器-编辑.jpg) |
### 移动端(管理后台)
| biu | biu | biu |
|----------------------------------|----------------------------------|----------------------------------|
| ![](/.image/admin-uniapp/01.png) | ![](/.image/admin-uniapp/02.png) | ![](/.image/admin-uniapp/03.png) |
| ![](/.image/admin-uniapp/04.png) | ![](/.image/admin-uniapp/05.png) | ![](/.image/admin-uniapp/06.png) |
| ![](/.image/admin-uniapp/07.png) | ![](/.image/admin-uniapp/08.png) | ![](/.image/admin-uniapp/09.png) |
目前已经实现登录、我的、工作台、编辑资料、头像修改、密码修改、常见问题、关于我们等基础功能。
+4
View File
@@ -0,0 +1,4 @@
config.stopBubbling = true
lombok.tostring.callsuper=CALL
lombok.equalsandhashcode.callsuper=CALL
lombok.accessors.chain=true
+175
View File
@@ -0,0 +1,175 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao</artifactId>
<version>${revision}</version>
<packaging>pom</packaging>
<modules>
<module>yudao-dependencies</module>
<module>yudao-framework</module>
<!-- Server 主项目 -->
<module>yudao-server</module>
<!-- 各种 module 拓展 -->
<module>yudao-module-system</module>
<module>yudao-module-infra</module>
<module>yudao-module-member</module>
<!-- <module>yudao-module-bpm</module>-->
<!-- <module>yudao-module-report</module>-->
<!-- <module>yudao-module-mp</module>-->
<module>yudao-module-pay</module>
<module>yudao-module-mall</module>
<module>yudao-module-digital</module>
<!-- <module>yudao-module-crm</module>-->
<!-- <module>yudao-module-erp</module>-->
<!-- <module>yudao-module-iot</module>-->
<!-- AI 大模型的开启,请参考 https://doc.iocoder.cn/ai/build/ 文档,对 JDK 版本要要求! -->
<!-- <module>yudao-module-ai</module>-->
</modules>
<name>${project.artifactId}</name>
<description>芋道项目基础脚手架</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties>
<revision>2025.09-jdk8-SNAPSHOT</revision>
<!-- Maven 相关 -->
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<maven-surefire-plugin.version>3.5.3</maven-surefire-plugin.version>
<maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
<flatten-maven-plugin.version>1.7.2</flatten-maven-plugin.version>
<!-- maven-surefire-plugin 暂时无法通过 bom 的依赖读取(兼容老版本 IDEA 2024 及以前版本) -->
<lombok.version>1.18.38</lombok.version>
<spring.boot.version>2.7.18</spring.boot.version>
<mapstruct.version>1.6.3</mapstruct.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-dependencies</artifactId>
<version>${revision}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<plugins>
<!-- maven-surefire-plugin 插件,用于运行单元测试。 -->
<!-- 注意,需要使用 3.0.X+,因为要支持 Junit 5 版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
</plugin>
<!-- maven-compiler-plugin 插件,解决 spring-boot-configuration-processor + Lombok + MapStruct 组合 -->
<!-- https://stackoverflow.com/questions/33483697/re-run-spring-boot-configuration-annotation-processor-to-update-generated-metada -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring.boot.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<!-- 确保 Lombok 生成的 getter/setter 方法能被 MapStruct 正确识别,
避免出现 No property named “xxx" exists 的编译错误 -->
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<!-- 统一 revision 版本 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>${flatten-maven-plugin.version}</version>
<configuration>
<flattenMode>oss</flattenMode>
<updatePomFile>true</updatePomFile>
</configuration>
<executions>
<execution>
<goals>
<goal>flatten</goal>
</goals>
<id>flatten</id>
<phase>process-resources</phase>
</execution>
<execution>
<goals>
<goal>clean</goal>
</goals>
<id>flatten.clean</id>
<phase>clean</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- 使用 huawei / aliyun 的 Maven 源,提升下载速度 -->
<repositories>
<repository>
<id>huaweicloud</id>
<name>huawei</name>
<url>https://mirrors.huaweicloud.com/repository/maven/</url>
</repository>
<repository>
<id>aliyunmaven</id>
<name>aliyun</name>
<url>https://maven.aliyun.com/repository/public</url>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
</project>
+49
View File
@@ -0,0 +1,49 @@
# Docker Build & Up
目标: 快速部署体验系统,帮助了解系统之间的依赖关系。
依赖:docker compose v2,删除`name: yudao-system`,降低`version`版本为`3.3`以下,支持`docker-compose`
## 功能文件列表
```text
.
├── Docker-HOWTO.md
├── docker-compose.yml
├── docker.env <-- 提供docker-compose环境变量配置
├── yudao-server
│ └── Dockerfile
└── yudao-ui-admin
├── .dockerignore
├── Dockerfile
└── nginx.conf <-- 提供基础配置,gzip压缩、api转发
```
## 构建 jar 包
```shell
# 创建maven缓存volume
docker volume create --name yudao-maven-repo
docker run -it --rm --name yudao-maven \
-v yudao-maven-repo:/root/.m2 \
-v $PWD:/usr/src/mymaven \
-w /usr/src/mymaven \
maven mvn clean install package '-Dmaven.test.skip=true'
```
## 构建启动服务
```shell
docker compose --env-file docker.env up -d
```
首次运行会自动构建容器。可以通过`docker compose build [service]`来手动构建所有或某个docker镜像
`--env-file docker.env`为可选参数,只是展示了通过`.env`文件配置容器启动的环境变量,`docker-compose.yml`本身已经提供足够的默认参数来正常运行系统。
## 服务器的宿主机端口映射
- admin ui: http://localhost:8080
- api server: http://localhost:48080
- mysql: root/123456, port: 3306
- redis: port: 6379
+84
View File
@@ -0,0 +1,84 @@
version: "3.4"
name: yudao-system
services:
mysql:
container_name: yudao-mysql
image: mysql:8
restart: unless-stopped
tty: true
ports:
- "3306:3306"
environment:
MYSQL_DATABASE: ${MYSQL_DATABASE:-ruoyi-vue-pro}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-123456}
volumes:
- mysql:/var/lib/mysql/
- ../../sql/mysql/ruoyi-vue-pro.sql:/docker-entrypoint-initdb.d/ruoyi-vue-pro.sql:ro
redis:
container_name: yudao-redis
image: redis:6-alpine
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis:/data
server:
container_name: yudao-server
build:
context: ./yudao-server/
image: yudao-server
restart: unless-stopped
ports:
- "48080:48080"
environment:
# https://github.com/polovyivan/docker-pass-configs-to-container
SPRING_PROFILES_ACTIVE: local
JAVA_OPTS:
${JAVA_OPTS:-
-Xms512m
-Xmx512m
-Djava.security.egd=file:/dev/./urandom
}
ARGS:
--spring.datasource.dynamic.datasource.master.url=${MASTER_DATASOURCE_URL:-jdbc:mysql://yudao-mysql:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true}
--spring.datasource.dynamic.datasource.master.username=${MASTER_DATASOURCE_USERNAME:-root}
--spring.datasource.dynamic.datasource.master.password=${MASTER_DATASOURCE_PASSWORD:-123456}
--spring.datasource.dynamic.datasource.slave.url=${SLAVE_DATASOURCE_URL:-jdbc:mysql://yudao-mysql:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true}
--spring.datasource.dynamic.datasource.slave.username=${SLAVE_DATASOURCE_USERNAME:-root}
--spring.datasource.dynamic.datasource.slave.password=${SLAVE_DATASOURCE_PASSWORD:-123456}
--spring.redis.host=${REDIS_HOST:-yudao-redis}
depends_on:
- mysql
- redis
admin:
container_name: yudao-admin
build:
context: ./yudao-ui-admin
args:
NODE_ENV:
ENV=${NODE_ENV:-production}
PUBLIC_PATH=${PUBLIC_PATH:-/}
VUE_APP_TITLE=${VUE_APP_TITLE:-芋道管理系统}
VUE_APP_BASE_API=${VUE_APP_BASE_API:-/prod-api}
VUE_APP_APP_NAME=${VUE_APP_APP_NAME:-/}
VUE_APP_TENANT_ENABLE=${VUE_APP_TENANT_ENABLE:-true}
VUE_APP_CAPTCHA_ENABLE=${VUE_APP_CAPTCHA_ENABLE:-true}
VUE_APP_DOC_ENABLE=${VUE_APP_DOC_ENABLE:-true}
VUE_APP_BAIDU_CODE=${VUE_APP_BAIDU_CODE:-fadc1bd5db1a1d6f581df60a1807f8ab}
image: yudao-admin
restart: unless-stopped
ports:
- "8080:80"
depends_on:
- server
volumes:
mysql:
driver: local
redis:
driver: local
+25
View File
@@ -0,0 +1,25 @@
## mysql
MYSQL_DATABASE=ruoyi-vue-pro
MYSQL_ROOT_PASSWORD=123456
## server
JAVA_OPTS=-Xms512m -Xmx512m -Djava.security.egd=file:/dev/./urandom
MASTER_DATASOURCE_URL=jdbc:mysql://yudao-mysql:3306/${MYSQL_DATABASE}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
MASTER_DATASOURCE_USERNAME=root
MASTER_DATASOURCE_PASSWORD=${MYSQL_ROOT_PASSWORD}
SLAVE_DATASOURCE_URL=${MASTER_DATASOURCE_URL}
SLAVE_DATASOURCE_USERNAME=${MASTER_DATASOURCE_USERNAME}
SLAVE_DATASOURCE_PASSWORD=${MASTER_DATASOURCE_PASSWORD}
REDIS_HOST=yudao-redis
## admin
NODE_ENV=production
PUBLIC_PATH=/
VUE_APP_TITLE=芋道管理系统
VUE_APP_BASE_API=/prod-api
VUE_APP_APP_NAME=/
VUE_APP_TENANT_ENABLE=true
VUE_APP_CAPTCHA_ENABLE=true
VUE_APP_DOC_ENABLE=true
VUE_APP_BAIDU_CODE=fadc1bd5db1a1d6f581df60a1807f8ab
+20
View File
@@ -0,0 +1,20 @@
{
"local": {
"baseUrl": "http://127.0.0.1:48080/admin-api",
"token": "test1",
"adminTenantId": "1",
"appApi": "http://127.0.0.1:48080/app-api",
"appToken": "test247",
"appTenantId": "1"
},
"gateway": {
"baseUrl": "http://127.0.0.1:8888/admin-api",
"token": "test1",
"adminTenantId": "1",
"appApi": "http://127.0.0.1:8888/app-api",
"appToken": "test1",
"appTenantId": "1"
}
}
+60
View File
@@ -0,0 +1,60 @@
#!groovy
pipeline {
agent any
parameters {
string(name: 'TAG_NAME', defaultValue: '', description: '')
}
environment {
// DockerHub 凭证 ID(登录您的 DockerHub)
DOCKER_CREDENTIAL_ID = 'dockerhub-id'
// GitHub 凭证 ID (推送 tag 到 GitHub 仓库)
GITHUB_CREDENTIAL_ID = 'github-id'
// kubeconfig 凭证 ID (访问接入正在运行的 Kubernetes 集群)
KUBECONFIG_CREDENTIAL_ID = 'demo-kubeconfig'
// 镜像的推送
REGISTRY = 'docker.io'
// DockerHub 账号名
DOCKERHUB_NAMESPACE = 'docker_username'
// GitHub 账号名
GITHUB_ACCOUNT = 'https://gitee.com/zhijiantianya/ruoyi-vue-pro'
// 应用名称
APP_NAME = 'yudao-server'
// 应用部署路径
APP_DEPLOY_BASE_DIR = '/media/pi/KINGTON/data/work/projects/'
}
stages {
stage('检出') {
steps {
git url: "https://gitee.com/will-we/ruoyi-vue-pro.git",
branch: "devops"
}
}
stage('构建') {
steps {
// TODO 解决多环境链接、密码不同配置临时方案
sh 'if [ ! -d "' + "${env.HOME}" + '/resources" ];then\n' +
' echo "配置文件不存在无需修改"\n' +
'else\n' +
' cp -rf ' + "${env.HOME}" + '/resources/*.yaml ' + "${env.APP_NAME}" + '/src/main/resources\n' +
' echo "配置文件替换"\n' +
'fi'
sh 'mvn clean package -Dmaven.test.skip=true'
}
}
stage('部署') {
steps {
sh 'cp -f ' + ' bin/deploy.sh ' + "${env.APP_DEPLOY_BASE_DIR}" + "${env.APP_NAME}"
sh 'cp -f ' + "${env.APP_NAME}" + '/target/*.jar ' + "${env.APP_DEPLOY_BASE_DIR}" + "${env.APP_NAME}" +'/build/'
archiveArtifacts "${env.APP_NAME}" + '/target/*.jar'
sh 'chmod +x ' + "${env.APP_DEPLOY_BASE_DIR}" + "${env.APP_NAME}" + '/deploy.sh'
sh 'bash ' + "${env.APP_DEPLOY_BASE_DIR}" + "${env.APP_NAME}" + '/deploy.sh'
}
}
}
}
+160
View File
@@ -0,0 +1,160 @@
#!/bin/bash
set -e
DATE=$(date +%Y%m%d%H%M)
# 基础路径
BASE_PATH=/work/projects/yudao-server
# 编译后 jar 的地址。部署时,Jenkins 会上传 jar 包到该目录下
SOURCE_PATH=$BASE_PATH/build
# 服务名称。同时约定部署服务的 jar 包名字也为它。
SERVER_NAME=yudao-server
# 环境
PROFILES_ACTIVE=development
# 健康检查 URL
HEALTH_CHECK_URL=http://127.0.0.1:48080/actuator/health/
# heapError 存放路径
HEAP_ERROR_PATH=$BASE_PATH/heapError
# JVM 参数
JAVA_OPS="-Xms512m -Xmx512m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$HEAP_ERROR_PATH"
# SkyWalking Agent 配置
#export SW_AGENT_NAME=$SERVER_NAME
#export SW_AGENT_COLLECTOR_BACKEND_SERVICES=192.168.0.84:11800
#export SW_GRPC_LOG_SERVER_HOST=192.168.0.84
#export SW_AGENT_TRACE_IGNORE_PATH="Redisson/PING,/actuator/**,/admin/**"
#export JAVA_AGENT=-javaagent:/work/skywalking/apache-skywalking-apm-bin/agent/skywalking-agent.jar
# 备份
function backup() {
# 如果不存在,则无需备份
if [ ! -f "$BASE_PATH/$SERVER_NAME.jar" ]; then
echo "[backup] $BASE_PATH/$SERVER_NAME.jar 不存在,跳过备份"
# 如果存在,则备份到 backup 目录下,使用时间作为后缀
else
echo "[backup] 开始备份 $SERVER_NAME ..."
cp $BASE_PATH/$SERVER_NAME.jar $BASE_PATH/backup/$SERVER_NAME-$DATE.jar
echo "[backup] 备份 $SERVER_NAME 完成"
fi
}
# 最新构建代码 移动到项目环境
function transfer() {
echo "[transfer] 开始转移 $SERVER_NAME.jar"
# 删除原 jar 包
if [ ! -f "$BASE_PATH/$SERVER_NAME.jar" ]; then
echo "[transfer] $BASE_PATH/$SERVER_NAME.jar 不存在,跳过删除"
else
echo "[transfer] 移除 $BASE_PATH/$SERVER_NAME.jar 完成"
rm $BASE_PATH/$SERVER_NAME.jar
fi
# 复制新 jar 包
echo "[transfer] 从 $SOURCE_PATH 中获取 $SERVER_NAME.jar 并迁移至 $BASE_PATH ...."
cp $SOURCE_PATH/$SERVER_NAME.jar $BASE_PATH
echo "[transfer] 转移 $SERVER_NAME.jar 完成"
}
# 停止:优雅关闭之前已经启动的服务
function stop() {
echo "[stop] 开始停止 $BASE_PATH/$SERVER_NAME"
PID=$(ps -ef | grep $BASE_PATH/$SERVER_NAME | grep -v "grep" | awk '{print $2}')
# 如果 Java 服务启动中,则进行关闭
if [ -n "$PID" ]; then
# 正常关闭
echo "[stop] $BASE_PATH/$SERVER_NAME 运行中,开始 kill [$PID]"
kill -15 $PID
# 等待最大 120 秒,直到关闭完成。
for ((i = 0; i < 120; i++))
do
sleep 1
PID=$(ps -ef | grep $BASE_PATH/$SERVER_NAME | grep -v "grep" | awk '{print $2}')
if [ -n "$PID" ]; then
echo -e ".\c"
else
echo "[stop] 停止 $BASE_PATH/$SERVER_NAME 成功"
break
fi
done
# 如果正常关闭失败,那么进行强制 kill -9 进行关闭
if [ -n "$PID" ]; then
echo "[stop] $BASE_PATH/$SERVER_NAME 失败,强制 kill -9 $PID"
kill -9 $PID
fi
# 如果 Java 服务未启动,则无需关闭
else
echo "[stop] $BASE_PATH/$SERVER_NAME 未启动,无需停止"
fi
}
# 启动:启动后端项目
function start() {
# 开启启动前,打印启动参数
echo "[start] 开始启动 $BASE_PATH/$SERVER_NAME"
echo "[start] JAVA_OPS: $JAVA_OPS"
echo "[start] JAVA_AGENT: $JAVA_AGENT"
echo "[start] PROFILES: $PROFILES_ACTIVE"
# 开始启动
BUILD_ID=dontKillMe nohup java -server $JAVA_OPS $JAVA_AGENT -jar $BASE_PATH/$SERVER_NAME.jar --spring.profiles.active=$PROFILES_ACTIVE &
echo "[start] 启动 $BASE_PATH/$SERVER_NAME 完成"
}
# 健康检查:自动判断后端项目是否正常启动
function healthCheck() {
# 如果配置健康检查,则进行健康检查
if [ -n "$HEALTH_CHECK_URL" ]; then
# 健康检查最大 120 秒,直到健康检查通过
echo "[healthCheck] 开始通过 $HEALTH_CHECK_URL 地址,进行健康检查";
for ((i = 0; i < 120; i++))
do
# 请求健康检查地址,只获取状态码。
result=`curl -I -m 10 -o /dev/null -s -w %{http_code} $HEALTH_CHECK_URL || echo "000"`
# 如果状态码为 200,则说明健康检查通过
if [ "$result" == "200" ]; then
echo "[healthCheck] 健康检查通过";
break
# 如果状态码非 200,则说明未通过。sleep 1 秒后,继续重试
else
echo -e ".\c"
sleep 1
fi
done
# 健康检查未通过,则异常退出 shell 脚本,不继续部署。
if [ ! "$result" == "200" ]; then
echo "[healthCheck] 健康检查不通过,可能部署失败。查看日志,自行判断是否启动成功";
tail -n 10 nohup.out
exit 1;
# 健康检查通过,打印最后 10 行日志,可能部署的人想看下日志。
else
tail -n 10 nohup.out
fi
# 如果未配置健康检查,则 sleep 120 秒,人工看日志是否部署成功。
else
echo "[healthCheck] HEALTH_CHECK_URL 未配置,开始 sleep 120 秒";
sleep 120
echo "[healthCheck] sleep 120 秒完成,查看日志,自行判断是否启动成功";
tail -n 50 nohup.out
fi
}
# 部署
function deploy() {
cd $BASE_PATH
# 备份原 jar
backup
# 停止 Java 服务
stop
# 部署新 jar
transfer
# 启动 Java 服务
start
# 健康检查
healthCheck
}
deploy
+3
View File
@@ -0,0 +1,3 @@
暂未适配 IBM DB2 数据库,如果你有需要,可以微信联系 wangwenbin-server 一起建设。
你需要把表结构与数据导入到 DM 数据库,我来测试与适配代码。
@@ -0,0 +1,598 @@
package liquibase.database.core;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import liquibase.CatalogAndSchema;
import liquibase.Scope;
import liquibase.database.AbstractJdbcDatabase;
import liquibase.database.DatabaseConnection;
import liquibase.database.OfflineConnection;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.DatabaseException;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.exception.ValidationErrors;
import liquibase.executor.ExecutorService;
import liquibase.statement.DatabaseFunction;
import liquibase.statement.SequenceCurrentValueFunction;
import liquibase.statement.SequenceNextValueFunction;
import liquibase.statement.core.RawCallStatement;
import liquibase.statement.core.RawSqlStatement;
import liquibase.structure.DatabaseObject;
import liquibase.structure.core.Catalog;
import liquibase.structure.core.Index;
import liquibase.structure.core.PrimaryKey;
import liquibase.structure.core.Schema;
import liquibase.util.JdbcUtils;
import liquibase.util.StringUtil;
public class DmDatabase extends AbstractJdbcDatabase {
private static final String PRODUCT_NAME = "DM DBMS";
@Override
protected String getDefaultDatabaseProductName() {
return PRODUCT_NAME;
}
/**
* Is this AbstractDatabase subclass the correct one to use for the given connection.
*
* @param conn
*/
@Override
public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
return PRODUCT_NAME.equalsIgnoreCase(conn.getDatabaseProductName());
}
/**
* If this database understands the given url, return the default driver class name. Otherwise return null.
*
* @param url
*/
@Override
public String getDefaultDriver(String url) {
if(url.startsWith("jdbc:dm")) {
return "dm.jdbc.driver.DmDriver";
}
return null;
}
/**
* Returns an all-lower-case short name of the product. Used for end-user selecting of database type
* such as the DBMS precondition.
*/
@Override
public String getShortName() {
return "dm";
}
@Override
public Integer getDefaultPort() {
return 5236;
}
/**
* Returns whether this database support initially deferrable columns.
*/
@Override
public boolean supportsInitiallyDeferrableColumns() {
return true;
}
@Override
public boolean supportsTablespaces() {
return true;
}
@Override
public int getPriority() {
return PRIORITY_DEFAULT;
}
private static final Pattern PROXY_USER = Pattern.compile(".*(?:thin|oci)\\:(.+)/@.*");
protected final int SHORT_IDENTIFIERS_LENGTH = 30;
protected final int LONG_IDENTIFIERS_LEGNTH = 128;
public static final int ORACLE_12C_MAJOR_VERSION = 12;
private Set<String> reservedWords = new HashSet<>();
private Set<String> userDefinedTypes;
private Map<String, String> savedSessionNlsSettings;
private Boolean canAccessDbaRecycleBin;
private Integer databaseMajorVersion;
private Integer databaseMinorVersion;
/**
* Default constructor for an object that represents the Oracle Database DBMS.
*/
public DmDatabase() {
super.unquotedObjectsAreUppercased = true;
//noinspection HardCodedStringLiteral
super.setCurrentDateTimeFunction("SYSTIMESTAMP");
// Setting list of Oracle's native functions
//noinspection HardCodedStringLiteral
dateFunctions.add(new DatabaseFunction("SYSDATE"));
//noinspection HardCodedStringLiteral
dateFunctions.add(new DatabaseFunction("SYSTIMESTAMP"));
//noinspection HardCodedStringLiteral
dateFunctions.add(new DatabaseFunction("CURRENT_TIMESTAMP"));
//noinspection HardCodedStringLiteral
super.sequenceNextValueFunction = "%s.nextval";
//noinspection HardCodedStringLiteral
super.sequenceCurrentValueFunction = "%s.currval";
}
private void tryProxySession(final String url, final Connection con) {
Matcher m = PROXY_USER.matcher(url);
if (m.matches()) {
Properties props = new Properties();
props.put("PROXY_USER_NAME", m.group(1));
try {
Method method = con.getClass().getMethod("openProxySession", int.class, Properties.class);
method.setAccessible(true);
method.invoke(con, 1, props);
} catch (Exception e) {
Scope.getCurrentScope().getLog(getClass()).info("Could not open proxy session on OracleDatabase: " + e.getCause().getMessage());
}
}
}
@Override
public int getDatabaseMajorVersion() throws DatabaseException {
if (databaseMajorVersion == null) {
return super.getDatabaseMajorVersion();
} else {
return databaseMajorVersion;
}
}
@Override
public int getDatabaseMinorVersion() throws DatabaseException {
if (databaseMinorVersion == null) {
return super.getDatabaseMinorVersion();
} else {
return databaseMinorVersion;
}
}
@Override
public String getJdbcCatalogName(CatalogAndSchema schema) {
return null;
}
@Override
public String getJdbcSchemaName(CatalogAndSchema schema) {
return correctObjectName((schema.getCatalogName() == null) ? schema.getSchemaName() : schema.getCatalogName(), Schema.class);
}
@Override
protected String getAutoIncrementClause(final String generationType, final Boolean defaultOnNull) {
if (StringUtil.isEmpty(generationType)) {
return super.getAutoIncrementClause();
}
String autoIncrementClause = "GENERATED %s AS IDENTITY"; // %s -- [ ALWAYS | BY DEFAULT [ ON NULL ] ]
String generationStrategy = generationType;
if (Boolean.TRUE.equals(defaultOnNull) && generationType.toUpperCase().equals("BY DEFAULT")) {
generationStrategy += " ON NULL";
}
return String.format(autoIncrementClause, generationStrategy);
}
@Override
public String generatePrimaryKeyName(String tableName) {
if (tableName.length() > 27) {
//noinspection HardCodedStringLiteral
return "PK_" + tableName.toUpperCase(Locale.US).substring(0, 27);
} else {
//noinspection HardCodedStringLiteral
return "PK_" + tableName.toUpperCase(Locale.US);
}
}
@Override
public boolean isReservedWord(String objectName) {
return reservedWords.contains(objectName.toUpperCase());
}
@Override
public boolean supportsSequences() {
return true;
}
/**
* Oracle supports catalogs in liquibase terms
*
* @return false
*/
@Override
public boolean supportsSchemas() {
return false;
}
@Override
protected String getConnectionCatalogName() throws DatabaseException {
if (getConnection() instanceof OfflineConnection) {
return getConnection().getCatalog();
}
try {
//noinspection HardCodedStringLiteral
return Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForObject(new RawCallStatement("select sys_context( 'userenv', 'current_schema' ) from dual"), String.class);
} catch (Exception e) {
//noinspection HardCodedStringLiteral
Scope.getCurrentScope().getLog(getClass()).info("Error getting default schema", e);
}
return null;
}
@Override
public String getDefaultCatalogName() {//NOPMD
return (super.getDefaultCatalogName() == null) ? null : super.getDefaultCatalogName().toUpperCase(Locale.US);
}
/**
* <p>Returns an Oracle date literal with the same value as a string formatted using ISO 8601.</p>
*
* <p>Convert an ISO8601 date string to one of the following results:
* to_date('1995-05-23', 'YYYY-MM-DD')
* to_date('1995-05-23 09:23:59', 'YYYY-MM-DD HH24:MI:SS')</p>
* <p>
* Implementation restriction:<br>
* Currently, only the following subsets of ISO8601 are supported:<br>
* <ul>
* <li>YYYY-MM-DD</li>
* <li>YYYY-MM-DDThh:mm:ss</li>
* </ul>
*/
@Override
public String getDateLiteral(String isoDate) {
String normalLiteral = super.getDateLiteral(isoDate);
if (isDateOnly(isoDate)) {
return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD')";
} else if (isTimeOnly(isoDate)) {
return "TO_DATE(" + normalLiteral + ", 'HH24:MI:SS')";
} else if (isTimestamp(isoDate)) {
return "TO_TIMESTAMP(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS.FF')";
} else if (isDateTime(isoDate)) {
int seppos = normalLiteral.lastIndexOf('.');
if (seppos != -1) {
normalLiteral = normalLiteral.substring(0, seppos) + "'";
}
return "TO_DATE(" + normalLiteral + ", 'YYYY-MM-DD HH24:MI:SS')";
}
return "UNSUPPORTED:" + isoDate;
}
@Override
public boolean isSystemObject(DatabaseObject example) {
if (example == null) {
return false;
}
if (this.isLiquibaseObject(example)) {
return false;
}
if (example instanceof Schema) {
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
if ("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName())) {
return true;
}
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
if ("SYSTEM".equals(example.getSchema().getCatalogName()) || "SYS".equals(example.getSchema().getCatalogName()) || "CTXSYS".equals(example.getSchema().getCatalogName()) || "XDB".equals(example.getSchema().getCatalogName())) {
return true;
}
} else if (isSystemObject(example.getSchema())) {
return true;
}
if (example instanceof Catalog) {
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
if (("SYSTEM".equals(example.getName()) || "SYS".equals(example.getName()) || "CTXSYS".equals(example.getName()) || "XDB".equals(example.getName()))) {
return true;
}
} else if (example.getName() != null) {
//noinspection HardCodedStringLiteral
if (example.getName().startsWith("BIN$")) { //oracle deleted table
boolean filteredInOriginalQuery = this.canAccessDbaRecycleBin();
if (!filteredInOriginalQuery) {
filteredInOriginalQuery = StringUtil.trimToEmpty(example.getSchema().getName()).equalsIgnoreCase(this.getConnection().getConnectionUserName());
}
if (filteredInOriginalQuery) {
return !((example instanceof PrimaryKey) || (example instanceof Index) || (example instanceof
liquibase.statement.UniqueConstraint));
} else {
return true;
}
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("AQ$")) { //oracle AQ tables
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("DR$")) { //oracle index tables
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("SYS_IOT_OVER")) { //oracle system table
return true;
} else //noinspection HardCodedStringLiteral,HardCodedStringLiteral
if ((example.getName().startsWith("MDRT_") || example.getName().startsWith("MDRS_")) && example.getName().endsWith("$")) {
// CORE-1768 - Oracle creates these for spatial indices and will remove them when the index is removed.
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("MLOG$_")) { //Created by materliaized view logs for every table that is part of a materialized view. Not available for DDL operations.
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("RUPD$_")) { //Created by materialized view log tables using primary keys. Not available for DDL operations.
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("WM$_")) { //Workspace Manager backup tables.
return true;
} else //noinspection HardCodedStringLiteral
if ("CREATE$JAVA$LOB$TABLE".equals(example.getName())) { //This table contains the name of the Java object, the date it was loaded, and has a BLOB column to store the Java object.
return true;
} else //noinspection HardCodedStringLiteral
if ("JAVA$CLASS$MD5$TABLE".equals(example.getName())) { //This is a hash table that tracks the loading of Java objects into a schema.
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("ISEQ$$_")) { //System-generated sequence
return true;
} else //noinspection HardCodedStringLiteral
if (example.getName().startsWith("USLOG$")) { //for update materialized view
return true;
} else if (example.getName().startsWith("SYS_FBA")) { //for Flashback tables
return true;
}
}
return super.isSystemObject(example);
}
@Override
public boolean supportsAutoIncrement() {
// Oracle supports Identity beginning with version 12c
boolean isAutoIncrementSupported = false;
try {
if (getDatabaseMajorVersion() >= 12) {
isAutoIncrementSupported = true;
}
// Returning true will generate create table command with 'IDENTITY' clause, example:
// CREATE TABLE AutoIncTest (IDPrimaryKey NUMBER(19) GENERATED BY DEFAULT AS IDENTITY NOT NULL, TypeID NUMBER(3) NOT NULL, Description NVARCHAR2(50), CONSTRAINT PK_AutoIncTest PRIMARY KEY (IDPrimaryKey));
// While returning false will continue to generate create table command without 'IDENTITY' clause, example:
// CREATE TABLE AutoIncTest (IDPrimaryKey NUMBER(19) NOT NULL, TypeID NUMBER(3) NOT NULL, Description NVARCHAR2(50), CONSTRAINT PK_AutoIncTest PRIMARY KEY (IDPrimaryKey));
} catch (DatabaseException ex) {
isAutoIncrementSupported = false;
}
return isAutoIncrementSupported;
}
// public Set<UniqueConstraint> findUniqueConstraints(String schema) throws DatabaseException {
// Set<UniqueConstraint> returnSet = new HashSet<UniqueConstraint>();
//
// List<Map> maps = new Executor(this).queryForList(new RawSqlStatement("SELECT UC.CONSTRAINT_NAME, UCC.TABLE_NAME, UCC.COLUMN_NAME FROM USER_CONSTRAINTS UC, USER_CONS_COLUMNS UCC WHERE UC.CONSTRAINT_NAME=UCC.CONSTRAINT_NAME AND CONSTRAINT_TYPE='U' ORDER BY UC.CONSTRAINT_NAME"));
//
// UniqueConstraint constraint = null;
// for (Map map : maps) {
// if (constraint == null || !constraint.getName().equals(constraint.getName())) {
// returnSet.add(constraint);
// Table table = new Table((String) map.get("TABLE_NAME"));
// constraint = new UniqueConstraint(map.get("CONSTRAINT_NAME").toString(), table);
// }
// }
// if (constraint != null) {
// returnSet.add(constraint);
// }
//
// return returnSet;
// }
@Override
public boolean supportsRestrictForeignKeys() {
return false;
}
@Override
public int getDataTypeMaxParameters(String dataTypeName) {
//noinspection HardCodedStringLiteral
if ("BINARY_FLOAT".equals(dataTypeName.toUpperCase())) {
return 0;
}
//noinspection HardCodedStringLiteral
if ("BINARY_DOUBLE".equals(dataTypeName.toUpperCase())) {
return 0;
}
return super.getDataTypeMaxParameters(dataTypeName);
}
public String getSystemTableWhereClause(String tableNameColumn) {
List<String> clauses = new ArrayList<String>(Arrays.asList("BIN$",
"AQ$",
"DR$",
"SYS_IOT_OVER",
"MLOG$_",
"RUPD$_",
"WM$_",
"ISEQ$$_",
"USLOG$",
"SYS_FBA"));
for (int i = 0;i<clauses.size(); i++) {
clauses.set(i, tableNameColumn+" NOT LIKE '"+clauses.get(i)+"%'");
}
return "("+ StringUtil.join(clauses, " AND ") + ")";
}
@Override
public boolean jdbcCallsCatalogsSchemas() {
return true;
}
public Set<String> getUserDefinedTypes() {
if (userDefinedTypes == null) {
userDefinedTypes = new HashSet<>();
if ((getConnection() != null) && !(getConnection() instanceof OfflineConnection)) {
try {
try {
//noinspection HardCodedStringLiteral
userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT DISTINCT TYPE_NAME FROM ALL_TYPES"), String.class));
} catch (DatabaseException e) { //fall back to USER_TYPES if the user cannot see ALL_TYPES
//noinspection HardCodedStringLiteral
userDefinedTypes.addAll(Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor("jdbc", this).queryForList(new RawSqlStatement("SELECT TYPE_NAME FROM USER_TYPES"), String.class));
}
} catch (DatabaseException e) {
//ignore error
}
}
}
return userDefinedTypes;
}
@Override
public String generateDatabaseFunctionValue(DatabaseFunction databaseFunction) {
//noinspection HardCodedStringLiteral
if ((databaseFunction != null) && "current_timestamp".equalsIgnoreCase(databaseFunction.toString())) {
return databaseFunction.toString();
}
if ((databaseFunction instanceof SequenceNextValueFunction) || (databaseFunction instanceof
SequenceCurrentValueFunction)) {
String quotedSeq = super.generateDatabaseFunctionValue(databaseFunction);
// replace "myschema.my_seq".nextval with "myschema"."my_seq".nextval
return quotedSeq.replaceFirst("\"([^\\.\"]+)\\.([^\\.\"]+)\"", "\"$1\".\"$2\"");
}
return super.generateDatabaseFunctionValue(databaseFunction);
}
@Override
public ValidationErrors validate() {
ValidationErrors errors = super.validate();
DatabaseConnection connection = getConnection();
if ((connection == null) || (connection instanceof OfflineConnection)) {
//noinspection HardCodedStringLiteral
Scope.getCurrentScope().getLog(getClass()).info("Cannot validate offline database");
return errors;
}
if (!canAccessDbaRecycleBin()) {
errors.addWarning(getDbaRecycleBinWarning());
}
return errors;
}
public String getDbaRecycleBinWarning() {
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral,
// HardCodedStringLiteral
//noinspection HardCodedStringLiteral,HardCodedStringLiteral,HardCodedStringLiteral
return "Liquibase needs to access the DBA_RECYCLEBIN table so we can automatically handle the case where " +
"constraints are deleted and restored. Since Oracle doesn't properly restore the original table names " +
"referenced in the constraint, we use the information from the DBA_RECYCLEBIN to automatically correct this" +
" issue.\n" +
"\n" +
"The user you used to connect to the database (" + getConnection().getConnectionUserName() +
") needs to have \"SELECT ON SYS.DBA_RECYCLEBIN\" permissions set before we can perform this operation. " +
"Please run the following SQL to set the appropriate permissions, and try running the command again.\n" +
"\n" +
" GRANT SELECT ON SYS.DBA_RECYCLEBIN TO " + getConnection().getConnectionUserName() + ";";
}
public boolean canAccessDbaRecycleBin() {
if (canAccessDbaRecycleBin == null) {
DatabaseConnection connection = getConnection();
if ((connection == null) || (connection instanceof OfflineConnection)) {
return false;
}
Statement statement = null;
try {
statement = ((JdbcConnection) connection).createStatement();
@SuppressWarnings("HardCodedStringLiteral") ResultSet resultSet = statement.executeQuery("select 1 from dba_recyclebin where 0=1");
resultSet.close(); //don't need to do anything with the result set, just make sure statement ran.
this.canAccessDbaRecycleBin = true;
} catch (Exception e) {
//noinspection HardCodedStringLiteral
if ((e instanceof SQLException) && e.getMessage().startsWith("ORA-00942")) { //ORA-00942: table or view does not exist
this.canAccessDbaRecycleBin = false;
} else {
//noinspection HardCodedStringLiteral
Scope.getCurrentScope().getLog(getClass()).warning("Cannot check dba_recyclebin access", e);
this.canAccessDbaRecycleBin = false;
}
} finally {
JdbcUtils.close(null, statement);
}
}
return canAccessDbaRecycleBin;
}
@Override
public boolean supportsNotNullConstraintNames() {
return true;
}
/**
* Tests if the given String would be a valid identifier in Oracle DBMS. In Oracle, a valid identifier has
* the following form (case-insensitive comparison):
* 1st character: A-Z
* 2..n characters: A-Z0-9$_#
* The maximum length of an identifier differs by Oracle version and object type.
*/
public boolean isValidOracleIdentifier(String identifier, Class<? extends DatabaseObject> type) {
if ((identifier == null) || (identifier.length() < 1))
return false;
if (!identifier.matches("^(i?)[A-Z][A-Z0-9\\$\\_\\#]*$"))
return false;
/*
* @todo It seems we currently do not have a class for tablespace identifiers, and all other classes
* we do know seem to be supported as 12cR2 long identifiers, so:
*/
return (identifier.length() <= LONG_IDENTIFIERS_LEGNTH);
}
/**
* Returns the maximum number of bytes (NOT: characters) for an identifier. For Oracle <=12c Release 20, this
* is 30 bytes, and starting from 12cR2, up to 128 (except for tablespaces, PDB names and some other rather rare
* object types).
*
* @return the maximum length of an object identifier, in bytes
*/
public int getIdentifierMaximumLength() {
try {
if (getDatabaseMajorVersion() < ORACLE_12C_MAJOR_VERSION) {
return SHORT_IDENTIFIERS_LENGTH;
} else if ((getDatabaseMajorVersion() == ORACLE_12C_MAJOR_VERSION) && (getDatabaseMinorVersion() <= 1)) {
return SHORT_IDENTIFIERS_LENGTH;
} else {
return LONG_IDENTIFIERS_LEGNTH;
}
} catch (DatabaseException ex) {
throw new UnexpectedLiquibaseException("Cannot determine the Oracle database version number", ex);
}
}
}
@@ -0,0 +1,165 @@
package liquibase.datatype.core;
import liquibase.change.core.LoadDataChange;
import liquibase.database.Database;
import liquibase.database.core.*;
import liquibase.datatype.DataTypeInfo;
import liquibase.datatype.DatabaseDataType;
import liquibase.datatype.LiquibaseDataType;
import liquibase.exception.UnexpectedLiquibaseException;
import liquibase.statement.DatabaseFunction;
import liquibase.util.StringUtil;
import java.util.Locale;
import java.util.regex.Pattern;
@DataTypeInfo(name = "boolean", aliases = {"java.sql.Types.BOOLEAN", "java.lang.Boolean", "bit", "bool"}, minParameters = 0, maxParameters = 0, priority = LiquibaseDataType.PRIORITY_DEFAULT)
public class BooleanType extends LiquibaseDataType {
@Override
public DatabaseDataType toDatabaseDataType(Database database) {
String originalDefinition = StringUtil.trimToEmpty(getRawDefinition());
if ((database instanceof Firebird3Database)) {
return new DatabaseDataType("BOOLEAN");
}
if ((database instanceof Db2zDatabase) || (database instanceof FirebirdDatabase)) {
return new DatabaseDataType("SMALLINT");
} else if (database instanceof MSSQLDatabase) {
return new DatabaseDataType(database.escapeDataTypeName("bit"));
} else if (database instanceof MySQLDatabase) {
if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
return new DatabaseDataType("BIT", getParameters());
}
return new DatabaseDataType("BIT", 1);
} else if (database instanceof OracleDatabase) {
return new DatabaseDataType("NUMBER", 1);
} else if ((database instanceof SybaseASADatabase) || (database instanceof SybaseDatabase)) {
return new DatabaseDataType("BIT");
} else if (database instanceof DerbyDatabase) {
if (((DerbyDatabase) database).supportsBooleanDataType()) {
return new DatabaseDataType("BOOLEAN");
} else {
return new DatabaseDataType("SMALLINT");
}
} else if (database instanceof DB2Database) {
if (((DB2Database) database).supportsBooleanDataType())
return new DatabaseDataType("BOOLEAN");
else
return new DatabaseDataType("SMALLINT");
} else if (database instanceof HsqlDatabase) {
return new DatabaseDataType("BOOLEAN");
} else if (database instanceof PostgresDatabase) {
if (originalDefinition.toLowerCase(Locale.US).startsWith("bit")) {
return new DatabaseDataType("BIT", getParameters());
}
} else if (database instanceof DmDatabase) { // dhb52: DM Support
return new DatabaseDataType("bit");
}
return super.toDatabaseDataType(database);
}
@Override
public String objectToSql(Object value, Database database) {
if ((value == null) || "null".equals(value.toString().toLowerCase(Locale.US))) {
return null;
}
String returnValue;
if (value instanceof String) {
value = ((String) value).replaceAll("'", "");
if ("true".equals(((String) value).toLowerCase(Locale.US)) || "1".equals(value) || "b'1'".equals(((String) value).toLowerCase(Locale.US)) || "t".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getTrueBooleanValue(database).toLowerCase(Locale.US))) {
returnValue = this.getTrueBooleanValue(database);
} else if ("false".equals(((String) value).toLowerCase(Locale.US)) || "0".equals(value) || "b'0'".equals(
((String) value).toLowerCase(Locale.US)) || "f".equals(((String) value).toLowerCase(Locale.US)) || ((String) value).toLowerCase(Locale.US).equals(this.getFalseBooleanValue(database).toLowerCase(Locale.US))) {
returnValue = this.getFalseBooleanValue(database);
} else if (database instanceof PostgresDatabase && Pattern.matches("b?([01])\\1*(::bit|::\"bit\")?", (String) value)) {
returnValue = "b'"
+ value.toString()
.replace("b", "")
.replace("\"", "")
.replace("::it", "")
+ "'::\"bit\"";
} else {
throw new UnexpectedLiquibaseException("Unknown boolean value: " + value);
}
} else if (value instanceof Long) {
if (Long.valueOf(1).equals(value)) {
returnValue = this.getTrueBooleanValue(database);
} else {
returnValue = this.getFalseBooleanValue(database);
}
} else if (value instanceof Number) {
if (value.equals(1) || "1".equals(value.toString()) || "1.0".equals(value.toString())) {
returnValue = this.getTrueBooleanValue(database);
} else {
returnValue = this.getFalseBooleanValue(database);
}
} else if (value instanceof DatabaseFunction) {
return value.toString();
} else if (value instanceof Boolean) {
if (((Boolean) value)) {
returnValue = this.getTrueBooleanValue(database);
} else {
returnValue = this.getFalseBooleanValue(database);
}
} else {
throw new UnexpectedLiquibaseException("Cannot convert type " + value.getClass() + " to a boolean value");
}
return returnValue;
}
protected boolean isNumericBoolean(Database database) {
if (database instanceof Firebird3Database) {
return false;
}
if (database instanceof DerbyDatabase) {
return !((DerbyDatabase) database).supportsBooleanDataType();
} else if (database instanceof DB2Database) {
return !((DB2Database) database).supportsBooleanDataType();
}
return (database instanceof Db2zDatabase)
|| (database instanceof FirebirdDatabase)
|| (database instanceof MSSQLDatabase)
|| (database instanceof MySQLDatabase)
|| (database instanceof OracleDatabase)
|| (database instanceof SQLiteDatabase)
|| (database instanceof SybaseASADatabase)
|| (database instanceof SybaseDatabase)
|| (database instanceof DmDatabase); // dhb52: DM Support
}
/**
* The database-specific value to use for "false" "boolean" columns.
*/
public String getFalseBooleanValue(Database database) {
if (isNumericBoolean(database)) {
return "0";
}
if (database instanceof InformixDatabase) {
return "'f'";
}
return "FALSE";
}
/**
* The database-specific value to use for "true" "boolean" columns.
*/
public String getTrueBooleanValue(Database database) {
if (isNumericBoolean(database)) {
return "1";
}
if (database instanceof InformixDatabase) {
return "'t'";
}
return "TRUE";
}
@Override
public LoadDataChange.LOAD_DATA_TYPE getLoadTypeName() {
return LoadDataChange.LOAD_DATA_TYPE.BOOLEAN;
}
}
@@ -0,0 +1 @@
防止IDEA将`.``/`混为一谈
@@ -0,0 +1,21 @@
liquibase.database.core.CockroachDatabase
liquibase.database.core.DB2Database
liquibase.database.core.Db2zDatabase
liquibase.database.core.DerbyDatabase
liquibase.database.core.Firebird3Database
liquibase.database.core.FirebirdDatabase
liquibase.database.core.H2Database
liquibase.database.core.HsqlDatabase
liquibase.database.core.InformixDatabase
liquibase.database.core.Ingres9Database
liquibase.database.core.MSSQLDatabase
liquibase.database.core.MariaDBDatabase
liquibase.database.core.MockDatabase
liquibase.database.core.MySQLDatabase
liquibase.database.core.OracleDatabase
liquibase.database.core.PostgresDatabase
liquibase.database.core.SQLiteDatabase
liquibase.database.core.SybaseASADatabase
liquibase.database.core.SybaseDatabase
liquibase.database.core.DmDatabase
liquibase.database.core.UnsupportedDatabase
+179
View File
@@ -0,0 +1,179 @@
--
-- A hint submitted by a user: Oracle DB MUST be created as "shared" and the
-- job_queue_processes parameter must be greater than 2
-- However, these settings are pretty much standard after any
-- Oracle install, so most users need not worry about this.
--
-- Many other users (including the primary author of Quartz) have had success
-- running in dedicated mode, so only consider the above as a hint ;-)
--
drop table if exists qrtz_calendars;
drop table if exists qrtz_fired_triggers;
drop table if exists qrtz_blob_triggers;
drop table if exists qrtz_cron_triggers;
drop table if exists qrtz_simple_triggers;
drop table if exists qrtz_simprop_triggers;
drop table if exists qrtz_triggers;
drop table if exists qrtz_job_details;
drop table if exists qrtz_paused_trigger_grps;
drop table if exists qrtz_locks;
drop table if exists qrtz_scheduler_state;
CREATE TABLE qrtz_job_details
(
SCHED_NAME VARCHAR2(120) NOT NULL,
JOB_NAME VARCHAR2(200) NOT NULL,
JOB_GROUP VARCHAR2(200) NOT NULL,
DESCRIPTION VARCHAR2(250) NULL,
JOB_CLASS_NAME VARCHAR2(250) NOT NULL,
IS_DURABLE VARCHAR2(1) NOT NULL,
IS_NONCONCURRENT VARCHAR2(1) NOT NULL,
IS_UPDATE_DATA VARCHAR2(1) NOT NULL,
REQUESTS_RECOVERY VARCHAR2(1) NOT NULL,
JOB_DATA BLOB NULL,
CONSTRAINT QRTZ_JOB_DETAILS_PK PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
);
CREATE TABLE qrtz_triggers
(
SCHED_NAME VARCHAR2(120) NOT NULL,
TRIGGER_NAME VARCHAR2(200) NOT NULL,
TRIGGER_GROUP VARCHAR2(200) NOT NULL,
JOB_NAME VARCHAR2(200) NOT NULL,
JOB_GROUP VARCHAR2(200) NOT NULL,
DESCRIPTION VARCHAR2(250) NULL,
NEXT_FIRE_TIME NUMBER(19) NULL,
PREV_FIRE_TIME NUMBER(19) NULL,
PRIORITY NUMBER(13) NULL,
TRIGGER_STATE VARCHAR2(16) NOT NULL,
TRIGGER_TYPE VARCHAR2(8) NOT NULL,
START_TIME NUMBER(19) NOT NULL,
END_TIME NUMBER(19) NULL,
CALENDAR_NAME VARCHAR2(200) NULL,
MISFIRE_INSTR NUMBER(2) NULL,
JOB_DATA BLOB NULL,
CONSTRAINT QRTZ_TRIGGERS_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
CONSTRAINT QRTZ_TRIGGER_TO_JOBS_FK FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)
);
CREATE TABLE qrtz_simple_triggers
(
SCHED_NAME VARCHAR2(120) NOT NULL,
TRIGGER_NAME VARCHAR2(200) NOT NULL,
TRIGGER_GROUP VARCHAR2(200) NOT NULL,
REPEAT_COUNT NUMBER(7) NOT NULL,
REPEAT_INTERVAL NUMBER(12) NOT NULL,
TIMES_TRIGGERED NUMBER(10) NOT NULL,
CONSTRAINT QRTZ_SIMPLE_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
CONSTRAINT QRTZ_SIMPLE_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE qrtz_cron_triggers
(
SCHED_NAME VARCHAR2(120) NOT NULL,
TRIGGER_NAME VARCHAR2(200) NOT NULL,
TRIGGER_GROUP VARCHAR2(200) NOT NULL,
CRON_EXPRESSION VARCHAR2(120) NOT NULL,
TIME_ZONE_ID VARCHAR2(80),
CONSTRAINT QRTZ_CRON_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
CONSTRAINT QRTZ_CRON_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE qrtz_simprop_triggers
(
SCHED_NAME VARCHAR2(120) NOT NULL,
TRIGGER_NAME VARCHAR2(200) NOT NULL,
TRIGGER_GROUP VARCHAR2(200) NOT NULL,
STR_PROP_1 VARCHAR2(512) NULL,
STR_PROP_2 VARCHAR2(512) NULL,
STR_PROP_3 VARCHAR2(512) NULL,
INT_PROP_1 NUMBER(10) NULL,
INT_PROP_2 NUMBER(10) NULL,
LONG_PROP_1 NUMBER(19) NULL,
LONG_PROP_2 NUMBER(19) NULL,
DEC_PROP_1 NUMERIC(13,4) NULL,
DEC_PROP_2 NUMERIC(13,4) NULL,
BOOL_PROP_1 VARCHAR2(1) NULL,
BOOL_PROP_2 VARCHAR2(1) NULL,
TIME_ZONE_ID VARCHAR2(80) NULL,
CONSTRAINT QRTZ_SIMPROP_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
CONSTRAINT QRTZ_SIMPROP_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE qrtz_blob_triggers
(
SCHED_NAME VARCHAR2(120) NOT NULL,
TRIGGER_NAME VARCHAR2(200) NOT NULL,
TRIGGER_GROUP VARCHAR2(200) NOT NULL,
BLOB_DATA BLOB NULL,
CONSTRAINT QRTZ_BLOB_TRIG_PK PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),
CONSTRAINT QRTZ_BLOB_TRIG_TO_TRIG_FK FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)
);
CREATE TABLE qrtz_calendars
(
SCHED_NAME VARCHAR2(120) NOT NULL,
CALENDAR_NAME VARCHAR2(200) NOT NULL,
CALENDAR BLOB NOT NULL,
CONSTRAINT QRTZ_CALENDARS_PK PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)
);
CREATE TABLE qrtz_paused_trigger_grps
(
SCHED_NAME VARCHAR2(120) NOT NULL,
TRIGGER_GROUP VARCHAR2(200) NOT NULL,
CONSTRAINT QRTZ_PAUSED_TRIG_GRPS_PK PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)
);
CREATE TABLE qrtz_fired_triggers
(
SCHED_NAME VARCHAR2(120) NOT NULL,
ENTRY_ID VARCHAR2(140) NOT NULL,
TRIGGER_NAME VARCHAR2(200) NOT NULL,
TRIGGER_GROUP VARCHAR2(200) NOT NULL,
INSTANCE_NAME VARCHAR2(200) NOT NULL,
FIRED_TIME NUMBER(19) NOT NULL,
SCHED_TIME NUMBER(19) NOT NULL,
PRIORITY NUMBER(13) NOT NULL,
STATE VARCHAR2(16) NOT NULL,
JOB_NAME VARCHAR2(200) NULL,
JOB_GROUP VARCHAR2(200) NULL,
IS_NONCONCURRENT VARCHAR2(1) NULL,
REQUESTS_RECOVERY VARCHAR2(1) NULL,
CONSTRAINT QRTZ_FIRED_TRIGGER_PK PRIMARY KEY (SCHED_NAME,ENTRY_ID)
);
CREATE TABLE qrtz_scheduler_state
(
SCHED_NAME VARCHAR2(120) NOT NULL,
INSTANCE_NAME VARCHAR2(200) NOT NULL,
LAST_CHECKIN_TIME NUMBER(19) NOT NULL,
CHECKIN_INTERVAL NUMBER(13) NOT NULL,
CONSTRAINT QRTZ_SCHEDULER_STATE_PK PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)
);
CREATE TABLE qrtz_locks
(
SCHED_NAME VARCHAR2(120) NOT NULL,
LOCK_NAME VARCHAR2(40) NOT NULL,
CONSTRAINT QRTZ_LOCKS_PK PRIMARY KEY (SCHED_NAME,LOCK_NAME)
);
create index idx_qrtz_j_req_recovery on qrtz_job_details(SCHED_NAME,REQUESTS_RECOVERY);
create index idx_qrtz_j_grp on qrtz_job_details(SCHED_NAME,JOB_GROUP);
create index idx_qrtz_t_j on qrtz_triggers(SCHED_NAME,JOB_NAME,JOB_GROUP);
create index idx_qrtz_t_jg on qrtz_triggers(SCHED_NAME,JOB_GROUP);
create index idx_qrtz_t_c on qrtz_triggers(SCHED_NAME,CALENDAR_NAME);
create index idx_qrtz_t_g on qrtz_triggers(SCHED_NAME,TRIGGER_GROUP);
create index idx_qrtz_t_state on qrtz_triggers(SCHED_NAME,TRIGGER_STATE);
create index idx_qrtz_t_n_state on qrtz_triggers(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);
create index idx_qrtz_t_n_g_state on qrtz_triggers(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);
create index idx_qrtz_t_next_fire_time on qrtz_triggers(SCHED_NAME,NEXT_FIRE_TIME);
create index idx_qrtz_t_nft_st on qrtz_triggers(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);
create index idx_qrtz_t_nft_misfire on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);
create index idx_qrtz_t_nft_st_misfire on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);
create index idx_qrtz_t_nft_st_misfire_grp on qrtz_triggers(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);
create index idx_qrtz_ft_trig_inst_name on qrtz_fired_triggers(SCHED_NAME,INSTANCE_NAME);
create index idx_qrtz_ft_inst_job_req_rcvry on qrtz_fired_triggers(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY);
create index idx_qrtz_ft_j_g on qrtz_fired_triggers(SCHED_NAME,JOB_NAME,JOB_GROUP);
create index idx_qrtz_ft_jg on qrtz_fired_triggers(SCHED_NAME,JOB_GROUP);
create index idx_qrtz_ft_t_g on qrtz_fired_triggers(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP);
create index idx_qrtz_ft_tg on qrtz_fired_triggers(SCHED_NAME,TRIGGER_GROUP);
File diff suppressed because it is too large Load Diff
+170
View File
@@ -0,0 +1,170 @@
set client_min_messages = WARNING;
DROP TABLE IF EXISTS qrtz_fired_triggers;
DROP TABLE IF EXISTS qrtz_paused_trigger_grps;
DROP TABLE IF EXISTS qrtz_scheduler_state;
DROP TABLE IF EXISTS qrtz_locks;
DROP TABLE IF EXISTS qrtz_simprop_triggers;
DROP TABLE IF EXISTS qrtz_simple_triggers;
DROP TABLE IF EXISTS qrtz_cron_triggers;
DROP TABLE IF EXISTS qrtz_blob_triggers;
DROP TABLE IF EXISTS qrtz_triggers;
DROP TABLE IF EXISTS qrtz_job_details;
DROP TABLE IF EXISTS qrtz_calendars;
set client_min_messages = NOTICE;
CREATE TABLE qrtz_job_details
(
sched_name TEXT NOT NULL,
job_name TEXT NOT NULL,
job_group TEXT NOT NULL,
description TEXT NULL,
job_class_name TEXT NOT NULL,
is_durable BOOL NOT NULL,
is_nonconcurrent BOOL NOT NULL,
is_update_data BOOL NOT NULL,
requests_recovery BOOL NOT NULL,
job_data BYTEA NULL,
PRIMARY KEY (sched_name,job_name,job_group)
);
CREATE TABLE qrtz_triggers
(
sched_name TEXT NOT NULL,
trigger_name TEXT NOT NULL,
trigger_group TEXT NOT NULL,
job_name TEXT NOT NULL,
job_group TEXT NOT NULL,
description TEXT NULL,
next_fire_time BIGINT NULL,
prev_fire_time BIGINT NULL,
priority INTEGER NULL,
trigger_state TEXT NOT NULL,
trigger_type TEXT NOT NULL,
start_time BIGINT NOT NULL,
end_time BIGINT NULL,
calendar_name TEXT NULL,
misfire_instr SMALLINT NULL,
job_data BYTEA NULL,
PRIMARY KEY (sched_name,trigger_name,trigger_group),
FOREIGN KEY (sched_name,job_name,job_group)
REFERENCES qrtz_job_details(sched_name,job_name,job_group)
);
CREATE TABLE qrtz_simple_triggers
(
sched_name TEXT NOT NULL,
trigger_name TEXT NOT NULL,
trigger_group TEXT NOT NULL,
repeat_count BIGINT NOT NULL,
repeat_interval BIGINT NOT NULL,
times_triggered BIGINT NOT NULL,
PRIMARY KEY (sched_name,trigger_name,trigger_group),
FOREIGN KEY (sched_name,trigger_name,trigger_group)
REFERENCES qrtz_triggers(sched_name,trigger_name,trigger_group) ON DELETE CASCADE
);
CREATE TABLE QRTZ_SIMPROP_TRIGGERS
(
sched_name TEXT NOT NULL,
trigger_name TEXT NOT NULL ,
trigger_group TEXT NOT NULL ,
str_prop_1 TEXT NULL,
str_prop_2 TEXT NULL,
str_prop_3 TEXT NULL,
int_prop_1 INTEGER NULL,
int_prop_2 INTEGER NULL,
long_prop_1 BIGINT NULL,
long_prop_2 BIGINT NULL,
dec_prop_1 NUMERIC NULL,
dec_prop_2 NUMERIC NULL,
bool_prop_1 BOOL NULL,
bool_prop_2 BOOL NULL,
time_zone_id TEXT NULL,
PRIMARY KEY (sched_name,trigger_name,trigger_group),
FOREIGN KEY (sched_name,trigger_name,trigger_group)
REFERENCES qrtz_triggers(sched_name,trigger_name,trigger_group) ON DELETE CASCADE
);
CREATE TABLE qrtz_cron_triggers
(
sched_name TEXT NOT NULL,
trigger_name TEXT NOT NULL,
trigger_group TEXT NOT NULL,
cron_expression TEXT NOT NULL,
time_zone_id TEXT,
PRIMARY KEY (sched_name,trigger_name,trigger_group),
FOREIGN KEY (sched_name,trigger_name,trigger_group)
REFERENCES qrtz_triggers(sched_name,trigger_name,trigger_group) ON DELETE CASCADE
);
CREATE TABLE qrtz_blob_triggers
(
sched_name TEXT NOT NULL,
trigger_name TEXT NOT NULL,
trigger_group TEXT NOT NULL,
blob_data BYTEA NULL,
PRIMARY KEY (sched_name,trigger_name,trigger_group),
FOREIGN KEY (sched_name,trigger_name,trigger_group)
REFERENCES qrtz_triggers(sched_name,trigger_name,trigger_group) ON DELETE CASCADE
);
CREATE TABLE qrtz_calendars
(
sched_name TEXT NOT NULL,
calendar_name TEXT NOT NULL,
calendar BYTEA NOT NULL,
PRIMARY KEY (sched_name,calendar_name)
);
CREATE TABLE qrtz_paused_trigger_grps
(
sched_name TEXT NOT NULL,
trigger_group TEXT NOT NULL,
PRIMARY KEY (sched_name,trigger_group)
);
CREATE TABLE qrtz_fired_triggers
(
sched_name TEXT NOT NULL,
entry_id TEXT NOT NULL,
trigger_name TEXT NOT NULL,
trigger_group TEXT NOT NULL,
instance_name TEXT NOT NULL,
fired_time BIGINT NOT NULL,
sched_time BIGINT NOT NULL,
priority INTEGER NOT NULL,
state TEXT NOT NULL,
job_name TEXT NULL,
job_group TEXT NULL,
is_nonconcurrent BOOL NOT NULL,
requests_recovery BOOL NULL,
PRIMARY KEY (sched_name,entry_id)
);
CREATE TABLE qrtz_scheduler_state
(
sched_name TEXT NOT NULL,
instance_name TEXT NOT NULL,
last_checkin_time BIGINT NOT NULL,
checkin_interval BIGINT NOT NULL,
PRIMARY KEY (sched_name,instance_name)
);
CREATE TABLE qrtz_locks
(
sched_name TEXT NOT NULL,
lock_name TEXT NOT NULL,
PRIMARY KEY (sched_name,lock_name)
);
create index idx_qrtz_j_req_recovery on qrtz_job_details(requests_recovery);
create index idx_qrtz_t_next_fire_time on qrtz_triggers(next_fire_time);
create index idx_qrtz_t_state on qrtz_triggers(trigger_state);
create index idx_qrtz_t_nft_st on qrtz_triggers(next_fire_time,trigger_state);
create index idx_qrtz_ft_trig_name on qrtz_fired_triggers(trigger_name);
create index idx_qrtz_ft_trig_group on qrtz_fired_triggers(trigger_group);
create index idx_qrtz_ft_trig_nm_gp on qrtz_fired_triggers(sched_name,trigger_name,trigger_group);
create index idx_qrtz_ft_trig_inst_name on qrtz_fired_triggers(instance_name);
create index idx_qrtz_ft_job_name on qrtz_fired_triggers(job_name);
create index idx_qrtz_ft_job_group on qrtz_fired_triggers(job_group);
create index idx_qrtz_ft_job_req_recovery on qrtz_fired_triggers(requests_recovery);
File diff suppressed because it is too large Load Diff
+10602
View File
File diff suppressed because one or more lines are too long
+253
View File
@@ -0,0 +1,253 @@
-- ----------------------------
-- qrtz_blob_triggers
-- ----------------------------
CREATE TABLE qrtz_blob_triggers
(
sched_name varchar(120) NOT NULL,
trigger_name varchar(190) NOT NULL,
trigger_group varchar(190) NOT NULL,
blob_data bytea NULL,
PRIMARY KEY (sched_name, trigger_name, trigger_group)
);
CREATE INDEX idx_qrtz_blob_triggers_sched_name ON qrtz_blob_triggers (sched_name, trigger_name, trigger_group);
-- ----------------------------
-- qrtz_calendars
-- ----------------------------
CREATE TABLE qrtz_calendars
(
sched_name varchar(120) NOT NULL,
calendar_name varchar(190) NOT NULL,
calendar bytea NOT NULL,
PRIMARY KEY (sched_name, calendar_name)
);
-- ----------------------------
-- qrtz_cron_triggers
-- ----------------------------
CREATE TABLE qrtz_cron_triggers
(
sched_name varchar(120) NOT NULL,
trigger_name varchar(190) NOT NULL,
trigger_group varchar(190) NOT NULL,
cron_expression varchar(120) NOT NULL,
time_zone_id varchar(80) NULL DEFAULT NULL,
PRIMARY KEY (sched_name, trigger_name, trigger_group)
);
-- @formatter:off
BEGIN;
COMMIT;
-- @formatter:on
-- ----------------------------
-- qrtz_fired_triggers
-- ----------------------------
CREATE TABLE qrtz_fired_triggers
(
sched_name varchar(120) NOT NULL,
entry_id varchar(95) NOT NULL,
trigger_name varchar(190) NOT NULL,
trigger_group varchar(190) NOT NULL,
instance_name varchar(190) NOT NULL,
fired_time int8 NOT NULL,
sched_time int8 NOT NULL,
priority int4 NOT NULL,
state varchar(16) NOT NULL,
job_name varchar(190) NULL DEFAULT NULL,
job_group varchar(190) NULL DEFAULT NULL,
is_nonconcurrent varchar(1) NULL DEFAULT NULL,
requests_recovery varchar(1) NULL DEFAULT NULL,
PRIMARY KEY (sched_name, entry_id)
);
CREATE INDEX idx_qrtz_ft_trig_inst_name ON qrtz_fired_triggers (sched_name, instance_name);
CREATE INDEX idx_qrtz_ft_inst_job_req_rcvry ON qrtz_fired_triggers (sched_name, instance_name, requests_recovery);
CREATE INDEX idx_qrtz_ft_j_g ON qrtz_fired_triggers (sched_name, job_name, job_group);
CREATE INDEX idx_qrtz_ft_jg ON qrtz_fired_triggers (sched_name, job_group);
CREATE INDEX idx_qrtz_ft_t_g ON qrtz_fired_triggers (sched_name, trigger_name, trigger_group);
CREATE INDEX idx_qrtz_ft_tg ON qrtz_fired_triggers (sched_name, trigger_group);
-- ----------------------------
-- qrtz_job_details
-- ----------------------------
CREATE TABLE qrtz_job_details
(
sched_name varchar(120) NOT NULL,
job_name varchar(190) NOT NULL,
job_group varchar(190) NOT NULL,
description varchar(250) NULL DEFAULT NULL,
job_class_name varchar(250) NOT NULL,
is_durable varchar(1) NOT NULL,
is_nonconcurrent varchar(1) NOT NULL,
is_update_data varchar(1) NOT NULL,
requests_recovery varchar(1) NOT NULL,
job_data bytea NULL,
PRIMARY KEY (sched_name, job_name, job_group)
);
CREATE INDEX idx_qrtz_j_req_recovery ON qrtz_job_details (sched_name, requests_recovery);
CREATE INDEX idx_qrtz_j_grp ON qrtz_job_details (sched_name, job_group);
-- @formatter:off
BEGIN;
COMMIT;
-- @formatter:on
-- ----------------------------
-- qrtz_locks
-- ----------------------------
CREATE TABLE qrtz_locks
(
sched_name varchar(120) NOT NULL,
lock_name varchar(40) NOT NULL,
PRIMARY KEY (sched_name, lock_name)
);
-- @formatter:off
BEGIN;
COMMIT;
-- @formatter:on
-- ----------------------------
-- qrtz_paused_trigger_grps
-- ----------------------------
CREATE TABLE qrtz_paused_trigger_grps
(
sched_name varchar(120) NOT NULL,
trigger_group varchar(190) NOT NULL,
PRIMARY KEY (sched_name, trigger_group)
);
-- ----------------------------
-- qrtz_scheduler_state
-- ----------------------------
CREATE TABLE qrtz_scheduler_state
(
sched_name varchar(120) NOT NULL,
instance_name varchar(190) NOT NULL,
last_checkin_time int8 NOT NULL,
checkin_interval int8 NOT NULL,
PRIMARY KEY (sched_name, instance_name)
);
-- @formatter:off
BEGIN;
COMMIT;
-- @formatter:on
-- ----------------------------
-- qrtz_simple_triggers
-- ----------------------------
CREATE TABLE qrtz_simple_triggers
(
sched_name varchar(120) NOT NULL,
trigger_name varchar(190) NOT NULL,
trigger_group varchar(190) NOT NULL,
repeat_count int8 NOT NULL,
repeat_interval int8 NOT NULL,
times_triggered int8 NOT NULL,
PRIMARY KEY (sched_name, trigger_name, trigger_group)
);
-- ----------------------------
-- qrtz_simprop_triggers
-- ----------------------------
CREATE TABLE qrtz_simprop_triggers
(
sched_name varchar(120) NOT NULL,
trigger_name varchar(190) NOT NULL,
trigger_group varchar(190) NOT NULL,
str_prop_1 varchar(512) NULL DEFAULT NULL,
str_prop_2 varchar(512) NULL DEFAULT NULL,
str_prop_3 varchar(512) NULL DEFAULT NULL,
int_prop_1 int4 NULL DEFAULT NULL,
int_prop_2 int4 NULL DEFAULT NULL,
long_prop_1 int8 NULL DEFAULT NULL,
long_prop_2 int8 NULL DEFAULT NULL,
dec_prop_1 numeric(13, 4) NULL DEFAULT NULL,
dec_prop_2 numeric(13, 4) NULL DEFAULT NULL,
bool_prop_1 varchar(1) NULL DEFAULT NULL,
bool_prop_2 varchar(1) NULL DEFAULT NULL,
PRIMARY KEY (sched_name, trigger_name, trigger_group)
);
-- ----------------------------
-- qrtz_triggers
-- ----------------------------
CREATE TABLE qrtz_triggers
(
sched_name varchar(120) NOT NULL,
trigger_name varchar(190) NOT NULL,
trigger_group varchar(190) NOT NULL,
job_name varchar(190) NOT NULL,
job_group varchar(190) NOT NULL,
description varchar(250) NULL DEFAULT NULL,
next_fire_time int8 NULL DEFAULT NULL,
prev_fire_time int8 NULL DEFAULT NULL,
priority int4 NULL DEFAULT NULL,
trigger_state varchar(16) NOT NULL,
trigger_type varchar(8) NOT NULL,
start_time int8 NOT NULL,
end_time int8 NULL DEFAULT NULL,
calendar_name varchar(190) NULL DEFAULT NULL,
misfire_instr int2 NULL DEFAULT NULL,
job_data bytea NULL,
PRIMARY KEY (sched_name, trigger_name, trigger_group)
);
CREATE INDEX idx_qrtz_t_j ON qrtz_triggers (sched_name, job_name, job_group);
CREATE INDEX idx_qrtz_t_jg ON qrtz_triggers (sched_name, job_group);
CREATE INDEX idx_qrtz_t_c ON qrtz_triggers (sched_name, calendar_name);
CREATE INDEX idx_qrtz_t_g ON qrtz_triggers (sched_name, trigger_group);
CREATE INDEX idx_qrtz_t_state ON qrtz_triggers (sched_name, trigger_state);
CREATE INDEX idx_qrtz_t_n_state ON qrtz_triggers (sched_name, trigger_name, trigger_group, trigger_state);
CREATE INDEX idx_qrtz_t_n_g_state ON qrtz_triggers (sched_name, trigger_group, trigger_state);
CREATE INDEX idx_qrtz_t_next_fire_time ON qrtz_triggers (sched_name, next_fire_time);
CREATE INDEX idx_qrtz_t_nft_st ON qrtz_triggers (sched_name, trigger_state, next_fire_time);
CREATE INDEX idx_qrtz_t_nft_misfire ON qrtz_triggers (sched_name, misfire_instr, next_fire_time);
CREATE INDEX idx_qrtz_t_nft_st_misfire ON qrtz_triggers (sched_name, misfire_instr, next_fire_time, trigger_state);
CREATE INDEX idx_qrtz_t_nft_st_misfire_grp ON qrtz_triggers (sched_name, misfire_instr, next_fire_time, trigger_group,
trigger_state);
-- @formatter:off
BEGIN;
COMMIT;
-- @formatter:on
-- ----------------------------
-- FK: qrtz_blob_triggers
-- ----------------------------
ALTER TABLE qrtz_blob_triggers
ADD CONSTRAINT qrtz_blob_triggers_ibfk_1 FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES qrtz_triggers (sched_name,
trigger_name,
trigger_group);
-- ----------------------------
-- FK: qrtz_cron_triggers
-- ----------------------------
ALTER TABLE qrtz_cron_triggers
ADD CONSTRAINT qrtz_cron_triggers_ibfk_1 FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES qrtz_triggers (sched_name, trigger_name, trigger_group);
-- ----------------------------
-- FK: qrtz_simple_triggers
-- ----------------------------
ALTER TABLE qrtz_simple_triggers
ADD CONSTRAINT qrtz_simple_triggers_ibfk_1 FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES qrtz_triggers (sched_name,
trigger_name,
trigger_group);
-- ----------------------------
-- FK: qrtz_simprop_triggers
-- ----------------------------
ALTER TABLE qrtz_simprop_triggers
ADD CONSTRAINT qrtz_simprop_triggers_ibfk_1 FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES qrtz_triggers (sched_name, trigger_name, trigger_group);
-- ----------------------------
-- FK: qrtz_triggers
-- ----------------------------
ALTER TABLE qrtz_triggers
ADD CONSTRAINT qrtz_triggers_ibfk_1 FOREIGN KEY (sched_name, job_name, job_group) REFERENCES qrtz_job_details (sched_name, job_name, job_group);
File diff suppressed because it is too large Load Diff
+845
View File
@@ -0,0 +1,845 @@
/*
注意:仅仅需要 Quartz 定时任务的场景,可选!!!
Date: 15/06/2022 08:20:08
*/
-- ----------------------------
-- Table structure for QRTZ_BLOB_TRIGGERS
-- ----------------------------
DROP TABLE "QRTZ_BLOB_TRIGGERS";
CREATE TABLE "QRTZ_BLOB_TRIGGERS" (
"SCHED_NAME" VARCHAR2(120 BYTE) NOT NULL,
"TRIGGER_NAME" VARCHAR2(200 BYTE) NOT NULL,
"TRIGGER_GROUP" VARCHAR2(200 BYTE) NOT NULL,
"BLOB_DATA" BLOB
)
LOGGING
NOCOMPRESS
PCTFREE 10
INITRANS 1
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
)
PARALLEL 1
NOCACHE
DISABLE ROW MOVEMENT
;
-- ----------------------------
-- Records of QRTZ_BLOB_TRIGGERS
-- ----------------------------
COMMIT;
COMMIT;
-- ----------------------------
-- Table structure for QRTZ_CALENDARS
-- ----------------------------
DROP TABLE "QRTZ_CALENDARS";
CREATE TABLE "QRTZ_CALENDARS" (
"SCHED_NAME" VARCHAR2(120 BYTE) NOT NULL,
"CALENDAR_NAME" VARCHAR2(200 BYTE) NOT NULL,
"CALENDAR" BLOB NOT NULL
)
LOGGING
NOCOMPRESS
PCTFREE 10
INITRANS 1
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
)
PARALLEL 1
NOCACHE
DISABLE ROW MOVEMENT
;
-- ----------------------------
-- Records of QRTZ_CALENDARS
-- ----------------------------
COMMIT;
COMMIT;
-- ----------------------------
-- Table structure for QRTZ_CRON_TRIGGERS
-- ----------------------------
DROP TABLE "QRTZ_CRON_TRIGGERS";
CREATE TABLE "QRTZ_CRON_TRIGGERS" (
"SCHED_NAME" VARCHAR2(120 BYTE) NOT NULL,
"TRIGGER_NAME" VARCHAR2(200 BYTE) NOT NULL,
"TRIGGER_GROUP" VARCHAR2(200 BYTE) NOT NULL,
"CRON_EXPRESSION" VARCHAR2(120 BYTE) NOT NULL,
"TIME_ZONE_ID" VARCHAR2(80 BYTE)
)
LOGGING
NOCOMPRESS
PCTFREE 10
INITRANS 1
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
)
PARALLEL 1
NOCACHE
DISABLE ROW MOVEMENT
;
-- ----------------------------
-- Records of QRTZ_CRON_TRIGGERS
-- ----------------------------
COMMIT;
COMMIT;
-- ----------------------------
-- Table structure for QRTZ_FIRED_TRIGGERS
-- ----------------------------
DROP TABLE "QRTZ_FIRED_TRIGGERS";
CREATE TABLE "QRTZ_FIRED_TRIGGERS" (
"SCHED_NAME" VARCHAR2(120 BYTE) NOT NULL,
"ENTRY_ID" VARCHAR2(95 BYTE) NOT NULL,
"TRIGGER_NAME" VARCHAR2(200 BYTE) NOT NULL,
"TRIGGER_GROUP" VARCHAR2(200 BYTE) NOT NULL,
"INSTANCE_NAME" VARCHAR2(200 BYTE) NOT NULL,
"FIRED_TIME" NUMBER(13,0) NOT NULL,
"SCHED_TIME" NUMBER(13,0) NOT NULL,
"PRIORITY" NUMBER(13,0) NOT NULL,
"STATE" VARCHAR2(16 BYTE) NOT NULL,
"JOB_NAME" VARCHAR2(200 BYTE),
"JOB_GROUP" VARCHAR2(200 BYTE),
"IS_NONCONCURRENT" VARCHAR2(1 BYTE),
"REQUESTS_RECOVERY" VARCHAR2(1 BYTE)
)
LOGGING
NOCOMPRESS
PCTFREE 10
INITRANS 1
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
)
PARALLEL 1
NOCACHE
DISABLE ROW MOVEMENT
;
-- ----------------------------
-- Records of QRTZ_FIRED_TRIGGERS
-- ----------------------------
COMMIT;
COMMIT;
-- ----------------------------
-- Table structure for QRTZ_JOB_DETAILS
-- ----------------------------
DROP TABLE "QRTZ_JOB_DETAILS";
CREATE TABLE "QRTZ_JOB_DETAILS" (
"SCHED_NAME" VARCHAR2(120 BYTE) NOT NULL,
"JOB_NAME" VARCHAR2(200 BYTE) NOT NULL,
"JOB_GROUP" VARCHAR2(200 BYTE) NOT NULL,
"DESCRIPTION" VARCHAR2(250 BYTE),
"JOB_CLASS_NAME" VARCHAR2(250 BYTE) NOT NULL,
"IS_DURABLE" VARCHAR2(1 BYTE) NOT NULL,
"IS_NONCONCURRENT" VARCHAR2(1 BYTE) NOT NULL,
"IS_UPDATE_DATA" VARCHAR2(1 BYTE) NOT NULL,
"REQUESTS_RECOVERY" VARCHAR2(1 BYTE) NOT NULL,
"JOB_DATA" BLOB
)
LOGGING
NOCOMPRESS
PCTFREE 10
INITRANS 1
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
)
PARALLEL 1
NOCACHE
DISABLE ROW MOVEMENT
;
-- ----------------------------
-- Records of QRTZ_JOB_DETAILS
-- ----------------------------
COMMIT;
COMMIT;
-- ----------------------------
-- Table structure for QRTZ_LOCKS
-- ----------------------------
DROP TABLE "QRTZ_LOCKS";
CREATE TABLE "QRTZ_LOCKS" (
"SCHED_NAME" VARCHAR2(120 BYTE) NOT NULL,
"LOCK_NAME" VARCHAR2(40 BYTE) NOT NULL
)
LOGGING
NOCOMPRESS
PCTFREE 10
INITRANS 1
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
)
PARALLEL 1
NOCACHE
DISABLE ROW MOVEMENT
;
-- ----------------------------
-- Records of QRTZ_LOCKS
-- ----------------------------
COMMIT;
COMMIT;
-- ----------------------------
-- Table structure for QRTZ_PAUSED_TRIGGER_GRPS
-- ----------------------------
DROP TABLE "QRTZ_PAUSED_TRIGGER_GRPS";
CREATE TABLE "QRTZ_PAUSED_TRIGGER_GRPS" (
"SCHED_NAME" VARCHAR2(120 BYTE) NOT NULL,
"TRIGGER_GROUP" VARCHAR2(200 BYTE) NOT NULL
)
LOGGING
NOCOMPRESS
PCTFREE 10
INITRANS 1
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
)
PARALLEL 1
NOCACHE
DISABLE ROW MOVEMENT
;
-- ----------------------------
-- Records of QRTZ_PAUSED_TRIGGER_GRPS
-- ----------------------------
COMMIT;
COMMIT;
-- ----------------------------
-- Table structure for QRTZ_SCHEDULER_STATE
-- ----------------------------
DROP TABLE "QRTZ_SCHEDULER_STATE";
CREATE TABLE "QRTZ_SCHEDULER_STATE" (
"SCHED_NAME" VARCHAR2(120 BYTE) NOT NULL,
"INSTANCE_NAME" VARCHAR2(200 BYTE) NOT NULL,
"LAST_CHECKIN_TIME" NUMBER(13,0) NOT NULL,
"CHECKIN_INTERVAL" NUMBER(13,0) NOT NULL
)
LOGGING
NOCOMPRESS
PCTFREE 10
INITRANS 1
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
)
PARALLEL 1
NOCACHE
DISABLE ROW MOVEMENT
;
-- ----------------------------
-- Records of QRTZ_SCHEDULER_STATE
-- ----------------------------
COMMIT;
COMMIT;
-- ----------------------------
-- Table structure for QRTZ_SIMPLE_TRIGGERS
-- ----------------------------
DROP TABLE "QRTZ_SIMPLE_TRIGGERS";
CREATE TABLE "QRTZ_SIMPLE_TRIGGERS" (
"SCHED_NAME" VARCHAR2(120 BYTE) NOT NULL,
"TRIGGER_NAME" VARCHAR2(200 BYTE) NOT NULL,
"TRIGGER_GROUP" VARCHAR2(200 BYTE) NOT NULL,
"REPEAT_COUNT" NUMBER(7,0) NOT NULL,
"REPEAT_INTERVAL" NUMBER(12,0) NOT NULL,
"TIMES_TRIGGERED" NUMBER(10,0) NOT NULL
)
LOGGING
NOCOMPRESS
PCTFREE 10
INITRANS 1
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
)
PARALLEL 1
NOCACHE
DISABLE ROW MOVEMENT
;
-- ----------------------------
-- Records of QRTZ_SIMPLE_TRIGGERS
-- ----------------------------
COMMIT;
COMMIT;
-- ----------------------------
-- Table structure for QRTZ_SIMPROP_TRIGGERS
-- ----------------------------
DROP TABLE "QRTZ_SIMPROP_TRIGGERS";
CREATE TABLE "QRTZ_SIMPROP_TRIGGERS" (
"SCHED_NAME" VARCHAR2(120 BYTE) NOT NULL,
"TRIGGER_NAME" VARCHAR2(200 BYTE) NOT NULL,
"TRIGGER_GROUP" VARCHAR2(200 BYTE) NOT NULL,
"STR_PROP_1" VARCHAR2(512 BYTE),
"STR_PROP_2" VARCHAR2(512 BYTE),
"STR_PROP_3" VARCHAR2(512 BYTE),
"INT_PROP_1" NUMBER(10,0),
"INT_PROP_2" NUMBER(10,0),
"LONG_PROP_1" NUMBER(13,0),
"LONG_PROP_2" NUMBER(13,0),
"DEC_PROP_1" NUMBER(13,4),
"DEC_PROP_2" NUMBER(13,4),
"BOOL_PROP_1" VARCHAR2(1 BYTE),
"BOOL_PROP_2" VARCHAR2(1 BYTE)
)
LOGGING
NOCOMPRESS
PCTFREE 10
INITRANS 1
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
)
PARALLEL 1
NOCACHE
DISABLE ROW MOVEMENT
;
-- ----------------------------
-- Records of QRTZ_SIMPROP_TRIGGERS
-- ----------------------------
COMMIT;
COMMIT;
-- ----------------------------
-- Table structure for QRTZ_TRIGGERS
-- ----------------------------
DROP TABLE "QRTZ_TRIGGERS";
CREATE TABLE "QRTZ_TRIGGERS" (
"SCHED_NAME" VARCHAR2(120 BYTE) NOT NULL,
"TRIGGER_NAME" VARCHAR2(200 BYTE) NOT NULL,
"TRIGGER_GROUP" VARCHAR2(200 BYTE) NOT NULL,
"JOB_NAME" VARCHAR2(200 BYTE) NOT NULL,
"JOB_GROUP" VARCHAR2(200 BYTE) NOT NULL,
"DESCRIPTION" VARCHAR2(250 BYTE),
"NEXT_FIRE_TIME" NUMBER(13,0),
"PREV_FIRE_TIME" NUMBER(13,0),
"PRIORITY" NUMBER(13,0),
"TRIGGER_STATE" VARCHAR2(16 BYTE) NOT NULL,
"TRIGGER_TYPE" VARCHAR2(8 BYTE) NOT NULL,
"START_TIME" NUMBER(13,0) NOT NULL,
"END_TIME" NUMBER(13,0),
"CALENDAR_NAME" VARCHAR2(200 BYTE),
"MISFIRE_INSTR" NUMBER(2,0),
"JOB_DATA" BLOB
)
LOGGING
NOCOMPRESS
PCTFREE 10
INITRANS 1
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
)
PARALLEL 1
NOCACHE
DISABLE ROW MOVEMENT
;
-- ----------------------------
-- Records of QRTZ_TRIGGERS
-- ----------------------------
COMMIT;
COMMIT;
-- ----------------------------
-- Primary Key structure for table QRTZ_BLOB_TRIGGERS
-- ----------------------------
ALTER TABLE "QRTZ_BLOB_TRIGGERS" ADD CONSTRAINT "QRTZ_BLOB_TRIG_PK" PRIMARY KEY ("SCHED_NAME", "TRIGGER_NAME", "TRIGGER_GROUP");
-- ----------------------------
-- Checks structure for table QRTZ_BLOB_TRIGGERS
-- ----------------------------
ALTER TABLE "QRTZ_BLOB_TRIGGERS" ADD CONSTRAINT "SYS_C008266" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_BLOB_TRIGGERS" ADD CONSTRAINT "SYS_C008267" CHECK ("TRIGGER_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_BLOB_TRIGGERS" ADD CONSTRAINT "SYS_C008268" CHECK ("TRIGGER_GROUP" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_BLOB_TRIGGERS" ADD CONSTRAINT "SYS_C008653" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_BLOB_TRIGGERS" ADD CONSTRAINT "SYS_C008654" CHECK ("TRIGGER_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_BLOB_TRIGGERS" ADD CONSTRAINT "SYS_C008655" CHECK ("TRIGGER_GROUP" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
-- ----------------------------
-- Primary Key structure for table QRTZ_CALENDARS
-- ----------------------------
ALTER TABLE "QRTZ_CALENDARS" ADD CONSTRAINT "QRTZ_CALENDARS_PK" PRIMARY KEY ("SCHED_NAME", "CALENDAR_NAME");
-- ----------------------------
-- Checks structure for table QRTZ_CALENDARS
-- ----------------------------
ALTER TABLE "QRTZ_CALENDARS" ADD CONSTRAINT "SYS_C008271" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_CALENDARS" ADD CONSTRAINT "SYS_C008272" CHECK ("CALENDAR_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_CALENDARS" ADD CONSTRAINT "SYS_C008273" CHECK ("CALENDAR" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_CALENDARS" ADD CONSTRAINT "SYS_C008656" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_CALENDARS" ADD CONSTRAINT "SYS_C008657" CHECK ("CALENDAR_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_CALENDARS" ADD CONSTRAINT "SYS_C008658" CHECK ("CALENDAR" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
-- ----------------------------
-- Primary Key structure for table QRTZ_CRON_TRIGGERS
-- ----------------------------
ALTER TABLE "QRTZ_CRON_TRIGGERS" ADD CONSTRAINT "QRTZ_CRON_TRIG_PK" PRIMARY KEY ("SCHED_NAME", "TRIGGER_NAME", "TRIGGER_GROUP");
-- ----------------------------
-- Checks structure for table QRTZ_CRON_TRIGGERS
-- ----------------------------
ALTER TABLE "QRTZ_CRON_TRIGGERS" ADD CONSTRAINT "SYS_C008255" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_CRON_TRIGGERS" ADD CONSTRAINT "SYS_C008256" CHECK ("TRIGGER_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_CRON_TRIGGERS" ADD CONSTRAINT "SYS_C008257" CHECK ("TRIGGER_GROUP" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_CRON_TRIGGERS" ADD CONSTRAINT "SYS_C008258" CHECK ("CRON_EXPRESSION" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_CRON_TRIGGERS" ADD CONSTRAINT "SYS_C008659" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_CRON_TRIGGERS" ADD CONSTRAINT "SYS_C008660" CHECK ("TRIGGER_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_CRON_TRIGGERS" ADD CONSTRAINT "SYS_C008661" CHECK ("TRIGGER_GROUP" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_CRON_TRIGGERS" ADD CONSTRAINT "SYS_C008662" CHECK ("CRON_EXPRESSION" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
-- ----------------------------
-- Primary Key structure for table QRTZ_FIRED_TRIGGERS
-- ----------------------------
ALTER TABLE "QRTZ_FIRED_TRIGGERS" ADD CONSTRAINT "QRTZ_FIRED_TRIGGER_PK" PRIMARY KEY ("SCHED_NAME", "ENTRY_ID");
-- ----------------------------
-- Checks structure for table QRTZ_FIRED_TRIGGERS
-- ----------------------------
ALTER TABLE "QRTZ_FIRED_TRIGGERS" ADD CONSTRAINT "SYS_C008278" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_FIRED_TRIGGERS" ADD CONSTRAINT "SYS_C008279" CHECK ("ENTRY_ID" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_FIRED_TRIGGERS" ADD CONSTRAINT "SYS_C008280" CHECK ("TRIGGER_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_FIRED_TRIGGERS" ADD CONSTRAINT "SYS_C008281" CHECK ("TRIGGER_GROUP" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_FIRED_TRIGGERS" ADD CONSTRAINT "SYS_C008282" CHECK ("INSTANCE_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_FIRED_TRIGGERS" ADD CONSTRAINT "SYS_C008283" CHECK ("FIRED_TIME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_FIRED_TRIGGERS" ADD CONSTRAINT "SYS_C008284" CHECK ("SCHED_TIME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_FIRED_TRIGGERS" ADD CONSTRAINT "SYS_C008285" CHECK ("PRIORITY" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_FIRED_TRIGGERS" ADD CONSTRAINT "SYS_C008286" CHECK ("STATE" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_FIRED_TRIGGERS" ADD CONSTRAINT "SYS_C008663" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_FIRED_TRIGGERS" ADD CONSTRAINT "SYS_C008664" CHECK ("ENTRY_ID" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_FIRED_TRIGGERS" ADD CONSTRAINT "SYS_C008665" CHECK ("TRIGGER_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_FIRED_TRIGGERS" ADD CONSTRAINT "SYS_C008666" CHECK ("TRIGGER_GROUP" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_FIRED_TRIGGERS" ADD CONSTRAINT "SYS_C008667" CHECK ("INSTANCE_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_FIRED_TRIGGERS" ADD CONSTRAINT "SYS_C008668" CHECK ("FIRED_TIME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_FIRED_TRIGGERS" ADD CONSTRAINT "SYS_C008669" CHECK ("SCHED_TIME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_FIRED_TRIGGERS" ADD CONSTRAINT "SYS_C008670" CHECK ("PRIORITY" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_FIRED_TRIGGERS" ADD CONSTRAINT "SYS_C008671" CHECK ("STATE" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
-- ----------------------------
-- Indexes structure for table QRTZ_FIRED_TRIGGERS
-- ----------------------------
CREATE INDEX "IDX_QRTZ_FT_INST_JOB_REQ_RCVRY"
ON "QRTZ_FIRED_TRIGGERS" ("SCHED_NAME" ASC, "INSTANCE_NAME" ASC, "REQUESTS_RECOVERY" ASC)
LOGGING
VISIBLE
PCTFREE 10
INITRANS 2
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
);
CREATE INDEX "IDX_QRTZ_FT_JG"
ON "QRTZ_FIRED_TRIGGERS" ("SCHED_NAME" ASC, "JOB_GROUP" ASC)
LOGGING
ONLINE
NOSORT
VISIBLE
PCTFREE 10
INITRANS 2
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
);
CREATE INDEX "IDX_QRTZ_FT_J_G"
ON "QRTZ_FIRED_TRIGGERS" ("SCHED_NAME" ASC, "JOB_NAME" ASC, "JOB_GROUP" ASC)
LOGGING
VISIBLE
PCTFREE 10
INITRANS 2
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
);
CREATE INDEX "IDX_QRTZ_FT_TG"
ON "QRTZ_FIRED_TRIGGERS" ("SCHED_NAME" ASC, "TRIGGER_GROUP" ASC) LOCAL
LOGGING
NOSORT
VISIBLE
PCTFREE 10
INITRANS 2
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
);
-- ----------------------------
-- Primary Key structure for table QRTZ_JOB_DETAILS
-- ----------------------------
ALTER TABLE "QRTZ_JOB_DETAILS" ADD CONSTRAINT "QRTZ_JOB_DETAILS_PK" PRIMARY KEY ("SCHED_NAME", "JOB_NAME", "JOB_GROUP");
-- ----------------------------
-- Checks structure for table QRTZ_JOB_DETAILS
-- ----------------------------
ALTER TABLE "QRTZ_JOB_DETAILS" ADD CONSTRAINT "SYS_C008228" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_JOB_DETAILS" ADD CONSTRAINT "SYS_C008229" CHECK ("JOB_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_JOB_DETAILS" ADD CONSTRAINT "SYS_C008230" CHECK ("JOB_GROUP" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_JOB_DETAILS" ADD CONSTRAINT "SYS_C008231" CHECK ("JOB_CLASS_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_JOB_DETAILS" ADD CONSTRAINT "SYS_C008232" CHECK ("IS_DURABLE" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_JOB_DETAILS" ADD CONSTRAINT "SYS_C008233" CHECK ("IS_NONCONCURRENT" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_JOB_DETAILS" ADD CONSTRAINT "SYS_C008234" CHECK ("IS_UPDATE_DATA" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_JOB_DETAILS" ADD CONSTRAINT "SYS_C008235" CHECK ("REQUESTS_RECOVERY" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
-- ----------------------------
-- Indexes structure for table QRTZ_JOB_DETAILS
-- ----------------------------
CREATE INDEX "IDX_QRTZ_J_GRP"
ON "QRTZ_JOB_DETAILS" ("SCHED_NAME" ASC, "JOB_GROUP" ASC)
LOGGING
VISIBLE
PCTFREE 10
INITRANS 2
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
);
CREATE INDEX "IDX_QRTZ_J_REQ_RECOVERY"
ON "QRTZ_JOB_DETAILS" ("SCHED_NAME" ASC, "REQUESTS_RECOVERY" ASC) LOCAL
LOGGING
VISIBLE
PCTFREE 10
INITRANS 2
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
);
-- ----------------------------
-- Primary Key structure for table QRTZ_LOCKS
-- ----------------------------
ALTER TABLE "QRTZ_LOCKS" ADD CONSTRAINT "QRTZ_LOCKS_PK" PRIMARY KEY ("SCHED_NAME", "LOCK_NAME");
-- ----------------------------
-- Checks structure for table QRTZ_LOCKS
-- ----------------------------
ALTER TABLE "QRTZ_LOCKS" ADD CONSTRAINT "SYS_C008293" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_LOCKS" ADD CONSTRAINT "SYS_C008294" CHECK ("LOCK_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_LOCKS" ADD CONSTRAINT "SYS_C008672" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_LOCKS" ADD CONSTRAINT "SYS_C008673" CHECK ("LOCK_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
-- ----------------------------
-- Primary Key structure for table QRTZ_PAUSED_TRIGGER_GRPS
-- ----------------------------
ALTER TABLE "QRTZ_PAUSED_TRIGGER_GRPS" ADD CONSTRAINT "QRTZ_PAUSED_TRIG_GRPS_PK" PRIMARY KEY ("SCHED_NAME", "TRIGGER_GROUP");
-- ----------------------------
-- Checks structure for table QRTZ_PAUSED_TRIGGER_GRPS
-- ----------------------------
ALTER TABLE "QRTZ_PAUSED_TRIGGER_GRPS" ADD CONSTRAINT "SYS_C008275" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_PAUSED_TRIGGER_GRPS" ADD CONSTRAINT "SYS_C008276" CHECK ("TRIGGER_GROUP" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_PAUSED_TRIGGER_GRPS" ADD CONSTRAINT "SYS_C008674" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_PAUSED_TRIGGER_GRPS" ADD CONSTRAINT "SYS_C008675" CHECK ("TRIGGER_GROUP" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
-- ----------------------------
-- Primary Key structure for table QRTZ_SCHEDULER_STATE
-- ----------------------------
ALTER TABLE "QRTZ_SCHEDULER_STATE" ADD CONSTRAINT "QRTZ_SCHEDULER_STATE_PK" PRIMARY KEY ("SCHED_NAME", "INSTANCE_NAME");
-- ----------------------------
-- Checks structure for table QRTZ_SCHEDULER_STATE
-- ----------------------------
ALTER TABLE "QRTZ_SCHEDULER_STATE" ADD CONSTRAINT "SYS_C008288" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SCHEDULER_STATE" ADD CONSTRAINT "SYS_C008289" CHECK ("INSTANCE_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SCHEDULER_STATE" ADD CONSTRAINT "SYS_C008290" CHECK ("LAST_CHECKIN_TIME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SCHEDULER_STATE" ADD CONSTRAINT "SYS_C008291" CHECK ("CHECKIN_INTERVAL" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SCHEDULER_STATE" ADD CONSTRAINT "SYS_C008676" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SCHEDULER_STATE" ADD CONSTRAINT "SYS_C008677" CHECK ("INSTANCE_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SCHEDULER_STATE" ADD CONSTRAINT "SYS_C008678" CHECK ("LAST_CHECKIN_TIME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SCHEDULER_STATE" ADD CONSTRAINT "SYS_C008679" CHECK ("CHECKIN_INTERVAL" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
-- ----------------------------
-- Primary Key structure for table QRTZ_SIMPLE_TRIGGERS
-- ----------------------------
ALTER TABLE "QRTZ_SIMPLE_TRIGGERS" ADD CONSTRAINT "QRTZ_SIMPLE_TRIG_PK" PRIMARY KEY ("SCHED_NAME", "TRIGGER_NAME", "TRIGGER_GROUP");
-- ----------------------------
-- Checks structure for table QRTZ_SIMPLE_TRIGGERS
-- ----------------------------
ALTER TABLE "QRTZ_SIMPLE_TRIGGERS" ADD CONSTRAINT "SYS_C008247" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SIMPLE_TRIGGERS" ADD CONSTRAINT "SYS_C008248" CHECK ("TRIGGER_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SIMPLE_TRIGGERS" ADD CONSTRAINT "SYS_C008249" CHECK ("TRIGGER_GROUP" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SIMPLE_TRIGGERS" ADD CONSTRAINT "SYS_C008250" CHECK ("REPEAT_COUNT" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SIMPLE_TRIGGERS" ADD CONSTRAINT "SYS_C008251" CHECK ("REPEAT_INTERVAL" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SIMPLE_TRIGGERS" ADD CONSTRAINT "SYS_C008252" CHECK ("TIMES_TRIGGERED" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SIMPLE_TRIGGERS" ADD CONSTRAINT "SYS_C008680" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SIMPLE_TRIGGERS" ADD CONSTRAINT "SYS_C008681" CHECK ("TRIGGER_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SIMPLE_TRIGGERS" ADD CONSTRAINT "SYS_C008682" CHECK ("TRIGGER_GROUP" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SIMPLE_TRIGGERS" ADD CONSTRAINT "SYS_C008683" CHECK ("REPEAT_COUNT" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SIMPLE_TRIGGERS" ADD CONSTRAINT "SYS_C008684" CHECK ("REPEAT_INTERVAL" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SIMPLE_TRIGGERS" ADD CONSTRAINT "SYS_C008685" CHECK ("TIMES_TRIGGERED" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
-- ----------------------------
-- Primary Key structure for table QRTZ_SIMPROP_TRIGGERS
-- ----------------------------
ALTER TABLE "QRTZ_SIMPROP_TRIGGERS" ADD CONSTRAINT "QRTZ_SIMPROP_TRIG_PK" PRIMARY KEY ("SCHED_NAME", "TRIGGER_NAME", "TRIGGER_GROUP");
-- ----------------------------
-- Checks structure for table QRTZ_SIMPROP_TRIGGERS
-- ----------------------------
ALTER TABLE "QRTZ_SIMPROP_TRIGGERS" ADD CONSTRAINT "SYS_C008261" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SIMPROP_TRIGGERS" ADD CONSTRAINT "SYS_C008262" CHECK ("TRIGGER_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SIMPROP_TRIGGERS" ADD CONSTRAINT "SYS_C008263" CHECK ("TRIGGER_GROUP" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SIMPROP_TRIGGERS" ADD CONSTRAINT "SYS_C008686" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SIMPROP_TRIGGERS" ADD CONSTRAINT "SYS_C008687" CHECK ("TRIGGER_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_SIMPROP_TRIGGERS" ADD CONSTRAINT "SYS_C008688" CHECK ("TRIGGER_GROUP" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
-- ----------------------------
-- Primary Key structure for table QRTZ_TRIGGERS
-- ----------------------------
ALTER TABLE "QRTZ_TRIGGERS" ADD CONSTRAINT "QRTZ_TRIGGERS_PK" PRIMARY KEY ("SCHED_NAME", "TRIGGER_NAME", "TRIGGER_GROUP");
-- ----------------------------
-- Checks structure for table QRTZ_TRIGGERS
-- ----------------------------
ALTER TABLE "QRTZ_TRIGGERS" ADD CONSTRAINT "SYS_C008237" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_TRIGGERS" ADD CONSTRAINT "SYS_C008238" CHECK ("TRIGGER_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_TRIGGERS" ADD CONSTRAINT "SYS_C008239" CHECK ("TRIGGER_GROUP" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_TRIGGERS" ADD CONSTRAINT "SYS_C008240" CHECK ("JOB_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_TRIGGERS" ADD CONSTRAINT "SYS_C008241" CHECK ("JOB_GROUP" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_TRIGGERS" ADD CONSTRAINT "SYS_C008242" CHECK ("TRIGGER_STATE" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_TRIGGERS" ADD CONSTRAINT "SYS_C008243" CHECK ("TRIGGER_TYPE" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_TRIGGERS" ADD CONSTRAINT "SYS_C008244" CHECK ("START_TIME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_TRIGGERS" ADD CONSTRAINT "SYS_C008689" CHECK ("SCHED_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_TRIGGERS" ADD CONSTRAINT "SYS_C008690" CHECK ("TRIGGER_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_TRIGGERS" ADD CONSTRAINT "SYS_C008691" CHECK ("TRIGGER_GROUP" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_TRIGGERS" ADD CONSTRAINT "SYS_C008692" CHECK ("JOB_NAME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_TRIGGERS" ADD CONSTRAINT "SYS_C008693" CHECK ("JOB_GROUP" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_TRIGGERS" ADD CONSTRAINT "SYS_C008694" CHECK ("TRIGGER_STATE" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_TRIGGERS" ADD CONSTRAINT "SYS_C008695" CHECK ("TRIGGER_TYPE" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
ALTER TABLE "QRTZ_TRIGGERS" ADD CONSTRAINT "SYS_C008696" CHECK ("START_TIME" IS NOT NULL) NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
-- ----------------------------
-- Indexes structure for table QRTZ_TRIGGERS
-- ----------------------------
CREATE INDEX "IDX_QRTZ_T_C"
ON "QRTZ_TRIGGERS" ("SCHED_NAME" ASC, "CALENDAR_NAME" ASC) LOCAL
LOGGING
ONLINE
NOSORT
VISIBLE
PCTFREE 10
INITRANS 2
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
);
CREATE INDEX "IDX_QRTZ_T_J"
ON "QRTZ_TRIGGERS" ("SCHED_NAME" ASC, "JOB_NAME" ASC, "JOB_GROUP" ASC)
LOGGING
VISIBLE
PCTFREE 10
INITRANS 2
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
);
CREATE INDEX "IDX_QRTZ_T_JG"
ON "QRTZ_TRIGGERS" ("SCHED_NAME" ASC, "JOB_GROUP" ASC) LOCAL
LOGGING
ONLINE
NOSORT
VISIBLE
PCTFREE 10
INITRANS 2
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
);
CREATE INDEX "IDX_QRTZ_T_NEXT_FIRE_TIME"
ON "QRTZ_TRIGGERS" ("SCHED_NAME" ASC, "NEXT_FIRE_TIME" ASC)
LOGGING
VISIBLE
PCTFREE 10
INITRANS 2
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
);
CREATE INDEX "IDX_QRTZ_T_NFT_ST"
ON "QRTZ_TRIGGERS" ("SCHED_NAME" ASC, "TRIGGER_STATE" ASC, "NEXT_FIRE_TIME" ASC) LOCAL
LOGGING
VISIBLE
PCTFREE 10
INITRANS 2
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
);
CREATE INDEX "IDX_QRTZ_T_NFT_ST_MISFIRE"
ON "QRTZ_TRIGGERS" ("SCHED_NAME" ASC, "MISFIRE_INSTR" ASC, "NEXT_FIRE_TIME" ASC, "TRIGGER_STATE" ASC) LOCAL
LOGGING
ONLINE
NOSORT
VISIBLE
PCTFREE 10
INITRANS 2
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
);
CREATE INDEX "IDX_QRTZ_T_STATE"
ON "QRTZ_TRIGGERS" ("SCHED_NAME" ASC, "TRIGGER_STATE" ASC)
LOGGING
VISIBLE
PCTFREE 10
INITRANS 2
STORAGE (
INITIAL 65536
NEXT 1048576
MINEXTENTS 1
MAXEXTENTS 2147483645
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
);
-- ----------------------------
-- Foreign Keys structure for table QRTZ_BLOB_TRIGGERS
-- ----------------------------
ALTER TABLE "QRTZ_BLOB_TRIGGERS" ADD CONSTRAINT "QRTZ_BLOB_TRIG_TO_TRIG_FK" FOREIGN KEY ("SCHED_NAME", "TRIGGER_NAME", "TRIGGER_GROUP") REFERENCES "QRTZ_TRIGGERS" ("SCHED_NAME", "TRIGGER_NAME", "TRIGGER_GROUP") NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
-- ----------------------------
-- Foreign Keys structure for table QRTZ_CRON_TRIGGERS
-- ----------------------------
ALTER TABLE "QRTZ_CRON_TRIGGERS" ADD CONSTRAINT "QRTZ_CRON_TRIG_TO_TRIG_FK" FOREIGN KEY ("SCHED_NAME", "TRIGGER_NAME", "TRIGGER_GROUP") REFERENCES "QRTZ_TRIGGERS" ("SCHED_NAME", "TRIGGER_NAME", "TRIGGER_GROUP") NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
-- ----------------------------
-- Foreign Keys structure for table QRTZ_SIMPLE_TRIGGERS
-- ----------------------------
ALTER TABLE "QRTZ_SIMPLE_TRIGGERS" ADD CONSTRAINT "QRTZ_SIMPLE_TRIG_TO_TRIG_FK" FOREIGN KEY ("SCHED_NAME", "TRIGGER_NAME", "TRIGGER_GROUP") REFERENCES "QRTZ_TRIGGERS" ("SCHED_NAME", "TRIGGER_NAME", "TRIGGER_GROUP") NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
-- ----------------------------
-- Foreign Keys structure for table QRTZ_SIMPROP_TRIGGERS
-- ----------------------------
ALTER TABLE "QRTZ_SIMPROP_TRIGGERS" ADD CONSTRAINT "QRTZ_SIMPROP_TRIG_TO_TRIG_FK" FOREIGN KEY ("SCHED_NAME", "TRIGGER_NAME", "TRIGGER_GROUP") REFERENCES "QRTZ_TRIGGERS" ("SCHED_NAME", "TRIGGER_NAME", "TRIGGER_GROUP") NOT DEFERRABLE INITIALLY IMMEDIATE NORELY VALIDATE;
File diff suppressed because it is too large Load Diff
+208
View File
@@ -0,0 +1,208 @@
-- https://github.com/quartz-scheduler/quartz/blob/main/quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_postgres.sql
-- Thanks to Patrick Lightbody for submitting this...
--
-- In your Quartz properties file, you'll need to set
-- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
DROP TABLE IF EXISTS QRTZ_LOCKS;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
DROP TABLE IF EXISTS QRTZ_CALENDARS;
CREATE TABLE QRTZ_JOB_DETAILS
(
SCHED_NAME VARCHAR(120) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
IS_DURABLE BOOL NOT NULL,
IS_NONCONCURRENT BOOL NOT NULL,
IS_UPDATE_DATA BOOL NOT NULL,
REQUESTS_RECOVERY BOOL NOT NULL,
JOB_DATA BYTEA NULL,
PRIMARY KEY (SCHED_NAME, JOB_NAME, JOB_GROUP)
);
CREATE TABLE QRTZ_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
JOB_NAME VARCHAR(200) NOT NULL,
JOB_GROUP VARCHAR(200) NOT NULL,
DESCRIPTION VARCHAR(250) NULL,
NEXT_FIRE_TIME BIGINT NULL,
PREV_FIRE_TIME BIGINT NULL,
PRIORITY INTEGER NULL,
TRIGGER_STATE VARCHAR(16) NOT NULL,
TRIGGER_TYPE VARCHAR(8) NOT NULL,
START_TIME BIGINT NOT NULL,
END_TIME BIGINT NULL,
CALENDAR_NAME VARCHAR(200) NULL,
MISFIRE_INSTR SMALLINT NULL,
JOB_DATA BYTEA NULL,
PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME, JOB_NAME, JOB_GROUP)
REFERENCES QRTZ_JOB_DETAILS (SCHED_NAME, JOB_NAME, JOB_GROUP)
);
CREATE TABLE QRTZ_SIMPLE_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
REPEAT_COUNT BIGINT NOT NULL,
REPEAT_INTERVAL BIGINT NOT NULL,
TIMES_TRIGGERED BIGINT NOT NULL,
PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
);
CREATE TABLE QRTZ_CRON_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
CRON_EXPRESSION VARCHAR(120) NOT NULL,
TIME_ZONE_ID VARCHAR(80),
PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
);
CREATE TABLE QRTZ_SIMPROP_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
STR_PROP_1 VARCHAR(512) NULL,
STR_PROP_2 VARCHAR(512) NULL,
STR_PROP_3 VARCHAR(512) NULL,
INT_PROP_1 INT NULL,
INT_PROP_2 INT NULL,
LONG_PROP_1 BIGINT NULL,
LONG_PROP_2 BIGINT NULL,
DEC_PROP_1 NUMERIC(13, 4) NULL,
DEC_PROP_2 NUMERIC(13, 4) NULL,
BOOL_PROP_1 BOOL NULL,
BOOL_PROP_2 BOOL NULL,
PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
);
CREATE TABLE QRTZ_BLOB_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
BLOB_DATA BYTEA NULL,
PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
);
CREATE TABLE QRTZ_CALENDARS
(
SCHED_NAME VARCHAR(120) NOT NULL,
CALENDAR_NAME VARCHAR(200) NOT NULL,
CALENDAR BYTEA NOT NULL,
PRIMARY KEY (SCHED_NAME, CALENDAR_NAME)
);
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
(
SCHED_NAME VARCHAR(120) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
PRIMARY KEY (SCHED_NAME, TRIGGER_GROUP)
);
CREATE TABLE QRTZ_FIRED_TRIGGERS
(
SCHED_NAME VARCHAR(120) NOT NULL,
ENTRY_ID VARCHAR(95) NOT NULL,
TRIGGER_NAME VARCHAR(200) NOT NULL,
TRIGGER_GROUP VARCHAR(200) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
FIRED_TIME BIGINT NOT NULL,
SCHED_TIME BIGINT NOT NULL,
PRIORITY INTEGER NOT NULL,
STATE VARCHAR(16) NOT NULL,
JOB_NAME VARCHAR(200) NULL,
JOB_GROUP VARCHAR(200) NULL,
IS_NONCONCURRENT BOOL NULL,
REQUESTS_RECOVERY BOOL NULL,
PRIMARY KEY (SCHED_NAME, ENTRY_ID)
);
CREATE TABLE QRTZ_SCHEDULER_STATE
(
SCHED_NAME VARCHAR(120) NOT NULL,
INSTANCE_NAME VARCHAR(200) NOT NULL,
LAST_CHECKIN_TIME BIGINT NOT NULL,
CHECKIN_INTERVAL BIGINT NOT NULL,
PRIMARY KEY (SCHED_NAME, INSTANCE_NAME)
);
CREATE TABLE QRTZ_LOCKS
(
SCHED_NAME VARCHAR(120) NOT NULL,
LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME, LOCK_NAME)
);
CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY
ON QRTZ_JOB_DETAILS (SCHED_NAME, REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_J_GRP
ON QRTZ_JOB_DETAILS (SCHED_NAME, JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_J
ON QRTZ_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_JG
ON QRTZ_TRIGGERS (SCHED_NAME, JOB_GROUP);
CREATE INDEX IDX_QRTZ_T_C
ON QRTZ_TRIGGERS (SCHED_NAME, CALENDAR_NAME);
CREATE INDEX IDX_QRTZ_T_G
ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_T_STATE
ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_STATE
ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_N_G_STATE
ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP, TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME
ON QRTZ_TRIGGERS (SCHED_NAME, NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST
ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE, NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE
ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE
ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP
ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_GROUP, TRIGGER_STATE);
CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME
ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME);
CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY
ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME, REQUESTS_RECOVERY);
CREATE INDEX IDX_QRTZ_FT_J_G
ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_JG
ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_GROUP);
CREATE INDEX IDX_QRTZ_FT_T_G
ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP);
CREATE INDEX IDX_QRTZ_FT_TG
ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_GROUP);
COMMIT;
File diff suppressed because it is too large Load Diff
+533
View File
@@ -0,0 +1,533 @@
/*
注意:仅仅需要 Quartz 定时任务的场景,可选!!!
Date: 30/04/2024 09:54:18
*/
-- ----------------------------
-- Table structure for QRTZ_BLOB_TRIGGERS
-- ----------------------------
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[QRTZ_BLOB_TRIGGERS]') AND type IN ('U'))
DROP TABLE [dbo].[QRTZ_BLOB_TRIGGERS]
GO
CREATE TABLE [dbo].[QRTZ_BLOB_TRIGGERS] (
[SCHED_NAME] varchar(120) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[TRIGGER_NAME] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[TRIGGER_GROUP] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[BLOB_DATA] varbinary(max) NULL
)
GO
ALTER TABLE [dbo].[QRTZ_BLOB_TRIGGERS] SET (LOCK_ESCALATION = TABLE)
GO
-- ----------------------------
-- Records of QRTZ_BLOB_TRIGGERS
-- ----------------------------
BEGIN TRANSACTION
GO
COMMIT
GO
-- ----------------------------
-- Table structure for QRTZ_CALENDARS
-- ----------------------------
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[QRTZ_CALENDARS]') AND type IN ('U'))
DROP TABLE [dbo].[QRTZ_CALENDARS]
GO
CREATE TABLE [dbo].[QRTZ_CALENDARS] (
[SCHED_NAME] varchar(120) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[CALENDAR_NAME] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[CALENDAR] varbinary(max) NOT NULL
)
GO
ALTER TABLE [dbo].[QRTZ_CALENDARS] SET (LOCK_ESCALATION = TABLE)
GO
-- ----------------------------
-- Records of QRTZ_CALENDARS
-- ----------------------------
BEGIN TRANSACTION
GO
COMMIT
GO
-- ----------------------------
-- Table structure for QRTZ_CRON_TRIGGERS
-- ----------------------------
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[QRTZ_CRON_TRIGGERS]') AND type IN ('U'))
DROP TABLE [dbo].[QRTZ_CRON_TRIGGERS]
GO
CREATE TABLE [dbo].[QRTZ_CRON_TRIGGERS] (
[SCHED_NAME] varchar(120) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[TRIGGER_NAME] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[TRIGGER_GROUP] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[CRON_EXPRESSION] varchar(120) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[TIME_ZONE_ID] varchar(80) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
)
GO
ALTER TABLE [dbo].[QRTZ_CRON_TRIGGERS] SET (LOCK_ESCALATION = TABLE)
GO
-- ----------------------------
-- Records of QRTZ_CRON_TRIGGERS
-- ----------------------------
BEGIN TRANSACTION
GO
GO
COMMIT
GO
-- ----------------------------
-- Table structure for QRTZ_FIRED_TRIGGERS
-- ----------------------------
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[QRTZ_FIRED_TRIGGERS]') AND type IN ('U'))
DROP TABLE [dbo].[QRTZ_FIRED_TRIGGERS]
GO
CREATE TABLE [dbo].[QRTZ_FIRED_TRIGGERS] (
[SCHED_NAME] varchar(120) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[ENTRY_ID] varchar(95) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[TRIGGER_NAME] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[TRIGGER_GROUP] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[INSTANCE_NAME] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[FIRED_TIME] bigint NOT NULL,
[SCHED_TIME] bigint NOT NULL,
[PRIORITY] int NOT NULL,
[STATE] varchar(16) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[JOB_NAME] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[JOB_GROUP] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[IS_NONCONCURRENT] varchar(1) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[REQUESTS_RECOVERY] varchar(1) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
)
GO
ALTER TABLE [dbo].[QRTZ_FIRED_TRIGGERS] SET (LOCK_ESCALATION = TABLE)
GO
-- ----------------------------
-- Records of QRTZ_FIRED_TRIGGERS
-- ----------------------------
BEGIN TRANSACTION
GO
COMMIT
GO
-- ----------------------------
-- Table structure for QRTZ_JOB_DETAILS
-- ----------------------------
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[QRTZ_JOB_DETAILS]') AND type IN ('U'))
DROP TABLE [dbo].[QRTZ_JOB_DETAILS]
GO
CREATE TABLE [dbo].[QRTZ_JOB_DETAILS] (
[SCHED_NAME] varchar(120) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[JOB_NAME] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[JOB_GROUP] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[DESCRIPTION] varchar(250) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[JOB_CLASS_NAME] varchar(250) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[IS_DURABLE] varchar(1) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[IS_NONCONCURRENT] varchar(1) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[IS_UPDATE_DATA] varchar(1) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[REQUESTS_RECOVERY] varchar(1) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[JOB_DATA] varbinary(max) NULL
)
GO
ALTER TABLE [dbo].[QRTZ_JOB_DETAILS] SET (LOCK_ESCALATION = TABLE)
GO
-- ----------------------------
-- Records of QRTZ_JOB_DETAILS
-- ----------------------------
BEGIN TRANSACTION
GO
GO
COMMIT
GO
-- ----------------------------
-- Table structure for QRTZ_LOCKS
-- ----------------------------
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[QRTZ_LOCKS]') AND type IN ('U'))
DROP TABLE [dbo].[QRTZ_LOCKS]
GO
CREATE TABLE [dbo].[QRTZ_LOCKS] (
[SCHED_NAME] varchar(120) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[LOCK_NAME] varchar(40) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL
)
GO
ALTER TABLE [dbo].[QRTZ_LOCKS] SET (LOCK_ESCALATION = TABLE)
GO
-- ----------------------------
-- Records of QRTZ_LOCKS
-- ----------------------------
BEGIN TRANSACTION
GO
COMMIT
GO
-- ----------------------------
-- Table structure for QRTZ_PAUSED_TRIGGER_GRPS
-- ----------------------------
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[QRTZ_PAUSED_TRIGGER_GRPS]') AND type IN ('U'))
DROP TABLE [dbo].[QRTZ_PAUSED_TRIGGER_GRPS]
GO
CREATE TABLE [dbo].[QRTZ_PAUSED_TRIGGER_GRPS] (
[SCHED_NAME] varchar(120) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[TRIGGER_GROUP] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL
)
GO
ALTER TABLE [dbo].[QRTZ_PAUSED_TRIGGER_GRPS] SET (LOCK_ESCALATION = TABLE)
GO
-- ----------------------------
-- Records of QRTZ_PAUSED_TRIGGER_GRPS
-- ----------------------------
BEGIN TRANSACTION
GO
COMMIT
GO
-- ----------------------------
-- Table structure for QRTZ_SCHEDULER_STATE
-- ----------------------------
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[QRTZ_SCHEDULER_STATE]') AND type IN ('U'))
DROP TABLE [dbo].[QRTZ_SCHEDULER_STATE]
GO
CREATE TABLE [dbo].[QRTZ_SCHEDULER_STATE] (
[SCHED_NAME] varchar(120) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[INSTANCE_NAME] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[LAST_CHECKIN_TIME] bigint NOT NULL,
[CHECKIN_INTERVAL] bigint NOT NULL
)
GO
ALTER TABLE [dbo].[QRTZ_SCHEDULER_STATE] SET (LOCK_ESCALATION = TABLE)
GO
-- ----------------------------
-- Records of QRTZ_SCHEDULER_STATE
-- ----------------------------
BEGIN TRANSACTION
GO
COMMIT
GO
-- ----------------------------
-- Table structure for QRTZ_SIMPLE_TRIGGERS
-- ----------------------------
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[QRTZ_SIMPLE_TRIGGERS]') AND type IN ('U'))
DROP TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS]
GO
CREATE TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] (
[SCHED_NAME] varchar(120) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[TRIGGER_NAME] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[TRIGGER_GROUP] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[REPEAT_COUNT] bigint NOT NULL,
[REPEAT_INTERVAL] bigint NOT NULL,
[TIMES_TRIGGERED] bigint NOT NULL
)
GO
ALTER TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] SET (LOCK_ESCALATION = TABLE)
GO
-- ----------------------------
-- Records of QRTZ_SIMPLE_TRIGGERS
-- ----------------------------
BEGIN TRANSACTION
GO
COMMIT
GO
-- ----------------------------
-- Table structure for QRTZ_SIMPROP_TRIGGERS
-- ----------------------------
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[QRTZ_SIMPROP_TRIGGERS]') AND type IN ('U'))
DROP TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS]
GO
CREATE TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] (
[SCHED_NAME] varchar(120) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[TRIGGER_NAME] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[TRIGGER_GROUP] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[STR_PROP_1] varchar(512) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[STR_PROP_2] varchar(512) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[STR_PROP_3] varchar(512) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[INT_PROP_1] int NULL,
[INT_PROP_2] int NULL,
[LONG_PROP_1] bigint NULL,
[LONG_PROP_2] bigint NULL,
[DEC_PROP_1] numeric(13,4) NULL,
[DEC_PROP_2] numeric(13,4) NULL,
[BOOL_PROP_1] varchar(1) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[BOOL_PROP_2] varchar(1) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
)
GO
ALTER TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] SET (LOCK_ESCALATION = TABLE)
GO
-- ----------------------------
-- Records of QRTZ_SIMPROP_TRIGGERS
-- ----------------------------
BEGIN TRANSACTION
GO
COMMIT
GO
-- ----------------------------
-- Table structure for QRTZ_TRIGGERS
-- ----------------------------
IF EXISTS (SELECT * FROM sys.all_objects WHERE object_id = OBJECT_ID(N'[dbo].[QRTZ_TRIGGERS]') AND type IN ('U'))
DROP TABLE [dbo].[QRTZ_TRIGGERS]
GO
CREATE TABLE [dbo].[QRTZ_TRIGGERS] (
[SCHED_NAME] varchar(120) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[TRIGGER_NAME] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[TRIGGER_GROUP] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[JOB_NAME] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[JOB_GROUP] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[DESCRIPTION] varchar(250) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[NEXT_FIRE_TIME] bigint NULL,
[PREV_FIRE_TIME] bigint NULL,
[PRIORITY] int NULL,
[TRIGGER_STATE] varchar(16) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[TRIGGER_TYPE] varchar(8) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
[START_TIME] bigint NOT NULL,
[END_TIME] bigint NULL,
[CALENDAR_NAME] varchar(200) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
[MISFIRE_INSTR] smallint NULL,
[JOB_DATA] varbinary(max) NULL
)
GO
ALTER TABLE [dbo].[QRTZ_TRIGGERS] SET (LOCK_ESCALATION = TABLE)
GO
-- ----------------------------
-- Records of QRTZ_TRIGGERS
-- ----------------------------
BEGIN TRANSACTION
GO
COMMIT
GO
-- ----------------------------
-- Primary Key structure for table QRTZ_CALENDARS
-- ----------------------------
ALTER TABLE [dbo].[QRTZ_CALENDARS] ADD CONSTRAINT [PK_QRTZ_CALENDARS] PRIMARY KEY CLUSTERED ([SCHED_NAME], [CALENDAR_NAME])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
ON [PRIMARY]
GO
-- ----------------------------
-- Indexes structure for table QRTZ_CRON_TRIGGERS
-- ----------------------------
CREATE NONCLUSTERED INDEX [IX_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS]
ON [dbo].[QRTZ_CRON_TRIGGERS] (
[SCHED_NAME] ASC,
[TRIGGER_NAME] ASC,
[TRIGGER_GROUP] ASC
)
GO
-- ----------------------------
-- Primary Key structure for table QRTZ_CRON_TRIGGERS
-- ----------------------------
ALTER TABLE [dbo].[QRTZ_CRON_TRIGGERS] ADD CONSTRAINT [PK_QRTZ_CRON_TRIGGERS] PRIMARY KEY CLUSTERED ([SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
ON [PRIMARY]
GO
-- ----------------------------
-- Primary Key structure for table QRTZ_FIRED_TRIGGERS
-- ----------------------------
ALTER TABLE [dbo].[QRTZ_FIRED_TRIGGERS] ADD CONSTRAINT [PK_QRTZ_FIRED_TRIGGERS] PRIMARY KEY CLUSTERED ([SCHED_NAME], [ENTRY_ID])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
ON [PRIMARY]
GO
-- ----------------------------
-- Primary Key structure for table QRTZ_JOB_DETAILS
-- ----------------------------
ALTER TABLE [dbo].[QRTZ_JOB_DETAILS] ADD CONSTRAINT [PK_QRTZ_JOB_DETAILS] PRIMARY KEY CLUSTERED ([SCHED_NAME], [JOB_NAME], [JOB_GROUP])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
ON [PRIMARY]
GO
-- ----------------------------
-- Primary Key structure for table QRTZ_LOCKS
-- ----------------------------
ALTER TABLE [dbo].[QRTZ_LOCKS] ADD CONSTRAINT [PK_QRTZ_LOCKS] PRIMARY KEY CLUSTERED ([SCHED_NAME], [LOCK_NAME])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
ON [PRIMARY]
GO
-- ----------------------------
-- Primary Key structure for table QRTZ_PAUSED_TRIGGER_GRPS
-- ----------------------------
ALTER TABLE [dbo].[QRTZ_PAUSED_TRIGGER_GRPS] ADD CONSTRAINT [PK_QRTZ_PAUSED_TRIGGER_GRPS] PRIMARY KEY CLUSTERED ([SCHED_NAME], [TRIGGER_GROUP])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
ON [PRIMARY]
GO
-- ----------------------------
-- Primary Key structure for table QRTZ_SCHEDULER_STATE
-- ----------------------------
ALTER TABLE [dbo].[QRTZ_SCHEDULER_STATE] ADD CONSTRAINT [PK_QRTZ_SCHEDULER_STATE] PRIMARY KEY CLUSTERED ([SCHED_NAME], [INSTANCE_NAME])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
ON [PRIMARY]
GO
-- ----------------------------
-- Indexes structure for table QRTZ_SIMPLE_TRIGGERS
-- ----------------------------
CREATE NONCLUSTERED INDEX [IX_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS]
ON [dbo].[QRTZ_SIMPLE_TRIGGERS] (
[SCHED_NAME] ASC,
[TRIGGER_NAME] ASC,
[TRIGGER_GROUP] ASC
)
GO
-- ----------------------------
-- Primary Key structure for table QRTZ_SIMPLE_TRIGGERS
-- ----------------------------
ALTER TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] ADD CONSTRAINT [PK_QRTZ_SIMPLE_TRIGGERS] PRIMARY KEY CLUSTERED ([SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
ON [PRIMARY]
GO
-- ----------------------------
-- Indexes structure for table QRTZ_SIMPROP_TRIGGERS
-- ----------------------------
CREATE NONCLUSTERED INDEX [IX_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS]
ON [dbo].[QRTZ_SIMPROP_TRIGGERS] (
[SCHED_NAME] ASC,
[TRIGGER_NAME] ASC,
[TRIGGER_GROUP] ASC
)
GO
-- ----------------------------
-- Primary Key structure for table QRTZ_SIMPROP_TRIGGERS
-- ----------------------------
ALTER TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] ADD CONSTRAINT [PK_QRTZ_SIMPROP_TRIGGERS] PRIMARY KEY CLUSTERED ([SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
ON [PRIMARY]
GO
-- ----------------------------
-- Indexes structure for table QRTZ_TRIGGERS
-- ----------------------------
CREATE NONCLUSTERED INDEX [IX_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS]
ON [dbo].[QRTZ_TRIGGERS] (
[SCHED_NAME] ASC,
[TRIGGER_NAME] ASC,
[TRIGGER_GROUP] ASC
)
GO
-- ----------------------------
-- Primary Key structure for table QRTZ_TRIGGERS
-- ----------------------------
ALTER TABLE [dbo].[QRTZ_TRIGGERS] ADD CONSTRAINT [PK_QRTZ_TRIGGERS] PRIMARY KEY CLUSTERED ([SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
ON [PRIMARY]
GO
-- ----------------------------
-- Foreign Keys structure for table QRTZ_BLOB_TRIGGERS
-- ----------------------------
ALTER TABLE [dbo].[QRTZ_BLOB_TRIGGERS] ADD CONSTRAINT [FK_QRTZ_BLOB_TRIGGERS_QRTZ_TRIGGERS] FOREIGN KEY ([SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP]) REFERENCES [dbo].[QRTZ_TRIGGERS] ([SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP]) ON DELETE CASCADE ON UPDATE NO ACTION
GO
-- ----------------------------
-- Foreign Keys structure for table QRTZ_CRON_TRIGGERS
-- ----------------------------
ALTER TABLE [dbo].[QRTZ_CRON_TRIGGERS] ADD CONSTRAINT [FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS] FOREIGN KEY ([SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP]) REFERENCES [dbo].[QRTZ_TRIGGERS] ([SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP]) ON DELETE CASCADE ON UPDATE NO ACTION
GO
-- ----------------------------
-- Foreign Keys structure for table QRTZ_SIMPLE_TRIGGERS
-- ----------------------------
ALTER TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] ADD CONSTRAINT [FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS] FOREIGN KEY ([SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP]) REFERENCES [dbo].[QRTZ_TRIGGERS] ([SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP]) ON DELETE CASCADE ON UPDATE NO ACTION
GO
-- ----------------------------
-- Foreign Keys structure for table QRTZ_SIMPROP_TRIGGERS
-- ----------------------------
ALTER TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] ADD CONSTRAINT [FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS] FOREIGN KEY ([SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP]) REFERENCES [dbo].[QRTZ_TRIGGERS] ([SCHED_NAME], [TRIGGER_NAME], [TRIGGER_GROUP]) ON DELETE CASCADE ON UPDATE NO ACTION
GO
-- ----------------------------
-- Foreign Keys structure for table QRTZ_TRIGGERS
-- ----------------------------
ALTER TABLE [dbo].[QRTZ_TRIGGERS] ADD CONSTRAINT [FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS] FOREIGN KEY ([SCHED_NAME], [JOB_NAME], [JOB_GROUP]) REFERENCES [dbo].[QRTZ_JOB_DETAILS] ([SCHED_NAME], [JOB_NAME], [JOB_GROUP]) ON DELETE NO ACTION ON UPDATE NO ACTION
GO
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
# 忽略python虚拟环境
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
+130
View File
@@ -0,0 +1,130 @@
## 0. 友情提示
`sql/tools` 目录下,我们提供一些数据库相关的工具,包括测试数据库的快速启动、MySQL 转换其它数据库等等。
注意!所有的操作,必须在 `sql/tools` 目录下执行。
## 1. 测试数据库的快速启动
基于 Docker Compose,快速启动 MySQL、Oracle、PostgreSQL、SQL Server 等数据库。
注意!使用 Docker Compose 启动完测试数据后,因为会自动导入项目的 SQL 脚本,所以可能需要等待 1-2 分钟。
### 1.1 MySQL
```Bash
docker compose up -d mysql
```
#### 1.2 Oracle
```Bash
## x86 版本
docker compose up -d oracle
## MacBook Apple Silicon
docker compose up -d oracle_m1
```
> 注意:如果使用 MacBook Apple Silicon 版本,它的 ORACLE_SID 不是 XE,而是 FREE!!!
### 1.3 PostgreSQL
```Bash
docker compose up -d postgres
```
### 1.4 SQL Server
```Bash
docker compose up -d sqlserver
# 注意:启动完 sqlserver 后,需要手动再执行如下命令,因为 SQL Server 不支持初始化脚本
docker compose exec sqlserver bash /tmp/create_schema.sh
```
### 1.5 DM 达梦
① 下载达梦 Docker 镜像:<https://eco.dameng.com/download/> 地址,点击“Docker 镜像”选项,进行下载。
② 加载镜像文件,在镜像 tar 文件所在目录运行:
```Bash
docker load -i dm8_20240715_x86_rh6_rq_single.tar
```
③ 在项目 `sql/tools` 目录下运行:
```Bash
docker compose up -d dm8
# 注意:启动完 dm 后,需要手动再执行如下命令,因为 dm 不支持初始化脚本
docker compose exec dm8 bash -c '/opt/dmdbms/bin/disql SYSDBA/SYSDBA001 \`/tmp/schema.sql'
exit
```
### 1.6 KingbaseES 人大金仓
① 下载人大金仓 Docker 镜像:
* [x86_64 版本](https://kingbase.oss-cn-beijing.aliyuncs.com/KESV8R3/V009R001C001B0025-安装包-docker/x86_64/kdb_x86_64_V009R001C001B0025.tar) 【Windows 选择这个】
* [aarch64 版本](https://kingbase.oss-cn-beijing.aliyuncs.com/KESV8R3/V009R001C001B0025-安装包-docker/aarch64/kdb_aarch64_V009R001C001B0025.tar) 【MacBook Apple Silicon 选择这个】
② 加载镜像文件,在镜像 tar 文件所在目录运行:
```Bash
docker load -i kdb_x86_64_V009R001C001B0025.tar
```
③ 在项目 `sql/tools` 目录下运行:
```Bash
docker compose up -d kingbase
# 注意:启动完 kingbase 后,需要手动再执行如下命令
docker compose exec kingbase bash -c 'ksql -U $DB_USER -d test -f /tmp/schema.sql'
```
### 1.7 华为 OpenGauss
```Bash
docker compose up -d opengauss
# 注意:启动完 opengauss 后,需要手动再执行如下命令
docker compose exec opengauss bash -c '/usr/local/opengauss/bin/gsql -U $GS_USERNAME -W $GS_PASSWORD -d postgres -f /tmp/schema.sql'
```
## 1.X 容器的销毁重建
开发测试过程中,有时候需要创建全新干净的数据库。由于测试数据 Docker 容器采用数据卷 Volume 挂载数据库实例的数据目录,因此销毁数据需要停止容器后,删除数据卷,然后再重新创建容器。
以 postgres 为例,操作如下:
```Bash
docker compose down postgres
docker volume rm ruoyi-vue-pro_postgres
```
## 2. MySQL 转换其它数据库
项目提供了 `sql/tools/convertor.py` 脚本,支持将 MySQL 转换为 Oracle、PostgreSQL、SQL Server、达梦、人大金仓、OpenGauss 等数据库的脚本。
### 2.1 实现原理
通过读取 MySQL 的 `sql/mysql/ruoyi-vue-pro.sql` 数据库文件,转换成对应的数据库脚本。
### 2.2 使用方法
① 安装依赖库 `simple-ddl-parser`
```bash
pip install simple-ddl-parser
# pip3 install simple-ddl-parser
```
② 在 `sql/tools/` 目录下,执行如下命令打印生成 postgres 的脚本内容,其他可选参数有:`oracle``sqlserver``dm8``kingbase``opengauss`
```Bash
python3 convertor.py postgres
# python3 convertor.py postgres > tmp.sql
```
程序将 SQL 脚本打印到终端,可以重定向到临时文件 `tmp.sql`
确认无误后,可以利用 IDEA 进行格式化。当然,也可以直接导入到数据库中。
+945
View File
@@ -0,0 +1,945 @@
# encoding=utf8
"""芋道系统数据库迁移工具
Author: dhb52 (https://gitee.com/dhb52)
pip install simple-ddl-parser
or with uv
uv run --with simple-ddl-parser convertor.py postgres > ../postgresql/ruoyi-vue-pro.sql 239ms  四 5/22 21:03:16 2025
uv run --with simple-ddl-parser convertor.py sqlserver > ../sqlserver/ruoyi-vue-pro.sql
uv run --with simple-ddl-parser convertor.py kingbase > ../kingbase/ruoyi-vue-pro.sql
uv run --with simple-ddl-parser convertor.py opengauss > ../opengauss/ruoyi-vue-pro.sql
uv run --with simple-ddl-parser convertor.py oracle > ../oracle/ruoyi-vue-pro.sql
uv run --with simple-ddl-parser convertor.py dm8 > ../dm/ruoyi-vue-pro-dm8.sql
"""
import argparse
import pathlib
import re
import sys
import time
from abc import ABC, abstractmethod
from typing import Dict, Generator, Optional, Tuple, Union
from simple_ddl_parser import DDLParser
PREAMBLE = """/*
Yudao Database Transfer Tool
Source Server Type : MySQL
Target Server Type : {db_type}
Date: {date}
*/
"""
def load_and_clean(sql_file: str) -> str:
"""加载源 SQL 文件,并清理内容方便下一步 ddl 解析
Args:
sql_file (str): sql文件路径
Returns:
str: 清理后的sql文件内容
"""
REPLACE_PAIR_LIST = (
(")\nVALUES ", ") VALUES "),
(" CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ", " "),
(" KEY `", " INDEX `"),
("UNIQUE INDEX", "UNIQUE KEY"),
("b'0'", "'0'"),
("b'1'", "'1'"),
)
content = open(sql_file, encoding="utf-8").read()
for replace_pair in REPLACE_PAIR_LIST:
content = content.replace(*replace_pair)
content = re.sub(r"ENGINE.*COMMENT", "COMMENT", content)
content = re.sub(r"ENGINE.*;", ";", content)
return content
class Convertor(ABC):
def __init__(self, src: str, db_type) -> None:
self.src = src
self.db_type = db_type
self.content = load_and_clean(self.src)
self.table_script_list = re.findall(r"CREATE TABLE [^;]*;", self.content)
@abstractmethod
def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]) -> str:
"""字段类型转换
Args:
type (str): 字段类型
size (Optional[Union[int, Tuple[int]]]): 字段长度描述, 如varchar(255), decimal(10,2)
Returns:
str: 类型定义
"""
pass
@abstractmethod
def gen_create(self, table_ddl: Dict) -> str:
"""生成 create 脚本
Args:
table_ddl (Dict): 表DDL
Returns:
str: 生成脚本
"""
pass
@abstractmethod
def gen_pk(self, table_name: str) -> str:
"""生成主键定义
Args:
table_name (str): 表名
Returns:
str: 生成脚本
"""
pass
@abstractmethod
def gen_index(self, ddl: Dict) -> str:
"""生成索引定义
Args:
table_ddl (Dict): 表DDL
Returns:
str: 生成脚本
"""
pass
@abstractmethod
def gen_comment(self, table_ddl: Dict) -> str:
"""生成字段/表注释
Args:
table_ddl (Dict): 表DDL
Returns:
str: 生成脚本
"""
pass
@abstractmethod
def gen_uk(self, table_ddl: Dict) -> str:
"""生成
Args:
table_ddl (Dict): 表DDL
Returns:
str: 生成脚本
"""
@abstractmethod
def gen_insert(self, table_name: str) -> str:
"""生成 insert 语句块
Args:
table_name (str): 表名
Returns:
str: 生成脚本
"""
pass
def gen_dual(self) -> str:
"""生成虚拟 dual 表
Returns:
str: 生成脚本, 默认返回空脚本, 表示当前数据库无需手工创建
"""
return ""
@staticmethod
def inserts(table_name: str, script_content: str) -> Generator:
PREFIX = f"INSERT INTO `{table_name}`"
# 收集 `table_name` 对应的 insert 语句
for line in script_content.split("\n"):
if line.startswith(PREFIX):
head, tail = line.replace(PREFIX, "").split(" VALUES ", maxsplit=1)
head = head.strip().replace("`", "").lower()
tail = tail.strip().replace(r"\"", '"')
# tail = tail.replace("b'0'", "'0'").replace("b'1'", "'1'")
yield f"INSERT INTO {table_name.lower()} {head} VALUES {tail}"
@staticmethod
def index(ddl: Dict) -> Generator:
"""生成索引定义
Args:
ddl (Dict): 表DDL
Yields:
Generator[str]: create index 语句
"""
def generate_columns(columns):
keys = [
f"{col['name'].lower()}{' ' + col['order'].lower() if col['order'] != 'ASC' else ''}"
for col in columns[0]
]
return ", ".join(keys)
for no, index in enumerate(ddl["index"], 1):
columns = generate_columns(index["columns"])
table_name = ddl["table_name"].lower()
yield f"CREATE INDEX idx_{table_name}_{no:02d} ON {table_name} ({columns})"
@staticmethod
def unique_index(ddl: Dict) -> Generator:
if "constraints" in ddl and "uniques" in ddl["constraints"]:
uk_list = ddl["constraints"]["uniques"]
for uk in uk_list:
table_name = ddl["table_name"]
uk_name = uk["constraint_name"]
uk_columns = uk["columns"]
yield table_name, uk_name, uk_columns
@staticmethod
def filed_comments(table_sql: str) -> Generator:
for line in table_sql.split("\n"):
match = re.match(r"^`([^`]+)`.* COMMENT '([^']+)'", line.strip())
if match:
field = match.group(1)
comment_string = match.group(2).replace("\\n", "\n")
yield field, comment_string
def table_comment(self, table_sql: str) -> str:
match = re.search(r"COMMENT \='([^']+)';", table_sql)
return match.group(1) if match else None
def print(self):
"""打印转换后的sql脚本到终端"""
print(
PREAMBLE.format(
db_type=self.db_type,
date=time.strftime("%Y-%m-%d %H:%M:%S"),
)
)
dual = self.gen_dual()
if dual:
print(
f"""-- ----------------------------
-- Table structure for dual
-- ----------------------------
{dual}
"""
)
error_scripts = []
for table_sql in self.table_script_list:
ddl = DDLParser(table_sql.replace("`", "")).run()
# 如果parse失败, 需要跟进
if len(ddl) == 0:
error_scripts.append(table_sql)
continue
table_ddl = ddl[0]
table_name = table_ddl["table_name"]
# 忽略 quartz 的内容
if table_name.lower().startswith("qrtz"):
continue
# 解析注释
for column in table_ddl["columns"]:
column["comment"] = bytes(column["comment"], "utf-8").decode(
"unicode_escape"
)[1:-1]
table_ddl["comment"] = bytes(table_ddl["comment"], "utf-8").decode(
"unicode_escape"
)[1:-1]
# 为每个表生成个6个基本部分
create = self.gen_create(table_ddl)
pk = self.gen_pk(table_name)
uk = self.gen_uk(table_ddl)
index = self.gen_index(table_ddl)
comment = self.gen_comment(table_ddl)
inserts = self.gen_insert(table_name)
# 组合当前表的DDL脚本
script = f"""{create}
{pk}
{uk}
{index}
{comment}
{inserts}
"""
# 清理
script = re.sub("\n{3,}", "\n\n", script).strip() + "\n"
print(script)
# 将parse失败的脚本打印出来
if error_scripts:
print("!!! 以下内容无法正常解析", file=sys.stderr)
for script in error_scripts:
# print to stderr
print(script, file=sys.stderr)
class PostgreSQLConvertor(Convertor):
def __init__(self, src):
super().__init__(src, "PostgreSQL")
def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]):
"""类型转换"""
type = type.lower()
if type == "varchar":
return f"varchar({size})"
if type in ("int", "int unsigned"):
return "int4"
if type in ("bigint", "bigint unsigned"):
return "int8"
if type == "datetime":
return "timestamp"
if type == "timestamp":
return f"timestamp({size})"
if type == "bit":
return "bool"
if type in ("tinyint", "smallint"):
return "int2"
if type in ("text", "longtext"):
return "text"
if type in ("blob", "mediumblob"):
return "bytea"
if type == "decimal":
return (
f"numeric({','.join(str(s) for s in size)})" if len(size) else "numeric"
)
def gen_create(self, ddl: Dict) -> str:
"""生成 create"""
def _generate_column(col):
name = col["name"].lower()
if name == "deleted":
return "deleted int2 NOT NULL DEFAULT 0"
type = col["type"].lower()
full_type = self.translate_type(type, col["size"])
nullable = "NULL" if col["nullable"] else "NOT NULL"
default = f"DEFAULT {col['default']}" if col["default"] is not None else ""
return f"{name} {full_type} {nullable} {default}"
table_name = ddl["table_name"].lower()
columns = [f"{_generate_column(col).strip()}" for col in ddl["columns"]]
filed_def_list = ",\n ".join(columns)
script = f"""-- ----------------------------
-- Table structure for {table_name}
-- ----------------------------
DROP TABLE IF EXISTS {table_name};
CREATE TABLE {table_name} (
{filed_def_list}
);"""
return script
def gen_index(self, ddl: Dict) -> str:
return "\n".join(f"{script};" for script in self.index(ddl))
def gen_comment(self, table_ddl: Dict) -> str:
"""生成字段及表的注释"""
script = ""
for column in table_ddl["columns"]:
table_comment = column["comment"]
script += (
f"COMMENT ON COLUMN {table_ddl['table_name']}.{column['name']} IS '{table_comment}';"
+ "\n"
)
table_comment = table_ddl["comment"]
if table_comment:
script += (
f"COMMENT ON TABLE {table_ddl['table_name']} IS '{table_comment}';\n"
)
return script
def gen_pk(self, table_name) -> str:
"""生成主键定义"""
return f"ALTER TABLE {table_name} ADD CONSTRAINT pk_{table_name} PRIMARY KEY (id);\n"
def gen_uk(self, table_ddl: Dict) -> str:
script = ""
uk_list = list(Convertor.unique_index(table_ddl))
for idx, (table_name, _, uk_columns) in enumerate(uk_list, 1):
uk_name = f"uk_{table_name}_{idx:02d}"
script += f"CREATE UNIQUE INDEX {uk_name} ON {table_name} ({', '.join(uk_columns)});\n"
return script
def gen_insert(self, table_name: str) -> str:
"""生成 insert 语句,以及根据最后的 insert id+1 生成 Sequence"""
inserts = list(Convertor.inserts(table_name, self.content))
## 生成 insert 脚本
script = ""
last_id = 0
if inserts:
inserts_lines = "\n".join(inserts)
script += f"""\n\n-- ----------------------------
-- Records of {table_name.lower()}
-- ----------------------------
-- @formatter:off
BEGIN;
{inserts_lines}
COMMIT;
-- @formatter:on"""
match = re.search(r"VALUES \((\d+),", inserts[-1])
if match:
last_id = int(match.group(1))
# 生成 Sequence
script += (
"\n\n"
+ f"""DROP SEQUENCE IF EXISTS {table_name}_seq;
CREATE SEQUENCE {table_name}_seq
START {last_id + 1};"""
)
return script
def gen_dual(self) -> str:
return """DROP TABLE IF EXISTS dual;
CREATE TABLE dual
(
id int2
);
COMMENT ON TABLE dual IS '数据库连接的表';
-- ----------------------------
-- Records of dual
-- ----------------------------
-- @formatter:off
INSERT INTO dual VALUES (1);
-- @formatter:on"""
class OracleConvertor(Convertor):
def __init__(self, src):
super().__init__(src, "Oracle")
def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]):
"""类型转换"""
type = type.lower()
if type == "varchar":
return f"varchar2({size if size < 4000 else 4000})"
if type in ("int", "int unsigned"):
return "number"
if type == "bigint" or type == "bigint unsigned":
return "number"
if type == "datetime":
return "date"
if type == "timestamp":
return f"timestamp({size})"
if type == "bit":
return "number(1,0)"
if type in ("tinyint", "smallint"):
return "smallint"
if type in ("text", "longtext"):
return "clob"
if type in ("blob", "mediumblob"):
return "blob"
if type == "decimal":
return (
f"number({','.join(str(s) for s in size)})" if len(size) else "number"
)
def gen_create(self, ddl) -> str:
"""生成 CREATE 语句"""
def generate_column(col):
name = col["name"].lower()
if name == "deleted":
return "deleted number(1,0) DEFAULT 0 NOT NULL"
type = col["type"].lower()
full_type = self.translate_type(type, col["size"])
nullable = "NULL" if col["nullable"] else "NOT NULL"
# Oracle的 INSERT '' 不能通过NOT NULL校验,因此对文字类型字段覆写为 NULL
nullable = "NULL" if type in ("varchar", "text", "longtext") else nullable
default = f"DEFAULT {col['default']}" if col["default"] is not None else ""
# Oracle 中 size 不能作为字段名
field_name = '"size"' if name == "size" else name
# Oracle DEFAULT 定义在 NULLABLE 之前
return f"{field_name} {full_type} {default} {nullable}"
table_name = ddl["table_name"].lower()
columns = [f"{generate_column(col).strip()}" for col in ddl["columns"]]
field_def_list = ",\n ".join(columns)
script = f"""-- ----------------------------
-- Table structure for {table_name}
-- ----------------------------
CREATE TABLE {table_name} (
{field_def_list}
);"""
# oracle INSERT '' 不能通过 NOT NULL 校验
script = script.replace("DEFAULT '' NOT NULL", "DEFAULT '' NULL")
return script
def gen_index(self, ddl: Dict) -> str:
return "\n".join(f"{script};" for script in self.index(ddl))
def gen_comment(self, table_ddl: Dict) -> str:
script = ""
for column in table_ddl["columns"]:
table_comment = column["comment"]
script += (
f"COMMENT ON COLUMN {table_ddl['table_name']}.{column['name']} IS '{table_comment}';"
+ "\n"
)
table_comment = table_ddl["comment"]
if table_comment:
script += (
f"COMMENT ON TABLE {table_ddl['table_name']} IS '{table_comment}';\n"
)
return script
def gen_pk(self, table_name: str) -> str:
"""生成主键定义"""
return f"ALTER TABLE {table_name} ADD CONSTRAINT pk_{table_name} PRIMARY KEY (id);\n"
def gen_uk(self, table_ddl: Dict) -> str:
script = ""
uk_list = list(Convertor.unique_index(table_ddl))
for idx, (table_name, _, uk_columns) in enumerate(uk_list, 1):
uk_name = f"uk_{table_name}_{idx:02d}"
script += f"CREATE UNIQUE INDEX {uk_name} ON {table_name} ({', '.join(uk_columns)});\n"
return script
def gen_index(self, ddl: Dict) -> str:
return "\n".join(f"{script};" for script in self.index(ddl))
def gen_insert(self, table_name: str) -> str:
"""拷贝 INSERT 语句"""
inserts = []
for insert_script in Convertor.inserts(table_name, self.content):
# 对日期数据添加 TO_DATE 转换
insert_script = re.sub(
r"('\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}')",
r"to_date(\g<1>, 'SYYYY-MM-DD HH24:MI:SS')",
insert_script,
)
inserts.append(insert_script)
## 生成 insert 脚本
script = ""
last_id = 0
if inserts:
inserts_lines = "\n".join(inserts)
script += f"""\n\n-- ----------------------------
-- Records of {table_name.lower()}
-- ----------------------------
-- @formatter:off
{inserts_lines}
COMMIT;
-- @formatter:on"""
match = re.search(r"VALUES \((\d+),", inserts[-1])
if match:
last_id = int(match.group(1))
# 生成 Sequence
script += f"""
CREATE SEQUENCE {table_name}_seq
START WITH {last_id + 1};"""
return script
class SQLServerConvertor(Convertor):
"""_summary_
Args:
Convertor (_type_): _description_
"""
def __init__(self, src):
super().__init__(src, "Microsoft SQL Server")
def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]):
"""类型转换"""
type = type.lower()
if type == "varchar":
return f"nvarchar({size if size < 4000 else 4000})"
if type in ("int", "int unsigned"):
return "int"
if type in ("bigint", "bigint unsigned"):
return "bigint"
if type in ("datetime", "timestamp"):
return "datetime2"
if type == "bit":
return "varchar(1)"
if type in ("tinyint", "smallint"):
return "tinyint"
if type in ("text", "longtext"):
return "nvarchar(max)"
if type in ("blob", "mediumblob"):
return "varbinary(max)"
if type == "decimal":
return (
f"numeric({','.join(str(s) for s in size)})" if len(size) else "numeric"
)
def gen_create(self, ddl: Dict) -> str:
"""生成 create"""
def _generate_column(col):
name = col["name"].lower()
if name == "id":
return "id bigint NOT NULL PRIMARY KEY IDENTITY"
if name == "deleted":
return "deleted bit DEFAULT 0 NOT NULL"
type = col["type"].lower()
full_type = self.translate_type(type, col["size"])
nullable = "NULL" if col["nullable"] else "NOT NULL"
default = f"DEFAULT {col['default']}" if col["default"] is not None else ""
return f"{name} {full_type} {default} {nullable}"
table_name = ddl["table_name"].lower()
columns = [f"{_generate_column(col).strip()}" for col in ddl["columns"]]
filed_def_list = ",\n ".join(columns)
script = f"""-- ----------------------------
-- Table structure for {table_name}
-- ----------------------------
DROP TABLE IF EXISTS {table_name}
GO
CREATE TABLE {table_name} (
{filed_def_list}
)
GO"""
return script
def gen_comment(self, table_ddl: Dict) -> str:
"""生成字段及表的注释"""
script = ""
table_name = table_ddl["table_name"]
for column in table_ddl["columns"]:
column_comment = column["comment"]
field = column["name"]
script += f"""EXEC sp_addextendedproperty
'MS_Description', N'{column_comment}',
'SCHEMA', N'dbo',
'TABLE', N'{table_name}',
'COLUMN', N'{field}'
GO
"""
table_comment = table_ddl["comment"]
if table_comment:
script += f"""EXEC sp_addextendedproperty
'MS_Description', N'{table_comment}',
'SCHEMA', N'dbo',
'TABLE', N'{table_name}'
GO
"""
return script
def gen_pk(self, table_name: str) -> str:
"""生成主键定义"""
return ""
def gen_uk(self, table_ddl: Dict) -> str:
script = ""
uk_list = list(Convertor.unique_index(table_ddl))
for idx, (table_name, _, uk_columns) in enumerate(uk_list, 1):
uk_name = f"uk_{table_name}_{idx:02d}"
script += f"CREATE UNIQUE INDEX {uk_name} ON {table_name} ({', '.join(uk_columns)})\nGO"
return script
def gen_index(self, ddl: Dict) -> str:
"""生成 index"""
return "\n".join(f"{script}\nGO" for script in self.index(ddl))
def gen_insert(self, table_name: str) -> str:
"""生成 insert 语句"""
# 收集 `table_name` 对应的 insert 语句
inserts = []
for insert_script in Convertor.inserts(table_name, self.content):
# SQLServer: 字符串前加N,hack,是否存在替换字符串内容的风险
insert_script = insert_script.replace(", '", ", N'").replace(
"VALUES ('", "VALUES (N')"
)
# 删除 insert 的结尾分号
insert_script = re.sub(";$", r"\nGO", insert_script)
inserts.append(insert_script)
## 生成 insert 脚本
script = ""
if inserts:
inserts_lines = "\n".join(inserts)
script += f"""\n\n-- ----------------------------
-- Records of {table_name.lower()}
-- ----------------------------
-- @formatter:off
BEGIN TRANSACTION
GO
SET IDENTITY_INSERT {table_name.lower()} ON
GO
{inserts_lines}
SET IDENTITY_INSERT {table_name.lower()} OFF
GO
COMMIT
GO
-- @formatter:on"""
return script
def gen_dual(self) -> str:
return """DROP TABLE IF EXISTS dual
GO
CREATE TABLE dual
(
id int
)
GO
EXEC sp_addextendedproperty
'MS_Description', N'数据库连接的表',
'SCHEMA', N'dbo',
'TABLE', N'dual'
GO
-- ----------------------------
-- Records of dual
-- ----------------------------
-- @formatter:off
INSERT INTO dual VALUES (1)
GO
-- @formatter:on"""
class DM8Convertor(Convertor):
def __init__(self, src):
super().__init__(src, "DM8")
def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]):
"""类型转换"""
type = type.lower()
if type == "varchar":
return f"varchar({size})"
if type in ("int", "int unsigned"):
return "int"
if type in ("bigint", "bigint unsigned"):
return "bigint"
if type == "datetime":
return "datetime"
if type == "timestamp":
return f"timestamp({size})"
if type == "bit":
return "bit"
if type in ("tinyint", "smallint"):
return "smallint"
if type in ("text", "longtext"):
return "text"
if type in ("blob", "mediumblob"):
return "blob"
if type == "decimal":
return (
f"decimal({','.join(str(s) for s in size)})" if len(size) else "decimal"
)
def gen_create(self, ddl) -> str:
"""生成 CREATE 语句"""
def generate_column(col):
name = col["name"].lower()
if name == "id":
return "id bigint NOT NULL PRIMARY KEY IDENTITY"
type = col["type"].lower()
full_type = self.translate_type(type, col["size"])
nullable = "NULL" if col["nullable"] else "NOT NULL"
default = f"DEFAULT {col['default']}" if col["default"] is not None else ""
return f"{name} {full_type} {default} {nullable}"
table_name = ddl["table_name"].lower()
columns = [f"{generate_column(col).strip()}" for col in ddl["columns"]]
field_def_list = ",\n ".join(columns)
script = f"""-- ----------------------------
-- Table structure for {table_name}
-- ----------------------------
CREATE TABLE {table_name} (
{field_def_list}
);"""
# oracle INSERT '' 不能通过 NOT NULL 校验
script = script.replace("DEFAULT '' NOT NULL", "DEFAULT '' NULL")
return script
def gen_comment(self, table_ddl: Dict) -> str:
script = ""
for column in table_ddl["columns"]:
table_comment = column["comment"]
script += (
f"COMMENT ON COLUMN {table_ddl['table_name']}.{column['name']} IS '{table_comment}';"
+ "\n"
)
table_comment = table_ddl["comment"]
if table_comment:
script += (
f"COMMENT ON TABLE {table_ddl['table_name']} IS '{table_comment}';\n"
)
return script
def gen_pk(self, table_name: str) -> str:
"""生成主键定义"""
return ""
def gen_uk(self, table_ddl: Dict) -> str:
script = ""
uk_list = list(Convertor.unique_index(table_ddl))
for idx, (table_name, _, uk_columns) in enumerate(uk_list, 1):
uk_name = f"uk_{table_name}_{idx:02d}"
script += f"CREATE UNIQUE INDEX {uk_name} ON {table_name} ({', '.join(uk_columns)});\n"
return script
def gen_index(self, ddl: Dict) -> str:
return "\n".join(f"{script};" for script in self.index(ddl))
def gen_insert(self, table_name: str) -> str:
"""拷贝 INSERT 语句"""
inserts = list(Convertor.inserts(table_name, self.content))
## 生成 insert 脚本
script = ""
if inserts:
inserts_lines = "\n".join(inserts)
script += f"""\n\n-- ----------------------------
-- Records of {table_name.lower()}
-- ----------------------------
-- @formatter:off
SET IDENTITY_INSERT {table_name.lower()} ON;
{inserts_lines}
COMMIT;
SET IDENTITY_INSERT {table_name.lower()} OFF;
-- @formatter:on"""
return script
class KingbaseConvertor(PostgreSQLConvertor):
def __init__(self, src):
super().__init__(src)
self.db_type = "Kingbase"
def gen_create(self, ddl: Dict) -> str:
"""生成 create"""
def _generate_column(col):
name = col["name"].lower()
if name == "deleted":
return "deleted int2 NOT NULL DEFAULT 0"
type = col["type"].lower()
full_type = self.translate_type(type, col["size"])
nullable = "NULL" if col["nullable"] else "NOT NULL"
if full_type == "text":
nullable = "NULL"
default = f"DEFAULT {col['default']}" if col["default"] is not None else ""
return f"{name} {full_type} {nullable} {default}"
table_name = ddl["table_name"].lower()
columns = [f"{_generate_column(col).strip()}" for col in ddl["columns"]]
filed_def_list = ",\n ".join(columns)
script = f"""-- ----------------------------
-- Table structure for {table_name}
-- ----------------------------
DROP TABLE IF EXISTS {table_name};
CREATE TABLE {table_name} (
{filed_def_list}
);"""
# Kingbase INSERT '' 不能通过 NOT NULL 校验
script = script.replace("NOT NULL DEFAULT ''", "NULL DEFAULT ''")
return script
class OpengaussConvertor(KingbaseConvertor):
def __init__(self, src):
super().__init__(src)
self.db_type = "OpenGauss"
def main():
parser = argparse.ArgumentParser(description="芋道系统数据库转换工具")
parser.add_argument(
"type",
type=str,
help="目标数据库类型",
choices=["postgres", "oracle", "sqlserver", "dm8", "kingbase", "opengauss"],
)
args = parser.parse_args()
sql_file = pathlib.Path("../mysql/ruoyi-vue-pro.sql").resolve().as_posix()
convertor = None
if args.type == "postgres":
convertor = PostgreSQLConvertor(sql_file)
elif args.type == "oracle":
convertor = OracleConvertor(sql_file)
elif args.type == "sqlserver":
convertor = SQLServerConvertor(sql_file)
elif args.type == "dm8":
convertor = DM8Convertor(sql_file)
elif args.type == "kingbase":
convertor = KingbaseConvertor(sql_file)
elif args.type == "opengauss":
convertor = OpengaussConvertor(sql_file)
else:
raise NotImplementedError(f"不支持目标数据库类型: {args.type}")
convertor.print()
if __name__ == "__main__":
main()
+134
View File
@@ -0,0 +1,134 @@
name: ruoyi-vue-pro
volumes:
mysql: { }
postgres: { }
sqlserver: { }
dm8: { }
kingbase: { }
opengauss: { }
services:
mysql:
image: mysql:8.0.33
restart: unless-stopped
environment:
TZ: Asia/Shanghai
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: ruoyi-vue-pro
ports:
- "3306:3306"
volumes:
- mysql:/var/lib/mysql/
# 注入初始化脚本
- ./mysql/ruoyi-vue-pro.sql:/docker-entrypoint-initdb.d/init.sql:ro
command:
--default-authentication-plugin=mysql_native_password
--character-set-server=utf8mb4
--collation-server=utf8mb4_general_ci
--explicit_defaults_for_timestamp=true
--lower_case_table_names=1
postgres:
image: postgres:14.2
restart: unless-stopped
environment:
POSTGRES_USER: root
POSTGRES_PASSWORD: 123456
POSTGRES_DB: ruoyi-vue-pro
ports:
- "5432:5432"
volumes:
- postgres:/var/lib/postgresql/data
# 注入初始化脚本
- ../postgresql/quartz.sql:/docker-entrypoint-initdb.d/quartz.sql:ro
- ../postgresql/ruoyi-vue-pro.sql:/docker-entrypoint-initdb.d/ruoyi-vue-pro.sql:ro
oracle:
image: gvenzl/oracle-xe:18-slim-faststart
restart: unless-stopped
environment:
## 登录信息 SID: XE user: system password: oracle
ORACLE_PASSWORD: oracle
ports:
- "1521:1521"
volumes:
- ../oracle/ruoyi-vue-pro.sql:/tmp/schema.sql:ro
# 创建app用户: ROOT/123456@//localhost/XEPDB1
- ./oracle/1_create_user.sql:/docker-entrypoint-initdb.d/1_create_user.sql:ro
- ./oracle/2_create_schema.sh:/docker-entrypoint-initdb.d/2_create_schema.sh:ro
oracle_m1:
image: einslib/oracle-19c:19.3.0-ee-slim-faststart
restart: unless-stopped
environment:
## 登录信息 SID: FREE user: system password: oracle
ORACLE_PASSWORD: oracle
ports:
- "1521:1521"
volumes:
- ../oracle/ruoyi-vue-pro.sql:/tmp/schema.sql:ro
# 创建app用户: ROOT/123456@//localhost/XEPDB1
- ./oracle/1_create_user.sql:/docker-entrypoint-initdb.d/1_create_user.sql:ro
- ./oracle/2_create_schema.sh:/docker-entrypoint-initdb.d/2_create_schema.sh:ro
sqlserver:
image: mcr.microsoft.com/mssql/server:2017-latest
restart: unless-stopped
environment:
TZ: Asia/Shanghai
ACCEPT_EULA: "Y"
SA_PASSWORD: "Yudao@2024"
ports:
- "1433:1433"
volumes:
- sqlserver:/var/opt/mssql
- ../sqlserver/ruoyi-vue-pro.sql:/tmp/schema.sql:ro
# docker compose exec sqlserver bash /tmp/create_schema.sh
- ./sqlserver/create_schema.sh:/tmp/create_schema.sh:ro
dm8:
# docker load -i dm8_20240715_x86_rh6_rq_single.tar
image: dm8_single:dm8_20240715_rev232765_x86_rh6_64
restart: unless-stopped
environment:
PAGE_SIZE: 16
LD_LIBRARY_PATH: /opt/dmdbms/bin
EXTENT_SIZE: 32
BLANK_PAD_MODE: 1
LOG_SIZE: 1024
UNICODE_FLAG: 1
LENGTH_IN_CHAR: 1
INSTANCE_NAME: dm8_test
ports:
- "5236:5236"
volumes:
- dm8:/opt/dmdbms/data
- ../dm/ruoyi-vue-pro-dm8.sql:/tmp/schema.sql:ro
kingbase:
image: kingbase_v009r001c001b0025_single_x86:v1
# image: kingbase_v009r001c001b0025_single_arm:v1
restart: unless-stopped
environment:
DB_USER: root
DB_PASSWORD: 123456
ports:
- "54321:54321"
volumes:
- kingbase:/home/kingbase/userdata
- ../kingbase/ruoyi-vue-pro.sql:/tmp/schema.sql:ro
opengauss:
image: opengauss/opengauss:5.0.0
restart: unless-stopped
environment:
GS_USERNAME: root
GS_PASSWORD: Yudao@2024
LD_LIBRARY_PATH: /usr/local/opengauss/lib:/usr/lib
ports:
- "5432:5432"
volumes:
- opengauss:/var/lib/opengauss
- ../opengauss/ruoyi-vue-pro.sql:/tmp/schema.sql:ro
# docker compose exec opengauss bash -c '/usr/local/opengauss/bin/gsql -U $GS_USERNAME -W $GS_PASSWORD -d postgres -f /tmp/schema.sql'
+3
View File
@@ -0,0 +1,3 @@
ALTER SESSION SET CONTAINER=XEPDB1;
CREATE USER ROOT IDENTIFIED BY 123456 QUOTA UNLIMITED ON USERS;
GRANT CONNECT, RESOURCE TO ROOT;
+1
View File
@@ -0,0 +1 @@
sqlplus -s ROOT/123456@//localhost/XEPDB1 @/tmp/schema.sql
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P ${SA_PASSWORD} -Q "CREATE DATABASE [ruoyi-vue-pro];
GO"
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P ${SA_PASSWORD} -d 'ruoyi-vue-pro' -i /tmp/schema.sql
+720
View File
@@ -0,0 +1,720 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-dependencies</artifactId>
<version>${revision}</version>
<packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>基础 bom 文件,管理整个项目的依赖版本</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties>
<revision>2025.09-jdk8-SNAPSHOT</revision>
<flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
<!-- 统一依赖管理 -->
<spring.framework.version>5.3.39</spring.framework.version>
<spring.security.version>5.8.16</spring.security.version>
<spring.boot.version>2.7.18</spring.boot.version>
<!-- Web 相关 -->
<springdoc.version>1.8.0</springdoc.version>
<knife4j.version>4.5.0</knife4j.version>
<servlet.versoin>2.5</servlet.versoin>
<!-- DB 相关 -->
<druid.version>1.2.27</druid.version>
<mybatis.version>3.5.19</mybatis.version>
<mybatis-plus.version>3.5.12</mybatis-plus.version>
<mybatis-plus-join.version>1.5.4</mybatis-plus-join.version>
<dynamic-datasource.version>4.3.1</dynamic-datasource.version>
<easy-trans.version>3.0.6</easy-trans.version>
<redisson.version>3.51.0</redisson.version>
<dm8.jdbc.version>8.1.3.140</dm8.jdbc.version>
<kingbase.jdbc.version>8.6.0</kingbase.jdbc.version>
<opengauss.jdbc.version>5.1.0</opengauss.jdbc.version>
<taos.version>3.7.3</taos.version>
<!-- 消息队列 -->
<rocketmq-spring.version>2.3.4</rocketmq-spring.version>
<!-- 服务保障相关 -->
<lock4j.version>2.2.7</lock4j.version>
<!-- 监控相关 -->
<skywalking.version>8.12.0</skywalking.version>
<spring-boot-admin.version>2.7.15</spring-boot-admin.version>
<opentracing.version>0.33.0</opentracing.version>
<!-- Test 测试相关 -->
<podam.version>7.2.11.RELEASE</podam.version> <!-- Spring Boot 2.X 最多使用 7.2.11 版本 -->
<jedis-mock.version>1.1.11</jedis-mock.version>
<mockito-inline.version>4.11.0</mockito-inline.version>
<!-- Bpm 工作流相关 -->
<flowable.version>6.8.0</flowable.version>
<!-- 工具类相关 -->
<anji-plus-captcha.version>1.4.0</anji-plus-captcha.version>
<jsoup.version>1.21.2</jsoup.version>
<lombok.version>1.18.38</lombok.version>
<mapstruct.version>1.6.3</mapstruct.version>
<hutool-5.version>5.8.40</hutool-5.version>
<fastexcel.version>1.3.0</fastexcel.version>
<velocity.version>2.4</velocity.version> <!-- JDK8 不能从 2.4 升级到 2.4.1,会报包不存在!!!! -->
<fastjson.version>1.2.83</fastjson.version>
<guava.version>33.4.8-jre</guava.version>
<transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
<commons-net.version>3.11.1</commons-net.version>
<commons-lang3.version>3.18.0</commons-lang3.version>
<jsch.version>0.1.55</jsch.version>
<tika-core.version>2.9.3</tika-core.version> <!-- JDK8 不能从 2.9.3 升级到 3.X,会报 JDK8 不支持 -->
<ip2region.version>2.7.0</ip2region.version>
<bizlog-sdk.version>3.0.6</bizlog-sdk.version>
<netty.version>4.2.4.Final</netty.version>
<mqtt.version>1.2.5</mqtt.version>
<pf4j-spring.version>0.9.0</pf4j-spring.version>
<vertx.version>4.5.13</vertx.version>
<!-- 三方云服务相关 -->
<awssdk.version>2.30.14</awssdk.version>
<justauth.version>1.16.7</justauth.version>
<justauth-starter.version>1.4.0</justauth-starter.version>
<jimureport.version>2.1.1</jimureport.version>
<jimubi.version>2.1.0</jimubi.version>
<weixin-java.version>4.7.7-20250808.182223</weixin-java.version>
<!-- 专属于 JDK8 安全漏洞升级 -->
<logback.version>1.2.13</logback.version> <!-- 无法使用 1.3.X 版本,启动会报错 -->
</properties>
<dependencyManagement>
<dependencies>
<!-- 统一依赖管理 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-bom</artifactId>
<version>${netty.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId> <!-- JDK8 版本独有:保证 Spring Framework 尽量高 -->
<version>${spring.framework.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-bom</artifactId> <!-- JDK8 版本独有:保证 Spring Security 尽量高 -->
<version>${spring.security.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>io.github.mouzt</groupId>
<artifactId>bizlog-sdk</artifactId>
<version>${bizlog-sdk.version}</version>
<exclusions>
<exclusion> <!-- 排除掉springboot依赖使用项目的 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-data-permission</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
<version>${revision}</version>
</dependency>
<!-- Spring 核心 -->
<dependency>
<!-- 用于生成自定义的 Spring @ConfigurationProperties 配置类的说明文件 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-web</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-security</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-websocket</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<!-- 注意:必须声明,避免 flowable 和 mybatis-plus 引入的 mybatis 版本不一致!!! -->
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser-4.9</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId> <!-- 代码生成器,使用它解析表结构 -->
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 -->
<version>${dynamic-datasource.version}</version>
</dependency>
<dependency>
<groupId>com.github.yulichang</groupId>
<artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->
<version>${mybatis-plus-join.version}</version>
</dependency>
<dependency>
<groupId>com.fhs-opensource</groupId> <!-- VO 数据翻译 -->
<artifactId>easy-trans-spring-boot-starter</artifactId>
<version>${easy-trans.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.fhs-opensource</groupId>
<artifactId>easy-trans-mybatis-plus-extend</artifactId>
<version>${easy-trans.version}</version>
</dependency>
<dependency>
<groupId>com.fhs-opensource</groupId>
<artifactId>easy-trans-anno</artifactId>
<version>${easy-trans.version}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-redis</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</exclusion>
<exclusion>
<groupId>org.redisson</groupId>
<!-- 使用 redisson-spring-data-27 替代,解决 Tuple NoClassDefFoundError 报错 -->
<artifactId>redisson-spring-data-35</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-27</artifactId>
<version>${redisson.version}</version>
</dependency>
<dependency>
<groupId>com.dameng</groupId>
<artifactId>DmJdbcDriver18</artifactId>
<version>${dm8.jdbc.version}</version>
</dependency>
<dependency>
<groupId>org.opengauss</groupId>
<artifactId>opengauss-jdbc</artifactId>
<version>${opengauss.jdbc.version}</version>
</dependency>
<dependency>
<groupId>cn.com.kingbase</groupId>
<artifactId>kingbase8</artifactId>
<version>${kingbase.jdbc.version}</version>
</dependency>
<dependency>
<groupId>com.taosdata.jdbc</groupId>
<artifactId>taos-jdbcdriver</artifactId>
<version>${taos.version}</version>
</dependency>
<!-- Job 定时任务相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-job</artifactId>
<version>${revision}</version>
</dependency>
<!-- 消息队列相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mq</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>${rocketmq-spring.version}</version>
</dependency>
<!-- 服务保障相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-protection</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
<version>${lock4j.version}</version>
<exclusions>
<exclusion>
<artifactId>redisson-spring-boot-starter</artifactId>
<groupId>org.redisson</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 监控相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-monitor</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
<version>${skywalking.version}</version>
</dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-logback-1.x</artifactId>
<version>${skywalking.version}</version>
</dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-opentracing</artifactId>
<version>${skywalking.version}</version>
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <artifactId>opentracing-api</artifactId>-->
<!-- <groupId>io.opentracing</groupId>-->
<!-- </exclusion>-->
<!-- <exclusion>-->
<!-- <artifactId>opentracing-util</artifactId>-->
<!-- <groupId>io.opentracing</groupId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
</dependency>
<dependency>
<groupId>io.opentracing</groupId>
<artifactId>opentracing-api</artifactId>
<version>${opentracing.version}</version>
</dependency>
<dependency>
<groupId>io.opentracing</groupId>
<artifactId>opentracing-util</artifactId>
<version>${opentracing.version}</version>
</dependency>
<dependency>
<groupId>io.opentracing</groupId>
<artifactId>opentracing-noop</artifactId>
<version>${opentracing.version}</version>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId> <!-- 实现 Spring Boot Admin Server 服务端 -->
<version>${spring-boot-admin.version}</version>
<exclusions>
<exclusion>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-server-cloud</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId> <!-- 实现 Spring Boot Admin Server 服务端 -->
<version>${spring-boot-admin.version}</version>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
<version>${revision}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>${mockito-inline.version}</version> <!-- 支持 Mockito 的 final 类与 static 方法的 mock -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.version}</version>
<exclusions>
<exclusion>
<artifactId>asm</artifactId>
<groupId>org.ow2.asm</groupId>
</exclusion>
<exclusion>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.fppt</groupId> <!-- 单元测试,我们采用内嵌的 Redis 数据库 -->
<artifactId>jedis-mock</artifactId>
<version>${jedis-mock.version}</version>
</dependency>
<dependency>
<groupId>uk.co.jemos.podam</groupId> <!-- 单元测试,随机生成 POJO 类 -->
<artifactId>podam</artifactId>
<version>${podam.version}</version>
</dependency>
<!-- 工作流相关 -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-process</artifactId>
<version>${flowable.version}</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-actuator</artifactId>
<version>${flowable.version}</version>
</dependency>
<!-- 工作流相关结束 -->
<!-- 工具类相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-common</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-excel</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool-5.version}</version>
</dependency>
<dependency>
<groupId>cn.idev.excel</groupId>
<artifactId>fastexcel</artifactId>
<version>${fastexcel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId> <!-- 文件类型的识别 -->
<version>${tika-core.version}</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>${velocity.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId> <!-- 解决 ThreadLocal 父子线程的传值问题 -->
<version>${transmittable-thread-local.version}</version>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId> <!-- 解决 ftp 连接 -->
<version>${commons-net.version}</version>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId> <!-- 解决 sftp 连接 -->
<version>${jsch.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version> <!-- 解决 CVE-2025-48924 漏洞 -->
</dependency>
<dependency>
<groupId>com.anji-plus</groupId>
<artifactId>captcha-spring-boot-starter</artifactId> <!-- 验证码,一般用于登录使用 -->
<version>${anji-plus-captcha.version}</version>
</dependency>
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>${ip2region.version}</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>${jsoup.version}</version>
</dependency>
<!-- 三方云服务相关 -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>${awssdk.version}</version>
</dependency>
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId> <!-- 社交登陆(例如说,个人微信、企业微信等等) -->
<version>${justauth.version}</version>
</dependency>
<dependency>
<groupId>com.xkcoding.justauth</groupId>
<artifactId>justauth-spring-boot-starter</artifactId>
<version>${justauth-starter.version}</version>
<exclusions>
<!-- 移除,避免和项目里的 hutool-all 冲突 -->
<exclusion>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>${weixin-java.version}</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-mp-spring-boot-starter</artifactId>
<version>${weixin-java.version}</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-miniapp-spring-boot-starter</artifactId>
<version>${weixin-java.version}</version>
</dependency>
<!-- 积木报表-->
<dependency>
<groupId>org.jeecgframework.jimureport</groupId>
<artifactId>jimureport-spring-boot-starter</artifactId>
<version>${jimureport.version}</version>
</dependency>
<dependency>
<groupId>org.jeecgframework.jimureport</groupId>
<artifactId>jimubi-spring-boot-starter</artifactId>
<version>${jimubi.version}</version>
<exclusions>
<exclusion>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
</exclusion>
<exclusion>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- PF4J -->
<dependency>
<groupId>org.pf4j</groupId>
<artifactId>pf4j-spring</artifactId>
<version>${pf4j-spring.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Vert.x -->
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>${vertx.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
<version>${vertx.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-mqtt</artifactId>
<version>${vertx.version}</version>
</dependency>
<!-- MQTT -->
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>${mqtt.version}</version>
</dependency>
<!-- 专属于 JDK8 安全漏洞升级 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>${logback.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<!-- 统一 revision 版本 -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
<version>${flatten-maven-plugin.version}</version>
<configuration>
<flattenMode>bom</flattenMode>
<updatePomFile>true</updatePomFile>
</configuration>
<executions>
<execution>
<goals>
<goal>flatten</goal>
</goals>
<id>flatten</id>
<phase>process-resources</phase>
</execution>
<execution>
<goals>
<goal>clean</goal>
</goals>
<id>flatten.clean</id>
<phase>clean</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
+46
View File
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>yudao</artifactId>
<groupId>cn.iocoder.boot</groupId>
<version>${revision}</version>
</parent>
<packaging>pom</packaging>
<modules>
<module>yudao-common</module>
<module>yudao-spring-boot-starter-mybatis</module>
<module>yudao-spring-boot-starter-redis</module>
<module>yudao-spring-boot-starter-web</module>
<module>yudao-spring-boot-starter-security</module>
<module>yudao-spring-boot-starter-websocket</module>
<module>yudao-spring-boot-starter-monitor</module>
<module>yudao-spring-boot-starter-protection</module>
<module>yudao-spring-boot-starter-job</module>
<module>yudao-spring-boot-starter-mq</module>
<module>yudao-spring-boot-starter-excel</module>
<module>yudao-spring-boot-starter-test</module>
<module>yudao-spring-boot-starter-biz-tenant</module>
<module>yudao-spring-boot-starter-biz-data-permission</module>
<module>yudao-spring-boot-starter-biz-ip</module>
</modules>
<artifactId>yudao-framework</artifactId>
<description>
该包是技术组件,每个子包,代表一个组件。每个组件包括两部分:
1. core 包:是该组件的核心封装
2. config 包:是该组件基于 Spring 的配置
技术组件,也分成两类:
1. 框架组件:和我们熟悉的 MyBatis、Redis 等等的拓展
2. 业务组件:和业务相关的组件的封装,例如说数据字典、操作日志等等。
如果是业务组件,Maven 名字会包含 biz
</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
</project>
+150
View File
@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-framework</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-common</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>定义基础 pojo 类、枚举、工具类等等</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<dependencies>
<!-- Spring 核心 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
</dependency>
<dependency>
<!-- 用于生成自定义的 Spring @ConfigurationProperties 配置类的说明文件 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<scope>provided</scope> <!-- 设置为 provided,主要是 PageParam 使用到 -->
</dependency>
<!-- 监控相关 -->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<scope>provided</scope> <!-- 设置为 provided,主要是 PageParam 使用到 -->
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
</dependency>
<dependency>
<groupId>com.fhs-opensource</groupId> <!-- VO 数据翻译 -->
<artifactId>easy-trans-anno</artifactId> <!-- 默认引入的原因,方便 xxx-module-api 包使用 -->
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,32 @@
package cn.iocoder.yudao.framework.common.biz.infra.logger;
import cn.iocoder.yudao.framework.common.biz.infra.logger.dto.ApiAccessLogCreateReqDTO;
import org.springframework.scheduling.annotation.Async;
import javax.validation.Valid;
/**
* API 访问日志的 API 接口
*
* @author 芋道源码
*/
public interface ApiAccessLogCommonApi {
/**
* 创建 API 访问日志
*
* @param createDTO 创建信息
*/
void createApiAccessLog(@Valid ApiAccessLogCreateReqDTO createDTO);
/**
* 【异步】创建 API 访问日志
*
* @param createDTO 访问日志 DTO
*/
@Async
default void createApiAccessLogAsync(ApiAccessLogCreateReqDTO createDTO) {
createApiAccessLog(createDTO);
}
}
@@ -0,0 +1,32 @@
package cn.iocoder.yudao.framework.common.biz.infra.logger;
import cn.iocoder.yudao.framework.common.biz.infra.logger.dto.ApiErrorLogCreateReqDTO;
import org.springframework.scheduling.annotation.Async;
import javax.validation.Valid;
/**
* API 错误日志的 API 接口
*
* @author 芋道源码
*/
public interface ApiErrorLogCommonApi {
/**
* 创建 API 错误日志
*
* @param createDTO 创建信息
*/
void createApiErrorLog(@Valid ApiErrorLogCreateReqDTO createDTO);
/**
* 【异步】创建 API 异常日志
*
* @param createDTO 异常日志 DTO
*/
@Async
default void createApiErrorLogAsync(ApiErrorLogCreateReqDTO createDTO) {
createApiErrorLog(createDTO);
}
}
@@ -0,0 +1,103 @@
package cn.iocoder.yudao.framework.common.biz.infra.logger.dto;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
* API 访问日志
*
* @author 芋道源码
*/
@Data
public class ApiAccessLogCreateReqDTO {
/**
* 链路追踪编号
*/
private String traceId;
/**
* 用户编号
*/
private Long userId;
/**
* 用户类型
*/
private Integer userType;
/**
* 应用名
*/
@NotNull(message = "应用名不能为空")
private String applicationName;
/**
* 请求方法名
*/
@NotNull(message = "http 请求方法不能为空")
private String requestMethod;
/**
* 访问地址
*/
@NotNull(message = "访问地址不能为空")
private String requestUrl;
/**
* 请求参数
*/
private String requestParams;
/**
* 响应结果
*/
private String responseBody;
/**
* 用户 IP
*/
@NotNull(message = "ip 不能为空")
private String userIp;
/**
* 浏览器 UA
*/
@NotNull(message = "User-Agent 不能为空")
private String userAgent;
/**
* 操作模块
*/
private String operateModule;
/**
* 操作名
*/
private String operateName;
/**
* 操作分类
*
* 枚举,参见 OperateTypeEnum 类
*/
private Integer operateType;
/**
* 开始请求时间
*/
@NotNull(message = "开始请求时间不能为空")
private LocalDateTime beginTime;
/**
* 结束请求时间
*/
@NotNull(message = "结束请求时间不能为空")
private LocalDateTime endTime;
/**
* 执行时长,单位:毫秒
*/
@NotNull(message = "执行时长不能为空")
private Integer duration;
/**
* 结果码
*/
@NotNull(message = "错误码不能为空")
private Integer resultCode;
/**
* 结果提示
*/
private String resultMsg;
}
@@ -0,0 +1,107 @@
package cn.iocoder.yudao.framework.common.biz.infra.logger.dto;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
* API 错误日志
*
* @author 芋道源码
*/
@Data
public class ApiErrorLogCreateReqDTO {
/**
* 链路编号
*/
private String traceId;
/**
* 账号编号
*/
private Long userId;
/**
* 用户类型
*/
private Integer userType;
/**
* 应用名
*/
@NotNull(message = "应用名不能为空")
private String applicationName;
/**
* 请求方法名
*/
@NotNull(message = "http 请求方法不能为空")
private String requestMethod;
/**
* 访问地址
*/
@NotNull(message = "访问地址不能为空")
private String requestUrl;
/**
* 请求参数
*/
@NotNull(message = "请求参数不能为空")
private String requestParams;
/**
* 用户 IP
*/
@NotNull(message = "ip 不能为空")
private String userIp;
/**
* 浏览器 UA
*/
@NotNull(message = "User-Agent 不能为空")
private String userAgent;
/**
* 异常时间
*/
@NotNull(message = "异常时间不能为空")
private LocalDateTime exceptionTime;
/**
* 异常名
*/
@NotNull(message = "异常名不能为空")
private String exceptionName;
/**
* 异常发生的类全名
*/
@NotNull(message = "异常发生的类全名不能为空")
private String exceptionClassName;
/**
* 异常发生的类文件
*/
@NotNull(message = "异常发生的类文件不能为空")
private String exceptionFileName;
/**
* 异常发生的方法名
*/
@NotNull(message = "异常发生的方法名不能为空")
private String exceptionMethodName;
/**
* 异常发生的方法所在行
*/
@NotNull(message = "异常发生的方法所在行不能为空")
private Integer exceptionLineNumber;
/**
* 异常的栈轨迹异常的栈轨迹
*/
@NotNull(message = "异常的栈轨迹不能为空")
private String exceptionStackTrace;
/**
* 异常导致的根消息
*/
@NotNull(message = "异常导致的根消息不能为空")
private String exceptionRootCauseMessage;
/**
* 异常导致的消息
*/
@NotNull(message = "异常导致的消息不能为空")
private String exceptionMessage;
}
@@ -0,0 +1,4 @@
/**
* 针对 infra 模块的 api 包
*/
package cn.iocoder.yudao.framework.common.biz.infra;
@@ -0,0 +1,4 @@
/**
* 特殊:用于 framework 下,starter 需要调用 biz 业务模块的接口定义!
*/
package cn.iocoder.yudao.framework.common.biz;
@@ -0,0 +1,22 @@
package cn.iocoder.yudao.framework.common.biz.system.dict;
import cn.iocoder.yudao.framework.common.biz.system.dict.dto.DictDataRespDTO;
import java.util.List;
/**
* 字典数据 API 接口
*
* @author 芋道源码
*/
public interface DictDataCommonApi {
/**
* 获得指定字典类型的字典数据列表
*
* @param dictType 字典类型
* @return 字典数据列表
*/
List<DictDataRespDTO> getDictDataList(String dictType);
}
@@ -0,0 +1,33 @@
package cn.iocoder.yudao.framework.common.biz.system.dict.dto;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import lombok.Data;
/**
* 字典数据 Response DTO
*
* @author 芋道源码
*/
@Data
public class DictDataRespDTO {
/**
* 字典标签
*/
private String label;
/**
* 字典值
*/
private String value;
/**
* 字典类型
*/
private String dictType;
/**
* 状态
*
* 枚举 {@link CommonStatusEnum}
*/
private Integer status;
}
@@ -0,0 +1,32 @@
package cn.iocoder.yudao.framework.common.biz.system.logger;
import cn.iocoder.yudao.framework.common.biz.system.logger.dto.OperateLogCreateReqDTO;
import org.springframework.scheduling.annotation.Async;
import javax.validation.Valid;
/**
* 操作日志 API 接口
*
* @author 芋道源码
*/
public interface OperateLogCommonApi {
/**
* 创建操作日志
*
* @param createReqDTO 请求
*/
void createOperateLog(@Valid OperateLogCreateReqDTO createReqDTO);
/**
* 【异步】创建操作日志
*
* @param createReqDTO 请求
*/
@Async
default void createOperateLogAsync(OperateLogCreateReqDTO createReqDTO) {
createOperateLog(createReqDTO);
}
}
@@ -0,0 +1,85 @@
package cn.iocoder.yudao.framework.common.biz.system.logger.dto;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/**
* 系统操作日志 Create Request DTO
*
* @author HUIHUI
*/
@Data
public class OperateLogCreateReqDTO {
/**
* 链路追踪编号
*
* 一般来说,通过链路追踪编号,可以将访问日志,错误日志,链路追踪日志,logger 打印日志等,结合在一起,从而进行排错。
*/
private String traceId;
/**
* 用户编号
*
* 关联 MemberUserDO 的 id 属性,或者 AdminUserDO 的 id 属性
*/
@NotNull(message = "用户编号不能为空")
private Long userId;
/**
* 用户类型
*
* 关联 {@link UserTypeEnum}
*/
@NotNull(message = "用户类型不能为空")
private Integer userType;
/**
* 操作模块类型
*/
@NotEmpty(message = "操作模块类型不能为空")
private String type;
/**
* 操作名
*/
@NotEmpty(message = "操作名不能为空")
private String subType;
/**
* 操作模块业务编号
*/
@NotNull(message = "操作模块业务编号不能为空")
private Long bizId;
/**
* 操作内容,记录整个操作的明细
* 例如说,修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋道改成源码。
*/
@NotEmpty(message = "操作内容不能为空")
private String action;
/**
* 拓展字段,有些复杂的业务,需要记录一些字段 ( JSON 格式 )
* 例如说,记录订单编号,{ orderId: "1"}
*/
private String extra;
/**
* 请求方法名
*/
@NotEmpty(message = "请求方法名不能为空")
private String requestMethod;
/**
* 请求地址
*/
@NotEmpty(message = "请求地址不能为空")
private String requestUrl;
/**
* 用户 IP
*/
@NotEmpty(message = "用户 IP 不能为空")
private String userIp;
/**
* 浏览器 UA
*/
@NotEmpty(message = "浏览器 UA 不能为空")
private String userAgent;
}
@@ -0,0 +1,49 @@
package cn.iocoder.yudao.framework.common.biz.system.oauth2;
import cn.iocoder.yudao.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
import cn.iocoder.yudao.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCreateReqDTO;
import cn.iocoder.yudao.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenRespDTO;
import javax.validation.Valid;
/**
* OAuth2.0 Token API 接口
*
* @author 芋道源码
*/
public interface OAuth2TokenCommonApi {
/**
* 创建访问令牌
*
* @param reqDTO 访问令牌的创建信息
* @return 访问令牌的信息
*/
OAuth2AccessTokenRespDTO createAccessToken(@Valid OAuth2AccessTokenCreateReqDTO reqDTO);
/**
* 校验访问令牌
*
* @param accessToken 访问令牌
* @return 访问令牌的信息
*/
OAuth2AccessTokenCheckRespDTO checkAccessToken(String accessToken);
/**
* 移除访问令牌
*
* @param accessToken 访问令牌
* @return 访问令牌的信息
*/
OAuth2AccessTokenRespDTO removeAccessToken(String accessToken);
/**
* 刷新访问令牌
*
* @param refreshToken 刷新令牌
* @param clientId 客户端编号
* @return 访问令牌的信息
*/
OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, String clientId);
}
@@ -0,0 +1,43 @@
package cn.iocoder.yudao.framework.common.biz.system.oauth2.dto;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
* OAuth2.0 访问令牌的校验 Response DTO
*
* @author 芋道源码
*/
@Data
public class OAuth2AccessTokenCheckRespDTO implements Serializable {
/**
* 用户编号
*/
private Long userId;
/**
* 用户类型
*/
private Integer userType;
/**
* 用户信息
*/
private Map<String, String> userInfo;
/**
* 租户编号
*/
private Long tenantId;
/**
* 授权范围的数组
*/
private List<String> scopes;
/**
* 过期时间
*/
private LocalDateTime expiresTime;
}
@@ -0,0 +1,40 @@
package cn.iocoder.yudao.framework.common.biz.system.oauth2.dto;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;
/**
* OAuth2.0 访问令牌创建 Request DTO
*
* @author 芋道源码
*/
@Data
public class OAuth2AccessTokenCreateReqDTO implements Serializable {
/**
* 用户编号
*/
@NotNull(message = "用户编号不能为空")
private Long userId;
/**
* 用户类型
*/
@NotNull(message = "用户类型不能为空")
@InEnum(value = UserTypeEnum.class, message = "用户类型必须是 {value}")
private Integer userType;
/**
* 客户端编号
*/
@NotNull(message = "客户端编号不能为空")
private String clientId;
/**
* 授权范围
*/
private List<String> scopes;
}
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.framework.common.biz.system.oauth2.dto;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* OAuth2.0 访问令牌的信息 Response DTO
*
* @author 芋道源码
*/
@Data
public class OAuth2AccessTokenRespDTO implements Serializable {
/**
* 访问令牌
*/
private String accessToken;
/**
* 刷新令牌
*/
private String refreshToken;
/**
* 用户编号
*/
private Long userId;
/**
* 用户类型
*/
private Integer userType;
/**
* 过期时间
*/
private LocalDateTime expiresTime;
}
@@ -0,0 +1,4 @@
/**
* 针对 system 模块的 api 包
*/
package cn.iocoder.yudao.framework.common.biz.system;
@@ -0,0 +1,38 @@
package cn.iocoder.yudao.framework.common.biz.system.permission;
import cn.iocoder.yudao.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO;
/**
* 权限 API 接口
*
* @author 芋道源码
*/
public interface PermissionCommonApi {
/**
* 判断是否有权限,任一一个即可
*
* @param userId 用户编号
* @param permissions 权限
* @return 是否
*/
boolean hasAnyPermissions(Long userId, String... permissions);
/**
* 判断是否有角色,任一一个即可
*
* @param userId 用户编号
* @param roles 角色数组
* @return 是否
*/
boolean hasAnyRoles(Long userId, String... roles);
/**
* 获得登陆用户的部门数据权限
*
* @param userId 用户编号
* @return 部门数据权限
*/
DeptDataPermissionRespDTO getDeptDataPermission(Long userId);
}
@@ -0,0 +1,35 @@
package cn.iocoder.yudao.framework.common.biz.system.permission.dto;
import lombok.Data;
import java.util.HashSet;
import java.util.Set;
/**
* 部门的数据权限 Response DTO
*
* @author 芋道源码
*/
@Data
public class DeptDataPermissionRespDTO {
/**
* 是否可查看全部数据
*/
private Boolean all;
/**
* 是否可查看自己的数据
*/
private Boolean self;
/**
* 可查看的部门编号数组
*/
private Set<Long> deptIds;
public DeptDataPermissionRespDTO() {
this.all = false;
this.self = false;
this.deptIds = new HashSet<>();
}
}
@@ -0,0 +1,26 @@
package cn.iocoder.yudao.framework.common.biz.system.tenant;
import java.util.List;
/**
* 多租户的 API 接口
*
* @author 芋道源码
*/
public interface TenantCommonApi {
/**
* 获得所有租户
*
* @return 租户编号数组
*/
List<Long> getTenantIdList();
/**
* 校验租户是否合法
*
* @param id 租户编号
*/
void validateTenant(Long id);
}
@@ -0,0 +1,398 @@
package cn.iocoder.yudao.framework.common.constants;
/**
* @Description: 通用常量
* @author: smcp
*/
public interface CommonConstant {
public static final Integer CALLBACK_SUCCESS = 0;
public static final Integer CALLBACK_FAIL = -1;
public static final String TRY_ON_TYPE_ALGO_CLOTH = "cloth";
public static final String TRY_ON_TYPE_ALGO_HAIR = "hair";
public static final String TRY_ON_TYPE_SINGLE = "single";
public static final String TRY_ON_TYPE_BATCH = "batch";
public static final String ROLE_TYPE_SUPER_ADMIN = "admin";
public static final String COMMON_YES = "yes";
public static final String COMMON_NO = "no";
public static final String EXCLUDE_SUIT = "excludeSuit";
public static final String TYPE_TOP_CLOTH = "topCloth";
public static final String TYPE_BOTTOM_CLOTH = "bottomCloth";
public static final String TYPE_DRESS = "dress";
//发型
public static final String TYPE_HAIR = "hair";
//身材
public static final String TYPE_FIGURE = "figure";
//内衣
public static final String TYPE_UNDERWEAR = "underwear";
//试穿 生成中图片链接
public static final String TRY_ON_IN_PROCESS = "https://digit-person.oss-cn-beijing.aliyuncs.com/digital_cloth/1720084773937.png";
//试穿 生成失败图片链接
public static final String TRY_ON_FAIL = "https://digit-person.oss-cn-beijing.aliyuncs.com/digital_cloth/1720084729963.png";
/**
* 短信验证码redis-key的前缀
*/
String PHONE_REDIS_KEY_PRE = "phone_msg";
/**
* 衣服
*/
public static final String CLOTH_FILE_TYPE = "clothType";
/**
* 模特
*/
public static final String MODEL_FILE_TYPE = "modelType";
public static final String DEFAULT_MODEL_FILE_TYPE = "defaultModelType";
/**
* 头像
*/
public static final String FACE_FILE_TYPE = "faceType";
/**
* 发型
*/
public static final String HAIR_FILE_TYPE = "hair";
/**
* 超级管理员 tenantId 值
*/
String ADMIN_TENANT_ID = "0";
/**
* 数字人审核 成功! 1 合格 0不 合格
*/
public static final Integer CUSTOM_CHECK_SUCCESS = 1;
/**
* 视频是否合格 1 合格 0不 合格
*/
public static final Integer VIDEO_CHECK_FAIL = 0;
/**
* 竖版
*/
public static final String picture_align_type_VERTICAL = "VERTICAL";
/**
* :横版
*/
public static final String picture_align_type_HORIZONTAL = "HORIZONTAL";
/**
* 上传视频管理 前缀
*/
public static final String FILE_VIRTUAL_HUMAN = "virtual_human";
public static final String DIGITAL_CLOTH = "digital_cloth";
/**
* 文件状态 禁用
*/
public static final String FILE_STATUS_DISABLE = "disable";
/**
* 文件状态 失败
*/
public static final String FILE_STATUS_FAIL = "fail";
/**
* 文件状态可用
*/
public static final String FILE_STATUS_ENABLE = "enable";
/**
* 来源 第三方
*/
public static final String FILE_TYPE_VIDEO = "video";
public static final String PUTON_SWAP = "cloth_swap";
public static final String HAIR_SWAP = "hair";
public static final String FILE_TYPE_AUDIO = "audio";
public static final String FILE_TYPE_PICTURE = "picture";
/**
* 来源 自研
*/
public static final String SOURCE_TYPE_SELF_DEVELOP = "0";
/**
* 来源 第三方
*/
public static final String SOURCE_TYPE_THIRD = "1";
public static final String CUSTOMIZED_TYPE = "CUSTOMIZED";
public static final String FREE_TYPE = "FREE";
public static final Integer CUSTOMIZED_TYPE_VALUE = 1;
public static final Integer FREE_TYPE_VALUE = 2;
/**
* 删除标志
*/
public static final Integer DEL_FLAG_1 = 1;
/**
* 未删除
*/
public static final Integer DEL_FLAG_0 = 0;
/**
* 系统日志类型: 登录
*/
public static final int LOG_TYPE_1 = 1;
/**
* 系统日志类型: 操作
*/
public static final int LOG_TYPE_2 = 2;
/**
* 操作日志类型: 查询
*/
public static final int OPERATE_TYPE_1 = 1;
/**
* 操作日志类型: 添加
*/
public static final int OPERATE_TYPE_2 = 2;
/**
* 操作日志类型: 更新
*/
public static final int OPERATE_TYPE_3 = 3;
/**
* 操作日志类型: 删除
*/
public static final int OPERATE_TYPE_4 = 4;
/**
* 操作日志类型: 倒入
*/
public static final int OPERATE_TYPE_5 = 5;
/**
* 操作日志类型: 导出
*/
public static final int OPERATE_TYPE_6 = 6;
/**
* {@code 500 Server Error} (HTTP/1.0 - RFC 1945)
*/
public static final Integer SC_INTERNAL_SERVER_ERROR_500 = 500;
/**
* {@code 200 OK} (HTTP/1.0 - RFC 1945)
*/
public static final Integer SC_OK_200 = 200;
/**
* 访问权限认证未通过 510
*/
public static final Integer SC_JEECG_NO_AUTHZ = 510;
/**
* 登录用户Shiro权限缓存KEY前缀
*/
public static String PREFIX_USER_SHIRO_CACHE = "shiro:cache:org.jeecg.config.shiro.ShiroRealm.authorizationCache:";
/**
* 登录用户Token令牌缓存KEY前缀
*/
public static final String PREFIX_USER_TOKEN = "prefix_user_token_";
// /** Token缓存时间:3600秒即一小时 */
// public static final int TOKEN_EXPIRE_TIME = 3600;
/**
* 0:一级菜单
*/
public static final Integer MENU_TYPE_0 = 0;
/**
* 1:子菜单
*/
public static final Integer MENU_TYPE_1 = 1;
/**
* 2:按钮权限
*/
public static final Integer MENU_TYPE_2 = 2;
/**
* 通告对象类型(USER:指定用户,ALL:全体用户)
*/
public static final String MSG_TYPE_UESR = "USER";
public static final String MSG_TYPE_ALL = "ALL";
/**
* 发布状态(0未发布,1已发布,2已撤销)
*/
public static final String NO_SEND = "0";
public static final String HAS_SEND = "1";
public static final String HAS_CANCLE = "2";
/**
* 阅读状态(0未读,1已读)
*/
public static final String HAS_READ_FLAG = "1";
public static final String NO_READ_FLAG = "0";
/**
* 优先级(L低,M中,H高)
*/
public static final String PRIORITY_L = "L";
public static final String PRIORITY_M = "M";
public static final String PRIORITY_H = "H";
/**
* 短信模板方式 0 .登录模板、1.注册模板、2.忘记密码模板
*/
public static final String SMS_TPL_TYPE_0 = "0";
public static final String SMS_TPL_TYPE_1 = "1";
public static final String SMS_TPL_TYPE_2 = "2";
/**
* 状态(0无效1有效)
*/
public static final String STATUS_0 = "0";
public static final String STATUS_1 = "1";
/**
* 同步工作流引擎1同步0不同步
*/
public static final Integer ACT_SYNC_1 = 1;
public static final Integer ACT_SYNC_0 = 0;
/**
* 消息类型1:通知公告2:系统消息
*/
public static final String MSG_CATEGORY_1 = "1";
public static final String MSG_CATEGORY_2 = "2";
/**
* 是否配置菜单的数据权限 1是0否
*/
public static final Integer RULE_FLAG_0 = 0;
public static final Integer RULE_FLAG_1 = 1;
/**
* 是否用户已被冻结 1正常(解冻) 2冻结
*/
public static final Integer USER_UNFREEZE = 1;
public static final Integer USER_FREEZE = 2;
/**
* 字典翻译文本后缀
*/
public static final String DICT_TEXT_SUFFIX = "_dictText";
/**
* 文件上传类型(本地:localMiniominio,阿里云:alioss
*/
public static final String UPLOAD_TYPE_LOCAL = "local";
public static final String UPLOAD_TYPE_MINIO = "minio";
public static final String UPLOAD_TYPE_OSS = "alioss";
/**
* sys_quartz_job 表 job_class_name 唯一键索引
*/
public static final String SQL_INDEX_UNIQ_JOB_CLASS_NAME = "uniq_job_class_name";
/**
* sys_position 表 code 唯一键索引
*/
public static final String SQL_INDEX_UNIQ_CODE = "uniq_code";
/**
* sys_role 表 code 唯一键索引
*/
public static final String SQL_INDEX_UNIQ_SYS_ROLE_CODE = "uniq_sys_role_role_code";
/**
* sys_depart 表 code 唯一键索引
*/
public static final String SQL_INDEX_UNIQ_DEPART_ORG_CODE = "uniq_depart_org_code";
/**
* sys_category 表 code 唯一键索引
*/
public static final String SQL_INDEX_UNIQ_CATEGORY_CODE = "idx_sc_code";
public final static String X_ACCESS_TOKEN = "X-Access-Token";
public final static String X_SIGN = "X-Sign";
public final static String X_TIMESTAMP = "X-TIMESTAMP";
public final static String TOKEN_IS_INVALID_MSG = "Token失效,请重新登录!";
/**
* 多租户 请求头
*/
public final static String TENANT_ID = "tenant-id";
/**
* 微服务读取配置文件属性 服务地址
*/
public final static String CLOUD_SERVER_KEY = "spring.cloud.nacos.discovery.server-addr";
/**
* 第三方登录 验证密码/创建用户 都需要设置一个操作码 防止被恶意调用
*/
public final static String THIRD_LOGIN_CODE = "third_login_code";
/**
* ONLINE 报表权限用 从request中获取地址栏后的参数
*/
String ONL_REP_URL_PARAM_STR = "onlRepUrlParamStr";
/**
* POST请求
*/
String HTTP_POST = "POST";
/**
* PUT请求
*/
String HTTP_PUT = "PUT";
/**
* PATCH请求
*/
String HTTP_PATCH = "PATCH";
/**
* 未知的
*/
String UNKNOWN = "unknown";
/**
* 字符串http
*/
String STR_HTTP = "http";
/**
* String 类型的空值
*/
String STRING_NULL = "null";
/**
* .do
*/
String SPOT_DO = ".do";
}
@@ -0,0 +1,63 @@
package cn.iocoder.yudao.framework.common.constants;
public interface Face3dSeverHTTPConstant {
public static final String SWAP_UNDERWEAR = "http://192.168.11.219:8009/cloth/swapInner";
public static final String FIGURE_UPLOAD = "http://service.aicloud.fit:6068/bodyManager";
public static final String HAIR_UPLOAD = "http://service.aicloud.fit:7393/api/uploadHair/v1";
public static final String SWAP_HAIR_v1 = "http://service.aicloud.fit:7393/api/swapHair/v1";
public static final String SWAP_HAIR_v2 = "http://service.aicloud.fit:7393/api/swapHair/v2";
public static final String CLOTH_UPLOAD = "http://192.168.11.219:8009/cloth/upload";
public static final String SWAP_CLOTH = "http://192.168.11.219:8009/cloth/swap";
public static final String SWAP_CLOTH_MALE = "http://192.168.11.219:8009/cloth/swapMan";
public static final String CRAWLER_VIP = "http://192.168.11.138:9100/api/crawler_vip/v1";
/**
* DRAFT:草稿 IN_PROGRESS:制作中 IN_PROGRESS_FAIL:制作中 SUCCESS:制作成功 FAIL:制作失败
*/
public static final String CREATE_STATUS_CANCEL = "CANCEL";
public static final String CREATE_STATUS_DRAFT = "DRAFT";
public static final String CREATE_STATUS_IN_PROGRESS = "IN_PROGRESS";
/**
* DRAFT:草稿 IN_PROGRESS:制作中 IN_PROGRESS_FAIL:制作中 SUCCESS:制作成功 FAIL:制作失败
*/
public static final String CREATE_STATUS_SUCCESS = "SUCCESS";
/**
* DRAFT:草稿 IN_PROGRESS:制作中 IN_PROGRESS_FAIL:制作中 SUCCESS:制作成功 FAIL:制作失败
*/
public static final String CREATE_STATUS_FAIL = "FAIL";
public static final String result_success = "success";
public static final String result_fail = "FAIL";
/**
* 共享盘 存 衣服
*/
public static final String UPLOADED_FOLDER_CLOTH = "/cloth/";
/**
* 共享盘 存 模特
*/
public static final String UPLOADED_FOLDER_MODEL = "/model/";
/**
* 共享盘 存 平铺图
*/
public static final String UPLOADED_FOLDER_TILE = "/tile/";
}
@@ -0,0 +1,42 @@
/*
*
* * Copyright (c) 2019-2020, 冷冷 (wangiegie@gmail.com).
* * <p>
* * Licensed under the GNU Lesser General Public License 3.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* * <p>
* * https://www.gnu.org/licenses/lgpl.html
* * <p>
* * 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 cn.iocoder.yudao.framework.common.constants;
/**
* @author scott
* @date 2019年05月18日
* 服务名称
*/
public interface ServiceNameConstants {
/**
* 微服务名:系统管理模块
*/
String SERVICE_SYSTEM = "jeecg-system";
/**
* 微服务名:Demo模块
*/
String SERVICE_DEMO = "jeecg-demo";
/**
* gateway通过header传递根路径 basePath
*/
String X_GATEWAY_BASE_PATH = "X_GATEWAY_BASE_PATH";
}
@@ -0,0 +1,89 @@
package cn.iocoder.yudao.framework.common.constants;
/**
* @Description: 符号和特殊符号常用类
* @author: wangshuai
* @date: 2022年03月30日 17:44
*/
public class SymbolConstant {
/**
* 符号:点
*/
public static final String SPOT = ".";
/**
* 符号:双斜杠
*/
public static final String DOUBLE_BACKSLASH = "\\";
/**
* 符号:冒号
*/
public static final String COLON = ":";
/**
* 符号:逗号
*/
public static final String COMMA = ",";
/**
* 符号:左花括号 }
*/
public static final String LEFT_CURLY_BRACKET = "{";
/**
* 符号:右花括号 }
*/
public static final String RIGHT_CURLY_BRACKET = "}";
/**
* 符号:井号 #
*/
public static final String WELL_NUMBER = "#";
/**
* 符号:单斜杠
*/
public static final String SINGLE_SLASH = "/";
/**
* 符号:双斜杠
*/
public static final String DOUBLE_SLASH = "//";
/**
* 符号:感叹号
*/
public static final String EXCLAMATORY_MARK = "!";
/**
* 符号:下划线
*/
public static final String UNDERLINE = "_";
/**
* 符号:单引号
*/
public static final String SINGLE_QUOTATION_MARK = "'";
/**
* 符号:星号
*/
public static final String ASTERISK = "*";
/**
* 符号:百分号
*/
public static final String PERCENT_SIGN = "%";
/**
* 符号:美元 $
*/
public static final String DOLLAR = "$";
/**
* 符号:和 &
*/
public static final String AND = "&";
}
@@ -0,0 +1,64 @@
package cn.iocoder.yudao.framework.common.constants;
public interface ZitaCommonConstant {
/**
* 性别男
*/
public static final String sex_male = "male";
/**
* 性别女
*/
public static final String sex_female = "female";
/**
* 性别男 hairs
*/
public static final String sex_male_hairs = "[\"nan_tf_69\", \"nan_tf_92\", \"nan_tf_102\", \"nan_tf_142\", \"nan_tf_129\"]";
/**
* 性别女 hairs
*/
public static final String sex_female_hairs ="[\"nv_tf_128\", \"nv_tf_175\", \"nv_tf_132\", \"nv_tf_159\", \"nv_tf_161\"]";
/**
* 正常状态
*/
public static final String STATUS_NORMAL = "200";
/**
* 生成图片和模型 1
*/
public static final String TASK_TYPE_PICTURE = "1";
/**
* 生成视频 2
*/
public static final String TASK_TYPE_VIDEO = "2";
/**
* 新用户注册送5次试用机会
*/
public static final Integer New_register_member_try_times = 5;
/**
* 购买卡类型-试用
*/
public static final String MEMBER_TYPE_TRY_CARD = "1";
/**
* 购买卡类型-次卡
*/
public static final String MEMBER_TYPE_SUB_CARD = "2";
/**
* 购买卡类型-月卡
*/
public static final String MEMBER_TYPE_MONTH_CARD = "3";
/**
* 购买卡类型-年卡
*/
public static final String MEMBER_TYPE_YEAR_CARD = "4";
}
@@ -0,0 +1,15 @@
package cn.iocoder.yudao.framework.common.core;
/**
* 可生成 T 数组的接口
*
* @author HUIHUI
*/
public interface ArrayValuable<T> {
/**
* @return 数组
*/
T[] array();
}
@@ -0,0 +1,22 @@
package cn.iocoder.yudao.framework.common.core;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* Key Value 的键值对
*
* @author 芋道源码
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class KeyValue<K, V> implements Serializable {
private K key;
private V value;
}
@@ -0,0 +1,46 @@
package cn.iocoder.yudao.framework.common.enums;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 通用状态枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum CommonStatusEnum implements ArrayValuable<Integer> {
ENABLE(0, "开启"),
DISABLE(1, "关闭");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(CommonStatusEnum::getStatus).toArray(Integer[]::new);
/**
* 状态值
*/
private final Integer status;
/**
* 状态名
*/
private final String name;
@Override
public Integer[] array() {
return ARRAYS;
}
public static boolean isEnable(Integer status) {
return ObjUtil.equal(ENABLE.status, status);
}
public static boolean isDisable(Integer status) {
return ObjUtil.equal(DISABLE.status, status);
}
}
@@ -0,0 +1,47 @@
package cn.iocoder.yudao.framework.common.enums;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 时间间隔的枚举
*
* @author dhb52
*/
@Getter
@AllArgsConstructor
public enum DateIntervalEnum implements ArrayValuable<Integer> {
HOUR(0, "小时"), // 特殊:字典里,暂时不会有这个枚举!!!因为大多数情况下,用不到这个间隔
DAY(1, ""),
WEEK(2, ""),
MONTH(3, ""),
QUARTER(4, "季度"),
YEAR(5, "")
;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(DateIntervalEnum::getInterval).toArray(Integer[]::new);
/**
* 类型
*/
private final Integer interval;
/**
* 名称
*/
private final String name;
@Override
public Integer[] array() {
return ARRAYS;
}
public static DateIntervalEnum valueOf(Integer interval) {
return ArrayUtil.firstMatch(item -> item.getInterval().equals(interval), DateIntervalEnum.values());
}
}
@@ -0,0 +1,21 @@
package cn.iocoder.yudao.framework.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 文档地址
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum DocumentEnum {
REDIS_INSTALL("https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues/I4VCSJ", "Redis 安装文档"),
TENANT("https://doc.iocoder.cn", "SaaS 多租户文档");
private final String url;
private final String memo;
}
@@ -0,0 +1,17 @@
package cn.iocoder.yudao.framework.common.enums;
/**
* RPC 相关的枚举
*
* 虽然放在 yudao-spring-boot-starter-rpc 会相对合适,但是每个 API 模块需要使用到,所以暂时只好放在此处
*
* @author 芋道源码
*/
public class RpcConstants {
/**
* RPC API 的前缀
*/
public static final String RPC_API_PREFIX = "/rpc-api";
}
@@ -0,0 +1,40 @@
package cn.iocoder.yudao.framework.common.enums;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
* 终端的枚举
*
* @author 芋道源码
*/
@RequiredArgsConstructor
@Getter
public enum TerminalEnum implements ArrayValuable<Integer> {
UNKNOWN(0, "未知"), // 目的:在无法解析到 terminal 时,使用它
WECHAT_MINI_PROGRAM(10, "微信小程序"),
WECHAT_WAP(11, "微信公众号"),
H5(20, "H5 网页"),
APP(31, "手机 App"),
;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(TerminalEnum::getTerminal).toArray(Integer[]::new);
/**
* 终端
*/
private final Integer terminal;
/**
* 终端名
*/
private final String name;
@Override
public Integer[] array() {
return ARRAYS;
}
}
@@ -0,0 +1,39 @@
package cn.iocoder.yudao.framework.common.enums;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 全局用户类型枚举
*/
@AllArgsConstructor
@Getter
public enum UserTypeEnum implements ArrayValuable<Integer> {
MEMBER(1, "会员"), // 面向 c 端,普通用户
ADMIN(2, "管理员"); // 面向 b 端,管理后台
public static final Integer[] ARRAYS = Arrays.stream(values()).map(UserTypeEnum::getValue).toArray(Integer[]::new);
/**
* 类型
*/
private final Integer value;
/**
* 类型名
*/
private final String name;
public static UserTypeEnum valueOf(Integer value) {
return ArrayUtil.firstMatch(userType -> userType.getValue().equals(value), UserTypeEnum.values());
}
@Override
public Integer[] array() {
return ARRAYS;
}
}
@@ -0,0 +1,36 @@
package cn.iocoder.yudao.framework.common.enums;
/**
* Web 过滤器顺序的枚举类,保证过滤器按照符合我们的预期
*
* 考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 enums 包下
*
* @author 芋道源码
*/
public interface WebFilterOrderEnum {
int CORS_FILTER = Integer.MIN_VALUE;
int TRACE_FILTER = CORS_FILTER + 1;
int REQUEST_BODY_CACHE_FILTER = Integer.MIN_VALUE + 500;
int API_ENCRYPT_FILTER = REQUEST_BODY_CACHE_FILTER + 1;
// OrderedRequestContextFilter 默认为 -105,用于国际化上下文等等
int TENANT_CONTEXT_FILTER = - 104; // 需要保证在 ApiAccessLogFilter 前面
int API_ACCESS_LOG_FILTER = -103; // 需要保证在 RequestBodyCacheFilter 后面
int XSS_FILTER = -102; // 需要保证在 RequestBodyCacheFilter 后面
// Spring Security Filter 默认为 -100,可见 org.springframework.boot.autoconfigure.security.SecurityProperties 配置属性类
int TENANT_SECURITY_FILTER = -99; // 需要保证在 Spring Security 过滤器后面
int FLOWABLE_FILTER = -98; // 需要保证在 Spring Security 过滤后面
int DEMO_FILTER = Integer.MAX_VALUE;
}
@@ -0,0 +1,32 @@
package cn.iocoder.yudao.framework.common.exception;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.exception.enums.ServiceErrorCodeRange;
import lombok.Data;
/**
* 错误码对象
*
* 全局错误码,占用 [0, 999], 参见 {@link GlobalErrorCodeConstants}
* 业务异常错误码,占用 [1 000 000 000, +∞),参见 {@link ServiceErrorCodeRange}
*
* TODO 错误码设计成对象的原因,为未来的 i18 国际化做准备
*/
@Data
public class ErrorCode {
/**
* 错误码
*/
private final Integer code;
/**
* 错误提示
*/
private final String msg;
public ErrorCode(Integer code, String message) {
this.code = code;
this.msg = message;
}
}
@@ -0,0 +1,60 @@
package cn.iocoder.yudao.framework.common.exception;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 服务器异常 Exception
*/
@Data
@EqualsAndHashCode(callSuper = true)
public final class ServerException extends RuntimeException {
/**
* 全局错误码
*
* @see GlobalErrorCodeConstants
*/
private Integer code;
/**
* 错误提示
*/
private String message;
/**
* 空构造方法,避免反序列化问题
*/
public ServerException() {
}
public ServerException(ErrorCode errorCode) {
this.code = errorCode.getCode();
this.message = errorCode.getMsg();
}
public ServerException(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public ServerException setCode(Integer code) {
this.code = code;
return this;
}
@Override
public String getMessage() {
return message;
}
public ServerException setMessage(String message) {
this.message = message;
return this;
}
}
@@ -0,0 +1,60 @@
package cn.iocoder.yudao.framework.common.exception;
import cn.iocoder.yudao.framework.common.exception.enums.ServiceErrorCodeRange;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 业务逻辑异常 Exception
*/
@Data
@EqualsAndHashCode(callSuper = true)
public final class ServiceException extends RuntimeException {
/**
* 业务错误码
*
* @see ServiceErrorCodeRange
*/
private Integer code;
/**
* 错误提示
*/
private String message;
/**
* 空构造方法,避免反序列化问题
*/
public ServiceException() {
}
public ServiceException(ErrorCode errorCode) {
this.code = errorCode.getCode();
this.message = errorCode.getMsg();
}
public ServiceException(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public ServiceException setCode(Integer code) {
this.code = code;
return this;
}
@Override
public String getMessage() {
return message;
}
public ServiceException setMessage(String message) {
this.message = message;
return this;
}
}
@@ -0,0 +1,41 @@
package cn.iocoder.yudao.framework.common.exception.enums;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/**
* 全局错误码枚举
* 0-999 系统异常编码保留
*
* 一般情况下,使用 HTTP 响应状态码 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
* 虽然说,HTTP 响应状态码作为业务使用表达能力偏弱,但是使用在系统层面还是非常不错的
* 比较特殊的是,因为之前一直使用 0 作为成功,就不使用 200 啦。
*
* @author 芋道源码
*/
public interface GlobalErrorCodeConstants {
ErrorCode SUCCESS = new ErrorCode(0, "成功");
// ========== 客户端错误段 ==========
ErrorCode BAD_REQUEST = new ErrorCode(400, "请求参数不正确");
ErrorCode UNAUTHORIZED = new ErrorCode(401, "账号未登录");
ErrorCode FORBIDDEN = new ErrorCode(403, "没有该操作权限");
ErrorCode NOT_FOUND = new ErrorCode(404, "请求未找到");
ErrorCode METHOD_NOT_ALLOWED = new ErrorCode(405, "请求方法不正确");
ErrorCode LOCKED = new ErrorCode(423, "请求失败,请稍后重试"); // 并发请求,不允许
ErrorCode TOO_MANY_REQUESTS = new ErrorCode(429, "请求过于频繁,请稍后重试");
// ========== 服务端错误段 ==========
ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, "系统异常");
ErrorCode NOT_IMPLEMENTED = new ErrorCode(501, "功能未实现/未开启");
ErrorCode ERROR_CONFIGURATION = new ErrorCode(502, "错误的配置项");
// ========== 自定义错误段 ==========
ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求,请稍后重试"); // 重复请求
ErrorCode DEMO_DENY = new ErrorCode(901, "演示模式,禁止写操作");
ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
}
@@ -0,0 +1,48 @@
package cn.iocoder.yudao.framework.common.exception.enums;
/**
* 业务异常的错误码区间,解决:解决各模块错误码定义,避免重复,在此只声明不做实际使用
*
* 一共 10 位,分成四段
*
* 第一段,1 位,类型
* 1 - 业务级别异常
* x - 预留
* 第二段,3 位,系统类型
* 001 - 用户系统
* 002 - 商品系统
* 003 - 订单系统
* 004 - 支付系统
* 005 - 优惠劵系统
* ... - ...
* 第三段,3 位,模块
* 不限制规则。
* 一般建议,每个系统里面,可能有多个模块,可以再去做分段。以用户系统为例子:
* 001 - OAuth2 模块
* 002 - User 模块
* 003 - MobileCode 模块
* 第四段,3 位,错误码
* 不限制规则。
* 一般建议,每个模块自增。
*
* @author 芋道源码
*/
public class ServiceErrorCodeRange {
// 模块 infra 错误码区间 [1-001-000-000 ~ 1-002-000-000)
// 模块 system 错误码区间 [1-002-000-000 ~ 1-003-000-000)
// 模块 report 错误码区间 [1-003-000-000 ~ 1-004-000-000)
// 模块 member 错误码区间 [1-004-000-000 ~ 1-005-000-000)
// 模块 mp 错误码区间 [1-006-000-000 ~ 1-007-000-000)
// 模块 pay 错误码区间 [1-007-000-000 ~ 1-008-000-000)
// 模块 bpm 错误码区间 [1-009-000-000 ~ 1-010-000-000)
// 模块 product 错误码区间 [1-008-000-000 ~ 1-009-000-000)
// 模块 trade 错误码区间 [1-011-000-000 ~ 1-012-000-000)
// 模块 promotion 错误码区间 [1-013-000-000 ~ 1-014-000-000)
// 模块 crm 错误码区间 [1-020-000-000 ~ 1-021-000-000)
// 模块 ai 错误码区间 [1-022-000-000 ~ 1-023-000-000)
}
@@ -0,0 +1,77 @@
package cn.iocoder.yudao.framework.common.exception.util;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
/**
* {@link ServiceException} 工具类
*
* 目的在于,格式化异常信息提示。
* 考虑到 String.format 在参数不正确时会报错,因此使用 {} 作为占位符,并使用 {@link #doFormat(int, String, Object...)} 方法来格式化
*
*/
@Slf4j
public class ServiceExceptionUtil {
// ========== 和 ServiceException 的集成 ==========
public static ServiceException exception(ErrorCode errorCode) {
return exception0(errorCode.getCode(), errorCode.getMsg());
}
public static ServiceException exception(ErrorCode errorCode, Object... params) {
return exception0(errorCode.getCode(), errorCode.getMsg(), params);
}
public static ServiceException exception0(Integer code, String messagePattern, Object... params) {
String message = doFormat(code, messagePattern, params);
return new ServiceException(code, message);
}
public static ServiceException invalidParamException(String messagePattern, Object... params) {
return exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), messagePattern, params);
}
// ========== 格式化方法 ==========
/**
* 将错误编号对应的消息使用 params 进行格式化。
*
* @param code 错误编号
* @param messagePattern 消息模版
* @param params 参数
* @return 格式化后的提示
*/
@VisibleForTesting
public static String doFormat(int code, String messagePattern, Object... params) {
StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
int i = 0;
int j;
int l;
for (l = 0; l < params.length; l++) {
j = messagePattern.indexOf("{}", i);
if (j == -1) {
log.error("[doFormat][参数过多:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
if (i == 0) {
return messagePattern;
} else {
sbuf.append(messagePattern.substring(i));
return sbuf.toString();
}
} else {
sbuf.append(messagePattern, i, j);
sbuf.append(params[l]);
i = j + 2;
}
}
if (messagePattern.indexOf("{}", i) != -1) {
log.error("[doFormat][参数过少:错误码({})|错误内容({})|参数({})", code, messagePattern, params);
}
sbuf.append(messagePattern.substring(i));
return sbuf.toString();
}
}
@@ -0,0 +1,6 @@
/**
* 基础的通用类,和框架无关
*
* 例如说,CommonResult 为通用返回
*/
package cn.iocoder.yudao.framework.common;
@@ -0,0 +1,121 @@
package cn.iocoder.yudao.framework.common.pojo;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import java.io.Serializable;
import java.util.Objects;
/**
* 通用返回
*
* @param <T> 数据泛型
*/
@Data
public class CommonResult<T> implements Serializable {
/**
* 错误码
*
* @see ErrorCode#getCode()
*/
private Integer code;
/**
* 错误提示,用户可阅读
*
* @see ErrorCode#getMsg() ()
*/
private String msg;
/**
* 返回数据
*/
private T data;
/**
* 将传入的 result 对象,转换成另外一个泛型结果的对象
*
* 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
*
* @param result 传入的 result 对象
* @param <T> 返回的泛型
* @return 新的 CommonResult 对象
*/
public static <T> CommonResult<T> error(CommonResult<?> result) {
return error(result.getCode(), result.getMsg());
}
public static <T> CommonResult<T> error(Integer code, String message) {
Assert.notEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), code, "code 必须是错误的!");
CommonResult<T> result = new CommonResult<>();
result.code = code;
result.msg = message;
return result;
}
public static <T> CommonResult<T> error(ErrorCode errorCode, Object... params) {
Assert.notEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), errorCode.getCode(), "code 必须是错误的!");
CommonResult<T> result = new CommonResult<>();
result.code = errorCode.getCode();
result.msg = ServiceExceptionUtil.doFormat(errorCode.getCode(), errorCode.getMsg(), params);
return result;
}
public static <T> CommonResult<T> error(ErrorCode errorCode) {
return error(errorCode.getCode(), errorCode.getMsg());
}
public static <T> CommonResult<T> success(T data) {
CommonResult<T> result = new CommonResult<>();
result.code = GlobalErrorCodeConstants.SUCCESS.getCode();
result.data = data;
result.msg = "";
return result;
}
public static boolean isSuccess(Integer code) {
return Objects.equals(code, GlobalErrorCodeConstants.SUCCESS.getCode());
}
@JsonIgnore // 避免 jackson 序列化
public boolean isSuccess() {
return isSuccess(code);
}
@JsonIgnore // 避免 jackson 序列化
public boolean isError() {
return !isSuccess();
}
// ========= 和 Exception 异常体系集成 =========
/**
* 判断是否有异常。如果有,则抛出 {@link ServiceException} 异常
*/
public void checkError() throws ServiceException {
if (isSuccess()) {
return;
}
// 业务异常
throw new ServiceException(code, msg);
}
/**
* 判断是否有异常。如果有,则抛出 {@link ServiceException} 异常
* 如果没有,则返回 {@link #data} 数据
*/
@JsonIgnore // 避免 jackson 序列化
public T getCheckedData() {
checkError();
return data;
}
public static <T> CommonResult<T> error(ServiceException serviceException) {
return error(serviceException.getCode(), serviceException.getMessage());
}
}
@@ -0,0 +1,36 @@
package cn.iocoder.yudao.framework.common.pojo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@Schema(description="分页参数")
@Data
public class PageParam implements Serializable {
private static final Integer PAGE_NO = 1;
private static final Integer PAGE_SIZE = 10;
/**
* 每页条数 - 不分页
*
* 例如说,导出接口,可以设置 {@link #pageSize} 为 -1 不分页,查询所有数据。
*/
public static final Integer PAGE_SIZE_NONE = -1;
@Schema(description = "页码,从 1 开始", requiredMode = Schema.RequiredMode.REQUIRED,example = "1")
@NotNull(message = "页码不能为空")
@Min(value = 1, message = "页码最小值为 1")
private Integer pageNo = PAGE_NO;
@Schema(description = "每页条数,最大值为 100", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "每页条数不能为空")
@Min(value = 1, message = "每页条数最小值为 1")
@Max(value = 100, message = "每页条数最大值为 100")
private Integer pageSize = PAGE_SIZE;
}
@@ -0,0 +1,41 @@
package cn.iocoder.yudao.framework.common.pojo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Schema(description = "分页结果")
@Data
public final class PageResult<T> implements Serializable {
@Schema(description = "总量", requiredMode = Schema.RequiredMode.REQUIRED)
private Long total;
@Schema(description = "数据", requiredMode = Schema.RequiredMode.REQUIRED)
private List<T> list;
public PageResult() {
}
public PageResult(List<T> list, Long total) {
this.list = list;
this.total = total;
}
public PageResult(Long total) {
this.list = new ArrayList<>();
this.total = total;
}
public static <T> PageResult<T> empty() {
return new PageResult<>(0L);
}
public static <T> PageResult<T> empty(Long total) {
return new PageResult<>(total);
}
}
@@ -0,0 +1,19 @@
package cn.iocoder.yudao.framework.common.pojo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List;
@Schema(description = "可排序的分页参数")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SortablePageParam extends PageParam {
@Schema(description = "排序字段")
private List<SortingField> sortingFields;
}
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.framework.common.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 排序字段 DTO
*
* 类名加了 ing 的原因是,避免和 ES SortField 重名。
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SortingField implements Serializable {
/**
* 顺序 - 升序
*/
public static final String ORDER_ASC = "asc";
/**
* 顺序 - 降序
*/
public static final String ORDER_DESC = "desc";
/**
* 字段
*/
private String field;
/**
* 顺序
*/
private String order;
}
@@ -0,0 +1,394 @@
package cn.iocoder.yudao.framework.common.util;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 调用 Restful 接口 Util
*
* @author sunjianlei
*/
@Slf4j
public class RestUtil {
// 服务实例缓存,用于负载均衡
private static final Map<String, String[]> serviceInstances = new ConcurrentHashMap<>();
// 负载均衡计数器
private static final Map<String, AtomicInteger> serviceCounters = new ConcurrentHashMap<>();
private static String domain = null;
public static String getDomain() {
if (domain == null) {
domain = SpringContextUtils.getDomain();
// issues/2959
// 微服务版集成企业微信单点登录
// 因为微服务版没有端口号,导致 SpringContextUtils.getDomain() 方法获取的域名的端口号变成了:-1所以出问题了,只需要把这个-1给去掉就可以了。
String port=":-1";
if (domain.endsWith(port)) {
domain = domain.substring(0, domain.length() - 3);
}
}
return domain;
}
public static String path = null;
public static String getPath() {
if (path == null) {
path = SpringContextUtils.getApplicationContext().getEnvironment().getProperty("server.servlet.context-path");
}
return oConvertUtils.getString(path);
}
public static String getBaseUrl() {
String basepath = getDomain() + getPath();
log.info(" RestUtil.getBaseUrl: " + basepath);
return basepath;
}
/**
* 添加服务实例,用于负载均衡
* @param serviceName 服务名称
* @param instances 服务实例地址数组,如 ["localhost:8081", "localhost:8082"]
*/
public static void addServiceInstance(String serviceName, String... instances) {
serviceInstances.put(serviceName, instances);
serviceCounters.putIfAbsent(serviceName, new AtomicInteger(0));
}
/**
* 移除服务实例
* @param serviceName 服务名称
*/
public static void removeServiceInstance(String serviceName) {
serviceInstances.remove(serviceName);
serviceCounters.remove(serviceName);
}
/**
* RestAPI 调用器 (移除 @LoadBalanced 注解)
*/
private final static RestTemplate RT;
static {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(7200*1000);
requestFactory.setReadTimeout(7200*1000);
RT = new RestTemplate(requestFactory);
// 解决乱码问题
RT.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
}
public static RestTemplate getRestTemplate() {
return RT;
}
/**
* 解析URL并应用负载均衡
* 将 http://service-name/path 转换为 http://actual-host:port/path
* @param url 原始URL
* @return 解析后的URL
*/
private static String resolveUrlWithLoadBalance(String url) {
// 检查是否是服务名URL (例如: http://service-name/path)
if (url.startsWith("http://")) {
String withoutProtocol = url.substring(7);
int slashIndex = withoutProtocol.indexOf('/');
if (slashIndex > 0) {
String serviceName = withoutProtocol.substring(0, slashIndex);
// 检查是否有该服务的实例
if (serviceInstances.containsKey(serviceName)) {
// 使用轮询算法选择实例
String[] instances = serviceInstances.get(serviceName);
AtomicInteger counter = serviceCounters.get(serviceName);
int index = Math.abs(counter.getAndIncrement()) % instances.length;
String selectedInstance = instances[index];
// 替换服务名为实际地址
return "http://" + selectedInstance + withoutProtocol.substring(slashIndex);
}
}
}
return url;
}
/**
* 发送 get 请求
*/
public static JSONObject get(String url) {
return getNative(url, null, null).getBody();
}
/**
* 发送 get 请求
*/
public static JSONObject get(String url, JSONObject variables) {
return getNative(url, variables, null).getBody();
}
/**
* 发送 get 请求
*/
public static JSONObject get(String url, JSONObject variables, JSONObject params) {
return getNative(url, variables, params).getBody();
}
/**
* 发送 get 请求,返回原生 ResponseEntity 对象
*/
public static ResponseEntity<JSONObject> getNative(String url, JSONObject variables, JSONObject params) {
return request(url, HttpMethod.GET, variables, params);
}
/**
* 发送 Post 请求
*/
public static JSONObject post(String url) {
return postNative(url, null, null).getBody();
}
/**
* 发送 Post 请求
*/
public static JSONObject post(String url, JSONObject params) {
return postNative(url, null, params).getBody();
}
/**
* 发送 Post 请求
*/
public static JSONObject post(String url, JSONObject variables, JSONObject params) {
return postNative(url, variables, params).getBody();
}
/**
* 发送 POST 请求,返回原生 ResponseEntity 对象
*/
public static ResponseEntity<JSONObject> postNative(String url, JSONObject variables, JSONObject params) {
return request(url, HttpMethod.POST, variables, params);
}
/**
* 发送 put 请求
*/
public static JSONObject put(String url) {
return putNative(url, null, null).getBody();
}
/**
* 发送 put 请求
*/
public static JSONObject put(String url, JSONObject params) {
return putNative(url, null, params).getBody();
}
/**
* 发送 put 请求
*/
public static JSONObject put(String url, JSONObject variables, JSONObject params) {
return putNative(url, variables, params).getBody();
}
/**
* 发送 put 请求,返回原生 ResponseEntity 对象
*/
public static ResponseEntity<JSONObject> putNative(String url, JSONObject variables, JSONObject params) {
return request(url, HttpMethod.PUT, variables, params);
}
/**
* 发送 delete 请求
*/
public static JSONObject delete(String url) {
return deleteNative(url, null, null).getBody();
}
/**
* 发送 delete 请求
*/
public static JSONObject delete(String url, JSONObject variables, JSONObject params) {
return deleteNative(url, variables, params).getBody();
}
/**
* 发送 delete 请求,返回原生 ResponseEntity 对象
*/
public static ResponseEntity<JSONObject> deleteNative(String url, JSONObject variables, JSONObject params) {
return request(url, HttpMethod.DELETE, null, variables, params, JSONObject.class);
}
/**
* 发送请求
*/
public static ResponseEntity<JSONObject> request(String url, HttpMethod method, JSONObject variables, JSONObject params) {
return request(url, method, getHeaderApplicationJson(), variables, params, JSONObject.class);
}
/**
* 发送请求
*
* @param url 请求地址
* @param method 请求方式
* @param headers 请求头 可空
* @param variables 请求url参数 可空
* @param params 请求body参数 可空
* @param responseType 返回类型
* @return ResponseEntity<responseType>
*/
public static <T> ResponseEntity<T> request(String url, HttpMethod method, HttpHeaders headers, JSONObject variables, Object params, Class<T> responseType) {
log.info(" RestUtil --- request --- url = "+ url);
if (StringUtils.isEmpty(url)) {
throw new RuntimeException("url 不能为空");
}
if (method == null) {
throw new RuntimeException("method 不能为空");
}
if (headers == null) {
headers = new HttpHeaders();
}
// 应用负载均衡解析URL
String resolvedUrl = resolveUrlWithLoadBalance(url);
// 请求体
String body = "";
if (params != null) {
if (params instanceof JSONObject) {
body = ((JSONObject) params).toJSONString();
} else {
body = params.toString();
}
}
// 拼接 url 参数
if (variables != null) {
resolvedUrl += ("?" + asUrlVariables(variables));
}
// 发送请求
HttpEntity<String> request = new HttpEntity<>(body, headers);
return RT.exchange(resolvedUrl, method, request, responseType);
}
public static <T> ResponseEntity<T> zitaRequest(String url, HttpMethod method,
HttpHeaders headers ,JSONObject variables,
Object params, Class<T> responseType) {
log.info(" RestUtil --- request --- url = "+ url);
if (StringUtils.isEmpty(url)) {
throw new RuntimeException("url 不能为空");
}
if (method == null) {
throw new RuntimeException("method 不能为空");
}
// 应用负载均衡解析URL
String resolvedUrl = resolveUrlWithLoadBalance(url);
// 请求体
String body = "";
if (params != null) {
if (params instanceof JSONObject) {
body = ((JSONObject) params).toJSONString();
} else {
body = params.toString();
}
}
// 拼接 url 参数
if (variables != null) {
resolvedUrl += ("?" + asUrlVariables(variables));
}
// 发送请求
HttpEntity<String> request = new HttpEntity<>(body, headers);
return RT.exchange(resolvedUrl, method, request, responseType);
}
public static <T> ResponseEntity<T> zitaRequest(String url, HttpMethod method ,JSONObject variables, Object params, Class<T> responseType) {
log.info(" RestUtil --- request --- url = "+ url);
if (StringUtils.isEmpty(url)) {
throw new RuntimeException("url 不能为空");
}
if (method == null) {
throw new RuntimeException("method 不能为空");
}
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type","application/json;charset=UTF-8");
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
headers.add("Accept", MediaType.APPLICATION_JSON.toString());
// 应用负载均衡解析URL
String resolvedUrl = resolveUrlWithLoadBalance(url);
// 请求体
String body = "";
if (params != null) {
if (params instanceof JSONObject) {
body = ((JSONObject) params).toJSONString();
} else {
body = params.toString();
}
}
// 拼接 url 参数
if (variables != null) {
resolvedUrl += ("?" + asUrlVariables(variables));
}
// 发送请求
HttpEntity<String> request = new HttpEntity<>(body, headers);
return RT.exchange(resolvedUrl, method, request, responseType);
}
/**
* 获取JSON请求头
*/
public static HttpHeaders getHeaderApplicationJson() {
return getHeader(MediaType.APPLICATION_JSON_UTF8_VALUE);
}
/**
* 获取请求头
*/
public static HttpHeaders getHeader(String mediaType) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(mediaType));
headers.add("Accept", mediaType);
return headers;
}
/**
* 将 JSONObject 转为 a=1&b=2&c=3...&n=n 的形式
*/
public static String asUrlVariables(JSONObject variables) {
Map<String, Object> source = variables.getInnerMap();
Iterator<String> it = source.keySet().iterator();
StringBuilder urlVariables = new StringBuilder();
while (it.hasNext()) {
String key = it.next();
String value = "";
Object object = source.get(key);
if (object != null) {
if (!StringUtils.isEmpty(object.toString())) {
value = object.toString();
}
}
urlVariables.append("&").append(key).append("=").append(value);
}
// 去掉第一个&
return urlVariables.substring(1);
}
}
@@ -0,0 +1,81 @@
package cn.iocoder.yudao.framework.common.util;
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* 以静态变量保存Spring ApplicationContext, 可在任何代码任何地方任何时候中取出ApplicaitonContext.
*
* @author zyf
*/
@Slf4j
public class SpringContextHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext;
/**
* 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量.
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
// NOSONAR
SpringContextHolder.applicationContext = applicationContext;
}
/**
* 取得存储在静态变量中的ApplicationContext.
*/
public static ApplicationContext getApplicationContext() {
checkApplicationContext();
return applicationContext;
}
/**
* 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
public static <T> T getBean(String name) {
checkApplicationContext();
return (T) applicationContext.getBean(name);
}
/**
* 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
public static <T> T getHandler(String name, Class<T> cls) {
T t = null;
if (ObjectUtil.isNotEmpty(name)) {
checkApplicationContext();
try {
t = applicationContext.getBean(name, cls);
} catch (Exception e) {
log.warn("Customize redis listener handle [ " + name + " ], does not exist");
}
}
return t;
}
/**
* 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
public static <T> T getBean(Class<T> clazz) {
checkApplicationContext();
return applicationContext.getBean(clazz);
}
/**
* 清除applicationContext静态变量.
*/
public static void cleanApplicationContext() {
applicationContext = null;
}
private static void checkApplicationContext() {
if (applicationContext == null) {
throw new IllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义SpringContextHolder");
}
}
}
@@ -0,0 +1,108 @@
package cn.iocoder.yudao.framework.common.util;
import cn.iocoder.yudao.framework.common.constants.ServiceNameConstants;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @Description: spring上下文工具类
* @author: smcp
*/
@Component
public class SpringContextUtils implements ApplicationContextAware {
/**
* 上下文对象实例
*/
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtils.applicationContext = applicationContext;
}
/**
* 获取applicationContext
*
* @return
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 获取HttpServletRequest
*/
public static HttpServletRequest getHttpServletRequest() {
if (RequestContextHolder.getRequestAttributes() == null) {
return null;
}
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
/**
* 获取HttpServletResponse
*/
public static HttpServletResponse getHttpServletResponse() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
}
/**
* 获取项目根路径 basePath
*/
public static String getDomain(){
HttpServletRequest request = getHttpServletRequest();
StringBuffer url = request.getRequestURL();
//微服务情况下,获取gateway的basePath
String basePath = request.getHeader(ServiceNameConstants.X_GATEWAY_BASE_PATH);
if(oConvertUtils.isNotEmpty(basePath)){
return basePath;
}else{
return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString();
}
}
public static String getOrigin(){
HttpServletRequest request = getHttpServletRequest();
return request.getHeader("Origin");
}
/**
* 通过name获取 Bean.
*
* @param name
* @return
*/
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
/**
* 通过class获取Bean.
*
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* 通过name,以及Clazz返回指定的Bean
*
* @param name
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
@@ -0,0 +1,61 @@
package cn.iocoder.yudao.framework.common.util.cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.time.Duration;
import java.util.concurrent.Executors;
/**
* Cache 工具类
*
* @author 芋道源码
*/
public class CacheUtils {
/**
* 异步刷新的 LoadingCache 最大缓存数量
*
* @see <a href="">本地缓存 CacheUtils 工具类建议</a>
*/
private static final Integer CACHE_MAX_SIZE = 10000;
/**
* 构建异步刷新的 LoadingCache 对象
*
* 注意:如果你的缓存和 ThreadLocal 有关系,要么自己处理 ThreadLocal 的传递,要么使用 {@link #buildCache(Duration, CacheLoader)} 方法
*
* 或者简单理解:
* 1、和“人”相关的,使用 {@link #buildCache(Duration, CacheLoader)} 方法
* 2、和“全局”、“系统”相关的,使用当前缓存方法
*
* @param duration 过期时间
* @param loader CacheLoader 对象
* @return LoadingCache 对象
*/
public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> loader) {
return CacheBuilder.newBuilder()
.maximumSize(CACHE_MAX_SIZE)
// 只阻塞当前数据加载线程,其他线程返回旧值
.refreshAfterWrite(duration)
// 通过 asyncReloading 实现全异步加载,包括 refreshAfterWrite 被阻塞的加载线程
.build(CacheLoader.asyncReloading(loader, Executors.newCachedThreadPool())); // TODO 芋艿:可能要思考下,未来要不要做成可配置
}
/**
* 构建同步刷新的 LoadingCache 对象
*
* @param duration 过期时间
* @param loader CacheLoader 对象
* @return LoadingCache 对象
*/
public static <K, V> LoadingCache<K, V> buildCache(Duration duration, CacheLoader<K, V> loader) {
return CacheBuilder.newBuilder()
.maximumSize(CACHE_MAX_SIZE)
// 只阻塞当前数据加载线程,其他线程返回旧值
.refreshAfterWrite(duration)
.build(loader);
}
}
@@ -0,0 +1,58 @@
package cn.iocoder.yudao.framework.common.util.collection;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.util.ArrayUtil;
import java.util.Collection;
import java.util.function.Consumer;
import java.util.function.Function;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
/**
* Array 工具类
*
* @author 芋道源码
*/
public class ArrayUtils {
/**
* 将 object 和 newElements 合并成一个数组
*
* @param object 对象
* @param newElements 数组
* @param <T> 泛型
* @return 结果数组
*/
@SafeVarargs
public static <T> Consumer<T>[] append(Consumer<T> object, Consumer<T>... newElements) {
if (object == null) {
return newElements;
}
Consumer<T>[] result = ArrayUtil.newArray(Consumer.class, 1 + newElements.length);
result[0] = object;
System.arraycopy(newElements, 0, result, 1, newElements.length);
return result;
}
public static <T, V> V[] toArray(Collection<T> from, Function<T, V> mapper) {
return toArray(convertList(from, mapper));
}
@SuppressWarnings("unchecked")
public static <T> T[] toArray(Collection<T> from) {
if (CollectionUtil.isEmpty(from)) {
return (T[]) (new Object[0]);
}
return ArrayUtil.toArray(from, (Class<T>) IterUtil.getElementType(from.iterator()));
}
public static <T> T get(T[] array, int index) {
if (null == array || index >= array.length) {
return null;
}
return array[index];
}
}
@@ -0,0 +1,352 @@
package cn.iocoder.yudao.framework.common.util.collection;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import com.google.common.collect.ImmutableMap;
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static cn.hutool.core.convert.Convert.toCollection;
import static java.util.Arrays.asList;
/**
* Collection 工具类
*
* @author 芋道源码
*/
public class CollectionUtils {
public static boolean containsAny(Object source, Object... targets) {
return asList(targets).contains(source);
}
public static boolean isAnyEmpty(Collection<?>... collections) {
return Arrays.stream(collections).anyMatch(CollectionUtil::isEmpty);
}
public static <T> boolean anyMatch(Collection<T> from, Predicate<T> predicate) {
return from.stream().anyMatch(predicate);
}
public static <T> List<T> filterList(Collection<T> from, Predicate<T> predicate) {
if (CollUtil.isEmpty(from)) {
return new ArrayList<>();
}
return from.stream().filter(predicate).collect(Collectors.toList());
}
public static <T, R> List<T> distinct(Collection<T> from, Function<T, R> keyMapper) {
if (CollUtil.isEmpty(from)) {
return new ArrayList<>();
}
return distinct(from, keyMapper, (t1, t2) -> t1);
}
public static <T, R> List<T> distinct(Collection<T> from, Function<T, R> keyMapper, BinaryOperator<T> cover) {
if (CollUtil.isEmpty(from)) {
return new ArrayList<>();
}
return new ArrayList<>(convertMap(from, keyMapper, Function.identity(), cover).values());
}
public static <T, U> List<U> convertList(T[] from, Function<T, U> func) {
if (ArrayUtil.isEmpty(from)) {
return new ArrayList<>();
}
return convertList(Arrays.asList(from), func);
}
public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func) {
if (CollUtil.isEmpty(from)) {
return new ArrayList<>();
}
return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toList());
}
public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
if (CollUtil.isEmpty(from)) {
return new ArrayList<>();
}
return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList());
}
public static <T, U> PageResult<U> convertPage(PageResult<T> from, Function<T, U> func) {
if (ArrayUtil.isEmpty(from)) {
return new PageResult<>(from.getTotal());
}
return new PageResult<>(convertList(from.getList(), func), from.getTotal());
}
public static <T, U> List<U> convertListByFlatMap(Collection<T> from,
Function<T, ? extends Stream<? extends U>> func) {
if (CollUtil.isEmpty(from)) {
return new ArrayList<>();
}
return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
}
public static <T, U, R> List<R> convertListByFlatMap(Collection<T> from,
Function<? super T, ? extends U> mapper,
Function<U, ? extends Stream<? extends R>> func) {
if (CollUtil.isEmpty(from)) {
return new ArrayList<>();
}
return from.stream().map(mapper).filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
}
public static <K, V> List<V> mergeValuesFromMap(Map<K, List<V>> map) {
return map.values()
.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
}
public static <T> Set<T> convertSet(Collection<T> from) {
return convertSet(from, v -> v);
}
public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
if (CollUtil.isEmpty(from)) {
return new HashSet<>();
}
return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toSet());
}
public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func, Predicate<T> filter) {
if (CollUtil.isEmpty(from)) {
return new HashSet<>();
}
return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toSet());
}
public static <T, K> Map<K, T> convertMapByFilter(Collection<T> from, Predicate<T> filter, Function<T, K> keyFunc) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}
return from.stream().filter(filter).collect(Collectors.toMap(keyFunc, v -> v));
}
public static <T, U> Set<U> convertSetByFlatMap(Collection<T> from,
Function<T, ? extends Stream<? extends U>> func) {
if (CollUtil.isEmpty(from)) {
return new HashSet<>();
}
return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
}
public static <T, U, R> Set<R> convertSetByFlatMap(Collection<T> from,
Function<? super T, ? extends U> mapper,
Function<U, ? extends Stream<? extends R>> func) {
if (CollUtil.isEmpty(from)) {
return new HashSet<>();
}
return from.stream().map(mapper).filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
}
public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}
return convertMap(from, keyFunc, Function.identity());
}
public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc, Supplier<? extends Map<K, T>> supplier) {
if (CollUtil.isEmpty(from)) {
return supplier.get();
}
return convertMap(from, keyFunc, Function.identity(), supplier);
}
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}
return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1);
}
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, BinaryOperator<V> mergeFunction) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}
return convertMap(from, keyFunc, valueFunc, mergeFunction, HashMap::new);
}
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, Supplier<? extends Map<K, V>> supplier) {
if (CollUtil.isEmpty(from)) {
return supplier.get();
}
return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1, supplier);
}
public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, BinaryOperator<V> mergeFunction, Supplier<? extends Map<K, V>> supplier) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}
return from.stream().collect(Collectors.toMap(keyFunc, valueFunc, mergeFunction, supplier));
}
public static <T, K> Map<K, List<T>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}
return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(t -> t, Collectors.toList())));
}
public static <T, K, V> Map<K, List<V>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}
return from.stream()
.collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toList())));
}
// 暂时没想好名字,先以 2 结尾噶
public static <T, K, V> Map<K, Set<V>> convertMultiMap2(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {
if (CollUtil.isEmpty(from)) {
return new HashMap<>();
}
return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toSet())));
}
public static <T, K> Map<K, T> convertImmutableMap(Collection<T> from, Function<T, K> keyFunc) {
if (CollUtil.isEmpty(from)) {
return Collections.emptyMap();
}
ImmutableMap.Builder<K, T> builder = ImmutableMap.builder();
from.forEach(item -> builder.put(keyFunc.apply(item), item));
return builder.build();
}
/**
* 对比老、新两个列表,找出新增、修改、删除的数据
*
* @param oldList 老列表
* @param newList 新列表
* @param sameFunc 对比函数,返回 true 表示相同,返回 false 表示不同
* 注意,same 是通过每个元素的“标识”,判断它们是不是同一个数据
* @return [新增列表、修改列表、删除列表]
*/
public static <T> List<List<T>> diffList(Collection<T> oldList, Collection<T> newList,
BiFunction<T, T, Boolean> sameFunc) {
List<T> createList = new LinkedList<>(newList); // 默认都认为是新增的,后续会进行移除
List<T> updateList = new ArrayList<>();
List<T> deleteList = new ArrayList<>();
// 通过以 oldList 为主遍历,找出 updateList 和 deleteList
for (T oldObj : oldList) {
// 1. 寻找是否有匹配的
T foundObj = null;
for (Iterator<T> iterator = createList.iterator(); iterator.hasNext(); ) {
T newObj = iterator.next();
// 1.1 不匹配,则直接跳过
if (!sameFunc.apply(oldObj, newObj)) {
continue;
}
// 1.2 匹配,则移除,并结束寻找
iterator.remove();
foundObj = newObj;
break;
}
// 2. 匹配添加到 updateList;不匹配则添加到 deleteList 中
if (foundObj != null) {
updateList.add(foundObj);
} else {
deleteList.add(oldObj);
}
}
return asList(createList, updateList, deleteList);
}
public static boolean containsAny(Collection<?> source, Collection<?> candidates) {
return org.springframework.util.CollectionUtils.containsAny(source, candidates);
}
public static <T> T getFirst(List<T> from) {
return !CollectionUtil.isEmpty(from) ? from.get(0) : null;
}
public static <T> T findFirst(Collection<T> from, Predicate<T> predicate) {
return findFirst(from, predicate, Function.identity());
}
public static <T, U> U findFirst(Collection<T> from, Predicate<T> predicate, Function<T, U> func) {
if (CollUtil.isEmpty(from)) {
return null;
}
return from.stream().filter(predicate).findFirst().map(func).orElse(null);
}
public static <T, V extends Comparable<? super V>> V getMaxValue(Collection<T> from, Function<T, V> valueFunc) {
if (CollUtil.isEmpty(from)) {
return null;
}
assert !from.isEmpty(); // 断言,避免告警
T t = from.stream().max(Comparator.comparing(valueFunc)).get();
return valueFunc.apply(t);
}
public static <T, V extends Comparable<? super V>> V getMinValue(List<T> from, Function<T, V> valueFunc) {
if (CollUtil.isEmpty(from)) {
return null;
}
assert from.size() > 0; // 断言,避免告警
T t = from.stream().min(Comparator.comparing(valueFunc)).get();
return valueFunc.apply(t);
}
public static <T, V extends Comparable<? super V>> T getMinObject(List<T> from, Function<T, V> valueFunc) {
if (CollUtil.isEmpty(from)) {
return null;
}
assert from.size() > 0; // 断言,避免告警
return from.stream().min(Comparator.comparing(valueFunc)).get();
}
public static <T, V extends Comparable<? super V>> V getSumValue(Collection<T> from, Function<T, V> valueFunc,
BinaryOperator<V> accumulator) {
return getSumValue(from, valueFunc, accumulator, null);
}
public static <T, V extends Comparable<? super V>> V getSumValue(Collection<T> from, Function<T, V> valueFunc,
BinaryOperator<V> accumulator, V defaultValue) {
if (CollUtil.isEmpty(from)) {
return defaultValue;
}
assert !from.isEmpty(); // 断言,避免告警
return from.stream().map(valueFunc).filter(Objects::nonNull).reduce(accumulator).orElse(defaultValue);
}
public static <T> void addIfNotNull(Collection<T> coll, T item) {
if (item == null) {
return;
}
coll.add(item);
}
public static <T> Collection<T> singleton(T obj) {
return obj == null ? Collections.emptyList() : Collections.singleton(obj);
}
public static <T> List<T> newArrayList(List<List<T>> list) {
return list.stream().filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList());
}
/**
* 转换为 LinkedHashSet
*
* @param <T> 元素类型
* @param elementType 集合中元素类型
* @param value 被转换的值
* @return {@link LinkedHashSet}
*/
@SuppressWarnings("unchecked")
public static <T> LinkedHashSet<T> toLinkedHashSet(Class<T> elementType, Object value) {
return (LinkedHashSet<T>) toCollection(LinkedHashSet.class, elementType, value);
}
}
@@ -0,0 +1,68 @@
package cn.iocoder.yudao.framework.common.util.collection;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.core.KeyValue;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
/**
* Map 工具类
*
* @author 芋道源码
*/
public class MapUtils {
/**
* 从哈希表表中,获得 keys 对应的所有 value 数组
*
* @param multimap 哈希表
* @param keys keys
* @return value 数组
*/
public static <K, V> List<V> getList(Multimap<K, V> multimap, Collection<K> keys) {
List<V> result = new ArrayList<>();
keys.forEach(k -> {
Collection<V> values = multimap.get(k);
if (CollectionUtil.isEmpty(values)) {
return;
}
result.addAll(values);
});
return result;
}
/**
* 从哈希表查找到 key 对应的 value,然后进一步处理
* key 为 null 时, 不处理
* 注意,如果查找到的 value 为 null 时,不进行处理
*
* @param map 哈希表
* @param key key
* @param consumer 进一步处理的逻辑
*/
public static <K, V> void findAndThen(Map<K, V> map, K key, Consumer<V> consumer) {
if (ObjUtil.isNull(key) || CollUtil.isEmpty(map)) {
return;
}
V value = map.get(key);
if (value == null) {
return;
}
consumer.accept(value);
}
public static <K, V> Map<K, V> convertMap(List<KeyValue<K, V>> keyValues) {
Map<K, V> map = Maps.newLinkedHashMapWithExpectedSize(keyValues.size());
keyValues.forEach(keyValue -> map.put(keyValue.getKey(), keyValue.getValue()));
return map;
}
}
@@ -0,0 +1,19 @@
package cn.iocoder.yudao.framework.common.util.collection;
import cn.hutool.core.collection.CollUtil;
import java.util.Set;
/**
* Set 工具类
*
* @author 芋道源码
*/
public class SetUtils {
@SafeVarargs
public static <T> Set<T> asSet(T... objs) {
return CollUtil.newHashSet(objs);
}
}
@@ -0,0 +1,162 @@
package cn.iocoder.yudao.framework.common.util.date;
import cn.hutool.core.date.LocalDateTimeUtil;
import java.time.*;
import java.util.Calendar;
import java.util.Date;
/**
* 时间工具类
*
* @author 芋道源码
*/
public class DateUtils {
/**
* 时区 - 默认
*/
public static final String TIME_ZONE_DEFAULT = "GMT+8";
/**
* 秒转换成毫秒
*/
public static final long SECOND_MILLIS = 1000;
public static final String FORMAT_YEAR_MONTH_DAY = "yyyy-MM-dd";
public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = "yyyy-MM-dd HH:mm:ss";
/**
* 将 LocalDateTime 转换成 Date
*
* @param date LocalDateTime
* @return LocalDateTime
*/
public static Date of(LocalDateTime date) {
if (date == null) {
return null;
}
// 将此日期时间与时区相结合以创建 ZonedDateTime
ZonedDateTime zonedDateTime = date.atZone(ZoneId.systemDefault());
// 本地时间线 LocalDateTime 到即时时间线 Instant 时间戳
Instant instant = zonedDateTime.toInstant();
// UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
return Date.from(instant);
}
/**
* 将 Date 转换成 LocalDateTime
*
* @param date Date
* @return LocalDateTime
*/
public static LocalDateTime of(Date date) {
if (date == null) {
return null;
}
// 转为时间戳
Instant instant = date.toInstant();
// UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
}
public static Date addTime(Duration duration) {
return new Date(System.currentTimeMillis() + duration.toMillis());
}
public static boolean isExpired(LocalDateTime time) {
LocalDateTime now = LocalDateTime.now();
return now.isAfter(time);
}
/**
* 创建指定时间
*
* @param year 年
* @param month 月
* @param day 日
* @return 指定时间
*/
public static Date buildTime(int year, int month, int day) {
return buildTime(year, month, day, 0, 0, 0);
}
/**
* 创建指定时间
*
* @param year 年
* @param month 月
* @param day 日
* @param hour 小时
* @param minute 分钟
* @param second 秒
* @return 指定时间
*/
public static Date buildTime(int year, int month, int day,
int hour, int minute, int second) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, month - 1);
calendar.set(Calendar.DAY_OF_MONTH, day);
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
calendar.set(Calendar.SECOND, second);
calendar.set(Calendar.MILLISECOND, 0); // 一般情况下,都是 0 毫秒
return calendar.getTime();
}
public static Date max(Date a, Date b) {
if (a == null) {
return b;
}
if (b == null) {
return a;
}
return a.compareTo(b) > 0 ? a : b;
}
public static LocalDateTime max(LocalDateTime a, LocalDateTime b) {
if (a == null) {
return b;
}
if (b == null) {
return a;
}
return a.isAfter(b) ? a : b;
}
/**
* 是否今天
*
* @param date 日期
* @return 是否
*/
public static boolean isToday(LocalDateTime date) {
return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now());
}
/**
* 是否昨天
*
* @param date 日期
* @return 是否
*/
public static boolean isYesterday(LocalDateTime date) {
return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now().minusDays(1));
}
public static Date localDateTimeToDate(LocalDateTime localDateTime) {
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
}
public static LocalDateTime toLocalDateTime(Date date) {
return date == null ? null :
date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
}
public static String localDateTimeToStr(LocalDateTime createTime) {
return createTime == null ? null :
LocalDateTimeUtil.format(createTime, FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND);
}
}
@@ -0,0 +1,350 @@
package cn.iocoder.yudao.framework.common.util.date;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.date.TemporalAccessorUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
import java.sql.Timestamp;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.List;
import static cn.hutool.core.date.DatePattern.*;
/**
* 时间工具类,用于 {@link LocalDateTime}
*
* @author 芋道源码
*/
public class LocalDateTimeUtils {
/**
* 空的 LocalDateTime 对象,主要用于 DB 唯一索引的默认值
*/
public static LocalDateTime EMPTY = buildTime(1970, 1, 1);
public static DateTimeFormatter UTC_MS_WITH_XXX_OFFSET_FORMATTER = createFormatter(UTC_MS_WITH_XXX_OFFSET_PATTERN);
/**
* 解析时间
*
* 相比 {@link LocalDateTimeUtil#parse(CharSequence)} 方法来说,会尽量去解析,直到成功
*
* @param time 时间
* @return 时间字符串
*/
public static LocalDateTime parse(String time) {
try {
return LocalDateTimeUtil.parse(time, DatePattern.NORM_DATE_PATTERN);
} catch (DateTimeParseException e) {
return LocalDateTimeUtil.parse(time);
}
}
public static LocalDateTime addTime(Duration duration) {
return LocalDateTime.now().plus(duration);
}
public static LocalDateTime minusTime(Duration duration) {
return LocalDateTime.now().minus(duration);
}
public static boolean beforeNow(LocalDateTime date) {
return date.isBefore(LocalDateTime.now());
}
public static boolean afterNow(LocalDateTime date) {
return date.isAfter(LocalDateTime.now());
}
/**
* 创建指定时间
*
* @param year 年
* @param month 月
* @param day 日
* @return 指定时间
*/
public static LocalDateTime buildTime(int year, int month, int day) {
return LocalDateTime.of(year, month, day, 0, 0, 0);
}
public static LocalDateTime[] buildBetweenTime(int year1, int month1, int day1,
int year2, int month2, int day2) {
return new LocalDateTime[]{buildTime(year1, month1, day1), buildTime(year2, month2, day2)};
}
/**
* 判指定断时间,是否在该时间范围内
*
* @param startTime 开始时间
* @param endTime 结束时间
* @param time 指定时间
* @return 是否
*/
public static boolean isBetween(LocalDateTime startTime, LocalDateTime endTime, Timestamp time) {
if (startTime == null || endTime == null || time == null) {
return false;
}
return LocalDateTimeUtil.isIn(LocalDateTimeUtil.of(time), startTime, endTime);
}
/**
* 判指定断时间,是否在该时间范围内
*
* @param startTime 开始时间
* @param endTime 结束时间
* @param time 指定时间
* @return 是否
*/
public static boolean isBetween(LocalDateTime startTime, LocalDateTime endTime, String time) {
if (startTime == null || endTime == null || time == null) {
return false;
}
return LocalDateTimeUtil.isIn(parse(time), startTime, endTime);
}
/**
* 判断当前时间是否在该时间范围内
*
* @param startTime 开始时间
* @param endTime 结束时间
* @return 是否
*/
public static boolean isBetween(LocalDateTime startTime, LocalDateTime endTime) {
if (startTime == null || endTime == null) {
return false;
}
return LocalDateTimeUtil.isIn(LocalDateTime.now(), startTime, endTime);
}
/**
* 判断当前时间是否在该时间范围内
*
* @param startTime 开始时间
* @param endTime 结束时间
* @return 是否
*/
public static boolean isBetween(String startTime, String endTime) {
if (startTime == null || endTime == null) {
return false;
}
LocalDate nowDate = LocalDate.now();
return LocalDateTimeUtil.isIn(LocalDateTime.now(),
LocalDateTime.of(nowDate, LocalTime.parse(startTime)),
LocalDateTime.of(nowDate, LocalTime.parse(endTime)));
}
/**
* 判断时间段是否重叠
*
* @param startTime1 开始 time1
* @param endTime1 结束 time1
* @param startTime2 开始 time2
* @param endTime2 结束 time2
* @return 重叠:true 不重叠:false
*/
public static boolean isOverlap(LocalTime startTime1, LocalTime endTime1, LocalTime startTime2, LocalTime endTime2) {
LocalDate nowDate = LocalDate.now();
return LocalDateTimeUtil.isOverlap(LocalDateTime.of(nowDate, startTime1), LocalDateTime.of(nowDate, endTime1),
LocalDateTime.of(nowDate, startTime2), LocalDateTime.of(nowDate, endTime2));
}
/**
* 获取指定日期所在的月份的开始时间
* 例如:2023-09-30 00:00:00,000
*
* @param date 日期
* @return 月份的开始时间
*/
public static LocalDateTime beginOfMonth(LocalDateTime date) {
return date.with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN);
}
/**
* 获取指定日期所在的月份的最后时间
* 例如:2023-09-30 23:59:59,999
*
* @param date 日期
* @return 月份的结束时间
*/
public static LocalDateTime endOfMonth(LocalDateTime date) {
return date.with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX);
}
/**
* 获得指定日期所在季度
*
* @param date 日期
* @return 所在季度
*/
public static int getQuarterOfYear(LocalDateTime date) {
return (date.getMonthValue() - 1) / 3 + 1;
}
/**
* 获取指定日期到现在过了几天,如果指定日期在当前日期之后,获取结果为负
*
* @param dateTime 日期
* @return 相差天数
*/
public static Long between(LocalDateTime dateTime) {
return LocalDateTimeUtil.between(dateTime, LocalDateTime.now(), ChronoUnit.DAYS);
}
/**
* 获取今天的开始时间
*
* @return 今天
*/
public static LocalDateTime getToday() {
return LocalDateTimeUtil.beginOfDay(LocalDateTime.now());
}
/**
* 获取昨天的开始时间
*
* @return 昨天
*/
public static LocalDateTime getYesterday() {
return LocalDateTimeUtil.beginOfDay(LocalDateTime.now().minusDays(1));
}
/**
* 获取本月的开始时间
*
* @return 本月
*/
public static LocalDateTime getMonth() {
return beginOfMonth(LocalDateTime.now());
}
/**
* 获取本年的开始时间
*
* @return 本年
*/
public static LocalDateTime getYear() {
return LocalDateTime.now().with(TemporalAdjusters.firstDayOfYear()).with(LocalTime.MIN);
}
public static List<LocalDateTime[]> getDateRangeList(LocalDateTime startTime,
LocalDateTime endTime,
Integer interval) {
// 1.1 找到枚举
DateIntervalEnum intervalEnum = DateIntervalEnum.valueOf(interval);
Assert.notNull(intervalEnum, "interval({}} 找不到对应的枚举", interval);
// 1.2 将时间对齐
startTime = LocalDateTimeUtil.beginOfDay(startTime);
endTime = LocalDateTimeUtil.endOfDay(endTime);
// 2. 循环,生成时间范围
List<LocalDateTime[]> timeRanges = new ArrayList<>();
switch (intervalEnum) {
case HOUR:
while (startTime.isBefore(endTime)) {
timeRanges.add(new LocalDateTime[]{startTime, startTime.plusHours(1).minusNanos(1)});
startTime = startTime.plusHours(1);
}
case DAY:
while (startTime.isBefore(endTime)) {
timeRanges.add(new LocalDateTime[]{startTime, startTime.plusDays(1).minusNanos(1)});
startTime = startTime.plusDays(1);
}
break;
case WEEK:
while (startTime.isBefore(endTime)) {
LocalDateTime endOfWeek = startTime.with(DayOfWeek.SUNDAY).plusDays(1).minusNanos(1);
timeRanges.add(new LocalDateTime[]{startTime, endOfWeek});
startTime = endOfWeek.plusNanos(1);
}
break;
case MONTH:
while (startTime.isBefore(endTime)) {
LocalDateTime endOfMonth = startTime.with(TemporalAdjusters.lastDayOfMonth()).plusDays(1).minusNanos(1);
timeRanges.add(new LocalDateTime[]{startTime, endOfMonth});
startTime = endOfMonth.plusNanos(1);
}
break;
case QUARTER:
while (startTime.isBefore(endTime)) {
int quarterOfYear = getQuarterOfYear(startTime);
LocalDateTime quarterEnd = quarterOfYear == 4
? startTime.with(TemporalAdjusters.lastDayOfYear()).plusDays(1).minusNanos(1)
: startTime.withMonth(quarterOfYear * 3 + 1).withDayOfMonth(1).minusNanos(1);
timeRanges.add(new LocalDateTime[]{startTime, quarterEnd});
startTime = quarterEnd.plusNanos(1);
}
break;
case YEAR:
while (startTime.isBefore(endTime)) {
LocalDateTime endOfYear = startTime.with(TemporalAdjusters.lastDayOfYear()).plusDays(1).minusNanos(1);
timeRanges.add(new LocalDateTime[]{startTime, endOfYear});
startTime = endOfYear.plusNanos(1);
}
break;
default:
throw new IllegalArgumentException("Invalid interval: " + interval);
}
// 3. 兜底,最后一个时间,需要保持在 endTime 之前
LocalDateTime[] lastTimeRange = CollUtil.getLast(timeRanges);
if (lastTimeRange != null) {
lastTimeRange[1] = endTime;
}
return timeRanges;
}
/**
* 格式化时间范围
*
* @param startTime 开始时间
* @param endTime 结束时间
* @param interval 时间间隔
* @return 时间范围
*/
public static String formatDateRange(LocalDateTime startTime, LocalDateTime endTime, Integer interval) {
// 1. 找到枚举
DateIntervalEnum intervalEnum = DateIntervalEnum.valueOf(interval);
Assert.notNull(intervalEnum, "interval({}} 找不到对应的枚举", interval);
// 2. 循环,生成时间范围
switch (intervalEnum) {
case HOUR:
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATETIME_MINUTE_PATTERN);
case DAY:
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN);
case WEEK:
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN)
+ StrUtil.format("(第 {} 周)", LocalDateTimeUtil.weekOfYear(startTime));
case MONTH:
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_MONTH_PATTERN);
case QUARTER:
return StrUtil.format("{}-Q{}", startTime.getYear(), getQuarterOfYear(startTime));
case YEAR:
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_YEAR_PATTERN);
default:
throw new IllegalArgumentException("Invalid interval: " + interval);
}
}
/**
* 将给定的 {@link LocalDateTime} 转换为自 Unix 纪元时间(1970-01-01T00:00:00Z)以来的秒数。
*
* @param sourceDateTime 需要转换的本地日期时间,不能为空
* @return 自 1970-01-01T00:00:00Z 起的秒数(epoch second
* @throws NullPointerException 如果 {@code sourceDateTime} 为 {@code null}
* @throws DateTimeException 如果转换过程中发生时间超出范围或其他时间处理异常
*/
public static Long toEpochSecond(LocalDateTime sourceDateTime) {
return TemporalAccessorUtil.toInstant(sourceDateTime).getEpochSecond();
}
}
@@ -0,0 +1,184 @@
package cn.iocoder.yudao.framework.common.util.http;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.map.TableMap;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import lombok.SneakyThrows;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import javax.servlet.http.HttpServletRequest;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* HTTP 工具类
*
* @author 芋道源码
*/
public class HttpUtils {
/**
* 编码 URL 参数
*
* @param value 参数
* @return 编码后的参数
*/
@SneakyThrows
public static String encodeUtf8(String value) {
return URLEncoder.encode(value, StandardCharsets.UTF_8.name());
}
@SuppressWarnings("unchecked")
public static String replaceUrlQuery(String url, String key, String value) {
UrlBuilder builder = UrlBuilder.of(url, Charset.defaultCharset());
// 先移除
TableMap<CharSequence, CharSequence> query = (TableMap<CharSequence, CharSequence>)
ReflectUtil.getFieldValue(builder.getQuery(), "query");
query.remove(key);
// 后添加
builder.addQuery(key, value);
return builder.build();
}
public static String removeUrlQuery(String url) {
if (!StrUtil.contains(url, '?')) {
return url;
}
UrlBuilder builder = UrlBuilder.of(url, Charset.defaultCharset());
// 移除 query、fragment
builder.setQuery(null);
builder.setFragment(null);
return builder.build();
}
/**
* 拼接 URL
*
* copy from Spring Security OAuth2 的 AuthorizationEndpoint 类的 append 方法
*
* @param base 基础 URL
* @param query 查询参数
* @param keys query 的 key,对应的原本的 key 的映射。例如说 query 里有个 key 是 xx,实际它的 key 是 extra_xx,则通过 keys 里添加这个映射
* @param fragment URL 的 fragment,即拼接到 # 中
* @return 拼接后的 URL
*/
public static String append(String base, Map<String, ?> query, Map<String, String> keys, boolean fragment) {
UriComponentsBuilder template = UriComponentsBuilder.newInstance();
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(base);
URI redirectUri;
try {
// assume it's encoded to start with (if it came in over the wire)
redirectUri = builder.build(true).toUri();
} catch (Exception e) {
// ... but allow client registrations to contain hard-coded non-encoded values
redirectUri = builder.build().toUri();
builder = UriComponentsBuilder.fromUri(redirectUri);
}
template.scheme(redirectUri.getScheme()).port(redirectUri.getPort()).host(redirectUri.getHost())
.userInfo(redirectUri.getUserInfo()).path(redirectUri.getPath());
if (fragment) {
StringBuilder values = new StringBuilder();
if (redirectUri.getFragment() != null) {
String append = redirectUri.getFragment();
values.append(append);
}
for (String key : query.keySet()) {
if (values.length() > 0) {
values.append("&");
}
String name = key;
if (keys != null && keys.containsKey(key)) {
name = keys.get(key);
}
values.append(name).append("={").append(key).append("}");
}
if (values.length() > 0) {
template.fragment(values.toString());
}
UriComponents encoded = template.build().expand(query).encode();
builder.fragment(encoded.getFragment());
} else {
for (String key : query.keySet()) {
String name = key;
if (keys != null && keys.containsKey(key)) {
name = keys.get(key);
}
template.queryParam(name, "{" + key + "}");
}
template.fragment(redirectUri.getFragment());
UriComponents encoded = template.build().expand(query).encode();
builder.query(encoded.getQuery());
}
return builder.build().toUriString();
}
public static String[] obtainBasicAuthorization(HttpServletRequest request) {
String clientId;
String clientSecret;
// 先从 Header 中获取
String authorization = request.getHeader("Authorization");
authorization = StrUtil.subAfter(authorization, "Basic ", true);
if (StringUtils.hasText(authorization)) {
authorization = Base64.decodeStr(authorization);
clientId = StrUtil.subBefore(authorization, ":", false);
clientSecret = StrUtil.subAfter(authorization, ":", false);
// 再从 Param 中获取
} else {
clientId = request.getParameter("client_id");
clientSecret = request.getParameter("client_secret");
}
// 如果两者非空,则返回
if (StrUtil.isNotEmpty(clientId) && StrUtil.isNotEmpty(clientSecret)) {
return new String[]{clientId, clientSecret};
}
return null;
}
/**
* HTTP post 请求,基于 {@link cn.hutool.http.HttpUtil} 实现
*
* 为什么要封装该方法,因为 HttpUtil 默认封装的方法,没有允许传递 headers 参数
*
* @param url URL
* @param headers 请求头
* @param requestBody 请求体
* @return 请求结果
*/
public static String post(String url, Map<String, String> headers, String requestBody) {
try (HttpResponse response = HttpRequest.post(url)
.addHeaders(headers)
.body(requestBody)
.execute()) {
return response.body();
}
}
/**
* HTTP get 请求,基于 {@link cn.hutool.http.HttpUtil} 实现
*
* 为什么要封装该方法,因为 HttpUtil 默认封装的方法,没有允许传递 headers 参数
*
* @param url URL
* @param headers 请求头
* @return 请求结果
*/
public static String get(String url, Map<String, String> headers) {
try (HttpResponse response = HttpRequest.get(url)
.addHeaders(headers)
.execute()) {
return response.body();
}
}
}
@@ -0,0 +1,61 @@
package cn.iocoder.yudao.framework.common.util.io;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import lombok.SneakyThrows;
import java.io.File;
/**
* 文件工具类
*
* @author 芋道源码
*/
public class FileUtils {
/**
* 创建临时文件
* 该文件会在 JVM 退出时,进行删除
*
* @param data 文件内容
* @return 文件
*/
@SneakyThrows
public static File createTempFile(String data) {
File file = createTempFile();
// 写入内容
FileUtil.writeUtf8String(data, file);
return file;
}
/**
* 创建临时文件
* 该文件会在 JVM 退出时,进行删除
*
* @param data 文件内容
* @return 文件
*/
@SneakyThrows
public static File createTempFile(byte[] data) {
File file = createTempFile();
// 写入内容
FileUtil.writeBytes(data, file);
return file;
}
/**
* 创建临时文件,无内容
* 该文件会在 JVM 退出时,进行删除
*
* @return 文件
*/
@SneakyThrows
public static File createTempFile() {
// 创建文件,通过 UUID 保证唯一
File file = File.createTempFile(IdUtil.simpleUUID(), null);
// 标记 JVM 退出时,自动删除
file.deleteOnExit();
return file;
}
}
@@ -0,0 +1,28 @@
package cn.iocoder.yudao.framework.common.util.io;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import java.io.InputStream;
/**
* IO 工具类,用于 {@link cn.hutool.core.io.IoUtil} 缺失的方法
*
* @author 芋道源码
*/
public class IoUtils {
/**
* 从流中读取 UTF8 编码的内容
*
* @param in 输入流
* @param isClose 是否关闭
* @return 内容
* @throws IORuntimeException IO 异常
*/
public static String readUtf8(InputStream in, boolean isClose) throws IORuntimeException {
return StrUtil.utf8Str(IoUtil.read(in, isClose));
}
}
@@ -0,0 +1,232 @@
package cn.iocoder.yudao.framework.common.util.json;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeDeserializer;
import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeSerializer;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* JSON 工具类
*
* @author 芋道源码
*/
@Slf4j
public class JsonUtils {
@Getter
private static ObjectMapper objectMapper = new ObjectMapper();
static {
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略 null 值
// 解决 LocalDateTime 的序列化
SimpleModule simpleModule = new JavaTimeModule()
.addSerializer(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE)
.addDeserializer(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE);
objectMapper.registerModules(simpleModule);
}
/**
* 初始化 objectMapper 属性
* <p>
* 通过这样的方式,使用 Spring 创建的 ObjectMapper Bean
*
* @param objectMapper ObjectMapper 对象
*/
public static void init(ObjectMapper objectMapper) {
JsonUtils.objectMapper = objectMapper;
}
@SneakyThrows
public static String toJsonString(Object object) {
return objectMapper.writeValueAsString(object);
}
@SneakyThrows
public static byte[] toJsonByte(Object object) {
return objectMapper.writeValueAsBytes(object);
}
@SneakyThrows
public static String toJsonPrettyString(Object object) {
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
}
public static <T> T parseObject(String text, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return null;
}
try {
return objectMapper.readValue(text, clazz);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static <T> T parseObject(String text, String path, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return null;
}
try {
JsonNode treeNode = objectMapper.readTree(text);
JsonNode pathNode = treeNode.path(path);
return objectMapper.readValue(pathNode.toString(), clazz);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static <T> T parseObject(String text, Type type) {
if (StrUtil.isEmpty(text)) {
return null;
}
try {
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructType(type));
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static <T> T parseObject(byte[] text, Type type) {
if (ArrayUtil.isEmpty(text)) {
return null;
}
try {
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructType(type));
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
/**
* 将字符串解析成指定类型的对象
* 使用 {@link #parseObject(String, Class)} 时,在@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) 的场景下,
* 如果 text 没有 class 属性,则会报错。此时,使用这个方法,可以解决。
*
* @param text 字符串
* @param clazz 类型
* @return 对象
*/
public static <T> T parseObject2(String text, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return null;
}
return JSONUtil.toBean(text, clazz);
}
public static <T> T parseObject(byte[] bytes, Class<T> clazz) {
if (ArrayUtil.isEmpty(bytes)) {
return null;
}
try {
return objectMapper.readValue(bytes, clazz);
} catch (IOException e) {
log.error("json parse err,json:{}", bytes, e);
throw new RuntimeException(e);
}
}
public static <T> T parseObject(String text, TypeReference<T> typeReference) {
try {
return objectMapper.readValue(text, typeReference);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
/**
* 解析 JSON 字符串成指定类型的对象,如果解析失败,则返回 null
*
* @param text 字符串
* @param typeReference 类型引用
* @return 指定类型的对象
*/
public static <T> T parseObjectQuietly(String text, TypeReference<T> typeReference) {
try {
return objectMapper.readValue(text, typeReference);
} catch (IOException e) {
return null;
}
}
public static <T> List<T> parseArray(String text, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return new ArrayList<>();
}
try {
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static <T> List<T> parseArray(String text, String path, Class<T> clazz) {
if (StrUtil.isEmpty(text)) {
return null;
}
try {
JsonNode treeNode = objectMapper.readTree(text);
JsonNode pathNode = treeNode.path(path);
return objectMapper.readValue(pathNode.toString(), objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static JsonNode parseTree(String text) {
try {
return objectMapper.readTree(text);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static JsonNode parseTree(byte[] text) {
try {
return objectMapper.readTree(text);
} catch (IOException e) {
log.error("json parse err,json:{}", text, e);
throw new RuntimeException(e);
}
}
public static boolean isJson(String text) {
return JSONUtil.isTypeJSON(text);
}
/**
* 判断字符串是否为 JSON 类型的字符串
* @param str 字符串
*/
public static boolean isJsonObject(String str) {
return JSONUtil.isTypeJSONObject(str);
}
}
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.framework.common.util.json.databind;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
import java.io.IOException;
/**
* Long 序列化规则
*
* 会将超长 long 值转换为 string解决前端 JavaScript 最大安全整数是 2^53-1 的问题
*
* @author 星语
*/
@JacksonStdImpl
public class NumberSerializer extends com.fasterxml.jackson.databind.ser.std.NumberSerializer {
private static final long MAX_SAFE_INTEGER = 9007199254740991L;
private static final long MIN_SAFE_INTEGER = -9007199254740991L;
public static final NumberSerializer INSTANCE = new NumberSerializer(Number.class);
public NumberSerializer(Class<? extends Number> rawType) {
super(rawType);
}
@Override
public void serialize(Number value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// 超出范围 序列化位字符串
if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {
super.serialize(value, gen, serializers);
} else {
gen.writeString(value.toString());
}
}
}
@@ -0,0 +1,27 @@
package cn.iocoder.yudao.framework.common.util.json.databind;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
/**
* 基于时间戳的 LocalDateTime 反序列化器
*
* @author 老五
*/
public class TimestampLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
public static final TimestampLocalDateTimeDeserializer INSTANCE = new TimestampLocalDateTimeDeserializer();
@Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
// Long 时间戳转换为 LocalDateTime 对象
return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault());
}
}

Some files were not shown because too many files have changed in this diff Show More