TASK:模块化;

This commit is contained in:
shahaibo
2025-03-30 15:56:03 +08:00
parent 2cfd342ef8
commit 48c7182f98
18 changed files with 538 additions and 12 deletions

View File

@@ -0,0 +1,127 @@
package com.ai.da.common.config;
import com.ai.da.common.utils.MinioUtil;
import com.ai.da.mapper.primary.ThreeDDetailMapper;
import com.ai.da.mapper.primary.ThreeDSimpleMapper;
import com.ai.da.mapper.primary.entity.ThreeDDetail;
import com.ai.da.mapper.primary.entity.ThreeDSimple;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Component
public class ThreeDSave {
@Resource
private ThreeDDetailMapper threeDDetailMapper;
@Resource
private ThreeDSimpleMapper threeDSimpleMapper;
@Resource
private MinioUtil minioUtil;
@PostConstruct
public void test() {
// minioSave();
}
private void minioSave() {
// 指定目标文件夹路径
String folderPath = "C:\\workspace\\3D\\3D虚拟 1-7\\3D服装整理\\femaleZip";
// 创建文件对象
File folder = new File(folderPath);
// 检查文件夹是否存在且是目录
if (folder.exists() && folder.isDirectory()) {
// 获取所有 .zip 文件
File[] zipFiles = folder.listFiles((dir, name) -> name.toLowerCase().endsWith(".zip"));
List<String> zipFileNameList = new ArrayList<>();
// 输出文件名
if (zipFiles != null) {
for (File file : zipFiles) {
String zipFileName = file.getName();
String[] split = zipFileName.split("_");
String zipName = split[0];
String sizeWithSuffix = split[1];
String[] sizeWithSuffixSplit = sizeWithSuffix.split("\\.");
String size = sizeWithSuffixSplit[0];
// 找到第一个字母的位置
int index = 0;
while (index < size.length() && Character.UnicodeBlock.of(size.charAt(index)) == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS) {
index++;
}
if (index > 0 && index < size.length()) {
String prefix = size.substring(0, index); // "亚码"
String suffix = size.substring(index); // "L"
ThreeDDetail threeDDetail = new ThreeDDetail();
threeDDetail.setName(zipFileName);
String url = "aida-threed/female/zip/" + zipFileName;
threeDDetail.setGender("female");
threeDDetail.setSizeType(prefix);
threeDDetail.setSize(suffix);
threeDDetail.setUrl(url);
threeDDetailMapper.insert(threeDDetail);
minioUtil.upload(url, file);
QueryWrapper<ThreeDSimple> qw = new QueryWrapper<>();
qw.lambda().eq(ThreeDSimple::getName, zipName);
qw.lambda().eq(ThreeDSimple::getGender, "female");
List<ThreeDSimple> threeDSimples = threeDSimpleMapper.selectList(qw);
if (CollectionUtils.isEmpty(threeDSimples)) {
ThreeDSimple threeDSimple = new ThreeDSimple();
threeDSimple.setName(zipName);
threeDSimple.setGender("female");
String glbPath = "C:\\workspace\\3D\\3D虚拟 1-7\\3D服装整理\\female\\" + zipName + "\\" +"亚码L";
File glbFolder = new File(glbPath);
// 查找 .glb 文件
if (glbFolder.exists() && glbFolder.isDirectory()) {
File[] glbFiles = glbFolder.listFiles((dir, name) -> name.toLowerCase().endsWith(".glb"));
if (glbFiles != null && glbFiles.length > 0) {
for (File glbFile : glbFiles) {
String name = glbFile.getName();
String glbUrl = "aida-threed/female/glb/" + name;
threeDSimple.setUrl(glbUrl);
minioUtil.upload(glbUrl, glbFile);
}
} else {
System.out.println("未找到 GLB 文件。" + glbFolder);
}
} else {
System.out.println("GLB 文件夹不存在: " + glbPath);
}
threeDSimpleMapper.insert(threeDSimple);
threeDDetail.setThreeDSimpleId(threeDSimple.getId());
threeDDetailMapper.updateById(threeDDetail);
}else {
Long id = threeDSimples.get(0).getId();
threeDDetail.setThreeDSimpleId(id);
threeDDetailMapper.updateById(threeDDetail);
}
} else {
}
}
} else {
System.out.println("未找到 ZIP 文件。");
}
log.info("aaa");
} else {
System.out.println("文件夹不存在或不是一个目录。");
}
}
}

