Commit 7c8fe2fc50dcdc6f625d7420f7c9911f70eaf822

Authored by YunaiV
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 &quot;sys_user&quot; ( @@ -287,6 +287,7 @@ CREATE TABLE IF NOT EXISTS &quot;sys_user&quot; (
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
  1 +/**
  2 + * 多租户,支持如下层面:
  3 + * 1. DB:基于 MyBatis Plus 多租户的功能实现
  4 + * 2. Job:TODO
  5 + * 3. MQ:TODO
  6 + * 4. Web:TODO
  7 + */
  8 +package cn.iocoder.yudao.framework.tenant;
yudao-framework/yudao-spring-boot-starter-tenant/src/main/resources/META-INF/spring.factories 0 → 100644
  1 +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  2 + cn.iocoder.yudao.framework.tenant.config.YudaoTenantDatabaseAutoConfiguration,\
  3 + cn.iocoder.yudao.framework.tenant.config.YudaoTenantWebAutoConfiguration
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) =&gt; { @@ -12,6 +12,7 @@ export const request = (options) =&gt; {
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 // 校验信息是否变化