▶ 短剧 API v1.1接口目录 ↓
Developer Docs · 详细版

短剧统一 API 对接文档

一套接口统一接入红果、七猫和河马,覆盖请求约定、完整字段字典、5 个接口示例、播放策略、Android/H5 代码和联调验收。

BASE URLhttps://api.xueyuanpie.com
请求格式JSON POST · UTF-8
接口数量5 个统一接口

短剧统一 API 对接文档

面向 Android、iOS、H5 及其他 APP 客户端的完整接入手册。
  • 文档版本:v1.1
  • 更新日期:2026-08-10
  • 服务域名https://api.xueyuanpie.com
  • 接口前缀/app/playlet
  • 请求方式:全部为 POST
  • 请求格式application/json; charset=UTF-8
  • 响应格式application/json; charset=UTF-8
  • 鉴权方式:当前 5 个接口均标记 @TokenIgnore,暂不需要 Token
  • 字符编码:UTF-8
当前公网域名与 HTTPS 已配置完成。若短剧宿主服务尚未启动,接口会返回 HTTP 503 和明确的 JSON 提示;这表示 API 后端未部署,不是请求参数错误。

1. 快速开始

1.1 最小请求示例

curl -X POST 'https://api.xueyuanpie.com/app/playlet/search' \
  -H 'Content-Type: application/json; charset=UTF-8' \
  -d '{"platform":0,"keyword":"重生","page":1,"size":20}'

1.2 基本调用链路

选择平台
   ↓
获取分类 categories(可选)
   ↓
分页列表 page / 搜索 search
   ↓
详情与剧集 detail
   ↓
若 episodes[].playUrl 为空,则调用 play
   ↓
播放 url;失败时依次尝试 backupUrls

1.3 客户端必须遵守的三条规则

  1. sourceIdepisodeId 必须按 字符串 保存和传输。
  2. 调用详情和播放时,必须沿用列表结果对应的同一个 platform
  3. 播放地址可能带时效签名,应在即将播放时获取,不要永久缓存。

2. 平台编码

请求中的 platform 使用整数编码;响应中短剧条目的 platform 使用枚举字符串。

请求值平台响应枚举名分类能力
0红果短剧HONGGUO上游可能无独立分类,允许返回空数组
1七猫短剧QIMAO支持分类
2河马短剧HEMA支持分类

2.1 ID 精度说明

上游 ID 可能超过 JavaScript 安全整数上限 9007199254740991。即使某个平台当前返回较短的数字,也必须统一视为字符串。

正确:

{
  "sourceId": "7594403964322335768",
  "episodeId": "615427457"
}

错误:

{
  "sourceId": 7594403964322335768,
  "episodeId": 615427457
}

Java/Kotlin 使用 String;TypeScript 使用 string;Swift 使用 String,不要使用 LongDouble 或 JavaScript number 承载这些字段。


3. 请求约定

3.1 HTTP 头

Content-Type: application/json; charset=UTF-8
Accept: application/json

当前不需要 Authorization。如果生产环境后续启用鉴权,应由服务端另行约定 Header,不建议客户端预写固定 Token。

3.2 分页参数归一化

Controller 会对分页参数做以下处理:

参数情况实际处理
不传 page使用 1
page < 11 处理
不传 size使用 20
size < 11 处理
size > 5050 处理

客户端仍应主动传入合法值,不要依赖服务端修正。

3.3 空值兼容

上游平台字段完整度不同,以下字段都可能为 null、空字符串或空数组:

  • 封面、简介、评分、播放量、完结状态;
  • 总集数、剧集封面、剧集时长、清晰度;
  • 详情中的直接播放地址;
  • 备用播放地址。

UI 应提供默认封面、默认文案和空状态,不应因单个可选字段为空而导致页面崩溃。


4. 通用响应结构

接口使用宿主 Cool 项目的统一 R<T> 结构,成功业务数据位于 data

