Files
ledgerly-docs/dev/build-deploy.md
T
rosercode d94d4e1ed5 docs: 补充收支记录页 AI 生成规则、CORS/反向代理与邮箱检查进度时间戳
同步近期 ledgerly 功能与配置变更到文档站:新增收支记录页「AI 生成规则」入口的用户与开发说明、补充新接口与规则应用逻辑、修正 CORS/反向代理与运行环境编码说明,并更新邮箱检查进度时间戳描述。

变更内容:
- guide/transactions.md 批量操作区新增「AI 生成规则」按钮与「AI 生成规则(从勾选账单)」小节(含「同时应用到勾选账单」),引用新截图。
- guide/ai.md、guide/rules.md、guide/pages.md 补充两处生成入口与「同时应用」说明。
- dev/api.md 新增 POST /api/ai/rule/apply-transactions;dev/rule-engine.md、dev/ai.md 补充 applyToTransactions 与两个生成入口。
- dev/build-deploy.md 将 CORS 更新为放开全部来源并信任反向代理转发头,新增运行环境 UTF-8 locale 说明。
- guide/email-bills.md 补充「立即检查」进度每行带 HH:mm:ss 时间戳。
- scripts/ts/capture-elements.ts 新增 captureAiRuleFromTransactions 截图场景(交易页 AI 生成规则弹窗)。

说明:
- 新截图 elements/ai-rule-transactions.png 已上传 OSS,正文以 OSS 长期地址引用。
2026-10-13 22:39:40 +08:00

