我的动态页

文档编号 06

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 模板名个人动态
页面 slugmy_activity
访问地址https://kaiwen.work/my_activity
缓存目录usr/cache/(已在 .gitignore,服务器自动创建)

模块一:ACG 动态(Bangumi API)

API 基本信息

项目内容
Base URLhttps://api.bgm.tv
认证Authorization: Bearer BANGUMI_API_TOKENBANGUMI_TOKEN 亦可,二者等价)
Token 存放config.inc.phpdefine('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 / namecollections → subject中文名优先,fallback 日文名
images.commoncollections → subject封面图 URL
ep_statuscollections我的话数进度
vol_statuscollections我的卷数进度
ratecollections我的评分(0 = 未评)
rating.scoresubjects detail(非 collections)网站评分,collections 接口此字段可能为空
infoboxsubjects detail作者/出版社等元数据,值可能为字符串或嵌套数组

subject_type 枚举

类型
1书籍
2动画
3音乐
4游戏
6三次元

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 URLhttps://api.steampowered.com
认证Query param ?key=STEAM_API_KEY
Key 存放config.inc.phpdefine('STEAM_API_KEY', '...')
SteamIDconfig.inc.phpdefine('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.phpdefine('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 类贡献次数浅色模式颜色深色模式颜色
lv00var(--pico-muted-border-color)同左
lv11–2#9be9a8#0e4429
lv23–5#40c463#006d32
lv36–9#30a14e#26a641
lv410+#216e39#39d353

支持的事件类型

typeEmoji中文描述
PushEvent推送了 N 个 commit 到 {branch}(无数量时显示「推送了代码到 {branch}」)
CreateEvent创建了 {ref_type} {ref}
DeleteEvent🗑删除了 {ref_type} {ref}
WatchEventStar 了此仓库
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.phpJSON 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 动态 → 事件流增加分页或"加载更多"