335 lines
13 KiB
Java
335 lines
13 KiB
Java
package com.cadlabel.service.impl;
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
import com.cadlabel.common.api.ApiCode;
|
|
import com.cadlabel.common.api.PageResponse;
|
|
import com.cadlabel.common.exception.BizException;
|
|
import com.cadlabel.dto.cad.CadModelHistoryResponse;
|
|
import com.cadlabel.dto.cad.CadModelListQuery;
|
|
import com.cadlabel.dto.cad.CadModelResponse;
|
|
import com.cadlabel.dto.cad.UpdateCadModelRequest;
|
|
import com.cadlabel.entity.CadModel;
|
|
import com.cadlabel.entity.CadModelHistory;
|
|
import com.cadlabel.mapper.CadModelHistoryMapper;
|
|
import com.cadlabel.mapper.CadModelMapper;
|
|
import com.cadlabel.service.CadModelService;
|
|
import java.time.LocalDateTime;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.dao.DuplicateKeyException;
|
|
import org.springframework.http.HttpStatus;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
import org.springframework.util.StringUtils;
|
|
|
|
/**
|
|
* CAD 模型服务实现。
|
|
*
|
|
* <p>负责模型列表、详情、基础信息修改和建模历史查询。</p>
|
|
*/
|
|
@Slf4j
|
|
@Service
|
|
@RequiredArgsConstructor
|
|
public class CadModelServiceImpl implements CadModelService {
|
|
|
|
private static final int DEFAULT_PAGE = 1;
|
|
private static final int DEFAULT_SIZE = 10;
|
|
private static final int MAX_SIZE = 100;
|
|
private static final Set<String> MODEL_CONVERT_STATUSES = Set.of("pending", "success", "failed", "partial_failed");
|
|
|
|
private final CadModelMapper cadModelMapper;
|
|
private final CadModelHistoryMapper cadModelHistoryMapper;
|
|
|
|
/**
|
|
* 分页查询 CAD 模型列表。
|
|
*/
|
|
@Override
|
|
public PageResponse<CadModelResponse> listModels(CadModelListQuery query) {
|
|
int page = resolvePage(query == null ? DEFAULT_PAGE : query.page());
|
|
int size = resolveSize(query == null ? DEFAULT_SIZE : query.size());
|
|
Long categoryId = query == null ? null : query.categoryId();
|
|
String convertStatus = normalizeOptionalText(query == null ? null : query.convertStatus());
|
|
String keyword = normalizeOptionalText(query == null ? null : query.keyword());
|
|
|
|
if (categoryId != null && categoryId <= 0) {
|
|
throw invalidRequest("categoryId 必须为正数");
|
|
}
|
|
if (StringUtils.hasText(convertStatus)) {
|
|
validateModelConvertStatus(convertStatus);
|
|
}
|
|
|
|
Page<CadModel> result = cadModelMapper.selectPage(new Page<>(page, size), new LambdaQueryWrapper<CadModel>()
|
|
.eq(categoryId != null, CadModel::getCategoryId, categoryId)
|
|
.eq(StringUtils.hasText(convertStatus), CadModel::getConvertStatus, convertStatus)
|
|
.and(StringUtils.hasText(keyword), nested -> nested
|
|
.like(CadModel::getModelName, keyword)
|
|
.or()
|
|
.like(CadModel::getSourceSwFilename, keyword))
|
|
.orderByDesc(CadModel::getCreatedAt)
|
|
.orderByDesc(CadModel::getId));
|
|
|
|
List<CadModelResponse> items = result.getRecords().stream().map(this::toModelResponse).toList();
|
|
return new PageResponse<>(items, result.getCurrent(), result.getSize(), result.getTotal());
|
|
}
|
|
|
|
/**
|
|
* 获取 CAD 模型详情。
|
|
*/
|
|
@Override
|
|
public CadModelResponse getModel(Long id) {
|
|
return toModelResponse(requireModel(id));
|
|
}
|
|
|
|
/**
|
|
* 修改 CAD 模型基础信息。
|
|
*/
|
|
@Override
|
|
@Transactional
|
|
public CadModelResponse updateModel(Long id, UpdateCadModelRequest request) {
|
|
CadModel model = requireModel(id);
|
|
List<String> updatedFields = updatedFields(request);
|
|
if (updatedFields.isEmpty()) {
|
|
return toModelResponse(model);
|
|
}
|
|
|
|
validateUpdateRequest(request);
|
|
ensureCategoryFilenameUnique(id, model, request);
|
|
applyUpdate(model, request);
|
|
model.setUpdatedAt(LocalDateTime.now());
|
|
|
|
log.info("Updating CAD model. id={}, fields={}", id, updatedFields);
|
|
try {
|
|
cadModelMapper.updateById(model);
|
|
} catch (DuplicateKeyException exception) {
|
|
throw duplicateModelException();
|
|
}
|
|
return getModel(id);
|
|
}
|
|
|
|
/**
|
|
* 查询模型完整建模历史。
|
|
*/
|
|
@Override
|
|
public List<CadModelHistoryResponse> listHistories(Long modelId) {
|
|
requireModel(modelId);
|
|
return cadModelHistoryMapper.selectList(Wrappers.lambdaQuery(CadModelHistory.class)
|
|
.eq(CadModelHistory::getModelId, modelId)
|
|
.orderByAsc(CadModelHistory::getHistoryIndex)
|
|
.orderByAsc(CadModelHistory::getId))
|
|
.stream()
|
|
.map(this::toHistoryResponse)
|
|
.toList();
|
|
}
|
|
|
|
private CadModel requireModel(Long id) {
|
|
validatePositiveId(id);
|
|
CadModel model = cadModelMapper.selectById(id);
|
|
if (model == null) {
|
|
throw modelNotFound();
|
|
}
|
|
return model;
|
|
}
|
|
|
|
private void validatePositiveId(Long id) {
|
|
if (id == null || id <= 0) {
|
|
throw invalidRequest("id 必须为正数");
|
|
}
|
|
}
|
|
|
|
private int resolvePage(int page) {
|
|
if (page < 1) {
|
|
throw invalidRequest("page 必须大于等于 1");
|
|
}
|
|
return page;
|
|
}
|
|
|
|
private int resolveSize(int size) {
|
|
if (size < 1 || size > MAX_SIZE) {
|
|
throw invalidRequest("size 必须在 1 到 100 之间");
|
|
}
|
|
return size;
|
|
}
|
|
|
|
private void ensureCategoryFilenameUnique(Long id, CadModel current, UpdateCadModelRequest request) {
|
|
if (request.categoryId() == null && request.sourceSwFilename() == null) {
|
|
return;
|
|
}
|
|
|
|
Long categoryId = request.categoryId() == null ? current.getCategoryId() : request.categoryId();
|
|
String sourceSwFilename = request.sourceSwFilename() == null
|
|
? current.getSourceSwFilename()
|
|
: request.sourceSwFilename();
|
|
Long count = cadModelMapper.selectCount(Wrappers.lambdaQuery(CadModel.class)
|
|
.eq(CadModel::getCategoryId, categoryId)
|
|
.eq(CadModel::getSourceSwFilename, sourceSwFilename)
|
|
.ne(CadModel::getId, id));
|
|
if (count != null && count > 0) {
|
|
throw duplicateModelException();
|
|
}
|
|
}
|
|
|
|
private void validateUpdateRequest(UpdateCadModelRequest request) {
|
|
if (request.categoryId() != null && request.categoryId() <= 0) {
|
|
throw invalidRequest("categoryId 必须为正数");
|
|
}
|
|
validateMaxLength("modelName", request.modelName(), 255);
|
|
validateMaxLength("sourceSwFilename", request.sourceSwFilename(), 255);
|
|
validateMaxLength("sourceSwPath", request.sourceSwPath(), 1024);
|
|
validateMaxLength("featureTreeJsonPath", request.featureTreeJsonPath(), 1024);
|
|
validateMaxLength("viewPath", request.viewPath(), 1024);
|
|
validateMaxLength("applicationScenario", request.applicationScenario(), 255);
|
|
validateMaxLength("material", request.material(), 255);
|
|
validateMaxLength("manufacturingMethod", request.manufacturingMethod(), 255);
|
|
validateMaxLength("unit", request.unit(), 64);
|
|
validateMaxLength("convertStatus", request.convertStatus(), 32);
|
|
if (request.convertStatus() != null) {
|
|
validateModelConvertStatus(request.convertStatus());
|
|
}
|
|
}
|
|
|
|
private void validateMaxLength(String field, String value, int maxLength) {
|
|
if (value != null && value.length() > maxLength) {
|
|
throw invalidRequest(field + " 长度不能超过 " + maxLength);
|
|
}
|
|
}
|
|
|
|
private void applyUpdate(CadModel model, UpdateCadModelRequest request) {
|
|
if (request.categoryId() != null) {
|
|
model.setCategoryId(request.categoryId());
|
|
}
|
|
if (request.modelName() != null) {
|
|
model.setModelName(request.modelName());
|
|
}
|
|
if (request.sourceSwFilename() != null) {
|
|
model.setSourceSwFilename(request.sourceSwFilename());
|
|
}
|
|
if (request.sourceSwPath() != null) {
|
|
model.setSourceSwPath(request.sourceSwPath());
|
|
}
|
|
if (request.featureTreeJsonPath() != null) {
|
|
model.setFeatureTreeJsonPath(request.featureTreeJsonPath());
|
|
}
|
|
if (request.viewPath() != null) {
|
|
model.setViewPath(request.viewPath());
|
|
}
|
|
if (request.applicationScenario() != null) {
|
|
model.setApplicationScenario(request.applicationScenario());
|
|
}
|
|
if (request.material() != null) {
|
|
model.setMaterial(request.material());
|
|
}
|
|
if (request.manufacturingMethod() != null) {
|
|
model.setManufacturingMethod(request.manufacturingMethod());
|
|
}
|
|
if (request.unit() != null) {
|
|
model.setUnit(request.unit());
|
|
}
|
|
if (request.userRequirement() != null) {
|
|
model.setUserRequirement(request.userRequirement());
|
|
}
|
|
if (request.designDescription() != null) {
|
|
model.setDesignDescription(request.designDescription());
|
|
}
|
|
if (request.convertStatus() != null) {
|
|
model.setConvertStatus(request.convertStatus());
|
|
}
|
|
if (request.errorMessage() != null) {
|
|
model.setErrorMessage(request.errorMessage());
|
|
}
|
|
}
|
|
|
|
private List<String> updatedFields(UpdateCadModelRequest request) {
|
|
List<String> fields = new ArrayList<>();
|
|
if (request == null) {
|
|
return fields;
|
|
}
|
|
addField(fields, "categoryId", request.categoryId());
|
|
addField(fields, "modelName", request.modelName());
|
|
addField(fields, "sourceSwFilename", request.sourceSwFilename());
|
|
addField(fields, "sourceSwPath", request.sourceSwPath());
|
|
addField(fields, "featureTreeJsonPath", request.featureTreeJsonPath());
|
|
addField(fields, "viewPath", request.viewPath());
|
|
addField(fields, "applicationScenario", request.applicationScenario());
|
|
addField(fields, "material", request.material());
|
|
addField(fields, "manufacturingMethod", request.manufacturingMethod());
|
|
addField(fields, "unit", request.unit());
|
|
addField(fields, "userRequirement", request.userRequirement());
|
|
addField(fields, "designDescription", request.designDescription());
|
|
addField(fields, "convertStatus", request.convertStatus());
|
|
addField(fields, "errorMessage", request.errorMessage());
|
|
return fields;
|
|
}
|
|
|
|
private void addField(List<String> fields, String fieldName, Object value) {
|
|
if (value != null) {
|
|
fields.add(fieldName);
|
|
}
|
|
}
|
|
|
|
private String normalizeOptionalText(String value) {
|
|
return StringUtils.hasText(value) ? value.trim() : "";
|
|
}
|
|
|
|
private void validateModelConvertStatus(String status) {
|
|
if (!MODEL_CONVERT_STATUSES.contains(status)) {
|
|
throw invalidRequest("convertStatus 取值非法");
|
|
}
|
|
}
|
|
|
|
private BizException invalidRequest(String message) {
|
|
return new BizException(HttpStatus.UNPROCESSABLE_ENTITY.value(), ApiCode.INVALID_REQUEST, message);
|
|
}
|
|
|
|
private BizException modelNotFound() {
|
|
return new BizException(HttpStatus.NOT_FOUND.value(), ApiCode.NOT_FOUND, "CAD 模型不存在");
|
|
}
|
|
|
|
private BizException duplicateModelException() {
|
|
return new BizException(HttpStatus.CONFLICT.value(), "CAD_MODEL_DUPLICATE", "同分类下源 SolidWorks 文件名已存在");
|
|
}
|
|
|
|
private CadModelResponse toModelResponse(CadModel model) {
|
|
return new CadModelResponse(
|
|
model.getId(),
|
|
model.getCategoryId(),
|
|
model.getModelName(),
|
|
model.getSourceSwFilename(),
|
|
model.getSourceSwPath(),
|
|
model.getFeatureTreeJsonPath(),
|
|
model.getViewPath(),
|
|
model.getApplicationScenario(),
|
|
model.getMaterial(),
|
|
model.getManufacturingMethod(),
|
|
model.getUnit(),
|
|
model.getUserRequirement(),
|
|
model.getDesignDescription(),
|
|
model.getConvertStatus(),
|
|
model.getErrorMessage(),
|
|
model.getCreatedAt(),
|
|
model.getUpdatedAt()
|
|
);
|
|
}
|
|
|
|
private CadModelHistoryResponse toHistoryResponse(CadModelHistory history) {
|
|
return new CadModelHistoryResponse(
|
|
history.getId(),
|
|
history.getModelId(),
|
|
history.getSourceFeatureId(),
|
|
history.getHistoryIndex(),
|
|
history.getHistoryName(),
|
|
history.getOperationType(),
|
|
history.getStepPath(),
|
|
history.getGlbPath(),
|
|
history.getConvertStatus(),
|
|
history.getErrorMessage(),
|
|
history.getCreatedAt(),
|
|
history.getUpdatedAt()
|
|
);
|
|
}
|
|
}
|