package jnpf.bizcommon.onlyoffice.service.impl; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.json.JSONUtil; import cn.hutool.jwt.JWTUtil; import jnpf.bizcommon.onlyoffice.entity.OnlyOfficeProperties; import jnpf.exception.DataException; import org.springframework.stereotype.Component; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.LinkedHashMap; import java.util.Map; /** Generates a tracked-change DOCX on Document Server; never opens a browser edit session. */ @Component public class OnlyOfficeComparisonRenderer { public static final String SCRIPT = "builderJS.OpenFile(Argument.originalUrl);\n" + "var file = builderJS.OpenTmpFile(Argument.revisedUrl);\n" + "AscCommonWord.CompareDocuments(Api, file);\n" + "file.Close();\n" + "builderJS.SaveFile(\"docx\", \"Result.docx\");\n" + "builderJS.CloseFile();\n"; private final OnlyOfficeProperties properties; public OnlyOfficeComparisonRenderer(OnlyOfficeProperties properties) { this.properties = properties; } public String generate(String originalUrl, String revisedUrl) { Map request = new LinkedHashMap<>(); request.put("async", false); request.put("url", properties.getFileFetchBaseUrl() + "/api/biz/onlyoffice/compare/script.js"); request.put("argument", Map.of("originalUrl", originalUrl, "revisedUrl", revisedUrl)); request.put("token", JWTUtil.createToken(request, properties.getJwtSecret().getBytes(StandardCharsets.UTF_8))); try (HttpResponse response = HttpRequest.post(serverBase() + "/docbuilder") .timeout(90_000).body(JSONUtil.toJsonStr(request)).execute()) { if (!response.isOk()) throw new DataException("生成版本差异失败,请稍后重试"); cn.hutool.json.JSONObject result = JSONUtil.parseObj(response.body()); if (!Boolean.TRUE.equals(result.getBool("end")) || result.containsKey("error") || result.getJSONObject("urls") == null) { throw new DataException("OnlyOffice 未能生成版本差异,请稍后重试"); } return resultPath(result.getJSONObject("urls").getStr("Result.docx")); } catch (DataException e) { throw e; } catch (Exception e) { throw new DataException("生成版本差异失败或超时,请稍后重试"); } } /** Only DS cache paths are accepted; the host is always the configured DS server. */ static String resultPath(String url) { try { URI uri = URI.create(url); String path = uri.getRawPath(); if (path == null || !path.startsWith("/cache/files/") || path.contains("..") || path.contains("%") || !path.endsWith("/Result.docx")) { throw new IllegalArgumentException(); } return path + (uri.getRawQuery() == null ? "" : "?" + uri.getRawQuery()); } catch (Exception e) { throw new DataException("无效的版本比较结果地址"); } } public byte[] readResult(String path) { String safePath = resultPath(path); try (HttpResponse response = HttpRequest.get(serverBase() + safePath).timeout(30_000).execute()) { if (!response.isOk()) throw new DataException("比较结果已过期,请重新比较"); return response.bodyBytes(); } } private String serverBase() { String base = properties.getDsInternalUrl(); if (base == null || base.isBlank()) base = properties.getDsPublicUrl(); if (base == null || base.isBlank()) throw new DataException("OnlyOffice 服务地址未配置"); return base.replaceAll("/+$", ""); } }