package org.sxkj.resource.endpoint; import com.alibaba.fastjson.JSON; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.AllArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.springblade.core.oss.model.BladeFile; import org.springblade.core.tenant.annotation.NonDS; import org.springblade.core.tool.utils.FileUtil; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.sxkj.resource.builder.OssBuilder; import org.sxkj.resource.dto.OfficeCallbackDTO; import org.sxkj.resource.entity.Attach; import org.sxkj.resource.service.IAttachService; import java.io.InputStream; import java.util.HashMap; import java.util.Map; /** * ONLYOFFICE 回调端点 * * @author lw * @since 2026-07-08 */ @Slf4j @NonDS @RestController @AllArgsConstructor @RequestMapping("/office") @Api(value = "ONLYOFFICE回调端点", tags = "ONLYOFFICE回调接口") public class OfficeCallbackEndpoint { /** * 对象存储构建类 */ private final OssBuilder ossBuilder; /** * 附件表服务 */ private final IAttachService attachService; /** * ONLYOFFICE 保存回调接口 * 对应前端 config.editorConfig.callbackUrl = "http://localhost:8080/office/callback" * * @param callbackDTO 回调数据对象 * @return 回调响应结果 */ @SneakyThrows @PostMapping("/callback") @ApiOperation(value = "ONLYOFFICE保存回调", notes = "ONLYOFFICE文档编辑保存回调接口") public Map officeCallback(@RequestBody OfficeCallbackDTO callbackDTO) { log.info("ONLYOFFICE回调开始,回调数据: {}", JSON.toJSONString(callbackDTO)); Map result = new HashMap<>(4); // 步骤1:获取回调状态和文件信息 String status = callbackDTO.getStatus(); String fileKey = callbackDTO.getKey(); String fileDownloadUrl = callbackDTO.getUrl(); try { // 步骤2:判断是否是保存操作(1=手动保存,2=强制保存) if ("1".equals(status) || "2".equals(status)) { // 步骤3:下载ONLYOFFICE返回的新文件 byte[] fileContent = downloadFile(fileDownloadUrl); // 步骤4:获取文件名(可根据业务需求从数据库获取原始文件名) String fileName = generateFileName(fileKey); // 步骤5:上传文件到OSS BladeFile bladeFile = uploadToOss(fileName, fileContent); log.info("ONLYOFFICE文件下载成功,文件名: {}, 文件大小: {}", fileName, fileContent.length); // 步骤6:保存附件信息到数据库 Long attachId = saveAttachInfo(fileName, fileContent.length, bladeFile); // 步骤7:记录操作日志(可根据业务需求扩展) log.info("ONLYOFFICE文件保存成功,文件key: {}, 文件名: {}, 附件ID: {}, 下载URL: {}", fileKey, fileName, attachId, bladeFile.getLink()); // 步骤8:业务扩展(可根据需求添加) // - 更新数据库文件地址、版本号、更新时间 // - 记录操作日志、操作用户 // - 发送通知等 } // 步骤9:成功必须返回 error:0 result.put("error", 0); } catch (Exception e) { // 步骤10:异常处理,非0代表失败,编辑器会提示保存失败 log.error("ONLYOFFICE文件保存异常,文件key: {}, 异常信息: {}", fileKey, e.getMessage(), e); result.put("error", 1); result.put("message", "文件保存异常:" + e.getMessage()); } return result; } /** * 从ONLYOFFICE服务器下载文件 * * @param downloadUrl 文件下载URL * @return 文件内容字节数组 * @throws Exception 下载异常 */ private byte[] downloadFile(String downloadUrl) throws Exception { // 步骤1:创建HTTP客户端 try (CloseableHttpClient httpClient = HttpClients.createDefault()) { // 步骤2:构建GET请求 HttpGet httpGet = new HttpGet(downloadUrl); // 步骤3:执行请求并获取响应 try (CloseableHttpResponse response = httpClient.execute(httpGet)) { // 步骤4:获取响应内容流并读取到字节数组 InputStream inputStream = response.getEntity().getContent(); return inputStream.readAllBytes(); } } } /** * 上传文件到OSS * * @param fileName 文件名 * @param fileContent 文件内容 * @return BladeFile OSS文件信息 * @throws Exception 上传异常 */ private BladeFile uploadToOss(String fileName, byte[] fileContent) throws Exception { // 步骤1:创建输入流 InputStream inputStream = new java.io.ByteArrayInputStream(fileContent); // 步骤2:使用ossBuilder上传文件到指定存储桶(estack) return ossBuilder.template("estack").putFile(fileName, inputStream); } /** * 保存附件信息到数据库 * * @param fileName 文件名 * @param fileSize 文件大小 * @param bladeFile OSS文件信息 * @return 附件ID */ private Long saveAttachInfo(String fileName, int fileSize, BladeFile bladeFile) { // 步骤1:获取文件扩展名 String fileExtension = FileUtil.getFileExtension(fileName); // 步骤2:构建附件实体 Attach attach = new Attach(); attach.setDomainUrl(bladeFile.getDomain()); attach.setLink(bladeFile.getLink()); attach.setName(bladeFile.getName()); attach.setOriginalName(bladeFile.getOriginalName()); attach.setAttachSize(Long.valueOf(fileSize)); attach.setExtension(fileExtension); // 步骤3:保存附件信息 attachService.save(attach); // 步骤4:返回附件ID return attach.getId(); } /** * 根据文件key生成文件名 * * @param fileKey 文件唯一标识 * @return 文件名 */ private String generateFileName(String fileKey) { // 步骤1:可根据业务需求从数据库查询原始文件名 // FileEntity file = fileMapper.selectByKey(fileKey); // return file.getFileName(); // 步骤2:默认使用fileKey作为文件名(可根据业务修改) return fileKey + ".docx"; } }