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", "conversation.create", "conversation.read", "conversation.message", "conversation.action", "conversation.report"); /** module_id → 业务导入权限;Python 以模块 ID 作为授权 resource。 */ private static final Map MODULE_PERMISSIONS = Map.of( "mdm.item", SystemAgentBridgeGuard.PERMISSION_ITEM_IMPORT, "srm.supplier", "srm:supplier:create", "mes.bom", "mes:md-item:create", "mes.qc-template", "mes:qc-template:create", "mes.mps", "mes:pro-mps:create", "mes.route", "mes:pro-route:create"); @Resource private SystemAgentImportBridgeService bridgeService; @Resource private SystemAgentBridgeGuard bridgeGuard; private static final String IMPORT_PERMISSIONS_EXPR = "@ss.hasAnyPermissions('mdm:item:import','srm:supplier:create','mes:md-item:create'," + "'mes:qc-template:create','mes:pro-mps:create','mes:pro-route:create')"; @PostMapping("/import-sessions") @PreAuthorize(IMPORT_PERMISSIONS_EXPR) public CommonResult createSession() { return success(bridgeService.createSession()); } @PostMapping("/import-sessions/{sessionId}/attachments") @PreAuthorize(IMPORT_PERMISSIONS_EXPR) public CommonResult bindAttachment(@PathVariable Long sessionId, @Valid @RequestBody BindRequest request) { return success(bridgeService.bindAttachment(sessionId, request.getBlobId())); } @DeleteMapping("/attachments/{attachmentId}") public CommonResult deleteAttachment( @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken, @RequestHeader(value = "X-Agent-Delegation-Token", required = false) String delegationToken, @RequestHeader(value = "X-Agent-Task-Id", required = false) String taskId, @PathVariable Long attachmentId) { bridgeGuard.requireAny(serviceToken, delegationToken, taskId, "attachment.delete", null, attachmentId); bridgeService.deleteImportAttachment(attachmentId); return success(true); } @PostMapping("/authorize") public CommonResult> authorize( @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken, @RequestHeader(value = "X-Agent-Delegation-Token", required = false) String delegationToken, @RequestHeader(value = "X-Agent-Task-Id", required = false) String taskId, @Valid @RequestBody AuthorizeRequest request) { if (!ACTIONS.contains(request.getAction())) { throw new AgentBridgeException(HttpStatus.FORBIDDEN, "ACTION_FORBIDDEN", "不支持的智能体操作"); } AgentPrincipal principal; if (request.getAction().startsWith("conversation.")) { String permission = MODULE_PERMISSIONS.get(request.getResource()); principal = permission == null ? bridgeGuard.requireAny(serviceToken, delegationToken, taskId, request.getAction(), request.getResource(), null) : bridgeGuard.require(serviceToken, delegationToken, taskId, request.getAction(), permission, request.getResource(), null); } else { principal = bridgeGuard.require(serviceToken, delegationToken, taskId, request.getAction(), SystemAgentBridgeGuard.PERMISSION_ITEM_IMPORT, "mdm.item", requiresAttachmentOwnership(request.getAction()) ? parseId(request.getResource()) : null); } 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()), "policy_revision", principal.policyRevision())); } @GetMapping("/attachments/{attachmentId}") public ResponseEntity downloadAttachment( @RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken, @RequestHeader(value = "X-Agent-Delegation-Token", required = false) String delegationToken, @RequestHeader(value = "X-Agent-Task-Id", required = false) String taskId, @PathVariable Long attachmentId) { bridgeGuard.requireAny(serviceToken, delegationToken, taskId, "attachment.read", null, attachmentId); 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; } }