diff --git a/src/main/java/com/budwk/app/sys/controller/SysUnitController.java b/src/main/java/com/budwk/app/sys/controller/SysUnitController.java index 1f862dc..b731785 100644 --- a/src/main/java/com/budwk/app/sys/controller/SysUnitController.java +++ b/src/main/java/com/budwk/app/sys/controller/SysUnitController.java @@ -22,6 +22,7 @@ import com.budwk.app.sys.services.SysDictService; import com.budwk.app.sys.services.SysRoleService; import com.budwk.app.sys.services.SysUnitService; import com.budwk.app.sys.services.SysUserService; +import com.budwk.app.sys.utils.DataCenterUtil; import com.budwk.app.sys.views.View_user; import com.budwk.app.web.commons.auth.utils.AuthUtil; import com.budwk.app.web.commons.auth.utils.SecurityUtil; @@ -214,9 +215,9 @@ public class SysUnitController { if (AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name())) { Cnd cnd = Cnd.NEW(); if (Strings.isBlank(pid)) { - cnd.and("parentId", "=", "").or("parentId", "is", null); + cnd.and("id", "=", DataCenterUtil.findRootUnit(sysUnitService.query(Cnd.NEW())).getId()); } else { - cnd.and("parentId", "=", pid); + cnd.and("parentId", "=", pid).and("id", "<>", pid); } cnd.asc("location").asc("path"); list = sysUnitService.query(cnd); @@ -227,9 +228,9 @@ public class SysUnitController { } else { Cnd cnd = Cnd.NEW(); if (Strings.isBlank(pid)) { - cnd.and("parentId", "=", "").or("parentId", "is", null); + cnd.and("id", "=", DataCenterUtil.findRootUnit(sysUnitService.query(Cnd.NEW())).getId()); } else { - cnd.and("parentId", "=", pid); + cnd.and("parentId", "=", pid).and("id", "<>", pid); } cnd.asc("location").asc("path"); list = sysUnitService.query(cnd); @@ -258,9 +259,9 @@ public class SysUnitController { if (StpUtil.hasRole("SYSADMIN")) { Cnd cnd = Cnd.NEW(); if (Strings.isBlank(pid)) { - cnd.and("parentId", "=", "").or("parentId", "is", null); + cnd.and("id", "=", DataCenterUtil.findRootUnit(sysUnitService.query(Cnd.NEW())).getId()); } else { - cnd.and("parentId", "=", pid); + cnd.and("parentId", "=", pid).and("id", "<>", pid); } cnd.asc("location").asc("path"); list = sysUnitService.query(cnd); @@ -271,9 +272,9 @@ public class SysUnitController { } else { Cnd cnd = Cnd.NEW(); if (Strings.isBlank(pid)) { - cnd.and("parentId", "=", "").or("parentId", "is", null); + cnd.and("id", "=", DataCenterUtil.findRootUnit(sysUnitService.query(Cnd.NEW())).getId()); } else { - cnd.and("parentId", "=", pid); + cnd.and("parentId", "=", pid).and("id", "<>", pid); } cnd.asc("location").asc("path"); list = sysUnitService.query(cnd); @@ -300,24 +301,23 @@ public class SysUnitController { @SaCheckLogin public Object tree(@Param("pid") String pid, HttpServletRequest req) { try { - String rootId = StrUtil.blankToDefault(pid, "0"); String virtualRootId = "root"; Cnd cnd = Cnd.NEW(); cnd.asc("unitcode"); List list = sysUnitService.query(cnd); + String rootId = StrUtil.isBlank(pid) ? DataCenterUtil.findRootUnit(list).getId() : pid; List> nodeList = CollUtil.newArrayList(); for (int i = 0; i < list.size(); i++) { Sys_unit unit = list.get(i); /* - * 单位根节点存在 id 与 parentId 都为 0 的自引用数据。 - * 构建树时把当前查询根挂到虚拟根下,避免“中国地质大学”和 parentId=0 的学院被构造成同级。 + * 动态查询根挂到虚拟根下,兼容根节点父级为空或指向自身的数据。 */ String parentId = rootId.equals(unit.getId()) ? virtualRootId : unit.getParentId(); nodeList.add(new TreeNode<>(unit.getId(), parentId, unit.getName(), i) .setExtra( Map.of( - "unitTypeCode", unit.getUnitTypeCode() + "unitTypeCode", unit.getUnitTypeCode() == null ? 0 : unit.getUnitTypeCode() ) )); } diff --git a/src/main/java/com/budwk/app/sys/models/Sys_user.java b/src/main/java/com/budwk/app/sys/models/Sys_user.java index edc832a..18c89d5 100644 --- a/src/main/java/com/budwk/app/sys/models/Sys_user.java +++ b/src/main/java/com/budwk/app/sys/models/Sys_user.java @@ -49,7 +49,7 @@ public class Sys_user extends BaseModel implements Serializable { @Column @Comment("性别") @ColDefine(type = ColType.VARCHAR, width = 10) - @DataCenterColumn(name = "性别", key = "XB", dict = "USER_SEX") + @DataCenterColumn(name = "性别", key = "XBMC") private String sex; @Column @@ -65,6 +65,7 @@ public class Sys_user extends BaseModel implements Serializable { @Column @Comment("出生日期") @ColDefine(type = ColType.DATE) + @DataCenterColumn(name = "出生日期", key = "CSRQ") private Date birthday; @Column @@ -108,12 +109,13 @@ public class Sys_user extends BaseModel implements Serializable { @Column @Comment("手机号码") @ColDefine(type = ColType.VARCHAR, width = 32) - @DataCenterColumn(name = "手机号码", key = "YDDH") + @DataCenterColumn(name = "手机号码", key = "SJHM") private String mobile; @Column @Comment("学历") @ColDefine(type = ColType.VARCHAR, width = 20) + @DataCenterColumn(name = "学历", key = "ZGXLMC") private String education; @Column @@ -124,7 +126,7 @@ public class Sys_user extends BaseModel implements Serializable { @Column @Comment("学位") @ColDefine(type = ColType.VARCHAR, width = 50) -// @DataCenterColumn(name = "学位", key = "zgxwmc") + @DataCenterColumn(name = "学位", key = "ZGXWMC") private String academicDegree; @Column @@ -135,6 +137,7 @@ public class Sys_user extends BaseModel implements Serializable { @Column @Comment("职称") @ColDefine(type = ColType.VARCHAR, width = 50) + @DataCenterColumn(name = "职称", key = "ZYJSZWDMMC") private String professionalTitle; @Column @@ -175,7 +178,7 @@ public class Sys_user extends BaseModel implements Serializable { @Column @Comment("来校时间") @ColDefine(type = ColType.VARCHAR, width = 10) - @DataCenterColumn(name = "来校时间", key = "LXNY") + @DataCenterColumn(name = "来校时间", key = "LXRQ") private String arrivalAtSchoolDate; @Column @@ -191,7 +194,7 @@ public class Sys_user extends BaseModel implements Serializable { @Column @Comment("在职状态") @ColDefine(type = ColType.VARCHAR, width = 30) - @DataCenterColumn(name = "当前状态码", key = "DQZT", dict = "USER_STATE") + @DataCenterColumn(name = "在职状态", key = "DQZTMC") private String userState; @Column @@ -208,7 +211,7 @@ public class Sys_user extends BaseModel implements Serializable { @Column @Comment("教职工类别码") @ColDefine(type = ColType.VARCHAR, width = 30) - @DataCenterColumn(name = "教职工类别码", key = "RYLX", dict = "USER_PERSON_TYPE") + @DataCenterColumn(name = "人员类型", key = "YRFSDM", dict = "USER_PERSON_TYPE") private String personType; @Column @@ -220,6 +223,7 @@ public class Sys_user extends BaseModel implements Serializable { @Column @Comment("编制类别码") @ColDefine(type = ColType.VARCHAR, width = 30) + @DataCenterColumn(name = "编制类别", key = "BZLBDMMC") private String preparedBy; @Column @@ -274,7 +278,7 @@ public class Sys_user extends BaseModel implements Serializable { @Column @ColDefine(type = ColType.VARCHAR, width = 32) - @DataCenterColumn(name = "单位", key = "SZDWH") + @DataCenterColumn(name = "单位", key = "SZDWDM") private String unitId; @Column diff --git a/src/main/java/com/budwk/app/sys/services/impl/SysDataUnitPullServiceImpl.java b/src/main/java/com/budwk/app/sys/services/impl/SysDataUnitPullServiceImpl.java index c42ecf1..34e6f53 100644 --- a/src/main/java/com/budwk/app/sys/services/impl/SysDataUnitPullServiceImpl.java +++ b/src/main/java/com/budwk/app/sys/services/impl/SysDataUnitPullServiceImpl.java @@ -1,29 +1,25 @@ package com.budwk.app.sys.services.impl; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; -import cn.hutool.http.Header; -import cn.hutool.http.HttpRequest; -import cn.hutool.http.HttpUtil; -import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; -import cn.hutool.json.JSONUtil; import com.budwk.app.base.config.DataCenterProperties; import com.budwk.app.base.exception.BaseException; import com.budwk.app.base.service.impl.BaseServiceImpl; import com.budwk.app.sys.models.Sys_unit; import com.budwk.app.sys.services.SysDataUnitPullService; +import com.budwk.app.sys.utils.DataCenterUtil; import lombok.extern.slf4j.Slf4j; import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Dao; +import org.nutz.dao.Cnd; +import org.nutz.dao.Sqls; import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Comparator; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -42,7 +38,6 @@ import java.util.stream.Collectors; public class SysDataUnitPullServiceImpl extends BaseServiceImpl implements SysDataUnitPullService { private static final String DMP_UNIT_KEY = "unit"; - private static final int DMP_PAGE_SIZE = 1000; @Inject private DataCenterProperties dataCenterProperties; @@ -64,58 +59,29 @@ public class SysDataUnitPullServiceImpl extends BaseServiceImpl implem @Override @Aop(TransAop.READ_COMMITTED) public void updateUnits(List unitIds) { - String accessToken = getDmpAccessToken(); - List rawDataList = distinctDmpUnits(pullDmpUnits(getDmpUnitUrl(), accessToken)); + Sys_unit root = DataCenterUtil.findRootUnit(query(Cnd.NEW())); + List rawDataList = distinctDmpUnits(DataCenterUtil.pullData(getDmpUnitUrl())); + if (rawDataList.isEmpty()) { + throw new BaseException("当前未获取到单位数据,中止本次同步"); + } + if (StrUtil.isBlank(root.getPath())) { + root.setPath(getUnitPath("")); + dao().updateIgnoreNull(root); + } Set parentCodes = rawDataList.stream() - .map(this::getNormalizedParentCode) + .map(raw -> getNormalizedParentCode(raw, root.getId())) .filter(StrUtil::isNotBlank) .collect(Collectors.toSet()); rawDataList.stream() .sorted(Comparator.comparingInt(this::getUnitLevel)) - .forEach(raw -> saveOrUpdateUnit(raw, parentCodes)); + .forEach(raw -> saveOrUpdateUnit(raw, parentCodes, root)); + + dao().execute(Sqls.create("update sys_unit u left join (select parentId, count(*) as childCount from sys_unit where parentId is not null and id<>parentId group by parentId) children on children.parentId=u.id set u.hasChildren=(children.childCount is not null)")); log.info("信息中心单位数据同步完成,本次接口返回单位数量: {},待检查单位编码数量: {}", rawDataList.size(), unitIds == null ? 0 : unitIds.size()); } - /** - * 获取信息中心开放平台访问令牌。 - * 参数来自 data-center.token-url、data-center.key、data-center.secret; - * 返回值为后续单位接口 body 中 access_token 使用的字符串。 - */ - private String getDmpAccessToken() { - String tokenUrl = dataCenterProperties.getTokenUrl(); - if (StrUtil.isBlank(tokenUrl)) { - throw new BaseException("未配置信息中心token地址: data-center.token-url"); - } - String key = dataCenterProperties.getKey(); - if (StrUtil.isBlank(key)) { - throw new BaseException("未配置信息中心token key: data-center.key"); - } - String secret = dataCenterProperties.getSecret(); - if (StrUtil.isBlank(secret)) { - throw new BaseException("未配置信息中心token secret: data-center.secret"); - } - - String params = URLUtil.buildQuery(Map.of("key", key, "secret", secret), StandardCharsets.UTF_8); - String tokenResBody = HttpUtil.createGet(tokenUrl + "?" + params).execute().body(); - JSONObject tokenJsonBody = JSONUtil.parseObj(tokenResBody); - String accessToken = tokenJsonBody.getStr("access_token"); - if (StrUtil.isBlank(accessToken) && tokenJsonBody.getJSONObject("result") != null) { - accessToken = tokenJsonBody.getJSONObject("result").getStr("access_token"); - } - if (StrUtil.isBlank(accessToken) && tokenJsonBody.getJSONObject("data") != null) { - accessToken = tokenJsonBody.getJSONObject("data").getStr("access_token"); - } - if (StrUtil.isBlank(accessToken)) { - accessToken = tokenJsonBody.getStr("token"); - } - if (StrUtil.isBlank(accessToken)) { - throw new BaseException("获取信息中心token失败"); - } - return accessToken; - } - /** * 读取信息中心单位接口地址。 * 配置项为 data-center.urls.unit,返回值为单位全量接口URL。 @@ -128,53 +94,6 @@ public class SysDataUnitPullServiceImpl extends BaseServiceImpl implem return url; } - /** - * 分页拉取单位接口数据。 - * 入参 url 为单位接口地址,accessToken 为信息中心令牌; - * 请求 body 包含 access_token、per_page、page; - * 返回值为 result.data 合并后的单位原始 JSON 列表。 - */ - private List pullDmpUnits(String url, String accessToken) { - List rawDataList = new ArrayList<>(); - int page = 1; - int maxPage = 1; - int total = 0; - - do { - HttpRequest httpRequest = HttpUtil.createPost(url); - httpRequest.header(Header.CONTENT_TYPE, "application/json"); - httpRequest.body(JSONUtil.toJsonStr(Map.of( - "access_token", accessToken, - "per_page", String.valueOf(DMP_PAGE_SIZE), - "page", String.valueOf(page) - ))); - - log.info("请求信息中心单位数据,第{}页: {}", page, httpRequest); - String resBody = httpRequest.execute().body(); - log.info("请求信息中心单位数据第{}页结果: {}", page, resBody); - JSONObject jsonBody = JSONUtil.parseObj(resBody); - - if (jsonBody.getInt("code") != 10000) { - throw new BaseException("获取信息中心单位数据失败,错误码: " + jsonBody.getInt("code") + ";错误原因:" + jsonBody.getStr("message")); - } - - JSONObject result = jsonBody.getJSONObject("result"); - if (result == null) { - break; - } - total = result.getInt("total", total); - maxPage = result.getInt("max_page", maxPage); - JSONArray data = result.getJSONArray("data"); - if (CollUtil.isNotEmpty(data)) { - rawDataList.addAll(data.stream().map(v -> (JSONObject) v).toList()); - } - log.info("信息中心单位数据拉取进度: {}/{}", rawDataList.size(), total); - page++; - } while (page <= maxPage); - - return rawDataList; - } - /** * 按单位代码去重,避免接口分页或源数据重复导致同一 DWDM 重复写入。 */ @@ -183,7 +102,10 @@ public class SysDataUnitPullServiceImpl extends BaseServiceImpl implem for (JSONObject raw : rawDataList) { String unitCode = raw.getStr("DWDM"); if (StrUtil.isBlank(unitCode)) { - continue; + throw new BaseException("单位接口存在缺少 DWDM 的记录"); + } + if (raw.getInt("DWCC") == null || raw.getInt("DWCC") < 1) { + throw new BaseException("单位{}的 DWCC 无效", unitCode); } unitMap.putIfAbsent(unitCode, raw); } @@ -192,15 +114,18 @@ public class SysDataUnitPullServiceImpl extends BaseServiceImpl implem /** * 保存或更新单位。 - * DWDM 对应系统单位 id/unitcode,DWMC 对应名称,LSDWH 对应父级单位号,DWCC 对应信息中心原始单位层级。 + * DWDM 对应系统单位 id/unitcode,DWMC 对应名称,LSDWDM 对应父级单位号,DWCC 对应信息中心原始单位层级。 */ - private void saveOrUpdateUnit(JSONObject raw, Set parentCodes) { + private void saveOrUpdateUnit(JSONObject raw, Set parentCodes, Sys_unit root) { String unitCode = raw.getStr("DWDM"); if (StrUtil.isBlank(unitCode)) { return; } - String parentId = getParentId(unitCode, getNormalizedParentCode(raw)); + if (root.getId().equals(unitCode)) { + throw new BaseException("接口单位{}与本地顶级机构编号冲突,中止同步", unitCode); + } + String parentId = getParentId(unitCode, getNormalizedParentCode(raw, root.getId()), root.getId()); Sys_unit unit = fetch(unitCode); if (unit == null) { unit = new Sys_unit(); @@ -210,26 +135,37 @@ public class SysDataUnitPullServiceImpl extends BaseServiceImpl implem setUnitValue(unit, raw, parentId, parentCodes); dao().fastInsert(unit); } else { - setUnitValue(unit, raw, parentId, parentCodes); - if (StrUtil.isBlank(unit.getPath())) { - unit.setPath(getUnitPath(parentId)); + if (!parentId.equals(unit.getParentId()) || StrUtil.isBlank(unit.getPath())) { + String oldPath = unit.getPath(); + String newPath = getUnitPath(parentId); + if (StrUtil.isNotBlank(oldPath)) { + if (newPath.startsWith(oldPath)) { + throw new BaseException("单位{}不能移动到自身的下级单位", unitCode); + } + dao().execute(Sqls.create("update sys_unit set path=concat(@newPath, substring(path, @start)) where path like @prefix") + .setParam("newPath", newPath).setParam("start", oldPath.length() + 1).setParam("prefix", oldPath + "%")); + dao().execute(Sqls.create("update sys_user set unitPath=concat(@newPath, substring(unitPath, @start)) where unitPath like @prefix") + .setParam("newPath", newPath).setParam("start", oldPath.length() + 1).setParam("prefix", oldPath + "%")); + dao().execute(Sqls.create("update sys_user_source set unitPath=concat(@newPath, substring(unitPath, @start)) where unitPath like @prefix") + .setParam("newPath", newPath).setParam("start", oldPath.length() + 1).setParam("prefix", oldPath + "%")); + } + unit.setPath(newPath); } + setUnitValue(unit, raw, parentId, parentCodes); dao().updateIgnoreNull(unit); } } /** * 根据接口字段给系统单位赋值。 - * DWYXBS 表示单位是否有效,DWCC 表示信息中心原始单位层次; + * DWCC 表示信息中心原始单位层次; * 系统内置根单位占用一级层级,因此保存时统一将 DWCC 加一。 */ private void setUnitValue(Sys_unit unit, JSONObject raw, String parentId, Set parentCodes) { String unitCode = raw.getStr("DWDM"); String unitName = raw.getStr("DWMC"); String aliasName = StrUtil.blankToDefault(raw.getStr("DWJC"), unitName); - Integer rawUnitLevel = raw.getInt("DWCC"); Integer unitLevel = getNormalizedUnitLevel(raw); - boolean availableBusinessUnit = "是".equals(raw.getStr("DWYXBS")) && Integer.valueOf(2).equals(rawUnitLevel); unit.setParentId(parentId); unit.setName(unitName); @@ -237,24 +173,34 @@ public class SysDataUnitPullServiceImpl extends BaseServiceImpl implem unit.setUnitcode(unitCode); unit.setAddress(raw.getStr("DWDZ")); unit.setUnitLevel(unitLevel); - unit.setUnitType(raw.getStr("DWLB")); - unit.setUnitTypeCode(availableBusinessUnit ? 1 : 0); + unit.setUnitType(raw.getStr("DWLBDM")); + unit.setUnitTypeCode(1); unit.setHasChildren(parentCodes.contains(unitCode)); } /** * 计算父级单位号。 - * LSDWH 为空、等于自身或本地不存在该父级时按根级处理;系统根单位 0 允许直接作为父级。 + * 异常父级仅允许沿用本地已修正的关系,并确认父级链最终到达顶级机构。 */ - private String getParentId(String unitCode, String parentCode) { - if (StrUtil.isBlank(parentCode) || unitCode.equals(parentCode)) { - return ""; + private String getParentId(String unitCode, String parentCode, String rootId) { + if (StrUtil.isBlank(parentCode) || unitCode.equals(parentCode) || fetch(parentCode) == null) { + Sys_unit existing = fetch(unitCode); + parentCode = existing == null ? null : existing.getParentId(); } - if ("0".equals(parentCode)) { - return parentCode; + String ancestorId = parentCode; + Set visited = new HashSet<>(); + visited.add(unitCode); + while (!rootId.equals(ancestorId)) { + if (StrUtil.isBlank(ancestorId) || !visited.add(ancestorId)) { + throw new BaseException("单位{}的上级机构无效或存在循环,无法确定归属", unitCode); + } + Sys_unit ancestor = fetch(ancestorId); + if (ancestor == null) { + throw new BaseException("单位{}的上级机构{}不存在", unitCode, ancestorId); + } + ancestorId = ancestor.getParentId(); } - Sys_unit parentUnit = fetch(parentCode); - return parentUnit == null ? "" : parentCode; + return parentCode; } /** @@ -265,20 +211,23 @@ public class SysDataUnitPullServiceImpl extends BaseServiceImpl implem String parentPath = ""; if (StrUtil.isNotBlank(parentId)) { Sys_unit parentUnit = fetch(parentId); - parentPath = parentUnit == null ? "" : parentUnit.getPath(); + if (parentUnit == null || StrUtil.isBlank(parentUnit.getPath())) { + throw new BaseException("父机构{}不存在或路径为空", parentId); + } + parentPath = parentUnit.getPath(); } return getSubPath("sys_unit", "path", parentPath); } /** - * 信息中心一级单位应挂在系统根单位 0 下,其余单位沿用接口返回的 LSDWH。 + * 信息中心一级单位挂在本地顶级机构下,其余单位沿用接口返回的 LSDWDM。 */ - private String getNormalizedParentCode(JSONObject raw) { + private String getNormalizedParentCode(JSONObject raw, String rootId) { Integer rawUnitLevel = raw.getInt("DWCC"); if (Integer.valueOf(1).equals(rawUnitLevel)) { - return "0"; + return rootId; } - return raw.getStr("LSDWH"); + return raw.getStr("LSDWDM"); } /** diff --git a/src/main/java/com/budwk/app/sys/services/impl/SysDataUserAllUpdateServiceImpl.java b/src/main/java/com/budwk/app/sys/services/impl/SysDataUserAllUpdateServiceImpl.java index 41d323a..406aa62 100644 --- a/src/main/java/com/budwk/app/sys/services/impl/SysDataUserAllUpdateServiceImpl.java +++ b/src/main/java/com/budwk/app/sys/services/impl/SysDataUserAllUpdateServiceImpl.java @@ -133,7 +133,7 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService Sys_role publicRole = sysRoleService.getByCode(RoleConstant.PUBLIC); // 获取单位信息,后续判断单位变更 - List unitList = dao.query(Sys_unit.class, Cnd.where("unitTypeCode", "=", "1")); + List unitList = dao.query(Sys_unit.class, Cnd.NEW()); unitIdNameMap = unitList.stream().collect(Collectors.toMap(Sys_unit::getId, Sys_unit::getName)); // 查询数据源 diff --git a/src/main/java/com/budwk/app/sys/services/impl/SysDataUserIncrUpdateServiceImpl.java b/src/main/java/com/budwk/app/sys/services/impl/SysDataUserIncrUpdateServiceImpl.java index 251258d..3f7b1c4 100644 --- a/src/main/java/com/budwk/app/sys/services/impl/SysDataUserIncrUpdateServiceImpl.java +++ b/src/main/java/com/budwk/app/sys/services/impl/SysDataUserIncrUpdateServiceImpl.java @@ -3,10 +3,9 @@ package com.budwk.app.sys.services.impl; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.date.DateUtil; -import cn.hutool.core.thread.AsyncUtil; -import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.StrUtil; import com.budwk.app.base.constant.RoleConstant; +import com.budwk.app.base.exception.BaseException; import com.budwk.app.base.event.role.RoleEventMsg; import com.budwk.app.base.event.role.RoleEventPublisher; import com.budwk.app.base.utils.ConditionGroupUtil; @@ -15,6 +14,8 @@ import com.budwk.app.sys.models.*; import com.budwk.app.sys.param.SysDataUserUpdateParam; import com.budwk.app.sys.services.SysDataUserUpdateService; import com.budwk.app.sys.services.SysRoleService; +import com.budwk.app.sys.services.SysUnitService; +import com.budwk.app.sys.utils.DataCenterUtil; import com.budwk.app.zhgh.democratic.proposal.models.ProposalConfig; import com.budwk.app.zhgh.staffmanage.member.constant.MemberChangeOrigin; import com.budwk.app.zhgh.staffmanage.member.constant.MemberChangeType; @@ -28,7 +29,7 @@ import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.lang.random.R; import java.util.List; -import java.util.concurrent.CompletableFuture; +import java.util.ArrayList; /** * 增量更新系统用户数据 @@ -40,6 +41,8 @@ public class SysDataUserIncrUpdateServiceImpl implements SysDataUserUpdateServic private Dao dao; @Inject private SysRoleService sysRoleService; + @Inject + private SysUnitService sysUnitService; @Override @Aop(TransAop.READ_COMMITTED) @@ -58,6 +61,43 @@ public class SysDataUserIncrUpdateServiceImpl implements SysDataUserUpdateServic // 执行查询 List userSources = dao.query(Sys_user_source.class, cnd); + // 先补齐机构,再写入人员;补建机构统一挂到本地唯一顶级机构。 + List sysUnits = dao.query(Sys_unit.class, Cnd.NEW()); + var unitMap = sysUnits.stream().collect(java.util.stream.Collectors.toMap(Sys_unit::getId, v -> v)); + List insertUnits = new ArrayList<>(); + Sys_unit root = null; + for (Sys_user_source source : userSources) { + if (StrUtil.isBlank(source.getUnitId())) { + throw new BaseException("人员{}缺少所属机构编号", source.getLoginname()); + } + Sys_unit unit = unitMap.get(source.getUnitId()); + if (unit == null) { + if (StrUtil.isBlank(source.getUnitName())) { + throw new BaseException("人员{}所属机构{}缺少名称,无法补建", source.getLoginname(), source.getUnitId()); + } + if (root == null) { + root = DataCenterUtil.findRootUnit(sysUnits); + if (StrUtil.isBlank(root.getPath())) { + root.setPath(sysUnitService.getSubPath("sys_unit", "path", "")); + dao.updateIgnoreNull(root); + } + } + unit = new Sys_unit(); + unit.setUnitcode(source.getUnitId()); + unit.setName(source.getUnitName()); + unit.setUnitLevel(2); + unit.setUnitTypeCode(1); + sysUnitService.save(unit, root.getId()); + unitMap.put(unit.getId(), unit); + insertUnits.add(unit); + } + if (StrUtil.isBlank(unit.getPath())) { + throw new BaseException("人员{}所属机构{}的路径为空", source.getLoginname(), unit.getId()); + } + source.setUnitName(unit.getName()); + source.setUnitPath(unit.getPath()); + } + //加入到系统用户表 List sysUsers = BeanUtil.copyToList(userSources, Sys_user.class); for (Sys_user sysUser : sysUsers) { @@ -72,9 +112,9 @@ public class SysDataUserIncrUpdateServiceImpl implements SysDataUserUpdateServic } List> splitSysUsers = ListUtil.split(sysUsers, 500); - List>> splitSysUsersFutures = splitSysUsers.stream().map(v -> CompletableFuture.supplyAsync(() -> dao.fastInsert(v))).toList(); - //等待任务全部完成 - AsyncUtil.waitAll(CompletableFuture.allOf(splitSysUsersFutures.toArray(new CompletableFuture[0]))); + for (List batch : splitSysUsers) { + dao.fastInsert(batch); + } // 增量更新只处理新增人员,需要同步写入用户历史记录;列表的变更类型、变更时间通过 sys_user_history 最新记录展示。 List histories = BeanUtil.copyToList(userSources, Sys_user_history.class); @@ -85,8 +125,9 @@ public class SysDataUserIncrUpdateServiceImpl implements SysDataUserUpdateServic history.setChangeOrigin(MemberChangeOrigin.SYSTEM.name()); } List> splitHistories = ListUtil.split(histories, 500); - List>> splitHistoryFutures = splitHistories.stream().map(v -> CompletableFuture.supplyAsync(() -> dao.fastInsert(v))).toList(); - AsyncUtil.waitAll(CompletableFuture.allOf(splitHistoryFutures.toArray(new CompletableFuture[0]))); + for (List batch : splitHistories) { + dao.fastInsert(batch); + } Sys_role publicRole = sysRoleService.getByCode(RoleConstant.PUBLIC); @@ -97,38 +138,9 @@ public class SysDataUserIncrUpdateServiceImpl implements SysDataUserUpdateServic sysUserRole.setRoleId(publicRole.getId()); return sysUserRole; }).toList(); - ThreadUtil.execAsync(() -> { - dao.insert(sysUserRoles); - }); + dao.insert(sysUserRoles); sysRoleService.clearCache(); - //比较单位数据 - List sysUnits = dao.query(Sys_unit.class, Cnd.where(Sys_unit::getUnitLevel, "=", 2)); - List sysUnitIds = sysUnits.stream().map(Sys_unit::getId).toList(); - - //信息中心的数据 转为单位代码 ->单位名称 map - var unitIdNameMap = userSources.stream() - .filter(v -> StrUtil.isAllNotBlank(v.getUnitId(), v.getUnitName())) - .collect(java.util.stream.Collectors.toMap( - Sys_user_source::getUnitId, - Sys_user_source::getUnitName, - (existingValue, newValue) -> newValue)); - - //需要新增的单位 - List insertUnits = unitIdNameMap.entrySet().stream() - .filter(entry -> !sysUnitIds.contains(entry.getKey())) - .map(entry -> { - Sys_unit sysUnit = new Sys_unit(); - sysUnit.setId(entry.getKey()); - sysUnit.setName(entry.getValue()); - sysUnit.setParentId("1"); - sysUnit.setUnitLevel(2); - return sysUnit; - }).toList(); - ThreadUtil.execAsync(() -> { - dao.insert(insertUnits); - }); - // 6.1 更新提案的校领导角色,发送订阅 ProposalConfig config = dao.fetch(ProposalConfig.class, Cnd.where("delFlag", "=", false).desc("updatedAt")); for (String unitId : config.getSchoolLeaderUnitIds()) { diff --git a/src/main/java/com/budwk/app/sys/services/impl/SysDataUserPullServiceImpl.java b/src/main/java/com/budwk/app/sys/services/impl/SysDataUserPullServiceImpl.java index 565aede..7f409dd 100644 --- a/src/main/java/com/budwk/app/sys/services/impl/SysDataUserPullServiceImpl.java +++ b/src/main/java/com/budwk/app/sys/services/impl/SysDataUserPullServiceImpl.java @@ -52,8 +52,6 @@ import java.util.stream.Collectors; public class SysDataUserPullServiceImpl extends BaseServiceImpl implements SysDataUserPullService { private static final String DMP_TEACHER_KEY = "teacher"; - private static final String DMP_DISPATCH_KEY = "dispatch"; - private static final int DMP_PAGE_SIZE = 1000; @Inject private SysDictService sysDictService; @@ -120,21 +118,17 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl // 字典码表Map,key为父级编码,value为子级字典列表 Map> dataDictMap = dataDictList.stream().collect(Collectors.groupingBy(Sys_data_dict::getParentCode)); - String accessToken = getDmpAccessToken(); - List rawDataList = new ArrayList<>(); - rawDataList.addAll(pullDmpUsers(getDmpUrl(DMP_TEACHER_KEY), accessToken, "在岗教职工")); - rawDataList.addAll(pullDmpUsers(getDmpUrl(DMP_DISPATCH_KEY), accessToken, "在岗劳务派遣")); - rawDataList = distinctDmpUsers(rawDataList); + List rawDataList = distinctDmpUsers(DataCenterUtil.pullData(getDmpUrl(DMP_TEACHER_KEY))); if (rawDataList.isEmpty()) { - log.warn("当前未获取到人员数据"); + throw new BaseException("当前未获取到人员数据,中止本次同步"); } Date nowDate = new Date(); // 人员单位号先与单位表批量比对,缺失单位只同步一次,避免逐人查询单位表。 Set sourceUnitIds = rawDataList.stream() - .map(raw -> raw.getStr("SZDWH")) + .map(raw -> raw.getStr("SZDWDM")) .filter(StrUtil::isNotBlank) .collect(Collectors.toSet()); Map unitMap = dao().query(Sys_unit.class, Cnd.NEW()).stream() @@ -146,25 +140,34 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl .collect(Collectors.toMap(Sys_unit::getId, unit -> unit)); } - // 按单位表的实际等级判断:三级单位为3、直属二级单位为2;单位号位数不参与判断。 + String rootId = DataCenterUtil.findRootUnit(new ArrayList<>(unitMap.values())).getId(); + // 深层机构沿父级查找二级单位,并保留所属三级单位。 Map tertiaryParentUnitIds = new HashMap<>(); + Map tertiaryUnitIds = new HashMap<>(); for (String sourceUnitId : sourceUnitIds) { Sys_unit unit = unitMap.get(sourceUnitId); if (unit == null) { - log.warn("人员单位号{}在单位表中不存在,保留信息中心原始单位号", sourceUnitId); - continue; + throw new BaseException("人员单位号{}在机构同步后仍不存在,中止本次同步", sourceUnitId); } - if (!Integer.valueOf(3).equals(unit.getUnitLevel())) { - continue; + Set visited = new HashSet<>(); + while (!rootId.equals(unit.getId()) && !Integer.valueOf(2).equals(unit.getUnitLevel())) { + if (!visited.add(unit.getId())) { + throw new BaseException("人员单位{}存在循环父级", sourceUnitId); + } + if (Integer.valueOf(3).equals(unit.getUnitLevel())) { + tertiaryUnitIds.put(sourceUnitId, unit.getId()); + } + unit = unitMap.get(unit.getParentId()); + if (unit == null || rootId.equals(unit.getId())) { + throw new BaseException("人员单位{}缺少有效的二级上级机构", sourceUnitId); + } } - Sys_unit parentUnit = unitMap.get(unit.getParentId()); - if (parentUnit == null || !Integer.valueOf(2).equals(parentUnit.getUnitLevel())) { - log.warn("三级单位{}缺少有效的二级父单位,保留信息中心原始单位号", sourceUnitId); - continue; + if (!sourceUnitId.equals(unit.getId())) { + tertiaryParentUnitIds.put(sourceUnitId, unit.getId()); } - tertiaryParentUnitIds.put(sourceUnitId, parentUnit.getId()); } + Map resolvedUnitMap = unitMap; // 赋值 List latestSourceList = rawDataList.stream().map(raw -> { Sys_user_source sysUser = new Sys_user_source(); @@ -182,35 +185,44 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl .findFirst().orElse(new Sys_data_dict()); mapping.field.set(sysUser, StrUtil.blankToDefault(sysDataDict.getName(), raw.getStr(mapping.key))); } else { - // 特殊处理 根据身份证号获取性别和出生年月 - if ("SFZJH".equals(mapping.key)) { - String idCard = raw.getStr(mapping.key); - mapping.field.set(sysUser, idCard); - // 设置出生年月 - try { - sysUser.setBirthday(IdcardUtil.getBirthDate(idCard)); - } catch (Exception e) { - sysUser.setBirthday(null); - } - } else if ("LXNY".equals(mapping.key)) { + if ("LXRQ".equals(mapping.key)) { mapping.field.set(sysUser, normalizeArrivalAtSchoolDate(raw.getStr(mapping.key))); } else { mapping.field.set(sysUser, raw.getStr(mapping.key)); } } } else if (mapping.field.getType() == Date.class) { - mapping.field.set(sysUser, raw.getDate(mapping.key)); + String dateText = StrUtil.trim(raw.getStr(mapping.key)); + if (StrUtil.isNotBlank(dateText)) { + mapping.field.set(sysUser, dateText.matches("\\d{8}") + ? DateUtil.parse(dateText, "yyyyMMdd") : DateUtil.parse(dateText)); + } } } catch (IllegalAccessException e) { log.error("设置字段值失败: {}", mapping.field.getName(), e); } } - sysUser.setUnitName(raw.getStr("SZDW")); - String sourceUnitId = raw.getStr("SZDWH"); + if (sysUser.getBirthday() == null && StrUtil.isNotBlank(sysUser.getIdCard())) { + try { + sysUser.setBirthday(IdcardUtil.getBirthDate(sysUser.getIdCard())); + } catch (Exception e) { + sysUser.setBirthday(null); + } + } + String sourceUnitId = raw.getStr("SZDWDM"); + if (StrUtil.isBlank(sourceUnitId)) { + throw new BaseException("人员{}缺少所属机构编号 SZDWDM", sysUser.getLoginname()); + } String parentUnitId = tertiaryParentUnitIds.get(sourceUnitId); sysUser.setUnitId(StrUtil.blankToDefault(parentUnitId, sourceUnitId)); - sysUser.setThreeUnitId(parentUnitId == null ? null : sourceUnitId); + sysUser.setThreeUnitId(tertiaryUnitIds.get(sourceUnitId)); + Sys_unit unit = resolvedUnitMap.get(sysUser.getUnitId()); + if (unit == null || StrUtil.isBlank(unit.getPath())) { + throw new BaseException("人员{}所属机构不存在或路径为空", sysUser.getLoginname()); + } + sysUser.setUnitName(unit.getName()); + sysUser.setUnitPath(unit.getPath()); // 设置拉取时间 sysUser.setPullTime(nowDate); @@ -232,7 +244,7 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl /** * 规范化信息中心返回的来校时间。 - * 入参 arrivalAtSchoolDate 对应信息中心 LXNY 字段,允许 yyyy-MM-dd 或 yyyy-MM; + * 入参 arrivalAtSchoolDate 对应信息中心 LXRQ 字段,允许 yyyy-MM-dd 或 yyyy-MM; * 返回值用于 sys_user_source.arrivalAtSchoolDate 和后续 sys_user.arrivalAtSchoolDate,年月格式统一补为当月 01 日。 */ private String normalizeArrivalAtSchoolDate(String arrivalAtSchoolDate) { @@ -248,48 +260,8 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl String[] parts = dateText.split("-"); return parts[0] + "-" + StrUtil.padPre(parts[1], 2, '0') + "-01"; } - return dateText; - } - - /** - * 获取信息中心开放平台访问令牌。 - * 返回值为接口后续调用使用的 access_token 字符串。 - */ - private String getDmpAccessToken() { - String tokenUrl = dataCenterProperties.getTokenUrl(); - if (StrUtil.isBlank(tokenUrl)) { - throw new BaseException("未配置信息中心token地址: data-center.token-url"); - } - String key = dataCenterProperties.getKey(); - if (StrUtil.isBlank(key)) { - throw new BaseException("未配置信息中心token key: data-center.key"); - } - String secret = dataCenterProperties.getSecret(); - if (StrUtil.isBlank(secret)) { - throw new BaseException("未配置信息中心token secret: data-center.secret"); - } - // 信息中心token接口要求key和secret通过URL参数传入,配置中分开维护,调用时统一组装。 - String params = URLUtil.buildQuery(Map.of("key", key, "secret", secret), StandardCharsets.UTF_8); - HttpRequest tokenHttpRequest = HttpUtil.createGet(tokenUrl + "?" + params); - log.info("请求信息中心token: {}", tokenHttpRequest); - String tokenResBody = tokenHttpRequest.execute().body(); - log.info("请求信息中心token结果: {}", tokenResBody); - - JSONObject tokenJsonBody = JSONUtil.parseObj(tokenResBody); - String accessToken = tokenJsonBody.getStr("access_token"); - if (StrUtil.isBlank(accessToken) && tokenJsonBody.getJSONObject("result") != null) { - accessToken = tokenJsonBody.getJSONObject("result").getStr("access_token"); - } - if (StrUtil.isBlank(accessToken) && tokenJsonBody.getJSONObject("data") != null) { - accessToken = tokenJsonBody.getJSONObject("data").getStr("access_token"); - } - if (StrUtil.isBlank(accessToken)) { - accessToken = tokenJsonBody.getStr("token"); - } - if (StrUtil.isBlank(accessToken)) { - throw new BaseException("获取信息中心token失败"); - } - return accessToken; + return DateUtil.formatDate(dateText.matches("\\d{8}") + ? DateUtil.parse(dateText, "yyyyMMdd") : DateUtil.parse(dateText)); } /** @@ -305,54 +277,7 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl } /** - * 分页拉取单个人员接口数据。 - * 入参 url 为人员接口地址,accessToken 为信息中心令牌,sourceName 用于日志区分接口来源。 - * 返回值为该接口所有分页合并后的原始人员 JSON 列表。 - */ - private List pullDmpUsers(String url, String accessToken, String sourceName) { - List rawDataList = new ArrayList<>(); - int page = 1; - int maxPage = 1; - int total = 0; - - do { - // 信息中心接口单页最多返回1000条,按page循环拉完当前接口全部数据。 - HttpRequest httpRequest = HttpUtil.createPost(url); - httpRequest.header(Header.CONTENT_TYPE, "application/json"); - httpRequest.body(JSONUtil.toJsonStr(Map.of( - "access_token", accessToken, - "per_page", String.valueOf(DMP_PAGE_SIZE), - "page", String.valueOf(page) - ))); - - log.info("请求{}人员数据,第{}页: {}", sourceName, page, httpRequest); - String resBody = httpRequest.execute().body(); - log.info("请求{}人员数据第{}页结果: {}", sourceName, page, resBody); - JSONObject jsonBody = JSONUtil.parseObj(resBody); - - if (jsonBody.getInt("code") != 10000) { - throw new BaseException("获取" + sourceName + "人员数据失败,错误码: " + jsonBody.getInt("code") + ";错误原因:" + jsonBody.getStr("message")); - } - - JSONObject result = jsonBody.getJSONObject("result"); - if (result == null) { - break; - } - total = result.getInt("total", total); - maxPage = result.getInt("max_page", maxPage); - JSONArray data = result.getJSONArray("data"); - if (CollUtil.isNotEmpty(data)) { - rawDataList.addAll(data.stream().map(v -> (JSONObject) v).toList()); - } - log.info("{}人员数据拉取进度: {}/{}", sourceName, rawDataList.size(), total); - page++; - } while (page <= maxPage); - - return rawDataList; - } - - /** - * 合并两个接口数据时按职工号去重,避免同一批次出现重复人员。 + * 按职工号去重,避免同一批次出现重复人员。 * 返回值保持第一次出现的数据,用于后续统一映射入库。 */ private List distinctDmpUsers(List rawDataList) { @@ -360,7 +285,7 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl for (JSONObject raw : rawDataList) { String loginName = raw.getStr("ZGH"); if (StrUtil.isBlank(loginName)) { - loginName = raw.getStr("ID"); + throw new BaseException("人员接口存在缺少职工号 ZGH 的记录"); } userMap.putIfAbsent(loginName, raw); } diff --git a/src/main/java/com/budwk/app/sys/utils/DataCenterUtil.java b/src/main/java/com/budwk/app/sys/utils/DataCenterUtil.java index 09c0e3b..edfbd59 100644 --- a/src/main/java/com/budwk/app/sys/utils/DataCenterUtil.java +++ b/src/main/java/com/budwk/app/sys/utils/DataCenterUtil.java @@ -3,9 +3,18 @@ package com.budwk.app.sys.utils; import cn.hutool.core.convert.Convert; import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.budwk.app.base.exception.BaseException; +import com.budwk.app.sys.models.Sys_unit; import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; import java.util.Map; /** @@ -17,6 +26,50 @@ import java.util.Map; */ public class DataCenterUtil { + public static Sys_unit findRootUnit(List units) { + List roots = units.stream().filter(unit -> + StrUtil.isBlank(unit.getParentId()) + || (unit.getId().equals(unit.getParentId()) + && (unit.getUnitLevel() == null || unit.getUnitLevel() <= 1))).toList(); + if (roots.size() != 1) { + throw new BaseException("本地顶级机构数量为{},无法确定唯一的学校根机构", roots.size()); + } + return roots.get(0); + } + + public static List pullData(String url) { + JSONObject firstResponse = requestData(url, Map.of("", "")); + JSONObject pageInfo = firstResponse.getJSONObject("pageInfo"); + Integer pages = pageInfo == null ? null : pageInfo.getInt("pages"); + if (pages == null || pages < 0) { + throw new BaseException("数据中心接口未返回有效的 pageInfo.pages"); + } + List records = new ArrayList<>(); + for (int page = 1; page <= pages; page++) { + JSONObject response = requestData(url, Map.of("pageNum", page)); + JSONArray data = response.getJSONArray("dataSet"); + if (data == null) { + throw new BaseException("数据中心接口第{}页缺少 dataSet", page); + } + records.addAll(data.toList(JSONObject.class)); + } + return records; + } + + private static JSONObject requestData(String url, Map body) { + try (HttpResponse response = HttpUtil.createPost(url) + .body(JSONUtil.toJsonStr(body)).execute()) { + if (!response.isOk()) { + throw new BaseException("数据中心接口请求失败,HTTP状态码:{}", response.getStatus()); + } + JSONObject result = JSONUtil.parseObj(response.body()); + if (!Integer.valueOf(1).equals(result.getInt("returnStatus"))) { + throw new BaseException("数据中心接口返回失败:{}", result.getStr("errorMsg")); + } + return result; + } + } + /** * 映射字段 * @param obj diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml index 6344a05..953d93e 100644 --- a/src/main/resources/application-dev.yaml +++ b/src/main/resources/application-dev.yaml @@ -61,8 +61,8 @@ custom: password: 3848A3C738EFBB2C2E76E0D59B419DACA16A80F3AB0354CD token-url: https://bd.whcp.zjnu.edu.cn/sjjkfwpt/api/bd-api/oauth/token urls: - teacher: https://bd.whcp.zjnu.edu.cn/sjjkfwpt/api/bd-api/get/JZGJCSJZLB - unit: https://bd.whcp.zjnu.edu.cn/sjjkfwpt/api/bd-api/get/YXSDWJBSJZLB + teacher: http://mdm.zjnu.edu.cn/do/api/call/lxyxt_jzgjbxx + unit: http://mdm.zjnu.edu.cn/do/api/call/lxyxt_dwxx msg: platform: enabled: false diff --git a/src/main/resources/application-prd.yaml b/src/main/resources/application-prd.yaml index 75d2b49..5e9782c 100644 --- a/src/main/resources/application-prd.yaml +++ b/src/main/resources/application-prd.yaml @@ -61,8 +61,8 @@ custom: password: 3848A3C738EFBB2C2E76E0D59B419DACA16A80F3AB0354CD token-url: https://bd.whcp.edu.cn/sjjkfwpt/api/bd-api/oauth/token urls: - teacher: https://bd.whcp.edu.cn/sjjkfwpt/api/bd-api/get/JZGJCSJZLB - unit: https://bd.whcp.edu.cn/sjjkfwpt/api/bd-api/get/YXSDWJBSJZLB + teacher: http://mdm.zjnu.edu.cn/do/api/call/lxyxt_jzgjbxx + unit: http://mdm.zjnu.edu.cn/do/api/call/lxyxt_dwxx msg: platform: enabled: false diff --git a/src/main/resources/db/merge_zjnu_school_root.sql b/src/main/resources/db/merge_zjnu_school_root.sql new file mode 100644 index 0000000..a213df9 --- /dev/null +++ b/src/main/resources/db/merge_zjnu_school_root.sql @@ -0,0 +1,138 @@ +-- 目标:浙江师范大学 -> 下级单位,不再显示额外的“组织机构”层。 +-- 当前 v4 接口固定以 ID=0 构建树,因此将学校 ID=1 的信息合并到 ID=0。 +-- 只修改 sys_unit;不修改人员、角色和业务数据。执行期间不要并发写入相关数据。 +-- 检查其他表的机构 ID、机构 ID 数组及显式外键;存在对 ID=1 的引用时停止。 +-- 备份表固定命名,不覆盖已有备份。本文件独立执行,不要嵌入已有事务。 + +DELIMITER $$ + +DROP PROCEDURE IF EXISTS zhgh_zjnu.merge_zjnu_school_root$$ +CREATE PROCEDURE zhgh_zjnu.merge_zjnu_school_root() +BEGIN + DECLARE finished BOOLEAN DEFAULT FALSE; + DECLARE reference_table VARCHAR(64); + DECLARE reference_column VARCHAR(64); + DECLARE is_array BOOLEAN; + DECLARE error_message VARCHAR(128); + DECLARE root_has_children BOOLEAN; + DECLARE references_cursor CURSOR FOR + SELECT c.TABLE_NAME, c.COLUMN_NAME, + LOWER(c.COLUMN_NAME) LIKE '%unitids' + FROM information_schema.COLUMNS c + JOIN information_schema.TABLES t + ON t.TABLE_SCHEMA = c.TABLE_SCHEMA AND t.TABLE_NAME = c.TABLE_NAME + WHERE c.TABLE_SCHEMA = 'zhgh_zjnu' AND t.TABLE_TYPE = 'BASE TABLE' + AND c.TABLE_NAME <> 'sys_unit' AND LEFT(c.TABLE_NAME, 4) <> 'bak_' + AND (LOWER(c.COLUMN_NAME) LIKE '%unitid' + OR LOWER(c.COLUMN_NAME) LIKE '%unitids' + OR LOWER(c.COLUMN_NAME) = 'undertakeid' + OR EXISTS ( + SELECT 1 FROM information_schema.KEY_COLUMN_USAGE k + WHERE k.TABLE_SCHEMA = c.TABLE_SCHEMA AND k.TABLE_NAME = c.TABLE_NAME + AND k.COLUMN_NAME = c.COLUMN_NAME + AND k.REFERENCED_TABLE_SCHEMA = 'zhgh_zjnu' + AND k.REFERENCED_TABLE_NAME = 'sys_unit' + AND k.REFERENCED_COLUMN_NAME = 'id')); + DECLARE CONTINUE HANDLER FOR NOT FOUND SET finished = TRUE; + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + ROLLBACK; + RESIGNAL; + END; + + IF NOT EXISTS (SELECT 1 FROM information_schema.TABLES + WHERE TABLE_SCHEMA = 'zhgh_zjnu' AND TABLE_NAME = 'sys_unit' AND ENGINE = 'InnoDB') THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'sys_unit 必须使用 InnoDB'; + END IF; + + IF NOT EXISTS (SELECT 1 FROM zhgh_zjnu.sys_unit + WHERE id = '1' AND name = '浙江师范大学' AND parentId = '0') + OR NOT EXISTS (SELECT 1 FROM zhgh_zjnu.sys_unit + WHERE id = '0' AND name = '组织机构') THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '当前根节点与指定数据不一致,停止合并'; + END IF; + + IF EXISTS (SELECT 1 FROM information_schema.TABLES + WHERE TABLE_SCHEMA = 'zhgh_zjnu' AND TABLE_NAME = 'bak_sys_unit_before_school_root_merge') THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '学校根节点合并备份已存在,不覆盖原备份'; + END IF; + + OPEN references_cursor; + reference_loop: LOOP + FETCH references_cursor INTO reference_table, reference_column, is_array; + IF finished THEN + LEAVE reference_loop; + END IF; + SET @school_root_reference = 0; + SET @school_root_check_sql = CONCAT( + 'SELECT EXISTS (SELECT 1 FROM `zhgh_zjnu`.`', REPLACE(reference_table, '`', '``'), + '` WHERE ', + IF(is_array, + CONCAT('JSON_CONTAINS(`', REPLACE(reference_column, '`', '``'), + '`, JSON_QUOTE(''1'')) OR JSON_CONTAINS(`', + REPLACE(reference_column, '`', '``'), '`, ''1'')'), + CONCAT('CAST(`', REPLACE(reference_column, '`', '``'), '` AS CHAR) = ''1''')), + ') INTO @school_root_reference'); + PREPARE school_root_check_stmt FROM @school_root_check_sql; + EXECUTE school_root_check_stmt; + DEALLOCATE PREPARE school_root_check_stmt; + IF @school_root_reference = 1 THEN + SET error_message = LEFT(CONCAT('学校 ID=1 仍被引用:', reference_table, '.', reference_column), 128); + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = error_message; + END IF; + END LOOP; + CLOSE references_cursor; + + CREATE TABLE zhgh_zjnu.bak_sys_unit_before_school_root_merge LIKE zhgh_zjnu.sys_unit; + INSERT INTO zhgh_zjnu.bak_sys_unit_before_school_root_merge SELECT * FROM zhgh_zjnu.sys_unit; + + DROP TEMPORARY TABLE IF EXISTS zhgh_zjnu.tmp_school_root_merge; + CREATE TEMPORARY TABLE zhgh_zjnu.tmp_school_root_merge LIKE zhgh_zjnu.sys_unit; + + START TRANSACTION; + INSERT INTO zhgh_zjnu.tmp_school_root_merge SELECT * FROM zhgh_zjnu.sys_unit WHERE id = '1'; + + -- 先调整下级,再删除已暂存的学校记录;现有 ID=0 本身不删除。 + UPDATE zhgh_zjnu.sys_unit SET parentId = '0' WHERE parentId = '1'; + DELETE FROM zhgh_zjnu.sys_unit WHERE id = '1'; + + SELECT EXISTS (SELECT 1 FROM zhgh_zjnu.sys_unit WHERE parentId = '0' AND id <> '0') + INTO root_has_children; + + UPDATE zhgh_zjnu.sys_unit target + JOIN zhgh_zjnu.tmp_school_root_merge school ON school.id = '1' + SET target.parentId = '0', + target.path = school.path, + target.name = school.name, + target.aliasName = school.aliasName, + target.unitcode = school.unitcode, + target.note = school.note, + target.address = school.address, + target.telephone = school.telephone, + target.email = school.email, + target.website = school.website, + target.location = school.location, + target.hasChildren = root_has_children, + target.logo = school.logo, + target.unitLevel = school.unitLevel, + target.unionId = school.unionId, + target.unionGroupId = school.unionGroupId, + target.createdBy = school.createdBy, + target.createdAt = school.createdAt, + target.updatedBy = school.updatedBy, + target.updatedAt = school.updatedAt, + target.delFlag = school.delFlag, + target.divisionCollegeCode = school.divisionCollegeCode, + target.institutionType = school.institutionType, + target.unitType = school.unitType, + target.unitTypeCode = 0 + WHERE target.id = '0'; + + COMMIT; + DROP TEMPORARY TABLE zhgh_zjnu.tmp_school_root_merge; +END$$ + +CALL zhgh_zjnu.merge_zjnu_school_root()$$ +DROP PROCEDURE zhgh_zjnu.merge_zjnu_school_root$$ + +DELIMITER ; diff --git a/src/main/resources/db/v4_from_v3/repair_v4_unit_tree_after_v3_import.sql b/src/main/resources/db/v4_from_v3/repair_v4_unit_tree_after_v3_import.sql new file mode 100644 index 0000000..24629dc --- /dev/null +++ b/src/main/resources/db/v4_from_v3/repair_v4_unit_tree_after_v3_import.sql @@ -0,0 +1,87 @@ +-- 修复 v3 机构导入后 v4 单位树查询失败的问题,只操作 v4 的 sys_unit。 +-- 目标库:zhgh_zjnu。独立执行本文件,期间不要并发修改机构数据。 +-- 补齐 unitTypeCode;缺少 ID=0 的根节点时新增“组织机构”容器。 +-- 保留现有机构 ID、path、工会关系及人员数据,原顶层机构挂到 ID=0 下。 +-- 备份表不覆盖;存在同名备份时停止,防止覆盖首次修复前的数据。 + +DELIMITER $$ + +DROP PROCEDURE IF EXISTS zhgh_zjnu.repair_v4_unit_tree_after_v3_import$$ +CREATE PROCEDURE zhgh_zjnu.repair_v4_unit_tree_after_v3_import() +BEGIN + DECLARE root_has_children BOOLEAN DEFAULT FALSE; + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + ROLLBACK; + RESIGNAL; + END; + + IF NOT EXISTS (SELECT 1 FROM information_schema.TABLES + WHERE TABLE_SCHEMA = 'zhgh_zjnu' AND TABLE_NAME = 'sys_unit' AND ENGINE = 'InnoDB') THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '目标 sys_unit 必须存在且使用 InnoDB'; + END IF; + + IF NOT EXISTS (SELECT 1 FROM zhgh_zjnu.sys_unit) THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '机构表为空,无法修复导入的单位树'; + END IF; + + IF EXISTS (SELECT 1 FROM information_schema.TABLES + WHERE TABLE_SCHEMA = 'zhgh_zjnu' AND TABLE_NAME = 'bak_sys_unit_before_tree_repair') THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '单位树修复备份已存在,本次停止,不覆盖原备份'; + END IF; + + IF EXISTS (SELECT 1 FROM zhgh_zjnu.sys_unit WHERE id = 'root') THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '机构 ID=root 与接口虚拟根冲突,需明确 ID 映射后再处理'; + END IF; + + IF NOT EXISTS (SELECT 1 FROM zhgh_zjnu.sys_unit WHERE id = '0') + AND EXISTS (SELECT 1 FROM zhgh_zjnu.sys_unit WHERE unitcode = '0') THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '机构编码 0 已被其他机构使用,停止新增根节点'; + END IF; + + IF EXISTS (SELECT 1 FROM zhgh_zjnu.sys_unit child + LEFT JOIN zhgh_zjnu.sys_unit parent ON parent.id = child.parentId + WHERE child.id <> '0' AND COALESCE(TRIM(child.parentId), '') NOT IN ('', '0') + AND (parent.id IS NULL OR child.id = child.parentId)) THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '存在缺失的父机构或非根节点自引用,不能自动确定归属'; + END IF; + + -- 建表放在修复事务之前;完整保留修复前机构数据。 + CREATE TABLE zhgh_zjnu.bak_sys_unit_before_tree_repair LIKE zhgh_zjnu.sys_unit; + INSERT INTO zhgh_zjnu.bak_sys_unit_before_tree_repair SELECT * FROM zhgh_zjnu.sys_unit; + + START TRANSACTION; + + -- v3 没有此字段,一级及以上容器取 0,二级及下级部门取 1。 + UPDATE zhgh_zjnu.sys_unit + SET unitTypeCode = CASE WHEN unitLevel >= 2 THEN 1 ELSE 0 END + WHERE unitTypeCode IS NULL; + + IF NOT EXISTS (SELECT 1 FROM zhgh_zjnu.sys_unit WHERE id = '0') THEN + INSERT INTO zhgh_zjnu.sys_unit + (id, parentId, path, name, unitcode, location, hasChildren, + unitLevel, unitTypeCode, delFlag, createdAt, updatedAt) + VALUES + ('0', '0', NULL, '组织机构', '0', 0, 0, 0, 0, 0, + CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED), + CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED)); + END IF; + + UPDATE zhgh_zjnu.sys_unit + SET parentId = '0' + WHERE id <> '0' AND COALESCE(TRIM(parentId), '') IN ('', '0'); + + SELECT EXISTS (SELECT 1 FROM zhgh_zjnu.sys_unit WHERE parentId = '0' AND id <> '0') + INTO root_has_children; + + UPDATE zhgh_zjnu.sys_unit + SET parentId = '0', unitTypeCode = 0, hasChildren = root_has_children + WHERE id = '0'; + + COMMIT; +END$$ + +CALL zhgh_zjnu.repair_v4_unit_tree_after_v3_import()$$ +DROP PROCEDURE zhgh_zjnu.repair_v4_unit_tree_after_v3_import$$ + +DELIMITER ; diff --git a/src/main/resources/db/v4_from_v3/replace_v4_unit_union_from_v3.sql b/src/main/resources/db/v4_from_v3/replace_v4_unit_union_from_v3.sql new file mode 100644 index 0000000..9b9084f --- /dev/null +++ b/src/main/resources/db/v4_from_v3/replace_v4_unit_union_from_v3.sql @@ -0,0 +1,191 @@ +-- 仅替换组织机构、组织工会,不修改人员、角色、工会小组及业务数据。 +-- 按项目配置:同一 MySQL 8 实例,源库 zhgh_zjnu_v3,目标库 zhgh_zjnu。 +-- 保留 v3 的主键和父子关系;v4 独有的机构字段仅在原 ID 相同时保留。 +-- 本脚本需独立执行,期间不得有其他连接写入相关数据,不得嵌入已有事务。 +-- 若现有人员或关联数据无法继续引用导入的 ID,直接报错,不自动清除关联。 +-- 备份表使用固定名称且不覆盖;已存在时拒绝执行,避免丢失首次备份。 + +DELIMITER $$ + +DROP PROCEDURE IF EXISTS zhgh_zjnu.replace_v4_unit_union_from_v3$$ +CREATE PROCEDURE zhgh_zjnu.replace_v4_unit_union_from_v3() +BEGIN + DECLARE backup_created BOOLEAN DEFAULT FALSE; + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + ROLLBACK; + RESIGNAL; + END; + DECLARE EXIT HANDLER FOR SQLWARNING + BEGIN + ROLLBACK; + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '字段转换产生警告,替换已回滚;请核对源字段长度和类型'; + END; + + IF (SELECT COUNT(*) FROM information_schema.TABLES + WHERE TABLE_SCHEMA = 'zhgh_zjnu' AND TABLE_NAME IN ('sys_unit', 'sys_union') + AND ENGINE = 'InnoDB') <> 2 THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '目标机构、工会表必须存在且使用 InnoDB'; + END IF; + + IF EXISTS (SELECT 1 FROM information_schema.TABLES + WHERE TABLE_SCHEMA = 'zhgh_zjnu' + AND TABLE_NAME IN ('bak_sys_unit_before_v3_replace', 'bak_sys_union_before_v3_replace')) THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '替换备份表已存在,本次停止,不覆盖原备份'; + END IF; + + DROP TEMPORARY TABLE IF EXISTS tmp_v3_replace_unit; + DROP TEMPORARY TABLE IF EXISTS tmp_v3_replace_union; + CREATE TEMPORARY TABLE tmp_v3_replace_unit LIKE zhgh_zjnu.sys_unit; + CREATE TEMPORARY TABLE tmp_v3_replace_union LIKE zhgh_zjnu.sys_union; + + IF NOT EXISTS (SELECT 1 FROM zhgh_zjnu_v3.sys_unit) + OR NOT EXISTS (SELECT 1 FROM zhgh_zjnu_v3.sys_union) THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'v3 机构或工会为空,停止清除 v4 数据'; + END IF; + + IF EXISTS (SELECT 1 FROM zhgh_zjnu_v3.sys_unit + WHERE id IS NULL OR TRIM(id) = '' OR CHAR_LENGTH(id) > 32 + OR CHAR_LENGTH(parentId) > 32 OR CHAR_LENGTH(unitcode) > 32) THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'v3 机构 ID、父 ID 或编码不符合 v4 长度要求'; + END IF; + + IF EXISTS (SELECT 1 FROM zhgh_zjnu_v3.sys_union + WHERE id IS NULL OR TRIM(id) = '') THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'v3 存在空工会 ID,停止替换'; + END IF; + + IF EXISTS (SELECT 1 FROM zhgh_zjnu_v3.sys_unit child + LEFT JOIN zhgh_zjnu_v3.sys_unit parent ON parent.id = child.parentId + WHERE COALESCE(child.parentId, '') NOT IN ('', '0') AND parent.id IS NULL) THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'v3 机构存在缺失的父机构,停止替换'; + END IF; + + -- 按目标表结构暂存,唯一键冲突和字段转换异常均发生在清除数据之前。 + INSERT INTO tmp_v3_replace_union + (id, name, unionCode, telephone, campus, createdBy, createdAt, updatedBy, updatedAt, delFlag) + SELECT id, unionname, unioncode, telephone, campus, '', + CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED), '', + CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED), 0 + FROM zhgh_zjnu_v3.sys_union; + + INSERT INTO tmp_v3_replace_unit + (id, parentId, path, name, aliasName, unitcode, note, address, telephone, + email, website, location, hasChildren, logo, unitLevel, unionId, unionGroupId, + createdBy, createdAt, updatedBy, updatedAt, delFlag) + SELECT id, parentId, path, name, aliasName, unitcode, note, address, telephone, + email, website, location, hasChildren, logo, unitlevel, unionid, unionGroupId, + createdBy, createdAt, updatedBy, updatedAt, COALESCE(delFlag, 0) + FROM zhgh_zjnu_v3.sys_unit; + + UPDATE tmp_v3_replace_unit incoming + JOIN zhgh_zjnu.sys_unit existing ON existing.id = incoming.id + SET incoming.divisionCollegeCode = existing.divisionCollegeCode, + incoming.institutionType = existing.institutionType, + incoming.unitType = existing.unitType, + incoming.unitTypeCode = existing.unitTypeCode; + + IF EXISTS (SELECT 1 FROM tmp_v3_replace_unit unit + LEFT JOIN tmp_v3_replace_union union_info ON union_info.id = unit.unionId + WHERE COALESCE(unit.unionId, '') <> '' AND union_info.id IS NULL) THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'v3 机构引用的工会不存在,停止替换'; + END IF; + + -- 人员暂时不动:机构 ID、三级机构 ID、组织路径必须仍然有效。 + IF EXISTS (SELECT 1 FROM zhgh_zjnu.sys_user user_info + LEFT JOIN tmp_v3_replace_unit unit ON unit.id = user_info.unitId + WHERE COALESCE(user_info.unitId, '') <> '' + AND (unit.id IS NULL OR (COALESCE(user_info.unitPath, '') <> '' + AND NOT (user_info.unitPath <=> unit.path)))) THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '现有人员机构 ID 或路径与 v3 不兼容;人员不动时不能直接替换'; + END IF; + + IF EXISTS (SELECT 1 FROM zhgh_zjnu.sys_user user_info + LEFT JOIN tmp_v3_replace_unit unit ON unit.id = user_info.threeUnitId + WHERE COALESCE(user_info.threeUnitId, '') <> '' AND unit.id IS NULL) THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '现有人员的三级机构不在 v3 中,停止替换'; + END IF; + + IF EXISTS (SELECT 1 FROM zhgh_zjnu.sys_user_unit relation_info + LEFT JOIN tmp_v3_replace_unit unit ON unit.id = relation_info.unitId + WHERE COALESCE(relation_info.unitId, '') <> '' AND unit.id IS NULL) THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '现有人员兼职机构不在 v3 中,停止替换'; + END IF; + + IF EXISTS (SELECT 1 FROM zhgh_zjnu.sys_role role_info + LEFT JOIN tmp_v3_replace_unit unit ON unit.id = role_info.unitid + WHERE COALESCE(role_info.unitid, '') <> '' AND unit.id IS NULL) THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '现有角色所属机构不在 v3 中,停止替换'; + END IF; + + IF EXISTS (SELECT 1 FROM zhgh_zjnu.sys_user_role relation_info + LEFT JOIN tmp_v3_replace_unit unit ON unit.id = relation_info.unitId + LEFT JOIN tmp_v3_replace_union union_info ON union_info.id = relation_info.unionId + WHERE (COALESCE(relation_info.unitId, '') <> '' AND unit.id IS NULL) + OR (COALESCE(relation_info.unionId, '') <> '' AND union_info.id IS NULL)) THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '现有用户权限范围引用了 v3 中不存在的机构或工会'; + END IF; + + IF EXISTS (SELECT 1 FROM zhgh_zjnu.sys_branch_union_user_permission permission_info + LEFT JOIN tmp_v3_replace_union union_info ON union_info.id = permission_info.unionId + WHERE COALESCE(permission_info.unionId, '') <> '' AND union_info.id IS NULL) THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '现有分工会人员授权引用的工会不在 v3 中'; + END IF; + + IF EXISTS (SELECT 1 FROM zhgh_zjnu.special_staff staff + LEFT JOIN tmp_v3_replace_union union_info ON BINARY union_info.id = BINARY staff.unionRelationUnionId + WHERE COALESCE(staff.unionRelationUnionId, '') <> '' AND union_info.id IS NULL) THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '特殊人员工会关系引用的工会不在 v3 中'; + END IF; + + IF EXISTS (SELECT 1 FROM zhgh_zjnu.sys_union_cadre cadre + LEFT JOIN tmp_v3_replace_union union_info ON union_info.id = cadre.unionId + WHERE COALESCE(cadre.unionId, '') <> '' AND union_info.id IS NULL) THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '现有工会干部引用的工会不在 v3 中'; + END IF; + + -- 工会小组不在本次替换范围,不能写入缺失或归属不一致的小组 ID。 + IF EXISTS (SELECT 1 FROM tmp_v3_replace_unit unit + LEFT JOIN zhgh_zjnu.sys_union_group group_info ON group_info.id = unit.unionGroupId + WHERE COALESCE(unit.unionGroupId, '') <> '' + AND (group_info.id IS NULL OR NOT (group_info.unionId <=> unit.unionId))) THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'v3 机构的小组关系与 v4 不兼容,需另行明确小组迁移范围'; + END IF; + + IF EXISTS (SELECT 1 FROM zhgh_zjnu.sys_union_group group_info + LEFT JOIN tmp_v3_replace_union union_info ON union_info.id = group_info.unionId + WHERE COALESCE(group_info.unionId, '') <> '' AND union_info.id IS NULL) THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '现有工会小组所属工会不在 v3 中'; + END IF; + + IF EXISTS (SELECT 1 FROM zhgh_zjnu.sys_union_group group_info + JOIN JSON_TABLE(COALESCE(group_info.unitIds, JSON_ARRAY()), '$[*]' + COLUMNS (unitId VARCHAR(100) PATH '$')) binding + LEFT JOIN tmp_v3_replace_unit unit ON BINARY unit.id = BINARY binding.unitId + WHERE unit.id IS NULL OR NOT (unit.unionId <=> group_info.unionId)) THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '现有工会小组绑定机构与 v3 不兼容,停止替换'; + END IF; + + -- DDL 放在事务之前,避免创建备份表导致替换事务被隐式提交。 + CREATE TABLE zhgh_zjnu.bak_sys_unit_before_v3_replace LIKE zhgh_zjnu.sys_unit; + CREATE TABLE zhgh_zjnu.bak_sys_union_before_v3_replace LIKE zhgh_zjnu.sys_union; + SET backup_created = TRUE; + + START TRANSACTION; + INSERT INTO zhgh_zjnu.bak_sys_unit_before_v3_replace SELECT * FROM zhgh_zjnu.sys_unit; + INSERT INTO zhgh_zjnu.bak_sys_union_before_v3_replace SELECT * FROM zhgh_zjnu.sys_union; + + DELETE FROM zhgh_zjnu.sys_unit; + DELETE FROM zhgh_zjnu.sys_union; + INSERT INTO zhgh_zjnu.sys_union SELECT * FROM tmp_v3_replace_union; + INSERT INTO zhgh_zjnu.sys_unit SELECT * FROM tmp_v3_replace_unit; + COMMIT; + + DROP TEMPORARY TABLE tmp_v3_replace_unit; + DROP TEMPORARY TABLE tmp_v3_replace_union; +END$$ + +CALL zhgh_zjnu.replace_v4_unit_union_from_v3()$$ +DROP PROCEDURE zhgh_zjnu.replace_v4_unit_union_from_v3$$ + +DELIMITER ;