Repository navigation
Releases: SeanDictionary/SeanBlog-Frame
Release list
v0.7.2
[0.7.2] - 2026-09-10
Added
- 新增《使用指南》文档
docs/usage-guide.md:面向博主/站长的完整日常使用文档,覆盖登录与认证、后台仪表盘、文章管理与写作(含 Callout/github-repo/friend-link 三种特殊卡片语法、代码块、KaTeX、GFM、原始 HTML)、文章字段、状态与定时发布、修订历史、ZIP 导入导出、分类标签、评论审核与黑名单、媒体库、主题管理与 Callout CSS 编辑、站点设置、访问分析、操作日志、SEO 自动生成,并附特殊卡片语法速查表。README「文档索引」同步加入链接。
Fixed
- 文章编辑器预览与前台实际页面显示不一致:平台
globals.css给.article-content a统一加了下划线(特异性 0,1,1),而主题(如 Cardinal)靠主题正文容器 scope(如.cf-post-content .gh-card__link,特异性 0,2,0)才能覆盖回无下划线。但预览容器只挂article-content不挂主题容器 class,导致主题覆盖规则不命中,卡片链接出现下划线。现于.article-content块内追加.github-repo-card a, .friend-link-card a { text-decoration: none }作为平台兜底,不加.article-contentscope 保持特异性 0,1,1,靠层叠顺序压过正文链接规则;主题仍可用任意带 scope 的选择器(0,2,0)覆盖此兜底实现个性化样式,保证预览与前台一致且不锁死主题自由度。
Changed
- github-repo / friend-link 卡片指令现同时支持三种写法:容器指令(
:::github-repo{...}\n:::)、叶子指令(:::github-repo{...}单行无闭合)、行内指令(:github-repo{...})。此前仅支持容器指令,单行写法无法渲染。使用文档同步更新语法示例。README 新增「使用指南」板块链接到完整使用文档。
镜像
docker pull ghcr.io/seandictionary/seanblog-frame:0.7.2
部署见 docker-compose.yml。
Full Changelog: v0.7.1...v0.7.2
v0.7.1
[0.7.1] - 2026-09-10
Changed
- UA 解析拓充与标签简化:
parseBrowser在Chrome/兜底规则之前新增 App 内置 WebView 与厂商浏览器识别,修复微信/微博/支付宝/钉钉/飞书/QQ/夸克/华为/小米/OPPO/vivo/遨游/三星/Samsung/Opera 等 UA 因含Chrome/(Chromium 内核)被误记为 Chrome 的问题(微信 iOS 无Chrome/原被记为"其他浏览器")。同步补 iOS 客户端漏判:CriOS/→Chrome、FxiOS/→Firefox、OPiOS/→Opera(iOS 受 Apple 限制用 WKWebView,UA 不含Chrome//Firefox//OPR/)。标签去掉"浏览器""内置浏览器"后缀,只取品牌 / 产品名(微信/微博/支付宝/钉钉/飞书/QQ/UC/360/搜狗/猎豹/百度/夸克/遨游/华为/小米/OPPO/vivo/三星),手机 QQ 客户端与 QQ 浏览器统一归"QQ"。OS 与爬虫解析不变。
Fixed
install.sh拉取镜像失败回退本地旧镜像时,原先照常打出成功横幅、用户无从判断是否更新生效。现捕获docker compose pull结果:失败时在横幅首行用红色警告「镜像拉取失败,本次升级未生效,仍运行旧镜像」,并在末尾给出重试建议与当前运行版本(旧版镜像无version字段时显示「未知」),避免误判升级成功。
镜像
docker pull ghcr.io/seandictionary/seanblog-frame:0.7.1
部署见 docker-compose.yml。
Full Changelog: v0.7.0...v0.7.1
v0.7.0
[0.7.0] - 2026-09-10
Added
- 访问记录页新增「访问来源字段分布」弹窗:在
/admin/visits表头的「地区 / IP」「系统」「浏览器」「来源 URL」四列点击(带fa-chart-simple图标)即弹窗,按当前start/end日期范围聚合该字段全部值的分布,显示「统计值 / 计数·合计 / 百分占比」。本站内部跳转、直接访问、未采集、其他等哨兵值不纳入统计,合计只含已知值。 AnalyticsEvent新增派生列referrerDomain/operatingSystem/browser(均String?)与country/referrerDomain/operatingSystem/browser四个索引,供统计直接groupBy低基数列,避免对高基数原始referrer/userAgent聚合。- 新增
GET /api/admin/analytics/field-stats?field=country|referrer|os|browser&start=&end=(仅 admin):返回{ items: [{label,count}], total },统计前懒回填派生列。 - UA / 来源解析提取为公共模块
src/lib/analytics/parse.ts(parseBrowser/parseOperatingSystem/extractReferrerDomain/normalizeHost/isCrawlerUa),写入、访问记录展示、统计懒回填共用,保证解析口径一致。
Changed
- UA 解析修正与拓充:
parseBrowser先判爬虫再判国产浏览器(QQ/UC/360/搜狗/猎豹/百度)→ Edge(含Edg/EdgA/EdgiOS)→ Chrome/Firefox/Safari,修复 QQ 浏览器等被Chrome/抢匹配、Edge Android(EdgA/)漏判的问题;parseOperatingSystem将 iOS(iPhone|iPad|iPod)提到 macOS 之前,修复 iPhone/iPad UA 里like Mac OS X导致 iOS 被误记为 macOS(dev 数据 135 条)。 - 爬虫识别并入 UA 解析:
parseBrowser对爬虫 UA 返回「爬虫」,新增isCrawlerUa(/bot|crawler|spider|slurp|externalhit|externalagent/i,实测 18 爬虫全中、0 真人误判)。 AnalyticsEvent新增isBot Boolean?与@@index([isBot]),写入时落值、统计前懒回填 NULL。趋势图与各字段统计统一用isBot排除爬虫,不再依赖browser='爬虫'过滤。- 统计总览页全站访问趋势图新增「不含爬虫的访问量」线(绿色
viewsHuman),与「访问量」(蓝)、「访客数」(琥珀虚线)并列;buildTrend按isBot拆分真人/爬虫访问量。 - 访问来源字段分布弹窗:爬虫不纳入合计,弹窗底部单独显示「爬虫 N 条(不计入合计)」;
GET /api/admin/analytics/field-stats响应新增crawlerCount。 - 访问记录列表与详情按
isBot标记爬虫:列表爬虫行醒目燕麦色背景(浏览器列显示「爬虫」)、详情弹窗标题旁显示「爬虫访问」徽标。访问详情的系统/浏览器改用存储字段(operatingSystem/browser)直接显示,不再在读取时解析 UA(getAnalyticsVisitors统计前懒回填派生列,保证存量行也填好)。 - 后台统计总览页时间范围控制重构:移除「最近访问记录」「文章统计」「访问来源地区」「访问系统」四张卡片各自的独立时间选择器,合并为全站访问趋势卡片顶部的时间范围选择(
rangeDays),单一控制趋势、文章统计、来源地区、访问系统四张卡片的数据采样;最近访问记录固定展示最新 20 条,不再受时间范围影响。原 URL query 参数trendRangeDays/articlesRangeDays/recentRangeDays/sourcesRangeDays/systemsRangeDays合并为rangeDays(破坏性变更,不影响主题契约)。 - 总览页时间范围选择器从「全站访问趋势」卡片右上迁移至页面标题栏右侧(原「访客统计」跳转按钮位置),并移除「访客统计」跳转按钮(该入口仍可从侧边栏「访问记录」进入)。
AnalyticsTrendChart同步移除标题栏右上重复的图例 fallback(底部始终有图例),避免迁移后出现图例重复。
Fixed
- 修复统计总览页切换到 90/180 天时控制台报
Each child in a list should have a unique "key" prop的问题:作为toolbarprop 传入AnalyticsTrendChart并在标题栏与兄弟节点并列渲染的范围选择表单元素缺少key,React 19 在该元素由父组件创建、被子组件放入列表时提示缺 key。给该元素显式加上key即消除警告。
镜像
docker pull ghcr.io/seandictionary/seanblog-frame:0.7.0
部署见 docker-compose.yml。
Full Changelog: v0.6.0...v0.7.0
v0.6.0
[0.6.0] - 2026-09-09
Added
- 主题包支持「就地更新」:上传安装时新增
mode字段(install默认 /update)。update模式下 slug 已存在时先备份旧目录再覆盖写入,写入失败回滚到备份,不删除ThemeCustomization数据库行,用户自定义设置自然保留;可直接更新当前活跃主题(无需先切走)。若新版本号低于旧版本号,响应携带降级提示警告。后台「主题」页每张已安装主题卡新增「更新」按钮,复用导入确认弹窗并按mode=update提交。 - 新增站点设置
siteIcon(站点图标 URL / favicon):后台「设置 → 站点信息」新增字段,保存后注入到前台主题渲染<head>的<link rel="icon">(经enrichCtx追加到seo_head,主题无需改动)与后台 root layout head;ctx.site.icon供主题模板消费。 - 新增
GET /api/admin/version接口:读取当前package.json版本,拉取 GitHubSeanDictionary/SeanBlog-Frame的 latest release tag 做 semver 比较,带 5 分钟内存缓存。 - 后台侧边栏「SeanBlog Admin」下方新增小字版本行(当前版本号),点击触发版本检查;存在新版本时以绿色跟随显示新版本号,检查失败显示提示。
GET /api/health响应新增version(来自打包进镜像的package.json.version):供install.sh在部署/升级时读取当前运行版本,无需鉴权。install.sh据此在完成横幅打印「已升级 v旧 → v新 / 首次安装 v新 / 已运行 v新(未变化)」;应用未就绪或旧版 health 无version字段时省略版本行(向后兼容)。- 搜索索引自愈:
searchArticles现在搜索前先检测是否有searchText为 NULL 的已发布文章,有则现算(buildSearchText,纯 regex 无 Shiki)并用 raw SQL 写库补齐,然后走正常列查询。搜索结果不再依赖索引是否已预热——索引只是缓存,缓存冷时在搜索路径上即时补齐(只发生一次,之后纯快路径)。0.5.0 升级后无需任何手动回填步骤即可搜索。 - 新增
POST /api/admin/articles/backfill-search接口(仅 admin,记操作日志):可选的「预热」操作,原地回填存量文章的contentHtml/searchText(幂等,?force=1全量重渲染、?limit=N限量)。用 raw SQL 只写这两列、不触发@updatedAt,故不改动文章更新时间。非必须——搜索现已自愈。
Changed
- 文章渲染管线新增
wrapFriendLinkGrid后处理步骤:将连续的<figure class="friend-link-card">自动包裹在<div class="friend-link-grid">容器中,供主题侧以 CSS Grid 实现自适应多列布局。 - 后台「主题」页设置读取统一走
getThemeSettings(与公开渲染同一条缓存路径),不再直接拼dbRow + schema 默认值,消除settingsVersion未迁移前后台与前台显示不一致的窗口。 ThemesManager客户端 state 同步:活跃主题切换或themeSettingsprop 变化(如就地更新后服务端返回新值)时重建liveValues/themeSettingsState,避免表单与 Callout CSS 显示旧主题/旧值。- 保存主题设置时对「合并后的整体」跑一次
validateThemeSettingsValues,schema 收紧(如删除某 select 选项)时能及时发现库里残留旧值。 - 主题包导出显式排除磁盘上可能残留的
theme-settings.json,避免陈旧快照被打进发布包;includeSettings=true时改用新鲜快照。 - 导入失败回滚区分
install/update:install模式设置应用失败仍删整包回滚;update模式不删已更新的文件(避免丢掉旧版)。
Fixed
- 修复 ZIP 导入不回填
contentHtml/searchText的问题:导入路径createArticleInTransaction此前创建后只写contentPath,导致导入的文章searchText为 NULL(基于该列ILIKE的搜索失效,搜不到)、contentHtml为 NULL(文章页每请求实时 Shiki 渲染 ~800ms)。现与createArticle/updateArticle一致,导入即预渲染 HTML 并生成搜索文本。 - 修复
createArticle/updateArticle/ 导入路径写contentHtml列时用错字段名(写成contentHtml,实际模型字段为legacyContentHtml,@map("contentHtml"))导致这些路径运行时PrismaClientValidationError500 的问题。此前因站点文章均经导入而非后台新建/编辑产生,该 bug 未被触发。 - 修复 ZIP 导入不保留
article.json的updatedAt的问题:此前导入虽在create时传入了updatedAt,但随后的contentPathupdate 被 Prisma@updatedAt自动覆盖为当前时间,导致导入的文章更新时间全部变成导入时刻。现在该 update 中显式回写updatedAt,导入的文章保留原更新时间。 - 修复维护性回填会踩文章更新时间的问题:
backfillArticleSearchContent原用prisma.article.update写contentHtml/searchText,被@updatedAt自动覆盖updatedAt为当前时刻,会毁掉导入时保留的 WP 修改时间。现改用 raw SQLUPDATE "Article" SET "contentHtml"=..., "search_text"=... WHERE "id"=...,只动这两列、不碰updated_at。搜索路径的懒回填同样用 raw SQL。 - 修复更新主题需「先卸载再上传」、卸载又禁止删除活跃主题导致的 4 步流程与设置丢失问题。
- 修复前台分类/标签页 404 误报 500:
publicErrorResponse现识别ApiError.status,404 统一走主题404.hbs(回退内置静态 404),其他业务错误码透传其 status。此前/tags/<不存在>、/categories/<不存在>因classifyError仅判数据库错误、忽略ApiError.status而返回 500;文章详情路由靠手写兜底才正确,现已清理该重复兜底。 - 修复 GitHub 风格提示框
[!IMPORTANT]/[!CAUTION]拿不到对应配色的问题:remark-github-admonitions-to-directives默认把IMPORTANT映射成info、CAUTION映射成danger(为对齐 Docusaurus 词汇表),而主题callout.css配的是.callout--important/.callout--caution,导致这两种写法命不中配色、回退灰。现给插件传自定义mapping,让IMPORTANT→important、CAUTION→caution,与指令写法:::important/:::caution同名同样式(对齐 GitHub 5 种词汇表)。同时把CALLOUT_TYPES收敛为这 5 种(note/tip/important/warning/caution),移除info/success/danger::::info等不再渲染成提示框(已确认无存量内容使用)。:::callout{type=...}显式形式不受影响,仍可作自定义逃生口。
Removed
- 移除主题「主页预览 / 文章预览」功能(
/theme-preview路由、后台卡片上的两个预览入口、docs预览小节及相关说明)。预览页本是平台内置的简化 React 骨架,无法真实反映主题 Handlebars 模板与模板级自定义设置(如 cardinal 的showTopBar、heroWidth、sidebarPosition等),易误导上线判断;直接移除该功能,主题效果以真实前台为准。后台「主题」页每张主题卡按钮随之重排为单行:启用 / 更新 / 导出 / 卸载(默认主题仅启用)。
Upgrade Notes
从 0.5.0 升级到本版本需要手动执行以下步骤:
-
数据库迁移:容器启动时自动执行
prisma migrate deploy,新增contentHtml和searchText列。 -
搜索索引:无需手动操作。0.5.0 之前导入的文章
searchText/contentHtml为 NULL,升级后首次搜索会自动现算并用 raw SQL 补齐searchText(不改动文章更新时间),之后纯快路径——搜索结果不依赖索引是否预热。searchText:搜索硬依赖,但懒回填已保证 NULL 行也能搜到并即时补齐,无需任何手动步骤。contentHtml:仅文章页渲染性能优化。NULL 时文章页回退实时 Shiki 渲染(功能正确,~800ms,ISR 5 分钟兜底),不影响搜索。- (可选)想避免首次搜索的一次性现算成本、或预热
contentHtml省掉文章页首次渲染,可在后台已登录态调一次POST /api/admin/articles/backfill-search(幂等、raw SQL 不踩updatedAt),返回{ processed, skipped, errored, total }。非必须。 - 0.5.0 之后新建 / 编辑 / 导入的文章已在各自写入路径自动生成这两列,不会再产生 NULL。
-
更新 cardinal 主题:后台「主题」页使用「更新」按钮上传新主题包(见 Added 第一条)。
-
缓存失效:ISR 缓存 5 分钟,发布后最多等 5 分钟所有页面自动刷新。或手动触发:
curl http://localhost:3000/
镜像
docker pull ghcr.io/seandictionary/seanblog-frame:0.6.0
部署见 docker-compose.yml。
Full Changelog: v0.5.0...v0.6.0
v0.5.0
[0.5.0] - 2026-09-06
Added
- 后台「文章」列表支持分页浏览、每页条数选择(20/50/100,默认 20)。分页导航与每页选择器样式对齐访问记录页(
/admin/visits:min-w-8圆角 pill + 省略号 +每页 [select] 条),置于文章表格卡片底部。页码与每页条数均为 URL 驱动(page/pageSize查询参数),与现有搜索、状态/分类/标签过滤、排序参数协同:翻页保留全部过滤态,搜索/排序/切页数时自动回到第 1 页。修复此前 76 篇文章只展示前 20 条、无法翻页的问题。 - 统计总览页「最近访问记录」由最新 10 条增至 20 条。
- 全站 404 分层化:前台未匹配路径与不存在的文章 slug 统一返回 404 页。主题可在
templates/404.hbs提供自定义 404(套default布局,ctx 含page:'404'、站点/主题/侧边栏基础数据、robots: noindex的 seo);未提供时回退到平台内置 404 静态页(与 500/503 错误页同款样式,带「返回首页 / 返回上一页」按钮)。后台/admin/**未匹配路径走独立admin/not-found.tsx(在 admin layout 内、需登录,带「返回后台首页」)。/api/**未匹配路径返回 JSON{error:{code:"NOT_FOUND",...}}(4xx 契约),避免被前台兏底抢占而返回 HTML。
Changed
- 不存在的文章 slug 不再返回纯文本
Article not found,改为走统一的 404 渲染入口(主题 404.hbs 或平台内置 404 页)。 - 主题契约扩展:新增可选模板
404.hbs与templateExists探测,向后兼容(主题不提供则跳过)。 - 修订发版与 push 约定(见
AGENTS.md与根CHANGELOG.md发布流程段):Themes 仓库不打任何 tag、常规改动仅本地提交、仅正式发版时 bumptheme.yamlversion + CHANGELOG 版本段后 push 触发 CI;Frame 仓库常规改动保持原子化提交 + 同步 CHANGELOG、默认本地,仅迭代版本号时 bumppackage.jsonversion + CHANGELOG 版本段 + 打v*.*.*tag 后 push 触发 CI。
镜像
docker pull ghcr.io/seandictionary/seanblog-frame:0.5.0
部署见 docker-compose.yml。
Full Changelog: v0.4.0...v0.5.0
v0.4.0
[0.4.0] - 2026-09-05
Added
- 媒体库支持分页浏览、每页条数选择(20/50/100,默认 20)与关键字搜索(按文件名/存储路径模糊匹配,防抖 350ms)。分页导航与每页选择器样式对齐访问记录页(
/admin/visits:min-w-8圆角 pill + 省略号 +每页 [select] 条),并置于媒体库卡片右上角。媒体网格改为auto-fill, minmax(240px,1fr)响应式布局,按窗口宽度灵活呈现 3 列或 4 列。
Fixed
- 修复媒体库仅展示最新 100 张、更早的媒体(如先单独上传、随后批量导入文章附图时最早的那张)在后台不可见但仍可经 URL 访问的问题。根因:媒体页服务端硬编码
page:1, pageSize:100并丢弃分页元信息,按createdAt desc截断后第 101 条以后的记录无法操作。现改为读取page/pageSize/q查询参数并完整分页,所有媒体均可翻页到达并删除;删除/上传后按当前页刷新或回到第 1 页,删尽当前末页时自动回退一页。 - 修复
install.sh升级时只判断本地docker-compose.yml是否存在、从不与仓库最新版同步的问题。这会导致新版本引入的卷/服务(如seanblog_uploads)不生效——镜像已是新版但 compose 仍是旧版,表现为数据不持久或功能异常。现改为始终拉取最新版比对:本地过期则备份(docker-compose.yml.bak.<时间戳>)后更新,相同则跳过,离线时回退本地版本。README「升级」章节同步补充警示:不要只用docker compose pull && up -d升级。
镜像
docker pull ghcr.io/seandictionary/seanblog-frame:0.4.0
部署见 docker-compose.yml。
Full Changelog: v0.3.0...v0.4.0
v0.3.0
[0.3.0] - 2026-09-04
Fixed
- 修复后台「主题」页在客户端切换活跃主题后(如导入主题包后点击「启用」、或在多主题间切换),新主题中依赖默认值即勾选的级联设置项(典型如 Cardinal 的「侧边栏内容」默认含「个人简介」时,其下的头像/签名/自定义 HTML 等项)不显示、需刷新或重新勾选才出现的问题。根因是驱动显隐的
liveValues仅在组件首次挂载时按当时活跃主题构建,切换主题后 schema 已变但liveValues未按新主题重新构建,导致computeVisibility把默认就应勾选的项判为不可见。现改为在活跃主题 slug 变化时重新由 schema 默认值 + 当前themeSettings构建liveValues。 - 修复媒体库上传图片后前台无法查看(
/uploads/...返回 404)的问题。根因:上传文件写入public/uploads/,而 Next.js 生产模式(standalone)仅在服务器启动时把public/文件清单缓存一次,运行时新增的文件不在缓存中、不会被服务(dev 模式 dev server 每次读文件系统,故本地开发不可见)。同时修复上传媒体在重建容器时丢失的问题(原public/uploads无持久化卷)。
Changed
- 媒体存储迁出
public/:上传内容改写入独立存储根storage/uploads/(可由UPLOADS_DIR覆盖;容器内/app/storage/uploads,挂命名卷seanblog_uploads持久化),由新增的 catch-all 路由src/app/uploads/[...path]/route.ts流式服务(Content-Type、Range、长缓存、路径越界保护)。URL 前缀/uploads/不变;Media.key规范化为相对存储根的相对路径(media/{category}/{filename}、article-imports/{slug}/{filename}),服务端按 URL 路径读取、不依赖 DB 查询。集中存储逻辑于src/lib/media/storage.ts,为未来对象存储(S3/R2/MinIO)留接缝。文章导入附件同步迁入storage/uploads/article-imports/。 - 新增一次性迁移脚本
scripts/migrate-uploads-to-storage.mjs(幂等、--dry-run),把历史public/uploads/*搬到storage/uploads/*并规范化Media.key。
升级注意
升级到本版本后,需在服务器运行迁移脚本:
docker compose exec app node scripts/migrate-uploads-to-storage.mjs --dry-run # 预览
docker compose exec app node scripts/migrate-uploads-to-storage.mjs # 执行镜像
docker pull ghcr.io/seandictionary/seanblog-frame:0.3.0
部署见 docker-compose.yml。
Full Changelog: v0.2.1...v0.3.0
v0.2.1
[0.2.1] - 2026-09-03
Fixed
- 修复主题资源(
{{asset}}引用的assets/js/main.js等)在经 CDN/WAF 二次编码 URL 后返回 500INTERNAL_SERVER_ERROR的问题。根因是assethelper 对路径做了encodeURIComponent,/被预编码成%2F,再被中间层二次编码成%252F,服务端解码一次后仍残留%2F导致文件命中失败。改为不再预编码斜杠,并在资源路由对查询串做防御性解码;缺失资源现返回 404 而非 500。
镜像
docker pull ghcr.io/seandictionary/seanblog-frame:0.2.1
部署见 docker-compose.yml。
Full Changelog: v0.2.0...v0.2.1
v0.2.0
[0.2.0] - 2026-09-03
Added
- 部署支持通过
APP_PORT环境变量自定义对外端口(默认 3000,仅改宿主机映射,容器内固定 3000);install.sh与docker-compose.yml均已支持。 - 后台设置页(站点信息 / 访问统计 / 页脚)保存通知统一为 toast 弹窗,替代底部红字提示。
- 页脚自定义 HTML(
publicFooterText)与 RSS 显隐(publicFooterShowRss)接入seanblog-default主题 footer partial;容器为空样式,只继承字体颜色/大小,直接子元素margin清零,间距由内联style控制(seanblog-default主题版本 2.1.0)。
Changed
- CSRF 同源守卫(
requireSameOriginRequest)在Origin未命中允许集合时回退检查sec-fetch-site,修复反向代理/CDN 终结 TLS、siteUrl缓存未预热等场景下合法后台请求被误判为跨站而 403 的问题;跨站 CSRF(sec-fetch-site: cross-site)仍被拦截。
Removed
- 废弃的页头站点设置(
publicHeaderTitle、publicHeaderShowHome/ShowCategories/ShowTags/ShowSearch);页头导航/搜索/主题切换完全归主题theme.config.*控制。
镜像
docker pull ghcr.io/seandictionary/seanblog-frame:0.2.0
部署见 docker-compose.yml。
Full Changelog: v0.1.0...v0.2.0
v0.1.0
[0.1.0] - 2026-09-02
首个完整版本:一个面向长期使用的个人博客 CMS,用于替代 WordPress,基于 Next.js 单体全栈架构。
Added
内容与渲染
- 文章管理:Markdown 文件存储,支持草稿 / 发布 / 归档 / 定时发布 / 置顶 / 修订历史 / ZIP 导入导出;区分文章与页面,页面不进首页列表
- Markdown 渲染管线:unified + remark/rehype + Shiki 代码高亮 + KaTeX 数学公式 + 标题自动锚点
- 分类与标签:slug 唯一性校验、批量操作
- 评论系统:嵌套回复、人工审核、黑名单规则、按文章关闭 / 只读
后台管理
- 媒体库:任意类型文件上传,按 MIME 分类存储,支持多选 / 粘贴 / 拖拽上传
- 后台概览仪表盘:统计卡片、文章热度、最近评论、趋势图
- 操作日志:记录浏览器指纹 / 硬件特征,保留期可配置,支持 CSV 导出
主题系统
- 基于 Handlebars 模板引擎 + 平台
data-*渐进增强的前台主题框架 - 运行时上传与启用主题、版本化设置 schema(支持 select / range / textarea / list、条件显隐、1–2 层分组混用)、实时预览、设置快照导入导出与跨版本迁移
- 默认主题
seanblog-default随镜像发布并种子到命名卷;Cardinal主题设置兼容迁移
访问分析
- 访客与访问记录采集、每日物化统计、GeoIP 解析、GPU / 硬件特征规范化清洗后入库
- CSV 导出、交互式趋势图、访客维度下钻
SEO
seo_head注入 title / OpenGraph / canonical / JSON-LDsitemap.xml、robots.txt、/rss.xml动态生成,站点 URL 来自后台设置运行时读取
认证
- Auth.js v5 Credentials Provider + JWT 会话,单管理员模式
部署
- Docker 多阶段构建:Next.js standalone 输出 + Postgres 16 + 一次性密钥初始化服务
docker-compose.yml编排:密钥生成、数据库健康检查、启动脚本自动migrate deploy/ 初始化管理员 / 初始化内容- 单一 compose 文件同时声明
image与build:部署机只需下载docker-compose.yml即可拉镜像部署,加--build可从源码构建,无需维护两份文件 install.sh一键安装/升级脚本:自动检查依赖、下载 compose、拉镜像启动、等待就绪,并把首次管理员密码直接打印到终端- GitHub Action:打
v*.*.*标签自动构建多架构(amd64 / arm64)镜像推送 GHCR,并用本文件对应版本段落创建 GitHub Release - 站点 URL 改为后台「站点信息」设置项运行时读取(带 30s 短缓存),镜像与站点域名解耦,无需重建即可改域名
工程基线
- TypeScript strict、Prisma 7、Tailwind CSS v4、Next.js 16 App Router(Turbopack)
Security
- 内容安全策略(CSP):放行 Google Fonts / cdnjs,其余默认收紧
- Handlebars 恢复默认 HTML 转义,修复评论存储型 XSS
- 主题
cssVariable设置值在输出层转义,防 CSS 注入 - admin API same-origin 请求校验(Origin / Sec-Fetch-Site),防 CSRF
- 会话版本号吊销机制 + JWT
maxAge收紧到 7 天
镜像
docker pull ghcr.io/seandictionary/seanblog-frame:0.1.0
部署见 docker-compose.yml。
Initial Release