{
  "code": 1000,
  "message": "success",
  "data": {}
}
字段类型必有说明
codeinteger业务状态码;示例成功值为 1000
messagestring提示信息;部分宿主版本可能序列化为 msg
msgstring兼容字段,与 message 二选一读取
dataobject/array/null业务数据;失败时通常为 null
最终成功码和提示字段名称以实际部署的 Cool 宿主项目 R<T> 序列化为准。客户端应集中封装,不要在每个页面重复判断。

4.1 推荐判断顺序

  1. 检查是否发生 DNS、TLS、连接或超时错误;
  2. 检查 HTTP 状态是否为 2xx
  3. 尝试解析 JSON;
  4. 根据 code 判断业务是否成功;
  5. 成功后读取 data
  6. 失败文案优先读取 message,为空再读取 msg
  7. 未知错误显示通用提示,并记录请求路径、HTTP 状态、业务码和 trace 信息,禁止记录播放签名或敏感配置。

4.2 后端未部署响应

当公网 Nginx 可用、但 127.0.0.1:18082 没有短剧服务监听时,返回:

HTTP/1.1 503 Service Unavailable
Content-Type: application/json
Cache-Control: no-store
{
  "code": 503,
  "message": "短剧 API 后端尚未部署,请稍后重试",
  "data": null
}

此响应不能按业务成功处理。客户端可展示“服务暂不可用”,不应无限重试。

4.3 HTTP 状态处理建议

HTTP 状态含义客户端建议
200请求已到达业务服务继续判断业务 code
400JSON 或请求参数不合法不自动重试,检查请求体
404路由不存在检查 URL、接口前缀和版本
429请求过于频繁Retry-After 或指数退避重试
500服务内部异常提示稍后重试并上报日志
502网关连接上游失败短暂退避后有限重试
503服务未部署或暂不可用展示服务不可用,避免高频重试
504网关等待服务超时查询接口可有限重试,播放接口谨慎重试

5. 数据模型与字段字典

5.1 CategoryTab(分类分组)

分类接口使用宿主项目已有的 CategoryTabVo。典型结构如下:

{
  "category": "题材",
  "items": [
    {
      "id": "1273",
      "name": "都市",
      "current": false
    }
  ]
}

由于该模型来自宿主模块,不同版本可能存在字段命名差异。客户端初次联调时应以实际 JSON 为准。分类项的 id 必须按字符串保存,并原样作为 page.categoryId 传回。

5.2 PlayletItem(短剧条目)

{
  "platform": "QIMAO",
  "sourceId": "41000288558",
  "title": "短剧名称",
  "cover": "https://example.com/cover.jpg",
  "introduction": "剧情简介",
  "episodeCount": 80,
  "score": "9.3",
  "playCount": "1200万",
  "tags": ["都市", "重生"],
  "finishStatus": "已完结"
}
字段类型可空说明
platformstring平台枚举:HONGGUO / QIMAO / HEMA
sourceIdstring上游短剧 ID;详情和播放链路的关键参数
titlestring短剧名称
coverstring封面 URL;客户端需准备占位图
introductionstring剧情简介
episodeCountinteger总集数,未知时可能为空
scorestring上游评分原文,不保证可转数字
playCountstring上游播放量或热度原文,如“1200万”
tagsstring[]题材标签;默认空数组
finishStatusstring完结状态原文,如“已完结”“连载中”

5.3 Episode(剧集)

{
  "sourceEpisodeId": "615427457",
  "episodeNumber": 1,
  "title": "第1集",
  "cover": "https://example.com/episode-1.jpg",
  "durationSeconds": 125,
  "playable": true,
  "playUrl": null,
  "quality": "720P"
}
字段类型可空说明
sourceEpisodeIdstring上游剧集 ID;作为 play.episodeId 传入
episodeNumberinteger剧集序号;详情结果默认升序
titlestring剧集标题
coverstring剧集封面 URL
durationSecondsinteger时长,单位秒
playableboolean是否具备播放条件,默认 true
playUrlstring详情直接提供的地址;为空时调用 /play
qualitystring上游清晰度标识,如 720P
playable=false 时,客户端应禁用播放按钮或提示该集暂不可播,不要强行调用播放器。

