|
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.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<Long, String> 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<Attach> 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<IPage<AttachVO>> page(AttachPageParam attach, Query query) {
|
BladeUser user = AuthUtil.getUser();
|
IPage<AttachVO> 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<IPage<Attach>> list(Attach attach, Query query) {
|
String patrolTaskId = attach.getPatrolTaskId();
|
QueryWrapper<Attach> queryWrapper1 = Condition.getQueryWrapper(attach);
|
QueryWrapper<Attach> queryWrapper = queryWrapper1.eq(!StringUtils.isEmpty(patrolTaskId), "patrol_task_id", patrolTaskId);
|
queryWrapper.eq("is_deleted", 0);
|
IPage<Attach> pages = attachService.page(Condition.getPage(query), queryWrapper);
|
return R.data(pages);
|
}
|
|
/**
|
* 自定义分页 附件表
|
*/
|
// @PostMapping("/aiImagesPage")
|
// @ApiOperationSupport(order = 3)
|
// @ApiOperation(value = "自定义分页附件表分页", notes = "ai分页数据")
|
// public R<IPage<AttachVO>> aiPage(@RequestBody AttachVO attach, Query query) {
|
// String areaCode = HeaderUtils.getAreaCode();
|
// attach.setAreaCode(areaCode);
|
// List<Integer> 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<String> 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<AttachVO> aiAttachImages = attachService.findAiAttachImages(page, attach);
|
// AttachVO.settingNickName(aiAttachImages);
|
// page.setRecords(aiAttachImages);
|
// return R.data(page);
|
// }
|
|
/**
|
* 后台数据中心列表接口
|
*/
|
// @PostMapping("/attachmentsPage")
|
// @ApiOperationSupport(order = 4)
|
// @ApiOperation(value = "后台数据中心列表接口")
|
// public R<IPage<AttachVO>> 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<List<DeptAndSizeVo>> 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<TreeVo> 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<AnalysisAttachVo> list = attachService.contrastiveAnalysis(id);
|
// return ResponseResult.success(list);
|
// }
|
|
// @ApiOperation(value = "附件详情", notes = "")
|
// @GetMapping("/getAttachInfo")
|
// public ResponseResult<AttachInfoVO> getAttachInfo(@ApiParam(value = "附件iD") @RequestParam("id") Long id) throws IOException {
|
// return ResponseResult.success(attachService.getAttachInfo(id));
|
// }
|
|
// @ApiOperation(value = "通过任务id查询附件详情集合", notes = "")
|
// @GetMapping("/getAttachInfoByJobId")
|
// public ResponseResult<List<AttachInfoVO>> 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<Long> ids) {
|
// attachService.deleteMediaFile(ids);
|
// return ResponseResult.success();
|
// }
|
|
/**
|
* 附件统计
|
*/
|
// @ApiOperation(value = "附件统计", notes = "")
|
// @PostMapping("/attachTypeStatistics")
|
// public ResponseResult<List<AttachTypeStatisticsVO>> attachTypeStatistics(@RequestBody AttachStatisticsVo attachStatisticsVo) {
|
// List<AttachTypeStatisticsVO> result = attachService.attachTypeStatistics(attachStatisticsVo);
|
// return ResponseResult.success(result);
|
// }
|
|
/**
|
* 个人中心附件统计
|
*/
|
// @ApiOperation(value = "个人中心附件统计", notes = "")
|
// @PostMapping("/getManageAttachTypeStatistics")
|
// public ResponseResult<List<LineColumnDateVo>> getManageAttachTypeStatistics(@RequestBody WaylineJobInfoQueryDto dto) {
|
// WaylineJobInfoQueryParam param = new WaylineJobInfoQueryParam(dto);
|
// param.setFormatTime(ChartDataVo.getTimeFormatByEnum(param.getDateEnum()));
|
// List<LineColumnDateVo> result = attachService.getManageAttachTypeStatistics(param);
|
// return ResponseResult.success(result);
|
// }
|
|
/**
|
* 获取OnlyOffice文档编辑器配置信息
|
* <p>
|
* 通过代理下载方式解决OnlyOffice Document Server无法直接访问远程文件URL的问题。
|
* 将远程文件URL缓存到本地,并生成代理下载URL供Document Server使用。
|
* </p>
|
*
|
* @param request HTTP请求对象,用于构建代理下载URL
|
* @param attachId 附件ID,用于查询附件信息
|
* @return 返回包含用户信息和附件信息的OnlyOffice配置数据
|
*/
|
@GetMapping("/getOnlyOfficeConfig")
|
@ApiOperationSupport(order = 10)
|
@ApiOperation(value = "获取OnlyOffice配置", notes = "根据附件ID查询附件信息,返回包含当前用户信息和附件信息的OnlyOffice文档编辑器配置")
|
public R<Map<String, Object>> getOnlyOfficeConfig(
|
HttpServletRequest request,
|
@ApiParam(value = "附件ID", required = true) @RequestParam Long attachId) {
|
// 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<String, Object> userConfig = new HashMap<>();
|
userConfig.put("name", user != null ? user.getUserName() : "未知用户");
|
userConfig.put("id", user != null ? user.getUserId() : "0");
|
|
// 4. 构建编辑器自定义配置
|
Map<String, Object> customization = new HashMap<>();
|
customization.put("autosave", true);
|
customization.put("forcesave", true);
|
|
// 5. 构建编辑器配置
|
Map<String, Object> editorConfig = new HashMap<>();
|
editorConfig.put("customization", customization);
|
editorConfig.put("mode", "edit");
|
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<String, Object> document = new HashMap<>();
|
document.put("title", fileName != null ? fileName : "未命名文档");
|
document.put("url", documentUrl);
|
document.put("fileType", fileType);
|
document.put("key", attach.getMd5());
|
|
// 13. 构建最终返回结果
|
Map<String, Object> result = new HashMap<>();
|
result.put("documentType", documentType);
|
result.put("document", document);
|
result.put("editorConfig", editorConfig);
|
|
// 14. 生成 JWT token
|
Map<String, Object> 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;
|
}
|
|
/**
|
* OnlyOffice代理下载接口,用于代理下载远程文件
|
* <p>
|
* 当OnlyOffice Document Server无法直接访问远程文件URL时,
|
* 通过本接口代理下载文件并流式返回给Document Server。
|
* </p>
|
*
|
* @param request HTTP请求对象
|
* @param response HTTP响应对象
|
* @param attachId 附件ID,用于从缓存中查找对应的远程文件URL
|
* @throws IOException 当下载或写入文件流时发生I/O异常
|
*/
|
@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 {
|
|
// 1. 从缓存中获取远程文件URL
|
String remoteUrl = REMOTE_URL_CACHE.get(attachId);
|
if (remoteUrl == null || remoteUrl.isEmpty()) {
|
response.sendError(HttpServletResponse.SC_NOT_FOUND, "未找到对应的远程文件URL");
|
return;
|
}
|
|
// 2. 根据附件ID查询附件信息,获取文件名
|
Attach attach = attachService.getOne(Wrappers.lambdaQuery(Attach.class).eq(Attach::getId, attachId));
|
String fileName = (attach != null && attach.getName() != null) ? attach.getName() : "document.docx";
|
|
// 3. 从远程URL下载文件
|
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);
|
|
// 4. 检查响应状态码
|
int responseCode = connection.getResponseCode();
|
if (responseCode != HttpURLConnection.HTTP_OK) {
|
response.sendError(HttpServletResponse.SC_BAD_GATEWAY, "远程文件下载失败,状态码: " + responseCode);
|
return;
|
}
|
|
// 5. 获取远程文件的Content-Type
|
String contentType = connection.getContentType();
|
if (contentType == null || contentType.isEmpty()) {
|
contentType = "application/octet-stream";
|
}
|
|
// 6. 设置响应头
|
response.setContentType(contentType);
|
response.setHeader("Content-Disposition", "attachment; filename=\""
|
+ URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()) + "\"");
|
|
// 7. 流式传输文件内容
|
inputStream = connection.getInputStream();
|
outputStream = response.getOutputStream();
|
byte[] buffer = new byte[8192];
|
int bytesRead;
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
outputStream.write(buffer, 0, bytesRead);
|
}
|
outputStream.flush();
|
} 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 {
|
// 8. 关闭资源
|
if (inputStream != null) {
|
try { inputStream.close(); } catch (IOException ignored) {}
|
}
|
if (connection != null) {
|
connection.disconnect();
|
}
|
}
|
}
|
|
}
|