diff --git a/lesingle-creation-backend/src/main/java/com/lesingle/modules/leai/controller/LeaiProxyController.java b/lesingle-creation-backend/src/main/java/com/lesingle/modules/leai/controller/LeaiProxyController.java index 59fb43e..d82bd35 100644 --- a/lesingle-creation-backend/src/main/java/com/lesingle/modules/leai/controller/LeaiProxyController.java +++ b/lesingle-creation-backend/src/main/java/com/lesingle/modules/leai/controller/LeaiProxyController.java @@ -160,40 +160,48 @@ public class LeaiProxyController { body.putAll(requestBody); log.info("[乐读派代理] 编辑作品, workId={}", id); String responseBody = leaiApiClient.proxyPut("/update/work/" + id, body); - syncLocalAfterLeaiUpdate(id, responseBody); + syncLocalAfterLeaiUpdate(id, responseBody, requestBody); return jsonOk(responseBody); } /** - * 解析乐读派 update 响应并同步本地 t_ugc_work,避免仅依赖 Webhook 导致发布前状态滞后 + * 解析乐读派 update 响应并同步本地 t_ugc_work,避免仅依赖 Webhook 导致发布前状态滞后; + * 成功后额外用本次请求体覆盖本地元数据(与乐读派查询结果解耦,避免远端滞后)。 */ - private void syncLocalAfterLeaiUpdate(String remoteWorkId, String responseJson) { - if (responseJson == null || responseJson.isEmpty()) { - return; + private void syncLocalAfterLeaiUpdate(String remoteWorkId, String responseJson, Map proxyRequestBody) { + if (responseJson != null && !responseJson.isEmpty()) { + try { + Map root = objectMapper.readValue(responseJson, new TypeReference>() {}); + boolean ok = true; + if (root.containsKey("code")) { + int code = LeaiUtil.toInt(root.get("code"), 200); + // 乐读派部分接口成功为 0,与前端 publicApi 拦截器一致 + if (code != 200 && code != 0) { + ok = false; + log.debug("[乐读派代理] update 响应非成功,跳过远端拉取同步: code={}", code); + } + } + if (ok) { + @SuppressWarnings("unchecked") + Map data = (Map) root.get("data"); + if (data == null) { + data = root; + } + if (data.get("status") == null) { + data = leaiApiClient.fetchWorkDetail(remoteWorkId); + } + if (data != null) { + leaiSyncService.syncWork(remoteWorkId, data, "Proxy[update.work]"); + } + } + } catch (Exception e) { + log.warn("[乐读派代理] 编辑作品后同步本地失败: remoteWorkId={}", remoteWorkId, e); + } } try { - Map root = objectMapper.readValue(responseJson, new TypeReference>() {}); - if (root.containsKey("code")) { - int code = LeaiUtil.toInt(root.get("code"), 200); - if (code != 200) { - log.debug("[乐读派代理] update 响应非成功,跳过本地同步: code={}", code); - return; - } - } - @SuppressWarnings("unchecked") - Map data = (Map) root.get("data"); - if (data == null) { - data = root; - } - if (data.get("status") == null) { - data = leaiApiClient.fetchWorkDetail(remoteWorkId); - if (data == null) { - return; - } - } - leaiSyncService.syncWork(remoteWorkId, data, "Proxy[update.work]"); + leaiSyncService.applyLocalMetadataFromProxyPut(remoteWorkId, proxyRequestBody); } catch (Exception e) { - log.warn("[乐读派代理] 编辑作品后同步本地失败: remoteWorkId={}", remoteWorkId, e); + log.warn("[乐读派代理] 本地元数据覆盖失败: remoteWorkId={}", remoteWorkId, e); } } diff --git a/lesingle-creation-backend/src/main/java/com/lesingle/modules/leai/service/ILeaiSyncService.java b/lesingle-creation-backend/src/main/java/com/lesingle/modules/leai/service/ILeaiSyncService.java index 264216a..ff744d9 100644 --- a/lesingle-creation-backend/src/main/java/com/lesingle/modules/leai/service/ILeaiSyncService.java +++ b/lesingle-creation-backend/src/main/java/com/lesingle/modules/leai/service/ILeaiSyncService.java @@ -23,4 +23,12 @@ public interface ILeaiSyncService { * @param source 来源标识(用于日志,如 "Webhook[work.status_changed]") */ void syncWork(String remoteWorkId, Map remoteData, String source); + + /** + * PUT 代理成功后,用本次请求体中的元数据直接覆盖本地 t_ugc_work(与乐读派查询解耦,避免远端滞后)。 + * + * @param remoteWorkId 乐读派作品 ID + * @param proxyRequestBody 前端传入的 JSON(通常含 author、subtitle、intro、tags 等,不含 orgId/phone) + */ + void applyLocalMetadataFromProxyPut(String remoteWorkId, Map proxyRequestBody); } diff --git a/lesingle-creation-backend/src/main/java/com/lesingle/modules/leai/service/LeaiSyncService.java b/lesingle-creation-backend/src/main/java/com/lesingle/modules/leai/service/LeaiSyncService.java index 75fdfa0..8437d87 100644 --- a/lesingle-creation-backend/src/main/java/com/lesingle/modules/leai/service/LeaiSyncService.java +++ b/lesingle-creation-backend/src/main/java/com/lesingle/modules/leai/service/LeaiSyncService.java @@ -29,6 +29,8 @@ import java.util.*; @RequiredArgsConstructor public class LeaiSyncService implements ILeaiSyncService { + private static final String SOURCE_PROXY_UPDATE = "Proxy[update.work]"; + private final UgcWorkMapper ugcWorkMapper; private final UgcWorkPageMapper ugcWorkPageMapper; private final LeaiApiClient leaiApiClient; @@ -90,6 +92,16 @@ public class LeaiSyncService implements ILeaiSyncService { return; } + // PUT /update/work 仅改作者/副标题/简介时乐读派 status 不变,不走「状态前进」;需单独同步元数据 + if (remoteStatus >= LeaiCreationStatus.CATALOGED + && remoteStatus == localLeaiStatus + && source != null + && source.contains(SOURCE_PROXY_UPDATE)) { + applyMetadataFromRemote(localWork, remoteData); + log.info("[{}] 元数据同步(状态未变) remoteWorkId={}", source, remoteWorkId); + return; + } + // 旧数据或重复推送,忽略状态更新 // 但如果 remoteStatus >= 3 且本地缺少页面数据,需要补充拉取 if (remoteStatus >= LeaiCreationStatus.COMPLETED && !hasPages(localWork.getId())) { @@ -370,6 +382,94 @@ public class LeaiSyncService implements ILeaiSyncService { } } + /** + * 乐读派编辑元数据后 status 不变时,将 title/author/副标题与简介 写入本地(无 leaiStatus CAS) + */ + private void applyMetadataFromRemote(UgcWork work, Map remoteData) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.eq(UgcWork::getId, work.getId()); + boolean any = false; + if (remoteData.containsKey("title")) { + wrapper.set(UgcWork::getTitle, LeaiUtil.toString(remoteData.get("title"), null)); + any = true; + } + if (remoteData.containsKey("author")) { + wrapper.set(UgcWork::getAuthorName, LeaiUtil.toString(remoteData.get("author"), null)); + any = true; + } + if (remoteData.containsKey("intro") || remoteData.containsKey("subtitle")) { + String sub = remoteData.containsKey("subtitle") + ? LeaiUtil.toString(remoteData.get("subtitle"), "") + : ""; + String intro = remoteData.containsKey("intro") + ? LeaiUtil.toString(remoteData.get("intro"), "") + : ""; + String desc; + if (sub.isEmpty()) { + desc = intro; + } else if (intro.isEmpty()) { + desc = sub; + } else { + desc = sub + "\n" + intro; + } + desc = desc.trim(); + wrapper.set(UgcWork::getDescription, desc.isEmpty() ? null : desc); + any = true; + } + if (!any) { + return; + } + wrapper.set(UgcWork::getModifyTime, LocalDateTime.now()); + ugcWorkMapper.update(null, wrapper); + } + + @Override + public void applyLocalMetadataFromProxyPut(String remoteWorkId, Map proxyRequestBody) { + if (remoteWorkId == null || remoteWorkId.isEmpty() || proxyRequestBody == null || proxyRequestBody.isEmpty()) { + return; + } + UgcWork work = findByRemoteWorkId(remoteWorkId); + if (work == null) { + return; + } + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.eq(UgcWork::getId, work.getId()); + boolean any = false; + if (proxyRequestBody.containsKey("author")) { + wrapper.set(UgcWork::getAuthorName, LeaiUtil.toString(proxyRequestBody.get("author"), null)); + any = true; + } + if (proxyRequestBody.containsKey("title")) { + wrapper.set(UgcWork::getTitle, LeaiUtil.toString(proxyRequestBody.get("title"), null)); + any = true; + } + if (proxyRequestBody.containsKey("intro") || proxyRequestBody.containsKey("subtitle")) { + String sub = proxyRequestBody.containsKey("subtitle") + ? LeaiUtil.toString(proxyRequestBody.get("subtitle"), "") + : ""; + String intro = proxyRequestBody.containsKey("intro") + ? LeaiUtil.toString(proxyRequestBody.get("intro"), "") + : ""; + String desc; + if (sub.isEmpty()) { + desc = intro; + } else if (intro.isEmpty()) { + desc = sub; + } else { + desc = sub + "\n" + intro; + } + desc = desc.trim(); + wrapper.set(UgcWork::getDescription, desc.isEmpty() ? null : desc); + any = true; + } + if (!any) { + return; + } + wrapper.set(UgcWork::getModifyTime, LocalDateTime.now()); + ugcWorkMapper.update(null, wrapper); + log.info("[ProxyPut] 本地元数据已覆盖 remoteWorkId={}", remoteWorkId); + } + /** * 通过 remoteWorkId 查找本地作品 */ diff --git a/lesingle-creation-frontend/src/views/contests/judges/Index.vue b/lesingle-creation-frontend/src/views/contests/judges/Index.vue index 8ac4ce4..7bcfbdb 100644 --- a/lesingle-creation-frontend/src/views/contests/judges/Index.vue +++ b/lesingle-creation-frontend/src/views/contests/judges/Index.vue @@ -4,34 +4,35 @@