[Java] MinIO分布式存储(从0到Vue+SpringBoot整合开发)2024版

28 阅读3分钟

UE与SpringBoot数据交互 - 复杂对象与文件传输(含代码)

一、复杂数据对象传输

1. SpringBoot实体与DTO设计

// 产品实体
@Entity
@Table(name = "products")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(columnDefinition = "TEXT")
    private String description;
    
    @Column(nullable = false)
    private BigDecimal price;
    
    @ManyToOne
    @JoinColumn(name = "category_id")
    private Category category;
    
    @ElementCollection
    @CollectionTable(name = "product_images", joinColumns = @JoinColumn(name = "product_id"))
    @Column(name = "image_url")
    private List<String> imageUrls;
    
    // 构造函数、getter和setter
}

// 产品DTO(用于前端交互)
public class ProductDTO {
    private Long id;
    private String name;
    private String description;
    private BigDecimal price;
    private Long categoryId;
    private List<String> imageUrls;
    private List<String> tags;
    
    // 构造函数、getter和setter
}

// DTO转换服务
@Service
public class ProductMapper {
    public Product toEntity(ProductDTO productDTO) {
        Product product = new Product();
        product.setId(productDTO.getId());
        product.setName(productDTO.getName());
        product.setDescription(productDTO.getDescription());
        product.setPrice(productDTO.getPrice());
        // 其他字段转换...
        return product;
    }
    
    public ProductDTO toDTO(Product product) {
        ProductDTO productDTO = new ProductDTO();
        productDTO.setId(product.getId());
        productDTO.setName(product.getName());
        productDTO.setDescription(product.getDescription());
        productDTO.setPrice(product.getPrice());
        // 其他字段转换...
        return productDTO;
    }
}

2. 产品控制器

@RestController
@RequestMapping("/api/products")
@CrossOrigin(origins = "*")
public class ProductController {
    
    @Autowired
    private ProductService productService;
    
    @Autowired
    private ProductMapper productMapper;
    
    @GetMapping
    public ResponseEntity<List<ProductDTO>> getAllProducts() {
        List<Product> products = productService.findAll();
        List<ProductDTO> productDTOs = products.stream()
            .map(productMapper::toDTO)
            .collect(Collectors.toList());
        return ResponseEntity.ok(productDTOs);
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<ProductDTO> getProductById(@PathVariable Long id) {
        Product product = productService.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + id));
        ProductDTO productDTO = productMapper.toDTO(product);
        return ResponseEntity.ok(productDTO);
    }
    
    @PostMapping
    public ResponseEntity<ProductDTO> createProduct(@RequestBody ProductDTO productDTO) {
        Product product = productMapper.toEntity(productDTO);
        Product savedProduct = productService.save(product);
        ProductDTO savedProductDTO = productMapper.toDTO(savedProduct);
        return new ResponseEntity<>(savedProductDTO, HttpStatus.CREATED);
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<ProductDTO> updateProduct(@PathVariable Long id, @RequestBody ProductDTO productDTO) {
        Product existingProduct = productService.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + id));
        
        // 更新字段
        existingProduct.setName(productDTO.getName());
        existingProduct.setDescription(productDTO.getDescription());
        existingProduct.setPrice(productDTO.getPrice());
        // 其他字段更新...
        
        Product updatedProduct = productService.save(existingProduct);
        ProductDTO updatedProductDTO = productMapper.toDTO(updatedProduct);
        return ResponseEntity.ok(updatedProductDTO);
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteProduct(@PathVariable Long id) {
        Product product = productService.findById(id)
            .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + id));
        productService.delete(product);
        return ResponseEntity.noContent().build();
    }
}

二、UE前端数据展示与交互

1. UE产品列表界面(蓝图逻辑)

// 获取产品列表
void UProductListWidget::LoadProducts()
{
    HttpService->SendHttpRequest(
        TEXT("http://your-springboot-server/api/products"),
        TEXT("application/json"),
        TEXT(""),
        [this](FHttpResponsePtr Response, bool bWasSuccessful) {
            if (bWasSuccessful && Response.IsValid() && Response->GetResponseCode() == 200)
            {
                FString ResponseString = Response->GetContentAsString();
                TSharedPtr<FJsonObject> JsonObject;
                TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(ResponseString);
                
                if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid())
                {
                    TArray<TSharedPtr<FJsonValue>> ProductsArray = JsonObject->GetArrayField(TEXT("content"));
                    ProductListItems.Empty();
                    
                    for (const TSharedPtr<FJsonValue>& ProductValue : ProductsArray)
                    {
                        TSharedPtr<FJsonObject> ProductObject = ProductValue->AsObject();
                        FProductItem Item;
                        Item.Id = ProductObject->GetNumberField(TEXT("id"));
                        Item.Name = ProductObject->GetStringField(TEXT("name"));
                        Item.Description = ProductObject->GetStringField(TEXT("description"));
                        Item.Price = ProductObject->GetNumberField(TEXT("price"));
                        
                        ProductListItems.Add(Item);
                    }
                    
                    // 更新UI列表
                    UpdateProductListView();
                }
            }
            else
            {
                ShowErrorMessage("加载产品列表失败");
            }
        }
    );
}

2. UE产品详情界面

