library-picturebook-activity/lesingle-aicreate-client/企业定制对接指南_V3.1.md
2026-04-03 20:55:51 +08:00

803 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# AI 绘本创作系统 — 企业定制对接指南 V3.1
> 版本: V3.1 | 更新日期: 2026-04-03
> 适用客户: 自有 C端 H5 + 管理后台,需嵌入乐读派 AI 创作能力
---
## 一、整体架构
```
┌─────────────────────────────────────────────────────────────┐
│ 客户自有 C端 H5 │
│ ┌──────┐ ┌──────────────┐ ┌──────┐ ┌──────┐ │
│ │ 广场 │ │ 创作(iframe)│ │ 作品 │ │ 我的 │ │
│ │ │ │ ┌──────────┐ │ │ │ │ │ │
│ │ 优秀 │ │ │乐读派H5 │ │ │ AI作品│ │ 个人 │ │
│ │ 作品 │ │ │上传→提取 │ │ │ + │ │ 设置 │ │
│ │ 展示 │ │ │→画风→创作│ │ │ 自有 │ │ │ │
│ │ │ │ │→预览→配音│ │ │ 作品 │ │ │ │
│ │ │ │ └──────────┘ │ │ │ │ │ │
│ └──────┘ └──────────────┘ └──────┘ └──────┘ │
│ ↑ ↑ │
│ │ 读取客户DB 读取客户DB │
└─────┼─────────────────────────────────┼────────────────────┘
│ │
┌─────┼─────────────────────────────────┼────────────────────┐
│ │ 客户后端 │ │
│ │ │ │
│ 客户DB ←──── Webhook回调 ←──── 乐读派后端 │
│ (AI作品 (创作完成后实时推送) │
│ + 自有作品) │
└────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│ Android APK乐读派打包提供
│ 创作流程 → 完成 → Webhook回调 → 客户后端 │
│ "我的作品" → 调客户提供的API │
└────────────────────────────────────────────────────────────┘
```
### 职责划分
| 功能 | 负责方 | 说明 |
|------|--------|------|
| AI 创作 H5 页面 | **乐读派** | 提供完整创作流程,客户 iframe 嵌入 |
| AI 创作后端 API | **乐读派** | A6 角色提取、A3 故事创作、A20 配音等 |
| Android APK | **乐读派** | 打包发布,客户提供"我的作品"接口即可 |
| 广场 / 作品库 / 我的 | **客户** | 客户自有 H5 + 后端 |
| 作品管理后台 | **客户** | 客户自有管理后台 |
| Webhook 接收 | **客户** | 接收乐读派推送的创作结果 |
| 数据存储 | **客户** | AI 作品数据存入客户自己的 DB |
---
## 二、对接前准备
### 2.1 乐读派提供
以下信息由乐读派管理后台创建机构后生成,正式对接时填入:
| 项目 | 值 | 说明 |
|------|------|------|
| orgId机构ID | `__________` | 机构唯一标识,所有 API 调用和数据归属依据此 ID |
| appSecret机构密钥 | `__________` | API 认证密钥,**严禁泄露**,仅存于客户服务端 |
| H5 创作页地址 | `__________` | 乐读派 H5 前端 URLiframe src 用) |
| API 服务地址 | `__________` | 乐读派后端 API 基地址 |
| Android APK | 另行交付 | 已内置上述配置的签名发布包 |
| 创作额度 | `__________` 次/周期 | 机构总创作额度(管理后台可调整) |
> **重要**:以上所有 `__________` 空白项将在正式开通机构后由乐读派填入并发送给客户。请勿使用测试值上线。
### 2.2 客户提供
| 项目 | 内容 | 说明 |
|------|------|------|
| Webhook 接收 URL | `https://客户域名/webhook/leai` | HTTPS5 秒内返回 200 |
| H5 嵌入域名 | `https://客户h5域名` | 用于 CORS 和 iframe 白名单 |
| 机构查询接口Android用 | `GET /api/org/by-device?mac=xx` | 根据设备MAC返回orgId见 6.2 |
| 我的作品接口Android用 | `GET /api/my-works?orgId=xx&phone=xx` | 返回作品列表+详情(见 6.3 |
---
## 三、C端 H5 嵌入iframe 方案)
### 3.1 嵌入原理
客户的"创作"Tab 内放一个 iframe加载乐读派 H5 创作页面。创作完成后,乐读派 H5 通过 `postMessage` 通知客户父页面。
```
客户H5页面 乐读派H5(iframe内)
│ │
│ 1. 客户后端换取 sessionToken │
│ │
│ 2. iframe.src = 乐读派H5 │
│ + token + orgId + phone │
│ ──────────────────────────────→ │
│ │
│ 用户在iframe内创作... │
│ │
│ 3. 创作完成: postMessage │
│ ←────────────────────────────── │
│ {type:'WORK_CREATED', │
│ workId:'xxx'} │
│ │
│ 4. 同时: Webhook推送到客户后端 │
│ │
│ 5. 客户刷新作品列表 │
```
### 3.2 客户 H5 嵌入代码(完整示例,可直接使用)
```html
<!-- 客户的"创作"Tab页面 -->
<template>
<div class="create-tab">
<!-- 乐读派创作iframe -->
<iframe
v-if="iframeSrc"
:src="iframeSrc"
ref="creationFrame"
class="creation-iframe"
allow="camera;microphone"
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-modals"
/>
<div v-else class="loading">正在加载创作工具...</div>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
import axios from 'axios'
// ★★★ 替换为乐读派提供的值(见第二章配置表)★★★
const LEAI_H5_URL = '__________ /* 乐读派H5创作页地址 */'
const CREATE_TOKEN_API = '/api/create-token' // 客户自己的后端接口见3.3
// ★★★ 替换结束 ★★★
const iframeSrc = ref('')
onMounted(async () => {
try {
// 1. 调客户自己的后端,获取 sessionToken
// 客户后端内部会调乐读派的 /api/v1/auth/session
const { data } = await axios.post(CREATE_TOKEN_API, {
phone: getCurrentUserPhone() // 从客户登录态获取当前用户手机号
})
// 2. 拼接 iframe URL
iframeSrc.value = `${LEAI_H5_URL}/?token=${data.token}&orgId=${data.orgId}&phone=${data.phone}&embed=1`
} catch (e) {
console.error('获取创作令牌失败', e)
}
// 3. 监听 postMessage创作完成通知
window.addEventListener('message', onCreationMessage)
})
onBeforeUnmount(() => {
window.removeEventListener('message', onCreationMessage)
})
function onCreationMessage(event) {
// 安全校验只处理来自乐读派H5的消息
if (!event.origin.includes('leai')) return
const msg = event.data
if (msg?.type === 'WORK_CREATED') {
// 创作完成workId 可用于跳转到作品详情
console.log('新作品创建成功:', msg.workId)
// 客户可以:刷新作品列表 / 跳转到作品Tab / 显示成功提示
refreshMyWorks()
}
}
function getCurrentUserPhone() {
// ★ 替换为客户自己的获取当前登录用户手机号的逻辑
return '13800001111'
}
function refreshMyWorks() {
// ★ 替换为客户自己的刷新作品列表逻辑
}
</script>
<style scoped>
.create-tab {
width: 100%;
height: 100%;
}
.creation-iframe {
width: 100%;
height: 100%;
border: none;
}
</style>
```
### 3.3 客户后端:令牌交换接口(完整示例)
客户后端需要实现一个接口,内部调用乐读派的令牌交换 API
**Java (Spring Boot):**
```java
@RestController
public class LeAiController {
// ★★★ 替换为乐读派提供的值(见第二章配置表)★★★
private static final String LEAI_API = "__________"; // API服务地址
private static final String ORG_ID = "__________"; // 机构ID
private static final String APP_SECRET = "__________"; // 机构密钥
private final RestTemplate restTemplate = new RestTemplate();
/**
* 客户前端调这个接口获取创作令牌
* POST /api/create-token
* Body: { "phone": "13800001111" }
*/
@PostMapping("/api/create-token")
public Map<String, String> createToken(@RequestBody Map<String, String> req) {
String phone = req.get("phone");
// 调乐读派令牌交换接口
Map<String, String> body = new HashMap<>();
body.put("orgId", ORG_ID);
body.put("appSecret", APP_SECRET);
body.put("phone", phone);
Map response = restTemplate.postForObject(
LEAI_API + "/api/v1/auth/session", body, Map.class);
Map data = (Map) response.get("data");
// 返回给前端
Map<String, String> result = new HashMap<>();
result.put("token", (String) data.get("sessionToken"));
result.put("orgId", ORG_ID);
result.put("phone", phone);
return result;
}
}
```
**Python (Flask):**
```python
import requests
from flask import Flask, request, jsonify
LEAI_API = "__________" # ★ API服务地址见第二章配置表
ORG_ID = "__________" # ★ 机构ID
APP_SECRET = "__________" # ★ 机构密钥
app = Flask(__name__)
@app.route("/api/create-token", methods=["POST"])
def create_token():
phone = request.json["phone"]
res = requests.post(f"{LEAI_API}/api/v1/auth/session", json={
"orgId": ORG_ID, "appSecret": APP_SECRET, "phone": phone
})
token = res.json()["data"]["sessionToken"]
return jsonify({"token": token, "orgId": ORG_ID, "phone": phone})
```
**Node.js (Express):**
```javascript
const axios = require('axios')
const LEAI_API = '__________' // ★ API服务地址见第二章配置表
const ORG_ID = '__________' // ★ 机构ID
const APP_SECRET = '__________' // ★ 机构密钥
app.post('/api/create-token', async (req, res) => {
const { phone } = req.body
const { data } = await axios.post(`${LEAI_API}/api/v1/auth/session`, {
orgId: ORG_ID, appSecret: APP_SECRET, phone
})
res.json({ token: data.data.sessionToken, orgId: ORG_ID, phone })
})
```
### 3.4 iframe 嵌入注意事项
| 事项 | 说明 |
|------|------|
| CORS 白名单 | 联系乐读派将客户 H5 域名加入 `allowed_origins` |
| HTTPS 必须 | iframe 父页面和乐读派 H5 都必须是 HTTPS |
| `embed=1` 参数 | 告诉乐读派 H5 处于嵌入模式(隐藏返回按钮等) |
| Token 有效期 | 2 小时建议每次打开创作Tab时重新获取 |
| 相机权限 | iframe 需要 `allow="camera"` 属性才能拍照上传 |
---
## 四、Webhook 数据同步
### 4.1 同步机制全景图
```
用户在iframe中创作
乐读派后端完成AI生成
├──→ postMessage通知iframe父页面即时用于前端刷新
└──→ Webhook POST到客户后端1-3秒用于数据持久化
客户后端接收
├── 验签(确认来自乐读派)
├── 解析作品数据(标题/图片/音频/文字)
├── 存入客户DB供 广场/作品库 使用)
└── 返回200
```
### 4.2 客户需要实现的 Webhook 接口
**只需要一个 POST 端点:**
```
POST https://客户域名/webhook/leai
Content-Type: application/json
```
**完整实现示例 (Java Spring Boot):**
```java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Hex;
import java.security.MessageDigest;
@RestController
public class WebhookController {
private static final String APP_SECRET = "__________"; // ★ 机构密钥(见第二章配置表)
/**
* 接收乐读派Webhook回调
* 所有事件类型都走这一个接口
*/
@PostMapping("/webhook/leai")
public Map<String, String> handleWebhook(
@RequestBody String rawBody,
@RequestHeader("X-Webhook-Id") String webhookId,
@RequestHeader("X-Webhook-Timestamp") String timestamp,
@RequestHeader("X-Webhook-Signature") String signatureHeader) {
// 1. 时间窗口检查防重放5分钟有效
long ts = Long.parseLong(timestamp);
if (Math.abs(System.currentTimeMillis() - ts) > 300_000) {
return Map.of("error", "expired");
}
// 2. 验证签名
String signData = webhookId + "." + timestamp + "." + rawBody;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(APP_SECRET.getBytes("UTF-8"), "HmacSHA256"));
String expected = "HMAC-SHA256=" + Hex.encodeHexString(
mac.doFinal(signData.getBytes("UTF-8")));
if (!MessageDigest.isEqual(expected.getBytes(), signatureHeader.getBytes())) {
return Map.of("error", "invalid signature");
}
// 3. 解析事件
JSONObject payload = JSON.parseObject(rawBody);
String eventId = payload.getString("id");
String event = payload.getString("event");
JSONObject data = payload.getJSONObject("data");
// 4. 幂等去重用eventId判断是否已处理
if (isProcessed(eventId)) {
return Map.of("status", "duplicate");
}
// 5. 按事件类型处理
switch (event) {
case "work.completed":
handleWorkCompleted(data);
break;
case "work.updated":
handleWorkUpdated(data);
break;
case "work.audio_updated":
handleAudioUpdated(data);
break;
case "work.failed":
handleWorkFailed(data);
break;
// 其他事件按需处理
}
markProcessed(eventId);
return Map.of("status", "ok");
}
/**
* 作品创作完成 — 最重要的事件
* 存入客户DB供广场和作品库使用
*/
private void handleWorkCompleted(JSONObject data) {
String workId = data.getString("work_id");
String title = data.getString("title");
String author = data.getString("author");
String phone = data.getString("phone"); // 创作者手机号
String style = data.getString("style");
int completionStep = data.getIntValue("completion_step");
int dataVersion = data.getIntValue("data_version");
JSONArray pageList = data.getJSONArray("page_list");
// ★ 存入客户自己的作品表
// dataVersion门卫只有新版本才更新
MyWork local = myWorkRepository.findByWorkId(workId);
if (local != null && dataVersion <= local.getDataVersion()) {
return; // 旧数据,跳过
}
MyWork work = local != null ? local : new MyWork();
work.setWorkId(workId);
work.setTitle(title);
work.setAuthor(author);
work.setPhone(phone);
work.setStyle(style);
work.setStatus("COMPLETED");
work.setCompletionStep(completionStep);
work.setDataVersion(dataVersion);
work.setSource("AI_CREATION"); // 标记来源AI创作区别于客户自有作品
// 存储页面数据
if (pageList != null) {
work.setPageListJson(pageList.toJSONString());
// 封面图第一页的图片URL
if (pageList.size() > 0) {
work.setCoverUrl(pageList.getJSONObject(0).getString("image_url"));
}
}
myWorkRepository.save(work);
}
}
```
### 4.3 Webhook 回调数据格式
**请求头:**
```
X-Webhook-Id: evt_1912345678901234567
X-Webhook-Timestamp: 1712000000000
X-Webhook-Signature: HMAC-SHA256=a3f8c2d1e5b7...
```
**请求体work.completed 事件):**
```json
{
"id": "evt_1912345678901234567",
"event": "work.completed",
"created_at": 1712000000000,
"data": {
"work_id": "1912345678901234567",
"org_id": "ORG001",
"status": "COMPLETED",
"title": "小兔子的冒险",
"author": "小明",
"phone": "13800001111",
"style": "watercolor",
"pages": 6,
"completion_step": 0,
"data_version": 1,
"page_list": [
{"page_num": 0, "text": "小兔子的冒险", "image_url": "https://oss.../p0.png", "audio_url": null},
{"page_num": 1, "text": "在一个阳光明媚的早晨...", "image_url": "https://oss.../p1.png", "audio_url": null}
]
}
}
```
### 4.4 客户 DB 建表参考
```sql
CREATE TABLE my_works (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
work_id VARCHAR(50) UNIQUE NOT NULL, -- 乐读派作品ID
-- 乐读派同步字段Webhook写入不要手动改
title VARCHAR(200),
author VARCHAR(50),
phone VARCHAR(20), -- 创作者
status VARCHAR(20), -- COMPLETED/FAILED
style VARCHAR(50),
completion_step INT DEFAULT 0,
data_version INT NOT NULL DEFAULT 0, -- ★ 同步对比用
page_list_json MEDIUMTEXT, -- 页面JSON
cover_url VARCHAR(500), -- 封面图URL
-- 客户自有字段
source VARCHAR(20) DEFAULT 'AI_CREATION', -- AI_CREATION=AI创作 / USER_UPLOAD=用户自传
is_featured TINYINT DEFAULT 0, -- 是否精选(广场展示)
review_status VARCHAR(20) DEFAULT 'PENDING', -- 审核状态
user_id BIGINT, -- 客户系统的用户ID通过phone关联
created_at DATETIME DEFAULT NOW(),
updated_at DATETIME DEFAULT NOW() ON UPDATE NOW(),
INDEX idx_phone (phone),
INDEX idx_source (source),
INDEX idx_featured (is_featured)
);
```
### 4.5 签名验证依赖
Java 项目需要添加 Apache Commons Codec
```xml
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.16.0</version>
</dependency>
```
---
## 五、postMessage 通信协议
### 5.1 乐读派 H5 → 客户父页面
当用户在 iframe 中完成创作流程,乐读派 H5 会通过 `window.parent.postMessage` 发送以下消息:
| 事件 | 触发时机 | 数据 |
|------|---------|------|
| `WORK_CREATED` | 作品创建成功A3提交后 | `{type:'WORK_CREATED', workId:'xxx'}` |
| `WORK_COMPLETED` | 创作完成(图文生成完毕) | `{type:'WORK_COMPLETED', workId:'xxx'}` |
| `CREATION_ERROR` | 创作失败 | `{type:'CREATION_ERROR', message:'xxx'}` |
### 5.2 客户父页面监听示例
```javascript
window.addEventListener('message', (event) => {
// 安全校验
if (!event.origin.includes('leai域名')) return
const { type, workId } = event.data
switch (type) {
case 'WORK_COMPLETED':
// 创作完成,可以:
// 1. 切换到"作品"Tab
// 2. 刷新作品列表
// 3. 显示成功提示
showToast('创作完成!')
switchToWorksTab()
break
case 'CREATION_ERROR':
showToast('创作失败:' + event.data.message)
break
}
})
```
> **注意**: postMessage 用于前端即时通知告诉客户H5"创作完了")。完整的作品数据通过 Webhook 异步推送到客户后端,客户前端从自己的后端获取。
---
## 六、Android APK 对接
### 6.1 交付方式
乐读派打包签名的 APK 交付给客户。客户**不需要**源代码。
APK 中**不写死机构ID**,而是通过客户提供的接口动态获取(见 6.2)。
打包时,客户需提供以下信息(乐读派代入配置):
| 配置项 | 示例 | 说明 |
|--------|------|------|
| 乐读派 API 地址 | `__________` | 乐读派后端(见第二章) |
| 机构密钥 | `__________` | 客户的 appSecret见第二章 |
| 客户 API 基地址 | `https://客户域名/api` | 用于调 6.2/6.3 的接口 |
### 6.2 客户需提供的接口①获取机构ID
Android 端启动时,通过设备 MAC 地址向客户后端查询所属机构。**机构ID 不写死在 APK 中**,支持同一 APK 部署到不同机构的设备。
```
GET https://客户域名/api/org/by-device?mac={设备MAC地址}
```
响应格式:
```json
{
"code": 200,
"data": {
"orgId": "ORG001",
"orgName": "XX教育机构"
}
}
```
| 字段 | 类型 | 说明 |
|------|------|------|
| orgId | String | 乐读派分配的机构ID必须与第二章配置表一致 |
| orgName | String | 机构名称(可选,用于 Android 端显示) |
> **流程**Android 启动 → 读取设备 MAC → 调客户接口获取 orgId → 后续所有 API 调用使用该 orgId。
### 6.3 客户需提供的接口②:我的作品
Android 端"作品"Tab 展示当前用户在该机构下的作品列表。使用 **orgId + phone** 组合查询。
**作品列表:**
```
GET https://客户域名/api/my-works?orgId={orgId}&phone={phone}&page=1&size=20
```
响应格式(**字段名固定,乐读派 Android 端直接解析**
```json
{
"code": 200,
"data": {
"total": 42,
"records": [
{
"workId": "1912345678901234567",
"title": "小兔子的冒险",
"coverUrl": "https://oss.../p0.png",
"status": "COMPLETED",
"createdAt": "2026-04-03 10:30:00"
}
]
}
}
```
**作品详情:**
```
GET https://客户域名/api/my-works/{workId}?orgId={orgId}
```
响应格式:
```json
{
"code": 200,
"data": {
"workId": "1912345678901234567",
"title": "小兔子的冒险",
"author": "小明",
"pageList": [
{"pageNum": 0, "text": "封面", "imageUrl": "https://...", "audioUrl": null},
{"pageNum": 1, "text": "故事内容...", "imageUrl": "https://...", "audioUrl": "https://..."}
]
}
}
```
> **注意**
> - 字段名使用 **camelCase**(如果客户 DB 存的是 Webhook 的 snake_case需在接口层转换
> - `orgId` 必填,用于隔离不同机构的数据
> - `phone` 来自用户登录Android 端自动携带
### 6.4 Android 端数据流
```
Android 启动
├── 读取设备MAC地址
├── GET /api/org/by-device?mac=xx:xx:xx → 获取 orgId
├── 用户登录 → 获取 phone
├── 创作流程 → 调乐读派APIorgId + appSecret + phone
│ │
│ └── 创作完成 → Webhook推送到客户后端
└── "我的作品" → GET /api/my-works?orgId=xx&phone=xx → 客户后端返回
```
---
## 七、数据流全景与同步时序
### 7.1 用户创作一个作品的完整数据流
```
时间轴 →
用户操作 客户H5 乐读派H5(iframe) 乐读派后端 客户后端
│ │ │ │ │
│ 点击"创作" │ │ │ │
│ ──────────→ │ │ │ │
│ │ 换取token │ │ │
│ │ ──────────────────────────────→ │ │
│ │ ←─ sessionToken ────────────── │ │
│ │ │ │ │
│ │ 加载iframe │ │ │
│ │ ──────────→ │ │ │
│ │ │ │ │
│ 拍照上传 │ │ A6角色提取 │ │
│ ──────────────────────────→ │ ────────────→ │ │
│ │ │ ←── 角色列表 ── │ │
│ │ │ │ │
│ 选画风+写故事 │ │ A3创作 │ │
│ ──────────────────────────→ │ ────────────→ │ │
│ │ │ │ AI生成中... │
│ │ │ ←── 进度更新 ── │ │
│ │ │ │ │
│ │ │ ←── 创作完成 ── │ │
│ │ ← postMessage │ │ │
│ │ WORK_COMPLETED │ │ │
│ │ │ │ Webhook POST │
│ │ │ │ ────────────→ │
│ │ │ │ │ 验签+存DB
│ │ │ │ ← 200 ────── │
│ 看到"创作完成" │ │ │ │
│ │ 刷新作品列表 │ │ │
│ │ (从客户后端取) │ │ │
```
### 7.2 数据同步保障
| 层级 | 机制 | 说明 |
|------|------|------|
| 实时通知 | postMessage | iframe 创作完成后立即通知客户 H5 前端 |
| 数据同步 | Webhook | 创作完成后 1-3 秒推送到客户后端 |
| 重试保障 | 自动重试 5 次 | 10s/30s/2m/10m/30m确保数据不丢 |
| 兜底对账 | B3 定时查询 | 建议每 5 分钟查一次,对比 data_version |
---
## 八、对接验证清单
按顺序逐步验证,每步都通过后再进行下一步:
### Phase 1: 后端连通1天
- [ ] 收到乐读派提供的 orgId + appSecret
- [ ] 调用令牌交换接口成功:`POST /api/v1/auth/session`
- [ ] 实现 Webhook 接收端点:`POST /webhook/leai`
- [ ] 管理后台配置回调 URL + 测试连通
### Phase 2: iframe 嵌入1天
- [ ] 客户 H5 域名加入 CORS 白名单(联系乐读派)
- [ ] iframe 加载乐读派 H5 正常显示
- [ ] iframe 内可拍照/选图上传
- [ ] iframe 内完整创作流程走通(上传→提取→画风→创作→预览)
### Phase 3: 数据同步1天
- [ ] Webhook 收到 `work.completed` 事件
- [ ] 签名验证通过
- [ ] 作品数据正确写入客户 DB
- [ ] 客户"作品库"能展示 AI 创作的作品
- [ ] postMessage 通知正常接收
### Phase 4: Android 交付1天
- [ ] 客户提供"我的作品"API 接口文档
- [ ] 乐读派打包 APK 配置客户参数
- [ ] APK 安装后创作流程正常
- [ ] "我的作品"展示客户接口返回的数据
- [ ] Webhook 正常推送
---
## 九、常见问题
**Q: iframe 内创作完成后,客户怎么知道?**
A: 两个通道同时通知:① postMessage 即时通知客户前端用于刷新UI② Webhook 异步推送到客户后端(用于持久化数据)。
**Q: 客户的"广场"数据怎么来?**
A: 所有 AI 作品通过 Webhook 同步到客户 DB 后,客户在管理后台标记"精选",广场从客户 DB 读取 `is_featured=1` 的作品展示。
**Q: 用户在 iframe 创作时网络断了怎么办?**
A: 创作请求已提交到乐读派后端的不受影响后端异步生成。Webhook 会在创作完成后推送。如果用户关闭了页面,下次打开"作品库"也能看到已完成的作品。
**Q: Token 过期了怎么办?**
A: 每次用户打开"创作"Tab 时重新获取 Token2小时有效。创作过程中 Token 过期不影响已提交的创作任务。
**Q: 客户想修改创作 UI 怎么办?**
A: 联系乐读派,我们修改 H5 代码后重新部署。客户不需要改任何代码iframe 自动加载最新版本。
**Q: OSS 图片 URL 会过期吗?**
A: 不会。图片存储在乐读派 OSSURL 永久有效(除非作品被删除)。客户可以直接在广场/作品库中使用这些 URL。
**Q: Android 端需要热更新怎么办?**
A: 目前需要重新打包 APK。创作流程的 UI/逻辑更新需乐读派重新打包后交付。
---
## 附录: 错误码速查
| 错误码 | 说明 | 处理 |
|--------|------|------|
| 200 | 成功 | - |
| 10006 | 请求过于频繁 | 降低频率 |
| 20002 | 账号锁定5次密钥错误 | 等10分钟 |
| 20010 | 会话令牌无效/过期 | 重新换取 token |
| 30001 | 机构不存在 | 检查 orgId |
| 30002 | 机构未授权 | 联系乐读派 |
| 30003 | 创作额度不足 | 联系乐读派充值 |
---
> 乐读派 AI 绘本创作系统 | 企业定制对接指南 V3.1 | 2026-04-03