| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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; |
| | | } |
| | | } |