mirror of
http://192.168.1.205:9980/cf_devdept2/cf_imes_server.git
synced 2026-08-12 21:02:08 +08:00
排单锁优化
This commit is contained in:
+4
-2
@@ -192,7 +192,8 @@ public class PlanController {
|
|||||||
@PostMapping("/lock")
|
@PostMapping("/lock")
|
||||||
@Operation(summary = "获取排单锁")
|
@Operation(summary = "获取排单锁")
|
||||||
@PreAuthorize("@ss.hasAnyPermissions('placeorder:optimize','production:manager-list:calculate','manage:order-plan:process-scheme','producePlan:order-plan:process-scheme','producePlan:optimize')")
|
@PreAuthorize("@ss.hasAnyPermissions('placeorder:optimize','production:manager-list:calculate','manage:order-plan:process-scheme','producePlan:order-plan:process-scheme','producePlan:optimize')")
|
||||||
public CommonResult<Boolean> getPlanLock(@RequestBody OrderPlanLockReqVO orderPlanLockReqVO) {
|
public CommonResult<Boolean> getPlanLock(
|
||||||
|
@Valid @RequestBody OrderPlanLockReqVO orderPlanLockReqVO) {
|
||||||
planService.getPlanLock(orderPlanLockReqVO);
|
planService.getPlanLock(orderPlanLockReqVO);
|
||||||
return success(true);
|
return success(true);
|
||||||
}
|
}
|
||||||
@@ -200,7 +201,8 @@ public class PlanController {
|
|||||||
@PostMapping("/release/lock")
|
@PostMapping("/release/lock")
|
||||||
@Operation(summary = "释放排单锁")
|
@Operation(summary = "释放排单锁")
|
||||||
@PreAuthorize("@ss.hasAnyPermissions('placeorder:optimize','production:manager-list:calculate','manage:order-plan:process-scheme','producePlan:order-plan:process-scheme','producePlan:optimize')")
|
@PreAuthorize("@ss.hasAnyPermissions('placeorder:optimize','production:manager-list:calculate','manage:order-plan:process-scheme','producePlan:order-plan:process-scheme','producePlan:optimize')")
|
||||||
public CommonResult<Boolean> releasePlanLock(@RequestBody OrderPlanLockReqVO orderPlanLockReqVO) {
|
public CommonResult<Boolean> releasePlanLock(
|
||||||
|
@Valid @RequestBody OrderPlanLockReqVO orderPlanLockReqVO) {
|
||||||
planService.releasePlanLock(orderPlanLockReqVO);
|
planService.releasePlanLock(orderPlanLockReqVO);
|
||||||
return success(true);
|
return success(true);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,7 @@
|
|||||||
package com.cf.imes.module.executor.controller.admin.plan.vo;
|
package com.cf.imes.module.executor.controller.admin.plan.vo;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@@ -18,6 +19,6 @@ public class OrderPlanLockReqVO {
|
|||||||
private Long planId;
|
private Long planId;
|
||||||
|
|
||||||
@Schema(description = "排单优化本地值")
|
@Schema(description = "排单优化本地值")
|
||||||
@NotNull(message = "排单优化本地值不能为空")
|
@NotBlank(message = "排单优化本地值不能为空")
|
||||||
private String val;
|
private String val;
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-4
@@ -66,6 +66,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.aop.framework.AopContext;
|
import org.springframework.aop.framework.AopContext;
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
@@ -81,6 +82,7 @@ import java.util.stream.Collectors;
|
|||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
|
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
|
||||||
import static com.cf.imes.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
|
import static com.cf.imes.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
|
||||||
import static com.cf.imes.framework.common.util.json.JsonUtils.parseArray;
|
import static com.cf.imes.framework.common.util.json.JsonUtils.parseArray;
|
||||||
import static com.cf.imes.framework.redis.constants.RedisKeyConstants.ORDER_PLAN_OPTIMIZE_KEY;
|
import static com.cf.imes.framework.redis.constants.RedisKeyConstants.ORDER_PLAN_OPTIMIZE_KEY;
|
||||||
@@ -97,6 +99,20 @@ import static com.cf.imes.module.executor.enums.ErrorCodeConstants.*;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class PlanServiceImpl implements PlanService {
|
public class PlanServiceImpl implements PlanService {
|
||||||
|
|
||||||
|
/** 原子校验锁归属并删除,避免 GET 与 DELETE 之间锁过期后误删其他请求的新锁。 */
|
||||||
|
private static final DefaultRedisScript<Long> RELEASE_PLAN_LOCK_SCRIPT;
|
||||||
|
|
||||||
|
static {
|
||||||
|
RELEASE_PLAN_LOCK_SCRIPT = new DefaultRedisScript<>();
|
||||||
|
RELEASE_PLAN_LOCK_SCRIPT.setScriptText("""
|
||||||
|
if redis.call('get', KEYS[1]) == ARGV[1] then
|
||||||
|
return redis.call('del', KEYS[1])
|
||||||
|
end
|
||||||
|
return 0
|
||||||
|
""");
|
||||||
|
RELEASE_PLAN_LOCK_SCRIPT.setResultType(Long.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private PlanMapper planMapper;
|
private PlanMapper planMapper;
|
||||||
|
|
||||||
@@ -2189,6 +2205,7 @@ public class PlanServiceImpl implements PlanService {
|
|||||||
public void getPlanLock(OrderPlanLockReqVO orderPlanLockReqVO) {
|
public void getPlanLock(OrderPlanLockReqVO orderPlanLockReqVO) {
|
||||||
Long planId = orderPlanLockReqVO.getPlanId();
|
Long planId = orderPlanLockReqVO.getPlanId();
|
||||||
String lockValInput = orderPlanLockReqVO.getVal();
|
String lockValInput = orderPlanLockReqVO.getVal();
|
||||||
|
validatePlanLockValue(lockValInput);
|
||||||
|
|
||||||
PlanDO planDO = planMapper.selectById(planId);
|
PlanDO planDO = planMapper.selectById(planId);
|
||||||
|
|
||||||
@@ -2200,7 +2217,7 @@ public class PlanServiceImpl implements PlanService {
|
|||||||
String lockVal = stringRedisTemplate.opsForValue().get(planLockKey);
|
String lockVal = stringRedisTemplate.opsForValue().get(planLockKey);
|
||||||
|
|
||||||
// 没有锁,尝试获取
|
// 没有锁,尝试获取
|
||||||
if (StrUtil.isBlank(lockVal)) {
|
if (lockVal == null) {
|
||||||
|
|
||||||
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(
|
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(
|
||||||
planLockKey,
|
planLockKey,
|
||||||
@@ -2226,13 +2243,20 @@ public class PlanServiceImpl implements PlanService {
|
|||||||
public void releasePlanLock(OrderPlanLockReqVO orderPlanLockReqVO) {
|
public void releasePlanLock(OrderPlanLockReqVO orderPlanLockReqVO) {
|
||||||
Long planId = orderPlanLockReqVO.getPlanId();
|
Long planId = orderPlanLockReqVO.getPlanId();
|
||||||
String lockValInput = orderPlanLockReqVO.getVal();
|
String lockValInput = orderPlanLockReqVO.getVal();
|
||||||
|
validatePlanLockValue(lockValInput);
|
||||||
|
|
||||||
String lockKey = String.format(ORDER_PLAN_OPTIMIZE_KEY, planId);
|
String lockKey = String.format(ORDER_PLAN_OPTIMIZE_KEY, planId);
|
||||||
|
|
||||||
String lockVal = stringRedisTemplate.opsForValue().get(lockKey);
|
Long released = stringRedisTemplate.execute(
|
||||||
|
RELEASE_PLAN_LOCK_SCRIPT, Collections.singletonList(lockKey), lockValInput);
|
||||||
|
if (!Long.valueOf(1L).equals(released)) {
|
||||||
|
log.warn("释放排单锁失败,锁不存在或锁值不匹配,planId={}", planId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Objects.equals(lockVal, lockValInput)) {
|
private void validatePlanLockValue(String lockValue) {
|
||||||
stringRedisTemplate.delete(lockKey);
|
if (StrUtil.isBlank(lockValue)) {
|
||||||
|
throw invalidParamException("排单优化本地值不能为空");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
package com.cf.imes.module.executor.service.plan;
|
||||||
|
|
||||||
|
import com.cf.imes.framework.common.exception.ServiceException;
|
||||||
|
import com.cf.imes.module.executor.controller.admin.plan.vo.OrderPlanLockReqVO;
|
||||||
|
import com.cf.imes.module.executor.dal.dataobject.plan.PlanDO;
|
||||||
|
import com.cf.imes.module.executor.dal.mysql.plan.PlanMapper;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.ValueOperations;
|
||||||
|
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.ORDER_PLAN_LOCKED;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class PlanServiceImplLockTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private PlanMapper planMapper;
|
||||||
|
@Mock
|
||||||
|
private StringRedisTemplate stringRedisTemplate;
|
||||||
|
@Mock
|
||||||
|
private ValueOperations<String, String> valueOperations;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private PlanServiceImpl planService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRejectBlankLockValueInService() {
|
||||||
|
OrderPlanLockReqVO request = lockRequest(1L, " ");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> planService.getPlanLock(request))
|
||||||
|
.isInstanceOf(ServiceException.class)
|
||||||
|
.hasMessage("排单优化本地值不能为空");
|
||||||
|
verifyNoInteractions(planMapper, stringRedisTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotTreatExistingEmptyValueAsUnlocked() {
|
||||||
|
Long planId = 1L;
|
||||||
|
PlanDO plan = new PlanDO();
|
||||||
|
plan.setId(planId);
|
||||||
|
when(planMapper.selectById(planId)).thenReturn(plan);
|
||||||
|
when(stringRedisTemplate.opsForValue()).thenReturn(valueOperations);
|
||||||
|
when(valueOperations.get("order_plan_optimize:" + planId)).thenReturn("");
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> planService.getPlanLock(lockRequest(planId, "owner-1")))
|
||||||
|
.isInstanceOfSatisfying(ServiceException.class,
|
||||||
|
ex -> assertThat(ex.getCode()).isEqualTo(ORDER_PLAN_LOCKED.getCode()));
|
||||||
|
verify(valueOperations, never()).setIfAbsent(anyString(), anyString(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReleaseLockWithAtomicLuaScript() {
|
||||||
|
Long planId = 1L;
|
||||||
|
String lockKey = "order_plan_optimize:" + planId;
|
||||||
|
|
||||||
|
planService.releasePlanLock(lockRequest(planId, "owner-1"));
|
||||||
|
|
||||||
|
verify(stringRedisTemplate).execute(
|
||||||
|
any(DefaultRedisScript.class),
|
||||||
|
eq(Collections.singletonList(lockKey)),
|
||||||
|
eq("owner-1"));
|
||||||
|
verify(stringRedisTemplate, never()).opsForValue();
|
||||||
|
verify(stringRedisTemplate, never()).delete(anyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrderPlanLockReqVO lockRequest(Long planId, String value) {
|
||||||
|
OrderPlanLockReqVO request = new OrderPlanLockReqVO();
|
||||||
|
request.setPlanId(planId);
|
||||||
|
request.setVal(value);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user