CHANGELOG

更新记录

Utterlog 每次推送到 main 的改动都会自动构建镜像并发布版本。下面列出最近的 30 个发布。

v2.3.9
2026年5月7日

[2.3.9] - 2026-05-08

修复

  • 后台升级在自定义安装路径下"未找到 docker-compose 文件":v2.3.3 加的 probeComposeWorkingDir() (用 docker compose label 自动探测真实安装目录) 实际从来没被触发,因为 docker-compose.yml 默认 UTTERLOG_INSTALL_DIR=${UTTERLOG_INSTALL_DIR:-/opt/utterlog} 让 env 变量永远非空,老逻辑「env 优先 → 非空就直接用 → label 探测被跳过」导致用户装在 /opt/utterlog-pancn//root/my-blog/ 等任何非 /opt/utterlog 路径时升级一律拿到错的 /opt/utterlog 然后报错。本次调整优先级 → docker compose label > env 变量 > /opt/utterlog 兜底。compose label 是 docker compose 自己起容器时打上的最权威路径,永远准确;env 变量退到 fallback(万一 label 探测失败 / docker socket 不可用时仍能用)。日志里也明确标出来源((来自 compose label) / (来自 UTTERLOG_INSTALL_DIR env) / (兜底默认))方便排查。
v2.3.8
2026年5月7日

[2.3.8] - 2026-05-08

优化

  • coding 缓存键加版本号 v2 —— 强制让旧形态缓存失效:本次后端 contributions 数据从「自然年 Jan-Dec」形态改成「rolling 365 天」形态,但 Redis 缓存保留 30 天 stale-while-revalidate,旧 key 命中会继续返回旧形态数据 → 用户升级后看到的还是 ~19 周老视图。coding.gocodingCacheVersion = "v2" 常量并把它前缀到 cache key 上 (v2:<usernames>:<repos>:<token>),旧 <usernames>:<repos>:<token> 形态的 key 全部失配,新代码冷启动就拉新数据,30 天 stale 期里那些条目自然 TTL 失效不再占用。未来 contributions 形态再调时把版本 bump 到 v3 / v4 即可。
  • 后端 coding contributions API 改成 rolling 365 天(修复"数量不对"):v2.3.5 前端把热力图改成 GitHub-style "今天落在最右、往前 1 整年"(53 周 / 12 个月)排版,但后端 coding.gocurrentYearContributionRange / emptyCodingContributions 一直只取「自然年 Jan 1 到今天」(年中访问时只 ~18 周数据),前端 rollingDays 过滤后只剩这一小段 → 热力图实际只渲染 ~18 列,看起来"数量不对,没有完整显示出来"。本次新增 rolling365Range(now) helper 返回 [今天 - 364, 今天]fetchGitHubContributionCalendar 改用它向 GitHub 拉数据;emptyCodingContributions 同步改成 365 天 rolling 占位底盘。前端 53 周自动撑满,跨年边界正常。GraphQL contributionsCollection 接受任意 from/to 区间,跨年没问题。
  • coding 热力图等分页面宽度(12 个月 / 53 周):v2.3.6 / v2.3.7 一直用 min-width / max-width 720~740 + gap 3-4px 的 grid 布局,整张图被强制锁在 720~740px,跟页面内容区宽度脱节;某些容器里 cell 还会被拉到 60+px。本次彻底重写:globals.css.coding-heatmap 改成 flex 布局,.coding-heatmap-week { flex: 1 0 0 } 53 个 week 等分父级宽度,cell 走 width: 100%; aspect-ratio: 1 自然变方块。整张图自动撑满页面内容区不留白边、不出现横滚(Nebula 960px 容器 cell ~15px / Azure ~1300px 容器 cell ~22px / 各主题随自己页面宽度变化),跟 archive 页 .nebula-heatmap 同款行为,cell 尺寸跨页面自动一致。各主题之前 v2.3.7/v2.3.8 加的尺寸覆写(Azure / Chred / Flux / Nebula / Renascent / Utterlog 6 份各自的 min-width / max-width / gap !important)全部移除。page.tsx.coding-heatmap 元素上 inline 设的 gridTemplateColumns 在 flex 容器下被自动忽略,无需改 React。
  • Nebula 通用页宽度回到 960px:v2.3.6 把 tags / categories / archive / search / links 这些页的容器从 960 临时拉到 1200,但跟 Nebula 首页 / 文章页 / coding 页统一的 960px 内容栏宽度对不上,破坏了全站视觉统一感。本次回退到 960px,跟其他页面对齐。tags / categories 多 chip 时换行多一些,但视觉一致优先。
  • Nebula footer 默认高度跟 header 对齐(64px):v2.3.6 footer 用 min-height: 86px,跟 header 的 .nebula-header-inner height: 64px 不齐。本次 footer min-height: 86 → 64padding 10/10(v2.3.6 是 16/13)—— 默认无备案号 / 单行内容时跟 header 完全对齐;有备案号 / 多行内容时 min-height 不限制上界,内容自然撑开。

修复

暂无。

移除

暂无。

v2.3.7
2026年5月7日

[2.3.7] - 2026-05-08

修复

  • 全主题 coding 页热力图尺寸 GitHub-style 收口:globals.css 之前 .coding-heatmap 只有 min-width: 920px + width: 100%、没有 max-width,53 列 cell 在 Azure 1304px 内容区里被拉到 ~20px 巨型方块,远比 GitHub 真实贡献图(~11px)大。Nebula 之前自己写了一份 min-width: 720 + max-width: 740 + gap: 3 的覆写绕过这个问题,但其他主题没动。本次把 GitHub-style 紧凑(min-width: 740 / max-width: 740 / gap: 3px.coding-heatmap-week 同 gap 3px)写进 globals.css,全主题共用一份;Nebula 那份覆写删掉避免漂移,仅保留绿色阶梯颜色覆写。
  • 非 Nebula 主题(Azure / Chred / Flux / Renascent / Utterlog)的友链页 / 订阅页错位:v2.3.1 把这两个共享客户端组件 (web/app/(blog)/feeds/FeedsClient.tsx / web/app/(blog)/links/LinksClient.tsx) 重做成 toolbar + className 驱动的新布局(feeds-toolbar / feeds-view-toggle / feed-card-*links-toolbar / links-view-btn / links-group-tab),但相关 CSS 只在 web/themes/Nebula/styles.css 里写了,没给其他主题留 baseline。Azure / Chred / Flux 等渲染时拿不到任何样式 → toolbar 按钮散落 + 卡片裸奔。修复:把每个客户端组件拆成 Nebula*View(v2.3.x 新版本)+ Legacy*View(v2.3.0 inline-style 版本),原 FeedsClient / LinksClient 改成 useThemeContext() 主题分发器,Nebula 走新版、其他主题走老版。老版纯 inline 样式不依赖任何主题 class,跨主题安全;Nebula 版的视觉特性 (toolbar / 视图切换 / 随机骰子) 完全不污染其他主题。

移除

暂无。

v2.3.6
2026年5月7日

[2.3.6] - 2026-05-07

新增

暂无。

