From 6439d336d49456c24b91dc7d3f72f6146438dc4f Mon Sep 17 00:00:00 2001 From: rosercode Date: Mon, 12 Oct 2026 13:06:10 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E5=89=8D=E5=90=8E=E7=AB=AF=E5=BC=95?= =?UTF-8?q?=E5=85=A5=E4=BB=A3=E7=A0=81=E6=A3=80=E6=9F=A5=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E5=B9=B6=E6=95=B4=E6=94=B9=E5=AD=98=E9=87=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为统一前后端代码规范与质量,前端引入 ESLint + Prettier,后端在 Spotless 之外新增 Checkstyle 与 SpotBugs,检查接入构建与打包流程并失败即阻断,同时一次性整改全部存量告警,检查范围为后端主源码与前端源码。 变更内容: - 前端新增 ESLint(flat config)与 Prettier 配置及 lint / lint:fix / format / format:check 脚本,并新增对应开发依赖 - 后端 build.gradle 应用 checkstyle 与 com.github.spotbugs 插件,新增 gradle/checkstyle/checkstyle.xml、suppressions.xml 与 gradle/spotbugs/exclude.xml - 新增 Gradle frontendLint 任务并作为 frontendBuild 前置,新增 lint 聚合任务(Spotless + Checkstyle + SpotBugs + 前端 ESLint) - 修复后端 Checkstyle 1 处(补花括号)与 SpotBugs 17 处(getParent() 判空、拆装箱冗余、异常捕获与日志、volatile 同步等),并按设计豁免值对象引用暴露等告警 - 修复前端规则问题(未使用变量、无意义赋值、v-for key、v-html 安全提示)并格式化存量文件 - README 补充依赖安装、代码检查与打包说明 说明: - SpotBugs 工具版本固定为 4.8.6,规避其 BCEL 与 Spring Boot BOM 选定 commons-lang3 3.14.0 的 NoSuchMethodError - 检查范围仅主源码,src/test/ 本地测试不纳入 Checkstyle/SpotBugs - 前端格式化统一为无分号、单引号、行宽 120、LF,与 .gitattributes 保持一致 --- README.md | 24 +- build.gradle | 64 +- frontend/.prettierignore | 4 + frontend/.prettierrc.json | 7 + frontend/eslint.config.js | 42 + frontend/package.json | 13 +- frontend/src/App.vue | 11 +- frontend/src/api/ai.ts | 54 +- frontend/src/api/emailBill.ts | 24 +- frontend/src/api/log.ts | 15 +- frontend/src/api/notification.ts | 15 +- frontend/src/api/request.ts | 2 +- frontend/src/api/transaction.ts | 29 +- .../src/components/AccountSecurityPanel.vue | 25 +- frontend/src/components/AiConfigPanel.vue | 36 +- frontend/src/components/AiQueryWindow.vue | 55 +- frontend/src/components/AiRecordModal.vue | 217 ++-- frontend/src/components/AiSuggestModal.vue | 71 +- frontend/src/components/AiUsagePanel.vue | 147 ++- frontend/src/components/BillImportResult.vue | 12 +- frontend/src/components/BillPreviewPanel.vue | 149 ++- .../src/components/BillSourceStatsPanel.vue | 20 +- .../src/components/DatabaseConfigPanel.vue | 35 +- .../src/components/DisplayConfigPanel.vue | 70 +- frontend/src/components/EmojiPicker.vue | 86 +- frontend/src/components/FloatingPanel.vue | 75 +- frontend/src/components/LocaleSwitch.vue | 8 +- .../src/components/NavMenuConfigPanel.vue | 18 +- .../components/NotificationConfigPanel.vue | 54 +- .../components/RegistrationConfigPanel.vue | 10 +- .../src/components/TransactionBatchModal.vue | 71 +- .../src/components/TransactionDetailModal.vue | 76 +- .../TransactionRelationEditorModal.vue | 105 +- .../components/TransactionRelationModal.vue | 4 +- .../components/TransactionRelationPanel.vue | 62 +- frontend/src/composables/useBreakpoints.ts | 15 +- frontend/src/locales/messages.ts | 1110 ++++++++++++++--- frontend/src/router/index.ts | 4 +- frontend/src/utils/apiCrypto.ts | 14 +- frontend/src/utils/navItems.ts | 57 +- frontend/src/utils/password.ts | 2 +- frontend/src/utils/render.ts | 5 +- frontend/src/utils/transactionFilterPrefs.ts | 12 +- frontend/src/views/AiUsage.vue | 2 +- frontend/src/views/BillImportModal.vue | 2 +- frontend/src/views/BillImportPanel.vue | 34 +- frontend/src/views/Calendar.vue | 93 +- frontend/src/views/CategoryManage.vue | 58 +- frontend/src/views/ChannelManage.vue | 48 +- frontend/src/views/Dashboard.vue | 203 ++- frontend/src/views/EmailBillManage.vue | 528 ++++++-- frontend/src/views/Layout.vue | 66 +- frontend/src/views/Login.vue | 18 +- frontend/src/views/Logs.vue | 232 ++-- frontend/src/views/Register.vue | 14 +- frontend/src/views/RuleManage.vue | 277 ++-- frontend/src/views/SqlQuery.vue | 106 +- frontend/src/views/TagManage.vue | 71 +- frontend/src/views/TransactionList.vue | 594 ++++++--- frontend/tsconfig.json | 5 +- gradle/checkstyle/checkstyle.xml | 58 + gradle/checkstyle/suppressions.xml | 7 + gradle/spotbugs/exclude.xml | 34 + .../ledgerly/config/app/AppConfigStore.java | 5 +- .../datasource/DatabaseConfigStore.java | 5 +- .../controller/AppConfigController.java | 5 +- .../ledgerly/email/EmailBillStorage.java | 8 +- .../launcher/LauncherConfigStore.java | 5 +- .../launcher/LauncherIconProvider.java | 5 +- .../launcher/SingleInstanceGuard.java | 5 +- .../NotificationSenderFactory.java | 5 +- .../ledgerly/service/AiImageStorage.java | 5 +- .../ledgerly/service/AppLogStreamService.java | 4 +- .../service/impl/BillImportServiceImpl.java | 4 +- .../service/impl/DatabaseServiceImpl.java | 5 +- .../service/impl/StatisticsServiceImpl.java | 4 +- .../impl/TransactionGroupServiceImpl.java | 3 +- .../service/impl/UserServiceImpl.java | 1 + .../ledgerly/util/crypto/RsaKeyManager.java | 5 +- 79 files changed, 3950 insertions(+), 1503 deletions(-) create mode 100644 frontend/.prettierignore create mode 100644 frontend/.prettierrc.json create mode 100644 frontend/eslint.config.js create mode 100644 gradle/checkstyle/checkstyle.xml create mode 100644 gradle/checkstyle/suppressions.xml create mode 100644 gradle/spotbugs/exclude.xml diff --git a/README.md b/README.md index 0329eab..95f57de 100644 --- a/README.md +++ b/README.md @@ -17,26 +17,34 @@ ## 快速开始 -前置条件:JDK 21(Temurin)、Node.js。 +前置条件:JDK 21(Temurin,含编译工具链与 jpackage)、Node.js(含 npm)。 + +> 首次构建或运行需联网:Gradle 会下载发行版与 Maven 依赖,前端会 `npm install` 安装依赖。使用 wrapper(`./gradlew` / `gradlew.bat`)可自动获取所需 Gradle 版本。 ```bash +# Windows 用 gradlew.bat,Linux/macOS 用 ./gradlew # 后端(默认 8080,接口文档 http://localhost:8080/doc.html) -gradlew.bat bootRun +./gradlew bootRun -# 前端(默认 5173) -cd frontend && npm run dev +# 前端(默认 5173,/api 由 Vite 代理到 8080) +cd frontend && npm install && npm run dev ``` 数据库无需预先准备:启动后在页面按引导选择 SQLite(本地文件,零配置)或 MySQL,保存后立即生效并自动建表;配置持久化于 `config/database.json`。 +## 代码检查 + +- 后端:`./gradlew lint`(Spotless + Checkstyle + SpotBugs)。 +- 前端:`cd frontend && npm run lint`(ESLint;`npm run lint:fix` 自动修复,`npm run format` 用 Prettier 格式化)。 +- `bootJar` / `bootGuiJar` 会先执行后端检查与前端 ESLint,检查不通过则中断打包。 + ## 构建与打包 ```bash -gradle bootJar bootGuiJar # 普通 jar + 带 GUI 启动器的 jar,产物在 build/libs/ -gradle jpackageAppImage # 免安装应用(普通 + GUI),产物在 build/jpackage/ +./gradlew bootJar bootGuiJar # 普通 jar + 带 GUI 启动器的 jar(内嵌前端),产物在 build/libs/ +./gradlew jpackageAppImage # 免安装应用(普通 + GUI),产物在 build/jpackage/ ``` +- 打包前会先安装前端依赖并构建前端产物,再一并打入 jar,无需手动 `npm run build`。 - GUI jar 内嵌前后端与跨平台 JavaFX,单文件即可运行;配置与日志默认落在 jar 所在目录,可用 `--home=<目录>` 或环境变量 `LEDGERLY_HOME` 指定。 - app-image 自带运行时、免安装,但只能为**当前操作系统**生成。 - -> 更详细的目录结构、启动器、jpackage、图标与开发环境说明见 `AGENTS.md`。 diff --git a/build.gradle b/build.gradle index 242fbd7..abbf598 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,13 @@ +import com.github.spotbugs.snom.Confidence +import com.github.spotbugs.snom.Effort + plugins { id 'java' + id 'checkstyle' id 'org.springframework.boot' version '3.3.6' id 'io.spring.dependency-management' version '1.1.7' id 'com.diffplug.spotless' version '8.10.2' + id 'com.github.spotbugs' version '6.5.12' } group = 'cn.rosercode' @@ -120,6 +125,44 @@ tasks.named('compileJava') { dependsOn 'spotlessJavaCheck' } +// ===== 代码静态检查(Checkstyle / SpotBugs)===== +// 二者挂在 check 任务上,由 ./package.sh(gradle check)触发; +// 不进入 ./reload.sh 的快速热重载路径(compileJava),保持开发体验。 +// 配置文件放在 gradle/ 下(项目根的 config/ 被 .gitignore 忽略,仅用于运行时配置)。 +checkstyle { + configDirectory = file('gradle/checkstyle') + maxWarnings = 0 + ignoreFailures = false +} + +spotbugs { + // 固定 4.8.6:其 BCEL 不依赖 commons-lang3 3.18+ 的 getJdkModulePath, + // 避免与 Spring Boot BOM 选定的 commons-lang3 3.14.0 冲突(NoSuchMethodError)。 + toolVersion = '4.8.6' + effort = Effort.MAX + reportLevel = Confidence.valueOf('MEDIUM') + excludeFilter = file('gradle/spotbugs/exclude.xml') + ignoreFailures = false +} + +tasks.named('spotbugsMain') { + reports { + xml { + required = true + outputLocation = file("${layout.buildDirectory.get().asFile}/reports/spotbugs/main.xml") + } + html { + required = true + outputLocation = file("${layout.buildDirectory.get().asFile}/reports/spotbugs/main.html") + } + } +} + +// 测试源码(src/test)仅本地验证、不提交(见 AGENTS.md),不纳入 Checkstyle/SpotBugs +tasks.matching { it.name == 'checkstyleTest' || it.name == 'spotbugsTest' }.configureEach { + enabled = false +} + // ===== 前端构建与打包 ===== def frontendDir = file("${projectDir}/frontend") def frontendDistDir = file("${frontendDir}/dist") @@ -144,11 +187,23 @@ tasks.register('npmInstall', Exec) { outputs.dir("${frontendDir}/node_modules") } +// 前端代码检查(ESLint),失败则中断构建 +tasks.register('frontendLint', Exec) { + group = 'frontend' + description = '运行前端 ESLint 检查(npm run lint)' + dependsOn 'npmInstall' + workingDir frontendDir + commandLine resolveNpm(), 'run', 'lint' + inputs.dir("${frontendDir}/src") + inputs.file("${frontendDir}/eslint.config.js") + inputs.file("${frontendDir}/package.json") +} + // 构建前端产物 tasks.register('frontendBuild', Exec) { group = 'frontend' description = '构建前端产物(npm run build)' - dependsOn 'npmInstall' + dependsOn 'npmInstall', 'frontendLint' workingDir frontendDir commandLine resolveNpm(), 'run', 'build' inputs.dir("${frontendDir}/src") @@ -158,6 +213,13 @@ tasks.register('frontendBuild', Exec) { outputs.dir(frontendDistDir) } +// 聚合代码检查任务:后端 Spotless/Checkstyle/SpotBugs(主源码)+ 前端 ESLint +tasks.register('lint') { + group = 'verification' + description = '运行全部代码检查(后端 Spotless、Checkstyle、SpotBugs 与前端 ESLint)' + dependsOn 'spotlessCheck', 'checkstyleMain', 'spotbugsMain', 'frontendLint' +} + // 将前端产物打入可执行 jar 的静态资源目录,实现前后端一体化部署 tasks.named('bootJar') { dependsOn 'frontendBuild' diff --git a/frontend/.prettierignore b/frontend/.prettierignore new file mode 100644 index 0000000..695fe50 --- /dev/null +++ b/frontend/.prettierignore @@ -0,0 +1,4 @@ +dist +node_modules +package-lock.json +public diff --git a/frontend/.prettierrc.json b/frontend/.prettierrc.json new file mode 100644 index 0000000..c591ee2 --- /dev/null +++ b/frontend/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "semi": false, + "singleQuote": true, + "printWidth": 120, + "trailingComma": "all", + "endOfLine": "lf" +} diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..f57a3fe --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,42 @@ +import js from '@eslint/js' +import pluginVue from 'eslint-plugin-vue' +import globals from 'globals' +import tseslint from 'typescript-eslint' +import prettier from 'eslint-config-prettier' + +export default tseslint.config( + // 忽略构建产物与依赖目录 + { ignores: ['dist/**', 'node_modules/**', 'public/**', 'src/**/*.d.ts'] }, + js.configs.recommended, + ...tseslint.configs.recommended, + ...pluginVue.configs['flat/recommended'], + { + // Vue 单文件组件的