5.4 PlayletDetail(详情与剧集)

{
  "detail": {
    "platform": "HEMA",
    "sourceId": "41000288558",
    "title": "短剧名称",
    "cover": "https://example.com/cover.jpg",
    "introduction": "剧情简介",
    "episodeCount": 80,
    "score": null,
    "playCount": null,
    "tags": ["都市"],
    "finishStatus": "已完结"
  },
  "episodes": []
}
字段类型可空说明
detailPlayletItem平台未返回有效详情时可能为 null
episodesEpisode[]剧集列表,默认空数组并按集数升序

5.5 PlayletPlay(播放信息)

{
  "sourceEpisodeId": "615427457",
  "url": "https://example.com/video.m3u8?sign=...",
  "quality": "720P",
  "format": "m3u8",
  "backupUrls": [
    "https://backup.example.com/video.m3u8?sign=..."
  ]
}
字段类型可空说明
sourceEpisodeIdstring对应请求中的剧集 ID
urlstring首选播放地址
qualitystring清晰度标识
formatstring封装格式,如 mp4m3u8
backupUrlsstring[]备用地址,默认空数组

url 为空不代表 HTTP 请求失败,但客户端无法开始播放,应提示“暂未获取到播放地址”,并允许用户稍后手动重试。

5.6 PageResult(分页结果)

{
  "list": [],
  "pagination": {
    "page": 1,
    "size": 20,
    "total": 99999
  }
}
字段类型说明
listPlayletItem[]当前页数据
pagination.pageinteger当前页码
pagination.sizeinteger每页条数
pagination.totalinteger总记录数;可能为未知总数占位值 99999

部分上游不返回精确总记录数,服务端会使用 99999。因此客户端不要使用 page * size >= total 作为唯一的结束条件。

推荐结束判断:

本页 list 为空               → 已到底
本页 list.size < 请求 size   → 通常已到底
本页 list.size == 请求 size  → 可以继续请求下一页

如上游最后一页刚好等于 size,客户端可能多请求一次空页,这是可接受的。


6. 接口总览

接口路径主要用途返回 data
获取分类/app/playlet/categories获取平台分类筛选项CategoryTab[]
分页列表/app/playlet/page推荐或分类短剧列表PageResult<PlayletItem>
搜索短剧/app/playlet/search按关键词搜索PageResult<PlayletItem>
详情剧集/app/playlet/detail获取短剧信息和剧集PlayletDetail
播放信息/app/playlet/play获取指定剧集播放地址PlayletPlay

7. 接口详情

7.1 获取平台分类

POST /app/playlet/categories

请求参数

字段类型必填允许值说明
platforminteger0 / 1 / 2平台编码

请求示例

{
  "platform": 1
}

完整成功响应示例

{
  "code": 1000,
  "message": "success",
  "data": [
    {
      "category": "题材",
      "items": [
        {
          "id": "1273",
          "name": "都市",
          "current": false
        },
        {
          "id": "1274",
          "name": "重生",
          "current": false
        }
      ]
    }
  ]
}

空分类响应

红果或上游未提供独立分类时,正常返回空数组,不属于错误:

{
  "code": 1000,
  "message": "success",
  "data": []
}

cURL

curl -X POST 'https://api.xueyuanpie.com/app/playlet/categories' \
  -H 'Content-Type: application/json; charset=UTF-8' \
  -d '{"platform":1}'

客户端处理

  • 分类为空时隐藏分类栏,直接调用分页列表;
  • 分类 ID 原样以字符串传入 page.categoryId
  • 切换平台时清空旧平台的分类选择和分页状态。

7.2 分页获取短剧列表

POST /app/playlet/page

请求参数

字段类型必填默认值说明
platforminteger-平台编码
categoryIdstringnull分类接口返回的 ID;红果可不传
pageinteger1页码,最小 1
sizeinteger20每页条数,范围 1~50

请求示例

{
  "platform": 1,
  "categoryId": "1273",
  "page": 1,
  "size": 20
}

不使用分类时:

