library-picturebook-activity/lesingle-aicreate-backend-demo/企业同步创作数据_核心三步_V4.0(1).md
En 98e9ad1d28 feat(前端): 测试环境登录框支持自动填充测试账号
通过 VITE_AUTO_FILL_TEST 环境变量控制,在 .env.test 中启用,
使测试环境构建后登录框也能自动填充测试账号,方便测试人员使用。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 17:03:22 +08:00

14 KiB
Raw Permalink Blame History

企业同步创作数据 — 核心三步

基于《AI绘本创作系统 企业后端集成指南 V4.0》,提炼企业同步乐读派创作数据的核心实现路径。

三层防线共用同一套同步判断规则,区别仅在于触发方式和时机。


前置知识5步状态机与同步判断规则

状态定义

数值 状态名 含义
-1 FAILED 创作失败(异常状态)
1 PENDING 已提交,排队等待
2 PROCESSING AI创作中进度变化
3 COMPLETED 图片完成,待编目
4 CATALOGED 编目完成,待配音
5 DUBBED 配音完成(乐读派终态)

正常流转:1 → 2 → 3 → 4 → 5,严格单向递增,不可回退。

统一同步判断规则(三步通用)

if (本地无记录)           → INSERT新作品
if (remote_status == -1)  → 强制UPDATE失败通知无条件处理
if (remote_status == 2)   → 强制UPDATE进度变化无条件更新
if (remote_status > local_status) → 全量UPDATE状态前进
其他                      → SKIP旧数据/重复,忽略)

重点PROCESSING(2) 和 FAILED(-1) 是特殊状态,不参与数值大小比较,收到即强制更新。


第一步Webhook 回调推送(实时主通道)

作用

乐读派后端在作品状态变更时,主动 POST 推送到企业配置的 Webhook URL实现秒级数据同步。

触发时机

每次 status 发生变化时自动推送,包括:

  • 状态前进1→2、2→3、3→4、4→5
  • 创作失败any→-1

两种事件类型

事件 触发时机 频率 说明
work.status_changed 状态变更 每次状态变更1次 核心事件,携带完整作品数据(含 page_list
work.progress PROCESSING 阶段进度变化 多次/作品 进度里程碑推送10%/30%/50%/70%/90%

Webhook 请求格式

POST {企业webhook_url}
Content-Type: application/json
X-Webhook-Id: evt_190368671438289
X-Webhook-Event: work.status_changed
X-Webhook-Timestamp: 1712000000000
X-Webhook-Signature: HMAC-SHA256=a3f8c2d1...

企业处理逻辑Java 伪代码)

@Transactional
public void handleWebhook(JSONObject data) {
    String workId = data.getString("work_id");
    int remoteStatus = data.getIntValue("status");

    // 1. 幂等去重(用 event_id防止重复处理
    if (processedEvents.contains(eventId)) return;

    // 2. 查本地记录(建议 SELECT ... FOR UPDATE 行锁)
    WorkRecord local = db.selectForUpdate(workId);

    // 3. 新作品 → 直接入库
    if (local == null) {
        db.insert(buildRecord(data));
        return;
    }

    // 4. FAILED(-1) → 强制更新,无条件
    if (remoteStatus == -1) {
        local.setStatus(-1);
        local.setFailReason(data.getString("fail_reason"));
        db.update(local);
        return;
    }

    // 5. PROCESSING(2) → 强制更新进度,无条件
    if (remoteStatus == 2) {
        local.setProgress(data.getIntValue("progress"));
        local.setProgressMessage(data.getString("progress_message"));
        db.update(local);
        return;
    }

    // 6. 状态前进 → 全量覆盖
    if (remoteStatus > local.getStatus()) {
        updateAllFields(local, data); // 更新 title/author/page_list 等
        db.update(local);
        return;
    }

    // 7. 旧数据/重复 → 忽略
    log.info("skip: remote={} <= local={}", remoteStatus, local.getStatus());
}

各状态下企业应保存的关键数据

状态 企业需保存的字段 用途
1 PENDING work_id, phone, org_id, style, original_image_url 创建本地记录,关联企业用户
2 PROCESSING progress, progress_message 显示创作进度(可选)
3 COMPLETED title, pages, page_list含 image_url 作品图片已生成,可预览展示
4 CATALOGED title, author, subtitle, intro, tags 用户填写的编目信息
5 DUBBED page_list含 audio_url 配音URL已填充作品完整可用
-1 FAILED fail_reason 记录失败原因,通知用户

