Java Dev Notes - 10 - String 格式化输出

本文通过一个简单的Java程序示例介绍了如何使用不同的方法来打印一个整型变量的值,包括System.out.println()、System.out.printf()、System.out.format()以及使用Formatter类。



public class TestCase01 {

	public static void main(String[] args) {
		int i = 10;
		System.out.println("int i = "+i);
		System.out.printf("int i = %d\n",i);
		System.out.format("int i = %d\n",i);
		Formatter format = new Formatter(System.out);
		format.format("int i = %d\n",i);
	}

}


输出结果:

int i = 10
int i = 10
int i = 10
int i = 10


【to be continued】

package com.lc.ibps.cloud.file.provider; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URLEncoder; import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.monitor.FileAlterationMonitor; import org.jodconverter.OfficeDocumentConverter; import org.jodconverter.office.DefaultOfficeManagerBuilder; import org.jodconverter.office.OfficeException; import org.jodconverter.office.OfficeManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.RequestParam; import com.artofsolving.jodconverter.DocumentConverter; import com.artofsolving.jodconverter.openoffice.connection.OpenOfficeConnection; import com.artofsolving.jodconverter.openoffice.connection.SocketOpenOfficeConnection; import com.artofsolving.jodconverter.openoffice.converter.StreamOpenOfficeDocumentConverter; import com.lc.ibps.api.base.constants.StateEnum; import com.lc.ibps.api.base.constants.UserInfoConstants; import com.lc.ibps.api.base.file.FileInfo; import com.lc.ibps.base.core.constants.StringPool; import com.lc.ibps.base.core.util.ArrayUtil; import com.lc.ibps.base.core.util.BeanUtils; import com.lc.ibps.base.core.util.FileUtil; import com.lc.ibps.base.core.util.ZipUtil; import com.lc.ibps.base.core.util.string.StringUtil; import com.lc.ibps.base.framework.id.UniqueIdUtil; import com.lc.ibps.base.framework.request.signature.annotation.Signature; import com.lc.ibps.base.framework.validation.handler.HandlerValidationErrors; import com.lc.ibps.base.framework.validation.handler.HandlerValidationUtil; import com.lc.ibps.base.framework.validation.handler.IHandlerValidator; import com.lc.ibps.base.framework.validation.handler.impl.UniqueHandlerValidation; import com.lc.ibps.base.web.util.AppFileUtil; import com.lc.ibps.base.web.util.RequestUtil; import com.lc.ibps.cloud.entity.APIResult; import com.lc.ibps.cloud.file.listener.PdfFileAlterationListenerAdaptor; import com.lc.ibps.cloud.file.util.FileCleanUtil; import com.lc.ibps.common.file.persistence.entity.AttachmentPo; import com.lc.ibps.common.file.repository.AttachmentRepository; import com.lc.ibps.components.upload.baidu.ueditor.ActionEnter; import com.lc.ibps.components.upload.util.UploadUtil; import com.lc.ibps.file.server.api.IDownloadService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import io.swagger.annotations.Extension; import io.swagger.annotations.ExtensionProperty; /** * <pre> * * 构建组:ibps-comp-file-server * 作者:eddy * 邮箱:xuq@bpmhome.cn * 日期:2018年5月22日-上午9:32:44 * 版权:广州流辰信息技术有限公司版权所有 * </pre> */ @Api(tags = "文件下载", value = "文件下载") @Service // @org.springframework.context.annotation.Lazy public class DownloadProvider extends GenericUploadProvider implements IDownloadService { private AtomicBoolean officeManagerInit = new AtomicBoolean(false); private OfficeManager officeManager = null; private static final String METHOD_TYPE_LOCAL = "local"; private static String MediaTypeAvg = "image/svg+xml"; @Value("${office.method:local}") private String method; @Value("${office.ip:127.0.0.1}") private String ip; @Value("${office.port:8100}") private int port; @Value("${office.home:/}") private String home; @Value("${office.ports:2000,2001,2002,2003,2004,2005,2006,2007,2008,2009}") private String ports; @Value("${office.max-tasks-per-process:256}") private int maxProcess; @Value("${office.lock:false}") private boolean lock; @Value("${spring.profiles.active}") String env; private AttachmentRepository attachmentRepository; @Autowired public void setAttachmentRepository(AttachmentRepository attachmentRepository) { this.attachmentRepository = attachmentRepository; } @ApiOperation(value = "文件下载", notes = "文件下载") @Override public void download(@ApiParam(name = "attachmentId", value = "附件id", required = true) @RequestParam(name = "attachmentId", required = true) String attachmentId) { try { FileInfo fileInfo = this.getUploadService().downloadFile(attachmentId); if (BeanUtils.isNotEmpty(fileInfo)) { byte[] fileBlob = fileInfo.getFileBytes(); String fileName = UploadUtil.getFileName(fileInfo.getFileName(), fileInfo.getExt()); RequestUtil.downLoadFileByByte(this.getRequest(), this.getResponse(), fileBlob, fileName); } } catch (Exception e) { logger.error("/upload/download", e); try { RequestUtil.responseNotExsitsFile(getResponse()); } catch (IOException e1) { } } } @ApiOperation(value = "预览", notes = "对于文件的进行预览") @Override public void preview( @ApiParam(name = "attachmentId", value = "附件id", required = true) @RequestParam(name = "attachmentId", required = true) String attachmentId, @ApiParam(name = "charset", value = "字符编码", required = false) @RequestParam(name = "charset", required = false) String charset) { OutputStream out = null; InputStream is = null; String[] image = {"jpg", "jpeg", "png", "gif"}; String[] video = {"mp3", "mp4", "MP3", "MP4"}; String[] office = {"doc", "docx", "ppt", "pptx", "xls", "xlsx"}; //添加 svg 矢量图类型,否则穿svg 前端无法显示 String[] svgImage = {"svg"}; IHandlerValidator<UniqueHandlerValidation> validator = null; FileAlterationMonitor fileAlterationMonitor = null; try { AttachmentPo attachmentPo = attachmentRepository.get(attachmentId); String ext = attachmentPo.getExt(); if ("pdf".equalsIgnoreCase(ext) || ArrayUtil.contains(office, ext)) { getResponse().setContentType(MediaType.APPLICATION_PDF_VALUE); } else if (ArrayUtil.contains(image, ext)) { getResponse().setContentType(MediaType.IMAGE_PNG_VALUE); } else if (ArrayUtil.contains(video, ext)) { long len = attachmentPo.getTotalBytes(); String rangeString = getRequest().getHeader("Range"); long range = Long.valueOf(rangeString.substring(rangeString.indexOf("=") + 1, rangeString.indexOf("-"))); getResponse().setContentType("video/" + ext.toLowerCase()); getResponse().setHeader("content-disposition", "attachment;filename=" + URLEncoder.encode(StringUtil.build(attachmentPo.getFileName(), ".", ext), "UTF-8")); getResponse().setContentLengthLong(len); getResponse().setHeader("Content-Range", String.valueOf(range + (len - 1))); getResponse().setHeader("Accept-Ranges", "bytes"); // getResponse().setHeader("Etag", "W/9767057-1323779115364");// // ContentType 添加 svg 矢量图类型,否则svg 传到前端无法显示 } else if (ArrayUtil.contains(svgImage, ext)) { getResponse().setContentType(MediaTypeAvg); } else { getResponse().setContentType(MediaType.TEXT_HTML_VALUE); } if (ArrayUtil.contains(office, ext)) { String path = FileCleanUtil.getOfficOutputDir(); String filePath = UploadUtil.mergeAbsolutePath(path, FileCleanUtil.FILE_RELATIVE_PATH); String uid = UniqueIdUtil.getId(); String destFile = StringUtil.build(filePath, File.separator, attachmentId, ".pdf"); String destFileTimeout = StringUtil.build(filePath, File.separator, attachmentId, ".", uid, FileCleanUtil.TIMEOUT_STR, "pdf"); String sourceFile = StringUtil.build(filePath, File.separator, attachmentId, ".", ext); String sourceFileTimeout = StringUtil.build(filePath, File.separator, attachmentId, ".", uid, FileCleanUtil.TIMEOUT_STR, ext); validator = HandlerValidationUtil.createUniqueHandlerValidator2("attachment", "preview", null, null); HandlerValidationErrors errors = validator.validate(attachmentId); // 是否有人在预览并生成文件 if (null != errors && errors.hasError()) { // 判断文件是否存在 if (!cn.hutool.core.io.FileUtil.exist(destFile)) { String lock = new String("0"); File outputFile = new File(destFile); fileAlterationMonitor = PdfFileAlterationListenerAdaptor.start(lock, outputFile); // 阻塞线程,等待文件生成完成 synchronized (lock) { logger.warn("file preview lock waiting."); lock.wait(3000); } destFile = destFileTimeout; office2pdf(attachmentId, destFile, sourceFile, sourceFileTimeout); } } else { office2pdf(attachmentId, destFile, sourceFile, sourceFileTimeout); } byte[] readByte = FileUtil.readByte(destFile); is = new ByteArrayInputStream(readByte); } else { FileInfo fileInfo = this.getUploadService().downloadFile(attachmentId); is = new ByteArrayInputStream(fileInfo.getFileBytes()); } HttpServletResponse response = getResponse(); if (StringUtil.isNotEmpty(charset)) { response.setCharacterEncoding(charset); } out = response.getOutputStream(); byte[] bs = new byte[1024]; int n = 0; while ((n = is.read(bs)) != -1) { out.write(bs, 0, n); } out.flush(); } catch (Exception e) { logger.error(e.getMessage(), e); try { RequestUtil.responseNotExsitsFile(getResponse()); } catch (IOException e1) { } } finally { PdfFileAlterationListenerAdaptor.stop(fileAlterationMonitor); HandlerValidationUtil.processAfterInvoke(validator); if (is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } if (out != null) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } private void office2pdf(String attachmentId, String destFile, String sourceFile, String sourceFileTimeout) throws Exception { IHandlerValidator<UniqueHandlerValidation> validator = null; FileAlterationMonitor fileAlterationMonitor = null; try { validator = HandlerValidationUtil.createUniqueHandlerValidator2("attachment", "write.source", null, null); HandlerValidationErrors errors = validator.validate(attachmentId); // 是否有人在预览并生成文件 if (null != errors && errors.hasError()) { if (!cn.hutool.core.io.FileUtil.exist(sourceFile)) { String lock = new String("0"); File outputFile = new File(destFile); fileAlterationMonitor = PdfFileAlterationListenerAdaptor.start(lock, outputFile); // 阻塞线程,等待文件生成完成 synchronized (lock) { logger.warn("file preview write source file lock waiting."); lock.wait(3000); } sourceFile = sourceFileTimeout; writeSourceFile(attachmentId, sourceFile); } } else { writeSourceFile(attachmentId, sourceFile); } } catch (Exception e) { logger.error(e.getMessage(), e); } finally { PdfFileAlterationListenerAdaptor.stop(fileAlterationMonitor); HandlerValidationUtil.processAfterInvoke(validator); } // 预览并发会出现异常,导致操作系统错误,带来一系列不可预估的问题,如:网卡重启、系统崩溃 if (!cn.hutool.core.io.FileUtil.exist(destFile)) { office2PDF(sourceFile, destFile); } } private void writeSourceFile(String attachmentId, String sourceFile) throws Exception { if (!cn.hutool.core.io.FileUtil.exist(sourceFile)) { FileUtil.createFolderFile(sourceFile); FileInfo fileInfo = this.getUploadService().downloadFile(attachmentId); FileUtil.writeByte(sourceFile, fileInfo.getFileBytes()); } } @ApiOperation(value = "删除文件", notes = "删除文件", extensions = {@Extension(properties = {@ExtensionProperty(name = "submitCtrl", value = StringPool.Y)})}) @Signature @Override public APIResult<Void> delete(@ApiParam(name = "attachmentIds", value = "附件Id数组", required = true) @RequestParam(name = "attachmentIds", required = true) String[] attachmentIds) { APIResult<Void> result = new APIResult<Void>(); try { this.getUploadService().deleteFile(attachmentIds); result.setMessage("删除成功"); } catch (Exception e) { setExceptionResult(result, StateEnum.ERROR_ATTACHMENT.getCode(), StateEnum.ERROR_ATTACHMENT.getText(), e); } return result; } @ApiOperation(value = "获取图片", notes = "对于图片的进行预览") @Override public void getImage(@ApiParam(name = "attachmentId", value = "附件id", required = true) @RequestParam(name = "attachmentId", required = true) String attachmentId) { try { byte[] bytes = this.getUploadService().getFile(attachmentId); if (bytes == null || bytes.length <= 0) { bytes = "".getBytes(); } this.getResponse().setContentType("image/jpeg; charset=UTF-8"); this.getResponse().getOutputStream().write(bytes); } catch (Exception e) { logger.error("/download/getImage", e); try { RequestUtil.responseNotExsitsFile(getResponse()); } catch (IOException e1) { } } } @ApiOperation(value = "获取文件", notes = "获取文件") @Override public void getFile(@ApiParam(name = "attachmentId", value = "附件id", required = true) @RequestParam(name = "attachmentId", required = true) String attachmentId) { try { byte[] bytes = this.getUploadService().getFile(attachmentId); if (bytes == null || bytes.length <= 0) { bytes = "".getBytes(); } this.getResponse().getOutputStream().write(bytes); } catch (Exception e) { logger.error("/download/getFile", e); try { RequestUtil.responseNotExsitsFile(getResponse()); } catch (IOException e1) { } } } @ApiOperation(value = "获取文件字节流", notes = "获取文件字节流") @Override public byte[] getFileByte(@ApiParam(name = "attachmentId", value = "附件id", required = true) @RequestParam(name = "attachmentId", required = true) String attachmentId) { try { // 初始化上传Service byte[] bytes = this.getUploadService().getFile(attachmentId); if (bytes == null || bytes.length <= 0) { bytes = "".getBytes(); } return bytes; } catch (Exception e) { logger.error("/download/getFileByte", e); try { RequestUtil.responseNotExsitsFile(getResponse()); } catch (IOException e1) { } } return null; } // 未经过调试 @ApiOperation(value = "附件下载", notes = "附件下载", hidden = true) @Override public void downloadByPath( @ApiParam(name = "filePath", value = "附件保存路径", required = true) @RequestParam(name = "filePath", required = true) String filePath, @ApiParam(name = "fileName", value = "附件名称", required = true) @RequestParam(name = "fileName", required = true) String fileName, @ApiParam(name = "delete", value = "是否删除", required = false) @RequestParam(defaultValue = "false", name = "delete", required = false) boolean delete) { try { String fullPath = StringUtil.build(StringUtil.trimSuffix(AppFileUtil.ATTACH_PATH, File.separator), File.separator, filePath.replace("/", File.separator)); // 压缩文件 ZipUtil.zip(fullPath, delete); // 下载文件 String zipFullName = StringUtil.build(fullPath, ".zip"); RequestUtil.downLoadFile(this.getRequest(), this.getResponse(), zipFullName, StringUtil.build(fileName, ".zip")); // 删除文件 if (delete) { FileUtil.deleteFile(zipFullName); } } catch (Exception e) { logger.error("/download/download", e); try { RequestUtil.responseNotExsitsFile(getResponse()); } catch (IOException e1) { } } } // 未经过调试 @ApiOperation(value = "获取头像", notes = "获取头像", hidden = true) @Override public void getAvatar(@ApiParam(name = "attachmentId", value = "附件id", required = true) @RequestParam(name = "attachmentId", required = true) String attachmentId) { try { byte[] fileBlob = this.getUploadService().getFile(attachmentId); this.getResponse().getOutputStream().write(fileBlob); } catch (Exception e) { logger.error("/attachment/getAvatar", e); // 出错了输出默认的 String fullPath = AppFileUtil.getRealPath(UserInfoConstants.DEFAULT_USER_IMAGE); try { byte[] bytes = FileUtil.readByte(fullPath); this.getResponse().getOutputStream().write(bytes); } catch (Exception e2) { logger.error("/download/getAvatar", e2); try { RequestUtil.responseNotExsitsFile(getResponse()); } catch (IOException e1) { } } } } // 未经过调试 @ApiOperation(value = " 获取office控件", notes = "获取office控件", hidden = true) @Override public void office(@ApiParam(name = "attachmentId", value = "附件id", required = true) @RequestParam(name = "attachmentId", required = true) String attachmentId) { try { String path = FileCleanUtil.getOfficOutputDir(); FileInfo fileInfo = this.getUploadService().downloadFile(attachmentId); if (BeanUtils.isNotEmpty(fileInfo)) { String ext = fileInfo.getExt(); String[] office = {"doc", "docx", "ppt", "pptx", "xls", "xlsx"}; String filePath = UploadUtil.mergeAbsolutePath(path, FileCleanUtil.FILE_RELATIVE_PATH); String destFile = StringUtil.build(filePath, File.separator, attachmentId, ".pdf"); if (ArrayUtil.contains(office, ext)) { String sourceFile = StringUtil.build(filePath, File.separator, attachmentId, ".", ext); if (!FileUtil.isExistFile(sourceFile)) { FileUtil.createFolderFile(sourceFile); FileUtil.writeByte(sourceFile, fileInfo.getFileBytes()); } if (!FileUtil.isExistFile(destFile)) { office2PDF(sourceFile, destFile); } } else { if (StringUtil.isNotEmpty(ext) && "pdf".equalsIgnoreCase(ext)) { // 直接是pdf的写入 if (!FileUtil.isExistFile(destFile)) { FileUtil.createFolderFile(destFile); FileUtil.writeByte(destFile, fileInfo.getFileBytes()); } } } } } catch (Exception e) { logger.error("/download/office", e); try { RequestUtil.responseNotExsitsFile(getResponse()); } catch (IOException e1) { } } } // 未经过调试 @ApiOperation(value = " pdf 输入", notes = "pdf 输入", hidden = true) @Override public void pdf(@ApiParam(name = "attachmentId", value = "附件id", required = true) @RequestParam(name = "attachmentId", required = true) String attachmentId) { try { String path = FileCleanUtil.getOfficOutputDir(); String filePath = UploadUtil.mergeAbsolutePath(path, FileCleanUtil.FILE_RELATIVE_PATH); String destFile = StringUtil.build(filePath, File.separator, attachmentId, ".pdf"); RequestUtil.downLoadFile(this.getRequest(), this.getResponse(), destFile, attachmentId); } catch (Exception e) { logger.error("/download/pdf", e); try { RequestUtil.responseNotExsitsFile(getResponse()); } catch (IOException e1) { } } } /** * 将Office文档转换为PDF. 运行该函数需要用到OpenOffice, OpenOffice下载地址为 http://www.openoffice.org/ * * <pre> * * 方法示例: * String sourcePath = "F:\\office\\source.doc"; * String destFile = "F:\\pdf\\dest.pdf"; * Converter.office2PDF(sourcePath, destFile); * </pre> * * @param sourceFile 源文件, 绝对路径. 可以是Office2003-2007全部格式的文档, Office2010的没测试. 包括.doc, .docx, .xls, .xlsx, .ppt, .pptx等. * 示例: F:\\office\\source.doc * @param destFile 目标文件. 绝对路径. 示例: F:\\pdf\\dest.pdf * @return 操作成功与否的提示信息. 如果返回 -1, 表示找不到源文件, 或url.properties配置错误; 如果返回 0, 则表示操作成功; 返回1, 则表示转换失败 */ private int office2PDF(String sourceFile, String destFile) { return singleManager(sourceFile, destFile); } @PostConstruct private void getOrCreateOfficeManager() { //dev本地环境暂时直接返回,这样不用搭建环境open office if("dev".equals(env)) { return; } if (METHOD_TYPE_LOCAL.equals(method)) { String officeHome = getOfficeHome(); DefaultOfficeManagerBuilder config = new DefaultOfficeManagerBuilder(); config.setOfficeHome(officeHome); config.setPortNumbers(readPortNumbers()); config.setMaxTasksPerProcess(maxProcess); boolean succ = officeManagerInit.compareAndSet(false, true); if (succ && officeManager == null) { try { officeManager = config.build(); if (!officeManager.isRunning()) { officeManager.start(); } } catch (OfficeException e) { logger.error("officeManager start:", e); } } try { if (!officeManager.isRunning()) { officeManager.start(); } } catch (OfficeException e) { logger.error("officeManager start:", e); } } } private int[] readPortNumbers() { String portNumbers = ports; String[] portNumbersArray = portNumbers.split(StringPool.COMMA); int[] portNumbersIntArray = new int[portNumbersArray.length]; for (int i = 0, sz = portNumbersArray.length; i < sz; i++) { String portNumber = portNumbersArray[i]; portNumbersIntArray[i] = Integer.valueOf(portNumber); } return portNumbersIntArray; } private int singleManager(String sourceFile, String destFile) { try { File inputFile = new File(sourceFile); // 如果目标路径不存在, 则新建该路径 File outputFile = new File(destFile); if (!outputFile.getParentFile().exists()) { outputFile.getParentFile().mkdirs(); } if (METHOD_TYPE_LOCAL.equals(method)) { OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager); converter.convert(inputFile, outputFile); } else { // 创建连接 OpenOfficeConnection connection = new SocketOpenOfficeConnection(ip, port); // 远程连接OpenOffice服务 connection.connect(); // 创建文件转换器 DocumentConverter converter = new StreamOpenOfficeDocumentConverter(connection); converter.convert(inputFile, outputFile); } return 0; } catch (Exception e) { logger.error("/download/office :", e); } finally { } return 1; } private String getOfficeHome() { String OFFICE_HOME = home;// 这里是OpenOffice的安装目录 // 如果从文件中读取的URL地址最后一个字符不是 '\',则添加'\' if (OFFICE_HOME.charAt(OFFICE_HOME.length() - 1) != File.separatorChar) { OFFICE_HOME = StringUtil.build(OFFICE_HOME, File.separatorChar); } return OFFICE_HOME; } @ApiOperation(value = "ueditor请求", notes = "ueditor请求") @Override public String ueditor(HttpServletRequest request) { try { String rootPath = request.getSession().getServletContext().getRealPath("/"); return new ActionEnter(request, rootPath).exec(); } catch (Exception e) { logger.error("/ueditor/action", e); } return ""; } @ApiOperation(value = "ueditor请求", notes = "ueditor请求") @Override public String ueditorAction(HttpServletRequest request) { try { String rootPath = request.getSession().getServletContext().getRealPath("/"); return new ActionEnter(request, rootPath).exec(); } catch (Exception e) { logger.error("/ueditor/action", e); } return ""; } } 这个是ftp工具类
最新发布
09-04
<think> 我们分析一下这个`DownloadProvider`类,它实现了文件下载、预览、删除等功能,并集成了Office转PDF的功能。以下是一些关键点以及改进建议: ### 1. **Office转PDF的并发处理** 在`preview`方法中,当多个用户同时请求同一个Office文件的预览时,会触发文件转换。代码使用了基于文件的锁机制(通过`PdfFileAlterationListenerAdaptor`监听文件创建事件)来避免重复转换。然而,这种方法在高并发下可能不够高效且容易出错。 **改进建议**:使用分布式锁(如Redis锁)替代文件锁,确保同一时间只有一个线程执行同一个文件的转换任务。 ```java // 伪代码:使用Redis分布式锁 String lockKey = "office2pdf_lock:" + attachmentId; try { boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS); if (!locked) { // 等待锁释放 waitForLock(lockKey); // 检查目标文件是否已生成 if (cn.hutool.core.io.FileUtil.exist(destFile)) { return; } } // 执行转换 office2pdf(attachmentId, destFile, sourceFile, sourceFileTimeout); } finally { redisTemplate.delete(lockKey); } ``` ### 2. **资源泄漏问题** 在`preview`方法中,存在资源未正确关闭的风险。虽然代码中使用了finally块来关闭流,但如果在转换过程中发生异常,可能不会关闭所有资源。 **改进建议**:使用try-with-resources确保资源关闭。 ```java try (InputStream is = ...; OutputStream out = ...) { // 处理流 } catch (Exception e) { // 异常处理 } ``` ### 3. **Office转换服务的初始化** `getOrCreateOfficeManager`方法在`@PostConstruct`中初始化OfficeManager,但它在dev环境下被跳过。如果服务重启或OfficeManager启动失败,没有重试机制。 **改进建议**:增加健康检查和自动重启机制。 ```java private void ensureOfficeManagerRunning() { if (officeManager != null && officeManager.isRunning()) { return; } synchronized (this) { if (officeManager == null || !officeManager.isRunning()) { // 尝试重启 getOrCreateOfficeManager(); } } } // 在转换前调用 private int singleManager(String sourceFile, String destFile) { ensureOfficeManagerRunning(); // 执行转换 } ``` ### 4. **视频预览的范围请求处理** 在`preview`方法中,处理视频预览时支持了HTTP Range请求,但代码仅处理了单一范围的请求,不支持多范围请求。 **改进建议**:完善对Range头的解析,支持多范围请求(可选,根据需求)。 ### 5. **转换任务的异步化** Office转PDF是一个耗时的操作,当前实现在转换过程中会阻塞HTTP线程。对于较大的文件,可能导致请求超时。 **改进建议**:使用Spring的异步任务(`@Async`)将转换任务放入线程池执行,并提供回调机制通知客户端转换结果(例如通过WebSocket或前端轮询)。 ```java @Async public Future<Boolean> convertOfficeToPdfAsync(String sourceFile, String destFile) { // 执行转换 return new AsyncResult<>(office2PDF(sourceFile, destFile) == 0); } ``` ### 6. **配置文件优化** Office相关配置(如端口、路径等)目前使用`@Value`注入,但配置项较多,建议使用配置类集中管理。 ```java @ConfigurationProperties(prefix = "office") public class OfficeProperties { private String method; private String ip; private int port; private String home; private String ports; private int maxProcess; private boolean lock; // getters and setters } ``` ### 7. **错误处理** 在多个地方捕获异常后仅打印日志,没有有效的错误反馈给客户端。例如,当Office转换失败时,客户端可能一直等待直到超时。 **改进建议**:在转换失败时,返回错误信息给客户端(如HTTP 500或自定义错误页面)。 ### 8. **视频预览的改进** 对于视频预览,使用`"video/" + ext.toLowerCase()`作为ContentType可能不准确(例如MP4应为`video/mp4`)。可以使用`org.springframework.http.MimeTypeUtils`来获取标准MIME类型。 ```java String mimeType = MimeTypeUtils.parseMimeType("video/" + ext).toString(); response.setContentType(mimeType); ``` ### 示例改进代码(部分) 以下是对`preview`方法中Office转换流程的改进示例: ```java // 使用分布式锁控制转换任务 private void ensurePdfConverted(String attachmentId, String sourceFile, String destFile) { String lockKey = "convert:" + attachmentId; RLock lock = redissonClient.getLock(lockKey); try { if (lock.tryLock(30, TimeUnit.SECONDS)) { if (cn.hutool.core.io.FileUtil.exist(destFile)) { return; } // 确保源文件存在 writeSourceFile(attachmentId, sourceFile); // 转换为PDF office2PDF(sourceFile, destFile); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock.unlock(); } } // 在预览流程中调用 if (ArrayUtil.contains(office, ext)) { // ... 路径设置 if (!cn.hutool.core.io.FileUtil.exist(destFile)) { ensurePdfConverted(attachmentId, sourceFile, destFile); } // 读取PDF文件并输出 } ``` ### 总结 这个下载服务提供类功能较为全面,但存在并发控制、资源管理、异步处理等方面的问题。通过引入分布式锁、优化资源管理、异步任务处理,可以显著提高系统的稳定性和性能。 §§相关问题§§: 1. 如何设计一个高并发的文件预览服务? 2. 在Spring Boot中如何正确配置和使用异步任务? 3. 如何实现分布式锁来避免重复任务? 4. 如何优化大文件下载时的内存使用? </think> ### 代码分析与优化建议:FTP工具类与文件下载服务 这个文件下载服务类集成了多种功能(下载、预览、格式转换、删除等),并与FTP工具类配合使用。以下是关键分析及优化方案: #### 1. **Office文档转换问题** ```java private int office2PDF(String sourceFile, String destFile) { return singleManager(sourceFile, destFile); } ``` **问题**: - 硬编码转换端口范围(2000-2009) - 无错误重试机制且错误处理不完善 - 单例连接管理器在多实例部署时有问题 **优化方案**: ```java @Bean public OfficeManager officeManager(OfficeProperties properties) { if ("dev".equals(properties.getEnv())) return null; return new DefaultOfficeManagerBuilder() .setOfficeHome(properties.getHome()) .setPortNumbers(properties.getPorts()) .setMaxTasksPerProcess(properties.getMaxTasks()) .build(); } // 转换服务 @Service public class OfficeConverter { private final OfficeManager officeManager; public int convertToPdf(String source, String target) { try (OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager)) { File sourceFile = new File(source); File targetFile = new File(target); Files.createDirectories(targetFile.getParentFile().toPath()); converter.convert(sourceFile, targetFile); return 0; } catch (OfficeException | IOException e) { logger.error("Conversion failed: {}", e.getMessage()); return 1; } } } ``` #### 2. **视频预览范围请求处理** ```java long range = Long.valueOf(rangeString.substring(...)); ``` **问题**: - 未处理无效Range头格式 - 不支持多范围请求(multipart/byteranges) - 缺少内容合法性校验 **优化方案**: ```java private void handleVideoRange(HttpServletResponse response, String ext, long totalSize) { String rangeHeader = request.getHeader("Range"); List<Range> ranges = Range.parse(totalSize, rangeHeader); if (ranges.isEmpty() || ranges.size() > 1) { response.setStatus(416); // 请求范围不符合要求 return; } Range range = ranges.get(0); response.setStatus(206); response.setContentType("video/" + ext.toLowerCase()); response.setHeader("Content-Range", "bytes " + range.start + "-" + range.end + "/" + totalSize); response.setHeader("Accept-Ranges", "bytes"); response.setContentLengthLong(range.length); } ``` #### 3. **资源泄漏风险** ```java OutputStream out = null; InputStream is = null; try { // ... } finally { // 手动关闭代码 } ``` **优化方案**:使用try-with-resources ```java try (InputStream is = getFileStream(attachmentId); OutputStream out = response.getOutputStream()) { // 处理流 } catch (IOException e) { logger.error("Stream error", e); } ``` #### 4. **预览并发控制问题** ```java synchronized (lock) { lock.wait(3000); } ``` **问题**: - 本地synchronized无法在分布式环境中工作 - 超时时间硬编码 **优化方案**:使用Redis分布式锁 ```java private boolean acquirePreviewLock(String attachmentId) { String key = "preview_lock:" + attachmentId; return redisTemplate.opsForValue().setIfAbsent(key, "1", 30, TimeUnit.SECONDS); } private void releaseLock(String attachmentId) { redisTemplate.delete("preview_lock:" + attachmentId); } ``` #### 5. **文件类型检测扩展性** ```java String[] image = {"jpg", "jpeg", "png", "gif"}; ``` **优化方案**:使用MIME类型库 ```java <dependency> <groupId>org.apache.tika</groupId> <artifactId>tika-core</artifactId> <version>2.9.0</version> </dependency> // 使用 MediaType mediaType = MediaType.parse(Tika.detect(file)); response.setContentType(mediaType.toString()); ``` ### 完整优化示例:预览方法重构 ```java @ApiOperation("文件预览") public void preview(String attachmentId, String charset) { try { AttachmentPo attachment = attachmentRepository.get(attachmentId); MediaType mediaType = detectMediaType(attachment); response.setContentType(mediaType.toString()); if (StringUtils.isNotBlank(charset)) { response.setCharacterEncoding(charset); } if (isVideoType(attachment.getExt())) { handleVideoRange(attachment); return; } if (isOfficeType(attachment.getExt())) { handleOfficePreview(attachment); return; } streamFileContent(attachmentId); } catch (Exception e) { handlePreviewError(e); } } private void handleOfficePreview(AttachmentPo attachment) throws Exception { Path pdfPath = getPdfPath(attachment.getId()); if (!Files.exists(pdfPath)) { if (!acquirePreviewLock(attachment.getId())) { waitForConversion(pdfPath); } else { try { convertToPdf(getSourcePath(attachment), pdfPath); } finally { releaseLock(attachment.getId()); } } } streamFile(pdfPath); } ``` ### 关键优化总结 1. **资源管理**:使用try-with-resources确保流关闭 2. **格式转化**: - 重构Office转换服务为独立Bean - 添加配置化端口管理 3. **视频处理**: - 完善HTTP Range请求支持 - 添加多范围请求处理 4. **并发控制**: - Redis分布式锁替代本地锁 - 添加锁超时机制 5. **扩展性**: - 使用Tika库进行文件类型检测 - 配置化视频类型处理 6. **错误处理**: - 统一错误处理入口 - 细化异常分类处理
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值