{
  "platform": 0,
  "page": 1,
  "size": 20
}

完整成功响应示例

{
  "code": 1000,
  "message": "success",
  "data": {
    "list": [
      {
        "platform": "QIMAO",
        "sourceId": "41000288558",
        "title": "重生后我走向人生巅峰",
        "cover": "https://example.com/cover.jpg",
        "introduction": "一段示例剧情简介",
        "episodeCount": 80,
        "score": "9.3",
        "playCount": "1200万",
        "tags": ["都市", "重生"],
        "finishStatus": "已完结"
      }
    ],
    "pagination": {
      "page": 1,
      "size": 20,
      "total": 99999
    }
  }
}

cURL

curl -X POST 'https://api.xueyuanpie.com/app/playlet/page' \
  -H 'Content-Type: application/json; charset=UTF-8' \
  -d '{"platform":1,"categoryId":"1273","page":1,"size":20}'

客户端处理

  • 首次进入、切换平台或切换分类时把 page 重置为 1
  • 刷新操作替换列表,加载更多操作追加列表;
  • 可用 platform + sourceId 作为跨平台唯一键;
  • 不要只用 sourceId 去重,因为不同平台的命名空间可能重合;
  • 使用当前页数据量判断是否还能加载,不能完全依赖 total

POST /app/playlet/search

请求参数

字段类型必填默认值说明
platforminteger-平台编码
keywordstring-搜索词,去除首尾空白后不得为空
pageinteger1页码,最小 1
sizeinteger20每页条数,范围 1~50

请求示例

{
  "platform": 0,
  "keyword": "重生",
  "page": 1,
  "size": 20
}

完整成功响应示例

{
  "code": 1000,
  "message": "success",
  "data": {
    "list": [
      {
        "platform": "HONGGUO",
        "sourceId": "7594403964322335768",
        "title": "重生之示例短剧",
        "cover": "https://example.com/hongguo-cover.jpg",
        "introduction": "搜索结果示例",
        "episodeCount": 60,
        "score": null,
        "playCount": "热播",
        "tags": ["重生"],
        "finishStatus": "已完结"
      }
    ],
    "pagination": {
      "page": 1,
      "size": 20,
      "total": 99999
    }
  }
}

空结果响应

{
  "code": 1000,
  "message": "success",
  "data": {
    "list": [],
    "pagination": {
      "page": 1,
      "size": 20,
      "total": 99999
    }
  }
}

参数错误示意

业务错误的具体 code 由宿主项目决定,提示内容如下:

{
  "code": 400,
  "message": "搜索关键词不能为空",
  "data": null
}

cURL

curl -X POST 'https://api.xueyuanpie.com/app/playlet/search' \
  -H 'Content-Type: application/json; charset=UTF-8' \
  -d '{"platform":0,"keyword":"重生","page":1,"size":20}'

客户端处理

  • 输入为空时在客户端直接拦截;
  • 建议输入停止 300~500ms 后再发起搜索;
  • 新关键词搜索前取消旧请求,防止旧响应覆盖新结果;
  • 搜索历史应只保存关键词,不保存短时播放 URL;
  • 切换平台后重新搜索,并清空旧平台结果。

7.4 获取详情和剧集列表

POST /app/playlet/detail

请求参数

字段类型必填说明
platforminteger必须与列表结果来源平台一致
sourceIdstring列表或搜索结果返回的短剧 ID

请求示例

{
  "platform": 2,
  "sourceId": "41000288558"
}

完整成功响应示例

{
  "code": 1000,
  "message": "success",
  "data": {
    "detail": {
      "platform": "HEMA",
      "sourceId": "41000288558",
      "title": "示例短剧",
      "cover": "https://example.com/cover.jpg",
      "introduction": "剧情简介",
      "episodeCount": 2,
      "score": "9.0",
      "playCount": "100万",
      "tags": ["都市"],
      "finishStatus": "已完结"
    },
    "episodes": [
      {
        "sourceEpisodeId": "615427457",
        "episodeNumber": 1,
        "title": "第1集",
        "cover": "https://example.com/episode-1.jpg",
        "durationSeconds": 125,
        "playable": true,
        "playUrl": null,
        "quality": "720P"
      },
      {
        "sourceEpisodeId": "615427458",
        "episodeNumber": 2,
        "title": "第2集",
        "cover": null,
        "durationSeconds": 132,
        "playable": true,
        "playUrl": "https://example.com/direct-video.m3u8?sign=...",
        "quality": "720P"
      }
    ]
  }
}

