1. 修复回滚父子角色功能时错误的代码逻辑,补全单元测试用例
2. 新增支持切换后业务菜单查询需限定只查询该公司业务数据能力
This commit is contained in:
@@ -38,4 +38,7 @@ public interface GlobalErrorCodeConstants {
|
||||
|
||||
ErrorCode UNKNOWN = new ErrorCode(999, "未知错误");
|
||||
|
||||
// ========== 业务错误段 ==========
|
||||
// 用户未设置公司信息,无法办理当前业务
|
||||
ErrorCode USER_NOT_SET_DEPT = new ErrorCode(1000, "用户未设置有效的公司或部门信息,无法办理当前业务");
|
||||
}
|
||||
|
||||
@@ -49,6 +49,33 @@ public class CommonResult<T> implements Serializable {
|
||||
return error(result.getCode(), result.getMsg());
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义的中间返回
|
||||
* @param data
|
||||
* @param code
|
||||
* @return
|
||||
* @param <T>
|
||||
*/
|
||||
public static <T> CommonResult<T> customize(T data, int code, String msg) {
|
||||
CommonResult<T> result = new CommonResult<>();
|
||||
result.code = code;
|
||||
result.data = data;
|
||||
result.msg = msg;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义的中间返回
|
||||
*/
|
||||
public static <T> CommonResult<T> customize(T data, CommonResultCodeEnum commonResultCodeEnum) {
|
||||
CommonResult<T> result = new CommonResult<>();
|
||||
result.code = commonResultCodeEnum.getCode();
|
||||
result.data = data;
|
||||
result.msg = commonResultCodeEnum.getMessage();
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public static <T> CommonResult<T> error(Integer code, String message) {
|
||||
Assert.notEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), code, "code 必须是错误的!");
|
||||
CommonResult<T> result = new CommonResult<>();
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package cn.iocoder.yudao.framework.common.pojo;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*/
|
||||
|
||||
public enum CommonResultCodeEnum {
|
||||
// 成功
|
||||
SUCCESS(0, "成功"),
|
||||
|
||||
// 根据返回重试
|
||||
NEED_ADJUST(400, "需要根据返回二次重新请求"),
|
||||
|
||||
//
|
||||
ERROR(500, "错误");
|
||||
|
||||
private final int code;
|
||||
private final String message;
|
||||
|
||||
CommonResultCodeEnum(int code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.iocoder.yudao.framework.common.pojo;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 登录用户信息
|
||||
*
|
||||
* @author chenbowen
|
||||
*/
|
||||
@Data
|
||||
public class CompanyDeptInfo {
|
||||
/**
|
||||
* 公司Id
|
||||
*/
|
||||
private Long companyId;
|
||||
/**
|
||||
* 公司名称
|
||||
*/
|
||||
private String companyName;
|
||||
/**
|
||||
* 部门Id
|
||||
*/
|
||||
private Long deptId;
|
||||
/**
|
||||
* 部门名称
|
||||
*/
|
||||
private String deptName;
|
||||
}
|
||||
@@ -11,20 +11,26 @@
|
||||
<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>
|
||||
<version>${revision}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-security</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-biz-data-permission</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,10 @@
|
||||
package cn.iocoder.yudao.framework.business.annotation;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*
|
||||
* 业务代码自动补全的注解,在 DO filed 中与 @TableField(fill = FieldFill.INSERT) 一起标注后自动新增时生成 Code chenbowen
|
||||
*/
|
||||
public @interface BusinessCode {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.iocoder.yudao.framework.business.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.business.interceptor.BusinessHeaderInterceptor;
|
||||
import cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*/
|
||||
@AutoConfiguration(after = YudaoWebAutoConfiguration.class)
|
||||
public class YudaoBusinessAutoConfiguration implements WebMvcConfigurer {
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// 只拦截增删改和 set 相关的 url
|
||||
registry.addInterceptor(new BusinessHeaderInterceptor())
|
||||
.addPathPatterns("/**/add**", "/**/create**", "/**/update**", "/**/edit**", "/**/delete**", "/**/remove**", "/**/set**");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.iocoder.yudao.framework.business.framework;
|
||||
|
||||
import cn.iocoder.yudao.framework.datapermission.core.rule.company.CompanyDataPermissionRuleCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class BusinessDataPermissionConfiguration {
|
||||
@Bean
|
||||
public CompanyDataPermissionRuleCustomizer sysDeptDataPermissionRuleCustomizer() {
|
||||
return rule -> {
|
||||
// companyId
|
||||
rule.addCompanyColumn("demo_contract", "company_id");
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package cn.iocoder.yudao.framework.business.interceptor;
|
||||
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
* 标记是否业务接口,如果是业务接口需要确定唯一的公司和部门信息才能放行
|
||||
*/
|
||||
public interface BusinessControllerMarker {
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package cn.iocoder.yudao.framework.business.interceptor;
|
||||
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResultCodeEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CompanyDeptInfo;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.singleton;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUser;
|
||||
|
||||
/**
|
||||
* @author chenbowen
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BusinessHeaderInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) throws Exception {
|
||||
if (!(handler instanceof HandlerMethod handlerMethod)) {
|
||||
return true;
|
||||
}
|
||||
Object bean = handlerMethod.getBean();
|
||||
if (!(bean instanceof BusinessControllerMarker)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
String companyId = request.getHeader("visit-company-id");
|
||||
String deptId = request.getHeader("visit-dept-id");
|
||||
LoginUser loginUser = Optional.ofNullable(getLoginUser()).orElse(new LoginUser().setInfo(new HashMap<>()));
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
Set<CompanyDeptInfo> companyDeptSet = JSONUtil.parseArray(loginUser.getInfo().getOrDefault(LoginUser.INFO_KEY_COMPANY_DEPT_SET, "[]")).stream()
|
||||
.map(obj -> JSONUtil.toBean((JSONObject) obj, CompanyDeptInfo.class))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// 1. 有 companyId
|
||||
if (companyId != null && !companyId.isBlank()) {
|
||||
Set<CompanyDeptInfo> companyDeptSetByCompanyId = companyDeptSet.stream().filter(companyDeptInfo -> companyDeptInfo.getCompanyId().toString().equals(companyId)).collect(Collectors.toSet());
|
||||
List<CompanyDeptInfo> filtered = companyDeptSetByCompanyId.stream()
|
||||
.filter(info -> String.valueOf(info.getCompanyId()).equals(companyId)).toList();
|
||||
if (filtered.isEmpty()) {
|
||||
// 当前公司下没有部门
|
||||
CompanyDeptInfo data = new CompanyDeptInfo();
|
||||
data.setCompanyId(Long.valueOf(companyId));
|
||||
data.setDeptId(0L);
|
||||
return writeResponse(response, HttpStatus.OK.value(), CommonResult.customize(singleton(data), CommonResultCodeEnum.NEED_ADJUST), objectMapper);
|
||||
}
|
||||
// 如果有 deptId,校验其是否属于该 companyId
|
||||
if (deptId != null) {
|
||||
boolean valid = filtered.stream().anyMatch(info -> String.valueOf(info.getDeptId()).equals(deptId));
|
||||
if (!valid) {
|
||||
return writeResponse(response, HttpStatus.BAD_REQUEST.value(), CommonResult.customize(null, CommonResultCodeEnum.ERROR.getCode(),"当前用户匹配部门不属于此公司"), objectMapper);
|
||||
}else{
|
||||
// 部门存在,放行
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (filtered.size() == 1) {
|
||||
// 唯一部门
|
||||
return writeResponse(response, HttpStatus.OK.value(), CommonResult.customize(filtered,CommonResultCodeEnum.NEED_ADJUST), objectMapper);
|
||||
} else {
|
||||
// 多个部门
|
||||
return writeResponse(response, HttpStatus.OK.value(), CommonResult.customize(filtered,CommonResultCodeEnum.NEED_ADJUST), objectMapper);
|
||||
}
|
||||
}
|
||||
// 2. 没有公司信息,尝试唯一性自动推断
|
||||
// 如果当前用户下只有一个公司和部门的对于关系
|
||||
if (companyDeptSet.size() == 1) {
|
||||
CompanyDeptInfo info = new CompanyDeptInfo();
|
||||
CompanyDeptInfo companyDeptInfo = companyDeptSet.iterator().next();
|
||||
info.setCompanyId(companyDeptInfo.getCompanyId());
|
||||
info.setDeptId(companyDeptInfo.getDeptId());
|
||||
return writeResponse(response, HttpStatus.OK.value(), CommonResult.success(singleton(info)), objectMapper);
|
||||
} else {
|
||||
return writeResponse(response, HttpStatus.OK.value(), CommonResult.customize(companyDeptSet,CommonResultCodeEnum.NEED_ADJUST), objectMapper);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean writeResponse(HttpServletResponse response, int status, CommonResult<?> result, ObjectMapper objectMapper) throws Exception {
|
||||
response.setStatus(status);
|
||||
response.getWriter().write(objectMapper.writeValueAsString(result));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
cn.iocoder.yudao.framework.business.config.YudaoBusinessAutoConfiguration
|
||||
@@ -0,0 +1,36 @@
|
||||
package cn.iocoder.yudao.framework.datapermission.config;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.rule.company.CompanyDataPermissionRule;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.rule.company.CompanyDataPermissionRuleCustomizer;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRule;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRuleCustomizer;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 基于部门的数据权限 AutoConfiguration
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass(LoginUser.class)
|
||||
@ConditionalOnBean(value = {CompanyDataPermissionRuleCustomizer.class})
|
||||
public class YudaoCompanyDataPermissionAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public CompanyDataPermissionRule companyDataPermissionRule(List<CompanyDataPermissionRuleCustomizer> customizers) {
|
||||
|
||||
// 创建 CompanyDataPermissionRule 对象
|
||||
CompanyDataPermissionRule rule = new CompanyDataPermissionRule();
|
||||
// 补全表配置
|
||||
customizers.forEach(customizer -> customizer.customize(rule));
|
||||
return rule;
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,7 @@ import java.util.List;
|
||||
public class YudaoDeptDataPermissionAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public DeptDataPermissionRule deptDataPermissionRule(PermissionCommonApi permissionApi,
|
||||
List<DeptDataPermissionRuleCustomizer> customizers) {
|
||||
public DeptDataPermissionRule deptDataPermissionRule(PermissionCommonApi permissionApi, List<DeptDataPermissionRuleCustomizer> customizers) {
|
||||
// Cloud 专属逻辑:优先使用本地的 PermissionApi 实现类,而不是 Feign 调用
|
||||
// 原因:在创建租户时,租户还没创建好,导致 Feign 调用获取数据权限时,报“租户不存在”的错误
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package cn.iocoder.yudao.framework.datapermission.core.rule.company;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.sf.jsqlparser.expression.*;
|
||||
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 java.util.*;
|
||||
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserCompanyId;
|
||||
|
||||
/**
|
||||
* 基于公司类型部门的 {@link DataPermissionRule} 数据权限规则实现
|
||||
* 注意,使用 CompanyDataPermissionRule 时,需要保证表中有 company_id 公司编号的字段,可自定义。
|
||||
*
|
||||
* @author chenbowen
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Slf4j
|
||||
public class CompanyDataPermissionRule implements DataPermissionRule {
|
||||
static final Expression EXPRESSION_NULL = new NullValue();
|
||||
|
||||
/**
|
||||
* 基于部门的表字段配置
|
||||
* 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。
|
||||
* key:表名
|
||||
* value:字段名
|
||||
*/
|
||||
private final Map<String, String> companyColumns = new HashMap<>();
|
||||
/**
|
||||
* 所有表名,是 {@link #companyColumns} 的合集
|
||||
*/
|
||||
private final Set<String> TABLE_NAMES = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public Set<String> getTableNames() {
|
||||
return TABLE_NAMES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression getExpression(String tableName, Alias tableAlias) {
|
||||
// 业务拼接 Company 的条件
|
||||
if (getLoginUserCompanyId() == null) {
|
||||
// 如果没有登录用户的公司编号,则不需要拼接条件
|
||||
return null;
|
||||
}
|
||||
Expression companyExpression = buildCompanyExpression(tableName, tableAlias, Collections.singleton(getLoginUserCompanyId()));
|
||||
return Objects.requireNonNullElse(companyExpression, EXPRESSION_NULL);
|
||||
}
|
||||
|
||||
private Expression buildCompanyExpression(String tableName, Alias tableAlias, Set<Long> companyIds) {
|
||||
// 如果不存在配置,则无需作为条件
|
||||
String columnName = companyColumns.get(tableName);
|
||||
if (StrUtil.isEmpty(columnName)) {
|
||||
return null;
|
||||
}
|
||||
// 如果为空,则无条件
|
||||
if (CollUtil.isEmpty(companyIds)) {
|
||||
return null;
|
||||
}
|
||||
// 拼接条件
|
||||
return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName),
|
||||
// Parenthesis 的目的,是提供 (1,2,3) 的 () 左右括号
|
||||
new ParenthesedExpressionList<>(new ExpressionList<>(CollectionUtils.convertList(companyIds, LongValue::new))));
|
||||
}
|
||||
|
||||
public void addCompanyColumn(String tableName, String columnName) {
|
||||
companyColumns.put(tableName, columnName);
|
||||
TABLE_NAMES.add(tableName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.iocoder.yudao.framework.datapermission.core.rule.company;
|
||||
|
||||
import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRule;
|
||||
|
||||
/**
|
||||
* {@link DeptDataPermissionRule} 的自定义配置接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface CompanyDataPermissionRuleCustomizer {
|
||||
|
||||
/**
|
||||
* 自定义该权限规则
|
||||
* 1. 调用 {@link CompanyDataPermissionRule#addCompanyColumn(Class, String)} 方法,配置基于 dept_id 的过滤规则
|
||||
*
|
||||
* @param rule 权限规则
|
||||
*/
|
||||
void customize(CompanyDataPermissionRule rule);
|
||||
|
||||
}
|
||||
@@ -121,6 +121,11 @@ public class DeptDataPermissionRule implements DataPermissionRule {
|
||||
// 添加到上下文中,避免重复计算
|
||||
loginUser.setContext(CONTEXT_KEY, deptDataPermission);
|
||||
}
|
||||
// 如果不归属任何部门,且无可查询所有部门的权限,提示当前用户未关联任何部门
|
||||
// if (CollUtil.isEmpty(deptDataPermission.getDeptIds()) && !deptDataPermission.getAll()) {
|
||||
// log.error("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 未关联任何部门]", JsonUtils.toJsonString(loginUser), tableName, tableAlias.getName(), JsonUtils.toJsonString(deptDataPermission));
|
||||
// throw new NullPointerException("当前登录用户未关联任何部门,请先联系管理员进行部门关联");
|
||||
// }
|
||||
// 如果开启了公司上下文,且缓存的公司编号不等于 CompanyContextHolder 的公司编号,则更新缓存
|
||||
if(!CompanyContextHolder.isIgnore()) {
|
||||
Long companyId = CompanyContextHolder.getCompanyId();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
cn.iocoder.yudao.framework.datapermission.config.YudaoDataPermissionAutoConfiguration
|
||||
cn.iocoder.yudao.framework.datapermission.config.YudaoDeptDataPermissionAutoConfiguration
|
||||
cn.iocoder.yudao.framework.datapermission.config.YudaoCompanyDataPermissionAutoConfiguration
|
||||
cn.iocoder.yudao.framework.datapermission.config.YudaoDataPermissionRpcAutoConfiguration
|
||||
|
||||
@@ -99,6 +99,10 @@
|
||||
<groupId>com.fhs-opensource</groupId>
|
||||
<artifactId>easy-trans-mybatis-plus-extend</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.cloud</groupId>
|
||||
<artifactId>yudao-spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package cn.iocoder.yudao.framework.business.core.db;
|
||||
package cn.iocoder.yudao.framework.mybatis.core.dataobject;
|
||||
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import lombok.Data;
|
||||
@@ -12,7 +11,7 @@ import org.apache.ibatis.type.JdbcType;
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class BusinessBaseDO extends TenantBaseDO {
|
||||
public class BusinessBaseDO extends BaseDO {
|
||||
|
||||
/** 公司编号 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
@@ -29,6 +28,10 @@ public class BusinessBaseDO extends TenantBaseDO {
|
||||
/** 岗位编号 */
|
||||
@TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
|
||||
private Long postId;
|
||||
/**
|
||||
* 多租户编号
|
||||
*/
|
||||
private Long tenantId;
|
||||
|
||||
/**
|
||||
* 清除 creator、createTime、updateTime、updater 等字段,避免前端直接传递这些字段,导致被更新
|
||||
@@ -44,3 +47,4 @@ public class BusinessBaseDO extends TenantBaseDO {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
package cn.iocoder.yudao.framework.mybatis.core.handler;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BusinessBaseDO;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.USER_NOT_SET_DEPT;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUser;
|
||||
|
||||
/**
|
||||
* 通用参数填充实现类
|
||||
*
|
||||
* 如果没有显式的对通用参数进行赋值,这里会对通用参数进行填充、赋值
|
||||
*
|
||||
* @author hexiaowu
|
||||
@@ -41,6 +52,30 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
|
||||
baseDO.setUpdater(userId.toString());
|
||||
}
|
||||
}
|
||||
if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BusinessBaseDO businessBaseDO) {
|
||||
// 公司编号、公司名称、部门编号、部门名称、岗位编号等字段,默认不填充
|
||||
// 需要在业务层手动设置
|
||||
LoginUser loginUser = getLoginUser();
|
||||
Long visitCompanyId = loginUser.getVisitCompanyId();
|
||||
Long visitDeptId = loginUser.getVisitDeptId();
|
||||
loginUser.getInfo().getOrDefault(LoginUser.INFO_KEY_POST_IDS,"[]");
|
||||
// 更加合理的写法
|
||||
Set<Long> postIds = new HashSet<>(JSONUtil.parseArray(
|
||||
loginUser.getInfo().getOrDefault(LoginUser.INFO_KEY_POST_IDS, "[]")
|
||||
).toList(Long.class));
|
||||
// 如果 visitCompanyId 不存在,不能进行业务办理
|
||||
if (Objects.isNull(visitCompanyId) || Objects.isNull(visitDeptId)) {
|
||||
throw exception(USER_NOT_SET_DEPT);
|
||||
}
|
||||
businessBaseDO.setCompanyId(visitCompanyId);
|
||||
businessBaseDO.setCompanyName(loginUser.getVisitCompanyName());
|
||||
businessBaseDO.setDeptId(visitDeptId);
|
||||
businessBaseDO.setDeptName(loginUser.getVisitDeptName());
|
||||
// 暂时没有具体业务要求,岗位默认当前用户第一个 todo chenbowen
|
||||
businessBaseDO.setPostId(postIds.isEmpty() ? 0L : postIds.iterator().next());
|
||||
// 多租户编号,默认不填充
|
||||
businessBaseDO.setTenantId(loginUser.getTenantId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -20,6 +20,14 @@ public class LoginUser {
|
||||
|
||||
public static final String INFO_KEY_NICKNAME = "nickname";
|
||||
public static final String INFO_KEY_TENANT_ID = "tenantId";
|
||||
// 用户关联的公司 Id
|
||||
public static final String INFO_KEY_COMPANY_IDS = "companyIds";
|
||||
// 用户关联的部门 Id
|
||||
public static final String INFO_KEY_DEPT_IDS = "deptIds";
|
||||
// 用户关联的公司与部门关联关系
|
||||
public static final String INFO_KEY_COMPANY_DEPT_SET = "companyDeptSet";
|
||||
// 用户关联的岗位信息
|
||||
public static final String INFO_KEY_POST_IDS = "postIds";
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
|
||||
@@ -82,6 +82,17 @@ public class SecurityFrameworkUtils {
|
||||
return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当前用户访问的公司 Id,从上下文中
|
||||
*
|
||||
* @return 用户编号
|
||||
*/
|
||||
@Nullable
|
||||
public static Long getLoginUserCompanyId() {
|
||||
LoginUser loginUser = getLoginUser();
|
||||
return loginUser != null ? loginUser.getVisitCompanyId() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当前用户的编号,从上下文中
|
||||
*
|
||||
|
||||
@@ -29,6 +29,8 @@ public class WebFrameworkUtils {
|
||||
public static final String HEADER_VISIT_TENANT_ID = "visit-tenant-id";
|
||||
public static final String HEADER_VISIT_COMPANY_ID = "visit-company-id";
|
||||
public static final String HEADER_VISIT_COMPANY_NAME = "visit-company-name";
|
||||
public static final String HEADER_VISIT_DEPT_ID = "visit-dept-id";
|
||||
public static final String HEADER_VISIT_DEPT_NAME = "visit-dept-name";
|
||||
|
||||
/**
|
||||
* 终端的 Header
|
||||
@@ -199,13 +201,22 @@ public class WebFrameworkUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得访问的公司名称,从 header 中
|
||||
* 获得访问的公司名称,从 header 中,并进行 URL 解码
|
||||
* @param request 请求
|
||||
* @return 公司名称,解析失败或无效时返回空字符串
|
||||
*/
|
||||
public static String getCompanyName(HttpServletRequest request) {
|
||||
String companyName = request.getHeader(HEADER_VISIT_COMPANY_NAME);
|
||||
return StrUtil.isBlank(companyName) ? StrUtil.EMPTY : companyName;
|
||||
if (StrUtil.isBlank(companyName)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
try {
|
||||
// URL 解码
|
||||
return java.net.URLDecoder.decode(companyName, java.nio.charset.StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
// 解码失败,返回原始值
|
||||
return companyName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,7 +225,7 @@ public class WebFrameworkUtils {
|
||||
* @return 部门编号,解析失败或无效时返回 0
|
||||
*/
|
||||
public static Long getDeptId(HttpServletRequest request) {
|
||||
String deptIdHeader = request.getHeader(WebFrameworkUtils.HEADER_VISIT_TENANT_ID);
|
||||
String deptIdHeader = request.getHeader(WebFrameworkUtils.HEADER_VISIT_DEPT_ID);
|
||||
if (StrUtil.isBlank(deptIdHeader)) {
|
||||
return 0L;
|
||||
}
|
||||
@@ -228,12 +239,21 @@ public class WebFrameworkUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得访问的部门名称,从 header 中
|
||||
* 获得访问的部门名称,从 header 中,并进行 URL 解码
|
||||
* @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;
|
||||
String deptName = request.getHeader(WebFrameworkUtils.HEADER_VISIT_DEPT_NAME);
|
||||
if (StrUtil.isBlank(deptName)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
try {
|
||||
// URL 解码
|
||||
return java.net.URLDecoder.decode(deptName, java.nio.charset.StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
// 解码失败,返回原始值
|
||||
return deptName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user