# 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 ``` 访问: 本地 `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 已公开,提供两种互斥方式: 1. **systemd 一键安装(推荐)**:程序独占 `/opt/kaidi` 和 `kaidi-finance.service`,不创建宝塔 Java 项目。 2. **宝塔手动部署**:下载压缩包后解压并运行本地初始化脚本,再创建 Spring Boot 项目。 两种方式不能同时运行在同一个端口。程序不会安装 MySQL,数据库只支持运维人员预先准备的外部 MySQL 8.4.x, PostgreSQL 18 兼容工作继续冻结。 ### systemd 一键安装(推荐) 先在宝塔面板停止并删除当前错误的 Java 项目,再执行: ```bash curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.26/install.sh | sudo env KAIDI_APP_PORT=18080 bash ``` 该命令只安装程序运行所需的 systemd 单元,自动创建 `kaidi` 用户并检测现有 Java 17(包括 `/www/server/java/*/bin/java`);没有可用 Java 时才下载匹配架构的运行时。它不会安装 MySQL,也不会要求宝塔面板提供 `kaidi` 用户。安装完成后查看 `/root/kaidi-first-login.txt`,通过反向代理域名进入 `/setup`。 ### 彻底清理旧安装 重新安装前先在宝塔面板停止并删除名为 `kaidi-finance` 的 Java 项目,避免面板守护进程重新拉起旧 Java。 随后执行一键清理: ```bash curl -fsSL https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.24/purge.sh \ -o /tmp/kaidi-purge.sh \ && printf '%s %s\n' c49c5460260d2c67d88aced99463e3d98a67f8ef0309f5c8d5d8a8f5e4addf36 /tmp/kaidi-purge.sh \ | sha256sum -c - \ && sudo env KAIDI_PURGE_CONFIRM=DELETE_LOCAL_KAIDI_INSTALLATION bash /tmp/kaidi-purge.sh \ && rm -f /tmp/kaidi-purge.sh ``` 清理脚本会先停止 systemd 和遗留 Kaidi 进程,将旧配置、文件存储、安装码以及已有数据库备份归档到 `/root/kaidi-reinstall-backups/`,再删除 `/opt/kaidi`、`/www/wwwroot/kaidi`、`/etc/kaidi`、 `/var/lib/kaidi`、`/var/lib/kaidi-update`、`/var/log/kaidi` 和三个 systemd 单元。恢复包权限固定为 `0600`,确认新安装及数据无误后再由 root 删除。脚本不删除外部 MySQL、系统 Java、Nginx 或宝塔站点配置; 新安装必须连接一个新的空 MySQL 数据库,旧数据库保留用于回滚。 ### Preview.25 直链与宝塔手动部署 本版不使用一键安装脚本。发布物是一个不带顶层目录的压缩包,下载后直接解压到版本目录,再由宝塔面板创建 Spring Boot 项目。数据库、JDK、Nginx 和宝塔本身都由运维人员准备;程序不会自动安装 MySQL 或任何第三方服务。 下载地址: `https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.25/kaidi-finance-1.0.0-preview.25.tar.gz` 校验文件:`https://git.awaioi.com/ERP-Team/kaidi/releases/download/v1.0.0-preview.25/SHA256SUMS` 服务器要求:Linux + systemd、Java 17(宝塔项目选择 JDK 17)、可访问外部 MySQL 8.4.x;32 位 Linux 需要宿主机 已经提供可运行的 32 位 Java 17。程序只连接已有数据库,不安装数据库客户端或数据库服务。初始化只需要 `bash`、`openssl`、`sha256sum` 和基础 Linux 工具;启用在线更新前,另行准备 `curl`、`jq`、`flock`、`gzip`、`runuser` 以及与外部 MySQL 兼容的 `mysqldump`。程序不会替你安装这些依赖。 #### 1. 下载、校验、解压 以下命令只做文件下载和解压,不执行远程脚本。`VERSION` 必须替换成发布版本;归档内的 `app.jar`、`public/`、`ops/` 必须直接位于 `RELEASE_ROOT`,不能再嵌套一层目录。 ```bash VERSION=1.0.0-preview.25 APP_ROOT=/www/wwwroot/kaidi RELEASE_ROOT="$APP_ROOT/releases/$VERSION" sudo install -d -m 0755 "$RELEASE_ROOT" wget -O "/tmp/kaidi-finance-$VERSION.tar.gz" \ "https://git.awaioi.com/ERP-Team/kaidi/releases/download/v$VERSION/kaidi-finance-$VERSION.tar.gz" wget -O /tmp/kaidi-SHA256SUMS \ "https://git.awaioi.com/ERP-Team/kaidi/releases/download/v$VERSION/SHA256SUMS" (cd /tmp && grep " kaidi-finance-$VERSION.tar.gz$" kaidi-SHA256SUMS | sha256sum -c -) sudo tar -xzf "/tmp/kaidi-finance-$VERSION.tar.gz" -C "$RELEASE_ROOT" ``` 如果服务器没有 `wget`,可使用浏览器下载后上传同一文件;`curl -fL URL -o FILE` 也只用于下载压缩包,不能替代初始化脚本。 #### 2. 执行本地初始化 初始化脚本来自已下载的归档,并在本机以 root 运行。它创建 `kaidi` 启动用户、受保护配置、在线更新监听器和一次性 安装码;不会下载 Java、安装 MySQL 或修改反向代理。端口参数可改为 `1024-65535` 中的空闲端口,默认 `18080`。 ```bash sudo "$RELEASE_ROOT/ops/baota-init.sh" 18080 sudo cat /root/kaidi-first-login.txt ``` 初始化失败会回滚本次写入的配置、unit 和 `current` 链接,可以直接修正原因后重试;已经存在正式安装时不要重复执行,先按“彻底清理旧安装”流程处理。 #### 3. 宝塔 Spring Boot 项目字段 在宝塔面板创建项目,类型选择 **Spring Boot**,不要选择 Tomcat。填写如下: | 字段 | 填写值 | | --- | --- | | 项目路径 | `/www/wwwroot/kaidi/current` | | 项目名称 | `kaidi-finance` | | 项目 JDK | `JDK 17` | | 项目启动命令 | `/www/wwwroot/kaidi/current/ops/baota-start.sh` | | 启动用户 | `kaidi` | | 项目端口 | `18080`(与初始化参数一致) | | 绑定域名 | 业务域名,例如 `fi.awaioi.com` | | 后端路由 | `/` | | 前端资源 | 留空,Spring Boot 直接提供 `public/` | | 进程守护 | 开启 | | 项目意外重启 | 勾选 | **环境变量全部留空,不要创建 DB_URL、DB_USERNAME、DB_PASSWORD 或 FIELD_ENCRYPTION_KEY。** `ops/baota.env.example` 仅是说明文件,不能把其中的占位值粘贴到面板。启动器先读取 `/etc/kaidi/kaidi.env`,向导完成后 再读取 `/var/lib/kaidi/setup/application.env`;宝塔只有填写了非空覆盖值时才会覆盖受保护配置。数据库密码不会出现在 Java 启动命令或 `ps` 参数中。 #### 4. 首次向导与反向代理 启动宝塔项目后,访问 `https://业务域名/setup`,输入 `/root/kaidi-first-login.txt` 中的一次性安装码,填写外部 MySQL 连接信息和系统管理员账号密码。迁移完成后应用会退出一次,宝塔守护进程会自动拉起正式模式;再次访问域名根路径即可登录。 首次配置数据库密码必须使用 HTTPS;没有域名时通过 SSH 隧道访问回环端口: ```bash ssh -L 18080:127.0.0.1:18080 root@SERVER_IP ``` 本机浏览器打开 `http://127.0.0.1:18080/setup`。反向代理只需把整个 `/` 转发到 `http://127.0.0.1:18080`,并保留以下请求头: ```nginx location / { proxy_pass http://127.0.0.1:18080; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 120s; client_max_body_size 500m; } ``` HTTPS 启用后,在 `/etc/kaidi/kaidi.env` 将 `SESSION_COOKIE_SECURE` 设为 `true`,再从宝塔面板重启项目。健康检查: ```bash curl -fsS http://127.0.0.1:18080/actuator/health curl -fsS http://127.0.0.1:18080/ | head systemctl is-enabled kaidi-update.path ``` #### 5. 后台在线更新 初始化脚本已启用 `kaidi-update.path`,后台“系统治理 → 系统更新”流程保持不变:点击“获取更新”下载并验签,确认后点击“立即重启”。 root 更新器原子切换 `/www/wwwroot/kaidi/current`,向当前 Java 进程发送停止信号;宝塔的意外重启守护随后按新 `current` 链接拉起 Java, 健康检查通过后页面等待 10 秒并自动刷新。更新器不会安装或启动 `kaidi-finance.service`,也不会修改 Nginx。 更新前请在服务器预装与外部 MySQL 兼容的 `mysqldump`,更新器只生成权限为 `0600` 的备份,不会自动安装 MySQL。迁移失败时恢复旧应用版本并保留数据库备份和事务证据,遵循至少一个版本向后兼容的迁移规则。 后续版本仍按同样方式直接下载并解压到新的 `releases/`;不要手工删除 `current`,在线更新负责原子切换和回滚。 ### Linux 32 位 压缩包本身不绑定 CPU 架构,关键是宝塔项目选用可运行的 32 位 Java 17。32 位主机不能在本机运行 64 位 JDK,也不应尝试在本机安装 MySQL 8.4;提前准备外部 MySQL,完成上述手动解压、初始化和向导即可。 ### 停用并移除程序 以下命令移除应用程序和服务,但保留 `/var/lib/kaidi`、`/var/lib/kaidi-update`、`/etc/kaidi` 以及数据库, 便于审计、备份或重新安装。确认数据备份前不要删除这些保留目录或外部 MySQL 数据。 反向代理由运维人员独立管理,停用程序不会修改其配置。 ```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 systemctl daemon-reload 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 发布 Token 只用于该工作流创建 Release;生产服务器从公共 Release 下载,不保存 Token。私钥不得提交到 Git。`.gitea/workflows/release.yml` 要求 act_runner 提供 `ubuntu-24.04` 标签,并在 tag 发布时执行后端、前端、OpenAPI、Shell、安装/更新和浏览器门禁。 工作流先建立不可见草稿,再显式上传并核对 10 个资产的名称和大小,最后才发布为 `/releases/latest`。Gitea 的 `latest` 会排除 `prerelease=true`,因此即使 tag 名含 `preview`,发布记录的 `prerelease` 也固定为 `false`; Preview 属性由 SemVer 版本名表达。之后推送 tag 即会构建、测试、签名并发布: ```bash git tag v1.0.0-preview.25 git push origin v1.0.0-preview.25 ``` 在线更新仍使用独立的 TDesign 页面:系统管理员进入“系统治理 → 系统更新”。更新源由 root 在 `/etc/kaidi/update.env` 固定为公共 Gitea Latest Release API;页面和普通 API 不能提交 URL、Token、脚本或命令。 宝塔模式下,root 更新器只负责下载、验签、切换 `current` 和停止旧 Java,**不创建或重启 `kaidi-finance.service`**;宝塔项目的“项目意外重启”负责按新链接拉起 Java。 更新流程固定为: 1. 点击“获取更新”,后端排队 `DOWNLOAD`;更新器下载 manifest、签名和应用包,执行 RSA、SHA-256、版本、文件名和压缩包路径校验, 通过后缓存到 `/var/lib/kaidi-update/cache/`,业务服务继续运行。 2. 页面显示 `READY/等待重启` 后才出现“立即重启”;未缓存或版本不一致的包不能进入安装。 3. 管理员点击“立即重启”,更新器预先调用宿主机已有的 `mysqldump`,备份权限为 `0600`,默认保留最近 5 份;程序不会安装 MySQL 或客户端。 4. 更新器安装并保护新版本目录,原子切换 `/www/wwwroot/kaidi/current`,校验旧 Java 的受控 PID 后发送停止信号;宝塔守护自动启动新版本。 5. 健康端点、静态首页和运行版本全部通过后,页面等待 10 秒自动刷新;期间断线由前端轮询恢复。任一检查失败则切回上一应用版本, 保留数据库备份和事务证据供人工处理。 忙碌期间检查、下载和安装按钮保持禁用,防止重复请求;10 秒只用于成功后的页面刷新,不延迟服务端切换。数据库迁移必须至少保持一个版本向后兼容。 查看状态和日志: ```bash sudo systemctl status kaidi-update.path kaidi-update.service sudo journalctl -u kaidi-update.service -n 100 --no-pager cat /var/lib/kaidi-update/status.json ``` 如果 `status.json` 显示 `FAILED` 且日志提示回滚未完成,失败请求会归档到 `/var/lib/kaidi-update/failed`,活动事务保留在 `transactions/active`,系统不会自动重复安装。修复日志所示的 磁盘、权限或旧版本健康问题后,才明确执行一次: ```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.25 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`。 发布命令同时输出 Release 公钥和归档校验值;将压缩包 SHA-256 写入直链部署说明后再发布。