✨ 组织与人员支持OA增量同步,并完善用户启停与性别空值

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-10 13:56:29 +08:00
parent e20afc0d55
commit 4e7193893f
22 changed files with 423 additions and 100 deletions
@@ -93,7 +93,8 @@ public class DeptController extends BladeController {
@PreAuth(menu = "dept")
@GetMapping("/list")
@Parameters({
@Parameter(name = "deptName", description = "部门名称", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
@Parameter(name = "deptName", description = "组织名称", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
@Parameter(name = "shortName", description = "组织简称", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
@Parameter(name = "fullName", description = "部门全称", in = ParameterIn.QUERY, schema = @Schema(type = "string"))
})
@ApiOperationSupport(order = 2)
@@ -108,7 +109,8 @@ public class DeptController extends BladeController {
@PreAuth(menu = "dept")
@GetMapping("/lazy-list")
@Parameters({
@Parameter(name = "deptName", description = "部门名称", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
@Parameter(name = "deptName", description = "组织名称", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
@Parameter(name = "shortName", description = "组织简称", in = ParameterIn.QUERY, schema = @Schema(type = "string")),
@Parameter(name = "fullName", description = "部门全称", in = ParameterIn.QUERY, schema = @Schema(type = "string"))
})
@ApiOperationSupport(order = 3)
@@ -195,8 +197,10 @@ public class DeptController extends BladeController {
@Operation(summary = "同步OA公司")
public R<OaOrgSyncPageVO> syncOaCompany(
@RequestParam(defaultValue = "1") Integer current,
@RequestParam(defaultValue = "20") Integer size) {
return R.data(oaSyncService.syncCompanyPage(current, size));
@RequestParam(defaultValue = "20") Integer size,
@RequestParam(defaultValue = "false") Boolean incremental,
@RequestParam(required = false) String modifiedSince) {
return R.data(oaSyncService.syncCompanyPage(current, size, Boolean.TRUE.equals(incremental), modifiedSince));
}
/**
@@ -208,8 +212,10 @@ public class DeptController extends BladeController {
@Operation(summary = "同步OA部门")
public R<OaOrgSyncPageVO> syncOaDepartment(
@RequestParam(defaultValue = "1") Integer current,
@RequestParam(defaultValue = "20") Integer size) {
return R.data(oaSyncService.syncDepartmentPage(current, size));
@RequestParam(defaultValue = "20") Integer size,
@RequestParam(defaultValue = "false") Boolean incremental,
@RequestParam(required = false) String modifiedSince) {
return R.data(oaSyncService.syncDepartmentPage(current, size, Boolean.TRUE.equals(incremental), modifiedSince));
}
/**
@@ -171,8 +171,10 @@ public class UserController {
@Operation(summary = "同步OA人员")
public R<OaPersonSyncPageVO> syncIamAccounts(
@RequestParam(defaultValue = "1") Integer current,
@RequestParam(defaultValue = "20") Integer size) {
return R.data(oaSyncService.syncPersonFromUserList(current, size));
@RequestParam(defaultValue = "20") Integer size,
@RequestParam(defaultValue = "false") Boolean incremental,
@RequestParam(required = false) String modifiedSince) {
return R.data(oaSyncService.syncPersonFromUserList(current, size, Boolean.TRUE.equals(incremental), modifiedSince));
}
/**
@@ -186,6 +188,18 @@ public class UserController {
return R.status(userService.updateUser(user));
}
/**
* 启用或停用
*/
@IsAdmin
@PostMapping("/change-status")
@ApiOperationSupport(order = 6)
@Operation(summary = "启用停用", description = "传入id和status")
public R changeStatus(@Parameter(description = "用户主键", required = true) @RequestParam Long id,
@Parameter(description = "状态:1启用,0停用", required = true) @RequestParam Integer status) {
return R.status(userService.changeUserStatus(id, status));
}
/**
* 删除
*/
@@ -47,7 +47,8 @@ public interface DeptConvert {
// 父公司id转为父部门编码,要拼接前缀
@Mapping(source = "supsubcomid", target = "parentCode")
@Mapping(source = "created", target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
@Mapping(source = "modified", target = "updateTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
// OA modified 单独落库,用于增量比对,避免覆盖本系统 updateTime
@Mapping(source = "modified", target = "oaModified")
Dept baseConvert(OACompanyResponse company);
@Mapping(source = "departmentname", target = "deptName")
@@ -57,7 +58,8 @@ public interface DeptConvert {
// 所属子公司id转为所属公司编码,要拼接前缀
@Mapping(source = "subcompanyid1", target = "belongCompanyCode")
@Mapping(source = "created", target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
@Mapping(source = "modified", target = "updateTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
// OA modified 单独落库,用于增量比对,避免覆盖本系统 updateTime
@Mapping(source = "modified", target = "oaModified")
Dept baseConvert(OADepartmentResponse department);
/**
@@ -70,6 +70,7 @@ public interface UserConvert {
@Mapping(source = "lastname", target = "realName")
@Mapping(source = "mobile", target = "phone")
@Mapping(source = "email", target = "email")
@Mapping(source = "modified", target = "oaModified")
User baseConvert(OAPersonResponse person);
@Mapping(source = "subcompanyname", target = "companyName")
@@ -80,6 +80,9 @@
<if test="param3.deptName!=null and param3.deptName!=''">
and dept.dept_name like concat(concat('%', #{param3.deptName}),'%')
</if>
<if test="param3.shortName!=null and param3.shortName!=''">
and dept.short_name like concat(concat('%', #{param3.shortName}),'%')
</if>
<if test="param3.fullName!=null and param3.fullName!=''">
and dept.full_name like concat(concat('%', #{param3.fullName}),'%')
</if>
@@ -32,27 +32,33 @@ public interface IOASyncService {
/**
* 按页从 OA 人员接口同步组织与人员
*
* @param current 当前页,从 1 开始
* @param size 每页条数
* @param current 当前页,从 1 开始
* @param size 每页条数
* @param incremental 是否增量同步(按本地最大 oaModified 过滤 OA)
* @param modifiedSince 增量起始时间;首屏可空,后续分页回传首屏返回值
* @return 本页同步结果
*/
OaPersonSyncPageVO syncPersonFromUserList(int current, int size);
OaPersonSyncPageVO syncPersonFromUserList(int current, int size, boolean incremental, String modifiedSince);
/**
* 按页从 OA 公司接口同步公司
*
* @param current 当前页,从 1 开始
* @param size 每页条数
* @param current 当前页,从 1 开始
* @param size 每页条数
* @param incremental 是否增量同步(按本地最大 oaModified 过滤 OA)
* @param modifiedSince 增量起始时间;首屏可空,后续分页回传首屏返回值
* @return 本页同步结果
*/
OaOrgSyncPageVO syncCompanyPage(int current, int size);
OaOrgSyncPageVO syncCompanyPage(int current, int size, boolean incremental, String modifiedSince);
/**
* 按页从 OA 部门接口同步部门;最后一页完成后更新祖级列表
*
* @param current 当前页,从 1 开始
* @param size 每页条数
* @param current 当前页,从 1 开始
* @param size 每页条数
* @param incremental 是否增量同步(按本地最大 oaModified 过滤 OA)
* @param modifiedSince 增量起始时间;首屏可空,后续分页回传首屏返回值
* @return 本页同步结果
*/
OaOrgSyncPageVO syncDepartmentPage(int current, int size);
OaOrgSyncPageVO syncDepartmentPage(int current, int size, boolean incremental, String modifiedSince);
}
@@ -93,6 +93,15 @@ public interface IUserService extends BaseService<User> {
*/
boolean updateUser(User user);
/**
* 启用或停用用户(租户守卫校验用户归属,仅更新状态)
*
* @param userId 用户主键
* @param status 状态:1 启用,0 停用
* @return 是否成功
*/
boolean changeUserStatus(Long userId, Integer status);
/**
* 修改用户基本信息(仅限当前登录用户自身,忽略角色 / 部门 / 岗位 / 账号等敏感字段)
*
@@ -434,6 +434,10 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
if (dept.getIsPlatformCompany() == null) {
dept.setIsPlatformCompany(0);
}
// 排序未填时默认 1
if (dept.getSort() == null) {
dept.setSort(1);
}
if (Func.isEmpty(dept.getTenantId())) {
throw new ServiceException("租户ID不能为空");
}
@@ -60,6 +60,7 @@ public class OASyncServiceImpl implements IOASyncService {
private final IOAClient oaClient;
private final DeptConvert deptConvert;
private final IDeptService deptService;
private final IUserService userService;
private final IRoleService roleService;
private final IMKPushService mkPushService;
private final IDataSyncRecordService dataSyncRecordService;
@@ -124,10 +125,10 @@ public class OASyncServiceImpl implements IOASyncService {
@Transactional(rollbackFor = Exception.class)
@Override
public OaPersonSyncPageVO syncPersonFromUserList(int current, int size) {
public OaPersonSyncPageVO syncPersonFromUserList(int current, int size, boolean incremental, String modifiedSince) {
try {
ComposeLogUtil.addLog(log);
return this.syncPersonFromOaPage(current, size);
return this.syncPersonFromOaPage(current, size, incremental, modifiedSince);
} finally {
ComposeLogUtil.removeLastLog();
}
@@ -135,10 +136,10 @@ public class OASyncServiceImpl implements IOASyncService {
@Transactional(rollbackFor = Exception.class)
@Override
public OaOrgSyncPageVO syncCompanyPage(int current, int size) {
public OaOrgSyncPageVO syncCompanyPage(int current, int size, boolean incremental, String modifiedSince) {
try {
ComposeLogUtil.addLog(log);
return this.syncCompanyFromOaPage(current, size);
return this.syncCompanyFromOaPage(current, size, incremental, modifiedSince);
} finally {
ComposeLogUtil.removeLastLog();
}
@@ -146,10 +147,10 @@ public class OASyncServiceImpl implements IOASyncService {
@Transactional(rollbackFor = Exception.class)
@Override
public OaOrgSyncPageVO syncDepartmentPage(int current, int size) {
public OaOrgSyncPageVO syncDepartmentPage(int current, int size, boolean incremental, String modifiedSince) {
try {
ComposeLogUtil.addLog(log);
return this.syncDepartmentFromOaPage(current, size);
return this.syncDepartmentFromOaPage(current, size, incremental, modifiedSince);
} finally {
ComposeLogUtil.removeLastLog();
}
@@ -280,14 +281,17 @@ public class OASyncServiceImpl implements IOASyncService {
/**
* 按页从 OA 人员列表同步组织与人员
*
* @param current 当前页
* @param size 每页条数
* @param current 当前页
* @param size 每页条数
* @param incremental 是否增量同步
* @param modifiedSince 增量起始时间(后续分页回传)
* @return 本页同步结果
*/
private OaPersonSyncPageVO syncPersonFromOaPage(int current, int size) {
private OaPersonSyncPageVO syncPersonFromOaPage(int current, int size, boolean incremental, String modifiedSince) {
int pageNo = current < 1 ? 1 : current;
int pageSize = size < 1 ? 20 : Math.min(size, 200);
OAPersonSearch personSearch = buildPersonSearch(null);
IncrementalStart incrementalStart = resolvePersonIncrementalStart(incremental, modifiedSince);
OAPersonSearch personSearch = buildPersonSearch(incrementalStart.getStartTime());
personSearch.setCurPage(pageNo);
personSearch.setPageSize(pageSize);
OAResponse<OAPersonResponse> oaResponse = oaClient.queryPersonPage(new OASearch<>(personSearch));
@@ -311,26 +315,31 @@ public class OASyncServiceImpl implements IOASyncService {
pageVO.setFetchedCount(oaPersons.size());
pageVO.setSyncedCount(personSyncCount.getSyncedCount());
pageVO.setSkippedCount(personSyncCount.getSkippedCount());
pageVO.setModifiedSince(incrementalStart.getModifiedSince());
boolean finished = oaPersons.isEmpty()
|| oaPersons.size() < pageSize
|| (long) pageNo * pageSize >= totalSize;
pageVO.setFinished(finished);
ComposeLogUtil.getLastLog().info("OA人员分页同步完成 {}/{},成功{},跳过{}",
pageNo, totalSize, personSyncCount.getSyncedCount(), personSyncCount.getSkippedCount());
ComposeLogUtil.getLastLog().info("OA人员分页同步完成 mode={} {}/{},成功{},跳过{}",
incremental ? "incremental" : "full", pageNo, totalSize,
personSyncCount.getSyncedCount(), personSyncCount.getSkippedCount());
return pageVO;
}
/**
* 按页从 OA 公司接口同步公司
*
* @param current 当前页
* @param size 每页条数
* @param current 当前页
* @param size 每页条数
* @param incremental 是否增量同步
* @param modifiedSince 增量起始时间(后续分页回传)
* @return 本页同步结果
*/
private OaOrgSyncPageVO syncCompanyFromOaPage(int current, int size) {
private OaOrgSyncPageVO syncCompanyFromOaPage(int current, int size, boolean incremental, String modifiedSince) {
int pageNo = current < 1 ? 1 : current;
int pageSize = size < 1 ? 20 : Math.min(size, 200);
OACompanySearch companySearch = buildCompanySearch(null);
IncrementalStart incrementalStart = resolveIncrementalStart(incremental, modifiedSince, DeptCategory.COMPANY);
OACompanySearch companySearch = buildCompanySearch(incrementalStart.getStartTime());
companySearch.setCurPage(pageNo);
companySearch.setPageSize(pageSize);
OAResponse<OACompanyResponse> oaResponse = oaClient.queryCompanyPage(new OASearch<>(companySearch));
@@ -347,6 +356,7 @@ public class OASyncServiceImpl implements IOASyncService {
ComposeLogUtil.getLastLog().warn("同步公司,本页未处理数据:{}", JSON.toJSONString(syncCount.getNotHandledList()));
}
OaOrgSyncPageVO pageVO = buildOrgSyncPageVO("company", pageNo, pageSize, totalSize, oaCompanies.size(), syncCount);
pageVO.setModifiedSince(incrementalStart.getModifiedSince());
if (Boolean.TRUE.equals(pageVO.getFinished())) {
// 公司全部分批入库后:重建层级,并删除上级不存在的数据
deptService.rebuildOaHierarchy();
@@ -354,19 +364,22 @@ public class OASyncServiceImpl implements IOASyncService {
}
CacheUtil.clear(SYS_CACHE);
CacheUtil.clear(SYS_CACHE, Boolean.FALSE);
ComposeLogUtil.getLastLog().info("OA公司分页同步完成 {}/{},成功{},跳过{}",
pageNo, totalSize, syncCount.getSyncedCount(), syncCount.getSkippedCount());
ComposeLogUtil.getLastLog().info("OA公司分页同步完成 mode={} {}/{},成功{},跳过{}",
incremental ? "incremental" : "full", pageNo, totalSize,
syncCount.getSyncedCount(), syncCount.getSkippedCount());
return pageVO;
}
/**
* 按页从 OA 部门接口同步部门
*
* @param current 当前页
* @param size 每页条数
* @param current 当前页
* @param size 每页条数
* @param incremental 是否增量同步
* @param modifiedSince 增量起始时间(后续分页回传)
* @return 本页同步结果
*/
private OaOrgSyncPageVO syncDepartmentFromOaPage(int current, int size) {
private OaOrgSyncPageVO syncDepartmentFromOaPage(int current, int size, boolean incremental, String modifiedSince) {
int pageNo = current < 1 ? 1 : current;
int pageSize = size < 1 ? 20 : Math.min(size, 200);
String subCompanyIds = getOaSubCompanyIds(true);
@@ -376,7 +389,8 @@ public class OASyncServiceImpl implements IOASyncService {
emptyPageVO.setFinished(true);
return emptyPageVO;
}
OADepartmentSearch departmentSearch = buildDepartmentSearch(null, subCompanyIds);
IncrementalStart incrementalStart = resolveIncrementalStart(incremental, modifiedSince, DeptCategory.DEPT);
OADepartmentSearch departmentSearch = buildDepartmentSearch(incrementalStart.getStartTime(), subCompanyIds);
departmentSearch.setCurPage(pageNo);
departmentSearch.setPageSize(pageSize);
OAResponse<OADepartmentResponse> oaResponse = oaClient.queryDepartmentPage(new OASearch<>(departmentSearch));
@@ -393,6 +407,7 @@ public class OASyncServiceImpl implements IOASyncService {
ComposeLogUtil.getLastLog().warn("同步部门,本页未处理数据:{}", JSON.toJSONString(syncCount.getNotHandledList()));
}
OaOrgSyncPageVO pageVO = buildOrgSyncPageVO("department", pageNo, pageSize, totalSize, oaDepartments.size(), syncCount);
pageVO.setModifiedSince(incrementalStart.getModifiedSince());
if (Boolean.TRUE.equals(pageVO.getFinished())) {
// 部门分批同步完成后,重建层级、清理无法挂载数据并刷新祖级列表
deptService.rebuildOaHierarchy();
@@ -401,11 +416,112 @@ public class OASyncServiceImpl implements IOASyncService {
}
CacheUtil.clear(SYS_CACHE);
CacheUtil.clear(SYS_CACHE, Boolean.FALSE);
ComposeLogUtil.getLastLog().info("OA部门分页同步完成 {}/{},成功{},跳过{}",
pageNo, totalSize, syncCount.getSyncedCount(), syncCount.getSkippedCount());
ComposeLogUtil.getLastLog().info("OA部门分页同步完成 mode={} {}/{},成功{},跳过{}",
incremental ? "incremental" : "full", pageNo, totalSize,
syncCount.getSyncedCount(), syncCount.getSkippedCount());
return pageVO;
}
/**
* 解析增量同步起始时间:优先使用前端回传值,保证同阶段分页起点不变
*/
private IncrementalStart resolveIncrementalStart(boolean incremental, String modifiedSince, DeptCategory deptCategory) {
if (!incremental) {
return IncrementalStart.empty();
}
if (StringUtils.isNotBlank(modifiedSince)) {
try {
Date startTime = DateUtil.parse(modifiedSince.trim(), DateUtil.PATTERN_DATETIME);
return new IncrementalStart(startTime, modifiedSince.trim());
} catch (Exception exception) {
ComposeLogUtil.getLastLog().warn("解析回传 modifiedSince 失败:{},重新计算", modifiedSince);
}
}
Dept latest = deptService.getOne(Wrappers.<Dept>lambdaQuery()
.select(Dept::getOaModified)
.eq(Dept::getIsOa, YES)
.eq(Dept::getDeptCategory, deptCategory.getCode())
.isNotNull(Dept::getOaModified)
.ne(Dept::getOaModified, "")
.orderByDesc(Dept::getOaModified)
.last("limit 1"), false);
if (latest == null || StringUtils.isBlank(latest.getOaModified())) {
ComposeLogUtil.getLastLog().info("本地无 {} oaModified 记录,增量同步按全量拉取后按字段比对跳过",
deptCategory.getName());
return IncrementalStart.empty();
}
try {
Date startTime = DateUtil.parse(latest.getOaModified(), DateUtil.PATTERN_DATETIME);
ComposeLogUtil.getLastLog().info("增量同步{} OA modified 起始时间:{}",
deptCategory.getName(), latest.getOaModified());
return new IncrementalStart(startTime, latest.getOaModified());
} catch (Exception exception) {
ComposeLogUtil.getLastLog().warn("解析本地最大 oaModified 失败:{},回退为全量拉取", latest.getOaModified());
return IncrementalStart.empty();
}
}
/**
* 人员增量同步起始时间:优先回传值,否则取本地 OA 用户最大 oaModified
*/
private IncrementalStart resolvePersonIncrementalStart(boolean incremental, String modifiedSince) {
if (!incremental) {
return IncrementalStart.empty();
}
if (StringUtils.isNotBlank(modifiedSince)) {
try {
Date startTime = DateUtil.parse(modifiedSince.trim(), DateUtil.PATTERN_DATETIME);
return new IncrementalStart(startTime, modifiedSince.trim());
} catch (Exception exception) {
ComposeLogUtil.getLastLog().warn("解析人员回传 modifiedSince 失败:{},重新计算", modifiedSince);
}
}
User latest = userService.getOne(Wrappers.<User>lambdaQuery()
.select(User::getOaModified)
.eq(User::getIsOa, YES)
.isNotNull(User::getOaModified)
.ne(User::getOaModified, "")
.orderByDesc(User::getOaModified)
.last("limit 1"), false);
if (latest == null || StringUtils.isBlank(latest.getOaModified())) {
ComposeLogUtil.getLastLog().info("本地无人员 oaModified 记录,增量同步按全量拉取后按字段比对跳过");
return IncrementalStart.empty();
}
try {
Date startTime = DateUtil.parse(latest.getOaModified(), DateUtil.PATTERN_DATETIME);
ComposeLogUtil.getLastLog().info("增量同步人员 OA modified 起始时间:{}", latest.getOaModified());
return new IncrementalStart(startTime, latest.getOaModified());
} catch (Exception exception) {
ComposeLogUtil.getLastLog().warn("解析本地人员最大 oaModified 失败:{},回退为全量拉取", latest.getOaModified());
return IncrementalStart.empty();
}
}
/**
* 增量同步起点
*/
private static class IncrementalStart {
private final Date startTime;
private final String modifiedSince;
private IncrementalStart(Date startTime, String modifiedSince) {
this.startTime = startTime;
this.modifiedSince = modifiedSince;
}
private static IncrementalStart empty() {
return new IncrementalStart(null, null);
}
private Date getStartTime() {
return startTime;
}
private String getModifiedSince() {
return modifiedSince;
}
}
/**
* 组装组织分页同步结果
*/
@@ -507,14 +623,10 @@ public class OASyncServiceImpl implements IOASyncService {
return new OrgSyncCount(0, filteredSkipCount, Collections.emptyList());
}
// 查询数据库的部门,转换成map
Map<String, Long> deptMap = getAllCompanyDeptMap();
// 处理部门
List<Dept> notHandledList = handleDept(allParam, deptMap, DeptCategory.COMPANY);
int syncedCount = allParam.size() - notHandledList.size();
int skippedCount = filteredSkipCount + notHandledList.size();
return new OrgSyncCount(syncedCount, skippedCount, notHandledList);
// 查询已存在公司(含 oaModified),按编码索引
Map<String, Dept> existingDeptMap = loadExistingDeptMap(DeptCategory.COMPANY, null);
Map<String, Long> deptIdByCode = toDeptIdMap(existingDeptMap);
return persistSyncedDepts(allParam, existingDeptMap, deptIdByCode, DeptCategory.COMPANY, filteredSkipCount);
}
/**
@@ -558,39 +670,62 @@ public class OASyncServiceImpl implements IOASyncService {
deptCodes.add(dept.getParentCode());
}
});
// 查询数据库的部门,转换成map。包含本页及上级编码,便于命中已入库的上级
Map<String, Long> deptMap = deptCodes.isEmpty() ? new HashMap<>() : deptService.list(Wrappers.<Dept>lambdaQuery()
.eq(Dept::getDeptCategory, DeptCategory.DEPT.getCode())
.in(Dept::getDeptCode, deptCodes)
).stream()
.filter(dept -> StringUtils.isNotEmpty(dept.getDeptCode()))
.collect(Collectors.toMap(Dept::getDeptCode, Dept::getId, (a, b) -> b));
// 查询数据库已有部门(含 oaModified)
Map<String, Dept> existingDeptMap = loadExistingDeptMap(DeptCategory.DEPT, deptCodes);
Map<String, Long> deptIdByCode = toDeptIdMap(existingDeptMap);
// 公司编码也放入 map,根部门挂到公司时才能解析父级
companyDeptMap.forEach(deptMap::putIfAbsent);
// 处理部门
List<Dept> notHandledList = this.handleDept(allParam, deptMap, DeptCategory.DEPT);
int syncedCount = allParam.size() - notHandledList.size();
int skippedCount = filteredSkipCount + notHandledList.size();
return new OrgSyncCount(syncedCount, skippedCount, notHandledList);
companyDeptMap.forEach(deptIdByCode::putIfAbsent);
return persistSyncedDepts(allParam, existingDeptMap, deptIdByCode, DeptCategory.DEPT, filteredSkipCount);
}
/**
* 处理部门
* @param allParam 待处理的部门
* @param deptMap 数据库所有部门编码和id map
* @return 未处理的数据
* 加载已存在组织,按部门编码索引
*
* @param deptCategory 组织类型
* @param deptCodes 限定编码;为空则按类型全量加载
*/
private List<Dept> handleDept(List<Dept> allParam, Map<String, Long> deptMap, DeptCategory deptCategory) {
// 数据库存在的所有部门id
Set<Long> existsDeptIds = new HashSet<>(deptMap.values());
private Map<String, Dept> loadExistingDeptMap(DeptCategory deptCategory, Set<String> deptCodes) {
if (deptCodes != null && deptCodes.isEmpty()) {
return new HashMap<>();
}
List<Dept> deptList = deptService.list(Wrappers.<Dept>lambdaQuery()
.eq(Dept::getDeptCategory, deptCategory.getCode())
.in(deptCodes != null, Dept::getDeptCode, deptCodes)
.select(Dept::getId, Dept::getDeptCode, Dept::getOaModified, Dept::getParentId));
return deptList.stream()
.filter(dept -> StringUtils.isNotEmpty(dept.getDeptCode()))
.collect(Collectors.toMap(Dept::getDeptCode, dept -> dept, (a, b) -> b));
}
private Map<String, Long> toDeptIdMap(Map<String, Dept> existingDeptMap) {
Map<String, Long> deptIdByCode = new HashMap<>();
existingDeptMap.forEach((code, dept) -> deptIdByCode.put(code, dept.getId()));
return deptIdByCode;
}
/**
* 持久化同步数据:不存在则新增;存在且 oaModified 不一致才修改
*
* @param allParam 待处理数据
* @param existingDeptMap 已存在组织(同类型)
* @param deptIdByCode 编码→id(含上级公司等)
* @param deptCategory 组织类型
* @param filteredSkipCount 上游过滤跳过数
* @return 同步统计
*/
private OrgSyncCount persistSyncedDepts(List<Dept> allParam, Map<String, Dept> existingDeptMap,
Map<String, Long> deptIdByCode, DeptCategory deptCategory,
int filteredSkipCount) {
Set<Long> existsDeptIds = existingDeptMap.values().stream()
.map(Dept::getId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
allParam.forEach(dept -> {
if (deptMap.containsKey(dept.getDeptCode())) {
// 根据部门编码获取对应的部门id
dept.setId(deptMap.get(dept.getDeptCode()));
if (deptIdByCode.containsKey(dept.getDeptCode())) {
dept.setId(deptIdByCode.get(dept.getDeptCode()));
} else {
// 没有就生成一个id
dept.setId(DefaultIdentifierGenerator.getInstance().nextId(null));
deptMap.put(dept.getDeptCode(), dept.getId());
deptIdByCode.put(dept.getDeptCode(), dept.getId());
}
});
@@ -600,7 +735,7 @@ public class OASyncServiceImpl implements IOASyncService {
if (dept.getParentId() != null) {
continue;
}
Long parentId = deptMap.get(dept.getParentCode());
Long parentId = deptIdByCode.get(dept.getParentCode());
if (parentId != null) {
dept.setParentId(parentId);
continue;
@@ -615,11 +750,10 @@ public class OASyncServiceImpl implements IOASyncService {
deptCategory.getName(), unresolvedParentCount);
}
// 父id不为空才新增;已存在的记录即使父id未解析也更新(空父id不会覆盖原值)
// 父id不为空才新增;已存在的记录即使父id未解析也允许更新(空父id不会覆盖原值)
List<Dept> list = allParam.stream()
.filter(dept -> dept.getParentId() != null || existsDeptIds.contains(dept.getId()))
.toList();
// 不存在的新增
List<Dept> addList = list.stream()
.filter(dept -> !existsDeptIds.contains(dept.getId()))
.toList();
@@ -627,18 +761,43 @@ public class OASyncServiceImpl implements IOASyncService {
ComposeLogUtil.getLastLog().info("批量新增{}:{}", deptCategory.getName(), addList.size());
deptService.saveBatch(addList);
}
// 存在的修改
// 已存在且 oaModified 不一致(或不存在可比对值)才修改
List<Dept> updateList = list.stream()
.filter(dept -> existsDeptIds.contains(dept.getId()))
.filter(dept -> !shouldSkipOaUpdate(existingDeptMap.get(dept.getDeptCode()), dept))
.toList();
if (CollectionUtil.isNotEmpty(updateList)) {
ComposeLogUtil.getLastLog().info("批量修改{}:{}", deptCategory.getName(), updateList.size());
deptService.updateBatchById(updateList);
}
// 返回仍未保存的数据
return allParam.stream()
int unchangedCount = (int) list.stream()
.filter(dept -> existsDeptIds.contains(dept.getId()))
.filter(dept -> shouldSkipOaUpdate(existingDeptMap.get(dept.getDeptCode()), dept))
.count();
List<Dept> notHandledList = allParam.stream()
.filter(dept -> dept.getParentId() == null && !existsDeptIds.contains(dept.getId()))
.toList();
int syncedCount = addList.size() + updateList.size();
int skippedCount = filteredSkipCount + unchangedCount + notHandledList.size();
if (unchangedCount > 0) {
ComposeLogUtil.getLastLog().info("跳过未变更{}:{}", deptCategory.getName(), unchangedCount);
}
return new OrgSyncCount(syncedCount, skippedCount, notHandledList);
}
/**
* oaModified 一致则跳过更新;任一侧为空无法确认一致,执行更新以补齐字段
*/
private boolean shouldSkipOaUpdate(Dept existing, Dept incoming) {
if (existing == null) {
return false;
}
String localModified = StringUtils.trimToEmpty(existing.getOaModified());
String remoteModified = StringUtils.trimToEmpty(incoming == null ? null : incoming.getOaModified());
if (localModified.isEmpty() || remoteModified.isEmpty()) {
return false;
}
return localModified.equals(remoteModified);
}
/**
@@ -249,6 +249,7 @@ public class OaUserListSyncHelper {
});
Map<String, Long> userMap = new HashMap<>();
Set<Long> existsUserIds = new HashSet<>();
Set<Long> unchangedUserIds = new HashSet<>();
users.forEach(user -> {
User existingUser = existingByAccount.get(user.getAccount());
if (existingUser == null && StringUtils.isNotBlank(user.getPhone())) {
@@ -258,6 +259,9 @@ public class OaUserListSyncHelper {
user.setId(existingUser.getId());
user.setPassword(null);
existsUserIds.add(existingUser.getId());
if (shouldSkipOaUpdate(existingUser, user)) {
unchangedUserIds.add(existingUser.getId());
}
} else {
user.setId(DefaultIdentifierGenerator.getInstance().nextId(null));
user.setPostId("-1");
@@ -279,12 +283,18 @@ public class OaUserListSyncHelper {
}
List<User> updateUsers = users.stream()
.filter(user -> existsUserIds.contains(user.getId()))
.filter(user -> !unchangedUserIds.contains(user.getId()))
.toList();
if (CollectionUtil.isNotEmpty(updateUsers)) {
ComposeLogUtil.getLastLog().info("批量修改用户:{}", updateUsers.size());
userService.updateBatchById(updateUsers);
}
if (!unchangedUserIds.isEmpty()) {
ComposeLogUtil.getLastLog().info("跳过未变更用户:{}", unchangedUserIds.size());
}
ComposeLogUtil.getLastLog().info("缺少账号已跳过的人员:{}", skippedCount);
int syncedCount = addUsers.size() + updateUsers.size();
skippedCount += unchangedUserIds.size();
if (userMap.isEmpty()) {
return new PersonSyncCount(0, skippedCount);
}
@@ -295,7 +305,13 @@ public class OaUserListSyncHelper {
.collect(Collectors.toMap(this::getUserDeptKey, UserDept::getId, (first, second) -> second));
Set<Long> existsUserDeptIds = new HashSet<>(userDeptMap.values());
List<UserDept> userDeptList = oaPersons.stream()
.filter(oaPerson -> userMap.containsKey(userConvert.resolveAccount(oaPerson)))
.filter(oaPerson -> {
String account = userConvert.resolveAccount(oaPerson);
if (!userMap.containsKey(account)) {
return false;
}
return !unchangedUserIds.contains(userMap.get(account));
})
.map(oaPerson -> this.buildUserDept(oaPerson, userMap, orgIndex))
.filter(Objects::nonNull)
.toList();
@@ -333,6 +349,7 @@ public class OaUserListSyncHelper {
.collect(Collectors.toSet());
String defaultRoleId = getDefaultRoleId();
List<User> updateUserParams = list.stream()
.filter(userDeptIds -> !unchangedUserIds.contains(userDeptIds.getUserId()))
.filter(userDeptIds -> StringUtils.isNotBlank(userDeptIds.getDeptIds()) && userDeptIds.getDeptIds().length() <= MAX_DEPT_ID_LENGTH)
.map(userDeptIds -> {
User user = new User();
@@ -347,7 +364,22 @@ public class OaUserListSyncHelper {
if (CollectionUtil.isNotEmpty(updateUserParams)) {
userService.updateBatchById(updateUserParams);
}
return new PersonSyncCount(users.size(), skippedCount);
return new PersonSyncCount(syncedCount, skippedCount);
}
/**
* oaModified 一致则跳过更新;任一侧为空无法确认一致,执行更新以补齐字段
*/
private boolean shouldSkipOaUpdate(User existing, User incoming) {
if (existing == null) {
return false;
}
String localModified = StringUtils.trimToEmpty(existing.getOaModified());
String remoteModified = StringUtils.trimToEmpty(incoming == null ? null : incoming.getOaModified());
if (localModified.isEmpty() || remoteModified.isEmpty()) {
return false;
}
return localModified.equals(remoteModified);
}
private Dept getOrCreateRootCompany() {
@@ -339,6 +339,23 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
return submitUserInfo(user) && submitUserDept(user) && submitUserScopes(user);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean changeUserStatus(Long userId, Integer status) {
if (Func.isEmpty(userId)) {
throw new ServiceException("用户主键不能为空");
}
if (!Objects.equals(status, DataStatusEnum.ENABLE.getCode()) && !Objects.equals(status, 0)
&& !Objects.equals(status, DataStatusEnum.DISABLE.getCode())) {
throw new ServiceException("启停状态不正确");
}
TenantGuard.verify(this, userId, USER);
Integer targetStatus = Objects.equals(status, DataStatusEnum.ENABLE.getCode())
? DataStatusEnum.ENABLE.getCode() : 0;
CacheUtil.clear(USER_CACHE);
return changeStatus(Collections.singletonList(userId), targetStatus);
}
@Override
public boolean updateUserInfo(User user) {
// 用户修改自身信息强制指定当前请求账号的ID
@@ -364,7 +381,14 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
throw new ServiceException(StringUtil.format("当前手机 [{}] 已存在!", user.getPhone()));
}
user.setPassword(null);
return updateById(user);
Integer originalSex = user.getSex();
Integer normalizedSex = normalizeUserSex(originalSex);
user.setSex(normalizedSex);
boolean updated = updateById(user);
if (user.getId() != null && originalSex != null && normalizedSex == null) {
this.update(Wrappers.<User>lambdaUpdate().set(User::getSex, null).eq(User::getId, user.getId()));
}
return updated;
}
private boolean submitUserDept(User user) {
@@ -1086,6 +1110,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
throw new ServiceException(StringUtil.format("当前手机 [{}] 已存在!", user.getPhone()));
}
CacheUtil.clear(USER_CACHE);
user.setSex(normalizeUserSex(user.getSex()));
return save(user) && submitUserDept(user) && submitUserScopes(user);
}
@@ -1096,6 +1121,16 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
if (user.getIncludeNewCustomer() == null) user.setIncludeNewCustomer(0);
}
/**
* 性别仅保留男 / 女 / 未知;空值或历史占位 -1 不回填,按空处理。
*/
private Integer normalizeUserSex(Integer sex) {
if (Objects.equals(sex, 1) || Objects.equals(sex, 2) || Objects.equals(sex, 3)) {
return sex;
}
return null;
}
private boolean submitUserScopes(User user) {
userDataScopeMapper.delete(Wrappers.<UserDataScope>lambdaQuery().eq(UserDataScope::getUserId, user.getId()));
userCustomerScopeMapper.delete(Wrappers.<UserCustomerScope>lambdaQuery().eq(UserCustomerScope::getUserId, user.getId()));
@@ -38,6 +38,7 @@ import org.springblade.system.pojo.enums.DictEnum;
import org.springblade.system.pojo.vo.UserVO;
import java.util.List;
import java.util.Objects;
/**
* 包装类,返回视图层所需的字段
@@ -81,7 +82,11 @@ public class UserWrapper extends BaseEntityWrapper<User, UserVO> {
userVO.setPostId(StringPool.EMPTY);
userVO.setPostName("暂未分配");
}
userVO.setSexName(DictCache.getValue(DictEnum.SEX, user.getSex()));
if (user.getSex() == null || Objects.equals(user.getSex(), -1)) {
userVO.setSexName("");
} else {
userVO.setSexName(DictCache.getValue(DictEnum.SEX, user.getSex()));
}
userVO.setUserTypeName(DictCache.getValue(DictEnum.USER_TYPE, user.getUserType()));
return userVO;
}