15 KiB
Kaidi 财务项目基础系统
本仓库当前处于 R1 开发执行阶段,需求基线见下方唯一总方案。
唯一需求、开发、测试和交付基线:
本地开发
后端默认连接本机 MySQL 127.0.0.1:3307,启动 Java 服务:
cd backend
./mvnw spring-boot:run
前端使用官方 TDesign Vue Next Starter,来源记录见 frontend/UPSTREAM.md。
cd frontend
HUSKY=0 npm ci
npm run dev:linux -- --host 0.0.0.0 --port 3002
本地 local profile 会创建 admin、project、finance、archive、demo 演示账号,初始密码均为
LocalOnly@123;该固定密码只用于本机开发,生产安装会生成随机管理员密码。
正式构建:
cd frontend
npm run build
后端运行后更新唯一 OpenAPI 机器制品:
./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 安装
(
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。
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;这些值在浏览器中填写。
只有已完成安装的修复性重装才从运行时配置读取数据库值,详见下文。
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。
安装后执行以下命令确认应用、反向代理和首次登录信息:
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 数据库,之后仍然只执行一个安装命令:
(
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:
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,保留数据库、字段加密密钥、
管理员数据和运维人员新增的环境变量:
# 使用上方同一安装命令,把 REINSTALL=false 改成 REINSTALL=true。
重装失败会恢复原应用链接、Java 运行时、环境文件、systemd 单元和 Nginx 配置;脚本会明确报告回滚不完整, 不会把恢复失败吞掉。
如果安装器已完成但向导尚未提交,可使用同一命令同时设置 REINSTALL=true 和 KAIDI_SETUP_WIZARD=true 重新生成一次性安装码;
该恢复路径只接受仍处于向导模式且未锁定的安装,正式模式不会被覆盖。
停用并移除程序
以下命令移除应用程序和服务,但保留 /var/lib/kaidi、/var/lib/kaidi-update、/etc/kaidi 以及数据库,
便于审计、备份或重新安装。确认数据备份前不要删除这些保留目录或 MySQL 数据卷。
安装器已经删除原 Nginx 默认站点;停用后需按该主机原有配置恢复或另行创建默认站点。
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 与在线更新
首次建立发布仓库时生成一次签名密钥:
./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 即会构建、测试、签名并发布:
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、脚本或命令。更新流程固定为:
- 点击“获取更新”,后端先读取 Release 元数据,再自动排队
DOWNLOAD;root 更新器从 Gitea 下载 manifest、签名和应用包,执行 RSA、 SHA-256、版本、文件名和压缩包路径校验后缓存到/var/lib/kaidi-update/cache/<version>。业务服务不停机。 - 页面显示
READY/等待重启后才出现“立即重启”;管理员点击后提交安装。未缓存或版本不一致 的包不能进入安装。 - 安装请求持久领取到
/var/lib/kaidi-update/processing;进程或主机中断后由 systemd 恢复未完成事务。 - root 更新器重新验签和验哈希,确认
mysqldump成功并生成权限为0600的备份,默认保留最近 5 份。 - 校验更新脚本和 systemd 单元后,原子切换 updater、systemd、Nginx 和应用版本。
- 同时检查后端直连、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、
审计参数或更新日志。
数据库迁移必须保持至少一个版本的向后兼容。查看状态和日志:
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 或旧版本健康问题后执行:
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
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;把这两个值写入
受控部署记录,再替换上述一键安装命令中的占位符。