06 - 个人动态页开发笔记
创建日期:2026-04-28
用途:/my_activity 独立页面的维护参考,包含 ACG 动态(Bangumi)、Steam 动态和 Github 动态三个模块的实现细节
页面基本信息
| 项目 | 内容 |
|---|
| 页面模板文件 | usr/themes/classic-22/my-activity.php |
| 共享函数文件 | usr/themes/classic-22/inc/activity-functions.php |
| 异步 API 端点 | activity-api.php(项目根目录) |
| Typecho 模板名 | 个人动态 |
| 页面 slug | my_activity |
| 访问地址 | https://kaiwen.work/my_activity |
| 缓存目录 | usr/cache/(已在 .gitignore,服务器自动创建) |
模块一:ACG 动态(Bangumi API)
API 基本信息
| 项目 | 内容 |
|---|
| Base URL | https://api.bgm.tv |
| 认证 | Authorization: Bearer BANGUMI_API_TOKEN(BANGUMI_TOKEN 亦可,二者等价) |
| Token 存放 | config.inc.php → define('BANGUMI_API_TOKEN', '...') 和 define('BANGUMI_TOKEN', BANGUMI_API_TOKEN) |
| 缓存文件 | usr/cache/bangumi_activity.json |
| 缓存有效期 | 6 小时 |
调用接口
# 1. 获取最近 10 条收藏
GET /v0/users/xmicrox/collections?limit=10&type=0
# 2. 获取每个收藏的详细信息(含 infobox)
GET /v0/subjects/{subject_id}
关键字段说明
| 字段 | 来源 | 说明 |
|---|
name_cn / name | collections → subject | 中文名优先,fallback 日文名 |
images.common | collections → subject | 封面图 URL |
ep_status | collections | 我的话数进度 |
vol_status | collections | 我的卷数进度 |
rate | collections | 我的评分(0 = 未评) |
rating.score | subjects detail(非 collections) | 网站评分,collections 接口此字段可能为空 |
infobox | subjects detail | 作者/出版社等元数据,值可能为字符串或嵌套数组 |
subject_type 枚举
infobox 提取逻辑
// 作者/导演:按优先级匹配 key
$authorKeys = ['作者', '原作', '导演', '监督', '原案'];
// 出版/发行:按优先级匹配 key
$publisherKeys = ['出版社', '发行', '动画制作', '制作公司', '发行方', '开发'];
// 注意:value 可能是字符串,也可能是 [{v: '...', k: '...'}] 形式的数组
已知问题 / 注意事项
rating.score 必须从 /v0/subjects/{id} 获取,collections 接口的 subject 对象中该字段有时为空ep_status 优先于 vol_status 展示进度(有 ep_status 则显示话数,否则显示卷数)- 每次刷新数据需删除缓存:
rm {TYPECHO_ROOT}/usr/cache/bangumi_activity.json
模块二:Steam 动态
API 基本信息
| 项目 | 内容 |
|---|
| Base URL | https://api.steampowered.com |
| 认证 | Query param ?key=STEAM_API_KEY |
| Key 存放 | config.inc.php → define('STEAM_API_KEY', '...') |
| SteamID | config.inc.php → define('STEAM_ID', '76561198141733567') |
| 缓存文件 | usr/cache/steam_activity.json(+ steam_activity_fallback.json 永久兜底) |
| 缓存有效期 | 6 小时 |
| 缓存写入判据 | 仅当 fetchSteamPlayerSummary() 成功($player !== null)才写入短缓存和 fallback。渲染端硬依赖 player 字段,若以"任一子接口成功"为判据会用 player=null 的半成品污染缓存,导致页面空白持续整个 TTL 周期。详见 00-dev-errors.md E15。 |
调用接口
# 1. 玩家基本资料(头像、昵称、在线状态)
GET /ISteamUser/GetPlayerSummaries/v2/?key={key}&steamids={steamid}
# 2. 近期游戏(最多6条,近2周)
GET /IPlayerService/GetRecentlyPlayedGames/v1/?key={key}&steamid={steamid}&count=6
# 3. 徽章(player_level、badge_count)
GET /IPlayerService/GetBadges/v1/?key={key}&steamid={steamid}
personastate 枚举
| 值 | 文字 | 颜色 |
|---|
| 0 | 离线 | #888888 |
| 1 | 在线 | #57cbde |
| 2 | 忙碌 | #f8c84e |
| 3 | 离开 | #f8c84e |
| 4 | 打盹 | #f8c84e |
| 5 | 想交易 | #57cbde |
| 6 | 想游戏 | #57cbde |
游戏封面 URL 拼装
⚠️ 旧格式对 2024 年后上架的新游戏返回 404,当前使用 IStoreBrowseService/GetItems 获取真实 URL。
当前实现(2026-04-29 更新):
调用 fetchSteamGameCovers()(activity-functions.php),批量向 api.steampowered.com/IStoreBrowseService/GetItems/v1/ 请求,从 assets.asset_url_format + assets.header 构造完整封面 URL:
CDN = https://shared.akamai.steamstatic.com/store_item_assets/
URL = CDN + asset_url_format.replace("${FILENAME}", assets.header)
示例(新格式):
https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/4126220/8583acfa.../header.jpg?t=1776328466
示例(老格式,无 hash 段):
https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/730/header.jpg?t=1749053861
兜底机制(封面 URL 404 时):onerror 将 <img> 替换为带 Steam 品牌渐变(#1b2838 → #2a475e)的 <div class="steam-cover-missing">。
游戏卡片 UI
游戏卡片使用全幅封面图 + 底部半透明渐变遮罩设计:
- 卡片比例固定
aspect-ratio: 460 / 215(Steam header 图标准比例) - 封面图绝对定位铺满卡片(
object-fit: cover) - 游戏名、时长信息通过
position: absolute; bottom: 0 + 渐变背景(rgba(0,0,0,0.75) → transparent)浮于底部 - 无封面时显示深蓝渐变占位(
.steam-cover-missing)
隐私前提
用户 Steam 个人资料及游戏详情须设为公开,否则 GetRecentlyPlayedGames 返回空数组。
验证字段:communityvisibilitystate = 3
模块三:Github 动态
API 基本信息
| 项目 | 内容 |
|---|
| Base URL (REST) | https://api.github.com |
| Base URL (GraphQL) | https://api.github.com/graphql |
| 认证 | Authorization: Bearer GITHUB_PUBLIC_TOKEN |
| Token 类型 | Fine-grained PAT,All repositories,Metadata read-only |
| Token 存放 | config.inc.php → define('GITHUB_PUBLIC_TOKEN', '...') |
| GitHub 用户名 | kxue4 |
| 缓存文件 | usr/cache/github_activity.json |
| 缓存有效期 | 6 小时 |
调用接口
# 1. 贡献热力图(GraphQL,最近90天)
POST https://api.github.com/graphql
{
user(login: "kxue4") {
contributionsCollection(from: "YYYY-MM-DDT00:00:00Z", to: "YYYY-MM-DDT23:59:59Z") {
contributionCalendar {
totalContributions
weeks { contributionDays { contributionCount date } }
}
}
}
}
# 2. 活跃仓库(最近更新的4个,含私有)
GET /user/repos?sort=updated&per_page=4&affiliation=owner
# 3. 事件流(最近15条公开事件)
GET /users/kxue4/events?per_page=15
重要限制
| 限制 | 说明 |
|---|
| 事件流 vs 私有仓库 | /users/{username}/events 不返回私有仓库事件,即使有 token 认证 |
| 事件保留时间 | GitHub 只保留最近 90 天的事件 |
| REST 速率限制 | 无认证 60次/小时;有认证 5000次/小时 |
| GraphQL 速率限制 | 有认证 5000 points/小时 |
热力图色阶
| CSS 类 | 贡献次数 | 浅色模式颜色 | 深色模式颜色 |
|---|
lv0 | 0 | var(--pico-muted-border-color) | 同左 |
lv1 | 1–2 | #9be9a8 | #0e4429 |
lv2 | 3–5 | #40c463 | #006d32 |
lv3 | 6–9 | #30a14e | #26a641 |
lv4 | 10+ | #216e39 | #39d353 |
支持的事件类型
| type | Emoji | 中文描述 |
|---|
| PushEvent | ⬆ | 推送了 N 个 commit 到 {branch}(无数量时显示「推送了代码到 {branch}」) |
| CreateEvent | ✨ | 创建了 {ref_type} {ref} |
| DeleteEvent | 🗑 | 删除了 {ref_type} {ref} |
| WatchEvent | ⭐ | Star 了此仓库 |
| ForkEvent | 🍴 | Fork 到 {forkee} |
| PullRequestEvent | 🔀 | {action} PR:{title} |
| IssuesEvent | 📋 | {action} Issue:{title} |
| ReleaseEvent | 🚀 | 发布了 {tag_name} |
| IssueCommentEvent | 💬 | 评论了 Issue:{title} |
页面布局结构
[折叠标题] ▶ ACG 动态 上次更新 2026-04-29 18:28
<div id="acg-content"> ← 骨架屏占位,JS 异步注入
┌──────────┬──────────┐
│ 卡片1 │ 卡片2 │ ← .activity-grid(2列 Grid)
├──────────┼──────────┤
│ ... │ ... │ ← 10 条收藏
└──────────┴──────────┘
[折叠标题] ▶ Steam 动态 上次更新 2026-04-29 19:10
<div id="steam-content"> ← 骨架屏占位,JS 异步注入
┌─────────────────────────────┐
│ [头像] 昵称 在线状态 Lv.XX│ ← .steam-profile(用户状态卡)
├─────────────────────────────┤
│ 近 2 周游戏 │
│ ┌───────────┬───────────┐ │
│ │ [封面全幅] │ [封面全幅] │ │ ← .steam-games-grid(2列)
│ │ ▓▓▓ 遮罩 │ ▓▓▓ 遮罩 │ │ ← 渐变遮罩覆盖底部
│ │ 游戏名 │ 游戏名 │ │
│ │ 时长 │ 时长 │ │
│ └───────────┴───────────┘ │
└─────────────────────────────┘
[折叠标题] ▶ Github 动态 上次更新 2026-04-29 18:28
<div id="github-content"> ← 骨架屏占位,JS 异步注入
┌─────────────────────────────┐
│ 贡献热力图(全宽,90天) │ ← .github-heatmap-wrap
├─────────────────────────────┤
│ 活跃仓库(全宽,4个,2列) │ ← .github-repos-wrap
├─────────────────────────────┤
│ 事件流(时间线) │ ← .github-timeline-wrap
└─────────────────────────────┘
响应式断点: 900px(仓库 2列 → 单列,ACG 卡片 2列 → 单列)
异步加载架构
页面采用前端 JS 异步加载,消除 API 调用阻塞首屏渲染。
数据流:
浏览器请求 /my_activity
↓(极快,仅输出骨架屏 HTML)
my-activity.php(无 API 调用)
↓ JS fetch(并行)
├── GET /activity-api.php?module=bangumi → {"html": "..."}
├── GET /activity-api.php?module=steam → {"html": "..."}
└── GET /activity-api.php?module=github → {"html": "..."}
↓ JS 注入 innerHTML
├── #acg-content ← ACG 动态卡片
├── #steam-content ← 用户状态卡 + 游戏网格
└── #github-content ← 热力图 + 仓库 + 事件流
关键文件:
| 文件 | 职责 |
|---|
my-activity.php | 渲染骨架屏页面骨架 + CSS + JS 加载逻辑 |
activity-api.php | JSON API 端点,接收 ?module=bangumi/steam/github,返回 {"html":"..."} |
usr/themes/classic-22/inc/activity-functions.php | 所有数据函数 + render_bangumi_content() + render_steam_content() + render_github_content() |
activity-api.php 响应格式:
// 成功
{ "html": "<div class=\"activity-grid\">...</div>", "updated_at": "2026-04-29 18:28" }
// 失败(非法 module)
HTTP 400 { "error": "invalid module" }
updated_at 来源规则:
| 数据来源 | updated_at 取值 |
|---|
| 实时拉取成功 | 当前时间 |
| 6 小时短缓存命中 | 短缓存文件的 filemtime |
| Fallback 命中 | Fallback 文件的 filemtime |
注意: 服务器 PHP 时区需设为 Asia/Shanghai(在 /etc/php/x.x/fpm/php.ini 中设置 date.timezone = Asia/Shanghai),否则 date() 返回 UTC 时间。
配置文件(config.inc.php)
// 以下 token 存放在 config.inc.php(已在 .gitignore,不提交 git)
define('GITHUB_TOKEN', '...'); // 用于访问 kaiwen.work 私有仓库(原有)
define('GITHUB_PUBLIC_TOKEN', '...'); // 用于 Github 动态模块 API 调用
define('BANGUMI_API_TOKEN', '...'); // 用于 ACG 动态模块 API 调用
define('BANGUMI_TOKEN', BANGUMI_API_TOKEN); // 等价别名,供 rating API 体系使用
define('STEAM_API_KEY', '...'); // 用于 Steam 动态模块 API 调用
define('STEAM_ID', '...'); // Steam 用户 64位 ID
常用维护操作
# 清除 Bangumi 缓存(强制下次访问刷新数据)
rm {TYPECHO_ROOT}/usr/cache/bangumi_activity.json
# 清除 Github 缓存
rm {TYPECHO_ROOT}/usr/cache/github_activity.json
# 清除 Steam 缓存
rm {TYPECHO_ROOT}/usr/cache/steam_activity.json
# 清除全部缓存
rm {TYPECHO_ROOT}/usr/cache/*.json
# 查看缓存状态
ls -la {TYPECHO_ROOT}/usr/cache/
# 检查 PHP 错误日志(如果页面异常)
tail -50 /var/log/nginx/error.log
后续扩展计划
- [x] Github 动态 → 贡献热力图支持自定义天数(当前固定90天)
- [x] 页面新增第三个模块(Steam 动态,已实现:用户状态卡 + 近期游戏网格)
- [ ] Github 动态 → 事件流增加分页或"加载更多"