chore: initialize qiming workspace repository
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>app-platform-file</artifactId>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-file-web</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.deploy.skip>true</maven.deploy.skip>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>app-platform-file-application</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.xspaceagi</groupId>
|
||||
<artifactId>system-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,400 @@
|
||||
package com.xspaceagi.file.web.controller;
|
||||
|
||||
import com.xspaceagi.file.application.service.FileManagementService;
|
||||
import com.xspaceagi.file.domain.model.FileRecordDomain;
|
||||
import com.xspaceagi.file.sdk.IFileAccessService;
|
||||
import com.xspaceagi.file.web.dto.FileRecordVO;
|
||||
import com.xspaceagi.system.sdk.service.UserAccessKeyApiService;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
|
||||
import com.xspaceagi.system.spec.enums.HttpStatusEnum;
|
||||
import com.xspaceagi.system.spec.exception.BizException;
|
||||
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
|
||||
import com.xspaceagi.system.web.controller.ChatKeyCheck;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* File Management Controller
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "File Management", description = "File upload, download, delete operations")
|
||||
public class FileManagementController {
|
||||
|
||||
private final FileManagementService fileManagementService;
|
||||
|
||||
private final UserAccessKeyApiService userAccessKeyApiService;
|
||||
|
||||
private final IFileAccessService iFileAccessService;
|
||||
|
||||
@PostMapping("/file/upload")
|
||||
@Operation(summary = "Upload file")
|
||||
public ReqResult<FileRecordVO> uploadFile(HttpServletRequest request, @RequestParam("file") MultipartFile file,
|
||||
@RequestParam(value = "targetType", required = false) String targetType,
|
||||
@RequestParam(value = "targetId", required = false, defaultValue = "-1") Long targetId,
|
||||
@RequestParam(value = "metadata", required = false) String metadata) {
|
||||
if (!RequestContext.get().isLogin()) {
|
||||
ChatKeyCheck.check(request, userAccessKeyApiService);
|
||||
}
|
||||
boolean isAuthRequired = true;
|
||||
// If targetType or targetId not provided, try to parse from Referer
|
||||
if (targetType == null || targetId == null) {
|
||||
String referer = request.getHeader("Referer");
|
||||
if (referer != null && !referer.isEmpty()) {
|
||||
TargetInfo targetInfo = parseTargetFromReferer(referer);
|
||||
if (targetInfo != null) {
|
||||
if (targetType == null) {
|
||||
targetType = targetInfo.getType();
|
||||
}
|
||||
if (targetId == null) {
|
||||
targetId = targetInfo.getId();
|
||||
}
|
||||
} else {
|
||||
targetType = "default";
|
||||
}
|
||||
|
||||
// Check if authentication is required: match public path patterns
|
||||
isAuthRequired = !isPublicPath(referer);
|
||||
}
|
||||
}
|
||||
FileRecordDomain fileRecord = fileManagementService.uploadFile(file, RequestContext.get().getTenantId(),
|
||||
RequestContext.get().getUserId(), targetType, targetId, metadata, isAuthRequired);
|
||||
|
||||
if (!RequestContext.get().isLogin()) {
|
||||
String fileUrl = iFileAccessService.getFileUrlWithAk(fileRecord.getFileUrl());
|
||||
fileRecord.setFileUrl(fileUrl);
|
||||
}
|
||||
|
||||
return ReqResult.success(toVO(fileRecord));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if path is public (no authentication required)
|
||||
* Public paths include:
|
||||
* - /space/{tenantId}/agent/{agentId}
|
||||
* - /system/config/setting
|
||||
* - /space/{tenantId}/app-dev/{appId}
|
||||
* - /space/{tenantId}/workflow/{workflowId}
|
||||
* - /space/{tenantId}/plugin/{pluginId}
|
||||
* - /space/{tenantId}/skill-details/{skillId}
|
||||
* - /space/{tenantId}/mcp/edit/{mcpId}
|
||||
*/
|
||||
private boolean isPublicPath(String referer) {
|
||||
if (referer == null || referer.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String path = extractPath(referer);
|
||||
if (path == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Define public path regex patterns
|
||||
String[] publicPatterns = {
|
||||
"/space/\\d+/agent/\\d+",
|
||||
"/system/config/setting",
|
||||
"/space/\\d+/app-dev/\\d+",
|
||||
"/space/\\d+/workflow/\\d+",
|
||||
"/space/\\d+/plugin/\\d+",
|
||||
"/space/\\d+/skill-details/\\d+",
|
||||
"/space/\\d+/mcp/edit/\\d+"
|
||||
};
|
||||
|
||||
for (String pattern : publicPatterns) {
|
||||
if (Pattern.matches(pattern, path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse targetType and targetId from Referer URL
|
||||
* Supported URL formats:
|
||||
* - /home/chat/{targetId} -> targetType=agent
|
||||
* - /space/knowledge/{targetId} -> targetType=knowledge
|
||||
* - Other paths -> targetType=other
|
||||
*/
|
||||
private TargetInfo parseTargetFromReferer(String referer) {
|
||||
if (referer == null || referer.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Extract path part (remove protocol, domain, query params)
|
||||
String path = extractPath(referer);
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use regex to match path patterns
|
||||
TargetInfo result = matchPathPattern(path, "/home/chat/(\\d+)/(\\d+)", "agent", 2);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = matchPathPattern(path, "/agent/(\\d+)", "agent", 1);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = matchPathPattern(path, "/app/(\\d+)", "agent", 1);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = matchPathPattern(path, "/space/(\\d+)/knowledge/(\\d+)", "knowledge", 2);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = matchPathPattern(path, "/space/(\\d+)/workflow/(\\d+)", "workflow", 2);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = matchPathPattern(path, "/space/(\\d+)/plugin/(\\d+)", "plugin", 2);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = matchPathPattern(path, "/space/(\\d+)/skill-details/(\\d+)", "skill", 2);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = matchPathPattern(path, "/space/(\\d+)/mcp/edit/(\\d+)", "mcp", 2);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return matchPathPattern(path, "/space/(\\d+)/app-dev/(\\d+)", "app-dev", 2);
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to parse Referer: {}", referer, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract path part from URL
|
||||
*/
|
||||
private String extractPath(String url) {
|
||||
try {
|
||||
// Remove query params and anchor
|
||||
int queryIndex = url.indexOf('?');
|
||||
if (queryIndex > 0) {
|
||||
url = url.substring(0, queryIndex);
|
||||
}
|
||||
int anchorIndex = url.indexOf('#');
|
||||
if (anchorIndex > 0) {
|
||||
url = url.substring(0, anchorIndex);
|
||||
}
|
||||
|
||||
// Remove protocol and domain
|
||||
int pathStart = url.indexOf("://");
|
||||
if (pathStart > 0) {
|
||||
pathStart = url.indexOf('/', pathStart + 3);
|
||||
if (pathStart > 0) {
|
||||
return url.substring(pathStart);
|
||||
}
|
||||
}
|
||||
return url;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Match path pattern using regex
|
||||
*/
|
||||
private TargetInfo matchPathPattern(String path, String regex, String targetType, int index) {
|
||||
try {
|
||||
Pattern pattern = Pattern.compile(regex);
|
||||
Matcher matcher = pattern.matcher(path);
|
||||
if (matcher.find()) {
|
||||
Long id = Long.parseLong(matcher.group(index));
|
||||
return new TargetInfo(targetType, id);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to match path pattern: pattern={}, path={}", regex, path);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract last number from path as ID
|
||||
*/
|
||||
private TargetInfo extractLastNumber(String path) {
|
||||
String[] segments = path.split("/");
|
||||
for (int i = segments.length - 1; i >= 0; i--) {
|
||||
try {
|
||||
Long id = Long.parseLong(segments[i]);
|
||||
return new TargetInfo("other", id);
|
||||
} catch (NumberFormatException e) {
|
||||
// Continue trying previous segment
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Target info inner class
|
||||
*/
|
||||
@Data
|
||||
private static class TargetInfo {
|
||||
private final String type;
|
||||
private final Long id;
|
||||
|
||||
public TargetInfo(String type, Long id) {
|
||||
this.type = type;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Access file by fileKey
|
||||
* Local storage: return file stream directly
|
||||
* Cloud storage (s3/cos/oss): 302 redirect to presigned URL
|
||||
*/
|
||||
@GetMapping("/f/**")
|
||||
@Operation(summary = "Access file by fileKey")
|
||||
public ResponseEntity<?> getFileByPath(HttpServletRequest request, @RequestParam(name = "download", required = false) Integer download) {
|
||||
String fullPath = request.getRequestURI();
|
||||
String fileKey = fullPath.substring("/api/f/".length());
|
||||
FileRecordDomain fileRecord = fileManagementService.getFileByKey(fileKey);
|
||||
boolean authRequired = fileRecord == null || !Boolean.FALSE.equals(fileRecord.getAuthRequired());
|
||||
|
||||
if (!RequestContext.get().isLogin() && authRequired) {
|
||||
try {
|
||||
iFileAccessService.checkFileUrlAk0(request.getRequestURI(), request.getParameter("ak"));
|
||||
} catch (Exception e) {
|
||||
throw BizException.of(HttpStatusEnum.UNAUTHORIZED, ErrorCodeEnum.UNAUTHORIZED,
|
||||
BizExceptionCodeEnum.systemUnauthorizedOrSessionExpired);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("Accessing file: {}", fileKey);
|
||||
|
||||
try {
|
||||
// Extract storage type from fileKey (format: /storageType/businessType/date/uuid.ext)
|
||||
String storageType = extractStorageTypeFromFileKey(fileKey);
|
||||
if (storageType == null) {
|
||||
log.error("Cannot extract storage type from fileKey: {}", fileKey);
|
||||
return ResponseEntity.badRequest().body("Invalid file key format");
|
||||
}
|
||||
|
||||
// Check storage type
|
||||
if ("local".equals(storageType)) {
|
||||
// Local storage: query database for file info, then return file stream
|
||||
if (fileRecord == null) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
InputStream inputStream = fileManagementService.downloadFile(fileKey);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.parseMediaType(
|
||||
fileRecord.getFileType() != null ? fileRecord.getFileType() : "application/octet-stream"));
|
||||
if (download != null && download == 1) {
|
||||
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + URLEncoder.encode(fileRecord.getFileName(), StandardCharsets.UTF_8) + "\"");
|
||||
} else {
|
||||
headers.add(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + URLEncoder.encode(fileRecord.getFileName(), StandardCharsets.UTF_8) + "\"");
|
||||
}
|
||||
return ResponseEntity.ok()
|
||||
.headers(headers)
|
||||
.body(new InputStreamResource(inputStream));
|
||||
} else {
|
||||
// Cloud storage (s3/cos/oss): generate presigned URL and 302 redirect, no database query needed
|
||||
String signedUrl = fileManagementService.generatePresignedUrlByType(fileKey, storageType, 3600, download); // 1 hour validity
|
||||
if (signedUrl == null) {
|
||||
log.error("Failed to generate presigned URL: storageType={}, fileKey={}", storageType, fileKey);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body("Failed to generate presigned URL");
|
||||
}
|
||||
|
||||
log.info("302 redirect to presigned URL: storageType={}, fileKey={}", storageType, fileKey);
|
||||
return ResponseEntity.status(HttpStatus.FOUND)
|
||||
.location(URI.create(signedUrl))
|
||||
.build();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("File access failed: {}", fileKey, e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract storage type from fileKey
|
||||
* fileKey format: /storageType/businessType/date/uuid.ext
|
||||
* Example: /s3/agent/20260417/xxx.jpg -> s3
|
||||
*/
|
||||
private String extractStorageTypeFromFileKey(String fileKey) {
|
||||
if (fileKey == null || fileKey.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove leading slash
|
||||
String key = fileKey.startsWith("/") ? fileKey.substring(1) : fileKey;
|
||||
|
||||
// Extract first segment as storage type
|
||||
int firstSlash = key.indexOf('/');
|
||||
if (firstSlash > 0) {
|
||||
return key.substring(0, firstSlash);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static FileRecordVO toVO(FileRecordDomain domain) {
|
||||
return FileRecordVO.builder()
|
||||
.id(domain.getId())
|
||||
.tenantId(domain.getTenantId())
|
||||
.userId(domain.getUserId())
|
||||
.targetType(domain.getTargetType())
|
||||
.targetId(domain.getTargetId())
|
||||
.fileName(domain.getFileName())
|
||||
.size(domain.getFileSize())
|
||||
.mimeType(domain.getFileType())
|
||||
.fileExtension(domain.getFileExtension())
|
||||
.metadata(domain.getMetadata())
|
||||
.key(domain.getFileKey())
|
||||
.storageType(domain.getStorageType())
|
||||
.url(domain.getFileUrl())
|
||||
.authRequired(domain.getAuthRequired())
|
||||
.status(domain.getStatus())
|
||||
.created(domain.getCreated())
|
||||
.modified(domain.getModified())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.xspaceagi.file.web.controller.api;
|
||||
|
||||
import com.xspaceagi.agent.core.adapter.application.PublishApplicationService;
|
||||
import com.xspaceagi.agent.core.adapter.dto.PublishedPermissionDto;
|
||||
import com.xspaceagi.agent.core.adapter.repository.entity.Published;
|
||||
import com.xspaceagi.file.application.service.FileManagementService;
|
||||
import com.xspaceagi.file.domain.model.FileRecordDomain;
|
||||
import com.xspaceagi.file.sdk.IFileAccessService;
|
||||
import com.xspaceagi.file.web.dto.FileRecordVO;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static com.xspaceagi.file.web.controller.FileManagementController.toVO;
|
||||
|
||||
@Tag(name = "文件上传")
|
||||
@Slf4j
|
||||
@RestController
|
||||
public class FileApiController {
|
||||
|
||||
@Resource
|
||||
private IFileAccessService iFileAccessService;
|
||||
|
||||
@Resource
|
||||
private FileManagementService fileManagementService;
|
||||
|
||||
@Resource
|
||||
private PublishApplicationService publishApplicationService;
|
||||
;
|
||||
|
||||
@Operation(summary = "文件上传接口", description = "文件上传接口,返回文件网络地址")
|
||||
@PostMapping("/api/v1/file/upload")
|
||||
public ReqResult<FileRecordVO> uploadFile(@RequestParam("file") MultipartFile file,
|
||||
@RequestParam(value = "targetType", required = false, defaultValue = "Default") String targetType,
|
||||
@RequestParam(value = "targetId", required = false, defaultValue = "-1") Long targetId,
|
||||
@RequestParam(value = "metadata", required = false) String metadata) {
|
||||
|
||||
if ("Agent".equals(targetType) && targetId > 0) {
|
||||
PublishedPermissionDto publishedPermissionDto = publishApplicationService.hasPermission(Published.TargetType.Agent, targetId);
|
||||
if (!publishedPermissionDto.isView()) {
|
||||
return ReqResult.error("No permission to upload file for this agent id");
|
||||
}
|
||||
}
|
||||
|
||||
FileRecordDomain fileRecord = fileManagementService.uploadFile(file, RequestContext.get().getTenantId(),
|
||||
RequestContext.get().getUserId(), targetType, targetId, metadata, true);
|
||||
|
||||
return ReqResult.success(toVO(fileRecord));
|
||||
}
|
||||
|
||||
@GetMapping("/api/v1/file/ak")
|
||||
public ReqResult<String> fileAk(@RequestParam(name = "fileUrl") String fileUrl) {
|
||||
fileUrl = URLDecoder.decode(fileUrl, StandardCharsets.UTF_8);
|
||||
//fileUrl中移除ak参数
|
||||
fileUrl = fileUrl.replaceAll("([?&])ak(?:=[^&]*)?&?", "$1").replaceAll("\\?$", "");
|
||||
return ReqResult.success(iFileAccessService.getFileUrlWithAk(fileUrl, true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.xspaceagi.file.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 文件记录VO
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "文件记录")
|
||||
public class FileRecordVO {
|
||||
|
||||
@Schema(description = "文件ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "租户ID")
|
||||
private Long tenantId;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "来源对象类型")
|
||||
private String targetType;
|
||||
|
||||
@Schema(description = "来源对象ID")
|
||||
private Long targetId;
|
||||
|
||||
@Schema(description = "文件名称")
|
||||
private String fileName;
|
||||
|
||||
@Schema(description = "文件大小(字节)")
|
||||
private Long size;
|
||||
|
||||
@Schema(description = "文件类型")
|
||||
private String mimeType;
|
||||
|
||||
@Schema(description = "文件扩展名")
|
||||
private String fileExtension;
|
||||
|
||||
@Schema(description = "文件元数据(JSON格式)")
|
||||
private String metadata;
|
||||
|
||||
@Schema(description = "文件存储标识key")
|
||||
private String key;
|
||||
|
||||
@Schema(description = "存储方式")
|
||||
private String storageType;
|
||||
|
||||
@Schema(description = "文件访问URL")
|
||||
private String url;
|
||||
|
||||
@Schema(description = "是否需要认证访问")
|
||||
private Boolean authRequired;
|
||||
|
||||
@Schema(description = "文件状态")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private Date created;
|
||||
|
||||
@Schema(description = "修改时间")
|
||||
private Date modified;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.xspaceagi.file.web.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 文件上传请求
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "文件上传请求")
|
||||
public class FileUploadRequest {
|
||||
|
||||
@Schema(description = "租户ID", required = true)
|
||||
private Long tenantId;
|
||||
|
||||
@Schema(description = "用户ID", required = true)
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "来源对象类型")
|
||||
private String targetType;
|
||||
|
||||
@Schema(description = "来源对象ID")
|
||||
private Long targetId;
|
||||
|
||||
@Schema(description = "文件元数据(JSON格式)")
|
||||
private String metadata;
|
||||
}
|
||||
Reference in New Issue
Block a user