优化

  • 升级流程 compose 文件双源拉取:sidecar 之前只从 utterlog.io/docker-compose.yml 拉,下载失败就跳过。现在 utterlog.io 不可达时自动 fallback 到 https://raw.githubusercontent.com/utterlog/utterlog/main/docker-compose.yml;两源都不可达才放弃刷新(仍能继续后续的镜像拉取流程)。新封装 try_fetch_compose() 助手函数统一处理 HTTP/合法性校验,日志里清晰标出主源 / 兜底源命中情况。
  • 升级流程镜像拉取双源策略:默认 registry.utterlog.io/utterlog/utterlog-{api,web};若 docker compose pull 失败(注册中心维护、网络抖动、限流),sidecar 自动 export UTTERLOG_IMAGE_PREFIX=ghcr.io/utterlog 重试一次 —— GHA 的 docker-publish.yml workflow 把每个 release 的镜像同时推到这两个 registry,GHCR 能完整顶替 registry.utterlog.io。注意要 export(不只是赋值)这样后面 docker compose up -d 子进程才会读到同一个 prefix,避免「拉了 ghcr 的但 up 时又去找 registry.utterlog.io」的不一致。两源都失败才报 [TASK-END] 退出。
  • Nebula coding 热力图视觉压缩回到 v2.3.4 紧凑感:v2.3.5 把日期范围从「自然年 1/1~12/31」改成 rolling 365 天,年中访问从 ~20 周变成满 53 周,整张图占满 960px 内容区,视觉上比之前大得多。Nebula 加覆写:.coding-heatmapmin-width 920 → 720、gap 4px → 3px、加 max-width: 740px 不让它再撑满全宽;.coding-heatmap-week 同步把 gap 改 3px。单格回到 GitHub-style 的 ~11px,整体视觉跟 v2.3.4(部分年渲染)的紧凑感对齐。功能(rolling 365 天)保留不变。
  • Nebula footer 整体降 10%:v2.3.5 用 min-height: 96px + padding 18/14(总 32px)解决「无备案号站点 footer 看着像被踩扁」,但放在已经有备案号 / 多行内容的站点上偏高了。本次 min-height 96 → 86、padding-top 18 → 16、padding-bottom 14 → 13(总 29px)—— 三个值各降约 10%,无备案站点底线仍稳定不踩扁,有备案 / 内容多的站点不再显得头重脚也重。
  • Nebula 首页"最新评论者头像墙"屏蔽管理员LatestCommenters.tsx 之前用 exclude_admin=0 把博主自己的评论也算进来,头像墙第一个永远是博主,喧宾夺主。改成 exclude_admin=1(API 端按 admin email + user_id != 1 双重过滤),只展示访客社区氛围;per_page 同步从 40 提到 60 留缓冲,dedup 后仍能稳定凑齐 20 个去重头像。
  • Nebula tags / categories / archive / search 等通用页 max-width 960 → 1200:之前 960px 在 tags 页 chip 多时被迫频繁换行;archive / search / links 在大屏上左右留白偏宽。统一拉到 1200px,文章页(.nebula-article / .nebula-post)保持自己的窄栏不受影响。
  • Nebula 短内容页页脚贴底:之前 .nebula-main 是默认 block 布局,短内容页(tags 空、categories 几个、search 无结果)<Footer> 浮在页面中部,下面一大片黑。改成 display: flex; flex-direction: column.nebula-frameflex: 1 0 auto 占满剩余高度把 footer 顶到视口底;内容长时 frame 自然撑开不影响。
  • 全主题:侧栏 / 首页分类导航过滤掉 0 篇文章的分类:之前侧栏 / 首页中部 hero tabs / 分类筛选条都直接 categories.map 渲染,新建但还没分配文章的分类(count = 0)会作为"空目录"留在 UI 上喧宾夺主。覆盖范围:Azure / Chred / Flux 的 Sidebar.tsx「文章分类」区(全部为空时整段隐藏)、Azure / Chred / Flux 的 HomePage.tsx hero 左侧 tabs、Nebula 的 nebula-category-strip 中部分类筛选条、Renascent 的 renascent-category-strip —— 统一在渲染前 filter(c => (c.count || 0) > 0),并把 allTabs / tabCount / 自动轮播下标基数 / 边框最后一项判定 等所有依赖原 categories.length 的地方改成 visibleCategories.length,保证活跃 tab 索引、auto-rotate cycle、hero 高度全部跟实际渲染对齐。Azure 自定义 sidebar 路径里 admin 配进菜单的 0 count 分类也单独 return null 跳过。
  • Nebula 足迹页 Mapbox 底图改暗色 (dark-v11):之前 FootprintMap.tsx 把 style 写死 mapbox/light-v11,在 Nebula 暗主题里像页面中间嵌了一块大白板,跟周围撞色刺眼。改成 useThemeContext()theme.name,命中 Nebula 时切到 mapbox/dark-v11;高亮国家的填充色 #4f9cff → #80cfff (Nebula sky 系),描边色 #0052d9 → #80cfff 并把 opacity 从 0.18/0.38 提到 0.22/0.55,确保深底上仍能看清。mapStyle 加进 useEffect 依赖数组,主题切换会重建地图。Nebula CSS 同步加 .footprint-mapbox-popup 适配:popup tip 小三角(mapbox-gl 默认写死白色)按 8 个 anchor 方向各自翻成 var(--nebula-card)、popup 内容外层换蓝细边 + 更深的阴影、卡片分隔线翻深、hover 用 sky 蓝半透。
  • Nebula 通用页标题字体改 PingFang SC 栈.blog-page-title-text 之前用 var(--nebula-font-display)(Google Sans Display 英文优先 + 系统字体 fallback),中文标题在 macOS 上回退到 SF/Helvetica 字形偏单薄。改成 'PingFang SC', -apple-system, ..., 'Microsoft YaHei', 'Noto Sans SC' 栈,中文标题字形规整紧凑跟 macOS / iOS 系统字体一致;Windows / Android 走 YaHei / Noto SC 兜底;.coding-page-title 仍保留自己的 display 字体(页面定调不一样)。

修复

  • Nebula 首页文章每页数量没跟 admin posts_per_page 设置走web/themes/Nebula/HomePage.tsx 顶部写死 const PER_PAGE = 10,server 端 /app/(blog)/page.tsx 已经按 admin 选项 posts_per_page 拉了 SSR 首屏数据并把 perPage 作 prop 传过来,但 Nebula 完全忽略这个 prop。后果:admin 配 6 篇/页时 SSR 首屏 6 篇 + totalPages 按 6 算,但点分类筛选 / 翻页时 AJAX fetchPage 用 PER_PAGE = 10 重新拉 → 数量对不上、totalPages 也错位。修复:删掉模块级常量,析构 perPage = 10(fallback 跟 server 一致)作 prop,fetchPage 和 PostCard index={(page-1) * perPage + index + 1} 全部改用 prop 值。
  • Renascent 首页 PostCard 序号跨页冲突(page - 1) * posts.length + index + 1 在末页只显示 N 篇时(N < perPage),从下一页第一条会重新从前面的小数字开始,跟前页编号撞车。posts.length 是当前页实际数量不是固定值,根本不能当步长。改用 perPage 作步长,类型上 perPage?: number 早就声明了但一直没解构进函数体;这次解构 + 替换。
  • Nebula 说说工具栏"说说"标题客户端导航后变深灰看不见MomentsClient.tsx<span>说说</span> 写的是 inline color: #1a1a1a,Nebula CSS 用 [style*="color:#1a1a1a"] 属性选择器翻成白。SSR 直接进 /moments 时 HTML 串里就是 #1a1a1a 选择器命中没问题;但用户从首页客户端路由跳过去时,React 在 DOM 上重设 inline style,浏览器把它规范化成 rgb(26, 26, 26) —— 原选择器失配 → "说说" 仍是 #1a1a1a 深灰,跟 Nebula 暗背景撞色几乎看不见,必须强制刷新才白。修复跟 v2.3.5「moments-tag-chip / moments-month-chip / moments-year-btn 改用类钩子」同思路:<span>说说</span>className="moments-toolbar-title",Nebula 用 [data-theme="Nebula"] .moments-toolbar-title { color: var(--nebula-white) !important; } 强覆盖;inline-style 属性匹配保留作兜底,并补 rgb(26, 26, 26) / rgb(26,26,26) 两种规范化形态,万一旧 SSR 缓存 / 旧 client 的 HTML 没带 class 也能命中。

修复

暂无。

移除

暂无。

v2.3.5
2026年5月7日

新增

后台升级日志改成模态框

之前内联在升级面板中部,体验像"在页面里塞了一段终端"。改成全屏 <Modal size="xl"> 弹出:

  • 顶部状态条:spinner / ✓ / ✗ + "正在拉取镜像 / 重建容器…" + 启动时间
  • 60vh 高度可滚动正文,每行带语义高亮(时间戳 / [START] / 容器名 / 成功 / WARN / ERROR)
  • 底部蓝色扫光进度条(upgradeLogProgressBar 关键帧 1.5s 一周)
  • 新行自动滚动到末尾哨兵(scrollIntoView 平滑),最新一行始终在视口
  • 每行飘入动画(upgradeLogLineIn:opacity 0→1 + 左滑 4px → 0,0.28s ease-out)
  • 模态框关闭后页面留"查看升级日志"按钮,状态还在的话能随时重开
  • prefers-reduced-motion: reduce 用户偏好下自动关掉动画

Nebula 菜单声明 footercategory(中部分类导航)

admin 在「主题 → 菜单 → 分类导航」配的菜单项现在驱动首页中间的分类筛选条;不配时仍回退到自动列出所有分类(向后兼容)。

优化

Coding 页面热力图 rolling 365 天

之前按"自然年 1/1 ~ 12/31"排版,5 月落在中间不符合 GitHub 风格。改成今天落在最右、往前数 365 天。

Nebula 文章 <strong> 加粗看不出 → 真正加粗

font-weight: 600700(系统字体 / Google Sans Display / Noto Sans SC 都加载了 700 weight),<b> 标签也加进选择器。之前 600 + 颜色跟正文同色 --nebula-white,加粗几乎看不出。

评论提交后页面无感更新(WordPress 风格)

CommentList 的 fetchCommentsmode 参数(initial / switch / silent),提交回复后走 silent —— 列表保持显示、新数据无缝替换、不显示 loading 骨架、不强制 smooth-scroll 到顶部。原地多一条卡片,其它结构不动。

Nebula footer 没备案号时高度偏低

.nebula-footermin-height: 96px + flex 垂直居中,.nebula-footer-row 上下 padding 18/14。无备案号站点 footer 不再像被压扁了。

后台版本号 / 铃铛红点 实时事件总线同步 ⭐ 关键改进

