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