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