若依微服务实战:Spring Boot + Vue 搞定PDF上传预览,附完整前后端代码与配置避坑

张开发
2026/5/5 11:17:29 15 分钟阅读

分享文章

若依微服务实战:Spring Boot + Vue 搞定PDF上传预览,附完整前后端代码与配置避坑
若依微服务实战Spring Boot Vue 实现PDF上传预览全流程指南在当今企业级应用开发中文件管理功能几乎是每个系统的标配需求。特别是PDF文件的上传与预览功能在合同管理、报告审批、知识库建设等场景中尤为常见。本文将基于若依RuoYi微服务框架手把手带你实现一个完整的PDF文件上传预览解决方案。1. 环境准备与基础配置1.1 项目依赖配置首先确保你的若依微服务项目已经正确搭建。我们需要在业务模块中添加必要的依赖!-- Spring Web 核心依赖 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 文件操作工具类 -- dependency groupIdcommons-io/groupId artifactIdcommons-io/artifactId version2.11.0/version /dependency1.2 配置文件上传参数在application.yml中配置以下关键参数# 文件上传配置 file: upload: base-dir: /data/upload/ # 文件存储基础路径 max-size: 10485760 # 10MB大小限制 # Spring文件上传配置 spring: servlet: multipart: enabled: true max-file-size: 10MB # 单个文件最大尺寸 max-request-size: 10MB # 整个请求最大尺寸 file-size-threshold: 0B # 文件大小阈值超过则写入临时文件注意base-dir路径需要确保应用有读写权限生产环境建议使用绝对路径。2. 后端核心实现2.1 文件上传接口创建文件上传控制器处理PDF文件接收和存储RestController RequestMapping(/api/file) public class FileController { Value(${file.upload.base-dir}) private String baseDir; PostMapping(/upload) public AjaxResult uploadFile(RequestParam(file) MultipartFile file) { try { // 1. 基础校验 if (file.isEmpty()) { return AjaxResult.error(上传文件不能为空); } // 2. 文件类型校验 String originalFilename file.getOriginalFilename(); if (!originalFilename.toLowerCase().endsWith(.pdf)) { return AjaxResult.error(仅支持PDF文件上传); } // 3. 文件存储 String fileName UUID.randomUUID() .pdf; Path filePath Paths.get(baseDir, fileName); Files.createDirectories(filePath.getParent()); file.transferTo(filePath); // 4. 返回结果 String accessUrl /api/file/preview/ fileName; return AjaxResult.success(上传成功) .put(fileName, originalFilename) .put(filePath, accessUrl); } catch (IOException e) { return AjaxResult.error(文件上传失败 e.getMessage()); } } }2.2 文件预览接口实现PDF文件预览功能GetMapping(/preview/{fileName}) public void previewFile(PathVariable String fileName, HttpServletResponse response) throws IOException { Path filePath Paths.get(baseDir, fileName); if (!Files.exists(filePath)) { response.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } // 设置响应头 response.setContentType(application/pdf); response.setHeader(Content-Disposition, inline; filename\ fileName \); // 输出文件内容 Files.copy(filePath, response.getOutputStream()); response.getOutputStream().flush(); }3. 前端Vue实现3.1 文件上传组件使用Element UI的上传组件实现前端上传功能template el-upload classpdf-uploader drag :actionuploadUrl :headersheaders :before-uploadbeforeUpload :on-successhandleSuccess :on-errorhandleError :show-file-listfalse accept.pdf i classel-icon-upload/i div classel-upload__text 将PDF文件拖到此处或em点击上传/em /div div classel-upload__tip slottip 只能上传PDF文件且不超过10MB /div /el-upload /template script import { getToken } from /utils/auth export default { data() { return { uploadUrl: process.env.VUE_APP_BASE_API /api/file/upload, headers: { Authorization: Bearer getToken() } } }, methods: { beforeUpload(file) { const isPDF file.type application/pdf const isLt10M file.size / 1024 / 1024 10 if (!isPDF) { this.$message.error(只能上传PDF格式的文件!) return false } if (!isLt10M) { this.$message.error(上传文件大小不能超过10MB!) return false } return true }, handleSuccess(response) { if (response.code 200) { this.$message.success(上传成功) this.$emit(upload-success, response.data) } else { this.$message.error(response.msg) } }, handleError() { this.$message.error(上传失败请重试) } } } /script3.2 PDF预览组件实现PDF预览弹窗template el-dialog :visible.syncvisible titlePDF预览 width80% top5vh div classpdf-preview-container iframe :srcpdfUrl frameborder0 stylewidth:100%;height:80vh /iframe /div /el-dialog /template script export default { props: { visible: Boolean, filePath: String }, computed: { pdfUrl() { return process.env.VUE_APP_BASE_API this.filePath } } } /script4. 常见问题与解决方案4.1 文件大小限制问题若依微服务框架中可能会遇到多层级文件大小限制Spring Boot限制通过spring.servlet.multipart.max-file-size配置Tomcat限制默认1MB需要在application.yml中额外配置server: tomcat: max-http-form-post-size: 10MB4.2 跨服务文件访问在微服务架构下文件服务可能独立部署需要处理跨服务访问方案一通过网关统一路由文件请求方案二使用独立文件服务提供HTTP接口方案三共享存储如NFS、MinIO等4.3 安全防护措施文件上传功能需要特别注意安全性文件类型校验不仅检查扩展名还应检查MIME类型文件内容扫描使用防病毒软件扫描上传文件路径安全防止目录遍历攻击权限控制确保用户只能访问自己有权限的文件5. 性能优化建议5.1 大文件分片上传对于可能的大PDF文件实现分片上传// 前端分片上传示例 async function chunkUpload(file) { const chunkSize 2 * 1024 * 1024 // 2MB每片 const chunks Math.ceil(file.size / chunkSize) for (let i 0; i chunks; i) { const start i * chunkSize const end Math.min(file.size, start chunkSize) const chunk file.slice(start, end) const formData new FormData() formData.append(file, chunk) formData.append(chunkNumber, i) formData.append(totalChunks, chunks) formData.append(identifier, file.name file.size) await axios.post(/api/file/upload-chunk, formData, { headers: { Content-Type: multipart/form-data } }) } }5.2 文件存储优化考虑使用专业文件存储方案存储方案优点缺点本地存储简单直接扩展性差单点故障NFS共享访问性能瓶颈MinIOS3兼容高性能需要额外部署云存储弹性扩展成本较高5.3 缓存与CDN加速对于频繁访问的PDF文件配置HTTP缓存头使用CDN分发静态文件实现服务端缓存机制// 设置缓存头示例 response.setHeader(Cache-Control, public, max-age86400);6. 扩展功能实现6.1 文件水印功能为预览的PDF添加水印public void addWatermark(Path pdfPath, String watermarkText) throws IOException { PDDocument document PDDocument.load(pdfPath.toFile()); for (PDPage page : document.getPages()) { PDPageContentStream contentStream new PDPageContentStream( document, page, PDPageContentStream.AppendMode.APPEND, true); contentStream.setFont(PDType1Font.HELVETICA_BOLD, 36); contentStream.setNonStrokingColor(200, 200, 200); // 获取页面尺寸 PDRectangle pageSize page.getMediaBox(); float pageWidth pageSize.getWidth(); float pageHeight pageSize.getHeight(); // 计算水印位置和角度 contentStream.beginText(); contentStream.setTextMatrix( AffineTransform.getRotateInstance(Math.toRadians(45), pageWidth/3, pageHeight/3)); contentStream.showText(watermarkText); contentStream.endText(); contentStream.close(); } document.save(pdfPath.toFile()); document.close(); }6.2 文件加密存储保护敏感PDF文件public void encryptFile(Path sourcePath, Path targetPath, String password) throws IOException { PDDocument document PDDocument.load(sourcePath.toFile()); // 设置访问权限 AccessPermission ap new AccessPermission(); ap.setCanPrint(false); // 设置所有者密码 StandardProtectionPolicy spp new StandardProtectionPolicy( password, password, ap); spp.setEncryptionKeyLength(128); document.protect(spp); document.save(targetPath.toFile()); document.close(); }7. 部署与运维7.1 容器化部署配置若使用Docker部署需要注意文件存储卷的挂载FROM openjdk:8-jdk VOLUME /data/upload COPY target/your-service.jar app.jar ENTRYPOINT [java,-jar,/app.jar]7.2 日志监控配置文件操作日志便于问题排查Slf4j RestController RequestMapping(/api/file) public class FileController { PostMapping(/upload) public AjaxResult uploadFile(RequestParam(file) MultipartFile file) { log.info(开始上传文件: {}, 大小: {} bytes, file.getOriginalFilename(), file.getSize()); // ...上传逻辑 } }7.3 健康检查添加文件存储健康检查端点RestController RequestMapping(/actuator) public class HealthCheckController { Value(${file.upload.base-dir}) private String baseDir; GetMapping(/storage-health) public ResponseEntity? checkStorageHealth() { Path path Paths.get(baseDir); try { if (!Files.exists(path)) { Files.createDirectories(path); } // 测试写入权限 Path testFile path.resolve(healthcheck.tmp); Files.write(testFile, test.getBytes()); Files.delete(testFile); return ResponseEntity.ok().body( Map.of(status, UP, message, 文件存储可用)); } catch (Exception e) { return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) .body(Map.of(status, DOWN, error, e.getMessage())); } } }8. 最佳实践总结在实际项目中实现PDF上传预览功能时有几个关键点需要特别注意文件命名策略使用UUID而非原始文件名防止文件名冲突和注入攻击并发控制处理同时上传同名文件的情况断点续传对于大文件实现断点续传功能预览优化对于移动端可以考虑转换为图片序列预览版本控制实现文件版本管理保留历史版本// 文件版本控制示例 public class FileVersionService { public void saveWithVersion(Path filePath, MultipartFile file) throws IOException { String baseName FilenameUtils.getBaseName(filePath.toString()); String extension FilenameUtils.getExtension(filePath.toString()); // 查找现有版本 int version 1; while (Files.exists(filePath)) { version; filePath filePath.resolveSibling( String.format(%s_v%d.%s, baseName, version, extension)); } Files.copy(file.getInputStream(), filePath); } }在若依微服务框架中集成文件功能时建议将文件服务独立为一个微服务通过Feign客户端供其他服务调用。这样可以实现文件管理的统一和复用也便于后期扩展分布式文件存储等功能。

更多文章