mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-30 09:48:43 +08:00 
			
		
		
		
	
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							| @@ -153,16 +153,16 @@ ps:核心功能已经实现,正在对接微信小程序中... | ||||
|  | ||||
| | 框架 | 说明 | 版本       | 学习指南 | | ||||
| | --- | --- |----------| --- | | ||||
| | [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.5.9    | [文档](https://github.com/YunaiV/SpringBoot-Labs) | | ||||
| | [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.5.10    | [文档](https://github.com/YunaiV/SpringBoot-Labs) | | ||||
| | [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7      |  | | ||||
| | [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.8    | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) | | ||||
| | [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.4.3.4  | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) | | ||||
| | [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.1  | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) | | ||||
| | [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.5.0    | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) | | ||||
| | [Redis](https://redis.io/) | key-value 数据库 | 5.0      |  | | ||||
| | [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.16.8   | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) | | ||||
| | [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架  | 5.3.15   | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) | | ||||
| | [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.5.4    | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) | | ||||
| | [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.0    | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) | | ||||
| | [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架  | 5.3.16   | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) | | ||||
| | [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.5.5    | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) | | ||||
| | [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.2    | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) | | ||||
| | [Activiti](https://github.com/Activiti/Activiti) | 工作流引擎 | 7.1.0.M6 | [文档](TODO)  | | ||||
| | [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2    | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) | | ||||
| | [Knife4j](https://gitee.com/xiaoym/knife4j) | Swagger 增强 UI 实现 | 3.0.2    | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) | | ||||
|   | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -16,7 +16,7 @@ | ||||
|     <properties> | ||||
|         <revision>1.5.0-snapshot</revision> | ||||
|         <!-- 统一依赖管理 --> | ||||
|         <spring.boot.version>2.5.9</spring.boot.version> | ||||
|         <spring.boot.version>2.5.10</spring.boot.version> | ||||
|         <!-- Web 相关 --> | ||||
|         <knife4j.version>3.0.2</knife4j.version> | ||||
|         <swagger-annotations.version>1.5.22</swagger-annotations.version> | ||||
|   | ||||
| @@ -0,0 +1,20 @@ | ||||
| package cn.iocoder.yudao.framework.common.enums; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Getter; | ||||
|  | ||||
| /** | ||||
|  * 文档地址 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| public enum DocumentEnum { | ||||
|  | ||||
|     REDIS_INSTALL("https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues/I4VCSJ", "Redis 安装文档"); | ||||
|  | ||||
|     private final String url; | ||||
|     private final String memo; | ||||
|  | ||||
| } | ||||
| @@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.common.enums; | ||||
| /** | ||||
|  * Web 过滤器顺序的枚举类,保证过滤器按照符合我们的预期 | ||||
|  * | ||||
|  * 考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 util 包下 | ||||
|  *  考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 util 包下 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.common.util.collection; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.collection.CollectionUtil; | ||||
| import com.google.common.collect.ImmutableMap; | ||||
|  | ||||
| import java.util.*; | ||||
| import java.util.function.BinaryOperator; | ||||
| @@ -125,6 +126,15 @@ public class CollectionUtils { | ||||
|         return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toSet()))); | ||||
|     } | ||||
|  | ||||
|     public static <T, K> Map<K, T> convertImmutableMap(Collection<T> from, Function<T, K> keyFunc) { | ||||
|         if (CollUtil.isEmpty(from)) { | ||||
|             return Collections.emptyMap(); | ||||
|         } | ||||
|         ImmutableMap.Builder<K, T> builder = ImmutableMap.builder(); | ||||
|         from.forEach(item -> builder.put(keyFunc.apply(item), item)); | ||||
|         return builder.build(); | ||||
|     } | ||||
|  | ||||
|     public static boolean containsAny(Collection<?> source, Collection<?> candidates) { | ||||
|         return org.springframework.util.CollectionUtils.containsAny(source, candidates); | ||||
|     } | ||||
| @@ -140,6 +150,15 @@ public class CollectionUtils { | ||||
|         return from.stream().filter(predicate).findFirst().orElse(null); | ||||
|     } | ||||
|  | ||||
|     public static <T, V extends Comparable<? super V>> V getMaxValue(List<T> from, Function<T, V> valueFunc) { | ||||
|         if (CollUtil.isEmpty(from)) { | ||||
|             return null; | ||||
|         } | ||||
|         assert from.size() > 0; // 断言,避免告警 | ||||
|         T t = from.stream().max(Comparator.comparing(valueFunc)).get(); | ||||
|         return valueFunc.apply(t); | ||||
|     } | ||||
|  | ||||
|     public static <T> void addIfNotNull(Collection<T> coll, T item) { | ||||
|         if (item == null) { | ||||
|             return; | ||||
| @@ -147,4 +166,7 @@ public class CollectionUtils { | ||||
|         coll.add(item); | ||||
|     } | ||||
|  | ||||
|     public static <T> Collection<T> singleton(T deptId) { | ||||
|         return deptId == null ? Collections.emptyList() : Collections.singleton(deptId); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -14,22 +14,28 @@ import java.util.Set; | ||||
| @Data | ||||
| public class TenantProperties { | ||||
|  | ||||
| //    /** | ||||
| //     * 租户是否开启 | ||||
| //     */ | ||||
| //    private static final Boolean ENABLE_DEFAULT = true; | ||||
| // | ||||
| //    /** | ||||
| //     * 是否开启 | ||||
| //     */ | ||||
| //    private Boolean enable = ENABLE_DEFAULT; | ||||
|     /** | ||||
|      * 租户是否开启 | ||||
|      */ | ||||
|     private static final Boolean ENABLE_DEFAULT = true; | ||||
|  | ||||
|     /** | ||||
|      * 需要多租户的表 | ||||
|      * | ||||
|      * 由于多租户并不作为 yudao 项目的重点功能,更多是扩展性的功能,所以采用正向配置需要多租户的表。 | ||||
|      * 如果需要,你可以改成 ignoreTables 来取消部分不需要的表 | ||||
|      * 是否开启 | ||||
|      */ | ||||
|     private Set<String> tables; | ||||
|     private Boolean enable = ENABLE_DEFAULT; | ||||
|  | ||||
|     /** | ||||
|      * 需要忽略多租户的请求 | ||||
|      * | ||||
|      * 默认情况下,每个请求需要带上 tenant-id 的请求头。但是,部分请求是无需带上的,例如说短信回调、支付回调等 Open API! | ||||
|      */ | ||||
|     private Set<String> ignoreUrls; | ||||
|  | ||||
|     /** | ||||
|      * 需要忽略多租户的表 | ||||
|      * | ||||
|      * 即默认所有表都开启多租户的功能,所以记得添加对应的 tenant_id 字段哟 | ||||
|      */ | ||||
|     private Set<String> ignoreTables; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,106 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.config; | ||||
|  | ||||
| import cn.hutool.core.annotation.AnnotationUtil; | ||||
| import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum; | ||||
| import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; | ||||
| import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; | ||||
| import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnoreAspect; | ||||
| import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor; | ||||
| import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; | ||||
| import cn.iocoder.yudao.framework.tenant.core.job.TenantJobHandlerDecorator; | ||||
| import cn.iocoder.yudao.framework.tenant.core.mq.TenantRedisMessageInterceptor; | ||||
| import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter; | ||||
| import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService; | ||||
| import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter; | ||||
| import cn.iocoder.yudao.framework.web.config.WebProperties; | ||||
| import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; | ||||
| import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; | ||||
| import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; | ||||
| import org.springframework.beans.BeansException; | ||||
| import org.springframework.beans.factory.config.BeanPostProcessor; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||||
| import org.springframework.boot.web.servlet.FilterRegistrationBean; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| @Configuration | ||||
| @ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) // 允许使用 yudao.tenant.enable=false 禁用多租户 | ||||
| @EnableConfigurationProperties(TenantProperties.class) | ||||
| public class YudaoTenantAutoConfiguration { | ||||
|  | ||||
|     // ========== AOP ========== | ||||
|  | ||||
|     @Bean | ||||
|     public TenantIgnoreAspect tenantIgnoreAspect() { | ||||
|         return new TenantIgnoreAspect(); | ||||
|     } | ||||
|  | ||||
|     // ========== DB ========== | ||||
|  | ||||
|     @Bean | ||||
|     public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties properties, | ||||
|                                                                  MybatisPlusInterceptor interceptor) { | ||||
|         TenantLineInnerInterceptor inner = new TenantLineInnerInterceptor(new TenantDatabaseInterceptor(properties)); | ||||
|         // 添加到 interceptor 中 | ||||
|         // 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定 | ||||
|         MyBatisUtils.addInterceptor(interceptor, inner, 0); | ||||
|         return inner; | ||||
|     } | ||||
|  | ||||
|     // ========== WEB ========== | ||||
|  | ||||
|     @Bean | ||||
|     public FilterRegistrationBean<TenantContextWebFilter> tenantContextWebFilter() { | ||||
|         FilterRegistrationBean<TenantContextWebFilter> registrationBean = new FilterRegistrationBean<>(); | ||||
|         registrationBean.setFilter(new TenantContextWebFilter()); | ||||
|         registrationBean.setOrder(WebFilterOrderEnum.TENANT_CONTEXT_FILTER); | ||||
|         return registrationBean; | ||||
|     } | ||||
|  | ||||
|     // ========== Security ========== | ||||
|  | ||||
|     @Bean | ||||
|     public FilterRegistrationBean<TenantSecurityWebFilter> tenantSecurityWebFilter(TenantProperties tenantProperties, | ||||
|                                                                                    WebProperties webProperties, | ||||
|                                                                                    GlobalExceptionHandler globalExceptionHandler, | ||||
|                                                                                    TenantFrameworkService tenantFrameworkService) { | ||||
|         FilterRegistrationBean<TenantSecurityWebFilter> registrationBean = new FilterRegistrationBean<>(); | ||||
|         registrationBean.setFilter(new TenantSecurityWebFilter(tenantProperties, webProperties, | ||||
|                 globalExceptionHandler, tenantFrameworkService)); | ||||
|         registrationBean.setOrder(WebFilterOrderEnum.TENANT_SECURITY_FILTER); | ||||
|         return registrationBean; | ||||
|     } | ||||
|  | ||||
|     // ========== MQ ========== | ||||
|  | ||||
|     @Bean | ||||
|     public TenantRedisMessageInterceptor tenantRedisMessageInterceptor() { | ||||
|         return new TenantRedisMessageInterceptor(); | ||||
|     } | ||||
|  | ||||
|     // ========== Job ========== | ||||
|  | ||||
|     @Bean | ||||
|     @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") | ||||
|     public BeanPostProcessor jobHandlerBeanPostProcessor(TenantFrameworkService tenantFrameworkService) { | ||||
|         return new BeanPostProcessor() { | ||||
|  | ||||
|             @Override | ||||
|             public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { | ||||
|                 if (!(bean instanceof JobHandler)) { | ||||
|                     return bean; | ||||
|                 } | ||||
|                 // 有 TenantJob 注解的情况下,才会进行处理 | ||||
|                 if (!AnnotationUtil.hasAnnotation(bean.getClass(), TenantJob.class)) { | ||||
|                     return bean; | ||||
|                 } | ||||
|  | ||||
|                 // 使用 TenantJobHandlerDecorator 装饰 | ||||
|                 return new TenantJobHandlerDecorator(tenantFrameworkService, (JobHandler) bean); | ||||
|             } | ||||
|  | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,30 +0,0 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.config; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; | ||||
| import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor; | ||||
| import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; | ||||
| import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; | ||||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| /** | ||||
|  * 多租户针对 DB 的自动配置 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Configuration | ||||
| @EnableConfigurationProperties(TenantProperties.class) | ||||
| public class YudaoTenantDatabaseAutoConfiguration { | ||||
|  | ||||
|     @Bean | ||||
|     public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties properties, | ||||
|                                                                  MybatisPlusInterceptor interceptor) { | ||||
|         TenantLineInnerInterceptor inner = new TenantLineInnerInterceptor(new TenantDatabaseInterceptor(properties)); | ||||
|         // 添加到 interceptor 中 | ||||
|         // 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定 | ||||
|         MyBatisUtils.addInterceptor(interceptor, inner, 0); | ||||
|         return inner; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,43 +0,0 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.config; | ||||
|  | ||||
| import cn.hutool.core.annotation.AnnotationUtil; | ||||
| import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; | ||||
| import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; | ||||
| import cn.iocoder.yudao.framework.tenant.core.job.TenantJobHandlerDecorator; | ||||
| import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService; | ||||
| import org.springframework.beans.BeansException; | ||||
| import org.springframework.beans.factory.config.BeanPostProcessor; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| /** | ||||
|  * 多租户针对 Job 的自动配置 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Configuration | ||||
| public class YudaoTenantJobAutoConfiguration { | ||||
|  | ||||
|     @Bean | ||||
|     @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") | ||||
|     public BeanPostProcessor jobHandlerBeanPostProcessor(TenantFrameworkService tenantFrameworkService) { | ||||
|         return new BeanPostProcessor() { | ||||
|  | ||||
|             @Override | ||||
|             public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { | ||||
|                 if (!(bean instanceof JobHandler)) { | ||||
|                     return bean; | ||||
|                 } | ||||
|                 // 有 TenantJob 注解的情况下,才会进行处理 | ||||
|                 if (!AnnotationUtil.hasAnnotation(bean.getClass(), TenantJob.class)) { | ||||
|                     return bean; | ||||
|                 } | ||||
|  | ||||
|                 // 使用 TenantJobHandlerDecorator 装饰 | ||||
|                 return new TenantJobHandlerDecorator(tenantFrameworkService, (JobHandler) bean); | ||||
|             } | ||||
|  | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,20 +0,0 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.config; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.tenant.core.mq.TenantRedisMessageInterceptor; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| /** | ||||
|  * 多租户针对 MQ 的自动配置 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Configuration | ||||
| public class YudaoTenantMQAutoConfiguration { | ||||
|  | ||||
|     @Bean | ||||
|     public TenantRedisMessageInterceptor tenantRedisMessageInterceptor() { | ||||
|         return new TenantRedisMessageInterceptor(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,25 +0,0 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.config; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum; | ||||
| import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter; | ||||
| import org.springframework.boot.web.servlet.FilterRegistrationBean; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| /** | ||||
|  * 多租户针对 Web 的自动配置 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Configuration | ||||
| public class YudaoTenantSecurityAutoConfiguration { | ||||
|  | ||||
|     @Bean | ||||
|     public FilterRegistrationBean<TenantSecurityWebFilter> tenantSecurityWebFilter() { | ||||
|         FilterRegistrationBean<TenantSecurityWebFilter> registrationBean = new FilterRegistrationBean<>(); | ||||
|         registrationBean.setFilter(new TenantSecurityWebFilter()); | ||||
|         registrationBean.setOrder(WebFilterOrderEnum.TENANT_SECURITY_FILTER); | ||||
|         return registrationBean; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,25 +0,0 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.config; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum; | ||||
| import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter; | ||||
| import org.springframework.boot.web.servlet.FilterRegistrationBean; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
|  | ||||
| /** | ||||
|  * 多租户针对 Web 的自动配置 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Configuration | ||||
| public class YudaoTenantWebAutoConfiguration { | ||||
|  | ||||
|     @Bean | ||||
|     public FilterRegistrationBean<TenantContextWebFilter> tenantContextWebFilter() { | ||||
|         FilterRegistrationBean<TenantContextWebFilter> registrationBean = new FilterRegistrationBean<>(); | ||||
|         registrationBean.setFilter(new TenantContextWebFilter()); | ||||
|         registrationBean.setOrder(WebFilterOrderEnum.TENANT_CONTEXT_FILTER); | ||||
|         return registrationBean; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,18 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.core.aop; | ||||
|  | ||||
| import java.lang.annotation.*; | ||||
|  | ||||
| /** | ||||
|  * 忽略租户,标记指定方法不进行租户的自动过滤 | ||||
|  * | ||||
|  * 注意,只有 DB 的场景会过滤,其它场景暂时不过滤: | ||||
|  * 1、Redis 场景:因为是基于 Key 实现多租户的能力,所以忽略没有意义,不像 DB 是一个 column 实现的 | ||||
|  * 2、MQ 场景:有点难以抉择,目前可以通过 Consumer 手动在消费的方法上,添加 @TenantIgnore 进行忽略 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Target({ElementType.METHOD}) | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| @Inherited | ||||
| public @interface TenantIgnore { | ||||
| } | ||||
| @@ -0,0 +1,32 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.core.aop; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.aspectj.lang.ProceedingJoinPoint; | ||||
| import org.aspectj.lang.annotation.Around; | ||||
| import org.aspectj.lang.annotation.Aspect; | ||||
|  | ||||
| /** | ||||
|  * 忽略多租户的 Aspect,基于 {@link TenantIgnore} 注解实现,用于一些全局的逻辑。 | ||||
|  * 例如说,一个定时任务,读取所有数据,进行处理。 | ||||
|  * 又例如说,读取所有数据,进行缓存。 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Aspect | ||||
| @Slf4j | ||||
| public class TenantIgnoreAspect { | ||||
|  | ||||
|     @Around("@annotation(tenantIgnore)") | ||||
|     public Object around(ProceedingJoinPoint joinPoint, TenantIgnore tenantIgnore) throws Throwable { | ||||
|         Boolean oldIgnore = TenantContextHolder.isIgnore(); | ||||
|         try { | ||||
|             TenantContextHolder.setIgnore(true); | ||||
|             // 执行逻辑 | ||||
|             return joinPoint.proceed(); | ||||
|         } finally { | ||||
|             TenantContextHolder.setIgnore(oldIgnore); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -9,12 +9,15 @@ import com.alibaba.ttl.TransmittableThreadLocal; | ||||
|  */ | ||||
| public class TenantContextHolder { | ||||
|  | ||||
|     /** | ||||
|      * 当前租户编号 | ||||
|      */ | ||||
|     private static final ThreadLocal<Long> TENANT_ID = new TransmittableThreadLocal<>(); | ||||
|  | ||||
|     /** | ||||
|      * 租户编号 - 空 | ||||
|      * 是否忽略租户 | ||||
|      */ | ||||
|     private static final Long TENANT_ID_NULL = 0L; | ||||
|     private static final ThreadLocal<Boolean> IGNORE = new TransmittableThreadLocal<>(); | ||||
|  | ||||
|     /** | ||||
|      * 获得租户编号。 | ||||
| @@ -33,26 +36,31 @@ public class TenantContextHolder { | ||||
|     public static Long getRequiredTenantId() { | ||||
|         Long tenantId = getTenantId(); | ||||
|         if (tenantId == null) { | ||||
|             throw new NullPointerException("TenantContextHolder 不存在租户编号"); | ||||
|             throw new NullPointerException("TenantContextHolder 不存在租户编号"); // TODO 芋艿:增加文档链接 | ||||
|         } | ||||
|         return tenantId; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 在一些前端场景下,可能无法请求带上租户。例如说,<img /> 方式获取图片等 | ||||
|      * 此时,暂时的解决方案,是在该接口的 Controller 方法上,调用该方法 | ||||
|      * TODO 芋艿:思考有没更合适的方案,目标是去掉该方法 | ||||
|      */ | ||||
|     public static void setNullTenantId() { | ||||
|         TENANT_ID.set(TENANT_ID_NULL); | ||||
|     } | ||||
|  | ||||
|     public static void setTenantId(Long tenantId) { | ||||
|         TENANT_ID.set(tenantId); | ||||
|     } | ||||
|  | ||||
|     public static void setIgnore(Boolean ignore) { | ||||
|         IGNORE.set(ignore); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 当前是否忽略租户 | ||||
|      * | ||||
|      * @return 是否忽略 | ||||
|      */ | ||||
|     public static boolean isIgnore() { | ||||
|         return Boolean.TRUE.equals(IGNORE.get()); | ||||
|     } | ||||
|  | ||||
|     public static void clear() { | ||||
|         TENANT_ID.remove(); | ||||
|         IGNORE.remove(); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -3,12 +3,10 @@ package cn.iocoder.yudao.framework.tenant.core.db; | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.iocoder.yudao.framework.tenant.config.TenantProperties; | ||||
| import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | ||||
| import com.baomidou.mybatisplus.core.metadata.TableInfo; | ||||
| import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; | ||||
| import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; | ||||
| import lombok.AllArgsConstructor; | ||||
| import net.sf.jsqlparser.expression.Expression; | ||||
| import net.sf.jsqlparser.expression.StringValue; | ||||
| import net.sf.jsqlparser.expression.LongValue; | ||||
|  | ||||
| /** | ||||
|  * 基于 MyBatis Plus 多租户的功能,实现 DB 层面的多租户的功能 | ||||
| @@ -22,18 +20,13 @@ public class TenantDatabaseInterceptor implements TenantLineHandler { | ||||
|  | ||||
|     @Override | ||||
|     public Expression getTenantId() { | ||||
|         return new StringValue(TenantContextHolder.getRequiredTenantId().toString()); | ||||
|         return new LongValue( TenantContextHolder.getRequiredTenantId()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean ignoreTable(String tableName) { | ||||
|         // 如果实体类继承 TenantBaseDO 类,则是多租户表,不进行忽略 | ||||
|         TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName); | ||||
|         if (tableInfo != null && TenantBaseDO.class.isAssignableFrom(tableInfo.getEntityType())) { | ||||
|             return false; | ||||
|         } | ||||
|         // 不包含,说明要过滤 | ||||
|         return !CollUtil.contains(properties.getTables(), tableName); | ||||
|         return TenantContextHolder.isIgnore() // 情况一,全局忽略多租户 | ||||
|             || CollUtil.contains(properties.getIgnoreTables(), tableName); // 情况二,忽略多租户的表 | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,14 +0,0 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.core.job; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.quartz.core.handler.JobHandlerInvoker; | ||||
|  | ||||
| /** | ||||
|  * 多租户 JobHandlerInvoker 拓展实现类 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| public class TenantJobHandlerInvoker extends JobHandlerInvoker { | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
| @@ -1,13 +1,19 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.core.security; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; | ||||
| import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||||
| import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; | ||||
| import cn.iocoder.yudao.framework.security.core.LoginUser; | ||||
| import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; | ||||
| import cn.iocoder.yudao.framework.tenant.config.TenantProperties; | ||||
| import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | ||||
| import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService; | ||||
| import cn.iocoder.yudao.framework.web.config.WebProperties; | ||||
| import cn.iocoder.yudao.framework.web.core.filter.ApiRequestFilter; | ||||
| import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.web.filter.OncePerRequestFilter; | ||||
| import org.springframework.util.AntPathMatcher; | ||||
|  | ||||
| import javax.servlet.FilterChain; | ||||
| import javax.servlet.ServletException; | ||||
| @@ -18,34 +24,92 @@ import java.util.Objects; | ||||
|  | ||||
| /** | ||||
|  * 多租户 Security Web 过滤器 | ||||
|  * 校验用户访问的租户,是否是其所在的租户,避免越权问题 | ||||
|  * 1. 如果是登陆的用户,校验是否有权限访问该租户,避免越权问题。 | ||||
|  * 2. 如果请求未带租户的编号,检查是否是忽略的 URL,否则也不允许访问。 | ||||
|  * 3. 校验租户是合法,例如说被禁用、到期 | ||||
|  * | ||||
|  * 校验用户访问的租户,是否是其所在的租户, | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Slf4j | ||||
| public class TenantSecurityWebFilter extends OncePerRequestFilter { | ||||
| public class TenantSecurityWebFilter extends ApiRequestFilter { | ||||
|  | ||||
|     private final TenantProperties tenantProperties; | ||||
|  | ||||
|     private final AntPathMatcher pathMatcher; | ||||
|  | ||||
|     private final GlobalExceptionHandler globalExceptionHandler; | ||||
|     private final TenantFrameworkService tenantFrameworkService; | ||||
|  | ||||
|     public TenantSecurityWebFilter(TenantProperties tenantProperties, | ||||
|                                    WebProperties webProperties, | ||||
|                                    GlobalExceptionHandler globalExceptionHandler, | ||||
|                                    TenantFrameworkService tenantFrameworkService) { | ||||
|         super(webProperties); | ||||
|         this.tenantProperties = tenantProperties; | ||||
|         this.pathMatcher = new AntPathMatcher(); | ||||
|         this.globalExceptionHandler = globalExceptionHandler; | ||||
|         this.tenantFrameworkService = tenantFrameworkService; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) | ||||
|             throws ServletException, IOException { | ||||
|         Long tenantId = TenantContextHolder.getTenantId(); | ||||
|         // 1. 登陆的用户,校验是否有权限访问该租户,避免越权问题。 | ||||
|         LoginUser user = SecurityFrameworkUtils.getLoginUser(); | ||||
|         assert user != null; // shouldNotFilter 已经校验 | ||||
|         // 校验租户是否匹配。 | ||||
|         if (!Objects.equals(user.getTenantId(), TenantContextHolder.getTenantId())) { | ||||
|             log.error("[doFilterInternal][租户({}) User({}/{}) 越权访问租户({}) URL({}/{})]", | ||||
|                     user.getTenantId(), user.getId(), user.getUserType(), | ||||
|                     TenantContextHolder.getTenantId(), request.getRequestURI(), request.getMethod()); | ||||
|             ServletUtils.writeJSON(response, CommonResult.error(GlobalErrorCodeConstants.FORBIDDEN.getCode(), | ||||
|                     "您无权访问该租户的数据")); | ||||
|         if (user != null) { | ||||
|             // 如果获取不到租户编号,则尝试使用登陆用户的租户编号 | ||||
|             if (tenantId == null) { | ||||
|                 tenantId = user.getTenantId(); | ||||
|                 TenantContextHolder.setTenantId(tenantId); | ||||
|             // 如果传递了租户编号,则进行比对租户编号,避免越权问题 | ||||
|             } else if (!Objects.equals(user.getTenantId(), TenantContextHolder.getTenantId())) { | ||||
|                 log.error("[doFilterInternal][租户({}) User({}/{}) 越权访问租户({}) URL({}/{})]", | ||||
|                         user.getTenantId(), user.getId(), user.getUserType(), | ||||
|                         TenantContextHolder.getTenantId(), request.getRequestURI(), request.getMethod()); | ||||
|                 ServletUtils.writeJSON(response, CommonResult.error(GlobalErrorCodeConstants.FORBIDDEN.getCode(), | ||||
|                         "您无权访问该租户的数据")); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 2. 如果请求未带租户的编号,检查是否是忽略的 URL,否则也不允许访问。 | ||||
|         if (tenantId == null && !isIgnoreUrl(request)) { | ||||
|             log.error("[doFilterInternal][URL({}/{}) 未传递租户编号]", request.getRequestURI(), request.getMethod()); | ||||
|             ServletUtils.writeJSON(response, CommonResult.error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), | ||||
|                     "租户的请求未传递,请进行排查")); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // 3. 校验租户是合法,例如说被禁用、到期 | ||||
|         if (tenantId != null) { | ||||
|             try { | ||||
|                 tenantFrameworkService.validTenant(tenantId); | ||||
|             } catch (Throwable ex) { | ||||
|                 CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex); | ||||
|                 ServletUtils.writeJSON(response, result); | ||||
|                 return; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // 继续过滤 | ||||
|         chain.doFilter(request, response); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected boolean shouldNotFilter(HttpServletRequest request) { | ||||
|         return SecurityFrameworkUtils.getLoginUser() == null; | ||||
|     private boolean isIgnoreUrl(HttpServletRequest request) { | ||||
|         // 快速匹配,保证性能 | ||||
|         if (CollUtil.contains(tenantProperties.getIgnoreUrls(), request.getRequestURI())) { | ||||
|             return true; | ||||
|         } | ||||
|         // 逐个 Ant 路径匹配 | ||||
|         for (String url : tenantProperties.getIgnoreUrls()) { | ||||
|             if (pathMatcher.match(url, request.getRequestURI())) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -16,4 +16,11 @@ public interface TenantFrameworkService { | ||||
|      */ | ||||
|     List<Long> getTenantIds(); | ||||
|  | ||||
|     /** | ||||
|      * 校验租户是否合法 | ||||
|      * | ||||
|      * @param id 租户编号 | ||||
|      */ | ||||
|     void validTenant(Long id); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,36 @@ | ||||
| package cn.iocoder.yudao.framework.tenant.core.util; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | ||||
|  | ||||
| /** | ||||
|  * 多租户 Util | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| public class TenantUtils { | ||||
|  | ||||
|     /** | ||||
|      * 使用指定租户,执行对应的逻辑 | ||||
|      * | ||||
|      * 注意,如果当前是忽略租户的情况下,会被强制设置成不忽略租户 | ||||
|      * 当然,执行完成后,还是会恢复回去 | ||||
|      * | ||||
|      * @param tenantId 租户编号 | ||||
|      * @param runnable 逻辑 | ||||
|      */ | ||||
|     public static void execute(Long tenantId, Runnable runnable) { | ||||
|         Long oldTenantId = TenantContextHolder.getTenantId(); | ||||
|         Boolean oldIgnore = TenantContextHolder.isIgnore(); | ||||
|         try { | ||||
|             TenantContextHolder.setTenantId(tenantId); | ||||
|             TenantContextHolder.setIgnore(false); | ||||
|             // 执行逻辑 | ||||
|             runnable.run(); | ||||
|         } finally { | ||||
|             TenantContextHolder.setTenantId(oldTenantId); | ||||
|             TenantContextHolder.setIgnore(oldIgnore); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
| @@ -1,6 +1,2 @@ | ||||
| org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ | ||||
|   cn.iocoder.yudao.framework.tenant.config.YudaoTenantDatabaseAutoConfiguration,\ | ||||
|   cn.iocoder.yudao.framework.tenant.config.YudaoTenantWebAutoConfiguration,\ | ||||
|   cn.iocoder.yudao.framework.tenant.config.YudaoTenantJobAutoConfiguration,\ | ||||
|   cn.iocoder.yudao.framework.tenant.config.YudaoTenantMQAutoConfiguration,\ | ||||
|   cn.iocoder.yudao.framework.tenant.config.YudaoTenantSecurityAutoConfiguration | ||||
|   cn.iocoder.yudao.framework.tenant.config.YudaoTenantAutoConfiguration | ||||
|   | ||||
| @@ -1,6 +1,9 @@ | ||||
| package cn.iocoder.yudao.framework.mq.config; | ||||
|  | ||||
| import cn.hutool.core.map.MapUtil; | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.hutool.system.SystemUtil; | ||||
| import cn.iocoder.yudao.framework.common.enums.DocumentEnum; | ||||
| import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate; | ||||
| import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor; | ||||
| import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; | ||||
| @@ -10,10 +13,12 @@ import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.boot.autoconfigure.AutoConfigureAfter; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.data.redis.connection.RedisServerCommands; | ||||
| import org.springframework.data.redis.connection.stream.Consumer; | ||||
| import org.springframework.data.redis.connection.stream.ObjectRecord; | ||||
| import org.springframework.data.redis.connection.stream.ReadOffset; | ||||
| import org.springframework.data.redis.connection.stream.StreamOffset; | ||||
| import org.springframework.data.redis.core.RedisCallback; | ||||
| import org.springframework.data.redis.core.RedisTemplate; | ||||
| import org.springframework.data.redis.core.StringRedisTemplate; | ||||
| import org.springframework.data.redis.listener.ChannelTopic; | ||||
| @@ -22,6 +27,7 @@ import org.springframework.data.redis.stream.DefaultStreamMessageListenerContain | ||||
| import org.springframework.data.redis.stream.StreamMessageListenerContainer; | ||||
|  | ||||
| import java.util.List; | ||||
| import java.util.Properties; | ||||
|  | ||||
| /** | ||||
|  * 消息队列配置类 | ||||
| @@ -73,6 +79,7 @@ public class YudaoMQAutoConfiguration { | ||||
|     public StreamMessageListenerContainer<String, ObjectRecord<String, String>> redisStreamMessageListenerContainer( | ||||
|             RedisMQTemplate redisMQTemplate, List<AbstractStreamMessageListener<?>> listeners) { | ||||
|         RedisTemplate<String, ?> redisTemplate = redisMQTemplate.getRedisTemplate(); | ||||
|         checkRedisVersion(redisTemplate); | ||||
|         // 第一步,创建 StreamMessageListenerContainer 容器 | ||||
|         // 创建 options 配置 | ||||
|         StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, ObjectRecord<String, String>> containerOptions = | ||||
| @@ -118,4 +125,19 @@ public class YudaoMQAutoConfiguration { | ||||
|         return String.format("%s@%d", SystemUtil.getHostInfo().getAddress(), SystemUtil.getCurrentPID()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 校验 Redis 版本号,是否满足最低的版本号要求! | ||||
|      */ | ||||
|     private static void checkRedisVersion(RedisTemplate<String, ?> redisTemplate) { | ||||
|         // 获得 Redis 版本 | ||||
|         Properties info = redisTemplate.execute((RedisCallback<Properties>) RedisServerCommands::info); | ||||
|         String version = MapUtil.getStr(info, "redis_version"); | ||||
|         // 校验最低版本必须大于等于 5.0.0 | ||||
|         int majorVersion = Integer.parseInt(StrUtil.subBefore(version, '.', false)); | ||||
|         if (majorVersion < 5) { | ||||
|             throw new IllegalStateException(StrUtil.format("您当前的 Redis 版本为 {},小于最低要求的 5.0.0 版本!" + | ||||
|                     "请参考 {} 文档进行安装。", version, DocumentEnum.REDIS_INSTALL.getUrl())); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -43,12 +43,16 @@ public interface BaseMapperX<T> extends BaseMapper<T> { | ||||
|         return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2)); | ||||
|     } | ||||
|  | ||||
|     default Integer selectCount(String field, Object value) { | ||||
|         return selectCount(new QueryWrapper<T>().eq(field, value)).intValue(); | ||||
|     default Long selectCount() { | ||||
|         return selectCount(new QueryWrapper<T>()); | ||||
|     } | ||||
|  | ||||
|     default Integer selectCount(SFunction<T, ?> field, Object value) { | ||||
|         return selectCount(new LambdaQueryWrapper<T>().eq(field, value)).intValue(); | ||||
|     default Long selectCount(String field, Object value) { | ||||
|         return selectCount(new QueryWrapper<T>().eq(field, value)); | ||||
|     } | ||||
|  | ||||
|     default Long selectCount(SFunction<T, ?> field, Object value) { | ||||
|         return selectCount(new LambdaQueryWrapper<T>().eq(field, value)); | ||||
|     } | ||||
|  | ||||
|     default List<T> selectList() { | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| package cn.iocoder.yudao.module.system.test; | ||||
| package cn.iocoder.yudao.framework.test.config; | ||||
| 
 | ||||
| import com.github.fppt.jedismock.RedisServer; | ||||
| import org.springframework.boot.autoconfigure.data.redis.RedisProperties; | ||||
| @@ -9,6 +9,11 @@ import org.springframework.context.annotation.Lazy; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| /** | ||||
|  * Redis 测试 Configuration,主要实现内嵌 Redis 的启动 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Configuration(proxyBeanMethods = false) | ||||
| @Lazy(false) // 禁止延迟加载 | ||||
| @EnableConfigurationProperties(RedisProperties.class) | ||||
| @@ -20,7 +25,7 @@ public class RedisTestConfiguration { | ||||
|     @Bean | ||||
|     public RedisServer redisServer(RedisProperties properties) throws IOException { | ||||
|         RedisServer redisServer = new RedisServer(properties.getPort()); | ||||
|         // TODO 芋艿:一次执行多个单元测试时,貌似创建多个 spring 容器,导致不进行 stop。这样,就导致端口被占用,无法启动。。。 | ||||
|         // 一次执行多个单元测试时,貌似创建多个 spring 容器,导致不进行 stop。这样,就导致端口被占用,无法启动。。。 | ||||
|         try { | ||||
|             redisServer.start(); | ||||
|         } catch (Exception ignore) {} | ||||
| @@ -0,0 +1,52 @@ | ||||
| package cn.iocoder.yudao.framework.test.config; | ||||
|  | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; | ||||
| import org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties; | ||||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||||
| import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer; | ||||
| import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer; | ||||
| import org.springframework.boot.sql.init.DatabaseInitializationSettings; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.context.annotation.Lazy; | ||||
|  | ||||
| import javax.sql.DataSource; | ||||
|  | ||||
| /** | ||||
|  * SQL 初始化的测试 Configuration | ||||
|  * | ||||
|  * 为什么不使用 org.springframework.boot.autoconfigure.sql.init.DataSourceInitializationConfiguration 呢? | ||||
|  * 因为我们在单元测试会使用 spring.main.lazy-initialization 为 true,开启延迟加载。此时,会导致 DataSourceInitializationConfiguration 初始化 | ||||
|  * 不过呢,当前类的实现代码,基本是复制 DataSourceInitializationConfiguration 的哈! | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Configuration(proxyBeanMethods = false) | ||||
| @ConditionalOnMissingBean(AbstractScriptDatabaseInitializer.class) | ||||
| @ConditionalOnSingleCandidate(DataSource.class) | ||||
| @ConditionalOnClass(name = "org.springframework.jdbc.datasource.init.DatabasePopulator") | ||||
| @Lazy(value = false) // 禁止延迟加载 | ||||
| @EnableConfigurationProperties(SqlInitializationProperties.class) | ||||
| public class SqlInitializationTestConfiguration { | ||||
|  | ||||
| 	@Bean | ||||
| 	public DataSourceScriptDatabaseInitializer dataSourceScriptDatabaseInitializer(DataSource dataSource, | ||||
| 																				   SqlInitializationProperties initializationProperties) { | ||||
| 		DatabaseInitializationSettings settings = createFrom(initializationProperties); | ||||
| 		return new DataSourceScriptDatabaseInitializer(dataSource, settings); | ||||
| 	} | ||||
|  | ||||
| 	static DatabaseInitializationSettings createFrom(SqlInitializationProperties properties) { | ||||
| 		DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); | ||||
| 		settings.setSchemaLocations(properties.getSchemaLocations()); | ||||
| 		settings.setDataLocations(properties.getDataLocations()); | ||||
| 		settings.setContinueOnError(properties.isContinueOnError()); | ||||
| 		settings.setSeparator(properties.getSeparator()); | ||||
| 		settings.setEncoding(properties.getEncoding()); | ||||
| 		settings.setMode(properties.getMode()); | ||||
| 		return settings; | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -8,15 +8,11 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.http.HttpHeaders; | ||||
| import springfox.documentation.RequestHandler; | ||||
| import springfox.documentation.builders.ApiInfoBuilder; | ||||
| import springfox.documentation.builders.ExampleBuilder; | ||||
| import springfox.documentation.builders.PathSelectors; | ||||
| import springfox.documentation.service.ApiInfo; | ||||
| import springfox.documentation.service.ApiKey; | ||||
| import springfox.documentation.service.AuthorizationScope; | ||||
| import springfox.documentation.service.Contact; | ||||
| import springfox.documentation.service.SecurityReference; | ||||
| import springfox.documentation.service.SecurityScheme; | ||||
| import springfox.documentation.builders.RequestParameterBuilder; | ||||
| import springfox.documentation.service.*; | ||||
| import springfox.documentation.spi.DocumentationType; | ||||
| import springfox.documentation.spi.service.contexts.SecurityContext; | ||||
| import springfox.documentation.spring.web.plugins.Docket; | ||||
| @@ -24,7 +20,6 @@ import springfox.documentation.swagger2.annotations.EnableSwagger2; | ||||
|  | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.function.Predicate; | ||||
|  | ||||
| import static springfox.documentation.builders.RequestHandlerSelectors.basePackage; | ||||
|  | ||||
| @@ -37,8 +32,8 @@ import static springfox.documentation.builders.RequestHandlerSelectors.basePacka | ||||
| @EnableSwagger2 | ||||
| @EnableKnife4j | ||||
| @ConditionalOnClass({Docket.class, ApiInfoBuilder.class}) | ||||
| @ConditionalOnProperty(prefix = "yudao.swagger", value = "enable", matchIfMissing = true) | ||||
| // 允许使用 swagger.enable=false 禁用 Swagger | ||||
| @ConditionalOnProperty(prefix = "yudao.swagger", value = "enable", matchIfMissing = true) | ||||
| @EnableConfigurationProperties(SwaggerProperties.class) | ||||
| public class YudaoSwaggerAutoConfiguration { | ||||
|  | ||||
| @@ -62,9 +57,12 @@ public class YudaoSwaggerAutoConfiguration { | ||||
|                 .paths(PathSelectors.any()) | ||||
|                 .build() | ||||
|                 .securitySchemes(securitySchemes()) | ||||
|                 .globalRequestParameters(globalRequestParameters()) | ||||
|                 .securityContexts(securityContexts()); | ||||
|     } | ||||
|  | ||||
|     // ========== apiInfo ========== | ||||
|  | ||||
|     /** | ||||
|      * API 摘要信息 | ||||
|      */ | ||||
| @@ -77,6 +75,8 @@ public class YudaoSwaggerAutoConfiguration { | ||||
|                 .build(); | ||||
|     } | ||||
|  | ||||
|     // ========== securitySchemes ========== | ||||
|  | ||||
|     /** | ||||
|      * 安全模式,这里配置通过请求头 Authorization 传递 token 参数 | ||||
|      */ | ||||
| @@ -105,4 +105,12 @@ public class YudaoSwaggerAutoConfiguration { | ||||
|         return new AuthorizationScope[]{new AuthorizationScope("global", "accessEverything")}; | ||||
|     } | ||||
|  | ||||
|     // ========== globalRequestParameters ========== | ||||
|  | ||||
|     private static List<RequestParameter> globalRequestParameters() { | ||||
|         RequestParameterBuilder tenantParameter = new RequestParameterBuilder().name("tenant-id").description("租户编号") | ||||
|                 .in(ParameterType.HEADER).example(new ExampleBuilder().value(1L).build()); | ||||
|         return Collections.singletonList(tenantParameter.build()); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,27 @@ | ||||
| package cn.iocoder.yudao.framework.web.core.filter; | ||||
|  | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.iocoder.yudao.framework.web.config.WebProperties; | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import org.springframework.web.filter.OncePerRequestFilter; | ||||
|  | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
|  | ||||
| /** | ||||
|  * 过滤 /admin-api、/app-api 等 API 请求的过滤器 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @RequiredArgsConstructor | ||||
| public abstract class ApiRequestFilter extends OncePerRequestFilter { | ||||
|  | ||||
|     protected final WebProperties webProperties; | ||||
|  | ||||
|     @Override | ||||
|     protected boolean shouldNotFilter(HttpServletRequest request) { | ||||
|         // 只过滤 API 请求的地址 | ||||
|         return !StrUtil.startWithAny(request.getRequestURI(), webProperties.getAdminApi().getPrefix(), | ||||
|                 webProperties.getAppApi().getPrefix()); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -43,8 +43,8 @@ public interface ErrorCodeConstants { | ||||
|     ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1009004002, "流程取消失败,该流程不是你发起的"); | ||||
|  | ||||
|     // ========== 流程任务 1-009-005-000 ========== | ||||
|     ErrorCode TASK_COMPLETE_FAIL_NOT_EXISTS = new ErrorCode(1009004000, "审批任务失败,原因:该任务不处于未审批"); | ||||
|     ErrorCode TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1009004001, "审批任务失败,原因:该任务的审批人不是你"); | ||||
|     ErrorCode TASK_COMPLETE_FAIL_NOT_EXISTS = new ErrorCode(1009005000, "审批任务失败,原因:该任务不处于未审批"); | ||||
|     ErrorCode TASK_COMPLETE_FAIL_ASSIGN_NOT_SELF = new ErrorCode(1009005001, "审批任务失败,原因:该任务的审批人不是你"); | ||||
|  | ||||
|     // ========== 流程任务分配规则 1-009-006-000 ========== | ||||
|     ErrorCode TASK_ASSIGN_RULE_EXISTS = new ErrorCode(1009006000, "流程({}) 的任务({}) 已经存在分配规则"); | ||||
| @@ -55,7 +55,7 @@ public interface ErrorCodeConstants { | ||||
|  | ||||
|     // ========== 动态表单模块 1-009-010-000 ========== | ||||
|     ErrorCode FORM_NOT_EXISTS = new ErrorCode(1009010000, "动态表单不存在"); | ||||
|     ErrorCode FORM_FIELD_REPEAT = new ErrorCode(1009010000, "表单项({}) 和 ({}) 使用了相同的字段名({})"); | ||||
|     ErrorCode FORM_FIELD_REPEAT = new ErrorCode(1009010001, "表单项({}) 和 ({}) 使用了相同的字段名({})"); | ||||
|  | ||||
|     // ========== 用户组模块 1-009-011-000 ========== | ||||
|     ErrorCode USER_GROUP_NOT_EXISTS = new ErrorCode(1009011000, "用户组不存在"); | ||||
|   | ||||
| @@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.bpm.test; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration; | ||||
| import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration; | ||||
| import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration; | ||||
| import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure; | ||||
| import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; | ||||
| import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; | ||||
| import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration; | ||||
| import org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration; | ||||
| import org.springframework.boot.test.context.SpringBootTest; | ||||
| import org.springframework.context.annotation.Import; | ||||
| import org.springframework.test.context.ActiveProfiles; | ||||
| @@ -21,7 +21,6 @@ import org.springframework.test.context.jdbc.Sql; | ||||
|  */ | ||||
| @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbUnitTest.Application.class) | ||||
| @ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件 | ||||
| @Sql(scripts = "/sql/create_tables.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) // 每个单元测试结束前,创建表 | ||||
| @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB | ||||
| public class BaseDbUnitTest { | ||||
|  | ||||
| @@ -31,7 +30,7 @@ public class BaseDbUnitTest { | ||||
|             DataSourceAutoConfiguration.class, // Spring DB 自动配置类 | ||||
|             DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类 | ||||
|             DruidDataSourceAutoConfigure.class, // Druid 自动配置类 | ||||
|             SqlInitializationAutoConfiguration.class, | ||||
|             SqlInitializationTestConfiguration.class, // SQL 初始化 | ||||
|             // MyBatis 配置类 | ||||
|             YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类 | ||||
|             MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类 | ||||
|   | ||||
| @@ -16,6 +16,9 @@ spring: | ||||
|     druid: | ||||
|       async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度 | ||||
|       initial-size: 1 # 单元测试,配置为 1,提升启动速度 | ||||
|   sql: | ||||
|     init: | ||||
|       schema-locations: classpath:/sql/create_tables.sql | ||||
|  | ||||
| mybatis: | ||||
|   lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试 | ||||
|   | ||||
| @@ -1,3 +1,2 @@ | ||||
| -- bpm 开头的 DB | ||||
| DELETE FROM "bpm_form"; | ||||
| DELETE FROM "bpm_user_group"; | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| -- bpm 开头的 DB | ||||
| CREATE TABLE IF NOT EXISTS "bpm_user_group" ( | ||||
|     "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, | ||||
|     "name" varchar(63) NOT NULL, | ||||
| @@ -11,7 +10,7 @@ CREATE TABLE IF NOT EXISTS "bpm_user_group" ( | ||||
|     "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||||
|     "deleted" bit NOT NULL DEFAULT FALSE, | ||||
|     PRIMARY KEY ("id") | ||||
|     ) COMMENT '用户组'; | ||||
| ) COMMENT '用户组'; | ||||
|  | ||||
| CREATE TABLE IF NOT EXISTS "bpm_form" ( | ||||
|     "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, | ||||
|   | ||||
| @@ -20,7 +20,6 @@ import org.springframework.test.context.jdbc.Sql; | ||||
|  */ | ||||
| @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbUnitTest.Application.class) | ||||
| @ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件 | ||||
| @Sql(scripts = "/sql/create_tables.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) // 每个单元测试结束前,创建表 | ||||
| @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB | ||||
| public class BaseDbUnitTest { | ||||
|  | ||||
|   | ||||
| @@ -16,6 +16,9 @@ spring: | ||||
|     druid: | ||||
|       async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度 | ||||
|       initial-size: 1 # 单元测试,配置为 1,提升启动速度 | ||||
|   sql: | ||||
|     init: | ||||
|       schema-locations: classpath:/sql/create_tables.sql | ||||
|  | ||||
|   # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 | ||||
|   redis: | ||||
|   | ||||
| @@ -1,2 +0,0 @@ | ||||
| -- bpm 开头的 DB | ||||
| DELETE FROM "bpm_form"; | ||||
|   | ||||
| @@ -1,28 +0,0 @@ | ||||
| CREATE TABLE IF NOT EXISTS "bpm_form" ( | ||||
|     "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, | ||||
|     "name" varchar(63) NOT NULL, | ||||
|     "status" tinyint NOT NULL, | ||||
|     "fields" varchar(255) NOT NULL, | ||||
|     "conf" varchar(255) NOT NULL, | ||||
|     "remark" varchar(255), | ||||
|     "creator" varchar(64) DEFAULT '', | ||||
|     "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||||
|     "updater" varchar(64) DEFAULT '', | ||||
|     "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||||
|     "deleted" bit NOT NULL DEFAULT FALSE, | ||||
|     PRIMARY KEY ("id") | ||||
|     ) COMMENT '动态表单'; | ||||
|  | ||||
| CREATE TABLE IF NOT EXISTS "bpm_user_group" ( | ||||
|     "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, | ||||
|     "name" varchar(63) NOT NULL, | ||||
|     "description" varchar(255) NOT NULL, | ||||
|     "status" tinyint NOT NULL, | ||||
|     "member_user_ids" varchar(255) NOT NULL, | ||||
|     "creator" varchar(64) DEFAULT '', | ||||
|     "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||||
|     "updater" varchar(64) DEFAULT '', | ||||
|     "update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||||
|     "deleted" bit NOT NULL DEFAULT FALSE, | ||||
|     PRIMARY KEY ("id") | ||||
|     ) COMMENT '用户组'; | ||||
|   | ||||
| @@ -30,5 +30,6 @@ public interface ErrorCodeConstants { | ||||
|     // ========= 文件相关 1001003000================= | ||||
|     ErrorCode FILE_PATH_EXISTS = new ErrorCode(1001003000, "文件路径已存在"); | ||||
|     ErrorCode FILE_NOT_EXISTS = new ErrorCode(1001003001, "文件不存在"); | ||||
|     ErrorCode FILE_IS_EMPTY = new ErrorCode(1001003002, "文件为空"); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -39,10 +39,6 @@ | ||||
|             <groupId>cn.iocoder.boot</groupId> | ||||
|             <artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>cn.iocoder.boot</groupId> | ||||
|             <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- Web 相关 --> | ||||
|         <dependency> | ||||
| @@ -67,6 +63,12 @@ | ||||
|             <artifactId>yudao-spring-boot-starter-config</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- Job 定时任务相关 --> | ||||
|         <dependency> | ||||
|             <groupId>cn.iocoder.boot</groupId> | ||||
|             <artifactId>yudao-spring-boot-starter-job</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- 消息队列相关 --> | ||||
|         <dependency> | ||||
|             <groupId>cn.iocoder.boot</groupId> | ||||
|   | ||||
| @@ -4,7 +4,6 @@ import cn.hutool.core.io.IoUtil; | ||||
| import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; | ||||
| import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | ||||
| import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FilePageReqVO; | ||||
| import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FileRespVO; | ||||
| import cn.iocoder.yudao.module.infra.convert.file.FileConvert; | ||||
| @@ -62,7 +61,6 @@ public class FileController { | ||||
|     @ApiOperation("下载文件") | ||||
|     @ApiImplicitParam(name = "path", value = "文件附件", required = true, dataTypeClass = MultipartFile.class) | ||||
|     public void getFile(HttpServletResponse response, @PathVariable("path") String path) throws IOException { | ||||
|         TenantContextHolder.setNullTenantId(); | ||||
|         FileDO file = fileService.getFile(path); | ||||
|         if (file == null) { | ||||
|             log.warn("[getFile][path({}) 文件不存在]", path); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| package cn.iocoder.yudao.module.infra.dal.dataobject.file; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; | ||||
| import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||||
| import com.baomidou.mybatisplus.annotation.IdType; | ||||
| import com.baomidou.mybatisplus.annotation.TableField; | ||||
| import com.baomidou.mybatisplus.annotation.TableId; | ||||
| @@ -21,7 +21,7 @@ import java.io.InputStream; | ||||
| @Builder | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| public class FileDO extends TenantBaseDO { | ||||
| public class FileDO extends BaseDO { | ||||
|  | ||||
|     /** | ||||
|      * 文件路径 | ||||
|   | ||||
| @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.infra.dal.dataobject.logger; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | ||||
| import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||||
| import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; | ||||
| import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||||
| import com.baomidou.mybatisplus.annotation.TableId; | ||||
| import com.baomidou.mybatisplus.annotation.TableName; | ||||
| import lombok.*; | ||||
| @@ -21,7 +21,7 @@ import java.util.Date; | ||||
| @Builder | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| public class ApiAccessLogDO extends TenantBaseDO { | ||||
| public class ApiAccessLogDO extends BaseDO { | ||||
|  | ||||
|     /** | ||||
|      * 编号 | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cn.iocoder.yudao.module.infra.dal.dataobject.logger; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | ||||
| import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; | ||||
| import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||||
| import cn.iocoder.yudao.module.infra.enums.logger.ApiErrorLogProcessStatusEnum; | ||||
| import com.baomidou.mybatisplus.annotation.TableId; | ||||
| import com.baomidou.mybatisplus.annotation.TableName; | ||||
| @@ -21,7 +21,7 @@ import java.util.Date; | ||||
| @Builder | ||||
| @NoArgsConstructor | ||||
| @AllArgsConstructor | ||||
| public class ApiErrorLogDO extends TenantBaseDO { | ||||
| public class ApiErrorLogDO extends BaseDO { | ||||
|  | ||||
|     /** | ||||
|      * 编号 | ||||
|   | ||||
| @@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | ||||
| import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; | ||||
| import cn.iocoder.yudao.module.infra.controller.admin.file.vo.FilePageReqVO; | ||||
| import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; | ||||
| import com.baomidou.mybatisplus.annotation.InterceptorIgnore; | ||||
| import org.apache.ibatis.annotations.Mapper; | ||||
|  | ||||
| /** | ||||
| @@ -24,19 +23,17 @@ public interface FileMapper extends BaseMapperX<FileDO> { | ||||
|                 .orderByDesc("create_time")); | ||||
|     } | ||||
|  | ||||
|     default Integer selectCountById(String id) { | ||||
|     default Long selectCountById(String id) { | ||||
|         return selectCount(FileDO::getId, id); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 基于 Path 获取文件 | ||||
|      * 实际上,是基于 ID 查询 | ||||
|      * 由于前端使用 <img /> 的方式获取图片,所以需要忽略租户的查询 | ||||
|      * | ||||
|      * @param path 路径 | ||||
|      * @return 文件 | ||||
|      */ | ||||
|     @InterceptorIgnore(tenantLine = "true") | ||||
|     default FileDO selectByPath(String path) { | ||||
|         return selectById(path); | ||||
|     } | ||||
|   | ||||
| @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.infra.test; | ||||
| import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration; | ||||
| import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration; | ||||
| import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; | ||||
| import cn.iocoder.yudao.framework.test.config.RedisTestConfiguration; | ||||
| import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration; | ||||
| import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure; | ||||
| import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; | ||||
| import org.redisson.spring.starter.RedissonAutoConfiguration; | ||||
| @@ -23,7 +25,6 @@ import org.springframework.test.context.jdbc.Sql; | ||||
|  */ | ||||
| @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbAndRedisUnitTest.Application.class) | ||||
| @ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件 | ||||
| @Sql(scripts = "/sql/create_tables.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) // 每个单元测试结束前,创建表 | ||||
| @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB | ||||
| public class BaseDbAndRedisUnitTest { | ||||
|  | ||||
| @@ -33,9 +34,11 @@ public class BaseDbAndRedisUnitTest { | ||||
|             DataSourceAutoConfiguration.class, // Spring DB 自动配置类 | ||||
|             DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类 | ||||
|             DruidDataSourceAutoConfigure.class, // Druid 自动配置类 | ||||
|             SqlInitializationTestConfiguration.class, // SQL 初始化 | ||||
|             // MyBatis 配置类 | ||||
|             YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类 | ||||
|             MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类 | ||||
|  | ||||
|             // Redis 配置类 | ||||
|             RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer | ||||
|             RedisAutoConfiguration.class, // Spring Redis 自动配置类 | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.infra.test; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration; | ||||
| import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration; | ||||
| import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration; | ||||
| import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure; | ||||
| import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; | ||||
| import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; | ||||
| @@ -20,7 +21,6 @@ import org.springframework.test.context.jdbc.Sql; | ||||
|  */ | ||||
| @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbUnitTest.Application.class) | ||||
| @ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件 | ||||
| @Sql(scripts = "/sql/create_tables.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) // 每个单元测试结束前,创建表 | ||||
| @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB | ||||
| public class BaseDbUnitTest { | ||||
|  | ||||
| @@ -30,6 +30,7 @@ public class BaseDbUnitTest { | ||||
|             DataSourceAutoConfiguration.class, // Spring DB 自动配置类 | ||||
|             DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类 | ||||
|             DruidDataSourceAutoConfigure.class, // Druid 自动配置类 | ||||
|             SqlInitializationTestConfiguration.class, // SQL 初始化 | ||||
|             // MyBatis 配置类 | ||||
|             YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类 | ||||
|             MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类 | ||||
|   | ||||
| @@ -1,30 +0,0 @@ | ||||
| package cn.iocoder.yudao.module.infra.test; | ||||
|  | ||||
| import com.github.fppt.jedismock.RedisServer; | ||||
| import org.springframework.boot.autoconfigure.data.redis.RedisProperties; | ||||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.context.annotation.Lazy; | ||||
|  | ||||
| import java.io.IOException; | ||||
|  | ||||
| @Configuration(proxyBeanMethods = false) | ||||
| @Lazy(false) // 禁止延迟加载 | ||||
| @EnableConfigurationProperties(RedisProperties.class) | ||||
| public class RedisTestConfiguration { | ||||
|  | ||||
|     /** | ||||
|      * 创建模拟的 Redis Server 服务器 | ||||
|      */ | ||||
|     @Bean | ||||
|     public RedisServer redisServer(RedisProperties properties) throws IOException { | ||||
|         RedisServer redisServer = new RedisServer(properties.getPort()); | ||||
|         // TODO 芋艿:一次执行多个单元测试时,貌似创建多个 spring 容器,导致不进行 stop。这样,就导致端口被占用,无法启动。。。 | ||||
|         try { | ||||
|             redisServer.start(); | ||||
|         } catch (Exception ignore) {} | ||||
|         return redisServer; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -16,6 +16,9 @@ spring: | ||||
|     druid: | ||||
|       async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度 | ||||
|       initial-size: 1 # 单元测试,配置为 1,提升启动速度 | ||||
|   sql: | ||||
|     init: | ||||
|       schema-locations: classpath:/sql/create_tables.sql | ||||
|  | ||||
|   # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 | ||||
|   redis: | ||||
|   | ||||
| @@ -13,9 +13,6 @@ public interface ErrorCodeConstants { | ||||
|     ErrorCode USER_NOT_EXISTS = new ErrorCode(1004001000, "用户不存在"); | ||||
|     ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1004001001, "密码校验失败"); | ||||
|  | ||||
|     // ========== 文件相关 1004002000 =========== | ||||
|     // TODO 芋艿:可以删除 | ||||
|     ErrorCode FILE_IS_EMPTY = new ErrorCode(1004002000, "文件为空"); | ||||
|  | ||||
|     // ========== AUTH 模块 1004003000 ========== | ||||
|     ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1004003000, "登录失败,账号密码不正确"); | ||||
|   | ||||
| @@ -21,7 +21,7 @@ import java.io.IOException; | ||||
| import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.*; | ||||
| import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | ||||
| import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; | ||||
| import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.FILE_IS_EMPTY; | ||||
| import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY; | ||||
|  | ||||
| @Api(tags = "用户 APP - 用户个人中心") | ||||
| @RestController | ||||
|   | ||||
| @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.member.test; | ||||
| import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration; | ||||
| import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration; | ||||
| import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; | ||||
| import cn.iocoder.yudao.framework.test.config.RedisTestConfiguration; | ||||
| import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration; | ||||
| import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure; | ||||
| import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; | ||||
| import org.redisson.spring.starter.RedissonAutoConfiguration; | ||||
| @@ -23,7 +25,6 @@ import org.springframework.test.context.jdbc.Sql; | ||||
|  */ | ||||
| @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbAndRedisUnitTest.Application.class) | ||||
| @ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件 | ||||
| @Sql(scripts = "/sql/create_tables.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) // 每个单元测试结束前,创建表 | ||||
| @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB | ||||
| public class BaseDbAndRedisUnitTest { | ||||
|  | ||||
| @@ -33,9 +34,11 @@ public class BaseDbAndRedisUnitTest { | ||||
|             DataSourceAutoConfiguration.class, // Spring DB 自动配置类 | ||||
|             DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类 | ||||
|             DruidDataSourceAutoConfigure.class, // Druid 自动配置类 | ||||
|             SqlInitializationTestConfiguration.class, // SQL 初始化 | ||||
|             // MyBatis 配置类 | ||||
|             YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类 | ||||
|             MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类 | ||||
|  | ||||
|             // Redis 配置类 | ||||
|             RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer | ||||
|             RedisAutoConfiguration.class, // Spring Redis 自动配置类 | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.member.test; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration; | ||||
| import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration; | ||||
| import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration; | ||||
| import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure; | ||||
| import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; | ||||
| import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; | ||||
| @@ -20,7 +21,6 @@ import org.springframework.test.context.jdbc.Sql; | ||||
|  */ | ||||
| @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbUnitTest.Application.class) | ||||
| @ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件 | ||||
| @Sql(scripts = "/sql/create_tables.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) // 每个单元测试结束前,创建表 | ||||
| @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB | ||||
| public class BaseDbUnitTest { | ||||
|  | ||||
| @@ -30,6 +30,7 @@ public class BaseDbUnitTest { | ||||
|             DataSourceAutoConfiguration.class, // Spring DB 自动配置类 | ||||
|             DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类 | ||||
|             DruidDataSourceAutoConfigure.class, // Druid 自动配置类 | ||||
|             SqlInitializationTestConfiguration.class, // SQL 初始化 | ||||
|             // MyBatis 配置类 | ||||
|             YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类 | ||||
|             MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类 | ||||
|   | ||||
| @@ -1,30 +0,0 @@ | ||||
| package cn.iocoder.yudao.module.member.test; | ||||
|  | ||||
| import com.github.fppt.jedismock.RedisServer; | ||||
| import org.springframework.boot.autoconfigure.data.redis.RedisProperties; | ||||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.context.annotation.Lazy; | ||||
|  | ||||
| import java.io.IOException; | ||||
|  | ||||
| @Configuration(proxyBeanMethods = false) | ||||
| @Lazy(false) // 禁止延迟加载 | ||||
| @EnableConfigurationProperties(RedisProperties.class) | ||||
| public class RedisTestConfiguration { | ||||
|  | ||||
|     /** | ||||
|      * 创建模拟的 Redis Server 服务器 | ||||
|      */ | ||||
|     @Bean | ||||
|     public RedisServer redisServer(RedisProperties properties) throws IOException { | ||||
|         RedisServer redisServer = new RedisServer(properties.getPort()); | ||||
|         // TODO 芋艿:一次执行多个单元测试时,貌似创建多个 spring 容器,导致不进行 stop。这样,就导致端口被占用,无法启动。。。 | ||||
|         try { | ||||
|             redisServer.start(); | ||||
|         } catch (Exception ignore) {} | ||||
|         return redisServer; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -16,6 +16,9 @@ spring: | ||||
|     druid: | ||||
|       async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度 | ||||
|       initial-size: 1 # 单元测试,配置为 1,提升启动速度 | ||||
|   sql: | ||||
|     init: | ||||
|       schema-locations: classpath:/sql/create_tables.sql | ||||
|  | ||||
|   # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 | ||||
|   redis: | ||||
|   | ||||
| @@ -33,6 +33,10 @@ | ||||
|             <groupId>cn.iocoder.boot</groupId> | ||||
|             <artifactId>yudao-spring-boot-starter-biz-pay</artifactId> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>cn.iocoder.boot</groupId> | ||||
|             <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- Web 相关 --> | ||||
|         <dependency> | ||||
|   | ||||
| @@ -1,12 +1,11 @@ | ||||
| package cn.iocoder.yudao.module.pay.dal.mysql.merchant; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | ||||
| import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; | ||||
| import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelExportReqVO; | ||||
| import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelPageReqVO; | ||||
| import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayChannelDO; | ||||
| import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | ||||
| import com.baomidou.mybatisplus.annotation.InterceptorIgnore; | ||||
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||
| import org.apache.ibatis.annotations.Mapper; | ||||
| import org.apache.ibatis.annotations.Select; | ||||
| @@ -23,7 +22,6 @@ public interface PayChannelMapper extends BaseMapperX<PayChannelDO> { | ||||
|     } | ||||
|  | ||||
|     @Select("SELECT id FROM pay_channel WHERE update_time > #{maxUpdateTime} LIMIT 1") | ||||
|     @InterceptorIgnore(tenantLine = "true") // 该方法忽略多租户。原因:该方法被异步 task 调用,此时获取不到租户编号 | ||||
|     Long selectExistsByUpdateTimeAfter(Date maxUpdateTime); | ||||
|  | ||||
|     default PageResult<PayChannelDO> selectPage(PayChannelPageReqVO reqVO) { | ||||
|   | ||||
| @@ -6,19 +6,21 @@ import cn.hutool.json.JSONUtil; | ||||
| import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||||
| import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; | ||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||||
| import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; | ||||
| import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; | ||||
| import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; | ||||
| import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelCreateReqVO; | ||||
| import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelExportReqVO; | ||||
| import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelPageReqVO; | ||||
| import cn.iocoder.yudao.module.pay.controller.admin.merchant.vo.channel.PayChannelUpdateReqVO; | ||||
| import cn.iocoder.yudao.module.pay.convert.channel.PayChannelConvert; | ||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; | ||||
| import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; | ||||
| import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayChannelDO; | ||||
| import cn.iocoder.yudao.module.pay.dal.mysql.merchant.PayChannelMapper; | ||||
| import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.context.annotation.Lazy; | ||||
| import org.springframework.scheduling.annotation.Scheduled; | ||||
| import org.springframework.stereotype.Service; | ||||
| import org.springframework.validation.annotation.Validated; | ||||
| @@ -27,12 +29,12 @@ import javax.annotation.PostConstruct; | ||||
| import javax.annotation.Resource; | ||||
| import javax.validation.Validator; | ||||
| import java.util.Collection; | ||||
| import java.util.Comparator; | ||||
| import java.util.Date; | ||||
| import java.util.List; | ||||
|  | ||||
| import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||||
| import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; | ||||
| import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_EXIST_SAME_CHANNEL_ERROR; | ||||
| import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.CHANNEL_NOT_EXISTS; | ||||
|  | ||||
| /** | ||||
|  * 支付渠道 Service 实现类 | ||||
| @@ -64,11 +66,16 @@ public class PayChannelServiceImpl implements PayChannelService { | ||||
|     @Resource | ||||
|     private Validator validator; | ||||
|  | ||||
|     @Resource | ||||
|     @Lazy // 注入自己,所以延迟加载 | ||||
|     private PayChannelService self; | ||||
|  | ||||
|     @Override | ||||
|     @PostConstruct | ||||
|     @TenantIgnore // 忽略自动化租户,全局初始化本地缓存 | ||||
|     public void initPayClients() { | ||||
|         // 获取支付渠道,如果有更新 | ||||
|         List<PayChannelDO> payChannels = this.loadPayChannelIfUpdate(maxUpdateTime); | ||||
|         List<PayChannelDO> payChannels = loadPayChannelIfUpdate(maxUpdateTime); | ||||
|         if (CollUtil.isEmpty(payChannels)) { | ||||
|             return; | ||||
|         } | ||||
| @@ -78,14 +85,13 @@ public class PayChannelServiceImpl implements PayChannelService { | ||||
|                 payChannel.getCode(), payChannel.getConfig())); | ||||
|  | ||||
|         // 写入缓存 | ||||
|         assert payChannels.size() > 0; // 断言,避免告警 | ||||
|         maxUpdateTime = payChannels.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime(); | ||||
|         maxUpdateTime = CollectionUtils.getMaxValue(payChannels, PayChannelDO::getUpdateTime); | ||||
|         log.info("[initPayClients][初始化 PayChannel 数量为 {}]", payChannels.size()); | ||||
|     } | ||||
|  | ||||
|     @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD) | ||||
|     public void schedulePeriodicRefresh() { | ||||
|         initPayClients(); | ||||
|         self.initPayClients(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.pay.test; | ||||
| import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration; | ||||
| import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration; | ||||
| import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; | ||||
| import cn.iocoder.yudao.framework.test.config.RedisTestConfiguration; | ||||
| import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration; | ||||
| import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure; | ||||
| import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; | ||||
| import org.redisson.spring.starter.RedissonAutoConfiguration; | ||||
| @@ -23,7 +25,6 @@ import org.springframework.test.context.jdbc.Sql; | ||||
|  */ | ||||
| @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbAndRedisUnitTest.Application.class) | ||||
| @ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件 | ||||
| @Sql(scripts = "/sql/create_tables.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) // 每个单元测试结束前,创建表 | ||||
| @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB | ||||
| public class BaseDbAndRedisUnitTest { | ||||
|  | ||||
| @@ -33,9 +34,11 @@ public class BaseDbAndRedisUnitTest { | ||||
|             DataSourceAutoConfiguration.class, // Spring DB 自动配置类 | ||||
|             DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类 | ||||
|             DruidDataSourceAutoConfigure.class, // Druid 自动配置类 | ||||
|             SqlInitializationTestConfiguration.class, // SQL 初始化 | ||||
|             // MyBatis 配置类 | ||||
|             YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类 | ||||
|             MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类 | ||||
|  | ||||
|             // Redis 配置类 | ||||
|             RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer | ||||
|             RedisAutoConfiguration.class, // Spring Redis 自动配置类 | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.test; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration; | ||||
| import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration; | ||||
| import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration; | ||||
| import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure; | ||||
| import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; | ||||
| import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; | ||||
| @@ -20,7 +21,6 @@ import org.springframework.test.context.jdbc.Sql; | ||||
|  */ | ||||
| @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbUnitTest.Application.class) | ||||
| @ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件 | ||||
| @Sql(scripts = "/sql/create_tables.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) // 每个单元测试结束前,创建表 | ||||
| @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB | ||||
| public class BaseDbUnitTest { | ||||
|  | ||||
| @@ -30,6 +30,7 @@ public class BaseDbUnitTest { | ||||
|             DataSourceAutoConfiguration.class, // Spring DB 自动配置类 | ||||
|             DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类 | ||||
|             DruidDataSourceAutoConfigure.class, // Druid 自动配置类 | ||||
|             SqlInitializationTestConfiguration.class, // SQL 初始化 | ||||
|             // MyBatis 配置类 | ||||
|             YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类 | ||||
|             MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类 | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package cn.iocoder.yudao.module.pay.test; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; | ||||
| import cn.iocoder.yudao.framework.test.config.RedisTestConfiguration; | ||||
| import org.redisson.spring.starter.RedissonAutoConfiguration; | ||||
| import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; | ||||
| import org.springframework.boot.test.context.SpringBootTest; | ||||
|   | ||||
| @@ -1,30 +0,0 @@ | ||||
| package cn.iocoder.yudao.module.pay.test; | ||||
|  | ||||
| import com.github.fppt.jedismock.RedisServer; | ||||
| import org.springframework.boot.autoconfigure.data.redis.RedisProperties; | ||||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.context.annotation.Lazy; | ||||
|  | ||||
| import java.io.IOException; | ||||
|  | ||||
| @Configuration(proxyBeanMethods = false) | ||||
| @Lazy(false) // 禁止延迟加载 | ||||
| @EnableConfigurationProperties(RedisProperties.class) | ||||
| public class RedisTestConfiguration { | ||||
|  | ||||
|     /** | ||||
|      * 创建模拟的 Redis Server 服务器 | ||||
|      */ | ||||
|     @Bean | ||||
|     public RedisServer redisServer(RedisProperties properties) throws IOException { | ||||
|         RedisServer redisServer = new RedisServer(properties.getPort()); | ||||
|         // TODO 芋艿:一次执行多个单元测试时,貌似创建多个 spring 容器,导致不进行 stop。这样,就导致端口被占用,无法启动。。。 | ||||
|         try { | ||||
|             redisServer.start(); | ||||
|         } catch (Exception ignore) {} | ||||
|         return redisServer; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -16,6 +16,9 @@ spring: | ||||
|     druid: | ||||
|       async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度 | ||||
|       initial-size: 1 # 单元测试,配置为 1,提升启动速度 | ||||
|   sql: | ||||
|     init: | ||||
|       schema-locations: classpath:/sql/create_tables.sql | ||||
|  | ||||
|   # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 | ||||
|   redis: | ||||
|   | ||||
| @@ -54,8 +54,9 @@ public class LoginLogCreateReqDTO { | ||||
|     private String userIp; | ||||
|     /** | ||||
|      * 浏览器 UserAgent | ||||
|      * | ||||
|      * 允许空,原因:Job 过期登出时,是无法传递 UserAgent 的 | ||||
|      */ | ||||
|     @NotEmpty(message = "浏览器 UserAgent 不能为空") | ||||
|     private String userAgent; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -18,52 +18,54 @@ public interface ErrorCodeConstants { | ||||
|     ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1002000005, "未绑定账号,需要进行绑定"); | ||||
|     ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1002000006, "Token 已经过期"); | ||||
|  | ||||
|     // ========== 菜单模块 1002002000 ========== | ||||
|     ErrorCode MENU_NAME_DUPLICATE = new ErrorCode(1002002000, "已经存在该名字的菜单"); | ||||
|     ErrorCode MENU_PARENT_NOT_EXISTS = new ErrorCode(1002002001, "父菜单不存在"); | ||||
|     ErrorCode MENU_PARENT_ERROR = new ErrorCode(1002002002, "不能设置自己为父菜单"); | ||||
|     ErrorCode MENU_NOT_EXISTS = new ErrorCode(1002002003, "菜单不存在"); | ||||
|     ErrorCode MENU_EXISTS_CHILDREN = new ErrorCode(1002002004, "存在子菜单,无法删除"); | ||||
|     ErrorCode MENU_PARENT_NOT_DIR_OR_MENU = new ErrorCode(1002002005, "父菜单的类型必须是目录或者菜单"); | ||||
|     // ========== 菜单模块 1002001000 ========== | ||||
|     ErrorCode MENU_NAME_DUPLICATE = new ErrorCode(1002001000, "已经存在该名字的菜单"); | ||||
|     ErrorCode MENU_PARENT_NOT_EXISTS = new ErrorCode(1002001001, "父菜单不存在"); | ||||
|     ErrorCode MENU_PARENT_ERROR = new ErrorCode(1002001002, "不能设置自己为父菜单"); | ||||
|     ErrorCode MENU_NOT_EXISTS = new ErrorCode(1002001003, "菜单不存在"); | ||||
|     ErrorCode MENU_EXISTS_CHILDREN = new ErrorCode(1002001004, "存在子菜单,无法删除"); | ||||
|     ErrorCode MENU_PARENT_NOT_DIR_OR_MENU = new ErrorCode(1002001005, "父菜单的类型必须是目录或者菜单"); | ||||
|  | ||||
|     // ========== 角色模块 1002003000 ========== | ||||
|     ErrorCode ROLE_NOT_EXISTS = new ErrorCode(1002003000, "角色不存在"); | ||||
|     ErrorCode ROLE_NAME_DUPLICATE = new ErrorCode(1002003001, "已经存在名为【{}】的角色"); | ||||
|     ErrorCode ROLE_CODE_DUPLICATE = new ErrorCode(1002003002, "已经存在编码为【{}】的角色"); | ||||
|     ErrorCode ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE = new ErrorCode(1002003004, "不能操作类型为系统内置的角色"); | ||||
|     ErrorCode ROLE_IS_DISABLE = new ErrorCode(1002003004, "名字为【{}】的角色已被禁用"); | ||||
|     // ========== 角色模块 1002002000 ========== | ||||
|     ErrorCode ROLE_NOT_EXISTS = new ErrorCode(1002002000, "角色不存在"); | ||||
|     ErrorCode ROLE_NAME_DUPLICATE = new ErrorCode(1002002001, "已经存在名为【{}】的角色"); | ||||
|     ErrorCode ROLE_CODE_DUPLICATE = new ErrorCode(1002002002, "已经存在编码为【{}】的角色"); | ||||
|     ErrorCode ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE = new ErrorCode(1002002003, "不能操作类型为系统内置的角色"); | ||||
|     ErrorCode ROLE_IS_DISABLE = new ErrorCode(1002002004, "名字为【{}】的角色已被禁用"); | ||||
|     ErrorCode ROLE_ADMIN_CODE_ERROR = new ErrorCode(1002002005, "编码【{}】不能使用"); | ||||
|  | ||||
|     // ========== 用户模块 1002004000 ========== | ||||
|     ErrorCode USER_USERNAME_EXISTS = new ErrorCode(1002004000, "用户账号已经存在"); | ||||
|     ErrorCode USER_MOBILE_EXISTS = new ErrorCode(1002004001, "手机号已经存在"); | ||||
|     ErrorCode USER_EMAIL_EXISTS = new ErrorCode(1002004002, "邮箱已经存在"); | ||||
|     ErrorCode USER_NOT_EXISTS = new ErrorCode(1002004003, "用户不存在"); | ||||
|     ErrorCode USER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1002004004, "导入用户数据不能为空!"); | ||||
|     ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1002004005, "用户密码校验失败"); | ||||
|     ErrorCode USER_IS_DISABLE = new ErrorCode(1002003004, "名字为【{}】的用户已被禁用"); | ||||
|     // ========== 用户模块 1002003000 ========== | ||||
|     ErrorCode USER_USERNAME_EXISTS = new ErrorCode(1002003000, "用户账号已经存在"); | ||||
|     ErrorCode USER_MOBILE_EXISTS = new ErrorCode(1002003001, "手机号已经存在"); | ||||
|     ErrorCode USER_EMAIL_EXISTS = new ErrorCode(1002003002, "邮箱已经存在"); | ||||
|     ErrorCode USER_NOT_EXISTS = new ErrorCode(1002003003, "用户不存在"); | ||||
|     ErrorCode USER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1002003004, "导入用户数据不能为空!"); | ||||
|     ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1002003005, "用户密码校验失败"); | ||||
|     ErrorCode USER_IS_DISABLE = new ErrorCode(1002003006, "名字为【{}】的用户已被禁用"); | ||||
|     ErrorCode USER_COUNT_MAX = new ErrorCode(1002003008, "创建用户失败,原因:超过租户最大租户配额({})!"); | ||||
|  | ||||
|     // ========== 部门模块 1002005000 ========== | ||||
|     ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1002004001, "已经存在该名字的部门"); | ||||
|     ErrorCode DEPT_PARENT_NOT_EXITS = new ErrorCode(1002004002,"父级部门不存在"); | ||||
|     ErrorCode DEPT_NOT_FOUND = new ErrorCode(1002004003, "当前部门不存在"); | ||||
|     ErrorCode DEPT_EXITS_CHILDREN = new ErrorCode(1002004004, "存在子部门,无法删除"); | ||||
|     ErrorCode DEPT_PARENT_ERROR = new ErrorCode(1002004005, "不能设置自己为父部门"); | ||||
|     ErrorCode DEPT_EXISTS_USER = new ErrorCode(1002004006, "部门中存在员工,无法删除"); | ||||
|     ErrorCode DEPT_NOT_ENABLE = new ErrorCode(1002004007, "部门不处于开启状态,不允许选择"); | ||||
|     ErrorCode DEPT_PARENT_IS_CHILD = new ErrorCode(1002004008, "不能设置自己的子部门为父部门"); | ||||
|     // ========== 部门模块 1002004000 ========== | ||||
|     ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1002004000, "已经存在该名字的部门"); | ||||
|     ErrorCode DEPT_PARENT_NOT_EXITS = new ErrorCode(1002004001,"父级部门不存在"); | ||||
|     ErrorCode DEPT_NOT_FOUND = new ErrorCode(1002004002, "当前部门不存在"); | ||||
|     ErrorCode DEPT_EXITS_CHILDREN = new ErrorCode(1002004003, "存在子部门,无法删除"); | ||||
|     ErrorCode DEPT_PARENT_ERROR = new ErrorCode(1002004004, "不能设置自己为父部门"); | ||||
|     ErrorCode DEPT_EXISTS_USER = new ErrorCode(1002004005, "部门中存在员工,无法删除"); | ||||
|     ErrorCode DEPT_NOT_ENABLE = new ErrorCode(1002004006, "部门不处于开启状态,不允许选择"); | ||||
|     ErrorCode DEPT_PARENT_IS_CHILD = new ErrorCode(1002004007, "不能设置自己的子部门为父部门"); | ||||
|  | ||||
|     // ========== 岗位模块 1002005000 ========== | ||||
|     ErrorCode POST_NOT_FOUND = new ErrorCode(1002005001, "当前岗位不存在"); | ||||
|     ErrorCode POST_NOT_ENABLE = new ErrorCode(1002005002, "岗位({}) 不处于开启状态,不允许选择"); | ||||
|     ErrorCode POST_NAME_DUPLICATE = new ErrorCode(1002005001, "已经存在该名字的岗位"); | ||||
|     ErrorCode POST_CODE_DUPLICATE = new ErrorCode(1002005001, "已经存在该标识的岗位"); | ||||
|     ErrorCode POST_NOT_FOUND = new ErrorCode(1002005000, "当前岗位不存在"); | ||||
|     ErrorCode POST_NOT_ENABLE = new ErrorCode(1002005001, "岗位({}) 不处于开启状态,不允许选择"); | ||||
|     ErrorCode POST_NAME_DUPLICATE = new ErrorCode(1002005002, "已经存在该名字的岗位"); | ||||
|     ErrorCode POST_CODE_DUPLICATE = new ErrorCode(1002005003, "已经存在该标识的岗位"); | ||||
|  | ||||
|     // ========== 字典类型 1002006000 ========== | ||||
|     ErrorCode DICT_TYPE_NOT_EXISTS = new ErrorCode(1002006001, "当前字典类型不存在"); | ||||
|     ErrorCode DICT_TYPE_NOT_ENABLE = new ErrorCode(1002006002, "字典类型不处于开启状态,不允许选择"); | ||||
|     ErrorCode DICT_TYPE_NAME_DUPLICATE = new ErrorCode(1002006003, "已经存在该名字的字典类型"); | ||||
|     ErrorCode DICT_TYPE_TYPE_DUPLICATE = new ErrorCode(1002006004, "已经存在该类型的字典类型"); | ||||
|     ErrorCode DICT_TYPE_HAS_CHILDREN = new ErrorCode(1002006004, "无法删除,该字典类型还有字典数据"); | ||||
|     ErrorCode DICT_TYPE_HAS_CHILDREN = new ErrorCode(1002006005, "无法删除,该字典类型还有字典数据"); | ||||
|  | ||||
|     // ========== 字典数据 1002007000 ========== | ||||
|     ErrorCode DICT_DATA_NOT_EXISTS = new ErrorCode(1002007001, "当前字典数据不存在"); | ||||
| @@ -73,45 +75,48 @@ public interface ErrorCodeConstants { | ||||
|     // ========== 通知公告 1002008000 ========== | ||||
|     ErrorCode NOTICE_NOT_FOUND = new ErrorCode(1002008001, "当前通知公告不存在"); | ||||
|  | ||||
|     // ========== 文件 1002009000 ========== | ||||
|     ErrorCode FILE_PATH_EXISTS = new ErrorCode(1002009001, "文件路径已经存在"); | ||||
|     ErrorCode FILE_UPLOAD_FAILED = new ErrorCode(1002009002, "文件上传失败"); | ||||
|     ErrorCode FILE_IS_EMPTY= new ErrorCode(1002009003, "文件为空"); | ||||
|  | ||||
|     // ========== 短信渠道 1002011000 ========== | ||||
|     ErrorCode SMS_CHANNEL_NOT_EXISTS = new ErrorCode(1002011000, "短信渠道不存在"); | ||||
|     ErrorCode SMS_CHANNEL_DISABLE = new ErrorCode(1002011001, "短信渠道不处于开启状态,不允许选择"); | ||||
|     ErrorCode SMS_CHANNEL_HAS_CHILDREN = new ErrorCode(1002011002, "无法删除,该短信渠道还有短信模板"); | ||||
|  | ||||
|     // ========== 短信模板 1002011000 ========== | ||||
|     ErrorCode SMS_TEMPLATE_NOT_EXISTS = new ErrorCode(1002011000, "短信模板不存在"); | ||||
|     ErrorCode SMS_TEMPLATE_CODE_DUPLICATE = new ErrorCode(1002011001, "已经存在编码为【{}】的短信模板"); | ||||
|     // ========== 短信模板 1002012000 ========== | ||||
|     ErrorCode SMS_TEMPLATE_NOT_EXISTS = new ErrorCode(1002012000, "短信模板不存在"); | ||||
|     ErrorCode SMS_TEMPLATE_CODE_DUPLICATE = new ErrorCode(1002012001, "已经存在编码为【{}】的短信模板"); | ||||
|  | ||||
|     // ========== 短信发送 1002012000 ========== | ||||
|     ErrorCode SMS_SEND_MOBILE_NOT_EXISTS = new ErrorCode(1002012000, "手机号不存在"); | ||||
|     ErrorCode SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS = new ErrorCode(1002012001, "模板参数({})缺失"); | ||||
|     ErrorCode SMS_SEND_TEMPLATE_NOT_EXISTS = new ErrorCode(1002012002, "短信模板不存在"); | ||||
|     // ========== 短信发送 1002013000 ========== | ||||
|     ErrorCode SMS_SEND_MOBILE_NOT_EXISTS = new ErrorCode(1002013000, "手机号不存在"); | ||||
|     ErrorCode SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS = new ErrorCode(1002013001, "模板参数({})缺失"); | ||||
|     ErrorCode SMS_SEND_TEMPLATE_NOT_EXISTS = new ErrorCode(1002013002, "短信模板不存在"); | ||||
|  | ||||
|     // ========== 短信验证码 1002013000 ========== | ||||
|     ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1002013000, "验证码不存在"); | ||||
|     ErrorCode SMS_CODE_EXPIRED = new ErrorCode(1002013001, "验证码已过期"); | ||||
|     ErrorCode SMS_CODE_USED = new ErrorCode(1002013002, "验证码已使用"); | ||||
|     ErrorCode SMS_CODE_NOT_CORRECT = new ErrorCode(1002013004, "验证码不正确"); | ||||
|     ErrorCode SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1002013005, "超过每日短信发送数量"); | ||||
|     ErrorCode SMS_CODE_SEND_TOO_FAST = new ErrorCode(1002013006, "短信发送过于频率"); | ||||
|     ErrorCode SMS_CODE_IS_EXISTS = new ErrorCode(1002013007, "手机号已被使用"); | ||||
|     ErrorCode SMS_CODE_IS_UNUSED = new ErrorCode(1002013008, "验证码未被使用"); | ||||
|     // ========== 短信验证码 1002014000 ========== | ||||
|     ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1002014000, "验证码不存在"); | ||||
|     ErrorCode SMS_CODE_EXPIRED = new ErrorCode(1002014001, "验证码已过期"); | ||||
|     ErrorCode SMS_CODE_USED = new ErrorCode(1002014002, "验证码已使用"); | ||||
|     ErrorCode SMS_CODE_NOT_CORRECT = new ErrorCode(1002014003, "验证码不正确"); | ||||
|     ErrorCode SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1002014004, "超过每日短信发送数量"); | ||||
|     ErrorCode SMS_CODE_SEND_TOO_FAST = new ErrorCode(1002014005, "短信发送过于频率"); | ||||
|     ErrorCode SMS_CODE_IS_EXISTS = new ErrorCode(1002014006, "手机号已被使用"); | ||||
|     ErrorCode SMS_CODE_IS_UNUSED = new ErrorCode(1002014007, "验证码未被使用"); | ||||
|  | ||||
|     // ========== 租户模块 1002014000 ========== | ||||
|     ErrorCode TENANT_NOT_EXISTS = new ErrorCode(1002014000, "租户不存在"); | ||||
|     // ========== 租户信息 1002015000 ========== | ||||
|     ErrorCode TENANT_NOT_EXISTS = new ErrorCode(1002015000, "租户不存在"); | ||||
|     ErrorCode TENANT_DISABLE = new ErrorCode(1002015001, "名字为【{}】的租户已被禁用"); | ||||
|     ErrorCode TENANT_EXPIRE = new ErrorCode(1002015002, "名字为【{}】的租户已过期"); | ||||
|     ErrorCode TENANT_CAN_NOT_UPDATE_SYSTEM = new ErrorCode(1002015003, "系统租户不能进行修改、删除等操作!"); | ||||
|  | ||||
|     // ========== 错误码模块 1002015000 ========== | ||||
|     ErrorCode ERROR_CODE_NOT_EXISTS = new ErrorCode(1002015000, "错误码不存在"); | ||||
|     ErrorCode ERROR_CODE_DUPLICATE = new ErrorCode(1002015001, "已经存在编码为【{}】的错误码"); | ||||
|     // ========== 租户套餐 1002016000 ========== | ||||
|     ErrorCode TENANT_PACKAGE_NOT_EXISTS = new ErrorCode(1002016000, "租户套餐不存在"); | ||||
|     ErrorCode TENANT_PACKAGE_USED = new ErrorCode(1002016001, "租户正在使用该套餐,请给租户重新设置套餐后再尝试删除"); | ||||
|     ErrorCode TENANT_PACKAGE_DISABLE = new ErrorCode(1002016002, "名字为【{}】的租户套餐已被禁用"); | ||||
|  | ||||
|     // ========== 社交用户 1002015000 ========== | ||||
|     ErrorCode SOCIAL_USER_AUTH_FAILURE = new ErrorCode(1002015000, "社交授权失败,原因是:{}"); | ||||
|     ErrorCode SOCIAL_USER_UNBIND_NOT_SELF = new ErrorCode(1002015001, "社交解绑失败,非当前用户绑定"); | ||||
|     ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1002015001, "社交授权失败,找不到对应的用户"); | ||||
|     // ========== 错误码模块 1002017000 ========== | ||||
|     ErrorCode ERROR_CODE_NOT_EXISTS = new ErrorCode(1002017000, "错误码不存在"); | ||||
|     ErrorCode ERROR_CODE_DUPLICATE = new ErrorCode(1002017001, "已经存在编码为【{}】的错误码"); | ||||
|  | ||||
|     // ========== 社交用户 1002018000 ========== | ||||
|     ErrorCode SOCIAL_USER_AUTH_FAILURE = new ErrorCode(1002018000, "社交授权失败,原因是:{}"); | ||||
|     ErrorCode SOCIAL_USER_UNBIND_NOT_SELF = new ErrorCode(1002018001, "社交解绑失败,非当前用户绑定"); | ||||
|     ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1002018002, "社交授权失败,找不到对应的用户"); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -53,11 +53,11 @@ | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>cn.iocoder.boot</groupId> | ||||
|             <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId> | ||||
|             <artifactId>yudao-spring-boot-starter-biz-social</artifactId> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>cn.iocoder.boot</groupId> | ||||
|             <artifactId>yudao-spring-boot-starter-biz-social</artifactId> | ||||
|             <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- Web 相关 --> | ||||
| @@ -77,6 +77,12 @@ | ||||
|             <artifactId>yudao-spring-boot-starter-redis</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- Job 定时任务相关 --> | ||||
|         <dependency> | ||||
|             <groupId>cn.iocoder.boot</groupId> | ||||
|             <artifactId>yudao-spring-boot-starter-job</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- 消息队列相关 --> | ||||
|         <dependency> | ||||
|             <groupId>cn.iocoder.boot</groupId> | ||||
|   | ||||
| @@ -72,7 +72,7 @@ public class AuthController { | ||||
|         // 获得角色列表 | ||||
|         List<RoleDO> roleList = roleService.getRolesFromCache(getLoginUserRoleIds()); | ||||
|         // 获得菜单列表 | ||||
|         List<MenuDO> menuList = permissionService.getRoleMenusFromCache( | ||||
|         List<MenuDO> menuList = permissionService.getRoleMenuListFromCache( | ||||
|                 getLoginUserRoleIds(), // 注意,基于登录的角色,因为后续的权限判断也是基于它 | ||||
|                 SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType(), MenuTypeEnum.BUTTON.getType()), | ||||
|                 SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus())); | ||||
| @@ -84,7 +84,7 @@ public class AuthController { | ||||
|     @ApiOperation("获得登录用户的菜单列表") | ||||
|     public CommonResult<List<AuthMenuRespVO>> getMenus() { | ||||
|         // 获得用户拥有的菜单列表 | ||||
|         List<MenuDO> menuList = permissionService.getRoleMenusFromCache( | ||||
|         List<MenuDO> menuList = permissionService.getRoleMenuListFromCache( | ||||
|                 getLoginUserRoleIds(), // 注意,基于登录的角色,因为后续的权限判断也是基于它 | ||||
|                 SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型 | ||||
|                 SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的 | ||||
|   | ||||
| @@ -20,8 +20,7 @@ public class DeptBaseVO { | ||||
|     @Size(max = 30, message = "部门名称长度不能超过30个字符") | ||||
|     private String name; | ||||
|  | ||||
|     @ApiModelProperty(value = "父菜单 ID", required = true, example = "1024") | ||||
|     @NotNull(message = "父菜单 ID 不能为空") | ||||
|     @ApiModelProperty(value = "父菜单 ID", example = "1024") | ||||
|     private Long parentId; | ||||
|  | ||||
|     @ApiModelProperty(value = "显示顺序不能为空", required = true, example = "1024") | ||||
|   | ||||
| @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.*; | ||||
| import cn.iocoder.yudao.module.system.convert.permission.MenuConvert; | ||||
| import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; | ||||
| import cn.iocoder.yudao.module.system.service.permission.MenuService; | ||||
| import cn.iocoder.yudao.module.system.service.tenant.TenantService; | ||||
| import io.swagger.annotations.Api; | ||||
| import io.swagger.annotations.ApiImplicitParam; | ||||
| import io.swagger.annotations.ApiOperation; | ||||
| @@ -28,6 +29,8 @@ public class MenuController { | ||||
|  | ||||
|     @Resource | ||||
|     private MenuService menuService; | ||||
|     @Resource | ||||
|     private TenantService tenantService; | ||||
|  | ||||
|     @PostMapping("/create") | ||||
|     @ApiOperation("创建菜单") | ||||
| @@ -55,7 +58,7 @@ public class MenuController { | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/list") | ||||
|     @ApiOperation("获取菜单列表") | ||||
|     @ApiOperation(value = "获取菜单列表", notes = "用于【菜单管理】界面") | ||||
|     @PreAuthorize("@ss.hasPermission('system:menu:query')") | ||||
|     public CommonResult<List<MenuRespVO>> getMenus(MenuListReqVO reqVO) { | ||||
|         List<MenuDO> list = menuService.getMenus(reqVO); | ||||
| @@ -64,13 +67,14 @@ public class MenuController { | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/list-all-simple") | ||||
|     @ApiOperation(value = "获取菜单精简信息列表", notes = "只包含被开启的菜单,主要用于前端的下拉选项") | ||||
|     @ApiOperation(value = "获取菜单精简信息列表", notes = "只包含被开启的菜单,用于【角色分配菜单】功能的选项。" + | ||||
|             "在多租户的场景下,会只返回租户所在套餐有的菜单") | ||||
|     public CommonResult<List<MenuSimpleRespVO>> getSimpleMenus() { | ||||
|         // 获得菜单列表,只要开启状态的 | ||||
|         MenuListReqVO reqVO = new MenuListReqVO(); | ||||
|         reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); | ||||
|         List<MenuDO> list = menuService.getMenus(reqVO); | ||||
|         // 排序后,返回个诶前端 | ||||
|         List<MenuDO> list = menuService.getTenantMenus(reqVO); | ||||
|         // 排序后,返回给前端 | ||||
|         list.sort(Comparator.comparing(MenuDO::getSort)); | ||||
|         return success(MenuConvert.INSTANCE.convertList02(list)); | ||||
|     } | ||||
|   | ||||
| @@ -1,13 +1,16 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.permission; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||||
| import cn.iocoder.yudao.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleDataScopeReqVO; | ||||
| import cn.iocoder.yudao.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleMenuReqVO; | ||||
| import cn.iocoder.yudao.module.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO; | ||||
| import cn.iocoder.yudao.module.system.service.permission.PermissionService; | ||||
| import cn.iocoder.yudao.module.system.service.tenant.TenantService; | ||||
| import io.swagger.annotations.Api; | ||||
| import io.swagger.annotations.ApiImplicitParam; | ||||
| import io.swagger.annotations.ApiOperation; | ||||
| import org.springframework.security.access.prepost.PreAuthorize; | ||||
| import org.springframework.validation.annotation.Validated; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
|  | ||||
| @@ -29,28 +32,33 @@ public class PermissionController { | ||||
|  | ||||
|     @Resource | ||||
|     private PermissionService permissionService; | ||||
|     @Resource | ||||
|     private TenantService tenantService; | ||||
|  | ||||
|     // TODO @芋艿:处理下全新啊标识 | ||||
|  | ||||
|     @ApiOperation("获得角色拥有的菜单编号") | ||||
|     @ApiImplicitParam(name = "roleId", value = "角色编号", required = true, dataTypeClass = Long.class) | ||||
|     @GetMapping("/list-role-resources") | ||||
| //    @RequiresPermissions("system:permission:assign-role-menu") | ||||
|     @PreAuthorize("@ss.hasPermission('system:permission:assign-role-menu')") | ||||
|     public CommonResult<Set<Long>> listRoleMenus(Long roleId) { | ||||
|         return success(permissionService.listRoleMenuIds(roleId)); | ||||
|         return success(permissionService.getRoleMenuIds(roleId)); | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/assign-role-menu") | ||||
|     @ApiOperation("赋予角色菜单") | ||||
| //    @RequiresPermissions("system:permission:assign-role-resource") | ||||
|     @PreAuthorize("@ss.hasPermission('system:permission:assign-role-menu')") | ||||
|     public CommonResult<Boolean> assignRoleMenu(@Validated @RequestBody PermissionAssignRoleMenuReqVO reqVO) { | ||||
|         // 开启多租户的情况下,需要过滤掉未开通的菜单 | ||||
|         tenantService.handleTenantMenu(menuIds -> reqVO.getMenuIds().removeIf(menuId -> !CollUtil.contains(menuIds, menuId))); | ||||
|  | ||||
|         // 执行菜单的分配 | ||||
|         permissionService.assignRoleMenu(reqVO.getRoleId(), reqVO.getMenuIds()); | ||||
|         return success(true); | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/assign-role-data-scope") | ||||
|     @ApiOperation("赋予角色数据权限") | ||||
| //    @RequiresPermissions("system:permission:assign-role-data-scope") | ||||
|     @PreAuthorize("@ss.hasPermission('system:permission:assign-role-data-scope')") | ||||
|     public CommonResult<Boolean> assignRoleDataScope(@Valid @RequestBody PermissionAssignRoleDataScopeReqVO reqVO) { | ||||
|         permissionService.assignRoleDataScope(reqVO.getRoleId(), reqVO.getDataScope(), reqVO.getDataScopeDeptIds()); | ||||
|         return success(true); | ||||
| @@ -59,14 +67,14 @@ public class PermissionController { | ||||
|     @ApiOperation("获得管理员拥有的角色编号列表") | ||||
|     @ApiImplicitParam(name = "userId", value = "用户编号", required = true, dataTypeClass = Long.class) | ||||
|     @GetMapping("/list-user-roles") | ||||
| //    @RequiresPermissions("system:permission:assign-user-role") | ||||
|     @PreAuthorize("@ss.hasPermission('system:permission:assign-user-role')") | ||||
|     public CommonResult<Set<Long>> listAdminRoles(@RequestParam("userId") Long userId) { | ||||
|         return success(permissionService.getUserRoleIdListByUserId(userId)); | ||||
|     } | ||||
|  | ||||
|     @ApiOperation("赋予用户角色") | ||||
|     @PostMapping("/assign-user-role") | ||||
| //    @RequiresPermissions("system:permission:assign-user-role") | ||||
|     @PreAuthorize("@ss.hasPermission('system:permission:assign-user-role')") | ||||
|     public CommonResult<Boolean> assignUserRole(@Validated @RequestBody PermissionAssignUserRoleReqVO reqVO) { | ||||
|         permissionService.assignUserRole(reqVO.getUserId(), reqVO.getRoleIds()); | ||||
|         return success(true); | ||||
|   | ||||
| @@ -40,7 +40,7 @@ public class RoleController { | ||||
|     @ApiOperation("创建角色") | ||||
|     @PreAuthorize("@ss.hasPermission('system:role:create')") | ||||
|     public CommonResult<Long> createRole(@Valid @RequestBody RoleCreateReqVO reqVO) { | ||||
|         return success(roleService.createRole(reqVO)); | ||||
|         return success(roleService.createRole(reqVO, null)); | ||||
|     } | ||||
|  | ||||
|     @PutMapping("/update") | ||||
| @@ -88,7 +88,7 @@ public class RoleController { | ||||
|     public CommonResult<List<RoleSimpleRespVO>> getSimpleRoles() { | ||||
|         // 获得角色列表,只要开启状态的 | ||||
|         List<RoleDO> list = roleService.getRoles(Collections.singleton(CommonStatusEnum.ENABLE.getStatus())); | ||||
|         // 排序后,返回个诶前端 | ||||
|         // 排序后,返回给前端 | ||||
|         list.sort(Comparator.comparing(RoleDO::getSort)); | ||||
|         return success(RoleConvert.INSTANCE.convertList02(list)); | ||||
|     } | ||||
|   | ||||
| @@ -6,6 +6,8 @@ import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import javax.validation.constraints.NotNull; | ||||
|  | ||||
| @ApiModel("管理后台 - 菜单精简信息 Response VO") | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| @@ -21,4 +23,8 @@ public class MenuSimpleRespVO { | ||||
|     @ApiModelProperty(value = "父菜单 ID", required = true, example = "1024") | ||||
|     private Long parentId; | ||||
|  | ||||
|     @ApiModelProperty(value = "类型", required = true, example = "1", notes = "参见 MenuTypeEnum 枚举类") | ||||
|     @NotNull(message = "菜单类型不能为空") | ||||
|     private Integer type; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -28,9 +28,6 @@ public class RoleBaseVO { | ||||
|     @NotNull(message = "显示顺序不能为空") | ||||
|     private Integer sort; | ||||
|  | ||||
|     @ApiModelProperty(value = "角色类型", required = true, example = "1", notes = "见 RoleTypeEnum 枚举") | ||||
|     private Integer type; | ||||
|  | ||||
|     @ApiModelProperty(value = "备注", example = "我是一个角色") | ||||
|     private String remark; | ||||
|  | ||||
|   | ||||
| @@ -19,7 +19,6 @@ import javax.servlet.http.HttpServletRequest; | ||||
|  | ||||
| import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | ||||
|  | ||||
| // TODO 芋艿:这块的接口命名,在纠结下 | ||||
| @Api(tags = "管理后台 - 短信回调") | ||||
| @RestController | ||||
| @RequestMapping("/system/sms/callback") | ||||
| @@ -28,7 +27,7 @@ public class SmsCallbackController { | ||||
|     @Resource | ||||
|     private SmsSendService smsSendService; | ||||
|  | ||||
|     @PostMapping("/sms/yunpian") | ||||
|     @PostMapping("/yunpian") | ||||
|     @ApiOperation(value = "云片短信的回调", notes = "参见 https://www.yunpian.com/official/document/sms/zh_cn/domestic_push_report 文档") | ||||
|     @ApiImplicitParam(name = "sms_status", value = "发送状态", required = true, example = "[{具体内容}]", dataTypeClass = String.class) | ||||
|     @OperateLog(enable = false) | ||||
| @@ -38,7 +37,7 @@ public class SmsCallbackController { | ||||
|         return "SUCCESS"; // 约定返回 SUCCESS 为成功 | ||||
|     } | ||||
|  | ||||
|     @PostMapping("/sms/aliyun") | ||||
|     @PostMapping("/aliyun") | ||||
|     @ApiOperation(value = "阿里云短信的回调", notes = "参见 https://help.aliyun.com/document_detail/120998.html 文档") | ||||
|     @OperateLog(enable = false) | ||||
|     public CommonResult<Boolean> receiveAliyunSmsStatus(HttpServletRequest request) throws Throwable { | ||||
|   | ||||
| @@ -0,0 +1,18 @@ | ||||
| ### 创建租户 /admin-api/system/tenant/create | ||||
| POST {{baseUrl}}/system/tenant/create | ||||
| Content-Type: application/json | ||||
| Authorization: Bearer {{token}} | ||||
| tenant-id: {{adminTenentId}} | ||||
|  | ||||
| { | ||||
|   "name": "芋道", | ||||
|   "contactName": "芋艿", | ||||
|   "contactMobile": "15601691300", | ||||
|   "status": 0, | ||||
|   "domain": "https://www.iocoder.cn", | ||||
|   "packageId": 110, | ||||
|   "expireTime": 1699545600000, | ||||
|   "accountCount": 20, | ||||
|   "username": "admin", | ||||
|   "password": "123321" | ||||
| } | ||||
| @@ -1,13 +1,13 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant; | ||||
|  | ||||
| import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.*; | ||||
| import cn.iocoder.yudao.module.system.convert.tenant.TenantConvert; | ||||
| import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO; | ||||
| import cn.iocoder.yudao.module.system.service.tenant.TenantService; | ||||
| import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; | ||||
| import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; | ||||
| import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.*; | ||||
| import cn.iocoder.yudao.module.system.convert.tenant.TenantConvert; | ||||
| import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO; | ||||
| import cn.iocoder.yudao.module.system.service.tenant.TenantService; | ||||
| import io.swagger.annotations.Api; | ||||
| import io.swagger.annotations.ApiImplicitParam; | ||||
| import io.swagger.annotations.ApiOperation; | ||||
| @@ -18,7 +18,6 @@ import javax.annotation.Resource; | ||||
| import javax.servlet.http.HttpServletResponse; | ||||
| import javax.validation.Valid; | ||||
| import java.io.IOException; | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
|  | ||||
| import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | ||||
| @@ -73,15 +72,6 @@ public class TenantController { | ||||
|         return success(TenantConvert.INSTANCE.convert(tenant)); | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/list") | ||||
|     @ApiOperation("获得租户列表") | ||||
|     @ApiImplicitParam(name = "ids", value = "编号列表", required = true, example = "1024,2048", dataTypeClass = List.class) | ||||
|     @PreAuthorize("@ss.hasPermission('system:tenant:query')") | ||||
|     public CommonResult<List<TenantRespVO>> getTenantList(@RequestParam("ids") Collection<Long> ids) { | ||||
|         List<TenantDO> list = tenantService.getTenantList(ids); | ||||
|         return success(TenantConvert.INSTANCE.convertList(list)); | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/page") | ||||
|     @ApiOperation("获得租户分页") | ||||
|     @PreAuthorize("@ss.hasPermission('system:tenant:query')") | ||||
|   | ||||
| @@ -0,0 +1,81 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||||
| import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.*; | ||||
| import cn.iocoder.yudao.module.system.convert.tenant.TenantPackageConvert; | ||||
| import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO; | ||||
| import cn.iocoder.yudao.module.system.service.tenant.TenantPackageService; | ||||
| import io.swagger.annotations.Api; | ||||
| import io.swagger.annotations.ApiImplicitParam; | ||||
| import io.swagger.annotations.ApiOperation; | ||||
| import org.springframework.security.access.prepost.PreAuthorize; | ||||
| import org.springframework.validation.annotation.Validated; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
| import javax.validation.Valid; | ||||
| import java.util.List; | ||||
|  | ||||
| import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | ||||
|  | ||||
| @Api(tags = "管理后台 - 租户套餐") | ||||
| @RestController | ||||
| @RequestMapping("/system/tenant-package") | ||||
| @Validated | ||||
| public class TenantPackageController { | ||||
|  | ||||
|     @Resource | ||||
|     private TenantPackageService tenantPackageService; | ||||
|  | ||||
|     @PostMapping("/create") | ||||
|     @ApiOperation("创建租户套餐") | ||||
|     @PreAuthorize("@ss.hasPermission('system:tenant-package:create')") | ||||
|     public CommonResult<Long> createTenantPackage(@Valid @RequestBody TenantPackageCreateReqVO createReqVO) { | ||||
|         return success(tenantPackageService.createTenantPackage(createReqVO)); | ||||
|     } | ||||
|  | ||||
|     @PutMapping("/update") | ||||
|     @ApiOperation("更新租户套餐") | ||||
|     @PreAuthorize("@ss.hasPermission('system:tenant-package:update')") | ||||
|     public CommonResult<Boolean> updateTenantPackage(@Valid @RequestBody TenantPackageUpdateReqVO updateReqVO) { | ||||
|         tenantPackageService.updateTenantPackage(updateReqVO); | ||||
|         return success(true); | ||||
|     } | ||||
|  | ||||
|     @DeleteMapping("/delete") | ||||
|     @ApiOperation("删除租户套餐") | ||||
|     @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class) | ||||
|     @PreAuthorize("@ss.hasPermission('system:tenant-package:delete')") | ||||
|     public CommonResult<Boolean> deleteTenantPackage(@RequestParam("id") Long id) { | ||||
|         tenantPackageService.deleteTenantPackage(id); | ||||
|         return success(true); | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/get") | ||||
|     @ApiOperation("获得租户套餐") | ||||
|     @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class) | ||||
|     @PreAuthorize("@ss.hasPermission('system:tenant-package:query')") | ||||
|     public CommonResult<TenantPackageRespVO> getTenantPackage(@RequestParam("id") Long id) { | ||||
|         TenantPackageDO tenantPackage = tenantPackageService.getTenantPackage(id); | ||||
|         return success(TenantPackageConvert.INSTANCE.convert(tenantPackage)); | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/page") | ||||
|     @ApiOperation("获得租户套餐分页") | ||||
|     @PreAuthorize("@ss.hasPermission('system:tenant-package:query')") | ||||
|     public CommonResult<PageResult<TenantPackageRespVO>> getTenantPackagePage(@Valid TenantPackagePageReqVO pageVO) { | ||||
|         PageResult<TenantPackageDO> pageResult = tenantPackageService.getTenantPackagePage(pageVO); | ||||
|         return success(TenantPackageConvert.INSTANCE.convertPage(pageResult)); | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/get-simple-list") | ||||
|     @ApiOperation(value = "获取租户套餐精简信息列表", notes = "只包含被开启的租户套餐,主要用于前端的下拉选项") | ||||
|     public CommonResult<List<TenantPackageSimpleRespVO>> getTenantPackageList() { | ||||
|         // 获得角色列表,只要开启状态的 | ||||
|         List<TenantPackageDO> list = tenantPackageService.getTenantPackageListByStatus(CommonStatusEnum.ENABLE.getStatus()); | ||||
|         return success(TenantPackageConvert.INSTANCE.convertList02(list)); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,29 +0,0 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant.vo; | ||||
|  | ||||
| import lombok.*; | ||||
| import io.swagger.annotations.*; | ||||
| import javax.validation.constraints.*; | ||||
|  | ||||
| /** | ||||
| * 租户 Base VO,提供给添加、修改、详细的子 VO 使用 | ||||
| * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 | ||||
| */ | ||||
| @Data | ||||
| public class TenantBaseVO { | ||||
|  | ||||
|     @ApiModelProperty(value = "租户名", required = true, example = "芋道") | ||||
|     @NotNull(message = "租户名不能为空") | ||||
|     private String name; | ||||
|  | ||||
|     @ApiModelProperty(value = "联系人", required = true, example = "芋艿") | ||||
|     @NotNull(message = "联系人不能为空") | ||||
|     private String contactName; | ||||
|  | ||||
|     @ApiModelProperty(value = "联系手机", example = "15601691300") | ||||
|     private String contactMobile; | ||||
|  | ||||
|     @ApiModelProperty(value = "租户状态(0正常 1停用)", required = true, example = "1") | ||||
|     @NotNull(message = "租户状态(0正常 1停用)不能为空") | ||||
|     private Integer status; | ||||
|  | ||||
| } | ||||
| @@ -1,12 +0,0 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant.vo; | ||||
|  | ||||
| import lombok.*; | ||||
| import io.swagger.annotations.*; | ||||
|  | ||||
| @ApiModel("管理后台 - 租户创建 Request VO") | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| @ToString(callSuper = true) | ||||
| public class TenantCreateReqVO extends TenantBaseVO { | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,31 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages; | ||||
|  | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| import lombok.Data; | ||||
|  | ||||
| import javax.validation.constraints.NotNull; | ||||
| import java.util.Set; | ||||
|  | ||||
| /** | ||||
| * 租户套餐 Base VO,提供给添加、修改、详细的子 VO 使用 | ||||
| * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 | ||||
| */ | ||||
| @Data | ||||
| public class TenantPackageBaseVO { | ||||
|  | ||||
|     @ApiModelProperty(value = "套餐名", required = true, example = "VIP") | ||||
|     @NotNull(message = "套餐名不能为空") | ||||
|     private String name; | ||||
|  | ||||
|     @ApiModelProperty(value = "状态", required = true, example = "1", notes = "参见 CommonStatusEnum 枚举") | ||||
|     @NotNull(message = "状态不能为空") | ||||
|     private Integer status; | ||||
|  | ||||
|     @ApiModelProperty(value = "备注", example = "好") | ||||
|     private String remark; | ||||
|  | ||||
|     @ApiModelProperty(value = "关联的菜单编号", required = true) | ||||
|     @NotNull(message = "关联的菜单编号不能为空") | ||||
|     private Set<Long> menuIds; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,14 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages; | ||||
|  | ||||
| import io.swagger.annotations.ApiModel; | ||||
| import lombok.Data; | ||||
| import lombok.EqualsAndHashCode; | ||||
| import lombok.ToString; | ||||
|  | ||||
| @ApiModel("管理后台 - 租户套餐创建 Request VO") | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| @ToString(callSuper = true) | ||||
| public class TenantPackageCreateReqVO extends TenantPackageBaseVO { | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,38 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.common.pojo.PageParam; | ||||
| import io.swagger.annotations.ApiModel; | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| import lombok.Data; | ||||
| import lombok.EqualsAndHashCode; | ||||
| import lombok.ToString; | ||||
| import org.springframework.format.annotation.DateTimeFormat; | ||||
|  | ||||
| import java.util.Date; | ||||
|  | ||||
| import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; | ||||
|  | ||||
| @ApiModel("管理后台 - 租户套餐分页 Request VO") | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| @ToString(callSuper = true) | ||||
| public class TenantPackagePageReqVO extends PageParam { | ||||
|  | ||||
|     @ApiModelProperty(value = "套餐名", example = "VIP") | ||||
|     private String name; | ||||
|  | ||||
|     @ApiModelProperty(value = "状态", example = "1") | ||||
|     private Integer status; | ||||
|  | ||||
|     @ApiModelProperty(value = "备注", example = "好") | ||||
|     private String remark; | ||||
|  | ||||
|     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | ||||
|     @ApiModelProperty(value = "开始创建时间") | ||||
|     private Date beginCreateTime; | ||||
|  | ||||
|     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | ||||
|     @ApiModelProperty(value = "结束创建时间") | ||||
|     private Date endCreateTime; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,23 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages; | ||||
|  | ||||
| import io.swagger.annotations.ApiModel; | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| import lombok.Data; | ||||
| import lombok.EqualsAndHashCode; | ||||
| import lombok.ToString; | ||||
|  | ||||
| import java.util.Date; | ||||
|  | ||||
| @ApiModel("管理后台 - 租户套餐 Response VO") | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| @ToString(callSuper = true) | ||||
| public class TenantPackageRespVO extends TenantPackageBaseVO { | ||||
|  | ||||
|     @ApiModelProperty(value = "套餐编号", required = true, example = "1024") | ||||
|     private Long id; | ||||
|  | ||||
|     @ApiModelProperty(value = "创建时间", required = true) | ||||
|     private Date createTime; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,21 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages; | ||||
|  | ||||
| import io.swagger.annotations.ApiModel; | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| import lombok.Data; | ||||
|  | ||||
| import javax.validation.constraints.NotNull; | ||||
|  | ||||
| @ApiModel("管理后台 - 租户套餐精简 Response VO") | ||||
| @Data | ||||
| public class TenantPackageSimpleRespVO { | ||||
|  | ||||
|     @ApiModelProperty(value = "套餐编号", required = true, example = "1024") | ||||
|     @NotNull(message = "套餐编号不能为空") | ||||
|     private Long id; | ||||
|  | ||||
|     @ApiModelProperty(value = "套餐名", required = true, example = "VIP") | ||||
|     @NotNull(message = "套餐名不能为空") | ||||
|     private String name; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,18 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages; | ||||
|  | ||||
| import lombok.*; | ||||
| import java.util.*; | ||||
| import io.swagger.annotations.*; | ||||
| import javax.validation.constraints.*; | ||||
|  | ||||
| @ApiModel("管理后台 - 租户套餐更新 Request VO") | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| @ToString(callSuper = true) | ||||
| public class TenantPackageUpdateReqVO extends TenantPackageBaseVO { | ||||
|  | ||||
|     @ApiModelProperty(value = "套餐编号", required = true, example = "1024") | ||||
|     @NotNull(message = "套餐编号不能为空") | ||||
|     private Long id; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,48 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant; | ||||
|  | ||||
| import lombok.*; | ||||
| import io.swagger.annotations.*; | ||||
| import org.hibernate.validator.constraints.URL; | ||||
|  | ||||
| import javax.validation.constraints.*; | ||||
| import java.util.Date; | ||||
|  | ||||
| /** | ||||
| * 租户 Base VO,提供给添加、修改、详细的子 VO 使用 | ||||
| * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 | ||||
| */ | ||||
| @Data | ||||
| public class TenantBaseVO { | ||||
|  | ||||
|     @ApiModelProperty(value = "租户名", required = true, example = "芋道") | ||||
|     @NotNull(message = "租户名不能为空") | ||||
|     private String name; | ||||
|  | ||||
|     @ApiModelProperty(value = "联系人", required = true, example = "芋艿") | ||||
|     @NotNull(message = "联系人不能为空") | ||||
|     private String contactName; | ||||
|  | ||||
|     @ApiModelProperty(value = "联系手机", example = "15601691300") | ||||
|     private String contactMobile; | ||||
|  | ||||
|     @ApiModelProperty(value = "租户状态", required = true, example = "1") | ||||
|     @NotNull(message = "租户状态") | ||||
|     private Integer status; | ||||
|  | ||||
|     @ApiModelProperty(value = "绑定域名", example = "https://www.iocoder.cn") | ||||
|     @URL(message = "绑定域名的地址非 URL 格式") | ||||
|     private String domain; | ||||
|  | ||||
|     @ApiModelProperty(value = "租户套餐编号", required = true, example = "1024") | ||||
|     @NotNull(message = "租户套餐编号不能为空") | ||||
|     private Long packageId; | ||||
|  | ||||
|     @ApiModelProperty(value = "过期时间", required = true) | ||||
|     @NotNull(message = "过期时间不能为空") | ||||
|     private Date expireTime; | ||||
|  | ||||
|     @ApiModelProperty(value = "账号数量", required = true, example = "1024") | ||||
|     @NotNull(message = "账号数量不能为空") | ||||
|     private Integer accountCount; | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,29 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant; | ||||
|  | ||||
| import lombok.*; | ||||
| import io.swagger.annotations.*; | ||||
| 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; | ||||
|  | ||||
| @ApiModel("管理后台 - 租户创建 Request VO") | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| @ToString(callSuper = true) | ||||
| public class TenantCreateReqVO extends TenantBaseVO { | ||||
|  | ||||
|     @ApiModelProperty(value = "用户账号", required = true, example = "yudao") | ||||
|     @NotBlank(message = "用户账号不能为空") | ||||
|     @Pattern(regexp = "^[a-zA-Z0-9]{4,30}$", message = "用户账号由 数字、字母 组成") | ||||
|     @Size(min = 4, max = 30, message = "用户账号长度为 4-30 个字符") | ||||
|     private String username; | ||||
|  | ||||
|     @ApiModelProperty(value = "密码", required = true, example = "123456") | ||||
|     @NotEmpty(message = "密码不能为空") | ||||
|     @Length(min = 4, max = 16, message = "密码长度为 4-16 位") | ||||
|     private String password; | ||||
|  | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant.vo; | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant; | ||||
| 
 | ||||
| import lombok.*; | ||||
| import java.util.*; | ||||
| @@ -1,4 +1,4 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant.vo; | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant; | ||||
| 
 | ||||
| import io.swagger.annotations.ApiModel; | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| @@ -1,4 +1,4 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant.vo; | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant; | ||||
| 
 | ||||
| import cn.iocoder.yudao.framework.common.pojo.PageParam; | ||||
| import io.swagger.annotations.ApiModel; | ||||
| @@ -1,4 +1,4 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant.vo; | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant; | ||||
| 
 | ||||
| import lombok.*; | ||||
| import java.util.*; | ||||
| @@ -1,4 +1,4 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant.vo; | ||||
| package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant; | ||||
| 
 | ||||
| import lombok.*; | ||||
| import io.swagger.annotations.*; | ||||
| @@ -1,24 +1,24 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.user; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | ||||
| import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; | ||||
| import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||||
| import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileRespVO; | ||||
| 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.convert.user.UserConvert; | ||||
| import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; | ||||
| import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO; | ||||
| import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; | ||||
| import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO; | ||||
| import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; | ||||
| 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.permission.RoleService; | ||||
| import cn.iocoder.yudao.module.system.service.user.AdminUserService; | ||||
| import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO; | ||||
| import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; | ||||
| import cn.iocoder.yudao.module.system.service.social.SocialUserService; | ||||
| import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | ||||
| import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; | ||||
| import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||||
| import cn.iocoder.yudao.module.system.service.user.AdminUserService; | ||||
| import io.swagger.annotations.Api; | ||||
| import io.swagger.annotations.ApiOperation; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| @@ -31,9 +31,9 @@ import javax.validation.Valid; | ||||
| import java.io.IOException; | ||||
| import java.util.List; | ||||
|  | ||||
| import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.FILE_IS_EMPTY; | ||||
| import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | ||||
| import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; | ||||
| import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY; | ||||
|  | ||||
| @Api(tags = "管理后台 - 用户个人中心") | ||||
| @RestController | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.user.vo.user; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.common.validation.Mobile; | ||||
| import io.swagger.annotations.ApiModelProperty; | ||||
| import lombok.Data; | ||||
| import org.hibernate.validator.constraints.Length; | ||||
|  | ||||
| import javax.validation.constraints.Email; | ||||
| import javax.validation.constraints.NotBlank; | ||||
| @@ -42,7 +42,7 @@ public class UserBaseVO { | ||||
|     private String email; | ||||
|  | ||||
|     @ApiModelProperty(value = "手机号码", example = "15601691300") | ||||
|     @Length(min = 11, max = 11, message = "手机号长度必须 11 位") | ||||
|     @Mobile | ||||
|     private String mobile; | ||||
|  | ||||
|     @ApiModelProperty(value = "用户性别", example = "1", notes = "参见 SexEnum 枚举类") | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.convert.permission; | ||||
|  | ||||
| import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.*; | ||||
| import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; | ||||
| import cn.iocoder.yudao.module.system.service.permission.bo.RoleCreateReqBO; | ||||
| import org.mapstruct.Mapper; | ||||
| import org.mapstruct.factory.Mappers; | ||||
|  | ||||
| @@ -22,4 +23,6 @@ public interface RoleConvert { | ||||
|  | ||||
|     List<RoleExcelVO> convertList03(List<RoleDO> list); | ||||
|  | ||||
|     RoleDO convert(RoleCreateReqBO bean); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,12 @@ | ||||
| package cn.iocoder.yudao.module.system.convert.tenant; | ||||
|  | ||||
| import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.TenantCreateReqVO; | ||||
| import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.TenantExcelVO; | ||||
| import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.TenantRespVO; | ||||
| import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.TenantUpdateReqVO; | ||||
| import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO; | ||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantCreateReqVO; | ||||
| import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantExcelVO; | ||||
| import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantRespVO; | ||||
| import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantUpdateReqVO; | ||||
| import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserCreateReqVO; | ||||
| import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO; | ||||
| import org.mapstruct.Mapper; | ||||
| import org.mapstruct.factory.Mappers; | ||||
|  | ||||
| @@ -33,4 +34,12 @@ public interface TenantConvert { | ||||
|  | ||||
|     List<TenantExcelVO> convertList02(List<TenantDO> list); | ||||
|  | ||||
|     default UserCreateReqVO convert02(TenantCreateReqVO bean) { | ||||
|         UserCreateReqVO reqVO = new UserCreateReqVO(); | ||||
|         reqVO.setUsername(bean.getUsername()); | ||||
|         reqVO.setPassword(bean.getPassword()); | ||||
|         reqVO.setNickname(bean.getContactName()).setMobile(bean.getContactMobile()); | ||||
|         return reqVO; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,37 @@ | ||||
| package cn.iocoder.yudao.module.system.convert.tenant; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleSimpleRespVO; | ||||
| import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackageCreateReqVO; | ||||
| import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackageRespVO; | ||||
| import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackageSimpleRespVO; | ||||
| import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.packages.TenantPackageUpdateReqVO; | ||||
| import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO; | ||||
| import org.mapstruct.Mapper; | ||||
| import org.mapstruct.factory.Mappers; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 租户套餐 Convert | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Mapper | ||||
| public interface TenantPackageConvert { | ||||
|  | ||||
|     TenantPackageConvert INSTANCE = Mappers.getMapper(TenantPackageConvert.class); | ||||
|  | ||||
|     TenantPackageDO convert(TenantPackageCreateReqVO bean); | ||||
|  | ||||
|     TenantPackageDO convert(TenantPackageUpdateReqVO bean); | ||||
|  | ||||
|     TenantPackageRespVO convert(TenantPackageDO bean); | ||||
|  | ||||
|     List<TenantPackageRespVO> convertList(List<TenantPackageDO> list); | ||||
|  | ||||
|     PageResult<TenantPackageRespVO> convertPage(PageResult<TenantPackageDO> page); | ||||
|  | ||||
|     List<TenantPackageSimpleRespVO> convertList02(List<TenantPackageDO> list); | ||||
|  | ||||
| } | ||||
| @@ -1,8 +1,8 @@ | ||||
| package cn.iocoder.yudao.module.system.dal.dataobject.auth; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | ||||
| import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||||
| import cn.iocoder.yudao.framework.security.core.LoginUser; | ||||
| import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; | ||||
| import com.baomidou.mybatisplus.annotation.IdType; | ||||
| import com.baomidou.mybatisplus.annotation.TableId; | ||||
| import com.baomidou.mybatisplus.annotation.TableName; | ||||
| @@ -25,7 +25,7 @@ import java.util.Date; | ||||
| @Data | ||||
| @Builder | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| public class UserSessionDO extends TenantBaseDO { | ||||
| public class UserSessionDO extends BaseDO { | ||||
|  | ||||
|     /** | ||||
|      * 会话编号, 即 sessionId | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| package cn.iocoder.yudao.module.system.dal.dataobject.dept; | ||||
|  | ||||
| import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; | ||||
| import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||||
| import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; | ||||
| import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||||
| import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; | ||||
| import com.baomidou.mybatisplus.annotation.TableId; | ||||
| import com.baomidou.mybatisplus.annotation.TableName; | ||||
| import lombok.Data; | ||||
| @@ -17,7 +17,7 @@ import lombok.EqualsAndHashCode; | ||||
| @TableName("system_dept") | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| public class DeptDO extends TenantBaseDO { | ||||
| public class DeptDO extends BaseDO { | ||||
|  | ||||
|     /** | ||||
|      * 部门ID | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cn.iocoder.yudao.module.system.dal.dataobject.dept; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||||
| import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; | ||||
| import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||||
| import com.baomidou.mybatisplus.annotation.TableId; | ||||
| import com.baomidou.mybatisplus.annotation.TableName; | ||||
| import lombok.Data; | ||||
| @@ -15,7 +15,7 @@ import lombok.EqualsAndHashCode; | ||||
| @TableName("system_post") | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| public class PostDO extends TenantBaseDO { | ||||
| public class PostDO extends BaseDO { | ||||
|  | ||||
|     /** | ||||
|      * 岗位序号 | ||||
|   | ||||
| @@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.system.dal.dataobject.logger; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | ||||
| import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||||
| import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||||
| import cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum; | ||||
| import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; | ||||
| import com.baomidou.mybatisplus.annotation.TableField; | ||||
| import com.baomidou.mybatisplus.annotation.TableId; | ||||
| import com.baomidou.mybatisplus.annotation.TableName; | ||||
| @@ -22,7 +22,7 @@ import java.util.Map; | ||||
| @TableName(value = "system_operate_log", autoResultMap = true) | ||||
| @Data | ||||
| @EqualsAndHashCode(callSuper = true) | ||||
| public class OperateLogDO extends TenantBaseDO { | ||||
| public class OperateLogDO extends BaseDO { | ||||
|  | ||||
|     /** | ||||
|      * {@link #javaMethodArgs} 的最大长度 | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 芋道源码
					芋道源码