From d070aa73a25d8e93372aafb43ab732953cd42faf Mon Sep 17 00:00:00 2001 From: chenyuan <1154693969@qq.com> Date: Tue, 17 Dec 2024 01:31:10 +0800 Subject: [PATCH] fix module --- pom.xml | 29 +- .../framework/common/util/date/DateUtils.java | 6 + .../db/DataPermissionDatabaseInterceptor.java | 641 ------------------ ...DataPermissionDatabaseInterceptorTest.java | 190 ------ ...ataPermissionDatabaseInterceptorTest2.java | 533 --------------- .../mybatis/core/enums/SqlConstants.java | 21 - .../core/type/JsonLongSetTypeHandler.java | 31 - .../yudao-spring-boot-starter-redis/pom.xml | 1 + .../core/annotations/PreAuthenticated.java | 17 - .../core/aop/PreAuthenticatedAspect.java | 25 - .../service/ApiAccessLogFrameworkService.java | 19 - .../ApiAccessLogFrameworkServiceImpl.java | 26 - .../service/ApiErrorLogFrameworkService.java | 19 - .../ApiErrorLogFrameworkServiceImpl.java | 26 - .../core/databind/NumberSerializer.java | 37 - .../TimestampLocalDateTimeDeserializer.java | 27 - .../TimestampLocalDateTimeSerializer.java | 26 - .../bpm/enums/task/BpmDeleteReasonEnum.java | 45 -- .../vo/model/BpmModeImportReqVO.java | 19 - .../vo/model/BpmModelCreateReqVO.java | 25 - .../vo/model/BpmModelPageReqVO.java | 23 - .../vo/model/BpmModelUpdateReqVO.java | 47 -- .../admin/task/BpmActivityController.java | 39 -- .../bpm/convert/task/BpmActivityConvert.java | 30 - .../BpmTaskCandidateDeptLeaderStrategy.java | 46 -- .../BpmTaskCandidateDeptMemberStrategy.java | 49 -- .../BpmTaskCandidateExpressionStrategy.java | 36 - .../BpmTaskCandidateGroupStrategy.java | 47 -- .../BpmTaskCandidatePostStrategy.java | 49 -- .../BpmTaskCandidateRoleStrategy.java | 44 -- ...mTaskCandidateStartUserSelectStrategy.java | 76 --- .../BpmTaskCandidateUserStrategy.java | 39 -- .../flowable/core/enums/BpmConstants.java | 40 -- .../dto/BpmModelMetaInfoRespDTO.java | 46 -- .../bpm/service/task/BpmActivityService.java | 31 - .../service/task/BpmActivityServiceImpl.java | 40 -- ...pmTaskCandidateDeptLeaderStrategyTest.java | 42 -- ...pmTaskCandidateDeptMemberStrategyTest.java | 42 -- ...pmTaskCandidateExpressionStrategyTest.java | 39 -- .../BpmTaskCandidateGroupStrategyTest.java | 42 -- .../BpmTaskCandidatePostStrategyTest.java | 45 -- .../BpmTaskCandidateRoleStrategyTest.java | 41 -- .../BpmTaskCandidateUserStrategyTest.java | 28 - .../codegen/vue3_schema/api/api.ts.vm | 46 -- .../codegen/vue3_schema/views/data.ts.vm | 124 ---- .../codegen/vue3_schema/views/form.vue.vm | 65 -- .../codegen/vue3_schema/views/index.vue.vm | 85 --- yudao-module-iot/yudao-module-iot-biz/pom.xml | 5 - .../config/SecurityConfiguration.java | 13 +- yudao-module-mes/yudao-module-mes-biz/pom.xml | 11 - .../config/SecurityConfiguration.java | 7 +- .../yudao-module-report-biz/pom.xml | 5 - .../service/JmReportTokenServiceImpl.java | 7 + .../config/SecurityConfiguration.java | 11 +- .../test/resources/application-unit-test.yaml | 5 - .../yudao/module/system/api/dept/DeptApi.java | 2 - .../system/api/logger/OperateLogApi.java | 5 +- .../api/logger/dto/OperateLogRespDTO.java | 2 +- .../dto/OAuth2AccessTokenCheckRespDTO.java | 2 - .../system/api/social/SocialClientApi.java | 39 +- .../system/api/social/SocialUserApi.java | 10 +- .../api/social/dto/SocialWxQrcodeReqDTO.java | 67 ++ .../SocialWxaSubscribeMessageSendReqDTO.java | 61 ++ .../SocialWxaSubscribeTemplateRespDTO.java | 42 ++ .../module/system/api/user/AdminUserApi.java | 6 +- .../system/api/user/dto/AdminUserRespDTO.java | 4 + .../system/enums/DictTypeConstants.java | 3 +- .../system/enums/ErrorCodeConstants.java | 20 +- .../module/system/enums/common/SexEnum.java | 2 +- .../enums/oauth2/OAuth2GrantTypeEnum.java | 2 +- .../yudao-module-system-biz/pom.xml | 13 - .../system/api/logger/OperateLogApiImpl.java | 2 - .../api/social/SocialClientApiImpl.java | 58 +- .../system/api/social/SocialUserApiImpl.java | 1 + .../system/api/user/AdminUserApiImpl.java | 20 +- .../controller/admin/auth/AuthController.java | 9 +- .../admin/auth/vo/AuthLoginReqVO.java | 5 +- .../admin/auth/vo/AuthRegisterReqVO.java | 40 ++ .../admin/captcha/CaptchaController.java | 16 +- .../admin/oauth2/OAuth2OpenController.java | 2 +- .../admin/permission/MenuController.java | 1 + .../admin/permission/vo/role/RoleRespVO.java | 3 +- .../permission/vo/role/RoleSaveReqVO.java | 11 +- .../admin/sms/SmsCallbackController.java | 27 +- .../admin/socail/SocialClientController.http | 20 + .../admin/socail/SocialClientController.java | 11 + .../tenant/vo/tenant/TenantSaveReqVO.java | 5 +- .../controller/admin/user/UserController.java | 5 +- .../admin/user/vo/user/UserPageReqVO.java | 3 + .../admin/user/vo/user/UserSaveReqVO.java | 4 +- .../app/dict/AppDictDataController.java | 2 + .../controller/app/ip/AppAreaController.java | 2 + .../dal/dataobject/mail/MailAccountDO.java | 2 + .../system/dal/dataobject/mail/MailLogDO.java | 2 + .../dal/dataobject/mail/MailTemplateDO.java | 2 + .../oauth2/OAuth2RefreshTokenDO.java | 4 +- .../dal/dataobject/permission/RoleDO.java | 6 +- .../dataobject/tenant/TenantPackageDO.java | 4 +- .../dal/dataobject/user/AdminUserDO.java | 4 +- .../system/dal/mysql/dept/DeptMapper.java | 4 + .../oauth2/OAuth2RefreshTokenMapper.java | 2 + .../dal/mysql/user/AdminUserMapper.java | 3 +- .../system/dal/redis/RedisKeyConstants.java | 9 + .../framework/sms/core/client/SmsClient.java | 2 + .../sms/core/client/SmsClientFactory.java | 3 +- .../core/client/impl/AbstractSmsClient.java | 6 - .../sms/core/client/impl/AliyunSmsClient.java | 245 +++---- .../client/impl/DebugDingTalkSmsClient.java | 4 - .../sms/core/client/impl/HuaweiSmsClient.java | 166 +++++ .../sms/core/client/impl/QiniuSmsClient.java | 155 +++++ .../client/impl/SmsClientFactoryImpl.java | 5 +- .../core/client/impl/TencentSmsClient.java | 229 +++---- .../sms/core/enums/SmsChannelEnum.java | 4 +- .../system/service/auth/AdminAuthService.java | 10 +- .../service/auth/AdminAuthServiceImpl.java | 43 +- .../system/service/dept/DeptService.java | 21 +- .../system/service/dept/DeptServiceImpl.java | 9 +- .../oauth2/OAuth2TokenServiceImpl.java | 28 +- .../service/permission/MenuService.java | 8 + .../service/permission/MenuServiceImpl.java | 53 +- .../permission/PermissionServiceImpl.java | 6 +- .../service/permission/RoleServiceImpl.java | 5 +- .../service/sms/SmsChannelServiceImpl.java | 81 +-- .../service/sms/SmsCodeServiceImpl.java | 3 +- .../service/social/SocialClientService.java | 46 +- .../social/SocialClientServiceImpl.java | 110 ++- .../service/social/SocialUserService.java | 1 + .../service/social/SocialUserServiceImpl.java | 1 + .../system/service/user/AdminUserService.java | 21 +- .../service/user/AdminUserServiceImpl.java | 65 +- .../core/client/impl/AliyunSmsClientTest.java | 162 ++--- .../core/client/impl/HuaweiSmsClientTest.java | 127 ++++ .../core/client/impl/QiniuSmsClientTest.java | 131 ++++ .../sms/core/client/impl/SmsClientTests.java | 151 +++++ .../client/impl/TencentSmsClientTest.java | 274 ++++---- .../auth/AdminAuthServiceImplTest.java | 7 + .../notify/NotifyMessageServiceImplTest.java | 4 - .../oauth2/OAuth2ApproveServiceImplTest.java | 3 +- .../oauth2/OAuth2CodeServiceImplTest.java | 6 +- .../oauth2/OAuth2TokenServiceImplTest.java | 54 +- .../permission/RoleServiceImplTest.java | 7 +- .../service/sms/SmsChannelServiceTest.java | 29 +- .../service/sms/SmsCodeServiceImplTest.java | 18 - .../user/AdminUserServiceImplTest.java | 19 + .../test/resources/application-unit-test.yaml | 5 - .../src/test/resources/sql/create_tables.sql | 3 +- 146 files changed, 2155 insertions(+), 3852 deletions(-) delete mode 100644 yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptor.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest2.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/SqlConstants.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/JsonLongSetTypeHandler.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/annotations/PreAuthenticated.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/aop/PreAuthenticatedAspect.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLogFrameworkService.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLogFrameworkServiceImpl.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLogFrameworkService.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLogFrameworkServiceImpl.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/NumberSerializer.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/TimestampLocalDateTimeDeserializer.java delete mode 100644 yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/TimestampLocalDateTimeSerializer.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModeImportReqVO.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelCreateReqVO.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelPageReqVO.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateReqVO.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmActivityController.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmActivityConvert.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategy.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmConstants.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/dto/BpmModelMetaInfoRespDTO.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityServiceImpl.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategyTest.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategyTest.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategyTest.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategyTest.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategyTest.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategyTest.java delete mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategyTest.java delete mode 100644 yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_schema/api/api.ts.vm delete mode 100644 yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_schema/views/data.ts.vm delete mode 100644 yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_schema/views/form.vue.vm delete mode 100644 yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_schema/views/index.vue.vm create mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java create mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxaSubscribeMessageSendReqDTO.java create mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxaSubscribeTemplateRespDTO.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthRegisterReqVO.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialClientController.http create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClientTest.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java diff --git a/pom.xml b/pom.xml index 11727a59e..e4136bef4 100644 --- a/pom.xml +++ b/pom.xml @@ -31,18 +31,18 @@ https://github.com/YunaiV/ruoyi-vue-pro - 2.1.0-jdk8-snapshot + 2.3.0-jdk8-SNAPSHOT 1.8 ${java.version} ${java.version} 3.0.0-M5 - 3.8.1 - 1.5.0 + 3.13.0 + 1.6.0 - 1.18.30 + 1.18.34 2.7.18 - 1.5.5.Final + 1.6.2 UTF-8 @@ -108,7 +108,7 @@ flatten-maven-plugin ${flatten-maven-plugin.version} - resolveCiFriendliesOnly + oss true @@ -143,6 +143,23 @@ aliyun https://maven.aliyun.com/repository/public + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + false + + diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java index b51a838c6..8d89d0eee 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java @@ -146,4 +146,10 @@ public class DateUtils { return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now().minusDays(1)); } + public static LocalDateTime[] getDateRange(LocalDateTime base){ + if(base==null)base = LocalDateTime.now(); + LocalDateTime start = LocalDateTime.of(base.getYear(),base.getMonth(),base.getDayOfMonth(),0,0,0); + LocalDateTime end = LocalDateTime.of(base.getYear(),base.getMonth(),base.getDayOfMonth(),23,59,59); + return new LocalDateTime[]{start,end}; + } } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptor.java b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptor.java deleted file mode 100644 index cbeedee4d..000000000 --- a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptor.java +++ /dev/null @@ -1,641 +0,0 @@ -package cn.iocoder.yudao.framework.datapermission.core.db; - -import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.framework.common.util.collection.SetUtils; -import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule; -import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory; -import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; -import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; -import com.baomidou.mybatisplus.core.toolkit.PluginUtils; -import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport; -import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import net.sf.jsqlparser.expression.*; -import net.sf.jsqlparser.expression.operators.conditional.AndExpression; -import net.sf.jsqlparser.expression.operators.conditional.OrExpression; -import net.sf.jsqlparser.expression.operators.relational.ExistsExpression; -import net.sf.jsqlparser.expression.operators.relational.ExpressionList; -import net.sf.jsqlparser.expression.operators.relational.InExpression; -import net.sf.jsqlparser.schema.Table; -import net.sf.jsqlparser.statement.delete.Delete; -import net.sf.jsqlparser.statement.select.*; -import net.sf.jsqlparser.statement.update.Update; -import org.apache.ibatis.executor.Executor; -import org.apache.ibatis.executor.statement.StatementHandler; -import org.apache.ibatis.mapping.BoundSql; -import org.apache.ibatis.mapping.MappedStatement; -import org.apache.ibatis.mapping.SqlCommandType; -import org.apache.ibatis.session.ResultHandler; -import org.apache.ibatis.session.RowBounds; - -import java.sql.Connection; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 数据权限拦截器,通过 {@link DataPermissionRule} 数据权限规则,重写 SQL 的方式来实现 - * 主要的 SQL 重写方法,可见 {@link #builderExpression(Expression, List)} 方法 - * - * 整体的代码实现上,参考 {@link com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor} 实现。 - * 所以每次 MyBatis Plus 升级时,需要 Review 下其具体的实现是否有变更! - * - * @author 芋道源码 - */ -@RequiredArgsConstructor -public class DataPermissionDatabaseInterceptor extends JsqlParserSupport implements InnerInterceptor { - - private final DataPermissionRuleFactory ruleFactory; - - @Getter - private final MappedStatementCache mappedStatementCache = new MappedStatementCache(); - - @Override // SELECT 场景 - public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { - // 获得 Mapper 对应的数据权限的规则 - List rules = ruleFactory.getDataPermissionRule(ms.getId()); - if (mappedStatementCache.noRewritable(ms, rules)) { // 如果无需重写,则跳过 - return; - } - - PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql); - try { - // 初始化上下文 - ContextHolder.init(rules); - // 处理 SQL - mpBs.sql(parserSingle(mpBs.sql(), null)); - } finally { - // 添加是否需要重写的缓存 - addMappedStatementCache(ms); - // 清空上下文 - ContextHolder.clear(); - } - } - - @Override // 只处理 UPDATE / DELETE 场景,不处理 INSERT 场景(因为 INSERT 不需要数据权限) - public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) { - PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh); - MappedStatement ms = mpSh.mappedStatement(); - SqlCommandType sct = ms.getSqlCommandType(); - if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) { - // 获得 Mapper 对应的数据权限的规则 - List rules = ruleFactory.getDataPermissionRule(ms.getId()); - if (mappedStatementCache.noRewritable(ms, rules)) { // 如果无需重写,则跳过 - return; - } - - PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql(); - try { - // 初始化上下文 - ContextHolder.init(rules); - // 处理 SQL - mpBs.sql(parserMulti(mpBs.sql(), null)); - } finally { - // 添加是否需要重写的缓存 - addMappedStatementCache(ms); - // 清空上下文 - ContextHolder.clear(); - } - } - } - - @Override - protected void processSelect(Select select, int index, String sql, Object obj) { - processSelectBody(select.getSelectBody()); - List withItemsList = select.getWithItemsList(); - if (!CollectionUtils.isEmpty(withItemsList)) { - withItemsList.forEach(this::processSelectBody); - } - } - - /** - * update 语句处理 - */ - @Override - protected void processUpdate(Update update, int index, String sql, Object obj) { - final Table table = update.getTable(); - update.setWhere(this.builderExpression(update.getWhere(), table)); - } - - /** - * delete 语句处理 - */ - @Override - protected void processDelete(Delete delete, int index, String sql, Object obj) { - delete.setWhere(this.builderExpression(delete.getWhere(), delete.getTable())); - } - - // ========== 和 TenantLineInnerInterceptor 一致的逻辑 ========== - - protected void processSelectBody(SelectBody selectBody) { - if (selectBody == null) { - return; - } - if (selectBody instanceof PlainSelect) { - processPlainSelect((PlainSelect) selectBody); - } else if (selectBody instanceof WithItem) { - WithItem withItem = (WithItem) selectBody; - processSelectBody(withItem.getSubSelect().getSelectBody()); - } else { - SetOperationList operationList = (SetOperationList) selectBody; - List selectBodyList = operationList.getSelects(); - if (CollectionUtils.isNotEmpty(selectBodyList)) { - selectBodyList.forEach(this::processSelectBody); - } - } - } - - /** - * 处理 PlainSelect - */ - protected void processPlainSelect(PlainSelect plainSelect) { - //#3087 github - List selectItems = plainSelect.getSelectItems(); - if (CollectionUtils.isNotEmpty(selectItems)) { - selectItems.forEach(this::processSelectItem); - } - - // 处理 where 中的子查询 - Expression where = plainSelect.getWhere(); - processWhereSubSelect(where); - - // 处理 fromItem - FromItem fromItem = plainSelect.getFromItem(); - List list = processFromItem(fromItem); - List
mainTables = new ArrayList<>(list); - - // 处理 join - List joins = plainSelect.getJoins(); - if (CollectionUtils.isNotEmpty(joins)) { - mainTables = processJoins(mainTables, joins); - } - - // 当有 mainTable 时,进行 where 条件追加 - if (CollectionUtils.isNotEmpty(mainTables)) { - plainSelect.setWhere(builderExpression(where, mainTables)); - } - } - - private List
processFromItem(FromItem fromItem) { - // 处理括号括起来的表达式 - while (fromItem instanceof ParenthesisFromItem) { - fromItem = ((ParenthesisFromItem) fromItem).getFromItem(); - } - - List
mainTables = new ArrayList<>(); - // 无 join 时的处理逻辑 - if (fromItem instanceof Table) { - Table fromTable = (Table) fromItem; - mainTables.add(fromTable); - } else if (fromItem instanceof SubJoin) { - // SubJoin 类型则还需要添加上 where 条件 - List
tables = processSubJoin((SubJoin) fromItem); - mainTables.addAll(tables); - } else { - // 处理下 fromItem - processOtherFromItem(fromItem); - } - return mainTables; - } - - /** - * 处理where条件内的子查询 - *

- * 支持如下: - * 1. in - * 2. = - * 3. > - * 4. < - * 5. >= - * 6. <= - * 7. <> - * 8. EXISTS - * 9. NOT EXISTS - *

- * 前提条件: - * 1. 子查询必须放在小括号中 - * 2. 子查询一般放在比较操作符的右边 - * - * @param where where 条件 - */ - protected void processWhereSubSelect(Expression where) { - if (where == null) { - return; - } - if (where instanceof FromItem) { - processOtherFromItem((FromItem) where); - return; - } - if (where.toString().indexOf("SELECT") > 0) { - // 有子查询 - if (where instanceof BinaryExpression) { - // 比较符号 , and , or , 等等 - BinaryExpression expression = (BinaryExpression) where; - processWhereSubSelect(expression.getLeftExpression()); - processWhereSubSelect(expression.getRightExpression()); - } else if (where instanceof InExpression) { - // in - InExpression expression = (InExpression) where; - Expression inExpression = expression.getRightExpression(); - if (inExpression instanceof SubSelect) { - processSelectBody(((SubSelect) inExpression).getSelectBody()); - } - } else if (where instanceof ExistsExpression) { - // exists - ExistsExpression expression = (ExistsExpression) where; - processWhereSubSelect(expression.getRightExpression()); - } else if (where instanceof NotExpression) { - // not exists - NotExpression expression = (NotExpression) where; - processWhereSubSelect(expression.getExpression()); - } else if (where instanceof Parenthesis) { - Parenthesis expression = (Parenthesis) where; - processWhereSubSelect(expression.getExpression()); - } - } - } - - protected void processSelectItem(SelectItem selectItem) { - if (selectItem instanceof SelectExpressionItem) { - SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem; - if (selectExpressionItem.getExpression() instanceof SubSelect) { - processSelectBody(((SubSelect) selectExpressionItem.getExpression()).getSelectBody()); - } else if (selectExpressionItem.getExpression() instanceof Function) { - processFunction((Function) selectExpressionItem.getExpression()); - } - } - } - - /** - * 处理函数 - *

支持: 1. select fun(args..) 2. select fun1(fun2(args..),args..)

- *

fixed gitee pulls/141

- * - * @param function - */ - protected void processFunction(Function function) { - ExpressionList parameters = function.getParameters(); - if (parameters != null) { - parameters.getExpressions().forEach(expression -> { - if (expression instanceof SubSelect) { - processSelectBody(((SubSelect) expression).getSelectBody()); - } else if (expression instanceof Function) { - processFunction((Function) expression); - } - }); - } - } - - /** - * 处理子查询等 - */ - protected void processOtherFromItem(FromItem fromItem) { - // 去除括号 - while (fromItem instanceof ParenthesisFromItem) { - fromItem = ((ParenthesisFromItem) fromItem).getFromItem(); - } - - if (fromItem instanceof SubSelect) { - SubSelect subSelect = (SubSelect) fromItem; - if (subSelect.getSelectBody() != null) { - processSelectBody(subSelect.getSelectBody()); - } - } else if (fromItem instanceof ValuesList) { - logger.debug("Perform a subQuery, if you do not give us feedback"); - } else if (fromItem instanceof LateralSubSelect) { - LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem; - if (lateralSubSelect.getSubSelect() != null) { - SubSelect subSelect = lateralSubSelect.getSubSelect(); - if (subSelect.getSelectBody() != null) { - processSelectBody(subSelect.getSelectBody()); - } - } - } - } - - /** - * 处理 sub join - * - * @param subJoin subJoin - * @return Table subJoin 中的主表 - */ - private List
processSubJoin(SubJoin subJoin) { - List
mainTables = new ArrayList<>(); - if (subJoin.getJoinList() != null) { - List
list = processFromItem(subJoin.getLeft()); - mainTables.addAll(list); - mainTables = processJoins(mainTables, subJoin.getJoinList()); - } - return mainTables; - } - - /** - * 处理 joins - * - * @param mainTables 可以为 null - * @param joins join 集合 - * @return List
右连接查询的 Table 列表 - */ - private List
processJoins(List
mainTables, List joins) { - // join 表达式中最终的主表 - Table mainTable = null; - // 当前 join 的左表 - Table leftTable = null; - - if (mainTables == null) { - mainTables = new ArrayList<>(); - } else if (mainTables.size() == 1) { - mainTable = mainTables.get(0); - leftTable = mainTable; - } - - //对于 on 表达式写在最后的 join,需要记录下前面多个 on 的表名 - Deque> onTableDeque = new LinkedList<>(); - for (Join join : joins) { - // 处理 on 表达式 - FromItem joinItem = join.getRightItem(); - - // 获取当前 join 的表,subJoint 可以看作是一张表 - List
joinTables = null; - if (joinItem instanceof Table) { - joinTables = new ArrayList<>(); - joinTables.add((Table) joinItem); - } else if (joinItem instanceof SubJoin) { - joinTables = processSubJoin((SubJoin) joinItem); - } - - if (joinTables != null) { - - // 如果是隐式内连接 - if (join.isSimple()) { - mainTables.addAll(joinTables); - continue; - } - - // 当前表是否忽略 - Table joinTable = joinTables.get(0); - - List
onTables = null; - // 如果不要忽略,且是右连接,则记录下当前表 - if (join.isRight()) { - mainTable = joinTable; - if (leftTable != null) { - onTables = Collections.singletonList(leftTable); - } - } else if (join.isLeft()) { - onTables = Collections.singletonList(joinTable); - } else if (join.isInner()) { - if (mainTable == null) { - onTables = Collections.singletonList(joinTable); - } else { - onTables = Arrays.asList(mainTable, joinTable); - } - mainTable = null; - } - - mainTables = new ArrayList<>(); - if (mainTable != null) { - mainTables.add(mainTable); - } - - // 获取 join 尾缀的 on 表达式列表 - Collection originOnExpressions = join.getOnExpressions(); - // 正常 join on 表达式只有一个,立刻处理 - if (originOnExpressions.size() == 1 && onTables != null) { - List onExpressions = new LinkedList<>(); - onExpressions.add(builderExpression(originOnExpressions.iterator().next(), onTables)); - join.setOnExpressions(onExpressions); - leftTable = joinTable; - continue; - } - // 表名压栈,忽略的表压入 null,以便后续不处理 - onTableDeque.push(onTables); - // 尾缀多个 on 表达式的时候统一处理 - if (originOnExpressions.size() > 1) { - Collection onExpressions = new LinkedList<>(); - for (Expression originOnExpression : originOnExpressions) { - List
currentTableList = onTableDeque.poll(); - if (CollectionUtils.isEmpty(currentTableList)) { - onExpressions.add(originOnExpression); - } else { - onExpressions.add(builderExpression(originOnExpression, currentTableList)); - } - } - join.setOnExpressions(onExpressions); - } - leftTable = joinTable; - } else { - processOtherFromItem(joinItem); - leftTable = null; - } - } - - return mainTables; - } - - // ========== 和 TenantLineInnerInterceptor 存在差异的逻辑:关键,实现权限条件的拼接 ========== - - /** - * 处理条件 - * - * @param currentExpression 当前 where 条件 - * @param table 单个表 - */ - protected Expression builderExpression(Expression currentExpression, Table table) { - return this.builderExpression(currentExpression, Collections.singletonList(table)); - } - - /** - * 处理条件 - * - * @param currentExpression 当前 where 条件 - * @param tables 多个表 - */ - protected Expression builderExpression(Expression currentExpression, List
tables) { - // 没有表需要处理直接返回 - if (CollectionUtils.isEmpty(tables)) { - return currentExpression; - } - - // 第一步,获得 Table 对应的数据权限条件 - Expression dataPermissionExpression = null; - for (Table table : tables) { - // 构建每个表的权限 Expression 条件 - Expression expression = buildDataPermissionExpression(table); - if (expression == null) { - continue; - } - // 合并到 dataPermissionExpression 中 - dataPermissionExpression = dataPermissionExpression == null ? expression - : new AndExpression(dataPermissionExpression, expression); - } - - // 第二步,合并多个 Expression 条件 - if (dataPermissionExpression == null) { - return currentExpression; - } - if (currentExpression == null) { - return dataPermissionExpression; - } - // ① 如果表达式为 Or,则需要 (currentExpression) AND dataPermissionExpression - if (currentExpression instanceof OrExpression) { - return new AndExpression(new Parenthesis(currentExpression), dataPermissionExpression); - } - // ② 如果表达式为 And,则直接返回 where AND dataPermissionExpression - return new AndExpression(currentExpression, dataPermissionExpression); - } - - /** - * 构建指定表的数据权限的 Expression 过滤条件 - * - * @param table 表 - * @return Expression 过滤条件 - */ - private Expression buildDataPermissionExpression(Table table) { - // 生成条件 - Expression allExpression = null; - for (DataPermissionRule rule : ContextHolder.getRules()) { - // 判断表名是否匹配 - String tableName = MyBatisUtils.getTableName(table); - if (!rule.getTableNames().contains(tableName)) { - continue; - } - // 如果有匹配的规则,说明可重写。 - // 为什么不是有 allExpression 非空才重写呢?在生成 column = value 过滤条件时,会因为 value 不存在,导致未重写。 - // 这样导致第一次无 value,被标记成无需重写;但是第二次有 value,此时会需要重写。 - ContextHolder.setRewrite(true); - - // 单条规则的条件 - Expression oneExpress = rule.getExpression(tableName, table.getAlias()); - if (oneExpress == null){ - continue; - } - // 拼接到 allExpression 中 - allExpression = allExpression == null ? oneExpress - : new AndExpression(allExpression, oneExpress); - } - - return allExpression; - } - - /** - * 判断 SQL 是否重写。如果没有重写,则添加到 {@link MappedStatementCache} 中 - * - * @param ms MappedStatement - */ - private void addMappedStatementCache(MappedStatement ms) { - if (ContextHolder.getRewrite()) { - return; - } - // 无重写,进行添加 - mappedStatementCache.addNoRewritable(ms, ContextHolder.getRules()); - } - - /** - * SQL 解析上下文,方便透传 {@link DataPermissionRule} 规则 - * - * @author 芋道源码 - */ - static final class ContextHolder { - - /** - * 该 {@link MappedStatement} 对应的规则 - */ - private static final ThreadLocal> RULES = ThreadLocal.withInitial(Collections::emptyList); - /** - * SQL 是否进行重写 - */ - private static final ThreadLocal REWRITE = ThreadLocal.withInitial(() -> Boolean.FALSE); - - public static void init(List rules) { - RULES.set(rules); - REWRITE.set(false); - } - - public static void clear() { - RULES.remove(); - REWRITE.remove(); - } - - public static boolean getRewrite() { - return REWRITE.get(); - } - - public static void setRewrite(boolean rewrite) { - REWRITE.set(rewrite); - } - - public static List getRules() { - return RULES.get(); - } - - } - - /** - * {@link MappedStatement} 缓存 - * 目前主要用于,记录 {@link DataPermissionRule} 是否对指定 {@link MappedStatement} 无效 - * 如果无效,则可以避免 SQL 的解析,加快速度 - * - * @author 芋道源码 - */ - static final class MappedStatementCache { - - /** - * 指定数据权限规则,对指定 MappedStatement 无需重写(不生效)的缓存 - * - * value:{@link MappedStatement#getId()} 编号 - */ - @Getter - private final Map, Set> noRewritableMappedStatements = new ConcurrentHashMap<>(); - - /** - * 判断是否无需重写 - * ps:虽然有点中文式英语,但是容易读懂即可 - * - * @param ms MappedStatement - * @param rules 数据权限规则数组 - * @return 是否无需重写 - */ - public boolean noRewritable(MappedStatement ms, List rules) { - // 如果规则为空,说明无需重写 - if (CollUtil.isEmpty(rules)) { - return true; - } - // 任一规则不在 noRewritableMap 中,则说明可能需要重写 - for (DataPermissionRule rule : rules) { - Set mappedStatementIds = noRewritableMappedStatements.get(rule.getClass()); - if (!CollUtil.contains(mappedStatementIds, ms.getId())) { - return false; - } - } - return true; - } - - /** - * 添加无需重写的 MappedStatement - * - * @param ms MappedStatement - * @param rules 数据权限规则数组 - */ - public void addNoRewritable(MappedStatement ms, List rules) { - for (DataPermissionRule rule : rules) { - Set mappedStatementIds = noRewritableMappedStatements.get(rule.getClass()); - if (CollUtil.isNotEmpty(mappedStatementIds)) { - mappedStatementIds.add(ms.getId()); - } else { - noRewritableMappedStatements.put(rule.getClass(), SetUtils.asSet(ms.getId())); - } - } - } - - /** - * 清空缓存 - * 目前主要提供给单元测试 - */ - public void clear() { - noRewritableMappedStatements.clear(); - } - - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest.java b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest.java deleted file mode 100644 index 145360789..000000000 --- a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest.java +++ /dev/null @@ -1,190 +0,0 @@ -package cn.iocoder.yudao.framework.datapermission.core.db; - -import cn.iocoder.yudao.framework.common.util.collection.SetUtils; -import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule; -import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory; -import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import com.baomidou.mybatisplus.core.toolkit.PluginUtils; -import net.sf.jsqlparser.expression.Alias; -import net.sf.jsqlparser.expression.Expression; -import net.sf.jsqlparser.expression.LongValue; -import net.sf.jsqlparser.expression.operators.relational.EqualsTo; -import net.sf.jsqlparser.schema.Column; -import org.apache.ibatis.executor.Executor; -import org.apache.ibatis.executor.statement.StatementHandler; -import org.apache.ibatis.mapping.BoundSql; -import org.apache.ibatis.mapping.MappedStatement; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockedStatic; - -import java.sql.Connection; -import java.util.*; - -import static java.util.Collections.singletonList; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -/** - * {@link DataPermissionDatabaseInterceptor} 的单元测试 - * 主要测试 {@link DataPermissionDatabaseInterceptor#beforePrepare(StatementHandler, Connection, Integer)} - * 和 {@link DataPermissionDatabaseInterceptor#beforeUpdate(Executor, MappedStatement, Object)} - * 以及在这个过程中,ContextHolder 和 MappedStatementCache - * - * @author 芋道源码 - */ -public class DataPermissionDatabaseInterceptorTest extends BaseMockitoUnitTest { - - @InjectMocks - private DataPermissionDatabaseInterceptor interceptor; - - @Mock - private DataPermissionRuleFactory ruleFactory; - - @BeforeEach - public void setUp() { - // 清理上下文 - DataPermissionDatabaseInterceptor.ContextHolder.clear(); - // 清空缓存 - interceptor.getMappedStatementCache().clear(); - } - - @Test // 不存在规则,且不匹配 - public void testBeforeQuery_withoutRule() { - try (MockedStatic pluginUtilsMock = mockStatic(PluginUtils.class)) { - // 准备参数 - MappedStatement mappedStatement = mock(MappedStatement.class); - BoundSql boundSql = mock(BoundSql.class); - - // 调用 - interceptor.beforeQuery(null, mappedStatement, null, null, null, boundSql); - // 断言 - pluginUtilsMock.verify(() -> PluginUtils.mpBoundSql(boundSql), never()); - } - } - - @Test // 存在规则,且不匹配 - public void testBeforeQuery_withMatchRule() { - try (MockedStatic pluginUtilsMock = mockStatic(PluginUtils.class)) { - // 准备参数 - MappedStatement mappedStatement = mock(MappedStatement.class); - BoundSql boundSql = mock(BoundSql.class); - // mock 方法(数据权限) - when(ruleFactory.getDataPermissionRule(same(mappedStatement.getId()))) - .thenReturn(singletonList(new DeptDataPermissionRule())); - // mock 方法(MPBoundSql) - PluginUtils.MPBoundSql mpBs = mock(PluginUtils.MPBoundSql.class); - pluginUtilsMock.when(() -> PluginUtils.mpBoundSql(same(boundSql))).thenReturn(mpBs); - // mock 方法(SQL) - String sql = "select * from t_user where id = 1"; - when(mpBs.sql()).thenReturn(sql); - // 针对 ContextHolder 和 MappedStatementCache 暂时不 mock,主要想校验过程中,数据是否正确 - - // 调用 - interceptor.beforeQuery(null, mappedStatement, null, null, null, boundSql); - // 断言 - verify(mpBs, times(1)).sql( - eq("SELECT * FROM t_user WHERE id = 1 AND t_user.dept_id = 100")); - // 断言缓存 - assertTrue(interceptor.getMappedStatementCache().getNoRewritableMappedStatements().isEmpty()); - } - } - - @Test // 存在规则,但不匹配 - public void testBeforeQuery_withoutMatchRule() { - try (MockedStatic pluginUtilsMock = mockStatic(PluginUtils.class)) { - // 准备参数 - MappedStatement mappedStatement = mock(MappedStatement.class); - BoundSql boundSql = mock(BoundSql.class); - // mock 方法(数据权限) - when(ruleFactory.getDataPermissionRule(same(mappedStatement.getId()))) - .thenReturn(singletonList(new DeptDataPermissionRule())); - // mock 方法(MPBoundSql) - PluginUtils.MPBoundSql mpBs = mock(PluginUtils.MPBoundSql.class); - pluginUtilsMock.when(() -> PluginUtils.mpBoundSql(same(boundSql))).thenReturn(mpBs); - // mock 方法(SQL) - String sql = "select * from t_role where id = 1"; - when(mpBs.sql()).thenReturn(sql); - // 针对 ContextHolder 和 MappedStatementCache 暂时不 mock,主要想校验过程中,数据是否正确 - - // 调用 - interceptor.beforeQuery(null, mappedStatement, null, null, null, boundSql); - // 断言 - verify(mpBs, times(1)).sql( - eq("SELECT * FROM t_role WHERE id = 1")); - // 断言缓存 - assertFalse(interceptor.getMappedStatementCache().getNoRewritableMappedStatements().isEmpty()); - } - } - - @Test - public void testAddNoRewritable() { - // 准备参数 - MappedStatement ms = mock(MappedStatement.class); - List rules = singletonList(new DeptDataPermissionRule()); - // mock 方法 - when(ms.getId()).thenReturn("selectById"); - - // 调用 - interceptor.getMappedStatementCache().addNoRewritable(ms, rules); - // 断言 - Map, Set> noRewritableMappedStatements = - interceptor.getMappedStatementCache().getNoRewritableMappedStatements(); - assertEquals(1, noRewritableMappedStatements.size()); - assertEquals(SetUtils.asSet("selectById"), noRewritableMappedStatements.get(DeptDataPermissionRule.class)); - } - - @Test - public void testNoRewritable() { - // 准备参数 - MappedStatement ms = mock(MappedStatement.class); - // mock 方法 - when(ms.getId()).thenReturn("selectById"); - // mock 数据 - List rules = singletonList(new DeptDataPermissionRule()); - interceptor.getMappedStatementCache().addNoRewritable(ms, rules); - - // 场景一,rules 为空 - assertTrue(interceptor.getMappedStatementCache().noRewritable(ms, null)); - // 场景二,rules 非空,可重写 - assertFalse(interceptor.getMappedStatementCache().noRewritable(ms, singletonList(new EmptyDataPermissionRule()))); - // 场景三,rule 非空,不可重写 - assertTrue(interceptor.getMappedStatementCache().noRewritable(ms, rules)); - } - - private static class DeptDataPermissionRule implements DataPermissionRule { - - private static final String COLUMN = "dept_id"; - - @Override - public Set getTableNames() { - return SetUtils.asSet("t_user"); - } - - @Override - public Expression getExpression(String tableName, Alias tableAlias) { - Column column = MyBatisUtils.buildColumn(tableName, tableAlias, COLUMN); - LongValue value = new LongValue(100L); - return new EqualsTo(column, value); - } - - } - - private static class EmptyDataPermissionRule implements DataPermissionRule { - - @Override - public Set getTableNames() { - return Collections.emptySet(); - } - - @Override - public Expression getExpression(String tableName, Alias tableAlias) { - return null; - } - - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest2.java b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest2.java deleted file mode 100644 index b8cad13cf..000000000 --- a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest2.java +++ /dev/null @@ -1,533 +0,0 @@ -package cn.iocoder.yudao.framework.datapermission.core.db; - -import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule; -import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory; -import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import net.sf.jsqlparser.expression.Alias; -import net.sf.jsqlparser.expression.Expression; -import net.sf.jsqlparser.expression.LongValue; -import net.sf.jsqlparser.expression.operators.relational.EqualsTo; -import net.sf.jsqlparser.expression.operators.relational.ExpressionList; -import net.sf.jsqlparser.expression.operators.relational.InExpression; -import net.sf.jsqlparser.schema.Column; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -import java.util.Arrays; -import java.util.Set; - -import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * {@link DataPermissionDatabaseInterceptor} 的单元测试 - * 主要复用了 MyBatis Plus 的 TenantLineInnerInterceptorTest 的单元测试 - * 不过它的单元测试不是很规范,考虑到是复用的,所以暂时不进行修改~ - * - * @author 芋道源码 - */ -public class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest { - - @InjectMocks - private DataPermissionDatabaseInterceptor interceptor; - - @Mock - private DataPermissionRuleFactory ruleFactory; - - @BeforeEach - public void setUp() { - // 租户的数据权限规则 - DataPermissionRule tenantRule = new DataPermissionRule() { - - private static final String COLUMN = "tenant_id"; - - @Override - public Set getTableNames() { - return asSet("entity", "entity1", "entity2", "entity3", "t1", "t2", "sys_dict_item", // 支持 MyBatis Plus 的单元测试 - "t_user", "t_role"); // 满足自己的单元测试 - } - - @Override - public Expression getExpression(String tableName, Alias tableAlias) { - Column column = MyBatisUtils.buildColumn(tableName, tableAlias, COLUMN); - LongValue value = new LongValue(1L); - return new EqualsTo(column, value); - } - - }; - // 部门的数据权限规则 - DataPermissionRule deptRule = new DataPermissionRule() { - - private static final String COLUMN = "dept_id"; - - @Override - public Set getTableNames() { - return asSet("t_user"); // 满足自己的单元测试 - } - - @Override - public Expression getExpression(String tableName, Alias tableAlias) { - Column column = MyBatisUtils.buildColumn(tableName, tableAlias, COLUMN); - ExpressionList values = new ExpressionList(new LongValue(10L), - new LongValue(20L)); - return new InExpression(column, values); - } - - }; - // 设置到上下文,保证 - DataPermissionDatabaseInterceptor.ContextHolder.init(Arrays.asList(tenantRule, deptRule)); - } - - @Test - void delete() { - assertSql("delete from entity where id = ?", - "DELETE FROM entity WHERE id = ? AND entity.tenant_id = 1"); - } - - @Test - void update() { - assertSql("update entity set name = ? where id = ?", - "UPDATE entity SET name = ? WHERE id = ? AND entity.tenant_id = 1"); - } - - @Test - void selectSingle() { - // 单表 - assertSql("select * from entity where id = ?", - "SELECT * FROM entity WHERE id = ? AND entity.tenant_id = 1"); - - assertSql("select * from entity where id = ? or name = ?", - "SELECT * FROM entity WHERE (id = ? OR name = ?) AND entity.tenant_id = 1"); - - assertSql("SELECT * FROM entity WHERE (id = ? OR name = ?)", - "SELECT * FROM entity WHERE (id = ? OR name = ?) AND entity.tenant_id = 1"); - - /* not */ - assertSql("SELECT * FROM entity WHERE not (id = ? OR name = ?)", - "SELECT * FROM entity WHERE NOT (id = ? OR name = ?) AND entity.tenant_id = 1"); - } - - @Test - void selectSubSelectIn() { - /* in */ - assertSql("SELECT * FROM entity e WHERE e.id IN (select e1.id from entity1 e1 where e1.id = ?)", - "SELECT * FROM entity e WHERE e.id IN (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1"); - // 在最前 - assertSql("SELECT * FROM entity e WHERE e.id IN " + - "(select e1.id from entity1 e1 where e1.id = ?) and e.id = ?", - "SELECT * FROM entity e WHERE e.id IN " + - "(SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.id = ? AND e.tenant_id = 1"); - // 在最后 - assertSql("SELECT * FROM entity e WHERE e.id = ? and e.id IN " + - "(select e1.id from entity1 e1 where e1.id = ?)", - "SELECT * FROM entity e WHERE e.id = ? AND e.id IN " + - "(SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1"); - // 在中间 - assertSql("SELECT * FROM entity e WHERE e.id = ? and e.id IN " + - "(select e1.id from entity1 e1 where e1.id = ?) and e.id = ?", - "SELECT * FROM entity e WHERE e.id = ? AND e.id IN " + - "(SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.id = ? AND e.tenant_id = 1"); - } - - @Test - void selectSubSelectEq() { - /* = */ - assertSql("SELECT * FROM entity e WHERE e.id = (select e1.id from entity1 e1 where e1.id = ?)", - "SELECT * FROM entity e WHERE e.id = (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1"); - } - - @Test - void selectSubSelectInnerNotEq() { - /* inner not = */ - assertSql("SELECT * FROM entity e WHERE not (e.id = (select e1.id from entity1 e1 where e1.id = ?))", - "SELECT * FROM entity e WHERE NOT (e.id = (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1)) AND e.tenant_id = 1"); - - assertSql("SELECT * FROM entity e WHERE not (e.id = (select e1.id from entity1 e1 where e1.id = ?) and e.id = ?)", - "SELECT * FROM entity e WHERE NOT (e.id = (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.id = ?) AND e.tenant_id = 1"); - } - - @Test - void selectSubSelectExists() { - /* EXISTS */ - assertSql("SELECT * FROM entity e WHERE EXISTS (select e1.id from entity1 e1 where e1.id = ?)", - "SELECT * FROM entity e WHERE EXISTS (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1"); - - - /* NOT EXISTS */ - assertSql("SELECT * FROM entity e WHERE NOT EXISTS (select e1.id from entity1 e1 where e1.id = ?)", - "SELECT * FROM entity e WHERE NOT EXISTS (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1"); - } - - @Test - void selectSubSelect() { - /* >= */ - assertSql("SELECT * FROM entity e WHERE e.id >= (select e1.id from entity1 e1 where e1.id = ?)", - "SELECT * FROM entity e WHERE e.id >= (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1"); - - - /* <= */ - assertSql("SELECT * FROM entity e WHERE e.id <= (select e1.id from entity1 e1 where e1.id = ?)", - "SELECT * FROM entity e WHERE e.id <= (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1"); - - - /* <> */ - assertSql("SELECT * FROM entity e WHERE e.id <> (select e1.id from entity1 e1 where e1.id = ?)", - "SELECT * FROM entity e WHERE e.id <> (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1"); - } - - @Test - void selectFromSelect() { - assertSql("SELECT * FROM (select e.id from entity e WHERE e.id = (select e1.id from entity1 e1 where e1.id = ?))", - "SELECT * FROM (SELECT e.id FROM entity e WHERE e.id = (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1)"); - } - - @Test - void selectBodySubSelect() { - assertSql("select t1.col1,(select t2.col2 from t2 t2 where t1.col1=t2.col1) from t1 t1", - "SELECT t1.col1, (SELECT t2.col2 FROM t2 t2 WHERE t1.col1 = t2.col1 AND t2.tenant_id = 1) FROM t1 t1 WHERE t1.tenant_id = 1"); - } - - @Test - void selectLeftJoin() { - // left join - assertSql("SELECT * FROM entity e " + - "left join entity1 e1 on e1.id = e.id " + - "WHERE e.id = ? OR e.name = ?", - "SELECT * FROM entity e " + - "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " + - "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1"); - - assertSql("SELECT * FROM entity e " + - "left join entity1 e1 on e1.id = e.id " + - "WHERE (e.id = ? OR e.name = ?)", - "SELECT * FROM entity e " + - "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " + - "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1"); - - assertSql("SELECT * FROM entity e " + - "left join entity1 e1 on e1.id = e.id " + - "left join entity2 e2 on e1.id = e2.id", - "SELECT * FROM entity e " + - "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " + - "LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1 " + - "WHERE e.tenant_id = 1"); - } - - @Test - void selectRightJoin() { - // right join - assertSql("SELECT * FROM entity e " + - "right join entity1 e1 on e1.id = e.id", - "SELECT * FROM entity e " + - "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " + - "WHERE e1.tenant_id = 1"); - - assertSql("SELECT * FROM with_as_1 e " + - "right join entity1 e1 on e1.id = e.id", - "SELECT * FROM with_as_1 e " + - "RIGHT JOIN entity1 e1 ON e1.id = e.id " + - "WHERE e1.tenant_id = 1"); - - assertSql("SELECT * FROM entity e " + - "right join entity1 e1 on e1.id = e.id " + - "WHERE e.id = ? OR e.name = ?", - "SELECT * FROM entity e " + - "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " + - "WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1"); - - assertSql("SELECT * FROM entity e " + - "right join entity1 e1 on e1.id = e.id " + - "right join entity2 e2 on e1.id = e2.id ", - "SELECT * FROM entity e " + - "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " + - "RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1 " + - "WHERE e2.tenant_id = 1"); - } - - @Test - void selectMixJoin() { - assertSql("SELECT * FROM entity e " + - "right join entity1 e1 on e1.id = e.id " + - "left join entity2 e2 on e1.id = e2.id", - "SELECT * FROM entity e " + - "RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 " + - "LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1 " + - "WHERE e1.tenant_id = 1"); - - assertSql("SELECT * FROM entity e " + - "left join entity1 e1 on e1.id = e.id " + - "right join entity2 e2 on e1.id = e2.id", - "SELECT * FROM entity e " + - "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " + - "RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1 " + - "WHERE e2.tenant_id = 1"); - - assertSql("SELECT * FROM entity e " + - "left join entity1 e1 on e1.id = e.id " + - "inner join entity2 e2 on e1.id = e2.id", - "SELECT * FROM entity e " + - "LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 " + - "INNER JOIN entity2 e2 ON e1.id = e2.id AND e.tenant_id = 1 AND e2.tenant_id = 1"); - } - - - @Test - void selectJoinSubSelect() { - assertSql("select * from (select * from entity) e1 " + - "left join entity2 e2 on e1.id = e2.id", - "SELECT * FROM (SELECT * FROM entity WHERE entity.tenant_id = 1) e1 " + - "LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1"); - - assertSql("select * from entity1 e1 " + - "left join (select * from entity2) e2 " + - "on e1.id = e2.id", - "SELECT * FROM entity1 e1 " + - "LEFT JOIN (SELECT * FROM entity2 WHERE entity2.tenant_id = 1) e2 " + - "ON e1.id = e2.id " + - "WHERE e1.tenant_id = 1"); - } - - @Test - void selectSubJoin() { - - assertSql("select * FROM " + - "(entity1 e1 right JOIN entity2 e2 ON e1.id = e2.id)", - "SELECT * FROM " + - "(entity1 e1 RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1) " + - "WHERE e2.tenant_id = 1"); - - assertSql("select * FROM " + - "(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id)", - "SELECT * FROM " + - "(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " + - "WHERE e1.tenant_id = 1"); - - - assertSql("select * FROM " + - "(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id) " + - "right join entity3 e3 on e1.id = e3.id", - "SELECT * FROM " + - "(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " + - "RIGHT JOIN entity3 e3 ON e1.id = e3.id AND e1.tenant_id = 1 " + - "WHERE e3.tenant_id = 1"); - - - assertSql("select * FROM entity e " + - "LEFT JOIN (entity1 e1 right join entity2 e2 ON e1.id = e2.id) " + - "on e.id = e2.id", - "SELECT * FROM entity e " + - "LEFT JOIN (entity1 e1 RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1) " + - "ON e.id = e2.id AND e2.tenant_id = 1 " + - "WHERE e.tenant_id = 1"); - - assertSql("select * FROM entity e " + - "LEFT JOIN (entity1 e1 left join entity2 e2 ON e1.id = e2.id) " + - "on e.id = e2.id", - "SELECT * FROM entity e " + - "LEFT JOIN (entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " + - "ON e.id = e2.id AND e1.tenant_id = 1 " + - "WHERE e.tenant_id = 1"); - - assertSql("select * FROM entity e " + - "RIGHT JOIN (entity1 e1 left join entity2 e2 ON e1.id = e2.id) " + - "on e.id = e2.id", - "SELECT * FROM entity e " + - "RIGHT JOIN (entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) " + - "ON e.id = e2.id AND e.tenant_id = 1 " + - "WHERE e1.tenant_id = 1"); - } - - - @Test - void selectLeftJoinMultipleTrailingOn() { - // 多个 on 尾缀的 - assertSql("SELECT * FROM entity e " + - "LEFT JOIN entity1 e1 " + - "LEFT JOIN entity2 e2 ON e2.id = e1.id " + - "ON e1.id = e.id " + - "WHERE (e.id = ? OR e.NAME = ?)", - "SELECT * FROM entity e " + - "LEFT JOIN entity1 e1 " + - "LEFT JOIN entity2 e2 ON e2.id = e1.id AND e2.tenant_id = 1 " + - "ON e1.id = e.id AND e1.tenant_id = 1 " + - "WHERE (e.id = ? OR e.NAME = ?) AND e.tenant_id = 1"); - - assertSql("SELECT * FROM entity e " + - "LEFT JOIN entity1 e1 " + - "LEFT JOIN with_as_A e2 ON e2.id = e1.id " + - "ON e1.id = e.id " + - "WHERE (e.id = ? OR e.NAME = ?)", - "SELECT * FROM entity e " + - "LEFT JOIN entity1 e1 " + - "LEFT JOIN with_as_A e2 ON e2.id = e1.id " + - "ON e1.id = e.id AND e1.tenant_id = 1 " + - "WHERE (e.id = ? OR e.NAME = ?) AND e.tenant_id = 1"); - } - - @Test - void selectInnerJoin() { - // inner join - assertSql("SELECT * FROM entity e " + - "inner join entity1 e1 on e1.id = e.id " + - "WHERE e.id = ? OR e.name = ?", - "SELECT * FROM entity e " + - "INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e1.tenant_id = 1 " + - "WHERE e.id = ? OR e.name = ?"); - - assertSql("SELECT * FROM entity e " + - "inner join entity1 e1 on e1.id = e.id " + - "WHERE (e.id = ? OR e.name = ?)", - "SELECT * FROM entity e " + - "INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e1.tenant_id = 1 " + - "WHERE (e.id = ? OR e.name = ?)"); - - // 隐式内连接 - assertSql("SELECT * FROM entity,entity1 " + - "WHERE entity.id = entity1.id", - "SELECT * FROM entity, entity1 " + - "WHERE entity.id = entity1.id AND entity.tenant_id = 1 AND entity1.tenant_id = 1"); - - // 隐式内连接 - assertSql("SELECT * FROM entity a, with_as_entity1 b " + - "WHERE a.id = b.id", - "SELECT * FROM entity a, with_as_entity1 b " + - "WHERE a.id = b.id AND a.tenant_id = 1"); - - assertSql("SELECT * FROM with_as_entity a, with_as_entity1 b " + - "WHERE a.id = b.id", - "SELECT * FROM with_as_entity a, with_as_entity1 b " + - "WHERE a.id = b.id"); - - // SubJoin with 隐式内连接 - assertSql("SELECT * FROM (entity,entity1) " + - "WHERE entity.id = entity1.id", - "SELECT * FROM (entity, entity1) " + - "WHERE entity.id = entity1.id " + - "AND entity.tenant_id = 1 AND entity1.tenant_id = 1"); - - assertSql("SELECT * FROM ((entity,entity1),entity2) " + - "WHERE entity.id = entity1.id and entity.id = entity2.id", - "SELECT * FROM ((entity, entity1), entity2) " + - "WHERE entity.id = entity1.id AND entity.id = entity2.id " + - "AND entity.tenant_id = 1 AND entity1.tenant_id = 1 AND entity2.tenant_id = 1"); - - assertSql("SELECT * FROM (entity,(entity1,entity2)) " + - "WHERE entity.id = entity1.id and entity.id = entity2.id", - "SELECT * FROM (entity, (entity1, entity2)) " + - "WHERE entity.id = entity1.id AND entity.id = entity2.id " + - "AND entity.tenant_id = 1 AND entity1.tenant_id = 1 AND entity2.tenant_id = 1"); - - // 沙雕的括号写法 - assertSql("SELECT * FROM (((entity,entity1))) " + - "WHERE entity.id = entity1.id", - "SELECT * FROM (((entity, entity1))) " + - "WHERE entity.id = entity1.id " + - "AND entity.tenant_id = 1 AND entity1.tenant_id = 1"); - - } - - - @Test - void selectWithAs() { - assertSql("with with_as_A as (select * from entity) select * from with_as_A", - "WITH with_as_A AS (SELECT * FROM entity WHERE entity.tenant_id = 1) SELECT * FROM with_as_A"); - } - - - @Test - void selectIgnoreTable() { - assertSql(" SELECT dict.dict_code, item.item_text AS \"text\", item.item_value AS \"value\" FROM sys_dict_item item INNER JOIN sys_dict dict ON dict.id = item.dict_id WHERE dict.dict_code IN (1, 2, 3) AND item.item_value IN (1, 2, 3)", - "SELECT dict.dict_code, item.item_text AS \"text\", item.item_value AS \"value\" FROM sys_dict_item item INNER JOIN sys_dict dict ON dict.id = item.dict_id AND item.tenant_id = 1 WHERE dict.dict_code IN (1, 2, 3) AND item.item_value IN (1, 2, 3)"); - } - - private void assertSql(String sql, String targetSql) { - assertEquals(targetSql, interceptor.parserSingle(sql, null)); - } - - - // ========== 额外的测试 ========== - - @Test - public void testSelectSingle() { - // 单表 - assertSql("select * from t_user where id = ?", - "SELECT * FROM t_user WHERE id = ? AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)"); - - assertSql("select * from t_user where id = ? or name = ?", - "SELECT * FROM t_user WHERE (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)"); - - assertSql("SELECT * FROM t_user WHERE (id = ? OR name = ?)", - "SELECT * FROM t_user WHERE (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)"); - - /* not */ - assertSql("SELECT * FROM t_user WHERE not (id = ? OR name = ?)", - "SELECT * FROM t_user WHERE NOT (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)"); - } - - @Test - public void testSelectLeftJoin() { - // left join - assertSql("SELECT * FROM t_user e " + - "left join t_role e1 on e1.id = e.id " + - "WHERE e.id = ? OR e.name = ?", - "SELECT * FROM t_user e " + - "LEFT JOIN t_role e1 ON e1.id = e.id AND e1.tenant_id = 1 " + - "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1 AND e.dept_id IN (10, 20)"); - - // 条件 e.id = ? OR e.name = ? 带括号 - assertSql("SELECT * FROM t_user e " + - "left join t_role e1 on e1.id = e.id " + - "WHERE (e.id = ? OR e.name = ?)", - "SELECT * FROM t_user e " + - "LEFT JOIN t_role e1 ON e1.id = e.id AND e1.tenant_id = 1 " + - "WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1 AND e.dept_id IN (10, 20)"); - } - - @Test - public void testSelectRightJoin() { - // right join - assertSql("SELECT * FROM t_user e " + - "right join t_role e1 on e1.id = e.id " + - "WHERE e.id = ? OR e.name = ?", - "SELECT * FROM t_user e " + - "RIGHT JOIN t_role e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) " + - "WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1"); - - // 条件 e.id = ? OR e.name = ? 带括号 - assertSql("SELECT * FROM t_user e " + - "right join t_role e1 on e1.id = e.id " + - "WHERE (e.id = ? OR e.name = ?)", - "SELECT * FROM t_user e " + - "RIGHT JOIN t_role e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) " + - "WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1"); - } - - @Test - public void testSelectInnerJoin() { - // inner join - assertSql("SELECT * FROM t_user e " + - "inner join entity1 e1 on e1.id = e.id " + - "WHERE e.id = ? OR e.name = ?", - "SELECT * FROM t_user e " + - "INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) AND e1.tenant_id = 1 " + - "WHERE e.id = ? OR e.name = ?"); - - // 条件 e.id = ? OR e.name = ? 带括号 - assertSql("SELECT * FROM t_user e " + - "inner join entity1 e1 on e1.id = e.id " + - "WHERE (e.id = ? OR e.name = ?)", - "SELECT * FROM t_user e " + - "INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) AND e1.tenant_id = 1 " + - "WHERE (e.id = ? OR e.name = ?)"); - - // 没有 On 的 inner join - assertSql("SELECT * FROM entity,entity1 " + - "WHERE entity.id = entity1.id", - "SELECT * FROM entity, entity1 " + - "WHERE entity.id = entity1.id AND entity.tenant_id = 1 AND entity1.tenant_id = 1"); - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/SqlConstants.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/SqlConstants.java deleted file mode 100644 index d775f17c7..000000000 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/enums/SqlConstants.java +++ /dev/null @@ -1,21 +0,0 @@ -package cn.iocoder.yudao.framework.mybatis.core.enums; - -import com.baomidou.mybatisplus.annotation.DbType; - -/** - * SQL相关常量类 - * - * @author 芋道源码 - */ -public class SqlConstants { - - /** - * 数据库的类型 - */ - public static DbType DB_TYPE; - - public static void init(DbType dbType) { - DB_TYPE = dbType; - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/JsonLongSetTypeHandler.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/JsonLongSetTypeHandler.java deleted file mode 100644 index 052c7232e..000000000 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/JsonLongSetTypeHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.framework.mybatis.core.type; - -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; -import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler; -import com.fasterxml.jackson.core.type.TypeReference; - -import java.util.Set; - -/** - * 参考 {@link com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler} 实现 - * 在我们将字符串反序列化为 Set 并且泛型为 Long 时,如果每个元素的数值太小,会被处理成 Integer 类型,导致可能存在隐性的 BUG。 - * - * 例如说哦,SysUserDO 的 postIds 属性 - * - * @author 芋道源码 - */ -public class JsonLongSetTypeHandler extends AbstractJsonTypeHandler { - - private static final TypeReference> TYPE_REFERENCE = new TypeReference>(){}; - - @Override - protected Object parse(String json) { - return JsonUtils.parseObject(json, TYPE_REFERENCE); - } - - @Override - protected String toJson(Object obj) { - return JsonUtils.toJsonString(obj); - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-redis/pom.xml b/yudao-framework/yudao-spring-boot-starter-redis/pom.xml index 659bf60f3..3e059bac3 100644 --- a/yudao-framework/yudao-spring-boot-starter-redis/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-redis/pom.xml @@ -30,6 +30,7 @@ org.redisson redisson-spring-data-27 + org.springframework.boot spring-boot-starter-cache diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/annotations/PreAuthenticated.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/annotations/PreAuthenticated.java deleted file mode 100644 index efc85c678..000000000 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/annotations/PreAuthenticated.java +++ /dev/null @@ -1,17 +0,0 @@ -package cn.iocoder.yudao.framework.security.core.annotations; - -import java.lang.annotation.*; - -/** - * 声明用户需要登录 - * - * 为什么不使用 {@link org.springframework.security.access.prepost.PreAuthorize} 注解,原因是不通过时,抛出的是认证不通过,而不是未登录 - * - * @author 芋道源码 - */ -@Target({ElementType.METHOD}) -@Retention(RetentionPolicy.RUNTIME) -@Inherited -@Documented -public @interface PreAuthenticated { -} diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/aop/PreAuthenticatedAspect.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/aop/PreAuthenticatedAspect.java deleted file mode 100644 index 808afc393..000000000 --- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/aop/PreAuthenticatedAspect.java +++ /dev/null @@ -1,25 +0,0 @@ -package cn.iocoder.yudao.framework.security.core.aop; - -import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; -import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; - -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED; -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; - -@Aspect -@Slf4j -public class PreAuthenticatedAspect { - - @Around("@annotation(preAuthenticated)") - public Object around(ProceedingJoinPoint joinPoint, PreAuthenticated preAuthenticated) throws Throwable { - if (SecurityFrameworkUtils.getLoginUser() == null) { - throw exception(UNAUTHORIZED); - } - return joinPoint.proceed(); - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLogFrameworkService.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLogFrameworkService.java deleted file mode 100644 index 2f3c78f60..000000000 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLogFrameworkService.java +++ /dev/null @@ -1,19 +0,0 @@ -package cn.iocoder.yudao.framework.apilog.core.service; - -import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO; - -/** - * API 访问日志 Framework Service 接口 - * - * @author 芋道源码 - */ -public interface ApiAccessLogFrameworkService { - - /** - * 创建 API 访问日志 - * - * @param reqDTO API 访问日志 - */ - void createApiAccessLog(ApiAccessLogCreateReqDTO reqDTO); - -} diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLogFrameworkServiceImpl.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLogFrameworkServiceImpl.java deleted file mode 100644 index 8f8e34306..000000000 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLogFrameworkServiceImpl.java +++ /dev/null @@ -1,26 +0,0 @@ -package cn.iocoder.yudao.framework.apilog.core.service; - -import cn.iocoder.yudao.module.infra.api.logger.ApiAccessLogApi; -import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO; -import lombok.RequiredArgsConstructor; -import org.springframework.scheduling.annotation.Async; - -/** - * API 访问日志 Framework Service 实现类 - * - * 基于 {@link ApiAccessLogApi} 服务,记录访问日志 - * - * @author 芋道源码 - */ -@RequiredArgsConstructor -public class ApiAccessLogFrameworkServiceImpl implements ApiAccessLogFrameworkService { - - private final ApiAccessLogApi apiAccessLogApi; - - @Override - @Async - public void createApiAccessLog(ApiAccessLogCreateReqDTO reqDTO) { - apiAccessLogApi.createApiAccessLog(reqDTO); - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLogFrameworkService.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLogFrameworkService.java deleted file mode 100644 index 33bebb711..000000000 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLogFrameworkService.java +++ /dev/null @@ -1,19 +0,0 @@ -package cn.iocoder.yudao.framework.apilog.core.service; - -import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO; - -/** - * API 错误日志 Framework Service 接口 - * - * @author 芋道源码 - */ -public interface ApiErrorLogFrameworkService { - - /** - * 创建 API 错误日志 - * - * @param reqDTO API 错误日志 - */ - void createApiErrorLog(ApiErrorLogCreateReqDTO reqDTO); - -} diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLogFrameworkServiceImpl.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLogFrameworkServiceImpl.java deleted file mode 100644 index 32e4f8043..000000000 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLogFrameworkServiceImpl.java +++ /dev/null @@ -1,26 +0,0 @@ -package cn.iocoder.yudao.framework.apilog.core.service; - -import cn.iocoder.yudao.module.infra.api.logger.ApiErrorLogApi; -import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO; -import lombok.RequiredArgsConstructor; -import org.springframework.scheduling.annotation.Async; - -/** - * API 错误日志 Framework Service 实现类 - * - * 基于 {@link ApiErrorLogApi} 服务,记录错误日志 - * - * @author 芋道源码 - */ -@RequiredArgsConstructor -public class ApiErrorLogFrameworkServiceImpl implements ApiErrorLogFrameworkService { - - private final ApiErrorLogApi apiErrorLogApi; - - @Override - @Async - public void createApiErrorLog(ApiErrorLogCreateReqDTO reqDTO) { - apiErrorLogApi.createApiErrorLog(reqDTO); - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/NumberSerializer.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/NumberSerializer.java deleted file mode 100644 index f6ddd3f27..000000000 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/NumberSerializer.java +++ /dev/null @@ -1,37 +0,0 @@ -package cn.iocoder.yudao.framework.jackson.core.databind; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.annotation.JacksonStdImpl; - -import java.io.IOException; - -/** - * Long 序列化规则 - * - * 会将超长 long 值转换为 string,解决前端 JavaScript 最大安全整数是 2^53-1 的问题 - * - * @author 星语 - */ -@JacksonStdImpl -public class NumberSerializer extends com.fasterxml.jackson.databind.ser.std.NumberSerializer { - - private static final long MAX_SAFE_INTEGER = 9007199254740991L; - private static final long MIN_SAFE_INTEGER = -9007199254740991L; - - public static final NumberSerializer INSTANCE = new NumberSerializer(Number.class); - - public NumberSerializer(Class rawType) { - super(rawType); - } - - @Override - public void serialize(Number value, JsonGenerator gen, SerializerProvider serializers) throws IOException { - // 超出范围 序列化位字符串 - if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) { - super.serialize(value, gen, serializers); - } else { - gen.writeString(value.toString()); - } - } -} diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/TimestampLocalDateTimeDeserializer.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/TimestampLocalDateTimeDeserializer.java deleted file mode 100644 index 71a480fbf..000000000 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/TimestampLocalDateTimeDeserializer.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.iocoder.yudao.framework.jackson.core.databind; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; - -import java.io.IOException; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; - -/** - * 基于时间戳的 LocalDateTime 反序列化器 - * - * @author 老五 - */ -public class TimestampLocalDateTimeDeserializer extends JsonDeserializer { - - public static final TimestampLocalDateTimeDeserializer INSTANCE = new TimestampLocalDateTimeDeserializer(); - - @Override - public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - // 将 Long 时间戳,转换为 LocalDateTime 对象 - return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault()); - } - -} diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/TimestampLocalDateTimeSerializer.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/TimestampLocalDateTimeSerializer.java deleted file mode 100644 index e72c47bb8..000000000 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/jackson/core/databind/TimestampLocalDateTimeSerializer.java +++ /dev/null @@ -1,26 +0,0 @@ -package cn.iocoder.yudao.framework.jackson.core.databind; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -import java.io.IOException; -import java.time.LocalDateTime; -import java.time.ZoneId; - -/** - * 基于时间戳的 LocalDateTime 序列化器 - * - * @author 老五 - */ -public class TimestampLocalDateTimeSerializer extends JsonSerializer { - - public static final TimestampLocalDateTimeSerializer INSTANCE = new TimestampLocalDateTimeSerializer(); - - @Override - public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException { - // 将 LocalDateTime 对象,转换为 Long 时间戳 - gen.writeNumber(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); - } - -} diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java deleted file mode 100644 index 802b9d890..000000000 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmDeleteReasonEnum.java +++ /dev/null @@ -1,45 +0,0 @@ -package cn.iocoder.yudao.module.bpm.enums.task; - -import cn.hutool.core.util.StrUtil; -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * 流程实例/任务的删除原因枚举 - * - * @author 芋道源码 - */ -@Getter -@AllArgsConstructor -public enum BpmDeleteReasonEnum { - - // ========== 流程实例的独有原因 ========== - - REJECT_TASK("审批不通过任务,原因:{}"), // 场景:用户审批不通过任务。修改文案时,需要注意 isRejectReason 方法 - CANCEL_PROCESS_INSTANCE_BY_START_USER("用户主动取消流程,原因:{}"), // 场景:用户主动取消流程 - CANCEL_PROCESS_INSTANCE_BY_ADMIN("管理员【{}】取消流程,原因:{}"), // 场景:管理员取消流程 - - // ========== 流程任务的独有原因 ========== - - CANCEL_BY_SYSTEM("系统自动取消"), // 场景:非常多,比如说:1)多任务审批已经满足条件,无需审批该任务;2)流程实例被取消,无需审批该任务;等等 - ; - - private final String reason; - - /** - * 格式化理由 - * - * @param args 参数 - * @return 理由 - */ - public String format(Object... args) { - return StrUtil.format(reason, args); - } - - // ========== 逻辑 ========== - - public static boolean isRejectReason(String reason) { - return StrUtil.startWith(reason, "审批不通过任务,原因:"); - } - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModeImportReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModeImportReqVO.java deleted file mode 100644 index 0b549ca9a..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModeImportReqVO.java +++ /dev/null @@ -1,19 +0,0 @@ -package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.springframework.web.multipart.MultipartFile; - -import javax.validation.constraints.NotNull; - -@Schema(description = "管理后台 - 流程模型的导入 Request VO 相比流程模型的新建来说,只是多了一个 bpmnFile 文件") -@Data -public class BpmModeImportReqVO extends BpmModelCreateReqVO { - - @Schema(description = "BPMN 文件", requiredMode = Schema.RequiredMode.REQUIRED) - @NotNull(message = "BPMN 文件不能为空") - private MultipartFile bpmnFile; - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelCreateReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelCreateReqVO.java deleted file mode 100644 index 39e4844f7..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelCreateReqVO.java +++ /dev/null @@ -1,25 +0,0 @@ -package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -import javax.validation.constraints.NotEmpty; - -@Schema(description = "管理后台 - 流程模型的创建 Request VO") -@Data -public class BpmModelCreateReqVO { - - @Schema(description = "流程标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "process_yudao") - @NotEmpty(message = "流程标识不能为空") - private String key; - - @Schema(description = "流程名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") - @NotEmpty(message = "流程名称不能为空") - private String name; - - @Schema(description = "流程描述", example = "我是描述") - private String description; - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelPageReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelPageReqVO.java deleted file mode 100644 index ec14b1aa8..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelPageReqVO.java +++ /dev/null @@ -1,23 +0,0 @@ -package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - - -@Schema(description = "管理后台 - 流程模型分页 Request VO") -@Data -public class BpmModelPageReqVO extends PageParam { - - @Schema(description = "标识,精准匹配", example = "process1641042089407") - private String key; - - @Schema(description = "名字,模糊匹配", example = "芋道") - private String name; - - @Schema(description = "流程分类", example = "1") - private String category; - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateReqVO.java deleted file mode 100644 index 231ead168..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/BpmModelUpdateReqVO.java +++ /dev/null @@ -1,47 +0,0 @@ -package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model; - -import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import org.hibernate.validator.constraints.URL; - -import javax.validation.constraints.NotEmpty; - -@Schema(description = "管理后台 - 流程模型的更新 Request VO") -@Data -public class BpmModelUpdateReqVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotEmpty(message = "编号不能为空") - private String id; - - @Schema(description = "流程名称", example = "芋道") - private String name; - - @Schema(description = "流程图标", example = "https://www.iocoder.cn/yudao.jpg") - @URL(message = "流程图标格式不正确") - private String icon; - - @Schema(description = "流程描述", example = "我是描述") - private String description; - - @Schema(description = "流程分类", example = "1") - private String category; - - @Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED) - private String bpmnXml; - - @Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1") - @InEnum(BpmModelFormTypeEnum.class) - private Integer formType; - @Schema(description = "表单编号-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", example = "1024") - private Long formId; - @Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", - example = "/bpm/oa/leave/create") - private String formCustomCreatePath; - @Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", - example = "/bpm/oa/leave/view") - private String formCustomViewPath; - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmActivityController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmActivityController.java deleted file mode 100644 index 44d0373f1..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmActivityController.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.iocoder.yudao.module.bpm.controller.admin.task; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO; -import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService; -import io.swagger.v3.oas.annotations.tags.Tag; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Operation; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import javax.annotation.Resource; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - -@Tag(name = "管理后台 - 流程活动实例") -@RestController -@RequestMapping("/bpm/activity") -@Validated -public class BpmActivityController { - - @Resource - private BpmActivityService activityService; - - @GetMapping("/list") - @Operation(summary = "生成指定流程实例的高亮流程图", - description = "只高亮进行中的任务。不过要注意,该接口暂时没用,通过前端的 ProcessViewer.vue 界面的 highlightDiagram 方法生成") - @Parameter(name = "processInstanceId", description = "流程实例的编号", required = true) - @PreAuthorize("@ss.hasPermission('bpm:task:query')") - public CommonResult> getActivityList( - @RequestParam("processInstanceId") String processInstanceId) { - return success(activityService.getActivityListByProcessInstanceId(processInstanceId)); - } -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmActivityConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmActivityConvert.java deleted file mode 100644 index 3cb674c1c..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmActivityConvert.java +++ /dev/null @@ -1,30 +0,0 @@ -package cn.iocoder.yudao.module.bpm.convert.task; - -import cn.iocoder.yudao.framework.common.util.date.DateUtils; -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO; -import org.flowable.engine.history.HistoricActivityInstance; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; -import org.mapstruct.factory.Mappers; - -import java.util.List; - -/** - * BPM 活动 Convert - * - * @author 芋道源码 - */ -@Mapper(uses = DateUtils.class) -public interface BpmActivityConvert { - - BpmActivityConvert INSTANCE = Mappers.getMapper(BpmActivityConvert.class); - - List convertList(List list); - - @Mappings({ - @Mapping(source = "activityId", target = "key"), - @Mapping(source = "activityType", target = "type") - }) - BpmActivityRespVO convert(HistoricActivityInstance bean); -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java deleted file mode 100644 index e3c439ed0..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategy.java +++ /dev/null @@ -1,46 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; - -import cn.iocoder.yudao.framework.common.util.string.StrUtils; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; -import cn.iocoder.yudao.module.system.api.dept.DeptApi; -import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; -import org.flowable.engine.delegate.DelegateExecution; -import org.springframework.stereotype.Component; - -import javax.annotation.Resource; -import java.util.List; -import java.util.Set; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; - -/** - * 部门的负责人 {@link BpmTaskCandidateStrategy} 实现类 - * - * @author kyle - */ -@Component -public class BpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrategy { - - @Resource - private DeptApi deptApi; - - @Override - public BpmTaskCandidateStrategyEnum getStrategy() { - return BpmTaskCandidateStrategyEnum.DEPT_LEADER; - } - - @Override - public void validateParam(String param) { - Set deptIds = StrUtils.splitToLongSet(param); - deptApi.validateDeptList(deptIds); - } - - @Override - public Set calculateUsers(DelegateExecution execution, String param) { - Set deptIds = StrUtils.splitToLongSet(param); - List depts = deptApi.getDeptList(deptIds); - return convertSet(depts, DeptRespDTO::getLeaderUserId); - } - -} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java deleted file mode 100644 index 78f1589e1..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategy.java +++ /dev/null @@ -1,49 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; - -import cn.iocoder.yudao.framework.common.util.string.StrUtils; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; -import cn.iocoder.yudao.module.system.api.dept.DeptApi; -import cn.iocoder.yudao.module.system.api.user.AdminUserApi; -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import org.flowable.engine.delegate.DelegateExecution; -import org.springframework.stereotype.Component; - -import javax.annotation.Resource; -import java.util.List; -import java.util.Set; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; - -/** - * 部门的成员 {@link BpmTaskCandidateStrategy} 实现类 - * - * @author kyle - */ -@Component -public class BpmTaskCandidateDeptMemberStrategy implements BpmTaskCandidateStrategy { - - @Resource - private DeptApi deptApi; - @Resource - private AdminUserApi adminUserApi; - - @Override - public BpmTaskCandidateStrategyEnum getStrategy() { - return BpmTaskCandidateStrategyEnum.DEPT_MEMBER; - } - - @Override - public void validateParam(String param) { - Set deptIds = StrUtils.splitToLongSet(param); - deptApi.validateDeptList(deptIds); - } - - @Override - public Set calculateUsers(DelegateExecution execution, String param) { - Set deptIds = StrUtils.splitToLongSet(param); - List users = adminUserApi.getUserListByDeptIds(deptIds); - return convertSet(users, AdminUserRespDTO::getId); - } - -} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategy.java deleted file mode 100644 index e0f9dabe5..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategy.java +++ /dev/null @@ -1,36 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; - -import cn.hutool.core.convert.Convert; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; -import org.flowable.engine.delegate.DelegateExecution; -import org.springframework.stereotype.Component; - -import java.util.Set; - -/** - * 流程表达式 {@link BpmTaskCandidateStrategy} 实现类 - * - * @author 芋道源码 - */ -@Component -public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrategy { - - @Override - public BpmTaskCandidateStrategyEnum getStrategy() { - return BpmTaskCandidateStrategyEnum.EXPRESSION; - } - - @Override - public void validateParam(String param) { - // do nothing 因为它基本做不了校验 - } - - @Override - public Set calculateUsers(DelegateExecution execution, String param) { - Object result = FlowableUtils.getExpressionValue(execution, param); - return Convert.toSet(Long.class, result); - } - -} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java deleted file mode 100644 index da1aa3944..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategy.java +++ /dev/null @@ -1,47 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; - -import cn.iocoder.yudao.framework.common.util.string.StrUtils; -import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; -import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService; -import org.flowable.engine.delegate.DelegateExecution; -import org.springframework.stereotype.Component; - -import javax.annotation.Resource; -import java.util.Collection; -import java.util.List; -import java.util.Set; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; - -/** - * 用户组 {@link BpmTaskCandidateStrategy} 实现类 - * - * @author kyle - */ -@Component -public class BpmTaskCandidateGroupStrategy implements BpmTaskCandidateStrategy { - - @Resource - private BpmUserGroupService userGroupService; - - @Override - public BpmTaskCandidateStrategyEnum getStrategy() { - return BpmTaskCandidateStrategyEnum.USER_GROUP; - } - - @Override - public void validateParam(String param) { - Set groupIds = StrUtils.splitToLongSet(param); - userGroupService.getUserGroupList(groupIds); - } - - @Override - public Set calculateUsers(DelegateExecution execution, String param) { - Set groupIds = StrUtils.splitToLongSet(param); - List groups = userGroupService.getUserGroupList(groupIds); - return convertSetByFlatMap(groups, BpmUserGroupDO::getUserIds, Collection::stream); - } - -} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java deleted file mode 100644 index ca259ffff..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategy.java +++ /dev/null @@ -1,49 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; - -import cn.iocoder.yudao.framework.common.util.string.StrUtils; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; -import cn.iocoder.yudao.module.system.api.dept.PostApi; -import cn.iocoder.yudao.module.system.api.user.AdminUserApi; -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import org.flowable.engine.delegate.DelegateExecution; -import org.springframework.stereotype.Component; - -import javax.annotation.Resource; -import java.util.List; -import java.util.Set; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; - -/** - * 岗位 {@link BpmTaskCandidateStrategy} 实现类 - * - * @author kyle - */ -@Component -public class BpmTaskCandidatePostStrategy implements BpmTaskCandidateStrategy { - - @Resource - private PostApi postApi; - @Resource - private AdminUserApi adminUserApi; - - @Override - public BpmTaskCandidateStrategyEnum getStrategy() { - return BpmTaskCandidateStrategyEnum.POST; - } - - @Override - public void validateParam(String param) { - Set postIds = StrUtils.splitToLongSet(param); - postApi.validPostList(postIds); - } - - @Override - public Set calculateUsers(DelegateExecution execution, String param) { - Set postIds = StrUtils.splitToLongSet(param); - List users = adminUserApi.getUserListByPostIds(postIds); - return convertSet(users, AdminUserRespDTO::getId); - } - -} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java deleted file mode 100644 index de51b3c77..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategy.java +++ /dev/null @@ -1,44 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; - -import cn.iocoder.yudao.framework.common.util.string.StrUtils; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; -import cn.iocoder.yudao.module.system.api.permission.PermissionApi; -import cn.iocoder.yudao.module.system.api.permission.RoleApi; -import org.flowable.engine.delegate.DelegateExecution; -import org.springframework.stereotype.Component; - -import javax.annotation.Resource; -import java.util.Set; - -/** - * 角色 {@link BpmTaskCandidateStrategy} 实现类 - * - * @author kyle - */ -@Component -public class BpmTaskCandidateRoleStrategy implements BpmTaskCandidateStrategy { - - @Resource - private RoleApi roleApi; - @Resource - private PermissionApi permissionApi; - - @Override - public BpmTaskCandidateStrategyEnum getStrategy() { - return BpmTaskCandidateStrategyEnum.ROLE; - } - - @Override - public void validateParam(String param) { - Set roleIds = StrUtils.splitToLongSet(param); - roleApi.validRoleList(roleIds); - } - - @Override - public Set calculateUsers(DelegateExecution execution, String param) { - Set roleIds = StrUtils.splitToLongSet(param); - return permissionApi.getUserRoleIdListByRoleIds(roleIds); - } - -} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java deleted file mode 100644 index 36750effb..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateStartUserSelectStrategy.java +++ /dev/null @@ -1,76 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.Assert; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; -import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; -import org.flowable.bpmn.model.BpmnModel; -import org.flowable.bpmn.model.UserTask; -import org.flowable.engine.delegate.DelegateExecution; -import org.flowable.engine.runtime.ProcessInstance; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Component; - -import javax.annotation.Resource; -import java.util.*; - -/** - * 发起人自选 {@link BpmTaskCandidateUserStrategy} 实现类 - * - * @author 芋道源码 - */ -@Component -public class BpmTaskCandidateStartUserSelectStrategy implements BpmTaskCandidateStrategy { - - @Resource - @Lazy // 延迟加载,避免循环依赖 - private BpmProcessInstanceService processInstanceService; - - @Override - public BpmTaskCandidateStrategyEnum getStrategy() { - return BpmTaskCandidateStrategyEnum.START_USER_SELECT; - } - - @Override - public void validateParam(String param) {} - - @Override - public Set calculateUsers(DelegateExecution execution, String param) { - ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId()); - Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId()); - Map> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance); - Assert.notNull(startUserSelectAssignees, "流程实例({}) 的发起人自选审批人不能为空", - execution.getProcessInstanceId()); - // 获得审批人 - List assignees = startUserSelectAssignees.get(execution.getCurrentActivityId()); - return new LinkedHashSet<>(assignees); - } - - @Override - public boolean isParamRequired() { - return false; - } - - /** - * 获得发起人自选审批人的 UserTask 列表 - * - * @param bpmnModel BPMN 模型 - * @return UserTask 列表 - */ - public static List getStartUserSelectUserTaskList(BpmnModel bpmnModel) { - if (bpmnModel == null) { - return null; - } - List userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class); - if (CollUtil.isEmpty(userTaskList)) { - return null; - } - userTaskList.removeIf(userTask -> !Objects.equals(BpmnModelUtils.parseCandidateStrategy(userTask), - BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy())); - return userTaskList; - } - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java deleted file mode 100644 index a6daec0e0..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategy.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; - -import cn.iocoder.yudao.framework.common.util.string.StrUtils; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; -import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; -import cn.iocoder.yudao.module.system.api.user.AdminUserApi; -import org.flowable.engine.delegate.DelegateExecution; -import org.springframework.stereotype.Component; - -import javax.annotation.Resource; -import java.util.Set; - -/** - * 用户 {@link BpmTaskCandidateStrategy} 实现类 - * - * @author kyle - */ -@Component -public class BpmTaskCandidateUserStrategy implements BpmTaskCandidateStrategy { - - @Resource - private AdminUserApi adminUserApi; - - @Override - public BpmTaskCandidateStrategyEnum getStrategy() { - return BpmTaskCandidateStrategyEnum.USER; - } - - @Override - public void validateParam(String param) { - adminUserApi.validateUserList(StrUtils.splitToLongSet(param)); - } - - @Override - public Set calculateUsers(DelegateExecution execution, String param) { - return StrUtils.splitToLongSet(param); - } - -} \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmConstants.java deleted file mode 100644 index e965d2281..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmConstants.java +++ /dev/null @@ -1,40 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums; - -import org.flowable.engine.runtime.ProcessInstance; - -/** - * BPM 通用常量 - * - * @author 芋道源码 - */ -public class BpmConstants { - - /** - * 流程实例的变量 - 状态 - * - * @see ProcessInstance#getProcessVariables() - */ - public static final String PROCESS_INSTANCE_VARIABLE_STATUS = "PROCESS_STATUS"; - /** - * 流程实例的变量 - 发起用户选择的审批人 Map - * - * @see ProcessInstance#getProcessVariables() - */ - public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES"; - - /** - * 任务的变量 - 状态 - * - * @see org.flowable.task.api.Task#getTaskLocalVariables() - */ - public static final String TASK_VARIABLE_STATUS = "TASK_STATUS"; - /** - * 任务的变量 - 理由 - * - * 例如说:审批通过、不通过的理由 - * - * @see org.flowable.task.api.Task#getTaskLocalVariables() - */ - public static final String TASK_VARIABLE_REASON = "TASK_REASON"; - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/dto/BpmModelMetaInfoRespDTO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/dto/BpmModelMetaInfoRespDTO.java deleted file mode 100644 index 8ec9dc64b..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/dto/BpmModelMetaInfoRespDTO.java +++ /dev/null @@ -1,46 +0,0 @@ -package cn.iocoder.yudao.module.bpm.service.definition.dto; - -import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum; -import lombok.Data; - -/** - * BPM 流程 MetaInfo Response DTO - * 主要用于 { Model#setMetaInfo(String)} 的存储 - * - * 最终,它的字段和 {@link cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO} 是一致的 - * - * @author 芋道源码 - */ -@Data -public class BpmModelMetaInfoRespDTO { - - /** - * 流程图标 - */ - private String icon; - /** - * 流程描述 - */ - private String description; - - /** - * 表单类型 - */ - private Integer formType; - /** - * 表单编号 - * 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时 - */ - private Long formId; - /** - * 自定义表单的提交路径,使用 Vue 的路由地址 - * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时 - */ - private String formCustomCreatePath; - /** - * 自定义表单的查看路径,使用 Vue 的路由地址 - * 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时 - */ - private String formCustomViewPath; - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java deleted file mode 100644 index 4bed16413..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityService.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.module.bpm.service.task; - -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO; -import org.flowable.engine.history.HistoricActivityInstance; - -import java.util.List; - -/** - * BPM 活动实例 Service 接口 - * - * @author 芋道源码 - */ -public interface BpmActivityService { - - /** - * 获得指定流程实例的活动实例列表 - * - * @param processInstanceId 流程实例的编号 - * @return 活动实例列表 - */ - List getActivityListByProcessInstanceId(String processInstanceId); - - /** - * 获得执行编号对应的活动实例 - * - * @param executionId 执行编号 - * @return 活动实例 - */ - List getHistoricActivityListByExecutionId(String executionId); - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityServiceImpl.java deleted file mode 100644 index c06445efa..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityServiceImpl.java +++ /dev/null @@ -1,40 +0,0 @@ -package cn.iocoder.yudao.module.bpm.service.task; - -import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO; -import cn.iocoder.yudao.module.bpm.convert.task.BpmActivityConvert; -import lombok.extern.slf4j.Slf4j; -import org.flowable.engine.HistoryService; -import org.flowable.engine.history.HistoricActivityInstance; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import javax.annotation.Resource; -import java.util.List; - - -/** - * BPM 活动实例 Service 实现类 - * - * @author 芋道源码 - */ -@Service -@Slf4j -@Validated -public class BpmActivityServiceImpl implements BpmActivityService { - - @Resource - private HistoryService historyService; - - @Override - public List getActivityListByProcessInstanceId(String processInstanceId) { - List activityList = historyService.createHistoricActivityInstanceQuery() - .processInstanceId(processInstanceId).list(); - return BpmActivityConvert.INSTANCE.convertList(activityList); - } - - @Override - public List getHistoricActivityListByExecutionId(String executionId) { - return historyService.createHistoricActivityInstanceQuery().executionId(executionId).list(); - } - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategyTest.java b/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategyTest.java deleted file mode 100644 index 9b89e2bd3..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptLeaderStrategyTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; - -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.system.api.dept.DeptApi; -import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -import java.util.Set; - -import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static java.util.Arrays.asList; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -public class BpmTaskCandidateDeptLeaderStrategyTest extends BaseMockitoUnitTest { - - @InjectMocks - private BpmTaskCandidateDeptLeaderStrategy strategy; - - @Mock - private DeptApi deptApi; - - @Test - public void testCalculateUsers() { - // 准备参数 - String param = "1,2"; - // mock 方法 - DeptRespDTO dept1 = randomPojo(DeptRespDTO.class, o -> o.setLeaderUserId(11L)); - DeptRespDTO dept2 = randomPojo(DeptRespDTO.class, o -> o.setLeaderUserId(22L)); - when(deptApi.getDeptList(eq(asSet(1L, 2L)))).thenReturn(asList(dept1, dept2)); - - // 调用 - Set results = strategy.calculateUsers(null, param); - // 断言 - assertEquals(asSet(11L, 22L), results); - } - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategyTest.java b/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategyTest.java deleted file mode 100644 index 4e72c0281..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateDeptMemberStrategyTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; - -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.system.api.user.AdminUserApi; -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -import java.util.List; -import java.util.Set; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -public class BpmTaskCandidateDeptMemberStrategyTest extends BaseMockitoUnitTest { - - @InjectMocks - private BpmTaskCandidateDeptMemberStrategy strategy; - - @Mock - private AdminUserApi adminUserApi; - - @Test - public void testCalculateUsers() { - // 准备参数 - String param = "11,22"; - // mock 方法 - List users = convertList(asSet(11L, 22L), - id -> new AdminUserRespDTO().setId(id)); - when(adminUserApi.getUserListByDeptIds(eq(asSet(11L, 22L)))).thenReturn(users); - - // 调用 - Set results = strategy.calculateUsers(null, param); - // 断言 - assertEquals(asSet(11L, 22L), results); - } - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategyTest.java b/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategyTest.java deleted file mode 100644 index eaa2ef7b5..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateExpressionStrategyTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; - -import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import org.flowable.engine.delegate.DelegateExecution; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.MockedStatic; - -import java.util.Set; - -import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; - -public class BpmTaskCandidateExpressionStrategyTest extends BaseMockitoUnitTest { - - @InjectMocks - private BpmTaskCandidateExpressionStrategy strategy; - - @Test - public void testCalculateUsers() { - try (MockedStatic flowableUtilMockedStatic = mockStatic(FlowableUtils.class)) { - // 准备参数 - String param = "1,2"; - DelegateExecution execution = mock(DelegateExecution.class); - // mock 方法 - flowableUtilMockedStatic.when(() -> FlowableUtils.getExpressionValue(same(execution), eq(param))) - .thenReturn(asSet(1L, 2L)); - - // 调用 - Set results = strategy.calculateUsers(execution, param); - // 断言 - assertEquals(asSet(1L, 2L), results); - } - } - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategyTest.java b/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategyTest.java deleted file mode 100644 index 8bd2c88af..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateGroupStrategyTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; - -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO; -import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -import java.util.Arrays; -import java.util.Set; - -import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -public class BpmTaskCandidateGroupStrategyTest extends BaseMockitoUnitTest { - - @InjectMocks - private BpmTaskCandidateGroupStrategy strategy; - - @Mock - private BpmUserGroupService userGroupService; - - @Test - public void testCalculateUsers() { - // 准备参数 - String param = "1,2"; - // mock 方法 - BpmUserGroupDO userGroup1 = randomPojo(BpmUserGroupDO.class, o -> o.setUserIds(asSet(11L, 12L))); - BpmUserGroupDO userGroup2 = randomPojo(BpmUserGroupDO.class, o -> o.setUserIds(asSet(21L, 22L))); - when(userGroupService.getUserGroupList(eq(asSet(1L, 2L)))).thenReturn(Arrays.asList(userGroup1, userGroup2)); - - // 调用 - Set results = strategy.calculateUsers(null, param); - // 断言 - assertEquals(asSet(11L, 12L, 21L, 22L), results); - } - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategyTest.java b/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategyTest.java deleted file mode 100644 index a30ce28eb..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidatePostStrategyTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; - -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.system.api.dept.PostApi; -import cn.iocoder.yudao.module.system.api.user.AdminUserApi; -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -import java.util.List; -import java.util.Set; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -public class BpmTaskCandidatePostStrategyTest extends BaseMockitoUnitTest { - - @InjectMocks - private BpmTaskCandidatePostStrategy strategy; - - @Mock - private PostApi postApi; - @Mock - private AdminUserApi adminUserApi; - - @Test - public void testCalculateUsers() { - // 准备参数 - String param = "1,2"; - // mock 方法 - List users = convertList(asSet(11L, 22L), - id -> new AdminUserRespDTO().setId(id)); - when(adminUserApi.getUserListByPostIds(eq(asSet(1L, 2L)))).thenReturn(users); - - // 调用 - Set results = strategy.calculateUsers(null, param); - // 断言 - assertEquals(asSet(11L, 22L), results); - } - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategyTest.java b/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategyTest.java deleted file mode 100644 index 7c4247bcd..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateRoleStrategyTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; - -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import cn.iocoder.yudao.module.system.api.permission.PermissionApi; -import cn.iocoder.yudao.module.system.api.permission.RoleApi; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; -import org.mockito.Mock; - -import java.util.Set; - -import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -public class BpmTaskCandidateRoleStrategyTest extends BaseMockitoUnitTest { - - @InjectMocks - private BpmTaskCandidateRoleStrategy strategy; - - @Mock - private RoleApi roleApi; - @Mock - private PermissionApi permissionApi; - - @Test - public void testCalculateUsers() { - // 准备参数 - String param = "1,2"; - // mock 方法 - when(permissionApi.getUserRoleIdListByRoleIds(eq(asSet(1L, 2L)))) - .thenReturn(asSet(11L, 22L)); - - // 调用 - Set results = strategy.calculateUsers(null, param); - // 断言 - assertEquals(asSet(11L, 22L), results); - } - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategyTest.java b/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategyTest.java deleted file mode 100644 index d71ccebfd..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/BpmTaskCandidateUserStrategyTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy; - -import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; -import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; - -import java.util.Set; - -import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet; -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class BpmTaskCandidateUserStrategyTest extends BaseMockitoUnitTest { - - @InjectMocks - private BpmTaskCandidateUserStrategy strategy; - - @Test - public void testCalculateUsers() { - // 准备参数 - String param = "1,2"; - - // 调用 - Set results = strategy.calculateUsers(null, param); - // 断言 - assertEquals(asSet(1L, 2L), results); - } - -} diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_schema/api/api.ts.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_schema/api/api.ts.vm deleted file mode 100644 index 48cd5422b..000000000 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_schema/api/api.ts.vm +++ /dev/null @@ -1,46 +0,0 @@ -import request from '@/config/axios' -#set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}") - -export interface ${simpleClassName}VO { - #foreach ($column in $columns) - #if ($column.createOperation || $column.updateOperation) - #if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal") - ${column.javaField}: number - #elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdatetime") - ${column.javaField}: Date - #else - ${column.javaField}: ${column.javaType.toLowerCase()} - #end - #end - #end -} - -// 查询${table.classComment}列表 -export const get${simpleClassName}Page = async (params) => { - return await request.get({ url: '${baseURL}/page', params }) -} - -// 查询${table.classComment}详情 -export const get${simpleClassName} = async (id: number) => { - return await request.get({ url: '${baseURL}/get?id=' + id }) -} - -// 新增${table.classComment} -export const create${simpleClassName} = async (data: ${simpleClassName}VO) => { - return await request.post({ url: '${baseURL}/create', data }) -} - -// 修改${table.classComment} -export const update${simpleClassName} = async (data: ${simpleClassName}VO) => { - return await request.put({ url: '${baseURL}/update', data }) -} - -// 删除${table.classComment} -export const delete${simpleClassName} = async (id: number) => { - return await request.delete({ url: '${baseURL}/delete?id=' + id }) -} - -// 导出${table.classComment} Excel -export const export${simpleClassName}Api = async (params) => { - return await request.download({ url: '${baseURL}/export-excel', params }) -} diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_schema/views/data.ts.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_schema/views/data.ts.vm deleted file mode 100644 index ff4fa810a..000000000 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_schema/views/data.ts.vm +++ /dev/null @@ -1,124 +0,0 @@ -import type { CrudSchema } from '@/hooks/web/useCrudSchemas' -import { dateFormatter } from '@/utils/formatTime' - -// 表单校验 -export const rules = reactive({ -#foreach ($column in $columns) -#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键 -#set($comment=$column.columnComment) - $column.javaField: [required], -#end -#end -}) - -// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/ -const crudSchemas = reactive([ -#foreach($column in $columns) -#if ($column.listOperation || $column.listOperationResult || $column.createOperation || $column.updateOperation) -#set ($dictType = $column.dictType) -#set ($javaField = $column.javaField) -#set ($javaType = $column.javaType) - { - label: '${column.columnComment}', - field: '${column.javaField}', -## ========= 字典部分 ========= - #if ("" != $dictType)## 有数据字典 - dictType: DICT_TYPE.$dictType.toUpperCase(), - #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short") - dictClass: 'number', - #elseif ($javaType == "String") - dictClass: 'string', - #elseif ($javaType == "Boolean") - dictClass: 'boolean', - #end - #end -## ========= Table 表格部分 ========= - #if (!$column.listOperationResult) - isTable: false, - #else - #if ($column.htmlType == "datetime") - formatter: dateFormatter, - #end - #end -## ========= Search 表格部分 ========= - #if ($column.listOperation) - isSearch: true, - #if ($column.htmlType == "datetime") - search: { - component: 'DatePicker', - componentProps: { - valueFormat: 'YYYY-MM-DD HH:mm:ss', - type: 'daterange', - defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')] - } - }, - #end - #end -## ========= Form 表单部分 ========= - #if ((!$column.createOperation && !$column.updateOperation) || $column.primaryKey) - isForm: false, - #else - #if($column.htmlType == "imageUpload")## 图片上传 - form: { - component: 'UploadImg' - }, - #elseif($column.htmlType == "fileUpload")## 文件上传 - form: { - component: 'UploadFile' - }, - #elseif($column.htmlType == "editor")## 文本编辑器 - form: { - component: 'Editor', - componentProps: { - valueHtml: '', - height: 200 - } - }, - #elseif($column.htmlType == "select")## 下拉框 - form: { - component: 'SelectV2' - }, - #elseif($column.htmlType == "checkbox")## 多选框 - form: { - component: 'Checkbox' - }, - #elseif($column.htmlType == "radio")## 单选框 - form: { - component: 'Radio' - }, - #elseif($column.htmlType == "datetime")## 时间框 - form: { - component: 'DatePicker', - componentProps: { - type: 'datetime', - valueFormat: 'x' - } - }, - #elseif($column.htmlType == "textarea")## 文本框 - form: { - component: 'Input', - componentProps: { - type: 'textarea', - rows: 4 - }, - colProps: { - span: 24 - } - }, - #elseif(${javaType.toLowerCase()} == "long" || ${javaType.toLowerCase()} == "integer")## 文本框 - form: { - component: 'InputNumber', - value: 0 - }, - #end - #end - }, -#end -#end - { - label: '操作', - field: 'action', - isForm: false - } -]) -export const { allSchemas } = useCrudSchemas(crudSchemas) diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_schema/views/form.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_schema/views/form.vue.vm deleted file mode 100644 index 52f20a2f5..000000000 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_schema/views/form.vue.vm +++ /dev/null @@ -1,65 +0,0 @@ - - diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_schema/views/index.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_schema/views/index.vue.vm deleted file mode 100644 index 6e8f1403a..000000000 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_schema/views/index.vue.vm +++ /dev/null @@ -1,85 +0,0 @@ - - diff --git a/yudao-module-iot/yudao-module-iot-biz/pom.xml b/yudao-module-iot/yudao-module-iot-biz/pom.xml index 146560c78..f04cdf4d4 100644 --- a/yudao-module-iot/yudao-module-iot-biz/pom.xml +++ b/yudao-module-iot/yudao-module-iot-biz/pom.xml @@ -57,11 +57,6 @@ cn.iocoder.boot yudao-spring-boot-starter-test - - - xerces - xercesImpl - cn.iocoder.boot diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/security/config/SecurityConfiguration.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/security/config/SecurityConfiguration.java index 74a05e6b2..223342484 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/security/config/SecurityConfiguration.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/security/config/SecurityConfiguration.java @@ -1,13 +1,10 @@ package cn.iocoder.yudao.module.iot.framework.security.config; import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer; -import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; - -import javax.annotation.Resource; +import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; /** * Report 模块的 Security 配置 @@ -15,19 +12,17 @@ import javax.annotation.Resource; @Configuration("iotSecurityConfiguration") public class SecurityConfiguration { - @Resource - private OAuth2TokenApi oauth2TokenApi; - @Bean("iotAuthorizeRequestsCustomizer") public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { return new AuthorizeRequestsCustomizer() { @Override - public void customize(ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry) { - registry.antMatchers("/iot/open/**").permitAll(); // 积木报表 + public void customize(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) { + registry.requestMatchers("/iot/open/**").permitAll(); // 积木报表 } }; } } + diff --git a/yudao-module-mes/yudao-module-mes-biz/pom.xml b/yudao-module-mes/yudao-module-mes-biz/pom.xml index 814c98bf1..b083f75b3 100644 --- a/yudao-module-mes/yudao-module-mes-biz/pom.xml +++ b/yudao-module-mes/yudao-module-mes-biz/pom.xml @@ -68,17 +68,6 @@ yudao-spring-boot-starter-test - - - org.jeecgframework.jimureport - jimureport-spring-boot-starter - - - - xerces - xercesImpl - - cn.iocoder.boot yudao-spring-boot-starter-excel diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/framework/security/config/SecurityConfiguration.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/framework/security/config/SecurityConfiguration.java index a3337766b..f8dbe21e9 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/framework/security/config/SecurityConfiguration.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/framework/security/config/SecurityConfiguration.java @@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; +import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; import javax.annotation.Resource; @@ -23,11 +23,10 @@ public class SecurityConfiguration { return new AuthorizeRequestsCustomizer() { @Override - public void customize(ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry) { - registry.antMatchers("/mes/open/**").permitAll(); // 积木报表 + public void customize(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) { + registry.requestMatchers("/mes/open/**").permitAll(); // 积木报表 } }; } - } diff --git a/yudao-module-report/yudao-module-report-biz/pom.xml b/yudao-module-report/yudao-module-report-biz/pom.xml index b2c4d181f..2453cdbab 100644 --- a/yudao-module-report/yudao-module-report-biz/pom.xml +++ b/yudao-module-report/yudao-module-report-biz/pom.xml @@ -64,11 +64,6 @@ org.jeecgframework.jimureport jimureport-spring-boot-starter - - - xerces - xercesImpl - cn.iocoder.boot diff --git a/yudao-module-report/yudao-module-report-biz/src/main/java/cn/iocoder/yudao/module/report/framework/jmreport/core/service/JmReportTokenServiceImpl.java b/yudao-module-report/yudao-module-report-biz/src/main/java/cn/iocoder/yudao/module/report/framework/jmreport/core/service/JmReportTokenServiceImpl.java index 8bd4eaa29..f0b65e334 100644 --- a/yudao-module-report/yudao-module-report-biz/src/main/java/cn/iocoder/yudao/module/report/framework/jmreport/core/service/JmReportTokenServiceImpl.java +++ b/yudao-module-report/yudao-module-report-biz/src/main/java/cn/iocoder/yudao/module/report/framework/jmreport/core/service/JmReportTokenServiceImpl.java @@ -133,6 +133,13 @@ public class JmReportTokenServiceImpl implements JmReportTokenServiceI { @Override public String[] getRoles(String token) { + // 设置租户上下文。原因是:/jmreport/** 纯前端地址,不会走 buildLoginUserByToken 逻辑 + LoginUser loginUser = SecurityFrameworkUtils.getLoginUser(); + if (loginUser == null) { + return null; + } + TenantContextHolder.setTenantId(loginUser.getTenantId()); + // 参见文档 https://help.jeecg.com/jimureport/prodSafe.html 文档 // 适配:如果是本系统的管理员,则转换成 jimu 报表的管理员 Long userId = SecurityFrameworkUtils.getLoginUserId(); diff --git a/yudao-module-report/yudao-module-report-biz/src/main/java/cn/iocoder/yudao/module/report/framework/security/config/SecurityConfiguration.java b/yudao-module-report/yudao-module-report-biz/src/main/java/cn/iocoder/yudao/module/report/framework/security/config/SecurityConfiguration.java index 0de31d9d6..f03801bba 100644 --- a/yudao-module-report/yudao-module-report-biz/src/main/java/cn/iocoder/yudao/module/report/framework/security/config/SecurityConfiguration.java +++ b/yudao-module-report/yudao-module-report-biz/src/main/java/cn/iocoder/yudao/module/report/framework/security/config/SecurityConfiguration.java @@ -1,30 +1,25 @@ package cn.iocoder.yudao.module.report.framework.security.config; import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer; -import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer; import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; -import javax.annotation.Resource; - /** * Report 模块的 Security 配置 */ @Configuration("reportSecurityConfiguration") public class SecurityConfiguration { - @Resource - private OAuth2TokenApi oauth2TokenApi; - @Bean("reportAuthorizeRequestsCustomizer") public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { return new AuthorizeRequestsCustomizer() { @Override - public void customize(ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry) { - registry.antMatchers("/jmreport/**").permitAll(); // 积木报表 + public void customize(AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry registry) { + registry.requestMatchers("/jmreport/**").permitAll(); // 积木报表 } }; diff --git a/yudao-module-report/yudao-module-report-biz/src/test/resources/application-unit-test.yaml b/yudao-module-report/yudao-module-report-biz/src/test/resources/application-unit-test.yaml index eb823d904..e451c913f 100644 --- a/yudao-module-report/yudao-module-report-biz/src/test/resources/application-unit-test.yaml +++ b/yudao-module-report/yudao-module-report-biz/src/test/resources/application-unit-test.yaml @@ -46,8 +46,3 @@ mybatis: yudao: info: base-package: cn.iocoder.yudao.module - captcha: - timeout: 5m - width: 160 - height: 60 - enable: true diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java index 352b24f98..bdc3ba59b 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/DeptApi.java @@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import java.util.Collection; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -47,7 +46,6 @@ public interface DeptApi { * @return 部门 Map */ default Map getDeptMap(Collection ids) { - if(ids.isEmpty())return new HashMap<>(); List list = getDeptList(ids); return CollectionUtils.convertMap(list, DeptRespDTO::getId); } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/OperateLogApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/OperateLogApi.java index 4a6c6782b..e61070608 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/OperateLogApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/OperateLogApi.java @@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogRespDTO; import org.springframework.scheduling.annotation.Async; import javax.validation.Valid; + /** * 操作日志 API 接口 * @@ -34,9 +35,9 @@ public interface OperateLogApi { /** * 获取指定模块的指定数据的操作日志分页 * - * @param pageReqVO 请求 + * @param pageReqDTO 请求 * @return 操作日志分页 */ - PageResult getOperateLogPage(OperateLogPageReqDTO pageReqVO); + PageResult getOperateLogPage(OperateLogPageReqDTO pageReqDTO); } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/dto/OperateLogRespDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/dto/OperateLogRespDTO.java index 34a7526f9..41ff437f0 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/dto/OperateLogRespDTO.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/dto/OperateLogRespDTO.java @@ -26,7 +26,7 @@ public class OperateLogRespDTO implements VO { /** * 用户编号 */ - @Trans(type = TransType.RPC, targetClassName = "cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO", + @Trans(type = TransType.SIMPLE, targetClassName = "cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO", fields = "nickname", ref = "userName") private Long userId; /** diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/oauth2/dto/OAuth2AccessTokenCheckRespDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/oauth2/dto/OAuth2AccessTokenCheckRespDTO.java index 97b89be6e..c678b48a6 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/oauth2/dto/OAuth2AccessTokenCheckRespDTO.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/oauth2/dto/OAuth2AccessTokenCheckRespDTO.java @@ -35,11 +35,9 @@ public class OAuth2AccessTokenCheckRespDTO implements Serializable { * 授权范围的数组 */ private List scopes; - /** * 过期时间 */ private LocalDateTime expiresTime; - } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java index 7fdb35a32..ef02dd108 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java @@ -1,9 +1,12 @@ package cn.iocoder.yudao.module.system.api.social; -import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO; -import cn.iocoder.yudao.module.system.api.social.dto.SocialWxPhoneNumberInfoRespDTO; +import cn.iocoder.yudao.module.system.api.social.dto.*; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; +import javax.validation.Valid; + +import java.util.List; + /** * 社交应用的 API 接口 * @@ -14,8 +17,8 @@ public interface SocialClientApi { /** * 获得社交平台的授权 URL * - * @param socialType 社交平台的类型 {@link SocialTypeEnum} - * @param userType 用户类型 + * @param socialType 社交平台的类型 {@link SocialTypeEnum} + * @param userType 用户类型 * @param redirectUri 重定向 URL * @return 社交平台的授权 URL */ @@ -25,18 +28,42 @@ public interface SocialClientApi { * 创建微信公众号 JS SDK 初始化所需的签名 * * @param userType 用户类型 - * @param url 访问的 URL 地址 + * @param url 访问的 URL 地址 * @return 签名 */ SocialWxJsapiSignatureRespDTO createWxMpJsapiSignature(Integer userType, String url); + //======================= 微信小程序独有 ======================= + /** * 获得微信小程序的手机信息 * - * @param userType 用户类型 + * @param userType 用户类型 * @param phoneCode 手机授权码 * @return 手机信息 */ SocialWxPhoneNumberInfoRespDTO getWxMaPhoneNumberInfo(Integer userType, String phoneCode); + /** + * 获得小程序二维码 + * + * @param reqVO 请求信息 + * @return 小程序二维码 + */ + byte[] getWxaQrcode(@Valid SocialWxQrcodeReqDTO reqVO); + + /** + * 获得微信小程订阅模板 + * + * @return 小程序订阅消息模版 + */ + List getWxaSubscribeTemplateList(Integer userType); + + /** + * 发送微信小程序订阅消息 + * + * @param reqDTO 请求 + */ + void sendWxaSubscribeMessage(SocialWxaSubscribeMessageSendReqDTO reqDTO); + } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java index 05c6acb7a..402df75f4 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java @@ -32,8 +32,8 @@ public interface SocialUserApi { /** * 获得社交用户,基于 userId * - * @param userType 用户类型 - * @param userId 用户编号 + * @param userType 用户类型 + * @param userId 用户编号 * @param socialType 社交平台的类型 * @return 社交用户 */ @@ -44,10 +44,10 @@ public interface SocialUserApi { * * 在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常 * - * @param userType 用户类型 + * @param userType 用户类型 * @param socialType 社交平台的类型 - * @param code 授权码 - * @param state state + * @param code 授权码 + * @param state state * @return 社交用户 */ SocialUserRespDTO getSocialUserByCode(Integer userType, Integer socialType, String code, String state); diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java new file mode 100644 index 000000000..d9f0befd1 --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java @@ -0,0 +1,67 @@ +package cn.iocoder.yudao.module.system.api.social.dto; + +import lombok.Data; + +import javax.validation.constraints.NotEmpty; + +/** + * 获取小程序码 Request DTO + * + * @author HUIHUI + * @see 获取不限制的小程序码 + */ +@Data +public class SocialWxQrcodeReqDTO { + + /** + * 页面路径不能携带参数(参数请放在scene字段里) + */ + public static final String SCENE = ""; + /** + * 二维码宽度 + */ + public static final Integer WIDTH = 430; + /** + * 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 + */ + public static final Boolean AUTO_COLOR = true; + /** + * 检查 page 是否存在 + */ + public static final Boolean CHECK_PATH = true; + /** + * 是否需要透明底色 + * + * hyaline 为 true 时,生成透明底色的小程序码 + */ + public static final Boolean HYALINE = true; + + /** + * 场景 + */ + @NotEmpty(message = "场景不能为空") + private String scene; + /** + * 页面路径 + */ + @NotEmpty(message = "页面路径不能为空") + private String path; + /** + * 二维码宽度 + */ + private Integer width; + + /** + * 是否需要透明底色 + */ + private Boolean autoColor; + /** + * 是否检查 page 是否存在 + */ + private Boolean checkPath; + /** + * 是否需要透明底色 + */ + private Boolean hyaline; + +} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxaSubscribeMessageSendReqDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxaSubscribeMessageSendReqDTO.java new file mode 100644 index 000000000..fa074a2c1 --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxaSubscribeMessageSendReqDTO.java @@ -0,0 +1,61 @@ +package cn.iocoder.yudao.module.system.api.social.dto; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.HashMap; +import java.util.Map; + +/** + * 微信小程序订阅消息发送 Request DTO + * + * @author HUIHUI + */ +@Data +public class SocialWxaSubscribeMessageSendReqDTO { + + /** + * 用户编号 + * + * 关联 MemberUserDO 的 id 编号 + * 关联 AdminUserDO 的 id 编号 + */ + @NotNull(message = "用户编号不能为空") + private Long userId; + /** + * 用户类型 + * + * 关联 {@link UserTypeEnum} + */ + @NotNull(message = "用户类型不能为空") + private Integer userType; + + /** + * 消息模版标题 + */ + @NotEmpty(message = "消息模版标题不能为空") + private String templateTitle; + + /** + * 点击模板卡片后的跳转页面,仅限本小程序内的页面 + * + * 支持带参数,(示例 index?foo=bar )。该字段不填则模板无跳转。 + */ + private String page; + + /** + * 模板内容的参数 + */ + private Map messages; + + public SocialWxaSubscribeMessageSendReqDTO addMessage(String key, String value) { + if (messages == null) { + messages = new HashMap<>(); + } + messages.put(key, value); + return this; + } + +} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxaSubscribeTemplateRespDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxaSubscribeTemplateRespDTO.java new file mode 100644 index 000000000..ae14cc543 --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxaSubscribeTemplateRespDTO.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.system.api.social.dto; + +import lombok.Data; + + +/** + * 小程序订阅消息模版 Response DTO + * + * @author HUIHUI + */ +@Data +public class SocialWxaSubscribeTemplateRespDTO { + + /** + * 模版编号 + */ + private String id; + + /** + * 模版标题 + */ + private String title; + + /** + * 模版内容 + */ + private String content; + + /** + * 模板内容示例 + */ + private String example; + + /** + * 模版类型 + * + * 2:为一次性订阅 + * 3:为长期订阅 + */ + private Integer type; + +} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java index 3fd8a94a3..507fb4b3e 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java @@ -3,7 +3,10 @@ package cn.iocoder.yudao.module.system.api.user; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; /** * Admin 用户 API 接口 @@ -59,7 +62,6 @@ public interface AdminUserApi { * @return 用户 Map */ default Map getUserMap(Collection ids) { - if(ids.isEmpty())return new HashMap<>(); List users = getUserList(ids); return CollectionUtils.convertMap(users, AdminUserRespDTO::getId); } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/dto/AdminUserRespDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/dto/AdminUserRespDTO.java index ac13c3a8b..d86f3e548 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/dto/AdminUserRespDTO.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/dto/AdminUserRespDTO.java @@ -40,5 +40,9 @@ public class AdminUserRespDTO { * 手机号码 */ private String mobile; + /** + * 用户头像 + */ + private String avatar; } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/DictTypeConstants.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/DictTypeConstants.java index d7967fe28..d7592c34c 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/DictTypeConstants.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/DictTypeConstants.java @@ -13,12 +13,11 @@ public interface DictTypeConstants { // ========== SYSTEM 模块 ========== String USER_SEX = "system_user_sex"; // 用户性别 + String DATA_SCOPE = "system_data_scope"; // 数据范围 String LOGIN_TYPE = "system_login_type"; // 登录日志的类型 String LOGIN_RESULT = "system_login_result"; // 登录结果 - String ERROR_CODE_TYPE = "system_error_code_type"; // 错误码的类型枚举 - String SMS_CHANNEL_CODE = "system_sms_channel_code"; // 短信渠道编码 String SMS_TEMPLATE_TYPE = "system_sms_template_type"; // 短信模板类型 String SMS_SEND_STATUS = "system_sms_send_status"; // 短信发送状态 diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java index 1b4c313c8..96052dc72 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java @@ -14,8 +14,8 @@ public interface ErrorCodeConstants { ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1_002_000_001, "登录失败,账号被禁用"); ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1_002_000_004, "验证码不正确,原因:{}"); ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1_002_000_005, "未绑定账号,需要进行绑定"); - ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1_002_000_006, "Token 已经过期"); ErrorCode AUTH_MOBILE_NOT_EXISTS = new ErrorCode(1_002_000_007, "手机号不存在"); + ErrorCode AUTH_REGISTER_CAPTCHA_CODE_ERROR = new ErrorCode(1_002_000_008, "验证码不正确,原因:{}"); // ========== 菜单模块 1-002-001-000 ========== ErrorCode MENU_NAME_DUPLICATE = new ErrorCode(1_002_001_000, "已经存在该名字的菜单"); @@ -28,10 +28,10 @@ public interface ErrorCodeConstants { // ========== 角色模块 1-002-002-000 ========== ErrorCode ROLE_NOT_EXISTS = new ErrorCode(1_002_002_000, "角色不存在"); ErrorCode ROLE_NAME_DUPLICATE = new ErrorCode(1_002_002_001, "已经存在名为【{}】的角色"); - ErrorCode ROLE_CODE_DUPLICATE = new ErrorCode(1_002_002_002, "已经存在编码为【{}】的角色"); + ErrorCode ROLE_CODE_DUPLICATE = new ErrorCode(1_002_002_002, "已经存在标识为【{}】的角色"); ErrorCode ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE = new ErrorCode(1_002_002_003, "不能操作类型为系统内置的角色"); ErrorCode ROLE_IS_DISABLE = new ErrorCode(1_002_002_004, "名字为【{}】的角色已被禁用"); - ErrorCode ROLE_ADMIN_CODE_ERROR = new ErrorCode(1_002_002_005, "编码【{}】不能使用"); + ErrorCode ROLE_ADMIN_CODE_ERROR = new ErrorCode(1_002_002_005, "标识【{}】不能使用"); // ========== 用户模块 1-002-003-000 ========== ErrorCode USER_USERNAME_EXISTS = new ErrorCode(1_002_003_000, "用户账号已经存在"); @@ -42,6 +42,7 @@ public interface ErrorCodeConstants { ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1_002_003_005, "用户密码校验失败"); ErrorCode USER_IS_DISABLE = new ErrorCode(1_002_003_006, "名字为【{}】的用户已被禁用"); ErrorCode USER_COUNT_MAX = new ErrorCode(1_002_003_008, "创建用户失败,原因:超过租户最大租户配额({})!"); + ErrorCode USER_IMPORT_INIT_PASSWORD = new ErrorCode(1_002_003_009, "初始密码不能为空"); // ========== 部门模块 1-002-004-000 ========== ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1_002_004_000, "已经存在该名字的部门"); @@ -49,7 +50,6 @@ public interface ErrorCodeConstants { ErrorCode DEPT_NOT_FOUND = new ErrorCode(1_002_004_002, "当前部门不存在"); ErrorCode DEPT_EXITS_CHILDREN = new ErrorCode(1_002_004_003, "存在子部门,无法删除"); ErrorCode DEPT_PARENT_ERROR = new ErrorCode(1_002_004_004, "不能设置自己为父部门"); - ErrorCode DEPT_EXISTS_USER = new ErrorCode(1_002_004_005, "部门中存在员工,无法删除"); ErrorCode DEPT_NOT_ENABLE = new ErrorCode(1_002_004_006, "部门({})不处于开启状态,不允许选择"); ErrorCode DEPT_PARENT_IS_CHILD = new ErrorCode(1_002_004_007, "不能设置自己的子部门为父部门"); @@ -96,11 +96,8 @@ public interface ErrorCodeConstants { ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1_002_014_000, "验证码不存在"); ErrorCode SMS_CODE_EXPIRED = new ErrorCode(1_002_014_001, "验证码已过期"); ErrorCode SMS_CODE_USED = new ErrorCode(1_002_014_002, "验证码已使用"); - ErrorCode SMS_CODE_NOT_CORRECT = new ErrorCode(1_002_014_003, "验证码不正确"); ErrorCode SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1_002_014_004, "超过每日短信发送数量"); ErrorCode SMS_CODE_SEND_TOO_FAST = new ErrorCode(1_002_014_005, "短信发送过于频繁"); - ErrorCode SMS_CODE_IS_EXISTS = new ErrorCode(1_002_014_006, "手机号已被使用"); - ErrorCode SMS_CODE_IS_UNUSED = new ErrorCode(1_002_014_007, "验证码未被使用"); // ========== 租户信息 1-002-015-000 ========== ErrorCode TENANT_NOT_EXISTS = new ErrorCode(1_002_015_000, "租户不存在"); @@ -120,8 +117,12 @@ public interface ErrorCodeConstants { ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1_002_018_001, "社交授权失败,找不到对应的用户"); ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_PHONE_CODE_ERROR = new ErrorCode(1_002_018_200, "获得手机号失败"); - ErrorCode SOCIAL_CLIENT_NOT_EXISTS = new ErrorCode(1_002_018_201, "社交客户端不存在"); - ErrorCode SOCIAL_CLIENT_UNIQUE = new ErrorCode(1_002_018_202, "社交客户端已存在配置"); + ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_QRCODE_ERROR = new ErrorCode(1_002_018_201, "获得小程序码失败"); + ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_SUBSCRIBE_TEMPLATE_ERROR = new ErrorCode(1_002_018_202, "获得小程序订阅消息模版失败"); + ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_SUBSCRIBE_MESSAGE_ERROR = new ErrorCode(1_002_018_203, "发送小程序订阅消息失败"); + ErrorCode SOCIAL_CLIENT_NOT_EXISTS = new ErrorCode(1_002_018_210, "社交客户端不存在"); + ErrorCode SOCIAL_CLIENT_UNIQUE = new ErrorCode(1_002_018_211, "社交客户端已存在配置"); + // ========== OAuth2 客户端 1-002-020-000 ========= ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1_002_020_000, "OAuth2 客户端不存在"); @@ -136,7 +137,6 @@ public interface ErrorCodeConstants { ErrorCode OAUTH2_GRANT_CLIENT_ID_MISMATCH = new ErrorCode(1_002_021_000, "client_id 不匹配"); ErrorCode OAUTH2_GRANT_REDIRECT_URI_MISMATCH = new ErrorCode(1_002_021_001, "redirect_uri 不匹配"); ErrorCode OAUTH2_GRANT_STATE_MISMATCH = new ErrorCode(1_002_021_002, "state 不匹配"); - ErrorCode OAUTH2_GRANT_CODE_NOT_EXISTS = new ErrorCode(1_002_021_003, "code 不存在"); // ========== OAuth2 授权 1-002-022-000 ========= ErrorCode OAUTH2_CODE_NOT_EXISTS = new ErrorCode(1_002_022_000, "code 不存在"); diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/common/SexEnum.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/common/SexEnum.java index f6120c418..c1e222bed 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/common/SexEnum.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/common/SexEnum.java @@ -17,7 +17,7 @@ public enum SexEnum { /** 女 */ FEMALE(2), /* 未知 */ - UNKNOWN(3); + UNKNOWN(0); /** * 性别 diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/oauth2/OAuth2GrantTypeEnum.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/oauth2/OAuth2GrantTypeEnum.java index 80ce4610b..0b09fe5b0 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/oauth2/OAuth2GrantTypeEnum.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/oauth2/OAuth2GrantTypeEnum.java @@ -22,7 +22,7 @@ public enum OAuth2GrantTypeEnum { private final String grantType; - public static OAuth2GrantTypeEnum getByGranType(String grantType) { + public static OAuth2GrantTypeEnum getByGrantType(String grantType) { return ArrayUtil.firstMatch(o -> o.getGrantType().equals(grantType), values()); } diff --git a/yudao-module-system/yudao-module-system-biz/pom.xml b/yudao-module-system/yudao-module-system-biz/pom.xml index 6de1a58a1..5f191237d 100644 --- a/yudao-module-system/yudao-module-system-biz/pom.xml +++ b/yudao-module-system/yudao-module-system-biz/pom.xml @@ -110,19 +110,6 @@ wx-java-miniapp-spring-boot-starter - - com.aliyun - aliyun-java-sdk-core - - - com.aliyun - aliyun-java-sdk-dysmsapi - - - com.tencentcloudapi - tencentcloud-sdk-java-sms - - com.xingyuv spring-boot-starter-captcha-plus diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/logger/OperateLogApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/logger/OperateLogApiImpl.java index 867751ef3..526ef8d46 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/logger/OperateLogApiImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/logger/OperateLogApiImpl.java @@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogRespDTO; import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogDO; import cn.iocoder.yudao.module.system.service.logger.OperateLogService; import com.fhs.core.trans.anno.TransMethodResult; -import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; @@ -27,7 +26,6 @@ public class OperateLogApiImpl implements OperateLogApi { private OperateLogService operateLogService; @Override - @Async public void createOperateLog(OperateLogCreateReqDTO createReqDTO) { operateLogService.createOperateLog(createReqDTO); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java index e05251f61..d0e60405c 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java @@ -1,15 +1,25 @@ package cn.iocoder.yudao.module.system.api.social; import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO; -import cn.iocoder.yudao.module.system.api.social.dto.SocialWxPhoneNumberInfoRespDTO; +import cn.iocoder.yudao.module.system.api.social.dto.*; +import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import cn.iocoder.yudao.module.system.service.social.SocialClientService; +import cn.iocoder.yudao.module.system.service.social.SocialUserService; +import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.bean.WxJsapiSignature; +import me.chanjar.weixin.common.bean.subscribemsg.TemplateInfo; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; +import java.util.List; + +import static cn.hutool.core.collection.CollUtil.findOne; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; /** * 社交应用的 API 实现类 @@ -18,10 +28,13 @@ import javax.annotation.Resource; */ @Service @Validated +@Slf4j public class SocialClientApiImpl implements SocialClientApi { @Resource private SocialClientService socialClientService; + @Resource + private SocialUserService socialUserService; @Override public String getAuthorizeUrl(Integer socialType, Integer userType, String redirectUri) { @@ -34,10 +47,51 @@ public class SocialClientApiImpl implements SocialClientApi { return BeanUtils.toBean(signature, SocialWxJsapiSignatureRespDTO.class); } + //======================= 微信小程序独有 ======================= + @Override public SocialWxPhoneNumberInfoRespDTO getWxMaPhoneNumberInfo(Integer userType, String phoneCode) { WxMaPhoneNumberInfo info = socialClientService.getWxMaPhoneNumberInfo(userType, phoneCode); return BeanUtils.toBean(info, SocialWxPhoneNumberInfoRespDTO.class); } + @Override + public byte[] getWxaQrcode(SocialWxQrcodeReqDTO reqVO) { + return socialClientService.getWxaQrcode(reqVO); + } + + @Override + public List getWxaSubscribeTemplateList(Integer userType) { + List list = socialClientService.getSubscribeTemplateList(userType); + return convertList(list, item -> BeanUtils.toBean(item, SocialWxaSubscribeTemplateRespDTO.class).setId(item.getPriTmplId())); + } + + @Override + public void sendWxaSubscribeMessage(SocialWxaSubscribeMessageSendReqDTO reqDTO) { + // 1.1 获得订阅模版列表 + List templateList = socialClientService.getSubscribeTemplateList(reqDTO.getUserType()); + if (CollUtil.isEmpty(templateList)) { + log.warn("[sendSubscribeMessage][reqDTO({}) 发送订阅消息失败,原因:没有找到订阅模板]", reqDTO); + return; + } + // 1.2 获得需要使用的模版 + TemplateInfo template = findOne(templateList, item -> + ObjUtil.equal(item.getTitle(), reqDTO.getTemplateTitle())); + if (template == null) { + log.warn("[sendWxaSubscribeMessage][reqDTO({}) 发送订阅消息失败,原因:没有找到订阅模板]", reqDTO); + return; + } + + // 2. 获得社交用户 + SocialUserRespDTO socialUser = socialUserService.getSocialUserByUserId(reqDTO.getUserType(), reqDTO.getUserId(), + SocialTypeEnum.WECHAT_MINI_APP.getType()); + if (StrUtil.isBlankIfStr(socialUser.getOpenid())) { + log.warn("[sendWxaSubscribeMessage][reqDTO({}) 发送订阅消息失败,原因:会员 openid 缺失]", reqDTO); + return; + } + + // 3. 发送订阅消息 + socialClientService.sendSubscribeMessage(reqDTO, template.getPriTmplId(), socialUser.getOpenid()); + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java index 1892dbc28..a29f8670a 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.api.social; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; import cn.iocoder.yudao.module.system.service.social.SocialUserService; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java index 373e088e4..9532cb909 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.api.user; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; @@ -11,10 +12,10 @@ import cn.iocoder.yudao.module.system.service.user.AdminUserService; import org.springframework.stereotype.Service; import javax.annotation.Resource; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Set; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; @@ -40,21 +41,13 @@ public class AdminUserApiImpl implements AdminUserApi { @Override public List getUserListBySubordinate(Long id) { // 1.1 获取用户负责的部门 - AdminUserDO user = userService.getUser(id); - if (user == null) { - return Collections.emptyList(); - } - ArrayList deptIds = new ArrayList<>(); - DeptDO dept = deptService.getDept(user.getDeptId()); - if (dept == null) { - return Collections.emptyList(); - } - if (ObjUtil.notEqual(dept.getLeaderUserId(), id)) { // 校验为负责人 + List depts = deptService.getDeptListByLeaderUserId(id); + if (CollUtil.isEmpty(depts)) { return Collections.emptyList(); } - deptIds.add(dept.getId()); // 1.2 获取所有子部门 - List childDeptList = deptService.getChildDeptList(dept.getId()); + Set deptIds = convertSet(depts, DeptDO::getId); + List childDeptList = deptService.getChildDeptList(deptIds); if (CollUtil.isNotEmpty(childDeptList)) { deptIds.addAll(convertSet(childDeptList, DeptDO::getId)); } @@ -66,6 +59,7 @@ public class AdminUserApiImpl implements AdminUserApi { } @Override + @DataPermission(enable = false) // 禁用数据权限。原因是,一般基于指定 id 的 API 查询,都是数据拼接为主 public List getUserList(Collection ids) { List users = userService.getUserList(ids); return BeanUtils.toBean(users, AdminUserRespDTO.class); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java index 521e4e7cf..4f785dc83 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java @@ -109,12 +109,19 @@ public class AuthController { // 1.3 获得菜单列表 Set menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId)); List menuList = menuService.getMenuList(menuIds); - menuList.removeIf(menu -> !CommonStatusEnum.ENABLE.getStatus().equals(menu.getStatus())); // 移除禁用的菜单 + menuList = menuService.filterDisableMenus(menuList); // 2. 拼接结果返回 return success(AuthConvert.INSTANCE.convert(user, roles, menuList)); } + @PostMapping("/register") + @PermitAll + @Operation(summary = "注册用户") + public CommonResult register(@RequestBody @Valid AuthRegisterReqVO registerReqVO) { + return success(authService.register(registerReqVO)); + } + // ========== 短信登录相关 ========== @PostMapping("/sms-login") diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginReqVO.java index f57a6908c..2009a9e44 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginReqVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginReqVO.java @@ -12,6 +12,7 @@ import org.hibernate.validator.constraints.Length; import javax.validation.constraints.AssertTrue; import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.Pattern; @Schema(description = "管理后台 - 账号密码登录 Request VO,如果登录并绑定社交用户,需要传递 social 开头的参数") @Data @@ -22,8 +23,8 @@ public class AuthLoginReqVO { @Schema(description = "账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudaoyuanma") @NotEmpty(message = "登录账号不能为空") - @Length(min = 2, max = 30, message = "账号长度为 2-30 位") - //@Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母") + @Length(min = 4, max = 16, message = "账号长度为 4-16 位") + @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母") private String username; @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "buzhidao") diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthRegisterReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthRegisterReqVO.java new file mode 100644 index 000000000..735f9e74c --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthRegisterReqVO.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.system.controller.admin.auth.vo; + + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.hibernate.validator.constraints.Length; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + +@Schema(description = "管理后台 - Register Request VO") +@Data +public class AuthRegisterReqVO { + + @Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao") + @NotBlank(message = "用户账号不能为空") + @Pattern(regexp = "^[a-zA-Z0-9]{4,30}$", message = "用户账号由 数字、字母 组成") + @Size(min = 4, max = 30, message = "用户账号长度为 4-30 个字符") + private String username; + + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + @NotBlank(message = "用户昵称不能为空") + @Size(max = 30, message = "用户昵称长度不能超过 30 个字符") + private String nickname; + + @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456") + @NotEmpty(message = "密码不能为空") + @Length(min = 4, max = 16, message = "密码长度为 4-16 位") + private String password; + + // ========== 图片验证码相关 ========== + + @Schema(description = "验证码,验证码开启时,需要传递", requiredMode = Schema.RequiredMode.REQUIRED, + example = "PfcH6mgr8tpXuMWFjvW6YVaqrswIuwmWI5dsVZSg7sGpWtDCUbHuDEXl3cFB1+VvCC/rAkSwK8Fad52FSuncVg==") + @NotEmpty(message = "验证码不能为空", groups = AuthLoginReqVO.CodeEnableGroup.class) + private String captchaVerification; + +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/captcha/CaptchaController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/captcha/CaptchaController.java index 7d112f334..f2c7141ed 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/captcha/CaptchaController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/captcha/CaptchaController.java @@ -2,13 +2,15 @@ package cn.iocoder.yudao.module.system.controller.admin.captcha; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; -import cn.iocoder.yudao.module.system.service.auth.AdminAuthService; import com.xingyuv.captcha.model.common.ResponseModel; import com.xingyuv.captcha.model.vo.CaptchaVO; import com.xingyuv.captcha.service.CaptchaService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.annotation.security.PermitAll; @@ -21,15 +23,7 @@ public class CaptchaController { @Resource private CaptchaService captchaService; - @Resource - private AdminAuthService authService; - @GetMapping({"/isEnable"}) - @Operation(summary ="是否开启验证码") - @PermitAll - public ResponseModel get() { - boolean isEnable = authService.getCaptchaEnable(); - return ResponseModel.successData(isEnable); - } + @PostMapping({"/get"}) @Operation(summary = "获得验证码") @PermitAll diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.java index 429f9c98c..0b6675c0f 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.java @@ -105,7 +105,7 @@ public class OAuth2OpenController { @RequestParam(value = "refresh_token", required = false) String refreshToken) { // 刷新模式 List scopes = OAuth2Utils.buildScopes(scope); // 1.1 校验授权类型 - OAuth2GrantTypeEnum grantTypeEnum = OAuth2GrantTypeEnum.getByGranType(grantType); + OAuth2GrantTypeEnum grantTypeEnum = OAuth2GrantTypeEnum.getByGrantType(grantType); if (grantTypeEnum == null) { throw exception0(BAD_REQUEST.getCode(), StrUtil.format("未知授权类型({})", grantType)); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java index 1a0dca331..3ee384e9b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/MenuController.java @@ -72,6 +72,7 @@ public class MenuController { public CommonResult> getSimpleMenuList() { List list = menuService.getMenuListByTenant( new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus())); + list = menuService.filterDisableMenus(list); list.sort(Comparator.comparing(MenuDO::getSort)); return success(BeanUtils.toBean(list, MenuSimpleRespVO.class)); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleRespVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleRespVO.java index 12b3ba9a6..81bad5a4c 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleRespVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleRespVO.java @@ -46,7 +46,8 @@ public class RoleRespVO { private String remark; @Schema(description = "数据范围,参见 DataScopeEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @ExcelProperty("数据范围") + @ExcelProperty(value = "数据范围", converter = DictConvert.class) + @DictFormat(DictTypeConstants.DATA_SCOPE) private Integer dataScope; @Schema(description = "数据范围(指定部门数组)", example = "1") diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleSaveReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleSaveReqVO.java index 6830e0147..ad50ff74f 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleSaveReqVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleSaveReqVO.java @@ -1,5 +1,7 @@ package cn.iocoder.yudao.module.system.controller.admin.permission.vo.role; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.validation.InEnum; import com.mzt.logapi.starter.annotation.DiffLogField; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -23,7 +25,7 @@ public class RoleSaveReqVO { @NotBlank(message = "角色标志不能为空") @Size(max = 100, message = "角色标志长度不能超过 100 个字符") - @Schema(description = "角色编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "ADMIN") + @Schema(description = "角色标志", requiredMode = Schema.RequiredMode.REQUIRED, example = "ADMIN") @DiffLogField(name = "角色标志") private String code; @@ -32,7 +34,14 @@ public class RoleSaveReqVO { @DiffLogField(name = "显示顺序") private Integer sort; + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @DiffLogField(name = "状态") + @NotNull(message = "状态不能为空") + @InEnum(value = CommonStatusEnum.class, message = "状态必须是 {value}") + private Integer status; + @Schema(description = "备注", example = "我是一个角色") + @Size(max = 500, message = "备注长度不能超过 500 个字符") @DiffLogField(name = "备注") private String remark; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java index 895decf13..b682ea9d6 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java @@ -6,9 +6,7 @@ import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsChannelEnum; import cn.iocoder.yudao.module.system.service.sms.SmsSendService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.annotation.security.PermitAll; @@ -26,7 +24,7 @@ public class SmsCallbackController { @PostMapping("/aliyun") @PermitAll - @Operation(summary = "阿里云短信的回调", description = "参见 https://help.aliyun.com/zh/sms/developer-reference/configure-delivery-receipts-1 文档") + @Operation(summary = "阿里云短信的回调", description = "参见 https://help.aliyun.com/document_detail/120998.html 文档") public CommonResult receiveAliyunSmsStatus(HttpServletRequest request) throws Throwable { String text = ServletUtils.getBody(request); smsSendService.receiveSmsStatus(SmsChannelEnum.ALIYUN.getCode(), text); @@ -35,11 +33,28 @@ public class SmsCallbackController { @PostMapping("/tencent") @PermitAll - @Operation(summary = "腾讯云短信的回调", description = "参见 https://cloud.tencent.com/document/product/382/59178 文档") + @Operation(summary = "腾讯云短信的回调", description = "参见 https://cloud.tencent.com/document/product/382/52077 文档") public CommonResult receiveTencentSmsStatus(HttpServletRequest request) throws Throwable { String text = ServletUtils.getBody(request); smsSendService.receiveSmsStatus(SmsChannelEnum.TENCENT.getCode(), text); return success(true); } -} + + @PostMapping("/huawei") + @PermitAll + @Operation(summary = "华为云短信的回调", description = "参见 https://support.huaweicloud.com/api-msgsms/sms_05_0003.html 文档") + public CommonResult receiveHuaweiSmsStatus(@RequestBody String requestBody) throws Throwable { + smsSendService.receiveSmsStatus(SmsChannelEnum.HUAWEI.getCode(), requestBody); + return success(true); + } + + @PostMapping("/qiniu") + @PermitAll + @Operation(summary = "七牛云短信的回调", description = "参见 https://developer.qiniu.com/sms/5910/message-push 文档") + public CommonResult receiveQiniuSmsStatus(@RequestBody String requestBody) throws Throwable { + smsSendService.receiveSmsStatus(SmsChannelEnum.QINIU.getCode(), requestBody); + return success(true); + } + +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialClientController.http b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialClientController.http new file mode 100644 index 000000000..5ab64392a --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialClientController.http @@ -0,0 +1,20 @@ +### 请求 /system/social-client/send-subscribe-message 接口 => 发送测试订阅消息 +POST {{baseUrl}}/system/social-client/send-subscribe-message +Authorization: Bearer {{token}} +Content-Type: application/json +#Authorization: Bearer test100 +tenant-id: {{adminTenentId}} + +{ + "userId": 247, + "userType": 1, + "socialType": 34, + "templateTitle": "充值成功通知", + "page": "", + "messages": { + "character_string1":"5616122165165", + "amount2":"1000.00", + "time3":"2024-01-01 10:10:10", + "phrase4": "充值成功" + } +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialClientController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialClientController.java index c9b5f4da7..5cfde1834 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialClientController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/socail/SocialClientController.java @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.system.controller.admin.socail; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.system.api.social.SocialClientApi; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientRespVO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO; @@ -28,6 +30,8 @@ public class SocialClientController { @Resource private SocialClientService socialClientService; + @Resource + private SocialClientApi socialClientApi; @PostMapping("/create") @Operation(summary = "创建社交客户端") @@ -70,4 +74,11 @@ public class SocialClientController { return success(BeanUtils.toBean(pageResult, SocialClientRespVO.class)); } + @PostMapping("/send-subscribe-message") + @Operation(summary = "发送订阅消息") // 用于测试 + @PreAuthorize("@ss.hasPermission('system:social-client:query')") + public void sendSubscribeMessage(@RequestBody SocialWxaSubscribeMessageSendReqDTO reqDTO) { + socialClientApi.sendWxaSubscribeMessage(reqDTO); + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantSaveReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantSaveReqVO.java index 8b7c228a0..117d365ff 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantSaveReqVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantSaveReqVO.java @@ -8,6 +8,7 @@ import org.hibernate.validator.constraints.Length; import javax.validation.constraints.AssertTrue; import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; import java.time.LocalDateTime; @@ -51,8 +52,8 @@ public class TenantSaveReqVO { // ========== 仅【创建】时,需要传递的字段 ========== @Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao") - //@Pattern(regexp = "^[a-zA-Z0-9]{4,30}$", message = "用户账号由 数字、字母 组成") - @Size(min = 2, max = 30, message = "用户账号长度为 4-30 个字符") + @Pattern(regexp = "^[a-zA-Z0-9]{4,30}$", message = "用户账号由 数字、字母 组成") + @Size(min = 4, max = 30, message = "用户账号长度为 4-30 个字符") private String username; @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456") diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java index e8b5de80c..3f7dc6127 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java @@ -89,7 +89,7 @@ public class UserController { @GetMapping("/page") @Operation(summary = "获得用户分页列表") - @PreAuthorize("@ss.hasPermission('system:user:list')") + @PreAuthorize("@ss.hasPermission('system:user:query')") public CommonResult> getUserPage(@Valid UserPageReqVO pageReqVO) { // 获得用户分页列表 PageResult pageResult = userService.getUserPage(pageReqVO); @@ -119,6 +119,9 @@ public class UserController { @PreAuthorize("@ss.hasPermission('system:user:query')") public CommonResult getUser(@RequestParam("id") Long id) { AdminUserDO user = userService.getUser(id); + if (user == null) { + return success(null); + } // 拼接数据 DeptDO dept = deptService.getDept(user.getDeptId()); return success(UserConvert.INSTANCE.convert(user, dept)); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserPageReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserPageReqVO.java index 525cb11c4..7dadb513b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserPageReqVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserPageReqVO.java @@ -35,4 +35,7 @@ public class UserPageReqVO extends PageParam { @Schema(description = "部门编号,同时筛选子部门", example = "1024") private Long deptId; + @Schema(description = "角色编号", example = "1024") + private Long roleId; + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java index b0a6d99f0..192e0539f 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java @@ -23,8 +23,8 @@ public class UserSaveReqVO { @Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao") @NotBlank(message = "用户账号不能为空") - //@Pattern(regexp = "^[a-zA-Z0-9]{4,30}$", message = "用户账号由 数字、字母 组成") - @Size(min = 2, max = 30, message = "用户账号长度为 4-30 个字符") + @Pattern(regexp = "^[a-zA-Z0-9]{4,30}$", message = "用户账号由 数字、字母 组成") + @Size(min = 4, max = 30, message = "用户账号长度为 4-30 个字符") @DiffLogField(name = "用户账号") private String username; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/dict/AppDictDataController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/dict/AppDictDataController.java index 8c4b97f6f..8ebdc2364 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/dict/AppDictDataController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/dict/AppDictDataController.java @@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; +import javax.annotation.security.PermitAll; import java.util.List; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -32,6 +33,7 @@ public class AppDictDataController { @GetMapping("/type") @Operation(summary = "根据字典类型查询字典数据信息") @Parameter(name = "type", description = "字典类型", required = true, example = "common_status") + @PermitAll public CommonResult> getDictDataListByType(@RequestParam("type") String type) { List list = dictDataService.getDictDataList( CommonStatusEnum.ENABLE.getStatus(), type); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/ip/AppAreaController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/ip/AppAreaController.java index 54b0e87db..ccfd5d788 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/ip/AppAreaController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/ip/AppAreaController.java @@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import javax.annotation.security.PermitAll; import java.util.List; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -25,6 +26,7 @@ public class AppAreaController { @GetMapping("/tree") @Operation(summary = "获得地区树") + @PermitAll public CommonResult> getAreaTree() { Area area = AreaUtils.getArea(Area.ID_CHINA); Assert.notNull(area, "获取不到中国"); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailAccountDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailAccountDO.java index 0f9588616..1fcd22f5b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailAccountDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailAccountDO.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.system.dal.dataobject.mail; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @@ -15,6 +16,7 @@ import lombok.EqualsAndHashCode; * @since 2022-03-21 */ @TableName(value = "system_mail_account", autoResultMap = true) +@KeySequence("system_mail_account_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 @Data @EqualsAndHashCode(callSuper = true) public class MailAccountDO extends BaseDO { diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java index 0d08c7117..76e1e2ec5 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailLogDO.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.dal.dataobject.mail; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.module.system.enums.mail.MailSendStatusEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; @@ -20,6 +21,7 @@ import java.util.Map; * @since 2022-03-21 */ @TableName(value = "system_mail_log", autoResultMap = true) +@KeySequence("system_mail_log_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailTemplateDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailTemplateDO.java index f669b455f..69e4909db 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailTemplateDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/mail/MailTemplateDO.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.dal.dataobject.mail; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 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.TableName; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; @@ -17,6 +18,7 @@ import java.util.List; * @since 2022-03-21 */ @TableName(value = "system_mail_template", autoResultMap = true) +@KeySequence("system_mail_template_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 @Data @EqualsAndHashCode(callSuper = true) public class MailTemplateDO extends BaseDO { diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/oauth2/OAuth2RefreshTokenDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/oauth2/OAuth2RefreshTokenDO.java index 70ddea20e..99d153e8b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/oauth2/OAuth2RefreshTokenDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/oauth2/OAuth2RefreshTokenDO.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.system.dal.dataobject.oauth2; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; @@ -24,7 +24,7 @@ import java.util.List; @Data @EqualsAndHashCode(callSuper = true) @Accessors(chain = true) -public class OAuth2RefreshTokenDO extends BaseDO { +public class OAuth2RefreshTokenDO extends TenantBaseDO { /** * 编号,数据库字典 diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/permission/RoleDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/permission/RoleDO.java index ab0ec912a..482a4b59d 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/permission/RoleDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/permission/RoleDO.java @@ -1,14 +1,14 @@ package cn.iocoder.yudao.module.system.dal.dataobject.permission; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.mybatis.core.type.JsonLongSetTypeHandler; -import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum; import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; +import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum; import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum; 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 com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import lombok.Data; import lombok.EqualsAndHashCode; @@ -72,7 +72,7 @@ public class RoleDO extends TenantBaseDO { * * 适用于 {@link #dataScope} 的值为 {@link DataScopeEnum#DEPT_CUSTOM} 时 */ - @TableField(typeHandler = JsonLongSetTypeHandler.class) + @TableField(typeHandler = JacksonTypeHandler.class) private Set dataScopeDeptIds; } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/tenant/TenantPackageDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/tenant/TenantPackageDO.java index dba7569af..60efbc8a5 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/tenant/TenantPackageDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/tenant/TenantPackageDO.java @@ -2,10 +2,10 @@ package cn.iocoder.yudao.module.system.dal.dataobject.tenant; 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.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import lombok.*; import java.util.Set; @@ -46,7 +46,7 @@ public class TenantPackageDO extends BaseDO { /** * 关联的菜单编号 */ - @TableField(typeHandler = JsonLongSetTypeHandler.class) + @TableField(typeHandler = JacksonTypeHandler.class) private Set menuIds; } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/user/AdminUserDO.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/user/AdminUserDO.java index 84f54a344..2f07a3015 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/user/AdminUserDO.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/user/AdminUserDO.java @@ -1,13 +1,13 @@ package cn.iocoder.yudao.module.system.dal.dataobject.user; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.mybatis.core.type.JsonLongSetTypeHandler; import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; import cn.iocoder.yudao.module.system.enums.common.SexEnum; 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 com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import lombok.*; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -58,7 +58,7 @@ public class AdminUserDO extends TenantBaseDO { /** * 岗位编号数组 */ - @TableField(typeHandler = JsonLongSetTypeHandler.class) + @TableField(typeHandler = JacksonTypeHandler.class) private Set postIds; /** * 用户邮箱 diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/dept/DeptMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/dept/DeptMapper.java index cc4f334e6..a09fcf7d7 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/dept/DeptMapper.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/dept/DeptMapper.java @@ -30,4 +30,8 @@ public interface DeptMapper extends BaseMapperX { return selectList(DeptDO::getParentId, parentIds); } + default List selectListByLeaderUserId(Long id) { + return selectList(DeptDO::getLeaderUserId, id); + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/oauth2/OAuth2RefreshTokenMapper.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/oauth2/OAuth2RefreshTokenMapper.java index 713be89cf..bf91457cd 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/oauth2/OAuth2RefreshTokenMapper.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/oauth2/OAuth2RefreshTokenMapper.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.dal.mysql.oauth2; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO; import org.apache.ibatis.annotations.Mapper; @@ -13,6 +14,7 @@ public interface OAuth2RefreshTokenMapper extends BaseMapperX { return selectOne(AdminUserDO::getMobile, mobile); } - default PageResult selectPage(UserPageReqVO reqVO, Collection deptIds) { + default PageResult selectPage(UserPageReqVO reqVO, Collection deptIds, Collection userIds) { return selectPage(reqVO, new LambdaQueryWrapperX() .likeIfPresent(AdminUserDO::getUsername, reqVO.getUsername()) .likeIfPresent(AdminUserDO::getMobile, reqVO.getMobile()) .eqIfPresent(AdminUserDO::getStatus, reqVO.getStatus()) .betweenIfPresent(AdminUserDO::getCreateTime, reqVO.getCreateTime()) .inIfPresent(AdminUserDO::getDeptId, deptIds) + .inIfPresent(AdminUserDO::getId, userIds) .orderByDesc(AdminUserDO::getId)); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java index 8b9e7681f..b400ede02 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java @@ -98,4 +98,13 @@ public interface RedisKeyConstants { * VALUE 数据格式:String 模版信息 */ String SMS_TEMPLATE = "sms_template"; + + /** + * 小程序订阅模版的缓存 + * + * KEY 格式:wxa_subscribe_template:{userType} + * VALUE 数据格式 String, 模版信息 + */ + String WXA_SUBSCRIBE_TEMPLATE = "wxa_subscribe_template"; + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClient.java index 46224663c..5d7b80990 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClient.java @@ -46,6 +46,8 @@ public interface SmsClient { /** * 查询指定的短信模板 * + * 如果查询失败,则返回 null 空 + * * @param apiTemplateId 短信 API 的模板编号 * @return 短信模板 */ diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClientFactory.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClientFactory.java index a1133177f..ad878b78e 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClientFactory.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/SmsClientFactory.java @@ -30,7 +30,8 @@ public interface SmsClientFactory { * 创建短信 Client * * @param properties 配置对象 + * @return 短信 Client */ - void createOrUpdateSmsClient(SmsChannelProperties properties); + SmsClient createOrUpdateSmsClient(SmsChannelProperties properties); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AbstractSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AbstractSmsClient.java index 3b6e0eb0d..a1883bfdf 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AbstractSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AbstractSmsClient.java @@ -26,15 +26,9 @@ public abstract class AbstractSmsClient implements SmsClient { * 初始化 */ public final void init() { - doInit(); log.debug("[init][配置({}) 初始化完成]", properties); } - /** - * 自定义初始化 - */ - protected abstract void doInit(); - public final void refresh(SmsChannelProperties properties) { // 判断是否更新 if (properties.equals(this.properties)) { diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java index 7d01e6cdf..558dbdef2 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClient.java @@ -1,35 +1,33 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; +import cn.hutool.core.date.format.FastDateFormat; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.digest.DigestUtil; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; import cn.iocoder.yudao.framework.common.core.KeyValue; import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; -import com.aliyuncs.DefaultAcsClient; -import com.aliyuncs.IAcsClient; -import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest; -import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateResponse; -import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest; -import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse; -import com.aliyuncs.profile.DefaultProfile; -import com.aliyuncs.profile.IClientProfile; -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.annotations.VisibleForTesting; -import lombok.Data; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Objects; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -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; /** * 阿里短信客户端的实现类 @@ -40,20 +38,11 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DE @Slf4j public class AliyunSmsClient extends AbstractSmsClient { - /** - * 调用成功 code - */ - public static final String API_CODE_SUCCESS = "OK"; - - /** - * REGION, 使用杭州 - */ - private static final String ENDPOINT = "cn-hangzhou"; + private static final String URL = "https://dysmsapi.aliyuncs.com"; + private static final String HOST = "dysmsapi.aliyuncs.com"; + private static final String VERSION = "2017-05-25"; - /** - * 阿里云客户端 - */ - private volatile IAcsClient client; + private static final String RESPONSE_CODE_SUCCESS = "OK"; public AliyunSmsClient(SmsChannelProperties properties) { super(properties); @@ -61,49 +50,66 @@ public class AliyunSmsClient extends AbstractSmsClient { Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); } - @Override - protected void doInit() { - IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, properties.getApiKey(), properties.getApiSecret()); - client = new DefaultAcsClient(profile); - } - @Override public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, List> templateParams) throws Throwable { - // 构建请求 - SendSmsRequest request = new SendSmsRequest(); - request.setPhoneNumbers(mobile); - request.setSignName(properties.getSignature()); - request.setTemplateCode(apiTemplateId); - request.setTemplateParam(JsonUtils.toJsonString(MapUtils.convertMap(templateParams))); - request.setOutId(String.valueOf(sendLogId)); - // 执行请求 - SendSmsResponse response = client.getAcsResponse(request); - return new SmsSendRespDTO().setSuccess(Objects.equals(response.getCode(), API_CODE_SUCCESS)).setSerialNo(response.getBizId()) - .setApiRequestId(response.getRequestId()).setApiCode(response.getCode()).setApiMsg(response.getMessage()); + Assert.notBlank(properties.getSignature(), "短信签名不能为空"); + // 1. 执行请求 + // 参考链接 https://api.aliyun.com/document/Dysmsapi/2017-05-25/SendSms + TreeMap queryParam = new TreeMap<>(); + queryParam.put("PhoneNumbers", mobile); + queryParam.put("SignName", properties.getSignature()); + queryParam.put("TemplateCode", apiTemplateId); + queryParam.put("TemplateParam", JsonUtils.toJsonString(MapUtils.convertMap(templateParams))); + queryParam.put("OutId", sendLogId); + JSONObject response = request("SendSms", queryParam); + + // 2. 解析请求 + return new SmsSendRespDTO() + .setSuccess(Objects.equals(response.getStr("Code"), RESPONSE_CODE_SUCCESS)) + .setSerialNo(response.getStr("BizId")) + .setApiRequestId(response.getStr("RequestId")) + .setApiCode(response.getStr("Code")) + .setApiMsg(response.getStr("Message")); } @Override public List parseSmsReceiveStatus(String text) { - List statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class); - return convertList(statuses, status -> new SmsReceiveRespDTO().setSuccess(status.getSuccess()) - .setErrorCode(status.getErrCode()).setErrorMsg(status.getErrMsg()) - .setMobile(status.getPhoneNumber()).setReceiveTime(status.getReportTime()) - .setSerialNo(status.getBizId()).setLogId(Long.valueOf(status.getOutId()))); + JSONArray statuses = JSONUtil.parseArray(text); + // 字段参考 https://help.aliyun.com/zh/sms/developer-reference/smsreport-2 + return convertList(statuses, status -> { + JSONObject statusObj = (JSONObject) status; + return new SmsReceiveRespDTO() + .setSuccess(statusObj.getBool("success")) // 是否接收成功 + .setErrorCode(statusObj.getStr("err_code")) // 状态报告编码 + .setErrorMsg(statusObj.getStr("err_msg")) // 状态报告说明 + .setMobile(statusObj.getStr("phone_number")) // 手机号 + .setReceiveTime(statusObj.getLocalDateTime("report_time", null)) // 状态报告时间 + .setSerialNo(statusObj.getStr("biz_id")) // 发送序列号 + .setLogId(statusObj.getLong("out_id")); // 用户序列号 + }); } @Override public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { - // 构建请求 - QuerySmsTemplateRequest request = new QuerySmsTemplateRequest(); - request.setTemplateCode(apiTemplateId); - // 执行请求 - QuerySmsTemplateResponse response = client.getAcsResponse(request); - if (response.getTemplateStatus() == null) { + // 1. 执行请求 + // 参考链接 https://api.aliyun.com/document/Dysmsapi/2017-05-25/QuerySmsTemplate + TreeMap queryParam = new TreeMap<>(); + queryParam.put("TemplateCode", apiTemplateId); + JSONObject response = request("QuerySmsTemplate", queryParam); + + // 2.1 请求失败 + String code = response.getStr("Code"); + if (ObjectUtil.notEqual(code, RESPONSE_CODE_SUCCESS)) { + log.error("[getSmsTemplate][模版编号({}) 响应不正确({})]", apiTemplateId, response); return null; } - return new SmsTemplateRespDTO().setId(response.getTemplateCode()).setContent(response.getTemplateContent()) - .setAuditStatus(convertSmsTemplateAuditStatus(response.getTemplateStatus())).setAuditReason(response.getReason()); + // 2.2 请求成功 + return new SmsTemplateRespDTO() + .setId(response.getStr("TemplateCode")) + .setContent(response.getStr("TemplateContent")) + .setAuditStatus(convertSmsTemplateAuditStatus(response.getInt("TemplateStatus"))) + .setAuditReason(response.getStr("Reason")); } @VisibleForTesting @@ -117,66 +123,71 @@ public class AliyunSmsClient extends AbstractSmsClient { } /** - * 短信接收状态 + * 请求阿里云短信 * - * 参见 文档 - * - * @author 芋道源码 + * @see V3 版本请求体&签名机制 + * @param apiName 请求的 API 名称 + * @param queryParams 请求参数 + * @return 请求结果 */ - @Data - public static class SmsReceiveStatus { - - /** - * 手机号 - */ - @JsonProperty("phone_number") - private String phoneNumber; - /** - * 发送时间 - */ - @JsonProperty("send_time") - @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT) - private LocalDateTime sendTime; - /** - * 状态报告时间 - */ - @JsonProperty("report_time") - @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT) - private LocalDateTime reportTime; - /** - * 是否接收成功 - */ - private Boolean success; - /** - * 状态报告说明 - */ - @JsonProperty("err_msg") - private String errMsg; - /** - * 状态报告编码 - */ - @JsonProperty("err_code") - private String errCode; - /** - * 发送序列号 - */ - @JsonProperty("biz_id") - private String bizId; - /** - * 用户序列号 - * - * 这里我们传递的是 SysSmsLogDO 的日志编号 - */ - @JsonProperty("out_id") - private String outId; - /** - * 短信长度,例如说 1、2、3 - * - * 140 字节算一条短信,短信长度超过 140 字节时会拆分成多条短信发送 - */ - @JsonProperty("sms_size") - private Integer smsSize; + private JSONObject request(String apiName, TreeMap queryParams) { + // 1. 请求参数 + String queryString = queryParams.entrySet().stream() + .map(entry -> percentCode(entry.getKey()) + "=" + percentCode(String.valueOf(entry.getValue()))) + .collect(Collectors.joining("&")); + + // 2.1 请求 Header + TreeMap headers = new TreeMap<>(); + headers.put("host", HOST); + headers.put("x-acs-version", VERSION); + headers.put("x-acs-action", apiName); + headers.put("x-acs-date", FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'", TimeZone.getTimeZone("GMT")).format(new Date())); + headers.put("x-acs-signature-nonce", IdUtil.randomUUID()); + + // 2.2 构建签名 Header + StringBuilder canonicalHeaders = new StringBuilder(); // 构造请求头,多个规范化消息头,按照消息头名称(小写)的字符代码顺序以升序排列后拼接在一起 + StringBuilder signedHeadersBuilder = new StringBuilder(); // 已签名消息头列表,多个请求头名称(小写)按首字母升序排列并以英文分号(;)分隔 + headers.entrySet().stream().filter(entry -> entry.getKey().toLowerCase().startsWith("x-acs-") + || entry.getKey().equalsIgnoreCase("host") + || entry.getKey().equalsIgnoreCase("content-type")) + .sorted(Map.Entry.comparingByKey()).forEach(entry -> { + String lowerKey = entry.getKey().toLowerCase(); + canonicalHeaders.append(lowerKey).append(":").append(String.valueOf(entry.getValue()).trim()).append("\n"); + signedHeadersBuilder.append(lowerKey).append(";"); + }); + String signedHeaders = signedHeadersBuilder.substring(0, signedHeadersBuilder.length() - 1); + + // 3. 请求 Body + String requestBody = ""; // 短信 API 为 RPC 接口,query parameters 在 uri 中拼接,因此 request body 如果没有特殊要求,设置为空。 + String hashedRequestBody = DigestUtil.sha256Hex(requestBody); + + // 4. 构建 Authorization 签名 + String canonicalRequest = "POST" + "\n" + "/" + "\n" + queryString + "\n" + + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody; + String hashedCanonicalRequest = DigestUtil.sha256Hex(canonicalRequest); + String stringToSign = "ACS3-HMAC-SHA256" + "\n" + hashedCanonicalRequest; + String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); // 计算签名 + headers.put("Authorization", "ACS3-HMAC-SHA256" + " " + "Credential=" + properties.getApiKey() + + ", " + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature); + + // 5. 发起请求 + String responseBody = HttpUtils.post(URL + "?" + queryString, headers, requestBody); + return JSONUtil.parseObj(responseBody); + } + /** + * 对指定的字符串进行 URL 编码,并对特定的字符进行替换,以符合URL编码规范 + * + * @param str 需要进行 URL 编码的字符串 + * @return 编码后的字符串 + */ + @SneakyThrows + private static String percentCode(String str) { + Assert.notNull(str, "str 不能为空"); + return URLEncoder.encode(str, StandardCharsets.UTF_8.name()) + .replace("+", "%20") // 加号 "+" 被替换为 "%20" + .replace("*", "%2A") // 星号 "*" 被替换为 "%2A" + .replace("%7E", "~"); // 波浪号 "%7E" 被替换为 "~" } -} +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/DebugDingTalkSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/DebugDingTalkSmsClient.java index e9fcc6c41..6d2f2d017 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/DebugDingTalkSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/DebugDingTalkSmsClient.java @@ -36,10 +36,6 @@ public class DebugDingTalkSmsClient extends AbstractSmsClient { Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); } - @Override - protected void doInit() { - } - @Override public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, List> templateParams) throws Throwable { diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java new file mode 100644 index 000000000..82f55395e --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java @@ -0,0 +1,166 @@ +package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; + +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.date.format.FastDateFormat; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import cn.iocoder.yudao.framework.common.core.KeyValue; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; +import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; +import lombok.extern.slf4j.Slf4j; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.*; + +import static cn.hutool.crypto.digest.DigestUtil.sha256Hex; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; + +/** + * 华为短信客户端的实现类 + * + * @author scholar + * @since 2024/6/02 11:55 + */ +@Slf4j +public class HuaweiSmsClient extends AbstractSmsClient { + + private static final String URL = "https://smsapi.cn-north-4.myhuaweicloud.com:443/sms/batchSendSms/v1";//APP接入地址+接口访问URI + private static final String HOST = "smsapi.cn-north-4.myhuaweicloud.com:443"; + private static final String SIGNEDHEADERS = "content-type;host;x-sdk-date"; + + private static final String RESPONSE_CODE_SUCCESS = "000000"; + + public HuaweiSmsClient(SmsChannelProperties properties) { + super(properties); + Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); + Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); + validateSender(properties); + } + + /** + * 参数校验华为云的 sender 通道号 + * + * 原因是:验华为云发放短信的时候,需要额外的参数 sender + * + * 解决方案:考虑到不破坏原有的 apiKey + apiSecret 的结构,所以将 secretId 拼接到 apiKey 字段中,格式为 "secretId sdkAppId"。 + * + * @param properties 配置 + */ + private static void validateSender(SmsChannelProperties properties) { + String combineKey = properties.getApiKey(); + Assert.notEmpty(combineKey, "apiKey 不能为空"); + String[] keys = combineKey.trim().split(" "); + Assert.isTrue(keys.length == 2, "华为云短信 apiKey 配置格式错误,请配置 为[accessKeyId sender]"); + } + + private String getAccessKey() { + return StrUtil.subBefore(properties.getApiKey(), " ", true); + } + + private String getSender() { + return StrUtil.subAfter(properties.getApiKey(), " ", true); + } + + @Override + public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, + List> templateParams) throws Throwable { + StringBuilder requestBody = new StringBuilder(); + appendToBody(requestBody, "from=", getSender()); + appendToBody(requestBody, "&to=", mobile); + appendToBody(requestBody, "&templateId=", apiTemplateId); + appendToBody(requestBody, "&templateParas=", JsonUtils.toJsonString( + convertList(templateParams, kv -> String.valueOf(kv.getValue())))); + appendToBody(requestBody, "&statusCallback=", properties.getCallbackUrl()); + appendToBody(requestBody, "&extend=", String.valueOf(sendLogId)); + JSONObject response = request("/sms/batchSendSms/v1/", "POST", requestBody.toString()); + + // 2. 解析请求 + if (!response.containsKey("result")) { // 例如说:密钥不正确 + return new SmsSendRespDTO().setSuccess(false) + .setApiCode(response.getStr("code")) + .setApiMsg(response.getStr("description")); + } + JSONObject sendResult = response.getJSONArray("result").getJSONObject(0); + return new SmsSendRespDTO().setSuccess(RESPONSE_CODE_SUCCESS.equals(response.getStr("code"))) + .setSerialNo(sendResult.getStr("smsMsgId")).setApiCode(sendResult.getStr("status")); + } + + /** + * 请求华为云短信 + * + * @see https://support.huaweicloud.com/api-msgsms/sms_05_0046.html + * @param uri 请求 URI + * @param method 请求 Method + * @param requestBody 请求 Body + * @return 请求结果 + */ + private JSONObject request(String uri, String method, String requestBody) { + // 1.1 请求 Header + TreeMap headers = new TreeMap<>(); + headers.put("Content-Type", "application/x-www-form-urlencoded"); + String sdkDate = FastDateFormat.getInstance("yyyyMMdd'T'HHmmss'Z'", TimeZone.getTimeZone("UTC")).format(new Date()); + headers.put("X-Sdk-Date", sdkDate); + headers.put("host", HOST); + + // 1.2 构建签名 Header + String canonicalQueryString = ""; // 查询参数为空 + String canonicalHeaders = "content-type:application/x-www-form-urlencoded\n" + + "host:"+ HOST +"\n" + "x-sdk-date:" + sdkDate + "\n"; + String canonicalRequest = method + "\n" + uri + "\n" + canonicalQueryString + "\n" + + canonicalHeaders + "\n" + SIGNEDHEADERS + "\n" + sha256Hex(requestBody); + String stringToSign = "SDK-HMAC-SHA256" + "\n" + sdkDate + "\n" + sha256Hex(canonicalRequest); + String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); // 计算签名 + headers.put("Authorization", "SDK-HMAC-SHA256" + " " + "Access=" + getAccessKey() + + ", " + "SignedHeaders=" + SIGNEDHEADERS + ", " + "Signature=" + signature); + + // 2. 发起请求 + String responseBody = HttpUtils.post(URL, headers, requestBody); + return JSONUtil.parseObj(responseBody); + } + + @Override + public List parseSmsReceiveStatus(String requestBody) { + Map params = HttpUtil.decodeParamMap(requestBody, StandardCharsets.UTF_8); + // 字段参考 https://support.huaweicloud.com/api-msgsms/sms_05_0003.html + return ListUtil.of(new SmsReceiveRespDTO() + .setSuccess("DELIVRD".equals(params.get("status"))) // 是否接收成功 + .setErrorCode(params.get("status")) // 状态报告编码 + .setErrorMsg(params.get("statusDesc")) + .setMobile(params.get("to")) // 手机号 + .setReceiveTime(LocalDateTime.ofInstant(Instant.parse(params.get("updateTime")), ZoneId.of("UTC"))) // 状态报告时间 + .setSerialNo(params.get("smsMsgId")) // 发送序列号 + .setLogId(Long.valueOf(params.get("extend")))); // 用户序列号 + } + + @Override + public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { + // 华为短信模板查询和发送短信,是不同的两套 key 和 secret,与阿里、腾讯的区别较大,这里模板查询校验暂不实现 + String[] strs = apiTemplateId.split(" "); + Assert.isTrue(strs.length == 2, "格式不正确,需要满足:apiTemplateId sender"); + return new SmsTemplateRespDTO().setId(apiTemplateId).setContent(null) + .setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(null); + } + + @SuppressWarnings("CharsetObjectCanBeUsed") + private static void appendToBody(StringBuilder body, String key, String value) throws UnsupportedEncodingException { + if (StrUtil.isNotEmpty(value)) { + body.append(key).append(URLEncoder.encode(value, CharsetUtil.CHARSET_UTF_8.name())); + } + } + +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java new file mode 100644 index 000000000..a041970be --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClient.java @@ -0,0 +1,155 @@ +package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; + +import cn.hutool.core.collection.CollStreamUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.digest.HmacAlgorithm; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import cn.iocoder.yudao.framework.common.core.KeyValue; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; +import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; +import com.google.common.annotations.VisibleForTesting; +import lombok.extern.slf4j.Slf4j; + +import java.util.*; +import java.util.function.Function; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; + +/** + * 七牛云短信客户端的实现类 + * + * @author scholar + * @since 2024/08/26 15:35 + */ +@Slf4j +public class QiniuSmsClient extends AbstractSmsClient { + + private static final String HOST = "sms.qiniuapi.com"; + + public QiniuSmsClient(SmsChannelProperties properties) { + super(properties); + Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); + Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); + } + + public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, + List> templateParams) throws Throwable { + // 1. 执行请求 + // 参考链接 https://developer.qiniu.com/sms/5824/through-the-api-send-text-messages + LinkedHashMap body = new LinkedHashMap<>(); + body.put("template_id", apiTemplateId); + body.put("mobile", mobile); + body.put("parameters", CollStreamUtil.toMap(templateParams, KeyValue::getKey, KeyValue::getValue)); + body.put("seq", Long.toString(sendLogId)); + JSONObject response = request("POST", body, "/v1/message/single"); + + // 2. 解析请求 + if (ObjectUtil.isNotEmpty(response.getStr("error"))) { + // 短信请求失败 + return new SmsSendRespDTO().setSuccess(false) + .setApiCode(response.getStr("error")) + .setApiRequestId(response.getStr("request_id")) + .setApiMsg(response.getStr("message")); + } + return new SmsSendRespDTO().setSuccess(response.containsKey("message_id")) + .setSerialNo(response.getStr("message_id")); + } + + /** + * 请求七牛云短信 + * + * @see + * @param httpMethod http请求方法 + * @param body http请求消息体 + * @param path URL path + * @return 请求结果 + */ + private JSONObject request(String httpMethod, LinkedHashMap body, String path) { + String signDate = DateUtil.date().setTimeZone(TimeZone.getTimeZone("UTC")).toString("yyyyMMdd'T'HHmmss'Z'"); + // 1. 请求头 + Map header = new HashMap<>(4); + header.put("HOST", HOST); + header.put("Authorization", getSignature(httpMethod, path, body != null ? JSONUtil.toJsonStr(body) : "", signDate)); + header.put("Content-Type", "application/json"); + header.put("X-Qiniu-Date", signDate); + + // 2. 发起请求 + String responseBody; + if (Objects.equals(httpMethod, "POST")){ + responseBody = HttpUtils.post("https://" + HOST + path, header, JSONUtil.toJsonStr(body)); + } else { + responseBody = HttpUtils.get("https://" + HOST + path, header); + } + return JSONUtil.parseObj(responseBody); + } + + private String getSignature(String method, String path, String body, String signDate) { + StringBuilder dataToSign = new StringBuilder(); + dataToSign.append(method.toUpperCase()).append(" ").append(path) + .append("\nHost: ").append(HOST) + .append("\n").append("Content-Type").append(": ").append("application/json") + .append("\n").append("X-Qiniu-Date").append(": ").append(signDate) + .append("\n\n"); + if (ObjectUtil.isNotEmpty(body)) { + dataToSign.append(body); + } + String signature = SecureUtil.hmac(HmacAlgorithm.HmacSHA1, properties.getApiSecret()) + .digestBase64(dataToSign.toString(), true); + return "Qiniu " + properties.getApiKey() + ":" + signature; + } + + @Override + public List parseSmsReceiveStatus(String text) { + JSONObject status = JSONUtil.parseObj(text); + // 字段参考 https://developer.qiniu.com/sms/5910/message-push + return convertList(status.getJSONArray("items"), new Function() { + + @Override + public SmsReceiveRespDTO apply(Object item) { + JSONObject statusObj = (JSONObject) item; + return new SmsReceiveRespDTO() + .setSuccess("DELIVRD".equals(statusObj.getStr("status"))) // 是否接收成功 + .setErrorMsg(statusObj.getStr("status")) // 状态报告编码 + .setMobile(statusObj.getStr("mobile")) // 手机号 + .setReceiveTime(LocalDateTimeUtil.of(statusObj.getLong("delivrd_at") * 1000L)) // 状态报告时间 + .setSerialNo(statusObj.getStr("message_id")) // 发送序列号 + .setLogId(statusObj.getLong("seq")); // 用户序列号 + } + + }); + } + + @Override + public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { + // 1. 执行请求 + // 参考链接 https://developer.qiniu.com/sms/5969/query-a-single-template + JSONObject response = request("GET", null, "/v1/template/" + apiTemplateId); + + // 2.2 解析请求 + return new SmsTemplateRespDTO() + .setId(response.getStr("id")) + .setContent(response.getStr("template")) + .setAuditStatus(convertSmsTemplateAuditStatus(response.getStr("audit_status"))) + .setAuditReason(response.getStr("reject_reason")); + } + + @VisibleForTesting + Integer convertSmsTemplateAuditStatus(String templateStatus) { + switch (templateStatus) { + case "passed": return SmsTemplateAuditStatusEnum.SUCCESS.getStatus(); + case "reviewing": return SmsTemplateAuditStatusEnum.CHECKING.getStatus(); + case "rejected": return SmsTemplateAuditStatusEnum.FAIL.getStatus(); + default: + throw new IllegalArgumentException(String.format("未知审核状态(%str)", templateStatus)); + } + } +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java index 94fe88da9..da783189b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java @@ -59,7 +59,7 @@ public class SmsClientFactoryImpl implements SmsClientFactory { } @Override - public void createOrUpdateSmsClient(SmsChannelProperties properties) { + public SmsClient createOrUpdateSmsClient(SmsChannelProperties properties) { AbstractSmsClient client = channelIdClients.get(properties.getId()); if (client == null) { client = this.createSmsClient(properties); @@ -68,6 +68,7 @@ public class SmsClientFactoryImpl implements SmsClientFactory { } else { client.refresh(properties); } + return client; } private AbstractSmsClient createSmsClient(SmsChannelProperties properties) { @@ -78,6 +79,8 @@ public class SmsClientFactoryImpl implements SmsClientFactory { case ALIYUN: return new AliyunSmsClient(properties); case DEBUG_DING_TALK: return new DebugDingTalkSmsClient(properties); case TENCENT: return new TencentSmsClient(properties); + case HUAWEI: return new HuaweiSmsClient(properties); + case QINIU: return new QiniuSmsClient(properties); } // 创建失败,错误日志 + 抛出异常 log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java index dc238be77..19cde8c26 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClient.java @@ -1,30 +1,29 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; +import cn.hutool.core.date.format.FastDateFormat; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; +import cn.hutool.crypto.digest.HmacAlgorithm; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; import cn.iocoder.yudao.framework.common.core.KeyValue; import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; import cn.iocoder.yudao.module.system.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.sms.v20210111.SmsClient; -import com.tencentcloudapi.sms.v20210111.models.*; -import lombok.Data; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Objects; +import java.nio.charset.StandardCharsets; +import java.util.*; +import static cn.hutool.crypto.digest.DigestUtil.sha256Hex; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -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; /** * 腾讯云短信功能实现 @@ -35,16 +34,15 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DE */ public class TencentSmsClient extends AbstractSmsClient { + private static final String HOST = "sms.tencentcloudapi.com"; + private static final String VERSION = "2021-01-11"; + private static final String REGION = "ap-guangzhou"; + /** * 调用成功 code */ public static final String API_CODE_SUCCESS = "Ok"; - /** - * REGION,使用南京 - */ - private static final String ENDPOINT = "ap-nanjing"; - /** * 是否国际/港澳台短信: * @@ -53,21 +51,12 @@ public class TencentSmsClient extends AbstractSmsClient { */ private static final long INTERNATIONAL_CHINA = 0L; - private SmsClient client; - public TencentSmsClient(SmsChannelProperties properties) { super(properties); Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); validateSdkAppId(properties); } - @Override - protected void doInit() { - // 实例化一个认证对象,入参需要传入腾讯云账户密钥对 secretId,secretKey - Credential credential = new Credential(getApiKey(), properties.getApiSecret()); - client = new SmsClient(credential, ENDPOINT); - } - /** * 参数校验腾讯云的 SDK AppId * @@ -95,45 +84,63 @@ public class TencentSmsClient extends AbstractSmsClient { @Override public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, List> templateParams) throws Throwable { - // 构建请求 - SendSmsRequest request = new SendSmsRequest(); - request.setSmsSdkAppId(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))); - // 执行请求 - SendSmsResponse response = client.SendSms(request); - SendStatus status = response.getSendStatusSet()[0]; - return new SmsSendRespDTO().setSuccess(Objects.equals(status.getCode(), API_CODE_SUCCESS)).setSerialNo(status.getSerialNo()) - .setApiRequestId(response.getRequestId()).setApiCode(status.getCode()).setApiMsg(status.getMessage()); + // 1. 执行请求 + // 参考链接 https://cloud.tencent.com/document/product/382/55981 + TreeMap body = new TreeMap<>(); + body.put("PhoneNumberSet", new String[]{mobile}); + body.put("SmsSdkAppId", getSdkAppId()); + body.put("SignName", properties.getSignature()); + body.put("TemplateId", apiTemplateId); + body.put("TemplateParamSet", ArrayUtils.toArray(templateParams, param -> String.valueOf(param.getValue()))); + JSONObject response = request("SendSms", body); + + // 2. 解析请求 + JSONObject responseResult = response.getJSONObject("Response"); + JSONObject error = responseResult.getJSONObject("Error"); + if (error != null) { + return new SmsSendRespDTO().setSuccess(false) + .setApiRequestId(responseResult.getStr("RequestId")) + .setApiCode(error.getStr("Code")) + .setApiMsg(error.getStr("Message")); + } + JSONObject sendResult = responseResult.getJSONArray("SendStatusSet").getJSONObject(0); + return new SmsSendRespDTO().setSuccess(Objects.equals(API_CODE_SUCCESS, sendResult.getStr("Code"))) + .setApiRequestId(responseResult.getStr("RequestId")) + .setSerialNo(sendResult.getStr("SerialNo")) + .setApiMsg(sendResult.getStr("Message")); } @Override public List parseSmsReceiveStatus(String text) { - List callback = JsonUtils.parseArray(text, SmsReceiveStatus.class); - return convertList(callback, status -> new SmsReceiveRespDTO() - .setSuccess(SmsReceiveStatus.SUCCESS_CODE.equalsIgnoreCase(status.getStatus())) - .setErrorCode(status.getErrCode()).setErrorMsg(status.getDescription()) - .setMobile(status.getMobile()).setReceiveTime(status.getReceiveTime()) - .setSerialNo(status.getSerialNo()).setLogId(status.getSessionContext().getLogId())); + JSONArray statuses = JSONUtil.parseArray(text); + // 字段参考 + return convertList(statuses, status -> { + JSONObject statusObj = (JSONObject) status; + return new SmsReceiveRespDTO() + .setSuccess("SUCCESS".equals(statusObj.getStr("report_status"))) // 是否接收成功 + .setErrorCode(statusObj.getStr("errmsg")) // 状态报告编码 + .setMobile(statusObj.getStr("mobile")) // 手机号 + .setReceiveTime(statusObj.getLocalDateTime("user_receive_time", null)) // 状态报告时间 + .setSerialNo(statusObj.getStr("sid")); // 发送序列号 + }); } @Override public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { - // 构建请求 - DescribeSmsTemplateListRequest request = new DescribeSmsTemplateListRequest(); - request.setTemplateIdSet(new Long[]{Long.parseLong(apiTemplateId)}); - request.setInternational(INTERNATIONAL_CHINA); - // 执行请求 - DescribeSmsTemplateListResponse response = client.DescribeSmsTemplateList(request); - DescribeTemplateListStatus status = response.getDescribeTemplateStatusSet()[0]; - if (status == null || status.getStatusCode() == null) { - return null; - } - return new SmsTemplateRespDTO().setId(status.getTemplateId().toString()).setContent(status.getTemplateContent()) - .setAuditStatus(convertSmsTemplateAuditStatus(status.getStatusCode().intValue())).setAuditReason(status.getReviewReply()); + // 1. 构建请求 + // 参考链接 https://cloud.tencent.com/document/product/382/52067 + TreeMap body = new TreeMap<>(); + body.put("International", INTERNATIONAL_CHINA); + body.put("TemplateIdSet", new Integer[]{Integer.valueOf(apiTemplateId)}); + JSONObject response = request("DescribeSmsTemplateList", body); + + // 2. 解析请求 + JSONObject statusResult = response.getJSONObject("Response") + .getJSONArray("DescribeTemplateStatusSet").getJSONObject(0); + return new SmsTemplateRespDTO().setId(apiTemplateId) + .setContent(statusResult.get("TemplateContent").toString()) + .setAuditStatus(convertSmsTemplateAuditStatus(statusResult.getInt("StatusCode"))) + .setAuditReason(statusResult.get("ReviewReply").toString()); } @VisibleForTesting @@ -146,73 +153,49 @@ public class TencentSmsClient extends AbstractSmsClient { } } - @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 LocalDateTime 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; - + /** + * 请求腾讯云短信 + * + * @see 签名方法 v3 + * + * @param action 请求的 API 名称 + * @param body 请求参数 + * @return 请求结果 + */ + private JSONObject request(String action, TreeMap body) { + // 1.1 请求 Header + Map headers = new HashMap<>(); + headers.put("Content-Type", "application/json; charset=utf-8"); + headers.put("Host", HOST); + headers.put("X-TC-Action", action); + Date now = new Date(); + String nowStr = FastDateFormat.getInstance("yyyy-MM-dd", TimeZone.getTimeZone("UTC")).format(now); + headers.put("X-TC-Timestamp", String.valueOf(now.getTime() / 1000)); + headers.put("X-TC-Version", VERSION); + headers.put("X-TC-Region", REGION); + + // 1.2 构建签名 Header + String canonicalQueryString = ""; + String canonicalHeaders = "content-type:application/json; charset=utf-8\n" + + "host:" + HOST + "\n" + "x-tc-action:" + action.toLowerCase() + "\n"; + String signedHeaders = "content-type;host;x-tc-action"; + String canonicalRequest = "POST" + "\n" + "/" + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + + signedHeaders + "\n" + sha256Hex(JSONUtil.toJsonStr(body)); + String credentialScope = nowStr + "/" + "sms" + "/" + "tc3_request"; + String stringToSign = "TC3-HMAC-SHA256" + "\n" + now.getTime() / 1000 + "\n" + credentialScope + "\n" + + sha256Hex(canonicalRequest); + byte[] secretService = hmac256(hmac256(("TC3" + properties.getApiSecret()).getBytes(StandardCharsets.UTF_8), nowStr), "sms"); + String signature = HexUtil.encodeHexStr(hmac256(hmac256(secretService, "tc3_request"), stringToSign)); + headers.put("Authorization", "TC3-HMAC-SHA256" + " " + "Credential=" + getApiKey() + "/" + credentialScope + ", " + + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature); + + // 2. 发起请求 + String responseBody = HttpUtils.post("https://" + HOST, headers, JSONUtil.toJsonStr(body)); + return JSONUtil.parseObj(responseBody); } - @VisibleForTesting - @Data - static class SessionContext { - - /** - * 发送短信记录id - */ - private Long logId; - + private static byte[] hmac256(byte[] key, String msg) { + return DigestUtil.hmac(HmacAlgorithm.HmacSHA256, key).digest(msg); } -} +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/enums/SmsChannelEnum.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/enums/SmsChannelEnum.java index 7bd192223..cbbde696b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/enums/SmsChannelEnum.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/enums/SmsChannelEnum.java @@ -17,7 +17,8 @@ public enum SmsChannelEnum { DEBUG_DING_TALK("DEBUG_DING_TALK", "调试(钉钉)"), ALIYUN("ALIYUN", "阿里云"), TENCENT("TENCENT", "腾讯云"), -// HUA_WEI("HUA_WEI", "华为云"), + HUAWEI("HUAWEI", "华为云"), + QINIU("QINIU", "七牛云"), ; /** @@ -34,3 +35,4 @@ public enum SmsChannelEnum { } } + diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java index 228a9ad6a..280a8304b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java @@ -13,7 +13,7 @@ import javax.validation.Valid; * @author 芋道源码 */ public interface AdminAuthService { - Boolean getCaptchaEnable(); + /** * 验证账号 + 密码。如果通过,则返回用户 * @@ -70,4 +70,12 @@ public interface AdminAuthService { */ AuthLoginRespVO refreshToken(String refreshToken); + /** + * 用户注册 + * + * @param createReqVO 注册用户 + * @return 注册结果 + */ + AuthLoginRespVO register(AuthRegisterReqVO createReqVO); + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java index f108fb3a3..2a189d98e 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java @@ -71,23 +71,11 @@ public class AdminAuthServiceImpl implements AdminAuthService { @Value("${yudao.captcha.enable:true}") private Boolean captchaEnable; - @Override - public Boolean getCaptchaEnable(){ - return captchaEnable; - } - public static boolean is13DigitPhoneNumber(String str) { - // 检查字符串是否为13位并且是否全由数字组成 - return str != null && str.length() == 11 && str.matches("\\d+"); - } @Override public AdminUserDO authenticate(String username, String password) { final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME; // 校验账号是否存在 - AdminUserDO user = null; - if(is13DigitPhoneNumber(username)){ - user = userService.getUserByMobile(username); - } - else user = userService.getUserByUsername(username); + AdminUserDO user = userService.getUserByUsername(username); if (user == null) { createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS); throw exception(AUTH_LOGIN_BAD_CREDENTIALS); @@ -259,4 +247,33 @@ public class AdminAuthServiceImpl implements AdminAuthService { return UserTypeEnum.ADMIN; } + @Override + public AuthLoginRespVO register(AuthRegisterReqVO registerReqVO) { + // 1. 校验验证码 + validateCaptcha(registerReqVO); + + // 2. 校验用户名是否已存在 + Long userId = userService.registerUser(registerReqVO); + + // 3. 创建 Token 令牌,记录登录日志 + return createTokenAfterLoginSuccess(userId, registerReqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME); + } + + @VisibleForTesting + void validateCaptcha(AuthRegisterReqVO reqVO) { + // 如果验证码关闭,则不进行校验 + if (!captchaEnable) { + return; + } + // 校验验证码 + ValidationUtils.validate(validator, reqVO, AuthLoginReqVO.CodeEnableGroup.class); + CaptchaVO captchaVO = new CaptchaVO(); + captchaVO.setCaptchaVerification(reqVO.getCaptchaVerification()); + ResponseModel response = captchaService.verification(captchaVO); + // 验证不通过 + if (!response.isSuccess()) { + throw exception(AUTH_REGISTER_CAPTCHA_CODE_ERROR, response.getRepMsg()); + } + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptService.java index cb9e71f78..a0b765e59 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptService.java @@ -67,7 +67,6 @@ public interface DeptService { * @return 部门 Map */ default Map getDeptMap(Collection ids) { - if(ids.isEmpty())return new HashMap<>(); List list = getDeptList(ids); return CollectionUtils.convertMap(list, DeptDO::getId); } @@ -78,7 +77,25 @@ public interface DeptService { * @param id 部门编号 * @return 子部门列表 */ - List getChildDeptList(Long id); + default List getChildDeptList(Long id) { + return getChildDeptList(Collections.singleton(id)); + } + + /** + * 获得指定部门的所有子部门 + * + * @param ids 部门编号数组 + * @return 子部门列表 + */ + List getChildDeptList(Collection ids); + + /** + * 获得指定领导者的部门列表 + * + * @param id 领导者编号 + * @return 部门列表 + */ + List getDeptListByLeaderUserId(Long id); /** * 获得所有子部门,从缓存中 diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java index 183f11fc7..94a443535 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java @@ -170,10 +170,10 @@ public class DeptServiceImpl implements DeptService { } @Override - public List getChildDeptList(Long id) { + public List getChildDeptList(Collection ids) { List children = new LinkedList<>(); // 遍历每一层 - Collection parentIds = Collections.singleton(id); + Collection parentIds = ids; for (int i = 0; i < Short.MAX_VALUE; i++) { // 使用 Short.MAX_VALUE 避免 bug 场景下,存在死循环 // 查询当前层,所有的子部门 List depts = deptMapper.selectListByParentId(parentIds); @@ -188,6 +188,11 @@ public class DeptServiceImpl implements DeptService { return children; } + @Override + public List getDeptListByLeaderUserId(Long id) { + return deptMapper.selectListByLeaderUserId(id); + } + @Override @DataPermission(enable = false) // 禁用数据权限,避免建立不正确的缓存 @Cacheable(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, key = "#id") diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImpl.java index d2cfa7137..04bc74447 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImpl.java @@ -9,8 +9,10 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO; @@ -56,7 +58,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { private AdminUserService adminUserService; @Override - @Transactional + @Transactional(rollbackFor = Exception.class) public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String clientId, List scopes) { OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId); // 创建刷新令牌 @@ -66,6 +68,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { } @Override + @Transactional(rollbackFor = Exception.class) public OAuth2AccessTokenDO refreshAccessToken(String refreshToken, String clientId) { // 查询访问令牌 OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectByRefreshToken(refreshToken); @@ -82,7 +85,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { // 移除相关的访问令牌 List accessTokenDOs = oauth2AccessTokenMapper.selectListByRefreshToken(refreshToken); if (CollUtil.isNotEmpty(accessTokenDOs)) { - oauth2AccessTokenMapper.deleteBatchIds(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getId)); + oauth2AccessTokenMapper.deleteByIds(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getId)); oauth2AccessTokenRedisDAO.deleteList(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getAccessToken)); } @@ -104,8 +107,18 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { return accessTokenDO; } - // 获取不到,从 MySQL 中获取 + // 获取不到,从 MySQL 中获取访问令牌 accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken); + if (accessTokenDO == null) { + // 特殊:从 MySQL 中获取刷新令牌。原因:解决部分场景不方便刷新访问令牌场景 + // 例如说,积木报表只允许传递 token,不允许传递 refresh_token,导致无法刷新访问令牌 + // 再例如说,前端 WebSocket 的 token 直接跟在 url 上,无法传递 refresh_token + OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectByRefreshToken(accessToken); + if (refreshTokenDO != null && !DateUtils.isExpired(refreshTokenDO.getExpiresTime())) { + accessTokenDO = convertToAccessToken(refreshTokenDO); + } + } + // 如果在 MySQL 存在,则往 Redis 中写入 if (accessTokenDO != null && !DateUtils.isExpired(accessTokenDO.getExpiresTime())) { oauth2AccessTokenRedisDAO.set(accessTokenDO); @@ -126,6 +139,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { } @Override + @Transactional(rollbackFor = Exception.class) public OAuth2AccessTokenDO removeAccessToken(String accessToken) { // 删除访问令牌 OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken); @@ -167,6 +181,14 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { return refreshToken; } + private OAuth2AccessTokenDO convertToAccessToken(OAuth2RefreshTokenDO refreshTokenDO) { + OAuth2AccessTokenDO accessTokenDO = BeanUtils.toBean(refreshTokenDO, OAuth2AccessTokenDO.class) + .setAccessToken(refreshTokenDO.getRefreshToken()); + TenantUtils.execute(refreshTokenDO.getTenantId(), + () -> accessTokenDO.setUserInfo(buildUserInfo(refreshTokenDO.getUserId(), refreshTokenDO.getUserType()))); + return accessTokenDO; + } + /** * 加载用户信息,方便 {@link cn.iocoder.yudao.framework.security.core.LoginUser} 获取到昵称、部门等信息 * diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java index adc2b3151..d74dc6134 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuService.java @@ -52,6 +52,14 @@ public interface MenuService { */ List getMenuListByTenant(MenuListReqVO reqVO); + /** + * 过滤掉关闭的菜单及其子菜单 + * + * @param list 菜单列表 + * @return 过滤后的菜单列表 + */ + List filterDisableMenus(List list); + /** * 筛选菜单列表 * diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java index e20bd70af..d253cd6ca 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.system.service.permission; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuListReqVO; import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuSaveVO; @@ -19,11 +21,11 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; -import java.util.Collection; -import java.util.List; +import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; import static cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO.ID_ROOT; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; @@ -106,12 +108,59 @@ public class MenuServiceImpl implements MenuService { @Override public List getMenuListByTenant(MenuListReqVO reqVO) { + // 查询所有菜单,并过滤掉关闭的节点 List menus = getMenuList(reqVO); // 开启多租户的情况下,需要过滤掉未开通的菜单 tenantService.handleTenantMenu(menuIds -> menus.removeIf(menu -> !CollUtil.contains(menuIds, menu.getId()))); return menus; } + @Override + public List filterDisableMenus(List menuList) { + if (CollUtil.isEmpty(menuList)){ + return Collections.emptyList(); + } + Map menuMap = convertMap(menuList, MenuDO::getId); + + // 遍历 menu 菜单,查找不是禁用的菜单,添加到 enabledMenus 结果 + List enabledMenus = new ArrayList<>(); + Set disabledMenuCache = new HashSet<>(); // 存下递归搜索过被禁用的菜单,防止重复的搜索 + for (MenuDO menu : menuList) { + if (isMenuDisabled(menu, menuMap, disabledMenuCache)) { + continue; + } + enabledMenus.add(menu); + } + return enabledMenus; + } + + private boolean isMenuDisabled(MenuDO node, Map menuMap, Set disabledMenuCache) { + // 如果已经判定是禁用的节点,直接结束 + if (disabledMenuCache.contains(node.getId())) { + return true; + } + + // 1. 先判断自身是否禁用 + if (CommonStatusEnum.isDisable(node.getStatus())) { + disabledMenuCache.add(node.getId()); + return true; + } + + // 2. 遍历到 parentId 为根节点,则无需判断 + Long parentId = node.getParentId(); + if (ObjUtil.equal(parentId, ID_ROOT)) { + return false; + } + + // 3. 继续遍历 parent 节点 + MenuDO parent = menuMap.get(parentId); + if (parent == null || isMenuDisabled(parent, menuMap, disabledMenuCache)) { + disabledMenuCache.add(node.getId()); + return true; + } + return false; + } + @Override public List getMenuList(MenuListReqVO reqVO) { return menuMapper.selectList(reqVO); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java index dba2831ca..cef739dfa 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java @@ -132,8 +132,12 @@ public class PermissionServiceImpl implements PermissionService { @Override @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换 - @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST, + @Caching(evict = { + @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST, + allEntries = true), + @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, allEntries = true) // allEntries 清空所有缓存,主要一次更新涉及到的 menuIds 较多,反倒批量会更快 + }) public void assignRoleMenu(Long roleId, Set menuIds) { // 获得角色拥有菜单编号 Set dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java index fc5c6de19..6203cd7d3 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.service.permission; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; @@ -18,6 +19,7 @@ import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum; import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum; import com.google.common.annotations.VisibleForTesting; import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; import com.mzt.logapi.starter.annotation.LogRecord; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.CacheEvict; @@ -60,7 +62,7 @@ public class RoleServiceImpl implements RoleService { // 2. 插入到数据库 RoleDO role = BeanUtils.toBean(createReqVO, RoleDO.class) .setType(ObjectUtil.defaultIfNull(type, RoleTypeEnum.CUSTOM.getType())) - .setStatus(CommonStatusEnum.ENABLE.getStatus()) + .setStatus(ObjUtil.defaultIfNull(createReqVO.getStatus(), CommonStatusEnum.ENABLE.getStatus())) .setDataScope(DataScopeEnum.ALL.getScope()); // 默认可查看所有数据。原因是,可能一些项目不需要项目权限 roleMapper.insert(role); @@ -116,6 +118,7 @@ public class RoleServiceImpl implements RoleService { permissionService.processRoleDeleted(id); // 3. 记录操作日志上下文 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(role, RoleSaveReqVO.class)); LogRecordContext.putVariable("role", role); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java index a7455f152..d7b6124c0 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceImpl.java @@ -1,27 +1,21 @@ package cn.iocoder.yudao.module.system.service.sms; -import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient; -import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClientFactory; -import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelSaveReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO; import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import lombok.Getter; +import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient; +import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClientFactory; +import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.Resource; -import java.time.Duration; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS; @@ -34,46 +28,6 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNE @Slf4j public class SmsChannelServiceImpl implements SmsChannelService { - /** - * {@link SmsClient} 缓存,通过它异步刷新 smsClientFactory - */ - @Getter - private final LoadingCache idClientCache = buildAsyncReloadingCache(Duration.ofSeconds(10L), - new CacheLoader() { - - @Override - public SmsClient load(Long id) { - // 查询,然后尝试刷新 - SmsChannelDO channel = smsChannelMapper.selectById(id); - if (channel != null) { - SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class); - smsClientFactory.createOrUpdateSmsClient(properties); - } - return smsClientFactory.getSmsClient(id); - } - - }); - - /** - * {@link SmsClient} 缓存,通过它异步刷新 smsClientFactory - */ - @Getter - private final LoadingCache codeClientCache = buildAsyncReloadingCache(Duration.ofSeconds(60L), - new CacheLoader() { - - @Override - public SmsClient load(String code) { - // 查询,然后尝试刷新 - SmsChannelDO channel = smsChannelMapper.selectByCode(code); - if (channel != null) { - SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class); - smsClientFactory.createOrUpdateSmsClient(properties); - } - return smsClientFactory.getSmsClient(code); - } - - }); - @Resource private SmsClientFactory smsClientFactory; @@ -93,41 +47,22 @@ public class SmsChannelServiceImpl implements SmsChannelService { @Override public void updateSmsChannel(SmsChannelSaveReqVO updateReqVO) { // 校验存在 - SmsChannelDO channel = validateSmsChannelExists(updateReqVO.getId()); + validateSmsChannelExists(updateReqVO.getId()); // 更新 SmsChannelDO updateObj = BeanUtils.toBean(updateReqVO, SmsChannelDO.class); smsChannelMapper.updateById(updateObj); - - // 清空缓存 - clearCache(updateReqVO.getId(), channel.getCode()); } @Override public void deleteSmsChannel(Long id) { // 校验存在 - SmsChannelDO channel = validateSmsChannelExists(id); + validateSmsChannelExists(id); // 校验是否有在使用该账号的模版 if (smsTemplateService.getSmsTemplateCountByChannelId(id) > 0) { throw exception(SMS_CHANNEL_HAS_CHILDREN); } // 删除 smsChannelMapper.deleteById(id); - - // 清空缓存 - clearCache(id, channel.getCode()); - } - - /** - * 清空指定渠道编号的缓存 - * - * @param id 渠道编号 - * @param code 渠道编码 - */ - private void clearCache(Long id, String code) { - idClientCache.invalidate(id); - if (StrUtil.isNotEmpty(code)) { - codeClientCache.invalidate(code); - } } private SmsChannelDO validateSmsChannelExists(Long id) { @@ -155,12 +90,14 @@ public class SmsChannelServiceImpl implements SmsChannelService { @Override public SmsClient getSmsClient(Long id) { - return idClientCache.getUnchecked(id); + SmsChannelDO channel = smsChannelMapper.selectById(id); + SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class); + return smsClientFactory.createOrUpdateSmsClient(properties); } @Override public SmsClient getSmsClient(String code) { - return codeClientCache.getUnchecked(code); + return smsClientFactory.getSmsClient(code); } } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsCodeServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsCodeServiceImpl.java index 79162c623..de84605ef 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsCodeServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/sms/SmsCodeServiceImpl.java @@ -67,7 +67,8 @@ public class SmsCodeServiceImpl implements SmsCodeService { } // 创建验证码记录 - String code = String.valueOf(randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1)); + String code = String.format("%0" + smsCodeProperties.getEndCode().toString().length() + "d", + randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1)); SmsCodeDO newSmsCode = SmsCodeDO.builder().mobile(mobile).code(code).scene(scene) .todayIndex(lastSmsCode != null && isToday(lastSmsCode.getCreateTime()) ? lastSmsCode.getTodayIndex() + 1 : 1) .createIp(ip).used(false).build(); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java index dba86c699..71c716c52 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java @@ -2,12 +2,17 @@ package cn.iocoder.yudao.module.system.service.social; import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import com.xingyuv.jushauth.model.AuthUser; import me.chanjar.weixin.common.bean.WxJsapiSignature; +import me.chanjar.weixin.common.bean.subscribemsg.TemplateInfo; + +import java.util.List; import javax.validation.Valid; @@ -21,8 +26,8 @@ public interface SocialClientService { /** * 获得社交平台的授权 URL * - * @param socialType 社交平台的类型 {@link SocialTypeEnum} - * @param userType 用户类型 + * @param socialType 社交平台的类型 {@link SocialTypeEnum} + * @param userType 用户类型 * @param redirectUri 重定向 URL * @return 社交平台的授权 URL */ @@ -32,9 +37,9 @@ public interface SocialClientService { * 请求社交平台,获得授权的用户 * * @param socialType 社交平台的类型 - * @param userType 用户类型 - * @param code 授权码 - * @param state 授权 state + * @param userType 用户类型 + * @param code 授权码 + * @param state 授权 state * @return 授权的用户 */ AuthUser getAuthUser(Integer socialType, Integer userType, String code, String state); @@ -45,7 +50,7 @@ public interface SocialClientService { * 创建微信公众号的 JS SDK 初始化所需的签名 * * @param userType 用户类型 - * @param url 访问的 URL 地址 + * @param url 访问的 URL 地址 * @return 签名 */ WxJsapiSignature createWxMpJsapiSignature(Integer userType, String url); @@ -55,12 +60,39 @@ public interface SocialClientService { /** * 获得微信小程序的手机信息 * - * @param userType 用户类型 + * @param userType 用户类型 * @param phoneCode 手机授权码 * @return 手机信息 */ WxMaPhoneNumberInfo getWxMaPhoneNumberInfo(Integer userType, String phoneCode); + /** + * 获得小程序二维码 + * + * @param reqVO 请求信息 + * @return 小程序二维码 + */ + byte[] getWxaQrcode(SocialWxQrcodeReqDTO reqVO); + + /** + * 获得微信小程订阅模板 + * + * 缓存的目的:考虑到微信小程序订阅消息选择好模版后几乎不会变动,缓存增加查询效率 + * + * @param userType 用户类型 + * @return 微信小程订阅模板 + */ + List getSubscribeTemplateList(Integer userType); + + /** + * 发送微信小程序订阅消息 + * + * @param reqDTO 请求 + * @param templateId 模版编号 + * @param openId 会员 openId + */ + void sendSubscribeMessage(SocialWxaSubscribeMessageSendReqDTO reqDTO, String templateId, String openId); + // =================== 客户端管理 =================== /** diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java index 23b3cea5c..8d1927e5d 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java @@ -1,22 +1,30 @@ package cn.iocoder.yudao.module.system.service.social; import cn.binarywang.wx.miniapp.api.WxMaService; +import cn.binarywang.wx.miniapp.api.WxMaSubscribeService; import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; +import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage; import cn.binarywang.wx.miniapp.config.impl.WxMaRedisBetterConfigImpl; +import cn.binarywang.wx.miniapp.constant.WxMaConstants; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ReflectUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.cache.CacheUtils; import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO; import cn.iocoder.yudao.module.system.dal.mysql.social.SocialClientMapper; +import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties; import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; @@ -33,19 +41,25 @@ import com.xingyuv.justauth.AuthRequestFactory; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.bean.WxJsapiSignature; +import me.chanjar.weixin.common.bean.subscribemsg.TemplateInfo; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; import me.chanjar.weixin.mp.api.WxMpService; import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.time.Duration; +import java.util.List; +import java.util.Map; import java.util.Objects; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; @@ -58,6 +72,25 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; @Slf4j public class SocialClientServiceImpl implements SocialClientService { + /** + * 小程序码要打开的小程序版本 + * + * 1. release:正式版 + * 2. trial:体验版 + * 3. developer:开发版 + */ + @Value("${yudao.wxa-code.env-version:release}") + public String envVersion; + /** + * 订阅消息跳转小程序类型 + * + * 1. developer:开发版 + * 2. trial:体验版 + * 3. formal:正式版 + */ + @Value("${yudao.wxa-subscribe-message.miniprogram-state:formal}") + public String miniprogramState; + @Resource private AuthRequestFactory authRequestFactory; @@ -139,7 +172,7 @@ public class SocialClientServiceImpl implements SocialClientService { * 构建 AuthRequest 对象,支持多租户配置 * * @param socialType 社交类型 - * @param userType 用户类型 + * @param userType 用户类型 * @return AuthRequest 对象 */ @VisibleForTesting @@ -196,7 +229,7 @@ public class SocialClientServiceImpl implements SocialClientService { /** * 创建 clientId + clientSecret 对应的 WxMpService 对象 * - * @param clientId 微信公众号 appId + * @param clientId 微信公众号 appId * @param clientSecret 微信公众号 secret * @return WxMpService 对象 */ @@ -227,6 +260,73 @@ public class SocialClientServiceImpl implements SocialClientService { } } + @Override + public byte[] getWxaQrcode(SocialWxQrcodeReqDTO reqVO) { + WxMaService service = getWxMaService(UserTypeEnum.MEMBER.getValue()); + try { + return service.getQrcodeService().createWxaCodeUnlimitBytes( + ObjUtil.defaultIfEmpty(reqVO.getScene(), SocialWxQrcodeReqDTO.SCENE), + reqVO.getPath(), + ObjUtil.defaultIfNull(reqVO.getCheckPath(), SocialWxQrcodeReqDTO.CHECK_PATH), + envVersion, + ObjUtil.defaultIfNull(reqVO.getWidth(), SocialWxQrcodeReqDTO.WIDTH), + ObjUtil.defaultIfNull(reqVO.getAutoColor(), SocialWxQrcodeReqDTO.AUTO_COLOR), + null, + ObjUtil.defaultIfNull(reqVO.getHyaline(), SocialWxQrcodeReqDTO.HYALINE)); + } catch (WxErrorException e) { + log.error("[getWxQrcode][reqVO({}) 获得小程序码失败]", reqVO, e); + throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_QRCODE_ERROR); + } + } + + @Override + @Cacheable(cacheNames = RedisKeyConstants.WXA_SUBSCRIBE_TEMPLATE, key = "#userType", + unless = "#result == null") + public List getSubscribeTemplateList(Integer userType) { + WxMaService service = getWxMaService(userType); + try { + WxMaSubscribeService subscribeService = service.getSubscribeService(); + return subscribeService.getTemplateList(); + } catch (WxErrorException e) { + log.error("[getSubscribeTemplate][userType({}) 获得小程序订阅消息模版]", userType, e); + throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_SUBSCRIBE_TEMPLATE_ERROR); + } + } + + @Override + public void sendSubscribeMessage(SocialWxaSubscribeMessageSendReqDTO reqDTO, String templateId, String openId) { + WxMaService service = getWxMaService(reqDTO.getUserType()); + try { + WxMaSubscribeService subscribeService = service.getSubscribeService(); + subscribeService.sendSubscribeMsg(buildMessageSendReqDTO(reqDTO, templateId, openId)); + } catch (WxErrorException e) { + log.error("[sendSubscribeMessage][reqVO({}) templateId({}) openId({}) 发送小程序订阅消息失败]", reqDTO, templateId, openId, e); + throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_SUBSCRIBE_MESSAGE_ERROR); + } + } + + /** + * 构建发送消息请求参数 + * + * @param reqDTO 请求 + * @param templateId 模版编号 + * @param openId 会员 openId + * @return 微信小程序订阅消息请求参数 + */ + private WxMaSubscribeMessage buildMessageSendReqDTO(SocialWxaSubscribeMessageSendReqDTO reqDTO, + String templateId, String openId) { + // 设置订阅消息基本参数 + WxMaSubscribeMessage subscribeMessage = new WxMaSubscribeMessage().setLang(WxMaConstants.MiniProgramLang.ZH_CN) + .setMiniprogramState(miniprogramState).setTemplateId(templateId).setToUser(openId).setPage(reqDTO.getPage()); + // 设置具体消息参数 + Map messages = reqDTO.getMessages(); + if (CollUtil.isNotEmpty(messages)) { + reqDTO.getMessages().keySet().forEach(key -> findAndThen(messages, key, value -> + subscribeMessage.addData(new WxMaSubscribeMessage.MsgData(key, value)))); + } + return subscribeMessage; + } + /** * 获得 clientId + clientSecret 对应的 WxMpService 对象 * @@ -248,7 +348,7 @@ public class SocialClientServiceImpl implements SocialClientService { /** * 创建 clientId + clientSecret 对应的 WxMaService 对象 * - * @param clientId 微信小程序 appId + * @param clientId 微信小程序 appId * @param clientSecret 微信小程序 secret * @return WxMaService 对象 */ @@ -310,8 +410,8 @@ public class SocialClientServiceImpl implements SocialClientService { * * 原因是,不同端(userType)选择某个社交登录(socialType)时,需要通过 {@link #buildAuthRequest(Integer, Integer)} 构建对应的请求 * - * @param id 编号 - * @param userType 用户类型 + * @param id 编号 + * @param userType 用户类型 * @param socialType 社交类型 */ private void validateSocialClientUnique(Long id, Integer userType, Integer socialType) { diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java index b8fa3f74c..21128a569 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.user.SocialUserPageReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java index 7458d9cd9..29c4f06a2 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.user.SocialUserPageReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO; import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java index e715a6968..a695a40d6 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java @@ -1,16 +1,23 @@ package cn.iocoder.yudao.module.system.service.user; import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthRegisterReqVO; import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO; import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO; -import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.*; -import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserImportExcelVO; +import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserImportRespVO; +import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserPageReqVO; +import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import javax.validation.Valid; import java.io.InputStream; -import java.util.*; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * 后台用户 Service 接口 @@ -27,6 +34,14 @@ public interface AdminUserService { */ Long createUser(@Valid UserSaveReqVO createReqVO); + /** + * 注册用户 + * + * @param registerReqVO 用户信息 + * @return 用户编号 + */ + Long registerUser(@Valid AuthRegisterReqVO registerReqVO); + /** * 修改用户 * diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java index d37e093fb..040802cbc 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java @@ -9,8 +9,11 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils; +import cn.iocoder.yudao.module.infra.api.config.ConfigApi; import cn.iocoder.yudao.module.infra.api.file.FileApi; +import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthRegisterReqVO; import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO; import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO; import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserImportExcelVO; @@ -31,20 +34,19 @@ import com.mzt.logapi.context.LogRecordContext; import com.mzt.logapi.service.impl.DiffParseFunction; import com.mzt.logapi.starter.annotation.LogRecord; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; +import javax.validation.ConstraintViolationException; import java.io.InputStream; import java.time.LocalDateTime; import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.system.enums.LogRecordConstants.*; @@ -57,8 +59,7 @@ import static cn.iocoder.yudao.module.system.enums.LogRecordConstants.*; @Slf4j public class AdminUserServiceImpl implements AdminUserService { - @Value("${sys.user.init-password:yudaoyuanma}") - private String userInitPassword; + static final String USER_INIT_PASSWORD_KEY = "system.user.init-password"; @Resource private AdminUserMapper userMapper; @@ -80,6 +81,8 @@ public class AdminUserServiceImpl implements AdminUserService { @Resource private FileApi fileApi; + @Resource + private ConfigApi configApi; @Override @Transactional(rollbackFor = Exception.class) @@ -112,6 +115,26 @@ public class AdminUserServiceImpl implements AdminUserService { return user.getId(); } + @Override + public Long registerUser(AuthRegisterReqVO registerReqVO) { + // 1.1 校验账户配合 + tenantService.handleTenantInfo(tenant -> { + long count = userMapper.selectCount(); + if (count >= tenant.getAccountCount()) { + throw exception(USER_COUNT_MAX, tenant.getAccountCount()); + } + }); + // 1.2 校验正确性 + validateUserForCreateOrUpdate(null, registerReqVO.getUsername(), null, null, null, null); + + // 2. 插入用户 + AdminUserDO user = BeanUtils.toBean(registerReqVO, AdminUserDO.class); + user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启 + user.setPassword(encodePassword(registerReqVO.getPassword())); // 加密密码 + userMapper.insert(user); + return user.getId(); + } + @Override @Transactional(rollbackFor = Exception.class) @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", @@ -248,7 +271,12 @@ public class AdminUserServiceImpl implements AdminUserService { @Override public PageResult getUserPage(UserPageReqVO reqVO) { - return userMapper.selectPage(reqVO, getDeptCondition(reqVO.getDeptId())); + // 如果有角色编号,查询角色对应的用户编号 + Set userIds = reqVO.getRoleId() != null ? + permissionService.getUserRoleIdListByRoleId(singleton(reqVO.getRoleId())) : null; + + // 分页查询 + return userMapper.selectPage(reqVO, getDeptCondition(reqVO.getDeptId()), userIds); } @Override @@ -311,6 +339,7 @@ public class AdminUserServiceImpl implements AdminUserService { /** * 获得部门条件:查询指定部门的子部门编号们,包括自身 + * * @param deptId 部门编号 * @return 部门编号集合 */ @@ -428,13 +457,28 @@ public class AdminUserServiceImpl implements AdminUserService { @Override @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入 public UserImportRespVO importUserList(List importUsers, boolean isUpdateSupport) { + // 1.1 参数校验 if (CollUtil.isEmpty(importUsers)) { throw exception(USER_IMPORT_LIST_IS_EMPTY); } + // 1.2 初始化密码不能为空 + String initPassword = configApi.getConfigValueByKey(USER_INIT_PASSWORD_KEY); + if (StrUtil.isEmpty(initPassword)) { + throw exception(USER_IMPORT_INIT_PASSWORD); + } + + // 2. 遍历,逐个创建 or 更新 UserImportRespVO respVO = UserImportRespVO.builder().createUsernames(new ArrayList<>()) .updateUsernames(new ArrayList<>()).failureUsernames(new LinkedHashMap<>()).build(); importUsers.forEach(importUser -> { - // 校验,判断是否有不符合的原因 + // 2.1.1 校验字段是否符合要求 + try { + ValidationUtils.validate(BeanUtils.toBean(importUser, UserSaveReqVO.class).setPassword(initPassword)); + } catch (ConstraintViolationException ex){ + respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage()); + return; + } + // 2.1.2 校验,判断是否有不符合的原因 try { validateUserForCreateOrUpdate(null, null, importUser.getMobile(), importUser.getEmail(), importUser.getDeptId(), null); @@ -442,15 +486,16 @@ public class AdminUserServiceImpl implements AdminUserService { respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage()); return; } - // 判断如果不存在,在进行插入 + + // 2.2.1 判断如果不存在,在进行插入 AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername()); if (existUser == null) { userMapper.insert(BeanUtils.toBean(importUser, AdminUserDO.class) - .setPassword(encodePassword(userInitPassword)).setPostIds(new HashSet<>())); // 设置默认密码及空岗位编号数组 + .setPassword(encodePassword(initPassword)).setPostIds(new HashSet<>())); // 设置默认密码及空岗位编号数组 respVO.getCreateUsernames().add(importUser.getUsername()); return; } - // 如果存在,判断是否允许更新 + // 2.2.2 如果存在,判断是否允许更新 if (!isUpdateSupport) { respVO.getFailureUsernames().put(importUser.getUsername(), USER_USERNAME_EXISTS.getMsg()); return; diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java index ac26d139b..093060e84 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java @@ -1,36 +1,30 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; -import cn.hutool.core.util.ReflectUtil; import cn.iocoder.yudao.framework.common.core.KeyValue; -import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; -import com.aliyuncs.IAcsClient; -import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest; -import com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateResponse; -import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest; -import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse; import com.google.common.collect.Lists; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatcher; import org.mockito.InjectMocks; -import org.mockito.Mock; +import org.mockito.MockedStatic; import java.time.LocalDateTime; 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 cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.when; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mockStatic; /** - * {@link AliyunSmsClient} 的单元测试 + * {@link cn.iocoder.yudao.module.system.framework.sms.core.client.impl.AliyunSmsClient} 的单元测试 * * @author 芋道源码 */ @@ -44,77 +38,53 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest { @InjectMocks private final AliyunSmsClient smsClient = new AliyunSmsClient(properties); - @Mock - private IAcsClient client; - - @Test - public void testDoInit() { - // 准备参数 - // mock 方法 - - // 调用 - smsClient.doInit(); - // 断言 - assertNotSame(client, ReflectUtil.getFieldValue(smsClient, "acsClient")); - } - @Test public void tesSendSms_success() throws Throwable { - // 准备参数 - Long sendLogId = randomLongId(); - String mobile = randomString(); - String apiTemplateId = randomString(); - List> templateParams = Lists.newArrayList( - new KeyValue<>("code", 1234), new KeyValue<>("op", "login")); - // mock 方法 - SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> o.setCode("OK")); - when(client.getAcsResponse(argThat((ArgumentMatcher) acsRequest -> { - assertEquals(mobile, acsRequest.getPhoneNumbers()); - assertEquals(properties.getSignature(), acsRequest.getSignName()); - assertEquals(apiTemplateId, acsRequest.getTemplateCode()); - assertEquals(toJsonString(MapUtils.convertMap(templateParams)), acsRequest.getTemplateParam()); - assertEquals(sendLogId.toString(), acsRequest.getOutId()); - return true; - }))).thenReturn(response); - - // 调用 - SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, - apiTemplateId, templateParams); - // 断言 - assertTrue(result.getSuccess()); - assertEquals(response.getRequestId(), result.getApiRequestId()); - assertEquals(response.getCode(), result.getApiCode()); - assertEquals(response.getMessage(), result.getApiMsg()); - assertEquals(response.getBizId(), result.getSerialNo()); + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + Long sendLogId = randomLongId(); + String mobile = randomString(); + String apiTemplateId = randomString(); + List> templateParams = Lists.newArrayList( + new KeyValue<>("code", 1234), new KeyValue<>("op", "login")); + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn("{\"Message\":\"OK\",\"RequestId\":\"30067CE9-3710-5984-8881-909B21D8DB28\",\"Code\":\"OK\",\"BizId\":\"800025323183427988\"}"); + + // 调用 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, + apiTemplateId, templateParams); + // 断言 + assertTrue(result.getSuccess()); + assertEquals("30067CE9-3710-5984-8881-909B21D8DB28", result.getApiRequestId()); + assertEquals("OK", result.getApiCode()); + assertEquals("OK", result.getApiMsg()); + assertEquals("800025323183427988", result.getSerialNo()); + } } @Test public void tesSendSms_fail() throws Throwable { - // 准备参数 - Long sendLogId = randomLongId(); - String mobile = randomString(); - String apiTemplateId = randomString(); - List> templateParams = Lists.newArrayList( - new KeyValue<>("code", 1234), new KeyValue<>("op", "login")); - // mock 方法 - SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> o.setCode("ERROR")); - when(client.getAcsResponse(argThat((ArgumentMatcher) acsRequest -> { - assertEquals(mobile, acsRequest.getPhoneNumbers()); - assertEquals(properties.getSignature(), acsRequest.getSignName()); - assertEquals(apiTemplateId, acsRequest.getTemplateCode()); - assertEquals(toJsonString(MapUtils.convertMap(templateParams)), acsRequest.getTemplateParam()); - assertEquals(sendLogId.toString(), acsRequest.getOutId()); - return true; - }))).thenReturn(response); - - // 调用 - SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams); - // 断言 - assertFalse(result.getSuccess()); - assertEquals(response.getRequestId(), result.getApiRequestId()); - assertEquals(response.getCode(), result.getApiCode()); - assertEquals(response.getMessage(), result.getApiMsg()); - assertEquals(response.getBizId(), result.getSerialNo()); + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + Long sendLogId = randomLongId(); + String mobile = randomString(); + String apiTemplateId = randomString(); + List> templateParams = Lists.newArrayList( + new KeyValue<>("code", 1234), new KeyValue<>("op", "login")); + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn("{\"Message\":\"手机号码格式错误\",\"RequestId\":\"B7700B8E-227E-5886-9564-26036172F01F\",\"Code\":\"isv.MOBILE_NUMBER_ILLEGAL\"}"); + + // 调用 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams); + // 断言 + assertFalse(result.getSuccess()); + assertEquals("B7700B8E-227E-5886-9564-26036172F01F", result.getApiRequestId()); + assertEquals("isv.MOBILE_NUMBER_ILLEGAL", result.getApiCode()); + assertEquals("手机号码格式错误", result.getApiMsg()); + assertNull(result.getSerialNo()); + } } @Test @@ -151,25 +121,21 @@ public class AliyunSmsClientTest extends BaseMockitoUnitTest { @Test public void testGetSmsTemplate() throws Throwable { - // 准备参数 - String apiTemplateId = randomString(); - // mock 方法 - QuerySmsTemplateResponse response = randomPojo(QuerySmsTemplateResponse.class, o -> { - o.setCode("OK"); - o.setTemplateStatus(1); // 设置模板通过 - }); - when(client.getAcsResponse(argThat((ArgumentMatcher) acsRequest -> { - assertEquals(apiTemplateId, acsRequest.getTemplateCode()); - return true; - }))).thenReturn(response); - - // 调用 - SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId); - // 断言 - assertEquals(response.getTemplateCode(), result.getId()); - assertEquals(response.getTemplateContent(), result.getContent()); - assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus()); - assertEquals(response.getReason(), result.getAuditReason()); + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + String apiTemplateId = randomString(); + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn("{\"TemplateCode\":\"SMS_207945135\",\"RequestId\":\"6F4CC077-29C8-5BA5-AB62-5FF95068A5AC\",\"Message\":\"OK\",\"TemplateContent\":\"您的验证码${code},该验证码5分钟内有效,请勿泄漏于他人!\",\"TemplateName\":\"公告通知\",\"TemplateType\":0,\"Code\":\"OK\",\"CreateDate\":\"2020-12-23 17:34:42\",\"Reason\":\"无审批备注\",\"TemplateStatus\":1}"); + + // 调用 + SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId); + // 断言 + assertEquals("SMS_207945135", result.getId()); + assertEquals("您的验证码${code},该验证码5分钟内有效,请勿泄漏于他人!", result.getContent()); + assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus()); + assertEquals("无审批备注", result.getAuditReason()); + } } @Test diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClientTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClientTest.java new file mode 100644 index 000000000..3f97412c8 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClientTest.java @@ -0,0 +1,127 @@ +package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; + +import cn.iocoder.yudao.framework.common.core.KeyValue; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; +import com.google.common.collect.Lists; + +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.MockedStatic; + +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mockStatic; + +/** + * {@link HuaweiSmsClient} 的单元测试 + * + * @author scholar + */ +public class HuaweiSmsClientTest extends BaseMockitoUnitTest { + + private final SmsChannelProperties properties = new SmsChannelProperties() + .setApiKey(randomString() + " " + randomString()) // 随机一个 apiKey,避免构建报错 + .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错 + .setSignature("芋道源码"); + + @InjectMocks + private HuaweiSmsClient smsClient = new HuaweiSmsClient(properties); + + @Test + public void testDoSendSms_success() throws Throwable { + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + Long sendLogId = randomLongId(); + String mobile = randomString(); + String apiTemplateId = randomString() + " " + randomString(); + List> templateParams = Lists.newArrayList( + new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); + + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn("{\"result\":[{\"originTo\":\"+86155****5678\",\"createTime\":\"2018-05-25T16:34:34Z\",\"from\":\"1069********0012\",\"smsMsgId\":\"d6e3cdd0-522b-4692-8304-a07553cdf591_8539659\",\"status\":\"000000\",\"countryId\":\"CN\",\"total\":2}],\"code\":\"000000\",\"description\":\"Success\"}\n"); + + // 调用 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, + apiTemplateId, templateParams); + // 断言 + assertTrue(result.getSuccess()); + assertEquals("d6e3cdd0-522b-4692-8304-a07553cdf591_8539659", result.getSerialNo()); + assertEquals("000000", result.getApiCode()); + } + } + + @Test + public void testDoSendSms_fail_01() throws Throwable { + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + Long sendLogId = randomLongId(); + String mobile = randomString(); + String apiTemplateId = randomString() + " " + randomString(); + List> templateParams = Lists.newArrayList( + new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); + + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn("{\"result\":[{\"total\":1,\"originTo\":\"17321315478\",\"createTime\":\"2024-08-18T11:32:20Z\",\"from\":\"x8824060312575\",\"smsMsgId\":\"06e4b966-ad87-479f-8b74-f57fb7aafb60_304613461\",\"countryId\":\"CN\",\"status\":\"E200033\"}],\"code\":\"E000510\",\"description\":\"The SMS fails to be sent. For details, see status.\"}"); + + // 调用 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, + apiTemplateId, templateParams); + // 断言 + assertFalse(result.getSuccess()); + assertEquals("06e4b966-ad87-479f-8b74-f57fb7aafb60_304613461", result.getSerialNo()); + assertEquals("E200033", result.getApiCode()); + } + } + + @Test + public void testDoSendSms_fail_02() throws Throwable { + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + Long sendLogId = randomLongId(); + String mobile = randomString(); + String apiTemplateId = randomString() + " " + randomString(); + List> templateParams = Lists.newArrayList( + new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); + + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn("{\"code\":\"E000102\",\"description\":\"Invalid app_key.\"}"); + + // 调用 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, + apiTemplateId, templateParams); + // 断言 + assertFalse(result.getSuccess()); + assertEquals("E000102", result.getApiCode()); + assertEquals("Invalid app_key.", result.getApiMsg()); + } + } + + @Test + public void testParseSmsReceiveStatus() { + // 准备参数 + String text = "sequence=1&total=1&statusDesc=%E7%94%A8%E6%88%B7%E5%B7%B2%E6%88%90%E5%8A%9F%E6%94%B6%E5%88%B0%E7%9F%AD%E4%BF%A1&updateTime=2024-08-15T03%3A00%3A34Z&source=2&smsMsgId=70207ed7-1d02-41b0-8537-bb25fd1c2364_143684459&status=DELIVRD&extend=176"; + + // 调用 + List statuses = smsClient.parseSmsReceiveStatus(text); + // 断言 + assertEquals(1, statuses.size()); + SmsReceiveRespDTO status = statuses.get(0); + assertTrue(status.getSuccess()); + assertEquals("DELIVRD", status.getErrorCode()); + assertEquals(LocalDateTime.of(2024, 8, 15, 3, 0, 34), status.getReceiveTime()); + assertEquals("70207ed7-1d02-41b0-8537-bb25fd1c2364_143684459", status.getSerialNo()); + } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java new file mode 100644 index 000000000..93b99bcc5 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/QiniuSmsClientTest.java @@ -0,0 +1,131 @@ +package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; + +import cn.iocoder.yudao.framework.common.core.KeyValue; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; +import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; +import com.google.common.collect.Lists; + +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.MockedStatic; + +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mockStatic; + +/** + * {@link QiniuSmsClient} 的单元测试 + * + * @author scholar + */ +public class QiniuSmsClientTest extends BaseMockitoUnitTest { + + private final SmsChannelProperties properties = new SmsChannelProperties() + .setApiKey(randomString())// 随机一个 apiKey,避免构建报错 + .setApiSecret(randomString()) // 随机一个 apiSecret,避免构建报错 + .setSignature("芋道源码"); + + @InjectMocks + private QiniuSmsClient smsClient = new QiniuSmsClient(properties); + + @Test + public void testDoSendSms_success() throws Throwable { + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + Long sendLogId = randomLongId(); + String mobile = randomString(); + String apiTemplateId = randomString() + " " + randomString(); + List> templateParams = Lists.newArrayList( + new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn("{\"message_id\":\"17245678901\"}"); + // 调用 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, + apiTemplateId, templateParams); + // 断言 + assertTrue(result.getSuccess()); + assertEquals("17245678901", result.getSerialNo()); + } + } + + @Test + public void testDoSendSms_fail() throws Throwable { + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + Long sendLogId = randomLongId(); + String mobile = randomString(); + String apiTemplateId = randomString() + " " + randomString(); + List> templateParams = Lists.newArrayList( + new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn("{\"error\":\"BadToken\",\"message\":\"Your authorization token is invalid\",\"request_id\":\"etziWcJFo1C8Ne8X\"}"); + // 调用 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, + apiTemplateId, templateParams); + // 断言 + assertFalse(result.getSuccess()); + assertEquals("BadToken", result.getApiCode()); + assertEquals("Your authorization token is invalid", result.getApiMsg()); + assertEquals("etziWcJFo1C8Ne8X", result.getApiRequestId()); + } + } + + @Test + public void testGetSmsTemplate() throws Throwable { + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + String apiTemplateId = randomString(); + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.get(anyString(), anyMap())) + .thenReturn("{\"audit_status\":\"passed\",\"created_at\":1724231187,\"description\":\"\",\"disable_broadcast\":false,\"disable_broadcast_reason\":\"\",\"disable_reason\":\"\",\"disabled\":false,\"id\":\"1826184073773596672\",\"is_oversea\":false,\"name\":\"dd\",\"parameters\":[\"code\"],\"reject_reason\":\"\",\"signature_id\":\"1826099896017498112\",\"signature_text\":\"yudao\",\"template\":\"您的验证码为:${code}\",\"type\":\"verification\",\"uid\":1383022432,\"updated_at\":1724288561,\"variable_count\":0}"); + // 调用 + SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId); + // 断言 + assertEquals("1826184073773596672", result.getId()); + assertEquals("您的验证码为:${code}", result.getContent()); + assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus()); + assertEquals("", result.getAuditReason()); + } + } + + @Test + public void testParseSmsReceiveStatus() { + // 准备参数 + String text = "{\"items\":[{\"mobile\":\"18881234567\",\"message_id\":\"10135515063508004167\",\"status\":\"DELIVRD\",\"delivrd_at\":1724591666,\"error\":\"DELIVRD\",\"seq\":\"123\"}]}"; + // 调用 + List statuses = smsClient.parseSmsReceiveStatus(text); + // 断言 + assertEquals(1, statuses.size()); + SmsReceiveRespDTO status = statuses.get(0); + assertTrue(status.getSuccess()); + assertEquals("DELIVRD", status.getErrorMsg()); + assertEquals(LocalDateTime.of(2024, 8, 25, 21, 14, 26), status.getReceiveTime()); + assertEquals("18881234567", status.getMobile()); + assertEquals("10135515063508004167", status.getSerialNo()); + assertEquals(123, status.getLogId()); + } + + @Test + public void testConvertSmsTemplateAuditStatus() { + assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), + smsClient.convertSmsTemplateAuditStatus("passed")); + assertEquals(SmsTemplateAuditStatusEnum.CHECKING.getStatus(), + smsClient.convertSmsTemplateAuditStatus("reviewing")); + assertEquals(SmsTemplateAuditStatusEnum.FAIL.getStatus(), + smsClient.convertSmsTemplateAuditStatus("rejected")); + assertThrows(IllegalArgumentException.class, () -> smsClient.convertSmsTemplateAuditStatus("unknown"), + "未知审核状态(3)"); + } +} \ No newline at end of file diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java new file mode 100644 index 000000000..faba754a2 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java @@ -0,0 +1,151 @@ +package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; + +import cn.hutool.core.collection.ListUtil; +import cn.iocoder.yudao.framework.common.core.KeyValue; +import cn.iocoder.yudao.module.system.framework.sms.core.client.SmsClient; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.List; + +/** + * 各种 {@link SmsClient} 的集成测试 + * + * @author 芋道源码 + */ +public class SmsClientTests { + + // ========== 阿里云 ========== + + @Test + @Disabled + public void testAliyunSmsClient_getSmsTemplate() throws Throwable { + SmsChannelProperties properties = new SmsChannelProperties() + .setApiKey(System.getenv("SMS_ALIYUN_ACCESS_KEY")) + .setApiSecret(System.getenv("SMS_ALIYUN_SECRET_KEY")); + AliyunSmsClient client = new AliyunSmsClient(properties); + // 准备参数 + String apiTemplateId = "SMS_207945135"; + // 调用 + SmsTemplateRespDTO template = client.getSmsTemplate(apiTemplateId); + // 打印结果 + System.out.println(template); + } + + @Test + @Disabled + public void testAliyunSmsClient_sendSms() throws Throwable { + SmsChannelProperties properties = new SmsChannelProperties() + .setApiKey(System.getenv("SMS_ALIYUN_ACCESS_KEY")) + .setApiSecret(System.getenv("SMS_ALIYUN_SECRET_KEY")) + .setSignature("Ballcat"); + AliyunSmsClient client = new AliyunSmsClient(properties); + // 准备参数 + Long sendLogId = System.currentTimeMillis(); + String mobile = "15601691323"; + String apiTemplateId = "SMS_207945135"; + // 调用 + SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, ListUtil.of(new KeyValue<>("code", "1024"))); + // 打印结果 + System.out.println(sendRespDTO); + } + + // ========== 腾讯云 ========== + + @Test + @Disabled + public void testTencentSmsClient_sendSms() throws Throwable { + String sdkAppId = "1400500458"; + SmsChannelProperties properties = new SmsChannelProperties() + .setApiKey(System.getenv("SMS_TENCENT_ACCESS_KEY") + " " + sdkAppId) + .setApiSecret(System.getenv("SMS_TENCENT_SECRET_KEY")) + .setSignature("芋道源码"); + TencentSmsClient client = new TencentSmsClient(properties); + // 准备参数 + Long sendLogId = System.currentTimeMillis(); + String mobile = "15601691323"; + String apiTemplateId = "358212"; + // 调用 + SmsSendRespDTO sendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, ListUtil.of(new KeyValue<>("code", "1024"))); + // 打印结果 + System.out.println(sendRespDTO); + } + + @Test + @Disabled + public void testTencentSmsClient_getSmsTemplate() throws Throwable { + String sdkAppId = "1400500458"; + SmsChannelProperties properties = new SmsChannelProperties() + .setApiKey(System.getenv("SMS_TENCENT_ACCESS_KEY") + " " + sdkAppId) + .setApiSecret(System.getenv("SMS_TENCENT_SECRET_KEY")) + .setSignature("芋道源码"); + TencentSmsClient client = new TencentSmsClient(properties); + // 准备参数 + String apiTemplateId = "358212"; + // 调用 + SmsTemplateRespDTO template = client.getSmsTemplate(apiTemplateId); + // 打印结果 + System.out.println(template); + } + + // ========== 华为云 ========== + + @Test + @Disabled + public void testHuaweiSmsClient_sendSms() throws Throwable { + String sender = "x8824060312575"; + SmsChannelProperties properties = new SmsChannelProperties() + .setApiKey(System.getenv("SMS_HUAWEI_ACCESS_KEY") + " " + sender) + .setApiSecret(System.getenv("SMS_HUAWEI_SECRET_KEY")) + .setSignature("runpu"); + HuaweiSmsClient client = new HuaweiSmsClient(properties); + // 准备参数 + Long sendLogId = System.currentTimeMillis(); + String mobile = "17321315478"; + String apiTemplateId = "3644cdab863546a3b718d488659a99ef"; + List> templateParams = ListUtil.of(new KeyValue<>("code", "1024")); + // 调用 + SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams); + // 打印结果 + System.out.println(smsSendRespDTO); + } + + // ========== 七牛云 ========== + + @Test + @Disabled + public void testQiniuSmsClient_sendSms() throws Throwable { + SmsChannelProperties properties = new SmsChannelProperties() + .setApiKey("SMS_QINIU_ACCESS_KEY") + .setApiSecret("SMS_QINIU_SECRET_KEY"); + QiniuSmsClient client = new QiniuSmsClient(properties); + // 准备参数 + Long sendLogId = System.currentTimeMillis(); + String mobile = "17321315478"; + String apiTemplateId = "3644cdab863546a3b718d488659a99ef"; + List> templateParams = ListUtil.of(new KeyValue<>("code", "1122")); + // 调用 + SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams); + // 打印结果 + System.out.println(smsSendRespDTO); + } + + @Test + @Disabled + public void testQiniuSmsClient_getSmsTemplate() throws Throwable { + SmsChannelProperties properties = new SmsChannelProperties() + .setApiKey("SMS_QINIU_ACCESS_KEY") + .setApiSecret("SMS_QINIU_SECRET_KEY"); + QiniuSmsClient client = new QiniuSmsClient(properties); + // 准备参数 + String apiTemplateId = "3644cdab863546a3b718d488659a99ef"; + // 调用 + SmsTemplateRespDTO template = client.getSmsTemplate(apiTemplateId); + // 打印结果 + System.out.println(template); + } +} + diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java index e93435f4d..060a34558 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java @@ -1,10 +1,7 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; -import cn.hutool.core.util.ReflectUtil; 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.MapUtils; -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; @@ -12,24 +9,19 @@ import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateR import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; 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 org.mockito.MockedStatic; import java.time.LocalDateTime; -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.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.when; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mockStatic; /** * {@link TencentSmsClient} 的单元测试 @@ -46,113 +38,108 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest { @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")); + public void testDoSendSms_success() throws Throwable { + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + Long sendLogId = randomLongId(); + String mobile = randomString(); + String apiTemplateId = randomString(); + List> templateParams = Lists.newArrayList( + new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn("{\n" + + " \"Response\": {\n" + + " \"SendStatusSet\": [\n" + + " {\n" + + " \"SerialNo\": \"5000:1045710669157053657849499619\",\n" + + " \"PhoneNumber\": \"+8618511122233\",\n" + + " \"Fee\": 1,\n" + + " \"SessionContext\": \"test\",\n" + + " \"Code\": \"Ok\",\n" + + " \"Message\": \"send success\",\n" + + " \"IsoCode\": \"CN\"\n" + + " },\n" + + " ],\n" + + " \"RequestId\": \"a0aabda6-cf91-4f3e-a81f-9198114a2279\"\n" + + " }\n" + + "}"); + + // 调用 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, + apiTemplateId, templateParams); + // 断言 + assertTrue(result.getSuccess()); + assertEquals("5000:1045710669157053657849499619", result.getSerialNo()); + assertEquals("a0aabda6-cf91-4f3e-a81f-9198114a2279", result.getApiRequestId()); + assertEquals("send success", result.getApiMsg()); + } } @Test - public void testDoSendSms_success() throws Throwable { - // 准备参数 - Long sendLogId = randomLongId(); - String mobile = randomString(); - String apiTemplateId = randomString(); - List> 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_CODE_SUCCESS); - 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); - - // 调用 - SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams); - // 断言 - assertTrue(result.getSuccess()); - assertEquals(response.getRequestId(), result.getApiRequestId()); - assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode()); - assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg()); - assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo()); + public void testDoSendSms_fail_01() throws Throwable { + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + Long sendLogId = randomLongId(); + String mobile = randomString(); + String apiTemplateId = randomString(); + List> templateParams = Lists.newArrayList( + new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); + + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn("{\n" + + " \"Response\": {\n" + + " \"SendStatusSet\": [\n" + + " {\n" + + " \"SerialNo\": \"5000:1045710669157053657849499619\",\n" + + " \"PhoneNumber\": \"+8618511122233\",\n" + + " \"Fee\": 1,\n" + + " \"SessionContext\": \"test\",\n" + + " \"Code\": \"ERROR\",\n" + + " \"Message\": \"send success\",\n" + + " \"IsoCode\": \"CN\"\n" + + " },\n" + + " ],\n" + + " \"RequestId\": \"a0aabda6-cf91-4f3e-a81f-9198114a2279\"\n" + + " }\n" + + "}"); + + // 调用 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, + apiTemplateId, templateParams); + // 断言 + assertFalse(result.getSuccess()); + assertEquals("5000:1045710669157053657849499619", result.getSerialNo()); + assertEquals("a0aabda6-cf91-4f3e-a81f-9198114a2279", result.getApiRequestId()); + assertEquals("send success", result.getApiMsg()); + } } @Test - public void testDoSendSms_fail() throws Throwable { - // 准备参数 - Long sendLogId = randomLongId(); - String mobile = randomString(); - String apiTemplateId = randomString(); - List> 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("ERROR"); - 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); - - // 调用 - SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams); - // 断言 - assertFalse(result.getSuccess()); - assertEquals(response.getRequestId(), result.getApiRequestId()); - assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode()); - assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg()); - assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo()); + public void testDoSendSms_fail_02() throws Throwable { + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + Long sendLogId = randomLongId(); + String mobile = randomString(); + String apiTemplateId = randomString(); + List> templateParams = Lists.newArrayList( + new KeyValue<>("1", 1234), new KeyValue<>("2", "login")); + + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn("{\"Response\":{\"Error\":{\"Code\":\"AuthFailure.SecretIdNotFound\",\"Message\":\"The SecretId is not found, please ensure that your SecretId is correct.\"},\"RequestId\":\"2a88f82a-261c-4ac6-9fa9-c7d01aaa486a\"}}"); + + // 调用 + SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, + apiTemplateId, templateParams); + // 断言 + assertFalse(result.getSuccess()); + assertEquals("2a88f82a-261c-4ac6-9fa9-c7d01aaa486a", result.getApiRequestId()); + assertEquals("AuthFailure.SecretIdNotFound", result.getApiCode()); + assertEquals("The SecretId is not found, please ensure that your SecretId is correct.", result.getApiMsg()); + } } @Test @@ -170,7 +157,6 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest { " \"ext\": {\"logId\":\"67890\"}\n" + " }\n" + "]"; - // mock 方法 // 调用 List statuses = smsClient.parseSmsReceiveStatus(text); @@ -178,41 +164,43 @@ public class TencentSmsClientTest extends BaseMockitoUnitTest { 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(LocalDateTime.of(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 testGetSmsTemplate() 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); - - // 调用 - SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId.toString()); - // 断言 - assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateId().toString(), result.getId()); - assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateContent(), result.getContent()); - assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus()); - assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getAuditReason()); + try (MockedStatic httpUtilsMockedStatic = mockStatic(HttpUtils.class)) { + // 准备参数 + String apiTemplateId = "1122"; + + // mock 方法 + httpUtilsMockedStatic.when(() -> HttpUtils.post(anyString(), anyMap(), anyString())) + .thenReturn("{ \"Response\": {\n" + + " \"DescribeTemplateStatusSet\": [\n" + + " {\n" + + " \"TemplateName\": \"验证码\",\n" + + " \"TemplateId\": 1122,\n" + + " \"International\": 0,\n" + + " \"ReviewReply\": \"审批备注\",\n" + + " \"CreateTime\": 1617379200,\n" + + " \"TemplateContent\": \"您的验证码是{1}\",\n" + + " \"StatusCode\": 0\n" + + " },\n" + + " \n" + + " ],\n" + + " \"RequestId\": \"f36e4f00-605e-49b1-ad0d-bfaba81c7325\"\n" + + " }}"); + + // 调用 + SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId); + // 断言 + assertEquals("1122", result.getId()); + assertEquals("您的验证码是{1}", result.getContent()); + assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus()); + assertEquals("审批备注", result.getAuditReason()); + } } @Test diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java index 9ade60aa0..ba751ebe4 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImplTest.java @@ -210,6 +210,13 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest { String mobile = randomString(); String code = randomString(); AuthSmsLoginReqVO reqVO = new AuthSmsLoginReqVO(mobile, code); + // mock 方法(验证码) + doNothing().when(smsCodeApi).useSmsCode((argThat(smsCodeUseReqDTO -> { + assertEquals(mobile, smsCodeUseReqDTO.getMobile()); + assertEquals(code, smsCodeUseReqDTO.getCode()); + assertEquals(SmsSceneEnum.ADMIN_MEMBER_LOGIN.getScene(), smsCodeUseReqDTO.getScene()); + return true; + }))); // mock 方法(用户信息) AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(1L)); when(userService.getUserByMobile(eq(mobile))).thenReturn(user); diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/notify/NotifyMessageServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/notify/NotifyMessageServiceImplTest.java index 9e2158da1..ccc46ffd1 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/notify/NotifyMessageServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/notify/NotifyMessageServiceImplTest.java @@ -3,14 +3,12 @@ package cn.iocoder.yudao.module.system.service.notify; import cn.hutool.core.map.MapUtil; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.enums.SqlConstants; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.module.system.controller.admin.notify.vo.message.NotifyMessageMyPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.notify.vo.message.NotifyMessagePageReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyMessageDO; import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyTemplateDO; import cn.iocoder.yudao.module.system.dal.mysql.notify.NotifyMessageMapper; -import com.baomidou.mybatisplus.annotation.DbType; import org.junit.jupiter.api.Test; import org.springframework.context.annotation.Import; @@ -158,7 +156,6 @@ public class NotifyMessageServiceImplTest extends BaseDbUnitTest { @Test public void testGetUnreadNotifyMessageList() { - SqlConstants.init(DbType.MYSQL); // mock 数据 NotifyMessageDO dbNotifyMessage = randomPojo(NotifyMessageDO.class, o -> { // 等会查询到 o.setUserId(1L); @@ -187,7 +184,6 @@ public class NotifyMessageServiceImplTest extends BaseDbUnitTest { @Test public void testGetUnreadNotifyMessageCount() { - SqlConstants.init(DbType.MYSQL); // mock 数据 NotifyMessageDO dbNotifyMessage = randomPojo(NotifyMessageDO.class, o -> { // 等会查询到 o.setUserId(1L); diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ApproveServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ApproveServiceImplTest.java index 1ca22a999..d5abed680 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ApproveServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ApproveServiceImplTest.java @@ -209,7 +209,8 @@ public class OAuth2ApproveServiceImplTest extends BaseDbUnitTest { List result = oauth2ApproveService.getApproveList(userId, userType, clientId); // 断言 assertEquals(1, result.size()); - assertPojoEquals(approve, result.get(0)); + // TODO @芋艿:expiresTime 被屏蔽,仅 win11 会复现,建议后续修复。 + assertPojoEquals(approve, result.get(0), "expiresTime"); } @Test diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2CodeServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2CodeServiceImplTest.java index 2ed9c978f..187f46e97 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2CodeServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2CodeServiceImplTest.java @@ -50,7 +50,8 @@ class OAuth2CodeServiceImplTest extends BaseDbUnitTest { scopes, redirectUri, state); // 断言 OAuth2CodeDO dbCodeDO = oauth2CodeMapper.selectByCode(codeDO.getCode()); - assertPojoEquals(codeDO, dbCodeDO, "createTime", "updateTime", "deleted"); + // TODO @芋艿:expiresTime 被屏蔽,仅 win11 会复现,建议后续修复。 + assertPojoEquals(codeDO, dbCodeDO, "expiresTime", "createTime", "updateTime", "deleted"); assertEquals(userId, codeDO.getUserId()); assertEquals(userType, codeDO.getUserType()); assertEquals(clientId, codeDO.getClientId()); @@ -92,7 +93,8 @@ class OAuth2CodeServiceImplTest extends BaseDbUnitTest { // 调用 OAuth2CodeDO result = oauth2CodeService.consumeAuthorizationCode(code); - assertPojoEquals(codeDO, result); + // TODO @芋艿:expiresTime 被屏蔽,仅 win11 会复现,建议后续修复。 + assertPojoEquals(codeDO, result, "expiresTime"); assertNull(oauth2CodeMapper.selectByCode(code)); } diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImplTest.java index 03e4a02b9..d95920672 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImplTest.java @@ -77,7 +77,8 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest { OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, userType, clientId, scopes); // 断言访问令牌 OAuth2AccessTokenDO dbAccessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessTokenDO.getAccessToken()); - assertPojoEquals(accessTokenDO, dbAccessTokenDO, "createTime", "updateTime", "deleted"); + // TODO @芋艿:expiresTime 被屏蔽,仅 win11 会复现,建议后续修复。 + assertPojoEquals(accessTokenDO, dbAccessTokenDO, "expiresTime", "createTime", "updateTime", "deleted"); assertEquals(userId, accessTokenDO.getUserId()); assertEquals(userType, accessTokenDO.getUserType()); assertEquals(2, accessTokenDO.getUserInfo().size()); @@ -88,7 +89,8 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest { assertFalse(DateUtils.isExpired(accessTokenDO.getExpiresTime())); // 断言访问令牌的缓存 OAuth2AccessTokenDO redisAccessTokenDO = oauth2AccessTokenRedisDAO.get(accessTokenDO.getAccessToken()); - assertPojoEquals(accessTokenDO, redisAccessTokenDO, "createTime", "updateTime", "deleted"); + // TODO @芋艿:expiresTime 被屏蔽,仅 win11 会复现,建议后续修复。 + assertPojoEquals(accessTokenDO, redisAccessTokenDO, "expiresTime", "createTime", "updateTime", "deleted"); // 断言刷新令牌 OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectList().get(0); assertPojoEquals(accessTokenDO, refreshTokenDO, "id", "expiresTime", "createTime", "updateTime", "deleted"); @@ -142,7 +144,7 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest { // 调用,并断言 assertServiceException(() -> oauth2TokenService.refreshAccessToken(refreshToken, clientId), new ErrorCode(401, "刷新令牌已过期")); - assertEquals(0, oauth2RefreshTokenMapper.selectCount()); + assertEquals(0, oauth2AccessTokenMapper.selectCount()); } @Test @@ -156,10 +158,11 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest { .setAccessTokenValiditySeconds(30); when(oauth2ClientService.validOAuthClientFromCache(eq(clientId))).thenReturn(clientDO); // mock 数据(访问令牌) - OAuth2RefreshTokenDO refreshTokenDO = randomPojo(OAuth2RefreshTokenDO.class) - .setRefreshToken(refreshToken).setClientId(clientId) - .setExpiresTime(LocalDateTime.now().plusDays(1)) - .setUserType(UserTypeEnum.ADMIN.getValue()); + OAuth2RefreshTokenDO refreshTokenDO = randomPojo(OAuth2RefreshTokenDO.class, o -> + o.setRefreshToken(refreshToken).setClientId(clientId) + .setExpiresTime(LocalDateTime.now().plusDays(1)) + .setUserType(UserTypeEnum.ADMIN.getValue()) + .setTenantId(TenantContextHolder.getTenantId())); oauth2RefreshTokenMapper.insert(refreshTokenDO); // mock 数据(访问令牌) OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class).setRefreshToken(refreshToken) @@ -177,13 +180,15 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest { assertNull(oauth2AccessTokenRedisDAO.get(accessTokenDO.getAccessToken())); // 断言,新的访问令牌 OAuth2AccessTokenDO dbAccessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(newAccessTokenDO.getAccessToken()); - assertPojoEquals(newAccessTokenDO, dbAccessTokenDO, "createTime", "updateTime", "deleted"); + // TODO @芋艿:expiresTime 被屏蔽,仅 win11 会复现,建议后续修复。 + assertPojoEquals(newAccessTokenDO, dbAccessTokenDO, "expiresTime", "createTime", "updateTime", "deleted"); assertPojoEquals(newAccessTokenDO, refreshTokenDO, "id", "expiresTime", "createTime", "updateTime", "deleted", "creator", "updater"); assertFalse(DateUtils.isExpired(newAccessTokenDO.getExpiresTime())); // 断言,新的访问令牌的缓存 OAuth2AccessTokenDO redisAccessTokenDO = oauth2AccessTokenRedisDAO.get(newAccessTokenDO.getAccessToken()); - assertPojoEquals(newAccessTokenDO, redisAccessTokenDO, "createTime", "updateTime", "deleted"); + // TODO @芋艿:expiresTime 被屏蔽,仅 win11 会复现,建议后续修复。 + assertPojoEquals(newAccessTokenDO, redisAccessTokenDO, "expiresTime", "createTime", "updateTime", "deleted"); } @Test @@ -198,9 +203,11 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest { // 调用 OAuth2AccessTokenDO result = oauth2TokenService.getAccessToken(accessToken); // 断言 - assertPojoEquals(accessTokenDO, result, "createTime", "updateTime", "deleted", + // TODO @芋艿:expiresTime 被屏蔽,仅 win11 会复现,建议后续修复。 + assertPojoEquals(accessTokenDO, result, "expiresTime", "createTime", "updateTime", "deleted", "creator", "updater"); - assertPojoEquals(accessTokenDO, oauth2AccessTokenRedisDAO.get(accessToken), "createTime", "updateTime", "deleted", + // TODO @芋艿:expiresTime 被屏蔽,仅 win11 会复现,建议后续修复。 + assertPojoEquals(accessTokenDO, oauth2AccessTokenRedisDAO.get(accessToken), "expiresTime", "createTime", "updateTime", "deleted", "creator", "updater"); } @@ -225,6 +232,22 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest { new ErrorCode(401, "访问令牌已过期")); } + @Test + public void testCheckAccessToken_refreshToken() { + // mock 数据(访问令牌) + OAuth2RefreshTokenDO refreshTokenDO = randomPojo(OAuth2RefreshTokenDO.class) + .setExpiresTime(LocalDateTime.now().plusDays(1)); + oauth2RefreshTokenMapper.insert(refreshTokenDO); + // 准备参数 + String accessToken = refreshTokenDO.getRefreshToken(); + + // 调研,并断言 + OAuth2AccessTokenDO result = oauth2TokenService.getAccessToken(accessToken); + // 断言 + assertPojoEquals(refreshTokenDO, result, "expiresTime", "createTime", "updateTime", "deleted", + "creator", "updater"); + } + @Test public void testCheckAccessToken_success() { // mock 数据(访问令牌) @@ -237,7 +260,8 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest { // 调研,并断言 OAuth2AccessTokenDO result = oauth2TokenService.getAccessToken(accessToken); // 断言 - assertPojoEquals(accessTokenDO, result, "createTime", "updateTime", "deleted", + // TODO @芋艿:expiresTime 被屏蔽,仅 win11 会复现,建议后续修复。 + assertPojoEquals(accessTokenDO, result, "expiresTime", "createTime", "updateTime", "deleted", "creator", "updater"); } @@ -259,7 +283,8 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest { oauth2RefreshTokenMapper.insert(refreshTokenDO); // 调用 OAuth2AccessTokenDO result = oauth2TokenService.removeAccessToken(accessTokenDO.getAccessToken()); - assertPojoEquals(accessTokenDO, result, "createTime", "updateTime", "deleted", + // TODO @芋艿:expiresTime 被屏蔽,仅 win11 会复现,建议后续修复。 + assertPojoEquals(accessTokenDO, result, "expiresTime", "createTime", "updateTime", "deleted", "creator", "updater"); // 断言数据 assertNull(oauth2AccessTokenMapper.selectByAccessToken(accessTokenDO.getAccessToken())); @@ -297,7 +322,8 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest { // 断言 assertEquals(1, pageResult.getTotal()); assertEquals(1, pageResult.getList().size()); - assertPojoEquals(dbAccessToken, pageResult.getList().get(0)); + // TODO @芋艿:expiresTime 被屏蔽,仅 win11 会复现,建议后续修复。 + assertPojoEquals(dbAccessToken, pageResult.getList().get(0), "expiresTime"); } } diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImplTest.java index 51c1de6ca..922a16482 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImplTest.java @@ -51,7 +51,8 @@ public class RoleServiceImplTest extends BaseDbUnitTest { public void testCreateRole() { // 准备参数 RoleSaveReqVO reqVO = randomPojo(RoleSaveReqVO.class) - .setId(null); // 防止 id 被赋值 + .setId(null) // 防止 id 被赋值 + .setStatus(randomCommonStatus()); // 调用 Long roleId = roleService.createRole(reqVO, null); @@ -59,7 +60,6 @@ public class RoleServiceImplTest extends BaseDbUnitTest { RoleDO roleDO = roleMapper.selectById(roleId); assertPojoEquals(reqVO, roleDO, "id"); assertEquals(RoleTypeEnum.CUSTOM.getType(), roleDO.getType()); - assertEquals(CommonStatusEnum.ENABLE.getStatus(), roleDO.getStatus()); assertEquals(DataScopeEnum.ALL.getScope(), roleDO.getDataScope()); } @@ -70,7 +70,8 @@ public class RoleServiceImplTest extends BaseDbUnitTest { roleMapper.insert(roleDO); // 准备参数 Long id = roleDO.getId(); - RoleSaveReqVO reqVO = randomPojo(RoleSaveReqVO.class, o -> o.setId(id)); + RoleSaveReqVO reqVO = randomPojo(RoleSaveReqVO.class, o -> o.setId(id) + .setStatus(randomCommonStatus())); // 调用 roleService.updateRole(reqVO); diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceTest.java index f22701cc0..60d7ed269 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsChannelServiceTest.java @@ -57,9 +57,6 @@ public class SmsChannelServiceTest extends BaseDbUnitTest { // 校验记录的属性是否正确 SmsChannelDO smsChannel = smsChannelMapper.selectById(smsChannelId); assertPojoEquals(reqVO, smsChannel, "id"); - // 断言 cache - assertNull(smsChannelService.getIdClientCache().getIfPresent(smsChannel.getId())); - assertNull(smsChannelService.getCodeClientCache().getIfPresent(smsChannel.getCode())); } @Test @@ -79,9 +76,6 @@ public class SmsChannelServiceTest extends BaseDbUnitTest { // 校验是否更新正确 SmsChannelDO smsChannel = smsChannelMapper.selectById(reqVO.getId()); // 获取最新的 assertPojoEquals(reqVO, smsChannel); - // 断言 cache - assertNull(smsChannelService.getIdClientCache().getIfPresent(smsChannel.getId())); - assertNull(smsChannelService.getCodeClientCache().getIfPresent(smsChannel.getCode())); } @Test @@ -105,9 +99,6 @@ public class SmsChannelServiceTest extends BaseDbUnitTest { smsChannelService.deleteSmsChannel(id); // 校验数据不存在了 assertNull(smsChannelMapper.selectById(id)); - // 断言 cache - assertNull(smsChannelService.getIdClientCache().getIfPresent(dbSmsChannel.getId())); - assertNull(smsChannelService.getCodeClientCache().getIfPresent(dbSmsChannel.getCode())); } @Test @@ -196,29 +187,23 @@ public class SmsChannelServiceTest extends BaseDbUnitTest { // mock 数据 SmsChannelDO channel = randomPojo(SmsChannelDO.class); smsChannelMapper.insert(channel); - // mock 参数 + // 准备参数 Long id = channel.getId(); // mock 方法 SmsClient mockClient = mock(SmsClient.class); - when(smsClientFactory.getSmsClient(eq(id))).thenReturn(mockClient); + SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class); + when(smsClientFactory.createOrUpdateSmsClient(eq(properties))).thenReturn(mockClient); // 调用 SmsClient client = smsChannelService.getSmsClient(id); // 断言 assertSame(client, mockClient); - verify(smsClientFactory).createOrUpdateSmsClient(argThat(arg -> { - SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class); - return properties.equals(arg); - })); } @Test public void testGetSmsClient_code() { - // mock 数据 - SmsChannelDO channel = randomPojo(SmsChannelDO.class); - smsChannelMapper.insert(channel); - // mock 参数 - String code = channel.getCode(); + // 准备参数 + String code = randomString(); // mock 方法 SmsClient mockClient = mock(SmsClient.class); when(smsClientFactory.getSmsClient(eq(code))).thenReturn(mockClient); @@ -227,10 +212,6 @@ public class SmsChannelServiceTest extends BaseDbUnitTest { SmsClient client = smsChannelService.getSmsClient(code); // 断言 assertSame(client, mockClient); - verify(smsClientFactory).createOrUpdateSmsClient(argThat(arg -> { - SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class); - return properties.equals(arg); - })); } } diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsCodeServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsCodeServiceImplTest.java index fea1419ca..896027caf 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsCodeServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/sms/SmsCodeServiceImplTest.java @@ -1,7 +1,6 @@ package cn.iocoder.yudao.module.system.service.sms; import cn.hutool.core.map.MapUtil; -import cn.iocoder.yudao.framework.mybatis.core.enums.SqlConstants; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO; import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO; @@ -10,7 +9,6 @@ import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsCodeDO; import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsCodeMapper; import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; import cn.iocoder.yudao.module.system.framework.sms.config.SmsCodeProperties; -import com.baomidou.mybatisplus.annotation.DbType; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.test.mock.mockito.MockBean; @@ -61,8 +59,6 @@ public class SmsCodeServiceImplTest extends BaseDbUnitTest { o.setMobile("15601691300"); o.setScene(SmsSceneEnum.MEMBER_LOGIN.getScene()); }); - // mock 方法 - SqlConstants.init(DbType.MYSQL); // 调用 smsCodeService.sendSmsCode(reqDTO); @@ -88,8 +84,6 @@ public class SmsCodeServiceImplTest extends BaseDbUnitTest { o.setMobile("15601691300"); o.setScene(SmsSceneEnum.MEMBER_LOGIN.getScene()); }); - // mock 方法 - SqlConstants.init(DbType.MYSQL); // 调用,并断言异常 assertServiceException(() -> smsCodeService.sendSmsCode(reqDTO), @@ -107,8 +101,6 @@ public class SmsCodeServiceImplTest extends BaseDbUnitTest { o.setMobile("15601691300"); o.setScene(SmsSceneEnum.MEMBER_LOGIN.getScene()); }); - // mock 方法 - SqlConstants.init(DbType.MYSQL); when(smsCodeProperties.getSendFrequency()).thenReturn(Duration.ofMillis(0)); // 调用,并断言异常 @@ -123,8 +115,6 @@ public class SmsCodeServiceImplTest extends BaseDbUnitTest { o.setMobile("15601691300"); o.setScene(randomEle(SmsSceneEnum.values()).getScene()); }); - // mock 数据 - SqlConstants.init(DbType.MYSQL); smsCodeMapper.insert(randomPojo(SmsCodeDO.class, o -> { o.setMobile(reqDTO.getMobile()).setScene(reqDTO.getScene()) .setCode(reqDTO.getCode()).setUsed(false); @@ -146,8 +136,6 @@ public class SmsCodeServiceImplTest extends BaseDbUnitTest { o.setMobile("15601691300"); o.setScene(randomEle(SmsSceneEnum.values()).getScene()); }); - // mock 数据 - SqlConstants.init(DbType.MYSQL); smsCodeMapper.insert(randomPojo(SmsCodeDO.class, o -> o.setMobile(reqDTO.getMobile()) .setScene(reqDTO.getScene()).setCode(reqDTO.getCode()).setUsed(false))); @@ -162,8 +150,6 @@ public class SmsCodeServiceImplTest extends BaseDbUnitTest { o.setMobile("15601691300"); o.setScene(randomEle(SmsSceneEnum.values()).getScene()); }); - // mock 数据 - SqlConstants.init(DbType.MYSQL); // 调用,并断言异常 assertServiceException(() -> smsCodeService.validateSmsCode(reqDTO), @@ -177,8 +163,6 @@ public class SmsCodeServiceImplTest extends BaseDbUnitTest { o.setMobile("15601691300"); o.setScene(randomEle(SmsSceneEnum.values()).getScene()); }); - // mock 数据 - SqlConstants.init(DbType.MYSQL); smsCodeMapper.insert(randomPojo(SmsCodeDO.class, o -> o.setMobile(reqDTO.getMobile()) .setScene(reqDTO.getScene()).setCode(reqDTO.getCode()).setUsed(false) .setCreateTime(LocalDateTime.now().minusMinutes(6)))); @@ -195,8 +179,6 @@ public class SmsCodeServiceImplTest extends BaseDbUnitTest { o.setMobile("15601691300"); o.setScene(randomEle(SmsSceneEnum.values()).getScene()); }); - // mock 数据 - SqlConstants.init(DbType.MYSQL); smsCodeMapper.insert(randomPojo(SmsCodeDO.class, o -> o.setMobile(reqDTO.getMobile()) .setScene(reqDTO.getScene()).setCode(reqDTO.getCode()).setUsed(true) .setCreateTime(LocalDateTime.now()))); diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java index 03897a83f..44153449d 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java @@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.infra.api.config.ConfigApi; import cn.iocoder.yudao.module.infra.api.file.FileApi; import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO; import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO; @@ -23,6 +24,7 @@ import cn.iocoder.yudao.module.system.service.dept.DeptService; import cn.iocoder.yudao.module.system.service.dept.PostService; import cn.iocoder.yudao.module.system.service.permission.PermissionService; import cn.iocoder.yudao.module.system.service.tenant.TenantService; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.stubbing.Answer; import org.springframework.boot.test.mock.mockito.MockBean; @@ -46,6 +48,7 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEq import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.system.service.user.AdminUserServiceImpl.USER_INIT_PASSWORD_KEY; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static org.assertj.core.util.Lists.newArrayList; @@ -76,6 +79,14 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { private TenantService tenantService; @MockBean private FileApi fileApi; + @MockBean + private ConfigApi configApi; + + @BeforeEach + public void before() { + // mock 初始化密码 + when(configApi.getConfigValueByKey(USER_INIT_PASSWORD_KEY)).thenReturn("yudaoyuanma"); + } @Test public void testCreatUser_success() { @@ -419,6 +430,8 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { public void testImportUserList_01() { // 准备参数 UserImportExcelVO importUser = randomPojo(UserImportExcelVO.class, o -> { + o.setEmail(randomEmail()); + o.setMobile(randomMobile()); }); // mock 方法,模拟失败 doThrow(new ServiceException(DEPT_NOT_FOUND)).when(deptService).validateDeptList(any()); @@ -441,6 +454,8 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { UserImportExcelVO importUser = randomPojo(UserImportExcelVO.class, o -> { o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 o.setSex(randomEle(SexEnum.values()).getSex()); // 保证 sex 的范围 + o.setEmail(randomEmail()); + o.setMobile(randomMobile()); }); // mock deptService 的方法 DeptDO dept = randomPojo(DeptDO.class, o -> { @@ -475,6 +490,8 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 o.setSex(randomEle(SexEnum.values()).getSex()); // 保证 sex 的范围 o.setUsername(dbUser.getUsername()); + o.setEmail(randomEmail()); + o.setMobile(randomMobile()); }); // mock deptService 的方法 DeptDO dept = randomPojo(DeptDO.class, o -> { @@ -505,6 +522,8 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest { o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围 o.setSex(randomEle(SexEnum.values()).getSex()); // 保证 sex 的范围 o.setUsername(dbUser.getUsername()); + o.setEmail(randomEmail()); + o.setMobile(randomMobile()); }); // mock deptService 的方法 DeptDO dept = randomPojo(DeptDO.class, o -> { diff --git a/yudao-module-system/yudao-module-system-biz/src/test/resources/application-unit-test.yaml b/yudao-module-system/yudao-module-system-biz/src/test/resources/application-unit-test.yaml index eb823d904..e451c913f 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/resources/application-unit-test.yaml +++ b/yudao-module-system/yudao-module-system-biz/src/test/resources/application-unit-test.yaml @@ -46,8 +46,3 @@ mybatis: yudao: info: base-package: cn.iocoder.yudao.module - captcha: - timeout: 5m - width: 160 - height: 60 - enable: true diff --git a/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql b/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql index 087540a6e..58f029f50 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql +++ b/yudao-module-system/yudao-module-system-biz/src/test/resources/sql/create_tables.sql @@ -473,7 +473,7 @@ CREATE TABLE IF NOT EXISTS "system_oauth2_access_token" ( "updater" varchar DEFAULT '', "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "deleted" bit NOT NULL DEFAULT FALSE, - "tenant_id" bigint NOT NULL, + "tenant_id" bigint not null, PRIMARY KEY ("id") ) COMMENT 'OAuth2 访问令牌'; @@ -491,6 +491,7 @@ CREATE TABLE IF NOT EXISTS "system_oauth2_refresh_token" ( "updater" varchar DEFAULT '', "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "deleted" bit NOT NULL DEFAULT FALSE, + "tenant_id" bigint not null default '0', PRIMARY KEY ("id") ) COMMENT 'OAuth2 刷新令牌';