✨ 组织与人员支持OA增量同步,并完善用户启停与性别空值
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+12
-6
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+16
-2
@@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
|
||||
+4
-2
@@ -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);
|
||||
|
||||
/**
|
||||
|
||||
+1
@@ -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>
|
||||
|
||||
+15
-9
@@ -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);
|
||||
}
|
||||
|
||||
+9
@@ -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);
|
||||
|
||||
/**
|
||||
* 修改用户基本信息(仅限当前登录用户自身,忽略角色 / 部门 / 岗位 / 账号等敏感字段)
|
||||
*
|
||||
|
||||
+4
@@ -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不能为空");
|
||||
}
|
||||
|
||||
+222
-63
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+34
-2
@@ -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() {
|
||||
|
||||
+36
-1
@@ -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()));
|
||||
|
||||
+6
-1
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user