1. 修复回滚父子角色功能时错误的代码逻辑,补全单元测试用例

2. 新增支持切换后业务菜单查询需限定只查询该公司业务数据能力
This commit is contained in:
chenbowen
2025-07-10 19:05:58 +08:00
parent 92959efdc6
commit 7f0957d9c4
60 changed files with 1749 additions and 64 deletions

View File

@@ -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>

View File

@@ -0,0 +1,10 @@
package cn.iocoder.yudao.framework.business.annotation;
/**
* @author chenbowen
*
* 业务代码自动补全的注解,在 DO filed 中与 @TableField(fill = FieldFill.INSERT) 一起标注后自动新增时生成 Code chenbowen
*/
public @interface BusinessCode {
}

View File

@@ -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**");
}
}

View File

@@ -1,46 +0,0 @@
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, 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.postId = null;
}
}

View File

@@ -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");
};
}
}

View File

@@ -0,0 +1,9 @@
package cn.iocoder.yudao.framework.business.interceptor;
/**
* @author chenbowen
* 标记是否业务接口,如果是业务接口需要确定唯一的公司和部门信息才能放行
*/
public interface BusinessControllerMarker {
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1 @@
cn.iocoder.yudao.framework.business.config.YudaoBusinessAutoConfiguration