86 个 Lint 错误清零 + 空库一键迁移:卫星平台 2.0 基线修复实战

注:86 个 Lint 错误清零 + 空库一键迁移:卫星平台 2.0 基线修复实战(第一周全记录)

上期发布了《卫星星座管理平台 2.0:14 项缺陷复盘 + 工程级升级路线图》。本期进入"施工期",带来 M0 基线修复第一周的完整落地记录:数据库基线重建、Flyway 版本化迁移、ESLint 质量门禁落地、密钥全量外置、DoD 运行时验收。每个环节都附真实日志与量化数据,供正在做工程化改造的团队直接参考。

一、M0 目标:一周修平"可构建、可运行、可审计"底座

回顾上期盘点的 14 项工程缺陷,M0 的靶心是其中 4 项 P0 缺陷加 README 一致性修正:

#缺陷问题M0 对策
E1init.sql 缺失数据库无法脚本化重建,Docker 初始化挂载失败重建 V1 基线脚本
E3lint 配置缺失npm run lint 直接报错,无代码质量门禁ESLint 9 flat config 落地 + 存量清零
E6JWT 密钥硬编码密钥泄露即全线沦陷全部环境变量化,仓库零明文
E8无迁移工具表结构变更无法版本化演进Flyway 10 集成(M1-1 提前项)

第一周任务卡与交付物:

日期任务交付物
D1M0-1 数据库基线重建V1__baseline.sql(542 行:10 表 + 23 索引 + 幂等种子)
D2M1-1 Flyway 集成 + 验证pom / application.yml / compose 三处修改
D3M0-2 ESLint 9 flat config 落地eslint.config.js + 存量告警清零
D4M0-3/4 密钥外置 + README 修正配置外置 + 文档与实际对齐
D5DoD 验收构建/lint/密钥扫描/运行时演练全绿

目标是让平台完成从"功能演示级"到"工程级"的第一步跨越。

gnjg.png

二、D1-D2:数据库工程化——基线重建 + Flyway 落地

2.1 问题现场

平台 10 张业务表长期"靠既存库活着":仓库里没有建表脚本,docker-compose 挂载的 init.sql 根本不存在,新环境无法从零复现。更棘手的是已经出现"文档与真实库不一致"的征兆——README 承诺的 operator 账号密码,在真实库里登录失败。

2.2 V1__baseline.sql:以现状库为唯一事实源

重建策略不是"照着实体类理想化设计",而是三步对齐:

  1. pg_dump -s 导出真实库结构;
  2. 逐表比对 10 个实体类(字段名、类型、JSONB 列、索引、外键);
  3. 差异清单显式记录、逐项决策。

最终产出 542 行脚本:10 张表 + 23 个索引 + 全部表/列注释 + 幂等种子数据

关键设计一:全脚本幂等。它既是 Flyway 的 V1 迁移,也能手动 psql -f 重放:

sql
复制代码
CREATE TABLE IF NOT EXISTS sys_user ( ... ); INSERT INTO sys_user (username, password, real_name, email, role, department, status) VALUES ('admin', '$2a$10$...', 'System Admin', 'admin@leo-platform.com', 'ADMIN', 'Mission Control', 1), ('operator', '$2a$10$...', 'Satellite Operator', 'operator@leo-platform.com', 'OPERATOR', 'Operations', 1) ON CONFLICT (username) DO NOTHING;

关键设计二:差异清单显式记录。与现状库比对暴露了三个需要决策的差异,全部记录在脚本头部注释,而不是"悄悄处理":

  • 现状库无外键约束(仅主键/唯一键)→ 基线保持"逻辑外键"、不新增硬 FK,避免改变既有删除行为(如删除数据源不级联删除日志),如需强一致约束留待后续版本迁移追加;
  • operator 种子密码 hash 失效 → 经 BCrypt 实测不匹配,重新生成并验证后替换,让 README 承诺的登录成立(详见第六章踩坑笔记);
  • telemetry_data."timestamp" 为关键字列 → 保持引号写法,与现状库一致。

工程视角:基线脚本的价值不在"建表语法",而在把隐式的库结构变成显式的、可评审的、可重放的事实

v1基线.png

2.3 Flyway 集成:三处改动,零侵入

位置改动说明
pom.xml+flyway-coreflyway-database-postgresqlSpring Boot 3.3.5 管理版本 10.10.0
application.ymlenabled=truelocations=classpath:db/migrationbaseline-on-migrate=false约束迁移来源、禁止"假装已迁移"
docker-compose.yml移除对不存在init.sql 的挂载建库完全交给应用启动时的迁移

