chore: 前后端引入代码检查工具并整改存量问题

为统一前后端代码规范与质量,前端引入 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 保持一致
This commit is contained in:
2026-10-12 13:06:10 +08:00
parent 863e221220
commit 6439d336d4
79 changed files with 3950 additions and 1503 deletions
@@ -67,7 +67,10 @@ public class AppConfigStore {
public void save(AppConfig config) {
Path file = getConfigFile();
try {
Files.createDirectories(file.getParent());
Path parent = file.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
objectMapper.writerWithDefaultPrettyPrinter().writeValue(file.toFile(), config);
log.info("应用配置已保存到 {}", file.toAbsolutePath());
} catch (IOException e) {
@@ -81,7 +81,10 @@ public class DatabaseConfigStore {
public void save(DatabaseConfig config) {
Path file = getConfigFile();
try {
Files.createDirectories(file.getParent());
Path parent = file.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
objectMapper.writerWithDefaultPrettyPrinter().writeValue(file.toFile(), config);
log.info("数据库配置已保存到 {}", file.toAbsolutePath());
} catch (IOException e) {
@@ -68,7 +68,10 @@ public class AppConfigController {
@Operation(summary = "查询日志保留天数", description = "读取访问日志与登录日志的自动清理保留天数")
public Result<Integer> getLogRetention() {
Integer days = appConfigStore.load().getLogRetentionDays();
return Result.success(days == null ? AppConfig.DEFAULT_LOG_RETENTION_DAYS : days);
if (days == null) {
days = AppConfig.DEFAULT_LOG_RETENTION_DAYS;
}
return Result.success(days);
}
/**
@@ -40,7 +40,10 @@ public class EmailBillStorage {
String relative = configId + "/" + uid + "/" + index + "_" + sanitize(fileName);
Path target = ROOT.resolve(relative);
try {
Files.createDirectories(target.getParent());
Path parent = target.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
Files.write(target, content);
return relative;
} catch (IOException e) {
@@ -103,7 +106,8 @@ public class EmailBillStorage {
if (fileName == null || fileName.isBlank()) {
return "attachment";
}
String name = Paths.get(fileName).getFileName().toString();
Path namePath = Paths.get(fileName).getFileName();
String name = namePath == null ? fileName : namePath.toString();
return name.replaceAll("[\\\\/:*?\"<>|]", "_");
}
}
@@ -65,7 +65,10 @@ public class LauncherConfigStore {
*/
public void save(LauncherConfig config) {
try {
Files.createDirectories(file.getParent());
Path parent = file.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
objectMapper.writerWithDefaultPrettyPrinter().writeValue(file.toFile(), config);
} catch (IOException e) {
// 配置保存失败不影响主流程,仅忽略
@@ -130,7 +130,10 @@ public final class LauncherIconProvider {
log.warn("下载启动器图标失败:HTTP {}", response.statusCode());
return;
}
Files.createDirectories(cacheFile.getParent());
Path parent = cacheFile.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
Path temp = cacheFile.resolveSibling(CACHE_FILE + ".tmp");
Files.write(temp, response.body());
Files.move(temp, cacheFile, StandardCopyOption.REPLACE_EXISTING);
@@ -38,7 +38,10 @@ public class SingleInstanceGuard implements AutoCloseable {
*/
public static SingleInstanceGuard acquire(Path home) throws IOException {
Path lockFile = home.resolve("config").resolve(LOCK_FILE);
Files.createDirectories(lockFile.getParent());
Path parent = lockFile.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
FileChannel channel = FileChannel.open(lockFile,
StandardOpenOption.CREATE, StandardOpenOption.WRITE);
FileLock lock = channel.tryLock();
@@ -38,10 +38,13 @@ public class NotificationSenderFactory {
* @throws BusinessException 类型不支持时抛出
*/
public NotificationSender get(String type) {
if (type == null) {
throw new BusinessException("不支持的通知渠道类型:null");
}
NotificationChannelType channelType;
try {
channelType = NotificationChannelType.valueOf(type);
} catch (IllegalArgumentException | NullPointerException e) {
} catch (IllegalArgumentException e) {
throw new BusinessException("不支持的通知渠道类型:" + type);
}
NotificationSender sender = senders.get(channelType);
@@ -47,7 +47,10 @@ public class AiImageStorage {
Path relative = Paths.get(ROOT, String.valueOf(userId), month, fileName);
Path target = AppHome.getHome().resolve(relative);
try {
Files.createDirectories(target.getParent());
Path parent = target.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
Files.write(target, content);
} catch (IOException e) {
log.error("保存 AI 记账图片失败: userId={}, path={}", userId, target, e);
@@ -94,7 +94,7 @@ public class AppLogStreamService {
/** 尚未构成完整行的字节缓冲 */
private final ByteArrayOutputStream pending = new ByteArrayOutputStream();
private ScheduledFuture<?> future;
private volatile ScheduledFuture<?> future;
private volatile boolean closed = false;
StreamTask(SseEmitter emitter, Path file, String levelFilter, String keywordFilter,
@@ -195,7 +195,7 @@ public class AppLogStreamService {
}
emitCompleteLines();
}
} catch (Exception e) {
} catch (IOException | RuntimeException e) {
// 发送失败通常意味着连接已断开
stop();
}
@@ -244,8 +244,8 @@ public class BillImportServiceImpl implements BillImportService {
transaction.setRemark(truncate(buildRemark(item.getCounterparty(), item.getProduct()), 255));
transaction.setExternalNo(item.getExternalNo());
transaction.setTransactionTime(item.getTransactionTime());
transaction.setIncludeInStats(item.getIncludeInStats() == null
? sourceStatsDefault : item.getIncludeInStats());
Boolean includeInStats = item.getIncludeInStats();
transaction.setIncludeInStats(includeInStats == null ? Boolean.valueOf(sourceStatsDefault) : includeInStats);
transaction.setRawData(item.getRawData());
pending.add(transaction);
@@ -290,8 +290,9 @@ public class DatabaseServiceImpl implements DatabaseService {
Path rawPath = Paths.get(config.getSqlitePath());
Path path = (rawPath.isAbsolute() ? rawPath : AppHome.getHome().resolve(rawPath)).normalize();
try {
if (path.getParent() != null) {
Files.createDirectories(path.getParent());
Path parent = path.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
} catch (Exception e) {
throw new BusinessException("创建 SQLite 数据库目录失败:" + e.getMessage());
@@ -465,7 +465,9 @@ public class StatisticsServiceImpl implements StatisticsService {
return custom;
}
// 兼容历史 BANK 渠道
if ("BANK".equals(code)) return "银行卡";
if ("BANK".equals(code)) {
return "银行卡";
}
return ChannelType.labelOf(code);
}
@@ -386,7 +386,8 @@ public class TransactionGroupServiceImpl implements TransactionGroupService {
entity.setGroupId(groupId);
entity.setTransactionId(item.getTransactionId());
entity.setRole(item.getRole());
entity.setSortOrder(item.getSortOrder() == null ? 0 : item.getSortOrder());
Integer sortOrder = item.getSortOrder();
entity.setSortOrder(sortOrder == null ? Integer.valueOf(0) : sortOrder);
transactionGroupItemMapper.insert(entity);
}
}
@@ -186,6 +186,7 @@ public class UserServiceImpl implements UserService {
logService.recordLoginLog(loginLog);
} catch (Exception e) {
// 登录日志记录失败不应影响登录流程
log.debug("记录登录日志失败:{}", e.getMessage());
}
}
@@ -108,7 +108,10 @@ public class RsaKeyManager {
keyFile.setPrivateKey(Base64.getEncoder().encodeToString(this.privateKey.getEncoded()));
keyFile.setCreatedAt(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
Files.createDirectories(file.getParent());
Path parent = file.getParent();
if (parent != null) {
Files.createDirectories(parent);
}
objectMapper.writerWithDefaultPrettyPrinter().writeValue(file.toFile(), keyFile);
log.info("已生成接口加密 RSA 密钥并保存到 {},keyId={}", file.toAbsolutePath(), keyId);
} catch (Exception e) {