方式一 spire.xls.free(没找设置分辨率的方法)
macOs开发Java GUI程序提示缺少字体问题解决
Spire.XLS:一款Excel处理神器_spire.xls免费版和收费版的区别-CSDN博客
官方文档
XML
<dependency>
<groupId>e-iceblue</groupId>
<artifactId>spire.xls.free</artifactId>
<version>5.1.0</version>
</dependency>
<repositories>
<repository>
<id>e-iceblue</id>
<name>e-iceblue</name>
<url>https://repo.e-iceblue.cn/repository/maven-public/</url>
</repository>
</repositories>
java
/**
* 功能描述: 处理将Excel文件转换为图片并提供下载的请求。
* 参数说明:
* excelFilePath: 存储在服务器上的Excel文件的绝对路径。
* 返回值说明: ResponseEntity 包含图片文件流,供浏览器下载。
* 使用示例:
* GET /downloadExcelAsImage?excelFilePath=/path/to/your/excel.xlsx
*/
@GetMapping( "/downloadImage/{id}")
public ResponseEntity<InputStreamResource> downloadExcelAsImage(@PathVariable("id") String id) throws IOException {
// 调用服务层方法将Excel转换为图片
File imageFile = resultHistoryService.convertFromFilePath(id);
// 设置HTTP头,用于文件下载
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + imageFile.getName());
headers.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
headers.add(HttpHeaders.PRAGMA, "no-cache");
headers.add(HttpHeaders.EXPIRES, "0");
InputStreamResource resource = new InputStreamResource(new FileInputStream(imageFile));
return ResponseEntity.ok()
.headers(headers)
.contentLength(imageFile.length())
// 或者根据实际生成的图片类型调整 MediaType.IMAGE_JPEG 等
.contentType(MediaType.IMAGE_PNG)
.body(resource);
}
java
public File convertFromFilePath(String id) throws IOException {
ResultHistoryDO resultHistoryDO = resultHistoryDao.get(id);
String uploadPath = bootdoConfig.getUploadPath();
String fileurl = resultHistoryDO.getFileurl();
String[] split = fileurl.split("/files/");
String fileName =uploadPath+ split[1] ;
Workbook workbook = new Workbook();
// 加载Excel文档
workbook.loadFromFile(fileName);
// 获取第一个工作表 (您可以根据需要选择特定的工作表)
Worksheet sheet = workbook.getWorksheets().get(0);
// 定义输出图片的文件名和路径 (这里我们将其保存在临时目录)
// 您可以根据需要更改保存路径和文件名逻辑
File outputFile = File.createTempFile("excel_image_", ".png");
// 将工作表保存为图片
sheet.saveToImage(outputFile.getAbsolutePath());
return outputFile;
}
方式二aspose-cells(推荐设置分辨率转出的图片更清晰)
XML
<dependency>
<groupId>com.luhuiguo</groupId>
<artifactId>aspose-cells</artifactId>
<version>23.1</version>
</dependency>
java
package com.charsming.common.domain;
/**
* 功能描述: 用于封装图片数据及其元数据的包装类。
*/
public class ImageDataWrapper {
// 图片的字节数组
private byte[] data;
// 建议的下载文件名
private String fileName;
// 图片的MIME类型 (例如 "image/png")
private String contentType;
/**
* 功能描述: ImageDataWrapper的构造函数。
* 参数: data - 图片的字节数组。
* 参数: fileName - 建议的下载文件名。
* 参数: contentType - 图片的MIME类型。
*/
public ImageDataWrapper(byte[] data, String fileName, String contentType) {
this.data = data;
this.fileName = fileName;
this.contentType = contentType;
}
// Getter 方法
public byte[] getData() {
return data;
}
public String getFileName() {
return fileName;
}
public String getContentType() {
return contentType;
}
}
java
@GetMapping("/downloadImage/{id}")
public ResponseEntity<InputStreamResource> downloadExcelAsImage(@PathVariable("id") String id, HttpServletResponse response) throws Exception {
try {
// logger.info("请求下载图片,ID: {}", id); // 日志记录
ImageDataWrapper imageData = resultHistoryService.convertExcelToImageData(id);
HttpHeaders headers = new HttpHeaders();
// 设置Content-Disposition,提示浏览器下载并指定文件名
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + imageData.getFileName() + "\"");
// 其他缓存控制相关的头信息 (可选,但推荐)
headers.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
headers.add(HttpHeaders.PRAGMA, "no-cache");
headers.add(HttpHeaders.EXPIRES, "0");
InputStreamResource resource = new InputStreamResource(new ByteArrayInputStream(imageData.getData()));
// logger.info("成功生成图片: {}, 大小: {} bytes", imageData.getFileName(), imageData.getData().length); // 日志记录
return ResponseEntity.ok()
.headers(headers)
.contentLength(imageData.getData().length)
.contentType(MediaType.parseMediaType(imageData.getContentType()))
.body(resource);
} catch (Exception e) {
// logger.error("下载图片失败,ID: {}. 错误: {}", id, e.getMessage(), e); // 记录异常堆栈
// 在发生错误时,返回一个带有错误信息的ResponseEntity
// 您可以根据需要定制错误响应的格式,例如返回一个JSON对象
String errorMessage = "下载图片失败: " + e.getMessage();
InputStreamResource errorResource = new InputStreamResource(
new ByteArrayInputStream(errorMessage.getBytes(java.nio.charset.StandardCharsets.UTF_8))
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.contentType(MediaType.TEXT_PLAIN)
.body(errorResource);
}
java
@Override
public ImageDataWrapper convertExcelToImageData(String id) throws Exception {
ResultHistoryDO resultHistoryDO = resultHistoryDao.get(id);
if (resultHistoryDO == null || resultHistoryDO.getFileurl() == null) {
throw new Exception("未找到ID为 " + id + " 的结果历史记录或文件路径为空。");
}
String uploadPath = bootdoConfig.getUploadPath();
String fileurl = resultHistoryDO.getFileurl();
// 注意:此处的分割逻辑可能需要根据您的实际fileurl格式调整
String[] split = fileurl.split("/files/");
if (split.length < 2) {
throw new Exception("文件路径格式不正确,无法提取文件名: " + fileurl);
}
String excelFilePath = uploadPath + split[1];
Workbook workbook = null;
// 使用try-with-resources确保baos关闭
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
workbook = new Workbook(excelFilePath);
Worksheet worksheet = workbook.getWorksheets().get(0);
// --- 全局修改默认字体 开始 ---
Style defaultStyle = workbook.getDefaultStyle();
Font defaultFont = defaultStyle.getFont();
defaultFont.setName("Times New Roman"); // 设置全局默认字体名称
defaultFont.setSize(11); // 设置全局默认字体大小
// defaultFont.setBold(false); // 根据需要设置其他属性
workbook.setDefaultStyle(defaultStyle); // 应用修改后的默认样式到整个工作簿
ImageOrPrintOptions imgOptions = new ImageOrPrintOptions();
imgOptions.setImageType(ImageType.PNG);
imgOptions.setHorizontalResolution(800);
imgOptions.setVerticalResolution(800);
SheetRender sr = new SheetRender(worksheet, imgOptions);
if (sr.getPageCount() > 0) {
// 渲染第一页
int pageIndexToRender = 0;
sr.toImage(pageIndexToRender, baos);
// baos.flush(); // ByteArrayOutputStream的flush是空操作,可以省略
byte[] imageBytes = baos.toByteArray();
String originalFileName = new File(excelFilePath).getName();
String baseName = originalFileName.contains(".") ? originalFileName.substring(0, originalFileName.lastIndexOf('.')) : originalFileName;
String suggestedFileName = baseName + (pageIndexToRender + 1) + ".png";
String contentType = "image/png";
return new ImageDataWrapper(imageBytes, suggestedFileName, contentType);
} else {
throw new Exception("Excel工作表 " + excelFilePath + " 为空或无法渲染成图片。");
}
} finally {
if (workbook != null) {
// 根据Aspose.Cells文档,Workbook类实现了IDisposable接口,
// 在.NET中通常用using语句处理。在Java中,如果它有close()或dispose()方法,应在此调用。
// 查阅文档,Aspose.Cells for Java 通常不需要显式调用 workbook.dispose(),垃圾回收器会处理。
// 但如果遇到内存问题,可以检查是否有相关API。
}
}
}
javascript
async function downloadImage(imageId) {
// 构建下载文件的 URL
const downloadUrl = `${prefix}/downloadImage/${imageId}`;
let loadingIndex; // 用于存储 Layui 加载层的索引
try {
loadingIndex = layer.load(1);
// 使用 fetch API 发送 GET 请求
const response = await fetch(downloadUrl, {
method: 'GET', // 后端是 @GetMapping,所以前端也用 GET
cache: 'no-cache', // 根据你的后端设置,这里也禁用缓存
});
// 检查响应是否成功
if (!response.ok) {
// 如果服务器返回错误状态 (如 404, 500)
// 你可以根据 response.status 和 response.statusText 来处理不同类型的错误
const errorText = await response.text(); // 尝试获取错误信息文本
throw new Error(`服务器错误: ${response.status} ${response.statusText}. ${errorText}`);
}
// 从响应头中获取文件名
// 后端设置了 Content-Disposition: attachment; filename=...
const contentDisposition = response.headers.get('content-disposition');
let filename = `image_${imageId}.png`; // 默认文件名,如果无法从头部获取
if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename\*?=['"]?(?:UTF-\d['"]*)?([^;"\n]*)/i);
if (filenameMatch && filenameMatch[1]) {
filename = decodeURIComponent(filenameMatch[1]);
}
}
// 将响应体转换为 Blob 对象
const blob = await response.blob();
// 创建一个指向 Blob 的 URL
const objectUrl = window.URL.createObjectURL(blob);
// 创建一个临时的 <a> 标签用于触发下载
const link = document.createElement('a');
link.href = objectUrl;
link.setAttribute('download', filename); // 设置下载的文件名
document.body.appendChild(link);
// 触发点击
link.click();
// 清理:移除 <a> 标签并释放 Object URL
document.body.removeChild(link);
window.URL.revokeObjectURL(objectUrl);
// 如果你使用了 parent.layer.alert,可以在这里提示成功
if (parent && parent.layer) {
parent.layer.alert("图片下载成功!");
}
} catch (error) {
console.error('下载图片时发生错误:', error);
// 如果你使用了 parent.layer.alert,可以在这里提示错误
if (parent && parent.layer) {
parent.layer.alert(`连接或下载错误: ${error.message}`);
}
throw error; // 重新抛出错误,以便调用者可以进一步处理
}
finally {
layer.close(loadingIndex);
}
}
// 如果你仍然想在类似 $.ajax 的结构中使用,可以这样包装:
function triggerDownloadWithAjaxLikeStructure(imageId) {
// 这里不再需要 $('#signupForm').serialize(),因为 ID 是直接传递的
// 也不再需要 type: "POST",因为下载是 GET
// async: false 非常不推荐,fetch 默认就是异步的,应该使用 Promise 处理
downloadImage(imageId)
.then(() => {
// 成功回调,提示已经在 downloadImage 函数内部处理
// 你可以在这里添加额外的成功逻辑
console.log('图片下载流程成功完成。');
})
.catch(error => {
// 错误回调,提示已经在 downloadImage 函数内部处理
// 你可以在这里添加额外的错误处理逻辑
console.error('图片下载流程发生错误。', error);
});
}
javascript
triggerDownloadWithAjaxLikeStructure(id)