星穹情报站 开放 API 文档
基础地址:{你的站点域名}/api/v1
所有接口均开启 CORS:响应头 Access-Control-Allow-Origin: *
---
通用约定
统一 JSON 响应信封
除 RSS 接口返回 XML 外,其余所有接口统一使用以下 JSON 信封结构,HTTP 状态码恒为 200,业务状态由 code 字段判定:
{
"code": 0,
"message": "success",
"data": { }
}
字段说明:
| 字段 | 类型 | 说明 |
|------|------|------|
| code | int | 业务码,0 表示成功,非 0 表示失败 |
| message | string | 人类可读的提示信息(成功时为 success) |
| data | object \| array \| null | 业务数据;成功时才有意义,失败时通常为 null |
业务错误码
| code | 含义 | 触发场景 |
|------|------|----------|
| 0 | 成功 | — |
| 1001 | 参数错误 | id 无效(新闻详情)、q 为空或超长(搜索)等 |
| 1004 | 资源不存在 | 新闻 id 不存在或已被删除 |
| 1401 | 令牌无效或缺失 | POST /api/v1/sync 未携带或错误的 X-Admin-Token |
| 5000 | 服务端错误 | 应用尚未安装、同步时抓取异常 |
枚举:新闻来源 source
代码实现(source_name())的来源中文名映射:
| source | source_name | 说明 |
|--------|-------------|------|
| official | 官网 | 游戏官方公告站 |
| miyoushe | 米游社 | 米哈游官方社区 |
| honkai3rd | 圣芙蕾雅档案馆 | 崩坏3rd 第三方存档站 |
分页参数
所有列表接口的分页参数一致:
| 参数 | 类型 | 默认值 | 范围 | 说明 |
|------|------|--------|------|------|
| page | int | 1 | ≥ 1 | 页码;小于 1 时自动修正为 1 |
| page_size | int | 20 | 1 ~ 50 | 每页条数;可在 config.php 的 page_size.default / page_size.max 调整 |
关键词字段 keywords
keywords 是同步时基于新闻标题自动提取的标签数组(最多 5 个),来源:
1. 固定关键词匹配:前瞻 PV 直播 版本 更新 维护 活动 公告 补偿 兑换码 祈愿 跃迁
2. 版本号正则:\d+\.\d+(如 3.4 5.0)
---
接口 1:游戏列表
GET /api/v1/games
返回所有已启用(enabled = 1)的游戏字典,用于前端渲染 Tab 或作为其它接口 game 参数的候选值。
请求
无查询参数。
响应
{
"code": 0,
"message": "success",
"data": {
"items": [
{ "id": 1, "code": "genshin", "name": "原神", "icon": "https://..." },
{ "id": 2, "code": "starrail", "name": "崩坏:星穹铁道", "icon": "" },
{ "id": 3, "code": "honkai3", "name": "崩坏3", "icon": "" }
]
}
}
data.items[] 字段
| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 游戏主键 id(来自 uc_games.id) |
| code | string | 游戏英文代号,唯一;用作其它接口的 game 参数值 |
| name | string | 游戏中文名 |
| icon | string | 游戏图标 URL(可为空字符串) |
---
接口 2:新闻列表(分页)
GET /api/v1/news
按来源 / 游戏 / 分类筛选新闻,支持分页。列表只返回卡片字段,正文 content 和 videos 不在列表里,请用详情接口获取。
请求查询参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| source | string | 否 | 来源过滤,取值见上文枚举(official / miyoushe / honkai3rd);省略则不限 |
| game | string | 否 | 游戏 code 过滤(必须是 /api/v1/games 返回的 code);未知 code 返回空集合 |
| category | string | 否 | 分类名过滤(如 公告 前瞻);省略则不限 |
| page | int | 否 | 页码,默认 1 |
| page_size | int | 否 | 每页条数,默认 20,最大 50 |
提示:不存在keyword/q参数;关键词搜索请使用/api/v1/search。
响应
{
"code": 0,
"message": "success",
"data": {
"items": [
{
"id": 42,
"source": "miyoushe",
"source_name": "米游社",
"game": "starrail",
"game_name": "崩坏:星穹铁道",
"title": "《崩坏:星穹铁道》3.4 版本「等醒来再哭泣」前瞻特别节目预告",
"summary": "各位开拓者,《崩坏:星穹铁道》3.4 版本前瞻特别节目即将开启,敬请期待…",
"cover": "https://uploadstatic.mihoyo.com/dys/upload/2026/09/01/xxxx.jpg",
"url": "https://www.miyoushe.com/dys/article/1234567",
"category": "前瞻",
"keywords": ["前瞻", "3.4"],
"published_at": "2026-09-01 12:00:00"
}
],
"total": 128,
"page": 1,
"page_size": 20
}
}
data 顶层字段
| 字段 | 类型 | 说明 |
|------|------|------|
| items | array | 本页新闻卡片数组(见下表) |
| total | int | 匹配条件的新闻总条数(用于前端构造分页器) |
| page | int | 当前页码(原样回显) |
| page_size | int | 当前每页条数(原样回显,已夹到最大范围) |
data.items[] 单条新闻卡片
| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 本地新闻表主键 id,用于 /api/v1/news/{id} |
| source | string | 来源代号(枚举) |
| source_name | string | 来源中文名(前端可直接显示) |
| game | string | 游戏 code(等价于 games 接口的 code) |
| game_name | string | 游戏中文名 |
| title | string | 新闻标题(原始,未截断) |
| summary | string | 摘要:富文本去标签 → 纯文本 → 120 字截断(超长末尾追加 …),保证列表布局整齐 |
| cover | string | 封面图 URL,可为空字符串 |
| url | string | 新闻原文外链(米游社/官网原始 URL),可为空 |
| category | string | 匹配到的分类名(后台分类规则匹配;没匹配到则为空字符串) |
| keywords | string[] | 标题提取标签,最多 5 个;空集合时返回 [] |
| published_at | string | 发布时间,格式 YYYY-MM-DD HH:MM:SS;未知时为空字符串 |
---
接口 3:新闻详情
GET /api/v1/news/{id}
获取单条新闻的完整内容,包括富文本正文 content 与附件视频列表 videos。
请求
| 路径参数 | 类型 | 说明 |
|----------|------|------|
| id | int | 新闻 id(必须是列表接口返回的正整数 id) |
响应:成功
{
"code": 0,
"message": "success",
"data": {
"id": 42,
"source": "miyoushe",
"source_name": "米游社",
"game": "starrail",
"game_name": "崩坏:星穹铁道",
"title": "《崩坏:星穹铁道》3.4 版本前瞻特别节目预告",
"summary": "《崩坏:星穹铁道》3.4 版本前瞻特别节目将于本周六晚 19:30 开启,本次节目将带来新版本的内容介绍……",
"content": "<h2>节目时间</h2>\n<p>2026 年 09 月 06 日 19:30</p>\n...",
"cover": "https://uploadstatic.mihoyo.com/dys/upload/2026/09/01/xxxx.jpg",
"url": "https://www.miyoushe.com/dys/article/1234567",
"category": "前瞻",
"keywords": ["前瞻", "3.4"],
"published_at": "2026-09-01 12:00:00",
"videos": [
{
"id": 5,
"title": "3.4 PV",
"url": "https://.../3.4-pv.mp4",
"duration": 185,
"size": 32768000,
"quality": "1080p"
}
]
}
}
data 字段(详情)
与列表卡片字段基本一致,以下是相比卡片新增或行为不同的字段:
| 字段 | 类型 | 差异说明 |
|------|------|----------|
| summary | string | 不做 120 字截断,返回数据库原值(最长 2000 字符),通常是正文前几行的纯文本快照 |
| content | string | 富文本 HTML 正文;来源支持时会同步拉取详情页填充正文,否则为空字符串(米游社已接入 getPostFull 详情接口) |
| videos | array | 关联的附件视频列表;无视频时为 [] |
data.videos[] 单条视频
| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 视频表主键 id |
| title | string | 视频标题 |
| url | string | 视频资源 URL |
| duration | int | 时长(秒);未知时为 0 |
| size | int | 文件大小(字节);未知时为 0 |
| quality | string | 清晰度标识,如 720p 1080p;未知时为空字符串 |
响应:失败
// id 非正整数
{ "code": 1001, "message": "参数错误:id 无效", "data": null }
// id 不存在或已被删除
{ "code": 1004, "message": "新闻不存在或已被删除", "data": null }
---
接口 4:分类规则列表
GET /api/v1/categories
返回后台维护的标题关键词 → 分类名匹配规则。主要用途:前端自定义命中逻辑或展示可选分类筛选项。
请求查询参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| source | string | 否 | 来源过滤;省略或空时返回所有来源规则 + 全局规则(source = '' 的记录) |
| game | string | 否 | 游戏 code 过滤;未知 code 返回空集合 |
响应
{
"code": 0,
"message": "success",
"data": {
"items": [
{
"id": 1,
"source": "miyoushe",
"game_id": 2,
"name": "前瞻",
"keywords": ["前瞻", "前瞻特别节目", "直播预告"]
},
{
"id": 2,
"source": "",
"game_id": 0,
"name": "公告",
"keywords": ["公告", "停服", "维护"]
}
]
}
}
data.items[] 字段
| 字段 | 类型 | 说明 |
|------|------|------|
| id | int | 规则主键 id |
| source | string | 适用来源;空字符串表示所有来源通用 |
| game_id | int | 适用游戏 id;0 表示所有游戏通用 |
| name | string | 命中后写入 news.category 的分类名 |
| keywords | string[] | 触发关键词数组;标题中出现任意一个即命中(大小写不敏感、子串匹配)。底层以 , 分隔存储,返回前已按逗号切割并去空去两端空格 |
---
接口 5:关键词搜索(分页)
GET /api/v1/search
对标题 + 摘要进行关键词搜索,返回结构与新闻列表完全一致。搜索内部优先尝试 MySQL FULLTEXT 索引(若存在),失败时自动回退为 LIKE 模糊匹配。
请求查询参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| q | string | 是 | 搜索关键词;为空返回 1001;超过 100 字符时服务端会自动截断至前 100 字符(不报错) |
| source | string | 否 | 来源过滤,同新闻列表 |
| game | string | 否 | 游戏 code 过滤,同新闻列表 |
| category | string | 否 | 分类名过滤,同新闻列表 |
| page | int | 否 | 页码,默认 1 |
| page_size | int | 否 | 每页条数,默认 20,最大 50 |
响应
结构与新闻列表完全相同:{ items, total, page, page_size },items[] 字段定义请参见「新闻列表」章节。
{
"code": 0,
"message": "success",
"data": {
"items": [ /* 同新闻列表卡片 */ ],
"total": 7,
"page": 1,
"page_size": 20
}
}
响应:失败
// q 为空
{ "code": 1001, "message": "参数错误:q 不能为空", "data": null }
---
接口 6:RSS 2.0 订阅
GET /api/v1/rss
返回符合 RSS 2.0 规范的 XML 文档,内容类型 application/rss+xml;不是 JSON,不走统一错误信封。
请求查询参数
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| source | string | 否 | 来源过滤 |
| game | string | 否 | 游戏 code 过滤 |
响应
- HTTP 头:
Content-Type: application/rss+xml; charset=utf-8 - 数量:取筛选后最新 50 条(硬编码于 RssService)
- 链接优先级:若原新闻有
url外链则用外链,否则用站内详情页/news/{id} <guid>:恒为站内详情页 URL(用base_url('news/{id}')生成)<enclosure>:当cover非空时输出图片附件,type=image/jpeg
示例(节选):
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>星穹情报站 - 崩坏:星穹铁道 · 米游社</title>
<link>https://stellar.example.com/</link>
<description>星穹情报站新闻订阅</description>
<generator>星穹情报站</generator>
<lastBuildDate>Tue, 01 Sep 2026 12:00:00 +0800</lastBuildDate>
<item>
<title>《崩坏:星穹铁道》3.4 版本前瞻特别节目预告</title>
<link>https://www.miyoushe.com/dys/article/1234567</link>
<description>各位开拓者,……</description>
<enclosure url="https://.../xxx.jpg" type="image/jpeg" length="0"/>
<category>崩坏:星穹铁道 · 前瞻</category>
<pubDate>Tue, 01 Sep 2026 12:00:00 +0800</pubDate>
<guid><![CDATA[https://stellar.example.com/news/42]]></guid>
</item>
</channel>
</rss>
---
接口 7:手动触发同步
POST /api/v1/sync
用于外部系统(CI/CD、定时任务面板等)远程触发新闻抓取与入库。需要令牌鉴权。
认证
请求头必须携带:
X-Admin-Token: {config/admin/token 的值}
- 令牌值保存在
config/config.php的admin.token中,首次安装时随机生成 32 位 hex - 令牌比较使用
hash_equals()(时序安全) - 当
config.php中admin.token为空字符串时,请求恒为失败(防止未配置就开放)
请求
- 方法:
POST - 内容类型:
application/json(通过Request::json()读取;不能用 form-data)
{
"source": "all",
"game": "starrail"
}
| Body 字段 | 类型 | 必填 | 说明 |
|-----------|------|------|------|
| source | string | 否 | 同步源:miyoushe / official / honkai3rd / all;默认 miyoushe。all 等价于同时同步 miyoushe 和 official(honkai3rd 不包含,需单独指定) |
| game | string | 否 | 限定某个游戏 code;省略或空字符串时同步所有已启用的游戏 |
响应:成功
{
"code": 0,
"message": "同步完成",
"data": {
"miyoushe": {
"games": 4,
"new_count": 12,
"total_count": 80,
"errors": 0
},
"official": {
"games": 2,
"new_count": 0,
"total_count": 20,
"errors": 0
}
}
}
data 是对象,键 = 被同步的 source,值 = 该源的汇总:
| 字段 | 类型 | 说明 |
|------|------|------|
| games | int | 本次实际同步的游戏数(受已启用与 game 参数共同限制) |
| new_count | int | 新增入库的新闻数(已存在的不会重复新增;对每个 origin_id 做 upsert) |
| total_count | int | 从远端源拉取的新闻总数(含重复/已存在) |
| errors | int | 抓取阶段抛出异常或部分写入失败的累计次数 |
响应:失败
// 未携带或错误的 X-Admin-Token
{ "code": 1401, "message": "令牌无效或缺失(Header: X-Admin-Token)", "data": null }
// 未知的 source 或 source 未配置 available() 检查不通过
{ "code": 5000, "message": "同步失败:新闻源未配置: miyoushe", "data": null }
---
附录:字段行为对照表
以下是易混淆字段在「列表卡片」和「详情对象」中的差异总结:
| 字段 | 列表 /api/v1/news | 详情 /api/v1/news/{id} |
|------|------|------|
| summary | 富文本去标签 + 120 字截断(末尾 …) | 数据库原值,最长 2000 字符 |
| content | 不存在该字段 | 完整富文本 HTML(米游社同步后填充) |
| videos | 不存在该字段 | 视频对象数组,空时为 [] |
---
附录:部署前自检 Checklist
1. 访问 GET /api/v1/games,确保返回 code=0 且至少有 1 个游戏
2. 访问 GET /api/v1/news?page_size=1,确保返回结构完整(含 total / page / page_size)
3. 取第 2 步中的 items[0].id,访问 GET /api/v1/news/{id},确认 content 和 videos 字段存在
4. 访问 GET /api/v1/search?q=前瞻,确认 q 为空时得到 code=1001
5. POST /api/v1/sync 不带 X-Admin-Token,确认得到 code=1401
6. GET /api/v1/rss 返回头 Content-Type 含 rss+xml,根元素是 <rss version="2.0">