diff --git a/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/controller/GaeaFileController.java b/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/controller/GaeaFileController.java new file mode 100644 index 00000000..30009f11 --- /dev/null +++ b/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/controller/GaeaFileController.java @@ -0,0 +1,71 @@ +package com.anjiplus.template.gaea.business.modules.file.controller; + +import com.anji.plus.gaea.bean.ResponseBean; +import com.anji.plus.gaea.curd.service.GaeaBaseService; +import com.anjiplus.template.gaea.business.base.BaseController; +import com.anjiplus.template.gaea.business.modules.file.controller.dto.GaeaFileDTO; +import com.anjiplus.template.gaea.business.modules.file.controller.param.GaeaFileParam; +import com.anjiplus.template.gaea.business.modules.file.entity.GaeaFile; +import com.anjiplus.template.gaea.business.modules.file.service.GaeaFileService; +import io.swagger.annotations.Api; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * (GaeaFile)实体类 + * + * @author peiyanni + * @since 2021-02-18 14:48:33 + */ +@RestController +@RequestMapping("/file") +@Api(value = "/file", tags = "") +public class GaeaFileController extends BaseController { + @Autowired + private GaeaFileService gaeaFileService; + + @PostMapping("/upload") + public ResponseBean upload(@RequestParam("file") MultipartFile file) { + return ResponseBean.builder().message("success").data((gaeaFileService.upload(file))).build(); + } + + @GetMapping(value = "/download/{fileId}") + public ResponseEntity download(HttpServletRequest request, HttpServletResponse response, @PathVariable("fileId") String fileId) { + return gaeaFileService.download(request, response, fileId); + } + + /** + * 获取实际服务类 + * + * @return + */ + @Override + public GaeaBaseService getService() { + return gaeaFileService; + } + + /** + * 获取当前Controller数据库实体Entity + * + * @return + */ + @Override + public GaeaFile getEntity() { + return new GaeaFile(); + } + + /** + * 获取当前Controller数据传输DTO + * + * @return + */ + @Override + public GaeaFileDTO getDTO() { + return new GaeaFileDTO(); + } +} diff --git a/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/controller/dto/GaeaFileDTO.java b/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/controller/dto/GaeaFileDTO.java new file mode 100644 index 00000000..649c7f64 --- /dev/null +++ b/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/controller/dto/GaeaFileDTO.java @@ -0,0 +1,67 @@ +package com.anjiplus.template.gaea.business.modules.file.controller.dto; + +import com.anji.plus.gaea.curd.dto.GaeaBaseDTO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; + +/** + * (GaeaFile)实体类 + * + * @author peiyanni + * @since 2021-02-18 14:48:27 + */ +@ApiModel(value = "") +public class GaeaFileDTO extends GaeaBaseDTO { + + /** + * 文件uuid + */ + private String fileId; + /** + * 文件在linux中的完整目录,比如/app/dist/export/excel/${fileid}.xlsx + */ + @ApiModelProperty(value = "文件在linux中的完整目录,比如/app/dist/export/excel/${fileid}.xlsx") + private String filePath; + /** + * 通过接口的下载完整http路径 + */ + @ApiModelProperty(value = "通过接口的下载完整http路径") + private String urlPath; + /** + * 文件内容说明,比如 对账单(202001~202012) + */ + @ApiModelProperty(value = "文件内容说明,比如 对账单(202001~202012)") + private String fileInstruction; + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + public String getUrlPath() { + return urlPath; + } + + public void setUrlPath(String urlPath) { + this.urlPath = urlPath; + } + + public String getFileInstruction() { + return fileInstruction; + } + + public void setFileInstruction(String fileInstruction) { + this.fileInstruction = fileInstruction; + } + + public String getFileId() { + return fileId; + } + + public void setFileId(String fileId) { + this.fileId = fileId; + } +} diff --git a/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/controller/param/GaeaFileParam.java b/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/controller/param/GaeaFileParam.java new file mode 100644 index 00000000..0aa4040b --- /dev/null +++ b/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/controller/param/GaeaFileParam.java @@ -0,0 +1,31 @@ +package com.anjiplus.template.gaea.business.modules.file.controller.param; + + +import com.anji.plus.gaea.annotation.Query; +import com.anji.plus.gaea.constant.QueryEnum; +import com.anji.plus.gaea.curd.params.PageParam; + +import java.io.Serializable; + +/** + * (GaeaFile)param + * + * @author peiyanni + * @since 2021-02-18 14:48:29 + */ +public class GaeaFileParam extends PageParam implements Serializable { + + /** + * 文件在linux中的完整目录,比如/app/dist/export/excel/${fileid}.xlsx + */ + @Query(QueryEnum.LIKE) + private String filePath; + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } +} diff --git a/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/dao/GaeaFileMapper.java b/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/dao/GaeaFileMapper.java new file mode 100644 index 00000000..dabb1d35 --- /dev/null +++ b/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/dao/GaeaFileMapper.java @@ -0,0 +1,17 @@ +package com.anjiplus.template.gaea.business.modules.file.dao; + +import com.anji.plus.gaea.curd.mapper.GaeaBaseMapper; +import com.anjiplus.template.gaea.business.modules.file.entity.GaeaFile; +import org.apache.ibatis.annotations.Mapper; + +/** + * (GaeaFile)Mapper + * + * @author peiyanni + * @since 2021-02-18 14:48:24 + */ +@Mapper +public interface GaeaFileMapper extends GaeaBaseMapper { + + +} diff --git a/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/entity/GaeaFile.java b/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/entity/GaeaFile.java new file mode 100644 index 00000000..38852564 --- /dev/null +++ b/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/entity/GaeaFile.java @@ -0,0 +1,65 @@ +package com.anjiplus.template.gaea.business.modules.file.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.anji.plus.gaea.curd.entity.GaeaBaseEntity; + +import java.io.Serializable; + +/** + * (GaeaFile)实体类 + * + * @author peiyanni + * @since 2021-02-18 14:48:20 + */ +@TableName("gaea_file") +public class GaeaFile extends GaeaBaseEntity implements Serializable { + + /** + * 文件uuid + */ + private String fileId; + /** + * 文件在linux中的完整目录,比如/app/dist/export/excel/${fileid}.xlsx + */ + private String filePath; + /** + * 通过接口的下载完整http路径 + */ + private String urlPath; + /** + * 文件内容说明,比如 对账单(202001~202012) + */ + private String fileInstruction; + + public String getFilePath() { + return filePath; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + public String getUrlPath() { + return urlPath; + } + + public void setUrlPath(String urlPath) { + this.urlPath = urlPath; + } + + public String getFileInstruction() { + return fileInstruction; + } + + public void setFileInstruction(String fileInstruction) { + this.fileInstruction = fileInstruction; + } + + public String getFileId() { + return fileId; + } + + public void setFileId(String fileId) { + this.fileId = fileId; + } +} diff --git a/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/service/GaeaFileService.java b/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/service/GaeaFileService.java new file mode 100644 index 00000000..6c7f0331 --- /dev/null +++ b/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/service/GaeaFileService.java @@ -0,0 +1,38 @@ +package com.anjiplus.template.gaea.business.modules.file.service; + +import com.anji.plus.gaea.curd.service.GaeaBaseService; +import com.anjiplus.template.gaea.business.modules.file.entity.GaeaFile; +import com.anjiplus.template.gaea.business.modules.file.controller.param.GaeaFileParam; +import org.springframework.http.ResponseEntity; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * (GaeaFile)Service + * + * @author peiyanni + * @since 2021-02-18 14:48:25 + */ +public interface GaeaFileService extends GaeaBaseService { + + + /** + * 文件上传 + * + * @param file + * @return 文件访问路径 + */ + String upload(MultipartFile file); + + /** + * 根据fileId显示图片或者下载文件 + * + * @param request + * @param response + * @param fileId + * @return + */ + ResponseEntity download(HttpServletRequest request, HttpServletResponse response, String fileId); +} diff --git a/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/service/impl/GaeaFileServiceImpl.java b/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/service/impl/GaeaFileServiceImpl.java new file mode 100644 index 00000000..259e3a8d --- /dev/null +++ b/report-core/src/main/java/com/anjiplus/template/gaea/business/modules/file/service/impl/GaeaFileServiceImpl.java @@ -0,0 +1,187 @@ +package com.anjiplus.template.gaea.business.modules.file.service.impl; + +import com.anji.plus.gaea.constant.BaseOperationEnum; +import com.anji.plus.gaea.exception.BusinessException; +import com.anjiplus.template.gaea.business.modules.file.dao.GaeaFileMapper; +import com.anjiplus.template.gaea.business.modules.file.entity.GaeaFile; +import com.anjiplus.template.gaea.business.modules.file.service.GaeaFileService; +import com.anjiplus.template.gaea.business.modules.export.dao.GaeaExportMapper; +import com.anjiplus.template.gaea.business.modules.export.dao.entity.GaeaExport; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.anjiplus.template.gaea.common.RespCommonCode; +import com.anji.plus.gaea.curd.mapper.GaeaBaseMapper; +import com.anji.plus.gaea.exception.BusinessExceptionBuilder; +import com.anjiplus.template.gaea.common.util.StringPatternUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.CacheControl; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.interceptor.TransactionAspectSupport; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * (GaeaFile)ServiceImpl + * + * @author peiyanni + * @since 2021-02-18 14:48:26 + */ +@Service +@Slf4j +public class GaeaFileServiceImpl implements GaeaFileService { + + @Value("${file.dist-path:''}") + private String dictPath; + + @Value("${file.white-list:''}") + private String whiteList; + + @Value("${file.excelSuffix:''}") + private String excelSuffix; + + @Value("${file.downloadPath:''}") + private String fileDownloadPath; + + @Autowired + private GaeaFileMapper gaeaFileMapper; + @Autowired + private GaeaExportMapper gaeaExportMapper; + + @Override + public GaeaBaseMapper getMapper() { + return gaeaFileMapper; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String upload(MultipartFile file) { + try { + String fileName = file.getOriginalFilename(); + if (StringUtils.isBlank(fileName)) { + throw BusinessExceptionBuilder.build(RespCommonCode.FILE_EMPTY_FILENAME); + } + String suffixName = fileName.substring(fileName.lastIndexOf(".")); + //白名单校验(不区分大小写) + List list = new ArrayList(Arrays.asList(whiteList.split("\\|"))); + list.addAll(list.stream().map(String::toUpperCase).collect(Collectors.toList())); + if (!list.contains(suffixName)) { + throw BusinessExceptionBuilder.build(RespCommonCode.FILE_SUFFIX_UNSUPPORTED); + } + // 生成文件唯一性标识 + String fileId = UUID.randomUUID().toString(); + String newFileName = fileId + suffixName; + // 本地文件保存路径 + String filePath = dictPath + newFileName; + String urlPath = fileDownloadPath + File.separator + fileId; + + GaeaFile gaeaFile = new GaeaFile(); + gaeaFile.setFilePath(filePath); + gaeaFile.setFileId(fileId); + gaeaFile.setUrlPath(urlPath); + gaeaFileMapper.insert(gaeaFile); + + //写文件 将文件保存/app/dictPath/upload/下 + File dest = new File(dictPath + newFileName); + file.transferTo(dest); + // 将完整的http访问路径返回 + return urlPath; + } catch (Exception e) { + TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); + log.error("file upload error: {}", e); + throw BusinessExceptionBuilder.build(RespCommonCode.FILE_UPLOAD_ERROR); + } + } + + @Override + public ResponseEntity download(HttpServletRequest request, HttpServletResponse response, String fileId) { + try { + String userAgent = request.getHeader("User-Agent"); + boolean isIEBrowser = userAgent.indexOf("MSIE") > 0; + //根据fileId,从gaea_file中读出filePath + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); + queryWrapper.eq(GaeaFile::getFileId, fileId); + GaeaFile gaeaFile = gaeaFileMapper.selectOne(queryWrapper); + if (null == gaeaFile) { + throw BusinessExceptionBuilder.build(RespCommonCode.FILE_ONT_EXSIT); + } + //解析文件路径、文件名和后缀 + String filePath = gaeaFile.getFilePath(); + if (StringUtils.isBlank(filePath)) { + throw BusinessExceptionBuilder.build(RespCommonCode.FILE_ONT_EXSIT); + } + String filename = filePath.substring(filePath.lastIndexOf(File.separator)); + String fileSuffix = filename.substring(filename.lastIndexOf(".")); + //特殊处理:如果是excel文件,则从t_export表中查询文件名 + List list = Arrays.asList(excelSuffix.split("\\|")); + if (list.contains(fileSuffix)) { + LambdaQueryWrapper exportWrapper = Wrappers.lambdaQuery(); + exportWrapper.eq(GaeaExport::getFileId, fileId); + GaeaExport exportPO = gaeaExportMapper.selectOne(exportWrapper); + if (null != exportPO) { + filename = exportPO.getFileTitle() + fileSuffix; + } + } + //根据文件后缀来判断,是显示图片\视频\音频,还是下载文件 + File file = new File(filePath); + ResponseEntity.BodyBuilder builder = ResponseEntity.ok(); + builder.contentLength(file.length()); + if (StringPatternUtil.StringMatchIgnoreCase(fileSuffix, "(.png|.jpg|.jpeg|.bmp|.gif|.icon)")) { + builder.cacheControl(CacheControl.noCache()).contentType(MediaType.IMAGE_PNG); + } else if (StringPatternUtil.StringMatchIgnoreCase(fileSuffix, "(.flv|.swf|.mkv|.avi|.rm|.rmvb|.mpeg|.mpg|.ogg|.ogv|.mov|.wmv|.mp4|.webm|.wav|.mid|.mp3|.aac)")) { + builder.header("Content-Type", "video/mp4; charset=UTF-8"); + } else { + //application/octet-stream 二进制数据流(最常见的文件下载) + builder.contentType(MediaType.APPLICATION_OCTET_STREAM); + filename = URLEncoder.encode(filename, "UTF-8"); + if (isIEBrowser) { + builder.header("Content-Disposition", "attachment; filename=" + filename); + } else { + builder.header("Content-Disposition", "attacher; filename*=UTF-8''" + filename); + } + } + return builder.body(FileUtils.readFileToByteArray(file)); + } catch (Exception e) { + log.error("file download error: {}", e); + return null; + } + } + + /** + * 批处理操作后续处理 + * 删除本地已经存在的文件 + * + * @param entities + * @param operationEnum 操作类型 + * @throws BusinessException 阻止程序继续执行或回滚事务 + */ + @Override + public void processBatchAfterOperation(List entities, BaseOperationEnum operationEnum) throws BusinessException { + if (operationEnum.equals(BaseOperationEnum.DELETE_BATCH)) { + // 删除本地文件 + entities.forEach(gaeaFile -> { + String filePath = gaeaFile.getFilePath(); + File file = new File(filePath); + if (file.exists()) { + file.delete(); + } + }); + } + + } +}