1. 新增业务数据查询,新增 部门 数据权限规则支持

2. 补全子角色排除父角色管理菜单测试用例
This commit is contained in:
chenbowen
2025-07-15 10:01:46 +08:00
parent 7f0957d9c4
commit eaea76e955
11 changed files with 360 additions and 26 deletions

View File

@@ -1,6 +1,7 @@
package cn.iocoder.yudao.framework.business.framework;
import cn.iocoder.yudao.framework.datapermission.core.rule.company.CompanyDataPermissionRuleCustomizer;
import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRuleCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -10,10 +11,18 @@ import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class BusinessDataPermissionConfiguration {
@Bean
public CompanyDataPermissionRuleCustomizer sysDeptDataPermissionRuleCustomizer() {
public CompanyDataPermissionRuleCustomizer sysCompanyDataPermissionRuleCustomizer() {
return rule -> {
// companyId
rule.addCompanyColumn("demo_contract", "company_id");
};
}
@Bean
public DeptDataPermissionRuleCustomizer sysDeptDataPermissionRuleCustomizer() {
return rule -> {
// dept
rule.addDeptColumn("demo_contract", "dept_id");
};
}
}

View File

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

View File

@@ -0,0 +1,238 @@
package cn.iocoder.yudao.framework.business.interceptor;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.CompanyDeptInfo;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.web.method.HandlerMethod;
import java.io.PrintWriter;
import java.util.*;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.springframework.security.core.context.SecurityContextHolder.getContext;
class BusinessHeaderInterceptorTest {
private BusinessHeaderInterceptor interceptor;
private HttpServletRequest request;
private HttpServletResponse response;
private HandlerMethod handlerMethod;
private PrintWriter writer;
@BeforeEach
void setUp() throws Exception {
interceptor = new BusinessHeaderInterceptor();
request = mock(HttpServletRequest.class);
response = mock(HttpServletResponse.class);
handlerMethod = mock(HandlerMethod.class);
writer = mock(PrintWriter.class);
when(response.getWriter()).thenReturn(writer);
}
/**
* 用例:传入的 handler 不是 HandlerMethod应该直接返回 true
*/
@Test
void testPreHandle_NotHandlerMethod() throws Exception {
boolean result = interceptor.preHandle(request, response, new Object());
assertTrue(result);
}
/**
* 用例handlerMethod.getBean() 不是 BusinessControllerMarker应该直接返回 true
*/
@Test
void testPreHandle_NotBusinessControllerMarker() throws Exception {
when(handlerMethod.getBean()).thenReturn(new Object());
boolean result = interceptor.preHandle(request, response, handlerMethod);
assertTrue(result);
}
/**
* 用例handlerMethod.getBean() 是普通 Controller未实现 marker 接口),应直接返回 true
*/
@Test
void testPreHandle_NormalController() throws Exception {
class NormalController {}
when(handlerMethod.getBean()).thenReturn(new NormalController());
boolean result = interceptor.preHandle(request, response, handlerMethod);
assertTrue(result);
}
/**
* 用例marker controller且 header 无 companyId/deptIdloginUser 有多个公司部门,应该返回 false 并提示 NEED_ADJUST
*/
@Test
void testPreHandle_NoCompanyId_MultiCompanyDept() throws Exception {
class TestBusinessController implements BusinessControllerMarker {}
when(handlerMethod.getBean()).thenReturn(new TestBusinessController());
when(request.getHeader("visit-company-id")).thenReturn(null);
when(request.getHeader("visit-dept-id")).thenReturn(null);
// 构造 loginUser包含多个公司部门
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
.setUserType(UserTypeEnum.ADMIN.getValue()));
Map<String, String> infoMap = new HashMap<>();
infoMap.put(LoginUser.INFO_KEY_COMPANY_DEPT_SET, "[{\"companyId\":1,\"deptId\":2},{\"companyId\":2,\"deptId\":3}]");
loginUser.setInfo(infoMap);
// 通过反射或包可见性设置 getLoginUser 返回
setLoginUserForTest(loginUser);
boolean result = interceptor.preHandle(request, response, handlerMethod);
assertFalse(result);
verify(writer).write(contains("400"));
}
/**
* 用例header 有 companyId/deptIdloginUser 有多个公司部门,应该正常通过
*/
@Test
void testPreHandle_WithCompanyIdDeptId_MultiCompanyDept() throws Exception {
class TestBusinessController implements BusinessControllerMarker {}
when(handlerMethod.getBean()).thenReturn(new TestBusinessController());
when(request.getHeader("visit-company-id")).thenReturn("1");
when(request.getHeader("visit-dept-id")).thenReturn("2");
// 构造 loginUser包含多个公司部门
CompanyDeptInfo deptInfo1 = new CompanyDeptInfo();
deptInfo1.setCompanyId(1L);
deptInfo1.setDeptId(2L);
CompanyDeptInfo deptInfo2 = new CompanyDeptInfo();
deptInfo2.setCompanyId(2L);
deptInfo2.setDeptId(3L);
Set<CompanyDeptInfo> deptSet = new HashSet<>();
deptSet.add(deptInfo1);
deptSet.add(deptInfo2);
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
.setUserType(UserTypeEnum.ADMIN.getValue()));
String deptSetJson = "[" +
"{\"companyId\":" + deptInfo1.getCompanyId() + ",\"deptId\":" + deptInfo1.getDeptId() + "}," +
"{\"companyId\":" + deptInfo2.getCompanyId() + ",\"deptId\":" + deptInfo2.getDeptId() + "}]";
Map<String, String> infoMap = new HashMap<>();
infoMap.put(LoginUser.INFO_KEY_COMPANY_DEPT_SET, deptSetJson);
loginUser.setInfo(infoMap);
setLoginUserForTest(loginUser);
boolean result = interceptor.preHandle(request, response, handlerMethod);
assertTrue(result);
}
/**
* 用例header 无 companyId/deptIdloginUser 只有一个公司部门,应该自动填充 header 并通过
*/
@Test
void testPreHandle_NoHeader_SingleCompanyDept() throws Exception {
class TestBusinessController implements BusinessControllerMarker {}
when(handlerMethod.getBean()).thenReturn(new TestBusinessController());
when(request.getHeader("visit-company-id")).thenReturn(null);
when(request.getHeader("visit-dept-id")).thenReturn(null);
// 构造 loginUser只有一个公司且公司下只有一个部门
CompanyDeptInfo deptInfo = new CompanyDeptInfo();
deptInfo.setCompanyId(100L);
deptInfo.setDeptId(200L);
Set<CompanyDeptInfo> deptSet = new HashSet<>();
deptSet.add(deptInfo);
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
.setUserType(UserTypeEnum.ADMIN.getValue()));
// 只放一个公司部门
String deptSetJson = "[{\"companyId\":" + deptInfo.getCompanyId() + ",\"deptId\":" + deptInfo.getDeptId() + "}]";
Map<String, String> infoMap = new HashMap<>();
infoMap.put(LoginUser.INFO_KEY_COMPANY_DEPT_SET, deptSetJson);
loginUser.setInfo(infoMap);
setLoginUserForTest(loginUser);
boolean result = interceptor.preHandle(request, response, handlerMethod);
assertFalse(result);
// 可选verify(request).setAttribute("visit-company-id", String.valueOf(deptInfo.getCompanyId()));
// 可选verify(request).setAttribute("visit-dept-id", String.valueOf(deptInfo.getDeptId()));
}
/**
* 用例header 无 companyId/deptIdloginUser 有多个公司部门,应该返回 false 并提示 400
*/
@Test
void testPreHandle_NoHeader_MultiCompanyDept() throws Exception {
class TestBusinessController implements BusinessControllerMarker {}
when(handlerMethod.getBean()).thenReturn(new TestBusinessController());
when(request.getHeader("visit-company-id")).thenReturn(null);
when(request.getHeader("visit-dept-id")).thenReturn(null);
// 构造 loginUser多个公司部门
CompanyDeptInfo deptInfo1 = new CompanyDeptInfo();
deptInfo1.setCompanyId(1L);
deptInfo1.setDeptId(2L);
CompanyDeptInfo deptInfo2 = new CompanyDeptInfo();
deptInfo2.setCompanyId(2L);
deptInfo2.setDeptId(3L);
Set<CompanyDeptInfo> deptSet = new HashSet<>();
deptSet.add(deptInfo1);
deptSet.add(deptInfo2);
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
.setUserType(UserTypeEnum.ADMIN.getValue()));
String deptSetJson = "[" +
"{\"companyId\":" + deptInfo1.getCompanyId() + ",\"deptId\":" + deptInfo1.getDeptId() + "}," +
"{\"companyId\":" + deptInfo2.getCompanyId() + ",\"deptId\":" + deptInfo2.getDeptId() + "}]";
Map<String, String> infoMap = new HashMap<>();
infoMap.put(LoginUser.INFO_KEY_COMPANY_DEPT_SET, deptSetJson);
loginUser.setInfo(infoMap);
setLoginUserForTest(loginUser);
boolean result = interceptor.preHandle(request, response, handlerMethod);
assertFalse(result);
verify(writer).write(contains("400"));
}
/**
* 用例header 有错误的 companyId/deptIdloginUser 不包含该公司部门,应该返回 false 并提示 400
*/
@Test
void testPreHandle_HeaderNotMatchUserCompanyDept() throws Exception {
class TestBusinessController implements BusinessControllerMarker {}
when(handlerMethod.getBean()).thenReturn(new TestBusinessController());
when(request.getHeader("visit-company-id")).thenReturn("999");
when(request.getHeader("visit-dept-id")).thenReturn("888");
// 构造 loginUser只有其他公司部门
CompanyDeptInfo deptInfo1 = new CompanyDeptInfo();
deptInfo1.setCompanyId(1L);
deptInfo1.setDeptId(2L);
CompanyDeptInfo deptInfo2 = new CompanyDeptInfo();
deptInfo2.setCompanyId(2L);
deptInfo2.setDeptId(3L);
Set<CompanyDeptInfo> deptSet = new HashSet<>();
deptSet.add(deptInfo1);
deptSet.add(deptInfo2);
LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)
.setUserType(UserTypeEnum.ADMIN.getValue()));
String deptSetJson = "[" +
"{\"companyId\":" + deptInfo1.getCompanyId() + ",\"deptId\":" + deptInfo1.getDeptId() + "}," +
"{\"companyId\":" + deptInfo2.getCompanyId() + ",\"deptId\":" + deptInfo2.getDeptId() + "}]";
Map<String, String> infoMap = new HashMap<>();
infoMap.put(LoginUser.INFO_KEY_COMPANY_DEPT_SET, deptSetJson);
loginUser.setInfo(infoMap);
setLoginUserForTest(loginUser);
boolean result = interceptor.preHandle(request, response, handlerMethod);
assertFalse(result);
verify(writer).write(contains("400"));
}
// 工具方法:通过 Spring Security 设置当前登录用户,仅测试环境使用
private void setLoginUserForTest(LoginUser loginUser) {
// 使用 Spring Security 的 SecurityContextHolder 设置 Authentication
getContext()
.setAuthentication(new UsernamePasswordAuthenticationToken(
loginUser, null, null
));
}
}