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()));
|
}
|
|
@PostMapping("/authorize")
|
@PreAuthorize(IMPORT_PERMISSIONS_EXPR)
|
public CommonResult<Map<String, String>> authorize(
|
@RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken,
|
@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)
|
: bridgeGuard.require(serviceToken, permission);
|
} else {
|
principal = bridgeGuard.require(serviceToken, SystemAgentBridgeGuard.PERMISSION_ITEM_IMPORT);
|
}
|
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(IMPORT_PERMISSIONS_EXPR)
|
public ResponseEntity<FileSystemResource> downloadAttachment(
|
@RequestHeader(value = "X-Agent-Service-Token", required = false) String serviceToken,
|
@PathVariable Long attachmentId) {
|
bridgeGuard.requireAny(serviceToken);
|
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;
|
}
|
}
|