v 1.0
1. 新增用户与部门,一对多的关系; 2. 新增管理多部门用户,如果有为公司的多个部门可以进行选择登录(选择后,直到下次变更访问公司前,只能访问此次选择公的业务数据,使用 company_id 控制,后续补充此数据权限的实现); 3. sql 转化工具修复,现在可以正确的对 mysql 进行不同数据库实例的转化了; 4. 所有表格主键,修改为分布式 Id 实现; 5. 补全在初始版本中没有被纳入的其他预制功能模块
This commit is contained in:
@@ -31,6 +31,7 @@
|
||||
<module>yudao-spring-boot-starter-biz-tenant</module>
|
||||
<module>yudao-spring-boot-starter-biz-data-permission</module>
|
||||
<module>yudao-spring-boot-starter-biz-ip</module>
|
||||
<module>yudao-spring-boot-starter-biz-business</module>
|
||||
</modules>
|
||||
|
||||
<artifactId>yudao-framework</artifactId>
|
||||
|
||||
@@ -19,10 +19,14 @@ public class DeptDataPermissionRespDTO {
|
||||
@Schema(description = "可查看的部门编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 3]")
|
||||
private Set<Long> deptIds;
|
||||
|
||||
@Schema(description = "可查看的公司编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 3]")
|
||||
private Long companyId;
|
||||
|
||||
public DeptDataPermissionRespDTO() {
|
||||
this.all = false;
|
||||
this.self = false;
|
||||
this.deptIds = new HashSet<>();
|
||||
this.companyId = 0L;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-framework</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<artifactId>yudao-spring-boot-starter-biz-business</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-module-system-api</artifactId>
|
||||
<version>2.6.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,50 @@
|
||||
package cn.iocoder.yudao.framework.business.core.db;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class BusinessBaseDO extends TenantBaseDO {
|
||||
|
||||
/** 公司编号 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Long companyId;
|
||||
/** 公司名称 */
|
||||
@TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
|
||||
private String companyName;
|
||||
/** 部门编号 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Long deptId;
|
||||
/** 部门名称 */
|
||||
@TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
|
||||
private String deptName;
|
||||
/** 任务编号 */
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.VARCHAR)
|
||||
private Long taskId;
|
||||
/** 岗位编号 */
|
||||
@TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
|
||||
private Long postId;
|
||||
|
||||
/**
|
||||
* 清除 creator、createTime、updateTime、updater 等字段,避免前端直接传递这些字段,导致被更新
|
||||
*/
|
||||
@Override
|
||||
public void clean() {
|
||||
super.clean();
|
||||
this.companyId = null;
|
||||
this.companyName = null;
|
||||
this.deptId = null;
|
||||
this.deptName = null;
|
||||
this.taskId = null;
|
||||
this.postId = null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.iocoder.yudao.framework.business.core.util;
|
||||
|
||||
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 用户与部门一对多改动,此处统一处理用户与部门关系
|
||||
* @author chenbowen
|
||||
*/
|
||||
public class DeptUtil {
|
||||
/**
|
||||
* 从用户信息中获取唯一 deptId (现阶段取第一个,后续如有特殊规则统一调整此处即可)
|
||||
*/
|
||||
public static Long getDeptId(AdminUserRespDTO adminUserRespDTO) {
|
||||
List<Long> deptIds = Optional.ofNullable(adminUserRespDTO.getDeptIds()).orElse(new ArrayList<>());
|
||||
return deptIds.stream().findFirst().orElse(0L);
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.DeptContextHolder;
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.CompanyContextHolder;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -21,17 +21,22 @@ 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.AndExpression;
|
||||
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
|
||||
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
|
||||
import net.sf.jsqlparser.expression.operators.relational.InExpression;
|
||||
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
|
||||
import net.sf.jsqlparser.expression.operators.relational.*;
|
||||
import net.sf.jsqlparser.schema.Column;
|
||||
import net.sf.jsqlparser.schema.Table;
|
||||
import net.sf.jsqlparser.statement.select.ParenthesedSelect;
|
||||
import net.sf.jsqlparser.statement.select.PlainSelect;
|
||||
import net.sf.jsqlparser.statement.select.SelectItem;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
|
||||
/**
|
||||
* 基于部门的 {@link DataPermissionRule} 数据权限规则实现
|
||||
*
|
||||
@@ -62,13 +67,13 @@ public class DeptDataPermissionRule implements DataPermissionRule {
|
||||
private static final String USER_COLUMN_NAME = "user_id";
|
||||
|
||||
static final Expression EXPRESSION_NULL = new NullValue();
|
||||
public static final String SYSTEM_USERS = "system_users";
|
||||
|
||||
private final PermissionCommonApi permissionApi;
|
||||
|
||||
/**
|
||||
* 基于部门的表字段配置
|
||||
* 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。
|
||||
*
|
||||
* key:表名
|
||||
* value:字段名
|
||||
*/
|
||||
@@ -76,7 +81,6 @@ public class DeptDataPermissionRule implements DataPermissionRule {
|
||||
/**
|
||||
* 基于用户的表字段配置
|
||||
* 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。
|
||||
*
|
||||
* key:表名
|
||||
* value:字段名
|
||||
*/
|
||||
@@ -85,6 +89,7 @@ public class DeptDataPermissionRule implements DataPermissionRule {
|
||||
* 所有表名,是 {@link #deptColumns} 和 {@link #userColumns} 的合集
|
||||
*/
|
||||
private final Set<String> TABLE_NAMES = new HashSet<>();
|
||||
private static final String SYSTEM_USER_DEPT = "system_user_dept";
|
||||
|
||||
@Override
|
||||
public Set<String> getTableNames() {
|
||||
@@ -116,11 +121,11 @@ public class DeptDataPermissionRule implements DataPermissionRule {
|
||||
// 添加到上下文中,避免重复计算
|
||||
loginUser.setContext(CONTEXT_KEY, deptDataPermission);
|
||||
}
|
||||
// 如果开启了部门上下文,且缓存的部门编号不等于 DeptContextHolder 的部门编号,则更新缓存
|
||||
if(!DeptContextHolder.isIgnore()) {
|
||||
Set<Long> deptIds = DeptContextHolder.getDeptIdList();
|
||||
if (CollUtil.isNotEmpty(deptIds) && !CollUtil.isEqualList(deptDataPermission.getDeptIds(), deptIds)) {
|
||||
deptDataPermission.setDeptIds(deptIds);
|
||||
// 如果开启了公司上下文,且缓存的公司编号不等于 CompanyContextHolder 的公司编号,则更新缓存
|
||||
if(!CompanyContextHolder.isIgnore()) {
|
||||
Long companyId = CompanyContextHolder.getCompanyId();
|
||||
if (companyId != null && !companyId.equals(deptDataPermission.getCompanyId())) {
|
||||
deptDataPermission.setCompanyId(companyId);
|
||||
// 更新到上下文中
|
||||
loginUser.setContext(CONTEXT_KEY, deptDataPermission);
|
||||
}
|
||||
@@ -137,8 +142,9 @@ public class DeptDataPermissionRule implements DataPermissionRule {
|
||||
return new EqualsTo(null, null); // WHERE null = null,可以保证返回的数据为空
|
||||
}
|
||||
|
||||
// 情况三,拼接 Dept 和 User 的条件,最后组合
|
||||
Expression deptExpression = buildDeptExpression(tableName,tableAlias, deptDataPermission.getDeptIds());
|
||||
// 情况三,拼接 Dept 和 Company User 的条件,最后组合
|
||||
Expression deptExpression = buildDeptExpression(tableName, tableAlias, deptDataPermission.getDeptIds());
|
||||
// Expression deptExpression = buildDeptExpression(tableName, tableAlias, deptDataPermission.getDeptIds());
|
||||
Expression userExpression = buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId());
|
||||
if (deptExpression == null && userExpression == null) {
|
||||
// TODO 芋艿:获得不到条件的时候,暂时不抛出异常,而是不返回数据
|
||||
@@ -161,6 +167,35 @@ public class DeptDataPermissionRule implements DataPermissionRule {
|
||||
private Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds) {
|
||||
// 如果不存在配置,则无需作为条件
|
||||
String columnName = deptColumns.get(tableName);
|
||||
// 特殊处理:system_users 表没有 dept_id 字段,已经迁移到了 user_dept 表
|
||||
if (SYSTEM_USERS.equals(tableName)) {
|
||||
// system_users 走 exists 子查询 user_dept
|
||||
if (CollUtil.isEmpty(deptIds)) {
|
||||
return null;
|
||||
}
|
||||
// 构造 exists (select 1 from user_dept where user_dept.user_id = system_users.id and user_dept.dept_id in (...))
|
||||
PlainSelect plainSelect = new PlainSelect();
|
||||
plainSelect.setSelectItems(singletonList(new SelectItem<>(new LongValue(1))));
|
||||
Table userDept = new Table(SYSTEM_USER_DEPT);
|
||||
// 使用 user 表别名避免语法错误
|
||||
Table user = new Table(tableAlias == null ? tableName : tableAlias.getName());
|
||||
plainSelect.setFromItem(userDept);
|
||||
// where user_dept.user_id = system_users.id and user_dept.dept_id in (...)
|
||||
Column userDeptUserIdCol = new Column(userDept, USER_COLUMN_NAME);
|
||||
Column systemUsersIdCol = new Column(user, "id");
|
||||
EqualsTo userIdEquals = new EqualsTo(userDeptUserIdCol, systemUsersIdCol);
|
||||
Column userDeptDeptIdCol = new Column(userDept, DEPT_COLUMN_NAME);
|
||||
InExpression deptIn = new InExpression(userDeptDeptIdCol, new ParenthesedExpressionList<>(new ExpressionList<>(CollectionUtils.convertList(deptIds, LongValue::new))));
|
||||
Expression whereExp = new AndExpression(userIdEquals, deptIn);
|
||||
plainSelect.setWhere(whereExp);
|
||||
//
|
||||
ParenthesedSelect parenthesedSelect = new ParenthesedSelect();
|
||||
parenthesedSelect.setSelect(plainSelect);
|
||||
// 构建 exists 表达式
|
||||
ExistsExpression existsExpr = new ExistsExpression();
|
||||
existsExpr.setRightExpression(parenthesedSelect);
|
||||
return existsExpr;
|
||||
}
|
||||
if (StrUtil.isEmpty(columnName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import cn.iocoder.yudao.framework.tenant.core.redis.TenantRedisCacheManager;
|
||||
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.service.TenantFrameworkServiceImpl;
|
||||
import cn.iocoder.yudao.framework.tenant.core.web.DeptVisitContextInterceptor;
|
||||
import cn.iocoder.yudao.framework.tenant.core.web.CompanyVisitContextInterceptor;
|
||||
import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter;
|
||||
import cn.iocoder.yudao.framework.tenant.core.web.TenantVisitContextInterceptor;
|
||||
import cn.iocoder.yudao.framework.web.config.WebProperties;
|
||||
@@ -135,8 +135,8 @@ public class YudaoTenantAutoConfiguration {
|
||||
return new TenantVisitContextInterceptor(tenantProperties, securityFrameworkService);
|
||||
}
|
||||
@Bean
|
||||
public DeptVisitContextInterceptor deptVisitContextInterceptor(SecurityFrameworkService securityFrameworkService) {
|
||||
return new DeptVisitContextInterceptor(securityFrameworkService);
|
||||
public CompanyVisitContextInterceptor deptVisitContextInterceptor(SecurityFrameworkService securityFrameworkService) {
|
||||
return new CompanyVisitContextInterceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -153,11 +153,11 @@ public class YudaoTenantAutoConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public WebMvcConfigurer deptWebMvcConfigurer(TenantProperties tenantProperties, DeptVisitContextInterceptor deptVisitContextInterceptor) {
|
||||
public WebMvcConfigurer deptWebMvcConfigurer(TenantProperties tenantProperties, CompanyVisitContextInterceptor companyVisitContextInterceptor) {
|
||||
return new WebMvcConfigurer() {
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(deptVisitContextInterceptor)
|
||||
registry.addInterceptor(companyVisitContextInterceptor)
|
||||
.excludePathPatterns(tenantProperties.getIgnoreVisitUrls().toArray(new String[0]));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import java.lang.annotation.*;
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface DeptVisitIgnore {
|
||||
public @interface CompanyVisitIgnore {
|
||||
|
||||
/**
|
||||
* 是否开启忽略租户,默认为 true 开启
|
||||
@@ -1,36 +1,36 @@
|
||||
package cn.iocoder.yudao.framework.tenant.core.aop;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.util.spring.SpringExpressionUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.CompanyContextHolder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
|
||||
/**
|
||||
* 忽略单位切换,标记指定方法不进行租户切换的覆盖,基于 {@link DeptVisitIgnore} 注解实现,用于一些全局的逻辑。
|
||||
* 忽略单位切换,标记指定方法不进行租户切换的覆盖,基于 {@link CompanyVisitIgnore} 注解实现,用于一些全局的逻辑。
|
||||
* 例如说,一个定时任务,读取所有数据,进行处理。
|
||||
* 又例如说,读取所有数据,进行缓存。
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Aspect
|
||||
@Slf4j
|
||||
public class DeptVisitIgnoreAspect {
|
||||
public class CompanyVisitIgnoreAspect {
|
||||
|
||||
@Around("@annotation(deptVisitIgnore)")
|
||||
public Object around(ProceedingJoinPoint joinPoint, DeptVisitIgnore deptVisitIgnore) throws Throwable {
|
||||
Boolean oldIgnore = TenantContextHolder.isIgnore();
|
||||
@Around("@annotation(companyVisitIgnore)")
|
||||
public Object around(ProceedingJoinPoint joinPoint, CompanyVisitIgnore companyVisitIgnore) throws Throwable {
|
||||
Boolean oldIgnore = CompanyContextHolder.isIgnore();
|
||||
try {
|
||||
// 计算条件,满足的情况下,才进行忽略
|
||||
Object enable = SpringExpressionUtils.parseExpression(deptVisitIgnore.enable());
|
||||
Object enable = SpringExpressionUtils.parseExpression(companyVisitIgnore.enable());
|
||||
if (Boolean.TRUE.equals(enable)) {
|
||||
TenantContextHolder.setIgnore(true);
|
||||
CompanyContextHolder.setIgnore(true);
|
||||
}
|
||||
|
||||
// 执行逻辑
|
||||
return joinPoint.proceed();
|
||||
} finally {
|
||||
TenantContextHolder.setIgnore(oldIgnore);
|
||||
CompanyContextHolder.setIgnore(oldIgnore);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package cn.iocoder.yudao.framework.tenant.core.context;
|
||||
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
|
||||
/**
|
||||
* 公司上下文 Holder
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class CompanyContextHolder {
|
||||
|
||||
/**
|
||||
* 当前公司编号
|
||||
*/
|
||||
private static final ThreadLocal<Long> COMPANY_ID = new TransmittableThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 是否忽略公司
|
||||
*/
|
||||
private static final ThreadLocal<Boolean> IGNORE = new TransmittableThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 获得公司编号
|
||||
*
|
||||
* @return 公司编号
|
||||
*/
|
||||
public static Long getCompanyId() {
|
||||
return COMPANY_ID.get();
|
||||
}
|
||||
|
||||
public static void setCompanyId(Long companyId) {
|
||||
COMPANY_ID.set(companyId);
|
||||
}
|
||||
|
||||
public static void setIgnore(Boolean ignore) {
|
||||
IGNORE.set(ignore);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前是否忽略公司
|
||||
*
|
||||
* @return 是否忽略
|
||||
*/
|
||||
public static boolean isIgnore() {
|
||||
return Boolean.TRUE.equals(IGNORE.get());
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
COMPANY_ID.remove();
|
||||
IGNORE.remove();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package cn.iocoder.yudao.framework.tenant.core.context;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.DocumentEnum;
|
||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 多部门上下文 Holder
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class DeptContextHolder {
|
||||
|
||||
/**
|
||||
* 当前部门编号列表
|
||||
*/
|
||||
private static final ThreadLocal<Set<Long>> DEPT_ID_LIST = new TransmittableThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 是否忽略部门
|
||||
*/
|
||||
private static final ThreadLocal<Boolean> IGNORE = new TransmittableThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 获得部门编号列表
|
||||
*
|
||||
* @return 部门编号列表
|
||||
*/
|
||||
public static Set<Long> getDeptIdList() {
|
||||
return DEPT_ID_LIST.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得部门编号列表。如果不存在,则抛出 NullPointerException 异常
|
||||
*
|
||||
* @return 部门编号列表
|
||||
*/
|
||||
public static Set<Long> getRequiredDeptIdList() {
|
||||
Set<Long> deptIdList = getDeptIdList();
|
||||
if (deptIdList == null) {
|
||||
throw new NullPointerException("DeptContextHolder 不存在部门编号列表!可参考文档:"
|
||||
+ DocumentEnum.TENANT.getUrl());
|
||||
}
|
||||
return deptIdList;
|
||||
}
|
||||
|
||||
public static void setDeptIdList(Set<Long> deptIdList) {
|
||||
DEPT_ID_LIST.set(deptIdList);
|
||||
}
|
||||
|
||||
public static void setIgnore(Boolean ignore) {
|
||||
IGNORE.set(ignore);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前是否忽略部门
|
||||
*
|
||||
* @return 是否忽略
|
||||
*/
|
||||
public static boolean isIgnore() {
|
||||
return Boolean.TRUE.equals(IGNORE.get());
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
DEPT_ID_LIST.remove();
|
||||
IGNORE.remove();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
package cn.iocoder.yudao.framework.tenant.core.web;
|
||||
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService;
|
||||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.DeptContextHolder;
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.CompanyContextHolder;
|
||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
@@ -11,44 +10,49 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class DeptVisitContextInterceptor implements HandlerInterceptor {
|
||||
public class CompanyVisitContextInterceptor implements HandlerInterceptor {
|
||||
|
||||
private static final String PERMISSION = "system:dept:visit";
|
||||
|
||||
private final SecurityFrameworkService securityFrameworkService;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||
// 解析 header 并设置 visitDeptIds
|
||||
Set<Long> deptIds = WebFrameworkUtils.getVisitDeptIds(request);
|
||||
if (deptIds == null) {
|
||||
// 解析 header 并设置 visitCompanyId
|
||||
Long companyId = WebFrameworkUtils.getCompanyId(request);
|
||||
String companyName = WebFrameworkUtils.getCompanyName(request);
|
||||
if (companyId <= 0L) {
|
||||
// 如果没有设置 companyId,则忽略
|
||||
CompanyContextHolder.setIgnore(true);
|
||||
return true;
|
||||
}
|
||||
Long deptId = WebFrameworkUtils.getDeptId(request);
|
||||
String deptName = WebFrameworkUtils.getDeptName(request);
|
||||
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
|
||||
if (loginUser == null) {
|
||||
return true;
|
||||
}
|
||||
if (deptId > 0L) {
|
||||
loginUser.setVisitDeptId(deptId);
|
||||
loginUser.setVisitDeptName(deptName);
|
||||
}
|
||||
// if (!securityFrameworkService.hasAnyPermissions(PERMISSION)) {
|
||||
// throw exception0(GlobalErrorCodeConstants.FORBIDDEN.getCode(), "您无权切换部门");
|
||||
// }
|
||||
loginUser.setVisitDeptIds(deptIds);
|
||||
DeptContextHolder.setDeptIdList(deptIds);
|
||||
loginUser.setVisitCompanyId(companyId);
|
||||
loginUser.setVisitCompanyName(companyName);
|
||||
CompanyContextHolder.setCompanyId(companyId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||
// 清理 visitDeptIds
|
||||
// 清理 visitCompanyId
|
||||
LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
|
||||
if (loginUser != null) {
|
||||
loginUser.setVisitDeptIds(null);
|
||||
loginUser.setVisitCompanyId(0L);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,10 @@ import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 基础实体对象
|
||||
*
|
||||
* 为什么实现 {@link TransPojo} 接口?
|
||||
* 因为使用 Easy-Trans TransType.SIMPLE 模式,集成 MyBatis Plus 查询
|
||||
*
|
||||
* @author 芋道源码
|
||||
* @author chenbowen
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(value = "transMap") // 由于 Easy-Trans 会添加 transMap 属性,避免 Jackson 在 Spring Cache 反序列化报错
|
||||
|
||||
@@ -19,8 +19,7 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
|
||||
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseDO) {
|
||||
BaseDO baseDO = (BaseDO) metaObject.getOriginalObject();
|
||||
if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseDO baseDO) {
|
||||
|
||||
LocalDateTime current = LocalDateTime.now();
|
||||
// 创建时间为空,则以当前时间为插入时间
|
||||
|
||||
@@ -9,7 +9,6 @@ import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 登录用户信息
|
||||
@@ -20,7 +19,6 @@ import java.util.Set;
|
||||
public class LoginUser {
|
||||
|
||||
public static final String INFO_KEY_NICKNAME = "nickname";
|
||||
public static final String INFO_KEY_DEPT_ID = "deptId";
|
||||
public static final String INFO_KEY_TENANT_ID = "tenantId";
|
||||
|
||||
/**
|
||||
@@ -63,7 +61,11 @@ public class LoginUser {
|
||||
*/
|
||||
private Long visitTenantId;
|
||||
|
||||
private Set<Long> visitDeptIds;
|
||||
private Long visitCompanyId;
|
||||
private String visitCompanyName;
|
||||
|
||||
private Long visitDeptId;
|
||||
private String visitDeptName;
|
||||
|
||||
public void setContext(String key, Object value) {
|
||||
if (context == null) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -13,7 +14,6 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
@@ -104,17 +104,6 @@ public class SecurityFrameworkUtils {
|
||||
return loginUser != null ? MapUtil.getStr(loginUser.getInfo(), LoginUser.INFO_KEY_NICKNAME) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当前用户的部门编号,从上下文中
|
||||
*
|
||||
* @return 部门编号
|
||||
*/
|
||||
@Nullable
|
||||
public static Long getLoginUserDeptId() {
|
||||
LoginUser loginUser = getLoginUser();
|
||||
return loginUser != null ? MapUtil.getLong(loginUser.getInfo(), LoginUser.INFO_KEY_DEPT_ID) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前用户
|
||||
*
|
||||
|
||||
@@ -13,9 +13,6 @@ import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 专属于 web 包的工具类
|
||||
*
|
||||
@@ -30,7 +27,8 @@ public class WebFrameworkUtils {
|
||||
|
||||
public static final String HEADER_TENANT_ID = "tenant-id";
|
||||
public static final String HEADER_VISIT_TENANT_ID = "visit-tenant-id";
|
||||
public static final String HEADER_VISIT_DEPT_IDS = "visit-dept-ids";
|
||||
public static final String HEADER_VISIT_COMPANY_ID = "visit-company-id";
|
||||
public static final String HEADER_VISIT_COMPANY_NAME = "visit-company-name";
|
||||
|
||||
/**
|
||||
* 终端的 Header
|
||||
@@ -182,26 +180,60 @@ public class WebFrameworkUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得访问的部门编号列表,从 header 中
|
||||
* 获得访问的公司编号,从 header 中
|
||||
* @param request 请求
|
||||
* @return 部门编号列表,解析失败或无效时返回 null
|
||||
* @return 公司部门编号,解析失败或无效时返回 0
|
||||
*/
|
||||
public static Set<Long> getVisitDeptIds(HttpServletRequest request) {
|
||||
String deptIdsHeader = request.getHeader(HEADER_VISIT_DEPT_IDS);
|
||||
if (StrUtil.isBlank(deptIdsHeader)) {
|
||||
return new HashSet<>();
|
||||
public static Long getCompanyId(HttpServletRequest request) {
|
||||
String companyIdHeader = request.getHeader(HEADER_VISIT_COMPANY_ID);
|
||||
if (StrUtil.isBlank(companyIdHeader)) {
|
||||
return 0L;
|
||||
}
|
||||
try {
|
||||
Set<Long> deptIds = java.util.Arrays.stream(deptIdsHeader.split(","))
|
||||
.map(String::trim)
|
||||
.filter(cn.hutool.core.util.StrUtil::isNotBlank)
|
||||
.map(Long::valueOf)
|
||||
.collect(java.util.stream.Collectors.toSet());
|
||||
return deptIds.isEmpty() ? new HashSet<>() : deptIds;
|
||||
// 解析部门编号列表
|
||||
return Long.parseLong(companyIdHeader);
|
||||
} catch (Exception e) {
|
||||
// 解析失败
|
||||
return new HashSet<>();
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得访问的公司名称,从 header 中
|
||||
* @param request 请求
|
||||
* @return 公司名称,解析失败或无效时返回空字符串
|
||||
*/
|
||||
public static String getCompanyName(HttpServletRequest request) {
|
||||
String companyName = request.getHeader(HEADER_VISIT_COMPANY_NAME);
|
||||
return StrUtil.isBlank(companyName) ? StrUtil.EMPTY : companyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得访问的部门编号,从 header 中
|
||||
* @param request 请求
|
||||
* @return 部门编号,解析失败或无效时返回 0
|
||||
*/
|
||||
public static Long getDeptId(HttpServletRequest request) {
|
||||
String deptIdHeader = request.getHeader(WebFrameworkUtils.HEADER_VISIT_TENANT_ID);
|
||||
if (StrUtil.isBlank(deptIdHeader)) {
|
||||
return 0L;
|
||||
}
|
||||
try {
|
||||
// 解析部门编号
|
||||
return Long.parseLong(deptIdHeader);
|
||||
} catch (Exception e) {
|
||||
// 解析失败
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得访问的部门名称,从 header 中
|
||||
* @param request 请求
|
||||
* @return 部门名称,解析失败或无效时返回空字符串
|
||||
*/
|
||||
public static String getDeptName(HttpServletRequest request) {
|
||||
String deptName = request.getHeader(WebFrameworkUtils.HEADER_VISIT_COMPANY_NAME);
|
||||
return StrUtil.isBlank(deptName) ? StrUtil.EMPTY : deptName;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user