From 3b26591f9d94e2cbcf1f92c6b8057a4a4f640b97 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Fri, 18 Sep 2026 20:25:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=90=8C=E6=AD=A5=E5=85=AC?= =?UTF-8?q?=E5=8F=B8=E3=80=81=E7=BB=84=E7=BB=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/pojo/vo/OaOrgSyncPageVO.java | 69 +++++ .../system/controller/DeptController.java | 37 ++- .../system/service/IOASyncService.java | 19 ++ .../service/impl/OASyncServiceImpl.java | 243 +++++++++++++++--- 4 files changed, 335 insertions(+), 33 deletions(-) create mode 100644 blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/OaOrgSyncPageVO.java diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/OaOrgSyncPageVO.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/OaOrgSyncPageVO.java new file mode 100644 index 0000000..590a8ba --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/OaOrgSyncPageVO.java @@ -0,0 +1,69 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + *

+ * Use of this software is governed by the Commercial License Agreement + * obtained after purchasing a license from BladeX. + *

+ * 1. This software is for development use only under a valid license + * from BladeX. + *

+ * 2. Redistribution of this software's source code to any third party + * without a commercial license is strictly prohibited. + *

+ * 3. Licensees may copyright their own code but cannot use segments + * from this software for such purposes. Copyright of this software + * remains with BladeX. + *

+ * Using this software signifies agreement to this License, and the software + * must not be used for illegal purposes. + *

+ * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is + * not liable for any claims arising from secondary or illegal development. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.system.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * OA组织(公司/部门)分页同步结果 + * + * @author Chill + */ +@Data +@Schema(description = "OA组织分页同步结果") +public class OaOrgSyncPageVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "同步阶段:company / department") + private String stage; + + @Schema(description = "当前页") + private Integer current; + + @Schema(description = "每页条数") + private Integer size; + + @Schema(description = "OA总条数") + private Long total; + + @Schema(description = "本页从OA拉取的条数") + private Integer fetchedCount; + + @Schema(description = "本页同步成功条数") + private Integer syncedCount; + + @Schema(description = "本页跳过条数") + private Integer skippedCount; + + @Schema(description = "是否已到最后一页") + private Boolean finished; +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/DeptController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/DeptController.java index 9cccc5d..e407597 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/DeptController.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/DeptController.java @@ -50,8 +50,10 @@ import org.springblade.system.pojo.entity.Dept; import org.springblade.system.pojo.entity.User; import org.springblade.system.pojo.enums.DictEnum; import org.springblade.system.pojo.vo.DeptVO; +import org.springblade.system.pojo.vo.OaOrgSyncPageVO; import org.springblade.system.pojo.vo.UserVO; import org.springblade.system.service.IDeptService; +import org.springblade.system.service.IOASyncService; import org.springblade.system.wrapper.DeptWrapper; import org.springframework.web.bind.annotation.*; @@ -73,6 +75,7 @@ import static org.springblade.core.cache.constant.CacheConstant.SYS_CACHE; public class DeptController extends BladeController { private final IDeptService deptService; + private final IOASyncService oaSyncService; /** * 详情 @@ -170,12 +173,38 @@ public class DeptController extends BladeController { return R.data(deptService.syncIamOrganizations()); } + /** + * 从OA按页同步公司 + */ + @IsAdmin + @PostMapping("/sync-oa-company") + @ApiOperationSupport(order = 8) + @Operation(summary = "同步OA公司") + public R syncOaCompany( + @RequestParam(defaultValue = "1") Integer current, + @RequestParam(defaultValue = "50") Integer size) { + return R.data(oaSyncService.syncCompanyPage(current, size)); + } + + /** + * 从OA按页同步部门(需先完成公司同步) + */ + @IsAdmin + @PostMapping("/sync-oa-department") + @ApiOperationSupport(order = 9) + @Operation(summary = "同步OA部门") + public R syncOaDepartment( + @RequestParam(defaultValue = "1") Integer current, + @RequestParam(defaultValue = "50") Integer size) { + return R.data(oaSyncService.syncDepartmentPage(current, size)); + } + /** * 删除 */ @IsAdmin @PostMapping("/remove") - @ApiOperationSupport(order = 8) + @ApiOperationSupport(order = 10) @Operation(summary = "删除", description = "传入ids") public R remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) { CacheUtil.clear(SYS_CACHE); @@ -188,7 +217,7 @@ public class DeptController extends BladeController { */ @PreAuth(AuthConstant.PERMIT_ALL) @GetMapping("/select") - @ApiOperationSupport(order = 9) + @ApiOperationSupport(order = 11) @Operation(summary = "下拉数据源", description = "传入id集合") public R> select(Long userId, String deptId) { if (Func.isNotEmpty(userId)) { @@ -205,7 +234,7 @@ public class DeptController extends BladeController { */ @PreAuth(AuthConstant.PERMIT_ALL) @GetMapping("/platform-company-select") - @ApiOperationSupport(order = 10) + @ApiOperationSupport(order = 12) @Operation(summary = "平台公司下拉", description = "返回是否平台公司=是的部门列表") public R> platformCompanySelect() { return R.data(deptService.listPlatformCompany()); @@ -216,7 +245,7 @@ public class DeptController extends BladeController { */ @IsAdmin @GetMapping("/dept-leader-info") - @ApiOperationSupport(order = 11) + @ApiOperationSupport(order = 13) @Operation(summary = "获取部门的主管信息", description = "传入deptId") public R> deptLeaderInfo(@Parameter(description = "部门id", required = true) @RequestParam Long deptId) { List list = deptService.deptLeaderInfo(deptId); diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IOASyncService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IOASyncService.java index 45b7437..3e11ad4 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IOASyncService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IOASyncService.java @@ -1,5 +1,6 @@ package org.springblade.system.service; +import org.springblade.system.pojo.vo.OaOrgSyncPageVO; import org.springblade.system.pojo.vo.OaPersonSyncPageVO; /** @@ -36,4 +37,22 @@ public interface IOASyncService { * @return 本页同步结果 */ OaPersonSyncPageVO syncPersonFromUserList(int current, int size); + + /** + * 按页从 OA 公司接口同步公司 + * + * @param current 当前页,从 1 开始 + * @param size 每页条数 + * @return 本页同步结果 + */ + OaOrgSyncPageVO syncCompanyPage(int current, int size); + + /** + * 按页从 OA 部门接口同步部门;最后一页完成后更新祖级列表 + * + * @param current 当前页,从 1 开始 + * @param size 每页条数 + * @return 本页同步结果 + */ + OaOrgSyncPageVO syncDepartmentPage(int current, int size); } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java index 0a02139..5e208c7 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OASyncServiceImpl.java @@ -18,6 +18,7 @@ import org.springblade.system.log.ComposeLogUtil; import org.springblade.system.pojo.entity.*; import org.springblade.system.pojo.enums.DataSync; import org.springblade.system.pojo.enums.DeptCategory; +import org.springblade.system.pojo.vo.OaOrgSyncPageVO; import org.springblade.system.pojo.vo.OaPersonSyncPageVO; import org.springblade.system.service.*; import org.springblade.system.util.DataSyncRecordUtils; @@ -124,6 +125,28 @@ public class OASyncServiceImpl implements IOASyncService { } } + @Transactional(rollbackFor = Exception.class) + @Override + public OaOrgSyncPageVO syncCompanyPage(int current, int size) { + try { + ComposeLogUtil.addLog(log); + return this.syncCompanyFromOaPage(current, size); + } finally { + ComposeLogUtil.removeLastLog(); + } + } + + @Transactional(rollbackFor = Exception.class) + @Override + public OaOrgSyncPageVO syncDepartmentPage(int current, int size) { + try { + ComposeLogUtil.addLog(log); + return this.syncDepartmentFromOaPage(current, size); + } finally { + ComposeLogUtil.removeLastLog(); + } + } + /** * 同步并记录 * @@ -166,20 +189,15 @@ public class OASyncServiceImpl implements IOASyncService { // 未处理的数据 List notHandleList = new ArrayList<>(); // 1. 设置查询参数 - OACompanySearch companySearch = new OACompanySearch(); - companySearch.setCurPage(1); - if (startTime != null) { - // 开始时间不为空,设置修改时间参数 - companySearch.setModified(DateUtil.format(startTime, DateUtil.PATTERN_DATETIME)); - } + OACompanySearch companySearch = buildCompanySearch(startTime); // 2. 分页查询并处理数据 OAUtils.pageSyncHandler(companySearch, param -> oaClient.queryCompanyPage(new OASearch<>(param)), response -> { ComposeLogUtil.getLastLog().error("调用OA接口查询公司信息失败 {}", JSON.toJSONString(response)); return new ServiceException("调用OA接口查询公司信息失败"); }, 10000, ComposeLogUtil.getLastLog()::info).accept(list -> { // 处理数据 - List deptList = handleCompany(list); - notHandleList.addAll(deptList); + OrgSyncCount syncCount = handleCompany(list); + notHandleList.addAll(syncCount.getNotHandledList()); }); // 3. 未处理的数据 if (CollectionUtil.isNotEmpty(notHandleList)) { @@ -203,21 +221,15 @@ public class OASyncServiceImpl implements IOASyncService { // 未处理的数据 List notHandleList = new ArrayList<>(); // 1. 设置查询参数 - OADepartmentSearch departmentSearch = new OADepartmentSearch(); - departmentSearch.setCurPage(1); - departmentSearch.setSubcompanyid1(subCompanyIds); - if (startTime != null) { - // 开始时间不为空,设置修改时间参数 - departmentSearch.setModified(DateUtil.format(startTime, DateUtil.PATTERN_DATETIME)); - } + OADepartmentSearch departmentSearch = buildDepartmentSearch(startTime, subCompanyIds); // 2. 分页查询并处理数据 OAUtils.pageSyncHandler(departmentSearch, param -> oaClient.queryDepartmentPage(new OASearch<>(param)), response -> { ComposeLogUtil.getLastLog().error("调用OA接口查询部门信息失败 {}", JSON.toJSONString(response)); return new ServiceException("调用OA接口查询部门信息失败"); }, 10000, ComposeLogUtil.getLastLog()::info).accept(list -> { // 处理数据 - List deptList = handleDept(list); - notHandleList.addAll(deptList); + OrgSyncCount syncCount = handleDept(list); + notHandleList.addAll(syncCount.getNotHandledList()); }); // 3. 未处理的数据 if (CollectionUtil.isNotEmpty(notHandleList)) { @@ -300,6 +312,139 @@ public class OASyncServiceImpl implements IOASyncService { return pageVO; } + /** + * 按页从 OA 公司接口同步公司 + * + * @param current 当前页 + * @param size 每页条数 + * @return 本页同步结果 + */ + private OaOrgSyncPageVO syncCompanyFromOaPage(int current, int size) { + int pageNo = current < 1 ? 1 : current; + int pageSize = size < 1 ? 50 : Math.min(size, 200); + OACompanySearch companySearch = buildCompanySearch(null); + companySearch.setCurPage(pageNo); + companySearch.setPageSize(pageSize); + OAResponse oaResponse = oaClient.queryCompanyPage(new OASearch<>(companySearch)); + if (oaResponse == null || !OAConstant.OA_SUCCESS_CODE.equals(oaResponse.getCode()) || oaResponse.getData() == null) { + ComposeLogUtil.getLastLog().error("调用OA接口查询公司信息失败 {}", JSON.toJSONString(oaResponse)); + throw new ServiceException("调用OA接口查询公司信息失败"); + } + OAResponseData responseData = oaResponse.getData(); + List oaCompanies = responseData.getDataList() == null + ? Collections.emptyList() : responseData.getDataList(); + long totalSize = responseData.getTotalSize() == null ? 0L : responseData.getTotalSize(); + OrgSyncCount syncCount = handleCompany(oaCompanies); + if (CollectionUtil.isNotEmpty(syncCount.getNotHandledList())) { + ComposeLogUtil.getLastLog().warn("同步公司,本页未处理数据:{}", JSON.toJSONString(syncCount.getNotHandledList())); + } + CacheUtil.clear(SYS_CACHE); + CacheUtil.clear(SYS_CACHE, Boolean.FALSE); + OaOrgSyncPageVO pageVO = buildOrgSyncPageVO("company", pageNo, pageSize, totalSize, oaCompanies.size(), syncCount); + ComposeLogUtil.getLastLog().info("OA公司分页同步完成 {}/{},成功{},跳过{}", + pageNo, totalSize, syncCount.getSyncedCount(), syncCount.getSkippedCount()); + return pageVO; + } + + /** + * 按页从 OA 部门接口同步部门 + * + * @param current 当前页 + * @param size 每页条数 + * @return 本页同步结果 + */ + private OaOrgSyncPageVO syncDepartmentFromOaPage(int current, int size) { + int pageNo = current < 1 ? 1 : current; + int pageSize = size < 1 ? 50 : Math.min(size, 200); + String subCompanyIds = getSubCompanyIds(); + if (StringUtils.isEmpty(subCompanyIds)) { + ComposeLogUtil.getLastLog().warn("未查询到子公司查询参数,跳过部门同步"); + OaOrgSyncPageVO emptyPageVO = buildOrgSyncPageVO("department", pageNo, pageSize, 0L, 0, OrgSyncCount.empty()); + emptyPageVO.setFinished(true); + return emptyPageVO; + } + OADepartmentSearch departmentSearch = buildDepartmentSearch(null, subCompanyIds); + departmentSearch.setCurPage(pageNo); + departmentSearch.setPageSize(pageSize); + OAResponse oaResponse = oaClient.queryDepartmentPage(new OASearch<>(departmentSearch)); + if (oaResponse == null || !OAConstant.OA_SUCCESS_CODE.equals(oaResponse.getCode()) || oaResponse.getData() == null) { + ComposeLogUtil.getLastLog().error("调用OA接口查询部门信息失败 {}", JSON.toJSONString(oaResponse)); + throw new ServiceException("调用OA接口查询部门信息失败"); + } + OAResponseData responseData = oaResponse.getData(); + List oaDepartments = responseData.getDataList() == null + ? Collections.emptyList() : responseData.getDataList(); + long totalSize = responseData.getTotalSize() == null ? 0L : responseData.getTotalSize(); + OrgSyncCount syncCount = handleDept(oaDepartments); + if (CollectionUtil.isNotEmpty(syncCount.getNotHandledList())) { + ComposeLogUtil.getLastLog().warn("同步部门,本页未处理数据:{}", JSON.toJSONString(syncCount.getNotHandledList())); + } + CacheUtil.clear(SYS_CACHE); + CacheUtil.clear(SYS_CACHE, Boolean.FALSE); + OaOrgSyncPageVO pageVO = buildOrgSyncPageVO("department", pageNo, pageSize, totalSize, oaDepartments.size(), syncCount); + if (Boolean.TRUE.equals(pageVO.getFinished())) { + // 部门同步完成后更新祖级列表 + deptService.updateAncestors(null); + } + ComposeLogUtil.getLastLog().info("OA部门分页同步完成 {}/{},成功{},跳过{}", + pageNo, totalSize, syncCount.getSyncedCount(), syncCount.getSkippedCount()); + return pageVO; + } + + /** + * 组装组织分页同步结果 + */ + private OaOrgSyncPageVO buildOrgSyncPageVO(String stage, int pageNo, int pageSize, long totalSize, + int fetchedCount, OrgSyncCount syncCount) { + OaOrgSyncPageVO pageVO = new OaOrgSyncPageVO(); + pageVO.setStage(stage); + pageVO.setCurrent(pageNo); + pageVO.setSize(pageSize); + pageVO.setTotal(totalSize); + pageVO.setFetchedCount(fetchedCount); + pageVO.setSyncedCount(syncCount.getSyncedCount()); + pageVO.setSkippedCount(syncCount.getSkippedCount()); + boolean finished = fetchedCount == 0 + || fetchedCount < pageSize + || (long) pageNo * pageSize >= totalSize; + pageVO.setFinished(finished); + return pageVO; + } + + /** + * 组装 OA 公司分页查询参数 + * + * @param startTime 增量查询开始时间 + * @return 查询参数 + */ + private OACompanySearch buildCompanySearch(Date startTime) { + OACompanySearch companySearch = new OACompanySearch(); + companySearch.setCurPage(1); + companySearch.setPageSize(50); + if (startTime != null) { + companySearch.setModified(DateUtil.format(startTime, DateUtil.PATTERN_DATETIME)); + } + return companySearch; + } + + /** + * 组装 OA 部门分页查询参数 + * + * @param startTime 增量查询开始时间 + * @param subCompanyIds 子公司 id 列表 + * @return 查询参数 + */ + private OADepartmentSearch buildDepartmentSearch(Date startTime, String subCompanyIds) { + OADepartmentSearch departmentSearch = new OADepartmentSearch(); + departmentSearch.setCurPage(1); + departmentSearch.setPageSize(50); + departmentSearch.setSubcompanyid1(subCompanyIds); + if (startTime != null) { + departmentSearch.setModified(DateUtil.format(startTime, DateUtil.PATTERN_DATETIME)); + } + return departmentSearch; + } + /** * 组装 OA 人员分页查询参数 * @@ -325,12 +470,11 @@ public class OASyncServiceImpl implements IOASyncService { /** * 处理oa公司 * @param oaCompanies - * @return 未处理的数据 + * @return 同步统计 */ - private List handleCompany(List oaCompanies) { + private OrgSyncCount handleCompany(List oaCompanies) { if (CollectionUtil.isEmpty(oaCompanies)) { - // 数据为空,直接返回 - return Collections.emptyList(); + return OrgSyncCount.empty(); } // 获取需要的公司名称 Set companyNames = getCompanyNames(); @@ -340,30 +484,36 @@ public class OASyncServiceImpl implements IOASyncService { .filter(company -> companyNames.contains(company.getSubcompanyname())) .map(deptConvert::company2dept) .toList(); + int filteredSkipCount = oaCompanies.size() - allParam.size(); + if (CollectionUtil.isEmpty(allParam)) { + return new OrgSyncCount(0, filteredSkipCount, Collections.emptyList()); + } // 查询数据库的部门,转换成map Map deptMap = getAllCompanyDeptMap(); // 处理部门 - return handleDept(allParam, deptMap, DeptCategory.COMPANY); + List notHandledList = handleDept(allParam, deptMap, DeptCategory.COMPANY); + int syncedCount = allParam.size() - notHandledList.size(); + int skippedCount = filteredSkipCount + notHandledList.size(); + return new OrgSyncCount(syncedCount, skippedCount, notHandledList); } /** * 处理oa部门 * @param oaDepts - * @return 未处理的数据 + * @return 同步统计 */ - private List handleDept(List oaDepts) { + private OrgSyncCount handleDept(List oaDepts) { if (CollectionUtil.isEmpty(oaDepts)) { - // 数据为空,直接返回 - return Collections.emptyList(); + return OrgSyncCount.empty(); } // 查询所有公司的编码和id的map Map companyDeptMap = getAllCompanyDeptMap(); // 根公司id String rootCompanyId = getRootCompanyId(); if (rootCompanyId == null) { - return Collections.emptyList(); + return new OrgSyncCount(0, oaDepts.size(), Collections.emptyList()); } // 根公司下要同步的部门名称 Set rootCompanyDeptNames = getRootCompanyDeptNames(); @@ -373,8 +523,9 @@ public class OASyncServiceImpl implements IOASyncService { .filter(oaDept -> !rootCompanyId.equals(oaDept.getSubcompanyid1()) || (OAConstant.ROOT_COMPANY_ID.equals(oaDept.getSupdepid()) && rootCompanyDeptNames.contains(oaDept.getDepartmentname()))) .map(dept -> deptConvert.dept2dept(dept, companyDeptMap)) .toList(); + int filteredSkipCount = oaDepts.size() - allParam.size(); if (CollectionUtil.isEmpty(allParam)) { - return Collections.emptyList(); + return new OrgSyncCount(0, filteredSkipCount, Collections.emptyList()); } Set deptCodes = allParam.stream() .map(Dept::getDeptCode) @@ -387,7 +538,10 @@ public class OASyncServiceImpl implements IOASyncService { .filter(dept -> StringUtils.isNotEmpty(dept.getDeptCode())) .collect(Collectors.toMap(Dept::getDeptCode, Dept::getId, (a, b) -> b)); // 处理部门 - return this.handleDept(allParam, deptMap, DeptCategory.DEPT); + List notHandledList = this.handleDept(allParam, deptMap, DeptCategory.DEPT); + int syncedCount = allParam.size() - notHandledList.size(); + int skippedCount = filteredSkipCount + notHandledList.size(); + return new OrgSyncCount(syncedCount, skippedCount, notHandledList); } /** @@ -441,6 +595,37 @@ public class OASyncServiceImpl implements IOASyncService { .toList(); } + /** + * 组织同步统计 + */ + private static class OrgSyncCount { + private final int syncedCount; + private final int skippedCount; + private final List notHandledList; + + private OrgSyncCount(int syncedCount, int skippedCount, List notHandledList) { + this.syncedCount = syncedCount; + this.skippedCount = skippedCount; + this.notHandledList = notHandledList == null ? Collections.emptyList() : notHandledList; + } + + private static OrgSyncCount empty() { + return new OrgSyncCount(0, 0, Collections.emptyList()); + } + + private int getSyncedCount() { + return syncedCount; + } + + private int getSkippedCount() { + return skippedCount; + } + + private List getNotHandledList() { + return notHandledList; + } + } + /** * 获取oa查询参数,子公司id参数 * @return