package org.sxkj.resource.controller; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springblade.core.boot.ctrl.BladeController; import org.springblade.core.mp.support.Condition; import org.springblade.core.mp.support.Query; import org.springblade.core.secure.BladeUser; import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.tenant.annotation.NonDS; import org.springblade.core.tool.api.R; import org.springblade.core.tool.utils.BeanUtil; import org.springblade.core.tool.utils.DateTimeUtil; import org.springblade.core.tool.utils.Func; import org.springblade.core.tool.utils.StringUtil; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import org.sxkj.common.model.ResponseResult; import org.sxkj.resource.dto.AttachDto; import org.sxkj.resource.entity.Attach; import org.sxkj.resource.param.AttachPageParam; import org.sxkj.resource.param.AttachParam; import org.sxkj.resource.service.IAttachService; import org.sxkj.resource.util.OnlyofficeJwt; import org.sxkj.resource.vo.AttachVO; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; /** * 附件表 控制器 * * @author Chill */ @Slf4j @NonDS @RestController @RequiredArgsConstructor @RequestMapping("/attach") @Api(value = "附件表接口", tags = "附件") public class AttachController extends BladeController { private final IAttachService attachService; private final OnlyofficeJwt onlyofficeJwt; /** OnlyOffice应用服务基础URL,供Document Server访问代理下载接口 */ @Value("${onlyoffice.base-url:}") private String onlyofficeBaseUrl; /** OnlyOffice回调地址 */ @Value("${onlyoffice.callback-url:}") private String onlyofficeCallbackUrl; /** 远程文件URL缓存,key为attachId,value为远程文件URL */ private static final ConcurrentHashMap REMOTE_URL_CACHE = new ConcurrentHashMap<>(); /** 代理下载连接超时时间(毫秒) */ private static final int CONNECT_TIMEOUT = 10000; /** 代理下载读取超时时间(毫秒) */ private static final int READ_TIMEOUT = 30000; /** * 详情 */ @GetMapping("/detail") @ApiOperationSupport(order = 1) @ApiOperation(value = "附件详情", notes = "传入attach") public R detail(AttachParam attach) { Attach detail = attachService.getOne(Wrappers.lambdaQuery(Attach.class).eq(Attach::getId, attach.getId())); return R.data(detail); } /** * 自定义分页 附件表 */ @GetMapping("/page") @ApiOperationSupport(order = 3) @ApiOperation(value = "附件分页", notes = "传入attach") public R> page(AttachPageParam attach, Query query) { BladeUser user = AuthUtil.getUser(); IPage pages = attachService.selectAttachPage(Condition.getPage(query), attach); return R.data(pages); } /** * 新增或修改 附件表 */ @PostMapping("/submit") @ApiOperationSupport(order = 6) @ApiOperation(value = "新增或修改", notes = "传入attach") public R submit(@Valid @RequestBody AttachDto attachDto) { Attach attach = Objects.requireNonNull(BeanUtil.copy(attachDto, Attach.class)); return R.status(attachService.saveOrUpdate(attach)); } /** * 删除 附件表 */ @PostMapping("/remove") @ApiOperationSupport(order = 7) @ApiOperation(value = "附件逻辑删除", notes = "传入ids") public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) { return R.status(attachService.update(Wrappers.lambdaUpdate(Attach.class) .set(Attach::getIsDeleted, true) .in(Attach::getId, Func.toLongList(ids)))); } // @GetMapping("/test") // public R test(@RequestParam(required = true) Date startTime, @RequestParam(required = true) Date endTime) { // CacheUtil.clear(RESOURCE_CACHE); // attachClient.processingYesterdayVideo(startTime, endTime); // return R.success("成功"); // } // @GetMapping("/getlotInfoList") // public ResponseResult getlotInfoList(@ApiParam Long lotInfoId) { // return ResponseResult.success(attachService.getlotInfoList(lotInfoId)); // } /** * 分页 附件表 */ @GetMapping("/list") @ApiOperationSupport(order = 2) @ApiOperation(value = "附件分页", notes = "传入attach") public R> list(Attach attach, Query query) { String patrolTaskId = attach.getPatrolTaskId(); QueryWrapper queryWrapper1 = Condition.getQueryWrapper(attach); QueryWrapper queryWrapper = queryWrapper1.eq(!StringUtils.isEmpty(patrolTaskId), "patrol_task_id", patrolTaskId); queryWrapper.eq("is_deleted", 0); IPage pages = attachService.page(Condition.getPage(query), queryWrapper); return R.data(pages); } /** * 自定义分页 附件表 */ // @PostMapping("/aiImagesPage") // @ApiOperationSupport(order = 3) // @ApiOperation(value = "自定义分页附件表分页", notes = "ai分页数据") // public R> aiPage(@RequestBody AttachVO attach, Query query) { // String areaCode = HeaderUtils.getAreaCode(); // attach.setAreaCode(areaCode); // List resultTypes = attach.getResultTypes(); // String wordOrderType = attach.getWordOrderType(); // settingResultType(attach, wordOrderType, resultTypes); // if (Objects.nonNull(attach.getAiStatus()) && Objects.nonNull(attach.getResultTypes()) // && attach.getResultTypes().contains(Attach.RESULT_TYPE_AI)) { // attach.setResultTypes(Arrays.asList(Attach.RESULT_TYPE_AI)); // } // List aiEventMd5s = attachService.findAiEventMd5s(attach.getEventRecordIds()); // attach.setMd5s(aiEventMd5s); // if (!CollectionUtils.isEmpty(attach.getEventRecordIds())) { // attach.setResultTypes(Arrays.asList(Attach.RESULT_TYPE_AI, Attach.RESULT_TYPE_IMG)); // } // IPage page = Condition.getPage(query); // List aiAttachImages = attachService.findAiAttachImages(page, attach); // AttachVO.settingNickName(aiAttachImages); // page.setRecords(aiAttachImages); // return R.data(page); // } /** * 后台数据中心列表接口 */ // @PostMapping("/attachmentsPage") // @ApiOperationSupport(order = 4) // @ApiOperation(value = "后台数据中心列表接口") // public R> attachmentsPage(@RequestBody AttachVO attach, Query query) { // IPage page = Condition.getPage(query); // IPage attachPage = attachService.findAttachImages(page, attach); // return R.data(attachPage); // } /** * 新增 附件表 */ @PostMapping("/save") @ApiOperationSupport(order = 4) @ApiOperation(value = "新增", notes = "传入attach") public R save(@Valid @RequestBody Attach attach) { return R.status(attachService.save(attach)); } /** * 修改 附件表 */ @PostMapping("/update") @ApiOperationSupport(order = 5) @ApiOperation(value = "修改", notes = "传入attach") public R update(@Valid @RequestBody Attach attach) { return R.status(attachService.updateById(attach)); } /** * 流式附件下载接口,支持下载zip压缩包,不使用本地存储 * @param response * @throws IOException */ @ApiOperation(value = "流式附件下载接口", notes = "使用流方式返回数据,不使用本地存储") @GetMapping("/downloadByByte") public void downloadByByte( @ApiParam(value = "附件下载参数") @RequestParam("attachIds") String attachIds, HttpServletResponse response) throws IOException { // 设置文件名 String timestamp = DateTimeUtil.format(LocalDateTime.now(), "yyyyMMdd_HHmmss"); String fileName = "attachments_" + timestamp + ".zip"; // 设置响应头 response.setContentType("application/zip"); response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name())); // 获取响应输出流 ServletOutputStream outputStream = null; try { outputStream = response.getOutputStream(); Boolean result = attachService.downloadByByte(attachIds, outputStream); if (!result) { return; } } catch (Exception e) { if (!response.isCommitted()) { response.reset(); response.setContentType("application/json;charset=UTF-8"); ResponseResult errorResult = ResponseResult.error("下载文件失败: " + e.getMessage()); // 使用输出流写入错误信息,避免getWriter()冲突 try { byte[] errorBytes = JSON.toJSONString(errorResult).getBytes(StandardCharsets.UTF_8); outputStream.write(errorBytes); } catch (IOException ioException) { log.error("Error writing error response: {}", ioException.getMessage()); } } else { log.error("Cannot send error response: response already committed. Exception: {}", e.getMessage()); } } } /** * 删除 附件表及对应的附件信息 * * @param ids * @return */ // @PostMapping("/removeAttachAndData") // @ApiOperationSupport(order = 9) // @ApiOperation(value = "删除", notes = "传入ids") // @PreAuth(RoleConstant.HAS_ROLE_ADMINISTRATOR) // public R removeAttachAndData(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) { // return R.status(attachService.removeBatchAndDataByIds(Func.toLongList(ids))); // } // @GetMapping("/deptSize") // @ApiOperationSupport(order = 8) // @ApiOperation(value = "获取部门文件大小") // public R> getDeptSize(@RequestParam String startTime, @RequestParam String endTime) { // return R.data(attachService.getDeptAllSize(startTime, endTime)); // } /** * 历史正射图片处理 * * @return */ // @GetMapping("/hisOrtPicHandler") // @ApiOperationSupport(order = 9) // @ApiOperation(value = "历史正射图片处理") // public R hisOrtPicHandler() { // return R.data(attachService.hisOrtPicHandler()); // } /** * 自动更新附件表md5值 * * @return */ // @GetMapping("saveMd5") // public R saveMd5() { // attachService.saveMd5(); // return R.success(""); // } // @ApiOperation(value = "地图位置上图片数据", notes = "") // @PostMapping("/mapAttachs") // public ResponseResult mapEvents(@RequestBody AttachQueryParam dto) { // if (Objects.nonNull(dto.getDateEnum())) { // TimeRange timeRange = TimeRangeUtils.getTimeRange(dto.getDateEnum()); // dto.setStartDate(timeRange.getStartTime()); // dto.setEndDate(timeRange.getEndTime()); // } // if (Objects.nonNull(dto.getStartDate())) { // dto.setStartTime(TimeRangeUtils.getEpochMilli(dto.getStartDate())); // } // if (Objects.nonNull(dto.getEndDate())) { // dto.setEndTime(TimeRangeUtils.getEpochMilli(dto.getEndDate())); // } // String areaCode = HeaderUtils.getHeader(CommonConstant.AREA_CODE).orElse(null); // dto.setAreaCode(!StringUtils.isEmpty(dto.getAreaCode()) ? dto.getAreaCode() : areaCode); // TreeVo treeVo = attachService.mapAttachEvents(dto); // return ResponseResult.success(treeVo); // } /** * 图片分析对比 前后图片对比 * * @param id 图片id * @return */ // @GetMapping("contrastiveAnalysis") // public ResponseResult contrastiveAnalysis(@RequestParam() Long id) { // List list = attachService.contrastiveAnalysis(id); // return ResponseResult.success(list); // } // @ApiOperation(value = "附件详情", notes = "") // @GetMapping("/getAttachInfo") // public ResponseResult getAttachInfo(@ApiParam(value = "附件iD") @RequestParam("id") Long id) throws IOException { // return ResponseResult.success(attachService.getAttachInfo(id)); // } // @ApiOperation(value = "通过任务id查询附件详情集合", notes = "") // @GetMapping("/getAttachInfoByJobId") // public ResponseResult> getAttachInfoByJobId(@ApiParam(value = "任务id") @RequestParam("jobId") String jobId) throws IOException { // return ResponseResult.success(attachService.getAttachInfoByJobId(jobId)); // } // @ApiLog("修改文件名称") // @PostMapping("/updateFileName") // public ResponseResult updateFileName(@RequestParam Long id, @RequestParam String nickName) { // if (attachService.updateFileName(id, nickName) > 0) { // return ResponseResult.success(); // } // return ResponseResult.error("修改失败"); // } /** * 删除无用的404状态的链接地址 * * @return */ // @GetMapping("deletedNotExistsUrl") // public ResponseResult deletedNotExistsUrl() { // attachService.deletedNotExistsUrl(); // return ResponseResult.success(); // } // @ApiLog("删除图片视频") // @DeleteMapping("/deleteMediaFile") // public ResponseResult deleteMediaFile(@RequestBody List ids) { // attachService.deleteMediaFile(ids); // return ResponseResult.success(); // } /** * 附件统计 */ // @ApiOperation(value = "附件统计", notes = "") // @PostMapping("/attachTypeStatistics") // public ResponseResult> attachTypeStatistics(@RequestBody AttachStatisticsVo attachStatisticsVo) { // List result = attachService.attachTypeStatistics(attachStatisticsVo); // return ResponseResult.success(result); // } /** * 个人中心附件统计 */ // @ApiOperation(value = "个人中心附件统计", notes = "") // @PostMapping("/getManageAttachTypeStatistics") // public ResponseResult> getManageAttachTypeStatistics(@RequestBody WaylineJobInfoQueryDto dto) { // WaylineJobInfoQueryParam param = new WaylineJobInfoQueryParam(dto); // param.setFormatTime(ChartDataVo.getTimeFormatByEnum(param.getDateEnum())); // List result = attachService.getManageAttachTypeStatistics(param); // return ResponseResult.success(result); // } /** * 获取OnlyOffice文档编辑器配置信息 *

