Files
kaidi/README.md
T
2026-08-17 14:08:40 +08:00

283 lines
15 KiB
Markdown
Raw 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.
# Kaidi 财务项目基础系统
本仓库当前处于 R1 开发执行阶段,需求基线见下方唯一总方案。
唯一需求、开发、测试和交付基线:
- [财务项目基础系统开发交付总方案](docs/财务系统开发交付总方案.md)
## 本地开发
后端默认连接本机 MySQL `127.0.0.1:3307`,启动 Java 服务:
```bash
cd backend
./mvnw spring-boot:run
```
前端使用官方 TDesign Vue Next Starter,来源记录见 [`frontend/UPSTREAM.md`](frontend/UPSTREAM.md)。
```bash
cd frontend
HUSKY=0 npm ci
npm run dev:linux -- --host 0.0.0.0 --port 3002
```
访问:<http://localhost:3002/>
本地 `local` profile 会创建 `admin`、`project`、`finance`、`archive`、`demo` 演示账号,初始密码均为
`LocalOnly@123`;该固定密码只用于本机开发,生产安装会生成随机管理员密码。
正式构建:
```bash
cd frontend
npm run build
```
后端运行后更新唯一 OpenAPI 机器制品:
```bash
./scripts/export-openapi.sh http://127.0.0.1:18080
```
## R1 Preview 一键安装
Release 发布后,在 Linux 服务器执行下面一组命令即可安装。代码仓库和更新源固定为私有 Gitea
`https://git.awaioi.com/ERP-Team/kaidi`;服务器读取
`https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest`,不在生产机执行 `git pull` 或现场编译。
先从受信任的 CI 输出或内部发布记录取得 `RELEASE_TAG`、`INSTALLER_SHA256` 和 `PUBLIC_KEY_SHA256`。
后两项也会写入 Release 的 `bootstrap-checksums.txt`,但首次安装必须通过独立渠道核对,不能把同源下载值
直接当作信任根。私有仓库 Token 只授予仓库/Release 读取权限,不得复用 CI 的发布写 Token。
执行命令的机器需预装 `bash`、`sudo`、`curl`、`mktemp` 和 `sha256sum`,并能访问目标 Gitea;`jq`、Java、
Nginx 和数据库客户端由安装器补齐。应用固定安装到 `/opt/kaidi`、`/var/lib/kaidi`、
`/var/lib/kaidi-update` 和 `/etc/kaidi`。目标机应为专用主机,或确认现有 Nginx 默认站点可以被替换且
80 端口可用;安装器会接管默认 HTTP 站点。
### 直接 curl 安装
```bash
(
set -Eeuo pipefail
RELEASE_TAG=v1.0.0-preview.8
INSTALLER_SHA256=33d4921bcad7ef2be12f20bc0512a5f6df4227ea6dd3b7e6225164077d9cd9e7
PUBLIC_KEY_SHA256=807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9
REINSTALL=false
SETUP_WIZARD=true
read -rsp 'Gitea 只读 Token: ' GITEA_READ_TOKEN; printf '\n'
token_file=$(mktemp); header_file=$(mktemp); installer=$(mktemp)
trap 'rm -f "$token_file" "$header_file" "$installer"' EXIT
printf '%s' "$GITEA_READ_TOKEN" > "$token_file"
printf 'Authorization: token %s\n' "$GITEA_READ_TOKEN" > "$header_file"
unset GITEA_READ_TOKEN
chmod 0600 "$token_file" "$header_file" "$installer"
curl --fail --silent --show-error --proto '=https' --tlsv1.2 \
--header "@$header_file" \
"https://git.awaioi.com/ERP-Team/kaidi/releases/download/$RELEASE_TAG/install.sh" \
-o "$installer"
printf '%s %s\n' "$INSTALLER_SHA256" "$installer" | sha256sum -c -
sudo env \
KAIDI_RELEASE_API_URL=https://git.awaioi.com/api/v1/repos/ERP-Team/kaidi/releases/latest \
KAIDI_RELEASE_TOKEN_FILE="$token_file" \
KAIDI_RELEASE_PUBLIC_KEY_SHA256="$PUBLIC_KEY_SHA256" \
KAIDI_REINSTALL="$REINSTALL" \
KAIDI_SETUP_WIZARD="$SETUP_WIZARD" \
bash "$installer"
)
```
### 从固定 Git tag 拉取后安装
需要保留源码快照时,可以拉取与 Release 对应的固定 tag,再运行仓库内包装器。私有仓库会在 `git clone`
时要求 Gitea 凭据,包装器随后单独要求只读 Release Token;它会在 `sudo` 前校验 `deploy/install.sh`
的固定 SHA-256,再按同一公钥信任链安装最新签名 Release。
```bash
git clone --branch v1.0.0-preview.8 --depth 1 https://git.awaioi.com/ERP-Team/kaidi.git kaidi-preview
cd kaidi-preview
KAIDI_SETUP_WIZARD=true ./deploy/install-from-git.sh
```
首次安装使用向导时不要传 `KAIDI_DB_URL`、`KAIDI_DB_USERNAME` 或 `KAIDI_DB_PASSWORD`;这些值在浏览器中填写。
只有已完成安装的修复性重装才从运行时配置读取数据库值,详见下文。
```bash
KAIDI_DB_URL='jdbc:mysql://MYSQL_HOST:3306/kaidi_finance?useUnicode=true&characterEncoding=utf8&connectionTimeZone=UTC&serverTimezone=UTC' \
KAIDI_DB_USERNAME=kaidi KAIDI_DB_PASSWORD='DB_PASSWORD' ./deploy/install-from-git.sh
```
安装器会完成以下动作:
- 仅在 Linux + systemd 环境执行;首版 32 位支持基线为带 systemd 的 Debian/Ubuntu x86 32 位 Linux。
- 识别 `x86_64`、`aarch64`、`armv7` 或 32 位 `i386/i486/i586/i686`,校验 SHA-256 后安装对应的 Azul Java 17 JRE。
- 使用安装命令固定的 SHA-256 指纹校验 Release 公钥,再用该公钥验证发布清单 RSA 签名。
- 首次安装默认启用 `/setup` 向导,不在命令行保存数据库密码;向导只接受 MySQL 8.4.x,并在提交前验证 DDL/DML 权限。
- 安装签名 Release 到 `/opt/kaidi/releases/<version>`,以 `/opt/kaidi/current` 原子切换当前版本。
- 安装 Nginx、`kaidi-finance.service`、更新监听服务和健康检查。
- 向导只初始化一个由操作者填写的 `SYSTEM_ADMIN` 管理员,不创建项目、财务、资料或演示账号。
- 安装器把一次性安装码写入仅 root 可读的 `/root/kaidi-first-login.txt`;完成向导后写入锁定标记并切换正式应用。
安装完成后先执行 `sudo cat /root/kaidi-first-login.txt`,访问其中的 `/setup` 地址完成数据库和管理员配置;完成后再访问
`http://SERVER_IP/` 登录。Preview 使用 HTTP 时安装器默认设置
`SESSION_COOKIE_SECURE=false`;配置 HTTPS 反向代理后,应在 `/etc/kaidi/kaidi.env` 改为
`SESSION_COOKIE_SECURE=true` 并执行 `sudo systemctl restart kaidi-finance`。
安装后执行以下命令确认应用、反向代理和首次登录信息:
```bash
curl -fsS http://127.0.0.1/actuator/health | jq -e '.status == "UP"'
sudo systemctl --no-pager --full status kaidi-finance kaidi-update.path
sudo cat /root/kaidi-first-login.txt
```
### Linux 32 位
应用已按 Java 17 字节码构建,安装器会在 32 位 Linux 下载 `i686` JRE。MySQL 8.4 没有可用于该部署方式的
32 位服务端镜像,因此 32 位主机需要预先连接一台 MySQL 8.4 数据库,之后仍然只执行一个安装命令:
```bash
(
set -Eeuo pipefail
RELEASE_TAG=v1.0.0-preview.8; INSTALLER_SHA256=33d4921bcad7ef2be12f20bc0512a5f6df4227ea6dd3b7e6225164077d9cd9e7; PUBLIC_KEY_SHA256=807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9
read -rsp 'Gitea 只读 Token: ' GITEA_READ_TOKEN; printf '\n'
token_file=$(mktemp); header_file=$(mktemp); installer=$(mktemp)
trap 'rm -f "$token_file" "$header_file" "$installer"' EXIT
printf '%s' "$GITEA_READ_TOKEN" > "$token_file"
printf 'Authorization: token %s\n' "$GITEA_READ_TOKEN" > "$header_file"
unset GITEA_READ_TOKEN; chmod 0600 "$token_file" "$header_file" "$installer"
curl --fail --silent --show-error --proto '=https' --tlsv1.2 --header "@$header_file" \
"https://git.awaioi.com/ERP-Team/kaidi/releases/download/$RELEASE_TAG/install.sh" -o "$installer"
printf '%s %s\n' "$INSTALLER_SHA256" "$installer" | sha256sum -c -
sudo env KAIDI_RELEASE_TOKEN_FILE="$token_file" KAIDI_RELEASE_PUBLIC_KEY_SHA256="$PUBLIC_KEY_SHA256" \
KAIDI_SETUP_WIZARD=true bash "$installer"
)
```
32 位主机不能运行安装器自动创建的 MySQL 容器,因此在打开向导前,需要预先创建 `kaidi_finance`,并授予安装账号该库的
DDL、DML 权限。向导会连接数据库、核验 MySQL 8.4.x 版本并用临时表验证权限,全部通过后 Flyway 才会建表。
数据库管理员可在 MySQL 8.4 中按实际应用服务器地址执行以下基线 SQL:
```sql
CREATE DATABASE kaidi_finance CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
CREATE USER 'kaidi'@'KAIDI_SERVER_IP' IDENTIFIED BY 'DB_PASSWORD';
GRANT ALL PRIVILEGES ON kaidi_finance.* TO 'kaidi'@'KAIDI_SERVER_IP';
```
### 修复性重装
正常升级统一使用后台“在线更新”。只有安装文件损坏且后台更新不可用时,才在原服务器执行修复性重装;
安装器会优先读取向导完成后生成的 `/var/lib/kaidi/setup/application.env`,再回退到 `/etc/kaidi/kaidi.env`,保留数据库、字段加密密钥、
管理员数据和运维人员新增的环境变量:
```bash
# 使用上方同一安装命令,把 REINSTALL=false 改成 REINSTALL=true。
```
重装失败会恢复原应用链接、Java 运行时、环境文件、systemd 单元和 Nginx 配置;脚本会明确报告回滚不完整,
不会把恢复失败吞掉。
如果安装器已完成但向导尚未提交,可使用同一命令同时设置 `REINSTALL=true` 和 `KAIDI_SETUP_WIZARD=true` 重新生成一次性安装码;
该恢复路径只接受仍处于向导模式且未锁定的安装,正式模式不会被覆盖。
### 停用并移除程序
以下命令移除应用程序和服务,但保留 `/var/lib/kaidi`、`/var/lib/kaidi-update`、`/etc/kaidi` 以及数据库,
便于审计、备份或重新安装。确认数据备份前不要删除这些保留目录或 MySQL 数据卷。
安装器已经删除原 Nginx 默认站点;停用后需按该主机原有配置恢复或另行创建默认站点。
```bash
sudo systemctl disable --now kaidi-update.path kaidi-update.service kaidi-finance.service
sudo rm -f /etc/systemd/system/kaidi-finance.service /etc/systemd/system/kaidi-update.service /etc/systemd/system/kaidi-update.path
sudo rm -f /etc/nginx/conf.d/kaidi-finance.conf
sudo systemctl daemon-reload
sudo nginx -t && sudo systemctl reload nginx
sudo rm -rf /opt/kaidi
```
## Release 与在线更新
首次建立发布仓库时生成一次签名密钥:
```bash
./scripts/generate-release-key.sh release-signing-private.pem
base64 < release-signing-private.pem | tr -d '\n'
```
将私钥的 Base64 内容保存为 Gitea Actions Secret `RELEASE_SIGNING_KEY_B64`,并将命令输出的公钥 SHA-256
保存为 Gitea Actions Variable `KAIDI_RELEASE_PUBLIC_KEY_SHA256`。Gitea 内建 `GITEA_TOKEN` 只用于该工作流创建
Release;生产服务器使用另一个只读 Token。私钥不得提交到 Git。`.gitea/workflows/release.yml` 要求
act_runner 提供 `ubuntu-24.04` 标签,并在 tag 发布时执行后端、前端、OpenAPI、Shell、安装/更新和浏览器门禁。
工作流先建立不可见草稿,再显式上传并核对 9 个资产的名称和大小,最后才发布为 `/releases/latest`。Gitea 的
`latest` 会排除 `prerelease=true`,因此即使 tag 名含 `preview`,发布记录的 `prerelease` 也固定为 `false`;
Preview 属性由 SemVer 版本名表达。之后推送 tag 即会构建、测试、签名并发布:
```bash
git tag v1.0.0-preview.8
git push origin v1.0.0-preview.8
```
在线更新使用独立的 TDesign 页面:隔离的系统管理员进入“系统治理 → 系统更新”。权限与配置页只管理用户、角色、数据范围、表单模板和参数版本,不配置系统名称或域名。
更新源由 root 在 `/etc/kaidi/update.env` 固定为私有 Gitea Latest Release API;页面和普通 API 都看不到 Token,
也不能提交 URL、脚本或命令。更新流程固定为:
1. 点击“获取更新”,后端先读取 Release 元数据,再自动排队 `DOWNLOAD`;root 更新器从 Gitea 下载 manifest、签名和应用包,执行 RSA、
SHA-256、版本、文件名和压缩包路径校验后缓存到 `/var/lib/kaidi-update/cache/<version>`。业务服务不停机。
3. 页面显示 `READY/等待重启` 后才出现“立即重启”;管理员点击后提交安装。未缓存或版本不一致
的包不能进入安装。
4. 安装请求持久领取到 `/var/lib/kaidi-update/processing`;进程或主机中断后由 systemd 恢复未完成事务。
5. root 更新器重新验签和验哈希,确认 `mysqldump` 成功并生成权限为 `0600` 的备份,默认保留最近 5 份。
6. 校验更新脚本和 systemd 单元后,原子切换 updater、systemd、Nginx 和应用版本。
7. 同时检查后端直连、Nginx 健康端点、更新 path unit 和静态首页。全部通过后页面显示 10 秒倒计时并自动
刷新;刷新或短暂断线发生在安装中时,页面会恢复 3 秒轮询。任一检查失败则恢复并验证上一版本。
忙碌期间检查、下载和安装按钮保持禁用,防止重复请求;这就是更新执行冷却。10 秒只用于成功后的页面刷新,
不会延迟服务端切换。systemd 在 300 秒内连续失败 3 次后停止自动重试,避免失败任务空跑。
安装器会把 API 地址和只读 Token 同步写入 root-only 的 `/etc/kaidi/kaidi.env` 与
`/etc/kaidi/update.env`,两者权限均为 `0600`:前者供 Java 后端“检查更新”读取,后者供 root 更新器下载资产。
轮换 Token 时必须同时更新两个文件,再执行 `sudo systemctl restart kaidi-finance`;Token 不写入页面、状态 JSON、
审计参数或更新日志。
数据库迁移必须保持至少一个版本的向后兼容。查看状态和日志:
```bash
sudo systemctl status kaidi-finance kaidi-update.path
sudo journalctl -u kaidi-update.service -n 100 --no-pager
cat /var/lib/kaidi-update/status.json
```
如果 `status.json` 显示 `FAILED` 且日志提示回滚未完成,不要删除
`/var/lib/kaidi-update/processing/request.json` 或活动事务目录。systemd 在 300 秒内连续失败 3 次后会停止自动重试,
修复日志所示的磁盘、权限、Nginx 或旧版本健康问题后执行:
```bash
sudo systemctl reset-failed kaidi-update.service kaidi-update.path
sudo systemctl start kaidi-update.service
sudo journalctl -u kaidi-update.service -n 100 --no-pager
cat /var/lib/kaidi-update/status.json
```
只有状态恢复为 `SUCCEEDED`、`CURRENT` 或确定性的终态 `FAILED`,且 `transactions/active` 已处理完成后,
才算本次恢复结束。后台会保留真实失败状态,不会把待恢复的 processing 请求误显示成普通排队。
## 手工生成 Release
```bash
KAIDI_RELEASE_SIGNING_KEY=/secure/release-signing-private.pem \
KAIDI_TRUSTED_RELEASE_PUBLIC_KEY_SHA256=807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9 \
./scripts/package-release.sh 1.0.0-preview.8
KAIDI_TRUSTED_RELEASE_PUBLIC_KEY_SHA256=807c6aec1dc3f7ce494db16aa9d763c66f292033c38f328afd0390d2715a8cd9 \
./scripts/verify-release.sh dist/release
```
输出位于 `dist/release/`,包含安装脚本、签名清单、公钥、SHA-256 清单、前后端 CycloneDX SBOM、
首次安装指纹文件和完整应用压缩包。
打包脚本会把 Maven `revision` 与 npm 包版本临时绑定到 Release SemVer,构建结束后恢复工作区源文件;
JAR、两个 SBOM、签名清单或 tag 的版本只要有一项不一致,发布即失败。
签名清单同时绑定应用包、两份 SBOM、安装器、公钥、bootstrap 指纹和 Git 源码修订;本地脏工作区会明确记录
`source.dirty=true`,tag 工作流只接受干净 checkout 并记录 `source.dirty=false`。
发布命令同时在终端输出 `Trusted release public-key SHA-256` 与 `Installer SHA-256`;把这两个值写入
受控部署记录,再替换上述一键安装命令中的占位符。