1、新增组件订单数据拉取接口;2、新增上传url产生文件数据/根据文件id获取文件地址接口;

This commit is contained in:
gaoqr
2026-07-29 10:04:14 +08:00
parent 445cc9b4fa
commit 10170d2ea2
44 changed files with 6382 additions and 11 deletions
@@ -2,6 +2,7 @@ package com.cf.imes.module.infra.api.file;
import com.cf.imes.framework.common.pojo.CommonResult;
import com.cf.imes.module.infra.api.file.dto.FileCreateReqDTO;
import com.cf.imes.module.infra.api.file.dto.ExternalFileCreateReqDTO;
import com.cf.imes.module.infra.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -12,6 +13,7 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.GetMapping;
import jakarta.validation.Valid;
@@ -102,4 +104,34 @@ public interface FileApi {
@PostMapping(PREFIX + "/getFileHashByIds")
@Operation(summary = "根据文件id请求和fileHash的对应关系")
CommonResult<Map<Long, String>> getFileHashMapByIds(@RequestBody Set<Long> fileIds);
/**
* 登记一个已由外部系统托管的文件地址,不上传或复制文件内容。
*
* @param createReqDTO 外部文件名称及访问地址
* @return iMES 文件记录 ID
*/
@PostMapping(PREFIX + "/external")
@Operation(summary = "登记外部文件地址并返回文件 ID")
CommonResult<Long> createExternalFile(@Valid @RequestBody ExternalFileCreateReqDTO createReqDTO);
/**
* 根据 iMES 文件记录 ID 获取外部文件地址。
*
* @param id iMES 文件记录 ID
* @return 外部文件地址
*/
@GetMapping(PREFIX + "/external/{id}/url")
@Operation(summary = "根据文件 ID 获取外部文件地址")
CommonResult<String> getExternalFileUrl(@PathVariable("id") Long id);
/**
* 删除外部文件在 iMES 中的登记记录,不删除外部存储中的实际文件。
*
* @param id iMES 文件记录 ID
* @return 是否删除成功
*/
@DeleteMapping(PREFIX + "/external/{id}")
@Operation(summary = "仅删除外部文件登记记录")
CommonResult<Boolean> deleteExternalFileRecord(@PathVariable("id") Long id);
}
@@ -0,0 +1,28 @@
package com.cf.imes.module.infra.api.file.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* 外部文件地址登记请求。
*
* <p>仅保存文件元数据和访问地址,不读取、校验或上传地址对应的文件内容。</p>
*/
@Schema(description = "RPC 服务 - 外部文件地址登记 Request DTO")
@Data
public class ExternalFileCreateReqDTO {
/**
* 展示用文件名称;为空时由服务端使用 URL 代替。
*/
@Schema(description = "文件名称", example = "cad-view-26072801000449.json")
private String name;
/**
* 外部存储中的文件访问地址。
*/
@Schema(description = "外部文件地址", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "外部文件地址不能为空")
private String url;
}
@@ -2,6 +2,7 @@ package com.cf.imes.module.infra.api.file;
import com.cf.imes.framework.common.pojo.CommonResult;
import com.cf.imes.module.infra.api.file.dto.FileCreateReqDTO;
import com.cf.imes.module.infra.api.file.dto.ExternalFileCreateReqDTO;
import com.cf.imes.module.infra.dal.dataobject.file.FileDO;
import com.cf.imes.module.infra.service.file.FileService;
import org.springframework.validation.annotation.Validated;
@@ -54,4 +55,29 @@ public class FileApiImpl implements FileApi {
FileDO::getName
)));
}
/**
* {@inheritDoc}
*/
@Override
public CommonResult<Long> createExternalFile(ExternalFileCreateReqDTO createReqDTO) {
return success(fileService.createExternalFile(createReqDTO.getName(), createReqDTO.getUrl()));
}
/**
* {@inheritDoc}
*/
@Override
public CommonResult<String> getExternalFileUrl(Long id) {
return success(fileService.getExternalFileUrl(id));
}
/**
* {@inheritDoc}
*/
@Override
public CommonResult<Boolean> deleteExternalFileRecord(Long id) {
fileService.deleteExternalFileRecord(id);
return success(true);
}
}
@@ -11,6 +11,7 @@ import com.cf.imes.framework.operatelog.core.annotations.OperateLog;
import com.cf.imes.module.infra.controller.admin.file.vo.file.FilePageReqVO;
import com.cf.imes.module.infra.controller.admin.file.vo.file.FileRespVO;
import com.cf.imes.module.infra.controller.admin.file.vo.file.FileUploadReqVO;
import com.cf.imes.module.infra.api.file.dto.ExternalFileCreateReqDTO;
import com.cf.imes.module.infra.dal.dataobject.file.FileDO;
import com.cf.imes.module.infra.service.file.FileService;
import io.swagger.v3.oas.annotations.Operation;
@@ -59,6 +60,33 @@ public class FileController {
return success(fileService.createFileDO(file.getOriginalFilename(), null, file.getBytes()));
}
/**
* 登记外部文件地址并生成 iMES 文件 ID。
*
* @param reqDTO 外部文件登记信息
* @return iMES 文件记录 ID
*/
@PostMapping("/external")
@Operation(summary = "登记外部文件地址")
@OperateLog(logArgs = false)
public CommonResult<Long> createExternalFile(@Valid @RequestBody ExternalFileCreateReqDTO reqDTO) {
return success(fileService.createExternalFile(reqDTO.getName(), reqDTO.getUrl()));
}
/**
* 根据 iMES 文件 ID 获取保存的文件地址。
*
* @param id iMES 文件记录 ID
* @return 文件访问地址
*/
@GetMapping("/url/{id}")
@Operation(summary = "根据文件 ID 获取文件地址")
@Parameter(name = "id", description = "文件编号", required = true)
@OperateLog(enable = false)
public CommonResult<String> getFileUrl(@PathVariable("id") Long id) {
return success(fileService.getExternalFileUrl(id));
}
@DeleteMapping("/delete")
@Operation(summary = "删除文件")
@@ -81,4 +81,28 @@ public interface FileService {
* @return
*/
List<FileDO> getByIds(Set<Long> ids);
/**
* 登记外部托管文件,不上传或读取文件内容。
*
* @param name 文件名称;为空时使用 URL
* @param url 外部文件访问地址
* @return iMES 文件记录 ID
*/
Long createExternalFile(String name, String url);
/**
* 根据 iMES 文件记录 ID 获取外部文件地址。
*
* @param id iMES 文件记录 ID
* @return 外部文件地址
*/
String getExternalFileUrl(Long id);
/**
* 仅删除外部文件在 iMES 中的登记记录,不删除外部存储中的实际文件。
*
* @param id iMES 文件记录 ID
*/
void deleteExternalFileRecord(Long id);
}
@@ -24,6 +24,7 @@ import java.util.Set;
import static com.cf.imes.module.infra.enums.ErrorCodeConstants.FILE_NOT_EXISTS;
import static com.cf.imes.module.infra.enums.ErrorCodeConstants.FILE_REMOVE_FAIL;
import static com.cf.imes.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY;
/**
* 文件 Service 实现类
@@ -176,4 +177,38 @@ public class FileServiceImpl implements FileService {
public List<FileDO> getByIds(Set<Long> ids) {
return fileMapper.selectByIds(ids);
}
/**
* {@inheritDoc}
*/
@Override
public Long createExternalFile(String name, String url) {
AssertUtils.notEmpty(url, FILE_IS_EMPTY);
FileDO file = new FileDO();
file.setConfigId(null);
file.setName(CharSequenceUtil.emptyToDefault(name, url));
file.setPath(url);
file.setUrl(url);
file.setType("application/json");
file.setSize(0);
fileMapper.insert(file);
return file.getId();
}
/**
* {@inheritDoc}
*/
@Override
public String getExternalFileUrl(Long id) {
return validateFileExists(id).getUrl();
}
/**
* {@inheritDoc}
*/
@Override
public void deleteExternalFileRecord(Long id) {
validateFileExists(id);
fileMapper.deleteById(id);
}
}
@@ -71,6 +71,12 @@ public class OrderModelDO {
@Schema(description = "偏移量Y")
private BigDecimal offsetY;
@Schema(description = "封边数据 X 偏移")
private BigDecimal sealOffsetX;
@Schema(description = "封边数据 Y 偏移")
private BigDecimal sealOffsetY;
@Schema(description = "是否有二维刀路")
private Boolean has2DModel;
@Schema(description = "是否有三维刀路")
@@ -216,4 +216,9 @@ public class PlateDO extends BaseDO {
* cad自定义板编号
*/
private String cadPlateNo;
/**
* 组件生产系统的物理板件 ID,对应来源报文中的 {@code blockItems[].id}。
*/
private String sourceBlockItemId;
}
@@ -36,6 +36,12 @@ public class PointDetail {
@Schema(description = "点的封边尺寸,封边尺寸,封边的厚度,长同边长")
private BigDecimal sealSize;
@Schema(description = "封边颜色")
private String sealColor;
@Schema(description = "开料预留边")
private BigDecimal reservedEdge;
@Schema(description = "加工工艺")
private String process;
}
@@ -25,4 +25,13 @@ public class ErrorCodeConstants {
public static final ErrorCode WEBCAD_ORDER_IMPORT_ORDER_UPDATE_CONCURRENCY_ERROR = new ErrorCode(1_005_000_015, "webcad.order.import.order.update.concurrent");
public static final ErrorCode WEBCAD_ORDER_IMPORT_GOODS_UPDATE_CONCURRENCY_ERROR = new ErrorCode(1_005_000_016, "webcad.order.import.goods.update.concurrent");
public static final ErrorCode WEBCAD_ORDER_IMPORT_CAD_VIEW_FILE_ID_EMPTY_ERROR = new ErrorCode(1_005_000_017, "webcad.order.import.cad.view.file.id.empty.error");
public static final ErrorCode PRODUCING_ORDER_PULL_FAILED = new ErrorCode(1_005_000_018, "producing.order.pull.failed");
public static final ErrorCode PRODUCING_ORDER_RESPONSE_INVALID = new ErrorCode(1_005_000_019, "producing.order.response.invalid");
public static final ErrorCode PRODUCING_ORDER_BLOCK_ITEMS_EMPTY = new ErrorCode(1_005_000_020, "producing.order.block_items.empty");
public static final ErrorCode PRODUCING_ORDER_REFERENCE_INVALID = new ErrorCode(1_005_000_021, "producing.order.reference.invalid");
public static final ErrorCode PRODUCING_ORDER_MATERIAL_MAPPING_INVALID = new ErrorCode(1_005_000_022, "producing.order.material_mapping.invalid");
public static final ErrorCode PRODUCING_ORDER_CAD_VIEW_PATH_EMPTY = new ErrorCode(1_005_000_023, "producing.order.cad_view_path.empty");
public static final ErrorCode PRODUCING_ORDER_CAD_OBJECT_INVALID = new ErrorCode(1_005_000_024, "producing.order.cad_object.invalid");
public static final ErrorCode PRODUCING_ORDER_ENUM_INVALID = new ErrorCode(1_005_000_025, "producing.order.enum.invalid");
public static final ErrorCode PRODUCING_ORDER_IMPORT_FAILED = new ErrorCode(1_005_000_026, "producing.order.import.failed");
}
@@ -6,12 +6,16 @@ import com.cf.imes.framework.common.pojo.CommonResult;
import com.cf.imes.framework.operatelog.core.annotations.OperateLog;
import com.cf.imes.framework.organ.core.context.OrganContextHolder;
import com.cf.imes.module.plan.service.orderImport.WebCadOrderImportService;
import com.cf.imes.module.plan.service.orderImport.ProducingOrderImportService;
import com.cf.imes.module.plan.controller.admin.orderimport.producing.vo.ProducingOrderImportReqVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
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.bind.annotation.RequestBody;
import jakarta.validation.Valid;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.Resource;
@@ -29,6 +33,8 @@ import static com.cf.imes.module.plan.enums.ErrorCodeConstants.WEBCAD_ORDER_IMPO
public class OrderImportController {
@Resource
private WebCadOrderImportService webCadOrderImportService;
@Resource
private ProducingOrderImportService producingOrderImportService;
@PostMapping("/webcad/import")
@Operation(summary = "webcad拆单导入订单")
@@ -42,4 +48,18 @@ public class OrderImportController {
webCadOrderImportService.webCadOrderImport(file);
return CommonResult.success(true);
}
/**
* 同步拉取一条组件生产单,并导入到指定的 iMES 订单。
*
* @param request 来源订单、目标订单及板材映射
* @return 是否导入成功
*/
@PostMapping("/producing/import")
@Operation(summary = "拉取组件生产单并同步导入订单")
@OperateLog(type = IMPORT)
public CommonResult<Boolean> producingOrderImport(
@Valid @RequestBody ProducingOrderImportReqVO request) {
return CommonResult.success(producingOrderImportService.importOrder(request));
}
}
@@ -0,0 +1,61 @@
package com.cf.imes.module.plan.controller.admin.orderimport.producing.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
/**
* 组件生产单同步导入请求。
*
* <p>一次请求仅处理一条来源订单和一条目标订单;板材映射由前端完成后提交。</p>
*/
@Schema(description = "管理后台 - 组件生产单同步导入 Request VO")
@Data
public class ProducingOrderImportReqVO {
/**
* 组件生产系统订单 ID。
*/
@Schema(description = "组件系统订单 ID", requiredMode = Schema.RequiredMode.REQUIRED,
example = "26072801000449")
@NotBlank(message = "{producing.order.import.sourceOrderId.notBlank}")
private String sourceOrderId;
/**
* 本次数据要拆入的 iMES 目标订单 ID。
*/
@Schema(description = "iMES 目标订单 ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "{producing.order.import.targetOrderId.notNull}")
private Long targetOrderId;
/**
* 对方板材 ID 与 iMES 板材库 plate.id 的一对一映射。
*/
@Schema(description = "组件系统板材与 iMES 板材映射", requiredMode = Schema.RequiredMode.REQUIRED)
@Valid
@NotEmpty(message = "{producing.order.import.materialMappings.notEmpty}")
private List<MaterialMapping> materialMappings;
@Data
public static class MaterialMapping {
/**
* 对方 materials 中 type=1 的板材 ID。
*/
@Schema(description = "组件系统 materials[type=1].id", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{producing.order.import.sourceMaterialId.notBlank}")
private String sourceMaterialId;
/**
* 映射后的 iMES 材质库 plate.id。
*/
@Schema(description = "iMES 材质库 plate.id", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "{producing.order.import.plateId.notNull}")
private Long plateId;
}
}
@@ -75,6 +75,12 @@ public class OrderModelDO {
@Schema(description = "偏移量Y")
private BigDecimal offsetY;
@Schema(description = "封边数据 X 偏移")
private BigDecimal sealOffsetX;
@Schema(description = "封边数据 Y 偏移")
private BigDecimal sealOffsetY;
@Schema(description = "是否有二维刀路")
private Boolean has2DModel;
@Schema(description = "是否有三维刀路")
@@ -268,4 +268,9 @@ public class PlateDO extends BaseDO {
* 造型数据
*/
private String modelData;
/**
* 组件生产系统的物理板件 ID,对应来源报文中的 {@code blockItems[].id}。
*/
private String sourceBlockItemId;
}
@@ -36,6 +36,12 @@ public class PointDetail {
@Schema(description = "点的封边尺寸,封边尺寸,封边的厚度,长同边长")
private BigDecimal sealSize;
@Schema(description = "封边颜色")
private String sealColor;
@Schema(description = "开料预留边")
private BigDecimal reservedEdge;
@Schema(description = "加工工艺")
private String process;
}
@@ -2,6 +2,7 @@ package com.cf.imes.module.plan.framework.rpc.config;
import com.cf.imes.module.infra.api.file.FileApi;
import com.cf.imes.module.system.api.assemblyconfig.AssemblyConfigApi;
import com.cf.imes.module.system.api.auth.AuthApi;
import com.cf.imes.module.system.api.customplateno.CustomPlateNoSeqApi;
import com.cf.imes.module.system.api.dict.DictDataApi;
import com.cf.imes.module.system.api.systemconfig.SystemConfigApi;
@@ -10,9 +11,13 @@ import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
/**
* @author there
* 生产计划模块 RPC 客户端配置。
*
* <p>组件生产单导入通过 {@link FileApi} 登记外部图纸地址,并通过 {@link AuthApi}
* 获取调用组件生产系统所需的内部 JWT。</p>
*/
@Configuration(proxyBeanMethods = false)
@EnableFeignClients(clients = {AdminUserApi.class, DictDataApi.class, SystemConfigApi.class, CustomPlateNoSeqApi.class, FileApi.class, AssemblyConfigApi.class})
@EnableFeignClients(clients = {AdminUserApi.class, DictDataApi.class, SystemConfigApi.class, CustomPlateNoSeqApi.class,
FileApi.class, AssemblyConfigApi.class, AuthApi.class})
public class RpcConfiguration {
}
@@ -0,0 +1,17 @@
package com.cf.imes.module.plan.service.orderImport;
import com.cf.imes.module.plan.controller.admin.orderimport.producing.vo.ProducingOrderImportReqVO;
/**
* 组件生产系统订单的同步导入入口。
*/
public interface ProducingOrderImportService {
/**
* 同步拉取、校验并导入一条组件生产单。
*
* @param request 来源订单、目标订单及板材映射
* @return 是否导入成功
*/
boolean importOrder(ProducingOrderImportReqVO request);
}
@@ -0,0 +1,150 @@
package com.cf.imes.module.plan.service.orderImport;
import cn.hutool.core.util.ObjectUtil;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.organ.core.context.OrganContextHolder;
import com.cf.imes.framework.redis.config.ChenfengCacheProperties;
import com.cf.imes.framework.redis.constants.RedisKeyConstants;
import com.cf.imes.framework.redis.util.RedisLockUtil;
import com.cf.imes.module.infra.api.file.FileApi;
import com.cf.imes.module.infra.api.file.dto.ExternalFileCreateReqDTO;
import com.cf.imes.module.plan.controller.admin.orderimport.producing.vo.ProducingOrderImportReqVO;
import com.cf.imes.module.plan.service.orderImport.producing.ProducingOrderClient;
import com.cf.imes.module.plan.service.orderImport.producing.ProducingOrderConverter;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderResponse;
import com.cf.imes.module.system.api.auth.AuthApi;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.ORDER_IMPORT_ORGAN_LOCK_ERROR;
import static com.cf.imes.module.plan.enums.ErrorCodeConstants.PRODUCING_ORDER_IMPORT_FAILED;
import static com.cf.imes.module.plan.enums.ErrorCodeConstants.WEBCAD_ORDER_IMPORT_ORGANID_EMPTY_ERROR;
/**
* 编排组件生产单导入的事务外步骤:生成内部调用令牌、拉取并校验报文、登记图纸外链、
* 获取组织级导入锁,再进入数据库事务。
*
* <p>图纸文件记录必须先创建;后续失败时仅做尽力补偿,补偿失败不覆盖原始导入异常。</p>
*/
@Service
@Slf4j
public class ProducingOrderImportServiceImpl implements ProducingOrderImportService {
private static final String IMPORT_LOCK_VALUE = "webcad";
@Resource
private AuthApi authApi;
@Resource
private FileApi fileApi;
@Resource
private ProducingOrderClient producingOrderClient;
@Resource
private ProducingOrderConverter producingOrderConverter;
@Resource
private ProducingOrderImportTransactionalService transactionalService;
@Resource
private RedisLockUtil redisLockUtil;
@Resource
private ChenfengCacheProperties chenfengCacheProperties;
/**
* {@inheritDoc}
*
* <p>数据库事务开始前完成远程报文拉取、完整校验和图纸地址登记,
* 避免把网络等待纳入订单写库事务。</p>
*/
@Override
public boolean importOrder(ProducingOrderImportReqVO request) {
long totalStartedAt = System.nanoTime();
Long organId = OrganContextHolder.getOrganId();
if (ObjectUtil.isNull(organId)) {
throw new ServiceException(WEBCAD_ORDER_IMPORT_ORGANID_EMPTY_ERROR);
}
long stageStartedAt = System.nanoTime();
String token = authApi.getMesJwtToken().getCheckedData();
logStage(request, "token", stageStartedAt);
stageStartedAt = System.nanoTime();
ProducingOrderResponse response =
producingOrderClient.getOrder(request.getSourceOrderId(), token);
logStage(request, "pull", stageStartedAt);
stageStartedAt = System.nanoTime();
producingOrderConverter.validate(response, request);
logStage(request, "validate", stageStartedAt);
ExternalFileCreateReqDTO fileRequest = new ExternalFileCreateReqDTO();
fileRequest.setName("cad-view-" + request.getSourceOrderId() + ".json");
fileRequest.setUrl(response.getData().getCadViewPath());
stageStartedAt = System.nanoTime();
Long fileId = fileApi.createExternalFile(fileRequest).getCheckedData();
logStage(request, "file-register", stageStartedAt);
if (fileId == null) {
throw new ServiceException(PRODUCING_ORDER_IMPORT_FAILED);
}
String lockKey = String.format(RedisKeyConstants.ORDER_IMPORT_LOCK_KEY, organId);
boolean locked = false;
try {
stageStartedAt = System.nanoTime();
locked = redisLockUtil.acquireLock(lockKey, IMPORT_LOCK_VALUE,
chenfengCacheProperties.getLockTimeout());
logStage(request, "lock", stageStartedAt);
if (!locked) {
throw new ServiceException(ORDER_IMPORT_ORGAN_LOCK_ERROR);
}
stageStartedAt = System.nanoTime();
transactionalService.importOrder(response, request, organId, fileId);
logStage(request, "transaction", stageStartedAt);
log.info("[producing-import] sourceOrderId={} targetOrderId={} stage=total elapsedMs={}",
request.getSourceOrderId(), request.getTargetOrderId(),
elapsedMillis(totalStartedAt));
return true;
} catch (Exception ex) {
compensateFileRecord(fileId);
if (ex instanceof ServiceException serviceException) {
throw serviceException;
}
log.error("Import producing order {} failed", request.getSourceOrderId(), ex);
throw new ServiceException(PRODUCING_ORDER_IMPORT_FAILED);
} finally {
if (locked) {
redisLockUtil.releaseLock(lockKey, IMPORT_LOCK_VALUE);
}
}
}
/**
* 导入失败后尽力删除本次生成的文件登记记录。
*
* <p>补偿失败只记录日志,不覆盖原始导入异常,也不会尝试删除 OSS 文件。</p>
*
* @param fileId 本次导入预先生成的 iMES 文件 ID
*/
private void compensateFileRecord(Long fileId) {
try {
fileApi.deleteExternalFileRecord(fileId).getCheckedData();
} catch (Exception compensationError) {
log.warn("Failed to compensate external file record {}", fileId, compensationError);
}
}
/**
* 输出事务外导入阶段耗时。
*/
private void logStage(ProducingOrderImportReqVO request, String stage, long startedAt) {
log.info("[producing-import] sourceOrderId={} targetOrderId={} stage={} elapsedMs={}",
request.getSourceOrderId(), request.getTargetOrderId(), stage,
elapsedMillis(startedAt));
}
/**
* 将纳秒计时起点换算为已耗费毫秒数。
*/
private long elapsedMillis(long startedAt) {
return (System.nanoTime() - startedAt) / 1_000_000;
}
}
@@ -0,0 +1,183 @@
package com.cf.imes.module.plan.service.orderImport;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.cf.imes.framework.common.enums.OrderPackageStatusEnum;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.id.core.util.SnowflakeIdWorker3rd;
import com.cf.imes.framework.mybatis.core.generator.SnowFlakeGenerator;
import com.cf.imes.module.executor.enums.DataTypeEnum;
import com.cf.imes.module.executor.enums.OrderStatusEnum;
import com.cf.imes.module.plan.controller.admin.orderimport.producing.vo.ProducingOrderImportReqVO;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportData;
import com.cf.imes.module.plan.dal.dataobject.order.OrderDO;
import com.cf.imes.module.plan.dal.dataobject.plate.PlateDO;
import com.cf.imes.module.plan.dal.mysql.goods.GoodsMapper;
import com.cf.imes.module.plan.dal.mysql.order.OrderMapper;
import com.cf.imes.module.plan.dal.mysql.orderBody.OrderBodyMapper;
import com.cf.imes.module.plan.dal.mysql.plate.PlateMapper;
import com.cf.imes.module.plan.service.customplateno.CustomPlateNoGenerateService;
import com.cf.imes.module.plan.service.orderImport.factory.ProducingOrderImportFactory;
import com.cf.imes.module.plan.service.orderImport.producing.ProducingOrderConverter;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderResponse;
import com.cf.imes.module.plan.service.orderImport.property.ProducingOrderImportProperties;
import com.cf.imes.module.system.api.assemblyconfig.AssemblyConfigApi;
import com.cf.imes.module.system.api.systemconfig.SystemConfigApi;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import static com.cf.imes.module.plan.enums.ErrorCodeConstants.WEBCAD_ORDER_IMPORT_ORDER_PLATENUM_REACH_THRESHOLD_ERROR;
import static com.cf.imes.module.plan.enums.ErrorCodeConstants.WEBCAD_ORDER_NOT_EXISTS_ERROR;
/**
* 组件生产单的写库事务边界。
*
* <p>目标订单校验、报文转换、订单状态更新以及板件/配件/加工组/部件组入库任一步失败,
* 都会回滚本次数据库变更。</p>
*/
@Service
@Slf4j
public class ProducingOrderImportTransactionalService {
@Resource
private OrderMapper orderMapper;
@Resource
private OrderBodyMapper orderBodyMapper;
@Resource
private PlateMapper plateMapper;
@Resource
private GoodsMapper goodsMapper;
@Resource
private SnowFlakeGenerator snowFlakeGenerator;
@Resource
private SnowflakeIdWorker3rd idWorker;
@Resource
private CustomPlateNoGenerateService customPlateNoGenerateService;
@Resource
private SystemConfigApi systemConfigApi;
@Resource
private AssemblyConfigApi assemblyConfigApi;
@Resource
private ProducingOrderImportProperties producingOrderImportProperties;
@Resource
private JdbcTemplate jdbcTemplate;
@Resource
private ProducingOrderConverter producingOrderConverter;
/**
* 在一个事务内完成目标订单状态更新及全部拆单业务数据写入。
*
* @param response 对方组件生产单完整报文
* @param request 本次导入请求
* @param organId 当前组织 ID
* @param cadViewFileId 事务外预先生成的图纸文件 ID
*/
@Transactional(rollbackFor = Exception.class)
public void importOrder(ProducingOrderResponse response,
ProducingOrderImportReqVO request,
Long organId,
Long cadViewFileId) {
long totalStartedAt = System.nanoTime();
long stageStartedAt = System.nanoTime();
OrderDO order = getTargetOrder(request.getTargetOrderId(), organId);
logStage(request, "target-order", stageStartedAt);
stageStartedAt = System.nanoTime();
validatePlateThreshold(order.getId(), organId,
response.getData().getData().getBlockItems().size());
logStage(request, "plate-threshold", stageStartedAt);
stageStartedAt = System.nanoTime();
ProducingImportData producingImportData = producingOrderConverter.convert(
response, request, organId, cadViewFileId);
logStage(request, "convert", stageStartedAt);
stageStartedAt = System.nanoTime();
updateOrderForImport(order);
logStage(request, "update-order-state", stageStartedAt);
ProducingOrderImportFactory factory = new ProducingOrderImportFactory(
organId, order, snowFlakeGenerator, orderMapper, orderBodyMapper, plateMapper,
idWorker, goodsMapper, customPlateNoGenerateService, systemConfigApi,
assemblyConfigApi, producingOrderImportProperties.getPlateNumThreshold(),
jdbcTemplate);
stageStartedAt = System.nanoTime();
factory.analyzeTempData(producingImportData);
logStage(request, "factory", stageStartedAt);
log.info("[producing-import-transaction] sourceOrderId={} targetOrderId={} stage=total elapsedMs={}",
request.getSourceOrderId(), request.getTargetOrderId(),
elapsedMillis(totalStartedAt));
}
/**
* 输出事务内各处理阶段耗时。
*/
private void logStage(ProducingOrderImportReqVO request, String stage, long startedAt) {
log.info("[producing-import-transaction] sourceOrderId={} targetOrderId={} stage={} elapsedMs={}",
request.getSourceOrderId(), request.getTargetOrderId(), stage,
elapsedMillis(startedAt));
}
/**
* 将纳秒计时起点换算为已耗费毫秒数。
*/
private long elapsedMillis(long startedAt) {
return (System.nanoTime() - startedAt) / 1_000_000;
}
/**
* 在当前组织内查询未删除的目标订单。
*/
private OrderDO getTargetOrder(Long orderId, Long organId) {
OrderDO order = orderMapper.selectOne(new LambdaQueryWrapper<OrderDO>()
.eq(OrderDO::getId, orderId)
.eq(OrderDO::getOrganId, organId)
.eq(OrderDO::getDeleted, false));
if (ObjectUtil.isNull(order)) {
throw new ServiceException(WEBCAD_ORDER_NOT_EXISTS_ERROR, orderId);
}
return order;
}
/**
* 在写库前校验“已有板件数 + 本次板件数”没有超过订单导入上限。
*/
private void validatePlateThreshold(Long orderId, Long organId, int importCount) {
Long existingCount = plateMapper.selectCount(new LambdaQueryWrapper<PlateDO>()
.eq(PlateDO::getOrderId, orderId)
.eq(PlateDO::getOrganId, organId)
.eq(PlateDO::getDeleted, false));
int threshold = producingOrderImportProperties.getPlateNumThreshold();
if (existingCount + importCount > threshold) {
throw new ServiceException(
WEBCAD_ORDER_IMPORT_ORDER_PLATENUM_REACH_THRESHOLD_ERROR,
orderId, threshold);
}
}
/**
* 沿用原拆单规则调整订单数据来源、生产状态和打包状态。
*/
private void updateOrderForImport(OrderDO order) {
Integer status = order.getStatus();
LambdaUpdateWrapper<OrderDO> update = new LambdaUpdateWrapper<OrderDO>()
.eq(OrderDO::getId, order.getId())
.eq(OrderDO::getOrganId, order.getOrganId())
.eq(OrderDO::getDeleted, false)
.set(OrderDO::getDataType, DataTypeEnum.API_IMPORT.getStatus());
if (OrderStatusEnum.EMPTY.getStatus().equals(status)) {
update.set(OrderDO::getStatus, OrderStatusEnum.NEW_ORDER.getStatus());
}
if (OrderPackageStatusEnum.PACKAGED.getStatus().equals(order.getPackaged())) {
update.set(OrderDO::getPackaged, OrderPackageStatusEnum.UNPACKED.getStatus());
}
if (OrderStatusEnum.FINISH_PRODUCTION.getStatus().equals(status)) {
update.set(OrderDO::getStatus, OrderStatusEnum.IN_PRODUCTION.getStatus());
}
orderMapper.update(update);
}
}
@@ -0,0 +1,102 @@
package com.cf.imes.module.plan.service.orderImport.producing;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderResponse;
import com.cf.imes.module.plan.service.orderImport.property.ProducingOrderImportProperties;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import static com.cf.imes.module.plan.enums.ErrorCodeConstants.PRODUCING_ORDER_PULL_FAILED;
import static com.cf.imes.module.plan.enums.ErrorCodeConstants.PRODUCING_ORDER_RESPONSE_INVALID;
/**
* 组件生产系统订单查询客户端。
*
* <p>仅网络 I/O 异常按配置重试;HTTP 错误、业务错误和无法解析的报文直接失败,
* 防止无意义的重复请求拉长同步导入事务外耗时。</p>
*/
@Component
@Slf4j
public class ProducingOrderClient {
private final ProducingOrderImportProperties properties;
private final ObjectMapper objectMapper;
private final HttpClient httpClient;
/**
* 创建组件生产系统客户端,并从配置初始化超时参数。
*
* @param properties 组件生产单接口配置
* @param objectMapper 系统 Jackson 配置
*/
public ProducingOrderClient(ProducingOrderImportProperties properties, ObjectMapper objectMapper) {
this.properties = properties;
this.objectMapper = objectMapper.copy()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
this.httpClient = HttpClient.newBuilder()
.connectTimeout(properties.getConnectTimeout())
.build();
}
/**
* 使用后端生成的 MES JWT 同步拉取一条组件生产单。
*
* @param orderId 组件生产系统订单 ID
* @param token MES JWT,不包含 {@code Bearer } 前缀
* @return 已完成基础业务码校验的生产单报文
*/
public ProducingOrderResponse getOrder(String orderId, String token) {
String encodedOrderId = URLEncoder.encode(orderId, StandardCharsets.UTF_8);
URI uri = URI.create(properties.getOrderUrl().replace("{id}", encodedOrderId));
int maxAttempts = Math.max(1, properties.getNetworkRetryTimes() + 1);
IOException lastNetworkError = null;
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
HttpRequest request = HttpRequest.newBuilder(uri)
.timeout(properties.getReadTimeout())
.header("Authorization", "Bearer " + token)
.header("Accept", "application/json")
.GET()
.build();
try {
HttpResponse<String> response = httpClient.send(
request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new ServiceException(PRODUCING_ORDER_PULL_FAILED,
"HTTP " + response.statusCode());
}
ProducingOrderResponse result = objectMapper.readValue(
response.body(), ProducingOrderResponse.class);
if (result == null || result.getCode() == null || result.getCode() != 0) {
String message = result == null ? "empty response" : result.getMessage();
throw new ServiceException(PRODUCING_ORDER_RESPONSE_INVALID, message);
}
return result;
} catch (JsonProcessingException ex) {
throw new ServiceException(PRODUCING_ORDER_RESPONSE_INVALID,
"invalid JSON response");
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new ServiceException(PRODUCING_ORDER_PULL_FAILED, "request interrupted");
} catch (IOException ex) {
lastNetworkError = ex;
if (attempt < maxAttempts) {
log.warn("Pull producing order {} failed on attempt {}, retrying",
orderId, attempt, ex);
}
}
}
String message = lastNetworkError == null ? "network error" : lastNetworkError.getMessage();
throw new ServiceException(PRODUCING_ORDER_PULL_FAILED, message);
}
}
@@ -0,0 +1,442 @@
package com.cf.imes.module.plan.service.orderImport.producing.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import java.util.ArrayList;
import java.util.List;
/**
* 组件生产单板件中间模型。
*/
@NoArgsConstructor
@Schema(description = "组件生产单板件中间模型")
@Data
public class ProducingImportBlock {
/**
* 对方组件生产系统的板件 ID。
*/
private String sourceBlockItemId;
private Integer orginId;
private Integer objectId;
private String name;
private Integer type;
private Integer custBlockNo;
@JsonProperty(value = "ID")
private Integer ID;
@Max(value = 99999, message = "板件宽度不能超过99999")
@Min(value = 0, message = "板件宽度最小值为0")
private Double width;
@Max(value = 99999, message = "板件高度不能超过99999")
@Min(value = 0, message = "板件高度最小值为0")
private Double height;
private String material;
private String materialName;
@Max(value = 99999, message = "板件厚度不能超过99999")
@Min(value = 0, message = "板件厚度最小值为0")
private Double thickness;
private Integer boardType;
private Double area;
private Integer texture;
private Integer holeFace;
private Integer holeArrange;
@JsonProperty(value = "unregular_point_count")
private Integer unregularPointCount;
private List<FrontBackHolesDTO> frontBackHoles;
private List<SideHolesDTO> sideHoles;
private Integer frontHoleCount;
private Integer backHoleCount;
private Integer sideHoleCount;
private Integer frontModelCount;
private Integer backModelCount;
private Boolean isDoor;
private Integer openDoorType;
private Double offsetX;
private Double offsetY;
private Double sealOffsetX;
private Double sealOffsetY;
private Boolean isArcBase;
private boolean isSpecialShape;
private String remark;
private Double kaiLiaoHeight;
private Double kaiLiaoWidth;
@Valid
// private InfoDTO info;
private String cabinetName;
private String roomName;
private Double cabinetDepth;
private Double cabinetWidth;
private Double cabinetHeight;
private PointInfoDTO pointInfo;
private Double sealLeft;
private Double sealRight;
private Double sealUp;
private Double sealDown;
private Double reservedEdgeUp;
private Double reservedEdgeDown;
private Double reservedEdgeLeft;
private Double reservedEdgeRight;
private Boolean isRect;
private String spliteHeight;
private String spliteThickness;
private String spliteWidth;
private boolean has2DModel;
private boolean has3DModel;
private String boardTypeName;
private boolean isModel;
private boolean isSideModel;
private boolean hasSide2DModel;
private List<List<String>> allRemarkList;
// private Long plateGoodsId;
private String goodsCode;
private List<Integer> groupIds = new ArrayList<>();
// 组件id
private Integer componentId;
/**
* 设置是否为异形板。
*
* @param specialShape 是否为异形板
*/
public void setIsSpecialShape(boolean specialShape) {
isSpecialShape = specialShape;
}
/**
* 设置是否包含侧面造型。
*
* @param sideModel 是否包含侧面造型
*/
public void setIsSideModel(boolean sideModel) {
isSideModel = sideModel;
}
/**
* 设置是否包含正反面造型。
*
* @param model 是否包含正反面造型
*/
public void setIsModel(boolean model) {
isModel = model;
}
@NoArgsConstructor
@Data
public static class FrontBackHolesDTO {
private Integer type;
private PositionDTO position;
private Double radius;
private Double depth;
private Integer face;
private Double angle;
private String name;
private String goodsId;
@NoArgsConstructor
@Data
public static class PositionDTO {
private Double x;
private Double y;
private Double z;
}
}
@NoArgsConstructor
@Data
public static class SideHolesDTO {
private Integer type;
private PositionDTO position;
private EndPtDTO endPt;
private Integer radius;
private Double depth;
private Integer face;
private String name;
private String goodsId;
@NoArgsConstructor
@Data
public static class PositionDTO {
private Double x;
private Double y;
private Double z;
}
@NoArgsConstructor
@Data
public static class EndPtDTO {
private Double x;
private Double y;
private Integer z;
}
}
@NoArgsConstructor
@Data
public static class PointInfoDTO {
@JsonProperty("PointDetail")
private List<PointDetailDTO> pointDetail;
@JsonProperty("OrgPointDetail")
private List<OrgPointDetailDTO> orgPointDetail;
@JsonProperty("HoleDetail")
private List<HoleDetailDTO> holeDetail;
@JsonProperty("SideHoleDetail")
private List<SideHoleDetailDTO> sideHoleDetail;
@JsonProperty("ModelDetail")
private List<ModelDetailDTO> modelDetail;
@JsonProperty("SideModelDetail")
private List<SideModelDetailDTO> sideModelDetail;
@JsonProperty("Side2DModelDetail")
private List<SideModelDetailDTO> side2DModelDetail;
@NoArgsConstructor
@Data
public static class PointDetailDTO {
@JsonProperty("PointID")
private Integer pointID;
@JsonProperty("PointX")
private Double pointX;
@JsonProperty("PointY")
private Double pointY;
@JsonProperty("Curve")
private Double curve;
@JsonProperty("SealSize")
private Double sealSize;
@JsonProperty("SealColor")
private String sealColor;
@JsonProperty("ReservedEdge")
private Double reservedEdge;
}
@NoArgsConstructor
@Data
public static class OrgPointDetailDTO {
@JsonProperty("PointID")
private Integer pointID;
@JsonProperty("PointX")
private Double pointX;
@JsonProperty("PointY")
private Double pointY;
@JsonProperty("Curve")
private Double curve;
@JsonProperty("SealSize")
private Double sealSize;
@JsonProperty("SealColor")
private String sealColor;
@JsonProperty("ReservedEdge")
private Double reservedEdge;
}
@NoArgsConstructor
@Data
public static class HoleDetailDTO {
@JsonProperty("HoleID")
private Integer holeID;
@JsonProperty("HoleType")
private Integer holeType;
@JsonProperty("Face")
private Integer face;
@JsonProperty("PointX")
private Double pointX;
@JsonProperty("PointY")
private Double pointY;
@JsonProperty("PointZ")
private Integer pointZ;
@JsonProperty("Radius")
private Double radius;
@JsonProperty("Depth")
private Double depth;
@JsonProperty("EndPoint")
private String endPoint;
@JsonProperty("Angle")
private Integer angle;
}
@NoArgsConstructor
@Data
public static class SideHoleDetailDTO {
@JsonProperty("HoleID")
private Integer holeID;
@JsonProperty("HoleType")
private Integer holeType;
@JsonProperty("Face")
private Integer face;
@JsonProperty("PointX")
private Double pointX;
@JsonProperty("PointY")
private Double pointY;
@JsonProperty("PointZ")
private Double pointZ;
@JsonProperty("Radius")
private Double radius;
@JsonProperty("Depth")
private Double depth;
@JsonProperty("EndPoint")
private String endPoint;
@JsonProperty("PointX2")
private Double pointX2;
@JsonProperty("PointY2")
private Double pointY2;
}
@NoArgsConstructor
@Data
public static class ModelDetailDTO {
@JsonProperty("ModelID")
private Integer modelID;
@JsonProperty("LineID")
private Integer lineID;
@JsonProperty("Face")
private Integer face;
@JsonProperty("KnifeName")
private String knifeName;
@JsonProperty("KnifeRadius")
private Double knifeRadius;
@JsonProperty("Depth")
private Double depth;
@JsonProperty("PointList")
private List<PointListDTO> pointList;
@JsonProperty("OriginModeling")
private OriginModelingDTO originModeling;
@JsonProperty("ModelType")
private Integer modelType;
@JsonProperty("OffSetList")
private List<OffSetListDTO> offSetList;
@NoArgsConstructor
@Data
public static class OriginModelingDTO {
@JsonProperty("outline")
private OutlineDTO outline;
@JsonProperty("addLen")
private Double addLen;
@JsonProperty("addWidth")
private Double addWidth;
@JsonProperty("addDepth")
private Double addDepth;
@JsonProperty("thickness")
private Double thickness;
@JsonProperty("dir")
private Integer dir;
@JsonProperty("knifeRadius")
private Double knifeRadius;
@JsonProperty("holes")
private List<HolesDTO> holes;
@NoArgsConstructor
@Data
public static class OutlineDTO {
@JsonProperty("pts")
private List<PtsDTO> pts;
@JsonProperty("buls")
private List<Double> buls;
@NoArgsConstructor
@Data
public static class PtsDTO {
@JsonProperty("x")
private Double x;
@JsonProperty("y")
private Double y;
}
}
@NoArgsConstructor
@Data
public static class HolesDTO {
@JsonProperty("pts")
private List<PtsDTO> pts;
@JsonProperty("buls")
private List<Double> buls;
@NoArgsConstructor
@Data
public static class PtsDTO {
@JsonProperty("x")
private Double x;
@JsonProperty("y")
private Double y;
}
}
}
@NoArgsConstructor
@Data
public static class PointListDTO {
@JsonProperty("LineID")
private Integer lineID;
@JsonProperty("PointID")
private Integer pointID;
@JsonProperty("PointX")
private Double pointX;
@JsonProperty("PointY")
private Double pointY;
@JsonProperty("Depth")
private Double depth;
@JsonProperty("Curve")
private Double curve;
@JsonProperty("Radius")
private Double radius;
}
@NoArgsConstructor
@Data
public static class OffSetListDTO {
@JsonProperty("ID")
private String id;
@JsonProperty("Value")
private Double value;
@JsonProperty("Deep")
private Double deep;
@JsonProperty("Angle")
private Double angle;
@JsonProperty("Name")
private String name;
@JsonProperty("Radius")
private Double radius;
}
}
@NoArgsConstructor
@Data
public static class SideModelDetailDTO {
@JsonProperty("ModelID")
private Integer modelID;
@JsonProperty("LineID")
private Integer lineID;
@JsonProperty("Face")
private Integer face;
@JsonProperty("KnifeName")
private String knifeName;
@JsonProperty("ChaiDanName")
private String chaiDanName;
@JsonProperty("KnifeRadius")
private Double knifeRadius;
@JsonProperty("Depth")
private Double depth;
@JsonProperty("ModelType")
private Integer modelType;
@JsonProperty("OriginModeling")
private ModelDetailDTO.OriginModelingDTO originModeling;
@JsonProperty("PointList")
private List<ModelDetailDTO.PointListDTO> pointList;
@JsonProperty("OffSetList")
private List<ModelDetailDTO.OffSetListDTO> offSetList;
}
}
}
@@ -0,0 +1,22 @@
package com.cf.imes.module.plan.service.orderImport.producing.dto;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 组件生产单板件备注中间模型。
*/
@NoArgsConstructor
@Data
public class ProducingImportBlockRemark {
private List<String> remarks;
private JSONObject extraObject;
private JSONArray drillsObj;
}
@@ -0,0 +1,36 @@
package com.cf.imes.module.plan.service.orderImport.producing.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
/**
* 组件生产单部件组中间模型。
*/
@NoArgsConstructor
@Data
public class ProducingImportComponentGroup {
@JsonProperty("groupID")
private Integer groupId;
private String name;
private BoxSize boxSize = new BoxSize();
private List<ProducingImportComponentGroup> children = new ArrayList<>();
@NoArgsConstructor
@Data
public static class BoxSize {
private Double width;
private Double height;
private Double depth;
}
}
@@ -0,0 +1,47 @@
package com.cf.imes.module.plan.service.orderImport.producing.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.Valid;
import java.util.List;
/**
* 组件生产单导入中间模型。
*/
@Schema(description = "组件生产单导入中间模型")
@Data
public class ProducingImportData {
@JsonProperty(value = "OrderNo")
private String orderNo;
@JsonProperty(value = "CadViewFileId")
private Long cadViewFileId;
/**
* 是否使用 type=4 的实际加工组名称。
*/
private boolean useConcreteProcessGroupName;
@JsonProperty(value = "Materials")
private List<ProducingImportMaterial> Materials;
@JsonProperty(value = "Parts")
private List<ProducingImportRoomTree> Parts;
@JsonProperty(value = "ProcessGroup")
@Valid
private List<ProducingImportProcessGroup> ProcessGroup;
@JsonProperty(value = "ComponentGroupTree")
@Valid
private List<ProducingImportComponentGroup> ComponentGroup;
@Valid
private List<ProducingImportRoomTree> douleRoomTree;
@JsonProperty(value = "Blocks")
private List<ProducingImportRoomTree> blocks;
}
@@ -0,0 +1,24 @@
package com.cf.imes.module.plan.service.orderImport.producing.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 组件生产单加工组解析结果。
*/
@NoArgsConstructor
@Data
public class ProducingImportGroupInfo {
private String groupTypeName;
private Long groupTypeId;
private String groupName;
private Double width;
private Double height;
private Double depth;
}
@@ -0,0 +1,45 @@
package com.cf.imes.module.plan.service.orderImport.producing.dto;
import lombok.Data;
import java.util.List;
/**
* 组件生产单材质中间模型。
*/
@Data
public class ProducingImportMaterial {
private Double height;
private Double width;
private Double thickness;
private String material;
private String color;
private String brand;
private String goodsId;
private List<Integer> orginIds;
private String name;
private boolean texture;
private String spec;
private String id;
/**
* 获取板材纹理标识。
*
* @return {@code true} 表示有纹理
*/
public boolean getTexture() {
return texture;
}
}
@@ -0,0 +1,78 @@
package com.cf.imes.module.plan.service.orderImport.producing.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
/**
* 组件生产单配件中间模型。
*/
@NoArgsConstructor
@Data
public class ProducingImportPart {
private String roomName;
private String cabinetName;
private String name;
private String type;
private String model;
private String spec;
private String width;
private String height;
private String thickness;
private String material;
private String brand;
private String factory;
private String unit;
private Double num;
private Boolean isComposite;
private String remark;
private Integer objectId;
private Long imesPartsId;
private List<Integer> groupIds = new ArrayList<>();
private GroupData groupData;
private List<Integer> componentIds = new ArrayList<>();
@NoArgsConstructor
@Data
public static class GroupData {
@JsonProperty("size")
private Integer size;
@JsonProperty("material")
private String material;
@JsonProperty("boardName")
private String boardName;
@JsonProperty("thickness")
private Integer thickness;
@JsonProperty("color")
private String color;
}
}
@@ -0,0 +1,44 @@
package com.cf.imes.module.plan.service.orderImport.producing.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 组件生产单加工组中间模型。
*/
@NoArgsConstructor
@Data
public class ProducingImportProcessGroup {
private String type;
private List<Project> projects;
@NoArgsConstructor
@Data
public static class Project {
private String projName;
private List<Integer> brIds;
private List<Integer> objIds;
private BoxSize boxSize;
private Integer groupID;
@NoArgsConstructor
@Data
public static class BoxSize {
private Double width;
private Double height;
private Double depth;
}
}
}
@@ -0,0 +1,54 @@
package com.cf.imes.module.plan.service.orderImport.producing.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import java.util.List;
/**
* 组件生产单房间、柜体及其内容的中间模型。
*/
@NoArgsConstructor
@Data
public class ProducingImportRoomTree {
private String name;
private Integer count;
@Valid
private List<ChildrenDTO> children;
@NoArgsConstructor
@Data
public static class ChildrenDTO {
private String name;
/**
* 外部模块的稳定标识,避免仅凭房间名和柜体名定位。
*/
private String identityKey;
private Double width;
private Double height;
private Double depth;
@NotNull(message = "拆单倍数不能为空")
@Max(value = 5000, message = "拆单倍数不能超过5000")
@Min(value = 1, message = "拆单倍数最小值为1")
private Integer count;
private List<ProducingImportBlock> blockList;
private List<ProducingImportPart> objList;
}
}
@@ -0,0 +1,262 @@
package com.cf.imes.module.plan.service.orderImport.producing.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonAlias;
import lombok.Data;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* 组件生产系统订单响应。
*
* <p>对方实体 ID 全部保留为字符串,只有写入原拆单结构时才按目标字段要求转换,
* 避免反序列化阶段发生精度丢失。</p>
*/
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class ProducingOrderResponse {
private Integer code;
private String message;
private Payload data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Payload {
private Map<String, String> cadObjectIdMap = Collections.emptyMap();
private ProductionOrder data;
private String cadViewPath;
private String cadFileId;
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class ProductionOrder {
private String id;
private List<ProductOrder> orderList = Collections.emptyList();
private List<Material> materials = Collections.emptyList();
private List<Module> modules = Collections.emptyList();
private List<Block> blockList = Collections.emptyList();
private List<Item> blockItems = Collections.emptyList();
private List<Part> partsList = Collections.emptyList();
private List<Item> partsItems = Collections.emptyList();
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class ProductOrder {
private String id;
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Material {
private String id;
private Integer type;
private String name;
private String factory;
private String brand;
private String spec;
private String material;
private String color;
private Double width;
private Double length;
private Double thickness;
private Boolean hasWave;
private String model;
private Double size;
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Module {
private String id;
private String orderId;
private String name;
private Integer type;
private String parentId;
private ModuleExtra extra;
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class ModuleExtra {
private Size3D size;
private Boolean isComposite;
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Size3D {
private Double width;
private Double height;
private Double depth;
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class BoardSize {
private Double width;
private Double length;
private Double thickness;
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Block {
private String id;
private String orderId;
private String name;
private String materialId;
private String type;
private BoardSize size;
private BoardSize realSize;
private List<String> sealIds = Collections.emptyList();
private List<String> edgeRemarks = Collections.emptyList();
private List<Double> reservedEdges = Collections.emptyList();
private Point sealOffset;
private Integer waveType;
private Integer doorType;
private Integer composingType;
private BorderLengths borderLengths;
private Boolean isArcBlock;
private Path points;
private Path orgPoints;
private List<Drill> drills = Collections.emptyList();
private List<SideDrill> sideDrills = Collections.emptyList();
private List<Shape> shapes = Collections.emptyList();
private List<Shape> sideShapes = Collections.emptyList();
/**
* 新报文可能直接返回刀路标记;旧报文则放在 remarks 中,转换层同时兼容两种结构。
*/
private Boolean has2DModel;
private Boolean has3DModel;
/**
* 板件定义级扩展信息,仅用于读取兼容字段,不作为导入后的板件备注。
*/
private Map<String, Object> remarks = Collections.emptyMap();
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Item {
private String orderId;
private String id;
private String relationId;
private String boxId;
private List<String> processGroupIds = Collections.emptyList();
private String componentGroupId;
/**
* 物理板件/配件实例的业务备注,导入备注只取本字段。
*/
private Map<String, Object> remarks = Collections.emptyMap();
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Part {
private String id;
private String orderId;
private String name;
private String materialId;
private Integer type;
private String group;
private Double quantity;
/**
* 对方配件定义的长、宽、高;转换层适配到原拆单结构的宽、长、厚字段。
*/
private Double length;
private Double width;
private Double height;
private Map<String, Object> remarks = Collections.emptyMap();
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class BorderLengths {
private Double heavy;
private Double light;
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Path {
private List<Point> pts = Collections.emptyList();
private List<Double> buls = Collections.emptyList();
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Point {
private Double x;
private Double y;
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Drill {
private Integer id;
private Double depth;
private Point offset;
private Integer position;
private Double radius;
private Double rotation;
private Integer holeType;
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class SideDrill {
private Integer id;
private Double depth;
private Point startOffset;
private Point endOffset;
private Double pointZ;
private Object holeType;
private Integer position;
private Double radius;
private Integer face;
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Shape {
private Integer id;
private String type;
private Integer toolPathId;
private List<ShapePath> profiles = Collections.emptyList();
private Integer position;
private Double depth;
private Double radius;
private String knifeName;
private Integer face;
private String processName;
private Path contour;
private List<Path> innerProfile = Collections.emptyList();
private Double addWidth;
private Double addLen;
private Double addDepth;
private List<ShapeOffset> offsetList = Collections.emptyList();
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class ShapePath extends Path {
private Integer id;
private List<Double> depths = Collections.emptyList();
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class ShapeOffset {
private Double angle;
private Double depth;
@JsonAlias("KnifeName")
private String knifeName;
private Double radius;
private Double value;
private String toolPath;
}
}
@@ -0,0 +1,44 @@
package com.cf.imes.module.plan.service.orderImport.property;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
/**
* 组件生产单拉取及导入配置。
*
* <p>统一管理对方订单接口地址、HTTP 超时和重试策略,以及单订单板件数量上限。</p>
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "chenfeng.plan.producing-order")
public class ProducingOrderImportProperties {
/**
* 组件生产单查询地址,其中 {@code {id}} 会替换为来源订单 ID。
*/
private String orderUrl =
"http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/{id}";
/**
* 建立 HTTP 连接的超时时间。
*/
private Duration connectTimeout = Duration.ofSeconds(5);
/**
* 单次 HTTP 请求的读取超时时间。
*/
private Duration readTimeout = Duration.ofSeconds(60);
/**
* 网络 I/O 失败后的重试次数;HTTP 或业务错误不重试。
*/
private int networkRetryTimes = 1;
/**
* 单个订单允许累计导入的板件数量上限。
*/
private int plateNumThreshold = 20000;
}
@@ -164,6 +164,12 @@ chenfeng:
enable: false
publicKey: cfimes
plan:
producing-order:
order-url: http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/{id}
connect-timeout: 5s
read-timeout: 60s
network-retry-times: 1
plate-num-threshold: 20000
webcad:
import:
node: 1
@@ -192,6 +192,12 @@ chenfeng:
enable: false
publicKey: cfimes
plan:
producing-order:
order-url: http://proc.mes.cf/api-branch=opt_split_order/api/v1/producing/order/{id}
connect-timeout: 5s
read-timeout: 60s
network-retry-times: 1
plate-num-threshold: 20000
webcad:
import:
node: 1
@@ -0,0 +1,5 @@
producing.order.import.sourceOrderId.notBlank=組件系統訂單 ID 不能為空
producing.order.import.targetOrderId.notNull=目標訂單 ID 不能為空
producing.order.import.materialMappings.notEmpty=板材映射不能為空
producing.order.import.sourceMaterialId.notBlank=組件系統板材 ID 不能為空
producing.order.import.plateId.notNull=iMES 板材 ID 不能為空
@@ -0,0 +1,5 @@
producing.order.import.sourceOrderId.notBlank=Component system order ID cannot be empty
producing.order.import.targetOrderId.notNull=Target order ID cannot be empty
producing.order.import.materialMappings.notEmpty=Material mappings cannot be empty
producing.order.import.sourceMaterialId.notBlank=Component system material ID cannot be empty
producing.order.import.plateId.notNull=iMES material ID cannot be empty
@@ -31,3 +31,12 @@ webcad.order.import.peek.not_exist=匯入失敗:資料來源不存在,請檢
webcad.order.import.order.update.concurrent=匯入失敗:訂單號{0}板件數量已發生變更,請重新匯入
webcad.order.import.goods.update.concurrent=匯入失敗:材質{0}板件數量已發生變更,請重新匯入
webcad.order.import.cad.view.file.id.empty.error=匯入失敗:請檢查CAD圖紙數據是否上傳成功
producing.order.pull.failed=拉取組件生產單失敗:{0}
producing.order.response.invalid=組件生產單報文無效:{0}
producing.order.block_items.empty=組件生產單沒有板件,禁止匯入
producing.order.reference.invalid=組件生產單關聯資料無效:{0}
producing.order.material_mapping.invalid=板材映射無效:{0}
producing.order.cad_view_path.empty=組件生產單缺少 CAD 圖紙地址
producing.order.cad_object.invalid=組件生產單 CAD 物件映射無效:{0}
producing.order.enum.invalid=組件生產單枚舉值無效:{0}
producing.order.import.failed=組件生產單匯入失敗
@@ -31,3 +31,12 @@ webcad.order.import.peek.not_exist=Import failed: data source does not exist, pl
webcad.order.import.order.update.concurrent=Import failed: plate quantity under production order {0} has changed, please re-import
webcad.order.import.goods.update.concurrent=Import failed: plate quantity for material {0} has changed, please re-import
webcad.order.import.cad.view.file.id.empty.error=Import failed: Please check if the CAD drawing data was uploaded successfully
producing.order.pull.failed=Failed to pull producing order: {0}
producing.order.response.invalid=Invalid producing order response: {0}
producing.order.block_items.empty=The producing order contains no board items
producing.order.reference.invalid=Invalid producing order reference: {0}
producing.order.material_mapping.invalid=Invalid material mapping: {0}
producing.order.cad_view_path.empty=The producing order has no CAD view path
producing.order.cad_object.invalid=Invalid CAD object mapping: {0}
producing.order.enum.invalid=Invalid producing order enum value: {0}
producing.order.import.failed=Failed to import producing order
@@ -31,3 +31,12 @@ webcad.order.import.peek.not_exist=导入失败:数据源不存在,请检查
webcad.order.import.order.update.concurrent=导入失败:订单号{0}板件数量已发生改变,请重新导入
webcad.order.import.goods.update.concurrent=导入失败:材质{0}板件数量已发生改变,请重新导入
webcad.order.import.cad.view.file.id.empty.error=导入失败:请检查CAD图纸数据是否上传成功
producing.order.pull.failed=拉取组件生产单失败:{0}
producing.order.response.invalid=组件生产单报文无效:{0}
producing.order.block_items.empty=组件生产单没有板件,禁止导入
producing.order.reference.invalid=组件生产单关联数据无效:{0}
producing.order.material_mapping.invalid=板材映射无效:{0}
producing.order.cad_view_path.empty=组件生产单缺少 CAD 图纸地址
producing.order.cad_object.invalid=组件生产单 CAD 对象映射无效:{0}
producing.order.enum.invalid=组件生产单枚举值无效:{0}
producing.order.import.failed=组件生产单导入失败
@@ -0,0 +1,5 @@
producing.order.import.sourceOrderId.notBlank=组件系统订单 ID 不能为空
producing.order.import.targetOrderId.notNull=目标订单 ID 不能为空
producing.order.import.materialMappings.notEmpty=板材映射不能为空
producing.order.import.sourceMaterialId.notBlank=组件系统板材 ID 不能为空
producing.order.import.plateId.notNull=iMES 板材 ID 不能为空
@@ -0,0 +1,250 @@
package com.cf.imes.module.plan.service.orderImport.producing;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.module.plan.controller.admin.orderimport.producing.vo.ProducingOrderImportReqVO;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportBlock;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingImportData;
import com.cf.imes.module.plan.dal.dataobject.plate.PlateGoodDO;
import com.cf.imes.module.plan.dal.mysql.plate.PlateGoodMapper;
import com.cf.imes.module.plan.service.orderImport.producing.dto.ProducingOrderResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
class ProducingOrderConverterTest {
private ProducingOrderConverter converter;
private PlateGoodMapper plateGoodMapper;
@BeforeEach
void setUp() {
converter = new ProducingOrderConverter();
plateGoodMapper = Mockito.mock(PlateGoodMapper.class);
ReflectionTestUtils.setField(converter, "plateGoodMapper", plateGoodMapper);
}
@Test
void shouldConvertPhysicalBoardAndKeepExternalIdentity() {
ProducingOrderResponse response = validResponse();
ProducingOrderImportReqVO request = validRequest();
PlateGoodDO localPlate = localPlate();
Mockito.when(plateGoodMapper.selectList(any())).thenReturn(List.of(localPlate));
converter.validate(response, request);
ProducingImportData converted = converter.convert(response, request, 1L, 123L);
assertThat(converted.getCadViewFileId()).isEqualTo(123L);
assertThat(converted.isUseConcreteProcessGroupName()).isTrue();
assertThat(converted.getMaterials()).singleElement()
.extracting("goodsId").isEqualTo("LOCAL-GOODS");
ProducingImportBlock block = converted.getBlocks().get(0)
.getChildren().get(0).getBlockList().get(0);
assertThat(block.getSourceBlockItemId()).isEqualTo("physical-1");
assertThat(block.getID()).isEqualTo(9001);
assertThat(block.getTexture()).isEqualTo(2);
assertThat(block.getSealDown()).isEqualTo(1D);
assertThat(block.getSealRight()).isZero();
assertThat(block.getSealUp()).isEqualTo(1D);
assertThat(block.getSealLeft()).isZero();
assertThat(block.getComponentId()).isNull();
assertThat(block.getAllRemarkList())
.containsExactly(List.of("Remark-0", "item remark"));
assertThat(converted.getBlocks().get(0).getChildren().get(0).getIdentityKey())
.isEqualTo("producing:body-1");
assertThat(converted.getBlocks().get(0).getChildren().get(0).getWidth())
.isEqualTo(600D);
}
@Test
void shouldRejectOrderWithoutPhysicalBoards() {
ProducingOrderResponse response = validResponse();
response.getData().getData().setBlockItems(List.of());
assertThatThrownBy(() -> converter.validate(response, validRequest()))
.isInstanceOf(ServiceException.class);
}
@Test
void shouldConvertReferencedPartWithDefaultValues() {
ProducingOrderResponse response = validResponse();
ProducingOrderResponse.Material material = new ProducingOrderResponse.Material();
material.setId("part-material-1");
material.setType(2);
material.setSpec("规格|材质|颜色");
ProducingOrderResponse.Part part = new ProducingOrderResponse.Part();
part.setId("part-1");
part.setOrderId("order-1");
part.setName("铰链");
part.setMaterialId(material.getId());
part.setType(0);
part.setLength(100D);
part.setWidth(20D);
part.setHeight(5D);
part.setRemarks(Map.of("definition", "definition remark"));
part.setGroup("五金");
ProducingOrderResponse.Item item = new ProducingOrderResponse.Item();
item.setId("part-item-1");
item.setOrderId("order-1");
item.setRelationId(part.getId());
item.setBoxId("body-1");
item.setProcessGroupIds(List.of("401"));
item.setComponentGroupId("0");
item.setRemarks(Map.of("Remark-0", "item remark"));
var materials = new java.util.ArrayList<>(
response.getData().getData().getMaterials());
materials.add(material);
response.getData().getData().setMaterials(materials);
response.getData().getData().setPartsList(List.of(part));
response.getData().getData().setPartsItems(List.of(item));
response.getData().setCadObjectIdMap(Map.of(
"block-1", "9001",
"part-1", "9002"));
Mockito.when(plateGoodMapper.selectList(any()))
.thenReturn(List.of(localPlate()));
converter.validate(response, validRequest());
ProducingImportData converted = converter.convert(
response, validRequest(), 1L, 123L);
var convertedPart = converted.getParts().get(0)
.getChildren().get(0).getObjList().get(0);
assertThat(convertedPart.getObjectId()).isEqualTo(9002);
assertThat(convertedPart.getNum()).isEqualTo(1D);
assertThat(convertedPart.getWidth()).isEqualTo("20.0");
assertThat(convertedPart.getHeight()).isEqualTo("100.0");
assertThat(convertedPart.getThickness()).isEqualTo("5.0");
assertThat(convertedPart.getType()).isEqualTo("五金");
assertThat(convertedPart.getIsComposite()).isFalse();
assertThat(convertedPart.getRemark()).isEqualTo("item remark");
}
private PlateGoodDO localPlate() {
PlateGoodDO localPlate = PlateGoodDO.builder()
.id(99L)
.goodsId("LOCAL-GOODS")
.goodsName("本地板材")
.material("生态板")
.color("檀木")
.texture(true)
.width(1220D)
.height(2440D)
.thickness(18D)
.spec("1220*2440*18")
.brand("CF")
.organId(1L)
.build();
localPlate.setDeleted(false);
return localPlate;
}
private ProducingOrderImportReqVO validRequest() {
ProducingOrderImportReqVO.MaterialMapping mapping =
new ProducingOrderImportReqVO.MaterialMapping();
mapping.setSourceMaterialId("material-1");
mapping.setPlateId(99L);
ProducingOrderImportReqVO request = new ProducingOrderImportReqVO();
request.setSourceOrderId("order-1");
request.setTargetOrderId(100L);
request.setMaterialMappings(List.of(mapping));
return request;
}
private ProducingOrderResponse validResponse() {
ProducingOrderResponse.Material board = new ProducingOrderResponse.Material();
board.setId("material-1");
board.setType(1);
ProducingOrderResponse.Material seal = new ProducingOrderResponse.Material();
seal.setId("seal-1");
seal.setType(3);
seal.setSize(1D);
ProducingOrderResponse.Material noSeal = new ProducingOrderResponse.Material();
noSeal.setId("seal-0");
noSeal.setType(3);
noSeal.setSize(0D);
ProducingOrderResponse.Module room = module("room-1", 1, "0", "房间");
ProducingOrderResponse.Module body = module("body-1", 2, "room-1", "柜体");
ProducingOrderResponse.ModuleExtra extra = new ProducingOrderResponse.ModuleExtra();
ProducingOrderResponse.Size3D bodySize = new ProducingOrderResponse.Size3D();
bodySize.setWidth(600D);
bodySize.setHeight(2000D);
bodySize.setDepth(500D);
extra.setSize(bodySize);
body.setExtra(extra);
ProducingOrderResponse.Module groupType = module("301", 3, "0", "加工类型");
ProducingOrderResponse.Module group = module("401", 4, "301", "加工组");
ProducingOrderResponse.Block block = new ProducingOrderResponse.Block();
block.setId("block-1");
block.setOrderId("order-1");
block.setName("背板");
block.setMaterialId("material-1");
block.setType("2");
ProducingOrderResponse.BoardSize size = new ProducingOrderResponse.BoardSize();
size.setWidth(500D);
size.setLength(1000D);
size.setThickness(18D);
block.setSize(size);
block.setSealIds(List.of("seal-1", "seal-0", "seal-1", "seal-0"));
block.setReservedEdges(List.of(0D, 0D, 0D, 0D));
block.setWaveType(1);
block.setDoorType(0);
block.setComposingType(2);
ProducingOrderResponse.Path emptyPath = new ProducingOrderResponse.Path();
emptyPath.setPts(List.of());
emptyPath.setBuls(List.of());
block.setPoints(emptyPath);
block.setOrgPoints(emptyPath);
block.setRemarks(Map.of("definition", "definition remark"));
ProducingOrderResponse.Item item = new ProducingOrderResponse.Item();
item.setId("physical-1");
item.setOrderId("order-1");
item.setRelationId("block-1");
item.setBoxId("body-1");
item.setProcessGroupIds(List.of("401"));
item.setComponentGroupId("0");
item.setRemarks(Map.of("Remark-0", "item remark"));
ProducingOrderResponse.ProductionOrder order =
new ProducingOrderResponse.ProductionOrder();
order.setMaterials(List.of(board, seal, noSeal));
order.setModules(List.of(room, body, groupType, group));
order.setBlockList(List.of(block));
order.setBlockItems(List.of(item));
order.setPartsList(List.of());
order.setPartsItems(List.of());
ProducingOrderResponse.Payload payload = new ProducingOrderResponse.Payload();
payload.setCadViewPath("https://oss.example/cad-view");
payload.setCadObjectIdMap(Map.of("block-1", "9001"));
payload.setData(order);
ProducingOrderResponse response = new ProducingOrderResponse();
response.setCode(0);
response.setData(payload);
return response;
}
private ProducingOrderResponse.Module module(
String id, int type, String parentId, String name) {
ProducingOrderResponse.Module module = new ProducingOrderResponse.Module();
module.setId(id);
module.setOrderId("order-1");
module.setName(name);
module.setType(type);
module.setParentId(parentId);
return module;
}
}
@@ -0,0 +1,27 @@
package com.cf.imes.module.system.api.auth;
import com.cf.imes.framework.common.pojo.CommonResult;
import com.cf.imes.module.system.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
/**
* 系统认证 RPC API。
*/
@FeignClient(name = ApiConstants.NAME)
@Tag(name = "RPC 服务 - 认证")
public interface AuthApi {
String PREFIX = ApiConstants.PREFIX + "/auth";
/**
* 按当前登录用户及组织生成调用组件生产系统所需的 MES JWT。
*
* @return MES JWT;仅供后端内部调用,不返回给前端
*/
@GetMapping(PREFIX + "/mes-token")
@Operation(summary = "为当前登录用户生成 MES JWT")
CommonResult<String> getMesJwtToken();
}
@@ -0,0 +1,28 @@
package com.cf.imes.module.system.api.auth;
import com.cf.imes.framework.common.pojo.CommonResult;
import com.cf.imes.module.system.service.auth.AdminAuthService;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import static com.cf.imes.framework.common.pojo.CommonResult.success;
/**
* 系统认证 RPC API 实现。
*/
@RestController
@Validated
public class AuthApiImpl implements AuthApi {
@Resource
private AdminAuthService authService;
/**
* {@inheritDoc}
*/
@Override
public CommonResult<String> getMesJwtToken() {
return success(authService.getMesJwtToken());
}
}