package cn.iocoder.yudao.module.qcreport.service.version;
|
|
import cn.hutool.core.util.StrUtil;
|
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
import cn.iocoder.yudao.module.qcreport.controller.admin.version.vo.QcReportTemplateVersionPageReqVO;
|
import cn.iocoder.yudao.module.qcreport.controller.admin.version.vo.QcReportTemplateVersionSaveReqVO;
|
import cn.iocoder.yudao.module.qcreport.controller.admin.version.vo.QcReportTemplateVersionUpdateReqVO;
|
import cn.iocoder.yudao.module.qcreport.dal.dataobject.template.QcReportTemplateDO;
|
import cn.iocoder.yudao.module.qcreport.dal.dataobject.version.QcReportTemplateVersionDO;
|
import cn.iocoder.yudao.module.qcreport.dal.dataobject.version.ReportTemplateSchema;
|
import cn.iocoder.yudao.module.qcreport.dal.mysql.version.QcReportTemplateVersionMapper;
|
import cn.iocoder.yudao.module.qcreport.engine.QualityReportEngine;
|
import cn.iocoder.yudao.module.qcreport.engine.render.CanvasSafety;
|
import cn.iocoder.yudao.module.qcreport.service.template.QcReportTemplateService;
|
import jakarta.annotation.Resource;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.validation.annotation.Validated;
|
|
import java.util.List;
|
import java.util.regex.Matcher;
|
import java.util.regex.Pattern;
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.module.qcreport.enums.ErrorCodeConstants.*;
|
import static cn.iocoder.yudao.module.qcreport.enums.QcReportEnums.TemplateStatusEnum.ENABLE;
|
import static cn.iocoder.yudao.module.qcreport.enums.QcReportEnums.VersionStatusEnum.*;
|
|
/**
|
* 智能质检报告模板版本 Service 实现类
|
*/
|
@Service
|
@Validated
|
public class QcReportTemplateVersionServiceImpl implements QcReportTemplateVersionService {
|
|
private static final Pattern VERSION_PATTERN = Pattern.compile("^v(\\d+)(?:\\.(\\d+))*$");
|
|
@Resource
|
private QcReportTemplateVersionMapper versionMapper;
|
|
@Resource
|
private QcReportTemplateService templateService;
|
|
@Override
|
public Long createVersion(QcReportTemplateVersionSaveReqVO createReqVO) {
|
QcReportTemplateDO template = templateService.validateTemplateExists(createReqVO.getTemplateId());
|
if (!ENABLE.getStatus().equals(template.getStatus())) {
|
throw exception(TEMPLATE_STATUS_DISABLED);
|
}
|
|
// 未指定版本号时,按最新版本自动递增(v1.0 -> v1.1)
|
String version = StrUtil.blankToDefault(createReqVO.getVersion(), null);
|
if (version == null) {
|
QcReportTemplateVersionDO latest = versionMapper.selectLatestByTemplateId(createReqVO.getTemplateId());
|
version = nextVersion(latest == null ? null : latest.getVersion());
|
}
|
validateVersionUnique(createReqVO.getTemplateId(), version, null);
|
|
validateSchema(createReqVO.getSchema());
|
|
QcReportTemplateVersionDO createObj = BeanUtils.toBean(createReqVO, QcReportTemplateVersionDO.class);
|
createObj.setVersion(version);
|
createObj.setStatus(DRAFT.getStatus());
|
versionMapper.insert(createObj);
|
return createObj.getId();
|
}
|
|
@Override
|
public void updateVersion(QcReportTemplateVersionUpdateReqVO updateReqVO) {
|
QcReportTemplateVersionDO version = validateVersionExists(updateReqVO.getId());
|
// 已发布的版本不允许修改,只能新建版本,保证历史报告可复现
|
if (PUBLISHED.getStatus().equals(version.getStatus())) {
|
throw exception(TEMPLATE_VERSION_PUBLISHED_CANNOT_MODIFY);
|
}
|
validateSchema(updateReqVO.getSchema());
|
|
QcReportTemplateVersionDO updateObj = BeanUtils.toBean(updateReqVO, QcReportTemplateVersionDO.class);
|
versionMapper.updateById(updateObj);
|
}
|
|
@Override
|
public void deleteVersion(Long id) {
|
QcReportTemplateVersionDO version = validateVersionExists(id);
|
if (!DRAFT.getStatus().equals(version.getStatus())) {
|
throw exception(TEMPLATE_VERSION_PUBLISHED_CANNOT_MODIFY);
|
}
|
versionMapper.deleteById(id);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void publishVersion(Long id) {
|
QcReportTemplateVersionDO version = validateVersionExists(id);
|
if (version.getSchema() == null) {
|
throw exception(TEMPLATE_VERSION_SCHEMA_EMPTY);
|
}
|
|
QcReportTemplateVersionDO updateObj = new QcReportTemplateVersionDO();
|
updateObj.setId(id);
|
updateObj.setStatus(PUBLISHED.getStatus());
|
versionMapper.updateById(updateObj);
|
|
// 模板的当前版本指向刚发布的版本
|
templateService.updateCurrentVersion(version.getTemplateId(), version.getVersion());
|
}
|
|
@Override
|
public void rollbackVersion(Long id) {
|
QcReportTemplateVersionDO version = validateVersionExists(id);
|
if (!PUBLISHED.getStatus().equals(version.getStatus())) {
|
throw exception(TEMPLATE_VERSION_NOT_PUBLISHED);
|
}
|
// 只切换模板的当前版本号,不改动任何版本内容
|
templateService.updateCurrentVersion(version.getTemplateId(), version.getVersion());
|
}
|
|
@Override
|
public void disableVersion(Long id) {
|
QcReportTemplateVersionDO version = validateVersionExists(id);
|
|
// 停用「当前版本」会造出一份自相矛盾的模板:template.current_version 还指着它,它却已经不可用,
|
// 于是这个模板自此出不了报告(到出件那一刻才报 1_070_101_003),而用户不会想到起因是刚才那次停用。
|
// 这里直接拒绝,而不是顺手把 current_version 改掉——「改哪个版本」是用户的决定,
|
// 不该由停用这个动作悄悄代劳;要整个模板停下来,另有「停用模板」这个入口。
|
QcReportTemplateDO template = templateService.getTemplate(version.getTemplateId());
|
if (template != null && version.getVersion().equals(template.getCurrentVersion())) {
|
throw exception(TEMPLATE_VERSION_CURRENT_CANNOT_DISABLE,
|
version.getVersion(), template.getTemplateName());
|
}
|
|
QcReportTemplateVersionDO updateObj = new QcReportTemplateVersionDO();
|
updateObj.setId(id);
|
updateObj.setStatus(DISABLED.getStatus());
|
versionMapper.updateById(updateObj);
|
}
|
|
@Override
|
public QcReportTemplateVersionDO validateVersionExists(Long id) {
|
QcReportTemplateVersionDO version = versionMapper.selectById(id);
|
if (version == null) {
|
throw exception(TEMPLATE_VERSION_NOT_EXISTS);
|
}
|
return version;
|
}
|
|
@Override
|
public QcReportTemplateVersionDO getVersion(Long id) {
|
return versionMapper.selectById(id);
|
}
|
|
@Override
|
public QcReportTemplateVersionDO getPublishedVersion(Long templateId, String version) {
|
QcReportTemplateVersionDO versionDO = versionMapper.selectByTemplateIdAndVersion(templateId, version);
|
if (versionDO == null) {
|
throw exception(TEMPLATE_VERSION_NOT_EXISTS);
|
}
|
if (!PUBLISHED.getStatus().equals(versionDO.getStatus())) {
|
throw exception(TEMPLATE_VERSION_NOT_PUBLISHED);
|
}
|
return versionDO;
|
}
|
|
@Override
|
public QcReportTemplateVersionDO getVersionByTemplateIdAndVersion(Long templateId, String version) {
|
return versionMapper.selectByTemplateIdAndVersion(templateId, version);
|
}
|
|
@Override
|
public PageResult<QcReportTemplateVersionDO> getVersionPage(QcReportTemplateVersionPageReqVO pageReqVO) {
|
return versionMapper.selectPage(pageReqVO);
|
}
|
|
@Override
|
public List<QcReportTemplateVersionDO> getVersionListByTemplateId(Long templateId) {
|
return versionMapper.selectListByTemplateId(templateId);
|
}
|
|
/**
|
* 校验设计器提交的 Schema 结构。Schema 允许为空(先建版本后设计),
|
* 但一旦提供就必须带 schemaVersion,避免存入无法识别的内容
|
*/
|
/**
|
* 校验模板 Schema,不合格的直接退回,不让它落库。
|
* <p>
|
* 规则必须在这里拦:规则写错了要到出件时才炸,用户看到的是「报告出不来」,
|
* 却不知道是模板里哪条规则写坏的。存的时候就说清楚,改起来才有着落。
|
*/
|
private void validateSchema(ReportTemplateSchema schema) {
|
if (schema == null) {
|
return;
|
}
|
if (StrUtil.isBlank(schema.getSchemaVersion())) {
|
throw exception(TEMPLATE_VERSION_SCHEMA_INVALID);
|
}
|
List<String> ruleErrors = QualityReportEngine.validateRules(schema);
|
if (!ruleErrors.isEmpty()) {
|
// 逐条列出具体规则与原因:只报「规则不合法」等于让用户自己去猜是哪条
|
throw new ServiceException(TEMPLATE_VERSION_RULE_INVALID.getCode(),
|
TEMPLATE_VERSION_RULE_INVALID.getMsg() + ":" + String.join(";", ruleErrors));
|
}
|
// 画布里有渲染时会原样拼进产物的内容(标签名、属性名、整段 CSS),在这里卡住;
|
// 位置放在渲染侧就动了 HtmlRenderer,会让对拍产物与存量报告的逐字复现一起变,见 CanvasSafety 类注释
|
List<String> canvasProblems = CanvasSafety.validate(schema.getGrapes());
|
if (!canvasProblems.isEmpty()) {
|
throw new ServiceException(TEMPLATE_VERSION_CANVAS_UNSAFE.getCode(),
|
StrUtil.format(TEMPLATE_VERSION_CANVAS_UNSAFE.getMsg(),
|
String.join(";", canvasProblems)));
|
}
|
}
|
|
private void validateVersionUnique(Long templateId, String version, Long id) {
|
QcReportTemplateVersionDO exists = versionMapper.selectByTemplateIdAndVersion(templateId, version);
|
if (exists != null && !exists.getId().equals(id)) {
|
throw exception(TEMPLATE_VERSION_DUPLICATE);
|
}
|
}
|
|
/**
|
* 计算下一个版本号:v1.0 -> v1.1;无历史或格式无法识别时从 v1.0 开始
|
*/
|
private String nextVersion(String currentVersion) {
|
if (StrUtil.isBlank(currentVersion)) {
|
return "v1.0";
|
}
|
Matcher matcher = VERSION_PATTERN.matcher(currentVersion);
|
if (!matcher.matches()) {
|
return "v1.0";
|
}
|
// 取 "v" 之后的第 1 段作为主版本,第 2 段作为次版本
|
String[] segments = currentVersion.substring(1).split("\\.");
|
int major = Integer.parseInt(segments[0]);
|
int minor = segments.length > 1 ? Integer.parseInt(segments[1]) : 0;
|
return "v" + major + "." + (minor + 1);
|
}
|
|
}
|