diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WPS365ApiServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WPS365ApiServiceImpl.java index 8ba6565..b127b89 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WPS365ApiServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/WPS365ApiServiceImpl.java @@ -226,29 +226,104 @@ public class WPS365ApiServiceImpl implements IWPS365ApiService { // 如果用户只提供了一个ID(fileId),则fileId和worksheetId使用同一个值 String baseUrl = "https://openapi.wps.cn/v7"; - // 如果worksheetId为空或与fileId相同,说明用户只配置了一个ID(类似腾讯文档) - // 在AirSheet中,这个ID既是file_id也是worksheet_id + // 根据WPS365官方文档,AirSheet的worksheet_id必须是整数 + // 如果worksheetId为空或"0",使用0(第一个工作表) + // 如果worksheetId与fileId相同,说明用户只配置了一个ID,尝试使用fileId作为worksheetId String wsId; - if (worksheetId == null || worksheetId.trim().isEmpty() || worksheetId.equals(fileId)) { - // 使用fileId作为worksheetId(AirSheet中文件ID就是工作表ID) + if (worksheetId == null || worksheetId.trim().isEmpty() || "0".equals(worksheetId)) { + // 默认使用0(第一个工作表) + wsId = "0"; + } else if (worksheetId.equals(fileId)) { + // 如果worksheetId与fileId相同,说明用户只配置了一个ID(类似腾讯文档) + // 在AirSheet中,这个ID可能既是file_id也是worksheet_id wsId = fileId; } else { - // 使用提供的worksheetId(可能是整数索引,如"0") + // 使用提供的worksheetId(应该是整数) wsId = worksheetId; } - // 尝试不同的API路径格式 - // 格式1: /v7/airsheet/{file_id}/worksheets/{worksheet_id} - String url = baseUrl + "/airsheet/" + fileId + "/worksheets/" + wsId; - - // 如果指定了range,添加range参数 - // 根据官方文档,可能需要使用 /values 子路径,或者使用query参数 - if (range != null && !range.trim().isEmpty()) { - // 先尝试作为query参数 - url += "?range=" + java.net.URLEncoder.encode(range, "UTF-8"); + // 根据官方文档:https://open.wps.cn/documents/app-integration-dev/wps365/server/airsheet/worksheets/VbHZwButmh + // 正确路径:https://openapi.wps.cn/v7/airsheet/{file_id}/worksheets + // 注意:路径中不需要 worksheet_id,只需要 file_id + try { + String url = baseUrl + "/airsheet/" + fileId + "/worksheets"; + + // 如果指定了range,添加range参数 + if (range != null && !range.trim().isEmpty()) { + url += "?range=" + java.net.URLEncoder.encode(range, "UTF-8"); + } + + // 如果指定了worksheetId,也可以作为查询参数传递(如果API支持) + if (worksheetId != null && !worksheetId.trim().isEmpty() && !worksheetId.equals("0") && !worksheetId.equals(fileId)) { + if (url.contains("?")) { + url += "&worksheet_id=" + java.net.URLEncoder.encode(worksheetId, "UTF-8"); + } else { + url += "?worksheet_id=" + java.net.URLEncoder.encode(worksheetId, "UTF-8"); + } + } + + log.debug("使用官方文档路径 - url: {}, fileId: {}, worksheetId: {}, range: {}", url, fileId, worksheetId, range); + return WPS365ApiUtil.httpRequest("GET", url, accessToken, null); + } catch (Exception e) { + log.debug("官方文档路径失败,尝试其他方案", e); } - log.debug("读取AirSheet数据 - url: {}, fileId: {}, worksheetId: {}, range: {}", url, fileId, wsId, range); + // 尝试多种API路径格式(降级方案) + // 方案1: 尝试使用fileId作为worksheetId(如果用户只配置了一个ID) + if (wsId.equals("0") && !fileId.equals(worksheetId)) { + try { + String url = baseUrl + "/airsheet/" + fileId + "/worksheets/" + fileId; + if (range != null && !range.trim().isEmpty()) { + url += "?range=" + java.net.URLEncoder.encode(range, "UTF-8"); + } + log.debug("尝试方案1 - 使用fileId作为worksheetId - url: {}", url); + return WPS365ApiUtil.httpRequest("GET", url, accessToken, null); + } catch (Exception e) { + log.debug("方案1失败,尝试其他方案", e); + } + } + + // 方案2: 使用 /range_data 子路径(根据官方文档,这是读取区域数据的标准路径) + // 注意:range_data接口需要使用 row_from, row_to, col_from, col_to 参数,而不是 range=A1:B5 + try { + String url = baseUrl + "/airsheet/" + fileId + "/worksheets/" + wsId + "/range_data"; + if (range != null && !range.trim().isEmpty()) { + // 尝试解析A1:B5格式的range,转换为行列参数 + int[] rangeParams = parseRangeToRowCol(range); + if (rangeParams != null && rangeParams.length == 4) { + // 使用行列参数格式(row_from, row_to, col_from, col_to) + // 注意:WPS365的行列索引可能从0开始或从1开始,需要测试确认 + url += "?row_from=" + rangeParams[0] + "&row_to=" + rangeParams[1] + + "&col_from=" + rangeParams[2] + "&col_to=" + rangeParams[3]; + } else { + // 如果解析失败,尝试作为range参数传递 + url += "?range=" + java.net.URLEncoder.encode(range, "UTF-8"); + } + } + log.debug("尝试方案2 - 使用/range_data子路径 - url: {}", url); + return WPS365ApiUtil.httpRequest("GET", url, accessToken, null); + } catch (Exception e) { + log.debug("方案2失败,尝试其他方案", e); + } + + // 方案3: 使用 /values 子路径 + try { + String url = baseUrl + "/airsheet/" + fileId + "/worksheets/" + wsId + "/values"; + if (range != null && !range.trim().isEmpty()) { + url += "?range=" + java.net.URLEncoder.encode(range, "UTF-8"); + } + log.debug("尝试方案3 - 使用/values子路径 - url: {}", url); + return WPS365ApiUtil.httpRequest("GET", url, accessToken, null); + } catch (Exception e) { + log.debug("方案3失败,尝试其他方案", e); + } + + // 方案4: 基础路径(不带子路径) + String url = baseUrl + "/airsheet/" + fileId + "/worksheets/" + wsId; + if (range != null && !range.trim().isEmpty()) { + url += "?range=" + java.net.URLEncoder.encode(range, "UTF-8"); + } + log.debug("尝试方案4 - 基础路径 - url: {}", url); JSONObject result = WPS365ApiUtil.httpRequest("GET", url, accessToken, null); return result; } catch (Exception e) { @@ -274,19 +349,13 @@ public class WPS365ApiServiceImpl implements IWPS365ApiService { public JSONObject updateAirSheetCells(String accessToken, String fileId, String worksheetId, String range, List> values) { try { // WPS365 AirSheet API路径格式 - // 注意:AirSheet中,fileId和worksheetId可能是同一个值 + // 根据文档:https://open.wps.cn/documents/app-integration-dev/wps365/server/airsheet/worksheets/VbHZwButmh + // 正确路径:https://openapi.wps.cn/v7/airsheet/{file_id}/worksheets + // 注意:路径中不需要 worksheet_id,只需要 file_id String baseUrl = "https://openapi.wps.cn/v7"; - // 如果worksheetId为空或与fileId相同,说明用户只配置了一个ID - String wsId; - if (worksheetId == null || worksheetId.trim().isEmpty() || worksheetId.equals(fileId)) { - wsId = fileId; - } else { - wsId = worksheetId; - } - - // 尝试不同的API路径格式 - String url = baseUrl + "/airsheet/" + fileId + "/worksheets/" + wsId; + // 使用官方文档中的正确路径格式 + String url = baseUrl + "/airsheet/" + fileId + "/worksheets"; // 构建请求体 JSONObject requestBody = new JSONObject(); @@ -309,23 +378,106 @@ public class WPS365ApiServiceImpl implements IWPS365ApiService { } requestBody.put("values", valuesArray); + // 如果指定了worksheetId,也可以添加到请求体中(如果API支持) + if (worksheetId != null && !worksheetId.trim().isEmpty() && !worksheetId.equals("0") && !worksheetId.equals(fileId)) { + requestBody.put("worksheet_id", worksheetId); + } + String bodyStr = requestBody.toJSONString(); log.debug("更新AirSheet数据 - url: {}, fileId: {}, worksheetId: {}, range: {}, values: {}", - url, fileId, wsId, range, bodyStr); + url, fileId, worksheetId, range, bodyStr); - try { - return WPS365ApiUtil.httpRequest("PUT", url, accessToken, bodyStr); - } catch (Exception e) { - // 如果失败,尝试使用 /values 子路径 - log.warn("使用基础路径失败,尝试/values子路径", e); - String urlWithValues = baseUrl + "/airsheet/" + fileId + "/worksheets/" + wsId + "/values"; - log.debug("尝试使用/values子路径 - url: {}", urlWithValues); - return WPS365ApiUtil.httpRequest("PUT", urlWithValues, accessToken, bodyStr); - } + return WPS365ApiUtil.httpRequest("PUT", url, accessToken, bodyStr); } catch (Exception e) { log.error("更新AirSheet数据失败 - fileId: {}, worksheetId: {}, range: {}", fileId, worksheetId, range, e); throw new RuntimeException("更新AirSheet数据失败: " + e.getMessage(), e); } } + + /** + * 解析A1:B5格式的range,转换为行列参数 + * 返回数组:[row_from, row_to, col_from, col_to] + * 注意:WPS365的行列索引可能从0开始或从1开始,这里假设从1开始(Excel标准) + * + * @param range 单元格范围,如 "A1:B5" + * @return 行列参数数组,如果解析失败返回null + */ + private int[] parseRangeToRowCol(String range) { + if (range == null || range.trim().isEmpty()) { + return null; + } + + try { + // 解析A1:B5格式 + String[] parts = range.split(":"); + if (parts.length != 2) { + return null; + } + + String startCell = parts[0].trim(); + String endCell = parts[1].trim(); + + // 解析起始单元格,如 "A1" -> row=1, col=1 + int[] start = parseCellAddress(startCell); + int[] end = parseCellAddress(endCell); + + if (start == null || end == null) { + return null; + } + + // 返回 [row_from, row_to, col_from, col_to] + // 注意:WPS365可能从0开始索引,这里先使用从1开始的索引(Excel标准) + // 如果API要求从0开始,需要减1 + return new int[]{start[0], end[0], start[1], end[1]}; + } catch (Exception e) { + log.warn("解析range失败: {}", range, e); + return null; + } + } + + /** + * 解析单元格地址,如 "A1" -> [row=1, col=1] + * + * @param cellAddress 单元格地址,如 "A1", "B5" + * @return [row, col] 数组,如果解析失败返回null + */ + private int[] parseCellAddress(String cellAddress) { + if (cellAddress == null || cellAddress.trim().isEmpty()) { + return null; + } + + try { + // 分离字母部分(列)和数字部分(行) + // 例如 "A1" -> col="A", row="1" + String colStr = ""; + String rowStr = ""; + + for (char c : cellAddress.toCharArray()) { + if (Character.isLetter(c)) { + colStr += c; + } else if (Character.isDigit(c)) { + rowStr += c; + } + } + + if (colStr.isEmpty() || rowStr.isEmpty()) { + return null; + } + + // 转换列字母为数字,A=1, B=2, ..., Z=26, AA=27, ... + int col = 0; + for (char c : colStr.toUpperCase().toCharArray()) { + col = col * 26 + (c - 'A' + 1); + } + + // 转换行号为整数 + int row = Integer.parseInt(rowStr); + + return new int[]{row, col}; + } catch (Exception e) { + log.warn("解析单元格地址失败: {}", cellAddress, e); + return null; + } + } }