cURL

curl -X POST 'https://api.xueyuanpie.com/app/playlet/detail' \
  -H 'Content-Type: application/json; charset=UTF-8' \
  -d '{"platform":2,"sourceId":"41000288558"}'

客户端处理

  • episodes 默认按 episodeNumber 升序,但客户端仍可做防御性排序;
  • playable=false 的剧集不发起播放;
  • playUrl 非空时可直接播放,失败后再调用 /play 刷新地址;
  • playUrl 为空时,把 sourceEpisodeId 作为 /playepisodeId
  • detail=nullepisodes 非空时仍可展示基础剧集页;
  • episodes=[] 时展示“暂无可用剧集”,不要访问数组第一项。

7.5 获取播放信息

POST /app/playlet/play

请求参数

字段类型必填说明
platforminteger与详情请求保持一致
sourceIdstring短剧 ID
episodeIdstringepisodes[].sourceEpisodeId

请求示例

{
  "platform": 2,
  "sourceId": "41000288558",
  "episodeId": "615427457"
}

完整成功响应示例

{
  "code": 1000,
  "message": "success",
  "data": {
    "sourceEpisodeId": "615427457",
    "url": "https://example.com/video.m3u8?sign=...",
    "quality": "720P",
    "format": "m3u8",
    "backupUrls": [
      "https://backup-1.example.com/video.m3u8?sign=...",
      "https://backup-2.example.com/video.m3u8?sign=..."
    ]
  }
}

无可用地址示例

{
  "code": 1000,
  "message": "success",
  "data": {
    "sourceEpisodeId": "615427457",
    "url": null,
    "quality": null,
    "format": null,
    "backupUrls": []
  }
}

cURL

curl -X POST 'https://api.xueyuanpie.com/app/playlet/play' \
  -H 'Content-Type: application/json; charset=UTF-8' \
  -d '{"platform":2,"sourceId":"41000288558","episodeId":"615427457"}'

推荐播放策略

  1. 用户点击某集后检查 playable
  2. 若详情中的 playUrl 非空,先尝试该地址;
  3. 否则调用 /play
  4. 优先使用返回的 url
  5. 主地址发生网络错误、403、404 或媒体解析失败时,按顺序尝试 backupUrls
  6. 全部失败后允许用户点击“重新加载”,重新调用 /play 获取新签名;
  7. 切换剧集时取消上一集尚未完成的播放地址请求。

播放器注意事项

  • m3u8 通常使用 HLS 播放;Android 推荐 Media3/ExoPlayer,iOS 推荐 AVPlayer;
  • 不要只根据 URL 后缀判断格式,优先参考 format,必要时结合响应 Content-Type;
  • 某些 CDN 可能校验 User-Agent、Referer 或时效签名,遇到 403 应先重新获取地址;
  • 不要把完整带签名播放 URL 写入公开日志、埋点参数或崩溃报告;
  • 不建议后台预取大量剧集播放地址,避免签名过期和上游压力。

8. 参数校验与典型错误

Controller 当前具备以下校验:

场景提示文案
未传 platform短剧平台不能为空
platform 不是 0/1/2不支持的短剧平台: {code}
搜索词为空搜索关键词不能为空
详情未传 sourceId短剧ID不能为空
播放未传 sourceId短剧ID不能为空
播放未传 episodeId剧集ID不能为空

业务错误码的具体数字由 Cool 宿主的异常处理器决定,因此客户端不要硬编码“某段文案必然对应某个数字”。

8.1 重试原则

