package jnpf.elnService.impl; import cn.dev33.satoken.stp.StpUtil; import cn.hutool.core.util.IdUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import jnpf.audit.model.AuditEventDTO; import jnpf.audit.sdk.AuditClient; import jnpf.base.UserInfo; import jnpf.database.util.TenantDataSourceUtil; import jnpf.elnEntity.field.ElnFieldDefEntity; import jnpf.elnEntity.field.ElnFieldEnums; import jnpf.elnEntity.field.ElnFieldForms; import jnpf.elnEntity.field.ElnFieldVersionEntity; import jnpf.elnEntity.field.ElnFieldVos; import jnpf.elnMapper.ElnFieldDefMapper; import jnpf.elnMapper.ElnFieldVersionMapper; import jnpf.elnService.ElnFieldCatalogService; import jnpf.elnService.ElnFieldProviderCatalog; import jnpf.elnService.field.ElnTestResultFieldCatalog; import jnpf.elnService.field.ElnTestResultFields; import jnpf.exception.DataException; import jnpf.model.tenant.TenantVO; import jnpf.util.JsonUtil; import jnpf.util.UserProvider; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.regex.Pattern; @Service @Transactional(rollbackFor = Exception.class) public class ElnFieldCatalogServiceImpl implements ElnFieldCatalogService { static final String MENU_PERMISSION = "dianziJiluGonggongZiduanGuanli"; private static final String ENABLED = "enabled"; private static final String DISABLED = "disabled"; private static final int CODE_RETRY_LIMIT = 3; private static final Pattern CHECKBOX_GROUP_CODE = Pattern.compile("^[A-Za-z0-9_-]{1,80}$"); private final FieldCatalogStore store; private final ElnFieldProviderCatalog providerCatalog; private final Supplier userContextSupplier; private final Supplier idSupplier; private final ElnFieldCodeGenerator codeGenerator; private final Consumer auditRecorder; private final BooleanSupplier maintainPermission; private final BooleanSupplier promotePermission; private final ElnTestResultFieldCatalog testResultFieldCatalog; @Autowired public ElnFieldCatalogServiceImpl(ElnFieldDefMapper definitionMapper, ElnFieldVersionMapper versionMapper, ElnFieldProviderCatalog providerCatalog, ObjectProvider auditClientProvider, ObjectProvider testResultFieldCatalogProvider) { this(new MyBatisFieldCatalogStore(definitionMapper, versionMapper), providerCatalog, ElnFieldCatalogServiceImpl::currentUserContext, IdUtil::getSnowflakeNextIdStr, new ElnFieldCodeGenerator(() -> IdUtil.fastSimpleUUID().substring(0, 6)), event -> { AuditClient client = auditClientProvider.getIfAvailable(); if (client != null) { client.record(event); } }, ElnFieldCatalogServiceImpl::hasMaintainPermission, ElnFieldCatalogServiceImpl::hasPromotePermission, testResultFieldCatalogProvider.getIfAvailable()); } ElnFieldCatalogServiceImpl(FieldCatalogStore store, ElnFieldProviderCatalog providerCatalog, Supplier userContextSupplier, Supplier idSupplier, ElnFieldCodeGenerator codeGenerator, Consumer auditRecorder, BooleanSupplier maintainPermission, BooleanSupplier promotePermission) { this(store, providerCatalog, userContextSupplier, idSupplier, codeGenerator, auditRecorder, maintainPermission, promotePermission, null); } ElnFieldCatalogServiceImpl(FieldCatalogStore store, ElnFieldProviderCatalog providerCatalog, Supplier userContextSupplier, Supplier idSupplier, ElnFieldCodeGenerator codeGenerator, Consumer auditRecorder, BooleanSupplier maintainPermission, BooleanSupplier promotePermission, ElnTestResultFieldCatalog testResultFieldCatalog) { this.store = store; this.providerCatalog = providerCatalog; this.userContextSupplier = userContextSupplier; this.idSupplier = idSupplier; this.codeGenerator = codeGenerator; this.auditRecorder = auditRecorder; this.maintainPermission = maintainPermission; this.promotePermission = promotePermission; this.testResultFieldCatalog = testResultFieldCatalog; } @Override public void ensureTestSubmissionFields() { if (testResultFieldCatalog == null) return; UserContext context = userContextSupplier.get(); if (context == null) throw new DataException("请先登录后再维护字段目录"); testResultFieldCatalog.ensure(context.getTenantId(), context.getUserId()); } @Override public ElnFieldVos.Detail create(ElnFieldForms.Create form) { UserContext context = requireCatalogAccess(); if (form == null) { throw new DataException("字段信息不能为空"); } if (StringUtils.hasText(form.getZuoyongFanwei()) && !ElnFieldEnums.Scope.GLOBAL.name().equals(form.getZuoyongFanwei().trim())) { throw new DataException("字段维护页只能创建全局字段"); } validateDraftBasics(form); return createIdentity(context.getTenantId(), context.getUserId(), false, null, form.getBeizhu(), form, false); } @Override public ElnFieldVos.Detail createTemplateFieldAndPreRelease( ElnFieldForms.TemplateFieldCreate command) { if (command == null || !StringUtils.hasText(command.getTenantId()) || !StringUtils.hasText(command.getTemplateCode()) || !StringUtils.hasText(command.getOperatorId())) { throw new DataException("私有字段命令缺少租户、模板或操作者"); } UserContext routed = userContextSupplier.get(); if (routed == null || !command.getTenantId().trim().equals(routed.getTenantId())) { throw new DataException("私有字段命令租户与当前数据源租户不一致"); } if (!StringUtils.hasText(command.getZiduanMingcheng()) || !StringUtils.hasText(command.getShujuLeixing())) { throw new DataException("私有字段名称和类型不能为空"); } if (StringUtils.hasText(command.getRecommendedCheckboxGroupCode())) { throw new DataException("模板私有字段的选择组应在模板标注时配置"); } command.setShifouBitian(command.getShifouBitian() == null ? 0 : command.getShifouBitian()); command.setPiciMoshi(ElnFieldEnums.BatchMode.RECORD_PRIVATE.name()); command.setBinding(defaultManualBinding()); validateDraftBasics(command); String tenantId = command.getTenantId().trim(); String templateCode = command.getTemplateCode().trim(); if (store.hasEnabledTemplateFieldName(tenantId, templateCode, command.getZiduanMingcheng().trim())) { throw new DataException("当前模板已存在同名字段,请直接选择已有字段"); } return createIdentity(tenantId, command.getOperatorId().trim(), true, templateCode, command.getBeizhu(), command, true); } @Override @Transactional(readOnly = true) public ElnFieldVos.Detail detail(String id) { UserContext context = requireCatalogAccess(); ElnFieldDefEntity definition = requireDefinition(context.getTenantId(), id, false); return toDetail(definition, store.listVersions(context.getTenantId(), definition.getId())); } @Override @Transactional(readOnly = true) public IPage page(ElnFieldForms.Query query) { UserContext context = requireCatalogAccess(); ElnFieldForms.Query actual = query == null ? new ElnFieldForms.Query() : query; if (actual.getCurrentPage() < 1 || actual.getPageSize() < 1) { throw new DataException("分页参数必须大于 0"); } IPage source = store.page(context.getTenantId(), actual); List records = new ArrayList<>(); for (ElnFieldDefEntity definition : source.getRecords()) { records.add(toListItem(definition, store.listVersions(context.getTenantId(), definition.getId()))); } return new Page(source.getCurrent(), source.getSize(), source.getTotal()) .setRecords(records); } @Override public ElnFieldVos.Version createVersion(String fieldId, ElnFieldForms.VersionCreate form) { UserContext context = requireCatalogAccess(); ElnFieldDefEntity definition = requireDefinition(context.getTenantId(), fieldId, true); requireMutable(definition); requireEnabled(definition); if (form == null || !StringUtils.hasText(form.getSourceVersionId())) { throw new DataException("复制版本必须指定来源版本"); } ElnFieldVersionEntity source = requireVersion(context.getTenantId(), form.getSourceVersionId()); requireSameDefinition(definition, source); if (!ElnFieldEnums.VersionStatus.PRE_RELEASE.name().equals(source.getBizStatus()) && !ElnFieldEnums.VersionStatus.PUBLISHED.name().equals(source.getBizStatus())) { throw new DataException("只能基于预发布或已发布版本创建新版本"); } int nextVersion = nextVersionNumber(context.getTenantId(), definition.getId()); ElnFieldVersionEntity created = copyVersion(source); created.setId(idSupplier.get()); created.setBanbenHao(nextVersion); created.setBizStatus(ElnFieldEnums.VersionStatus.DRAFT.name()); created.setZuofeiYuanyin(null); created.setFCreatorUserId(context.getUserId()); created.setFLastModifyUserId(context.getUserId()); created.setFVersion(0); store.insertVersion(created); audit("ELN_FIELD_VERSION_CREATE", "eln_field_version", created.getId(), context, null, created, null); return toVersion(created); } @Override public ElnFieldVos.Version updateDraft(String versionId, ElnFieldForms.VersionUpdate form) { UserContext context = requireCatalogAccess(); ElnFieldVersionEntity version = requireVersion(context.getTenantId(), versionId); if (!ElnFieldEnums.VersionStatus.DRAFT.name().equals(version.getBizStatus())) { throw new DataException("只有草稿版本可以修改"); } ElnFieldDefEntity definition = requireDefinition( context.getTenantId(), version.getZiduanDingyiId(), true); requireMutable(definition); requireEnabled(definition); validateDraftBasics(form); ElnFieldVersionEntity before = copyVersion(version); applyRules(version, form); version.setFLastModifyUserId(context.getUserId()); store.updateVersion(version); audit("ELN_FIELD_VERSION_UPDATE", "eln_field_version", version.getId(), context, before, version, null); return toVersion(version); } @Override public void preRelease(String versionId) { UserContext context = requireCatalogAccess(); ElnFieldVersionEntity version = requireVersion(context.getTenantId(), versionId); if (!ElnFieldEnums.VersionStatus.DRAFT.name().equals(version.getBizStatus())) { throw new DataException("只有草稿版本可以提交预发布"); } ElnFieldDefEntity definition = requireDefinition( context.getTenantId(), version.getZiduanDingyiId(), true); requireMutable(definition); requireEnabled(definition); validateForReference(definition, version); changeVersionStatus(version, ElnFieldEnums.VersionStatus.PRE_RELEASE, null, context, "ELN_FIELD_VERSION_PRE_RELEASE"); } @Override public void publish(String versionId) { UserContext context = requireCatalogAccess(); ElnFieldVersionEntity version = requireVersion(context.getTenantId(), versionId); if (!ElnFieldEnums.VersionStatus.PRE_RELEASE.name().equals(version.getBizStatus())) { throw new DataException("只有预发布版本可以发布"); } ElnFieldDefEntity definition = requireDefinition( context.getTenantId(), version.getZiduanDingyiId(), true); requireMutable(definition); requireEnabled(definition); validateForReference(definition, version); changeVersionStatus(version, ElnFieldEnums.VersionStatus.PUBLISHED, null, context, "ELN_FIELD_VERSION_PUBLISH"); } @Override public void cancel(String versionId, String reason) { UserContext context = requireCatalogAccess(); String actualReason = requireReason(reason); ElnFieldVersionEntity version = requireVersion(context.getTenantId(), versionId); if (!ElnFieldEnums.VersionStatus.DRAFT.name().equals(version.getBizStatus()) && !ElnFieldEnums.VersionStatus.PRE_RELEASE.name().equals(version.getBizStatus())) { throw new DataException("只有草稿或预发布版本可以作废"); } changeVersionStatus(version, ElnFieldEnums.VersionStatus.CANCELLED, actualReason, context, "ELN_FIELD_VERSION_CANCEL"); } @Override public void switchCurrent(String fieldId, String versionId, String reason) { UserContext context = requireCatalogAccess(); String actualReason = requireReason(reason); ElnFieldDefEntity definition = requireDefinition(context.getTenantId(), fieldId, true); requireMutable(definition); requireEnabled(definition); ElnFieldVersionEntity version = requireVersion(context.getTenantId(), versionId); requireSameDefinition(definition, version); if (!ElnFieldEnums.VersionStatus.PUBLISHED.name().equals(version.getBizStatus())) { throw new DataException("当前版本只能切换到已发布版本"); } validateForReference(definition, version); ElnFieldDefEntity before = copyDefinition(definition); definition.setDangqianZiduanBanbenId(version.getId()); definition.setFLastModifyUserId(context.getUserId()); store.updateDefinition(definition); audit("ELN_FIELD_SWITCH_CURRENT", "eln_field_def", definition.getId(), context, before, definition, actualReason); } @Override public void promoteToGlobal(String fieldId, String reason) { UserContext context = requireCatalogAccess(); if (!promotePermission.getAsBoolean()) { throw new DataException("没有全局字段维护权限"); } String actualReason = requireReason(reason); ElnFieldDefEntity definition = requireDefinition(context.getTenantId(), fieldId, true); if (!ElnFieldEnums.Scope.TEMPLATE.name().equals(definition.getZuoyongFanwei())) { throw new DataException("只有模板私有字段可以升格为全局字段"); } ElnFieldDefEntity before = copyDefinition(definition); definition.setZuoyongFanwei(ElnFieldEnums.Scope.GLOBAL.name()); definition.setMubanBianhao(null); definition.setFLastModifyUserId(context.getUserId()); store.updateDefinition(definition); audit("ELN_FIELD_PROMOTE_GLOBAL", "eln_field_def", definition.getId(), context, before, definition, actualReason); } @Override public void enable(String fieldId, String reason) { changeEnabled(fieldId, ENABLED, requireReason(reason), "ELN_FIELD_ENABLE"); } @Override public void disable(String fieldId, String reason) { changeEnabled(fieldId, DISABLED, requireReason(reason), "ELN_FIELD_DISABLE"); } private ElnFieldVos.Detail createIdentity(String tenantId, String operatorId, boolean templateField, String templateCode, String remark, ElnFieldForms.VersionRule rules, boolean preRelease) { ElnFieldDefEntity definition = null; DuplicateKeyException lastCollision = null; for (int attempt = 0; attempt < CODE_RETRY_LIMIT; attempt++) { definition = new ElnFieldDefEntity(); definition.setId(idSupplier.get()); definition.setFTenantId(tenantId); definition.setZiduanBianma(codeGenerator.generate( rules.getZiduanMingcheng(), templateField)); definition.setZuoyongFanwei(templateField ? ElnFieldEnums.Scope.TEMPLATE.name() : ElnFieldEnums.Scope.GLOBAL.name()); definition.setMubanBianhao(templateCode); definition.setBizEnabled(ENABLED); definition.setBeizhu(trimToNull(remark)); definition.setFVersion(0); definition.setFDeleteMark(0); definition.setFCreatorUserId(operatorId); definition.setFLastModifyUserId(operatorId); Date now = new Date(); definition.setFCreatorTime(now); definition.setFLastModifyTime(now); try { store.insertDefinition(definition); lastCollision = null; break; } catch (DuplicateKeyException collision) { lastCollision = collision; } } if (lastCollision != null || definition == null) { throw new DataException("字段编码生成冲突,请重试"); } UserContext actor = new UserContext(tenantId, operatorId); audit("ELN_FIELD_CREATE", "eln_field_def", definition.getId(), actor, null, definition, null); ElnFieldVersionEntity version = new ElnFieldVersionEntity(); version.setId(idSupplier.get()); version.setFTenantId(tenantId); version.setZiduanDingyiId(definition.getId()); version.setBanbenHao(1); version.setBizStatus(ElnFieldEnums.VersionStatus.DRAFT.name()); version.setFVersion(0); version.setFDeleteMark(0); version.setFCreatorUserId(operatorId); version.setFLastModifyUserId(operatorId); applyRules(version, rules); store.insertVersion(version); audit("ELN_FIELD_VERSION_CREATE", "eln_field_version", version.getId(), actor, null, version, null); if (preRelease) { validateForReference(definition, version); changeVersionStatus(version, ElnFieldEnums.VersionStatus.PRE_RELEASE, null, actor, "ELN_FIELD_VERSION_PRE_RELEASE"); } return toDetail(definition, Collections.singletonList(version)); } private void changeEnabled(String fieldId, String target, String reason, String action) { UserContext context = requireCatalogAccess(); ElnFieldDefEntity definition = requireDefinition(context.getTenantId(), fieldId, true); requireMutable(definition); if (target.equals(definition.getBizEnabled())) { throw new DataException(ENABLED.equals(target) ? "字段已经启用" : "字段已经停用"); } ElnFieldDefEntity before = copyDefinition(definition); definition.setBizEnabled(target); definition.setFLastModifyUserId(context.getUserId()); store.updateDefinition(definition); audit(action, "eln_field_def", definition.getId(), context, before, definition, reason); } private void changeVersionStatus(ElnFieldVersionEntity version, ElnFieldEnums.VersionStatus status, String cancelReason, UserContext context, String action) { ElnFieldVersionEntity before = copyVersion(version); version.setBizStatus(status.name()); version.setZuofeiYuanyin(cancelReason); version.setFLastModifyUserId(context.getUserId()); store.updateVersion(version); audit(action, "eln_field_version", version.getId(), context, before, version, cancelReason); } private void validateDraftBasics(ElnFieldForms.VersionRule rules) { if (rules == null || !StringUtils.hasText(rules.getZiduanMingcheng())) { throw new DataException("字段名称不能为空"); } enumValue(ElnFieldEnums.DataType.class, rules.getShujuLeixing(), "字段类型无效"); if (rules.getShifouBitian() == null) { rules.setShifouBitian(0); } if (rules.getShifouBitian() != 0 && rules.getShifouBitian() != 1) { throw new DataException("必填标志只能为 0 或 1"); } if (!StringUtils.hasText(rules.getPiciMoshi())) { rules.setPiciMoshi(ElnFieldEnums.BatchMode.RECORD_PRIVATE.name()); } enumValue(ElnFieldEnums.BatchMode.class, rules.getPiciMoshi(), "批次模式无效"); } private void validateForReference(ElnFieldDefEntity definition, ElnFieldVersionEntity version) { enumValue(ElnFieldEnums.DataType.class, version.getShujuLeixing(), "字段类型无效"); enumValue(ElnFieldEnums.BatchMode.class, version.getPiciMoshi(), "批次模式无效"); if (version.getShifouBitian() == null || (version.getShifouBitian() != 0 && version.getShifouBitian() != 1)) { throw new DataException("必填标志只能为 0 或 1"); } validateControlConfig(version.getKongjianPeizhi()); ElnFieldForms.Binding binding = parseBinding(version.getBangdingPeizhi()); if (binding == null || binding.getRead() == null || binding.getWrite() == null) { throw new DataException("字段 binding 配置不完整"); } String source = binding.getRead().getSource(); enumValue(ElnFieldEnums.ReadSource.class, source, "字段读取来源无效"); if (!"ELN_RECORD".equals(binding.getWrite().getTarget())) { throw new DataException("字段写入目标必须为 ELN_RECORD"); } boolean external = ElnFieldEnums.ReadSource.LIMS_TASK.name().equals(source) || ElnFieldEnums.ReadSource.SYSTEM.name().equals(source); if (external) { if (!ElnFieldEnums.Scope.GLOBAL.name().equals(definition.getZuoyongFanwei())) { throw new DataException("外部来源只允许用于全局字段"); } ElnFieldProviderCatalog.Property property = providerCatalog.reference( source, binding.getRead().getProperty(), binding.getRead().getPropertyRevision()); if (property == null || !property.isReadOnly() || !property.isEnabled()) { throw new DataException("provider 属性不在允许目录中"); } if (!providerTypeCompatible(property.getDataType(), version.getShujuLeixing())) { throw new DataException("provider 属性类型与字段类型不兼容"); } if (!ElnFieldEnums.BatchMode.RECORD_PRIVATE.name().equals(version.getPiciMoshi())) { throw new DataException("外部来源字段不允许多批次共享"); } } else if (StringUtils.hasText(binding.getRead().getProperty())) { throw new DataException("NONE 来源不能配置 provider 属性"); } } private void applyRules(ElnFieldVersionEntity target, ElnFieldForms.VersionRule source) { target.setZiduanMingcheng(source.getZiduanMingcheng().trim()); target.setShujuLeixing(source.getShujuLeixing().trim()); target.setDanwei(trimToNull(source.getDanwei())); target.setShifouBitian(source.getShifouBitian()); target.setPiciMoshi(source.getPiciMoshi().trim()); target.setBangdingPeizhi(source.getBinding() == null ? null : JsonUtil.getObjectToString(source.getBinding())); target.setKongjianPeizhi(source.getKongjianPeizhi() == null ? null : JsonUtil.getObjectToString(source.getKongjianPeizhi())); applyCheckboxGroupRecommendation(target, source); } private void applyCheckboxGroupRecommendation(ElnFieldVersionEntity target, ElnFieldForms.VersionRule source) { String code = trimToNull(source.getRecommendedCheckboxGroupCode()); if (code == null) { target.setRecommendedCheckboxGroupCode(null); target.setRecommendedCheckboxMinSelected(null); target.setRecommendedCheckboxMaxSelected(null); return; } if (!CHECKBOX_GROUP_CODE.matcher(code).matches()) { throw new DataException("推荐选择组编码只能包含字母、数字、下划线或连字符,且长度不能超过 80"); } if (!ElnFieldEnums.DataType.CHECKBOX.name().equals(source.getShujuLeixing())) { throw new DataException("只有复选框字段可以配置推荐选择组"); } ElnFieldForms.Binding binding = source.getBinding(); String readSource = binding == null || binding.getRead() == null ? null : binding.getRead().getSource(); if (!ElnFieldEnums.ReadSource.NONE.name().equals(readSource)) { throw new DataException("外部只读字段不能配置推荐选择组"); } Integer min = source.getRecommendedCheckboxMinSelected(); Integer max = source.getRecommendedCheckboxMaxSelected(); min = min == null ? 0 : min; if (min < 0) { throw new DataException("推荐最少选择数不能小于 0"); } if (max != null && max < 1) { throw new DataException("推荐最多选择数必须大于 0,或设为不限"); } if (max != null && min > max) { throw new DataException("推荐最少选择数不能大于最多选择数"); } target.setRecommendedCheckboxGroupCode(code); target.setRecommendedCheckboxMinSelected(min); target.setRecommendedCheckboxMaxSelected(max); } private int nextVersionNumber(String tenantId, String definitionId) { int max = 0; for (ElnFieldVersionEntity version : store.listVersions(tenantId, definitionId)) { if (version.getBanbenHao() != null && version.getBanbenHao() > max) { max = version.getBanbenHao(); } } return max + 1; } private ElnFieldDefEntity requireDefinition(String tenantId, String id, boolean forUpdate) { if (!StringUtils.hasText(id)) { throw new DataException("字段 id 不能为空"); } ElnFieldDefEntity definition = store.findDefinition(tenantId, id.trim(), forUpdate); if (definition == null) { throw new DataException("字段不存在"); } return definition; } private ElnFieldVersionEntity requireVersion(String tenantId, String id) { if (!StringUtils.hasText(id)) { throw new DataException("字段版本 id 不能为空"); } ElnFieldVersionEntity version = store.findVersion(tenantId, id.trim()); if (version == null) { throw new DataException("字段版本不存在"); } return version; } private static void requireSameDefinition(ElnFieldDefEntity definition, ElnFieldVersionEntity version) { if (!definition.getId().equals(version.getZiduanDingyiId())) { throw new DataException("字段版本不属于当前字段"); } } private static void requireEnabled(ElnFieldDefEntity definition) { if (!ENABLED.equals(definition.getBizEnabled())) { throw new DataException("字段已停用,不能执行该操作"); } } private UserContext requireLogin() { UserContext context = userContextSupplier.get(); if (context == null || !StringUtils.hasText(context.getTenantId()) || !StringUtils.hasText(context.getUserId())) { throw new DataException("请先登录后再维护字段目录"); } return context; } private UserContext requireCatalogAccess() { UserContext context = requireLogin(); if (!maintainPermission.getAsBoolean()) { throw new DataException("没有全局字段维护权限"); } return context; } private static String requireReason(String reason) { if (!StringUtils.hasText(reason)) { throw new DataException("操作原因不能为空"); } return reason.trim(); } private static void requireMutable(ElnFieldDefEntity definition) { if (definition != null && ElnTestResultFields.isSystemField(definition.getZiduanBianma())) { throw new DataException("系统结果字段不允许通过通用字段目录修改"); } } private static > E enumValue(Class type, String value, String message) { if (!StringUtils.hasText(value)) { throw new DataException(message); } try { return Enum.valueOf(type, value.trim()); } catch (IllegalArgumentException exception) { throw new DataException(message); } } private static ElnFieldForms.Binding defaultManualBinding() { ElnFieldForms.Read read = new ElnFieldForms.Read(); read.setSource(ElnFieldEnums.ReadSource.NONE.name()); ElnFieldForms.Write write = new ElnFieldForms.Write(); write.setTarget("ELN_RECORD"); ElnFieldForms.Binding binding = new ElnFieldForms.Binding(); binding.setRead(read); binding.setWrite(write); return binding; } private static ElnFieldForms.Binding parseBinding(String json) { return StringUtils.hasText(json) ? JsonUtil.getJsonToBean(json, ElnFieldForms.Binding.class) : null; } private static boolean providerTypeCompatible(String providerType, String fieldType) { if ("BOOLEAN".equals(providerType)) return ElnFieldEnums.DataType.CHECKBOX.name().equals(fieldType); if ("TEXT".equals(providerType)) { return ElnFieldEnums.DataType.TEXT.name().equals(fieldType) || ElnFieldEnums.DataType.TEXTAREA.name().equals(fieldType) || ElnFieldEnums.DataType.SELECT.name().equals(fieldType); } return providerType != null && providerType.equals(fieldType); } private static void validateControlConfig(String json) { if (!StringUtils.hasText(json)) { return; } try { Map config = JsonUtil.getJsonToBean(json, Map.class); if (config == null) { throw new DataException("控件配置必须是 JSON 对象"); } } catch (RuntimeException exception) { throw new DataException("控件配置必须是 JSON 对象"); } } @SuppressWarnings("unchecked") private static Map parseMap(String json) { return StringUtils.hasText(json) ? JsonUtil.getJsonToBean(json, Map.class) : null; } private ElnFieldVos.Detail toDetail(ElnFieldDefEntity definition, List versions) { ElnFieldVos.Detail result = new ElnFieldVos.Detail(); result.setId(definition.getId()); result.setZiduanBianma(definition.getZiduanBianma()); result.setZuoyongFanwei(definition.getZuoyongFanwei()); result.setMubanBianhao(definition.getMubanBianhao()); result.setDangqianZiduanBanbenId(definition.getDangqianZiduanBanbenId()); result.setBizEnabled(definition.getBizEnabled()); result.setBeizhu(definition.getBeizhu()); List sorted = new ArrayList<>(versions); sorted.sort(Comparator.comparing(ElnFieldVersionEntity::getBanbenHao)); List versionVos = new ArrayList<>(); for (ElnFieldVersionEntity version : sorted) { versionVos.add(toVersion(version)); } result.setVersions(versionVos); return result; } private ElnFieldVos.ListItem toListItem(ElnFieldDefEntity definition, List versions) { ElnFieldVos.ListItem result = new ElnFieldVos.ListItem(); result.setId(definition.getId()); result.setZiduanBianma(definition.getZiduanBianma()); result.setZuoyongFanwei(definition.getZuoyongFanwei()); result.setMubanBianhao(definition.getMubanBianhao()); result.setDangqianZiduanBanbenId(definition.getDangqianZiduanBanbenId()); result.setBizEnabled(definition.getBizEnabled()); result.setGengxinShijian(definition.getFLastModifyTime() == null ? definition.getFCreatorTime() : definition.getFLastModifyTime()); int latestPublished = 0; for (ElnFieldVersionEntity version : versions) { if (version.getId().equals(definition.getDangqianZiduanBanbenId())) { result.setZiduanMingcheng(version.getZiduanMingcheng()); result.setDangqianBanbenHao(version.getBanbenHao()); } if (ElnFieldEnums.VersionStatus.PUBLISHED.name().equals(version.getBizStatus()) && version.getBanbenHao() != null && version.getBanbenHao() > latestPublished) { latestPublished = version.getBanbenHao(); } } if (result.getZiduanMingcheng() == null && !versions.isEmpty()) { ElnFieldVersionEntity latest = Collections.max(versions, Comparator.comparing(ElnFieldVersionEntity::getBanbenHao)); result.setZiduanMingcheng(latest.getZiduanMingcheng()); } result.setZuixinFabuBanbenHao(latestPublished == 0 ? null : latestPublished); result.setMubanYinyongShu(0L); return result; } private ElnFieldVos.Version toVersion(ElnFieldVersionEntity source) { ElnFieldVos.Version result = new ElnFieldVos.Version(); result.setId(source.getId()); result.setZiduanDingyiId(source.getZiduanDingyiId()); result.setBanbenHao(source.getBanbenHao()); result.setZiduanMingcheng(source.getZiduanMingcheng()); result.setShujuLeixing(source.getShujuLeixing()); result.setDanwei(source.getDanwei()); result.setShifouBitian(source.getShifouBitian()); result.setPiciMoshi(source.getPiciMoshi()); result.setBinding(parseBinding(source.getBangdingPeizhi())); result.setKongjianPeizhi(parseMap(source.getKongjianPeizhi())); result.setRecommendedCheckboxGroupCode(source.getRecommendedCheckboxGroupCode()); result.setRecommendedCheckboxMinSelected(source.getRecommendedCheckboxMinSelected()); result.setRecommendedCheckboxMaxSelected(source.getRecommendedCheckboxMaxSelected()); result.setBizStatus(source.getBizStatus()); result.setZuofeiYuanyin(source.getZuofeiYuanyin()); result.setChuangjianShijian(source.getFCreatorTime()); result.setGengxinShijian(source.getFLastModifyTime()); return result; } private static ElnFieldDefEntity copyDefinition(ElnFieldDefEntity source) { return JsonUtil.getJsonToBean(source, ElnFieldDefEntity.class); } private static ElnFieldVersionEntity copyVersion(ElnFieldVersionEntity source) { return JsonUtil.getJsonToBean(source, ElnFieldVersionEntity.class); } private void audit(String actionCode, String table, String targetId, UserContext context, Object before, Object after, String reason) { Map snapshot = new LinkedHashMap<>(); snapshot.put("before", before); snapshot.put("after", after); auditRecorder.accept(AuditEventDTO.builder() .tenantId(context.getTenantId()) .operatorId(context.getUserId()) .eventTime(new Date()) .eventType("DATA_CHANGE") .actionCode(actionCode) .actionLabel(actionCode) .bizModule("eln") .bizType("eln_field_def".equals(table) ? "eln_field" : "eln_field_version") .targetTable(table) .targetId(targetId) .reason(reason) .dataSnapshot(JsonUtil.getObjectToString(snapshot)) .build()); } private static String trimToNull(String value) { return StringUtils.hasText(value) ? value.trim() : null; } private static UserContext currentUserContext() { UserInfo user = UserProvider.getUser(); String tenantId = null; TenantVO routed = TenantDataSourceUtil.getTenantInfo(); if (routed != null && StringUtils.hasText(routed.getEnCode())) { tenantId = routed.getEnCode().trim(); } else if (user != null && StringUtils.hasText(user.getTenantId())) { tenantId = user.getTenantId().trim(); } if (!StringUtils.hasText(tenantId)) { tenantId = "0"; } return new UserContext(tenantId, user == null ? null : trimToNull(user.getUserId())); } private static boolean hasPromotePermission() { return hasMenuPermission(); } private static boolean hasMaintainPermission() { return hasMenuPermission(); } private static boolean hasMenuPermission() { UserInfo user = UserProvider.getUser(); return user != null && (Boolean.TRUE.equals(user.getIsAdministrator()) || StpUtil.hasPermission(MENU_PERMISSION)); } static final class UserContext { private final String tenantId; private final String userId; UserContext(String tenantId, String userId) { this.tenantId = tenantId; this.userId = userId; } String getTenantId() { return tenantId; } String getUserId() { return userId; } } interface FieldCatalogStore { void insertDefinition(ElnFieldDefEntity entity); void insertVersion(ElnFieldVersionEntity entity); ElnFieldDefEntity findDefinition(String tenantId, String id, boolean forUpdate); ElnFieldVersionEntity findVersion(String tenantId, String id); List listVersions(String tenantId, String definitionId); boolean hasEnabledTemplateFieldName(String tenantId, String templateCode, String fieldName); IPage page(String tenantId, ElnFieldForms.Query query); void updateDefinition(ElnFieldDefEntity entity); void updateVersion(ElnFieldVersionEntity entity); } private static final class MyBatisFieldCatalogStore implements FieldCatalogStore { private final ElnFieldDefMapper definitionMapper; private final ElnFieldVersionMapper versionMapper; private MyBatisFieldCatalogStore(ElnFieldDefMapper definitionMapper, ElnFieldVersionMapper versionMapper) { this.definitionMapper = definitionMapper; this.versionMapper = versionMapper; } @Override public void insertDefinition(ElnFieldDefEntity entity) { if (definitionMapper.insertIgnoreCode(entity) != 1) { throw new DuplicateKeyException("字段编码已存在"); } } @Override public void insertVersion(ElnFieldVersionEntity entity) { versionMapper.insert(entity); } @Override public ElnFieldDefEntity findDefinition(String tenantId, String id, boolean forUpdate) { LambdaQueryWrapper query = Wrappers.lambdaQuery() .eq(ElnFieldDefEntity::getFTenantId, tenantId) .eq(ElnFieldDefEntity::getId, id) .and(item -> item.isNull(ElnFieldDefEntity::getFDeleteMark) .or().eq(ElnFieldDefEntity::getFDeleteMark, 0)); if (forUpdate) { query.last("FOR UPDATE"); } return definitionMapper.selectOne(query); } @Override public ElnFieldVersionEntity findVersion(String tenantId, String id) { return versionMapper.selectOne(Wrappers.lambdaQuery() .eq(ElnFieldVersionEntity::getFTenantId, tenantId) .eq(ElnFieldVersionEntity::getId, id) .and(item -> item.isNull(ElnFieldVersionEntity::getFDeleteMark) .or().eq(ElnFieldVersionEntity::getFDeleteMark, 0))); } @Override public List listVersions(String tenantId, String definitionId) { return versionMapper.selectList(Wrappers.lambdaQuery() .eq(ElnFieldVersionEntity::getFTenantId, tenantId) .eq(ElnFieldVersionEntity::getZiduanDingyiId, definitionId) .and(item -> item.isNull(ElnFieldVersionEntity::getFDeleteMark) .or().eq(ElnFieldVersionEntity::getFDeleteMark, 0)) .orderByAsc(ElnFieldVersionEntity::getBanbenHao)); } @Override public boolean hasEnabledTemplateFieldName(String tenantId, String templateCode, String fieldName) { List definitions = definitionMapper.selectList( Wrappers.lambdaQuery() .eq(ElnFieldDefEntity::getFTenantId, tenantId) .eq(ElnFieldDefEntity::getZuoyongFanwei, ElnFieldEnums.Scope.TEMPLATE.name()) .eq(ElnFieldDefEntity::getMubanBianhao, templateCode) .eq(ElnFieldDefEntity::getBizEnabled, ENABLED) .and(item -> item.isNull(ElnFieldDefEntity::getFDeleteMark) .or().eq(ElnFieldDefEntity::getFDeleteMark, 0))); List definitionIds = new ArrayList<>(); for (ElnFieldDefEntity definition : definitions) { definitionIds.add(definition.getId()); } return !definitionIds.isEmpty() && versionMapper.selectCount( Wrappers.lambdaQuery() .eq(ElnFieldVersionEntity::getFTenantId, tenantId) .in(ElnFieldVersionEntity::getZiduanDingyiId, definitionIds) .eq(ElnFieldVersionEntity::getZiduanMingcheng, fieldName) .and(item -> item.isNull(ElnFieldVersionEntity::getFDeleteMark) .or().eq(ElnFieldVersionEntity::getFDeleteMark, 0))) > 0; } @Override public IPage page(String tenantId, ElnFieldForms.Query form) { LambdaQueryWrapper query = Wrappers.lambdaQuery() .eq(ElnFieldDefEntity::getFTenantId, tenantId) .and(item -> item.isNull(ElnFieldDefEntity::getFDeleteMark) .or().eq(ElnFieldDefEntity::getFDeleteMark, 0)); if (StringUtils.hasText(form.getZuoyongFanwei())) { query.eq(ElnFieldDefEntity::getZuoyongFanwei, form.getZuoyongFanwei().trim()); } if (StringUtils.hasText(form.getBizEnabled())) { query.eq(ElnFieldDefEntity::getBizEnabled, form.getBizEnabled().trim()); } if (StringUtils.hasText(form.getMubanBianhao())) { query.eq(ElnFieldDefEntity::getMubanBianhao, form.getMubanBianhao().trim()); } if (StringUtils.hasText(form.getKeyword())) { String keyword = form.getKeyword().trim(); List definitionIds = versionMapper.selectObjs( Wrappers.lambdaQuery() .select(ElnFieldVersionEntity::getZiduanDingyiId) .eq(ElnFieldVersionEntity::getFTenantId, tenantId) .like(ElnFieldVersionEntity::getZiduanMingcheng, keyword)); query.and(item -> { item.like(ElnFieldDefEntity::getZiduanBianma, keyword); if (!definitionIds.isEmpty()) { item.or().in(ElnFieldDefEntity::getId, definitionIds); } }); } query.orderByDesc(ElnFieldDefEntity::getFLastModifyTime) .orderByDesc(ElnFieldDefEntity::getFCreatorTime); return definitionMapper.selectPage( new Page<>(form.getCurrentPage(), form.getPageSize()), query); } @Override public void updateDefinition(ElnFieldDefEntity entity) { if (definitionMapper.updateById(entity) != 1) { throw new DataException("字段状态已变化,请刷新后重试"); } } @Override public void updateVersion(ElnFieldVersionEntity entity) { if (versionMapper.updateById(entity) != 1) { throw new DataException("字段版本状态已变化,请刷新后重试"); } } } }