View File

@@ -570,6 +570,36 @@ public class MinioUtil {
throw new BusinessException(e.getMessage());
}
}
public void upload(String url, File file) {
try {
// 分割桶名和对象路径
int firstSlashIndex = url.indexOf("/");
if (firstSlashIndex == -1) {
throw new IllegalArgumentException("URL 格式不正确,无法解析桶名和对象路径");
}
String bucketName = url.substring(0, firstSlashIndex);
String objectName = url.substring(firstSlashIndex + 1);
// 读取文件流
try (FileInputStream fileInputStream = new FileInputStream(file)) {
// 上传到 MinIO
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(fileInputStream, file.length(), -1)
.contentType("application/zip")
.build()
);
System.out.println("文件上传成功: " + url);
}
} catch (Exception e) {
e.printStackTrace();
System.err.println("文件上传失败: " + e.getMessage());
}
}
}

View File

@@ -3,25 +3,23 @@ package com.ai.da.controller;
import com.ai.da.common.response.PageBaseResponse;
import com.ai.da.common.response.Response;
import com.ai.da.mapper.primary.entity.LibraryModelPoint;
import com.ai.da.model.dto.MannequinDTO;
import com.ai.da.model.dto.ModuleSaveDTO;
import com.ai.da.model.dto.ProjectDTO;
import com.ai.da.model.dto.ProjectQueryDTO;
import com.ai.da.mapper.primary.entity.ThreeDLayout;
import com.ai.da.model.dto.*;
import com.ai.da.model.enums.MannequinType;
import com.ai.da.model.vo.*;
import com.ai.da.service.UserLikeGroupService;
import com.ai.da.service.WorkspaceService;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.minio.errors.MinioException;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
@Api(tags = "Project模块")
@Slf4j
@@ -90,4 +88,39 @@ public class ProjectController {
public Response<QueryLibraryPageVO> getMannequinDetail(@Valid @RequestBody MannequinDTO mannequinDTO) {
return Response.success(userLikeGroupService.getMannequinDetail(mannequinDTO));
}
@PostMapping("/threeDPage")
@ApiOperationSupport(order = 8)
@ApiOperation(value = "获取3Dpage", notes = "传入ThreeDLayoutQueryDTO")
public Response<PageBaseResponse<ThreeDLayoutVO>> threeDPage(@Valid @RequestBody ThreeDLayoutQueryDTO threeDLayoutQueryDTO) {
return Response.success(PageBaseResponse.success(userLikeGroupService.getThreeDLayoutPage(threeDLayoutQueryDTO)));
}
@PostMapping("/getLayoutDetail")
@ApiOperationSupport(order = 9)
@ApiOperation(value = "获取3D详情", notes = "传入project")
public Response<ThreeDVO> getLayoutDetail(@RequestParam(value = "threeDSimpleId") Long threeDSimpleId) {
return Response.success(userLikeGroupService.getLayoutDetail(threeDSimpleId));
}
@PostMapping("/getThreeDSize")
@ApiOperationSupport(order = 10)
@ApiOperation(value = "获取尺码", notes = "传入project")
public Response<ThreeDSizeVO> getThreeDSize(@RequestParam(value = "threeDSimpleId") Long threeDSimpleId) {
return Response.success(userLikeGroupService.getThreeDSize(threeDSimpleId));
}
@GetMapping("/getThreeDGlb")
@ApiOperationSupport(order = 11)
@ApiOperation(value = "获取GLB", notes = "传入project")
public void getThreeDGlb(@RequestParam(value = "threeDSimpleId") Long threeDSimpleId, HttpServletResponse response) throws MinioException, IOException {
userLikeGroupService.getThreeDGlb(threeDSimpleId, response);
}
@GetMapping("/downloadZip")
@ApiOperationSupport(order = 11)
@ApiOperation(value = "下载", notes = "传入project")
public void downloadZip(@RequestParam(value = "threeDSimpleId") Long threeDSimpleId, @RequestParam(value = "sizeType") String sizeType, @RequestParam(value = "size") String size, HttpServletResponse response) throws MinioException, IOException {
userLikeGroupService.downloadZip(threeDSimpleId, sizeType, size, response);
}
}

