设备日历接口修改

parent 1dd2520c
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -3,7 +3,9 @@ package com.aps.common.util;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.databind.DeserializationFeature;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
......@@ -27,10 +29,38 @@ public class JsonFileReader {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 添加更多配置确保字段正确映射
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
// 3. 反序列化JSON到对象列表
return objectMapper.readValue(jsonContent,
objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
}
}
public static <T> List<T> readListFromFile(String filePath, Class<T> clazz) throws IOException {
// 1. 检查文件是否存在
File file = new File(filePath);
if (!file.exists()) {
throw new IOException("File not found: " + filePath);
}
// 2. 读取文件内容
Scanner scanner = new Scanner(file, StandardCharsets.UTF_8.name()).useDelimiter("\\A");
String jsonContent = scanner.hasNext() ? scanner.next() : "";
// 3. 配置ObjectMapper支持Java 8时间API
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 添加更多配置确保字段正确映射
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
// 4. 反序列化JSON到对象列表
return objectMapper.readValue(jsonContent,
objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
}
}
\ No newline at end of file
package com.aps.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*") // 修改这里
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
// 允许所有域名访问,生产环境建议指定具体域名
config.addAllowedOriginPattern("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setAllowCredentials(true);
config.setMaxAge(3600L);
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
\ No newline at end of file
package com.aps.controller.gantt;
import com.aps.common.util.R;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
@Slf4j
@RestController
@RequestMapping("/api/files")
@Tag(name = "文件上传管理", description = "文件上传管理")
public class FileUploadController {
// 从配置文件中获取文件存储目录
@Value("${file.upload.dir:uploads}")
private String uploadDir;
private String absoluteUploadDir;
@PostConstruct
public void init() {
// 获取上传目录的绝对路径
absoluteUploadDir = Paths.get(uploadDir).toAbsolutePath().toString();
// 确保上传目录存在
File directory = new File(absoluteUploadDir);
if (!directory.exists()) {
boolean created = directory.mkdirs();
if (created) {
log.info("创建上传目录: {}", absoluteUploadDir);
} else {
log.error("创建上传目录失败: {}", absoluteUploadDir);
}
} else {
log.info("使用已存在的上传目录: {}", absoluteUploadDir);
}
}
@PostMapping(value = "/upload/machines", consumes = "multipart/form-data")
@Operation(summary = "上传machines.json文件", description = "上传machines.json文件")
@RequestBody(description = "选择要上传的 machines.json 文件", required = true,
content = @Content(mediaType = "multipart/form-data"))
public R<String> uploadMachines(
@Parameter(description = "选择要上传的 machines.json 文件")
@RequestParam("file") MultipartFile file) {
return uploadFile(file, "machines.json");
}
@PostMapping(value = "/upload/orders", consumes = "multipart/form-data")
@Operation(summary = "上传orders.json文件", description = "上传orders.json文件")
@RequestBody(description = "选择要上传的 orders.json 文件", required = true,
content = @Content(mediaType = "multipart/form-data"))
public R<String> uploadOrders(
@Parameter(description = "选择要上传的 orders.json 文件")
@RequestParam("file") MultipartFile file) {
return uploadFile(file, "orders.json");
}
@PostMapping(value = "/upload/products", consumes = "multipart/form-data")
@Operation(summary = "上传products.json文件", description = "上传products.json文件")
@RequestBody(description = "选择要上传的 products.json 文件", required = true,
content = @Content(mediaType = "multipart/form-data"))
public R<String> uploadProducts(
@Parameter(description = "选择要上传的 products.json 文件")
@RequestParam("file") MultipartFile file) {
return uploadFile(file, "products.json");
}
private R<String> uploadFile(MultipartFile file, String expectedFileName) {
if (file.isEmpty()) {
return R.failed("上传文件不能为空");
}
// 检查文件名
if (!file.getOriginalFilename().equals(expectedFileName)) {
return R.failed("请上传正确的文件: " + expectedFileName);
}
try {
// 保存文件到指定目录
String filePath = Paths.get(absoluteUploadDir, expectedFileName).toString();
file.transferTo(new File(filePath));
log.info("文件上传成功: {}", filePath);
return R.ok("文件上传成功: " + expectedFileName);
} catch (IOException e) {
log.error("文件上传失败", e);
return R.failed("文件上传失败: " + e.getMessage());
}
}
/**
* 获取上传文件的路径
* @param fileName 文件名
* @return 文件路径
*/
public String getUploadedFilePath(String fileName) {
return Paths.get(absoluteUploadDir, fileName).toString();
}
/**
* 检查文件是否存在
* @param fileName 文件名
* @return 是否存在
*/
public boolean isFileUploaded(String fileName) {
String filePath = Paths.get(absoluteUploadDir, fileName).toString();
return new File(filePath).exists();
}
}
\ No newline at end of file
package com.aps.controller.gantt;
import com.aps.entity.Gantt.ProductGanttVO;
import com.aps.entity.Gantt.ResourceGanttVO;
import com.aps.entity.Gantt.TaskVO;
import com.aps.entity.Schedule.MachineVO;
import com.aps.entity.basic.*;
import com.aps.service.plan.PlanResultService;
import com.aps.service.plan.PlanSchedulerService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@RestController
@RequestMapping("/Gantt")
@Tag(name = "甘特图管理", description = "甘特图管理")
public class ResourceGanttController {
@Autowired
private PlanSchedulerService schedulingService;
@Autowired
private PlanResultService planResultService;
@GetMapping("/resourceGantt")
@Operation(summary = "获取资源甘特图数据", description = "获取资源甘特图数据")
public List<ResourceGanttVO> getResourceGantt(@RequestParam(required = false, defaultValue = "1") Long sceneId) {
// 从PlanResultService获取ScheduleChromosome列表
List<ScheduleChromosome> scheduleChromosomes = planResultService.execute();
// 根据sceneId查找对应的ScheduleChromosome
ScheduleChromosome targetChromosome = null;
for (ScheduleChromosome chromosome : scheduleChromosomes) {
if (chromosome.getSceneId() == sceneId) {
targetChromosome = chromosome;
break;
}
}
// 如果找不到对应的场景,返回空列表
if (targetChromosome == null) {
return new ArrayList<>();
}
// 转换为 ResourceGanttVO 格式
List<ResourceGanttVO> resourceGanttVOList = new ArrayList<>();
List<ResourceGanttVO> resourceGanttVOs = convertToResourceGanttVO(targetChromosome);
resourceGanttVOList.addAll(resourceGanttVOs);
return resourceGanttVOList;
}
@GetMapping("/productGantt")
@Operation(summary = "获取产品甘特图数据", description = "获取产品甘特图数据")
public List<ProductGanttVO> getProductGantt(@RequestParam(required = false, defaultValue = "1") Long sceneId) {
// 从PlanResultService获取ScheduleChromosome列表
List<ScheduleChromosome> scheduleChromosomes = planResultService.execute();
// 根据sceneId查找对应的ScheduleChromosome
ScheduleChromosome targetChromosome = null;
for (ScheduleChromosome chromosome : scheduleChromosomes) {
if (chromosome.getSceneId() == sceneId) {
targetChromosome = chromosome;
break;
}
}
// 如果找不到对应的场景,返回空列表
if (targetChromosome == null) {
return new ArrayList<>();
}
// 转换为 ProductGanttVO 格式
List<ProductGanttVO> productGanttVOList = new ArrayList<>();
List<ProductGanttVO> productGanttVOs = convertToProductGanttVO(targetChromosome);
productGanttVOList.addAll(productGanttVOs);
return productGanttVOList;
}
@GetMapping("/sceneIds")
@Operation(summary = "获取所有场景ID", description = "获取所有场景ID")
public List<Long> getSceneIds() {
// 调用 PlanResultService 获取 ScheduleChromosome 列表
List<ScheduleChromosome> scheduleChromosomes = planResultService.execute();
// 提取所有场景ID
return scheduleChromosomes.stream()
.map(ScheduleChromosome::getSceneId)
.collect(Collectors.toList());
}
/**
* 将 ScheduleChromosome 转换为 ResourceGanttVO 列表
* @param scheduleChromosome 调度结果
* @return 转换后的数据
*/
private List<ResourceGanttVO> convertToResourceGanttVO(ScheduleChromosome scheduleChromosome) {
List<ResourceGanttVO> resourceGanttVOList = new ArrayList<>();
// 遍历所有机器资源
if (scheduleChromosome.getMachines() != null) {
for (int i = 0; i < scheduleChromosome.getMachines().size(); i++) {
Machine machine = scheduleChromosome.getMachines().get(i);
ResourceGanttVO resourceGanttVO = new ResourceGanttVO();
resourceGanttVO.setId(machine.getId());
resourceGanttVO.setName(machine.getName() != null ? machine.getName() : "设备-" + machine.getId());
resourceGanttVO.setType("设备类型"+machine.getId()); // 需要从原始数据获取
resourceGanttVO.setChange(0); // 默认值
resourceGanttVO.setSplit(0); // 默认值
resourceGanttVO.setDepartmentId(machine.getId()); // 默认值
resourceGanttVO.setDepartmentName(machine.getId()+"号线"); // 默认值
resourceGanttVO.setShopName(machine.getId()+"车间"); // 默认值
resourceGanttVO.setShopId(machine.getId()); // 默认值
resourceGanttVO.setCode("设备编码-" + machine.getId()); // 默认值
resourceGanttVO.setShift(convertToVO(machine));
// 转换任务列表
List<TaskVO> taskVOList = new ArrayList<>();
if (scheduleChromosome.getGenes() != null) {
// 筛选出属于当前设备的任务
List<Gene> machineGenes = scheduleChromosome.getGenes().stream()
.filter(gene -> gene.getMachineId()==(machine.getId()))
.collect(Collectors.toList());
// 按开始时间排序
machineGenes.sort((g1, g2) -> Integer.compare(g1.getStartTime(), g2.getStartTime()));
for (Gene gene : machineGenes) {
TaskVO taskVO = new TaskVO();
taskVO.setId(gene.getId()); // 临时处理
taskVO.setPlanId(gene.getOrderId()); // 默认值
taskVO.setProductType(0); // 默认值
taskVO.setProductName("产品"+gene.getProductId());
taskVO.setProductId(gene.getProductId()); // 默认值
taskVO.setQuantity(gene.getBatchSize());
taskVO.setStart(scheduleChromosome.getBaseTime().plusMinutes(gene.getAbsoluteStartTime()));
taskVO.setEnd(scheduleChromosome.getBaseTime().plusMinutes(gene.getAbsoluteEndTime()));
taskVO.setSetup(gene.getAbsolutePreparationTime()*60); // 默认值
taskVO.setTeardown(gene.getAbsoluteTeardownTime()*60); // 默认值
taskVO.setEquipChange(gene.getSetupTime()*60); // 默认值
taskVO.setEquipCooling(0); // 默认值
taskVO.setEquipType(resourceGanttVO.getType());
taskVO.setEquipName(resourceGanttVO.getName());
// taskVO.setDuration(calculateDuration(
// scheduleChromosome.getBaseTime().plusMinutes(gene.getStartTime()),
// scheduleChromosome.getBaseTime().plusMinutes(gene.getEndTime()))); // 计算持续时间
taskVO.setDuration(0); //
taskVO.setEquipId(machine.getId());
taskVO.setShopId(machine.getId());
taskVO.setShopName(resourceGanttVO.getShopName());
taskVO.setStatus(0); // 默认值
taskVO.setDetailId((long) gene.getProductId() * 1000 + gene.getOperationId()); // 将productId和operationID组合为detailId
taskVO.setHeaderId(gene.getProductId()); // 默认值
taskVO.setHeaderName("工艺"+gene.getProductId()); // 默认值
taskVO.setSeq(gene.getSequenceId()); // 使用工序ID
taskVO.setSeqName( "工序名称"+gene.getSequenceId());
taskVO.setProcessingTime(gene.getProcessingTime()*60);
taskVO.setAbsoluteStart(scheduleChromosome.getBaseTime().plusMinutes(gene.getStartTime()));
taskVO.setAbsoluteEnd(scheduleChromosome.getBaseTime().plusMinutes(gene.getEndTime()));
taskVOList.add(taskVO);
// 调试:检查machine中的shifts状态
if (machine.getShifts() != null) {
for (Shift shift : machine.getShifts()) {
System.out.println("Before setting shifts - Shift status: " + shift.getStatus());
}
}
taskVO.setAbsolutePreparationTime(gene.getTeardownTime());
}
}
resourceGanttVO.setList(taskVOList);
resourceGanttVOList.add(resourceGanttVO);
}
}
return resourceGanttVOList;
}
/**
* 将 ScheduleChromosome 转换为 ProductGanttVO 列表
* @param scheduleChromosome 调度结果
* @return 转换后的数据
*/
private List<ProductGanttVO> convertToProductGanttVO(ScheduleChromosome scheduleChromosome) {
List<ProductGanttVO> productGanttVOList = new ArrayList<>();
// 按产品ID和工单ID分组基因
if (scheduleChromosome.getGenes() != null) {
// 按工单ID分组
scheduleChromosome.getGenes().stream()
.collect(Collectors.groupingBy(Gene::getOrderId))
.forEach((orderId, genes) -> {
if (!genes.isEmpty()) {
ProductGanttVO productGanttVO = new ProductGanttVO();
Gene firstGene = genes.get(0);
productGanttVO.setId(firstGene.getId());
productGanttVO.setProductName("产品"+firstGene.getProductId()); // 默认值,实际应从订单数据获取
productGanttVO.setProductType(0);
productGanttVO.setProductId(firstGene.getProductId());
// 计算总数量(假设同一批次)
productGanttVO.setQuantity(firstGene.getBatchSize());
productGanttVO.setCode("编号"+firstGene.getProductId()); // 默认值
productGanttVO.setShopId(firstGene.getMachineId()); // 默认值
productGanttVO.setShopName(firstGene.getMachineId()+"号线"); // 默认值
productGanttVO.setStatus("已发布");
productGanttVO.setHeaderId(firstGene.getProductId());
productGanttVO.setHeaderName("工艺"+firstGene.getProductId()); // 默认值
// 计算开始和结束时间
int minStartTime = genes.stream()
.mapToInt(Gene::getStartTime)
.min()
.orElse(0);
int maxEndTime = genes.stream()
.mapToInt(Gene::getEndTime)
.max()
.orElse(0);
productGanttVO.setStartDate(scheduleChromosome.getBaseTime().plusMinutes(minStartTime));
productGanttVO.setEndDate(scheduleChromosome.getBaseTime().plusMinutes(maxEndTime));
// 转换任务列表
List<TaskVO> taskVOList = new ArrayList<>();
// 按工序顺序排序
genes.sort((g1, g2) -> Integer.compare(g1.getSequenceId(), g2.getSequenceId()));
for (int i = 0; i < genes.size(); i++) {
Gene gene = genes.get(i);
TaskVO taskVO = new TaskVO();
taskVO.setId(gene.getId()); // 生成唯一ID
taskVO.setPlanId(orderId);
taskVO.setProductType(0);
taskVO.setProductName("产品"+gene.getProductId());
taskVO.setProductId(gene.getProductId());
taskVO.setQuantity(gene.getBatchSize());
taskVO.setStart(scheduleChromosome.getBaseTime().plusMinutes(gene.getAbsoluteStartTime()));
taskVO.setEnd(scheduleChromosome.getBaseTime().plusMinutes(gene.getAbsoluteEndTime()));
taskVO.setSetup(gene.getAbsolutePreparationTime()*60); // 默认值
taskVO.setTeardown(gene.getAbsoluteTeardownTime()*60); // 默认值
taskVO.setEquipChange(gene.getSetupTime()*60); // 默认值
taskVO.setEquipCooling(0); // 默认值
// taskVO.setEquipType("PTT-" + (i+1) + "-" + gene.getOperationName().toUpperCase().substring(0, Math.min(3, gene.getOperationName().length())));
// taskVO.setEquipName(gene.getOperationName());
taskVO.setDuration(calculateDuration(
scheduleChromosome.getBaseTime().plusMinutes(gene.getStartTime()),
scheduleChromosome.getBaseTime().plusMinutes(gene.getEndTime())));
taskVO.setEquipId(gene.getMachineId()); // 生成设备ID
taskVO.setShopId(gene.getMachineId());
taskVO.setShopName(gene.getMachineId()+"车间");
taskVO.setStatus(0);
taskVO.setDetailId((long) gene.getProductId() * 1000 + gene.getOperationId());
taskVO.setHeaderId(gene.getProductId());
taskVO.setHeaderName("工艺"+gene.getProductId());
taskVO.setSeq(gene.getSequenceId());
taskVO.setSeqName("工序名称"+gene.getSequenceId());
taskVO.setAbsoluteStart(scheduleChromosome.getBaseTime().plusMinutes(gene.getStartTime()));
taskVO.setAbsoluteEnd(scheduleChromosome.getBaseTime().plusMinutes(gene.getEndTime()));
taskVOList.add(taskVO);
}
productGanttVO.setList(taskVOList);
productGanttVOList.add(productGanttVO);
}
});
}
return productGanttVOList;
}
/**
* 计算任务持续时间(分钟)
* @param start 开始时间
* @param end 结束时间
* @return 持续时间(分钟)
*/
private Integer calculateDuration(LocalDateTime start, LocalDateTime end) {
if (start == null || end == null) {
return 0;
}
return Math.toIntExact(java.time.Duration.between(start, end).toMinutes());
}
private ShiftVO convertToVO(Machine machine) {
ShiftVO shiftVO= new ShiftVO();
shiftVO.setId(machine.getId());
shiftVO.setName(machine.getName());
shiftVO.setShifts(machine.getShifts());
shiftVO.setMaintenanceWindows(machine.getMaintenanceWindows());
// 注意:tasks 字段需要在其他地方设置,因为 Machine 类中没有任务信息
return shiftVO;
}
}
\ No newline at end of file
package com.aps.entity.Gantt;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@Schema(name = "产品甘特图数据模型", description = "产品甘特图数据模型")
public class ProductGanttVO {
@Schema(description = "产品ID")
private Integer id;
@Schema(description = "产品名称")
private String productName;
@Schema(description = "产品类型")
private Integer productType;
@Schema(description = "产品编号")
private Integer productId;
@Schema(description = "数量")
private Integer quantity;
@Schema(description = "编码")
private String code;
@Schema(description = "开始日期")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
private LocalDateTime startDate;
@Schema(description = "结束日期")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd")
private LocalDateTime endDate;
@Schema(description = "车间ID")
private Integer shopId;
@Schema(description = "车间名称")
private String shopName;
@Schema(description = "状态")
private String status;
@Schema(description = "头ID")
private Integer headerId;
@Schema(description = "头名称")
private String headerName;
@Schema(description = "任务列表")
private List<TaskVO> list;
}
\ No newline at end of file
package com.aps.entity.Gantt;
import com.aps.entity.basic.ShiftVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@Schema(name = "资源甘特图数据模型", description = "资源甘特图数据模型")
public class ResourceGanttVO {
@Schema(description = "设备ID")
private Integer id;
@Schema(description = "设备名称")
private String name;
@Schema(description = "设备类型")
private String type;
@Schema(description = "是否可更换")
private Integer change;
@Schema(description = "是否可拆分")
private Integer split;
@Schema(description = "部门ID")
private Integer departmentId;
@Schema(description = "部门名称")
private String departmentName;
@Schema(description = "车间名称")
private String shopName;
@Schema(description = "车间ID")
private Integer shopId;
@Schema(description = "设备编码")
private String code;
@Schema(description = "任务列表")
private List<TaskVO> list;
private ShiftVO shift;
}
\ No newline at end of file
package com.aps.entity.Gantt;
import com.aps.entity.basic.Shift;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@Schema(name = "任务数据模型", description = "任务数据模型")
public class TaskVO {
@Schema(description = "任务ID")
private Integer id;
@Schema(description = "计划ID")
private Integer planId;
@Schema(description = "产品类型")
private Integer productType;
@Schema(description = "产品名称")
private String productName;
@Schema(description = "产品ID")
private Integer productId;
@Schema(description = "数量")
private Integer quantity;
@Schema(description = "开始时间")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
private LocalDateTime start;
@Schema(description = "结束时间")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
private LocalDateTime end;
@Schema(description = "开始时间")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
private LocalDateTime absoluteStart;
@Schema(description = "结束时间")
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
private LocalDateTime absoluteEnd;
@Schema(description = "准备时间")
private Integer setup;
@Schema(description = "收尾时间")
private Integer teardown;
@Schema(description = "设备更换时间")
private Integer equipChange;
@Schema(description = "设备冷却时间")
private Integer equipCooling;
@Schema(description = "设备类型")
private String equipType;
@Schema(description = "设备名称")
private String equipName;
@Schema(description = "持续时间")
private Integer duration;
@Schema(description = "设备ID")
private Integer equipId;
@Schema(description = "车间ID")
private Integer shopId;
@Schema(description = "车间名称")
private String shopName;
//‘0’:已发布 ‘1’:未发布
@Schema(description = "状态")
private Integer status;
@Schema(description = "明细ID")
private Long detailId;
@Schema(description = "头ID")
private Integer headerId;
@Schema(description = "头名称")
private String headerName;
@Schema(description = "工序序号")
private Integer seq;
@Schema(description = "工序名称")
private String seqName;
private int processingTime; // 绝对处理时间(分钟)
private int absolutePreparationTime; // 新增:绝对准备时间
}
\ No newline at end of file
......@@ -12,10 +12,25 @@ public class Gene {
private int orderId;
private int productId;
private int operationId;
private String operationName; // 添加工序名称
private int machineId;
private int startTime; // 相对开始时间(分钟)
private int endTime; // 相对结束时间(分钟)
private int batchSize; // 批次大小(订单可拆分)
private List<GeneDetail> geneDetails; // 时间详情
private int processingTime; // 绝对处理时间(分钟)
private int Id;
private int sequenceId;
private int setupTime; // 换型时
private int teardownTime; // 后处理时间(收尾时间)
private int contantTime; // 常数时间
private int preTime;
private int absoluteStartTime; // 相对开始时间(分钟)
private int absoluteEndTime; // 相对结束时间(分钟)
private int absolutePreparationTime; // 新增:绝对前处理时间
private int absoluteTeardownTime; // 新增:绝对后处理时间
private int absoluteSetupTime; // 新增:绝对换型时间
}
\ No newline at end of file
......@@ -28,4 +28,21 @@ public class Holiday {
public void setEnd(LocalDateTime end) {
this.end = end;
}
/**
* 将Holiday转换为MaintenanceWindow
* @return 对应的MaintenanceWindow对象
*/
public MaintenanceWindow toMaintenanceWindow() {
return new MaintenanceWindow(this.start, this.end, "Holiday Period");
}
/**
* 将Holiday转换为MaintenanceWindow(可指定原因)
* @param reason 维护原因
* @return 对应的MaintenanceWindow对象
*/
public MaintenanceWindow toMaintenanceWindow(String reason) {
return new MaintenanceWindow(this.start, this.end, reason);
}
}
\ No newline at end of file
......@@ -10,4 +10,7 @@ public class MachineOption {
private int machineId;
private int processingTime; // 加工时间
private int setupTime; // 换型时间(如果与前一个产品不同)
private int teardownTime; // 收尾时间(后处理时间)
private int contantTime; // 常数时间
private int preTime; // 前处理时间
}
\ No newline at end of file
......@@ -8,6 +8,7 @@ public class MaintenanceWindow {
private LocalDateTime endTime;
private String reason;
public MaintenanceWindow() {}
public MaintenanceWindow(LocalDateTime startTime, LocalDateTime endTime, String reason) {
......@@ -16,6 +17,23 @@ public class MaintenanceWindow {
this.reason = reason;
}
/**
* 从Holiday对象构造MaintenanceWindow
* @param holiday 假期对象
*/
public MaintenanceWindow(Holiday holiday) {
this(holiday.getStart(), holiday.getEnd(), "Holiday Period");
}
/**
* 从Holiday对象构造MaintenanceWindow(可指定原因)
* @param holiday 假期对象
* @param reason 维护原因
*/
public MaintenanceWindow(Holiday holiday, String reason) {
this(holiday.getStart(), holiday.getEnd(), reason);
}
// Getters and Setters
public LocalDateTime getStartTime() {
return startTime;
......@@ -41,6 +59,8 @@ public class MaintenanceWindow {
this.reason = reason;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
......
......@@ -15,7 +15,7 @@ public class ScheduleChromosome {
private double tardiness;
private Map<Integer, Double> objectiveValues;
private LocalDateTime baseTime;
private long sceneId; // 新增场景ID字段
public ScheduleChromosome() {
this.genes = new ArrayList<>();
this.objectiveValues = new HashMap<>();
......
package com.aps.entity.basic;
import lombok.Data;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Set;
@Data
public class Shift {
private LocalTime startTime;
private LocalTime endTime;
......@@ -13,6 +16,12 @@ public class Shift {
private boolean isTemporaryShift;
private int priority;
private Integer status;//0:正常班次 1:临时班次 2:维修
// 添加设备ID和名称字段
private Integer machineId;
private String machineName;
public Shift() {}
......@@ -40,13 +49,7 @@ public class Shift {
this.days = days;
}
// 辅助方法:将数字转换为DayOfWeek
public Set<DayOfWeek> getDaysAsEnum() {
if (days == null) return null;
return days.stream()
.map(day -> DayOfWeek.of(day == 0 ? 7 : day)) // 处理周日(0)转换为7
.collect(java.util.stream.Collectors.toSet());
}
public LocalDateTime getShiftDate() {
return shiftDate;
......@@ -71,4 +74,28 @@ public class Shift {
public void setPriority(int priority) {
this.priority = priority;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Integer getMachineId() {
return machineId;
}
public void setMachineId(Integer machineId) {
this.machineId = machineId;
}
public String getMachineName() {
return machineName;
}
public void setMachineName(String machineName) {
this.machineName = machineName;
}
}
\ No newline at end of file
package com.aps.entity.basic;
import lombok.Data;
import java.util.List;
@Data
public class ShiftVO {
private int id;
private String name;
private List<Shift> shifts;
private List<MaintenanceWindow> maintenanceWindows;
}
......@@ -153,9 +153,36 @@ public class AlgorithmScheduler6 {
}
int processingTime = machineOption.getProcessingTime() * batchSize;
int preTime = machineOption.getPreTime(); // 前处理时间(常数时间,与批次大小无关)
int setupTime = 0; // 换型时间默认为0,仅在产品变化时计算
int teardownTime = machineOption.getTeardownTime(); // 后处理时间(常数时间,与批次大小无关)
// 计算换型时间
// 查找该机器上一个任务
Gene lastGeneOnMachine = chromosome.getGenes().stream()
.filter(g -> g.getMachineId() == machine.getId())
.max(Comparator.comparingInt(Gene::getEndTime))
.orElse(null);
// 如果上一个任务的产品与当前任务不同,则需要换型时间
if (lastGeneOnMachine != null && lastGeneOnMachine.getProductId() != order.getProductId()) {
setupTime = machineOption.getSetupTime();
}
// 前处理时间可以在上一个工序未完成时开始,所以调整开始时间
int adjustedPrevTime = prevtime;
if (preTime > 0 && lastGeneOnMachine != null) {
// 前处理可以在上一个任务结束前preTime时间开始
adjustedPrevTime = lastGeneOnMachine.getEndTime() - preTime;
}
// List<GeneDetail> geneDetails = GetNextAvailableTime(
// machine, prevtime, -1, processingTime,
// chromosome.getGenes(), false, true
// );
List<GeneDetail> geneDetails = GetNextAvailableTime(
machine, prevtime, -1, processingTime,
machine, adjustedPrevTime, -1, processingTime + teardownTime + setupTime,
chromosome.getGenes(), false, true
);
......@@ -163,7 +190,7 @@ public class AlgorithmScheduler6 {
int endTime = geneDetails.stream().mapToInt(GeneDetail::getEndTime).max().orElse(0);
machine.setEarliestTime(endTime);
machine.setTotalTaskTime(machine.getTotalTaskTime() + processingTime);
machine.setTotalTaskTime(machine.getTotalTaskTime() + +teardownTime + setupTime);
Gene gene = new Gene();
gene.setOrderId(order.getId());
......@@ -175,6 +202,8 @@ public class AlgorithmScheduler6 {
gene.setEndTime(endTime);
gene.setProcessingTime(processingTime);
gene.setGeneDetails(deepCopyGeneDetailList(geneDetails));
gene.setId(chromosome.getGenes().size() + 1);
gene.setSequenceId(operation.getSequence());
chromosome.getGenes().add(gene);
}
}
......
package com.aps.service.plan;
import com.aps.common.util.FileHelper;
import com.aps.entity.basic.*;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
public class AlgorithmScheduler7 {
private final List<Product> _products;
private final List<Machine> _machines;
private final List<Order> _orders;
private final Random _random;
private final MachineSchedulerService _machineScheduler;
private int _populationSize = 100;
private double _crossoverRate = 0.8;
private double _mutationRate = 0.1;
private int _maxGenerations = 500;
private int _elitismCount = 5;
private final LocalDateTime baseTime = LocalDateTime.of(2025, 10, 1, 0, 0, 0);
public AlgorithmScheduler7(List<Product> products, List<Machine> machines, List<Order> orders, MachineSchedulerService machineScheduler) {
this._products = deepCopyProductList(products);
this._machines = deepCopyMachineList(machines);
this._orders = deepCopyOrderList(orders);
this._machineScheduler = machineScheduler;
this._random = new Random();
}
public ScheduleChromosome Run() {
List<ScheduleChromosome> population = InitializePopulation();
ScheduleChromosome bestSolution = null;
ScheduleChromosome currentBest = null;
double bestFitness = Double.MIN_VALUE;
currentBest = population.stream()
.max(Comparator.comparingDouble(ScheduleChromosome::getFitness))
.orElse(null);
if (currentBest != null) {
bestFitness = currentBest.getFitness();
bestSolution = deepCopyScheduleChromosome(currentBest);
}
return bestSolution;
}
public List<ScheduleChromosome> RunAll() {
return InitializePopulation();
}
private List<ScheduleChromosome> InitializePopulation() {
List<ScheduleChromosome> population = new ArrayList<>();
System.out.println("开始初始化种群,目标大小: " + _populationSize);
for (int i = 0; i < _populationSize; i++) {
ScheduleChromosome chromosome = new ScheduleChromosome(baseTime);
chromosome.setGenes(new ArrayList<>());
chromosome.setObjectiveValues(new HashMap<>());
chromosome.setOrders(deepCopyOrderList(_orders));
chromosome.setMachines(deepCopyMachineList(_machines));
// 为每个订单分配工序
for (Order order : _orders) {
Product product = _products.stream()
.filter(p -> p.getId() == order.getProductId())
.findFirst()
.orElseThrow(() -> new NoSuchElementException("Product not found: " + order.getProductId()));
int prevtime = 0;
int remainingQuantity = order.getQuantity();
// 订单拆分逻辑 - 增强随机性
while (remainingQuantity > 0) {
int batchSize;
if (order.isCanSplit() && remainingQuantity > 1) {
// 修改:增强批次拆分的随机性
int maxSplit = Math.min(remainingQuantity, Math.max(1, remainingQuantity / 2));
batchSize = _random.nextInt(maxSplit) + 1;
} else {
batchSize = remainingQuantity;
}
if (batchSize > remainingQuantity) {
batchSize = remainingQuantity;
}
remainingQuantity -= batchSize;
// 按工序顺序处理
for (Operation operation : product.getOperations().stream()
.sorted(Comparator.comparingInt(Operation::getSequence))
.collect(Collectors.toList())) {
if (operation.getId() > 1) {
Gene prevGene = chromosome.getGenes().stream()
.filter(t -> t.getOrderId() == order.getId() && t.getOperationId() == operation.getId() - 1)
.findFirst()
.orElse(null);
prevtime = prevGene != null ? prevGene.getEndTime() : 0;
}
MachineOption machineOption;
Machine machine;
// 多设备选择逻辑 - 增强随机性
if (operation.getMachineOptions().size() > 1) {
Set<Integer> machineIds = operation.getMachineOptions().stream()
.map(MachineOption::getMachineId)
.collect(Collectors.toSet());
// 修改:增强设备选择的随机性
List<Machine> availableMachines = chromosome.getMachines().stream()
.filter(t -> machineIds.contains(t.getId()))
.collect(Collectors.toList());
if (!availableMachines.isEmpty()) {
// 随机打乱后选择
Collections.shuffle(availableMachines, _random);
machine = availableMachines.get(0);
Machine finalMachine = machine;
machineOption = operation.getMachineOptions().stream()
.filter(t -> t.getMachineId() == finalMachine.getId())
.findFirst()
.orElseThrow(() -> new NoSuchElementException("MachineOption not found for machine: " + finalMachine.getId()));
} else {
// 回退到原始逻辑
machine = chromosome.getMachines().stream()
.filter(t -> machineIds.contains(t.getId()))
.sorted(Comparator.comparingInt(Machine::getEarliestTime)
.thenComparingInt(Machine::getTotalTaskTime)
.thenComparing(t -> _random.nextDouble()))
.findFirst()
.orElseThrow(() -> new NoSuchElementException("No available machine for operation: " + operation.getId()));
Machine finalMachine = machine;
machineOption = operation.getMachineOptions().stream()
.filter(t -> t.getMachineId() == finalMachine.getId())
.findFirst()
.orElseThrow(() -> new NoSuchElementException("MachineOption not found for machine: " + finalMachine.getId()));
}
} else {
machineOption = operation.getMachineOptions().get(0);
MachineOption finalMachineOption = machineOption;
machine = chromosome.getMachines().stream()
.filter(m -> m.getId() == finalMachineOption.getMachineId())
.findFirst()
.orElseThrow(() -> new NoSuchElementException("Machine not found: " + finalMachineOption.getMachineId()));
}
// ==================== 修正的时间计算逻辑开始 ====================
int processingTime = machineOption.getProcessingTime() * batchSize;
if (machineOption.getContantTime() > 0) {
processingTime = machineOption.getContantTime();
}
int preTime = machineOption.getPreTime();
int teardownTime = machineOption.getTeardownTime();
int setupTime = calculateSetupTime(chromosome.getGenes(), order, machine, machineOption);
int effectivePrepTime = Math.max(machineOption.getPreTime(), calculateSetupTime(chromosome.getGenes(), order, machine, machineOption));
// 检查是否是设备上的第一个产品
boolean isFirstProductOnMachine = chromosome.getGenes().stream()
.filter(g -> g.getMachineId() == machine.getId())
.noneMatch(g -> g.getProductId() == order.getProductId());
// 计算调整后的开始时间(使用有效准备时间)
int adjustedPrevTime = calculateAdjustedStartTime(
chromosome.getGenes(), order.getId(), operation.getId(),
effectivePrepTime, prevtime, machine.getId(), isFirstProductOnMachine
);
// 关键修改2:总处理时间 = 有效准备时间(最大值) + 加工时间 + 后处理时间
int totalProcessingDuration = effectivePrepTime + processingTime + teardownTime;
// 关键修改3:调整提议的开始时间,基于有效准备时间提前
int proposedStartTimeWithPreTime = adjustedPrevTime;
if (operation.getId() > 1 && effectivePrepTime > 0) { // 使用有效准备时间判断
// 对于第二道及后续工序,有效准备时间可以提前
Gene lastGeneOnMachine = chromosome.getGenes().stream()
.filter(g -> g.getMachineId() == machine.getId())
.max(Comparator.comparingInt(Gene::getEndTime))
.orElse(null);
int machineEarliestTime = lastGeneOnMachine != null ? lastGeneOnMachine.getEndTime() : 0;
// 提前量基于有效准备时间计算
proposedStartTimeWithPreTime = Math.max(adjustedPrevTime - effectivePrepTime, machineEarliestTime);
}
// 查找可用时间槽(基于有效准备时间的总时长)
List<GeneDetail> geneDetails = GetNextAvailableTime(
machine, proposedStartTimeWithPreTime, -1, totalProcessingDuration,
chromosome.getGenes(), false, true
);
int startTime = geneDetails.stream().mapToInt(GeneDetail::getStartTime).min().orElse(0);
int endTime = geneDetails.stream().mapToInt(GeneDetail::getEndTime).max().orElse(0);
// 验证有效准备时间是否被正确安排
if (operation.getId() > 1 && effectivePrepTime > 0) { // 使用有效准备时间判断
Gene prevOperationGene = chromosome.getGenes().stream()
.filter(g -> g.getOrderId() == order.getId() && g.getOperationId() == operation.getId() - 1)
.findFirst()
.orElse(null);
if (prevOperationGene != null && startTime < prevOperationGene.getEndTime()) {
// 调整提前量,基于有效准备时间
int actualStartTime = Math.max(startTime, prevOperationGene.getEndTime() - effectivePrepTime);
if (actualStartTime != startTime) {
geneDetails = GetNextAvailableTime(
machine, actualStartTime, -1, totalProcessingDuration,
chromosome.getGenes(), false, true
);
startTime = geneDetails.stream().mapToInt(GeneDetail::getStartTime).min().orElse(0);
endTime = geneDetails.stream().mapToInt(GeneDetail::getEndTime).max().orElse(0);
}
}
}
// ==================== 修正的时间计算逻辑结束 ====================
machine.setEarliestTime(endTime);
machine.setTotalTaskTime(machine.getTotalTaskTime() +
processingTime + effectivePrepTime + teardownTime );
Gene gene = new Gene();
gene.setOrderId(order.getId());
gene.setProductId(order.getProductId());
gene.setOperationId(operation.getId());
gene.setMachineId(machineOption.getMachineId());
gene.setBatchSize(batchSize);
gene.setStartTime(startTime); // 包含前处理的开始时间
gene.setEndTime(endTime); // 包含后处理的结束时间
gene.setProcessingTime(processingTime);
gene.setPreTime(preTime);
gene.setSetupTime(setupTime);
gene.setTeardownTime(teardownTime);
gene.setOperationName("工序" + operation.getId());
gene.setGeneDetails((geneDetails));
gene.setId(chromosome.getGenes().size() + 1);
gene.setSequenceId(operation.getSequence());
chromosome.getGenes().add(gene);
int absolutePreparationTime = calculateAbsolutePreparationTime(gene, machineOption);
int absoluteSetupTime =calculateAbsoluteSetupTime(gene, machineOption);
gene.setAbsolutePreparationTime(absolutePreparationTime);
int absoluteStartTime =startTime+Math.max(absolutePreparationTime,absoluteSetupTime);
int absoluteTeardownTime = calculateAbsoluteTeardownTime(gene, machineOption);
int absoluteEndTime = endTime-absoluteTeardownTime;
gene.setAbsoluteStartTime(absoluteStartTime);
gene.setAbsoluteEndTime(absoluteEndTime);
gene.setAbsoluteTeardownTime(absoluteTeardownTime);
// 更新prevtime为当前工序的结束时间
prevtime = endTime;
}
}
}
population.add(chromosome);
}
System.out.println("生成种群大小: " + population.size());
Map<String, List<ScheduleChromosome>> grouped = population.stream()
.collect(Collectors.groupingBy(ScheduleChromosome::getGenesStr));
population = grouped.values().stream()
.map(group -> group.get(0))
.collect(Collectors.toList());
// 调试输出染色体多样性
System.out.println("染色体多样性分析:");
Map<String, Integer> keyCounts = new HashMap<>();
for (ScheduleChromosome chrom : population) {
String key = chrom.getGenesStr();
keyCounts.put(key, keyCounts.getOrDefault(key, 0) + 1);
}
System.out.println("唯一染色体数量: " + keyCounts.size());
// 并行计算适应度
population.parallelStream().forEach(this::CalculateFitness);
// 打印摘要
population.forEach(this::WriteScheduleSummary);
return population;
}
private int calculateSetupTime(List<Gene> existingGenes, Order currentOrder, Machine machine, MachineOption machineOption) {
// 查找设备上最新的任务(按结束时间排序)
Gene lastGeneOnMachine = existingGenes.stream()
.filter(g -> g.getMachineId() == machine.getId())
.max(Comparator.comparingInt(Gene::getEndTime))
.orElse(null);
// 无历史任务 → 换型时间0
if (lastGeneOnMachine == null) {
System.out.printf("设备%d:无历史任务,换型时间=0%n", machine.getId());
return 0;
}
// 产品不同 → 返回换型时间;产品相同 → 0
int setupTime = (lastGeneOnMachine.getProductId() != currentOrder.getProductId())
? machineOption.getSetupTime()
: 0;
System.out.printf("设备%d:上一产品ID=%d,当前产品ID=%d,换型时间=%d分钟%n",
machine.getId(), lastGeneOnMachine.getProductId(), currentOrder.getProductId(), setupTime);
return setupTime;
}
/**
* 计算调整后的开始时间,考虑前处理时间的重叠
*/
private int calculateAdjustedStartTime(List<Gene> existingGenes, int orderId,
int operationId, int preTime, int prevTime,
int machineId, boolean isFirstProductOnMachine) {
if (preTime <= 0 || operationId == 1) {
return prevTime; // 第一道工序或没有前处理时间,不提前
}
// 查找同一订单的上一个工序
Gene prevOperationGene = existingGenes.stream()
.filter(g -> g.getOrderId() == orderId && g.getOperationId() == operationId - 1)
.max(Comparator.comparingInt(Gene::getEndTime))
.orElse(null);
// 查找设备上一个任务的结束时间
Gene lastGeneOnMachine = existingGenes.stream()
.filter(g -> g.getMachineId() == machineId)
.max(Comparator.comparingInt(Gene::getEndTime))
.orElse(null);
int lastMachineTaskEndTime = lastGeneOnMachine != null ? lastGeneOnMachine.getEndTime() : 0;
if (prevOperationGene != null) {
// 关键修改:允许前处理时间提前开始
// 前处理可以在上一个工序结束前开始,最多提前preTime分钟
int earliestPossibleStart = Math.max(
Math.max(prevTime, lastMachineTaskEndTime),
prevOperationGene.getEndTime() - preTime // 允许提前preTime分钟
);
// 但不能早于上一个工序的开始时间
earliestPossibleStart = Math.max(earliestPossibleStart, prevOperationGene.getStartTime());
return earliestPossibleStart;
}
return Math.max(prevTime, lastMachineTaskEndTime);
}
/**
* 验证时间安排的合理性
*/
/**
* 验证时间安排的合理性
*/
/**
* 验证时间安排的合理性
*/
private boolean validateTimeArrangement(List<Gene> existingGenes, int orderId,
int operationId, int startTime, int endTime,
int preTime, boolean isFirstProductOnMachine) {
// 验证1:开始时间不能小于0
if (startTime < 0) {
return false;
}
// 验证2:第一道工序的前处理时间不能提前
if (operationId == 1 && preTime > 0) {
// 第一道工序的前处理时间应该从开始时间计算,不能提前
// 但需要有足够的时间包含前处理时间
if (endTime - startTime < preTime) {
return false; // 总时间不足以包含前处理时间
}
}
// 验证3:对于非首道工序,前处理时间不能早于上一个工序的开始时间
if (operationId > 1 && preTime > 0) {
Gene prevOperationGene = existingGenes.stream()
.filter(g -> g.getOrderId() == orderId && g.getOperationId() == operationId - 1)
.max(Comparator.comparingInt(Gene::getStartTime))
.orElse(null);
if (prevOperationGene != null) {
// 前处理开始时间不能早于上一个工序的开始时间
if (startTime < prevOperationGene.getStartTime()) {
return false;
}
}
}
// 验证4:结束时间必须大于开始时间
if (endTime <= startTime) {
return false;
}
// 验证5:总时间必须足够包含所有处理时间
int totalRequiredTime = preTime + (endTime - startTime - preTime); // 简化计算
if (endTime - startTime < totalRequiredTime) {
return false;
}
return true;
}
/**
* 计算总处理时间(用于调试和验证)
*/
private int calculateTotalDuration(Gene gene) {
return (gene.getEndTime() - gene.getStartTime()) -
(gene.getPreTime() + gene.getTeardownTime() + gene.getSetupTime());
}
// ==================== 原有方法保持不变 ====================
private List<ScheduleChromosome> removeTrueDuplicates(List<ScheduleChromosome> population) {
Map<String, ScheduleChromosome> uniqueMap = new LinkedHashMap<>();
for (ScheduleChromosome chrom : population) {
String key = generateChromosomeKey(chrom);
uniqueMap.putIfAbsent(key, chrom);
}
return new ArrayList<>(uniqueMap.values());
}
private String generateChromosomeKey(ScheduleChromosome chrom) {
return chrom.getGenes().stream()
.sorted(Comparator.comparing(Gene::getOrderId)
.thenComparing(Gene::getOperationId)
.thenComparing(Gene::getStartTime))
.map(g -> String.format("%d-%d-%d-%d-%d-%d",
g.getOrderId(), g.getOperationId(), g.getMachineId(),
g.getBatchSize(), g.getStartTime(), g.getEndTime()))
.collect(Collectors.joining("|"));
}
private void CalculateFitness(ScheduleChromosome chromosome) {
// 1. 最大完工时间
double makespan = chromosome.getGenes().stream()
.mapToInt(Gene::getEndTime)
.max()
.orElse(0);
// 2. 总延迟时间
double tardiness = 0.0;
Map<Integer, List<Gene>> orderGroups = chromosome.getGenes().stream()
.collect(Collectors.groupingBy(Gene::getOrderId));
for (Map.Entry<Integer, List<Gene>> group : orderGroups.entrySet()) {
int orderCompletion = group.getValue().stream()
.mapToInt(Gene::getEndTime)
.max()
.orElse(0);
Order order = _orders.stream()
.filter(o -> o.getId() == group.getKey())
.findFirst()
.orElse(null);
if (order != null) {
LocalDateTime completionTime = chromosome.getBaseTime().plusMinutes(orderCompletion);
LocalDateTime dueDateTime = order.getDueDate().toLocalDateTime();
if (completionTime.isAfter(dueDateTime)) {
long totalMinutes = java.time.temporal.ChronoUnit.MINUTES.between(dueDateTime, completionTime);
double tardinessHours = totalMinutes / 60.0;
order.setTardiness(tardinessHours);
tardiness += tardinessHours;
}
}
}
// 3. 总换型时间
double totalSetupTime = CalculateTotalSetupTime(chromosome);
// 4. 总流程时间
double totalFlowTime = CalculateTotalFlowTime(chromosome);
// 5. 机器负载均衡
double machineLoadBalance = CalculateMachineLoadBalance(chromosome);
// 存储目标值
double finalTardiness = tardiness;
HashMap<Integer, Double> objMap = new HashMap<>();
objMap.put(0, makespan);
objMap.put(1, finalTardiness);
objMap.put(2, totalSetupTime);
objMap.put(3, totalFlowTime);
objMap.put(4, machineLoadBalance);
chromosome.setObjectiveValues(objMap);
// 计算适应度
chromosome.setFitness(
0.4 * (1.0 / (1 + makespan)) +
0.3 * (1.0 / (1 + tardiness)) +
0.1 * (1.0 / (1 + totalSetupTime)) +
0.1 * (1.0 / (1 + totalFlowTime)) +
0.1 * machineLoadBalance
);
}
private double CalculateTotalFlowTime(ScheduleChromosome chromosome) {
double totalFlowTime = 0.0;
Map<Integer, List<Gene>> orderGroups = chromosome.getGenes().stream()
.collect(Collectors.groupingBy(Gene::getOrderId));
for (Map.Entry<Integer, List<Gene>> group : orderGroups.entrySet()) {
int minStartTime = group.getValue().stream().mapToInt(Gene::getStartTime).min().orElse(0);
int maxEndTime = group.getValue().stream().mapToInt(Gene::getEndTime).max().orElse(0);
LocalDateTime start = chromosome.getBaseTime().plusMinutes(minStartTime);
LocalDateTime end = chromosome.getBaseTime().plusMinutes(maxEndTime);
long hours = ChronoUnit.HOURS.between(start, end);
long minutes = ChronoUnit.MINUTES.between(start, end) % 60;
totalFlowTime += hours + (double) minutes / 60;
}
return totalFlowTime;
}
private double CalculateTotalSetupTime(ScheduleChromosome chromosome) {
double totalSetupTime = 0.0;
double totalTeardownTime = 0.0;
double totalPreTime = 0.0;
Map<Integer, List<Gene>> machineGroups = chromosome.getGenes().stream()
.collect(Collectors.groupingBy(Gene::getMachineId));
for (Map.Entry<Integer, List<Gene>> machineGroup : machineGroups.entrySet()) {
List<Gene> sortedGenes = machineGroup.getValue().stream()
.sorted(Comparator.comparingInt(Gene::getStartTime))
.collect(Collectors.toList());
// 累加所有任务的前处理时间和后处理时间
for (Gene gene : sortedGenes) {
totalPreTime += gene.getPreTime();
totalTeardownTime += gene.getTeardownTime();
}
// 计算换型时间(仅在产品变化时触发)
Integer lastProductId = null;
for (Gene gene : sortedGenes) {
if (lastProductId != null && lastProductId != gene.getProductId()) {
Product product = _products.stream()
.filter(p -> p.getId() == gene.getProductId())
.findFirst()
.orElse(null);
if (product == null) continue;
Operation operation = product.getOperations().stream()
.filter(o -> o.getId() == gene.getOperationId())
.findFirst()
.orElse(null);
if (operation == null) continue;
MachineOption machineOption = operation.getMachineOptions().stream()
.filter(m -> m.getMachineId() == gene.getMachineId())
.findFirst()
.orElse(null);
if (machineOption != null) {
totalSetupTime += machineOption.getSetupTime();
}
}
lastProductId = gene.getProductId();
}
}
return totalSetupTime + totalPreTime + totalTeardownTime;
}
private double CalculateMachineLoadBalance(ScheduleChromosome chromosome) {
Map<Integer, Double> machineUtilization = new HashMap<>();
int maxEndTime = chromosome.getGenes().stream()
.mapToInt(Gene::getEndTime)
.max()
.orElse(0);
if (maxEndTime == 0) return 0.0;
for (Machine machine : _machines) {
double busyTime = chromosome.getGenes().stream()
.filter(g -> g.getMachineId() == machine.getId())
.mapToInt(g -> g.getEndTime() - g.getStartTime())
.sum();
machineUtilization.put(machine.getId(), busyTime / maxEndTime);
}
double avgUtilization = machineUtilization.values().stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
double variance = machineUtilization.values().stream()
.mapToDouble(u -> Math.pow(u - avgUtilization, 2))
.average()
.orElse(0.0);
return 1.0 / (1 + variance);
}
private void RemoveMachineAvailable(Machine machine, GeneDetail geneDetails) {
List<TimeSegment> timeSegments = new ArrayList<>();
int index = machine.getAvailability().stream()
.filter(t -> t.getKey().equals(geneDetails.getKey()))
.findFirst()
.map(machine.getAvailability()::indexOf)
.orElse(-1);
if (index > -1) {
TimeSegment targetSegment = machine.getAvailability().get(index);
LocalDateTime geneEndTime = baseTime.plusMinutes(geneDetails.getEndTime());
if (targetSegment.getEnd().isAfter(geneEndTime)) {
TimeSegment usedSegment = new TimeSegment();
usedSegment.setStart(baseTime.plusMinutes(geneDetails.getStartTime()));
usedSegment.setEnd(geneEndTime);
usedSegment.setHoliday(false);
usedSegment.setKey(UUID.randomUUID().toString());
usedSegment.setType(SegmentType.REGULAR);
usedSegment.setUsed(true);
timeSegments.add(usedSegment);
geneDetails.setKey(usedSegment.getKey());
targetSegment.setStart(geneEndTime);
} else {
targetSegment.setUsed(true);
}
}
if (!timeSegments.isEmpty()) {
machine.getAvailability().addAll(timeSegments);
}
machine.getAvailability().sort(Comparator.comparing(TimeSegment::getStart));
}
private void AddMachineAvailable(Machine machine, List<GeneDetail> geneDetails) {
if (geneDetails == null || geneDetails.isEmpty()) return;
for (GeneDetail detail : geneDetails) {
machine.getAvailability().stream()
.filter(t -> t.getKey().equals(detail.getKey()))
.findFirst()
.ifPresent(t -> t.setUsed(false));
}
machine.setAvailability(MergeSegments(machine.getAvailability()));
}
private List<TimeSegment> MergeSegments(List<TimeSegment> segments) {
List<TimeSegment> maintenanceSegments = segments.stream()
.filter(t -> t.getType() == SegmentType.MAINTENANCE)
.collect(Collectors.toList());
List<TimeSegment> unusedRegularSegments = segments.stream()
.filter(t -> t.getType() != SegmentType.MAINTENANCE && !t.isUsed())
.collect(Collectors.toList());
List<TimeSegment> usedRegularSegments = segments.stream()
.filter(t -> t.getType() != SegmentType.MAINTENANCE && t.isUsed())
.collect(Collectors.toList());
unusedRegularSegments.sort(Comparator.comparing(TimeSegment::getStart));
Deque<TimeSegment> mergedUnused = new ArrayDeque<>();
for (TimeSegment seg : unusedRegularSegments) {
if (!mergedUnused.isEmpty() && mergedUnused.peekLast().getEnd().isAfter(seg.getStart())) {
TimeSegment last = mergedUnused.pollLast();
last.setEnd(last.getEnd().isAfter(seg.getEnd()) ? last.getEnd() : seg.getEnd());
mergedUnused.offerLast(last);
} else {
mergedUnused.offerLast(seg);
}
}
List<TimeSegment> result = new ArrayList<>(mergedUnused);
result.addAll(usedRegularSegments);
result.addAll(maintenanceSegments);
result.sort(Comparator.comparing(TimeSegment::getStart));
return result;
}
private List<GeneDetail> GetNextAvailableTime(
Machine machine, int proposedStartTime, int prevtime,
int processingTime, List<Gene> existingTasks,
boolean IsInterrupt, boolean istask
) {
LocalDateTime startTime = baseTime.plusMinutes(proposedStartTime);
String prevtimeStr = prevtime > -1 ? baseTime.plusMinutes(prevtime).toString() : "";
return FindEarliestStart(machine, processingTime, startTime, prevtimeStr, existingTasks, istask);
}
private List<GeneDetail> FindEarliestStart(
Machine machine, int processingTime, LocalDateTime currentTime,
String prevtime, List<Gene> existingTasks, boolean checkprevtime
) {
List<Gene> machineTasks = existingTasks.stream()
.filter(t -> t.getMachineId() == machine.getId())
.sorted(Comparator.comparingInt(Gene::getStartTime))
.collect(Collectors.toList());
List<GeneDetail> times = new ArrayList<>();
TimeSegment slot = GetCurrentOrNextShift(machine, currentTime, prevtime, checkprevtime);
if (slot == null) return times;
LocalDateTime prevTimeDateTime = StringUtils.isEmpty(prevtime) ? null : LocalDateTime.parse(prevtime);
LocalDateTime startCandidate = slot.getStart().isAfter(prevTimeDateTime != null ? prevTimeDateTime : currentTime)
? slot.getStart()
: (prevTimeDateTime != null ? prevTimeDateTime : currentTime);
LocalDateTime endCandidate = startCandidate.plusMinutes(processingTime);
if (endCandidate.isAfter(slot.getEnd())) {
return CaldEarliestStart(machine, processingTime, currentTime, prevtime, machineTasks, checkprevtime);
} else {
GeneDetail time = new GeneDetail();
time.setKey(slot.getKey());
time.setStartTime((int) ChronoUnit.MINUTES.between(baseTime, startCandidate));
time.setEndTime((int) ChronoUnit.MINUTES.between(baseTime, endCandidate));
times.add(time);
RemoveMachineAvailable(machine, time);
return times;
}
}
private List<GeneDetail> CaldEarliestStart(
Machine machine, int processingTime, LocalDateTime currentTime,
String prevtime, List<Gene> machineTasks, boolean checkprevtime
) {
int remainingTime = processingTime;
LocalDateTime st = StringUtils.isEmpty(prevtime) ? currentTime : LocalDateTime.parse(prevtime);
LocalDateTime prevEnd = LocalDateTime.of(2000, 1, 1, 0, 0, 0);
List<GeneDetail> times = new ArrayList<>();
List<GeneDetail> oldTimes = new ArrayList<>();
while (remainingTime > 0) {
TimeSegment shift = GetCurrentOrNextShift(machine, currentTime, prevtime, checkprevtime);
if (shift == null) break;
LocalDateTime shiftStart = shift.getStart();
LocalDateTime shiftEnd = shift.getEnd();
// 检查班次间冲突
if (!prevEnd.isEqual(LocalDateTime.of(2000, 1, 1, 0, 0, 0))) {
if (prevEnd.isBefore(currentTime)) {
// 检查班次间任务
LocalDateTime finalPrevEnd = prevEnd;
boolean hasTask = machineTasks.stream()
.anyMatch(t -> {
LocalDateTime taskStart = baseTime.plusMinutes(t.getStartTime());
return taskStart.isAfter(finalPrevEnd) && taskStart.isBefore(shiftStart);
});
if (hasTask) {
// 重置状态
currentTime = shiftStart;
st = shiftStart;
remainingTime = processingTime;
prevEnd = LocalDateTime.of(2000, 1, 1, 0, 0, 0);
oldTimes.addAll(deepCopyGeneDetailList(times));
times.clear();
continue;
}
// 检查班次间维修窗口
if (machine.getMaintenanceWindows() != null) {
LocalDateTime finalPrevEnd1 = prevEnd;
boolean hasMaintenance = machine.getMaintenanceWindows().stream()
.anyMatch(w -> w.getStartTime().isAfter(finalPrevEnd1) && w.getStartTime().isBefore(shiftStart));
if (hasMaintenance) {
currentTime = shiftStart;
st = shiftStart;
remainingTime = processingTime;
prevEnd = LocalDateTime.of(2000, 1, 1, 0, 0, 0);
times.clear();
continue;
}
}
}
}
prevEnd = shiftEnd;
// 计算有效时间
LocalDateTime effectiveStart = st.isAfter(shiftStart) ? st : shiftStart;
long availableMinutes = ChronoUnit.MINUTES.between(effectiveStart, shiftEnd);
// 处理当前班次
int processable = Math.min(remainingTime, (int) availableMinutes);
remainingTime -= processable;
currentTime = effectiveStart.plusMinutes(processable);
// 添加时间详情
GeneDetail time = new GeneDetail();
time.setKey(shift.getKey());
time.setStartTime((int) ChronoUnit.MINUTES.between(baseTime, effectiveStart));
time.setEndTime((int) ChronoUnit.MINUTES.between(baseTime, currentTime));
times.add(time);
RemoveMachineAvailable(machine, time);
}
// 还原未使用的时间段
AddMachineAvailable(machine, oldTimes);
return times;
}
private TimeSegment GetCurrentOrNextShift(Machine machine, LocalDateTime time, String prevtime, boolean checkprevtime) {
TimeSegment start = null;
// 查找有效班次
start = machine.getAvailability().stream()
.filter(slot -> !slot.isUsed() && slot.getType() != SegmentType.MAINTENANCE)
.filter(slot -> slot.getStart().isAfter(time) || slot.getEnd().isAfter(time))
.findFirst()
.orElse(null);
if (start == null) {
// 生成新时间段
List<TimeSegment> timeSegments = _machineScheduler.generateTimeSegment(machine, time);
machine.getAvailability().addAll(timeSegments);
// 更新设备时间线
Machine originalMachine = _machines.stream()
.filter(t -> t.getId() == machine.getId())
.findFirst()
.orElse(null);
if (originalMachine != null) {
MachineTimeline timeline = _machineScheduler.getOrCreateTimeline(originalMachine);
}
// 递归查找
return GetCurrentOrNextShift(machine, time, prevtime, checkprevtime);
}
return start;
}
public void PrintScheduleSummary(ScheduleChromosome schedule) {
System.out.println("\n=== Schedule Summary ===");
// 按订单分组打印
Map<Integer, List<Gene>> orderGroups = schedule.getGenes().stream()
.collect(Collectors.groupingBy(Gene::getOrderId));
for (Map.Entry<Integer, List<Gene>> group : orderGroups.entrySet()) {
System.out.printf("\nOrder %d Schedule:%n", group.getKey());
// 按工序排序
List<Gene> sortedJobs = group.getValue().stream()
.sorted(Comparator.comparingInt(Gene::getOperationId))
.collect(Collectors.toList());
for (Gene job : sortedJobs) {
Product product = _products.stream()
.filter(p -> p.getId() == job.getProductId())
.findFirst()
.orElse(null);
Operation operation = product != null ? product.getOperations().stream()
.filter(o -> o.getId() == job.getOperationId())
.findFirst()
.orElse(null) : null;
if (product != null && operation != null) {
String print = String.format(
"[%s-%s] Order %d, Product %s, Machine %d, Operation %d, Batch %d, " +
"processingTime %d, preTime %d, setupTime %d, teardownTime %d, " +
"实际加工时间: %d分钟",
ConvertTime(job.getStartTime()),
ConvertTime(job.getEndTime()),
job.getOrderId(),
product.getName(),
job.getMachineId(),
operation.getSequence(),
job.getBatchSize(),
job.getProcessingTime(),
job.getPreTime(),
job.getSetupTime(),
job.getTeardownTime(),
(job.getEndTime() - job.getStartTime()) -
(job.getPreTime() + job.getTeardownTime() + job.getSetupTime())
);
System.out.println(print);
}
}
}
}
public void WriteScheduleSummary(ScheduleChromosome schedule) {
// 写入日志
FileHelper.writeLogFile(String.format("\n=== Schedule Summary === 适应度: %f", schedule.getFitness()));
FileHelper.writeLogFile(String.format("最大完工时间: %f 分钟", schedule.getObjectiveValues().get(0)));
FileHelper.writeLogFile(String.format("总延迟时间: %f 小时", schedule.getObjectiveValues().get(1)));
FileHelper.writeLogFile(String.format("总换型时间: %f 分钟", schedule.getObjectiveValues().get(2)));
FileHelper.writeLogFile(String.format("总流程时间: %f 分钟", schedule.getObjectiveValues().get(3)));
FileHelper.writeLogFile(String.format("机器负载均衡: %.2f%%", schedule.getObjectiveValues().get(4) * 100));
FileHelper.writeLogFile("-------------------------");
// 按订单分组写入
Map<Integer, List<Gene>> orderGroups = schedule.getGenes().stream()
.collect(Collectors.groupingBy(Gene::getOrderId));
for (Map.Entry<Integer, List<Gene>> group : orderGroups.entrySet()) {
FileHelper.writeLogFile(String.format("\n订单 %d 的调度安排:", group.getKey()));
List<Gene> sortedJobs = group.getValue().stream()
.sorted(Comparator.comparingInt(Gene::getOperationId))
.collect(Collectors.toList());
for (Gene job : sortedJobs) {
Product product = _products.stream()
.filter(p -> p.getId() == job.getProductId())
.findFirst()
.orElse(null);
Operation operation = product != null ? product.getOperations().stream()
.filter(o -> o.getId() == job.getOperationId())
.findFirst()
.orElse(null) : null;
if (product != null && operation != null) {
StringBuilder sb = new StringBuilder();
sb.append(String.format(
"[%d-%d]:[%s-%s] 订单%d, 产品%s, 设备%d, 工序%d, 批次%d, " +
"加工%d分钟, 前处理%d分钟, 换型%d分钟, 后处理%d分钟, 总时长%d分钟",
job.getStartTime(),
job.getEndTime(),
ConvertTime(job.getStartTime()),
ConvertTime(job.getEndTime()),
job.getOrderId(),
product.getName(),
job.getMachineId(),
operation.getSequence(),
job.getBatchSize(),
job.getProcessingTime(),
job.getPreTime(),
job.getSetupTime(),
job.getTeardownTime(),
job.getEndTime() - job.getStartTime()
));
// 追加基因详情
for (GeneDetail d : job.getGeneDetails()) {
sb.append(String.format(
"\n\t\t\t\t\t\t\t\t\t\t [%d-%d]:[%s-%s] 时长%d分钟",
d.getStartTime(),
d.getEndTime(),
ConvertTime(d.getStartTime()),
ConvertTime(d.getEndTime()),
d.getEndTime() - d.getStartTime()
));
}
FileHelper.writeLogFile(sb.toString());
}
}
FileHelper.writeLogFile("");
}
}
private String ConvertTime(int minute) {
return baseTime.plusMinutes(minute).format(java.time.format.DateTimeFormatter.ofPattern("MM-dd HH:mm"));
}
// ========================== 深拷贝工具方法 ==========================
private List<Product> deepCopyProductList(List<Product> source) {
return source == null ? new ArrayList<>() : source.stream().map(this::deepCopyProduct).collect(Collectors.toList());
}
private Product deepCopyProduct(Product source) {
if (source == null) return null;
Product copy = new Product();
copy.setId(source.getId());
copy.setName(source.getName());
copy.setOperations(deepCopyOperationList(source.getOperations()));
return copy;
}
private List<Operation> deepCopyOperationList(List<Operation> source) {
return source == null ? new ArrayList<>() : source.stream().map(this::deepCopyOperation).collect(Collectors.toList());
}
private Operation deepCopyOperation(Operation source) {
if (source == null) return null;
Operation copy = new Operation();
copy.setId(source.getId());
copy.setProductId(source.getProductId());
copy.setSequence(source.getSequence());
copy.setInterrupt(source.isInterrupt());
copy.setMachineOptions(deepCopyMachineOptionList(source.getMachineOptions()));
return copy;
}
private List<MachineOption> deepCopyMachineOptionList(List<MachineOption> source) {
return source == null ? new ArrayList<>() : source.stream().map(this::deepCopyMachineOption).collect(Collectors.toList());
}
private MachineOption deepCopyMachineOption(MachineOption source) {
if (source == null) return null;
MachineOption copy = new MachineOption();
copy.setMachineId(source.getMachineId());
copy.setProcessingTime(source.getProcessingTime());
copy.setPreTime(source.getPreTime());
copy.setSetupTime(source.getSetupTime());
copy.setTeardownTime(source.getTeardownTime());
return copy;
}
private List<Machine> deepCopyMachineList(List<Machine> source) {
return source == null ? new ArrayList<>() : source.stream().map(this::deepCopyMachine).collect(Collectors.toList());
}
private Machine deepCopyMachine(Machine source) {
if (source == null) return null;
Machine copy = new Machine();
copy.setId(source.getId());
copy.setName(source.getName());
copy.setEarliestTime(source.getEarliestTime());
copy.setTotalTaskTime(source.getTotalTaskTime());
copy.setShifts(deepCopyShiftList(source.getShifts()));
copy.setMaintenanceWindows(deepCopyMaintenanceWindowList(source.getMaintenanceWindows()));
copy.setAvailability(deepCopyTimeSegmentList(source.getAvailability()));
copy.setShiftsChanged(source.getShiftsChanged());
copy.setMaintenanceWindowsChanged(source.getMaintenanceWindowsChanged());
return copy;
}
private List<Shift> deepCopyShiftList(List<Shift> source) {
return source == null ? new ArrayList<>() : source.stream().map(this::deepCopyShift).collect(Collectors.toList());
}
private Shift deepCopyShift(Shift source) {
if (source == null) return null;
Shift copy = new Shift();
copy.setStartTime(source.getStartTime());
copy.setEndTime(source.getEndTime());
copy.setDays(source.getDays() != null ? new HashSet<>(source.getDays()) : null);
copy.setShiftDate(source.getShiftDate());
copy.setTemporaryShift(source.isTemporaryShift());
copy.setPriority(source.getPriority());
copy.setStatus(source.getStatus());
copy.setMachineId(source.getMachineId());
copy.setMachineName(source.getMachineName());
return copy;
}
private List<MaintenanceWindow> deepCopyMaintenanceWindowList(List<MaintenanceWindow> source) {
return source == null ? new ArrayList<>() : source.stream().map(this::deepCopyMaintenanceWindow).collect(Collectors.toList());
}
private MaintenanceWindow deepCopyMaintenanceWindow(MaintenanceWindow source) {
if (source == null) return null;
MaintenanceWindow copy = new MaintenanceWindow();
copy.setStartTime(source.getStartTime());
copy.setEndTime(source.getEndTime());
copy.setReason(source.getReason());
return copy;
}
private List<TimeSegment> deepCopyTimeSegmentList(List<TimeSegment> source) {
return source == null ? new ArrayList<>() : source.stream().map(this::deepCopyTimeSegment).collect(Collectors.toList());
}
private TimeSegment deepCopyTimeSegment(TimeSegment source) {
if (source == null) return null;
TimeSegment copy = new TimeSegment();
copy.setKey(source.getKey());
copy.setStart(source.getStart());
copy.setEnd(source.getEnd());
copy.setEarliestTime(source.getEarliestTime());
copy.setTotalTaskTime(source.getTotalTaskTime());
copy.setType(source.getType());
copy.setHoliday(source.isHoliday());
copy.setUsed(source.isUsed());
return copy;
}
private List<Order> deepCopyOrderList(List<Order> source) {
return source == null ? new ArrayList<>() : source.stream().map(this::deepCopyOrder).collect(Collectors.toList());
}
private Order deepCopyOrder(Order source) {
if (source == null) return null;
Order copy = new Order();
copy.setId(source.getId());
copy.setProductId(source.getProductId());
copy.setQuantity(source.getQuantity());
copy.setDueDate(source.getDueDate());
copy.setOrderCompletion(source.getOrderCompletion());
copy.setTardiness(source.getTardiness());
copy.setPriority(source.getPriority());
copy.setCanSplit(source.isCanSplit());
copy.setCanInterrupt(source.isCanInterrupt());
return copy;
}
private List<Gene> deepCopyGeneList(List<Gene> source) {
return source == null ? new ArrayList<>() : source.stream().map(this::deepCopyGene).collect(Collectors.toList());
}
private Gene deepCopyGene(Gene source) {
if (source == null) return null;
Gene copy = new Gene();
copy.setOrderId(source.getOrderId());
copy.setProductId(source.getProductId());
copy.setOperationId(source.getOperationId());
copy.setMachineId(source.getMachineId());
copy.setBatchSize(source.getBatchSize());
copy.setStartTime(source.getStartTime());
copy.setEndTime(source.getEndTime());
copy.setProcessingTime(source.getProcessingTime());
copy.setPreTime(source.getPreTime());
copy.setSetupTime(source.getSetupTime());
copy.setTeardownTime(source.getTeardownTime());
copy.setOperationName(source.getOperationName());
copy.setGeneDetails(deepCopyGeneDetailList(source.getGeneDetails()));
return copy;
}
private List<GeneDetail> deepCopyGeneDetailList(List<GeneDetail> source) {
return source == null ? new ArrayList<>() : source.stream().map(this::deepCopyGeneDetail).collect(Collectors.toList());
}
private GeneDetail deepCopyGeneDetail(GeneDetail source) {
if (source == null) return null;
GeneDetail copy = new GeneDetail();
copy.setKey(source.getKey());
copy.setStartTime(source.getStartTime());
copy.setEndTime(source.getEndTime());
return copy;
}
private ScheduleChromosome deepCopyScheduleChromosome(ScheduleChromosome source) {
if (source == null) return null;
ScheduleChromosome copy = new ScheduleChromosome(source.getBaseTime());
copy.setGenes(deepCopyGeneList(source.getGenes()));
copy.setOrders(deepCopyOrderList(source.getOrders()));
copy.setMachines(deepCopyMachineList(source.getMachines()));
copy.setObjectiveValues(new HashMap<>(source.getObjectiveValues()));
copy.setFitness(source.getFitness());
copy.setTardiness(source.getTardiness());
return copy;
}
// ========================== 辅助类:StringUtils ==========================
private static class StringUtils {
public static boolean isEmpty(String str) {
return str == null || str.trim().isEmpty();
}
}
/**
* 计算绝对开始时间(考虑设备日历+前处理+换型时间,跨时段自动拆分)
*/
/**
* 计算绝对开始时间(完善边界处理:结束点自动顺延)
*/
/**
* 计算绝对开始时间(前处理+换型时间不跨设备非工作时段,仅占用当前可用时段)
* 核心:前处理时间最多用到当前可用时段结束点,不顺延到下一个时段
*/
private int calculateAbsoluteStartTime(Gene gene, MachineOption machineOption) {
int startTime = gene.getStartTime(); // 基因开始时间(相对baseTime分钟数)
int setupTime = gene.getSetupTime(); // 换型时间(分钟)
int preTime = gene.getPreTime(); // 前处理时间(分钟)
int totalPrepTime = setupTime + preTime;
if (totalPrepTime <= 0) {
// 无准备时间,直接返回基因开始时间(按日历调整边界)
return adjustTimeByMachineCalendar(gene.getMachineId(), startTime, true);
}
// 获取目标设备(深拷贝,避免污染原状态)
Machine targetMachine = deepCopyMachine(
_machines.stream()
.filter(m -> m.getId() == gene.getMachineId())
.findFirst()
.orElseThrow(() -> new NoSuchElementException("Machine not found: " + gene.getMachineId()))
);
// 步骤1:找到基因开始时间所在的“当前可用时段”
LocalDateTime geneStartDateTime = baseTime.plusMinutes(startTime);
TimeSegment currentAvailableSegment = findCurrentAvailableSegment(targetMachine, geneStartDateTime);
if (currentAvailableSegment == null) {
// 无当前可用时段,兜底返回原计算值
int fallbackTime = adjustTimeByMachineCalendar(gene.getMachineId(), startTime, true);
System.out.printf("设备%d:无当前可用时段,兜底绝对开始时间=%d(%s)%n",
targetMachine.getId(), fallbackTime, ConvertTime(fallbackTime));
return fallbackTime;
}
// 步骤2:计算当前可用时段的“剩余时间”(从基因开始时间到时段结束点)
long segmentRemainingMinutes = ChronoUnit.MINUTES.between(
geneStartDateTime,
currentAvailableSegment.getEnd()
);
int usablePrepTime = (int) Math.min(totalPrepTime, segmentRemainingMinutes);
// 步骤3:绝对开始时间 = 基因开始时间 + 实际可用的准备时间(不跨时段)
int absoluteStartTime = startTime + usablePrepTime;
// 步骤4:边界校验(确保不超过当前时段结束点)
LocalDateTime absoluteStartDateTime = baseTime.plusMinutes(absoluteStartTime);
if (absoluteStartDateTime.isAfter(currentAvailableSegment.getEnd())) {
absoluteStartTime = (int) ChronoUnit.MINUTES.between(baseTime, currentAvailableSegment.getEnd());
}
// 步骤5:最终按日历调整(避免落在时段结束点)
absoluteStartTime = adjustTimeByMachineCalendar(gene.getMachineId(), absoluteStartTime, true);
return absoluteStartTime;
}
/**
* 辅助方法:找到指定时间所在的“当前可用时段”(未占用、非维修、包含该时间)
*/
private TimeSegment findCurrentAvailableSegment(Machine machine, LocalDateTime targetTime) {
return machine.getAvailability().stream()
.filter(segment -> !segment.isUsed()) // 未被占用
.filter(segment -> segment.getType() != SegmentType.MAINTENANCE) // 非维修时段
.filter(segment -> {
// 包含目标时间(且目标时间不是时段结束点)
boolean isAfterStart = !targetTime.isBefore(segment.getStart());
boolean isBeforeEnd = targetTime.isBefore(segment.getEnd());
return isAfterStart && isBeforeEnd;
})
.findFirst()
.orElse(null);
}
/**
* 计算绝对结束时间(考虑设备日历+后处理时间)
*/
/**
* 计算绝对结束时间(完善边界处理)
*/
/**
* 计算绝对结束时间(正确逻辑:基因结束时间 - 后处理时间 + 设备日历调整)
* 绝对结束时间 = 加工完成时间(后处理开始前的时间)
*/
private int calculateAbsoluteEndTime(Gene gene, MachineOption machineOption) {
int geneEndTime = gene.getEndTime(); // 基因的结束时间(含后处理,相对baseTime的分钟数)
int teardownTime = gene.getTeardownTime(); // 后处理时间(分钟,加工完成后执行)
// 无后处理时间:绝对结束时间=基因结束时间(直接按日历调整)
if (teardownTime <= 0) {
return adjustTimeByMachineCalendar(gene.getMachineId(), geneEndTime, false);
}
// 核心:加工完成时间 = 基因结束时间 - 后处理时间(后处理在加工完成后执行)
int processCompletionTime = geneEndTime - teardownTime;
// 校验:加工完成时间不能早于基因开始时间(避免逻辑矛盾)
if (processCompletionTime < gene.getStartTime()) {
System.out.printf("设备%d:订单%d工序%d - 后处理时间%d分钟过长,基因结束时间%d < 开始时间%d,兜底为基因开始时间%n",
gene.getMachineId(), gene.getOrderId(), gene.getOperationId(),
teardownTime, geneEndTime, gene.getStartTime());
processCompletionTime = gene.getStartTime();
}
// 按设备日历调整(处理非工作时段、结束点边界)
int absoluteEndTime = adjustTimeByMachineCalendar(gene.getMachineId(), processCompletionTime, false);
// 最终校验:调整后的绝对结束时间不能早于基因开始时间
absoluteEndTime = Math.max(absoluteEndTime, gene.getStartTime());
// 日志输出(清晰展示计算链路)
System.out.printf("设备%d:基因结束时间=%d(%s),后处理%d分钟 → 加工完成时间=%d(%s)→ 调整后绝对结束时间=%d(%s)%n",
gene.getMachineId(),
geneEndTime, ConvertTime(geneEndTime),
teardownTime,
processCompletionTime, ConvertTime(processCompletionTime),
absoluteEndTime, ConvertTime(absoluteEndTime));
return absoluteEndTime;
}
/**
* 根据设备日历调整时间(处理边界情况:时间=时段结束点→顺延到下一个可用时段)
*/
private int adjustTimeByMachineCalendar(Machine machine, int proposedTime, boolean isStartTime) {
LocalDateTime proposedDateTime = baseTime.plusMinutes(proposedTime);
// 查找包含提议时间的可用时段(排除维修时段、已占用时段)
TimeSegment availableSegment = machine.getAvailability().stream()
.filter(segment -> !segment.isUsed())
.filter(segment -> segment.getType() != SegmentType.MAINTENANCE)
.filter(segment -> {
// 关键修改:包含「时段内」但排除「时段结束点」(结束点视为不可用)
boolean isAfterStart = !proposedDateTime.isBefore(segment.getStart());
boolean isBeforeEnd = proposedDateTime.isBefore(segment.getEnd()); // 用isBefore替代!isAfter
return isAfterStart && isBeforeEnd;
})
.findFirst()
.orElse(null);
if (availableSegment != null) {
return proposedTime; // 时间在可用时段内(非结束点),无需调整
}
// 情况1:提议时间是开始时间 → 查找下一个可用时段的开始时间
if (isStartTime) {
TimeSegment nextSegment = machine.getAvailability().stream()
.filter(segment -> !segment.isUsed())
.filter(segment -> segment.getType() != SegmentType.MAINTENANCE)
.filter(segment -> segment.getStart().isAfter(proposedDateTime))
.min(Comparator.comparing(TimeSegment::getStart))
.orElse(null);
if (nextSegment != null) {
int adjustedTime = (int) ChronoUnit.MINUTES.between(baseTime, nextSegment.getStart());
System.out.printf("设备%d:开始时间%d(%s)是时段结束点/非可用时间,顺延到下一个可用时段开始时间%d(%s)%n",
machine.getId(), proposedTime, ConvertTime(proposedTime),
adjustedTime, ConvertTime(adjustedTime));
return adjustedTime;
}
}
// 情况2:提议时间是结束时间 → 查找前一个可用时段的结束时间(避免跨时段)
else {
TimeSegment prevSegment = machine.getAvailability().stream()
.filter(segment -> !segment.isUsed())
.filter(segment -> segment.getType() != SegmentType.MAINTENANCE)
.filter(segment -> segment.getEnd().isBefore(proposedDateTime))
.max(Comparator.comparing(TimeSegment::getEnd))
.orElse(null);
if (prevSegment != null) {
return (int) ChronoUnit.MINUTES.between(baseTime, prevSegment.getEnd());
}
}
// 兜底:无可用时段时返回原时间(理论上不会发生)
return proposedTime;
}
/**
* 按设备ID查询可用时间(复用adjustTimeByMachineCalendar逻辑)
*/
private int adjustTimeByMachineCalendar(int machineId, int proposedTime, boolean isStartTime) {
Machine machine = _machines.stream()
.filter(m -> m.getId() == machineId)
.findFirst()
.orElse(null);
if (machine == null) {
return proposedTime;
}
return adjustTimeByMachineCalendar(machine, proposedTime, isStartTime);
}
/**
* 计算绝对准备时间(考虑设备日历,跳过非工作时间)
*/
private int calculateAbsolutePreparationTime(Gene gene, MachineOption machineOption) {
int totalPreparationTime = gene.getPreTime(); // 总准备时间
if (totalPreparationTime <= 0) {
return 0; // 没有准备时间
}
// 获取设备
Machine machine = _machines.stream()
.filter(m -> m.getId() == gene.getMachineId())
.findFirst()
.orElse(null);
if (machine == null) {
return totalPreparationTime; // 找不到设备,返回原始时间
}
// 开始时间(基因开始时间)
int startTime = gene.getStartTime();
LocalDateTime currentTime = baseTime.plusMinutes(startTime);
// 计算考虑日历的实际准备时间
return calculateCalendarAdjustedTime(machine, currentTime, totalPreparationTime);
}
/**
* 计算绝对换型时间(考虑设备日历,跳过非工作时间)
*/
private int calculateAbsoluteSetupTime(Gene gene, MachineOption machineOption) {
int totalPreparationTime = gene.getSetupTime(); // 总准备时间
if (totalPreparationTime <= 0) {
return 0; // 没有准备时间
}
// 获取设备
Machine machine = _machines.stream()
.filter(m -> m.getId() == gene.getMachineId())
.findFirst()
.orElse(null);
if (machine == null) {
return totalPreparationTime; // 找不到设备,返回原始时间
}
// 开始时间(基因开始时间)
int startTime = gene.getStartTime();
LocalDateTime currentTime = baseTime.plusMinutes(startTime);
// 计算考虑日历的实际准备时间
return calculateCalendarAdjustedTime(machine, currentTime, totalPreparationTime);
}
/**
* 计算考虑设备日历的时间(跳过非工作时间)
*/
private int calculateCalendarAdjustedTime(Machine machine, LocalDateTime startTime, int requiredMinutes) {
LocalDateTime currentTime = startTime;
int remainingMinutes = requiredMinutes;
int totalElapsedMinutes = 0; // 实际经过的分钟数(包含非工作时间)
System.out.printf(" 开始计算: %s, 需要%d分钟%n",
startTime.format(java.time.format.DateTimeFormatter.ofPattern("MM-dd HH:mm")),
requiredMinutes);
while (remainingMinutes > 0) {
// 查找包含当前时间的可用时间段
TimeSegment currentSegment = findAvailableSegmentAtTime(machine, currentTime);
if (currentSegment == null) {
// 没有找到可用时间段,查找下一个可用时间段
TimeSegment nextSegment = findNextAvailableSegment(machine, currentTime);
if (nextSegment == null) {
System.out.printf(" 警告: 找不到后续可用时间段,剩余%d分钟无法安排%n", remainingMinutes);
break; // 无法安排剩余时间
}
// 跳到下一个可用时间段的开始
long jumpMinutes = ChronoUnit.MINUTES.between(currentTime, nextSegment.getStart());
totalElapsedMinutes += (int) jumpMinutes;
currentTime = nextSegment.getStart();
System.out.printf(" 跳过非工作时间: 跳转%d分钟到 %s%n",
jumpMinutes, currentTime.format(java.time.format.DateTimeFormatter.ofPattern("MM-dd HH:mm")));
continue;
}
// 计算当前时间段可用的分钟数
long availableMinutesInSegment = ChronoUnit.MINUTES.between(currentTime, currentSegment.getEnd());
int processableMinutes = (int) Math.min(remainingMinutes, availableMinutesInSegment);
// 处理当前时间段
remainingMinutes -= processableMinutes;
totalElapsedMinutes += processableMinutes;
currentTime = currentTime.plusMinutes(processableMinutes);
System.out.printf(" 在当前时间段处理%d分钟: %s → %s, 剩余%d分钟%n",
processableMinutes,
currentTime.minusMinutes(processableMinutes).format(java.time.format.DateTimeFormatter.ofPattern("MM-dd HH:mm")),
currentTime.format(java.time.format.DateTimeFormatter.ofPattern("MM-dd HH:mm")),
remainingMinutes);
// 如果当前时间段用完但还有剩余时间,移动到时间段结束点
if (remainingMinutes > 0 && currentTime.equals(currentSegment.getEnd())) {
// 查找下一个时间段
TimeSegment nextSegment = findNextAvailableSegment(machine, currentTime);
if (nextSegment == null) {
System.out.printf(" 警告: 当前时间段用完,但找不到后续时间段,剩余%d分钟无法安排%n", remainingMinutes);
break;
}
// 跳到下一个时间段的开始
long jumpMinutes = ChronoUnit.MINUTES.between(currentTime, nextSegment.getStart());
totalElapsedMinutes += (int) jumpMinutes;
currentTime = nextSegment.getStart();
System.out.printf(" 时间段结束,跳转到下一个: 跳转%d分钟到 %s%n",
jumpMinutes, currentTime.format(java.time.format.DateTimeFormatter.ofPattern("MM-dd HH:mm")));
}
}
System.out.printf(" 计算完成: 原始需要%d分钟,实际经过%d分钟%n", requiredMinutes, totalElapsedMinutes);
return totalElapsedMinutes;
}
/**
* 查找包含指定时间的可用时间段
*/
private TimeSegment findAvailableSegmentAtTime(Machine machine, LocalDateTime time) {
return machine.getAvailability().stream()
.filter(segment -> !segment.isUsed())
.filter(segment -> segment.getType() != SegmentType.MAINTENANCE)
.filter(segment ->
!segment.getStart().isAfter(time) &&
segment.getEnd().isAfter(time))
.findFirst()
.orElse(null);
}
/**
* 查找下一个可用时间段
*/
private TimeSegment findNextAvailableSegment(Machine machine, LocalDateTime afterTime) {
return machine.getAvailability().stream()
.filter(segment -> !segment.isUsed())
.filter(segment -> segment.getType() != SegmentType.MAINTENANCE)
.filter(segment -> segment.getStart().isAfter(afterTime))
.min(Comparator.comparing(TimeSegment::getStart))
.orElse(null);
}
/**
* 反向查找:找到包含指定时间的可用工作时段(用于向前追溯)
*/
private TimeSegment findAvailableSegmentAtTimeReverse(Machine machine, LocalDateTime time) {
return machine.getAvailability().stream()
.filter(segment -> !segment.isUsed())
.filter(segment -> segment.getType() != SegmentType.MAINTENANCE)
.filter(segment -> !segment.isHoliday())
.filter(segment -> {
boolean isAfterStart = !time.isBefore(segment.getStart());
boolean isBeforeEnd = time.isBefore(segment.getEnd());
return isAfterStart && isBeforeEnd;
})
.findFirst()
.orElse(null);
}
/**
* 查找前一个可用时间段
*/
private TimeSegment findPreviousAvailableSegment(Machine machine, LocalDateTime beforeTime) {
return machine.getAvailability().stream()
.filter(segment -> !segment.isUsed())
.filter(segment -> segment.getType() != SegmentType.MAINTENANCE)
.filter(segment -> segment.getEnd().isBefore(beforeTime))
.max(Comparator.comparing(TimeSegment::getEnd))
.orElse(null);
}
/**
* 计算绝对后处理时间(从结束时间向前计算,考虑设备日历,跳过非工作时间)
* 后处理时间是从加工完成时间向前安排的
*/
private int calculateAbsoluteTeardownTime(Gene gene, MachineOption machineOption) {
int teardownTime = gene.getTeardownTime(); // 后处理时间
if (teardownTime <= 0) {
return 0; // 没有后处理时间
}
// 获取设备
Machine machine = _machines.stream()
.filter(m -> m.getId() == gene.getMachineId())
.findFirst()
.orElse(null);
if (machine == null) {
return teardownTime; // 找不到设备,返回原始时间
}
// 后处理结束时间 = 基因结束时间(包含后处理的时间)
int endTime = gene.getEndTime();
LocalDateTime endDateTime = baseTime.plusMinutes(endTime);
System.out.printf("计算绝对后处理时间 - 设备%d: 结束时间=%d (%s), 后处理时间=%d分钟%n",
machine.getId(), endTime, ConvertTime(endTime), teardownTime);
// 计算考虑日历的实际后处理时间(向前计算)
return calculateCalendarAdjustedTimeBackward(machine, endDateTime, teardownTime);
}
/**
* 向后计算考虑设备日历的时间(从结束时间向前回溯,跳过非工作时间)
*/
private int calculateCalendarAdjustedTimeBackward(Machine machine, LocalDateTime endTime, int requiredMinutes) {
LocalDateTime currentTime = endTime;
int remainingMinutes = requiredMinutes;
int totalElapsedMinutes = 0; // 实际经过的分钟数(包含非工作时间)
System.out.printf(" 开始向后计算后处理时间: 结束时间=%s, 需要%d分钟%n",
endTime.format(java.time.format.DateTimeFormatter.ofPattern("MM-dd HH:mm")),
requiredMinutes);
while (remainingMinutes > 0) {
// 查找包含当前时间的可用时间段(向前查找)
TimeSegment currentSegment = findAvailableSegmentAtTimeBackward(machine, currentTime);
if (currentSegment == null) {
// 没有找到可用时间段,查找前一个可用时间段
TimeSegment prevSegment = findPreviousAvailableSegment(machine, currentTime);
if (prevSegment == null) {
System.out.printf(" 警告: 找不到前续可用时间段,剩余%d分钟无法安排%n", remainingMinutes);
break; // 无法安排剩余时间
}
// 跳到前一个可用时间段的结束时间
long jumpMinutes = ChronoUnit.MINUTES.between(prevSegment.getEnd(), currentTime);
totalElapsedMinutes += (int) jumpMinutes;
currentTime = prevSegment.getEnd();
System.out.printf(" 跳过非工作时间: 向后跳转%d分钟到 %s%n",
jumpMinutes, currentTime.format(java.time.format.DateTimeFormatter.ofPattern("MM-dd HH:mm")));
continue;
}
// 计算当前时间段可用的分钟数(向前计算)
long availableMinutesInSegment = ChronoUnit.MINUTES.between(currentSegment.getStart(), currentTime);
int processableMinutes = (int) Math.min(remainingMinutes, availableMinutesInSegment);
// 处理当前时间段(向前移动)
remainingMinutes -= processableMinutes;
totalElapsedMinutes += processableMinutes;
currentTime = currentTime.minusMinutes(processableMinutes);
System.out.printf(" 在当前时间段处理%d分钟: %s ← %s, 剩余%d分钟%n",
processableMinutes,
currentTime.format(java.time.format.DateTimeFormatter.ofPattern("MM-dd HH:mm")),
currentTime.plusMinutes(processableMinutes).format(java.time.format.DateTimeFormatter.ofPattern("MM-dd HH:mm")),
remainingMinutes);
// 如果当前时间段用完但还有剩余时间,移动到时间段开始点
if (remainingMinutes > 0 && currentTime.equals(currentSegment.getStart())) {
// 查找前一个时间段
TimeSegment prevSegment = findPreviousAvailableSegment(machine, currentTime);
if (prevSegment == null) {
System.out.printf(" 警告: 当前时间段用完,但找不到前续时间段,剩余%d分钟无法安排%n", remainingMinutes);
break;
}
// 跳到前一个时间段的结束时间
long jumpMinutes = ChronoUnit.MINUTES.between(prevSegment.getEnd(), currentTime);
totalElapsedMinutes += (int) jumpMinutes;
currentTime = prevSegment.getEnd();
System.out.printf(" 时间段开始,跳转到前一个: 向后跳转%d分钟到 %s%n",
jumpMinutes, currentTime.format(java.time.format.DateTimeFormatter.ofPattern("MM-dd HH:mm")));
}
}
System.out.printf(" 后处理时间计算完成: 原始需要%d分钟,实际经过%d分钟%n", requiredMinutes, totalElapsedMinutes);
return totalElapsedMinutes;
}
/**
* 向后查找包含指定时间的可用时间段
*/
private TimeSegment findAvailableSegmentAtTimeBackward(Machine machine, LocalDateTime time) {
return machine.getAvailability().stream()
.filter(segment -> !segment.isUsed())
.filter(segment -> segment.getType() != SegmentType.MAINTENANCE)
.filter(segment -> {
// 包含目标时间(且目标时间不是时段开始点)
boolean isAfterStart = time.isAfter(segment.getStart());
boolean isBeforeEnd = !time.isAfter(segment.getEnd()); // 用!isAfter替代isBefore
return isAfterStart && isBeforeEnd;
})
.findFirst()
.orElse(null);
}
}
\ No newline at end of file
package com.aps.service.plan;
import com.aps.common.util.JsonFileReader;
import com.aps.controller.gantt.FileUploadController;
import com.aps.entity.Schedule.GenVO;
import com.aps.entity.Schedule.MachineVO;
import com.aps.entity.basic.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class PlanResultService {
// 注入FileUploadController
@Autowired
private FileUploadController fileUploadController;
private final LocalDateTime baseTime = LocalDateTime.of(2025, 10, 1, 0, 0, 0);
public List<ScheduleChromosome> execute() {
try {
// 1. 读取数据
List<Machine> machines = loadData("machines.json", Machine.class);
List<Product> products = loadData("products.json", Product.class);
List<Order> orders = loadData("orders.json", Order.class);
// 设置机器信息到班次中
for (Machine machine : machines) {
if (machine.getShifts() != null) {
for (Shift shift : machine.getShifts()) {
shift.setMachineId(machine.getId());
shift.setMachineName(machine.getName());
}
}
// 调试:打印机器和班次信息
System.out.println("Machine: " + machine.getId() + ", Name: " + machine.getName());
if (machine.getShifts() != null) {
for (Shift shift : machine.getShifts()) {
System.out.println(" Shift: " + shift.getStartTime() + " - " + shift.getEndTime() +
", Status: " + shift.getStatus() +
", MachineId: " + shift.getMachineId() +
", MachineName: " + shift.getMachineName());
}
}
}
// 创建节假日
List<Holiday> holidays = Arrays.asList(
new Holiday(LocalDateTime.of(2025, 10, 1, 0, 0),
LocalDateTime.of(2025, 10, 7, 23, 59))
);
// 将节假日添加到所有设备中
addHolidaysToAllMachines(machines, holidays);
// 3. 创建调度服务
MachineSchedulerService machineScheduler = new MachineSchedulerService(
holidays, LocalDateTime.of(2025, 10, 1, 0, 0, 0));
// 4. 初始化机器时间线
for (Machine machine : machines) {
MachineTimeline timeline = machineScheduler.getOrCreateTimeline(machine);
machine.setAvailability(timeline.getSegments());
}
// 5. 执行调度算法
AlgorithmScheduler7 scheduler = new AlgorithmScheduler7(products, machines, orders, machineScheduler);
List<ScheduleChromosome> scheduleChromosomes = scheduler.RunAll();
// 对调度结果按照 fitness 由高到低排序
scheduleChromosomes.sort((c1, c2) -> Double.compare(c2.getFitness(), c1.getFitness()));
// 为每个 ScheduleChromosome 分配场景ID(基于排序后的位置)
for (int i = 0; i < scheduleChromosomes.size(); i++) {
scheduleChromosomes.get(i).setSceneId(i + 1); // 场景ID从1开始
}
return scheduleChromosomes;
} catch (Exception e) {
throw new RuntimeException("调度执行失败", e);
}
}
/**
* 加载数据,优先从上传文件夹加载,如果不存在则从resources加载
*
* @param fileName 文件名
* @param clazz 类型
* @param <T> 泛型
* @return 数据列表
* @throws IOException IO异常
*/
private <T> List<T> loadData(String fileName, Class<T> clazz) throws IOException {
// 检查是否有上传的文件
if (fileUploadController.isFileUploaded(fileName)) {
String filePath = fileUploadController.getUploadedFilePath(fileName);
return JsonFileReader.readListFromFile(filePath, clazz);
} else {
// 使用默认的resources文件
return JsonFileReader.readListFromResources(fileName, clazz);
}
}
/**
* 将假期添加到所有设备的维护窗口中
* @param machines 设备列表
* @param holidays 假期列表
*/
private void addHolidaysToAllMachines(List<Machine> machines, List<Holiday> holidays) {
if (machines == null || holidays == null) {
return;
}
for (Machine machine : machines) {
// 确保维护窗口列表不为null
if (machine.getMaintenanceWindows() == null) {
machine.setMaintenanceWindows(new ArrayList<>());
}
for (Holiday holiday : holidays) {
// 将假期转换为维护窗口并添加到设备中
MaintenanceWindow maintenanceWindow = new MaintenanceWindow(holiday, "Holiday Period");
machine.addMaintenanceWindow(maintenanceWindow);
}
}
}
public GenVO convertGeneToGenVO(Gene gene, LocalDateTime baseTime) {
GenVO genVO = new GenVO();
genVO.setOrderId(String.valueOf(gene.getOrderId()));
genVO.setOperationId(gene.getOperationId());
genVO.setEquipId(gene.getMachineId());
genVO.setQuantity(BigDecimal.valueOf(gene.getBatchSize()));
genVO.setStartTime(baseTime.plusMinutes(gene.getStartTime()));
genVO.setEndTime(baseTime.plusMinutes(gene.getEndTime()));
genVO.setOperationName(""); // 从其他数据源获取
genVO.setEquipName(""); // 从其他数据源获取
return genVO;
}
// 批量转换
public List<GenVO> convertGeneListToGenVO(List<Gene> geneList, LocalDateTime baseTime) {
return geneList.stream()
.map(gene -> convertGeneToGenVO(gene, baseTime))
.collect(Collectors.toList());
}
public List<MachineVO> convertMachineListToVO(List<Machine> machines) {
return machines.stream()
.map(this::convertToVO)
.collect(Collectors.toList());
}
private MachineVO convertToVO(Machine machine) {
MachineVO machineVO = new MachineVO();
machineVO.setId(machine.getId());
machineVO.setEquipId(String.valueOf(machine.getId()));
machineVO.setEquipName(machine.getName());
// 注意:tasks 字段需要在其他地方设置,因为 Machine 类中没有任务信息
return machineVO;
}
}
\ No newline at end of file
......@@ -2,6 +2,10 @@ server:
port: 8181 # 修改为你想要的端口号
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
mvc:
pathmatch:
matching-strategy: ant_path_matcher # Spring Boot 2.6+ 需要这个配置
......@@ -37,7 +41,11 @@ spring:
username: sa
password: root_mes123456
# MyBatis-Plus配置
# 文件上传配置
file:
upload:
dir: uploads
mybatis-plus:
mapper-locations: classpath:mapper/**/*.xml # Mapper XML路径
type-aliases-package: com.aps.entity # 实体类包路径
......
......@@ -7,32 +7,37 @@
"days": [1, 2, 3, 4, 5],
"shiftDate": "0001-01-01T00:00:00",
"temporaryShift": false,
"priority": 0
"priority": 0,
"status":0
},{
"startTime": "18:00:00",
"endTime": "20:00:00",
"days": [1, 2, 3, 4, 5],
"shiftDate": "0001-01-01T00:00:00",
"temporaryShift": true,
"priority": 0
"priority": 0,
"status":1
},{
"startTime": "08:00:00",
"endTime": "18:00:00",
"days": [0],
"shiftDate": "0001-01-01T00:00:00",
"temporaryShift": true,
"priority": 0
"priority": 0,
"status":1
},{
"startTime": "08:00:00",
"endTime": "18:00:00",
"days": null,
"shiftDate": "2025-10-01T00:00:00",
"temporaryShift": true,
"priority": 0
"priority": 0,
"status":1
}],
"maintenanceWindows": [{
"startTime": "2025-10-08T10:00:00",
"endTime": "2025-10-10T12:00:00"
"endTime": "2025-10-10T12:00:00",
"status":2
}]
}, {
"id": 2,
......@@ -43,7 +48,8 @@
"days": [1, 2, 3,4,5],
"shiftDate": "0001-01-01T00:00:00",
"temporaryShift": false,
"priority": 0
"priority": 0,
"status":0
}],
"maintenanceWindows": null
}, {
......@@ -55,7 +61,8 @@
"days": [1, 2, 3, 4, 5],
"shiftDate": "0001-01-01T00:00:00",
"temporaryShift": false,
"priority": 0
"priority": 0,
"status":0
}],
"maintenanceWindows": null
}, {
......@@ -67,7 +74,8 @@
"days": [1, 2, 3, 4, 5],
"shiftDate": "0001-01-01T00:00:00",
"temporaryShift": false,
"priority": 0
"priority": 0,
"status":0
}],
"maintenanceWindows": null
}, {
......@@ -79,7 +87,8 @@
"days": [1, 2, 3, 4, 5],
"shiftDate": "0001-01-01T00:00:00",
"temporaryShift": false,
"priority": 0
"priority": 0,
"status":0
}],
"maintenanceWindows": null
}]
\ No newline at end of file
......@@ -11,7 +11,9 @@
{
"machineId": 2,
"processingTime": 11,
"setupTime": 59
"setupTime": 0,
"teardownTime": 0,
"preTime": 0
}
],
"isInterrupt": false
......@@ -24,12 +26,16 @@
{
"machineId": 3,
"processingTime": 5,
"setupTime": 45
"setupTime": 0,
"teardownTime": 0,
"preTime": 0
},
{
"machineId": 5,
"processingTime": 21,
"setupTime": 11
"setupTime": 0,
"teardownTime": 0,
"preTime": 0
}
],
"isInterrupt": false
......@@ -42,7 +48,9 @@
{
"machineId": 5,
"processingTime": 6,
"setupTime": 40
"setupTime": 0,
"teardownTime": 0,
"preTime": 0
}
],
"isInterrupt": false
......@@ -55,12 +63,16 @@
{
"machineId": 1,
"processingTime": 19,
"setupTime": 48
"setupTime": 0,
"teardownTime": 0,
"preTime": 0
},
{
"machineId": 4,
"processingTime": 20,
"setupTime": 51
"setupTime": 0,
"teardownTime": 0,
"preTime": 0
}
],
"isInterrupt": false
......@@ -79,7 +91,9 @@
{
"machineId": 1,
"processingTime": 5,
"setupTime": 30
"setupTime": 0,
"teardownTime": 0,
"preTime": 0
}
],
"isInterrupt": false
......@@ -92,7 +106,9 @@
{
"machineId": 2,
"processingTime": 22,
"setupTime": 47
"setupTime": 0,
"teardownTime": 0,
"preTime": 0
}
],
"isInterrupt": false
......@@ -105,12 +121,16 @@
{
"machineId": 1,
"processingTime": 8,
"setupTime": 35
"setupTime": 0,
"teardownTime": 0,
"preTime": 0
},
{
"machineId": 5,
"processingTime": 28,
"setupTime": 31
"setupTime": 0,
"teardownTime": 0,
"preTime": 0
}
],
"isInterrupt": false
......
......@@ -4,6 +4,7 @@ package com.aps.demo;
import com.aps.common.util.JsonFileReader;
import com.aps.entity.basic.*;
import com.aps.service.plan.AlgorithmScheduler6;
import com.aps.service.plan.AlgorithmScheduler7;
import com.aps.service.plan.MachineSchedulerService;
import java.time.LocalDateTime;
......@@ -82,7 +83,7 @@ public class MachineSchedulerTest {
}
AlgorithmScheduler6 scheduler = new AlgorithmScheduler6(products,machines,orders, machineScheduler);
AlgorithmScheduler7 scheduler = new AlgorithmScheduler7(products,machines,orders, machineScheduler);
List<ScheduleChromosome> scheduleChromosomes = scheduler.RunAll();
for (ScheduleChromosome run:scheduleChromosomes
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment