86 个 Lint 错误清零 + 空库一键迁移:卫星平台 2.0 基线修复实战
注:86 个 Lint 错误清零 + 空库一键迁移:卫星平台 2.0 基线修复实战(第一周全记录)
上期发布了《卫星星座管理平台 2.0:14 项缺陷复盘 + 工程级升级路线图》。本期进入"施工期",带来 M0 基线修复第一周的完整落地记录:数据库基线重建、Flyway 版本化迁移、ESLint 质量门禁落地、密钥全量外置、DoD 运行时验收。每个环节都附真实日志与量化数据,供正在做工程化改造的团队直接参考。
一、M0 目标:一周修平"可构建、可运行、可审计"底座
回顾上期盘点的 14 项工程缺陷,M0 的靶心是其中 4 项 P0 缺陷加 README 一致性修正:
| # | 缺陷 | 问题 | M0 对策 |
|---|---|---|---|
| E1 | init.sql 缺失 | 数据库无法脚本化重建,Docker 初始化挂载失败 | 重建 V1 基线脚本 |
| E3 | lint 配置缺失 | npm run lint 直接报错,无代码质量门禁 | ESLint 9 flat config 落地 + 存量清零 |
| E6 | JWT 密钥硬编码 | 密钥泄露即全线沦陷 | 全部环境变量化,仓库零明文 |
| E8 | 无迁移工具 | 表结构变更无法版本化演进 | Flyway 10 集成(M1-1 提前项) |
第一周任务卡与交付物:
| 日期 | 任务 | 交付物 |
|---|---|---|
| D1 | M0-1 数据库基线重建 | V1__baseline.sql(542 行:10 表 + 23 索引 + 幂等种子) |
| D2 | M1-1 Flyway 集成 + 验证 | pom / application.yml / compose 三处修改 |
| D3 | M0-2 ESLint 9 flat config 落地 | eslint.config.js + 存量告警清零 |
| D4 | M0-3/4 密钥外置 + README 修正 | 配置外置 + 文档与实际对齐 |
| D5 | DoD 验收 | 构建/lint/密钥扫描/运行时演练全绿 |
目标是让平台完成从"功能演示级"到"工程级"的第一步跨越。

二、D1-D2:数据库工程化——基线重建 + Flyway 落地
2.1 问题现场
平台 10 张业务表长期"靠既存库活着":仓库里没有建表脚本,docker-compose 挂载的 init.sql 根本不存在,新环境无法从零复现。更棘手的是已经出现"文档与真实库不一致"的征兆——README 承诺的 operator 账号密码,在真实库里登录失败。
2.2 V1__baseline.sql:以现状库为唯一事实源
重建策略不是"照着实体类理想化设计",而是三步对齐:
pg_dump -s导出真实库结构;- 逐表比对 10 个实体类(字段名、类型、JSONB 列、索引、外键);
- 差异清单显式记录、逐项决策。
最终产出 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"为关键字列 → 保持引号写法,与现状库一致。
工程视角:基线脚本的价值不在"建表语法",而在把隐式的库结构变成显式的、可评审的、可重放的事实。

2.3 Flyway 集成:三处改动,零侵入
| 位置 | 改动 | 说明 |
|---|---|---|
pom.xml | +flyway-core、flyway-database-postgresql | Spring Boot 3.3.5 管理版本 10.10.0 |
application.yml | enabled=true、locations=classpath:db/migration、baseline-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 可正常登录;验证后临时库与进程已清理干净。

三、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

四、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:-开发默认} 注入 + "生产必改"注释模板 |
两个值得强调的设计细节:
spring.datasource.password: ${DB_PASSWORD}不带默认值——强制注入,漏配即启动失败。Fail-Fast 远优于静默跑在一个弱密码上;而 Redis 密码可空,用${REDIS_PASSWORD:}表达"允许为空"的语义。- 环境变量优先级天然高于 profile 文件(Spring 的 relaxed binding)——本地开发不设任何环境变量也能跑,生产通过 compose 注入强随机值后自动覆盖兜底,一套代码两种形态。
4.2 扫描验证
修改完成后做多模式全仓扫描:原 JWT 密钥串 0 残留、yml 中无明文密码赋值、无 PEM 私钥、Java 代码无硬编码。唯一保留的 admin/admin123 是 README 公开的演示账号(登录页有提示),不属于敏感泄露。

五、D5:DoD 验收——数字说话
| 验收项 | 命令 / 方法 | 结果 |
|---|---|---|
| 后端构建 | mvn clean package | BUILD SUCCESS(14.5s,55.8MB 可执行 jar) |
| 前端 lint | npm run lint | 0 error 0 warning |
| 前端类型检查 | vue-tsc --noEmit | 0 error |
| 密钥扫描 | 多模式正则全仓扫描 | 0 命中 |
| 空库迁移 | 临时库 + 应用启动 | 自动建 10 表 + 种子;二次启动幂等 |
| 登录链路 | POST /api/auth/login | 200 + JWT(token / refreshToken 完整返回) |
| 产物核对 | jar 内资源检查 | db/migration/V1__baseline.sql、application-local.yml 均在包内 |
登录与运行态验证——种子账号 admin 登录成功,平台核心功能在迁移后的空库上正常工作:

M0 出口清单除"docker compose 一键拉起"(本机暂无 Docker,留待环境就绪后补验)外,全部达成。偏差与验证记录已同步写入实施计划的完成记录,保证过程可复查。
六、踩坑笔记(可直接抄作业)
- 种子密码 hash 必须实测。operator 账号的 BCrypt hash 疑似来自某文档示例,校验根本不匹配——"看起来对"不等于"能登录"。教训:种子数据里的每个 hash 都要跑一遍校验脚本再入库。
- lint 绿 ≠ 类型绿。本次 lint 清零后,
vue-tsc仍报出类型化门面的参数隐式 any——对象字面量中的泛型箭头函数不会从接口定义获得参数推断,必须显式标注。ESLint 与类型检查是两把尺子,缺一不可。 - ESLint 9 flat config 移除了
--ext。旧脚本eslint src --ext .ts,.vue直接报错,flat 时代改用eslint .,由配置中的files匹配接管。 - 幂等是基线脚本的底线。所有建表走
IF NOT EXISTS、种子走ON CONFLICT DO NOTHING——脚本既能被 Flyway 编排,也能被人工重放,两种用法都安全。 - 占位符有无默认值是两种安全语义。
${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 SUCCESS、0 error 0 warning、No migration necessary。
功能看演示,工程看门禁。从"功能齐全"到"工程可靠",跨越的不是代码量,而是纪律:数据库变更可版本化回放、配置零明文、质量门禁真实生效、每一项验收都有日志可查。第一周只是一小步,但方向已经立住。
📌 觉得这篇实战记录有价值?点赞 + 在看 + 转发,让更多航天与软件工程师看到! 💬 留言聊聊:你们团队的数据库迁移是怎么管的?有没有被"文档与真实库不一致"坑过?
*本文关键词:卫星平台 2.0 | 基线修复 | Flyway | 数据库迁移 | ESLint 9 | 密钥外置 | 工程化实战 | Spring Boot | Vue 3 | PostgreSQL *
