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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user