parent
9db0a7f082
commit
e96052c4ba
22 changed files with 618 additions and 61 deletions
@ -0,0 +1,65 @@ |
||||
package org.springblade.desk.produce.pojo.enums; |
||||
|
||||
import lombok.AllArgsConstructor; |
||||
import lombok.Getter; |
||||
import org.springblade.core.tool.utils.ObjectUtil; |
||||
import org.springblade.core.tool.utils.StringPool; |
||||
|
||||
import java.util.Arrays; |
||||
|
||||
/** |
||||
* 车间订单枚举 |
||||
* |
||||
* @author litao |
||||
* @date 2026-2-4 |
||||
*/ |
||||
@Getter |
||||
@AllArgsConstructor |
||||
public enum WorkOrderEnum { |
||||
EMPTY(StringPool.EMPTY, -1), |
||||
|
||||
/** |
||||
* 状态枚举 |
||||
*/ |
||||
RUN_STATUS_NORMAL("未下达", 1), |
||||
RUN_STATUS_ISSUED("已下达", 2), |
||||
RUN_STATUS_RECEIVE("加工中", 3), |
||||
RUN_STATUS_CHECK("检验中", 4), |
||||
RUN_STATUS_CRAFT_CHANGE("工艺变更", 5), |
||||
RUN_STATUS_HEAR("审理中", 13), |
||||
RUN_STATUS_COMPLETED("已完工", 15), |
||||
RUN_STATUS_HANDOVER("已交接", 17), |
||||
RUN_STATUS_REWORK("返工", 19), |
||||
RUN_STATUS_SCRAP("报废", 20), |
||||
RUN_STATUS_VOIDED("已关闭", 21); |
||||
final String name; |
||||
final int category; |
||||
|
||||
/** |
||||
* 匹配枚举值 |
||||
* |
||||
* @param name 名称 |
||||
* @return BladeUserEnum |
||||
*/ |
||||
public static WorkOrderEnum of(String name) { |
||||
return Arrays.stream(WorkOrderEnum.values()) |
||||
.filter(userEnum -> userEnum.getName().equalsIgnoreCase(name != null ? name : "web")) |
||||
.findFirst() |
||||
// 在没有找到匹配项时返回默认值
|
||||
.orElse(WorkOrderEnum.EMPTY); |
||||
} |
||||
|
||||
/** |
||||
* 根据值获取名称 |
||||
* |
||||
* @param category |
||||
* @return |
||||
*/ |
||||
public static String getName(int category) { |
||||
WorkOrderEnum item = Arrays.stream(WorkOrderEnum.values()) |
||||
.filter(enumItem -> enumItem.getCategory() == category) |
||||
.findFirst() |
||||
.orElse(null); |
||||
return ObjectUtil.isEmpty(item) ? StringPool.EMPTY : item.getName(); |
||||
} |
||||
} |
||||
@ -0,0 +1,101 @@ |
||||
package org.springblade.desk.produce.pojo.vo; |
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema; |
||||
import lombok.Data; |
||||
|
||||
import java.util.List; |
||||
|
||||
/** |
||||
* 订单缓存 视图实体类 |
||||
* |
||||
* @author litao |
||||
* @since 2026-2-3 |
||||
*/ |
||||
@Data |
||||
public class CacheWorkOrderVO { |
||||
|
||||
@Schema(description = "车间订单ID") |
||||
private Long woId; |
||||
|
||||
@Schema(description = "生产订单ID") |
||||
private Long yoId; |
||||
|
||||
@Schema(description = "主加工单位:班组") |
||||
private String mainTsName; |
||||
|
||||
@Schema(description = "主加工单位:外协") |
||||
private String mainOcName; |
||||
|
||||
@Schema(description = "班组id") |
||||
private String makeTeam; |
||||
|
||||
@Schema(description = "外协id") |
||||
private String ppsOcId; |
||||
|
||||
@Schema(description = "班组") |
||||
private String tsName; |
||||
|
||||
@Schema(description = "外协") |
||||
private String ocName; |
||||
|
||||
@Schema(description = "工序") |
||||
private String ppsName; |
||||
|
||||
@Schema(description = "生产订单号") |
||||
private String yoCode; |
||||
|
||||
@Schema(description = "计划单号") |
||||
private String ypCode; |
||||
|
||||
@Schema(description = "零件号") |
||||
private String partCode; |
||||
|
||||
@Schema(description = "生产标识") |
||||
private String productIdent; |
||||
|
||||
@Schema(description = "产品名称") |
||||
private String partName; |
||||
|
||||
@Schema(description = "镀种信息") |
||||
private String plate; |
||||
|
||||
@Schema(description = "产品型号") |
||||
private String productType; |
||||
|
||||
@Schema(description = "需求部门") |
||||
private String useDept; |
||||
|
||||
@Schema(description = "面积(dm²)") |
||||
private String totalArea; |
||||
|
||||
@Schema(description = "单批次面积") |
||||
private String area; |
||||
|
||||
@Schema(description = "调度员") |
||||
private String dispatcherName; |
||||
|
||||
@Schema(description = "未入库数量") |
||||
private Integer notInQty; |
||||
|
||||
@Schema(description = "下序id") |
||||
private Integer ppsIdNext; |
||||
|
||||
@Schema(description = "下序名称") |
||||
private Integer ppsNameNext; |
||||
|
||||
@Schema(description = "下一班组id") |
||||
private Integer makeTeamNext; |
||||
|
||||
@Schema(description = "下一班组") |
||||
private Integer makeTeamNextName; |
||||
|
||||
@Schema(description = "接收人员id") |
||||
private Integer receiveUser; |
||||
|
||||
@Schema(description = "接收人员") |
||||
private Integer receiveUserNamr; |
||||
|
||||
@Schema(description = "工序集合") |
||||
private List<CacheWorkPlanVO> processList; |
||||
|
||||
} |
||||
@ -0,0 +1,103 @@ |
||||
package org.springblade.desk.produce.pojo.vo; |
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema; |
||||
import lombok.Data; |
||||
|
||||
import java.math.BigDecimal; |
||||
import java.time.LocalDateTime; |
||||
import java.util.Date; |
||||
|
||||
/** |
||||
* 工序缓存 视图实体类 |
||||
* |
||||
* @author litao |
||||
* @since 2026-2-3 |
||||
*/ |
||||
@Data |
||||
public class CacheWorkPlanVO { |
||||
|
||||
@Schema(description = "车间订单") |
||||
private Long woId; |
||||
|
||||
@Schema(description = "工序") |
||||
private Long ppsId; |
||||
|
||||
@Schema(description = "工序号") |
||||
private String orders; |
||||
|
||||
@Schema(description = "工序描述") |
||||
private String makeMemo; |
||||
|
||||
@Schema(description = "工时定额") |
||||
private BigDecimal hourQuota; |
||||
|
||||
@Schema(description = "计划开始") |
||||
private LocalDateTime startTime; |
||||
|
||||
@Schema(description = "计划结束") |
||||
private LocalDateTime endTime; |
||||
|
||||
@Schema(description = "加工班组") |
||||
private Long makeTeam; |
||||
|
||||
@Schema(description = "是否外协") |
||||
private String oem; |
||||
|
||||
@Schema(description = "外协商") |
||||
private Long ocId; |
||||
|
||||
@Schema(description = "上一道") |
||||
private Long frontWpId; |
||||
|
||||
@Schema(description = "下一道") |
||||
private Long nextWpId; |
||||
|
||||
@Schema(description = "试验数量") |
||||
private Double testQty; |
||||
|
||||
@Schema(description = "合格数量") |
||||
private Double qualifiedQty; |
||||
|
||||
@Schema(description = "报废数量") |
||||
private Double scrapQty; |
||||
|
||||
@Schema(description = "不合格数量") |
||||
private Integer unqualifiedQty; |
||||
|
||||
@Schema(description = "实际开始") |
||||
private Date factStartTime; |
||||
|
||||
@Schema(description = "实际结束") |
||||
private Date factEndTime; |
||||
|
||||
@Schema(description = "报工数量") |
||||
private Double workQty; |
||||
|
||||
@Schema(description = "接收人") |
||||
private Long receiveMan; |
||||
|
||||
@Schema(description = "加工工时") |
||||
private BigDecimal hours; |
||||
|
||||
@Schema(description = "工艺能力") |
||||
private Long caId; |
||||
|
||||
@Schema(description = "附属班组") |
||||
private Long subsidiaryTeam; |
||||
|
||||
@Schema(description = "消耗数量") |
||||
private Double lossQty; |
||||
|
||||
@Schema(description = "金额") |
||||
private BigDecimal wpMoney; |
||||
|
||||
@Schema(description = "工艺文件编号/版本号") |
||||
private String papers; |
||||
|
||||
@Schema(description = "引用文件/版本号") |
||||
private String referenceFile; |
||||
|
||||
@Schema(description = "作业中心") |
||||
private Long workCenterId; |
||||
|
||||
} |
||||
@ -0,0 +1,49 @@ |
||||
package org.springblade.desk.produce.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.tags.Tag; |
||||
import jakarta.annotation.Resource; |
||||
import lombok.RequiredArgsConstructor; |
||||
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.tool.api.R; |
||||
import org.springblade.desk.dashboard.service.IPrReworkProcessService; |
||||
import org.springblade.erpdata.feign.IErpDataProduceClient; |
||||
import org.springblade.erpdata.pojo.dto.ReworkProcessDTO; |
||||
import org.springblade.erpdata.pojo.vo.ReworkProcessVO; |
||||
import org.springframework.web.bind.annotation.*; |
||||
|
||||
/** |
||||
* 返工订单 |
||||
* |
||||
* @author litao |
||||
*/ |
||||
@RestController |
||||
@RequiredArgsConstructor |
||||
@RequestMapping("/ReworkProcess") |
||||
@Tag(name = "返工订单", description = "接口") |
||||
public class ReworkProcessController extends BladeController { |
||||
|
||||
private final IErpDataProduceClient erpDataProduceClient; |
||||
|
||||
private final IPrReworkProcessService reworkProcessService; |
||||
|
||||
@GetMapping("/loadReworkOrder") |
||||
@ApiOperationSupport(order = 1) |
||||
@Operation(summary = "erp查询返工订单", description = "传入ReworkProcessDTO") |
||||
public R<IPage<ReworkProcessVO>> page(ReworkProcessDTO prReworkProcess, Query query) { |
||||
prReworkProcess.setQuery(query); |
||||
return erpDataProduceClient.loadReworkOrder(prReworkProcess); |
||||
} |
||||
|
||||
// @GetMapping("/treeProcess")
|
||||
// @ApiOperationSupport(order = 2)
|
||||
// @Operation(summary = "查询返工工序树", description = "")
|
||||
// public R taskComplete(@RequestBody ReworkProcessDTO prReworkProcess) {
|
||||
// return R.data(reworkProcessService.treeProcess(prReworkProcess));
|
||||
// }
|
||||
|
||||
} |
||||
@ -0,0 +1,28 @@ |
||||
package org.springblade.desk.produce.listener; |
||||
|
||||
import lombok.RequiredArgsConstructor; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springblade.desk.produce.service.impl.OrderCacheService; |
||||
import org.springframework.boot.context.event.ApplicationReadyEvent; |
||||
import org.springframework.context.event.EventListener; |
||||
import org.springframework.scheduling.annotation.Async; |
||||
import org.springframework.stereotype.Component; |
||||
|
||||
/** |
||||
* @author litao |
||||
*/ |
||||
@Component |
||||
@RequiredArgsConstructor |
||||
@Slf4j |
||||
public class CacheInitListener { |
||||
|
||||
private final OrderCacheService orderCacheService; |
||||
|
||||
@Async |
||||
@EventListener(ApplicationReadyEvent.class) |
||||
public void initCacheAfterReady() { |
||||
log.info("===== SpringBoot3 ApplicationReadyEvent 触发:开始初始化缓存 ====="); |
||||
orderCacheService.initRunningOrderCache(); |
||||
log.info("===== SpringBoot3 ApplicationReadyEvent 触发:缓存初始化完成 ====="); |
||||
} |
||||
} |
||||
@ -0,0 +1,173 @@ |
||||
package org.springblade.desk.produce.service.impl; |
||||
|
||||
import jakarta.annotation.PostConstruct; |
||||
import lombok.RequiredArgsConstructor; |
||||
import lombok.extern.slf4j.Slf4j; |
||||
import org.springblade.desk.produce.pojo.entity.WorkOrder; |
||||
import org.springblade.desk.produce.pojo.vo.CacheWorkOrderVO; |
||||
import org.springblade.desk.produce.pojo.vo.CacheWorkPlanVO; |
||||
import org.springblade.desk.produce.service.IWorkOrderService; |
||||
import org.springframework.beans.BeanUtils; |
||||
import org.springframework.cache.Cache; |
||||
import org.springframework.cache.CacheManager; |
||||
import org.springframework.cache.annotation.CacheEvict; |
||||
import org.springframework.cache.annotation.CachePut; |
||||
import org.springframework.cache.annotation.Cacheable; |
||||
import org.springframework.stereotype.Service; |
||||
import org.springframework.util.CollectionUtils; |
||||
|
||||
import java.util.*; |
||||
import java.util.concurrent.ConcurrentHashMap; |
||||
import java.util.concurrent.locks.ReentrantLock; |
||||
import java.util.stream.Collectors; |
||||
|
||||
/** |
||||
* 订单本地缓存核心服务:封装缓存初始化、查询、更新、删除,统一管理缓存操作 |
||||
* @author litao |
||||
*/ |
||||
@Service |
||||
@RequiredArgsConstructor |
||||
@Slf4j |
||||
public class OrderCacheService { |
||||
|
||||
private final IWorkOrderService workOrderService; |
||||
|
||||
// 注入Spring缓存管理器(核心新增:用于手动操作缓存)
|
||||
private final CacheManager cacheManager; |
||||
|
||||
// 细粒度并发锁:key=订单ID,value=对应锁对象 → 仅对同一订单加锁,不影响其他订单(高性能)
|
||||
private final Map<Long, ReentrantLock> orderLockMap = new ConcurrentHashMap<>(); |
||||
|
||||
// 声明订单缓存实例(初始化后赋值,避免多次获取)
|
||||
private Cache runningOrderCache; |
||||
|
||||
// 初始化缓存实例(项目启动后执行,仅一次)
|
||||
@PostConstruct |
||||
public void initCacheInstance() { |
||||
// 获取配置文件中定义的runningOrderCache缓存实例
|
||||
this.runningOrderCache = cacheManager.getCache("runningOrderCache"); |
||||
} |
||||
|
||||
// ====================== 核心缓存注解:贴合Caffeine配置 ======================
|
||||
// 缓存名称:runningOrderCache(与application.yml一致)
|
||||
// 缓存Key:#orderId(订单ID,精准映射)
|
||||
|
||||
/** |
||||
* 从缓存查询订单(优先走缓存,无则查库并加载到缓存) |
||||
* @Cacheable:缓存不存在时执行方法体,存在则直接返回缓存 |
||||
*/ |
||||
@Cacheable(cacheNames = "runningOrderCache", key = "#orderId", unless = "#result == null") |
||||
public CacheWorkOrderVO getOrderFromCache(Long orderId) { |
||||
// 缓存不存在时,查库加载最新数据并返回(自动存入缓存)
|
||||
return loadOrderFromDB(orderId); |
||||
} |
||||
|
||||
/** |
||||
* 更新缓存:订单/工序修改后,同步更新缓存(数据库已先更新) |
||||
* @CachePut:无论缓存是否存在,都会执行方法体并将结果更新到缓存 → 保证缓存与数据库一致 |
||||
*/ |
||||
@CachePut(cacheNames = "runningOrderCache", key = "#orderId", unless = "#result == null") |
||||
public CacheWorkOrderVO updateOrderCache(Long orderId) { |
||||
// 先加锁,防止多线程同时更新同一订单缓存
|
||||
ReentrantLock lock = getOrderLock(orderId); |
||||
lock.lock(); |
||||
try { |
||||
// 查库获取最新数据,更新到缓存
|
||||
return loadOrderFromDB(orderId); |
||||
} finally { |
||||
// 必须释放锁,避免死锁
|
||||
lock.unlock(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 删除缓存:订单变为非运行中状态(完成/取消)时,从缓存移除 |
||||
* @CacheEvict:删除指定Key的缓存 |
||||
*/ |
||||
@CacheEvict(cacheNames = "runningOrderCache", key = "#orderId") |
||||
public void removeOrderCache(Long orderId) { |
||||
// 方法体可留空,注解自动完成删除
|
||||
log.info("订单[{}]已变为非运行中状态,从本地缓存移除", orderId); |
||||
} |
||||
|
||||
/** |
||||
* 批量删除缓存(可选:如批量更新订单状态时使用) |
||||
*/ |
||||
@CacheEvict(cacheNames = "runningOrderCache", allEntries = false) |
||||
public void removeBatchOrderCache(Long[] orderIds) { |
||||
// 注解中#orderIds为数组,自动遍历删除对应Key的缓存
|
||||
} |
||||
|
||||
// ====================== 启动初始化缓存:加载所有运行中订单+工序 ======================
|
||||
public void initRunningOrderCache() { |
||||
log.info("开始初始化运行中订单本地缓存..."); |
||||
// 1. 查库:获取所有运行中订单
|
||||
List<WorkOrder> runningOrderList = workOrderService.selectAllRunningOrder(); |
||||
if (CollectionUtils.isEmpty(runningOrderList)) { |
||||
log.error("无运行中订单,缓存初始化完成"); |
||||
return; |
||||
} |
||||
// 2. 提取订单ID列表,批量查询所有关联工序(避免N+1)
|
||||
List<Long> orderIds = runningOrderList.stream().map(WorkOrder::getId).collect(Collectors.toList()); |
||||
List<CacheWorkPlanVO> allProcessList = workOrderService.selectProcessByOrderIds(orderIds); |
||||
// 工序按订单ID分组:key=订单ID,value=对应工序列表
|
||||
Map<Long, List<CacheWorkPlanVO>> processGroupMap = allProcessList.stream().collect(Collectors.groupingBy(CacheWorkPlanVO::getWoId)); |
||||
|
||||
// 3. 组装缓存VO,批量加载到本地缓存
|
||||
for (WorkOrder order : runningOrderList) { |
||||
Long orderId = order.getId(); |
||||
// 加锁初始化单个订单缓存,避免与启动后的业务更新并发
|
||||
ReentrantLock lock = getOrderLock(orderId); |
||||
lock.lock(); |
||||
try { |
||||
CacheWorkOrderVO cacheVO = assembleOrderCacheVO(order, processGroupMap.getOrDefault(orderId, new ArrayList<>())); |
||||
// 调用updateOrderCache,利用@CachePut将数据存入缓存
|
||||
// updateOrderCache(orderId);
|
||||
// 手动存入缓存:直接用缓存实例put,无二次查库(核心优化)
|
||||
runningOrderCache.put(orderId, cacheVO); |
||||
} finally { |
||||
lock.unlock(); |
||||
} |
||||
} |
||||
log.info("运行中订单本地缓存初始化完成,共加载[{}]个订单", runningOrderList.size()); |
||||
} |
||||
|
||||
// ====================== 私有工具方法:抽离通用逻辑 ======================
|
||||
/** |
||||
* 根据订单ID查库,加载订单+关联工序的最新数据 |
||||
*/ |
||||
private CacheWorkOrderVO loadOrderFromDB(Long orderId) { |
||||
// 1. 查订单主信息
|
||||
WorkOrder order = workOrderService.getById(orderId); |
||||
if (order == null) { |
||||
return null; |
||||
} |
||||
// 非运行中订单,直接返回null(不会存入缓存)
|
||||
Integer orderStatus = order.getRunStatus(); |
||||
if (orderStatus == null || !WorkOrder.RUNNING_ORDER_STATUS.contains(orderStatus)) { |
||||
return null; |
||||
} |
||||
// 2. 查关联工序
|
||||
List<CacheWorkPlanVO> processList = workOrderService.selectProcessByOrderIds(Collections.singletonList(orderId)); |
||||
// 3. 组装VO
|
||||
return assembleOrderCacheVO(order, processList); |
||||
} |
||||
|
||||
/** |
||||
* 组装订单缓存VO |
||||
*/ |
||||
private CacheWorkOrderVO assembleOrderCacheVO(WorkOrder order, List<CacheWorkPlanVO> processList) { |
||||
CacheWorkOrderVO cacheVO = new CacheWorkOrderVO(); |
||||
BeanUtils.copyProperties(order, cacheVO); |
||||
cacheVO.setProcessList(processList); |
||||
return cacheVO; |
||||
} |
||||
|
||||
/** |
||||
* 获取订单对应的锁对象(ConcurrentHashMap保证线程安全) |
||||
*/ |
||||
private ReentrantLock getOrderLock(Long orderId) { |
||||
// 不存在则创建新锁,存在则返回已有锁
|
||||
return orderLockMap.computeIfAbsent(orderId, k -> new ReentrantLock()); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue