星穹情报站 开放 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.phppage_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.phpadmin.token 中,首次安装时随机生成 32 位 hex
  • 令牌比较使用 hash_equals()(时序安全)
  • config.phpadmin.token 为空字符串时,请求恒为失败(防止未配置就开放)

请求

  • 方法:POST
  • 内容类型:application/json(通过 Request::json() 读取;不能用 form-data)
{
  "source": "all",
  "game": "starrail"
}

| Body 字段 | 类型 | 必填 | 说明 |

|-----------|------|------|------|

| source | string | 否 | 同步源:miyoushe / official / honkai3rd / all;默认 miyousheall 等价于同时同步 miyousheofficial(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},确认 contentvideos 字段存在

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-Typerss+xml,根元素是 <rss version="2.0">