package jnpf.dmsService; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import jnpf.base.UserInfo; import jnpf.base.DictionaryDataApi; import jnpf.base.entity.DictionaryDataEntity; import jnpf.dmsEntity.permission.entity.DmsDocumentEntity; import jnpf.dmsEntity.permission.entity.DmsFileVersionEntity; import jnpf.dmsEntity.preview.enums.DmsWatermarkTargetType; import jnpf.dmsEntity.preview.model.DmsWatermarkPositionSnapshot; import jnpf.dmsEntity.preview.model.DmsWatermarkSnapshot; import jnpf.dmsEntity.watermark.DmsWatermarkConfigEntity; import jnpf.dmsEntity.watermark.DmsWatermarkPositionEntity; import jnpf.dmsEntity.watermark.DmsWatermarkParameters; import jnpf.dmsIntegration.audit.DmsVersionStateAuditRow; import jnpf.dmsMapper.DmsDocumentMapper; import jnpf.dmsMapper.DmsVersionStateMapper; import jnpf.dmsMapper.DmsWatermarkConfigMapper; import jnpf.dmsMapper.DmsWatermarkPositionMapper; import jnpf.permission.OrganizeApi; import jnpf.permission.entity.OrganizeEntity; import lombok.RequiredArgsConstructor; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @Service @RequiredArgsConstructor public class DmsWatermarkPreviewResolver { private final DmsWatermarkConfigMapper configMapper; private final DmsWatermarkPositionMapper positionMapper; private final DmsDocumentMapper documentMapper; private final DmsVersionStateMapper versionStateMapper; private final OrganizeApi organizeApi; private final DictionaryDataApi dictionaryDataApi; @Transactional(readOnly = true) public List resolve(UserInfo user, String tenantId, DmsFileVersionEntity version) { return resolve(user, tenantId, version, Scene.PREVIEW, new DmsWatermarkParameters()); } @Transactional(readOnly = true) public List resolveDownload(UserInfo user, String tenantId, DmsFileVersionEntity version) { return resolve(user, tenantId, version, Scene.DOWNLOAD, new DmsWatermarkParameters()); } @Transactional(readOnly = true) public List resolvePrint(UserInfo user, String tenantId, DmsFileVersionEntity version) { return resolve(user, tenantId, version, Scene.PRINT, new DmsWatermarkParameters()); } @Transactional(readOnly = true) public List resolve(UserInfo user, String tenantId, DmsFileVersionEntity version, DmsWatermarkParameters parameters) { return resolve(user, tenantId, version, Scene.PREVIEW, parameters); } @Transactional(readOnly = true) public List resolveDownload(UserInfo user, String tenantId, DmsFileVersionEntity version, DmsWatermarkParameters parameters) { return resolve(user, tenantId, version, Scene.DOWNLOAD, parameters); } @Transactional(readOnly = true) public List resolvePrint(UserInfo user, String tenantId, DmsFileVersionEntity version, DmsWatermarkParameters parameters) { return resolve(user, tenantId, version, Scene.PRINT, parameters); } private List resolve(UserInfo user, String tenantId, DmsFileVersionEntity version, Scene scene, DmsWatermarkParameters parameters) { DmsWatermarkParameters options = parameters.normalized(); List businessTypes = options.getWatermarkBusinessTypes(); if (businessTypes == null || businessTypes.isEmpty()) return Collections.emptyList(); List dictionary = dictionaryDataApi.getList("879384732964547653", "true"); if (dictionary == null) throw new jnpf.exception.DataException("水印业务类型字典查询失败"); java.util.Set allowedTypes = dictionary.stream() .filter(item -> item != null && item.getDeleteMark() == null && Integer.valueOf(1).equals(item.getEnabledMark()) && "879384732964547653".equals(item.getDictionaryTypeId())) .map(DictionaryDataEntity::getEnCode) .collect(Collectors.toSet()); if (!allowedTypes.containsAll(businessTypes)) { throw new jnpf.exception.DataException("水印业务类型包含无效的字典编码"); } if (DmsWatermarkTargetType.from(version.getFileName(), version.getMimeType()) == DmsWatermarkTargetType.UNSUPPORTED) { return Collections.emptyList(); } DmsVersionStateAuditRow state = versionStateMapper.selectAuditState(tenantId, version.getId()); if (state == null || state.getLifecycleStatus() == null || !java.util.Set.of("EFFECTIVE", "SUPERSEDED", "EXPIRED", "REVOKED", "OBSOLETE", "INVALIDATED").contains(state.getLifecycleStatus())) { return Collections.emptyList(); } boolean current = "EFFECTIVE".equals(state.getLifecycleStatus()); LambdaQueryWrapper configQuery = new LambdaQueryWrapper() .eq(DmsWatermarkConfigEntity::getTenantId, tenantId) .eq(DmsWatermarkConfigEntity::getEnabled, true); if (scene == Scene.DOWNLOAD) { configQuery.eq(DmsWatermarkConfigEntity::getLoadDownload, true); } else if (scene == Scene.PRINT) { configQuery.eq(DmsWatermarkConfigEntity::getLoadPrint, true); } else { configQuery.eq(DmsWatermarkConfigEntity::getLoadPreview, true); } if (current) { configQuery.in(DmsWatermarkConfigEntity::getVersionScope, "ALL", "CURRENT"); } else { configQuery.eq(DmsWatermarkConfigEntity::getVersionScope, "ALL"); } List configs = configMapper.selectList(configQuery .isNull(DmsWatermarkConfigEntity::getDeletedAt) .orderByAsc(DmsWatermarkConfigEntity::getCreatedAt)); if (configs.isEmpty()) { return Collections.emptyList(); } DmsDocumentEntity document = documentMapper.selectByTenantAndId(tenantId, version.getDocumentId()); VariableContext variables = variables(user, document, version, current); List configIds = configs.stream().map(DmsWatermarkConfigEntity::getId) .collect(Collectors.toList()); java.util.Set matchingIds = new java.util.HashSet<>(configMapper.selectMatchingConfigIds( tenantId, configIds, businessTypes)); if (matchingIds.isEmpty()) return Collections.emptyList(); List positions = positionMapper.selectList( new LambdaQueryWrapper() .eq(DmsWatermarkPositionEntity::getTenantId, tenantId) .in(DmsWatermarkPositionEntity::getConfigId, configIds) .orderByAsc(DmsWatermarkPositionEntity::getSortOrder)); List result = new ArrayList<>(); for (DmsWatermarkConfigEntity config : configs) { if (!matchingIds.contains(config.getId())) continue; boolean custom = "BARCODE".equals(config.getWatermarkType()) && DmsWatermarkParameters.CUSTOM_BARCODE_TEMPLATE.equals(config.getContentTemplate()); if (custom && options.getCustomBarcode() == null) continue; List configuredPositions = positions.stream() .filter(position -> config.getId().equals(position.getConfigId())) .map(this::positionSnapshot) .collect(Collectors.toList()); List entries = custom ? options.getCustomBarcode() : Collections.singletonList(null); for (DmsWatermarkParameters.CustomBarcode entry : entries) { DmsWatermarkSnapshot snapshot = new DmsWatermarkSnapshot(); BeanUtils.copyProperties(config, snapshot); snapshot.setHorizontalAlign(config.getHorizontalAlign() == null ? "LEFT" : config.getHorizontalAlign()); snapshot.setVerticalAlign(config.getVerticalAlign() == null ? "TOP" : config.getVerticalAlign()); snapshot.setCustomBarcode(custom); // Barcode values and labels are literal runtime data, never template expressions. snapshot.setContent(custom ? entry.getBarValue() : replaceVariables(config.getContentTemplate(), variables)); if (custom) { snapshot.setBarcodeText(entry.getBarText()); snapshot.setShowBarcodeText(true); snapshot.setRenderPages(entry.getPage() == null ? null : new ArrayList<>(Collections.singletonList(entry.getPage()))); } if ("BARCODE".equals(snapshot.getWatermarkType()) && "CODE_128".equals(snapshot.getBarcodeFormat()) && snapshot.getContent() != null && snapshot.getContent().chars().anyMatch(c -> c > 127 && (c < 241 || c > 244))) { throw new jnpf.exception.DataException("水印“" + config.getName() + "”使用 Code 128,但条码内容包含中文等不支持的字符,请将条码格式改为二维码"); } snapshot.setPositions(configuredPositions); if (!snapshot.getPositions().isEmpty()) result.add(snapshot); } } return result; } private VariableContext variables(UserInfo user, DmsDocumentEntity document, DmsFileVersionEntity version, boolean current) { String department = user.getOrganize(); if (!StringUtils.hasText(department) && StringUtils.hasText(user.getOrganizeId())) { OrganizeEntity organization = organizeApi.getInfoById(user.getOrganizeId().trim()); department = organization == null ? null : organization.getFullName(); } String userName = StringUtils.hasText(user.getUserName()) ? user.getUserName().trim() : user.getUserId().trim(); String versionText = StringUtils.hasText(version.getVersionLabel()) ? version.getVersionLabel().trim() : version.getVersionNo() == null ? "" : String.valueOf(version.getVersionNo()); return new VariableContext(userName, text(department), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), version.getFileName(), document == null ? "" : text(document.getDocumentNo()), versionText, current ? "现行版本" : "历史版本"); } private String replaceVariables(String template, VariableContext value) { if (template == null) { return null; } return template.replace("{/CUN}", value.userName) .replace("{/CDN}", value.department) .replace("{/CDT}", value.time) .replace("{/FILENAME}", value.fileName) .replace("{/FILENO}", value.fileNo) .replace("{/VERSION}", value.version) .replace("{/FILESTATE}", value.fileState); } private DmsWatermarkPositionSnapshot positionSnapshot(DmsWatermarkPositionEntity value) { DmsWatermarkPositionSnapshot result = new DmsWatermarkPositionSnapshot(); BeanUtils.copyProperties(value, result); return result; } private String text(String value) { return value == null ? "" : value.trim(); } private enum Scene { PREVIEW, DOWNLOAD, PRINT } private static final class VariableContext { private final String userName; private final String department; private final String time; private final String fileName; private final String fileNo; private final String version; private final String fileState; private VariableContext(String userName, String department, String time, String fileName, String fileNo, String version, String fileState) { this.userName = userName; this.department = department; this.time = time; this.fileName = fileName; this.fileNo = fileNo; this.version = version; this.fileState = fileState; } } }