Commit 7c8fe2fc50dcdc6f625d7420f7c9911f70eaf822
1 parent
ccb56b3b
1. 增加 yudao-spring-boot-starter-tenant 租户的组件
2. 改造 UserDO,接入多租户
Showing
35 changed files
with
426 additions
and
15 deletions
yudao-admin-server/pom.xml
| @@ -118,6 +118,11 @@ | @@ -118,6 +118,11 @@ | ||
| 118 | </dependency> | 118 | </dependency> |
| 119 | 119 | ||
| 120 | <dependency> | 120 | <dependency> |
| 121 | + <groupId>cn.iocoder.boot</groupId> | ||
| 122 | + <artifactId>yudao-spring-boot-starter-tenant</artifactId> | ||
| 123 | + </dependency> | ||
| 124 | + | ||
| 125 | + <dependency> | ||
| 121 | <groupId>org.apache.velocity</groupId> | 126 | <groupId>org.apache.velocity</groupId> |
| 122 | <artifactId>velocity-engine-core</artifactId> | 127 | <artifactId>velocity-engine-core</artifactId> |
| 123 | </dependency> | 128 | </dependency> |
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/SysAuthController.http
| 1 | ### 请求 /login 接口 => 成功 | 1 | ### 请求 /login 接口 => 成功 |
| 2 | POST {{baseUrl}}/login | 2 | POST {{baseUrl}}/login |
| 3 | Content-Type: application/json | 3 | Content-Type: application/json |
| 4 | +tenant-id: 0 | ||
| 4 | 5 | ||
| 5 | { | 6 | { |
| 6 | "username": "admin", | 7 | "username": "admin", |
| 7 | "password": "admin123", | 8 | "password": "admin123", |
| 8 | - "uuid": "9b2ffbc1-7425-4155-9894-9d5c08541d62", | 9 | + "uuid": "3acd87a09a4f48fb9118333780e94883", |
| 9 | "code": "1024" | 10 | "code": "1024" |
| 10 | } | 11 | } |
| 11 | 12 |
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/framework/captcha/config/CaptchaProperties.java
| @@ -12,6 +12,13 @@ import java.time.Duration; | @@ -12,6 +12,13 @@ import java.time.Duration; | ||
| 12 | @Data | 12 | @Data |
| 13 | public class CaptchaProperties { | 13 | public class CaptchaProperties { |
| 14 | 14 | ||
| 15 | + private static final Boolean ENABLE_DEFAULT = true; | ||
| 16 | + | ||
| 17 | + /** | ||
| 18 | + * 是否开启 | ||
| 19 | + * 注意,这里仅仅是后端 Server 是否校验,暂时不控制前端的逻辑 | ||
| 20 | + */ | ||
| 21 | + private Boolean enable = ENABLE_DEFAULT; | ||
| 15 | /** | 22 | /** |
| 16 | * 验证码的过期时间 | 23 | * 验证码的过期时间 |
| 17 | */ | 24 | */ |
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/impl/SysAuthServiceImpl.java
| @@ -133,9 +133,13 @@ public class SysAuthServiceImpl implements SysAuthService { | @@ -133,9 +133,13 @@ public class SysAuthServiceImpl implements SysAuthService { | ||
| 133 | } | 133 | } |
| 134 | 134 | ||
| 135 | private void verifyCaptcha(String username, String captchaUUID, String captchaCode) { | 135 | private void verifyCaptcha(String username, String captchaUUID, String captchaCode) { |
| 136 | + // 如果验证码关闭,则不进行校验 | ||
| 137 | + if (!captchaService.isCaptchaEnable()) { | ||
| 138 | + return; | ||
| 139 | + } | ||
| 140 | + // 验证码不存在 | ||
| 136 | final SysLoginLogTypeEnum logTypeEnum = SysLoginLogTypeEnum.LOGIN_USERNAME; | 141 | final SysLoginLogTypeEnum logTypeEnum = SysLoginLogTypeEnum.LOGIN_USERNAME; |
| 137 | String code = captchaService.getCaptchaCode(captchaUUID); | 142 | String code = captchaService.getCaptchaCode(captchaUUID); |
| 138 | - // 验证码不存在 | ||
| 139 | if (code == null) { | 143 | if (code == null) { |
| 140 | // 创建登录失败日志(验证码不存在) | 144 | // 创建登录失败日志(验证码不存在) |
| 141 | this.createLoginLog(username, logTypeEnum, SysLoginResultEnum.CAPTCHA_NOT_FOUND); | 145 | this.createLoginLog(username, logTypeEnum, SysLoginResultEnum.CAPTCHA_NOT_FOUND); |
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/common/SysCaptchaService.java
| @@ -15,6 +15,13 @@ public interface SysCaptchaService { | @@ -15,6 +15,13 @@ public interface SysCaptchaService { | ||
| 15 | SysCaptchaImageRespVO getCaptchaImage(); | 15 | SysCaptchaImageRespVO getCaptchaImage(); |
| 16 | 16 | ||
| 17 | /** | 17 | /** |
| 18 | + * 是否开启图片验证码 | ||
| 19 | + * | ||
| 20 | + * @return 是否 | ||
| 21 | + */ | ||
| 22 | + Boolean isCaptchaEnable(); | ||
| 23 | + | ||
| 24 | + /** | ||
| 18 | * 获得 uuid 对应的验证码 | 25 | * 获得 uuid 对应的验证码 |
| 19 | * | 26 | * |
| 20 | * @param uuid 验证码编号 | 27 | * @param uuid 验证码编号 |
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/common/impl/SysCaptchaServiceImpl.java
| @@ -36,6 +36,11 @@ public class SysCaptchaServiceImpl implements SysCaptchaService { | @@ -36,6 +36,11 @@ public class SysCaptchaServiceImpl implements SysCaptchaService { | ||
| 36 | } | 36 | } |
| 37 | 37 | ||
| 38 | @Override | 38 | @Override |
| 39 | + public Boolean isCaptchaEnable() { | ||
| 40 | + return captchaProperties.getEnable(); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + @Override | ||
| 39 | public String getCaptchaCode(String uuid) { | 44 | public String getCaptchaCode(String uuid) { |
| 40 | return captchaRedisDAO.get(uuid); | 45 | return captchaRedisDAO.get(uuid); |
| 41 | } | 46 | } |
yudao-admin-server/src/main/resources/application-local.yaml
| @@ -166,6 +166,8 @@ logging: | @@ -166,6 +166,8 @@ logging: | ||
| 166 | 166 | ||
| 167 | # 芋道配置项,设置当前项目所有自定义的配置 | 167 | # 芋道配置项,设置当前项目所有自定义的配置 |
| 168 | yudao: | 168 | yudao: |
| 169 | + captcha: | ||
| 170 | + enable: false # 本地环境,暂时关闭图片验证码,方便登录等接口的测试 | ||
| 169 | security: | 171 | security: |
| 170 | token-header: Authorization | 172 | token-header: Authorization |
| 171 | token-secret: abcdefghijklmnopqrstuvwxyz | 173 | token-secret: abcdefghijklmnopqrstuvwxyz |
yudao-admin-server/src/main/resources/application.yaml
| @@ -73,5 +73,7 @@ yudao: | @@ -73,5 +73,7 @@ yudao: | ||
| 73 | constants-class-list: | 73 | constants-class-list: |
| 74 | - cn.iocoder.yudao.adminserver.modules.infra.enums.InfErrorCodeConstants | 74 | - cn.iocoder.yudao.adminserver.modules.infra.enums.InfErrorCodeConstants |
| 75 | - cn.iocoder.yudao.adminserver.modules.system.enums.SysErrorCodeConstants | 75 | - cn.iocoder.yudao.adminserver.modules.system.enums.SysErrorCodeConstants |
| 76 | + tenant: | ||
| 77 | + tables: sys_user | ||
| 76 | 78 | ||
| 77 | debug: false | 79 | debug: false |
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/SysAuthServiceImplTest.java
| @@ -17,6 +17,7 @@ import cn.iocoder.yudao.coreservice.modules.system.service.user.SysUserCoreServi | @@ -17,6 +17,7 @@ import cn.iocoder.yudao.coreservice.modules.system.service.user.SysUserCoreServi | ||
| 17 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | 17 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 18 | import cn.iocoder.yudao.framework.security.core.LoginUser; | 18 | import cn.iocoder.yudao.framework.security.core.LoginUser; |
| 19 | import cn.iocoder.yudao.framework.test.core.util.AssertUtils; | 19 | import cn.iocoder.yudao.framework.test.core.util.AssertUtils; |
| 20 | +import org.junit.jupiter.api.BeforeEach; | ||
| 20 | import org.junit.jupiter.api.Test; | 21 | import org.junit.jupiter.api.Test; |
| 21 | import org.springframework.boot.test.mock.mockito.MockBean; | 22 | import org.springframework.boot.test.mock.mockito.MockBean; |
| 22 | import org.springframework.context.annotation.Import; | 23 | import org.springframework.context.annotation.Import; |
| @@ -71,6 +72,11 @@ public class SysAuthServiceImplTest extends BaseDbUnitTest { | @@ -71,6 +72,11 @@ public class SysAuthServiceImplTest extends BaseDbUnitTest { | ||
| 71 | @MockBean | 72 | @MockBean |
| 72 | private SysPostService postService; | 73 | private SysPostService postService; |
| 73 | 74 | ||
| 75 | + @BeforeEach | ||
| 76 | + public void setUp() { | ||
| 77 | + when(captchaService.isCaptchaEnable()).thenReturn(true); | ||
| 78 | + } | ||
| 79 | + | ||
| 74 | @Test | 80 | @Test |
| 75 | public void testLoadUserByUsername_success() { | 81 | public void testLoadUserByUsername_success() { |
| 76 | // 准备参数 | 82 | // 准备参数 |
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/user/SysUserServiceImplTest.java
| @@ -21,6 +21,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | @@ -21,6 +21,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 21 | import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; | 21 | import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; |
| 22 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | 22 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 23 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | 23 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| 24 | +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | ||
| 24 | import org.junit.jupiter.api.Test; | 25 | import org.junit.jupiter.api.Test; |
| 25 | import org.mockito.stubbing.Answer; | 26 | import org.mockito.stubbing.Answer; |
| 26 | import org.springframework.boot.test.mock.mockito.MockBean; | 27 | import org.springframework.boot.test.mock.mockito.MockBean; |
yudao-admin-server/src/test/resources/sql/create_tables.sql
| @@ -287,6 +287,7 @@ CREATE TABLE IF NOT EXISTS "sys_user" ( | @@ -287,6 +287,7 @@ CREATE TABLE IF NOT EXISTS "sys_user" ( | ||
| 287 | "updater" varchar(64) default '', | 287 | "updater" varchar(64) default '', |
| 288 | "update_time" timestamp not null default current_timestamp, | 288 | "update_time" timestamp not null default current_timestamp, |
| 289 | "deleted" bit not null default false, | 289 | "deleted" bit not null default false, |
| 290 | + "tenant_id" bigint not null default '0', | ||
| 290 | primary key ("id") | 291 | primary key ("id") |
| 291 | ) comment '用户信息表'; | 292 | ) comment '用户信息表'; |
| 292 | 293 |
yudao-core-service/pom.xml
| @@ -92,6 +92,11 @@ | @@ -92,6 +92,11 @@ | ||
| 92 | 92 | ||
| 93 | <!-- 工具类相关 --> | 93 | <!-- 工具类相关 --> |
| 94 | <dependency> | 94 | <dependency> |
| 95 | + <groupId>cn.iocoder.boot</groupId> | ||
| 96 | + <artifactId>yudao-spring-boot-starter-tenant</artifactId> | ||
| 97 | + </dependency> | ||
| 98 | + | ||
| 99 | + <dependency> | ||
| 95 | <groupId>com.google.guava</groupId> | 100 | <groupId>com.google.guava</groupId> |
| 96 | <artifactId>guava</artifactId> | 101 | <artifactId>guava</artifactId> |
| 97 | </dependency> | 102 | </dependency> |
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/system/dal/dataobject/user/SysUserDO.java
| @@ -2,8 +2,8 @@ package cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user; | @@ -2,8 +2,8 @@ package cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.user; | ||
| 2 | 2 | ||
| 3 | import cn.iocoder.yudao.coreservice.modules.system.enums.common.SysSexEnum; | 3 | import cn.iocoder.yudao.coreservice.modules.system.enums.common.SysSexEnum; |
| 4 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | 4 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 5 | -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||
| 6 | import cn.iocoder.yudao.framework.mybatis.core.type.JsonLongSetTypeHandler; | 5 | import cn.iocoder.yudao.framework.mybatis.core.type.JsonLongSetTypeHandler; |
| 6 | +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; | ||
| 7 | import com.baomidou.mybatisplus.annotation.TableField; | 7 | import com.baomidou.mybatisplus.annotation.TableField; |
| 8 | import com.baomidou.mybatisplus.annotation.TableId; | 8 | import com.baomidou.mybatisplus.annotation.TableId; |
| 9 | import com.baomidou.mybatisplus.annotation.TableName; | 9 | import com.baomidou.mybatisplus.annotation.TableName; |
| @@ -24,7 +24,7 @@ import java.util.Set; | @@ -24,7 +24,7 @@ import java.util.Set; | ||
| 24 | @Builder | 24 | @Builder |
| 25 | @NoArgsConstructor | 25 | @NoArgsConstructor |
| 26 | @AllArgsConstructor | 26 | @AllArgsConstructor |
| 27 | -public class SysUserDO extends BaseDO { | 27 | +public class SysUserDO extends TenantBaseDO { |
| 28 | 28 | ||
| 29 | /** | 29 | /** |
| 30 | * 用户ID | 30 | * 用户ID |
yudao-dependencies/pom.xml
| @@ -48,6 +48,7 @@ | @@ -48,6 +48,7 @@ | ||
| 48 | <velocity.version>2.2</velocity.version> | 48 | <velocity.version>2.2</velocity.version> |
| 49 | <screw.version>1.0.5</screw.version> | 49 | <screw.version>1.0.5</screw.version> |
| 50 | <guava.version>30.1.1-jre</guava.version> | 50 | <guava.version>30.1.1-jre</guava.version> |
| 51 | + <transmittable-thread-local.version>2.12.2</transmittable-thread-local.version> | ||
| 51 | <!-- 三方云服务相关 --> | 52 | <!-- 三方云服务相关 --> |
| 52 | <aliyun-java-sdk-core.version>4.5.25</aliyun-java-sdk-core.version> | 53 | <aliyun-java-sdk-core.version>4.5.25</aliyun-java-sdk-core.version> |
| 53 | <aliyun-java-sdk-dysmsapi.version>2.1.0</aliyun-java-sdk-dysmsapi.version> | 54 | <aliyun-java-sdk-dysmsapi.version>2.1.0</aliyun-java-sdk-dysmsapi.version> |
| @@ -351,6 +352,12 @@ | @@ -351,6 +352,12 @@ | ||
| 351 | </dependency> | 352 | </dependency> |
| 352 | 353 | ||
| 353 | <dependency> | 354 | <dependency> |
| 355 | + <groupId>cn.iocoder.boot</groupId> | ||
| 356 | + <artifactId>yudao-spring-boot-starter-tenant</artifactId> | ||
| 357 | + <version>${revision}</version> | ||
| 358 | + </dependency> | ||
| 359 | + | ||
| 360 | + <dependency> | ||
| 354 | <groupId>org.projectlombok</groupId> | 361 | <groupId>org.projectlombok</groupId> |
| 355 | <artifactId>lombok</artifactId> | 362 | <artifactId>lombok</artifactId> |
| 356 | <version>${lombok.version}</version> | 363 | <version>${lombok.version}</version> |
| @@ -402,6 +409,12 @@ | @@ -402,6 +409,12 @@ | ||
| 402 | <version>${guava.version}</version> | 409 | <version>${guava.version}</version> |
| 403 | </dependency> | 410 | </dependency> |
| 404 | 411 | ||
| 412 | + <dependency> | ||
| 413 | + <groupId>com.alibaba</groupId> | ||
| 414 | + <artifactId>transmittable-thread-local</artifactId> <!-- 解决 ThreadLocal 父子线程的传值问题 --> | ||
| 415 | + <version>${transmittable-thread-local.version}</version> | ||
| 416 | + </dependency> | ||
| 417 | + | ||
| 405 | <!-- 三方云服务相关 --> | 418 | <!-- 三方云服务相关 --> |
| 406 | 419 | ||
| 407 | <!-- SMS SDK begin --> | 420 | <!-- SMS SDK begin --> |
yudao-framework/pom.xml
| @@ -32,6 +32,7 @@ | @@ -32,6 +32,7 @@ | ||
| 32 | <module>yudao-spring-boot-starter-biz-pay</module> | 32 | <module>yudao-spring-boot-starter-biz-pay</module> |
| 33 | <module>yudao-spring-boot-starter-biz-weixin</module> | 33 | <module>yudao-spring-boot-starter-biz-weixin</module> |
| 34 | <module>yudao-spring-boot-starter-extension</module> | 34 | <module>yudao-spring-boot-starter-extension</module> |
| 35 | + <module>yudao-spring-boot-starter-tenant</module> | ||
| 35 | </modules> | 36 | </modules> |
| 36 | 37 | ||
| 37 | <artifactId>yudao-framework</artifactId> | 38 | <artifactId>yudao-framework</artifactId> |
yudao-framework/yudao-common/pom.xml
| @@ -122,6 +122,17 @@ | @@ -122,6 +122,17 @@ | ||
| 122 | <artifactId>jakarta.validation-api</artifactId> | 122 | <artifactId>jakarta.validation-api</artifactId> |
| 123 | <scope>provided</scope> <!-- 设置为 provided,主要是 PageParam 使用到 --> | 123 | <scope>provided</scope> <!-- 设置为 provided,主要是 PageParam 使用到 --> |
| 124 | </dependency> | 124 | </dependency> |
| 125 | + | ||
| 126 | + <dependency> | ||
| 127 | + <groupId>cn.hutool</groupId> | ||
| 128 | + <artifactId>hutool-all</artifactId> | ||
| 129 | + </dependency> | ||
| 130 | + | ||
| 131 | + <dependency> | ||
| 132 | + <groupId>com.alibaba</groupId> | ||
| 133 | + <artifactId>transmittable-thread-local</artifactId> | ||
| 134 | + </dependency> | ||
| 135 | + | ||
| 125 | </dependencies> | 136 | </dependencies> |
| 126 | 137 | ||
| 127 | </project> | 138 | </project> |
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/enums/WebFilterOrderEnum.java
| @@ -17,9 +17,11 @@ public interface WebFilterOrderEnum { | @@ -17,9 +17,11 @@ public interface WebFilterOrderEnum { | ||
| 17 | 17 | ||
| 18 | // OrderedRequestContextFilter 默认为 -105,用于国际化上下文等等 | 18 | // OrderedRequestContextFilter 默认为 -105,用于国际化上下文等等 |
| 19 | 19 | ||
| 20 | - int API_ACCESS_LOG_FILTER = -104; // 需要保证在 RequestBodyCacheFilter 后面 | 20 | + int TENANT_FILTER = - 100; // 需要保证在 ApiAccessLogFilter 前面 |
| 21 | 21 | ||
| 22 | - int XSS_FILTER = -103; // 需要保证在 RequestBodyCacheFilter 后面 | 22 | + int API_ACCESS_LOG_FILTER = -90; // 需要保证在 RequestBodyCacheFilter 后面 |
| 23 | + | ||
| 24 | + int XSS_FILTER = -80; // 需要保证在 RequestBodyCacheFilter 后面 | ||
| 23 | 25 | ||
| 24 | // Spring Security Filter 默认为 -100,可见 SecurityProperties 配置属性类 | 26 | // Spring Security Filter 默认为 -100,可见 SecurityProperties 配置属性类 |
| 25 | 27 |
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/dataobject/BaseDO.java
| @@ -10,9 +10,11 @@ import java.util.Date; | @@ -10,9 +10,11 @@ import java.util.Date; | ||
| 10 | 10 | ||
| 11 | /** | 11 | /** |
| 12 | * 基础实体对象 | 12 | * 基础实体对象 |
| 13 | + * | ||
| 14 | + * @author 芋道源码 | ||
| 13 | */ | 15 | */ |
| 14 | @Data | 16 | @Data |
| 15 | -public class BaseDO implements Serializable { | 17 | +public abstract class BaseDO implements Serializable { |
| 16 | 18 | ||
| 17 | /** | 19 | /** |
| 18 | * 创建时间 | 20 | * 创建时间 |
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java
| @@ -4,9 +4,13 @@ import cn.hutool.core.collection.CollectionUtil; | @@ -4,9 +4,13 @@ import cn.hutool.core.collection.CollectionUtil; | ||
| 4 | import cn.iocoder.yudao.framework.common.pojo.PageParam; | 4 | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| 5 | import cn.iocoder.yudao.framework.common.pojo.SortingField; | 5 | import cn.iocoder.yudao.framework.common.pojo.SortingField; |
| 6 | import com.baomidou.mybatisplus.core.metadata.OrderItem; | 6 | import com.baomidou.mybatisplus.core.metadata.OrderItem; |
| 7 | +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; | ||
| 8 | +import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; | ||
| 7 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | 9 | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| 8 | 10 | ||
| 11 | +import java.util.ArrayList; | ||
| 9 | import java.util.Collection; | 12 | import java.util.Collection; |
| 13 | +import java.util.List; | ||
| 10 | import java.util.stream.Collectors; | 14 | import java.util.stream.Collectors; |
| 11 | 15 | ||
| 12 | /** | 16 | /** |
| @@ -30,4 +34,17 @@ public class MyBatisUtils { | @@ -30,4 +34,17 @@ public class MyBatisUtils { | ||
| 30 | return page; | 34 | return page; |
| 31 | } | 35 | } |
| 32 | 36 | ||
| 37 | + /** | ||
| 38 | + * 将拦截器添加到链中 | ||
| 39 | + * 由于 MybatisPlusInterceptor 不支持添加拦截器,所以只能全量设置 | ||
| 40 | + * | ||
| 41 | + * @param interceptor 链 | ||
| 42 | + * @param inner 拦截器 | ||
| 43 | + */ | ||
| 44 | + public static void addInterceptor(MybatisPlusInterceptor interceptor, InnerInterceptor inner) { | ||
| 45 | + List<InnerInterceptor> inners = new ArrayList<>(interceptor.getInterceptors()); | ||
| 46 | + inners.add(0, inner); | ||
| 47 | + interceptor.setInterceptors(inners); | ||
| 48 | + } | ||
| 49 | + | ||
| 33 | } | 50 | } |
yudao-framework/yudao-spring-boot-starter-tenant/pom.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
| 2 | +<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
| 3 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| 4 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| 5 | + <parent> | ||
| 6 | + <artifactId>yudao-framework</artifactId> | ||
| 7 | + <groupId>cn.iocoder.boot</groupId> | ||
| 8 | + <version>${revision}</version> | ||
| 9 | + </parent> | ||
| 10 | + <modelVersion>4.0.0</modelVersion> | ||
| 11 | + <artifactId>yudao-spring-boot-starter-tenant</artifactId> | ||
| 12 | + <packaging>jar</packaging> | ||
| 13 | + | ||
| 14 | + <name>${artifactId}</name> | ||
| 15 | + <description>多租户</description> | ||
| 16 | + <url>https://github.com/YunaiV/ruoyi-vue-pro</url> | ||
| 17 | + | ||
| 18 | + <dependencies> | ||
| 19 | + <dependency> | ||
| 20 | + <groupId>cn.iocoder.boot</groupId> | ||
| 21 | + <artifactId>yudao-common</artifactId> | ||
| 22 | + </dependency> | ||
| 23 | + | ||
| 24 | + <!-- Web 相关 --> | ||
| 25 | + <dependency> | ||
| 26 | + <groupId>org.springframework.boot</groupId> | ||
| 27 | + <artifactId>spring-boot-starter-web</artifactId> | ||
| 28 | + </dependency> | ||
| 29 | + | ||
| 30 | + <!-- DB 相关 --> | ||
| 31 | + <dependency> | ||
| 32 | + <groupId>cn.iocoder.boot</groupId> | ||
| 33 | + <artifactId>yudao-spring-boot-starter-mybatis</artifactId> | ||
| 34 | + </dependency> | ||
| 35 | + </dependencies> | ||
| 36 | + | ||
| 37 | +</project> |
yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/TenantProperties.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.tenant.config; | ||
| 2 | + | ||
| 3 | +import lombok.Data; | ||
| 4 | +import org.springframework.boot.context.properties.ConfigurationProperties; | ||
| 5 | + | ||
| 6 | +import java.util.Set; | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * 多租户配置 | ||
| 10 | + * | ||
| 11 | + * @author 芋道源码 | ||
| 12 | + */ | ||
| 13 | +@ConfigurationProperties(prefix = "yudao.tenant") | ||
| 14 | +@Data | ||
| 15 | +public class TenantProperties { | ||
| 16 | + | ||
| 17 | + /** | ||
| 18 | + * 需要多租户的表 | ||
| 19 | + * | ||
| 20 | + * 由于多租户并不作为 yudao 项目的重点功能,更多是扩展性的功能,所以采用正向配置需要多租户的表。 | ||
| 21 | + * 如果需要,你可以改成 ignoreTables 来取消部分不需要的表 | ||
| 22 | + */ | ||
| 23 | + private Set<String> tables; | ||
| 24 | + | ||
| 25 | +} |
yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantDatabaseAutoConfiguration.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.tenant.config; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils; | ||
| 4 | +import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor; | ||
| 5 | +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; | ||
| 6 | +import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; | ||
| 7 | +import org.springframework.beans.BeansException; | ||
| 8 | +import org.springframework.beans.factory.config.BeanPostProcessor; | ||
| 9 | +import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||
| 10 | +import org.springframework.context.annotation.Bean; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * 多租户针对 DB 的自动配置 | ||
| 14 | + * | ||
| 15 | + * @author 芋道源码 | ||
| 16 | + */ | ||
| 17 | +@EnableConfigurationProperties(TenantProperties.class) | ||
| 18 | +public class YudaoTenantDatabaseAutoConfiguration { | ||
| 19 | + | ||
| 20 | + @Bean | ||
| 21 | + public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties properties) { | ||
| 22 | + return new TenantLineInnerInterceptor(new TenantDatabaseInterceptor(properties)); | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + @Bean | ||
| 26 | + public BeanPostProcessor mybatisPlusInterceptorBeanPostProcessor(TenantLineInnerInterceptor tenantLineInnerInterceptor) { | ||
| 27 | + return new BeanPostProcessor() { | ||
| 28 | + | ||
| 29 | + @Override | ||
| 30 | + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { | ||
| 31 | + if (!(bean instanceof MybatisPlusInterceptor)) { | ||
| 32 | + return bean; | ||
| 33 | + } | ||
| 34 | + // 将 TenantDatabaseInterceptor 添加到最前面 | ||
| 35 | + MybatisPlusInterceptor interceptor = (MybatisPlusInterceptor) bean; | ||
| 36 | + MyBatisUtils.addInterceptor(interceptor, tenantLineInnerInterceptor); | ||
| 37 | + return bean; | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + }; | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | +} |
yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantWebAutoConfiguration.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.tenant.config; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum; | ||
| 4 | +import cn.iocoder.yudao.framework.tenant.core.web.TenantWebFilter; | ||
| 5 | +import org.springframework.boot.web.servlet.FilterRegistrationBean; | ||
| 6 | +import org.springframework.context.annotation.Bean; | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * 多租户针对 Web 的自动配置 | ||
| 10 | + * | ||
| 11 | + * @author 芋道源码 | ||
| 12 | + */ | ||
| 13 | +public class YudaoTenantWebAutoConfiguration { | ||
| 14 | + | ||
| 15 | + @Bean | ||
| 16 | + public FilterRegistrationBean<TenantWebFilter> tenantWebFilter() { | ||
| 17 | + FilterRegistrationBean<TenantWebFilter> registrationBean = new FilterRegistrationBean<>(); | ||
| 18 | + registrationBean.setFilter(new TenantWebFilter()); | ||
| 19 | + registrationBean.setOrder(WebFilterOrderEnum.TENANT_FILTER); | ||
| 20 | + return registrationBean; | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | +} |
yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/context/TenantContextHolder.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.tenant.core.context; | ||
| 2 | + | ||
| 3 | +import com.alibaba.ttl.TransmittableThreadLocal; | ||
| 4 | + | ||
| 5 | +/** | ||
| 6 | + * 多租户上下文 Holder | ||
| 7 | + * | ||
| 8 | + * @author 芋道源码 | ||
| 9 | + */ | ||
| 10 | +public class TenantContextHolder { | ||
| 11 | + | ||
| 12 | + private static final ThreadLocal<Long> TENANT_ID = new TransmittableThreadLocal<>(); | ||
| 13 | + | ||
| 14 | + public static Long getTenantId() { | ||
| 15 | + return TENANT_ID.get(); | ||
| 16 | + } | ||
| 17 | + | ||
| 18 | + public static void setTenantId(Long tenantId) { | ||
| 19 | + TENANT_ID.set(tenantId); | ||
| 20 | + } | ||
| 21 | + | ||
| 22 | + public static void clear() { | ||
| 23 | + TENANT_ID.remove(); | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | +} |
yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/db/TenantBaseDO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.tenant.core.db; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||
| 4 | +import lombok.Data; | ||
| 5 | +import lombok.EqualsAndHashCode; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * 拓展多租户的 BaseDO 基类 | ||
| 9 | + * | ||
| 10 | + * @author 芋道源码 | ||
| 11 | + */ | ||
| 12 | +@Data | ||
| 13 | +@EqualsAndHashCode(callSuper = true) | ||
| 14 | +public abstract class TenantBaseDO extends BaseDO { | ||
| 15 | + | ||
| 16 | + /** | ||
| 17 | + * 多租户编号 | ||
| 18 | + */ | ||
| 19 | + private Long tenantId; | ||
| 20 | + | ||
| 21 | +} |
yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/db/TenantDatabaseInterceptor.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.tenant.core.db; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 4 | +import cn.iocoder.yudao.framework.tenant.config.TenantProperties; | ||
| 5 | +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | ||
| 6 | +import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; | ||
| 7 | +import lombok.AllArgsConstructor; | ||
| 8 | +import net.sf.jsqlparser.expression.Expression; | ||
| 9 | +import net.sf.jsqlparser.expression.StringValue; | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * 基于 MyBatis Plus 多租户的功能,实现 DB 层面的多租户的功能 | ||
| 13 | + * | ||
| 14 | + * @author 芋道源码 | ||
| 15 | + */ | ||
| 16 | +@AllArgsConstructor | ||
| 17 | +public class TenantDatabaseInterceptor implements TenantLineHandler { | ||
| 18 | + | ||
| 19 | + private final TenantProperties properties; | ||
| 20 | + | ||
| 21 | + @Override | ||
| 22 | + public Expression getTenantId() { | ||
| 23 | + // TODO 芋艿:暂时不考虑获取不到的情况。此时,会存在 NPE 的报错 | ||
| 24 | + return new StringValue(TenantContextHolder.getTenantId().toString()); | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + @Override | ||
| 28 | + public boolean ignoreTable(String tableName) { | ||
| 29 | + // 不包含,说明要过滤 | ||
| 30 | + return !CollUtil.contains(properties.getTables(), tableName); | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | +} |
yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/web/TenantWebFilter.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.tenant.core.web; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.util.StrUtil; | ||
| 4 | +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | ||
| 5 | +import org.springframework.web.filter.OncePerRequestFilter; | ||
| 6 | + | ||
| 7 | +import javax.servlet.FilterChain; | ||
| 8 | +import javax.servlet.ServletException; | ||
| 9 | +import javax.servlet.http.HttpServletRequest; | ||
| 10 | +import javax.servlet.http.HttpServletResponse; | ||
| 11 | +import java.io.IOException; | ||
| 12 | + | ||
| 13 | +/** | ||
| 14 | + * 多租户 Web 过滤器 | ||
| 15 | + * 将请求 Header 中的 tenant-id 解析出来,添加到 {@link TenantContextHolder} 中,这样后续的 DB 等操作,可以获得到租户编号 | ||
| 16 | + * | ||
| 17 | + * @author 芋道源码 | ||
| 18 | + */ | ||
| 19 | +public class TenantWebFilter extends OncePerRequestFilter { | ||
| 20 | + | ||
| 21 | + private static final String HEADER_TENANT_ID = "tenant-id"; | ||
| 22 | + | ||
| 23 | + @Override | ||
| 24 | + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) | ||
| 25 | + throws ServletException, IOException { | ||
| 26 | + // 设置 | ||
| 27 | + String tenantId = request.getHeader(HEADER_TENANT_ID); | ||
| 28 | + if (StrUtil.isNotEmpty(tenantId)) { | ||
| 29 | + TenantContextHolder.setTenantId(Long.valueOf(tenantId)); | ||
| 30 | + } | ||
| 31 | + try { | ||
| 32 | + chain.doFilter(request, response); | ||
| 33 | + } finally { | ||
| 34 | + // 清理 | ||
| 35 | + TenantContextHolder.clear(); | ||
| 36 | + } | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | +} |
yudao-framework/yudao-spring-boot-starter-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/package-info.java
0 → 100644
yudao-framework/yudao-spring-boot-starter-tenant/src/main/resources/META-INF/spring.factories
0 → 100644
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/controller/user/SysUserProfileController.java
| @@ -41,8 +41,8 @@ public class SysUserProfileController { | @@ -41,8 +41,8 @@ public class SysUserProfileController { | ||
| 41 | @PutMapping("/update-nickname") | 41 | @PutMapping("/update-nickname") |
| 42 | @ApiOperation("修改用户昵称") | 42 | @ApiOperation("修改用户昵称") |
| 43 | @PreAuthenticated | 43 | @PreAuthenticated |
| 44 | - public CommonResult<Boolean> updateNickname(@RequestParam("nickName") String nickName) { | ||
| 45 | - userService.updateNickname(getLoginUserId(), nickName); | 44 | + public CommonResult<Boolean> updateNickname(@RequestParam("nickname") String nickname) { |
| 45 | + userService.updateNickname(getLoginUserId(), nickname); | ||
| 46 | return success(true); | 46 | return success(true); |
| 47 | } | 47 | } |
| 48 | 48 |
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/service/user/MbrUserService.java
| @@ -51,9 +51,9 @@ public interface MbrUserService { | @@ -51,9 +51,9 @@ public interface MbrUserService { | ||
| 51 | /** | 51 | /** |
| 52 | * 修改用户昵称 | 52 | * 修改用户昵称 |
| 53 | * @param userId 用户id | 53 | * @param userId 用户id |
| 54 | - * @param nickName 用户新昵称 | 54 | + * @param nickname 用户新昵称 |
| 55 | */ | 55 | */ |
| 56 | - void updateNickname(Long userId, String nickName); | 56 | + void updateNickname(Long userId, String nickname); |
| 57 | 57 | ||
| 58 | /** | 58 | /** |
| 59 | * 修改用户头像 | 59 | * 修改用户头像 |
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/member/service/user/impl/MbrUserServiceImpl.java
| @@ -86,15 +86,15 @@ public class MbrUserServiceImpl implements MbrUserService { | @@ -86,15 +86,15 @@ public class MbrUserServiceImpl implements MbrUserService { | ||
| 86 | } | 86 | } |
| 87 | 87 | ||
| 88 | @Override | 88 | @Override |
| 89 | - public void updateNickname(Long userId, String nickName) { | 89 | + public void updateNickname(Long userId, String nickname) { |
| 90 | MbrUserDO user = this.checkUserExists(userId); | 90 | MbrUserDO user = this.checkUserExists(userId); |
| 91 | // 仅当新昵称不等于旧昵称时进行修改 | 91 | // 仅当新昵称不等于旧昵称时进行修改 |
| 92 | - if (nickName.equals(user.getNickname())){ | 92 | + if (nickname.equals(user.getNickname())){ |
| 93 | return; | 93 | return; |
| 94 | } | 94 | } |
| 95 | MbrUserDO userDO = new MbrUserDO(); | 95 | MbrUserDO userDO = new MbrUserDO(); |
| 96 | userDO.setId(user.getId()); | 96 | userDO.setId(user.getId()); |
| 97 | - userDO.setNickname(nickName); | 97 | + userDO.setNickname(nickname); |
| 98 | userMapper.updateById(userDO); | 98 | userMapper.updateById(userDO); |
| 99 | } | 99 | } |
| 100 | 100 |
yudao-vue-ui/api/member/userProfile.js
| @@ -6,4 +6,18 @@ export function getUserInfo() { | @@ -6,4 +6,18 @@ export function getUserInfo() { | ||
| 6 | url: 'member/user/profile/get', | 6 | url: 'member/user/profile/get', |
| 7 | method: 'get' | 7 | method: 'get' |
| 8 | }) | 8 | }) |
| 9 | +} | ||
| 10 | + | ||
| 11 | +// 修改 | ||
| 12 | +export function updateNickname(nickname) { | ||
| 13 | + return request({ | ||
| 14 | + url: 'member/user/profile/update-nickname', | ||
| 15 | + method: 'post', | ||
| 16 | + header: { | ||
| 17 | + "Content-Type": "application/x-www-form-urlencoded" | ||
| 18 | + }, | ||
| 19 | + data: { | ||
| 20 | + nickname | ||
| 21 | + } | ||
| 22 | + }) | ||
| 9 | } | 23 | } |
| 10 | \ No newline at end of file | 24 | \ No newline at end of file |
yudao-vue-ui/common/js/request.js
| @@ -12,6 +12,7 @@ export const request = (options) => { | @@ -12,6 +12,7 @@ export const request = (options) => { | ||
| 12 | method: options.method || 'GET', | 12 | method: options.method || 'GET', |
| 13 | data: options.data || {}, | 13 | data: options.data || {}, |
| 14 | header: { | 14 | header: { |
| 15 | + ...options.header, | ||
| 15 | 'Authorization': authToken ? `Bearer ${authToken}` : '' | 16 | 'Authorization': authToken ? `Bearer ${authToken}` : '' |
| 16 | } | 17 | } |
| 17 | }).then(res => { | 18 | }).then(res => { |
yudao-vue-ui/pages/set/userInfo.vue
| @@ -20,8 +20,20 @@ | @@ -20,8 +20,20 @@ | ||
| 20 | <text class="tit fill">昵称</text> | 20 | <text class="tit fill">昵称</text> |
| 21 | <input class="input" v-model="userInfo.nickname" type="text" maxlength="8" placeholder="请输入昵称" placeholder-class="placeholder"> | 21 | <input class="input" v-model="userInfo.nickname" type="text" maxlength="8" placeholder="请输入昵称" placeholder-class="placeholder"> |
| 22 | </view> | 22 | </view> |
| 23 | + <u-cell-group> | ||
| 24 | + <u-cell title="昵称" :value="userInfo.nickname" isLink @click="nicknameClick()"></u-cell> | ||
| 25 | + </u-cell-group> | ||
| 26 | + | ||
| 27 | + <u-modal :show="nicknameOpen" title="修改昵称" showCancelButton @confirm="nicknameSubmit" @cancel="nicknameCancel"> | ||
| 28 | + <view class="slot-content"> | ||
| 29 | + <u--form labelPosition="left" :model="nicknameForm" :rules="nicknameRules" ref="nicknameForm" errorType="toast"> | ||
| 30 | + <u-form-item prop="nickname"> | ||
| 31 | + <u--input v-model="nicknameForm.nickname" placeholder="请输入昵称" border="none"></u--input> | ||
| 32 | + </u-form-item> | ||
| 33 | + </u--form> | ||
| 34 | + </view> | ||
| 35 | + </u-modal> | ||
| 23 | 36 | ||
| 24 | - <mix-button ref="confirmBtn" text="保存资料" marginTop="80rpx" @onConfirm="confirm"></mix-button> | ||
| 25 | </view> | 37 | </view> |
| 26 | </template> | 38 | </template> |
| 27 | 39 | ||
| @@ -32,6 +44,16 @@ | @@ -32,6 +44,16 @@ | ||
| 32 | uploadProgress: 100, //头像上传进度 | 44 | uploadProgress: 100, //头像上传进度 |
| 33 | tempAvatar: '', | 45 | tempAvatar: '', |
| 34 | userInfo: {}, | 46 | userInfo: {}, |
| 47 | + nicknameOpen: false, | ||
| 48 | + nicknameForm: { | ||
| 49 | + nickname: '' | ||
| 50 | + }, | ||
| 51 | + nicknameRules: { | ||
| 52 | + nickname: [{ | ||
| 53 | + required: true, | ||
| 54 | + message: '请输入昵称' | ||
| 55 | + }] | ||
| 56 | + } | ||
| 35 | } | 57 | } |
| 36 | }, | 58 | }, |
| 37 | computed: { | 59 | computed: { |
| @@ -50,6 +72,30 @@ | @@ -50,6 +72,30 @@ | ||
| 50 | this.userInfo = {avatar, nickname, gender}; | 72 | this.userInfo = {avatar, nickname, gender}; |
| 51 | }, | 73 | }, |
| 52 | methods: { | 74 | methods: { |
| 75 | + nicknameClick() { | ||
| 76 | + this.nicknameOpen = true; | ||
| 77 | + this.nicknameForm.nickname = this.userInfo.nickname; | ||
| 78 | + }, | ||
| 79 | + nicknameCancel() { | ||
| 80 | + this.nicknameOpen = false; | ||
| 81 | + }, | ||
| 82 | + nicknameSubmit() { | ||
| 83 | + this.$refs.nicknameForm.validate().then(() => { | ||
| 84 | + this.loading = true; | ||
| 85 | + // 执行登陆 | ||
| 86 | + const { mobile, code, password} = this.form; | ||
| 87 | + const loginPromise = this.loginType == 'password' ? login(mobile, password) : | ||
| 88 | + smsLogin(mobile, code); | ||
| 89 | + loginPromise.then(data => { | ||
| 90 | + // 登陆成功 | ||
| 91 | + this.loginSuccessCallBack(data); | ||
| 92 | + }).catch(errors => { | ||
| 93 | + }).finally(() => { | ||
| 94 | + this.loading = false; | ||
| 95 | + }) | ||
| 96 | + }).catch(errors => { | ||
| 97 | + }); | ||
| 98 | + }, | ||
| 53 | // 提交修改 | 99 | // 提交修改 |
| 54 | async confirm() { | 100 | async confirm() { |
| 55 | // 校验信息是否变化 | 101 | // 校验信息是否变化 |