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("文件上传失败");
}
}
);
}