签名验证(必须实现)

签名体 = "{X-Webhook-Id}.{X-Webhook-Timestamp}.{请求body原文}"
期望签名 = HMAC-SHA256(签名体, app_secret).toHex()

安全检查清单:

  • 时间窗口|当前时间 - X-Webhook-Timestamp| ≤ 5分钟,防重放
  • 幂等去重:用 X-Webhook-Id 记录已处理事件
  • 常量时间比较:用 MessageDigest.isEqual() 而非 .equals(),防时序攻击
  • 原始body:签名时使用 HTTP body 原文,不要反序列化再序列化

重试策略

首次发送 + 5次重试 = 共6次尝试。

延迟间隔:10s → 30s → 2min → 10min → 30min → 30min

全部失败后需通过第二步 B3 批量拉取兜底。


第二步B3 批量查询兜底(定时对账)

作用

Webhook 可能因网络问题、企业服务宕机等原因丢失。B3 定时批量拉取作为兜底通道,确保数据最终一致

接口信息

GET /api/v1/query/works?orgId=ORG001&updatedAfter=2026-04-05T00:00:00
认证方式HMAC-SHA256 签名

返回指定时间之后有变更的所有作品列表。

建议配置

配置项 推荐值 说明
对账频率 每 30 分钟 不低于 15 分钟,避免对 API 造成查询压力
查询范围 最近 2 小时 覆盖 2 个对账周期,防止边界遗漏

企业实现伪代码Java

@Scheduled(fixedRate = 30 * 60 * 1000) // 每30分钟执行
public void reconcile() {
    // 查询最近2小时内有变更的作品
    String since = twoHoursAgo.format(ISO_FORMAT);
    List<RemoteWork> remoteList = callB3(since);

    for (RemoteWork remote : remoteList) {
        WorkRecord local = db.get(remote.workId);

        // 同步判断规则与 Webhook 完全一致
        if (local == null
            || remote.status > local.getStatus()
            || remote.status == 2
            || remote.status == -1) {
            db.upsert(remote);
        }
    }
    lastSyncTime = now();
}

B3 调用签名示例

Map<String, String> params = new LinkedHashMap<>();
params.put("orgId", ORG_ID);
params.put("updatedAfter", "2026-04-05T00:00:00");

// 生成 HMAC 签名头4个 Header
Map<String, String> headers = buildHmacHeaders(params, ORG_ID, APP_SECRET);

String url = API_URL + "/api/v1/query/works?orgId=" + ORG_ID
    + "&updatedAfter=" + URLEncoder.encode("2026-04-05T00:00:00", "UTF-8");

// 注意:传 URI 对象,避免 RestTemplate 双重编码
restTemplate.getForObject(URI.create(url), String.class);

并发安全Webhook + B3 同时触发)

推荐两种方案防止同一作品被并发更新:

-- 方案1: 行锁(推荐,简单可靠)
SELECT * FROM enterprise_work WHERE work_id = ? FOR UPDATE;
-- 然后按同步规则判断和更新

