feat: 支持日计划逐根权重调度(任务4)
This commit is contained in:
@@ -21,6 +21,8 @@ public class DailyPlanStrategyDO extends TenantBaseDO {
|
|||||||
private Integer strategyMode;
|
private Integer strategyMode;
|
||||||
/** 当前游标日计划 ID */
|
/** 当前游标日计划 ID */
|
||||||
private Long cursorDailyPlanId;
|
private Long cursorDailyPlanId;
|
||||||
|
/** 当前权重轮转已执行数量 */
|
||||||
|
private Integer cursorExecutedQty;
|
||||||
/** 乐观锁版本号 */
|
/** 乐观锁版本号 */
|
||||||
private Integer version;
|
private Integer version;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,4 +34,13 @@ public interface DailyPlanStrategyMapper extends BaseMapperX<DailyPlanStrategyDO
|
|||||||
@Param("cursorDailyPlanId") Long cursorDailyPlanId,
|
@Param("cursorDailyPlanId") Long cursorDailyPlanId,
|
||||||
@Param("version") Integer version,
|
@Param("version") Integer version,
|
||||||
@Param("updater") String updater);
|
@Param("updater") String updater);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 乐观锁更新逐根权重轮转进度。
|
||||||
|
*/
|
||||||
|
int updateSingleProgressWithVersion(@Param("strategyId") Long strategyId,
|
||||||
|
@Param("cursorDailyPlanId") Long cursorDailyPlanId,
|
||||||
|
@Param("cursorExecutedQty") Integer cursorExecutedQty,
|
||||||
|
@Param("version") Integer version,
|
||||||
|
@Param("updater") String updater);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package cn.code.nl.module.lms.service.dailyplan;
|
package cn.code.nl.module.lms.service.dailyplan;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/** 供后续定时器调用的日计划调度服务。 */
|
/** 供后续定时器调用的日计划调度服务。 */
|
||||||
public interface DailyPlanScheduleService {
|
public interface DailyPlanScheduleService {
|
||||||
|
|
||||||
@@ -9,6 +11,21 @@ public interface DailyPlanScheduleService {
|
|||||||
/** 获取下一张待套管计划;无候选时返回 {@code null}。 */
|
/** 获取下一张待套管计划;无候选时返回 {@code null}。 */
|
||||||
DailyPlanScheduleCandidate getNextSleevingCandidate(String operator);
|
DailyPlanScheduleCandidate getNextSleevingCandidate(String operator);
|
||||||
|
|
||||||
|
/** 预览本轮备货候选顺序,不领取计划。 */
|
||||||
|
List<DailyPlanScheduleCandidate> previewStockingCandidates();
|
||||||
|
|
||||||
|
/** 预览本轮套管候选顺序,不领取计划。 */
|
||||||
|
List<DailyPlanScheduleCandidate> previewSleevingCandidates();
|
||||||
|
|
||||||
|
/** 指定领取一张备货计划,本次固定一根。 */
|
||||||
|
DailyPlanScheduleCandidate claimStockingCandidate(Long dailyPlanId, String operator);
|
||||||
|
|
||||||
|
/** 指定领取一张套管计划,本次固定一根。 */
|
||||||
|
DailyPlanScheduleCandidate claimSleevingCandidate(Long dailyPlanId, String operator);
|
||||||
|
|
||||||
|
/** 确认单根现场任务已经创建,成功后才推进权重轮转。 */
|
||||||
|
void confirmTaskCreated(String claimToken, String operator);
|
||||||
|
|
||||||
/** 原子增加已备货数量。 */
|
/** 原子增加已备货数量。 */
|
||||||
void increaseStockedQuantity(String claimToken, Long dailyPlanId, Integer quantity, String operator);
|
void increaseStockedQuantity(String claimToken, Long dailyPlanId, Integer quantity, String operator);
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static cn.code.nl.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.code.nl.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
@@ -62,45 +64,76 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService {
|
|||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public DailyPlanScheduleCandidate getNextStockingCandidate(String operator) {
|
public DailyPlanScheduleCandidate getNextStockingCandidate(String operator) {
|
||||||
expireClaims(operator);
|
expireClaims(operator);
|
||||||
return selectCandidate(DailyPlanStrategyTypeEnum.STOCKING, dailyPlanMapper.selectStockingCandidates(), operator);
|
List<DailyPlanScheduleCandidate> candidates = previewStockingCandidates();
|
||||||
|
return candidates.isEmpty() ? null : claimStockingCandidate(candidates.get(0).getDailyPlanId(), operator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public DailyPlanScheduleCandidate getNextSleevingCandidate(String operator) {
|
public DailyPlanScheduleCandidate getNextSleevingCandidate(String operator) {
|
||||||
expireClaims(operator);
|
expireClaims(operator);
|
||||||
return selectCandidate(DailyPlanStrategyTypeEnum.SLEEVING, dailyPlanMapper.selectSleevingCandidates(), operator);
|
List<DailyPlanScheduleCandidate> candidates = previewSleevingCandidates();
|
||||||
|
return candidates.isEmpty() ? null : claimSleevingCandidate(candidates.get(0).getDailyPlanId(), operator);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DailyPlanScheduleCandidate selectCandidate(DailyPlanStrategyTypeEnum type,
|
/** 预览本轮备货候选顺序。 */
|
||||||
List<DailyPlanDO> candidates, String operator) {
|
@Override
|
||||||
|
public List<DailyPlanScheduleCandidate> previewStockingCandidates() {
|
||||||
|
return previewCandidates(DailyPlanStrategyTypeEnum.STOCKING, dailyPlanMapper.selectStockingCandidates());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 预览本轮套管候选顺序。 */
|
||||||
|
@Override
|
||||||
|
public List<DailyPlanScheduleCandidate> previewSleevingCandidates() {
|
||||||
|
return previewCandidates(DailyPlanStrategyTypeEnum.SLEEVING, dailyPlanMapper.selectSleevingCandidates());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 按当前策略排列候选,但不产生领取。 */
|
||||||
|
private List<DailyPlanScheduleCandidate> previewCandidates(DailyPlanStrategyTypeEnum type,
|
||||||
|
List<DailyPlanDO> candidates) {
|
||||||
DailyPlanStrategyDO strategy = strategyInitializer.getOrCreate(type);
|
DailyPlanStrategyDO strategy = strategyInitializer.getOrCreate(type);
|
||||||
if (candidates.isEmpty()) {
|
if (candidates.isEmpty()) {
|
||||||
return null;
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
DailyPlanStrategyModeEnum mode = DailyPlanStrategyModeEnum.getByCode(strategy.getStrategyMode());
|
DailyPlanStrategyModeEnum mode = DailyPlanStrategyModeEnum.getByCode(strategy.getStrategyMode());
|
||||||
if (mode == null) {
|
if (mode == null) {
|
||||||
throw exception(DAILY_PLAN_STRATEGY_NOT_EXISTS);
|
throw exception(DAILY_PLAN_STRATEGY_NOT_EXISTS);
|
||||||
}
|
}
|
||||||
if (mode == DailyPlanStrategyModeEnum.SEQUENCE) {
|
List<DailyPlanDO> ordered = new ArrayList<>(candidates.size());
|
||||||
DailyPlanDO selected = candidates.get(0);
|
int start = mode == DailyPlanStrategyModeEnum.WEIGHT_ROUND_ROBIN
|
||||||
DailyPlanDO locked = lockSchedulablePlan(selected.getDailyPlanId(), type);
|
? findCursorIndex(candidates, strategy.getCursorDailyPlanId()) : 0;
|
||||||
return claimCandidate(locked, remaining(locked, type), mode.getCode(), type, operator);
|
for (int index = 0; index < candidates.size(); index++) {
|
||||||
|
ordered.add(candidates.get((start + index) % candidates.size()));
|
||||||
|
}
|
||||||
|
List<DailyPlanScheduleCandidate> result = new ArrayList<>(ordered.size());
|
||||||
|
for (DailyPlanDO plan : ordered) {
|
||||||
|
result.add(convertCandidate(plan, mode.getCode()));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int selectedIndex = findCursorIndex(candidates, strategy.getCursorDailyPlanId());
|
/** 指定领取一张备货计划。 */
|
||||||
DailyPlanDO selected = candidates.get(selectedIndex);
|
@Override
|
||||||
DailyPlanDO next = candidates.get((selectedIndex + 1) % candidates.size());
|
@Transactional(rollbackFor = Exception.class)
|
||||||
DailyPlanDO locked = lockSchedulablePlan(selected.getDailyPlanId(), type);
|
public DailyPlanScheduleCandidate claimStockingCandidate(Long dailyPlanId, String operator) {
|
||||||
int quantity = Math.min(locked.getWeight(), remaining(locked, type));
|
expireClaims(operator);
|
||||||
DailyPlanScheduleCandidate candidate = claimCandidate(locked, quantity, mode.getCode(), type, operator);
|
return claimSingleCandidate(dailyPlanId, DailyPlanStrategyTypeEnum.STOCKING, operator);
|
||||||
int affected = dailyPlanStrategyMapper.updateCursorWithVersion(strategy.getStrategyId(), next.getDailyPlanId(),
|
|
||||||
strategy.getVersion(), normalizedOperator(operator));
|
|
||||||
if (affected != 1) {
|
|
||||||
// 本次候选不应在游标推进失败时被执行,调用方可重新获取最新候选。
|
|
||||||
throw exception(DAILY_PLAN_STRATEGY_VERSION_CONFLICT);
|
|
||||||
}
|
}
|
||||||
return candidate;
|
|
||||||
|
/** 指定领取一张套管计划。 */
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public DailyPlanScheduleCandidate claimSleevingCandidate(Long dailyPlanId, String operator) {
|
||||||
|
expireClaims(operator);
|
||||||
|
return claimSingleCandidate(dailyPlanId, DailyPlanStrategyTypeEnum.SLEEVING, operator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 锁定指定计划并领取一根任务额度。 */
|
||||||
|
private DailyPlanScheduleCandidate claimSingleCandidate(Long dailyPlanId, DailyPlanStrategyTypeEnum type,
|
||||||
|
String operator) {
|
||||||
|
DailyPlanStrategyDO strategy = strategyInitializer.getOrCreate(type);
|
||||||
|
DailyPlanDO locked = lockSchedulablePlan(dailyPlanId, type);
|
||||||
|
return claimCandidate(locked, 1, strategy.getStrategyMode(), type, operator);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DailyPlanDO lockSchedulablePlan(Long dailyPlanId, DailyPlanStrategyTypeEnum type) {
|
private DailyPlanDO lockSchedulablePlan(Long dailyPlanId, DailyPlanStrategyTypeEnum type) {
|
||||||
@@ -122,11 +155,7 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int remaining(DailyPlanDO plan, DailyPlanStrategyTypeEnum type) {
|
/** 创建单根排他领取。 */
|
||||||
int completed = type == DailyPlanStrategyTypeEnum.STOCKING ? plan.getStockedQty() : plan.getSleevedQty();
|
|
||||||
return plan.getPlanQty() + plan.getAppendQty() - completed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private DailyPlanScheduleCandidate claimCandidate(DailyPlanDO plan, int quantity, Integer strategyMode,
|
private DailyPlanScheduleCandidate claimCandidate(DailyPlanDO plan, int quantity, Integer strategyMode,
|
||||||
DailyPlanStrategyTypeEnum type, String operator) {
|
DailyPlanStrategyTypeEnum type, String operator) {
|
||||||
LocalDateTime expireTime = nextClaimExpireTime();
|
LocalDateTime expireTime = nextClaimExpireTime();
|
||||||
@@ -147,6 +176,15 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService {
|
|||||||
} catch (DuplicateKeyException ex) {
|
} catch (DuplicateKeyException ex) {
|
||||||
throw exception(DAILY_PLAN_CLAIM_CONFLICT);
|
throw exception(DAILY_PLAN_CLAIM_CONFLICT);
|
||||||
}
|
}
|
||||||
|
DailyPlanScheduleCandidate candidate = convertCandidate(plan, strategyMode);
|
||||||
|
candidate.setSuggestedQuantity(quantity);
|
||||||
|
candidate.setClaimToken(claim.getClaimToken());
|
||||||
|
candidate.setClaimExpireTime(expireTime);
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将日计划转换为调度候选。 */
|
||||||
|
private DailyPlanScheduleCandidate convertCandidate(DailyPlanDO plan, Integer strategyMode) {
|
||||||
DailyPlanScheduleCandidate candidate = new DailyPlanScheduleCandidate();
|
DailyPlanScheduleCandidate candidate = new DailyPlanScheduleCandidate();
|
||||||
candidate.setDailyPlanId(plan.getDailyPlanId());
|
candidate.setDailyPlanId(plan.getDailyPlanId());
|
||||||
candidate.setPlanCode(plan.getPlanCode());
|
candidate.setPlanCode(plan.getPlanCode());
|
||||||
@@ -154,14 +192,60 @@ public class DailyPlanScheduleServiceImpl implements DailyPlanScheduleService {
|
|||||||
candidate.setMaterialCode(plan.getMaterialCode());
|
candidate.setMaterialCode(plan.getMaterialCode());
|
||||||
candidate.setMaterialName(plan.getMaterialName());
|
candidate.setMaterialName(plan.getMaterialName());
|
||||||
candidate.setMaterialSpec(plan.getMaterialSpec());
|
candidate.setMaterialSpec(plan.getMaterialSpec());
|
||||||
candidate.setSuggestedQuantity(quantity);
|
candidate.setSuggestedQuantity(1);
|
||||||
candidate.setVersion(plan.getVersion());
|
candidate.setVersion(plan.getVersion());
|
||||||
candidate.setStrategyMode(strategyMode);
|
candidate.setStrategyMode(strategyMode);
|
||||||
candidate.setClaimToken(claim.getClaimToken());
|
|
||||||
candidate.setClaimExpireTime(expireTime);
|
|
||||||
return candidate;
|
return candidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 现场单根任务创建成功后推进权重轮转。 */
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void confirmTaskCreated(String claimToken, String operator) {
|
||||||
|
DailyPlanClaimDO claim = getClaimForUpdate(claimToken);
|
||||||
|
validateActiveClaim(claim);
|
||||||
|
DailyPlanStrategyTypeEnum type = DailyPlanStrategyTypeEnum.getByCode(claim.getStrategyType());
|
||||||
|
if (type == null) {
|
||||||
|
throw exception(DAILY_PLAN_STRATEGY_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
DailyPlanStrategyDO strategy = strategyInitializer.getOrCreate(type);
|
||||||
|
if (!DailyPlanStrategyModeEnum.WEIGHT_ROUND_ROBIN.getCode().equals(strategy.getStrategyMode())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (strategy.getCursorDailyPlanId() != null
|
||||||
|
&& !strategy.getCursorDailyPlanId().equals(claim.getDailyPlanId())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DailyPlanDO current = dailyPlanMapper.selectDailyPlanById(claim.getDailyPlanId());
|
||||||
|
if (current == null) {
|
||||||
|
throw exception(DAILY_PLAN_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
int executedQty = strategy.getCursorExecutedQty() + 1;
|
||||||
|
Long nextDailyPlanId = current.getDailyPlanId();
|
||||||
|
if (executedQty >= current.getWeight()) {
|
||||||
|
executedQty = 0;
|
||||||
|
List<DailyPlanDO> candidates = type == DailyPlanStrategyTypeEnum.STOCKING
|
||||||
|
? dailyPlanMapper.selectStockingCandidates() : dailyPlanMapper.selectSleevingCandidates();
|
||||||
|
nextDailyPlanId = findNextDailyPlanId(current, candidates);
|
||||||
|
}
|
||||||
|
if (dailyPlanStrategyMapper.updateSingleProgressWithVersion(strategy.getStrategyId(), nextDailyPlanId,
|
||||||
|
executedQty, strategy.getVersion(), normalizedOperator(operator)) != 1) {
|
||||||
|
throw exception(DAILY_PLAN_STRATEGY_VERSION_CONFLICT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查找当前计划之后的下一张可执行计划。 */
|
||||||
|
private Long findNextDailyPlanId(DailyPlanDO current, List<DailyPlanDO> candidates) {
|
||||||
|
for (DailyPlanDO candidate : candidates) {
|
||||||
|
if (candidate.getSortSeq() > current.getSortSeq()
|
||||||
|
|| candidate.getSortSeq().equals(current.getSortSeq())
|
||||||
|
&& candidate.getDailyPlanId() > current.getDailyPlanId()) {
|
||||||
|
return candidate.getDailyPlanId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return candidates.isEmpty() ? current.getDailyPlanId() : candidates.get(0).getDailyPlanId();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void increaseStockedQuantity(String claimToken, Long dailyPlanId, Integer quantity, String operator) {
|
public void increaseStockedQuantity(String claimToken, Long dailyPlanId, Integer quantity, String operator) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<mapper namespace="cn.code.nl.module.lms.dal.mysql.dailyplan.DailyPlanStrategyMapper">
|
<mapper namespace="cn.code.nl.module.lms.dal.mysql.dailyplan.DailyPlanStrategyMapper">
|
||||||
|
|
||||||
<sql id="strategyColumns">
|
<sql id="strategyColumns">
|
||||||
strategy_id, strategy_type, strategy_mode, cursor_daily_plan_id, version,
|
strategy_id, strategy_type, strategy_mode, cursor_daily_plan_id, cursor_executed_qty, version,
|
||||||
creator, create_time, updater, update_time, deleted, tenant_id
|
creator, create_time, updater, update_time, deleted, tenant_id
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
UPDATE lms_daily_plan_strategy
|
UPDATE lms_daily_plan_strategy
|
||||||
SET strategy_mode = #{strategyMode},
|
SET strategy_mode = #{strategyMode},
|
||||||
cursor_daily_plan_id = NULL,
|
cursor_daily_plan_id = NULL,
|
||||||
|
cursor_executed_qty = 0,
|
||||||
updater = #{updater},
|
updater = #{updater},
|
||||||
version = version + 1
|
version = version + 1
|
||||||
WHERE strategy_id = #{strategyId}
|
WHERE strategy_id = #{strategyId}
|
||||||
@@ -37,6 +38,18 @@
|
|||||||
<update id="updateCursorWithVersion">
|
<update id="updateCursorWithVersion">
|
||||||
UPDATE lms_daily_plan_strategy
|
UPDATE lms_daily_plan_strategy
|
||||||
SET cursor_daily_plan_id = #{cursorDailyPlanId},
|
SET cursor_daily_plan_id = #{cursorDailyPlanId},
|
||||||
|
cursor_executed_qty = 0,
|
||||||
|
updater = #{updater},
|
||||||
|
version = version + 1
|
||||||
|
WHERE strategy_id = #{strategyId}
|
||||||
|
AND version = #{version}
|
||||||
|
AND deleted = b'0'
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="updateSingleProgressWithVersion">
|
||||||
|
UPDATE lms_daily_plan_strategy
|
||||||
|
SET cursor_daily_plan_id = #{cursorDailyPlanId},
|
||||||
|
cursor_executed_qty = #{cursorExecutedQty},
|
||||||
updater = #{updater},
|
updater = #{updater},
|
||||||
version = version + 1
|
version = version + 1
|
||||||
WHERE strategy_id = #{strategyId}
|
WHERE strategy_id = #{strategyId}
|
||||||
|
|||||||
Reference in New Issue
Block a user