<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://bytefun.site/</id>
  <title type="text">晚风如歌</title>
  <subtitle type="text">晚风的博客</subtitle>
  <updated>2026-10-06T13:20:58.000Z</updated>
  <author><name>木子</name></author>
  <link rel="alternate" href="https://bytefun.site/"/>
  <link rel="self" href="https://bytefun.site/atom.xml"/>
  <generator uri="https://github.com/CuteLeaf/Firefly">Firefly v6.16.8</generator>
    <entry>
      <id>https://bytefun.site/posts/new-42fd0bdb/</id>
      <title type="text">上下班打卡APP</title>
      <published>2026-10-06T13:20:58.000Z</published>
      <updated>2026-10-06T13:20:58.000Z</updated>
      <author><name>木子</name></author>
      <link rel="alternate" href="https://bytefun.site/posts/new-42fd0bdb/"/>
      <summary type="text">基于一次迟到开发出来的APP😭</summary>
      <content type="html"><![CDATA[<section><h1>上下班打卡：开发记录与使用说明<a href="#上下班打卡开发记录与使用说明"><span>#</span></a></h1><p>本文记录「上下班打卡」从首次开发到 1.2 版的实现、问题修复与验证过程，并提供与当前界面一致的操作说明。</p>

<table><thead><tr><th>项目</th><th>内容</th></tr></thead><tbody><tr><td>文档整理日期</td><td>2026 年 10 月 6 日</td></tr><tr><td>当前应用版本</td><td>1.2（构建号 3）</td></tr><tr><td>运行设备</td><td>安装 iOS 27 或更新系统的 iPhone</td></tr><tr><td>已验证开发环境</td><td>Xcode 27.0（27A266a）、iOS 27 SDK、Swift 6</td></tr><tr><td>已验证真机</td><td>iPhone 17，iOS 27.0</td></tr><tr><td>界面与存储</td><td>SwiftUI、SwiftData、本机照片文件</td></tr><tr><td>使用方式</td><td>无需注册、无需后端服务器；记录默认保存在本机</td></tr></tbody></table><p>本文根据现有源代码、开发对话和留存验证材料整理。下文的编译与测试结果是此前实际执行的记录；本次文档整理没有重新运行测试，也不将未验证项目写成已经通过。</p><section><h2>一、开发过程<a href="#一开发过程"><span>#</span></a></h2><section><h3>1. 初始需求与产品范围<a href="#1-初始需求与产品范围"><span>#</span></a></h3><p>最初目标是开发一个可以直接编译、安装和使用的个人上下班打卡工具，而不只是界面演示。首版范围包括：</p><ul>
<li>每天分别记录一次上班、下班打卡，附带可选照片和备注。</li>
<li>显示当天状态，提供月历、历史筛选、搜索和记录详情。</li>
<li>设置上下班本地通知，选择提醒时间及生效星期。</li>
<li>修改、补录和删除记录前确认，导出 CSV，清除本机数据。</li>
<li>处理权限拒绝、保存失败、照片不可用及首次启动空状态。</li>
<li>使用简体中文、原生组件、系统颜色，适配深色模式、Dynamic Type 和 VoiceOver。</li>
<li>提供单元、持久化和 UI 测试，完成真机安装验证。</li>
</ul><p>产品定位是个人记录工具。打卡时间来自设备系统时钟，用户可以补录和修改内容，因此不作为企业防篡改考勤认证或历史地点证明。</p></section><section><h3>2. 工程搭建与架构选择<a href="#2-工程搭建与架构选择"><span>#</span></a></h3><p>工程采用 SwiftUI + ViewModel + Repository / Service 的分层结构，尽量把界面、业务规则、持久化和系统能力分开。通知、照片存储、数据仓库与定位提供可替换接口，便于测试成功和失败场景。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>签到打卡/</span></div></div><div><div><div>2</div></div><div><span>├── WorkPunch.xcodeproj/           Xcode 工程与共享 Scheme</span></div></div><div><div><div>3</div></div><div><span>├── WorkPunch/</span></div></div><div><div><div>4</div></div><div><span>│   ├── App/                      App 入口、依赖组装、通知路由</span></div></div><div><div><div>5</div></div><div><span>│   ├── Core/</span></div></div><div><div><div>6</div></div><div><span>│   │   ├── Models/               打卡、位置、提醒、节假日模型</span></div></div><div><div><div>7</div></div><div><span>│   │   ├── Repositories/         仓库接口与 SwiftData 实现</span></div></div><div><div><div>8</div></div><div><span>│   │   ├── Services/             打卡事务、照片文件与 CSV</span></div></div><div><div><div>9</div></div><div><span>│   │   └── Utilities/            日期、月历、筛选等规则</span></div></div><div><div><div>10</div></div><div><span>│   ├── ViewModels/               今日、日历、记录、设置与编辑逻辑</span></div></div><div><div><div>11</div></div><div><span>│   ├── Views/                    四个标签页与编辑、详情、隐私页面</span></div></div><div><div><div>12</div></div><div><span>│   ├── Services/                 通知、节假日、定位与图片处理</span></div></div><div><div><div>13</div></div><div><span>│   ├── Components/               相机、记录行、图片查看与分享面板</span></div></div><div><div><div>14</div></div><div><span>│   └── Resources/                权限、隐私清单、图标、节假日资源</span></div></div><div><div><div>15</div></div><div><span>├── WorkPunchTests/               单元与 SwiftData 集成测试</span></div></div><div><div><div>16</div></div><div><span>├── WorkPunchUITests/             真机交互流程测试</span></div></div><div><div><div>17</div></div><div><span>├── Scripts/                      工程生成、检查、编译与测试脚本</span></div></div><div><div><div>18</div></div><div><span>├── TestResults/                  编译日志、测试摘要、截图和验证报告</span></div></div><div><div><div>19</div></div><div><span>├── Package.swift                 可在 Mac 上测试的共享业务代码</span></div></div><div><div><div>20</div></div><div><span>├── README.md                     工程入口说明</span></div></div><div><div><div>21</div></div><div><span>└── 开发记录与使用说明.md          本文</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>主要技术选择及作用如下：</p>

<table><thead><tr><th>技术或组件</th><th>用途与选择原因</th></tr></thead><tbody><tr><td>SwiftUI、TabView、NavigationStack、List、Form</td><td>使用原生导航、列表和表单，保持日常工具的简洁界面</td></tr><tr><td>SwiftData</td><td>保存结构化记录，支持查询、唯一约束及后续模型迁移</td></tr><tr><td><code>@Observable</code>、共享 <code>PunchStore</code></td><td>数据提交成功后更新快照，让今日、日历、记录和详情同步</td></tr><tr><td><code>UserNotifications</code></td><td>本地通知授权、日程更新、测试通知和通知操作</td></tr><tr><td>PhotosPicker</td><td>用户只需选择要提供的照片，无需开放整个相册</td></tr><tr><td>UIImagePickerController</td><td>封装系统相机并接入 SwiftUI</td></tr><tr><td>Core Location</td><td>单次获取打卡位置，不申请后台定位</td></tr><tr><td>MKReverseGeocodingRequest</td><td>使用已安装 SDK 支持的地址查询 API，将坐标转换为可读地址</td></tr><tr><td>文件沙盒</td><td>保存压缩照片，数据库只存相对文件名</td></tr><tr><td>Swift Testing、XCTest</td><td>验证业务、数据库、权限配置及实际界面流程</td></tr></tbody></table><p>Swift 6 并发检查用于约束线程访问。界面、相机和主 ModelContext 的操作在主 Actor 执行，图片解码压缩、导出等耗时工作放到后台任务。工程将编译警告视为错误。</p></section><section><h3>3. 数据和核心业务实现<a href="#3-数据和核心业务实现"><span>#</span></a></h3><p><code>PunchRecord</code> 除了 UUID、类型、打卡时间、备注、照片路径、创建时间和更新时间，还保存以下信息：</p><ul>
<li><code>dayKey</code>、<code>timeZoneID</code>：记录打卡时的当地日期和时区，避免切换时区后历史记录换到另一日期。</li>
<li><code>uniquenessKey</code>：日期与打卡类型组成唯一键，配合业务检查防止同一天同类型重复保存。</li>
<li><code>isManual</code>：标记经过确认的补录记录。</li>
<li>可选位置字段：纬度、经度、水平精度、采集时间和地址。</li>
</ul><p>图片最长边压缩到 1800 像素，使用 JPEG 质量 0.78；缩略图单独降采样。处理时移除原图的 EXIF 和原始定位等元数据。用户主动附加的打卡位置作为独立记录字段保存。</p><p>保存新照片后如果数据库提交失败，会回滚记录并清理新文件。删除记录后同步清理无引用照片；清理异常会显示说明，并在后续加载时重试。提醒设置使用 Codable 编码后保存在 UserDefaults 中。</p><p>相关代码：<a href="WorkPunch/Core/Models/PunchRecord.swift">数据模型</a>、<a href="WorkPunch/Core/Services/PunchStore.swift">打卡业务</a>、<a href="WorkPunch/Core/Services/PhotoStorageService.swift">照片存储</a>。</p></section><section><h3>4. 首次编译和真机安装<a href="#4-首次编译和真机安装"><span>#</span></a></h3><p>开发开始时，命令行环境未找到可用的完整 Xcode。随后确认 Xcode 已安装，并完成 Apple 许可确认与首次组件安装；编译命令显式选择完整 Xcode 的 Developer 目录。</p><p>真机通过数据线连接后，依次完成解锁、信任电脑、签名安装及开发者 App 信任。安装初期系统曾拒绝启动尚未信任的开发者应用，完成信任后继续验证。真机测试期间也遇到设备锁定导致无法启动的情况，解锁后恢复测试。</p><p>首版留存的真机测试摘要为 <strong>22 / 22 通过</strong>，无运行时警告。证据见 <a href="TestResults/device-test-summary.json">首版测试摘要</a>。这只代表该阶段测试覆盖的范围；后续用户实际使用反馈又发现并推动修复了相机和通知问题。</p></section><section><h3>5. 1.1：修复闪退并完善提醒和节假日<a href="#5-11修复闪退并完善提醒和节假日"><span>#</span></a></h3><p>用户反馈点击拍照闪退、点击通知进入应用闪退，同时希望提醒持续保留、每小时再提醒，并跟随中国节假日和调休。处理过程如下。</p><section><h4>相机闪退<a href="#相机闪退"><span>#</span></a></h4><p>读取真机崩溃报告后，确认系统因安装包缺少 <code>NSCameraUsageDescription</code> 而执行隐私保护终止。</p><p>修复措施包括恢复相机和相册用途说明、在工程生成器中固定相关构建设置、打开相机前检查用途说明，以及增加最终安装包权限字符串测试。验证对象覆盖了实际安装包，避免只检查源文件却遗漏打包结果。</p></section><section><h4>点击通知闪退<a href="#点击通知闪退"><span>#</span></a></h4><p>崩溃记录显示异步通知委托桥接在协作执行器完成，进入 UIKit 快照或状态恢复流程时触发主线程断言。</p><p>修复采用系统支持的 completion-handler 委托入口，在 MainActor 上完成页面路由和完成回调，并通过 UIApplicationDelegate 在启动阶段注册、持有通知委托。随后验证了应用在后台以及完全终止后，点击真实本地通知进入打卡页的两种流程。</p></section><section><h4>持续提醒与可操作的测试入口<a href="#持续提醒与可操作的测试入口"><span>#</span></a></h4><p>设置页新增约 10 秒后的上班、下班测试通知，便于快速验证权限和点击跳转。正式提醒新增每小时追提醒、分类型停止按钮和通知长按操作。</p><p>刷新日程不再自动清空通知中心。普通通知点击、打开应用、完成打卡均不会自动停止追提醒；用户需要明确停止本轮或关闭对应提醒。实现同时保留了 iOS 的真实限制：第三方应用无法强制锁定通知中心条目，也不能保证无限期后台提醒。</p></section><section><h4>中国节假日与调休<a href="#中国节假日与调休"><span>#</span></a></h4><p>加入 2026 年离线安排，以及公开节假日 JSON 的下载、校验和缓存。放假日暂停，调休上班日开启，普通日期按所选星期。自动检查受 12 小时间隔限制，设置页可手动同步；尚未取得某年的安排时按所选星期回退并明确提示。</p><p>该阶段完整真机回归 <strong>39 / 39 通过</strong>，其中 32 项单元／集成测试、7 项 UI 测试；后续通知交付复测 <strong>34 / 34 通过</strong>。详细根因、日志和限制见 <a href="TestResults/VALIDATION-1.1.md">1.1 验证报告</a>。</p></section></section><section><h3>6. 1.2：附带当前位置与修复删除布局<a href="#6-12附带当前位置与修复删除布局"><span>#</span></a></h3><p>第二轮新增需求是打卡时附带当前位置，并修复删除操作位置过高、布局不正常的问题。</p><section><h4>单次定位及数据迁移<a href="#单次定位及数据迁移"><span>#</span></a></h4><p>新增 <code>LocationService</code>、<code>PunchLocation</code> 和 <code>LocationSummary</code>。实时打卡自动尝试单次定位，允许只使用近似位置；地址查询失败时保留有效坐标。获取位置最多等待约 25 秒，地址查询另有约 5 秒超时。</p><p>普通编辑默认保留原位置；补录默认关闭当前位置采集。实时打卡保存前，如果已有位置超过一分钟，会重新获取，以减少长时间停留表单造成的位置陈旧问题。关闭位置开关会取消请求并忽略后续结果。</p><p>数据库通过新增可选标量字段支持轻量迁移，使用旧版模型复制件测试升级后保留原记录、备注和照片路径。今日、日历、历史、详情和 CSV 同步支持位置。</p></section><section><h4>删除操作布局<a href="#删除操作布局"><span>#</span></a></h4><p>真机复现表明，旧 <code>confirmationDialog</code> 附着在整个详情页面上，会出现靠上方的确认气泡，且没有明确可见的取消按钮。改为居中的原生 <code>alert</code>，显示“取消”和“删除记录及照片”；记录操作行采用合适的按钮样式。</p><p>UI 测试验证了确认按钮高度、弹窗位置、取消保留记录以及确认后删除。见 <a href="TestResults/LocationScreenshots/DB248194-5E35-4126-8D58-F4F12933DD5A.png">删除确认截图</a>。</p></section></section><section><h3>7. 实际验证结果与边界<a href="#7-实际验证结果与边界"><span>#</span></a></h3>

<table><thead><tr><th>阶段</th><th>记录中的实际结果</th></tr></thead><tbody><tr><td>首版真机测试</td><td>22 / 22 通过</td></tr><tr><td>1.1 完整真机回归</td><td>39 / 39 通过，0 失败、0 跳过、0 运行时警告</td></tr><tr><td>1.1 后续通知交付复测</td><td>34 / 34 通过</td></tr><tr><td>1.2 Mac 核心及 SwiftData 测试</td><td>33 / 33 通过</td></tr><tr><td>1.2 真机完整运行</td><td>51 / 52 通过；1 项备注编辑 UI 断言失败</td></tr><tr><td>1.2 修正断言后定向复测</td><td>1 / 1 通过</td></tr><tr><td>1.2 最终覆盖</td><td>41 项单元／集成测试、11 项不同 UI 用例均通过，包含上述复测</td></tr><tr><td>1.2 构建及交付</td><td>编译无警告，测试结果无运行时警告；完成签名安装并以普通模式启动</td></tr></tbody></table><p>1.2 唯一失败的 UI 断言假定新文字一定追加在备注末尾，而自动化点击后的光标实际位于其他位置。将断言改为核对编辑框的实际输入与保存结果一致后，该用例单独复测通过。这里没有将两次结果写成一次完整的 52 / 52 全绿运行。</p><p>测试覆盖打卡保存、重复判断、日历状态、月份与闰年、历史筛选、CSV 转义、通知日程、节假日调休、照片删除、SwiftData 持久化和位置迁移。UI 覆盖无照片打卡、日历、筛选、修改提醒时间、位置保存与保留、关闭定位、删除弹窗、相机打开取消，以及通知后台点击与冷启动。</p><p>真机实际取得当前位置的流程已验证，但获取后取消，未保存真实位置或附加真实坐标截图。普通 UI 测试使用独立数据目录和虚构位置，不清除正常使用的记录。相机自动化只验证打开和取消，未拍摄用户环境；每小时间隔由固定时钟单元测试覆盖，没有声称完成长期每小时实际等待测试。</p><p>不同屏幕尺寸、横屏、深色模式、全部大字号和 VoiceOver 组合没有逐一完成真机验收。后台刷新执行时间、长期通知送达和专注模式／摘要的各种组合由系统影响，不作保证。</p><p>完整证据：<a href="TestResults/VALIDATION.md">当前验证报告</a>、<a href="TestResults/location-test-summary.json">1.2 完整运行摘要</a>、<a href="TestResults/location-edit-summary.json">定向复测摘要</a>、<a href="TestResults/location-core.log">Mac 测试日志</a>。</p></section></section><section><h2>二、应用使用方法<a href="#二应用使用方法"><span>#</span></a></h2><section><h3>1. 首次打开与四个页面<a href="#1-首次打开与四个页面"><span>#</span></a></h3><p>无需登录。底部四个标签的作用如下：</p>

<table><thead><tr><th>标签</th><th>主要用途</th></tr></thead><tbody><tr><td>今日</td><td>查看实时日期和时间，完成上下班打卡，查看今天的记录</td></tr><tr><td>日历</td><td>按月查看完成状态，选择日期查看详情，补录遗漏记录</td></tr><tr><td>记录</td><td>按日期浏览，按月份、类型和备注筛选，进入记录详情</td></tr><tr><td>设置</td><td>配置提醒、节假日、权限，导出或清除数据，查看隐私说明</td></tr></tbody></table><p>首次没有记录时会显示空状态，这是正常情况。完成第一条打卡后，各页面会同步显示。</p><p>默认上班时间为 <strong>08&lt;50&gt;</strong>，下班时间为 <strong>18&lt;00&gt;</strong>，普通工作日为周一至周五。上下班提醒初始都关闭；“每小时追提醒”和“同步中国节假日与调休”默认开启，但需要先开启相应的上下班提醒才会安排正式通知。</p></section><section><h3>2. 完成一次上班或下班打卡<a href="#2-完成一次上班或下班打卡"><span>#</span></a></h3><ol>
<li>打开“今日”，点击“上班打卡”或“下班打卡”。</li>
<li>确认页面会尝试获取当前位置；首次使用时按系统提示允许“使用 App 期间”定位。如果不需要位置，关闭“附带位置”。</li>
<li>可点击“拍照”调用相机，也可点击“从相册选择”。照片选填，拍照时首次需要相机授权。</li>
<li>在“备注”中填写说明，也可以留空。</li>
<li>点击“确认打卡”。实时打卡以保存时的设备时间记录。</li>
<li>成功后页面关闭，显示“打卡已保存”并给出触觉反馈；今日状态更新。</li>
</ol><p>同一天每种类型最多一条。已经完成的项目会显示记录和“查看或修改记录”，不会再次新增相同类型的打卡。照片处理中、定位中或保存中需要等待；如果开启了附带位置却没有取得位置，可重试，也可主动关闭开关后继续保存。</p></section><section><h3>3. 使用打卡位置<a href="#3-使用打卡位置"><span>#</span></a></h3><p>位置记录包括地址（如果查询成功）、经纬度、定位精度和实际采集时间。允许近似位置时，会保存系统提供的近似结果；精度受权限、信号和设备状态影响。</p><ul>
<li><strong>保留原位置</strong>：编辑已有记录时默认保留，修改备注不会自动把位置替换为当前所在地。</li>
<li><strong>重新采集</strong>：在编辑页点击“重新获取当前位置”，确认保存后替换原位置。</li>
<li><strong>不带位置或移除位置</strong>：关闭“附带位置”。编辑已有记录时，确认保存会移除该记录的位置。</li>
<li><strong>没有地址</strong>：只要取得有效坐标，即使地址查询失败，也可以保存坐标。</li>
<li><strong>授权失败</strong>：按页面提示打开定位设置，或从“设置 → 权限 → 打开系统设置”调整权限，再返回重试。</li>
</ul><p>应用不在后台持续定位。补录时取得的是此刻的位置，不代表所补录时刻的历史地点。</p></section><section><h3>4. 在日历查看和补录<a href="#4-在日历查看和补录"><span>#</span></a></h3><p>点击“日历”，用左右箭头切换月份；右上角“今天”返回当天。点击某一天，下面显示该日记录和可用操作。</p>

<table><thead><tr><th>标识</th><th>含义</th></tr></thead><tbody><tr><td>绿色圆点</td><td>上班、下班均已完成</td></tr><tr><td>橙色圆点</td><td>只完成一种打卡</td></tr><tr><td>没有圆点</td><td>没有打卡记录</td></tr><tr><td>“休”</td><td>已取得的节假日安排中的休息日</td></tr><tr><td>“班”</td><td>已取得的安排中的调休上班日</td></tr></tbody></table><p>“休／班”表示已知的节假日安排，实际是否按它安排提醒取决于设置中的节假日开关；普通周末不会因此都标成“休”。</p><p>补录步骤：选择今天或过去日期 → 点击缺少的“补录上班打卡”或“补录下班打卡” → 调整时间，填写可选内容 → 保存 → 在二次确认框确认。未来日期不提供补录操作；补录记录会有补录标记，默认不附带当前位置。</p><p>点击照片缩略图可全屏查看，支持捏合缩放、双击缩放，点击“完成”关闭。若照片损坏或丢失，会显示“照片不可用”，可去详情替换。</p></section><section><h3>5. 搜索、筛选、修改和删除<a href="#5-搜索筛选修改和删除"><span>#</span></a></h3><p>进入“记录”，默认按日期倒序展示全部记录。可选择“月份”，通过“打卡类型”选择“全部、上班、下班、异常”，并在搜索栏输入备注关键字。这些条件可以组合使用。</p><p>“异常”指过去日期只完成一次打卡，或者记录本身带有补录标记。整天没有任何打卡时，不会自动生成缺勤记录，也不会出现在异常列表中；今天尚未下班而只有上班记录，不因这一点立即判为异常。</p><p>修改记录：</p><ol>
<li>点击记录进入“打卡详情”。</li>
<li>点击“修改备注、照片或位置”。</li>
<li>修改内容后点击“保存修改”，再确认保存。</li>
<li>原始打卡时间不变，今日、日历、记录页面同步更新。</li>
</ol><p>删除记录：进入详情 → 点击“删除记录” → 在居中的系统弹窗选择“取消”或“删除记录及照片”。确认后移除本条记录、位置及对应照片，无法在应用内撤销。</p></section><section><h3>6. 开启和修改每日提醒<a href="#6-开启和修改每日提醒"><span>#</span></a></h3><ol>
<li>打开“设置”，分别开启“启用上班提醒”和／或“启用下班提醒”。</li>
<li>首次启用时，允许系统通知权限。</li>
<li>点击“上班提醒时间”或“下班提醒时间”，选择小时、分钟并点击“保存”。</li>
<li>在“提醒生效的星期”勾选需要的日期；星期选择同时用于两类提醒。</li>
<li>根据需要保留或关闭“同步中国节假日与调休”“每小时追提醒”。</li>
<li>在“测试与提醒状态”查看“下一次提醒”“已安排至”和待发送数量。</li>
</ol><p>修改时间后，应用取消旧日程并安排新日程，使用稳定标识避免重复通知。首次开启或修改时间时，如果当天时间已经过去，会从下一个有效日期开始，不补发启用前的通知。</p><p>通知文案分别是“上班时间快到了，别忘记打卡”和“准备下班了，记得完成下班打卡”。点击通知进入相应打卡页；已有对应记录时进入编辑流程，不会直接新增重复记录，也不会自动保存。</p></section><section><h3>7. 立即测试通知<a href="#7-立即测试通知"><span>#</span></a></h3><p>不用等到上班时间，也不用更改手机时间：</p><ol>
<li>进入“设置 → 测试与提醒状态”。</li>
<li>点击“发送上班测试通知（10 秒）”或“发送下班测试通知（10 秒）”。</li>
<li>如有授权提示，允许通知，然后返回桌面等待。</li>
<li>收到后点击通知，确认进入对应打卡页面。</li>
</ol><p>测试通知不受所选星期和节假日限制，不会自动保存打卡，也不会替你开启正式的上下班提醒。系统专注模式、通知摘要或其他设置可能延迟它，约 10 秒是安排时间，不是必达承诺。</p></section><section><h3>8. 每小时追提醒、停止本轮与彻底关闭<a href="#8-每小时追提醒停止本轮与彻底关闭"><span>#</span></a></h3><p>开启“每小时追提醒”后，每类提醒从首次到点开始按小时继续，<strong>可能跨夜</strong>；休息日暂停。进入 App 或完成打卡都不会自动停止，需要明确操作。</p>

<table><thead><tr><th>你的目的</th><th>操作与结果</th></tr></thead><tbody><tr><td>停止本轮上班提醒</td><td>设置中点击“停止本轮上班提醒”，或长按上班通知选择“停止本轮提醒”；下个有效工作日按设置时间恢复</td></tr><tr><td>停止本轮下班提醒</td><td>对下班提醒执行同样操作；上班、下班分别停止</td></tr><tr><td>保留每天提醒，取消每小时追提醒</td><td>关闭“每小时追提醒”，保留对应上下班提醒开关</td></tr><tr><td>不再接收某类正式提醒</td><td>关闭“启用上班提醒”或“启用下班提醒”</td></tr></tbody></table><p>明确停止本轮会清理对应已送达通知并取消当天后续追提醒。系统向应用回传单条通知清除操作时也会停止；“清除全部”不保证回传，因此推荐使用明确的停止按钮。</p><p>应用最多滚动预排 <strong>60 条正式通知</strong>，这是两类合计的队列，不是保证 60 天。返回应用或系统允许后台刷新时会续排；长期不开应用可能耗尽队列，请留意“已安排至”。</p><p>iOS 不允许第三方 App 强制通知常驻且不可清除。系统中的“持续横幅”也不等于永久锁定通知中心条目。专注模式、通知摘要、后台刷新、低电量及强制退出等都会影响提醒表现。</p></section><section><h3>9. 中国节假日与调休<a href="#9-中国节假日与调休"><span>#</span></a></h3><p>在“设置 → 节假日”开启“同步中国节假日与调休”，可点击“立即同步节假日”，并查看同步状态和时间。</p><p>提醒生效顺序为：</p><ol>
<li>已公布的放假日：不提醒，即使落在选中的周一至周五。</li>
<li>已公布的调休上班日：提醒，即使是原本未选中的周末。</li>
<li>其他日期：按“提醒生效的星期”判断。</li>
</ol><p>例如内置 2026 年安排中，10 月 1—7 日休息，10 月 10 日调休上班。因此在国庆期间正式提醒没有出现，可能是休息日规则正常生效；可用 10 秒测试通知检查通知能力。</p><p>如果未选择任何普通星期但开启了节假日跟随，已公布的调休上班日仍会提醒。关闭节假日跟随后，仅按所选星期判断。节假日只影响提醒安排，不限制休息日手动打卡。</p><p>离线时继续使用内置或缓存数据。某年数据尚未取得时，不会猜测放假安排，而是暂按所选星期提醒并显示说明。数据依据国务院通知整理，但单位实际排班可能不同，需要自行决定是否启用。</p></section><section><h3>10. 导出 CSV<a href="#10-导出-csv"><span>#</span></a></h3><p>进入“设置 → 数据 → 导出打卡记录（CSV）”，在系统分享面板选择“存储到‘文件’”或其他需要的目标。</p><p>导出文件名为 <code>上下班打卡记录.csv</code>，包含全部历史记录，不受“记录”页面当前筛选条件限制。字段为：日期、打卡类型、时间、备注、补录、时区、位置地址、纬度、经度、定位精度（米）、定位时间。没有位置的旧记录对应字段留空。</p><p>文件使用 UTF-8 BOM、标准 CSV 引号转义和 CRLF 行结束，支持中文、逗号、双引号和备注内换行。照片本体不在 CSV 中。分享结束后应用清理临时导出文件，已另存或发出的副本由使用者管理。</p><p>当前没有 CSV 导入或恢复功能，因此 CSV 是可阅读的记录导出，不能作为含照片的完整应用备份。</p></section><section><h3>11. 清除所有数据<a href="#11-清除所有数据"><span>#</span></a></h3><p>进入“设置 → 数据 → 清除所有数据”，阅读提示并确认“清除全部记录和照片”。此操作删除应用内记录（包括位置）、照片及临时导出文件，关闭提醒并恢复默认提醒设置，不能撤销。</p><p>需要保留记录时先导出。外部已保存的 CSV 不会随应用清除而删除；系统是否备份应用文件取决于自己的 iPhone 备份设置。卸载应用也会移除本机应用数据。</p></section><section><h3>12. 权限、网络与隐私<a href="#12-权限网络与隐私"><span>#</span></a></h3>

<table><thead><tr><th>能力</th><th>何时使用</th><th>拒绝后的处理</th></tr></thead><tbody><tr><td>通知</td><td>首次开启提醒或发送测试通知</td><td>到“设置 → 权限 → 打开系统设置”允许通知，再返回启用</td></tr><tr><td>相机</td><td>点击“拍照”时</td><td>允许相机后重试；也可从相册选图或不加照片</td></tr><tr><td>相册选择</td><td>打开系统 PhotosPicker 时</td><td>仅提供主动选中的项目；无需开放整个图库，遇到读取失败可重选或不加照片</td></tr><tr><td>定位</td><td>实时打卡或主动重新获取位置时</td><td>允许使用期间定位；也可关闭“附带位置”继续打卡</td></tr></tbody></table><p>照片、备注、打卡记录和位置保存在本机，开发者不会收集或上传这些资料，不包含广告和分析 SDK。应用不会主动进行 iCloud 同步。</p><p>两项系统／网络行为需要区分：节假日同步从公开托管源下载 JSON，不携带打卡数据，托管方仍可看到 IP 等基础请求信息；地址查询由系统 Apple 地图服务处理坐标。核心打卡和本机记录查看不需要后端，网络不可用时节假日依赖缓存，地址查询可能退回坐标显示。</p></section><section><h3>13. 常见问题<a href="#13-常见问题"><span>#</span></a></h3>

<table><thead><tr><th>现象</th><th>检查和操作</th></tr></thead><tbody><tr><td>开了 App 却没有通知</td><td>上下班提醒默认关闭；开启对应开关并检查权限、“下一次提醒”和休息日安排</td></tr><tr><td>测试通知没有及时出现</td><td>检查系统通知权限、通知中心／锁屏／横幅、专注模式和摘要；回到设置确认测试已安排</td></tr><tr><td>已打卡仍每小时提醒</td><td>这是持续提醒的当前规则；使用“停止本轮上班／下班提醒”明确停止</td></tr><tr><td>清空通知栏后又提醒</td><td>“清除全部”可能没有通知应用停止；使用 App 内或通知长按菜单的停止按钮</td></tr><tr><td>定位一直等待或保存按钮不可点</td><td>等待超时说明后重试，检查权限；不需要位置时关闭“附带位置”</td></tr><tr><td>有坐标但没有地址</td><td>地址查询受网络和服务影响；有效坐标仍可保存</td></tr><tr><td>照片不可用</td><td>文件可能损坏、删除，或选择的云端照片未能读取；在详情替换照片</td></tr><tr><td>找不到刚保存的历史记录</td><td>检查月份、类型和备注搜索是否仍有限制条件</td></tr><tr><td>休息日能否打卡</td><td>可以；节假日规则控制提醒，不禁止手动记录</td></tr><tr><td>换手机后看不到数据</td><td>当前没有应用云同步和导入恢复；数据在原机，系统备份／迁移效果取决于 iPhone 设置</td></tr></tbody></table></section></section><section><h2>三、后续开发与重新安装<a href="#三后续开发与重新安装"><span>#</span></a></h2><section><h3>1. 在 Xcode 中运行<a href="#1-在-xcode-中运行"><span>#</span></a></h3><ol>
<li>安装带 iOS 27 SDK 的完整 Xcode，首次打开完成许可和所需组件安装。</li>
<li>打开 <code>WorkPunch.xcodeproj</code>，选择共享 Scheme <code>WorkPunch</code>。</li>
<li>模拟器运行时选择已安装的 iOS 27 iPhone 模拟器；真机运行时连接手机、解锁并信任电脑，按系统要求开启开发者模式。</li>
<li>真机需在 Xcode 的 Accounts 登录 Apple ID，并在 App Target 的 Signing &amp; Capabilities 中选择可用 Team。给自己的新安装使用唯一 Bundle Identifier；更新当前安装并保留旧数据时，应保持原应用身份和可用签名。</li>
<li>按 <code>⌘R</code> 编译运行。若手机提示开发者未信任，按系统指引在“设置 → 通用 → VPN 与设备管理”完成信任。</li>
</ol><p>个人开发签名的可用期限受 Apple 当前规则约束，到期后可能需要重新签名安装；不要为了更新而先卸载已有应用，以免删除本机记录。</p></section><section><h3>2. 运行项目检查和测试<a href="#2-运行项目检查和测试"><span>#</span></a></h3><p>在项目根目录的终端执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 检查工程结构、资源和权限配置</span></div></div><div><div><div>2</div></div><div><span>python3</span><span> </span><span>Scripts/validate_project.py</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># Mac 上的共享核心与 SwiftData 测试</span></div></div><div><div><div>5</div></div><div><span>bash</span><span> </span><span>Scripts/test_core.sh</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 已安装 iOS 27 模拟器的构建与测试</span></div></div><div><div><div>8</div></div><div><span>bash</span><span> </span><span>Scripts/verify.sh</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span># 列出连接设备，取得需要测试的设备标识</span></div></div><div><div><div>11</div></div><div><span>DEVELOPER_DIR</span><span>=</span><span>/Applications/Xcode.app/Contents/Developer</span><span> </span><span>xcrun</span><span> </span><span>devicectl</span><span> </span><span>list</span><span> </span><span>devices</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span># 将占位内容替换为上一步设备标识；测试期间保持手机解锁</span></div></div><div><div><div>14</div></div><div><span>bash</span><span> </span><span>Scripts/verify_device.sh</span><span> </span><span>&lt;IPHONE_UDID&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>也可在 Xcode 中按 <code>⌘U</code>。脚本会选择完整 Xcode 并把结果写入 <code>TestResults/</code>。真机测试会操作测试 App；Debug 的 <code>--uitesting</code> 使用独立数据和设置，普通 UI 用例注入虚构位置，<code>--live-location</code> 用例才请求实际定位，Release 不包含测试定位实现。</p></section><section><h3>3. 手动回归建议<a href="#3-手动回归建议"><span>#</span></a></h3><p>每次涉及系统能力或签名的更新后，建议在真机完成以下检查：</p><ol>
<li>无照片打卡、有照片打卡，重启应用确认记录仍在。</li>
<li>相机授权、打开、取消和实际拍摄；相册选择并查看全屏照片。</li>
<li>定位允许、拒绝、不带位置保存，以及修改备注后位置保持。</li>
<li>10 秒测试通知，分别验证后台点击和应用终止后的通知启动。</li>
<li>修改提醒时间、停止本轮、关闭某类提醒，并查看重新安排后的状态。</li>
<li>节假日同步失败时保留缓存、调休工作日及普通星期规则。</li>
<li>删除弹窗取消与确认、日历状态和历史列表同步。</li>
<li>含中文、引号、逗号和换行备注的 CSV 导出。</li>
<li>深色模式、大字号、VoiceOver 和目标屏幕尺寸下的可读性。</li>
</ol></section><section><h3>4. 维护入口和参考材料<a href="#4-维护入口和参考材料"><span>#</span></a></h3>

<table><thead><tr><th>内容</th><th>入口</th></tr></thead><tbody><tr><td>工程说明与更多命令</td><td><a href="README.md">README.md</a></td></tr><tr><td>当前版本验证报告</td><td><a href="TestResults/VALIDATION.md">TestResults/VALIDATION.md</a></td></tr><tr><td>相机、通知和持续提醒修复记录</td><td><a href="TestResults/VALIDATION-1.1.md">TestResults/VALIDATION-1.1.md</a></td></tr><tr><td>定位与删除布局验证</td><td><a href="TestResults/LOCATION-VALIDATION.md">TestResults/LOCATION-VALIDATION.md</a></td></tr><tr><td>通知实现</td><td><a href="WorkPunch/Services/NotificationService.swift">NotificationService.swift</a></td></tr><tr><td>定位实现</td><td><a href="WorkPunch/Services/LocationService.swift">LocationService.swift</a></td></tr><tr><td>节假日同步</td><td><a href="WorkPunch/Services/HolidayService.swift">HolidayService.swift</a></td></tr><tr><td>工程生成器</td><td><a href="Scripts/generate_project.py">generate_project.py</a></td></tr></tbody></table><p>新增文件后可在 Xcode 中加入相应 Target，或运行 <code>python3 Scripts/generate_project.py</code> 重新生成工程。生成器保留现有签名 Team 和 Bundle Identifier，并固定相机、相册和定位权限说明。改动系统权限后应同时检查源码配置与最终安装包。</p><p>节假日参考来源：<a href="https://www.gov.cn/zhengce/zhengceku/202511/content_7047091.htm" target="_blank">国务院 2026 年放假通知</a>、<a href="https://github.com/NateScarlet/holiday-cn" target="_blank">NateScarlet/holiday-cn（MIT 许可）</a>。</p></section></section></section>]]></content>
    </entry>
    <entry>
      <id>https://bytefun.site/posts/new-b17543c6/</id>
      <title type="text">博客搭建过程</title>
      <published>2026-09-29T02:29:08.000Z</published>
      <updated>2026-09-29T02:29:08.000Z</updated>
      <author><name>木子</name></author>
      <link rel="alternate" href="https://bytefun.site/posts/new-b17543c6/"/>
      <summary type="text"></summary>
      <content type="html"><![CDATA[<section><h1>Firefly 博客搭建过程与使用说明<a href="#firefly-博客搭建过程与使用说明"><span>#</span></a></h1><p>最后更新：<strong>2026-09-29</strong>。本文根据项目代码和已有验收记录整理；本次更新文档没有重新部署服务器，也没有发布新的正文。</p><blockquote><p>这是一份给个人项目使用者看的说明。它记录 Firefly 博客是怎样搭建、部署和管理的，也说明日常修改网站时应该在哪里操作。</p><p>文档不会记录密码、SSH 私钥、数据库密码、Supabase Secret Key 或 <code>service_role</code> key。</p></blockquote><section><h2>先看这里：这次修改需要部署什么<a href="#先看这里这次修改需要部署什么"><span>#</span></a></h2>

<table><thead><tr><th>想做的事</th><th>操作</th><th>何时生效</th></tr></thead><tbody><tr><td>修改运行时配置</td><td>保存草稿，再发布配置</td><td>网站读取新配置后</td></tr><tr><td>修改标记“需要重建”的配置</td><td>先发布配置，再到发布中心点“重建线上内容与已发布配置…”</td><td>重建任务显示“已上线”后</td></tr><tr><td>修改文章、动态或“关于我”正文</td><td>对应内容页保存草稿，点“提交任务 → 发布…”</td><td>内容发布任务重建成功后</td></tr><tr><td>修改 Mac 应用按钮、表单和编辑器</td><td>在 Mac 编译并替换原应用</td><td>新版应用启动后，不需要重建网站</td></tr><tr><td>修改网站组件、样式或构建逻辑</td><td>生成并启用新的固定源码包，再提交网站重建</td><td>新代码任务部署成功后</td></tr></tbody></table><p><strong>网站重建按钮不会顺带发布未发布的文章或“关于我”草稿。</strong></p><p>近期进展：视频直传已完成生产验收，顶栏视频图标已改为包含“循环播放”和“播放 / 暂停”的二级菜单。“关于我与独立页面”管理功能已编译并安装，原文已接入远程草稿，测试文字没有发布。</p><p>最近一次<strong>已有记录的生产发布</strong>是 2026-09-28 19:38&lt;24&gt;（北京时间）的顶栏视频菜单任务。今天的应用更新不等于网站新发布。详细步骤见 <a href="docs/content/deployment.md">服务器部署手册</a>、<a href="docs/content/about-page-management.md">关于我管理说明</a> 和 <a href="docs/content/video-menu-verification.md">视频菜单验收</a>。</p></section><section><h2>1. 这个项目由什么组成<a href="#1-这个项目由什么组成"><span>#</span></a></h2><p>Firefly 不是只有一个网页，而是由三部分配合工作：</p><ol>
<li><strong>博客网站</strong>：访客打开的 <code>https://bytefun.site</code>，由 Astro 生成静态网页。</li>
<li><strong>Supabase 后端</strong>：保存登录用户、评论、网站配置、文章草稿、历史版本和资源信息。</li>
<li><strong>Firefly Admin</strong>：安装在 Mac 上的原生管理应用，用来改配置、写文章、上传资源和提交发布任务。</li>
</ol><p>配置或内容保存后，不一定会马上出现在访客页面。网站有两种生效方式：</p><ul>
<li><strong>运行时配置</strong>：例如网站标题、公告、主题色、壁纸 URL、评论开关。发布配置后，网站下次读取配置即可生效。</li>
<li><strong>构建时内容</strong>：例如文章正文、字体、Markdown、页面路由、RSS、站点地图和搜索索引。必须提交一次网站重建任务，服务器构建成功并切换线上目录后才生效。</li>
</ul><p>整个流程可以简单理解为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Mac 上编辑</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>|</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>v</span></div></div><div><div><div>4</div></div><div><span>Supabase 保存草稿和版本</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>|</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>+--&gt; 运行时配置发布 ------&gt; 网站读取最新配置</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>|</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>+--&gt; 内容发布/重建任务 ----&gt; 腾讯云 worker 构建 ----&gt; Nginx 切换网站</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h2>2. 使用到的技术<a href="#2-使用到的技术"><span>#</span></a></h2><section><h3>网站前端<a href="#网站前端"><span>#</span></a></h3>

<table><thead><tr><th>技术</th><th>在项目中的作用</th></tr></thead><tbody><tr><td>Astro 7.2.10</td><td>生成页面、路由和静态站点</td></tr><tr><td>Svelte 5.57.0</td><td>登录、评论、上传等需要交互的页面组件</td></tr><tr><td>TypeScript 6.0.3</td><td>业务代码和类型检查</td></tr><tr><td>Markdown / MDX</td><td>文章、动态、项目和独立页面的内容格式</td></tr><tr><td>Astro Content Collections</td><td>定义并校验文章、动态、项目和页面字段</td></tr><tr><td>Tailwind CSS 4、Stylus、PostCSS</td><td>页面布局和样式</td></tr><tr><td>Swup</td><td>页面切换动画</td></tr><tr><td>Pagefind</td><td>静态全文搜索</td></tr><tr><td>Sharp</td><td>图片压缩和尺寸处理</td></tr><tr><td>Expressive Code</td><td>代码块高亮、行号和折叠</td></tr><tr><td>Iconify</td><td>网站图标</td></tr></tbody></table></section><section><h3>后端<a href="#后端"><span>#</span></a></h3>

<table><thead><tr><th>技术</th><th>在项目中的作用</th></tr></thead><tbody><tr><td>Supabase PostgreSQL</td><td>保存账号、评论、配置、内容版本和发布任务</td></tr><tr><td>Supabase Auth</td><td>邮箱登录和 GitHub OAuth 登录</td></tr><tr><td>Row Level Security（RLS）</td><td>在数据库层限制普通用户和管理员的权限</td></tr><tr><td>Supabase Storage</td><td>保存网站图片、音频、视频和私密附件、预览文件</td></tr><tr><td>Supabase Realtime</td><td>订阅评论等实时数据变化</td></tr><tr><td>Supabase Edge Functions</td><td>提供管理员 API、上传地址、内容预览和公开配置接口</td></tr><tr><td>JSONB</td><td>保存可变化的网站配置和内容快照</td></tr></tbody></table></section><section><h3>部署和管理<a href="#部署和管理"><span>#</span></a></h3>

<table><thead><tr><th>技术</th><th>在项目中的作用</th></tr></thead><tbody><tr><td>腾讯云 CVM</td><td>运行构建 worker 和提供网站文件</td></tr><tr><td>Ubuntu Linux</td><td>服务器操作系统</td></tr><tr><td>Node.js 22.23.0</td><td>执行 Astro 构建和发布 worker</td></tr><tr><td>pnpm 11.22.0</td><td>安装依赖和运行构建命令</td></tr><tr><td>Nginx</td><td>提供静态页面、HTTPS 和原子目录切换</td></tr><tr><td>systemd</td><td>保证 <code>firefly-content-worker</code> 后台运行并自动重启</td></tr><tr><td>Let’s Encrypt / Certbot</td><td>HTTPS 证书</td></tr></tbody></table></section><section><h3>macOS 管理应用<a href="#macos-管理应用"><span>#</span></a></h3><p>Firefly Admin 是真正的 macOS 原生应用，不是网页套壳：</p><ul>
<li>Swift 6、SwiftUI、最低支持 macOS 14。</li>
<li><code>NavigationSplitView</code> 三栏导航，使用原生 <code>Form</code>、<code>List</code>、<code>Table</code>、<code>Sheet</code> 和 <code>Toolbar</code>。</li>
<li>MVVM 结构，网络层使用 <code>URLSession</code> + <code>Codable</code>。</li>
<li>登录令牌和本地草稿密钥放在 macOS Keychain。</li>
<li><code>OSLog</code> 记录网络和发布状态。</li>
<li><code>WKWebView</code> 显示线上预览、草稿预览和手机/平板尺寸预览。</li>
</ul></section></section><section><h2>3. 第一步：整理原有博客<a href="#3-第一步整理原有博客"><span>#</span></a></h2><p>开始搭建时，先检查了项目根目录、<code>package.json</code>、<code>src/config</code>、<code>src/types</code>、<code>src/content</code> 和 Astro 配置，确认原博客可以独立构建。</p><p>主要内容目录如下：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>src/content/posts/       文章</span></div></div><div><div><div>2</div></div><div><span>src/content/dynamic/     动态</span></div></div><div><div><div>3</div></div><div><span>src/content/projects/    项目展示</span></div></div><div><div><div>4</div></div><div><span>src/content/spec/        About、友链、留言板等独立页面</span></div></div><div><div><div>5</div></div><div><span>src/config/              网站默认配置</span></div></div><div><div><div>6</div></div><div><span>src/types/               配置类型定义</span></div></div><div><div><div>7</div></div><div><span>public/                  不需要处理、直接公开的文件</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>原有 Markdown 文件仍然保留。后台导入时会读取标题、摘要、日期、分类、标签、封面和自定义 Frontmatter；不认识的字段也会保留，避免编辑器暂时不支持某个字段时把内容弄丢。</p><p>文章的稳定 ID 和 URL slug 分开保存。修改 slug 会生成旧地址跳转页；撤回后页面、RSS、站点地图和搜索结果都会在下一次构建中移除。</p></section><section><h2>4. 第二步：在本地检查和构建网站<a href="#4-第二步在本地检查和构建网站"><span>#</span></a></h2><p>在 Mac 终端进入项目目录：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cd</span><span> </span><span>/Users/nightwind/aiWork/firefly/Firefly</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>首次安装依赖：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>pnpm</span><span> </span><span>install</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>修改网站代码后，建议按下面顺序检查：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>pnpm</span><span> </span><span>check</span></div></div><div><div><div>2</div></div><div><span>pnpm</span><span> </span><span>type-check</span></div></div><div><div><div>3</div></div><div><span>pnpm</span><span> </span><span>build</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>三个命令的含义：</p><ul>
<li><code>pnpm check</code>：检查 Astro 页面、组件和内容集合。</li>
<li><code>pnpm type-check</code>：检查 TypeScript 类型。</li>
<li><code>pnpm build</code>：生成完整的 <code>dist/</code>，同时处理图片、字体、Pagefind、RSS 和站点地图。</li>
</ul><p>本地查看效果：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>pnpm</span><span> </span><span>dev</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>浏览器打开 <code>http://localhost:4321</code>。<code>pnpm build</code> 只代表本地静态构建成功，它不会自动读取 Supabase 最新配置，也不会把网站部署到服务器。</p></section><section><h2>5. 第三步：部署到腾讯云<a href="#5-第三步部署到腾讯云"><span>#</span></a></h2><p>生产服务器信息如下：</p>

<table><thead><tr><th>项目</th><th>值</th></tr></thead><tbody><tr><td>公网 IP</td><td><code>xxx</code></td></tr><tr><td>系统</td><td>Ubuntu Linux x86_64</td></tr><tr><td>网站域名</td><td><code>https://bytefun.site</code></td></tr><tr><td>源码包目录</td><td><code>/opt/firefly</code>（传统源码目录）</td></tr><tr><td>固定构建源码</td><td><code>/opt/firefly-content/code/&lt;代码哈希&gt;/</code></td></tr><tr><td>构建任务目录</td><td><code>/var/lib/firefly-content/jobs/</code></td></tr><tr><td>线上发布目录</td><td><code>/var/www/firefly-managed/current</code></td></tr><tr><td>旧静态目录</td><td><code>/var/www/firefly</code>（保留作备份）</td></tr><tr><td>构建账号</td><td><code>firefly-build</code></td></tr><tr><td>后台服务</td><td><code>firefly-content-worker</code></td></tr></tbody></table><section><h3>第一次部署做了什么<a href="#第一次部署做了什么"><span>#</span></a></h3><ol>
<li>备份原有博客和 Nginx 配置。</li>
<li>在服务器安装 Node.js 和固定版本的 pnpm。</li>
<li>上传源码并执行 <code>pnpm install --frozen-lockfile</code>。</li>
<li>执行检查和生产构建，把 <code>dist/</code> 放到网站目录。</li>
<li>配置 Nginx 和 HTTPS。</li>
<li>用浏览器检查首页、文章、RSS、站点地图和 404 页面。</li>
</ol><p>Nginx 当前指向 <code>firefly-managed/current</code>，发布时只切换这个软链接。新构建失败时，线上软链接不变，原网站继续运行。</p></section></section><section><h2>6. 第四步：初始化 Supabase<a href="#6-第四步初始化-supabase"><span>#</span></a></h2><section><h3>6.1 创建项目并获取公开连接信息<a href="#61-创建项目并获取公开连接信息"><span>#</span></a></h3><p>在 Supabase Dashboard 创建项目，记录：</p><ul>
<li>Project URL，例如 <code>https://&lt;project-ref&gt;.supabase.co</code>。</li>
<li>Publishable key（旧项目可能叫 anon public key）。</li>
</ul><p>应用连接时使用项目根 URL，不要把 <code>/rest/v1/</code> 拼到 URL 末尾。Publishable key 可以放在网站和 Mac 应用中；Secret key、service role key 和数据库密码只能放在服务器端。</p></section><section><h3>6.2 初始化表和 Storage<a href="#62-初始化表和-storage"><span>#</span></a></h3><p>初始 SQL 位于：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>supabase/schema.sql</span></div></div><div><div><div>2</div></div><div><span>supabase/config-versions.sql</span></div></div><div><div><div>3</div></div><div><span>supabase/migrations/</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>在新项目上应按文件名顺序执行迁移，或使用已经登录并绑定项目的 Supabase CLI。生产环境不要把一份已经执行过的旧 SQL 再次当成新迁移执行。</p><p>主要数据包括：</p><ul>
<li><code>profiles</code>：用户公开资料。</li>
<li><code>blog_comments</code>：文章评论。</li>
<li><code>user_files</code>：用户文件元数据。</li>
<li><code>site_settings</code>：兼容旧版本的网站设置。</li>
<li><code>site_config_versions</code>、草稿和审计表：配置版本、发布和操作记录。</li>
<li><code>content_*</code>：文章、动态、项目、内容版本、发布任务和发布事件。</li>
<li><code>site_assets</code>：网站公开资源元数据。</li>
<li><code>content-assets</code>：内容草稿使用的私有附件桶。</li>
</ul></section><section><h3>6.3 设置管理员<a href="#63-设置管理员"><span>#</span></a></h3><p>管理员角色放在 Supabase Auth 的 <code>app_metadata</code> 中，普通用户不能自己修改：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>update</span><span> </span><span>auth</span><span>.</span><span>users</span></div></div><div><div><div>2</div></div><div><span>set</span><span><span> raw_app_meta_data </span><span>=</span></span></div></div><div><div><div>3</div></div><div><span>  </span><span>coalesce</span><span>(raw_app_meta_data, </span><span>'{}'</span><span>::jsonb) || </span><span>'{"role":"admin"}'</span><span>::jsonb</span></div></div><div><div><div>4</div></div><div><span>where</span><span><span> email </span><span>=</span><span> </span></span><span>'你的管理员邮箱'</span><span>;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>修改角色后退出应用并重新登录。GitHub 登录需要在 Supabase Authentication 中启用 GitHub provider，并加入应用的回调地址 <code>firefly-admin://auth/callback</code>。</p></section></section><section><h2>7. 权限和安全是怎样工作的<a href="#7-权限和安全是怎样工作的"><span>#</span></a></h2><p>所有重要写入都经过三层检查：</p><ol>
<li>Supabase Auth 检查是否登录。</li>
<li>Edge Function 检查 <code>app_metadata.role</code> 是否为 <code>admin</code>。</li>
<li>PostgreSQL RLS 再检查站点、用户和资源权限。</li>
</ol><p>因此：</p><ul>
<li>普通用户不能读取管理员草稿、历史私密正文或发布任务。</li>
<li>普通用户不能上传或删除网站资源。</li>
<li>客户端绝不保存 service role key、Secret key 或数据库密码。</li>
<li>上传接口同时检查 MIME 类型、扩展名、文件大小和文件名。</li>
<li>文章和配置使用版本号或更新时间做并发检查，避免覆盖其他设备的修改。</li>
<li>日志和错误提示不会输出登录令牌、私密正文或服务端密钥。</li>
</ul></section><section><h2>8. 第五步：接入网站运行时配置<a href="#8-第五步接入网站运行时配置"><span>#</span></a></h2><p>网站默认值仍在 <code>src/config/</code> 中。统一的 runtime-config 模块启动时读取 Supabase 中“已发布”的配置，再覆盖本地默认值：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>本地默认配置 -&gt; 读取远程已发布配置 -&gt; 合并 -&gt; 页面使用</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果网络或 Supabase 暂时不可用，网站继续使用本地配置，不会因为配置接口失败而白屏。配置会缓存并带版本号，页面通过 Swup 切换时复用同一份结果，不会每个组件重复请求。</p><section><h3>可以直接发布后生效的配置<a href="#可以直接发布后生效的配置"><span>#</span></a></h3><ul>
<li>网站标题、描述和导航标题。</li>
<li>主题色、浅色/深色/跟随系统模式。</li>
<li>壁纸地址、遮罩和毛玻璃相关运行时设置。</li>
<li>首页横幅文字、公告和个人资料。</li>
<li>侧边栏布局、已构建页面开关。</li>
<li>友链、打赏信息。</li>
<li>评论区域开关。</li>
</ul></section><section><h3>发布后仍需要重建的配置<a href="#发布后仍需要重建的配置"><span>#</span></a></h3><ul>
<li>字体下载、字体子集化和代码字体。</li>
<li>新增或修改 Astro 路由、Markdown/MDX、Content Collections。</li>
<li>Mermaid、PlantUML、代码高亮主题等构建插件。</li>
<li>RSS、站点地图、Pagefind 搜索索引和 SEO/OG 静态资源。</li>
<li>需要新增模板或组件才能支持的功能。</li>
<li>视频地址及标记为构建项的播放器配置。相关迁移和新版 <code>admin-assets</code> 已部署；循环开关可在新版网站运行时生效。</li>
</ul><p>每个配置字段在应用中会标记“运行时生效”或“需要重建”。</p></section></section><section><h2>9. 第六步：安装和使用 Firefly Admin<a href="#9-第六步安装和使用-firefly-admin"><span>#</span></a></h2><p>已安装应用位置：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>/Users/nightwind/Applications/Firefly Admin.app</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>日常直接双击这个应用即可，不需要每次重新编译。第一次从新位置启动时，macOS 可能询问是否允许访问原有钥匙串项目；在系统弹窗中允许即可，系统密码不要写入文档或聊天。</p><section><h3>本地重新构建应用<a href="#本地重新构建应用"><span>#</span></a></h3><p>在仓库根目录运行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cd</span><span> </span><span>/Users/nightwind/aiWork/firefly/Firefly</span></div></div><div><div><div>2</div></div><div><span>pnpm</span><span> </span><span>admin:manifest</span></div></div><div><div><div>3</div></div><div><span>bash</span><span> </span><span>FireflyAdmin/scripts/build-app.sh</span></div></div><div><div><div>4</div></div><div><span>codesign</span><span> </span><span>--verify</span><span> </span><span>--deep</span><span> </span><span>--strict</span><span> </span><span>"FireflyAdmin/build/Firefly Admin.app"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>pnpm admin:manifest</code> 用于刷新配置 Schema 和默认值，修改配置定义时需要执行；只修改 Swift 界面时通常不需要刷新它。构建脚本会自动生成并打包原项目的 About、友链说明和留言板说明，供首次接入使用。</p><p><strong>编译完成不等于日常应用已更新。</strong> 先按 Command-Q 退出旧应用，保留旧应用副本，再复制新应用到固定安装位置。复制命令见 <a href="docs/content/deployment.md#12-%E6%9B%B4%E6%96%B0-macos-%E7%AE%A1%E7%90%86%E5%BA%94%E7%94%A8">部署手册中的应用更新步骤</a>。之后仍从 <code>/Users/nightwind/Applications/Firefly Admin.app</code> 启动，避免误开其他旧副本。</p><p>本轮使用 Command Line Tools，原生编译与签名校验通过；没有完整 Xcode，不能记为 <code>xcodebuild</code> 通过。完整 Xcode 可用时，也可以打开 <code>FireflyAdmin/FireflyAdmin.xcodeproj</code>，选择实际的 <code>FireflyAdmin</code> scheme。个人本机使用临时签名；对外分发才需要另行安排 Developer ID 签名、公证和更新机制。</p><p>重新编译后，macOS 可能再次要求读取原钥匙串。请在系统弹窗中自行允许，不要发送密码，也不要删除钥匙串或本地草稿来绕过提示。</p></section><section><h3>登录和连接<a href="#登录和连接"><span>#</span></a></h3><ol>
<li>打开应用的“连接设置”。</li>
<li>输入 Supabase 项目根 URL 和 Publishable/anon key。</li>
<li>使用邮箱登录，或使用 GitHub OAuth 登录。</li>
<li>确认账号的角色显示为管理员。</li>
<li>线上预览地址填写 <code>https://bytefun.site</code>；本地预览可填写 <code>http://localhost:4321</code>。</li>
</ol><p>令牌保存在 Keychain；本地内容草稿使用加密文件保存，密钥也在 Keychain，并按项目、站点和账号隔离。</p></section></section><section><h2>10. 在应用中修改网站配置<a href="#10-在应用中修改网站配置"><span>#</span></a></h2><p>左侧配置页面按网站功能分组，例如“网站基础”“导航栏”“外观与主题”“壁纸”“首页与横幅”“侧边栏”“页面开关”“评论系统”“友链”“相册”和“高级配置”。</p><p>常见操作：</p><ol>
<li>打开一个配置分区，先同步线上已发布值。</li>
<li>修改字段。选项较少的地方使用整块可点击的方框卡片；数字使用减号、输入框和加号；图片字段提供上传和预览。</li>
<li>点击“保存草稿”或按 <code>Command + S</code>。</li>
<li>需要让访客看到时，点击“发布”，查看修改前后的差异后确认。</li>
<li>如果字段标记“需要重建”，发布配置后进入发布中心，点击“重建线上内容与已发布配置…”。</li>
</ol><p>“恢复默认值”只改变当前编辑值，不会自动发布；“撤销未保存修改”会恢复到上次保存状态。保存失败时，应用会保留本地内容，不会清空输入框。</p></section><section><h2>11. 壁纸、视频壁纸、毛玻璃和调色板<a href="#11-壁纸视频壁纸毛玻璃和调色板"><span>#</span></a></h2><section><h3>图片壁纸<a href="#图片壁纸"><span>#</span></a></h3><p>打开“壁纸与毛玻璃”：</p><ul>
<li>选择横幅、全屏、透明覆盖或纯色背景模式。</li>
<li>设置桌面和移动端壁纸 URL。</li>
<li>调整遮罩、顶栏透明和模糊强度。</li>
<li>保存并发布配置。</li>
</ul><p>图片 URL 可以从“资源管理”上传后直接复制，也可以使用已有公开 URL。清空 URL 会回到源码默认壁纸。</p></section><section><h3>视频壁纸<a href="#视频壁纸"><span>#</span></a></h3><p>视频壁纸需要三个条件：</p><ol>
<li>在“资源管理”上传合法的视频资源并得到 URL。</li>
<li>在“壁纸与毛玻璃”中打开视频开关并选择视频 URL。</li>
<li>如果字段标记“需要重建”，发布配置后提交一次网站重建。</li>
</ol><p>当前生产项目已部署视频迁移和新版 <code>admin-assets</code>。视频使用“获取上传凭证 → 直接上传 Storage → 校验登记”的流程，视频字节不再经过 Edge Function。32 MiB MP4 已完成真实上传验收，约 59 秒后成功登记；进度传完还需等待登记完成。</p><p>应用、数据库和存储桶已取消额外写死的视频上限，但<strong>当前 Supabase Free 项目全局上限仍为 50 MiB</strong>。更大文件可以使用其他服务的 HTTPS 视频直链，或自行升级套餐后同步调整项目限制与服务端能力值；没有自动购买升级。</p><ul>
<li><strong>网站访客</strong>：点击顶栏视频图标展开二级菜单，选择“循环播放”“播放 / 暂停”。图标本身只展开菜单，页面底部没有控制按钮。</li>
<li><strong>站长默认设置</strong>：在应用“壁纸与毛玻璃 → 循环播放视频”设置并发布。访客在菜单中修改只影响自己的当前播放。</li>
<li>修改视频地址后按字段提示发布并重建；只上传文件不会自动替换网站壁纸。</li>
</ul><p>播放失败时检查视频 URL 能否直接打开、编码是否被浏览器支持，以及开关和地址是否已发布到本次构建。</p></section><section><h3>调色板<a href="#调色板"><span>#</span></a></h3><p>网站右上角调色板入口由“调色板与显示”控制。若网站看不到入口，先确认入口开关已发布；若修改的是构建字段，再提交重建任务。</p></section></section><section><h2>12. 文章、动态和项目管理<a href="#12-文章动态和项目管理"><span>#</span></a></h2><section><h3>新建文章或动态<a href="#新建文章或动态"><span>#</span></a></h3><ol>
<li>打开左侧“文章管理”或“动态内容”。</li>
<li>点击“新建”或按 <code>Command + N</code>。</li>
<li>填写标题、slug、摘要、正文、日期、分类、标签、封面和 SEO 信息。</li>
<li>使用“插入”菜单添加标题、粗体、链接、列表、代码块或表格。</li>
<li>点击“保存草稿”。编辑器会显示已保存、保存中、离线或失败状态。</li>
<li>点击“提交任务 -&gt; 发布”，或者先生成私密网站预览。</li>
</ol><p>编辑已经上线的文章时，访客仍看到旧版本；新版本只有在构建和部署成功后才上线。</p></section><section><h3>导入已有 Markdown<a href="#导入已有-markdown"><span>#</span></a></h3><p>在文章或动态列表中点击“导入”：</p><ol>
<li>选择 Firefly 项目目录，扫描 <code>src/content</code>。</li>
<li>查看 Frontmatter、slug、图片路径和重复项。</li>
<li>解决冲突后导入可用项目。</li>
<li>导出导入报告，确认失败项。</li>
</ol><p>重复导入同一个文件不会创建重复内容；原 Markdown 和资源仍保留。MDX 文件可以只读查看和原样导出，应用不会在服务器直接执行来源不明的 MDX 组件。</p></section><section><h3>删除和批量操作<a href="#删除和批量操作"><span>#</span></a></h3><p>原文件或已上线内容需要先通过实际发布任务从网站正文、归档、RSS、站点地图和搜索中移除。列表多选后可提交一个统一删除/重建任务，成功后自动移入回收站；失败不会当作删除成功。单篇操作以确认窗口提示为准，已撤回内容可以直接移入回收站。</p><p>本地恢复副本与服务器内容是两回事。若列表仍显示“本地恢复副本”，在该列表中使用“彻底删除”，或“全选本地副本 → 批量彻底删除…”；这只清除本机副本，不删除服务器正式版本。</p></section><section><h3>修改“关于我”及其他独立页面<a href="#修改关于我及其他独立页面"><span>#</span></a></h3><ol>
<li>打开左侧 <strong>“关于我与独立页面”→“编辑关于我”</strong>，直接打开现有原文或草稿，不重复创建。</li>
<li>编辑 Markdown 正文，新增段落、标题、图片或链接。网页正文标题是以 <code>#</code> 开头的行；“管理标题”只用于应用列表。</li>
<li>点 <strong>保存草稿 → 提交任务 → 发布…</strong>，到发布中心等待服务器重建成功。</li>
<li><strong>新增页面…</strong> 可接入现有 About、留言板说明、友链说明。首次接入 Markdown 时可选空白正文，原文仍在历史中，已有草稿不会被清空。当前不新增任意 URL，可信友链 MDX 只读。</li>
<li><strong>删除…</strong> 明确提示影响范围。成功重建后地址返回 404，内容进入回收站；恢复后需再次发布。导航入口可另在“导航栏”移除。</li>
</ol><p>长篇“关于我”在这里修改；头像、昵称和简短简介仍在 <strong>“个人资料”</strong>。本轮只接入原文，没有发布测试内容。完整步骤及验收边界见 <a href="docs/content/about-page-management.md">关于我管理说明</a>。</p></section></section><section><h2>13. 图片、音频和相册<a href="#13-图片音频和相册"><span>#</span></a></h2><p>“资源管理”支持按以下类型筛选：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>logo、favicon、wallpaper、avatar、gallery、sponsor、music-cover、other</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以上传、预览、改名、复制公开 URL、查看大小和删除。图片会检查类型并压缩；音频支持 MP3、M4A、WAV、OGG。已被配置、文章、草稿或历史版本引用的资源默认不能直接删除，需要先替换引用。</p><p>相册继续使用原有相册入口，可以调整照片顺序、封面、替代文本、说明、日期、地点和标签。私密附件放在 <code>content-assets</code> 私有桶，不能因为 URL 难猜就当成公开保护。</p></section><section><h2>14. 网站预览<a href="#14-网站预览"><span>#</span></a></h2><p>应用提供两种预览：</p><ul>
<li><strong>编辑器快速预览</strong>：只检查 Markdown 排版，不代表最终网站模板效果。</li>
<li><strong>网站预览</strong>：使用实际 Astro 模板和样式。文章草稿通过发布中心生成一个私密预览任务，再在 WKWebView 中打开。</li>
</ul><p>网站预览支持桌面、平板和手机宽度，显示预览版本和生成时间。私密预览凭证短期有效，只能访问指定版本，不会改变线上发布指针，也不会进入公开 RSS、站点地图或 Pagefind。凭证过期后重新生成预览即可。</p></section><section><h2>15. 发布中心是怎样工作的<a href="#15-发布中心是怎样工作的"><span>#</span></a></h2><p>一次正式发布按以下顺序进行：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>保存草稿</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>-&gt; 校验内容和配置</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>-&gt; 生成不可变快照</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>-&gt; 创建发布任务</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>-&gt; worker 构建</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>-&gt; 检查首页、路由、RSS、站点地图和 Pagefind</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>-&gt; Nginx 原子切换目录</span></div></div><div><div><div>8</div></div><div><span><span>  </span></span><span>-&gt; 检查线上网站</span></div></div><div><div><div>9</div></div><div><span><span>  </span></span><span>-&gt; 标记已上线</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>发布中心的状态含义：</p><ul>
<li><strong>草稿未发布</strong>：只保存了编辑内容。</li>
<li><strong>配置已发布，等待重建</strong>：配置版本已经生效，但构建型改动还没有上线。</li>
<li><strong>排队中</strong>：服务器已收到任务。</li>
<li><strong>构建中</strong>：正在生成 Astro 网站。</li>
<li><strong>部署中</strong>：产物已生成，正在检查并切换线上目录。</li>
<li><strong>已上线</strong>：线上检查通过，数据库的 live 指针已更新。</li>
<li><strong>失败</strong>：线上保留上一个成功版本，可查看错误摘要并重试。</li>
</ul><p>Mac 应用关闭不会中断任务，worker 在服务器上继续执行。重复点击发布会使用幂等键避免重复任务；同一站点的任务按顺序执行，旧任务不会覆盖较新的线上版本。</p></section><section><h2>16. 配置历史、回滚和快照清理<a href="#16-配置历史回滚和快照清理"><span>#</span></a></h2><section><h3>配置回滚<a href="#配置回滚"><span>#</span></a></h3><p>在“快照管理”中选择历史版本，查看 JSON Diff 或可读 Diff，再点击“恢复此版本”。恢复会创建一个新的版本并留下审计记录，不会直接覆盖历史记录。</p></section><section><h3>内容回滚<a href="#内容回滚"><span>#</span></a></h3><p>在发布中心选择成功的发布任务，点击“回滚至此快照”。服务器会使用当时固定的代码包、内容和资源重新构建；只有线上检查通过后才会切换。内容回滚不会自动回滚独立的 runtime-config，如需完全恢复外观，还要在配置历史中回滚对应分区。</p></section><section><h3>释放空间<a href="#释放空间"><span>#</span></a></h3><p>配置快照回收站和服务器构建工作区是两个地方：</p><ul>
<li>“快照管理 → 回收站”处理配置历史记录，本身很小，不能靠删除它释放大量服务器磁盘。</li>
<li>“快照管理 → 服务器备份 · 释放磁盘”或“存储空间 → 服务器构建备份”可扫描服务器工作区，先看计划，再确认清理。</li>
<li>资源管理可检查未引用资源；它与服务器构建备份清理是不同操作。</li>
<li>服务器 <code>/var/lib/firefly-content/jobs/</code> 保存构建工作区和日志，不能在任务运行时直接删除。</li>
</ul><p>服务器清理前先查看任务状态，保留当前线上版本和约定数量的可回滚版本，再使用项目提供的清理脚本执行 dry-run。不要使用 <code>rm -rf /var/lib/firefly-content/*</code>，否则可能删除正在运行的构建或排查所需日志。</p></section></section><section><h2>17. 服务器日常维护<a href="#17-服务器日常维护"><span>#</span></a></h2><p>以下命令在服务器 SSH 会话中执行：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 查看 worker</span></div></div><div><div><div>2</div></div><div><span>systemctl</span><span> </span><span>status</span><span> </span><span>firefly-content-worker</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 查看最近日志</span></div></div><div><div><div>5</div></div><div><span>journalctl</span><span> </span><span>-u</span><span> </span><span>firefly-content-worker</span><span> </span><span>-n</span><span> </span><span>100</span><span> </span><span>--no-pager</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 查看当前线上目录</span></div></div><div><div><div>8</div></div><div><span>readlink</span><span> </span><span>/var/www/firefly-managed/current</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span># 查看线上发布标识</span></div></div><div><div><div>11</div></div><div><span>curl</span><span> </span><span>-fsS</span><span> </span><span>https://bytefun.site/content-release.json</span></div></div><div><div><div>12</div></div><div>
</div></div><div><div><div>13</div></div><div><span># 查看某个任务的构建日志（日志可能含私密正文，只在服务器查看）</span></div></div><div><div><div>14</div></div><div><span>tail</span><span> </span><span>-n</span><span> </span><span>80</span><span> </span><span>/var/lib/firefly-content/jobs/&lt;任务</span><span> </span><span>UUID&gt;/build.log</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>更新网站代码时不能直接修改已经启用的固定源码目录。正确流程是：本地检查代码、生成新的源码包和 SHA-256、上传到 <code>/opt/firefly-content/code/&lt;新哈希&gt;/</code>，在 Ubuntu 上安装 frozen lockfile 依赖，完成隔离构建后，把新代码哈希设为后续任务使用的版本。旧代码包要保留，方便回滚。</p></section><section><h2>18. 常见问题<a href="#18-常见问题"><span>#</span></a></h2><section><h3>修改后网页没有变化<a href="#修改后网页没有变化"><span>#</span></a></h3><p>先看字段说明：</p><ul>
<li>“运行时生效”：确认已经保存并发布配置，刷新网站或等待缓存过期。</li>
<li>“需要重建”：进入发布中心提交“重建线上内容与已发布配置”。</li>
</ul><p>普通 <code>pnpm build</code> 不会自动读取 Supabase，也不会自动部署。</p></section><section><h3>应用提示 <code>PGRST002</code><a href="#应用提示-pgrst002"><span>#</span></a></h3><p><code>Could not query the database for the schema cache</code> 通常是 Supabase PostgREST 暂时无法读取数据库结构，常见原因是数据库连接暂时不可用、迁移正在执行、项目暂停，或接口缓存尚未刷新。先检查 Supabase 项目状态和 SQL 迁移，再退出重开应用；不要把 service role key 放进应用来绕过问题。</p></section><section><h3>保存草稿超时<a href="#保存草稿超时"><span>#</span></a></h3><p>应用会保留当前编辑内容，并在超时后只读核对服务器版本，避免重复写入。检查网络、Supabase 状态和是否有另一个设备同时编辑。出现 409 冲突时先比较差异，再选择合并或重新加载。</p></section><section><h3>构建失败但网站还能打开<a href="#构建失败但网站还能打开"><span>#</span></a></h3><p>这是预期的保护行为。worker 在构建和产物检查通过后原子切换 <code>current</code>，再执行线上 HTTP 检查；检查失败会尝试切回上一目录，只有验证成功才标记“已上线”。若线上状态与数据库记录不一致，任务会保留保护状态，须先核对，不能直接改成成功。查看发布中心错误摘要和服务器 <code>build.log</code> 后处理。</p></section><section><h3>壁纸或顶栏效果消失<a href="#壁纸或顶栏效果消失"><span>#</span></a></h3><p>检查“壁纸与毛玻璃”中的 URL、模式、遮罩和顶栏透明设置是否已发布；涉及视频、字体或模板的字段还要重建。浏览器缓存也可能保留旧 CSS，可使用无痕窗口或强制刷新验证。</p></section><section><h3>页面、RSS 或搜索没有更新<a href="#页面rss-或搜索没有更新"><span>#</span></a></h3><p>文章和动态必须完成一次真实发布任务。只有 <code>succeeded</code> 且线上检查通过后，正文、分类、RSS、站点地图和 Pagefind 才会一起更新。</p></section><section><h3>应用启动时一直等待<a href="#应用启动时一直等待"><span>#</span></a></h3><p>检查 macOS 是否弹出钥匙串授权窗口。允许 Firefly Admin 读取登录凭证和本地草稿密钥后重新启动。若应用来自不同路径，macOS 可能再次询问授权。</p></section><section><h3>评论为什么没有显示想要的昵称或头像<a href="#评论为什么没有显示想要的昵称或头像"><span>#</span></a></h3><p>到网站 <code>/account/</code> 登录，编辑公开昵称和头像地址。评论读取用户公开资料，不展示登录邮箱；尚未设置公开昵称时显示稳定的“读者-…”名称，没有头像时使用默认头像。这里的评论账号资料与应用“个人资料”中的站长侧边栏介绍不是同一项设置。</p></section></section><section><h2>19. 当前状态和边界<a href="#19-当前状态和边界"><span>#</span></a></h2><section><h3>已完成并有生产记录<a href="#已完成并有生产记录"><span>#</span></a></h3><ul>
<li>网站已部署到腾讯云，Nginx 和 HTTPS 正常工作。</li>
<li>Supabase Auth、PostgreSQL、Storage、RLS、Realtime 和 Edge Functions 已接入。</li>
<li>配置草稿、发布、历史、审计和回滚已接入。</li>
<li>文章、动态、项目、Markdown 导入、资源管理和批量删除已接入。</li>
<li>服务器 worker、固定源码包、构建失败保护、Nginx 原子切换和内容回滚已有生产记录。</li>
<li>私密预览、定时发布、应用关闭后继续执行已有实现和验收记录。</li>
<li>视频资源迁移和 <code>admin-assets</code> 直传接口已完成生产部署，MP4 上传不再经过 Edge Function 中转。</li>
<li>Firefly Admin macOS 应用可构建、安装和使用。</li>
</ul></section><section><h3>需要留意的事项<a href="#需要留意的事项"><span>#</span></a></h3><ul>
<li>2026-09-28 已补齐真实管理员 MP4 上传验收，包括原生应用上传 32 MiB 文件；公开与私密视频、并发登记、伪造格式拒绝等 5 组生产检查及 14 项评论身份检查通过，测试数据已清理。视频最大文件仍受当前 Free 项目 50 MiB 上限约束。</li>
<li>“关于我与独立页面”已实现并安装，真实 API 接入原文且保留为 v1 草稿。本地原生编译、网站三项检查、183 项内容测试和 7 项构建映射测试通过；没有发布测试正文，也没有在生产删除真实 About 页面。最后一次列表滚动修正版的截图验收仍待补齐，不能记为全部界面验收通过。</li>
<li>字体下载依赖外部网络。字体下载失败会让构建任务失败，线上会继续保留上一成功版本。</li>
<li>新增网站组件、路由、字体、Markdown 内容处理或构建插件，必须生成新的固定源码包并重建网站。</li>
<li>本项目按个人使用需求维护，没有额外建设企业级灾备、告警和自动部署体系；已有服务器备份和回滚能力仍应保留。</li>
</ul></section></section><section><h2>20. 相关文件<a href="#20-相关文件"><span>#</span></a></h2><ul>
<li><a href="%E9%85%8D%E7%BD%AE%E6%B8%85%E5%8D%95.md">配置清单</a>：查看每个配置字段及是否需要重建。</li>
<li><a href="SUPABASE.md">Supabase 说明</a>：查看后端初始化和权限说明。</li>
<li><a href="docs/content/README.md">内容管理手册</a>：查看文章、动态、导入和发布细节。</li>
<li><a href="docs/content/deployment.md">部署手册</a>：查看服务器 worker、构建、切换和回滚操作。</li>
<li><a href="docs/content/phase4-verification.md">第四阶段生产验收</a>：查看真实任务和线上验证记录。</li>
<li><a href="docs/content/phase5-verification.md">第五阶段记录</a>：查看私密预览、定时发布和个人项目范围内的验收记录。</li>
<li><a href="FireflyAdmin/README.md">Firefly Admin 使用说明</a>：查看 Mac 应用登录、配置、预览和资源操作。</li>
<li><a href="docs/firefly-admin-api.md">Admin API 文档</a>：查看管理员接口和错误码。</li>
<li><a href="docs/content/about-page-management.md">关于我管理说明</a>：正文新增、修改、删除、恢复与验证范围。</li>
<li><a href="docs/content/video-comments-verification.md">视频上传与评论验收</a>：真实上传结果、评论身份和文件限制。</li>
<li><a href="docs/content/video-menu-verification.md">顶栏视频菜单验收</a>：最终二级菜单的生产任务与截图。</li>
</ul></section><section><h2>21. 最常用的操作清单<a href="#21-最常用的操作清单"><span>#</span></a></h2><section><h3>只改标题、壁纸、主题色或公告<a href="#只改标题壁纸主题色或公告"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>打开 Firefly Admin</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>-&gt; 对应配置分区</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>-&gt; 修改并保存草稿</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>-&gt; 发布配置</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>-&gt; 如果字段要求重建，再到发布中心提交重建</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>新增并上线一篇文章<a href="#新增并上线一篇文章"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>文章管理 -&gt; 新建文章</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>-&gt; 编辑正文和元数据</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>-&gt; 保存草稿</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>-&gt; 可选：生成私密网站预览</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>-&gt; 提交发布任务</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>-&gt; 等待“已上线”</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>服务器空间不足<a href="#服务器空间不足"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>先查看发布中心是否有 building/deploying 任务</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>-&gt; 打开“快照管理 -&gt; 服务器备份”或“存储空间 -&gt; 服务器构建备份”</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>-&gt; 扫描服务器并查看清理计划（dry-run）</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>-&gt; 保留当前线上版本和回滚版本</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>-&gt; 再清理旧构建工作区和日志</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>修改“关于我”并上线<a href="#修改关于我并上线"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>关于我与独立页面 -&gt; 编辑关于我</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>-&gt; 修改 Markdown 正文</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>-&gt; 保存草稿</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>-&gt; 提交任务 -&gt; 发布</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>-&gt; 等待发布中心显示“已上线”</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>记住：<strong>保存草稿、发布配置、重建网站、已上线是不同状态</strong>。运行时配置发布后即可由网站读取；需要重建的配置和正文，须等待相应服务器任务完成。“已保存”不代表访客已经看到改动。</p></section></section></section>]]></content>
    </entry>
    <entry>
      <id>https://bytefun.site/posts/firefly-admin-phase4-verification/</id>
      <title type="text">Firefly Admin 生产联调</title>
      <published>2026-09-18T17:23:56.000Z</published>
      <updated>2026-09-18T17:23:56.000Z</updated>
      <author><name>木子</name></author>
      <link rel="alternate" href="https://bytefun.site/posts/firefly-admin-phase4-verification/"/>
      <summary type="text">第四阶段生产构建、索引一致性与回滚验收。</summary>
      <content type="html"><![CDATA[<section><h2>生产发布验收<a href="#生产发布验收"><span>#</span></a></h2><p>这篇文章用于验证 Firefly Admin 到腾讯云服务器的真实构建与发布流程。</p><ul>
<li>原生应用保存远程草稿</li>
<li>服务器读取固定内容和配置快照</li>
<li>Astro 生成正文、RSS、站点地图与搜索索引</li>
<li>验收后通过发布中心回滚，保留历史版本</li>
</ul><p><strong>验收标记：FIREFLY_PHASE4_PRODUCTION_20260918</strong></p></section>]]></content>
    </entry>
</feed>
