5 changed files with 7481 additions and 21 deletions
@ -0,0 +1,391 @@ |
|||
package com.zdxt.service.test; |
|||
|
|||
import cn.dev33.satoken.context.mock.SaTokenContextMockUtil; |
|||
import cn.dev33.satoken.stp.StpUtil; |
|||
import cn.dev33.satoken.stp.parameter.SaLoginParameter; |
|||
import cn.hutool.core.collection.CollUtil; |
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|||
import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; |
|||
import com.fasterxml.jackson.annotation.JsonProperty; |
|||
import com.zdxt.enforcementcode.domain.EfcodeVirtualUnit; |
|||
import com.zdxt.enforcementcode.mapper.EfcodeVirtualUnitMapper; |
|||
import org.dromara.ZdxtAdminApplication; |
|||
import org.dromara.common.core.constant.SystemConstants; |
|||
import org.dromara.common.core.domain.model.LoginUser; |
|||
import org.dromara.common.core.utils.StringUtils; |
|||
import org.dromara.common.json.utils.JsonUtils; |
|||
import org.dromara.common.satoken.utils.LoginHelper; |
|||
import org.dromara.system.domain.SysDept; |
|||
import org.dromara.system.domain.vo.SysDeptVo; |
|||
import org.dromara.system.mapper.SysDeptMapper; |
|||
import org.junit.jupiter.api.AfterEach; |
|||
import org.junit.jupiter.api.BeforeEach; |
|||
import org.junit.jupiter.api.DisplayName; |
|||
import org.junit.jupiter.api.Test; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; |
|||
import org.springframework.boot.test.context.SpringBootTest; |
|||
import org.springframework.mock.web.MockHttpServletRequest; |
|||
import org.springframework.test.context.ActiveProfiles; |
|||
import org.springframework.web.context.request.RequestContextHolder; |
|||
import org.springframework.web.context.request.ServletRequestAttributes; |
|||
|
|||
import java.nio.charset.StandardCharsets; |
|||
import java.nio.file.Files; |
|||
import java.nio.file.Path; |
|||
import java.nio.file.Paths; |
|||
import java.util.*; |
|||
import java.util.stream.Collectors; |
|||
|
|||
/** |
|||
* 初始化虚拟单位树(V2 - 基于 V1 JSON 树层级) |
|||
* <p> |
|||
* 与 {@link InitVirtualUnitTest} 不同,V2 的树层级结构直接来源于 sysV1Data.json(id/pId), |
|||
* 该 JSON 已包含 V1 系统完整的父子关系;本测试按 JSON 层级生成 efcode_virtual_unit 数据: |
|||
* <ul> |
|||
* <li>保留 FIXED_VIR_ID_MAP 的 id 转换(各区固定 virId);</li> |
|||
* <li>虚拟根节点(深圳市/市直/区)使用固定 virId;</li> |
|||
* <li>"区直" 虚拟节点名称 = 父节点(区)名称 + "区直",virId = Area_XXX_QuZhi;</li> |
|||
* <li>真实部门节点的其他字段(名称/zone/机构/排序)从 sys_dept 表中获取。</li> |
|||
* </ul> |
|||
* </p> |
|||
*/ |
|||
@SpringBootTest(classes = ZdxtAdminApplication.class) |
|||
@ActiveProfiles({"dev", "dev-cust-zrb"}) |
|||
@AutoConfigureMockMvc |
|||
@DisplayName("初始化虚拟单位树V2") |
|||
public class InitVirtualUnitV2Test { |
|||
|
|||
@Autowired |
|||
private SysDeptMapper sysDeptMapper; |
|||
|
|||
@Autowired |
|||
private EfcodeVirtualUnitMapper efcodeVirtualUnitMapper; |
|||
|
|||
private static final String TEST_CLIENT_ID = "e5cd7e4891bf95d1d19206ce24a7b32e"; |
|||
private static final String TENANT_ID = "000000"; |
|||
|
|||
/** |
|||
* JSON 数据文件(相对模块或仓库根目录) |
|||
*/ |
|||
private static final String JSON_RELATIVE_PATH = |
|||
"src/test/java/com/zdxt/service/test/sysV1Data.json"; |
|||
private static final String JSON_REPO_PATH = |
|||
"zdxt-web-server/zdxt-admin-web-server/" + JSON_RELATIVE_PATH; |
|||
|
|||
/** |
|||
* 区级部门 deptId -> 固定 virId 映射 |
|||
*/ |
|||
private static final Map<String, String> FIXED_VIR_ID_MAP = new LinkedHashMap<>() {{ |
|||
put("20210922211852148-9BC0-86CB268FF", "Area_LuoHuQu"); |
|||
put("20210922211856415-B320-A08200FC3", "Area_FuTianQu"); |
|||
put("20210922211901413-5636-0EE262A06", "Area_NanShanQu"); |
|||
put("20210922211905159-EE09-09D606CFB", "Area_BaoAnQu"); |
|||
put("20210922211909285-8169-1B6610932", "Area_LongGangQu"); |
|||
put("20210922211913750-D059-984DC1A1A", "Area_YanTianQu"); |
|||
put("20210922211918204-7059-2B73EBA45", "Area_GuangMingQu"); |
|||
put("20210922211922409-1207-44E38EB4F", "Area_PingsHanQu"); |
|||
put("20210922211924206-457B-09712EC36", "Area_LongHuaQu"); |
|||
put("20210922211925882-0251-C23C3AA2E", "Area_DapengXinQu"); |
|||
put("20210922211927734-9A30-921277192", "Area_ShenShanTeBieHeZuoQu"); |
|||
}}; |
|||
|
|||
@BeforeEach |
|||
public void setUp() { |
|||
SaTokenContextMockUtil.setMockContext(); |
|||
|
|||
MockHttpServletRequest mockRequest = new MockHttpServletRequest(); |
|||
mockRequest.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"); |
|||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(mockRequest)); |
|||
|
|||
LoginUser loginUser = new LoginUser(); |
|||
loginUser.setUserId(1L); |
|||
loginUser.setUserType("sys_user"); |
|||
loginUser.setTenantId(TENANT_ID); |
|||
loginUser.setUsername("test_admin"); |
|||
loginUser.setNickname("测试管理员"); |
|||
loginUser.setDeptId("103"); |
|||
loginUser.setDeptName("测试部门"); |
|||
loginUser.setRolePermission(new HashSet<>()); |
|||
loginUser.setMenuPermission(new HashSet<>()); |
|||
|
|||
SaLoginParameter param = new SaLoginParameter(); |
|||
param.setExtra(LoginHelper.CLIENT_KEY, TEST_CLIENT_ID); |
|||
LoginHelper.login(loginUser, param); |
|||
} |
|||
|
|||
@AfterEach |
|||
public void tearDown() { |
|||
try { |
|||
StpUtil.logout(); |
|||
} catch (Exception ignored) { |
|||
} |
|||
RequestContextHolder.resetRequestAttributes(); |
|||
SaTokenContextMockUtil.clearContext(); |
|||
} |
|||
|
|||
@DisplayName("基于V1 JSON树初始化虚拟单位树数据到 efcode_virtual_unit 表") |
|||
@Test |
|||
public void initVirtualUnitTreeV2() throws Exception { |
|||
// 1. 读取 JSON 树数据(V1 系统导出的层级结构)
|
|||
List<V1Node> nodes = loadV1Nodes(); |
|||
if (CollUtil.isEmpty(nodes)) { |
|||
System.out.println("未读取到任何 JSON 节点数据,退出"); |
|||
return; |
|||
} |
|||
System.out.println("JSON 节点总数: " + nodes.size()); |
|||
|
|||
// 2. 查询 sys_dept 表,构建 deptId -> SysDeptVo 映射(其他字段来源)
|
|||
List<Integer> zones = Arrays.asList(1, 2, 3, 4, 5, 6, 16, 15); |
|||
LambdaQueryWrapper<SysDept> lqw = Wrappers.lambdaQuery(); |
|||
lqw.eq(SysDept::getDelFlag, SystemConstants.NORMAL); |
|||
lqw.in(SysDept::getZone, zones); |
|||
Map<String, SysDeptVo> deptMap = sysDeptMapper.selectVoList(lqw).stream() |
|||
.collect(Collectors.toMap(SysDeptVo::getDeptId, d -> d, (a, b) -> a)); |
|||
System.out.println("查询到部门总数: " + deptMap.size()); |
|||
|
|||
// JSON id -> 节点,用于回溯父节点名称
|
|||
Map<String, V1Node> nodeMap = nodes.stream() |
|||
.collect(Collectors.toMap(V1Node::getId, n -> n, (a, b) -> a)); |
|||
|
|||
// 3. 第一遍:构建 jsonId -> virId 映射(保留 FIXED_VIR_ID_MAP 转换)
|
|||
Map<String, String> jsonIdToVirId = new LinkedHashMap<>(); |
|||
for (V1Node node : nodes) { |
|||
jsonIdToVirId.put(node.getId(), resolveVirId(node)); |
|||
} |
|||
|
|||
// 4. 第二遍:按 JSON 层级构建 EfcodeVirtualUnit
|
|||
List<EfcodeVirtualUnit> allUnits = new ArrayList<>(); |
|||
// virId -> name,用于构建 displayPath
|
|||
Map<String, String> idNameMap = new LinkedHashMap<>(); |
|||
// 排序号:按 JSON 数据顺序升序递增
|
|||
long orderSeq = 1L; |
|||
|
|||
for (V1Node node : nodes) { |
|||
String virId = jsonIdToVirId.get(node.getId()); |
|||
boolean virtual = node.getZone() == null; |
|||
|
|||
EfcodeVirtualUnit unit = new EfcodeVirtualUnit(); |
|||
unit.setVirId(virId); |
|||
|
|||
// 4.1 父节点:JSON pId 映射为 virId;根节点(id==pId)置为 _root_
|
|||
String virParentId; |
|||
if (Objects.equals(node.getId(), node.getPId())) { |
|||
virParentId = "_root_"; |
|||
} else { |
|||
virParentId = jsonIdToVirId.getOrDefault(node.getPId(), "_root_"); |
|||
} |
|||
unit.setVirParentId(virParentId); |
|||
|
|||
// 4.2 名称/zone/其他字段
|
|||
String name; |
|||
Long zoneVal; |
|||
if (virtual) { |
|||
unit.setVirIsVirtual("是"); |
|||
if (isQuZhi(node)) { |
|||
// 区直:父节点(区)名称 + "区直",名称从 sys_dept 获取
|
|||
String parentName = resolveDeptName(node.getPId(), deptMap, nodeMap); |
|||
name = parentName + "区直"; |
|||
zoneVal = null; |
|||
} else { |
|||
// 深圳市/市直/区
|
|||
name = node.getName(); |
|||
if ("市直".equals(name)) { |
|||
zoneVal = 1L; |
|||
} else if ("区".equals(name)) { |
|||
zoneVal = 2L; |
|||
} else { |
|||
zoneVal = null; |
|||
} |
|||
} |
|||
unit.setSysDeptId(null); |
|||
unit.setSrzOrganizationId(null); |
|||
} else { |
|||
unit.setVirIsVirtual("否"); |
|||
SysDeptVo dept = deptMap.get(node.getId()); |
|||
if (dept != null) { |
|||
// 其他字段从 sys_dept 表中获取
|
|||
name = dept.getDeptName(); |
|||
zoneVal = dept.getZone() != null ? Long.valueOf(dept.getZone()) : null; |
|||
unit.setSysDeptId(dept.getDeptId()); |
|||
unit.setSrzOrganizationId(dept.getOrganizationId() != null ? dept.getOrganizationId() : ""); |
|||
} else { |
|||
// sys_dept 未匹配到,名称使用 JSON 回退;zone 直接取 sys_dept(无则 null)
|
|||
name = node.getName(); |
|||
zoneVal = null; |
|||
unit.setSysDeptId(node.getId()); |
|||
unit.setSrzOrganizationId(""); |
|||
System.out.println("警告: sys_dept 未找到部门 " + node.getId() + " [" + node.getName() + "]"); |
|||
} |
|||
} |
|||
|
|||
unit.setVirName(name); |
|||
unit.setVirZone(zoneVal); |
|||
unit.setVirStatus("启用"); |
|||
unit.setVirIncludeStatistics("是"); |
|||
unit.setVirIncludeSearch("是"); |
|||
unit.setVirShowAtBusiness("是"); |
|||
// 排序号按 JSON 数据顺序升序递增
|
|||
unit.setOrderNum(orderSeq++); |
|||
allUnits.add(unit); |
|||
idNameMap.put(virId, name); |
|||
} |
|||
|
|||
// 5. 构建 vir_ancestors 和 vir_display_path
|
|||
Map<String, EfcodeVirtualUnit> unitMap = allUnits.stream() |
|||
.collect(Collectors.toMap(EfcodeVirtualUnit::getVirId, u -> u, (a, b) -> a)); |
|||
|
|||
for (EfcodeVirtualUnit unit : allUnits) { |
|||
// 5.1 向上追溯所有祖级 virId + 自身 virId
|
|||
List<String> ancestorIds = new ArrayList<>(); |
|||
String parentId = unit.getVirParentId(); |
|||
while (parentId != null && !"_root_".equals(parentId)) { |
|||
EfcodeVirtualUnit parentUnit = unitMap.get(parentId); |
|||
if (parentUnit != null) { |
|||
ancestorIds.add(0, parentId); |
|||
parentId = parentUnit.getVirParentId(); |
|||
} else { |
|||
break; |
|||
} |
|||
} |
|||
ancestorIds.add(unit.getVirId()); |
|||
unit.setVirAncestors(String.join(",", ancestorIds)); |
|||
|
|||
// 5.2 基于 ancestors 中每个 virId 查找名称构建 displayPath
|
|||
String displayPath = ancestorIds.stream() |
|||
.map(id -> idNameMap.getOrDefault(id, "")) |
|||
.collect(Collectors.joining("/")); |
|||
unit.setVirDisplayPath(displayPath); |
|||
} |
|||
|
|||
// 6. 清空旧数据后批量插入
|
|||
long existCount = efcodeVirtualUnitMapper.selectCount(Wrappers.lambdaQuery()); |
|||
if (existCount > 0) { |
|||
System.out.println("清空旧数据,共 " + existCount + " 条"); |
|||
efcodeVirtualUnitMapper.delete(Wrappers.lambdaQuery()); |
|||
} |
|||
|
|||
int batchSize = 500; |
|||
int total = allUnits.size(); |
|||
int inserted = 0; |
|||
for (int i = 0; i < total; i += batchSize) { |
|||
List<EfcodeVirtualUnit> batch = allUnits.subList(i, Math.min(i + batchSize, total)); |
|||
efcodeVirtualUnitMapper.insertBatch(batch); |
|||
inserted += batch.size(); |
|||
} |
|||
|
|||
System.out.println("========== 初始化完成(V2) =========="); |
|||
System.out.println("虚拟节点数: " + allUnits.stream().filter(u -> "是".equals(u.getVirIsVirtual())).count()); |
|||
System.out.println("实际部门节点数: " + allUnits.stream().filter(u -> "否".equals(u.getVirIsVirtual())).count()); |
|||
System.out.println("总插入数: " + inserted); |
|||
} |
|||
|
|||
/** |
|||
* 解析 JSON 节点对应的 virId,保留 FIXED_VIR_ID_MAP 及固定虚拟节点转换 |
|||
*/ |
|||
private String resolveVirId(V1Node node) { |
|||
String id = node.getId(); |
|||
// 固定虚拟根节点
|
|||
if ("0".equals(id)) { |
|||
return "ShenZhen_City"; |
|||
} |
|||
if ("1".equals(id)) { |
|||
return "ShenZhen_ShiZhi"; |
|||
} |
|||
if ("2".equals(id)) { |
|||
return "ShenZhen_Areas"; |
|||
} |
|||
// 区级固定 virId
|
|||
if (FIXED_VIR_ID_MAP.containsKey(id)) { |
|||
return FIXED_VIR_ID_MAP.get(id); |
|||
} |
|||
// 区直虚拟节点:virId = 父区 virId 去除 Area_ 前缀 + _QuZhi
|
|||
if (isQuZhi(node)) { |
|||
String parentVir = FIXED_VIR_ID_MAP.get(node.getPId()); |
|||
if (parentVir != null && parentVir.startsWith("Area_")) { |
|||
return parentVir.replace("Area_", "") + "_QuZhi"; |
|||
} |
|||
} |
|||
// 其他真实部门:UUID
|
|||
return UUID.randomUUID().toString().replace("-", ""); |
|||
} |
|||
|
|||
/** |
|||
* 是否为 "区直" 虚拟节点(zone 为空且名称为"区直") |
|||
*/ |
|||
private boolean isQuZhi(V1Node node) { |
|||
return node.getZone() == null && "区直".equals(node.getName()); |
|||
} |
|||
|
|||
/** |
|||
* 获取部门名称:优先从 sys_dept 表获取,回退 JSON name |
|||
*/ |
|||
private String resolveDeptName(String deptId, Map<String, SysDeptVo> deptMap, Map<String, V1Node> nodeMap) { |
|||
SysDeptVo dept = deptMap.get(deptId); |
|||
if (dept != null && StringUtils.isNotEmpty(dept.getDeptName())) { |
|||
return dept.getDeptName(); |
|||
} |
|||
V1Node node = nodeMap.get(deptId); |
|||
return node != null && node.getName() != null ? node.getName() : ""; |
|||
} |
|||
|
|||
/** |
|||
* 读取 sysV1Data.json,兼容从模块目录或仓库根目录运行 |
|||
*/ |
|||
private List<V1Node> loadV1Nodes() throws Exception { |
|||
Path jsonPath = Paths.get(JSON_RELATIVE_PATH); |
|||
if (!Files.exists(jsonPath)) { |
|||
jsonPath = Paths.get(JSON_REPO_PATH); |
|||
} |
|||
String content = new String(Files.readAllBytes(jsonPath), StandardCharsets.UTF_8); |
|||
return JsonUtils.parseArray(content, V1Node.class); |
|||
} |
|||
|
|||
/** |
|||
* V1 JSON 节点结构 |
|||
*/ |
|||
@JsonIgnoreProperties(ignoreUnknown = true) |
|||
public static class V1Node { |
|||
@JsonProperty("id") |
|||
private String id; |
|||
@JsonProperty("pId") |
|||
private String pId; |
|||
@JsonProperty("name") |
|||
private String name; |
|||
@JsonProperty("zone") |
|||
private Integer zone; |
|||
|
|||
public String getId() { |
|||
return id; |
|||
} |
|||
|
|||
public void setId(String id) { |
|||
this.id = id; |
|||
} |
|||
|
|||
public String getPId() { |
|||
return pId; |
|||
} |
|||
|
|||
public void setPId(String pId) { |
|||
this.pId = pId; |
|||
} |
|||
|
|||
public String getName() { |
|||
return name; |
|||
} |
|||
|
|||
public void setName(String name) { |
|||
this.name = name; |
|||
} |
|||
|
|||
public Integer getZone() { |
|||
return zone; |
|||
} |
|||
|
|||
public void setZone(Integer zone) { |
|||
this.zone = zone; |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
Loading…
Reference in new issue