liu
2 天以前 43aab58c9ecb1203770ba382efbdbae299ca87ec
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/storage/SystemAgentImportBridgeService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,169 @@
package cn.iocoder.yudao.module.system.service.storage;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.system.controller.admin.storage.vo.SystemStorageAttachmentSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.storage.SystemAgentImportSessionDO;
import cn.iocoder.yudao.module.system.dal.dataobject.storage.SystemStorageAttachmentDO;
import cn.iocoder.yudao.module.system.dal.dataobject.storage.SystemStorageBlobDO;
import cn.iocoder.yudao.module.system.dal.mysql.storage.SystemAgentImportSessionMapper;
import cn.iocoder.yudao.module.system.dal.mysql.storage.SystemStorageAttachmentMapper;
import cn.iocoder.yudao.module.system.framework.agent.AgentBridgeException;
import cn.iocoder.yudao.module.system.framework.storage.config.StorageProperties;
import jakarta.annotation.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
import java.util.List;
import java.util.Objects;
@Service
public class SystemAgentImportBridgeService {
    public static final long MAX_FILE_BYTES = 8L * 1024 * 1024;
    public static final String RECORD_TYPE = "agent_import_session";
    @Resource
    private SystemAgentImportSessionMapper sessionMapper;
    @Resource
    private SystemStorageAttachmentMapper attachmentMapper;
    @Resource
    private SystemStorageBlobService blobService;
    @Resource
    private SystemStorageAttachmentService attachmentService;
    @Resource
    private StorageProperties storageProperties;
    @Transactional(rollbackFor = Exception.class)
    public Long createSession() {
        Long userId = SecurityFrameworkUtils.getLoginUserId();
        if (userId == null) {
            throw new AgentBridgeException(HttpStatus.UNAUTHORIZED, "UNAUTHORIZED", "未登录或登录已过期");
        }
        SystemAgentImportSessionDO session = new SystemAgentImportSessionDO();
        session.setOwnerUserId(userId);
        session.setStatus("active");
        sessionMapper.insert(session);
        return session.getId();
    }
    @Transactional(rollbackFor = Exception.class)
    public Long bindAttachment(Long sessionId, Long blobId) {
        requireOwnedSession(sessionId);
        SystemStorageBlobDO blob = blobService.getStorageBlob(blobId);
        requireOwnedBlob(blob);
        SystemStorageAttachmentSaveReqVO req = new SystemStorageAttachmentSaveReqVO();
        req.setApplication("file");
        req.setRecordType(RECORD_TYPE);
        req.setRecordId(sessionId);
        SystemStorageAttachmentSaveReqVO.BlobItem item = new SystemStorageAttachmentSaveReqVO.BlobItem();
        item.setBlobId(blobId);
        req.setBlobItems(List.of(item));
        attachmentService.bindAttachments(req);
        // é€šç”¨ bind å¯¹åŒä¸€ä¼šè¯æ˜¯æ›¿æ¢è¯­ä¹‰ï¼šé‡å¤ç»‘定时旧附件行会被删除。
        return attachmentMapper.selectListByApplicationAndRecordTypeAndRecordId("file", RECORD_TYPE, sessionId)
                .stream().filter(row -> blobId.equals(row.getStorageBlobId()))
                .map(SystemStorageAttachmentDO::getId).findFirst()
                .orElseThrow(() -> new AgentBridgeException(HttpStatus.CONFLICT, "ATTACHMENT_BIND_FAILED",
                        "附件绑定失败,请重新上传后再试", true, null));
    }
    /** åˆ é™¤è¡¥å½•附件所属会话:附件关系、blob è®°å½•与磁盘文件一并清理,附件不存在时幂等返回。 */
    @Transactional(rollbackFor = Exception.class)
    public void deleteImportAttachment(Long attachmentId) {
        SystemStorageAttachmentDO attachment = attachmentMapper.selectById(attachmentId);
        if (attachment == null) {
            return;
        }
        if (!RECORD_TYPE.equals(attachment.getRecordType())) {
            throw new AgentBridgeException(HttpStatus.FORBIDDEN, "ATTACHMENT_FORBIDDEN", "附件不可访问");
        }
        Long sessionId = attachment.getRecordId();
        requireOwnedSession(sessionId);
        // æŒ‰ä¼šè¯æ•´æ‰¹åˆ é™¤ï¼ŒåŒä¸€ä¼šè¯å†…被替换的旧附件行与 blob æ–‡ä»¶éƒ½ä¸ä¼šæ®‹ç•™
        attachmentService.deleteAttachmentsByRecordTypeAndRecordId(RECORD_TYPE, sessionId);
        sessionMapper.deleteById(sessionId);
    }
    public SystemAgentAttachmentRef requireOwnedAttachmentRef(Long attachmentId) {
        SystemStorageAttachmentDO attachment = attachmentMapper.selectById(attachmentId);
        if (attachment == null) {
            throw new AgentBridgeException(HttpStatus.CONFLICT, "ATTACHMENT_REPLACED",
                    "附件已失效(可能被重新上传替换),请重新上传后再试", true, null);
        }
        if (!RECORD_TYPE.equals(attachment.getRecordType())) {
            throw new AgentBridgeException(HttpStatus.FORBIDDEN, "ATTACHMENT_FORBIDDEN", "附件不可访问");
        }
        requireOwnedSession(attachment.getRecordId());
        SystemStorageBlobDO blob = blobService.getStorageBlob(attachment.getStorageBlobId());
        requireOwnedBlob(blob);
        Path file = resolveBlobFile(blob);
        String filename = blob.getOriginalFilename();
        if (filename == null || filename.isBlank()) {
            filename = "attachment";
        }
        return new SystemAgentAttachmentRef(attachmentId, attachment.getRecordId(), filename,
                blob.getByteSize(), file);
    }
    public String sha256OfAttachment(Long attachmentId) {
        SystemAgentAttachmentRef ref = requireOwnedAttachmentRef(attachmentId);
        try (InputStream in = Files.newInputStream(ref.path())) {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] buffer = new byte[8192];
            long total = 0;
            int read;
            while ((read = in.read(buffer)) != -1) {
                total += read;
                if (total > MAX_FILE_BYTES) {
                    throw new AgentBridgeException(HttpStatus.FORBIDDEN, "FILE_TOO_LARGE", "附件超过 8 MiB");
                }
                digest.update(buffer, 0, read);
            }
            return HexFormat.of().formatHex(digest.digest());
        } catch (IOException ex) {
            throw new AgentBridgeException(HttpStatus.FORBIDDEN, "ATTACHMENT_UNREADABLE", "附件不可访问");
        } catch (NoSuchAlgorithmException ex) {
            throw new IllegalStateException("SHA-256 ä¸å¯ç”¨", ex);
        }
    }
    private Path resolveBlobFile(SystemStorageBlobDO blob) {
        try {
            Path root = Path.of(storageProperties.getPath()).toRealPath();
            Path file = root.resolve(blob.getPath() == null ? "" : blob.getPath())
                    .resolve(blob.getUidFilename()).toRealPath();
            if (!file.startsWith(root) || !Files.isRegularFile(file)
                    || Files.size(file) > MAX_FILE_BYTES) {
                throw new AgentBridgeException(HttpStatus.FORBIDDEN, "ATTACHMENT_UNREADABLE", "附件不可访问");
            }
            return file;
        } catch (IOException ex) {
            throw new AgentBridgeException(HttpStatus.FORBIDDEN, "ATTACHMENT_UNREADABLE", "附件不可访问");
        }
    }
    private void requireOwnedSession(Long sessionId) {
        SystemAgentImportSessionDO session = sessionMapper.selectById(sessionId);
        Long userId = SecurityFrameworkUtils.getLoginUserId();
        if (session == null || userId == null || !Objects.equals(session.getOwnerUserId(), userId)
                || !"active".equals(session.getStatus())) {
            throw new AgentBridgeException(HttpStatus.FORBIDDEN, "SESSION_FORBIDDEN", "补录会话不可访问");
        }
    }
    private void requireOwnedBlob(SystemStorageBlobDO blob) {
        Long userId = SecurityFrameworkUtils.getLoginUserId();
        if (blob == null || userId == null || !String.valueOf(userId).equals(blob.getCreator())
                || blob.getByteSize() == null || blob.getByteSize() > MAX_FILE_BYTES) {
            throw new AgentBridgeException(HttpStatus.FORBIDDEN, "BLOB_FORBIDDEN", "文件不可访问或超过 8 MiB");
        }
    }
}