View File

@@ -279,7 +279,7 @@ public class SavedCollectionController {
@ApiOperation(value = "brandDNAUpload")
@PostMapping("/brandDNAUpload")
public Response<String> brandDNAUpload(@RequestParam("file") MultipartFile file, @RequestParam("brandId") Long brandId) throws IOException {
public Response<Library> brandDNAUpload(@RequestParam("file") MultipartFile file, @RequestParam("brandId") Long brandId) throws IOException {
return Response.success(userLikeGroupService.brandDNAUpload(file, brandId));
}

View File

@@ -0,0 +1,7 @@
package com.ai.da.mapper.primary;
import com.ai.da.common.config.mybatis.plus.CommonMapper;
import com.ai.da.mapper.primary.entity.ThreeDDetail;
public interface ThreeDDetailMapper extends CommonMapper<ThreeDDetail> {
}

View File

@@ -0,0 +1,7 @@
package com.ai.da.mapper.primary;
import com.ai.da.common.config.mybatis.plus.CommonMapper;
import com.ai.da.mapper.primary.entity.ThreeDLayout;
public interface ThreeDLayoutMapper extends CommonMapper<ThreeDLayout> {
}

View File

@@ -0,0 +1,7 @@
package com.ai.da.mapper.primary;
import com.ai.da.common.config.mybatis.plus.CommonMapper;
import com.ai.da.mapper.primary.entity.ThreeDPatternLayout;
public interface ThreeDPatternLayoutMapper extends CommonMapper<ThreeDPatternLayout> {
}

View File

@@ -0,0 +1,7 @@
package com.ai.da.mapper.primary;
import com.ai.da.common.config.mybatis.plus.CommonMapper;
import com.ai.da.mapper.primary.entity.ThreeDSimple;
public interface ThreeDSimpleMapper extends CommonMapper<ThreeDSimple> {
}

View File

@@ -0,0 +1,29 @@
package com.ai.da.mapper.primary.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("three_d_detail")
public class ThreeDDetail implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String name;
private String url;
private String gender;
private String sizeType;
private String size;
private Long threeDSimpleId;
}

View File

@@ -0,0 +1,30 @@
package com.ai.da.mapper.primary.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("three_d_layout")
public class ThreeDLayout implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String name;
private String url;
private String type;
private Long threeDSimpleId;
private String gender;
}

View File

@@ -0,0 +1,26 @@
package com.ai.da.mapper.primary.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("three_d_layout")
public class ThreeDPatternLayout implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String name;
private String url;
private Long threeDSimpleId;
}

View File

@@ -0,0 +1,26 @@
package com.ai.da.mapper.primary.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("three_d_simple")
public class ThreeDSimple implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String name;
private String url;
private String gender;
}

View File

@@ -0,0 +1,21 @@
package com.ai.da.model.dto;
import com.ai.da.model.vo.PageQueryBaseVo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
* 数据传输对象实体类
*
* @author SHAHAIBO
* @since 2023-08-01
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class ThreeDLayoutQueryDTO extends PageQueryBaseVo {
}

View File

@@ -0,0 +1,8 @@
package com.ai.da.model.vo;
import com.ai.da.mapper.primary.entity.ThreeDLayout;
import lombok.Data;
@Data
public class ThreeDLayoutVO extends ThreeDLayout {
}

View File

@@ -0,0 +1,11 @@
package com.ai.da.model.vo;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class ThreeDSizeVO {
private Map<String, List<String>> sizeTypeMap;
}

View File

@@ -0,0 +1,15 @@
package com.ai.da.model.vo;
import com.ai.da.mapper.primary.entity.ThreeDLayout;
import com.ai.da.mapper.primary.entity.ThreeDPatternLayout;
import lombok.Data;
import java.util.List;
@Data
public class ThreeDVO {
private List<ThreeDLayout> threeDLayoutList;
private ThreeDPatternLayout threeDPatternLayout;
}

View File

