1 项目说明

1.1 项目介绍

01.信息汇总
    **PISCESBOOT**,由**山西维格数智科技有限公司**倾力打造的低代码开发平台,是一套完整的企业级快速开发解决方案。
    采用前后端分离架构,涵盖 **SpringBoot2.x**、**SpringCloud**、**Ant Design & Vue**、**Mybatis-plus**、**Shiro**、**JWT**
    等技术栈,全面支持微服务架构。其强大的代码生成器支持前后端代码一键生成,真正实现低代码开发。平台提供一系列 **低代码模块**,
    包括在线表单开发、在线报表、报表动态配置、在线图表设计、大屏设计器、移动端配置、表单设计器、流程在线设计、流程自动化配置
    以及插件化开发能力(可插拔模块),助力实现真正的“零代码”开发体验。**PISCESBOOT** 适用于各类 J2EE 项目,尤其在 **SAAS 项目**、
    **企业信息管理系统(MIS)**、**内部办公系统(OA)**、**企业资源计划系统(ERP)**、**客户关系管理系统(CRM)** 等领域具有广泛应用。
    通过其半智能化的手工合并开发模式,**PISCESBOOT** 不仅大幅提升开发效率,帮助企业节省超过 **70%** 的开发时间,还能显著降低开发成本。

02.功能模块
    ├─系统管理
    │  ├─用户管理
    │  ├─角色管理
    │  ├─菜单管理
    │  ├─权限设置(支持按钮权限、数据权限)
    │  ├─表单权限(控制字段禁用、隐藏)
    │  ├─部门管理
    │  ├─我的部门(二级管理员)
    │  └─字典管理
    │  └─分类字典
    │  └─系统公告
    │  └─职务管理
    │  └─通讯录
    │  └─多租户管理
    ├─消息中心
    │  ├─消息管理
    │  ├─模板管理
    ├─代码生成器(低代码)
    │  ├─代码生成器功能(一键生成前后端代码,生成后无需修改直接用,绝对是后端开发福音)
    │  ├─代码生成器模板(默认2套模板)
    │  ├─代码生成器模板(生成代码,自带excel导入导出)
    │  ├─查询过滤器(查询逻辑无需编码,系统根据页面配置自动生成)
    │  ├─高级查询器(弹窗自动组合查询条件)
    │  ├─Excel导入导出工具集成(支持单表,一对多 导入导出)
    │  ├─平台移动自适应支持
    ├─系统监控
    │  ├─Gateway路由网关
    │  ├─性能扫描监控
    │  │  ├─监控 Redis
    │  │  ├─Tomcat
    │  │  ├─jvm
    │  │  ├─服务器信息
    │  │  ├─请求追踪
    │  │  ├─磁盘监控
    │  ├─定时任务
    │  ├─系统日志
    │  ├─消息中心(支持短信、邮件、微信推送等等)
    │  ├─数据日志(记录数据快照,可对比快照,查看数据变更情况)
    │  ├─系统通知
    │  ├─SQL监控
    │  ├─swagger-ui(在线接口文档)
    │─报表示例
    │  ├─曲线图
    │  └─饼状图
    │  └─柱状图
    │  └─折线图
    │  └─面积图
    │  └─雷达图
    │  └─仪表图
    │  └─进度条
    │  └─排名列表
    │  └─等等
    │─大屏模板
    │  ├─作战指挥中心大屏
    │  └─物流服务中心大屏
    │─常用示例
    │  ├─自定义组件
    │  ├─对象存储(对接阿里云)
    │  ├─JVXETable示例
    │  ├─单表模型例子
    │  └─一对多模型例子
    │  └─打印例子
    │  └─一对多TAB例子
    │  └─内嵌table例子
    │  └─常用选择组件
    │  └─异步树table
    │  └─接口模拟测试
    │  └─表格合计示例
    │  └─异步树列表示例
    │  └─一对多JEditable
    │  └─JEditable组件示例
    │  └─图片拖拽排序
    │  └─图片翻页
    │  └─图片预览
    │  └─PDF预览
    │  └─分屏功能
    │─封装通用组件
    │  ├─行编辑表格JEditableTable
    │  └─省略显示组件
    │  └─时间控件
    │  └─高级查询
    │  └─用户选择组件
    │  └─报表组件封装
    │  └─字典组件
    │  └─下拉多选组件
    │  └─选人组件
    │  └─选部门组件
    │  └─通过部门选人组件
    │  └─封装曲线、柱状图、饼状图、折线图等报表组件
    │  └─在线code编辑器
    │  └─上传文件组件
    │  └─验证码组件
    │  └─树列表组件
    │  └─表单禁用组件
    │  └─等等
    │─更多页面模板
    │  ├─各种高级表单
    │  ├─各种列表效果
    │  └─结果页面
    │  └─异常页面
    │  └─个人页面
    ├─高级功能
    │  ├─系统编码规则
    │  ├─提供单点登录CAS集成方案
    │  ├─提供APP发布方案
    │  ├─集成Websocket消息通知机制
    ├─Online在线开发
    │  ├─Online在线表单
    │  ├─Online代码生成器
    │  ├─Online在线报表
    │  ├─多数据源管理
    ├─积木报表设计器
       ├─打印设计器
       ├─数据报表设计
       ├─图形报表设计(支持echart)

1.2 快速开始

01.启动说明
    a.后端:启动无先后顺序
        9974端口:pisces-module-jimu(积木模块)
        9984端口:pisces-module-code(代码生成器)
        9994端口:pisces-module-system / pisces-module-demo(依赖pisces-module-system) / pisces-module-demo2(依赖pisces-module-system)
        -----------------------------------------------------------------------------------------------------
        目前只支持【pisces-module-system / pisces-module-demo / pisces-module-demo2】单个启动
        暂未实现【pisces-module-demo、pisces-module-demo2...】集成到【pisces-module-system】
        原因为pisces-module-demo引入pisces-module-system存在循环依赖,需要pisces-module-system抽离出biz
        -----------------------------------------------------------------------------------------------------
        更多文档,请移步如下各自模块的README.md
        业务模块:pisces-module-biz\README.md
        积木模板:pisces-module-jimu\README.md
        代码生成器:pisces-module-code\README.md
        系统核心模块:pisces-module-system\README.md
    b.前端
        方案:Vue3.0 + TypeScript + Vite + AntDesignVue + pinia + echarts
        第1步:pnpm install
        第2步:pnpm dev
        第3步:pnpm build
    c.结构
        /pisces-boot-starter-final (统一父项目)
        ├── pisces-module-system (系统核心模块,基础服务)
        │   ├── src/main
        │   │   ├── java
        │   │   └── resources
        │   │       ├── application.yml
        │   │       └── application-dev.yml
        │   └── pom.xml
        │
        ├── pisces-module-biz (业务模块集合)
        │   ├── pisces-module-demo (示例服务1)
        │   │   ├── src/main
        │   │   │   ├── java/cn/myslayers/modules
        │   │   │   └── resources/application.yml
        │   │   └── pom.xml
        │   │
        │   └── pisces-module-demo2 (示例服务2)
        │       ├── src/main
        │       │   ├── java/cn/myslayers
        │       │   └── resources/application.yml
        │       └── pom.xml
        │
        ├── pisces-module-code (代码生成模块)
        │   └── pom.xml
        │
        ├── pisces-module-jimu (积木报表模块)
        │   └── pom.xml
        │
        └── pom.xml (父POM配置)

02.维格数智管理系统-前端
    a.logo
        ..\frontend\public
        ..\frontend\public\resource\img
    b.标题
        费用预算管理系统 -> 维格数智管理系统
        集团费用预算管理系统 -> 维格数智管理系统
    c.env.development
        #后台接口父地址(必填)
        #维格数智管理系统(必填)
        #代码生成器(必填)
        #报表地址(必填)
        #上传接口(必填)
        VITE_GLOB_API_URL=/piscesboot
        VITE_GLOB_DOMAIN_URL=http://localhost:9994/pisces-boot
        VITE_GLOB_ONLINE_URL=http://localhost:9984/pisces-code
        VITE_GLOB_REPORT_URL=http://localhost:9974/pisces-jimu
        VITE_GLOB_UPLOAD_URL=http://localhost:3300
    d.env.production
        #后台接口父地址(必填)
        #维格数智管理系统(必填)
        #代码生成器(必填)
        #报表地址(必填)
        #上传接口(必填)
        VITE_GLOB_API_URL=/piscesboot
        VITE_GLOB_DOMAIN_URL=http://localhost:9994/pisces-boot
        VITE_GLOB_ONLINE_URL=http://localhost:9984/pisces-code
        VITE_GLOB_REPORT_URL=http://localhost:9974/pisces-jimu
        VITE_GLOB_UPLOAD_URL=http://localhost:3300
    e.index.html
        <script>
          window._CONFIG = {
            reportURL: 'http://localhost:9974/pisces-jimu',
            domianURL: 'http://localhost:9994/pisces-boot',
          };
        </script>

03.维格数智管理系统-后端
    a.application.yml
        spring:
          profiles:
            active: dev
            # active: '@profile.name@'
    b.application-dev.yml / application-prod.yml
        server:
          port: 9994  # 服务端口号
          servlet:
            context-path: /pisces-boot
          datasource:
            dynamic:
              datasource:
                master:
                  url: jdbc:mysql://127.0.0.1:3307/pisces-boot-xxx?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
                  username: root
                  password: 123456
                  driver-class-name: com.mysql.cj.jdbc.Driver
          redis:
            database: 2
            host: localhost
            port: 6379
            password: myslayers
            lettuce:
              pool:
                max-active: 8
                max-wait: -1
                max-idle: 8
                min-idle: 0
            timeout: 5000
    c.logback-spring.xml
        <property name="LOG_HOME" value="../logs" />

1.3 技术架构

01.开发环境
    语言:Java 8+ (小于17)
    IDE(JAVA): IDEA (必须安装lombok插件 )
    IDE(前端): Vscode、WebStorm、IDEA
    依赖管理:Maven
    缓存:Redis
    数据库脚本:MySQL5.7+  &  Oracle 11g & Sqlserver2017

02.支持数据库
    MySQL
    Oracle11g
    Sqlserver2017
    PostgreSQL
    MariaDB
    达梦、人大金仓

03.后端
    基础框架:Spring Boot 2.6.6
    微服务框架: Spring Cloud Alibaba 2021.0.1.0
    持久层框架:MybatisPlus 3.5.1
    报表工具: JimuReport 1.5.2
    安全框架:Apache Shiro 1.8.0,Jwt 3.11.0
    微服务技术栈:Spring Cloud Alibaba、Nacos、Gateway、Sentinel、Skywalking
    数据库连接池:阿里巴巴Druid 1.1.22
    日志打印:logback
    其他:autopoi, fastjson,poi,Swagger-ui,quartz, lombok(简化代码)等。

04.前端
    前端IDE建议:WebStorm、Vscode
    采用 Vue3.0+TypeScript+Vite5+Ant-Design-Vue等新技术方案,包括二次封装组件、utils、hooks、动态菜单、权限校验、按钮级别权限控制等功能
    最新技术栈:Vue3.0 + TypeScript + Vite5 + ant-design-vue4 + pinia + echarts + unocss + vxe-table + qiankun + es6
    依赖管理:node、npm、pnpm

1.4 微服务方案

01.目前实现功能
    服务注册和发现 Nacos
    统一配置中心 Nacos
    路由网关 gateway(三种加载方式)
    分布式 http feign
    熔断降级限流 Sentinel
    分布式文件 Minio、阿里OSS
    统一权限控制 JWT + Shiro
    服务监控 SpringBootAdmin
    链路跟踪 Skywalking
    消息中间件 RabbitMQ
    分布式任务 xxl-job
    分布式事务 Seata
    分布式日志 elk + kafka
    支持 docker-compose、k8s、jenkins
    CAS 单点登录
    路由限流

02.未来集成计划
    基础认证授权:Sa-Token
    复杂流程:Flowable
    轻量规则引擎:LiteFlow
    ...

2 版本说明

2.1 历史his

01.基础结构
    a.pisces-boot-starter_1.1.0
        pisces-boot-starter_1.1.0_【未合并到】pisces-module-common                                             【仍然存在jeecg字样】,pisces-boot-test
        pisces-boot-starter_1.1.0_【已合并到】pisces-module-common                                       已删档【仍然存在jeecg字样】,pisces-boot-test
        pisces-boot-starter_1.1.0_【存在2个版本】,一个是jeecg,一个是pisces                              已删档【仍然存在jeecg字样】,pisces-boot-test
        pisces-boot-starter_1.1.0_【存在1个版本】,只有pisces,可以打包到starter                          已删档【仍然存在jeecg字样】,pisces-boot-test
    b.pisces-boot-starter_1.1.0_web
        a.登录
            后端9994/前端3100,admin/123456
        b.验证码
            需要redis,【存在】前端验证码
        c.功能模块
            Dashboard
            系统管理
            低代码开发
            统计报表
            系统监控
            开发勿动

02.jeecg项目,制作代码生成器
    a.pisces-boot-starter_1.2.0
        a.jeecg-module-common-code1
            jeecg-module-common-code1_v1.1_删除quartz_可以直接登录.zip                                      【仍然存在jeecg字样】,pisces-boot-code
            jeecg-module-common-code1_v1.2_删除ejia附属信息_删除third第三方.zip                             【仍然存在jeecg字样】,pisces-boot-code
            jeecg-module-common-code1_v1.3_删除JustAuth.zip                                                【仍然存在jeecg字样】,pisces-boot-code
            jeecg-module-common-code1_v1.4_删除msg、oss.zip                                                【仍然存在jeecg字样】,pisces-boot-code
            jeecg-module-common-code1_v1.5_删除tenant、third.zip                                           【仍然存在jeecg字样】,pisces-boot-code
            jeecg-module-common-code1_v1.6_正式版本_带有redis_login.zip                                    【仍然存在jeecg字样】,pisces-boot-code
        b.jeecg-module-common-code2
            jeecg-module-common-code2_v1.1_必须redis才能登录.zip                                            【仍然存在jeecg字样】,pisces-boot-code
            jeecg-module-common-code2_v1.2_已删除登录的token,其他地方还存在.zip                             【仍然存在jeecg字样】,pisces-boot-code
            jeecg-module-common-code2_v1.3_仍需要redis,用于hibernate-re加密jar包.zip                       【仍然存在jeecg字样】,pisces-boot-code
            jeecg-module-common-code2_v1.4_正式版本_不需要redis_login_但不影响登录_代码生成器.zip            【仍然存在jeecg字样】,pisces-boot-code
            jeecg-module-common-code2_v1.5_正式版本_完全剔除jeecg字样.zip                                    【包名存在jeecg字样】,pisces-boot-code
            jeecg-module-common-code2_v1.6_正式版本_发布到final_代码生成器嵌入到系统中.zip                    【包名存在jeecg字样】,pisces-boot-code
        c.jeecg-module-common-code3
            jeecg-module-common-code3_v1.1_正式版本_发布到final_代码生成器嵌入到系统中_仅实现SysBaseApiImpl.zip【包名存在jeecg字样】,pisces-boot-code
        d.jeecg-module-common-origin
            jeecg-module-common-origin                                                                      【原始版本】,pisces-boot-test
    b.pisces-boot-starter_1.2.0_web
        a.登录
            后端9994/前端3100,admin/123456
        b.验证码
            需要redis,【不存在】前端验证码
        c.功能模块
            Dashboard
            系统管理
            低代码开发

03.pisces项目,接入代码生成器
    a.pisces-boot-starter_1.3.0
        a.pisces-module-common-code1
            pisces-module-common-code1_v1.1_移除jimureport_codegenerate_并且彻底删除jeecg痕迹.zip              【完全剔除jeecg字样】,pisces-boot-final
            pisces-module-common-code1_v1.2_完全剔除jeecg字样.zip                                             【完全剔除jeecg字样】,pisces-boot-final
        b.pisces-module-common-code2
            pisces-module-common-code2_v1.2_完全剔除jeecg字样.zip                                             【完全剔除jeecg字样】,pisces-boot-final
            pisces-module-common-code2_v1.2_正式版本_发布到final_代码生成器嵌入到系统中.zip                     【完全剔除jeecg字样】,pisces-boot-final
        c.pisces-module-common-code3
            pisces-module-common-code3_v1.1_正式版本_还原最初的system项目结构.zip                              【完全剔除jeecg字样】,pisces-boot-final\
        d.pisces-module-common-origin
            pisces-module-common-origin                                                                       【原始版本】,pisces-boot-test
    b.pisces-boot-starter_1.3.0_web
        a.登录
            后端9994/前端3100,admin/123456
        b.验证码
            需要redis,【存在】前端验证码
        c.功能模块
            Dashboard
            系统管理
            低代码开发
            统计报表
            系统监控
            开发勿动

04.pisces-boot-starter-final
    a.后端
        pisces-module-biz 来自 pisces-boot-starter-demo
        pisces-module-jimu 来自 jeecg-module-jimu-code2_v1.1_正式版本_1.6.5_根据环境校验Token.zip
        pisces-module-code 来自 jeecg-module-common-code2_v1.6_正式版本_发布到final_代码生成器嵌入到系统中_仅实现SysBaseApiImpl.zip
        pisces-module-code2 来自 jeecg-module-common-code2_v1.6_正式版本_发布到final_代码生成器嵌入到系统中.zip
        pisces-module-system 来自 pisces-module-common-code2_v1.2_正式版本_发布到final_代码生成器嵌入到系统中.zip
        -----------------------------------------------------------------------------------------------------
        pisces-boot-starter-final_v1.1_完全剔除jeecg字样_代码生成器没有嵌入到系统中.zip                         【完全剔除jeecg字样】
        pisces-boot-starter-final_v1.2_正式版本_发布到final_代码生成器嵌入到系统中.zip                          【包名存在jeecg字样】
        pisces-boot-starter-final_v1.3_正式版本_发布到final_积木报表嵌入到系统中.zip                           【完全剔除jeecg字样】
        pisces-boot-starter-final_v1.4_正式版本_发布到final_代码生成器嵌入到系统中_仅实现SysBaseApiImpl.zip     【包名存在jeecg字样】
        pisces-boot-starter-final_v1.4_正式版本_发布到final_代码生成器嵌入到系统中_仅实现SysBaseApiImpl.zip     【包名存在jeecg字样】
        pisces-boot-starter-final_v1.5_正式版本_发布到final_代码生成器_前端配置nginx.zip                       【包名存在jeecg字样】
        pisces-boot-starter-final_v1.6_正式版本_发布到final_仅更改readme发布到gogs.zip                         【包名存在jeecg字样】
        pisces-boot-starter-final_v1.6_正式版本_发布到final_仅更改readme发布到gogs_交换web与web2.zip            【包名存在jeecg字样】
        pisces-boot-starter-final_v1.7_正式发布_发布到gogs_v1.0.0_单体架构.zip                                 【包名存在jeecg字样】
    b.前端
        web 来自 pisces-boot-starter-web3 来自 pisces-boot-starter-web                                          【对接pisces-module-biz、pisces-module-jimu、pisces-module-code、pisces-module-system】
        -----------------------------------------------------------------------------------------------------
        v1.2_正式版本_发布到final_代码生成器嵌入到系统中:已将.env.development中的VITE_PROXY移动至vite.config.ts
        v1.3_正式版本_发布到final_积木报表嵌入到系统中:已将.env.development中的VITE_PROXY移动至vite.config.ts
        v1.5_正式版本_发布到final_代码生成器_前端配置nginx:分别配置到了env.development、env.production、index.html

05.pisces-cloud-starter-final
    a.后端
        pisces-module-biz 来自 pisces-boot-starter-demo
        pisces-module-jimu 来自 jeecg-module-jimu-code2_v1.1_正式版本_1.6.5_根据环境校验Token.zip
        pisces-module-code 来自 jeecg-module-common-code2_v1.6_正式版本_发布到final_代码生成器嵌入到系统中_仅实现SysBaseApiImpl.zip
        pisces-module-system 来自 pisces-module-common-code2_v1.2_正式版本_发布到final_代码生成器嵌入到系统中.zip
        -----------------------------------------------------------------------------------------------------
        pisces-cloud-starter-final_v1.5_正式版本_发布到final_代码生成器_前端配置nginx.zip                      【包名存在jeecg字样】
        pisces-cloud-starter-final_v1.6_正式版本_发布到final_仅更改readme发布到gogs.zip                        【包名存在jeecg字样】
        pisces-cloud-starter-final_v1.7_正式发布_发布到gogs_v1.0.0_单体架构.zip                                【包名存在jeecg字样】
    b.前端
        web 来自 pisces-boot-starter-web3 来自 pisces-boot-starter-web                                          【对接pisces-module-biz、pisces-module-jimu、pisces-module-code、pisces-module-system】

2.2 发行plus

01.pisces-boot-parent
    a.后端
        pisces-boot-parent_v1.8_正式发布_发布到gogs_v1.1.0_解耦各个模块.zip
        -----------------------------------------------------------------------------------------------------
        pisces-module-system-combine:依赖common版本,未拆分【pisces-system-api、pisces-system-biz、pisces-system-start】
        pisces-module-system-separate:依赖common版本,已拆分【pisces-system-api、pisces-system-biz、pisces-system-start】
        pisces-module-system-depend【使用】:独立版本,使用maven-shade-plugin,可执行jar,如xxx-module-xxx-1.0.0.jar,150MB
        pisces-module-system-standalone:独立版本,spring-boot-maven-plugin:可执行jar/war,如xxx-module-xxx-1.0.0-exec.jar,150MB
    b.前端
        web,jimu集成到system中
    c.示例
        pisces-boot-example,使用【maven-shade-plugin】
        pisces-boot-example-all,使用【spring-boot-maven-plugin】

02.pisces-cloud-parent
    a.后端
        pisces-boot-parent_v1.8_正式发布_发布到gogs_v1.1.0_解耦各个模块.zip
        -----------------------------------------------------------------------------------------------------
        pisces-cloud-gateway-http:V1,http版本,存在【静态路由】
        pisces-cloud-gateway-nacos:V2,nacos版本,存在【静态路由】+【动态路由】
        pisces-cloud-gateway:V3,nacos版本,存在【静态路由】+【动态路由】,允许系统模块【pisces-module-system】向网关发送路由更新请求,以【刷新网关Redis中的路由】配置
    b.前端
        略
    c.示例
        略

03.pisces-jimu-standalone,积木报表独立版
    a.后端
        jeecg-module-jimu-code1_v1.1_正式版本_1.6.5_无校验Token.zip                                      【包名剔除jeecg字样】
        jeecg-module-jimu-code2_v1.1_正式版本_1.6.5_根据环境校验Token.zip                                【包名剔除jeecg字样】
        jeecg-module-jimu-code3_v1.1_正式版本_1.9.1_security校验用户名密码.zip                           【包名剔除jeecg字样】
    b.前端
        默认后端自带web界面

04.pisces-code-standalone,代码生成独立版
    a.后端
        pisces-code-standalone_v1.0_web报错_已删除mock.zip                                                【已删除mock,本地运行没有问题,但build报错;但之前某一版已修复删除mock版本,已丢失,故这里面nginx可用】
        pisces-code-standalone_v1.1_web解决报错_未删除mock.zip                                            【未删除mock,本地运行没有问题,build没有问题,nginx也可用】
        pisces-code-standalone_v1.2_更换为piscescode_端口9984.zip                                           【未删除mock,替换路径为pisces-code,端口为9984】
    b.前端
        web 来自 pisces-boot-starter-web3 来自 pisces-boot-starter-web

2.3 迁移Migra

01.pisces-boot-parent
    a.安装pisces-boot-parent坐标
        cd pisces-boot-parent
        mvn install:install-file -Dfile=pom.xml -DpomFile=pom.xml
    b.安装各个模块
        cd pisces-boot-parent
        mvn install

02.批量修改内容
    a.logo
        ..\frontend\docs\logo.png
        ..\frontend\public\logo.png
        ..\frontend\public\resource\img\logo.png
        ..\frontend\src\assets\images\logo.png
    b.文件夹、文件
        REPLACE_OLD=("源点数智" "源点" "jggroup" "Ydsz" "ydsz" "YDSZ")
        REPLACE_NEW=("维格数智" "维格" "myslayers" "Pisces" "pisces" "PISCES")
    c.搜索
        ejia
        yunzhijia
        eas-datasource
        @ApiModelProperty(value = "eJia请求内容")
    d.修改文件
        LoginForm.vue
        LoginController.java
        ThirdAppController.java
    e.删除文件
        UserInfoUtil.java
        ScheduledSyncEjiaUser.java
        ThirdAppYunZhiJiaServiceImpl.java