-- 方案2: CAS 乐观锁(无锁,适合高并发)
UPDATE enterprise_work
SET status = #{remoteStatus}, title = #{title}, page_list = #{pageList}
WHERE work_id = #{workId}
  AND (status < #{remoteStatus} OR #{remoteStatus} = 2 OR #{remoteStatus} = -1);
-- rows=0 表示被其他线程抢先更新了,安全忽略

第三步:详情页进入时强制 B2 拉取(用户触发兜底)

作用

当用户点击进入作品详情页,且本地状态尚未完成(status < 3),企业强制调用 B2 单条查询拉取最新数据,确保用户看到的是最新状态。

为什么需要这一步

  • status 1/2 期间状态变化最快(排队 → 创作中 → 完成),是 Webhook 丢失的高风险窗口
  • B3 对账有 30 分钟延迟,用户可能在此期间打开详情页
  • 用户主动触发,即时补偿,体验最佳

触发条件

用户点击作品详情页 && local_status < 3

status >= 3 后变化由用户主动操作驱动(编目/配音Webhook + B3 已足够覆盖,无需额外拉取。

接口信息

GET /api/v1/query/work/{workId}?orgId=ORG001
认证方式HMAC-SHA256 签名 或 Session Token

企业实现伪代码Java

/**
 * 用户点击作品详情页时调用
 */
public WorkRecord getWorkDetail(String workId) {
    WorkRecord local = db.get(workId);

    // 本地状态 < 3PENDING 或 PROCESSING强制从乐读派拉取最新
    if (local != null && local.getStatus() < 3) {
        RemoteWork remote = callB2(workId);

        // 同步判断规则与 Webhook、B3 完全一致
        if (remote.status > local.getStatus()
            || remote.status == 2
            || remote.status == -1) {
            updateAllFields(local, remote);
            db.update(local);
        }
    }

    // 返回最新的本地记录,渲染详情页
    return db.get(workId);
}

前端配合(可选)

如果企业有自建 C 端,可在作品列表页面根据 status 做路由跳转:

switch (work.status) {
    case 1: case 2:  // 排队/创作中
        navigate('/creating/' + workId);   // → 进度等待页
        break;
    case 3:          // 图片完成
        navigate('/catalog/' + workId);    // → 编目编辑页(强制)
        break;
    case 4:          // 编目完成
        navigate('/dubbing/' + workId);    // → 配音编辑页(强制)
        break;
    case 5:          // 配音完成(终态)
        navigate('/reader/' + workId);     // → 阅读页
        break;
    case -1:         // 失败
        showError(work.failReason);
        break;
}

三步协同总览

┌──────────────────────────────────────────────────────────────────┐
│                    企业同步三层防线                                │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  第一步  Webhook 推送         秒级实时 ← 主通道(可能丢失)       │
│                ↓ 漏网之鱼                                        │
│  第二步  B3 定时对账          30分钟兜底 ← 保障通道(高可靠)     │
│                ↓ 用户等不及                                      │
│  第三步  B2 详情页强制拉取    用户触发 ← 即时补偿status<3时   │
│                                                                  │
├──────────────────────────────────────────────────────────────────┤
│  三层共用统一同步规则:                                            │
│  · remote > local        → 全量更新                              │
│  · remote == 2 (进度)    → 强制更新                              │
│  · remote == -1 (失败)   → 强制更新                              │
│  · 其他                  → 忽略                                  │
└──────────────────────────────────────────────────────────────────┘

数据流对比

维度 第一步 Webhook 第二步 B3 对账 第三步 B2 强制拉取
触发方式 乐读派主动推送 企业定时轮询 用户打开详情页
实时性 秒级 30分钟级 即时
可靠性 可能丢失 高可靠 高可靠
触发条件 状态变更时自动 定时任务 local_status < 3
数据范围 单条作品 时间范围内批量 单条作品
认证方式 签名验证(被动接收) HMAC 签名(主动请求) HMAC/Token主动请求

企业推荐表结构

CREATE TABLE `enterprise_work` (
    `work_id`            VARCHAR(32) PRIMARY KEY,
    `status`             INT NOT NULL DEFAULT 0   COMMENT '状态: 乐读派1-5, 企业>=6',
    `progress`           INT DEFAULT 0            COMMENT '创作进度0-100',
    `title`              VARCHAR(200),
    `author`             VARCHAR(50),
    `tags`               JSON,
    `page_list`          JSON                     COMMENT '页面数据含image_url/audio_url',
    `original_image_url` VARCHAR(500)             COMMENT '用户原创作品图片URL',
    `phone`              VARCHAR(20)              COMMENT '用户手机号(关联企业用户)',
    `fail_reason`        VARCHAR(500),
    `webhook_event_id`   VARCHAR(64)              COMMENT '最近一次webhook事件ID幂等去重',
    `synced_at`          DATETIME                 COMMENT '最近同步时间',
    `created_at`         DATETIME DEFAULT CURRENT_TIMESTAMP,
    `updated_at`         DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX `idx_status` (`status`),
    INDEX `idx_phone` (`phone`)
);

管理后台配置项

企业需在乐读派管理后台「机构管理」中配置以下 2 项:

配置项 填写内容 说明
Webhook URL https://你的域名/webhook/leai 接收作品状态变更推送(第一步)
认证回调URL https://你的域名/leai-auth H5 token 失效后跳回重新认证

完整接口规范、签名算法、代码示例请参考《AI绘本创作系统 企业后端集成指南 V4.0》及随附的 enterprise-demo/java-demo/ 目录。