// 加载产品详情
void UProductDetailWidget::LoadProductDetail(int32 ProductId)
{
    FString Url = FString::Printf(TEXT("http://your-springboot-server/api/products/%d"), ProductId);
    
    HttpService->SendHttpRequest(
        Url,
        TEXT("application/json"),
        TEXT(""),
        [this](FHttpResponsePtr Response, bool bWasSuccessful) {
            if (bWasSuccessful && Response.IsValid() && Response->GetResponseCode() == 200)
            {
                FString ResponseString = Response->GetContentAsString();
                TSharedPtr<FJsonObject> JsonObject;
                
                if (FJsonSerializer::Deserialize(TJsonReaderFactory<>::Create(ResponseString), JsonObject) && JsonObject.IsValid())
                {
                    // 解析产品详情数据
                    ProductId = JsonObject->GetNumberField(TEXT("id"));
                    ProductName = JsonObject->GetStringField(TEXT("name"));
                    ProductDescription = JsonObject->GetStringField(TEXT("description"));
                    ProductPrice = JsonObject->GetNumberField(TEXT("price"));
                    
                    // 更新UI显示
                    UpdateProductDetailDisplay();
                }
            }
            else
            {
                ShowErrorMessage("加载产品详情失败");
            }
        }
    );
}

三、文件上传与下载

1. SpringBoot文件上传控制器

@RestController
@RequestMapping("/api/files")
@CrossOrigin(origins = "*")
public class FileController {
    
    @Value("${file.upload-dir}")
    private String uploadDir;
    
    @PostMapping("/upload")
    public ResponseEntity<?> uploadFile(
            @RequestParam("file") MultipartFile file,
            @RequestParam(value = "productId", required = false) Long productId) {
        
        try {
            if (file.isEmpty()) {
                return ResponseEntity.badRequest().body("请选择要上传的文件");
            }
            
            // 创建上传目录
            Path uploadPath = Paths.get(uploadDir);
            if (!Files.exists(uploadPath)) {
                Files.createDirectories(uploadPath);
            }
            
            // 生成唯一文件名
            String fileName = UUID.randomUUID().toString() + "_" + file.getOriginalFilename();
            Path filePath = uploadPath.resolve(fileName);
            
            // 保存文件
            Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
            
            // 如果关联产品,保存文件路径到数据库
            if (productId != null) {
                // 这里添加保存文件路径到产品实体的逻辑
                // productService.associateFileToProduct(productId, fileName);
            }
            
            Map<String, String> response = new HashMap<>();
            response.put("fileName", fileName);
            response.put("fileUrl", "/api/files/download/" + fileName);
            response.put("size", String.valueOf(file.getSize()));
            
            return ResponseEntity.ok(response);
        } catch (IOException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("文件上传失败: " + e.getMessage());
        }
    }
    
    @GetMapping("/download/{fileName:.+}")
    public ResponseEntity<Resource> downloadFile(@PathVariable String fileName, HttpServletRequest request) {
        try {
            Path filePath = Paths.get(uploadDir).resolve(fileName).normalize();
            Resource resource = new UrlResource(filePath.toUri());
            
            if (resource.exists()) {
                // 确定内容类型
                String contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
                if (contentType == null) {
                    contentType = "application/octet-stream";
                }
                
                return ResponseEntity.ok()
                        .contentType(MediaType.parseMediaType(contentType))
                        .header(HttpHeaders.CONTENT_DISPOSITION, 
                                "attachment; filename="" + resource.getFilename() + """)
                        .body(resource);
            } else {
                return ResponseEntity.notFound().build();
            }
        } catch (IOException ex) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }
}

2. UE文件上传实现

// UE文件上传功能
void UFileUploadWidget::UploadFile(UTexture2D* TextureToUpload)
{
    if (!TextureToUpload)
    {
        ShowErrorMessage("没有选择要上传的文件");
        return;
    }
    
    // 将纹理转换为PNG数据
    TArray<uint8> PNGData;
    FBufferArchive Ar;
    FImageUtils::GetRawData(TextureToUpload, Ar);
    
    // 或者使用更简单的方法保存为临时文件然后上传
    FString TempFilePath = FPaths::ProjectSavedDir() + "TempUpload.png";
    FFileHelper::SaveArrayToFile(Ar, *TempFilePath);
    
    // 准备多部分表单数据
    FString Boundary = "----UEBoundary" + FGuid::NewGuid().ToString();
    FString ContentType = "multipart/form-data; boundary=" + Boundary;
    
    // 构建多部分表单请求体
    FString RequestBody;
    RequestBody.Append("--" + Boundary + "\r\n");
    RequestBody.Append("Content-Disposition: form-data; name="file"; filename="upload.png"\r\n");
    RequestBody.Append("Content-Type: image/png\r\n\r\n");
    
    // 读取文件内容
    FString FileContent;
    FFileHelper::LoadFileToString(FileContent, *TempFilePath);
    RequestBody.Append(FileContent);
    RequestBody.Append("\r\n");
    RequestBody.Append("--" + Boundary + "--\r\n");
    
    // 发送上传请求
    HttpService->SendMultipartFormDataRequest(
        TEXT("http://your-springboot-server/api/files/upload"),
        ContentType,
        RequestBody,
        [this](FHttpResponsePtr Response, bool bWasSuccessful) {
            if (bWasSuccessful && Response.IsValid() && Response->GetResponseCode() == 200)
            {
                FString ResponseString = Response->GetContentAsString();
                // 解析响应,获取上传的文件URL等信息
                ShowSuccessMessage("文件上传成功");
            }
            else
            {
                ShowErrorMessage("文件上传失败");
            }
        }
    );
}