Merge branch 'master' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/mail-1.6.1
Conflicts: yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/mail/MailSendApiImpl.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailAccountController.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailLogController.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/MailTemplateController.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountBaseVO.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountCreateReqVO.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountPageReqVO.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/account/MailAccountUpdateReqVO.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogBaseVO.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogExcelVO.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogExportReqVO.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogPageReqVO.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/log/MailLogRespVO.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/send/MailReqVO.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateBaseVO.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateCreateReqVO.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplatePageReqVO.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/mail/vo/template/MailTemplateUpdateReqVO.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/mail/MailAccountConvert.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/mail/MailLogConvert.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/mail/MailTemplateConvert.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailAccountDO.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailTemplateDO.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailAccountMapper.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailLogMapper.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailTemplateMapper.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailAccountService.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailLogService.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailSendService.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/MailTemplateService.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/impl/MailAccountServiceImpl.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/impl/MailLogServiceImpl.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/impl/MailSendServiceImpl.java yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/mail/impl/MailTemplateServiceImpl.java
This commit is contained in:
commit
0189993a2a
|
@ -21,7 +21,7 @@ pipeline {
|
|||
// GitHub 账号名
|
||||
GITHUB_ACCOUNT = 'https://gitee.com/zhijiantianya/ruoyi-vue-pro'
|
||||
// 应用名称
|
||||
APP_NAME = 'yudao-admin-server'
|
||||
APP_NAME = 'yudao-server'
|
||||
// 应用部署路径
|
||||
APP_DEPLOY_BASE_DIR = '/media/pi/KINGTON/data/work/projects/'
|
||||
}
|
||||
|
@ -57,4 +57,4 @@ pipeline {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
15
README.md
15
README.md
|
@ -5,21 +5,28 @@
|
|||
**「我喜欢写代码,乐此不疲」**
|
||||
**「我喜欢做开源,以此为乐」**
|
||||
|
||||
我 🐶 在上海艰苦奋斗,早中晚在 top3 大厂认真搬砖,夜里为开源做贡献。
|
||||
|
||||
如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。
|
||||
|
||||
## 🐯 平台简介
|
||||
|
||||
**芋道**,一套**全部开源**的**企业级**的快速开发平台,毫无保留给个人及企业免费使用。
|
||||
**芋道**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
|
||||
|
||||
> 有任何问题,或者想要的功能,可以在 _Issues_ 中提给艿艿。
|
||||
>
|
||||
> 😜 给项目点点 Star 吧,这对我们真的很重要!
|
||||
|
||||
* 前端采用 [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) ,正在支持 Vue 3 + ElementUI Plus 最新方案。
|
||||
* 后端采用 Spring Boot、MySQL + MyBatis Plus、Redis + Redisson。
|
||||
* 数据库可使用 MySQL、Oracle、PostgreSQL、SQL Server、MariaDB、国产达梦 DM、TiDB 等
|
||||
* 权限认证使用 Spring Security & Token & Redis,支持多终端、多种用户的认证系统。
|
||||
* 支持加载动态权限菜单,按钮级别权限控制,本地缓存提升性能。
|
||||
* 支持 SaaS 多租户系统,可自定义每个租户的权限,提供透明化的多租户底层封装。
|
||||
* 工作流使用 Activiti ,支持动态表单、在线设计流程、多种任务分配方式。
|
||||
* 工作流使用 Activiti + Flowable,支持动态表单、在线设计流程、多种任务分配方式。
|
||||
* 高效率开发,使用代码生成器可以一键生成前后端代码 + 单元测试 + Swagger 接口文档 + Validator 参数校验。
|
||||
* 集成微信小程序、微信公众号、企业微信、钉钉等三方登陆,集成支付宝、微信等支付与退款。
|
||||
* 集成阿里云、腾讯云、云片等短信渠道,集成阿里云、腾讯云、七牛云等云存储服务。
|
||||
* 集成阿里云、腾讯云、云片等短信渠道,集成 MinIO、阿里云、腾讯云、七牛云等云存储服务。
|
||||
|
||||
| 项目名 | 说明 | 传说门 |
|
||||
|--------------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|
@ -150,7 +157,7 @@ ps:核心功能已经实现,正在对接微信小程序中...
|
|||
|
||||
| 框架 | 说明 | 版本 | 学习指南 |
|
||||
|---------------------------------------------------------------------------------------------|------------------|----------|----------------------------------------------------------------|
|
||||
| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.5.10 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
|
||||
| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.5.12 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
|
||||
| [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 | |
|
||||
| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.8 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
|
||||
| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.1 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
|
||||
|
|
|
@ -1,20 +1,15 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# 基础
|
||||
# export JAVA_HOME=/work/programs/jdk/jdk1.8.0_181
|
||||
# export PATH=PATH=$PATH:$JAVA_HOME/bin
|
||||
# export CLASSPATH=$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
|
||||
|
||||
DATE=$(date +%Y%m%d%H%M)
|
||||
# 基础路径
|
||||
BASE_PATH=/media/pi/KINGTON/data/work/projects/yudao-admin-server
|
||||
BASE_PATH=/work/projects/yudao-server
|
||||
# 编译后 jar 的地址。部署时,Jenkins 会上传 jar 包到该目录下
|
||||
SOURCE_PATH=$BASE_PATH/build
|
||||
# 服务名称。同时约定部署服务的 jar 包名字也为它。
|
||||
SERVER_NAME=yudao-admin-server
|
||||
SERVER_NAME=yudao-server
|
||||
# 环境
|
||||
PROFILES_ACTIVE=dev
|
||||
PROFILES_ACTIVE=development
|
||||
# 健康检查 URL
|
||||
HEALTH_CHECK_URL=http://127.0.0.1:48080/actuator/health/
|
||||
|
||||
|
@ -62,7 +57,7 @@ function transfer() {
|
|||
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}')
|
||||
|
@ -71,8 +66,8 @@ function stop() {
|
|||
# 正常关闭
|
||||
echo "[stop] $BASE_PATH/$SERVER_NAME 运行中,开始 kill [$PID]"
|
||||
kill -15 $PID
|
||||
# 等待最大 60 秒,直到关闭完成。
|
||||
for ((i = 0; i < 60; i++))
|
||||
# 等待最大 120 秒,直到关闭完成。
|
||||
for ((i = 0; i < 120; i++))
|
||||
do
|
||||
sleep 1
|
||||
PID=$(ps -ef | grep $BASE_PATH/$SERVER_NAME | grep -v "grep" | awk '{print $2}')
|
||||
|
@ -95,7 +90,7 @@ function stop() {
|
|||
fi
|
||||
}
|
||||
|
||||
# 启动
|
||||
# 启动:启动后端项目
|
||||
function start() {
|
||||
# 开启启动前,打印启动参数
|
||||
echo "[start] 开始启动 $BASE_PATH/$SERVER_NAME"
|
||||
|
@ -108,13 +103,13 @@ function start() {
|
|||
echo "[start] 启动 $BASE_PATH/$SERVER_NAME 完成"
|
||||
}
|
||||
|
||||
# 健康检查
|
||||
# 健康检查:自动判断后端项目是否正常启动
|
||||
function healthCheck() {
|
||||
# 如果配置健康检查,则进行健康检查
|
||||
if [ -n "$HEALTH_CHECK_URL" ]; then
|
||||
# 健康检查最大 60 秒,直到健康检查通过
|
||||
# 健康检查最大 120 秒,直到健康检查通过
|
||||
echo "[healthCheck] 开始通过 $HEALTH_CHECK_URL 地址,进行健康检查";
|
||||
for ((i = 0; i < 60; i++))
|
||||
for ((i = 0; i < 120; i++))
|
||||
do
|
||||
# 请求健康检查地址,只获取状态码。
|
||||
result=`curl -I -m 10 -o /dev/null -s -w %{http_code} $HEALTH_CHECK_URL || echo "000"`
|
||||
|
@ -138,11 +133,11 @@ function healthCheck() {
|
|||
else
|
||||
tail -n 10 nohup.out
|
||||
fi
|
||||
# 如果未配置健康检查,则 slepp 60 秒,人工看日志是否部署成功。
|
||||
# 如果未配置健康检查,则 sleep 120 秒,人工看日志是否部署成功。
|
||||
else
|
||||
echo "[healthCheck] HEALTH_CHECK_URL 未配置,开始 sleep 60 秒";
|
||||
sleep 60
|
||||
echo "[healthCheck] sleep 60 秒完成,查看日志,自行判断是否启动成功";
|
||||
echo "[healthCheck] HEALTH_CHECK_URL 未配置,开始 sleep 120 秒";
|
||||
sleep 120
|
||||
echo "[healthCheck] sleep 120 秒完成,查看日志,自行判断是否启动成功";
|
||||
tail -n 50 nohup.out
|
||||
fi
|
||||
}
|
||||
|
@ -159,7 +154,7 @@ function deploy() {
|
|||
# 启动 Java 服务
|
||||
start
|
||||
# 健康检查
|
||||
# healthCheck
|
||||
healthCheck
|
||||
}
|
||||
|
||||
deploy
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -25,7 +25,7 @@
|
|||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<properties>
|
||||
<revision>1.6.0-snapshot</revision>
|
||||
<revision>1.6.2-snapshot</revision>
|
||||
<!-- Maven 相关 -->
|
||||
<java.version>1.8</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
|
|
4380
sql/bpm-activiti.sql
4380
sql/bpm-activiti.sql
File diff suppressed because one or more lines are too long
1517
sql/bpm-flowable.sql
1517
sql/bpm-flowable.sql
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,3 @@
|
|||
暂未适配 IBM DB2 数据库,如果你有需要,可以微信联系 wangwenbin-server 一起建设。
|
||||
|
||||
你需要把表结构与数据导入到 DM 数据库,我来测试与适配代码。
|
|
@ -0,0 +1,3 @@
|
|||
暂未适配国产 DM 数据库,如果你有需要,可以微信联系 wangwenbin-server 一起建设。
|
||||
|
||||
你需要把表结构与数据导入到 DM 数据库,我来测试与适配代码。
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
288
sql/quartz.sql
288
sql/quartz.sql
|
@ -1,288 +0,0 @@
|
|||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 127.0.0.1
|
||||
Source Server Type : MySQL
|
||||
Source Server Version : 80026
|
||||
Source Host : localhost:3306
|
||||
Source Schema : ruoyi-vue-pro
|
||||
|
||||
Target Server Type : MySQL
|
||||
Target Server Version : 80026
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 05/02/2022 00:50:30
|
||||
*/
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for QRTZ_BLOB_TRIGGERS
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `QRTZ_BLOB_TRIGGERS`;
|
||||
CREATE TABLE `QRTZ_BLOB_TRIGGERS` (
|
||||
`SCHED_NAME` varchar(120) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`TRIGGER_NAME` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`TRIGGER_GROUP` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`BLOB_DATA` blob,
|
||||
PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
|
||||
KEY `SCHED_NAME` (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
|
||||
CONSTRAINT `qrtz_blob_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of QRTZ_BLOB_TRIGGERS
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for QRTZ_CALENDARS
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `QRTZ_CALENDARS`;
|
||||
CREATE TABLE `QRTZ_CALENDARS` (
|
||||
`SCHED_NAME` varchar(120) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`CALENDAR_NAME` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`CALENDAR` blob NOT NULL,
|
||||
PRIMARY KEY (`SCHED_NAME`,`CALENDAR_NAME`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of QRTZ_CALENDARS
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for QRTZ_CRON_TRIGGERS
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `QRTZ_CRON_TRIGGERS`;
|
||||
CREATE TABLE `QRTZ_CRON_TRIGGERS` (
|
||||
`SCHED_NAME` varchar(120) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`TRIGGER_NAME` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`TRIGGER_GROUP` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`CRON_EXPRESSION` varchar(120) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`TIME_ZONE_ID` varchar(80) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
|
||||
CONSTRAINT `qrtz_cron_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of QRTZ_CRON_TRIGGERS
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
INSERT INTO `QRTZ_CRON_TRIGGERS` VALUES ('schedulerName', 'payNotifyJob', 'DEFAULT', '* * * * * ?', 'Asia/Shanghai');
|
||||
INSERT INTO `QRTZ_CRON_TRIGGERS` VALUES ('schedulerName', 'userSessionTimeoutJob', 'DEFAULT', '0 * * * * ? *', 'Asia/Shanghai');
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for QRTZ_FIRED_TRIGGERS
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `QRTZ_FIRED_TRIGGERS`;
|
||||
CREATE TABLE `QRTZ_FIRED_TRIGGERS` (
|
||||
`SCHED_NAME` varchar(120) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`ENTRY_ID` varchar(95) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`TRIGGER_NAME` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`TRIGGER_GROUP` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`INSTANCE_NAME` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`FIRED_TIME` bigint NOT NULL,
|
||||
`SCHED_TIME` bigint NOT NULL,
|
||||
`PRIORITY` int NOT NULL,
|
||||
`STATE` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`JOB_NAME` varchar(190) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`JOB_GROUP` varchar(190) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`IS_NONCONCURRENT` varchar(1) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`REQUESTS_RECOVERY` varchar(1) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
PRIMARY KEY (`SCHED_NAME`,`ENTRY_ID`),
|
||||
KEY `IDX_QRTZ_FT_TRIG_INST_NAME` (`SCHED_NAME`,`INSTANCE_NAME`),
|
||||
KEY `IDX_QRTZ_FT_INST_JOB_REQ_RCVRY` (`SCHED_NAME`,`INSTANCE_NAME`,`REQUESTS_RECOVERY`),
|
||||
KEY `IDX_QRTZ_FT_J_G` (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`),
|
||||
KEY `IDX_QRTZ_FT_JG` (`SCHED_NAME`,`JOB_GROUP`),
|
||||
KEY `IDX_QRTZ_FT_T_G` (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
|
||||
KEY `IDX_QRTZ_FT_TG` (`SCHED_NAME`,`TRIGGER_GROUP`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of QRTZ_FIRED_TRIGGERS
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for QRTZ_JOB_DETAILS
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `QRTZ_JOB_DETAILS`;
|
||||
CREATE TABLE `QRTZ_JOB_DETAILS` (
|
||||
`SCHED_NAME` varchar(120) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`JOB_NAME` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`JOB_GROUP` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`DESCRIPTION` varchar(250) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`JOB_CLASS_NAME` varchar(250) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`IS_DURABLE` varchar(1) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`IS_NONCONCURRENT` varchar(1) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`IS_UPDATE_DATA` varchar(1) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`REQUESTS_RECOVERY` varchar(1) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`JOB_DATA` blob,
|
||||
PRIMARY KEY (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`),
|
||||
KEY `IDX_QRTZ_J_REQ_RECOVERY` (`SCHED_NAME`,`REQUESTS_RECOVERY`),
|
||||
KEY `IDX_QRTZ_J_GRP` (`SCHED_NAME`,`JOB_GROUP`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of QRTZ_JOB_DETAILS
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
INSERT INTO `QRTZ_JOB_DETAILS` VALUES ('schedulerName', 'payNotifyJob', 'DEFAULT', NULL, 'cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker', '0', '1', '1', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000027400064A4F425F49447372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000000000000057400104A4F425F48414E444C45525F4E414D4574000C7061794E6F746966794A6F627800);
|
||||
INSERT INTO `QRTZ_JOB_DETAILS` VALUES ('schedulerName', 'userSessionTimeoutJob', 'DEFAULT', NULL, 'cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker', '0', '1', '1', '0', 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000027400064A4F425F49447372000E6A6176612E6C616E672E4C6F6E673B8BE490CC8F23DF0200014A000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B0200007870000000000000000D7400104A4F425F48414E444C45525F4E414D457400157573657253657373696F6E54696D656F75744A6F627800);
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for QRTZ_LOCKS
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `QRTZ_LOCKS`;
|
||||
CREATE TABLE `QRTZ_LOCKS` (
|
||||
`SCHED_NAME` varchar(120) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`LOCK_NAME` varchar(40) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
PRIMARY KEY (`SCHED_NAME`,`LOCK_NAME`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of QRTZ_LOCKS
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
INSERT INTO `QRTZ_LOCKS` VALUES ('schedulerName', 'STATE_ACCESS');
|
||||
INSERT INTO `QRTZ_LOCKS` VALUES ('schedulerName', 'TRIGGER_ACCESS');
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for QRTZ_PAUSED_TRIGGER_GRPS
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `QRTZ_PAUSED_TRIGGER_GRPS`;
|
||||
CREATE TABLE `QRTZ_PAUSED_TRIGGER_GRPS` (
|
||||
`SCHED_NAME` varchar(120) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`TRIGGER_GROUP` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
PRIMARY KEY (`SCHED_NAME`,`TRIGGER_GROUP`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of QRTZ_PAUSED_TRIGGER_GRPS
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for QRTZ_SCHEDULER_STATE
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `QRTZ_SCHEDULER_STATE`;
|
||||
CREATE TABLE `QRTZ_SCHEDULER_STATE` (
|
||||
`SCHED_NAME` varchar(120) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`INSTANCE_NAME` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`LAST_CHECKIN_TIME` bigint NOT NULL,
|
||||
`CHECKIN_INTERVAL` bigint NOT NULL,
|
||||
PRIMARY KEY (`SCHED_NAME`,`INSTANCE_NAME`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of QRTZ_SCHEDULER_STATE
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
INSERT INTO `QRTZ_SCHEDULER_STATE` VALUES ('schedulerName', 'Yunai.local1635571630493', 1635572537879, 15000);
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for QRTZ_SIMPLE_TRIGGERS
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `QRTZ_SIMPLE_TRIGGERS`;
|
||||
CREATE TABLE `QRTZ_SIMPLE_TRIGGERS` (
|
||||
`SCHED_NAME` varchar(120) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`TRIGGER_NAME` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`TRIGGER_GROUP` varchar(190) COLLATE utf8mb4_unicode_ci 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`),
|
||||
CONSTRAINT `qrtz_simple_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of QRTZ_SIMPLE_TRIGGERS
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for QRTZ_SIMPROP_TRIGGERS
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `QRTZ_SIMPROP_TRIGGERS`;
|
||||
CREATE TABLE `QRTZ_SIMPROP_TRIGGERS` (
|
||||
`SCHED_NAME` varchar(120) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`TRIGGER_NAME` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`TRIGGER_GROUP` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`STR_PROP_1` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`STR_PROP_2` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`STR_PROP_3` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`INT_PROP_1` int DEFAULT NULL,
|
||||
`INT_PROP_2` int DEFAULT NULL,
|
||||
`LONG_PROP_1` bigint DEFAULT NULL,
|
||||
`LONG_PROP_2` bigint DEFAULT NULL,
|
||||
`DEC_PROP_1` decimal(13,4) DEFAULT NULL,
|
||||
`DEC_PROP_2` decimal(13,4) DEFAULT NULL,
|
||||
`BOOL_PROP_1` varchar(1) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`BOOL_PROP_2` varchar(1) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
|
||||
CONSTRAINT `qrtz_simprop_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`) REFERENCES `QRTZ_TRIGGERS` (`SCHED_NAME`, `TRIGGER_NAME`, `TRIGGER_GROUP`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of QRTZ_SIMPROP_TRIGGERS
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for QRTZ_TRIGGERS
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `QRTZ_TRIGGERS`;
|
||||
CREATE TABLE `QRTZ_TRIGGERS` (
|
||||
`SCHED_NAME` varchar(120) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`TRIGGER_NAME` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`TRIGGER_GROUP` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`JOB_NAME` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`JOB_GROUP` varchar(190) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`DESCRIPTION` varchar(250) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`NEXT_FIRE_TIME` bigint DEFAULT NULL,
|
||||
`PREV_FIRE_TIME` bigint DEFAULT NULL,
|
||||
`PRIORITY` int DEFAULT NULL,
|
||||
`TRIGGER_STATE` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`TRIGGER_TYPE` varchar(8) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`START_TIME` bigint NOT NULL,
|
||||
`END_TIME` bigint DEFAULT NULL,
|
||||
`CALENDAR_NAME` varchar(190) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
|
||||
`MISFIRE_INSTR` smallint DEFAULT NULL,
|
||||
`JOB_DATA` blob,
|
||||
PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`),
|
||||
KEY `IDX_QRTZ_T_J` (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`),
|
||||
KEY `IDX_QRTZ_T_JG` (`SCHED_NAME`,`JOB_GROUP`),
|
||||
KEY `IDX_QRTZ_T_C` (`SCHED_NAME`,`CALENDAR_NAME`),
|
||||
KEY `IDX_QRTZ_T_G` (`SCHED_NAME`,`TRIGGER_GROUP`),
|
||||
KEY `IDX_QRTZ_T_STATE` (`SCHED_NAME`,`TRIGGER_STATE`),
|
||||
KEY `IDX_QRTZ_T_N_STATE` (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`,`TRIGGER_STATE`),
|
||||
KEY `IDX_QRTZ_T_N_G_STATE` (`SCHED_NAME`,`TRIGGER_GROUP`,`TRIGGER_STATE`),
|
||||
KEY `IDX_QRTZ_T_NEXT_FIRE_TIME` (`SCHED_NAME`,`NEXT_FIRE_TIME`),
|
||||
KEY `IDX_QRTZ_T_NFT_ST` (`SCHED_NAME`,`TRIGGER_STATE`,`NEXT_FIRE_TIME`),
|
||||
KEY `IDX_QRTZ_T_NFT_MISFIRE` (`SCHED_NAME`,`MISFIRE_INSTR`,`NEXT_FIRE_TIME`),
|
||||
KEY `IDX_QRTZ_T_NFT_ST_MISFIRE` (`SCHED_NAME`,`MISFIRE_INSTR`,`NEXT_FIRE_TIME`,`TRIGGER_STATE`),
|
||||
KEY `IDX_QRTZ_T_NFT_ST_MISFIRE_GRP` (`SCHED_NAME`,`MISFIRE_INSTR`,`NEXT_FIRE_TIME`,`TRIGGER_GROUP`,`TRIGGER_STATE`),
|
||||
CONSTRAINT `qrtz_triggers_ibfk_1` FOREIGN KEY (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`) REFERENCES `QRTZ_JOB_DETAILS` (`SCHED_NAME`, `JOB_NAME`, `JOB_GROUP`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of QRTZ_TRIGGERS
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
INSERT INTO `QRTZ_TRIGGERS` VALUES ('schedulerName', 'payNotifyJob', 'DEFAULT', 'payNotifyJob', 'DEFAULT', NULL, 1635572540000, 1635572539000, 5, 'WAITING', 'CRON', 1635294882000, 0, NULL, 0, 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000037400114A4F425F48414E444C45525F504152414D707400124A4F425F52455452595F494E54455256414C737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000074000F4A4F425F52455452595F434F554E5471007E000B7800);
|
||||
INSERT INTO `QRTZ_TRIGGERS` VALUES ('schedulerName', 'userSessionTimeoutJob', 'DEFAULT', 'userSessionTimeoutJob', 'DEFAULT', NULL, 1643993400000, -1, 5, 'WAITING', 'CRON', 1643993386000, 0, NULL, 0, 0xACED0005737200156F72672E71756172747A2E4A6F62446174614D61709FB083E8BFA9B0CB020000787200266F72672E71756172747A2E7574696C732E537472696E674B65794469727479466C61674D61708208E8C3FBC55D280200015A0013616C6C6F77735472616E7369656E74446174617872001D6F72672E71756172747A2E7574696C732E4469727479466C61674D617013E62EAD28760ACE0200025A000564697274794C00036D617074000F4C6A6176612F7574696C2F4D61703B787001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000C770800000010000000037400114A4F425F48414E444C45525F504152414D707400124A4F425F52455452595F494E54455256414C737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B0200007870000007D074000F4A4F425F52455452595F434F554E547371007E0009000000037800);
|
||||
COMMIT;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
|
@ -14,9 +14,9 @@
|
|||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<properties>
|
||||
<revision>1.6.0-snapshot</revision>
|
||||
<revision>1.6.2-snapshot</revision>
|
||||
<!-- 统一依赖管理 -->
|
||||
<spring.boot.version>2.5.10</spring.boot.version>
|
||||
<spring.boot.version>2.5.12</spring.boot.version>
|
||||
<!-- Web 相关 -->
|
||||
<knife4j.version>3.0.2</knife4j.version>
|
||||
<swagger-annotations.version>1.5.22</swagger-annotations.version>
|
||||
|
@ -25,10 +25,11 @@
|
|||
<mysql.version>5.1.46</mysql.version>
|
||||
<druid.version>1.2.8</druid.version>
|
||||
<mybatis-plus.version>3.4.3.4</mybatis-plus.version>
|
||||
<mybatis-plus-generator.version>3.5.2</mybatis-plus-generator.version>
|
||||
<dynamic-datasource.version>3.5.0</dynamic-datasource.version>
|
||||
<redisson.version>3.16.6</redisson.version>
|
||||
<redisson.version>3.17.0</redisson.version>
|
||||
<!-- Config 配置中心相关 -->
|
||||
<apollo.version>1.7.0</apollo.version>
|
||||
<apollo.version>1.9.2</apollo.version>
|
||||
<!-- Job 定时任务相关 -->
|
||||
<!-- 服务保障相关 -->
|
||||
<lock4j.version>2.2.0</lock4j.version>
|
||||
|
@ -45,6 +46,7 @@
|
|||
<activiti.version>7.1.0.M6</activiti.version>
|
||||
<flowable.version>6.7.0</flowable.version>
|
||||
<!-- 工具类相关 -->
|
||||
<jasypt-spring-boot-starter.version>3.0.4</jasypt-spring-boot-starter.version>
|
||||
<lombok.version>1.18.20</lombok.version>
|
||||
<mapstruct.version>1.4.1.Final</mapstruct.version>
|
||||
<hutool.version>5.6.1</hutool.version>
|
||||
|
@ -52,13 +54,15 @@
|
|||
<velocity.version>2.2</velocity.version>
|
||||
<screw.version>1.0.5</screw.version>
|
||||
<guava.version>30.1.1-jre</guava.version>
|
||||
<guice.version>5.1.0</guice.version>
|
||||
<transmittable-thread-local.version>2.12.2</transmittable-thread-local.version>
|
||||
<commons-net.version>3.8.0</commons-net.version>
|
||||
<jsch.version>0.1.55</jsch.version>
|
||||
<!-- 三方云服务相关 -->
|
||||
<s3.version>2.17.147</s3.version>
|
||||
<minio.version>8.2.2</minio.version>
|
||||
<aliyun-java-sdk-core.version>4.5.25</aliyun-java-sdk-core.version>
|
||||
<aliyun-java-sdk-dysmsapi.version>2.1.0</aliyun-java-sdk-dysmsapi.version>
|
||||
<tencentcloud-sdk-java.version>3.1.471</tencentcloud-sdk-java.version>
|
||||
<yunpian-java-sdk.version>1.2.7</yunpian-java-sdk.version>
|
||||
<justauth.version>1.4.0</justauth.version>
|
||||
</properties>
|
||||
|
@ -190,6 +194,11 @@
|
|||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-generator</artifactId> <!-- 代码生成器,使用它解析表结构 -->
|
||||
<version>${mybatis-plus-generator.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>dynamic-datasource-spring-boot-starter</artifactId> <!-- 多数据源 -->
|
||||
|
@ -426,6 +435,12 @@
|
|||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.ulisesbocchio</groupId>
|
||||
<artifactId>jasypt-spring-boot-starter</artifactId> <!-- 加解密 -->
|
||||
<version>${jasypt-spring-boot-starter.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-excel</artifactId>
|
||||
|
@ -490,6 +505,12 @@
|
|||
<version>${guava.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.inject</groupId>
|
||||
<artifactId>guice</artifactId>
|
||||
<version>${guice.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>transmittable-thread-local</artifactId> <!-- 解决 ThreadLocal 父子线程的传值问题 -->
|
||||
|
@ -514,9 +535,9 @@
|
|||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3</artifactId>
|
||||
<version>${s3.version}</version>
|
||||
<groupId>io.minio</groupId>
|
||||
<artifactId>minio</artifactId>
|
||||
<version>${minio.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- SMS SDK begin -->
|
||||
|
@ -545,6 +566,11 @@
|
|||
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
|
||||
<version>${aliyun-java-sdk-dysmsapi.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.tencentcloudapi</groupId>
|
||||
<artifactId>tencentcloud-sdk-java</artifactId>
|
||||
<version>${tencentcloud-sdk-java.version}</version>
|
||||
</dependency>
|
||||
<!-- SMS SDK end -->
|
||||
|
||||
<dependency>
|
||||
|
|
|
@ -1,8 +1,19 @@
|
|||
package cn.iocoder.yudao.framework.common.util.collection;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.TypeUtil;
|
||||
import org.springframework.cglib.core.TypeUtils;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
/**
|
||||
* Array 工具类
|
||||
|
@ -30,4 +41,16 @@ public class ArrayUtils {
|
|||
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>) CollectionUtil.getElementType(from.iterator()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -115,7 +115,7 @@ public class CollectionUtils {
|
|||
return new HashMap<>();
|
||||
}
|
||||
return from.stream()
|
||||
.collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toList())));
|
||||
.collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toList())));
|
||||
}
|
||||
|
||||
// 暂时没想好名字,先以 2 结尾噶
|
||||
|
@ -169,4 +169,5 @@ public class CollectionUtils {
|
|||
public static <T> Collection<T> singleton(T deptId) {
|
||||
return deptId == null ? Collections.emptyList() : Collections.singleton(deptId);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.common.util.http;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.map.TableMap;
|
||||
import cn.hutool.core.net.url.UrlBuilder;
|
||||
import cn.hutool.core.util.ReferenceUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
|
|
@ -2,7 +2,6 @@ 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.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
|
|
@ -35,7 +35,7 @@ import java.util.Set;
|
|||
* 注意,使用 DeptDataPermissionRule 时,需要保证表中有 dept_id 部门编号的字段,可自定义。
|
||||
*
|
||||
* 实际业务场景下,会存在一个经典的问题?当用户修改部门时,冗余的 dept_id 是否需要修改?
|
||||
* 1. 一般情况下,dept_id 不进行修改,则会导致用户看到之前的数据。【yudao-admin-server 采用该方案】
|
||||
* 1. 一般情况下,dept_id 不进行修改,则会导致用户看到之前的数据。【yudao-server 采用该方案】
|
||||
* 2. 部分情况下,希望该用户还是能看到之前的数据,则有两种方式解决:【需要你改造该 DeptDataPermissionRule 的实现代码】
|
||||
* 1)编写洗数据的脚本,将 dept_id 修改成新部门的编号;【建议】
|
||||
* 最终过滤条件是 WHERE dept_id = ?
|
||||
|
|
|
@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
|||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayQrPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayWapPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXNativePayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPubPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
|
@ -63,6 +64,7 @@ public class PayClientFactoryImpl implements PayClientFactory {
|
|||
case WX_PUB: return (AbstractPayClient<Config>) new WXPubPayClient(channelId, (WXPayClientConfig) config);
|
||||
case WX_LITE: return (AbstractPayClient<Config>) new WXPubPayClient(channelId, (WXPayClientConfig) config);
|
||||
case WX_APP: return (AbstractPayClient<Config>) new WXPubPayClient(channelId, (WXPayClientConfig) config);
|
||||
case WX_NATIVE: return (AbstractPayClient<Config>) new WXNativePayClient(channelId, (WXPayClientConfig) config);
|
||||
case ALIPAY_WAP: return (AbstractPayClient<Config>) new AlipayWapPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
case ALIPAY_QR: return (AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
case ALIPAY_APP: return (AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
|
||||
|
|
|
@ -18,6 +18,7 @@ import lombok.SneakyThrows;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
|
@ -54,7 +55,8 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
|
|||
*/
|
||||
@Override
|
||||
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception {
|
||||
Map<String, String> params = data.getParams();
|
||||
Map<String, String> params = strToMap(data.getBody());
|
||||
|
||||
return PayOrderNotifyRespDTO.builder().orderExtensionNo(params.get("out_trade_no"))
|
||||
.channelOrderNo(params.get("trade_no")).channelUserId(params.get("seller_id"))
|
||||
.tradeStatus(params.get("trade_status"))
|
||||
|
@ -64,7 +66,7 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
|
|||
|
||||
@Override
|
||||
public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
|
||||
Map<String, String> params = notifyData.getParams();
|
||||
Map<String, String> params = strToMap(notifyData.getBody());
|
||||
PayRefundNotifyDTO notifyDTO = PayRefundNotifyDTO.builder().channelOrderNo(params.get("trade_no"))
|
||||
.tradeNo(params.get("out_trade_no"))
|
||||
.reqNo(params.get("out_biz_no"))
|
||||
|
@ -128,4 +130,17 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
|
|||
}
|
||||
}
|
||||
|
||||
public static Map<String, String> strToMap(String s) {
|
||||
Map<String, String> stringStringMap = new HashMap<>();
|
||||
//调整时间格式
|
||||
String s3 = s.replaceAll("%3A", ":");
|
||||
//获取map
|
||||
String s4 = s3.replace("+", " ");
|
||||
String[] split = s4.split("&");
|
||||
for (String s1 : split) {
|
||||
String[] split1 = s1.split("=");
|
||||
stringStringMap.put(split1[0], split1[1]);
|
||||
}
|
||||
return stringStringMap;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
|
||||
import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult;
|
||||
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
|
||||
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
|
||||
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
|
||||
import com.github.binarywang.wxpay.config.WxPayConfig;
|
||||
import com.github.binarywang.wxpay.constant.WxPayConstants;
|
||||
import com.github.binarywang.wxpay.exception.WxPayException;
|
||||
import com.github.binarywang.wxpay.service.WxPayService;
|
||||
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS;
|
||||
import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS;
|
||||
|
||||
|
||||
@Slf4j
|
||||
public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
|
||||
private WxPayService client;
|
||||
|
||||
public WXNativePayClient(Long channelId, WXPayClientConfig config) {
|
||||
super(channelId, PayChannelEnum.WX_NATIVE.getCode(), config, new WXCodeMapping());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInit() {
|
||||
WxPayConfig payConfig = new WxPayConfig();
|
||||
BeanUtil.copyProperties(config, payConfig, "keyContent");
|
||||
payConfig.setTradeType(WxPayConstants.TradeType.NATIVE); // 设置使用 native 支付方式
|
||||
// if (StrUtil.isNotEmpty(config.getKeyContent())) {
|
||||
// payConfig.setKeyContent(config.getKeyContent().getBytes(StandardCharsets.UTF_8));
|
||||
// }
|
||||
if (StrUtil.isNotEmpty(config.getPrivateKeyContent())) {
|
||||
// weixin-pay-java 存在 BUG,无法直接设置内容,所以创建临时文件来解决
|
||||
payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath());
|
||||
}
|
||||
if (StrUtil.isNotEmpty(config.getPrivateCertContent())) {
|
||||
// weixin-pay-java 存在 BUG,无法直接设置内容,所以创建临时文件来解决
|
||||
payConfig.setPrivateCertPath(FileUtils.createTempFile(config.getPrivateCertContent()).getPath());
|
||||
}
|
||||
// 真实客户端
|
||||
this.client = new WxPayServiceImpl();
|
||||
client.setConfig(payConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayCommonResult<String> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
// 这里原生的返回的是支付的 url 所以直接使用string接收
|
||||
//"invokeResponse": "weixin://wxpay/bizpayurl?pr=EGYAem7zz"
|
||||
String responseV3;
|
||||
try {
|
||||
switch (config.getApiVersion()) {
|
||||
case WXPayClientConfig.API_VERSION_V2:
|
||||
responseV3 = unifiedOrderV2(reqDTO).getCodeUrl();
|
||||
break;
|
||||
case WXPayClientConfig.API_VERSION_V3:
|
||||
responseV3 = this.unifiedOrderV3(reqDTO);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||
}
|
||||
} catch (WxPayException e) {
|
||||
log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
|
||||
return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
|
||||
ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()), null, codeMapping);
|
||||
}
|
||||
return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, responseV3, codeMapping);
|
||||
}
|
||||
|
||||
private WxPayNativeOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
|
||||
//前端
|
||||
String trade_type = reqDTO.getChannelExtras().get("trade_type");
|
||||
// 构建 WxPayUnifiedOrderRequest 对象
|
||||
WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest
|
||||
.newBuilder()
|
||||
.outTradeNo(reqDTO.getMerchantOrderId())
|
||||
.body(reqDTO.getBody())
|
||||
.totalFee(reqDTO.getAmount().intValue()) // 单位分
|
||||
.timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
|
||||
.spbillCreateIp(reqDTO.getUserIp())
|
||||
.notifyUrl(reqDTO.getNotifyUrl())
|
||||
.productId(trade_type)
|
||||
.build();
|
||||
// 执行请求
|
||||
return client.createOrder(request);
|
||||
}
|
||||
|
||||
private String unifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
|
||||
// 构建 WxPayUnifiedOrderRequest 对象
|
||||
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
|
||||
request.setOutTradeNo(reqDTO.getMerchantOrderId());
|
||||
request.setDescription(reqDTO.getBody());
|
||||
request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount().intValue())); // 单位分
|
||||
request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
|
||||
request.setNotifyUrl(reqDTO.getNotifyUrl());
|
||||
// 执行请求
|
||||
// log.info("支付字段request:{}",request.getTimeExpire());
|
||||
|
||||
return client.createOrderV3(TradeTypeEnum.NATIVE, request);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException {
|
||||
WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
|
||||
Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
|
||||
// 转换结果
|
||||
return PayOrderNotifyRespDTO.builder().orderExtensionNo(notifyResult.getOutTradeNo())
|
||||
.channelOrderNo(notifyResult.getTransactionId()).channelUserId(notifyResult.getOpenid())
|
||||
.successTime(DateUtil.parse(notifyResult.getTimeEnd(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
|
||||
.data(data.getBody()).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
|
||||
//TODO 需要实现
|
||||
throw new UnsupportedOperationException("需要实现");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
||||
//TODO 需要实现
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
|
@ -82,9 +82,9 @@ public class WXPayClientConfig implements PayClientConfig {
|
|||
@NotBlank(message = "apiclient_cert 不能为空", groups = V3.class)
|
||||
private String privateCertContent;
|
||||
/**
|
||||
* apiV3 秘钥值
|
||||
* apiV3 密钥值
|
||||
*/
|
||||
@NotBlank(message = "apiV3 秘钥值 不能为空", groups = V3.class)
|
||||
@NotBlank(message = "apiV3 密钥值 不能为空", groups = V3.class)
|
||||
private String apiV3Key;
|
||||
|
||||
/**
|
||||
|
|
|
@ -98,7 +98,7 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
// TODO 芋艿:貌似没 title?
|
||||
.body(reqDTO.getBody())
|
||||
.totalFee(reqDTO.getAmount().intValue()) // 单位分
|
||||
.timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss"))
|
||||
.timeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
|
||||
.spbillCreateIp(reqDTO.getUserIp())
|
||||
.openid(getOpenid(reqDTO))
|
||||
.notifyUrl(reqDTO.getNotifyUrl())
|
||||
|
@ -114,7 +114,7 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
|
|||
// TODO 芋艿:貌似没 title?
|
||||
request.setDescription(reqDTO.getBody());
|
||||
request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount().intValue())); // 单位分
|
||||
request.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyyMMddHHmmss"));
|
||||
request.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"));
|
||||
request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO)));
|
||||
request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
|
||||
request.setNotifyUrl(reqDTO.getNotifyUrl());
|
||||
|
|
|
@ -20,6 +20,8 @@ public enum PayChannelEnum {
|
|||
WX_PUB("wx_pub", "微信 JSAPI 支付", WXPayClientConfig.class), // 公众号网页
|
||||
WX_LITE("wx_lite", "微信小程序支付", WXPayClientConfig.class),
|
||||
WX_APP("wx_app", "微信 App 支付", WXPayClientConfig.class),
|
||||
WX_NATIVE("wx_native", "微信 native 支付", WXPayClientConfig.class),
|
||||
|
||||
|
||||
ALIPAY_PC("alipay_pc", "支付宝 PC 网站支付", AlipayPayClientConfig.class),
|
||||
ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付", AlipayPayClientConfig.class),
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>短信拓展,支持阿里云、云片</description>
|
||||
<description>短信拓展,支持阿里云、云片、腾讯云</description>
|
||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<dependencies>
|
||||
|
@ -77,6 +77,10 @@
|
|||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.tencentcloudapi</groupId>
|
||||
<artifactId>tencentcloud-sdk-java</artifactId>
|
||||
</dependency>
|
||||
<!-- SMS SDK end -->
|
||||
</dependencies>
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import java.util.List;
|
|||
* 短信客户端,用于对接各短信平台的 SDK,实现短信发送等功能
|
||||
*
|
||||
* @author zzf
|
||||
* @date 2021/1/25 14:14
|
||||
* @since 2021/1/25 14:14
|
||||
*/
|
||||
public interface SmsClient {
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
|
|||
* 短信客户端的工厂接口
|
||||
*
|
||||
* @author zzf
|
||||
* @date 2021/1/28 14:01
|
||||
* @since 2021/1/28 14:01
|
||||
*/
|
||||
public interface SmsClientFactory {
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import java.util.List;
|
|||
* 短信客户端的抽象类,提供模板方法,减少子类的冗余代码
|
||||
*
|
||||
* @author zzf
|
||||
* @date 2021/2/1 9:28
|
||||
* @since 2021/2/1 9:28
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractSmsClient implements SmsClient {
|
||||
|
@ -31,7 +31,7 @@ public abstract class AbstractSmsClient implements SmsClient {
|
|||
protected final SmsCodeMapping codeMapping;
|
||||
|
||||
public AbstractSmsClient(SmsChannelProperties properties, SmsCodeMapping codeMapping) {
|
||||
this.properties = properties;
|
||||
this.properties = prepareProperties(properties);
|
||||
this.codeMapping = codeMapping;
|
||||
}
|
||||
|
||||
|
@ -54,11 +54,21 @@ public abstract class AbstractSmsClient implements SmsClient {
|
|||
return;
|
||||
}
|
||||
log.info("[refresh][配置({})发生变化,重新初始化]", properties);
|
||||
this.properties = properties;
|
||||
this.properties = prepareProperties(properties);
|
||||
// 初始化
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在赋值给{@link this#properties}前,子类可根据需要预处理短信渠道配置
|
||||
*
|
||||
* @param properties 数据库中存储的短信渠道配置
|
||||
* @return 满足子类实现的短信渠道配置
|
||||
*/
|
||||
protected SmsChannelProperties prepareProperties(SmsChannelProperties properties) {
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getId() {
|
||||
return properties.getId();
|
||||
|
|
|
@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.sms.core.client.SmsClient;
|
|||
import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.impl.aliyun.AliyunSmsClient;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.impl.debug.DebugDingTalkSmsClient;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.impl.tencent.TencentSmsClient;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.impl.yunpian.YunpianSmsClient;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsChannelEnum;
|
||||
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
|
||||
|
@ -44,7 +45,7 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
|
|||
Arrays.stream(SmsChannelEnum.values()).forEach(channel -> {
|
||||
// 创建一个空的 SmsChannelProperties 对象
|
||||
SmsChannelProperties properties = new SmsChannelProperties().setCode(channel.getCode())
|
||||
.setApiKey("default").setApiSecret("default");
|
||||
.setApiKey("default default").setApiSecret("default");
|
||||
// 创建 Sms 客户端
|
||||
AbstractSmsClient smsClient = createSmsClient(properties);
|
||||
channelCodeClients.put(channel.getCode(), smsClient);
|
||||
|
@ -81,6 +82,7 @@ public class SmsClientFactoryImpl implements SmsClientFactory {
|
|||
case ALIYUN: return new AliyunSmsClient(properties);
|
||||
case YUN_PIAN: return new YunpianSmsClient(properties);
|
||||
case DEBUG_DING_TALK: return new DebugDingTalkSmsClient(properties);
|
||||
case TENCENT: return new TencentSmsClient(properties);
|
||||
}
|
||||
// 创建失败,错误日志 + 抛出异常
|
||||
log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties);
|
||||
|
|
|
@ -41,7 +41,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DE
|
|||
* 阿里短信客户端的实现类
|
||||
*
|
||||
* @author zzf
|
||||
* @date 2021/1/25 14:17
|
||||
* @since 2021/1/25 14:17
|
||||
*/
|
||||
@Slf4j
|
||||
public class AliyunSmsClient extends AbstractSmsClient {
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
package cn.iocoder.yudao.framework.sms.core.client.impl.tencent;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 腾讯云短信配置实现类
|
||||
* 腾讯云发送短信时,需要额外的参数 sdkAppId,
|
||||
*
|
||||
* @author shiwp
|
||||
*/
|
||||
@Data
|
||||
public class TencentSmsChannelProperties extends SmsChannelProperties {
|
||||
|
||||
/**
|
||||
* 应用 id
|
||||
*/
|
||||
private String sdkAppId;
|
||||
|
||||
/**
|
||||
* 考虑到不破坏原有的 apiKey + apiSecret 的结构,
|
||||
* 所以腾讯云短信存储时,将 secretId 拼接到 apiKey 字段中,格式为 "secretId sdkAppId"。
|
||||
* 因此在使用时,需要将 secretId 和 sdkAppId 解析出来,分别存储到对应字段中。
|
||||
*/
|
||||
public static TencentSmsChannelProperties build(SmsChannelProperties properties) {
|
||||
if (properties instanceof TencentSmsChannelProperties) {
|
||||
return (TencentSmsChannelProperties) properties;
|
||||
}
|
||||
TencentSmsChannelProperties result = BeanUtil.toBean(properties, TencentSmsChannelProperties.class);
|
||||
String combineKey = properties.getApiKey();
|
||||
Assert.notEmpty(combineKey, "apiKey 不能为空");
|
||||
String[] keys = combineKey.trim().split(" ");
|
||||
Assert.isTrue(keys.length == 2, "腾讯云短信 apiKey 配置格式错误,请配置 为[secretId sdkAppId]");
|
||||
Assert.notBlank(keys[0], "腾讯云短信 secretId 不能为空");
|
||||
Assert.notBlank(keys[1], "腾讯云短信 sdkAppId 不能为空");
|
||||
result.setSdkAppId(keys[1]).setApiKey(keys[0]);
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,302 @@
|
|||
package cn.iocoder.yudao.framework.sms.core.client.impl.tencent;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.impl.AbstractSmsClient;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.tencentcloudapi.common.Credential;
|
||||
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
|
||||
import com.tencentcloudapi.sms.v20210111.SmsClient;
|
||||
import com.tencentcloudapi.sms.v20210111.models.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;
|
||||
|
||||
/**
|
||||
* 腾讯云短信功能实现
|
||||
* <p>
|
||||
* 参见 https://cloud.tencent.com/document/product/382/52077
|
||||
*
|
||||
* @author shiwp
|
||||
*/
|
||||
public class TencentSmsClient extends AbstractSmsClient {
|
||||
|
||||
/**
|
||||
* 调用成功 code
|
||||
*/
|
||||
public static final String API_SUCCESS_CODE = "Ok";
|
||||
|
||||
/**
|
||||
* REGION,使用南京
|
||||
*/
|
||||
private static final String ENDPOINT = "ap-nanjing";
|
||||
|
||||
/**
|
||||
* 是否国际/港澳台短信:
|
||||
* 0:表示国内短信。
|
||||
* 1:表示国际/港澳台短信。
|
||||
*/
|
||||
private static final long INTERNATIONAL = 0L;
|
||||
|
||||
private SmsClient client;
|
||||
|
||||
public TencentSmsClient(SmsChannelProperties properties) {
|
||||
super(properties, new TencentSmsCodeMapping());
|
||||
Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doInit() {
|
||||
// 实例化一个认证对象,入参需要传入腾讯云账户密钥对 secretId,secretKey
|
||||
Credential credential = new Credential(properties.getApiKey(), properties.getApiSecret());
|
||||
client = new SmsClient(credential, ENDPOINT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SmsCommonResult<SmsSendRespDTO> doSendSms(Long sendLogId,
|
||||
String mobile,
|
||||
String apiTemplateId,
|
||||
List<KeyValue<String, Object>> templateParams) throws Throwable {
|
||||
return invoke(() -> buildSendSmsRequest(sendLogId, mobile, apiTemplateId, templateParams),
|
||||
this::doSendSms0,
|
||||
response -> {
|
||||
SendStatus sendStatus = response.getSendStatusSet()[0];
|
||||
return SmsCommonResult.build(sendStatus.getCode(), sendStatus.getMessage(), response.getRequestId(),
|
||||
new SmsSendRespDTO().setSerialNo(sendStatus.getSerialNo()), codeMapping);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 腾讯云发放短信的时候,需要额外的参数 sdkAppId。
|
||||
* 考虑到不破坏原有的 apiKey + apiSecret 的结构,所以将 secretId 拼接到 apiKey 字段中,格式为 "secretId sdkAppId"。
|
||||
* 因此,这边需要使用 TencentSmsChannelProperties 做拆分,重新封装到 properties 内。
|
||||
*
|
||||
* @param properties 数据库中存储的短信渠道配置
|
||||
* @return TencentSmsChannelProperties
|
||||
*/
|
||||
@Override
|
||||
protected SmsChannelProperties prepareProperties(SmsChannelProperties properties) {
|
||||
return TencentSmsChannelProperties.build(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用腾讯云 SDK 发送短信
|
||||
*
|
||||
* @param request 发送短信请求
|
||||
* @return 发送短信响应
|
||||
* @throws TencentCloudSDKException SDK 用来封装发送短信失败
|
||||
*/
|
||||
private SendSmsResponse doSendSms0(SendSmsRequest request) throws TencentCloudSDKException {
|
||||
return client.SendSms(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装腾讯云发送短信请求
|
||||
*
|
||||
* @param sendLogId 日志编号
|
||||
* @param mobile 手机号
|
||||
* @param apiTemplateId 短信 API 的模板编号
|
||||
* @param templateParams 短信模板参数。通过 List 数组,保证参数的顺序
|
||||
* @return 腾讯云发送短信请求
|
||||
*/
|
||||
private SendSmsRequest buildSendSmsRequest(Long sendLogId,
|
||||
String mobile,
|
||||
String apiTemplateId,
|
||||
List<KeyValue<String, Object>> templateParams) {
|
||||
SendSmsRequest request = new SendSmsRequest();
|
||||
request.setSmsSdkAppId(((TencentSmsChannelProperties) properties).getSdkAppId());
|
||||
request.setPhoneNumberSet(new String[]{mobile});
|
||||
request.setSignName(properties.getSignature());
|
||||
request.setTemplateId(apiTemplateId);
|
||||
request.setTemplateParamSet(ArrayUtils.toArray(templateParams, e -> String.valueOf(e.getValue())));
|
||||
request.setSessionContext(JsonUtils.toJsonString(new SessionContext().setLogId(sendLogId)));
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<SmsReceiveRespDTO> doParseSmsReceiveStatus(String text) throws Throwable {
|
||||
List<SmsReceiveStatus> callback = JsonUtils.parseArray(text, SmsReceiveStatus.class);
|
||||
return CollectionUtils.convertList(callback, status -> {
|
||||
SmsReceiveRespDTO data = new SmsReceiveRespDTO();
|
||||
data.setErrorCode(status.getErrCode()).setErrorMsg(status.getDescription());
|
||||
data.setReceiveTime(status.getReceiveTime()).setSuccess(SmsReceiveStatus.SUCCESS_CODE.equalsIgnoreCase(status.getStatus()));
|
||||
data.setMobile(status.getMobile()).setSerialNo(status.getSerialNo());
|
||||
SessionContext context;
|
||||
Long logId;
|
||||
Assert.notNull(context = status.getSessionContext(), "回执信息中未解析出 context,请联系腾讯云小助手");
|
||||
Assert.notNull(logId = context.getLogId(), "回执信息中未解析出 logId,请联系腾讯云小助手");
|
||||
data.setLogId(logId);
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SmsCommonResult<SmsTemplateRespDTO> doGetSmsTemplate(String apiTemplateId) throws Throwable {
|
||||
return invoke(() -> this.buildSmsTemplateStatusRequest(apiTemplateId),
|
||||
this::doGetSmsTemplate0,
|
||||
response -> {
|
||||
SmsTemplateRespDTO data = convertTemplateStatusDTO(response.getDescribeTemplateStatusSet()[0]);
|
||||
return SmsCommonResult.build(API_SUCCESS_CODE, null, response.getRequestId(), data, codeMapping);
|
||||
});
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
SmsTemplateRespDTO convertTemplateStatusDTO(DescribeTemplateListStatus templateStatus) {
|
||||
if (templateStatus == null) {
|
||||
return null;
|
||||
}
|
||||
SmsTemplateAuditStatusEnum auditStatus;
|
||||
Assert.notNull(templateStatus.getStatusCode(),
|
||||
StrUtil.format("短信模版审核状态为 null,模版 id{}", templateStatus.getTemplateId()));
|
||||
switch (templateStatus.getStatusCode().intValue()) {
|
||||
case -1:
|
||||
auditStatus = SmsTemplateAuditStatusEnum.FAIL;
|
||||
break;
|
||||
case 0:
|
||||
auditStatus = SmsTemplateAuditStatusEnum.SUCCESS;
|
||||
break;
|
||||
case 1:
|
||||
auditStatus = SmsTemplateAuditStatusEnum.CHECKING;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException(StrUtil.format("不能解析短信模版审核状态{},模版 id{}",
|
||||
templateStatus.getStatusCode(), templateStatus.getTemplateId()));
|
||||
}
|
||||
SmsTemplateRespDTO data = new SmsTemplateRespDTO();
|
||||
data.setId(String.valueOf(templateStatus.getTemplateId())).setContent(templateStatus.getTemplateContent());
|
||||
data.setAuditStatus(auditStatus.getStatus()).setAuditReason(templateStatus.getReviewReply());
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装查询模版审核状态请求
|
||||
* @param apiTemplateId api 的模版 id
|
||||
* @return 查询模版审核状态请求
|
||||
*/
|
||||
private DescribeSmsTemplateListRequest buildSmsTemplateStatusRequest(String apiTemplateId) {
|
||||
DescribeSmsTemplateListRequest request = new DescribeSmsTemplateListRequest();
|
||||
request.setTemplateIdSet(new Long[]{Long.parseLong(apiTemplateId)});
|
||||
// 地区 0:表示国内短信。1:表示国际/港澳台短信。
|
||||
request.setInternational(INTERNATIONAL);
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用腾讯云 SDK 查询短信模版状态
|
||||
*
|
||||
* @param request 查询短信模版状态请求
|
||||
* @return 查询短信模版状态响应
|
||||
* @throws TencentCloudSDKException SDK 用来封装查询短信模版状态失败
|
||||
*/
|
||||
private DescribeSmsTemplateListResponse doGetSmsTemplate0(DescribeSmsTemplateListRequest request) throws TencentCloudSDKException {
|
||||
return client.DescribeSmsTemplateList(request);
|
||||
}
|
||||
|
||||
<Q, P, R> SmsCommonResult<R> invoke(Supplier<Q> requestSupplier,
|
||||
SdkFunction<Q, P> responseSupplier,
|
||||
Function<P, SmsCommonResult<R>> resultGen) {
|
||||
// 构建请求body
|
||||
Q request = requestSupplier.get();
|
||||
P response;
|
||||
// 调用腾讯云发送短信
|
||||
try {
|
||||
response = responseSupplier.apply(request);
|
||||
} catch (TencentCloudSDKException e) {
|
||||
// 调用异常,封装结果
|
||||
return SmsCommonResult.build(e.getErrorCode(), e.getMessage(), e.getRequestId(), null, codeMapping);
|
||||
}
|
||||
return resultGen.apply(response);
|
||||
}
|
||||
|
||||
@Data
|
||||
private static class SmsReceiveStatus {
|
||||
|
||||
/**
|
||||
* 短信接受成功 code
|
||||
*/
|
||||
public static final String SUCCESS_CODE = "SUCCESS";
|
||||
|
||||
/**
|
||||
* 用户实际接收到短信的时间
|
||||
*/
|
||||
@JsonProperty("user_receive_time")
|
||||
@JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)
|
||||
private Date receiveTime;
|
||||
|
||||
/**
|
||||
* 国家(或地区)码
|
||||
*/
|
||||
@JsonProperty("nationcode")
|
||||
private String nationCode;
|
||||
|
||||
/**
|
||||
* 手机号码
|
||||
*/
|
||||
private String mobile;
|
||||
|
||||
/**
|
||||
* 实际是否收到短信接收状态,SUCCESS(成功)、FAIL(失败)
|
||||
*/
|
||||
@JsonProperty("report_status")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 用户接收短信状态码错误信息
|
||||
*/
|
||||
@JsonProperty("errmsg")
|
||||
private String errCode;
|
||||
|
||||
/**
|
||||
* 用户接收短信状态描述
|
||||
*/
|
||||
@JsonProperty("description")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 本次发送标识 ID(与发送接口返回的SerialNo对应)
|
||||
*/
|
||||
@JsonProperty("sid")
|
||||
private String serialNo;
|
||||
|
||||
/**
|
||||
* 用户的 session 内容(与发送接口的请求参数SessionContext一致)
|
||||
*/
|
||||
@JsonProperty("ext")
|
||||
private SessionContext sessionContext;
|
||||
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@Data
|
||||
static class SessionContext {
|
||||
|
||||
/**
|
||||
* 发送短信记录id
|
||||
*/
|
||||
private Long logId;
|
||||
}
|
||||
|
||||
private interface SdkFunction<T, R> {
|
||||
R apply(T t) throws TencentCloudSDKException;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package cn.iocoder.yudao.framework.sms.core.client.impl.tencent;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsCodeMapping;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
|
||||
|
||||
import static cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 腾讯云的 SmsCodeMapping 实现类
|
||||
*
|
||||
* 参见 https://cloud.tencent.com/document/api/382/52075#.E5.85.AC.E5.85.B1.E9.94.99.E8.AF.AF.E7.A0.81
|
||||
*
|
||||
* @author : shiwp
|
||||
*/
|
||||
public class TencentSmsCodeMapping implements SmsCodeMapping {
|
||||
|
||||
@Override
|
||||
public ErrorCode apply(String apiCode) {
|
||||
switch (apiCode) {
|
||||
case TencentSmsClient.API_SUCCESS_CODE: return GlobalErrorCodeConstants.SUCCESS;
|
||||
case "FailedOperation.ContainSensitiveWord": return SMS_SEND_CONTENT_INVALID;
|
||||
case "FailedOperation.JsonParseFail":
|
||||
case "MissingParameter.EmptyPhoneNumberSet":
|
||||
case "LimitExceeded.PhoneNumberCountLimit":
|
||||
case "FailedOperation.FailResolvePacket": return GlobalErrorCodeConstants.BAD_REQUEST;
|
||||
case "FailedOperation.InsufficientBalanceInSmsPackage": return SMS_ACCOUNT_MONEY_NOT_ENOUGH;
|
||||
case "FailedOperation.MarketingSendTimeConstraint": return SMS_SEND_MARKET_LIMIT_CONTROL;
|
||||
case "FailedOperation.PhoneNumberInBlacklist": return SMS_MOBILE_BLACK;
|
||||
case "FailedOperation.SignatureIncorrectOrUnapproved": return SMS_SIGN_INVALID;
|
||||
case "FailedOperation.MissingTemplateToModify":
|
||||
case "FailedOperation.TemplateIncorrectOrUnapproved": return SMS_TEMPLATE_INVALID;
|
||||
case "InvalidParameterValue.IncorrectPhoneNumber": return SMS_MOBILE_INVALID;
|
||||
case "InvalidParameterValue.SdkAppIdNotExist": return SMS_APP_ID_INVALID;
|
||||
case "InvalidParameterValue.TemplateParameterLengthLimit":
|
||||
case "InvalidParameterValue.TemplateParameterFormatError": return SMS_TEMPLATE_PARAM_ERROR;
|
||||
case "LimitExceeded.PhoneNumberDailyLimit": return SMS_SEND_DAY_LIMIT_CONTROL;
|
||||
case "LimitExceeded.PhoneNumberThirtySecondLimit":
|
||||
case "LimitExceeded.PhoneNumberOneHourLimit": return SMS_SEND_BUSINESS_LIMIT_CONTROL;
|
||||
case "UnauthorizedOperation.RequestPermissionDeny":
|
||||
case "FailedOperation.ForbidAddMarketingTemplates":
|
||||
case "FailedOperation.NotEnterpriseCertification":
|
||||
case "UnauthorizedOperation.IndividualUserMarketingSmsPermissionDeny": return SMS_PERMISSION_DENY;
|
||||
case "UnauthorizedOperation.RequestIpNotInWhitelist": return SMS_IP_DENY;
|
||||
case "AuthFailure.SecretIdNotFound": return SMS_ACCOUNT_INVALID;
|
||||
}
|
||||
return SmsFrameworkErrorCodeConstants.SMS_UNKNOWN;
|
||||
}
|
||||
}
|
|
@ -35,7 +35,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DE
|
|||
* 云片短信客户端的实现类
|
||||
*
|
||||
* @author zzf
|
||||
* @date 9:48 2021/3/5
|
||||
* @since 9:48 2021/3/5
|
||||
*/
|
||||
@Slf4j
|
||||
public class YunpianSmsClient extends AbstractSmsClient {
|
||||
|
|
|
@ -8,7 +8,7 @@ import lombok.Getter;
|
|||
* 短信渠道枚举
|
||||
*
|
||||
* @author zzf
|
||||
* @date 2021/1/25 10:56
|
||||
* @since 2021/1/25 10:56
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
|
@ -17,7 +17,7 @@ public enum SmsChannelEnum {
|
|||
DEBUG_DING_TALK("DEBUG_DING_TALK", "调试(钉钉)"),
|
||||
YUN_PIAN("YUN_PIAN", "云片"),
|
||||
ALIYUN("ALIYUN", "阿里云"),
|
||||
// TENCENT("TENCENT", "腾讯云"),
|
||||
TENCENT("TENCENT", "腾讯云"),
|
||||
// HUA_WEI("HUA_WEI", "华为云"),
|
||||
;
|
||||
|
||||
|
|
|
@ -26,6 +26,9 @@ public interface SmsFrameworkErrorCodeConstants {
|
|||
|
||||
ErrorCode SMS_SEND_CONTENT_INVALID = new ErrorCode(2001000104, "短信内容有敏感词");
|
||||
|
||||
// 腾讯云:为避免骚扰用户,营销短信只允许在8点到22点发送。
|
||||
ErrorCode SMS_SEND_MARKET_LIMIT_CONTROL = new ErrorCode(2001000105, "营销短信发送时间限制");
|
||||
|
||||
// ========== 模板相关 2001000200 ==========
|
||||
ErrorCode SMS_TEMPLATE_INVALID = new ErrorCode(2001000200, "短信模板不合法"); // 包括短信模板不存在
|
||||
ErrorCode SMS_TEMPLATE_PARAM_ERROR = new ErrorCode(2001000201, "模板参数不正确");
|
||||
|
@ -41,6 +44,7 @@ public interface SmsFrameworkErrorCodeConstants {
|
|||
ErrorCode SMS_API_PARAM_ERROR = new ErrorCode(2001000900, "请求参数缺失");
|
||||
ErrorCode SMS_MOBILE_INVALID = new ErrorCode(2001000901, "手机格式不正确");
|
||||
ErrorCode SMS_MOBILE_BLACK = new ErrorCode(2001000902, "手机号在黑名单中");
|
||||
ErrorCode SMS_APP_ID_INVALID = new ErrorCode(2001000903, "SdkAppId不合法");
|
||||
|
||||
ErrorCode EXCEPTION = new ErrorCode(2001000999, "调用异常");
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import javax.validation.constraints.NotNull;
|
|||
* 短信渠道配置类
|
||||
*
|
||||
* @author zzf
|
||||
* @date 2021/1/25 17:01
|
||||
* @since 2021/1/25 17:01
|
||||
*/
|
||||
@Data
|
||||
@Validated
|
||||
|
@ -40,9 +40,9 @@ public class SmsChannelProperties {
|
|||
@NotEmpty(message = "短信 API 的账号不能为空")
|
||||
private String apiKey;
|
||||
/**
|
||||
* 短信 API 的秘钥
|
||||
* 短信 API 的密钥
|
||||
*/
|
||||
@NotEmpty(message = "短信 API 的秘钥不能为空")
|
||||
@NotEmpty(message = "短信 API 的密钥不能为空")
|
||||
private String apiSecret;
|
||||
/**
|
||||
* 短信发送回调 URL
|
||||
|
|
|
@ -0,0 +1,222 @@
|
|||
package cn.iocoder.yudao.framework.sms.core.client.impl.tencent;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsReceiveRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsSendRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.dto.SmsTemplateRespDTO;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsTemplateAuditStatusEnum;
|
||||
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.tencentcloudapi.sms.v20210111.SmsClient;
|
||||
import com.tencentcloudapi.sms.v20210111.models.DescribeSmsTemplateListResponse;
|
||||
import com.tencentcloudapi.sms.v20210111.models.DescribeTemplateListStatus;
|
||||
import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;
|
||||
import com.tencentcloudapi.sms.v20210111.models.SendStatus;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* {@link TencentSmsClient} 的单元测试
|
||||
*
|
||||
* @author shiwp
|
||||
*/
|
||||
public class TencentSmsClientTest extends BaseMockitoUnitTest {
|
||||
|
||||
private final SmsChannelProperties properties = new SmsChannelProperties()
|
||||
.setApiKey(randomString() + " " + randomString()) // 随机一个 apiKey,避免构建报错
|
||||
.setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错
|
||||
.setSignature("芋道源码");
|
||||
|
||||
@InjectMocks
|
||||
private TencentSmsClient smsClient = new TencentSmsClient(properties);
|
||||
|
||||
@Mock
|
||||
private SmsClient client;
|
||||
|
||||
@Test
|
||||
public void testDoInit() {
|
||||
// 准备参数
|
||||
// mock 方法
|
||||
|
||||
// 调用
|
||||
smsClient.doInit();
|
||||
// 断言
|
||||
assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "client"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRefresh() {
|
||||
// 准备参数
|
||||
SmsChannelProperties p = new SmsChannelProperties()
|
||||
.setApiKey(randomString() + " " + randomString()) // 随机一个 apiKey,避免构建报错
|
||||
.setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错
|
||||
.setSignature("芋道源码");
|
||||
// 调用
|
||||
smsClient.refresh(p);
|
||||
// 断言
|
||||
assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "client"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoSendSms() throws Throwable {
|
||||
// 准备参数
|
||||
Long sendLogId = randomLongId();
|
||||
String mobile = randomString();
|
||||
String apiTemplateId = randomString();
|
||||
List<KeyValue<String, Object>> templateParams = Lists.newArrayList(
|
||||
new KeyValue<>("1", 1234), new KeyValue<>("2", "login"));
|
||||
String requestId = randomString();
|
||||
String serialNo = randomString();
|
||||
// mock 方法
|
||||
SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> {
|
||||
o.setRequestId(requestId);
|
||||
SendStatus[] sendStatuses = new SendStatus[1];
|
||||
o.setSendStatusSet(sendStatuses);
|
||||
SendStatus sendStatus = new SendStatus();
|
||||
sendStatuses[0] = sendStatus;
|
||||
sendStatus.setCode(TencentSmsClient.API_SUCCESS_CODE);
|
||||
sendStatus.setMessage("send success");
|
||||
sendStatus.setSerialNo(serialNo);
|
||||
});
|
||||
when(client.SendSms(argThat(request -> {
|
||||
assertEquals(mobile, request.getPhoneNumberSet()[0]);
|
||||
assertEquals(properties.getSignature(), request.getSignName());
|
||||
assertEquals(apiTemplateId, request.getTemplateId());
|
||||
assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)),
|
||||
toJsonString(request.getTemplateParamSet()));
|
||||
assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), "logId"));
|
||||
return true;
|
||||
}))).thenReturn(response);
|
||||
|
||||
// 调用
|
||||
SmsCommonResult<SmsSendRespDTO> result = smsClient.doSendSms(sendLogId, mobile,
|
||||
apiTemplateId, templateParams);
|
||||
// 断言
|
||||
assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode());
|
||||
assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg());
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
|
||||
assertEquals(response.getRequestId(), result.getApiRequestId());
|
||||
// 断言结果
|
||||
assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getData().getSerialNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoTParseSmsReceiveStatus() throws Throwable {
|
||||
// 准备参数
|
||||
String text = "[\n" +
|
||||
" {\n" +
|
||||
" \"user_receive_time\": \"2015-10-17 08:03:04\",\n" +
|
||||
" \"nationcode\": \"86\",\n" +
|
||||
" \"mobile\": \"13900000001\",\n" +
|
||||
" \"report_status\": \"SUCCESS\",\n" +
|
||||
" \"errmsg\": \"DELIVRD\",\n" +
|
||||
" \"description\": \"用户短信送达成功\",\n" +
|
||||
" \"sid\": \"12345\",\n" +
|
||||
" \"ext\": {\"logId\":\"67890\"}\n" +
|
||||
" }\n" +
|
||||
"]";
|
||||
// mock 方法
|
||||
|
||||
// 调用
|
||||
List<SmsReceiveRespDTO> statuses = smsClient.doParseSmsReceiveStatus(text);
|
||||
// 断言
|
||||
assertEquals(1, statuses.size());
|
||||
assertTrue(statuses.get(0).getSuccess());
|
||||
assertEquals("DELIVRD", statuses.get(0).getErrorCode());
|
||||
assertEquals("用户短信送达成功", statuses.get(0).getErrorMsg());
|
||||
assertEquals("13900000001", statuses.get(0).getMobile());
|
||||
assertEquals(DateUtils.buildTime(2015, 10, 17, 8, 3, 4), statuses.get(0).getReceiveTime());
|
||||
assertEquals("12345", statuses.get(0).getSerialNo());
|
||||
assertEquals(67890L, statuses.get(0).getLogId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoGetSmsTemplate() throws Throwable {
|
||||
// 准备参数
|
||||
Long apiTemplateId = randomLongId();
|
||||
String requestId = randomString();
|
||||
|
||||
// mock 方法
|
||||
DescribeSmsTemplateListResponse response = randomPojo(DescribeSmsTemplateListResponse.class, o -> {
|
||||
DescribeTemplateListStatus[] describeTemplateListStatuses = new DescribeTemplateListStatus[1];
|
||||
DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus();
|
||||
templateStatus.setTemplateId(apiTemplateId);
|
||||
templateStatus.setStatusCode(0L);// 设置模板通过
|
||||
describeTemplateListStatuses[0] = templateStatus;
|
||||
o.setDescribeTemplateStatusSet(describeTemplateListStatuses);
|
||||
o.setRequestId(requestId);
|
||||
});
|
||||
when(client.DescribeSmsTemplateList(argThat(request -> {
|
||||
assertEquals(apiTemplateId, request.getTemplateIdSet()[0]);
|
||||
return true;
|
||||
}))).thenReturn(response);
|
||||
|
||||
// 调用
|
||||
SmsCommonResult<SmsTemplateRespDTO> result = smsClient.doGetSmsTemplate(apiTemplateId.toString());
|
||||
// 断言
|
||||
assertEquals(TencentSmsClient.API_SUCCESS_CODE, result.getApiCode());
|
||||
assertNull(result.getApiMsg());
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
|
||||
assertEquals(response.getRequestId(), result.getApiRequestId());
|
||||
// 断言结果
|
||||
assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateId().toString(), result.getData().getId());
|
||||
assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateContent(), result.getData().getContent());
|
||||
assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getData().getAuditStatus());
|
||||
assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getData().getAuditReason());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertSuccessTemplateStatus() {
|
||||
testTemplateStatus(SmsTemplateAuditStatusEnum.SUCCESS, 0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertCheckingTemplateStatus() {
|
||||
testTemplateStatus(SmsTemplateAuditStatusEnum.CHECKING, 1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertFailTemplateStatus() {
|
||||
testTemplateStatus(SmsTemplateAuditStatusEnum.FAIL, -1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvertUnknownTemplateStatus() {
|
||||
DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus();
|
||||
templateStatus.setStatusCode(3L);
|
||||
Long templateId = randomLongId();
|
||||
// 调用,并断言结果
|
||||
assertThrows(IllegalStateException.class, () -> smsClient.convertTemplateStatusDTO(templateStatus),
|
||||
StrUtil.format("不能解析短信模版审核状态[3],模版id[{}]", templateId));
|
||||
}
|
||||
|
||||
private void testTemplateStatus(SmsTemplateAuditStatusEnum expected, Long value) {
|
||||
DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus();
|
||||
templateStatus.setStatusCode(value);
|
||||
SmsTemplateRespDTO result = smsClient.convertTemplateStatusDTO(templateStatus);
|
||||
assertEquals(expected.getStatus(), result.getAuditStatus());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package cn.iocoder.yudao.framework.sms.core.client.impl.tencent;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.sms.core.enums.SmsFrameworkErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.InjectMocks;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* {@link TencentSmsCodeMapping} 的单元测试
|
||||
*
|
||||
* @author : shiwp
|
||||
*/
|
||||
public class TencentSmsCodeMappingTest extends BaseMockitoUnitTest {
|
||||
|
||||
@InjectMocks
|
||||
private TencentSmsCodeMapping codeMapping;
|
||||
|
||||
@Test
|
||||
public void testApply() {
|
||||
assertEquals(GlobalErrorCodeConstants.SUCCESS, codeMapping.apply(TencentSmsClient.API_SUCCESS_CODE));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_CONTENT_INVALID, codeMapping.apply("FailedOperation.ContainSensitiveWord"));
|
||||
assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("FailedOperation.JsonParseFail"));
|
||||
assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("MissingParameter.EmptyPhoneNumberSet"));
|
||||
assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("LimitExceeded.PhoneNumberCountLimit"));
|
||||
assertEquals(GlobalErrorCodeConstants.BAD_REQUEST, codeMapping.apply("FailedOperation.FailResolvePacket"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_MONEY_NOT_ENOUGH, codeMapping.apply("FailedOperation.InsufficientBalanceInSmsPackage"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_MARKET_LIMIT_CONTROL, codeMapping.apply("FailedOperation.MarketingSendTimeConstraint"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_MOBILE_BLACK, codeMapping.apply("FailedOperation.PhoneNumberInBlacklist"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_SIGN_INVALID, codeMapping.apply("FailedOperation.SignatureIncorrectOrUnapproved"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_INVALID, codeMapping.apply("FailedOperation.MissingTemplateToModify"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_INVALID, codeMapping.apply("FailedOperation.TemplateIncorrectOrUnapproved"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_MOBILE_INVALID, codeMapping.apply("InvalidParameterValue.IncorrectPhoneNumber"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_APP_ID_INVALID, codeMapping.apply("InvalidParameterValue.SdkAppIdNotExist"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_PARAM_ERROR, codeMapping.apply("InvalidParameterValue.TemplateParameterLengthLimit"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_TEMPLATE_PARAM_ERROR, codeMapping.apply("InvalidParameterValue.TemplateParameterFormatError"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_DAY_LIMIT_CONTROL, codeMapping.apply("LimitExceeded.PhoneNumberDailyLimit"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_BUSINESS_LIMIT_CONTROL, codeMapping.apply("LimitExceeded.PhoneNumberThirtySecondLimit"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_SEND_BUSINESS_LIMIT_CONTROL, codeMapping.apply("LimitExceeded.PhoneNumberOneHourLimit"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_PERMISSION_DENY, codeMapping.apply("UnauthorizedOperation.RequestPermissionDeny"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_PERMISSION_DENY, codeMapping.apply("FailedOperation.ForbidAddMarketingTemplates"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_PERMISSION_DENY, codeMapping.apply("FailedOperation.NotEnterpriseCertification"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_PERMISSION_DENY, codeMapping.apply("UnauthorizedOperation.IndividualUserMarketingSmsPermissionDeny"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_IP_DENY, codeMapping.apply("UnauthorizedOperation.RequestIpNotInWhitelist"));
|
||||
assertEquals(SmsFrameworkErrorCodeConstants.SMS_ACCOUNT_INVALID, codeMapping.apply("AuthFailure.SecretIdNotFound"));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
package cn.iocoder.yudao.framework.social.config;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
|
||||
import com.xkcoding.http.HttpUtil;
|
||||
import com.xkcoding.http.support.hutool.HutoolImpl;
|
||||
import com.xkcoding.justauth.autoconfigure.JustAuthProperties;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.zhyd.oauth.cache.AuthStateCache;
|
||||
|
@ -23,6 +26,9 @@ public class YudaoSocialAutoConfiguration {
|
|||
@Bean
|
||||
@ConditionalOnProperty(prefix = "justauth", value = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public YudaoAuthRequestFactory yudaoAuthRequestFactory(JustAuthProperties properties, AuthStateCache authStateCache) {
|
||||
// 需要修改 HttpUtil 使用的实现,避免类报错
|
||||
HttpUtil.setHttp(new HutoolImpl());
|
||||
// 创建 YudaoAuthRequestFactory
|
||||
return new YudaoAuthRequestFactory(properties, authStateCache);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,29 +4,40 @@ import cn.hutool.core.collection.CollUtil;
|
|||
import cn.iocoder.yudao.framework.tenant.config.TenantProperties;
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
|
||||
import lombok.AllArgsConstructor;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.LongValue;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 基于 MyBatis Plus 多租户的功能,实现 DB 层面的多租户的功能
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class TenantDatabaseInterceptor implements TenantLineHandler {
|
||||
|
||||
private final TenantProperties properties;
|
||||
private final Set<String> ignoreTables = new HashSet<>();
|
||||
|
||||
public TenantDatabaseInterceptor(TenantProperties properties) {
|
||||
// 不同 DB 下,大小写的习惯不同,所以需要都添加进去
|
||||
properties.getIgnoreTables().forEach(table -> {
|
||||
ignoreTables.add(table.toLowerCase());
|
||||
ignoreTables.add(table.toUpperCase());
|
||||
});
|
||||
// 在 OracleKeyGenerator 中,生成主键时,会查询这个表,查询这个表后,会自动拼接 TENANT_ID 导致报错
|
||||
ignoreTables.add("DUAL");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression getTenantId() {
|
||||
return new LongValue( TenantContextHolder.getRequiredTenantId());
|
||||
return new LongValue(TenantContextHolder.getRequiredTenantId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ignoreTable(String tableName) {
|
||||
return TenantContextHolder.isIgnore() // 情况一,全局忽略多租户
|
||||
|| CollUtil.contains(properties.getIgnoreTables(), tableName); // 情况二,忽略多租户的表
|
||||
|| CollUtil.contains(ignoreTables, tableName); // 情况二,忽略多租户的表
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ public class TenantSecurityWebFilter extends ApiRequestFilter {
|
|||
}
|
||||
}
|
||||
|
||||
//检查是否是忽略的 URL, 如果是则允许访问
|
||||
// 如果非允许忽略租户的 URL,则校验租户是否合法
|
||||
if (!isIgnoreUrl(request)) {
|
||||
// 2. 如果请求未带租户的编号,不允许访问。
|
||||
if (tenantId == null) {
|
||||
|
@ -92,6 +92,10 @@ public class TenantSecurityWebFilter extends ApiRequestFilter {
|
|||
ServletUtils.writeJSON(response, result);
|
||||
return;
|
||||
}
|
||||
} else { // 如果是允许忽略租户的 URL,若未传递租户编号,则默认忽略租户编号,避免报错
|
||||
if (tenantId == null) {
|
||||
TenantContextHolder.setIgnore(true);
|
||||
}
|
||||
}
|
||||
|
||||
// 继续过滤
|
||||
|
|
|
@ -15,12 +15,12 @@ import java.util.List;
|
|||
public interface ConfigFrameworkDAO {
|
||||
|
||||
/**
|
||||
* 查询是否存在比 maxUpdateTime 更新记录更晚的配置
|
||||
* 查询是否存在比 maxUpdateTime 的更新记录数量
|
||||
*
|
||||
* @param maxUpdateTime 最大更新时间
|
||||
* @return 是否存在
|
||||
*/
|
||||
boolean selectExistsByUpdateTimeAfter(Date maxUpdateTime);
|
||||
int selectCountByUpdateTimeGt(Date maxUpdateTime);
|
||||
|
||||
/**
|
||||
* 查询配置列表
|
||||
|
|
|
@ -24,7 +24,6 @@ import java.util.List;
|
|||
import java.util.Properties;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@Slf4j
|
||||
public class DBConfigRepository extends AbstractConfigRepository {
|
||||
|
@ -172,7 +171,7 @@ public class DBConfigRepository extends AbstractConfigRepository {
|
|||
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
|
||||
log.info("[loadConfigIfUpdate][首次加载全量配置]");
|
||||
} else { // 判断数据库中是否有更新的配置
|
||||
if (!configFrameworkDAO.selectExistsByUpdateTimeAfter(maxUpdateTime)) {
|
||||
if (configFrameworkDAO.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
return null;
|
||||
}
|
||||
log.info("[loadConfigIfUpdate][增量加载全量配置]");
|
||||
|
|
|
@ -63,8 +63,8 @@
|
|||
|
||||
<!-- 三方云服务相关 -->
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3</artifactId>
|
||||
<groupId>io.minio</groupId>
|
||||
<artifactId>minio</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
|
|
|
@ -20,15 +20,17 @@ public interface FileClient {
|
|||
* @param content 文件流
|
||||
* @param path 相对路径
|
||||
* @return 完整路径,即 HTTP 访问地址
|
||||
* @throws Exception 上传文件时,抛出 Exception 异常
|
||||
*/
|
||||
String upload(byte[] content, String path);
|
||||
String upload(byte[] content, String path) throws Exception;
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*
|
||||
* @param path 相对路径
|
||||
* @throws Exception 删除文件时,抛出 Exception 异常
|
||||
*/
|
||||
void delete(String path);
|
||||
void delete(String path) throws Exception;
|
||||
|
||||
/**
|
||||
* 获得文件的内容
|
||||
|
@ -36,6 +38,6 @@ public interface FileClient {
|
|||
* @param path 相对路径
|
||||
* @return 文件的内容
|
||||
*/
|
||||
byte[] getContent(String path);
|
||||
byte[] getContent(String path) throws Exception;
|
||||
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> {
|
|||
String dir = StrUtil.removeSuffix(filePath, fileName);
|
||||
boolean success = ftp.upload(dir, fileName, new ByteArrayInputStream(content));
|
||||
if (!success) {
|
||||
throw new FtpException(StrUtil.format("上海文件到目标目录 ({}) 失败", filePath));
|
||||
throw new FtpException(StrUtil.format("上传文件到目标目录 ({}) 失败", filePath));
|
||||
}
|
||||
// 拼接返回路径
|
||||
return super.formatFileUrl(config.getDomain(), path);
|
||||
|
|
|
@ -1,19 +1,14 @@
|
|||
package cn.iocoder.yudao.framework.file.core.client.s3;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.iocoder.yudao.framework.file.core.client.AbstractFileClient;
|
||||
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
|
||||
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
|
||||
import software.amazon.awssdk.core.sync.RequestBody;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
import software.amazon.awssdk.services.s3.S3Client;
|
||||
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
|
||||
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
|
||||
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
|
||||
import io.minio.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_QINIU;
|
||||
import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_ALIYUN;
|
||||
|
||||
/**
|
||||
* 基于 S3 协议的文件客户端,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务
|
||||
|
@ -24,7 +19,7 @@ import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.
|
|||
*/
|
||||
public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
|
||||
|
||||
private S3Client client;
|
||||
private MinioClient client;
|
||||
|
||||
public S3FileClient(Long id, S3FileClientConfig config) {
|
||||
super(id, config);
|
||||
|
@ -34,34 +29,27 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
|
|||
protected void doInit() {
|
||||
// 补全 domain
|
||||
if (StrUtil.isEmpty(config.getDomain())) {
|
||||
config.setDomain(createDomain());
|
||||
config.setDomain(buildDomain());
|
||||
}
|
||||
// 初始化客户端
|
||||
client = S3Client.builder()
|
||||
.serviceConfiguration(sb -> sb.pathStyleAccessEnabled(false) // 关闭路径风格
|
||||
.chunkedEncodingEnabled(false)) // 禁用 chunk
|
||||
.endpointOverride(createURI()) // 上传地址
|
||||
.region(Region.of(config.getRegion())) // Region
|
||||
.credentialsProvider(StaticCredentialsProvider.create( // 认证密钥
|
||||
AwsBasicCredentials.create(config.getAccessKey(), config.getAccessSecret())))
|
||||
.overrideConfiguration(cb -> cb.addExecutionInterceptor(new S3ModifyPathInterceptor(config.getBucket())))
|
||||
client = MinioClient.builder()
|
||||
.endpoint(buildEndpointURL()) // Endpoint URL
|
||||
.region(buildRegion()) // Region
|
||||
.credentials(config.getAccessKey(), config.getAccessSecret()) // 认证密钥
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于 endpoint 构建调用云服务的 URI 地址
|
||||
* 基于 endpoint 构建调用云服务的 URL 地址
|
||||
*
|
||||
* @return URI 地址
|
||||
*/
|
||||
private URI createURI() {
|
||||
String uri;
|
||||
// 如果是七牛,无需拼接 bucket
|
||||
if (config.getEndpoint().contains(ENDPOINT_QINIU)) {
|
||||
uri = StrUtil.format("https://{}", config.getEndpoint());
|
||||
} else {
|
||||
uri = StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint());
|
||||
private String buildEndpointURL() {
|
||||
// 如果已经是 http 或者 https,则不进行拼接.主要适配 MinIO
|
||||
if (HttpUtil.isHttp(config.getEndpoint()) || HttpUtil.isHttps(config.getEndpoint())) {
|
||||
return config.getEndpoint();
|
||||
}
|
||||
return URI.create(uri);
|
||||
return StrUtil.format("https://{}", config.getEndpoint());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -69,35 +57,56 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
|
|||
*
|
||||
* @return Domain 地址
|
||||
*/
|
||||
private String createDomain() {
|
||||
private String buildDomain() {
|
||||
// 如果已经是 http 或者 https,则不进行拼接.主要适配 MinIO
|
||||
if (HttpUtil.isHttp(config.getEndpoint()) || HttpUtil.isHttps(config.getEndpoint())) {
|
||||
return StrUtil.format("{}/{}", config.getEndpoint(), config.getBucket());
|
||||
}
|
||||
// 阿里云、腾讯云、华为云都适合。七牛云比较特殊,必须有自定义域名
|
||||
return StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint());
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于 bucket 构建 region 地区
|
||||
*
|
||||
* @return region 地区
|
||||
*/
|
||||
private String buildRegion() {
|
||||
// 阿里云必须有 region,否则会报错
|
||||
if (config.getEndpoint().contains(ENDPOINT_ALIYUN)) {
|
||||
return StrUtil.subBefore(config.getEndpoint(), '.', false)
|
||||
.replaceAll("-internal", ""); // 去除内网 Endpoint 的后缀
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String upload(byte[] content, String path) {
|
||||
public String upload(byte[] content, String path) throws Exception {
|
||||
// 执行上传
|
||||
PutObjectRequest.Builder request = PutObjectRequest.builder()
|
||||
client.putObject(PutObjectArgs.builder()
|
||||
.bucket(config.getBucket()) // bucket 必须传递
|
||||
.key(path); // 相对路径作为 key
|
||||
client.putObject(request.build(), RequestBody.fromBytes(content));
|
||||
.object(path) // 相对路径作为 key
|
||||
.stream(new ByteArrayInputStream(content), content.length, -1) // 文件内容
|
||||
.build());
|
||||
// 拼接返回路径
|
||||
return config.getDomain() + "/" + path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String path) {
|
||||
DeleteObjectRequest.Builder request = DeleteObjectRequest.builder()
|
||||
public void delete(String path) throws Exception {
|
||||
client.removeObject(RemoveObjectArgs.builder()
|
||||
.bucket(config.getBucket()) // bucket 必须传递
|
||||
.key(path); // 相对路径作为 key
|
||||
client.deleteObject(request.build());
|
||||
.object(path) // 相对路径作为 key
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getContent(String path) {
|
||||
GetObjectRequest.Builder request = GetObjectRequest.builder()
|
||||
public byte[] getContent(String path) throws Exception {
|
||||
GetObjectResponse response = client.getObject(GetObjectArgs.builder()
|
||||
.bucket(config.getBucket()) // bucket 必须传递
|
||||
.key(path); // 相对路径作为 key
|
||||
return client.getObjectAsBytes(request.build()).asByteArray();
|
||||
.object(path) // 相对路径作为 key
|
||||
.build());
|
||||
return IoUtil.readBytes(response);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,37 +18,28 @@ import javax.validation.constraints.NotNull;
|
|||
public class S3FileClientConfig implements FileClientConfig {
|
||||
|
||||
public static final String ENDPOINT_QINIU = "qiniucs.com";
|
||||
public static final String ENDPOINT_ALIYUN = "aliyuncs.com";
|
||||
|
||||
/**
|
||||
* 节点地址
|
||||
* 1. MinIO:
|
||||
* 1. MinIO:https://www.iocoder.cn/Spring-Boot/MinIO 。例如说,http://127.0.0.1:9000
|
||||
* 2. 阿里云:https://help.aliyun.com/document_detail/31837.html
|
||||
* 3. 腾讯云:
|
||||
* 3. 腾讯云:https://cloud.tencent.com/document/product/436/6224
|
||||
* 4. 七牛云:https://developer.qiniu.com/kodo/4088/s3-access-domainname
|
||||
* 5. 华为云:
|
||||
* 5. 华为云:https://developer.huaweicloud.com/endpoint?OBS
|
||||
*/
|
||||
@NotNull(message = "endpoint 不能为空")
|
||||
private String endpoint;
|
||||
/**
|
||||
* 自定义域名
|
||||
* 1. MinIO:
|
||||
* 1. MinIO:通过 Nginx 配置
|
||||
* 2. 阿里云:https://help.aliyun.com/document_detail/31836.html
|
||||
* 3. 腾讯云:https://cloud.tencent.com/document/product/436/11142
|
||||
* 4. 七牛云:https://developer.qiniu.com/kodo/8556/set-the-custom-source-domain-name
|
||||
* 5. 华为云:
|
||||
* 5. 华为云:https://support.huaweicloud.com/usermanual-obs/obs_03_0032.html
|
||||
*/
|
||||
@URL(message = "domain 必须是 URL 格式")
|
||||
private String domain;
|
||||
/**
|
||||
* 区域
|
||||
* 1. MinIO:
|
||||
* 2. 阿里云:https://help.aliyun.com/document_detail/31837.html
|
||||
* 3. 腾讯云:
|
||||
* 4. 七牛云:https://developer.qiniu.com/kodo/4088/s3-access-domainname
|
||||
* 5. 华为云:
|
||||
*/
|
||||
@NotNull(message = "region 不能为空")
|
||||
private String region;
|
||||
/**
|
||||
* 存储 Bucket
|
||||
*/
|
||||
|
@ -57,11 +48,11 @@ public class S3FileClientConfig implements FileClientConfig {
|
|||
|
||||
/**
|
||||
* 访问 Key
|
||||
* 1. MinIO:
|
||||
* 2. 阿里云:
|
||||
* 1. MinIO:https://www.iocoder.cn/Spring-Boot/MinIO
|
||||
* 2. 阿里云:https://ram.console.aliyun.com/manage/ak
|
||||
* 3. 腾讯云:https://console.cloud.tencent.com/cam/capi
|
||||
* 4. 七牛云:https://portal.qiniu.com/user/key
|
||||
* 5. 华为云:
|
||||
* 5. 华为云:https://support.huaweicloud.com/qs-obs/obs_qs_0005.html
|
||||
*/
|
||||
@NotNull(message = "accessKey 不能为空")
|
||||
private String accessKey;
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
package cn.iocoder.yudao.framework.file.core.client.s3;
|
||||
|
||||
import software.amazon.awssdk.core.interceptor.Context;
|
||||
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
|
||||
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
|
||||
import software.amazon.awssdk.http.SdkHttpRequest;
|
||||
|
||||
/**
|
||||
* S3 修改路径的拦截器,移除多余的 Bucket 前缀。
|
||||
* 如果不使用该拦截器,希望上传的路径是 /tudou.jpg 时,会被添加成 /bucket/tudou.jpg
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class S3ModifyPathInterceptor implements ExecutionInterceptor {
|
||||
|
||||
private final String bucket;
|
||||
|
||||
public S3ModifyPathInterceptor(String bucket) {
|
||||
this.bucket = "/" + bucket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {
|
||||
SdkHttpRequest request = context.httpRequest();
|
||||
SdkHttpRequest.Builder rb = SdkHttpRequest.builder().protocol(request.protocol()).host(request.host()).port(request.port())
|
||||
.method(request.method()).headers(request.headers()).rawQueryParameters(request.rawQueryParameters());
|
||||
// 移除 path 前的 bucket 路径
|
||||
if (request.encodedPath().startsWith(bucket)) {
|
||||
rb.encodedPath(request.encodedPath().substring(bucket.length()));
|
||||
} else {
|
||||
rb.encodedPath(request.encodedPath());
|
||||
}
|
||||
return rb.build();
|
||||
}
|
||||
|
||||
}
|
|
@ -3,11 +3,13 @@ package cn.iocoder.yudao.framework.file.core.client.ftp;
|
|||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.extra.ftp.FtpMode;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class FtpFileClientTest {
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void test() {
|
||||
// 创建客户端
|
||||
FtpFileClientConfig config = new FtpFileClientConfig();
|
||||
|
|
|
@ -2,11 +2,13 @@ package cn.iocoder.yudao.framework.file.core.client.local;
|
|||
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class LocalFileClientTest {
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void test() {
|
||||
// 创建客户端
|
||||
LocalFileClientConfig config = new LocalFileClientConfig();
|
||||
|
|
|
@ -2,7 +2,6 @@ package cn.iocoder.yudao.framework.file.core.client.s3;
|
|||
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -11,9 +10,25 @@ import javax.validation.Validation;
|
|||
|
||||
public class S3FileClientTest {
|
||||
|
||||
@Test
|
||||
@Disabled // MinIO,如果要集成测试,可以注释本行
|
||||
public void testMinIO() throws Exception {
|
||||
S3FileClientConfig config = new S3FileClientConfig();
|
||||
// 配置成你自己的
|
||||
config.setAccessKey("admin");
|
||||
config.setAccessSecret("password");
|
||||
config.setBucket("yudaoyuanma");
|
||||
config.setDomain(null);
|
||||
// 默认 9000 endpoint
|
||||
config.setEndpoint("http://127.0.0.1:9000");
|
||||
|
||||
// 执行上传
|
||||
testExecuteUpload(config);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled // 阿里云 OSS,如果要集成测试,可以注释本行
|
||||
public void testAliyun() {
|
||||
public void testAliyun() throws Exception {
|
||||
S3FileClientConfig config = new S3FileClientConfig();
|
||||
// 配置成你自己的
|
||||
config.setAccessKey(System.getenv("ALIYUN_ACCESS_KEY"));
|
||||
|
@ -29,7 +44,7 @@ public class S3FileClientTest {
|
|||
|
||||
@Test
|
||||
@Disabled // 腾讯云 COS,如果要集成测试,可以注释本行
|
||||
public void testQCloud() {
|
||||
public void testQCloud() throws Exception {
|
||||
S3FileClientConfig config = new S3FileClientConfig();
|
||||
// 配置成你自己的
|
||||
config.setAccessKey(System.getenv("QCLOUD_ACCESS_KEY"));
|
||||
|
@ -38,7 +53,6 @@ public class S3FileClientTest {
|
|||
config.setDomain(null); // 如果有自定义域名,则可以设置。http://tengxun-oss.iocoder.cn
|
||||
// 默认上海的 endpoint
|
||||
config.setEndpoint("cos.ap-shanghai.myqcloud.com");
|
||||
config.setRegion("ap-shanghai");
|
||||
|
||||
// 执行上传
|
||||
testExecuteUpload(config);
|
||||
|
@ -46,7 +60,7 @@ public class S3FileClientTest {
|
|||
|
||||
@Test
|
||||
@Disabled // 七牛云存储,如果要集成测试,可以注释本行
|
||||
public void testQiniu() {
|
||||
public void testQiniu() throws Exception {
|
||||
S3FileClientConfig config = new S3FileClientConfig();
|
||||
// 配置成你自己的
|
||||
// config.setAccessKey(System.getenv("QINIU_ACCESS_KEY"));
|
||||
|
@ -62,11 +76,24 @@ public class S3FileClientTest {
|
|||
testExecuteUpload(config);
|
||||
}
|
||||
|
||||
private void testExecuteUpload(S3FileClientConfig config) {
|
||||
// 补全配置
|
||||
if (config.getRegion() == null) {
|
||||
config.setRegion(StrUtil.subBefore(config.getEndpoint(), '.', false));
|
||||
}
|
||||
@Test
|
||||
@Disabled // 华为云存储,如果要集成测试,可以注释本行
|
||||
public void testHuaweiCloud() throws Exception {
|
||||
S3FileClientConfig config = new S3FileClientConfig();
|
||||
// 配置成你自己的
|
||||
// config.setAccessKey(System.getenv("HUAWEI_CLOUD_ACCESS_KEY"));
|
||||
// config.setAccessSecret(System.getenv("HUAWEI_CLOUD_SECRET_KEY"));
|
||||
config.setBucket("yudao");
|
||||
config.setDomain(null); // 如果有自定义域名,则可以设置。
|
||||
// 默认上海的 endpoint
|
||||
config.setEndpoint("obs.cn-east-3.myhuaweicloud.com");
|
||||
|
||||
// 执行上传
|
||||
testExecuteUpload(config);
|
||||
}
|
||||
|
||||
private void testExecuteUpload(S3FileClientConfig config) throws Exception {
|
||||
// 校验配置
|
||||
ValidationUtils.validate(Validation.buildDefaultValidatorFactory().getValidator(), config);
|
||||
// 创建 Client
|
||||
S3FileClient client = new S3FileClient(0L, config);
|
||||
|
@ -77,9 +104,9 @@ public class S3FileClientTest {
|
|||
String fullPath = client.upload(content, path);
|
||||
System.out.println("访问地址:" + fullPath);
|
||||
// 读取文件
|
||||
if (false) {
|
||||
if (true) {
|
||||
byte[] bytes = client.getContent(path);
|
||||
System.out.println("文件内容:" + bytes);
|
||||
System.out.println("文件内容:" + bytes.length);
|
||||
}
|
||||
// 删除文件
|
||||
if (false) {
|
||||
|
|
|
@ -2,11 +2,13 @@ package cn.iocoder.yudao.framework.file.core.client.sftp;
|
|||
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class SftpFileClientTest {
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void test() {
|
||||
// 创建客户端
|
||||
SftpFileClientConfig config = new SftpFileClientConfig();
|
||||
|
|
|
@ -36,7 +36,6 @@
|
|||
<artifactId>jakarta.validation-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -38,6 +38,19 @@
|
|||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>${mysql.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.jdbc</groupId>
|
||||
<artifactId>ojdbc8</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
package cn.iocoder.yudao.framework.mybatis.config;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.enums.SqlConstants;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.env.EnvironmentPostProcessor;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 当 IdType 为 {@link IdType#NONE} 时,根据 PRIMARY 数据源所使用的数据库,自动设置
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
public class IdTypeEnvironmentPostProcessor implements EnvironmentPostProcessor {
|
||||
|
||||
private static final String ID_TYPE_KEY = "mybatis-plus.global-config.db-config.id-type";
|
||||
|
||||
private static final String DATASOURCE_DYNAMIC_KEY = "spring.datasource.dynamic";
|
||||
|
||||
private static final String QUARTZ_JOB_STORE_DRIVER_KEY = "spring.quartz.properties.org.quartz.jobStore.driverDelegateClass";
|
||||
|
||||
private static final Set<DbType> INPUT_ID_TYPES = SetUtils.asSet(DbType.ORACLE, DbType.ORACLE_12C,
|
||||
DbType.POSTGRE_SQL, DbType.KINGBASE_ES, DbType.DB2, DbType.H2);
|
||||
|
||||
@Override
|
||||
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
||||
// 如果获取不到 DbType,则不进行处理
|
||||
DbType dbType = getDbType(environment);
|
||||
if (dbType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置 Quartz JobStore 对应的 Driver
|
||||
// TODO 芋艿:暂时没有找到特别合适的地方,先放在这里
|
||||
setJobStoreDriverIfPresent(environment, dbType);
|
||||
|
||||
// 初始化 SQL 静态变量
|
||||
SqlConstants.init(dbType);
|
||||
|
||||
// 如果非 NONE,则不进行处理
|
||||
IdType idType = getIdType(environment);
|
||||
if (idType != IdType.NONE) {
|
||||
return;
|
||||
}
|
||||
// 情况一,用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库
|
||||
if (INPUT_ID_TYPES.contains(dbType)) {
|
||||
setIdType(environment, IdType.INPUT);
|
||||
return;
|
||||
}
|
||||
// 情况二,自增 ID,适合 MySQL 等直接自增的数据库
|
||||
setIdType(environment, IdType.AUTO);
|
||||
}
|
||||
|
||||
public IdType getIdType(ConfigurableEnvironment environment) {
|
||||
return environment.getProperty(ID_TYPE_KEY, IdType.class);
|
||||
}
|
||||
|
||||
public void setIdType(ConfigurableEnvironment environment, IdType idType) {
|
||||
environment.getSystemProperties().put(ID_TYPE_KEY, idType);
|
||||
log.info("[setIdType][修改 MyBatis Plus 的 idType 为({})]", idType);
|
||||
}
|
||||
|
||||
public void setJobStoreDriverIfPresent(ConfigurableEnvironment environment, DbType dbType) {
|
||||
String driverClass = environment.getProperty(QUARTZ_JOB_STORE_DRIVER_KEY);
|
||||
if (StrUtil.isNotEmpty(driverClass)) {
|
||||
return;
|
||||
}
|
||||
// 根据 dbType 类型,获取对应的 driverClass
|
||||
switch (dbType) {
|
||||
case POSTGRE_SQL:
|
||||
driverClass = "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate";
|
||||
break;
|
||||
case ORACLE:
|
||||
case ORACLE_12C:
|
||||
driverClass = "org.quartz.impl.jdbcjobstore.oracle.OracleDelegate";
|
||||
break;
|
||||
case SQL_SERVER:
|
||||
case SQL_SERVER2005:
|
||||
driverClass = "org.quartz.impl.jdbcjobstore.MSSQLDelegate";
|
||||
break;
|
||||
}
|
||||
// 设置 driverClass 变量
|
||||
if (StrUtil.isNotEmpty(driverClass)) {
|
||||
environment.getSystemProperties().put(QUARTZ_JOB_STORE_DRIVER_KEY, driverClass);
|
||||
}
|
||||
}
|
||||
|
||||
public static DbType getDbType(ConfigurableEnvironment environment) {
|
||||
String primary = environment.getProperty(DATASOURCE_DYNAMIC_KEY + "." + "primary");
|
||||
if (StrUtil.isEmpty(primary)) {
|
||||
return null;
|
||||
}
|
||||
String url = environment.getProperty(DATASOURCE_DYNAMIC_KEY + ".datasource." + primary + ".url");
|
||||
if (StrUtil.isEmpty(url)) {
|
||||
return null;
|
||||
}
|
||||
return JdbcUtils.getDbType(url);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,13 +1,22 @@
|
|||
package cn.iocoder.yudao.framework.mybatis.config;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.handler.DefaultDBFieldHandler;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
|
||||
import com.baomidou.mybatisplus.extension.incrementer.H2KeyGenerator;
|
||||
import com.baomidou.mybatisplus.extension.incrementer.KingbaseKeyGenerator;
|
||||
import com.baomidou.mybatisplus.extension.incrementer.OracleKeyGenerator;
|
||||
import com.baomidou.mybatisplus.extension.incrementer.PostgreKeyGenerator;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
|
||||
/**
|
||||
* MyBaits 配置类
|
||||
|
@ -31,4 +40,25 @@ public class YudaoMybatisAutoConfiguration {
|
|||
return new DefaultDBFieldHandler(); // 自动填充参数类
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "mybatis-plus.global-config.db-config", name = "id-type", havingValue = "INPUT")
|
||||
public IKeyGenerator keyGenerator(ConfigurableEnvironment environment) {
|
||||
DbType dbType = IdTypeEnvironmentPostProcessor.getDbType(environment);
|
||||
if (dbType != null) {
|
||||
switch (dbType) {
|
||||
case POSTGRE_SQL:
|
||||
return new PostgreKeyGenerator();
|
||||
case ORACLE:
|
||||
case ORACLE_12C:
|
||||
return new OracleKeyGenerator();
|
||||
case H2:
|
||||
return new H2KeyGenerator();
|
||||
case KINGBASE_ES:
|
||||
return new KingbaseKeyGenerator();
|
||||
}
|
||||
}
|
||||
// 找不到合适的 IKeyGenerator 实现类
|
||||
throw new IllegalArgumentException(StrUtil.format("DbType{} 找不到合适的 IKeyGenerator 实现类", dbType));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ package cn.iocoder.yudao.framework.mybatis.core.dataobject;
|
|||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
@ -32,14 +32,14 @@ public abstract class BaseDO implements Serializable {
|
|||
*
|
||||
* 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
@TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
|
||||
private String creator;
|
||||
/**
|
||||
* 更新者,目前使用 SysUser 的 id 编号
|
||||
*
|
||||
* 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.VARCHAR)
|
||||
private String updater;
|
||||
/**
|
||||
* 是否删除
|
||||
|
|
|
@ -1,11 +1,21 @@
|
|||
package cn.iocoder.yudao.framework.mybatis.core.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
|
||||
/**
|
||||
* SQL相关常量类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface SqlConstants {
|
||||
public class SqlConstants {
|
||||
|
||||
/**
|
||||
* 数据库的类型
|
||||
*/
|
||||
public static DbType DB_TYPE;
|
||||
|
||||
String LIMIT1 = "LIMIT 1";
|
||||
public static void init(DbType dbType) {
|
||||
DB_TYPE = dbType;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -75,12 +75,20 @@ public interface BaseMapperX<T> extends BaseMapper<T> {
|
|||
return selectList(new LambdaQueryWrapper<T>().in(field, values));
|
||||
}
|
||||
|
||||
/**
|
||||
* 逐条插入,适合少量数据插入,或者对性能要求不高的场景
|
||||
*
|
||||
* 如果大量,请使用 {@link com.baomidou.mybatisplus.extension.service.impl.ServiceImpl#saveBatch(Collection)} 方法
|
||||
* 使用示例,可见 RoleMenuBatchInsertMapper、UserRoleBatchInsertMapper 类
|
||||
*
|
||||
* @param entities 实体们
|
||||
*/
|
||||
default void insertBatch(Collection<T> entities) {
|
||||
// TODO 芋艿:修改成支持批量的
|
||||
entities.forEach(this::insert);
|
||||
}
|
||||
|
||||
default void updateBatch(T update) {
|
||||
update(update, new QueryWrapper<>());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.mybatis.core.query;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.enums.SqlConstants;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
|
@ -124,4 +126,24 @@ public class QueryWrapperX<T> extends QueryWrapper<T> {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置只返回最后一条
|
||||
*
|
||||
* TODO 芋艿:不是完美解,需要在思考下。如果使用多数据源,并且数据源是多种类型时,可能会存在问题:实现之返回一条的语法不同
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public QueryWrapperX<T> limit1() {
|
||||
Assert.notNull(SqlConstants.DB_TYPE, "获取不到数据库的类型");
|
||||
switch (SqlConstants.DB_TYPE) {
|
||||
case ORACLE:
|
||||
case ORACLE_12C:
|
||||
super.eq("ROWNUM", 1);
|
||||
break;
|
||||
default:
|
||||
super.last("LIMIT 1");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
package cn.iocoder.yudao.framework.mybatis.core.type;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import org.apache.ibatis.type.MappedJdbcTypes;
|
||||
import org.apache.ibatis.type.MappedTypes;
|
||||
import org.apache.ibatis.type.TypeHandler;
|
||||
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* List<String> 的类型转换器实现类,对应数据库的 varchar 类型
|
||||
*
|
||||
* @author 永不言败
|
||||
* @since 2022 3/23 12:50:15
|
||||
*/
|
||||
@MappedJdbcTypes(JdbcType.VARCHAR)
|
||||
@MappedTypes(List.class)
|
||||
public class StringLiSTTypeHandler implements TypeHandler<List<String>> {
|
||||
|
||||
private static final String COMMA = ",";
|
||||
|
||||
@Override
|
||||
public void setParameter(PreparedStatement ps, int i, List<String> strings, JdbcType jdbcType) throws SQLException {
|
||||
// 设置占位符
|
||||
ps.setString(i, CollUtil.join(strings, COMMA));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getResult(ResultSet rs, String columnName) throws SQLException {
|
||||
String value = rs.getString(columnName);
|
||||
return getResult(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
String value = rs.getString(columnIndex);
|
||||
return getResult(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
String value = cs.getString(columnIndex);
|
||||
return getResult(value);
|
||||
}
|
||||
|
||||
private List<String> getResult(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return StrUtil.splitTrim(value, COMMA);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package cn.iocoder.yudao.framework.mybatis.core.util;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
|
||||
/**
|
||||
* JDBC 工具类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class JdbcUtils {
|
||||
|
||||
/**
|
||||
* 判断连接是否正确
|
||||
*
|
||||
* @param url 数据源连接
|
||||
* @param username 账号
|
||||
* @param password 密码
|
||||
* @return 是否正确
|
||||
*/
|
||||
public static boolean isConnectionOK(String url, String username, String password) {
|
||||
try (Connection ignored = DriverManager.getConnection(url, username, password)) {
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得 URL 对应的 DB 类型
|
||||
*
|
||||
* @param url URL
|
||||
* @return DB 类型
|
||||
*/
|
||||
public static DbType getDbType(String url) {
|
||||
String name = com.alibaba.druid.util.JdbcUtils.getDbType(url, null);
|
||||
return DbType.getDbType(name);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration,\
|
||||
cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration
|
||||
org.springframework.boot.env.EnvironmentPostProcessor=\
|
||||
cn.iocoder.yudao.framework.mybatis.config.IdTypeEnvironmentPostProcessor
|
||||
|
|
|
@ -26,6 +26,12 @@
|
|||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId> <!-- 实现对 Caches 的自动化配置 -->
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package cn.iocoder.yudao.framework.redis.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.cache.CacheProperties;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
|
||||
/**
|
||||
* Cache 配置类,基于 Redis 实现
|
||||
*/
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class YudaoCacheAutoConfiguration {
|
||||
|
||||
/**
|
||||
* RedisCacheConfiguration Bean
|
||||
*
|
||||
* 参考 org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration 的 createConfiguration 方法
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
|
||||
// 设置使用 JSON 序列化方式
|
||||
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
|
||||
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));
|
||||
|
||||
// 设置 CacheProperties.Redis 的属性
|
||||
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
|
||||
if (redisProperties.getTimeToLive() != null) {
|
||||
config = config.entryTtl(redisProperties.getTimeToLive());
|
||||
}
|
||||
if (redisProperties.getKeyPrefix() != null) {
|
||||
config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
|
||||
}
|
||||
if (!redisProperties.isCacheNullValues()) {
|
||||
config = config.disableCachingNullValues();
|
||||
}
|
||||
if (!redisProperties.isUseKeyPrefix()) {
|
||||
config = config.disableKeyPrefix();
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package cn.iocoder.yudao.framework.redis.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
|
@ -11,7 +10,6 @@ import org.springframework.data.redis.serializer.RedisSerializer;
|
|||
* Redis 配置类
|
||||
*/
|
||||
@Configuration
|
||||
@Slf4j
|
||||
public class YudaoRedisAutoConfiguration {
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration
|
||||
cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration,\
|
||||
cn.iocoder.yudao.framework.redis.config.YudaoCacheAutoConfiguration
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<http://www.iocoder.cn/Spring-Boot/Cache/?yudao>
|
|
@ -37,10 +37,10 @@ public class SecurityProperties {
|
|||
@NotNull(message = "mock 模式的开关不能为空")
|
||||
private Boolean mockEnable;
|
||||
/**
|
||||
* mock 模式的秘钥
|
||||
* 一定要配置秘钥,保证安全性
|
||||
* mock 模式的密钥
|
||||
* 一定要配置密钥,保证安全性
|
||||
*/
|
||||
@NotEmpty(message = "mock 模式的秘钥不能为空") // 这里设置了一个默认值,因为实际上只有 mockEnable 为 true 时才需要配置。
|
||||
@NotEmpty(message = "mock 模式的密钥不能为空") // 这里设置了一个默认值,因为实际上只有 mockEnable 为 true 时才需要配置。
|
||||
private String mockSecret = "yudaoyuanma";
|
||||
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.security.config;
|
|||
import cn.iocoder.yudao.framework.security.core.aop.PreAuthenticatedAspect;
|
||||
import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
|
||||
import cn.iocoder.yudao.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy;
|
||||
import cn.iocoder.yudao.framework.security.core.filter.JWTAuthenticationTokenFilter;
|
||||
import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
|
||||
import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl;
|
||||
import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl;
|
||||
import cn.iocoder.yudao.framework.security.core.handler.LogoutSuccessHandlerImpl;
|
||||
|
@ -86,9 +86,9 @@ public class YudaoSecurityAutoConfiguration {
|
|||
* Token 认证过滤器 Bean
|
||||
*/
|
||||
@Bean
|
||||
public JWTAuthenticationTokenFilter authenticationTokenFilter(MultiUserDetailsAuthenticationProvider authenticationProvider,
|
||||
GlobalExceptionHandler globalExceptionHandler) {
|
||||
return new JWTAuthenticationTokenFilter(securityProperties, authenticationProvider, globalExceptionHandler);
|
||||
public TokenAuthenticationFilter authenticationTokenFilter(MultiUserDetailsAuthenticationProvider authenticationProvider,
|
||||
GlobalExceptionHandler globalExceptionHandler) {
|
||||
return new TokenAuthenticationFilter(securityProperties, authenticationProvider, globalExceptionHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,7 +2,7 @@ package cn.iocoder.yudao.framework.security.config;
|
|||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.security.core.authentication.MultiUserDetailsAuthenticationProvider;
|
||||
import cn.iocoder.yudao.framework.security.core.filter.JWTAuthenticationTokenFilter;
|
||||
import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
|
||||
import cn.iocoder.yudao.framework.web.config.WebProperties;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
@ -55,7 +55,7 @@ public class YudaoWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdap
|
|||
* Token 认证过滤器 Bean
|
||||
*/
|
||||
@Resource
|
||||
private JWTAuthenticationTokenFilter authenticationTokenFilter;
|
||||
private TokenAuthenticationFilter authenticationTokenFilter;
|
||||
|
||||
/**
|
||||
* 自定义的权限映射 Bean 们
|
||||
|
|
|
@ -6,7 +6,6 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
|||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Data;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.*;
|
||||
|
@ -61,10 +60,6 @@ public class LoginUser implements UserDetails {
|
|||
* 部门编号
|
||||
*/
|
||||
private Long deptId;
|
||||
/**
|
||||
* 所属岗位
|
||||
*/
|
||||
private Set<Long> postIds;
|
||||
|
||||
// ========== 上下文 ==========
|
||||
/**
|
||||
|
|
|
@ -18,13 +18,13 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* JWT 过滤器,验证 token 的有效性
|
||||
* Token 过滤器,验证 token 的有效性
|
||||
* 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
|
||||
public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final SecurityProperties securityProperties;
|
||||
|
||||
|
@ -43,7 +43,7 @@ public class JWTAuthenticationTokenFilter extends OncePerRequestFilter {
|
|||
LoginUser loginUser = authenticationProvider.verifyTokenAndRefresh(request, token);
|
||||
// 模拟 Login 功能,方便日常开发调试
|
||||
if (loginUser == null) {
|
||||
loginUser = this.mockLoginUser(request, token);
|
||||
loginUser = mockLoginUser(request, token);
|
||||
}
|
||||
// 设置当前用户
|
||||
if (loginUser != null) {
|
|
@ -21,6 +21,17 @@
|
|||
<artifactId>yudao-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- DB 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package cn.iocoder.yudao.module.pay.test;
|
||||
package cn.iocoder.yudao.framework.test.core.ut;
|
||||
|
||||
import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
|
||||
import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;
|
|
@ -1,4 +1,4 @@
|
|||
package cn.iocoder.yudao.module.infra.test;
|
||||
package cn.iocoder.yudao.framework.test.core.ut;
|
||||
|
||||
import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
|
||||
import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;
|
|
@ -1,4 +1,4 @@
|
|||
package cn.iocoder.yudao.module.pay.test;
|
||||
package cn.iocoder.yudao.framework.test.core.ut;
|
||||
|
||||
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
|
||||
import cn.iocoder.yudao.framework.test.config.RedisTestConfiguration;
|
|
@ -2,21 +2,18 @@ package cn.iocoder.yudao.framework.apilog.core.filter;
|
|||
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkService;
|
||||
import cn.iocoder.yudao.framework.apilog.core.service.dto.ApiAccessLogCreateReqDTO;
|
||||
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
|
||||
import cn.iocoder.yudao.framework.web.config.WebProperties;
|
||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import cn.iocoder.yudao.framework.web.config.WebProperties;
|
||||
import cn.iocoder.yudao.framework.web.core.filter.ApiRequestFilter;
|
||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
|
@ -26,27 +23,24 @@ import java.io.IOException;
|
|||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.*;
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
|
||||
/**
|
||||
* API 访问日志 Filter
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class ApiAccessLogFilter extends OncePerRequestFilter {
|
||||
public class ApiAccessLogFilter extends ApiRequestFilter {
|
||||
|
||||
private final WebProperties webProperties;
|
||||
private final String applicationName;
|
||||
|
||||
private final ApiAccessLogFrameworkService apiAccessLogFrameworkService;
|
||||
|
||||
@Override
|
||||
protected boolean shouldNotFilter(HttpServletRequest request) {
|
||||
// 只过滤 API 请求的地址
|
||||
return !StrUtil.startWithAny(request.getRequestURI(), webProperties.getAppApi().getPrefix(),
|
||||
webProperties.getAppApi().getPrefix());
|
||||
public ApiAccessLogFilter(WebProperties webProperties, String applicationName, ApiAccessLogFrameworkService apiAccessLogFrameworkService) {
|
||||
super(webProperties);
|
||||
this.applicationName = applicationName;
|
||||
this.apiAccessLogFrameworkService = apiAccessLogFrameworkService;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
<modules>
|
||||
<module>yudao-module-bpm-api</module>
|
||||
<module>yudao-module-bpm-base</module>
|
||||
<module>yudao-module-bpm-impl-flowable</module>
|
||||
<module>yudao-module-bpm-impl-activiti</module>
|
||||
<module>yudao-module-bpm-biz-flowable</module>
|
||||
<module>yudao-module-bpm-biz-activiti</module>
|
||||
</modules>
|
||||
<artifactId>yudao-module-bpm</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
@ -24,9 +24,9 @@
|
|||
bpm 解释:https://baike.baidu.com/item/BPM/1933
|
||||
|
||||
目前提供两套实现方案:
|
||||
1. 基于 Activiti 7 实现的 yudao-module-bpm-impl-activiti
|
||||
2. 基于 Flowable 6 实现的 yudao-module-bpm-impl-flowable
|
||||
两套实现会存在共享的逻辑,所以会继承 yudao-module-impl-base
|
||||
1. 基于 Activiti 7 实现的 yudao-module-bpm-biz-activiti
|
||||
2. 基于 Flowable 6 实现的 yudao-module-bpm-biz-flowable
|
||||
两套实现会存在共享的逻辑,所以会继承 yudao-module-bpm-base
|
||||
</description>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -3,12 +3,8 @@ package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
|
|||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Map;
|
||||
|
||||
@ApiModel("管理后台 - 通过流程任务的 Request VO")
|
||||
@Data
|
||||
|
@ -20,6 +16,6 @@ public class BpmTaskApproveReqVO {
|
|||
|
||||
@ApiModelProperty(value = "审批意见", required = true, example = "不错不错!")
|
||||
@NotEmpty(message = "审批意见不能为空")
|
||||
private String comment;
|
||||
private String reason;
|
||||
|
||||
}
|
||||
|
|
|
@ -22,6 +22,6 @@ public class BpmTaskDonePageItemRespVO extends BpmTaskTodoPageItemRespVO {
|
|||
@ApiModelProperty(value = "任务结果", required = true, notes = "参见 bpm_process_instance_result", example = "2")
|
||||
private Integer result;
|
||||
@ApiModelProperty(value = "审批建议", required = true, example = "不请假了!")
|
||||
private String comment;
|
||||
private String reason;
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,6 @@ public class BpmTaskRejectReqVO {
|
|||
|
||||
@ApiModelProperty(value = "审批意见", required = true, example = "不错不错!")
|
||||
@NotEmpty(message = "审批意见不能为空")
|
||||
private String comment;
|
||||
private String reason;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package cn.iocoder.yudao.module.bpm.dal.dataobject.definition;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
@ -16,6 +17,7 @@ import java.util.List;
|
|||
* @author 芋道源码
|
||||
*/
|
||||
@TableName(value = "bpm_form", autoResultMap = true)
|
||||
@KeySequence("bpm_form_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
|
|
|
@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.dal.dataobject.definition;
|
|||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
@ -17,6 +18,7 @@ import java.util.List;
|
|||
* @author 芋道源码
|
||||
*/
|
||||
@TableName(value = "bpm_process_definition_ext", autoResultMap = true)
|
||||
@KeySequence("bpm_process_definition_ext_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
|
|
|
@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
|||
import cn.iocoder.yudao.framework.mybatis.core.type.JsonLongSetTypeHandler;
|
||||
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum;
|
||||
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskRuleScriptEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
@ -21,6 +22,7 @@ import java.util.Set;
|
|||
* @author 芋道源码
|
||||
*/
|
||||
@TableName(value = "bpm_task_assign_rule", autoResultMap = true)
|
||||
@KeySequence("bpm_task_assign_rule_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
|
@ -31,8 +33,9 @@ public class BpmTaskAssignRuleDO extends BaseDO {
|
|||
|
||||
/**
|
||||
* {@link #processDefinitionId} 空串,用于标识属于流程模型,而不属于流程定义
|
||||
* 不使用空串的原因,Oracle 针对空串,会处理成 null,进而导致无法检索
|
||||
*/
|
||||
public static final String PROCESS_DEFINITION_ID_NULL = "";
|
||||
public static final String PROCESS_DEFINITION_ID_NULL = "DEFAULT";
|
||||
|
||||
/**
|
||||
* 编号
|
||||
|
@ -64,7 +67,6 @@ public class BpmTaskAssignRuleDO extends BaseDO {
|
|||
*
|
||||
* 枚举 {@link BpmTaskAssignRuleTypeEnum}
|
||||
*/
|
||||
@TableField("`type`")
|
||||
private Integer type;
|
||||
/**
|
||||
* 规则值数组,一般关联指定表的编号
|
||||
|
|
|
@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.bpm.dal.dataobject.definition;
|
|||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.type.JsonLongSetTypeHandler;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
@ -16,6 +17,7 @@ import java.util.Set;
|
|||
* @author 芋道源码
|
||||
*/
|
||||
@TableName(value = "bpm_user_group", autoResultMap = true)
|
||||
@KeySequence("bpm_user_group_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package cn.iocoder.yudao.module.bpm.dal.dataobject.oa;
|
||||
|
||||
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
|
||||
import lombok.*;
|
||||
import java.util.*;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* OA 请假申请 DO
|
||||
|
@ -15,6 +18,7 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
|||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("bpm_oa_leave")
|
||||
@KeySequence("bpm_oa_leave_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
|
@ -37,8 +41,7 @@ public class BpmOALeaveDO extends BaseDO {
|
|||
/**
|
||||
* 请假类型
|
||||
*/
|
||||
@TableField("`type`")
|
||||
private String type;
|
||||
private Integer type;
|
||||
/**
|
||||
* 原因
|
||||
*/
|
||||
|
|
|
@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.bpm.dal.dataobject.task;
|
|||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
|
||||
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
@ -21,6 +22,7 @@ import java.util.Map;
|
|||
* @author 芋道源码
|
||||
*/
|
||||
@TableName(value = "bpm_process_instance_ext", autoResultMap = true)
|
||||
@KeySequence("bpm_process_instance_ext_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
|
|
|
@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.bpm.dal.dataobject.task;
|
|||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
@ -17,6 +19,7 @@ import java.util.Date;
|
|||
* @author 芋道源码
|
||||
*/
|
||||
@TableName(value = "bpm_task_ext", autoResultMap = true)
|
||||
@KeySequence("bpm_task_ext_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
|
@ -60,7 +63,7 @@ public class BpmTaskExtDO extends BaseDO {
|
|||
/**
|
||||
* 审批建议
|
||||
*/
|
||||
private String comment;
|
||||
private String reason;
|
||||
/**
|
||||
* 任务的结束时间
|
||||
*
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package cn.iocoder.yudao.module.bpm.dal.mysql.definition;
|
||||
|
||||
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form.BpmFormPageReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
|
@ -17,9 +17,9 @@ import org.apache.ibatis.annotations.Mapper;
|
|||
public interface BpmFormMapper extends BaseMapperX<BpmFormDO> {
|
||||
|
||||
default PageResult<BpmFormDO> selectPage(BpmFormPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new QueryWrapperX<BpmFormDO>()
|
||||
.likeIfPresent("name", reqVO.getName())
|
||||
.orderByDesc("id"));
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<BpmFormDO>()
|
||||
.likeIfPresent(BpmFormDO::getName, reqVO.getName())
|
||||
.orderByDesc(BpmFormDO::getId));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package cn.iocoder.yudao.module.bpm.dal.mysql.definition;
|
||||
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Collection;
|
||||
|
@ -11,11 +11,11 @@ import java.util.List;
|
|||
public interface BpmProcessDefinitionExtMapper extends BaseMapperX<BpmProcessDefinitionExtDO> {
|
||||
|
||||
default List<BpmProcessDefinitionExtDO> selectListByProcessDefinitionIds(Collection<String> processDefinitionIds) {
|
||||
return selectList("process_definition_id", processDefinitionIds);
|
||||
return selectList(BpmProcessDefinitionExtDO::getProcessDefinitionId, processDefinitionIds);
|
||||
}
|
||||
|
||||
default BpmProcessDefinitionExtDO selectByProcessDefinitionId(String processDefinitionId) {
|
||||
return selectOne("process_definition_id", processDefinitionId);
|
||||
return selectOne(BpmProcessDefinitionExtDO::getProcessDefinitionId, processDefinitionId);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package cn.iocoder.yudao.module.bpm.dal.mysql.definition;
|
||||
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
|
@ -13,23 +13,23 @@ public interface BpmTaskAssignRuleMapper extends BaseMapperX<BpmTaskAssignRuleDO
|
|||
|
||||
default List<BpmTaskAssignRuleDO> selectListByProcessDefinitionId(String processDefinitionId,
|
||||
@Nullable String taskDefinitionKey) {
|
||||
return selectList(new QueryWrapperX<BpmTaskAssignRuleDO>()
|
||||
.eq("process_definition_id", processDefinitionId)
|
||||
.eqIfPresent("task_definition_key", taskDefinitionKey));
|
||||
return selectList(new LambdaQueryWrapperX<BpmTaskAssignRuleDO>()
|
||||
.eq(BpmTaskAssignRuleDO::getProcessDefinitionId, processDefinitionId)
|
||||
.eqIfPresent(BpmTaskAssignRuleDO::getTaskDefinitionKey, taskDefinitionKey));
|
||||
}
|
||||
|
||||
default List<BpmTaskAssignRuleDO> selectListByModelId(String modelId) {
|
||||
return selectList(new QueryWrapperX<BpmTaskAssignRuleDO>()
|
||||
.eq("model_id", modelId)
|
||||
.eq("process_definition_id", BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL));
|
||||
return selectList(new LambdaQueryWrapperX<BpmTaskAssignRuleDO>()
|
||||
.eq(BpmTaskAssignRuleDO::getModelId, modelId)
|
||||
.eq(BpmTaskAssignRuleDO::getProcessDefinitionId, BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL));
|
||||
}
|
||||
|
||||
default BpmTaskAssignRuleDO selectListByModelIdAndTaskDefinitionKey(String modelId,
|
||||
String taskDefinitionKey) {
|
||||
return selectOne(new QueryWrapperX<BpmTaskAssignRuleDO>()
|
||||
.eq("model_id", modelId)
|
||||
.eq("process_definition_id", BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL)
|
||||
.eq("task_definition_key", taskDefinitionKey));
|
||||
return selectOne(new LambdaQueryWrapperX<BpmTaskAssignRuleDO>()
|
||||
.eq(BpmTaskAssignRuleDO::getModelId, modelId)
|
||||
.eq(BpmTaskAssignRuleDO::getProcessDefinitionId, BpmTaskAssignRuleDO.PROCESS_DEFINITION_ID_NULL)
|
||||
.eq(BpmTaskAssignRuleDO::getTaskDefinitionKey, taskDefinitionKey));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,35 +1,34 @@
|
|||
package cn.iocoder.yudao.module.bpm.dal.mysql.task;
|
||||
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceMyPageReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceMyPageReqVO;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface BpmProcessInstanceExtMapper extends BaseMapperX<BpmProcessInstanceExtDO> {
|
||||
|
||||
default PageResult<BpmProcessInstanceExtDO> selectPage(Long userId, BpmProcessInstanceMyPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new QueryWrapperX<BpmProcessInstanceExtDO>()
|
||||
.eqIfPresent("start_user_id", userId)
|
||||
.likeIfPresent("name", reqVO.getName())
|
||||
.eqIfPresent("process_definition_id", reqVO.getProcessDefinitionId())
|
||||
.eqIfPresent("category", reqVO.getCategory())
|
||||
.eqIfPresent("status", reqVO.getStatus())
|
||||
.eqIfPresent("result", reqVO.getResult())
|
||||
.betweenIfPresent("create_time", reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
|
||||
.orderByDesc("id"));
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<BpmProcessInstanceExtDO>()
|
||||
.eqIfPresent(BpmProcessInstanceExtDO::getStartUserId, userId)
|
||||
.likeIfPresent(BpmProcessInstanceExtDO::getName, reqVO.getName())
|
||||
.eqIfPresent(BpmProcessInstanceExtDO::getProcessDefinitionId, reqVO.getProcessDefinitionId())
|
||||
.eqIfPresent(BpmProcessInstanceExtDO::getCategory, reqVO.getCategory())
|
||||
.eqIfPresent(BpmProcessInstanceExtDO::getStatus, reqVO.getStatus())
|
||||
.eqIfPresent(BpmProcessInstanceExtDO::getResult, reqVO.getResult())
|
||||
.betweenIfPresent(BpmProcessInstanceExtDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime())
|
||||
.orderByDesc(BpmProcessInstanceExtDO::getId));
|
||||
}
|
||||
|
||||
default BpmProcessInstanceExtDO selectByProcessInstanceId(String processDefinitionId) {
|
||||
return selectOne("process_instance_id", processDefinitionId);
|
||||
return selectOne(BpmProcessInstanceExtDO::getProcessInstanceId, processDefinitionId);
|
||||
}
|
||||
|
||||
default void updateByProcessInstanceId(BpmProcessInstanceExtDO updateObj) {
|
||||
update(updateObj, new QueryWrapper<BpmProcessInstanceExtDO>()
|
||||
.eq("process_instance_id", updateObj.getProcessInstanceId()));
|
||||
update(updateObj, new LambdaQueryWrapperX<BpmProcessInstanceExtDO>()
|
||||
.eq(BpmProcessInstanceExtDO::getProcessInstanceId, updateObj.getProcessInstanceId()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package cn.iocoder.yudao.module.bpm.dal.mysql.task;
|
||||
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmTaskExtDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
|
@ -20,6 +20,6 @@ public interface BpmTaskExtMapper extends BaseMapperX<BpmTaskExtDO> {
|
|||
}
|
||||
|
||||
default List<BpmTaskExtDO> selectListByProcessInstanceId(String processInstanceId) {
|
||||
return selectList("process_instance_id", processInstanceId);
|
||||
return selectList(BpmTaskExtDO::getProcessInstanceId, processInstanceId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ public interface BpmMessageService {
|
|||
*/
|
||||
void sendMessageWhenProcessInstanceApprove(@Valid BpmMessageSendWhenProcessInstanceApproveReqDTO reqDTO);
|
||||
|
||||
|
||||
/**
|
||||
* 发送流程实例被不通过的消息
|
||||
*
|
||||
|
|
|
@ -44,7 +44,7 @@ public class BpmMessageServiceImpl implements BpmMessageService {
|
|||
public void sendMessageWhenProcessInstanceReject(BpmMessageSendWhenProcessInstanceRejectReqDTO reqDTO) {
|
||||
Map<String, Object> templateParams = new HashMap<>();
|
||||
templateParams.put("processInstanceName", reqDTO.getProcessInstanceName());
|
||||
templateParams.put("comment", reqDTO.getComment());
|
||||
templateParams.put("reason", reqDTO.getReason());
|
||||
templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId()));
|
||||
smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getStartUserId(),
|
||||
BpmMessageEnum.PROCESS_INSTANCE_REJECT.getSmsTemplateCode(), templateParams));
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue