清除多租户功能
This commit is contained in:
@@ -129,11 +129,6 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.njcn</groupId>
|
||||
<artifactId>msgpush-spring-boot-starter-biz-tenant</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.njcn</groupId>
|
||||
<artifactId>msgpush-spring-boot-starter-biz-ip</artifactId>
|
||||
|
||||
@@ -21,9 +21,6 @@ public class OAuth2AccessTokenCheckRespDTO implements Serializable {
|
||||
@Schema(description = "用户信息", example = "{\"nickname\": \"灿能\"}")
|
||||
private Map<String, String> userInfo;
|
||||
|
||||
@Schema(description = "租户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long tenantId;
|
||||
|
||||
@Schema(description = "授权范围的数组", example = "user_info")
|
||||
private List<String> scopes;
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.njcn.msgpush.framework.common.biz.system.tenant;
|
||||
|
||||
import com.njcn.msgpush.framework.common.enums.RpcConstants;
|
||||
import com.njcn.msgpush.framework.common.pojo.CommonResult;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@FeignClient(name = RpcConstants.SYSTEM_NAME) // TODO 芋艿:fallbackFactory =
|
||||
@Tag(name = "RPC 服务 - 多租户")
|
||||
public interface TenantCommonApi {
|
||||
|
||||
String PREFIX = RpcConstants.SYSTEM_PREFIX + "/tenant";
|
||||
|
||||
@GetMapping(PREFIX + "/id-list")
|
||||
@Operation(summary = "获得所有租户编号")
|
||||
CommonResult<List<Long>> getTenantIdList();
|
||||
|
||||
@GetMapping(PREFIX + "/valid")
|
||||
@Operation(summary = "校验租户是否合法")
|
||||
@Parameter(name = "id", description = "租户编号", required = true, example = "1024")
|
||||
CommonResult<Boolean> validTenant(@RequestParam("id") Long id);
|
||||
|
||||
}
|
||||
@@ -12,8 +12,7 @@ import lombok.Getter;
|
||||
@AllArgsConstructor
|
||||
public enum DocumentEnum {
|
||||
|
||||
REDIS_INSTALL("https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues/I4VCSJ", "Redis 安装文档"),
|
||||
TENANT("https://doc.iocoder.cn", "SaaS 多租户文档");
|
||||
REDIS_INSTALL("https://gitee.com/zhijiantianya/ruoyi-vue-pro/issues/I4VCSJ", "Redis 安装文档");
|
||||
|
||||
private final String url;
|
||||
private final String memo;
|
||||
|
||||
@@ -21,16 +21,12 @@ public interface WebFilterOrderEnum {
|
||||
|
||||
// OrderedRequestContextFilter 默认为 -105,用于国际化上下文等等
|
||||
|
||||
int TENANT_CONTEXT_FILTER = - 104; // 需要保证在 ApiAccessLogFilter 前面
|
||||
|
||||
int API_ACCESS_LOG_FILTER = -103; // 需要保证在 RequestBodyCacheFilter 后面
|
||||
|
||||
int XSS_FILTER = -102; // 需要保证在 RequestBodyCacheFilter 后面
|
||||
|
||||
// Spring Security Filter 默认为 -100,可见 org.springframework.boot.autoconfigure.security.SecurityProperties 配置属性类
|
||||
|
||||
int TENANT_SECURITY_FILTER = -99; // 需要保证在 Spring Security 过滤器后面
|
||||
|
||||
int FLOWABLE_FILTER = -98; // 需要保证在 Spring Security 过滤后面
|
||||
|
||||
int DEMO_FILTER = Integer.MAX_VALUE;
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>msgpush-framework</artifactId>
|
||||
<groupId>com.njcn</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>msgpush-spring-boot-starter-biz-tenant</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
<description>多租户</description>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.njcn</groupId>
|
||||
<artifactId>msgpush-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>com.njcn</groupId>
|
||||
<artifactId>msgpush-spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- DB 相关 -->
|
||||
<dependency>
|
||||
<groupId>com.njcn</groupId>
|
||||
<artifactId>msgpush-spring-boot-starter-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.njcn</groupId>
|
||||
<artifactId>msgpush-spring-boot-starter-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- RPC 远程调用相关 -->
|
||||
<dependency>
|
||||
<groupId>com.njcn</groupId>
|
||||
<artifactId>msgpush-spring-boot-starter-rpc</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- 消息队列相关 -->
|
||||
<dependency>
|
||||
<groupId>com.njcn</groupId>
|
||||
<artifactId>msgpush-spring-boot-starter-mq</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.kafka</groupId>
|
||||
<artifactId>spring-kafka</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.amqp</groupId>
|
||||
<artifactId>spring-rabbit</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.rocketmq</groupId>
|
||||
<artifactId>rocketmq-spring-boot-starter</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 测试相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类相关 -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -1,212 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.config;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.njcn.msgpush.framework.common.biz.system.tenant.TenantCommonApi;
|
||||
import com.njcn.msgpush.framework.common.enums.WebFilterOrderEnum;
|
||||
import com.njcn.msgpush.framework.mybatis.core.util.MyBatisUtils;
|
||||
import com.njcn.msgpush.framework.redis.config.MsgpushCacheProperties;
|
||||
import com.njcn.msgpush.framework.security.core.service.SecurityFrameworkService;
|
||||
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
|
||||
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnoreAspect;
|
||||
import com.njcn.msgpush.framework.tenant.core.db.TenantDatabaseInterceptor;
|
||||
import com.njcn.msgpush.framework.tenant.core.mq.rabbitmq.TenantRabbitMQInitializer;
|
||||
import com.njcn.msgpush.framework.tenant.core.mq.redis.TenantRedisMessageInterceptor;
|
||||
import com.njcn.msgpush.framework.tenant.core.mq.rocketmq.TenantRocketMQInitializer;
|
||||
import com.njcn.msgpush.framework.tenant.core.redis.TenantRedisCacheManager;
|
||||
import com.njcn.msgpush.framework.tenant.core.security.TenantSecurityWebFilter;
|
||||
import com.njcn.msgpush.framework.tenant.core.service.TenantFrameworkService;
|
||||
import com.njcn.msgpush.framework.tenant.core.service.TenantFrameworkServiceImpl;
|
||||
import com.njcn.msgpush.framework.tenant.core.web.TenantContextWebFilter;
|
||||
import com.njcn.msgpush.framework.tenant.core.web.TenantVisitContextInterceptor;
|
||||
import com.njcn.msgpush.framework.web.config.WebProperties;
|
||||
import com.njcn.msgpush.framework.web.core.handler.GlobalExceptionHandler;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
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.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.data.redis.cache.BatchStrategies;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.njcn.msgpush.framework.common.util.collection.CollectionUtils.convertList;
|
||||
|
||||
@AutoConfiguration
|
||||
@ConditionalOnProperty(prefix = "msgpush.tenant", value = "enable", matchIfMissing = true) // 允许使用 msgpush.tenant.enable=false 禁用多租户
|
||||
@EnableConfigurationProperties(TenantProperties.class)
|
||||
public class MsgpushTenantAutoConfiguration {
|
||||
|
||||
@Resource
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Bean
|
||||
public TenantFrameworkService tenantFrameworkService(TenantCommonApi tenantApi) {
|
||||
try {
|
||||
TenantCommonApi tenantApiImpl = SpringUtil.getBean("tenantApiImpl", TenantCommonApi.class);
|
||||
if (tenantApiImpl != null) {
|
||||
tenantApi = tenantApiImpl;
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
return new TenantFrameworkServiceImpl(tenantApi);
|
||||
}
|
||||
|
||||
// ========== 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;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TenantVisitContextInterceptor tenantVisitContextInterceptor(TenantProperties tenantProperties,
|
||||
SecurityFrameworkService securityFrameworkService) {
|
||||
return new TenantVisitContextInterceptor(tenantProperties, securityFrameworkService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebMvcConfigurer tenantWebMvcConfigurer(TenantProperties tenantProperties,
|
||||
TenantVisitContextInterceptor tenantVisitContextInterceptor) {
|
||||
return new WebMvcConfigurer() {
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(tenantVisitContextInterceptor)
|
||||
.excludePathPatterns(tenantProperties.getIgnoreVisitUrls().toArray(new String[0]));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ========== Security ==========
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean<TenantSecurityWebFilter> tenantSecurityWebFilter(TenantProperties tenantProperties,
|
||||
WebProperties webProperties,
|
||||
GlobalExceptionHandler globalExceptionHandler,
|
||||
TenantFrameworkService tenantFrameworkService) {
|
||||
FilterRegistrationBean<TenantSecurityWebFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(new TenantSecurityWebFilter(webProperties, tenantProperties, getTenantIgnoreUrls(),
|
||||
globalExceptionHandler, tenantFrameworkService));
|
||||
registrationBean.setOrder(WebFilterOrderEnum.TENANT_SECURITY_FILTER);
|
||||
return registrationBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果 Controller 接口上,有 {@link TenantIgnore} 注解,则添加到忽略租户的 URL 集合中
|
||||
*
|
||||
* @return 忽略租户的 URL 集合
|
||||
*/
|
||||
private Set<String> getTenantIgnoreUrls() {
|
||||
Set<String> ignoreUrls = new HashSet<>();
|
||||
// 获得接口对应的 HandlerMethod 集合
|
||||
RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping)
|
||||
applicationContext.getBean("requestMappingHandlerMapping");
|
||||
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
|
||||
// 获得有 @TenantIgnore 注解的接口
|
||||
for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethodMap.entrySet()) {
|
||||
HandlerMethod handlerMethod = entry.getValue();
|
||||
if (!handlerMethod.hasMethodAnnotation(TenantIgnore.class) // 方法级
|
||||
&& !handlerMethod.getBeanType().isAnnotationPresent(TenantIgnore.class)) { // 接口级
|
||||
continue;
|
||||
}
|
||||
// 添加到忽略的 URL 中
|
||||
if (entry.getKey().getPatternsCondition() != null) {
|
||||
ignoreUrls.addAll(entry.getKey().getPatternsCondition().getPatterns());
|
||||
}
|
||||
if (entry.getKey().getPathPatternsCondition() != null) {
|
||||
ignoreUrls.addAll(
|
||||
convertList(entry.getKey().getPathPatternsCondition().getPatterns(), PathPattern::getPatternString));
|
||||
}
|
||||
}
|
||||
return ignoreUrls;
|
||||
}
|
||||
|
||||
// ========== MQ ==========
|
||||
|
||||
/**
|
||||
* 多租户 Redis 消息队列的配置类
|
||||
*
|
||||
* 为什么要单独一个配置类呢?如果直接把 TenantRedisMessageInterceptor Bean 的初始化放外面,会报 RedisMessageInterceptor 类不存在的错误
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(name = "com.njcn.msgpush.framework.mq.redis.core.RedisMQTemplate")
|
||||
public static class TenantRedisMQAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public TenantRedisMessageInterceptor tenantRedisMessageInterceptor() {
|
||||
return new TenantRedisMessageInterceptor();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = "org.springframework.amqp.rabbit.core.RabbitTemplate")
|
||||
public TenantRabbitMQInitializer tenantRabbitMQInitializer() {
|
||||
return new TenantRabbitMQInitializer();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = "org.apache.rocketmq.spring.core.RocketMQTemplate")
|
||||
public TenantRocketMQInitializer tenantRocketMQInitializer() {
|
||||
return new TenantRocketMQInitializer();
|
||||
}
|
||||
|
||||
// ========== Redis ==========
|
||||
|
||||
@Bean
|
||||
@Primary // 引入租户时,tenantRedisCacheManager 为主 Bean
|
||||
public RedisCacheManager tenantRedisCacheManager(RedisTemplate<String, Object> redisTemplate,
|
||||
RedisCacheConfiguration redisCacheConfiguration,
|
||||
MsgpushCacheProperties msgpushCacheProperties,
|
||||
TenantProperties tenantProperties) {
|
||||
// 创建 RedisCacheWriter 对象
|
||||
RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory());
|
||||
RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory,
|
||||
BatchStrategies.scan(msgpushCacheProperties.getRedisScanBatchSize()));
|
||||
// 创建 TenantRedisCacheManager 对象
|
||||
return new TenantRedisCacheManager(cacheWriter, redisCacheConfiguration, tenantProperties.getIgnoreCaches());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.config;
|
||||
|
||||
import com.njcn.msgpush.framework.tenant.core.rpc.TenantRequestInterceptor;
|
||||
import com.njcn.msgpush.framework.common.biz.system.tenant.TenantCommonApi;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@AutoConfiguration
|
||||
@ConditionalOnProperty(prefix = "msgpush.tenant", value = "enable", matchIfMissing = true) // 允许使用 msgpush.tenant.enable=false 禁用多租户
|
||||
@EnableFeignClients(clients = TenantCommonApi.class) // 主要是引入相关的 API 服务
|
||||
public class MsgpushTenantRpcAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public TenantRequestInterceptor tenantRequestInterceptor() {
|
||||
return new TenantRequestInterceptor();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 多租户配置
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "msgpush.tenant")
|
||||
@Data
|
||||
public class TenantProperties {
|
||||
|
||||
/**
|
||||
* 租户是否开启
|
||||
*/
|
||||
private static final Boolean ENABLE_DEFAULT = true;
|
||||
|
||||
/**
|
||||
* 是否开启
|
||||
*/
|
||||
private Boolean enable = ENABLE_DEFAULT;
|
||||
|
||||
/**
|
||||
* 需要忽略多租户的请求
|
||||
*
|
||||
* 默认情况下,每个请求需要带上 tenant-id 的请求头。但是,部分请求是无需带上的,例如说短信回调、支付回调等 Open API!
|
||||
*/
|
||||
private Set<String> ignoreUrls = new HashSet<>();
|
||||
|
||||
/**
|
||||
* 需要忽略跨(切换)租户访问的请求
|
||||
*
|
||||
* 原因是:某些接口,访问的是个人信息,在跨租户是获取不到的!
|
||||
*/
|
||||
private Set<String> ignoreVisitUrls = Collections.emptySet();
|
||||
|
||||
/**
|
||||
* 需要忽略多租户的表
|
||||
*
|
||||
* 即默认所有表都开启多租户的功能,所以记得添加对应的 tenant_id 字段哟
|
||||
*/
|
||||
private Set<String> ignoreTables = Collections.emptySet();
|
||||
|
||||
/**
|
||||
* 需要忽略多租户的 Spring Cache 缓存
|
||||
*
|
||||
* 即默认所有缓存都开启多租户的功能,所以记得添加对应的 tenant_id 字段哟
|
||||
*/
|
||||
private Set<String> ignoreCaches = Collections.emptySet();
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.aop;
|
||||
|
||||
import com.njcn.msgpush.framework.tenant.config.TenantProperties;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 忽略租户,标记指定方法不进行租户的自动过滤
|
||||
*
|
||||
* 注意,只有 DB 的场景会过滤,其它场景暂时不过滤:
|
||||
* 1、Redis 场景:因为是基于 Key 实现多租户的能力,所以忽略没有意义,不像 DB 是一个 column 实现的
|
||||
* 2、MQ 场景:有点难以抉择,目前可以通过 Consumer 手动在消费的方法上,添加 @TenantIgnore 进行忽略
|
||||
*
|
||||
* 特殊:
|
||||
* 1、如果添加到 Controller 类上,则该 URL 自动添加到 {@link TenantProperties#getIgnoreUrls()} 中
|
||||
* 2、如果添加到 DO 实体类上,则它对应的表名“相当于”自动添加到 {@link TenantProperties#getIgnoreTables()} 中
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface TenantIgnore {
|
||||
|
||||
/**
|
||||
* 是否开启忽略租户,默认为 true 开启
|
||||
*
|
||||
* 支持 Spring EL 表达式,如果返回 true 则满足条件,进行租户的忽略
|
||||
*/
|
||||
String enable() default "true";
|
||||
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.aop;
|
||||
|
||||
import com.njcn.msgpush.framework.common.util.spring.SpringExpressionUtils;
|
||||
import com.njcn.msgpush.framework.tenant.core.context.TenantContextHolder;
|
||||
import com.njcn.msgpush.framework.tenant.core.util.TenantUtils;
|
||||
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} 注解实现,用于一些全局的逻辑。
|
||||
* 例如说,一个定时任务,读取所有数据,进行处理。
|
||||
* 又例如说,读取所有数据,进行缓存。
|
||||
*
|
||||
* 整体逻辑的实现,和 {@link TenantUtils#executeIgnore(Runnable)} 需要保持一致
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Aspect
|
||||
@Slf4j
|
||||
public class TenantIgnoreAspect {
|
||||
|
||||
@Around("@annotation(tenantIgnore)")
|
||||
public Object around(ProceedingJoinPoint joinPoint, TenantIgnore tenantIgnore) throws Throwable {
|
||||
Boolean oldIgnore = TenantContextHolder.isIgnore();
|
||||
try {
|
||||
// 计算条件,满足的情况下,才进行忽略
|
||||
Object enable = SpringExpressionUtils.parseExpression(tenantIgnore.enable());
|
||||
if (Boolean.TRUE.equals(enable)) {
|
||||
TenantContextHolder.setIgnore(true);
|
||||
}
|
||||
|
||||
// 执行逻辑
|
||||
return joinPoint.proceed();
|
||||
} finally {
|
||||
TenantContextHolder.setIgnore(oldIgnore);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.context;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.njcn.msgpush.framework.common.enums.DocumentEnum;
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
|
||||
/**
|
||||
* 多租户上下文 Holder
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
public class TenantContextHolder {
|
||||
|
||||
/**
|
||||
* 当前租户编号
|
||||
*/
|
||||
private static final ThreadLocal<Long> TENANT_ID = new TransmittableThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 是否忽略租户
|
||||
*/
|
||||
private static final ThreadLocal<Boolean> IGNORE = new TransmittableThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 获得租户编号
|
||||
*
|
||||
* @return 租户编号
|
||||
*/
|
||||
public static Long getTenantId() {
|
||||
return TENANT_ID.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得租户编号。如果不存在,则抛出 NullPointerException 异常
|
||||
*
|
||||
* @return 租户编号
|
||||
*/
|
||||
public static Long getRequiredTenantId() {
|
||||
Long tenantId = getTenantId();
|
||||
if (tenantId == null) {
|
||||
throw new NullPointerException("TenantContextHolder 不存在租户编号!可参考文档:"
|
||||
+ DocumentEnum.TENANT.getUrl());
|
||||
}
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.db;
|
||||
|
||||
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 拓展多租户的 BaseDO 基类
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public abstract class TenantBaseDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 多租户编号
|
||||
*/
|
||||
private Long tenantId;
|
||||
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.db;
|
||||
|
||||
import com.njcn.msgpush.framework.tenant.config.TenantProperties;
|
||||
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
|
||||
import com.njcn.msgpush.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 com.baomidou.mybatisplus.extension.toolkit.SqlParserUtils;
|
||||
import net.sf.jsqlparser.expression.Expression;
|
||||
import net.sf.jsqlparser.expression.LongValue;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 基于 MyBatis Plus 多租户的功能,实现 DB 层面的多租户的功能
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
public class TenantDatabaseInterceptor implements TenantLineHandler {
|
||||
|
||||
/**
|
||||
* 忽略的表
|
||||
*
|
||||
* KEY:表名
|
||||
* VALUE:是否忽略
|
||||
*/
|
||||
private final Map<String, Boolean> ignoreTables = new HashMap<>();
|
||||
|
||||
public TenantDatabaseInterceptor(TenantProperties properties) {
|
||||
// 不同 DB 下,大小写的习惯不同,所以需要都添加进去
|
||||
properties.getIgnoreTables().forEach(table -> {
|
||||
addIgnoreTable(table, true);
|
||||
});
|
||||
// 在 OracleKeyGenerator 中,生成主键时,会查询这个表,查询这个表后,会自动拼接 TENANT_ID 导致报错
|
||||
addIgnoreTable("DUAL", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression getTenantId() {
|
||||
return new LongValue(TenantContextHolder.getRequiredTenantId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ignoreTable(String tableName) {
|
||||
// 情况一,全局忽略多租户
|
||||
if (TenantContextHolder.isIgnore()) {
|
||||
return true;
|
||||
}
|
||||
// 情况二,忽略多租户的表
|
||||
tableName = SqlParserUtils.removeWrapperSymbol(tableName);
|
||||
Boolean ignore = ignoreTables.get(tableName.toLowerCase());
|
||||
if (ignore == null) {
|
||||
ignore = computeIgnoreTable(tableName);
|
||||
synchronized (ignoreTables) {
|
||||
addIgnoreTable(tableName, ignore);
|
||||
}
|
||||
}
|
||||
return ignore;
|
||||
}
|
||||
|
||||
private void addIgnoreTable(String tableName, boolean ignore) {
|
||||
ignoreTables.put(tableName.toLowerCase(), ignore);
|
||||
ignoreTables.put(tableName.toUpperCase(), ignore);
|
||||
}
|
||||
|
||||
private boolean computeIgnoreTable(String tableName) {
|
||||
// 找不到的表,说明不是 msgpush 项目里的,不进行拦截(忽略租户)
|
||||
TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName);
|
||||
if (tableInfo == null) {
|
||||
return true;
|
||||
}
|
||||
// 如果继承了 TenantBaseDO 基类,显然不忽略租户
|
||||
if (TenantBaseDO.class.isAssignableFrom(tableInfo.getEntityType())) {
|
||||
return false;
|
||||
}
|
||||
// 如果添加了 @TenantIgnore 注解,则忽略租户
|
||||
TenantIgnore tenantIgnore = tableInfo.getEntityType().getAnnotation(TenantIgnore.class);
|
||||
return tenantIgnore != null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.mq.kafka;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.env.EnvironmentPostProcessor;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
|
||||
/**
|
||||
* 多租户的 Kafka 的 {@link EnvironmentPostProcessor} 实现类
|
||||
*
|
||||
* Kafka Producer 发送消息时,增加 {@link TenantKafkaProducerInterceptor} 拦截器
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Slf4j
|
||||
public class TenantKafkaEnvironmentPostProcessor implements EnvironmentPostProcessor {
|
||||
|
||||
private static final String PROPERTY_KEY_INTERCEPTOR_CLASSES = "spring.kafka.producer.properties.interceptor.classes";
|
||||
|
||||
@Override
|
||||
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
||||
// 添加 TenantKafkaProducerInterceptor 拦截器
|
||||
try {
|
||||
String value = environment.getProperty(PROPERTY_KEY_INTERCEPTOR_CLASSES);
|
||||
if (StrUtil.isEmpty(value)) {
|
||||
value = TenantKafkaProducerInterceptor.class.getName();
|
||||
} else {
|
||||
value += "," + TenantKafkaProducerInterceptor.class.getName();
|
||||
}
|
||||
environment.getSystemProperties().put(PROPERTY_KEY_INTERCEPTOR_CLASSES, value);
|
||||
} catch (NoClassDefFoundError ignore) {
|
||||
// 如果触发 NoClassDefFoundError 异常,说明 TenantKafkaProducerInterceptor 类不存在,即没引入 kafka-spring 依赖
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.mq.kafka;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import com.njcn.msgpush.framework.tenant.core.context.TenantContextHolder;
|
||||
import org.apache.kafka.clients.producer.ProducerInterceptor;
|
||||
import org.apache.kafka.clients.producer.ProducerRecord;
|
||||
import org.apache.kafka.clients.producer.RecordMetadata;
|
||||
import org.apache.kafka.common.header.Headers;
|
||||
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static com.njcn.msgpush.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
|
||||
|
||||
/**
|
||||
* Kafka 消息队列的多租户 {@link ProducerInterceptor} 实现类
|
||||
*
|
||||
* 1. Producer 发送消息时,将 {@link TenantContextHolder} 租户编号,添加到消息的 Header 中
|
||||
* 2. Consumer 消费消息时,将消息的 Header 的租户编号,添加到 {@link TenantContextHolder} 中,通过 {@link InvocableHandlerMethod} 实现
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
public class TenantKafkaProducerInterceptor implements ProducerInterceptor<Object, Object> {
|
||||
|
||||
@Override
|
||||
public ProducerRecord<Object, Object> onSend(ProducerRecord<Object, Object> record) {
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
if (tenantId != null) {
|
||||
Headers headers = (Headers) ReflectUtil.getFieldValue(record, "headers"); // private 属性,没有 get 方法,智能反射
|
||||
headers.add(HEADER_TENANT_ID, tenantId.toString().getBytes());
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(Map<String, ?> configs) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.mq.rabbitmq;
|
||||
|
||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
|
||||
/**
|
||||
* 多租户的 RabbitMQ 初始化器
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
public class TenantRabbitMQInitializer implements BeanPostProcessor {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("PatternVariableCanBeUsed")
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof RabbitTemplate) {
|
||||
RabbitTemplate rabbitTemplate = (RabbitTemplate) bean;
|
||||
rabbitTemplate.addBeforePublishPostProcessors(new TenantRabbitMQMessagePostProcessor());
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.mq.rabbitmq;
|
||||
|
||||
import com.njcn.msgpush.framework.tenant.core.context.TenantContextHolder;
|
||||
import org.apache.kafka.clients.producer.ProducerInterceptor;
|
||||
import org.springframework.amqp.AmqpException;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.core.MessagePostProcessor;
|
||||
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
|
||||
|
||||
import static com.njcn.msgpush.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
|
||||
|
||||
/**
|
||||
* RabbitMQ 消息队列的多租户 {@link ProducerInterceptor} 实现类
|
||||
*
|
||||
* 1. Producer 发送消息时,将 {@link TenantContextHolder} 租户编号,添加到消息的 Header 中
|
||||
* 2. Consumer 消费消息时,将消息的 Header 的租户编号,添加到 {@link TenantContextHolder} 中,通过 {@link InvocableHandlerMethod} 实现
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
public class TenantRabbitMQMessagePostProcessor implements MessagePostProcessor {
|
||||
|
||||
@Override
|
||||
public Message postProcessMessage(Message message) throws AmqpException {
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
if (tenantId != null) {
|
||||
message.getMessageProperties().getHeaders().put(HEADER_TENANT_ID, tenantId);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.mq.redis;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.njcn.msgpush.framework.mq.redis.core.interceptor.RedisMessageInterceptor;
|
||||
import com.njcn.msgpush.framework.mq.redis.core.message.AbstractRedisMessage;
|
||||
import com.njcn.msgpush.framework.tenant.core.context.TenantContextHolder;
|
||||
|
||||
import static com.njcn.msgpush.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
|
||||
|
||||
/**
|
||||
* 多租户 {@link AbstractRedisMessage} 拦截器
|
||||
*
|
||||
* 1. Producer 发送消息时,将 {@link TenantContextHolder} 租户编号,添加到消息的 Header 中
|
||||
* 2. Consumer 消费消息时,将消息的 Header 的租户编号,添加到 {@link TenantContextHolder} 中
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
public class TenantRedisMessageInterceptor implements RedisMessageInterceptor {
|
||||
|
||||
@Override
|
||||
public void sendMessageBefore(AbstractRedisMessage message) {
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
if (tenantId != null) {
|
||||
message.addHeader(HEADER_TENANT_ID, tenantId.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consumeMessageBefore(AbstractRedisMessage message) {
|
||||
String tenantIdStr = message.getHeader(HEADER_TENANT_ID);
|
||||
if (StrUtil.isNotEmpty(tenantIdStr)) {
|
||||
TenantContextHolder.setTenantId(Long.valueOf(tenantIdStr));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consumeMessageAfter(AbstractRedisMessage message) {
|
||||
// 注意,Consumer 是一个逻辑的入口,所以不考虑原本上下文就存在租户编号的情况
|
||||
TenantContextHolder.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.mq.rocketmq;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.njcn.msgpush.framework.tenant.core.context.TenantContextHolder;
|
||||
import org.apache.rocketmq.client.hook.ConsumeMessageContext;
|
||||
import org.apache.rocketmq.client.hook.ConsumeMessageHook;
|
||||
import org.apache.rocketmq.common.message.MessageExt;
|
||||
import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.njcn.msgpush.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
|
||||
|
||||
/**
|
||||
* RocketMQ 消息队列的多租户 {@link ConsumeMessageHook} 实现类
|
||||
*
|
||||
* Consumer 消费消息时,将消息的 Header 的租户编号,添加到 {@link TenantContextHolder} 中,通过 {@link InvocableHandlerMethod} 实现
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
public class TenantRocketMQConsumeMessageHook implements ConsumeMessageHook {
|
||||
|
||||
@Override
|
||||
public String hookName() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consumeMessageBefore(ConsumeMessageContext context) {
|
||||
// 校验,消息必须是单条,不然设置租户可能不正确
|
||||
List<MessageExt> messages = context.getMsgList();
|
||||
Assert.isTrue(messages.size() == 1, "消息条数({})不正确", messages.size());
|
||||
// 设置租户编号
|
||||
String tenantId = messages.get(0).getUserProperty(HEADER_TENANT_ID);
|
||||
if (StrUtil.isNotEmpty(tenantId)) {
|
||||
TenantContextHolder.setTenantId(Long.parseLong(tenantId));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void consumeMessageAfter(ConsumeMessageContext context) {
|
||||
TenantContextHolder.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.mq.rocketmq;
|
||||
|
||||
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
|
||||
import org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl;
|
||||
import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl;
|
||||
import org.apache.rocketmq.client.producer.DefaultMQProducer;
|
||||
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||
import org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
|
||||
/**
|
||||
* 多租户的 RocketMQ 初始化器
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
public class TenantRocketMQInitializer implements BeanPostProcessor {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("PatternVariableCanBeUsed")
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof DefaultRocketMQListenerContainer) {
|
||||
DefaultRocketMQListenerContainer container = (DefaultRocketMQListenerContainer) bean;
|
||||
initTenantConsumer(container.getConsumer());
|
||||
} else if (bean instanceof RocketMQTemplate) {
|
||||
RocketMQTemplate template = (RocketMQTemplate) bean;
|
||||
initTenantProducer(template.getProducer());
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
private void initTenantProducer(DefaultMQProducer producer) {
|
||||
if (producer == null) {
|
||||
return;
|
||||
}
|
||||
DefaultMQProducerImpl producerImpl = producer.getDefaultMQProducerImpl();
|
||||
if (producerImpl == null) {
|
||||
return;
|
||||
}
|
||||
producerImpl.registerSendMessageHook(new TenantRocketMQSendMessageHook());
|
||||
}
|
||||
|
||||
private void initTenantConsumer(DefaultMQPushConsumer consumer) {
|
||||
if (consumer == null) {
|
||||
return;
|
||||
}
|
||||
DefaultMQPushConsumerImpl consumerImpl = consumer.getDefaultMQPushConsumerImpl();
|
||||
if (consumerImpl == null) {
|
||||
return;
|
||||
}
|
||||
consumerImpl.registerConsumeMessageHook(new TenantRocketMQConsumeMessageHook());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.mq.rocketmq;
|
||||
|
||||
import com.njcn.msgpush.framework.tenant.core.context.TenantContextHolder;
|
||||
import org.apache.rocketmq.client.hook.SendMessageContext;
|
||||
import org.apache.rocketmq.client.hook.SendMessageHook;
|
||||
|
||||
import static com.njcn.msgpush.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
|
||||
|
||||
/**
|
||||
* RocketMQ 消息队列的多租户 {@link SendMessageHook} 实现类
|
||||
*
|
||||
* Producer 发送消息时,将 {@link TenantContextHolder} 租户编号,添加到消息的 Header 中
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
public class TenantRocketMQSendMessageHook implements SendMessageHook {
|
||||
|
||||
@Override
|
||||
public String hookName() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessageBefore(SendMessageContext sendMessageContext) {
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
if (tenantId == null) {
|
||||
return;
|
||||
}
|
||||
sendMessageContext.getMessage().putUserProperty(HEADER_TENANT_ID, tenantId.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessageAfter(SendMessageContext sendMessageContext) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.redis;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.njcn.msgpush.framework.redis.core.TimeoutRedisCacheManager;
|
||||
import com.njcn.msgpush.framework.tenant.core.context.TenantContextHolder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 多租户的 {@link RedisCacheManager} 实现类
|
||||
*
|
||||
* 操作指定 name 的 {@link Cache} 时,自动拼接租户后缀,格式为 name + ":" + tenantId + 后缀
|
||||
*
|
||||
* @author airhead
|
||||
*/
|
||||
@Slf4j
|
||||
public class TenantRedisCacheManager extends TimeoutRedisCacheManager {
|
||||
|
||||
private static final String SPLIT = "#";
|
||||
|
||||
private final Set<String> ignoreCaches;
|
||||
|
||||
public TenantRedisCacheManager(RedisCacheWriter cacheWriter,
|
||||
RedisCacheConfiguration defaultCacheConfiguration,
|
||||
Set<String> ignoreCaches) {
|
||||
super(cacheWriter, defaultCacheConfiguration);
|
||||
this.ignoreCaches = ignoreCaches;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cache getCache(String name) {
|
||||
String[] names = StrUtil.splitToArray(name, SPLIT);
|
||||
// 如果开启多租户,则 name 拼接租户后缀
|
||||
if (!TenantContextHolder.isIgnore()
|
||||
&& TenantContextHolder.getTenantId() != null
|
||||
&& !CollUtil.contains(ignoreCaches, names[0])) {
|
||||
name = name + ":" + TenantContextHolder.getTenantId();
|
||||
}
|
||||
|
||||
// 继续基于父方法
|
||||
return super.getCache(name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.rpc;
|
||||
|
||||
import com.njcn.msgpush.framework.tenant.core.context.TenantContextHolder;
|
||||
import com.njcn.msgpush.framework.web.core.util.WebFrameworkUtils;
|
||||
import feign.RequestInterceptor;
|
||||
import feign.RequestTemplate;
|
||||
|
||||
import static com.njcn.msgpush.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
|
||||
|
||||
/**
|
||||
* Tenant 的 RequestInterceptor 实现类:Feign 请求时,将 {@link TenantContextHolder} 设置到 header 中,继续透传给被调用的服务
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
public class TenantRequestInterceptor implements RequestInterceptor {
|
||||
|
||||
@Override
|
||||
public void apply(RequestTemplate requestTemplate) {
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
if (tenantId != null) {
|
||||
requestTemplate.header(HEADER_TENANT_ID, String.valueOf(tenantId));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.security;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.njcn.msgpush.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import com.njcn.msgpush.framework.common.pojo.CommonResult;
|
||||
import com.njcn.msgpush.framework.common.util.servlet.ServletUtils;
|
||||
import com.njcn.msgpush.framework.security.core.LoginUser;
|
||||
import com.njcn.msgpush.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.njcn.msgpush.framework.tenant.config.TenantProperties;
|
||||
import com.njcn.msgpush.framework.tenant.core.context.TenantContextHolder;
|
||||
import com.njcn.msgpush.framework.tenant.core.service.TenantFrameworkService;
|
||||
import com.njcn.msgpush.framework.web.config.WebProperties;
|
||||
import com.njcn.msgpush.framework.web.core.filter.ApiRequestFilter;
|
||||
import com.njcn.msgpush.framework.web.core.handler.GlobalExceptionHandler;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 多租户 Security Web 过滤器
|
||||
* 1. 如果是登陆的用户,校验是否有权限访问该租户,避免越权问题。
|
||||
* 2. 如果请求未带租户的编号,检查是否是忽略的 URL,否则也不允许访问。
|
||||
* 3. 校验租户是合法,例如说被禁用、到期
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Slf4j
|
||||
public class TenantSecurityWebFilter extends ApiRequestFilter {
|
||||
|
||||
private final TenantProperties tenantProperties;
|
||||
|
||||
/**
|
||||
* 允许忽略租户的 URL 列表
|
||||
*
|
||||
* 目的:解决 <a href="https://gitee.com/zhijiantianya/msgpush-cloud/issues/ICUQL9">修改配置会导致 @TenantIgnore Controller 接口过滤失效</>
|
||||
*/
|
||||
private final Set<String> ignoreUrls;
|
||||
|
||||
private final AntPathMatcher pathMatcher;
|
||||
|
||||
private final GlobalExceptionHandler globalExceptionHandler;
|
||||
private final TenantFrameworkService tenantFrameworkService;
|
||||
|
||||
public TenantSecurityWebFilter(WebProperties webProperties,
|
||||
TenantProperties tenantProperties,
|
||||
Set<String> ignoreUrls,
|
||||
GlobalExceptionHandler globalExceptionHandler,
|
||||
TenantFrameworkService tenantFrameworkService) {
|
||||
super(webProperties);
|
||||
this.tenantProperties = tenantProperties;
|
||||
this.ignoreUrls = ignoreUrls;
|
||||
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();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果非允许忽略租户的 URL,则校验租户是否合法
|
||||
if (!isIgnoreUrl(request)) {
|
||||
// 2. 如果请求未带租户的编号,不允许访问。
|
||||
if (tenantId == null) {
|
||||
log.error("[doFilterInternal][URL({}/{}) 未传递租户编号]", request.getRequestURI(), request.getMethod());
|
||||
ServletUtils.writeJSON(response, CommonResult.error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
|
||||
"请求的租户标识未传递,请进行排查"));
|
||||
return;
|
||||
}
|
||||
// 3. 校验租户是合法,例如说被禁用、到期
|
||||
try {
|
||||
tenantFrameworkService.validTenant(tenantId);
|
||||
} catch (Throwable ex) {
|
||||
CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex);
|
||||
ServletUtils.writeJSON(response, result);
|
||||
return;
|
||||
}
|
||||
} else { // 如果是允许忽略租户的 URL,若未传递租户编号,则默认忽略租户编号,避免报错
|
||||
if (tenantId == null) {
|
||||
TenantContextHolder.setIgnore(true);
|
||||
}
|
||||
}
|
||||
|
||||
// 继续过滤
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
private boolean isIgnoreUrl(HttpServletRequest request) {
|
||||
String apiUri = request.getRequestURI().substring(request.getContextPath().length());
|
||||
// 快速匹配,保证性能
|
||||
if (CollUtil.contains(tenantProperties.getIgnoreUrls(), apiUri)
|
||||
|| CollUtil.contains(ignoreUrls, apiUri)) {
|
||||
return true;
|
||||
}
|
||||
// 逐个 Ant 路径匹配
|
||||
for (String url : tenantProperties.getIgnoreUrls()) {
|
||||
if (pathMatcher.match(url, apiUri)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (String url : ignoreUrls) {
|
||||
if (pathMatcher.match(url, apiUri)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Tenant 框架 Service 接口,定义获取租户信息
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
public interface TenantFrameworkService {
|
||||
|
||||
/**
|
||||
* 获得所有租户
|
||||
*
|
||||
* @return 租户编号数组
|
||||
*/
|
||||
List<Long> getTenantIds();
|
||||
|
||||
/**
|
||||
* 校验租户是否合法
|
||||
*
|
||||
* @param id 租户编号
|
||||
*/
|
||||
void validTenant(Long id);
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.service;
|
||||
|
||||
import com.njcn.msgpush.framework.common.pojo.CommonResult;
|
||||
import com.njcn.msgpush.framework.common.biz.system.tenant.TenantCommonApi;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
import static com.njcn.msgpush.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
|
||||
|
||||
/**
|
||||
* Tenant 框架 Service 实现类
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class TenantFrameworkServiceImpl implements TenantFrameworkService {
|
||||
|
||||
private final TenantCommonApi tenantApi;
|
||||
|
||||
/**
|
||||
* 针对 {@link #getTenantIds()} 的缓存
|
||||
*/
|
||||
private final LoadingCache<Object, List<Long>> getTenantIdsCache = buildAsyncReloadingCache(
|
||||
Duration.ofMinutes(1L), // 过期时间 1 分钟
|
||||
new CacheLoader<Object, List<Long>>() {
|
||||
|
||||
@Override
|
||||
public List<Long> load(Object key) {
|
||||
return tenantApi.getTenantIdList().getCheckedData();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* 针对 {@link #validTenant(Long)} 的缓存
|
||||
*/
|
||||
private final LoadingCache<Long, CommonResult<Boolean>> validTenantCache = buildAsyncReloadingCache(
|
||||
Duration.ofMinutes(1L), // 过期时间 1 分钟
|
||||
new CacheLoader<Long, CommonResult<Boolean>>() {
|
||||
|
||||
@Override
|
||||
public CommonResult<Boolean> load(Long id) {
|
||||
return tenantApi.validTenant(id);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public List<Long> getTenantIds() {
|
||||
return getTenantIdsCache.get(Boolean.TRUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void validTenant(Long id) {
|
||||
validTenantCache.get(id).checkError();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.util;
|
||||
|
||||
import com.njcn.msgpush.framework.tenant.core.context.TenantContextHolder;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import static com.njcn.msgpush.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
|
||||
|
||||
/**
|
||||
* 多租户 Util
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定租户,执行对应的逻辑
|
||||
*
|
||||
* 注意,如果当前是忽略租户的情况下,会被强制设置成不忽略租户
|
||||
* 当然,执行完成后,还是会恢复回去
|
||||
*
|
||||
* @param tenantId 租户编号
|
||||
* @param callable 逻辑
|
||||
* @return 结果
|
||||
*/
|
||||
public static <V> V execute(Long tenantId, Callable<V> callable) {
|
||||
Long oldTenantId = TenantContextHolder.getTenantId();
|
||||
Boolean oldIgnore = TenantContextHolder.isIgnore();
|
||||
try {
|
||||
TenantContextHolder.setTenantId(tenantId);
|
||||
TenantContextHolder.setIgnore(false);
|
||||
// 执行逻辑
|
||||
return callable.call();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
TenantContextHolder.setTenantId(oldTenantId);
|
||||
TenantContextHolder.setIgnore(oldIgnore);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略租户,执行对应的逻辑
|
||||
*
|
||||
* @param runnable 逻辑
|
||||
*/
|
||||
public static void executeIgnore(Runnable runnable) {
|
||||
Boolean oldIgnore = TenantContextHolder.isIgnore();
|
||||
try {
|
||||
TenantContextHolder.setIgnore(true);
|
||||
// 执行逻辑
|
||||
runnable.run();
|
||||
} finally {
|
||||
TenantContextHolder.setIgnore(oldIgnore);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略租户,执行对应的逻辑
|
||||
*
|
||||
* @param callable 逻辑
|
||||
* @return 结果
|
||||
*/
|
||||
public static <V> V executeIgnore(Callable<V> callable) {
|
||||
Boolean oldIgnore = TenantContextHolder.isIgnore();
|
||||
try {
|
||||
TenantContextHolder.setIgnore(true);
|
||||
// 执行逻辑
|
||||
return callable.call();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
TenantContextHolder.setIgnore(oldIgnore);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将多租户编号,添加到 header 中
|
||||
*
|
||||
* @param headers HTTP 请求 headers
|
||||
* @param tenantId 租户编号
|
||||
*/
|
||||
public static void addTenantHeader(Map<String, String> headers, Long tenantId) {
|
||||
if (tenantId != null) {
|
||||
headers.put(HEADER_TENANT_ID, tenantId.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.web;
|
||||
|
||||
import com.njcn.msgpush.framework.tenant.core.context.TenantContextHolder;
|
||||
import com.njcn.msgpush.framework.web.core.util.WebFrameworkUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 多租户 Context Web 过滤器
|
||||
* 将请求 Header 中的 tenant-id 解析出来,添加到 {@link TenantContextHolder} 中,这样后续的 DB 等操作,可以获得到租户编号。
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
public class TenantContextWebFilter extends OncePerRequestFilter {
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws ServletException, IOException {
|
||||
// 设置
|
||||
Long tenantId = WebFrameworkUtils.getTenantId(request);
|
||||
if (tenantId != null) {
|
||||
TenantContextHolder.setTenantId(tenantId);
|
||||
}
|
||||
try {
|
||||
chain.doFilter(request, response);
|
||||
} finally {
|
||||
// 清理
|
||||
TenantContextHolder.clear();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package com.njcn.msgpush.framework.tenant.core.web;
|
||||
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import com.njcn.msgpush.framework.common.exception.enums.GlobalErrorCodeConstants;
|
||||
import com.njcn.msgpush.framework.security.core.LoginUser;
|
||||
import com.njcn.msgpush.framework.security.core.service.SecurityFrameworkService;
|
||||
import com.njcn.msgpush.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.njcn.msgpush.framework.tenant.config.TenantProperties;
|
||||
import com.njcn.msgpush.framework.tenant.core.context.TenantContextHolder;
|
||||
import com.njcn.msgpush.framework.web.core.util.WebFrameworkUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import static com.njcn.msgpush.framework.common.exception.util.ServiceExceptionUtil.exception0;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class TenantVisitContextInterceptor implements HandlerInterceptor {
|
||||
|
||||
private static final String PERMISSION = "system:tenant:visit";
|
||||
|
||||
private final TenantProperties tenantProperties;
|
||||
|
||||
private final SecurityFrameworkService securityFrameworkService;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
// 如果和当前租户编号一致,则直接跳过
|
||||
Long visitTenantId = WebFrameworkUtils.getVisitTenantId(request);
|
||||
if (visitTenantId == null) {
|
||||
return true;
|
||||
}
|
||||
if (ObjUtil.equal(visitTenantId, TenantContextHolder.getTenantId())) {
|
||||
return true;
|
||||
}
|
||||
// 必须是登录用户
|
||||
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
|
||||
if (loginUser == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 校验用户是否可切换租户
|
||||
if (!securityFrameworkService.hasAnyPermissions(PERMISSION)) {
|
||||
throw exception0(GlobalErrorCodeConstants.FORBIDDEN.getCode(), "您无权切换租户");
|
||||
}
|
||||
|
||||
// 【重点】切换租户编号
|
||||
loginUser.setVisitTenantId(visitTenantId);
|
||||
TenantContextHolder.setTenantId(visitTenantId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
// 【重点】清理切换,换回原租户编号
|
||||
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
|
||||
if (loginUser != null && loginUser.getTenantId() != null) {
|
||||
TenantContextHolder.setTenantId(loginUser.getTenantId());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* 多租户,支持如下层面:
|
||||
* 1. DB:基于 MyBatis Plus 多租户的功能实现。
|
||||
* 2. Redis:通过在 Redis Key 上拼接租户编号的方式,进行隔离。
|
||||
* 3. Web:请求 HTTP API 时,解析 Header 的 tenant-id 租户编号,添加到租户上下文。
|
||||
* 4. Security:校验当前登陆的用户,是否越权访问其它租户的数据。
|
||||
* 5. Job:在 JobHandler 执行任务时,会按照每个租户,都独立并行执行一次。
|
||||
* 6. MQ:在 Producer 发送消息时,Header 带上 tenant-id 租户编号;在 Consumer 消费消息时,将 Header 的 tenant-id 租户编号,添加到租户上下文。
|
||||
* 7. Async:异步需要保证 ThreadLocal 的传递性,通过使用阿里开源的 TransmittableThreadLocal 实现。相关的改造点,可见:
|
||||
* 1)Spring Async:
|
||||
* {@link com.njcn.msgpush.framework.quartz.config.MsgpushAsyncAutoConfiguration#threadPoolTaskExecutorBeanPostProcessor()}
|
||||
* 2)Spring Security:
|
||||
* TransmittableThreadLocalSecurityContextHolderStrategy
|
||||
* 和 MsgpushSecurityAutoConfiguration#securityContextHolderMethodInvokingFactoryBean() 方法
|
||||
*
|
||||
*/
|
||||
package com.njcn.msgpush.framework.tenant;
|
||||
@@ -1,275 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.messaging.handler.invocation;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.njcn.msgpush.framework.tenant.core.context.TenantContextHolder;
|
||||
import com.njcn.msgpush.framework.tenant.core.util.TenantUtils;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.handler.HandlerMethod;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import static com.njcn.msgpush.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
|
||||
|
||||
/**
|
||||
* Extension of {@link HandlerMethod} that invokes the underlying method with
|
||||
* argument values resolved from the current HTTP request through a list of
|
||||
* {@link HandlerMethodArgumentResolver}.
|
||||
*
|
||||
* 针对 rabbitmq-spring 和 kafka-spring,不存在合适的拓展点,可以实现 Consumer 消费前,读取 Header 中的 tenant-id 设置到 {@link TenantContextHolder} 中
|
||||
* TODO 芋艿:持续跟进,看看有没新的拓展点
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Juergen Hoeller
|
||||
* @since 4.0
|
||||
*/
|
||||
public class InvocableHandlerMethod extends HandlerMethod {
|
||||
|
||||
private static final Object[] EMPTY_ARGS = new Object[0];
|
||||
|
||||
|
||||
private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
|
||||
|
||||
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
|
||||
|
||||
|
||||
/**
|
||||
* Create an instance from a {@code HandlerMethod}.
|
||||
*/
|
||||
public InvocableHandlerMethod(HandlerMethod handlerMethod) {
|
||||
super(handlerMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from a bean instance and a method.
|
||||
*/
|
||||
public InvocableHandlerMethod(Object bean, Method method) {
|
||||
super(bean, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new handler method with the given bean instance, method name and parameters.
|
||||
* @param bean the object bean
|
||||
* @param methodName the method name
|
||||
* @param parameterTypes the method parameter types
|
||||
* @throws NoSuchMethodException when the method cannot be found
|
||||
*/
|
||||
public InvocableHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes)
|
||||
throws NoSuchMethodException {
|
||||
|
||||
super(bean, methodName, parameterTypes);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers} to use for resolving method argument values.
|
||||
*/
|
||||
public void setMessageMethodArgumentResolvers(HandlerMethodArgumentResolverComposite argumentResolvers) {
|
||||
this.resolvers = argumentResolvers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ParameterNameDiscoverer for resolving parameter names when needed
|
||||
* (e.g. default request attribute name).
|
||||
* <p>Default is a {@link org.springframework.core.DefaultParameterNameDiscoverer}.
|
||||
*/
|
||||
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
|
||||
this.parameterNameDiscoverer = parameterNameDiscoverer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Invoke the method after resolving its argument values in the context of the given message.
|
||||
* <p>Argument values are commonly resolved through
|
||||
* {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
|
||||
* The {@code providedArgs} parameter however may supply argument values to be used directly,
|
||||
* i.e. without argument resolution.
|
||||
* <p>Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the
|
||||
* resolved arguments.
|
||||
* @param message the current message being processed
|
||||
* @param providedArgs "given" arguments matched by type, not resolved
|
||||
* @return the raw value returned by the invoked method
|
||||
* @throws Exception raised if no suitable argument resolver can be found,
|
||||
* or if the method raised an exception
|
||||
* @see #getMethodArgumentValues
|
||||
* @see #doInvoke
|
||||
*/
|
||||
@Nullable
|
||||
public Object invoke(Message<?> message, Object... providedArgs) throws Exception {
|
||||
Object[] args = getMethodArgumentValues(message, providedArgs);
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Arguments: " + Arrays.toString(args));
|
||||
}
|
||||
// 注意:如下是本类的改动点!!!
|
||||
// 情况一:无租户编号的情况
|
||||
Long tenantId= parseTenantId(message);
|
||||
if (tenantId == null) {
|
||||
return doInvoke(args);
|
||||
}
|
||||
// 情况二:有租户的情况下
|
||||
return TenantUtils.execute(tenantId, () -> doInvoke(args));
|
||||
}
|
||||
|
||||
private Long parseTenantId(Message<?> message) {
|
||||
Object tenantId = message.getHeaders().get(HEADER_TENANT_ID);
|
||||
if (tenantId == null) {
|
||||
return null;
|
||||
}
|
||||
if (tenantId instanceof Long) {
|
||||
return (Long) tenantId;
|
||||
}
|
||||
if (tenantId instanceof Number) {
|
||||
return ((Number) tenantId).longValue();
|
||||
}
|
||||
if (tenantId instanceof String) {
|
||||
return Long.parseLong((String) tenantId);
|
||||
}
|
||||
if (tenantId instanceof byte[]) {
|
||||
return Long.parseLong(new String((byte[]) tenantId));
|
||||
}
|
||||
throw new IllegalArgumentException("未知的数据类型:" + tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the method argument values for the current message, checking the provided
|
||||
* argument values and falling back to the configured argument resolvers.
|
||||
* <p>The resulting array will be passed into {@link #doInvoke}.
|
||||
* @since 5.1.2
|
||||
*/
|
||||
protected Object[] getMethodArgumentValues(Message<?> message, Object... providedArgs) throws Exception {
|
||||
MethodParameter[] parameters = getMethodParameters();
|
||||
if (ObjectUtils.isEmpty(parameters)) {
|
||||
return EMPTY_ARGS;
|
||||
}
|
||||
|
||||
Object[] args = new Object[parameters.length];
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
MethodParameter parameter = parameters[i];
|
||||
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
|
||||
args[i] = findProvidedArgument(parameter, providedArgs);
|
||||
if (args[i] != null) {
|
||||
continue;
|
||||
}
|
||||
if (!this.resolvers.supportsParameter(parameter)) {
|
||||
throw new MethodArgumentResolutionException(
|
||||
message, parameter, formatArgumentError(parameter, "No suitable resolver"));
|
||||
}
|
||||
try {
|
||||
args[i] = this.resolvers.resolveArgument(parameter, message);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Leave stack trace for later, exception may actually be resolved and handled...
|
||||
if (logger.isDebugEnabled()) {
|
||||
String exMsg = ex.getMessage();
|
||||
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
|
||||
logger.debug(formatArgumentError(parameter, exMsg));
|
||||
}
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the handler method with the given argument values.
|
||||
*/
|
||||
@Nullable
|
||||
protected Object doInvoke(Object... args) throws Exception {
|
||||
try {
|
||||
return getBridgedMethod().invoke(getBean(), args);
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
assertTargetBean(getBridgedMethod(), getBean(), args);
|
||||
String text = (ex.getMessage() == null || ex.getCause() instanceof NullPointerException) ?
|
||||
"Illegal argument": ex.getMessage();
|
||||
throw new IllegalStateException(formatInvokeError(text, args), ex);
|
||||
}
|
||||
catch (InvocationTargetException ex) {
|
||||
// Unwrap for HandlerExceptionResolvers ...
|
||||
Throwable targetException = ex.getTargetException();
|
||||
if (targetException instanceof RuntimeException runtimeException) {
|
||||
throw runtimeException;
|
||||
}
|
||||
else if (targetException instanceof Error error) {
|
||||
throw error;
|
||||
}
|
||||
else if (targetException instanceof Exception exception) {
|
||||
throw exception;
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MethodParameter getAsyncReturnValueType(@Nullable Object returnValue) {
|
||||
return new AsyncResultMethodParameter(returnValue);
|
||||
}
|
||||
|
||||
|
||||
private class AsyncResultMethodParameter extends AnnotatedMethodParameter {
|
||||
|
||||
@Nullable
|
||||
private final Object returnValue;
|
||||
|
||||
private final ResolvableType returnType;
|
||||
|
||||
public AsyncResultMethodParameter(@Nullable Object returnValue) {
|
||||
super(-1);
|
||||
this.returnValue = returnValue;
|
||||
this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric();
|
||||
}
|
||||
|
||||
protected AsyncResultMethodParameter(AsyncResultMethodParameter original) {
|
||||
super(original);
|
||||
this.returnValue = original.returnValue;
|
||||
this.returnType = original.returnType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getParameterType() {
|
||||
if (this.returnValue != null) {
|
||||
return this.returnValue.getClass();
|
||||
}
|
||||
if (!ResolvableType.NONE.equals(this.returnType)) {
|
||||
return this.returnType.toClass();
|
||||
}
|
||||
return super.getParameterType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getGenericParameterType() {
|
||||
return this.returnType.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncResultMethodParameter clone() {
|
||||
return new AsyncResultMethodParameter(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
org.springframework.boot.env.EnvironmentPostProcessor=\
|
||||
com.njcn.msgpush.framework.tenant.core.mq.kafka.TenantKafkaEnvironmentPostProcessor
|
||||
@@ -1,2 +0,0 @@
|
||||
com.njcn.msgpush.framework.tenant.config.MsgpushTenantRpcAutoConfiguration
|
||||
com.njcn.msgpush.framework.tenant.config.MsgpushTenantAutoConfiguration
|
||||
@@ -75,7 +75,7 @@ public class MsgpushCacheAutoConfiguration {
|
||||
RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory());
|
||||
RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory,
|
||||
BatchStrategies.scan(msgpushCacheProperties.getRedisScanBatchSize()));
|
||||
// 创建 TenantRedisCacheManager 对象
|
||||
// 创建 RedisCacheManager 对象
|
||||
return new TimeoutRedisCacheManager(cacheWriter, redisCacheConfiguration);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,10 +35,7 @@ public class LoginUser {
|
||||
* 额外的用户信息
|
||||
*/
|
||||
private Map<String, String> info;
|
||||
/**
|
||||
* 租户编号
|
||||
*/
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 授权范围
|
||||
*/
|
||||
@@ -56,10 +53,6 @@ public class LoginUser {
|
||||
*/
|
||||
@JsonIgnore
|
||||
private Map<String, Object> context;
|
||||
/**
|
||||
* 访问的租户编号
|
||||
*/
|
||||
private Long visitTenantId;
|
||||
|
||||
public void setContext(String key, Object value) {
|
||||
if (context == null) {
|
||||
|
||||
@@ -96,7 +96,6 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
||||
// 构建登录用户
|
||||
return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
|
||||
.setInfo(accessToken.getUserInfo()) // 额外的用户信息
|
||||
.setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes())
|
||||
.setExpiresTime(accessToken.getExpiresTime());
|
||||
} catch (ServiceException serviceException) {
|
||||
// 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
|
||||
@@ -124,8 +123,7 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
||||
}
|
||||
// 构建模拟用户
|
||||
Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length()));
|
||||
return new LoginUser().setId(userId).setUserType(userType)
|
||||
.setTenantId(WebFrameworkUtils.getTenantId(request));
|
||||
return new LoginUser().setId(userId).setUserType(userType);
|
||||
}
|
||||
|
||||
private LoginUser buildLoginUserByHeader(HttpServletRequest request) {
|
||||
|
||||
@@ -16,7 +16,6 @@ import java.util.List;
|
||||
|
||||
import static com.njcn.msgpush.framework.common.util.cache.CacheUtils.buildCache;
|
||||
import static com.njcn.msgpush.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
import static com.njcn.msgpush.framework.security.core.util.SecurityFrameworkUtils.skipPermissionCheck;
|
||||
|
||||
/**
|
||||
* 默认的 {@link SecurityFrameworkService} 实现类
|
||||
@@ -64,11 +63,6 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public boolean hasAnyPermissions(String... permissions) {
|
||||
// 特殊:跨租户访问
|
||||
if (skipPermissionCheck()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 权限校验
|
||||
Long userId = getLoginUserId();
|
||||
if (userId == null) {
|
||||
@@ -85,11 +79,6 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public boolean hasAnyRoles(String... roles) {
|
||||
// 特殊:跨租户访问
|
||||
if (skipPermissionCheck()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 权限校验
|
||||
Long userId = getLoginUserId();
|
||||
if (userId == null) {
|
||||
@@ -105,11 +94,6 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
|
||||
|
||||
@Override
|
||||
public boolean hasAnyScopes(String... scope) {
|
||||
// 特殊:跨租户访问
|
||||
if (skipPermissionCheck()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 权限校验
|
||||
LoginUser user = SecurityFrameworkUtils.getLoginUser();
|
||||
if (user == null) {
|
||||
|
||||
@@ -142,21 +142,5 @@ public class SecurityFrameworkUtils {
|
||||
return authenticationToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否条件跳过权限校验,包括数据权限、功能权限
|
||||
*
|
||||
* @return 是否跳过
|
||||
*/
|
||||
public static boolean skipPermissionCheck() {
|
||||
LoginUser loginUser = getLoginUser();
|
||||
if (loginUser == null) {
|
||||
return false;
|
||||
}
|
||||
if (loginUser.getVisitTenantId() == null) {
|
||||
return false;
|
||||
}
|
||||
// 重点:跨租户访问时,无法进行权限校验
|
||||
return ObjUtil.notEqual(loginUser.getVisitTenantId(), loginUser.getTenantId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.njcn.msgpush.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
|
||||
|
||||
/**
|
||||
* Swagger 自动配置类,基于 OpenAPI + Springdoc 实现。
|
||||
@@ -127,24 +126,12 @@ public class MsgpushSwaggerAutoConfiguration {
|
||||
.group(group)
|
||||
.pathsToMatch("/admin-api/" + path + "/**", "/app-api/" + path + "/**")
|
||||
.addOperationCustomizer((operation, handlerMethod) -> operation
|
||||
.addParametersItem(buildTenantHeaderParameter())
|
||||
.addParametersItem(buildSecurityHeaderParameter()))
|
||||
.addOperationCustomizer(buildOperationIdCustomizer())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 Tenant 租户编号请求头参数
|
||||
*
|
||||
* @return 多租户参数
|
||||
*/
|
||||
private static Parameter buildTenantHeaderParameter() {
|
||||
return new Parameter()
|
||||
.name(HEADER_TENANT_ID) // header 名
|
||||
.description("租户编号") // 描述
|
||||
.in(String.valueOf(SecurityScheme.In.HEADER)) // 请求 header
|
||||
.schema(new IntegerSchema()._default(1L).name(HEADER_TENANT_ID).description("租户编号")); // 默认:使用租户编号为 1
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建 Authorization 认证请求头参数
|
||||
@@ -158,7 +145,7 @@ public class MsgpushSwaggerAutoConfiguration {
|
||||
.name(HttpHeaders.AUTHORIZATION) // header 名
|
||||
.description("认证 Token") // 描述
|
||||
.in(String.valueOf(SecurityScheme.In.HEADER)) // 请求 header
|
||||
.schema(new StringSchema()._default("Bearer test1").name(HEADER_TENANT_ID).description("认证 Token")); // 默认:使用用户编号为 1
|
||||
.schema(new StringSchema()._default("Bearer test1").description("认证 Token")); // 默认:使用用户编号为 1
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,9 +27,6 @@ public class WebFrameworkUtils {
|
||||
|
||||
private static final String REQUEST_ATTRIBUTE_COMMON_RESULT = "common_result";
|
||||
|
||||
public static final String HEADER_TENANT_ID = "tenant-id";
|
||||
public static final String HEADER_VISIT_TENANT_ID = "visit-tenant-id";
|
||||
|
||||
/**
|
||||
* 终端的 Header
|
||||
*
|
||||
@@ -43,29 +40,8 @@ public class WebFrameworkUtils {
|
||||
WebFrameworkUtils.properties = webProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得租户编号,从 header 中
|
||||
* 考虑到其它 framework 组件也会使用到租户编号,所以不得不放在 WebFrameworkUtils 统一提供
|
||||
*
|
||||
* @param request 请求
|
||||
* @return 租户编号
|
||||
*/
|
||||
public static Long getTenantId(HttpServletRequest request) {
|
||||
String tenantId = request.getHeader(HEADER_TENANT_ID);
|
||||
return NumberUtil.isNumber(tenantId) ? Long.valueOf(tenantId) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得访问的租户编号,从 header 中
|
||||
* 考虑到其它 framework 组件也会使用到租户编号,所以不得不放在 WebFrameworkUtils 统一提供
|
||||
*
|
||||
* @param request 请求
|
||||
* @return 租户编号
|
||||
*/
|
||||
public static Long getVisitTenantId(HttpServletRequest request) {
|
||||
String tenantId = request.getHeader(HEADER_VISIT_TENANT_ID);
|
||||
return NumberUtil.isNumber(tenantId)? Long.valueOf(tenantId) : null;
|
||||
}
|
||||
|
||||
|
||||
public static void setLoginUserId(ServletRequest request, Long userId) {
|
||||
request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID, userId);
|
||||
|
||||
@@ -58,16 +58,6 @@
|
||||
<artifactId>rocketmq-spring-boot-starter</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- 业务组件 -->
|
||||
<dependency>
|
||||
<!-- 为什么要依赖 tenant 组件?
|
||||
因为广播某个类型的用户时候,需要根据租户过滤下,避免广播到别的租户!
|
||||
-->
|
||||
<groupId>com.njcn</groupId>
|
||||
<artifactId>msgpush-spring-boot-starter-biz-tenant</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -3,7 +3,6 @@ package com.njcn.msgpush.framework.websocket.core.handler;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.TypeUtil;
|
||||
import com.njcn.msgpush.framework.common.util.json.JsonUtils;
|
||||
import com.njcn.msgpush.framework.tenant.core.util.TenantUtils;
|
||||
import com.njcn.msgpush.framework.websocket.core.listener.WebSocketMessageListener;
|
||||
import com.njcn.msgpush.framework.websocket.core.message.JsonWebSocketMessage;
|
||||
import com.njcn.msgpush.framework.websocket.core.util.WebSocketFrameworkUtils;
|
||||
@@ -73,8 +72,7 @@ public class JsonWebSocketMessageHandler extends TextWebSocketHandler {
|
||||
// 2.3 处理消息
|
||||
Type type = TypeUtil.getTypeArgument(messageListener.getClass(), 0);
|
||||
Object messageObj = JsonUtils.parseObject(jsonMessage.getContent(), type);
|
||||
Long tenantId = WebSocketFrameworkUtils.getTenantId(session);
|
||||
TenantUtils.execute(tenantId, () -> messageListener.onMessage(session, messageObj));
|
||||
messageListener.onMessage(session, messageObj);
|
||||
} catch (Throwable ex) {
|
||||
log.error("[handleTextMessage][session({}) message({}) 处理异常]", session.getId(), message.getPayload());
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.njcn.msgpush.framework.websocket.core.session;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.njcn.msgpush.framework.security.core.LoginUser;
|
||||
import com.njcn.msgpush.framework.tenant.core.context.TenantContextHolder;
|
||||
import com.njcn.msgpush.framework.websocket.core.util.WebSocketFrameworkUtils;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
@@ -95,18 +94,10 @@ public class WebSocketSessionManagerImpl implements WebSocketSessionManager {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
LinkedList<WebSocketSession> result = new LinkedList<>(); // 避免扩容
|
||||
Long contextTenantId = TenantContextHolder.getTenantId();
|
||||
for (List<WebSocketSession> sessions : userSessionsMap.values()) {
|
||||
if (CollUtil.isEmpty(sessions)) {
|
||||
continue;
|
||||
}
|
||||
// 特殊:如果租户不匹配,则直接排除
|
||||
if (contextTenantId != null) {
|
||||
Long userTenantId = WebSocketFrameworkUtils.getTenantId(sessions.get(0));
|
||||
if (!contextTenantId.equals(userTenantId)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result.addAll(sessions);
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -53,15 +53,5 @@ public class WebSocketFrameworkUtils {
|
||||
return loginUser != null ? loginUser.getUserType() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当前用户的租户编号
|
||||
*
|
||||
* @param session Session
|
||||
* @return 租户编号
|
||||
*/
|
||||
public static Long getTenantId(WebSocketSession session) {
|
||||
LoginUser loginUser = getLoginUser(session);
|
||||
return loginUser != null ? loginUser.getTenantId() : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
<module>msgpush-spring-boot-starter-rpc</module>
|
||||
<module>msgpush-spring-boot-starter-excel</module>
|
||||
<module>msgpush-spring-boot-starter-test</module>
|
||||
<module>msgpush-spring-boot-starter-biz-tenant</module>
|
||||
<module>msgpush-spring-boot-starter-biz-ip</module>
|
||||
</modules>
|
||||
|
||||
|
||||
@@ -28,10 +28,7 @@ public class LoginUser {
|
||||
* 额外的用户信息
|
||||
*/
|
||||
private Map<String, String> info;
|
||||
/**
|
||||
* 租户编号
|
||||
*/
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 授权范围
|
||||
*/
|
||||
|
||||
@@ -30,7 +30,7 @@ import static com.njcn.msgpush.framework.common.util.cache.CacheUtils.buildAsync
|
||||
|
||||
/**
|
||||
* Token 过滤器,验证 token 的有效性
|
||||
* 1. 验证通过时,将 userId、userType、tenantId 通过 Header 转发给服务
|
||||
* 1. 验证通过时,将 userId、userType通过 Header 转发给服务
|
||||
* 2. 验证不通过,还是会转发给服务。因为,接口是否需要登录的校验,还是交给服务自身处理
|
||||
*
|
||||
* @author hongawen
|
||||
@@ -42,11 +42,12 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered {
|
||||
* CommonResult<OAuth2AccessTokenCheckRespDTO> 对应的 TypeReference 结果,用于解析 checkToken 的结果
|
||||
*/
|
||||
private static final TypeReference<CommonResult<OAuth2AccessTokenCheckRespDTO>> CHECK_RESULT_TYPE_REFERENCE
|
||||
= new TypeReference<CommonResult<OAuth2AccessTokenCheckRespDTO>>() {};
|
||||
= new TypeReference<CommonResult<OAuth2AccessTokenCheckRespDTO>>() {
|
||||
};
|
||||
|
||||
/**
|
||||
* 空的 LoginUser 的结果
|
||||
*
|
||||
* <p>
|
||||
* 用于解决如下问题:
|
||||
* 1. {@link #getLoginUser(ServerWebExchange, String)} 返回 Mono.empty() 时,会导致后续的 flatMap 无法进行处理的问题。
|
||||
* 2. {@link #buildUser(String)} 时,如果 Token 已经过期,返回 LOGIN_USER_EMPTY 对象,避免缓存无法刷新
|
||||
@@ -57,25 +58,21 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered {
|
||||
|
||||
/**
|
||||
* 登录用户的本地缓存
|
||||
*
|
||||
* key1:多租户的编号
|
||||
* <p>
|
||||
* key2:访问令牌
|
||||
*/
|
||||
private final LoadingCache<KeyValue<Long, String>, LoginUser> loginUserCache = buildAsyncReloadingCache(Duration.ofMinutes(1),
|
||||
new CacheLoader<KeyValue<Long, String>, LoginUser>() {
|
||||
|
||||
private final LoadingCache<String, LoginUser> loginUserCache = buildAsyncReloadingCache(Duration.ofMinutes(1),
|
||||
new CacheLoader<String, LoginUser>() {
|
||||
@Override
|
||||
public LoginUser load(KeyValue<Long, String> token) {
|
||||
String body = checkAccessToken(token.getKey(), token.getValue()).block();
|
||||
public LoginUser load(String token) {
|
||||
String body = checkAccessToken(token).block();
|
||||
return buildUser(body);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
public TokenAuthenticationFilter(ReactorLoadBalancerExchangeFilterFunction lbFunction) {
|
||||
// Q:为什么不使用 OAuth2TokenApi 进行调用?
|
||||
// A1:Spring Cloud OpenFeign 官方未内置 Reactive 的支持 https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#reactive-support
|
||||
// A2:校验 Token 的 API 需要使用到 header[tenant-id] 传递租户编号,暂时不想编写 RequestInterceptor 实现
|
||||
// 因此,这里采用 WebClient,通过 lbFunction 实现负载均衡
|
||||
this.webClient = WebClient.builder().filter(lbFunction).build();
|
||||
}
|
||||
@@ -91,7 +88,7 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered {
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
// 情况二,如果有 Token 令牌,则解析对应 userId、userType、tenantId 等字段,并通过 通过 Header 转发给服务
|
||||
// 情况二,如果有 Token 令牌,则解析对应 userId、userType等字段,并通过 通过 Header 转发给服务
|
||||
// 重要说明:defaultIfEmpty 作用,保证 Mono.empty() 情况,可以继续执行 `flatMap 的 chain.filter(exchange)` 逻辑,避免返回给前端空的 Response!!
|
||||
ServerWebExchange finalExchange = exchange;
|
||||
return getLoginUser(exchange, token).defaultIfEmpty(LOGIN_USER_EMPTY).flatMap(user -> {
|
||||
@@ -111,30 +108,26 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered {
|
||||
}
|
||||
|
||||
private Mono<LoginUser> getLoginUser(ServerWebExchange exchange, String token) {
|
||||
// 从缓存中,获取 LoginUser
|
||||
Long tenantId = WebFrameworkUtils.getTenantId(exchange);
|
||||
KeyValue<Long, String> cacheKey = new KeyValue<Long, String>().setKey(tenantId).setValue(token);
|
||||
LoginUser localUser = loginUserCache.getIfPresent(cacheKey);
|
||||
LoginUser localUser = loginUserCache.getIfPresent(token);
|
||||
if (localUser != null) {
|
||||
return Mono.just(localUser);
|
||||
}
|
||||
|
||||
// 缓存不存在,则请求远程服务
|
||||
return checkAccessToken(tenantId, token).flatMap((Function<String, Mono<LoginUser>>) body -> {
|
||||
return checkAccessToken(token).flatMap((Function<String, Mono<LoginUser>>) body -> {
|
||||
LoginUser remoteUser = buildUser(body);
|
||||
if (remoteUser != null) {
|
||||
// 非空,则进行缓存
|
||||
loginUserCache.put(cacheKey, remoteUser);
|
||||
loginUserCache.put(token, remoteUser);
|
||||
return Mono.just(remoteUser);
|
||||
}
|
||||
return Mono.empty();
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<String> checkAccessToken(Long tenantId, String token) {
|
||||
private Mono<String> checkAccessToken(String token) {
|
||||
return webClient.get()
|
||||
.uri(OAuth2TokenCommonApi.URL_CHECK, uriBuilder -> uriBuilder.queryParam("accessToken", token).build())
|
||||
.headers(httpHeaders -> WebFrameworkUtils.setTenantIdHeader(tenantId, httpHeaders)) // 设置租户的 Header
|
||||
.retrieve().bodyToMono(String.class);
|
||||
}
|
||||
|
||||
@@ -156,7 +149,6 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered {
|
||||
OAuth2AccessTokenCheckRespDTO tokenInfo = result.getData();
|
||||
return new LoginUser().setId(tokenInfo.getUserId()).setUserType(tokenInfo.getUserType())
|
||||
.setInfo(tokenInfo.getUserInfo()) // 额外的用户信息
|
||||
.setTenantId(tokenInfo.getTenantId()).setScopes(tokenInfo.getScopes())
|
||||
.setExpiresTime(tokenInfo.getExpiresTime());
|
||||
}
|
||||
|
||||
|
||||
@@ -26,27 +26,9 @@ import reactor.core.publisher.Mono;
|
||||
@Slf4j
|
||||
public class WebFrameworkUtils {
|
||||
|
||||
private static final String HEADER_TENANT_ID = "tenant-id";
|
||||
|
||||
private WebFrameworkUtils() {}
|
||||
|
||||
/**
|
||||
* 将 Gateway 请求中的 header,设置到 HttpHeaders 中
|
||||
*
|
||||
* @param tenantId 租户编号
|
||||
* @param httpHeaders WebClient 的请求
|
||||
*/
|
||||
public static void setTenantIdHeader(Long tenantId, HttpHeaders httpHeaders) {
|
||||
if (tenantId == null) {
|
||||
return;
|
||||
}
|
||||
httpHeaders.set(HEADER_TENANT_ID, String.valueOf(tenantId));
|
||||
}
|
||||
|
||||
public static Long getTenantId(ServerWebExchange exchange) {
|
||||
String tenantId = exchange.getRequest().getHeaders().getFirst(HEADER_TENANT_ID);
|
||||
return NumberUtil.isNumber(tenantId) ? Long.valueOf(tenantId) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回 JSON 字符串
|
||||
|
||||
@@ -15,15 +15,6 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE = new ErrorCode(1_001_000_003, "不能删除类型为系统内置的参数配置");
|
||||
ErrorCode CONFIG_GET_VALUE_ERROR_IF_VISIBLE = new ErrorCode(1_001_000_004, "获取参数配置失败,原因:不允许获取不可见配置");
|
||||
|
||||
// ========== 定时任务 1-001-001-000 ==========
|
||||
ErrorCode JOB_NOT_EXISTS = new ErrorCode(1_001_001_000, "定时任务不存在");
|
||||
ErrorCode JOB_HANDLER_EXISTS = new ErrorCode(1_001_001_001, "定时任务的处理器已经存在");
|
||||
ErrorCode JOB_CHANGE_STATUS_INVALID = new ErrorCode(1_001_001_002, "只允许修改为开启或者关闭状态");
|
||||
ErrorCode JOB_CHANGE_STATUS_EQUALS = new ErrorCode(1_001_001_003, "定时任务已经处于该状态,无需修改");
|
||||
ErrorCode JOB_UPDATE_ONLY_NORMAL_STATUS = new ErrorCode(1_001_001_004, "只有开启状态的任务,才可以修改");
|
||||
ErrorCode JOB_CRON_EXPRESSION_VALID = new ErrorCode(1_001_001_005, "CRON 表达式不正确");
|
||||
ErrorCode JOB_HANDLER_BEAN_NOT_EXISTS = new ErrorCode(1_001_001_006, "定时任务的处理器 Bean 不存在,注意 Bean 默认首字母小写");
|
||||
ErrorCode JOB_HANDLER_BEAN_TYPE_ERROR = new ErrorCode(1_001_001_007, "定时任务的处理器 Bean 类型不正确,未实现 JobHandler 接口");
|
||||
|
||||
// ========== API 错误日志 1-001-002-000 ==========
|
||||
ErrorCode API_ERROR_LOG_NOT_FOUND = new ErrorCode(1_001_002_000, "API 错误日志不存在");
|
||||
@@ -56,17 +47,6 @@ public interface ErrorCodeConstants {
|
||||
ErrorCode DATA_SOURCE_CONFIG_NOT_EXISTS = new ErrorCode(1_001_007_000, "数据源配置不存在");
|
||||
ErrorCode DATA_SOURCE_CONFIG_NOT_OK = new ErrorCode(1_001_007_001, "数据源配置不正确,无法进行连接");
|
||||
|
||||
// ========== 学生 1-001-201-000 ==========
|
||||
ErrorCode DEMO01_CONTACT_NOT_EXISTS = new ErrorCode(1_001_201_000, "示例联系人不存在");
|
||||
ErrorCode DEMO02_CATEGORY_NOT_EXISTS = new ErrorCode(1_001_201_001, "示例分类不存在");
|
||||
ErrorCode DEMO02_CATEGORY_EXITS_CHILDREN = new ErrorCode(1_001_201_002, "存在存在子示例分类,无法删除");
|
||||
ErrorCode DEMO02_CATEGORY_PARENT_NOT_EXITS = new ErrorCode(1_001_201_003,"父级示例分类不存在");
|
||||
ErrorCode DEMO02_CATEGORY_PARENT_ERROR = new ErrorCode(1_001_201_004, "不能设置自己为父示例分类");
|
||||
ErrorCode DEMO02_CATEGORY_NAME_DUPLICATE = new ErrorCode(1_001_201_005, "已经存在该名字的示例分类");
|
||||
ErrorCode DEMO02_CATEGORY_PARENT_IS_CHILD = new ErrorCode(1_001_201_006, "不能设置自己的子示例分类为父示例分类");
|
||||
ErrorCode DEMO03_STUDENT_NOT_EXISTS = new ErrorCode(1_001_201_007, "学生不存在");
|
||||
ErrorCode DEMO03_COURSE_NOT_EXISTS = new ErrorCode(1_001_201_008, "学生课程不存在");
|
||||
ErrorCode DEMO03_GRADE_NOT_EXISTS = new ErrorCode(1_001_201_009, "学生班级不存在");
|
||||
ErrorCode DEMO03_GRADE_EXISTS = new ErrorCode(1_001_201_010, "学生班级已存在");
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -32,11 +32,6 @@
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 业务组件 -->
|
||||
<dependency>
|
||||
<groupId>com.njcn</groupId>
|
||||
<artifactId>msgpush-spring-boot-starter-biz-tenant</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo.demo01;
|
||||
|
||||
import com.njcn.msgpush.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import com.njcn.msgpush.framework.common.pojo.CommonResult;
|
||||
import com.njcn.msgpush.framework.common.pojo.PageParam;
|
||||
import com.njcn.msgpush.framework.common.pojo.PageResult;
|
||||
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
|
||||
import com.njcn.msgpush.framework.excel.core.util.ExcelUtils;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo01.vo.Demo01ContactPageReqVO;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo01.vo.Demo01ContactRespVO;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo01.vo.Demo01ContactSaveReqVO;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo01.Demo01ContactDO;
|
||||
import com.njcn.msgpush.module.infra.service.demo.demo01.Demo01ContactService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static com.njcn.msgpush.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
|
||||
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 示例联系人")
|
||||
@RestController
|
||||
@RequestMapping("/infra/demo01-contact")
|
||||
@Validated
|
||||
public class Demo01ContactController {
|
||||
|
||||
@Resource
|
||||
private Demo01ContactService demo01ContactService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建示例联系人")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo01-contact:create')")
|
||||
public CommonResult<Long> createDemo01Contact(@Valid @RequestBody Demo01ContactSaveReqVO createReqVO) {
|
||||
return success(demo01ContactService.createDemo01Contact(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新示例联系人")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo01-contact:update')")
|
||||
public CommonResult<Boolean> updateDemo01Contact(@Valid @RequestBody Demo01ContactSaveReqVO updateReqVO) {
|
||||
demo01ContactService.updateDemo01Contact(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除示例联系人")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo01-contact:delete')")
|
||||
public CommonResult<Boolean> deleteDemo01Contact(@RequestParam("id") Long id) {
|
||||
demo01ContactService.deleteDemo01Contact(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-list")
|
||||
@Parameter(name = "ids", description = "编号", required = true)
|
||||
@Operation(summary = "批量删除示例联系人")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo01-contact:delete')")
|
||||
public CommonResult<Boolean> deleteDemo0iContactList(@RequestParam("ids") List<Long> ids) {
|
||||
demo01ContactService.deleteDemo0iContactList(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得示例联系人")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo01-contact:query')")
|
||||
public CommonResult<Demo01ContactRespVO> getDemo01Contact(@RequestParam("id") Long id) {
|
||||
Demo01ContactDO demo01Contact = demo01ContactService.getDemo01Contact(id);
|
||||
return success(BeanUtils.toBean(demo01Contact, Demo01ContactRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得示例联系人分页")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo01-contact:query')")
|
||||
public CommonResult<PageResult<Demo01ContactRespVO>> getDemo01ContactPage(@Valid Demo01ContactPageReqVO pageReqVO) {
|
||||
PageResult<Demo01ContactDO> pageResult = demo01ContactService.getDemo01ContactPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, Demo01ContactRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出示例联系人 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo01-contact:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportDemo01ContactExcel(@Valid Demo01ContactPageReqVO pageReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
List<Demo01ContactDO> list = demo01ContactService.getDemo01ContactPage(pageReqVO).getList();
|
||||
// 导出 Excel
|
||||
ExcelUtils.write(response, "示例联系人.xls", "数据", Demo01ContactRespVO.class,
|
||||
BeanUtils.toBean(list, Demo01ContactRespVO.class));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo.demo01.vo;
|
||||
|
||||
import com.njcn.msgpush.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static com.njcn.msgpush.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 示例联系人分页 Request VO")
|
||||
@Data
|
||||
public class Demo01ContactPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "名字", example = "张三")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "性别", example = "1")
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo.demo01.vo;
|
||||
|
||||
import com.njcn.msgpush.framework.excel.core.annotations.DictFormat;
|
||||
import com.njcn.msgpush.framework.excel.core.convert.DictConvert;
|
||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 示例联系人 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class Demo01ContactRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21555")
|
||||
@ExcelProperty("编号")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
|
||||
@ExcelProperty("名字")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@ExcelProperty(value = "性别", converter = DictConvert.class)
|
||||
@DictFormat("system_user_sex") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "出生年", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("出生年")
|
||||
private LocalDateTime birthday;
|
||||
|
||||
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "你说的对")
|
||||
@ExcelProperty("简介")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "头像")
|
||||
@ExcelProperty("头像")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo.demo01.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 示例联系人新增/修改 Request VO")
|
||||
@Data
|
||||
public class Demo01ContactSaveReqVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21555")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
|
||||
@NotEmpty(message = "名字不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotNull(message = "性别不能为空")
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "出生年", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "出生年不能为空")
|
||||
private LocalDateTime birthday;
|
||||
|
||||
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "你说的对")
|
||||
@NotEmpty(message = "简介不能为空")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "头像")
|
||||
private String avatar;
|
||||
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo.demo02;
|
||||
|
||||
import com.njcn.msgpush.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import com.njcn.msgpush.framework.common.pojo.CommonResult;
|
||||
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
|
||||
import com.njcn.msgpush.framework.excel.core.util.ExcelUtils;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo02.vo.Demo02CategoryListReqVO;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo02.vo.Demo02CategoryRespVO;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo02.vo.Demo02CategorySaveReqVO;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo02.Demo02CategoryDO;
|
||||
import com.njcn.msgpush.module.infra.service.demo.demo02.Demo02CategoryService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static com.njcn.msgpush.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
|
||||
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 示例分类")
|
||||
@RestController
|
||||
@RequestMapping("/infra/demo02-category")
|
||||
@Validated
|
||||
public class Demo02CategoryController {
|
||||
|
||||
@Resource
|
||||
private Demo02CategoryService demo02CategoryService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建示例分类")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo02-category:create')")
|
||||
public CommonResult<Long> createDemo02Category(@Valid @RequestBody Demo02CategorySaveReqVO createReqVO) {
|
||||
return success(demo02CategoryService.createDemo02Category(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新示例分类")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo02-category:update')")
|
||||
public CommonResult<Boolean> updateDemo02Category(@Valid @RequestBody Demo02CategorySaveReqVO updateReqVO) {
|
||||
demo02CategoryService.updateDemo02Category(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除示例分类")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo02-category:delete')")
|
||||
public CommonResult<Boolean> deleteDemo02Category(@RequestParam("id") Long id) {
|
||||
demo02CategoryService.deleteDemo02Category(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得示例分类")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo02-category:query')")
|
||||
public CommonResult<Demo02CategoryRespVO> getDemo02Category(@RequestParam("id") Long id) {
|
||||
Demo02CategoryDO demo02Category = demo02CategoryService.getDemo02Category(id);
|
||||
return success(BeanUtils.toBean(demo02Category, Demo02CategoryRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "获得示例分类列表")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo02-category:query')")
|
||||
public CommonResult<List<Demo02CategoryRespVO>> getDemo02CategoryList(@Valid Demo02CategoryListReqVO listReqVO) {
|
||||
List<Demo02CategoryDO> list = demo02CategoryService.getDemo02CategoryList(listReqVO);
|
||||
return success(BeanUtils.toBean(list, Demo02CategoryRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出示例分类 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo02-category:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportDemo02CategoryExcel(@Valid Demo02CategoryListReqVO listReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
List<Demo02CategoryDO> list = demo02CategoryService.getDemo02CategoryList(listReqVO);
|
||||
// 导出 Excel
|
||||
ExcelUtils.write(response, "示例分类.xls", "数据", Demo02CategoryRespVO.class,
|
||||
BeanUtils.toBean(list, Demo02CategoryRespVO.class));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo.demo02.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static com.njcn.msgpush.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 示例分类列表 Request VO")
|
||||
@Data
|
||||
public class Demo02CategoryListReqVO {
|
||||
|
||||
@Schema(description = "名字", example = "芋艿")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "父级编号", example = "6080")
|
||||
private Long parentId;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo.demo02.vo;
|
||||
|
||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 示例分类 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class Demo02CategoryRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10304")
|
||||
@ExcelProperty("编号")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
|
||||
@ExcelProperty("名字")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "父级编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6080")
|
||||
@ExcelProperty("父级编号")
|
||||
private Long parentId;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo.demo02.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 示例分类新增/修改 Request VO")
|
||||
@Data
|
||||
public class Demo02CategorySaveReqVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10304")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
|
||||
@NotEmpty(message = "名字不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "父级编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6080")
|
||||
@NotNull(message = "父级编号不能为空")
|
||||
private Long parentId;
|
||||
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo.demo03.erp;
|
||||
|
||||
import com.njcn.msgpush.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import com.njcn.msgpush.framework.common.pojo.CommonResult;
|
||||
import com.njcn.msgpush.framework.common.pojo.PageParam;
|
||||
import com.njcn.msgpush.framework.common.pojo.PageResult;
|
||||
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
|
||||
import com.njcn.msgpush.framework.excel.core.util.ExcelUtils;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo03.erp.vo.Demo03StudentErpPageReqVO;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo03.erp.vo.Demo03StudentErpRespVO;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo03.erp.vo.Demo03StudentErpSaveReqVO;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03CourseDO;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03GradeDO;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03StudentDO;
|
||||
import com.njcn.msgpush.module.infra.service.demo.demo03.erp.Demo03StudentErpService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static com.njcn.msgpush.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
|
||||
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 学生")
|
||||
@RestController
|
||||
@RequestMapping("/infra/demo03-student-erp")
|
||||
@Validated
|
||||
public class Demo03StudentErpController {
|
||||
|
||||
@Resource
|
||||
private Demo03StudentErpService demo03StudentErpService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建学生")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:create')")
|
||||
public CommonResult<Long> createDemo03Student(@Valid @RequestBody Demo03StudentErpSaveReqVO createReqVO) {
|
||||
return success(demo03StudentErpService.createDemo03Student(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新学生")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:update')")
|
||||
public CommonResult<Boolean> updateDemo03Student(@Valid @RequestBody Demo03StudentErpSaveReqVO updateReqVO) {
|
||||
demo03StudentErpService.updateDemo03Student(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除学生")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:delete')")
|
||||
public CommonResult<Boolean> deleteDemo03Student(@RequestParam("id") Long id) {
|
||||
demo03StudentErpService.deleteDemo03Student(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-list")
|
||||
@Parameter(name = "ids", description = "编号", required = true)
|
||||
@Operation(summary = "批量删除学生")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:delete')")
|
||||
public CommonResult<Boolean> deleteDemo03StudentList(@RequestParam("ids") List<Long> ids) {
|
||||
demo03StudentErpService.deleteDemo03StudentList(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得学生")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
|
||||
public CommonResult<Demo03StudentErpRespVO> getDemo03Student(@RequestParam("id") Long id) {
|
||||
Demo03StudentDO demo03Student = demo03StudentErpService.getDemo03Student(id);
|
||||
return success(BeanUtils.toBean(demo03Student, Demo03StudentErpRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得学生分页")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
|
||||
public CommonResult<PageResult<Demo03StudentErpRespVO>> getDemo03StudentPage(@Valid Demo03StudentErpPageReqVO pageReqVO) {
|
||||
PageResult<Demo03StudentDO> pageResult = demo03StudentErpService.getDemo03StudentPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, Demo03StudentErpRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出学生 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportDemo03StudentExcel(@Valid Demo03StudentErpPageReqVO pageReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
List<Demo03StudentDO> list = demo03StudentErpService.getDemo03StudentPage(pageReqVO).getList();
|
||||
// 导出 Excel
|
||||
ExcelUtils.write(response, "学生.xls", "数据", Demo03StudentErpRespVO.class,
|
||||
BeanUtils.toBean(list, Demo03StudentErpRespVO.class));
|
||||
}
|
||||
|
||||
// ==================== 子表(学生课程) ====================
|
||||
|
||||
@GetMapping("/demo03-course/page")
|
||||
@Operation(summary = "获得学生课程分页")
|
||||
@Parameter(name = "studentId", description = "学生编号")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
|
||||
public CommonResult<PageResult<Demo03CourseDO>> getDemo03CoursePage(PageParam pageReqVO,
|
||||
@RequestParam("studentId") Long studentId) {
|
||||
return success(demo03StudentErpService.getDemo03CoursePage(pageReqVO, studentId));
|
||||
}
|
||||
|
||||
@PostMapping("/demo03-course/create")
|
||||
@Operation(summary = "创建学生课程")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:create')")
|
||||
public CommonResult<Long> createDemo03Course(@Valid @RequestBody Demo03CourseDO demo03Course) {
|
||||
return success(demo03StudentErpService.createDemo03Course(demo03Course));
|
||||
}
|
||||
|
||||
@PutMapping("/demo03-course/update")
|
||||
@Operation(summary = "更新学生课程")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:update')")
|
||||
public CommonResult<Boolean> updateDemo03Course(@Valid @RequestBody Demo03CourseDO demo03Course) {
|
||||
demo03StudentErpService.updateDemo03Course(demo03Course);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/demo03-course/delete")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@Operation(summary = "删除学生课程")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:delete')")
|
||||
public CommonResult<Boolean> deleteDemo03Course(@RequestParam("id") Long id) {
|
||||
demo03StudentErpService.deleteDemo03Course(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/demo03-course/delete-list")
|
||||
@Parameter(name = "ids", description = "编号", required = true)
|
||||
@Operation(summary = "批量删除学生课程")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:delete')")
|
||||
public CommonResult<Boolean> deleteDemo03CourseList(@RequestParam("ids") List<Long> ids) {
|
||||
demo03StudentErpService.deleteDemo03CourseList(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/demo03-course/get")
|
||||
@Operation(summary = "获得学生课程")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
|
||||
public CommonResult<Demo03CourseDO> getDemo03Course(@RequestParam("id") Long id) {
|
||||
return success(demo03StudentErpService.getDemo03Course(id));
|
||||
}
|
||||
|
||||
// ==================== 子表(学生班级) ====================
|
||||
|
||||
@GetMapping("/demo03-grade/page")
|
||||
@Operation(summary = "获得学生班级分页")
|
||||
@Parameter(name = "studentId", description = "学生编号")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
|
||||
public CommonResult<PageResult<Demo03GradeDO>> getDemo03GradePage(PageParam pageReqVO,
|
||||
@RequestParam("studentId") Long studentId) {
|
||||
return success(demo03StudentErpService.getDemo03GradePage(pageReqVO, studentId));
|
||||
}
|
||||
|
||||
@PostMapping("/demo03-grade/create")
|
||||
@Operation(summary = "创建学生班级")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:create')")
|
||||
public CommonResult<Long> createDemo03Grade(@Valid @RequestBody Demo03GradeDO demo03Grade) {
|
||||
return success(demo03StudentErpService.createDemo03Grade(demo03Grade));
|
||||
}
|
||||
|
||||
@PutMapping("/demo03-grade/update")
|
||||
@Operation(summary = "更新学生班级")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:update')")
|
||||
public CommonResult<Boolean> updateDemo03Grade(@Valid @RequestBody Demo03GradeDO demo03Grade) {
|
||||
demo03StudentErpService.updateDemo03Grade(demo03Grade);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/demo03-grade/delete")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@Operation(summary = "删除学生班级")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:delete')")
|
||||
public CommonResult<Boolean> deleteDemo03Grade(@RequestParam("id") Long id) {
|
||||
demo03StudentErpService.deleteDemo03Grade(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/demo03-grade/delete-list")
|
||||
@Parameter(name = "ids", description = "编号", required = true)
|
||||
@Operation(summary = "批量删除学生班级")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:delete')")
|
||||
public CommonResult<Boolean> deleteDemo03GradeList(@RequestParam("ids") List<Long> ids) {
|
||||
demo03StudentErpService.deleteDemo03GradeList(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/demo03-grade/get")
|
||||
@Operation(summary = "获得学生班级")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
|
||||
public CommonResult<Demo03GradeDO> getDemo03Grade(@RequestParam("id") Long id) {
|
||||
return success(demo03StudentErpService.getDemo03Grade(id));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo.demo03.erp.vo;
|
||||
|
||||
import com.njcn.msgpush.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static com.njcn.msgpush.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 学生分页 Request VO")
|
||||
@Data
|
||||
public class Demo03StudentErpPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "名字", example = "芋艿")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "性别")
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "简介", example = "随便")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo.demo03.erp.vo;
|
||||
|
||||
import com.njcn.msgpush.framework.excel.core.annotations.DictFormat;
|
||||
import com.njcn.msgpush.framework.excel.core.convert.DictConvert;
|
||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 学生 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class Demo03StudentErpRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8525")
|
||||
@ExcelProperty("编号")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
|
||||
@ExcelProperty("名字")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty(value = "性别", converter = DictConvert.class)
|
||||
@DictFormat("system_user_sex") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("出生日期")
|
||||
private LocalDateTime birthday;
|
||||
|
||||
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "随便")
|
||||
@ExcelProperty("简介")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo.demo03.erp.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 学生新增/修改 Request VO")
|
||||
@Data
|
||||
public class Demo03StudentErpSaveReqVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8525")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
|
||||
@NotEmpty(message = "名字不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "性别不能为空")
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "出生日期不能为空")
|
||||
private LocalDateTime birthday;
|
||||
|
||||
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "随便")
|
||||
@NotEmpty(message = "简介不能为空")
|
||||
private String description;
|
||||
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo.demo03.inner;
|
||||
|
||||
import com.njcn.msgpush.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import com.njcn.msgpush.framework.common.pojo.CommonResult;
|
||||
import com.njcn.msgpush.framework.common.pojo.PageParam;
|
||||
import com.njcn.msgpush.framework.common.pojo.PageResult;
|
||||
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
|
||||
import com.njcn.msgpush.framework.excel.core.util.ExcelUtils;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo03.inner.vo.Demo03StudentInnerPageReqVO;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo03.inner.vo.Demo03StudentInnerRespVO;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo03.inner.vo.Demo03StudentInnerSaveReqVO;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03CourseDO;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03GradeDO;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03StudentDO;
|
||||
import com.njcn.msgpush.module.infra.service.demo.demo03.inner.Demo03StudentInnerService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static com.njcn.msgpush.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
|
||||
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 学生")
|
||||
@RestController
|
||||
@RequestMapping("/infra/demo03-student-inner")
|
||||
@Validated
|
||||
public class Demo03StudentInnerController {
|
||||
|
||||
@Resource
|
||||
private Demo03StudentInnerService demo03StudentInnerService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建学生")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:create')")
|
||||
public CommonResult<Long> createDemo03Student(@Valid @RequestBody Demo03StudentInnerSaveReqVO createReqVO) {
|
||||
return success(demo03StudentInnerService.createDemo03Student(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新学生")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:update')")
|
||||
public CommonResult<Boolean> updateDemo03Student(@Valid @RequestBody Demo03StudentInnerSaveReqVO updateReqVO) {
|
||||
demo03StudentInnerService.updateDemo03Student(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除学生")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:delete')")
|
||||
public CommonResult<Boolean> deleteDemo03Student(@RequestParam("id") Long id) {
|
||||
demo03StudentInnerService.deleteDemo03Student(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-list")
|
||||
@Parameter(name = "ids", description = "编号", required = true)
|
||||
@Operation(summary = "批量删除学生")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:delete')")
|
||||
public CommonResult<Boolean> deleteDemo03StudentList(@RequestParam("ids") List<Long> ids) {
|
||||
demo03StudentInnerService.deleteDemo03StudentList(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得学生")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
|
||||
public CommonResult<Demo03StudentInnerRespVO> getDemo03Student(@RequestParam("id") Long id) {
|
||||
Demo03StudentDO demo03Student = demo03StudentInnerService.getDemo03Student(id);
|
||||
return success(BeanUtils.toBean(demo03Student, Demo03StudentInnerRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得学生分页")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
|
||||
public CommonResult<PageResult<Demo03StudentInnerRespVO>> getDemo03StudentPage(@Valid Demo03StudentInnerPageReqVO pageReqVO) {
|
||||
PageResult<Demo03StudentDO> pageResult = demo03StudentInnerService.getDemo03StudentPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, Demo03StudentInnerRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出学生 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportDemo03StudentExcel(@Valid Demo03StudentInnerPageReqVO pageReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
List<Demo03StudentDO> list = demo03StudentInnerService.getDemo03StudentPage(pageReqVO).getList();
|
||||
// 导出 Excel
|
||||
ExcelUtils.write(response, "学生.xls", "数据", Demo03StudentInnerRespVO.class,
|
||||
BeanUtils.toBean(list, Demo03StudentInnerRespVO.class));
|
||||
}
|
||||
|
||||
// ==================== 子表(学生课程) ====================
|
||||
|
||||
@GetMapping("/demo03-course/list-by-student-id")
|
||||
@Operation(summary = "获得学生课程列表")
|
||||
@Parameter(name = "studentId", description = "学生编号")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
|
||||
public CommonResult<List<Demo03CourseDO>> getDemo03CourseListByStudentId(@RequestParam("studentId") Long studentId) {
|
||||
return success(demo03StudentInnerService.getDemo03CourseListByStudentId(studentId));
|
||||
}
|
||||
|
||||
// ==================== 子表(学生班级) ====================
|
||||
|
||||
@GetMapping("/demo03-grade/get-by-student-id")
|
||||
@Operation(summary = "获得学生班级")
|
||||
@Parameter(name = "studentId", description = "学生编号")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
|
||||
public CommonResult<Demo03GradeDO> getDemo03GradeByStudentId(@RequestParam("studentId") Long studentId) {
|
||||
return success(demo03StudentInnerService.getDemo03GradeByStudentId(studentId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo.demo03.inner.vo;
|
||||
|
||||
import com.njcn.msgpush.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static com.njcn.msgpush.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 学生分页 Request VO")
|
||||
@Data
|
||||
public class Demo03StudentInnerPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "名字", example = "芋艿")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "性别")
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "简介", example = "随便")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo.demo03.inner.vo;
|
||||
|
||||
import com.njcn.msgpush.framework.excel.core.annotations.DictFormat;
|
||||
import com.njcn.msgpush.framework.excel.core.convert.DictConvert;
|
||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 学生 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class Demo03StudentInnerRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8525")
|
||||
@ExcelProperty("编号")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
|
||||
@ExcelProperty("名字")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty(value = "性别", converter = DictConvert.class)
|
||||
@DictFormat("system_user_sex") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("出生日期")
|
||||
private LocalDateTime birthday;
|
||||
|
||||
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "随便")
|
||||
@ExcelProperty("简介")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo.demo03.inner.vo;
|
||||
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03CourseDO;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03GradeDO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 学生新增/修改 Request VO")
|
||||
@Data
|
||||
public class Demo03StudentInnerSaveReqVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8525")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
|
||||
@NotEmpty(message = "名字不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "性别不能为空")
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "出生日期不能为空")
|
||||
private LocalDateTime birthday;
|
||||
|
||||
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "随便")
|
||||
@NotEmpty(message = "简介不能为空")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "学生课程列表")
|
||||
private List<Demo03CourseDO> demo03Courses;
|
||||
|
||||
@Schema(description = "学生班级")
|
||||
private Demo03GradeDO demo03Grade;
|
||||
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo.demo03.normal;
|
||||
|
||||
import com.njcn.msgpush.framework.apilog.core.annotation.ApiAccessLog;
|
||||
import com.njcn.msgpush.framework.common.pojo.CommonResult;
|
||||
import com.njcn.msgpush.framework.common.pojo.PageParam;
|
||||
import com.njcn.msgpush.framework.common.pojo.PageResult;
|
||||
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
|
||||
import com.njcn.msgpush.framework.excel.core.util.ExcelUtils;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo03.normal.vo.Demo03StudentNormalPageReqVO;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo03.normal.vo.Demo03StudentNormalRespVO;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo03.normal.vo.Demo03StudentNormalSaveReqVO;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03CourseDO;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03GradeDO;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03StudentDO;
|
||||
import com.njcn.msgpush.module.infra.service.demo.demo03.normal.Demo03StudentNormalService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static com.njcn.msgpush.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
|
||||
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "管理后台 - 学生")
|
||||
@RestController
|
||||
@RequestMapping("/infra/demo03-student-normal")
|
||||
@Validated
|
||||
public class Demo03StudentNormalController {
|
||||
|
||||
@Resource
|
||||
private Demo03StudentNormalService demo03StudentNormalService;
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建学生")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:create')")
|
||||
public CommonResult<Long> createDemo03Student(@Valid @RequestBody Demo03StudentNormalSaveReqVO createReqVO) {
|
||||
return success(demo03StudentNormalService.createDemo03Student(createReqVO));
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "更新学生")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:update')")
|
||||
public CommonResult<Boolean> updateDemo03Student(@Valid @RequestBody Demo03StudentNormalSaveReqVO updateReqVO) {
|
||||
demo03StudentNormalService.updateDemo03Student(updateReqVO);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除学生")
|
||||
@Parameter(name = "id", description = "编号", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:delete')")
|
||||
public CommonResult<Boolean> deleteDemo03Student(@RequestParam("id") Long id) {
|
||||
demo03StudentNormalService.deleteDemo03Student(id);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-list")
|
||||
@Parameter(name = "ids", description = "编号", required = true)
|
||||
@Operation(summary = "批量删除学生")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:delete')")
|
||||
public CommonResult<Boolean> deleteDemo03StudentList(@RequestParam("ids") List<Long> ids) {
|
||||
demo03StudentNormalService.deleteDemo03StudentList(ids);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
@Operation(summary = "获得学生")
|
||||
@Parameter(name = "id", description = "编号", required = true, example = "1024")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
|
||||
public CommonResult<Demo03StudentNormalRespVO> getDemo03Student(@RequestParam("id") Long id) {
|
||||
Demo03StudentDO demo03Student = demo03StudentNormalService.getDemo03Student(id);
|
||||
return success(BeanUtils.toBean(demo03Student, Demo03StudentNormalRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获得学生分页")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
|
||||
public CommonResult<PageResult<Demo03StudentNormalRespVO>> getDemo03StudentPage(@Valid Demo03StudentNormalPageReqVO pageReqVO) {
|
||||
PageResult<Demo03StudentDO> pageResult = demo03StudentNormalService.getDemo03StudentPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, Demo03StudentNormalRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出学生 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:export')")
|
||||
@ApiAccessLog(operateType = EXPORT)
|
||||
public void exportDemo03StudentExcel(@Valid Demo03StudentNormalPageReqVO pageReqVO,
|
||||
HttpServletResponse response) throws IOException {
|
||||
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
|
||||
List<Demo03StudentDO> list = demo03StudentNormalService.getDemo03StudentPage(pageReqVO).getList();
|
||||
// 导出 Excel
|
||||
ExcelUtils.write(response, "学生.xls", "数据", Demo03StudentNormalRespVO.class,
|
||||
BeanUtils.toBean(list, Demo03StudentNormalRespVO.class));
|
||||
}
|
||||
|
||||
// ==================== 子表(学生课程) ====================
|
||||
|
||||
@GetMapping("/demo03-course/list-by-student-id")
|
||||
@Operation(summary = "获得学生课程列表")
|
||||
@Parameter(name = "studentId", description = "学生编号")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
|
||||
public CommonResult<List<Demo03CourseDO>> getDemo03CourseListByStudentId(@RequestParam("studentId") Long studentId) {
|
||||
return success(demo03StudentNormalService.getDemo03CourseListByStudentId(studentId));
|
||||
}
|
||||
|
||||
// ==================== 子表(学生班级) ====================
|
||||
|
||||
@GetMapping("/demo03-grade/get-by-student-id")
|
||||
@Operation(summary = "获得学生班级")
|
||||
@Parameter(name = "studentId", description = "学生编号")
|
||||
@PreAuthorize("@ss.hasPermission('infra:demo03-student:query')")
|
||||
public CommonResult<Demo03GradeDO> getDemo03GradeByStudentId(@RequestParam("studentId") Long studentId) {
|
||||
return success(demo03StudentNormalService.getDemo03GradeByStudentId(studentId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo.demo03.normal.vo;
|
||||
|
||||
import com.njcn.msgpush.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static com.njcn.msgpush.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
|
||||
|
||||
@Schema(description = "管理后台 - 学生分页 Request VO")
|
||||
@Data
|
||||
public class Demo03StudentNormalPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "名字", example = "芋艿")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "性别")
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "简介", example = "随便")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo.demo03.normal.vo;
|
||||
|
||||
import com.njcn.msgpush.framework.excel.core.annotations.DictFormat;
|
||||
import com.njcn.msgpush.framework.excel.core.convert.DictConvert;
|
||||
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
|
||||
import cn.idev.excel.annotation.ExcelProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 学生 Response VO")
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
public class Demo03StudentNormalRespVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8525")
|
||||
@ExcelProperty("编号")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
|
||||
@ExcelProperty("名字")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty(value = "性别", converter = DictConvert.class)
|
||||
@DictFormat("system_user_sex") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("出生日期")
|
||||
private LocalDateTime birthday;
|
||||
|
||||
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "随便")
|
||||
@ExcelProperty("简介")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo.demo03.normal.vo;
|
||||
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03CourseDO;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03GradeDO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 学生新增/修改 Request VO")
|
||||
@Data
|
||||
public class Demo03StudentNormalSaveReqVO {
|
||||
|
||||
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8525")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
|
||||
@NotEmpty(message = "名字不能为空")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "性别", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "性别不能为空")
|
||||
private Integer sex;
|
||||
|
||||
@Schema(description = "出生日期", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "出生日期不能为空")
|
||||
private LocalDateTime birthday;
|
||||
|
||||
@Schema(description = "简介", requiredMode = Schema.RequiredMode.REQUIRED, example = "随便")
|
||||
@NotEmpty(message = "简介不能为空")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "学生课程列表")
|
||||
private List<Demo03CourseDO> demo03Courses;
|
||||
|
||||
@Schema(description = "学生班级")
|
||||
private Demo03GradeDO demo03Grade;
|
||||
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
/**
|
||||
* 代码生成示例
|
||||
*
|
||||
* 1. demo01:单表(增删改查)
|
||||
* 2. demo02:单表(树形结构)
|
||||
* 3. demo03:主子表(标准模式)+ 主子表(ERP 模式)+ 主子表(内嵌模式)
|
||||
*/
|
||||
package com.njcn.msgpush.module.infra.controller.admin.demo;
|
||||
@@ -1,45 +0,0 @@
|
||||
### 请求 /infra/file-config/create 接口 => 成功
|
||||
POST {{baseUrl}}/infra/file-config/create
|
||||
Content-Type: application/json
|
||||
tenant-id: {{adminTenantId}}
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
{
|
||||
"name": "S3 - 七牛云",
|
||||
"remark": "",
|
||||
"storage": 20,
|
||||
"config": {
|
||||
"accessKey": "b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8",
|
||||
"accessSecret": "kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP",
|
||||
"bucket": "ruoyi-vue-pro",
|
||||
"endpoint": "s3-cn-south-1.qiniucs.com",
|
||||
"domain": "http://test.msgpush.iocoder.cn",
|
||||
"region": "oss-cn-beijing"
|
||||
}
|
||||
}
|
||||
|
||||
### 请求 /infra/file-config/update 接口 => 成功
|
||||
PUT {{baseUrl}}/infra/file-config/update
|
||||
Content-Type: application/json
|
||||
tenant-id: {{adminTenantId}}
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
{
|
||||
"id": 2,
|
||||
"name": "S3 - 七牛云",
|
||||
"remark": "",
|
||||
"config": {
|
||||
"accessKey": "b7yvuhBSAGjmtPhMFcn9iMOxUOY_I06cA_p0ZUx8",
|
||||
"accessSecret": "kXM1l5ia1RvSX3QaOEcwI3RLz3Y2rmNszWonKZtP",
|
||||
"bucket": "ruoyi-vue-pro",
|
||||
"endpoint": "s3-cn-south-1.qiniucs.com",
|
||||
"domain": "http://test.msgpush.iocoder.cn",
|
||||
"region": "oss-cn-beijing"
|
||||
}
|
||||
}
|
||||
|
||||
### 请求 /infra/file-config/test 接口 => 成功
|
||||
GET {{baseUrl}}/infra/file-config/test?id=2
|
||||
Content-Type: application/json
|
||||
tenant-id: {{adminTenantId}}
|
||||
Authorization: Bearer {{token}}
|
||||
@@ -6,7 +6,7 @@ import cn.hutool.core.util.URLUtil;
|
||||
import com.njcn.msgpush.framework.common.pojo.CommonResult;
|
||||
import com.njcn.msgpush.framework.common.pojo.PageResult;
|
||||
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
|
||||
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
|
||||
|
||||
import com.njcn.msgpush.module.infra.controller.admin.file.vo.file.*;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.file.FileDO;
|
||||
import com.njcn.msgpush.module.infra.service.file.FileService;
|
||||
@@ -100,7 +100,6 @@ public class FileController {
|
||||
|
||||
@GetMapping("/{configId}/get/**")
|
||||
@PermitAll
|
||||
@TenantIgnore
|
||||
@Operation(summary = "下载文件")
|
||||
@Parameter(name = "configId", description = "配置编号", required = true)
|
||||
public void getFileContent(HttpServletRequest request,
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
### 请求 /infra/redis/get-monitor-info 接口 => 成功
|
||||
GET {{baseUrl}}/infra/redis/get-monitor-info
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenantId}}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.njcn.msgpush.module.infra.dal.dataobject.codegen;
|
||||
|
||||
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
|
||||
|
||||
import com.njcn.msgpush.module.infra.enums.codegen.CodegenColumnHtmlTypeEnum;
|
||||
import com.njcn.msgpush.module.infra.enums.codegen.CodegenColumnListConditionEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
@@ -18,7 +18,6 @@ import lombok.Data;
|
||||
@TableName(value = "infra_codegen_column", autoResultMap = true)
|
||||
@KeySequence("infra_codegen_column_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@TenantIgnore
|
||||
public class CodegenColumnDO extends BaseDO {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.njcn.msgpush.module.infra.dal.dataobject.codegen;
|
||||
|
||||
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
|
||||
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.db.DataSourceConfigDO;
|
||||
import com.njcn.msgpush.module.infra.enums.codegen.CodegenFrontTypeEnum;
|
||||
import com.njcn.msgpush.module.infra.enums.codegen.CodegenSceneEnum;
|
||||
@@ -20,7 +20,6 @@ import lombok.Data;
|
||||
@TableName(value = "infra_codegen_table", autoResultMap = true)
|
||||
@KeySequence("infra_codegen_table_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@TenantIgnore
|
||||
public class CodegenTableDO extends BaseDO {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.njcn.msgpush.module.infra.dal.dataobject.config;
|
||||
|
||||
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
|
||||
|
||||
import com.njcn.msgpush.module.infra.enums.config.ConfigTypeEnum;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
@@ -20,7 +20,6 @@ import lombok.ToString;
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@TenantIgnore
|
||||
public class ConfigDO extends BaseDO {
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.njcn.msgpush.module.infra.dal.dataobject.db;
|
||||
|
||||
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.njcn.msgpush.framework.mybatis.core.type.EncryptTypeHandler;
|
||||
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
@@ -16,7 +16,6 @@ import lombok.Data;
|
||||
@TableName(value = "infra_data_source_config", autoResultMap = true)
|
||||
@KeySequence("infra_data_source_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@TenantIgnore
|
||||
public class DataSourceConfigDO extends BaseDO {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.dal.dataobject.demo.demo01;
|
||||
|
||||
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 示例联系人 DO
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@TableName("msgpush_demo01_contact")
|
||||
@KeySequence("msgpush_demo01_contact_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Demo01ContactDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 性别
|
||||
*
|
||||
* 枚举 {@link TODO system_user_sex 对应的类}
|
||||
*/
|
||||
private Integer sex;
|
||||
/**
|
||||
* 出生年
|
||||
*/
|
||||
private LocalDateTime birthday;
|
||||
/**
|
||||
* 简介
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
private String avatar;
|
||||
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.dal.dataobject.demo.demo02;
|
||||
|
||||
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 示例分类 DO
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@TableName("msgpush_demo02_category")
|
||||
@KeySequence("msgpush_demo02_category_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Demo02CategoryDO extends BaseDO {
|
||||
|
||||
public static final Long PARENT_ID_ROOT = 0L;
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 父级编号
|
||||
*/
|
||||
private Long parentId;
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03;
|
||||
|
||||
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 学生课程 DO
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@TableName("msgpush_demo03_course")
|
||||
@KeySequence("msgpush_demo03_course_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Demo03CourseDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 学生编号
|
||||
*/
|
||||
private Long studentId;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 分数
|
||||
*/
|
||||
private Integer score;
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03;
|
||||
|
||||
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 学生班级 DO
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@TableName("msgpush_demo03_grade")
|
||||
@KeySequence("msgpush_demo03_grade_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Demo03GradeDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 学生编号
|
||||
*/
|
||||
private Long studentId;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 班主任
|
||||
*/
|
||||
private String teacher;
|
||||
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03;
|
||||
|
||||
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 学生 DO
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@TableName("msgpush_demo03_student")
|
||||
@KeySequence("msgpush_demo03_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Demo03StudentDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 性别
|
||||
*
|
||||
* 枚举 {@link TODO system_user_sex 对应的类}
|
||||
*/
|
||||
private Integer sex;
|
||||
/**
|
||||
* 出生日期
|
||||
*/
|
||||
private LocalDateTime birthday;
|
||||
/**
|
||||
* 简介
|
||||
*/
|
||||
private String description;
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package com.njcn.msgpush.module.infra.dal.dataobject.file;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.njcn.msgpush.framework.common.util.json.JsonUtils;
|
||||
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
|
||||
|
||||
import com.njcn.msgpush.module.infra.framework.file.core.client.FileClientConfig;
|
||||
import com.njcn.msgpush.module.infra.framework.file.core.client.db.DBFileClientConfig;
|
||||
import com.njcn.msgpush.module.infra.framework.file.core.client.ftp.FtpFileClientConfig;
|
||||
@@ -33,7 +33,6 @@ import java.lang.reflect.Field;
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@TenantIgnore
|
||||
public class FileConfigDO extends BaseDO {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.njcn.msgpush.module.infra.dal.dataobject.file;
|
||||
|
||||
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
|
||||
|
||||
import com.njcn.msgpush.module.infra.framework.file.core.client.db.DBFileClient;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
@@ -23,7 +23,6 @@ import lombok.*;
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@TenantIgnore
|
||||
public class FileContentDO extends BaseDO {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.njcn.msgpush.module.infra.dal.dataobject.file;
|
||||
|
||||
import com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.njcn.msgpush.framework.tenant.core.aop.TenantIgnore;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
@@ -20,7 +20,6 @@ import lombok.*;
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@TenantIgnore
|
||||
public class FileDO extends BaseDO {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.dal.mysql.demo.demo01;
|
||||
|
||||
import com.njcn.msgpush.framework.common.pojo.PageResult;
|
||||
import com.njcn.msgpush.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.msgpush.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo01.vo.Demo01ContactPageReqVO;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo01.Demo01ContactDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 示例联系人 Mapper
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Mapper
|
||||
public interface Demo01ContactMapper extends BaseMapperX<Demo01ContactDO> {
|
||||
|
||||
default PageResult<Demo01ContactDO> selectPage(Demo01ContactPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<Demo01ContactDO>()
|
||||
.likeIfPresent(Demo01ContactDO::getName, reqVO.getName())
|
||||
.eqIfPresent(Demo01ContactDO::getSex, reqVO.getSex())
|
||||
.betweenIfPresent(Demo01ContactDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(Demo01ContactDO::getId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.dal.mysql.demo.demo02;
|
||||
|
||||
import com.njcn.msgpush.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.msgpush.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo02.vo.Demo02CategoryListReqVO;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo02.Demo02CategoryDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 示例分类 Mapper
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Mapper
|
||||
public interface Demo02CategoryMapper extends BaseMapperX<Demo02CategoryDO> {
|
||||
|
||||
default List<Demo02CategoryDO> selectList(Demo02CategoryListReqVO reqVO) {
|
||||
return selectList(new LambdaQueryWrapperX<Demo02CategoryDO>()
|
||||
.likeIfPresent(Demo02CategoryDO::getName, reqVO.getName())
|
||||
.eqIfPresent(Demo02CategoryDO::getParentId, reqVO.getParentId())
|
||||
.betweenIfPresent(Demo02CategoryDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(Demo02CategoryDO::getId));
|
||||
}
|
||||
|
||||
default Demo02CategoryDO selectByParentIdAndName(Long parentId, String name) {
|
||||
return selectOne(Demo02CategoryDO::getParentId, parentId, Demo02CategoryDO::getName, name);
|
||||
}
|
||||
|
||||
default Long selectCountByParentId(Long parentId) {
|
||||
return selectCount(Demo02CategoryDO::getParentId, parentId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.dal.mysql.demo.demo03.erp;
|
||||
|
||||
import com.njcn.msgpush.framework.common.pojo.PageParam;
|
||||
import com.njcn.msgpush.framework.common.pojo.PageResult;
|
||||
import com.njcn.msgpush.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.msgpush.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03CourseDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 学生课程 Mapper
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Mapper
|
||||
public interface Demo03CourseErpMapper extends BaseMapperX<Demo03CourseDO> {
|
||||
|
||||
default PageResult<Demo03CourseDO> selectPage(PageParam reqVO, Long studentId) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<Demo03CourseDO>()
|
||||
.eq(Demo03CourseDO::getStudentId, studentId)
|
||||
.orderByDesc(Demo03CourseDO::getId));
|
||||
}
|
||||
|
||||
default int deleteByStudentId(Long studentId) {
|
||||
return delete(Demo03CourseDO::getStudentId, studentId);
|
||||
}
|
||||
|
||||
default int deleteByStudentIds(List<Long> studentIds) {
|
||||
return deleteBatch(Demo03CourseDO::getStudentId, studentIds);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.dal.mysql.demo.demo03.erp;
|
||||
|
||||
import com.njcn.msgpush.framework.common.pojo.PageParam;
|
||||
import com.njcn.msgpush.framework.common.pojo.PageResult;
|
||||
import com.njcn.msgpush.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.msgpush.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03GradeDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 学生班级 Mapper
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Mapper
|
||||
public interface Demo03GradeErpMapper extends BaseMapperX<Demo03GradeDO> {
|
||||
|
||||
default PageResult<Demo03GradeDO> selectPage(PageParam reqVO, Long studentId) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<Demo03GradeDO>()
|
||||
.eq(Demo03GradeDO::getStudentId, studentId)
|
||||
.orderByDesc(Demo03GradeDO::getId));
|
||||
}
|
||||
|
||||
default Demo03GradeDO selectByStudentId(Long studentId) {
|
||||
return selectOne(Demo03GradeDO::getStudentId, studentId);
|
||||
}
|
||||
|
||||
default int deleteByStudentId(Long studentId) {
|
||||
return delete(Demo03GradeDO::getStudentId, studentId);
|
||||
}
|
||||
|
||||
default int deleteByStudentIds(List<Long> studentIds) {
|
||||
return deleteBatch(Demo03GradeDO::getStudentId, studentIds);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.dal.mysql.demo.demo03.erp;
|
||||
|
||||
import com.njcn.msgpush.framework.common.pojo.PageResult;
|
||||
import com.njcn.msgpush.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.msgpush.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo03.erp.vo.Demo03StudentErpPageReqVO;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03StudentDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 学生 Mapper
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Mapper
|
||||
public interface Demo03StudentErpMapper extends BaseMapperX<Demo03StudentDO> {
|
||||
|
||||
default PageResult<Demo03StudentDO> selectPage(Demo03StudentErpPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<Demo03StudentDO>()
|
||||
.likeIfPresent(Demo03StudentDO::getName, reqVO.getName())
|
||||
.eqIfPresent(Demo03StudentDO::getSex, reqVO.getSex())
|
||||
.eqIfPresent(Demo03StudentDO::getDescription, reqVO.getDescription())
|
||||
.betweenIfPresent(Demo03StudentDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(Demo03StudentDO::getId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.dal.mysql.demo.demo03.inner;
|
||||
|
||||
import com.njcn.msgpush.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03CourseDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 学生课程 Mapper
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Mapper
|
||||
public interface Demo03CourseInnerMapper extends BaseMapperX<Demo03CourseDO> {
|
||||
|
||||
default List<Demo03CourseDO> selectListByStudentId(Long studentId) {
|
||||
return selectList(Demo03CourseDO::getStudentId, studentId);
|
||||
}
|
||||
|
||||
default int deleteByStudentId(Long studentId) {
|
||||
return delete(Demo03CourseDO::getStudentId, studentId);
|
||||
}
|
||||
|
||||
default int deleteByStudentIds(List<Long> studentIds) {
|
||||
return deleteBatch(Demo03CourseDO::getStudentId, studentIds);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.dal.mysql.demo.demo03.inner;
|
||||
|
||||
import com.njcn.msgpush.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03GradeDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 学生班级 Mapper
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Mapper
|
||||
public interface Demo03GradeInnerMapper extends BaseMapperX<Demo03GradeDO> {
|
||||
|
||||
default Demo03GradeDO selectByStudentId(Long studentId) {
|
||||
return selectOne(Demo03GradeDO::getStudentId, studentId);
|
||||
}
|
||||
|
||||
default int deleteByStudentId(Long studentId) {
|
||||
return delete(Demo03GradeDO::getStudentId, studentId);
|
||||
}
|
||||
|
||||
default int deleteByStudentIds(List<Long> studentIds) {
|
||||
return deleteBatch(Demo03GradeDO::getStudentId, studentIds);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.dal.mysql.demo.demo03.inner;
|
||||
|
||||
import com.njcn.msgpush.framework.common.pojo.PageResult;
|
||||
import com.njcn.msgpush.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.msgpush.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo03.inner.vo.Demo03StudentInnerPageReqVO;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03StudentDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 学生 Mapper
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Mapper
|
||||
public interface Demo03StudentInnerMapper extends BaseMapperX<Demo03StudentDO> {
|
||||
|
||||
default PageResult<Demo03StudentDO> selectPage(Demo03StudentInnerPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<Demo03StudentDO>()
|
||||
.likeIfPresent(Demo03StudentDO::getName, reqVO.getName())
|
||||
.eqIfPresent(Demo03StudentDO::getSex, reqVO.getSex())
|
||||
.eqIfPresent(Demo03StudentDO::getDescription, reqVO.getDescription())
|
||||
.betweenIfPresent(Demo03StudentDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(Demo03StudentDO::getId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.dal.mysql.demo.demo03.normal;
|
||||
|
||||
import com.njcn.msgpush.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03CourseDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 学生课程 Mapper
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Mapper
|
||||
public interface Demo03CourseNormalMapper extends BaseMapperX<Demo03CourseDO> {
|
||||
|
||||
default List<Demo03CourseDO> selectListByStudentId(Long studentId) {
|
||||
return selectList(Demo03CourseDO::getStudentId, studentId);
|
||||
}
|
||||
|
||||
default int deleteByStudentId(Long studentId) {
|
||||
return delete(Demo03CourseDO::getStudentId, studentId);
|
||||
}
|
||||
|
||||
default int deleteByStudentIds(List<Long> studentIds) {
|
||||
return deleteBatch(Demo03CourseDO::getStudentId, studentIds);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.dal.mysql.demo.demo03.normal;
|
||||
|
||||
import com.njcn.msgpush.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03GradeDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 学生班级 Mapper
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Mapper
|
||||
public interface Demo03GradeNormalMapper extends BaseMapperX<Demo03GradeDO> {
|
||||
|
||||
default Demo03GradeDO selectByStudentId(Long studentId) {
|
||||
return selectOne(Demo03GradeDO::getStudentId, studentId);
|
||||
}
|
||||
|
||||
default int deleteByStudentId(Long studentId) {
|
||||
return delete(Demo03GradeDO::getStudentId, studentId);
|
||||
}
|
||||
|
||||
default int deleteByStudentIds(List<Long> studentIds) {
|
||||
return deleteBatch(Demo03GradeDO::getStudentId, studentIds);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.dal.mysql.demo.demo03.normal;
|
||||
|
||||
import com.njcn.msgpush.framework.common.pojo.PageResult;
|
||||
import com.njcn.msgpush.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.msgpush.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo03.normal.vo.Demo03StudentNormalPageReqVO;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo03.Demo03StudentDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 学生 Mapper
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
@Mapper
|
||||
public interface Demo03StudentNormalMapper extends BaseMapperX<Demo03StudentDO> {
|
||||
|
||||
default PageResult<Demo03StudentDO> selectPage(Demo03StudentNormalPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<Demo03StudentDO>()
|
||||
.likeIfPresent(Demo03StudentDO::getName, reqVO.getName())
|
||||
.eqIfPresent(Demo03StudentDO::getSex, reqVO.getSex())
|
||||
.eqIfPresent(Demo03StudentDO::getDescription, reqVO.getDescription())
|
||||
.betweenIfPresent(Demo03StudentDO::getCreateTime, reqVO.getCreateTime())
|
||||
.orderByDesc(Demo03StudentDO::getId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -59,10 +59,7 @@ public class CodegenBuilder {
|
||||
.put("date", CodegenColumnHtmlTypeEnum.DATETIME)
|
||||
.build();
|
||||
|
||||
/**
|
||||
* 多租户编号的字段名
|
||||
*/
|
||||
public static final String TENANT_ID_FIELD = "tenantId";
|
||||
|
||||
/**
|
||||
* {@link com.njcn.msgpush.framework.mybatis.core.dataobject.BaseDO} 的字段
|
||||
*/
|
||||
@@ -86,7 +83,6 @@ public class CodegenBuilder {
|
||||
|
||||
static {
|
||||
Arrays.stream(ReflectUtil.getFields(BaseDO.class)).forEach(field -> BASE_DO_FIELDS.add(field.getName()));
|
||||
BASE_DO_FIELDS.add(TENANT_ID_FIELD);
|
||||
// 处理 OPERATION 相关的字段
|
||||
CREATE_OPERATION_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);
|
||||
UPDATE_OPERATION_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package com.njcn.msgpush.module.infra.service.demo.demo01;
|
||||
|
||||
import com.njcn.msgpush.framework.common.pojo.PageResult;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo01.vo.Demo01ContactPageReqVO;
|
||||
import com.njcn.msgpush.module.infra.controller.admin.demo.demo01.vo.Demo01ContactSaveReqVO;
|
||||
import com.njcn.msgpush.module.infra.dal.dataobject.demo.demo01.Demo01ContactDO;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 示例联系人 Service 接口
|
||||
*
|
||||
* @author hongawen
|
||||
*/
|
||||
public interface Demo01ContactService {
|
||||
|
||||
/**
|
||||
* 创建示例联系人
|
||||
*
|
||||
* @param createReqVO 创建信息
|
||||
* @return 编号
|
||||
*/
|
||||
Long createDemo01Contact(@Valid Demo01ContactSaveReqVO createReqVO);
|
||||
|
||||
/**
|
||||
* 更新示例联系人
|
||||
*
|
||||
* @param updateReqVO 更新信息
|
||||
*/
|
||||
void updateDemo01Contact(@Valid Demo01ContactSaveReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 删除示例联系人
|
||||
*
|
||||
* @param id 编号
|
||||
*/
|
||||
void deleteDemo01Contact(Long id);
|
||||
|
||||
/**
|
||||
* 批量删除示例联系人
|
||||
*
|
||||
* @param ids 编号
|
||||
*/
|
||||
void deleteDemo0iContactList(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 获得示例联系人
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 示例联系人
|
||||
*/
|
||||
Demo01ContactDO getDemo01Contact(Long id);
|
||||
|
||||
/**
|
||||
* 获得示例联系人分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 示例联系人分页
|
||||
*/
|
||||
PageResult<Demo01ContactDO> getDemo01ContactPage(Demo01ContactPageReqVO pageReqVO);
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user