# parking-cloud-yudao 迁移手册 > 新工程根目录。老工程 `/Users/wangbiao/IdeaProjects/parking/parking-cloud` 与老服务 48082 **保持原样**,本工程的任何操作都不改它。 > 所有新文档都放在本目录下;设计稿见 `docs/superpowers/specs/2026-11-02-yudao-replatform-design.md`。 ## 0. 两个新仓库 | 仓库 | 路径 | 来源 | 版本 | |---|---|---|---| | 后端 | `parking-cloud-yudao` | `chifeng/ruoyi-vue-pro` 的 `master-jdk17` | commit `0b1889a8e8`,Boot 3.5.15 / JDK17 / MP 3.5.17 | | 前端 | `parking-cloud-yudao-ui` | `yudao-ui-admin-vue3` 最新 | `2026.10-snapshot`,Vue 3.5.34 / EP 2.13.7 | ## 1. 当前进度 - [x] 新建后端仓库(`master-jdk17`) - [x] 新建前端仓库(官方最新) - [x] 设计稿落盘 - [x] 数据库拷贝 + 上游表准备(`parking_cloud_yudao`,重名旧表改 `pcbak_system_*`) - [x] 上游模块裁剪(只保留 system / infra / iot) - [x] parking 业务模块搬迁(10 个模块编译通过) - [x] 系统兼容桥(`LoginUserBridge` 打通 yudao 登录态 → 存量 `UserInfo`) - [x] 前端业务页面搬迁(park/order/finance/report/ops/... 已进 `src/views`,构建通过) - [x] **后端单 Jar 在 48083 启动成功,登录 + 存量接口冒烟通过**(2026-11-02) - [x] **全页面运行巡检:58 个停车页 + 12 个系统页,`code != 0` 调用 0 个**(2026-11-02) - [x] **与老环境隔离:新服务 Redis 用 db1(老服务 db0)+ `scripts/warm-cache.sh` 缓存预热**(2026-11-02) - [x] **菜单 path 归一化:子页改相对路径,侧边栏不再出现 `/park/park/park-lot` 404**(2026-11-02,见 §7.4) - [x] **业务页全局样式回流:令牌/版式类映射到芋道主题(`src/styles/parking-pages.scss`)**(2026-11-02,见 §7.3 #5) - [ ] 菜单/权限收口(462 个 `@PreAuthorize` 权限串尚无菜单记录;admin 不受影响) - [ ] 调度去重(48082 与 48083 同时跑存量 `@Scheduled`) - [ ] 组织 id 空间打通(`system_org` ↔ 老 `sys_org`) - [ ] 视频接入(WVP-PRO + ZLMediaKit)与 IoT 时序存储(TDengine) ## 2. 关键决策(已定) | 编号 | 决策 | |---|---| | D1 | 独立新仓库,不原地改老工程 | | D2 | 业务代码保留 `com.zteits.*` 包名与 `parking-module-*` artifactId | | D3 | 框架层用 `yudao-framework`;只保留 `parking-common` + `parking-legacy-common` 作兼容层 | | D4 | 上游 `yudao-module-system` 接管登录/权限/菜单/字典/日志;parking 系统模块按「丢弃 / 保留 / 桥接」三类拆分 | | D4.1 | **只挂上游 4 个模块**:`system / infra / iot`(`im` 暂不挂)。上游 `pay/member/report/mall/bpm/crm/erp/ai/mp/mes/wms/hrm/fms/pms/oa` 全部不挂,避免与 parking 业务路由/表冲突 | | D5 | 新工程连 **`parking_cloud_yudao`(`parking_cloud` 的拷贝)**;原库不动 | | D6 | 业务菜单 id 整体 +100000 导入,避免与上游菜单 id 冲突 | | D7 | 前端只搬业务 `views` / `api` / 自研组件,框架壳用上游 | | D8 | 业务 Controller 继续返回 `com.zteits.parking.framework.common.pojo.CommonResult`;JSON 与上游一致 | | D9 | 保留 pagehelper 6.1.1 + jsqlparser 4.9,约 700 处 `PageHelper.startPage` 不动 | | D10 | 启动类扫描顺序:`com.clouds.common.redis/threadpool/web/oss` → `cn.iocoder.yudao.server/.module` → `com.zteits.*` | ## 3. 数据库方案(安全优先) **新库 = `parking_cloud_yudao`,是 `parking_cloud` 的完整拷贝。** 1. `mysqldump parking_cloud` → 还原到 `parking_cloud_yudao`。 2. 在拷贝里把 12 张与上游重名的旧表改名为 `pcbak_system_*`(`system_org` 是停车专有表,保留原名)。 3. 从上游模板库 `yudao_template` 导入 `system_* / infra_* / iot_* / QRTZ_*`。 4. 旧管理员密码写入新 `system_users` id=1,登录密码不变。 5. 旧业务菜单(id>=153 的 345 条 + parking 专有系统页)以 `id+100000` 导入,`parent_id` 按规则重映射。 6. 旧字典(5 类 / 17 条)以新 id 导入新 `system_dict_*`。 7. 原库 `parking_cloud` 完全不动,老服务 48082 继续可用。 脚本:`scripts/migrate-db.sh`(拷贝 + 建表)、`scripts/migrate-menus.sql`(菜单/字典/管理员)。 ## 4. 模块范围 ### 4.1 保留的上游模块 ``` yudao-dependencies yudao-framework # 全部 starter yudao-module-system yudao-module-infra yudao-module-iot # core / gateway / biz yudao-server ``` ### 4.2 搬迁的 parking 模块 ``` parking-framework/parking-common # 67 个,兼容 POJO parking-framework/parking-legacy-common # 567 个,存量工具 parking-module-api # 2581 个,存量契约 parking-module-park # 1150 parking-module-order # 1018 parking-module-pay # 977 parking-module-bill # 413 parking-module-charge # 67 parking-module-business # 100 parking-module-datacollect # 1(空实现) parking-module-system-legacy # 从 parking-module-system 剥出的停车专有系统代码 ``` ### 4.3 系统模块三分法 - **丢弃(上游已有)**:`auth / user / role / menu / permission / dict / notice / operate-log / oauth2`。 - **保留(停车专有)**:`org / refresh-cache / tb-work-order / tb-work-order-process / aliyuncs-ecs / message-center / app-news`。 - **桥接(存量引用)**:`UserDO / MenuDO / RoleDO / OrgDO / OperateLogDO / PermissionService` 等。 已核对冲突:parking 有 8 条 `/system/*` 路由与上游重名,全部属于「丢弃」类;保留类路径与上游不冲突。 ## 5. 兼容层清单(业务模块只引用 36 个唯一类) | 类别 | 处理 | |---|---| | `common.*`(CommonResult / PageResult / PageParam / ServiceException / 枚举 / 校验 / 工具) | 保留 `parking-common` 原类 | | `BaseDO / BaseMapperX / LambdaQueryWrapperX / QueryWrapperX` | 从 `parking-spring-boot-starter-mybatis` 摘出 4 个类,去掉自动配置,放进 `parking-common` 或 `parking-framework-compat` | | `SecurityFrameworkUtils` / `LoginUser` | 薄适配到上游 SecurityContext | | `OperateLog` / `OperateTypeEnum` | 先保留注解(不生效也不影响编译),后续映射到上游操作日志 | | `ExcelUtils` | 复制工具类 | | `common.biz.system.oauth2/permission DTO` | 薄适配到上游 system API | **注意**:不要保留 parking 的 `ParkingMybatisAutoConfiguration / ParkingSecurityAutoConfiguration / ParkingWebAutoConfiguration`,它们会和上游自动配置冲突。 ## 6. 启动类改造要点 新启动类 `cn.iocoder.yudao.server.YudaoServerApplication`: ```java @SpringBootApplication(scanBasePackages = { "com.clouds.common.redis", "com.clouds.common.threadpool", "com.clouds.common.web", "com.clouds.common.oss", "${yudao.info.base-package}.server", "${yudao.info.base-package}.module", "com.zteits.park", "com.zteits.order", "com.zteits.pay", "com.zteits.bill", "com.zteits.charge", "com.zteits.business", "com.zteits.sys", "com.zteits.ipay", "com.zteits.configuration", "com.zteits.wxcp", "com.zteits.glockcollect" }) @MapperScan(basePackages = { "com.zteits.park.mapper", "com.zteits.order.mapper", "com.zteits.pay.mapper", "com.zteits.bill.mapper", "com.zteits.charge.mapper", "com.zteits.business.mapper", "com.zteits.sys.mapper" }, nameGenerator = LegacyMapperBeanNameGenerator.class) ``` - `com.clouds.common.*` 必须排最前,否则存量 `CacheUtil` 静态字段被钉成 null,启动 NPE。 - `LegacyMapperBeanNameGenerator` 需要从老工程搬过来。 - 上游 `yudao.info.base-package=cn.iocoder.yudao` 保持不变。 - 租户关闭:`yudao.tenant.enable: false`。 - 审计字段:合并一个 `MetaObjectHandler`,同时填 `createTime/updateTime/createBy/updateBy` 与 `creator/updater`。 ## 7. 前端搬迁 从老 `parking-cloud-ui` 搬: ``` src/api/{park,order,pay,report,ops,system,index,login,ext} src/views/{park,order,finance,report,ops,member,assess,ext} src/components/ 下自研组件 src/locales/ 中停车文案 ``` 保留上游:`layout / components 基础组件 / utils / store / router / permission / config / styles`。 - 版本漂移:Vue 3.3→3.5、Element Plus 2.4→2.13、Vite 4→最新、UnoCSS 0.57→最新。 - 先搬高频 10 页跑通,再批量修剩余。 ### 7.1 兼容层 `src/utils/request.ts` 老 API 文件用 axios 原生风格 `request.post(url, data)`,URL 里已经带了 `/admin-api` (`ADMIN_API_PREFIX`);而上游 axios 实例的 baseURL 是 `VITE_BASE_URL + VITE_API_URL`,同样以 `/admin-api` 结尾。兼容层必须 **剥离 URL 里的前缀**,否则实际请求会变成 `/admin-api/admin-api/...` → 404。 (2026-11-02 实测踩到,已在 `stripApiPrefix` 里修掉。) ### 7.2 本地联调配置(`.env.local`) | 变量 | 值 | 说明 | |---|---|---| | `VITE_BASE_URL` | `http://localhost:48083` | 新后端 | | `VITE_APP_TENANT_ENABLE` | `false` | 后端 `yudao.tenant.enable=false` | | `VITE_APP_CAPTCHA_ENABLE` | `false` | 后端 `yudao.captcha.enable=false` | ⚠️ 端口:`VITE_PORT=80` 在 macOS 非 root 下绑不了;联调时用 `pnpm dev --port 5175`(5173/5174 可能被其他工程占用)。 ### 7.3 浏览器实测(2026-11-02) 用 playwright-cli 在 `http://localhost:5175` 实测: - 登录页 → `admin/admin123` → 进入 `/index`,无 401; - `/park/park-lot`:车场列表返回真实数据(中国传感网国际创新园停车场等 2700 泊位); - `/order/parking-record`、`/park/caryard-overview`、`/park/berth` 均有数据、无报错。 **全量页面巡检(同日)**:用 `page.goto` 逐页跑完 `system_menu` 里 55 个停车页面(订单 12 / 财务 11 / 运维 9 / 会员 4 / 报表 8 / 停车设施 6 / 考核·外部 2)+ 12 个系统页面,逐页收集 `console.error` 与 HTTP ≥400 的后端请求。结果:**0 个页面报错、0 个失败请求**;有数据的页面 (停车记录、车场、泊位、工单、巡检、会员卡、报表等)均渲染出真实行, 无数据的页面落「暂无数据」空态而非报错。 **第二轮巡检(同日,修正版)**:第一轮只看 `console.error` 与 HTTP ≥400,漏掉了 「HTTP 200 但响应体 `code=500`」的静默失败。第二轮改为捕获所有 `/admin-api` JSON 响应的 `code != 0`,58 个停车页面 + 12 个系统页面全部复跑, 修完后结果 **`[]`(0 个失败调用)**。详见 §8.1 的 #12–#15。 **侧边栏点击巡检(同日)**:不止按 URL 巡检,还从侧边栏逐项点进去。范围 = 迁移的 60 个停车页面(8 个停车目录 55 个 + 挂在芋道「系统管理」下的组织管理/收费员账号/ 消息中心/规则设置/运行概况 5 个),记录落地 `path`、是否出现「您访问的页面不存在」、 以及响应体 `code != 0`。结果:**60/60 正确落地,0 个 404、0 个失败调用**。 (脚本坑:路由切换后 el-menu 会自动收起子菜单,点击前必须重新展开;另 2 个报表项 因滚动/动画导致脚本点击超时,人工复核可直接进入,不是页面问题。) 巡检同时修掉 4 个「老页面写法 vs 上游组件契约」的前端兼容问题(改的都是共享层, 不是逐页改): | # | 现象 | 根因 | 修法 | |---|---|---|---| | 1 | 请求变成 `/admin-api/admin-api/...` 404 | 老 API 文件 URL 自带前缀,上游 axios baseURL 也带 | `request.ts` 加 `stripApiPrefix` | | 2 | `hasPermi` 指令抛 `permission.hasPermission` | 老页面写字符串(198 处),上游指令只收数组 | `hasPermi.ts` 同时接受 string / array | | 3 | `Missing required prop: "type"` | 老页面用 `` | 迁过来的页面统一 `` | | 4 | `value.trim is not a function` | 存量接口时间字段是**毫秒数字**,`formatDateTime` 只处理字符串 | `format.ts` 用 dayjs 格式化数字,0/负数/null 显示 `-` | | 5 | 业务页面内容区**完全没有样式**(标题与数值竖排、表格无边框、无内边距) | 老工程 `main.ts` 全局引入了 `styles/tokens.css` + `styles/index.scss`(`--text-1`/`--fs-12`/`.stat-strip`/`.table-card`/`.search-field` 等 100+ 页共用的令牌与版式);新工程只搬了页面视图,两个全局文件没跟着搬 | 新增 `src/styles/parking-pages.scss` 并在 `src/styles/index.scss` 里 `@use`:业务令牌**映射到 Element Plus 变量**(`--text-1: var(--el-text-color-primary)`、`--brand-600: var(--el-color-primary)` …),页面随芋道主题(含暗色)走;`.stat-strip`/`.table-card`/`.search-field` 版式类原样保留,表格紧凑规则限定在 `.table-card` 内,不污染上游系统页。**没有**照搬老 tokens.css 对 `--el-*` 的整套覆盖(那会把整站改成政务蓝) | | 6 | 查询区还是老政务版式:label 在控件上方、控件通栏、按钮独占底部一行、空白过大 | 老 `SearchBar` 按「政务表单」设计;芋道标准是 `ContentWrap` + inline form(label 在左、控件 240px、搜索/重置跟在条件后同一行) | 重写 `components/SearchBar/index.vue` 外壳:flex 自动换行替代列数栅格、`搜索/重置` 带图标与条件同行;`styles/parking-pages.scss` 里 `.search-field` 改 inline-flex(label 90px 右对齐)、控件统一 `240px`。100+ 个页面用法不变,无需逐页改 | | 7 | 50 处 `userStore.userInfo?.nickname/username/orgId` 取到空值(打印/导出「操作人」为 `-`),`pnpm ts:check` 报 50 个 TS2339 | 老工程 store 暴露 `userInfo`;芋道 store 只有 `user`/`getUser()`,且 `get-permission-info` 只返回 nickname/deptId | `store/modules/user.ts` 新增兼容 getter `userInfo`(nickname 兜底 username、透出 id/deptId);**`orgId` 刻意不填**——老 orgId 是 `sys_org.id`,deptId 是另一套 id,把 deptId 当 orgId 传给存量接口会静默返回空数据,正确做法是不传、由后端 `LoginUserBridge` 换算 | ### 7.4 菜单 path 归一化(侧边栏 404 的根因) 现象:点侧边栏「停车设施 → 车场列表」,地址变成 **`/park/park/park-lot`**, 页面显示「抱歉,您访问的页面不存在」。 根因:老 `system_menu` 的子页 path 写的是**绝对路径**(`/park/park-lot`), 而 yudao 约定是「目录绝对路径 + 子页相对路径」(上游自己就是 `/system` + `user`)。 `src/utils/routerHelper.ts` 的 `pathResolve(parentPath, path)` 会无条件拼接: ```ts const childPath = path.startsWith('/') ? path : `/${path}` return `${parentPath}${childPath}`.replace(/\/+/g, '/') // /park + /park/park-lot ``` 所以只要子页 path 以 `/` 开头且目录已经有同名首段,就必然重复一段。 修法(两处,缺一不可): 1. **数据**:`system_menu` 里 `id>=100000` 的 60 条子页记录,path 去掉目录首段 (`/park/park-lot` → `park-lot`、`/order/parking-record` → `parking-record`、 `/system/org` → `org`、`/finance/bill` → `bill` …)。 2. **脚本**:`scripts/migrate-menus.sql` 的 INSERT 加 `CASE` 归一化, 重建库时不会再写回绝对路径: ```sql CASE WHEN type = 2 AND path LIKE '/%' AND LOCATE('/', path, 2) > 0 THEN SUBSTRING(path, LOCATE('/', path, 2) + 1) ELSE path END ``` (`type=2` 才是菜单页;`type=1` 目录保持绝对路径,`type=3` 按钮无 path。) 验证:重新登录后展开「停车设施」点「车场列表」,`location.pathname` = `/park/park-lot`,标题「芋道管理系统 - 车场列表」,0 条 console error。 ## 8. 视频接入(项目要求) 上游 `yudao-module-iot` **没有** GB28181 / ONVIF / RTSP 视频接入能力;它只有设备/产品/物模型/OTA/规则/告警和 TCP/UDP/HTTP/MQTT/EMQX/CoAP/Modbus 协议。 建议方案(待选型): - 先挂载 `yudao-module-iot` 作为设备接入底座; - 新增 `parking-module-video`,对接开源视频网关: - **WVP-PRO**(GB28181 信令 + 设备管理)+ **ZLMediaKit**(流媒体); - 或直接接 RTSP/ONVIF 的轻量网关。 - 该模块独立于 IoT,通过 `parking-module-api` 暴露视频分页/预览/回放接口。 ## 8.1 启动实测与踩坑记录(2026-11-02) 48083 首次完整跑通:`Started YudaoServerApplication in 18.156 seconds`,登录 `POST /admin-api/system/auth/login`(admin/admin123,tenant-id 头可带可不带)返回 `code=0`, 存量接口 `POST /admin-api/park/area/query-area-and-block-tree` 返回省市区数据。 启动路上逐个踩掉的坑(都已修在代码/配置里,不是临时命令行参数): | # | 现象 | 根因 | 修法 | |---|---|---|---| | 1 | `NoClassDefFoundError: WxRedisOps` | 老微信 cp starter 3.3.0 拖来 weixin-java-common 3.3.0,没有该类 | `parking-dependencies` 钉 `weixin-java-common 4.8.6-*` | | 2 | `BeanDefinitionOverrideException: springContextUtil` | easy-trans `TransServiceConfig` 注册了同名 `@Bean` | 存量 `SpringContextUtil` 改 Bean 名 `legacySpringContextUtil`(全仓只用静态方法) | | 3 | `No qualifying bean RocketMQTemplate` | Boot 3 不再读老 starter 的 `spring.factories` | `parking-legacy-common` 补 `NoopRocketMQTemplate` + `ParkingLegacyRocketMQAutoConfiguration`,走 `AutoConfiguration.imports` | | 4 | `MailService` 缺 `JavaMailSender` | 没配 `spring.mail` | `application.yaml` 新增 `spring.mail.*`(全部环境变量) | | 5 | `NoSuchFieldError: okhttp3...Companion` | okhttp 4.12 配 okio 1.11 | BOM 的 okio 改 3.6.0 | | 6 | `WxCpProperties.getAppConfigs()` NPE | `WxCpConfiguration.initServices()` 直接 `.stream()` | `wechat.cp.app-configs: []` | | 7 | `PlaceholderResolutionException: parking.refresh-cache.token` | 存量配置类强依赖 | `application.yaml` 加 `parking.refresh-cache.token: ${REFRESH_CACHE_TOKEN:}` | | 8 | `IotStorageInitRunner` 启动成功后又 `System.exit(1)` | 本机没有 TDengine/IoTDB | 启动类 `excludeFilters` 排除该 Runner(接视频/时序数据时部署 TDengine 再移除) | | 9 | 存量接口全部 NPE(`UserInfo.getOrgId()` 空) | 合并工程里生效的是 **yudao 安全栈**,parking 的 starter 没注册(其 `OAuth2TokenCommonApi` 无实现),`SecurityContext` 里是 yudao 的 `LoginUser` | `LoginUserBridge` 增加 yudao `LoginUser` 兜底转换;`OAuth2TokenServiceImpl#buildUserInfo` 补 `username` | | 10 | 报表页报「请求参数类型错误:2026-11-01 00:00:00」 | 存量 DTO 的 `java.util.Date` 字段;yudao 只配了**序列化**(毫秒),没配反序列化格式 | `parking-legacy-common` 新增 `LegacyDateAutoConfiguration`(Jackson `Date` 反序列化器 + MVC `String→Date` 转换器),同时接受毫秒数字、`yyyy-MM-dd HH:mm:ss`、ISO-8601;走 `AutoConfiguration.imports` 注册 | | 11 | 日志刷 45 条 `ClassCastException: EmptyMap cannot be cast to List`(`RedissonStreamCommands.xReadGroup`) | yudao BOM 的 Redisson 4.8.0 + `redisson-spring-data-35` 有 RESP2 `xReadGroup` 适配 bug | `yudao-dependencies` 钉 Redisson **3.52.0**(自带 `redisson-spring-data-35`);重启后 0 条 | | 12 | 充电稽核/对账/会员卡等 5 个页面 HTTP 200 但 `code=500`:`MybatisPlusException: Failed to process ... ParseException: Encountered unexpected token: "AND"` | 上游 `DataPermissionInterceptor` 对**每条** mapper 语句先用 JSqlParser 解析;合并工程为 PageHelper 6.1.1 固定 JSqlParser **4.9**,存量 SQL(UNION ALL、动态 GROUP BY、count 包裹)大量解析失败 | 新增 `LegacySafeDataPermissionInterceptor`:非 `cn.iocoder.yudao.*` 的语句在解析前直接放行。配套 `LegacyDataPermissionAutoConfiguration` 重建上游 3 个 Bean,并在 **`application.yaml` 与 `application-local.yaml` 两处** exclude 上游 `YudaoDataPermissionAutoConfiguration`(profile 的 `autoconfigure.exclude` 会**替换**而非合并基座值,只改一处会 Bean 冲突) | | 13 | `/order/charger-audit` 报 `only_full_group_by`(`charger_income_statistic_day.charger_name`) | MySQL 8 默认开启 `ONLY_FULL_GROUP_BY`,存量 SQL 按 5.7 写 | JDBC URL 加 `sessionVariables=sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'`(**仅本应用连接**降级,不动 MySQL 全局,老 48082 不受影响) | | 14 | `/finance/balance` 报 `near 'desc ) tmp_count'`;另有 `order_park`/`income_finance_bill`/`income_statistic_day`/`enclosed_parking_lot_config` 的同类 SQL | MySQL 8 已删除 `GROUP BY col DESC/ASC` 语法(5.7 起废弃),PageHelper 的 count 包裹后报语法错 | 逐条改为等价写法:`GROUP BY check_date ORDER BY check_date DESC`、`GROUP BY year,num ORDER BY year DESC,num DESC`、`GROUP BY staticDate ORDER BY staticDate DESC`、`GROUP BY l.sort ORDER BY l.sort asc`;子查询里无意义的 `desc` 直接去掉。已全仓 grep 排查,无残留 | | 15 | `/finance/reconciliation` 报 `业务错误:appId为空!`,汇总报 `near 'and a.report_date >=...'` | 桥取 `LoginUser.deptId`(本地 admin=**103**,芋道演示部门树遗留)换算组织;`org-id-mapping` 只配了 `1:10003`,103 原样落到 `sys_org_app_id` 查不到 → `appIds` 为空 | 本地映射改为 `103:10003,1:10003`;对账入参变成 `orgIds=[10003]`,分页与汇总均 `BIZ_SUCCESS` | | 16 | 新服务把老服务的出场延迟消息抢走(潜在) | 新老服务默认都连 **Redis db 0**;存量 `EqpOutDelayQueueSchedule` 每 10s `poll()` 同一个 `RBlockingQueue`,缓存 key(`zteits-park:*`)也同名 | `application-local.yaml` 的 `spring.data.redis.database` 改 **1**(老服务仍是 0)。登录令牌/缓存/队列全部隔离,老环境不受影响 | | 17 | 切到 db1 后 `/order/park-record/query-park-order-for-finance` 500:`ParkingLotDTO.getOrgId()` NPE | 存量代码直接读 Redis hash(`ParkingLotCacheUtil`),新 db 里缓存为空 | 启动后调用 `/admin-api/system/refresh-cache/{park,sys,pay}-cache?type=-1` 与 `refresh-bill-cache?type=1`;已封装成 `scripts/warm-cache.sh`,首次启动或清 Redis 后执行 | 已知未决(不阻塞当前联调): - **两套调度同时在跑**:老 48082 与新 48083 都会执行存量 `@Scheduled`/Quartz, 联调期先只开一个;正式切换前把存量定时任务收口到新工程的 Quartz。 - **组织 id 空间**:`LoginUserBridge` 用 `parking.legacy.org-id-mapping`(本地 `103:10003,1:10003`)换算; 长期方案是把 `system_users.dept_id` 对齐到真实组织、老 `sys_org` 的 id 原样迁进 `system_org`,然后删掉映射。 - **菜单/权限收口**:672 个 `@PreAuthorize` 权限点中约 462 个在 `system_menu` 无记录, 目前 admin 可通,非超管角色需补菜单(含导出权限)后才可用。 ## 9. 常用命令 ```bash export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home export PATH=$JAVA_HOME/bin:$PATH # 后端构建(裁模块后) mvn -T 1C -DskipTests clean package mvn -pl yudao-server -am -DskipTests package # 启动(新服务 48083,避免与老服务 48082 冲突;配置全在 application-local.yaml) java -jar yudao-server/target/yudao-server.jar --spring.profiles.active=local # 预热存量缓存(首次启动 / 清过 Redis 后必须执行,否则依赖 Redis 的存量接口会 NPE) # 注意:同一账号再登录会把旧 token 踢下线;脚本内部会登录,跑完浏览器需重新登录 ./scripts/warm-cache.sh # 冒烟 curl -s -X POST http://127.0.0.1:48083/admin-api/system/auth/login \ -H 'Content-Type: application/json' -d '{"username":"admin","password":"admin123"}' # 前端(VITE_PORT=80 在 macOS 非 root 下绑不了,用 5175) cd ../parking-cloud-yudao-ui && pnpm install && pnpm dev --port 5175 ``` ## 10. 续跑口令 在新目录开 Claude 窗口后,直接说: > 读 `MIGRATION.md` 和 `docs/superpowers/specs/2026-11-02-yudao-replatform-design.md`,从「当前进度」的下一项继续迁移。先确认 `mvn -pl yudao-server -am -DskipTests package` 能过,再按模块推进。数据库用 `parking_cloud_yudao`,不要动 `parking_cloud`。 ## 11. 已知坑 1. 上游 `pay/member/report/...` 不能挂,路由和表都会撞。 2. 两个 `MetaObjectHandler` 不能同时注册。 3. `com.clouds.common.*` 扫描顺序错 → 启动 NPE。 4. 上游全局 `logic-delete` 只配了值、没配字段,parking 无 `deleted` 的 DO 安全。 5. 菜单 id 必须错开,否则和上游 seed 冲突。 6. 老库 `system_org` 与上游 `system_dept` 是两套,先保留 org,后续再谈合并。 7. `pnpm ts:check` 还有 **28 个存量页面的类型错误**(18 个 EP `el-tag`/`el-select` 可空 prop、2 个 `orgName`、8 个未使用变量), 不影响 `pnpm dev` 与 `vite build`(构建不跑 vue-tsc);新代码提交前请保证自己改的文件不新增错误。