From 43aab58c9ecb1203770ba382efbdbae299ca87ec Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期一, 26 十月 2026 15:12:31 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_business' into dev_business

---
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentImportBridgeController.java |  151 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 151 insertions(+), 0 deletions(-)

diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentImportBridgeController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentImportBridgeController.java
new file mode 100644
index 0000000..f92a84e
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/agent/SystemAgentImportBridgeController.java
@@ -0,0 +1,151 @@
+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<String> 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 鈫� 涓氬姟瀵煎叆鏉冮檺锛汸ython 浠ユā鍧� ID 浣滀负鎺堟潈 resource銆� */
+    private static final Map<String, String> 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<Long> createSession() {
+        return success(bridgeService.createSession());
+    }
+
+    @PostMapping("/import-sessions/{sessionId}/attachments")
+    @PreAuthorize(IMPORT_PERMISSIONS_EXPR)
+    public CommonResult<Long> bindAttachment(@PathVariable Long sessionId, @Valid @RequestBody BindRequest request) {
+        return success(bridgeService.bindAttachment(sessionId, request.getBlobId()));
+    }
+
+    @DeleteMapping("/attachments/{attachmentId}")
+    public CommonResult<Boolean> 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<Map<String, String>> 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<FileSystemResource> 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;
+    }
+}

--
Gitblit v1.9.3