Files
cadannotation/src/main/java/com/cadlabel/service/impl/CadModelServiceImpl.java
T

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()
);
}
}