From 56bddb52065f48cf55c3de86d8652b1226cc4537 Mon Sep 17 00:00:00 2001 From: zhonghua Date: Mon, 13 Apr 2026 17:16:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20EditInfo=E7=9B=B4=E6=8E=A5=E5=8F=91?= =?UTF-8?q?=E5=B8=83=E8=81=94=E8=B0=83=E3=80=81=E4=B9=90=E8=AF=BB=E6=B4=BE?= =?UTF-8?q?PUT=E5=90=8E=E5=90=8C=E6=AD=A5UGC=E3=80=81=E5=88=9B=E4=BD=9C?= =?UTF-8?q?=E6=B5=81=E7=A8=8Bkeep-alive=E6=BF=80=E6=B4=BB=E5=88=B7?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made-with: Cursor --- .../design/public/ugc-work-status-redesign.md | 12 +++-- .../leai/controller/LeaiProxyController.java | 44 ++++++++++++++- .../modules/leai/service/LeaiSyncService.java | 8 +-- .../public/create/views/BookReaderView.vue | 10 ++-- .../public/create/views/CharactersView.vue | 8 ++- .../public/create/views/CreatingView.vue | 14 ++++- .../views/public/create/views/DubbingView.vue | 4 +- .../public/create/views/EditInfoView.vue | 53 ++++++++++++++++--- .../views/public/create/views/PreviewView.vue | 22 ++------ .../public/create/views/SaveSuccessView.vue | 11 ++-- .../public/create/views/StyleSelectView.vue | 6 ++- .../views/public/create/views/UploadView.vue | 11 ++-- 12 files changed, 148 insertions(+), 55 deletions(-) diff --git a/docs/design/public/ugc-work-status-redesign.md b/docs/design/public/ugc-work-status-redesign.md index 689bbfe..d53fd16 100644 --- a/docs/design/public/ugc-work-status-redesign.md +++ b/docs/design/public/ugc-work-status-redesign.md @@ -75,11 +75,11 @@ draft / pending_review / published / rejected └───────┬───────┘ │ │ 用户在 EditInfoView 点 - │ 保存 / 去配音 / 立即发布 - │ 任意按钮 = 配音完成 + │ 保存 / 去配音 / 直接发布 + │ 编目完成(leai=CATALOGED)后同步为未发布 ▼ ┌───────────────┐ - │ UNPUBLISHED │ 成品私有:已配音,可发布 + │ UNPUBLISHED │ 成品私有:已编目,配音可选 │ 未发布 │◀──────────────┐ └───────┬───────┘ │ │ │ @@ -111,7 +111,7 @@ draft / pending_review / published / rejected | 起始状态 | 触发 | 目标状态 | 触发方 | 接口 | |---|---|---|---|---| | (无) | 创建作品 | DRAFT | 系统 | leai 创作流程内部 | -| DRAFT | 配音完成(leai status → DUBBED) | UNPUBLISHED | 系统(webhook 同步) | LeaiSyncService | +| DRAFT | 编目完成(leai status → **CATALOGED**,配音为可选) | UNPUBLISHED | 系统(Webhook 或 `PUT /leai-proxy/work/{id}` 后 `LeaiSyncService` 同步) | LeaiSyncService | | UNPUBLISHED | 用户点「公开发布」 | PENDING_REVIEW | 用户 | `POST /public/works/{id}/publish` | | UNPUBLISHED | 用户补充配音/编辑 | UNPUBLISHED | 用户 | 更新内容接口 | | PENDING_REVIEW | 审核通过 | PUBLISHED | 超管 | `POST /content-review/works/{id}/approve` | @@ -121,6 +121,8 @@ draft / pending_review / published / rejected | PUBLISHED | 超管强制下架 | UNPUBLISHED 或 TAKEN_DOWN | 超管 | `POST /content-review/works/{id}/takedown` | | REJECTED | 改完重交 | PENDING_REVIEW | 用户 | `POST /public/works/{id}/publish` | +**实现说明(与代码对齐)**:`LeaiSyncService` 在乐读派进度前进至 ≥ `CATALOGED` 时,若本地 `status` 仍为 `draft`,则写入 `unpublished`。`PUT /leai-proxy/work/{id}` 代理在乐读派返回成功后主动调用 `syncWork`,减少仅依赖 Webhook 的延迟,便于「直接发布」单次提交审核。 + ### 2.3 状态可见性矩阵 | 状态 | 用户作品库可见 | 用户详情页可见 | 发现页可见 | 超管端可见 | @@ -407,7 +409,7 @@ CREATE INDEX idx_ugc_work_leai_status ON t_ugc_work(leai_status); | **本地发布状态** | 我们自己定义的作品发布状态 | 字符串,5 个值 | | **CATALOGED** | leai 状态 4,编目完成 | 用户在 EditInfoView 保存信息后到达 | | **DUBBED** | leai 状态 5,配音完成 | 配音是可选步骤 | -| **未发布 unpublished** | 本次新增状态 | 已编目完成但未公开 | +| **未发布 unpublished** | 本次新增状态 | 乐读派进度 ≥ CATALOGED(4) 且本地同步后,用户尚未提交审核 | ## 附录 B:状态命名为什么是 `unpublished` 而不是 `private` 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 6a529ce..59fb43e 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 @@ -3,7 +3,10 @@ package com.lesingle.modules.leai.controller; import com.lesingle.common.exception.BusinessException; import com.lesingle.common.util.SecurityUtil; import com.lesingle.modules.leai.config.LeaiConfig; +import com.lesingle.modules.leai.service.ILeaiSyncService; import com.lesingle.modules.leai.service.LeaiApiClient; +import com.lesingle.modules.leai.util.LeaiUtil; +import com.fasterxml.jackson.core.type.TypeReference; import com.lesingle.modules.sys.entity.SysUser; import com.lesingle.modules.sys.service.ISysUserService; import com.fasterxml.jackson.databind.ObjectMapper; @@ -35,6 +38,7 @@ public class LeaiProxyController { private final LeaiConfig leaiConfig; private final ISysUserService sysUserService; private final ObjectMapper objectMapper; + private final ILeaiSyncService leaiSyncService; /** * 获取当前用户手机号,校验非空 @@ -152,9 +156,45 @@ public class LeaiProxyController { @PutMapping("/work/{id}") @Operation(summary = "编辑作品代理") public ResponseEntity proxyUpdateWork(@PathVariable String id, @RequestBody Map requestBody) { - Map body = new HashMap<>(requestBody); + Map body = buildBaseBody(); + body.putAll(requestBody); log.info("[乐读派代理] 编辑作品, workId={}", id); - return jsonOk(leaiApiClient.proxyPut("/update/work/" + id, body)); + String responseBody = leaiApiClient.proxyPut("/update/work/" + id, body); + syncLocalAfterLeaiUpdate(id, responseBody); + return jsonOk(responseBody); + } + + /** + * 解析乐读派 update 响应并同步本地 t_ugc_work,避免仅依赖 Webhook 导致发布前状态滞后 + */ + private void syncLocalAfterLeaiUpdate(String remoteWorkId, String responseJson) { + if (responseJson == null || responseJson.isEmpty()) { + return; + } + 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]"); + } catch (Exception e) { + log.warn("[乐读派代理] 编辑作品后同步本地失败: remoteWorkId={}", remoteWorkId, e); + } } /** 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 d915e16..75fdfa0 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 @@ -109,8 +109,8 @@ public class LeaiSyncService implements ILeaiSyncService { work.setTitle(LeaiUtil.toString(remoteData.get("title"), "未命名作品")); int leaiStatus = LeaiUtil.toInt(remoteData.get("status"), LeaiCreationStatus.PENDING); work.setLeaiStatus(leaiStatus); - // 本地发布状态:创作进度 >= DUBBED 时自动设为 unpublished,否则为 draft - work.setStatus(leaiStatus >= LeaiCreationStatus.DUBBED + // 本地发布状态:编目完成(CATALOGED)起视为可入库「未发布」,与 EditInfo 保存/直接发布一致 + work.setStatus(leaiStatus >= LeaiCreationStatus.CATALOGED ? WorkPublishStatus.UNPUBLISHED.getValue() : WorkPublishStatus.DRAFT.getValue()); work.setVisibility(Visibility.PRIVATE.getValue()); @@ -228,8 +228,8 @@ public class LeaiSyncService implements ILeaiSyncService { .lt(UgcWork::getLeaiStatus, remoteStatus) .set(UgcWork::getLeaiStatus, remoteStatus); - // 当 leaiStatus 推进到 DUBBED 且当前 status 仍为 draft → 自动设 status 为 unpublished - if (remoteStatus >= LeaiCreationStatus.DUBBED + // 编目完成(CATALOGED)起且当前仍为 draft → 升为 unpublished(配音为可选步骤) + if (remoteStatus >= LeaiCreationStatus.CATALOGED && WorkPublishStatus.DRAFT.getValue().equals(work.getStatus())) { wrapper.set(UgcWork::getStatus, WorkPublishStatus.UNPUBLISHED.getValue()); } diff --git a/lesingle-creation-frontend/src/views/public/create/views/BookReaderView.vue b/lesingle-creation-frontend/src/views/public/create/views/BookReaderView.vue index 8b34dd0..dfca943 100644 --- a/lesingle-creation-frontend/src/views/public/create/views/BookReaderView.vue +++ b/lesingle-creation-frontend/src/views/public/create/views/BookReaderView.vue @@ -93,7 +93,7 @@ export default { name: 'BookReaderView' }