160 lines
7.7 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.
# 构建与部署
本章介绍「汇账」的构建方式、分发形态与生产部署流程。
## 前置条件
- **JDK 21**(Temurin 21 LTS,含 `jpackage`)
- **Node.js**(构建前端时需要)
- 依赖仓库:本地 Nexus(Maven)与 verdaccio(npm);构建时经 `-I nexus-init.gradle` 注入 Maven 私服地址
## 开发模式
前后端分离,便于调试:
```bash
# 后端(默认 8080)
cd ledgerly
./gradlew bootRun # Windows: gradlew.bat bootRun
# 前端(默认 5173,接口代理到 8080)
cd ledgerly/frontend
npm run dev
```
- 后端地址:`http://localhost:8080`,接口文档 `http://localhost:8080/doc.html`
- 前端地址:`http://localhost:5173`,`/api` 经 Vite 代理到后端 8080
## 构建可执行 jar(前后端一体化)
`bootJar` 会先执行前端 `npm run build`,再把 `frontend/dist` 复制到 jar 内的 `BOOT-INF/classes/static/`,单文件同时提供页面与接口。
```bash
# 普通 jar(无 GUI,不含 JavaFX)
./gradlew -I nexus-init.gradle bootJar
# 带 GUI 启动器的 jar
./gradlew -I nexus-init.gradle bootGuiJar
# 或使用宿主机脚本(一次产出两者)
./package.sh
```
产物文件名携带目标 JDK 版本:
| 任务 | 产物 | 说明 |
|---|---|---|
| `bootJar` | `build/libs/ledgerly-0.1-jdk21.jar` | 普通服务 jar |
| `bootGuiJar` | `build/libs/ledgerly-0.1-jdk21-gui.jar` | 入口为桌面启动器 |
启动(默认 8080,可另指定端口):
```bash
java -jar build/libs/ledgerly-0.1-jdk21.jar --server.port=18081
```
构建任务链(`build.gradle`):`npmInstall` → `frontendLint` → `frontendBuild` → `bootJar`;`check` 任务另挂 Spotless、Checkstyle、SpotBugs。代码检查不通过会中断构建与打包,详见[开发环境与规范](/dev/development)。
::: tip 提示
jar 内置的前端为生产构建,访问时与后端同源,无需额外的反向代理。
:::
为减小可执行 jar 体积,普通 jar 与 GUI jar 均做了裁剪:AI 调用改用 Spring `RestClient` 直连 OpenAI 兼容接口(不再打包 OpenAI Java SDK);接口文档仅保留 `/v3/api-docs`(JSON),Knife4j 的 `doc.html` UI 依赖为 `developmentOnly`,不进生产 jar;普通 jar 通过 `exclude` 排除桌面启动器包,保持无 JavaFX 依赖。
运行时首次配置或切换数据库时,「测试连接」与「保存并应用」会通过 SSE **实时推送执行进度**(连接测试、建表、结构迁移、数据源切换等)。
## 构建免安装应用(jpackage app-image)
`jpackageAppImage` 聚合任务一次生成两个 app-image,自带精简运行时,双击即用;**仅能为当前操作系统生成**(Windows 上生成 Windows 产物,Linux 上生成 Linux 产物)。
```bash
./gradlew -I nexus-init.gradle jpackageAppImage
# 也可单独执行 jpackageServerAppImage / jpackageGuiAppImage
```
| 任务 | 应用名 | 源 jar | 入口 |
|---|---|---|---|
| `jpackageServerAppImage` | `LedgerlyServer` | `bootJar` | `bin/LedgerlyServer`(Windows 为 `LedgerlyServer.exe`,附带控制台) |
| `jpackageGuiAppImage` | `Ledgerly` | `bootGuiJar` | `bin/Ledgerly`(Windows 为 `Ledgerly.exe`) |
产物位于 `build/jpackage/LedgerlyServer/` 与 `build/jpackage/Ledgerly/`。
图标不随源码分发:打包时 `downloadAppIcon` 从 `src/main/resources/launcher/icon.url` 下载源图,再由 `tools/IconGen.java` 生成平台图标(`ico` / `icns` / `png`);可用 `-PsourceIcon=<png>` 指定本地源图。
## 桌面 GUI 启动器
`cn.rosercode.ledgerly.launcher` 提供系统托盘 + 控制窗口的桌面启动器(进程内启停服务、设置端口、打开浏览器、实时查看日志、单实例运行),仅打入带 GUI 的 jar,普通 jar 不含 JavaFX 依赖。
## 分发形态
| 形态 | 构建方式 | 说明 |
|---|---|---|
| 普通 jar | `bootJar` | 需 JDK 21 运行,跨平台 |
| 带 GUI 启动器 jar | `bootGuiJar` | 内含桌面启动器,跨平台(打包含 JavaFX 全平台原生库) |
| Windows 免安装包 | `jpackageAppImage` | 在 Windows 上构建,zip 分发 |
| Linux 免安装包 | `jpackageAppImage` | 在 Linux 上构建,tar.gz 分发 |
## 发布产物与下载地址
发布产物上传至阿里云 OSS(桶 `adccrac`,对象键前缀 `ledgerly/release/<版本>/`),通过 10 年有效的长期签名 URL 对外提供下载;每个文件同时提供 `.sha256` 校验文件与汇总清单 `SHA256SUMS`。
当前工程版本为 `0.1`;最新下载地址见使用文档的[下载与安装](/guide/download)(该页链接指向已上传的发布产物)。
上传方式:
- Linux 产物(jar 与 Linux app-image 包):工作区根 `oss-release-upload.py`,需在带 `oss2` 的虚拟环境执行(如 `20260807_131528/oss-venv`),上传后输出长期签名 URL。
- Windows 产物(Windows app-image 包):在 Windows 主机上用 Node 脚本(`ali-oss` + STS)直接上传,不回传本地。
## 生产部署(云服务器)
服务部署在云服务器 `101.35.20.223` 的 `/opt/ledgerly` 目录,由 Docker Compose 编排:
| 容器 | 镜像 | 端口 | 说明 |
|---|---|---|---|
| `ledgerly` | `eclipse-temurin:21-jre` | `127.0.0.1:15180 → 8080` | 挂载 `app/ledgerly-0.1.jar`、`config/`、`data/`,JVM `-Xmx512m` |
| `ledgerly-docs` | `nginx:1.27` | `127.0.0.1:15181 → 80` | 挂载 `docs-dist/` 与 `docs/nginx.conf` |
云上 `nginx` 容器反代这两个回环端口并终止 HTTPS,证书由 `certbot` 容器负责续期:
- 服务:[https://finance.rosercode.cn](https://finance.rosercode.cn)
- 文档:[https://docs.finance.rosercode.cn](https://docs.finance.rosercode.cn)
### 安全配置
- **JWT 签名密钥**:默认使用内置密钥签发 Token(有效期 24 小时);生产环境可通过环境变量 `JWT_SECRET` 覆盖,未配置时回退内置默认值,避免个人自托管场景重启后登录态失效。
- **CORS 放行来源**:放开全部来源(`allowedOriginPatterns("*")` 并允许携带凭证)。因 `allowCredentials(true)` 不允许 `allowedOrigins("*")`,需使用 origin pattern 通配;接口认证基于 JWT(`Authorization` 头存于 `localStorage`,跨站脚本无法自动携带),风险可控。
- **反向代理转发头**:`application.yml` 配置 `server.forward-headers-strategy: framework`,信任 `X-Forwarded-Proto`/`Host`;经 nginx/frp 反代时使 HTTPS 同源请求被正确识别为同源,避免被误判为跨域(否则带 `Origin` 的请求会返回 `403 Invalid CORS request`)。
- **运行环境编码**:容器或直接运行 jar 的 JVM 需为 UTF-8 locale(`LANG`/`LC_ALL=C.UTF-8`);否则 `sun.jnu.encoding` 退化为 ASCII,含中文的文件名(如邮箱账单附件名)落盘会抛 `InvalidPathException`。
### 发布流程
服务(构建 → 复制 → 重启):
```bash
# 1) 本地构建 jar(见上)
# 2) 复制到云端部署目录,目标文件名须与 compose 挂载一致
scp -i ~/.ssh/id_ed25519_www.rosercode.cn \
build/libs/ledgerly-0.1-jdk21.jar \
root@101.35.20.223:/opt/ledgerly/app/ledgerly-0.1.jar
# 3) 重启容器
ssh -i ~/.ssh/id_ed25519_www.rosercode.cn root@101.35.20.223 \
'cd /opt/ledgerly && docker compose restart'
```
文档站(构建 → 覆盖 `docs-dist`,目录挂载即时生效):
```bash
cd doc-site && npm run docs:build
scp -i ~/.ssh/id_ed25519_www.rosercode.cn -r .vitepress/dist/* \
root@101.35.20.223:/opt/ledgerly/docs-dist/
```
> 云服务器不能直连 DockerHub,新镜像需本地 `docker save | gzip` → `scp` → 云端 `docker load` 导入。
## 相关文档
- [项目结构](/dev/project-structure)
- [开发环境与规范](/dev/development)
- [下载与安装(使用文档)](/guide/download)
- [快速开始(使用文档)](/guide/getting-started)