diff --git a/src/main/java/com/xujie/sys/common/utils/OfficeConverter.java b/src/main/java/com/xujie/sys/common/utils/OfficeConverter.java new file mode 100644 index 00000000..83a0572f --- /dev/null +++ b/src/main/java/com/xujie/sys/common/utils/OfficeConverter.java @@ -0,0 +1,53 @@ +package com.xujie.sys.common.utils; + +import com.aspose.cells.PageOrientationType; +import com.aspose.cells.PageSetup; +import com.aspose.cells.PaperSizeType; +import com.aspose.cells.SaveFormat; +import com.aspose.cells.Workbook; +import com.aspose.cells.Worksheet; +import com.aspose.words.Document; + +import java.io.File; + +/** + * Office文件转PDF工具类(使用Aspose) + */ +public class OfficeConverter { + + public static String getFileExtension(File file) { + String fileName = file.getName(); + int lastDot = fileName.lastIndexOf('.'); + return (lastDot == -1) ? "" : fileName.substring(lastDot + 1); + } + + public static void convertExcelToPdf(File file) throws Exception { + Workbook workbook; + String filePath = file.getAbsolutePath(); + + if (filePath.endsWith(".xlsx")) { + workbook = new Workbook(file.getAbsolutePath()); + } else { + workbook = new Workbook(filePath); + } + + Worksheet worksheet = workbook.getWorksheets().get(0); + PageSetup pageSetup = worksheet.getPageSetup(); + pageSetup.setOrientation(PageOrientationType.PORTRAIT); + pageSetup.setPaperSize(PaperSizeType.PAPER_A_4); + pageSetup.setZoom(50); + pageSetup.setLeftMargin(0); + pageSetup.setRightMargin(0); + pageSetup.setTopMargin(0.5); + pageSetup.setBottomMargin(0.5); + + String pdfPath = filePath.replaceAll("\\.xlsx|\\.xls", ".pdf"); + workbook.save(pdfPath, SaveFormat.PDF); + } + + public static void convertWordToPdf(File file) throws Exception { + Document document = new Document(file.getAbsolutePath()); + String pdfPath = file.getAbsolutePath().replace(".docx", ".pdf").replace(".doc", ".pdf"); + document.save(pdfPath); + } +} diff --git a/src/main/java/com/xujie/sys/config/ShiroConfig.java b/src/main/java/com/xujie/sys/config/ShiroConfig.java index f0088d23..f2d5f183 100644 --- a/src/main/java/com/xujie/sys/config/ShiroConfig.java +++ b/src/main/java/com/xujie/sys/config/ShiroConfig.java @@ -48,6 +48,7 @@ public class ShiroConfig { filterMap.put("/tcp", "anon"); filterMap.put("/ckp-file/**", "anon"); filterMap.put("/oss/2/**", "anon"); + filterMap.put("/oss/previewOssFile", "anon"); // filterMap.put("/**", "authc"); filterMap.put("/webjars/**", "anon"); filterMap.put("/druid/**", "anon"); diff --git a/src/main/java/com/xujie/sys/modules/oss/controller/OssController.java b/src/main/java/com/xujie/sys/modules/oss/controller/OssController.java index c8e96273..5c7094e8 100644 --- a/src/main/java/com/xujie/sys/modules/oss/controller/OssController.java +++ b/src/main/java/com/xujie/sys/modules/oss/controller/OssController.java @@ -1,21 +1,33 @@ package com.xujie.sys.modules.oss.controller; +import com.xujie.sys.common.utils.OfficeConverter; import com.xujie.sys.common.utils.R; import com.xujie.sys.modules.oss.entity.SysOssEntity; import com.xujie.sys.modules.oss.service.SysOssService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import jakarta.servlet.http.HttpServletResponse; import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.List; @RestController @RequestMapping("/oss") public class OssController { + private static final Logger logger = LoggerFactory.getLogger(OssController.class); + @Value("${sys-file.mes-url}") + private String mesUrl; @Autowired private SysOssService sysOssService; @@ -62,4 +74,220 @@ public class OssController { public void previewOssFileById2(@PathVariable("id") Integer id, HttpServletResponse response) throws Exception { sysOssService.previewOssFileById2(id,response); } + + /** + * 通过OSS服务代理预览或下载附件 + * OSS接口:POST http://172.26.68.17:8091//oss/2/{id} + * Word/Excel在预览模式下自动转换为PDF后输出 + * + * @param id 附件ID(sys_oss.id) + * @param download 是否强制下载(传 true 则下载,否则预览) + */ + @GetMapping("/previewOssFile") + public void previewOssFile(Long id, String download, HttpServletResponse response) { + File tempFile = null; + File pdfFile = null; + try { + SysOssEntity attachment = sysOssService.getById(id); + if (attachment == null) { + response.sendError(HttpServletResponse.SC_NOT_FOUND, "附件不存在"); + return; + } + String fileType = attachment.getFileType(); + String fileName = attachment.getFileName(); + if (fileType != null && fileType.startsWith(".")) { + fileType = fileType.substring(1); + } + fileType = fileType == null ? "" : fileType.toLowerCase().trim(); + + boolean forceDownload = "true".equalsIgnoreCase(download); + + String ossUrl = mesUrl + id; + logger.info("请求OSS文件: {}", ossUrl); + HttpURLConnection conn = (HttpURLConnection) new URL(ossUrl).openConnection(); + conn.setRequestMethod("POST"); + conn.setConnectTimeout(10000); + conn.setReadTimeout(60000); + conn.setDoInput(true); + conn.setDoOutput(true); + conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8"); + conn.setRequestProperty("Content-Length", "0"); + try (OutputStream reqOs = conn.getOutputStream()) { + reqOs.write(new byte[0]); + reqOs.flush(); + } + + int httpCode = conn.getResponseCode(); + String ossContentType = conn.getContentType(); + logger.info("OSS响应码: {}, Content-Type: {}", httpCode, ossContentType); + if (httpCode != HttpURLConnection.HTTP_OK) { + response.sendError(HttpServletResponse.SC_BAD_GATEWAY, "OSS服务器响应错误: " + httpCode); + conn.disconnect(); + return; + } + + byte[] fileBytes; + try (InputStream is = conn.getInputStream()) { + fileBytes = is.readAllBytes(); + } + conn.disconnect(); + + if (fileBytes.length == 0) { + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "OSS服务器返回空文件"); + return; + } + + String actualFileType = detectActualFileType(fileBytes, ossContentType, fileType); + logger.info("OSS文件大小: {} bytes, db_fileType: {}, actual: {}", fileBytes.length, fileType, actualFileType); + + if (!forceDownload && actualFileType.equals("pdf")) { + fileType = "pdf"; + int dotIdx = fileName.lastIndexOf('.'); + fileName = (dotIdx > 0 ? fileName.substring(0, dotIdx) : fileName) + ".pdf"; + } else if (!forceDownload && (actualFileType.equals("doc") || actualFileType.equals("docx") + || actualFileType.equals("xls") || actualFileType.equals("xlsx"))) { + tempFile = File.createTempFile("oss_preview_", "." + actualFileType); + try (FileOutputStream fos = new FileOutputStream(tempFile)) { + fos.write(fileBytes); + fos.flush(); + } + logger.info("临时文件已写入: {}, size: {}", tempFile.getAbsolutePath(), tempFile.length()); + if (actualFileType.equals("xls") || actualFileType.equals("xlsx")) { + OfficeConverter.convertExcelToPdf(tempFile); + } else { + OfficeConverter.convertWordToPdf(tempFile); + } + String pdfPath = tempFile.getAbsolutePath() + .replaceAll("\\.(doc|docx|xls|xlsx)$", ".pdf"); + pdfFile = new File(pdfPath); + logger.info("期望PDF路径: {}, 是否存在: {}", pdfPath, pdfFile.exists()); + if (pdfFile.exists()) { + fileBytes = Files.readAllBytes(pdfFile.toPath()); + fileType = "pdf"; + int dotIdx = fileName.lastIndexOf('.'); + fileName = (dotIdx > 0 ? fileName.substring(0, dotIdx) : fileName) + ".pdf"; + } else { + forceDownload = true; + fileType = actualFileType; + } + } else { + fileType = actualFileType; + } + + response.setContentType(getContentType(fileType)); + response.setContentLengthLong(fileBytes.length); + response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + response.setHeader("Pragma", "no-cache"); + if (!forceDownload && isPreviewableType(fileType)) { + response.setHeader("Content-Disposition", "inline"); + } else { + String encoded = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()) + .replaceAll("\\+", "%20"); + response.setHeader("Content-Disposition", + "attachment; filename=\"" + encoded + "\"; filename*=UTF-8''" + encoded); + } + try (OutputStream os = response.getOutputStream()) { + os.write(fileBytes); + os.flush(); + } + + } catch (Exception e) { + logger.error("previewOssFile error: {}", e.getMessage(), e); + try { + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "获取文件失败: " + e.getMessage()); + } catch (Exception ex) { + logger.error("sendError failed", ex); + } + } finally { + if (tempFile != null && tempFile.exists()) tempFile.delete(); + if (pdfFile != null && pdfFile.exists()) pdfFile.delete(); + } + } + + private String detectActualFileType(byte[] bytes, String ossContentType, String dbFileType) { + if (bytes != null && bytes.length >= 4) { + if (bytes[0] == 0x25 && bytes[1] == 0x50 && bytes[2] == 0x44 && bytes[3] == 0x46) { + return "pdf"; + } + if (bytes[0] == 0x50 && bytes[1] == 0x4B) { + if (dbFileType != null && (dbFileType.equals("xlsx") || dbFileType.equals("xls"))) { + return "xlsx"; + } + return "docx"; + } + if ((bytes[0] & 0xFF) == 0xD0 && (bytes[1] & 0xFF) == 0xCF + && bytes[2] == 0x11 && (bytes[3] & 0xFF) == 0xE0) { + if (dbFileType != null && (dbFileType.equals("xls") || dbFileType.equals("xlsx"))) { + return "xls"; + } + return "doc"; + } + if ((bytes[0] & 0xFF) == 0xFF && (bytes[1] & 0xFF) == 0xD8) { + return "jpg"; + } + if ((bytes[0] & 0xFF) == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47) { + return "png"; + } + } + if (ossContentType != null) { + String ct = ossContentType.toLowerCase(); + if (ct.contains("pdf")) return "pdf"; + if (ct.contains("jpeg") || ct.contains("jpg")) return "jpg"; + if (ct.contains("png")) return "png"; + if (ct.contains("word") || ct.contains("msword")) { + return dbFileType != null && dbFileType.equals("docx") ? "docx" : "doc"; + } + if (ct.contains("excel") || ct.contains("spreadsheet")) { + return dbFileType != null && dbFileType.equals("xlsx") ? "xlsx" : "xls"; + } + } + return dbFileType != null ? dbFileType : "bin"; + } + + private boolean isPreviewableType(String fileType) { + if (fileType == null || fileType.trim().isEmpty()) { + return false; + } + String t = fileType.toLowerCase().trim(); + if (t.startsWith(".")) t = t.substring(1); + return t.equals("pdf") || t.equals("jpg") || t.equals("jpeg") || t.equals("png") + || t.equals("gif") || t.equals("bmp") || t.equals("svg") || t.equals("webp") + || t.equals("txt") || t.equals("html") || t.equals("htm") + || t.equals("xml") || t.equals("json"); + } + + private String getContentType(String fileType) { + if (fileType == null || fileType.trim().isEmpty()) { + return "application/octet-stream"; + } + String t = fileType.toLowerCase().trim(); + if (t.startsWith(".")) t = t.substring(1); + switch (t) { + case "jpg": case "jpeg": return "image/jpeg"; + case "png": return "image/png"; + case "gif": return "image/gif"; + case "bmp": return "image/bmp"; + case "svg": return "image/svg+xml"; + case "webp": return "image/webp"; + case "ico": return "image/x-icon"; + case "pdf": return "application/pdf"; + case "doc": return "application/msword"; + case "docx": return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; + case "xls": return "application/vnd.ms-excel"; + case "xlsx": return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + case "ppt": return "application/vnd.ms-powerpoint"; + case "pptx": return "application/vnd.openxmlformats-officedocument.presentationml.presentation"; + case "txt": return "text/plain; charset=UTF-8"; + case "html": case "htm": return "text/html; charset=UTF-8"; + case "xml": return "text/xml; charset=UTF-8"; + case "json": return "application/json; charset=UTF-8"; + case "zip": return "application/zip"; + case "rar": return "application/x-rar-compressed"; + case "7z": return "application/x-7z-compressed"; + case "mp4": return "video/mp4"; + case "avi": return "video/x-msvideo"; + case "mp3": return "audio/mpeg"; + default: return "application/octet-stream"; + } + } } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 0527a857..56db05c8 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -4,7 +4,7 @@ spring: druid: driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver # url: jdbc:sqlserver://192.168.1.90:1433;databaseName=CKP_MES_Real_250207 - url: jdbc:sqlserver://xujiesoft.vicp.net:11515;databaseName=CKP_MES_Real_250207 + url: jdbc:sqlserver://xujiesoft.vicp.net:11515;databaseName=CKT_MES_II_REAL_0309 # url: jdbc:sqlserver://xujiesoft.vicp.net:11515;databaseName=ckp-srm username: sa password: XJsoft123 @@ -43,6 +43,7 @@ spring: sys-file: file-path: 'D:\ckp-file'#不可以放入中文字符 oss-url: 'http://192.168.1.160:9000/' + mes-url: 'http://192.168.1.162:8089/oss/2/' #--------------------------------------------定时器任务参数------------------------------------------------- task: