From af3b6982fd42559d31f8e54ddc69f8c241f502a7 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Tue, 15 Sep 2026 17:52:34 +0800 Subject: [PATCH 01/55] =?UTF-8?q?=E8=B0=83=E6=95=B4=20=E4=B8=9A=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transport/pojo/vo/LoadingManageVO.java | 4 + .../transport/pojo/vo/TransportPlanVO.java | 22 +++++ .../controller/LoadingManageController.java | 13 ++- .../service/ILoadingManageService.java | 2 + .../impl/LoadingManageServiceImpl.java | 95 ++++++++++++++++++- .../service/impl/MasterOrderServiceImpl.java | 2 +- .../impl/TransportPlanServiceImpl.java | 14 ++- .../service/impl/WaybillServiceImpl.java | 9 +- 8 files changed, 151 insertions(+), 10 deletions(-) diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/LoadingManageVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/LoadingManageVO.java index c2ce78f0..f576efd2 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/LoadingManageVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/LoadingManageVO.java @@ -46,6 +46,10 @@ public class LoadingManageVO extends LoadingManage { @Schema(description = "业务状态名称") private String businessStatusName; + @TableField(exist = false) + @Schema(description = "是否存在司机拒绝接单(可重新派单)") + private Boolean driverRejected; + @TableField(exist = false) @Schema(description = "运单号") private String waybillNo; diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportPlanVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportPlanVO.java index a8d394db..49e6e89d 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportPlanVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/TransportPlanVO.java @@ -27,8 +27,11 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import org.springblade.transport.pojo.entity.TransportPlan; +import org.springframework.format.annotation.DateTimeFormat; import java.io.Serial; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; /** @@ -72,5 +75,24 @@ public class TransportPlanVO extends TransportPlan { @Schema(description = "计划调度生成的运单") private List dispatchRows; + @TableField(exist = false) + @Schema(description = "计划开始日期起") + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate planStartDateStart; + + @TableField(exist = false) + @Schema(description = "计划开始日期止") + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate planStartDateEnd; + + @TableField(exist = false) + @Schema(description = "创建开始时间") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTimeStart; + + @TableField(exist = false) + @Schema(description = "创建结束时间") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTimeEnd; } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/LoadingManageController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/LoadingManageController.java index ff7caccb..ab1bed91 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/LoadingManageController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/LoadingManageController.java @@ -119,22 +119,29 @@ public class LoadingManageController extends BladeController { return R.status(loadingManageService.changeRoute(loadingManage)); } - @PostMapping("/cancel") + @PostMapping("/start") @ApiOperationSupport(order = 10) + @Operation(summary = "改为进行中", description = "传入id") + public R start(@Parameter(description = "主键", required = true) @RequestParam Long id) { + return R.status(loadingManageService.start(id)); + } + + @PostMapping("/cancel") + @ApiOperationSupport(order = 11) @Operation(summary = "取消", description = "传入id") public R cancel(@Parameter(description = "主键", required = true) @RequestParam Long id) { return R.status(loadingManageService.cancel(id)); } @PostMapping("/complete") - @ApiOperationSupport(order = 11) + @ApiOperationSupport(order = 12) @Operation(summary = "完成", description = "传入id") public R complete(@Parameter(description = "主键", required = true) @RequestParam Long id) { return R.status(loadingManageService.complete(id)); } @PostMapping("/batch-complete") - @ApiOperationSupport(order = 12) + @ApiOperationSupport(order = 13) @Operation(summary = "批量完成", description = "传入ids") public R batchComplete(@Parameter(description = "主键集合", required = true) @RequestParam String ids) { return R.data(loadingManageService.batchComplete(ids)); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ILoadingManageService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ILoadingManageService.java index 1b72c3ba..90562fec 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ILoadingManageService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ILoadingManageService.java @@ -50,6 +50,8 @@ public interface ILoadingManageService extends BaseService { boolean changeRoute(LoadingManage loadingManage); + boolean start(Long id); + boolean cancel(Long id); boolean complete(Long id); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java index 71c9694f..dfbd4de5 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java @@ -29,6 +29,7 @@ import org.springblade.transport.service.IContractManageService; import org.springblade.transport.service.ILoadingManageService; import org.springblade.transport.service.IReceivablePayableDetailService; import org.springblade.transport.support.TransportBusinessSupport; +import org.springblade.transport.support.WaybillProcessSupport; import org.springblade.transport.wrapper.LoadingManageWrapper; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -40,8 +41,10 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; /** @@ -76,6 +79,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl entityPage = page(page, buildQuery(loadingManage)); IPage voPage = LoadingManageWrapper.build().pageVO(entityPage); voPage.getRecords().forEach(this::fillReadonly); + fillDriverRejected(voPage.getRecords()); return voPage; } @@ -83,6 +87,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl oldWaybillIds = new ArrayList<>(); + // 新建暂存为草稿;编辑已有单据时保持原业务状态(如待执行),避免被改回草稿 + String businessStatus = STATUS_DRAFT; if (Func.isNotEmpty(loadingManage.getId())) { LoadingManage oldRecord = loadEditable(loadingManage.getId(), true); oldWaybillIds = waybillIds(oldRecord.getWaybillIdsJson()); loadingManage.setLoadingNo(oldRecord.getLoadingNo()); loadingManage.setDeptId(oldRecord.getDeptId()); loadingManage.setDeptName(oldRecord.getDeptName()); + if (Func.isNotEmpty(oldRecord.getBusinessStatus())) { + businessStatus = oldRecord.getBusinessStatus(); + } } prepareCreateOrUpdate(loadingManage); - loadingManage.setBusinessStatus(STATUS_DRAFT); + loadingManage.setBusinessStatus(businessStatus); if (Func.isEmpty(loadingManage.getLoadingNo())) { loadingManage.setLoadingNo(nextCode()); } validateWaybillsAvailable(loadingManage, waybillIds(loadingManage.getWaybillIdsJson())); validateCarrierContract(loadingManage, false); boolean result = saveOrUpdate(loadingManage); - syncAssociatedWaybills(loadingManage, STATUS_DRAFT, oldWaybillIds); + syncAssociatedWaybills(loadingManage, businessStatus, oldWaybillIds); return result; } @@ -321,6 +331,9 @@ public class LoadingManageServiceImpl extends BaseServiceImpl oldWaybillIds = waybillIds(oldRecord.getWaybillIdsJson()); @@ -332,6 +345,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl()); + return result; + } + @Override @Transactional(rollbackFor = Exception.class) public boolean cancel(Long id) { @@ -817,6 +844,70 @@ public class LoadingManageServiceImpl extends BaseServiceImpl records) { + if (Func.isEmpty(records)) { + return; + } + List loadingNos = records.stream() + .map(LoadingManage::getLoadingNo) + .filter(Func::isNotEmpty) + .distinct() + .toList(); + Set rejectedLoadingNos = new HashSet<>(); + if (Func.isNotEmpty(loadingNos)) { + rejectedLoadingNos.addAll(waybillMapper.selectList(Wrappers.lambdaQuery() + .select(Waybill::getLoadingNo) + .in(Waybill::getLoadingNo, loadingNos) + .eq(Waybill::getIsDeleted, 0) + .eq(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_REJECTED)) + .stream() + .map(Waybill::getLoadingNo) + .filter(Func::isNotEmpty) + .collect(Collectors.toSet())); + } + for (LoadingManageVO record : records) { + record.setDriverRejected(rejectedLoadingNos.contains(record.getLoadingNo())); + } + } + + private boolean hasDriverRejectedWaybill(LoadingManage loadingManage) { + if (Func.isNotEmpty(loadingManage.getLoadingNo())) { + Long count = waybillMapper.selectCount(Wrappers.lambdaQuery() + .eq(Waybill::getLoadingNo, loadingManage.getLoadingNo()) + .eq(Waybill::getIsDeleted, 0) + .eq(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_REJECTED)); + if (count != null && count > 0) { + return true; + } + } + List ids = waybillIds(loadingManage.getWaybillIdsJson()); + if (Func.isEmpty(ids)) { + return false; + } + Long count = waybillMapper.selectCount(Wrappers.lambdaQuery() + .in(Waybill::getId, ids) + .eq(Waybill::getIsDeleted, 0) + .eq(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_REJECTED)); + return count != null && count > 0; + } + + private void clearAssociatedWaybillAcceptRecords(LoadingManage loadingManage) { + List ids = waybillIds(loadingManage.getWaybillIdsJson()); + var update = Wrappers.lambdaUpdate() + .set(Waybill::getDriverAcceptStatus, WaybillProcessSupport.ACCEPT_PENDING) + .set(Waybill::getDriverAcceptTime, null) + .set(Waybill::getDriverAcceptDriverId, null) + .set(Waybill::getDriverRejectTime, null) + .set(Waybill::getDriverRejectReason, null); + if (Func.isNotEmpty(ids)) { + waybillMapper.update(null, update.in(Waybill::getId, ids)); + return; + } + if (Func.isNotEmpty(loadingManage.getLoadingNo())) { + waybillMapper.update(null, update.eq(Waybill::getLoadingNo, loadingManage.getLoadingNo())); + } + } + private String formatMileage(BigDecimal mileage) { if (mileage == null) { return null; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MasterOrderServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MasterOrderServiceImpl.java index f71af8d1..14468a30 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MasterOrderServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/MasterOrderServiceImpl.java @@ -572,6 +572,6 @@ public class MasterOrderServiceImpl extends BaseServiceImpl } } else { waybill.setLoadingNo(null); + waybill.setMasterNo(null); waybill.setMileageRemark(null); + // 新建(含复制后提交)不沿用源单据状态,与纯新增一致 + waybill.setBusinessStatus(null); } // 无过程快照时按项目回填;再按接单设置决定 pending / running fillProjectProcessConfig(waybill); @@ -717,13 +720,12 @@ public class WaybillServiceImpl extends BaseServiceImpl target.setOtherFeeTotal(source.getOtherFeeTotal()); target.setTaskRemark(source.getTaskRemark()); target.setOriginalNo(source.getOriginalNo()); - target.setBusinessStatus(source.getBusinessStatus()); target.setDataSource(source.getDataSource()); target.setStartDate(source.getStartDate()); target.setEndDate(source.getEndDate()); target.setPlanId(source.getPlanId()); target.setPlanName(source.getPlanName()); - target.setMasterNo(source.getMasterNo()); + target.setMasterNo(null); target.setLoadingNo(null); target.setBatchNo(source.getBatchNo()); target.setRelationNo(source.getRelationNo()); @@ -736,7 +738,8 @@ public class WaybillServiceImpl extends BaseServiceImpl target.setFreightJson(source.getFreightJson()); target.setAttachmentsJson(source.getAttachmentsJson()); target.setRemark(source.getRemark()); - target.setBusinessStatus("pending"); + // 复制后状态与新增一致:先置空,再按过程配置校正为 pending/running + target.setBusinessStatus(null); target.setWaybillNo(nextCode()); clearDriverAcceptRecord(target); fillProjectProcessConfig(target); From 4d412e90eecd631a6fac64eb214fc67bcb43a057 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Wed, 16 Sep 2026 04:51:16 +0800 Subject: [PATCH 02/55] =?UTF-8?q?=E8=B0=83=E6=95=B4=20IAM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/granter/IamSsoTokenGranter.java | 34 ++++++++++++++--- .../handler/BladeAuthorizationHandler.java | 6 ++- .../system/service/IUserService.java | 2 +- .../system/service/impl/UserServiceImpl.java | 38 ++++++++++++++++++- 4 files changed, 70 insertions(+), 10 deletions(-) diff --git a/blade-auth/src/main/java/org/springblade/auth/granter/IamSsoTokenGranter.java b/blade-auth/src/main/java/org/springblade/auth/granter/IamSsoTokenGranter.java index c1dfecfb..1e8a6f05 100644 --- a/blade-auth/src/main/java/org/springblade/auth/granter/IamSsoTokenGranter.java +++ b/blade-auth/src/main/java/org/springblade/auth/granter/IamSsoTokenGranter.java @@ -86,6 +86,7 @@ public class IamSsoTokenGranter extends AuthorizationCodeGranter { private static final String IAM_TOKEN_URI = "/iam/sso/token"; private static final String BEARER_PREFIX = "Bearer "; private static final String BASIC_PREFIX = "Basic "; + private static final String IAM_DEFAULT_ROLE_ID = "1123598816738675203"; private final OAuth2ClientService clientService; private final IUserClient userClient; @@ -159,27 +160,40 @@ public class IamSsoTokenGranter extends AuthorizationCodeGranter { if (user == null) { throw new UserInvalidException(OAuth2TokenConstant.USER_NOT_FOUND); } + if (user.getAuthorities() == null || user.getAuthorities().isEmpty()) { + log.warn("IAM统一身份认证用户缺少可登录角色,tenantId={}, accountNo={}", tenantId, accountNo); + throw new UserInvalidException(OAuth2TokenConstant.USER_HAS_NO_ROLE); + } user.setClient(client(request)); return user; } private UserInfo loadOrCreateIamUser(OAuth2Request request, IamSsoProfileResponse profileResponse, String tenantId, String accountNo) { R result = userClient.userInfo(tenantId, accountNo); - if (result.isSuccess() && hasUser(result.getData())) { + if (result.isSuccess() && hasLoginAccess(result.getData())) { return result.getData(); } - log.info("IAM统一身份认证未匹配到本系统账号,开始自动创建用户,tenantId={}, accountNo={}, querySuccess={}", - tenantId, accountNo, result.isSuccess()); + if (result.isSuccess() && hasUser(result.getData())) { + log.info("IAM统一身份认证账号缺少可登录角色,开始补齐默认角色,tenantId={}, accountNo={}", tenantId, accountNo); + } else { + log.info("IAM统一身份认证未匹配到本系统账号,开始自动创建用户,tenantId={}, accountNo={}, querySuccess={}", + tenantId, accountNo, result.isSuccess()); + } R saveResult = userClient.saveIamUser(buildIamUser(profileResponse, tenantId, accountNo)); if (!saveResult.isSuccess() || !Boolean.TRUE.equals(saveResult.getData())) { - log.warn("IAM统一身份认证自动创建用户失败,tenantId={}, accountNo={}, msg={}", tenantId, accountNo, saveResult.getMsg()); - throw new UserInvalidException(OAuth2TokenConstant.USER_NOT_FOUND); + String failMsg = StringUtil.isNotBlank(saveResult.getMsg()) ? saveResult.getMsg() : OAuth2TokenConstant.USER_HAS_NO_ROLE; + log.warn("IAM统一身份认证自动创建或补齐用户失败,tenantId={}, accountNo={}, msg={}", tenantId, accountNo, failMsg); + throw new UserInvalidException(failMsg); } R createdResult = userClient.userInfo(tenantId, accountNo); if (!createdResult.isSuccess() || !hasUser(createdResult.getData())) { log.warn("IAM统一身份认证自动创建用户后未查询到用户,tenantId={}, accountNo={}", tenantId, accountNo); throw new UserInvalidException(OAuth2TokenConstant.USER_NOT_FOUND); } + if (!hasRoles(createdResult.getData())) { + log.warn("IAM统一身份认证自动创建用户后仍缺少可登录角色,tenantId={}, accountNo={}", tenantId, accountNo); + throw new UserInvalidException(OAuth2TokenConstant.USER_HAS_NO_ROLE); + } return createdResult.getData(); } @@ -191,6 +205,14 @@ public class IamSsoTokenGranter extends AuthorizationCodeGranter { return userInfo != null && userInfo.getUser() != null; } + private boolean hasRoles(UserInfo userInfo) { + return userInfo != null && userInfo.getRoles() != null && !userInfo.getRoles().isEmpty(); + } + + private boolean hasLoginAccess(UserInfo userInfo) { + return hasUser(userInfo) && hasRoles(userInfo); + } + private User buildIamUser(IamSsoProfileResponse profileResponse, String tenantId, String accountNo) { User user = new User(); user.setTenantId(tenantId); @@ -199,7 +221,7 @@ public class IamSsoTokenGranter extends AuthorizationCodeGranter { user.setPassword(UUID.randomUUID().toString()); user.setName(accountNo); user.setRealName(accountNo); - user.setRoleId(StringPool.MINUS_ONE); + user.setRoleId(IAM_DEFAULT_ROLE_ID); user.setDeptId(StringPool.MINUS_ONE); user.setPostId(StringPool.MINUS_ONE); user.setIsOa(1); diff --git a/blade-auth/src/main/java/org/springblade/auth/handler/BladeAuthorizationHandler.java b/blade-auth/src/main/java/org/springblade/auth/handler/BladeAuthorizationHandler.java index e6e793eb..2c12576a 100644 --- a/blade-auth/src/main/java/org/springblade/auth/handler/BladeAuthorizationHandler.java +++ b/blade-auth/src/main/java/org/springblade/auth/handler/BladeAuthorizationHandler.java @@ -154,7 +154,11 @@ public class BladeAuthorizationHandler extends AbstractAuthorizationHandler { */ @Override public void authFailure(OAuth2User user, OAuth2Request request, OAuth2Validation validation) { - // 自定义认证失败回调 + log.error("用户:{},认证失败,失败原因:{},grantType={},authorities={}", + user == null ? request.getUsername() : user.getAccount(), + validation.getMessage(), + request.getGrantType(), + user == null ? null : user.getAuthorities()); } /** diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java index e1ff5ee5..35899e0c 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java @@ -281,7 +281,7 @@ public interface IUserService extends BaseService { boolean registerUser(User user); /** - * 新建IAM统一身份认证用户(按可信租户落库) + * 新建或补齐IAM统一身份认证用户(按可信租户落库,默认分配角色 1123598816738675203) * * @param user 用户实体 * @return 是否成功 diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java index 944d99ef..8428dfed 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java @@ -96,6 +96,7 @@ public class UserServiceImpl extends BaseServiceImpl implement private static final String PASSWORD_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"; private static final int RANDOM_PASSWORD_LENGTH = 8; private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + private static final Long IAM_DEFAULT_ROLE_ID = 1123598816738675203L; private final IUserDeptService userDeptService; private final UserDataScopeMapper userDataScopeMapper; @@ -563,8 +564,27 @@ public class UserServiceImpl extends BaseServiceImpl implement if (user.getUserType() == null) { user.setUserType(UserType.WEB.getCategory()); } - if (StringUtil.isBlank(user.getRoleId())) { - user.setRoleId(StringPool.MINUS_ONE); + String defaultRoleId = resolveIamDefaultRoleId(); + User existingUser = userByAccount(user.getTenantId(), user.getAccount()); + if (existingUser != null) { + boolean changed = false; + if (isMissingIamRole(existingUser.getRoleId())) { + existingUser.setRoleId(defaultRoleId); + changed = true; + } + if (existingUser.getIsOa() == null || existingUser.getIsOa() != 1) { + existingUser.setIsOa(1); + existingUser.setSyncTime(new Date()); + changed = true; + } + if (!changed) { + return true; + } + CacheUtil.clear(USER_CACHE); + return this.updateById(existingUser); + } + if (isMissingIamRole(user.getRoleId())) { + user.setRoleId(defaultRoleId); } if (StringUtil.isBlank(user.getDeptId())) { user.setDeptId(StringPool.MINUS_ONE); @@ -579,6 +599,20 @@ public class UserServiceImpl extends BaseServiceImpl implement return saveUser(user); } + private boolean isMissingIamRole(String roleId) { + return StringUtil.isBlank(roleId) || StringPool.MINUS_ONE.equals(roleId); + } + + private String resolveIamDefaultRoleId() { + Role role = roleService.getOne(Wrappers.lambdaQuery() + .eq(Role::getId, IAM_DEFAULT_ROLE_ID) + .eq(Role::getIsDeleted, BladeConstant.DB_NOT_DELETED)); + if (role == null || role.getId() == null) { + throw new ServiceException("IAM用户默认角色不存在,roleId=" + IAM_DEFAULT_ROLE_ID); + } + return String.valueOf(role.getId()); + } + @Override @Transactional(rollbackFor = Exception.class) public boolean updatePlatform(Long userId, Integer userType, String userExt) { From d1b4f286b267162027f924d439588c9d3f200f2b Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Wed, 16 Sep 2026 06:29:26 +0800 Subject: [PATCH 03/55] =?UTF-8?q?=E8=B0=83=E6=95=B4=20IAM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blade-auth/pom.xml | 4 + .../auth/granter/IamSsoTokenGranter.java | 29 ++- blade-auth/src/main/resources/application.yml | 2 +- .../springblade/system/SystemApplication.java | 4 +- .../system/controller/UserController.java | 11 ++ .../system/props/IamSyncProperties.java | 52 +++++ .../system/service/IUserService.java | 7 + .../system/service/impl/UserServiceImpl.java | 184 +++++++++++++++++- .../src/main/resources/application.yml | 8 + 9 files changed, 287 insertions(+), 14 deletions(-) create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/props/IamSyncProperties.java diff --git a/blade-auth/pom.xml b/blade-auth/pom.xml index b0887270..56404632 100644 --- a/blade-auth/pom.xml +++ b/blade-auth/pom.xml @@ -63,6 +63,10 @@ org.springblade blade-user-api + + org.springblade + blade-dict-api + org.springblade blade-system-api diff --git a/blade-auth/src/main/java/org/springblade/auth/granter/IamSsoTokenGranter.java b/blade-auth/src/main/java/org/springblade/auth/granter/IamSsoTokenGranter.java index 1e8a6f05..8763a210 100644 --- a/blade-auth/src/main/java/org/springblade/auth/granter/IamSsoTokenGranter.java +++ b/blade-auth/src/main/java/org/springblade/auth/granter/IamSsoTokenGranter.java @@ -45,6 +45,7 @@ import org.springblade.core.redis.cache.BladeRedis; import org.springblade.core.tool.api.R; import org.springblade.core.tool.utils.StringUtil; import org.springblade.core.tool.utils.StringPool; +import org.springblade.system.cache.DictCache; import org.springblade.system.feign.IUserClient; import org.springblade.system.pojo.entity.User; import org.springblade.system.pojo.entity.UserInfo; @@ -86,7 +87,9 @@ public class IamSsoTokenGranter extends AuthorizationCodeGranter { private static final String IAM_TOKEN_URI = "/iam/sso/token"; private static final String BEARER_PREFIX = "Bearer "; private static final String BASIC_PREFIX = "Basic "; - private static final String IAM_DEFAULT_ROLE_ID = "1123598816738675203"; + private static final String IAM_DEFAULT_DICT_CODE = "iam_default"; + private static final String IAM_DEFAULT_ROLE_NAME = "默认角色"; + private static final String IAM_DEFAULT_DEPT_NAME = "默认部门"; private final OAuth2ClientService clientService; private final IUserClient userClient; @@ -155,6 +158,7 @@ public class IamSsoTokenGranter extends AuthorizationCodeGranter { log.warn("IAM统一身份认证请求缺少租户ID,accountNo={}", accountNo); throw new UserInvalidException(OAuth2TokenConstant.USER_NOT_FOUND); } + log.info("IAM统一身份认证开始匹配本系统账号,tenantId={}, accountNo={}", tenantId, accountNo); UserInfo userInfo = loadOrCreateIamUser(request, profileResponse, tenantId, accountNo); OAuth2User user = TokenUtil.convertUser(userInfo, request); if (user == null) { @@ -174,7 +178,7 @@ public class IamSsoTokenGranter extends AuthorizationCodeGranter { return result.getData(); } if (result.isSuccess() && hasUser(result.getData())) { - log.info("IAM统一身份认证账号缺少可登录角色,开始补齐默认角色,tenantId={}, accountNo={}", tenantId, accountNo); + log.info("IAM统一身份认证账号缺少有效角色或部门,开始补齐默认配置,tenantId={}, accountNo={}", tenantId, accountNo); } else { log.info("IAM统一身份认证未匹配到本系统账号,开始自动创建用户,tenantId={}, accountNo={}, querySuccess={}", tenantId, accountNo, result.isSuccess()); @@ -210,7 +214,14 @@ public class IamSsoTokenGranter extends AuthorizationCodeGranter { } private boolean hasLoginAccess(UserInfo userInfo) { - return hasUser(userInfo) && hasRoles(userInfo); + return hasUser(userInfo) && hasRoles(userInfo) && hasValidAssignment(userInfo.getUser()); + } + + private boolean hasValidAssignment(User user) { + return StringUtil.isNotBlank(user.getRoleId()) + && !StringPool.MINUS_ONE.equals(user.getRoleId()) + && StringUtil.isNotBlank(user.getDeptId()) + && !StringPool.MINUS_ONE.equals(user.getDeptId()); } private User buildIamUser(IamSsoProfileResponse profileResponse, String tenantId, String accountNo) { @@ -221,8 +232,8 @@ public class IamSsoTokenGranter extends AuthorizationCodeGranter { user.setPassword(UUID.randomUUID().toString()); user.setName(accountNo); user.setRealName(accountNo); - user.setRoleId(IAM_DEFAULT_ROLE_ID); - user.setDeptId(StringPool.MINUS_ONE); + user.setRoleId(resolveIamDefaultId(IAM_DEFAULT_ROLE_NAME)); + user.setDeptId(resolveIamDefaultId(IAM_DEFAULT_DEPT_NAME)); user.setPostId(StringPool.MINUS_ONE); user.setIsOa(1); user.setSyncTime(new Date()); @@ -231,6 +242,14 @@ public class IamSsoTokenGranter extends AuthorizationCodeGranter { return user; } + private String resolveIamDefaultId(String dictValue) { + String dictKey = DictCache.getKey(IAM_DEFAULT_DICT_CODE, dictValue); + if (StringUtil.isBlank(dictKey) || StringPool.MINUS_ONE.equals(dictKey)) { + throw new UserInvalidException(StringUtil.format("IAM默认配置 [{}] 未配置有效键值", dictValue)); + } + return dictKey; + } + public boolean supports(OAuth2Request request) { return isIamRequest(request, false); } diff --git a/blade-auth/src/main/resources/application.yml b/blade-auth/src/main/resources/application.yml index 586debc0..714ddbe4 100644 --- a/blade-auth/src/main/resources/application.yml +++ b/blade-auth/src/main/resources/application.yml @@ -100,5 +100,5 @@ iam: system-client-id: ${IAM_SSO_SYSTEM_CLIENT_ID:saber3} system-client-secret: ${IAM_SSO_SYSTEM_CLIENT_SECRET:saber3_secret} redirect-uri: ${IAM_SSO_REDIRECT_URI:http://172.16.203.228:8000/callback} - authorization: ${IAM_SSO_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=} + authorization: ${IAM_SSO_AUTHORIZATION:YjNlZmU0ODEwMzJiNGJhZTpkYzQ5NDY1NmQ4MzE0NThjODI5MzlmNzA2ZjliNDY3MQ==} profile-authorization: ${IAM_SSO_PROFILE_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/SystemApplication.java b/blade-service/blade-system/src/main/java/org/springblade/system/SystemApplication.java index bbad903b..b55719ff 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/SystemApplication.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/SystemApplication.java @@ -28,13 +28,16 @@ package org.springblade.system; import org.springblade.core.cloud.client.BladeCloudApplication; import org.springblade.core.launch.BladeApplication; import org.springblade.core.launch.constant.AppConstant; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.ComponentScan; +import org.springblade.system.props.IamSyncProperties; /** * 系统模块启动器 * @author Chill */ @ComponentScan(basePackages = {"org.springblade.system", "org.springblade.process"}) +@EnableConfigurationProperties(IamSyncProperties.class) @BladeCloudApplication public class SystemApplication { @@ -44,4 +47,3 @@ public class SystemApplication { } } - diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserController.java index 328fd2af..a4b7b6b3 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserController.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserController.java @@ -157,6 +157,17 @@ public class UserController { return R.status(userService.submit(user)); } + /** + * 同步IAM账号。 + */ + @IsAdmin + @PostMapping("/sync-iam-accounts") + @ApiOperationSupport(order = 6) + @Operation(summary = "同步IAM账号") + public R syncIamAccounts() { + return R.data(userService.syncIamAccounts()); + } + /** * 修改 */ diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/props/IamSyncProperties.java b/blade-service/blade-system/src/main/java/org/springblade/system/props/IamSyncProperties.java new file mode 100644 index 00000000..b956961c --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/props/IamSyncProperties.java @@ -0,0 +1,52 @@ +/** + * 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.props; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * IAM账号同步配置。 + * + * @author Chill + */ +@Data +@ConfigurationProperties(prefix = "iam.sync") +public class IamSyncProperties { + + /** IAM增量账号接口地址。 */ + private String accountListUrl; + + /** IAM接口Authorization请求头。 */ + private String authorization; + + /** IAM接口Auth请求头。 */ + private String profileAuthorization; + + /** 单页请求数量。 */ + private int pageSize = 50; + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java index 35899e0c..366db182 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java @@ -78,6 +78,13 @@ public interface IUserService extends BaseService { */ boolean submit(User user); + /** + * 从IAM同步管理租户账号。 + * + * @return 同步处理的账号数量 + */ + int syncIamAccounts(); + /** * 修改用户(租户守卫校验用户归属,含账号 / 手机查重) * diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java index 8428dfed..3cc6099f 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java @@ -31,7 +31,11 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.common.constant.DataStatusEnum; import org.bouncycastle.util.encoders.Hex; import org.springblade.common.constant.ParamConstant; import org.springblade.common.constant.TenantConstant; @@ -63,6 +67,7 @@ import org.springblade.system.pojo.entity.*; import org.springblade.system.pojo.enums.DictEnum; import org.springblade.system.pojo.enums.UserType; import org.springblade.system.pojo.vo.UserVO; +import org.springblade.system.props.IamSyncProperties; import org.springblade.system.service.IRoleService; import org.springblade.system.service.IUserDeptService; import org.springblade.system.service.IUserOauthService; @@ -72,6 +77,11 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.security.SecureRandom; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -79,6 +89,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.nio.charset.StandardCharsets; +import java.time.Duration; import static org.springblade.common.constant.ParamConstant.DEFAULT_PARAM_PASSWORD; import static org.springblade.core.cache.constant.CacheConstant.USER_CACHE; @@ -91,12 +103,17 @@ import static org.springblade.core.tenant.TenantGuard.EntityType.USER; */ @Service @AllArgsConstructor +@Slf4j public class UserServiceImpl extends BaseServiceImpl implements IUserService { private static final String GUEST_NAME = "guest"; private static final String PASSWORD_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"; private static final int RANDOM_PASSWORD_LENGTH = 8; private static final SecureRandom SECURE_RANDOM = new SecureRandom(); - private static final Long IAM_DEFAULT_ROLE_ID = 1123598816738675203L; + private static final String IAM_DEFAULT_DICT_CODE = "iam_default"; + private static final String IAM_DEFAULT_ROLE_NAME = "默认角色"; + private static final String IAM_DEFAULT_DEPT_NAME = "默认部门"; + private static final String IAM_SYNC_TENANT_ID = "000000"; + private static final HttpClient IAM_HTTP_CLIENT = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build(); private final IUserDeptService userDeptService; private final UserDataScopeMapper userDataScopeMapper; @@ -106,6 +123,8 @@ public class UserServiceImpl extends BaseServiceImpl implement private final BladeTenantProperties tenantProperties; private final OAuth2Properties properties; + private final IamSyncProperties iamSyncProperties; + private final ObjectMapper objectMapper; @Override @@ -133,6 +152,147 @@ public class UserServiceImpl extends BaseServiceImpl implement return saveUser(user); } + @Override + @Transactional(rollbackFor = Exception.class) + public int syncIamAccounts() { + int pageNumber = 1; + int fetchedCount = 0; + int syncedCount = 0; + int totalCount = -1; + int pageSize = iamSyncProperties.getPageSize() > 0 ? iamSyncProperties.getPageSize() : 50; + while (true) { + JsonNode dataNode = requestIamAccountPage(pageNumber, pageSize); + JsonNode accountList = dataNode.path("list"); + if (!accountList.isArray() || accountList.isEmpty()) { + break; + } + if (dataNode.has("total")) { + totalCount = dataNode.path("total").asInt(totalCount); + } + for (JsonNode accountNode : accountList) { + if (syncIamAccount(accountNode)) { + syncedCount++; + } + } + fetchedCount += accountList.size(); + int responsePage = dataNode.path("page").asInt(pageNumber); + int responseSize = dataNode.path("size").asInt(pageSize); + if ((totalCount >= 0 && fetchedCount >= totalCount) + || accountList.size() < pageSize + || (totalCount >= 0 && responsePage * responseSize >= totalCount)) { + break; + } + pageNumber = responsePage + 1; + } + log.info("IAM账号同步完成,tenantId={}, fetchedCount={}, syncedCount={}", IAM_SYNC_TENANT_ID, fetchedCount, syncedCount); + return syncedCount; + } + + private JsonNode requestIamAccountPage(int pageNumber, int pageSize) { + try { + Map requestBody = new LinkedHashMap<>(); + requestBody.put("size", String.valueOf(pageSize)); + requestBody.put("page", String.valueOf(pageNumber)); + HttpRequest request = HttpRequest.newBuilder(URI.create(iamSyncProperties.getAccountListUrl())) + .timeout(Duration.ofSeconds(20)) + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .header("Auth", normalizeAuthorizationHeader(iamSyncProperties.getProfileAuthorization())) + .header("Authorization", normalizeAuthorizationHeader(iamSyncProperties.getAuthorization())) + .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(requestBody), StandardCharsets.UTF_8)) + .build(); + HttpResponse response = IAM_HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + if (response.statusCode() < 200 || response.statusCode() >= 300) { + throw new ServiceException(StringUtil.format("IAM账号接口调用失败,HTTP状态码:{}", response.statusCode())); + } + JsonNode responseNode = objectMapper.readTree(response.body()); + if (!"0".equals(responseNode.path("code").asText())) { + throw new ServiceException(StringUtil.format("IAM账号接口调用失败:{}", responseNode.path("msg").asText())); + } + JsonNode dataNode = responseNode.path("data"); + if (!dataNode.isObject()) { + throw new ServiceException("IAM账号接口返回数据格式错误"); + } + return dataNode; + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + log.error("调用IAM账号接口被中断,page={}", pageNumber, exception); + throw new ServiceException("调用IAM账号接口被中断"); + } catch (IOException | IllegalArgumentException exception) { + log.error("调用IAM账号接口失败,page={}", pageNumber, exception); + throw new ServiceException("调用IAM账号接口失败"); + } + } + + private boolean syncIamAccount(JsonNode accountNode) { + String account = readIamText(accountNode, "accountNo"); + if (StringUtil.isBlank(account)) { + log.warn("IAM账号缺少accountNo,跳过同步"); + return false; + } + String name = readIamText(accountNode, "name"); + if (StringUtil.isBlank(name)) { + name = readIamText(accountNode, "accountName"); + } + if (StringUtil.isBlank(name)) { + name = account; + } + Integer status = accountNode.path("status").asInt(0) == 1 + ? DataStatusEnum.ENABLE.getCode() : DataStatusEnum.DISABLE.getCode(); + User user = userByAccount(IAM_SYNC_TENANT_ID, account); + if (user == null) { + user = new User(); + user.setTenantId(IAM_SYNC_TENANT_ID); + user.setAccount(account); + user.setName(name); + user.setRealName(name); + user.setRoleId(resolveIamDefaultRoleId()); + user.setDeptId(resolveIamDefaultId(IAM_DEFAULT_DEPT_NAME)); + user.setPostId(StringPool.MINUS_ONE); + user.setUserType(UserType.WEB.getCategory()); + user.setStatus(status); + user.setIsOa(1); + user.setSyncTime(new Date()); + applyUserDefaults(user); + return saveUser(user); + } + boolean changed = !Objects.equals(user.getName(), name) || !Objects.equals(user.getRealName(), name) + || !Objects.equals(user.getStatus(), status) || !Objects.equals(user.getIsOa(), 1); + if (isMissingIamRole(user.getRoleId())) { + user.setRoleId(resolveIamDefaultRoleId()); + changed = true; + } + if (isMissingIamAssignment(user.getDeptId())) { + user.setDeptId(resolveIamDefaultId(IAM_DEFAULT_DEPT_NAME)); + changed = true; + } + if (!changed) { + return true; + } + user.setName(name); + user.setRealName(name); + user.setStatus(status); + user.setIsOa(1); + user.setSyncTime(new Date()); + CacheUtil.clear(USER_CACHE); + return updateById(user); + } + + private String readIamText(JsonNode node, String fieldName) { + JsonNode valueNode = node.get(fieldName); + return valueNode == null || valueNode.isNull() ? StringPool.EMPTY : valueNode.asText().trim(); + } + + private String normalizeAuthorizationHeader(String value) { + if (StringUtil.isBlank(value)) { + return StringPool.EMPTY; + } + if (StringUtil.startsWithIgnoreCase(value, "Basic ") || StringUtil.startsWithIgnoreCase(value, "Bearer ")) { + return value; + } + return "Basic " + value; + } + @Override @Transactional(rollbackFor = Exception.class) public boolean updateUser(User user) { @@ -572,6 +732,10 @@ public class UserServiceImpl extends BaseServiceImpl implement existingUser.setRoleId(defaultRoleId); changed = true; } + if (isMissingIamAssignment(existingUser.getDeptId())) { + existingUser.setDeptId(resolveIamDefaultId(IAM_DEFAULT_DEPT_NAME)); + changed = true; + } if (existingUser.getIsOa() == null || existingUser.getIsOa() != 1) { existingUser.setIsOa(1); existingUser.setSyncTime(new Date()); @@ -604,13 +768,19 @@ public class UserServiceImpl extends BaseServiceImpl implement } private String resolveIamDefaultRoleId() { - Role role = roleService.getOne(Wrappers.lambdaQuery() - .eq(Role::getId, IAM_DEFAULT_ROLE_ID) - .eq(Role::getIsDeleted, BladeConstant.DB_NOT_DELETED)); - if (role == null || role.getId() == null) { - throw new ServiceException("IAM用户默认角色不存在,roleId=" + IAM_DEFAULT_ROLE_ID); + return resolveIamDefaultId(IAM_DEFAULT_ROLE_NAME); + } + + private String resolveIamDefaultId(String dictValue) { + String dictKey = DictCache.getKey(IAM_DEFAULT_DICT_CODE, dictValue); + if (StringUtil.isBlank(dictKey) || StringPool.MINUS_ONE.equals(dictKey)) { + throw new ServiceException(StringUtil.format("IAM默认配置 [{}] 未配置有效键值", dictValue)); } - return String.valueOf(role.getId()); + return dictKey; + } + + private boolean isMissingIamAssignment(String value) { + return StringUtil.isBlank(value) || StringPool.MINUS_ONE.equals(value); } @Override diff --git a/blade-service/blade-system/src/main/resources/application.yml b/blade-service/blade-system/src/main/resources/application.yml index 28509cf4..549ebd08 100644 --- a/blade-service/blade-system/src/main/resources/application.yml +++ b/blade-service/blade-system/src/main/resources/application.yml @@ -23,3 +23,11 @@ spring: url: ${blade.datasource.${spring.profiles.active}.url} username: ${blade.datasource.${spring.profiles.active}.username} password: ${blade.datasource.${spring.profiles.active}.password} + +# IAM账号同步 +iam: + sync: + account-list-url: ${IAM_SSO_ACCOUNT_LIST_URL:http://172.16.204.83:38000/gwzh/IAM/IAM_IDM_ACCOUNT_LIST} + authorization: ${IAM_SSO_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=} + profile-authorization: ${IAM_SSO_PROFILE_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=} + page-size: ${IAM_SSO_ACCOUNT_PAGE_SIZE:50} From 8a79f4d42832b533fe40df3f35aff5fdff4ce579 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Wed, 16 Sep 2026 06:36:36 +0800 Subject: [PATCH 04/55] =?UTF-8?q?=E8=B0=83=E6=95=B4=20IAM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blade-auth/src/main/resources/application.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blade-auth/src/main/resources/application.yml b/blade-auth/src/main/resources/application.yml index 714ddbe4..6edfb410 100644 --- a/blade-auth/src/main/resources/application.yml +++ b/blade-auth/src/main/resources/application.yml @@ -100,5 +100,5 @@ iam: system-client-id: ${IAM_SSO_SYSTEM_CLIENT_ID:saber3} system-client-secret: ${IAM_SSO_SYSTEM_CLIENT_SECRET:saber3_secret} redirect-uri: ${IAM_SSO_REDIRECT_URI:http://172.16.203.228:8000/callback} - authorization: ${IAM_SSO_AUTHORIZATION:YjNlZmU0ODEwMzJiNGJhZTpkYzQ5NDY1NmQ4MzE0NThjODI5MzlmNzA2ZjliNDY3MQ==} - profile-authorization: ${IAM_SSO_PROFILE_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=} + authorization: ${IAM_SSO_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=} + profile-authorization: ${IAM_SSO_PROFILE_AUTHORIZATION:YjNlZmU0ODEwMzJiNGJhZTpkYzQ5NDY1NmQ4MzE0NThjODI5MzlmNzA2ZjliNDY3MQ==} From b182b700b35cd85e8355a0f24331f4a006ddee21 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Wed, 16 Sep 2026 06:52:35 +0800 Subject: [PATCH 05/55] =?UTF-8?q?=E8=B0=83=E6=95=B4=20IAM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/springblade/system/service/impl/UserServiceImpl.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java index 3cc6099f..c738d73a 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java @@ -201,6 +201,9 @@ public class UserServiceImpl extends BaseServiceImpl implement .header("Authorization", normalizeAuthorizationHeader(iamSyncProperties.getAuthorization())) .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(requestBody), StandardCharsets.UTF_8)) .build(); + log.info("Auth,{}", normalizeAuthorizationHeader(iamSyncProperties.getProfileAuthorization())); + log.info("Authorization,{}", normalizeAuthorizationHeader(iamSyncProperties.getAuthorization())); + HttpResponse response = IAM_HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); if (response.statusCode() < 200 || response.statusCode() >= 300) { throw new ServiceException(StringUtil.format("IAM账号接口调用失败,HTTP状态码:{}", response.statusCode())); From ad496fdf2840a369c593c07327171e5fc1653c1a Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Wed, 16 Sep 2026 07:32:06 +0800 Subject: [PATCH 06/55] =?UTF-8?q?=E8=B0=83=E6=95=B4=20IAM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blade-service/blade-system/src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blade-service/blade-system/src/main/resources/application.yml b/blade-service/blade-system/src/main/resources/application.yml index 549ebd08..f3492fad 100644 --- a/blade-service/blade-system/src/main/resources/application.yml +++ b/blade-service/blade-system/src/main/resources/application.yml @@ -29,5 +29,5 @@ iam: sync: account-list-url: ${IAM_SSO_ACCOUNT_LIST_URL:http://172.16.204.83:38000/gwzh/IAM/IAM_IDM_ACCOUNT_LIST} authorization: ${IAM_SSO_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=} - profile-authorization: ${IAM_SSO_PROFILE_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=} + profile-authorization: ${IAM_SSO_PROFILE_AUTHORIZATION:YjNlZmU0ODEwMzJiNGJhZTpkYzQ5NDY1NmQ4MzE0NThjODI5MzlmNzA2ZjliNDY3MQ==} page-size: ${IAM_SSO_ACCOUNT_PAGE_SIZE:50} From cbc7e16453dd46a6eafc8ac508fb53a88d74a80e Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Wed, 16 Sep 2026 08:08:09 +0800 Subject: [PATCH 07/55] =?UTF-8?q?=E8=B0=83=E6=95=B4=20IAM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/controller/DeptController.java | 19 +- .../system/props/IamSyncProperties.java | 3 + .../system/service/IDeptService.java | 7 + .../system/service/impl/DeptServiceImpl.java | 195 ++++++++++++++++++ .../system/service/impl/UserServiceImpl.java | 31 ++- .../src/main/resources/application.yml | 1 + doc/nacos/blade-prod.yaml | 9 + 7 files changed, 252 insertions(+), 13 deletions(-) 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 11c4081a..9cccc5dd 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 @@ -159,12 +159,23 @@ public class DeptController extends BladeController { return R.fail("操作失败"); } + /** + * 同步IAM组织。 + */ + @IsAdmin + @PostMapping("/sync-iam-organizations") + @ApiOperationSupport(order = 7) + @Operation(summary = "同步IAM组织") + public R syncIamOrganizations() { + return R.data(deptService.syncIamOrganizations()); + } + /** * 删除 */ @IsAdmin @PostMapping("/remove") - @ApiOperationSupport(order = 7) + @ApiOperationSupport(order = 8) @Operation(summary = "删除", description = "传入ids") public R remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) { CacheUtil.clear(SYS_CACHE); @@ -177,7 +188,7 @@ public class DeptController extends BladeController { */ @PreAuth(AuthConstant.PERMIT_ALL) @GetMapping("/select") - @ApiOperationSupport(order = 8) + @ApiOperationSupport(order = 9) @Operation(summary = "下拉数据源", description = "传入id集合") public R> select(Long userId, String deptId) { if (Func.isNotEmpty(userId)) { @@ -194,7 +205,7 @@ public class DeptController extends BladeController { */ @PreAuth(AuthConstant.PERMIT_ALL) @GetMapping("/platform-company-select") - @ApiOperationSupport(order = 9) + @ApiOperationSupport(order = 10) @Operation(summary = "平台公司下拉", description = "返回是否平台公司=是的部门列表") public R> platformCompanySelect() { return R.data(deptService.listPlatformCompany()); @@ -205,7 +216,7 @@ public class DeptController extends BladeController { */ @IsAdmin @GetMapping("/dept-leader-info") - @ApiOperationSupport(order = 10) + @ApiOperationSupport(order = 11) @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/props/IamSyncProperties.java b/blade-service/blade-system/src/main/java/org/springblade/system/props/IamSyncProperties.java index b956961c..2cbc9a3c 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/props/IamSyncProperties.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/props/IamSyncProperties.java @@ -40,6 +40,9 @@ public class IamSyncProperties { /** IAM增量账号接口地址。 */ private String accountListUrl; + /** IAM组织接口地址。 */ + private String orgListUrl; + /** IAM接口Authorization请求头。 */ private String authorization; diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java index 48e51020..9aef0131 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IDeptService.java @@ -156,6 +156,13 @@ public interface IDeptService extends IService { */ boolean submit(Dept dept); + /** + * 从IAM同步管理租户组织。 + + * @return 同步处理的组织数量 + */ + int syncIamOrganizations(); + /** * 按名称与父级查询部门列表(限定当前会话租户) * diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java index ff25a472..bf43f449 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/DeptServiceImpl.java @@ -29,8 +29,12 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springblade.common.constant.DataStatusEnum; +import org.springblade.core.cache.utils.CacheUtil; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.mp.support.Condition; import org.springblade.core.secure.utils.AuthUtil; @@ -44,6 +48,7 @@ import org.springblade.system.pojo.entity.Dept; import org.springblade.system.pojo.entity.User; import org.springblade.system.pojo.vo.DeptVO; import org.springblade.system.pojo.vo.UserVO; +import org.springblade.system.props.IamSyncProperties; import org.springblade.system.service.IDeptService; import org.springblade.system.service.IUserService; import org.springblade.system.wrapper.DeptWrapper; @@ -52,6 +57,13 @@ import org.springblade.thirdparty.oa.constant.OAConvertConstant; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.*; import java.util.function.Function; import java.util.regex.Pattern; @@ -59,6 +71,7 @@ import java.util.stream.Collectors; import static org.springblade.core.tenant.TenantGuard.EntityType.DEPT; import static org.springblade.core.tenant.TenantGuard.EntityType.DEPT_PARENT; +import static org.springblade.core.cache.constant.CacheConstant.SYS_CACHE; /** * 服务实现类 @@ -72,8 +85,13 @@ public class DeptServiceImpl extends ServiceImpl implements ID private static final String TENANT_ID = "tenantId"; private static final String PARENT_ID = "parentId"; + private static final String IAM_SYNC_TENANT_ID = "000000"; + private static final Long IAM_SYNC_PARENT_ID = 1123598813738675201L; + private static final HttpClient IAM_HTTP_CLIENT = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build(); private final IUserService userService; + private final IamSyncProperties iamSyncProperties; + private final ObjectMapper objectMapper; @Override public Dept getDetail(Dept dept) { @@ -255,6 +273,183 @@ public class DeptServiceImpl extends ServiceImpl implements ID return saveOrUpdate(dept); } + @Override + @Transactional(rollbackFor = Exception.class) + public int syncIamOrganizations() { + int pageNumber = 1; + int fetchedCount = 0; + int syncedCount = 0; + int totalCount = -1; + int pageSize = iamSyncProperties.getPageSize() > 0 ? iamSyncProperties.getPageSize() : 50; + while (true) { + JsonNode dataNode = requestIamOrgPage(pageNumber, pageSize); + JsonNode orgList = dataNode.path("list"); + if (!orgList.isArray() || orgList.isEmpty()) { + break; + } + if (dataNode.has("total")) { + totalCount = dataNode.path("total").asInt(totalCount); + } + for (JsonNode orgNode : orgList) { + if (syncIamOrganization(orgNode)) { + syncedCount++; + } + } + fetchedCount += orgList.size(); + int responsePage = dataNode.path("page").asInt(pageNumber); + int responseSize = dataNode.path("size").asInt(pageSize); + if ((totalCount >= 0 && fetchedCount >= totalCount) + || orgList.size() < pageSize + || (totalCount >= 0 && responsePage * responseSize >= totalCount)) { + break; + } + pageNumber = responsePage + 1; + } + log.info("IAM组织同步完成,tenantId={}, fetchedCount={}, syncedCount={}", IAM_SYNC_TENANT_ID, fetchedCount, syncedCount); + return syncedCount; + } + + private JsonNode requestIamOrgPage(int pageNumber, int pageSize) { + try { + Map requestBody = new LinkedHashMap<>(); + requestBody.put("size", String.valueOf(pageSize)); + requestBody.put("page", String.valueOf(pageNumber)); + HttpRequest request = HttpRequest.newBuilder(URI.create(iamSyncProperties.getOrgListUrl())) + .timeout(Duration.ofSeconds(20)) + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .header("Auth", normalizeAuthorizationHeader(iamSyncProperties.getProfileAuthorization())) + .header("Authorization", normalizeAuthorizationHeader(iamSyncProperties.getAuthorization())) + .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(requestBody), StandardCharsets.UTF_8)) + .build(); + HttpResponse response = IAM_HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + if (response.statusCode() < 200 || response.statusCode() >= 300) { + throw new ServiceException(StringUtil.format("IAM组织接口调用失败,HTTP状态码:{}", response.statusCode())); + } + JsonNode responseNode = objectMapper.readTree(response.body()); + if (!"0".equals(responseNode.path("code").asText())) { + throw new ServiceException(StringUtil.format("IAM组织接口调用失败:{}", responseNode.path("msg").asText())); + } + JsonNode dataNode = responseNode.path("data"); + if (!dataNode.isObject()) { + throw new ServiceException("IAM组织接口返回数据格式错误"); + } + return dataNode; + } catch (InterruptedException exception) { + Thread.currentThread().interrupt(); + log.error("调用IAM组织接口被中断,page={}", pageNumber, exception); + throw new ServiceException("调用IAM组织接口被中断"); + } catch (IOException | IllegalArgumentException exception) { + log.error("调用IAM组织接口失败,page={}", pageNumber, exception); + throw new ServiceException("调用IAM组织接口失败"); + } + } + + private boolean syncIamOrganization(JsonNode orgNode) { + String orgCode = readIamText(orgNode, "orgCode", "org_code", "organizationCode", "organization_code", + "code", "app_org__org_code", "app_org__org_no", "app_org__organization_code", "app_org__code"); + String orgId = readIamText(orgNode, "orgId", "org_id", "id", "app_org__id", "app_org__org_id"); + if (StringUtil.isBlank(orgCode)) { + orgCode = orgId; + } + if (StringUtil.isBlank(orgCode)) { + log.warn("IAM组织缺少组织编码,跳过同步"); + return false; + } + if (orgCode.length() > 30) { + log.warn("IAM组织编码超过30个字符,跳过同步,orgCode={}", orgCode); + return false; + } + String orgName = readIamText(orgNode, "name", "orgName", "org_name", "organizationName", "organization_name", + "fullName", "app_org__name", "app_org__org_name", "app_org__org_full_name", "app_org__organization_name"); + if (StringUtil.isBlank(orgName)) { + log.warn("IAM组织缺少组织名称,跳过同步,orgCode={}", orgCode); + return false; + } + Integer status = readIamInt(orgNode, "status", "org_status", "app_org__status", "app_org__org_status") == 1 + ? DataStatusEnum.ENABLE.getCode() : DataStatusEnum.DISABLE.getCode(); + Dept dept = getOne(Wrappers.lambdaQuery() + .eq(Dept::getTenantId, IAM_SYNC_TENANT_ID) + .eq(Dept::getDeptCode, orgCode), false); + if (dept == null) { + dept = new Dept(); + dept.setTenantId(IAM_SYNC_TENANT_ID); + dept.setParentId(IAM_SYNC_PARENT_ID); + dept.setAncestors(resolveIamParentAncestors()); + dept.setDeptCode(orgCode); + dept.setDeptName(orgName); + dept.setFullName(orgName); + dept.setShortName(orgName); + dept.setDeptCategory(1); + dept.setSort(0); + dept.setStatus(status); + dept.setIsDeleted(BladeConstant.DB_NOT_DELETED); + dept.setIsOa(1); + dept.setIsPlatformCompany(0); + dept.setSyncTime(new Date()); + boolean saved = save(dept); + if (saved) { + CacheUtil.clear(SYS_CACHE); + CacheUtil.clear(SYS_CACHE, Boolean.FALSE); + } + return saved; + } + boolean changed = !Objects.equals(dept.getDeptName(), orgName) || !Objects.equals(dept.getFullName(), orgName) + || !Objects.equals(dept.getShortName(), orgName) || !Objects.equals(dept.getStatus(), status) + || !Objects.equals(dept.getIsOa(), 1); + if (!changed) { + return true; + } + dept.setDeptName(orgName); + dept.setFullName(orgName); + dept.setShortName(orgName); + dept.setStatus(status); + dept.setIsOa(1); + dept.setSyncTime(new Date()); + CacheUtil.clear(SYS_CACHE); + CacheUtil.clear(SYS_CACHE, Boolean.FALSE); + return updateById(dept); + } + + private String resolveIamParentAncestors() { + Dept parent = getById(IAM_SYNC_PARENT_ID); + String ancestors = parent == null ? String.valueOf(BladeConstant.TOP_PARENT_ID) : parent.getAncestors(); + if (StringUtil.isBlank(ancestors)) { + ancestors = String.valueOf(BladeConstant.TOP_PARENT_ID); + } + return ancestors + StringPool.COMMA + IAM_SYNC_PARENT_ID; + } + + private String readIamText(JsonNode node, String... fieldNames) { + JsonNode valueNode = findIamNode(node, fieldNames); + return valueNode == null || valueNode.isNull() ? StringPool.EMPTY : valueNode.asText().trim(); + } + + private int readIamInt(JsonNode node, String... fieldNames) { + JsonNode valueNode = findIamNode(node, fieldNames); + return valueNode == null || valueNode.isNull() ? 0 : valueNode.asInt(0); + } + + private JsonNode findIamNode(JsonNode node, String... fieldNames) { + for (String fieldName : fieldNames) { + JsonNode valueNode = node.get(fieldName); + if (valueNode != null && !valueNode.isNull()) { + return valueNode; + } + } + return null; + } + + private String normalizeAuthorizationHeader(String value) { + if (StringUtil.isBlank(value)) { + return StringPool.EMPTY; + } + if (StringUtil.startsWithIgnoreCase(value, "Basic ") || StringUtil.startsWithIgnoreCase(value, "Bearer ")) { + return value; + } + return "Basic " + value; + } + private void validateDeptCategory(Dept dept, Dept parent) { if (parent == null) { throw new ServiceException("请选择上级组织"); diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java index c738d73a..3eb7d438 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java @@ -200,9 +200,7 @@ public class UserServiceImpl extends BaseServiceImpl implement .header("Auth", normalizeAuthorizationHeader(iamSyncProperties.getProfileAuthorization())) .header("Authorization", normalizeAuthorizationHeader(iamSyncProperties.getAuthorization())) .POST(HttpRequest.BodyPublishers.ofString(objectMapper.writeValueAsString(requestBody), StandardCharsets.UTF_8)) - .build(); - log.info("Auth,{}", normalizeAuthorizationHeader(iamSyncProperties.getProfileAuthorization())); - log.info("Authorization,{}", normalizeAuthorizationHeader(iamSyncProperties.getAuthorization())); + .build(); HttpResponse response = IAM_HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); if (response.statusCode() < 200 || response.statusCode() >= 300) { @@ -228,19 +226,19 @@ public class UserServiceImpl extends BaseServiceImpl implement } private boolean syncIamAccount(JsonNode accountNode) { - String account = readIamText(accountNode, "accountNo"); + String account = readIamText(accountNode, "accountNo", "account_no", "app_account__account_no"); if (StringUtil.isBlank(account)) { log.warn("IAM账号缺少accountNo,跳过同步"); return false; } - String name = readIamText(accountNode, "name"); + String name = readIamText(accountNode, "name", "app_account__name"); if (StringUtil.isBlank(name)) { - name = readIamText(accountNode, "accountName"); + name = readIamText(accountNode, "accountName", "account_name", "app_account__account_name"); } if (StringUtil.isBlank(name)) { name = account; } - Integer status = accountNode.path("status").asInt(0) == 1 + Integer status = readIamInt(accountNode, "status", "app_account__status") == 1 ? DataStatusEnum.ENABLE.getCode() : DataStatusEnum.DISABLE.getCode(); User user = userByAccount(IAM_SYNC_TENANT_ID, account); if (user == null) { @@ -281,11 +279,26 @@ public class UserServiceImpl extends BaseServiceImpl implement return updateById(user); } - private String readIamText(JsonNode node, String fieldName) { - JsonNode valueNode = node.get(fieldName); + private String readIamText(JsonNode node, String... fieldNames) { + JsonNode valueNode = findIamNode(node, fieldNames); return valueNode == null || valueNode.isNull() ? StringPool.EMPTY : valueNode.asText().trim(); } + private int readIamInt(JsonNode node, String... fieldNames) { + JsonNode valueNode = findIamNode(node, fieldNames); + return valueNode == null || valueNode.isNull() ? 0 : valueNode.asInt(0); + } + + private JsonNode findIamNode(JsonNode node, String... fieldNames) { + for (String fieldName : fieldNames) { + JsonNode valueNode = node.get(fieldName); + if (valueNode != null && !valueNode.isNull()) { + return valueNode; + } + } + return null; + } + private String normalizeAuthorizationHeader(String value) { if (StringUtil.isBlank(value)) { return StringPool.EMPTY; diff --git a/blade-service/blade-system/src/main/resources/application.yml b/blade-service/blade-system/src/main/resources/application.yml index f3492fad..1d8d630c 100644 --- a/blade-service/blade-system/src/main/resources/application.yml +++ b/blade-service/blade-system/src/main/resources/application.yml @@ -28,6 +28,7 @@ spring: iam: sync: account-list-url: ${IAM_SSO_ACCOUNT_LIST_URL:http://172.16.204.83:38000/gwzh/IAM/IAM_IDM_ACCOUNT_LIST} + org-list-url: ${IAM_SSO_ORG_LIST_URL:http://172.16.204.83:38000/gwzh/IAM/IAM_IDM_ORG_LIST} authorization: ${IAM_SSO_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=} profile-authorization: ${IAM_SSO_PROFILE_AUTHORIZATION:YjNlZmU0ODEwMzJiNGJhZTpkYzQ5NDY1NmQ4MzE0NThjODI5MzlmNzA2ZjliNDY3MQ==} page-size: ${IAM_SSO_ACCOUNT_PAGE_SIZE:50} diff --git a/doc/nacos/blade-prod.yaml b/doc/nacos/blade-prod.yaml index 16c1b029..7a749a08 100644 --- a/doc/nacos/blade-prod.yaml +++ b/doc/nacos/blade-prod.yaml @@ -107,3 +107,12 @@ baidu: powerjob: worker: server-address: 172.16.203.228:7700 + +# IAM账号与组织同步配置 +iam: + sync: + account-list-url: http://172.16.204.83:38000/gwzh/IAM/IAM_IDM_ACCOUNT_LIST + org-list-url: http://172.16.204.83:38000/gwzh/IAM/IAM_IDM_ORG_LIST + authorization: ${IAM_SSO_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=} + profile-authorization: ${IAM_SSO_PROFILE_AUTHORIZATION:YjNlZmU0ODEwMzJiNGJhZTpkYzQ5NDY1NmQ4MzE0NThjODI5MzlmNzA2ZjliNDY3MQ==} + page-size: 50 From 8207afe04f72d97c86a1486344c27b259bd21742 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Wed, 16 Sep 2026 08:50:24 +0800 Subject: [PATCH 08/55] =?UTF-8?q?=E8=B0=83=E6=95=B4=20IAM?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/granter/IamSsoTokenGranter.java | 27 ++++++++++++------- .../auth/props/IamSsoProperties.java | 3 ++- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/blade-auth/src/main/java/org/springblade/auth/granter/IamSsoTokenGranter.java b/blade-auth/src/main/java/org/springblade/auth/granter/IamSsoTokenGranter.java index 8763a210..e7c6a272 100644 --- a/blade-auth/src/main/java/org/springblade/auth/granter/IamSsoTokenGranter.java +++ b/blade-auth/src/main/java/org/springblade/auth/granter/IamSsoTokenGranter.java @@ -85,7 +85,6 @@ public class IamSsoTokenGranter extends AuthorizationCodeGranter { private static final String GRANT_TYPE = "iam_sso"; private static final String IAM_GRANT_TYPE = "authorization_code"; private static final String IAM_TOKEN_URI = "/iam/sso/token"; - private static final String BEARER_PREFIX = "Bearer "; private static final String BASIC_PREFIX = "Basic "; private static final String IAM_DEFAULT_DICT_CODE = "iam_default"; private static final String IAM_DEFAULT_ROLE_NAME = "默认角色"; @@ -339,12 +338,16 @@ public class IamSsoTokenGranter extends AuthorizationCodeGranter { private IamSsoProfileResponse requestIamProfile(String accessToken) { try { - HttpRequest httpRequest = HttpRequest.newBuilder(URI.create(buildProfileUrl(accessToken))) + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(URI.create(buildProfileUrl(accessToken))) .timeout(Duration.ofSeconds(10)) .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) - .header(HttpHeaders.AUTHORIZATION, profileAuthorizationHeader(accessToken)) - .GET() - .build(); + // 与 IAM 同步接口一致:Authorization 走网关凭证,Auth 走业务凭证。 + .header(HttpHeaders.AUTHORIZATION, authorizationHeader()); + String profileAuthHeader = profileAuthHeader(); + if (StringUtil.isNotBlank(profileAuthHeader)) { + requestBuilder.header("Auth", profileAuthHeader); + } + HttpRequest httpRequest = requestBuilder.GET().build(); log.info("IAM统一身份认证获取用户信息请求,url={}, method={}", properties.getProfileUrl(), httpRequest.method()); HttpResponse response = httpClient.send( httpRequest, @@ -352,7 +355,7 @@ public class IamSsoTokenGranter extends AuthorizationCodeGranter { ); log.info("IAM统一身份认证获取用户信息响应,status={}", response.statusCode()); if (response.statusCode() < 200 || response.statusCode() >= 300) { - log.warn("IAM统一身份认证获取用户信息失败,status={}", response.statusCode()); + log.warn("IAM统一身份认证获取用户信息失败,status={}, body={}", response.statusCode(), response.body()); throw new UserInvalidException(OAuth2TokenConstant.TOKEN_NOT_CORRECT); } return objectMapper.readValue(response.body(), IamSsoProfileResponse.class); @@ -420,11 +423,15 @@ public class IamSsoTokenGranter extends AuthorizationCodeGranter { return BASIC_PREFIX + authorization; } - private String profileAuthorizationHeader(String accessToken) { - if (StringUtil.isNotBlank(properties.getProfileAuthorization())) { - return withBasicPrefix(properties.getProfileAuthorization()); + /** + * IAM 业务侧 Auth 头。网关 Authorization 使用 {@link #authorizationHeader()}, + * 用户令牌通过 query 参数 access_token 传递。 + */ + private String profileAuthHeader() { + if (StringUtil.isBlank(properties.getProfileAuthorization())) { + return null; } - return BEARER_PREFIX + accessToken; + return withBasicPrefix(properties.getProfileAuthorization()); } private String encode(String value) { diff --git a/blade-auth/src/main/java/org/springblade/auth/props/IamSsoProperties.java b/blade-auth/src/main/java/org/springblade/auth/props/IamSsoProperties.java index ff74ff23..85158597 100644 --- a/blade-auth/src/main/java/org/springblade/auth/props/IamSsoProperties.java +++ b/blade-auth/src/main/java/org/springblade/auth/props/IamSsoProperties.java @@ -78,7 +78,8 @@ public class IamSsoProperties { private String authorization; /** - * IAM用户信息认证头。为空时默认使用 Bearer accessToken + * IAM业务侧 Auth 请求头。为空时不传 Auth; + * Authorization 统一使用网关凭证 {@link #authorization}。 */ private String profileAuthorization; From 0fa0eae43cac42694ae0f0bdfe55746aa51dc0a3 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Wed, 16 Sep 2026 23:42:01 +0800 Subject: [PATCH 09/55] =?UTF-8?q?=E8=B0=83=E6=95=B4=20=E8=BF=90=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/WaybillServiceImpl.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java index 208d4118..14e3c3bf 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java @@ -477,9 +477,11 @@ public class WaybillServiceImpl extends BaseServiceImpl waybill.setLoadingNo(null); waybill.setMasterNo(null); waybill.setMileageRemark(null); - // 新建(含复制后提交)不沿用源单据状态,与纯新增一致 - waybill.setBusinessStatus(null); } + // 新建若已带业务状态(如复制提交)则在过程校正后回写,保证与原运单一致 + String preservedBusinessStatus = created + ? TransportBusinessSupport.trimToNull(waybill.getBusinessStatus()) + : null; // 无过程快照时按项目回填;再按接单设置决定 pending / running fillProjectProcessConfig(waybill); prepare(waybill); @@ -489,6 +491,9 @@ public class WaybillServiceImpl extends BaseServiceImpl clearDriverAcceptRecord(waybill); } applyDriverAcceptBusinessStatus(waybill); + if (Func.isNotEmpty(preservedBusinessStatus)) { + waybill.setBusinessStatus(preservedBusinessStatus); + } fillCustomerName(waybill); if (created && Func.isEmpty(waybill.getWaybillNo())) { waybill.setWaybillNo(nextCode()); @@ -738,13 +743,17 @@ public class WaybillServiceImpl extends BaseServiceImpl target.setFreightJson(source.getFreightJson()); target.setAttachmentsJson(source.getAttachmentsJson()); target.setRemark(source.getRemark()); - // 复制后状态与新增一致:先置空,再按过程配置校正为 pending/running - target.setBusinessStatus(null); + // 复制后业务状态与原运单保持一致 + String sourceBusinessStatus = source.getBusinessStatus(); + target.setBusinessStatus(sourceBusinessStatus); target.setWaybillNo(nextCode()); clearDriverAcceptRecord(target); fillProjectProcessConfig(target); prepare(target); applyDriverAcceptBusinessStatus(target); + if (Func.isNotEmpty(sourceBusinessStatus)) { + target.setBusinessStatus(sourceBusinessStatus); + } validate(target); save(target); return detail(target.getId()); From 01993779a7adb587a732b03da21f7f3a10a2f16a Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Fri, 18 Sep 2026 16:31:37 +0800 Subject: [PATCH 10/55] =?UTF-8?q?1=E3=80=81=E6=96=B0=E5=A2=9E=E5=B0=8F?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3=202?= =?UTF-8?q?=E3=80=81=E8=B0=83=E6=95=B4OA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blade-auth/pom.xml | 4 + .../auth/constant/BladeAuthConstant.java | 6 + .../OAuth2UserInfoResponseAdvice.java | 94 ++ .../auth/endpoint/OAuth2UserInfoVO.java | 55 + .../auth/endpoint/Oauth2SmsEndpoint.java | 15 +- .../auth/granter/SmsTokenGranter.java | 9 +- .../auth/granter/WechatMiniTokenGranter.java | 102 ++ .../service/BladeClientDetailService.java | 15 +- .../gateway/provider/AuthProvider.java | 1 + .../springblade/system/feign/ISysClient.java | 10 + .../system/feign/ISysClientFallback.java | 5 + .../system/pojo/vo/OaPersonSyncPageVO.java | 66 + .../pojo/vo/AdminDriverOptionVO.java | 30 + .../transport/pojo/vo/AdminHomeBadgesVO.java | 47 + .../transport/pojo/vo/AdminHomeStatsVO.java | 53 + .../transport/pojo/vo/AdminHomeVO.java | 55 + .../transport/pojo/vo/AdminTodoItemVO.java | 65 + .../pojo/vo/AdminVehicleOptionVO.java | 24 + .../transport/pojo/vo/AdminWaybillCardVO.java | 95 ++ .../pojo/vo/AdminWaybillDetailVO.java | 154 ++ .../pojo/vo/DriverWaybillCardVO.java | 51 + .../springblade/system/feign/IUserClient.java | 15 + .../system/pojo/dto/PhoneChangeDTO.java | 54 + .../system/pojo/dto/PhoneVerifyDTO.java | 51 + blade-service/blade-system/pom.xml | 4 + .../system/controller/UserController.java | 27 +- .../controller/UserPhoneController.java | 93 ++ .../system/convert/UserConvert.java | 28 +- .../springblade/system/feign/SysClient.java | 6 + .../springblade/system/feign/UserClient.java | 6 + .../system/service/IMenuService.java | 8 + .../system/service/IOASyncService.java | 18 + .../system/service/IUserPhoneService.java | 63 + .../system/service/IUserService.java | 5 + .../system/service/impl/MenuServiceImpl.java | 29 + .../service/impl/OASyncServiceImpl.java | 293 ++-- .../service/impl/OaUserListSyncHelper.java | 609 ++++++++ .../service/impl/UserPhoneServiceImpl.java | 212 +++ .../system/service/impl/UserServiceImpl.java | 40 + .../ExceptionDisposalController.java | 12 +- .../controller/ManageWaybillController.java | 143 ++ .../service/IDriverWaybillService.java | 6 + .../service/IManageWaybillService.java | 90 ++ .../transport/service/IWaybillService.java | 6 + .../impl/DriverWaybillServiceImpl.java | 74 +- .../impl/ManageWaybillServiceImpl.java | 642 ++++++++ .../service/impl/WaybillServiceImpl.java | 12 +- .../oa/config/OAFeignClientConfig.java | 23 +- .../oa/constant/OAConvertConstant.java | 4 + .../thirdparty/oa/feign/IOAClient.java | 2 +- .../blade-wechat-api/pom.xml | 27 + .../ThirdPartyWechatAutoConfiguration.java | 14 + .../wechat/config/WechatMiniProperties.java | 28 + .../wechat/constant/WechatMiniConstant.java | 20 + .../wechat/exception/WechatMiniException.java | 16 + .../wechat/pojo/vo/WechatPhoneVO.java | 25 + .../wechat/pojo/vo/WechatSessionVO.java | 21 + .../wechat/service/IWechatMiniService.java | 21 + .../service/impl/WechatMiniServiceImpl.java | 148 ++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + blade-third-party-api/pom.xml | 1 + doc/nacos/blade-dev.yaml | 6 + doc/nacos/blade-prod.yaml | 5 + doc/nacos/blade.yaml | 3 + doc/nacos/routes/blade-gateway-dev.json | 22 + doc/nacos/third-party-api.yaml | 6 + doc/sql/update/add-mp-auth-permission.sql | 21 + .../update/add-wechat-applet-grant-type.sql | 8 + hs_err_pid22077.log | 1379 +++++++++++++++++ hs_err_pid40988.log | 1368 ++++++++++++++++ pom.xml | 5 + 71 files changed, 6463 insertions(+), 213 deletions(-) create mode 100644 blade-auth/src/main/java/org/springblade/auth/endpoint/OAuth2UserInfoResponseAdvice.java create mode 100644 blade-auth/src/main/java/org/springblade/auth/endpoint/OAuth2UserInfoVO.java create mode 100644 blade-auth/src/main/java/org/springblade/auth/granter/WechatMiniTokenGranter.java create mode 100644 blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/OaPersonSyncPageVO.java create mode 100644 blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminDriverOptionVO.java create mode 100644 blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminHomeBadgesVO.java create mode 100644 blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminHomeStatsVO.java create mode 100644 blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminHomeVO.java create mode 100644 blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminTodoItemVO.java create mode 100644 blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminVehicleOptionVO.java create mode 100644 blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillCardVO.java create mode 100644 blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillDetailVO.java create mode 100644 blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/dto/PhoneChangeDTO.java create mode 100644 blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/dto/PhoneVerifyDTO.java create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/controller/UserPhoneController.java create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/service/IUserPhoneService.java create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OaUserListSyncHelper.java create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserPhoneServiceImpl.java create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ManageWaybillController.java create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/service/IManageWaybillService.java create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ManageWaybillServiceImpl.java create mode 100644 blade-third-party-api/blade-wechat-api/pom.xml create mode 100644 blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/config/ThirdPartyWechatAutoConfiguration.java create mode 100644 blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/config/WechatMiniProperties.java create mode 100644 blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/constant/WechatMiniConstant.java create mode 100644 blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/exception/WechatMiniException.java create mode 100644 blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/pojo/vo/WechatPhoneVO.java create mode 100644 blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/pojo/vo/WechatSessionVO.java create mode 100644 blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/service/IWechatMiniService.java create mode 100644 blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/service/impl/WechatMiniServiceImpl.java create mode 100644 blade-third-party-api/blade-wechat-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 doc/sql/update/add-mp-auth-permission.sql create mode 100644 doc/sql/update/add-wechat-applet-grant-type.sql create mode 100644 hs_err_pid22077.log create mode 100644 hs_err_pid40988.log diff --git a/blade-auth/pom.xml b/blade-auth/pom.xml index 56404632..8bfe4ea1 100644 --- a/blade-auth/pom.xml +++ b/blade-auth/pom.xml @@ -75,6 +75,10 @@ org.springblade blade-mk-api + + org.springblade + blade-wechat-api + org.springblade blade-resource-api diff --git a/blade-auth/src/main/java/org/springblade/auth/constant/BladeAuthConstant.java b/blade-auth/src/main/java/org/springblade/auth/constant/BladeAuthConstant.java index b9c8420b..b8f8ed3a 100644 --- a/blade-auth/src/main/java/org/springblade/auth/constant/BladeAuthConstant.java +++ b/blade-auth/src/main/java/org/springblade/auth/constant/BladeAuthConstant.java @@ -32,4 +32,10 @@ package org.springblade.auth.constant; */ public interface BladeAuthConstant { + /** + * 小程序/登录短信验证码资源编号(对应后台 /resource/sms 的 smsCode) + */ + String LOGIN_SMS_CODE = "ali_reg"; + } + diff --git a/blade-auth/src/main/java/org/springblade/auth/endpoint/OAuth2UserInfoResponseAdvice.java b/blade-auth/src/main/java/org/springblade/auth/endpoint/OAuth2UserInfoResponseAdvice.java new file mode 100644 index 00000000..8e207ad1 --- /dev/null +++ b/blade-auth/src/main/java/org/springblade/auth/endpoint/OAuth2UserInfoResponseAdvice.java @@ -0,0 +1,94 @@ +/** + * 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.auth.endpoint; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.oauth2.endpoint.OAuth2TokenEndPoint; +import org.springblade.core.secure.BladeUser; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.BeanUtil; +import org.springblade.core.tool.utils.Func; +import org.springblade.system.feign.ISysClient; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +import java.util.Collections; +import java.util.List; + +/** + * 增强 /oauth/user-info 响应,补充 permission 权限标识字段 + * + * @author Chill + */ +@Slf4j +@RequiredArgsConstructor +@RestControllerAdvice(assignableTypes = OAuth2TokenEndPoint.class) +public class OAuth2UserInfoResponseAdvice implements ResponseBodyAdvice { + + private final ISysClient sysClient; + + @Override + public boolean supports(MethodParameter returnType, Class> converterType) { + return returnType.getMethod() != null && "userInfo".equals(returnType.getMethod().getName()); + } + + @Override + public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, + Class> selectedConverterType, + ServerHttpRequest request, ServerHttpResponse response) { + if (!(body instanceof BladeUser bladeUser)) { + return body; + } + OAuth2UserInfoVO userInfo = BeanUtil.copyProperties(bladeUser, OAuth2UserInfoVO.class); + if (userInfo == null) { + return body; + } + userInfo.setPermission(loadPermission(bladeUser.getRoleId())); + return userInfo; + } + + private List loadPermission(String roleId) { + if (Func.isBlank(roleId)) { + return Collections.emptyList(); + } + try { + R> result = sysClient.getPermissions(roleId); + if (result != null && result.isSuccess() && result.getData() != null) { + return result.getData(); + } + } catch (Exception exception) { + log.warn("加载用户权限标识失败, roleId={}", roleId, exception); + } + return Collections.emptyList(); + } + +} diff --git a/blade-auth/src/main/java/org/springblade/auth/endpoint/OAuth2UserInfoVO.java b/blade-auth/src/main/java/org/springblade/auth/endpoint/OAuth2UserInfoVO.java new file mode 100644 index 00000000..528a490e --- /dev/null +++ b/blade-auth/src/main/java/org/springblade/auth/endpoint/OAuth2UserInfoVO.java @@ -0,0 +1,55 @@ +/** + * 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.auth.endpoint; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springblade.core.secure.BladeUser; + +import java.io.Serial; +import java.util.List; + +/** + * OAuth用户信息(含权限标识,供小程序等客户端使用) + * + * @author Chill + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Schema(description = "OAuth用户信息") +public class OAuth2UserInfoVO extends BladeUser { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 权限标识集合(菜单按钮 code) + */ + @Schema(description = "权限标识集合") + private List permission; + +} diff --git a/blade-auth/src/main/java/org/springblade/auth/endpoint/Oauth2SmsEndpoint.java b/blade-auth/src/main/java/org/springblade/auth/endpoint/Oauth2SmsEndpoint.java index 878c5261..b48ac804 100644 --- a/blade-auth/src/main/java/org/springblade/auth/endpoint/Oauth2SmsEndpoint.java +++ b/blade-auth/src/main/java/org/springblade/auth/endpoint/Oauth2SmsEndpoint.java @@ -28,13 +28,14 @@ package org.springblade.auth.endpoint; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AllArgsConstructor; import lombok.SneakyThrows; +import org.springblade.auth.constant.BladeAuthConstant; import org.springblade.core.oauth2.props.OAuth2Properties; import org.springblade.core.oauth2.provider.OAuth2Request; import org.springblade.core.oauth2.service.OAuth2User; import org.springblade.core.oauth2.service.OAuth2UserService; import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.Func; import org.springblade.core.tool.utils.SM2Util; -import org.springblade.core.tool.utils.StringPool; import org.springblade.core.tool.utils.StringUtil; import org.springblade.resource.feign.ISmsClient; import org.springblade.resource.utils.SmsUtil; @@ -73,11 +74,14 @@ public class Oauth2SmsEndpoint { * 短信验证码发送 * * @param tenantId 租户ID - * @param phone 手机号 + * @param phone 手机号(SM2 加密) + * @param code 短信资源编号,默认 ali_reg(后台 /resource/sms) */ @SneakyThrows @PostMapping("/oauth/sms/send-validate") - public R sendValidate(@RequestParam String tenantId, @RequestParam String phone) { + public R sendValidate(@RequestParam String tenantId, + @RequestParam String phone, + @RequestParam(required = false) String code) { // 校验手机加密认证,防止恶意发送验证码 String decryptedPhone = SM2Util.decrypt(phone, properties.getPublicKey(), properties.getPrivateKey()); if (StringUtil.isBlank(decryptedPhone)) { @@ -90,8 +94,9 @@ public class Oauth2SmsEndpoint { if (oAuth2User == null) { return R.fail(USER_PHONE_NOT_FOUND); } - // 用户存在则发送验证码 - R result = smsClient.sendValidate(tenantId, StringPool.EMPTY, decryptedPhone); + // 使用指定短信资源(默认 ali_reg) + String smsResourceCode = Func.toStr(code, BladeAuthConstant.LOGIN_SMS_CODE); + R result = smsClient.sendValidate(tenantId, smsResourceCode, decryptedPhone); return result.isSuccess() ? R.data(result.getData(), SmsUtil.SEND_SUCCESS) : R.fail(SmsUtil.SEND_FAIL); } diff --git a/blade-auth/src/main/java/org/springblade/auth/granter/SmsTokenGranter.java b/blade-auth/src/main/java/org/springblade/auth/granter/SmsTokenGranter.java index 0c37eee5..540095c0 100644 --- a/blade-auth/src/main/java/org/springblade/auth/granter/SmsTokenGranter.java +++ b/blade-auth/src/main/java/org/springblade/auth/granter/SmsTokenGranter.java @@ -26,6 +26,7 @@ package org.springblade.auth.granter; import jakarta.servlet.http.HttpServletRequest; +import org.springblade.auth.constant.BladeAuthConstant; import org.springblade.core.oauth2.constant.OAuth2TokenConstant; import org.springblade.core.oauth2.exception.UserInvalidException; import org.springblade.core.oauth2.granter.AbstractTokenGranter; @@ -37,8 +38,8 @@ import org.springblade.core.oauth2.service.OAuth2User; import org.springblade.core.oauth2.service.OAuth2UserService; import org.springblade.core.sms.model.SmsCode; import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.Func; import org.springblade.core.tool.utils.SM2Util; -import org.springblade.core.tool.utils.StringPool; import org.springblade.core.tool.utils.StringUtil; import org.springblade.core.tool.utils.WebUtil; import org.springblade.resource.feign.ISmsClient; @@ -78,8 +79,10 @@ public class SmsTokenGranter extends AbstractTokenGranter { if (StringUtil.isBlank(decryptedPhone)) { throw new UserInvalidException(OAuth2TokenConstant.USER_PHONE_NOT_FOUND); } - // 获取短信验证信息 - R result = smsClient.validateMessage(tenantId, StringPool.EMPTY, smsCode.getId(), smsCode.getValue(), decryptedPhone); + // 与发送时使用同一短信资源编号(默认 ali_reg) + HttpServletRequest httpRequest = WebUtil.getRequest(); + String smsResourceCode = Func.toStr(httpRequest.getParameter("code"), BladeAuthConstant.LOGIN_SMS_CODE); + R result = smsClient.validateMessage(tenantId, smsResourceCode, smsCode.getId(), smsCode.getValue(), decryptedPhone); if (!result.isSuccess()) { throw new UserInvalidException(OAuth2TokenConstant.CAPTCHA_NOT_CORRECT); } diff --git a/blade-auth/src/main/java/org/springblade/auth/granter/WechatMiniTokenGranter.java b/blade-auth/src/main/java/org/springblade/auth/granter/WechatMiniTokenGranter.java new file mode 100644 index 00000000..1bdd67bf --- /dev/null +++ b/blade-auth/src/main/java/org/springblade/auth/granter/WechatMiniTokenGranter.java @@ -0,0 +1,102 @@ +package org.springblade.auth.granter; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.oauth2.exception.UserInvalidException; +import org.springblade.core.oauth2.granter.AbstractTokenGranter; +import org.springblade.core.oauth2.handler.PasswordHandler; +import org.springblade.core.oauth2.provider.OAuth2Request; +import org.springblade.core.oauth2.service.OAuth2ClientService; +import org.springblade.core.oauth2.service.OAuth2User; +import org.springblade.core.oauth2.service.OAuth2UserService; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.Func; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.core.tool.utils.WebUtil; +import org.springblade.system.feign.IUserClient; +import org.springblade.thirdparty.wechat.constant.WechatMiniConstant; +import org.springblade.thirdparty.wechat.exception.WechatMiniException; +import org.springblade.thirdparty.wechat.pojo.vo.WechatPhoneVO; +import org.springblade.thirdparty.wechat.pojo.vo.WechatSessionVO; +import org.springblade.thirdparty.wechat.service.IWechatMiniService; +import org.springframework.stereotype.Component; + +/** + * 微信小程序手机号一键登录。 + *

+ * grant_type=wechat_applet,参数:loginCode(wx.login)、phoneCode(getPhoneNumber)。 + * 流程:换 openid + 手机号 → 按手机号查用户(不存在则拒绝)→ 记录 openid → 发令牌(与密码登录一致)。 + */ +@Slf4j +@Component +public class WechatMiniTokenGranter extends AbstractTokenGranter { + + private final OAuth2UserService userService; + private final IWechatMiniService wechatMiniService; + private final IUserClient userClient; + + public WechatMiniTokenGranter(OAuth2ClientService clientService, + OAuth2UserService userService, + PasswordHandler passwordHandler, + IWechatMiniService wechatMiniService, + IUserClient userClient) { + super(clientService, userService, passwordHandler); + this.userService = userService; + this.wechatMiniService = wechatMiniService; + this.userClient = userClient; + } + + @Override + public String type() { + return WechatMiniConstant.GRANT_TYPE; + } + + @Override + public OAuth2User user(OAuth2Request request) { + // 先校验客户端是否允许该授权类型,避免先调微信再因客户端配置失败 + var oauthClient = client(request); + + HttpServletRequest httpRequest = WebUtil.getRequest(); + String loginCode = httpRequest.getParameter("loginCode"); + String phoneCode = httpRequest.getParameter("phoneCode"); + if (StringUtil.isBlank(loginCode) || StringUtil.isBlank(phoneCode)) { + throw new UserInvalidException("微信登录参数不完整"); + } + + WechatSessionVO session; + WechatPhoneVO phoneInfo; + try { + session = wechatMiniService.code2Session(loginCode); + phoneInfo = wechatMiniService.getPhoneNumber(phoneCode); + } catch (WechatMiniException e) { + log.warn("微信小程序登录失败: {}", e.getMessage()); + throw new UserInvalidException(e.getMessage()); + } + + String phone = Func.toStr(phoneInfo.getPurePhoneNumber(), phoneInfo.getPhoneNumber()); + if (StringUtil.isBlank(phone)) { + throw new UserInvalidException("未获取到微信手机号"); + } + + OAuth2User user = userService.loadByPhone(phone, request); + if (!userService.validateUser(user)) { + throw new UserInvalidException("用户不存在,无法登录"); + } + + R bindResult = userClient.bindWxMiniOpenId( + request.getTenantId(), + Func.toLong(user.getUserId()), + session.getOpenid(), + phone + ); + if (bindResult == null || !bindResult.isSuccess()) { + String msg = bindResult != null ? bindResult.getMsg() : "绑定 openid 失败"; + log.warn("绑定微信 openid 失败 userId={} openid={} msg={}", user.getUserId(), session.getOpenid(), msg); + throw new UserInvalidException(StringUtil.isBlank(msg) ? "绑定 openid 失败" : msg); + } + + user.setClient(oauthClient); + return user; + } + +} diff --git a/blade-auth/src/main/java/org/springblade/auth/service/BladeClientDetailService.java b/blade-auth/src/main/java/org/springblade/auth/service/BladeClientDetailService.java index ee43e92f..db727e84 100644 --- a/blade-auth/src/main/java/org/springblade/auth/service/BladeClientDetailService.java +++ b/blade-auth/src/main/java/org/springblade/auth/service/BladeClientDetailService.java @@ -25,11 +25,17 @@ */ package org.springblade.auth.service; +import org.springblade.core.oauth2.constant.OAuth2GranterConstant; import org.springblade.core.oauth2.provider.OAuth2Request; import org.springblade.core.oauth2.service.OAuth2Client; import org.springblade.core.oauth2.service.impl.OAuth2ClientDetailService; +import org.springblade.core.tool.utils.Func; +import org.springblade.core.tool.utils.StringPool; import org.springframework.jdbc.core.JdbcTemplate; +import java.util.Arrays; +import java.util.Optional; + /** * BladeClientDetailService * @@ -57,6 +63,13 @@ public class BladeClientDetailService extends OAuth2ClientDetailService { @Override public boolean validateGranter(OAuth2Client client, String grantType) { - return super.validateGranter(client, grantType); + // 微信小程序一键登录:兼容库表未配置 wechat_applet 的存量客户端 + if (OAuth2GranterConstant.WECHAT_APPLET.equals(grantType) || "wechat_mini".equals(grantType)) { + return true; + } + return Optional.ofNullable(client) + .map(c -> Arrays.stream(Func.split(c.getAuthorizedGrantTypes(), StringPool.COMMA)) + .anyMatch(s -> s.trim().equals(grantType))) + .orElse(false); } } diff --git a/blade-gateway/src/main/java/org/springblade/gateway/provider/AuthProvider.java b/blade-gateway/src/main/java/org/springblade/gateway/provider/AuthProvider.java index 86787cf5..a259f7ba 100644 --- a/blade-gateway/src/main/java/org/springblade/gateway/provider/AuthProvider.java +++ b/blade-gateway/src/main/java/org/springblade/gateway/provider/AuthProvider.java @@ -48,6 +48,7 @@ public class AuthProvider { DEFAULT_SKIP_URL.add("/oauth/sms/**"); DEFAULT_SKIP_URL.add("/oauth/clear-cache/**"); DEFAULT_SKIP_URL.add("/oauth/user-info"); + DEFAULT_SKIP_URL.add("/oauth/logout/**"); DEFAULT_SKIP_URL.add("/oauth/render/**"); DEFAULT_SKIP_URL.add("/oauth/callback/**"); DEFAULT_SKIP_URL.add("/oauth/revoke/**"); diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/ISysClient.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/ISysClient.java index 37e33b1c..77eeef8d 100644 --- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/ISysClient.java +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/ISysClient.java @@ -73,6 +73,7 @@ public interface ISysClient { String REGION = API_PREFIX + "/region"; String FEE_ITEMS = API_PREFIX + "/fee-items"; String CARGO_TYPES = API_PREFIX + "/cargo-types"; + String PERMISSIONS = API_PREFIX + "/permissions"; /** * 获取菜单 @@ -316,4 +317,13 @@ public interface ISysClient { @GetMapping(CARGO_TYPES) R> getCargoTypes(); + /** + * 获取角色权限标识集合(按钮编号) + * + * @param roleId 角色id + * @return 权限标识 + */ + @GetMapping(PERMISSIONS) + R> getPermissions(@RequestParam("roleId") String roleId); + } diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/ISysClientFallback.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/ISysClientFallback.java index c7598c34..4825a4da 100644 --- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/ISysClientFallback.java +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/feign/ISysClientFallback.java @@ -174,4 +174,9 @@ public class ISysClientFallback implements ISysClient { return R.fail("获取数据失败"); } + @Override + public R> getPermissions(String roleId) { + return R.fail("获取数据失败"); + } + } diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/OaPersonSyncPageVO.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/OaPersonSyncPageVO.java new file mode 100644 index 00000000..b493bdf3 --- /dev/null +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/vo/OaPersonSyncPageVO.java @@ -0,0 +1,66 @@ +/** + * 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 OaPersonSyncPageVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @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-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminDriverOptionVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminDriverOptionVO.java new file mode 100644 index 00000000..21839e5e --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminDriverOptionVO.java @@ -0,0 +1,30 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + */ +package org.springblade.transport.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +@Data +@Schema(description = "调度端司机搜索项") +public class AdminDriverOptionVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "司机ID") + private Long id; + + @Schema(description = "姓名") + private String name; + + @Schema(description = "手机号") + private String phone; + + @Schema(description = "绑定车牌") + private String vehicleNo; +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminHomeBadgesVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminHomeBadgesVO.java new file mode 100644 index 00000000..d37bd5bd --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminHomeBadgesVO.java @@ -0,0 +1,47 @@ +/** + * 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. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 调度端首页快捷入口角标 + */ +@Data +@Schema(description = "调度端首页角标") +public class AdminHomeBadgesVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "异常处置待办数(disposalStatus≠completed)") + private long exception; + + @Schema(description = "风险记录待办数(disposalStatus=pending)") + private long risk; + +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminHomeStatsVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminHomeStatsVO.java new file mode 100644 index 00000000..b3b97705 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminHomeStatsVO.java @@ -0,0 +1,53 @@ +/** + * 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. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 调度端首页顶部统计(对齐小程序 admin/home) + */ +@Data +@Schema(description = "调度端首页运单状态统计") +public class AdminHomeStatsVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "运输中(businessStatus=running)") + private long transporting; + + @Schema(description = "待接单(businessStatus=pending)") + private long pendingAccept; + + @Schema(description = "在途异常(异常处置状态≠已完成)") + private long exception; + + @Schema(description = "已完成(businessStatus=completed)") + private long completed; + +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminHomeVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminHomeVO.java new file mode 100644 index 00000000..2302825d --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminHomeVO.java @@ -0,0 +1,55 @@ +/** + * 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. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * 调度端首页聚合数据(统计 + 角标 + 待处理 feed + 用户名) + */ +@Data +@Schema(description = "调度端首页聚合") +public class AdminHomeVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "当前登录用户姓名") + private String userName; + + @Schema(description = "顶部运单状态统计") + private AdminHomeStatsVO stats = new AdminHomeStatsVO(); + + @Schema(description = "快捷入口角标") + private AdminHomeBadgesVO badges = new AdminHomeBadgesVO(); + + @Schema(description = "待处理事项(异常处置状态≠已完成)") + private List feed = new ArrayList<>(); + +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminTodoItemVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminTodoItemVO.java new file mode 100644 index 00000000..8d8430fb --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminTodoItemVO.java @@ -0,0 +1,65 @@ +/** + * 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. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 调度端首页「待处理事项」条目 + */ +@Data +@Schema(description = "调度端首页待处理事项") +public class AdminTodoItemVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "条目ID(异常处置ID)") + private Long id; + + @Schema(description = "类型:exception=异常待处置 / reassign=重新派单待处理") + private String type; + + @Schema(description = "标题") + private String title; + + @Schema(description = "相对时间文案,如「2分钟」") + private String timeAgo; + + @Schema(description = "摘要描述") + private String desc; + + @Schema(description = "运单号") + private String waybillNo; + + @Schema(description = "操作按钮文案") + private String actionLabel; + + @Schema(description = "跳转路径(小程序内路径)") + private String targetUrl; + +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminVehicleOptionVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminVehicleOptionVO.java new file mode 100644 index 00000000..53bcaa4f --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminVehicleOptionVO.java @@ -0,0 +1,24 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + */ +package org.springblade.transport.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +@Data +@Schema(description = "调度端车牌搜索项") +public class AdminVehicleOptionVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "车牌号") + private String vehicleNo; + + @Schema(description = "关联司机名(可选)") + private String driverName; +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillCardVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillCardVO.java new file mode 100644 index 00000000..9e63df31 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillCardVO.java @@ -0,0 +1,95 @@ +/** + * 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. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 调度端运单列表卡片(对齐小程序 admin/waybill-list) + */ +@Data +@Schema(description = "调度端运单列表卡片") +public class AdminWaybillCardVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "运单ID") + private Long id; + + @Schema(description = "运单号") + private String waybillNo; + + @Schema(description = "起点") + private String fromName; + + @Schema(description = "终点") + private String toName; + + @Schema(description = "货物名称") + private String cargo; + + @Schema(description = "重量(带单位)") + private String weight; + + @Schema(description = "计划开始时间") + private String planTime; + + @Schema(description = "计划结束时间") + private String planTimeEnd; + + @Schema(description = "状态:0待接单/1运输中/2已完成/3已取消") + private Integer status; + + @Schema(description = "承运方") + private String carrierName; + + @Schema(description = "司机姓名") + private String driverName; + + @Schema(description = "车牌号") + private String vehicleNo; + + @Schema(description = "是否有未完成异常") + private Boolean hasException; + + @Schema(description = "运输组织类型:common普通 / load配载") + private String transportType; + + @Schema(description = "运输方式:road / 公路运输 / 铁路运输 等") + private String transportMode; + + @Schema(description = "创建时间") + private String createTime; + + @Schema(description = "需重新派单(司机已拒单)") + private Boolean needReassign; + + @Schema(description = "采购方是否已付款(预留)") + private Boolean buyerPaid; + +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillDetailVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillDetailVO.java new file mode 100644 index 00000000..ebbd9cce --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/AdminWaybillDetailVO.java @@ -0,0 +1,154 @@ +/** + * 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. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.List; + +/** + * 调度端运单详情(对齐小程序 pages/waybill/detail) + */ +@Data +@Schema(description = "调度端运单详情") +public class AdminWaybillDetailVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "运单ID") + private Long id; + + @Schema(description = "运单号") + private String waybillNo; + + @Schema(description = "状态:0待接单/1运输中/2已完成/3已取消") + private Integer status; + + @Schema(description = "起点名称") + private String fromName; + + @Schema(description = "终点名称") + private String toName; + + @Schema(description = "起点地址") + private String fromAddress; + + @Schema(description = "终点地址") + private String toAddress; + + @Schema(description = "装货地址") + private String pickupAddress; + + @Schema(description = "卸货地址") + private String unloadAddress; + + @Schema(description = "货物名称") + private String cargoName; + + @Schema(description = "货物数量(带单位)") + private String cargoQuantity; + + @Schema(description = "重量(带单位)") + private String weight; + + @Schema(description = "合计货重") + private String totalWeight; + + @Schema(description = "运输方式文案:公路运输 / 铁路运输 等") + private String transportType; + + @Schema(description = "运输方式字典值:road 等") + private String transportMode; + + @Schema(description = "运输组织:common普通 / load配载") + private String transportOrgType; + + @Schema(description = "计划发货时间") + private String planShipTime; + + @Schema(description = "计划完成时间") + private String planFinishTime; + + @Schema(description = "承运方") + private String carrierName; + + @Schema(description = "司机姓名") + private String driverName; + + @Schema(description = "司机联系方式") + private String driverPhone; + + @Schema(description = "车牌号") + private String vehicleNo; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "是否有未完成异常") + private Boolean hasException; + + @Schema(description = "最近一条未完成异常处置ID(有异常时返回)") + private Long exceptionId; + + @Schema(description = "需重新派单") + private Boolean needReassign; + + @Schema(description = "司机接单状态:pending/accepted/rejected") + private String acceptStatus; + + @Schema(description = "司机拒绝接单原因") + private String rejectReason; + + @Schema(description = "原指派司机ID") + private Long driverId; + + @Schema(description = "装卸点列表") + private List routePoints; + + @Schema(description = "过程打卡节点(与司机端 punchNodes 同结构)") + private List punchNodes; + + @Schema(description = "途打卡记录") + private List enrouteRecords; + + @Data + @Schema(description = "装卸点") + public static class AdminRoutePointVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "点位名称") + private String name; + + @Schema(description = "详细地址") + private String address; + + @Schema(description = "状态:pending/done/active") + private String status; + } + +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverWaybillCardVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverWaybillCardVO.java index 162b3860..57e394d1 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverWaybillCardVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/DriverWaybillCardVO.java @@ -133,4 +133,55 @@ public class DriverWaybillCardVO implements Serializable { @Schema(description = "过程配置中 punch=是 的打卡节点列表(详情返回)") private List punchNodes; + /* ===== pages/waybill/detail 接单查看字段 ===== */ + + @Schema(description = "货物名称") + private String cargoName; + + @Schema(description = "装货地址") + private String pickupAddress; + + @Schema(description = "卸货地址") + private String unloadAddress; + + @Schema(description = "货物数量(带单位)") + private String cargoQuantity; + + @Schema(description = "运输方式文案:公路运输等") + private String transportType; + + @Schema(description = "计划发货时间") + private String planShipTime; + + @Schema(description = "计划完成时间") + private String planFinishTime; + + @Schema(description = "合计货重") + private String totalWeight; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "装卸点(详情返回)") + private List routePoints; + + @Schema(description = "过程配置 JSON(详情返回,供前端兜底推导打卡节点)") + private String processJson; + + @Data + @Schema(description = "司机端装卸点") + public static class DriverRoutePointVO implements Serializable { + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "点位名称") + private String name; + + @Schema(description = "详细地址") + private String address; + + @Schema(description = "状态:pending/done/active") + private String status; + } + } diff --git a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/feign/IUserClient.java b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/feign/IUserClient.java index cc78a955..50be69e0 100644 --- a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/feign/IUserClient.java +++ b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/feign/IUserClient.java @@ -61,6 +61,7 @@ public interface IUserClient { String SAVE_IAM_USER = API_PREFIX + "/save-iam-user"; String REGISTER_USER = API_PREFIX + "/register-user"; String REMOVE_USER = API_PREFIX + "/remove-user"; + String BIND_WX_MINI_OPENID = API_PREFIX + "/bind-wx-mini-openid"; /** * 获取用户信息 @@ -187,4 +188,18 @@ public interface IUserClient { @PostMapping(REMOVE_USER) R removeUser(@RequestParam("tenantIds") String tenantIds); + /** + * 绑定微信小程序 openid(写入 blade_user_oauth,source=WECHAT_MINI) + * + * @param tenantId 租户ID + * @param userId 用户ID + * @param openid 微信 openid + * @param phone 手机号(可选,写入 username) + */ + @PostMapping(BIND_WX_MINI_OPENID) + R bindWxMiniOpenId(@RequestParam("tenantId") String tenantId, + @RequestParam("userId") Long userId, + @RequestParam("openid") String openid, + @RequestParam(value = "phone", required = false) String phone); + } diff --git a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/dto/PhoneChangeDTO.java b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/dto/PhoneChangeDTO.java new file mode 100644 index 00000000..17433768 --- /dev/null +++ b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/dto/PhoneChangeDTO.java @@ -0,0 +1,54 @@ +/** + * 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.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 更换手机号参数 + * + * @author Chill + */ +@Data +@Schema(description = "更换手机号参数") +public class PhoneChangeDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "新手机号", requiredMode = Schema.RequiredMode.REQUIRED) + private String newPhone; + + @Schema(description = "短信校验 ID(发送验证码接口返回)", requiredMode = Schema.RequiredMode.REQUIRED) + private String id; + + @Schema(description = "新手机号短信验证码", requiredMode = Schema.RequiredMode.REQUIRED) + private String code; +} diff --git a/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/dto/PhoneVerifyDTO.java b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/dto/PhoneVerifyDTO.java new file mode 100644 index 00000000..79520b8f --- /dev/null +++ b/blade-service-api/blade-user-api/src/main/java/org/springblade/system/pojo/dto/PhoneVerifyDTO.java @@ -0,0 +1,51 @@ +/** + * 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.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 原手机号短信校验参数 + * + * @author Chill + */ +@Data +@Schema(description = "原手机号短信校验参数") +public class PhoneVerifyDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "短信校验 ID(发送验证码接口返回)", requiredMode = Schema.RequiredMode.REQUIRED) + private String id; + + @Schema(description = "短信验证码", requiredMode = Schema.RequiredMode.REQUIRED) + private String code; +} diff --git a/blade-service/blade-system/pom.xml b/blade-service/blade-system/pom.xml index 08aea8b1..35b4f527 100644 --- a/blade-service/blade-system/pom.xml +++ b/blade-service/blade-system/pom.xml @@ -45,6 +45,10 @@ org.springblade blade-user-api + + org.springblade + blade-resource-api + org.springblade blade-process-api diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserController.java index a4b7b6b3..36c7f7d0 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserController.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserController.java @@ -54,7 +54,9 @@ import org.springblade.core.tool.utils.StringPool; import org.springblade.system.excel.UserExcel; import org.springblade.system.excel.UserImporter; import org.springblade.system.pojo.entity.User; +import org.springblade.system.pojo.vo.OaPersonSyncPageVO; import org.springblade.system.pojo.vo.UserVO; +import org.springblade.system.service.IOASyncService; import org.springblade.system.service.IUserService; import org.springblade.system.wrapper.UserWrapper; import org.springframework.web.bind.annotation.*; @@ -77,6 +79,7 @@ import java.util.Map; public class UserController { private final IUserService userService; + private final IOASyncService oaSyncService; /** * 查询单条 @@ -158,14 +161,16 @@ public class UserController { } /** - * 同步IAM账号。 + * 从OA按页同步人员,并按公司/部门生成组织后绑定到三级部门。 */ @IsAdmin @PostMapping("/sync-iam-accounts") @ApiOperationSupport(order = 6) - @Operation(summary = "同步IAM账号") - public R syncIamAccounts() { - return R.data(userService.syncIamAccounts()); + @Operation(summary = "同步OA人员") + public R syncIamAccounts( + @RequestParam(defaultValue = "1") Integer current, + @RequestParam(defaultValue = "50") Integer size) { + return R.data(oaSyncService.syncPersonFromUserList(current, size)); } /** @@ -227,6 +232,20 @@ public class UserController { return R.status(temp); } + /** + * 当前用户设置/重置登录密码(小程序首次设密、短信验证后改密) + *

+ * 对外路径:/blade-system/user/password ;网关别名 /blade-user/password 亦可到达。 + */ + @PostMapping("/password") + @ApiOperationSupport(order = 10) + @Operation(summary = "设置登录密码", description = "当前登录用户设置密码,无需原密码") + public R password(BladeUser user, + @Parameter(description = "新密码", required = true) @RequestParam String password, + @Parameter(description = "确认密码", required = true) @RequestParam String password2) { + return R.status(userService.setPassword(user.getUserId(), password, password2)); + } + /** * 管理员修改密码 */ diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserPhoneController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserPhoneController.java new file mode 100644 index 00000000..ecf7b715 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserPhoneController.java @@ -0,0 +1,93 @@ +/** + * 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.controller; + +import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import org.springblade.core.tenant.annotation.NonDS; +import org.springblade.core.tool.api.R; +import org.springblade.system.pojo.dto.PhoneChangeDTO; +import org.springblade.system.pojo.dto.PhoneVerifyDTO; +import org.springblade.system.service.IUserPhoneService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * 用户手机号变更(小程序「修改手机号」) + *

+ * 对外路径:/blade-system/user/phone/** ;网关别名 /blade-user/phone/** 亦可到达。 + * + * @author Chill + */ +@NonDS +@RestController +@AllArgsConstructor +@RequestMapping("/user/phone") +@Tag(name = "用户手机号", description = "修改手机号") +public class UserPhoneController { + + private final IUserPhoneService userPhoneService; + + /** + * 发送短信验证码(需登录) + *

+ * 当前手机号、未占用的新手机号均可发送;新号若已被其他账号占用则拒绝。 + */ + @PostMapping("/send-code") + @ApiOperationSupport(order = 1) + @Operation(summary = "发送手机号变更验证码", description = "传入明文手机号,返回短信校验 id") + public R sendCode(@Parameter(description = "手机号", required = true) @RequestParam String phone) { + return userPhoneService.sendCode(phone); + } + + /** + * 校验原手机号验证码(修改手机号第 1 步) + */ + @PostMapping("/verify-old") + @ApiOperationSupport(order = 2) + @Operation(summary = "校验原手机号验证码", description = "传入发送验证码返回的 id 与验证码") + public R verifyOld(@Valid @RequestBody PhoneVerifyDTO phoneVerify) { + return R.status(userPhoneService.verifyOldPhone(phoneVerify)); + } + + /** + * 绑定新手机号(修改手机号第 3 步,需先完成 verify-old) + */ + @PostMapping("/change") + @ApiOperationSupport(order = 3) + @Operation(summary = "更换手机号", description = "传入新手机号及短信校验 id、验证码") + public R change(@Valid @RequestBody PhoneChangeDTO phoneChange) { + return R.status(userPhoneService.changePhone(phoneChange)); + } + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java b/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java index 924daee0..28f7004b 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/convert/UserConvert.java @@ -64,11 +64,11 @@ public interface UserConvert { @Mapping(target = "password", ignore = true) @Mapping(target = "birthday", ignore = true) @Mapping(target = "sex", ignore = true) + @Mapping(target = "account", ignore = true) @Mapping(source = "workcode", target = "code") @Mapping(source = "lastname", target = "name") @Mapping(source = "lastname", target = "realName") @Mapping(source = "mobile", target = "phone") - @Mapping(source = "mobile", target = "account") @Mapping(source = "email", target = "email") User baseConvert(OAPersonResponse person); @@ -86,7 +86,7 @@ public interface UserConvert { */ default User person2user(OAPersonResponse person, String defaultPassword) { User user = baseConvert(person); - + user.setAccount(resolveAccount(person)); // 密码 user.setPassword(defaultPassword); // 性别 @@ -102,6 +102,28 @@ public interface UserConvert { return user; } + /** + * 解析本系统登录账号:优先 OA loginid,其次工号,最后手机号 + * + * @param person OA人员 + * @return 账号,无法识别时返回 null + */ + default String resolveAccount(OAPersonResponse person) { + if (person == null) { + return null; + } + if (StringUtils.isNotBlank(person.getLoginid())) { + return person.getLoginid().trim(); + } + if (StringUtils.isNotBlank(person.getWorkcode())) { + return person.getWorkcode().trim(); + } + if (StringUtils.isNotBlank(person.getMobile())) { + return person.getMobile().trim(); + } + return null; + } + /** * oa人员转本系统用户部门 * @param person @@ -120,7 +142,7 @@ public interface UserConvert { // 排序 userDept.setSort(OAUtils.parseInt(person.getDsporder())); // 用户id - userDept.setUserId(userMap.get(person.getMobile())); + userDept.setUserId(userMap.get(resolveAccount(person))); userDept.setSyncTime(new Date()); return userDept; } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/feign/SysClient.java b/blade-service/blade-system/src/main/java/org/springblade/system/feign/SysClient.java index 49c86132..d08d36a6 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/feign/SysClient.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/feign/SysClient.java @@ -231,4 +231,10 @@ public class SysClient implements ISysClient { .orderByAsc(CargoType::getCargoCode))); } + @Override + @GetMapping(PERMISSIONS) + public R> getPermissions(String roleId) { + return R.data(menuService.permissionCodes(roleId)); + } + } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/feign/UserClient.java b/blade-service/blade-system/src/main/java/org/springblade/system/feign/UserClient.java index 12b97fd0..f25da255 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/feign/UserClient.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/feign/UserClient.java @@ -133,4 +133,10 @@ public class UserClient implements IUserClient { return R.data(service.remove(Wrappers.query().lambda().in(User::getTenantId, Func.toStrList(tenantIds)))); } + @Override + @PostMapping(BIND_WX_MINI_OPENID) + public R bindWxMiniOpenId(String tenantId, Long userId, String openid, String phone) { + return R.data(service.bindWxMiniOpenId(tenantId, userId, openid, phone)); + } + } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IMenuService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IMenuService.java index 6b18066c..2d7c77f3 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IMenuService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IMenuService.java @@ -77,6 +77,14 @@ public interface IMenuService extends IService

{ */ List buttons(String roleId); + /** + * 权限标识集合(按钮编号,与前端 GetButtons 叶子 code 一致) + * + * @param roleId 角色id + * @return 权限标识 + */ + List permissionCodes(String roleId); + /** * 树形结构 * 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 1c7c1d9a..45b74377 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,7 @@ package org.springblade.system.service; +import org.springblade.system.pojo.vo.OaPersonSyncPageVO; + /** * oa同步接口 * @author bfhuange @@ -18,4 +20,20 @@ public interface IOASyncService { * @param syncAll 是否同步所有 */ void syncPersonAndPushMK(boolean syncAll); + + /** + * 从 OA 人员接口全量同步组织与人员,不推送 MK + * + * @return 处理的人员数量 + */ + int syncPersonFromUserList(); + + /** + * 按页从 OA 人员接口同步组织与人员 + * + * @param current 当前页,从 1 开始 + * @param size 每页条数 + * @return 本页同步结果 + */ + OaPersonSyncPageVO syncPersonFromUserList(int current, int size); } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserPhoneService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserPhoneService.java new file mode 100644 index 00000000..75653ac3 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserPhoneService.java @@ -0,0 +1,63 @@ +/** + * 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.service; + +import org.springblade.core.tool.api.R; +import org.springblade.system.pojo.dto.PhoneChangeDTO; +import org.springblade.system.pojo.dto.PhoneVerifyDTO; + +/** + * 用户手机号变更服务 + * + * @author Chill + */ +public interface IUserPhoneService { + + /** + * 发送变更手机号短信验证码 + * + * @param phone 明文手机号 + * @return 含短信校验 id 的响应 + */ + R sendCode(String phone); + + /** + * 校验原手机号验证码,通过后写入短期凭证 + * + * @param phoneVerify 校验参数 + * @return 是否通过 + */ + boolean verifyOldPhone(PhoneVerifyDTO phoneVerify); + + /** + * 校验新手机号验证码并更换手机号 + * + * @param phoneChange 更换参数 + * @return 是否成功 + */ + boolean changePhone(PhoneChangeDTO phoneChange); + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java index 366db182..7c3db61f 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/IUserService.java @@ -188,6 +188,11 @@ public interface IUserService extends BaseService { */ UserInfo userInfo(UserOauth userOauth); + /** + * 绑定微信小程序 openid 到已有用户(blade_user_oauth,source=WECHAT_MINI) + */ + boolean bindWxMiniOpenId(String tenantId, Long userId, String openid, String phone); + /** * 根据租户与账号获取用户 * diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MenuServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MenuServiceImpl.java index e04feb2e..7b0ced47 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MenuServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MenuServiceImpl.java @@ -164,6 +164,35 @@ public class MenuServiceImpl extends ServiceImpl implements IM return menuWrapper.listNodeVO(buttons); } + @Override + public List permissionCodes(String roleId) { + List permissionCodes = new ArrayList<>(); + // Feign 调用时无登录态,不能走 AuthUtil.isAdministrator() 分支;按 roleId 取按钮权限 + List

buttons = StringUtil.isBlank(roleId) + ? Collections.emptyList() + : baseMapper.buttons(Func.toLongList(roleId)); + MenuWrapper menuWrapper = new MenuWrapper(); + collectLeafPermissionCodes(menuWrapper.listNodeVO(buttons), permissionCodes); + return permissionCodes; + } + + /** + * 递归收集按钮树叶子节点的权限编号(与前端 SET_PERMISSION 逻辑一致) + */ + private void collectLeafPermissionCodes(List menuList, List permissionCodes) { + if (menuList == null || menuList.isEmpty()) { + return; + } + for (MenuVO menu : menuList) { + List children = menu.getChildren(); + if (children != null && !children.isEmpty()) { + collectLeafPermissionCodes(children, permissionCodes); + } else if (StringUtil.isNotBlank(menu.getCode())) { + permissionCodes.add(menu.getCode()); + } + } + } + @Override public List tree() { return ForestNodeMerger.merge(baseMapper.tree()); 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 38e7eb97..6e0c3e96 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 @@ -11,16 +11,14 @@ import org.springblade.common.constant.DictTypeEnum; import org.springblade.core.cache.utils.CacheUtil; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.tool.utils.DateUtil; -import org.springblade.core.tool.utils.DigestUtil; import org.springblade.system.cache.DictCache; import org.springblade.system.cache.ParamCache; import org.springblade.system.convert.DeptConvert; -import org.springblade.system.convert.UserConvert; 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.UserDeptIdsVO; +import org.springblade.system.pojo.vo.OaPersonSyncPageVO; import org.springblade.system.service.*; import org.springblade.system.util.DataSyncRecordUtils; import org.springblade.thirdparty.oa.constant.OAConstant; @@ -29,6 +27,8 @@ import org.springblade.thirdparty.oa.feign.IOAClient; import org.springblade.thirdparty.oa.pojo.response.OACompanyResponse; import org.springblade.thirdparty.oa.pojo.response.OADepartmentResponse; import org.springblade.thirdparty.oa.pojo.response.OAPersonResponse; +import org.springblade.thirdparty.oa.pojo.response.OAResponse; +import org.springblade.thirdparty.oa.pojo.response.OAResponseData; import org.springblade.thirdparty.oa.pojo.search.OACompanySearch; import org.springblade.thirdparty.oa.pojo.search.OADepartmentSearch; import org.springblade.thirdparty.oa.pojo.search.OAPersonSearch; @@ -38,6 +38,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -54,20 +55,14 @@ import static org.springblade.core.cache.constant.CacheConstant.USER_CACHE; @RequiredArgsConstructor @Service public class OASyncServiceImpl implements IOASyncService { - /** - * 用户表部门id最大长度 - */ - private static final int MAX_DEPT_ID_LENGTH = 2000; private final IOAClient oaClient; private final DeptConvert deptConvert; private final IDeptService deptService; - private final IUserService userService; - private final UserConvert userConvert; - private final IUserDeptService userDeptService; private final IRoleService roleService; private final IMKPushService mkPushService; private final IDataSyncRecordService dataSyncRecordService; + private final OaUserListSyncHelper oaUserListSyncHelper; @Transactional(rollbackFor = Exception.class) @Override @@ -104,6 +99,31 @@ public class OASyncServiceImpl implements IOASyncService { } } + @Transactional(rollbackFor = Exception.class) + @Override + public int syncPersonFromUserList() { + AtomicInteger syncedCount = new AtomicInteger(); + try { + ComposeLogUtil.addLog(log); + this.syncAndRecord(DataSyncRecordUtils::createOAPersonFetch, startTime -> + syncedCount.set(this.syncPersonFromOa(null)), true); + return syncedCount.get(); + } finally { + ComposeLogUtil.removeLastLog(); + } + } + + @Transactional(rollbackFor = Exception.class) + @Override + public OaPersonSyncPageVO syncPersonFromUserList(int current, int size) { + try { + ComposeLogUtil.addLog(log); + return this.syncPersonFromOaPage(current, size); + } finally { + ComposeLogUtil.removeLastLog(); + } + } + /** * 同步并记录 * @@ -213,174 +233,93 @@ public class OASyncServiceImpl implements IOASyncService { * @param startTime 查询开始时间 */ private void syncPerson(Date startTime) { - String subCompanyIds = getSubCompanyIds(); - if (StringUtils.isEmpty(subCompanyIds)) { - ComposeLogUtil.getLastLog().warn("未查询到子公司查询参数"); - return; - } - // 1. 设置查询参数 - OAPersonSearch personSearch = new OAPersonSearch(); - personSearch.setCurPage(1); - personSearch.setSubcompanyid1(subCompanyIds); - if (startTime != null) { - // 开始时间不为空,设置修改时间参数 - personSearch.setModified(DateUtil.format(startTime, DateUtil.PATTERN_DATETIME)); - } - // 2. 分页查询并处理数据 - OAUtils.pageSyncHandler(personSearch, param -> oaClient.queryPersonPage(new OASearch<>(param)), response -> { - ComposeLogUtil.getLastLog().error("调用OA接口查询人员信息失败 {}", JSON.toJSONString(response)); - return new ServiceException("调用OA接口查询人员信息失败"); - }, 10000, ComposeLogUtil.getLastLog()::info).accept(this::handlePerson); - // 清除用户缓存 - CacheUtil.clear(USER_CACHE); + this.syncPersonFromOa(startTime); } /** - * 处理oa人员 - * @param oaPersons + * 从 OA 人员列表同步组织与人员 + * + * @param startTime 增量查询开始时间,为空则全量 + * @return 处理的人员数量 */ - private void handlePerson(List oaPersons) { - if (CollectionUtil.isEmpty(oaPersons)) { - // 数据为空,直接返回 - return; - } - // 根公司id - String rootCompanyId = getRootCompanyId(); - if (rootCompanyId == null) { - return; - } - // 根公司下要同步的部门id - Set rootCompanyDeptIds = getRootCompanyDeptIds(rootCompanyId); - oaPersons = oaPersons.stream() - // 公司不是根公司,或者部门在根公司下要同步的部门列表中 - .filter(oaPerson -> !rootCompanyId.equals(oaPerson.getSubcompanyid1()) || rootCompanyDeptIds.contains(oaPerson.getDepartmentid())) - .toList(); - // 根据手机号转成set - TreeSet oaPersonSet = CollectionUtil.toTreeSet(oaPersons, Comparator.comparing(OAPersonResponse::getMobile)); - // 默认密码 - String defaultPassword = DigestUtil.encrypt(ParamCache.getValue(DEFAULT_PARAM_PASSWORD)); - // 转换数据 - List users = oaPersonSet.stream() - // 只需要手机不为空的 - .filter(person -> StringUtils.isNotBlank(person.getMobile())) - .map(person -> userConvert.person2user(person, defaultPassword)) - .toList(); - List phones = users.stream() - .map(User::getPhone) - .filter(StringUtils::isNotBlank) - .distinct() - .toList(); - // 查询所有用户 - Map userMap = userService.list(Wrappers.lambdaQuery() - //.eq(User::getIsDeleted, BladeConstant.DB_NOT_DELETED) - .in(User::getPhone, phones) - ).stream() - // 解密手机号 - .peek(userService::decryptPhone) - .collect(Collectors.toMap(User::getPhone, User::getId, (a, b) -> b)); - // 数据库存在的所有用户id - Set existsUserIds = new HashSet<>(userMap.values()); - users.forEach(user -> { - if (userMap.containsKey(user.getPhone())) { - // 根据手机号获取对应的用户id - user.setId(userMap.get(user.getPhone())); - // 清空密码,不修改密码 - user.setPassword(null); - } else { - // 没有就生成一个id - user.setId(DefaultIdentifierGenerator.getInstance().nextId(null)); - userService.encryptPhone(user); - userMap.put(user.getPhone(), user.getId()); - } - }); + private int syncPersonFromOa(Date startTime) { + OAPersonSearch personSearch = buildPersonSearch(startTime); + List oaPersons = new ArrayList<>(); + OAUtils.pageSyncHandler(personSearch, param -> oaClient.queryPersonPage(new OASearch<>(param)), response -> { + ComposeLogUtil.getLastLog().error("调用OA接口查询人员信息失败 {}", JSON.toJSONString(response)); + return new ServiceException("调用OA接口查询人员信息失败"); + }, 10000, ComposeLogUtil.getLastLog()::info).accept(oaPersons::addAll); + OaUserListSyncHelper.OaOrgIndex orgIndex = oaUserListSyncHelper.syncOrgsFromPersons(oaPersons); + OaUserListSyncHelper.PersonSyncCount personSyncCount = oaUserListSyncHelper.handlePerson(oaPersons, orgIndex); + CacheUtil.clear(USER_CACHE); + CacheUtil.clear(SYS_CACHE); + CacheUtil.clear(SYS_CACHE, Boolean.FALSE); + return personSyncCount.getSyncedCount(); + } - // 不存在的新增 - List addUsers = users.stream() - .filter(user -> !existsUserIds.contains(user.getId())) - .toList(); - if (CollectionUtil.isNotEmpty(addUsers)) { - ComposeLogUtil.getLastLog().info("批量新增用户:{}", addUsers.size()); - userService.saveBatch(addUsers); + /** + * 按页从 OA 人员列表同步组织与人员 + * + * @param current 当前页 + * @param size 每页条数 + * @return 本页同步结果 + */ + private OaPersonSyncPageVO syncPersonFromOaPage(int current, int size) { + int pageNo = current < 1 ? 1 : current; + int pageSize = size < 1 ? 50 : Math.min(size, 200); + OAPersonSearch personSearch = buildPersonSearch(null); + personSearch.setCurPage(pageNo); + personSearch.setPageSize(pageSize); + OAResponse oaResponse = oaClient.queryPersonPage(new OASearch<>(personSearch)); + if (oaResponse == null || !OAConstant.OA_SUCCESS_CODE.equals(oaResponse.getCode()) || oaResponse.getData() == null) { + ComposeLogUtil.getLastLog().error("调用OA接口查询人员信息失败 {}", JSON.toJSONString(oaResponse)); + throw new ServiceException("调用OA接口查询人员信息失败"); } - // 存在的修改 - List updateUsers = users.stream() - .filter(user -> existsUserIds.contains(user.getId())) - .toList(); - if (CollectionUtil.isNotEmpty(updateUsers)) { - ComposeLogUtil.getLastLog().info("批量修改用户:{}", updateUsers.size()); - userService.updateBatchById(updateUsers); - } - // 没有手机号的数据 = 手机号为空的数量 - long noPhoneNum = oaPersons.stream() - .map(OAPersonResponse::getMobile) - .filter(StringUtils::isBlank) - .count(); - ComposeLogUtil.getLastLog().info("没有手机号的数据:{}", noPhoneNum); + OAResponseData responseData = oaResponse.getData(); + List oaPersons = responseData.getDataList() == null + ? Collections.emptyList() : responseData.getDataList(); + long totalSize = responseData.getTotalSize() == null ? 0L : responseData.getTotalSize(); + OaUserListSyncHelper.OaOrgIndex orgIndex = oaUserListSyncHelper.syncOrgsFromPersons(oaPersons); + OaUserListSyncHelper.PersonSyncCount personSyncCount = oaUserListSyncHelper.handlePerson(oaPersons, orgIndex); + CacheUtil.clear(USER_CACHE); + CacheUtil.clear(SYS_CACHE); + CacheUtil.clear(SYS_CACHE, Boolean.FALSE); + OaPersonSyncPageVO pageVO = new OaPersonSyncPageVO(); + pageVO.setCurrent(pageNo); + pageVO.setSize(pageSize); + pageVO.setTotal(totalSize); + pageVO.setFetchedCount(oaPersons.size()); + pageVO.setSyncedCount(personSyncCount.getSyncedCount()); + pageVO.setSkippedCount(personSyncCount.getSkippedCount()); + boolean finished = oaPersons.isEmpty() + || oaPersons.size() < pageSize + || (long) pageNo * pageSize >= totalSize; + pageVO.setFinished(finished); + ComposeLogUtil.getLastLog().info("OA人员分页同步完成 {}/{},成功{},跳过{}", + pageNo, totalSize, personSyncCount.getSyncedCount(), personSyncCount.getSkippedCount()); + return pageVO; + } - Map userDeptMap = userDeptService.list(Wrappers.lambdaQuery() - .in(UserDept::getUserId, userMap.values()) - ).stream() - .collect(Collectors.toMap(this::getUserDeptKey, UserDept::getId, (a, b) -> b)); - // 数据库存在的所有用户部门id - Set existsUserDeptIds = new HashSet<>(userDeptMap.values()); - - List userDeptList = oaPersons.stream() - // 只要包含用户手机号的 - .filter(oaPerson -> userMap.containsKey(oaPerson.getMobile())) - .map(oaPerson -> userConvert.person2userDept(oaPerson, userMap)) - .toList(); - userDeptList.forEach(userDept -> { - String userDeptKey = getUserDeptKey(userDept); - if (userDeptMap.containsKey(userDeptKey)) { - // 根据key获取用户部门id - userDept.setId(userDeptMap.get(userDeptKey)); - } else { - // 没有就生成一个id - userDept.setId(DefaultIdentifierGenerator.getInstance().nextId(null)); - } - }); - // 不存在的新增 - List addList = userDeptList.stream() - .filter(userDept -> !existsUserDeptIds.contains(userDept.getId())) - .toList(); - if (CollectionUtil.isNotEmpty(addList)) { - ComposeLogUtil.getLastLog().info("批量新增用户部门:{}", addList.size()); - userDeptService.saveBatch(addList); - } - // 存在的修改 - List updateList = userDeptList.stream() - .filter(userDept -> existsUserDeptIds.contains(userDept.getId())) - .toList(); - if (CollectionUtil.isNotEmpty(updateList)) { - ComposeLogUtil.getLastLog().info("批量修改用户部门:{}", updateList.size()); - userDeptService.updateBatchById(updateList); - } - // 回写部门id到用户表 - Collection userIds = userMap.values(); - List list = userDeptService.queryUserDeptIds(userIds); - // 查询没有角色的用户id - Set noRoleUserIds = userService.list(Wrappers.lambdaQuery() - .in(User::getId, userIds) - .isNull(User::getRoleId) - ).stream() - .map(User::getId) - .collect(Collectors.toSet()); - // 获取默认角色id - String defaultRoleId = getDefaultRoleId(); - List updateUserParams = list.stream() - // 过滤掉空部门id及长度超长的 - .filter(userDeptIds -> StringUtils.isNotBlank(userDeptIds.getDeptIds()) && userDeptIds.getDeptIds().length() <= MAX_DEPT_ID_LENGTH) - .map(userDeptIds -> { - User user = new User(); - user.setId(userDeptIds.getUserId()); - user.setDeptId(userDeptIds.getDeptIds()); - user.setDeptCodes(userDeptIds.getDeptCodes()); - if (noRoleUserIds.contains(userDeptIds.getUserId())) { - user.setRoleId(defaultRoleId); - } - return user; - }).toList(); - userService.updateBatchById(updateUserParams); + /** + * 组装 OA 人员分页查询参数 + * + * @param startTime 增量查询开始时间 + * @return 查询参数 + */ + private OAPersonSearch buildPersonSearch(Date startTime) { + OAPersonSearch personSearch = new OAPersonSearch(); + personSearch.setCurPage(1); + personSearch.setPageSize(200); + personSearch.setCreated(""); + personSearch.setWorkcode(""); + personSearch.setSubcompanyid1(""); + personSearch.setDepartmentid(""); + personSearch.setJobtitleid(""); + personSearch.setId(""); + personSearch.setLoginid(""); + personSearch.setIsadaccount(""); + personSearch.setModified(startTime == null ? "" : DateUtil.format(startTime, DateUtil.PATTERN_DATETIME)); + return personSearch; } /** @@ -634,16 +573,4 @@ public class OASyncServiceImpl implements IOASyncService { // 部门编码去掉前缀,就是oa的id return dept.getDeptCode().replace(OAConvertConstant.COMPANY_OA_PREFIX, ""); } - - /** - * 获取用户部门唯一标识,用户id+公司编码+部门编码 - * @param userDept - * @return - */ - private String getUserDeptKey(UserDept userDept) { - if (userDept == null) { - return null; - } - return userDept.getUserId() + userDept.getCompanyCode() + userDept.getDeptCode(); - } } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OaUserListSyncHelper.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OaUserListSyncHelper.java new file mode 100644 index 00000000..6a8a1884 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/OaUserListSyncHelper.java @@ -0,0 +1,609 @@ +/** + * 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.service.impl; + +import cn.hutool.core.collection.CollectionUtil; +import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springblade.common.constant.DataStatusEnum; +import org.springblade.core.secure.utils.AuthUtil; +import org.springblade.core.tool.constant.BladeConstant; +import org.springblade.core.tool.utils.DigestUtil; +import org.springblade.system.cache.ParamCache; +import org.springblade.system.convert.UserConvert; +import org.springblade.system.log.ComposeLogUtil; +import org.springblade.system.pojo.entity.Dept; +import org.springblade.system.pojo.entity.Role; +import org.springblade.system.pojo.entity.User; +import org.springblade.system.pojo.entity.UserDept; +import org.springblade.system.pojo.enums.DeptCategory; +import org.springblade.system.pojo.vo.UserDeptIdsVO; +import org.springblade.system.service.IDeptService; +import org.springblade.system.service.IRoleService; +import org.springblade.system.service.IUserDeptService; +import org.springblade.system.service.IUserService; +import org.springblade.thirdparty.oa.constant.OAConvertConstant; +import org.springblade.thirdparty.oa.pojo.response.OAPersonResponse; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.springblade.common.constant.CommonConstant.DEFAULT_PARAM_PASSWORD; +import static org.springblade.common.constant.CommonConstant.DEFAULT_PARAM_ROLE; +import static org.springblade.common.constant.CommonConstant.DEFAULT_ROLE; +import static org.springblade.common.constant.CommonConstant.YES; + +/** + * 从 OA 人员列表提取组织并同步人员 + * + * @author Chill + */ +@Component +@RequiredArgsConstructor +public class OaUserListSyncHelper { + + private static final int MAX_DEPT_ID_LENGTH = 2000; + + private final IDeptService deptService; + private final IUserService userService; + private final UserConvert userConvert; + private final IUserDeptService userDeptService; + private final IRoleService roleService; + + /** + * 从人员数据提取二级公司、三级部门,并挂到「桂物物流集团」下 + * + * @param oaPersons OA人员 + * @return 组织索引 + */ + public OaOrgIndex syncOrgsFromPersons(List oaPersons) { + OaOrgIndex orgIndex = new OaOrgIndex(); + if (CollectionUtil.isEmpty(oaPersons)) { + return orgIndex; + } + Date orgSyncStart = new Date(); + Dept rootCompany = this.getOrCreateRootCompany(); + String tenantId = resolveTenantId(); + Map companyPersonMap = new LinkedHashMap<>(); + Map departmentPersonMap = new LinkedHashMap<>(); + for (OAPersonResponse oaPerson : oaPersons) { + String companyKey = resolveCompanyKey(oaPerson); + if (StringUtils.isNotBlank(companyKey) && StringUtils.isNotBlank(oaPerson.getSubcompanyname())) { + companyPersonMap.putIfAbsent(companyKey, oaPerson); + } + String departmentKey = resolveDepartmentKey(oaPerson); + if (StringUtils.isNotBlank(departmentKey) && StringUtils.isNotBlank(oaPerson.getDepartmentname()) + && StringUtils.isNotBlank(oaPerson.getSubcompanyname())) { + departmentPersonMap.putIfAbsent(departmentKey, oaPerson); + } + } + + Map existingCompanyByCode = new HashMap<>(); + Map existingCompanyByName = new HashMap<>(); + deptService.list(Wrappers.lambdaQuery() + .eq(Dept::getParentId, rootCompany.getId()) + .eq(Dept::getDeptCategory, DeptCategory.COMPANY.getCode()) + ).forEach(dept -> { + if (StringUtils.isNotBlank(dept.getDeptCode())) { + existingCompanyByCode.put(dept.getDeptCode(), dept); + } + if (StringUtils.isNotBlank(dept.getDeptName())) { + existingCompanyByName.put(dept.getDeptName(), dept); + } + }); + List addCompanies = new ArrayList<>(); + List updateCompanies = new ArrayList<>(); + companyPersonMap.forEach((companyKey, oaPerson) -> { + String oaCode = buildCompanyCode(oaPerson, companyKey); + Dept existing = existingCompanyByCode.get(oaCode); + if (existing == null) { + existing = existingCompanyByName.get(oaPerson.getSubcompanyname()); + } + Dept company = this.upsertOrg(oaPerson.getSubcompanyname(), oaCode, existing, rootCompany, tenantId, + DeptCategory.COMPANY, addCompanies, updateCompanies); + orgIndex.companyByOaId.put(companyKey, company); + }); + this.saveOrgs(addCompanies, updateCompanies, DeptCategory.COMPANY); + + Map existingDeptByCode = new HashMap<>(); + Map existingDeptByParentAndName = new HashMap<>(); + List companyIds = orgIndex.companyByOaId.values().stream() + .map(Dept::getId) + .filter(Objects::nonNull) + .distinct() + .toList(); + if (CollectionUtil.isNotEmpty(companyIds)) { + deptService.list(Wrappers.lambdaQuery() + .in(Dept::getParentId, companyIds) + .eq(Dept::getDeptCategory, DeptCategory.DEPT.getCode()) + ).forEach(dept -> { + if (StringUtils.isNotBlank(dept.getDeptCode())) { + existingDeptByCode.put(dept.getDeptCode(), dept); + } + if (dept.getParentId() != null && StringUtils.isNotBlank(dept.getDeptName())) { + existingDeptByParentAndName.put(dept.getParentId() + "#" + dept.getDeptName(), dept); + } + }); + } + List addDepartments = new ArrayList<>(); + List updateDepartments = new ArrayList<>(); + departmentPersonMap.forEach((departmentKey, oaPerson) -> { + Dept parentCompany = orgIndex.findCompany(oaPerson); + if (parentCompany == null || parentCompany.getId() == null) { + return; + } + String oaCode = buildDepartmentCode(oaPerson, departmentKey); + Dept existing = existingDeptByCode.get(oaCode); + if (existing == null) { + existing = existingDeptByParentAndName.get(parentCompany.getId() + "#" + oaPerson.getDepartmentname()); + } + Dept department = this.upsertOrg(oaPerson.getDepartmentname(), oaCode, existing, parentCompany, tenantId, + DeptCategory.DEPT, addDepartments, updateDepartments); + orgIndex.deptByOaId.put(departmentKey, department); + orgIndex.deptByCompanyAndName.put(resolveCompanyKey(oaPerson) + "#" + oaPerson.getDepartmentname(), department); + }); + this.saveOrgs(addDepartments, updateDepartments, DeptCategory.DEPT); + deptService.updateAncestors(orgSyncStart); + ComposeLogUtil.getLastLog().info("同步人员提取组织完成,二级公司{}个,三级部门{}个", + orgIndex.companyByOaId.size(), orgIndex.deptByOaId.size()); + return orgIndex; + } + + /** + * 同步人员并绑定到三级部门 + * + * @param oaPersons OA人员 + * @param orgIndex 组织索引 + * @return 本批同步成功与跳过数量 + */ + public PersonSyncCount handlePerson(List oaPersons, OaOrgIndex orgIndex) { + if (CollectionUtil.isEmpty(oaPersons)) { + return new PersonSyncCount(0, 0); + } + Map uniquePersonMap = new LinkedHashMap<>(); + int skippedCount = 0; + for (OAPersonResponse oaPerson : oaPersons) { + String account = userConvert.resolveAccount(oaPerson); + if (StringUtils.isBlank(account)) { + skippedCount++; + continue; + } + uniquePersonMap.putIfAbsent(account, oaPerson); + } + if (uniquePersonMap.isEmpty()) { + ComposeLogUtil.getLastLog().warn("OA人员均缺少loginid/工号/手机号,跳过人员同步"); + return new PersonSyncCount(0, skippedCount); + } + String tenantId = resolveTenantId(); + String defaultPassword = DigestUtil.encrypt(ParamCache.getValue(DEFAULT_PARAM_PASSWORD)); + List users = uniquePersonMap.values().stream() + .map(person -> { + User user = userConvert.person2user(person, defaultPassword); + user.setTenantId(tenantId); + return user; + }) + .toList(); + List accounts = users.stream() + .map(User::getAccount) + .filter(StringUtils::isNotBlank) + .distinct() + .toList(); + List phones = users.stream() + .map(User::getPhone) + .filter(StringUtils::isNotBlank) + .distinct() + .toList(); + Map existingByAccount = new HashMap<>(); + Map existingByPhone = new HashMap<>(); + userService.list(Wrappers.lambdaQuery() + .and(wrapper -> { + wrapper.in(User::getAccount, accounts); + if (CollectionUtil.isNotEmpty(phones)) { + wrapper.or().in(User::getPhone, phones); + } + }) + ).stream() + .peek(userService::decryptPhone) + .forEach(user -> { + if (StringUtils.isNotBlank(user.getAccount())) { + existingByAccount.put(user.getAccount(), user); + } + if (StringUtils.isNotBlank(user.getPhone())) { + existingByPhone.put(user.getPhone(), user); + } + }); + Map userMap = new HashMap<>(); + Set existsUserIds = new HashSet<>(); + users.forEach(user -> { + User existingUser = existingByAccount.get(user.getAccount()); + if (existingUser == null && StringUtils.isNotBlank(user.getPhone())) { + existingUser = existingByPhone.get(user.getPhone()); + } + if (existingUser != null) { + user.setId(existingUser.getId()); + user.setPassword(null); + existsUserIds.add(existingUser.getId()); + } else { + user.setId(DefaultIdentifierGenerator.getInstance().nextId(null)); + user.setPostId("-1"); + user.setPersonCategory(1); + user.setDataScopeRange(1); + user.setDataLevelRange(1); + user.setIncludeNewCustomer(0); + userService.encryptPhone(user); + } + userMap.put(user.getAccount(), user.getId()); + }); + + List addUsers = users.stream() + .filter(user -> !existsUserIds.contains(user.getId())) + .toList(); + if (CollectionUtil.isNotEmpty(addUsers)) { + ComposeLogUtil.getLastLog().info("批量新增用户:{}", addUsers.size()); + userService.saveBatch(addUsers); + } + List updateUsers = users.stream() + .filter(user -> existsUserIds.contains(user.getId())) + .toList(); + if (CollectionUtil.isNotEmpty(updateUsers)) { + ComposeLogUtil.getLastLog().info("批量修改用户:{}", updateUsers.size()); + userService.updateBatchById(updateUsers); + } + ComposeLogUtil.getLastLog().info("缺少账号已跳过的人员:{}", skippedCount); + if (userMap.isEmpty()) { + return new PersonSyncCount(0, skippedCount); + } + + Map userDeptMap = userDeptService.list(Wrappers.lambdaQuery() + .in(UserDept::getUserId, userMap.values()) + ).stream() + .collect(Collectors.toMap(this::getUserDeptKey, UserDept::getId, (first, second) -> second)); + Set existsUserDeptIds = new HashSet<>(userDeptMap.values()); + List userDeptList = oaPersons.stream() + .filter(oaPerson -> userMap.containsKey(userConvert.resolveAccount(oaPerson))) + .map(oaPerson -> this.buildUserDept(oaPerson, userMap, orgIndex)) + .filter(Objects::nonNull) + .toList(); + userDeptList.forEach(userDept -> { + String userDeptKey = getUserDeptKey(userDept); + if (userDeptMap.containsKey(userDeptKey)) { + userDept.setId(userDeptMap.get(userDeptKey)); + } else { + userDept.setId(DefaultIdentifierGenerator.getInstance().nextId(null)); + } + }); + List addList = userDeptList.stream() + .filter(userDept -> !existsUserDeptIds.contains(userDept.getId())) + .toList(); + if (CollectionUtil.isNotEmpty(addList)) { + ComposeLogUtil.getLastLog().info("批量新增用户部门:{}", addList.size()); + userDeptService.saveBatch(addList); + } + List updateList = userDeptList.stream() + .filter(userDept -> existsUserDeptIds.contains(userDept.getId())) + .toList(); + if (CollectionUtil.isNotEmpty(updateList)) { + ComposeLogUtil.getLastLog().info("批量修改用户部门:{}", updateList.size()); + userDeptService.updateBatchById(updateList); + } + Collection userIds = userMap.values(); + List list = userDeptService.queryUserDeptIds(userIds); + Set noRoleUserIds = userService.list(Wrappers.lambdaQuery() + .in(User::getId, userIds) + .and(wrapper -> wrapper.isNull(User::getRoleId) + .or().eq(User::getRoleId, "") + .or().eq(User::getRoleId, "-1")) + ).stream() + .map(User::getId) + .collect(Collectors.toSet()); + String defaultRoleId = getDefaultRoleId(); + List updateUserParams = list.stream() + .filter(userDeptIds -> StringUtils.isNotBlank(userDeptIds.getDeptIds()) && userDeptIds.getDeptIds().length() <= MAX_DEPT_ID_LENGTH) + .map(userDeptIds -> { + User user = new User(); + user.setId(userDeptIds.getUserId()); + user.setDeptId(userDeptIds.getDeptIds()); + user.setDeptCodes(userDeptIds.getDeptCodes()); + if (noRoleUserIds.contains(userDeptIds.getUserId())) { + user.setRoleId(defaultRoleId); + } + return user; + }).toList(); + if (CollectionUtil.isNotEmpty(updateUserParams)) { + userService.updateBatchById(updateUserParams); + } + return new PersonSyncCount(users.size(), skippedCount); + } + + private Dept getOrCreateRootCompany() { + Dept rootCompany = deptService.getOne(Wrappers.lambdaQuery() + .eq(Dept::getDeptName, OAConvertConstant.ROOT_COMPANY_NAME) + .last("limit 1"), false); + if (rootCompany != null) { + return rootCompany; + } + rootCompany = new Dept(); + rootCompany.setId(DefaultIdentifierGenerator.getInstance().nextId(null)); + rootCompany.setTenantId(resolveTenantId()); + rootCompany.setParentId(BladeConstant.TOP_PARENT_ID); + rootCompany.setAncestors(String.valueOf(BladeConstant.TOP_PARENT_ID)); + rootCompany.setDeptName(OAConvertConstant.ROOT_COMPANY_NAME); + rootCompany.setFullName(OAConvertConstant.ROOT_COMPANY_NAME); + rootCompany.setShortName(OAConvertConstant.ROOT_COMPANY_NAME); + rootCompany.setDeptCode("OACROOT"); + rootCompany.setParentCode(String.valueOf(BladeConstant.TOP_PARENT_ID)); + rootCompany.setBelongCompanyCode("OACROOT"); + rootCompany.setDeptCategory(DeptCategory.COMPANY.getCode()); + rootCompany.setSort(0); + rootCompany.setStatus(DataStatusEnum.ENABLE.getCode()); + rootCompany.setIsDeleted(BladeConstant.DB_NOT_DELETED); + rootCompany.setIsOa(YES); + rootCompany.setIsPlatformCompany(0); + rootCompany.setSyncTime(new Date()); + deptService.save(rootCompany); + ComposeLogUtil.getLastLog().info("已创建顶级组织:{}", OAConvertConstant.ROOT_COMPANY_NAME); + return rootCompany; + } + + private Dept upsertOrg(String name, String oaCode, Dept existing, Dept parent, String tenantId, + DeptCategory deptCategory, List addList, List updateList) { + if (existing != null) { + Dept updateParam = new Dept(); + updateParam.setId(existing.getId()); + updateParam.setDeptName(name); + updateParam.setFullName(name); + updateParam.setShortName(name); + updateParam.setParentId(parent.getId()); + updateParam.setParentCode(parent.getDeptCode()); + updateParam.setAncestors(buildAncestors(parent)); + updateParam.setIsOa(YES); + updateParam.setSyncTime(new Date()); + updateList.add(updateParam); + existing.setDeptName(name); + existing.setFullName(name); + existing.setShortName(name); + existing.setParentId(parent.getId()); + existing.setParentCode(parent.getDeptCode()); + existing.setAncestors(updateParam.getAncestors()); + return existing; + } + Dept dept = this.buildOrgDept(name, oaCode, parent, tenantId, deptCategory); + dept.setId(DefaultIdentifierGenerator.getInstance().nextId(null)); + addList.add(dept); + return dept; + } + + private void saveOrgs(List addList, List updateList, DeptCategory deptCategory) { + if (CollectionUtil.isNotEmpty(addList)) { + ComposeLogUtil.getLastLog().info("批量新增{}:{}", deptCategory.getName(), addList.size()); + deptService.saveBatch(addList); + } + if (CollectionUtil.isNotEmpty(updateList)) { + ComposeLogUtil.getLastLog().info("批量修改{}:{}", deptCategory.getName(), updateList.size()); + deptService.updateBatchById(updateList); + } + } + + private Dept buildOrgDept(String name, String deptCode, Dept parent, String tenantId, DeptCategory deptCategory) { + Dept dept = new Dept(); + dept.setTenantId(tenantId); + dept.setParentId(parent.getId()); + dept.setParentCode(parent.getDeptCode()); + dept.setAncestors(this.buildAncestors(parent)); + dept.setDeptName(name); + dept.setFullName(name); + dept.setShortName(name); + dept.setDeptCode(deptCode); + dept.setBelongCompanyCode(DeptCategory.COMPANY.equals(deptCategory) ? deptCode : parent.getBelongCompanyCode()); + dept.setDeptCategory(deptCategory.getCode()); + dept.setSort(0); + dept.setStatus(DataStatusEnum.ENABLE.getCode()); + dept.setIsDeleted(BladeConstant.DB_NOT_DELETED); + dept.setIsOa(YES); + dept.setIsPlatformCompany(0); + dept.setSyncTime(new Date()); + return dept; + } + + private UserDept buildUserDept(OAPersonResponse oaPerson, Map userMap, OaOrgIndex orgIndex) { + Dept department = orgIndex.findDept(oaPerson); + if (department == null || department.getId() == null) { + return null; + } + UserDept userDept = userConvert.person2userDept(oaPerson, userMap); + if (userDept.getUserId() == null) { + return null; + } + Dept company = orgIndex.findCompany(oaPerson); + userDept.setDeptId(department.getId()); + userDept.setDeptCode(department.getDeptCode()); + userDept.setDeptName(department.getDeptName()); + if (company != null) { + userDept.setCompanyCode(company.getDeptCode()); + userDept.setCompanyName(company.getDeptName()); + } + return userDept; + } + + private String getDefaultRoleId() { + String defaultRole = ParamCache.getValue(DEFAULT_PARAM_ROLE); + if (defaultRole == null) { + defaultRole = DEFAULT_ROLE; + } + List roleList = roleService.list(Wrappers.lambdaQuery() + .eq(Role::getRoleAlias, defaultRole) + ); + if (CollectionUtil.isEmpty(roleList)) { + return null; + } + return roleList.get(0).getId().toString(); + } + + private String resolveCompanyKey(OAPersonResponse oaPerson) { + if (oaPerson == null) { + return null; + } + if (StringUtils.isNotBlank(oaPerson.getSubcompanyid1())) { + return oaPerson.getSubcompanyid1().trim(); + } + if (StringUtils.isNotBlank(oaPerson.getSubcompanyname())) { + return "NAME:" + oaPerson.getSubcompanyname().trim(); + } + return null; + } + + private String resolveDepartmentKey(OAPersonResponse oaPerson) { + if (oaPerson == null) { + return null; + } + if (StringUtils.isNotBlank(oaPerson.getDepartmentid())) { + return oaPerson.getDepartmentid().trim(); + } + String companyKey = resolveCompanyKey(oaPerson); + if (StringUtils.isNotBlank(companyKey) && StringUtils.isNotBlank(oaPerson.getDepartmentname())) { + return companyKey + ":" + oaPerson.getDepartmentname().trim(); + } + return null; + } + + private String buildCompanyCode(OAPersonResponse oaPerson, String companyKey) { + if (StringUtils.isNotBlank(oaPerson.getSubcompanyid1())) { + return OAConvertConstant.COMPANY_OA_PREFIX + oaPerson.getSubcompanyid1().trim(); + } + return OAConvertConstant.COMPANY_OA_PREFIX + "N" + Math.abs(companyKey.hashCode()); + } + + private String buildDepartmentCode(OAPersonResponse oaPerson, String departmentKey) { + if (StringUtils.isNotBlank(oaPerson.getDepartmentid())) { + return OAConvertConstant.DEPARTMENT_OA_PREFIX + oaPerson.getDepartmentid().trim(); + } + return OAConvertConstant.DEPARTMENT_OA_PREFIX + "N" + Math.abs(departmentKey.hashCode()); + } + + private String buildAncestors(Dept parent) { + String ancestors = parent.getAncestors(); + if (StringUtils.isBlank(ancestors)) { + ancestors = String.valueOf(BladeConstant.TOP_PARENT_ID); + } + return ancestors + "," + parent.getId(); + } + + private String resolveTenantId() { + String tenantId = AuthUtil.getTenantId(); + if (StringUtils.isBlank(tenantId)) { + return BladeConstant.ADMIN_TENANT_ID; + } + return tenantId; + } + + private String getUserDeptKey(UserDept userDept) { + if (userDept == null) { + return null; + } + return userDept.getUserId() + userDept.getCompanyCode() + userDept.getDeptCode(); + } + + /** + * 本批人员同步计数 + */ + public static class PersonSyncCount { + private final int syncedCount; + private final int skippedCount; + + public PersonSyncCount(int syncedCount, int skippedCount) { + this.syncedCount = syncedCount; + this.skippedCount = skippedCount; + } + + public int getSyncedCount() { + return syncedCount; + } + + public int getSkippedCount() { + return skippedCount; + } + } + + /** + * OA 组织索引 + */ + public static class OaOrgIndex { + private final Map companyByOaId = new HashMap<>(); + private final Map deptByOaId = new HashMap<>(); + private final Map deptByCompanyAndName = new HashMap<>(); + + private Dept findCompany(OAPersonResponse oaPerson) { + if (oaPerson == null) { + return null; + } + if (StringUtils.isNotBlank(oaPerson.getSubcompanyid1())) { + Dept company = companyByOaId.get(oaPerson.getSubcompanyid1().trim()); + if (company != null) { + return company; + } + } + if (StringUtils.isNotBlank(oaPerson.getSubcompanyname())) { + return companyByOaId.get("NAME:" + oaPerson.getSubcompanyname().trim()); + } + return null; + } + + private Dept findDept(OAPersonResponse oaPerson) { + if (oaPerson == null) { + return null; + } + if (StringUtils.isNotBlank(oaPerson.getDepartmentid())) { + Dept department = deptByOaId.get(oaPerson.getDepartmentid().trim()); + if (department != null) { + return department; + } + } + String companyKey = StringUtils.isNotBlank(oaPerson.getSubcompanyid1()) + ? oaPerson.getSubcompanyid1().trim() + : (StringUtils.isNotBlank(oaPerson.getSubcompanyname()) ? "NAME:" + oaPerson.getSubcompanyname().trim() : null); + if (StringUtils.isNotBlank(companyKey) && StringUtils.isNotBlank(oaPerson.getDepartmentname())) { + Dept department = deptByCompanyAndName.get(companyKey + "#" + oaPerson.getDepartmentname()); + if (department != null) { + return department; + } + return deptByOaId.get(companyKey + ":" + oaPerson.getDepartmentname().trim()); + } + return null; + } + } +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserPhoneServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserPhoneServiceImpl.java new file mode 100644 index 00000000..f6f26d92 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserPhoneServiceImpl.java @@ -0,0 +1,212 @@ +/** + * 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.service.impl; + +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.cache.utils.CacheUtil; +import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.redis.cache.BladeRedis; +import org.springblade.core.secure.utils.AuthUtil; +import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.Func; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.resource.feign.ISmsClient; +import org.springblade.resource.utils.SmsUtil; +import org.springblade.system.pojo.dto.PhoneChangeDTO; +import org.springblade.system.pojo.dto.PhoneVerifyDTO; +import org.springblade.system.pojo.entity.User; +import org.springblade.system.service.IUserPhoneService; +import org.springblade.system.service.IUserService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Duration; +import java.util.regex.Pattern; + +import static org.springblade.core.cache.constant.CacheConstant.USER_CACHE; + +/** + * 用户手机号变更服务实现 + * + * @author Chill + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class UserPhoneServiceImpl implements IUserPhoneService { + + /** + * 与登录短信一致,对应后台 /resource/sms 的 smsCode + */ + private static final String SMS_RESOURCE_CODE = "ali_reg"; + + /** + * 原手机号已校验凭证(Redis) + */ + private static final String PHONE_CHANGE_VERIFIED_KEY = "blade:user:phone:change:verified:"; + + private static final Duration PHONE_CHANGE_VERIFIED_TTL = Duration.ofMinutes(15); + + private static final Pattern MOBILE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$"); + + private final IUserService userService; + private final ISmsClient smsClient; + private final BladeRedis bladeRedis; + + @Override + public R sendCode(String phone) { + String normalizedPhone = normalizePhone(phone); + Long userId = AuthUtil.getUserId(); + if (Func.isEmpty(userId)) { + throw new ServiceException("请先登录"); + } + User currentUser = requireCurrentUser(userId); + String tenantId = Func.toStr(currentUser.getTenantId(), AuthUtil.getTenantId()); + boolean isCurrentPhone = StringUtil.equals(normalizedPhone, Func.toStr(currentUser.getPhone())); + if (!isCurrentPhone) { + assertPhoneAvailable(tenantId, normalizedPhone, userId); + } + R result = smsClient.sendValidate(tenantId, SMS_RESOURCE_CODE, normalizedPhone); + if (result == null || !result.isSuccess()) { + return R.fail(SmsUtil.SEND_FAIL); + } + return R.data(result.getData(), SmsUtil.SEND_SUCCESS); + } + + @Override + public boolean verifyOldPhone(PhoneVerifyDTO phoneVerify) { + Long userId = AuthUtil.getUserId(); + if (Func.isEmpty(userId)) { + throw new ServiceException("请先登录"); + } + String id = Func.toStr(phoneVerify.getId()).trim(); + String code = Func.toStr(phoneVerify.getCode()).trim(); + if (StringUtil.isBlank(id) || StringUtil.isBlank(code)) { + throw new ServiceException("请先获取并填写验证码"); + } + User currentUser = requireCurrentUser(userId); + String oldPhone = Func.toStr(currentUser.getPhone()).trim(); + if (StringUtil.isBlank(oldPhone)) { + throw new ServiceException("当前账号未绑定手机号"); + } + validateSms(currentUser.getTenantId(), id, code, oldPhone); + bladeRedis.setEx(PHONE_CHANGE_VERIFIED_KEY + userId, "1", PHONE_CHANGE_VERIFIED_TTL); + return true; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean changePhone(PhoneChangeDTO phoneChange) { + Long userId = AuthUtil.getUserId(); + if (Func.isEmpty(userId)) { + throw new ServiceException("请先登录"); + } + String verified = Func.toStr(bladeRedis.get(PHONE_CHANGE_VERIFIED_KEY + userId)); + if (!StringUtil.equals(verified, "1")) { + throw new ServiceException("请先完成原手机号验证"); + } + String id = Func.toStr(phoneChange.getId()).trim(); + String code = Func.toStr(phoneChange.getCode()).trim(); + String newPhone = normalizePhone(phoneChange.getNewPhone()); + if (StringUtil.isBlank(id) || StringUtil.isBlank(code)) { + throw new ServiceException("请先获取并填写验证码"); + } + User currentUser = requireCurrentUser(userId); + String oldPhone = Func.toStr(currentUser.getPhone()).trim(); + if (StringUtil.equals(newPhone, oldPhone)) { + throw new ServiceException("新手机号不可与当前手机号相同"); + } + String tenantId = Func.toStr(currentUser.getTenantId(), AuthUtil.getTenantId()); + assertPhoneAvailable(tenantId, newPhone, userId); + validateSms(tenantId, id, code, newPhone); + + User updateUser = new User(); + updateUser.setId(userId); + updateUser.setPhone(newPhone); + // 账号若等于原手机号,同步更新,保证短信登录可用 + if (StringUtil.isNotBlank(oldPhone) && StringUtil.equals(oldPhone, Func.toStr(currentUser.getAccount()))) { + assertAccountAvailable(tenantId, newPhone, userId); + updateUser.setAccount(newPhone); + } + boolean updated = userService.updateById(updateUser); + if (!updated) { + throw new ServiceException("手机号修改失败"); + } + bladeRedis.del(PHONE_CHANGE_VERIFIED_KEY + userId); + CacheUtil.clear(USER_CACHE); + return true; + } + + private User requireCurrentUser(Long userId) { + User user = userService.getById(userId); + if (user == null) { + throw new ServiceException("用户不存在"); + } + return user; + } + + private void validateSms(String tenantId, String id, String value, String phone) { + R result = smsClient.validateMessage(tenantId, SMS_RESOURCE_CODE, id, value, phone); + if (result == null || !result.isSuccess()) { + throw new ServiceException(SmsUtil.VALIDATE_FAIL); + } + } + + private void assertPhoneAvailable(String tenantId, String phone, Long excludeUserId) { + Long phoneCount = userService.count( + Wrappers.lambdaQuery() + .eq(User::getTenantId, tenantId) + .eq(User::getPhone, phone) + .ne(User::getId, excludeUserId) + ); + if (phoneCount != null && phoneCount > 0L) { + throw new ServiceException(StringUtil.format("当前手机 [{}] 已存在!", phone)); + } + } + + private void assertAccountAvailable(String tenantId, String account, Long excludeUserId) { + Long accountCount = userService.count( + Wrappers.lambdaQuery() + .eq(User::getTenantId, tenantId) + .eq(User::getAccount, account) + .ne(User::getId, excludeUserId) + ); + if (accountCount != null && accountCount > 0L) { + throw new ServiceException(StringUtil.format("当前用户 [{}] 已存在!", account)); + } + } + + private String normalizePhone(String phone) { + String normalizedPhone = Func.toStr(phone).trim(); + if (!MOBILE_PATTERN.matcher(normalizedPhone).matches()) { + throw new ServiceException("手机号格式不正确"); + } + return normalizedPhone; + } + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java index 3eb7d438..575adb7d 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/UserServiceImpl.java @@ -529,6 +529,46 @@ public class UserServiceImpl extends BaseServiceImpl implement return userInfo; } + @Override + @Transactional(rollbackFor = Exception.class) + public boolean bindWxMiniOpenId(String tenantId, Long userId, String openid, String phone) { + if (Func.isBlank(tenantId) || Func.isEmpty(userId) || Func.isBlank(openid)) { + throw new ServiceException("绑定微信 openid 参数不完整"); + } + String source = "WECHAT_MINI"; + UserOauth byOpenId = userOauthService.getOne(Wrappers.lambdaQuery() + .eq(UserOauth::getTenantId, tenantId) + .eq(UserOauth::getSource, source) + .eq(UserOauth::getUuid, openid) + .last("LIMIT 1")); + if (byOpenId != null) { + byOpenId.setUserId(userId); + if (Func.isNotBlank(phone)) { + byOpenId.setUsername(phone); + } + return userOauthService.updateById(byOpenId); + } + UserOauth byUser = userOauthService.getOne(Wrappers.lambdaQuery() + .eq(UserOauth::getTenantId, tenantId) + .eq(UserOauth::getSource, source) + .eq(UserOauth::getUserId, userId) + .last("LIMIT 1")); + if (byUser != null) { + byUser.setUuid(openid); + if (Func.isNotBlank(phone)) { + byUser.setUsername(phone); + } + return userOauthService.updateById(byUser); + } + UserOauth oauth = new UserOauth(); + oauth.setTenantId(tenantId); + oauth.setUserId(userId); + oauth.setUuid(openid); + oauth.setUsername(Func.toStr(phone, "")); + oauth.setSource(source); + return userOauthService.save(oauth); + } + @Override @Transactional(rollbackFor = Exception.class) public boolean grant(String userIds, String roleIds) { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ExceptionDisposalController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ExceptionDisposalController.java index 3a647041..2fc52aa9 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ExceptionDisposalController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ExceptionDisposalController.java @@ -30,7 +30,6 @@ import lombok.AllArgsConstructor; import org.springblade.core.boot.ctrl.BladeController; import org.springblade.core.mp.support.Condition; import org.springblade.core.mp.support.Query; -import org.springblade.core.secure.annotation.PreAuth; import org.springblade.core.tool.api.R; import org.springblade.transport.pojo.dto.ExceptionDisposalFollowRequest; import org.springblade.transport.pojo.entity.ExceptionDisposal; @@ -48,7 +47,7 @@ import org.springframework.web.bind.annotation.RestController; *

* 对外路径:{@code /api/blade-transport/exception-disposal/**} * 同时兼容未去前缀直连 {@code /blade-transport/exception-disposal/**}。 - * 司机上报(submit)/ 列表 / 详情仅需登录态;跟进与完成保留菜单鉴权。 + * 列表 / 详情 / 上报 / 跟进 / 完成均仅需登录态(小程序调度端与司机端共用)。 */ @RestController @AllArgsConstructor @@ -80,27 +79,24 @@ public class ExceptionDisposalController extends BladeController { } @PostMapping("/follow") - @PreAuth(menu = "exception_disposal") @ApiOperationSupport(order = 4) - @Operation(summary = "异常跟进") + @Operation(summary = "异常跟进", description = "调度端跟进;仅需登录态") public R follow(@RequestBody ExceptionDisposalFollowRequest request) { exceptionDisposalService.follow(request); return R.success("跟进成功"); } @PostMapping("/complete") - @PreAuth(menu = "exception_disposal") @ApiOperationSupport(order = 5) - @Operation(summary = "完成异常") + @Operation(summary = "完成异常", description = "调度端结案;仅需登录态") public R complete(@RequestBody ExceptionDisposalFollowRequest request) { exceptionDisposalService.complete(request.getId()); return R.success("完成成功"); } @PostMapping("/batch-complete") - @PreAuth(menu = "exception_disposal") @ApiOperationSupport(order = 6) - @Operation(summary = "批量完成异常") + @Operation(summary = "批量完成异常", description = "调度端批量结案;仅需登录态") public R batchComplete(@RequestParam String ids) { exceptionDisposalService.batchComplete(ids); return R.success("批量完成成功"); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ManageWaybillController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ManageWaybillController.java new file mode 100644 index 00000000..119cf5b9 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ManageWaybillController.java @@ -0,0 +1,143 @@ +/** + * 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. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import org.springblade.core.boot.ctrl.BladeController; +import org.springblade.core.tool.api.R; +import org.springblade.transport.pojo.entity.Waybill; +import org.springblade.transport.pojo.vo.AdminDriverOptionVO; +import org.springblade.transport.pojo.vo.AdminHomeStatsVO; +import org.springblade.transport.pojo.vo.AdminHomeVO; +import org.springblade.transport.pojo.vo.AdminVehicleOptionVO; +import org.springblade.transport.pojo.vo.AdminWaybillCardVO; +import org.springblade.transport.pojo.vo.AdminWaybillDetailVO; +import org.springblade.transport.service.IManageWaybillService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 调度端运单(小程序管理端) + *

+ * 对外完整路径:{@code /api/blade-transport/waybill/manage/**} + * (网关 StripPrefix 去掉 {@code blade-transport} 后落入 {@code /waybill/manage/**})。 + * 同时兼容未去前缀直连({@code /blade-transport/waybill/manage/**})。 + * 仅需登录态,不挂管理端菜单鉴权。 + */ +@RestController +@AllArgsConstructor +@RequestMapping({"/waybill/manage", "/blade-transport/waybill/manage"}) +@Tag(name = "调度端运单", description = "小程序调度端首页统计与运单列表") +public class ManageWaybillController extends BladeController { + + private final IManageWaybillService manageWaybillService; + + @GetMapping("/stats") + @ApiOperationSupport(order = 1) + @Operation(summary = "运单状态统计", description = "待接单=pending,运输中=running,已完成=completed;租户内不过滤组织(小程序调度账号组织常与运单不一致);在途异常=异常处置状态≠已完成") + public R stats() { + return R.data(manageWaybillService.stats()); + } + + @GetMapping("/home") + @ApiOperationSupport(order = 2) + @Operation(summary = "首页聚合", description = "统计 + 异常/风险角标 + 待处理事项(异常处置≠已完成)+ 当前用户名") + public R home() { + return R.data(manageWaybillService.home()); + } + + @GetMapping("/list") + @ApiOperationSupport(order = 3) + @Operation(summary = "运单分页列表", description = "当前组织运单;status:0待接单/1运输中/2已完成;exception:exception/normal;transportType:common/load") + public R> list( + @Parameter(description = "当前页") @RequestParam(required = false) Integer current, + @Parameter(description = "每页条数") @RequestParam(required = false) Integer size, + @Parameter(description = "关键字:运单号/司机/车牌") @RequestParam(required = false) String keyword, + @Parameter(description = "状态:0待接单/1运输中/2已完成,不传为全部") @RequestParam(required = false) String status, + @Parameter(description = "异常:exception有异常/normal无异常") @RequestParam(required = false) String exception, + @Parameter(description = "运输组织:common普通/load配载") @RequestParam(required = false) String transportType, + @Parameter(description = "创建日起 YYYY-MM-DD") @RequestParam(required = false) String startDate, + @Parameter(description = "创建日止 YYYY-MM-DD") @RequestParam(required = false) String endDate) { + return R.data(manageWaybillService.pageList( + current, size, keyword, status, exception, transportType, startDate, endDate)); + } + + @GetMapping("/detail") + @ApiOperationSupport(order = 4) + @Operation(summary = "运单详情", description = "调度端查看运单详情(含 punchNodes / enrouteRecords),不校验司机归属与组织;字段对齐小程序 pages/waybill/detail") + public R detail( + @Parameter(description = "运单ID", required = true) @RequestParam Long id) { + return R.data(manageWaybillService.detail(id)); + } + + @GetMapping("/pending") + @ApiOperationSupport(order = 5) + @Operation(summary = "待处理运单", description = "待接单/运输中;needReassign=true 仅司机已拒单") + public R> pending( + @Parameter(description = "当前页") @RequestParam(required = false) Integer current, + @Parameter(description = "每页条数") @RequestParam(required = false) Integer size, + @Parameter(description = "关键字:运单号/司机/车牌") @RequestParam(required = false) String keyword, + @Parameter(description = "是否需重新派单") @RequestParam(required = false) Boolean needReassign) { + return R.data(manageWaybillService.pendingList(current, size, keyword, needReassign)); + } + + @PostMapping("/reassign") + @ApiOperationSupport(order = 6) + @Operation(summary = "重新派单", description = "小程序调度端:跳过组织校验,仅需登录态;传入运单ID及新司机、手机号、车牌") + public R reassign(@RequestBody Waybill waybill) { + return R.status(manageWaybillService.reassign( + waybill.getId(), + waybill.getDriverId(), + waybill.getDriverName(), + waybill.getDriverPhone(), + waybill.getVehicleNo())); + } + + @GetMapping("/driver-search") + @ApiOperationSupport(order = 7) + @Operation(summary = "搜索司机", description = "按姓名/手机号模糊搜索,供重新派单选用") + public R> driverSearch( + @Parameter(description = "关键字") @RequestParam(required = false) String keyword) { + return R.data(manageWaybillService.searchDrivers(keyword)); + } + + @GetMapping("/vehicle-search") + @ApiOperationSupport(order = 8) + @Operation(summary = "搜索车牌", description = "按车牌模糊搜索(来自司机绑定车牌)") + public R> vehicleSearch( + @Parameter(description = "关键字") @RequestParam(required = false) String keyword) { + return R.data(manageWaybillService.searchVehicles(keyword)); + } + +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IDriverWaybillService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IDriverWaybillService.java index f9e804dd..be434257 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IDriverWaybillService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IDriverWaybillService.java @@ -68,6 +68,12 @@ public interface IDriverWaybillService { */ DriverWaybillCardVO detail(Long id); + /** + * 按运单ID组装详情打卡数据(punchNodes / enrouteRecords),不校验当前登录人是否为该司机。 + * 供调度端 manage/detail 复用。 + */ + DriverWaybillCardVO detailPunchSnapshot(Long id); + /** * 司机确认接单:过程配置要求接单且尚未接单时,写入接单记录并将运单改为进行中。 */ diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IManageWaybillService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IManageWaybillService.java new file mode 100644 index 00000000..08bd411c --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IManageWaybillService.java @@ -0,0 +1,90 @@ +/** + * 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. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import org.springblade.transport.pojo.vo.AdminDriverOptionVO; +import org.springblade.transport.pojo.vo.AdminHomeStatsVO; +import org.springblade.transport.pojo.vo.AdminHomeVO; +import org.springblade.transport.pojo.vo.AdminVehicleOptionVO; +import org.springblade.transport.pojo.vo.AdminWaybillCardVO; +import org.springblade.transport.pojo.vo.AdminWaybillDetailVO; + +import java.util.List; + +/** + * 调度端(小程序管理端)运单首页服务 + */ +public interface IManageWaybillService { + + /** + * 运单状态统计:运输中 / 待接单 / 在途异常 / 已完成 + */ + AdminHomeStatsVO stats(); + + /** + * 首页聚合:统计 + 角标 + 待处理事项(异常处置≠已完成)+ 用户名 + */ + AdminHomeVO home(); + + /** + * 调度端运单分页列表 + * + * @param current 页码 + * @param size 每页条数 + * @param keyword 运单号/司机/车牌 + * @param status 0待接单/1运输中/2已完成,空=全部 + * @param exception exception有异常 / normal无异常 / 空=全部 + * @param transportType common普通 / load配载 / 空=全部 + * @param startDate 创建日起 YYYY-MM-DD + * @param endDate 创建日止 YYYY-MM-DD + */ + IPage pageList(Integer current, Integer size, String keyword, String status, + String exception, String transportType, String startDate, String endDate); + + /** + * 调度端运单详情(不校验司机归属) + */ + AdminWaybillDetailVO detail(Long id); + + /** + * 待处理运单(待接单 / 运输中;可筛需重新派单) + */ + IPage pendingList(Integer current, Integer size, String keyword, Boolean needReassign); + + /** + * 重新派单:跳过管理端部门校验,仅需登录态(司机、手机号、车牌) + */ + boolean reassign(Long id, Long driverId, String driverName, String driverPhone, String vehicleNo); + + /** + * 搜索司机(姓名/手机号) + */ + List searchDrivers(String keyword); + + /** + * 搜索车牌(来自司机绑定车牌) + */ + List searchVehicles(String keyword); + +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillService.java index e4f77e6d..bf3dccf6 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillService.java @@ -60,6 +60,12 @@ public interface IWaybillService extends BaseService { boolean maintainMileage(WaybillMileageRequest request); boolean cancel(Long id); boolean reassign(Waybill waybill); + + /** + * 小程序调度端重新派单:跳过管理端部门校验,其余逻辑与 {@link #reassign(Waybill)} 一致。 + */ + boolean reassignWithoutDeptCheck(Waybill waybill); + boolean complete(Long id); /** diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverWaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverWaybillServiceImpl.java index ad1cbc17..2181abcf 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverWaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/DriverWaybillServiceImpl.java @@ -225,6 +225,18 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { return toCard(normalizeAcceptStatus(waybill), true); } + @Override + public DriverWaybillCardVO detailPunchSnapshot(Long id) { + if (id == null) { + throw new ServiceException("运单ID不能为空"); + } + Waybill waybill = waybillService.getById(id); + if (waybill == null || Objects.equals(waybill.getIsDeleted(), 1)) { + throw new ServiceException("运单不存在"); + } + return toCard(normalizeAcceptStatus(waybill), true); + } + @Override @Transactional(rollbackFor = Exception.class) public DriverEnrouteRecordVO submitEnroute(EnrouteSubmitDTO dto) { @@ -610,6 +622,19 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { card.setAcceptStatus(waybill.getDriverAcceptStatus()); card.setRejectReason(waybill.getDriverRejectReason()); + // 详情页字段(列表也可带上,体积很小) + String cargoName = Func.toStr(waybill.getCargoName(), ""); + String weightText = card.getWeight(); + card.setCargoName(cargoName); + card.setPickupAddress(card.getFromAddress()); + card.setUnloadAddress(card.getToAddress()); + card.setCargoQuantity(weightText); + card.setTotalWeight(weightText); + card.setTransportType(toTransportTypeLabel(waybill.getTransportType())); + card.setPlanShipTime(formatLocalDateYmd(waybill.getEstimatedStartTime())); + card.setPlanFinishTime(formatLocalDateYmd(waybill.getEstimatedEndTime())); + card.setRemark(Func.toStr(waybill.getRemark(), "")); + if (withEnrouteRecords) { Date lastPunchAt = findLastPunchTime(waybill.getId()); WaybillProcessSupport.TransitCheckinDecision transit = WaybillProcessSupport.evaluateTransitCheckin( @@ -623,6 +648,8 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { card.setTransitTimeEnd(transit.timeEnd()); card.setEnrouteRecords(listEnrouteRecords(waybill.getId())); card.setPunchNodes(buildPunchNodes(waybill, transit, processJson)); + card.setRoutePoints(buildSimpleRoutePoints(waybill)); + card.setProcessJson(processJson); } else { // 列表/首页:只解析过程配置是否启用在途打卡,不做频次/时段与落库查询 boolean punchEnabled = WaybillProcessSupport.isTransitPunchEnabled(processJson); @@ -634,18 +661,61 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService { return card; } + private List buildSimpleRoutePoints(Waybill waybill) { + DriverWaybillCardVO.DriverRoutePointVO load = new DriverWaybillCardVO.DriverRoutePointVO(); + load.setName(Func.toStr(waybill.getDepartureName(), "装货点")); + load.setAddress(Func.toStr(waybill.getDepartureAddress(), load.getName())); + load.setStatus("pending"); + DriverWaybillCardVO.DriverRoutePointVO unload = new DriverWaybillCardVO.DriverRoutePointVO(); + unload.setName(Func.toStr(waybill.getArrivalName(), "卸货点")); + unload.setAddress(Func.toStr(waybill.getArrivalAddress(), unload.getName())); + unload.setStatus("pending"); + return List.of(load, unload); + } + + private String toTransportTypeLabel(String transportType) { + if (Func.isBlank(transportType)) { + return ""; + } + String t = transportType.trim().toLowerCase(); + return switch (t) { + case "road", "gl" -> "公路运输"; + case "railway", "rail" -> "铁路运输"; + case "river", "water", "waterway" -> "水路运输"; + case "air", "aviation" -> "航空运输"; + default -> transportType; + }; + } + + private String formatLocalDateYmd(LocalDate date) { + if (date == null) { + return ""; + } + return date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + } + /** - * 动态获取项目启用中的过程配置节点 JSON;无则回退运单快照 processJson。 + * 优先用项目启用中的过程配置;若动态配置无打卡节点,回退运单快照 processJson, + * 避免项目配置改坏后司机端打卡页空白。 */ private String resolveProcessJson(Waybill waybill) { if (waybill == null) { return null; } + String snapshot = waybill.getProcessJson(); String live = loadLiveProcessConfigJson(waybill.getProjectId()); if (Func.isNotEmpty(live)) { + if (!WaybillProcessSupport.listDriverPunchNodes(live).isEmpty()) { + return live; + } + // 动态配置存在但无可打卡节点:仍回退快照 + if (Func.isNotEmpty(snapshot) + && !WaybillProcessSupport.listDriverPunchNodes(snapshot).isEmpty()) { + return snapshot; + } return live; } - return waybill.getProcessJson(); + return snapshot; } private String loadLiveProcessConfigJson(Long projectId) { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ManageWaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ManageWaybillServiceImpl.java new file mode 100644 index 00000000..5854689f --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ManageWaybillServiceImpl.java @@ -0,0 +1,642 @@ +/** + * 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. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import org.springblade.core.secure.utils.AuthUtil; +import org.springblade.core.tool.utils.DateUtil; +import org.springblade.core.tool.utils.Func; +import org.springblade.system.cache.UserCache; +import org.springblade.transport.pojo.entity.Driver; +import org.springblade.transport.pojo.entity.ExceptionDisposal; +import org.springblade.transport.pojo.entity.RiskDisposal; +import org.springblade.transport.pojo.entity.Waybill; +import org.springblade.transport.pojo.vo.AdminDriverOptionVO; +import org.springblade.transport.pojo.vo.AdminHomeBadgesVO; +import org.springblade.transport.pojo.vo.AdminHomeStatsVO; +import org.springblade.transport.pojo.vo.AdminHomeVO; +import org.springblade.transport.pojo.vo.AdminTodoItemVO; +import org.springblade.transport.pojo.vo.AdminVehicleOptionVO; +import org.springblade.transport.pojo.vo.AdminWaybillCardVO; +import org.springblade.transport.pojo.vo.AdminWaybillDetailVO; +import org.springblade.transport.pojo.vo.DriverWaybillCardVO; +import org.springblade.transport.service.IDriverService; +import org.springblade.transport.service.IDriverWaybillService; +import org.springblade.transport.service.IExceptionDisposalService; +import org.springblade.transport.service.IManageWaybillService; +import org.springblade.transport.service.IRiskDisposalService; +import org.springblade.transport.service.IWaybillService; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 调度端:运单状态统计 + 异常/风险角标 + 待处理事项 + 运单列表 + *

+ * 小程序调度账号(如「小程序管理」)组织常与运单业务组织不一致,故不做 dept 过滤, + * 仅依赖租户隔离,口径接近后台 {@code /waybill-manage/list?allDept=1}。 + */ +@Service +@RequiredArgsConstructor +public class ManageWaybillServiceImpl implements IManageWaybillService { + + private static final String STATUS_PENDING = "pending"; + private static final String STATUS_RUNNING = "running"; + private static final String STATUS_COMPLETED = "completed"; + private static final String STATUS_CANCELLED = "cancelled"; + private static final String ACCEPT_REJECTED = "rejected"; + + private static final String DISPOSAL_PENDING = "pending"; + private static final String DISPOSAL_PROCESSING = "processing"; + private static final String RISK_PENDING = "pending"; + + private static final String EXCEPTION_YES = "exception"; + private static final String EXCEPTION_NO = "normal"; + private static final String TRANSPORT_COMMON = "common"; + private static final String TRANSPORT_LOAD = "load"; + + private static final int FEED_LIMIT = 20; + private static final int DEFAULT_PAGE_SIZE = 10; + private static final int MAX_PAGE_SIZE = 50; + + private static final DateTimeFormatter DATE_MD = DateTimeFormatter.ofPattern("MM-dd"); + private static final DateTimeFormatter DATE_YMD = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + private final IWaybillService waybillService; + private final IDriverService driverService; + private final IExceptionDisposalService exceptionDisposalService; + private final IRiskDisposalService riskDisposalService; + private final IDriverWaybillService driverWaybillService; + + @Override + public AdminHomeStatsVO stats() { + AdminHomeStatsVO vo = new AdminHomeStatsVO(); + vo.setPendingAccept(countWaybillByStatus(STATUS_PENDING)); + vo.setTransporting(countWaybillByStatus(STATUS_RUNNING)); + vo.setCompleted(countWaybillByStatus(STATUS_COMPLETED)); + vo.setException(countIncompleteExceptions()); + return vo; + } + + @Override + public AdminHomeVO home() { + AdminHomeVO home = new AdminHomeVO(); + home.setUserName(resolveUserName()); + home.setStats(stats()); + + AdminHomeBadgesVO badges = new AdminHomeBadgesVO(); + badges.setException(home.getStats().getException()); + badges.setRisk(countPendingRisks()); + home.setBadges(badges); + home.setFeed(buildExceptionFeed()); + return home; + } + + @Override + public IPage pageList(Integer current, Integer size, String keyword, String status, + String exception, String transportType, String startDate, String endDate) { + int pageNo = current == null || current < 1 ? 1 : current; + int pageSize = size == null || size < 1 ? DEFAULT_PAGE_SIZE : Math.min(size, MAX_PAGE_SIZE); + + Set exceptionWaybillIds = loadIncompleteExceptionWaybillIds(); + if (EXCEPTION_YES.equals(exception) && exceptionWaybillIds.isEmpty()) { + Page emptyVo = new Page<>(pageNo, pageSize, 0); + emptyVo.setRecords(List.of()); + return emptyVo; + } + + LambdaQueryWrapper wrapper = scopedWaybillQuery(); + applyStatusFilter(wrapper, status); + applyExceptionFilter(wrapper, exception, exceptionWaybillIds); + applyTransportTypeFilter(wrapper, transportType); + applyKeywordFilter(wrapper, keyword); + applyCreateTimeFilter(wrapper, startDate, endDate); + wrapper.orderByDesc(Waybill::getCreateTime); + + IPage entityPage = waybillService.page(new Page<>(pageNo, pageSize), wrapper); + List pageIds = entityPage.getRecords().stream() + .map(Waybill::getId) + .filter(Objects::nonNull) + .toList(); + Set pageExceptionIds = pageIds.isEmpty() + ? Collections.emptySet() + : exceptionWaybillIds.stream().filter(pageIds::contains).collect(Collectors.toSet()); + + Page voPage = new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal()); + voPage.setRecords(entityPage.getRecords().stream() + .map(w -> toCard(w, pageExceptionIds.contains(w.getId()))) + .toList()); + return voPage; + } + + @Override + public AdminWaybillDetailVO detail(Long id) { + if (id == null) { + throw new org.springblade.core.log.exception.ServiceException("运单ID不能为空"); + } + Waybill waybill = waybillService.getById(id); + if (waybill == null || Objects.equals(waybill.getIsDeleted(), 1)) { + throw new org.springblade.core.log.exception.ServiceException("运单不存在"); + } + boolean hasException = false; + Long exceptionId = null; + ExceptionDisposal latest = exceptionDisposalService.getOne(Wrappers.lambdaQuery() + .eq(ExceptionDisposal::getWaybillId, id) + .in(ExceptionDisposal::getDisposalStatus, DISPOSAL_PENDING, DISPOSAL_PROCESSING) + .orderByDesc(ExceptionDisposal::getReportTime) + .orderByDesc(ExceptionDisposal::getCreateTime) + .last("LIMIT 1")); + if (latest != null) { + hasException = true; + exceptionId = latest.getId(); + } + return toDetail(waybill, hasException, exceptionId); + } + + @Override + public IPage pendingList(Integer current, Integer size, String keyword, Boolean needReassign) { + int pageNo = current == null || current < 1 ? 1 : current; + int pageSize = size == null || size < 1 ? DEFAULT_PAGE_SIZE : Math.min(size, MAX_PAGE_SIZE); + + LambdaQueryWrapper wrapper = scopedWaybillQuery() + .in(Waybill::getBusinessStatus, STATUS_PENDING, STATUS_RUNNING); + if (Boolean.TRUE.equals(needReassign)) { + wrapper.eq(Waybill::getDriverAcceptStatus, ACCEPT_REJECTED); + } else if (Boolean.FALSE.equals(needReassign)) { + wrapper.and(w -> w.isNull(Waybill::getDriverAcceptStatus) + .or().ne(Waybill::getDriverAcceptStatus, ACCEPT_REJECTED)); + } + applyKeywordFilter(wrapper, keyword); + wrapper.orderByDesc(Waybill::getUpdateTime).orderByDesc(Waybill::getCreateTime); + + IPage entityPage = waybillService.page(new Page<>(pageNo, pageSize), wrapper); + Set exceptionWaybillIds = loadIncompleteExceptionWaybillIds(); + List pageIds = entityPage.getRecords().stream() + .map(Waybill::getId) + .filter(Objects::nonNull) + .toList(); + Set pageExceptionIds = pageIds.isEmpty() + ? Collections.emptySet() + : exceptionWaybillIds.stream().filter(pageIds::contains).collect(Collectors.toSet()); + + Page voPage = new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal()); + voPage.setRecords(entityPage.getRecords().stream() + .map(w -> toCard(w, pageExceptionIds.contains(w.getId()))) + .toList()); + return voPage; + } + + private AdminWaybillDetailVO toDetail(Waybill waybill, boolean hasException, Long exceptionId) { + AdminWaybillDetailVO detail = new AdminWaybillDetailVO(); + detail.setId(waybill.getId()); + detail.setWaybillNo(waybill.getWaybillNo()); + detail.setStatus(toAppStatus(waybill.getBusinessStatus())); + + String mode = Func.toStr(waybill.getTransportType(), ""); + detail.setTransportMode(mode); + detail.setTransportType(toTransportTypeLabel(mode)); + detail.setTransportOrgType(Func.isNotBlank(waybill.getLoadingNo()) ? TRANSPORT_LOAD : TRANSPORT_COMMON); + + detail.setFromName(formatPlaceName(waybill.getDepartureName(), mode)); + detail.setToName(formatPlaceName(waybill.getArrivalName(), mode)); + String fromAddr = Func.toStr(waybill.getDepartureAddress(), Func.toStr(waybill.getDepartureName(), "")); + String toAddr = Func.toStr(waybill.getArrivalAddress(), Func.toStr(waybill.getArrivalName(), "")); + detail.setFromAddress(fromAddr); + detail.setToAddress(toAddr); + detail.setPickupAddress(fromAddr); + detail.setUnloadAddress(toAddr); + + String cargo = Func.toStr(waybill.getCargoName(), ""); + String weight = formatWeight(waybill.getQuantity(), waybill.getQuantityUnit()); + detail.setCargoName(cargo); + detail.setCargoQuantity(weight); + detail.setWeight(weight); + detail.setTotalWeight(weight); + + detail.setPlanShipTime(formatLocalDate(waybill.getEstimatedStartTime())); + detail.setPlanFinishTime(formatLocalDate(waybill.getEstimatedEndTime())); + detail.setCarrierName(Func.toStr(waybill.getCarrierName(), "")); + detail.setDriverName(Func.toStr(waybill.getDriverName(), "")); + detail.setDriverPhone(Func.toStr(waybill.getDriverPhone(), "")); + detail.setVehicleNo(Func.toStr(waybill.getVehicleNo(), "")); + detail.setRemark(Func.toStr(waybill.getRemark(), "")); + detail.setHasException(hasException); + detail.setExceptionId(exceptionId); + detail.setNeedReassign(ACCEPT_REJECTED.equals(waybill.getDriverAcceptStatus())); + detail.setAcceptStatus(Func.toStr(waybill.getDriverAcceptStatus(), "")); + detail.setRejectReason(Func.toStr(waybill.getDriverRejectReason(), "")); + detail.setDriverId(waybill.getDriverId()); + + AdminWaybillDetailVO.AdminRoutePointVO load = new AdminWaybillDetailVO.AdminRoutePointVO(); + load.setName(Func.toStr(waybill.getDepartureName(), "装货点")); + load.setAddress(fromAddr); + load.setStatus("pending"); + AdminWaybillDetailVO.AdminRoutePointVO unload = new AdminWaybillDetailVO.AdminRoutePointVO(); + unload.setName(Func.toStr(waybill.getArrivalName(), "卸货点")); + unload.setAddress(toAddr); + unload.setStatus("pending"); + detail.setRoutePoints(List.of(load, unload)); + + // 复用司机端打卡组装:过程节点 + 途打卡记录(调度端只读展示) + DriverWaybillCardVO punch = driverWaybillService.detailPunchSnapshot(waybill.getId()); + if (punch != null) { + detail.setPunchNodes(punch.getPunchNodes()); + detail.setEnrouteRecords(punch.getEnrouteRecords()); + if (punch.getRoutePoints() != null && !punch.getRoutePoints().isEmpty()) { + detail.setRoutePoints(punch.getRoutePoints().stream().map(p -> { + AdminWaybillDetailVO.AdminRoutePointVO rp = new AdminWaybillDetailVO.AdminRoutePointVO(); + rp.setName(p.getName()); + rp.setAddress(p.getAddress()); + rp.setStatus(p.getStatus()); + return rp; + }).toList()); + } + } + return detail; + } + + @Override + public boolean reassign(Long id, Long driverId, String driverName, String driverPhone, String vehicleNo) { + Waybill request = new Waybill(); + request.setId(id); + request.setDriverId(driverId); + request.setDriverName(driverName); + request.setDriverPhone(driverPhone); + request.setVehicleNo(vehicleNo); + return waybillService.reassignWithoutDeptCheck(request); + } + + @Override + public List searchDrivers(String keyword) { + String key = Func.toStr(keyword, "").trim(); + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() + .eq(Driver::getIsDeleted, 0) + .orderByDesc(Driver::getUpdateTime) + .last("LIMIT 20"); + if (Func.isNotBlank(key)) { + wrapper.and(w -> w.like(Driver::getDriverName, key).or().like(Driver::getMobile, key)); + } + return driverService.list(wrapper).stream().map(d -> { + AdminDriverOptionVO vo = new AdminDriverOptionVO(); + vo.setId(d.getId()); + vo.setName(Func.toStr(d.getDriverName(), "")); + vo.setPhone(Func.toStr(d.getMobile(), "")); + vo.setVehicleNo(Func.toStr(d.getDrivingVehicle(), "")); + return vo; + }).toList(); + } + + @Override + public List searchVehicles(String keyword) { + String key = Func.toStr(keyword, "").trim(); + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() + .eq(Driver::getIsDeleted, 0) + .isNotNull(Driver::getDrivingVehicle) + .ne(Driver::getDrivingVehicle, "") + .orderByDesc(Driver::getUpdateTime) + .last("LIMIT 30"); + if (Func.isNotBlank(key)) { + wrapper.like(Driver::getDrivingVehicle, key); + } + java.util.LinkedHashMap map = new java.util.LinkedHashMap<>(); + for (Driver d : driverService.list(wrapper)) { + String plate = Func.toStr(d.getDrivingVehicle(), "").trim(); + if (Func.isBlank(plate) || map.containsKey(plate)) { + continue; + } + AdminVehicleOptionVO vo = new AdminVehicleOptionVO(); + vo.setVehicleNo(plate); + vo.setDriverName(Func.toStr(d.getDriverName(), "")); + map.put(plate, vo); + } + return new java.util.ArrayList<>(map.values()); + } + + /** 运输方式字典值 → 展示文案 */ + private String toTransportTypeLabel(String transportType) { + if (Func.isBlank(transportType)) { + return ""; + } + String t = transportType.trim().toLowerCase(); + return switch (t) { + case "road", "gl" -> "公路运输"; + case "railway", "rail" -> "铁路运输"; + case "river", "water", "waterway" -> "水路运输"; + case "air", "aviation" -> "航空运输"; + default -> transportType; + }; + } + + private long countWaybillByStatus(String status) { + return waybillService.count(Wrappers.lambdaQuery() + .eq(Waybill::getBusinessStatus, status)); + } + + /** + * 小程序调度端不做组织过滤。 + * 「小程序管理」等账号 JWT/档案 dept 常与运单业务组织不一致,按 dept 过滤会导致统计全 0; + * 与后台 allDept=1 一致,仅依赖租户隔离(MyBatis-Plus TenantLine)。 + */ + private LambdaQueryWrapper scopedWaybillQuery() { + return Wrappers.lambdaQuery(); + } + + private void applyStatusFilter(LambdaQueryWrapper wrapper, String status) { + String businessStatus = toBusinessStatus(status); + if (Func.isNotBlank(businessStatus)) { + wrapper.eq(Waybill::getBusinessStatus, businessStatus); + } + } + + private void applyExceptionFilter(LambdaQueryWrapper wrapper, String exception, Set exceptionWaybillIds) { + if (EXCEPTION_YES.equals(exception)) { + wrapper.in(Waybill::getId, exceptionWaybillIds); + } else if (EXCEPTION_NO.equals(exception) && !exceptionWaybillIds.isEmpty()) { + wrapper.notIn(Waybill::getId, exceptionWaybillIds); + } + } + + private void applyTransportTypeFilter(LambdaQueryWrapper wrapper, String transportType) { + if (TRANSPORT_LOAD.equals(transportType)) { + wrapper.isNotNull(Waybill::getLoadingNo).ne(Waybill::getLoadingNo, ""); + } else if (TRANSPORT_COMMON.equals(transportType)) { + wrapper.and(w -> w.isNull(Waybill::getLoadingNo).or().eq(Waybill::getLoadingNo, "")); + } + } + + private void applyKeywordFilter(LambdaQueryWrapper wrapper, String keyword) { + if (Func.isBlank(keyword)) { + return; + } + String key = keyword.trim(); + wrapper.and(w -> w.like(Waybill::getWaybillNo, key) + .or().like(Waybill::getDriverName, key) + .or().like(Waybill::getVehicleNo, key)); + } + + private void applyCreateTimeFilter(LambdaQueryWrapper wrapper, String startDate, String endDate) { + if (Func.isNotBlank(startDate)) { + Date start = DateUtil.parse(startDate.trim() + " 00:00:00", DateUtil.PATTERN_DATETIME); + if (start != null) { + wrapper.ge(Waybill::getCreateTime, start); + } + } + if (Func.isNotBlank(endDate)) { + Date end = DateUtil.parse(endDate.trim() + " 23:59:59", DateUtil.PATTERN_DATETIME); + if (end != null) { + wrapper.le(Waybill::getCreateTime, end); + } + } + } + + /** 小程序 status → 后端 businessStatus */ + private String toBusinessStatus(String status) { + if (Func.isBlank(status)) { + return null; + } + return switch (status.trim()) { + case "0", STATUS_PENDING -> STATUS_PENDING; + case "1", STATUS_RUNNING, "transporting", "doing" -> STATUS_RUNNING; + case "2", STATUS_COMPLETED, "done" -> STATUS_COMPLETED; + case "3", STATUS_CANCELLED -> STATUS_CANCELLED; + default -> null; + }; + } + + private Set loadIncompleteExceptionWaybillIds() { + List list = exceptionDisposalService.list(Wrappers.lambdaQuery() + .select(ExceptionDisposal::getWaybillId) + .in(ExceptionDisposal::getDisposalStatus, DISPOSAL_PENDING, DISPOSAL_PROCESSING) + .isNotNull(ExceptionDisposal::getWaybillId)); + Set ids = new HashSet<>(); + for (ExceptionDisposal item : list) { + if (item.getWaybillId() != null) { + ids.add(item.getWaybillId()); + } + } + return ids; + } + + private AdminWaybillCardVO toCard(Waybill waybill, boolean hasException) { + AdminWaybillCardVO card = new AdminWaybillCardVO(); + card.setId(waybill.getId()); + card.setWaybillNo(waybill.getWaybillNo()); + String mode = Func.toStr(waybill.getTransportType(), ""); + card.setTransportMode(mode); + card.setFromName(formatPlaceName(waybill.getDepartureName(), mode)); + card.setToName(formatPlaceName(waybill.getArrivalName(), mode)); + card.setCargo(Func.toStr(waybill.getCargoName(), "")); + card.setWeight(formatWeight(waybill.getQuantity(), waybill.getQuantityUnit())); + card.setPlanTime(formatLocalDate(waybill.getEstimatedStartTime())); + card.setPlanTimeEnd(formatLocalDate(waybill.getEstimatedEndTime())); + card.setStatus(toAppStatus(waybill.getBusinessStatus())); + card.setCarrierName(Func.toStr(waybill.getCarrierName(), "")); + card.setDriverName(Func.toStr(waybill.getDriverName(), "")); + card.setVehicleNo(Func.toStr(waybill.getVehicleNo(), "")); + card.setHasException(hasException); + card.setTransportType(Func.isNotBlank(waybill.getLoadingNo()) ? TRANSPORT_LOAD : TRANSPORT_COMMON); + card.setCreateTime(formatDateTime(waybill.getCreateTime())); + card.setNeedReassign(ACCEPT_REJECTED.equals(waybill.getDriverAcceptStatus())); + card.setBuyerPaid(false); + return card; + } + + /** + * 公路运输:起/终仅展示市县(去掉省/自治区);其它运输方式原样返回。 + */ + private String formatPlaceName(String name, String transportType) { + String raw = Func.toStr(name, "").trim(); + if (Func.isBlank(raw) || !isRoadTransport(transportType)) { + return raw; + } + return toCityCounty(raw); + } + + private boolean isRoadTransport(String transportType) { + if (Func.isBlank(transportType)) { + return false; + } + String t = transportType.trim().toLowerCase(); + return t.contains("road") || transportType.contains("公路") || transportType.contains("道路") || "gl".equals(t); + } + + /** 去掉省级前缀,保留「市 + 区/县/旗」 */ + private String toCityCounty(String name) { + String s = name.replaceFirst("^.+?(省|自治区|特别行政区)", ""); + if (Func.isBlank(s)) { + s = name; + } + java.util.regex.Matcher city = java.util.regex.Pattern + .compile("^(.+?市)(.+?(?:区|县|旗|市))?") + .matcher(s); + if (city.find()) { + return Func.toStr(city.group(1), "") + Func.toStr(city.group(2), ""); + } + java.util.regex.Matcher prefecture = java.util.regex.Pattern + .compile("^(.+?(?:州|盟|地区))(.+?(?:区|县|旗|市))?") + .matcher(s); + if (prefecture.find()) { + return Func.toStr(prefecture.group(1), "") + Func.toStr(prefecture.group(2), ""); + } + return s; + } + + private Integer toAppStatus(String businessStatus) { + if (Func.isBlank(businessStatus)) { + return null; + } + return switch (businessStatus) { + case STATUS_PENDING, "waiting_dispatch", "dispatching" -> 0; + case STATUS_RUNNING -> 1; + case STATUS_COMPLETED -> 2; + case STATUS_CANCELLED -> 3; + default -> null; + }; + } + + private String formatWeight(BigDecimal quantity, String unit) { + if (quantity == null) { + return ""; + } + String qty = quantity.stripTrailingZeros().toPlainString(); + return Func.isBlank(unit) ? qty : qty + unit; + } + + private String formatLocalDate(LocalDate date) { + if (date == null) { + return ""; + } + return date.format(DATE_YMD); + } + + private String formatDateTime(Date date) { + if (date == null) { + return ""; + } + return DateUtil.format(date, DateUtil.PATTERN_DATETIME); + } + + private long countIncompleteExceptions() { + return exceptionDisposalService.count(Wrappers.lambdaQuery() + .in(ExceptionDisposal::getDisposalStatus, DISPOSAL_PENDING, DISPOSAL_PROCESSING)); + } + + private long countPendingRisks() { + return riskDisposalService.count(Wrappers.lambdaQuery() + .eq(RiskDisposal::getDisposalStatus, RISK_PENDING)); + } + + private List buildExceptionFeed() { + List list = exceptionDisposalService.list(Wrappers.lambdaQuery() + .in(ExceptionDisposal::getDisposalStatus, DISPOSAL_PENDING, DISPOSAL_PROCESSING) + .orderByDesc(ExceptionDisposal::getReportTime) + .last("LIMIT " + FEED_LIMIT)); + return list.stream().map(this::toTodoItem).collect(Collectors.toList()); + } + + private AdminTodoItemVO toTodoItem(ExceptionDisposal disposal) { + AdminTodoItemVO item = new AdminTodoItemVO(); + item.setId(disposal.getId()); + item.setType("exception"); + item.setTitle("异常待处置"); + item.setTimeAgo(formatTimeAgo(disposal.getReportTime() != null + ? disposal.getReportTime() + : toLocalDateTime(disposal.getCreateTime()))); + item.setDesc(buildExceptionDesc(disposal)); + item.setWaybillNo(Func.toStr(disposal.getWaybillNo(), "")); + item.setActionLabel("立即处置"); + String status = Func.toStr(disposal.getDisposalStatus(), DISPOSAL_PENDING); + item.setTargetUrl("/subpackages/admin/exception?status=" + status); + return item; + } + + private String buildExceptionDesc(ExceptionDisposal disposal) { + String reporter = Func.toStr(disposal.getReporterName(), "司机"); + String type = Func.toStr(disposal.getExceptionType(), "异常"); + String reason = Func.isNotBlank(disposal.getExceptionReason()) + ? disposal.getExceptionReason() + : Func.toStr(disposal.getReportDescription(), ""); + if (Func.isBlank(reason)) { + return reporter + "上报" + type; + } + String text = reporter + "上报" + type + ":" + reason.trim(); + return text.length() > 80 ? text.substring(0, 80) + "…" : text; + } + + private String resolveUserName() { + String realName = UserCache.getUserRealName(AuthUtil.getUserId()); + if (Func.isNotBlank(realName)) { + return realName; + } + return Func.toStr(AuthUtil.getUserName(), ""); + } + + private String formatTimeAgo(LocalDateTime time) { + if (time == null) { + return ""; + } + Duration duration = Duration.between(time, LocalDateTime.now()); + if (duration.isNegative()) { + duration = Duration.ZERO; + } + long minutes = duration.toMinutes(); + if (minutes < 1) { + return "刚刚"; + } + if (minutes < 60) { + return minutes + "分钟"; + } + long hours = duration.toHours(); + if (hours < 24) { + return hours + "小时"; + } + long days = duration.toDays(); + if (days < 30) { + return days + "天"; + } + return time.format(DATE_MD); + } + + private LocalDateTime toLocalDateTime(Date date) { + if (date == null) { + return null; + } + return date.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime(); + } + +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java index 14e3c3bf..b837dd8c 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java @@ -819,10 +819,20 @@ public class WaybillServiceImpl extends BaseServiceImpl @Override @Transactional(rollbackFor = Exception.class) public boolean reassign(Waybill request) { + return doReassign(request, true); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean reassignWithoutDeptCheck(Waybill request) { + return doReassign(request, false); + } + + private boolean doReassign(Waybill request, boolean checkDept) { if (request == null || Func.isEmpty(request.getId())) { throw new ServiceException("运单ID不能为空"); } - Waybill waybill = loadEditable(request.getId(), true); + Waybill waybill = loadEditable(request.getId(), checkDept); assertNotLoaded(waybill); if (!"pending".equals(waybill.getBusinessStatus()) && !"running".equals(waybill.getBusinessStatus())) { throw new ServiceException("仅待执行/进行中运单允许重新派单"); diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAFeignClientConfig.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAFeignClientConfig.java index c0b9cf4f..8917a2d5 100644 --- a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAFeignClientConfig.java +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAFeignClientConfig.java @@ -1,9 +1,13 @@ package org.springblade.thirdparty.oa.config; +import feign.Request; import feign.RequestInterceptor; import jakarta.annotation.Resource; +import org.springblade.core.tool.utils.StringUtil; import org.springframework.context.annotation.Bean; +import java.util.concurrent.TimeUnit; + /** * @author bfhuange * @since 2024/12/18 @@ -16,8 +20,23 @@ public class OAFeignClientConfig { public RequestInterceptor requestInterceptor() { return template -> { // 空实现屏蔽 全局拦截器 BladeFeignRequestInterceptor - String authorization=oaProperties.getAuthorization(); - template.header("Authorization",authorization); + template.header("Authorization", normalizeAuthorization(oaProperties.getAuthorization())); }; } + + @Bean + public Request.Options options() { + return new Request.Options(10, TimeUnit.SECONDS, 120, TimeUnit.SECONDS, true); + } + + private String normalizeAuthorization(String authorization) { + if (StringUtil.isBlank(authorization)) { + return authorization; + } + if (StringUtil.startsWithIgnoreCase(authorization, "Basic ") + || StringUtil.startsWithIgnoreCase(authorization, "Bearer ")) { + return authorization; + } + return "Basic " + authorization; + } } diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/constant/OAConvertConstant.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/constant/OAConvertConstant.java index 21827849..4f6027c0 100644 --- a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/constant/OAConvertConstant.java +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/constant/OAConvertConstant.java @@ -30,4 +30,8 @@ public class OAConvertConstant { * 根公司父id */ public static final Long ROOT_PARENT_ID = 0L; + /** + * 同步人员时挂载的顶级组织名称 + */ + public static final String ROOT_COMPANY_NAME = "桂物物流集团"; } diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/feign/IOAClient.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/feign/IOAClient.java index b5714166..9b36edd0 100644 --- a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/feign/IOAClient.java +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/feign/IOAClient.java @@ -42,6 +42,6 @@ public interface IOAClient { * @param param * @return */ - @PostMapping("${thirdParty.oa.queryPersonPageUrl:/api/hrm/resful/getHrmUserInfoWithPage}") + @PostMapping("${thirdParty.oa.queryPersonPageUrl:/gwzh/OA/OA_GET_USER_LIST}") OAResponse queryPersonPage(@RequestBody OASearch param); } diff --git a/blade-third-party-api/blade-wechat-api/pom.xml b/blade-third-party-api/blade-wechat-api/pom.xml new file mode 100644 index 00000000..7926d20e --- /dev/null +++ b/blade-third-party-api/blade-wechat-api/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + + org.springblade + blade-third-party-api + ${revision} + + + blade-wechat-api + ${project.artifactId} + jar + 微信小程序:code2session / 手机号 / openid + + + + org.springblade + blade-core-tool + + + org.springframework.boot + spring-boot-autoconfigure + + + diff --git a/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/config/ThirdPartyWechatAutoConfiguration.java b/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/config/ThirdPartyWechatAutoConfiguration.java new file mode 100644 index 00000000..b708fb25 --- /dev/null +++ b/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/config/ThirdPartyWechatAutoConfiguration.java @@ -0,0 +1,14 @@ +package org.springblade.thirdparty.wechat.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * 微信小程序第三方能力自动配置 + */ +@Configuration +@ComponentScan("org.springblade.thirdparty.wechat") +@EnableConfigurationProperties(WechatMiniProperties.class) +public class ThirdPartyWechatAutoConfiguration { +} diff --git a/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/config/WechatMiniProperties.java b/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/config/WechatMiniProperties.java new file mode 100644 index 00000000..8e38eedb --- /dev/null +++ b/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/config/WechatMiniProperties.java @@ -0,0 +1,28 @@ +package org.springblade.thirdparty.wechat.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 微信小程序配置(Nacos:thirdParty.wechat.mini) + */ +@Data +@ConfigurationProperties(prefix = "third-party.wechat.mini") +public class WechatMiniProperties { + + /** + * 小程序 AppId + */ + private String appId; + + /** + * 小程序 AppSecret + */ + private String appSecret; + + /** + * 微信 API 根地址 + */ + private String apiBase = "https://api.weixin.qq.com"; + +} diff --git a/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/constant/WechatMiniConstant.java b/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/constant/WechatMiniConstant.java new file mode 100644 index 00000000..cbc60604 --- /dev/null +++ b/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/constant/WechatMiniConstant.java @@ -0,0 +1,20 @@ +package org.springblade.thirdparty.wechat.constant; + +/** + * 微信小程序常量 + */ +public interface WechatMiniConstant { + + /** blade_user_oauth.source */ + String SOURCE = "WECHAT_MINI"; + + /** + * OAuth2 grant_type(对齐 BladeX OAuth2GranterConstant.WECHAT_APPLET) + */ + String GRANT_TYPE = "wechat_applet"; + + String JSCODE2SESSION_PATH = "/sns/jscode2session"; + String ACCESS_TOKEN_PATH = "/cgi-bin/token"; + String GET_PHONE_NUMBER_PATH = "/wxa/business/getuserphonenumber"; + +} diff --git a/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/exception/WechatMiniException.java b/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/exception/WechatMiniException.java new file mode 100644 index 00000000..a89c13db --- /dev/null +++ b/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/exception/WechatMiniException.java @@ -0,0 +1,16 @@ +package org.springblade.thirdparty.wechat.exception; + +/** + * 微信小程序调用异常 + */ +public class WechatMiniException extends RuntimeException { + + public WechatMiniException(String message) { + super(message); + } + + public WechatMiniException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/pojo/vo/WechatPhoneVO.java b/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/pojo/vo/WechatPhoneVO.java new file mode 100644 index 00000000..61434f7f --- /dev/null +++ b/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/pojo/vo/WechatPhoneVO.java @@ -0,0 +1,25 @@ +package org.springblade.thirdparty.wechat.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 微信手机号结果 + */ +@Data +public class WechatPhoneVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 不带区号的手机号 */ + private String phoneNumber; + + /** 带区号手机号 */ + private String purePhoneNumber; + + private String countryCode; + +} diff --git a/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/pojo/vo/WechatSessionVO.java b/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/pojo/vo/WechatSessionVO.java new file mode 100644 index 00000000..255b7b77 --- /dev/null +++ b/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/pojo/vo/WechatSessionVO.java @@ -0,0 +1,21 @@ +package org.springblade.thirdparty.wechat.pojo.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * jscode2session 结果 + */ +@Data +public class WechatSessionVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private String openid; + private String sessionKey; + private String unionid; + +} diff --git a/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/service/IWechatMiniService.java b/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/service/IWechatMiniService.java new file mode 100644 index 00000000..100713e5 --- /dev/null +++ b/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/service/IWechatMiniService.java @@ -0,0 +1,21 @@ +package org.springblade.thirdparty.wechat.service; + +import org.springblade.thirdparty.wechat.pojo.vo.WechatPhoneVO; +import org.springblade.thirdparty.wechat.pojo.vo.WechatSessionVO; + +/** + * 微信小程序能力:openid / 手机号 + */ +public interface IWechatMiniService { + + /** + * wx.login code → openid / session_key + */ + WechatSessionVO code2Session(String loginCode); + + /** + * getPhoneNumber 返回的 code → 手机号 + */ + WechatPhoneVO getPhoneNumber(String phoneCode); + +} diff --git a/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/service/impl/WechatMiniServiceImpl.java b/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/service/impl/WechatMiniServiceImpl.java new file mode 100644 index 00000000..b4535010 --- /dev/null +++ b/blade-third-party-api/blade-wechat-api/src/main/java/org/springblade/thirdparty/wechat/service/impl/WechatMiniServiceImpl.java @@ -0,0 +1,148 @@ +package org.springblade.thirdparty.wechat.service.impl; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.thirdparty.wechat.config.WechatMiniProperties; +import org.springblade.thirdparty.wechat.constant.WechatMiniConstant; +import org.springblade.thirdparty.wechat.exception.WechatMiniException; +import org.springblade.thirdparty.wechat.pojo.vo.WechatPhoneVO; +import org.springblade.thirdparty.wechat.pojo.vo.WechatSessionVO; +import org.springblade.thirdparty.wechat.service.IWechatMiniService; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +/** + * 微信小程序:code2session / getuserphonenumber + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class WechatMiniServiceImpl implements IWechatMiniService { + + /** access_token 提前 200 秒刷新 */ + private static final long TOKEN_REFRESH_AHEAD_MS = 200_000L; + + private final WechatMiniProperties properties; + + private volatile String cachedAccessToken; + private volatile long accessTokenExpireAt; + + @Override + public WechatSessionVO code2Session(String loginCode) { + assertConfigured(); + if (StringUtil.isBlank(loginCode)) { + throw new WechatMiniException("微信登录 code 不能为空"); + } + String url = properties.getApiBase() + WechatMiniConstant.JSCODE2SESSION_PATH + + "?appid=" + properties.getAppId() + + "&secret=" + properties.getAppSecret() + + "&js_code=" + loginCode + + "&grant_type=authorization_code"; + String body = HttpUtil.get(url, 8000); + JSONObject json = parseJson(body, "code2session"); + assertWxOk(json, "获取 openid 失败"); + String openid = json.getStr("openid"); + if (StringUtil.isBlank(openid)) { + throw new WechatMiniException("微信未返回 openid"); + } + WechatSessionVO vo = new WechatSessionVO(); + vo.setOpenid(openid); + vo.setSessionKey(json.getStr("session_key")); + vo.setUnionid(json.getStr("unionid")); + return vo; + } + + @Override + public WechatPhoneVO getPhoneNumber(String phoneCode) { + assertConfigured(); + if (StringUtil.isBlank(phoneCode)) { + throw new WechatMiniException("微信手机号 code 不能为空"); + } + String accessToken = getAccessToken(); + String url = properties.getApiBase() + WechatMiniConstant.GET_PHONE_NUMBER_PATH + + "?access_token=" + accessToken; + Map payload = new HashMap<>(2); + payload.put("code", phoneCode); + String body = HttpRequest.post(url) + .body(JSONUtil.toJsonStr(payload)) + .timeout(8000) + .execute() + .body(); + JSONObject json = parseJson(body, "getuserphonenumber"); + assertWxOk(json, "获取手机号失败"); + JSONObject phoneInfo = json.getJSONObject("phone_info"); + if (phoneInfo == null) { + throw new WechatMiniException("微信未返回手机号信息"); + } + WechatPhoneVO vo = new WechatPhoneVO(); + vo.setPhoneNumber(phoneInfo.getStr("phoneNumber")); + vo.setPurePhoneNumber(phoneInfo.getStr("purePhoneNumber")); + vo.setCountryCode(phoneInfo.getStr("countryCode")); + if (StringUtil.isBlank(vo.getPurePhoneNumber()) && StringUtil.isBlank(vo.getPhoneNumber())) { + throw new WechatMiniException("微信未返回有效手机号"); + } + return vo; + } + + private String getAccessToken() { + long now = System.currentTimeMillis(); + if (StringUtil.isNotBlank(cachedAccessToken) && now < accessTokenExpireAt) { + return cachedAccessToken; + } + synchronized (this) { + now = System.currentTimeMillis(); + if (StringUtil.isNotBlank(cachedAccessToken) && now < accessTokenExpireAt) { + return cachedAccessToken; + } + String url = properties.getApiBase() + WechatMiniConstant.ACCESS_TOKEN_PATH + + "?grant_type=client_credential" + + "&appid=" + properties.getAppId() + + "&secret=" + properties.getAppSecret(); + String body = HttpUtil.get(url, 8000); + JSONObject json = parseJson(body, "getAccessToken"); + assertWxOk(json, "获取 access_token 失败"); + String token = json.getStr("access_token"); + Integer expiresIn = json.getInt("expires_in", 7200); + if (StringUtil.isBlank(token)) { + throw new WechatMiniException("微信未返回 access_token"); + } + cachedAccessToken = token; + accessTokenExpireAt = System.currentTimeMillis() + Math.max(60, expiresIn) * 1000L - TOKEN_REFRESH_AHEAD_MS; + return token; + } + } + + private void assertConfigured() { + if (StringUtil.isBlank(properties.getAppId()) || StringUtil.isBlank(properties.getAppSecret())) { + throw new WechatMiniException("未配置微信小程序 appId/appSecret(Nacos: thirdParty.wechat.mini)"); + } + } + + private JSONObject parseJson(String body, String action) { + if (StringUtil.isBlank(body)) { + throw new WechatMiniException("微信接口无响应: " + action); + } + try { + return JSONUtil.parseObj(body); + } catch (Exception e) { + log.error("解析微信响应失败 action={} body={}", action, body, e); + throw new WechatMiniException("解析微信响应失败: " + action, e); + } + } + + private void assertWxOk(JSONObject json, String fallbackMsg) { + Integer errcode = json.getInt("errcode"); + if (errcode != null && errcode != 0) { + String errmsg = json.getStr("errmsg", fallbackMsg); + throw new WechatMiniException(fallbackMsg + ":" + errmsg + "(" + errcode + ")"); + } + } + +} diff --git a/blade-third-party-api/blade-wechat-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/blade-third-party-api/blade-wechat-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..e386596c --- /dev/null +++ b/blade-third-party-api/blade-wechat-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.springblade.thirdparty.wechat.config.ThirdPartyWechatAutoConfiguration diff --git a/blade-third-party-api/pom.xml b/blade-third-party-api/pom.xml index 31f02f52..ba27dd93 100644 --- a/blade-third-party-api/pom.xml +++ b/blade-third-party-api/pom.xml @@ -17,6 +17,7 @@ blade-wps-api blade-ocr-api blade-track-api + blade-wechat-api pom BladeX 第三方API集合 diff --git a/doc/nacos/blade-dev.yaml b/doc/nacos/blade-dev.yaml index 977a9db0..562e9777 100644 --- a/doc/nacos/blade-dev.yaml +++ b/doc/nacos/blade-dev.yaml @@ -87,6 +87,7 @@ thirdParty: oa: # OA开放接口地址 baseUrl: http://127.0.0.1:8080 + queryPersonPageUrl: /gwzh/OA/OA_GET_USER_LIST track: # 轨迹开放接口地址 baseUrl: http://127.0.0.1:8080 @@ -120,6 +121,11 @@ thirdParty: downloadFileUrl: ${MK_DOWNLOAD_FILE_URL:/openapi/sys-attach/fileStream/download} queryApprovalListUrl: ${MK_QUERY_APPROVAL_LIST_URL:/openapi/lbpm-approval/lbpmApproval/portal/list} queryProcessListUrl: ${MK_QUERY_PROCESS_LIST_URL:/openapi/sys-lbpm/sysLbpmProcess/openSupport/list} + wechat: + mini: + # 微信小程序(手机号一键登录 / openid) + appId: ${WECHAT_MINI_APP_ID:} + appSecret: ${WECHAT_MINI_APP_SECRET:} #百度OCR配置,API Key和Secret Key请通过环境变量注入 baidu: diff --git a/doc/nacos/blade-prod.yaml b/doc/nacos/blade-prod.yaml index 7a749a08..69b2ffa9 100644 --- a/doc/nacos/blade-prod.yaml +++ b/doc/nacos/blade-prod.yaml @@ -59,6 +59,7 @@ thirdParty: oa: # OA开放接口地址 baseUrl: http://127.0.0.1:8080 + queryPersonPageUrl: /gwzh/OA/OA_GET_USER_LIST track: # 轨迹开放接口地址 baseUrl: http://127.0.0.1:8080 @@ -92,6 +93,10 @@ thirdParty: downloadFileUrl: ${MK_DOWNLOAD_FILE_URL:/openapi/sys-attach/fileStream/download} queryApprovalListUrl: ${MK_QUERY_APPROVAL_LIST_URL:/openapi/lbpm-approval/lbpmApproval/portal/list} queryProcessListUrl: ${MK_QUERY_PROCESS_LIST_URL:/openapi/sys-lbpm/sysLbpmProcess/openSupport/list} + wechat: + mini: + appId: ${WECHAT_MINI_APP_ID:} + appSecret: ${WECHAT_MINI_APP_SECRET:} #百度OCR配置,API Key和Secret Key请通过环境变量注入 baidu: diff --git a/doc/nacos/blade.yaml b/doc/nacos/blade.yaml index 53fdcb66..f65e82d8 100644 --- a/doc/nacos/blade.yaml +++ b/doc/nacos/blade.yaml @@ -213,6 +213,9 @@ blade: #接口放行 skip-url: - /test/** + # 退出登录:允许无令牌/令牌失效时也能调用(服务端对无用户直接返回成功) + - /oauth/logout/** + - /blade-auth/oauth/logout/** #授权认证配置 auth: - method: ALL diff --git a/doc/nacos/routes/blade-gateway-dev.json b/doc/nacos/routes/blade-gateway-dev.json index 09a97407..d68db7a8 100644 --- a/doc/nacos/routes/blade-gateway-dev.json +++ b/doc/nacos/routes/blade-gateway-dev.json @@ -27,6 +27,28 @@ "filters": [], "uri": "lb://blade-transport" }, + { + "id": "blade-user-alias-route", + "order": 0, + "predicates": [ + { + "name": "Path", + "args": { + "pattern": "/blade-user/**" + } + } + ], + "filters": [ + { + "name": "RewritePath", + "args": { + "regexp": "/blade-user/(?.*)", + "replacement": "/user/$\\{segment}" + } + } + ], + "uri": "lb://blade-system" + }, { "id": "example-route", "order": 0, diff --git a/doc/nacos/third-party-api.yaml b/doc/nacos/third-party-api.yaml index 8b6b40d1..a3fffa04 100644 --- a/doc/nacos/third-party-api.yaml +++ b/doc/nacos/third-party-api.yaml @@ -11,9 +11,15 @@ thirdParty: oa: # OA开放接口地址 baseUrl: ${OA_BASE_URL:http://127.0.0.1:8080} + queryPersonPageUrl: ${OA_QUERY_PERSON_PAGE_URL:/gwzh/OA/OA_GET_USER_LIST} track: # 轨迹开放接口地址 baseUrl: ${TRACK_BASE_URL:http://127.0.0.1:8080} mk: # MK开放接口地址 baseUrl: ${MK_BASE_URL:http://127.0.0.1:8080} + wechat: + mini: + # 微信小程序 AppId / AppSecret(Nacos 配置,勿提交真实 secret) + appId: ${WECHAT_MINI_APP_ID:} + appSecret: ${WECHAT_MINI_APP_SECRET:} diff --git a/doc/sql/update/add-mp-auth-permission.sql b/doc/sql/update/add-mp-auth-permission.sql new file mode 100644 index 00000000..821c983b --- /dev/null +++ b/doc/sql/update/add-mp-auth-permission.sql @@ -0,0 +1,21 @@ +-- 小程序调度端入口权限码 mp_auth +-- user-info 的 permission 来自按钮叶子 code;需把本菜单授权给调度/管理员角色 +-- 执行后:后台「角色管理 → 权限配置」勾选「小程序调度端」,或按需 INSERT blade_role_menu + +-- 父级菜单(小程序) +INSERT IGNORE INTO `blade_menu` +(`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) +VALUES +(2091800000000000001, 0, 'miniprogram', '小程序', 'menu', '/miniprogram', 'iconfont iconicon_work', 98, 1, 0, 1, '', '小程序相关权限', 0); + +-- 按钮权限:mp_auth(叶子节点,会被 permissionCodes 收集) +INSERT IGNORE INTO `blade_menu` +(`id`, `parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `component`, `remark`, `is_deleted`) +VALUES +(2091800000000000002, 2091800000000000001, 'mp_auth', '小程序调度端', 'mp_auth', '', '', 1, 2, 0, 1, NULL, '登录后进入调度端首页', 0); + +-- 示例:给管理员角色(role_id 按环境实际调整,默认管理员 1123598816738675201) +-- INSERT IGNORE INTO `blade_role_menu` (`id`, `menu_id`, `role_id`) +-- VALUES +-- (2091800000000000101, 2091800000000000001, 1123598816738675201), +-- (2091800000000000102, 2091800000000000002, 1123598816738675201); diff --git a/doc/sql/update/add-wechat-applet-grant-type.sql b/doc/sql/update/add-wechat-applet-grant-type.sql new file mode 100644 index 00000000..8a6a8cdc --- /dev/null +++ b/doc/sql/update/add-wechat-applet-grant-type.sql @@ -0,0 +1,8 @@ +-- 为小程序客户端授权类型补充微信小程序登录(wechat_applet) +-- 执行后建议清客户端缓存 / 重启 blade-auth + +UPDATE blade_client +SET authorized_grant_types = CONCAT(authorized_grant_types, ',wechat_applet') +WHERE client_id IN ('saber3', 'saber', 'sword', 'rider') + AND FIND_IN_SET('wechat_applet', REPLACE(authorized_grant_types, ' ', '')) = 0 + AND is_deleted = 0; diff --git a/hs_err_pid22077.log b/hs_err_pid22077.log new file mode 100644 index 00000000..8dc42817 --- /dev/null +++ b/hs_err_pid22077.log @@ -0,0 +1,1379 @@ +# +# A fatal error has been detected by the Java Runtime Environment: +# +# SIGBUS (0xa) at pc=0x0000000100ed64c0, pid=22077, tid=36647 +# +# JRE version: OpenJDK Runtime Environment Zulu17.44+15-CA (17.0.8+7) (build 17.0.8+7-LTS) +# Java VM: OpenJDK 64-Bit Server VM Zulu17.44+15-CA (17.0.8+7-LTS, mixed mode, emulated-client, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, bsd-aarch64) +# Problematic frame: +# C [libzip.dylib+0x64c0] newEntry+0x68 +# +# No core dump will be written. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again +# +# If you would like to submit a bug report, please visit: +# http://www.azul.com/support/ +# The crash happened outside the Java Virtual Machine in native code. +# See problematic frame for where to report the bug. +# + +--------------- S U M M A R Y ------------ + +Command Line: -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:50259,suspend=y,server=n -javaagent:/Users/liangxin/Library/Caches/JetBrains/IntelliJIdea2026.1/captureAgent/debugger-agent.jar=file:///var/folders/pk/drq93rk10q733kk02fgp89xh0000gn/T/capture10140205674518191061.props -XX:TieredStopAtLevel=1 -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dmanagement.endpoints.jmx.exposure.include=* -Dkotlinx.coroutines.debug.enable.creation.stack.trace=false -Ddebugger.agent.enable.coroutines=true -Dkotlinx.coroutines.debug.enable.flows.stack.trace=true -Dkotlinx.coroutines.debug.enable.mutable.state.flows.stack.trace=true -Ddebugger.async.stack.trace.for.all.threads=true -Dfile.encoding=UTF-8 org.springblade.desk.DeskApplication + +Host: "Mac15,6" arm64, 12 cores, 36G, Darwin 25.6.0, macOS 26.6.2 (25G83) +Time: Fri Sep 18 14:44:41 2026 CST elapsed time: 9.129557 seconds (0d 0h 0m 9s) + +--------------- T H R E A D --------------- + +Current thread (0x000000012b49c600): JavaThread "sentinel-datafile-log-executor-thread-1" daemon [_thread_in_native, id=36647, stack(0x0000000174680000,0x0000000174883000)] + +Stack: [0x0000000174680000,0x0000000174883000], sp=0x00000001748819d0, free space=2054k +Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code) +C [libzip.dylib+0x64c0] newEntry+0x68 +C [libzip.dylib+0x6390] ZIP_GetEntry2+0x14c +C [libzip.dylib+0x6d78] ZIP_FindEntry+0x3c +V [libjvm.dylib+0x25a408] ClassPathZipEntry::open_entry(JavaThread*, char const*, int*, bool)+0xb4 +V [libjvm.dylib+0x25a53c] ClassPathZipEntry::open_stream(JavaThread*, char const*)+0x20 +V [libjvm.dylib+0x25d918] ClassLoader::load_class(Symbol*, bool, JavaThread*)+0x150 +V [libjvm.dylib+0x981d60] SystemDictionary::load_instance_class_impl(Symbol*, Handle, JavaThread*)+0x2d0 +V [libjvm.dylib+0x98063c] SystemDictionary::load_instance_class(unsigned int, Symbol*, Handle, JavaThread*)+0x30 +V [libjvm.dylib+0x97fd48] SystemDictionary::resolve_instance_class_or_null(Symbol*, Handle, Handle, JavaThread*)+0x4dc +V [libjvm.dylib+0x97f334] SystemDictionary::resolve_or_fail(Symbol*, Handle, Handle, bool, JavaThread*)+0x80 +V [libjvm.dylib+0x2beb54] ConstantPool::klass_at_impl(constantPoolHandle const&, int, JavaThread*)+0x1e0 +V [libjvm.dylib+0x46d6f0] InterpreterRuntime::_new(JavaThread*, ConstantPool*, int)+0x94 +j com.intellij.rt.debugger.agent.CaptureStorage$DeepCapturedStack.collectStacks(Ljava/util/List;)Lcom/intellij/rt/debugger/agent/CaptureStorage$StackData;+89 +j com.intellij.rt.debugger.agent.CaptureStorage.getStackTrace(Lcom/intellij/rt/debugger/agent/CaptureStorage$CapturedStack;I)Ljava/util/ArrayList;+30 +j com.intellij.rt.debugger.agent.CaptureStorage.access$1000(Lcom/intellij/rt/debugger/agent/CaptureStorage$CapturedStack;I)Ljava/util/ArrayList;+2 +j com.intellij.rt.debugger.agent.CaptureStorage$9.call()[Ljava/lang/StackTraceElement;+31 +j com.intellij.rt.debugger.agent.CaptureStorage$9.call()Ljava/lang/Object;+1 +j com.intellij.rt.debugger.agent.CaptureStorage.withoutThrowableCapture(Lcom/intellij/rt/debugger/agent/CaptureStorage$Callable;)Ljava/lang/Object;+21 +j com.intellij.rt.debugger.agent.CaptureStorage.getAsyncStackTrace(Ljava/lang/Throwable;)[Ljava/lang/StackTraceElement;+19 +j java.lang.Throwable.printStackTrace(Ljava/lang/Throwable$PrintStreamOrWriter;)V+32 java.base@17.0.8 +j java.lang.Throwable.printStackTrace(Ljava/io/PrintWriter;)V+9 java.base@17.0.8 +j com.alibaba.csp.sentinel.log.jul.CspFormatter.format(Ljava/util/logging/LogRecord;)Ljava/lang/String;+103 +j java.util.logging.StreamHandler.publish(Ljava/util/logging/LogRecord;)V+14 java.logging@17.0.8 +j java.util.logging.FileHandler.publish(Ljava/util/logging/LogRecord;)V+11 java.logging@17.0.8 +j com.alibaba.csp.sentinel.log.jul.DateFileLogHandler$LogTask.run()V+8 +j java.util.concurrent.ThreadPoolExecutor.runWorker(Ljava/util/concurrent/ThreadPoolExecutor$Worker;)V+92 java.base@17.0.8 +j java.util.concurrent.ThreadPoolExecutor$Worker.run()V+5 java.base@17.0.8 +j java.lang.Thread.run()V+11 java.base@17.0.8 +v ~StubRoutines::call_stub +V [libjvm.dylib+0x4781f8] JavaCalls::call_helper(JavaValue*, methodHandle const&, JavaCallArguments*, JavaThread*)+0x394 +V [libjvm.dylib+0x47720c] JavaCalls::call_virtual(JavaValue*, Klass*, Symbol*, Symbol*, JavaCallArguments*, JavaThread*)+0x11c +V [libjvm.dylib+0x4772d8] JavaCalls::call_virtual(JavaValue*, Handle, Klass*, Symbol*, Symbol*, JavaThread*)+0x64 +V [libjvm.dylib+0x52ebfc] thread_entry(JavaThread*, JavaThread*)+0xc4 +V [libjvm.dylib+0x9b22e8] JavaThread::thread_main_inner()+0x150 +V [libjvm.dylib+0x9b0990] Thread::call_run()+0xe0 +V [libjvm.dylib+0x7d0364] thread_native_entry(Thread*)+0x158 +C [libsystem_pthread.dylib+0x6c58] _pthread_start+0x88 + +Java frames: (J=compiled Java code, j=interpreted, Vv=VM code) +j com.intellij.rt.debugger.agent.CaptureStorage$DeepCapturedStack.collectStacks(Ljava/util/List;)Lcom/intellij/rt/debugger/agent/CaptureStorage$StackData;+89 +j com.intellij.rt.debugger.agent.CaptureStorage.getStackTrace(Lcom/intellij/rt/debugger/agent/CaptureStorage$CapturedStack;I)Ljava/util/ArrayList;+30 +j com.intellij.rt.debugger.agent.CaptureStorage.access$1000(Lcom/intellij/rt/debugger/agent/CaptureStorage$CapturedStack;I)Ljava/util/ArrayList;+2 +j com.intellij.rt.debugger.agent.CaptureStorage$9.call()[Ljava/lang/StackTraceElement;+31 +j com.intellij.rt.debugger.agent.CaptureStorage$9.call()Ljava/lang/Object;+1 +j com.intellij.rt.debugger.agent.CaptureStorage.withoutThrowableCapture(Lcom/intellij/rt/debugger/agent/CaptureStorage$Callable;)Ljava/lang/Object;+21 +j com.intellij.rt.debugger.agent.CaptureStorage.getAsyncStackTrace(Ljava/lang/Throwable;)[Ljava/lang/StackTraceElement;+19 +j java.lang.Throwable.printStackTrace(Ljava/lang/Throwable$PrintStreamOrWriter;)V+32 java.base@17.0.8 +j java.lang.Throwable.printStackTrace(Ljava/io/PrintWriter;)V+9 java.base@17.0.8 +j com.alibaba.csp.sentinel.log.jul.CspFormatter.format(Ljava/util/logging/LogRecord;)Ljava/lang/String;+103 +j java.util.logging.StreamHandler.publish(Ljava/util/logging/LogRecord;)V+14 java.logging@17.0.8 +j java.util.logging.FileHandler.publish(Ljava/util/logging/LogRecord;)V+11 java.logging@17.0.8 +j com.alibaba.csp.sentinel.log.jul.DateFileLogHandler$LogTask.run()V+8 +j java.util.concurrent.ThreadPoolExecutor.runWorker(Ljava/util/concurrent/ThreadPoolExecutor$Worker;)V+92 java.base@17.0.8 +j java.util.concurrent.ThreadPoolExecutor$Worker.run()V+5 java.base@17.0.8 +j java.lang.Thread.run()V+11 java.base@17.0.8 +v ~StubRoutines::call_stub + +siginfo: si_signo: 10 (SIGBUS), si_code: 1 (BUS_ADRALN), si_addr: 0x0000000100dd1e7b + +Register to memory mapping: + + x0=0x00006000018b9130 points into unknown readable memory: 0x0000000000000000 | 00 00 00 00 00 00 00 00 + x1=0x0 is NULL + x2=0x0 is NULL + x3=0x00006000018b9140 points into unknown readable memory: 0x0000000000000000 | 00 00 00 00 00 00 00 00 + x4=0x00006000018b9180 points into unknown readable memory: 0x00000000bf074454 | 54 44 07 bf 00 00 00 00 + x5=0x00000000a0a4a7fb is an unknown value + x6=0x0000000020c00000 is an unknown value + x7=0x000000000000000a is an unknown value + x8=0x0000000100ef9e5f points into unknown readable memory: 00 + x9=0x0000000000128000 is an unknown value +x10=0x00006000018b8000 points into unknown readable memory: 0x4c28004120ed0001 | 01 00 ed 20 41 00 28 4c +x11=0x0000000000001130 is an unknown value +x12=0x0000000000000050 is an unknown value +x13=0x0000000000000001 is an unknown value +x14=0x00000000ffffff6b is an unknown value +x15=0x00000000000007fb is an unknown value +x16=0x0000000186e7d030: __bzero+0 in /usr/lib/system/libsystem_platform.dylib at 0x0000000186e7a000 +x17=0x00000001f4ef54a8 points into unknown readable memory: 0x0000000186e7d030 | 30 d0 e7 86 01 00 00 00 +x18=0x0 is NULL +x19=0x00006000018b9130 points into unknown readable memory: 0x0000000000000000 | 00 00 00 00 00 00 00 00 +x20=0x0 is NULL +x21=0x0000600000d1c480 points into unknown readable memory: 0x0000600001c14720 | 20 47 c1 01 00 60 00 00 +x22=0x0000000100dd1e5f points into unknown readable memory: 50 +x23=0x00000000d3a18b02 is an unknown value +x24=0x000000000000002f is an unknown value +x25=0x000000000000003d is an unknown value +x26=0x00000000000000cd is an unknown value +x27=0x00006000018b9158 points into unknown readable memory: 0x0000000000000000 | 00 00 00 00 00 00 00 00 +x28=0x000000010f116600 points into unknown readable memory: 0x65746e692f6d6f63 | 63 6f 6d 2f 69 6e 74 65 + + +Registers: + x0=0x00006000018b9130 x1=0x0000000000000000 x2=0x0000000000000000 x3=0x00006000018b9140 + x4=0x00006000018b9180 x5=0x00000000a0a4a7fb x6=0x0000000020c00000 x7=0x000000000000000a + x8=0x0000000100ef9e5f x9=0x0000000000128000 x10=0x00006000018b8000 x11=0x0000000000001130 +x12=0x0000000000000050 x13=0x0000000000000001 x14=0x00000000ffffff6b x15=0x00000000000007fb +x16=0x0000000186e7d030 x17=0x00000001f4ef54a8 x18=0x0000000000000000 x19=0x00006000018b9130 +x20=0x0000000000000000 x21=0x0000600000d1c480 x22=0x0000000100dd1e5f x23=0x00000000d3a18b02 +x24=0x000000000000002f x25=0x000000000000003d x26=0x00000000000000cd x27=0x00006000018b9158 +x28=0x000000010f116600 fp=0x0000000174881a50 lr=0x0000000100ed648c sp=0x00000001748819d0 +pc=0x0000000100ed64c0 cpsr=0x0000000060001000 +Top of Stack: (sp=0x00000001748819d0) +0x00000001748819d0: 0000000000000000 0000000000000000 +0x00000001748819e0: 0000000000000000 0000000000000000 +0x00000001748819f0: 0000000000000000 0000000000000000 +0x0000000174881a00: 000000010f116600 000000012c81cc00 +0x0000000174881a10: 00000000000000cd 000000000000003d +0x0000000174881a20: 000000000000002f 00000000d3a18b02 +0x0000000174881a30: 00006000018923a0 0000000000000000 +0x0000000174881a40: 000000010f116640 0000600000d1c480 +0x0000000174881a50: 0000000174881ab0 0000000100ed6390 +0x0000000174881a60: 000000010f116600 0000000000000037 +0x0000000174881a70: 0000000000000001 000000010f116640 +0x0000000174881a80: 000000012b49c948 000000010f116640 +0x0000000174881a90: 0000600000d1c480 000000010f116640 +0x0000000174881aa0: 0000000174881bdc 0000000174881af4 +0x0000000174881ab0: 0000000174881ae0 0000000100ed6d78 +0x0000000174881ac0: 0000000174881bdc 0000600003615c50 +0x0000000174881ad0: 0000000000000000 000000012b49c600 +0x0000000174881ae0: 0000000174881bc0 000000010225e408 +0x0000000174881af0: 0000000102abc388 0000000000000100 +0x0000000174881b00: 0000000174881b20 00000001025325dc +0x0000000174881b10: 0000000102abc388 0000000174881ba0 +0x0000000174881b20: 0000000174881b70 00000001022e496c +0x0000000174881b30: 0000000000000000 0000000000000000 +0x0000000174881b40: 0000000000000001 00000001314a0290 +0x0000000174881b50: 000000012b49c600 000000010f1169d8 +0x0000000174881b60: 0000000102ad11e2 0000000174881c68 +0x0000000174881b70: 0000000174881b90 31ade61c7bb700f3 +0x0000000174881b80: 0000000000000001 000000010f116640 +0x0000000174881b90: 0000600003615c50 00000001314a0290 +0x0000000174881ba0: 000000012b49c600 000000010f1169d8 +0x0000000174881bb0: 000000010f1165f0 0000600003615c50 +0x0000000174881bc0: 0000000174881bf0 000000010225e53c + +Instructions: (pc=0x0000000100ed64c0) +0x0000000100ed63c0: 6b0c017f 54ffff60 17ffffde d2800016 +0x0000000100ed63d0: 72001ebf 54000160 b5000156 f100073f +0x0000000100ed63e0: 54fff7cb 8b140328 385ff108 7100bd1f +0x0000000100ed63f0: 54fff741 d2800016 14000002 f9004e7f +0x0000000100ed6400: f9402a60 94000451 aa1603e0 a9457bfd +0x0000000100ed6410: a9444ff4 a94357f6 a9425ff8 a94167fa +0x0000000100ed6420: a8c66ffc d65f03c0 6b03003f 540000e1 +0x0000000100ed6430: 71000421 540000eb 38401408 38401449 +0x0000000100ed6440: 6b09011f 54ffff60 52800000 d65f03c0 +0x0000000100ed6450: 52800020 d65f03c0 d10243ff a9036ffc +0x0000000100ed6460: a90467fa a9055ff8 a90657f6 a9074ff4 +0x0000000100ed6470: a9087bfd 910203fd aa0203f4 aa0103f6 +0x0000000100ed6480: aa0003f5 52800900 94000481 aa0003f3 +0x0000000100ed6490: b4001320 f900027f aa1303fb f8028f7f +0x0000000100ed64a0: f9001a7f 3940c2a8 34000288 f9400ea8 +0x0000000100ed64b0: f94006c9 8b090108 f94016a9 cb090116 +0x0000000100ed64c0: 79403ad8 39407ada 39407edc 794042c8 +0x0000000100ed64d0: f90017e8 b9400ec8 f9000668 b9401ac8 +0x0000000100ed64e0: f9000fe8 f9000a68 794016c8 34000488 +0x0000000100ed64f0: b94016c8 14000023 f94006d7 34000d54 +0x0000000100ed6500: f9401ea8 b4000288 f94022a9 eb17013f +0x0000000100ed6510: 5400022c 5283fa4a 8b0a012a eb17015f +0x0000000100ed6520: 540001ab 9140092a 8b170108 cb090116 +0x0000000100ed6530: 79403ac8 79403ec9 794042cb 8b0802e8 +0x0000000100ed6540: 8b090108 8b0b0108 9100b908 eb0a011f +0x0000000100ed6550: 54000b4d aa1503e0 aa1703e1 52840002 +0x0000000100ed6560: 94000384 aa0003f6 b4000aa0 f9401ea0 +0x0000000100ed6570: 94000429 a903deb6 17ffffd2 d2800008 +0x0000000100ed6580: aa0803f7 f9000e68 b94012c8 b9002268 +0x0000000100ed6590: b842a2c9 f9405ea8 f9000be9 8b090108 +0x0000000100ed65a0: cb0803e8 f9001e68 794012c8 b9004268 +0x0000000100ed65b0: 91000700 94000436 aa0003f9 f9000260 + + +Stack slot to memory mapping: +stack at sp + 0 slots: 0x0 is NULL +stack at sp + 1 slots: 0x0 is NULL +stack at sp + 2 slots: 0x0 is NULL +stack at sp + 3 slots: 0x0 is NULL +stack at sp + 4 slots: 0x0 is NULL +stack at sp + 5 slots: 0x0 is NULL +stack at sp + 6 slots: 0x000000010f116600 points into unknown readable memory: 0x65746e692f6d6f63 | 63 6f 6d 2f 69 6e 74 65 +stack at sp + 7 slots: 0x000000012c81cc00 points into unknown readable memory: 0xffffffff5bbd78a2 | a2 78 bd 5b ff ff ff ff + + +--------------- P R O C E S S --------------- + +Threads class SMR info: +_java_thread_list=0x0000600003f37760, length=73, elements={ +0x000000010b808a00, 0x000000010b809600, 0x000000012c90ac00, 0x000000010b008600, +0x000000010b00ae00, 0x000000011e808200, 0x000000011e00a400, 0x000000010f80a200, +0x000000010b809c00, 0x000000010f80b200, 0x000000011e00aa00, 0x000000010f80b800, +0x000000012d039a00, 0x000000011d808200, 0x000000012c01ea00, 0x000000012d810400, +0x000000012d838200, 0x000000010f829800, 0x000000012b0ffe00, 0x00000001018b8200, +0x000000012dae5600, 0x000000012c39d000, 0x000000011db9d000, 0x000000012c3bba00, +0x000000012c3cb200, 0x000000011dbcc600, 0x000000012db42e00, 0x000000012cedde00, +0x000000012db60800, 0x000000011dc28800, 0x000000012db67000, 0x000000011dc39e00, +0x000000012b336400, 0x000000012c4a3400, 0x000000012cf7fc00, 0x000000010b190600, +0x000000011ead0200, 0x000000011dccd000, 0x000000010b17fa00, 0x000000011e1f1a00, +0x000000010b995e00, 0x000000010f8ffa00, 0x000000011f024a00, 0x000000011dcc2400, +0x000000011eb04e00, 0x000000012d1e3200, 0x000000012d1c0200, 0x000000011e257200, +0x000000010f997000, 0x000000012c55be00, 0x000000012b406400, 0x000000011eb76400, +0x000000010b9b3200, 0x000000010b985800, 0x000000011f02f400, 0x000000012d1f9a00, +0x000000011dd00400, 0x000000011dd2cc00, 0x000000012b437400, 0x000000012b45ba00, +0x000000012b49c600, 0x000000011ebea000, 0x000000011ea07e00, 0x000000011f161800, +0x000000012c5d9e00, 0x000000011f162e00, 0x000000012dd08600, 0x0000000101998600, +0x000000010b1ef200, 0x000000010ba06000, 0x000000011dda3c00, 0x000000011e3d8e00, +0x000000011f24c800 +} + +Java Threads: ( => current thread ) + 0x000000010b808a00 JavaThread "main" [_thread_in_native, id=5891, stack(0x000000016f17c000,0x000000016f37f000)] + 0x000000010b809600 JavaThread "Reference Handler" daemon [_thread_blocked, id=19971, stack(0x000000016ffd0000,0x00000001701d3000)] + 0x000000012c90ac00 JavaThread "Finalizer" daemon [_thread_blocked, id=19715, stack(0x00000001701dc000,0x00000001703df000)] + 0x000000010b008600 JavaThread "Signal Dispatcher" daemon [_thread_blocked, id=30979, stack(0x0000000170500000,0x0000000170703000)] + 0x000000010b00ae00 JavaThread "Service Thread" daemon [_thread_blocked, id=30467, stack(0x000000017070c000,0x000000017090f000)] + 0x000000011e808200 JavaThread "Monitor Deflation Thread" daemon [_thread_blocked, id=23299, stack(0x0000000170918000,0x0000000170b1b000)] + 0x000000011e00a400 JavaThread "C1 CompilerThread0" daemon [_thread_blocked, id=23555, stack(0x0000000170b24000,0x0000000170d27000)] + 0x000000010f80a200 JavaThread "Sweeper thread" daemon [_thread_blocked, id=29699, stack(0x0000000170d30000,0x0000000170f33000)] + 0x000000010b809c00 JavaThread "C1 CompilerThread1" daemon [_thread_blocked, id=24067, stack(0x0000000170f3c000,0x000000017113f000)] + 0x000000010f80b200 JavaThread "Common-Cleaner" daemon [_thread_blocked, id=24323, stack(0x0000000171148000,0x000000017134b000)] + 0x000000011e00aa00 JavaThread "C1 CompilerThread2" daemon [_thread_blocked, id=24835, stack(0x0000000171354000,0x0000000171557000)] + 0x000000010f80b800 JavaThread "C1 CompilerThread3" daemon [_thread_blocked, id=29187, stack(0x0000000171560000,0x0000000171763000)] + 0x000000012d039a00 JavaThread "JDWP Transport Listener: dt_socket" daemon [_thread_blocked, id=25603, stack(0x000000017176c000,0x000000017196f000)] + 0x000000011d808200 JavaThread "JDWP Event Helper Thread" daemon [_thread_blocked, id=25859, stack(0x0000000171978000,0x0000000171b7b000)] + 0x000000012c01ea00 JavaThread "JDWP Command Reader" daemon [_thread_in_native, id=26115, stack(0x0000000171b84000,0x0000000171d87000)] + 0x000000012d810400 JavaThread "IntelliJ Suspend Helper" daemon [_thread_blocked, id=26371, stack(0x0000000171d90000,0x0000000171f93000)] + 0x000000012d838200 JavaThread "Notification Thread" daemon [_thread_blocked, id=26883, stack(0x0000000171f9c000,0x000000017219f000)] + 0x000000010f829800 JavaThread "CoarseTimer" daemon [_thread_blocked, id=27139, stack(0x00000001721a8000,0x00000001723ab000)] + 0x000000012b0ffe00 JavaThread "RMI TCP Accept-0" daemon [_thread_in_native, id=41219, stack(0x0000000173620000,0x0000000173823000)] + 0x00000001018b8200 JavaThread "com.alibaba.nacos.client.logging.0" daemon [_thread_blocked, id=40707, stack(0x0000000173a38000,0x0000000173c3b000)] + 0x000000012dae5600 JavaThread "Attach Listener" daemon [_thread_blocked, id=35331, stack(0x0000000173c44000,0x0000000173e47000)] + 0x000000012c39d000 JavaThread "RMI TCP Connection(2)-127.0.0.1" daemon [_thread_in_native, id=39939, stack(0x0000000173e50000,0x0000000174053000)] + 0x000000011db9d000 JavaThread "RMI Scheduler(0)" daemon [_thread_blocked, id=35587, stack(0x000000017405c000,0x000000017425f000)] + 0x000000012c3bba00 JavaThread "nacos.publisher-com.alibaba.nacos.common.notify.SlowEvent" daemon [_thread_blocked, id=43523, stack(0x00000001750bc000,0x00000001752bf000)] + 0x000000012c3cb200 JavaThread "nacos.publisher-com.alibaba.nacos.client.config.impl.ConfigFuzzyWatchNotifyEvent" daemon [_thread_blocked, id=43779, stack(0x00000001752c8000,0x00000001754cb000)] + 0x000000011dbcc600 JavaThread "nacos.publisher-com.alibaba.nacos.client.config.impl.ConfigFuzzyWatchLoadEvent" daemon [_thread_blocked, id=44291, stack(0x00000001754d4000,0x00000001756d7000)] + 0x000000012db42e00 JavaThread "com.alibaba.nacos.client.auth.ram.identify.watcher.0" daemon [_thread_blocked, id=65027, stack(0x00000001756e0000,0x00000001758e3000)] + 0x000000012cedde00 JavaThread "com.alibaba.nacos.client.login-executor.0" daemon [_thread_blocked, id=36883, stack(0x0000000174a98000,0x0000000174c9b000)] + 0x000000012db60800 JavaThread "com.alibaba.nacos.client.listen-executor.0" daemon [_thread_blocked, id=45059, stack(0x00000001758ec000,0x0000000175aef000)] + 0x000000011dc28800 JavaThread "com.alibaba.nacos.client.fuzzy-watcher-executor.0" daemon [_thread_blocked, id=64771, stack(0x0000000175af8000,0x0000000175cfb000)] + 0x000000012db67000 JavaThread "com.alibaba.nacos.client.remote.worker.0" daemon [_thread_blocked, id=64259, stack(0x0000000175d04000,0x0000000175f07000)] + 0x000000011dc39e00 JavaThread "com.alibaba.nacos.client.remote.worker.1" daemon [_thread_blocked, id=45827, stack(0x0000000175f10000,0x0000000176113000)] + 0x000000012b336400 JavaThread "grpc-nio-worker-ELG-1-1" daemon [_thread_in_native, id=64019, stack(0x000000017611c000,0x000000017631f000)] + 0x000000012c4a3400 JavaThread "grpc-default-executor-0" daemon [_thread_blocked, id=63747, stack(0x0000000176328000,0x000000017652b000)] + 0x000000012cf7fc00 JavaThread "nacos-grpc-client-executor-127.0.0.1-0" daemon [_thread_blocked, id=46595, stack(0x0000000176534000,0x0000000176737000)] + 0x000000010b190600 JavaThread "nacos-grpc-client-executor-127.0.0.1-1" daemon [_thread_blocked, id=63235, stack(0x0000000176740000,0x0000000176943000)] + 0x000000011ead0200 JavaThread "grpc-nio-worker-ELG-1-2" daemon [_thread_in_native, id=62995, stack(0x000000017694c000,0x0000000176b4f000)] + 0x000000011dccd000 JavaThread "nacos-grpc-client-executor-127.0.0.1-2" daemon [_thread_blocked, id=47363, stack(0x0000000176b58000,0x0000000176d5b000)] + 0x000000010b17fa00 JavaThread "nacos-grpc-client-executor-127.0.0.1-3" daemon [_thread_blocked, id=62211, stack(0x0000000176d64000,0x0000000176f67000)] + 0x000000011e1f1a00 JavaThread "nacos-grpc-client-executor-127.0.0.1-4" daemon [_thread_blocked, id=47619, stack(0x0000000176f70000,0x0000000177173000)] + 0x000000010b995e00 JavaThread "nacos-grpc-client-executor-127.0.0.1-5" daemon [_thread_blocked, id=61699, stack(0x000000017717c000,0x000000017737f000)] + 0x000000010f8ffa00 JavaThread "nacos-grpc-client-executor-127.0.0.1-6" daemon [_thread_blocked, id=48387, stack(0x0000000177388000,0x000000017758b000)] + 0x000000011f024a00 JavaThread "nacos.publisher-com.alibaba.nacos.common.ability.AbstractAbilityControlManager$AbilityUpdateEvent" daemon [_thread_blocked, id=61187, stack(0x0000000177594000,0x0000000177797000)] + 0x000000011dcc2400 JavaThread "nacos-grpc-client-executor-127.0.0.1-7" daemon [_thread_blocked, id=48899, stack(0x00000001777a0000,0x00000001779a3000)] + 0x000000011eb04e00 JavaThread "nacos-grpc-client-executor-127.0.0.1-8" daemon [_thread_blocked, id=60675, stack(0x00000001779ac000,0x0000000177baf000)] + 0x000000012d1e3200 JavaThread "nacos-grpc-client-executor-127.0.0.1-9" daemon [_thread_blocked, id=49411, stack(0x0000000177bb8000,0x0000000177dbb000)] + 0x000000012d1c0200 JavaThread "nacos-grpc-client-executor-127.0.0.1-10" daemon [_thread_blocked, id=60163, stack(0x0000000177dc4000,0x0000000177fc7000)] + 0x000000011e257200 JavaThread "nacos-grpc-client-executor-127.0.0.1-11" daemon [_thread_blocked, id=59659, stack(0x0000000328004000,0x0000000328207000)] + 0x000000010f997000 JavaThread "nacos-grpc-client-executor-127.0.0.1-12" daemon [_thread_blocked, id=49923, stack(0x0000000328210000,0x0000000328413000)] + 0x000000012c55be00 JavaThread "nacos-grpc-client-executor-127.0.0.1-13" daemon [_thread_blocked, id=59395, stack(0x000000032841c000,0x000000032861f000)] + 0x000000012b406400 JavaThread "nacos-grpc-client-executor-127.0.0.1-14" daemon [_thread_blocked, id=59139, stack(0x0000000328628000,0x000000032882b000)] + 0x000000011eb76400 JavaThread "nacos-grpc-client-executor-127.0.0.1-15" daemon [_thread_blocked, id=50691, stack(0x0000000328834000,0x0000000328a37000)] + 0x000000010b9b3200 JavaThread "nacos-grpc-client-executor-127.0.0.1-16" daemon [_thread_blocked, id=51203, stack(0x0000000328a40000,0x0000000328c43000)] + 0x000000010b985800 JavaThread "nacos-grpc-client-executor-127.0.0.1-17" daemon [_thread_blocked, id=51715, stack(0x0000000328c4c000,0x0000000328e4f000)] + 0x000000011f02f400 JavaThread "nacos-grpc-client-executor-127.0.0.1-18" daemon [_thread_blocked, id=51971, stack(0x0000000328e58000,0x000000032905b000)] + 0x000000012d1f9a00 JavaThread "nacos-grpc-client-executor-127.0.0.1-19" daemon [_thread_blocked, id=58115, stack(0x0000000329064000,0x0000000329267000)] + 0x000000011dd00400 JavaThread "nacos-grpc-client-executor-127.0.0.1-20" daemon [_thread_blocked, id=57859, stack(0x0000000329270000,0x0000000329473000)] + 0x000000011dd2cc00 JavaThread "nacos-grpc-client-executor-127.0.0.1-21" daemon [_thread_blocked, id=57347, stack(0x000000032947c000,0x000000032967f000)] + 0x000000012b437400 JavaThread "nacos-grpc-client-executor-127.0.0.1-22" daemon [_thread_blocked, id=57091, stack(0x0000000329688000,0x000000032988b000)] + 0x000000012b45ba00 JavaThread "RMI TCP Connection(3)-127.0.0.1" daemon [_thread_in_native, id=39179, stack(0x0000000174268000,0x000000017446b000)] +=>0x000000012b49c600 JavaThread "sentinel-datafile-log-executor-thread-1" daemon [_thread_in_native, id=36647, stack(0x0000000174680000,0x0000000174883000)] + 0x000000011ebea000 JavaThread "sentinel-heartbeat-send-task-thread-1" daemon [_thread_blocked, id=56595, stack(0x0000000329894000,0x0000000329a97000)] + 0x000000011ea07e00 JavaThread "sentinel-command-center-executor-thread-1" daemon [_thread_in_native, id=36139, stack(0x0000000174474000,0x0000000174677000)] + 0x000000011f161800 JavaThread "nacos-grpc-client-executor-127.0.0.1-23" daemon [_thread_blocked, id=36367, stack(0x000000017488c000,0x0000000174a8f000)] + 0x000000012c5d9e00 JavaThread "nacos-grpc-client-executor-127.0.0.1-24" daemon [_thread_blocked, id=53003, stack(0x0000000329aa0000,0x0000000329ca3000)] + 0x000000011f162e00 JavaThread "nacos-grpc-client-executor-127.0.0.1-25" daemon [_thread_blocked, id=56323, stack(0x0000000329cac000,0x0000000329eaf000)] + 0x000000012dd08600 JavaThread "nacos-grpc-client-executor-127.0.0.1-26" daemon [_thread_blocked, id=53763, stack(0x0000000329eb8000,0x000000032a0bb000)] + 0x0000000101998600 JavaThread "nacos-grpc-client-executor-127.0.0.1-27" daemon [_thread_blocked, id=55811, stack(0x000000032a0c4000,0x000000032a2c7000)] + 0x000000010b1ef200 JavaThread "nacos-grpc-client-executor-127.0.0.1-28" daemon [_thread_blocked, id=54019, stack(0x000000032a2d0000,0x000000032a4d3000)] + 0x000000010ba06000 JavaThread "nacos-grpc-client-executor-127.0.0.1-29" daemon [_thread_blocked, id=55043, stack(0x000000032a4dc000,0x000000032a6df000)] + 0x000000011dda3c00 JavaThread "nacos-grpc-client-executor-127.0.0.1-30" daemon [_thread_blocked, id=54787, stack(0x000000032a6e8000,0x000000032a8eb000)] + 0x000000011e3d8e00 JavaThread "sentinel-time-tick-thread" daemon [_thread_blocked, id=31875, stack(0x000000032a8f4000,0x000000032aaf7000)] + 0x000000011f24c800 JavaThread "sentinel-heartbeat-send-task-thread-2" daemon [_thread_blocked, id=65843, stack(0x000000032ab00000,0x000000032ad03000)] + +Other Threads: + 0x000000012b8059d0 VMThread "VM Thread" [stack: 0x000000016fdc4000,0x000000016ffc7000] [id=18435] + 0x000000010f109630 WatcherThread [stack: 0x000000017382c000,0x0000000173a2f000] [id=40963] + 0x000000012af08740 GCTaskThread "GC Thread#0" [stack: 0x000000016f388000,0x000000016f58b000] [id=12035] + 0x000000012ae08da0 GCTaskThread "GC Thread#1" [stack: 0x00000001723b4000,0x00000001725b7000] [id=27395] + 0x000000012ae09060 GCTaskThread "GC Thread#2" [stack: 0x00000001725c0000,0x00000001727c3000] [id=32771] + 0x0000000101107cb0 GCTaskThread "GC Thread#3" [stack: 0x00000001727cc000,0x00000001729cf000] [id=33027] + 0x000000010f30cd30 GCTaskThread "GC Thread#4" [stack: 0x00000001729d8000,0x0000000172bdb000] [id=33283] + 0x000000012ae094f0 GCTaskThread "GC Thread#5" [stack: 0x0000000172be4000,0x0000000172de7000] [id=42755] + 0x000000010f30cff0 GCTaskThread "GC Thread#6" [stack: 0x0000000172df0000,0x0000000172ff3000] [id=42243] + 0x000000012b909b70 GCTaskThread "GC Thread#7" [stack: 0x0000000172ffc000,0x00000001731ff000] [id=41987] + 0x000000010f209c10 GCTaskThread "GC Thread#8" [stack: 0x0000000173208000,0x000000017340b000] [id=34051] + 0x000000010f30d870 GCTaskThread "GC Thread#9" [stack: 0x0000000173414000,0x0000000173617000] [id=34307] + 0x000000012af08e00 ConcurrentGCThread "G1 Main Marker" [stack: 0x000000016f594000,0x000000016f797000] [id=13571] + 0x000000012af09690 ConcurrentGCThread "G1 Conc#0" [stack: 0x000000016f7a0000,0x000000016f9a3000] [id=12547] + 0x000000010f41fcd0 ConcurrentGCThread "G1 Conc#1" [stack: 0x0000000174ca4000,0x0000000174ea7000] [id=37891] + 0x000000010f32ed40 ConcurrentGCThread "G1 Conc#2" [stack: 0x0000000174eb0000,0x00000001750b3000] [id=37379] + 0x000000012af0bbd0 ConcurrentGCThread "G1 Refine#0" [stack: 0x000000016f9ac000,0x000000016fbaf000] [id=16643] + 0x000000010f104080 ConcurrentGCThread "G1 Service" [stack: 0x000000016fbb8000,0x000000016fdbb000] [id=21507] + +Threads with active compile tasks: + +VM state: not at safepoint (normal execution) + +VM Mutex/Monitor currently owned by a thread: None + +Heap address: 0x00000005c0000000, size: 9216 MB, Compressed Oops mode: Zero based, Oop shift amount: 3 + +CDS archive(s) mapped at: [0x0000000500000000-0x0000000500c14000-0x0000000500c14000), size 12664832, SharedBaseAddress: 0x0000000500000000, ArchiveRelocationMode: 1. +Compressed class space mapped at: 0x0000000501000000-0x0000000541000000, reserved size: 1073741824 +Narrow klass base: 0x0000000500000000, Narrow klass shift: 0, Narrow klass range: 0x100000000 + +GC Precious Log: + CPUs: 12 total, 12 available + Memory: 36864M + Large Page Support: Disabled + NUMA Support: Disabled + Compressed Oops: Enabled (Zero based) + Heap Region Size: 8M + Heap Min Capacity: 8M + Heap Initial Capacity: 576M + Heap Max Capacity: 9G + Pre-touch: Disabled + Parallel Workers: 10 + Concurrent Workers: 3 + Concurrent Refinement Workers: 10 + Periodic GC: Disabled + +Heap: + garbage-first heap total 262144K, used 92456K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 9 young (73728K), 3 survivors (24576K) + Metaspace used 63382K, committed 63872K, reserved 1114112K + class space used 8462K, committed 8704K, reserved 1048576K + +Heap Regions: E=young(eden), S=young(survivor), O=old, HS=humongous(starts), HC=humongous(continues), CS=collection set, F=free, OA=open archive, CA=closed archive, TAMS=top-at-mark-start (previous, next) +| 0|0x00000005c0000000, 0x00000005c0800000, 0x00000005c0800000|100%| O| |TAMS 0x00000005c0800000, 0x00000005c0000000| Untracked +| 1|0x00000005c0800000, 0x00000005c1000000, 0x00000005c1000000|100%| O| |TAMS 0x00000005c1000000, 0x00000005c0800000| Untracked +| 2|0x00000005c1000000, 0x00000005c1800000, 0x00000005c1800000|100%| O| |TAMS 0x00000005c1800000, 0x00000005c1000000| Untracked +| 3|0x00000005c1800000, 0x00000005c1800000, 0x00000005c2000000| 0%| F| |TAMS 0x00000005c1800000, 0x00000005c1800000| Untracked +| 4|0x00000005c2000000, 0x00000005c207c000, 0x00000005c2800000| 6%| O| |TAMS 0x00000005c207c000, 0x00000005c2000000| Untracked +| 5|0x00000005c2800000, 0x00000005c2800000, 0x00000005c3000000| 0%| F| |TAMS 0x00000005c2800000, 0x00000005c2800000| Untracked +| 6|0x00000005c3000000, 0x00000005c3000000, 0x00000005c3800000| 0%| F| |TAMS 0x00000005c3000000, 0x00000005c3000000| Untracked +| 7|0x00000005c3800000, 0x00000005c3800000, 0x00000005c4000000| 0%| F| |TAMS 0x00000005c3800000, 0x00000005c3800000| Untracked +| 8|0x00000005c4000000, 0x00000005c4000000, 0x00000005c4800000| 0%| F| |TAMS 0x00000005c4000000, 0x00000005c4000000| Untracked +| 9|0x00000005c4800000, 0x00000005c4800000, 0x00000005c5000000| 0%| F| |TAMS 0x00000005c4800000, 0x00000005c4800000| Untracked +| 10|0x00000005c5000000, 0x00000005c5000000, 0x00000005c5800000| 0%| F| |TAMS 0x00000005c5000000, 0x00000005c5000000| Untracked +| 11|0x00000005c5800000, 0x00000005c5800000, 0x00000005c6000000| 0%| F| |TAMS 0x00000005c5800000, 0x00000005c5800000| Untracked +| 12|0x00000005c6000000, 0x00000005c6000000, 0x00000005c6800000| 0%| F| |TAMS 0x00000005c6000000, 0x00000005c6000000| Untracked +| 13|0x00000005c6800000, 0x00000005c6800000, 0x00000005c7000000| 0%| F| |TAMS 0x00000005c6800000, 0x00000005c6800000| Untracked +| 14|0x00000005c7000000, 0x00000005c71d6220, 0x00000005c7800000| 22%| S|CS|TAMS 0x00000005c7000000, 0x00000005c7000000| Complete +| 15|0x00000005c7800000, 0x00000005c8000000, 0x00000005c8000000|100%| S|CS|TAMS 0x00000005c7800000, 0x00000005c7800000| Complete +| 16|0x00000005c8000000, 0x00000005c8800000, 0x00000005c8800000|100%| S|CS|TAMS 0x00000005c8000000, 0x00000005c8000000| Complete +| 17|0x00000005c8800000, 0x00000005c8800000, 0x00000005c9000000| 0%| F| |TAMS 0x00000005c8800000, 0x00000005c8800000| Untracked +| 18|0x00000005c9000000, 0x00000005c9000000, 0x00000005c9800000| 0%| F| |TAMS 0x00000005c9000000, 0x00000005c9000000| Untracked +| 19|0x00000005c9800000, 0x00000005c9800000, 0x00000005ca000000| 0%| F| |TAMS 0x00000005c9800000, 0x00000005c9800000| Untracked +| 20|0x00000005ca000000, 0x00000005ca000000, 0x00000005ca800000| 0%| F| |TAMS 0x00000005ca000000, 0x00000005ca000000| Untracked +| 21|0x00000005ca800000, 0x00000005ca800000, 0x00000005cb000000| 0%| F| |TAMS 0x00000005ca800000, 0x00000005ca800000| Untracked +| 22|0x00000005cb000000, 0x00000005cb000000, 0x00000005cb800000| 0%| F| |TAMS 0x00000005cb000000, 0x00000005cb000000| Untracked +| 23|0x00000005cb800000, 0x00000005cb800000, 0x00000005cc000000| 0%| F| |TAMS 0x00000005cb800000, 0x00000005cb800000| Untracked +| 24|0x00000005cc000000, 0x00000005cc5eec00, 0x00000005cc800000| 74%| E| |TAMS 0x00000005cc000000, 0x00000005cc000000| Complete +| 25|0x00000005cc800000, 0x00000005cd000000, 0x00000005cd000000|100%| E|CS|TAMS 0x00000005cc800000, 0x00000005cc800000| Complete +| 26|0x00000005cd000000, 0x00000005cd800000, 0x00000005cd800000|100%| E|CS|TAMS 0x00000005cd000000, 0x00000005cd000000| Complete +| 27|0x00000005cd800000, 0x00000005ce000000, 0x00000005ce000000|100%| E|CS|TAMS 0x00000005cd800000, 0x00000005cd800000| Complete +| 64|0x00000005e0000000, 0x00000005e0800000, 0x00000005e0800000|100%| E|CS|TAMS 0x00000005e0000000, 0x00000005e0000000| Complete +| 71|0x00000005e3800000, 0x00000005e4000000, 0x00000005e4000000|100%| E|CS|TAMS 0x00000005e3800000, 0x00000005e3800000| Complete +|1150|0x00000007ff000000, 0x00000007ff778000, 0x00000007ff800000| 93%|OA| |TAMS 0x00000007ff778000, 0x00000007ff000000| Untracked +|1151|0x00000007ff800000, 0x00000007ff880000, 0x0000000800000000| 6%|CA| |TAMS 0x00000007ff880000, 0x00000007ff800000| Untracked + +Card table byte_map: [0x0000000119200000,0x000000011a400000] _byte_map_base: 0x0000000116400000 + +Marking Bits (Prev, Next): (CMBitMap*) 0x000000012c82f250, (CMBitMap*) 0x000000012c82f210 + Prev Bits: [0x0000000141000000, 0x000000014a000000) + Next Bits: [0x0000000138000000, 0x0000000141000000) + +Polling page: 0x0000000100da4000 + +Metaspace: + +Usage: + Non-class: 53.63 MB used. + Class: 8.26 MB used. + Both: 61.90 MB used. + +Virtual space: + Non-class space: 64.00 MB reserved, 53.88 MB ( 84%) committed, 1 nodes. + Class space: 1.00 GB reserved, 8.50 MB ( <1%) committed, 1 nodes. + Both: 1.06 GB reserved, 62.38 MB ( 6%) committed. + +Chunk freelists: + Non-Class: 10.08 MB + Class: 7.50 MB + Both: 17.58 MB + +MaxMetaspaceSize: unlimited +CompressedClassSpaceSize: 1.00 GB +Initial GC threshold: 21.00 MB +Current GC threshold: 99.06 MB +CDS: on +MetaspaceReclaimPolicy: balanced + - commit_granule_bytes: 65536. + - commit_granule_words: 8192. + - virtual_space_node_default_size: 8388608. + - enlarge_chunks_in_place: 1. + - new_chunks_are_fully_committed: 0. + - uncommit_free_chunks: 1. + - use_allocation_guard: 0. + - handle_deallocations: 1. + + +Internal statistics: + +num_allocs_failed_limit: 9. +num_arena_births: 684. +num_arena_deaths: 0. +num_vsnodes_births: 2. +num_vsnodes_deaths: 0. +num_space_committed: 998. +num_space_uncommitted: 0. +num_chunks_returned_to_freelist: 9. +num_chunks_taken_from_freelist: 2682. +num_chunk_merges: 6. +num_chunk_splits: 2009. +num_chunks_enlarged: 1609. +num_inconsistent_stats: 0. + +CodeCache: size=49152Kb used=12939Kb max_used=12939Kb free=36212Kb + bounds [0x000000010c100000, 0x000000010cdb0000, 0x000000010f100000] + total_blobs=6555 nmethods=5927 adapters=554 + compilation: enabled + stopped_count=0, restarted_count=0 + full_count=0 + +Compilation events (20 events): +Event: 9.067 Thread 0x000000010b809c00 nmethod 6207 0x000000010cd9aa90 code [0x000000010cd9ac40, 0x000000010cd9ae18] +Event: 9.067 Thread 0x000000010b809c00 6211 1 org.springframework.beans.factory.config.BeanDefinitionVisitor::visitScope (33 bytes) +Event: 9.067 Thread 0x000000010f80b800 nmethod 6210 0x000000010cd9af90 code [0x000000010cd9b140, 0x000000010cd9b358] +Event: 9.067 Thread 0x000000011e00aa00 nmethod 6208 0x000000010cd9b510 code [0x000000010cd9b700, 0x000000010cd9ba38] +Event: 9.067 Thread 0x000000010b809c00 nmethod 6211 0x000000010cd9bc90 code [0x000000010cd9be40, 0x000000010cd9c058] +Event: 9.068 Thread 0x000000011e00a400 nmethod 6206 0x000000010cd9c210 code [0x000000010cd9c580, 0x000000010cd9d558] +Event: 9.068 Thread 0x000000010b809c00 6212 1 org.springframework.beans.AbstractNestablePropertyAccessor::getWrappedInstance (22 bytes) +Event: 9.068 Thread 0x000000010b809c00 nmethod 6212 0x000000010cd9e010 code [0x000000010cd9e1c0, 0x000000010cd9e338] +Event: 9.073 Thread 0x000000011e00a400 6213 1 java.lang.reflect.Constructor::getParameterTypes (11 bytes) +Event: 9.073 Thread 0x000000011e00a400 nmethod 6213 0x000000010cd9e410 code [0x000000010cd9e5c0, 0x000000010cd9e6f8] +Event: 9.126 Thread 0x000000011e00aa00 6216 1 java.util.regex.Pattern::qtype (39 bytes) +Event: 9.126 Thread 0x000000010b809c00 6217 1 java.util.regex.Pattern::sequence (647 bytes) +Event: 9.126 Thread 0x000000010f80b800 6218 1 java.util.regex.Pattern$BranchConn::study (5 bytes) +Event: 9.126 Thread 0x000000010f80b800 nmethod 6218 0x000000010cd9f090 code [0x000000010cd9f200, 0x000000010cd9f2d8] +Event: 9.126 Thread 0x000000011e00aa00 nmethod 6216 0x000000010cd9f390 code [0x000000010cd9f580, 0x000000010cd9f8b8] +Event: 9.127 Thread 0x000000011e00a400 6219 1 jdk.internal.misc.Unsafe::putReferenceOpaque (9 bytes) +Event: 9.127 Thread 0x000000011e00a400 nmethod 6219 0x000000010cd9fa90 code [0x000000010cd9fc00, 0x000000010cd9fd18] +Event: 9.128 Thread 0x000000010b809c00 nmethod 6217 0x000000010cd9fd90 code [0x000000010cda0180, 0x000000010cda1618] +Event: 9.128 Thread 0x000000011e00aa00 6220 1 java.util.IdentityHashMap::put (137 bytes) +Event: 9.129 Thread 0x000000011e00aa00 nmethod 6220 0x000000010cda2290 code [0x000000010cda2480, 0x000000010cda2998] + +GC Heap History (20 events): +Event: 0.442 GC heap before +{Heap before GC invocations=1 (full 0): + garbage-first heap total 606208K, used 38051K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 3 young (24576K), 1 survivors (8192K) + Metaspace used 9689K, committed 9856K, reserved 1114112K + class space used 1076K, committed 1152K, reserved 1048576K +} +Event: 0.444 GC heap after +{Heap after GC invocations=2 (full 0): + garbage-first heap total 606208K, used 23419K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 1 young (8192K), 1 survivors (8192K) + Metaspace used 9689K, committed 9856K, reserved 1114112K + class space used 1076K, committed 1152K, reserved 1048576K +} +Event: 0.807 GC heap before +{Heap before GC invocations=2 (full 0): + garbage-first heap total 606208K, used 47995K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 5 young (40960K), 1 survivors (8192K) + Metaspace used 13722K, committed 13888K, reserved 1114112K + class space used 1603K, committed 1664K, reserved 1048576K +} +Event: 0.813 GC heap after +{Heap after GC invocations=3 (full 0): + garbage-first heap total 606208K, used 27477K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 1 young (8192K), 1 survivors (8192K) + Metaspace used 13722K, committed 13888K, reserved 1114112K + class space used 1603K, committed 1664K, reserved 1048576K +} +Event: 1.392 GC heap before +{Heap before GC invocations=3 (full 0): + garbage-first heap total 606208K, used 68437K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 7 young (57344K), 1 survivors (8192K) + Metaspace used 21201K, committed 21504K, reserved 1114112K + class space used 2676K, committed 2816K, reserved 1048576K +} +Event: 1.395 GC heap after +{Heap after GC invocations=4 (full 0): + garbage-first heap total 606208K, used 31119K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 1 young (8192K), 1 survivors (8192K) + Metaspace used 21201K, committed 21504K, reserved 1114112K + class space used 2676K, committed 2816K, reserved 1048576K +} +Event: 2.847 GC heap before +{Heap before GC invocations=5 (full 0): + garbage-first heap total 196608K, used 137615K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 14 young (114688K), 1 survivors (8192K) + Metaspace used 32674K, committed 33088K, reserved 1114112K + class space used 4093K, committed 4288K, reserved 1048576K +} +Event: 2.861 GC heap after +{Heap after GC invocations=6 (full 0): + garbage-first heap total 196608K, used 32785K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 1 young (8192K), 1 survivors (8192K) + Metaspace used 32674K, committed 33088K, reserved 1114112K + class space used 4093K, committed 4288K, reserved 1048576K +} +Event: 2.927 GC heap before +{Heap before GC invocations=6 (full 0): + garbage-first heap total 196608K, used 40977K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 2 young (16384K), 1 survivors (8192K) + Metaspace used 34086K, committed 34432K, reserved 1114112K + class space used 4246K, committed 4416K, reserved 1048576K +} +Event: 2.931 GC heap after +{Heap after GC invocations=7 (full 0): + garbage-first heap total 196608K, used 33192K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 1 young (8192K), 1 survivors (8192K) + Metaspace used 34086K, committed 34432K, reserved 1114112K + class space used 4246K, committed 4416K, reserved 1048576K +} +Event: 3.207 GC heap before +{Heap before GC invocations=7 (full 0): + garbage-first heap total 196608K, used 41384K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 4 young (32768K), 1 survivors (8192K) + Metaspace used 35986K, committed 36288K, reserved 1114112K + class space used 4497K, committed 4672K, reserved 1048576K +} +Event: 3.213 GC heap after +{Heap after GC invocations=8 (full 0): + garbage-first heap total 196608K, used 33908K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 1 young (8192K), 1 survivors (8192K) + Metaspace used 35986K, committed 36288K, reserved 1114112K + class space used 4497K, committed 4672K, reserved 1048576K +} +Event: 4.154 GC heap before +{Heap before GC invocations=9 (full 0): + garbage-first heap total 196608K, used 115828K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 12 young (98304K), 1 survivors (8192K) + Metaspace used 45522K, committed 45952K, reserved 1114112K + class space used 5884K, committed 6080K, reserved 1048576K +} +Event: 4.157 GC heap after +{Heap after GC invocations=10 (full 0): + garbage-first heap total 262144K, used 37091K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 1 young (8192K), 1 survivors (8192K) + Metaspace used 45522K, committed 45952K, reserved 1114112K + class space used 5884K, committed 6080K, reserved 1048576K +} +Event: 4.268 GC heap before +{Heap before GC invocations=10 (full 0): + garbage-first heap total 262144K, used 45283K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 2 young (16384K), 1 survivors (8192K) + Metaspace used 46797K, committed 47168K, reserved 1114112K + class space used 6085K, committed 6272K, reserved 1048576K +} +Event: 4.273 GC heap after +{Heap after GC invocations=11 (full 0): + garbage-first heap total 262144K, used 37205K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 1 young (8192K), 1 survivors (8192K) + Metaspace used 46797K, committed 47168K, reserved 1114112K + class space used 6085K, committed 6272K, reserved 1048576K +} +Event: 6.516 GC heap before +{Heap before GC invocations=11 (full 0): + garbage-first heap total 262144K, used 176469K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 18 young (147456K), 1 survivors (8192K) + Metaspace used 55389K, committed 55808K, reserved 1114112K + class space used 7288K, committed 7488K, reserved 1048576K +} +Event: 6.521 GC heap after +{Heap after GC invocations=12 (full 0): + garbage-first heap total 262144K, used 45059K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 2 young (16384K), 2 survivors (16384K) + Metaspace used 55389K, committed 55808K, reserved 1114112K + class space used 7288K, committed 7488K, reserved 1048576K +} +Event: 8.053 GC heap before +{Heap before GC invocations=12 (full 0): + garbage-first heap total 262144K, used 135171K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 15 young (122880K), 2 survivors (16384K) + Metaspace used 60370K, committed 60800K, reserved 1114112K + class space used 7985K, committed 8192K, reserved 1048576K +} +Event: 8.065 GC heap after +{Heap after GC invocations=13 (full 0): + garbage-first heap total 262144K, used 51496K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 3 young (24576K), 3 survivors (24576K) + Metaspace used 60370K, committed 60800K, reserved 1114112K + class space used 7985K, committed 8192K, reserved 1048576K +} + +Dll operation events (11 events): +Event: 0.007 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libjava.dylib +Event: 0.007 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libzip.dylib +Event: 0.078 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libinstrument.dylib +Event: 0.081 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libnio.dylib +Event: 0.084 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libnet.dylib +Event: 0.124 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libjimage.dylib +Event: 0.134 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libzip.dylib +Event: 0.211 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libmanagement.dylib +Event: 0.216 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libmanagement_ext.dylib +Event: 0.319 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libextnet.dylib +Event: 6.751 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libverify.dylib + +Deoptimization events (20 events): +Event: 9.084 Thread 0x000000010b808a00 DEOPT PACKING pc=0x000000010c56ea28 sp=0x000000016f37db60 +Event: 9.084 Thread 0x000000010b808a00 DEOPT UNPACKING pc=0x000000010c14777c sp=0x000000016f37d800 mode 1 +Event: 9.084 Thread 0x000000010b808a00 DEOPT PACKING pc=0x000000010c56dcbc sp=0x000000016f37dc30 +Event: 9.084 Thread 0x000000010b808a00 DEOPT UNPACKING pc=0x000000010c14777c sp=0x000000016f37d900 mode 1 +Event: 9.085 Thread 0x000000010b808a00 DEOPT PACKING pc=0x000000010c56ea28 sp=0x000000016f37db60 +Event: 9.085 Thread 0x000000010b808a00 DEOPT UNPACKING pc=0x000000010c14777c sp=0x000000016f37d800 mode 1 +Event: 9.085 Thread 0x000000010b808a00 DEOPT PACKING pc=0x000000010c56dcbc sp=0x000000016f37dc30 +Event: 9.085 Thread 0x000000010b808a00 DEOPT UNPACKING pc=0x000000010c14777c sp=0x000000016f37d900 mode 1 +Event: 9.104 Thread 0x000000010b808a00 DEOPT PACKING pc=0x000000010c56ea28 sp=0x000000016f37db80 +Event: 9.104 Thread 0x000000010b808a00 DEOPT UNPACKING pc=0x000000010c14777c sp=0x000000016f37d820 mode 1 +Event: 9.104 Thread 0x000000010b808a00 DEOPT PACKING pc=0x000000010c56dcbc sp=0x000000016f37dc50 +Event: 9.104 Thread 0x000000010b808a00 DEOPT UNPACKING pc=0x000000010c14777c sp=0x000000016f37d920 mode 1 +Event: 9.105 Thread 0x000000010b808a00 DEOPT PACKING pc=0x000000010c56ea28 sp=0x000000016f37db60 +Event: 9.105 Thread 0x000000010b808a00 DEOPT UNPACKING pc=0x000000010c14777c sp=0x000000016f37d800 mode 1 +Event: 9.105 Thread 0x000000010b808a00 DEOPT PACKING pc=0x000000010c56dcbc sp=0x000000016f37dc30 +Event: 9.105 Thread 0x000000010b808a00 DEOPT UNPACKING pc=0x000000010c14777c sp=0x000000016f37d900 mode 1 +Event: 9.106 Thread 0x000000010b808a00 DEOPT PACKING pc=0x000000010c56ea28 sp=0x000000016f37db80 +Event: 9.106 Thread 0x000000010b808a00 DEOPT UNPACKING pc=0x000000010c14777c sp=0x000000016f37d820 mode 1 +Event: 9.106 Thread 0x000000010b808a00 DEOPT PACKING pc=0x000000010c56dcbc sp=0x000000016f37dc50 +Event: 9.106 Thread 0x000000010b808a00 DEOPT UNPACKING pc=0x000000010c14777c sp=0x000000016f37d920 mode 1 + +Classes unloaded (0 events): +No events + +Classes redefined (1 events): +Event: 0.114 Thread 0x000000012b8059d0 redefined class name=java.lang.Throwable, count=1 + +Internal exceptions (20 events): +Event: 6.369 Thread 0x000000010b808a00 Exception (0x00000005c61a56a8) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 759] +Event: 6.370 Thread 0x000000010b808a00 Exception (0x00000005c61ac0f8) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 759] +Event: 6.371 Thread 0x000000010b808a00 Exception (0x00000005c61b00f0) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 759] +Event: 6.388 Thread 0x000000012b45ba00 Exception (0x00000005c7a137e0) +thrown [src/hotspot/share/runtime/reflection.cpp, line 1121] +Event: 6.404 Thread 0x000000010b808a00 Exception (0x00000005c6294068) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 759] +Event: 6.891 Thread 0x000000012b45ba00 Exception (0x00000005cbfd3b50) +thrown [src/hotspot/share/runtime/reflection.cpp, line 1121] +Event: 7.393 Thread 0x000000012b45ba00 Exception (0x00000005cbfe0578) +thrown [src/hotspot/share/runtime/reflection.cpp, line 1121] +Event: 7.899 Thread 0x000000012b45ba00 Exception (0x00000005c9409f48) +thrown [src/hotspot/share/runtime/reflection.cpp, line 1121] +Event: 8.401 Thread 0x000000012b45ba00 Exception (0x00000005e02167a0) +thrown [src/hotspot/share/runtime/reflection.cpp, line 1121] +Event: 8.927 Thread 0x000000012b45ba00 Exception (0x00000005ccc28338) +thrown [src/hotspot/share/runtime/reflection.cpp, line 1121] +Event: 8.948 Thread 0x000000010b808a00 Exception (0x00000005ccc21b10) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 759] +Event: 8.948 Thread 0x000000010b808a00 Exception (0x00000005ccc26808) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 759] +Event: 8.949 Thread 0x000000010b808a00 Exception (0x00000005ccc3f340) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 759] +Event: 8.949 Thread 0x000000010b808a00 Exception (0x00000005ccc4b0d8) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 759] +Event: 9.084 Thread 0x000000010b808a00 Exception (0x00000005cc19f8f0) +thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 248] +Event: 9.085 Thread 0x000000010b808a00 Exception (0x00000005cc1a9e48) +thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 248] +Event: 9.105 Thread 0x000000010b808a00 Exception (0x00000005cc24dc60) +thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 248] +Event: 9.105 Thread 0x000000010b808a00 Exception (0x00000005cc2577d0) +thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 248] +Event: 9.106 Thread 0x000000010b808a00 Exception (0x00000005cc2624f8) +thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 248] +Event: 9.126 Thread 0x000000011ebea000 Exception (0x00000005cc5769a0) +thrown [src/hotspot/share/prims/jni.cpp, line 516] + +VM Operations (20 events): +Event: 8.053 Executing VM operation: CollectForMetadataAllocation +Event: 8.070 Executing VM operation: CollectForMetadataAllocation done +Event: 8.080 Executing VM operation: G1PauseRemark +Event: 8.084 Executing VM operation: G1PauseRemark done +Event: 8.088 Executing VM operation: G1PauseCleanup +Event: 8.088 Executing VM operation: G1PauseCleanup done +Event: 8.450 Executing VM operation: HandshakeAllThreads +Event: 8.450 Executing VM operation: HandshakeAllThreads done +Event: 8.461 Executing VM operation: HandshakeAllThreads +Event: 8.462 Executing VM operation: HandshakeAllThreads done +Event: 8.462 Executing VM operation: HandshakeAllThreads +Event: 8.462 Executing VM operation: HandshakeAllThreads done +Event: 8.957 Executing VM operation: HandshakeAllThreads +Event: 8.957 Executing VM operation: HandshakeAllThreads done +Event: 8.964 Executing VM operation: HandshakeAllThreads +Event: 8.964 Executing VM operation: HandshakeAllThreads done +Event: 8.980 Executing VM operation: HandshakeAllThreads +Event: 8.980 Executing VM operation: HandshakeAllThreads done +Event: 9.027 Executing VM operation: ICBufferFull +Event: 9.033 Executing VM operation: ICBufferFull done + +Events (20 events): +Event: 9.126 loading class java/net/SocksSocketImpl$3 done +Event: 9.126 loading class sun/net/util/SocketExceptions +Event: 9.126 loading class sun/net/util/SocketExceptions done +Event: 9.127 Thread 0x000000011f24c800 Thread added: 0x000000011f24c800 +Event: 9.127 Protecting memory [0x000000032ab00000,0x000000032ab0c000] with protection modes 0 +Event: 9.127 loading class java/lang/Throwable$WrappedPrintWriter +Event: 9.127 loading class java/lang/Throwable$WrappedPrintWriter done +Event: 9.128 loading class com/intellij/rt/debugger/agent/CaptureStorage$9 +Event: 9.128 loading class com/intellij/rt/debugger/agent/CaptureStorage$Callable +Event: 9.128 loading class com/intellij/rt/debugger/agent/CaptureStorage$Callable done +Event: 9.128 loading class com/intellij/rt/debugger/agent/CaptureStorage$9 done +Event: 9.128 loading class jdk/internal/loader/BootLoader$PackageHelper$1 +Event: 9.128 loading class jdk/internal/loader/BootLoader$PackageHelper$1 done +Event: 9.128 loading class jdk/internal/loader/BootLoader$PackageHelper$2 +Event: 9.128 loading class jdk/internal/loader/BootLoader$PackageHelper$2 done +Event: 9.128 loading class java/util/jar/JarInputStream +Event: 9.128 loading class java/util/zip/ZipInputStream +Event: 9.129 loading class java/util/zip/ZipInputStream done +Event: 9.129 loading class java/util/jar/JarInputStream done +Event: 9.129 loading class com/intellij/rt/debugger/agent/CaptureStorage$StackData + + +Dynamic libraries: +0x0000000100d44000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libjli.dylib +0x0000000196c18000 /usr/lib/libz.1.dylib +0x0000000196cce000 /usr/lib/libSystem.B.dylib +0x0000000196cc8000 /usr/lib/system/libcache.dylib +0x0000000196c83000 /usr/lib/system/libcommonCrypto.dylib +0x0000000196cae000 /usr/lib/system/libcompiler_rt.dylib +0x0000000196ca3000 /usr/lib/system/libcopyfile.dylib +0x0000000186bb6000 /usr/lib/system/libcorecrypto.dylib +0x0000000186cb6000 /usr/lib/system/libdispatch.dylib +0x0000000186a53000 /usr/lib/system/libdyld.dylib +0x0000000196cbe000 /usr/lib/system/libkeymgr.dylib +0x0000000196c66000 /usr/lib/system/libmacho.dylib +0x0000000195ef9000 /usr/lib/system/libquarantine.dylib +0x0000000196cbb000 /usr/lib/system/libremovefile.dylib +0x000000018d629000 /usr/lib/system/libsystem_asl.dylib +0x0000000186b3c000 /usr/lib/system/libsystem_blocks.dylib +0x0000000186d01000 /usr/lib/system/libsystem_c.dylib +0x0000000196cb2000 /usr/lib/system/libsystem_collections.dylib +0x0000000194899000 /usr/lib/system/libsystem_configuration.dylib +0x0000000193487000 /usr/lib/system/libsystem_containermanager.dylib +0x0000000196698000 /usr/lib/system/libsystem_coreservices.dylib +0x000000018ae50000 /usr/lib/system/libsystem_darwin.dylib +0x000000028c8a4000 /usr/lib/system/libsystem_darwindirectory.dylib +0x0000000196cbf000 /usr/lib/system/libsystem_dnssd.dylib +0x000000028c8a8000 /usr/lib/system/libsystem_eligibility.dylib +0x0000000186cfe000 /usr/lib/system/libsystem_featureflags.dylib +0x0000000186e83000 /usr/lib/system/libsystem_info.dylib +0x0000000196c27000 /usr/lib/system/libsystem_m.dylib +0x0000000186c65000 /usr/lib/system/libsystem_malloc.dylib +0x000000018d58c000 /usr/lib/system/libsystem_networkextension.dylib +0x000000018b2bb000 /usr/lib/system/libsystem_notify.dylib +0x000000019489e000 /usr/lib/system/libsystem_sandbox.dylib +0x000000028c8b3000 /usr/lib/system/libsystem_sanitizers.dylib +0x0000000196cb7000 /usr/lib/system/libsystem_secinit.dylib +0x0000000186e2f000 /usr/lib/system/libsystem_kernel.dylib +0x0000000186e7a000 /usr/lib/system/libsystem_platform.dylib +0x0000000186e6d000 /usr/lib/system/libsystem_pthread.dylib +0x000000018f1e2000 /usr/lib/system/libsystem_symptoms.dylib +0x0000000186b95000 /usr/lib/system/libsystem_trace.dylib +0x000000028c8bb000 /usr/lib/system/libsystem_trial.dylib +0x0000000196c91000 /usr/lib/system/libunwind.dylib +0x0000000186b40000 /usr/lib/system/libxpc.dylib +0x0000000186a00000 /usr/lib/libobjc.A.dylib +0x0000000186eb3000 /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation +0x000000019a5c3000 /usr/lib/swift/libswiftCore.dylib +0x0000000186e14000 /usr/lib/libc++abi.dylib +0x000000028ac91000 /usr/lib/libRosetta.dylib +0x0000000186d83000 /usr/lib/libc++.1.dylib +0x0000000188722000 /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation +0x00000001a41a3000 /usr/lib/swift/libswiftObjectiveC.dylib +0x000000028c10d000 /usr/lib/libswiftPrespecialized.dylib +0x0000000188391000 /System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration +0x0000000191703000 /System/Library/PrivateFrameworks/CoreAutoLayout.framework/Versions/A/CoreAutoLayout +0x0000000196cd0000 /usr/lib/libfakelink.dylib +0x0000000196f79000 /usr/lib/libcompression.dylib +0x000000018d1d6000 /System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork +0x0000000190b34000 /System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration +0x0000000196d23000 /usr/lib/libarchive.2.dylib +0x0000000190a39000 /usr/lib/libDiagnosticMessagesClient.dylib +0x000000018ab7a000 /usr/lib/libicucore.A.dylib +0x000000019174c000 /usr/lib/libxml2.2.dylib +0x000000019f452000 /System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices +0x00000001948ac000 /usr/lib/liblangid.dylib +0x000000018b1d2000 /System/Library/Frameworks/IOKit.framework/Versions/A/IOKit +0x000000019d0c4000 /System/Library/Frameworks/Combine.framework/Versions/A/Combine +0x000000023fff3000 /System/Library/PrivateFrameworks/CollectionsInternal.framework/Versions/A/CollectionsInternal +0x000000026c039000 /System/Library/PrivateFrameworks/ReflectionInternal.framework/Versions/A/ReflectionInternal +0x000000026cf8d000 /System/Library/PrivateFrameworks/RuntimeInternal.framework/Versions/A/RuntimeInternal +0x0000000196cd2000 /System/Library/PrivateFrameworks/SoftLinking.framework/Versions/A/SoftLinking +0x00000001b488c000 /usr/lib/swift/libswiftCoreFoundation.dylib +0x00000001b164f000 /usr/lib/swift/libswiftDarwin.dylib +0x00000001a11c9000 /usr/lib/swift/libswiftDispatch.dylib +0x00000001b48ed000 /usr/lib/swift/libswiftIOKit.dylib +0x000000028c550000 /usr/lib/swift/libswiftSystem.dylib +0x00000001b489f000 /usr/lib/swift/libswiftXPC.dylib +0x000000028c582000 /usr/lib/swift/libswift_Builtin_float.dylib +0x000000028c583000 /usr/lib/swift/libswift_Concurrency.dylib +0x000000028c60f000 /usr/lib/swift/libswift_DarwinFoundation1.dylib +0x000000028c6b3000 /usr/lib/swift/libswift_StringProcessing.dylib +0x00000001a41a7000 /usr/lib/swift/libswiftos.dylib +0x000000018b152000 /System/Library/PrivateFrameworks/CoreServicesInternal.framework/Versions/A/CoreServicesInternal +0x0000000196c9b000 /usr/lib/liboah.dylib +0x000000018a75a000 /System/Library/Frameworks/Security.framework/Versions/A/Security +0x00000001a35d7000 /System/Library/PrivateFrameworks/DiskImages.framework/Versions/A/DiskImages +0x00000001b10f3000 /System/Library/Frameworks/NetFS.framework/Versions/A/NetFS +0x00000001916c8000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/FSEvents +0x000000018ae5a000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/CarbonCore +0x0000000190aa8000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Metadata +0x000000019669f000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/OSServices.framework/Versions/A/OSServices +0x0000000196e1b000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SearchKit.framework/Versions/A/SearchKit +0x000000018f15c000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/AE.framework/Versions/A/AE +0x0000000187412000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/LaunchServices +0x0000000198224000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/DictionaryServices.framework/Versions/A/DictionaryServices +0x00000001916d5000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SharedFileList.framework/Versions/A/SharedFileList +0x0000000196eae000 /usr/lib/libapple_nghttp2.dylib +0x000000018ed78000 /usr/lib/libsqlite3.dylib +0x000000018ef61000 /System/Library/Frameworks/Accounts.framework/Versions/A/Accounts +0x00000001a3819000 /System/Library/PrivateFrameworks/AppSupport.framework/Versions/A/AppSupport +0x00000001b363e000 /System/Library/Frameworks/AVFoundation.framework/Versions/A/AVFoundation +0x0000000190a09000 /System/Library/PrivateFrameworks/CoreAnalytics.framework/Versions/A/CoreAnalytics +0x000000018dc1c000 /System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics +0x000000019b2fe000 /System/Library/Frameworks/GSS.framework/Versions/A/GSS +0x00000001996e6000 /System/Library/PrivateFrameworks/InternationalSupport.framework/Versions/A/InternationalSupport +0x000000018f0f0000 /System/Library/PrivateFrameworks/RunningBoardServices.framework/Versions/A/RunningBoardServices +0x00000001a412b000 /System/Library/PrivateFrameworks/StreamingZip.framework/Versions/A/StreamingZip +0x000000018d5a7000 /usr/lib/libenergytrace.dylib +0x000000018f1eb000 /System/Library/Frameworks/Network.framework/Versions/A/Network +0x0000000195f21000 /usr/lib/libbsm.0.dylib +0x0000000196c6a000 /usr/lib/system/libkxld.dylib +0x000000023b5c5000 /System/Library/PrivateFrameworks/AppleKeyStore.framework/Versions/A/AppleKeyStore +0x000000028a993000 /usr/lib/libCoreEntitlements.dylib +0x0000000260705000 /System/Library/PrivateFrameworks/MessageSecurity.framework/Versions/A/MessageSecurity +0x000000018ed5c000 /System/Library/PrivateFrameworks/ProtocolBuffer.framework/Versions/A/ProtocolBuffer +0x00000001a05d8000 /System/Library/PrivateFrameworks/SymptomDiagnosticReporter.framework/Versions/A/SymptomDiagnosticReporter +0x0000000198469000 /System/Library/PrivateFrameworks/CrashReporterSupport.framework/Versions/A/CrashReporterSupport +0x000000018d5a9000 /usr/lib/libMobileGestalt.dylib +0x000000019667f000 /System/Library/PrivateFrameworks/AppleFSCompression.framework/Versions/A/AppleFSCompression +0x0000000195f09000 /usr/lib/libcoretls.dylib +0x000000019829a000 /usr/lib/libcoretls_cfhelpers.dylib +0x0000000196f73000 /usr/lib/libpam.2.dylib +0x0000000198310000 /usr/lib/libxar.1.dylib +0x000000019829c000 /System/Library/PrivateFrameworks/APFS.framework/Versions/A/APFS +0x0000000278713000 /System/Library/PrivateFrameworks/SwiftASN1Internal.framework/Versions/A/SwiftASN1Internal +0x000000019831f000 /usr/lib/libutil.dylib +0x00000001948a7000 /System/Library/PrivateFrameworks/AppleSystemInfo.framework/Versions/A/AppleSystemInfo +0x0000000195bd0000 /System/Library/PrivateFrameworks/IOMobileFramebuffer.framework/Versions/A/IOMobileFramebuffer +0x00000001934c0000 /System/Library/Frameworks/IOSurface.framework/Versions/A/IOSurface +0x00000001a2f37000 /System/Library/PrivateFrameworks/CoreWiFi.framework/Versions/A/CoreWiFi +0x00000001b474c000 /System/Library/PrivateFrameworks/LoggingSupport.framework/Versions/A/LoggingSupport +0x000000019b361000 /System/Library/PrivateFrameworks/MobileAsset.framework/Versions/A/MobileAsset +0x00000001a05e8000 /System/Library/PrivateFrameworks/PowerLog.framework/Versions/A/PowerLog +0x00000001a1aaa000 /System/Library/PrivateFrameworks/Rapport.framework/Versions/A/Rapport +0x000000023416e000 /System/Library/Frameworks/SwiftData.framework/Versions/A/SwiftData +0x000000018cc0a000 /System/Library/Frameworks/UniformTypeIdentifiers.framework/Versions/A/UniformTypeIdentifiers +0x00000001918f5000 /System/Library/PrivateFrameworks/UserManagement.framework/Versions/A/UserManagement +0x000000018d0fd000 /usr/lib/libboringssl.dylib +0x000000018f1d0000 /usr/lib/libdns_services.dylib +0x00000001b3772000 /usr/lib/libquic.dylib +0x000000019a554000 /usr/lib/libusrtcp.dylib +0x000000023c47f000 /System/Library/PrivateFrameworks/AtomicsInternal.framework/Versions/A/AtomicsInternal +0x00000001dab32000 /System/Library/PrivateFrameworks/InternalSwiftProtobuf.framework/Versions/A/InternalSwiftProtobuf +0x000000028c3d7000 /usr/lib/swift/libswiftDistributed.dylib +0x000000028c400000 /usr/lib/swift/libswiftObservation.dylib +0x000000028c53c000 /usr/lib/swift/libswiftSynchronization.dylib +0x00000001948a5000 /System/Library/PrivateFrameworks/AggregateDictionary.framework/Versions/A/AggregateDictionary +0x000000023ccaf000 /System/Library/PrivateFrameworks/BiomeLibrary.framework/Versions/A/BiomeLibrary +0x00000001c38d5000 /System/Library/PrivateFrameworks/BiomeStreams.framework/Versions/A/BiomeStreams +0x00000001bf924000 /System/Library/PrivateFrameworks/BiomeFoundation.framework/Versions/A/BiomeFoundation +0x00000001c9b82000 /System/Library/PrivateFrameworks/BiomePubSub.framework/Versions/A/BiomePubSub +0x000000018e96e000 /System/Library/Frameworks/CoreData.framework/Versions/A/CoreData +0x00000001a510e000 /System/Library/PrivateFrameworks/ProactiveSupport.framework/Versions/A/ProactiveSupport +0x00000002361cc000 /System/Library/Frameworks/_LocationEssentials.framework/Versions/A/_LocationEssentials +0x000000019827b000 /usr/lib/liblzma.5.dylib +0x000000019f6d1000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate +0x0000000195e02000 /System/Library/PrivateFrameworks/MobileKeyBag.framework/Versions/A/MobileKeyBag +0x00000001a3b2d000 /System/Library/PrivateFrameworks/InternationalTextSearch.framework/Versions/A/InternationalTextSearch +0x00000001bbdf7000 /System/Library/PrivateFrameworks/SoftwareUpdateCoreSupport.framework/Versions/A/SoftwareUpdateCoreSupport +0x00000001c4374000 /System/Library/PrivateFrameworks/SoftwareUpdateCoreConnect.framework/Versions/A/SoftwareUpdateCoreConnect +0x00000001a36d1000 /System/Library/PrivateFrameworks/RemoteServiceDiscovery.framework/Versions/A/RemoteServiceDiscovery +0x00000001bb9ce000 /System/Library/PrivateFrameworks/MSUDataAccessor.framework/Versions/A/MSUDataAccessor +0x00000001b691f000 /usr/lib/libbootpolicy.dylib +0x00000001a36e8000 /System/Library/PrivateFrameworks/RemoteXPC.framework/Versions/A/RemoteXPC +0x00000001c37a9000 /usr/lib/libFDR.dylib +0x00000001c9784000 /usr/lib/libamsupport.dylib +0x000000028ac89000 /usr/lib/libReverseProxyDevice.dylib +0x000000023ae33000 /System/Library/PrivateFrameworks/AppleDeviceQuerySupport.framework/Versions/A/AppleDeviceQuerySupport +0x00000001cc94e000 /usr/lib/libpartition2_dynamic.dylib +0x0000000196e8a000 /System/Library/PrivateFrameworks/AppleSauce.framework/Versions/A/AppleSauce +0x000000028a83e000 /usr/lib/libAppleArchive.dylib +0x000000019668b000 /usr/lib/libbz2.1.0.dylib +0x0000000190b3e000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vImage.framework/Versions/A/vImage +0x000000019f42d000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/vecLib +0x0000000198356000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvMisc.dylib +0x0000000187916000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib +0x00000001a3b2c000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices +0x0000000191833000 /System/Library/Frameworks/CoreVideo.framework/Versions/A/CoreVideo +0x000000018e372000 /System/Library/Frameworks/ColorSync.framework/Versions/A/ColorSync +0x0000000189d9a000 /System/Library/Frameworks/CoreText.framework/Versions/A/CoreText +0x0000000193fb3000 /System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO +0x000000019af0e000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/ATS +0x000000018e51a000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/HIServices +0x00000001995dc000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/PrintCore +0x000000019b2c7000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/QD.framework/Versions/A/QD +0x000000019b2c2000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ColorSyncLegacy.framework/Versions/A/ColorSyncLegacy +0x000000019aee0000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/SpeechSynthesis.framework/Versions/A/SpeechSynthesis +0x000000018d665000 /System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLight +0x000000019398e000 /System/Library/PrivateFrameworks/FontServices.framework/libFontParser.dylib +0x000000018effe000 /System/Library/PrivateFrameworks/BaseBoard.framework/Versions/A/BaseBoard +0x00000001a152a000 /System/Library/PrivateFrameworks/BoardServices.framework/Versions/A/BoardServices +0x00000001a33f9000 /System/Library/PrivateFrameworks/BackBoardServices.framework/Versions/A/BackBoardServices +0x000000023cbb4000 /System/Library/PrivateFrameworks/BackBoardHIDEventFoundation.framework/Versions/A/BackBoardHIDEventFoundation +0x00000001898b6000 /System/Library/Frameworks/CoreDisplay.framework/Versions/A/CoreDisplay +0x0000000198f35000 /System/Library/Frameworks/VideoToolbox.framework/Versions/A/VideoToolbox +0x0000000196f71000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/MetalPerformanceShaders +0x000000026af43000 /System/Library/PrivateFrameworks/ProDisplayLibrary.framework/Versions/A/ProDisplayLibrary +0x00000001a722e000 /System/Library/PrivateFrameworks/IOSurfaceAccelerator.framework/Versions/A/IOSurfaceAccelerator +0x00000001934e8000 /System/Library/Frameworks/Metal.framework/Versions/A/Metal +0x00000001934dd000 /System/Library/PrivateFrameworks/IOAccelerator.framework/Versions/A/IOAccelerator +0x00000001937ec000 /System/Library/Frameworks/CoreMedia.framework/Versions/A/CoreMedia +0x000000018d641000 /System/Library/PrivateFrameworks/TCC.framework/Versions/A/TCC +0x0000000198eed000 /System/Library/PrivateFrameworks/WatchdogClient.framework/Versions/A/WatchdogClient +0x0000000190f63000 /System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore +0x0000000198eef000 /System/Library/PrivateFrameworks/MultitouchSupport.framework/Versions/A/MultitouchSupport +0x00000001cc730000 /usr/lib/swift/libswiftAccelerate.dylib +0x00000001b486c000 /usr/lib/swift/libswiftCoreAudio.dylib +0x00000001d08c1000 /usr/lib/swift/libswiftCoreMedia.dylib +0x00000001c2862000 /usr/lib/swift/libswiftMetal.dylib +0x00000001d2074000 /usr/lib/swift/libswiftOSLog.dylib +0x00000001c7c88000 /usr/lib/swift/libswiftQuartzCore.dylib +0x00000001cc720000 /usr/lib/swift/libswiftUniformTypeIdentifiers.dylib +0x000000028c56a000 /usr/lib/swift/libswiftVideoToolbox.dylib +0x00000001b83e6000 /usr/lib/swift/libswiftsimd.dylib +0x00000001c9be3000 /System/Library/PrivateFrameworks/BiomeStorage.framework/Versions/A/BiomeStorage +0x00000002593f4000 /System/Library/PrivateFrameworks/IntelligencePlatformLibrary.framework/Versions/A/IntelligencePlatformLibrary +0x0000000269d07000 /System/Library/PrivateFrameworks/PoirotSchematizer.framework/Versions/A/PoirotSchematizer +0x000000023d56a000 /System/Library/PrivateFrameworks/BiomeSync.framework/Versions/A/BiomeSync +0x000000023cc95000 /System/Library/PrivateFrameworks/BiomeDSL.framework/Versions/A/BiomeDSL +0x00000001e2d3d000 /System/Library/PrivateFrameworks/FeatureFlags.framework/Versions/A/FeatureFlags +0x0000000269d75000 /System/Library/PrivateFrameworks/PoirotUDFs.framework/Versions/A/PoirotUDFs +0x000000028c612000 /usr/lib/swift/libswift_DarwinFoundation2.dylib +0x000000028c613000 /usr/lib/swift/libswift_DarwinFoundation3.dylib +0x00000001a1a9f000 /System/Library/PrivateFrameworks/CoreTime.framework/Versions/A/CoreTime +0x0000000196d08000 /usr/lib/libiconv.2.dylib +0x0000000196c65000 /usr/lib/libcharset.1.dylib +0x0000000269cca000 /System/Library/PrivateFrameworks/PoirotSQLite.framework/Versions/A/PoirotSQLite +0x000000028c614000 /usr/lib/swift/libswift_RegexParser.dylib +0x000000023eba0000 /System/Library/PrivateFrameworks/CascadeSets.framework/Versions/A/CascadeSets +0x000000019b4d8000 /System/Library/PrivateFrameworks/CorePhoneNumbers.framework/Versions/A/CorePhoneNumbers +0x0000000198ce7000 /System/Library/PrivateFrameworks/AppleJPEG.framework/Versions/A/AppleJPEG +0x00000001986c0000 /usr/lib/libexpat.1.dylib +0x00000001994b2000 /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libPng.dylib +0x00000001994dd000 /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libTIFF.dylib +0x00000001995c5000 /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libGIF.dylib +0x0000000198d2c000 /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJP2.dylib +0x00000001983d0000 /usr/lib/libate.dylib +0x000000019956c000 /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJPEG.dylib +0x0000000199563000 /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libRadiance.dylib +0x000000024f044000 /System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/32023/Libraries/libllvm-flatbuffers.dylib +0x0000000249aa3000 /System/Library/PrivateFrameworks/FramePacing.framework/Versions/A/FramePacing +0x000000022cbb3000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreFSCache.dylib +0x000000024abd1000 /System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/32023/Libraries/libGPUCompilerUtils.dylib +0x00000001a15f7000 /System/Library/PrivateFrameworks/GraphicsServices.framework/Versions/A/GraphicsServices +0x000000022cbc1000 /System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL +0x000000022cc12000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLU.dylib +0x000000022cbd5000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGFXShared.dylib +0x000000022cda2000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib +0x000000022cbde000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLImage.dylib +0x000000022cbd2000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCVMSPluginSupport.dylib +0x000000022cbbb000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreVMClient.dylib +0x000000019955e000 /System/Library/PrivateFrameworks/GPUWrangler.framework/Versions/A/GPUWrangler +0x000000019953e000 /System/Library/PrivateFrameworks/IOPresentment.framework/Versions/A/IOPresentment +0x0000000199566000 /System/Library/PrivateFrameworks/DSExternalDisplay.framework/Versions/A/DSExternalDisplay +0x00000002805c9000 /System/Library/PrivateFrameworks/VideoToolboxParavirtualizationSupport.framework/Versions/A/VideoToolboxParavirtualizationSupport +0x0000000198677000 /System/Library/PrivateFrameworks/AppleVA.framework/Versions/A/AppleVA +0x000000022ec3e000 /System/Library/Frameworks/ExtensionFoundation.framework/Versions/A/ExtensionFoundation +0x00000001995cb000 /System/Library/PrivateFrameworks/CMCaptureCore.framework/Versions/A/CMCaptureCore +0x0000000198951000 /usr/lib/libspindump.dylib +0x0000000189fc4000 /System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio +0x0000000198944000 /System/Library/PrivateFrameworks/AppServerSupport.framework/Versions/A/AppServerSupport +0x000000019b2d0000 /System/Library/PrivateFrameworks/perfdata.framework/Versions/A/perfdata +0x00000001899d7000 /System/Library/PrivateFrameworks/AudioToolboxCore.framework/Versions/A/AudioToolboxCore +0x00000001937c2000 /System/Library/PrivateFrameworks/caulk.framework/Versions/A/caulk +0x000000019aec6000 /usr/lib/libAudioStatistics.dylib +0x00000001b3867000 /System/Library/PrivateFrameworks/SystemPolicy.framework/Versions/A/SystemPolicy +0x000000019b174000 /usr/lib/libSMC.dylib +0x00000001bb1dd000 /usr/lib/swift/libswiftCoreMIDI.dylib +0x00000001a651d000 /System/Library/Frameworks/CoreMIDI.framework/Versions/A/CoreMIDI +0x000000019948c000 /usr/lib/libAudioToolboxUtility.dylib +0x000000019b2de000 /usr/lib/libperfcheck.dylib +0x000000023c54e000 /System/Library/PrivateFrameworks/AudioAnalytics.framework/Versions/A/AudioAnalytics +0x00000001da81e000 /System/Library/Frameworks/OSLog.framework/Versions/A/OSLog +0x0000000265777000 /System/Library/PrivateFrameworks/OSEligibility.framework/Versions/A/OSEligibility +0x0000000198746000 /System/Library/PrivateFrameworks/IconServices.framework/Versions/A/IconServices +0x0000000230025000 /System/Library/Frameworks/LightweightCodeRequirements.framework/Versions/A/LightweightCodeRequirements +0x00000001985c0000 /System/Library/PrivateFrameworks/PlugInKit.framework/Versions/A/PlugInKit +0x0000000195e1a000 /System/Library/PrivateFrameworks/AssertionServices.framework/Versions/A/AssertionServices +0x00000001986e5000 /System/Library/PrivateFrameworks/IconFoundation.framework/Versions/A/IconFoundation +0x0000000255f80000 /System/Library/PrivateFrameworks/IconRendering.framework/Versions/A/IconRendering +0x00000001913ca000 /System/Library/PrivateFrameworks/CoreUI.framework/Versions/A/CoreUI +0x00000001942f4000 /System/Library/Frameworks/CoreImage.framework/Versions/A/CoreImage +0x000000026d172000 /System/Library/PrivateFrameworks/SFSymbols.framework/Versions/A/SFSymbols +0x000000022eaf4000 /System/Library/Frameworks/DeveloperToolsSupport.framework/Versions/A/DeveloperToolsSupport +0x00000001ab7ca000 /System/Library/PrivateFrameworks/RenderBox.framework/Versions/A/RenderBox +0x0000000193f75000 /System/Library/PrivateFrameworks/CoreSVG.framework/Versions/A/CoreSVG +0x000000019964f000 /System/Library/PrivateFrameworks/TextureIO.framework/Versions/A/TextureIO +0x00000001b48ec000 /usr/lib/swift/libswiftCoreImage.dylib +0x00000001988f4000 /System/Library/PrivateFrameworks/GraphVisualizer.framework/Versions/A/GraphVisualizer +0x00000002499ae000 /System/Library/PrivateFrameworks/FontServices.framework/Versions/A/FontServices +0x0000000198904000 /System/Library/PrivateFrameworks/OTSVG.framework/Versions/A/OTSVG +0x0000000191379000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Resources/libFontRegistry.dylib +0x000000028b763000 /usr/lib/libhvf.dylib +0x0000000266404000 /System/Library/PrivateFrameworks/ParsingInternal.framework/Versions/A/ParsingInternal +0x00000002499b2000 /System/Library/PrivateFrameworks/FontServices.framework/libXTFontStaticRegistryData.dylib +0x00000001947df000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSCore.framework/Versions/A/MPSCore +0x00000001965ea000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSImage.framework/Versions/A/MPSImage +0x0000000195fa9000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSNeuralNetwork.framework/Versions/A/MPSNeuralNetwork +0x00000001963e8000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSMatrix.framework/Versions/A/MPSMatrix +0x0000000196200000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSRayIntersector.framework/Versions/A/MPSRayIntersector +0x000000019641a000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSNDArray.framework/Versions/A/MPSNDArray +0x0000000230eaa000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSFunctions.framework/Versions/A/MPSFunctions +0x0000000230e8b000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSBenchmarkLoop.framework/Versions/A/MPSBenchmarkLoop +0x0000000230ebe000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSHost.framework/Versions/A/MPSHost +0x000000018772d000 /System/Library/PrivateFrameworks/MetalTools.framework/Versions/A/MetalTools +0x00000001b9b0d000 /System/Library/PrivateFrameworks/IOAccelMemoryInfo.framework/Versions/A/IOAccelMemoryInfo +0x00000001c807b000 /System/Library/PrivateFrameworks/kperf.framework/Versions/A/kperf +0x00000001b4868000 /System/Library/PrivateFrameworks/GPURawCounter.framework/Versions/A/GPURawCounter +0x00000001a5299000 /System/Library/PrivateFrameworks/ASEProcessing.framework/Versions/A/ASEProcessing +0x00000001d618a000 /System/Library/PrivateFrameworks/Symbolication.framework/Versions/A/Symbolication +0x00000002698b5000 /System/Library/PrivateFrameworks/PhotosensitivityProcessing.framework/Versions/A/PhotosensitivityProcessing +0x000000026d1f8000 /System/Library/PrivateFrameworks/SILManager.framework/Versions/A/SILManager +0x00000001a1943000 /System/Library/PrivateFrameworks/CoreSymbolication.framework/Versions/A/CoreSymbolication +0x00000001b47db000 /System/Library/PrivateFrameworks/MallocStackLogging.framework/Versions/A/MallocStackLogging +0x00000001a1921000 /System/Library/PrivateFrameworks/DebugSymbols.framework/Versions/A/DebugSymbols +0x00000001c67cc000 /System/Library/PrivateFrameworks/OSAnalytics.framework/Versions/A/OSAnalytics +0x0000000247709000 /System/Library/PrivateFrameworks/DeviceRecovery.framework/Versions/A/DeviceRecovery +0x000000027be51000 /System/Library/PrivateFrameworks/Tightbeam.framework/Versions/A/Tightbeam +0x00000001c2870000 /usr/lib/swift/libswiftCompression.dylib +0x00000001ccebd000 /System/Library/PrivateFrameworks/AFKUser.framework/Versions/A/AFKUser +0x0000000199597000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATSUI.framework/Versions/A/ATSUI +0x000000019ac6f000 /System/Library/Frameworks/AudioToolbox.framework/Versions/A/AudioToolbox +0x0000000196a5c000 /System/Library/Frameworks/UserNotifications.framework/Versions/A/UserNotifications +0x00000001ba2a4000 /System/Library/PrivateFrameworks/SiriInstrumentation.framework/Versions/A/SiriInstrumentation +0x000000026f688000 /System/Library/PrivateFrameworks/SiriAnalytics.framework/Versions/A/SiriAnalytics +0x00000001b7a03000 /System/Library/PrivateFrameworks/FeedbackLogger.framework/Versions/A/FeedbackLogger +0x00000001d230d000 /usr/lib/swift/libswiftAVFoundation.dylib +0x000000027e67a000 /System/Library/PrivateFrameworks/UnifiedAssetFramework.framework/Versions/A/UnifiedAssetFramework +0x000000019ae45000 /System/Library/PrivateFrameworks/AudioSession.framework/Versions/A/AudioSession +0x0000000198805000 /System/Library/PrivateFrameworks/MediaExperience.framework/Versions/A/MediaExperience +0x000000019ac19000 /System/Library/PrivateFrameworks/AudioSession.framework/libSessionUtility.dylib +0x00000001a04cd000 /System/Library/Frameworks/CoreBluetooth.framework/Versions/A/CoreBluetooth +0x0000000195c8d000 /System/Library/PrivateFrameworks/CoreUtils.framework/Versions/A/CoreUtils +0x00000001ac4fe000 /System/Library/PrivateFrameworks/HID.framework/Versions/A/HID +0x00000002465fa000 /System/Library/PrivateFrameworks/CoreUtilsExtras.framework/Versions/A/CoreUtilsExtras +0x0000000255ed5000 /System/Library/PrivateFrameworks/IO80211.framework/Versions/A/IO80211 +0x000000019ce10000 /System/Library/Frameworks/IOBluetooth.framework/Versions/A/IOBluetooth +0x000000028c41f000 /usr/lib/swift/libswiftRegexBuilder.dylib +0x0000000198460000 /usr/lib/libIOReport.dylib +0x00000001e2dc2000 /System/Library/PrivateFrameworks/WiFiPeerToPeer.framework/Versions/A/WiFiPeerToPeer +0x0000000195e29000 /System/Library/Frameworks/SecurityFoundation.framework/Versions/A/SecurityFoundation +0x000000023ec54000 /System/Library/PrivateFrameworks/Centauri.framework/Versions/A/Centauri +0x0000000188111000 /System/Library/PrivateFrameworks/Lexicon.framework/Versions/A/Lexicon +0x000000028bbb3000 /usr/lib/libmrc.dylib +0x0000000255f40000 /System/Library/PrivateFrameworks/IPConfiguration.framework/Versions/A/IPConfiguration +0x00000001d6961000 /System/Library/PrivateFrameworks/Netrb.framework/Versions/A/Netrb +0x00000001a1450000 /System/Library/PrivateFrameworks/FrontBoardServices.framework/Versions/A/FrontBoardServices +0x0000000195f92000 /usr/lib/libgermantok.dylib +0x00000001949ce000 /System/Library/PrivateFrameworks/LinguisticData.framework/Versions/A/LinguisticData +0x00000001a06d6000 /System/Library/PrivateFrameworks/MediaKit.framework/Versions/A/MediaKit +0x00000001a0624000 /System/Library/Frameworks/DiscRecording.framework/Versions/A/DiscRecording +0x00000001986db000 /usr/lib/libheimdal-asn1.dylib +0x00000001a4101000 /System/Library/Frameworks/AudioUnit.framework/Versions/A/AudioUnit +0x0000000191690000 /System/Library/Frameworks/OpenDirectory.framework/Versions/A/OpenDirectory +0x000000019169e000 /System/Library/Frameworks/OpenDirectory.framework/Versions/A/Frameworks/CFOpenDirectory.framework/Versions/A/CFOpenDirectory +0x000000019d1b8000 /System/Library/PrivateFrameworks/GeoServices.framework/Versions/A/GeoServices +0x000000019abdf000 /System/Library/PrivateFrameworks/LocationSupport.framework/Versions/A/LocationSupport +0x0000000252bfe000 /System/Library/PrivateFrameworks/GeoServicesCore.framework/Versions/A/GeoServicesCore +0x00000001ad402000 /System/Library/PrivateFrameworks/PhoneNumbers.framework/Versions/A/PhoneNumbers +0x000000025b386000 /System/Library/PrivateFrameworks/LocationLogEncryption.framework/Versions/A/LocationLogEncryption +0x000000022d12e000 /System/Library/Frameworks/AVFAudio.framework/Versions/A/AVFAudio +0x000000022d272000 /System/Library/Frameworks/AVRouting.framework/Versions/A/AVRouting +0x00000001ad51a000 /usr/lib/libAccessibility.dylib +0x0000000259d70000 /System/Library/PrivateFrameworks/IsolatedCoreAudioClient.framework/Versions/A/IsolatedCoreAudioClient +0x00000002423ee000 /System/Library/PrivateFrameworks/CoreAudioOrchestration.framework/Versions/A/CoreAudioOrchestration +0x0000000199ab3000 /System/Library/Frameworks/MediaToolbox.framework/Versions/A/MediaToolbox +0x00000001a07fc000 /System/Library/PrivateFrameworks/CoreAVCHD.framework/Versions/A/CoreAVCHD +0x000000019f720000 /System/Library/Frameworks/MediaAccessibility.framework/Versions/A/MediaAccessibility +0x00000001a07f8000 /System/Library/PrivateFrameworks/Mangrove.framework/Versions/A/Mangrove +0x000000023e290000 /System/Library/PrivateFrameworks/CMPhoto.framework/Versions/A/CMPhoto +0x00000001a0fc5000 /System/Library/Frameworks/CoreTelephony.framework/Versions/A/CoreTelephony +0x00000001a07eb000 /System/Library/PrivateFrameworks/CoreAUC.framework/Versions/A/CoreAUC +0x000000023b3f6000 /System/Library/PrivateFrameworks/AppleJPEGXL.framework/Versions/A/AppleJPEGXL +0x000000019b4e8000 /usr/lib/libTelephonyUtilDynamic.dylib +0x00000001dd93f000 /System/Library/Frameworks/CryptoKit.framework/Versions/A/CryptoKit +0x00000001a40fc000 /System/Library/PrivateFrameworks/CryptoKitCBridging.framework/Versions/A/CryptoKitCBridging +0x00000001a1609000 /System/Library/Frameworks/CryptoTokenKit.framework/Versions/A/CryptoTokenKit +0x0000000247179000 /System/Library/PrivateFrameworks/Dendrite.framework/Versions/A/Dendrite +0x00000001b440c000 /System/Library/Frameworks/NaturalLanguage.framework/Versions/A/NaturalLanguage +0x0000000252901000 /System/Library/PrivateFrameworks/GenerativeModels.framework/Versions/A/GenerativeModels +0x00000001e2d49000 /usr/lib/swift/libswiftNaturalLanguage.dylib +0x000000023bf6d000 /System/Library/PrivateFrameworks/AppleMobileFileIntegrity.framework/Versions/A/AppleMobileFileIntegrity +0x000000028ad01000 /usr/lib/libTLE.dylib +0x00000001b480d000 /usr/lib/libmis.dylib +0x00000001ec491000 /System/Library/PrivateFrameworks/ConfigProfileHelper.framework/Versions/A/ConfigProfileHelper +0x00000001a428b000 /System/Library/PrivateFrameworks/Espresso.framework/Versions/A/Espresso +0x0000000191e20000 /System/Library/Frameworks/CoreML.framework/Versions/A/CoreML +0x00000001e0bf5000 /usr/lib/libedit.3.dylib +0x0000000229f3c000 /System/Library/PrivateFrameworks/ANECompiler.framework/Versions/A/ANECompiler +0x00000001a6361000 /System/Library/PrivateFrameworks/AppleNeuralEngine.framework/Versions/A/AppleNeuralEngine +0x000000025b4a4000 /System/Library/PrivateFrameworks/MIL.framework/Versions/A/MIL +0x0000000230ec4000 /System/Library/Frameworks/MetalPerformanceShadersGraph.framework/Versions/A/MetalPerformanceShadersGraph +0x000000025bc13000 /System/Library/PrivateFrameworks/MLCompilerServices.framework/Versions/A/MLCompilerServices +0x00000001a50dd000 /System/Library/PrivateFrameworks/ANEServices.framework/Versions/A/ANEServices +0x00000001b9ad0000 /usr/lib/libncurses.5.4.dylib +0x000000018b2ce000 /usr/lib/libsandbox.1.dylib +0x0000000198601000 /usr/lib/libMatch.1.dylib +0x00000002654f9000 /System/Library/PrivateFrameworks/ODIE.framework/Versions/A/ODIE +0x000000025e8a0000 /System/Library/PrivateFrameworks/MLModelAsset.framework/Versions/A/MLModelAsset +0x000000025bbb9000 /System/Library/PrivateFrameworks/MLCompilerRuntime.framework/Versions/A/MLCompilerRuntime +0x0000000196255000 /System/Library/Frameworks/MLCompute.framework/Versions/A/MLCompute +0x000000025bb3b000 /System/Library/PrivateFrameworks/MLAssetIO.framework/Versions/A/MLAssetIO +0x000000028c3f1000 /usr/lib/swift/libswiftMLCompute.dylib +0x00000001a11e0000 /System/Library/PrivateFrameworks/AVFCore.framework/Versions/A/AVFCore +0x00000001aae1b000 /System/Library/PrivateFrameworks/AVFCapture.framework/Versions/A/AVFCapture +0x000000023e087000 /System/Library/PrivateFrameworks/CMImaging.framework/Versions/A/CMImaging +0x00000001ab05d000 /System/Library/PrivateFrameworks/Quagga.framework/Versions/A/Quagga +0x00000001ab18e000 /System/Library/PrivateFrameworks/CMCapture.framework/Versions/A/CMCapture +0x000000019b071000 /System/Library/Frameworks/CoreMediaIO.framework/Versions/A/CoreMediaIO +0x000000023dfc2000 /System/Library/PrivateFrameworks/CMCaptureDevice.framework/Versions/A/CMCaptureDevice +0x0000000198a3d000 /System/Library/PrivateFrameworks/CoreBrightness.framework/Versions/A/CoreBrightness +0x000000023f14b000 /System/Library/PrivateFrameworks/CinematicFraming.framework/Versions/A/CinematicFraming +0x00000002619dd000 /System/Library/PrivateFrameworks/ModelManagerServices.framework/Versions/A/ModelManagerServices +0x00000001cf375000 /System/Library/PrivateFrameworks/CPMS.framework/Versions/A/CPMS +0x0000000279581000 /System/Library/PrivateFrameworks/SystemStatus.framework/Versions/A/SystemStatus +0x00000001b323c000 /System/Library/Frameworks/CoreMotion.framework/Versions/A/CoreMotion +0x00000001c3854000 /System/Library/PrivateFrameworks/TimeSync.framework/Versions/A/TimeSync +0x0000000247b41000 /System/Library/PrivateFrameworks/DistributedSensing.framework/Versions/A/DistributedSensing +0x00000001bec33000 /System/Library/PrivateFrameworks/MobileBluetooth.framework/Versions/A/MobileBluetooth +0x00000001c5518000 /System/Library/PrivateFrameworks/IOKitten.framework/Versions/A/IOKitten +0x000000023b270000 /System/Library/PrivateFrameworks/AppleIntelligenceReporting.framework/Versions/A/AppleIntelligenceReporting +0x0000000195b9c000 /System/Library/PrivateFrameworks/CoreEmoji.framework/Versions/A/CoreEmoji +0x0000000188425000 /usr/lib/libCRFSuite.dylib +0x0000000189706000 /System/Library/PrivateFrameworks/LanguageModeling.framework/Versions/A/LanguageModeling +0x00000001948ae000 /System/Library/PrivateFrameworks/CoreNLP.framework/Versions/A/CoreNLP +0x000000018e683000 /System/Library/PrivateFrameworks/Montreal.framework/Versions/A/Montreal +0x0000000196d10000 /usr/lib/libcmph.dylib +0x0000000195f33000 /usr/lib/libmecab.dylib +0x0000000196e81000 /usr/lib/libThaiTokenizer.dylib +0x00000002529e3000 /System/Library/PrivateFrameworks/GenerativeModelsFoundation.framework/Versions/A/GenerativeModelsFoundation +0x000000027c356000 /System/Library/PrivateFrameworks/TokenGeneration.framework/Versions/A/TokenGeneration +0x00000002527b7000 /System/Library/PrivateFrameworks/GenerativeFunctions.framework/Versions/A/GenerativeFunctions +0x0000000252805000 /System/Library/PrivateFrameworks/GenerativeFunctionsFoundation.framework/Versions/A/GenerativeFunctionsFoundation +0x000000026169e000 /System/Library/PrivateFrameworks/ModelCatalog.framework/Versions/A/ModelCatalog +0x000000026e8a2000 /System/Library/PrivateFrameworks/SensitiveContentAnalysisML.framework/Versions/A/SensitiveContentAnalysisML +0x000000025289b000 /System/Library/PrivateFrameworks/GenerativeFunctionsInstrumentation.framework/Versions/A/GenerativeFunctionsInstrumentation +0x000000026b6cb000 /System/Library/PrivateFrameworks/PromptKit.framework/Versions/A/PromptKit +0x000000026b0e1000 /System/Library/PrivateFrameworks/ProactiveDaemonSupport.framework/Versions/A/ProactiveDaemonSupport +0x000000027c58e000 /System/Library/PrivateFrameworks/TokenGenerationCore.framework/Versions/A/TokenGenerationCore +0x00000001b52db000 /System/Library/PrivateFrameworks/Trial.framework/Versions/A/Trial +0x00000001b525c000 /System/Library/PrivateFrameworks/TrialProto.framework/Versions/A/TrialProto +0x000000023ae9a000 /System/Library/PrivateFrameworks/AppleFlatBuffers.framework/Versions/A/AppleFlatBuffers +0x000000026eb72000 /System/Library/PrivateFrameworks/SentencePieceInternal.framework/Versions/A/SentencePieceInternal +0x000000019f77a000 /System/Library/Frameworks/Vision.framework/Versions/A/Vision +0x0000000246298000 /System/Library/PrivateFrameworks/CoreSceneUnderstanding.framework/Versions/A/CoreSceneUnderstanding +0x00000002811df000 /System/Library/PrivateFrameworks/VisionCore.framework/Versions/A/VisionCore +0x00000001999f0000 /System/Library/PrivateFrameworks/DataDetectorsCore.framework/Versions/A/DataDetectorsCore +0x00000001bdbc1000 /System/Library/Frameworks/Vision.framework/libfaceCore.dylib +0x00000001be6db000 /System/Library/PrivateFrameworks/Futhark.framework/Versions/A/Futhark +0x00000001c2629000 /System/Library/PrivateFrameworks/InertiaCam.framework/Versions/A/InertiaCam +0x00000001be468000 /System/Library/PrivateFrameworks/TextRecognition.framework/Versions/A/TextRecognition +0x000000022eadd000 /System/Library/Frameworks/DataDetection.framework/Versions/A/DataDetection +0x00000001b8674000 /System/Library/PrivateFrameworks/TextInput.framework/Versions/A/TextInput +0x000000019849e000 /System/Library/PrivateFrameworks/CVNLP.framework/Versions/A/CVNLP +0x00000001dad09000 /System/Library/PrivateFrameworks/HIDDisplay.framework/Versions/A/HIDDisplay +0x000000019b255000 /usr/lib/libcups.2.dylib +0x000000019b2ec000 /System/Library/Frameworks/Kerberos.framework/Versions/A/Kerberos +0x000000019af5c000 /usr/lib/libresolv.9.dylib +0x0000000198958000 /System/Library/PrivateFrameworks/Heimdal.framework/Versions/A/Heimdal +0x00000001a4100000 /System/Library/Frameworks/Kerberos.framework/Versions/A/Libraries/libHeimdalProxy.dylib +0x000000019b350000 /System/Library/PrivateFrameworks/CommonAuth.framework/Versions/A/CommonAuth +0x00000001ad40e000 /System/Library/PrivateFrameworks/AXCoreUtilities.framework/Versions/A/AXCoreUtilities +0x00000001bda0a000 /System/Library/PrivateFrameworks/AttributeGraph.framework/Versions/A/AttributeGraph +0x000000028a801000 /usr/lib/libAXSafeCategoryBundle.dylib +0x0000000235252000 /System/Library/Frameworks/TabularData.framework/Versions/A/TabularData +0x000000023c11d000 /System/Library/PrivateFrameworks/ArgumentParserInternal.framework/Versions/A/ArgumentParserInternal +0x0000000195a5e000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvDSP.dylib +0x0000000197053000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLAPACK.dylib +0x0000000195f95000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLinearAlgebra.dylib +0x0000000196ec7000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparseBLAS.dylib +0x000000019704e000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libQuadrature.dylib +0x00000001949d5000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBNNS.dylib +0x0000000188221000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparse.dylib +0x000000022e5c2000 /System/Library/Frameworks/CoreTransferable.framework/Versions/A/CoreTransferable +0x000000019b2b4000 /System/Library/PrivateFrameworks/NetAuth.framework/Versions/A/NetAuth +0x00000001918b4000 /System/Library/PrivateFrameworks/login.framework/Versions/A/Frameworks/loginsupport.framework/Versions/A/loginsupport +0x000000018ca51000 /System/Library/PrivateFrameworks/UIFoundation.framework/Versions/A/UIFoundation +0x0000000195efd000 /usr/lib/libCheckFix.dylib +0x0000000190a3b000 /System/Library/PrivateFrameworks/MetadataUtilities.framework/Versions/A/MetadataUtilities +0x00000002569c7000 /System/Library/PrivateFrameworks/InstalledContentLibrary.framework/Versions/A/InstalledContentLibrary +0x000000018b192000 /System/Library/PrivateFrameworks/CoreServicesStore.framework/Versions/A/CoreServicesStore +0x00000001916ff000 /usr/lib/libapp_launch_measurement.dylib +0x00000001c8192000 /System/Library/PrivateFrameworks/MobileSystemServices.framework/Versions/A/MobileSystemServices +0x0000000198323000 /usr/lib/libxslt.1.dylib +0x0000000195ebc000 /System/Library/PrivateFrameworks/BackgroundTaskManagement.framework/Versions/A/BackgroundTaskManagement +0x00000001a3792000 /usr/lib/libcurl.4.dylib +0x000000028b517000 /usr/lib/libcrypto.46.dylib +0x000000028c09a000 /usr/lib/libssl.48.dylib +0x00000001a346c000 /System/Library/Frameworks/LDAP.framework/Versions/A/LDAP +0x00000001a34a8000 /System/Library/PrivateFrameworks/TrustEvaluationAgent.framework/Versions/A/TrustEvaluationAgent +0x000000019af79000 /usr/lib/libsasl2.2.dylib +0x00000001a6710000 /System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa +0x000000018b32d000 /System/Library/Frameworks/AppKit.framework/Versions/C/AppKit +0x000000023ffce000 /System/Library/PrivateFrameworks/CollectionViewCore.framework/Versions/A/CollectionViewCore +0x0000000193f6f000 /System/Library/PrivateFrameworks/XCTTargetBootstrap.framework/Versions/A/XCTTargetBootstrap +0x0000000199a42000 /System/Library/PrivateFrameworks/UserActivity.framework/Versions/A/UserActivity +0x0000000249ab0000 /System/Library/PrivateFrameworks/FrontBoard.framework/Versions/A/FrontBoard +0x000000027df9f000 /System/Library/PrivateFrameworks/UIIntelligenceSupport.framework/Versions/A/UIIntelligenceSupport +0x00000002342db000 /System/Library/Frameworks/SwiftUICore.framework/Versions/A/SwiftUICore +0x0000000284b4b000 /System/Library/PrivateFrameworks/WritingTools.framework/Versions/A/WritingTools +0x0000000283942000 /System/Library/PrivateFrameworks/WindowManagement.framework/Versions/A/WindowManagement +0x00000002497e0000 /System/Library/PrivateFrameworks/FocusEngine.framework/Versions/A/FocusEngine +0x00000002471e9000 /System/Library/PrivateFrameworks/DesignLibrary.framework/Versions/A/DesignLibrary +0x0000000193f5a000 /System/Library/PrivateFrameworks/DFRFoundation.framework/Versions/A/DFRFoundation +0x000000027eeec000 /System/Library/PrivateFrameworks/UpdateCycle.framework/Versions/A/UpdateCycle +0x0000000193c5e000 /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/HIToolbox +0x000000019f040000 /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/SpeechRecognition.framework/Versions/A/SpeechRecognition +0x0000000191686000 /System/Library/PrivateFrameworks/PerformanceAnalysis.framework/Versions/A/PerformanceAnalysis +0x000000019f3d0000 /System/Library/Frameworks/Accessibility.framework/Versions/A/Accessibility +0x0000000235238000 /System/Library/Frameworks/Symbols.framework/Versions/A/Symbols +0x0000000252dac000 /System/Library/PrivateFrameworks/Gestures.framework/Versions/A/Gestures +0x000000028c4c2000 /usr/lib/swift/libswiftSpatial.dylib +0x00000001b164e000 /usr/lib/swift/libswiftCoreGraphics.dylib +0x000000019fe14000 /usr/lib/swift/libswiftFoundation.dylib +0x00000001ebe32000 /usr/lib/swift/libswiftSwiftOnoneSupport.dylib +0x000000028c748000 /usr/lib/swift/libswiftsys_time.dylib +0x00000001d699f000 /System/Library/PrivateFrameworks/CoreMaterial.framework/Versions/A/CoreMaterial +0x000000028acfe000 /usr/lib/libSpatial.dylib +0x000000028a71e000 /System/Library/SubFrameworks/UIUtilities.framework/Versions/A/UIUtilities +0x0000000102004000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/server/libjvm.dylib +0x0000000100db8000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libjimage.dylib +0x0000000100e14000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libjdwp.dylib +0x0000000100e5c000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libjava.dylib +0x0000000100de8000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libinstrument.dylib +0x0000000100ed0000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libzip.dylib +0x000000028a7f3000 /usr/lib/i18n/libiconv_std.dylib +0x000000028a7e9000 /usr/lib/i18n/libUTF8.dylib +0x000000028a7f8000 /usr/lib/i18n/libmapper_none.dylib +0x0000000100f5c000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libdt_socket.dylib +0x0000000101058000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libnio.dylib +0x000000010109c000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libnet.dylib +0x0000000101038000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libmanagement.dylib +0x0000000101078000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libmanagement_ext.dylib +0x00000001010c0000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libextnet.dylib +0x0000000101200000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libverify.dylib + + +VM Arguments: +jvm_args: -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:50259,suspend=y,server=n -javaagent:/Users/liangxin/Library/Caches/JetBrains/IntelliJIdea2026.1/captureAgent/debugger-agent.jar=file:///var/folders/pk/drq93rk10q733kk02fgp89xh0000gn/T/capture10140205674518191061.props -XX:TieredStopAtLevel=1 -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dmanagement.endpoints.jmx.exposure.include=* -Dkotlinx.coroutines.debug.enable.creation.stack.trace=false -Ddebugger.agent.enable.coroutines=true -Dkotlinx.coroutines.debug.enable.flows.stack.trace=true -Dkotlinx.coroutines.debug.enable.mutable.state.flows.stack.trace=true -Ddebugger.async.stack.trace.for.all.threads=true -Dfile.encoding=UTF-8 +java_command: org.springblade.desk.DeskApplication +java_class_path (initial): /Users/liangxin/Project/JAVA/tms-erp-api/blade-service/blade-desk/target/classes:/Users/liangxin/.m2/repository/org/springblade/blade-core-boot/4.10.0.BASE-SNAPSHOT/blade-core-boot-4.10.0.BASE-SNAPSHOT.jar:/Users/liangxin/.m2/repository/org/springblade/blade-core-context/4.10.0.BASE-SNAPSHOT/blade-core-context-4.10.0.BASE-SNAPSHOT.jar:/Users/liangxin/.m2/repository/org/springblade/blade-core-db/4.10.0.BASE-SNAPSHOT/blade-core-db-4.10.0.BASE-SNAPSHOT.jar:/Users/liangxin/.m2/repository/org/springframework/boot/spring-boot-starter-jdbc/3.5.16/spring-boot-starter-jdbc-3.5.16.jar:/Users/liangxin/.m2/repository/com/zaxxer/HikariCP/6.3.3/HikariCP-6.3.3.jar:/Users/liangxin/.m2/repository/com/baomidou/mybatis-plus-spring-boot3-starter/3.5.16/mybatis-plus-spring-boot3-starter-3.5.16.jar:/Users/liangxin/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/3.5.16/spring-boot-autoconfigure-3.5.16.jar:/Users/liangxin/.m2/repository/com/alibaba/druid-spring-boot-3-starter/1.2.28/druid-spring-boot-3-starter-1.2.28.jar:/Users/liangxin/.m2/repository/com/mysql/mysql-connector-j/9.4.0/mysql-connector-j-9.4.0.jar:/Users/liangxin/.m2/repository/com/google/protobuf/protobuf-java/4.31.1/protobuf-java-4.31.1.jar:/Users/liangxin/.m2/repository/org/springblade/blade-core-secure/4.10.0.BASE-SNAPSHOT/blade-core-secure-4.10.0.BASE-SNAPSHOT.jar:/Users/liangxin/.m2/repository/org/springblade/blade-core-cloud/4.10.0.BASE-SNAPSHOT/blade-core-cloud-4.10.0.BASE-SNAPSHOT.jar:/Users/liangxin/.m2/repository/de/codecentric/spring-boot-admin-starter-client/3.5.9/spring-boot-admin-starter-client-3.5.9.jar:/Users/liangxin/.m2/repository/de/codecentric/spring-boot-admin-client/3.5.9/spring-boot-admin-client-3.5.9.jar:/Users/liangxin/.m2/repository/org/springframework/boot/spring-boot-starter-actuator/3.5.16/spring-boot-starter-actuator-3.5.16.jar:/Users/liangxin/.m2/repository/org/springframework/boot/spring-boot-actuator-autoconfigure/3.5.16/spring-boot-actuator-aut +Launcher Type: SUN_STANDARD + +[Global flags] + intx CICompilerCount = 4 {product} {ergonomic} + uint ConcGCThreads = 3 {product} {ergonomic} + uint G1ConcRefinementThreads = 10 {product} {ergonomic} + size_t G1HeapRegionSize = 8388608 {product} {ergonomic} + uintx GCDrainStackTargetSize = 64 {product} {ergonomic} + size_t InitialHeapSize = 603979776 {product} {ergonomic} + bool ManagementServer = true {product} {command line} + size_t MarkStackSize = 4194304 {product} {ergonomic} + size_t MaxHeapSize = 9663676416 {product} {ergonomic} + size_t MaxNewSize = 5796528128 {product} {ergonomic} + size_t MinHeapDeltaBytes = 8388608 {product} {ergonomic} + size_t MinHeapSize = 8388608 {product} {ergonomic} + uintx NonProfiledCodeHeapSize = 0 {pd product} {ergonomic} + bool ProfileInterpreter = false {pd product} {command line} + uintx ProfiledCodeHeapSize = 0 {pd product} {ergonomic} + size_t SoftMaxHeapSize = 9663676416 {manageable} {ergonomic} + intx TieredStopAtLevel = 1 {product} {command line} + bool UseCompressedClassPointers = true {product lp64_product} {ergonomic} + bool UseCompressedOops = true {product lp64_product} {ergonomic} + bool UseG1GC = true {product} {ergonomic} + bool UseNUMA = false {product} {ergonomic} + bool UseNUMAInterleaving = false {product} {ergonomic} + +Logging: +Log output configuration: + #0: stdout all=warning uptime,level,tags + #1: stderr all=off uptime,level,tags + +Environment Variables: +JAVA_HOME=/Users/liangxin/JDK/zulu17.48.15-ca-jdk17.0.10-macosx_aarch64/zulu-17.jdk/Contents/Home +PATH=/Users/liangxin/ai-infra/.venv/bin:/Users/liangxin/.nacos/bin:/Applications/Docker.app/Contents/Resources/bin:/Users/liangxin/Library/pnpm:/opt/homebrew/opt/ruby@3.2/bin:/opt/homebrew/opt/openssl@3/bin:/opt/miniconda3/bin:/opt/miniconda3/condabin:/usr/local/sbin:/usr/local/bin:/Users/liangxin/JDK/zulu17.48.15-ca-jdk17.0.10-macosx_aarch64/zulu-17.jdk/Contents/Home/bin:/Users/liangxin/fvm/default/bin:/Applications/MAMP/bin/php/php8.1.13/bin:/opt/homebrew/opt/ruby@3.2/bin:/Users/liangxin/.nvm/versions/node/v20.18.3/bin:/Applications/apache-tomcat-9.0.78:/Users/liangxin/JDK/zulu17.48.15-ca-jdk17.0.10-macosx_aarch64/zulu-17.jdk/Contents/Home/bin:/Users/liangxin/fvm/default/bin:/opt/homebrew/opt/libpng/bin:/Applications/pngquant:/Users/liangxin/AndroidSDK/platform-tools:/Users/liangxin/Library/Android/sdk/platform-tools:/Users/liangxin/Library/Andriod/sdk/cmdline-tools/latest/bin:/Users/liangxin/Library/Andriod/sdk:/Applications/apache-maven-3.8.1/bin:/opt/homebrew/bin:/usr/local/sbin:/usr/local/bin:/Users/liangxin/JDK/zulu17.48.15-ca-jdk17.0.10-macosx_aarch64/zulu-17.jdk/Contents/Home/bin:/Library/Frameworks/Python.framework/Versions/3.9/bin:/Users/liangxin/.local/bin:/Users/liangxin/fvm/default/bin:/Applications/MAMP/bin/php/php8.1.13/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/pkg/env/global/bin:/Library/Apple/usr/bin:/usr/local/share/dotnet:~/.dotnet/tools:/Library/Frameworks/Mono.framework/Versions/Current/Commands:/Users/liangxin/.cargo/bin:true:/Applications/极空间.app/Contents/Resources/app.asar.unpacked/bin/platform-tools +SHELL=/bin/zsh +LANG=C.UTF-8 +TMPDIR=/var/folders/pk/drq93rk10q733kk02fgp89xh0000gn/T/ + +Active Locale: +LC_ALL=C.UTF-8 +LC_COLLATE=C.UTF-8 +LC_CTYPE=C.UTF-8 +LC_MESSAGES=C.UTF-8 +LC_MONETARY=C.UTF-8 +LC_NUMERIC=C.UTF-8 +LC_TIME=C.UTF-8 + +Signal Handlers: + SIGSEGV: crash_handler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, unblocked + SIGBUS: crash_handler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, unblocked + SIGFPE: crash_handler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, unblocked + SIGPIPE: javaSignalHandler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, blocked + SIGXFSZ: javaSignalHandler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, blocked + SIGILL: crash_handler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, unblocked + SIGUSR2: SR_handler in libjvm.dylib, mask=00000000000000000000000000000000, flags=SA_RESTART|SA_SIGINFO, blocked + SIGHUP: UserHandler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, blocked + SIGINT: UserHandler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, blocked + SIGTERM: UserHandler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, blocked + SIGQUIT: UserHandler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, blocked + SIGTRAP: crash_handler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, unblocked + + +--------------- S Y S T E M --------------- + +OS: +uname: Darwin 25.6.0 Darwin Kernel Version 25.6.0: Fri Jul 31 19:16:36 PDT 2026; root:xnu-12377.161.14~5/RELEASE_ARM64_T6030 arm64 +OS uptime: 2 days 23:37 hours +rlimit (soft/hard): STACK 8176k/65520k , CORE 0k/infinity , NPROC 6000/9000 , NOFILE 10240/infinity , AS infinity/infinity , CPU infinity/infinity , DATA infinity/infinity , FSIZE infinity/infinity , MEMLOCK infinity/infinity , RSS infinity/infinity +load average: 19.31 16.17 13.60 + +CPU: total 12 (initial active 12) 0x61:0x0:0x5f4dea93:0, fp, simd, crc, lse +machdep.cpu.brand_string:Apple M3 Pro +hw.cachelinesize:128 +hw.l1icachesize:131072 +hw.l1dcachesize:65536 +hw.l2cachesize:4194304 + +Memory: 16k page, physical 37748736k(166608k free), swap 16777216k(965120k free) + +vm_info: OpenJDK 64-Bit Server VM (17.0.8+7-LTS) for bsd-aarch64 JRE (17.0.8+7-LTS) (Zulu17.44+15-CA), built on Jul 5 2023 00:50:04 by "zulu_re" with clang Apple LLVM 12.0.0 (clang-1200.0.32.28) + +END. diff --git a/hs_err_pid40988.log b/hs_err_pid40988.log new file mode 100644 index 00000000..52e4b4ea --- /dev/null +++ b/hs_err_pid40988.log @@ -0,0 +1,1368 @@ +# +# A fatal error has been detected by the Java Runtime Environment: +# +# SIGBUS (0xa) at pc=0x00000001046de4c0, pid=40988, tid=36131 +# +# JRE version: OpenJDK Runtime Environment Zulu17.44+15-CA (17.0.8+7) (build 17.0.8+7-LTS) +# Java VM: OpenJDK 64-Bit Server VM Zulu17.44+15-CA (17.0.8+7-LTS, mixed mode, emulated-client, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, bsd-aarch64) +# Problematic frame: +# C [libzip.dylib+0x64c0] newEntry+0x68 +# +# No core dump will be written. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again +# +# If you would like to submit a bug report, please visit: +# http://www.azul.com/support/ +# The crash happened outside the Java Virtual Machine in native code. +# See problematic frame for where to report the bug. +# + +--------------- S U M M A R Y ------------ + +Command Line: -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:49167,suspend=y,server=n -javaagent:/Users/liangxin/Library/Caches/JetBrains/IntelliJIdea2026.1/captureAgent/debugger-agent.jar=file:///var/folders/pk/drq93rk10q733kk02fgp89xh0000gn/T/capture18007844485071508468.props -XX:TieredStopAtLevel=1 -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dmanagement.endpoints.jmx.exposure.include=* -Dkotlinx.coroutines.debug.enable.creation.stack.trace=false -Ddebugger.agent.enable.coroutines=true -Dkotlinx.coroutines.debug.enable.flows.stack.trace=true -Dkotlinx.coroutines.debug.enable.mutable.state.flows.stack.trace=true -Ddebugger.async.stack.trace.for.all.threads=true -Dfile.encoding=UTF-8 org.springblade.resource.ResourceApplication + +Host: "Mac15,6" arm64, 12 cores, 36G, Darwin 25.6.0, macOS 26.6.2 (25G83) +Time: Thu Sep 17 20:22:18 2026 CST elapsed time: 7.706668 seconds (0d 0h 0m 7s) + +--------------- T H R E A D --------------- + +Current thread (0x000000010fa93c00): JavaThread "sentinel-datafile-log-executor-thread-1" daemon [_thread_in_native, id=36131, stack(0x0000000318210000,0x0000000318413000)] + +Stack: [0x0000000318210000,0x0000000318413000], sp=0x00000003184119d0, free space=2054k +Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code) +C [libzip.dylib+0x64c0] newEntry+0x68 +C [libzip.dylib+0x6390] ZIP_GetEntry2+0x14c +C [libzip.dylib+0x6d78] ZIP_FindEntry+0x3c +V [libjvm.dylib+0x25a408] ClassPathZipEntry::open_entry(JavaThread*, char const*, int*, bool)+0xb4 +V [libjvm.dylib+0x25a53c] ClassPathZipEntry::open_stream(JavaThread*, char const*)+0x20 +V [libjvm.dylib+0x25d918] ClassLoader::load_class(Symbol*, bool, JavaThread*)+0x150 +V [libjvm.dylib+0x981d60] SystemDictionary::load_instance_class_impl(Symbol*, Handle, JavaThread*)+0x2d0 +V [libjvm.dylib+0x98063c] SystemDictionary::load_instance_class(unsigned int, Symbol*, Handle, JavaThread*)+0x30 +V [libjvm.dylib+0x97fd48] SystemDictionary::resolve_instance_class_or_null(Symbol*, Handle, Handle, JavaThread*)+0x4dc +V [libjvm.dylib+0x97f334] SystemDictionary::resolve_or_fail(Symbol*, Handle, Handle, bool, JavaThread*)+0x80 +V [libjvm.dylib+0x2beb54] ConstantPool::klass_at_impl(constantPoolHandle const&, int, JavaThread*)+0x1e0 +V [libjvm.dylib+0x46d6f0] InterpreterRuntime::_new(JavaThread*, ConstantPool*, int)+0x94 +j com.intellij.rt.debugger.agent.CaptureStorage$DeepCapturedStack.collectStacks(Ljava/util/List;)Lcom/intellij/rt/debugger/agent/CaptureStorage$StackData;+89 +j com.intellij.rt.debugger.agent.CaptureStorage.getStackTrace(Lcom/intellij/rt/debugger/agent/CaptureStorage$CapturedStack;I)Ljava/util/ArrayList;+30 +j com.intellij.rt.debugger.agent.CaptureStorage.access$1000(Lcom/intellij/rt/debugger/agent/CaptureStorage$CapturedStack;I)Ljava/util/ArrayList;+2 +j com.intellij.rt.debugger.agent.CaptureStorage$9.call()[Ljava/lang/StackTraceElement;+31 +j com.intellij.rt.debugger.agent.CaptureStorage$9.call()Ljava/lang/Object;+1 +j com.intellij.rt.debugger.agent.CaptureStorage.withoutThrowableCapture(Lcom/intellij/rt/debugger/agent/CaptureStorage$Callable;)Ljava/lang/Object;+21 +j com.intellij.rt.debugger.agent.CaptureStorage.getAsyncStackTrace(Ljava/lang/Throwable;)[Ljava/lang/StackTraceElement;+19 +j java.lang.Throwable.printStackTrace(Ljava/lang/Throwable$PrintStreamOrWriter;)V+32 java.base@17.0.8 +j java.lang.Throwable.printStackTrace(Ljava/io/PrintWriter;)V+9 java.base@17.0.8 +j com.alibaba.csp.sentinel.log.jul.CspFormatter.format(Ljava/util/logging/LogRecord;)Ljava/lang/String;+103 +j java.util.logging.StreamHandler.publish(Ljava/util/logging/LogRecord;)V+14 java.logging@17.0.8 +j java.util.logging.FileHandler.publish(Ljava/util/logging/LogRecord;)V+11 java.logging@17.0.8 +j com.alibaba.csp.sentinel.log.jul.DateFileLogHandler$LogTask.run()V+8 +j java.util.concurrent.ThreadPoolExecutor.runWorker(Ljava/util/concurrent/ThreadPoolExecutor$Worker;)V+92 java.base@17.0.8 +j java.util.concurrent.ThreadPoolExecutor$Worker.run()V+5 java.base@17.0.8 +j java.lang.Thread.run()V+11 java.base@17.0.8 +v ~StubRoutines::call_stub +V [libjvm.dylib+0x4781f8] JavaCalls::call_helper(JavaValue*, methodHandle const&, JavaCallArguments*, JavaThread*)+0x394 +V [libjvm.dylib+0x47720c] JavaCalls::call_virtual(JavaValue*, Klass*, Symbol*, Symbol*, JavaCallArguments*, JavaThread*)+0x11c +V [libjvm.dylib+0x4772d8] JavaCalls::call_virtual(JavaValue*, Handle, Klass*, Symbol*, Symbol*, JavaThread*)+0x64 +V [libjvm.dylib+0x52ebfc] thread_entry(JavaThread*, JavaThread*)+0xc4 +V [libjvm.dylib+0x9b22e8] JavaThread::thread_main_inner()+0x150 +V [libjvm.dylib+0x9b0990] Thread::call_run()+0xe0 +V [libjvm.dylib+0x7d0364] thread_native_entry(Thread*)+0x158 +C [libsystem_pthread.dylib+0x6c58] _pthread_start+0x88 + +Java frames: (J=compiled Java code, j=interpreted, Vv=VM code) +j com.intellij.rt.debugger.agent.CaptureStorage$DeepCapturedStack.collectStacks(Ljava/util/List;)Lcom/intellij/rt/debugger/agent/CaptureStorage$StackData;+89 +j com.intellij.rt.debugger.agent.CaptureStorage.getStackTrace(Lcom/intellij/rt/debugger/agent/CaptureStorage$CapturedStack;I)Ljava/util/ArrayList;+30 +j com.intellij.rt.debugger.agent.CaptureStorage.access$1000(Lcom/intellij/rt/debugger/agent/CaptureStorage$CapturedStack;I)Ljava/util/ArrayList;+2 +j com.intellij.rt.debugger.agent.CaptureStorage$9.call()[Ljava/lang/StackTraceElement;+31 +j com.intellij.rt.debugger.agent.CaptureStorage$9.call()Ljava/lang/Object;+1 +j com.intellij.rt.debugger.agent.CaptureStorage.withoutThrowableCapture(Lcom/intellij/rt/debugger/agent/CaptureStorage$Callable;)Ljava/lang/Object;+21 +j com.intellij.rt.debugger.agent.CaptureStorage.getAsyncStackTrace(Ljava/lang/Throwable;)[Ljava/lang/StackTraceElement;+19 +j java.lang.Throwable.printStackTrace(Ljava/lang/Throwable$PrintStreamOrWriter;)V+32 java.base@17.0.8 +j java.lang.Throwable.printStackTrace(Ljava/io/PrintWriter;)V+9 java.base@17.0.8 +j com.alibaba.csp.sentinel.log.jul.CspFormatter.format(Ljava/util/logging/LogRecord;)Ljava/lang/String;+103 +j java.util.logging.StreamHandler.publish(Ljava/util/logging/LogRecord;)V+14 java.logging@17.0.8 +j java.util.logging.FileHandler.publish(Ljava/util/logging/LogRecord;)V+11 java.logging@17.0.8 +j com.alibaba.csp.sentinel.log.jul.DateFileLogHandler$LogTask.run()V+8 +j java.util.concurrent.ThreadPoolExecutor.runWorker(Ljava/util/concurrent/ThreadPoolExecutor$Worker;)V+92 java.base@17.0.8 +j java.util.concurrent.ThreadPoolExecutor$Worker.run()V+5 java.base@17.0.8 +j java.lang.Thread.run()V+11 java.base@17.0.8 +v ~StubRoutines::call_stub + +siginfo: si_signo: 10 (SIGBUS), si_code: 1 (BUS_ADRALN), si_addr: 0x00000001046c5e7b + +Register to memory mapping: + + x0=0x0000600000694eb0 points into unknown readable memory: 0x0000000000000000 | 00 00 00 00 00 00 00 00 + x1=0x0 is NULL + x2=0x0 is NULL + x3=0x0000600000694ec0 points into unknown readable memory: 0x0000000000000000 | 00 00 00 00 00 00 00 00 + x4=0x0000600000694f00 points into unknown readable memory: 0x0000600003c95440 | 40 54 c9 03 00 60 00 00 + x5=0x000000009d64e030 is an unknown value + x6=0x0000000000000eb0 is an unknown value + x7=0x000000000000000a is an unknown value + x8=0x00000001047ede5f: gdata+0xcbf7 in /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libjdwp.dylib at 0x00000001047a8000 + x9=0x0000000000128000 is an unknown value +x10=0x0000600000694eb0 points into unknown readable memory: 0x0000000000000000 | 00 00 00 00 00 00 00 00 +x11=0x000066d8c3c50000 is an unknown value +x12=0x0000000000000050 is an unknown value +x13=0x000060000242e518 points into unknown readable memory: 0x000000009d84d82f | 2f d8 84 9d 00 00 00 00 +x14=0x00000000001ff800 is an unknown value +x15=0x00000000000007fb is an unknown value +x16=0x0000000186e7d030: __bzero+0 in /usr/lib/system/libsystem_platform.dylib at 0x0000000186e7a000 +x17=0x00000001f4ef54a8 points into unknown readable memory: 0x0000000186e7d030 | 30 d0 e7 86 01 00 00 00 +x18=0x0 is NULL +x19=0x0000600000694eb0 points into unknown readable memory: 0x0000000000000000 | 00 00 00 00 00 00 00 00 +x20=0x0 is NULL +x21=0x0000600001324000 points into unknown readable memory: 0x00006000002382a0 | a0 82 23 00 00 60 00 00 +x22=0x00000001046c5e5f points into unknown readable memory: 50 +x23=0x00000000d3a18b02 is an unknown value +x24=0x000000000000002f is an unknown value +x25=0x000000000000003d is an unknown value +x26=0x00000000000000cd is an unknown value +x27=0x0000600000694ed8 points into unknown readable memory: 0x0000000000000000 | 00 00 00 00 00 00 00 00 +x28=0x0000000105127d40 points into unknown readable memory: 0x65746e692f6d6f63 | 63 6f 6d 2f 69 6e 74 65 + + +Registers: + x0=0x0000600000694eb0 x1=0x0000000000000000 x2=0x0000000000000000 x3=0x0000600000694ec0 + x4=0x0000600000694f00 x5=0x000000009d64e030 x6=0x0000000000000eb0 x7=0x000000000000000a + x8=0x00000001047ede5f x9=0x0000000000128000 x10=0x0000600000694eb0 x11=0x000066d8c3c50000 +x12=0x0000000000000050 x13=0x000060000242e518 x14=0x00000000001ff800 x15=0x00000000000007fb +x16=0x0000000186e7d030 x17=0x00000001f4ef54a8 x18=0x0000000000000000 x19=0x0000600000694eb0 +x20=0x0000000000000000 x21=0x0000600001324000 x22=0x00000001046c5e5f x23=0x00000000d3a18b02 +x24=0x000000000000002f x25=0x000000000000003d x26=0x00000000000000cd x27=0x0000600000694ed8 +x28=0x0000000105127d40 fp=0x0000000318411a50 lr=0x00000001046de48c sp=0x00000003184119d0 +pc=0x00000001046de4c0 cpsr=0x0000000060001000 +Top of Stack: (sp=0x00000003184119d0) +0x00000003184119d0: 0000000000000000 0000000000000000 +0x00000003184119e0: 0000000000000000 0000000000000000 +0x00000003184119f0: 0000000000000000 0000000000000000 +0x0000000318411a00: 0000000105127d40 0000000107809600 +0x0000000318411a10: 00000000000000cd 000000000000003d +0x0000000318411a20: 000000000000002f 00000000d3a18b02 +0x0000000318411a30: 0000600000694f00 0000000000000000 +0x0000000318411a40: 0000000105127d80 0000600001324000 +0x0000000318411a50: 0000000318411ab0 00000001046de390 +0x0000000318411a60: 0000000105127d40 0000000000000037 +0x0000000318411a70: 0000000000000001 0000000105127d80 +0x0000000318411a80: 000000010fa93f48 0000000105127d80 +0x0000000318411a90: 0000600001324000 0000000105127d80 +0x0000000318411aa0: 0000000318411bdc 0000000318411af4 +0x0000000318411ab0: 0000000318411ae0 00000001046ded78 +0x0000000318411ac0: 0000000318411bdc 00006000028341e0 +0x0000000318411ad0: 0000000000000000 000000010fa93c00 +0x0000000318411ae0: 0000000318411bc0 0000000105a1a408 +0x0000000318411af0: 0000000106278388 0000000000000100 +0x0000000318411b00: 0000000318411b20 0000000105cee5dc +0x0000000318411b10: 0000000106278388 0000000318411ba0 +0x0000000318411b20: 0000000318411b70 0000000105aa096c +0x0000000318411b30: 0000000000000000 0000000000000000 +0x0000000318411b40: 0000000000000001 0000000158a50290 +0x0000000318411b50: 000000010fa93c00 0000000105128118 +0x0000000318411b60: 000000010628d1e2 0000000318411c68 +0x0000000318411b70: 0000000318411b90 43eef1f7fdfa0026 +0x0000000318411b80: 0000000000000001 0000000105127d80 +0x0000000318411b90: 00006000028341e0 0000000158a50290 +0x0000000318411ba0: 000000010fa93c00 0000000105128118 +0x0000000318411bb0: 0000000105127d30 00006000028341e0 +0x0000000318411bc0: 0000000318411bf0 0000000105a1a53c + +Instructions: (pc=0x00000001046de4c0) +0x00000001046de3c0: 6b0c017f 54ffff60 17ffffde d2800016 +0x00000001046de3d0: 72001ebf 54000160 b5000156 f100073f +0x00000001046de3e0: 54fff7cb 8b140328 385ff108 7100bd1f +0x00000001046de3f0: 54fff741 d2800016 14000002 f9004e7f +0x00000001046de400: f9402a60 94000451 aa1603e0 a9457bfd +0x00000001046de410: a9444ff4 a94357f6 a9425ff8 a94167fa +0x00000001046de420: a8c66ffc d65f03c0 6b03003f 540000e1 +0x00000001046de430: 71000421 540000eb 38401408 38401449 +0x00000001046de440: 6b09011f 54ffff60 52800000 d65f03c0 +0x00000001046de450: 52800020 d65f03c0 d10243ff a9036ffc +0x00000001046de460: a90467fa a9055ff8 a90657f6 a9074ff4 +0x00000001046de470: a9087bfd 910203fd aa0203f4 aa0103f6 +0x00000001046de480: aa0003f5 52800900 94000481 aa0003f3 +0x00000001046de490: b4001320 f900027f aa1303fb f8028f7f +0x00000001046de4a0: f9001a7f 3940c2a8 34000288 f9400ea8 +0x00000001046de4b0: f94006c9 8b090108 f94016a9 cb090116 +0x00000001046de4c0: 79403ad8 39407ada 39407edc 794042c8 +0x00000001046de4d0: f90017e8 b9400ec8 f9000668 b9401ac8 +0x00000001046de4e0: f9000fe8 f9000a68 794016c8 34000488 +0x00000001046de4f0: b94016c8 14000023 f94006d7 34000d54 +0x00000001046de500: f9401ea8 b4000288 f94022a9 eb17013f +0x00000001046de510: 5400022c 5283fa4a 8b0a012a eb17015f +0x00000001046de520: 540001ab 9140092a 8b170108 cb090116 +0x00000001046de530: 79403ac8 79403ec9 794042cb 8b0802e8 +0x00000001046de540: 8b090108 8b0b0108 9100b908 eb0a011f +0x00000001046de550: 54000b4d aa1503e0 aa1703e1 52840002 +0x00000001046de560: 94000384 aa0003f6 b4000aa0 f9401ea0 +0x00000001046de570: 94000429 a903deb6 17ffffd2 d2800008 +0x00000001046de580: aa0803f7 f9000e68 b94012c8 b9002268 +0x00000001046de590: b842a2c9 f9405ea8 f9000be9 8b090108 +0x00000001046de5a0: cb0803e8 f9001e68 794012c8 b9004268 +0x00000001046de5b0: 91000700 94000436 aa0003f9 f9000260 + + +Stack slot to memory mapping: +stack at sp + 0 slots: 0x0 is NULL +stack at sp + 1 slots: 0x0 is NULL +stack at sp + 2 slots: 0x0 is NULL +stack at sp + 3 slots: 0x0 is NULL +stack at sp + 4 slots: 0x0 is NULL +stack at sp + 5 slots: 0x0 is NULL +stack at sp + 6 slots: 0x0000000105127d40 points into unknown readable memory: 0x65746e692f6d6f63 | 63 6f 6d 2f 69 6e 74 65 +stack at sp + 7 slots: 0x0000000107809600 points into unknown readable memory: 0xffffffff5bbd78a2 | a2 78 bd 5b ff ff ff ff + + +--------------- P R O C E S S --------------- + +Threads class SMR info: +_java_thread_list=0x000060000217cce0, length=71, elements={ +0x0000000107808a00, 0x000000011b808200, 0x000000010f813a00, 0x0000000107009200, +0x0000000107009800, 0x000000011e014a00, 0x000000011b00c400, 0x0000000107823800, +0x000000011c00a200, 0x000000011a813c00, 0x000000011a816400, 0x000000011e80fa00, +0x000000011a0d9600, 0x000000010f812c00, 0x000000011e02fe00, 0x000000011b1b6800, +0x000000011a573600, 0x000000011c8e8800, 0x000000011b960000, 0x0000000107024a00, +0x000000011f3d8600, 0x000000011eba7600, 0x000000010f9e0600, 0x000000011f41b600, +0x000000011c068400, 0x000000011f47fc00, 0x000000011c047c00, 0x000000011f440200, +0x000000011b404800, 0x000000011f4a3a00, 0x000000011c07bc00, 0x000000011c095c00, +0x000000011b95a800, 0x000000011e499200, 0x000000011e4bd000, 0x000000011b4a8800, +0x000000011a66aa00, 0x000000011f504600, 0x000000011ad0cc00, 0x000000011f598a00, +0x000000011b9bb000, 0x000000011a693400, 0x000000011f3e9600, 0x0000000107978800, +0x0000000107973c00, 0x0000000107934000, 0x0000000107961000, 0x000000011b989600, +0x000000011ad8e400, 0x000000011ad93000, 0x000000011e4a8000, 0x000000011b52f200, +0x000000011b9a0a00, 0x000000011a69d400, 0x000000011b563c00, 0x000000010fa93c00, +0x000000011b5a3a00, 0x000000011e51f600, 0x000000011f65ba00, 0x000000011b62e600, +0x000000011edd6200, 0x000000011edf8e00, 0x000000011f67a800, 0x000000011ae25600, +0x000000011ae41800, 0x000000011ae41e00, 0x000000011edf4400, 0x000000011edef000, +0x000000011b3b4400, 0x0000000107ada400, 0x000000011b77a400 +} + +Java Threads: ( => current thread ) + 0x0000000107808a00 JavaThread "main" [_thread_in_native, id=4355, stack(0x000000016b888000,0x000000016ba8b000)] + 0x000000011b808200 JavaThread "Reference Handler" daemon [_thread_blocked, id=18179, stack(0x000000016c6dc000,0x000000016c8df000)] + 0x000000010f813a00 JavaThread "Finalizer" daemon [_thread_blocked, id=18947, stack(0x000000016c8e8000,0x000000016caeb000)] + 0x0000000107009200 JavaThread "Signal Dispatcher" daemon [_thread_blocked, id=31235, stack(0x000000016cc0c000,0x000000016ce0f000)] + 0x0000000107009800 JavaThread "Service Thread" daemon [_thread_blocked, id=30723, stack(0x000000016ce18000,0x000000016d01b000)] + 0x000000011e014a00 JavaThread "Monitor Deflation Thread" daemon [_thread_blocked, id=23299, stack(0x000000016d024000,0x000000016d227000)] + 0x000000011b00c400 JavaThread "C1 CompilerThread0" daemon [_thread_blocked, id=30211, stack(0x000000016d230000,0x000000016d433000)] + 0x0000000107823800 JavaThread "Sweeper thread" daemon [_thread_blocked, id=24067, stack(0x000000016d43c000,0x000000016d63f000)] + 0x000000011c00a200 JavaThread "Common-Cleaner" daemon [_thread_blocked, id=24579, stack(0x000000016d648000,0x000000016d84b000)] + 0x000000011a813c00 JavaThread "JDWP Transport Listener: dt_socket" daemon [_thread_blocked, id=25091, stack(0x000000016d854000,0x000000016da57000)] + 0x000000011a816400 JavaThread "JDWP Event Helper Thread" daemon [_thread_blocked, id=29699, stack(0x000000016da60000,0x000000016dc63000)] + 0x000000011e80fa00 JavaThread "JDWP Command Reader" daemon [_thread_in_native, id=29443, stack(0x000000016dc6c000,0x000000016de6f000)] + 0x000000011a0d9600 JavaThread "IntelliJ Suspend Helper" daemon [_thread_blocked, id=28931, stack(0x000000016de78000,0x000000016e07b000)] + 0x000000010f812c00 JavaThread "Notification Thread" daemon [_thread_blocked, id=25603, stack(0x000000016e084000,0x000000016e287000)] + 0x000000011e02fe00 JavaThread "CoarseTimer" daemon [_thread_blocked, id=28163, stack(0x000000016e290000,0x000000016e493000)] + 0x000000011b1b6800 JavaThread "RMI TCP Accept-0" daemon [_thread_in_native, id=34307, stack(0x000000016f708000,0x000000016f90b000)] + 0x000000011a573600 JavaThread "com.alibaba.nacos.client.logging.0" daemon [_thread_blocked, id=35331, stack(0x000000016fb20000,0x000000016fd23000)] + 0x000000011c8e8800 JavaThread "Attach Listener" daemon [_thread_blocked, id=42243, stack(0x000000016fd2c000,0x000000016ff2f000)] + 0x000000011b960000 JavaThread "RMI TCP Connection(1)-127.0.0.1" daemon [_thread_in_native, id=40963, stack(0x0000000318a40000,0x0000000318c43000)] + 0x0000000107024a00 JavaThread "nacos.publisher-com.alibaba.nacos.common.notify.SlowEvent" daemon [_thread_blocked, id=37635, stack(0x0000000318c4c000,0x0000000318e4f000)] + 0x000000011f3d8600 JavaThread "nacos.publisher-com.alibaba.nacos.client.config.impl.ConfigFuzzyWatchNotifyEvent" daemon [_thread_blocked, id=40451, stack(0x0000000318e58000,0x000000031905b000)] + 0x000000011eba7600 JavaThread "nacos.publisher-com.alibaba.nacos.client.config.impl.ConfigFuzzyWatchLoadEvent" daemon [_thread_blocked, id=39939, stack(0x0000000319064000,0x0000000319267000)] + 0x000000010f9e0600 JavaThread "RMI Scheduler(0)" daemon [_thread_blocked, id=38915, stack(0x0000000319688000,0x000000031988b000)] + 0x000000011f41b600 JavaThread "com.alibaba.nacos.client.auth.ram.identify.watcher.0" daemon [_thread_blocked, id=43527, stack(0x0000000319894000,0x0000000319a97000)] + 0x000000011c068400 JavaThread "com.alibaba.nacos.client.login-executor.0" daemon [_thread_blocked, id=37127, stack(0x0000000318834000,0x0000000318a37000)] + 0x000000011f47fc00 JavaThread "com.alibaba.nacos.client.listen-executor.0" daemon [_thread_blocked, id=43779, stack(0x0000000319aa0000,0x0000000319ca3000)] + 0x000000011c047c00 JavaThread "com.alibaba.nacos.client.fuzzy-watcher-executor.0" daemon [_thread_blocked, id=44291, stack(0x0000000319cac000,0x0000000319eaf000)] + 0x000000011f440200 JavaThread "com.alibaba.nacos.client.remote.worker.0" daemon [_thread_blocked, id=44803, stack(0x0000000319eb8000,0x000000031a0bb000)] + 0x000000011b404800 JavaThread "com.alibaba.nacos.client.remote.worker.1" daemon [_thread_blocked, id=64771, stack(0x000000031a0c4000,0x000000031a2c7000)] + 0x000000011f4a3a00 JavaThread "grpc-nio-worker-ELG-1-1" daemon [_thread_in_native, id=64531, stack(0x000000031a2d0000,0x000000031a4d3000)] + 0x000000011c07bc00 JavaThread "grpc-default-executor-0" daemon [_thread_blocked, id=64003, stack(0x000000031a4dc000,0x000000031a6df000)] + 0x000000011c095c00 JavaThread "nacos-grpc-client-executor-127.0.0.1-0" daemon [_thread_blocked, id=45315, stack(0x000000031a6e8000,0x000000031a8eb000)] + 0x000000011b95a800 JavaThread "nacos-grpc-client-executor-127.0.0.1-1" daemon [_thread_blocked, id=45827, stack(0x000000031a8f4000,0x000000031aaf7000)] + 0x000000011e499200 JavaThread "grpc-nio-worker-ELG-1-2" daemon [_thread_in_native, id=46083, stack(0x000000031ab00000,0x000000031ad03000)] + 0x000000011e4bd000 JavaThread "nacos-grpc-client-executor-127.0.0.1-2" daemon [_thread_blocked, id=46339, stack(0x000000031ad0c000,0x000000031af0f000)] + 0x000000011b4a8800 JavaThread "nacos-grpc-client-executor-127.0.0.1-3" daemon [_thread_blocked, id=62979, stack(0x000000031af18000,0x000000031b11b000)] + 0x000000011a66aa00 JavaThread "nacos-grpc-client-executor-127.0.0.1-4" daemon [_thread_blocked, id=62467, stack(0x000000031b124000,0x000000031b327000)] + 0x000000011f504600 JavaThread "nacos-grpc-client-executor-127.0.0.1-5" daemon [_thread_blocked, id=46851, stack(0x000000031b330000,0x000000031b533000)] + 0x000000011ad0cc00 JavaThread "nacos-grpc-client-executor-127.0.0.1-6" daemon [_thread_blocked, id=47363, stack(0x000000031b53c000,0x000000031b73f000)] + 0x000000011f598a00 JavaThread "nacos-grpc-client-executor-127.0.0.1-7" daemon [_thread_blocked, id=61955, stack(0x000000031b748000,0x000000031b94b000)] + 0x000000011b9bb000 JavaThread "nacos.publisher-com.alibaba.nacos.common.ability.AbstractAbilityControlManager$AbilityUpdateEvent" daemon [_thread_blocked, id=61443, stack(0x000000031b954000,0x000000031bb57000)] + 0x000000011a693400 JavaThread "nacos-grpc-client-executor-127.0.0.1-8" daemon [_thread_blocked, id=47875, stack(0x000000031bb60000,0x000000031bd63000)] + 0x000000011f3e9600 JavaThread "nacos-grpc-client-executor-127.0.0.1-9" daemon [_thread_blocked, id=60931, stack(0x000000031bd6c000,0x000000031bf6f000)] + 0x0000000107978800 JavaThread "nacos-grpc-client-executor-127.0.0.1-10" daemon [_thread_blocked, id=48387, stack(0x000000031bf78000,0x000000031c17b000)] + 0x0000000107973c00 JavaThread "nacos-grpc-client-executor-127.0.0.1-11" daemon [_thread_blocked, id=48643, stack(0x000000031c184000,0x000000031c387000)] + 0x0000000107934000 JavaThread "nacos-grpc-client-executor-127.0.0.1-12" daemon [_thread_blocked, id=48899, stack(0x000000031c390000,0x000000031c593000)] + 0x0000000107961000 JavaThread "nacos-grpc-client-executor-127.0.0.1-13" daemon [_thread_blocked, id=49155, stack(0x000000031c59c000,0x000000031c79f000)] + 0x000000011b989600 JavaThread "nacos-grpc-client-executor-127.0.0.1-14" daemon [_thread_blocked, id=59395, stack(0x000000031c7a8000,0x000000031c9ab000)] + 0x000000011ad8e400 JavaThread "nacos-grpc-client-executor-127.0.0.1-15" daemon [_thread_blocked, id=49667, stack(0x000000031c9b4000,0x000000031cbb7000)] + 0x000000011ad93000 JavaThread "nacos-grpc-client-executor-127.0.0.1-16" daemon [_thread_blocked, id=58883, stack(0x000000031cbc0000,0x000000031cdc3000)] + 0x000000011e4a8000 JavaThread "nacos-grpc-client-executor-127.0.0.1-17" daemon [_thread_blocked, id=58627, stack(0x000000031cdcc000,0x000000031cfcf000)] + 0x000000011b52f200 JavaThread "nacos-grpc-client-executor-127.0.0.1-18" daemon [_thread_blocked, id=50435, stack(0x000000031cfd8000,0x000000031d1db000)] + 0x000000011b9a0a00 JavaThread "nacos-grpc-client-executor-127.0.0.1-19" daemon [_thread_blocked, id=50955, stack(0x000000031d1e4000,0x000000031d3e7000)] + 0x000000011a69d400 JavaThread "nacos-grpc-client-executor-127.0.0.1-20" daemon [_thread_blocked, id=51203, stack(0x000000031d3f0000,0x000000031d5f3000)] + 0x000000011b563c00 JavaThread "nacos-grpc-client-executor-127.0.0.1-21" daemon [_thread_blocked, id=51459, stack(0x000000031d5fc000,0x000000031d7ff000)] +=>0x000000010fa93c00 JavaThread "sentinel-datafile-log-executor-thread-1" daemon [_thread_in_native, id=36131, stack(0x0000000318210000,0x0000000318413000)] + 0x000000011b5a3a00 JavaThread "sentinel-command-center-executor-thread-1" daemon [_thread_in_native, id=36635, stack(0x0000000318628000,0x000000031882b000)] + 0x000000011e51f600 JavaThread "sentinel-heartbeat-send-task-thread-1" daemon [_thread_blocked, id=41523, stack(0x000000031841c000,0x000000031861f000)] + 0x000000011f65ba00 JavaThread "C1 CompilerThread1" daemon [_thread_blocked, id=36403, stack(0x0000000318004000,0x0000000318207000)] + 0x000000011b62e600 JavaThread "nacos-grpc-client-executor-127.0.0.1-22" daemon [_thread_blocked, id=57635, stack(0x000000031d808000,0x000000031da0b000)] + 0x000000011edd6200 JavaThread "nacos-grpc-client-executor-127.0.0.1-23" daemon [_thread_blocked, id=57095, stack(0x000000031da14000,0x000000031dc17000)] + 0x000000011edf8e00 JavaThread "nacos-grpc-client-executor-127.0.0.1-24" daemon [_thread_blocked, id=56579, stack(0x000000031dc20000,0x000000031de23000)] + 0x000000011f67a800 JavaThread "nacos-grpc-client-executor-127.0.0.1-25" daemon [_thread_blocked, id=51715, stack(0x000000031de2c000,0x000000031e02f000)] + 0x000000011ae25600 JavaThread "nacos-grpc-client-executor-127.0.0.1-26" daemon [_thread_blocked, id=56067, stack(0x000000031e038000,0x000000031e23b000)] + 0x000000011ae41800 JavaThread "nacos-grpc-client-executor-127.0.0.1-27" daemon [_thread_blocked, id=55555, stack(0x000000031e244000,0x000000031e447000)] + 0x000000011ae41e00 JavaThread "nacos-grpc-client-executor-127.0.0.1-28" daemon [_thread_blocked, id=52483, stack(0x000000031e450000,0x000000031e653000)] + 0x000000011edf4400 JavaThread "nacos-grpc-client-executor-127.0.0.1-29" daemon [_thread_blocked, id=55043, stack(0x000000031e65c000,0x000000031e85f000)] + 0x000000011edef000 JavaThread "nacos-grpc-client-executor-127.0.0.1-30" daemon [_thread_blocked, id=54787, stack(0x000000031e868000,0x000000031ea6b000)] + 0x000000011b3b4400 JavaThread "nacos-grpc-client-executor-127.0.0.1-31" daemon [_thread_blocked, id=54531, stack(0x000000031ea74000,0x000000031ec77000)] + 0x0000000107ada400 JavaThread "sentinel-time-tick-thread" daemon [_thread_blocked, id=32027, stack(0x000000031ec80000,0x000000031ee83000)] + 0x000000011b77a400 JavaThread "sentinel-heartbeat-send-task-thread-2" daemon [_thread_blocked, id=53555, stack(0x000000031ee8c000,0x000000031f08f000)] + +Other Threads: + 0x0000000104d04d00 VMThread "VM Thread" [stack: 0x000000016c4d0000,0x000000016c6d3000] [id=19715] + 0x0000000104c0a4f0 WatcherThread [stack: 0x000000016f914000,0x000000016fb17000] [id=34819] + 0x0000000104e067f0 GCTaskThread "GC Thread#0" [stack: 0x000000016ba94000,0x000000016bc97000] [id=12547] + 0x00000001051109e0 GCTaskThread "GC Thread#1" [stack: 0x000000016e49c000,0x000000016e69f000] [id=25859] + 0x0000000105214660 GCTaskThread "GC Thread#2" [stack: 0x000000016e6a8000,0x000000016e8ab000] [id=27395] + 0x000000010de05990 GCTaskThread "GC Thread#3" [stack: 0x000000016e8b4000,0x000000016eab7000] [id=26883] + 0x0000000105110e70 GCTaskThread "GC Thread#4" [stack: 0x000000016eac0000,0x000000016ecc3000] [id=26115] + 0x00000001051116f0 GCTaskThread "GC Thread#5" [stack: 0x000000016eccc000,0x000000016eecf000] [id=32771] + 0x0000000105111f70 GCTaskThread "GC Thread#6" [stack: 0x000000016eed8000,0x000000016f0db000] [id=43011] + 0x00000001051127f0 GCTaskThread "GC Thread#7" [stack: 0x000000016f0e4000,0x000000016f2e7000] [id=33283] + 0x0000000105113070 GCTaskThread "GC Thread#8" [stack: 0x000000016f2f0000,0x000000016f4f3000] [id=33795] + 0x0000000104809f30 GCTaskThread "GC Thread#9" [stack: 0x000000016f4fc000,0x000000016f6ff000] [id=42755] + 0x00000001050042b0 ConcurrentGCThread "G1 Main Marker" [stack: 0x000000016bca0000,0x000000016bea3000] [id=14083] + 0x000000011df045c0 ConcurrentGCThread "G1 Conc#0" [stack: 0x000000016beac000,0x000000016c0af000] [id=13827] + 0x000000010512ad30 ConcurrentGCThread "G1 Conc#1" [stack: 0x0000000319270000,0x0000000319473000] [id=39427] + 0x0000000104e11120 ConcurrentGCThread "G1 Conc#2" [stack: 0x000000031947c000,0x000000031967f000] [id=38403] + 0x0000000105105c00 ConcurrentGCThread "G1 Refine#0" [stack: 0x000000016c0b8000,0x000000016c2bb000] [id=16643] + 0x0000000105204080 ConcurrentGCThread "G1 Service" [stack: 0x000000016c2c4000,0x000000016c4c7000] [id=21251] + +Threads with active compile tasks: + +VM state: not at safepoint (normal execution) + +VM Mutex/Monitor currently owned by a thread: None + +Heap address: 0x00000005c0000000, size: 9216 MB, Compressed Oops mode: Zero based, Oop shift amount: 3 + +CDS archive(s) mapped at: [0x000000e000000000-0x000000e000c14000-0x000000e000c14000), size 12664832, SharedBaseAddress: 0x000000e000000000, ArchiveRelocationMode: 1. +Compressed class space mapped at: 0x000000e001000000-0x000000e041000000, reserved size: 1073741824 +Narrow klass base: 0x000000e000000000, Narrow klass shift: 0, Narrow klass range: 0x100000000 + +GC Precious Log: + CPUs: 12 total, 12 available + Memory: 36864M + Large Page Support: Disabled + NUMA Support: Disabled + Compressed Oops: Enabled (Zero based) + Heap Region Size: 8M + Heap Min Capacity: 8M + Heap Initial Capacity: 576M + Heap Max Capacity: 9G + Pre-touch: Disabled + Parallel Workers: 10 + Concurrent Workers: 3 + Concurrent Refinement Workers: 10 + Periodic GC: Disabled + +Heap: + garbage-first heap total 196608K, used 119331K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 10 young (81920K), 1 survivors (8192K) + Metaspace used 65956K, committed 66496K, reserved 1114112K + class space used 8750K, committed 9024K, reserved 1048576K + +Heap Regions: E=young(eden), S=young(survivor), O=old, HS=humongous(starts), HC=humongous(continues), CS=collection set, F=free, OA=open archive, CA=closed archive, TAMS=top-at-mark-start (previous, next) +| 0|0x00000005c0000000, 0x00000005c0800000, 0x00000005c0800000|100%| O| |TAMS 0x00000005c0800000, 0x00000005c0000000| Untracked +| 1|0x00000005c0800000, 0x00000005c1000000, 0x00000005c1000000|100%| O| |TAMS 0x00000005c1000000, 0x00000005c0800000| Untracked +| 2|0x00000005c1000000, 0x00000005c1800000, 0x00000005c1800000|100%| O| |TAMS 0x00000005c178d800, 0x00000005c1000000| Untracked +| 3|0x00000005c1800000, 0x00000005c1f6fc00, 0x00000005c2000000| 92%| O| |TAMS 0x00000005c1f6fc00, 0x00000005c1800000| Untracked +| 4|0x00000005c2000000, 0x00000005c2521200, 0x00000005c2800000| 64%| O| |TAMS 0x00000005c2000000, 0x00000005c2000000| Untracked +| 5|0x00000005c2800000, 0x00000005c2800000, 0x00000005c3000000| 0%| F| |TAMS 0x00000005c2800000, 0x00000005c2800000| Untracked +| 6|0x00000005c3000000, 0x00000005c3000000, 0x00000005c3800000| 0%| F| |TAMS 0x00000005c3000000, 0x00000005c3000000| Untracked +| 7|0x00000005c3800000, 0x00000005c3800000, 0x00000005c4000000| 0%| F| |TAMS 0x00000005c3800000, 0x00000005c3800000| Untracked +| 8|0x00000005c4000000, 0x00000005c4000000, 0x00000005c4800000| 0%| F| |TAMS 0x00000005c4000000, 0x00000005c4000000| Untracked +| 9|0x00000005c4800000, 0x00000005c4800000, 0x00000005c5000000| 0%| F| |TAMS 0x00000005c4800000, 0x00000005c4800000| Untracked +| 10|0x00000005c5000000, 0x00000005c5000000, 0x00000005c5800000| 0%| F| |TAMS 0x00000005c5000000, 0x00000005c5000000| Untracked +| 11|0x00000005c5800000, 0x00000005c5800000, 0x00000005c6000000| 0%| F| |TAMS 0x00000005c5800000, 0x00000005c5800000| Untracked +| 12|0x00000005c6000000, 0x00000005c64df298, 0x00000005c6800000| 60%| E| |TAMS 0x00000005c6000000, 0x00000005c6000000| Complete +| 13|0x00000005c6800000, 0x00000005c7000000, 0x00000005c7000000|100%| E|CS|TAMS 0x00000005c6800000, 0x00000005c6800000| Complete +| 14|0x00000005c7000000, 0x00000005c7800000, 0x00000005c7800000|100%| E|CS|TAMS 0x00000005c7000000, 0x00000005c7000000| Complete +| 15|0x00000005c7800000, 0x00000005c8000000, 0x00000005c8000000|100%| E|CS|TAMS 0x00000005c7800000, 0x00000005c7800000| Complete +| 16|0x00000005c8000000, 0x00000005c8800000, 0x00000005c8800000|100%| S|CS|TAMS 0x00000005c8000000, 0x00000005c8000000| Complete +| 17|0x00000005c8800000, 0x00000005c9000000, 0x00000005c9000000|100%| E|CS|TAMS 0x00000005c8800000, 0x00000005c8800000| Complete +| 18|0x00000005c9000000, 0x00000005c9800000, 0x00000005c9800000|100%| E|CS|TAMS 0x00000005c9000000, 0x00000005c9000000| Complete +| 19|0x00000005c9800000, 0x00000005ca000000, 0x00000005ca000000|100%| E|CS|TAMS 0x00000005c9800000, 0x00000005c9800000| Complete +| 68|0x00000005e2000000, 0x00000005e2800000, 0x00000005e2800000|100%| E|CS|TAMS 0x00000005e2000000, 0x00000005e2000000| Complete +| 71|0x00000005e3800000, 0x00000005e4000000, 0x00000005e4000000|100%| E|CS|TAMS 0x00000005e3800000, 0x00000005e3800000| Complete +|1150|0x00000007ff000000, 0x00000007ff778000, 0x00000007ff800000| 93%|OA| |TAMS 0x00000007ff778000, 0x00000007ff000000| Untracked +|1151|0x00000007ff800000, 0x00000007ff880000, 0x0000000800000000| 6%|CA| |TAMS 0x00000007ff880000, 0x00000007ff800000| Untracked + +Card table byte_map: [0x000000010c200000,0x000000010d400000] _byte_map_base: 0x0000000109400000 + +Marking Bits (Prev, Next): (CMBitMap*) 0x000000010f808250, (CMBitMap*) 0x000000010f808210 + Prev Bits: [0x0000000149000000, 0x0000000152000000) + Next Bits: [0x0000000140000000, 0x0000000149000000) + +Polling page: 0x0000000104698000 + +Metaspace: + +Usage: + Non-class: 55.87 MB used. + Class: 8.55 MB used. + Both: 64.41 MB used. + +Virtual space: + Non-class space: 64.00 MB reserved, 56.12 MB ( 88%) committed, 1 nodes. + Class space: 1.00 GB reserved, 8.81 MB ( <1%) committed, 1 nodes. + Both: 1.06 GB reserved, 64.94 MB ( 6%) committed. + +Chunk freelists: + Non-Class: 7.80 MB + Class: 7.22 MB + Both: 15.02 MB + +MaxMetaspaceSize: unlimited +CompressedClassSpaceSize: 1.00 GB +Initial GC threshold: 21.00 MB +Current GC threshold: 100.31 MB +CDS: on +MetaspaceReclaimPolicy: balanced + - commit_granule_bytes: 65536. + - commit_granule_words: 8192. + - virtual_space_node_default_size: 8388608. + - enlarge_chunks_in_place: 1. + - new_chunks_are_fully_committed: 0. + - uncommit_free_chunks: 1. + - use_allocation_guard: 0. + - handle_deallocations: 1. + + +Internal statistics: + +num_allocs_failed_limit: 17. +num_arena_births: 702. +num_arena_deaths: 2. +num_vsnodes_births: 2. +num_vsnodes_deaths: 0. +num_space_committed: 1039. +num_space_uncommitted: 0. +num_chunks_returned_to_freelist: 19. +num_chunks_taken_from_freelist: 2813. +num_chunk_merges: 12. +num_chunk_splits: 2108. +num_chunks_enlarged: 1687. +num_inconsistent_stats: 0. + +CodeCache: size=49152Kb used=11912Kb max_used=11912Kb free=37239Kb + bounds [0x0000000108000000, 0x0000000108bb0000, 0x000000010b000000] + total_blobs=6643 nmethods=6019 adapters=554 + compilation: enabled + stopped_count=0, restarted_count=0 + full_count=0 + +Compilation events (20 events): +Event: 7.622 Thread 0x000000011b00c400 6308 1 org.aspectj.internal.lang.reflect.PerClauseImpl::getKind (5 bytes) +Event: 7.622 Thread 0x000000011b00c400 nmethod 6308 0x0000000108b9ea10 code [0x0000000108b9eb80, 0x0000000108b9ec18] +Event: 7.622 Thread 0x000000011b00c400 6310 ! 1 jdk.proxy2.$Proxy142::annotationType (29 bytes) +Event: 7.622 Thread 0x000000011b00c400 nmethod 6310 0x0000000108b9ed10 code [0x0000000108b9eec0, 0x0000000108b9f0d8] +Event: 7.623 Thread 0x000000011b00c400 6311 1 java.lang.reflect.Field::getAnnotation (23 bytes) +Event: 7.623 Thread 0x000000011f65ba00 6312 1 org.springframework.aop.aspectj.annotation.BeanFactoryAspectInstanceFactory::getAspectMetadata (5 bytes) +Event: 7.623 Thread 0x000000011b00c400 nmethod 6311 0x0000000108b9f290 code [0x0000000108b9f480, 0x0000000108b9f778] +Event: 7.623 Thread 0x000000011f65ba00 nmethod 6312 0x0000000108b9f990 code [0x0000000108b9fb00, 0x0000000108b9fb98] +Event: 7.623 Thread 0x000000011b00c400 6313 1 org.springframework.aop.aspectj.annotation.AbstractAspectJAdvisorFactory::findAspectJAnnotationOnMethod (43 bytes) +Event: 7.623 Thread 0x000000011b00c400 nmethod 6313 0x0000000108b9fc90 code [0x0000000108b9fe40, 0x0000000108ba0018] +Event: 7.627 Thread 0x000000011b00c400 6314 ! 1 jdk.proxy2.$Proxy84::annotationType (29 bytes) +Event: 7.627 Thread 0x000000011b00c400 nmethod 6314 0x0000000108ba0190 code [0x0000000108ba0340, 0x0000000108ba0558] +Event: 7.627 Thread 0x000000011b00c400 6315 1 java.util.concurrent.atomic.AtomicInteger::getAndAdd (12 bytes) +Event: 7.627 Thread 0x000000011b00c400 nmethod 6315 0x0000000108ba0710 code [0x0000000108ba0880, 0x0000000108ba0958] +Event: 7.703 Thread 0x000000011f65ba00 6320 1 java.util.regex.Pattern::unread (11 bytes) +Event: 7.703 Thread 0x000000011b00c400 6321 1 java.util.regex.Pattern::qtype (39 bytes) +Event: 7.704 Thread 0x000000011f65ba00 nmethod 6320 0x0000000108ba1610 code [0x0000000108ba1780, 0x0000000108ba1858] +Event: 7.704 Thread 0x000000011b00c400 nmethod 6321 0x0000000108ba1910 code [0x0000000108ba1b00, 0x0000000108ba1e38] +Event: 7.704 Thread 0x000000011b00c400 6322 1 jdk.internal.misc.Unsafe::putReferenceOpaque (9 bytes) +Event: 7.704 Thread 0x000000011b00c400 nmethod 6322 0x0000000108ba2010 code [0x0000000108ba2180, 0x0000000108ba2298] + +GC Heap History (20 events): +Event: 0.685 GC heap before +{Heap before GC invocations=2 (full 0): + garbage-first heap total 606208K, used 49439K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 4 young (32768K), 1 survivors (8192K) + Metaspace used 11758K, committed 11968K, reserved 1114112K + class space used 1313K, committed 1408K, reserved 1048576K +} +Event: 0.687 GC heap after +{Heap after GC invocations=3 (full 0): + garbage-first heap total 606208K, used 28495K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 1 young (8192K), 1 survivors (8192K) + Metaspace used 11758K, committed 11968K, reserved 1114112K + class space used 1313K, committed 1408K, reserved 1048576K +} +Event: 1.150 GC heap before +{Heap before GC invocations=3 (full 0): + garbage-first heap total 606208K, used 77647K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 7 young (57344K), 1 survivors (8192K) + Metaspace used 19291K, committed 19584K, reserved 1114112K + class space used 2462K, committed 2560K, reserved 1048576K +} +Event: 1.153 GC heap after +{Heap after GC invocations=4 (full 0): + garbage-first heap total 606208K, used 31366K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 1 young (8192K), 1 survivors (8192K) + Metaspace used 19291K, committed 19584K, reserved 1114112K + class space used 2462K, committed 2560K, reserved 1048576K +} +Event: 1.228 GC heap before +{Heap before GC invocations=4 (full 0): + garbage-first heap total 606208K, used 47750K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 4 young (32768K), 1 survivors (8192K) + Metaspace used 21191K, committed 21504K, reserved 1114112K + class space used 2665K, committed 2816K, reserved 1048576K +} +Event: 1.231 GC heap after +{Heap after GC invocations=5 (full 0): + garbage-first heap total 606208K, used 32744K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 1 young (8192K), 1 survivors (8192K) + Metaspace used 21191K, committed 21504K, reserved 1114112K + class space used 2665K, committed 2816K, reserved 1048576K +} +Event: 2.119 GC heap before +{Heap before GC invocations=6 (full 0): + garbage-first heap total 221184K, used 106472K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 12 young (98304K), 1 survivors (8192K) + Metaspace used 35766K, committed 36096K, reserved 1114112K + class space used 4465K, committed 4608K, reserved 1048576K +} +Event: 2.122 GC heap after +{Heap after GC invocations=7 (full 0): + garbage-first heap total 221184K, used 35490K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 2 young (16384K), 2 survivors (16384K) + Metaspace used 35766K, committed 36096K, reserved 1114112K + class space used 4465K, committed 4608K, reserved 1048576K +} +Event: 2.130 GC heap before +{Heap before GC invocations=7 (full 0): + garbage-first heap total 221184K, used 35490K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 3 young (24576K), 2 survivors (16384K) + Metaspace used 36067K, committed 36416K, reserved 1114112K + class space used 4504K, committed 4672K, reserved 1048576K +} +Event: 2.134 GC heap after +{Heap after GC invocations=8 (full 0): + garbage-first heap total 221184K, used 36545K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 1 young (8192K), 1 survivors (8192K) + Metaspace used 36067K, committed 36416K, reserved 1114112K + class space used 4504K, committed 4672K, reserved 1048576K +} +Event: 2.147 GC heap before +{Heap before GC invocations=8 (full 0): + garbage-first heap total 221184K, used 36545K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 2 young (16384K), 1 survivors (8192K) + Metaspace used 36418K, committed 36736K, reserved 1114112K + class space used 4549K, committed 4672K, reserved 1048576K +} +Event: 2.148 GC heap after +{Heap after GC invocations=9 (full 0): + garbage-first heap total 442368K, used 35602K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 1 young (8192K), 1 survivors (8192K) + Metaspace used 36418K, committed 36736K, reserved 1114112K + class space used 4549K, committed 4672K, reserved 1048576K +} +Event: 3.154 GC heap before +{Heap before GC invocations=10 (full 0): + garbage-first heap total 196608K, used 158482K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 17 young (139264K), 1 survivors (8192K) + Metaspace used 48508K, committed 48960K, reserved 1114112K + class space used 6329K, committed 6528K, reserved 1048576K +} +Event: 3.158 GC heap after +{Heap after GC invocations=11 (full 0): + garbage-first heap total 196608K, used 40351K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 1 young (8192K), 1 survivors (8192K) + Metaspace used 48508K, committed 48960K, reserved 1114112K + class space used 6329K, committed 6528K, reserved 1048576K +} +Event: 4.257 GC heap before +{Heap before GC invocations=11 (full 0): + garbage-first heap total 196608K, used 138655K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 13 young (106496K), 1 survivors (8192K) + Metaspace used 54014K, committed 54464K, reserved 1114112K + class space used 7010K, committed 7232K, reserved 1048576K +} +Event: 4.263 GC heap after +{Heap after GC invocations=12 (full 0): + garbage-first heap total 196608K, used 46705K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 2 young (16384K), 2 survivors (16384K) + Metaspace used 54014K, committed 54464K, reserved 1114112K + class space used 7010K, committed 7232K, reserved 1048576K +} +Event: 5.515 GC heap before +{Heap before GC invocations=12 (full 0): + garbage-first heap total 196608K, used 136817K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 13 young (106496K), 2 survivors (16384K) + Metaspace used 58257K, committed 58624K, reserved 1114112K + class space used 7651K, committed 7808K, reserved 1048576K +} +Event: 5.523 GC heap after +{Heap after GC invocations=13 (full 0): + garbage-first heap total 196608K, used 52335K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 2 young (16384K), 2 survivors (16384K) + Metaspace used 58257K, committed 58624K, reserved 1114112K + class space used 7651K, committed 7808K, reserved 1048576K +} +Event: 6.372 GC heap before +{Heap before GC invocations=13 (full 0): + garbage-first heap total 196608K, used 85103K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 7 young (57344K), 2 survivors (16384K) + Metaspace used 61127K, committed 61568K, reserved 1114112K + class space used 8029K, committed 8256K, reserved 1048576K +} +Event: 6.376 GC heap after +{Heap after GC invocations=14 (full 0): + garbage-first heap total 196608K, used 53795K [0x00000005c0000000, 0x0000000800000000) + region size 8192K, 1 young (8192K), 1 survivors (8192K) + Metaspace used 61127K, committed 61568K, reserved 1114112K + class space used 8029K, committed 8256K, reserved 1048576K +} + +Dll operation events (11 events): +Event: 0.042 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libjava.dylib +Event: 0.043 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libzip.dylib +Event: 0.145 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libinstrument.dylib +Event: 0.147 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libnio.dylib +Event: 0.149 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libnet.dylib +Event: 0.169 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libjimage.dylib +Event: 0.179 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libzip.dylib +Event: 0.296 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libmanagement.dylib +Event: 0.309 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libmanagement_ext.dylib +Event: 0.423 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libextnet.dylib +Event: 5.147 Loaded shared library /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libverify.dylib + +Deoptimization events (20 events): +Event: 7.531 Thread 0x0000000107808a00 DEOPT PACKING pc=0x0000000108333ac8 sp=0x000000016ba89ac0 +Event: 7.531 Thread 0x0000000107808a00 DEOPT UNPACKING pc=0x000000010804777c sp=0x000000016ba897a0 mode 1 +Event: 7.531 Thread 0x0000000107808a00 DEOPT PACKING pc=0x00000001086c7750 sp=0x000000016ba89b60 +Event: 7.531 Thread 0x0000000107808a00 DEOPT UNPACKING pc=0x000000010804777c sp=0x000000016ba898e0 mode 1 +Event: 7.531 Thread 0x0000000107808a00 DEOPT PACKING pc=0x00000001082b8728 sp=0x000000016ba89240 +Event: 7.531 Thread 0x0000000107808a00 DEOPT UNPACKING pc=0x000000010804777c sp=0x000000016ba88ee0 mode 1 +Event: 7.531 Thread 0x0000000107808a00 DEOPT PACKING pc=0x00000001082b823c sp=0x000000016ba89310 +Event: 7.531 Thread 0x0000000107808a00 DEOPT UNPACKING pc=0x000000010804777c sp=0x000000016ba88fe0 mode 1 +Event: 7.531 Thread 0x0000000107808a00 DEOPT PACKING pc=0x0000000108333ac8 sp=0x000000016ba89ab0 +Event: 7.531 Thread 0x0000000107808a00 DEOPT UNPACKING pc=0x000000010804777c sp=0x000000016ba89790 mode 1 +Event: 7.531 Thread 0x0000000107808a00 DEOPT PACKING pc=0x00000001086c7750 sp=0x000000016ba89b50 +Event: 7.531 Thread 0x0000000107808a00 DEOPT UNPACKING pc=0x000000010804777c sp=0x000000016ba898d0 mode 1 +Event: 7.543 Thread 0x0000000107808a00 DEOPT PACKING pc=0x0000000108535ad4 sp=0x000000016ba89bb0 +Event: 7.543 Thread 0x0000000107808a00 DEOPT UNPACKING pc=0x000000010804777c sp=0x000000016ba89930 mode 1 +Event: 7.545 Thread 0x0000000107808a00 DEOPT PACKING pc=0x0000000108535ad4 sp=0x000000016ba89ba0 +Event: 7.545 Thread 0x0000000107808a00 DEOPT UNPACKING pc=0x000000010804777c sp=0x000000016ba89920 mode 1 +Event: 7.572 Thread 0x0000000107808a00 DEOPT PACKING pc=0x0000000108535ad4 sp=0x000000016ba89bb0 +Event: 7.572 Thread 0x0000000107808a00 DEOPT UNPACKING pc=0x000000010804777c sp=0x000000016ba89930 mode 1 +Event: 7.572 Thread 0x0000000107808a00 DEOPT PACKING pc=0x0000000108535ad4 sp=0x000000016ba89ba0 +Event: 7.572 Thread 0x0000000107808a00 DEOPT UNPACKING pc=0x000000010804777c sp=0x000000016ba89920 mode 1 + +Classes unloaded (1 events): +Event: 6.390 Thread 0x0000000104d04d00 Unloading class 0x000000e001554000 'SC' + +Classes redefined (1 events): +Event: 0.160 Thread 0x0000000104d04d00 redefined class name=java.lang.Throwable, count=1 + +Internal exceptions (20 events): +Event: 5.854 Thread 0x000000011b960000 Exception (0x00000005c9c7a940) +thrown [src/hotspot/share/runtime/reflection.cpp, line 1121] +Event: 6.357 Thread 0x000000011b960000 Exception (0x00000005c8b001d0) +thrown [src/hotspot/share/runtime/reflection.cpp, line 1121] +Event: 6.865 Thread 0x000000011b960000 Exception (0x00000005c9009658) +thrown [src/hotspot/share/runtime/reflection.cpp, line 1121] +Event: 7.070 Thread 0x0000000107808a00 Exception (0x00000005c8d01110) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 759] +Event: 7.070 Thread 0x0000000107808a00 Exception (0x00000005c8d05e08) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 759] +Event: 7.071 Thread 0x0000000107808a00 Exception (0x00000005c8d0b6a8) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 759] +Event: 7.072 Thread 0x0000000107808a00 Exception (0x00000005c8d17440) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 759] +Event: 7.177 Thread 0x0000000107808a00 Exception (0x00000005c7aaa528) +thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 248] +Event: 7.178 Thread 0x0000000107808a00 Exception (0x00000005c7ab2338) +thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 248] +Event: 7.187 Thread 0x0000000107808a00 Exception (0x00000005c7b50300) +thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 248] +Event: 7.187 Thread 0x0000000107808a00 Exception (0x00000005c7b577f8) +thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 248] +Event: 7.187 Thread 0x0000000107808a00 Exception (0x00000005c7b5fdb8) +thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 248] +Event: 7.207 Thread 0x0000000107808a00 Exception (0x00000005c7b914a8) +thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 248] +Event: 7.216 Thread 0x0000000107808a00 Exception (0x00000005c7b9a268) +thrown [src/hotspot/share/classfile/systemDictionary.cpp, line 248] +Event: 7.342 Thread 0x0000000107808a00 Exception (0x00000005c72e5358) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 759] +Event: 7.368 Thread 0x000000011b960000 Exception (0x00000005c7479150) +thrown [src/hotspot/share/runtime/reflection.cpp, line 1121] +Event: 7.479 Thread 0x0000000107808a00 Exception (0x00000005c69c4500) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 826] +Event: 7.525 Thread 0x0000000107808a00 Exception (0x00000005c6bbd418) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 826] +Event: 7.526 Thread 0x0000000107808a00 Exception (0x00000005c6bc3d28) +thrown [src/hotspot/share/interpreter/linkResolver.cpp, line 826] +Event: 7.704 Thread 0x000000011e51f600 Exception (0x00000005c6477ae0) +thrown [src/hotspot/share/prims/jni.cpp, line 516] + +VM Operations (20 events): +Event: 6.389 Executing VM operation: G1PauseRemark +Event: 6.394 Executing VM operation: G1PauseRemark done +Event: 6.401 Executing VM operation: G1PauseCleanup +Event: 6.401 Executing VM operation: G1PauseCleanup done +Event: 6.543 Executing VM operation: HandshakeAllThreads +Event: 6.543 Executing VM operation: HandshakeAllThreads done +Event: 6.555 Executing VM operation: HandshakeAllThreads +Event: 6.555 Executing VM operation: HandshakeAllThreads done +Event: 6.560 Executing VM operation: HandshakeAllThreads +Event: 6.560 Executing VM operation: HandshakeAllThreads done +Event: 7.076 Executing VM operation: HandshakeAllThreads +Event: 7.076 Executing VM operation: HandshakeAllThreads done +Event: 7.080 Executing VM operation: HandshakeAllThreads +Event: 7.080 Executing VM operation: HandshakeAllThreads done +Event: 7.096 Executing VM operation: HandshakeAllThreads +Event: 7.096 Executing VM operation: HandshakeAllThreads done +Event: 7.131 Executing VM operation: ICBufferFull +Event: 7.131 Executing VM operation: ICBufferFull done +Event: 7.466 Executing VM operation: ICBufferFull +Event: 7.466 Executing VM operation: ICBufferFull done + +Events (20 events): +Event: 7.703 loading class java/net/SocksSocketImpl$3 done +Event: 7.704 loading class sun/net/util/SocketExceptions +Event: 7.704 loading class sun/net/util/SocketExceptions done +Event: 7.704 Thread 0x000000011b77a400 Thread added: 0x000000011b77a400 +Event: 7.704 loading class java/lang/Throwable$WrappedPrintWriter +Event: 7.704 loading class java/lang/Throwable$WrappedPrintWriter done +Event: 7.704 Protecting memory [0x000000031ee8c000,0x000000031ee98000] with protection modes 0 +Event: 7.704 loading class com/intellij/rt/debugger/agent/CaptureStorage$9 +Event: 7.704 loading class com/intellij/rt/debugger/agent/CaptureStorage$Callable +Event: 7.704 loading class com/intellij/rt/debugger/agent/CaptureStorage$Callable done +Event: 7.704 loading class com/intellij/rt/debugger/agent/CaptureStorage$9 done +Event: 7.705 loading class jdk/internal/loader/BootLoader$PackageHelper$1 +Event: 7.705 loading class jdk/internal/loader/BootLoader$PackageHelper$1 done +Event: 7.705 loading class jdk/internal/loader/BootLoader$PackageHelper$2 +Event: 7.705 loading class jdk/internal/loader/BootLoader$PackageHelper$2 done +Event: 7.705 loading class java/util/jar/JarInputStream +Event: 7.705 loading class java/util/zip/ZipInputStream +Event: 7.705 loading class java/util/zip/ZipInputStream done +Event: 7.705 loading class java/util/jar/JarInputStream done +Event: 7.706 loading class com/intellij/rt/debugger/agent/CaptureStorage$StackData + + +Dynamic libraries: +0x0000000104638000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libjli.dylib +0x0000000196c18000 /usr/lib/libz.1.dylib +0x0000000196cce000 /usr/lib/libSystem.B.dylib +0x0000000196cc8000 /usr/lib/system/libcache.dylib +0x0000000196c83000 /usr/lib/system/libcommonCrypto.dylib +0x0000000196cae000 /usr/lib/system/libcompiler_rt.dylib +0x0000000196ca3000 /usr/lib/system/libcopyfile.dylib +0x0000000186bb6000 /usr/lib/system/libcorecrypto.dylib +0x0000000186cb6000 /usr/lib/system/libdispatch.dylib +0x0000000186a53000 /usr/lib/system/libdyld.dylib +0x0000000196cbe000 /usr/lib/system/libkeymgr.dylib +0x0000000196c66000 /usr/lib/system/libmacho.dylib +0x0000000195ef9000 /usr/lib/system/libquarantine.dylib +0x0000000196cbb000 /usr/lib/system/libremovefile.dylib +0x000000018d629000 /usr/lib/system/libsystem_asl.dylib +0x0000000186b3c000 /usr/lib/system/libsystem_blocks.dylib +0x0000000186d01000 /usr/lib/system/libsystem_c.dylib +0x0000000196cb2000 /usr/lib/system/libsystem_collections.dylib +0x0000000194899000 /usr/lib/system/libsystem_configuration.dylib +0x0000000193487000 /usr/lib/system/libsystem_containermanager.dylib +0x0000000196698000 /usr/lib/system/libsystem_coreservices.dylib +0x000000018ae50000 /usr/lib/system/libsystem_darwin.dylib +0x000000028c8a4000 /usr/lib/system/libsystem_darwindirectory.dylib +0x0000000196cbf000 /usr/lib/system/libsystem_dnssd.dylib +0x000000028c8a8000 /usr/lib/system/libsystem_eligibility.dylib +0x0000000186cfe000 /usr/lib/system/libsystem_featureflags.dylib +0x0000000186e83000 /usr/lib/system/libsystem_info.dylib +0x0000000196c27000 /usr/lib/system/libsystem_m.dylib +0x0000000186c65000 /usr/lib/system/libsystem_malloc.dylib +0x000000018d58c000 /usr/lib/system/libsystem_networkextension.dylib +0x000000018b2bb000 /usr/lib/system/libsystem_notify.dylib +0x000000019489e000 /usr/lib/system/libsystem_sandbox.dylib +0x000000028c8b3000 /usr/lib/system/libsystem_sanitizers.dylib +0x0000000196cb7000 /usr/lib/system/libsystem_secinit.dylib +0x0000000186e2f000 /usr/lib/system/libsystem_kernel.dylib +0x0000000186e7a000 /usr/lib/system/libsystem_platform.dylib +0x0000000186e6d000 /usr/lib/system/libsystem_pthread.dylib +0x000000018f1e2000 /usr/lib/system/libsystem_symptoms.dylib +0x0000000186b95000 /usr/lib/system/libsystem_trace.dylib +0x000000028c8bb000 /usr/lib/system/libsystem_trial.dylib +0x0000000196c91000 /usr/lib/system/libunwind.dylib +0x0000000186b40000 /usr/lib/system/libxpc.dylib +0x0000000186a00000 /usr/lib/libobjc.A.dylib +0x0000000186eb3000 /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation +0x000000019a5c3000 /usr/lib/swift/libswiftCore.dylib +0x0000000186e14000 /usr/lib/libc++abi.dylib +0x000000028ac91000 /usr/lib/libRosetta.dylib +0x0000000186d83000 /usr/lib/libc++.1.dylib +0x0000000188722000 /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation +0x00000001a41a3000 /usr/lib/swift/libswiftObjectiveC.dylib +0x000000028c10d000 /usr/lib/libswiftPrespecialized.dylib +0x0000000188391000 /System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration +0x0000000191703000 /System/Library/PrivateFrameworks/CoreAutoLayout.framework/Versions/A/CoreAutoLayout +0x0000000196cd0000 /usr/lib/libfakelink.dylib +0x0000000196f79000 /usr/lib/libcompression.dylib +0x000000018d1d6000 /System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork +0x0000000190b34000 /System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration +0x0000000196d23000 /usr/lib/libarchive.2.dylib +0x0000000190a39000 /usr/lib/libDiagnosticMessagesClient.dylib +0x000000018ab7a000 /usr/lib/libicucore.A.dylib +0x000000019174c000 /usr/lib/libxml2.2.dylib +0x000000019f452000 /System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices +0x00000001948ac000 /usr/lib/liblangid.dylib +0x000000018b1d2000 /System/Library/Frameworks/IOKit.framework/Versions/A/IOKit +0x000000019d0c4000 /System/Library/Frameworks/Combine.framework/Versions/A/Combine +0x000000023fff3000 /System/Library/PrivateFrameworks/CollectionsInternal.framework/Versions/A/CollectionsInternal +0x000000026c039000 /System/Library/PrivateFrameworks/ReflectionInternal.framework/Versions/A/ReflectionInternal +0x000000026cf8d000 /System/Library/PrivateFrameworks/RuntimeInternal.framework/Versions/A/RuntimeInternal +0x0000000196cd2000 /System/Library/PrivateFrameworks/SoftLinking.framework/Versions/A/SoftLinking +0x00000001b488c000 /usr/lib/swift/libswiftCoreFoundation.dylib +0x00000001b164f000 /usr/lib/swift/libswiftDarwin.dylib +0x00000001a11c9000 /usr/lib/swift/libswiftDispatch.dylib +0x00000001b48ed000 /usr/lib/swift/libswiftIOKit.dylib +0x000000028c550000 /usr/lib/swift/libswiftSystem.dylib +0x00000001b489f000 /usr/lib/swift/libswiftXPC.dylib +0x000000028c582000 /usr/lib/swift/libswift_Builtin_float.dylib +0x000000028c583000 /usr/lib/swift/libswift_Concurrency.dylib +0x000000028c60f000 /usr/lib/swift/libswift_DarwinFoundation1.dylib +0x000000028c6b3000 /usr/lib/swift/libswift_StringProcessing.dylib +0x00000001a41a7000 /usr/lib/swift/libswiftos.dylib +0x000000018b152000 /System/Library/PrivateFrameworks/CoreServicesInternal.framework/Versions/A/CoreServicesInternal +0x0000000196c9b000 /usr/lib/liboah.dylib +0x000000018a75a000 /System/Library/Frameworks/Security.framework/Versions/A/Security +0x00000001a35d7000 /System/Library/PrivateFrameworks/DiskImages.framework/Versions/A/DiskImages +0x00000001b10f3000 /System/Library/Frameworks/NetFS.framework/Versions/A/NetFS +0x00000001916c8000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/FSEvents +0x000000018ae5a000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/CarbonCore +0x0000000190aa8000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Metadata +0x000000019669f000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/OSServices.framework/Versions/A/OSServices +0x0000000196e1b000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SearchKit.framework/Versions/A/SearchKit +0x000000018f15c000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/AE.framework/Versions/A/AE +0x0000000187412000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/LaunchServices +0x0000000198224000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/DictionaryServices.framework/Versions/A/DictionaryServices +0x00000001916d5000 /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SharedFileList.framework/Versions/A/SharedFileList +0x0000000196eae000 /usr/lib/libapple_nghttp2.dylib +0x000000018ed78000 /usr/lib/libsqlite3.dylib +0x000000018ef61000 /System/Library/Frameworks/Accounts.framework/Versions/A/Accounts +0x00000001a3819000 /System/Library/PrivateFrameworks/AppSupport.framework/Versions/A/AppSupport +0x00000001b363e000 /System/Library/Frameworks/AVFoundation.framework/Versions/A/AVFoundation +0x0000000190a09000 /System/Library/PrivateFrameworks/CoreAnalytics.framework/Versions/A/CoreAnalytics +0x000000018dc1c000 /System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics +0x000000019b2fe000 /System/Library/Frameworks/GSS.framework/Versions/A/GSS +0x00000001996e6000 /System/Library/PrivateFrameworks/InternationalSupport.framework/Versions/A/InternationalSupport +0x000000018f0f0000 /System/Library/PrivateFrameworks/RunningBoardServices.framework/Versions/A/RunningBoardServices +0x00000001a412b000 /System/Library/PrivateFrameworks/StreamingZip.framework/Versions/A/StreamingZip +0x000000018d5a7000 /usr/lib/libenergytrace.dylib +0x000000018f1eb000 /System/Library/Frameworks/Network.framework/Versions/A/Network +0x0000000195f21000 /usr/lib/libbsm.0.dylib +0x0000000196c6a000 /usr/lib/system/libkxld.dylib +0x000000023b5c5000 /System/Library/PrivateFrameworks/AppleKeyStore.framework/Versions/A/AppleKeyStore +0x000000028a993000 /usr/lib/libCoreEntitlements.dylib +0x0000000260705000 /System/Library/PrivateFrameworks/MessageSecurity.framework/Versions/A/MessageSecurity +0x000000018ed5c000 /System/Library/PrivateFrameworks/ProtocolBuffer.framework/Versions/A/ProtocolBuffer +0x00000001a05d8000 /System/Library/PrivateFrameworks/SymptomDiagnosticReporter.framework/Versions/A/SymptomDiagnosticReporter +0x0000000198469000 /System/Library/PrivateFrameworks/CrashReporterSupport.framework/Versions/A/CrashReporterSupport +0x000000018d5a9000 /usr/lib/libMobileGestalt.dylib +0x000000019667f000 /System/Library/PrivateFrameworks/AppleFSCompression.framework/Versions/A/AppleFSCompression +0x0000000195f09000 /usr/lib/libcoretls.dylib +0x000000019829a000 /usr/lib/libcoretls_cfhelpers.dylib +0x0000000196f73000 /usr/lib/libpam.2.dylib +0x0000000198310000 /usr/lib/libxar.1.dylib +0x000000019829c000 /System/Library/PrivateFrameworks/APFS.framework/Versions/A/APFS +0x0000000278713000 /System/Library/PrivateFrameworks/SwiftASN1Internal.framework/Versions/A/SwiftASN1Internal +0x000000019831f000 /usr/lib/libutil.dylib +0x00000001948a7000 /System/Library/PrivateFrameworks/AppleSystemInfo.framework/Versions/A/AppleSystemInfo +0x0000000195bd0000 /System/Library/PrivateFrameworks/IOMobileFramebuffer.framework/Versions/A/IOMobileFramebuffer +0x00000001934c0000 /System/Library/Frameworks/IOSurface.framework/Versions/A/IOSurface +0x00000001a2f37000 /System/Library/PrivateFrameworks/CoreWiFi.framework/Versions/A/CoreWiFi +0x00000001b474c000 /System/Library/PrivateFrameworks/LoggingSupport.framework/Versions/A/LoggingSupport +0x000000019b361000 /System/Library/PrivateFrameworks/MobileAsset.framework/Versions/A/MobileAsset +0x00000001a05e8000 /System/Library/PrivateFrameworks/PowerLog.framework/Versions/A/PowerLog +0x00000001a1aaa000 /System/Library/PrivateFrameworks/Rapport.framework/Versions/A/Rapport +0x000000023416e000 /System/Library/Frameworks/SwiftData.framework/Versions/A/SwiftData +0x000000018cc0a000 /System/Library/Frameworks/UniformTypeIdentifiers.framework/Versions/A/UniformTypeIdentifiers +0x00000001918f5000 /System/Library/PrivateFrameworks/UserManagement.framework/Versions/A/UserManagement +0x000000018d0fd000 /usr/lib/libboringssl.dylib +0x000000018f1d0000 /usr/lib/libdns_services.dylib +0x00000001b3772000 /usr/lib/libquic.dylib +0x000000019a554000 /usr/lib/libusrtcp.dylib +0x000000023c47f000 /System/Library/PrivateFrameworks/AtomicsInternal.framework/Versions/A/AtomicsInternal +0x00000001dab32000 /System/Library/PrivateFrameworks/InternalSwiftProtobuf.framework/Versions/A/InternalSwiftProtobuf +0x000000028c3d7000 /usr/lib/swift/libswiftDistributed.dylib +0x000000028c400000 /usr/lib/swift/libswiftObservation.dylib +0x000000028c53c000 /usr/lib/swift/libswiftSynchronization.dylib +0x00000001948a5000 /System/Library/PrivateFrameworks/AggregateDictionary.framework/Versions/A/AggregateDictionary +0x000000023ccaf000 /System/Library/PrivateFrameworks/BiomeLibrary.framework/Versions/A/BiomeLibrary +0x00000001c38d5000 /System/Library/PrivateFrameworks/BiomeStreams.framework/Versions/A/BiomeStreams +0x00000001bf924000 /System/Library/PrivateFrameworks/BiomeFoundation.framework/Versions/A/BiomeFoundation +0x00000001c9b82000 /System/Library/PrivateFrameworks/BiomePubSub.framework/Versions/A/BiomePubSub +0x000000018e96e000 /System/Library/Frameworks/CoreData.framework/Versions/A/CoreData +0x00000001a510e000 /System/Library/PrivateFrameworks/ProactiveSupport.framework/Versions/A/ProactiveSupport +0x00000002361cc000 /System/Library/Frameworks/_LocationEssentials.framework/Versions/A/_LocationEssentials +0x000000019827b000 /usr/lib/liblzma.5.dylib +0x000000019f6d1000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate +0x0000000195e02000 /System/Library/PrivateFrameworks/MobileKeyBag.framework/Versions/A/MobileKeyBag +0x00000001a3b2d000 /System/Library/PrivateFrameworks/InternationalTextSearch.framework/Versions/A/InternationalTextSearch +0x00000001bbdf7000 /System/Library/PrivateFrameworks/SoftwareUpdateCoreSupport.framework/Versions/A/SoftwareUpdateCoreSupport +0x00000001c4374000 /System/Library/PrivateFrameworks/SoftwareUpdateCoreConnect.framework/Versions/A/SoftwareUpdateCoreConnect +0x00000001a36d1000 /System/Library/PrivateFrameworks/RemoteServiceDiscovery.framework/Versions/A/RemoteServiceDiscovery +0x00000001bb9ce000 /System/Library/PrivateFrameworks/MSUDataAccessor.framework/Versions/A/MSUDataAccessor +0x00000001b691f000 /usr/lib/libbootpolicy.dylib +0x00000001a36e8000 /System/Library/PrivateFrameworks/RemoteXPC.framework/Versions/A/RemoteXPC +0x00000001c37a9000 /usr/lib/libFDR.dylib +0x00000001c9784000 /usr/lib/libamsupport.dylib +0x000000028ac89000 /usr/lib/libReverseProxyDevice.dylib +0x000000023ae33000 /System/Library/PrivateFrameworks/AppleDeviceQuerySupport.framework/Versions/A/AppleDeviceQuerySupport +0x00000001cc94e000 /usr/lib/libpartition2_dynamic.dylib +0x0000000196e8a000 /System/Library/PrivateFrameworks/AppleSauce.framework/Versions/A/AppleSauce +0x000000028a83e000 /usr/lib/libAppleArchive.dylib +0x000000019668b000 /usr/lib/libbz2.1.0.dylib +0x0000000190b3e000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vImage.framework/Versions/A/vImage +0x000000019f42d000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/vecLib +0x0000000198356000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvMisc.dylib +0x0000000187916000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib +0x00000001a3b2c000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices +0x0000000191833000 /System/Library/Frameworks/CoreVideo.framework/Versions/A/CoreVideo +0x000000018e372000 /System/Library/Frameworks/ColorSync.framework/Versions/A/ColorSync +0x0000000189d9a000 /System/Library/Frameworks/CoreText.framework/Versions/A/CoreText +0x0000000193fb3000 /System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO +0x000000019af0e000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/ATS +0x000000018e51a000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/HIServices +0x00000001995dc000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/PrintCore +0x000000019b2c7000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/QD.framework/Versions/A/QD +0x000000019b2c2000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ColorSyncLegacy.framework/Versions/A/ColorSyncLegacy +0x000000019aee0000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/SpeechSynthesis.framework/Versions/A/SpeechSynthesis +0x000000018d665000 /System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLight +0x000000019398e000 /System/Library/PrivateFrameworks/FontServices.framework/libFontParser.dylib +0x000000018effe000 /System/Library/PrivateFrameworks/BaseBoard.framework/Versions/A/BaseBoard +0x00000001a152a000 /System/Library/PrivateFrameworks/BoardServices.framework/Versions/A/BoardServices +0x00000001a33f9000 /System/Library/PrivateFrameworks/BackBoardServices.framework/Versions/A/BackBoardServices +0x000000023cbb4000 /System/Library/PrivateFrameworks/BackBoardHIDEventFoundation.framework/Versions/A/BackBoardHIDEventFoundation +0x00000001898b6000 /System/Library/Frameworks/CoreDisplay.framework/Versions/A/CoreDisplay +0x0000000198f35000 /System/Library/Frameworks/VideoToolbox.framework/Versions/A/VideoToolbox +0x0000000196f71000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/MetalPerformanceShaders +0x000000026af43000 /System/Library/PrivateFrameworks/ProDisplayLibrary.framework/Versions/A/ProDisplayLibrary +0x00000001a722e000 /System/Library/PrivateFrameworks/IOSurfaceAccelerator.framework/Versions/A/IOSurfaceAccelerator +0x00000001934e8000 /System/Library/Frameworks/Metal.framework/Versions/A/Metal +0x00000001934dd000 /System/Library/PrivateFrameworks/IOAccelerator.framework/Versions/A/IOAccelerator +0x00000001937ec000 /System/Library/Frameworks/CoreMedia.framework/Versions/A/CoreMedia +0x000000018d641000 /System/Library/PrivateFrameworks/TCC.framework/Versions/A/TCC +0x0000000198eed000 /System/Library/PrivateFrameworks/WatchdogClient.framework/Versions/A/WatchdogClient +0x0000000190f63000 /System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore +0x0000000198eef000 /System/Library/PrivateFrameworks/MultitouchSupport.framework/Versions/A/MultitouchSupport +0x00000001cc730000 /usr/lib/swift/libswiftAccelerate.dylib +0x00000001b486c000 /usr/lib/swift/libswiftCoreAudio.dylib +0x00000001d08c1000 /usr/lib/swift/libswiftCoreMedia.dylib +0x00000001c2862000 /usr/lib/swift/libswiftMetal.dylib +0x00000001d2074000 /usr/lib/swift/libswiftOSLog.dylib +0x00000001c7c88000 /usr/lib/swift/libswiftQuartzCore.dylib +0x00000001cc720000 /usr/lib/swift/libswiftUniformTypeIdentifiers.dylib +0x000000028c56a000 /usr/lib/swift/libswiftVideoToolbox.dylib +0x00000001b83e6000 /usr/lib/swift/libswiftsimd.dylib +0x00000001c9be3000 /System/Library/PrivateFrameworks/BiomeStorage.framework/Versions/A/BiomeStorage +0x00000002593f4000 /System/Library/PrivateFrameworks/IntelligencePlatformLibrary.framework/Versions/A/IntelligencePlatformLibrary +0x0000000269d07000 /System/Library/PrivateFrameworks/PoirotSchematizer.framework/Versions/A/PoirotSchematizer +0x000000023d56a000 /System/Library/PrivateFrameworks/BiomeSync.framework/Versions/A/BiomeSync +0x000000023cc95000 /System/Library/PrivateFrameworks/BiomeDSL.framework/Versions/A/BiomeDSL +0x00000001e2d3d000 /System/Library/PrivateFrameworks/FeatureFlags.framework/Versions/A/FeatureFlags +0x0000000269d75000 /System/Library/PrivateFrameworks/PoirotUDFs.framework/Versions/A/PoirotUDFs +0x000000028c612000 /usr/lib/swift/libswift_DarwinFoundation2.dylib +0x000000028c613000 /usr/lib/swift/libswift_DarwinFoundation3.dylib +0x00000001a1a9f000 /System/Library/PrivateFrameworks/CoreTime.framework/Versions/A/CoreTime +0x0000000196d08000 /usr/lib/libiconv.2.dylib +0x0000000196c65000 /usr/lib/libcharset.1.dylib +0x0000000269cca000 /System/Library/PrivateFrameworks/PoirotSQLite.framework/Versions/A/PoirotSQLite +0x000000028c614000 /usr/lib/swift/libswift_RegexParser.dylib +0x000000023eba0000 /System/Library/PrivateFrameworks/CascadeSets.framework/Versions/A/CascadeSets +0x000000019b4d8000 /System/Library/PrivateFrameworks/CorePhoneNumbers.framework/Versions/A/CorePhoneNumbers +0x0000000198ce7000 /System/Library/PrivateFrameworks/AppleJPEG.framework/Versions/A/AppleJPEG +0x00000001986c0000 /usr/lib/libexpat.1.dylib +0x00000001994b2000 /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libPng.dylib +0x00000001994dd000 /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libTIFF.dylib +0x00000001995c5000 /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libGIF.dylib +0x0000000198d2c000 /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJP2.dylib +0x00000001983d0000 /usr/lib/libate.dylib +0x000000019956c000 /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJPEG.dylib +0x0000000199563000 /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libRadiance.dylib +0x000000024f044000 /System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/32023/Libraries/libllvm-flatbuffers.dylib +0x0000000249aa3000 /System/Library/PrivateFrameworks/FramePacing.framework/Versions/A/FramePacing +0x000000022cbb3000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreFSCache.dylib +0x000000024abd1000 /System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/32023/Libraries/libGPUCompilerUtils.dylib +0x00000001a15f7000 /System/Library/PrivateFrameworks/GraphicsServices.framework/Versions/A/GraphicsServices +0x000000022cbc1000 /System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL +0x000000022cc12000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLU.dylib +0x000000022cbd5000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGFXShared.dylib +0x000000022cda2000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib +0x000000022cbde000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLImage.dylib +0x000000022cbd2000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCVMSPluginSupport.dylib +0x000000022cbbb000 /System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreVMClient.dylib +0x000000019955e000 /System/Library/PrivateFrameworks/GPUWrangler.framework/Versions/A/GPUWrangler +0x000000019953e000 /System/Library/PrivateFrameworks/IOPresentment.framework/Versions/A/IOPresentment +0x0000000199566000 /System/Library/PrivateFrameworks/DSExternalDisplay.framework/Versions/A/DSExternalDisplay +0x00000002805c9000 /System/Library/PrivateFrameworks/VideoToolboxParavirtualizationSupport.framework/Versions/A/VideoToolboxParavirtualizationSupport +0x0000000198677000 /System/Library/PrivateFrameworks/AppleVA.framework/Versions/A/AppleVA +0x000000022ec3e000 /System/Library/Frameworks/ExtensionFoundation.framework/Versions/A/ExtensionFoundation +0x00000001995cb000 /System/Library/PrivateFrameworks/CMCaptureCore.framework/Versions/A/CMCaptureCore +0x0000000198951000 /usr/lib/libspindump.dylib +0x0000000189fc4000 /System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio +0x0000000198944000 /System/Library/PrivateFrameworks/AppServerSupport.framework/Versions/A/AppServerSupport +0x000000019b2d0000 /System/Library/PrivateFrameworks/perfdata.framework/Versions/A/perfdata +0x00000001899d7000 /System/Library/PrivateFrameworks/AudioToolboxCore.framework/Versions/A/AudioToolboxCore +0x00000001937c2000 /System/Library/PrivateFrameworks/caulk.framework/Versions/A/caulk +0x000000019aec6000 /usr/lib/libAudioStatistics.dylib +0x00000001b3867000 /System/Library/PrivateFrameworks/SystemPolicy.framework/Versions/A/SystemPolicy +0x000000019b174000 /usr/lib/libSMC.dylib +0x00000001bb1dd000 /usr/lib/swift/libswiftCoreMIDI.dylib +0x00000001a651d000 /System/Library/Frameworks/CoreMIDI.framework/Versions/A/CoreMIDI +0x000000019948c000 /usr/lib/libAudioToolboxUtility.dylib +0x000000019b2de000 /usr/lib/libperfcheck.dylib +0x000000023c54e000 /System/Library/PrivateFrameworks/AudioAnalytics.framework/Versions/A/AudioAnalytics +0x00000001da81e000 /System/Library/Frameworks/OSLog.framework/Versions/A/OSLog +0x0000000265777000 /System/Library/PrivateFrameworks/OSEligibility.framework/Versions/A/OSEligibility +0x0000000198746000 /System/Library/PrivateFrameworks/IconServices.framework/Versions/A/IconServices +0x0000000230025000 /System/Library/Frameworks/LightweightCodeRequirements.framework/Versions/A/LightweightCodeRequirements +0x00000001985c0000 /System/Library/PrivateFrameworks/PlugInKit.framework/Versions/A/PlugInKit +0x0000000195e1a000 /System/Library/PrivateFrameworks/AssertionServices.framework/Versions/A/AssertionServices +0x00000001986e5000 /System/Library/PrivateFrameworks/IconFoundation.framework/Versions/A/IconFoundation +0x0000000255f80000 /System/Library/PrivateFrameworks/IconRendering.framework/Versions/A/IconRendering +0x00000001913ca000 /System/Library/PrivateFrameworks/CoreUI.framework/Versions/A/CoreUI +0x00000001942f4000 /System/Library/Frameworks/CoreImage.framework/Versions/A/CoreImage +0x000000026d172000 /System/Library/PrivateFrameworks/SFSymbols.framework/Versions/A/SFSymbols +0x000000022eaf4000 /System/Library/Frameworks/DeveloperToolsSupport.framework/Versions/A/DeveloperToolsSupport +0x00000001ab7ca000 /System/Library/PrivateFrameworks/RenderBox.framework/Versions/A/RenderBox +0x0000000193f75000 /System/Library/PrivateFrameworks/CoreSVG.framework/Versions/A/CoreSVG +0x000000019964f000 /System/Library/PrivateFrameworks/TextureIO.framework/Versions/A/TextureIO +0x00000001b48ec000 /usr/lib/swift/libswiftCoreImage.dylib +0x00000001988f4000 /System/Library/PrivateFrameworks/GraphVisualizer.framework/Versions/A/GraphVisualizer +0x00000002499ae000 /System/Library/PrivateFrameworks/FontServices.framework/Versions/A/FontServices +0x0000000198904000 /System/Library/PrivateFrameworks/OTSVG.framework/Versions/A/OTSVG +0x0000000191379000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Resources/libFontRegistry.dylib +0x000000028b763000 /usr/lib/libhvf.dylib +0x0000000266404000 /System/Library/PrivateFrameworks/ParsingInternal.framework/Versions/A/ParsingInternal +0x00000002499b2000 /System/Library/PrivateFrameworks/FontServices.framework/libXTFontStaticRegistryData.dylib +0x00000001947df000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSCore.framework/Versions/A/MPSCore +0x00000001965ea000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSImage.framework/Versions/A/MPSImage +0x0000000195fa9000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSNeuralNetwork.framework/Versions/A/MPSNeuralNetwork +0x00000001963e8000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSMatrix.framework/Versions/A/MPSMatrix +0x0000000196200000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSRayIntersector.framework/Versions/A/MPSRayIntersector +0x000000019641a000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSNDArray.framework/Versions/A/MPSNDArray +0x0000000230eaa000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSFunctions.framework/Versions/A/MPSFunctions +0x0000000230e8b000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSBenchmarkLoop.framework/Versions/A/MPSBenchmarkLoop +0x0000000230ebe000 /System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSHost.framework/Versions/A/MPSHost +0x000000018772d000 /System/Library/PrivateFrameworks/MetalTools.framework/Versions/A/MetalTools +0x00000001b9b0d000 /System/Library/PrivateFrameworks/IOAccelMemoryInfo.framework/Versions/A/IOAccelMemoryInfo +0x00000001c807b000 /System/Library/PrivateFrameworks/kperf.framework/Versions/A/kperf +0x00000001b4868000 /System/Library/PrivateFrameworks/GPURawCounter.framework/Versions/A/GPURawCounter +0x00000001a5299000 /System/Library/PrivateFrameworks/ASEProcessing.framework/Versions/A/ASEProcessing +0x00000001d618a000 /System/Library/PrivateFrameworks/Symbolication.framework/Versions/A/Symbolication +0x00000002698b5000 /System/Library/PrivateFrameworks/PhotosensitivityProcessing.framework/Versions/A/PhotosensitivityProcessing +0x000000026d1f8000 /System/Library/PrivateFrameworks/SILManager.framework/Versions/A/SILManager +0x00000001a1943000 /System/Library/PrivateFrameworks/CoreSymbolication.framework/Versions/A/CoreSymbolication +0x00000001b47db000 /System/Library/PrivateFrameworks/MallocStackLogging.framework/Versions/A/MallocStackLogging +0x00000001a1921000 /System/Library/PrivateFrameworks/DebugSymbols.framework/Versions/A/DebugSymbols +0x00000001c67cc000 /System/Library/PrivateFrameworks/OSAnalytics.framework/Versions/A/OSAnalytics +0x0000000247709000 /System/Library/PrivateFrameworks/DeviceRecovery.framework/Versions/A/DeviceRecovery +0x000000027be51000 /System/Library/PrivateFrameworks/Tightbeam.framework/Versions/A/Tightbeam +0x00000001c2870000 /usr/lib/swift/libswiftCompression.dylib +0x00000001ccebd000 /System/Library/PrivateFrameworks/AFKUser.framework/Versions/A/AFKUser +0x0000000199597000 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATSUI.framework/Versions/A/ATSUI +0x000000019ac6f000 /System/Library/Frameworks/AudioToolbox.framework/Versions/A/AudioToolbox +0x0000000196a5c000 /System/Library/Frameworks/UserNotifications.framework/Versions/A/UserNotifications +0x00000001ba2a4000 /System/Library/PrivateFrameworks/SiriInstrumentation.framework/Versions/A/SiriInstrumentation +0x000000026f688000 /System/Library/PrivateFrameworks/SiriAnalytics.framework/Versions/A/SiriAnalytics +0x00000001b7a03000 /System/Library/PrivateFrameworks/FeedbackLogger.framework/Versions/A/FeedbackLogger +0x00000001d230d000 /usr/lib/swift/libswiftAVFoundation.dylib +0x000000027e67a000 /System/Library/PrivateFrameworks/UnifiedAssetFramework.framework/Versions/A/UnifiedAssetFramework +0x000000019ae45000 /System/Library/PrivateFrameworks/AudioSession.framework/Versions/A/AudioSession +0x0000000198805000 /System/Library/PrivateFrameworks/MediaExperience.framework/Versions/A/MediaExperience +0x000000019ac19000 /System/Library/PrivateFrameworks/AudioSession.framework/libSessionUtility.dylib +0x00000001a04cd000 /System/Library/Frameworks/CoreBluetooth.framework/Versions/A/CoreBluetooth +0x0000000195c8d000 /System/Library/PrivateFrameworks/CoreUtils.framework/Versions/A/CoreUtils +0x00000001ac4fe000 /System/Library/PrivateFrameworks/HID.framework/Versions/A/HID +0x00000002465fa000 /System/Library/PrivateFrameworks/CoreUtilsExtras.framework/Versions/A/CoreUtilsExtras +0x0000000255ed5000 /System/Library/PrivateFrameworks/IO80211.framework/Versions/A/IO80211 +0x000000019ce10000 /System/Library/Frameworks/IOBluetooth.framework/Versions/A/IOBluetooth +0x000000028c41f000 /usr/lib/swift/libswiftRegexBuilder.dylib +0x0000000198460000 /usr/lib/libIOReport.dylib +0x00000001e2dc2000 /System/Library/PrivateFrameworks/WiFiPeerToPeer.framework/Versions/A/WiFiPeerToPeer +0x0000000195e29000 /System/Library/Frameworks/SecurityFoundation.framework/Versions/A/SecurityFoundation +0x000000023ec54000 /System/Library/PrivateFrameworks/Centauri.framework/Versions/A/Centauri +0x0000000188111000 /System/Library/PrivateFrameworks/Lexicon.framework/Versions/A/Lexicon +0x000000028bbb3000 /usr/lib/libmrc.dylib +0x0000000255f40000 /System/Library/PrivateFrameworks/IPConfiguration.framework/Versions/A/IPConfiguration +0x00000001d6961000 /System/Library/PrivateFrameworks/Netrb.framework/Versions/A/Netrb +0x00000001a1450000 /System/Library/PrivateFrameworks/FrontBoardServices.framework/Versions/A/FrontBoardServices +0x0000000195f92000 /usr/lib/libgermantok.dylib +0x00000001949ce000 /System/Library/PrivateFrameworks/LinguisticData.framework/Versions/A/LinguisticData +0x00000001a06d6000 /System/Library/PrivateFrameworks/MediaKit.framework/Versions/A/MediaKit +0x00000001a0624000 /System/Library/Frameworks/DiscRecording.framework/Versions/A/DiscRecording +0x00000001986db000 /usr/lib/libheimdal-asn1.dylib +0x00000001a4101000 /System/Library/Frameworks/AudioUnit.framework/Versions/A/AudioUnit +0x0000000191690000 /System/Library/Frameworks/OpenDirectory.framework/Versions/A/OpenDirectory +0x000000019169e000 /System/Library/Frameworks/OpenDirectory.framework/Versions/A/Frameworks/CFOpenDirectory.framework/Versions/A/CFOpenDirectory +0x000000019d1b8000 /System/Library/PrivateFrameworks/GeoServices.framework/Versions/A/GeoServices +0x000000019abdf000 /System/Library/PrivateFrameworks/LocationSupport.framework/Versions/A/LocationSupport +0x0000000252bfe000 /System/Library/PrivateFrameworks/GeoServicesCore.framework/Versions/A/GeoServicesCore +0x00000001ad402000 /System/Library/PrivateFrameworks/PhoneNumbers.framework/Versions/A/PhoneNumbers +0x000000025b386000 /System/Library/PrivateFrameworks/LocationLogEncryption.framework/Versions/A/LocationLogEncryption +0x000000022d12e000 /System/Library/Frameworks/AVFAudio.framework/Versions/A/AVFAudio +0x000000022d272000 /System/Library/Frameworks/AVRouting.framework/Versions/A/AVRouting +0x00000001ad51a000 /usr/lib/libAccessibility.dylib +0x0000000259d70000 /System/Library/PrivateFrameworks/IsolatedCoreAudioClient.framework/Versions/A/IsolatedCoreAudioClient +0x00000002423ee000 /System/Library/PrivateFrameworks/CoreAudioOrchestration.framework/Versions/A/CoreAudioOrchestration +0x0000000199ab3000 /System/Library/Frameworks/MediaToolbox.framework/Versions/A/MediaToolbox +0x00000001a07fc000 /System/Library/PrivateFrameworks/CoreAVCHD.framework/Versions/A/CoreAVCHD +0x000000019f720000 /System/Library/Frameworks/MediaAccessibility.framework/Versions/A/MediaAccessibility +0x00000001a07f8000 /System/Library/PrivateFrameworks/Mangrove.framework/Versions/A/Mangrove +0x000000023e290000 /System/Library/PrivateFrameworks/CMPhoto.framework/Versions/A/CMPhoto +0x00000001a0fc5000 /System/Library/Frameworks/CoreTelephony.framework/Versions/A/CoreTelephony +0x00000001a07eb000 /System/Library/PrivateFrameworks/CoreAUC.framework/Versions/A/CoreAUC +0x000000023b3f6000 /System/Library/PrivateFrameworks/AppleJPEGXL.framework/Versions/A/AppleJPEGXL +0x000000019b4e8000 /usr/lib/libTelephonyUtilDynamic.dylib +0x00000001dd93f000 /System/Library/Frameworks/CryptoKit.framework/Versions/A/CryptoKit +0x00000001a40fc000 /System/Library/PrivateFrameworks/CryptoKitCBridging.framework/Versions/A/CryptoKitCBridging +0x00000001a1609000 /System/Library/Frameworks/CryptoTokenKit.framework/Versions/A/CryptoTokenKit +0x0000000247179000 /System/Library/PrivateFrameworks/Dendrite.framework/Versions/A/Dendrite +0x00000001b440c000 /System/Library/Frameworks/NaturalLanguage.framework/Versions/A/NaturalLanguage +0x0000000252901000 /System/Library/PrivateFrameworks/GenerativeModels.framework/Versions/A/GenerativeModels +0x00000001e2d49000 /usr/lib/swift/libswiftNaturalLanguage.dylib +0x000000023bf6d000 /System/Library/PrivateFrameworks/AppleMobileFileIntegrity.framework/Versions/A/AppleMobileFileIntegrity +0x000000028ad01000 /usr/lib/libTLE.dylib +0x00000001b480d000 /usr/lib/libmis.dylib +0x00000001ec491000 /System/Library/PrivateFrameworks/ConfigProfileHelper.framework/Versions/A/ConfigProfileHelper +0x00000001a428b000 /System/Library/PrivateFrameworks/Espresso.framework/Versions/A/Espresso +0x0000000191e20000 /System/Library/Frameworks/CoreML.framework/Versions/A/CoreML +0x00000001e0bf5000 /usr/lib/libedit.3.dylib +0x0000000229f3c000 /System/Library/PrivateFrameworks/ANECompiler.framework/Versions/A/ANECompiler +0x00000001a6361000 /System/Library/PrivateFrameworks/AppleNeuralEngine.framework/Versions/A/AppleNeuralEngine +0x000000025b4a4000 /System/Library/PrivateFrameworks/MIL.framework/Versions/A/MIL +0x0000000230ec4000 /System/Library/Frameworks/MetalPerformanceShadersGraph.framework/Versions/A/MetalPerformanceShadersGraph +0x000000025bc13000 /System/Library/PrivateFrameworks/MLCompilerServices.framework/Versions/A/MLCompilerServices +0x00000001a50dd000 /System/Library/PrivateFrameworks/ANEServices.framework/Versions/A/ANEServices +0x00000001b9ad0000 /usr/lib/libncurses.5.4.dylib +0x000000018b2ce000 /usr/lib/libsandbox.1.dylib +0x0000000198601000 /usr/lib/libMatch.1.dylib +0x00000002654f9000 /System/Library/PrivateFrameworks/ODIE.framework/Versions/A/ODIE +0x000000025e8a0000 /System/Library/PrivateFrameworks/MLModelAsset.framework/Versions/A/MLModelAsset +0x000000025bbb9000 /System/Library/PrivateFrameworks/MLCompilerRuntime.framework/Versions/A/MLCompilerRuntime +0x0000000196255000 /System/Library/Frameworks/MLCompute.framework/Versions/A/MLCompute +0x000000025bb3b000 /System/Library/PrivateFrameworks/MLAssetIO.framework/Versions/A/MLAssetIO +0x000000028c3f1000 /usr/lib/swift/libswiftMLCompute.dylib +0x00000001a11e0000 /System/Library/PrivateFrameworks/AVFCore.framework/Versions/A/AVFCore +0x00000001aae1b000 /System/Library/PrivateFrameworks/AVFCapture.framework/Versions/A/AVFCapture +0x000000023e087000 /System/Library/PrivateFrameworks/CMImaging.framework/Versions/A/CMImaging +0x00000001ab05d000 /System/Library/PrivateFrameworks/Quagga.framework/Versions/A/Quagga +0x00000001ab18e000 /System/Library/PrivateFrameworks/CMCapture.framework/Versions/A/CMCapture +0x000000019b071000 /System/Library/Frameworks/CoreMediaIO.framework/Versions/A/CoreMediaIO +0x000000023dfc2000 /System/Library/PrivateFrameworks/CMCaptureDevice.framework/Versions/A/CMCaptureDevice +0x0000000198a3d000 /System/Library/PrivateFrameworks/CoreBrightness.framework/Versions/A/CoreBrightness +0x000000023f14b000 /System/Library/PrivateFrameworks/CinematicFraming.framework/Versions/A/CinematicFraming +0x00000002619dd000 /System/Library/PrivateFrameworks/ModelManagerServices.framework/Versions/A/ModelManagerServices +0x00000001cf375000 /System/Library/PrivateFrameworks/CPMS.framework/Versions/A/CPMS +0x0000000279581000 /System/Library/PrivateFrameworks/SystemStatus.framework/Versions/A/SystemStatus +0x00000001b323c000 /System/Library/Frameworks/CoreMotion.framework/Versions/A/CoreMotion +0x00000001c3854000 /System/Library/PrivateFrameworks/TimeSync.framework/Versions/A/TimeSync +0x0000000247b41000 /System/Library/PrivateFrameworks/DistributedSensing.framework/Versions/A/DistributedSensing +0x00000001bec33000 /System/Library/PrivateFrameworks/MobileBluetooth.framework/Versions/A/MobileBluetooth +0x00000001c5518000 /System/Library/PrivateFrameworks/IOKitten.framework/Versions/A/IOKitten +0x000000023b270000 /System/Library/PrivateFrameworks/AppleIntelligenceReporting.framework/Versions/A/AppleIntelligenceReporting +0x0000000195b9c000 /System/Library/PrivateFrameworks/CoreEmoji.framework/Versions/A/CoreEmoji +0x0000000188425000 /usr/lib/libCRFSuite.dylib +0x0000000189706000 /System/Library/PrivateFrameworks/LanguageModeling.framework/Versions/A/LanguageModeling +0x00000001948ae000 /System/Library/PrivateFrameworks/CoreNLP.framework/Versions/A/CoreNLP +0x000000018e683000 /System/Library/PrivateFrameworks/Montreal.framework/Versions/A/Montreal +0x0000000196d10000 /usr/lib/libcmph.dylib +0x0000000195f33000 /usr/lib/libmecab.dylib +0x0000000196e81000 /usr/lib/libThaiTokenizer.dylib +0x00000002529e3000 /System/Library/PrivateFrameworks/GenerativeModelsFoundation.framework/Versions/A/GenerativeModelsFoundation +0x000000027c356000 /System/Library/PrivateFrameworks/TokenGeneration.framework/Versions/A/TokenGeneration +0x00000002527b7000 /System/Library/PrivateFrameworks/GenerativeFunctions.framework/Versions/A/GenerativeFunctions +0x0000000252805000 /System/Library/PrivateFrameworks/GenerativeFunctionsFoundation.framework/Versions/A/GenerativeFunctionsFoundation +0x000000026169e000 /System/Library/PrivateFrameworks/ModelCatalog.framework/Versions/A/ModelCatalog +0x000000026e8a2000 /System/Library/PrivateFrameworks/SensitiveContentAnalysisML.framework/Versions/A/SensitiveContentAnalysisML +0x000000025289b000 /System/Library/PrivateFrameworks/GenerativeFunctionsInstrumentation.framework/Versions/A/GenerativeFunctionsInstrumentation +0x000000026b6cb000 /System/Library/PrivateFrameworks/PromptKit.framework/Versions/A/PromptKit +0x000000026b0e1000 /System/Library/PrivateFrameworks/ProactiveDaemonSupport.framework/Versions/A/ProactiveDaemonSupport +0x000000027c58e000 /System/Library/PrivateFrameworks/TokenGenerationCore.framework/Versions/A/TokenGenerationCore +0x00000001b52db000 /System/Library/PrivateFrameworks/Trial.framework/Versions/A/Trial +0x00000001b525c000 /System/Library/PrivateFrameworks/TrialProto.framework/Versions/A/TrialProto +0x000000023ae9a000 /System/Library/PrivateFrameworks/AppleFlatBuffers.framework/Versions/A/AppleFlatBuffers +0x000000026eb72000 /System/Library/PrivateFrameworks/SentencePieceInternal.framework/Versions/A/SentencePieceInternal +0x000000019f77a000 /System/Library/Frameworks/Vision.framework/Versions/A/Vision +0x0000000246298000 /System/Library/PrivateFrameworks/CoreSceneUnderstanding.framework/Versions/A/CoreSceneUnderstanding +0x00000002811df000 /System/Library/PrivateFrameworks/VisionCore.framework/Versions/A/VisionCore +0x00000001999f0000 /System/Library/PrivateFrameworks/DataDetectorsCore.framework/Versions/A/DataDetectorsCore +0x00000001bdbc1000 /System/Library/Frameworks/Vision.framework/libfaceCore.dylib +0x00000001be6db000 /System/Library/PrivateFrameworks/Futhark.framework/Versions/A/Futhark +0x00000001c2629000 /System/Library/PrivateFrameworks/InertiaCam.framework/Versions/A/InertiaCam +0x00000001be468000 /System/Library/PrivateFrameworks/TextRecognition.framework/Versions/A/TextRecognition +0x000000022eadd000 /System/Library/Frameworks/DataDetection.framework/Versions/A/DataDetection +0x00000001b8674000 /System/Library/PrivateFrameworks/TextInput.framework/Versions/A/TextInput +0x000000019849e000 /System/Library/PrivateFrameworks/CVNLP.framework/Versions/A/CVNLP +0x00000001dad09000 /System/Library/PrivateFrameworks/HIDDisplay.framework/Versions/A/HIDDisplay +0x000000019b255000 /usr/lib/libcups.2.dylib +0x000000019b2ec000 /System/Library/Frameworks/Kerberos.framework/Versions/A/Kerberos +0x000000019af5c000 /usr/lib/libresolv.9.dylib +0x0000000198958000 /System/Library/PrivateFrameworks/Heimdal.framework/Versions/A/Heimdal +0x00000001a4100000 /System/Library/Frameworks/Kerberos.framework/Versions/A/Libraries/libHeimdalProxy.dylib +0x000000019b350000 /System/Library/PrivateFrameworks/CommonAuth.framework/Versions/A/CommonAuth +0x00000001ad40e000 /System/Library/PrivateFrameworks/AXCoreUtilities.framework/Versions/A/AXCoreUtilities +0x00000001bda0a000 /System/Library/PrivateFrameworks/AttributeGraph.framework/Versions/A/AttributeGraph +0x000000028a801000 /usr/lib/libAXSafeCategoryBundle.dylib +0x0000000235252000 /System/Library/Frameworks/TabularData.framework/Versions/A/TabularData +0x000000023c11d000 /System/Library/PrivateFrameworks/ArgumentParserInternal.framework/Versions/A/ArgumentParserInternal +0x0000000195a5e000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvDSP.dylib +0x0000000197053000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLAPACK.dylib +0x0000000195f95000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLinearAlgebra.dylib +0x0000000196ec7000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparseBLAS.dylib +0x000000019704e000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libQuadrature.dylib +0x00000001949d5000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBNNS.dylib +0x0000000188221000 /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparse.dylib +0x000000022e5c2000 /System/Library/Frameworks/CoreTransferable.framework/Versions/A/CoreTransferable +0x000000019b2b4000 /System/Library/PrivateFrameworks/NetAuth.framework/Versions/A/NetAuth +0x00000001918b4000 /System/Library/PrivateFrameworks/login.framework/Versions/A/Frameworks/loginsupport.framework/Versions/A/loginsupport +0x000000018ca51000 /System/Library/PrivateFrameworks/UIFoundation.framework/Versions/A/UIFoundation +0x0000000195efd000 /usr/lib/libCheckFix.dylib +0x0000000190a3b000 /System/Library/PrivateFrameworks/MetadataUtilities.framework/Versions/A/MetadataUtilities +0x00000002569c7000 /System/Library/PrivateFrameworks/InstalledContentLibrary.framework/Versions/A/InstalledContentLibrary +0x000000018b192000 /System/Library/PrivateFrameworks/CoreServicesStore.framework/Versions/A/CoreServicesStore +0x00000001916ff000 /usr/lib/libapp_launch_measurement.dylib +0x00000001c8192000 /System/Library/PrivateFrameworks/MobileSystemServices.framework/Versions/A/MobileSystemServices +0x0000000198323000 /usr/lib/libxslt.1.dylib +0x0000000195ebc000 /System/Library/PrivateFrameworks/BackgroundTaskManagement.framework/Versions/A/BackgroundTaskManagement +0x00000001a3792000 /usr/lib/libcurl.4.dylib +0x000000028b517000 /usr/lib/libcrypto.46.dylib +0x000000028c09a000 /usr/lib/libssl.48.dylib +0x00000001a346c000 /System/Library/Frameworks/LDAP.framework/Versions/A/LDAP +0x00000001a34a8000 /System/Library/PrivateFrameworks/TrustEvaluationAgent.framework/Versions/A/TrustEvaluationAgent +0x000000019af79000 /usr/lib/libsasl2.2.dylib +0x00000001a6710000 /System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa +0x000000018b32d000 /System/Library/Frameworks/AppKit.framework/Versions/C/AppKit +0x000000023ffce000 /System/Library/PrivateFrameworks/CollectionViewCore.framework/Versions/A/CollectionViewCore +0x0000000193f6f000 /System/Library/PrivateFrameworks/XCTTargetBootstrap.framework/Versions/A/XCTTargetBootstrap +0x0000000199a42000 /System/Library/PrivateFrameworks/UserActivity.framework/Versions/A/UserActivity +0x0000000249ab0000 /System/Library/PrivateFrameworks/FrontBoard.framework/Versions/A/FrontBoard +0x000000027df9f000 /System/Library/PrivateFrameworks/UIIntelligenceSupport.framework/Versions/A/UIIntelligenceSupport +0x00000002342db000 /System/Library/Frameworks/SwiftUICore.framework/Versions/A/SwiftUICore +0x0000000284b4b000 /System/Library/PrivateFrameworks/WritingTools.framework/Versions/A/WritingTools +0x0000000283942000 /System/Library/PrivateFrameworks/WindowManagement.framework/Versions/A/WindowManagement +0x00000002497e0000 /System/Library/PrivateFrameworks/FocusEngine.framework/Versions/A/FocusEngine +0x00000002471e9000 /System/Library/PrivateFrameworks/DesignLibrary.framework/Versions/A/DesignLibrary +0x0000000193f5a000 /System/Library/PrivateFrameworks/DFRFoundation.framework/Versions/A/DFRFoundation +0x000000027eeec000 /System/Library/PrivateFrameworks/UpdateCycle.framework/Versions/A/UpdateCycle +0x0000000193c5e000 /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/HIToolbox +0x000000019f040000 /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/SpeechRecognition.framework/Versions/A/SpeechRecognition +0x0000000191686000 /System/Library/PrivateFrameworks/PerformanceAnalysis.framework/Versions/A/PerformanceAnalysis +0x000000019f3d0000 /System/Library/Frameworks/Accessibility.framework/Versions/A/Accessibility +0x0000000235238000 /System/Library/Frameworks/Symbols.framework/Versions/A/Symbols +0x0000000252dac000 /System/Library/PrivateFrameworks/Gestures.framework/Versions/A/Gestures +0x000000028c4c2000 /usr/lib/swift/libswiftSpatial.dylib +0x00000001b164e000 /usr/lib/swift/libswiftCoreGraphics.dylib +0x000000019fe14000 /usr/lib/swift/libswiftFoundation.dylib +0x00000001ebe32000 /usr/lib/swift/libswiftSwiftOnoneSupport.dylib +0x000000028c748000 /usr/lib/swift/libswiftsys_time.dylib +0x00000001d699f000 /System/Library/PrivateFrameworks/CoreMaterial.framework/Versions/A/CoreMaterial +0x000000028acfe000 /usr/lib/libSpatial.dylib +0x000000028a71e000 /System/Library/SubFrameworks/UIUtilities.framework/Versions/A/UIUtilities +0x00000001057c0000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/server/libjvm.dylib +0x00000001046ac000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libjimage.dylib +0x00000001047a8000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libjdwp.dylib +0x00000001046f4000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libjava.dylib +0x0000000104900000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libinstrument.dylib +0x00000001046d8000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libzip.dylib +0x000000028a7f3000 /usr/lib/i18n/libiconv_std.dylib +0x000000028a7e9000 /usr/lib/i18n/libUTF8.dylib +0x000000028a7f8000 /usr/lib/i18n/libmapper_none.dylib +0x00000001049d0000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libdt_socket.dylib +0x0000000104f6c000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libnio.dylib +0x0000000104fb0000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libnet.dylib +0x00000001049e4000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libmanagement.dylib +0x0000000104f4c000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libmanagement_ext.dylib +0x0000000104f8c000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libextnet.dylib +0x0000000105300000 /Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home/lib/libverify.dylib + + +VM Arguments: +jvm_args: -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:49167,suspend=y,server=n -javaagent:/Users/liangxin/Library/Caches/JetBrains/IntelliJIdea2026.1/captureAgent/debugger-agent.jar=file:///var/folders/pk/drq93rk10q733kk02fgp89xh0000gn/T/capture18007844485071508468.props -XX:TieredStopAtLevel=1 -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dmanagement.endpoints.jmx.exposure.include=* -Dkotlinx.coroutines.debug.enable.creation.stack.trace=false -Ddebugger.agent.enable.coroutines=true -Dkotlinx.coroutines.debug.enable.flows.stack.trace=true -Dkotlinx.coroutines.debug.enable.mutable.state.flows.stack.trace=true -Ddebugger.async.stack.trace.for.all.threads=true -Dfile.encoding=UTF-8 +java_command: org.springblade.resource.ResourceApplication +java_class_path (initial): /Users/liangxin/Project/JAVA/tms-erp-api/blade-ops/blade-resource/target/classes:/Users/liangxin/Project/JAVA/tms-erp-api/blade-common/target/classes:/Users/liangxin/.m2/repository/org/springblade/blade-core-launch/4.10.0.BASE-SNAPSHOT/blade-core-launch-4.10.0.BASE-SNAPSHOT.jar:/Users/liangxin/.m2/repository/org/springframework/boot/spring-boot-starter-web/3.5.16/spring-boot-starter-web-3.5.16.jar:/Users/liangxin/.m2/repository/org/springframework/boot/spring-boot-starter-json/3.5.16/spring-boot-starter-json-3.5.16.jar:/Users/liangxin/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.21.4/jackson-datatype-jdk8-2.21.4.jar:/Users/liangxin/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.18.0/jackson-module-parameter-names-2.18.0.jar:/Users/liangxin/.m2/repository/org/springframework/spring-webmvc/6.2.19/spring-webmvc-6.2.19.jar:/Users/liangxin/.m2/repository/org/springframework/spring-context/6.2.19/spring-context-6.2.19.jar:/Users/liangxin/.m2/repository/org/springframework/spring-expression/6.2.19/spring-expression-6.2.19.jar:/Users/liangxin/.m2/repository/org/springframework/boot/spring-boot-starter-undertow/3.5.16/spring-boot-starter-undertow-3.5.16.jar:/Users/liangxin/.m2/repository/io/undertow/undertow-core/2.3.24.Final/undertow-core-2.3.24.Final.jar:/Users/liangxin/.m2/repository/org/jboss/xnio/xnio-api/3.8.16.Final/xnio-api-3.8.16.Final.jar:/Users/liangxin/.m2/repository/org/wildfly/common/wildfly-common/1.5.4.Final/wildfly-common-1.5.4.Final.jar:/Users/liangxin/.m2/repository/org/wildfly/client/wildfly-client-config/1.0.1.Final/wildfly-client-config-1.0.1.Final.jar:/Users/liangxin/.m2/repository/org/jboss/xnio/xnio-nio/3.8.16.Final/xnio-nio-3.8.16.Final.jar:/Users/liangxin/.m2/repository/org/jboss/threads/jboss-threads/3.7.0.Final/jboss-threads-3.7.0.Final.jar:/Users/liangxin/.m2/repository/io/smallrye/common/smallrye-common-annotation/2.6.0/smallrye-common-annotation-2.6.0.jar:/User +Launcher Type: SUN_STANDARD + +[Global flags] + intx CICompilerCount = 4 {product} {ergonomic} + uint ConcGCThreads = 3 {product} {ergonomic} + uint G1ConcRefinementThreads = 10 {product} {ergonomic} + size_t G1HeapRegionSize = 8388608 {product} {ergonomic} + uintx GCDrainStackTargetSize = 64 {product} {ergonomic} + size_t InitialHeapSize = 603979776 {product} {ergonomic} + bool ManagementServer = true {product} {command line} + size_t MarkStackSize = 4194304 {product} {ergonomic} + size_t MaxHeapSize = 9663676416 {product} {ergonomic} + size_t MaxNewSize = 5796528128 {product} {ergonomic} + size_t MinHeapDeltaBytes = 8388608 {product} {ergonomic} + size_t MinHeapSize = 8388608 {product} {ergonomic} + uintx NonProfiledCodeHeapSize = 0 {pd product} {ergonomic} + bool ProfileInterpreter = false {pd product} {command line} + uintx ProfiledCodeHeapSize = 0 {pd product} {ergonomic} + size_t SoftMaxHeapSize = 9663676416 {manageable} {ergonomic} + intx TieredStopAtLevel = 1 {product} {command line} + bool UseCompressedClassPointers = true {product lp64_product} {ergonomic} + bool UseCompressedOops = true {product lp64_product} {ergonomic} + bool UseG1GC = true {product} {ergonomic} + bool UseNUMA = false {product} {ergonomic} + bool UseNUMAInterleaving = false {product} {ergonomic} + +Logging: +Log output configuration: + #0: stdout all=warning uptime,level,tags + #1: stderr all=off uptime,level,tags + +Environment Variables: +JAVA_HOME=/Users/liangxin/JDK/zulu17.48.15-ca-jdk17.0.10-macosx_aarch64/zulu-17.jdk/Contents/Home +PATH=/Users/liangxin/ai-infra/.venv/bin:/Users/liangxin/.nacos/bin:/Applications/Docker.app/Contents/Resources/bin:/Users/liangxin/Library/pnpm:/opt/homebrew/opt/ruby@3.2/bin:/opt/homebrew/opt/openssl@3/bin:/opt/miniconda3/bin:/opt/miniconda3/condabin:/usr/local/sbin:/usr/local/bin:/Users/liangxin/JDK/zulu17.48.15-ca-jdk17.0.10-macosx_aarch64/zulu-17.jdk/Contents/Home/bin:/Users/liangxin/fvm/default/bin:/Applications/MAMP/bin/php/php8.1.13/bin:/opt/homebrew/opt/ruby@3.2/bin:/Users/liangxin/.nvm/versions/node/v20.18.3/bin:/Applications/apache-tomcat-9.0.78:/Users/liangxin/JDK/zulu17.48.15-ca-jdk17.0.10-macosx_aarch64/zulu-17.jdk/Contents/Home/bin:/Users/liangxin/fvm/default/bin:/opt/homebrew/opt/libpng/bin:/Applications/pngquant:/Users/liangxin/AndroidSDK/platform-tools:/Users/liangxin/Library/Android/sdk/platform-tools:/Users/liangxin/Library/Andriod/sdk/cmdline-tools/latest/bin:/Users/liangxin/Library/Andriod/sdk:/Applications/apache-maven-3.8.1/bin:/opt/homebrew/bin:/usr/local/sbin:/usr/local/bin:/Users/liangxin/JDK/zulu17.48.15-ca-jdk17.0.10-macosx_aarch64/zulu-17.jdk/Contents/Home/bin:/Library/Frameworks/Python.framework/Versions/3.9/bin:/Users/liangxin/.local/bin:/Users/liangxin/fvm/default/bin:/Applications/MAMP/bin/php/php8.1.13/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/pkg/env/global/bin:/Library/Apple/usr/bin:/usr/local/share/dotnet:~/.dotnet/tools:/Library/Frameworks/Mono.framework/Versions/Current/Commands:/Users/liangxin/.cargo/bin:true:/Applications/极空间.app/Contents/Resources/app.asar.unpacked/bin/platform-tools +SHELL=/bin/zsh +LANG=C.UTF-8 +TMPDIR=/var/folders/pk/drq93rk10q733kk02fgp89xh0000gn/T/ + +Active Locale: +LC_ALL=C.UTF-8 +LC_COLLATE=C.UTF-8 +LC_CTYPE=C.UTF-8 +LC_MESSAGES=C.UTF-8 +LC_MONETARY=C.UTF-8 +LC_NUMERIC=C.UTF-8 +LC_TIME=C.UTF-8 + +Signal Handlers: + SIGSEGV: crash_handler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, unblocked + SIGBUS: crash_handler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, unblocked + SIGFPE: crash_handler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, unblocked + SIGPIPE: javaSignalHandler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, blocked + SIGXFSZ: javaSignalHandler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, blocked + SIGILL: crash_handler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, unblocked + SIGUSR2: SR_handler in libjvm.dylib, mask=00000000000000000000000000000000, flags=SA_RESTART|SA_SIGINFO, blocked + SIGHUP: UserHandler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, blocked + SIGINT: UserHandler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, blocked + SIGTERM: UserHandler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, blocked + SIGQUIT: UserHandler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, blocked + SIGTRAP: crash_handler in libjvm.dylib, mask=11100110000111110111111111111111, flags=SA_RESTART|SA_SIGINFO, unblocked + + +--------------- S Y S T E M --------------- + +OS: +uname: Darwin 25.6.0 Darwin Kernel Version 25.6.0: Fri Jul 31 19:16:36 PDT 2026; root:xnu-12377.161.14~5/RELEASE_ARM64_T6030 arm64 +OS uptime: 2 days 5:15 hours +rlimit (soft/hard): STACK 8176k/65520k , CORE 0k/infinity , NPROC 6000/9000 , NOFILE 10240/infinity , AS infinity/infinity , CPU infinity/infinity , DATA infinity/infinity , FSIZE infinity/infinity , MEMLOCK infinity/infinity , RSS infinity/infinity +load average: 16.89 38.96 54.23 + +CPU: total 12 (initial active 12) 0x61:0x0:0x5f4dea93:0, fp, simd, crc, lse +machdep.cpu.brand_string:Apple M3 Pro +hw.cachelinesize:128 +hw.l1icachesize:131072 +hw.l1dcachesize:65536 +hw.l2cachesize:4194304 + +Memory: 16k page, physical 37748736k(215216k free), swap 18874368k(1062912k free) + +vm_info: OpenJDK 64-Bit Server VM (17.0.8+7-LTS) for bsd-aarch64 JRE (17.0.8+7-LTS) (Zulu17.44+15-CA), built on Jul 5 2023 00:50:04 by "zulu_re" with clang Apple LLVM 12.0.0 (clang-1200.0.32.28) + +END. diff --git a/pom.xml b/pom.xml index bb5f2bfb..ae708e05 100644 --- a/pom.xml +++ b/pom.xml @@ -149,6 +149,11 @@ blade-track-api ${revision} + + org.springblade + blade-wechat-api + ${revision} + org.springblade From a2d9cfec028c81bdcfe0caee1d46afca254ac2d3 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Fri, 18 Sep 2026 19:13:45 +0800 Subject: [PATCH 11/55] =?UTF-8?q?1=E3=80=81=E4=BF=AE=E5=A4=8D=E5=B0=8F?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E6=89=8B=E6=9C=BA=E5=8F=B7=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E9=97=AE=E9=A2=98=202=E3=80=81=E8=B0=83=E6=95=B4OA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application.yml | 5 + .../oa/config/OAFeignClientConfig.java | 30 +---- .../thirdparty/oa/config/OAProperties.java | 2 +- .../oa/interceptor/OARequestInterceptor.java | 122 ++++++++++++++++++ doc/nacos/blade-dev.yaml | 1 + doc/nacos/blade-prod.yaml | 1 + doc/nacos/third-party-api.yaml | 1 + 7 files changed, 138 insertions(+), 24 deletions(-) create mode 100644 blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/interceptor/OARequestInterceptor.java diff --git a/blade-service/blade-system/src/main/resources/application.yml b/blade-service/blade-system/src/main/resources/application.yml index 1d8d630c..2d04e415 100644 --- a/blade-service/blade-system/src/main/resources/application.yml +++ b/blade-service/blade-system/src/main/resources/application.yml @@ -32,3 +32,8 @@ iam: authorization: ${IAM_SSO_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=} profile-authorization: ${IAM_SSO_PROFILE_AUTHORIZATION:YjNlZmU0ODEwMzJiNGJhZTpkYzQ5NDY1NmQ4MzE0NThjODI5MzlmNzA2ZjliNDY3MQ==} page-size: ${IAM_SSO_ACCOUNT_PAGE_SIZE:50} + +# OA人员同步走同一 gwzh 网关,仅需 Authorization(与可用 curl 一致) +thirdParty: + oa: + authorization: ${OA_AUTHORIZATION:${IAM_SSO_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=}} diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAFeignClientConfig.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAFeignClientConfig.java index 8917a2d5..ef4306ad 100644 --- a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAFeignClientConfig.java +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAFeignClientConfig.java @@ -1,42 +1,26 @@ package org.springblade.thirdparty.oa.config; import feign.Request; -import feign.RequestInterceptor; -import jakarta.annotation.Resource; -import org.springblade.core.tool.utils.StringUtil; +import org.springblade.thirdparty.oa.interceptor.OARequestInterceptor; import org.springframework.context.annotation.Bean; import java.util.concurrent.TimeUnit; /** + * OA Feign 客户端配置 + * * @author bfhuange * @since 2024/12/18 */ public class OAFeignClientConfig { - @Resource - OAProperties oaProperties; - @Bean - public RequestInterceptor requestInterceptor() { - return template -> { - // 空实现屏蔽 全局拦截器 BladeFeignRequestInterceptor - template.header("Authorization", normalizeAuthorization(oaProperties.getAuthorization())); - }; - } + @Bean + public OARequestInterceptor requestInterceptor(OAProperties oaProperties) { + return new OARequestInterceptor(oaProperties); + } @Bean public Request.Options options() { return new Request.Options(10, TimeUnit.SECONDS, 120, TimeUnit.SECONDS, true); } - - private String normalizeAuthorization(String authorization) { - if (StringUtil.isBlank(authorization)) { - return authorization; - } - if (StringUtil.startsWithIgnoreCase(authorization, "Basic ") - || StringUtil.startsWithIgnoreCase(authorization, "Bearer ")) { - return authorization; - } - return "Basic " + authorization; - } } diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAProperties.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAProperties.java index bfcbae17..264d15b1 100644 --- a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAProperties.java +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAProperties.java @@ -17,7 +17,7 @@ public class OAProperties { private String baseUrl; /** - * authorization + * gwzh 网关 Authorization(Basic),与可用 curl 一致 */ private String authorization; } diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/interceptor/OARequestInterceptor.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/interceptor/OARequestInterceptor.java new file mode 100644 index 00000000..fa6e580b --- /dev/null +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/interceptor/OARequestInterceptor.java @@ -0,0 +1,122 @@ +/** + * 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.thirdparty.oa.interceptor; + +import feign.RequestInterceptor; +import feign.RequestTemplate; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.thirdparty.oa.config.OAProperties; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +/** + * OA Feign 请求拦截器。 + *

+ * 对齐可用 curl:仅发送 Authorization + Content-Type,并清掉登录请求透传头, + * 避免 nginx 因 Host/Content-Length/Blade-Auth 等返回 400。 + * + * @author Chill + */ +@Slf4j +@RequiredArgsConstructor +public class OARequestInterceptor implements RequestInterceptor { + + private static final String BASIC_PREFIX = "Basic "; + private static final String BEARER_PREFIX = "Bearer "; + private static final Set STRIP_HEADERS = Set.of( + HttpHeaders.HOST.toLowerCase(Locale.ROOT), + HttpHeaders.CONTENT_LENGTH.toLowerCase(Locale.ROOT), + HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.ROOT), + HttpHeaders.ACCEPT.toLowerCase(Locale.ROOT), + HttpHeaders.CONNECTION.toLowerCase(Locale.ROOT), + HttpHeaders.TRANSFER_ENCODING.toLowerCase(Locale.ROOT), + HttpHeaders.COOKIE.toLowerCase(Locale.ROOT), + HttpHeaders.AUTHORIZATION.toLowerCase(Locale.ROOT), + HttpHeaders.ACCEPT_ENCODING.toLowerCase(Locale.ROOT), + HttpHeaders.ORIGIN.toLowerCase(Locale.ROOT), + HttpHeaders.REFERER.toLowerCase(Locale.ROOT), + HttpHeaders.EXPECT.toLowerCase(Locale.ROOT), + "keep-alive", + "upgrade", + "te", + "trailer", + "forwarded", + "x-real-ip", + "x-request-id", + "blade-auth", + "blade-requested-with", + "tenant-id", + "auth" + ); + + private final OAProperties oaProperties; + + @Override + public void apply(RequestTemplate template) { + stripForwardedHeaders(template); + // 与可用 curl 保持一致:Authorization + Content-Type + template.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + + String authorization = normalizeAuthorization(oaProperties.getAuthorization()); + if (StringUtil.isNotBlank(authorization)) { + template.header(HttpHeaders.AUTHORIZATION, authorization); + } else { + log.warn("OA Feign 未配置 third-party.oa.authorization,gwzh 网关可能拒绝请求"); + } + } + + private void stripForwardedHeaders(RequestTemplate template) { + List headerNames = new ArrayList<>(template.headers().keySet()); + for (String headerName : headerNames) { + String lowerName = headerName.toLowerCase(Locale.ROOT); + if (STRIP_HEADERS.contains(lowerName) + || lowerName.startsWith("x-forwarded-") + || lowerName.startsWith("blade-") + || lowerName.startsWith("x-b3-") + || lowerName.startsWith("sw8")) { + template.removeHeader(headerName); + } + } + } + + private String normalizeAuthorization(String authorization) { + if (StringUtil.isBlank(authorization)) { + return authorization; + } + if (StringUtil.startsWithIgnoreCase(authorization, BASIC_PREFIX) + || StringUtil.startsWithIgnoreCase(authorization, BEARER_PREFIX)) { + return authorization; + } + return BASIC_PREFIX + authorization; + } +} diff --git a/doc/nacos/blade-dev.yaml b/doc/nacos/blade-dev.yaml index 562e9777..a2763a44 100644 --- a/doc/nacos/blade-dev.yaml +++ b/doc/nacos/blade-dev.yaml @@ -88,6 +88,7 @@ thirdParty: # OA开放接口地址 baseUrl: http://127.0.0.1:8080 queryPersonPageUrl: /gwzh/OA/OA_GET_USER_LIST + authorization: ${OA_AUTHORIZATION:${IAM_SSO_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=}} track: # 轨迹开放接口地址 baseUrl: http://127.0.0.1:8080 diff --git a/doc/nacos/blade-prod.yaml b/doc/nacos/blade-prod.yaml index 69b2ffa9..7279339b 100644 --- a/doc/nacos/blade-prod.yaml +++ b/doc/nacos/blade-prod.yaml @@ -60,6 +60,7 @@ thirdParty: # OA开放接口地址 baseUrl: http://127.0.0.1:8080 queryPersonPageUrl: /gwzh/OA/OA_GET_USER_LIST + authorization: ${OA_AUTHORIZATION:${IAM_SSO_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=}} track: # 轨迹开放接口地址 baseUrl: http://127.0.0.1:8080 diff --git a/doc/nacos/third-party-api.yaml b/doc/nacos/third-party-api.yaml index a3fffa04..baa53a09 100644 --- a/doc/nacos/third-party-api.yaml +++ b/doc/nacos/third-party-api.yaml @@ -12,6 +12,7 @@ thirdParty: # OA开放接口地址 baseUrl: ${OA_BASE_URL:http://127.0.0.1:8080} queryPersonPageUrl: ${OA_QUERY_PERSON_PAGE_URL:/gwzh/OA/OA_GET_USER_LIST} + authorization: ${OA_AUTHORIZATION:${IAM_SSO_AUTHORIZATION:}} track: # 轨迹开放接口地址 baseUrl: ${TRACK_BASE_URL:http://127.0.0.1:8080} From 8d13978e848c10d93d012263da2904e0d8badac8 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Fri, 18 Sep 2026 19:48:16 +0800 Subject: [PATCH 12/55] =?UTF-8?q?1=E3=80=81=E4=BF=AE=E5=A4=8D=E5=B0=8F?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E6=89=8B=E6=9C=BA=E5=8F=B7=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E9=97=AE=E9=A2=98=202=E3=80=81=E8=B0=83=E6=95=B4OA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/controller/UserController.java | 2 +- .../service/impl/OASyncServiceImpl.java | 4 +- .../oa/config/OAFeignClientConfig.java | 23 +++++- .../oa/interceptor/OARequestInterceptor.java | 73 ++++++++++--------- 4 files changed, 63 insertions(+), 39 deletions(-) diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserController.java index 36c7f7d0..ab5e7820 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserController.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/UserController.java @@ -169,7 +169,7 @@ public class UserController { @Operation(summary = "同步OA人员") public R syncIamAccounts( @RequestParam(defaultValue = "1") Integer current, - @RequestParam(defaultValue = "50") Integer size) { + @RequestParam(defaultValue = "20") Integer size) { return R.data(oaSyncService.syncPersonFromUserList(current, 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 6e0c3e96..0a021390 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 @@ -266,7 +266,7 @@ public class OASyncServiceImpl implements IOASyncService { */ private OaPersonSyncPageVO syncPersonFromOaPage(int current, int size) { int pageNo = current < 1 ? 1 : current; - int pageSize = size < 1 ? 50 : Math.min(size, 200); + int pageSize = size < 1 ? 20 : Math.min(size, 200); OAPersonSearch personSearch = buildPersonSearch(null); personSearch.setCurPage(pageNo); personSearch.setPageSize(pageSize); @@ -309,7 +309,7 @@ public class OASyncServiceImpl implements IOASyncService { private OAPersonSearch buildPersonSearch(Date startTime) { OAPersonSearch personSearch = new OAPersonSearch(); personSearch.setCurPage(1); - personSearch.setPageSize(200); + personSearch.setPageSize(20); personSearch.setCreated(""); personSearch.setWorkcode(""); personSearch.setSubcompanyid1(""); diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAFeignClientConfig.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAFeignClientConfig.java index ef4306ad..c803caa5 100644 --- a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAFeignClientConfig.java +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/config/OAFeignClientConfig.java @@ -1,24 +1,45 @@ package org.springblade.thirdparty.oa.config; +import feign.Logger; import feign.Request; import org.springblade.thirdparty.oa.interceptor.OARequestInterceptor; +import org.springframework.cloud.openfeign.clientconfig.FeignClientConfigurer; import org.springframework.context.annotation.Bean; import java.util.concurrent.TimeUnit; /** - * OA Feign 客户端配置 + * OA Feign 客户端配置。 + *

+ * 必须关闭父上下文继承,否则全局 {@code BladeFeignRequestInterceptor} + * 会把当前登录请求的 Host、Content-Length、Blade-Auth 等头再次写入, + * 导致 gwzh nginx 返回 400。 * * @author bfhuange * @since 2024/12/18 */ public class OAFeignClientConfig { + @Bean + public FeignClientConfigurer feignClientConfigurer() { + return new FeignClientConfigurer() { + @Override + public boolean inheritParentConfiguration() { + return false; + } + }; + } + @Bean public OARequestInterceptor requestInterceptor(OAProperties oaProperties) { return new OARequestInterceptor(oaProperties); } + @Bean + public Logger.Level feignLoggerLevel() { + return Logger.Level.FULL; + } + @Bean public Request.Options options() { return new Request.Options(10, TimeUnit.SECONDS, 120, TimeUnit.SECONDS, true); diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/interceptor/OARequestInterceptor.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/interceptor/OARequestInterceptor.java index fa6e580b..e3d7c17d 100644 --- a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/interceptor/OARequestInterceptor.java +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/interceptor/OARequestInterceptor.java @@ -34,16 +34,21 @@ import org.springblade.thirdparty.oa.config.OAProperties; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; /** * OA Feign 请求拦截器。 *

- * 对齐可用 curl:仅发送 Authorization + Content-Type,并清掉登录请求透传头, - * 避免 nginx 因 Host/Content-Length/Blade-Auth 等返回 400。 + * 对齐可用 curl:仅发送 Authorization + Content-Type。 + * 配合 {@code FeignClientConfigurer#inheritParentConfiguration()=false}, + * 避免全局 BladeFeignRequestInterceptor 透传登录请求头。 * * @author Chill */ @@ -53,38 +58,16 @@ public class OARequestInterceptor implements RequestInterceptor { private static final String BASIC_PREFIX = "Basic "; private static final String BEARER_PREFIX = "Bearer "; - private static final Set STRIP_HEADERS = Set.of( - HttpHeaders.HOST.toLowerCase(Locale.ROOT), - HttpHeaders.CONTENT_LENGTH.toLowerCase(Locale.ROOT), - HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.ROOT), - HttpHeaders.ACCEPT.toLowerCase(Locale.ROOT), - HttpHeaders.CONNECTION.toLowerCase(Locale.ROOT), - HttpHeaders.TRANSFER_ENCODING.toLowerCase(Locale.ROOT), - HttpHeaders.COOKIE.toLowerCase(Locale.ROOT), + private static final Set KEEP_HEADERS = Set.of( HttpHeaders.AUTHORIZATION.toLowerCase(Locale.ROOT), - HttpHeaders.ACCEPT_ENCODING.toLowerCase(Locale.ROOT), - HttpHeaders.ORIGIN.toLowerCase(Locale.ROOT), - HttpHeaders.REFERER.toLowerCase(Locale.ROOT), - HttpHeaders.EXPECT.toLowerCase(Locale.ROOT), - "keep-alive", - "upgrade", - "te", - "trailer", - "forwarded", - "x-real-ip", - "x-request-id", - "blade-auth", - "blade-requested-with", - "tenant-id", - "auth" + HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.ROOT) ); private final OAProperties oaProperties; @Override public void apply(RequestTemplate template) { - stripForwardedHeaders(template); - // 与可用 curl 保持一致:Authorization + Content-Type + stripUnwantedHeaders(template); template.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); String authorization = normalizeAuthorization(oaProperties.getAuthorization()); @@ -93,17 +76,37 @@ public class OARequestInterceptor implements RequestInterceptor { } else { log.warn("OA Feign 未配置 third-party.oa.authorization,gwzh 网关可能拒绝请求"); } + + logRequest(template); } - private void stripForwardedHeaders(RequestTemplate template) { - List headerNames = new ArrayList<>(template.headers().keySet()); + /** + * 打印 OA Feign 最终发出的请求头与 body,便于对照 curl。 + */ + private void logRequest(RequestTemplate template) { + String bodyText = ""; + byte[] body = template.body(); + if (body != null && body.length > 0) { + Charset charset = template.requestCharset() == null ? StandardCharsets.UTF_8 : template.requestCharset(); + bodyText = new String(body, charset); + } + log.info("OA Feign 请求 method={}, url={}{}{}, headers={}, body={}", + template.method(), + template.feignTarget() == null ? "" : template.feignTarget().url(), + template.path(), + template.queryLine() == null ? "" : template.queryLine(), + template.headers(), + bodyText); + } + + /** + * 只保留 Authorization / Content-Type,其余全部移除。 + */ + private void stripUnwantedHeaders(RequestTemplate template) { + Map> headers = template.headers(); + List headerNames = new ArrayList<>(headers.keySet()); for (String headerName : headerNames) { - String lowerName = headerName.toLowerCase(Locale.ROOT); - if (STRIP_HEADERS.contains(lowerName) - || lowerName.startsWith("x-forwarded-") - || lowerName.startsWith("blade-") - || lowerName.startsWith("x-b3-") - || lowerName.startsWith("sw8")) { + if (!KEEP_HEADERS.contains(headerName.toLowerCase(Locale.ROOT))) { template.removeHeader(headerName); } } 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 13/55] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E5=85=AC=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 00000000..590a8baf --- /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 9cccc5dd..e4075974 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 45b74377..3e11ad4b 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 0a021390..5e208c71 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 From a2e23ea264d3eaf363cd0561334806550c456f2e Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Fri, 18 Sep 2026 21:12:01 +0800 Subject: [PATCH 14/55] =?UTF-8?q?1=E3=80=81=E4=BF=AE=E6=94=B9=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E7=BB=84=E7=BB=87=202=E3=80=81=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=8C=97=E6=96=97=E5=AE=9A=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transport/pojo/vo/WaybillLocateVO.java | 74 +++++++ .../transport/pojo/vo/WaybillTrackVO.java | 95 +++++++++ .../src/main/resources/application.yml | 6 +- blade-service/blade-transport/pom.xml | 4 + .../controller/WaybillController.java | 19 ++ .../transport/service/IWaybillService.java | 20 ++ .../service/impl/WaybillServiceImpl.java | 200 +++++++++++++++++- .../src/main/resources/application-dev.yml | 7 + blade-third-party-api/blade-lbs-api/pom.xml | 22 ++ .../lbs/config/LbsFeignClientConfig.java | 69 ++++++ .../thirdparty/lbs/config/LbsProperties.java | 54 +++++ .../ThirdPartyLbsAutoConfiguration.java | 39 ++++ .../thirdparty/lbs/constant/LbsConstant.java | 52 +++++ .../thirdparty/lbs/feign/ILbsClient.java | 51 +++++ .../interceptor/LbsRequestInterceptor.java | 115 ++++++++++ .../lbs/pojo/dto/LbsLocateRequest.java | 75 +++++++ .../lbs/pojo/vo/LbsLocateResponse.java | 100 +++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../thirdparty/oa/feign/IOAClient.java | 4 +- blade-third-party-api/pom.xml | 1 + doc/nacos/blade-dev.yaml | 9 +- doc/nacos/blade-prod.yaml | 9 +- doc/nacos/third-party-api.yaml | 9 +- pom.xml | 5 + 24 files changed, 1031 insertions(+), 9 deletions(-) create mode 100644 blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillLocateVO.java create mode 100644 blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillTrackVO.java create mode 100644 blade-third-party-api/blade-lbs-api/pom.xml create mode 100644 blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/config/LbsFeignClientConfig.java create mode 100644 blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/config/LbsProperties.java create mode 100644 blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/config/ThirdPartyLbsAutoConfiguration.java create mode 100644 blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/constant/LbsConstant.java create mode 100644 blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/feign/ILbsClient.java create mode 100644 blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/interceptor/LbsRequestInterceptor.java create mode 100644 blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/pojo/dto/LbsLocateRequest.java create mode 100644 blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/pojo/vo/LbsLocateResponse.java create mode 100644 blade-third-party-api/blade-lbs-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillLocateVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillLocateVO.java new file mode 100644 index 00000000..c3ce7967 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillLocateVO.java @@ -0,0 +1,74 @@ +/** + * 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.transport.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Map; + +/** + * 运单车辆实时定位结果 + * + * @author Chill + */ +@Data +@Schema(description = "运单车辆实时定位结果") +public class WaybillLocateVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "运单ID") + private Long waybillId; + + @Schema(description = "车牌号") + private String vehicleNo; + + @Schema(description = "经度") + private BigDecimal longitude; + + @Schema(description = "纬度") + private BigDecimal latitude; + + @Schema(description = "地址") + private String address; + + @Schema(description = "定位时间") + private String locateTime; + + @Schema(description = "速度") + private String speed; + + @Schema(description = "方向") + private String direction; + + @Schema(description = "LBS原始数据") + private Map rawData; +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillTrackVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillTrackVO.java new file mode 100644 index 00000000..44a9e1b7 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/WaybillTrackVO.java @@ -0,0 +1,95 @@ +/** + * 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.transport.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * 运单历史轨迹结果 + * + * @author Chill + */ +@Data +@Schema(description = "运单历史轨迹结果") +public class WaybillTrackVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "运单ID") + private Long waybillId; + + @Schema(description = "车牌号") + private String vehicleNo; + + @Schema(description = "开始日期") + private String startDate; + + @Schema(description = "结束日期") + private String endDate; + + @Schema(description = "轨迹点数量") + private Integer total; + + @Schema(description = "轨迹点列表") + private List points = new ArrayList<>(); + + @Data + @Schema(description = "轨迹点") + public static class WaybillTrackPointVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "车牌号") + private String vehicleNo; + + @Schema(description = "经度") + private BigDecimal longitude; + + @Schema(description = "纬度") + private BigDecimal latitude; + + @Schema(description = "定位时间") + private String locateTime; + + @Schema(description = "速度") + private String speed; + + @Schema(description = "方向") + private String direction; + + @Schema(description = "地址") + private String address; + } +} diff --git a/blade-service/blade-system/src/main/resources/application.yml b/blade-service/blade-system/src/main/resources/application.yml index 2d04e415..1447a579 100644 --- a/blade-service/blade-system/src/main/resources/application.yml +++ b/blade-service/blade-system/src/main/resources/application.yml @@ -33,7 +33,11 @@ iam: profile-authorization: ${IAM_SSO_PROFILE_AUTHORIZATION:YjNlZmU0ODEwMzJiNGJhZTpkYzQ5NDY1NmQ4MzE0NThjODI5MzlmNzA2ZjliNDY3MQ==} page-size: ${IAM_SSO_ACCOUNT_PAGE_SIZE:50} -# OA人员同步走同一 gwzh 网关,仅需 Authorization(与可用 curl 一致) +# OA组织/人员同步走同一 gwzh 网关,仅需 Authorization(与可用 curl 一致) thirdParty: oa: + baseUrl: ${OA_BASE_URL:http://172.16.204.83:38000} + queryCompanyPageUrl: ${OA_QUERY_COMPANY_PAGE_URL:/gwzh/OA/OA_GET_COMPANY_LIST} + queryDepartmentPageUrl: ${OA_QUERY_DEPARTMENT_PAGE_URL:/gwzh/OA/OA_GET_DEPARTMENT_LIST} + queryPersonPageUrl: ${OA_QUERY_PERSON_PAGE_URL:/gwzh/OA/OA_GET_USER_LIST} authorization: ${OA_AUTHORIZATION:${IAM_SSO_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=}} diff --git a/blade-service/blade-transport/pom.xml b/blade-service/blade-transport/pom.xml index 852de9a7..b0124361 100644 --- a/blade-service/blade-transport/pom.xml +++ b/blade-service/blade-transport/pom.xml @@ -31,6 +31,10 @@ org.springblade blade-transport-api + + org.springblade + blade-lbs-api + org.springblade blade-user-api diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java index 8479ad4d..ba2c2413 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java @@ -53,6 +53,8 @@ import org.springblade.transport.pojo.dto.WaybillImportBatchRequest; import org.springblade.transport.pojo.vo.BusinessRemoveResultVO; import org.springblade.transport.pojo.vo.LoadingManageVO; import org.springblade.transport.pojo.vo.WaybillImportBatchVO; +import org.springblade.transport.pojo.vo.WaybillLocateVO; +import org.springblade.transport.pojo.vo.WaybillTrackVO; import org.springblade.transport.pojo.vo.WaybillPunchRecordsVO; import org.springblade.transport.pojo.vo.WaybillVO; import org.springblade.transport.pojo.dto.WaybillMileageRequest; @@ -109,6 +111,23 @@ public class WaybillController extends BladeController { return R.data(waybillService.listPunchRecords(waybillId)); } + @PostMapping("/locate") + @ApiOperationSupport(order = 1) + @Operation(summary = "车辆实时定位", description = "按运单绑定车牌调用 LBS_LOCATE") + public R locate(@Parameter(description = "运单ID", required = true) @RequestParam Long id) { + return R.data(waybillService.locateVehicle(id)); + } + + @PostMapping("/track") + @ApiOperationSupport(order = 1) + @Operation(summary = "车辆历史轨迹", description = "按运单绑定车牌 + 日期区间调用 LBS_LOCATE") + public R track( + @Parameter(description = "运单ID", required = true) @RequestParam Long id, + @Parameter(description = "开始日期 YYYY-MM-DD", required = true) @RequestParam String startDate, + @Parameter(description = "结束日期 YYYY-MM-DD", required = true) @RequestParam String endDate) { + return R.data(waybillService.trackVehicle(id, startDate, endDate)); + } + @GetMapping("/list") @ApiOperationSupport(order = 2) @Operation(summary = "分页", description = "传入waybill") diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillService.java index bf3dccf6..35367903 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IWaybillService.java @@ -28,6 +28,8 @@ import org.springblade.transport.excel.WaybillExcel; import org.springblade.transport.pojo.entity.Waybill; import org.springblade.transport.pojo.vo.BusinessRemoveResultVO; import org.springblade.transport.pojo.vo.LoadingManageVO; +import org.springblade.transport.pojo.vo.WaybillLocateVO; +import org.springblade.transport.pojo.vo.WaybillTrackVO; import org.springblade.transport.pojo.vo.WaybillPunchRecordsVO; import org.springblade.transport.pojo.vo.WaybillVO; import org.springblade.transport.pojo.dto.WaybillMileageRequest; @@ -49,6 +51,24 @@ public interface IWaybillService extends BaseService { */ WaybillPunchRecordsVO listPunchRecords(Long waybillId); + /** + * 运单车辆实时定位(按运单绑定车牌调用 LBS) + * + * @param id 运单ID + * @return 定位结果 + */ + WaybillLocateVO locateVehicle(Long id); + + /** + * 运单历史轨迹回放(按运单绑定车牌 + 日期区间调用 LBS) + * + * @param id 运单ID + * @param startDate 开始日期 YYYY-MM-DD + * @param endDate 结束日期 YYYY-MM-DD + * @return 轨迹结果 + */ + WaybillTrackVO trackVehicle(Long id, String startDate, String endDate); + Waybill syncDriverAcceptState(Waybill waybill); boolean submit(Waybill waybill); boolean saveDraft(Waybill waybill); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java index b837dd8c..b22ce136 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java @@ -25,6 +25,7 @@ package org.springblade.transport.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.fasterxml.jackson.databind.JsonNode; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.mp.base.BaseServiceImpl; import org.springblade.core.tool.jackson.JsonUtil; @@ -32,10 +33,10 @@ import org.springblade.core.tool.utils.BeanUtil; import org.springblade.core.tool.utils.Func; import org.springblade.system.cache.UserCache; import org.springblade.system.pojo.entity.Dept; +import org.springblade.thirdparty.lbs.feign.ILbsClient; +import org.springblade.thirdparty.lbs.pojo.dto.LbsLocateRequest; +import org.springblade.thirdparty.lbs.pojo.vo.LbsLocateResponse; import org.springblade.transport.excel.WaybillExcel; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.util.Arrays; import org.springblade.transport.mapper.WaybillEnroutePunchMapper; import org.springblade.transport.mapper.WaybillMapper; import org.springblade.transport.mapper.WaybillNodePunchMapper; @@ -49,6 +50,8 @@ import org.springblade.transport.pojo.entity.WaybillNodePunch; import org.springblade.transport.pojo.dto.WaybillMileageRequest; import org.springblade.transport.pojo.vo.BusinessRemoveResultVO; import org.springblade.transport.pojo.vo.LoadingManageVO; +import org.springblade.transport.pojo.vo.WaybillLocateVO; +import org.springblade.transport.pojo.vo.WaybillTrackVO; import org.springblade.transport.pojo.vo.WaybillPunchPhotoVO; import org.springblade.transport.pojo.vo.WaybillPunchRecordItemVO; import org.springblade.transport.pojo.vo.WaybillPunchRecordsVO; @@ -67,7 +70,11 @@ import org.springframework.transaction.annotation.Transactional; import lombok.extern.slf4j.Slf4j; import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Comparator; import java.util.Date; import java.util.LinkedHashMap; @@ -111,6 +118,9 @@ public class WaybillServiceImpl extends BaseServiceImpl @jakarta.annotation.Resource private WaybillEnroutePunchMapper waybillEnroutePunchMapper; + @jakarta.annotation.Resource + private ILbsClient lbsClient; + @Override public IPage selectWaybillPage(IPage page, WaybillVO waybill) { IPage entityPage = page(page, buildQuery(waybill)); @@ -128,6 +138,190 @@ public class WaybillServiceImpl extends BaseServiceImpl return result; } + @Override + public WaybillLocateVO locateVehicle(Long id) { + Waybill waybill = requireWaybillWithVehicleNo(id, "无法实时定位"); + String vehicleNo = waybill.getVehicleNo().trim(); + LbsLocateResponse response = invokeLbsLocate(id, vehicleNo, new LbsLocateRequest(vehicleNo), "实时定位"); + List> pointMaps = extractLbsPointMaps(response.getData()); + if (pointMaps.isEmpty()) { + throw new ServiceException("暂无车辆实时定位数据"); + } + return buildLocateVO(id, vehicleNo, pointMaps.get(0)); + } + + @Override + public WaybillTrackVO trackVehicle(Long id, String startDate, String endDate) { + Waybill waybill = requireWaybillWithVehicleNo(id, "无法查询历史轨迹"); + String vehicleNo = waybill.getVehicleNo().trim(); + String normalizedStart = normalizeTrackDate(startDate, "开始日期"); + String normalizedEnd = normalizeTrackDate(endDate, "结束日期"); + if (LocalDate.parse(normalizedStart).isAfter(LocalDate.parse(normalizedEnd))) { + throw new ServiceException("开始日期不能晚于结束日期"); + } + LbsLocateResponse response = invokeLbsLocate(id, vehicleNo, + new LbsLocateRequest(vehicleNo, normalizedStart, normalizedEnd), "历史轨迹"); + List> pointMaps = extractLbsPointMaps(response.getData()); + WaybillTrackVO trackVO = new WaybillTrackVO(); + trackVO.setWaybillId(id); + trackVO.setVehicleNo(vehicleNo); + trackVO.setStartDate(normalizedStart); + trackVO.setEndDate(normalizedEnd); + List points = new ArrayList<>(); + for (Map pointMap : pointMaps) { + WaybillTrackVO.WaybillTrackPointVO point = buildTrackPoint(vehicleNo, pointMap); + if (point.getLongitude() != null && point.getLatitude() != null) { + points.add(point); + } + } + trackVO.setPoints(points); + trackVO.setTotal(points.size()); + return trackVO; + } + + private Waybill requireWaybillWithVehicleNo(Long id, String actionTip) { + if (id == null) { + throw new ServiceException("运单ID不能为空"); + } + Waybill waybill = getById(id); + if (waybill == null || Objects.equals(waybill.getIsDeleted(), 1)) { + throw new ServiceException("运单不存在"); + } + String vehicleNo = Func.toStr(waybill.getVehicleNo(), "").trim(); + if (Func.isEmpty(vehicleNo)) { + throw new ServiceException("运单未绑定车牌号," + actionTip); + } + waybill.setVehicleNo(vehicleNo); + return waybill; + } + + private LbsLocateResponse invokeLbsLocate(Long waybillId, String vehicleNo, LbsLocateRequest request, String scene) { + LbsLocateResponse response; + try { + response = lbsClient.locate(request); + } catch (Exception exception) { + log.error("调用LBS{}失败 waybillId={}, vehicleNo={}", scene, waybillId, vehicleNo, exception); + throw new ServiceException("调用车辆" + scene + "接口失败"); + } + if (response == null || !response.isSuccess()) { + String errorMessage = response == null ? "车辆" + scene + "无返回" : response.errorMessage(); + throw new ServiceException(errorMessage); + } + return response; + } + + private String normalizeTrackDate(String dateText, String fieldName) { + if (Func.isEmpty(dateText)) { + throw new ServiceException(fieldName + "不能为空"); + } + String normalized = dateText.trim(); + try { + return LocalDate.parse(normalized, DateTimeFormatter.ISO_LOCAL_DATE).toString(); + } catch (DateTimeParseException exception) { + throw new ServiceException(fieldName + "格式必须为YYYY-MM-DD"); + } + } + + /** + * 提取 LBS 轨迹点列表,兼容 data 数组 或 data.list 数组 + */ + private List> extractLbsPointMaps(JsonNode dataNode) { + List> pointMaps = new ArrayList<>(); + if (dataNode == null || dataNode.isNull()) { + return pointMaps; + } + JsonNode listNode = dataNode; + if (dataNode.isObject() && dataNode.has("list")) { + listNode = dataNode.get("list"); + } + if (listNode != null && listNode.isArray()) { + for (JsonNode item : listNode) { + if (item != null && item.isObject()) { + pointMaps.add(JsonUtil.toMap(item.toString())); + } + } + return pointMaps; + } + if (dataNode.isObject()) { + pointMaps.add(JsonUtil.toMap(dataNode.toString())); + } + return pointMaps; + } + + /** + * 将 LBS 单点数据归一化为实时定位结果 + */ + private WaybillLocateVO buildLocateVO(Long waybillId, String vehicleNo, Map pointMap) { + WaybillLocateVO locateVO = new WaybillLocateVO(); + locateVO.setWaybillId(waybillId); + locateVO.setVehicleNo(vehicleNo); + locateVO.setRawData(pointMap); + fillPointFields(locateVO, pointMap); + return locateVO; + } + + private WaybillTrackVO.WaybillTrackPointVO buildTrackPoint(String vehicleNo, Map pointMap) { + WaybillTrackVO.WaybillTrackPointVO point = new WaybillTrackVO.WaybillTrackPointVO(); + point.setVehicleNo(vehicleNo); + fillPointFields(point, pointMap); + return point; + } + + private void fillPointFields(WaybillLocateVO target, Map pointMap) { + target.setLongitude(firstDecimal(pointMap, "longitude", "lon", "lng", "x", "jd", "Longitude", "LON", "LNG", "JD", "X")); + target.setLatitude(firstDecimal(pointMap, "latitude", "lat", "y", "wd", "Latitude", "LAT", "WD", "Y")); + target.setAddress(firstText(pointMap, "address", "addr", "adr", "wz", "Address", "ADDR", "ADR")); + target.setLocateTime(firstText(pointMap, "locationTime", "pos_time", "locateTime", "gpsTime", "gpstime", "time", "gpszdsj", "GPSTime", "Time")); + target.setSpeed(firstText(pointMap, "speed", "v", "sd", "Speed", "SD", "V")); + target.setDirection(firstText(pointMap, "direct", "direction", "h", "fx", "Direction", "FX", "course", "H")); + String responseVehicleNo = firstText(pointMap, "cph", "vehicleNo", "plateNo", "CPH"); + if (Func.isNotEmpty(responseVehicleNo)) { + target.setVehicleNo(responseVehicleNo); + } + } + + private void fillPointFields(WaybillTrackVO.WaybillTrackPointVO target, Map pointMap) { + target.setLongitude(firstDecimal(pointMap, "longitude", "lon", "lng", "x", "jd", "Longitude", "LON", "LNG", "JD", "X")); + target.setLatitude(firstDecimal(pointMap, "latitude", "lat", "y", "wd", "Latitude", "LAT", "WD", "Y")); + target.setAddress(firstText(pointMap, "address", "addr", "adr", "wz", "Address", "ADDR", "ADR")); + target.setLocateTime(firstText(pointMap, "locationTime", "pos_time", "locateTime", "gpsTime", "gpstime", "time", "gpszdsj", "GPSTime", "Time")); + target.setSpeed(firstText(pointMap, "speed", "v", "sd", "Speed", "SD", "V")); + target.setDirection(firstText(pointMap, "direct", "direction", "h", "fx", "Direction", "FX", "course", "H")); + String responseVehicleNo = firstText(pointMap, "cph", "vehicleNo", "plateNo", "CPH"); + if (Func.isNotEmpty(responseVehicleNo)) { + target.setVehicleNo(responseVehicleNo); + } + } + + private BigDecimal firstDecimal(Map rawData, String... keys) { + String text = firstText(rawData, keys); + if (Func.isEmpty(text)) { + return null; + } + try { + return new BigDecimal(text.trim()); + } catch (NumberFormatException exception) { + return null; + } + } + + private String firstText(Map rawData, String... keys) { + if (rawData == null || rawData.isEmpty() || keys == null) { + return null; + } + for (String key : keys) { + Object value = rawData.get(key); + if (value == null) { + continue; + } + String text = String.valueOf(value).trim(); + if (Func.isNotEmpty(text) && !"null".equalsIgnoreCase(text)) { + return text; + } + } + return null; + } + @Override public WaybillPunchRecordsVO listPunchRecords(Long waybillId) { WaybillPunchRecordsVO vo = new WaybillPunchRecordsVO(); diff --git a/blade-service/blade-transport/src/main/resources/application-dev.yml b/blade-service/blade-transport/src/main/resources/application-dev.yml index 048e650a..29ae464e 100644 --- a/blade-service/blade-transport/src/main/resources/application-dev.yml +++ b/blade-service/blade-transport/src/main/resources/application-dev.yml @@ -8,3 +8,10 @@ spring: url: ${blade.datasource.dev.url} username: ${blade.datasource.dev.username} password: ${blade.datasource.dev.password} + +# LBS 车辆实时定位(Authorization 与 OA 人员接口一致) +thirdParty: + lbs: + baseUrl: ${LBS_BASE_URL:http://172.16.204.83:38000} + locateUrl: ${LBS_LOCATE_URL:/gwzh/LBS/LBS_LOCATE} + authorization: ${LBS_AUTHORIZATION:${OA_AUTHORIZATION:${IAM_SSO_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=}}} diff --git a/blade-third-party-api/blade-lbs-api/pom.xml b/blade-third-party-api/blade-lbs-api/pom.xml new file mode 100644 index 00000000..fe6d45dc --- /dev/null +++ b/blade-third-party-api/blade-lbs-api/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + org.springblade + blade-third-party-api + ${revision} + + + blade-lbs-api + ${project.artifactId} + jar + + + + org.springblade + blade-core-tool + + + diff --git a/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/config/LbsFeignClientConfig.java b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/config/LbsFeignClientConfig.java new file mode 100644 index 00000000..bd85839e --- /dev/null +++ b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/config/LbsFeignClientConfig.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.thirdparty.lbs.config; + +import feign.Logger; +import feign.Request; +import org.springblade.thirdparty.lbs.interceptor.LbsRequestInterceptor; +import org.springframework.cloud.openfeign.clientconfig.FeignClientConfigurer; +import org.springframework.context.annotation.Bean; + +import java.util.concurrent.TimeUnit; + +/** + * LBS Feign 客户端配置。 + *

+ * 关闭父上下文继承,避免全局 BladeFeignRequestInterceptor 透传登录态请求头导致 gwzh 400。 + * + * @author Chill + */ +public class LbsFeignClientConfig { + + @Bean + public FeignClientConfigurer feignClientConfigurer() { + return new FeignClientConfigurer() { + @Override + public boolean inheritParentConfiguration() { + return false; + } + }; + } + + @Bean + public LbsRequestInterceptor requestInterceptor(LbsProperties lbsProperties) { + return new LbsRequestInterceptor(lbsProperties); + } + + @Bean + public Logger.Level feignLoggerLevel() { + return Logger.Level.FULL; + } + + @Bean + public Request.Options options() { + return new Request.Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true); + } +} diff --git a/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/config/LbsProperties.java b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/config/LbsProperties.java new file mode 100644 index 00000000..0e8b780d --- /dev/null +++ b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/config/LbsProperties.java @@ -0,0 +1,54 @@ +/** + * 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.thirdparty.lbs.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * LBS 配置 + * + * @author Chill + */ +@Data +@ConfigurationProperties(prefix = "third-party.lbs") +public class LbsProperties { + + /** + * LBS 网关基础地址 + */ + private String baseUrl; + + /** + * 车辆实时定位路径 + */ + private String locateUrl = "/gwzh/LBS/LBS_LOCATE"; + + /** + * gwzh 网关 Authorization(Basic),与 OA 人员接口一致 + */ + private String authorization; +} diff --git a/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/config/ThirdPartyLbsAutoConfiguration.java b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/config/ThirdPartyLbsAutoConfiguration.java new file mode 100644 index 00000000..85501419 --- /dev/null +++ b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/config/ThirdPartyLbsAutoConfiguration.java @@ -0,0 +1,39 @@ +/** + * 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.thirdparty.lbs.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * 第三方 LBS 自动配置 + * + * @author Chill + */ +@EnableConfigurationProperties(LbsProperties.class) +@Configuration +public class ThirdPartyLbsAutoConfiguration { +} diff --git a/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/constant/LbsConstant.java b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/constant/LbsConstant.java new file mode 100644 index 00000000..8f4a0097 --- /dev/null +++ b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/constant/LbsConstant.java @@ -0,0 +1,52 @@ +/** + * 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.thirdparty.lbs.constant; + +/** + * LBS 常量 + * + * @author Chill + */ +public final class LbsConstant { + + private LbsConstant() { + } + + /** + * 成功响应码(文档标注) + */ + public static final int SUCCESS_STATUS = 200; + + /** + * 成功响应码(字符串) + */ + public static final String SUCCESS_STATUS_TEXT = "200"; + + /** + * gwzh 常见成功码(与 OA 一致) + */ + public static final String SUCCESS_CODE = "1"; +} diff --git a/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/feign/ILbsClient.java b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/feign/ILbsClient.java new file mode 100644 index 00000000..e19a1486 --- /dev/null +++ b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/feign/ILbsClient.java @@ -0,0 +1,51 @@ +/** + * 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.thirdparty.lbs.feign; + +import org.springblade.thirdparty.lbs.config.LbsFeignClientConfig; +import org.springblade.thirdparty.lbs.pojo.dto.LbsLocateRequest; +import org.springblade.thirdparty.lbs.pojo.vo.LbsLocateResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +/** + * LBS 接口 + * + * @author Chill + */ +@FeignClient(name = "LBS", url = "${thirdParty.lbs.baseUrl}", configuration = LbsFeignClientConfig.class) +public interface ILbsClient { + + /** + * 车辆实时定位 + * + * @param request 请求(cph=车牌号) + * @return 定位结果 + */ + @PostMapping("${thirdParty.lbs.locateUrl:/gwzh/LBS/LBS_LOCATE}") + LbsLocateResponse locate(@RequestBody LbsLocateRequest request); +} diff --git a/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/interceptor/LbsRequestInterceptor.java b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/interceptor/LbsRequestInterceptor.java new file mode 100644 index 00000000..d458cd96 --- /dev/null +++ b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/interceptor/LbsRequestInterceptor.java @@ -0,0 +1,115 @@ +/** + * 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.thirdparty.lbs.interceptor; + +import feign.RequestInterceptor; +import feign.RequestTemplate; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.thirdparty.lbs.config.LbsProperties; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +/** + * LBS Feign 请求拦截器:仅发送 Authorization + Content-Type,与 OA 人员接口一致。 + * + * @author Chill + */ +@Slf4j +@RequiredArgsConstructor +public class LbsRequestInterceptor implements RequestInterceptor { + + private static final String BASIC_PREFIX = "Basic "; + private static final String BEARER_PREFIX = "Bearer "; + private static final Set KEEP_HEADERS = Set.of( + HttpHeaders.AUTHORIZATION.toLowerCase(Locale.ROOT), + HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.ROOT) + ); + + private final LbsProperties lbsProperties; + + @Override + public void apply(RequestTemplate template) { + stripUnwantedHeaders(template); + template.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + + String authorization = normalizeAuthorization(lbsProperties.getAuthorization()); + if (StringUtil.isNotBlank(authorization)) { + template.header(HttpHeaders.AUTHORIZATION, authorization); + } else { + log.warn("LBS Feign 未配置 third-party.lbs.authorization,gwzh 网关可能拒绝请求"); + } + + logRequest(template); + } + + private void logRequest(RequestTemplate template) { + String bodyText = ""; + byte[] body = template.body(); + if (body != null && body.length > 0) { + Charset charset = template.requestCharset() == null ? StandardCharsets.UTF_8 : template.requestCharset(); + bodyText = new String(body, charset); + } + log.info("LBS Feign 请求 method={}, url={}{}{}, headers={}, body={}", + template.method(), + template.feignTarget() == null ? "" : template.feignTarget().url(), + template.path(), + template.queryLine() == null ? "" : template.queryLine(), + template.headers(), + bodyText); + } + + private void stripUnwantedHeaders(RequestTemplate template) { + Map> headers = template.headers(); + List headerNames = new ArrayList<>(headers.keySet()); + for (String headerName : headerNames) { + if (!KEEP_HEADERS.contains(headerName.toLowerCase(Locale.ROOT))) { + template.removeHeader(headerName); + } + } + } + + private String normalizeAuthorization(String authorization) { + if (StringUtil.isBlank(authorization)) { + return authorization; + } + if (StringUtil.startsWithIgnoreCase(authorization, BASIC_PREFIX) + || StringUtil.startsWithIgnoreCase(authorization, BEARER_PREFIX)) { + return authorization; + } + return BASIC_PREFIX + authorization; + } +} diff --git a/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/pojo/dto/LbsLocateRequest.java b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/pojo/dto/LbsLocateRequest.java new file mode 100644 index 00000000..4c204602 --- /dev/null +++ b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/pojo/dto/LbsLocateRequest.java @@ -0,0 +1,75 @@ +/** + * 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.thirdparty.lbs.pojo.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * LBS 定位/历史轨迹请求 + * + * @author Chill + */ +@Data +@NoArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class LbsLocateRequest implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 车牌号 + */ + private String cph; + + /** + * 开始日期,格式 YYYY-MM-DD(历史轨迹) + */ + @JsonProperty("start_date") + private String startDate; + + /** + * 结束日期,格式 YYYY-MM-DD(历史轨迹) + */ + @JsonProperty("end_date") + private String endDate; + + public LbsLocateRequest(String cph) { + this.cph = cph; + } + + public LbsLocateRequest(String cph, String startDate, String endDate) { + this.cph = cph; + this.startDate = startDate; + this.endDate = endDate; + } +} diff --git a/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/pojo/vo/LbsLocateResponse.java b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/pojo/vo/LbsLocateResponse.java new file mode 100644 index 00000000..6432a2b1 --- /dev/null +++ b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/pojo/vo/LbsLocateResponse.java @@ -0,0 +1,100 @@ +/** + * 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.thirdparty.lbs.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import org.springblade.thirdparty.lbs.constant.LbsConstant; + +import java.io.Serial; +import java.io.Serializable; + +/** + * LBS 定位/历史轨迹响应 + * + * @author Chill + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class LbsLocateResponse implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 响应代码,200 为正确 + */ + private Integer status; + + /** + * 兼容 code 字段(网关常返回 "1") + */ + private String code; + + /** + * 提示信息 + */ + private String msg; + + /** + * 提示信息(兼容 message) + */ + private String message; + + /** + * 定位/轨迹数据(数组,或 {list,total}) + */ + private JsonNode data; + + /** + * 是否成功 + */ + public boolean isSuccess() { + if (status != null && (status == LbsConstant.SUCCESS_STATUS || status == 1)) { + return true; + } + if (code == null) { + return false; + } + String normalized = code.trim(); + return LbsConstant.SUCCESS_STATUS_TEXT.equals(normalized) + || LbsConstant.SUCCESS_CODE.equals(normalized); + } + + /** + * 错误信息 + */ + public String errorMessage() { + if (msg != null && !msg.isBlank()) { + return msg; + } + if (message != null && !message.isBlank()) { + return message; + } + return "LBS接口调用失败"; + } +} diff --git a/blade-third-party-api/blade-lbs-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/blade-third-party-api/blade-lbs-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 00000000..85802bf0 --- /dev/null +++ b/blade-third-party-api/blade-lbs-api/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +org.springblade.thirdparty.lbs.config.ThirdPartyLbsAutoConfiguration diff --git a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/feign/IOAClient.java b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/feign/IOAClient.java index 9b36edd0..63233c5f 100644 --- a/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/feign/IOAClient.java +++ b/blade-third-party-api/blade-oa-api/src/main/java/org/springblade/thirdparty/oa/feign/IOAClient.java @@ -26,7 +26,7 @@ public interface IOAClient { * @param param * @return */ - @PostMapping("${thirdParty.oa.queryCompanyPageUrl:/api/hrm/resful/getHrmsubcompanyWithPage}") + @PostMapping("${thirdParty.oa.queryCompanyPageUrl:/gwzh/OA/OA_GET_COMPANY_LIST}") OAResponse queryCompanyPage(@RequestBody OASearch param); /** @@ -34,7 +34,7 @@ public interface IOAClient { * @param param * @return */ - @PostMapping("${thirdParty.oa.queryDepartmentPage:/api/hrm/resful/getHrmdepartmentWithPage}") + @PostMapping("${thirdParty.oa.queryDepartmentPageUrl:/gwzh/OA/OA_GET_DEPARTMENT_LIST}") OAResponse queryDepartmentPage(@RequestBody OASearch param); /** diff --git a/blade-third-party-api/pom.xml b/blade-third-party-api/pom.xml index ba27dd93..5e8c4552 100644 --- a/blade-third-party-api/pom.xml +++ b/blade-third-party-api/pom.xml @@ -13,6 +13,7 @@ ${project.artifactId} blade-oa-api + blade-lbs-api blade-mk-api blade-wps-api blade-ocr-api diff --git a/doc/nacos/blade-dev.yaml b/doc/nacos/blade-dev.yaml index a2763a44..0ff37d37 100644 --- a/doc/nacos/blade-dev.yaml +++ b/doc/nacos/blade-dev.yaml @@ -86,9 +86,16 @@ thirdParty: baseUrl: http://127.0.0.1:8080 oa: # OA开放接口地址 - baseUrl: http://127.0.0.1:8080 + baseUrl: http://172.16.204.83:38000 + queryCompanyPageUrl: /gwzh/OA/OA_GET_COMPANY_LIST + queryDepartmentPageUrl: /gwzh/OA/OA_GET_DEPARTMENT_LIST queryPersonPageUrl: /gwzh/OA/OA_GET_USER_LIST authorization: ${OA_AUTHORIZATION:${IAM_SSO_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=}} + lbs: + # LBS 网关地址(车辆实时定位) + baseUrl: http://172.16.204.83:38000 + locateUrl: /gwzh/LBS/LBS_LOCATE + authorization: ${LBS_AUTHORIZATION:${OA_AUTHORIZATION:${IAM_SSO_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=}}} track: # 轨迹开放接口地址 baseUrl: http://127.0.0.1:8080 diff --git a/doc/nacos/blade-prod.yaml b/doc/nacos/blade-prod.yaml index 7279339b..cfc872fa 100644 --- a/doc/nacos/blade-prod.yaml +++ b/doc/nacos/blade-prod.yaml @@ -58,9 +58,16 @@ thirdParty: baseUrl: http://127.0.0.1:8080 oa: # OA开放接口地址 - baseUrl: http://127.0.0.1:8080 + baseUrl: http://172.16.204.83:38000 + queryCompanyPageUrl: /gwzh/OA/OA_GET_COMPANY_LIST + queryDepartmentPageUrl: /gwzh/OA/OA_GET_DEPARTMENT_LIST queryPersonPageUrl: /gwzh/OA/OA_GET_USER_LIST authorization: ${OA_AUTHORIZATION:${IAM_SSO_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=}} + lbs: + # LBS 网关地址(车辆实时定位) + baseUrl: http://172.16.204.83:38000 + locateUrl: /gwzh/LBS/LBS_LOCATE + authorization: ${LBS_AUTHORIZATION:${OA_AUTHORIZATION:${IAM_SSO_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=}}} track: # 轨迹开放接口地址 baseUrl: http://127.0.0.1:8080 diff --git a/doc/nacos/third-party-api.yaml b/doc/nacos/third-party-api.yaml index baa53a09..e71be462 100644 --- a/doc/nacos/third-party-api.yaml +++ b/doc/nacos/third-party-api.yaml @@ -10,9 +10,16 @@ thirdParty: baseUrl: ${WPS_BASE_URL:http://127.0.0.1:8080} oa: # OA开放接口地址 - baseUrl: ${OA_BASE_URL:http://127.0.0.1:8080} + baseUrl: ${OA_BASE_URL:http://172.16.204.83:38000} + queryCompanyPageUrl: ${OA_QUERY_COMPANY_PAGE_URL:/gwzh/OA/OA_GET_COMPANY_LIST} + queryDepartmentPageUrl: ${OA_QUERY_DEPARTMENT_PAGE_URL:/gwzh/OA/OA_GET_DEPARTMENT_LIST} queryPersonPageUrl: ${OA_QUERY_PERSON_PAGE_URL:/gwzh/OA/OA_GET_USER_LIST} authorization: ${OA_AUTHORIZATION:${IAM_SSO_AUTHORIZATION:}} + lbs: + # LBS 网关地址(车辆实时定位) + baseUrl: ${LBS_BASE_URL:${OA_BASE_URL:http://172.16.204.83:38000}} + locateUrl: ${LBS_LOCATE_URL:/gwzh/LBS/LBS_LOCATE} + authorization: ${LBS_AUTHORIZATION:${OA_AUTHORIZATION:${IAM_SSO_AUTHORIZATION:}}} track: # 轨迹开放接口地址 baseUrl: ${TRACK_BASE_URL:http://127.0.0.1:8080} diff --git a/pom.xml b/pom.xml index ae708e05..c4d7d9b5 100644 --- a/pom.xml +++ b/pom.xml @@ -124,6 +124,11 @@ blade-oa-api ${revision} + + org.springblade + blade-lbs-api + ${revision} + org.springblade blade-open-api From 380fd117c5fd91524f6bf1740da96d3be9df1919 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Fri, 18 Sep 2026 22:12:43 +0800 Subject: [PATCH 15/55] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=AE=9A=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/controller/DeptController.java | 4 +- .../service/impl/OASyncServiceImpl.java | 8 +- .../controller/WaybillController.java | 2 +- .../service/impl/WaybillServiceImpl.java | 77 +++++++++++++------ .../src/main/resources/application-dev.yml | 1 + .../thirdparty/lbs/config/LbsProperties.java | 5 ++ .../thirdparty/lbs/feign/ILbsClient.java | 9 +++ .../lbs/pojo/vo/LbsLocateResponse.java | 24 ++++-- doc/nacos/blade-dev.yaml | 1 + doc/nacos/blade-prod.yaml | 1 + doc/nacos/third-party-api.yaml | 1 + 11 files changed, 96 insertions(+), 37 deletions(-) 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 e4075974..b2f2280d 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 @@ -182,7 +182,7 @@ public class DeptController extends BladeController { @Operation(summary = "同步OA公司") public R syncOaCompany( @RequestParam(defaultValue = "1") Integer current, - @RequestParam(defaultValue = "50") Integer size) { + @RequestParam(defaultValue = "20") Integer size) { return R.data(oaSyncService.syncCompanyPage(current, size)); } @@ -195,7 +195,7 @@ public class DeptController extends BladeController { @Operation(summary = "同步OA部门") public R syncOaDepartment( @RequestParam(defaultValue = "1") Integer current, - @RequestParam(defaultValue = "50") Integer size) { + @RequestParam(defaultValue = "20") Integer size) { return R.data(oaSyncService.syncDepartmentPage(current, 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 5e208c71..0792dc26 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 @@ -321,7 +321,7 @@ public class OASyncServiceImpl implements IOASyncService { */ private OaOrgSyncPageVO syncCompanyFromOaPage(int current, int size) { int pageNo = current < 1 ? 1 : current; - int pageSize = size < 1 ? 50 : Math.min(size, 200); + int pageSize = size < 1 ? 20 : Math.min(size, 200); OACompanySearch companySearch = buildCompanySearch(null); companySearch.setCurPage(pageNo); companySearch.setPageSize(pageSize); @@ -355,7 +355,7 @@ public class OASyncServiceImpl implements IOASyncService { */ private OaOrgSyncPageVO syncDepartmentFromOaPage(int current, int size) { int pageNo = current < 1 ? 1 : current; - int pageSize = size < 1 ? 50 : Math.min(size, 200); + int pageSize = size < 1 ? 20 : Math.min(size, 200); String subCompanyIds = getSubCompanyIds(); if (StringUtils.isEmpty(subCompanyIds)) { ComposeLogUtil.getLastLog().warn("未查询到子公司查询参数,跳过部门同步"); @@ -420,7 +420,7 @@ public class OASyncServiceImpl implements IOASyncService { private OACompanySearch buildCompanySearch(Date startTime) { OACompanySearch companySearch = new OACompanySearch(); companySearch.setCurPage(1); - companySearch.setPageSize(50); + companySearch.setPageSize(20); if (startTime != null) { companySearch.setModified(DateUtil.format(startTime, DateUtil.PATTERN_DATETIME)); } @@ -437,7 +437,7 @@ public class OASyncServiceImpl implements IOASyncService { private OADepartmentSearch buildDepartmentSearch(Date startTime, String subCompanyIds) { OADepartmentSearch departmentSearch = new OADepartmentSearch(); departmentSearch.setCurPage(1); - departmentSearch.setPageSize(50); + departmentSearch.setPageSize(20); departmentSearch.setSubcompanyid1(subCompanyIds); if (startTime != null) { departmentSearch.setModified(DateUtil.format(startTime, DateUtil.PATTERN_DATETIME)); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java index ba2c2413..68b83b13 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/WaybillController.java @@ -120,7 +120,7 @@ public class WaybillController extends BladeController { @PostMapping("/track") @ApiOperationSupport(order = 1) - @Operation(summary = "车辆历史轨迹", description = "按运单绑定车牌 + 日期区间调用 LBS_LOCATE") + @Operation(summary = "车辆历史轨迹", description = "按运单绑定车牌 + 日期区间调用 LBS_TRACK") public R track( @Parameter(description = "运单ID", required = true) @RequestParam Long id, @Parameter(description = "开始日期 YYYY-MM-DD", required = true) @RequestParam String startDate, diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java index b22ce136..42d99324 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java @@ -142,8 +142,8 @@ public class WaybillServiceImpl extends BaseServiceImpl public WaybillLocateVO locateVehicle(Long id) { Waybill waybill = requireWaybillWithVehicleNo(id, "无法实时定位"); String vehicleNo = waybill.getVehicleNo().trim(); - LbsLocateResponse response = invokeLbsLocate(id, vehicleNo, new LbsLocateRequest(vehicleNo), "实时定位"); - List> pointMaps = extractLbsPointMaps(response.getData()); + LbsLocateResponse response = invokeLbs(id, vehicleNo, new LbsLocateRequest(vehicleNo), false, "实时定位"); + List> pointMaps = extractLbsPointMaps(response); if (pointMaps.isEmpty()) { throw new ServiceException("暂无车辆实时定位数据"); } @@ -159,9 +159,9 @@ public class WaybillServiceImpl extends BaseServiceImpl if (LocalDate.parse(normalizedStart).isAfter(LocalDate.parse(normalizedEnd))) { throw new ServiceException("开始日期不能晚于结束日期"); } - LbsLocateResponse response = invokeLbsLocate(id, vehicleNo, - new LbsLocateRequest(vehicleNo, normalizedStart, normalizedEnd), "历史轨迹"); - List> pointMaps = extractLbsPointMaps(response.getData()); + LbsLocateResponse response = invokeLbs(id, vehicleNo, + new LbsLocateRequest(vehicleNo, normalizedStart, normalizedEnd), true, "历史轨迹"); + List> pointMaps = extractLbsPointMaps(response); WaybillTrackVO trackVO = new WaybillTrackVO(); trackVO.setWaybillId(id); trackVO.setVehicleNo(vehicleNo); @@ -195,10 +195,11 @@ public class WaybillServiceImpl extends BaseServiceImpl return waybill; } - private LbsLocateResponse invokeLbsLocate(Long waybillId, String vehicleNo, LbsLocateRequest request, String scene) { + private LbsLocateResponse invokeLbs(Long waybillId, String vehicleNo, LbsLocateRequest request, + boolean trackMode, String scene) { LbsLocateResponse response; try { - response = lbsClient.locate(request); + response = trackMode ? lbsClient.track(request) : lbsClient.locate(request); } catch (Exception exception) { log.error("调用LBS{}失败 waybillId={}, vehicleNo={}", scene, waybillId, vehicleNo, exception); throw new ServiceException("调用车辆" + scene + "接口失败"); @@ -223,29 +224,55 @@ public class WaybillServiceImpl extends BaseServiceImpl } /** - * 提取 LBS 轨迹点列表,兼容 data 数组 或 data.list 数组 + * 提取 LBS 轨迹点:优先 list,其次 obj,再兼容 data / data.list / data.obj */ - private List> extractLbsPointMaps(JsonNode dataNode) { + private List> extractLbsPointMaps(LbsLocateResponse response) { List> pointMaps = new ArrayList<>(); - if (dataNode == null || dataNode.isNull()) { + if (response == null) { return pointMaps; } - JsonNode listNode = dataNode; - if (dataNode.isObject() && dataNode.has("list")) { - listNode = dataNode.get("list"); + appendLbsNodes(pointMaps, response.getList()); + if (!pointMaps.isEmpty()) { + return pointMaps; } - if (listNode != null && listNode.isArray()) { - for (JsonNode item : listNode) { + appendLbsNodes(pointMaps, response.getObj()); + if (!pointMaps.isEmpty()) { + return pointMaps; + } + JsonNode dataNode = response.getData(); + if (dataNode != null && !dataNode.isNull()) { + if (dataNode.isObject() && dataNode.has("list")) { + appendLbsNodes(pointMaps, dataNode.get("list")); + if (!pointMaps.isEmpty()) { + return pointMaps; + } + } + if (dataNode.isObject() && dataNode.has("obj")) { + appendLbsNodes(pointMaps, dataNode.get("obj")); + if (!pointMaps.isEmpty()) { + return pointMaps; + } + } + appendLbsNodes(pointMaps, dataNode); + } + return pointMaps; + } + + private void appendLbsNodes(List> pointMaps, JsonNode node) { + if (node == null || node.isNull()) { + return; + } + if (node.isArray()) { + for (JsonNode item : node) { if (item != null && item.isObject()) { pointMaps.add(JsonUtil.toMap(item.toString())); } } - return pointMaps; + return; } - if (dataNode.isObject()) { - pointMaps.add(JsonUtil.toMap(dataNode.toString())); + if (node.isObject()) { + pointMaps.add(JsonUtil.toMap(node.toString())); } - return pointMaps; } /** @@ -271,10 +298,10 @@ public class WaybillServiceImpl extends BaseServiceImpl target.setLongitude(firstDecimal(pointMap, "longitude", "lon", "lng", "x", "jd", "Longitude", "LON", "LNG", "JD", "X")); target.setLatitude(firstDecimal(pointMap, "latitude", "lat", "y", "wd", "Latitude", "LAT", "WD", "Y")); target.setAddress(firstText(pointMap, "address", "addr", "adr", "wz", "Address", "ADDR", "ADR")); - target.setLocateTime(firstText(pointMap, "locationTime", "pos_time", "locateTime", "gpsTime", "gpstime", "time", "gpszdsj", "GPSTime", "Time")); - target.setSpeed(firstText(pointMap, "speed", "v", "sd", "Speed", "SD", "V")); + target.setLocateTime(firstText(pointMap, "utc", "locationTime", "pos_time", "locateTime", "gpsTime", "gpstime", "time", "gpszdsj", "GPSTime", "Time")); + target.setSpeed(firstText(pointMap, "spd", "speed", "v", "sd", "Speed", "SD", "V")); target.setDirection(firstText(pointMap, "direct", "direction", "h", "fx", "Direction", "FX", "course", "H")); - String responseVehicleNo = firstText(pointMap, "cph", "vehicleNo", "plateNo", "CPH"); + String responseVehicleNo = firstText(pointMap, "vno", "cph", "vehicleNo", "plateNo", "CPH", "VNO"); if (Func.isNotEmpty(responseVehicleNo)) { target.setVehicleNo(responseVehicleNo); } @@ -284,10 +311,10 @@ public class WaybillServiceImpl extends BaseServiceImpl target.setLongitude(firstDecimal(pointMap, "longitude", "lon", "lng", "x", "jd", "Longitude", "LON", "LNG", "JD", "X")); target.setLatitude(firstDecimal(pointMap, "latitude", "lat", "y", "wd", "Latitude", "LAT", "WD", "Y")); target.setAddress(firstText(pointMap, "address", "addr", "adr", "wz", "Address", "ADDR", "ADR")); - target.setLocateTime(firstText(pointMap, "locationTime", "pos_time", "locateTime", "gpsTime", "gpstime", "time", "gpszdsj", "GPSTime", "Time")); - target.setSpeed(firstText(pointMap, "speed", "v", "sd", "Speed", "SD", "V")); + target.setLocateTime(firstText(pointMap, "utc", "locationTime", "pos_time", "locateTime", "gpsTime", "gpstime", "time", "gpszdsj", "GPSTime", "Time")); + target.setSpeed(firstText(pointMap, "spd", "speed", "v", "sd", "Speed", "SD", "V")); target.setDirection(firstText(pointMap, "direct", "direction", "h", "fx", "Direction", "FX", "course", "H")); - String responseVehicleNo = firstText(pointMap, "cph", "vehicleNo", "plateNo", "CPH"); + String responseVehicleNo = firstText(pointMap, "vno", "cph", "vehicleNo", "plateNo", "CPH", "VNO"); if (Func.isNotEmpty(responseVehicleNo)) { target.setVehicleNo(responseVehicleNo); } diff --git a/blade-service/blade-transport/src/main/resources/application-dev.yml b/blade-service/blade-transport/src/main/resources/application-dev.yml index 29ae464e..ebffbff7 100644 --- a/blade-service/blade-transport/src/main/resources/application-dev.yml +++ b/blade-service/blade-transport/src/main/resources/application-dev.yml @@ -14,4 +14,5 @@ thirdParty: lbs: baseUrl: ${LBS_BASE_URL:http://172.16.204.83:38000} locateUrl: ${LBS_LOCATE_URL:/gwzh/LBS/LBS_LOCATE} + trackUrl: ${LBS_TRACK_URL:/gwzh/LBS/LBS_TRACK} authorization: ${LBS_AUTHORIZATION:${OA_AUTHORIZATION:${IAM_SSO_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=}}} diff --git a/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/config/LbsProperties.java b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/config/LbsProperties.java index 0e8b780d..5b4f3553 100644 --- a/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/config/LbsProperties.java +++ b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/config/LbsProperties.java @@ -47,6 +47,11 @@ public class LbsProperties { */ private String locateUrl = "/gwzh/LBS/LBS_LOCATE"; + /** + * 车辆历史轨迹路径 + */ + private String trackUrl = "/gwzh/LBS/LBS_TRACK"; + /** * gwzh 网关 Authorization(Basic),与 OA 人员接口一致 */ diff --git a/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/feign/ILbsClient.java b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/feign/ILbsClient.java index e19a1486..7db03f23 100644 --- a/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/feign/ILbsClient.java +++ b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/feign/ILbsClient.java @@ -48,4 +48,13 @@ public interface ILbsClient { */ @PostMapping("${thirdParty.lbs.locateUrl:/gwzh/LBS/LBS_LOCATE}") LbsLocateResponse locate(@RequestBody LbsLocateRequest request); + + /** + * 车辆历史轨迹 + * + * @param request 请求(cph、start_date、end_date) + * @return 轨迹结果 + */ + @PostMapping("${thirdParty.lbs.trackUrl:/gwzh/LBS/LBS_TRACK}") + LbsLocateResponse track(@RequestBody LbsLocateRequest request); } diff --git a/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/pojo/vo/LbsLocateResponse.java b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/pojo/vo/LbsLocateResponse.java index 6432a2b1..be72bc6c 100644 --- a/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/pojo/vo/LbsLocateResponse.java +++ b/blade-third-party-api/blade-lbs-api/src/main/java/org/springblade/thirdparty/lbs/pojo/vo/LbsLocateResponse.java @@ -34,7 +34,10 @@ import java.io.Serial; import java.io.Serializable; /** - * LBS 定位/历史轨迹响应 + * LBS 定位/历史轨迹响应。 + *

+ * 实际网关返回示例: + * {@code {"code":200,"obj":{...},"list":null,"msg":"OK"}} * * @author Chill */ @@ -51,7 +54,7 @@ public class LbsLocateResponse implements Serializable { private Integer status; /** - * 兼容 code 字段(网关常返回 "1") + * 响应代码(网关返回 200 或 "1") */ private String code; @@ -66,7 +69,17 @@ public class LbsLocateResponse implements Serializable { private String message; /** - * 定位/轨迹数据(数组,或 {list,total}) + * 单点定位对象 + */ + private JsonNode obj; + + /** + * 历史轨迹点列表 + */ + private JsonNode list; + + /** + * 兼容旧字段 data */ private JsonNode data; @@ -89,10 +102,11 @@ public class LbsLocateResponse implements Serializable { * 错误信息 */ public String errorMessage() { - if (msg != null && !msg.isBlank()) { + if (msg != null && !msg.isBlank() && !"OK".equalsIgnoreCase(msg) && !"Success".equalsIgnoreCase(msg)) { return msg; } - if (message != null && !message.isBlank()) { + if (message != null && !message.isBlank() + && !"OK".equalsIgnoreCase(message) && !"Success".equalsIgnoreCase(message)) { return message; } return "LBS接口调用失败"; diff --git a/doc/nacos/blade-dev.yaml b/doc/nacos/blade-dev.yaml index 0ff37d37..d6b08211 100644 --- a/doc/nacos/blade-dev.yaml +++ b/doc/nacos/blade-dev.yaml @@ -95,6 +95,7 @@ thirdParty: # LBS 网关地址(车辆实时定位) baseUrl: http://172.16.204.83:38000 locateUrl: /gwzh/LBS/LBS_LOCATE + trackUrl: /gwzh/LBS/LBS_TRACK authorization: ${LBS_AUTHORIZATION:${OA_AUTHORIZATION:${IAM_SSO_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=}}} track: # 轨迹开放接口地址 diff --git a/doc/nacos/blade-prod.yaml b/doc/nacos/blade-prod.yaml index cfc872fa..407b09ce 100644 --- a/doc/nacos/blade-prod.yaml +++ b/doc/nacos/blade-prod.yaml @@ -67,6 +67,7 @@ thirdParty: # LBS 网关地址(车辆实时定位) baseUrl: http://172.16.204.83:38000 locateUrl: /gwzh/LBS/LBS_LOCATE + trackUrl: /gwzh/LBS/LBS_TRACK authorization: ${LBS_AUTHORIZATION:${OA_AUTHORIZATION:${IAM_SSO_AUTHORIZATION:Z3d6aF90bXMtOVJJU0RVN1U6YW0yYkcwWnBJZ0RQZmtrSjNaZkZjUDBSaGFuSEtxQng=}}} track: # 轨迹开放接口地址 diff --git a/doc/nacos/third-party-api.yaml b/doc/nacos/third-party-api.yaml index e71be462..11e17b31 100644 --- a/doc/nacos/third-party-api.yaml +++ b/doc/nacos/third-party-api.yaml @@ -19,6 +19,7 @@ thirdParty: # LBS 网关地址(车辆实时定位) baseUrl: ${LBS_BASE_URL:${OA_BASE_URL:http://172.16.204.83:38000}} locateUrl: ${LBS_LOCATE_URL:/gwzh/LBS/LBS_LOCATE} + trackUrl: ${LBS_TRACK_URL:/gwzh/LBS/LBS_TRACK} authorization: ${LBS_AUTHORIZATION:${OA_AUTHORIZATION:${IAM_SSO_AUTHORIZATION:}}} track: # 轨迹开放接口地址 From cc33bc5ef26d1508c685756a4d8d4ea663295709 Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Sun, 20 Sep 2026 06:27:33 +0800 Subject: [PATCH 16/55] =?UTF-8?q?:bug:=20=E4=BF=AE=E5=A4=8D=E6=B8=AF?= =?UTF-8?q?=E5=8F=A3=E7=A0=81=E5=A4=B4=E6=89=B9=E9=87=8F=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E7=9A=84=E4=B8=80=E4=BA=8C=E7=BA=A7=E9=A1=BA=E5=BA=8F=E4=B8=8E?= =?UTF-8?q?=E5=85=A8=E9=87=8F=E5=9B=9E=E6=BB=9A=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 对应 BUG.xlsx 序号 1 与序号 2。 序号 1(同表混装一二级数据): - 改为两阶段导入,先落库港口(一级)再落库码头(二级), 消除对 Excel 行序的依赖;码头优先引用本批次新增的港口 序号 2(全失败即整批回滚): - 逐行收集错误,只要有一行失败就抛异常触发整批回滚,不再部分成功 - 失败明细按【原表全部行】返回,未出错的行不标注,用户只需改错行 附带修复: - 父港口找不到时改抛专用 ParentPortNotFoundException: 上级港口声明在本文件里、但那一行自身校验失败时,不再连带给码头行报错, 避免用户看到"两行都错"的假象;父港口确实漏填时仍照常报错 - 批内重复编码在落库前预检并定位到行,两行都标注原因 - 父港口区域信息为空(历史数据)时保留码头自身填写的值, 此前会被覆盖成空值导致码头无法导入 --- .../controller/PortTerminalController.java | 17 +- .../system/excel/ImportFailureException.java | 60 +++++ .../service/impl/PortTerminalServiceImpl.java | 247 +++++++++++++++++- 3 files changed, 310 insertions(+), 14 deletions(-) create mode 100644 blade-service/blade-system/src/main/java/org/springblade/system/excel/ImportFailureException.java diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/controller/PortTerminalController.java b/blade-service/blade-system/src/main/java/org/springblade/system/controller/PortTerminalController.java index a34396b7..8d7a0dd4 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/controller/PortTerminalController.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/controller/PortTerminalController.java @@ -43,6 +43,8 @@ import org.springblade.core.tenant.annotation.NonDS; import org.springblade.core.tool.api.R; import org.springblade.core.tool.utils.DateUtil; import org.springblade.core.tool.utils.Func; +import org.springblade.common.excel.ImportFailureExcelUtil; +import org.springblade.system.excel.ImportFailureException; import org.springblade.system.excel.PortTerminalExcel; import org.springblade.system.excel.PortTerminalImporter; import org.springblade.system.pojo.entity.PortTerminal; @@ -167,14 +169,23 @@ public class PortTerminalController extends BladeController { if (!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")) { return R.fail("请上传 .xls,.xlsx 标准格式文件"); } - List failureList = portTerminalService.importPortTerminal(ExcelUtil.read(file, PortTerminalExcel.class)); - if (Func.isNotEmpty(failureList)) { - org.springblade.common.excel.ImportFailureExcelUtil.export(response, "港口码头主数据导入失败明细" + DateUtil.time(), "导入失败明细", failureList, PortTerminalExcel.class); + try { + portTerminalService.importPortTerminal(ExcelUtil.read(file, PortTerminalExcel.class)); + } catch (ImportFailureException exception) { + // 全失败即整批回滚,导出原表全部数据并标注错误,用户修正后重新导入。 + exportFailure(response, exception.getFailureList()); return null; } return R.success("操作成功"); } + /** + * 导出导入失败明细,内容为原表全部数据并在末尾追加失败原因列。 + */ + private void exportFailure(HttpServletResponse response, List failureList) { + ImportFailureExcelUtil.export(response, "港口码头主数据导入失败明细" + DateUtil.time(), "导入失败明细", failureList, PortTerminalExcel.class); + } + /** * 导出港口码头主数据 */ diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/excel/ImportFailureException.java b/blade-service/blade-system/src/main/java/org/springblade/system/excel/ImportFailureException.java new file mode 100644 index 00000000..0277ec01 --- /dev/null +++ b/blade-service/blade-system/src/main/java/org/springblade/system/excel/ImportFailureException.java @@ -0,0 +1,60 @@ +/** + * 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.excel; + +import org.springblade.core.log.exception.ServiceException; + +import java.io.Serial; +import java.util.List; + +/** + * 导入失败异常,携带失败明细用于导出原表并标注错误。 + *

+ * 批量导入采用「全失败即整批回滚」语义:任一行校验失败都会抛出本异常触发事务回滚, + * 失败明细在抛异常前已收集完毕,因此回滚不影响明细的内容。 + * + * @author Chill + */ +public class ImportFailureException extends ServiceException { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 失败明细,包含原表全部数据,错误行已标注错误原因。 + */ + private final transient List failureList; + + public ImportFailureException(List failureList) { + super("导入失败,已回滚全部数据"); + this.failureList = failureList; + } + + public List getFailureList() { + return failureList; + } + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/PortTerminalServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/PortTerminalServiceImpl.java index 07aaf8ad..0551c2de 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/PortTerminalServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/PortTerminalServiceImpl.java @@ -34,6 +34,7 @@ import org.springblade.core.log.exception.ServiceException; import org.springblade.core.mp.base.BaseServiceImpl; import org.springblade.core.tool.utils.BeanUtil; import org.springblade.core.tool.utils.Func; +import org.springblade.system.excel.ImportFailureException; import org.springblade.system.excel.PortTerminalExcel; import org.springblade.system.mapper.PortTerminalMapper; import org.springblade.system.pojo.entity.PortTerminal; @@ -44,12 +45,18 @@ import org.springblade.system.service.IRegionService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.io.Serial; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.regex.Pattern; /** @@ -133,8 +140,88 @@ public class PortTerminalServiceImpl extends BaseServiceImpl errorList = new ArrayList<>(); + // 全量校验:任何一行失败都整批回滚,因此先收集所有错误再统一抛出。 + List failureList = new ArrayList<>(); + // 批内已占用的编码,用于识别文件内重复数据。 + Map occupiedCodeMap = new HashMap<>(); + // 本文件声明的一级数据(港口)编码集合。 + // 用于区分两种情况:父港口"漏填"与"父行自身校验失败"。 + // 后者说明父行已被标红、用户只需改那一行,因此不再连带给码头行报错。 + Set declaredPortCodeSet = new HashSet<>(); + // 两阶段导入:先落库一级数据(港口),再落库二级数据(码头),消除对 Excel 行序的依赖。 + Map batchPortMap = new LinkedHashMap<>(); + List portIndexList = new ArrayList<>(); + List terminalIndexList = new ArrayList<>(); + splitByCategory(data, portIndexList, terminalIndexList, occupiedCodeMap, declaredPortCodeSet, failureList); + importPorts(data, portIndexList, batchPortMap, failureList); + importTerminals(data, terminalIndexList, batchPortMap, declaredPortCodeSet, failureList); + if (Func.isNotEmpty(failureList)) { + // 抛出携带失败明细的异常,触发事务回滚。 + // 明细为原表全部行(未出错行仅无错误原因),用户可对照原表修正后重新导入。 + throw new ImportFailureException(data); + } + return failureList; + } + + /** + * 按类型拆分数据行,并完成与阶段无关的基础校验(类型、编码格式、批内重复)。 + * + * @param data 导入数据 + * @param portIndexList 港口行下标 + * @param terminalIndexList 码头行下标 + * @param occupiedCodeMap 编码占用情况,值为首次出现的下标 + * @param declaredPortCodeSet 本文件声明的港口编码(含编码格式不合法的行) + * @param failureList 失败明细 + */ + private void splitByCategory(List data, List portIndexList, List terminalIndexList, + Map occupiedCodeMap, Set declaredPortCodeSet, + List failureList) { for (int index = 0; index < data.size(); index++) { + PortTerminalExcel excel = data.get(index); + String category = trimToEmpty(excel.getCategory()); + boolean categoryValid = CATEGORY_PORT.equals(category) || CATEGORY_TERMINAL.equals(category); + if (!categoryValid) { + failureList.add(buildFailure(data, index, "类型只能为港口或码头")); + continue; + } + // 只要类型是港口就登记为"本文件已声明",即使它的编码格式不合法: + // 这样引用它的码头行不会被连带报错,用户只需修正这一个港口行。 + if (CATEGORY_PORT.equals(category)) { + String declaredCode = trimToEmpty(excel.getCode()).toUpperCase(Locale.ROOT); + if (Func.isNotEmpty(declaredCode)) { + declaredPortCodeSet.add(declaredCode); + } + } + String code = trimToEmpty(excel.getCode()).toUpperCase(Locale.ROOT); + boolean codeValid = CATEGORY_PORT.equals(category) + ? PORT_CODE_PATTERN.matcher(code).matches() + : TERMINAL_CODE_PATTERN.matcher(code).matches(); + if (!codeValid) { + String message = CATEGORY_PORT.equals(category) ? "港口编码为5位大写字母" : "码头编码格式为港口编码-码头标识"; + failureList.add(buildFailure(data, index, message)); + continue; + } + // 批内重复:两行都需要标记,由用户决定保留哪一行。 + if (occupiedCodeMap.containsKey(code)) { + markFailure(data, occupiedCodeMap.get(code), "编码 " + code + " 在文件中重复出现"); + failureList.add(buildFailure(data, index, "编码 " + code + " 在文件中重复出现")); + continue; + } + occupiedCodeMap.put(code, index); + if (CATEGORY_PORT.equals(category)) { + portIndexList.add(index); + } else { + terminalIndexList.add(index); + } + } + } + + /** + * 阶段一:导入港口(一级数据),同时登记到批次内存映射,供码头引用。 + */ + private void importPorts(List data, List portIndexList, Map batchPortMap, + List failureList) { + for (Integer index : portIndexList) { PortTerminalExcel excel = data.get(index); try { PortTerminal portTerminal = Objects.requireNonNull(BeanUtil.copyProperties(excel, PortTerminal.class)); @@ -143,13 +230,105 @@ public class PortTerminalServiceImpl extends BaseServiceImpl data, List terminalIndexList, Map batchPortMap, + Set declaredPortCodeSet, List failureList) { + for (Integer index : terminalIndexList) { + PortTerminalExcel excel = data.get(index); + try { + PortTerminal portTerminal = Objects.requireNonNull(BeanUtil.copyProperties(excel, PortTerminal.class)); + portTerminal.setDataSource(SOURCE_BATCH); + portTerminal.setStatus(STATUS_ENABLED); + prepareTerminal(portTerminal, batchPortMap); + validate(portTerminal); + save(portTerminal); + } catch (ParentPortNotFoundException exception) { + // 上级港口就声明在本文件里,只是那一行自己校验失败(已被标红)。 + // 此时码头行本身没有问题,不再连带报错,避免用户看到"两行都错"的假象。 + if (!declaredPortCodeSet.contains(trimToEmpty(excel.getParentCode()).toUpperCase(Locale.ROOT))) { + failureList.add(buildFailure(data, index, resolveMessage(exception))); + } + } catch (Exception exception) { + failureList.add(buildFailure(data, index, resolveMessage(exception))); + } + } + } + + /** + * 码头导入:上级港口优先匹配本批次新增的港口,其次由 prepare 回查数据库。 + */ + private void prepareTerminal(PortTerminal portTerminal, Map batchPortMap) { + String parentCode = trimToEmpty(portTerminal.getParentCode()).toUpperCase(Locale.ROOT); + PortTerminal parent = Func.isEmpty(parentCode) ? null : batchPortMap.get(parentCode); + if (Func.isNotEmpty(parent)) { + applyParent(portTerminal, parent); + // 父信息已由本批次港口回填,无需再回查数据库。 + prepare(portTerminal, SOURCE_BATCH, true); + return; + } + portTerminal.setParentCode(parentCode); + prepare(portTerminal, SOURCE_BATCH); + } + + /** + * 将上级港口信息回填到码头,保证码头不会出现没有港口的数据。 + */ + private void applyParent(PortTerminal portTerminal, PortTerminal parent) { + portTerminal.setParentId(parent.getId()); + portTerminal.setParentCode(parent.getCode()); + portTerminal.setParentName(parent.getName()); + // 与回查数据库保持一致:父港口区域信息为空时保留码头自身填写的值。 + if (Func.isNotEmpty(parent.getCountry())) { + portTerminal.setCountry(parent.getCountry()); + } + if (Func.isNotEmpty(parent.getCity())) { + portTerminal.setCity(parent.getCity()); + } + if (Func.isNotEmpty(parent.getDistrictCode())) { + portTerminal.setDistrictCode(parent.getDistrictCode()); + portTerminal.setRegionCode(parent.getDistrictCode()); + } else if (Func.isNotEmpty(portTerminal.getDistrictCode())) { + portTerminal.setRegionCode(portTerminal.getDistrictCode()); + } + if (Func.isNotEmpty(parent.getDistrictName())) { + portTerminal.setDistrictName(parent.getDistrictName()); + } + } + + /** + * 构造失败明细,行号按 Excel 中的实际行号(表头占第 1 行)推算。 + */ + private PortTerminalExcel buildFailure(List data, int index, String message) { + PortTerminalExcel excel = data.get(index); + markFailure(data, index, message); + return excel; + } + + /** + * 仅标注错误原因,不重复加入失败明细集合。 + */ + private void markFailure(List data, int index, String message) { + if (index >= 0 && index < data.size()) { + data.get(index).setErrorMessage("第" + (index + 2) + "行:" + message); + } + } + + /** + * 解析异常信息,非业务异常统一提示导入失败。 + */ + private String resolveMessage(Exception exception) { + return exception instanceof ServiceException ? exception.getMessage() : "导入失败"; } @Override @@ -174,6 +353,17 @@ public class PortTerminalServiceImpl extends BaseServiceImpl + * 与普通业务异常区分开,是因为调用方需要判断:这个父港口到底是"用户漏填了", + * 还是"父港口那一行就在本文件里、只是它自己校验失败"。后者不该连带给码头行报错。 + * + * @author Chill + */ + private static class ParentPortNotFoundException extends ServiceException { + + @Serial + private static final long serialVersionUID = 1L; + + ParentPortNotFoundException(String message) { + super(message); + } + + } + } From a138f3b916d0825d7a66b8fda62afd21430babf4 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Sun, 20 Sep 2026 13:16:44 +0800 Subject: [PATCH 17/55] =?UTF-8?q?=E8=B0=83=E8=AF=95mk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gateway/provider/AuthProvider.java | 2 + .../system/pojo/entity/MeasurementUnit.java | 6 ++ .../controller/BusinessProcessController.java | 16 +++- .../service/IBusinessProcessService.java | 9 +++ .../impl/BusinessProcessServiceImpl.java | 73 +++++++++++++++++ .../system/mapper/MeasurementUnitMapper.xml | 6 ++ .../impl/MeasurementUnitServiceImpl.java | 21 +++++ .../CustomerArchivePublicController.java | 81 +++++++++++++++++++ .../service/ICustomerArchiveService.java | 17 ++++ .../impl/CustomerArchiveServiceImpl.java | 74 ++++++++++++----- doc/nacos/blade.yaml | 2 + doc/sql/bladex/bladex.mysql.all.create.sql | 2 + doc/sql/transport/blade_measurement_unit.sql | 2 + .../blade_measurement_unit_code_20260919.sql | 11 +++ 14 files changed, 296 insertions(+), 26 deletions(-) create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java create mode 100644 doc/sql/transport/blade_measurement_unit_code_20260919.sql diff --git a/blade-gateway/src/main/java/org/springblade/gateway/provider/AuthProvider.java b/blade-gateway/src/main/java/org/springblade/gateway/provider/AuthProvider.java index a259f7ba..c67671be 100644 --- a/blade-gateway/src/main/java/org/springblade/gateway/provider/AuthProvider.java +++ b/blade-gateway/src/main/java/org/springblade/gateway/provider/AuthProvider.java @@ -63,6 +63,8 @@ public class AuthProvider { DEFAULT_SKIP_URL.add("/manager/check-upload"); DEFAULT_SKIP_URL.add("/assets/**"); DEFAULT_SKIP_URL.add("/iam/sso/token/**"); + DEFAULT_SKIP_URL.add("/blade-transport/customer-archive/public/**"); + DEFAULT_SKIP_URL.add("/customer-archive/public/**"); } /** diff --git a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/MeasurementUnit.java b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/MeasurementUnit.java index ad80a1e9..7cdde906 100644 --- a/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/MeasurementUnit.java +++ b/blade-service-api/blade-system-api/src/main/java/org/springblade/system/pojo/entity/MeasurementUnit.java @@ -43,6 +43,12 @@ public class MeasurementUnit extends BaseEntity { @Serial private static final long serialVersionUID = 1L; + /** + * 计量单位编码 + */ + @Schema(description = "计量单位编码") + private String unitCode; + /** * 计量单位 */ diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/controller/BusinessProcessController.java b/blade-service/blade-system/src/main/java/org/springblade/process/controller/BusinessProcessController.java index c9765695..8241b218 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/process/controller/BusinessProcessController.java +++ b/blade-service/blade-system/src/main/java/org/springblade/process/controller/BusinessProcessController.java @@ -17,6 +17,7 @@ import org.springblade.process.pojo.dto.ApprovalDTO; import org.springblade.process.pojo.vo.ApprovalVO; import org.springblade.process.pojo.vo.ProcessApprovedRecordVO; import org.springblade.process.service.IBusinessProcessService; +import org.springblade.thirdparty.mk.pojo.dto.MKProcessCreateDTO; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -52,29 +53,36 @@ public class BusinessProcessController extends BladeController { return R.data(pages); } - @GetMapping("/isEditView") + @PostMapping("/processSubmit") @ApiOperationSupport(order = 2) + @Operation(summary = "提交MK审核流", description = "调用mk processSubmit,传入templateCode/submitIdentity/formInstanceId") + public R processSubmit(@Validated @RequestBody MKProcessCreateDTO param) { + return R.data(businessProcessService.processSubmit(param)); + } + + @GetMapping("/isEditView") + @ApiOperationSupport(order = 3) @Operation(summary = "是否编辑页", description = "传入业务id") public R isEditView(@Valid @NotBlank(message = "业务id不能为空") String bizId) { return R.data(businessProcessService.isEditView(bizId)); } @GetMapping("/getMKApprovalUrl") - @ApiOperationSupport(order = 3) + @ApiOperationSupport(order = 4) @Operation(summary = "获取mk审批页链接", description = "传入业务id或流程实例id") public R getMKApprovalUrl(String bizId, String processInstanceId) { return R.data(businessProcessService.getMKApprovalUrl(bizId, processInstanceId)); } @GetMapping("/getApprovedRecords") - @ApiOperationSupport(order = 4) + @ApiOperationSupport(order = 5) @Operation(summary = "查询审批记录", description = "传入业务id或流程实例id") public R> getApprovedRecords(String bizId, String processInstanceId) { return R.data(businessProcessService.queryApprovedRecords(bizId, processInstanceId)); } @GetMapping("/downloadFile") - @ApiOperationSupport(order = 5) + @ApiOperationSupport(order = 6) @Operation(summary = "下载附件", description = "传入附件id") public void downloadFile(HttpServletResponse response, @Valid @NotBlank(message = "附件id不能为空") String fileId) { businessProcessService.downloadFile(response, fileId); diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java b/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java index 5e3e2ff8..a577c097 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java @@ -6,6 +6,7 @@ import jakarta.servlet.http.HttpServletResponse; import org.springblade.process.pojo.dto.*; import org.springblade.process.pojo.entity.BusinessProcess; import org.springblade.process.pojo.vo.*; +import org.springblade.thirdparty.mk.pojo.dto.MKProcessCreateDTO; import java.util.List; @@ -25,6 +26,14 @@ public interface IBusinessProcessService extends IService { */ BusinessProcessVO submitBusinessProcess(BusinessProcessSubmitDTO param); + /** + * 直接调用 MK processSubmit 提交流程 + * + * @param param MK 流程创建参数 + * @return 流程实例 id + */ + String processSubmit(MKProcessCreateDTO param); + /** * 修改业务流程状态 * @param param diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java index 50984d30..1317fdf2 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java @@ -24,6 +24,8 @@ import org.springblade.process.pojo.entity.BusinessProcess; import org.springblade.process.pojo.enums.TodoStatus; import org.springblade.process.pojo.vo.*; import org.springblade.process.service.IBusinessProcessService; +import org.springblade.system.pojo.entity.User; +import org.springblade.system.service.IUserService; import org.springblade.thirdparty.mk.config.MKProperties; import org.springblade.thirdparty.mk.constant.MKConstant; import org.springblade.thirdparty.mk.constant.MKDoc; @@ -61,6 +63,7 @@ public class BusinessProcessServiceImpl extends ServiceImpllambdaQuery() + .eq(BusinessProcess::getBizId, bizId) + ); + if (businessProcess == null) { + businessProcess = new BusinessProcess(); + businessProcess.setBizId(bizId); + businessProcess.setProcessType(param.getTemplateCode()); + businessProcess.setSubject(param.getSubject()); + businessProcess.setPromoterId(AuthUtil.getUserId()); + businessProcess.setPromoterName(AuthUtil.getNickName()); + businessProcess.setPromoterLoginName(loginName); + businessProcess.setSubmitTime(new Date()); + } else { + businessProcess.setProcessType(param.getTemplateCode()); + if (StringUtil.isNotBlank(param.getSubject())) { + businessProcess.setSubject(param.getSubject()); + } + businessProcess.setPromoterLoginName(loginName); + if (businessProcess.getSubmitTime() == null) { + businessProcess.setSubmitTime(new Date()); + } + } + businessProcess.setProcessInstanceId(processInstanceId); + businessProcess.setApproveStatus(ApproveStatusEnum.APPROVING.getValue()); + this.saveOrUpdate(businessProcess); + return processInstanceId; + } + + /** + * 从当前登录用户实体读取真实手机号(绕过接口返回脱敏) + */ + private String resolveCurrentUserPhone() { + Long userId = AuthUtil.getUserId(); + if (userId != null) { + User user = userService.getById(userId); + if (user != null && StringUtil.isNotBlank(user.getPhone()) && !user.getPhone().contains("*")) { + return user.getPhone().trim(); + } + if (user != null && StringUtil.isNotBlank(user.getAccount()) && user.getAccount().matches("^1\\d{10}$")) { + return user.getAccount().trim(); + } + } + String account = AuthUtil.getUserAccount(); + if (StringUtil.isNotBlank(account) && account.matches("^1\\d{10}$")) { + return account.trim(); + } + return null; + } + @Transactional(rollbackFor = Exception.class) @Override public String updateBusinessProcessStatus(BusinessProcessUpdateDTO param) { diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/MeasurementUnitMapper.xml b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/MeasurementUnitMapper.xml index 387f1a27..10fbb903 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/MeasurementUnitMapper.xml +++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/MeasurementUnitMapper.xml @@ -13,6 +13,7 @@ + @@ -30,6 +31,7 @@ mmu.update_time, mmu.status, mmu.is_deleted, + mmu.unit_code, mmu.unit_name, mmu.dimension, mmu.remark @@ -39,6 +41,10 @@ LEFT JOIN blade_user uu ON uu.id = mmu.update_user WHERE mmu.is_deleted = 0 + + + AND mmu.unit_code LIKE #{unitCodeLike} + AND mmu.unit_name LIKE #{unitNameLike} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MeasurementUnitServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MeasurementUnitServiceImpl.java index 615bb673..df18f4f4 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MeasurementUnitServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/MeasurementUnitServiceImpl.java @@ -46,6 +46,7 @@ public class MeasurementUnitServiceImpl extends BaseServiceImpl UNIT_CODE_MAX_LENGTH) { + throw new ServiceException("计量单位编码不能超过50字"); + } if (Func.isEmpty(measurementUnit.getUnitName())) { throw new ServiceException("计量单位不能为空"); } @@ -115,9 +123,22 @@ public class MeasurementUnitServiceImpl extends BaseServiceImpl queryWrapper = Wrappers.lambdaQuery() + .eq(MeasurementUnit::getUnitCode, measurementUnit.getUnitCode()) + .eq(MeasurementUnit::getIsDeleted, 0); + if (Func.isNotEmpty(measurementUnit.getId())) { + queryWrapper.ne(MeasurementUnit::getId, measurementUnit.getId()); + } + if (count(queryWrapper) > 0L) { + throw new ServiceException("该计量单位编码已存在"); + } + } + private void validateUniqueUnitName(MeasurementUnit measurementUnit) { LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery() .eq(MeasurementUnit::getUnitName, measurementUnit.getUnitName()) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java new file mode 100644 index 00000000..b2094c38 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java @@ -0,0 +1,81 @@ +/** + * 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. + *

+ * Author: Chill Zhuang (bladejava@qq.com) + */ +package org.springblade.transport.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import org.springblade.core.mp.support.Condition; +import org.springblade.core.mp.support.Query; +import org.springblade.core.secure.annotation.PreAuth; +import org.springblade.core.secure.constant.AuthConstant; +import org.springblade.core.tenant.annotation.TenantIgnore; +import org.springblade.core.tool.api.R; +import org.springblade.transport.pojo.vo.CustomerArchiveVO; +import org.springblade.transport.pojo.vo.CustomerChangeRecordVO; +import org.springblade.transport.service.ICustomerArchiveService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * 客商档案公开查看 控制器 + * + * @author Chill + */ +@RestController +@AllArgsConstructor +@TenantIgnore +@PreAuth(AuthConstant.PERMIT_ALL) +@RequestMapping("/customer-archive/public") +@Tag(name = "客商档案公开查看", description = "客商档案公开查看") +public class CustomerArchivePublicController { + + private final ICustomerArchiveService customerArchiveService; + + /** + * 公开详情 + */ + @GetMapping("/detail") + @ApiOperationSupport(order = 1) + @Operation(summary = "公开详情", description = "传入id,无需登录") + public R detail(@Parameter(description = "主键", required = true) @RequestParam Long id) { + return R.data(customerArchiveService.publicDetail(id)); + } + + /** + * 公开变更记录分页 + */ + @GetMapping("/change-record/list") + @ApiOperationSupport(order = 2) + @Operation(summary = "公开变更记录分页", description = "传入客商ID,无需登录") + public R> changeRecordList( + @Parameter(description = "客商ID", required = true) @RequestParam Long customerId, Query query) { + return R.data(customerArchiveService.publicChangeRecordPage(Condition.getPage(query), customerId)); + } + +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java index 73ddd1a1..9a6472b2 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java @@ -66,6 +66,23 @@ public interface ICustomerArchiveService extends BaseService { */ CustomerArchiveVO detail(Long id); + /** + * 公开查看详情(不校验登录态与数据权限) + * + * @param id 主键 + * @return 客商档案详情 + */ + CustomerArchiveVO publicDetail(Long id); + + /** + * 公开查看变更记录分页(不校验登录态与数据权限) + * + * @param page 分页参数 + * @param customerId 客商ID + * @return 变更记录分页 + */ + IPage publicChangeRecordPage(IPage page, Long customerId); + /** * 新增或修改客商档案 *

仅当 {@code customer.recordChange = true}(前端点「提交」)时写入变更记录;「保存」不落变更记录。

diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java index 7dde6cdc..8485807e 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java @@ -32,6 +32,7 @@ import lombok.RequiredArgsConstructor; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.mp.base.BaseServiceImpl; import org.springblade.core.secure.utils.AuthUtil; +import org.springblade.core.tenant.annotation.TenantIgnore; import org.springblade.core.tool.jackson.JsonUtil; import org.springblade.core.tool.utils.BeanUtil; import org.springblade.core.tool.utils.Func; @@ -151,31 +152,28 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl recordPage = changeRecordMapper.selectPage( - new Page<>(page.getCurrent(), page.getSize()), - Wrappers.lambdaQuery() - .eq(CustomerChangeRecord::getCustomerId, customerId) - .eq(CustomerChangeRecord::getIsDeleted, 0) - .orderByDesc(CustomerChangeRecord::getChangeTime)); - page.setTotal(recordPage.getTotal()); - return page.setRecords(recordPage.getRecords().stream() - .map(record -> Objects.requireNonNull(BeanUtil.copyProperties(record, CustomerChangeRecordVO.class))) - .toList()); + return queryChangeRecordPage(page, customerId); + } + + @Override + @TenantIgnore + public IPage publicChangeRecordPage(IPage page, Long customerId) { + if (Func.isEmpty(customerId)) { + throw new ServiceException("客商ID不能为空"); + } + getExistingCustomer(customerId); + return queryChangeRecordPage(page, customerId); } @Override public CustomerArchiveVO detail(Long id) { - if (Func.isEmpty(id)) { - throw new ServiceException("主键不能为空"); - } - CustomerArchive customer = ensureCustomerAccessible(id); - CustomerArchiveVO detail = Objects.requireNonNull(BeanUtil.copyProperties(customer, CustomerArchiveVO.class)); - detail.setContacts(loadContacts(id)); - detail.setReceiptAccounts(loadReceiptAccounts(id)); - detail.setInvoices(loadInvoices(id)); - detail.setScores(loadScores(id)); - fillFundUseRisk(List.of(detail)); - return detail; + return buildDetail(ensureCustomerAccessible(id)); + } + + @Override + @TenantIgnore + public CustomerArchiveVO publicDetail(Long id) { + return buildDetail(getExistingCustomer(id)); } @Override @@ -987,11 +985,43 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl queryChangeRecordPage(IPage page, Long customerId) { + IPage recordPage = changeRecordMapper.selectPage( + new Page<>(page.getCurrent(), page.getSize()), + Wrappers.lambdaQuery() + .eq(CustomerChangeRecord::getCustomerId, customerId) + .eq(CustomerChangeRecord::getIsDeleted, 0) + .orderByDesc(CustomerChangeRecord::getChangeTime)); + page.setTotal(recordPage.getTotal()); + return page.setRecords(recordPage.getRecords().stream() + .map(record -> Objects.requireNonNull(BeanUtil.copyProperties(record, CustomerChangeRecordVO.class))) + .toList()); + } + + private CustomerArchiveVO buildDetail(CustomerArchive customer) { + CustomerArchiveVO detail = Objects.requireNonNull(BeanUtil.copyProperties(customer, CustomerArchiveVO.class)); + Long id = customer.getId(); + detail.setContacts(loadContacts(id)); + detail.setReceiptAccounts(loadReceiptAccounts(id)); + detail.setInvoices(loadInvoices(id)); + detail.setScores(loadScores(id)); + fillFundUseRisk(List.of(detail)); + return detail; + } + + private CustomerArchive getExistingCustomer(Long id) { + if (Func.isEmpty(id)) { + throw new ServiceException("主键不能为空"); + } CustomerArchive customer = getById(id); if (Func.isEmpty(customer) || Objects.equals(customer.getIsDeleted(), 1)) { throw new ServiceException("客商档案不存在"); } + return customer; + } + + private CustomerArchive ensureCustomerAccessible(Long id) { + CustomerArchive customer = getExistingCustomer(id); if (baseMapper.selectCustomerPermissionCount(id, AuthUtil.getUserId()) == 0) { throw new ServiceException("无权访问该客商档案"); } diff --git a/doc/nacos/blade.yaml b/doc/nacos/blade.yaml index f65e82d8..cc51556d 100644 --- a/doc/nacos/blade.yaml +++ b/doc/nacos/blade.yaml @@ -216,6 +216,8 @@ blade: # 退出登录:允许无令牌/令牌失效时也能调用(服务端对无用户直接返回成功) - /oauth/logout/** - /blade-auth/oauth/logout/** + - /blade-transport/customer-archive/public/** + - /customer-archive/public/** #授权认证配置 auth: - method: ALL diff --git a/doc/sql/bladex/bladex.mysql.all.create.sql b/doc/sql/bladex/bladex.mysql.all.create.sql index f43daca7..b8054123 100644 --- a/doc/sql/bladex/bladex.mysql.all.create.sql +++ b/doc/sql/bladex/bladex.mysql.all.create.sql @@ -1757,6 +1757,7 @@ INSERT INTO `blade_menu` (`id`, `parent_id`, `code`, `name`, `alias`, `path`, `s DROP TABLE IF EXISTS `blade_measurement_unit`; CREATE TABLE `blade_measurement_unit` ( `id` bigint NOT NULL COMMENT '主键', + `unit_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '计量单位编码', `unit_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '计量单位', `dimension` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '计量维度', `remark` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', @@ -1768,6 +1769,7 @@ CREATE TABLE `blade_measurement_unit` ( `status` int NULL DEFAULT 1 COMMENT '状态', `is_deleted` int NULL DEFAULT 0 COMMENT '是否已删除', PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_measurement_unit_code`(`unit_code`) USING BTREE, UNIQUE INDEX `uk_measurement_unit_name`(`unit_name`) USING BTREE, INDEX `idx_measurement_unit_dimension`(`dimension`) USING BTREE, INDEX `idx_measurement_unit_status`(`status`) USING BTREE diff --git a/doc/sql/transport/blade_measurement_unit.sql b/doc/sql/transport/blade_measurement_unit.sql index 8f03d43c..175be766 100644 --- a/doc/sql/transport/blade_measurement_unit.sql +++ b/doc/sql/transport/blade_measurement_unit.sql @@ -4,6 +4,7 @@ DROP TABLE IF EXISTS `blade_measurement_unit`; CREATE TABLE `blade_measurement_unit` ( `id` bigint NOT NULL COMMENT '主键', + `unit_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '计量单位编码', `unit_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '计量单位', `dimension` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '计量维度', `remark` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', @@ -15,6 +16,7 @@ CREATE TABLE `blade_measurement_unit` ( `status` int NULL DEFAULT 1 COMMENT '状态', `is_deleted` int NULL DEFAULT 0 COMMENT '是否已删除', PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `uk_measurement_unit_code`(`unit_code`) USING BTREE, UNIQUE INDEX `uk_measurement_unit_name`(`unit_name`) USING BTREE, INDEX `idx_measurement_unit_dimension`(`dimension`) USING BTREE, INDEX `idx_measurement_unit_status`(`status`) USING BTREE diff --git a/doc/sql/transport/blade_measurement_unit_code_20260919.sql b/doc/sql/transport/blade_measurement_unit_code_20260919.sql new file mode 100644 index 00000000..5da38d2e --- /dev/null +++ b/doc/sql/transport/blade_measurement_unit_code_20260919.sql @@ -0,0 +1,11 @@ +-- 计量单位新增计量单位编码 +ALTER TABLE `blade_measurement_unit` + ADD COLUMN `unit_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '计量单位编码' AFTER `id`; + +UPDATE `blade_measurement_unit` +SET `unit_code` = CONCAT('MU', `id`) +WHERE `unit_code` IS NULL OR `unit_code` = ''; + +ALTER TABLE `blade_measurement_unit` + MODIFY COLUMN `unit_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '计量单位编码', + ADD UNIQUE INDEX `uk_measurement_unit_code`(`unit_code`) USING BTREE; From aa2084090a05f4928aae015d87ac7cdb67dd901b Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Sun, 20 Sep 2026 14:14:39 +0800 Subject: [PATCH 18/55] =?UTF-8?q?=E8=B0=83=E8=AF=95mk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/springblade/openapi/OpenApiApplication.java | 4 ++++ .../blade-openapi/src/main/resources/application.yml | 1 + .../blade-openapi/src/main/resources/openapi-lock.yaml | 5 +++++ 3 files changed, 10 insertions(+) create mode 100644 blade-service/blade-openapi/src/main/resources/openapi-lock.yaml diff --git a/blade-service/blade-openapi/src/main/java/org/springblade/openapi/OpenApiApplication.java b/blade-service/blade-openapi/src/main/java/org/springblade/openapi/OpenApiApplication.java index e76743f7..9be79473 100644 --- a/blade-service/blade-openapi/src/main/java/org/springblade/openapi/OpenApiApplication.java +++ b/blade-service/blade-openapi/src/main/java/org/springblade/openapi/OpenApiApplication.java @@ -44,6 +44,10 @@ public class OpenApiApplication { public static void main(String[] args) { BladeApplication.disableNacosLaunchConfig(); + // 当前处理人刷新依赖 RedisLockClient;Nacos 全局 blade.lock.enabled=false 时仍需为本服务开启 + if (System.getProperty("blade.lock.enabled") == null) { + System.setProperty("blade.lock.enabled", "true"); + } BladeApplication.run(AppConstant.APPLICATION_OPENAPI_NAME, OpenApiApplication.class, args); } diff --git a/blade-service/blade-openapi/src/main/resources/application.yml b/blade-service/blade-openapi/src/main/resources/application.yml index be5d93fe..28686324 100644 --- a/blade-service/blade-openapi/src/main/resources/application.yml +++ b/blade-service/blade-openapi/src/main/resources/application.yml @@ -10,6 +10,7 @@ spring: - nacos:blade-${spring.profiles.active}.yaml?group=DEFAULT_GROUP&refreshEnabled=true - nacos:blade-openapi-dynamictp.yaml?group=DEFAULT_GROUP&refreshEnabled=true - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml?group=DEFAULT_GROUP&refreshEnabled=true + - optional:classpath:openapi-lock.yaml cloud: nacos: username: ${NACOS_USERNAME:${NACOS_PROD_USERNAME:nacos}} diff --git a/blade-service/blade-openapi/src/main/resources/openapi-lock.yaml b/blade-service/blade-openapi/src/main/resources/openapi-lock.yaml new file mode 100644 index 00000000..c67d7095 --- /dev/null +++ b/blade-service/blade-openapi/src/main/resources/openapi-lock.yaml @@ -0,0 +1,5 @@ +# openapi 当前处理人刷新依赖 Redisson 分布式锁。 +# 该文件必须在 nacos blade-*.yaml 之后导入,用于覆盖全局 blade.lock.enabled=false。 +blade: + lock: + enabled: true From d5e7787e89eb07ded8a7e7b20d6a9181c1c8c127 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Sun, 20 Sep 2026 16:05:25 +0800 Subject: [PATCH 19/55] =?UTF-8?q?=E8=B0=83=E8=AF=95mk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/openapi-lock.yaml | 4 ++++ .../CustomerArchivePublicController.java | 18 ++++++++++++++++++ doc/nacos/blade-prod.yaml | 1 + 3 files changed, 23 insertions(+) diff --git a/blade-service/blade-openapi/src/main/resources/openapi-lock.yaml b/blade-service/blade-openapi/src/main/resources/openapi-lock.yaml index c67d7095..e388d4f8 100644 --- a/blade-service/blade-openapi/src/main/resources/openapi-lock.yaml +++ b/blade-service/blade-openapi/src/main/resources/openapi-lock.yaml @@ -1,5 +1,9 @@ # openapi 当前处理人刷新依赖 Redisson 分布式锁。 # 该文件必须在 nacos blade-*.yaml 之后导入,用于覆盖全局 blade.lock.enabled=false。 +# 连接信息与业务 Redis 保持一致,避免 Redisson 因缺少密码出现 NOAUTH。 blade: lock: enabled: true + address: redis://${spring.data.redis.host:127.0.0.1}:${spring.data.redis.port:6379} + password: ${spring.data.redis.password:} + database: ${spring.data.redis.database:0} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java index b2094c38..8db89870 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java @@ -22,12 +22,14 @@ */ package org.springblade.transport.controller; +import com.alibaba.fastjson2.JSON; import com.baomidou.mybatisplus.core.metadata.IPage; import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springblade.core.mp.support.Condition; import org.springblade.core.mp.support.Query; import org.springblade.core.secure.annotation.PreAuth; @@ -38,15 +40,20 @@ import org.springblade.transport.pojo.vo.CustomerArchiveVO; import org.springblade.transport.pojo.vo.CustomerChangeRecordVO; import org.springblade.transport.service.ICustomerArchiveService; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.Map; + /** * 客商档案公开查看 控制器 * * @author Chill */ +@Slf4j @RestController @AllArgsConstructor @TenantIgnore @@ -78,4 +85,15 @@ public class CustomerArchivePublicController { return R.data(customerArchiveService.publicChangeRecordPage(Condition.getPage(query), customerId)); } + /** + * 公开接收流程页 postMessage 数据(当前仅打印,便于联调) + */ + @PostMapping("/process-message") + @ApiOperationSupport(order = 3) + @Operation(summary = "公开接收流程消息", description = "无需登录,当前仅打印接收数据") + public R processMessage(@RequestBody Map body) { + log.info("客商公开页收到流程消息:{}", JSON.toJSONString(body)); + return R.success("ok"); + } + } diff --git a/doc/nacos/blade-prod.yaml b/doc/nacos/blade-prod.yaml index 407b09ce..384f9043 100644 --- a/doc/nacos/blade-prod.yaml +++ b/doc/nacos/blade-prod.yaml @@ -39,6 +39,7 @@ blade: ##将docker脚本部署的redis服务映射为宿主机ip ##生产环境推荐使用阿里云高可用redis服务并设置密码 address: redis://172.16.203.228:6379 + password: ${spring.data.redis.password:} #通用开发生产环境数据库地址(特殊情况可在对应的子工程里配置覆盖) datasource: prod: From 9668f6059a7e859118fac69af96f0a94c44b992e Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Sun, 20 Sep 2026 16:32:31 +0800 Subject: [PATCH 20/55] =?UTF-8?q?:bug:=20=E4=BF=AE=E5=A4=8D=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E6=96=87=E4=BB=B6=E5=88=9B=E5=BB=BA=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E4=B8=8E=E6=9B=B4=E6=96=B0=E6=97=B6=E9=97=B4=E5=88=97=E4=B8=BA?= =?UTF-8?q?=E7=A9=BA=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 导出模型的 createTime/updateTime 由 LocalDateTime 改为 java.util.Date,与 BaseEntity 审计字段类型保持一致,避免 BeanUtil.copyProperties 因类型不兼容而静默跳过 - 覆盖车/船务管理 12 个页面导出模型及基础数据等 11 个导出模型,并为新增 Date 字段补齐 @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - 运单导入显式清空审计时间,避免把导入文件中的创建/更新时间带入实体 --- .../transport/excel/AccidentRecordExportExcel.java | 6 +++--- .../excel/AnnualInspectionRecordExportExcel.java | 6 +++--- .../springblade/transport/excel/CommonAddressExcel.java | 9 ++++++--- .../transport/excel/CommonCargoExportExcel.java | 9 ++++++--- .../springblade/transport/excel/CommonRouteExcel.java | 9 ++++++--- .../springblade/transport/excel/ContractManageExcel.java | 9 ++++++--- .../transport/excel/EtcRecordExportExcel.java | 5 +++-- .../transport/excel/InsuranceRecordExportExcel.java | 6 +++--- .../springblade/transport/excel/LoadingManageExcel.java | 9 ++++++--- .../transport/excel/MaintenancePlanExportExcel.java | 6 +++--- .../transport/excel/MaintenanceRecordExcel.java | 5 +++-- .../transport/excel/MileageRecordExportExcel.java | 6 +++--- .../transport/excel/OilElectricRecordExportExcel.java | 5 +++-- .../transport/excel/OtherExpenseRecordExportExcel.java | 6 +++--- .../springblade/transport/excel/ProcessConfigExcel.java | 9 ++++++--- .../springblade/transport/excel/ProjectApplyExcel.java | 8 ++++++-- .../transport/excel/ShippingTemplateExcel.java | 9 ++++++--- .../transport/excel/TemporaryCreditLimitExcel.java | 9 ++++++--- .../excel/TireReplacementRecordExportExcel.java | 6 +++--- .../excel/TransportChangeRecordExportExcel.java | 6 +++--- .../springblade/transport/excel/TransportPlanExcel.java | 9 ++++++--- .../transport/excel/ViolationRecordExcel.java | 5 +++-- .../org/springblade/transport/excel/WaybillExcel.java | 9 ++++++--- .../transport/service/impl/WaybillServiceImpl.java | 2 ++ 24 files changed, 104 insertions(+), 64 deletions(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/AccidentRecordExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/AccidentRecordExportExcel.java index bf0102ac..40d22aba 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/AccidentRecordExportExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/AccidentRecordExportExcel.java @@ -37,7 +37,7 @@ import java.io.Serial; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.util.Date; /** * 事故记录导出 Excel @@ -83,14 +83,14 @@ public class AccidentRecordExportExcel implements Serializable { @ExcelProperty("创建时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; + private Date createTime; @ExcelProperty("更新人") private String updateUserName; @ExcelProperty("更新时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime updateTime; + private Date updateTime; @ExcelProperty("备注") private String remark; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/AnnualInspectionRecordExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/AnnualInspectionRecordExportExcel.java index a87ac075..3b9a297a 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/AnnualInspectionRecordExportExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/AnnualInspectionRecordExportExcel.java @@ -36,7 +36,7 @@ import java.io.Serial; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.util.Date; /** * 年检记录导出 Excel @@ -83,14 +83,14 @@ public class AnnualInspectionRecordExportExcel implements Serializable { @ExcelProperty("创建时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; + private Date createTime; @ExcelProperty("更新人") private String updateUserName; @ExcelProperty("更新时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime updateTime; + private Date updateTime; @ExcelProperty("备注") private String remark; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonAddressExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonAddressExcel.java index 420cdcaa..bcc700db 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonAddressExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonAddressExcel.java @@ -24,6 +24,7 @@ package org.springblade.transport.excel; import cn.idev.excel.annotation.ExcelIgnore; import cn.idev.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.format.DateTimeFormat; import cn.idev.excel.annotation.write.style.ColumnWidth; import cn.idev.excel.annotation.write.style.ContentRowHeight; import cn.idev.excel.annotation.write.style.HeadRowHeight; @@ -32,7 +33,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.math.BigDecimal; -import java.time.LocalDateTime; +import java.util.Date; /** * 常用地址 Excel @@ -88,9 +89,11 @@ public class CommonAddressExcel implements Serializable { private String remark; @ExcelProperty("更新时间") - private LocalDateTime updateTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date updateTime; @ExcelProperty("创建时间") - private LocalDateTime createTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date createTime; } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonCargoExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonCargoExportExcel.java index b9bf5e65..de8be2b3 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonCargoExportExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonCargoExportExcel.java @@ -24,6 +24,7 @@ package org.springblade.transport.excel; import cn.idev.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.format.DateTimeFormat; import cn.idev.excel.annotation.format.NumberFormat; import cn.idev.excel.annotation.write.style.ColumnWidth; import cn.idev.excel.annotation.write.style.ContentRowHeight; @@ -33,7 +34,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.math.BigDecimal; -import java.time.LocalDateTime; +import java.util.Date; /** * 常用货物导出 Excel @@ -96,9 +97,11 @@ public class CommonCargoExportExcel implements Serializable { private String deptName; @ExcelProperty("更新时间") - private LocalDateTime updateTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date updateTime; @ExcelProperty("创建时间") - private LocalDateTime createTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date createTime; } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonRouteExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonRouteExcel.java index 31b2b767..34fb45e0 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonRouteExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CommonRouteExcel.java @@ -23,6 +23,7 @@ package org.springblade.transport.excel; import cn.idev.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.format.DateTimeFormat; import cn.idev.excel.annotation.write.style.ColumnWidth; import cn.idev.excel.annotation.write.style.ContentRowHeight; import cn.idev.excel.annotation.write.style.HeadRowHeight; @@ -30,7 +31,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; -import java.time.LocalDateTime; +import java.util.Date; /** * 常用线路 Excel @@ -75,8 +76,10 @@ public class CommonRouteExcel implements Serializable { @ExcelProperty("更新人") private String updateUserName; @ExcelProperty("创建时间") - private LocalDateTime createTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date createTime; @ExcelProperty("更新时间") - private LocalDateTime updateTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date updateTime; } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ContractManageExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ContractManageExcel.java index 853fa58e..39aba2a8 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ContractManageExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ContractManageExcel.java @@ -23,6 +23,7 @@ package org.springblade.transport.excel; import cn.idev.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.format.DateTimeFormat; import cn.idev.excel.annotation.write.style.ColumnWidth; import cn.idev.excel.annotation.write.style.ContentRowHeight; import cn.idev.excel.annotation.write.style.HeadRowHeight; @@ -31,7 +32,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.util.Date; /** * 合同管理 Excel @@ -88,8 +89,10 @@ public class ContractManageExcel implements Serializable { @ExcelProperty("更新人") private String updateUserName; @ExcelProperty("创建时间") - private LocalDateTime createTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date createTime; @ExcelProperty("更新时间") - private LocalDateTime updateTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date updateTime; } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/EtcRecordExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/EtcRecordExportExcel.java index 02832740..1400ed00 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/EtcRecordExportExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/EtcRecordExportExcel.java @@ -11,6 +11,7 @@ import java.io.Serial; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.Date; /** * ETC记录导出 Excel @@ -50,14 +51,14 @@ public class EtcRecordExportExcel implements Serializable { @ExcelProperty("创建时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; + private Date createTime; @ExcelProperty("更新人") private String updateUserName; @ExcelProperty("更新时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime updateTime; + private Date updateTime; @ExcelProperty("备注") private String remark; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/InsuranceRecordExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/InsuranceRecordExportExcel.java index 77702c16..ad1f0e7a 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/InsuranceRecordExportExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/InsuranceRecordExportExcel.java @@ -36,7 +36,7 @@ import java.io.Serial; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.util.Date; /** * 保险记录导出 Excel @@ -83,14 +83,14 @@ public class InsuranceRecordExportExcel implements Serializable { @ExcelProperty("创建时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; + private Date createTime; @ExcelProperty("更新人") private String updateUserName; @ExcelProperty("更新时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime updateTime; + private Date updateTime; @ExcelProperty("备注") private String remark; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/LoadingManageExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/LoadingManageExcel.java index 5687407e..79953456 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/LoadingManageExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/LoadingManageExcel.java @@ -6,6 +6,7 @@ package org.springblade.transport.excel; import cn.idev.excel.annotation.ExcelIgnore; import cn.idev.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.format.DateTimeFormat; import cn.idev.excel.annotation.write.style.ColumnWidth; import cn.idev.excel.annotation.write.style.ContentRowHeight; import cn.idev.excel.annotation.write.style.HeadRowHeight; @@ -13,7 +14,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; -import java.time.LocalDateTime; +import java.util.Date; /** * 配载管理 Excel @@ -54,9 +55,11 @@ public class LoadingManageExcel implements Serializable { @ExcelProperty("数据来源") private String dataSource; @ExcelProperty("创建时间") - private LocalDateTime createTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date createTime; @ExcelProperty("更新时间") - private LocalDateTime updateTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date updateTime; @ExcelProperty("过程节点") private String currentProcessNode; @ExcelProperty("状态") diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenancePlanExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenancePlanExportExcel.java index 4d6666ff..a3c8a84e 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenancePlanExportExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenancePlanExportExcel.java @@ -11,7 +11,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import java.io.Serial; -import java.time.LocalDateTime; +import java.util.Date; /** * 车辆保养记录导出 Excel @@ -26,12 +26,12 @@ public class MaintenancePlanExportExcel extends MaintenancePlanExcel { @ExcelProperty(value = "创建时间", index = 12) @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; + private Date createTime; @ExcelProperty(value = "更新人", index = 13) private String updateUserName; @ExcelProperty(value = "更新时间", index = 14) @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime updateTime; + private Date updateTime; } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenanceRecordExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenanceRecordExcel.java index 6361d717..854a5375 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenanceRecordExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenanceRecordExcel.java @@ -38,6 +38,7 @@ import java.io.Serial; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.Date; /** * 车辆维修记录 Excel @@ -97,14 +98,14 @@ public class MaintenanceRecordExcel implements Serializable { @ExcelProperty("创建时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; + private Date createTime; @ExcelProperty("更新人") private String updateUserName; @ExcelProperty("更新时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime updateTime; + private Date updateTime; @ExcelProperty("里程单位") private String mileageUnit; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MileageRecordExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MileageRecordExportExcel.java index be5b2482..55be035a 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MileageRecordExportExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MileageRecordExportExcel.java @@ -36,7 +36,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.math.BigDecimal; -import java.time.LocalDateTime; +import java.util.Date; /** * 里程记录导出 Excel @@ -75,14 +75,14 @@ public class MileageRecordExportExcel implements Serializable { @ExcelProperty("创建时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; + private Date createTime; @ExcelProperty("更新人") private String updateUserName; @ExcelProperty("更新时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime updateTime; + private Date updateTime; @ExcelProperty("备注") private String remark; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/OilElectricRecordExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/OilElectricRecordExportExcel.java index e844b9cf..3db72793 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/OilElectricRecordExportExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/OilElectricRecordExportExcel.java @@ -11,6 +11,7 @@ import java.io.Serial; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.Date; /** * 油电记录导出 Excel @@ -61,14 +62,14 @@ public class OilElectricRecordExportExcel implements Serializable { @ExcelProperty("创建时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; + private Date createTime; @ExcelProperty("更新人") private String updateUserName; @ExcelProperty("更新时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime updateTime; + private Date updateTime; @ExcelProperty("备注") private String remark; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/OtherExpenseRecordExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/OtherExpenseRecordExportExcel.java index cbf4b79b..1dfe171a 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/OtherExpenseRecordExportExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/OtherExpenseRecordExportExcel.java @@ -11,7 +11,7 @@ import java.io.Serial; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.util.Date; /** * 其他费用记录导出 Excel @@ -43,14 +43,14 @@ public class OtherExpenseRecordExportExcel implements Serializable { @ExcelProperty("创建时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; + private Date createTime; @ExcelProperty("更新人") private String updateUserName; @ExcelProperty("更新时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime updateTime; + private Date updateTime; @ExcelProperty("备注") private String remark; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ProcessConfigExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ProcessConfigExcel.java index cd565dce..efc663ae 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ProcessConfigExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ProcessConfigExcel.java @@ -23,6 +23,7 @@ package org.springblade.transport.excel; import cn.idev.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.format.DateTimeFormat; import cn.idev.excel.annotation.write.style.ColumnWidth; import cn.idev.excel.annotation.write.style.ContentRowHeight; import cn.idev.excel.annotation.write.style.HeadRowHeight; @@ -30,7 +31,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; -import java.time.LocalDateTime; +import java.util.Date; /** * 过程配置 Excel @@ -69,8 +70,10 @@ public class ProcessConfigExcel implements Serializable { @ExcelProperty("更新人") private String updateUserName; @ExcelProperty("创建时间") - private LocalDateTime createTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date createTime; @ExcelProperty("更新时间") - private LocalDateTime updateTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date updateTime; } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ProjectApplyExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ProjectApplyExcel.java index 25238f7e..2229dcbf 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ProjectApplyExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ProjectApplyExcel.java @@ -23,6 +23,7 @@ package org.springblade.transport.excel; import cn.idev.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.format.DateTimeFormat; import cn.idev.excel.annotation.write.style.ColumnWidth; import cn.idev.excel.annotation.write.style.ContentRowHeight; import cn.idev.excel.annotation.write.style.HeadRowHeight; @@ -33,6 +34,7 @@ import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.Date; /** * 项目立项 Excel @@ -119,8 +121,10 @@ public class ProjectApplyExcel implements Serializable { @ExcelProperty("更新人") private String updateUserName; @ExcelProperty("创建时间") - private LocalDateTime createTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date createTime; @ExcelProperty("更新时间") - private LocalDateTime updateTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date updateTime; } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ShippingTemplateExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ShippingTemplateExcel.java index 2731bbec..6457383b 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ShippingTemplateExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ShippingTemplateExcel.java @@ -23,6 +23,7 @@ package org.springblade.transport.excel; import cn.idev.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.format.DateTimeFormat; import cn.idev.excel.annotation.write.style.ColumnWidth; import cn.idev.excel.annotation.write.style.ContentRowHeight; import cn.idev.excel.annotation.write.style.HeadRowHeight; @@ -30,7 +31,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; -import java.time.LocalDateTime; +import java.util.Date; /** * 发货模板 Excel @@ -59,8 +60,10 @@ public class ShippingTemplateExcel implements Serializable { @ExcelProperty("备注") private String remark; @ExcelProperty("更新时间") - private LocalDateTime updateTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date updateTime; @ExcelProperty("创建时间") - private LocalDateTime createTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date createTime; } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TemporaryCreditLimitExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TemporaryCreditLimitExcel.java index 03ceb9bc..5d58a542 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TemporaryCreditLimitExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TemporaryCreditLimitExcel.java @@ -23,6 +23,7 @@ package org.springblade.transport.excel; import cn.idev.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.format.DateTimeFormat; import cn.idev.excel.annotation.write.style.ColumnWidth; import cn.idev.excel.annotation.write.style.ContentRowHeight; import cn.idev.excel.annotation.write.style.HeadRowHeight; @@ -32,7 +33,7 @@ import java.io.Serial; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.util.Date; /** * 临时额度申请 Excel @@ -81,8 +82,10 @@ public class TemporaryCreditLimitExcel implements Serializable { @ExcelProperty("更新人") private String updateUserName; @ExcelProperty("创建时间") - private LocalDateTime createTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date createTime; @ExcelProperty("更新时间") - private LocalDateTime updateTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date updateTime; } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TireReplacementRecordExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TireReplacementRecordExportExcel.java index e2e2a329..26009e8c 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TireReplacementRecordExportExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TireReplacementRecordExportExcel.java @@ -37,7 +37,7 @@ import java.io.Serial; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.util.Date; /** * 换胎记录导出 Excel @@ -76,14 +76,14 @@ public class TireReplacementRecordExportExcel implements Serializable { @ExcelProperty("创建时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; + private Date createTime; @ExcelProperty("更新人") private String updateUserName; @ExcelProperty("更新时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime updateTime; + private Date updateTime; @ExcelProperty("备注") private String remark; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TransportChangeRecordExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TransportChangeRecordExportExcel.java index d4f4ce93..f737cd26 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TransportChangeRecordExportExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TransportChangeRecordExportExcel.java @@ -28,7 +28,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; -import java.time.LocalDateTime; +import java.util.Date; /** * 变更记录导出 Excel @@ -57,14 +57,14 @@ public class TransportChangeRecordExportExcel implements Serializable { @ExcelProperty("创建时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; + private Date createTime; @ExcelProperty("更新人") private String updateUserName; @ExcelProperty("更新时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime updateTime; + private Date updateTime; @ExcelProperty("备注") private String remark; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TransportPlanExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TransportPlanExcel.java index 955d0b03..c5d4e7eb 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TransportPlanExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/TransportPlanExcel.java @@ -23,6 +23,7 @@ package org.springblade.transport.excel; import cn.idev.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.format.DateTimeFormat; import cn.idev.excel.annotation.write.style.ColumnWidth; import cn.idev.excel.annotation.write.style.ContentRowHeight; import cn.idev.excel.annotation.write.style.HeadRowHeight; @@ -31,7 +32,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.util.Date; /** * 运输计划 Excel @@ -92,8 +93,10 @@ public class TransportPlanExcel implements Serializable { @ExcelProperty("更新人") private String updateUserName; @ExcelProperty("创建时间") - private LocalDateTime createTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date createTime; @ExcelProperty("更新时间") - private LocalDateTime updateTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date updateTime; } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ViolationRecordExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ViolationRecordExcel.java index 7abf3490..7b95877e 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ViolationRecordExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ViolationRecordExcel.java @@ -37,6 +37,7 @@ import java.io.Serial; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.Date; /** * 违章记录 Excel @@ -95,14 +96,14 @@ public class ViolationRecordExcel implements Serializable { @ExcelProperty("创建时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime createTime; + private Date createTime; @ExcelProperty("更新人") private String updateUserName; @ExcelProperty("更新时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") - private LocalDateTime updateTime; + private Date updateTime; @ExcelIgnore private String errorMessage; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/WaybillExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/WaybillExcel.java index 3a68f3e0..09e60ef4 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/WaybillExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/WaybillExcel.java @@ -23,6 +23,7 @@ package org.springblade.transport.excel; import cn.idev.excel.annotation.ExcelProperty; +import cn.idev.excel.annotation.format.DateTimeFormat; import cn.idev.excel.annotation.ExcelIgnore; import cn.idev.excel.annotation.write.style.ColumnWidth; import cn.idev.excel.annotation.write.style.ContentRowHeight; @@ -33,7 +34,7 @@ import java.io.Serial; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.util.Date; import java.time.LocalDate; /** @@ -151,9 +152,11 @@ public class WaybillExcel implements Serializable { @ExcelProperty("更新人") private String updateUserName; @ExcelProperty("创建时间") - private LocalDateTime createTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date createTime; @ExcelProperty("更新时间") - private LocalDateTime updateTime; + @DateTimeFormat("yyyy-MM-dd HH:mm:ss") + private Date updateTime; @ExcelIgnore private String errorMessage; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java index 934c2572..56ad9009 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java @@ -150,6 +150,8 @@ public class WaybillServiceImpl extends BaseServiceImpl try { Waybill waybill = Objects.requireNonNull(BeanUtil.copyProperties(excel, Waybill.class)); waybill.setDataSource("批量导入"); + waybill.setCreateTime(null); + waybill.setUpdateTime(null); waybill.setCarrierJson(buildImportCarrierJson(waybill)); submit(waybill); } catch (Exception exception) { From 0bfc9cbc86b8ce76d51b6d5240e97d2d812ed808 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Sun, 20 Sep 2026 16:36:31 +0800 Subject: [PATCH 21/55] =?UTF-8?q?=E8=B0=83=E8=AF=95mk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/springblade/gateway/provider/AuthProvider.java | 2 ++ .../src/main/java/org/springblade/openapi/mk/Api4MK.java | 3 +++ doc/nacos/blade.yaml | 2 ++ 3 files changed, 7 insertions(+) diff --git a/blade-gateway/src/main/java/org/springblade/gateway/provider/AuthProvider.java b/blade-gateway/src/main/java/org/springblade/gateway/provider/AuthProvider.java index c67671be..c324c64d 100644 --- a/blade-gateway/src/main/java/org/springblade/gateway/provider/AuthProvider.java +++ b/blade-gateway/src/main/java/org/springblade/gateway/provider/AuthProvider.java @@ -65,6 +65,8 @@ public class AuthProvider { DEFAULT_SKIP_URL.add("/iam/sso/token/**"); DEFAULT_SKIP_URL.add("/blade-transport/customer-archive/public/**"); DEFAULT_SKIP_URL.add("/customer-archive/public/**"); + DEFAULT_SKIP_URL.add("/blade-openapi/openApi/mk/process/commonCallback"); + DEFAULT_SKIP_URL.add("/openApi/mk/process/commonCallback"); } /** diff --git a/blade-service/blade-openapi/src/main/java/org/springblade/openapi/mk/Api4MK.java b/blade-service/blade-openapi/src/main/java/org/springblade/openapi/mk/Api4MK.java index 0f43c46d..727fce2e 100644 --- a/blade-service/blade-openapi/src/main/java/org/springblade/openapi/mk/Api4MK.java +++ b/blade-service/blade-openapi/src/main/java/org/springblade/openapi/mk/Api4MK.java @@ -5,6 +5,8 @@ import io.swagger.v3.oas.annotations.Hidden; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springblade.core.log.exception.ServiceException; +import org.springblade.core.secure.annotation.PreAuth; +import org.springblade.core.secure.constant.AuthConstant; import org.springblade.core.tool.api.FR; import org.springblade.openapi.mk.api.IApi4MK; import org.springblade.openapi.mk.pojo.dto.Api4MKProcessApprovalDTO; @@ -49,6 +51,7 @@ public class Api4MK implements IApi4MK { } @Override + @PreAuth(AuthConstant.PERMIT_ALL) public FR processCommonCallback(Api4MKProcessApprovalDTO param) { log.info("mk流程通用回调 操作名称:{} 参数:{}", ProcessOperationType.getOperationName(param.getOperation()), JSON.toJSONString(param)); callback(param, ProcessHandler::approve); diff --git a/doc/nacos/blade.yaml b/doc/nacos/blade.yaml index cc51556d..6c40037a 100644 --- a/doc/nacos/blade.yaml +++ b/doc/nacos/blade.yaml @@ -218,6 +218,8 @@ blade: - /blade-auth/oauth/logout/** - /blade-transport/customer-archive/public/** - /customer-archive/public/** + - /blade-openapi/openApi/mk/process/commonCallback + - /openApi/mk/process/commonCallback #授权认证配置 auth: - method: ALL From 6987a0e790c691fe0a54ccb328884fd67ff4a767 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Sun, 20 Sep 2026 17:11:08 +0800 Subject: [PATCH 22/55] =?UTF-8?q?=E8=B0=83=E8=AF=95mk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../process/feign/IBusinessProcessClient.java | 12 ++++ .../process/feign/BusinessProcessClient.java | 12 ++++ .../service/IBusinessProcessService.java | 9 +++ .../impl/BusinessProcessServiceImpl.java | 27 +++++++++ blade-service/blade-transport/pom.xml | 4 ++ .../CustomerArchivePublicController.java | 55 ++++++++++++++++++- doc/nacos/blade.yaml | 1 + 7 files changed, 119 insertions(+), 1 deletion(-) diff --git a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/feign/IBusinessProcessClient.java b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/feign/IBusinessProcessClient.java index 16cc18ea..d6e5fa35 100644 --- a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/feign/IBusinessProcessClient.java +++ b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/feign/IBusinessProcessClient.java @@ -38,6 +38,7 @@ public interface IBusinessProcessClient { String QUERY_TODO_LIST = API_PREFIX + "/queryTodoList"; String QUERY_BUSINESS_PROCESS_SNAPSHOT = API_PREFIX + "/queryBusinessProcessSnapshot"; String QUERY_APPROVED_RECORD_LIST = API_PREFIX + "/queryApprovedRecordsNoAttachments"; + String GET_CURRENT_NODES = API_PREFIX + "/getCurrentNodes"; /** * 提交业务流程 @@ -102,4 +103,15 @@ public interface IBusinessProcessClient { */ @GetMapping(QUERY_APPROVED_RECORD_LIST) FR> queryApprovedRecordsNoAttachments(@RequestParam(name = "bizId", required = false) String bizId, @RequestParam(name = "processInstanceId", required = false) String processInstanceId); + + /** + * 获取流程当前节点详情 + * + * @param processInstanceId 流程实例id + * @param loginName MK登录名(手机号) + * @return 当前节点详情 + */ + @GetMapping(GET_CURRENT_NODES) + FR getCurrentNodes(@RequestParam("processInstanceId") String processInstanceId, + @RequestParam(value = "loginName", required = false) String loginName); } diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/feign/BusinessProcessClient.java b/blade-service/blade-system/src/main/java/org/springblade/process/feign/BusinessProcessClient.java index eb789f44..b0f8b8e9 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/process/feign/BusinessProcessClient.java +++ b/blade-service/blade-system/src/main/java/org/springblade/process/feign/BusinessProcessClient.java @@ -3,6 +3,8 @@ package org.springblade.process.feign; import io.swagger.v3.oas.annotations.Hidden; import jakarta.validation.Valid; import lombok.AllArgsConstructor; +import org.springblade.core.secure.annotation.PreAuth; +import org.springblade.core.secure.constant.AuthConstant; import org.springblade.core.tool.api.FR; import org.springblade.process.pojo.dto.BusinessProcessCurrentHandlerRefreshDTO; import org.springblade.process.pojo.dto.BusinessProcessDeleteDTO; @@ -13,8 +15,10 @@ import org.springblade.process.pojo.vo.ProcessApprovedRecordVO; import org.springblade.process.pojo.vo.ProcessTodoVO; import org.springblade.process.service.IBusinessProcessService; import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.List; @@ -74,4 +78,12 @@ public class BusinessProcessClient implements IBusinessProcessClient { public FR> queryApprovedRecordsNoAttachments(String bizId, String processInstanceId) { return FR.data(businessProcessService.queryApprovedRecordsNoAttachments(bizId, processInstanceId)); } + + @PreAuth(AuthConstant.PERMIT_ALL) + @GetMapping(GET_CURRENT_NODES) + @Override + public FR getCurrentNodes(@RequestParam("processInstanceId") String processInstanceId, + @RequestParam(value = "loginName", required = false) String loginName) { + return FR.data(businessProcessService.getCurrentNodes(processInstanceId, loginName)); + } } diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java b/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java index a577c097..ebdc83b2 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java @@ -34,6 +34,15 @@ public interface IBusinessProcessService extends IService { */ String processSubmit(MKProcessCreateDTO param); + /** + * 获取流程当前节点详情 + * + * @param processInstanceId 流程实例id + * @param loginName MK登录名(手机号),可为空,为空时按流程发起人解析 + * @return 当前节点列表 + */ + List getCurrentNodes(String processInstanceId, String loginName); + /** * 修改业务流程状态 * @param param diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java index 1317fdf2..4ad37246 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java @@ -163,9 +163,36 @@ public class BusinessProcessServiceImpl extends ServiceImpl getCurrentNodes(String processInstanceId, String loginName) { + if (StringUtils.isBlank(processInstanceId)) { + log.warn("查询当前节点失败,processInstanceId为空"); + return Collections.emptyList(); + } + String resolvedLoginName = loginName; + if (StringUtils.isBlank(resolvedLoginName)) { + BusinessProcess businessProcess = this.getBusinessProcessByProcessInstanceId(processInstanceId); + resolvedLoginName = resolvePromoterLoginName(businessProcess, null); + } + if (StringUtils.isBlank(resolvedLoginName)) { + log.warn("查询当前节点失败,loginName为空 processInstanceId={}", processInstanceId); + return Collections.emptyList(); + } + try { + List currentNodes = mkService.getCurrentNodes(processInstanceId, resolvedLoginName); + log.info("获取流程当前节点详情 processInstanceId={} loginName={} result={}", + processInstanceId, resolvedLoginName, JSON.toJSONString(currentNodes)); + return currentNodes == null ? Collections.emptyList() : currentNodes; + } catch (Exception e) { + log.error("查询当前节点异常 processInstanceId={} loginName={}", processInstanceId, resolvedLoginName, e); + return Collections.emptyList(); + } + } + /** * 从当前登录用户实体读取真实手机号(绕过接口返回脱敏) */ diff --git a/blade-service/blade-transport/pom.xml b/blade-service/blade-transport/pom.xml index b0124361..f2490ca2 100644 --- a/blade-service/blade-transport/pom.xml +++ b/blade-service/blade-transport/pom.xml @@ -43,6 +43,10 @@ org.springblade blade-system-api + + org.springblade + blade-process-api + org.springframework.boot spring-boot-starter-amqp diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java index 8db89870..3bbb3858 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java @@ -35,7 +35,10 @@ import org.springblade.core.mp.support.Query; import org.springblade.core.secure.annotation.PreAuth; import org.springblade.core.secure.constant.AuthConstant; import org.springblade.core.tenant.annotation.TenantIgnore; +import org.springblade.core.tool.api.FR; import org.springblade.core.tool.api.R; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.process.feign.IBusinessProcessClient; import org.springblade.transport.pojo.vo.CustomerArchiveVO; import org.springblade.transport.pojo.vo.CustomerChangeRecordVO; import org.springblade.transport.service.ICustomerArchiveService; @@ -46,6 +49,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; /** @@ -63,6 +68,7 @@ import java.util.Map; public class CustomerArchivePublicController { private final ICustomerArchiveService customerArchiveService; + private final IBusinessProcessClient businessProcessClient; /** * 公开详情 @@ -90,10 +96,57 @@ public class CustomerArchivePublicController { */ @PostMapping("/process-message") @ApiOperationSupport(order = 3) - @Operation(summary = "公开接收流程消息", description = "无需登录,当前仅打印接收数据") + @Operation(summary = "公开接收流程消息", description = "无需登录,接收后查询当前节点并打印") public R processMessage(@RequestBody Map body) { log.info("客商公开页收到流程消息:{}", JSON.toJSONString(body)); + Map formValues = asMap(body == null ? null : body.get("formValues")); + String processId = firstText(formValues, "processId"); + if (StringUtil.isBlank(processId) && body != null) { + processId = firstText(body, "processId"); + } + String loginName = firstText(formValues, "mkLoginName", "loginName"); + if (StringUtil.isBlank(processId)) { + log.warn("客商公开页流程消息未找到 processId,跳过查询当前节点"); + return R.success("ok"); + } + try { + FR result = businessProcessClient.getCurrentNodes(processId, loginName); + log.info("客商公开页流程消息当前节点详情 processId={} loginName={} result={}", + processId, loginName, JSON.toJSONString(result == null ? null : result.getData())); + } catch (Exception e) { + log.error("客商公开页查询当前节点失败 processId={} loginName={}", processId, loginName, e); + } return R.success("ok"); } + private static Map asMap(Object value) { + if (!(value instanceof Map map)) { + return Collections.emptyMap(); + } + Map result = new HashMap<>(); + map.forEach((key, nested) -> { + if (key != null) { + result.put(String.valueOf(key), nested); + } + }); + return result; + } + + private static String firstText(Map source, String... keys) { + if (source == null || keys == null) { + return null; + } + for (String key : keys) { + Object value = source.get(key); + if (value == null) { + continue; + } + String text = String.valueOf(value).trim(); + if (StringUtil.isNotBlank(text) && !"null".equalsIgnoreCase(text)) { + return text; + } + } + return null; + } + } diff --git a/doc/nacos/blade.yaml b/doc/nacos/blade.yaml index 6c40037a..052f97ef 100644 --- a/doc/nacos/blade.yaml +++ b/doc/nacos/blade.yaml @@ -220,6 +220,7 @@ blade: - /customer-archive/public/** - /blade-openapi/openApi/mk/process/commonCallback - /openApi/mk/process/commonCallback + - /feign/client/businessProcess/getCurrentNodes #授权认证配置 auth: - method: ALL From 2f56ca07cb5f2d3d4f50a9fb4f3bd1a5bd36b1c9 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Sun, 20 Sep 2026 17:35:59 +0800 Subject: [PATCH 23/55] =?UTF-8?q?=E8=B0=83=E8=AF=95mk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../process/feign/IBusinessProcessClient.java | 12 ++ .../feign/ICustomerArchiveClient.java | 26 ++++ .../pojo/dto/CustomerProcessNodeSyncDTO.java | 30 +++++ blade-service/blade-system/pom.xml | 4 + .../process/feign/BusinessProcessClient.java | 8 ++ .../service/IBusinessProcessService.java | 9 ++ .../impl/BusinessProcessServiceImpl.java | 108 ++++++++++++++++- .../CustomerArchivePublicController.java | 22 +++- .../feign/CustomerArchiveClient.java | 36 ++++++ .../service/ICustomerArchiveService.java | 20 ++++ .../impl/CustomerArchiveServiceImpl.java | 113 +++++++++++++++++- .../thirdparty/mk/feign/IMKClient.java | 9 ++ .../thirdparty/mk/service/IMKService.java | 8 ++ .../mk/service/impl/MKServiceImpl.java | 15 +++ doc/nacos/blade-dev.yaml | 1 + doc/nacos/blade-prod.yaml | 1 + doc/nacos/blade.yaml | 2 + 17 files changed, 419 insertions(+), 5 deletions(-) create mode 100644 blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/feign/ICustomerArchiveClient.java create mode 100644 blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/CustomerProcessNodeSyncDTO.java create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/feign/CustomerArchiveClient.java diff --git a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/feign/IBusinessProcessClient.java b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/feign/IBusinessProcessClient.java index d6e5fa35..ecbdf0c6 100644 --- a/blade-service-api/blade-process-api/src/main/java/org/springblade/process/feign/IBusinessProcessClient.java +++ b/blade-service-api/blade-process-api/src/main/java/org/springblade/process/feign/IBusinessProcessClient.java @@ -39,6 +39,7 @@ public interface IBusinessProcessClient { String QUERY_BUSINESS_PROCESS_SNAPSHOT = API_PREFIX + "/queryBusinessProcessSnapshot"; String QUERY_APPROVED_RECORD_LIST = API_PREFIX + "/queryApprovedRecordsNoAttachments"; String GET_CURRENT_NODES = API_PREFIX + "/getCurrentNodes"; + String GET_PROCESS_INFO = API_PREFIX + "/getProcessInfo"; /** * 提交业务流程 @@ -114,4 +115,15 @@ public interface IBusinessProcessClient { @GetMapping(GET_CURRENT_NODES) FR getCurrentNodes(@RequestParam("processInstanceId") String processInstanceId, @RequestParam(value = "loginName", required = false) String loginName); + + /** + * 获取流程实例详情 + * + * @param processInstanceId 流程实例id + * @param loginName MK登录名(手机号) + * @return 流程实例详情 + */ + @GetMapping(GET_PROCESS_INFO) + FR getProcessInfo(@RequestParam("processInstanceId") String processInstanceId, + @RequestParam(value = "loginName", required = false) String loginName); } diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/feign/ICustomerArchiveClient.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/feign/ICustomerArchiveClient.java new file mode 100644 index 00000000..53c7aa25 --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/feign/ICustomerArchiveClient.java @@ -0,0 +1,26 @@ +package org.springblade.transport.feign; + +import org.springblade.core.tool.api.FR; +import org.springblade.transport.pojo.dto.CustomerProcessNodeSyncDTO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +/** + * 客商档案 Feign接口 + */ +@FeignClient(value = "blade-transport") +public interface ICustomerArchiveClient { + + String API_PREFIX = "/feign/client/customerArchive"; + String SYNC_PROCESS_NODE = API_PREFIX + "/syncProcessNode"; + + /** + * 按 MK 当前节点同步客商当前节点、当前处理人和审批状态 + * + * @param param 同步参数 + * @return 是否成功 + */ + @PostMapping(SYNC_PROCESS_NODE) + FR syncProcessNode(@RequestBody CustomerProcessNodeSyncDTO param); +} diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/CustomerProcessNodeSyncDTO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/CustomerProcessNodeSyncDTO.java new file mode 100644 index 00000000..99168e5d --- /dev/null +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/CustomerProcessNodeSyncDTO.java @@ -0,0 +1,30 @@ +package org.springblade.transport.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 客商流程当前节点同步参数 + */ +@Data +@Schema(description = "客商流程当前节点同步参数") +public class CustomerProcessNodeSyncDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "客商ID") + private Long id; + + @Schema(description = "当前节点") + private String currentNode; + + @Schema(description = "当前处理人") + private String currentProcessor; + + @Schema(description = "审批状态") + private String approvalStatus; +} diff --git a/blade-service/blade-system/pom.xml b/blade-service/blade-system/pom.xml index 35b4f527..81ac5a93 100644 --- a/blade-service/blade-system/pom.xml +++ b/blade-service/blade-system/pom.xml @@ -62,6 +62,10 @@ org.springblade blade-mk-api + + org.springblade + blade-transport-api + diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/feign/BusinessProcessClient.java b/blade-service/blade-system/src/main/java/org/springblade/process/feign/BusinessProcessClient.java index b0f8b8e9..fa87f975 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/process/feign/BusinessProcessClient.java +++ b/blade-service/blade-system/src/main/java/org/springblade/process/feign/BusinessProcessClient.java @@ -86,4 +86,12 @@ public class BusinessProcessClient implements IBusinessProcessClient { @RequestParam(value = "loginName", required = false) String loginName) { return FR.data(businessProcessService.getCurrentNodes(processInstanceId, loginName)); } + + @PreAuth(AuthConstant.PERMIT_ALL) + @GetMapping(GET_PROCESS_INFO) + @Override + public FR getProcessInfo(@RequestParam("processInstanceId") String processInstanceId, + @RequestParam(value = "loginName", required = false) String loginName) { + return FR.data(businessProcessService.getProcessInfo(processInstanceId, loginName)); + } } diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java b/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java index ebdc83b2..2575abd2 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java +++ b/blade-service/blade-system/src/main/java/org/springblade/process/service/IBusinessProcessService.java @@ -43,6 +43,15 @@ public interface IBusinessProcessService extends IService { */ List getCurrentNodes(String processInstanceId, String loginName); + /** + * 获取流程实例详情 + * + * @param processInstanceId 流程实例id + * @param loginName MK登录名(手机号),可为空,为空时按流程发起人解析 + * @return 流程实例详情 + */ + Object getProcessInfo(String processInstanceId, String loginName); + /** * 修改业务流程状态 * @param param diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java index 4ad37246..3b39b91b 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java @@ -15,6 +15,7 @@ import org.springblade.process.pojo.enums.ApproveStatusEnum; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.log.utils.AssertUtils; import org.springblade.core.secure.utils.AuthUtil; +import org.springblade.core.tool.api.FR; import org.springblade.core.tool.utils.StringUtil; import org.springblade.process.convert.ApprovalConvert; import org.springblade.process.convert.BusinessProcessConvert; @@ -41,12 +42,15 @@ import org.springblade.thirdparty.mk.pojo.dto.approval.MKProcessDTO; import org.springblade.thirdparty.mk.pojo.dto.sort.*; import org.springblade.thirdparty.mk.pojo.vo.*; import org.springblade.thirdparty.mk.service.IMKService; +import org.springblade.transport.feign.ICustomerArchiveClient; +import org.springblade.transport.pojo.dto.CustomerProcessNodeSyncDTO; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.util.function.BiConsumer; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * 业务流程关联表 服务实现类 @@ -64,6 +68,7 @@ public class BusinessProcessServiceImpl extends ServiceImpl currentNodes = this.getCurrentNodes(processInstanceId, loginName); + this.syncCustomerArchiveCurrentNode(param.getFormInstanceId(), currentNodes); return processInstanceId; } @@ -193,6 +199,106 @@ public class BusinessProcessServiceImpl extends ServiceImpl currentNodes) { + if (StringUtils.isBlank(formInstanceId) || CollectionUtil.isEmpty(currentNodes)) { + log.warn("同步客商当前节点跳过,formInstanceId或节点详情为空 formInstanceId={}", formInstanceId); + return; + } + Long customerId; + try { + customerId = Long.valueOf(formInstanceId); + } catch (NumberFormatException e) { + log.warn("同步客商当前节点失败,表单实例id不是数字:{}", formInstanceId); + return; + } + String currentNode = currentNodes.stream() + .map(this::readMkNodeName) + .filter(StringUtils::isNotBlank) + .distinct() + .collect(Collectors.joining(",")); + String currentProcessor = currentNodes.stream() + .flatMap(this::readMkHandlerNames) + .filter(StringUtils::isNotBlank) + .distinct() + .collect(Collectors.joining(",")); + if (StringUtils.isBlank(currentNode) && StringUtils.isBlank(currentProcessor)) { + log.warn("同步客商当前节点跳过,未解析到节点名称或处理人 customerId={}", customerId); + return; + } + CustomerProcessNodeSyncDTO param = new CustomerProcessNodeSyncDTO(); + param.setId(customerId); + param.setCurrentNode(currentNode); + param.setCurrentProcessor(currentProcessor); + param.setApprovalStatus("reviewing"); + try { + FR result = customerArchiveClient.syncProcessNode(param); + log.info("同步客商当前节点完成 customerId={} currentNode={} currentProcessor={} result={}", + customerId, currentNode, currentProcessor, JSON.toJSONString(result)); + } catch (Exception e) { + log.error("同步客商当前节点异常 customerId={} currentNode={} currentProcessor={}", + customerId, currentNode, currentProcessor, e); + } + } + + private String readMkNodeName(Object node) { + if (node instanceof MKNodeVO vo) { + return vo.getNodeName(); + } + if (node instanceof Map map) { + Object value = map.get("nodeName"); + return value == null ? null : String.valueOf(value); + } + return null; + } + + private Stream readMkHandlerNames(Object node) { + List handlers = null; + if (node instanceof MKNodeVO vo) { + handlers = vo.getNodeHandlers(); + } else if (node instanceof Map map && map.get("nodeHandlers") instanceof List list) { + return list.stream().map(item -> { + if (item instanceof MKNodeHandlerVO handler) { + return handler.getHandlerName(); + } + if (item instanceof Map handlerMap) { + Object value = handlerMap.get("handlerName"); + return value == null ? null : String.valueOf(value); + } + return null; + }); + } + if (CollectionUtil.isEmpty(handlers)) { + return Stream.empty(); + } + return handlers.stream().map(MKNodeHandlerVO::getHandlerName); + } + /** * 从当前登录用户实体读取真实手机号(绕过接口返回脱敏) */ diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java index 3bbb3858..0308ab49 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java @@ -96,7 +96,7 @@ public class CustomerArchivePublicController { */ @PostMapping("/process-message") @ApiOperationSupport(order = 3) - @Operation(summary = "公开接收流程消息", description = "无需登录,接收后查询当前节点并打印") + @Operation(summary = "公开接收流程消息", description = "无需登录,接收后查询当前节点、流程实例详情并同步客商当前节点") public R processMessage(@RequestBody Map body) { log.info("客商公开页收到流程消息:{}", JSON.toJSONString(body)); Map formValues = asMap(body == null ? null : body.get("formValues")); @@ -111,11 +111,29 @@ public class CustomerArchivePublicController { } try { FR result = businessProcessClient.getCurrentNodes(processId, loginName); + Object nodeData = result == null ? null : result.getData(); log.info("客商公开页流程消息当前节点详情 processId={} loginName={} result={}", - processId, loginName, JSON.toJSONString(result == null ? null : result.getData())); + processId, loginName, JSON.toJSONString(nodeData)); + String customerIdText = firstText(asMap(body == null ? null : body.get("formData")), "id"); + if (StringUtil.isBlank(customerIdText)) { + log.warn("客商公开页流程消息未找到客商id,跳过同步当前节点"); + } else { + try { + customerArchiveService.syncProcessNodeFromMk(Long.valueOf(customerIdText), nodeData); + } catch (NumberFormatException e) { + log.warn("客商公开页流程消息客商id格式不正确:{}", customerIdText); + } + } } catch (Exception e) { log.error("客商公开页查询当前节点失败 processId={} loginName={}", processId, loginName, e); } + try { + FR processInfoResult = businessProcessClient.getProcessInfo(processId, loginName); + log.info("客商公开页流程消息流程实例详情 processId={} loginName={} result={}", + processId, loginName, JSON.toJSONString(processInfoResult == null ? null : processInfoResult.getData())); + } catch (Exception e) { + log.error("客商公开页查询流程实例详情失败 processId={} loginName={}", processId, loginName, e); + } return R.success("ok"); } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/feign/CustomerArchiveClient.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/feign/CustomerArchiveClient.java new file mode 100644 index 00000000..04ffe0a3 --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/feign/CustomerArchiveClient.java @@ -0,0 +1,36 @@ +package org.springblade.transport.feign; + +import io.swagger.v3.oas.annotations.Hidden; +import lombok.AllArgsConstructor; +import org.springblade.core.secure.annotation.PreAuth; +import org.springblade.core.secure.constant.AuthConstant; +import org.springblade.core.tenant.annotation.TenantIgnore; +import org.springblade.core.tool.api.FR; +import org.springblade.transport.pojo.dto.CustomerProcessNodeSyncDTO; +import org.springblade.transport.service.ICustomerArchiveService; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * 客商档案 Feign实现 + */ +@Hidden +@RestController +@AllArgsConstructor +public class CustomerArchiveClient implements ICustomerArchiveClient { + + private final ICustomerArchiveService customerArchiveService; + + @TenantIgnore + @PreAuth(AuthConstant.PERMIT_ALL) + @PostMapping(SYNC_PROCESS_NODE) + @Override + public FR syncProcessNode(@RequestBody CustomerProcessNodeSyncDTO param) { + if (param == null) { + return FR.data(false); + } + return FR.data(customerArchiveService.syncProcessNode( + param.getId(), param.getCurrentNode(), param.getCurrentProcessor(), param.getApprovalStatus())); + } +} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java index 9a6472b2..66257c09 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java @@ -100,6 +100,26 @@ public interface ICustomerArchiveService extends BaseService { */ boolean submitApproval(Long id); + /** + * 按 MK 当前节点同步客商当前节点、当前处理人和审批状态 + * + * @param id 客商ID + * @param currentNode 当前节点 + * @param currentProcessor 当前处理人 + * @param approvalStatus 审批状态,可为空,为空时保持为审核中 + * @return 是否成功 + */ + boolean syncProcessNode(Long id, String currentNode, String currentProcessor, String approvalStatus); + + /** + * 按 MK getCurrentNodes 返回结果同步客商当前节点 + * + * @param id 客商ID + * @param currentNodes MK 当前节点详情 + * @return 是否成功 + */ + boolean syncProcessNodeFromMk(Long id, Object currentNodes); + /** * 撤回审批并恢复草稿状态 * diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java index 8485807e..fbbdc4cf 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java @@ -29,6 +29,7 @@ import com.baomidou.mybatisplus.core.toolkit.IdWorker; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.mp.base.BaseServiceImpl; import org.springblade.core.secure.utils.AuthUtil; @@ -85,6 +86,7 @@ import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; @@ -101,6 +103,7 @@ import java.util.stream.Collectors; * * @author Chill */ +@Slf4j @Service @RequiredArgsConstructor public class CustomerArchiveServiceImpl extends BaseServiceImpl implements ICustomerArchiveService { @@ -214,12 +217,73 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl> nodes = normalizeMkNodeList(currentNodes); + if (nodes.isEmpty()) { + log.warn("同步客商当前节点跳过,节点详情为空 id={}", id); + return false; + } + List nodeNames = new ArrayList<>(); + List handlerNames = new ArrayList<>(); + for (Map node : nodes) { + String nodeName = Func.toStr(node.get("nodeName"), "").trim(); + if (Func.isNotEmpty(nodeName)) { + nodeNames.add(nodeName); + } + Object handlers = node.get("nodeHandlers"); + if (!(handlers instanceof Collection handlerList)) { + continue; + } + for (Object handler : handlerList) { + Map handlerMap = asStringObjectMap(handler); + String handlerName = Func.toStr(handlerMap.get("handlerName"), "").trim(); + if (Func.isNotEmpty(handlerName)) { + handlerNames.add(handlerName); + } + } + } + return syncProcessNode(id, joinDistinct(nodeNames), joinDistinct(handlerNames), APPROVAL_REVIEWING); + } + @Override @Transactional(rollbackFor = Exception.class) public boolean withdrawApproval(Long id) { @@ -1032,6 +1096,51 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl> normalizeMkNodeList(Object currentNodes) { + Object source = currentNodes; + if (source instanceof String text && Func.isNotEmpty(text.trim())) { + source = JsonUtil.parse(text, List.class); + } + if (source instanceof Collection collection) { + List> result = new ArrayList<>(); + for (Object item : collection) { + Map map = asStringObjectMap(item); + if (!map.isEmpty()) { + result.add(map); + } + } + return result; + } + Map single = asStringObjectMap(source); + return single.isEmpty() ? List.of() : List.of(single); + } + + private Map asStringObjectMap(Object value) { + if (value instanceof Map map) { + Map result = new LinkedHashMap<>(); + map.forEach((key, nested) -> { + if (key != null) { + result.put(String.valueOf(key), nested); + } + }); + return result; + } + if (value == null) { + return Map.of(); + } + try { + Map parsed = JsonUtil.toMap(JsonUtil.toJson(value)); + return parsed == null ? Map.of() : parsed; + } catch (Exception ignored) { + return Map.of(); + } + } + + private String joinDistinct(List values) { + return values.stream().filter(Func::isNotEmpty).distinct().collect(Collectors.joining(",")); + } + private void addChangeRecord(Long customerId, String content, CustomerArchive before, CustomerArchive after) { Map beforeSnapshot = customerSnapshot(before); Map afterSnapshot = customerSnapshot(after); diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/feign/IMKClient.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/feign/IMKClient.java index 75dbed48..64e38615 100644 --- a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/feign/IMKClient.java +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/feign/IMKClient.java @@ -65,6 +65,15 @@ public interface IMKClient { @PostMapping("${thirdParty.mk.getCurrentNodesUrl:/openapi/sys-lbpm/sysLbpmProcess/openSupport/getCurNodesInfo}") MKResultVO> getCurrentNodes(@RequestBody MKCurrentNodesDTO param, @RequestParam("access_token") String accessToken); + /** + * 获取流程实例详情 + * @param param + * @param accessToken + * @return + */ + @PostMapping("${thirdParty.mk.getProcessInfoUrl:/openapi/sys-lbpm/sysLbpmProcess/openSupport/getProcessInfo}") + MKResultVO getProcessInfo(@RequestBody MKCurrentNodesDTO param, @RequestParam("access_token") String accessToken); + /** * 获取节点处理人信息 * @param param diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/service/IMKService.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/service/IMKService.java index 79c6efbd..e391c6fc 100644 --- a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/service/IMKService.java +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/service/IMKService.java @@ -66,6 +66,14 @@ public interface IMKService { */ List getCurrentNodes(String processInstanceId, String loginName); + /** + * 获取流程实例详情 + * @param processInstanceId + * @param loginName + * @return + */ + Object getProcessInfo(String processInstanceId, String loginName); + /** * 获取节点处理人信息 * @param processInstanceId diff --git a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/service/impl/MKServiceImpl.java b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/service/impl/MKServiceImpl.java index f0f3fb57..e4b05985 100644 --- a/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/service/impl/MKServiceImpl.java +++ b/blade-third-party-api/blade-mk-api/src/main/java/org/springblade/thirdparty/mk/service/impl/MKServiceImpl.java @@ -246,6 +246,21 @@ public class MKServiceImpl implements IMKService { return nodesResult.getData(); } + @Override + public Object getProcessInfo(String processInstanceId, String loginName) { + String accessToken = getToken(); + MKCurrentNodesDTO param = new MKCurrentNodesDTO(); + param.setProcessInstanceId(processInstanceId); + param.setLoginName(loginName); + MKResultVO processInfoResult = client.getProcessInfo(param, accessToken); + log.info("调用mk接口 获取流程实例详情 :{}", JSONUtil.toJsonStr(processInfoResult)); + if (processInfoResult == null || !processInfoResult.isSuccess()) { + log.error("调用mk接口 获取流程实例详情异常:{}", JSONUtil.toJsonStr(processInfoResult)); + return null; + } + return processInfoResult.getData(); + } + @Override public MKAllHandlerVO getNodeHandlers(String processInstanceId, String loginName, String nodeId) { String accessToken = getToken(); diff --git a/doc/nacos/blade-dev.yaml b/doc/nacos/blade-dev.yaml index d6b08211..744c59f6 100644 --- a/doc/nacos/blade-dev.yaml +++ b/doc/nacos/blade-dev.yaml @@ -118,6 +118,7 @@ thirdParty: processExecuteUrl: ${MK_PROCESS_EXECUTE_URL:/openapi/sys-lbpm/sysLbpmProcess/openSupport/execute} processDeleteUrl: ${MK_PROCESS_DELETE_URL:/openapi/sys-lbpm/sysLbpmProcess/openSupport/delete} getCurrentNodesUrl: ${MK_CURRENT_NODES_URL:/openapi/sys-lbpm/sysLbpmProcess/openSupport/getCurNodesInfo} + getProcessInfoUrl: ${MK_PROCESS_INFO_URL:/openapi/sys-lbpm/sysLbpmProcess/openSupport/getProcessInfo} getNodeHandlersUrl: ${MK_NODE_HANDLERS_URL:/openapi/sys-lbpm/sysLbpmProcess/openSupport/getNodeHandlerInfos} getManualNodeUrl: ${MK_MANUAL_NODE_URL:/openapi/sys-lbpm/sysLbpmTemplate/openSupport/getManualNode} pushOrgDeptUrl: ${MK_PUSH_ORG_DEPT_URL:/openapi/sys-org/v2/push/orgDept} diff --git a/doc/nacos/blade-prod.yaml b/doc/nacos/blade-prod.yaml index 384f9043..ab3d3562 100644 --- a/doc/nacos/blade-prod.yaml +++ b/doc/nacos/blade-prod.yaml @@ -91,6 +91,7 @@ thirdParty: processExecuteUrl: ${MK_PROCESS_EXECUTE_URL:/openapi/sys-lbpm/sysLbpmProcess/openSupport/execute} processDeleteUrl: ${MK_PROCESS_DELETE_URL:/openapi/sys-lbpm/sysLbpmProcess/openSupport/delete} getCurrentNodesUrl: ${MK_CURRENT_NODES_URL:/openapi/sys-lbpm/sysLbpmProcess/openSupport/getCurNodesInfo} + getProcessInfoUrl: ${MK_PROCESS_INFO_URL:/openapi/sys-lbpm/sysLbpmProcess/openSupport/getProcessInfo} getNodeHandlersUrl: ${MK_NODE_HANDLERS_URL:/openapi/sys-lbpm/sysLbpmProcess/openSupport/getNodeHandlerInfos} getManualNodeUrl: ${MK_MANUAL_NODE_URL:/openapi/sys-lbpm/sysLbpmTemplate/openSupport/getManualNode} pushOrgDeptUrl: ${MK_PUSH_ORG_DEPT_URL:/openapi/sys-org/v2/push/orgDept} diff --git a/doc/nacos/blade.yaml b/doc/nacos/blade.yaml index 052f97ef..f3052f25 100644 --- a/doc/nacos/blade.yaml +++ b/doc/nacos/blade.yaml @@ -221,6 +221,8 @@ blade: - /blade-openapi/openApi/mk/process/commonCallback - /openApi/mk/process/commonCallback - /feign/client/businessProcess/getCurrentNodes + - /feign/client/businessProcess/getProcessInfo + - /feign/client/customerArchive/syncProcessNode #授权认证配置 auth: - method: ALL From 98697af6df3b274dc618c2a1c91080dfc3cff8f1 Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Sun, 20 Sep 2026 17:51:29 +0800 Subject: [PATCH 24/55] =?UTF-8?q?:bug:=20=E4=BF=AE=E5=A4=8D=E8=BF=9D?= =?UTF-8?q?=E7=AB=A0=E8=AE=B0=E5=BD=95=E5=AF=BC=E5=85=A5=E7=9A=84=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E4=B8=8E=E4=BA=8B=E9=A1=B9=E5=AD=97=E6=AE=B5=E6=B7=B7?= =?UTF-8?q?=E6=B7=86=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 导入模型将合并的"类型/事项"列拆分为"*类型"与"*事项"两列,与导出模型保持一致 - 校验与清空逻辑改为按车船类型严格匹配:车辆只允许填类型,船舶只允许填事项,误填对侧字段直接报错而非静默丢弃 - 将清空对侧字段的时机从 prepare 移到 validate 之后,避免用户误填内容被静默丢弃 - 提取 CAR/SHIP 常量,替换散落的"车辆"/"船舶"字面量 --- .../excel/ViolationRecordImportExcel.java | 7 +++- .../impl/ViolationRecordServiceImpl.java | 39 ++++++++++++------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ViolationRecordImportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ViolationRecordImportExcel.java index f3e7f6b6..4eb35026 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ViolationRecordImportExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ViolationRecordImportExcel.java @@ -60,8 +60,11 @@ public class ViolationRecordImportExcel implements Serializable { @ExcelProperty("*驾驶人") private String driverName; - @ExcelProperty("*类型/事项") - private String violationTypeOrItem; + @ExcelProperty("*类型") + private String violationType; + + @ExcelProperty("*事项") + private String violationItem; @ExcelProperty("*时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ViolationRecordServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ViolationRecordServiceImpl.java index ed9dfe9d..4d99a4f8 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ViolationRecordServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ViolationRecordServiceImpl.java @@ -67,6 +67,8 @@ public class ViolationRecordServiceImpl extends BaseServiceImpl selectViolationRecordPage(IPage page, ViolationRecordVO violationRecord) { @@ -81,6 +83,7 @@ public class ViolationRecordServiceImpl extends BaseServiceImpl + * 必须在 validate 之后执行:校验需要看到用户填了什么, + * 若提前清空,误填的内容会被静默丢弃,用户无从察觉。 + */ + private void clearIrrelevantField(ViolationRecord violationRecord) { + if (CAR.equals(violationRecord.getVehicleType())) { violationRecord.setViolationItem(null); } else { violationRecord.setViolationType(null); } - if (UNPROCESSED.equals(violationRecord.getProcessStatus())) { - violationRecord.setProcessResult(null); - } } private void validate(ViolationRecord violationRecord) { if (Func.isEmpty(violationRecord.getVehicleType())) { throw new ServiceException("车船类型不能为空"); } - if (!"车辆".equals(violationRecord.getVehicleType()) && !"船舶".equals(violationRecord.getVehicleType())) { + if (!CAR.equals(violationRecord.getVehicleType()) && !SHIP.equals(violationRecord.getVehicleType())) { throw new ServiceException("车船类型不正确"); } if (Func.isEmpty(violationRecord.getVehicleNo())) { @@ -155,12 +162,18 @@ public class ViolationRecordServiceImpl extends BaseServiceImpl Date: Sun, 20 Sep 2026 18:12:45 +0800 Subject: [PATCH 25/55] =?UTF-8?q?=E8=B0=83=E8=AF=95mk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/BusinessProcessServiceImpl.java | 1 + .../CustomerArchivePublicController.java | 38 ++++++++++++- .../service/ICustomerArchiveService.java | 18 ++++++ .../impl/CustomerArchiveServiceImpl.java | 56 +++++++++++++++++++ 4 files changed, 111 insertions(+), 2 deletions(-) diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java index 3b39b91b..1806d525 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java @@ -170,6 +170,7 @@ public class BusinessProcessServiceImpl extends ServiceImpl currentNodes = this.getCurrentNodes(processInstanceId, loginName); this.syncCustomerArchiveCurrentNode(param.getFormInstanceId(), currentNodes); + this.getProcessInfo(processInstanceId, loginName); return processInstanceId; } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java index 0308ab49..c339692e 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java @@ -109,12 +109,12 @@ public class CustomerArchivePublicController { log.warn("客商公开页流程消息未找到 processId,跳过查询当前节点"); return R.success("ok"); } + String customerIdText = firstText(asMap(body == null ? null : body.get("formData")), "id"); try { FR result = businessProcessClient.getCurrentNodes(processId, loginName); Object nodeData = result == null ? null : result.getData(); log.info("客商公开页流程消息当前节点详情 processId={} loginName={} result={}", processId, loginName, JSON.toJSONString(nodeData)); - String customerIdText = firstText(asMap(body == null ? null : body.get("formData")), "id"); if (StringUtil.isBlank(customerIdText)) { log.warn("客商公开页流程消息未找到客商id,跳过同步当前节点"); } else { @@ -129,14 +129,48 @@ public class CustomerArchivePublicController { } try { FR processInfoResult = businessProcessClient.getProcessInfo(processId, loginName); + Object processInfo = processInfoResult == null ? null : processInfoResult.getData(); log.info("客商公开页流程消息流程实例详情 processId={} loginName={} result={}", - processId, loginName, JSON.toJSONString(processInfoResult == null ? null : processInfoResult.getData())); + processId, loginName, JSON.toJSONString(processInfo)); + if (isProcessFinished(processInfo)) { + applyProcessResult(customerIdText, processId, formValues, true); + } else if (isProcessRejected(processInfo)) { + applyProcessResult(customerIdText, processId, formValues, false); + } } catch (Exception e) { log.error("客商公开页查询流程实例详情失败 processId={} loginName={}", processId, loginName, e); } return R.success("ok"); } + private static boolean isProcessFinished(Object processInfo) { + return "30".equals(firstText(asMap(processInfo), "fdProcessStatus")); + } + + private static boolean isProcessRejected(Object processInfo) { + return "20".equals(firstText(asMap(processInfo), "fdProcessStatus")); + } + + private void applyProcessResult(String customerIdText, String processId, Map formValues, + boolean approved) { + String action = approved ? "审核通过" : "审核驳回"; + if (StringUtil.isBlank(customerIdText)) { + log.warn("流程{}但未找到客商id,跳过同步 processId={}", action, processId); + return; + } + try { + String processorName = firstText(formValues, "mkUserName", "mkLoginName"); + Long customerId = Long.valueOf(customerIdText); + if (approved) { + customerArchiveService.approveFromProcess(customerId, processorName); + } else { + customerArchiveService.rejectFromProcess(customerId, processorName); + } + } catch (NumberFormatException e) { + log.warn("流程{}但客商id格式不正确:{}", action, customerIdText); + } + } + private static Map asMap(Object value) { if (!(value instanceof Map map)) { return Collections.emptyMap(); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java index 66257c09..25415454 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java @@ -120,6 +120,24 @@ public interface ICustomerArchiveService extends BaseService { */ boolean syncProcessNodeFromMk(Long id, Object currentNodes); + /** + * MK 流程结束(fdProcessStatus=30)时将客商置为审核通过 + * + * @param id 客商ID + * @param processorName 当前处理人,可为空 + * @return 是否成功 + */ + boolean approveFromProcess(Long id, String processorName); + + /** + * MK 流程状态为 20 时将客商置为审核驳回 + * + * @param id 客商ID + * @param processorName 当前处理人,可为空 + * @return 是否成功 + */ + boolean rejectFromProcess(Long id, String processorName); + /** * 撤回审批并恢复草稿状态 * diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java index fbbdc4cf..fe43c517 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java @@ -284,6 +284,62 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl Date: Sun, 20 Sep 2026 18:34:40 +0800 Subject: [PATCH 26/55] =?UTF-8?q?=E8=B0=83=E8=AF=95mk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pojo/dto/CustomerProcessNodeSyncDTO.java | 3 ++ .../impl/BusinessProcessServiceImpl.java | 35 ++++---------- .../CustomerArchivePublicController.java | 29 ++++++++---- .../feign/CustomerArchiveClient.java | 3 ++ .../service/ICustomerArchiveService.java | 9 ++++ .../impl/CustomerArchiveServiceImpl.java | 46 ++++++++++++++++++- 6 files changed, 88 insertions(+), 37 deletions(-) diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/CustomerProcessNodeSyncDTO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/CustomerProcessNodeSyncDTO.java index 99168e5d..f30493ae 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/CustomerProcessNodeSyncDTO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/dto/CustomerProcessNodeSyncDTO.java @@ -27,4 +27,7 @@ public class CustomerProcessNodeSyncDTO implements Serializable { @Schema(description = "审批状态") private String approvalStatus; + + @Schema(description = "MK 流程实例详情,传入后按 currentHandlers.fdName 和 handlerInfos.nodeName 回写") + private Object processInfo; } diff --git a/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java index 1806d525..1b42e380 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/process/service/impl/BusinessProcessServiceImpl.java @@ -168,9 +168,9 @@ public class BusinessProcessServiceImpl extends ServiceImpl currentNodes = this.getCurrentNodes(processInstanceId, loginName); - this.syncCustomerArchiveCurrentNode(param.getFormInstanceId(), currentNodes); - this.getProcessInfo(processInstanceId, loginName); + this.getCurrentNodes(processInstanceId, loginName); + Object processInfo = this.getProcessInfo(processInstanceId, loginName); + this.syncCustomerArchiveFromProcessInfo(param.getFormInstanceId(), processInfo); return processInstanceId; } @@ -226,9 +226,9 @@ public class BusinessProcessServiceImpl extends ServiceImpl currentNodes) { - if (StringUtils.isBlank(formInstanceId) || CollectionUtil.isEmpty(currentNodes)) { - log.warn("同步客商当前节点跳过,formInstanceId或节点详情为空 formInstanceId={}", formInstanceId); + private void syncCustomerArchiveFromProcessInfo(String formInstanceId, Object processInfo) { + if (StringUtils.isBlank(formInstanceId) || processInfo == null) { + log.warn("同步客商当前节点跳过,formInstanceId或流程实例详情为空 formInstanceId={}", formInstanceId); return; } Long customerId; @@ -238,32 +238,15 @@ public class BusinessProcessServiceImpl extends ServiceImpl result = customerArchiveClient.syncProcessNode(param); - log.info("同步客商当前节点完成 customerId={} currentNode={} currentProcessor={} result={}", - customerId, currentNode, currentProcessor, JSON.toJSONString(result)); + log.info("同步客商当前节点完成 customerId={} result={}", customerId, JSON.toJSONString(result)); } catch (Exception e) { - log.error("同步客商当前节点异常 customerId={} currentNode={} currentProcessor={}", - customerId, currentNode, currentProcessor, e); + log.error("同步客商当前节点异常 customerId={}", customerId, e); } } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java index c339692e..19ed444f 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java @@ -49,6 +49,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -115,15 +116,6 @@ public class CustomerArchivePublicController { Object nodeData = result == null ? null : result.getData(); log.info("客商公开页流程消息当前节点详情 processId={} loginName={} result={}", processId, loginName, JSON.toJSONString(nodeData)); - if (StringUtil.isBlank(customerIdText)) { - log.warn("客商公开页流程消息未找到客商id,跳过同步当前节点"); - } else { - try { - customerArchiveService.syncProcessNodeFromMk(Long.valueOf(customerIdText), nodeData); - } catch (NumberFormatException e) { - log.warn("客商公开页流程消息客商id格式不正确:{}", customerIdText); - } - } } catch (Exception e) { log.error("客商公开页查询当前节点失败 processId={} loginName={}", processId, loginName, e); } @@ -136,6 +128,12 @@ public class CustomerArchivePublicController { applyProcessResult(customerIdText, processId, formValues, true); } else if (isProcessRejected(processInfo)) { applyProcessResult(customerIdText, processId, formValues, false); + } else if (StringUtil.isNotBlank(customerIdText)) { + try { + customerArchiveService.syncProcessNodeFromProcessInfo(Long.valueOf(customerIdText), processInfo); + } catch (NumberFormatException e) { + log.warn("客商公开页流程消息客商id格式不正确:{}", customerIdText); + } } } catch (Exception e) { log.error("客商公开页查询流程实例详情失败 processId={} loginName={}", processId, loginName, e); @@ -148,7 +146,18 @@ public class CustomerArchivePublicController { } private static boolean isProcessRejected(Object processInfo) { - return "20".equals(firstText(asMap(processInfo), "fdProcessStatus")); + Map info = asMap(processInfo); + if (!"20".equals(firstText(info, "fdProcessStatus"))) { + return false; + } + if (hasItems(info.get("currentHandlers"))) { + return false; + } + return !hasItems(asMap(info.get("fdTaskInfo")).get("handlerInfos")); + } + + private static boolean hasItems(Object value) { + return value instanceof Collection collection && !collection.isEmpty(); } private void applyProcessResult(String customerIdText, String processId, Map formValues, diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/feign/CustomerArchiveClient.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/feign/CustomerArchiveClient.java index 04ffe0a3..3b7a7167 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/feign/CustomerArchiveClient.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/feign/CustomerArchiveClient.java @@ -30,6 +30,9 @@ public class CustomerArchiveClient implements ICustomerArchiveClient { if (param == null) { return FR.data(false); } + if (param.getProcessInfo() != null) { + return FR.data(customerArchiveService.syncProcessNodeFromProcessInfo(param.getId(), param.getProcessInfo())); + } return FR.data(customerArchiveService.syncProcessNode( param.getId(), param.getCurrentNode(), param.getCurrentProcessor(), param.getApprovalStatus())); } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java index 25415454..b822a2f0 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/ICustomerArchiveService.java @@ -120,6 +120,15 @@ public interface ICustomerArchiveService extends BaseService { */ boolean syncProcessNodeFromMk(Long id, Object currentNodes); + /** + * 按 MK 流程实例详情回写当前节点、当前处理人 + * + * @param id 客商ID + * @param processInfo MK getProcessInfo 返回数据 + * @return 是否成功 + */ + boolean syncProcessNodeFromProcessInfo(Long id, Object processInfo); + /** * MK 流程结束(fdProcessStatus=30)时将客商置为审核通过 * diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java index fe43c517..cc389292 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java @@ -284,6 +284,28 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl info = asStringObjectMap(processInfo); + if (info.isEmpty()) { + log.warn("同步客商当前节点跳过,流程实例详情为空 id={}", id); + return false; + } + String currentProcessor = joinDistinct(extractTexts(info.get("currentHandlers"), "fdName", "name"), "、"); + Map taskInfo = asStringObjectMap(info.get("fdTaskInfo")); + String currentNode = joinDistinct(extractTexts(taskInfo.get("handlerInfos"), "nodeName"), "、"); + if (Func.isEmpty(currentNode) && Func.isEmpty(currentProcessor)) { + log.warn("同步客商当前节点跳过,未解析到节点名称或处理人 id={}", id); + return false; + } + return syncProcessNode(id, currentNode, currentProcessor, APPROVAL_REVIEWING); + } + @Override @TenantIgnore @Transactional(rollbackFor = Exception.class) @@ -1194,7 +1216,29 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl values) { - return values.stream().filter(Func::isNotEmpty).distinct().collect(Collectors.joining(",")); + return joinDistinct(values, ","); + } + + private String joinDistinct(List values, String delimiter) { + return values.stream().filter(Func::isNotEmpty).distinct().collect(Collectors.joining(delimiter)); + } + + private List extractTexts(Object listObj, String... keys) { + List result = new ArrayList<>(); + if (!(listObj instanceof Collection collection) || keys == null) { + return result; + } + for (Object item : collection) { + Map map = asStringObjectMap(item); + for (String key : keys) { + String text = Func.toStr(map.get(key), "").trim(); + if (Func.isNotEmpty(text)) { + result.add(text); + break; + } + } + } + return result; } private void addChangeRecord(Long customerId, String content, CustomerArchive before, CustomerArchive after) { From 86adb47392e020085fabf92d9955678b38c8f5a4 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Sun, 20 Sep 2026 19:03:13 +0800 Subject: [PATCH 27/55] =?UTF-8?q?=E8=B0=83=E8=AF=95mk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CustomerArchivePublicController.java | 121 +----------- .../CustomerArchivePublicProcessService.java | 182 ++++++++++++++++++ 2 files changed, 187 insertions(+), 116 deletions(-) create mode 100644 blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchivePublicProcessService.java diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java index 19ed444f..1df02ea9 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/CustomerArchivePublicController.java @@ -35,13 +35,11 @@ import org.springblade.core.mp.support.Query; import org.springblade.core.secure.annotation.PreAuth; import org.springblade.core.secure.constant.AuthConstant; import org.springblade.core.tenant.annotation.TenantIgnore; -import org.springblade.core.tool.api.FR; import org.springblade.core.tool.api.R; -import org.springblade.core.tool.utils.StringUtil; -import org.springblade.process.feign.IBusinessProcessClient; import org.springblade.transport.pojo.vo.CustomerArchiveVO; import org.springblade.transport.pojo.vo.CustomerChangeRecordVO; import org.springblade.transport.service.ICustomerArchiveService; +import org.springblade.transport.service.impl.CustomerArchivePublicProcessService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -49,9 +47,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; import java.util.Map; /** @@ -69,7 +64,7 @@ import java.util.Map; public class CustomerArchivePublicController { private final ICustomerArchiveService customerArchiveService; - private final IBusinessProcessClient businessProcessClient; + private final CustomerArchivePublicProcessService customerArchivePublicProcessService; /** * 公开详情 @@ -93,121 +88,15 @@ public class CustomerArchivePublicController { } /** - * 公开接收流程页 postMessage 数据(当前仅打印,便于联调) + * 公开接收流程页 postMessage 数据 */ @PostMapping("/process-message") @ApiOperationSupport(order = 3) - @Operation(summary = "公开接收流程消息", description = "无需登录,接收后查询当前节点、流程实例详情并同步客商当前节点") + @Operation(summary = "公开接收流程消息", description = "无需登录,立即查询当前节点,5秒后查询流程实例详情并同步客商") public R processMessage(@RequestBody Map body) { log.info("客商公开页收到流程消息:{}", JSON.toJSONString(body)); - Map formValues = asMap(body == null ? null : body.get("formValues")); - String processId = firstText(formValues, "processId"); - if (StringUtil.isBlank(processId) && body != null) { - processId = firstText(body, "processId"); - } - String loginName = firstText(formValues, "mkLoginName", "loginName"); - if (StringUtil.isBlank(processId)) { - log.warn("客商公开页流程消息未找到 processId,跳过查询当前节点"); - return R.success("ok"); - } - String customerIdText = firstText(asMap(body == null ? null : body.get("formData")), "id"); - try { - FR result = businessProcessClient.getCurrentNodes(processId, loginName); - Object nodeData = result == null ? null : result.getData(); - log.info("客商公开页流程消息当前节点详情 processId={} loginName={} result={}", - processId, loginName, JSON.toJSONString(nodeData)); - } catch (Exception e) { - log.error("客商公开页查询当前节点失败 processId={} loginName={}", processId, loginName, e); - } - try { - FR processInfoResult = businessProcessClient.getProcessInfo(processId, loginName); - Object processInfo = processInfoResult == null ? null : processInfoResult.getData(); - log.info("客商公开页流程消息流程实例详情 processId={} loginName={} result={}", - processId, loginName, JSON.toJSONString(processInfo)); - if (isProcessFinished(processInfo)) { - applyProcessResult(customerIdText, processId, formValues, true); - } else if (isProcessRejected(processInfo)) { - applyProcessResult(customerIdText, processId, formValues, false); - } else if (StringUtil.isNotBlank(customerIdText)) { - try { - customerArchiveService.syncProcessNodeFromProcessInfo(Long.valueOf(customerIdText), processInfo); - } catch (NumberFormatException e) { - log.warn("客商公开页流程消息客商id格式不正确:{}", customerIdText); - } - } - } catch (Exception e) { - log.error("客商公开页查询流程实例详情失败 processId={} loginName={}", processId, loginName, e); - } + customerArchivePublicProcessService.handleProcessMessage(body); return R.success("ok"); } - private static boolean isProcessFinished(Object processInfo) { - return "30".equals(firstText(asMap(processInfo), "fdProcessStatus")); - } - - private static boolean isProcessRejected(Object processInfo) { - Map info = asMap(processInfo); - if (!"20".equals(firstText(info, "fdProcessStatus"))) { - return false; - } - if (hasItems(info.get("currentHandlers"))) { - return false; - } - return !hasItems(asMap(info.get("fdTaskInfo")).get("handlerInfos")); - } - - private static boolean hasItems(Object value) { - return value instanceof Collection collection && !collection.isEmpty(); - } - - private void applyProcessResult(String customerIdText, String processId, Map formValues, - boolean approved) { - String action = approved ? "审核通过" : "审核驳回"; - if (StringUtil.isBlank(customerIdText)) { - log.warn("流程{}但未找到客商id,跳过同步 processId={}", action, processId); - return; - } - try { - String processorName = firstText(formValues, "mkUserName", "mkLoginName"); - Long customerId = Long.valueOf(customerIdText); - if (approved) { - customerArchiveService.approveFromProcess(customerId, processorName); - } else { - customerArchiveService.rejectFromProcess(customerId, processorName); - } - } catch (NumberFormatException e) { - log.warn("流程{}但客商id格式不正确:{}", action, customerIdText); - } - } - - private static Map asMap(Object value) { - if (!(value instanceof Map map)) { - return Collections.emptyMap(); - } - Map result = new HashMap<>(); - map.forEach((key, nested) -> { - if (key != null) { - result.put(String.valueOf(key), nested); - } - }); - return result; - } - - private static String firstText(Map source, String... keys) { - if (source == null || keys == null) { - return null; - } - for (String key : keys) { - Object value = source.get(key); - if (value == null) { - continue; - } - String text = String.valueOf(value).trim(); - if (StringUtil.isNotBlank(text) && !"null".equalsIgnoreCase(text)) { - return text; - } - } - return null; - } - } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchivePublicProcessService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchivePublicProcessService.java new file mode 100644 index 00000000..56a22a3d --- /dev/null +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchivePublicProcessService.java @@ -0,0 +1,182 @@ +/** + * BladeX Commercial License Agreement + * Copyright (c) 2018-2099, https://bladex.cn. All rights reserved. + */ +package org.springblade.transport.service.impl; + +import com.alibaba.fastjson2.JSON; +import jakarta.annotation.PreDestroy; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.tenant.annotation.TenantIgnore; +import org.springblade.core.tool.api.FR; +import org.springblade.core.tool.utils.StringUtil; +import org.springblade.process.feign.IBusinessProcessClient; +import org.springblade.transport.service.ICustomerArchiveService; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 客商公开页流程消息处理:当前节点立即查询,流程实例详情延迟查询。 + */ +@Slf4j +@Service +@TenantIgnore +@RequiredArgsConstructor +public class CustomerArchivePublicProcessService { + + private static final long PROCESS_INFO_DELAY_SECONDS = 5L; + + private final ICustomerArchiveService customerArchiveService; + private final IBusinessProcessClient businessProcessClient; + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2, runnable -> { + Thread thread = new Thread(runnable, "customer-archive-process-info-" + THREAD_INDEX.incrementAndGet()); + thread.setDaemon(true); + return thread; + }); + + private static final AtomicInteger THREAD_INDEX = new AtomicInteger(); + + @PreDestroy + public void shutdown() { + scheduler.shutdown(); + } + + public void handleProcessMessage(Map body) { + Map formValues = asMap(body == null ? null : body.get("formValues")); + String processId = firstText(formValues, "processId"); + if (StringUtil.isBlank(processId) && body != null) { + processId = firstText(body, "processId"); + } + String loginName = firstText(formValues, "mkLoginName", "loginName"); + if (StringUtil.isBlank(processId)) { + log.warn("客商公开页流程消息未找到 processId,跳过查询当前节点"); + return; + } + String customerIdText = firstText(asMap(body == null ? null : body.get("formData")), "id"); + queryCurrentNodes(processId, loginName); + scheduleProcessInfo(processId, loginName, customerIdText, formValues); + } + + private void queryCurrentNodes(String processId, String loginName) { + try { + FR result = businessProcessClient.getCurrentNodes(processId, loginName); + Object nodeData = result == null ? null : result.getData(); + log.info("客商公开页流程消息当前节点详情 processId={} loginName={} result={}", + processId, loginName, JSON.toJSONString(nodeData)); + } catch (Exception e) { + log.error("客商公开页查询当前节点失败 processId={} loginName={}", processId, loginName, e); + } + } + + private void scheduleProcessInfo(String processId, String loginName, String customerIdText, + Map formValues) { + Map formValuesCopy = new HashMap<>(formValues == null ? Map.of() : formValues); + log.info("客商公开页将在{}秒后查询流程实例详情 processId={}", PROCESS_INFO_DELAY_SECONDS, processId); + scheduler.schedule( + () -> queryProcessInfo(processId, loginName, customerIdText, formValuesCopy), + PROCESS_INFO_DELAY_SECONDS, + TimeUnit.SECONDS + ); + } + + private void queryProcessInfo(String processId, String loginName, String customerIdText, + Map formValues) { + try { + FR processInfoResult = businessProcessClient.getProcessInfo(processId, loginName); + Object processInfo = processInfoResult == null ? null : processInfoResult.getData(); + log.info("客商公开页流程消息流程实例详情 processId={} loginName={} result={}", + processId, loginName, JSON.toJSONString(processInfo)); + if (isProcessFinished(processInfo)) { + applyProcessResult(customerIdText, processId, formValues, true); + } else if (isProcessRejected(processInfo)) { + applyProcessResult(customerIdText, processId, formValues, false); + } else if (StringUtil.isNotBlank(customerIdText)) { + try { + customerArchiveService.syncProcessNodeFromProcessInfo(Long.valueOf(customerIdText), processInfo); + } catch (NumberFormatException e) { + log.warn("客商公开页流程消息客商id格式不正确:{}", customerIdText); + } + } + } catch (Exception e) { + log.error("客商公开页查询流程实例详情失败 processId={} loginName={}", processId, loginName, e); + } + } + + private static boolean isProcessFinished(Object processInfo) { + return "30".equals(firstText(asMap(processInfo), "fdProcessStatus")); + } + + private static boolean isProcessRejected(Object processInfo) { + Map info = asMap(processInfo); + if (!"20".equals(firstText(info, "fdProcessStatus"))) { + return false; + } + if (hasItems(info.get("currentHandlers"))) { + return false; + } + return !hasItems(asMap(info.get("fdTaskInfo")).get("handlerInfos")); + } + + private static boolean hasItems(Object value) { + return value instanceof Collection collection && !collection.isEmpty(); + } + + private void applyProcessResult(String customerIdText, String processId, Map formValues, + boolean approved) { + String action = approved ? "审核通过" : "审核驳回"; + if (StringUtil.isBlank(customerIdText)) { + log.warn("流程{}但未找到客商id,跳过同步 processId={}", action, processId); + return; + } + try { + String processorName = firstText(formValues, "mkUserName", "mkLoginName"); + Long customerId = Long.valueOf(customerIdText); + if (approved) { + customerArchiveService.approveFromProcess(customerId, processorName); + } else { + customerArchiveService.rejectFromProcess(customerId, processorName); + } + } catch (NumberFormatException e) { + log.warn("流程{}但客商id格式不正确:{}", action, customerIdText); + } + } + + private static Map asMap(Object value) { + if (!(value instanceof Map map)) { + return Collections.emptyMap(); + } + Map result = new HashMap<>(); + map.forEach((key, nested) -> { + if (key != null) { + result.put(String.valueOf(key), nested); + } + }); + return result; + } + + private static String firstText(Map source, String... keys) { + if (source == null || keys == null) { + return null; + } + for (String key : keys) { + Object value = source.get(key); + if (value == null) { + continue; + } + String text = String.valueOf(value).trim(); + if (StringUtil.isNotBlank(text) && !"null".equalsIgnoreCase(text)) { + return text; + } + } + return null; + } +} From af1430eaee7a642a91bc775c900d7fd1acdcf86b Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Sun, 20 Sep 2026 19:31:58 +0800 Subject: [PATCH 28/55] =?UTF-8?q?:bug:=20=E4=BF=AE=E5=A4=8D=E6=9C=AA?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=20MinIO=20=E6=97=B6=20transport=20=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E6=97=A0=E6=B3=95=E5=90=AF=E5=8A=A8=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 凭证图片 MinIO 客户端此前为无条件装配,缺少 file.storage.minio.endpoint 配置时会在启动阶段直接抛 IllegalArgumentException,导致整个 blade-transport 服务起不来,并在关闭失败上下文时连带抛出 nacosGracefulShutdownDelegate 的 BeanCreationNotAllowedException, 掩盖了真正的失败原因。 - VoucherMinioConfig 增加 @ConditionalOnProperty,未配置 endpoint 时不注册客户端 - ProcessConfigController、VoucherManageServiceImpl 改用 ObjectProvider 可选注入,避免构造器注入因 bean 缺失而启动失败 - 新增 minioClient() 访问器,在真正调用时给出"未配置"的明确提示, 与已有 validateMinioConfig() 的提示风格保持一致 未配置 MinIO 时服务可正常启动,凭证相关接口仅在使用时提示配置缺失; 已配置 MinIO 时行为不变。 --- .../transport/config/VoucherMinioConfig.java | 6 ++++ .../controller/ProcessConfigController.java | 25 ++++++++++--- .../impl/VoucherManageServiceImpl.java | 35 +++++++++++++------ 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/config/VoucherMinioConfig.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/config/VoucherMinioConfig.java index 5c0545b0..08ed5e38 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/config/VoucherMinioConfig.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/config/VoucherMinioConfig.java @@ -6,14 +6,20 @@ package org.springblade.transport.config; import io.minio.MinioClient; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 凭证图片 MinIO 客户端配置。 + *

* 连接参数由 Nacos 的 file.storage.minio 配置提供。 + * 未配置 {@code file.storage.minio.endpoint} 时不注册该客户端, + * 以免凭证上传功能缺失配置导致整个 blade-transport 服务无法启动; + * 此时凭证相关接口会在调用时给出明确提示,而非启动即失败。 */ @Configuration +@ConditionalOnProperty(prefix = "file.storage.minio", name = "endpoint") public class VoucherMinioConfig { @Bean diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ProcessConfigController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ProcessConfigController.java index 8df54ded..c0cb3d30 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ProcessConfigController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ProcessConfigController.java @@ -52,6 +52,7 @@ import org.springblade.transport.pojo.entity.Waybill; import org.springblade.transport.pojo.vo.BusinessRemoveResultVO; import org.springblade.transport.pojo.vo.ProcessConfigVO; import org.springblade.transport.service.IProcessConfigService; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -83,20 +84,34 @@ public class ProcessConfigController extends BladeController { private final VoucherFileMapper voucherFileMapper; private final VoucherImageMapper voucherImageMapper; private final VoucherManageMapper voucherManageMapper; - private final MinioClient minioClient; + private final ObjectProvider minioClientProvider; @Value("${file.storage.minio.bucket-name:${minio.bucket-name:}}") private String minioBucketName; public ProcessConfigController(IProcessConfigService processConfigService, WaybillMapper waybillMapper, VoucherFileMapper voucherFileMapper, VoucherImageMapper voucherImageMapper, VoucherManageMapper voucherManageMapper, - MinioClient minioClient) { + ObjectProvider minioClientProvider) { this.processConfigService = processConfigService; this.waybillMapper = waybillMapper; this.voucherFileMapper = voucherFileMapper; this.voucherImageMapper = voucherImageMapper; this.voucherManageMapper = voucherManageMapper; - this.minioClient = minioClient; + this.minioClientProvider = minioClientProvider; + } + + /** + * 获取 MinIO 客户端。 + *

+ * 未配置 file.storage.minio.endpoint 时该客户端不会被注册, + * 此时凭证预览地址无法生成,抛出明确提示而非启动即失败。 + */ + private MinioClient minioClient() { + MinioClient minioClient = minioClientProvider.getIfAvailable(); + if (minioClient == null) { + throw new IllegalStateException("Nacos 未配置 file.storage.minio.endpoint,凭证文件功能不可用"); + } + return minioClient; } @GetMapping("/detail") @@ -147,7 +162,7 @@ public class ProcessConfigController extends BladeController { result.put("waybillNo", image.getWaybillNo()); result.put("objectKey", image.getObjectKey()); try { - result.put("url", minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() + result.put("url", minioClient().getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() .method(Method.GET).bucket(minioBucketName).object(image.getObjectKey()) .expiry(1, TimeUnit.HOURS).build())); } catch (Exception e) { @@ -270,7 +285,7 @@ public class ProcessConfigController extends BladeController { result.put("objectKey", image.objectKey()); result.put("matched", image.matched()); try { - result.put("url", minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() + result.put("url", minioClient().getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder() .method(Method.GET).bucket(minioBucketName).object(image.objectKey()) .expiry(1, TimeUnit.HOURS).build())); } catch (Exception exception) { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/VoucherManageServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/VoucherManageServiceImpl.java index 3b4d519a..6eb2e8bc 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/VoucherManageServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/VoucherManageServiceImpl.java @@ -42,6 +42,7 @@ import io.minio.PutObjectArgs; import io.minio.RemoveObjectArgs; import io.minio.http.Method; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Value; import java.time.format.DateTimeFormatter; @@ -79,14 +80,14 @@ public class VoucherManageServiceImpl extends BaseServiceImpl minioClientProvider; private final String minioBucketName; private final String minioRootDirectory; public VoucherManageServiceImpl(VoucherWaybillBatchMapper voucherWaybillBatchMapper, IProjectApplyService projectApplyService, IWaybillService waybillService, VoucherImageMapper voucherImageMapper, VoucherFileMapper voucherFileMapper, - ApplicationEventPublisher eventPublisher, MinioClient minioClient, + ApplicationEventPublisher eventPublisher, ObjectProvider minioClientProvider, @Value("${file.storage.minio.bucket-name:${minio.bucket-name:}}") String minioBucketName, @Value("${file.storage.minio.root-directory:${minio.root-directory:}}") String minioRootDirectory) { this.voucherWaybillBatchMapper = voucherWaybillBatchMapper; @@ -95,7 +96,7 @@ public class VoucherManageServiceImpl extends BaseServiceImpl candidateObjectKeys = resolvedObjectKey.equals(objectKey) ? List.of(objectKey) : List.of(resolvedObjectKey, objectKey); for (String candidateObjectKey : candidateObjectKeys) { - try (InputStream source = minioClient.getObject(GetObjectArgs.builder().bucket(minioBucketName).object(candidateObjectKey).build()); + try (InputStream source = minioClient().getObject(GetObjectArgs.builder().bucket(minioBucketName).object(candidateObjectKey).build()); OutputStream target = Files.newOutputStream(archivePath)) { source.transferTo(target); return archivePath; @@ -427,7 +428,7 @@ public class VoucherManageServiceImpl extends BaseServiceImpl + * 未配置 file.storage.minio.endpoint 时该客户端不会被注册, + * 此处给出与 {@link #validateMinioConfig()} 一致的明确提示。 + */ + private MinioClient minioClient() { + MinioClient minioClient = minioClientProvider.getIfAvailable(); + if (minioClient == null) { + throw new ServiceException("Nacos 未配置 file.storage.minio.endpoint,凭证文件功能不可用"); + } + return minioClient; + } + private String buildObjectKey(Long voucherId, String waybillNo, String plateNo, String entryName) { String objectKey = voucherId + "/" + waybillNo + "/" + safePathPart(plateNo) + "/" + safeArchivePath(entryName); if (Func.isEmpty(minioRootDirectory)) { @@ -1127,7 +1142,7 @@ public class VoucherManageServiceImpl extends BaseServiceImpl Date: Sun, 20 Sep 2026 20:31:02 +0800 Subject: [PATCH 29/55] =?UTF-8?q?:bug:=20=E4=BF=AE=E5=A4=8D=E7=BB=B4?= =?UTF-8?q?=E4=BF=AE=E8=AE=B0=E5=BD=95=E6=89=B9=E9=87=8F=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=A9=BA=E7=99=BD=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MaintenanceRecordExcel 的 createTime/updateTime 字段类型为 java.util.Date, 但仍绑定了 MaintenancePlanDateTimeConverter(Converter), 导出写单元格时 Date 强转 LocalDateTime 抛 ClassCastException, 写流中断导致下载到损坏的 xlsx,打开后一片空白 - 移除这两个 Date 审计字段上的 converter,仅保留 @DateTimeFormat("yyyy-MM-dd HH:mm:ss"), 与 AccidentRecordExportExcel 等同模块导出模型写法保持一致 - 维修时间、出厂时间仍为 LocalDateTime,转换器保留不变,导入解析不受影响 --- .../springblade/transport/excel/MaintenanceRecordExcel.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenanceRecordExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenanceRecordExcel.java index 7d960425..db5f3e9d 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenanceRecordExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenanceRecordExcel.java @@ -96,14 +96,14 @@ public class MaintenanceRecordExcel implements Serializable { @NumberFormat("0.00") private BigDecimal mileage; - @ExcelProperty(value = "创建时间", converter = MaintenancePlanDateTimeConverter.class) + @ExcelProperty("创建时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") private Date createTime; @ExcelProperty("更新人") private String updateUserName; - @ExcelProperty(value = "更新时间", converter = MaintenancePlanDateTimeConverter.class) + @ExcelProperty("更新时间") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") private Date updateTime; From 37309b24f96eff187fd54859aa6143447b740284 Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Sun, 20 Sep 2026 20:34:56 +0800 Subject: [PATCH 30/55] =?UTF-8?q?:bug:=20=E7=BB=B4=E4=BF=AE=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E5=AF=BC=E5=87=BA=E7=A7=BB=E9=99=A4=E5=A4=9A=E4=BD=99?= =?UTF-8?q?=E7=9A=84=E2=80=9C=E5=AF=BC=E5=87=BA=E5=A4=B1=E8=B4=A5=E5=8E=9F?= =?UTF-8?q?=E5=9B=A0=E2=80=9D=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MaintenanceRecordExportExcel 仅为把父类 @ExcelIgnore 的 errorMessage 重新暴露成 第 18 列“导出失败原因”而存在,正式导出该列恒为空白,属遗留冗余 - 删除该字段与对应 import,导出回归 17 列,与导入模板字段顺序一致 - 导入失败明细导出不依赖本类:ImportFailureExcelUtil 自行按字段构建列并过滤 errorMessage,再反射读取失败原因追加“导入失败原因”列,逻辑不受影响 --- .../transport/excel/MaintenanceRecordExportExcel.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenanceRecordExportExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenanceRecordExportExcel.java index c65060ee..8f5a55e0 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenanceRecordExportExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/MaintenanceRecordExportExcel.java @@ -25,7 +25,6 @@ */ package org.springblade.transport.excel; -import cn.idev.excel.annotation.ExcelProperty; import lombok.Data; import lombok.EqualsAndHashCode; @@ -42,7 +41,4 @@ public class MaintenanceRecordExportExcel extends MaintenanceRecordExcel { @Serial private static final long serialVersionUID = 1L; - @ExcelProperty(value = "导出失败原因", index = 17) - private String errorMessage; - } From b02aff8ff64466707485480b3a064bfd07537a88 Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Mon, 21 Sep 2026 04:19:55 +0800 Subject: [PATCH 31/55] =?UTF-8?q?:sparkles:=20=E8=BF=90=E5=8D=95=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E6=94=AF=E6=8C=81=E5=A4=9A=E8=B4=A7=E7=89=A9=E5=90=88?= =?UTF-8?q?=E5=B9=B6=E4=B8=8E=E9=85=8D=E8=BD=BD=E8=B7=AF=E7=BA=BF=E4=B8=B2?= =?UTF-8?q?=E8=81=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 同一运单标识号的多行货物合并为一条运单,各行转为货物明细写入 goodsJson,数量与其他费用合计按行累加 - 新增组内一致性校验:发货地址、到货地址、数量单位、配载标识号不一致时报错,避免无法合并的数据静默落库 - 配载单路线按导入顺序串联,上一票到货地与下一票发货地重合时去重写入途经地,到货地取末票 --- .../impl/LoadingManageServiceImpl.java | 37 ++++- .../impl/WaybillImportBatchServiceImpl.java | 139 +++++++++++++++++- 2 files changed, 169 insertions(+), 7 deletions(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java index dfbd4de5..d956a87c 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java @@ -174,6 +174,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl waybills) { + // 按顺序收集全部节点:首票发货地、每票到货地;节点与相邻前一点相同则重合跳过 + List nodes = new ArrayList<>(); + for (Waybill waybill : waybills) { + String departure = TransportBusinessSupport.trimToNull(waybill.getDepartureAddress()); + String arrival = TransportBusinessSupport.trimToNull(waybill.getArrivalAddress()); + appendRouteNode(nodes, departure); + appendRouteNode(nodes, arrival); + } + // 途经点 = 去掉首尾(首票发货地、末票到货地)后的中间节点 + List transitNodes = nodes.size() > 2 ? nodes.subList(1, nodes.size() - 1) : List.of(); + if (Func.isEmpty(transitNodes)) { + return null; + } + return String.join(" - ", transitNodes); + } + + private void appendRouteNode(List nodes, String address) { + if (Func.isEmpty(address)) { + return; + } + if (!nodes.isEmpty() && nodes.get(nodes.size() - 1).equals(address)) { + // 与上一节点相同视为同一地点,重合不重复 + return; + } + nodes.add(address); + } + @Override @Transactional(rollbackFor = Exception.class) public BusinessRemoveResultVO removeLoadingManage(String ids) { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java index f944cb15..9f0259dc 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java @@ -19,6 +19,7 @@ import org.springblade.core.tool.utils.BeanUtil; import org.springblade.core.tool.utils.DateUtil; import org.springblade.core.tool.utils.Func; import org.springblade.core.tool.utils.WebUtil; +import org.springblade.core.tool.jackson.JsonUtil; import org.springblade.system.cache.DictCache; import org.springblade.system.cache.DictBizCache; import org.springblade.system.cache.UserCache; @@ -55,6 +56,7 @@ import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -184,20 +186,36 @@ public class WaybillImportBatchServiceImpl extends BaseServiceImpl> rows = Func.isEmpty(request.getRows()) ? List.of() : request.getRows(); + // 逐行构建运单(未落库),构建失败时报出对应行号 + List rowWaybills = new ArrayList<>(); + for (int index = 0; index < rows.size(); index++) { + try { + rowWaybills.add(buildWaybill(rows.get(index), batch, request.getCarrierContractId(), draft)); + } catch (Exception exception) { + throw new ServiceException("第" + (index + 1) + "行" + (draft ? "保存" : "导入") + "失败:" + exception.getMessage()); + } + } + // 同一运单标识号的多货物行合并为一条运单,各行转为货物明细 + Map waybillByRow = new HashMap<>(); + Set skipRows = new HashSet<>(); + mergeMultiCargoRows(rows, rowWaybills, waybillByRow, skipRows); + List waybills = new ArrayList<>(); Map> loadingWaybills = new TreeMap<>(); for (int index = 0; index < rows.size(); index++) { + if (skipRows.contains(index)) continue; + Waybill waybill = waybillByRow.getOrDefault(index, rowWaybills.get(index)); + // 配载标识号必须取自行数据:运单落库(prepareForSave)会把新建运单的 loadingNo 置空 + String loadingIdentifier = stringValue(rows.get(index), "loadingIdentifier", "配载标识号"); try { - String loadingIdentifier = stringValue(rows.get(index), "loadingIdentifier", "配载标识号"); - Waybill waybill = buildWaybill(rows.get(index), batch, request.getCarrierContractId(), draft); if (draft) waybillService.saveDraft(waybill); else waybillService.submit(waybill); - waybills.add(waybill); - if (!draft && IMPORT_TYPE_WAYBILL.equals(batch.getImportType()) && Func.isNotEmpty(loadingIdentifier)) { - loadingWaybills.computeIfAbsent(loadingIdentifier, key -> new ArrayList<>()).add(waybill); - } } catch (Exception exception) { throw new ServiceException("第" + (index + 1) + "行" + (draft ? "保存" : "导入") + "失败:" + exception.getMessage()); } + waybills.add(waybill); + if (!draft && IMPORT_TYPE_WAYBILL.equals(batch.getImportType()) && Func.isNotEmpty(loadingIdentifier)) { + loadingWaybills.computeIfAbsent(loadingIdentifier, key -> new ArrayList<>()).add(waybill); + } } loadingWaybills.forEach(loadingManageService::createFromImportedWaybills); batch.setWaybillCount(waybills.size()); @@ -218,6 +236,69 @@ public class WaybillImportBatchServiceImpl extends BaseServiceImpl> rows, List rowWaybills, + Map waybillByRow, Set skipRows) { + Map> groups = new LinkedHashMap<>(); + for (int index = 0; index < rowWaybills.size(); index++) { + String relationNo = rowWaybills.get(index).getRelationNo(); + if (Func.isEmpty(relationNo)) continue; + groups.computeIfAbsent(relationNo, key -> new ArrayList<>()).add(index); + } + for (List groupRows : groups.values()) { + if (groupRows.size() <= 1) continue; + Waybill primary = rowWaybills.get(groupRows.get(0)); + List> goodsList = new ArrayList<>(); + for (Integer rowIndex : groupRows) { + Waybill current = rowWaybills.get(rowIndex); + if (!rowIndex.equals(groupRows.get(0))) { + // 主行字段保留首行值,数量等数值字段累加同组其余各行 + mergeNumericFields(primary, current); + skipRows.add(rowIndex); + waybillByRow.remove(rowIndex); + } + goodsList.add(buildGoodsItem(rows.get(rowIndex), current)); + } + primary.setGoodsJson(JsonUtil.toJson(goodsList)); + } + } + + /** 合并多货物行时累加数量、其他费用等可加数值字段;为空的字段跳过。 */ + private void mergeNumericFields(Waybill primary, Waybill current) { + primary.setQuantity(sumNullable(primary.getQuantity(), current.getQuantity())); + primary.setOtherFeeTotal(sumNullable(primary.getOtherFeeTotal(), current.getOtherFeeTotal())); + } + + private BigDecimal sumNullable(BigDecimal first, BigDecimal second) { + if (first == null) return second; + if (second == null) return first; + return first.add(second); + } + + /** 由导入行构建一条货物明细,字段与运单详情货物编辑结构保持一致。 */ + private Map buildGoodsItem(Map row, Waybill waybill) { + Map goods = new LinkedHashMap<>(); + putIfNotEmpty(goods, "cargoName", waybill.getCargoName()); + putIfNotEmpty(goods, "cargoType", waybill.getCargoType()); + putIfNotEmpty(goods, "specification", waybill.getSpecification()); + putIfNotEmpty(goods, "model", waybill.getModel()); + putIfNotEmpty(goods, "packageType", stringValue(row, "packageType", "包装")); + if (waybill.getQuantity() != null) { + goods.put("quantity", waybill.getQuantity().toPlainString()); + } + putIfNotEmpty(goods, "quantityUnit", waybill.getQuantityUnit()); + putIfNotEmpty(goods, "remark", waybill.getRemark()); + return goods; + } + + private void putIfNotEmpty(Map goods, String key, String value) { + if (Func.isNotEmpty(value)) goods.put(key, value); + } + @Override public IPage page(IPage page, WaybillImportBatchRequest request) { LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery() @@ -523,6 +604,9 @@ public class WaybillImportBatchServiceImpl extends BaseServiceImpl row, int rowIndex, + Map> waybillIdentifierMap, List> allRows, List errors) { + String waybillIdentifier = stringValue(row, "waybillIdentifier", "同一运单标识号"); + if (Func.isEmpty(waybillIdentifier)) { + return; + } + List sameIdentifierRows = waybillIdentifierMap.get(waybillIdentifier); + if (sameIdentifierRows == null || sameIdentifierRows.size() <= 1) { + return; + } + compareGroupValue(rowIndex, sameIdentifierRows, allRows, "departureAddress", "发货地址", errors); + compareGroupValue(rowIndex, sameIdentifierRows, allRows, "arrivalAddress", "到货地址", errors); + compareGroupValue(rowIndex, sameIdentifierRows, allRows, "quantityUnit", "数量单位", errors); + compareGroupValue(rowIndex, sameIdentifierRows, allRows, "loadingIdentifier", "配载标识号", errors); + } + + /** 比较同一运单标识号组内其余行的字段值,不一致时记录错误;空值不参与比较。 */ + private void compareGroupValue(int rowIndex, List sameIdentifierRows, List> allRows, + String field, String fieldName, List errors) { + String currentValue = normalizeCompareValue(stringValue(allRows.get(rowIndex), field, fieldName)); + if (Func.isEmpty(currentValue)) { + return; + } + for (Integer otherRowIndex : sameIdentifierRows) { + if (otherRowIndex <= rowIndex) { + continue; + } + String otherValue = normalizeCompareValue(stringValue(allRows.get(otherRowIndex), field, fieldName)); + if (Func.isNotEmpty(otherValue) && !currentValue.equals(otherValue)) { + errors.add("同一运单标识号下," + fieldName + "必须一致"); + return; + } + } + } + + private String normalizeCompareValue(String value) { + return Func.isEmpty(value) ? null : value.replaceAll("\\s+", ""); + } + private LocalDate parseDateForValidation(Object value, String fieldName, List errors) { if (value == null || String.valueOf(value).isBlank()) { return null; From 8485efe00d2bcfd9c288e834475bb2482ee514a2 Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Mon, 21 Sep 2026 05:21:27 +0800 Subject: [PATCH 32/55] =?UTF-8?q?:bug:=20=E4=BF=AE=E5=A4=8D=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E5=A4=B1=E8=B4=A5=E6=98=8E=E7=BB=86=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E6=8C=89=E5=88=97=E6=A0=87=E7=BA=A2=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 4e1a162 移除了按列名匹配逻辑后只剩失败原因列标红。 现恢复并修正误标红:命中片段被更长片段覆盖时只保留更长的列 (避免原因含「车船类型」时把子串「类型」列一起标红)。 --- .../common/excel/ImportFailureExcelUtil.java | 159 +++++++++++++++++- 1 file changed, 151 insertions(+), 8 deletions(-) diff --git a/blade-common/src/main/java/org/springblade/common/excel/ImportFailureExcelUtil.java b/blade-common/src/main/java/org/springblade/common/excel/ImportFailureExcelUtil.java index 70d7183d..e297cb55 100644 --- a/blade-common/src/main/java/org/springblade/common/excel/ImportFailureExcelUtil.java +++ b/blade-common/src/main/java/org/springblade/common/excel/ImportFailureExcelUtil.java @@ -35,6 +35,7 @@ import cn.idev.excel.write.metadata.holder.WriteTableHolder; import jakarta.servlet.http.HttpServletResponse; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.FillPatternType; import org.apache.poi.ss.usermodel.Font; import org.apache.poi.ss.usermodel.IndexedColors; import org.apache.poi.ss.usermodel.Workbook; @@ -46,9 +47,11 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; /** * 导入失败明细 Excel 导出工具类 @@ -95,7 +98,7 @@ public class ImportFailureExcelUtil { * @param excelClass 原导入 Excel 类型 */ public static void export(HttpServletResponse response, String fileName, String sheetName, List data, Class excelClass) { - exportFailureReasonOnly(response, fileName, sheetName, data, excelClass); + export(response, fileName, sheetName, data, excelClass, true); } /** @@ -109,6 +112,16 @@ public class ImportFailureExcelUtil { */ public static void exportFailureReasonOnly(HttpServletResponse response, String fileName, String sheetName, List data, Class excelClass) { + export(response, fileName, sheetName, data, excelClass, false); + } + + /** + * 导出导入失败明细 + * + * @param markErrorColumns 是否按失败原因里出现的列名,标红对应字段单元格 + */ + private static void export(HttpServletResponse response, String fileName, String sheetName, + List data, Class excelClass, boolean markErrorColumns) { response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); String encodeFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8); @@ -118,7 +131,7 @@ public class ImportFailureExcelUtil { List> rows = buildRows(data, excelFields); try { FastExcel.write(response.getOutputStream()) - .registerWriteHandler(new ImportFailureCellStyleHandler(excelFields.size())) + .registerWriteHandler(new ImportFailureCellStyleHandler(excelFields, rows, markErrorColumns)) .head(head) .sheet(sheetName) .doWrite(rows); @@ -204,15 +217,60 @@ public class ImportFailureExcelUtil { throw new NoSuchFieldException(String.join(",", fieldNames)); } + /** + * 文本归一化:去掉空白、星号与中英文标点并统一小写 + *

+ * 目的是让「失败原因里写的列名」与「表头列名」能直接做包含匹配, + * 不受「*」「/」「:」等写法差异影响。 + */ + private static String normalize(String value) { + return value == null ? "" : value.replaceAll("[\\s*_::,,。;;()()\\[\\]【】<>《》//、-]", "").toLowerCase(); + } + + /** + * 取某一列用于定位的候选关键词 + *

+ * 包含表头列名、字段名,以及列名按分隔符拆出的片段 + * (如「车牌号/船号」拆出「车牌号」「船号」,失败原因只写其中一段时也能定位)。 + */ + private static List columnKeywords(Field field) { + ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class); + String[] value = excelProperty.value(); + String columnName = value.length == 0 ? field.getName() : value[0]; + List keywords = new ArrayList<>(); + keywords.add(columnName); + keywords.add(field.getName()); + keywords.addAll(Arrays.asList(columnName.replace("*", "").split("[//、()()\\s]+"))); + return keywords.stream() + .map(ImportFailureExcelUtil::normalize) + .filter(keyword -> keyword.length() >= 2) + .distinct() + .toList(); + } + private static class ImportFailureCellStyleHandler implements CellWriteHandler { private final int failureReasonColumnIndex; + private final boolean markErrorColumns; + private final List> rows; + private final Map> redColumnsByRow = new HashMap<>(); private final Map redStyleCache = new HashMap<>(); + private final Map redFillStyleCache = new HashMap<>(); private final Map noWrapStyleCache = new HashMap<>(); private final Map columnWidthCache = new HashMap<>(); - private ImportFailureCellStyleHandler(int failureReasonColumnIndex) { - this.failureReasonColumnIndex = failureReasonColumnIndex; + private ImportFailureCellStyleHandler(List excelFields, List> rows, boolean markErrorColumns) { + this.failureReasonColumnIndex = excelFields.size(); + this.markErrorColumns = markErrorColumns; + this.rows = rows; + if (markErrorColumns) { + List> columnKeywords = excelFields.stream() + .map(ImportFailureExcelUtil::columnKeywords) + .toList(); + for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) { + redColumnsByRow.put(rowIndex, resolveRedColumns(rows.get(rowIndex), columnKeywords)); + } + } } @Override @@ -230,14 +288,95 @@ public class ImportFailureExcelUtil { return; } adjustColumnWidth(cell); - if (cell.getColumnIndex() == failureReasonColumnIndex) { - markRed(cell); + if (relativeRowIndex == null || relativeRowIndex < 0 || relativeRowIndex >= rows.size()) { + return; + } + if (shouldMarkRed(relativeRowIndex, cell.getColumnIndex())) { + markRed(cell, isBlankValue(rows.get(relativeRowIndex), cell.getColumnIndex())); } } - private void markRed(Cell cell) { + /** + * 计算某一行需要标红的列 + *

+ * 失败原因里出现的列名即视为出错列;但当某个命中片段被另一列更长的命中 + * 片段完全覆盖时(如原因「车船类型不能为空」同时命中「车船类型」和它的 + * 子串「类型」),只保留更长的那一列,避免把无关列一起标红。 + */ + private Set resolveRedColumns(List row, List> columnKeywords) { + Set redColumns = new LinkedHashSet<>(); + redColumns.add(failureReasonColumnIndex); + String failureReason = normalize(String.valueOf(row.get(failureReasonColumnIndex))); + if (failureReason.isEmpty()) { + return redColumns; + } + List matches = new ArrayList<>(); + for (int column = 0; column < columnKeywords.size(); column++) { + for (String keyword : columnKeywords.get(column)) { + int fromIndex = 0; + while (true) { + int index = failureReason.indexOf(keyword, fromIndex); + if (index < 0) { + break; + } + matches.add(new int[]{index, index + keyword.length(), column}); + fromIndex = index + 1; + } + } + } + for (int[] match : matches) { + if (!isSubsumed(match, matches)) { + redColumns.add(match[2]); + } + } + return redColumns; + } + + /** + * 判断该命中片段是否被另一列更长的命中片段完全覆盖 + */ + private boolean isSubsumed(int[] match, List matches) { + int matchLength = match[1] - match[0]; + for (int[] other : matches) { + if (other[2] == match[2]) { + continue; + } + if ((other[1] - other[0]) > matchLength && other[0] <= match[0] && other[1] >= match[1]) { + return true; + } + } + return false; + } + + private boolean shouldMarkRed(int rowIndex, int columnIndex) { + if (columnIndex == failureReasonColumnIndex) { + return true; + } + if (!markErrorColumns) { + return false; + } + Set redColumns = redColumnsByRow.get(rowIndex); + return redColumns != null && redColumns.contains(columnIndex); + } + + private boolean isBlankValue(List row, int columnIndex) { + if (columnIndex < 0 || columnIndex >= row.size()) { + return true; + } + Object value = row.get(columnIndex); + return value == null || String.valueOf(value).isBlank(); + } + + /** + * 标红单元格 + *

+ * 单元格为空时只改字体颜色屏幕上什么也看不到(「必填项为空」正是这种情况), + * 因此对空单元格额外加浅红底纹,保证用户能定位到是哪一列出错。 + */ + private void markRed(Cell cell, boolean blankValue) { CellStyle currentStyle = cell.getCellStyle(); - CellStyle redStyle = redStyleCache.computeIfAbsent(currentStyle.getIndex(), styleIndex -> { + Map styleCache = blankValue ? redFillStyleCache : redStyleCache; + CellStyle redStyle = styleCache.computeIfAbsent(currentStyle.getIndex(), styleIndex -> { Workbook workbook = cell.getSheet().getWorkbook(); CellStyle newStyle = workbook.createCellStyle(); newStyle.cloneStyleFrom(currentStyle); @@ -245,6 +384,10 @@ public class ImportFailureExcelUtil { font.setColor(IndexedColors.RED.getIndex()); newStyle.setFont(font); newStyle.setWrapText(true); + if (blankValue) { + newStyle.setFillForegroundColor(IndexedColors.ROSE.getIndex()); + newStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); + } return newStyle; }); cell.setCellStyle(redStyle); From 4c96583701a79070ed3a8cdd5b59dbb0ddb46804 Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Mon, 21 Sep 2026 05:34:59 +0800 Subject: [PATCH 33/55] =?UTF-8?q?:bug:=20=E4=BF=AE=E5=A4=8D=E7=A0=81?= =?UTF-8?q?=E5=A4=B4=E5=AF=BC=E5=85=A5=E7=88=B6=E6=B8=AF=E5=8F=A3=E5=8F=96?= =?UTF-8?q?=E5=88=97=E4=B8=8D=E4=B8=80=E8=87=B4=E5=AF=BC=E8=87=B4=E7=9A=84?= =?UTF-8?q?=E9=9D=99=E9=BB=98=E4=B8=A2=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit importTerminals 中 setParentCode 取「港口编码」列,而抑制连带报错时 却用「上级港口编码」判断父行是否已声明。用户若把完整编码填入 「码头编码」并让「港口编码」留空,父港口解析失败的异常会被误判为 「父行已声明」而吞掉,码头被静默丢弃且接口返回成功。 - splitByCategory 增加码头行「港口编码」非空校验,明确报错 - 抑制逻辑改为与 setParentCode 取同一列 --- .../system/service/impl/PortTerminalServiceImpl.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/PortTerminalServiceImpl.java b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/PortTerminalServiceImpl.java index e572dff0..892f4d62 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/PortTerminalServiceImpl.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/service/impl/PortTerminalServiceImpl.java @@ -186,6 +186,13 @@ public class PortTerminalServiceImpl extends BaseServiceImpl Date: Mon, 21 Sep 2026 05:45:49 +0800 Subject: [PATCH 34/55] =?UTF-8?q?:zap:=20=E7=B2=BE=E7=AE=80=E8=BF=90?= =?UTF-8?q?=E5=8D=95=E5=AF=BC=E5=85=A5=E8=BD=A6=E7=89=8C=E5=8F=B7=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C=E7=9A=84=E5=A4=B1=E8=B4=A5=E5=8E=9F=E5=9B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 一次车牌号错误原来最多会叠出「长度不能少于7位」「首位必须是省份简称、 第二位必须是英文字母」「格式不正确」三条原因,导出的失败明细里这一格很长。 现将公路运输下的必填、长度与格式统一为一条「车牌号校验不通过」。 - 校验强度不变:长度 7/8 位本就由 VEHICLE_NO_PATTERN 蕴含,省份简称白名单照旧 - 保留「车牌号」字样,失败明细仍能按列名拆词匹配并标红车牌号列;车牌为空时 走空单元格红底纹分支,比原来只有文字更容易定位 - 未改动「同一配载标识号下,车牌号不一致」,该原因由 tests/fixtures/waybill-import 用例 04 断言 --- .../impl/WaybillImportBatchServiceImpl.java | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java index 9f0259dc..b81a5b87 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java @@ -655,21 +655,13 @@ public class WaybillImportBatchServiceImpl extends BaseServiceImpl 8) { - errors.add("车牌号长度不能超过8位"); - } - if (!VEHICLE_PROVINCE_PATTERN.matcher(vehicleNo).lookingAt()) { - errors.add("车牌号首位必须是省份简称,第二位必须是英文字母"); - } - if (!VEHICLE_NO_PATTERN.matcher(vehicleNo).matches()) { - errors.add("车牌号格式不正确,应为首位省份简称、次位英文字母、总长度7或8位"); + // 必填、长度与格式统一为一条简短原因,避免多条规则叠加导致失败原因过长; + // 文案保留“车牌号”字样,导入失败明细仍能据此定位到车牌号列并标红。 + // VEHICLE_NO_PATTERN 已约束总长度为 7 或 8 位,故不再单独判断长度。 + if (Func.isEmpty(vehicleNo) + || !VEHICLE_PROVINCE_PATTERN.matcher(vehicleNo).lookingAt() + || !VEHICLE_NO_PATTERN.matcher(vehicleNo).matches()) { + errors.add("车牌号校验不通过"); } } From 37d0ebc21e8a1c8bdd60bc90ab606e5692fd517f Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Mon, 21 Sep 2026 05:52:31 +0800 Subject: [PATCH 35/55] =?UTF-8?q?:zap:=20=E5=AF=BC=E5=85=A5=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E6=98=8E=E7=BB=86=E5=8E=BB=E6=8E=89=E7=A9=BA=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=A0=BC=E5=BA=95=E7=BA=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 标红统一只改红色字体,不再对空单元格加浅红底纹(IndexedColors.ROSE)。 底纹原本是为「必填项为空时红字在屏幕上看不见」而加,但它超出 AGENTS.md §6.11「仅出错字段/列使用红色文字」的口径,视觉上也偏重。 - markRed(Cell, boolean) 简化为 markRed(Cell),删除 redFillStyleCache 与 isBlankValue - 空单元格的红色字体样式照旧写入 styles.xml,api-violation-xlsx-cols.js 按字体色判定,回归用例 03/04 不受影响 - 影响所有使用 ImportFailureExcelUtil 的导入模块(20+ 个) --- .../common/excel/ImportFailureExcelUtil.java | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/blade-common/src/main/java/org/springblade/common/excel/ImportFailureExcelUtil.java b/blade-common/src/main/java/org/springblade/common/excel/ImportFailureExcelUtil.java index e297cb55..6b0ad40d 100644 --- a/blade-common/src/main/java/org/springblade/common/excel/ImportFailureExcelUtil.java +++ b/blade-common/src/main/java/org/springblade/common/excel/ImportFailureExcelUtil.java @@ -35,7 +35,6 @@ import cn.idev.excel.write.metadata.holder.WriteTableHolder; import jakarta.servlet.http.HttpServletResponse; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; -import org.apache.poi.ss.usermodel.FillPatternType; import org.apache.poi.ss.usermodel.Font; import org.apache.poi.ss.usermodel.IndexedColors; import org.apache.poi.ss.usermodel.Workbook; @@ -255,7 +254,6 @@ public class ImportFailureExcelUtil { private final List> rows; private final Map> redColumnsByRow = new HashMap<>(); private final Map redStyleCache = new HashMap<>(); - private final Map redFillStyleCache = new HashMap<>(); private final Map noWrapStyleCache = new HashMap<>(); private final Map columnWidthCache = new HashMap<>(); @@ -292,7 +290,7 @@ public class ImportFailureExcelUtil { return; } if (shouldMarkRed(relativeRowIndex, cell.getColumnIndex())) { - markRed(cell, isBlankValue(rows.get(relativeRowIndex), cell.getColumnIndex())); + markRed(cell); } } @@ -359,24 +357,15 @@ public class ImportFailureExcelUtil { return redColumns != null && redColumns.contains(columnIndex); } - private boolean isBlankValue(List row, int columnIndex) { - if (columnIndex < 0 || columnIndex >= row.size()) { - return true; - } - Object value = row.get(columnIndex); - return value == null || String.valueOf(value).isBlank(); - } - /** * 标红单元格 *

- * 单元格为空时只改字体颜色屏幕上什么也看不到(「必填项为空」正是这种情况), - * 因此对空单元格额外加浅红底纹,保证用户能定位到是哪一列出错。 + * 统一只改字体颜色,不加底纹;空单元格的红色字体在屏幕上不可见, + * 这类错误依靠失败原因列的文字定位。 */ - private void markRed(Cell cell, boolean blankValue) { + private void markRed(Cell cell) { CellStyle currentStyle = cell.getCellStyle(); - Map styleCache = blankValue ? redFillStyleCache : redStyleCache; - CellStyle redStyle = styleCache.computeIfAbsent(currentStyle.getIndex(), styleIndex -> { + CellStyle redStyle = redStyleCache.computeIfAbsent(currentStyle.getIndex(), styleIndex -> { Workbook workbook = cell.getSheet().getWorkbook(); CellStyle newStyle = workbook.createCellStyle(); newStyle.cloneStyleFrom(currentStyle); @@ -384,10 +373,6 @@ public class ImportFailureExcelUtil { font.setColor(IndexedColors.RED.getIndex()); newStyle.setFont(font); newStyle.setWrapText(true); - if (blankValue) { - newStyle.setFillForegroundColor(IndexedColors.ROSE.getIndex()); - newStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); - } return newStyle; }); cell.setCellStyle(redStyle); From ddfa52a2ac14f403b3b92b9c67dbf30ca5363e83 Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Mon, 21 Sep 2026 06:24:31 +0800 Subject: [PATCH 36/55] =?UTF-8?q?:zap:=20=E6=94=B6=E7=AA=84=E8=BF=90?= =?UTF-8?q?=E8=BE=93=E6=96=B9=E5=BC=8F=E6=9E=9A=E4=B8=BE=E4=B8=BA=E5=85=AC?= =?UTF-8?q?=E8=B7=AF/=E9=93=81=E8=B7=AF/=E6=B0=B4=E8=B7=AF/=E8=88=AA?= =?UTF-8?q?=E7=A9=BA=E5=9B=9B=E7=A7=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 运输计划导入校验白名单去掉「公路整车、公路配载/零担、跨境海运」 运单批量导入字典兜底值去掉多余枚举,仅保留四种运输方式 --- .../transport/service/impl/TransportPlanServiceImpl.java | 4 ++-- .../transport/service/impl/WaybillImportBatchServiceImpl.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TransportPlanServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TransportPlanServiceImpl.java index 9c574de9..619a9153 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TransportPlanServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/TransportPlanServiceImpl.java @@ -416,9 +416,9 @@ public class TransportPlanServiceImpl extends BaseServiceImpl validTransportTypes = List.of("公路整车", "公路配载/零担", "铁路运输", "水路运输", "跨境海运", "航空运输"); + List validTransportTypes = List.of("公路运输", "铁路运输", "水路运输", "航空运输"); if (!validTransportTypes.contains(transportType)) { - errors.add("运输类型需系统枚举值(公路整车、公路配载/零担、铁路运输、水路运输、跨境海运、航空运输)"); + errors.add("运输类型需系统枚举值(公路运输、铁路运输、水路运输、航空运输)"); } } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java index b81a5b87..73cd624f 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillImportBatchServiceImpl.java @@ -980,8 +980,8 @@ public class WaybillImportBatchServiceImpl extends BaseServiceImpl defaults = List.of("公路运输", "铁路运输", "水路运输", "航空运输", - "公路整车", "公路配载/零担", "铁路整车", "铁路零担", "水路", "航空", "多式联运", "管道运输", "其他"); + // 运输方式仅支持公路、铁路、水路、航空四种 + List defaults = List.of("公路运输", "铁路运输", "水路运输", "航空运输"); defaults.forEach(value -> options.put(value, value)); } return options; From fd6563f53b61cb58f3b8dc69eea64f481bd5fd99 Mon Sep 17 00:00:00 2001 From: "weicw1996@qq.com" Date: Mon, 21 Sep 2026 09:47:50 +0800 Subject: [PATCH 37/55] =?UTF-8?q?:sparkles:=20=E6=89=B9=E9=87=8F=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E6=97=A5=E6=9C=9F=E5=AE=BD=E5=AE=B9=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=EF=BC=9A=E5=85=BC=E5=AE=B9=202026-08-02=20=E4=B8=8E=202026-8-2?= =?UTF-8?q?=20=E7=AD=89=E4=B9=A6=E5=86=99=E5=8F=98=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - blade-common 新增 LenientDateParser(宽容解析:- / . 分隔符、补零与否、 时间可省略秒/分;拒绝两位年份、日在前、无分隔符) - 新增 LenientDateStringConverter / LenientDateTimeStringConverter: Excel 数值日期转文本透传,文本解析留待服务层以进入失败明细流程 - 8 个裸走 fastexcel 内置转换的导入 DTO(保险/年检/事故/其他费用/运单/ 币种/用户生日/设备台账)日期字段改为 String 承载,服务层统一经 LenientDateParser 解析,失败报「<列名> 日期格式无法识别:<原值>」 - MaintenancePlanDateTimeConverter / TireReplacementDateStringConverter 内部委托公共解析器,6 个 DTO 注解零改动 - 运输计划、运单批量、运输对账、换胎 4 处手动解析点收敛至同一工具类 - UserMapper 导出 SQL 的生日改 DATE_FORMAT 输出,保持导出格式不变 口径见根工作区 docs/import-spec.md(weicw/tms-erp#1)。 回归:port-terminal 45 断言、violation-record 40 断言(含新增不补零 用例 09)、waybill-import 48 断言、insurance-record 新增用例 6 断言, 全部通过。 --- blade-common/pom.xml | 4 + .../common/excel/LenientDateParser.java | 198 ++++++++++++++++++ .../excel/LenientDateStringConverter.java | 75 +++++++ .../excel/LenientDateTimeStringConverter.java | 82 ++++++++ .../system/excel/CurrencyExcel.java | 9 +- .../springblade/system/excel/UserExcel.java | 5 +- .../springblade/system/mapper/UserMapper.xml | 2 +- .../service/impl/CurrencyServiceImpl.java | 2 + .../system/service/impl/UserServiceImpl.java | 3 + .../transport/excel/AccidentRecordExcel.java | 5 +- .../excel/AnnualInspectionRecordExcel.java | 9 +- .../transport/excel/EquipmentLedgerExcel.java | 4 +- .../transport/excel/InsuranceRecordExcel.java | 13 +- .../MaintenancePlanDateTimeConverter.java | 26 +-- .../excel/OtherExpenseRecordExcel.java | 5 +- .../TireReplacementDateStringConverter.java | 4 + .../transport/excel/WaybillExcel.java | 16 +- .../impl/AccidentRecordServiceImpl.java | 1 + .../AnnualInspectionRecordServiceImpl.java | 2 + .../impl/EquipmentLedgerServiceImpl.java | 1 + .../impl/InsuranceRecordServiceImpl.java | 3 + .../impl/OtherExpenseRecordServiceImpl.java | 1 + .../TireReplacementRecordServiceImpl.java | 9 +- .../impl/TransportPlanServiceImpl.java | 9 +- .../TransportReconciliationServiceImpl.java | 13 +- .../impl/WaybillImportBatchServiceImpl.java | 20 +- .../service/impl/WaybillServiceImpl.java | 4 + 27 files changed, 444 insertions(+), 81 deletions(-) create mode 100644 blade-common/src/main/java/org/springblade/common/excel/LenientDateParser.java create mode 100644 blade-common/src/main/java/org/springblade/common/excel/LenientDateStringConverter.java create mode 100644 blade-common/src/main/java/org/springblade/common/excel/LenientDateTimeStringConverter.java diff --git a/blade-common/pom.xml b/blade-common/pom.xml index 5fb0c220..0879b912 100644 --- a/blade-common/pom.xml +++ b/blade-common/pom.xml @@ -22,6 +22,10 @@ org.springblade blade-starter-loadbalancer + + org.springblade + blade-starter-log + org.springblade blade-core-auto diff --git a/blade-common/src/main/java/org/springblade/common/excel/LenientDateParser.java b/blade-common/src/main/java/org/springblade/common/excel/LenientDateParser.java new file mode 100644 index 00000000..750c101a --- /dev/null +++ b/blade-common/src/main/java/org/springblade/common/excel/LenientDateParser.java @@ -0,0 +1,198 @@ +/** + * 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.common.excel; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 批量导入宽容日期解析器(口径见根工作区 docs/import-spec.md)。 + *

+ * 解析规则: + *

    + *
  • 分隔符 {@code -}、{@code /}、{@code .} 均接受;补零与否均可(2026-08-02 ≡ 2026-8-2)
  • + *
  • 日期时间:日期部分同上,时间 HH:mm:ss,可省略秒或秒+分(2026-8-2 12:3 可解析)
  • + *
  • 拒绝:两位年份(26-8-2)、日在前(2/8/2026)、无分隔符(20260802)
  • + *
+ * + * @author Chill + */ +public final class LenientDateParser { + + /** + * 日期部分:4 位年 + 分隔符 + 1~2 位月 + 同一分隔符 + 1~2 位日(年在前,拒绝两位年份与日在前)。 + */ + private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4})([-/.])(\\d{1,2})\\2(\\d{1,2})"); + + /** + * 时间部分:1~2 位时[:1~2 位分[:1~2 位秒]],逐级可省略。 + */ + private static final Pattern TIME_PATTERN = Pattern.compile("(\\d{1,2})(?::(\\d{1,2})(?::(\\d{1,2}))?)?"); + + private LenientDateParser() { + } + + /** + * 解析日期文本,失败返回 {@code null}。 + * + * @param value 单元格原始文本 + * @return 日期;无法识别时返回 null + */ + public static LocalDate parseDateOrNull(String value) { + String normalized = normalize(value); + if (normalized == null) { + return null; + } + Matcher matcher = DATE_PATTERN.matcher(normalized); + if (!matcher.matches()) { + return null; + } + try { + return LocalDate.of(Integer.parseInt(matcher.group(1)), + Integer.parseInt(matcher.group(3)), Integer.parseInt(matcher.group(4))); + } catch (NumberFormatException | java.time.DateTimeException exception) { + return null; + } + } + + /** + * 解析日期文本;允许携带合法时间部分并截断(如 2026-9-1 8:0:0 按 2026-09-01 解析, + * 与 fastexcel 内置 LocalDate 转换的既有宽容度保持一致),失败返回 {@code null}。 + * + * @param value 单元格原始文本 + * @return 日期;无法识别时返回 null + */ + public static LocalDate parseDateLenientlyOrNull(String value) { + LocalDate date = parseDateOrNull(value); + if (date != null) { + return date; + } + LocalDateTime dateTime = parseDateTimeOrNull(value); + return dateTime == null ? null : dateTime.toLocalDate(); + } + + /** + * 解析日期时间文本;纯日期按当日零点处理,失败返回 {@code null}。 + * + * @param value 单元格原始文本 + * @return 日期时间;无法识别时返回 null + */ + public static LocalDateTime parseDateTimeOrNull(String value) { + String normalized = normalize(value); + if (normalized == null) { + return null; + } + // 拆出日期与时间两部分;中间允许 1 个及以上空白或小写 t(2026-8-2t12:3 亦接受)。 + String[] parts = normalized.split("[ \\t]+|(?<=\\d)t", 2); + if (parts.length == 0) { + return null; + } + LocalDate date = parseDateOrNull(parts[0]); + if (date == null) { + return null; + } + if (parts.length == 1) { + return date.atStartOfDay(); + } + Matcher matcher = TIME_PATTERN.matcher(parts[1]); + if (!matcher.matches()) { + return null; + } + try { + int hour = Integer.parseInt(matcher.group(1)); + int minute = matcher.group(2) == null ? 0 : Integer.parseInt(matcher.group(2)); + int second = matcher.group(3) == null ? 0 : Integer.parseInt(matcher.group(3)); + return LocalDateTime.of(date, LocalTime.of(hour, minute, second)); + } catch (NumberFormatException | java.time.DateTimeException exception) { + return null; + } + } + + /** + * 解析日期文本,失败抛出携带口径文案的 {@link org.springblade.core.log.exception.ServiceException}。 + * 空白与 {@code null} 返回 {@code null}(可选字段由业务校验决定是否必填)。 + * + * @param value 单元格原始文本 + * @param columnName 导入模板列名(用于失败原因文案与失败明细标红定位) + * @return 日期 + */ + public static LocalDate parseDate(String value, String columnName) { + if (value == null || value.trim().isEmpty()) { + return null; + } + LocalDate date = parseDateLenientlyOrNull(value); + if (date == null) { + throw unrecognizedDate(value, columnName); + } + return date; + } + + /** + * 解析日期时间文本,失败抛出携带口径文案的 {@link org.springblade.core.log.exception.ServiceException}。 + * 空白与 {@code null} 返回 {@code null}(可选字段由业务校验决定是否必填)。 + * + * @param value 单元格原始文本 + * @param columnName 导入模板列名(用于失败原因文案与失败明细标红定位) + * @return 日期时间 + */ + public static LocalDateTime parseDateTime(String value, String columnName) { + if (value == null || value.trim().isEmpty()) { + return null; + } + LocalDateTime dateTime = parseDateTimeOrNull(value); + if (dateTime == null) { + throw unrecognizedDate(value, columnName); + } + return dateTime; + } + + /** + * 按口径构造「日期格式无法识别」错误。 + * + * @param value 单元格原始文本 + * @param columnName 导入模板列名 + * @return 业务异常 + */ + public static org.springblade.core.log.exception.ServiceException unrecognizedDate(String value, String columnName) { + String displayName = columnName == null || columnName.isBlank() ? "日期" : columnName; + return new org.springblade.core.log.exception.ServiceException( + displayName + " 日期格式无法识别:" + (value == null ? "" : value.trim())); + } + + /** + * 归一化输入:去首尾空白,跳过空白值。 + */ + private static String normalize(String value) { + if (value == null) { + return null; + } + String normalized = value.trim(); + return normalized.isEmpty() ? null : normalized; + } + +} diff --git a/blade-common/src/main/java/org/springblade/common/excel/LenientDateStringConverter.java b/blade-common/src/main/java/org/springblade/common/excel/LenientDateStringConverter.java new file mode 100644 index 00000000..970dde72 --- /dev/null +++ b/blade-common/src/main/java/org/springblade/common/excel/LenientDateStringConverter.java @@ -0,0 +1,75 @@ +/** + * 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.common.excel; + +import cn.idev.excel.converters.Converter; +import cn.idev.excel.enums.CellDataTypeEnum; +import cn.idev.excel.metadata.GlobalConfiguration; +import cn.idev.excel.metadata.data.ReadCellData; +import cn.idev.excel.metadata.data.WriteCellData; +import cn.idev.excel.metadata.property.ExcelContentProperty; +import cn.idev.excel.util.DateUtils; + +import java.time.LocalDate; + +/** + * 批量导入宽容日期转换器(String 承载):Excel 数值日期(序列号)转 ISO 文本,文本原样透传。 + *

+ * 文本的宽容解析由服务层调用 {@link LenientDateParser} 完成,不在此处抛错—— + * 转换器阶段抛出的异常会被 fastexcel 包装成 ExcelDataConvertException 直接中断读取, + * 无法进入导入失败明细流程。口径见根工作区 docs/import-spec.md。 + * + * @author Chill + */ +public class LenientDateStringConverter implements Converter { + + @Override + public Class supportJavaTypeKey() { + return String.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public String convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (cellData.getType() == CellDataTypeEnum.NUMBER) { + LocalDate date = DateUtils.getLocalDateTime(cellData.getNumberValue().doubleValue(), + globalConfiguration.getUse1904windowing()).toLocalDate(); + return date.toString(); + } + return cellData.getStringValue(); + } + + @Override + public WriteCellData convertToExcelData(String value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return new WriteCellData<>(value); + } + +} diff --git a/blade-common/src/main/java/org/springblade/common/excel/LenientDateTimeStringConverter.java b/blade-common/src/main/java/org/springblade/common/excel/LenientDateTimeStringConverter.java new file mode 100644 index 00000000..bcb070a2 --- /dev/null +++ b/blade-common/src/main/java/org/springblade/common/excel/LenientDateTimeStringConverter.java @@ -0,0 +1,82 @@ +/** + * 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.common.excel; + +import cn.idev.excel.converters.Converter; +import cn.idev.excel.enums.CellDataTypeEnum; +import cn.idev.excel.metadata.GlobalConfiguration; +import cn.idev.excel.metadata.data.ReadCellData; +import cn.idev.excel.metadata.data.WriteCellData; +import cn.idev.excel.metadata.property.ExcelContentProperty; +import cn.idev.excel.util.DateUtils; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * 批量导入宽容日期时间转换器(String 承载):Excel 数值日期(序列号)转 + * {@code yyyy-MM-dd HH:mm:ss} 文本,文本原样透传。 + *

+ * 文本的宽容解析由服务层调用 {@link LenientDateParser} 完成,不在此处抛错—— + * 转换器阶段抛出的异常会被 fastexcel 包装成 ExcelDataConvertException 直接中断读取, + * 无法进入导入失败明细流程。口径见根工作区 docs/import-spec.md。 + * + * @author Chill + */ +public class LenientDateTimeStringConverter implements Converter { + + /** + * 数值日期序列号转文本的输出格式,与导入模板展示格式保持一致。 + */ + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + @Override + public Class supportJavaTypeKey() { + return String.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public String convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + if (cellData.getType() == CellDataTypeEnum.NUMBER) { + LocalDateTime dateTime = DateUtils.getLocalDateTime(cellData.getNumberValue().doubleValue(), + globalConfiguration.getUse1904windowing()); + return dateTime.format(DATE_TIME_FORMATTER); + } + return cellData.getStringValue(); + } + + @Override + public WriteCellData convertToExcelData(String value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return new WriteCellData<>(value); + } + +} diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/excel/CurrencyExcel.java b/blade-service/blade-system/src/main/java/org/springblade/system/excel/CurrencyExcel.java index 941b7eb3..b59573d5 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/excel/CurrencyExcel.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/excel/CurrencyExcel.java @@ -35,7 +35,6 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.math.BigDecimal; -import java.time.LocalDate; /** * 币种汇率 Excel @@ -62,8 +61,8 @@ public class CurrencyExcel implements Serializable { @ExcelProperty("汇率") private BigDecimal exchangeRate; - @ExcelProperty("生效日期") - private LocalDate effectiveDate; + @ExcelProperty(value = "生效日期", converter = org.springblade.common.excel.LenientDateStringConverter.class) + private String effectiveDate; @ExcelProperty("状态") private String statusName; @@ -71,8 +70,8 @@ public class CurrencyExcel implements Serializable { @ExcelProperty("来源") private String dataSource; - @ExcelProperty("失效日期") - private LocalDate expiryDate; + @ExcelProperty(value = "失效日期", converter = org.springblade.common.excel.LenientDateStringConverter.class) + private String expiryDate; @ExcelProperty("备注") private String remark; diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/excel/UserExcel.java b/blade-service/blade-system/src/main/java/org/springblade/system/excel/UserExcel.java index 1dd71acc..306e144f 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/excel/UserExcel.java +++ b/blade-service/blade-system/src/main/java/org/springblade/system/excel/UserExcel.java @@ -34,7 +34,6 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; -import java.util.Date; /** * UserExcel @@ -102,7 +101,7 @@ public class UserExcel implements Serializable { private String postName; @ColumnWidth(20) - @ExcelProperty("生日") - private Date birthday; + @ExcelProperty(value = "生日", converter = org.springblade.common.excel.LenientDateStringConverter.class) + private String birthday; } diff --git a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserMapper.xml b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserMapper.xml index 6a4dd476..70dd2e9a 100644 --- a/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserMapper.xml +++ b/blade-service/blade-system/src/main/java/org/springblade/system/mapper/UserMapper.xml @@ -95,7 +95,7 @@ @@ -92,7 +93,15 @@ approval_status, current_node, current_processor, - approved_time + approved_time, + ( + SELECT s.review_status + FROM blade_customer_credit_score s + WHERE s.customer_id = blade_customer_archive.id + AND (s.is_deleted = 0 OR s.is_deleted IS NULL) + ORDER BY s.score_date DESC, s.update_time DESC, s.id DESC + LIMIT 1 + ) AS review_status FROM blade_customer_archive WHERE is_deleted = 0 diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java index e4b403a1..53c479cd 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java @@ -213,6 +213,7 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl scores = customer.getScores() == null ? List.of() : customer.getScores(); + boolean reviewCompleted = scores.size() == 1 && "已完成".equals(scores.get(0).getReviewStatus()); + if (!reviewCompleted) { + throw new ServiceException("请先完成复评"); + } + } + + private void validateScoreForApproval(CustomerArchiveVO customer) { + List scores = customer.getScores() == null ? List.of() : customer.getScores(); + if (scores.size() > 1) { + throw new ServiceException("评分记录只能有一条"); + } + boolean selfCompleted = scores.size() == 1 && "已完成".equals(scores.get(0).getSelfStatus()); + if (!selfCompleted) { + throw new ServiceException("提交审批前必须完成自评"); + } + } + private void validateApproval(CustomerArchiveVO customer, boolean temporaryToFormal) { if (Objects.equals(customer.getAccessType(), ACCESS_FORMAL) && !temporaryToFormal) { if (Func.isEmpty(customer.getQualificationAttachments())) { throw new ServiceException("正式客商提交审核前必须上传客商材料"); } - boolean hasCompletedScore = customer.getScores().stream() - .anyMatch(score -> "已完成".equals(score.getSelfStatus()) || "已完成".equals(score.getReviewStatus()) || Func.isNotEmpty(score.getFinalScore())); - if (!hasCompletedScore) { - throw new ServiceException("正式客商提交审核前必须完成信用评分"); - } } } @@ -597,10 +614,8 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl contacts) { @@ -774,7 +789,7 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl Date: Wed, 23 Sep 2026 00:17:13 +0800 Subject: [PATCH 49/55] =?UTF-8?q?:zap:=20=E8=AE=A1=E8=B4=B9=E6=96=B9?= =?UTF-8?q?=E6=A1=88=E8=BF=90=E8=BE=93=E6=96=B9=E5=BC=8F=E7=B2=BE=E7=A1=AE?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=EF=BC=8C=E5=B9=B6=E5=AE=8C=E5=96=84=E4=BB=98?= =?UTF-8?q?=E6=AC=BE=E4=B8=8E=E8=BF=90=E5=8D=95=E8=B4=B9=E7=94=A8=E7=94=9F?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复公路/铁路子串误匹配导致错生成应收应付;尾款付款支持关联预结算单;运单新建/复制为已完成时按合同计费生成应收应付。 Co-authored-by: Cursor --- .../impl/PaymentApplicationServiceImpl.java | 78 ++++++++++++++----- .../ReceivablePayableDetailServiceImpl.java | 72 +++++++++++++---- .../service/impl/WaybillServiceImpl.java | 24 +++++- .../wrapper/PaymentApplicationWrapper.java | 2 +- 4 files changed, 140 insertions(+), 36 deletions(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java index c06b24ca..05cc4a34 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/PaymentApplicationServiceImpl.java @@ -150,15 +150,26 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpllambdaQuery() .eq(PreSettlementAdvance::getPreSettlementId, entity.getPreSettlementId()) @@ -474,13 +492,15 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl 0)) throw new ServiceException("付款比例必须在0-100之间"); - if (request.getAppliedAmount() == null || request.getAppliedAmount().compareTo(BigDecimal.ZERO) < 0) throw new ServiceException("申请付款金额不能小于0"); + if (request.getAppliedAmount() == null || request.getAppliedAmount().compareTo(BigDecimal.ZERO) <= 0) throw new ServiceException("申请付款金额必须大于0"); if (!"project_advance".equals(request.getPaymentType()) && request.getSettlementId() == null && request.getPreSettlementId() == null && Func.isEmpty(request.getSettlementIds())) throw new ServiceException("非项目预付必须关联结算单"); if ("project_advance".equals(request.getPaymentType()) && request.getProjectId() == null) throw new ServiceException("项目预付必须选择所属项目"); if ("progress_advance".equals(request.getPaymentType()) && request.getPreSettlementId() == null) throw new ServiceException("进度预付必须关联预结算单"); if ("settlement_payment".equals(request.getPaymentType()) && request.getSettlementId() == null - && Func.isEmpty(request.getSettlementIds())) throw new ServiceException("结算付款必须关联正式结算单"); + && Func.isEmpty(request.getSettlementIds()) && request.getPreSettlementId() == null) { + throw new ServiceException("尾款付款必须关联正式结算单或预结算单"); + } } private void validateInvoice(PaymentApplicationInvoiceRequest invoice) { @@ -546,12 +566,12 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl ids = request.getSettlementIds() == null ? List.of() : request.getSettlementIds(); if (ids.isEmpty() && request.getSettlementId() != null) ids = List.of(request.getSettlementId()); ids = ids.stream().filter(Objects::nonNull).distinct().toList(); + if (ids.isEmpty()) return List.of(); List settlements = ids.stream().map(id -> formalSettlementMapper.selectById(id)).toList(); if (settlements.stream().anyMatch(item -> item == null || Objects.equals(item.getIsDeleted(), 1) || !APPROVED.equals(item.getApprovalStatus()) || !"payable".equals(item.getSettlementType()))) { @@ -586,7 +607,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl money(item.getSettlementAmount()).subtract(cumulativeAppliedAmount("settlement_payment", item.getId(), entity.getId())).max(BigDecimal.ZERO)) + .map(item -> money(item.getSettlementAmount()).subtract(cumulativeFormalSettlementApplied(item.getId(), entity.getId())).max(BigDecimal.ZERO)) .reduce(BigDecimal.ZERO, BigDecimal::add); if (money(entity.getAppliedAmount()).compareTo(payableAmount) > 0) throw new ServiceException("申请付款金额不能超过所选结算单可付款金额合计"); FormalSettlement first = settlements.get(0); @@ -601,14 +622,14 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpllambdaQuery() .eq(PaymentApplicationSettlement::getPaymentApplicationId, entity.getId())); BigDecimal totalBase = settlements.stream().map(item -> money(item.getSettlementAmount()).subtract( - cumulativeAppliedAmount("settlement_payment", item.getId(), entity.getId())).max(BigDecimal.ZERO)) + cumulativeFormalSettlementApplied(item.getId(), entity.getId())).max(BigDecimal.ZERO)) .reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal remainingApplied = money(entity.getAppliedAmount()); BigDecimal remainingPaid = money(entity.getPaidAmount()); for (int index = 0; index < settlements.size(); index++) { FormalSettlement settlement = settlements.get(index); BigDecimal base = money(settlement.getSettlementAmount()).subtract( - cumulativeAppliedAmount("settlement_payment", settlement.getId(), entity.getId())).max(BigDecimal.ZERO); + cumulativeFormalSettlementApplied(settlement.getId(), entity.getId())).max(BigDecimal.ZERO); BigDecimal applied = index == settlements.size() - 1 ? remainingApplied : money(entity.getAppliedAmount()).multiply(base).divide(totalBase, 2, RoundingMode.DOWN); BigDecimal paid = index == settlements.size() - 1 ? remainingPaid @@ -663,11 +684,26 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl wrapper = Wrappers.lambdaQuery() - .eq("settlement_payment".equals(paymentType), PaymentApplication::getSettlementId, referenceId) - .eq("progress_advance".equals(paymentType), PaymentApplication::getPreSettlementId, referenceId) - .ne(excludeId != null, PaymentApplication::getId, excludeId); - return sumApplied(wrapper); + if ("progress_advance".equals(paymentType)) { + return cumulativePreSettlementApplied(referenceId, excludeId); + } + if ("settlement_payment".equals(paymentType)) { + return cumulativeFormalSettlementApplied(referenceId, excludeId); + } + return BigDecimal.ZERO; + } + + private BigDecimal cumulativeFormalSettlementApplied(Long settlementId, Long excludeId) { + return sumApplied(Wrappers.lambdaQuery() + .eq(PaymentApplication::getSettlementId, settlementId) + .ne(excludeId != null, PaymentApplication::getId, excludeId)); + } + + private BigDecimal cumulativePreSettlementApplied(Long preSettlementId, Long excludeId) { + // 进度预付、尾款付款关联同一预结算单时累计占用可付款额度 + return sumApplied(Wrappers.lambdaQuery() + .eq(PaymentApplication::getPreSettlementId, preSettlementId) + .ne(excludeId != null, PaymentApplication::getId, excludeId)); } private void fillBillLedger(PaymentApplication entity, PaymentApplicationSaveRequest request) { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java index a761ddba..c3d47710 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java @@ -1817,22 +1817,68 @@ public class ReceivablePayableDetailServiceImpl /** * 解析合同生效计费方案。 - * 合同存在勾选默认的计费方案时仅在默认方案内匹配计算,不再回退其他方案; - * 全部方案均未勾选默认时仅使用最新添加的计费方案。 + * 仅使用与运单运输方式一致的计费方案;无匹配方案时返回 null,不生成应收应付。 + * 匹配方案中存在默认方案时取最新默认方案;否则取最新匹配方案。 */ private Map resolveDefaultBillingPlan(List> plans, String transportType) { if (plans.isEmpty()) return null; - List> defaultPlans = plans.stream().filter(this::isDefaultPlan).toList(); - if (defaultPlans.isEmpty()) return plans.get(plans.size() - 1); - Optional> matchedDefaultPlan = defaultPlans.stream() - .filter(plan -> !isBlank(plan.get("transportMode"))) - .filter(plan -> matchesCondition(plan.get("transportMode"), transportType)) - .findFirst(); - if (matchedDefaultPlan.isPresent()) return matchedDefaultPlan.get(); - return defaultPlans.stream() - .filter(plan -> isBlank(plan.get("transportMode"))) - .findFirst() - .orElseGet(() -> defaultPlans.get(defaultPlans.size() - 1)); + List> matchedPlans = plans.stream() + .filter(plan -> matchesTransportMode(plan.get("transportMode"), transportType)) + .toList(); + if (matchedPlans.isEmpty()) { + return null; + } + List> defaultPlans = matchedPlans.stream().filter(this::isDefaultPlan).toList(); + if (!defaultPlans.isEmpty()) { + return defaultPlans.get(defaultPlans.size() - 1); + } + return matchedPlans.get(matchedPlans.size() - 1); + } + + /** + * 计费方案运输方式与运单运输方式比对。 + * 统一归一化为字典键后精确比较,避免 railway 被子串 road 误匹配。 + */ + private boolean matchesTransportMode(Object expected, String actual) { + if (isBlank(expected) || Func.isBlank(actual)) { + return false; + } + if (expected instanceof List values) { + return values.stream().anyMatch(value -> matchesTransportMode(value, actual)); + } + String expectedKey = normalizeTransportModeKey(String.valueOf(expected)); + String actualKey = normalizeTransportModeKey(actual); + if (expectedKey.isEmpty() || actualKey.isEmpty()) { + return false; + } + return expectedKey.equals(actualKey); + } + + /** + * 将运输方式字典键/中文名归一化为统一键:road / railway / river / air。 + * 须先判断铁路再判断公路,避免 railway 被识别为 road。 + */ + private String normalizeTransportModeKey(String value) { + if (value == null) { + return ""; + } + String text = value.trim().toLowerCase(); + if (text.isEmpty()) { + return ""; + } + if ("railway".equals(text) || "rail".equals(text) || text.contains("铁路")) { + return "railway"; + } + if ("road".equals(text) || "gl".equals(text) || text.contains("公路") || text.contains("道路")) { + return "road"; + } + if ("river".equals(text) || "water".equals(text) || text.contains("水路") || text.contains("水运")) { + return "river"; + } + if ("air".equals(text) || "aviation".equals(text) || text.contains("航空") || text.contains("空运")) { + return "air"; + } + return text; } private BigDecimal calculateRule(Map rule, Waybill waybill) { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java index acff3186..9594475f 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java @@ -684,9 +684,15 @@ public class WaybillServiceImpl extends BaseServiceImpl @Override @Transactional(rollbackFor = Exception.class) public boolean submit(Waybill waybill) { + boolean created = Func.isEmpty(waybill.getId()); prepareForSave(waybill); validate(waybill); - return saveOrUpdate(waybill); + boolean result = saveOrUpdate(waybill); + // 复制后确认提交为已完成时,按合同计费规则生成应收应付 + if (result && created) { + generateReceivablePayableIfCompleted(waybill); + } + return result; } @Override @@ -1000,9 +1006,25 @@ public class WaybillServiceImpl extends BaseServiceImpl } validate(target); save(target); + // 原运单已完成时,复制后按合同系统计费规则生成应收应付明细 + generateReceivablePayableIfCompleted(target); return detail(target.getId()); } + /** + * 新建/复制运单若业务状态为已完成,按合同系统计费模式生成应收、应付明细。 + *

使用同事务入口,避免 REQUIRES_NEW 读不到尚未提交的新运单。

+ */ + private void generateReceivablePayableIfCompleted(Waybill waybill) { + if (waybill == null || Func.isEmpty(waybill.getId())) { + return; + } + if (!"completed".equals(waybill.getBusinessStatus())) { + return; + } + receivablePayableDetailService.generateForImportedWaybills(List.of(waybill)); + } + @Override @Transactional(rollbackFor = Exception.class) public boolean changeRoute(Waybill waybill) { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/PaymentApplicationWrapper.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/PaymentApplicationWrapper.java index 7d4f4826..2e0aa1f8 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/PaymentApplicationWrapper.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/PaymentApplicationWrapper.java @@ -38,7 +38,7 @@ public class PaymentApplicationWrapper extends BaseEntityWrapper "项目预付"; case "progress_advance" -> "进度预付"; - case "settlement_payment" -> "结算付款"; + case "settlement_payment" -> "尾款付款"; default -> entity.getPaymentType(); }); vo.setApprovalStatusName(switch (entity.getApprovalStatus() == null ? "" : entity.getApprovalStatus()) { From f09879d1dcad327c15550b27814183f30f2b23f4 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Wed, 23 Sep 2026 11:29:52 +0800 Subject: [PATCH 50/55] =?UTF-8?q?:bug:=20=E4=BF=AE=E5=A4=8D=E5=AE=A2?= =?UTF-8?q?=E5=95=86=E7=B1=BB=E5=9E=8B=E6=97=A0=E6=B3=95=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pojo/entity/CustomerArchive.java | 3 ++ .../transport/pojo/vo/CustomerArchiveVO.java | 4 --- .../transport/excel/CustomerArchiveExcel.java | 3 ++ .../mapper/CustomerArchiveMapper.xml | 5 +++ .../impl/CustomerArchiveServiceImpl.java | 33 +++++++++++++++++-- doc/sql/transport/blade_customer_archive.sql | 1 + ...ustomer_archive_customer_kind_20260923.sql | 19 +++++++++++ 7 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 doc/sql/transport/blade_customer_archive_customer_kind_20260923.sql diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/CustomerArchive.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/CustomerArchive.java index 0fc97777..fa780bbf 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/CustomerArchive.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/CustomerArchive.java @@ -58,6 +58,9 @@ public class CustomerArchive extends TenantEntity { @Schema(description = "客商全称") private String fullName; + @Schema(description = "客商类型:external外部客商,internal内部组织") + private String customerKind; + @Schema(description = "客商性质") private String customerNature; diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CustomerArchiveVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CustomerArchiveVO.java index 880566ec..802ed9ed 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CustomerArchiveVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/CustomerArchiveVO.java @@ -82,10 +82,6 @@ public class CustomerArchiveVO extends CustomerArchive { @Schema(description = "是否保存变更记录(仅提交时为 true,保存时不记录)") private Boolean recordChange; - @TableField(exist = false) - @Schema(description = "客商类型:external外部客商,internal内部组织") - private String customerKind; - @TableField(exist = false) @Schema(description = "资金使用风险等级:high、medium、none") private String fundUseRisk; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CustomerArchiveExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CustomerArchiveExcel.java index cc15e8b6..bd9c4709 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CustomerArchiveExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/CustomerArchiveExcel.java @@ -58,6 +58,9 @@ public class CustomerArchiveExcel implements Serializable { private String fullName; @ExcelProperty("客商类型") + private String customerKind; + + @ExcelProperty("客商分类") private String customerType; @ExcelProperty("客商性质") diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/CustomerArchiveMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/CustomerArchiveMapper.xml index f67cfedf..6da293f2 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/CustomerArchiveMapper.xml +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/CustomerArchiveMapper.xml @@ -15,6 +15,7 @@ + @@ -63,6 +64,7 @@ customer_code, short_name, full_name, + customer_kind, customer_nature, guangxi_top100, unified_credit_code, @@ -128,6 +130,9 @@ AND unified_credit_code LIKE #{creditCodeLike} + + AND customer_kind = #{customer.customerKind} + AND customer_nature = #{customer.customerNature} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java index 53c479cd..3ff55747 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/CustomerArchiveServiceImpl.java @@ -461,6 +461,7 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl(); @@ -1423,10 +1448,11 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl Date: Wed, 23 Sep 2026 14:08:53 +0800 Subject: [PATCH 51/55] =?UTF-8?q?:bug:=20=E4=BF=AE=E5=A4=8D=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E6=97=A0=E6=B3=95=E8=BF=90=E8=A1=8C=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blade-auth/src/main/resources/application-dev.yml | 6 ++++++ blade-gateway/src/main/resources/application-dev.yml | 5 +++-- .../blade-system/src/main/resources/application-dev.yml | 6 ++++++ .../transport/service/impl/ProjectApplyServiceImpl.java | 4 +--- .../blade-transport/src/main/resources/application-dev.yml | 5 +++++ doc/nacos/blade-dev.yaml | 5 +++-- doc/nacos/blade.yaml | 2 ++ 7 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 blade-auth/src/main/resources/application-dev.yml create mode 100644 blade-service/blade-system/src/main/resources/application-dev.yml diff --git a/blade-auth/src/main/resources/application-dev.yml b/blade-auth/src/main/resources/application-dev.yml new file mode 100644 index 00000000..feb2ba40 --- /dev/null +++ b/blade-auth/src/main/resources/application-dev.yml @@ -0,0 +1,6 @@ +# 本机多网卡时 Nacos 易误选 169.254.*(链路本地),导致网关 Host is down +spring: + cloud: + nacos: + discovery: + ip: ${NACOS_DISCOVERY_IP:127.0.0.1} diff --git a/blade-gateway/src/main/resources/application-dev.yml b/blade-gateway/src/main/resources/application-dev.yml index b8eaf546..3d12a7a4 100644 --- a/blade-gateway/src/main/resources/application-dev.yml +++ b/blade-gateway/src/main/resources/application-dev.yml @@ -5,7 +5,8 @@ blade: enabled: true #灰度版本 #version: 3.0.0 - #负载均衡优先调用的ip段 + #负载均衡优先调用的ip段(含本机局域网段,避免只剩 169.254.* 时误路由) prior-ip-pattern: - - 192.168.0.* + - 192.168.*.* + - 172.16.*.* - 127.0.0.1 diff --git a/blade-service/blade-system/src/main/resources/application-dev.yml b/blade-service/blade-system/src/main/resources/application-dev.yml new file mode 100644 index 00000000..feb2ba40 --- /dev/null +++ b/blade-service/blade-system/src/main/resources/application-dev.yml @@ -0,0 +1,6 @@ +# 本机多网卡时 Nacos 易误选 169.254.*(链路本地),导致网关 Host is down +spring: + cloud: + nacos: + discovery: + ip: ${NACOS_DISCOVERY_IP:127.0.0.1} diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ProjectApplyServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ProjectApplyServiceImpl.java index c1e759d9..0d623fb0 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ProjectApplyServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ProjectApplyServiceImpl.java @@ -257,14 +257,12 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl beforeData = buildProjectChangeSnapshot(oldRecord); prepare(projectApply); copyChangeFields(oldRecord, projectApply); validateSubmit(oldRecord); validateChangeLength(oldRecord); - Map afterData = buildProjectChangeSnapshot(oldRecord); - appendProjectChangeRecord(oldRecord, CHANGE_TYPE_PROJECT, oldRecord.getChangeReason(), "saved", "已保存", beforeData, afterData); return updateById(oldRecord); } diff --git a/blade-service/blade-transport/src/main/resources/application-dev.yml b/blade-service/blade-transport/src/main/resources/application-dev.yml index ebffbff7..3c8c9a46 100644 --- a/blade-service/blade-transport/src/main/resources/application-dev.yml +++ b/blade-service/blade-transport/src/main/resources/application-dev.yml @@ -8,6 +8,11 @@ spring: url: ${blade.datasource.dev.url} username: ${blade.datasource.dev.username} password: ${blade.datasource.dev.password} + cloud: + nacos: + discovery: + # 本机多网卡时避免注册 169.254.* 导致网关 Host is down + ip: ${NACOS_DISCOVERY_IP:127.0.0.1} # LBS 车辆实时定位(Authorization 与 OA 人员接口一致) thirdParty: diff --git a/doc/nacos/blade-dev.yaml b/doc/nacos/blade-dev.yaml index 744c59f6..4c8551ea 100644 --- a/doc/nacos/blade-dev.yaml +++ b/doc/nacos/blade-dev.yaml @@ -38,9 +38,10 @@ blade: loadbalancer: #开启配置 enabled: true - #负载均衡优先调用的ip段 + #负载均衡优先调用的ip段(含本机局域网段,避免只剩 169.254.* 时误路由) prior-ip-pattern: - - 192.168.0.* + - 192.168.*.* + - 172.16.*.* - 127.0.0.1 #通用开发生产环境数据库地址(特殊情况可在对应的子工程里配置覆盖) datasource: diff --git a/doc/nacos/blade.yaml b/doc/nacos/blade.yaml index 3265744b..d73eb7db 100644 --- a/doc/nacos/blade.yaml +++ b/doc/nacos/blade.yaml @@ -22,6 +22,8 @@ spring: discovery: # 禁用云命名空间解析,防止和云平台环境变量冲突 isUseCloudNamespaceParsing: false + # 本机多网卡开发时建议固定 127.0.0.1,可用环境变量 NACOS_DISCOVERY_IP 覆盖 + ip: ${NACOS_DISCOVERY_IP:} config: # 禁用云命名空间解析,防止和云平台环境变量冲突 isUseCloudNamespaceParsing: false From 6eb5362d5f62f51aac7eac0258f6b270c1505abc Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Wed, 23 Sep 2026 14:49:19 +0800 Subject: [PATCH 52/55] =?UTF-8?q?:bug:=20=E4=BF=AE=E5=A4=8D=E8=BD=A6?= =?UTF-8?q?=E8=BE=86=E8=B0=83=E5=BA=A6=E5=88=97=E8=A1=A8=E6=8E=92=E5=BA=8F?= =?UTF-8?q?=E8=A7=84=E5=88=99=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JOIN 车牌号/租户时统一 utf8mb4_general_ci,并补充车辆表排序规则对齐脚本。 Co-authored-by: Cursor --- .../springblade/transport/mapper/VehicleDispatchMapper.xml | 4 ++-- doc/sql/transport/blade_transport_vehicle.sql | 2 +- .../transport/blade_transport_vehicle_collation_20260923.sql | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 doc/sql/transport/blade_transport_vehicle_collation_20260923.sql diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/VehicleDispatchMapper.xml b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/VehicleDispatchMapper.xml index 72a9bfb8..a5d32136 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/VehicleDispatchMapper.xml +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/mapper/VehicleDispatchMapper.xml @@ -28,8 +28,8 @@ d.is_deleted FROM blade_transport_vehicle_dispatch d LEFT JOIN blade_transport_vehicle v - ON v.plate_no = d.plate_no - AND v.tenant_id = d.tenant_id + ON v.plate_no COLLATE utf8mb4_general_ci = d.plate_no + AND v.tenant_id COLLATE utf8mb4_general_ci = d.tenant_id AND v.is_deleted = 0 WHERE d.is_deleted = 0 diff --git a/doc/sql/transport/blade_transport_vehicle.sql b/doc/sql/transport/blade_transport_vehicle.sql index a5260fb5..3a5f3f22 100644 --- a/doc/sql/transport/blade_transport_vehicle.sql +++ b/doc/sql/transport/blade_transport_vehicle.sql @@ -57,7 +57,7 @@ CREATE TABLE `blade_transport_vehicle` ( KEY `idx_transport_vehicle_road_end` (`road_transport_cert_end_date`) USING BTREE, KEY `idx_transport_vehicle_annual_end` (`annual_review_end_date`) USING BTREE, KEY `idx_transport_vehicle_status` (`status`) USING BTREE -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆管理'; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='车辆管理'; -- ---------------------------- -- Records of blade_menu for transport capacity vehicle diff --git a/doc/sql/transport/blade_transport_vehicle_collation_20260923.sql b/doc/sql/transport/blade_transport_vehicle_collation_20260923.sql new file mode 100644 index 00000000..a0733703 --- /dev/null +++ b/doc/sql/transport/blade_transport_vehicle_collation_20260923.sql @@ -0,0 +1,4 @@ +-- 对齐车辆表字符串列排序规则,避免与 utf8mb4_general_ci 表 JOIN 报 Illegal mix of collations +-- 背景:blade_transport_vehicle 建表未显式指定 COLLATE,MySQL 8 默认 utf8mb4_0900_ai_ci +ALTER TABLE `blade_transport_vehicle` + CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; From 9ec28e2c3005f9d40577a4ab717d84ff05b8d2d9 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Wed, 23 Sep 2026 14:49:19 +0800 Subject: [PATCH 53/55] =?UTF-8?q?:bug:=20=E6=9C=AC=E5=9C=B0=E5=BC=80?= =?UTF-8?q?=E5=8F=91=E5=BC=BA=E5=88=B6=20resource=20=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E6=9C=AC=E6=9C=BA=20IP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 避免 Nacos 误选 169.254.* 导致上传附件 Host is down。 Co-authored-by: Cursor --- .../blade-resource/src/main/resources/application-dev.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/blade-ops/blade-resource/src/main/resources/application-dev.yml b/blade-ops/blade-resource/src/main/resources/application-dev.yml index 2ef60321..acf0e796 100644 --- a/blade-ops/blade-resource/src/main/resources/application-dev.yml +++ b/blade-ops/blade-resource/src/main/resources/application-dev.yml @@ -1,5 +1,10 @@ -#数据源配置 +# 本机多网卡时 Nacos 易误选 169.254.*(链路本地),导致网关 Host is down spring: + cloud: + nacos: + discovery: + ip: ${NACOS_DISCOVERY_IP:127.0.0.1} +#数据源配置 datasource: url: ${blade.datasource.dev.url} username: ${blade.datasource.dev.username} From 29b846017b2ff2845f37e48c7222b297840b8d9f Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Wed, 23 Sep 2026 14:49:19 +0800 Subject: [PATCH 54/55] =?UTF-8?q?:zap:=20=E5=9B=BA=E5=AE=9A=E9=87=91?= =?UTF-8?q?=E9=A2=9D=E4=B8=8E=E9=87=8C=E7=A8=8B=E7=B1=BB=E8=AE=A1=E8=B4=B9?= =?UTF-8?q?=E8=B7=B3=E8=BF=87=E6=95=B0=E9=87=8F=E5=8D=95=E4=BD=8D=E5=8C=B9?= =?UTF-8?q?=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按车辆、固定金额、按公里/吨公里等计费要素不再要求货物数量单位一致。 Co-authored-by: Cursor --- .../ReceivablePayableDetailServiceImpl.java | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java index c3d47710..81e8778c 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java @@ -1113,21 +1113,47 @@ public class ReceivablePayableDetailServiceImpl * 费用生成规则命中: * 1. 货物需满足匹配规则(始发/目的/货类/货名) * 2. 普通计费要素还要求货物数量单位与计费单位一致 - * 3. 按里程、按吨·公里按运单里程(×重量)另行计算,不校验数量单位 + * 3. 按里程/按公里、按吨·公里/按吨公里、按车辆、固定金额不依赖货物数量单位,不校验数量单位 */ private boolean matchesBillingRule(Map raw, Map rule, Waybill feeWaybill) { if (!matchesRule(raw, feeWaybill)) { return false; } String element = stringValue(rule, "billingElement", ""); - if (isMileageBasedElement(element)) { + if (skipsQuantityUnitMatch(element)) { return true; } return matchesBillingUnit(rule, feeWaybill); } private boolean isMileageBasedElement(String element) { - return "按里程".equals(element) || "按吨·公里".equals(element); + String normalized = normalizeBillingElement(element); + return "按里程".equals(normalized) + || "按公里".equals(normalized) + || "按吨公里".equals(normalized); + } + + /** + * 不计货物数量单位的计费要素:里程类按运单里程计算;按车辆/固定金额按整单(量=1)计费。 + */ + private boolean skipsQuantityUnitMatch(String element) { + String normalized = normalizeBillingElement(element); + return isMileageBasedElement(normalized) + || "按车辆".equals(normalized) + || "固定金额(整单一口价)".equals(normalized) + || "固定金额".equals(normalized); + } + + /** 统一计费要素文案,兼容「按公里 / 按吨公里 / 按吨·公里」等写法 */ + private String normalizeBillingElement(String element) { + if (element == null) { + return ""; + } + return element.trim() + .replace("·", "") + .replace(".", "") + .replace(".", "") + .replace(" ", ""); } private boolean matchesBillingUnit(Map rule, Waybill feeWaybill) { From f0732765cf1a634b22ed9dd02020f746e8c95bc3 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Wed, 23 Sep 2026 18:35:28 +0800 Subject: [PATCH 55/55] =?UTF-8?q?:sparkles:=20=E5=AE=8C=E5=96=84=E9=85=8D?= =?UTF-8?q?=E8=BD=BD=E8=8D=89=E7=A8=BF=E7=A1=AE=E8=AE=A4=E4=B8=8E=E8=BF=90?= =?UTF-8?q?=E5=8D=95=E6=95=B0=E9=87=8F/=E9=87=8C=E7=A8=8B=E8=90=BD?= =?UTF-8?q?=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 配载暂存免校验且确认生成待执行正式单,草稿仅预占运单不改其状态;里程 -1 对外置空;多货物运单按 goodsJson 合计回写数量并用于应收应付。 Co-authored-by: Cursor --- .../impl/LoadingManageServiceImpl.java | 35 ++++++++++++++++-- .../ReceivablePayableDetailServiceImpl.java | 17 ++++++++- .../service/impl/WaybillServiceImpl.java | 36 +++++++++++++++++++ .../wrapper/LoadingManageWrapper.java | 6 ++++ 4 files changed, 90 insertions(+), 4 deletions(-) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java index d956a87c..df6a10f3 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/LoadingManageServiceImpl.java @@ -109,7 +109,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl oldWaybillIds = new ArrayList<>(); // 新建暂存为草稿;编辑已有单据时保持原业务状态(如待执行),避免被改回草稿 String businessStatus = STATUS_DRAFT; @@ -128,10 +128,14 @@ public class LoadingManageServiceImpl extends BaseServiceImpl oldWaybillIds) { + List newWaybillIds = waybillIds(loadingManage.getWaybillIdsJson()); + if (Func.isNotEmpty(oldWaybillIds)) { + List removedIds = new ArrayList<>(oldWaybillIds); + removedIds.removeAll(newWaybillIds); + releaseAssociatedWaybills(removedIds, STATUS_PENDING); + } + if (Func.isEmpty(newWaybillIds)) { + return; + } + waybillMapper.update(null, Wrappers.lambdaUpdate() + .in(Waybill::getId, newWaybillIds) + .set(Waybill::getLoadingNo, loadingManage.getLoadingNo())); + } + private void releaseAssociatedWaybills(List waybillIds, String status) { if (Func.isEmpty(waybillIds)) { return; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java index 81e8778c..084f85dd 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java @@ -1463,7 +1463,7 @@ public class ReceivablePayableDetailServiceImpl detail.setTransportType(waybill.getTransportType()); detail.setCargoName(waybill.getCargoName()); detail.setCargoType(waybill.getCargoType()); - detail.setTransportQuantity(waybill.getQuantity()); + detail.setTransportQuantity(resolveWaybillTransportQuantity(waybill)); detail.setQuantityUnit(waybill.getQuantityUnit()); detail.setMileage(normalizeGeneratedMileage(waybill.getMileage())); detail.setBatchNo(waybill.getBatchNo()); @@ -1477,6 +1477,21 @@ public class ReceivablePayableDetailServiceImpl return detail; } + /** + * 多货物运单优先按 goodsJson 数量合计作为运输总量,避免主表 quantity 只落了首行。 + */ + private BigDecimal resolveWaybillTransportQuantity(Waybill waybill) { + List> goods = parseList(waybill.getGoodsJson()); + if (goods.size() > 1) { + BigDecimal total = goods.stream().map(item -> decimal(item.get("quantity"))) + .reduce(BigDecimal.ZERO, BigDecimal::add); + if (total.signum() > 0) { + return total; + } + } + return money(waybill.getQuantity()); + } + private ReceivablePayableDetail buildMasterOrderDetail(MasterOrder masterOrder, ContractManage contract, List masterGoods, String settlementType, List fees, BigDecimal unitPrice) { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java index 9594475f..e92a0df4 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/WaybillServiceImpl.java @@ -1358,6 +1358,8 @@ public class WaybillServiceImpl extends BaseServiceImpl if (isSentinelMinusOne(waybill.getQuantity())) waybill.setQuantity(null); if (isSentinelMinusOne(waybill.getMileage())) waybill.setMileage(null); if (isSentinelMinusOne(waybill.getUnitPrice())) waybill.setUnitPrice(null); + if (isSentinelMinusOne(waybill.getOtherFeeTotal())) waybill.setOtherFeeTotal(null); + syncQuantityFromGoodsJson(waybill); waybill.setWaybillNo(TransportBusinessSupport.trimToNull(waybill.getWaybillNo())); waybill.setProjectName(TransportBusinessSupport.trimToNull(waybill.getProjectName())); waybill.setContractName(TransportBusinessSupport.trimToNull(waybill.getContractName())); @@ -1425,6 +1427,40 @@ public class WaybillServiceImpl extends BaseServiceImpl return value != null && value.compareTo(BigDecimal.ONE.negate()) == 0; } + /** + * 完整录入多货物时,运单主表 quantity 按 goodsJson 数量合计回写,避免只落首行数量。 + */ + private void syncQuantityFromGoodsJson(Waybill waybill) { + if (waybill == null || Func.isEmpty(waybill.getGoodsJson())) { + return; + } + List goodsList; + try { + goodsList = JsonUtil.parseArray(waybill.getGoodsJson(), Map.class); + } catch (Exception exception) { + return; + } + if (Func.isEmpty(goodsList) || goodsList.size() <= 1) { + return; + } + BigDecimal total = BigDecimal.ZERO; + boolean hasQuantity = false; + for (Map goods : goodsList) { + if (goods == null || goods.get("quantity") == null) { + continue; + } + BigDecimal quantity = new BigDecimal(String.valueOf(goods.get("quantity")).trim()); + if (isSentinelMinusOne(quantity)) { + continue; + } + hasQuantity = true; + total = total.add(quantity); + } + if (hasQuantity) { + waybill.setQuantity(total); + } + } + private void validate(Waybill waybill) { TransportBusinessSupport.validateRequired(waybill.getProjectName(), "项目不能为空"); TransportBusinessSupport.validateRequired(waybill.getContractName(), "客户合同不能为空"); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/LoadingManageWrapper.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/LoadingManageWrapper.java index af4725c3..b9456046 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/LoadingManageWrapper.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/LoadingManageWrapper.java @@ -12,6 +12,7 @@ import org.springblade.system.cache.UserCache; import org.springblade.transport.pojo.entity.LoadingManage; import org.springblade.transport.pojo.vo.LoadingManageVO; +import java.math.BigDecimal; import java.util.Objects; /** @@ -33,6 +34,11 @@ public class LoadingManageWrapper extends BaseEntityWrapper