请求是否建议自动重试建议
分类、列表、搜索、详情有条件只对连接失败、超时、502/504 重试 1~2 次
播放信息谨慎最多重试 1 次;用户操作优先
400、业务参数错误修正请求参数后再发起
404检查路径或部署版本
429遵循服务端等待时间并指数退避
503不立即重试提示服务暂不可用,稍后由用户重试

推荐退避:第一次等待约 500ms,第二次约 1500ms,加入少量随机抖动。不要无限循环。


9. Android / Kotlin 完整示例

以下示例使用 Retrofit、OkHttp 和 Gson 风格模型。若项目使用 Moshi 或 Kotlinx Serialization,可保持字段类型不变。

9.1 请求模型

data class PlatformRequest(
    val platform: Int
)

data class PageRequest(
    val platform: Int,
    val categoryId: String? = null,
    val page: Int = 1,
    val size: Int = 20
)

data class SearchRequest(
    val platform: Int,
    val keyword: String,
    val page: Int = 1,
    val size: Int = 20
)

data class DetailRequest(
    val platform: Int,
    val sourceId: String
)

data class PlayRequest(
    val platform: Int,
    val sourceId: String,
    val episodeId: String
)

9.2 响应模型

data class ApiResponse<T>(
    val code: Int,
    val message: String? = null,
    val msg: String? = null,
    val data: T? = null
) {
    fun displayMessage(): String = message ?: msg ?: "请求失败"
}

data class Pagination(
    val page: Long = 1,
    val size: Long = 20,
    val total: Long = 0
)

data class PageResult<T>(
    val list: List<T> = emptyList(),
    val pagination: Pagination = Pagination()
)

data class PlayletItem(
    val platform: String? = null,
    val sourceId: String? = null,
    val title: String? = null,
    val cover: String? = null,
    val introduction: String? = null,
    val episodeCount: Int? = null,
    val score: String? = null,
    val playCount: String? = null,
    val tags: List<String> = emptyList(),
    val finishStatus: String? = null
)

data class Episode(
    val sourceEpisodeId: String? = null,
    val episodeNumber: Int? = null,
    val title: String? = null,
    val cover: String? = null,
    val durationSeconds: Long? = null,
    val playable: Boolean = true,
    val playUrl: String? = null,
    val quality: String? = null
)

data class PlayletDetail(
    val detail: PlayletItem? = null,
    val episodes: List<Episode> = emptyList()
)

data class PlayletPlay(
    val sourceEpisodeId: String? = null,
    val url: String? = null,
    val quality: String? = null,
    val format: String? = null,
    val backupUrls: List<String> = emptyList()
)

9.3 Retrofit 接口

interface PlayletApi {
    @POST("app/playlet/categories")
    suspend fun categories(
        @Body body: PlatformRequest
    ): ApiResponse<List<Map<String, Any?>>>

    @POST("app/playlet/page")
    suspend fun page(
        @Body body: PageRequest
    ): ApiResponse<PageResult<PlayletItem>>

    @POST("app/playlet/search")
    suspend fun search(
        @Body body: SearchRequest
    ): ApiResponse<PageResult<PlayletItem>>

    @POST("app/playlet/detail")
    suspend fun detail(
        @Body body: DetailRequest
    ): ApiResponse<PlayletDetail>

    @POST("app/playlet/play")
    suspend fun play(
        @Body body: PlayRequest
    ): ApiResponse<PlayletPlay>
}

9.4 Retrofit 初始化