之前版本号 pill 用模块级 in-memory cache TTL 10 分钟,升级后左上角仍显旧版本要等 10 分钟或刷新页面。铃铛 60s 轮询,审批评论后红点不消失要等下一轮。

新增两条事件:

事件 触发场景 监听者
admin:version-changed 升级成功(version / commit / built_at 任一变化) VersionBadge 立即清缓存重拉
admin:notifications-changed 评论审批 / 拒绝 / 删除 / 移到垃圾箱 / 恢复(一处 fetchCounts 全场景覆盖) NotificationBell 立即重拉

服务端本来就实时,问题在前端缓存策略,现在不再要强制刷新 F5。

后台路由切换不再全屏闪

Suspense fallback 从 <Spinner overlay />(全屏遮罩)改成 <RouteFallback />(仅顶部 2px 蓝色不定进度条 + 透明主区域)。1.7MB 的 Analytics chunk 加载中也只有顶部一道扫光,侧栏 / 头部保持可见,体感顺滑。

Nebula 说说工具栏初始灰色 bug

CSS 写的 [style*="color:#7a7670"](无空格)跟 React 实际序列化的 color: #7a7670(带空格)匹配不上 → 图标保持原色 #7a7670 灰,强制刷新才白。修复:每条 inline-style 选择器都补带空格的版本 + 加 i 大小写不敏感标志。

Nebula 说说标签 / 月份弹出卡片配色

之前用 inline-style 字符串匹配(不可靠),改用类钩子 .moments-tag-chip / .moments-month-chip / .moments-year-btn(含 .is-active),CSS 用类选择器强覆盖。默认半透白底 + 半透白字、选中态用 sky 蓝底 + navy 深字。

Changelog 渲染支持 GFM 表格 + 分隔线

admin 后台和 utterlog.io/changelog 同步修复,renderChangelog() 加 GFM table parser → 输出 <table class="changelog-table">(紧凑边框 + 表头浅灰底 + zebra 行);--- 分隔线 → <hr/>

后台"更新内容"标题只显示版本号

{info.latest.name || info.latest.version}{info.latest.version}。GitHub release name 经常带长描述,标题里啰嗦。配套:把 GitHub releases 上的 v2.3.1 / v2.3.2 / v2.3.3 / v2.3.4 标题改成纯版本号,v2.0.2 补 v 前缀。

utterlog.io 静态版本代理

utterlog-landing 站构建期生成 public/api/version.json + public/api/releases.json,所有用户的 utterlog 后台改成查 https://utterlog.io/api/version.json,不再直接打 GitHub API。N 个用户共享同一份 CDN 缓存,配额从 60/h(匿名共享 IP)→ 实质无限。GitHub API 仍是 fallback。新增 version_source_url admin 选项给私有部署 / 企业 fork 指向自己的 mirror。

修复

管理员评论 / 回复邮件诊断日志

之前修复了管理员评论早返回不发邮件,但用户报告偶尔仍发。在 sendCommentNotifications 增加诊断日志:命中 admin 跳过时输出 [comment-notify] skipped — sender is admin (commentID=X, role=admin, user_id=1),没跳过时输出 role / user_id / senderIsAdmin 完整字段。下次出现可以直接看日志定位是 DB JOIN 没拿到 role 还是别的代码路径。

v2.3.4
2026年5月7日

新增

版本检查走 utterlog.io 自家代理(不再依赖 GitHub API)⭐ 关键改进

之前每个用户的 admin 后台都直接打 api.github.com,云出口共享 IP 的 60/h 匿名配额经常爆 → 403 → 升级面板看不到新版本号。

新架构

浏览器 → utterlog admin "一键升级"
  ↓
utterlog api → utterlog.io/api/version.json   ← 静态 JSON,CDN 缓存
  ↓
(单一 GHA token 在 landing 上,所有用户共享)