* 通过代理下载方式解决OnlyOffice Document Server无法直接访问远程文件URL的问题。 * 将远程文件URL缓存到本地,并生成代理下载URL供Document Server使用。 *

* * @param request HTTP请求对象,用于构建代理下载URL * @param attachId 附件ID,用于查询附件信息 * @return 返回包含用户信息和附件信息的OnlyOffice配置数据 */ @GetMapping("/getOnlyOfficeConfig") @ApiOperationSupport(order = 10) @ApiOperation(value = "获取OnlyOffice配置", notes = "根据附件ID查询附件信息,返回包含当前用户信息和附件信息的OnlyOffice文档编辑器配置") public R> getOnlyOfficeConfig( HttpServletRequest request, @ApiParam(value = "附件ID", required = true) @RequestParam Long attachId, @RequestParam(value = "mode", required = false) String mode) { // 1. 获取当前登录用户信息 BladeUser user = AuthUtil.getUser(); // 2. 根据附件ID查询附件信息 Attach attach = attachService.getOne(Wrappers.lambdaQuery(Attach.class).eq(Attach::getId, attachId)); if (attach == null) { return R.fail("附件不存在"); } // 3. 构建用户信息对象 Map userConfig = new HashMap<>(); userConfig.put("name", user != null ? user.getUserName() : "未知用户"); userConfig.put("id", user != null ? user.getUserId() : "0"); // 4. 构建编辑器自定义配置 Map customization = new HashMap<>(); customization.put("autosave", true); customization.put("forcesave", true); // 5. 构建编辑器配置 Map editorConfig = new HashMap<>(); editorConfig.put("customization", customization); editorConfig.put("mode", StringUtil.isBlank(mode) ? "view" : mode); editorConfig.put("callbackUrl", onlyofficeCallbackUrl); editorConfig.put("lang", "zh-CN"); editorConfig.put("user", userConfig); // 6. 从附件对象获取文件信息 String fileName = attach.getName(); // 附件名称 String fileUrl = attach.getLink(); // 附件URL地址(远程URL) String fileType = ""; // 文件类型(扩展名) // 7. 从文件名中提取文件类型 if (fileName != null && fileName.contains(".")) { fileType = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase(); } else if (fileUrl != null && fileUrl.contains(".")) { fileType = fileUrl.substring(fileUrl.lastIndexOf(".") + 1).toLowerCase(); } // 8. 根据文件类型确定文档类型 String documentType = "word"; // 默认为word类型 if (fileType.equals("xlsx") || fileType.equals("xls")) { documentType = "cell"; } else if (fileType.equals("pptx") || fileType.equals("ppt")) { documentType = "slide"; } // 9. 将远程文件URL缓存到本地,供代理下载接口使用 if (fileUrl != null && !fileUrl.isEmpty()) { REMOTE_URL_CACHE.put(attachId, fileUrl); } // 10. 构建代理下载URL,替代直接使用远程URL // Document Server通过代理下载接口获取文件,避免无法直接访问远程URL的问题 String documentUrl; // String documentUrl = buildProxyDownloadUrl(request, attachId); if (onlyofficeBaseUrl != null && !onlyofficeBaseUrl.isEmpty()) { // 使用代理下载URL,Document Server通过本服务的代理接口获取文件 documentUrl = onlyofficeBaseUrl + "/attach/onlyoffice-download?attachId=" + attachId; } else { // 未配置baseUrl时回退到直接使用远程URL documentUrl = fileUrl != null ? fileUrl : ""; } // 11. 生成文档唯一标识 String key = generateRevisionId(fileName); // 12. 构建文档配置,使用代理下载URL替代远程URL Map document = new HashMap<>(); document.put("title", fileName != null ? fileName : "未命名文档"); document.put("url", documentUrl); document.put("fileType", fileType); document.put("key", attach.getMd5()); // 13. 构建最终返回结果 Map result = new HashMap<>(); result.put("documentType", documentType); result.put("document", document); result.put("editorConfig", editorConfig); // 14. 生成 JWT token Map result1 = onlyofficeJwt.getToken(result); log.info("ONLYOFFICE配置生成完成,attachId: {}, documentUrl: {}", attachId, documentUrl); return R.data(result1); } public static final Integer MAX_KEY_LENGTH = 20; public static final Integer ANONYMOUS_USER_ID = 4; public static final Integer KILOBYTE_SIZE = 1024; public String generateRevisionId(final String expectedKey) { /* if the expected key length is greater than 20 then he expected key is hashed and a fixed length value is stored in the string format */ String formatKey = expectedKey.length() > MAX_KEY_LENGTH ? Integer.toString(expectedKey.hashCode()) : expectedKey; String key = formatKey.replace("[^0-9-.a-zA-Z_=]", "_"); return key.substring(0, Math.min(key.length(), MAX_KEY_LENGTH)); // the resulting key length is 20 or less } /** * 构建当前服务器的代理下载URL * * @param request HTTP请求对象,用于获取服务器地址 * @param attachId 附件ID * @return 代理下载URL地址 */ private String buildProxyDownloadUrl(HttpServletRequest request, Long attachId) { // 1. 获取请求的协议、主机名和端口 String scheme = request.getScheme(); String serverName = request.getServerName(); int serverPort = request.getServerPort(); // 2. 构建服务器基础URL(省略默认端口) StringBuilder baseUrl = new StringBuilder(); baseUrl.append(scheme).append("://").append(serverName); if (("http".equals(scheme) && serverPort != 80) || ("https".equals(scheme) && serverPort != 443)) { baseUrl.append(":").append(serverPort); } // baseUrl.append("http:192.168.1.33:80/blade-resource"); // 3. 拼接代理下载URL return baseUrl.toString() + "/attach/onlyoffice-download?attachId=" + attachId; } @GetMapping("/onlyoffice-download") @ApiOperation(value = "OnlyOffice代理下载", notes = "代理下载远程文件供OnlyOffice Document Server访问") public void onlyofficeDownload( HttpServletRequest request, HttpServletResponse response, @ApiParam(value = "附件ID", required = true) @RequestParam Long attachId) throws IOException { // 处理 OPTIONS 预检请求 if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); response.setHeader("Access-Control-Allow-Headers", "*"); response.setStatus(HttpServletResponse.SC_OK); return; } log.info("ONLYOFFICE 下载请求,attachId: {}", attachId); // 1. 根据附件ID查询附件信息 Attach attach = attachService.getOne(Wrappers.lambdaQuery(Attach.class).eq(Attach::getId, attachId)); if (attach == null) { response.sendError(HttpServletResponse.SC_NOT_FOUND, "附件不存在,attachId: " + attachId); return; } // 2. 优先从缓存中获取远程文件URL String remoteUrl = REMOTE_URL_CACHE.get(attachId); if (remoteUrl == null || remoteUrl.isEmpty()) { remoteUrl = attach.getLink(); log.info("从数据库获取远程URL,attachId: {}, link: {}", attachId, remoteUrl); } // 3. 验证远程URL if (remoteUrl == null || remoteUrl.isEmpty()) { response.sendError(HttpServletResponse.SC_NOT_FOUND, "附件缺少远程文件URL,attachId: " + attachId); return; } // 4. 获取纯文件名(不含路径) String fileName = attach.getName(); if (fileName == null || fileName.isEmpty()) { fileName = "document.docx"; } // 移除路径部分 if (fileName.contains("/")) { fileName = fileName.substring(fileName.lastIndexOf("/") + 1); } if (fileName.contains("\\")) { fileName = fileName.substring(fileName.lastIndexOf("\\") + 1); } log.info("下载文件名: {}", fileName); // 5. 下载远程文件 HttpURLConnection connection = null; InputStream inputStream = null; OutputStream outputStream = null; try { URL url = new URL(remoteUrl); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(CONNECT_TIMEOUT); connection.setReadTimeout(READ_TIMEOUT); // 复制请求头(如果需要) String userAgent = request.getHeader("User-Agent"); if (userAgent != null) { connection.setRequestProperty("User-Agent", userAgent); } int responseCode = connection.getResponseCode(); if (responseCode != HttpURLConnection.HTTP_OK) { log.error("远程文件下载失败,状态码: {}, url: {}", responseCode, remoteUrl); response.sendError(HttpServletResponse.SC_BAD_GATEWAY, "远程文件下载失败,状态码: " + responseCode); return; } // 6. 设置响应头 String contentType = connection.getContentType(); if (contentType == null || contentType.isEmpty()) { contentType = getContentTypeByFileName(fileName); } response.setContentType(contentType); // 设置 Content-Length int contentLength = connection.getContentLength(); if (contentLength > 0) { response.setContentLength(contentLength); } // 7. 正确设置 Content-Disposition String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()) .replaceAll("\\+", "%20"); // ONLYOFFICE 需要 inline 方式访问 response.setHeader("Content-Disposition", "inline; filename=\"" + encodedFileName + "\"; " + "filename*=UTF-8''" + encodedFileName); // CORS 头 response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); response.setHeader("Access-Control-Expose-Headers", "Content-Disposition"); // 缓存控制 response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); response.setHeader("Pragma", "no-cache"); response.setHeader("Expires", "0"); // 8. 流式传输 inputStream = connection.getInputStream(); outputStream = response.getOutputStream(); byte[] buffer = new byte[8192]; int bytesRead; long totalBytes = 0; while ((bytesRead = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, bytesRead); totalBytes += bytesRead; } outputStream.flush(); log.info("下载完成,文件大小: {} bytes", totalBytes); } catch (Exception e) { log.error("代理下载远程文件失败,attachId: {}, remoteUrl: {}, 错误: {}", attachId, remoteUrl, e.getMessage(), e); if (!response.isCommitted()) { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "代理下载远程文件失败: " + e.getMessage()); } } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException ignored) {} } if (outputStream != null) { try { outputStream.close(); } catch (IOException ignored) {} } if (connection != null) { connection.disconnect(); } } } /** * 根据文件名获取 Content-Type */ private String getContentTypeByFileName(String fileName) { if (fileName == null) return "application/octet-stream"; String extension = ""; int lastDot = fileName.lastIndexOf("."); if (lastDot > 0) { extension = fileName.substring(lastDot + 1).toLowerCase(); } switch (extension) { case "docx": return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; case "doc": return "application/msword"; case "xlsx": return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; case "xls": return "application/vnd.ms-excel"; case "pptx": return "application/vnd.openxmlformats-officedocument.presentationml.presentation"; case "ppt": return "application/vnd.ms-powerpoint"; case "pdf": return "application/pdf"; case "txt": return "text/plain"; case "csv": return "text/csv"; case "rtf": return "application/rtf"; default: return "application/octet-stream"; } } }