diff --git a/CHANGELOG.md b/CHANGELOG.md index ff1ed20..40ff607 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## 2026-08-17 +### 全自动复现与网络环境教程 + +- 扩展 README,提供可直接复制的私有 GitHub 仓库 + GitHub Actions 更新、测试、数据提交、静态制品上传和公开快照校验流程。 +- 说明通过 Cloudflare Pages 监听 Actions 数据提交复现主站自动发布效果,并明确 `SANSHENG_SITE_URL`、`PUBLIC_SITE_URL`、仓库权限、构建命令和输出目录。 +- 分别给出有外网 Windows/Linux Server、Web Server 无外网但有更新节点、任何机器都无境外网络能力时的运行、制品传输、静态发布和回滚路径。 +- 明确 GitHub/Gitea/计划任务只是调度层,核心代码无需为某个 CI 平台分叉;全网无可用出口时不能伪装为全量自动更新。 +- 验证 README 中的 Actions YAML 可正常解析且包含 10 个必要步骤;Python 12 项、Node 14 项测试和独立站静态构建通过。 + ### 更新时间字段与自托管交接 - 修正公开 JSON 快照把 `generated_at` 固定为 `2026-08-17T00:00:00Z` 的问题,恢复为真实 UTC 生成时刻;`as_of_date` 继续只表示北京时间业务日期。 diff --git a/README.md b/README.md index ec05f7f..0bf0b94 100644 --- a/README.md +++ b/README.md @@ -244,24 +244,183 @@ C# 实现必须保留以下语义:稳定 ID、UTC `generated_at`、业务 `as_ 不要一次性重写全部抓取器,也不要先引入 LLM。先让 C# 后端稳定消费现有 JSON,再按源逐步替换,最容易定位数据差异和回滚。 -## 不依赖 GitHub Pages 的自动部署 +## 复现主站的全自动更新与部署 -公司 Gitea 没有 Pages 不影响部署。推荐由 Gitea Runner、Windows 计划任务或公司 CI 执行: +独立站不依赖 GitHub Actions 或 Pages,但现有命令可以直接接入 GitHub Actions、Gitea Runner、Windows 计划任务和 Linux `systemd`。研发人员应先按实际网络与服务器条件选择方案,不要在 Web 请求中执行抓取。 + +| 条件 | 推荐方案 | 更新节点 | Web 节点 | +| --- | --- | --- | --- | +| 希望最快复现主站效果 | 私有 GitHub 仓库 + GitHub Actions + Cloudflare Pages | GitHub Hosted Runner | Cloudflare Pages | +| 公司有可访问全部源站的 Server | Gitea Runner、Windows 计划任务或 Linux `systemd` | 公司 Server | 同机 IIS/Nginx,或另一台内网 Web Server | +| Web Server 无外网,但有其他外网节点 | 外网更新节点生成 `dist/`,通过制品库、SFTP、SMB 或对象存储传入 | 外网更新节点 | 只接收并发布 `dist/` | +| 公司任何机器都不能访问部分境外源 | 合规代理、统一出口、GitHub Hosted Runner 或外部更新 Worker | 具备合法网络出口的节点 | 内网 Web Server | + +### 最快复现:私有 GitHub 仓库 + GitHub Actions + +该方案最接近个人主站:GitHub Actions 每天抓取、测试、构建并提交新数据;Cloudflare Pages 监听数据提交并重新发布 `dist/`。GitHub 仓库必须设为私有,避免把翻译缓存、信源状态和运行信息公开。 + +1. 在 GitHub 创建一个私有空仓库,把公司 Gitea 中的本项目推送到该仓库的 `main` 分支。这个 GitHub 仓库可以只是研发复现环境,不要求替代公司 Gitea 主仓库。 +2. 打开 `Settings -> Actions -> General -> Workflow permissions`,选择 `Read and write permissions`,允许工作流提交更新后的数据快照。 +3. 打开 `Settings -> Secrets and variables -> Actions -> Variables`,创建: + - `SANSHENG_SITE_URL`:独立站最终访问根地址,不带末尾 `/`。 + - `PUBLIC_SITE_URL`:已经部署完成并可访问的站点根地址;尚未部署时可以暂不创建。 +4. 在 GitHub 仓库创建 `.github/workflows/update-standalone.yml`: + +```yaml +name: Update Sansheng Intelligence + +on: + workflow_dispatch: + schedule: + # UTC 01:30,即北京时间 09:30。 + - cron: "30 1 * * *" + +concurrency: + group: update-sansheng-intelligence-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: write + +jobs: + update: + runs-on: ubuntu-latest + timeout-minutes: 35 + env: + SANSHENG_SITE_URL: ${{ vars.SANSHENG_SITE_URL }} + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: "3.11" + cache: pip + cache-dependency-path: requirements.txt + + - name: Setup Node + uses: actions/setup-node@v6 + with: + node-version: "24" + + - name: Validate configuration + run: | + if [ -z "$SANSHENG_SITE_URL" ]; then + echo "Repository variable SANSHENG_SITE_URL is required" + exit 1 + fi + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Update data and build dist + run: python scripts/update_all.py --site-url "$SANSHENG_SITE_URL" + + - name: Run contracts + run: | + python -m unittest discover -s tests -p "test_*.py" + node --test tests/test_frontend.mjs + + - name: Commit data snapshots + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add \ + data/textile-intelligence.json \ + data/textile-trends.json \ + data/textile-intelligence-source-status.json \ + data/textile-zh-cache.json + if git diff --cached --quiet; then + echo "No data changes to commit" + else + git commit -m "chore: update sansheng intelligence snapshot" + git push + fi + + - name: Upload static artifact + uses: actions/upload-artifact@v4 + with: + name: sansheng-intelligence-dist + path: dist/ + if-no-files-found: error + retention-days: 7 + + - name: Verify public deployment + if: ${{ vars.PUBLIC_SITE_URL != '' }} + run: | + python scripts/verify_public_snapshot.py \ + --base-url "${{ vars.PUBLIC_SITE_URL }}" \ + --expected-file data/textile-intelligence.json \ + --retries 12 \ + --interval 30 +``` + +5. 在 GitHub 的 `Actions` 页面手动运行一次 `Update Sansheng Intelligence`。首次验收应确认:抓取成功、测试通过、仓库出现数据提交、`sansheng-intelligence-dist` 制品可以下载并直接由静态服务器访问。 +6. 若使用 Cloudflare Pages,在 Pages 项目中连接这个 GitHub 仓库并配置: + - 生产分支:`main`。 + - 构建命令:`python scripts/build_site.py --site-url "$SANSHENG_SITE_URL"`。 + - 输出目录:`dist`。 + - 环境变量:`SANSHENG_SITE_URL` 使用与 GitHub 仓库变量相同的正式站点根地址。 +7. Cloudflare Pages 会在 Actions 推送数据提交后自动构建。设置 `PUBLIC_SITE_URL` 后,Actions 最后一步会比较公开 JSON 与本次数据;公开站仍是旧快照时工作流会失败,不能把“构建成功”误当成“部署成功”。 + +GitHub Hosted Runner 的公网出口通常比内地 Server 更适合抓取境外官方源,但仍可能被个别源站按机房 IP、地区或反爬规则拒绝。项目的严格模式会保留旧版并拒绝发布不完整数据;不要为了让工作流变绿而删除 `--strict` 或绕过 `source_target == source_success`。 + +### 有外网访问能力的 Windows/Linux Server + +这种环境不需要 GitHub,也不需要 Pages。Server 直接从公司 Gitea 检出代码,作为更新节点运行: ```text -检出仓库 +检出或更新代码 -> 创建 Python 虚拟环境并安装 requirements.txt + -> 设置 SANSHENG_SITE_URL + -> 运行 update_all.py -> 运行 Python 与 Node 契约测试 - -> 设置唯一 SANSHENG_SITE_URL - -> 运行 update_all.py(需要联网更新)或 build_site.py(只发布已提交快照) - -> 把 dist/ 作为构建制品 - -> 同步到 IIS/Nginx 新版本目录 - -> 健康检查通过后切换目录/发布槽 + -> 得到不可变的 dist/ 制品 + -> 发布到 IIS/Nginx 新版本目录 + -> 请求公开 JSON 校验 generated_at 和 as_of_date ``` +Windows Server 可以复用 `update.bat` 和 `install-schedule.bat`,或由 Task Scheduler/Windows Service 定时运行: + +```powershell +git pull --ff-only +$env:SANSHENG_SITE_URL='https://实际独立站域名' +.\.venv\Scripts\python.exe scripts\update_all.py --site-url $env:SANSHENG_SITE_URL +.\.venv\Scripts\python.exe -m unittest discover -s tests -p 'test_*.py' +node --test tests\test_frontend.mjs +``` + +Linux Server 使用同一组 Python 命令,由 `systemd timer`、Cron 或 Gitea Runner 调度;构建成功后把 `dist/` 同步到新的 Nginx 版本目录,再通过 `current` 软链接原子切换。完整的 Windows/IIS、Linux/Nginx、版本目录和回滚方式见 [Java/C# 自托管方案](docs/SELF_HOSTING_JAVA_CSHARP.md)。 + +### Web Server 没有外网,但存在外网更新节点 + +这是公司内网环境的推荐架构。外网更新节点可以是 GitHub Hosted Runner、公司代理区 Server、境外云主机或具备统一出口的 Gitea Runner;内网 Web Server 不运行抓取代码。 + +1. 外网更新节点运行 `update_all.py`,生成完整 `dist/`。 +2. 将 `dist/` 压缩为带版本时间和提交 SHA 的制品,并生成 SHA-256。 +3. 通过公司制品库、对象存储、SFTP、SMB 或人工审批通道传入内网。 +4. Web Server 把制品解压到新版本目录,校验 SHA-256、JSON、Sitemap 和首页。 +5. 验证通过后切换 IIS 物理路径或 Nginx `current` 软链接;失败则继续服务旧版本。 +6. 发布完成后,在能访问公开站点的节点运行 `verify_public_snapshot.py`。 + Web 节点只接收 `dist/`。不要把仓库、`.venv`、`data/textile-zh-cache.json`、`data/textile-intelligence-source-status.json`、`runtime/` 或抓取日志放进网站目录。IIS/Nginx 必须优先返回真实静态文件,不能让 `/data/*.json` 被 SPA fallback 替换成首页 HTML。 -仓库没有预置绑定具体服务器账号的 Gitea workflow,因为 Runner 标签、发布协议、IIS 路径和密钥名尚未确定。确定这些公司环境参数后,只需把上面的命令接入现有流水线,不需要 Pages。 +### 任何可用机器都没有境外网络能力 + +这种情况下不能复现“全部官方源自动更新”。代码无法替代网络出口,也不能安全地用空数据或旧时间伪装成功。只能选择以下路径之一: + +- 由公司网络团队提供合规的 `HTTP_PROXY`、`HTTPS_PROXY`、统一出口或官方域名白名单。 +- 使用 GitHub Hosted Runner、外部更新 Worker 或具有合法外网能力的云主机生成制品,再传入内网。 +- 由研发人员在具备外网能力的工作站运行 `update.bat`,人工提交带 SHA-256 的 `dist/` 制品。 +- 明确缩减信源范围,并同步调整数据契约、严格源数量和产品承诺;不能静默跳过不可访问源。 + +如果没有任何合规外网节点,也不允许人工制品交接,就只能基于仓库里最后一次已验证快照运行 `build_site.py`,可以持续提供静态站,但不能声称数据仍在自动更新。 + +仓库不预置绑定具体服务器账号的 Gitea 部署 workflow,因为 Runner 标签、发布协议、IIS/Nginx 路径、制品库和密钥名必须由公司环境决定。确定这些参数后,把本节相同命令接入现有流水线即可,不需要修改抓取、数据结构或前端代码。 ## SEO/GEO 基线