baseline-on-migrate=false 是一个关键决策:不对非空库"假装已迁移",任何环境都必须从干净的 V1 起步,杜绝"脚本与库两张皮"的历史遗留。

2.4 空库实测:迁移 + 幂等双验证

在 Docker 环境就绪之前,先用本机 PostgreSQL 17 建临时库做了完整演练,真实日志如下:

text
复制代码
# 第一次启动(空库) Database: jdbc:postgresql://localhost:5432/leo_satellite_verify (PostgreSQL 17.5) Schema history table "public"."flyway_schema_history" does not exist yet Creating Schema History table "public"."flyway_schema_history" ... Successfully applied 1 migration to schema "public", now at version v1 (execution time 00:00.169s) Started LeoSatelliteApplication in 6.994 seconds # 第二次启动(已迁移库,幂等复验) Schema "public" is up to date. No migration necessary.

库内核对结论:flyway_schema_history 记录 V1 成功;10 张业务表 + 历史表全部就位;种子账号 admin / operator 可正常登录;验证后临时库与进程已清理干净。

flyway验证.png

三、D3:前端质量门禁——86 个错误清零

3.1 存量盘点

eslint.config.js(ESLint 9 flat config,继承 @vue/eslint-config-typescript + eslint-plugin-vue 推荐集)落地后,全量扫描暴露了平台的历史欠账:86 errors + 1240 warnings

类别典型问题说明
no-explicit-any请求层双泛型、类型定义裸用 any占比最高,集中在类型层
no-unused-vars未使用的导入与变量多次迭代的沉积
其他隐式 any、空 catch 参数等零散分布

修复原则先立规矩:不关闭任何争议规则、不使用一处 eslint-disable——门禁的意义在于真实,以"禁规则"掩盖问题等于自欺。

3.2 两个典型修复范式

范式一:axios 泛型误用 → 类型化门面。

原来的调用长这样:

ts
复制代码
// 旧:第二个泛型被误当作"返回类型",与拦截器解包后的实际负载脱节 const res = await request.get<any, R<PageResult<Satellite>>>(url, { params })

问题根源:axios 的 get<T, R>R 本应是"响应体类型",而项目的响应拦截器已经解包出 data 负载——类型系统与实际运行时错位,调用方只能看到一堆 any

修复方案:封装类型化门面 ApiClient,让泛型直接表达"解包后的负载类型":

ts
复制代码
export interface ApiClient { get<T>(url: string, config?: AxiosRequestConfig): Promise<T> post<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> // ... } const request: ApiClient = { get: <T>(url: string, config?: AxiosRequestConfig) => service.get<T, T>(url, config), // ... }

一次改造,56 处 any 全部消除,调用方还获得了完整的类型推断。

范式二:v-model 联合类型断言。

动态表单场景中,v-model 绑定到 Record<string, string | number | boolean> 的字段时,组件要求的窄类型无法自动收窄。解法不是 as any,而是按字段类型分支断言:

html
复制代码
<el-input v-if="f.type === 'string'" v-model="cfg[f.key] as string" /> <el-input-number v-else-if="f.type === 'number'" v-model="cfg[f.key] as number" /> <el-switch v-else v-model="cfg[f.key] as boolean" />

Vue 3 编译器原生支持 v-model 中的 TS 断言语法(内部先剥离 TS 节点再生成赋值),类型检查零损耗。

3.3 结果

text
复制代码
npm run lint → 0 error 0 warning vue-tsc --noEmit → 0 error

lint全绿.png

四、D4:密钥外置——仓库零明文

4.1 三层配置体系

敏感配置(JWT 密钥、数据库/Redis 密码)从配置文件中彻底移除,改为三层体系,一份代码同时满足"本地开箱即用"与"生产强制注入":

mermaid
复制代码
flowchart TB CODE["application.yml(占位符声明)<br/>${DB_PASSWORD} · ${JWT_SECRET} · ${REDIS_PASSWORD:}"] CODE --> DEV["本地开发<br/>application-local.yml 兜底<br/>零配置启动"] CODE --> PROD["容器 / 生产<br/>docker-compose + .env 注入<br/>缺参即启动失败(Fail-Fast)"]
文件职责
声明层application.yml敏感项全部占位符化,仓库不出现任何真实值
开发兜底层application-local.yml本地开箱即用的 dev-only 默认值,明确标注"生产必须替换"
容器注入层docker-compose.yml + .env.example${VAR:-开发默认} 注入 + "生产必改"注释模板

两个值得强调的设计细节:

  1. spring.datasource.password: ${DB_PASSWORD} 不带默认值——强制注入,漏配即启动失败。Fail-Fast 远优于静默跑在一个弱密码上;而 Redis 密码可空,用 ${REDIS_PASSWORD:} 表达"允许为空"的语义。
  2. 环境变量优先级天然高于 profile 文件(Spring 的 relaxed binding)——本地开发不设任何环境变量也能跑,生产通过 compose 注入强随机值后自动覆盖兜底,一套代码两种形态。

4.2 扫描验证

修改完成后做多模式全仓扫描:原 JWT 密钥串 0 残留、yml 中无明文密码赋值、无 PEM 私钥、Java 代码无硬编码。唯一保留的 admin/admin123 是 README 公开的演示账号(登录页有提示),不属于敏感泄露。

密钥扫描.png

五、D5:DoD 验收——数字说话

验收项命令 / 方法结果
后端构建mvn clean packageBUILD SUCCESS(14.5s,55.8MB 可执行 jar)
前端 lintnpm run lint0 error 0 warning
前端类型检查vue-tsc --noEmit0 error
密钥扫描多模式正则全仓扫描0 命中
空库迁移临时库 + 应用启动自动建 10 表 + 种子;二次启动幂等
登录链路POST /api/auth/login200 + JWT(token / refreshToken 完整返回)
产物核对jar 内资源检查db/migration/V1__baseline.sqlapplication-local.yml 均在包内

登录与运行态验证——种子账号 admin 登录成功,平台核心功能在迁移后的空库上正常工作:

登录.png

M0 出口清单除"docker compose 一键拉起"(本机暂无 Docker,留待环境就绪后补验)外,全部达成。偏差与验证记录已同步写入实施计划的完成记录,保证过程可复查。

六、踩坑笔记(可直接抄作业)

  1. 种子密码 hash 必须实测。operator 账号的 BCrypt hash 疑似来自某文档示例,校验根本不匹配——"看起来对"不等于"能登录"。教训:种子数据里的每个 hash 都要跑一遍校验脚本再入库。
  2. lint 绿 ≠ 类型绿。本次 lint 清零后,vue-tsc 仍报出类型化门面的参数隐式 any——对象字面量中的泛型箭头函数不会从接口定义获得参数推断,必须显式标注。ESLint 与类型检查是两把尺子,缺一不可。
  3. ESLint 9 flat config 移除了 --ext。旧脚本 eslint src --ext .ts,.vue 直接报错,flat 时代改用 eslint .,由配置中的 files 匹配接管。
  4. 幂等是基线脚本的底线。所有建表走 IF NOT EXISTS、种子走 ON CONFLICT DO NOTHING——脚本既能被 Flyway 编排,也能被人工重放,两种用法都安全。
  5. 占位符有无默认值是两种安全语义${DB_PASSWORD} 表示"必须注入",${REDIS_PASSWORD:} 表示"允许为空",生产敏感项一律前者 + 本地兜底放进 profile 文件。

七、下周预告:M1 开局——测试体系从 0 到 1

M0 把地基修平,M1 开始搭工程化骨架(D6 起):

  • 后端测试体系:JUnit5 + Mockito 单测、Testcontainers 集成测试(真实 PostgreSQL 16 镜像)、JaCoCo 覆盖率门禁;
  • 前端测试:Vitest 组件测试 + Playwright E2E 冒烟(登录 → 仪表盘 → 卫星列表 → 遥测页);
  • 安全加固:WebSocket 握手鉴权(STOMP ChannelInterceptor 校验 JWT)、按钮级 RBAC、审计日志 AOP;
  • CI/CD:Gitee Go 流水线串联 lint → 单测 + 覆盖率 → 镜像构建,任一红即阻断发布。

八、结语

M0 这一周最值得"截图"的,不是任何一个页面,而是三行绿色输出:BUILD SUCCESS0 error 0 warningNo migration necessary

功能看演示,工程看门禁。从"功能齐全"到"工程可靠",跨越的不是代码量,而是纪律:数据库变更可版本化回放、配置零明文、质量门禁真实生效、每一项验收都有日志可查。第一周只是一小步,但方向已经立住。

📌 觉得这篇实战记录有价值?点赞 + 在看 + 转发,让更多航天与软件工程师看到! 💬 留言聊聊:你们团队的数据库迁移是怎么管的?有没有被"文档与真实库不一致"坑过?

*本文关键词:卫星平台 2.0 | 基线修复 | Flyway | 数据库迁移 | ESLint 9 | 密钥外置 | 工程化实战 | Spring Boot | Vue 3 | PostgreSQL *

0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
下载 APP