03.banner.txt
    a.网址
        https://tooltt.com/art-ascii/
    b.PiscesBootSystem
          ____  _                   ____              _   ____            _
         |  _ \(_)___  ___ ___  ___| __ )  ___   ___ | |_/ ___| _   _ ___| |_ ___ _ __ ___
         | |_) | / __|/ __/ _ \/ __|  _ \ / _ \ / _ \| __\___ \| | | / __| __/ _ \ '_ ` _ \
         |  __/| \__ \ (_|  __/\__ \ |_) | (_) | (_) | |_ ___) | |_| \__ \ ||  __/ | | | | |
         |_|   |_|___/\___\___||___/____/ \___/ \___/ \__|____/ \__, |___/\__\___|_| |_| |_|
                                                                |___/
    c.PiscesCodeSystem
          ____  _                    ____          _      ____            _
         |  _ \(_)___  ___ ___  ___ / ___|___   __| | ___/ ___| _   _ ___| |_ ___ _ __ ___
         | |_) | / __|/ __/ _ \/ __| |   / _ \ / _` |/ _ \___ \| | | / __| __/ _ \ '_ ` _ \
         |  __/| \__ \ (_|  __/\__ \ |__| (_) | (_| |  __/___) | |_| \__ \ ||  __/ | | | | |
         |_|   |_|___/\___\___||___/\____\___/ \__,_|\___|____/ \__, |___/\__\___|_| |_| |_|
    d.PiscesCloudSystem
          ____  _                    ____ _                 _  ____            _
         |  _ \(_)___  ___ ___  ___ / ___| | ___  _   _  __| |  / ___| __ _| |_ _____      ____ _ _   _
         | |_) | / __|/ __/ _ \/ __| |   | |/ _ \| | | |/ _` | | |  _ / _` | __/ _ \ \ /\ / / _` | | | |
         |  __/| \__ \ (_|  __/\__ | |___| | (_) | |_| | (_| | | |_| | (_| | ||  __/\ V  V / (_| | |_| |
         |_|   |_|___/\___\___||___ \____|_|\___/ \__,_|\__,_|  \____|\__,_|\__\___| \_/\_/ \__,_|\__, |
                                                                                             |___/
    e.PiscesCloudDemo
          ____  _                     ____ _                 _   ____
         |  _ \(_)___  ___ ___  ___  / ___| | ___  _   _  __| | |  _ \  ___ _ __ ___   ___
         | |_) | / __|/ __/ _ \/ __|| |   | |/ _ \| | | |/ _` | | | | |/ _ \ '_ ` _ \ / _ \
         |  __/| \__ \ (_|  __/\__ \| |___| | (_) | |_| | (_| | | |_| |  __/ | | | | | (_) |
         |_|   |_|___/\___\___||___/ \____|_|\___/ \__,_|\__,_| |____/ \___|_| |_| |_|\___/

2.4 常见配置

00.维格数智管理系统-数据库
    a.qrtz_job_details
        org.jeecg.modules.quartz.job.ScheduledSyncEjiaUser 修改为 cn.myslayers.modules.quartz.job.ScheduledSyncEjiaUser
    b.xxx
        xxx

01.维格数智管理系统-前端
    a.logo
        ..\frontend\public
        ..\frontend\public\resource\img
    b.标题
        费用预算管理系统 -> 维格数智管理系统
        集团费用预算管理系统 -> 维格数智管理系统
    c.env.development
        #后台接口父地址(必填)
        #维格数智管理系统(必填)
        #代码生成器(必填)
        #报表地址(必填)
        #上传接口(必填)
        VITE_GLOB_API_URL=/piscesboot
        VITE_GLOB_DOMAIN_URL=http://localhost:9994/pisces-boot
        VITE_GLOB_ONLINE_URL=http://localhost:9984/pisces-code
        VITE_GLOB_REPORT_URL=http://localhost:9974/pisces-jimu
        VITE_GLOB_UPLOAD_URL=http://localhost:3300
    d.env.production
        #后台接口父地址(必填)
        #维格数智管理系统(必填)
        #代码生成器(必填)
        #报表地址(必填)
        #上传接口(必填)
        VITE_GLOB_API_URL=/piscesboot
        VITE_GLOB_DOMAIN_URL=http://localhost:9994/pisces-boot
        VITE_GLOB_ONLINE_URL=http://localhost:9984/pisces-code
        VITE_GLOB_REPORT_URL=http://localhost:9974/pisces-jimu
        VITE_GLOB_UPLOAD_URL=http://localhost:3300
    e.index.html
        <script>
          window._CONFIG = {
            reportURL: 'http://localhost:9974/pisces-jimu',
            domianURL: 'http://localhost:9994/pisces-boot',
          };
        </script>

02.维格数智管理系统-后端
    a.application.yml
        spring:
          profiles:
            active: dev
            # active: '@profile.name@'
    b.application-dev.yml / application-prod.yml
        server:
          port: 9994  # 服务端口号
          servlet:
            context-path: /pisces-boot
          datasource:
            dynamic:
              datasource:
                master:
                  url: jdbc:mysql://127.0.0.1:3307/pisces-boot-xxx?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
                  username: root
                  password: 123456
                  driver-class-name: com.mysql.cj.jdbc.Driver
          redis:
            database: 2
            host: localhost
            port: 6379
            password: xxxxxx
            lettuce:
              pool:
                max-active: 8
                max-wait: -1
                max-idle: 8
                min-idle: 0
            timeout: 5000
    c.logback-spring.xml
        <property name="LOG_HOME" value="../logs" />

