This commit is contained in:
Leo
2026-02-05 00:11:59 +08:00
parent 7ed5a76d2f
commit 7367e28133
3 changed files with 101 additions and 31 deletions

View File

@@ -57,8 +57,12 @@ public class TencentDocController extends BaseController {
/** Redis key前缀用于存储上次处理的最大行数 */
private static final String LAST_PROCESSED_ROW_KEY_PREFIX = "tendoc:last_row:";
/** 单次请求最大行数(腾讯文档 API行数≤1000,一次拉 500 行 */
/** 单次请求最大行数(腾讯文档 API行数≤1000 */
private static final int API_MAX_ROWS_PER_REQUEST = 500;
/** 用 rowTotal 时接口实际单次只能读 200 行 */
private static final int READ_ROWS_WHEN_USE_ROW_TOTAL = 200;
/** 用 rowTotal 时 startRow = rowTotal - 200 必须大于此值(保证至少为 3避免表头 */
private static final int MIN_START_ROW_WHEN_USE_ROW_TOTAL = 3;
/** 数据区 range 列尾30 列 = A 到 ADAPI 列数≤200 */
private static final String DATA_RANGE_COL_END = "AD";
/** 回溯行数:下次起始行 = 本次扫描终点 + 1 - 回溯,用于覆盖物流变更或延后补填 */
@@ -1010,40 +1014,49 @@ public class TencentDocController extends BaseController {
}
}
// 计算本次同步的起始行和结束行(完美推送回溯)
// 1. 单次请求不超过 API_MAX_ROWS_PER_REQUEST避免 range invalid
// 2. 下次起始 = 本次扫描终点 + 1 - BACKTRACK_ROWS兼顾物流变更与补填
// 3. 至少前进行数 MIN_ADVANCE_ROWS避免文档中间大量空行时原地打转
int effectiveStartRow = configStartRow != null ? configStartRow : (headerRow + 1);
int startRow;
int endRow;
if (forceStartRow != null) {
startRow = forceStartRow;
endRow = startRow + API_MAX_ROWS_PER_REQUEST - 1;
log.info("使用强制指定的起始行: {}, 结束行: {}(单批最多 {} 行)", startRow, endRow, API_MAX_ROWS_PER_REQUEST);
} else if (lastMaxRow != null && lastMaxRow >= effectiveStartRow) {
// 带回溯的起始:从 (上次最大行 + 1 - 回溯行数) 开始,保证覆盖物流变更与延后补填
startRow = Math.max(effectiveStartRow, lastMaxRow + 1 - BACKTRACK_ROWS);
endRow = startRow + API_MAX_ROWS_PER_REQUEST - 1;
log.info("上次最大行: {},回溯 {} 行后从第 {} 行开始,到第 {} 行(本批最多 {} 行)",
lastMaxRow, BACKTRACK_ROWS, startRow, endRow, API_MAX_ROWS_PER_REQUEST);
// 直接拉取文档信息得到 rowTotal用「最大行 - 200」作为起始接口实际只能读 200 行),且保证 -200 之后 > 2
int rowTotal = tencentDocService.getSheetRowTotal(accessToken, fileId, sheetId);
if (rowTotal > 0) {
if (forceStartRow != null) {
startRow = forceStartRow;
endRow = Math.min(rowTotal, startRow + READ_ROWS_WHEN_USE_ROW_TOTAL - 1);
} else {
// startRow = rowTotal - 200且必须大于 2>= MIN_START_ROW_WHEN_USE_ROW_TOTAL
startRow = Math.max(MIN_START_ROW_WHEN_USE_ROW_TOTAL,
Math.max(effectiveStartRow, rowTotal - READ_ROWS_WHEN_USE_ROW_TOTAL));
endRow = Math.min(rowTotal, startRow + READ_ROWS_WHEN_USE_ROW_TOTAL - 1);
}
log.info("使用文档 rowTotal={},本次范围: 第 {} ~ {} 行(共 {} 行,单次最多 {} 行)",
rowTotal, startRow, endRow, endRow - startRow + 1, READ_ROWS_WHEN_USE_ROW_TOTAL);
} else {
startRow = effectiveStartRow;
endRow = startRow + API_MAX_ROWS_PER_REQUEST - 1;
log.info("首次同步或配置已重置,从第 {} 行开始,到第 {} 行(单批最多 {} 行)", startRow, endRow, API_MAX_ROWS_PER_REQUEST);
// 未取到 rowTotal 时退回原逻辑
if (forceStartRow != null) {
startRow = forceStartRow;
endRow = startRow + API_MAX_ROWS_PER_REQUEST - 1;
log.info("使用强制指定的起始行: {}, 结束行: {}(未取到 rowTotal", startRow, endRow);
} else if (lastMaxRow != null && lastMaxRow >= effectiveStartRow) {
startRow = Math.max(effectiveStartRow, lastMaxRow + 1 - BACKTRACK_ROWS);
endRow = startRow + API_MAX_ROWS_PER_REQUEST - 1;
log.info("上次最大行: {},回溯后从第 {} 行到第 {} 行(未取到 rowTotal", lastMaxRow, startRow, endRow);
} else {
startRow = effectiveStartRow;
endRow = startRow + API_MAX_ROWS_PER_REQUEST - 1;
log.info("首次同步,从第 {} 行到第 {} 行(未取到 rowTotal", startRow, endRow);
}
}
// 严格限制单次 range 行数,防止 API 报 range invalid
int rowCount = endRow - startRow + 1;
if (rowCount > API_MAX_ROWS_PER_REQUEST) {
endRow = startRow + API_MAX_ROWS_PER_REQUEST - 1;
log.info("已截断结束行以符合 API 限制: endRow={}, 行数={}", endRow, API_MAX_ROWS_PER_REQUEST);
// 严格限制单次 range 行数(接口实际只能读 200 行)
if (endRow - startRow + 1 > READ_ROWS_WHEN_USE_ROW_TOTAL) {
endRow = startRow + READ_ROWS_WHEN_USE_ROW_TOTAL - 1;
log.info("已截断结束行以符合 API 限制: endRow={}, 行数={}", endRow, READ_ROWS_WHEN_USE_ROW_TOTAL);
}
log.info("开始填充物流链接 - 文件ID: {}, 工作表ID: {}, 起始行: {}, 结束行: {}, 上次最大行: {}",
fileId, sheetId, startRow, endRow, lastMaxRow);
log.info("开始填充物流链接 - 文件ID: {}, 工作表ID: {}, 起始行: {}, 结束行: {}, rowTotal: {}",
fileId, sheetId, startRow, endRow, rowTotal > 0 ? rowTotal : "未获取");
// 读取表格数据(先读取表头行用于识别列位置)
// 根据官方文档,使用 A1 表示法Excel格式
@@ -1156,16 +1169,16 @@ public class TencentDocController extends BaseController {
log.info("列位置识别完成 - 单号: {}, 物流单号: {}, 备注: {}, 是否安排: {}, 标记: {}, 下单电话: {}",
orderNoColumn, logisticsLinkColumn, remarkColumn, arrangedColumn, markColumn, phoneColumn);
// 读取数据行:严格限制单次行数(避免 range invalid,失败时逐步缩小范围重试
int effectiveEndRow = Math.min(endRow, startRow + API_MAX_ROWS_PER_REQUEST - 1);
// 读取数据行:接口实际只能读 200 行,严格限制单次行数,失败时逐步缩小范围重试
int effectiveEndRow = Math.min(endRow, startRow + READ_ROWS_WHEN_USE_ROW_TOTAL - 1);
JSONObject sheetData = null;
int[] retryDecrements = new int[] { 0, 20, 50, 100 };
for (int decrement : retryDecrements) {
int tryEndRow = Math.max(startRow, effectiveEndRow - decrement);
int tryRowCount = tryEndRow - startRow + 1;
if (tryRowCount > API_MAX_ROWS_PER_REQUEST) {
tryEndRow = startRow + API_MAX_ROWS_PER_REQUEST - 1;
tryRowCount = API_MAX_ROWS_PER_REQUEST;
if (tryRowCount > READ_ROWS_WHEN_USE_ROW_TOTAL) {
tryEndRow = startRow + READ_ROWS_WHEN_USE_ROW_TOTAL - 1;
tryRowCount = READ_ROWS_WHEN_USE_ROW_TOTAL;
}
if (tryEndRow < startRow) {
continue;