@@ -7,8 +7,10 @@ import com.ai.da.model.vo.*;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import io.minio.errors.MinioException;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
@@ -86,9 +88,19 @@ public interface UserLikeGroupService extends IService<UserLikeGroup> {
Boolean brandDNASaveOrUpdate(BrandDNADTO brandDNADTO);
String brandDNAUpload(MultipartFile file, Long brandId) throws IOException;
Library brandDNAUpload(MultipartFile file, Long brandId) throws IOException;
PageBaseResponse<BrandDNAVO> brandDNAPage(BrandDNAQueryDTO brandDNAQueryDTO);
BrandDNAGenerateVO brandDNAGenerate(String prompt);
IPage<ThreeDLayoutVO> getThreeDLayoutPage(ThreeDLayoutQueryDTO threeDLayoutQueryDTO);
ThreeDVO getLayoutDetail(Long threeDSimpleId);
ThreeDSizeVO getThreeDSize(Long threeDSimpleId);
void getThreeDGlb(Long threeDSimpleId, HttpServletResponse response) throws MinioException, IOException;
void downloadZip(Long threeDSimpleId, String sizeType, String size, HttpServletResponse response) throws MinioException, IOException;
}

View File

@@ -32,6 +32,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.base.Function;
import com.google.gson.Gson;
import io.minio.errors.MinioException;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
@@ -42,6 +43,7 @@ import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.time.LocalDateTime;
import java.time.ZoneId;
@@ -115,6 +117,14 @@ public class UserLikeGroupServiceImpl extends ServiceImpl<UserLikeGroupMapper, U
private BrandDNAMapper brandDNAMapper;
@Resource
private BrandRelLibraryMapper brandRelLibraryMapper;
@Resource
private ThreeDLayoutMapper threeDLayoutMapper;
@Resource
private ThreeDPatternLayoutMapper threeDPatternLayoutMapper;
@Resource
private ThreeDDetailMapper threeDDetailMapper;
@Resource
private ThreeDSimpleMapper threeDSimpleMapper;
@Override
public void deleteUserGroup(Long userGroupId) {
@@ -1963,7 +1973,7 @@ public class UserLikeGroupServiceImpl extends ServiceImpl<UserLikeGroupMapper, U
}
@Override
public String brandDNAUpload(MultipartFile file, Long brandId) throws IOException {
public Library brandDNAUpload(MultipartFile file, Long brandId) throws IOException {
AuthPrincipalVo userHolder = UserContext.getUserHolder();
String path = userHolder.getId().toString() + "/brandLogo";
String upload = minioUtil.upload("aida-users", path, file);
@@ -1974,13 +1984,14 @@ public class UserLikeGroupServiceImpl extends ServiceImpl<UserLikeGroupMapper, U
library.setUrl(upload);
library.setMd5(MD5Utils.encryptFile(file.getInputStream()));
library.setCreateDate(new Date());
libraryMapper.insert(library);
BrandRelLibrary brandRelLibrary = new BrandRelLibrary();
brandRelLibrary.setLibraryId(library.getId());
brandRelLibrary.setBrandId(brandId);
brandRelLibraryMapper.insert(brandRelLibrary);
return upload;
return library;
}
@Override
@@ -2011,4 +2022,123 @@ public class UserLikeGroupServiceImpl extends ServiceImpl<UserLikeGroupMapper, U
vo.setMinioUrl(minioUtil.getPreSignedUrl(vo.getBrandLogo(), 24 * 60));
return vo;
}
@Override
public IPage<ThreeDLayoutVO> getThreeDLayoutPage(ThreeDLayoutQueryDTO query) {
// AuthPrincipalVo authPrincipalVo = UserContext.getUserHolder();
// 分页数据
QueryWrapper<ThreeDLayout> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(ThreeDLayout::getType, "front");
queryWrapper.lambda().eq(ThreeDLayout::getGender, "female");
IPage<ThreeDLayout> page = threeDLayoutMapper.selectPage(
new Page<>(query.getPage(), query.getSize()), queryWrapper);
if (CollectionUtils.isEmpty(page.getRecords())) {
return new Page<>();
}
IPage<ThreeDLayoutVO> convert = page.convert((Function<ThreeDLayout, ThreeDLayoutVO>) threeDLayout -> {
if (threeDLayout != null) {
ThreeDLayoutVO threeDLayoutVO = CopyUtil.copyObject(threeDLayout, ThreeDLayoutVO.class);
threeDLayoutVO.setUrl(minioUtil.getPreSignedUrl(threeDLayoutVO.getUrl(), 24 * 60));
return threeDLayoutVO;
}
return null;
});
return convert;
}
@Override
public ThreeDVO getLayoutDetail(Long threeDSimpleId) {
ThreeDVO result = new ThreeDVO();
QueryWrapper<ThreeDLayout> qw = new QueryWrapper<>();
qw.lambda().eq(ThreeDLayout::getThreeDSimpleId, threeDSimpleId);
List<ThreeDLayout> threeDLayoutList = threeDLayoutMapper.selectList(qw);
result.setThreeDLayoutList(threeDLayoutList);
QueryWrapper<ThreeDPatternLayout> threeDPatternLayoutQueryWrapper = new QueryWrapper<>();
threeDPatternLayoutQueryWrapper.lambda().eq(ThreeDPatternLayout::getThreeDSimpleId, threeDSimpleId);
List<ThreeDPatternLayout> threeDPatternLayoutList = threeDPatternLayoutMapper.selectList(threeDPatternLayoutQueryWrapper);
if (CollectionUtil.isNotEmpty(threeDPatternLayoutList)) {
result.setThreeDPatternLayout(threeDPatternLayoutList.get(0));
}
return result;
}
@Override
public ThreeDSizeVO getThreeDSize(Long threeDSimpleId) {
ThreeDSizeVO result = new ThreeDSizeVO();
QueryWrapper<ThreeDDetail> threeDDetailQueryWrapper = new QueryWrapper<>();
threeDDetailQueryWrapper.lambda().eq(ThreeDDetail::getThreeDSimpleId, threeDSimpleId);
List<ThreeDDetail> threeDDetailList = threeDDetailMapper.selectList(threeDDetailQueryWrapper);
if (CollectionUtil.isNotEmpty(threeDDetailList)) {
// 以 sizeType 分组
Map<String, List<String>> sizeTypeMap = new HashMap<>();
for (ThreeDDetail detail : threeDDetailList) {
sizeTypeMap
.computeIfAbsent(detail.getSizeType(), k -> new ArrayList<>())
.add(detail.getSize());
}
result.setSizeTypeMap(sizeTypeMap);
}
return result;
}
@Override
public void getThreeDGlb(Long threeDSimpleId, HttpServletResponse response) throws MinioException, IOException {
ThreeDSimple threeDSimple = threeDSimpleMapper.selectById(threeDSimpleId);
if (ObjectUtils.isAllFieldNull(threeDSimple)) {
throw new BusinessException("3D file is not exist");
}
InputStream inputStream = minioUtil.download(threeDSimple.getUrl());
// 设置响应头
response.setContentType("model/gltf-binary"); // 确保 MIME 类型正确
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
response.setHeader("Content-Disposition", "attachment; filename=\"" + threeDSimple.getName() + "\"");
// 将文件内容写入响应输出流
try {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
response.getOutputStream().write(buffer, 0, bytesRead);
}
inputStream.close();
response.getOutputStream().flush();
} catch (Exception e) {
throw new RuntimeException("Failed to get 3D .glb file", e);
}
}
@Override
public void downloadZip(Long threeDSimpleId, String sizeType, String size, HttpServletResponse response) throws MinioException, IOException {
QueryWrapper<ThreeDDetail> qw = new QueryWrapper<>();
qw.lambda().eq(ThreeDDetail::getThreeDSimpleId, threeDSimpleId);
qw.lambda().eq(ThreeDDetail::getSizeType, sizeType);
qw.lambda().eq(ThreeDDetail::getSize, size);
ThreeDDetail threeDDetail = threeDDetailMapper.selectOne(qw);
if (ObjectUtils.isAllFieldNull(threeDDetail)) {
throw new BusinessException("3D file is not exist");
}
InputStream inputStream = minioUtil.download(threeDDetail.getUrl());
// 设置响应头
response.setContentType("application/zip"); // 确保 ZIP 格式
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
response.setHeader("Content-Disposition", "attachment; filename=\"" + threeDDetail.getName() + "\"");
// 将文件内容写入响应输出流
try {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
response.getOutputStream().write(buffer, 0, bytesRead);
}
inputStream.close();
response.getOutputStream().flush();
} catch (Exception e) {
throw new RuntimeException("Failed to download ZIP file", e);
}
}
}