|
|
|
@ -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"; |
|
|
|
} |
|
|
|
} |
|
|
|
} |