val okHttpClient = OkHttpClient.Builder()
    .connectTimeout(10, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .writeTimeout(30, TimeUnit.SECONDS)
    .build()

val api = Retrofit.Builder()
    .baseUrl("https://api.xueyuanpie.com/")
    .client(okHttpClient)
    .addConverterFactory(GsonConverterFactory.create())
    .build()
    .create(PlayletApi::class.java)

9.5 详情到播放示例

suspend fun resolveEpisodeUrl(
    api: PlayletApi,
    platform: Int,
    sourceId: String,
    episode: Episode
): List<String> {
    if (!episode.playable) return emptyList()

    // 详情已直接给出地址时先使用它。
    episode.playUrl?.takeIf { it.isNotBlank() }?.let {
        return listOf(it)
    }

    val episodeId = episode.sourceEpisodeId
        ?: return emptyList()

    val response = api.play(
        PlayRequest(platform, sourceId, episodeId)
    )
    if (response.code != 1000) {
        throw IllegalStateException(response.displayMessage())
    }

    val play = response.data ?: return emptyList()
    return buildList {
        play.url?.takeIf { it.isNotBlank() }?.let(::add)
        addAll(play.backupUrls.filter { it.isNotBlank() })
    }.distinct()
}
如果实际宿主成功码不是 1000,应把成功判断集中配置,而不是散落在业务代码中。

10. Java / OkHttp 示例

MediaType JSON = MediaType.get("application/json; charset=utf-8");
String bodyJson = "{\"platform\":2,\"sourceId\":\"41000288558\"}";

Request request = new Request.Builder()
    .url("https://api.xueyuanpie.com/app/playlet/detail")
    .header("Accept", "application/json")
    .post(RequestBody.create(bodyJson, JSON))
    .build();

try (Response response = okHttpClient.newCall(request).execute()) {
    String responseText = response.body() == null
        ? ""
        : response.body().string();

    if (!response.isSuccessful()) {
        throw new IOException(
            "HTTP " + response.code() + ": " + responseText
        );
    }

    // 使用 Gson/Jackson 解析 ApiResponse<PlayletDetail>。
    // 先判断 code,再读取 data;提示兼容 message 和 msg。
}

11. TypeScript / H5 示例

type ApiResponse<T> = {
  code: number;
  message?: string;
  msg?: string;
  data: T | null;
};

type DetailRequest = {
  platform: 0 | 1 | 2;
  sourceId: string; // 必须是 string
};

async function postPlaylet<T>(path: string, body: unknown): Promise<T> {
  const response = await fetch(`https://api.xueyuanpie.com${path}`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json; charset=UTF-8",
      "Accept": "application/json"
    },
    body: JSON.stringify(body)
  });

  const result = await response.json() as ApiResponse<T>;
  if (!response.ok) {
    throw new Error(result.message ?? result.msg ?? `HTTP ${response.status}`);
  }
  if (result.code !== 1000 || result.data == null) {
    throw new Error(result.message ?? result.msg ?? "业务请求失败");
  }
  return result.data;
}

const detail = await postPlaylet(
  "/app/playlet/detail",
  { platform: 2, sourceId: "41000288558" } satisfies DetailRequest
);

禁止写成 sourceId: 41000288558,否则对于长 ID 可能在 JSON 发送前就已经发生精度损失。


12. 缓存、并发与性能建议

12.1 可缓存内容

内容建议缓存时间说明
平台分类10~30 分钟切换平台时按平台分别缓存
推荐/分类列表1~5 分钟下拉刷新可绕过缓存
搜索结果可不缓存可仅保留当前会话
短剧详情1~5 分钟连载短剧集数可能变化
播放地址不持久缓存可能存在短时签名
图片遵循 CDN Header使用常规图片缓存库

12.2 并发控制

  • 同一页面只保留最新一次搜索请求;
  • 切换平台、分类或关键词时取消旧分页请求;
  • 防止重复触发“加载更多”;
  • 播放按钮快速连点时,同一集只保留一个 /play 请求;
  • 不要在进入详情页时并发请求全部剧集的播放地址。

12.3 日志脱敏

允许记录:接口路径、平台编码、分页参数、耗时、HTTP 状态、业务码。

谨慎记录:sourceIdepisodeId,可仅保留末 4 位。

禁止记录:上游密钥、Cookie、设备会话参数、完整带签名播放 URL。


13. APP 页面状态建议

每个列表页至少区分以下状态:

  1. 首次加载:骨架屏或加载动画;
  2. 有数据:正常展示列表;
  3. 搜索为空:显示“未找到相关短剧”;
  4. 分类为空:隐藏分类控件,不作为错误;
  5. 网络错误:显示重试按钮;
  6. 服务不可用:针对 503 显示“服务暂不可用,请稍后再试”;
  7. 加载更多结束:本页为空或少于 size 时停止;
  8. 播放地址为空:显示“暂时无法播放”,提供手动重试。

14. 联调检查清单

14.1 基础网络

  • https://api.xueyuanpie.com TLS 证书校验通过;
  • 请求使用 HTTPS,不允许回退 HTTP;
  • Header 为 Content-Type: application/json; charset=UTF-8
  • 中文关键词发送和响应均无乱码;
  • 能区分 HTTP 错误与业务 code 错误。

14.2 三个平台

platform=0/1/2 分别验证:

  • 分类接口返回数组,允许为空;
  • 列表接口至少能解析一页或正常空数组;
  • 搜索接口可正确传递中文关键词;
  • 详情返回 detailepisodes
  • 可选择一集并获得播放地址;
  • 响应枚举名分别能解析 HONGGUOQIMAOHEMA

14.3 边界条件

  • page=0 时客户端不会崩溃;
  • size=100 时能接受服务端按 50 返回;
  • 空关键词在客户端被拦截;
  • 无效平台能展示服务端错误;
  • sourceId 超过 16 位仍保持原字符串;
  • detail=nullepisodes=[]playUrl=null 均可正常展示;
  • 主播放地址失败时会尝试备用地址;
  • 503 时不会无限自动重试。

14.4 上线前

  • 实际短剧后端已监听 127.0.0.1:18082
  • 公网 5 个接口返回真实业务 JSON,不再返回未部署 503
  • 成功业务码和 message/msg 字段已按实际宿主确认;
  • 已配置生产鉴权、限流及日志脱敏;
  • 播放资源的使用方式符合上游授权和内容合规要求;
  • 证书自动续签已验证。

15. 常见问题 FAQ

Q1:为什么分类接口返回空数组?

部分平台,尤其红果,上游可能没有独立分类接口。空数组是正常成功结果,直接请求分页列表即可。

Q2:为什么 total 总是 99999

这代表上游没有提供准确总数。它是占位值,不代表真的有 99999 条。请根据每页返回数量判断是否继续加载。

Q3:详情里已有 playUrl,还需要调用 /play 吗?

不需要。可以先直接播放;如果为空、过期或播放失败,再调用 /play 获取最新地址。

Q4:为什么同一个剧集播放地址过一段时间失效?

上游 CDN 地址可能带时间戳或签名。不要永久缓存,应在用户准备播放时重新获取。

Q5:能否把三个平台的结果混合在一个列表?

可以,但每条记录必须保留平台信息,并以 platform + sourceId 作为唯一标识。调用详情和播放时必须使用该条目的原平台。

Q6:HTTP 200 是否一定成功?

不一定。HTTP 200 只代表请求到达业务服务,还需要检查响应 code

Q7:HTTP 503 是否是参数错误?

不是。当前部署中,503 通常表示短剧宿主服务尚未启动或暂不可用。参数错误应由业务响应提示。

Q8:接口是否支持 GET 或表单提交?

不支持。5 个接口统一使用 JSON POST。


16. 版本与兼容性说明

  • 新增可选字段时,客户端应忽略未知 JSON 字段;
  • 可空字段未来可能补全,客户端模型不应限制为非空;
  • 请求平台整数编码在 v1 内保持不变;
  • 响应平台枚举名固定为 HONGGUOQIMAOHEMA
  • 若未来增加鉴权、API 版本前缀或新的成功码,应通过文档版本和发布通知同步;
  • 当前示例域名为正式 HTTPS 域名,但后端部署完成前接口可能返回明确的未部署 503。

17. 联系与问题反馈建议

联调反馈至少提供以下信息,便于快速定位:

发生时间(含时区):
接口路径:
platform:
HTTP 状态:
业务 code:
提示信息:
请求耗时:
是否可稳定复现:
脱敏后的 sourceId / episodeId:

请勿在群聊、工单或截图中公开上游密钥、Cookie、设备会话、完整签名播放地址。