refactor(file): 优化文件上传和下载功能

- 修改了文件上传接口,支持 InputStream 参数
- 优化了文件下载逻辑,使用临时文件和 ZipOutputStream
- 更新了相关实体类和控制器的方法- 新增了 RemindUserRequest 和 ZipDownloadRequest 类
This commit is contained in:
曹鹏飞 2025-04-07 17:13:02 +08:00
parent cd1775a297
commit 1a1f86c5ff
13 changed files with 111 additions and 45 deletions

View File

@ -3,8 +3,8 @@ package com.nflg.mobilebroken.admin.controller;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Sets;
import com.nflg.mobilebroken.admin.annotation.ApiMark;
@ -23,6 +23,7 @@ import com.nflg.mobilebroken.common.util.*;
import com.nflg.mobilebroken.repository.entity.*;
import com.nflg.mobilebroken.repository.service.*;
import com.nflg.mobilebroken.starter.annotation.MethodInfoMark;
import com.nflg.mobilebroken.starter.service.FileUploadService;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@ -36,9 +37,10 @@ import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@ -76,6 +78,9 @@ public class DeviceController extends ControllerBase {
@Resource
private IDeviceComponentService deviceComponentService;
@Resource
private FileUploadService fileUploadService;
/**
* 获取设备列表
@ -258,10 +263,10 @@ public class DeviceController extends ControllerBase {
public void downTemplateForAdd(HttpServletResponse response) throws Exception {
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode("设备新增导入模板.xlsx", "UTF-8"));
ClassPathResource resource = new ClassPathResource("templates/设备新增导入模板.xlsx");
ClassPathResource resource = new ClassPathResource("templates/deviceForAdd.xlsx");
List<DeviceAddImportDTO> datas = new ArrayList<>();
new Workbook()
.addSheet(new TemplateSheet(Paths.get(resource.getURI()))
.addSheet(new TemplateSheet(resource.getInputStream())
.setData(datas)
.setData("@list:deviceStateDesc", dictionaryItemService.getListByDictionaryCode(Constant.DICTIONARY_DEVICE_STATE)
.stream().map(DictionaryItem::getName).collect(Collectors.toList())))
@ -275,24 +280,28 @@ public class DeviceController extends ControllerBase {
@Transactional
@PostMapping("importForAdd")
@ApiMark(moduleName = "设备管理", apiName = "新增导入")
public void importForAdd(HttpServletResponse response, @RequestParam(value = "file") MultipartFile file) throws IOException {
public ApiResult importForAdd(HttpServletResponse response, @RequestParam(value = "file") MultipartFile file) throws IOException {
List<DeviceAddImportDTO> data = EecExcelUtil.getExcelContext(file.getInputStream(), DeviceAddImportDTO.class);
VUtils.trueThrowBusinessError(CollectionUtil.isEmpty(data)).throwMessage("导入文件内容为空");
if (addCheckAndImport(data)) {
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
byte[] datas = StrUtil.bytes(JSONUtil.toJsonStr(ApiResult.success()));
response.getOutputStream().write(datas, 0, datas.length);
return ApiResult.success();
} else {
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode("设备新增.xlsx", "UTF-8"));
ClassPathResource resource = new ClassPathResource("templates/设备新增导入模板.xlsx");
ClassPathResource resource = new ClassPathResource("templates/deviceForAdd.xlsx");
try(ByteArrayOutputStream osOut = new ByteArrayOutputStream()) {
new Workbook()
.addSheet(new TemplateSheet(Paths.get(resource.getURI()))
.addSheet(new TemplateSheet(resource.getInputStream())
.setData(data)
.setData("@list:deviceStateDesc", dictionaryItemService.getListByDictionaryCode(Constant.DICTIONARY_DEVICE_STATE)
.stream().map(DictionaryItem::getName).collect(Collectors.toList())))
.writeTo(response.getOutputStream());
.writeTo(osOut);
try(ByteArrayInputStream isIn = new ByteArrayInputStream(osOut.toByteArray())) {
return ApiResult.error(STATE.BusinessError, "导入文件失败",fileUploadService.upload("temp/" +DateTimeUtil.format(LocalDate.now(),"yyyyMMdd")+"/"+ IdUtil.fastUUID() + ".xlsx", isIn));
}
}catch (Exception e){
return ApiResult.error(STATE.BusinessError, "保存文件出错");
}
}
}
@ -446,7 +455,7 @@ public class DeviceController extends ControllerBase {
*/
@PostMapping("exportSelect")
@ApiMark(moduleName = "设备管理", apiName = "导出选中的设备")
public void exportSelect(HttpServletResponse response,@Valid @RequestBody @NotEmpty List<Integer> ids) throws IOException {
public void exportSelect(HttpServletResponse response,@Valid @RequestBody @NotEmpty List<Integer> ids) throws Exception {
List<Device> devices=deviceService.listByIds(ids);
List<DictionaryItem> states = dictionaryItemService.getListByDictionaryCode(Constant.DICTIONARY_DEVICE_STATE);
List<DeviceUpdateImportDTO> datas=devices.stream().map(d->{
@ -470,9 +479,9 @@ public class DeviceController extends ControllerBase {
}).collect(Collectors.toList());
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode("设备更新.xlsx", "UTF-8"));
ClassPathResource resource = new ClassPathResource("templates/设备更新导入模板.xlsx");
ClassPathResource resource = new ClassPathResource("templates/deviceForUpdate.xlsx");
new Workbook()
.addSheet(new TemplateSheet(Paths.get(resource.getURI()))
.addSheet(new TemplateSheet(resource.getInputStream())
.setData(datas)
.setData("@list:deviceStateDesc", dictionaryItemService.getListByDictionaryCode(Constant.DICTIONARY_DEVICE_STATE)
.stream().map(DictionaryItem::getName).collect(Collectors.toList())))
@ -486,24 +495,26 @@ public class DeviceController extends ControllerBase {
@Transactional
@PostMapping("importForUpdate")
@ApiMark(moduleName = "设备管理", apiName = "更新导入")
public void importForUpdate(HttpServletResponse response, @RequestParam(value = "file") MultipartFile file) throws IOException {
public ApiResult importForUpdate(@RequestParam(value = "file") MultipartFile file) throws IOException {
List<DeviceUpdateImportDTO> data = EecExcelUtil.getExcelContext(file.getInputStream(), DeviceUpdateImportDTO.class);
VUtils.trueThrowBusinessError(CollectionUtil.isEmpty(data)).throwMessage("导入文件内容为空");
if (updateCheckAndImport(data)) {
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
byte[] datas = StrUtil.bytes(JSONUtil.toJsonStr(ApiResult.success()));
response.getOutputStream().write(datas, 0, datas.length);
return ApiResult.success();
} else {
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode("设备更新.xlsx", "UTF-8"));
ClassPathResource resource = new ClassPathResource("templates/设备更新导入模板.xlsx");
ClassPathResource resource = new ClassPathResource("templates/deviceForUpdate.xlsx");
try(ByteArrayOutputStream osOut = new ByteArrayOutputStream()) {
new Workbook()
.addSheet(new TemplateSheet(Paths.get(resource.getURI()))
.addSheet(new TemplateSheet(resource.getInputStream())
.setData(data)
.setData("@list:deviceStateDesc", dictionaryItemService.getListByDictionaryCode(Constant.DICTIONARY_DEVICE_STATE)
.stream().map(DictionaryItem::getName).collect(Collectors.toList())))
.writeTo(response.getOutputStream());
.writeTo(osOut);
try(ByteArrayInputStream isIn = new ByteArrayInputStream(osOut.toByteArray())) {
return ApiResult.error(STATE.BusinessError, "导入文件失败",fileUploadService.upload("temp/" +DateTimeUtil.format(LocalDate.now(),"yyyyMMdd")+"/"+ IdUtil.fastUUID() + ".xlsx", isIn));
}
}catch (Exception e){
return ApiResult.error(STATE.BusinessError, "保存文件出错");
}
}
}

View File

@ -10,6 +10,7 @@ import com.nflg.mobilebroken.common.exception.NflgException;
import com.nflg.mobilebroken.common.pojo.ApiResult;
import com.nflg.mobilebroken.common.pojo.PageData;
import com.nflg.mobilebroken.common.pojo.request.FileSearchRequest;
import com.nflg.mobilebroken.common.pojo.request.ZipDownloadRequest;
import com.nflg.mobilebroken.common.pojo.vo.FileUploadVO;
import com.nflg.mobilebroken.common.pojo.vo.FileVO;
import com.nflg.mobilebroken.common.util.AdminUserUtil;
@ -191,18 +192,18 @@ public class FileController extends ControllerBase {
/**
* 文件压缩下载
*
* @param fileIds 文件id列表
* @param request 请求参数
*/
@PostMapping("zipDownload")
public ResponseEntity<byte[]> zipDownload(@Valid @RequestParam @NotNull List<Integer> fileIds) throws IOException {
List<String> urls = fileUploadRecordService.listByIds(fileIds).stream().map(FileUploadRecord::getUrl).collect(Collectors.toList());
public ResponseEntity<byte[]> zipDownload(@Valid @RequestBody ZipDownloadRequest request) throws IOException {
List<String> urls = fileUploadRecordService.listByIds(request.getFileIds()).stream().map(FileUploadRecord::getUrl).collect(Collectors.toList());
VUtils.trueThrowBusinessError(CollectionUtil.isEmpty(urls)).throwMessage("没有需要下载的文件");
Path tempZipFile = Files.createTempFile("files", ".zip");
try (FileOutputStream fos = new FileOutputStream(tempZipFile.toFile());
ZipOutputStream zos = new ZipOutputStream(fos)) {
for (String fileUrl : urls) {
Request request = new Request.Builder().url(fileUrl).build();
try (Response response = client.newCall(request).execute()) {
Request r = new Request.Builder().url(fileUrl).build();
try (Response response = client.newCall(r).execute()) {
if (response.isSuccessful() && response.body() != null) {
String fileName = fileUrl.substring(fileUrl.lastIndexOf('/') + 1);
ZipEntry zipEntry = new ZipEntry(fileName);

View File

@ -726,7 +726,7 @@ public class TicketController extends ControllerBase {
.setCreateTime(Instant.now())
.setAttachments(request.getAttachments())
.setImages(request.getImages())
.setRemindUserIds(request.getRemindUserIds());
.setRemindUsers(request.getRemindUsers());
if(Objects.nonNull(request.getQuoteId())){
ChatMessageDTO quoteMessage = ticketChatService.getMessage(request.getTicketId(), request.getQuoteId());
message.setQuote(quoteMessage);

View File

@ -323,7 +323,7 @@ public class TiketController extends ControllerBase {
.setCreateTime(Instant.now())
.setAttachments(request.getAttachments())
.setImages(request.getImages())
.setRemindUserIds(request.getRemindUserIds());
.setRemindUsers(request.getRemindUsers());
if(Objects.nonNull(request.getQuoteId())){
ChatMessageDTO quoteMessage = ticketChatService.getMessage(request.getTicketId(), request.getQuoteId());
message.setQuote(quoteMessage);

View File

@ -1,6 +1,7 @@
package com.nflg.mobilebroken.common.pojo.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.nflg.mobilebroken.common.pojo.request.RemindUserRequest;
import com.nflg.mobilebroken.common.pojo.vo.FileUploadVO;
import lombok.Data;
import lombok.experimental.Accessors;
@ -45,6 +46,6 @@ public class ChatMessageDTO {
// 引用的消息
private ChatMessageDTO quote;
//被艾特的用户id列表
private List<Integer> remindUserIds;
//被艾特的用户列表
private List<RemindUserRequest> remindUsers;
}

View File

@ -25,6 +25,6 @@ public class AddChatMessageRequest {
// 引用的消息
private String quoteId;
//被艾特的用户id列表
private List<Integer> remindUserIds;
//被艾特的用户列表
private List<RemindUserRequest> remindUsers;
}

View File

@ -0,0 +1,17 @@
package com.nflg.mobilebroken.common.pojo.request;
import lombok.Data;
@Data
public class RemindUserRequest {
/**
* 用户id
*/
private Integer id;
/**
* 用户名
*/
private String name;
}

View File

@ -0,0 +1,18 @@
package com.nflg.mobilebroken.common.pojo.request;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.List;
@Valid
@Data
public class ZipDownloadRequest {
/**
* 文件id列表
*/
@NotEmpty
private List<Integer> fileIds;
}

View File

@ -2,11 +2,13 @@ package com.nflg.mobilebroken.common.pojo.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class FileUploadVO {
/**

View File

@ -3,6 +3,7 @@ package com.nflg.mobilebroken.starter.service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
public interface FileUploadService {
@ -14,4 +15,13 @@ public interface FileUploadService {
* @throws IOException
*/
String upload(String filePath,MultipartFile file) throws IOException;
/**
* 上传文件
* @param filePath 文件完整路径: /cfs/xxx.jpg
* @param stream 文件流
* @return 可访问的url
* @throws IOException
*/
String upload(String filePath, InputStream stream) throws IOException;
}

View File

@ -13,6 +13,7 @@ import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
@Service
@ConditionalOnProperty(name = "file.upload.type", havingValue = "oss")
@ -30,8 +31,13 @@ public class OSSFileUploadService implements FileUploadService {
@Override
public String upload(String filePath, MultipartFile file) throws IOException {
return upload(filePath, file.getInputStream());
}
@Override
public String upload(String filePath, InputStream stream) {
// 上传文件到OSS
PutObjectResult result = ossClient.putObject(new PutObjectRequest(bucketName, filePath, file.getInputStream()));
PutObjectResult result = ossClient.putObject(new PutObjectRequest(bucketName, filePath, stream));
//log.debug("上传文件结果: " + result);
return StrUtil.format("{}/{}",domain, filePath);
}