P0-HANDOFF.md 13 KB

P0 / P1 交接说明(后端 + 前端)

P0 目标:后端 Maven 骨架 + BOM + 框架 starters + parking-server 单 Jar 跑通; 前端 parking-cloud-ui 骨架(登录 / 布局 / 动态路由 / 请求层)跑通。 P1 目标:parking-module-system 的六个系统域接真实接口与真实库,认证换成真实实现。 本文档记录验收证据、为跑通而做的偏离、以及仍未处理完的延期项。 前端工程自身的说明见 parking-cloud-ui/README.md。

1. 验收结果(2026-10-08 实测)

验收项 结果
整仓构建 mvn -T 1C clean install -DskipTests ✅ BUILD SUCCESS(13 个模块)
单 Jar 打包 mvn -pl parking-server -am -DskipTests clean package ✅ parking-server/target/parking-server-20261008-0645.jar(100M,唯一可执行 Jar)
启动 java -jar ... --spring.profiles.active=local ✅ Started ParkingServerApplication in 3.462 seconds,Tomcat 48080
GET /actuator/health ✅ {"status":"UP"},db=MySQL 8、redis=8.6.3 均 UP
GET /doc.html ✅ HTTP 200(Knife4j)
GET /v3/api-docs ✅ HTTP 200,openapi 3.0.1,5 个 path 全部挂在 /admin-api 下
未带 token 访问受限接口 ✅ 响应体 {"code":401,"msg":"账号未登录"}
POST /admin-api/system/auth/login ✅ code=0,返回 accessToken / refreshToken / userId / expiresTime
带 Authorization: Bearer <token> 访问 GET /admin-api/demo/status ✅ code=0,{"applicationName":"parking-server","loginUserId":1,"dbTime":...,"redisPing":true}(同时打通 MySQL 与 Redis)
GET /admin-api/system/auth/get-permission-info ✅ code=0,返回 user + roles(["super_admin"]) + menus(首页 + 系统管理 6 个子菜单,component 为 system/*/index)
POST /admin-api/system/auth/logout ✅ code=0
`mvn -pl parking-server dependency:tree \ grep -i dubbo`
`... \ grep -iE 'jakarta\

2. 前端验收(parking-cloud-ui,playwright-cli 实测)

验收项 结果
pnpm install ✅ 无阻塞(pnpm 11 的构建脚本白名单在 pnpm-workspace.yaml 的 allowBuilds)
pnpm type-check ✅ 0 错误(vue-tsc --noEmit;已把 vue-tsc@1.8.27 补进 devDependencies,此前脚本调用不到它)
pnpm build ✅ ✓ built in 4.76s,无告警;dist/ 2.1M,含 9 个 .gz、favicon.svg
1) 未登录访问 / ✅ 重定向到 /login?redirect=/dashboard(/ 经静态外壳 redirect 到 HOME_PATH 后由守卫拦截)
1) 验证码输入框 ✅ 按后端 captcha-info.captchaEnable=false 隐藏(P1 仍未启用,见 AGENTS.md §4:错误码与枚举已预留,后端置 true 时前端无需改)
2) 登录 ✅ POST /admin-api/system/auth/login → HTTP 200 + code=0,落地 /dashboard,token 入 localStorage
2) 侧边栏菜单 ✅ 来自后端 get-permission-info:首页、系统管理(下挂 6 个:用户/角色/菜单/组织/字典/日志)
3) 首页自检 ✅ 渲染出数据库时间与 Redis「正常」;/admin-api 调用记录:captcha-info、login、get-permission-info、demo/status 全部 200
4) 动态路由 ✅ 展开系统管理 → 点用户管理 → /system/user(Vite 懒加载 chunk),标签页出现「首页 / 用户管理」
5) 403 契约 ✅ 拦截响应为 HTTP 200 + body code=403 → toast「没有访问权限」,不登出、仍停 /dashboard
6) 401 契约 ✅ body code=401 → 弹「登录状态已过期,请重新登录」→ 点确定清 token → /login?redirect=%2Fdashboard
7) 未知路由 ✅ /no-such-page-xyz → /404
控制台 ✅ 0 个错误 / 0 个告警(el-tag type="primary" 的校验告警与 / 的 vue-router 告警均已修掉)

说明:上表是 P0 阶段的验收。P0 后端只有 admin(全权限)一个账号,所以当时的 403 分支是用拦截响应验证的 (验证的是前端契约处理,不是真实鉴权)——该缺口已在 P1 用真实受限账号补齐,见 §5 第 5 条。 另外 /dashboard 首次跳转仍会有一条 dev-only 的 No match found for location with path "/dashboard" 告警——这是「菜单驱动动态路由」的固有现象(跳转先于动态路由注册,守卫会立刻注册并重新导航),已在 parking-cloud-ui/README.md §7 记录。

3. 为跑通做的关键修复

后端(不修就起不来)

  1. lombok.config 缺失 → 框架层大量 new LoginUser().setId(x).setUserType(y) 链式写法 报「无法取消引用 void」。补上根目录 lombok.config(accessors.chain=true)解决。
  2. Redisson 空密码 → redis.password: ${REDIS_PASSWORD:} 会解析成空字符串(不是 null), Redisson 因此发送 AUTH "",Redis 回 ERR AUTH ... without any password configured,启动直接失败。 修法:本地无密码时整行注释掉(application-local.yaml 里已写明原因)。
  3. pagehelper 5.3.3 与 jsqlparser 4.9 冲突 → 启动时 NoClassDefFoundError: net/sf/jsqlparser/statement/select/SelectBody (该类比在 jsqlparser 4.7 被删除;MP 3.5.7 强制 4.9)。 修法:BOM 里把 com.github.pagehelper:pagehelper 覆盖为 6.1.1(改用 4.7+ API,不再引用 SelectBody), 并显式锁定 jsqlparser 4.9。starter 仍为 1.4.7(实测其 PageHelperAutoConfiguration 只引用 PageInterceptor + 自己的 properties 类,与 6.1.1 兼容)。 已逐类核验:pagehelper 6.1.1 字节码引用的 32 个 jsqlparser 类全部存在于 4.9。

前端

  1. utils/request.ts 无限递归(登录点了没反应的真凶) → 原写法 const request = service 之后又挂 request.post = (…) => service.post(…): request 与 service 是同一个对象,于是 service.post 被覆盖成自己,一调用就 RangeError: Maximum call stack size exceeded,请求根本发不出去;而 handleLogin 的 catch 又把异常吞了,表现为「点登录毫无反应、无报错、无 toast」。 修法:request 改为独立的方法包装对象(request.ts 里已加注释说明,避免后人再犯)。 教训:给 axios 实例挂同名方法必须先另建对象——已全仓 grep 确认无其他同类写法。
  2. el-tag 的 type="primary" 不被 Element Plus 2.4.2 接受 → 标签页控制台刷告警。 修法:选中态改用 type="" + effect="dark"(2.5+ 才有 primary,本项目锁 2.4.2)。
  3. / 外壳路由改为静态注册 → 原先等菜单加载完再 addRoute 一棵新树,导致 / 在登录跳转瞬间匹配不到(vue-router 告警),且菜单为空的账号没有任何外壳路由。 改为 staticRoutes 里注册 layoutRoute(redirect 到 HOME_PATH), 动态子路由用 router.addRoute('Root', child) 追加。
  4. pnpm type-check 本来就跑不了 → 脚本调 vue-tsc 但 devDependencies 里没有它。 已补 vue-tsc@1.8.27;顺带修掉它报出的 2 个类型错误(Sidebar 的 meta.activeMenu 需 as string、TagsView 的 tag type)。
  5. 缺 favicon → /favicon.ico 404。已加 public/favicon.svg 并改 index.html 引用。

4. 延期项(P1 已闭环 1/4/7,其余仍在)

4.1 已闭环

# 项 结果
1 P0 内存占位实现 已全部删除:P0StubConfiguration、parking-server/.../p0/、controller/admin/p0/ 均已移除,parking.p0.enabled 不复存在。真实实现是 parking-module-system 的 AdminAuthServiceImpl + OAuth2TokenServiceImpl(Spring Security + Redis Token + system_oauth2_* 表)。URL 与占位版一字不差,所以前端不需要改地址
4 Excel 的 DictDataCommonApi 占位 已换真实实现:parking-module-system 的 DictDataService extends DictDataCommonApi
7 前端 6 个骨架页的开关 开关已删除:P1_ENABLED、DICT_API_ENABLED 连同「骨架页展示端点清单」的写法一起移除,6 个系统页全部走真实接口

4.2 仍未处理

# 项 位置 说明
2 测试 starter 缺失 _p0_deferred/DictFrameworkUtilsTest.java.deferred 依赖 BaseMockitoUnitTest / randomPojo(需 h2 + podam,未进 BOM)且用了 Java 9 的 List.of。建 parking-spring-boot-starter-test 后原样还原
3 AreaConvert 被删 parking-spring-boot-starter-excel 的 convert 包 它依赖 biz-ip 组件(framework.ip.core.Area),P0 未引入。P3 起如需地区转换再补
5 多数据源未引入 JdbcUtils.getDbType() 已用反射预留 dynamic-datasource 分支,P6 引入依赖后无需改代码
6 政务库/大屏 — P6 / P7,P0 不涉及

5. P1 实际执行结果

原计划列在下方「原计划对照」中。实际落地如下:

  1. 建了 parking-module-system,刻意未建 parking-module-infra——文件/字典/日志暂由 system 域承担, 等真有文件上传/定时任务需求时再拆
  2. 建表 sql/01_system_schema.sql:13 张 system_* 新表 + sql/02_system_seed.sql 种子数据。 ⚠️ 组织表叫 system_org 而不是 yudao 的 system_dept(语义仍是部门, 用户的 orgId 与登录信息里的 deptId 是同一个值)
  3. 删除 P0 占位包、接入真实认证(见 §4.1)
  4. 前端 system/{user,role,menu,org,dict,log} 6 个页面全部换成真实 CRUD,开关一并删除
  5. 已补真实受限账号端到端用例(403 不再是拦截响应造的假象):建一个只授予 system:user:query 的角色 → 该账号侧边栏只剩「系统管理 / 用户管理」,新增/批量删除/导出/编辑/重置密码/删除按钮 不出现在 DOM 里;后端对越权调用返回 HTTP 200 + body code=403
  6. zteits-sys 的 27 个 impl 尚未迁——P1 只做了「新契约下的系统域」, 存量 impl 的搬迁留给 P2 与四模块一起做

5.1 P1 期间修掉的缺陷(回归时重点留意)

这几个都是「功能看着正常、实际在悄悄丢数据/失效」的类型,不是编译期能挡住的:

缺陷 症状 修法
OrgRespVO 缺 remark 编辑组织保存一次,库里的备注就被静默清成空串(表单拿不到原值只能提交空串,而 updateById 覆盖整列)。实测:种入 原始备注 → 改一次负责人 → 列为 '' OrgRespVO 补 remark;已提炼成 AGENTS.md §1 第 11 条通用陷阱
操作日志页缺 onMounted 进页面永远「共 0 条」,必须手点刷新才出数据(请求根本没发出,而库里已有 30 条) 补 onMounted(loadList)
登录后固定跳 /dashboard /dashboard 是动态路由(由后端「首页」菜单生成),没被授权该菜单的账号登录后直接落到 404 兜底 新增 permissionStore.getDefaultPath():有首页菜单用首页,否则用该账号第一个能解析到组件的页面;守卫在落点处改送
notFoundRoute 没有 name resetRoutes() 拿 'notFound' 这个假名字查 hasRoute 永远查不到 → 每次切换账号都再注册一条 catch-all,不断累积 兜底路由加 name: 'NotFoundCatchAll';判断「地址够不够得着」只能看 router.resolve(path).matched,因为 catch-all 记录上的 redirect 不会被 resolve 跟随

5.2 原计划对照(保留)

  1. 建 parking-module-system + parking-module-infra(从参考工程 urbanops 的对应模块移植,注意 CLAUDE.md §4.1 的三处 API 改写)
  2. 建表:system_users / system_role / system_menu / system_dept / system_dict_* / infra_*(新表用 BaseDO 字段)
  3. 用真实实现替换 §4 的 1、4 两项,然后删除 parking-server 的 p0 包,把 parking.p0.enabled 置 false
  4. 同一批把前端 system/{user,role,menu,org,dict,log} 6 个页面从骨架换成真实 CRUD: P1_ENABLED 与 DICT_API_ENABLED 置 true(页面里已写清各自要接的端点与权限点, 见 parking-cloud-ui/README.md §6 的对照表;system/user 是其余页面的参照样板)
  5. 补一个真实受限账号 + 对应路由/按钮权限,把 P0 用「拦截响应」验证的 403 分支换成真账号端到端用例
  6. 迁 zteits-sys 的 27 个 impl:实体进 domain/,XML 进 resources/mybatis/, BizResult 签名不动,Controller 层适配 CommonResult

6. 本地环境前提

# MySQL 8:无需密码的 root
mysql -uroot -e "CREATE DATABASE IF NOT EXISTS parking_cloud DEFAULT CHARSET utf8mb4;"
# Redis 6379:无密码
redis-cli ping    # PONG

前端:Node ≥ 16(本机 v26.0.0)、pnpm(本机 11.3.0)、registry 走 npmmirror。