utterlog.io/api/version.json 是 utterlog-landing 站构建期生成的静态文件 —— 用 GitHub Actions 的 GITHUB_TOKEN 拉一次 release 列表(5000/h 配额),写到 public/api/*.json → next build 自动导出 → Cloudflare CDN 缓存。

结果

  • 单一 token 在 landing 上,N 个用户共享同一份缓存,永远不会 rate-limit
  • 用户不需要配 GitHub Token,开箱即用
  • 国内访问走 Cloudflare 节点,比直连 api.github.com 快得多
  • GitHub API 仍是 fallback,主路径失败时自动切换

version_source_url admin 选项

私有部署 / 企业 fork 想用自己的 mirror,可在 admin options 加 version_source_url(如 https://your-mirror.example.com),代码会自动去 <url>/api/version.json 拉取。

优化

升级日志输出格式参考 1Panel

旧:

[2026-05-07T15:30:20Z] sidecar starting in /opt/...
[2026-05-07T15:30:25Z] mode=slim — pulling images

新:

2026/05/07 23:30:20 升级应用 [Utterlog] 任务开始 [START]
2026/05/07 23:30:21 检测容器名 api=[utterlog-pancn-api-1] web=[utterlog-pancn-web-1]
2026/05/07 23:30:21 检测部署模式 [slim]
2026/05/07 23:30:24 拉取镜像 成功
2026/05/07 23:30:42 重建容器 成功
2026/05/07 23:30:54 api 健康检查 成功 (12s)
2026/05/07 23:30:54 升级应用 [Utterlog] 成功 [TASK-END]

本地时区时间戳 + 中文动作 + [对象] + 状态/[标记],每一步语义清晰。容器名 / 安装目录 / 镜像 tag / digest 全部动态显示,肉眼能确认探测正确。

后台升级日志面板高亮

SystemUpdatePanel.tsx 新增 highlightLogLine(line),按语义着色:

Token 颜色
时间戳 2026/05/07 23:30:20 暗灰 #64748b
[START] [TASK-END] 琥珀加粗 #fbbf24
[xxx] 容器名 / 路径 / 镜像 天蓝 #7dd3fc
成功 亮绿 #4ade80
WARN #fbbf24
ERROR 失败 #f87171

容器外观:#0f172a → 更深的 #0a0e1a(终端感)+ 圆角 6px + 顶部状态徽标分割线 + max-height 280 → 360px。

Changelog 渲染支持 GFM 表格 + 分隔线

之前 renderChangelog() 不识别 | col1 | col2 | + |---|---| 表格语法,release notes 里的对比表都显示成原文 raw 字符。补上:

  • GFM table parser → 输出 <table class="changelog-table">(紧凑边框 + 表头浅灰底 + zebra 行)
  • --- 分隔线 → <hr/>

admin 后台 + utterlog.io/changelog 同步修复。

后台"更新内容"标题只显示版本号

{info.latest.name || info.latest.version}{info.latest.version}。GitHub release name 有时是 v2.3.3 — upgrade works on any compose project name 这种长描述,标题里啰嗦。现在固定显示 更新内容 — v2.3.3

配套:把 GitHub releases 上的 v2.3.1 / v2.3.2 / v2.3.3 标题改成纯版本号,v2.0.2 补 v 前缀。

v2.3.3
2026年5月7日

修复

一键升级在自定义 compose 项目名下静默失败 ⭐ 关键修复

之前代码写死 docker inspect utterlog-api-1。但 docker compose 给容器命名是 <project>-<service>-<index>项目名取决于安装目录的 basename

安装路径 compose 项目名 容器名 老代码
/opt/utterlog/ utterlog utterlog-api-1
/opt/utterlog-pancn/(1Panel 默认) utterlog-pancn utterlog-pancn-api-1
/root/my-blog/ my-blog my-blog-api-1
任何非默认目录 basename basename-api-1

老代码所有 docker inspect 调用都拿不到容器,健康检查 120s 干等到超时,admin UI 报 "升级未生效"。

修复apiContainerName()os.Hostname()(docker 默认把 hostname 设成短 ID)+ docker inspect <id> 反查真实容器名;webContainerName(api) 通过 com.docker.compose.project label 反查同项目里的 web 容器。所有原本写死 utterlog-api-1 的地方(probeComposeWorkingDir / probeAPIUploadsMountSource / sidecar 健康检查 / sidecar 终态 digest)改用动态名字;sidecar 启动时通过 API_CONTAINER / WEB_CONTAINER 环境变量传递。

覆盖面:未来任何用户不管装在哪个目录、用什么 compose project 名(utterlog / utterlog-pancn / my-blog / 大写小写下划线),后台升级按钮一次成功。

GitHub API 403 rate-limit

三个 GitHub API 调用都没鉴权,云出口共享 IP 的 60/h 匿名配额一打就爆。新增 applyGitHubHeaders(req) 助手,admin 配了 GitHub Token 时自动加 Authorization: Bearer ...,配额 60/h → 5000/h。403 错误信息明确化(提示去后台填 token)。

"升级未生效" 误报

verifyUpgradeApplied 之前只看 version 字符串严格相等。改成三层成功信号(version / commit / built_at)任一命中即成功,超时 60s → 180s,错误信息带实际拿到的 version + commit。

生产 named volume 下 sidecar 日志 admin 看不到

docker-compose.prod.ymluploads:/app/public/uploads(命名卷),api 读卷里的 upgrade.log,sidecar 写到宿主目录,两个完全不同的物理文件。新增 probeAPIUploadsMountSource() 探测真实挂载源,sidecar 启动时也挂载同一个源。

安装目录写死 /opt/utterlog

runUpgrade() 默认 installDir = /opt/utterlog,1Panel 装在 /opt/utterlog-pancn/ 直接报"找不到 docker-compose 文件"。新增 probeComposeWorkingDir() 用 docker label 自动探测真实路径。


已经在旧版本上踩坑的用户怎么修

你的 admin "一键升级" 按钮已经被旧 bug 困住,无法自己升到这个修复版本。需要手动跑一次:

ssh your-server
cd /your/install/dir
sudo docker compose pull
sudo docker compose up -d

升上来之后,未来所有 admin 升级永久不会再出这个问题

v2.3.2
2026年5月7日

新增

  • Nebula 顶部进度条 <TopProgress />:路由切换时顶部 2px 蓝色流光进度条,10s 兜底
  • 首页分类 tabs stagger fade-in:切分类时旧卡片留位 + .is-loading 半透明,新数据回来错峰 30ms 飘入;按钮 hover/active 弹簧反馈;尊重 prefers-reduced-motion
  • footer 回到顶部按钮:独立 36×36 squircle,贴 footer 右内边距 20px、垂直居中、container 之外
  • header logo / 站名 hover 中心放大:logo 块、渐变 mark、纯文字站名都加弹簧 cubic-bezier 缓动,hover 1.15x
  • /search 与 header 弹出搜索框样式统一:input 44px 胶囊形 + 左 leading 放大镜 + sky 蓝胶囊 submit 按钮(深底字)
  • 段落点评弹窗改 position: fixed:基于 trigger bbox 实时计算坐标,监听 .blog-main + window 滚动同步跟随;彻底脱离文档流,不被父级 overflow 裁切
  • OG / Twitter Card 图片兜底/[...permalink] 路由原本只设 title/description,没 og:image —— 现在跟 /posts/[slug] 一样用 post.cover_url 优先 + randomCoverUrl(post.id) 兜底

优化

  • 代码块底色 #0a0d14(之前 --nebula-input#121212,跟文章卡片 #131314 仅差 1 个亮度档);Prism token 在新底色上对比度更高
  • footer 4 个图标按钮统一 logo 框风格:音乐 / RSS / Utterlog / 登录 全部 24×24 squircle + border + 半透深底;RSS 图标 fa-square-rssfa-rss(外层方框已由按钮提供);登录 fa-lightfa-solid 视觉重量对齐
  • footer 已登录菜单对齐.nebula-footer-login-menu-item 全部 !important,盖过 .nebula-footer-links a 偷过来的 padding/gap/font-size
  • ICP 备案盾形图标默认灰、hover 才蓝 + transition: fill 0.2s 平滑淡入
  • AIReaderChat 卡片精炼:删右侧多余 message-bot 图标 + 12px 圆角毛玻璃 + sky 蓝/navy 深字气泡 + 推荐问题按钮 hover
  • AIReaderChat / AIChatBubble 取最后一个 <footer>querySelector('footer') 会拿到 PostPage 内层的 nebula-post-foot 标签 footer,导致卡片避错对象。改成 querySelectorAll('footer') 取最后一个
  • AIReaderChat scroll-aware lift 加上限:用 cardRef 实测卡片高度,maxLift = viewportH - cardH - 16 双向 clamp;小视口 / footer 巨大场景下卡片不再消失
  • AIChatBubble 暗色适配:用类钩子整体翻深;浮标和发送按钮用 sky 蓝 + navy 深字
  • 段落点评弹窗暗色 #1a1d22(比 #121212 浮起一档)+ 蓝细边 + 双层阴影 + 毛玻璃
  • LatestCommenters 头像去重:改成 email + name + avatar_url 三键交叉判断
  • 代码高亮 Prism token 配色:去掉 pre code { color: white !important } 强制覆盖(之前吞掉所有 token 颜色),新增 Nebula 蓝调专属调色板
  • 首页"边读边聊"卡片避让 footer:scroll-aware lift 替代静态 footer.offsetHeight
  • 字体托管转 static.utterlog.comGoogle Sans / Google Sans TextGoogle Sans DisplayNoto Sans SC 也自托管。Nebula 不再依赖 fonts.googleapis.com

修复

  • 博主回复评论会发邮件sendCommentNotifications 算完 senderIsAdmin 直接 early-return,admin 评论 / 回复一律跳过两条邮件路径
  • 评论邮件访客信息不全go LookupAndStoreGeo(...)go sendCommentNotifications(...) 之前并行起跑,邮件几乎一定先于地理查询完成 → 模板里 IP / 国旗 / 归属地全空。改成同一个 goroutine 串行
  • AIReaderChat × 关闭按钮无响应:组件订阅了 dismiss() 但没读 store 的 dismissed 状态。补上订阅 + mount/unmount 生命周期 + if (dismissed) return null
  • 博主评论显示等级标签:所有 5 处 CommentList 加 !comment.is_admin 排除(博主已有 crown 角标)
  • 首页 AIChatBubble 整体浅色:补类钩子 + Nebula 暗色覆盖
  • footer ICP 图标默认蓝色:移除永久覆盖,改为 hover 才蓝
  • CommentForm 提交按钮蓝底白字看不清:加 comment-submit-button 钩子,Nebula 翻 navy 深字

移除

  • footer "重新打开陪读" 浮动按钮:原本点 × 关闭后 footer 会出现一个圆形 message-bot 按钮重新唤起。改为关闭后只有强制刷新 / 切文章才重显,简化交互
v2.3.1
2026年5月6日

新增

  • Nebula 暗色科技主题:电紫强调 + 深蓝表面 + 玻璃质感卡片 + 蓝调发光阴影。覆盖 Header(Alimama 方圆体 logo + admin site_brand_mode 联动)、首页(说说磨砂胶囊 + 4 个爱好图块 hover 弹跳 / 文字徽章 + 评论者头像墙 hover 弹评论详情 + 分类切换不变 URL + 统计 inline 在 § ARTICLES 行)、文章页(横幅嵌标题 + 思源宋体 h2-h6 + 苹方正文 + Google Sans Code 代码 / Prism + 右侧 fixed sticky TOC + H2 蓝胶囊徽章 + h3-h4 蓝方块前缀 + 表格行列双向网格 + 评论 thread 包裹主评和回复气泡 + @mention popup 翻深色 + 边读边聊浮窗整套深色适配)、底部多行结构页脚(建站天数 + 总浏览 + 实时在线 + 最近访客地)。
  • 足迹国旗装饰:文章封面右下角自动显示该文涉及国家国旗(Nebula 主题 36×36 圆角,其他主题 50×50 不变)。
  • PostNavigation 友链卡随机封面:RSS 拉不到友链文章特色图,改用 \randomCoverUrl(hashFeedSeed(link))\ 取稳定的 img.et 占位图,每条 feed 用 djb2 哈希链接得到不同的整数 seed,刷新不闪、4 张卡 4 张图。
  • 首页"最新评论者头像墙":首页底部一排圆头像(10–20 个,按昵称去重),hover 弹出磨砂玻璃 popup 显示该用户最新评论内容 + 文章标题 + 时间,点击跳到对应评论锚点。

优化

  • \randomCoverUrl\ regex 放宽:\[?&]r=\d+\ → \[?&]r=[^&#]*\。之前 admin 设置 \r=abc\ 等非数字 seed 时 regex 不匹配,会去 else 分支追加第二个 \r={id}\ 参数,img.et 收到两个同名参数行为不可预测。现在任何 \r=<value>\ 都会被替换成 \r={post.id}\,每篇文章拿到稳定唯一的随机封面。
  • AISummary 图标:\fa-wand-magic-sparkles\(魔法棒)→ \fa-microchip-ai\(AI 微芯片),更切题。共享组件,所有主题受益。
  • 评论列表头像方→圆:CommentList 三处 inline \borderRadius: 0\ 全部改 \'50%'\,跟现代 UI 风格一致。
  • PostNavigation 友链 tab 也按 \pageSize\ 切片:之前 feeds tab 不分页全部渲染,现在跟其他 tab 一样切片,Nebula 传 \pageSize={4}\ 时友链卡也只显示 4 张。
  • CommentList / CommentForm / AIReaderChat 加 className 钩子:\comment-card\ / \comment-card--reply\ / \comment-thread\ / \comment-form\ / \ai-reader-chat--collapsed\ / \ai-reader-chat--open\,便于主题精准 override 而无需改 inline style。共享组件不动 inline 样式,其他主题不受影响。

修复

  • 友链 tab 多张卡片可能拿到同一张占位图:原因是 \randomCoverUrl\ 直接把整段 link URL 当 \r=\ 传给 img.et,长 URL 被服务端截断或归一化后彼此变成相同 seed。修复见上面 \randomCoverUrl\ regex + 友链卡新加的 \hashFeedSeed()\ djb2 哈希。
v2.3.0
2026年5月5日

统计子系统清理 + 表名统一

按 v2.2.0 之后的全局审查,把死代码 / 孤儿端点 / 多源同义 / 表前缀混乱一起收尾。无功能改动,纯重构 + 一致性。

优化

  • 统计表前缀统一为 ul_stats_:
  • ul_analytics_dailyul_stats_daily
  • ul_visitor_datesul_stats_visitor_dates
  • ul_visitor_post_datesul_stats_visitor_post_dates
  • InitDB 用 PG 的 DO $$ ... END $$ 做幂等迁移(老表存在 + 新表不存在时 RENAME),老库平滑升级
  • ul_stats_global / ul_stats_post_daily / ul_access_logs 已是该前缀或语义独立,不动
  • 「总访问量」三个调用点统一口径:
  • footer ArchiveStats / 后台 DashboardStats / 数据统计页 period=all 全部走新增的 handler.GlobalStats() helper,直接读 ul_stats_global 单行 O(1)
  • 之前 DashboardStatsCOUNT(*) FROM ul_access_logs 会随 90 天 prune 而"变小",现在跟 footer 永久对齐
  • 前后端"是否计入管理员"口径统一:PageViewTracker 删掉 isAdmin gate。v2.2.0 起后端"管理员也计入访问"是用户明确决定;前端再 gate 造成"管理员只 view_count +1 但 access_logs 没记"的不一致

移除

  • AccessLogger middleware:函数体已退化为 path filter + c.Next() + _ = path 实际无副作用,顺手删除 r.Use() 调用 + 只此一处用到的 skipLogPrefix / assetExt 常量
  • CleanupBotLogs / CleanupBotLogsPreview handler + 两条 /admin/analytics/cleanup-bots* 路由:这是为旧版 middleware 双写做的清理工具,middleware 不再写,该 bug 行不再产生,无前端调用
  • EnrichGeoIP handler + /analytics/enrich-geoip 路由:logAccess 已在写入时异步补 GeoIP,无积压可批量回填,无前端调用
  • InitStatsSync() 空 hook:v2.2.0 起只剩函数声明,无任何 background sync 任务
  • web/next.config.jsexperimental.staleTimes 配置:Next 16.2.4 默认 staleTimes.dynamic = 0,显式写 0 是 no-op

数据流图(改完后)

访客打开 /archives/X
  ├─ SSR: GET /api/v1/posts/X?track=1
  │    └─ maybeBumpPostView → IncrPostViews
  │         ├─ ul_posts.view_count + 1
  │         └─ ul_stats_post_daily.views + 1
  │
  └─ 浏览器: POST /api/v1/track
       └─ logAccess (一个事务):
            ├─ ul_stats_visitor_dates UPSERT       [站点 UV]
            ├─ ul_stats_global PV +1, UV +0/1      [永久站点累计]
            ├─ ul_stats_daily(_total) UPSERT       [日聚合]
            ├─ ul_stats_visitor_post_dates UPSERT  [文章 UV]
            ├─ ul_stats_post_daily.unique_visitors UPSERT
            └─ ul_access_logs INSERT               [90 天明细]

读取入口统一

数据 调用点 来源
站点总访问量 footer / 后台 dashboard / 数据统计页 all GlobalStats()ul_stats_global
时间窗口 visits 数据统计页 24h/7d/30d/year/365d sumVisits() UNION raw + stats_daily
时间窗口 unique 同上 sumUniqueVisitors() 走 stats_visitor_dates
文章累计 view_count 各处 post.view_count ul_posts.view_count(SSR ?track=1 维护)
v2.2.0
2026年5月4日

新增

  • 永久不丢失的实时统计系统。新增 3 张永久不可删表:
  • ul_stats_global:1 行站点级累计计数器(total_views / total_uniques / first_event_at),footer "总访问" 现在 O(1) 读这里。
  • ul_stats_post_daily:每篇文章每日 PV/UV,文章历史曲线的来源。
  • ul_visitor_post_dates:每篇文章每日访客唯一去重表,per-post UV 的真相源。
  • 站点 PV、文章 view_count、日聚合(_total 维度)现在事务化原子写入,单次访问一个事务,不再依赖每日 cron。
  • 首次启动自动从历史数据回填 ul_stats_global:现存 ul_access_logs 行数 + ul_analytics_daily 中 prune 早期日期的 _total 聚合 + ul_visitor_dates 累计 distinct 访客。

优化

  • footer "总访问量" 不再随 30 天 prune 而"变小"。ArchiveStats 接口由 COUNT(*) FROM ul_access_logs 改读 ul_stats_global.total_views 单行 O(1)。
  • 站点 PV / UV / 维度日聚合改为写入时实时累加(UPSERT),不再等 cron。今日数字立刻可见。
  • rollupRetentionDays 由 30 天提升至 90 天(ul_access_logs 热数据保留期)。永久数字现已在 ul_stats_global / ul_analytics_dailyaccess_logs 仅作"最近访客 / 维度 breakdown"的明细。
  • 取消 logAccess 的 30 秒去重 + 60 秒速率闸(≥8 hits 拒绝),刷新就 +1。
  • 取消管理员 skip:管理员自己访问也计入 PV / view_count。
  • 取消文章 SSR ?track=1 路径上的 IsBot UA 检查 —— 该路径 UA 永远是 Next.js 的 node,原检查导致每个真实访客的 SSR 渲染都被拦下不计数。bot 护栏保留在浏览器 /track 路径(UA 真实可信)。

修复

  • 修复 ul_posts.view_countul_stats_post_daily.views 漂移的双计 bug:旧版 logAccess(浏览器 /track)和 IncrPostViews(SSR ?track=1)都在累加 daily.views,一次 F5 文章被 +2。现 daily.views 由 SSR 路径独占。

移除

  • 删除死代码 IncrTotalViews() / GetTotalViews() / Redis stats:total_views —— PV 真相已迁移到 SQL。
  • analytics_rollup 不再聚合 _total 维度(由 logAccess 实时写入);维度 breakdown(browser / os / device / country)仍保留 cron 滚动。
v2.1.7
2026年5月4日

WordPress 风格的阅读数统计

整套阅读数实现重写。从异步客户端 +1 改成 WordPress 风格的服务端同步 +1

新数据流

访客打开 /archives/X
  ↓
Next.js SSR 拉 /api/v1/posts/X?track=1
  ↓
后端 GetPost 检查(?track=1 + 非 admin + 非 bot):
  · UPDATE ul_posts SET view_count = view_count + 1
  · 返回 +1 后的 post
  ↓
SSR 渲染 HTML(数字已经是新值)

用户的两个要求 ✓

要求 满足方式
数据实时 SSR 在同一次 fetch 里完成 +1 并返回新值,渲染出来就是最新
不增加请求 复用现有 SSR fetch,只多一个 query 参数;反而减少 v2.1.6 那个 LiveViewCount 客户端 fetch

副作用

  • 关闭 JS 也能计数(以前 JS 不跑就不计)
  • F5 刷新一定 +1(以前依赖异步丢包就丢)
  • bot UA + 登录管理员不计入

删除的东西

  • 文章页 cosmetic +1 乐观显示(4 主题:Azure / Flux / Chred / Utterlog)
  • web/components/blog/LiveViewCount.tsx(v2.1.6 引入的客户端 fetch 组件)
  • /track handler 里的 IncrPostViews 调用

/track 现在只做这些事

  • ul_access_logs 明细
  • Redis 全站 PV 计数器
  • Redis 在线访客标记
  • ul_visitor_dates(跨日唯一访客统计)

不再涉及 view_count,职责更清晰。

v2.1.6
2026年5月4日

修复

接续 v2.1.5 的阅读数实时更新修复。

根因复盘

v2.1.5 设的 experimental.staleTimes.dynamic = 0 在 Next 16.2.4 实际就是默认值(对照 node_modules/next/dist/esm/server/config-shared.js:207 确认),所以那次配置等于 no-op,首页卡片仍可能显示旧的阅读数。

v2.1.6 兜底方案

新增客户端组件 LiveViewCount:每个文章卡片 mount 后,向 /api/v1/posts/<id> 发一次 cache: 'no-store' 请求,拿到最新 view_count 后替换显示。

  • SSR 第一屏继续渲染服务端值(SEO / 首屏不受影响)
  • 客户端水合后异步把数字升级到最新
  • 无论 Router Cache、bfcache 还是浏览器其它隐性缓存,数字都会被客户端兜底刷新

接入的主题

  • Azure / Flux / Chred / Utterlog 四个主题的 PostCard.tsx

代价

首页约多 10 个 API 请求(每个卡片一次)。no-store 避开所有缓存,数据一致性的优先级高于网络成本。

v2.1.5
2026年5月4日

修复

  • 修复点击进入文章后再回首页,文章阅读数 / 评论数显示不更新的问题(必须强制刷新才能看到新数字)。
  • 原因是 Next.js 16 客户端 Router Cache 默认对动态路由保留 30 秒 RSC payload,从首页 <Link> 进文章后,浏览器内存里仍持有首页的旧 payload,后退或再次点首页时直接 replay 缓存,绕过服务端拉数据。
  • 修法:web/next.config.jsexperimental.staleTimes.dynamic = 0,强制每次导航都重新拉 RSC。静态页面继续保留 5 分钟缓存,不影响 hero / 站点信息这类内容。
  • 后端 view_count/track 接口完全正常,本次只是前端缓存策略调整。

代价:轻微失去"瞬间后退"的体验,但阅读数 / 评论数在所有页面之间保持一致。

v2.1.4
2026年5月4日

数据统计:分层保留 + 6 个时间窗口

新增

  • 6 个时间窗口:后台 → 数据统计页右上角时间切换器新增 当年 / 近一年 / 全部 三个长周期(原有 24 小时 / 7 天 / 30 天 保留)。
  • 统一 breakdown 接口 /api/v1/analytics/breakdown?period=&dimension=,返回任意时间窗口内的 visits / 唯一访客 / 浏览器 / OS / 设备 / 国家分布,每项含比例 ratio。可一次拉取所有维度(dimension=all),便于做访客统计大屏。

优化

  • 数据保留分层:ul_access_logs 稳态保留最近 30 天原始行,超期前由日 cron 聚合到永久表 ul_analytics_daily。"全部 / 当年 / 近一年" 等长周期查询走 UNION,所有累计数字永不丢失;数据库不再随访问无界增长。
  • 跨日唯一访客精确:新增永久表 ul_visitor_dates(visitor_id, date),任意时间窗口的"唯一访客数"通过 COUNT(DISTINCT visitor_id) 得出,不再 SUM 每日 unique 导致过计。
  • 最近访客面板:限制为最近 7 天 + 上限 1000 条,标题副文字"最近 7 天 · 上限 1000 条"。
  • 字段命名统一:浏览器 / OS / 设备 / 国家分布列表统一返回 {name, code, count, ratio}(之前 devices 用 type、countries 用 country,字段名不一致)。

体积估算(每日 1k 访问)

  • ul_access_logs:30 天稳态 ≈ 8 MB
  • ul_analytics_daily:一年 ≈ 1 MB
  • ul_visitor_dates:一年 ≈ 5 MB
  • 稳态总计 < 15 MB

自动归档

启动后台 cron StartAnalyticsRollupCron,每 24 小时聚合昨天及更早数据 → 删除 30 天前 raw 行。幂等,重跑安全。

v2.1.3
2026年5月4日

邮件通知整体重写

新增

  • bilibili 表情:评论邮件里 [:slug:] 标记现在会渲染为对应表情图(40 个),覆盖新评论 / 待审核 / 评论回复三类邮件。
  • 评论邮件展示访客来源:卡片头部增加昵称 + 国旗 + IP + 归属地,邮箱 / 网址独立两行,正文下方右下角显示完整时间戳。昵称如果填了网址会变成可点击链接。
  • 密码重置邮件展示申请来源:重置按钮下方新增"申请来源"块(IP + 国旗 + 归属 + 时间),便于账号主人识别是否本人发起。
  • 评论回复退订真正可用:回复邮件页脚左侧"不想再收到回复通知?点击此处退订",链接走 HMAC 签名验证,handler 写入 option,后续给该邮箱的回复通知会被自动跳过。
  • Azure 主题欢迎卡片右侧无缝:蓝色卡片右侧延伸 1px 覆盖 sidebar 灰色边框,卡片不再有"被分割线撞文章"的视觉断层。

优化

  • 邮件卡片 4px 圆角,brand 头部加 aria-hidden/user-select:none,系统朗读和文本选中都会跳过站点名直接落到正文。
  • 站点标题使用阿里妈妈方圆体,英文 Powered by 改用 Ubuntu;Powered by 居中、Utterlog! 加粗带链接(指向 utterlog.io)。
  • 邮件所有 <a>target="_blank",在 webmail 里点击不会替换掉邮件视图。
  • 邮件正文 / subject 移除装饰 emoji(⚠ / 🔗 / 📝 / 🔐 / ⏳ / 🎉 / 💬),verify_code 的提示 ℹ 改为内联 SVG。
  • 待审核评论邮件标题加上文章名(链接到原文),meta 结构与新评论邮件统一。
  • model.LookupGeo 导出供非评论场景使用。

配套工具

  • api/cmd/preview-emails:把 8 个邮件模板渲染到一张 HTML 预览页,本地开发时 cd api && go run ./cmd/preview-emails -o /tmp/x.html 一键看效果。
v2.1.2
2026年5月3日

修复

  • 修复文章评论中深层回复链被多次重复渲染的问题。 后台数据库存的只有一条,但当出现「楼主→回复→回复→回复」这种深层链时,深处的子评论会在前端被重复显示多次(楼层 2 出现 2 次、楼层 3 出现 4 次,以此类推 2^(n-1))。
  • 原因是 CommentRow 在每一层都重新调用 flattenReplies 扁平化整条链,递归调用的 CommentRow 又走一遍同样的扁平化代码。
  • 现把扁平化只保留在顶层 depth === 0,递归层不再二次扁平。Azure / Flux / Chred / Utterlog 四个主题以及公共 components/blog/CommentList 五处统一修正。

> 升级后建议刷新一下浏览器评论页缓存(强制刷新 Ctrl+Shift+R / Cmd+Shift+R)。

v2.1.1
2026年5月3日

新增

暂无。

优化

  • 优化文章页底部「友链更新」板块,后端改为从 RSS 订阅缓存随机抽取 5 条,前端卡片重新布局为左上角站点名、右上角发布时间、底部悬停显示文章标题与「阅读原文」入口;卡片复用 16:10 网格槽位,没有真实封面时使用渐变底色与 RSS 水印保持视觉一致。
  • 优化文章页底部相关板块的「换一批」按钮在友链更新 tab 下的行为,改为重新调用导航接口拉取新的随机 5 条订阅,比单纯翻页更符合换一批的语义。

修复

  • 修复文章页底部「友链更新」tab 一直显示为禁用、有订阅缓存数据时也无法点击进入的问题。原因是 tabs 的启用判断使用了占位空数组的长度,feeds 实际数据走独立变量被忽略;现改为统一通过 \count\ 字段判断,feeds 的 count 直接取真实订阅条数。

移除

暂无。

v2.1.0
2026年5月3日

新增

暂无。

优化

  • 优化前台说说散落布局,未激活的卡片硬限制最高 280px 并裁掉超长内容,无论文字多长、配图多大都不会跨行覆盖下一行卡片;点击卡片激活后解除限制查看完整内容,配合鼠标悬停升顶逻辑保证视觉层次清晰。
  • 优化前台说说卡片正文截断显示,配图说说限 2 行、纯文字说说限 6 行,配合外层硬限高让卡片高度真正可控。
  • 优化 Coding 页面 GitHub 数据缓存策略,新鲜期从 1 小时调整为 30 分钟,过期后的容忍期从 6 小时延长到 30 天。30 分钟内访问命中内存 / Redis 缓存毫秒返回;过 30 分钟在 30 天内访问立即返回旧数据并后台异步刷新接力,避免冷启动或长间隔访问时同步等待 GitHub 接口造成 4-5 秒卡顿。

修复

暂无。

移除

暂无。

v2.0.10
2026年5月3日

新增

  • 新增前台说说发布弹窗的推荐标签按最近使用动态聚合,默认展示 8 个,按每个标签最近一次使用时间倒序,不再写死 4 个固定关键词。
  • 新增 GET /api/v1/moments/recent-tags?limit=8 接口,从已发布的说说聚合最近使用过的 mood 标签。
  • 新增前台说说卡片散落布局的鼠标悬停自动升顶逻辑,光标移到哪张卡片,那张就自动浮到最上层,避免相邻卡片视觉遮挡正在阅读的内容。

优化

  • 优化前台说说卡片单图显示,统一按卡片宽度的 16:9 比例渲染,不再跟随原图高度撑高卡片导致溢出行高、压住下一行卡片;点击图片仍按原始比例弹出大图查看。
  • 优化说说发布与编辑流程,提交时新出现的 mood 标签会自动合并进后台 \moment_tags\ 选项,管理员后台标签管理器无需手工维护即可看到全部用过的标签。

修复

暂无。

移除

暂无。

v2.0.9
2026年5月3日

新增

暂无。

优化

  • 优化全站中文 UI 文案排版,统一按中文排版规范处理:中文相邻的省略号 ... 全部改为 、中文后紧跟的半角冒号 : 全部改为全角 、中文上下文里的半角括号 () 改为全角 (),覆盖 5 套博客主题与管理后台共约 200 处字符串。

修复

暂无。

移除

暂无。

v2.0.8
2026年5月3日

新增

暂无。

优化

  • 优化前台文章、分类、标签、日期和分页等列表型链接的预取策略,减少 Next.js 自动生成的 _rsc 预加载请求数量。
  • 优化前台首页 Hero 区数据加载,取消打开首页时把所有分类和模式组合一次性预拉的逻辑,改为切换 tab 时按需懒加载并把结果缓存在内存中复用,首屏减少大量 /posts?per_page=1 重复请求。
  • 优化前台 Azure 主题首页 Hero 自动轮播,循环范围跟随后台配置的 sidebar 分类,不再切到 sidebar 之外的隐藏分类,避免出现"左侧 tab 没有反应但文章在切换"的错觉。
  • 优化博客主题(Flux / Utterlog / Renascent / Chred)所有内部链接的预取行为,统一关闭 Next.js 默认 prefetch,避免视口可见或鼠标悬停时大量 ?_rsc 请求触发。
  • 优化前台 Coding 页面 SHIPPING LOG 分段显示,按"最近 3 天 / 本周 / 更早"三段分组,每段单独标题与天数计数,便于快速浏览近期与历史活动。
  • 优化前台 Coding 页面所有日期与时间按后台设置的站点时区渲染,避免浏览器本地时区影响分组边界与时间显示。
  • 优化公安备案图标改为使用本地静态资源 /images/beian/ghs.png,不再每次远程请求 beian.mps.gov.cn,加载更快也避免外部依赖。

修复

  • 修复生产 docker-compose.prod.yml.env 缺少数据库密码或 JWT 密钥时被 Compose 拒绝启动的问题,改为允许空值并把宿主机 .env 挂入容器,使 Web 安装向导写入的配置在重启后可被读取,外部数据库 / 外部 Redis 模式下的安装流程现在能完整跑通。

移除

暂无。

v2.0.7
2026年5月2日

新增

  • 新增第三方服务里的高德地图和腾讯位置服务 Key 配置,为后续国内地理编码和说说位置反查提供统一配置入口。
  • 新增同源位置反查接口,说说前台定位优先通过后端读取 Mapbox 配置解析城市名,并可使用高德、腾讯位置服务作为后续国内兜底。

优化

  • 优化 Markdown 编辑器代码相关按钮图标,区分行内代码和代码块语言选择器。
  • 优化说说来源显示,网页发布统一显示为 网页,兼容旧数据中的 localwebbrowser
  • 优化关于页面配置弹窗的模板操作按钮间距,避免按钮文字贴近边框。

修复

  • 修复新建草稿发布为正式文章时可能因 slug 唯一约束冲突导致发布失败的问题。
  • 修复草稿从文章列表快捷发布时分类、标签和足迹等关联信息丢失的问题。
  • 修复草稿编辑页会把草稿创建时间写入发布时间,导致首次发布不能使用当前发布时间的问题。
  • 修复新建文章发布时后台选择的发布时间没有写入数据库的问题。
  • 修复发布时间解析没有使用站点时区的问题,datetime-local 输入现在按后台时区设置解析。
  • 修复旧数据补全 published_at 时会错误写入未发布草稿的问题,并自动清理负数草稿 ID 上的发布时间残留。
  • 修复公开文章列表、归档、日期页、搜索结果和主题文章卡片仍按草稿创建时间展示或排序的问题,统一优先使用发布时间。
  • 修复文章创建和编辑时不能稳定从正文第一个 H1 自动识别标题的问题。
  • 修复文章创建、编辑和列表快捷状态切换失败时只显示笼统错误,无法看到后端具体原因的问题。
  • 修复前台说说位置反查失败时直接保存经纬度的问题,无法识别城市时改为提示手动填写,不再把坐标写入位置字段。
  • 修复关于页面默认模板和自定义 Markdown 模式没有严格互斥的问题,并在保存设置后同步刷新 /about 页面缓存。

移除

暂无。

v2.0.6
2026年5月1日

新增

  • 新增后台数据库清理按钮,可清理媒体库缺失文件记录、失效相册关联、孤儿文章关联、孤儿评论、足迹残留和过期授权数据。

优化

  • 优化 Coding 页面 Hero 顶部间距,删除标题上方多余空白。
  • 优化 Coding 页面 GitHub 数据缓存,支持 Redis 持久缓存、过期旧数据立即返回和后台刷新,减少打开页面时等待 GitHub API 的情况。
  • 优化 Coding 页面前端数据缓存,后台保存设置时会同步刷新 Coding 页面缓存。

修复

暂无。

移除

暂无。

v2.0.5
2026年5月1日

新增

暂无。

优化

暂无。

修复

  • 修复后台保存主题菜单或站点设置后 /api/revalidate 没有转发到 Next.js,导致前台菜单需要等待 options 缓存过期才生效的问题。

移除

暂无。

v2.0.4
2026年5月1日

新增

  • 新增 Coding/GitHub 内置页面,支持从个人资料社交链接自动识别 GitHub 地址,也可在页面管理中单独配置 GitHub 用户名或主页地址。
  • 新增 Coding/GitHub 页面 GitHub Token 配置,用于贡献统计 GraphQL 查询和提升 GitHub API 速率。

优化

  • 优化 Coding/GitHub 页面热力图,改为当前自然年贡献口径;配置 GitHub Token 后优先通过 GraphQL 读取授权可见贡献数据,并补齐全年网格避免格子比例异常。
  • 优化 Coding/GitHub 页面最近仓库列表,仓库标题只显示项目名,不再重复显示用户名。
  • 优化 Coding/GitHub 页面项目展示方式,后台可从 GitHub 公开仓库中选择要展示的项目,前台按项目展示且每个项目最多显示 5 条最近动作。
  • 优化 Coding/GitHub 页面 GitHub 组织账号支持,组织项目改用组织公开仓库接口读取,并按项目拉取最近动作。
  • 优化 Coding/GitHub 页面用户来源解析,填写 GitHub 用户地址时会自动合并该用户所属组织的公开仓库,仍不读取私有仓库。
  • 优化 Coding/GitHub 页面多 GitHub 地址支持,可同时配置多个用户或组织地址并汇总公开项目。
  • 优化 Coding/GitHub 页面标题区,移除 View on GitHub 链接,改为直接展示贡献数、仓库数和关注者统计。
  • 优化 Coding/GitHub 页面标题栏统计样式,避免统计项换行撑高标题栏,保持与其他页面标题栏高度一致。
  • 优化 Coding/GitHub 页面标题内容,标题改为 @用户名,副标题显示 GitHub 简介,并将 GitHub 头像移动到热力图右侧。
  • 优化 Coding/GitHub 页面标题栏排版,用户名和 GitHub 简介改为同一行显示。
  • 优化 Coding/GitHub 页面结构,移除中间重复的 GitHub 资料卡。
  • 优化 Coding/GitHub 页面标题栏用户名,改为 GitHub 链接并保持标题原色和无下划线样式,同时使用页面 serif 标题字体。
  • 优化 Coding/GitHub 页面 Hero 文案和版式,改为更清晰的 GitHub Journal 标题区与右侧数据摘要。
  • 优化 Coding/GitHub 页面贡献统计,标题栏 total contributions 改为全部历史贡献数,热力图图例明确显示当前自然年贡献数。
  • 优化 Coding/GitHub 页面 Hero 右侧数据摘要,改为显示今天的 GitHub contributions 数值。
  • 优化 Coding/GitHub 页面 Hero 文案,改为中英双语标题和说明。
  • 优化 Coding/GitHub 页面 section 编号,活动和项目区改为 § 01§ 02
  • 优化 Coding/GitHub 页面项目元信息样式,移除语言、Star 和 Fork 的外边框,改为轻量 inline 信息。
  • 优化 Coding/GitHub 页面 Hero 区域,移除 GitHub Journal 标签并压缩上下留白。
  • 优化 Coding/GitHub 页面活动短标签,明确区分 PUSHCOMCMT 等事件类型,避免 commit 和 comment 缩写混淆。
  • 优化 Coding/GitHub 页面活动短标签样式,不同 GitHub 事件类型使用不同的低饱和标签颜色。
  • 优化 Coding/GitHub 页面 Projects 区块,改为按日期聚合的 Shipping Log,按天展示涉及仓库和动作数量,并适配移动端单列阅读。
  • 优化 Coding/GitHub 页面 Shipping Log 卡片,移除与 ACROSS / REPOS 和徽章重复的中文文字总结。
  • 优化 Coding/GitHub 页面配置提示,明确组织仓库需要填写组织地址或仓库 URL,项目列表固定只读取公开仓库。
  • 优化后台文章管理导航结构,分类和标签从左侧子菜单移入文章模块顶部 tabs,左侧只保留文章一级入口。
  • 优化项目 README 文案,改为更偏产品介绍和使用场景的表达,减少过多技术细节。
  • 统一后台设置页与 AI 设置页的 tabs、section、卡片和表单行风格,沉淀为同一套 settings 设计组件与样式 token。

修复

  • 修复 Coding/GitHub 页面配置中填写 GitHub 仓库 URL 时只截取 owner,导致组织仓库筛选混乱的问题;仓库 URL 现在会自动解析为 owner 来源和项目筛选。
  • 修复关于页面编辑器在站点记录条目过多时弹窗内容不能滚动,导致上方配置区域被挤出视窗的问题。

移除

暂无。

v2.0.3
2026年5月1日

新增

  • 新增 Renascent 主题,基于学术极简风格提供 serif 标题、黑白灰排版和克制蓝色强调。
  • 新增文章内嵌说说短代码 [moment id="123"][/moment],文章编辑器插入说说时改为引用原始说说 ID,并在前台使用独立卡片样式渲染。
  • 新增 Markdown GitHub 仓库链接自动卡片化,正文中单独一行 https://github.com/owner/repo 会展示项目名称、描述、语言、版本号、Star、Fork 和作者头像。
  • 新增 Markdown X/Twitter 帖子自动嵌入,正文中单独一行 X 或 Twitter 状态链接会使用官方 widgets 渲染帖子。

优化

  • Renascent 主题改为独立组件和独立样式结构,按学术极简设计系统重写首页、文章页、页头、页脚和文章卡片,不再复用 Azure 的页面结构。
  • Renascent 首页进一步对齐 lixiaolai.com 的 Reborn 风格,改为文字驱动的 Hero、编号指标列表、CURRENTLY 信息条和文章目录式列表。
  • Renascent 文章页深度重构为出版式阅读版式,新增文章编号区、元信息侧栏、正文主栏、目录栏、封面题注、AI 摘要、上下篇、相关文章和评论区的统一视觉层级。
  • 补充 AGENT.md 项目协作说明,明确本地开发、主题同步、当前进度、部署、版本号和更新日志维护规则。
  • 主题菜单和资料卡设置文案改为通用描述,适配多套具备侧栏功能的主题。
  • 后台仪表盘最近文章 / 最新评论改为左右逐行严格对齐,并把 30 天访问趋势的日期标签改为月份加粗 + 日期两行、跨月才显示月份的紧凑布局。
  • 优化文章页边读边聊入口的显示时机,默认隐藏,阅读进度超过 40% 后再显示。
  • 优化 GitHub 仓库卡片的数据加载时机,改为页面加载完成后异步拉取仓库信息,并增加 5 秒兜底和加载状态。
  • 优化 GitHub 仓库卡片右侧视觉区,移除重复内容的 OpenGraph 预览图,改为 GitHub 用户头像和按语言占比渲染的全宽色条。
  • 优化 GitHub 仓库卡片元信息展示,新增 latest release 或最新 tag 版本号。
  • 优化 X/Twitter 帖子嵌入样式,恢复官方原始 widgets 渲染,不再对 iframe 做高度裁切和缩放。
  • 优化统计页面最近访客卡片的分页体验,翻页时保留表格和页面位置,只替换访客列表内容。
  • 优化 GitHub 仓库卡片为固定高度,描述改为单行省略,右侧视觉区改为轻量头像展示。
  • 优化 GitHub 仓库卡片头像定位,避免主题正文图片样式干扰,头像始终显示在右侧区域正中心。
  • 优化 GitHub 仓库卡片悬浮状态,保留边框和阴影反馈,不再产生位移。

修复

  • 修复后台仪表盘最新评论卡片不显示评论者昵称的问题(JSON 字段 author 被错误读为 author.name)。
  • 修复 Azure 主题评论提交或回复后整块评论区进入加载状态,导致页面出现重载感的问题。
  • 修复本地单端口开发时 Next.js HMR WebSocket 被开发源校验和 Go 反代升级处理阻断,导致控制台持续出现 /_next/webpack-hmr 连接失败的问题。
  • 修复 Renascent 切换后仍输出大量 Azure 组件结构和样式类名的问题。
  • 修复后台评论管理、文章预览、仪表盘最近文章和最新评论中的文章链接仍写死 /posts/slug 或跳转编辑页的问题,统一跟随站点固定连接打开真实文章页,并在基础设置保存后刷新后台站点链接缓存。
  • 修复 GitHub 仓库卡片被主题文章链接样式覆盖,导致卡片标题、描述和元信息出现下划线的问题。
  • 修复 GitHub 仓库卡片右侧视觉区被内容高度拉伸导致布局异常的问题。

移除

暂无。

v2.0.2
2026年4月29日

新增

  • 关于页面新增结构化个人主页模板,支持个人资料、MBTI、兴趣爱好、音乐偏好和站点更新记录。
  • 关于页面支持在默认模板与自定义 Markdown 正文之间切换,Markdown 内容单独保存。

改进

  • 友情链接分类管理支持上移/下移调整分类顺序,前台友链页按后台分类顺序展示。
  • 后台系统页面的关于页编辑改为填表式配置,并继续兼容旧版自定义 HTML 内容。
  • 页面管理中内置关于页的操作按钮明确指向关于页面配置编辑器。

修复

  • 修复 WordPress 同步分类和标签时按大小写敏感 slug 查重,导致 Debian / debian 等同名标签重复生成 meta 的问题,并在再次同步时合并已产生的来源重复项。
  • 修复主题自定义头部按钮和页脚图标时,Azure 固定随机访问按钮与固定 RSS 按钮可能被覆盖或挤出的问题。
v2.0.1
2026年4月29日

新增

  • 新增 content/ 运行时内容目录,后台上传主题统一保存到 content/themes/,插件统一保存到 content/plugins/

改进

  • 优化最近访客统计口径,同一访客会话只显示入站页面,并将会话内后续页面停留时间合并为总时长。
  • 优化相册管理页「新建相册」和空状态「创建第一个相册」按钮宽度与水平留白。
  • 优化 Utterlog 网络状态卡片的手动推送按钮宽度和水平留白,避免长文字贴近按钮边缘。
  • 移除安全设置中的 IP 信誉功能,后台不再显示 IP 信誉 tab,后端不再记录本地风险评分或按评分自动封禁。
  • 安全设置防御配置重构为统一表单行样式,访问控制、CC 防御和 GeoIP 封锁统一由底部「保存设置」提交,并补充个人博客使用提示。
  • 安全设置的防御设置保存按钮文案统一为「保存设置」。
  • 主题和插件管理改为优先读取 content/ 目录,同时兼容旧版根目录 themes/plugins/,避免升级后隐藏已有扩展。
  • /themes/* 资源路由改为先读取用户上传主题资源,再回退到内置主题资源,内置主题源码继续保留在 web/themes/ 参与前端构建。
  • Docker 开发与生产编排新增 content 持久化挂载,系统备份与恢复同步覆盖 content/ 运行时扩展目录。
  • 移除 API 侧重复的内置主题截图文件,内置主题静态资源统一由 web 侧构建产物提供。

修复

  • 修复统计页面选择「全部」时间范围时国家/地区和来源聚合 SQL 条件拼接错误,导致已有国家数据也显示为空的问题。
  • 修复数据统计访客地图在低缩放级别关闭世界复制时横向边界被夹住,导致只能上下拖动不能左右拖动的问题。
  • 修复前台访问统计组件在部分运行时拿不到 zustand persist hydration API 时导致页面崩溃的问题。
  • 修复 API 生产 Docker 镜像构建时因 api/public/ 目录没有跟踪文件导致 CI 无法复制 public 目录的问题。
  • 修复 /themes/*HEAD 请求可能落到 Next.js 回退路由并返回异常的问题。
  • 修复主题静态资源路由可能暴露点文件的问题。
v2.0.0
2026年4月29日

新增

  • 后台安全设置新增 GeoIP 数据源选择,可在默认 api.ipx.eecnip.io 之间切换。
  • 友情链接分类新增持久化配置,每个分类可单独选择卡片式或图标式显示。
  • 友情链接新增图标式展示样式,分类可按需切换为紧凑的一行多列布局。

改进

  • Azure 主题文章页 banner 恢复为与首页文章卡片一致的宽屏显示比例,避免 16:9 在文章页占用过高。
  • Azure 主题侧边栏资料卡改为贴边满宽直角样式,并接管原首页顶部社交 icon 到卡片右下角显示。
  • Azure 主题侧边栏排序调整,资料卡下方优先显示最新评论,文章、说说和友链统计卡片下移。
  • 访客统计、评论归属地、GeoIP 封锁、WordPress 导入评论归属地和服务器出口 IP 识别统一使用同一套 GeoIP provider。
  • 内置页面标题统一跟随后台页面名称,补齐说说、订阅、友链、相册、音乐等页面的浏览器标题。
  • 后台纯图标操作按钮统一为正方形边框样式,图标居中显示。
  • 版本线整理为正式发布版:历史版本合并为 1.0.0,当前版本作为 2.0.0 正式发布。

修复

  • 修复友情链接新增空分类刷新后丢失的问题,并允许默认分类修改显示名称。
  • 修复友情链接没有链接的分类仍在前台显示的问题。