release(main): 同步 dev 最新候选改动
Storage Persistence Guard / storage-persistence-audit (push) Has been cancelled
Client Quality Gates / Shared client and Web (push) Has been cancelled
Client Quality Gates / macOS Desktop (push) Has been cancelled
Client Quality Gates / Shared client and Web (pull_request) Has been cancelled
Client Quality Gates / macOS Desktop (pull_request) Has been cancelled
Storage Persistence Guard / storage-persistence-audit (pull_request) Has been cancelled

This commit is contained in:
Cheng Zhou
2026-07-16 17:15:50 +08:00
parent 32167fba02
commit d5279b124f
393 changed files with 51630 additions and 9711 deletions
+23 -4
View File
@@ -147,12 +147,12 @@ bash scripts/install.sh release --base-url https://ctms.example.com
```text
1. 检查 docker、docker compose、openssl、curl。
2. 检查 .env已存在则保留不改,不存在则新建。
3. 新建 main/release 的 .env 时自动生成 JWT 和 RSA 私钥。
2. 检查 .env保留已有身份与登录密钥,补齐并默认启用标准 ONLYOFFICE 配置,不存在则新建。
3. 所有环境自动生成独立的 ONLYOFFICE JWT 与稳定实例标识;新建 main/release 的 .env 时同时生成登录 JWT 和 RSA 私钥。
4. main/release 先构建并执行 backend-init,确保数据库迁移使用当前代码中的 Alembic revision。
5. 启动容器;默认执行 docker compose up -d --build--skip-build 时执行 docker compose up -d
5. 默认构建并启动数据库、后端、前端/Nginx 和 ONLYOFFICEONLYOFFICE 不再使用可选 Compose Profile
6. 执行 docker compose run --rm backend python -m alembic upgrade head。
7. 检查容器状态、后端环境、/health、/api/v1/auth/login-key。
7. 检查全部容器状态、后端环境、/health、/api/v1/auth/login-key 和 /onlyoffice/healthcheck
```
更新进入“执行部署更新”后会持续显示 Docker 镜像拉取与构建进度:交互终端使用滚动实时输出窗口,CI、远程面板或管道环境直接流式输出构建日志,并在两种模式下显示累计耗时。
@@ -164,6 +164,7 @@ git checkout
git pull
docker compose down -v
删除 pg_data
删除 backend/app/uploads
删除数据库 volume
自动安装系统依赖
```
@@ -194,6 +195,12 @@ ENV=development
JWT_SECRET_KEY=dev-secret
LOGIN_RSA_KEY_ID=default
LOGIN_RSA_PRIVATE_KEY=
ONLYOFFICE_ENABLED=true
ONLYOFFICE_JWT_SECRET=<替换为独立的随机密钥,至少 32 个字符>
ONLYOFFICE_INTERNAL_URL=http://onlyoffice
ONLYOFFICE_STORAGE_BASE_URL=http://backend:8000
ONLYOFFICE_INSTANCE_ID=ctms-dev-<替换为随机实例标识>
ONLYOFFICE_CONFIG_TTL_SECONDS=300
EOF
```
@@ -315,6 +322,12 @@ ENV=production
JWT_SECRET_KEY=<替换为 JWT 密钥>
LOGIN_RSA_KEY_ID=main-YYYYMMDD
LOGIN_RSA_PRIVATE_KEY=<替换为 RSA 私钥单行文本>
ONLYOFFICE_ENABLED=true
ONLYOFFICE_JWT_SECRET=<替换为不同于 JWT_SECRET_KEY 的独立随机密钥>
ONLYOFFICE_INTERNAL_URL=http://onlyoffice
ONLYOFFICE_STORAGE_BASE_URL=http://backend:8000
ONLYOFFICE_INSTANCE_ID=ctms-main-<替换为随机实例标识>
ONLYOFFICE_CONFIG_TTL_SECONDS=300
EOF
```
@@ -494,6 +507,12 @@ ENV=production
JWT_SECRET_KEY=<替换为生产 JWT 密钥>
LOGIN_RSA_KEY_ID=release-YYYYMMDD
LOGIN_RSA_PRIVATE_KEY=<替换为生产 RSA 私钥单行文本>
ONLYOFFICE_ENABLED=true
ONLYOFFICE_JWT_SECRET=<替换为不同于 JWT_SECRET_KEY 的独立生产随机密钥>
ONLYOFFICE_INTERNAL_URL=http://onlyoffice
ONLYOFFICE_STORAGE_BASE_URL=http://backend:8000
ONLYOFFICE_INSTANCE_ID=ctms-release-<替换为随机实例标识>
ONLYOFFICE_CONFIG_TTL_SECONDS=300
EOF
```
+2 -2
View File
@@ -106,8 +106,8 @@ npm run desktop:build:app
npm run desktop:build:app
```
正式桌面发布构建仍必须设置 updater 签名私钥执行
`npm run desktop:build -- --bundles app`
正式桌面发布构建仍必须设置 updater 签名私钥和 Apple 签名/公证变量后,先执行
`npm run desktop:release-readiness:check`,再执行 `npm run desktop:build:macos-release -- --ci`
后端改动应补充执行受影响模块的后端测试、迁移检查和接口回归。
+34 -4
View File
@@ -30,12 +30,19 @@ The runtime contract currently provides:
- file picker/save/open adapters
- desktop system notification adapters
- desktop updater adapters
- native desktop application menu and shortcut synchronization
- desktop window lifecycle and system appearance synchronization
Business modules must use this public runtime entry point. They must not inspect
Tauri globals or import Tauri packages directly. Native files, notifications,
secure session storage, and automatic updates must remain behind
`frontend/src/runtime/` and explicit capability flags.
Desktop menu accelerators and window appearance are synchronized through narrow
Tauri commands owned by `frontend/src/runtime/`. macOS close/reopen behavior is
handled by the Tauri event loop so a genuinely closed main window can be rebuilt from the Dock;
these concerns must not move into Vue business modules.
## Version Change
Update every client manifest with one command:
@@ -58,6 +65,10 @@ Manual edits that leave these files inconsistent fail CI.
## Stabilization Gates
Client builds require Node.js 22.13.0 or newer. The development frontend container,
Web CI, macOS release candidates, Windows internal validation, and the production
Web image use the same Node 22.13 baseline.
Client release candidates must pass the shared Web checks and the Desktop
release/security gate before promotion:
@@ -122,8 +133,8 @@ The release pipeline must:
2. build macOS Universal desktop artifacts;
3. sign and notarize the macOS app;
4. produce updater artifacts and `.sig` files with the updater private key;
5. generate `latest.json` and a checksum manifest;
6. verify the feed with `npm run desktop:update-feed:check -- --feed <latest.json> --artifacts-dir <artifact-dir>`;
5. generate `latest.json` and a checksum manifest with `npm run desktop:update-feed:create`;
6. verify the feed with `npm run desktop:update-feed:check -- --feed <release-dir>/latest.json --artifacts-dir <release-dir>`;
7. upload immutable artifacts first;
8. atomically replace `latest.json` last.
@@ -131,6 +142,13 @@ For Universal macOS artifacts, `latest.json` must provide both
`darwin-aarch64` and `darwin-x86_64` entries pointing at the same Universal
update package.
The signed release candidate workflow lives at
`.github/workflows/desktop-release-candidate.yml`. It must be run from a
matching `vX.Y.Z` tag and produces a verified release directory as a GitHub
artifact. That artifact is still only a release candidate; the release owner
must upload immutable files to the production download origin and replace
`latest.json` atomically after validation.
## Windows Build Readiness
Second-phase Windows work is limited to CI compatibility validation. A
@@ -147,6 +165,16 @@ Windows validation must cover:
- notification and updater compilation;
- future code-signing requirements.
The manual internal Windows validation workflow lives at
`.github/workflows/desktop-windows-internal.yml`. It is `workflow_dispatch`
only, runs on `windows-latest`, injects `VITE_BUILD_CHANNEL` and
`VITE_BUILD_COMMIT` from the selected branch context, executes the shared client
and Desktop safety gates, builds an unsigned NSIS installer with updater
artifacts disabled, and uploads the `.exe` plus `SHA256SUMS.txt` as a GitHub
Actions artifact. This workflow is for internal compatibility verification
only; it must not generate `latest.json`, update feeds, signed release
directories, or formal Windows release artifacts.
## Required Checks
```bash
@@ -167,8 +195,10 @@ export TAURI_SIGNING_PRIVATE_KEY="$UPDATER_PRIVATE_KEY"
export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="$UPDATER_PRIVATE_KEY_PASSWORD"
export REQUIRE_DESKTOP_SIGNING=true
npm run release:env:check
npm run desktop:build -- --bundles app
npm run desktop:update-feed:check -- --feed src-tauri/target/release/bundle/latest.json --artifacts-dir src-tauri/target/release/bundle
npm run desktop:release-readiness:check
npm run desktop:build:macos-release -- --ci
npm run desktop:update-feed:create -- --artifact <CTMS.app.tar.gz> --base-url <versioned-https-artifact-prefix> --output-dir <release-dir>
npm run desktop:update-feed:check -- --feed <release-dir>/latest.json --artifacts-dir <release-dir>
```
The Desktop build must run on macOS for the current first-phase target. A signed
+47
View File
@@ -0,0 +1,47 @@
# ONLYOFFICE 标准组件运行说明
ONLYOFFICE Document Server 是 CTMS 的标准部署组件,用于附件和文档版本预览,以及共享库在线协作编辑。PDF、图片、另存为和桌面端“打开”仍使用各自原有链路。
## 默认安装
执行标准安装即可同时安装 CTMS 与 ONLYOFFICE
```bash
bash scripts/install.sh dev
```
安装脚本会自动完成以下工作:
- 首次安装时生成独立的 256-bit 随机 `ONLYOFFICE_JWT_SECRET`,写入根目录 `.env`,且不在终端回显。
- 自动生成稳定、按部署环境隔离的实例标识并写入 `.env`
-`.env` 权限设置为 `0600`;该文件已被 Git 忽略。
- 默认构建并启动 backend、Document Server 与 Nginx,不再使用可选 Compose Profile。
- 将 Document Server 纳入容器状态、后端配置和 HTTP 健康检查。
- 后续启动默认复用密钥,防止后端和 Document Server 因签名密钥漂移而无法通信。
开发环境需要单独重建、验证或主动轮换密钥时,可使用兼容维护脚本:
```bash
bash scripts/onlyoffice-dev-up.sh
bash scripts/onlyoffice-dev-up.sh --rotate-secret
```
轮换会强制重建相关容器,已签发但尚未使用的短时预览配置会立即失效。生产环境不使用该自动生成流程,仍必须由部署密钥管理系统显式提供密钥。
可用 `ONLYOFFICE_IMAGE` 覆盖默认的 `onlyoffice/documentserver:9.4.0.1`。默认派生镜像只增加 Noto Sans CJK/Noto Serif CJK,不包含微软字体。
## 配置边界
- Compose 安装中的 `ONLYOFFICE_ENABLED` 默认 `true`;安装或更新旧环境时也会自动启用。
- `ONLYOFFICE_INTERNAL_URL` 默认 `http://onlyoffice`,只供后端健康探测。
- `ONLYOFFICE_STORAGE_BASE_URL` 默认 `http://backend:8000`,只允许后端生成固定的内部文件地址。
- `ONLYOFFICE_CONFIG_TTL_SECONDS` 默认 300 秒,允许范围为 60–900 秒。
- 内部内容接口不经过 Nginx 公网入口,仅接受 `AuthorizationJwt`,且 JWT 必须绑定到请求的精确 URL。
- 配置响应禁止缓存;JWT、内部文件 URL 和磁盘路径不得进入日志、审计详情或桌面缓存。
- `onlyoffice_data``onlyoffice_lib``onlyoffice_logs` 不是 CTMS 业务权威数据,不纳入业务备份或恢复来源。
## 生产部署要求
标准生产安装同样包含 ONLYOFFICE,并自动生成独立随机密钥和稳定、唯一的 `ONLYOFFICE_INSTANCE_ID`。正式上线前仍必须确认商业许可或 Community 版本授权边界、批准镜像、容量与备份监控策略,并完成 Web、macOS 和 Windows 内测端的真实 DOCX/XLSX/PPTX/WPS/ET/DPS 预览验证。
故障回退可临时关闭 `ONLYOFFICE_ENABLED`;下一次标准安装或更新会按标准组件策略重新启用。回退不得影响附件下载、另存为、打开、PDF 或图片预览。Community 与商业版本的授权边界以 [ONLYOFFICE 官方许可说明](https://helpcenter.onlyoffice.com/docs/faq/docs-community.aspx) 为准。
+1
View File
@@ -11,6 +11,7 @@
## 1.1 容器构建校验
- [ ] `docker compose config` 渲染成功
- [ ] 后端 `/code/app/uploads` 已持久化挂载到宿主机 `backend/app/uploads`
- [ ] `docker compose build backend frontend` 成功
- [ ] `docker compose run --rm --build backend-init` 成功
@@ -0,0 +1,94 @@
# 系统监测简易运维指南
## 定位与访问边界
系统监测用于单实例或小规模 CTMS 部署的日常巡检、访问追踪和初步故障定位,不替代集中式指标、日志和告警平台。监测页面及 `/api/v1/permission-monitoring/*` 仅允许系统管理员访问,项目 PM 不具备系统级监测权限。
## 健康检查
- `GET /health`:进程存活探针,不访问数据库,适合判断 HTTP 服务是否仍在响应。
- `GET /readyz`:服务就绪探针,会执行数据库查询;数据库不可用时返回 `503`。Nginx、安装脚本和后端容器健康检查均使用或暴露此探针。
- `GET /api/v1/permission-monitoring/health`:管理员健康详情,包含权限检查质量、缓存、进程内告警、数据库延迟、权限/安全日志写入队列、留存任务和监测表数据量。
部署后的最小检查:
```bash
curl -fsS http://127.0.0.1:8888/health
curl -fsS http://127.0.0.1:8888/readyz
docker compose ps
```
## 数据留存
后台任务在应用启动后立即清理一次,之后按固定间隔执行。默认策略:
- 权限访问日志和安全访问日志:90 天。
- 权限小时指标:400 天。
- 账号登录活动:180 天。
- 清理周期:86400 秒(每天)。
可通过后端环境变量调整:
```text
MONITORING_ACCESS_LOG_RETENTION_DAYS=90
MONITORING_METRIC_RETENTION_DAYS=400
MONITORING_RETENTION_INTERVAL_SECONDS=86400
MONITORING_IP_GEO_FALLBACK_ENABLED=true
MONITORING_IP_GEO_FALLBACK_TIMEOUT_SECONDS=2.5
MONITORING_IP_GEO_FALLBACK_CACHE_SECONDS=604800
MONITORING_IP_GEO_FALLBACK_MAX_LOOKUPS=10
USER_LOGIN_ACTIVITY_RETENTION_DAYS=180
USER_SESSION_ONLINE_SECONDS=300
```
访问日志留存范围为 7–3650 天,指标留存范围为 30–3650 天,清理周期范围为 60–604800 秒。修改后需重启后端。正式环境部署新版本前必须先执行 Alembic migration。
账号管理页的“在线”状态由服务端会话心跳计算:最近 `USER_SESSION_ONLINE_SECONDS` 秒内成功心跳且未退出的会话视为在线。登录记录保存客户端类型、版本、服务端观测到的登录 IP、登录/最近活动/退出时间;IP 属地由本地离线数据库按需解析,不发送到外部服务。登录记录接口仅限系统管理员访问并禁止 HTTP 缓存,不保存 Token 或密码,并与登录活动一起按 `USER_LOGIN_ACTIVITY_RETENTION_DAYS` 清理。
登录 IP 只接受可信反向代理提供的转发地址。Docker Compose 默认信任容器常用的 `172.16.0.0/12``192.168.0.0/16` 内部网段;其他部署必须通过 `TRUSTED_PROXY_CIDRS` 明确配置实际代理网段,不得直接信任任意来源的 `X-Forwarded-For`
## 来源地图服务器位置
来源地图中的服务器标记不使用固定城市或前端坐标。后端启动时按以下顺序确定部署服务器的公网地址,并缓存定位结果:
1. `MONITORING_SERVER_PUBLIC_IP` 明确指定的公网 IP,适用于禁止主动访问公网探测服务的生产网络。
2. `FRONTEND_PUBLIC_URL` 主机名解析出的公网 IP。
3. `MONITORING_PUBLIC_IP_DISCOVERY_URLS` 配置的公网出口 IP 查询服务。
默认公网查询服务为 `https://api64.ipify.org,https://icanhazip.com`,单次超时 2.5 秒。解析成功后通过本地 ip2region 数据库确定属地并转换为地图坐标;本地库没有坐标时复用下述 IP2Location.io 受控兜底。两条链路均失败时 API 返回 `server_location: null`,地图不会使用任意默认城市代替。默认打开全球视图,以便服务器与访问来源分属不同国家时仍能完整显示飞线。
## 访问来源坐标兜底
访问来源始终先使用本地 ip2region 和内置行政区质心解析。只有公网 IP 已识别、但本地链路无法得到地图坐标时,后端才调用 IPAddress.my 使用的 IP2Location.io 官方 JSON API `https://api.ip2location.io/` 补充经纬度;私网、回环、链路本地和无效地址不会发送给第三方。
兜底默认启用,单次请求最多查询 10 个尚未缓存的公网 IP,最多并发 5 个请求,超时 2.5 秒。成功结果缓存 7 天,失败结果缓存 1 小时;第三方不可用时继续返回本地解析结果,不影响访问来源接口。可设置 `MONITORING_IP_GEO_FALLBACK_ENABLED=false` 完全禁用外部查询。无密钥模式受服务方每日额度限制;如需配置 API Key,使用 `MONITORING_IP_GEO_FALLBACK_API_KEY`,后端通过 `Authorization: Bearer` 发送,禁止把密钥写入 URL 或日志。
受限网络建议显式配置:
```text
MONITORING_SERVER_PUBLIC_IP=<部署服务器公网IP>
MONITORING_PUBLIC_IP_DISCOVERY_URLS=
```
公网 IP 仅用于服务端定位,不写入来源分析 API 响应或浏览器界面。
## 日志完整性与隐私
- 每个请求由服务端生成 UUID 请求标识,并通过响应头 `X-Request-ID` 返回;权限日志和安全日志以该标识消除同一请求的重复统计。
- 写入队列保持非阻塞;队列满或数据库批次写入最终失败时不阻塞业务请求,但会累计丢弃量、失败批次、队列占用和最近错误时间,并在管理员健康页显示降级。
- 请求正文不采集。请求头仅保留运维白名单字段,认证、Cookie、密钥类字段统一脱敏;查询参数中的令牌、账号、受试者/患者、姓名、邮箱、电话、证件和地址类值会替换为 `[redacted]`
- IP、User-Agent 和请求路径仍属于运维审计数据,应按管理员最小授权和上述留存策略管理,不应复制到公开工单或通知正文。
## 常见异常处理
| 现象 | 首要检查 | 建议处理 |
| --- | --- | --- |
| `/health` 正常、`/readyz` 返回 503 | 数据库容器、连接串、迁移状态 | 检查 `docker compose ps`、数据库日志和 `alembic current` |
| 日志写入器降级 | 队列占用、丢弃量、失败批次、最近错误 | 检查数据库连接和容量;恢复后确认队列回落并重启以清零进程累计计数 |
| 留存任务异常 | 最近成功/错误时间 | 检查数据库权限、表结构和后端日志,确认 migration 已升级 |
| 页面显示“可能是上次成功结果” | 对应 API 或网络请求 | 使用刷新按钮重试,并结合 `/readyz` 与浏览器网络面板定位 |
| 安全事件突然增多 | 分类、来源 IP、路径和状态码 | 优先核对敏感路径探测、5xx 和无效令牌,不要仅按 4xx 总量或地理位置判断 |
## 当前边界与后续升级
权限缓存指标、告警列表和写入器累计计数仍为单进程内存状态,重启后会重置;当前也不负责短信、邮件、Slack 等外部通知。需要多实例部署、跨重启趋势、值班通知或长期容量分析时,应接入 Prometheus/OpenTelemetry、集中日志和 Alertmanager 类告警链路,并保留本模块作为管理员快速诊断入口。