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. 为跑通做的关键修复
后端(不修就起不来)
lombok.config缺失 → 框架层大量new LoginUser().setId(x).setUserType(y)链式写法 报「无法取消引用 void」。补上根目录lombok.config(accessors.chain=true)解决。- Redisson 空密码 →
redis.password: ${REDIS_PASSWORD:}会解析成空字符串(不是 null), Redisson 因此发送AUTH "",Redis 回ERR AUTH ... without any password configured,启动直接失败。 修法:本地无密码时整行注释掉(application-local.yaml里已写明原因)。 - 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。
前端
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 确认无其他同类写法。el-tag的type="primary"不被 Element Plus 2.4.2 接受 → 标签页控制台刷告警。 修法:选中态改用type=""+effect="dark"(2.5+ 才有primary,本项目锁 2.4.2)。/外壳路由改为静态注册 → 原先等菜单加载完再addRoute一棵新树,导致/在登录跳转瞬间匹配不到(vue-router 告警),且菜单为空的账号没有任何外壳路由。 改为staticRoutes里注册layoutRoute(redirect到HOME_PATH), 动态子路由用router.addRoute('Root', child)追加。pnpm type-check本来就跑不了 → 脚本调vue-tsc但 devDependencies 里没有它。 已补vue-tsc@1.8.27;顺带修掉它报出的 2 个类型错误(Sidebar的meta.activeMenu需as string、TagsView的 tag type)。- 缺 favicon →
/favicon.ico404。已加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 实际执行结果
原计划列在下方「原计划对照」中。实际落地如下:
- 建了
parking-module-system,刻意未建parking-module-infra——文件/字典/日志暂由 system 域承担, 等真有文件上传/定时任务需求时再拆 - 建表
sql/01_system_schema.sql:13 张system_*新表 +sql/02_system_seed.sql种子数据。 ⚠️ 组织表叫system_org而不是 yudao 的system_dept(语义仍是部门, 用户的orgId与登录信息里的deptId是同一个值) - 删除 P0 占位包、接入真实认证(见 §4.1)
- 前端
system/{user,role,menu,org,dict,log}6 个页面全部换成真实 CRUD,开关一并删除 - 已补真实受限账号端到端用例(403 不再是拦截响应造的假象):建一个只授予
system:user:query的角色 → 该账号侧边栏只剩「系统管理 / 用户管理」,新增/批量删除/导出/编辑/重置密码/删除按钮 不出现在 DOM 里;后端对越权调用返回 HTTP 200 + bodycode=403 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 原计划对照(保留)
- 建
parking-module-system+parking-module-infra(从参考工程 urbanops 的对应模块移植,注意CLAUDE.md§4.1 的三处 API 改写) - 建表:
system_users/system_role/system_menu/system_dept/system_dict_*/infra_*(新表用BaseDO字段) - 用真实实现替换 §4 的 1、4 两项,然后删除
parking-server的 p0 包,把parking.p0.enabled置 false - 同一批把前端
system/{user,role,menu,org,dict,log}6 个页面从骨架换成真实 CRUD:P1_ENABLED与DICT_API_ENABLED置true(页面里已写清各自要接的端点与权限点, 见parking-cloud-ui/README.md§6 的对照表;system/user是其余页面的参照样板) - 补一个真实受限账号 + 对应路由/按钮权限,把 P0 用「拦截响应」验证的 403 分支换成真账号端到端用例
- 迁
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。