liu
2 天以前 43aab58c9ecb1203770ba382efbdbae299ca87ec
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 â†’ ä¸šåŠ¡å¯¼å…¥æƒé™ï¼›Python ä»¥æ¨¡å— 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;
    }
}