# 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 ` 访问 `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` | ✅ 无输出(Dubbo/Zookeeper 已彻底移除) | | `... \| grep -iE 'jakarta\|jsqlparser'` | ✅ jsqlparser **4.9**;无 jakarta 运行时包(`jakarta.validation-api:2.0.2` / `jakarta.annotation-api:1.3.5` / `jakarta.xml.bind-api:2.3.3` 属于 Boot 2.7 线,提供的正是 `javax.*` 包) | ## 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**。 ### 前端 4. **`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 确认无其他同类写法。 5. **`el-tag` 的 `type="primary"` 不被 Element Plus 2.4.2 接受** → 标签页控制台刷告警。 修法:选中态改用 `type=""` + `effect="dark"`(2.5+ 才有 `primary`,本项目锁 2.4.2)。 6. **`/` 外壳路由改为静态注册** → 原先等菜单加载完再 `addRoute` 一棵新树,导致 `/` 在登录跳转瞬间匹配不到(vue-router 告警),且**菜单为空的账号没有任何外壳路由**。 改为 `staticRoutes` 里注册 `layoutRoute`(`redirect` 到 `HOME_PATH`), 动态子路由用 `router.addRoute('Root', child)` 追加。 7. **`pnpm type-check` 本来就跑不了** → 脚本调 `vue-tsc` 但 devDependencies 里没有它。 已补 `vue-tsc@1.8.27`;顺带修掉它报出的 2 个类型错误(`Sidebar` 的 `meta.activeMenu` 需 `as string`、`TagsView` 的 tag type)。 8. **缺 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. 本地环境前提 ```bash # 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。