package cn.iocoder.yudao.module.system.controller.admin.agent; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.module.system.framework.agent.AgentBridgeException; import cn.iocoder.yudao.module.system.framework.agent.AgentPrincipal; import cn.iocoder.yudao.module.system.framework.agent.SystemAgentBridgeGuard; import cn.iocoder.yudao.module.system.service.storage.SystemAgentAttachmentRef; import cn.iocoder.yudao.module.system.service.storage.SystemAgentImportBridgeService; import jakarta.annotation.Resource; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.Data; import org.springframework.core.io.FileSystemResource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Map; import java.util.Set; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; /** Read-only bridge for material onboarding; business writes live in the MDM module. */ @RestController @RequestMapping("/agent/bridge") public class SystemAgentImportBridgeController { private static final Set ACTIONS = Set.of("material_import.prepare", "material_import.read", "material_import.review", "material_import.preflight", "material_import.commit", "material_import.read_receipt"); @Resource private SystemAgentImportBridgeService bridgeService; @Resource private SystemAgentBridgeGuard bridgeGuard; @PostMapping("/import-sessions") @PreAuthorize("@ss.hasPermission('mdm:item:import')") public CommonResult createSession() { return success(bridgeService.createSession()); } @PostMapping("/import-sessions/{sessionId}/attachments") @PreAuthorize("@ss.hasPermission('mdm:item:import')") public CommonResult bindAttachment(@PathVariable Long sessionId, @Valid @RequestBody BindRequest request) { return success(bridgeService.bindAttachment(sessionId, request.getBlobId())); } @PostMapping("/authorize") @PreAuthorize("@ss.hasPermission('mdm:item:import')") public CommonResult> authorize( @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken, @Valid @RequestBody AuthorizeRequest request) { AgentPrincipal principal = bridgeGuard.require(serviceToken, SystemAgentBridgeGuard.PERMISSION_ITEM_IMPORT); if (!ACTIONS.contains(request.getAction())) { throw new AgentBridgeException(HttpStatus.FORBIDDEN, "ACTION_FORBIDDEN", "不支持的智能体操作"); } if (requiresAttachmentOwnership(request.getAction())) { bridgeService.requireOwnedAttachmentRef(parseId(request.getResource())); } // read/review/read_receipt 的任务 UUID 所有权由 Python checkpoint 作用域核验;本端重查当前按钮权限。 return success(Map.of("user_id", String.valueOf(principal.userId()), "tenant_id", String.valueOf(principal.tenantId()), "policy_revision", principal.policyRevision())); } @GetMapping("/attachments/{attachmentId}") @PreAuthorize("@ss.hasPermission('mdm:item:import')") public ResponseEntity downloadAttachment( @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken, @PathVariable Long attachmentId) { bridgeGuard.require(serviceToken, SystemAgentBridgeGuard.PERMISSION_ITEM_IMPORT); SystemAgentAttachmentRef ref = bridgeService.requireOwnedAttachmentRef(attachmentId); return ResponseEntity.ok().contentType(MediaType.APPLICATION_OCTET_STREAM) .contentLength(ref.byteSize()) .header("X-Agent-Filename-B64", Base64.getEncoder().encodeToString( ref.originalFilename().getBytes(StandardCharsets.UTF_8))) .body(new FileSystemResource(ref.path())); } private static boolean requiresAttachmentOwnership(String action) { return "material_import.prepare".equals(action) || "material_import.preflight".equals(action) || "material_import.commit".equals(action); } private Long parseId(String value) { try { return Long.valueOf(value); } catch (NumberFormatException ex) { throw new AgentBridgeException(HttpStatus.FORBIDDEN, "ATTACHMENT_FORBIDDEN", "附件不可访问"); } } @Data public static class BindRequest { @NotNull private Long blobId; } @Data public static class AuthorizeRequest { @NotNull private String action; @NotNull private String resource; } }