♻️ 生效配置与运单快照的取法、二选一规则收进 LiveProcessConfigSupport 一处
原先三处各写一份(运单服务、司机运单服务、运次读服务),且规则并不完全一样:司机端与运次读服务 在生效配置没有可打卡节点时回退快照,运单服务不回退。两份长得一样却不一样,改一处没有任何提示。 新类把取配置(单项目 LIKE + 逗号分段精确比对、批量一次查全)与两个变体都收在一处,并在 javadoc 里 写明各自适用面:接单需求、节点门禁、节点订正读 resolve(生效配置是权威,不能被快照顶掉); 司机端打卡页与列表卡读 resolveForPunch(配置被改坏时回退快照,避免打卡页空白)。 三处的 SQL 形状与逐分支行为保持不变,调用点只留一行委托。 新增 LiveProcessConfigSupportTest(8 项)覆盖两条规则的全部形态与项目 id 精确比对; blade-transport 全量 365 项用例通过。
This commit is contained in:
+6
-45
@@ -48,8 +48,8 @@ import org.springblade.transport.pojo.entity.ExceptionDisposal;
|
||||
import org.springblade.transport.pojo.entity.TransportVehicle;
|
||||
import org.springblade.transport.service.ITransportVehicleService;
|
||||
import org.springblade.transport.support.CertificationGate;
|
||||
import org.springblade.transport.support.LiveProcessConfigSupport;
|
||||
import org.springblade.transport.support.VehiclePlateSupport;
|
||||
import org.springblade.transport.pojo.entity.ProcessConfig;
|
||||
import org.springblade.transport.pojo.entity.Waybill;
|
||||
import org.springblade.transport.pojo.entity.WaybillEnroutePunch;
|
||||
import org.springblade.transport.pojo.entity.WaybillNodePunch;
|
||||
@@ -1185,55 +1185,16 @@ public class DriverWaybillServiceImpl implements IDriverWaybillService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 优先用项目启用中的过程配置;若动态配置无打卡节点,回退运单快照 processJson,
|
||||
* 避免项目配置改坏后司机端打卡页空白。
|
||||
* 优先用项目启用中的过程配置;若动态配置无打卡节点,回退运单快照,
|
||||
* 避免项目配置改坏后司机端打卡页空白。取法与规则都在 {@link LiveProcessConfigSupport}。
|
||||
*/
|
||||
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 snapshot;
|
||||
}
|
||||
|
||||
private String loadLiveProcessConfigJson(Long projectId) {
|
||||
if (projectId == null) {
|
||||
return null;
|
||||
}
|
||||
String projectIdStr = String.valueOf(projectId);
|
||||
return processConfigService.list(Wrappers.<ProcessConfig>lambdaQuery()
|
||||
.eq(ProcessConfig::getStatus, 1)
|
||||
.eq(ProcessConfig::getIsDeleted, 0)
|
||||
.like(ProcessConfig::getProjectIds, projectIdStr)
|
||||
.orderByDesc(ProcessConfig::getUpdateTime)
|
||||
.orderByDesc(ProcessConfig::getCreateTime))
|
||||
.stream()
|
||||
.filter(cfg -> containsProjectId(cfg.getProjectIds(), projectIdStr))
|
||||
.map(ProcessConfig::getNodeConfigJson)
|
||||
.filter(Func::isNotEmpty)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private boolean containsProjectId(String projectIds, String projectId) {
|
||||
if (Func.isEmpty(projectIds) || Func.isEmpty(projectId)) {
|
||||
return false;
|
||||
}
|
||||
return Arrays.stream(projectIds.split(","))
|
||||
.map(String::trim)
|
||||
.anyMatch(projectId::equals);
|
||||
return LiveProcessConfigSupport.resolveForPunch(
|
||||
LiveProcessConfigSupport.byProject(processConfigService, waybill.getProjectId()),
|
||||
waybill.getProcessJson());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+4
-46
@@ -30,7 +30,6 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.transport.pojo.entity.LoadingManage;
|
||||
import org.springblade.transport.pojo.entity.ProcessConfig;
|
||||
import org.springblade.transport.pojo.entity.Waybill;
|
||||
import org.springblade.transport.pojo.vo.TripQueryVO;
|
||||
import org.springblade.transport.pojo.vo.TripStationVO;
|
||||
@@ -40,12 +39,12 @@ import org.springblade.transport.service.ILoadingManageService;
|
||||
import org.springblade.transport.service.IProcessConfigService;
|
||||
import org.springblade.transport.service.ITripQueryService;
|
||||
import org.springblade.transport.service.IWaybillService;
|
||||
import org.springblade.transport.support.LiveProcessConfigSupport;
|
||||
import org.springblade.transport.support.WaybillProcessSupport;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
@@ -287,6 +286,7 @@ public class TripQueryServiceImpl implements ITripQueryService {
|
||||
* 按单票路径的口径把生效中的过程配置写回内存里的行:项目启用中的配置优先,它没有可打卡节点时回退运单快照。
|
||||
* <p>
|
||||
* 只改内存里的对象,分档与卡片下发都读它,不落库;生效配置一次取回后按项目复用,不逐条回查。
|
||||
* 取法与二选一规则都在 {@link LiveProcessConfigSupport},这里只做批量写回。
|
||||
*/
|
||||
private void applyLiveProcessConfig(List<Waybill> rows) {
|
||||
if (Func.isEmpty(rows)) {
|
||||
@@ -297,60 +297,18 @@ public class TripQueryServiceImpl implements ITripQueryService {
|
||||
if (projectIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<Long, String> live = liveProcessConfigs(projectIds);
|
||||
Map<Long, String> live = LiveProcessConfigSupport.byProjects(processConfigService, projectIds);
|
||||
if (live.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (Waybill row : rows) {
|
||||
String liveJson = live.get(row.getProjectId());
|
||||
if (Func.isNotEmpty(liveJson)) {
|
||||
row.setProcessJson(resolveProcessJson(liveJson, row.getProcessJson()));
|
||||
row.setProcessJson(LiveProcessConfigSupport.resolveForPunch(liveJson, row.getProcessJson()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 生效配置与快照二选一:生效配置没有可打卡节点时回退快照,两边都取不到节点就按生效配置来 */
|
||||
private String resolveProcessJson(String liveJson, String snapshot) {
|
||||
if (WaybillProcessSupport.listDriverPunchNodes(liveJson).isEmpty()
|
||||
&& Func.isNotEmpty(snapshot)
|
||||
&& !WaybillProcessSupport.listDriverPunchNodes(snapshot).isEmpty()) {
|
||||
return snapshot;
|
||||
}
|
||||
return liveJson;
|
||||
}
|
||||
|
||||
/** 生效中的过程配置:项目 → 节点配置 JSON,取最近更新的一条 */
|
||||
private Map<Long, String> liveProcessConfigs(Set<Long> projectIds) {
|
||||
List<ProcessConfig> configs = processConfigService.list(Wrappers.<ProcessConfig>lambdaQuery()
|
||||
.eq(ProcessConfig::getStatus, 1)
|
||||
.eq(ProcessConfig::getIsDeleted, 0)
|
||||
.orderByDesc(ProcessConfig::getUpdateTime)
|
||||
.orderByDesc(ProcessConfig::getCreateTime));
|
||||
Map<Long, String> live = new LinkedHashMap<>();
|
||||
for (Long projectId : projectIds) {
|
||||
if (projectId == null) {
|
||||
continue;
|
||||
}
|
||||
String projectIdStr = String.valueOf(projectId);
|
||||
for (ProcessConfig config : configs) {
|
||||
if (Func.isEmpty(config.getNodeConfigJson())
|
||||
|| !containsProjectId(config.getProjectIds(), projectIdStr)) {
|
||||
continue;
|
||||
}
|
||||
live.put(projectId, config.getNodeConfigJson());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return live;
|
||||
}
|
||||
|
||||
private boolean containsProjectId(String projectIds, String projectId) {
|
||||
if (Func.isEmpty(projectIds) || Func.isEmpty(projectId)) {
|
||||
return false;
|
||||
}
|
||||
return Arrays.stream(projectIds.split(",")).map(String::trim).anyMatch(projectId::equals);
|
||||
}
|
||||
|
||||
/**
|
||||
* 有子单被拒、且这趟车还没结束的运次键:整单拒绝后这趟车不再算待处理
|
||||
*/
|
||||
|
||||
+13
-29
@@ -45,7 +45,6 @@ import org.springblade.transport.mapper.WaybillMapper;
|
||||
import org.springblade.transport.mapper.WaybillNodePunchMapper;
|
||||
import org.springblade.transport.pojo.entity.LoadingManage;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.ProcessConfig;
|
||||
import org.springblade.transport.pojo.entity.ProjectApply;
|
||||
import org.springblade.transport.pojo.entity.Waybill;
|
||||
import org.springblade.transport.pojo.entity.WaybillEnroutePunch;
|
||||
@@ -66,6 +65,7 @@ import org.springblade.transport.service.IProjectApplyService;
|
||||
import org.springblade.transport.service.IReceivablePayableDetailService;
|
||||
import org.springblade.transport.service.IContractManageService;
|
||||
import org.springblade.transport.service.IWaybillService;
|
||||
import org.springblade.transport.support.LiveProcessConfigSupport;
|
||||
import org.springblade.transport.support.TransportBusinessSupport;
|
||||
import org.springblade.transport.support.WaybillProcessSupport;
|
||||
import org.springblade.transport.wrapper.WaybillWrapper;
|
||||
@@ -822,27 +822,20 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 动态过程配置优先,回退运单快照 */
|
||||
/**
|
||||
* 生效配置优先,回退运单快照。
|
||||
* <p>
|
||||
* 取法与二选一规则在 {@link LiveProcessConfigSupport};这里不用「生效配置没有可打卡节点就回退快照」
|
||||
* 那一版——接单需求、节点门禁、节点订正都读这份配置判断节点存不存在,生效配置是权威,
|
||||
* 不能因为快照里有节点而被顶掉。
|
||||
*/
|
||||
private String resolveLiveProcessJson(Waybill waybill) {
|
||||
if (waybill.getProjectId() != null) {
|
||||
String projectId = String.valueOf(waybill.getProjectId());
|
||||
String live = processConfigService.list(Wrappers.<ProcessConfig>lambdaQuery()
|
||||
.eq(ProcessConfig::getStatus, 1)
|
||||
.eq(ProcessConfig::getIsDeleted, 0)
|
||||
.like(ProcessConfig::getProjectIds, projectId)
|
||||
.orderByDesc(ProcessConfig::getUpdateTime)
|
||||
.orderByDesc(ProcessConfig::getCreateTime))
|
||||
.stream()
|
||||
.filter(cfg -> containsProjectId(cfg.getProjectIds(), projectId))
|
||||
.map(ProcessConfig::getNodeConfigJson)
|
||||
.filter(Func::isNotEmpty)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (Func.isNotEmpty(live)) {
|
||||
return live;
|
||||
}
|
||||
if (waybill == null) {
|
||||
return null;
|
||||
}
|
||||
return waybill.getProcessJson();
|
||||
return LiveProcessConfigSupport.resolve(
|
||||
LiveProcessConfigSupport.byProject(processConfigService, waybill.getProjectId()),
|
||||
waybill.getProcessJson());
|
||||
}
|
||||
|
||||
private Map<String, List<String>> buildVoucherTypesIndex(String processJson) {
|
||||
@@ -1175,15 +1168,6 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
waybill.setDriverRejectReason(null);
|
||||
}
|
||||
|
||||
private boolean containsProjectId(String projectIds, String projectId) {
|
||||
if (Func.isEmpty(projectIds)) {
|
||||
return false;
|
||||
}
|
||||
return List.of(projectIds.split(",")).stream()
|
||||
.map(String::trim)
|
||||
.anyMatch(projectId::equals);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public BusinessRemoveResultVO removeWaybill(String ids) {
|
||||
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.support;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.transport.pojo.entity.ProcessConfig;
|
||||
import org.springblade.transport.service.IProcessConfigService;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 过程配置的取法与「生效配置还是运单快照」的二选一规则,只此一处。
|
||||
* <p>
|
||||
* 生效配置({@code blade_process_config})按项目生效,是活的:改一次,该项目下所有运单下次读取
|
||||
* 都按新配置呈现;运单创建时另存一份 {@code process_json} 快照,只在生效配置取不到时兜底。
|
||||
* 生效配置按更新时间倒序取第一条命中该项目的,项目集合是逗号串,故先 LIKE 粗筛再按逗号分段精确比对 ——
|
||||
* 只靠 LIKE 会把「1234」命中到「12345」上。
|
||||
* <p>
|
||||
* 两个二选一版本的区别是**得到什么结果**,不是风格:
|
||||
* <ul>
|
||||
* <li>{@link #resolve} 生效配置优先 —— 接单需求、节点门禁、节点订正这些判断节点存不存在的路径用它。
|
||||
* 生效配置是权威,即使它没配可打卡节点也不能被快照顶掉,否则判断依据会随时间漂移。</li>
|
||||
* <li>{@link #resolveForPunch} 生效配置没有可打卡节点时回退快照 —— 司机端打卡页与列表卡用它。
|
||||
* 配置被改坏(节点全删)时,司机端仍能按快照渲染出打卡节点,不至于空白。</li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class LiveProcessConfigSupport {
|
||||
|
||||
private LiveProcessConfigSupport() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 单个项目的生效配置 JSON,没有则返回 null
|
||||
*
|
||||
* @param service 过程配置服务
|
||||
* @param projectId 项目主键
|
||||
*/
|
||||
public static String byProject(IProcessConfigService service, Long projectId) {
|
||||
if (service == null || projectId == null) {
|
||||
return null;
|
||||
}
|
||||
String projectIdStr = String.valueOf(projectId);
|
||||
return service.list(Wrappers.<ProcessConfig>lambdaQuery()
|
||||
.eq(ProcessConfig::getStatus, 1)
|
||||
.eq(ProcessConfig::getIsDeleted, 0)
|
||||
.like(ProcessConfig::getProjectIds, projectIdStr)
|
||||
.orderByDesc(ProcessConfig::getUpdateTime)
|
||||
.orderByDesc(ProcessConfig::getCreateTime))
|
||||
.stream()
|
||||
.filter(config -> containsProjectId(config.getProjectIds(), projectIdStr))
|
||||
.map(ProcessConfig::getNodeConfigJson)
|
||||
.filter(Func::isNotEmpty)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 一批项目的生效配置:项目主键 → 节点配置 JSON,一次查全再按项目匹配,供列表页避免逐条回查
|
||||
*
|
||||
* @param service 过程配置服务
|
||||
* @param projectIds 项目主键集合,允许含 null
|
||||
*/
|
||||
public static Map<Long, String> byProjects(IProcessConfigService service, Collection<Long> projectIds) {
|
||||
Map<Long, String> live = new LinkedHashMap<>();
|
||||
if (service == null || Func.isEmpty(projectIds)) {
|
||||
return live;
|
||||
}
|
||||
Set<Long> wanted = new LinkedHashSet<>();
|
||||
projectIds.stream().filter(Objects::nonNull).forEach(wanted::add);
|
||||
if (wanted.isEmpty()) {
|
||||
return live;
|
||||
}
|
||||
List<ProcessConfig> configs = service.list(Wrappers.<ProcessConfig>lambdaQuery()
|
||||
.eq(ProcessConfig::getStatus, 1)
|
||||
.eq(ProcessConfig::getIsDeleted, 0)
|
||||
.orderByDesc(ProcessConfig::getUpdateTime)
|
||||
.orderByDesc(ProcessConfig::getCreateTime));
|
||||
if (Func.isEmpty(configs)) {
|
||||
return live;
|
||||
}
|
||||
for (Long projectId : wanted) {
|
||||
String projectIdStr = String.valueOf(projectId);
|
||||
for (ProcessConfig config : configs) {
|
||||
if (Func.isEmpty(config.getNodeConfigJson())
|
||||
|| !containsProjectId(config.getProjectIds(), projectIdStr)) {
|
||||
continue;
|
||||
}
|
||||
live.put(projectId, config.getNodeConfigJson());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return live;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生效配置优先,取不到时回退运单快照
|
||||
*
|
||||
* @param liveJson 生效配置 JSON,可为空
|
||||
* @param snapshot 运单快照 JSON,可为空
|
||||
*/
|
||||
public static String resolve(String liveJson, String snapshot) {
|
||||
return Func.isNotEmpty(liveJson) ? liveJson : snapshot;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生效配置优先;生效配置没有可打卡节点时回退快照,两边都没有节点就按生效配置来
|
||||
*
|
||||
* @param liveJson 生效配置 JSON,可为空
|
||||
* @param snapshot 运单快照 JSON,可为空
|
||||
*/
|
||||
public static String resolveForPunch(String liveJson, String snapshot) {
|
||||
if (Func.isEmpty(liveJson)) {
|
||||
return snapshot;
|
||||
}
|
||||
if (!WaybillProcessSupport.listDriverPunchNodes(liveJson).isEmpty()) {
|
||||
return liveJson;
|
||||
}
|
||||
if (Func.isNotEmpty(snapshot) && !WaybillProcessSupport.listDriverPunchNodes(snapshot).isEmpty()) {
|
||||
return snapshot;
|
||||
}
|
||||
return liveJson;
|
||||
}
|
||||
|
||||
/** 项目集合是逗号串,必须按逗号分段精确比对 */
|
||||
private static boolean containsProjectId(String projectIds, String projectId) {
|
||||
if (Func.isEmpty(projectIds) || Func.isEmpty(projectId)) {
|
||||
return false;
|
||||
}
|
||||
return Arrays.stream(projectIds.split(","))
|
||||
.map(String::trim)
|
||||
.anyMatch(projectId::equals);
|
||||
}
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
package org.springblade.transport.support;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.springblade.transport.pojo.entity.ProcessConfig;
|
||||
import org.springblade.transport.service.IProcessConfigService;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 生效配置的取法与「生效配置 vs 快照」二选一的唯一实现处。
|
||||
* <p>
|
||||
* 两条规则必须分得清:接单需求与节点门禁读 {@code resolve}(生效配置是权威,不能因为快照里有节点被顶掉),
|
||||
* 司机端打卡页读 {@code resolveForPunch}(生效配置被改坏成没有可打卡节点时回退快照,避免打卡页空白)。
|
||||
* 排序由 SQL 负责(更新时间倒序、创建时间倒序),这里按「已排好序的列表」构造输入。
|
||||
*/
|
||||
class LiveProcessConfigSupportTest {
|
||||
|
||||
/** 有可打卡节点(装货,punch=是) */
|
||||
private static final String PUNCH_JSON =
|
||||
"[{\"key\":\"load\",\"name\":\"装货\",\"enabled\":true,\"punch\":true,\"location\":true}]";
|
||||
/** 一条可打卡节点都没有(装货被关掉 punch) */
|
||||
private static final String NO_PUNCH_JSON =
|
||||
"[{\"key\":\"load\",\"name\":\"装货\",\"enabled\":true,\"punch\":false}]";
|
||||
/** 另一个项目用的、与 PUNCH_JSON 可区分的一份 */
|
||||
private static final String OTHER_PUNCH_JSON =
|
||||
"[{\"key\":\"unload\",\"name\":\"卸货\",\"enabled\":true,\"punch\":true,\"location\":true}]";
|
||||
|
||||
private IProcessConfigService processConfigService;
|
||||
|
||||
@BeforeAll
|
||||
static void initTableInfo() {
|
||||
// 构造 LambdaQueryWrapper 需要 MyBatis-Plus 的实体元数据缓存
|
||||
MybatisConfiguration configuration = new MybatisConfiguration();
|
||||
MapperBuilderAssistant assistant = new MapperBuilderAssistant(configuration, "");
|
||||
assistant.setCurrentNamespace("test");
|
||||
TableInfoHelper.initTableInfo(assistant, ProcessConfig.class);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
processConfigService = mock(IProcessConfigService.class);
|
||||
}
|
||||
|
||||
private void stubConfigs(ProcessConfig... configs) {
|
||||
when(processConfigService.list(anyConfigWrapper())).thenReturn(List.of(configs));
|
||||
}
|
||||
|
||||
private static Wrapper<ProcessConfig> anyConfigWrapper() {
|
||||
return ArgumentMatchers.<Wrapper<ProcessConfig>>any();
|
||||
}
|
||||
|
||||
private static ProcessConfig config(String projectIds, String nodeConfigJson) {
|
||||
ProcessConfig config = new ProcessConfig();
|
||||
config.setProjectIds(projectIds);
|
||||
config.setNodeConfigJson(nodeConfigJson);
|
||||
return config;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("单项目:命中项目的第一条生效配置(列表已按更新时间倒序)")
|
||||
void singleProjectTakesFirstHit() {
|
||||
stubConfigs(config("999", OTHER_PUNCH_JSON), config("1234,5678", PUNCH_JSON));
|
||||
|
||||
assertEquals(PUNCH_JSON, LiveProcessConfigSupport.byProject(processConfigService, 1234L));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("单项目:项目 id 按逗号分段精确比对,1234 不命中 12345")
|
||||
void singleProjectMatchesExactId() {
|
||||
stubConfigs(config("12345", PUNCH_JSON));
|
||||
|
||||
assertNull(LiveProcessConfigSupport.byProject(processConfigService, 1234L));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("单项目:没有生效配置、或项目为空时返回 null")
|
||||
void singleProjectWithoutConfig() {
|
||||
stubConfigs();
|
||||
|
||||
assertNull(LiveProcessConfigSupport.byProject(processConfigService, 1234L));
|
||||
assertNull(LiveProcessConfigSupport.byProject(processConfigService, null));
|
||||
assertNull(LiveProcessConfigSupport.byProject(null, 1234L));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("单项目:节点配置为空的配置跳过,取后面那条有内容的")
|
||||
void singleProjectSkipsEmptyNodeJson() {
|
||||
stubConfigs(config("1234", ""), config("1234", PUNCH_JSON));
|
||||
|
||||
assertEquals(PUNCH_JSON, LiveProcessConfigSupport.byProject(processConfigService, 1234L));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("批量:一次查全后按项目分别命中,未命中的项目不进结果")
|
||||
void batchByProject() {
|
||||
stubConfigs(config("1234", PUNCH_JSON), config("5678", OTHER_PUNCH_JSON));
|
||||
|
||||
Map<Long, String> live = LiveProcessConfigSupport.byProjects(processConfigService, List.of(1234L, 5678L, 4321L));
|
||||
|
||||
assertEquals(2, live.size());
|
||||
assertEquals(PUNCH_JSON, live.get(1234L));
|
||||
assertEquals(OTHER_PUNCH_JSON, live.get(5678L));
|
||||
assertTrue(live.containsKey(1234L));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("resolve:生效配置优先,取不到才用快照")
|
||||
void resolvePrefersLive() {
|
||||
assertEquals(PUNCH_JSON, LiveProcessConfigSupport.resolve(PUNCH_JSON, NO_PUNCH_JSON));
|
||||
assertEquals(NO_PUNCH_JSON, LiveProcessConfigSupport.resolve(null, NO_PUNCH_JSON));
|
||||
assertNull(LiveProcessConfigSupport.resolve(null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("resolveForPunch:生效配置没有可打卡节点时才回退快照,两边都没有节点仍用生效配置")
|
||||
void resolveForPunchFallsBackToSnapshot() {
|
||||
assertEquals(PUNCH_JSON, LiveProcessConfigSupport.resolveForPunch(PUNCH_JSON, NO_PUNCH_JSON),
|
||||
"生效配置有可打卡节点:用生效配置");
|
||||
assertEquals(PUNCH_JSON, LiveProcessConfigSupport.resolveForPunch(NO_PUNCH_JSON, PUNCH_JSON),
|
||||
"生效配置没有可打卡节点、快照有:回退快照");
|
||||
assertEquals(NO_PUNCH_JSON, LiveProcessConfigSupport.resolveForPunch(NO_PUNCH_JSON, NO_PUNCH_JSON),
|
||||
"两边都没有可打卡节点:用生效配置");
|
||||
assertEquals(NO_PUNCH_JSON, LiveProcessConfigSupport.resolveForPunch(null, NO_PUNCH_JSON),
|
||||
"项目没有生效配置:用快照");
|
||||
assertEquals(NO_PUNCH_JSON, LiveProcessConfigSupport.resolveForPunch(NO_PUNCH_JSON, null),
|
||||
"快照为空:用生效配置");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("生效配置只查一次:批量取法与单项目取法都不逐条回查项目")
|
||||
void configQueryIsInvokedOncePerCall() {
|
||||
stubConfigs(config("1234", PUNCH_JSON), config("5678", OTHER_PUNCH_JSON));
|
||||
|
||||
LiveProcessConfigSupport.byProjects(processConfigService, List.of(1234L, 5678L));
|
||||
|
||||
verify(processConfigService, times(1)).list(ArgumentMatchers.<Wrapper<ProcessConfig>>any());
|
||||
verify(processConfigService, never()).getById(any());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user