Files
Sansheng-Intelligence/docs/SELF_HOSTING_JAVA_CSHARP.md

197 lines
9.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 脱离 GitHub 与 Pages 的 Java/C# 自托管方案
本文是给公司 Java/C# 后端、运维和测试人员的交接说明。GitHub Actions 和 GitHub Pages 只是当前演示环境,不是系统运行时依赖。生产环境可以完全不使用 GitHub、Pages、Python 常驻进程、数据库或大模型服务。
## 一、先区分两个节点
不要让对外 Web 服务器负责抓取外网:
```text
更新节点(可访问外网)
-> 抓取官方源、解析、去重、翻译、校验
-> 生成 JSON、HTML、Sitemap、JSON-LD、robots.txt、llms.txt
-> 生成完整 dist/ 发布包
-> 原子推送到 Web 节点
Web 节点(只提供静态文件)
-> IIS / Nginx / 对象存储 / CDN
-> 不运行 Python、Java、C#、Node、数据库或模型
```
更新节点可以是公司内网 Windows Server、Linux Server、公司 CI Runner 或一台具备外网出口的云主机。Web 节点即使完全不能访问外网也没有问题。
## 二、时间字段不能混用
- `generated_at`:本次 JSON 生成完成的 UTC 时刻,例如 `2026-08-17T12:30:21Z`。页面“更新时间”应展示它,并按访问者时区格式化。
- `as_of_date`:业务判断日期,当前按 `Asia/Shanghai` 计算,例如 `2026-08-17`。它用于日历过滤、备货窗口和“今日必看”,不是精确更新时间。
- `verified_at` / `checked_at`:单条来源最近一次核验时间。
更新程序禁止把 `generated_at` 固定为当天 `00:00:00Z`,也不能用 `as_of_date` 代替它。生成失败时保留上一版完整 JSON,不能把时间改新但把内容留空。
### GitHub Actions 成功不等于线上版本成功
资源检查绿色只代表 `dist/` 可以构建,不代表 Cloudflare Pages、IIS 或 Nginx 已经切到这次制品。发布后必须核对公开 JSON 的 `generated_at`、`as_of_date` 和本次构建日志;本仓库提供 `scripts/verify_public_snapshot.py`,可在 Gitea Runner 或 Windows/Linux 更新节点复用。
## 三、Java/C# 要复刻的最小流水线
### 独立站实际命令
```text
python scripts/update_all.py --site-url https://intel.example.com
```
`update_all.py` 会先抓取、清洗、翻译并生成公开 JSON,再调用 `build_site.py` 生成静态 SEO 页面;稳定版可以用 Java `ScheduledExecutorService` 或 .NET `BackgroundService` 编排同样的阶段,但不要让 Web 服务器承担抓取任务。
### 1. 抓取层
- Java:`java.net.http.HttpClient`、OkHttp 或公司统一 HTTP 客户端。
- C#:`HttpClientFactory` + `HttpClient`。
- 每个源独立超时、有限重试、User-Agent、响应大小上限和内容类型检查。
- HTML 解析使用 Java Jsoup 或 C# AngleSharp;JSON 使用 Jackson 或 `System.Text.Json`。
- 记录最终 URL、HTTP 状态、抓取时间、正文哈希、证据词和错误原因。
### 2. 规范化与去重层
必须保留以下字段语义:
```text
stable id
source_id / source / authority
url / source_url
title / title_original / title_english
source_language
published_at / verified_at
content_hash
start_date / end_date / as_of_date
needs_translation / translation_url / translation_fallback_url
```
日历至少按“规范化节日名 + 日期 + 国家/地区 + 来源类型”去重;跨天假期合并为一张卡,在 `related_dates` 或 `date_variants` 保存不同国家日期。不要用标题模糊相似度把不同国家的“独立日”“国庆日”强行合并。
### 3. 翻译层
当前项目没有 LLM、多模态理解、生图或提示词链路。翻译只是普通 HTTP 适配器:
```text
原文 -> 内容指纹缓存 -> Google 公共翻译(可用时)
-> Yandex 网页翻译入口(Google 不可用时)
-> 保留上一版中文或原文
```
中文官网、中文报名页、中文平台页不生成翻译按钮。CPSC、ZATCA 等 Google 不稳定域名可以保留 Yandex 回退;两边都不支持的域名隐藏按钮。翻译失败不得清空已验证的中文内容。
### 4. 校验与发布层
在替换正式目录前必须检查:
- 权威源成功数等于目标数;核心栏目非空。
- JSON 可解析、数据结构版本正确、`generated_at` 是合法 UTC ISO 8601。
- 所有公开 URL 为 `http/https`,没有内部缓存、日志、代理凭据或抓取状态文件。
- `sitemap.xml`、canonical、JSON-LD、robots.txt 和 `llms.txt` 与本次 `site_url` 一致。
- 任何一个关键步骤失败,都不替换旧版 `dist/`。
## 四、中国内地网络的三种部署方案
### 方案 A:公司外网更新节点,内网 Web 节点
更新节点放在具备合规外网出口的云主机或公司代理网络中。它运行定时任务,成功后把 `dist/` 和公开 JSON 作为制品上传到内网 Web 节点。Web 节点只需要访问内网制品仓库,不需要 VPN。
适合:抓取源较多、公司有统一代理或云主机、希望 Web 服务器最稳定的情况。
### 方案 B:公司 Gitea + Runner,不使用 Pages
1. 代码放在公司 Gitea。
2. 在有外网出口的 Windows/Linux 机器安装 Gitea Runner。
3. Runner 执行更新、测试、构建和制品上传。
4. IIS/Nginx 服务器只接收成功的 `dist/` 压缩包并原子解压切换。
Gitea 只负责代码和流水线,不要求启用 Pages。Runner 可以部署在外网可访问区,Web 服务器部署在内网。
### 方案 C:公司 GitHub 账号 + 自托管 Runner
可以使用公司 GitHub 账号保存代码,但不要依赖 Pages。自托管 Runner 在具备外网访问能力的服务器上执行:
```text
git checkout
更新数据
运行测试
构建 dist/
上传制品
部署到 IIS/Nginx
```
GitHub 只作为 Git 和 CI 控制面;真正的静态文件部署到公司服务器、对象存储或 CDN。
## 五、没有 VPN 时怎么办
不要把“所有服务器必须安装 VPN”当成唯一方案,按网络边界选择:
- **HTTPS 代理**:为更新节点配置 `HTTP_PROXY/HTTPS_PROXY`,代理凭据放在系统密钥库或 CI Secret,不写入仓库。
- **统一出口网关**:由网络团队提供固定出口、域名白名单和审计日志,应用只访问允许的官方域名。
- **远程更新 Worker**:在可访问外网的云主机执行抓取,向内网制品仓库上传构建包;内网服务器只下载制品。
- **分源策略**:国内可直连源由内网更新;境外源交给外网 Worker;两边通过 JSON 制品合并。
- **人工制品交接**:极端情况下由外网机器生成带 SHA-256 的 `dist.zip`,通过公司文件系统或制品库转入内网,部署端验证哈希后发布。
抓取节点无法访问某个源时,应保留上一版条目并记录源失败;不能因为单个国外站点不可达而清空整站数据。
## 六、Windows Server 部署
### 更新任务
- 使用 Windows Task Scheduler 或 Windows Service/Worker Service,每日固定时间运行 `update_all`。
- 工作目录使用独立服务账号;代理、翻译和 Git 凭据使用系统环境变量或凭据管理器。
- 更新过程先写 `runtime/update-*` 临时目录,成功构建后再替换正式目录。
### IIS 静态发布
推荐目录结构:
```text
D:\sites\sansheng\releases\20260817-123021\dist\
D:\sites\sansheng\current -> 当前发布目录
```
部署步骤:解压到新版本目录、验证 `index.html/data/*.json/sitemap.xml`、切换 IIS 站点物理路径或目录联接、保留最近 3-5 个版本。不要在 IIS 正在读取时直接覆盖正在服务的 `dist/`。
## 七、Linux Server 部署
- 使用 `systemd timer`、容器 CronJob 或公司 CI 调度更新节点。
- Web 节点使用 Nginx,静态根目录指向版本目录;通过软链接原子切换 `current`。
- 用 `rsync --delete` 或制品仓库同步完整目录,不要只复制部分 JSON。
- 发布后执行 HTTP 冒烟检查:首页、JSON、Sitemap、robots、一个详情页和一个图片资源。
- 保留最近 3-5 个版本目录,回滚只需把软链接切回上一版本并 reload Nginx。
## 八、Git 与无 Git 运行方式
生产更新不要求服务器执行 `git pull`。推荐顺序:
1. GitHub/Gitea/公司 Git 只保存源代码。
2. Runner 或更新服务构建不可变制品 `dist-YYYYMMDD-HHMMSS.zip`。
3. 制品库保存 SHA-256、构建日志和版本号。
4. Web 节点只接收已验证制品。
如果公司不允许生产服务器连接 Git,使用制品上传即可;如果公司需要审计,保留提交 SHA、数据 `generated_at`、构建日志和部署人。
## 九、回滚与故障处理
- 抓取失败:保留上一版 JSON 和 HTML,标记源失败,不发布半成品。
- 翻译服务失败:保留上一版中文,仍提供官方原文链接。
- 构建失败:不切换 `current`,继续服务上一版。
- 发布后发现问题:切回上一版本目录,随后用固定快照重建。
- 代码回滚必须连同公开 JSON、翻译缓存契约和前端构建结果一起回滚,避免字段版本不匹配。
## 十、交接验收
Java/C# 重构完成后至少通过:
```text
同一份输入快照 -> 输出 JSON 字段和数量稳定
相同内容指纹 -> 不重复翻译
单源失败 -> 旧内容保留
构建失败 -> dist 不替换
Windows/Linux -> 可定时运行、可查看日志、可回滚
无 VPN Web 节点 -> 仍能提供完整静态站
generated_at -> 精确 UTC 时刻,不是当天 00:00 的占位值
as_of_date -> 按 Asia/Shanghai 的业务日期计算
```