feat: 人资+机构同步
This commit is contained in:
@@ -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<Sys_unit> list = sysUnitService.query(cnd);
|
||||
String rootId = StrUtil.isBlank(pid) ? DataCenterUtil.findRootUnit(list).getId() : pid;
|
||||
|
||||
List<TreeNode<String>> 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()
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Sys_unit> 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<Sys_unit> implem
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void updateUnits(List<String> unitIds) {
|
||||
String accessToken = getDmpAccessToken();
|
||||
List<JSONObject> rawDataList = distinctDmpUnits(pullDmpUnits(getDmpUnitUrl(), accessToken));
|
||||
Sys_unit root = DataCenterUtil.findRootUnit(query(Cnd.NEW()));
|
||||
List<JSONObject> rawDataList = distinctDmpUnits(DataCenterUtil.pullData(getDmpUnitUrl()));
|
||||
if (rawDataList.isEmpty()) {
|
||||
throw new BaseException("当前未获取到单位数据,中止本次同步");
|
||||
}
|
||||
if (StrUtil.isBlank(root.getPath())) {
|
||||
root.setPath(getUnitPath(""));
|
||||
dao().updateIgnoreNull(root);
|
||||
}
|
||||
Set<String> 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<Sys_unit> implem
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页拉取单位接口数据。
|
||||
* 入参 url 为单位接口地址,accessToken 为信息中心令牌;
|
||||
* 请求 body 包含 access_token、per_page、page;
|
||||
* 返回值为 result.data 合并后的单位原始 JSON 列表。
|
||||
*/
|
||||
private List<JSONObject> pullDmpUnits(String url, String accessToken) {
|
||||
List<JSONObject> 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<Sys_unit> 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<Sys_unit> implem
|
||||
|
||||
/**
|
||||
* 保存或更新单位。
|
||||
* DWDM 对应系统单位 id/unitcode,DWMC 对应名称,LSDWH 对应父级单位号,DWCC 对应信息中心原始单位层级。
|
||||
* DWDM 对应系统单位 id/unitcode,DWMC 对应名称,LSDWDM 对应父级单位号,DWCC 对应信息中心原始单位层级。
|
||||
*/
|
||||
private void saveOrUpdateUnit(JSONObject raw, Set<String> parentCodes) {
|
||||
private void saveOrUpdateUnit(JSONObject raw, Set<String> 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<Sys_unit> 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<String> 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<Sys_unit> 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<String> 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<Sys_unit> 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");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -133,7 +133,7 @@ public class SysDataUserAllUpdateServiceImpl implements SysDataUserUpdateService
|
||||
Sys_role publicRole = sysRoleService.getByCode(RoleConstant.PUBLIC);
|
||||
|
||||
// 获取单位信息,后续判断单位变更
|
||||
List<Sys_unit> unitList = dao.query(Sys_unit.class, Cnd.where("unitTypeCode", "=", "1"));
|
||||
List<Sys_unit> unitList = dao.query(Sys_unit.class, Cnd.NEW());
|
||||
unitIdNameMap = unitList.stream().collect(Collectors.toMap(Sys_unit::getId, Sys_unit::getName));
|
||||
|
||||
// 查询数据源
|
||||
|
||||
+50
-38
@@ -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<Sys_user_source> userSources = dao.query(Sys_user_source.class, cnd);
|
||||
|
||||
// 先补齐机构,再写入人员;补建机构统一挂到本地唯一顶级机构。
|
||||
List<Sys_unit> sysUnits = dao.query(Sys_unit.class, Cnd.NEW());
|
||||
var unitMap = sysUnits.stream().collect(java.util.stream.Collectors.toMap(Sys_unit::getId, v -> v));
|
||||
List<Sys_unit> 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<Sys_user> sysUsers = BeanUtil.copyToList(userSources, Sys_user.class);
|
||||
for (Sys_user sysUser : sysUsers) {
|
||||
@@ -72,9 +112,9 @@ public class SysDataUserIncrUpdateServiceImpl implements SysDataUserUpdateServic
|
||||
}
|
||||
|
||||
List<List<Sys_user>> splitSysUsers = ListUtil.split(sysUsers, 500);
|
||||
List<CompletableFuture<List<Sys_user>>> splitSysUsersFutures = splitSysUsers.stream().map(v -> CompletableFuture.supplyAsync(() -> dao.fastInsert(v))).toList();
|
||||
//等待任务全部完成
|
||||
AsyncUtil.waitAll(CompletableFuture.allOf(splitSysUsersFutures.toArray(new CompletableFuture[0])));
|
||||
for (List<Sys_user> batch : splitSysUsers) {
|
||||
dao.fastInsert(batch);
|
||||
}
|
||||
|
||||
// 增量更新只处理新增人员,需要同步写入用户历史记录;列表的变更类型、变更时间通过 sys_user_history 最新记录展示。
|
||||
List<Sys_user_history> histories = BeanUtil.copyToList(userSources, Sys_user_history.class);
|
||||
@@ -85,8 +125,9 @@ public class SysDataUserIncrUpdateServiceImpl implements SysDataUserUpdateServic
|
||||
history.setChangeOrigin(MemberChangeOrigin.SYSTEM.name());
|
||||
}
|
||||
List<List<Sys_user_history>> splitHistories = ListUtil.split(histories, 500);
|
||||
List<CompletableFuture<List<Sys_user_history>>> splitHistoryFutures = splitHistories.stream().map(v -> CompletableFuture.supplyAsync(() -> dao.fastInsert(v))).toList();
|
||||
AsyncUtil.waitAll(CompletableFuture.allOf(splitHistoryFutures.toArray(new CompletableFuture[0])));
|
||||
for (List<Sys_user_history> 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<Sys_unit> sysUnits = dao.query(Sys_unit.class, Cnd.where(Sys_unit::getUnitLevel, "=", 2));
|
||||
List<String> 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<Sys_unit> 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()) {
|
||||
|
||||
@@ -52,8 +52,6 @@ import java.util.stream.Collectors;
|
||||
public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source> 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<Sys_user_source>
|
||||
// 字典码表Map,key为父级编码,value为子级字典列表
|
||||
Map<String, List<Sys_data_dict>> dataDictMap = dataDictList.stream().collect(Collectors.groupingBy(Sys_data_dict::getParentCode));
|
||||
|
||||
String accessToken = getDmpAccessToken();
|
||||
List<JSONObject> rawDataList = new ArrayList<>();
|
||||
rawDataList.addAll(pullDmpUsers(getDmpUrl(DMP_TEACHER_KEY), accessToken, "在岗教职工"));
|
||||
rawDataList.addAll(pullDmpUsers(getDmpUrl(DMP_DISPATCH_KEY), accessToken, "在岗劳务派遣"));
|
||||
rawDataList = distinctDmpUsers(rawDataList);
|
||||
List<JSONObject> rawDataList = distinctDmpUsers(DataCenterUtil.pullData(getDmpUrl(DMP_TEACHER_KEY)));
|
||||
|
||||
if (rawDataList.isEmpty()) {
|
||||
log.warn("当前未获取到人员数据");
|
||||
throw new BaseException("当前未获取到人员数据,中止本次同步");
|
||||
}
|
||||
|
||||
Date nowDate = new Date();
|
||||
|
||||
// 人员单位号先与单位表批量比对,缺失单位只同步一次,避免逐人查询单位表。
|
||||
Set<String> sourceUnitIds = rawDataList.stream()
|
||||
.map(raw -> raw.getStr("SZDWH"))
|
||||
.map(raw -> raw.getStr("SZDWDM"))
|
||||
.filter(StrUtil::isNotBlank)
|
||||
.collect(Collectors.toSet());
|
||||
Map<String, Sys_unit> unitMap = dao().query(Sys_unit.class, Cnd.NEW()).stream()
|
||||
@@ -146,25 +140,34 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source>
|
||||
.collect(Collectors.toMap(Sys_unit::getId, unit -> unit));
|
||||
}
|
||||
|
||||
// 按单位表的实际等级判断:三级单位为3、直属二级单位为2;单位号位数不参与判断。
|
||||
String rootId = DataCenterUtil.findRootUnit(new ArrayList<>(unitMap.values())).getId();
|
||||
// 深层机构沿父级查找二级单位,并保留所属三级单位。
|
||||
Map<String, String> tertiaryParentUnitIds = new HashMap<>();
|
||||
Map<String, String> 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<String> 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<String, Sys_unit> resolvedUnitMap = unitMap;
|
||||
// 赋值
|
||||
List<Sys_user_source> latestSourceList = rawDataList.stream().map(raw -> {
|
||||
Sys_user_source sysUser = new Sys_user_source();
|
||||
@@ -182,35 +185,44 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source>
|
||||
.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<Sys_user_source>
|
||||
|
||||
/**
|
||||
* 规范化信息中心返回的来校时间。
|
||||
* 入参 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<Sys_user_source>
|
||||
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<Sys_user_source>
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页拉取单个人员接口数据。
|
||||
* 入参 url 为人员接口地址,accessToken 为信息中心令牌,sourceName 用于日志区分接口来源。
|
||||
* 返回值为该接口所有分页合并后的原始人员 JSON 列表。
|
||||
*/
|
||||
private List<JSONObject> pullDmpUsers(String url, String accessToken, String sourceName) {
|
||||
List<JSONObject> 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<JSONObject> distinctDmpUsers(List<JSONObject> rawDataList) {
|
||||
@@ -360,7 +285,7 @@ public class SysDataUserPullServiceImpl extends BaseServiceImpl<Sys_user_source>
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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<Sys_unit> units) {
|
||||
List<Sys_unit> 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<JSONObject> 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<JSONObject> 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<String, ?> 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ;
|
||||
@@ -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 ;
|
||||
@@ -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 ;
|
||||
Reference in New Issue
Block a user