03.代码生成器,嵌入到系统中
    a.后端
        移除redis,跳过登录
        20250110,已将涉及【sys:cache:encrypt:user::admin】对应的【CacheConstant这个类关联的代码】全部删除
        移除redis后,虽然【登录+代码生成器】没有问题,但是控制台报错,故而还是采取配置redis
        -----------------------------------------------------------------------------------------------------
        保留token验证,因为pisces配置AUTO在线表单时,必须经过token验证。取消token过期设置
        token请求头位置                X-Access-Token
        pisces的Token(存在时间限制)    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.XyBp2NDHlDBIO-CwWrvLY838WcpUvXrmrcZKS1B-Z28
        code的Token(不存在时间限制)  eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MzY3MzgyOTgsInVzZXJuYW1lIjoiYWRtaW4ifQ.vY85tTCCT_F9tL9BEYtZLLvYYxhTjGNyMpQ7JJ5qKAI
        -----------------------------------------------------------------------------------------------------
        仅保存代码生成
        public class SysBaseApiImpl implements ISysBaseAPI {                          --代码生成器,必须实现这个类
        实现
        public interface ISysBaseAPI extends CommonAPI {
        继承
        public interface CommonAPI {
    b.前端,配置vite.config.ts
        .env                # 基础配置,所有环境都会加载
        .env.development    # 开发环境配置
        .env.production     # 生产环境配置
        .env.test           # 测试环境配置
        -----------------------------------------------------------------------------------------------------
        .env                # 可以删除,因为基础配置可以放在具体环境中
        .env.production     # 保留,生产环境必需
        .env.development    # 保留,开发环境必需
        .env.test           # 可以删除,如果没有测试环境
        -----------------------------------------------------------------------------------------------------
        配置加载流程:
        1.vite.config.ts 中通过 loadEnv 加载环境变量
        2.使用 wrapperEnv 处理环境变量类型转换
        3.最终在 vite.config.ts 中使用这些配置
        -----------------------------------------------------------------------------------------------------
        proxy: createProxy(VITE_PROXY),
        变成
        server: {
          // Listening on all local IPs
          host: true,
          https: false,
          port: VITE_PORT,
          // Load proxy configuration from .env
          proxy: {
            // online 相关请求转发到 9984 端口
            '^/piscesboot/online': {
              target: 'http://localhost:9984/pisces-boot',
              changeOrigin: true,
              rewrite: (path) => path.replace(/^\/piscesboot/, ''),
            },
            // 其他 piscesboot 请求转发到 9994 端口
            '/piscesboot': {
              target: 'http://localhost:9994/pisces-boot',
              changeOrigin: true,
              rewrite: (path) => path.replace(/^\/piscesboot/, ''),
            },
            // 上传请求转发
            '/upload': {
              target: 'http://localhost:3300',
              changeOrigin: true,
            },
          },
        },

04.积木报表,嵌入到系统中
    a.方案
        报表设计器
        路径:/jmreport
        组件:{{ window._CONFIG['domianURL'] }}/jmreport/list?token=${token}                                  原本环境
        组件:{{ window._CONFIG['reportURL'] }}/jmreport/list                                                 dev环境
        组件:{{ window._CONFIG['reportURL'] }}/jmreport/list?token=${token}                                  prod环境
        -----------------------------------------------------------------------------------------------------
        积木报表例子
        路径:/jmreport/view/961455b47c0b86dc961e90b5893bff05
        组件:{{ window._CONFIG['domianURL'] }}/jmreport/view/961455b47c0b86dc961e90b5893bff05?token=${token} 原本环境
        组件:{{ window._CONFIG['reportURL'] }}/jmreport/view/961455b47c0b86dc961e90b5893bff05                dev环境
        组件:{{ window._CONFIG['reportURL'] }}/jmreport/view/961455b47c0b86dc961e90b5893bff05?token=${token} prod环境
        组件:http://localhost:9974/pisces-jimu/jmreport/view/961455b47c0b86dc961e90b5893bff05?token=${token}
        -----------------------------------------------------------------------------------------------------
        生产销售监控大屏
        路径:/test/bigScreen1
        组件:{{ window._CONFIG['domianURL'] }}/test/bigScreen/templat/index1                                 原本环境
        组件:{{ window._CONFIG['reportURL'] }}/test/bigScreen/templat/index1                                 dev环境/prod环境
        组件:http://localhost:9974/pisces-jimu/test/bigScreen/templat/index1
        -----------------------------------------------------------------------------------------------------
        智慧物流监控大屏
        路径:/test/bigScreen2
        组件:{{ window._CONFIG['domianURL'] }}/test/bigScreen/templat/index2                                 原本环境
        组件:{{ window._CONFIG['reportURL'] }}/test/bigScreen/templat/index2                                 dev环境/prod环境
        组件:http://localhost:9974/pisces-jimu/test/bigScreen/templat/index2
    b.vite.config.ts向window._CONFIG添加http://localhost:9974/pisces-jimu
        define: {
          // setting vue-i18-next
          // Suppress warning
          __INTLIFY_PROD_DEVTOOLS__: false,
          __APP_INFO__: JSON.stringify(__APP_INFO__),
          'window._CONFIG': JSON.stringify({
            reportURL: 'http://localhost:9974/pisces-jimu',
            domianURL: VITE_GLOB_DOMAIN_URL,
          }),
        },

2.5 常见报错

00.总结
    a.MybatisPlusSaasConfig.java
        @Configuration
        @MapperScan(value={"cn.myslayers.**.mapper*"})
        public class MybatisPlusSaasConfig {
    b.PiscesSystemApplication.java
        @Slf4j
        @EnableScheduling
        @SpringBootApplication(scanBasePackages = {"org.jeecg", "cn.myslayers"})
        public class PiscesSystemApplication extends SpringBootServletInitializer{

            @Override
            protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
                return application.sources(PiscesSystemApplication.class);
            }

            public static void main(String[] args) throws UnknownHostException {
                SpringApplication.run(PiscesSystemApplication.class, args);
            }
        }
    c.application-dev.yml
        mybatis-plus:
        # mapper-locations:SQL映射文件的加载问题
        # cn.myslayers.config.mybatis.MybatisPlusSaasConfig:Mapper接口的注册问题
        mapper-locations: classpath*:cn/myslayers/**/mapper/xml/*.xml

01.问题1
    a.报错
        2025-01-03 17:37:52.846 [http-nio-9994-exec-5] WARN  org.apache.shiro.authc.AbstractAuthenticator:216 - Authentication failed for token submission [cn.myslayers.config.shiro.JwtToken@7aa36112].  Possible unexpected error? (Typical or expected login exceptions should extend from AuthenticationException).
        org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Could not resolve type id 'org.jeecg.common.system.vo.LoginUser' as a subtype of `java.lang.Object`: no such class found
         at [Source: (byte[])"["org.jeecg.common.system.vo.LoginUser",{"id":"IeYObuSa00K741IFwOv28hJ0HmTTuJxHVNAypLbgTR0=","username":"grXDeTwZNGStnSu4yQuIQA==","realname":"UTQuOhZCZXJmP76PKkcB/A==","password":"zXbRTxWkeGhiRRQn1xPLhg==","orgCode":"A02A18A03","avatar":"","birthday":["java.util.Date","2018-12-05"],"sex":1,"email":"f28NhDpxZuAdWRLU7uWS8w==","phone":"5nve2G2QIL/+BASLYhjl0g==","status":1,"delFlag":0,"activitiSync":1,"createTime":["java.util.Date",1561110850000],"userIdentity":2,"departIds":"","post":"Mdl9ext9euNJ"[truncated 66 bytes]; line: 1, column: 41]; nested exception is com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'org.jeecg.common.system.vo.LoginUser' as a subtype of `java.lang.Object`: no such class found
        -----------------------------------------------------------------------------------------------------
        org.jeecg.common.system.vo.LoginUser
    b.解决
        去找org.jeecg.common.system.vo.LoginUser,说明redis有缓存
        redis:
          database: 2
          host: localhost
          port: 6379
          password: xxxxxx
        -----------------------------------------------------------------------------------------------------
        清除2中全部信息
    c.可能的排查方向
        org.jeecg.modules.aspect.SysUserAspect
        org.jeecg.common.system.vo.LoginUser.java
        org.jeecg.modules.system.entity.SysUser.java
        org.jeecg.common.aspect.annotation.PermissionData

02.问题2
    a.报错
        org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): org.jeecg.modules.system.mapper.SysDepartMapper.queryUserDeparts
            at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:235)
            at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.<init>(MybatisMapperMethod.java:50)
            at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.lambda$cachedInvoker$0(MybatisMapperProxy.java:111)
            at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
            at com.baomidou.mybatisplus.core.toolkit.CollectionUtils.computeIfAbsent(CollectionUtils.java:115)
            at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.cachedInvoker(MybatisMapperProxy.java:98)
            at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)
            at com.sun.proxy.$Proxy149.queryUserDeparts(Unknown Source)
            at org.jeecg.modules.system.service.impl.SysDepartServiceImpl.queryUserDeparts(SysDepartServiceImpl.java:412)
            at org.jeecg.modules.system.service.impl.SysDepartServiceImpl$$FastClassBySpringCGLIB$$6f2aae1b.invoke(<generated>)
    b.配置
        @Configuration
        @MapperScan(value={"cn.myslayers.**.mapper*"})
        public class MybatisPlusSaasConfig {
        }
        -------------------------------------------------------------------------------------------------
        mybatis-plus:
          # mapper-locations:SQL映射文件的加载问题
          # cn.myslayers.config.mybatis.MybatisPlusSaasConfig:Mapper接口的注册问题
          mapper-locations: classpath*:cn/myslayers/**/mapper/xml/*.xml                --注意是classpath*,不是classpath:*
    c.排查步骤
        a.Mapper接口和XML文件的namespace不匹配:
            确保SysDepartMapper接口的namespace与XML文件中的namespace完全一致。你已经确认过这一点,所以这个问题可以排除。
        b.Mapper接口未被正确扫描:
            确保@MapperScan注解的配置正确,且能够扫描到SysDepartMapper接口。你在MybatisPlusSaasConfig中使用了@MapperScan(value={"org.jeecg.**.mapper*"}),这应该是正确的。
        c.XML文件的SQL语句未被加载:
            确保SysDepartMapper.xml文件在运行时能够被MyBatis加载。你可以在application-dev.yml中添加MyBatis的日志配置,以便查看加载的SQL语句:
        d.【重点】检查target
            jeecg-module-common\target\classes\org\jeecg\modules\system\mapper\SysAnnouncementMapper.class
            对应
            jeecg-module-common\target\classes\org\jeecg\modules\system\mapper\xml\SysAnnouncementMapper.xml
        e.【20250109】解决方案
            确定为【build】配置有误,参考【Packing4,父子模块】
            确定为【redis】缓存信息有问题,操作【redis -> 6349 -> DB2 -> 清空】
            确定为【JeecgSystemApplication】编译的内容没有mapper,清除IDE缓存,使用【右侧菜单 -> maven -> 生命周期:clean + compile】

2.6 常见信息

01.实体类
    a.VO (Value Object)
        a.含义
            VO(值对象)是一种只包含数据的对象,用于表示某个业务场景中的数据视图。通常用于向前端(客户端)传递数据。
            VO 通常是只读的,代表的是某个具体的业务场景或接口的响应结果。
            它不直接映射到数据库,而是和前端或者 API 层交互的载体。
        b.使用场景
            用于向前端或其他服务传递数据。
            表示用户看到的视图层数据。
        c.特点
            通常是一个简单的 Java 类,只包含属性、构造方法、getter 和 setter 方法。
            不包含业务逻辑。
        d.示例
            public class UserVO {
                private String name;
                private int age;

                public UserVO(String name, int age) {
                    this.name = name;
                    this.age = age;
                }

                public String getName() {
                    return name;
                }

                public void setName(String name) {
                    this.name = name;
                }

                public int getAge() {
                    return age;
                }

                public void setAge(int age) {
                    this.age = age;
                }
            }
        e.使用
            UserVO userVO = new UserVO("Alice", 25);
            return userVO; // 返回给前端
    b.Entity (实体类)
        a.含义
            Entity(实体类)是和数据库表直接映射的类,通常是持久化对象(PO,Persistent Object)。
            它的每个字段通常对应数据库表中的一个列。
        b.使用场景
            用于数据库交互,表示数据库中的一条记录。
            和 ORM 框架(如 Hibernate、JPA)配合使用。
        c.特点
            包含数据库表结构的信息。
            通常被注解(如 @Entity)标记。
            可能包含一些简单的业务逻辑,但通常不用于复杂逻辑。
        d.示例
            import jakarta.persistence.Entity;
            import jakarta.persistence.Id;

            @Entity
            public class UserEntity {
                @Id
                private Long id;
                private String name;
                private int age;

                // Getter and Setter
                public Long getId() {
                    return id;
                }

                public void setId(Long id) {
                    this.id = id;
                }

                public String getName() {
                    return name;
                }

                public void setName(String name) {
                    this.name = name;
                }

                public int getAge() {
                    return age;
                }

                public void setAge(int age) {
                    this.age = age;
                }
            }
        e.使用
            UserEntity userEntity = new UserEntity();
            userEntity.setId(1L);
            userEntity.setName("Alice");
            userEntity.setAge(25);
            userRepository.save(userEntity); // 保存到数据库
    c.DTO (Data Transfer Object)
        a.含义
            DTO(数据传输对象)是用于服务层之间(或服务与控制层之间)传递数据的对象。
            和 VO 类似,但 DTO 的重点是传输数据,而 VO 更偏向于表示视图层数据。
        b.使用场景
            用于在服务层之间传递数据,或者用于接收前端发送的数据。
            可以用于输入参数封装或 API 响应封装。
        c.特点
            通常包含业务相关的数据,但不包含业务逻辑。
            可以与 VO 或 Entity 进行数据转换。
        d.示例
            public class UserDTO {
                private String name;
                private int age;

                public UserDTO(String name, int age) {
                    this.name = name;
                    this.age = age;
                }

                public String getName() {
                    return name;
                }

                public void setName(String name) {
                    this.name = name;
                }

                public int getAge() {
                    return age;
                }

                public void setAge(int age) {
                    this.age = age;
                }
            }
        e.使用
            // Controller 接收数据
            @PostMapping("/addUser")
            public void addUser(@RequestBody UserDTO userDTO) {
                userService.saveUser(userDTO);
            }


            public void saveUser(UserDTO userDTO) {
                UserEntity userEntity = new UserEntity();
                userEntity.setName(userDTO.getName());
                userEntity.setAge(userDTO.getAge());
                userRepository.save(userEntity);
            }
    d.DAO (Data Access Object)
        a.含义
            DAO(数据访问对象)是一个用于访问数据库的对象,它封装了对数据库的增删改查操作。
            DAO 通常操作 Entity,并与数据库交互。
        b.使用场景
            用于对数据库的访问和操作。
            隐藏底层数据库访问的具体实现。
        c.特点
            通常是接口或类,定义了访问数据库的方法。
            配合持久化框架(如 MyBatis、Hibernate、JPA)使用。
        d.示例
            import org.springframework.data.jpa.repository.JpaRepository;

            public interface UserDAO extends JpaRepository<UserEntity, Long> {
                UserEntity findByName(String name);
            }
        e.使用
            @Service
            public class UserService {
                @Autowired
                private UserDAO userDAO;

                public UserEntity getUserByName(String name) {
                    return userDAO.findByName(name);
                }
            }
    e.使用场景的实际流程
        a.总结
            类型      职责                                        与数据库关系                使用场景                           简述
            VO       表示视图层数据,供前端使用                      无直接关系                  返回给前端的数据视图                 用于前端交互,表示视图层数据
            Entity   表示数据库表结构,和数据库表直接映射              直接映射(ORM 框架支持)     与数据库交互的持久化对象              与数据库表映射,表示持久化数据
            DTO      用于服务层之间传输数据或接收请求数据              无直接关系                  服务层间数据传递,或者接收/发送数据    用于服务层或 API 之间的数据传输
            DAO      负责数据库的增删改查操作,封装对数据库的访问逻辑     直接操作数据库              数据库访问和操作的具体实现           用于封装对数据库的访问逻辑
        b.数据流动
            1.前端发送请求数据(通过 DTO 封装)。
            2.Controller 层接收 DTO 并调用 Service 层。
            3.Service 层将 DTO 转换为 Entity,通过 DAO 层操作数据库。
            4.数据从数据库中查询出来后,返回为 Entity。
            5.Service 层将 Entity 转换为 VO,Controller 层将 VO 返回给前端。
        c.示例代码
            // Controller
            @RestController
            @RequestMapping("/users")
            public class UserController {
                @Autowired
                private UserService userService;

                @PostMapping
                public UserVO addUser(@RequestBody UserDTO userDTO) {
                    return userService.addUser(userDTO);
                }
            }

            // Service
            @Service
            public class UserService {
                @Autowired
                private UserDAO userDAO;

                public UserVO addUser(UserDTO userDTO) {
                    // DTO -> Entity
                    UserEntity userEntity = new UserEntity();
                    userEntity.setName(userDTO.getName());
                    userEntity.setAge(userDTO.getAge());

                    // Save to DB
                    userDAO.save(userEntity);

                    // Entity -> VO
                    return new UserVO(userEntity.getName(), userEntity.getAge());
                }
            }

            // DAO
            @Repository
            public interface UserDAO extends JpaRepository<UserEntity, Long> {
                // 默认提供了 save、findById 等方法
            }

02.build.gradle
    a.文件解析
        a.插件应用 (Plugins)
            apply plugin: java: 表明这是一个标准的 Java 项目
            apply plugin: eclipse: 用于生成 Eclipse 项目的配置文件
            apply plugin: com.gorylenko.gradle-git-properties: 一个第三方插件,作用是在打包时生成一个 git.properties 文件,包含当前 Git 仓库的分支、commit ID 等信息,非常便于版本追溯
        b.项目基本信息
            sourceCompatibility = 1.8 和 targetCompatibility = 1.8: 指定项目源代码和编译后字节码都使用 Java 8 版本。
            archivesBaseName = 'hs': 设置打包生成的 jar 文件名(不含版本号和后缀),最终会生成 hs.jar。
            tasks.withType(JavaCompile) 和 tasks.withType(Javadoc): 统一设置 Java 编译和生成 Javadoc 文档时的文件编码为 `UTF-8`,避免中文乱码。
        c.依赖管理 (Dependencies)
            a.核心依赖
                compile fileTree(dir: '../lib', include: '*.jar'): 这是项目最主要的依赖方式。它告诉 Gradle,将上一级目录 ../lib 下的所有 .jar 文件都作为编译和运行时的依赖。这是一种本地依赖管理方式,而不是从远程仓库下载。
            b.编译时依赖
                compileOnly 'javax.servlet:servlet-api:2.5': compileOnly 表示这个依赖只在编译时需要,打包时不会包含在最终的 jar 包里。这通常用于 Web 容器(如 Tomcat)已经提供了的 API,比如 servlet-api。
            c.测试依赖
                testCompile group: 'junit', name: 'junit', version: '4.12': testCompile 表示这个依赖只在运行测试代码时需要。从你的截图来看,junit:4.12 被正确识别并下载了。
        d.仓库配置 (Repositories)
            mavenCentral(): 使用 Maven 的中央仓库,这是最常用的公共仓库。
            maven { url 'http://icscrepo1.icsc.com.tw/repository/maven-public' }: 指定了一个公司的私有 Maven 仓库。Gradle 会按顺序从这些仓库查找依赖。
    b.回答你的问题
        a.我可以不可以把 http://icscrepo1.icsc.com.tw/repository/maven-public 换成阿里源?
            可以,但可能没有意义。
            原因: 你的项目核心依赖是通过 compile fileTree(dir: '../lib', include: '*.jar') 从本地 ../lib 文件夹加载的,根本不经过 Maven 仓库。
            例外情况: 只有 junit 和 servlet-api 这两个依赖会从远程仓库下载。mavenCentral() 已经包含了它们,所以即使你访问不了那个私有仓库,项目也能正常构建。
            结论: 换成阿里源可以提高 junit 等公共库的下载速度,但对你的项目影响不大,因为大部分 jar 包都在本地。如果你想替换,可以这样做:
                repositories {
                    // 使用阿里云镜像
                    maven { url 'https://maven.aliyun.com/repository/public' }
                    // 保留 mavenCentral 作为备用
                    mavenCentral()
                    // 公司的私有仓库,如果不再需要或者访问不了,可以注释或删除
                    // maven { url 'http://icscrepo1.icsc.com.tw/repository/maven-public' }
                }
        b.这个jar包可不可以直接下载得到?
            你说的是哪个 jar 包?
            如果指 ../lib 目录下的 jar 包: 这些 jar 包已经是下载好的、存在于你本地文件系统的文件了。你不需要再下载。这个项目的前提就是你已经通过某种方式(比如从公司代码库、或同事那里拷贝)获取了 lib 文件夹。
            如果指 junit 和 servlet-api.jar: 这些是由 Gradle 自动从远程仓库(`mavenCentral()` 或你配置的其他仓库)下载的。它们会缓存在你的用户目录下的 .gradle/caches 文件夹里。
            如果指 hs.jar: 这是你这个项目自己打包生成的,不是下载的。你可以在项目目录下运行 ./gradlew build 或 ./gradlew jar 命令来生成它。
        c.我可以不可以把 log4j 换成本地 jar 包?
            完全可以,这正是你项目现在的依赖管理方式!
            你的项目目前没有通过 Maven 坐标来声明 log4j 的依赖。如果你需要使用 log4j,操作步骤如下:
            1. 下载 Log4j 的 jar 包: 去 Log4j 官网或者 Maven 仓库网站(如 [mvnrepository.com](https://mvnrepository.com/artifact/log4j/log4j))下载你需要的 log4j 版本的 jar 文件(例如 log4j-1.2.17.jar)。
            2. 放入本地目录: 将下载好的 log4j-1.2.17.jar 文件放入项目的 ../lib 文件夹中。
            3. 刷新 Gradle 项目: 在 IDEA 中刷新 Gradle 项目。
            因为你的 build.gradle 文件里有 compile fileTree(dir: '../lib', include: '*.jar') 这行配置,Gradle 会自动扫描到 ../lib 目录下的新 jar 包并将其加入到项目的依赖中。
    c.总结与建议
        a.清理 build.gradle
            你的配置文件有大量重复内容,建议清理成一个干净的版本,避免混淆。
        b.理解依赖方式
            你的项目是混合依赖模式:大部分核心库依赖本地 ../lib 文件夹,少数(如 junit)依赖远程 Maven 仓库。
        c.管理本地依赖
            任何你想添加的第三方库(如 log4j),只需要将它的 jar 包放到 ../lib 目录下即可。这是这个项目预设的工作方式。
        d.仓库配置
            替换成阿里源可以,但对你这个项目影响不大。主要作用是加速下载 junit 等少量公共库。

3 后端管理

3.1 生产排查

00.程序位置
    a.查看pid、查看程序目录
        ps -ef | grep java                                                      --查看进程
        ------------------------------------------------------------------------
        lsof -i:9981                                                            --命令1:查找9981对应pid
        ps -ef | grep java | grep 1723891                                       --命令2:查找pid对应java
        tail -f jeecgboot-2025-02-06.0.log                                      --命令3:查看日志
        ------------------------------------------------------------------------
        ps -ef | grep $(lsof -ti:9981)                                          --组合命令
        ps -ef | grep $(lsof -ti:9981) | grep java                              --组合命令
        kill -9 [pid]
    b.curl测试
        curl --location 'http://172.17.10.201:9981/jeecg-boot/mo/isp/getIndicator' \
        --header 'Content-Type: application/json' \
        --data '{
            "indicatorDate": 202501
        }'
    c.前端日志
        tail -f /var/log/nginx/access.log                                       --访问日志
        tail -f /var/log/nginx/error.log                                        --错误日志
    d.后端日志
        cd /root/myprojects/logs/                                               --每天日志
        tail -f jeecgboot-2025-02-06.0.log                                      --实时日志
    e.停止、启动
        ps -ef | grep java                                                      --查看进程
        kill [pid]                                                              --优雅停机
        ------------------------------------------------------------------------
        sh /root/hr-project/backend-start.sh start                              --启动
        tail -f /root/hr-project/hr-backend/jeecg-module-system/jeecg-system-start/logs/jeecgboot-2025-03-02.0.log  --日志

01.查看系统
    a.使用uname -a命令
        uname -a                                                                --显示系统内核版本
    b.查看发行版特定的版本文件:
        cat /etc/redhat-release                                                 --适用于RedHat/CentOS系统
        cat /etc/debian_version                                                 --适用于Debian系统
        cat /etc/ubuntu-release                                                 --适用于Ubuntu系统
    c.使用hostnamectl命令
        hostnamectl                                                             --查看主机名和其他信息(适用于systemd系统)
    d.查看性能1
        lscpu                                                                   --查看 CPU 信息
        cat /proc/cpuinfo                                                       --查看详细的 CPU 信息
        free -h                                                                 --查看内存信息
        cat /proc/meminfo                                                       --查看详细的内存信息
        ------------------------------------------------------------------------
        df -h                                                                   --查看磁盘使用情况
        top                                                                     --查看系统负载和性能
        vmstat                                                                  --查看系统的虚拟内存、进程、CPU 活动等信息
        ------------------------------------------------------------------------
        dmesg | grep -i vmware                                                  --查看是否vmware虚拟化机器
        lscpu | grep -i hypervisor                                              --查看是否vmware虚拟化机器
    e.查看性能2
        echo "=== CPU 信息 ==="
        lscpu | grep -E '^CPU\(s\):|^Model name:|^Socket\(s\):|^Thread\(s\) per core:|^Core\(s\) per socket:'
        echo -e "\n=== 内存信息 ==="
        free -h
        echo -e "\n=== 磁盘信息 ==="
        df -h --total | grep 'total'
        echo -e "\n=== 显卡信息 ==="
        lspci | grep -i --color 'vga\|3d\|2d'

02.查看ip端口
    a.netstat命令
        yum install net-tools                                                   --安装 net-tools
        netstat -tunlp | grep 9981                                              --查看具体某个端
    b.ss命令
        ss -tunlp | grep 9981                                                   --查看具体某个端
    c.lsof命令
        yum install lsof                                                        --安装 lsof
        lsof -i                                                                 --查看所有网络连接
        lsof -i:9981
    d.firewall-cmd命令
        firewall-cmd --list-ports                                               --查看所有开放的端口
        firewall-cmd --query-port=9981/tcp                                      --查询某个端口是否开放

03.防火墙
    a.systemctl
        systemctl stop firewalld                                                --关闭防火墙
        systemctl disable firewalld                                             --禁止开机自启
        systemctl status firewalld                                              --防火墙状态
        systemctl start firewalld                                               --开启防火墙
        systemctl enable firewalld                                              --开启开机自启
    b.firewall-cmd
        firewall-cmd --reload                                                   --重启防火墙
        firewall-cmd --zone=public --add-port=3306/tcp --permanent              --开放3306端口
        firewall-cmd --zone=public --remove-port=3306/tcp --permanent           --关闭3306端口
        firewall-cmd --zone=public --list-ports                                 --查看开放端口
        firewall-cmd --zone=public --query-port=3306/tcp                        --查看具体端口
        firewall-cmd --state                                                    --查看防火墙状态

04.nginx
    a.查看nginx配置文件
        cd /etc/nginx/
        ls
        cat nginx.conf
    b.查看conf.d目录下的配置
        cd /etc/nginx/conf.d/
        ls
        cat *.conf
    c.查看sites-enabled目录(如果存在)
        cd /etc/nginx/sites-enabled/
        ls
    d.检查nginx配置是否正确
        nginx -t
    e.查看nginx进程
        ps -ef | grep nginx
    f.访问日志
        tail -f /var/log/nginx/access.log
    g.错误日志
        tail -f /var/log/nginx/error.log

05.自定义DNS
    a.编辑hosts文件
        vi /etc/hosts
        # 添加格式如下:
        # IP地址      域名
        192.168.1.100   example.com
        192.168.1.101   test.com
    b.修改DNS服务器
        vi /etc/resolv.conf
        # 添加或修改nameserver
        nameserver 8.8.8.8
        nameserver 114.114.114.114
    d.刷新DNS缓存
        systemctl restart NetworkManager
        # 或
        nscd -i hosts  # 如果安装了nscd
    e.测试DNS解析
        nslookup example.com
        ping example.com
    f.查看DNS是否生效
        # 查看当前DNS服务器
        cat /etc/resolv.conf
        # 测试DNS解析
        dig @8.8.8.8 example.com

06.系统编码
    a.linux
        [root@localhost hr-backend]# stat /etc/locale.conf
          File: /etc/locale.conf
          Size: 19              Blocks: 8          IO Block: 4096   regular file
        Device: fd00h/64768d    Inode: 67431224    Links: 1
        Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
        Access: 2025-03-08 10:50:06.920797929 +0800
        Modify: 2022-01-11 10:28:26.559915054 +0800
        Change: 2022-01-11 10:28:26.559915054 +0800
         Birth: 2022-01-11 10:28:26.558915052 +0800
        -----------------------------------------------------------------------------------------------------
        通常情况下,Java会使用操作系统的默认字符编码
        Access 字段表示文件的最后访问时间,这是指文件内容最后一次被读取的时间,而不是文件被修改或更改的时间
    b.查看编码
        $ locale
        $ cat /etc/locale.conf
        -----------------------------------------------------------------------------------------------------
        LANG=C.UTF-8      不提供特定语言的特性;用默认的、简单的格式            晋钢服务器,默认编码
        LANG=en_US.UTF-8  美国英语的语言特性;美国的习惯格式化日期、时间、货币   腾讯云服务器,默认编码
    c.查看系统安装的字符编码
        $ locale -a
        -----------------------------------------------------------------------------------------------------
        locale: Cannot set LC_CTYPE to default locale: No such file or directory
        locale: Cannot set LC_MESSAGES to default locale: No such file or directory
        locale: Cannot set LC_COLLATE to default locale: No such file or directory
        C
        C.utf8
        POSIX
        zh_CN
        zh_CN.gb18030
        zh_CN.gbk
        zh_CN.utf8
        zh_HK
        zh_HK.utf8
        zh_SG
        zh_SG.gbk
        zh_SG.utf8
        zh_TW
        zh_TW.euctw
        zh_TW.utf8
    d.生成编码
        # 打开 /etc/locale.gen 文件
        vi /etc/locale.gen
        # 确保文件中包含以下行(如果没有,请添加)
        C.UTF-8 UTF-8
        # 生成语言环境
        sudo locale-gen
        # 设置默认语言环境
        sudo update-locale LANG=C.UTF-8
    e.修改编码
        # 临时更改
        export LANG=C.UTF-8
        # 永久更改
        vi /etc/locale.conf
        LANG=C.UTF-8
        # 更新当前会话
        source /etc/locale.conf
    f.其他说明
        a.如果要设置中文版的字体编,在每个文件中增加以下内容
            # cat /etc/profile.d/locale.sh
            # vim /etc/profile.d/locale.sh
            export LC_CTYPE=zh_CN.UTF-8
            export LC_ALL=zh_CN.UTF-8

            # cat /etc/locale.conf
            # vim /etc/locale.conf
            LANG=zh_CN.UTF-8

            # cat /etc/sysconfig/i18n
            # vim /etc/sysconfig/i18n
            LANG=zh_CN.UTF-8

            # cat /etc/environment
            # vim /etc/environment
            LANG=zh_CN.UTF-8
            LC_ALL=zh_CN.UTF-8

            # source /etc/profile.d/locale.sh
            # source /etc/locale.conf
            # source /etc/sysconfig/i18n
            # source /etc/environment
        b.如果要设置英文版的字体编码。在每个文件中增加以下内容
            # cat /etc/profile.d/locale.sh
            # vim /etc/profile.d/locale.sh
            export LC_CTYPE=en_US.UTF-8
            export LC_ALL=en_US.UTF-8

            # cat /etc/locale.conf
            # vim /etc/locale.conf
            LANG=en_US.UTF-8

            # cat /etc/sysconfig/i18n
            # vim /etc/sysconfig/i18n
            LANG=en_US.UTF-8

            # cat /etc/environment
            # vim /etc/environment
            LANG=en_US.UTF-8
            LC_ALL=en_US.UTF-8

            # source /etc/profile.d/locale.sh
            # source /etc/locale.conf
            # source /etc/sysconfig/i18n
            # source /etc/environment
        c.临时更改
            export LANG=C.UTF-8
            export LANG=zh_CN.utf8

3.2 接入三方

00.关系图
                      职位表(Postion)
                          ↓
    三方表(Third)<---> 用户(User) <---> 角色(Role) <---> 菜单(Menu)
                          ↑                ↑             ↑
                      部门(Depart)------------------------
    ---------------------------------------------------------------------------------------------------------
    IC13060002
    123456
    ---------------------------------------------------------------------------------------------------------
    J057240
    qwER159263!

00.关系表
               表名                            新增          更新           删除
    组织表     sys_depart                      √             √              √
    组织表     sys_depart_permission
    组织表     sys_depart_role                 后续版本添加
    组织表     sys_depart_role_permission      后续版本添加
    组织表     sys_depart_role_user            后续版本添加
    菜单表     sys_permission
    菜单表     sys_permission_data_rule
    菜单表     sys_permission_v2
    职位表     sys_position                    √             √              √
    角色表     sys_role                        系统维护        系统维护        系统维护
    角色表     sys_role_index
    角色表     sys_role_permission
    三方表     sys_third_account               √             √              √
    用户表     sys_user                        √             √              √
    用户表     sys_user_role,关联角色          √,固定user   √,固定user     √
    用户表     sys_user_depart,关联部门        √             √              √
    用户表     sys_user_agent,关联代理人,表字段有代理人、所属部门、所属公司
    用户表     sys_user_position,关联职位,表不存在,冗余到sys_user的post字段,存储方式为【处长助理】或【devleader,总经理】
    用户表     sys_user_third,关联三方,表不存在,信息冗余到sys_third_account

01.第1种:共同数据,找到 jobNo/username 相同,但 age/sex/birthday 至少有一个不同的元素,待更新
    sys_user表                                               【待更新字段】                  user表(云之家)【对应字段】
    id                   主键id
    username             登录账号                                                          jobNo
    realname             真实姓名                               √                          name
    password             密码
    salt                 md5密码盐
    avatar               头像                                  √                          photoUrl
    birthday             生日                                  √                          birthday
    sex                  性别(0-默认未知,1-男,2-女)              √                          gender
    email                电子邮件                               √                          email
    phone                电话                                  √                          phone
    org_code             登录会话的机构编码                       √                          org_id
    status               性别(1-正常,2-冻结)
    del_flag             删除状态(0-正常,1-已删除)
    third_id             第三方登录的唯一标识                     √                          openid
    third_type           第三方类型
    activiti_sync        同步工作流引擎(1-同步,0-不同步)
    work_no              工号,唯一键                           √                          jobNo
    post                 职务,关联职务表                        √                          jobTitle
    telephone            座机号
    create_by            创建人
    create_time          创建时间
    update_by            更新人                                √                          固定admin
    update_time          更新时间                              √                          yyyy-MM-dd HH:mm:ss
    user_identity        身份(1普通成员 2上级)
    depart_ids           负责部门
    rel_tenant_ids       多租户标识
    client_id            设备ID
    file_no              档案号                                √                          fileNo

    sys_user_depart表                                         【待更新字段】
    id                   id
    user_id              用户id,对应sys_user表中的id
    dep_id               部门id                                √

    sys_third_account表                                       【待更新字段】               user表(云之家)【对应字段】
    id                   编号
    sys_user_id          第三方登录id,对应sys_user表中的id
    avatar               头像                                  √                        photoUrl
    status               状态(1-正常,2-冻结)
    del_flag             删除状态(0-正常,1-已删除)
    realname             真实姓名                               √                        name
    third_user_uuid      第三方账号
    third_user_id        第三方app用户账号,对应sys_user表中的id    √                       openid
    create_by            创建人登录名称
    create_time          创建日期
    update_by            更新人登录名称                          √                        固定admin
    update_time          更新日期                               √                        yyyy-MM-dd HH:mm:ss
    third_type           登录来源

02.第2种:只在 本地用户 存在:待删除。
    方案1:将 用户 中的 del_flag 设置为 1,逻辑删除,放到回收站
    方案2:将该用户的信息全部删除
    【本次采用方案1,冗余信息】。
    【不采用方案2,不冗余信息】。理由:云之家 会与 本系统 用户信息 强同步,无法创建额外比如 test1、test2 等本想系统独有的用户。
    注意,两种方案,请设置白名单。排除内容大致为 admin,test1,test2,jeecg,ceshi

03.第3种:只在 云之家数据 存在:待新增
    sys_user表                                               【待新增字段】                           user表(云之家)【对应字段】
    id                   主键id                                -,随机19位
    username             登录账号                               -                                    jobNo
    realname             真实姓名                               √                                    name
    password             密码                                  -,默认123456
    salt                 md5密码盐                             -,随机
    avatar               头像                                  √                                    photoUrl
    birthday             生日                                  √                                    birthday
    sex                  性别(0-默认未知,1-男,2-女)              √                                    gender
    email                电子邮件                               √                                    email
    phone                电话                                  √                                    phone
    org_code             登录会话的机构编码                       √                                    org_id
    status               性别(1-正常,2-冻结)                     -,默认1-正常
    del_flag             删除状态(0-正常,1-已删除)                -,默认0-正常
    third_id             第三方登录的唯一标识                     √                                    openid
    third_type           第三方类型                             -,默认yunzhijia
    activiti_sync        同步工作流引擎(1-同步,0-不同步)           -,默认0-不同步
    work_no              工号,唯一键                           √                                    jobNo
    post                 职务,关联职务表                        √                                    jobTitle
    telephone            座机号                                null
    create_by            创建人                                -,默认admin
    create_time          创建时间                              -,当前时间
    update_by            更新人                                √                                    固定admin
    update_time          更新时间                              √                                     yyyy-MM-dd HH:mm:ss
    user_identity        身份(1普通成员 2上级)                  -,默认1普通成员
    depart_ids           负责部门                              null
    rel_tenant_ids       多租户标识                             null
    client_id            设备ID                                null
    file_no              档案号                                √                                    fileNo

    sys_user_depart表                                         【待新增字段】                          user表(云之家)【对应字段】
    id                   id                                   -,随机19位
    user_id              用户id,对应sys_user表中的id            -,对应sys_user表中的id,随机19位
    dep_id               部门id                                √

    sys_third_account表                                       【待新增字段】                          user表(云之家)【对应字段】
    id                   编号                                  -,随机19位
    sys_user_id          第三方登录id,对应sys_user表中的id        -,对应sys_user表中的id,随机19位
    avatar               头像                                  √                                   photoUrl
    status               状态(1-正常,2-冻结)                     -,默认1-正常
    del_flag             删除状态(0-正常,1-已删除)                -,默认0-正常
    realname             真实姓名                               √                                   name
    third_user_uuid      第三方账号                              -,随机19位
    third_user_id        第三方app用户账号,对应sys_user表中的id    √                                  openid
    create_by            创建人登录名称                          -,固定admin
    create_time          创建日期                               -,yyyy-MM-dd HH:mm:ss
    update_by            更新人登录名称                          √                                   固定admin
    update_time          更新日期                               √                                   yyyy-MM-dd HH:mm:ss
    third_type           登录来源                               -,固定yunzhijia

3.3 默认日志

01.依赖
    a.spring-boot-starter
        <!--
        spring-boot-starter 是 Spring Boot 的核心依赖包,包含以下模块:
        1. Spring Framework 的基础模块(如 spring-beans、spring-context)。
        2. 日志功能(默认使用 SLF4J 和 Logback)。
        3. 提供 Spring Boot 应用运行所需的最小依赖集合。
        -->
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter</artifactId>
          <version>2.6.6</version>
        </dependency>
    b.slf4j
        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-api</artifactId>
          <version>1.7.30</version>
        </dependency>
        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-log4j12</artifactId>
          <version>1.7.30</version>
          <scope>provided</scope>
        </dependency>
    c.log4j-to-slf4j
        <dependency>
          <groupId>org.apache.logging.log4j</groupId>
          <artifactId>log4j-to-slf4j</artifactId>
          <version>2.11.2</version>
          <optional>true</optional>
          <scope>test</scope>
        </dependency>

02.logback
    a.引入依赖
        a.说明
            如果你在一个非Spring Boot项目中使用Logback,你需要手动添加Logback的依赖
        b.依赖
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>1.2.11</version> <!-- 使用最新版本 -->
            </dependency>
    b.在Spring Boot项目中使用
        a.默认集成
            在Spring Boot项目中,你通常不需要显式添加Logback依赖,因为`spring-boot-starter-logging`已经包含了它。
        b.日志使用示例
            import org.slf4j.Logger;
            import org.slf4j.LoggerFactory;
            import org.springframework.stereotype.Service;

            @Service
            public class MyService {

                private static final Logger logger = LoggerFactory.getLogger(MyService.class);

                public void doSomething() {
                    logger.trace("This is a TRACE message.");
                    logger.debug("This is a DEBUG message.");
                    logger.info("This is an INFO message.");
                    logger.warn("This is a WARN message.");
                    logger.error("This is an ERROR message.");
                }
            }
    c.配置Logback
        a.配置文件位置
            Logback的配置通常通过`logback-spring.xml`(Spring Boot推荐)或`logback.xml`文件进行。这些文件应该放在`src/main/resources`目录下。
        b.简单的`logback-spring.xml`示例
            <?xml version="1.0" encoding="UTF-8"?>
            <configuration>

                <!-- 控制台输出 -->
                <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
                    <encoder>
                        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
                    </encoder>
                </appender>

                <!-- 文件输出 -->
                <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
                    <file>logs/my-app.log</file>
                    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                        <!-- 日志文件按天归档 -->
                        <fileNamePattern>logs/my-app.%d{yyyy-MM-dd}.log</fileNamePattern>
                        <!-- 保留最近30天的日志 -->
                        <maxHistory>30</maxHistory>
                    </rollingPolicy>
                    <encoder>
                        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
                    </encoder>
                </appender>

                <!-- 根日志配置 -->
                <root level="INFO">
                    <appender-ref ref="CONSOLE"/>
                    <appender-ref ref="FILE"/>
                </root>

                <!-- 特定包的日志级别配置 -->
                <logger name="com.example.myapp" level="DEBUG" additivity="false">
                    <appender-ref ref="CONSOLE"/>
                    <appender-ref ref="FILE"/>
                </logger>

            </configuration>
    d.配置说明
        a.根元素
            `<configuration>`: Logback配置的根元素。
        b.日志输出目的地
            `<appender>`: 定义日志输出目的地。
            a.属性
                `name`: Appender的名称。
                `class`: Appender的实现类。
            b.实现类
                `ch.qos.logback.core.ConsoleAppender`: 输出到控制台。
                `ch.qos.logback.core.rolling.RollingFileAppender`: 输出到文件,并支持文件滚动(按大小、时间等)。
        c.日志格式
            `<encoder>`: 定义日志的格式。
            a.格式模式
                `pattern`: 日志输出的格式模式。
                - `%d{yyyy-MM-dd HH:mm:ss.SSS}`: 日期和时间。
                - `[%thread]`: 线程名称。
                - `%-5level`: 日志级别(左对齐,占5个字符)。
                - `%logger{36}`: 日志记录器名称(最多36个字符)。
                - `%msg`: 日志消息。
                - `%n`: 换行符。
        d.滚动策略
            `<rollingPolicy>`: `RollingFileAppender`用于配置日志文件的滚动策略。
            a.策略类型
                `ch.qos.logback.core.rolling.TimeBasedRollingPolicy`: 基于时间的滚动策略。
            b.文件名模式
                `fileNamePattern`: 滚动后的文件名模式。
            c.归档文件数量
                `maxHistory`: 保留的归档文件数量。
        e.根日志记录器
            `<root>`: 根日志记录器,所有未明确配置的日志都将使用它的配置。
            a.日志级别
                `level`: 根日志记录器的日志级别(TRACE, DEBUG, INFO, WARN, ERROR)。
            b.引用Appender
                `<appender-ref ref="CONSOLE"/>`: 引用之前定义的Appender。
        f.特定包或类的日志记录器
            `<logger>`: 配置特定包或类的日志记录器。
            a.名称
                `name`: 包或类的名称。
            b.日志级别
                `level`: 该日志记录器的日志级别。
            c.传递设置
                `additivity="false"`: 如果设置为`false`,则该日志记录器不会将日志事件传递给父记录器(包括根记录器)的Appender。这可以避免重复输出。
    e.在`application.properties`或`application.yml`中配置
        a.application.properties
            logging.level.root=INFO
            logging.level.com.example.myapp=DEBUG
            logging.file.name=logs/spring-boot-app.log
        b.application.yml
            logging:
              level:
                root: INFO
                com.example.myapp: DEBUG
              file:
                name: logs/spring-boot-app.log
    f.注意事项
        a.配置优先级
            `logback-spring.xml`的配置优先级高于`application.properties`/`application.yml`。
            如果两者都存在,`logback-spring.xml`将会覆盖`application.properties`/`application.yml`中的相同配置。
        b.复杂配置
            对于更复杂的Logback配置,建议使用`logback-spring.xml`文件。

03.slf4j
    a.SLF4J简介
        a.日志门面
            SLF4J(Simple Logging Facade for Java)本身并不是一个具体的日志实现,而是一个日志门面。
            这意味着你不能“使用 SLF4J 日志”来直接输出日志,而是要通过 SLF4J 提供的 API 来调用底层具体的日志实现
            (比如 Logback、Log4j2、java.util.logging 等)来完成日志的输出。
        b.接口层
            SLF4J 只是一个接口层,它允许你编写与具体日志实现无关的代码。
    b.引入SLF4J API依赖和具体的日志实现依赖
        a.对于Maven项目
            a.SLF4J API
                <dependency>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-api</artifactId>
                    <version>1.7.36</version> <!-- 使用最新版本 -->
                </dependency>
            b.选择一个日志实现 (以 Logback 为例)
                <dependency>
                    <groupId>ch.qos.logback</groupId>
                    <artifactId>logback-classic</artifactId>
                    <version>1.2.11</version> <!-- 使用最新版本 -->
                </dependency>
                `logback-classic` 包含了 `logback-core` 和 `slf4j-api`,所以如果你只使用 Logback,通常只需要引入 `logback-classic`。
        b.对于Spring Boot项目
            Spring Boot 默认就集成了 SLF4J 和 Logback。你通常不需要手动添加这些依赖,
            因为 `spring-boot-starter` 或 `spring-boot-starter-web` 等已经包含了 `spring-boot-starter-logging`,
            而 `spring-boot-starter-logging` 就包含了 SLF4J API 和 Logback。
    c.在Java代码中使用SLF4J API
        a.获取Logger实例
            import org.slf4j.Logger;
            import org.slf4j.LoggerFactory;

            public class MyApplication {

                private static final Logger logger = LoggerFactory.getLogger(MyApplication.class);

                public static void main(String[] args) {
                    // 记录不同级别的日志
                    logger.trace("This is a TRACE message."); // 最详细的日志,用于调试
                    logger.debug("This is a DEBUG message."); // 调试信息
                    logger.info("This is an INFO message.");   // 重要的业务流程信息
                    logger.warn("This is a WARN message.");   // 潜在的问题或不期望的事件
                    logger.error("This is an ERROR message."); // 错误信息,通常伴随异常

                    // 使用参数化日志,避免字符串拼接的性能开销和潜在问题
                    String userName = "Alice";
                    int age = 30;
                    logger.info("User {} with age {} logged in.", userName, age);

                    // 记录异常
                    try {
                        int result = 10 / 0; // 制造一个 ArithmeticException
                    } catch (ArithmeticException e) {
                        logger.error("An error occurred during division.", e);
                    }
                }
            }
    d.关键点
        a.核心日志接口
            `org.slf4j.Logger`: 这是 SLF4J 提供的核心日志接口。
        b.工厂类
            `org.slf4j.LoggerFactory`: 这是 SLF4J 提供的工厂类,用于获取 `Logger` 实例。
        c.日志级别
            SLF4J 支持以下日志级别(从低到高):
            a.TRACE
                最详细的日志信息,通常用于跟踪程序的执行流程。
            b.DEBUG
                调试信息,比 TRACE 级别略高。
            c.INFO
                重要的业务流程信息,默认情况下通常显示此级别及以上。
            d.WARN
                警告信息,表示可能存在问题,但不影响程序的正常运行。
            e.ERROR
                错误信息,表示程序运行中出现了错误,可能需要人工干预。
        d.参数化日志
            使用 `logger.info("User {} with age {} logged in.", userName, age);` 这种方式来记录日志,而不是 `logger.info("User " + userName + " with age " + age + " logged in.");`。
            a.优点
                i.性能优化
                    当日志级别低于当前配置时,参数化日志不会进行字符串拼接,从而减少了不必要的性能开销。
                ii.避免不必要的计算
                    如果日志不被记录,参数化日志中的参数表达式也不会被计算。
                iii.更清晰的日志
                    避免了复杂的字符串拼接,使日志代码更易读。
        e.记录异常
            当记录异常时,将异常对象作为最后一个参数传递给日志方法,这样日志实现就可以打印出完整的堆栈信息。
    e.配置底层日志实现 (以 Logback 为例)
        a.配置文件
            虽然你使用 SLF4J API 编写代码,但你仍然需要配置底层的日志实现(例如 Logback)来控制日志的输出格式、目的地(控制台、文件、数据库等)、滚动策略、日志级别等。
        b.配置文件位置
            对于 Logback,你通常会在 `src/main/resources` 目录下创建一个 `logback.xml` 或 `logback-spring.xml` 文件。这个文件的内容与之前介绍的配置方式是相同的。

04.logback-spring.xml:只打印SQL日志
    a.依赖
        <!--p6spy-->
        <dependency>
            <groupId>p6spy</groupId>
            <artifactId>p6spy</artifactId>
            <version>3.9.1</version>
        </dependency>
    b.开启p6spy
        spring:
          datasource:
            druid:
              stat-view-servlet:  # Druid监控servlet配置
                enabled: true  # 启用监控servlet
                loginUsername: admin  # 登录用户名
                loginPassword: 123456  # 登录密码
                allow:  # 允许访问的IP
              web-stat-filter:
                enabled: true  # 启用Web监控统计
            dynamic:
              p6spy: true  # 默认false,建议线上关闭                                         --只需开启此行
              druid:  # Druid连接池配置
                initial-size: 5  # 初始连接数
                min-idle: 5  # 最小空闲连接
                maxActive: 20  # 最大活动连接
                maxWait: 60000  # 获取连接等待超时时间
                timeBetweenEvictionRunsMillis: 60000  # 间隔多久检测一次空闲连接
                minEvictableIdleTimeMillis: 300000  # 空闲连接最小生存时间
                validationQuery: SELECT 1 FROM DUAL  # 验证查询
                testWhileIdle: true  # 申请连接的时候检测空闲时间
                testOnBorrow: false  # 申请连接时执行validationQuery检测
                testOnReturn: false  # 归还连接时执行validationQuery检测
                poolPreparedStatements: true  # 开启PSCache
                maxPoolPreparedStatementPerConnectionSize: 20  # 每个连接上PSCache的大小
                filters: stat,wall,slf4j  # 启用的过滤器
                connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000  # 连接属性
              datasource:
                master:  # 主数据源
                  url: jdbc:mysql://127.0.0.1:3307/pisces-boot-parent?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
                  username: root
                  password: 123456
                  driver-class-name: com.mysql.cj.jdbc.Driver
                eas-datasource:  # EAS数据源
                  url: jdbc:oracle:thin:@172.17.9.131:1521:jgdb
                  username: java_report_user
                  password: nPYCgZuIDIqXBVFF
                  driver-class-name: oracle.jdbc.driver.OracleDriver
    c.开启MP的log-impl
        mybatis-plus:
          # mapper-locations:SQL映射文件的加载问题
          # cn.myslayers.config.mybatis.MybatisPlusSaasConfig:Mapper接口的注册问题
          mapper-locations: classpath*:cn/myslayers/**/mapper/xml/*.xml
          global-config:
            banner: false
            db-config:
              #主键类型  0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
              id-type: ASSIGN_ID
              # 默认数据库表下划线命名
              table-underline: true
          configuration:
            # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
            log-impl: org.apache.ibatis.logging.stdout.StdOutImpl                         --只需开启此行
            # 返回类型为Map,显示null对应的字段
            call-setters-on-nulls: true
    d.logback-spring.xml,仅打印sql
        <?xml version="1.0" encoding="UTF-8"?>
        <configuration debug="false">
            <property name="LOG_HOME" value="../logs"/>

            <!--定义日志文件的存储地址 -->
            <appender class="ch.qos.logback.core.ConsoleAppender" name="STDOUT">
                <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                    <!-- 20250420,移除 P6Spy 日志中的 Prepared SQL 行,保留换行 -->
                    <pattern>%highlight(%d{yyyy-MM-dd HH:mm:ss.SSS}) [%thread] %highlight(%-5level) %cyan(%logger{50}:%L) - %replace(%msg){'\n[^\n]+(?=\n.*$)', ''}%n</pattern>

        <!--            &lt;!&ndash; 20250418,针对p6spy特殊设置的输出格式 &ndash;&gt;-->
        <!--            <pattern>%highlight(%d{yyyy-MM-dd HH:mm:ss.SSS}) [%thread] %highlight(%-5level) %cyan(%logger{50}:%L) - %msg%n</pattern>-->

        <!--            &lt;!&ndash; 格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符&ndash;&gt;-->
        <!--            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n</pattern>-->
                </encoder>
                <!-- 添加过滤器,屏蔽包含 QRTZ_ 的日志 -->
                <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
                    <evaluator>
                        <expression>return message.contains("QRTZ_");</expression>
                    </evaluator>
                    <!-- 不匹配时继续处理 -->
                    <OnMismatch>NEUTRAL</OnMismatch>
                    <!-- 匹配时拒绝 -->
                    <OnMatch>DENY</OnMatch>
                </filter>
            </appender>

            <!--<property name="COLOR_PATTERN" value="%black(%contextName-) %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta( %replace(%caller{1}){'\t|Caller.{1}0|\r\n', ''})- %gray(%msg%xEx%n)" />-->
            <!-- 控制台输出 -->
            <appender class="ch.qos.logback.core.rolling.RollingFileAppender" name="FILE">
                <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                    <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
                    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n</pattern>
                </encoder>
                <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                    <!--日志文件输出的文件名 -->
                    <FileNamePattern>${LOG_HOME}/piscesboot-%d{yyyy-MM-dd}.%i.log</FileNamePattern>
                    <!--日志文件保留天数 -->
                    <MaxHistory>30</MaxHistory>
                    <maxFileSize>10MB</maxFileSize>
                </rollingPolicy>
            </appender>

            <!-- 按照每天生成日志文件 -->
            <appender class="ch.qos.logback.core.FileAppender" name="HTML">
                <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
                    <layout class="ch.qos.logback.classic.html.HTMLLayout">
                        <pattern>%p%d%msg%M%F{32}%L</pattern>
                    </layout>
                </encoder>
                <file>${LOG_HOME}/error-log.html</file>
                <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                    <!--设置日志级别,过滤掉info日志,只输入error日志-->
                    <level>ERROR</level>
                </filter>
            </appender>

            <!-- 生成 error html格式日志开始 -->
            <appender class="ch.qos.logback.core.rolling.RollingFileAppender" name="FILE_HTML">
                <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
                    <layout class="ch.qos.logback.classic.html.HTMLLayout">
                        <pattern>%p%d%msg%M%F{32}%L</pattern>
                    </layout>
                </encoder>
                <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                    <!--日志文件输出的文件名 -->
                    <FileNamePattern>${LOG_HOME}/piscesboot-%d{yyyy-MM-dd}.%i.html</FileNamePattern>
                    <!--日志文件保留天数 -->
                    <MaxFileSize>10MB</MaxFileSize>
                    <MaxHistory>30</MaxHistory>
                </rollingPolicy>
            </appender>
            <!-- 生成 error html格式日志结束 -->

            <!-- 每天生成一个html格式的日志开始 -->
            <logger level="TRACE" name="com.apache.ibatis"/>
            <!-- 每天生成一个html格式的日志结束 -->

            <!--myibatis log configure -->
            <logger level="DEBUG" name="java.sql.Connection"/>
            <logger level="DEBUG" name="java.sql.Statement"/>
            <logger level="DEBUG" name="java.sql.PreparedStatement"/>

            <!-- 日志输出级别 -->
            <root level="INFO">
                <appender-ref ref="STDOUT"/>
                <appender-ref ref="FILE"/>
                <appender-ref ref="HTML"/>
                <appender-ref ref="FILE_HTML"/>
            </root>

        </configuration>
    e.logback-spring_bak.xml,默认jeecg配置
        <?xml version="1.0" encoding="UTF-8"?>
        <configuration debug="false">
          <property name="LOG_HOME" value="../logs"/>

          <!--定义日志文件的存储地址 -->
          <appender class="ch.qos.logback.core.ConsoleAppender" name="STDOUT">
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
              <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符
              <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n</pattern>-->
              <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{50}:%L) - %msg%n</pattern>
            </encoder>
          </appender>

          <!--<property name="COLOR_PATTERN" value="%black(%contextName-) %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta( %replace(%caller{1}){'\t|Caller.{1}0|\r\n', ''})- %gray(%msg%xEx%n)" />-->
          <!-- 控制台输出 -->
          <appender class="ch.qos.logback.core.rolling.RollingFileAppender" name="FILE">
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
              <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
              <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}:%L - %msg%n</pattern>
            </encoder>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
              <!--日志文件输出的文件名 -->
              <FileNamePattern>${LOG_HOME}/piscesboot-%d{yyyy-MM-dd}.%i.log</FileNamePattern>
              <!--日志文件保留天数 -->
              <MaxHistory>30</MaxHistory>
              <maxFileSize>10MB</maxFileSize>
            </rollingPolicy>
          </appender>

          <!-- 按照每天生成日志文件 -->
          <appender class="ch.qos.logback.core.FileAppender" name="HTML">
            <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
              <layout class="ch.qos.logback.classic.html.HTMLLayout">
                <pattern>%p%d%msg%M%F{32}%L</pattern>
              </layout>
            </encoder>
            <file>${LOG_HOME}/error-log.html</file>
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
              <!--设置日志级别,过滤掉info日志,只输入error日志-->
              <level>ERROR</level>
            </filter>
          </appender>

          <!-- 生成 error html格式日志开始 -->
          <appender class="ch.qos.logback.core.rolling.RollingFileAppender" name="FILE_HTML">
            <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
              <layout class="ch.qos.logback.classic.html.HTMLLayout">
                <pattern>%p%d%msg%M%F{32}%L</pattern>
              </layout>
            </encoder>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
              <!--日志文件输出的文件名 -->
              <FileNamePattern>${LOG_HOME}/piscesboot-%d{yyyy-MM-dd}.%i.html</FileNamePattern>
              <!--日志文件保留天数 -->
              <MaxFileSize>10MB</MaxFileSize>
              <MaxHistory>30</MaxHistory>
            </rollingPolicy>
          </appender>
          <!-- 生成 error html格式日志结束 -->

          <!-- 每天生成一个html格式的日志开始 -->
          <logger level="TRACE" name="com.apache.ibatis"/>
          <!-- 每天生成一个html格式的日志结束 -->

          <!--myibatis log configure -->
          <logger level="DEBUG" name="java.sql.Connection"/>
          <logger level="DEBUG" name="java.sql.Statement"/>
          <logger level="DEBUG" name="java.sql.PreparedStatement"/>

          <!-- 日志输出级别 -->
          <root level="INFO">
            <appender-ref ref="STDOUT"/>
            <appender-ref ref="FILE"/>
            <appender-ref ref="HTML"/>
            <appender-ref ref="FILE_HTML"/>
          </root>

        </configuration>

3.4 测试用例

01.摘要
    a.说明
        本文档详细记录了一次解决复杂 Maven 项目中 `mvn test` 命令执行失败问题的全过程。
        项目初期表现为测试无法运行,并伴随着一系列看似无关的衍生问题。
        通过层层递进的分析、调试和修复,最终不仅解决了问题,还对项目结构进行了标准化重构,消除了潜在的隐患。
        整个过程涉及 Maven 依赖范围、Surefire 插件配置、类路径污染、测试发现机制以及项目命名约定等多个核心知识点。
    b.默认
        核心难点:Maven 的“约定优于配置”原则。
        Surefire 插件默认只会扫描并执行以下模式命名的 Java 类:
        `**/Test*.java`
        `**/*Test.java`
        `**/*Tests.java`
        `**/*TestCase.java`
    c.命令
        mvn test -Dtest=cn.myslayers.test.utils.database22.DatabaseUtil22Test                         --默认
        mvn test -Dtest=cn.myslayers.test.utils.database22.DatabaseUtil22Test -X                      --详细日志
        mvn test -Dtest=cn.myslayers.test.utils.database22.DatabaseUtil22Test -DfailIfNoTests=false   --跳过失败

02.初始问题:最令人困惑的 `No tests were executed!`
    a.现象
        在项目根目录执行 `mvn test` 或 `mvn test -Dtest=<YourTestClass>` 时,构建失败,日志核心报错为:
        -----------------------------------------------------------------------------------------------------
        [INFO] Tests run: 0, Failures: 0, Errors: 0, Skipped: 0
        [ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.0.0-M5:test (default-test) on project pisces-boot-app: No tests were executed!
    b.难点与细节分析
        这个错误通常意味着 Maven 的测试插件 (Surefire) 没有找到任何测试来运行。初看之下,我们会检查:
        `pom.xml` 中是否有 `<skipTests>true</skipTests>`。 (检查后发现是 `false`)
        测试类名是否符合 `*Test.java` 的约定。 (当时我们以为符合)
        测试方法是否有 `@Test` 注解。 (检查后发现有)
        -----------------------------------------------------------------------------------------------------
        这些常规检查点都没有问题,这使得问题变得棘手。真正的突破口来自于使用 `-X` 参数开启 Maven 的调试模式:
        mvn test -Dtest=<YourTestClass> -X
        -----------------------------------------------------------------------------------------------------
        在海量的日志中,我们发现了关键线索:
        版本冲突:Surefire 插件加载的 JUnit Platform 版本 (`1.3.2`) 与项目依赖中声明的版本 (`5.8.2`) 不一致。
        类路径污染 (Classpath Pollution):这个旧的 `1.3.2` 版本来自于一个非常特殊的地方——`pisces-module-system-depend-1.0.0.jar`。这个 JAR 文件是一个业务模块,但它错误地将一个测试框架 (`junit-platform-launcher`) 以 `<scope>compile</scope>` 的范围打包了进去。
        核心难点:`<scope>compile</scope>` 的依赖具有传递性。当 `pisces-boot-app` 依赖 `pisces-module-system-depend` 时,这个被错误打包的、旧版本的测试框架就被“污染”到了主项目的编译和测试类路径中,导致 Surefire 插件在运行时选择了错误的版本,从而无法识别基于新版 JUnit 5 编写的测试。
    c.解决方案
        1.根治上游依赖:修改 `pisces-module-system-depend` 模块的 `pom.xml`,找到 `junit-platform-launcher` 这个依赖,将其 `<scope>` 从 `compile` 改为 `test`,或者直接删除(因为业务模块本身不应该依赖测试框架)。然后重新打包生成一个“干净”的 JAR。
        2.规范下游依赖:在主应用 `pisces-boot-app` 的 `pom.xml` 中,添加一个标准的、范围为 `<scope>test</scope>` 的测试依赖 `spring-boot-starter-test`。这确保了测试环境的独立和完整。
        3.升级 Surefire 插件:确保 `maven-surefire-plugin` 的版本足够新 (如 `3.0.0-M5` 或更高),以便与 JUnit 5 完美兼容。

03.衍生问题:`import` 找不到 `Launcher`
    a.现象
        `Test00_TestTotalSuite.java` 文件中的 `import org.junit.platform.launcher.Launcher;` 等语句开始报编译错误,提示找不到类。
    b.难点与细节分析
        核心难点:这个问题的出现恰恰证明了第一步的修复是正确的。
        因:我们成功地将 `junit-platform-launcher` 从主应用的 `compile` 类路径中移除了。
        果:现在,它只存在于 `test` 类路径中。
        矛盾:`Test00_TestTotalSuite.java` 是一个特殊的测试类,它不仅仅被动地接受测试,它还主动地调用 `Launcher` 的 API 来执行其他测试。这意味着 `Test00_TestTotalSuite.java` 在编译时就需要访问 `Launcher` 的 API。
        虽然 `spring-boot-starter-test` 已经传递地引入了 `launcher`,但对于这种“测试代码调用测试API”的特殊情况,最稳妥的做法是显式声明依赖。
    c.解决方案
        在 `pisces-boot-app` 的 `pom.xml` 中,额外添加一个对 `junit-platform-launcher` 的直接依赖,并确保其范围是 `<scope>test</scope>`。
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-launcher</artifactId>
            <scope>test</scope>
        </dependency>
        这笔“画龙点睛”的配置,既解决了测试代码的编译问题,又丝毫没有破坏我们之前努力实现的类路径隔离。

04.终极问题:不规范的配置与命名
    a.现象
        通过 `-Dtest` 指定单个测试类时,构建失败,报告 `Tests run: 0`。
        通过 `tree` 命令查看文件结构,发现大量测试类的命名不符合 Maven 的标准。
    b.难点与细节分析
        a.Surefire 的 include 配置
            最初的 `pom.xml` 中,`maven-surefire-plugin` 的 `<includes>` 配置过于复杂和不标准,包含了类似 `cn/myslayers/test/**/*.java` 的路径匹配。
            核心难点:这种不规范的配置可能会干扰 `-Dtest` 参数的覆盖行为。`-Dtest` 的设计初衷是忽略 `pom.xml` 中的 `<includes>`,强制只运行指定的测试。复杂的规则可能导致此机制失效。
        b.Maven 的测试类命名约定
            这是整个问题的根本原因,之前的依赖问题一直掩盖着它。
            核心难点:Maven 的“约定优于配置”原则。Surefire 插件默认只会扫描并执行以下模式命名的 Java 类:
            `**/Test*.java`
            `**/*Test.java`
            `**/*Tests.java`
            `**/*TestCase.java`
            而项目中的大量测试类,如 `Test01_DataSourceManagement.java`, `ExportPptx.java` 等,完全不符合这些模式。因此,即使依赖和配置都正确,Maven 在“测试发现”阶段也会直接忽略这些文件。
    c.解决方案
        a.简化 Surefire 配置(治标)
            将 `maven-surefire-plugin` 的 `<includes>` 块恢复到最简化的标准配置:
            <includes>
                <include>**/*Test.java</include>
                <include>**/*Tests.java</include>
            </includes>
        b.重命名所有测试类(治本 - 强烈推荐)
            这是最重要、最彻底的解决方案。将项目中所有不符合约定的测试类进行重命名。
            普通测试类:在末尾加上 `Test`。例如 `ExportPptx.java` -> `ExportPptxTest.java`。
            测试套件:可以命名为 `...Tests.java` 或 `...TestSuite.java`。例如 `Test00_TestTotalSuite.java` -> `Database12TestSuite.java`。

05.总结与教训
    a.警惕类路径污染
        永远不要在业务模块(库)中将测试范围的依赖以 `compile` 方式打包。这是“依赖地狱”的主要来源之一。
    b.相信调试日志
        Maven 的 `-X` 参数是解决复杂依赖问题的终极武器。日志中包含了版本冲突、类路径来源等关键信息。
    c.遵循约定优于配置
        尽可能遵守 Maven 的标准目录结构和文件命名约定。这可以让你免于编写复杂的配置,并使项目更容易被他人和工具理解。
    d.循序渐进排查问题
        从最底层的依赖和编译问题查起,逐步向上排查配置和执行层面的问题。一个问题的解决可能会引出下一个,要保持耐心,层层深入。

06.工作原理
    a.第一步:文件名过滤
        当 `mvn test` 命令被执行时,Surefire 插件会首先扫描 `src/test/java` 目录下的所有文件。然后,它会使用 `<includes>` 列表中的模式来匹配文件名。
        您提供的模式:
        `**/*Test.java`:匹配所有以 `Test.java` 结尾的文件,例如 `UserTest.java`, `service/OrderServiceTest.java`。
        `**/Test*.java`:匹配所有以 `Test` 开头、`.java` 结尾的文件,例如 `TestUser.java`, `TestOrderService.java`。
        `**/*Tests.java`:匹配所有以 `Tests.java` 结尾的文件,例如 `UserTests.java`, `OrderServiceTests.java`。
        `**/*TestCase.java`:匹配所有以 `TestCase.java` 结尾的文件,这主要为了兼容旧的 JUnit 3/4 命名习惯,例如 `UserTestCase.java`。
    b.第二步:内容扫描
        只有在文件名通过了第一步的过滤之后,Surefire 插件才会去分析这个 Java 类的内部,查找是否有 JUnit 的 `@Test` 注解(或其他测试框架的注解)。如果找到了,这些方法才会被执行。
    c.结论
        任何不符合 `<includes>` 中任何一个命名规则的 Java 类,即使它位于 `src/test/java` 目录下,并且内部包含了有效的 `@Test` 注解,`maven-surefire-plugin` 也不会将它们识别为测试类,因此不会执行它们。

07.举个例子
    a.`UserLogicTest.java`
        public class UserLogicTest {
            @Test
            public void testUserLogin() {
                System.out.println("正在测试用户登录...");
            }
        }
        -----------------------------------------------------------------------------------------------------
        这个文件的文件名 `UserLogicTest.java` 匹配 `**/*Test.java` 模式。因此,Surefire 会扫描它,找到 `@Test` 方法,并执行它。
    b.`UserLogicVerification.java`
        public class UserLogicVerification {
            @Test
            public void verifyUserData() {
                System.out.println("正在验证用户数据...");
            }
        }
        -----------------------------------------------------------------------------------------------------
        这个文件的文件名 `UserLogicVerification.java` 不匹配 `<includes>` 中的任何模式。因此,Surefire 会完全忽略这个文件,`verifyUserData` 方法永远不会被执行。

08.为什么这是最佳实践?
    a.清晰性与可维护性
        任何人看到以 `Test` 结尾的类,都能立刻明白它的用途。
    b.构建效率
        Surefire 插件不需要打开并解析每一个 `.java` 文件来判断它是否包含测试,它可以通过快速的文件名匹配来缩小范围,从而提高构建速度。
    c.工具兼容性
        IDE(如 IntelliJ IDEA, Eclipse)和持续集成工具(如 Jenkins)都默认识别这些命名约定,能更好地集成和支持。
    d.总结
        这种基于命名约定的方式是 Maven 和整个 Java 生态系统中的标准实践,其优点是确保了测试的一致性、可维护性和构建效率。

3.5 编码文件丢失

01.汇总
    a.查看文件
        a.模糊查询
            find /root/upFiles/temp -name "*钢材装车时间*"
            ls /root/upFiles/temp | grep "02-销售数据透视表_1689922597218_1743647311156.xlsx"
        b.使用 ls 和 grep
            ls /root/upFiles/temp | grep "钢材装车时间"
        c.直接查看文件
            ls /root/upFiles/temp/3月钢材装车时间_1743554832180.xlsx
    b.查看格式
        a.以.xlsx结尾的文件
            find /root/upFiles/temp -name "*.xlsx"
        b.以_开头并且以.xlsx结尾的文件
            find /root/upFiles/temp -name "_*.xlsx"

02.问题
    a.描述
        locale命令路径无法找到问题解决方法:Cannot set LC_CTYPE to default locale: No such file or directory
    b.locale
        root@ubuntu:/home# locale
        locale: Cannot set LC_CTYPE to default locale: No such file or directory
        locale: Cannot set LC_MESSAGES to default locale: No such file or directory
        locale: Cannot set LC_ALL to default locale: No such file or directory
        LANG=zh_CN.UTF-8
        LANGUAGE=zh_CN:zh
        LC_CTYPE="zh_CN.UTF-8"
        LC_NUMERIC=zh_CN
        LC_TIME=zh_CN
        LC_COLLATE="zh_CN.UTF-8"
        LC_MONETARY=zh_CN
        LC_MESSAGES="zh_CN.UTF-8"
        LC_PAPER=zh_CN
        LC_NAME=zh_CN
        LC_ADDRESS=zh_CN
        LC_TELEPHONE=zh_CN
        LC_MEASUREMENT=zh_CN
        LC_IDENTIFICATION=zh_CN
        LC_ALL=
    c.locale -a:查看系统安装的字符编码
        root@ubuntu:/home/yao# locale -a
        locale: Cannot set LC_CTYPE to default locale: No such file or directory
        locale: Cannot set LC_MESSAGES to default locale: No such file or directory
        locale: Cannot set LC_COLLATE to default locale: No such file or directory
        C
        C.UTF-8
        POSIX
        en_AG
        en_AG.utf8
        en_AU.utf8
        en_BW.utf8
        en_CA.utf8
        en_DK.utf8
        en_GB.utf8
        en_HK.utf8
        en_IE.utf8
        en_IN
        en_IN.utf8
        en_NG
        en_NG.utf8
        en_NZ.utf8
        en_PH.utf8
        en_SG.utf8
        en_US.utf8
        en_ZA.utf8
        en_ZM
        en_ZM.utf8
        en_ZW.utf8
        zh_CN
        zh_CN.gb2312
    d.说明
        发现问题是,我系统上设置的是字符编码是zh_CN.UTF-8,而系统并没有安装zh_CN.UTF-8字符编码
    e.解决方案
        a.安装该编码类型
            sudo locale-gen zh_CN.UTF-8
        b.修改配置文件,将zh_CN.UTF-8改为en_US.UTF-8,zh_CN改为en_US
            sudo vim /etc/default/locale
        c.将文档中所有zh_CN字符替换为en_US
            :%s/zh_CN/en_US/g
        d.验证
            locale

03.解决
    a.改进事项
        停机后,再备份数据
        丢失导入数据,带汇总
        -----------------------------------------------------------------------------------------------------
        有效日志:nginx+backend
        不同角色,命令的操作日志
        表字段,创建日期,更新日期
        方便线上调试:日志+远程断点+注解强制路由到本地代码
        每个用户对应的数据库权限,比如禁止配置root到配置文件,每个用户对应1个数据库
    b.20250308,临时解决
        nohup java -jar -Djava.net.preferIPv4Stack=true -Dfile.encoding=C.UTF-8 /home/zdh/shanxi/oms/cloud/sxoms-dcloud/$APP_NAME >$LOG_FILE 2>&1 &
    c.20250403,指定运行 JAR 包时服务器的终端编码,可以通过设置 LANG 或 LC_ALL 环境变量来实现
        nohup LANG=C.UTF-8 java -jar -Djava.net.preferIPv4Stack=true -Dfile.encoding=C.UTF-8 /home/zdh/shanxi/oms/cloud/sxoms-dcloud/$APP_NAME >$LOG_FILE 2>&1 &
        nohup LC_ALL=C.UTF-8 java -jar -Djava.net.preferIPv4Stack=true -Dfile.encoding=C.UTF-8 /home/zdh/shanxi/oms/cloud/sxoms-dcloud/$APP_NAME >$LOG_FILE 2>&1 &
        -----------------------------------------------------------------------------------------------------
        20250403,因为locale -a存在zh_CN.utf8编码,故设置为zh_CN.utf8编码
        nohup LANG=zh_CN.utf8 java -jar -Djava.net.preferIPv4Stack=true -Dfile.encoding=zh_CN.utf8 /home/zdh/shanxi/oms/cloud/sxoms-dcloud/$APP_NAME >$LOG_FILE 2>&1 &
        nohup LC_ALL=zh_CN.utf8 java -jar -Djava.net.preferIPv4Stack=true -Dfile.encoding=zh_CN.utf8 /home/zdh/shanxi/oms/cloud/sxoms-dcloud/$APP_NAME >$LOG_FILE 2>&1 &
        -----------------------------------------------------------------------------------------------------
        LANG:这个环境变量用于设置系统的默认语言和区域设置。如果你设置了 LANG=zh_CN.utf8,那么在该命令执行期间,所有与语言和区域相关的操作(如日期格式、货币符号等)都会使用中文(简体)和 UTF-8 编码
        LC_ALL:这个环境变量用于覆盖所有其他的 LC_* 环境变量,强制使用指定的区域设置。设置 LC_ALL=zh_CN.utf8 会使得所有与区域相关的设置都使用中文(简体)和 UTF-8 编码,优先级高于 LANG
    d.20250403,测试发现问题,启动脚本,跟终端编码有关系
        比如,临时修改为export LANG=zh_CN.utf8后,启动sh backend-start.sh start,上传图片+上传附件【正常】
        但,若采用默认终端,也就是默认LANG=en_US.UTF-8(环境丢失,报错locale: Cannot set LC_CTYPE to default locale: No such file or directory),启动sh backend-start.sh start,上传图片+上传附件【异常】
        nohup LANG=zh_CN.utf8 java -jar -Dfile.encoding=zh_CN.utf8 $JAR_HOME > $LOG_PATH 2>&1 &        --全局
        nohup env LANG=zh_CN.utf8 java -jar -Dfile.encoding=zh_CN.utf8 $JAR_HOME > $LOG_PATH 2>&1 &    --临时

3.6 ShiroConfig

00.总结
    a.报错
        Please create bean of type 'Realm' or add a shiro.ini in the root classpath
        (src/main/resources/shiro.ini) or in the META-INF folder (src/main/resources/META-INF/shiro.ini).
    b.解决
        解决在主启动类上,添加@ComponentScan(basePackages = {"cn.myslayers"}),扫描该路径,进行装配Bean
        @Slf4j
        @EnableScheduling
        @SpringBootApplication
        @ComponentScan(basePackages = {"org.jeecg", "cn.myslayers"})
        public class PiscesSystemApplication extends SpringBootServletInitializer {

01.常用信息
    a.shiro-spring-boot-starter-1.12.0,spring.factories
        org.springframework.boot.autoconfigure.EnableAutoConfiguration = \
          org.apache.shiro.spring.config.web.autoconfigure.ShiroWebAutoConfiguration,\
          org.apache.shiro.spring.config.web.autoconfigure.ShiroWebFilterConfiguration,\
          org.apache.shiro.spring.config.web.autoconfigure.ShiroWebMvcAutoConfiguration,\
          org.apache.shiro.spring.boot.autoconfigure.ShiroBeanAutoConfiguration,\
          org.apache.shiro.spring.boot.autoconfigure.ShiroAutoConfiguration,\
          org.apache.shiro.spring.boot.autoconfigure.ShiroAnnotationProcessorAutoConfiguration
        org.springframework.boot.diagnostics.FailureAnalyzer = \
          org.apache.shiro.spring.boot.autoconfigure.ShiroNoRealmConfiguredFailureAnalyzer
        org.springframework.boot.env.EnvironmentPostProcessor=\
          org.apache.shiro.spring.config.web.autoconfigure.ShiroEnvironmentPostProcessor
    b.目前配置
        a.位置
            pisces-boot-base-core\src\main\java\cn\myslayers\common\aspect\AutoLogAspect
            pisces-boot-base-core\src\main\java\cn\myslayers\common\constant\CommonConstant.java
            pisces-boot-base-core\src\main\java\cn\myslayers\common\exception\PiscesBootExceptionHandler.java
            pisces-boot-base-core\src\main\java\cn\myslayers\common\system\base\controller\PiscesController.java
            pisces-boot-base-core\src\main\java\cn\myslayers\common\system\util\JwtUtil.java
            pisces-boot-base-core\src\main\java\cn\myslayers\common\util\encryption\AesEncryptUtil.java
            pisces-boot-base-core\src\main\java\cn\myslayers\config\PiscesBaseConfig.java
            pisces-boot-base-core\src\main\java\cn\myslayers\config\mybatis\MybatisInterceptor.java
            pisces-boot-base-core\src\main\java\cn\myslayers\config\shiro\JwtToken.java
            pisces-boot-base-core\src\main\java\cn\myslayers\config\shiro\ShiroConfig.java
            pisces-boot-base-core\src\main\java\cn\myslayers\config\shiro\ShiroRealm.java
            pisces-boot-base-core\src\main\java\cn\myslayers\config\shiro\filters\CustomShiroFilterFactoryBean.java:
            pisces-boot-base-core\src\main\java\cn\myslayers\config\shiro\filters\JwtFilter.java
            pisces-boot-base-core\src\main\java\cn\myslayers\config\shiro\filters\ResourceCheckFilter.java
            pisces-boot-base-core\src\main\java\cn\myslayers\config\vo\Shiro.java
            pisces-boot-base-core\src\main\java\cn\myslayers\modules\base\service\impl\BaseCommonServiceImpl.java
        b.jeecg-boot-starter-3.4.3
            org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.jeecg.boot.starter.lock.config.RedissonConfiguration
            org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.jeecg.boot.starter.job.config.XxlJobConfiguration
            org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.jeecg.boot.starter.job.config.XxlJobConfiguration
            org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.jeecg.boot.starter.lock.config.RedissonConfiguration
        c.pisces-boot-starter
            org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.jeecgframework.minidao.auto.MinidaoAutoConfiguration
            org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.jeecg.modules.jmreport.config.init.JimuReportConfiguration
            org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.myslayers.boot.starter.lock.config.RedissonConfiguration
            org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.myslayers.boot.starter.lock.config.RedissonConfiguration
            org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.myslayers.boot.starter.job.config.XxlJobConfiguration
            org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.myslayers.boot.starter.job.config.XxlJobConfiguration

02.解决报错
    a.报错1
        a.报错
            Please create bean of type 'Realm' or add a shiro.ini in the root classpath
            (src/main/resources/shiro.ini) or in the META-INF folder (src/main/resources/META-INF/shiro.ini).
        b.解决
            解决在主启动类上,添加@ComponentScan(basePackages = {"cn.myslayers"}),扫描该路径,进行装配Bean
            @Slf4j
            @EnableScheduling
            @SpringBootApplication
            @ComponentScan(basePackages = {"org.jeecg", "cn.myslayers"})
            public class PiscesSystemApplication extends SpringBootServletInitializer {
    b.报错2
        a.报错
            Description:
            Parameter 0 of method authorizationAttributeSourceAdvisor in
            org.apache.shiro.spring.boot.autoconfigure.ShiroAnnotationProcessorAutoConfiguration
            required a bean named ‘authorizer’ that could not be found.
            Action:
            Consider defining a bean named ‘authorizer’ in your configuration.
        b.分析
            报错的意思是,在类 ShiroAnnotationProcessorAutoConfiguration中的这个方法的第0个参数,
            也就是securityManager ,缺少一个叫authorizer 的 Bean
            -------------------------------------------------------------------------------------------------
            因为依赖注入,这个方法需要参数securityManager,但是发现还没有这个Bean,于是进行实例化这个Bean,
            但是在实例化的时候,又依赖了一个叫 authorizer 的Bean,但发现无法实例化 authorizer ,于是报错。
            通过调试可以看到,报错具体在类AbstractShiroConfiguration中
            -------------------------------------------------------------------------------------------------
            理论上说,authorizer 是 Shiro 中很重要的一个 Bean,用于登录认证,Springboot 肯定会帮我们注入的。
            查找一下,发现在ShiroWebAutoConfiguration这个类中,有自动注入。
            -------------------------------------------------------------------------------------------------
            事实上,因为有 @ConditionalOnMissingBean 注解,意思是如果已存在这个类型的 Bean ,
            就不执行本方法。这个方法没有得到真正的执行。
            -------------------------------------------------------------------------------------------------
            实际上这个时候如果你对Spring的 Bean加载很熟悉的话,完全可以debug进去,
            看看为什么会出现已经有这个类型的 Bean 了。非常详细,参见:
            https://blog.csdn.net/weixin_34289454/article/details/94562045
            -------------------------------------------------------------------------------------------------
            结论就是,因为你自定义的 Realm 间接地实现了 Authorizer 接口,所以它是 Authorizer 类型的 Bean。
            所以刚才那里,@ConditionalOnMissingBean 会查到,已经有这个类型的 Bean了,不再注入了。
            但问题是,为什么刚才打断点的地方:
            securityManager.setAuthorizer(authorizer());
            中的 authorizer() 方法不执行?实际上是因为Spring的代理类去执行,会找名为 authorizer 的 Bean,
            发现没有,才报错。
        c.解决
            a.方法一:手动注入这个Bean
                @Configuration
                public class ShiroConfig {
                    @Bean
                    public Authorizer authorizer(){
                        return new ModularRealmAuthorizer();
                    }
                }
            b.方法二:不用Springboot自动注入的sercurityManager了,我自己在 ShiroConfig中注入。
                @Bean
                public SessionsSecurityManager securityManager() {
                    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
                    securityManager.setRealm(adminLoginRealm()) ;
                }
            c.方法三:我把自己写的Realm的Bean名字定为"authorizer"
                @Component("authorizer")
                public class AdminLoginRealm m extends AuthorizingRealm {

3.7 MybatisConfig

00.@Mapper和@MapperScan区别
    a.介绍
        @Mapper注解写在每个Dao接口层的接口类上,@MapperScan注解写在SpringBoot的启动类上。
        当我们的一个项目中存在多个Dao层接口的时候,此时我们需要对每个接口类都写上@Mapper注解,非常的麻烦,
        此时可以使用@MapperScan注解来解决这个问题,让这个接口进行一次性的注入,不需要在写@Mapper注解。
    b.示例
        @SpringBootApplication
        @MapperScan("cn.gyyx.mapper")
        // 这个注解可以扫描 cn.gyyx.mapper 这个包下面的所有接口类,可以把这个接口类全部的进行动态代理。
        public class WardenApplication {
            public static void main(String[] args) {
                SpringApplication.run(WardenApplication.class,args);
            }
        }
        -----------------------------------------------------------------------------------------------------
        @Mapper注解相当于是@Reponsitory注解和@MapperScan注解的和,会自动的进行配置加载。
        @MapperScan注解多个包,@Mapper只能把当前接口类进行动态代理。
    c.在实际开发中,如何使用@Mapper、@MapperSacn、@Repository注解
        在SpringBoot的启动类上给定@MapperSacn注解。
        此时Dao层可以省略@Mapper注解,当然@Repository注解可写可不写,最好还是写上。
        当使用@Mapper注解的时候,可以省略@MapperSacn以及@Repository。
    d.建议
        以后在使用的时候,在启动类上给定@MapperScan("Dao层接口所在的包路径")。
        在Dao层上不写@Mapper注解,写上@Repository即可

01.常见信息
    a.问题
        MybatisPlusSaasConfig 和 mybatis.mapper-locations 重复扫描,报错 Bean already defined 警告
    b.原因
        a.@MapperScan的作用
            @MapperScan 注解用于指定 Mapper 接口的包路径,并自动扫描这些接口以进行注册。
        b.mybatis.mapper-locations的作用
            mapper-locations 是 MyBatis XML 文件的路径配置,用于指定 Mapper XML 文件的位置。
        c.重复扫描的可能性
            如果 @MapperScan 和 mapper-locations 配置的扫描路径存在交集,MyBatis 会尝试重复注册 Mapper 接口,导致日志中出现的 Bean already defined 警告。
            尤其是 @MapperScan 的路径包含所有 Mapper 的包路径,同时 mapper-locations 也指向这些 Mapper 的 XML 文件路径时,这种冲突就会出现。
    c.解决方案
        a.方法1: 去掉 @MapperScan 注解
            如果所有 Mapper 的扫描均已通过 mapper-locations 解决,可以移除 @MapperScan,避免重复加载:
            @Configuration
            public class MybatisPlusSaasConfig {
                // 不需要额外配置 @MapperScan
            }
        b.方法2: 调整 @MapperScan 的路径范围
            如果你需要保留 @MapperScan,则需确保 mapper-locations 和 @MapperScan 配置的范围互补且不重叠:
            @Configuration
            @MapperScan(value = {"cn.myslayers.modules.someSpecificPackage.mapper"})
            public class MybatisPlusSaasConfig {
                // 仅扫描特定的 Mapper 接口
            }
        c.方法3: 移除 mapper-locations 配置
            如果项目中所有的 Mapper 都采用注解方式定义(即不需要 XML 文件),可以移除 mapper-locations 配置:
            # application.yml
            # 删除 mybatis.mapper-locations 配置
        d.方法4: 保留 mapper-locations,避免 @MapperScan 全局扫描
            通过配置 XML 文件的路径来管理 Mapper,避免全局使用 @MapperScan 扫描。

02.常见信息
    a.问题
        a.@Resource(JDK 提供的):
            默认按 名称(name) 注入,如果名称匹配不上,则回退到按类型(type)注入。
            因此,SysUserMapper 必须被正确注册为 Spring Bean,且名字或类型匹配。
        b.@Autowired(Spring 提供的):
            默认按 类型(type) 注入,如果有多个同类型的 Bean,则需要结合 @Qualifier 指定名称。
    b.为什么需要@MapperScan?
        a.@MapperScan 的作用:
            这是 MyBatis 提供的注解,用于指定 Mapper 接口所在的包路径,从而让 Spring 自动扫描并将其注册为 Spring Bean。
            如果没有使用 @MapperScan 或者等效的 XML 配置,MyBatis 无法将 SysUserMapper 注册到 Spring 容器中,即使 mapper-locations 指定了 XML 的位置。
        b.mapper-locations 的作用:
            它仅用于配置 MyBatis 的 XML 映射文件的位置,解决的是 SQL 映射问题。
            但它不会自动将 Mapper 接口(如 SysUserMapper)注册为 Spring Bean。
            因此,仅使用 mapper-locations 是不够的,还需要使用 @MapperScan 或等效的扫描配置。
    c.如何正确配置Mapper?
        a.推荐使用 @MapperScan: 在主启动类(@SpringBootApplication 标注的类)中添加:
            @SpringBootApplication
            @MapperScan("cn.myslayers.modules.system.mapper")
            public class Application {
                public static void main(String[] args) {
                    SpringApplication.run(Application.class, args);
                }
            }
        b.确保 Mapper 接口上有 @Mapper 注解:
            @Mapper
            public interface SysUserMapper {
                // 数据库操作方法
            }
        c.配置 mapper-locations(用于 XML 映射文件): 在 application.yml 中确保正确配置了 XML 文件的路径:
            mybatis:
              mapper-locations: classpath*:cn/myslayers/**/mapper/xml/*.xml
    d.总结
        @MapperScan 是必须的,用于扫描并注册 Mapper 接口。
        mapper-locations 是辅助的,用于加载对应的 XML 文件。
        两者配合才能使 Mapper 正常工作。
        如果没有 @MapperScan,即使配置了 mapper-locations,Mapper 接口也不会被注册为 Spring Bean,从而导致 @Resource 或 @Autowired 注入失败。

03.生产使用
    a.启动类
        @SpringBootApplication
        @MapperScan("cn.myslayers.modules.system.mapper")
        public class Application {
            public static void main(String[] args) {
                SpringApplication.run(Application.class, args);
            }
        }
    b.Mapper接口
        @Mapper
        public interface SysUserMapper {
            // 方法定义
        }
    c.Service层注入
        @Service
        public class SomeService {
            @Resource
            private SysUserMapper userMapper; // 注入成功
        }
    d.配置文件
        mybatis:
          mapper-locations: classpath*:cn/myslayers/**/mapper/xml/*.xml
    e.总结
          @MapperScan 是必须的,用于扫描并注册 Mapper 接口。
          mapper-locations 是辅助的,用于加载对应的 XML 文件。
          两者配合才能使 Mapper 正常工作。
          如果没有 @MapperScan,即使配置了 mapper-locations,Mapper 接口也不会被注册为 Spring Bean,从而导致 @Resource 或 @Autowired 注入失败。

04.报错
    a.问题
        org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): org.jeecg.modules.system.mapper.SysDepartMapper.queryUserDeparts
            at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:235)
            at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.<init>(MybatisMapperMethod.java:50)
            at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.lambda$cachedInvoker$0(MybatisMapperProxy.java:111)
            at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
            at com.baomidou.mybatisplus.core.toolkit.CollectionUtils.computeIfAbsent(CollectionUtils.java:115)
            at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.cachedInvoker(MybatisMapperProxy.java:98)
            at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)
            at com.sun.proxy.$Proxy149.queryUserDeparts(Unknown Source)
            at org.jeecg.modules.system.service.impl.SysDepartServiceImpl.queryUserDeparts(SysDepartServiceImpl.java:412)
            at org.jeecg.modules.system.service.impl.SysDepartServiceImpl$$FastClassBySpringCGLIB$$6f2aae1b.invoke(<generated>)
    b.MyBatis找到XML映射文件
        a.Maven 资源处理的特点
            Maven 默认只处理 src/main/resources 目录下的资源
            要处理 src/main/java 下的文件,需要明确配置
            <resources>
              <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
              </resource>
              <resource>
                <directory>src/main/java</directory>
                <includes>
                  <include>**/*.xml</include>
                  <include>**/*.json</include>
                  <include>**/*.ftl</include>
                </includes>
              </resource>
            </resources>
        b.第1种:resources目录方式(标准推荐方式)
            将XML文件放在src/main/resources目录下
            配置mapper-locations: classpath*:cn/myslayers/**/mapper/xml/*.xml
        c.第2种Java源码目录方式(你当前使用的方式)
            将XML文件放在src/main/java目录下
            需要在pom.xml中添加资源配置:
            <resources>
              <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
              </resource>
              <resource>
                <directory>src/main/java</directory>
                <includes>
                  <include>**/*.xml</include>
                  <include>**/*.json</include>
                  <include>**/*.ftl</include>
                </includes>
              </resource>
            </resources>
    c.查看配置
        a.配置
            @Configuration
            @MapperScan(value={"cn.myslayers.**.mapper*"})
            public class MybatisPlusSaasConfig {
            }
            -------------------------------------------------------------------------------------------------
            mybatis-plus:
              # mapper-locations:SQL映射文件的加载问题
              # cn.myslayers.config.mybatis.MybatisPlusSaasConfig:Mapper接口的注册问题
              mapper-locations: classpath*:cn/myslayers/**/mapper/xml/*.xml                --注意是classpath*,不是classpath:*
        b.排查步骤
            a.Mapper接口和XML文件的namespace不匹配:
                确保SysDepartMapper接口的namespace与XML文件中的namespace完全一致。你已经确认过这一点,所以这个问题可以排除。
            b.Mapper接口未被正确扫描:
                确保@MapperScan注解的配置正确,且能够扫描到SysDepartMapper接口。你在MybatisPlusSaasConfig中使用了@MapperScan(value={"org.jeecg.**.mapper*"}),这应该是正确的。
            c.XML文件的SQL语句未被加载:
                确保SysDepartMapper.xml文件在运行时能够被MyBatis加载。你可以在application-dev.yml中添加MyBatis的日志配置,以便查看加载的SQL语句:
            d.【重点】检查target
                jeecg-module-common\target\classes\org\jeecg\modules\system\mapper\SysAnnouncementMapper.class
                对应
                jeecg-module-common\target\classes\org\jeecg\modules\system\mapper\xml\SysAnnouncementMapper.xml
            e.【20250109】解决方案
                确定为【build】配置有误,参考【Packing4,父子模块】
                确定为【redis】缓存信息有问题,操作【redis -> 6349 -> DB2 -> 清空】
                确定为【JeecgSystemApplication】编译的内容没有mapper,清除IDE缓存,使用【右侧菜单 -> maven -> 生命周期:clean + compile】

4 前端管理

4.1 Husky

01.Git hooks脚本:Husky
    a.说明
        是一个 Git Hook 工具
        用于在 git 操作(如 commit、push)前执行一些脚本
    b.主要用途
        # 常见用法
        npx husky install  # 安装 git hooks
    c.典型应用场景
        提交前代码格式化
        运行测试
        代码风格检查
        commit message 格式验证
    d.例如一个典型的 Husky 配置:
        {
          "husky": {
            "hooks": {
              "pre-commit": "lint-staged",
              "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
            }
          }
        }
    e.总结
        所以 husky install 是用来设置 git hooks 的,与项目的国际化功能(i18n/t())完全是独立的两个概念。

4.2 AES加密

01.AES加密:系统监控 -> 数据源管理
    a.报错
        2025-01-25 16:14:22.012 [http-nio-9994-exec-8] ERROR c.j.common.exception.PiscesBootExceptionHandler:75 - InvalidKeyException: Invalid AES key length: 15 bytes
        cn.hutool.crypto.CryptoException: InvalidKeyException: Invalid AES key length: 15 bytes
            at cn.hutool.crypto.symmetric.SymmetricCrypto.decrypt(SymmetricCrypto.java:388)
            at cn.hutool.crypto.symmetric.SymmetricCrypto.decrypt(SymmetricCrypto.java:424)
            at cn.hutool.crypto.symmetric.SymmetricCrypto.decryptStr(SymmetricCrypto.java:435)
            at cn.myslayers.system.util.SecurityUtil.jiemi(SecurityUtil.java:40)
            at cn.myslayers.system.controller.SysDataSourceController.queryById(SysDataSourceController.java:169)
    b.解决
        cn/myslayers/system/util/SecurityUtil.java
        -----------------------------------------------------------------------------------------------------
        /**
         * 加密key(必须为16位)
         */
        private static String key = "PISCESBOOT14236780";
        -----------------------------------------------------------------------------------------------------
        以前是 PISCESBOOT1423670,15位。现在改为 PISCESBOOT14236780,16位
        -----------------------------------------------------------------------------------------------------
        官方文档和标准参考
        Java Cryptography Architecture (JCA) 官方文档
        AES 密钥必须是 16/24/32 字节长度
        对应 AES-128/AES-192/AES-256
        -----------------------------------------------------------------------------------------------------
        Hutool 官方文档
        SymmetricCrypto 类的说明:
        底层使用 JDK 的 AES 实现
        要求密钥长度符合 AES 标准

4.3 调用链路

01.结构
    a.原始
        components
            XxxForm.vue           弹框
            XxxModal.vue          表格
        Xxx.api.ts
        Xxx.data.ts
        Xxx_menu_insert.ts
        XxxList.vue
        };
    b.简单调用
        XxxList.vue(主页面) -> 调用components -> XxxModal.vue -> 调用XxxForm.vue(表单)
                              -> 引入Xxx.api.ts(定义API)
                              -> 引入Xxx.data.ts(定义Table列)
    c.深层次调用
        第1步:XxxList.vue(主页面) -> 调用components -> XxxModal.vue -> 调用XxxForm.vue(表单)
        第2步:XxxForm.vue(表单) -> src/components/Form/src/BasicForm.vue(表单)

02.字典
    a.分类字典
        sys_category
        http://127.0.0.1:3100/jeecgboot/sys/category/childList?pid=1710160530146082817&_t=1737948241529
    b.数据字典
        sys_dict
        sys_dict_item

4.4 全局样式

01.样式库
    a.链接
        https://help.jeecg.com/ui/config/css
    b.项目中使用的通用样式,都存放于src/design/下面
        src/design/
        ├── ant # ant design 一些样式覆盖
        ├── color.less # 颜色
        ├── index.less # 入口
        ├── public.less # 公共类
        ├── theme.less # 主题相关
        ├── config.less  # 每个组件都会自动引入样式
        ├── transition # 动画相关
        └── var # 变量
    c.使用
        web\src\views\system\depart\index.less
        web\src\views\system\departUser\index.less

02.Jeecg说明
    a.介绍
        a.说明
            主要介绍如何在项目中使用和规划样式文件。
            默认使用 less 作为预处理语言,建议在使用前或者遇到疑问时学习一下 Less 的相关特性(如果想获取基础的 CSS 知识或查阅属性,请参考 MDN 文档)。
            项目中使用的通用样式,都存放于 src/design/ 下面。
        b.目录结构
            ├── ant # ant design 一些样式覆盖
            ├── color.less # 颜色
            ├── index.less # 入口
            ├── public.less # 公共类
            ├── theme.less # 主题相关
            ├── config.less  # 每个组件都会自动引入样式
            ├── transition # 动画相关
            └── var # 变量
        c.全局注入
            config.less 这个文件会被全局注入到所有文件,所以在页面内可以直接使用变量而不需要手动引入
            <style lang="less" scoped>
              // 这里已经隐式注入了 config.less
            </style>
        d.tailwindcss(2.5.0+)
            项目中引用到了tailwindcss,具体可以见文件使用说明。
            语法如下:
            <div class="relative w-full h-full px-4"></div>
        e.windicss(2.5.0 已弃用)
            项目中使用了windicss,具体参见文件使用说明。
            语法如下:
            <div class="relative w-full h-full px-4"></div>
        f.注意事项
            windcss 目前会造成本地开发内存溢出,所以后续可能会考虑切换到 TailwindCss,两者基本相同。
            所以尽量少用 Windicss 新增的特性,防止后续切换成本高。
        g.为什么使用 Less
            主要是因为 Ant Design 默认使用 less 作为样式语言,使用 Less 可以跟其保持一致。
    b.开启 scoped
        a.说明
            没有加scoped属性,默认会编译成全局样式,可能会造成全局污染
            <style></style>
            <style scoped></style>
        b.温馨提醒
            使用 scoped 后,父组件的样式将不会渗透到子组件中
            不过一个子组件的根节点会同时受其父组件的 scoped CSS 和子组件的 scoped CSS 的影响
            这样设计是为了让父组件可以从布局的角度出发,调整其子组件根元素的样式
    c.深度选择器
        a.说明
            有时我们可能想明确地制定一个针对子组件的规则。
            如果你希望scoped样式中的一个选择器能够作用得“更深”,例如影响子组件,你可以使用>>>操作符
            有些像 Sass 之类的预处理器无法正确解析>>>。这种情况下你可以使用/deep/或::v-deep操作符取而代之——两者都是>>>的别名,同样可以正常工作。
            详情可以查看 RFC0023-scoped-styles-changes。
        b.使用 scoped 后,父组件的样式将不会渗透到子组件中,所以可以使用以下方式解决:
            <style scoped>
              /* deep selectors */
              ::v-deep(.foo) {
              }
              /* shorthand */
              :deep(.foo) {
              }

              /* targeting slot content */
              ::v-slotted(.foo) {
              }
              /* shorthand */
              :slotted(.foo) {
              }

              /* one-off global rule */
              ::v-global(.foo) {
              }
              /* shorthand */
              :global(.foo) {
              }
            </style>
    d.CSS Modules
        a.针对样式覆盖问题,还有一种方案是使用 CSS Modules 模块化方案。使用方式如下。
            <template>
              <span :class="$style.span1">hello</span>
            </template>

            <script>
              import { useCSSModule } from 'vue';

              export default {
                setup(props, context) {
                  const $style = useCSSModule();
                  const moduleAStyle = useCSSModule('moduleA');
                  return {
                    $style,
                    moduleAStyle,
                  };
                },
              };
            </script>

            <style lang="less" module>
              .span1 {
                color: green;
                font-size: 30px;
              }
            </style>

            <style lang="less" module="moduleA">
              .span1 {
                color: green;
                font-size: 30px;
              }
            </style>
    e.重复引用问题
        a.加上reference可以解决页面内重复引用导致实际生成的 style 样式表重复的问题。
            这步已经全局引入了。所以可以不写,直接使用变量
            <style lang="less" scoped>
              /* 该行代码已全局引用。可以不用单独引入 */
              @import (reference) '../../design/config.less';
            <style>

03.less定制的ant-design-vue全局样式
    a.说明
        a.提问
            @/src/views/device/cjsbgzms/ 使用css js vue页面,而这个项目是 ts项目 antdesingvue,使用less.
            但是目前 @/src/views/device/cjsbgzms/index.vue 虽然使用<style scoped src="./css/index.css"></style>,但是 逐级注册到根目录的app下时,被全局样式进行了覆盖。
            @/src/main.ts 中引入了import '/@/design/index.less'; 我现在想 在main设置一些东西,绕开全局css渲染到 某些组件,比如 @/src/views/device/cjsbgzms/index.vue
        b.手工思路
            element组件库
            根源less,设置作用域,失败,不支持
            根源less,插件注册,失败,不支持
            艾特less,基于less写css,失败,不支持
            绕开less,自定义js编译器,失败,不支持
            全局样式,排除某几个组件的渲染,不支持
            强制原生,node_modules/ant-design-vue/dist/antd.less,不支持过多!import
            手动挂载,报错SyntaxError: The requested module 'http://127.0.0.1:3100/src/views/device/cjsbgzms/js/index.js?vue&type=script&src=true&lang.js' doesn't provide an export named: 'default'
            ---------------------------- ---------------------------------------------------------------------
            继续用scoped + deep()
            改为 CSS Modules + global()
            创建一个专门的 CSS/Less 文件,里面包含一系列使用 :deep() 的规则,目的是精确地“抵消”或“重置” src/design/ant/ 中对 Ant Design 组件所做的所有全局修改,然后将这个“反向样式表”应用到 cjsbgzms/index.vue 组件上。
            -------------------------------------------------------------------------------------------------
            修改全局样式源文件 (src/design/ant/): 使用包装类(如 .custom-antd-theme)来限定这些修改的作用范围。这是最干净、最推荐的从源头解决问题的方法。
            使用 CSS Modules + :global(): 在组件的 .module.css 文件中,针对性地使用 :global() 结合模块类名来覆盖特定的全局 Antd 样式,只影响当前组件。
        c.AI思路
            您希望在 src/main.ts 中引入的全局 Less 样式 (/@/design/index.less) 不影响 src/views/device/cjsbgzms/index.vue 这个特定组件
            尽管该组件已经使用了 <style scoped src="./css/index.css"></style>
            全局样式覆盖 scoped 样式通常是由于 CSS 的特异性(Specificity)规则
            或者全局样式规则过于宽泛(例如直接针对 HTML 标签 input, button 等进行样式设置)导致
            在不修改代码的前提下,根据我的知识库和对 Vue 及 CSS 工作原理的理解,有以下几种可能的思路来解决这个问题或绕开全局样式的渲染:
    b.AI思路
        a.提高组件内部样式的特异性
            原理:CSS 根据选择器的特异性来决定应用哪个样式。如果全局样式的选择器特异性高于或等于 scoped 样式生成的选择器,全局样式就会生效。
            思路:在 src/views/device/cjsbgzms/css/index.css 文件中,使用更具体的选择器(例如,增加父级类名、ID,或者使用属性选择器等)来覆盖全局样式。或者,在必要时谨慎使用 !important(但这通常不推荐,会使样式难以维护)。
            可行性:高。这是标准的 CSS 问题解决方法,但可能需要仔细检查全局样式和组件样式,工作量取决于冲突的范围。
        b.使用 CSS Modules
            原理:CSS Modules 会为每个类名生成一个唯一的哈希值,确保样式只应用于导入它的组件,提供了比 scoped 更强的隔离性。
            思路:将 <style scoped src="./css/index.css"></style> 改为 <style module src="./css/index.module.css"></style>(需要重命名 CSS 文件),并在组件的 <template> 和 <script> 中相应地使用 $style.className 的方式来引用样式。
            可行性:高。Vue 和 Vite 对 CSS Modules 有良好支持。需要修改组件引用样式的方式以及 CSS 文件名。
        c.使用 Shadow DOM
            原理:Shadow DOM 提供了一个完全封装的 DOM 子树,其内部样式与外部完全隔离。
            思路:在 Vue 3 中,可以通过 <style scoped shadowmode="open"> (或 closed) 来启用 Shadow DOM 封装。
            可行性:中等。这是最强的隔离方式,但需要确认:
            Ant Design Vue 组件在 Shadow DOM 内是否能正常工作(某些依赖全局样式或 DOM 结构的操作可能会受影响)。
            浏览器兼容性是否满足项目要求。
        d.重构全局样式 (design/index.less)
            原理:让全局样式不那么“全局”,减少使用过于宽泛的选择器(如标签选择器 input, button),或者将全局样式限制在特定的父类下(例如,只对带有 .global-scope 类名的容器下的元素生效)。
            思路:修改 design/index.less,使其规则更加具体,避免直接影响所有组件。例如,将 button { ... } 改为 .antd-global-wrapper button { ... } 或类似形式。
            可行性:取决于全局样式文件的复杂度和项目影响范围。可能是最“干净”的解决方案,但改动可能较大。
        e.利用 PostCSS 或 Vite 插件进行条件化处理
            原理:在构建过程中,通过配置 PostCSS 或 Vite 插件,尝试识别并修改全局样式,使其不应用于特定组件或路径。
            思路:研究是否有 Vite 或 PostCSS 插件能实现这种精细控制,例如基于文件路径排除某些 CSS 规则的应用。
            可行性:低。这通常比较复杂,可能没有现成的插件能完美实现这个需求,需要定制开发或深入配置。
        f.Ant Design Vue 的 ConfigProvider
            原理:Ant Design Vue 提供 <a-config-provider> 组件,可以用来定制主题、语言等,可能也包含一些样式隔离或前缀配置的选项。
            思路:查阅 Ant Design Vue 文档,看 ConfigProvider 或其他全局配置是否提供了 CSS 前缀(prefixCls)或其他方式来限制其组件样式的影响范围,或者是否有方法让特定区域不继承全局 Antd 样式。
            可行性:中等。需要查阅文档确认是否有相关功能。prefixCls 通常用于给 Antd 组件添加统一前缀,不一定能阻止外部全局样式。
        g.总结与建议
            CSS Modules 和 提高组件内部样式特异性 是比较直接且常用的方法。CSS Modules 提供了更好的隔离性,而提高特异性则是在现有 scoped 基础上进行调整。
            Shadow DOM 提供了最强的隔离,但兼容性和对第三方库的影响需要仔细评估。
            重构全局样式 是根本性的解决方案,但可能工作量大。
            其他方法(Vite/PostCSS 插件、Antd 配置)需要进一步调研其可行性。
    c.使用 Less 和 Scoped 样式
        a.步骤
            将 <style scoped src="./css/index.css"></style> 替换为 <style lang="less" scoped>...</style>。
            在 <style> 块顶部添加 @import (reference) '../../design/config.less';。
            将原 index.css 的内容合并到 <style> 块中,并利用 Less 的嵌套特性重构 CSS 规则。
            对于 .ant-picker-* 相关的样式,保留它们的顶级结构。
        b.问题
            JavaScript 错误可能导致样式未能正确应用。
            样式加载失败可能是由于 Less 文件编译错误或变量未定义。
            数据结构不匹配导致渲染问题。
        c.解决方案
            重新使用 scoped 样式以提高样式优先级。
            将样式代码移回 index.vue 并启用 scoped。
    d.手动挂载
        a.创建独立的 Vue 应用实例
            做法:在 main.ts 或其他地方,除了创建主要的 app 实例 (createApp(App).mount('#app')) 之外,再为 cjsbgzms/index.vue 创建一个完全独立的 Vue 应用实例,并将其挂载到 index.html 中的另一个 DOM 节点上(例如 <div id="cjsbgzms-container"></div>)。
            效果:这个独立的实例理论上不会自动继承主应用 main.ts 中 import '/@/design/index.less'; 导入的全局样式。听起来似乎能隔离样式。
            -------------------------------------------------------------------------------------------------
            问题/缺点
            复杂性: 管理同一个页面上的多个独立 Vue 应用非常复杂。
            状态共享: 主应用和这个独立应用之间共享状态(如 Vuex store、用户登录信息、全局配置)会变得非常困难。
            路由: 该组件将脱离主应用的路由管理。
            Ant Design Vue: 如果 cjsbgzms/index.vue 内部使用了 Ant Design Vue 组件,您需要在这个独立的实例中重新注册 Ant Design Vue,并且可能需要再次引入 Ant Design Vue 的基础样式。如果您不引入 Antd 样式,组件会无法正确显示;如果您引入了(即使是 Antd 默认样式),那么您可能又会遇到样式冲突问题,只是范围不同。而且,您在 src/design/ant/ 中对 Antd 的全局修改,除非您也在此独立实例中引入,否则不会生效。
            构建和维护: 可能会增加构建配置的复杂性,并且维护成本高。
        b.使用 <Teleport> 组件
            做法:在 cjsbgzms/index.vue 或其父组件中使用 Vue 3 的 <Teleport to="body"> (或其他目标选择器) 将其渲染的 DOM 移动到指定的 DOM 位置。
            效果:<Teleport> 只改变组件渲染的物理位置,不改变其在 Vue 组件树中的逻辑位置,也不改变 CSS 的作用域规则。主应用 main.ts 中导入的全局样式仍然会应用到被 Teleport 的组件上。
            结论:无法解决全局样式覆盖问题。
        c.在 App.vue 中直接渲染(绕过路由)
            做法:修改 App.vue 的模板,在某些条件下直接渲染 <CjsbgzmsIndex /> 组件,而不是通过 <router-view>。
            效果:这只是改变了组件的渲染路径,它仍然是主 Vue 应用的一部分。main.ts 中导入的全局样式依然会对其生效。
            结论:无法解决全局样式覆盖问题。
        d.总结
            创建一个完全独立的 Vue 应用实例是唯一理论上能隔离 main.ts 全局样式导入的方式,但它带来了极大的复杂性和维护成本,通常不推荐在单页应用中用于解决样式冲突。
            使用 <Teleport> 或在 App.vue 中直接渲染并不能隔离全局导入的 CSS。
            因此,手动挂载(尤其是指创建独立实例)并不是解决您面临的全局样式(特别是 src/design/ant/ 中的样式)覆盖 cjsbgzms/index.vue 内部 Antd 组件样式的理想或实用方案。
        e.总结2
            问题的核心仍然是全局 CSS 规则(尤其是针对 .ant-* 类名的规则)与组件内部样式(无论是 scoped CSS、CSS Modules 还是普通 CSS)之间的特异性竞争和作用范围。
            我们最好还是聚焦于之前讨论的方案:
            1.CSS Modules + 局部覆盖: 接受全局 Antd 样式,在模块内用 :global() 和更高特异性覆盖不想要的样式。
            2.Shadow DOM: 彻底隔离,但需验证 Antd 兼容性。
            3.修改全局样式: 从源头减少全局样式的侵入性。
    e.CSS Modules
        a.步骤
            将 <style lang="less" scoped> 改为 <style lang="less" module>。
            在 <script setup> 中导入 useCSSModule 并获取 $style 对象。
            更新模板中所有元素的 class 绑定,使用 $style 对象来引用模块化的类名。
        b.问题
            全局 Less 变量在 CSS Modules 中失效。
            :global() 选择器可能不够精确或存在优先级问题。
            类名绑定可能存在拼写错误或遗漏。
        c.说明
            实现方式: 了解如何在 Vue 3 单文件组件 (SFC) 中启用和使用 CSS Modules (<style module>),以及如何在 <template> 和 <script> 中引用模块化的类名。Vite 内建了对 CSS Modules 的支持,配置通常很简单。
            与 Ant Design Vue 的兼容性: 重点关注使用 CSS Modules 时,如何有效地覆盖或定制 Ant Design Vue 组件的内部样式。因为 Antd 组件本身可能不直接暴露所有内部元素的类名供 CSS Modules 使用,可能需要结合 :global() 伪类或其他技巧。
            隔离效果: 确认 CSS Modules 提供的基于哈希类名的作用域隔离是否足够强大,能够有效抵御 design/index.less 中定义的全局样式(特别是那些针对通用标签或 Antd 默认类名的样式)。
            开发体验: 评估从 scoped CSS 迁移到 CSS Modules 的工作量和对开发流程的影响。
        d.无法完全阻止
            您提出了一个非常关键的问题。
            简单来说,仅仅使用 CSS Modules 本身,通常无法完全阻止 src/design/ant/ 目录下定义的全局样式对 cjsbgzms/index.vue 组件内部使用的 Ant Design Vue 组件(如 <a-button>, <a-input>)产生影响。
        e.原因如下
            CSS Modules 的作用域:CSS Modules 主要解决的是您在组件自身 <style module> 中定义的 自定义类名 的作用域问题。它会将 .container, .search-form 等您自己定义的类名转换成唯一的哈希值(例如 ._container_abc123_),确保这些样式只应用于当前组件,并且不会污染全局或被全局样式意外覆盖。
            Ant Design Vue 组件的类名:当您在 cjsbgzms/index.vue 中使用 <a-button type="primary">查询</a-button> 时,Ant Design Vue 库会渲染出带有其预定义类名的 HTML 元素,例如 <button class="ant-btn ant-btn-primary">...</button>。这些类名 (ant-btn, ant-btn-primary) 是固定的,由 Ant Design Vue 库决定,CSS Modules 不会去修改它们。
            全局样式的目标:您在 src/design/ant/btn.less 等文件中定义的样式,是直接针对 Ant Design Vue 的这些预定义类名(如 .ant-btn, .ant-btn-primary)进行修改或覆盖的。由于这些类名在整个应用程序中是全局存在的(只要使用了 Antd 组件),并且 CSS Modules 不改变这些类名,所以这些全局的 Antd 样式修改 仍然会应用 到 cjsbgzms/index.vue 组件内部渲染出的 Antd 组件上。
        f.CSS Modules 能做什么?
            它可以保护您为 cjsbgzms/index.vue 组件自身布局(比如 .container, .content-area, .tree-card 等)编写的样式,使其不受全局样式干扰。
            它可以防止您在 cjsbgzms/index.vue 中定义的样式(如 .custom-input)意外地影响到项目其他地方的同名类。
        g.如何解决 Antd 全局样式覆盖问题(结合 CSS Modules)?
            a.说明
                虽然 CSS Modules 不能直接阻止全局 Antd 样式,但您可以结合其他 CSS 技巧在 模块内部 覆盖这些全局样式:
            b.提高特异性 + :global()
                在您的 index.module.css 文件中,您可以使用 :global() 伪类来选择全局的 Antd 类名,并结合模块自身的类名来提高特异性,从而覆盖全局 Antd 样式。
                /* in src/views/device/cjsbgzms/css/index.module.css */
                .container :global(.ant-btn-primary) {
                  /* 在 .container 内部覆盖全局 antd 按钮样式 */
                  background-color: #ff0000 !important; /* 示例:强制改为红色 */
                  border-color: #ff0000 !important;
                  /* ... 其他需要覆盖的样式 ... */
                }

                .customInput :global(.ant-input) {
                   /* 覆盖 .customInput 容器内的 antd 输入框样式 */
                   border-radius: 0 !important;
                }
            c.然后在模板中确保 Antd 组件在具有模块类名的父元素下:
                <template>
                  <div :class="$style.container">
                     ...
                     <a-button type="primary">查询</a-button>
                     ...
                     <div :class="$style.customInput">
                        <a-input placeholder="特殊输入框" />
                     </div>
                     ...
                  </div>
                </template>

                <style module src="./css/index.module.css"></style>
            d.这种方法的缺点是
                您仍然需要识别哪些全局 Antd 样式被覆盖了,并在本地模块中再次覆盖它们,可能需要大量使用 !important。
            e.对比 Shadow DOM:
                Shadow DOM 的优势在于它创建了一个真正的样式边界。
                默认情况下,外部文档(包括 src/design/ant/ 中的全局样式)的样式规则不会渗透到 Shadow DOM 内部。
                因此,理论上,如果 Ant Design Vue 组件能在 Shadow DOM 内正常工作,那么它可以完全隔离全局 Antd 样式的影响,
                无需在组件内部进行覆盖。但正如之前讨论的,Antd 与 Shadow DOM 的兼容性是最大的风险点。
            f.结论
                如果您希望 cjsbgzms/index.vue 组件内部使用的 Antd 组件完全不受 src/design/ant/ 中全局样式的影响,CSS Modules 本身做不到。您需要:
                要么 接受全局 Antd 样式,然后如果需要特殊样式,在 CSS Modules 中使用高特异性或 :global() 进行局部覆盖。
                要么 探索 Shadow DOM 的可行性(需要仔细测试 Antd 兼容性)。
                要么 重新考虑修改全局样式 (src/design/ant/),使其不那么具有侵入性(例如,只在特定全局容器下生效)。
        h.在模块内用 :global()
            a.说明
                在 CSS Modules 中,:global() 是一个非常有用的“逃生舱口”,它允许您在模块化的 CSS 文件 (.module.css 或 <style module>) 中显式地引用全局作用域的 CSS 类名或选择器,而不是让 CSS Modules 自动为它们生成唯一的哈希类名。
                具体来说,:global() 可以做到以下几点:
            b.选择全局类名
                您可以使用 :global(.some-global-class) 来选中一个在全局(例如在 main.ts 导入的 index.less 或第三方库如 Ant Design Vue 定义的)存在的类名。CSS Modules 不会处理 .some-global-class,会原样输出。
                /* in index.module.css */
                :global(.ant-btn) {
                  /* 这个样式会应用到页面上所有的 .ant-btn 元素 */
                  /* 通常不建议在模块内直接这样写,因为它失去了模块化的意义 */
                  /* border-radius: 0; */
                }
            c.选择全局 HTML 标签:类似地,:global(button) 可以选择全局的 <button> 标签。
                /* in index.module.css */
                :global(button) {
                   /* 应用到所有 button */
                   /* cursor: pointer; */
                }
            d.结合模块类名,提高特异性以覆盖全局样式(这是关键用途):
                这是 :global() 最常见的用途,特别是在处理像 Ant Design Vue 这样的 UI 库时。您可以将模块自身的类名(会被哈希化)与 :global() 结合,来精确地定位并覆盖那些位于您组件内部的、由第三方库生成的、带有全局类名的元素。
                示例: 假设您想在 cjsbgzms/index.vue 组件内部,将所有 Antd 的主按钮(.ant-btn-primary)的背景色改成特定的橙色,但又不影响项目中其他地方的主按钮。
                首先,将 index.vue 的样式改为 CSS Modules:
                <!-- src/views/device/cjsbgzms/index.vue -->
                <template>
                  <!-- 给根元素或其他容器加上模块类名 -->
                  <div :class="$style.cjsbgzmsWrapper">
                    ...
                    <a-button type="primary" @click="fun_search()">查询</a-button>
                    ...
                    <a-input
                      v-model:value="searchForm.equipmentNo"
                      placeholder="请输入设备号"
                      allow-clear
                      :class="$style.customInput" <!-- 也可以给具体元素加模块类 -->
                    />
                    ...
                  </div>
                </template>

                <script src="./js/index.js" type="module"></script>
                <!-- 修改 style 标签 -->
                <style module src="./css/index.module.css"></style>
            e.然后,在 index.module.css 文件中:
                /* src/views/device/cjsbgzms/css/index.module.css */

                /* 定义模块自己的类 */
                .cjsbgzmsWrapper {
                  padding: 20px;
                  border: 1px solid #eee;
                }

                .customInput {
                   /* 模块自己的样式 */
                   border: 1px dashed blue;
                }

                /* --- 使用 :global() 覆盖 Antd 样式 --- */

                /* 选择 .cjsbgzmsWrapper 内部的所有 .ant-btn-primary 元素 */
                .cjsbgzmsWrapper :global(.ant-btn-primary) {
                  background-color: orange !important; /* 覆盖全局样式 */
                  border-color: orange !important;
                  color: white !important; /* 可能也需要覆盖文字颜色 */
                }
                /* :global() 可以嵌套 */
                .cjsbgzmsWrapper :global(.ant-form-item-label > label::after) {
                    /* 覆盖表单项标签冒号的样式,仅在此组件内 */
                    content: ':' !important; /* 假设全局改成了中文冒号,这里改回来 */
                }

                /* 也可以针对特定模块类下的全局类 */
                .customInput :global(.ant-input) {
                    /* 覆盖 .customInput 元素内部的 antd 输入框样式 */
                    box-shadow: 0 0 5px rgba(0, 0, 255, 0.5) !important;
                }
            f.效果:
                .cjsbgzmsWrapper 会被编译成类似 ._cjsbgzmsWrapper_xyz789_ 的唯一类名。
                最终生成的 CSS 规则会类似:._cjsbgzmsWrapper_xyz789_ .ant-btn-primary { background-color: orange !important; ... }。
                由于这个选择器 (._cjsbgzmsWrapper_xyz789_ .ant-btn-primary) 比全局的 .ant-btn-primary 或 src/design/ant/btn.less 中的选择器具有更高的特异性(因为它包含了模块的唯一哈希类名作为祖先),所以它能成功覆盖全局样式,但仅限于那些位于带有 ._cjsbgzmsWrapper_xyz789_ 类名的元素内部的 .ant-btn-primary 按钮。
    f.Shadow DOM
        a.说明
            实现方式: 了解如何在 Vue 3 SFC 中通过 <style scoped shadowmode="open"> 或 <style shadowmode="open"> (非 scoped) 启用 Shadow DOM。
            与 Ant Design Vue 的兼容性: 这是关键的研究点。Ant Design Vue 的许多组件(如 Modal, Dropdown, Tooltip 等)可能会动态地将元素添加到 document.body 而非组件内部,或者依赖于全局注册的样式和 JavaScript。Shadow DOM 的强隔离性可能会破坏这些组件的功能或样式。需要查找社区是否有关于 Antd 在 Shadow DOM 中使用的成功案例或已知问题。
            隔离效果: Shadow DOM 提供理论上最强的样式隔离,全局样式(包括 design/index.less)默认无法穿透 Shadow Boundary。
            浏览器兼容性: 确认项目目标浏览器对 Shadow DOM (V1) 的支持情况。
            事件处理和外部交互: 了解 Shadow DOM 对事件冒泡和组件与外部 DOM 交互可能产生的影响。
        b.概念
            Shadow DOM 会为您的组件 (cjsbgzms/index.vue) 创建一个封装的 DOM 树。
            在此边界外部定义的样式(包括来自 main.ts 和 src/design/ant/ 的全局样式)默认情况下无法穿透到内部。
            在 Shadow DOM 边界内部(在组件的 <style> 标签内)定义的样式仅在内部应用。
        c.影响
            完全隔离: 这将有效地保护您的组件免受全局 src/design/ant/ 样式的影响,这正是您想要的。
            丢失 Ant Design Vue 样式: 由于全局样式被阻止,在 cjsbgzms/index.vue 内部使用的 Ant Design Vue 组件(<a-button>, <a-input> 等)也将丢失 Ant Design Vue 自身提供的默认样式。它们很可能会显示为无样式状态。
            需要在内部重新导入样式: 为了解决这个问题,我们必须在 Shadow DOM 边界内部显式导入必要的 Ant Design Vue 基础样式。我们将导入默认的 antd.less。
            组件自身样式: 您在 src/views/device/cjsbgzms/css/index.css 中的样式也需要加载到 Shadow DOM 内部。
        d.潜在风险(理解这一点至关重要)
            Ant Design Vue 兼容性: 这是最大的风险。一些复杂的 Ant Design Vue 组件(如 Modal、Drawer、Dropdown、Tooltip、Message/Notification)可能依赖于与主文档的交互(例如,将元素附加到 <body>)或期望某些全局样式/脚本可用。由于强封装性,这些组件在放入 Shadow DOM 内部时可能会损坏或功能异常。对 cjsbgzms/index.vue 中使用的所有 Antd 组件进行彻底测试将是必不可少的。
            事件处理: 在某些边缘情况下,跨越 Shadow 边界的事件传播行为可能有所不同。
            第三方库: 如果组件使用了其他与 DOM 交互密切的 UI 库或脚本,它们也可能面临兼容性问题。
        e.实施计划
            修改 src/views/device/cjsbgzms/index.vue。
            将现有的 <style scoped src="./css/index.css"></style> 标签替换为新的 <style shadowmode="open"> 块。
            在这个新块内部,我们将 @import 两个文件:
            默认的 Ant Design Vue 样式 (ant-design-vue/dist/antd.less)。
            您组件自己的样式 (./css/index.css)。

4.5 i18n国际化

01.i18n国际化:示例管理 -> 功能示例2
    a.位置
        a.路径
            src/locales/
            ├── helper.ts                # 辅助函数
            ├── setupI18n.ts            # i18n 初始化设置
            ├── useLocale.ts            # 语言切换相关操作
            └── lang/
                ├── en/                 # 英文翻译
                │   ├── common.ts
                │   ├── component.ts
                │   ├── layout.ts
                │   ├── routes/
                │   │   ├── basic.ts
                │   │   ├── dashboard.ts
                │   │   └── demo.ts
                │   └── sys.ts
                ├── zh-CN/              # 中文翻译
                │   ├── common.ts
                │   ├── component.ts
                │   ├── layout.ts
                │   ├── routes/
                │   │   ├── basic.ts
                │   │   ├── dashboard.ts
                │   │   └── demo.ts
                │   └── sys.ts
                ├── en.ts               # 英文入口文件
                └── zh_CN.ts            # 中文入口文件
        b.在项目中,语言包的组织结构是通过文件夹来实现的
            src/locales/lang/
              ├── zh-CN/
              │   ├── common.ts     # 对应 common.xxx
              │   ├── component.ts  # 对应 component.xxx
              │   └── sys.ts       # 对应 sys.xxx
              └── zh-CN.ts         # 汇总所有翻译
        c.在 zh-CN.ts 中会统一导入并组织这些模块
            // src/locales/lang/zh-CN.ts
            import common from './zh-CN/common';
            import component from './zh-CN/component';
            import sys from './zh-CN/sys';

            export default {
              message: {
                common,     // 这里会自动形成 common.xxx 的结构
                component,  // 形成 component.xxx 的结构
                sys,       // 形成 sys.xxx 的结构
              },
            };
        d.当你使用 t('common.cancelText') 时2
            common 来自文件路径 zh-CN/common.ts
            cancelText 来自该文件中导出对象的属性
    b.翻译配置流程
        a.目录结构
            src/
              ├── locales/
              │   ├── lang/
              │   │   ├── zh-CN/  # 中文翻译
              │   │   └── en-US/  # 英文翻译
              │   └── helper.ts   # i18n 辅助函数
        b.使用流程
            // 1. 在组件中引入 useI18n hook
            import { useI18n } from '/@/hooks/web/useI18n';

            // 2. 获取翻译函数
            const { t } = useI18n();

            // 3. 使用翻译
            t('common.cancelText')  // 会返回对应语言的"取消"文本
            -------------------------------------------------------------------------------------------------
            t('common.cancelText') 是一个国际化(i18n)翻译函数的调用。这是一种常见的多语言支持实现方式。让我详细解释一下:
            工作原理:
            // 通常在组件中先引入 useI18n hook
            import { useI18n } from '/@/hooks/web/useI18n';
            // 然后解构出 t 函数
            const { t } = useI18n();
            -------------------------------------------------------------------------------------------------
            t 函数的作用:
            t 是 translate(翻译)的简写
            它会根据当前设置的语言环境,返回对应的翻译文本
            'common.cancelText' 是一个翻译 key,用点号分隔的路径形式
        c.翻译配置示例
            // src/locales/lang/zh-CN.ts
            export default {
              common: {
                cancelText: '取消',
                okText: '确定',
                searchText: '搜索'
              }
            }

            // src/locales/lang/en-US.ts
            export default {
              common: {
                cancelText: 'Cancel',
                okText: 'OK',
                searchText: 'Search'
              }
            }
        d.说明
            这是一个典型的国际化(i18n)实现方式:
            所有文本都通过 key 来管理(如 'common.cancelText')
            不同语言的翻译放在对应的语言配置文件中
            通过 useI18n hook 获取当前语言环境的翻译
            使用 t() 函数根据 key 获取对应的翻译文本

02.问题(开发环境正常,但在生产环境(build后)显示 common.cancelText)
    a.Vite配置
        // vite.config.ts
        export default defineConfig({
          build: {
            rollupOptions: {
              // 确保 locales 目录被包含在打包中
              input: {
                main: resolve(__dirname, 'src/main.ts'),
                locales: resolve(__dirname, 'src/locales/**/*.ts'),
              }
            }
          }
        })
    b.检查 i18n 的初始化是否正确
        // src/locales/setupI18n.ts
        import { createI18n } from 'vue-i18n';
        import { localeSetting } from '/@/settings/localeSetting';
        import { set } from 'lodash-es';

        const { fallback, availableLocales } = localeSetting;

        export let i18n: ReturnType<typeof createI18n>;

        async function createI18nOptions() {
          const locale = localeSetting.locale;
          const defaultLocal = await import(`./lang/${locale}.ts`);
          const message = defaultLocal.default?.message ?? {};

          return {
            legacy: false, // 设置为 false
            locale,
            fallbackLocale: fallback,
            messages: {
              [locale]: message,
            },
            availableLocales,
            sync: true,
            silentTranslationWarn: true,
            missingWarn: false,
            silentFallbackWarn: true,
          };
        }

        export async function setupI18n(app: App) {
          const options = await createI18nOptions();
          i18n = createI18n(options);
          app.use(i18n);
        }
    c.确保语言包被正确导入
        // src/locales/lang/zh-CN.ts
        import common from './zh-CN/common';
        import component from './zh-CN/component';

        export default {
          message: {
            ...common,
            ...component,
          },
        };
    d.检查组件中的使用方式
        <script setup lang="ts">
        import { useI18n } from '/@/hooks/web/useI18n';

        // 确保在 setup 中调用
        const { t } = useI18n();
        </script>

        <template>
          <span>{{ t('common.cancelText') }}</span>
        </template>
    e.可以添加调试代码来检查
        // src/hooks/web/useI18n.ts
        export function useI18n() {
          const { t, ...methods } = i18n.global;

          // 添加调试
          const tProxy = (...args: Parameters<typeof t>) => {
            const result = t(...args);
            if (result === args[0]) {
              console.warn('Translation missing:', args[0]);
            }
            return result;
          };

          return {
            t: tProxy,
            ...methods,
          };
        }
    f.确保生产环境的语言包加载
        // src/locales/lang/zh-CN.ts
        import type { LocaleMessages } from 'vue-i18n';

        const modules = import.meta.glob('./zh-CN/**/*.ts', { eager: true });
        const messages: LocaleMessages<any> = {};

        Object.keys(modules).forEach((key) => {
          const mod = (modules[key] as any).default ?? {};
          const matched = key.match(/([A-Za-z0-9-_]+)\./i)[1];
          messages[matched] = mod;
        });

        export default {
          message: messages,
        };
    g.如果问题仍然存在,建议:
        在生产环境添加调试日志检查 i18n 实例是否正确初始化
        检查打包后的文件中是否包含语言配置
        使用 Chrome DevTools 的 Network 面板检查语言文件是否正确加载
        确认生产环境的构建配置是否正确处理了 i18n 相关文件

03.解决(开发环境正常,但在生产环境(build后)显示 common.cancelText)
    a.位置
        D:\software_ware\99.Directory99\vilgoboot-plus\pisces-boot-example\frontend\src\components\Application\src\search\AppSearchModal.vue:
           11              </a-input>
           12              <span :class="`${prefixCls}-cancel`" @click="handleClose">
           13:               {{ t('common.cancelText') }}
           14              </span>
           15            </div>
        -----------------------------------------------------------------------------------------------------
        D:\software_ware\99.Directory99\vilgoboot-plus\pisces-boot-example\frontend\src\components\Button\src\PopConfirmButton.vue:
           34            {
           35              okText: t('common.okText'),
           36:             cancelText: t('common.cancelText'),
           37            },
           38            { ...props, ...unref(attrs) }
        -----------------------------------------------------------------------------------------------------
        D:\software_ware\99.Directory99\vilgoboot-plus\pisces-boot-example\frontend\src\components\Drawer\src\props.ts:
           11    showCancelBtn: { type: Boolean, default: true },
           12    cancelButtonProps: Object as PropType<Recordable>,
           13:   cancelText: { type: String, default: t('common.cancelText') },
           14    /**
           15     * @description: Show confirmation button
        -----------------------------------------------------------------------------------------------------
        D:\software_ware\99.Directory99\vilgoboot-plus\pisces-boot-example\frontend\src\components\Modal\src\props.ts:
           14    draggable: { type: Boolean, default: true },
           15    centered: { type: Boolean },
           16:   cancelText: { type: String, default: t('common.cancelText') },
           17    okText: { type: String, default: t('common.okText') },
           18
    b.关键文件
        a.src\components\Modal\props.ts
            export const modalProps = {
              visible: { type: Boolean },
              scrollTop: { type: Boolean, default: true },
              height: { type: Number },
              minHeight: { type: Number },
              // open drag
              draggable: { type: Boolean, default: true },
              centered: { type: Boolean },
              cancelText: { type: String, default: t('common.cancelText') },
              okText: { type: String, default: t('common.okText') },
              closeFunc: Function as PropType<() => Promise<boolean>>,
        b.src\components\Form\props.ts
            export const basicProps = {
              model: {
                type: Object as PropType<Recordable>,
                default: {},
              },
              // 标签宽度  固定宽度
              labelWidth: {
                type: [Number, String] as PropType<number | string>,
                default: 0,
              },
    c.BasicForm.vue 是如何使用 i18n 多语言的 common.okText 和 common.cancelText
        a.首先,我看到在 Modal/src/props.ts 中定义了这些文本的默认值:
            import { useI18n } from '/@/hooks/web/useI18n';
            const { t } = useI18n();

            export const modalProps = {
              // ...
              cancelText: { type: String, default: t('common.cancelText') },
              okText: { type: String, default: t('common.okText') },
              // ...
            };
        b.这里的调用链路是
            BasicForm.vue 通过 props 引入了 basicProps
            basicProps 继承了 modalProps 的所有属性 (通过 Object.assign({}, modalProps, {...}))
            modalProps 中使用了 useI18n hook 来获取多语言功能
            通过 t('common.cancelText') 和 t('common.okText') 来获取对应的多语言文本
        c.BasicForm.vue 确实是通过 props.ts 间接调用了 i18n 的多语言功能。这种设计模式的好处是:
            实现了多语言文本的集中管理
            组件可以通过 props 轻松覆盖默认的多语言文本
            保持了代码的可维护性和复用性
    d.最终解决
        20250126,src/components/Modal/src/props.ts将cancelText、okText写为固定值取消、确认
        cancelText: { type: String, default: '取消' },
        okText: { type: String, default: '确认' },

4.6 重复注册路由

01.问题描述
    a.报错
        Uncaught Error: A route named "PageNotFound" has been added as a child of a route with the same name. Route names must be unique and a nested route cannot use the same name as an ancestor.
            checkSameNameAsAncestor vue-router.mjs:1789
            addRoute vue-router.mjs:1503
            addRoute vue-router.mjs:1516
            createRouterMatcher vue-router.mjs:1648
            createRouterMatcher vue-router.mjs:1648
            createRouter vue-router.mjs:3092
            <anonymous> index.ts:17
    b.AI分析
        错误信息解析
        这个错误明确指出:一个名为 "PageNotFound" 的路由被添加为同名路由的子路由
        代码分析
        在 src/router/routes/modules/demo/page.ts 中,我们看到 PageNotFound 是作为 ExceptionPage 的子路由存在的
        而在之前的 index.ts 中,我们也定义了一个顶级的 PageNotFound 路由
        可能的场景
        这个错误可能发生在动态添加路由的过程中
        很可能是权限路由和基础路由的注册过程中出现了重复
        需要检查的文件
        我建议检查以下文件:
        src/router/guard/permissionGuard.ts - 权限守卫可能在动态添加路由
        src/store/modules/user.ts - 用户模块可能涉及权限路由的处理
        src/router/constant.ts - 可能定义了基础路由配置

02.方式1
    a.分析
        package.json:版本号前的符号
        "vue-router": "^4.0.14":
        ^ 符号表示“插入符号”版本范围
        允许更新到 4.x.x 版本中的最新版本,但不包括 5.0.0 或更高版本
        例如,如果有 4.1.0 或 4.0.15 发布,使用 ^4.0.14 会自动更新到这些版本
        -----------------------------------------------------------------------------------------------------
        "vue-router": "4.0.14":
        没有前缀符号,表示精确版本
        只安装 4.0.14 版本,不会自动更新到任何其他版本,即使有补丁或次要版本更新
        -----------------------------------------------------------------------------------------------------
        故而,将package.json中的"vue-router": "^4.0.14",修改为"vue-router": "4.0.14"
    b.修改版本依赖后
        更新依赖:运行 pnpm install 命令。这将根据 package.json 中的版本要求更新 node_modules 中的依赖,并更新 pnpm-lock.yaml 文件以反映这些更改
        检查 pnpm-lock.yaml:pnpm install 会自动更新 pnpm-lock.yaml 文件以匹配 package.json 中的新版本要求。你可以查看 pnpm-lock.yaml 文件,确保 vue-router 的版本已经更新为 4.0.14
        验证项目:运行项目的测试或启动项目,确保更改后的依赖版本不会引入新的问题

03.方式2
    a.GitHub issues
        A route named "PageNotFound" has been added as a child of a route with the same name
    b.src\router\routes\basic.ts,【网友推荐】
        export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = {
          path: '/:path(.*)*',
          name: PAGE_NOT_FOUND_NAME,
          component: LAYOUT,
          meta: {
            title: 'ErrorPage',
            hideBreadcrumb: true,
            hideMenu: true,
          },
          children: [
            {
              path: '/:path(.*)*',
              name: PAGE_NOT_FOUND_NAME.concat('2'),   // 从 PAGE_NOT_FOUND_NAME 修改为 PAGE_NOT_FOUND_NAME.concat('2')
              component: EXCEPTION_COMPONENT,
              meta: {
                title: 'ErrorPage',
                hideBreadcrumb: true,
                hideMenu: true,
              },
            },
          ],
        };
    c.src\router\routes\basic.ts,【jeecg作者推荐】
        export const PAGE_NOT_FOUND_ROUTE: AppRouteRecordRaw = {
          path: '/:path(.*)*',
          name: 'PageNotFound404',                    // 直接指定为PageNotFound404,指定死
          component: LAYOUT,
          meta: {
            title: 'ErrorPage',
            hideBreadcrumb: true,
            hideMenu: true,
          },
          children: [
            {
              path: '/:path(.*)*',
              name: PAGE_NOT_FOUND_NAME,
              component: EXCEPTION_COMPONENT,
              meta: {
                title: 'ErrorPage',
                hideBreadcrumb: true,
                hideMenu: true,
              },
            },
          ],
        };

04.方式3
    a.分析
        由于pnpm.lock会将版本升到4.0.15
        -----------------------------------------------------------------------------------------------------
        vue-router@^4.0.14:
          version "4.5.0"
          resolved "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.0.tgz#58fc5fe374e10b6018f910328f756c3dae081f14"
          integrity sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==
          dependencies:
            "@vue/devtools-api" "^6.6.4"
        -----------------------------------------------------------------------------------------------------
        vue-router@^4.0.14:
        表示这是一个符合 ^4.0.14 版本范围的 vue-router 依赖项
        version "4.5.0":
        实际安装的 vue-router 版本是 4.5.0。这个版本符合 ^4.0.14 的版本范围要求
        **resolved "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.0.tgz
        #58fc5f
        e374e10b6018f910328f756c3dae081f14"**:
        这是 vue-router 版本 4.5.0 的下载地址,指向一个具体的 tarball 文件
        #58fc5fe374e10b6018f910328f756c3dae081f14 是该文件的哈希值,用于确保下载的文件完整性
        integrity sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==:
        这是一个完整性校验值,使用 SHA-512 算法生成。用于验证下载的包没有被篡改
        dependencies:
        列出了 vue-router 的依赖项
        "@vue/devtools-api" "^6.6.4":表示 vue-router 依赖于 @vue/devtools-api,版本范围为 ^6.6.4
    b.方式1
        放弃pnpm,使用yarn
    c.方式2
        最简单方案,固定vue-router版本号
        第1步:"vue-router": "^4.0.14" 变成 "4.0.14"
        第2步:rm -f package-lock.json yarn.lock pnpm-lock.yaml
        第3步:pnpm install

4.7 组件无法使用

01.问题
    sales
        components
            SaleArticleForm.vue
            SaleArticleModal.vue
        SaleArticle.api.ts
        SaleArticle.data.ts
        SaleArticle_menu_insert.sql
        SaleArticleAuditList.vue
        SaleArticleList.vue
    ---------------------------------------------------------------------------------------------------------
    MonthPicker                       DatePicker.MonthPicker
    JTreeDict                         src/components/Form/src/jeecg/components/JTreeDict.vue
    JSelectMultiple                   src/components/Form/src/jeecg/components/JSelectMultiple.vue

02.搜索结果
    Searching 1429 files for "JSelectMultiple" (regex, case sensitive)

    D:\software_yare\my-erp_test\jeecg-vue3\src\components\Form\index.ts:
       17  export { default as JCodeEditor } from './src/jeecg/components/JCodeEditor.vue';
       18  export { default as JCategorySelect } from './src/jeecg/components/JCategorySelect.vue';
       19: export { default as JSelectMultiple } from './src/jeecg/components/JSelectMultiple.vue';
       20  export { default as JPopup } from './src/jeecg/components/JPopup.vue';
       21  export { default as JAreaSelect } from './src/jeecg/components/JAreaSelect.vue';

    D:\software_yare\my-erp_test\jeecg-vue3\src\components\Form\src\componentMap.ts:
       43  import JCodeEditor from './jeecg/components/JCodeEditor.vue';
       44  import JCategorySelect from './jeecg/components/JCategorySelect.vue';
       45: import JSelectMultiple from './jeecg/components/JSelectMultiple.vue';
       46  import JPopup from './jeecg/components/JPopup.vue';
       47  import JSwitch from './jeecg/components/JSwitch.vue';
       ..
      112  componentMap.set('JCodeEditor', JCodeEditor);
      113  componentMap.set('JCategorySelect', JCategorySelect);
      114: componentMap.set('JSelectMultiple', JSelectMultiple);
      115  componentMap.set('JPopup', JPopup);
      116  componentMap.set('JSwitch', JSwitch);

    D:\software_yare\my-erp_test\jeecg-vue3\src\components\Form\src\jeecg\components\JSelectMultiple.vue:
       26    const { createMessage, createErrorModal } = useMessage();
       27    export default defineComponent({
       28:     name: 'JSelectMultiple',
       29      components: {},
       30      inheritAttrs: false,

    D:\software_yare\my-erp_test\jeecg-vue3\src\components\Form\src\types\index.ts:
      127    | 'JCodeEditor'
      128    | 'JCategorySelect'
      129:   | 'JSelectMultiple'
      130    | 'JPopup'
      131    | 'JSwitch'

    D:\software_yare\my-erp_test\jeecg-vue3\src\views\demo\jeecg\Native\one\components\OneNativeForm.vue:
      150          <a-col :span="24">
      151            <a-form-item label="下拉多选" :labelCol="labelCol" :wrapperCol="wrapperCol" v-bind="validateInfos.zddtjxl">
      152:             <JSelectMultiple v-model:value="formState.zddtjxl" placeholder="请选择下拉多选" dictCode="sex"></JSelectMultiple>
      153            </a-form-item>
      154          </a-col>
      ...
      245    import JSelectInput from '/@/components/Form/src/jeecg/components/JSelectInput.vue';
      246    import JSelectPosition from '/@/components/Form/src/jeecg/components/JSelectPosition.vue';
      247:   import JSelectMultiple from '/@/components/Form/src/jeecg/components/JSelectMultiple.vue';
      248    import JInput from '/@/components/Form/src/jeecg/components/JInput.vue';
      249    import JSelectDept from '/@/components/Form/src/jeecg/components/JSelectDept.vue';

    D:\software_yare\my-erp_test\jeecg-vue3\src\views\kpitask\kpiDefinition\KpiDefinition.data.ts:
      238      label: '来源部门',
      239      field: 'datasourceDept',
      240:     component: 'JSelectMultiple',
      241      componentProps: {
      242        dictCode: 'kpi_dept,dept_name,dept_code',
      ...
      253      label: '考核对象',
      254      field: 'targetDept',
      255:     component: 'JSelectMultiple',
      256      componentProps: {
      257        dictCode: 'kpi_dept,dept_name,dept_code',

    D:\software_yare\my-erp_test\jeecg-vue3\src\views\sales\vue3\SaleArticle.data.ts:
       71      label: '状态',
       72      field: 'status',
       73:     component: 'JSelectMultiple',
       74      colProps: { span: 4 },
       75      componentProps: {

    D:\software_yare\my-erp_test\jeecg-vue3\src\views\truck\truck\JgTruck.data.ts:
       82      label: '备注',
       83      field: 'descDoc',
       84:     component: 'JSelectMultiple',
       85      componentProps:{
       86          dictCode:""

    D:\software_yare\my-erp_test\jeecg-vue3\src\views\yd\tree\YdBizTree.data.ts:
       51          label: '选择责任部门',
       52          field: 'responsibleUnits',
       53:         component: 'JSelectMultiple',
       54          componentProps: {
       55              dictCode: 'yd_responsible_unit,unit_name,id',

03.注册路径
    a.src/views/sales/vue3/SaleArticle.data.ts
        //查询数据
        export const searchFormSchema: FormSchema[] = [
          {
            label: '类型',
            field: 'types',
            component: 'JTreeDict',
            colProps: { span: 2 },
            componentProps: {
              parentCode: 'B03',
            },
          },
          {
            label: '发起日期',
            field: 'examineMonth',
            component: 'MonthPicker',
            colProps: {span: 4},
            componentProps: {
              valueFormat:'YYYY-MM',
            },
          },
          {
            label: '状态',
            field: 'status',
            component: 'JSelectMultiple',
            colProps: { span: 4 },
            componentProps: {
              dictCode: 'sale_status',
            },
          },
        ];
    b.src/components/Form/src/types/index.ts
        export type ComponentType =
          | 'Input'
          | 'InputGroup'
          | 'InputPassword'
          | 'InputSearch'
          | 'InputTextArea'
          | 'JAreaLinkage'
          | 'JSelectPosition'
          | 'JSelectRole'
          | 'JSelectUser'
          | 'JImageUpload'
          | 'JDictSelectTag'
          | 'JSelectDept'
          | 'JAreaSelect'
    c.通常的注册方式有两种:
        a.第1种:在main.ts中直接注册:
            import { JEditor, JUpload } from '/@/components/Form';
            app.component('JEditor', JEditor);
            app.component('JUpload', JUpload);
        b.第2种:src\components\Form\src\componentMap.ts
            // src/components/Form/src/componentMap.ts
            componentMap.set('JEditor', JEditor);
            componentMap.set('JUpload', JUpload);
    d.这个 componentMap 会在 Form 组件内部使用
        a.src\components\Form\src\components\FormItem.vue
            // src/components/Form/src/BasicForm.vue
            import { componentMap } from '../componentMap';
            // 根据 component 类型渲染对应组件
            const Comp = componentMap.get(schema.component);
        b.组件的使用链路:
            Form Schema定义
            -> componentMap查找组件
            -> 动态渲染组件
    e.建议的解决方案
        a.检查 vite 配置,确保组件被正确打包
            // vite.config.ts
            build: {
              rollupOptions: {
                external: [],
                output: {
                  manualChunks: {
                    'jeecg-components': ['/@/components/Form/src/jeecg/components']
                  }
                }
              }
            }
        b.使用异步组件注册
            componentMap.set('JEditor', defineAsyncComponent(() =>
              import('./jeecg/components/JEditor.vue')
            ));
        c.确保组件在使用前已注册
            // main.ts
            import { useForm } from '/@/components/Form';
            app.use(useForm);

04.FormItem.vue 是如何使用 componentMap
    a.工作流程:
        FormItem 组件接收 schema 配置
        从 schema 中获取 component 属性
        使用 componentMap.get(component) 获取对应的组件
        渲染该组件并传入相应的属性
    b.在 FormItem.vue 中的导入和使用(src\components\Form\src\components\FormItem.vue)
        // FormItem.vue
        import { componentMap } from '../componentMap';

        export default defineComponent({
          setup(props) {
            // 检查组件是否注册
            if (!componentMap.has(component)) {
              return null;
            }

            // 渲染组件的函数
            function renderComponent() {
              const { component } = props.schema;
              const Comp = componentMap.get(component);

              // 组件属性处理
              const compAttr: Recordable = {
                ...unref(getComponentsProps),
                ...props.formProps,
              };

              return <Comp {...compAttr} />;
            }

            return () => {
              // ... 其他渲染逻辑
              return renderComponent();
            };
          }
        });
    c.componentMap 的定义 (src/components/Form/src/componentMap.ts):
        import type { Component } from 'vue';
        import type { ComponentType } from './types/index';

        const componentMap = new Map<ComponentType, Component>();

        // 注册基础组件
        componentMap.set('Input', Input);
        componentMap.set('JEditor', JEditor);
        componentMap.set('JUpload', JUpload);
        // ... 其他组件注册

        export { componentMap };
    d.ComponentType 的定义 (src/components/Form/src/types/index.ts):
        export type ComponentType =
          | 'Input'
          | 'JEditor'
          | 'JUpload'
          // ... 其他组件类型
    e.在表单配置中的使用 (src/views/sales/vue3/SaleArticle.data.ts):
        const formSchema: FormSchema[] = [
          {
            label: '文章',
            field: 'article',
            component: 'JEditor',  // 使用注册的组件
          },
          {
            label: '附件',
            field: 'fileName',
            component: 'JUpload',  // 使用注册的组件
          }
        ];

05.JTreeDict报错
    a.问题
        Warning: Invalid prop `value` supplied to `TreeSelect`. You should use { label: string, value: string | number } or [{ label: string, value: string | number }] instead. warning.js:6:12
        JTreeDict 组件已挂载
        -----------------------------------------------------------------------------------------------------
        export function warning(valid, message) {
          // Support uglify
          if (process.env.NODE_ENV !== 'production' && !valid && console !== undefined) {
            console.error("Warning: ".concat(message));
          }
        }
    b.正确的 value 格式应该是:
        // 单选时
        const value = {
          label: 'label文本',
          value: '值'
        };

        // 多选时
        const value = [
          { label: 'label1', value: 'value1' },
          { label: 'label2', value: 'value2' }
        ];
    c.查看 JTreeDict 的使用代码 (SaleArticle.data.ts):
        {
          label: '类型',
          field: 'types',
          component: 'JTreeDict',
          componentProps: {
            parentCode: 'B03',
          }
        }
    d.问题原因:
        TreeSelect 组件期望接收一个包含 label 和 value 的对象
        但是当前传入的可能是简单的字符串或数组
        这导致了类型不匹配的警告
    e.解决方案:
        // 在 JTreeDict 组件中处理值的转换
        const formatValue = (val) => {
          if (!val) return undefined;

          // 如果是字符串,转换为对象
          if (typeof val === 'string') {
            return {
              label: val,  // 这里可能需要根据 value 查找对应的 label
              value: val
            };
          }

          // 如果是数组,转换每一项
          if (Array.isArray(val)) {
            return val.map(item => ({
              label: item,
              value: item
            }));
          }

          return val;
        };

        // 在组件中使用
        const treeValue = computed(() => formatValue(props.value));
        -----------------------------------------------------------------------------------------------------
        这样修改后:
        组件内部会自动处理值的格式转换
        对外仍然保持简单的字符串/数组接口
        消除 TreeSelect 的类型警告

06.其他内容
    a.配置
        http://127.0.0.1:3100/src/layouts/default/header/components/user-dropdown/DropMenuItem.vue
    b.分类字典
        销售中心
            每日价格
                板带
                冷系
                长材
                焊管
                副产品
                铁水
                长周期价格
                附说明
            政策发布
                销售政策
                提成政策
                作业标准
            数据维护
                冷轧销售处
                板材销售处
                长材销售处
                副产品销售处
                销售管理处
        -----------------------------------------------------------------------------------------------------
        sys_category
        http://127.0.0.1:3100/jeecgboot/sys/category/childList?pid=1710160530146082817&_t=1737948241529
        http://127.0.0.1:3100/jeecgboot/sys/category/rootList?column=createTime&order=desc&pageNo=1&pageSize=10&_t=1737949075020
        http://127.0.0.1:3100/jeecgboot/sys/category/getChildListBatch?parentIds=1710160479223037954,1710160530146082817&_t=1737949075084
    c.数据字典
        sys_dict
        sys_dict_item
        http://127.0.0.1:3100/jeecgboot/sys/dict/list?column=createTime&order=desc&pageNo=1&pageSize=10&_t=1737949182425
        -----------------------------------------------------------------------------------------------------
        销售发完状态
        sale_status
        销售发布文章的状态
        未审核
        审核通过
        审核不通过
    d.销售管理 -> 管理中心
        http://127.0.0.1:3100/jeecgboot/saleArticle/list?column=createTime&order=desc&pageNo=1&pageSize=10&_t=1737949226479
        -----------------------------------------------------------------------------------------------------
        http://127.0.0.1:3100/jeecgboot/sys/dict/getDictItems/sale_status
        {
            "success": true,
            "message": "",
            "code": 0,
            "result": [
                {
                    "value": "未审核",
                    "text": "未审核",
                    "displayTag": "0",
                    "tagColor": null,
                    "tagIcon": null,
                    "title": "未审核",
                    "label": "未审核"
                },
                {
                    "value": "审核通过",
                    "text": "审核通过",
                    "displayTag": "0",
                    "tagColor": null,
                    "tagIcon": null,
                    "title": "审核通过",
                    "label": "审核通过"
                },
                {
                    "value": "审核不通过",
                    "text": "审核不通过",
                    "displayTag": "0",
                    "tagColor": null,
                    "tagIcon": null,
                    "title": "审核不通过",
                    "label": "审核不通过"
                }
            ],
            "timestamp": 1737949226251
        }
        -----------------------------------------------------------------------------------------------------
        http://127.0.0.1:3100/jeecgboot/sys/category/loadTreeRoot?async=false&pcode=B03&_t=1737949558274
        {
            "success": true,
            "message": "",
            "code": 0,
            "result": [
                {
                    "key": "1710160530146082817",
                    "title": "每日价格",
                    "icon": null,
                    "parentId": "1710160479223037954",
                    "value": null,
                    "code": "B03A01",
                    "children": [
                        {
                            "key": "1710160804562616321",
                            "title": "板带",
                            "icon": null,
                            "parentId": "1710160530146082817",
                            "value": null,
                            "code": "B03A01A01",
                            "children": null,
                            "leaf": true
                        },
                        {
                            "key": "1710160833805303810",
                            "title": "冷系",
                            "icon": null,
                            "parentId": "1710160530146082817",
                            "value": null,
                            "code": "B03A01A02",
                            "children": null,
                            "leaf": true
                        },
                        {
                            "key": "1710160859776434178",
                            "title": "长材",
                            "icon": null,
                            "parentId": "1710160530146082817",
                            "value": null,
                            "code": "B03A01A03",
                            "children": null,
                            "leaf": true
                        },
                        {
                            "key": "1710160892034826241",
                            "title": "焊管",
                            "icon": null,
                            "parentId": "1710160530146082817",
                            "value": null,
                            "code": "B03A01A04",
                            "children": null,
                            "leaf": true
                        },
                        {
                            "key": "1710160957608574977",
                            "title": "副产品",
                            "icon": null,
                            "parentId": "1710160530146082817",
                            "value": null,
                            "code": "B03A01A05",
                            "children": null,
                            "leaf": true
                        },
                        {
                            "key": "1710160987992113154",
                            "title": "铁水",
                            "icon": null,
                            "parentId": "1710160530146082817",
                            "value": null,
                            "code": "B03A01A06",
                            "children": null,
                            "leaf": true
                        },
                        {
                            "key": "1710161036964806658",
                            "title": "长周期价格",
                            "icon": null,
                            "parentId": "1710160530146082817",
                            "value": null,
                            "code": "B03A01A07",
                            "children": null,
                            "leaf": true
                        },
                        {
                            "key": "1710161074721931266",
                            "title": "附加说明",
                            "icon": null,
                            "parentId": "1710160530146082817",
                            "value": null,
                            "code": "B03A01A08",
                            "children": null,
                            "leaf": true
                        }
                    ],
                    "leaf": false
                },
                {
                    "key": "1710160560907108354",
                    "title": "政策发布",
                    "icon": null,
                    "parentId": "1710160479223037954",
                    "value": null,
                    "code": "B03A02",
                    "children": [
                        {
                            "key": "1710161187255107586",
                            "title": "销售政策",
                            "icon": null,
                            "parentId": "1710160560907108354",
                            "value": null,
                            "code": "B03A02A01",
                            "children": null,
                            "leaf": true
                        },
                        {
                            "key": "1710161221472239617",
                            "title": "提成政策",
                            "icon": null,
                            "parentId": "1710160560907108354",
                            "value": null,
                            "code": "B03A02A02",
                            "children": null,
                            "leaf": true
                        },
                        {
                            "key": "1710161307363196930",
                            "title": "作业标准",
                            "icon": null,
                            "parentId": "1710160560907108354",
                            "value": null,
                            "code": "B03A02A03",
                            "children": null,
                            "leaf": true
                        }
                    ],
                    "leaf": false
                },
                {
                    "key": "1710160771041738753",
                    "title": "数据维护",
                    "icon": null,
                    "parentId": "1710160479223037954",
                    "value": null,
                    "code": "B03A03",
                    "children": [
                        {
                            "key": "1710161359099936769",
                            "title": "冷轧销售处",
                            "icon": null,
                            "parentId": "1710160771041738753",
                            "value": null,
                            "code": "B03A03A01",
                            "children": null,
                            "leaf": true
                        },
                        {
                            "key": "1710161408232013825",
                            "title": "板材销售处",
                            "icon": null,
                            "parentId": "1710160771041738753",
                            "value": null,
                            "code": "B03A03A02",
                            "children": null,
                            "leaf": true
                        },
                        {
                            "key": "1710161444181393410",
                            "title": "长材销售处",
                            "icon": null,
                            "parentId": "1710160771041738753",
                            "value": null,
                            "code": "B03A03A03",
                            "children": null,
                            "leaf": true
                        },
                        {
                            "key": "1710161538163163138",
                            "title": "副产品销售处",
                            "icon": null,
                            "parentId": "1710160771041738753",
                            "value": null,
                            "code": "B03A03A04",
                            "children": null,
                            "leaf": true
                        },
                        {
                            "key": "1710161573605031937",
                            "title": "销售管理处",
                            "icon": null,
                            "parentId": "1710160771041738753",
                            "value": null,
                            "code": "B03A03A05",
                            "children": null,
                            "leaf": true
                        }
                    ],
                    "leaf": false
                }
            ],
            "timestamp": 1737949558440
        }

4.8 请求网址与Token

01.js文件
    a.引入
        import { getToken } from '/@/utils/auth';
        import { useGlobSetting } from '/@/hooks/setting';
    b.地址
        wsUrl: useGlobSetting().domainUrl,                                                           --修改1
    c.请求
        async queryStopReasonType() {
          let _this = this;
          _this.loading = true;
          await axios({
            url: _this.wsUrl + '/public/stopReasonType',
            method: 'GET',
            headers: {
              'X-Access-Token': getToken(),                                                          --修改2
              'Content-Type': 'application/json;charset=UTF-8',
            },
          }).then((res) => {
            if (res.data.success) {
              _this.attrCnts.stopReasonNameOptions = res.data.result.map((item) => {
                return { label: item.stopReasonType, value: item.stopReasonType };
              });
            } else {
              ElMessage.error(res.data.message || '获取下拉框_故障模式失败');
            }
            _this.loading = false;
          });
        },

02.配置说明
    a.html项目
        a.山西电网
            import {getDkyUserInfo, timeFormat, watermark, wsConfig} from "../../../static/js/common.js";
            wsUrl: wsConfig.frontendUrl,
            wsUrl: Common.wsConfig.frontendUrl,
        b.设备异常信息共享平台
            wsUrl: 'http://localhost:9994/pisces-boot',
        c.解决:使用dev/prod全局网址
            wsUrl: useGlobSetting().domainUrl,
            import { useGlobSetting } from '/@/hooks/setting';
    b.vue项目
        a.env.development
            VITE_GLOB_API_URL=/jeecg-boot
            VITE_GLOB_DOMAIN_URL=http://localhost:9994/jeecg-boot
        b.env.production
            VITE_GLOB_API_URL=/jeecg-boot
            VITE_GLOB_DOMAIN_URL=http://erpfinance.app.myslayers.cn/jeecg-boot
        c.解决:www/pisces/_app.config.js
            VITE_GLOB_DOMAIN_URL=http://erpfinance.app.myslayers.cn/jeecg-boot
            修改为
            VITE_GLOB_DOMAIN_URL=http://localhost:9994/jeecg-boot
    c.核心:DNS解析
        a.报错
            GET http://localhost:9994/pisces-boot/public/plNoList net::ERR_CONNECTION_REFUSED
           (匿名) @ jeecg.0fb61e2e.js:138
            jeecg.0fb61e2e.js:141 WebSocket connection to 'ws://localhost:9994/pisces-boot/websocket/e9ca23d68d884d4ebb19d07889727dae_c14a05855a4f69512e84d9cb93834937' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED
            _e @ jeecg.0fb61e2e.js:141了解此错误
        b.解决:hosts配置域名
            10.70.20.87 erpfinance.app.myslayers.cn