# jeedp-report **Repository Path**: ydc123/jeedp-report ## Basic Information - **Project Name**: jeedp-report - **Description**: java快速开发平台-报表平台版 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-09 - **Last Updated**: 2026-09-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README
# Jeedp 快速开发框架 **Java 企业级快速开发平台 | 开箱即用 | 多端统一**
--- ## 简介 Jeedp 是一套基于 Spring Boot 的企业级快速开发平台,提供完整的用户权限管理、数据权限控制、操作审计、系统监控等基础能力。前端采用 Vue 3 + Arco Design,移动端采用 UniApp,实现 Web 端与移动端统一架构。 ## 技术栈 | 层级 | 技术 | 版本 | |------|------|------| | 后端框架 | Spring Boot | 2.7.18 | | 权限认证 | Sa-Token | 1.46.0 | | ORM | MyBatis-Plus | 3.5.17 | | 连接池 | Druid | 1.2.28 | | 缓存 | Redis | - | | 配置加密 | Jasypt | 3.0.5 | | 工具库 | Hutool | 5.8.47 | | JSON | Fastjson2 | 2.0.65 | | 系统监控 | OSHI | 6.12.0 | | 表格导出 | Apache Fesod (incubating) | 2.0.1-incubating | | 报表引擎 | 积木报表 JimuReport | 2.3.4 | | 大屏/仪表盘 | 积木BI JimuBI | 2.3.2.1 | | Web 前端 | Vue 3 + Arco Design | Vue 3.4 / Arco 2.56 | | 移动端 | UniApp | Vue 3 | | 构建工具 | Vite | 5.3 | | 数据库 | PostgreSQL | - | | JDK | JDK | 1.8 | ## 功能模块 ### 系统管理 | 模块 | 说明 | |------|------| | 用户管理 | 系统用户配置,支持数据权限过滤 | | 机构管理 | 组织机构树形维护(公司/部门/小组) | | 岗位管理 | 用户职务配置 | | 角色管理 | 角色权限分配、数据范围划分、子角色配置 | | 菜单管理 | 系统菜单、路由、按钮权限标识配置 | | 字典管理 | 系统常用固定数据维护 | | 参数管理 | 系统动态配置参数 | ### 系统监控 | 模块 | 说明 | |------|------| | 在线用户 | 当前活跃用户状态监控,支持强退 | | 定时任务 | 在线添加/修改/删除任务调度,含执行日志 | | 操作日志 | 系统正常操作与异常信息日志记录 | | 登录日志 | 登录记录查询,包含异常登录信息 | | 服务监控 | CPU、内存、磁盘、JVM 等系统信息 | | 缓存监控 | 系统缓存信息查询与命令统计 | ### 通知公告 系统公告信息发布与维护,支持已读状态追踪。 ### 报表中心 | 模块 | 说明 | |------|------| | 报表设计 | 积木报表在线设计器(`/jmreport/list`),数据源/数据集/导出 | | 大屏设计 | 积木BI 页面设计器(`/drag/list`),仪表盘与大屏 | | 报表挂菜单 | 菜单管理里直接选用报表/大屏作为内链菜单,自动回填路由、内链地址与权限标识 | | 入口鉴权 | 预览页按 `jmreport:view:{id}` / `drag:view:{id}` 判定,拿到 URL 也无法打开 | ## 项目结构 ``` jeedp-report/ ├── jeedp-report-server/ # 后端服务(Spring Boot) │ ├── src/main/java/com/jeedp/ │ │ ├── common/ # 公共模块(工具类、注解、异常、常量) │ │ ├── config/ # 配置类(拦截器、数据源、加密等) │ │ ├── framework/ # 框架集成(Sa-Token 权限实现) │ │ ├── monitor/ # 监控模块(在线用户、任务调度、服务监控) │ │ ├── report/ # 积木报表集成(资源只读查询、入口鉴权、地址约定) │ │ └── system/ # 系统管理模块(用户、角色、菜单、机构等) │ ├── sql/ # 数据库初始化脚本(单文件版,内部分 4 段) │ ├── tools/ # 构建期校验工具(字节码链接检查等) │ └── pom.xml ├── jeedp-report-web/ # Web 管理端(Vue 3 + Arco Design) │ ├── src/ │ │ ├── api/ # 接口请求 │ │ ├── views/ # 页面组件 │ │ ├── layout/ # 布局组件 │ │ ├── router/ # 路由配置 │ │ └── store/ # 状态管理(Pinia) │ └── package.json ├── jeedp-app-uniapp/ # 移动端(UniApp + Vue 3) │ ├── pages/ # 页面 │ ├── api/ # 接口请求 │ ├── store/ # 状态管理(Pinia) │ └── manifest.json └── jeedp-report-doc/ # 项目文档 ``` ## 快速开始 ### 环境要求 - JDK 1.8+ - PostgreSQL 12+ - Redis 6+ - Node.js 16+ - Maven 3.6+ ### 1. 克隆项目 ```bash git clone <仓库地址> ``` ### 2. 初始化数据库 ```bash # 建库(库名含连字符必须加引号,且与 application-dev.yml 里的 url 保持一致) psql -U postgres -c "CREATE DATABASE \"jeedp-report\" WITH ENCODING 'UTF8'" # 一个脚本建好全部对象:jeedp 业务表 + 积木报表/积木BI 23 张表 + 报表中心菜单 psql -U postgres -d jeedp-report -f jeedp-report-server/sql/jeedp_report_full_postgres.sql ``` 脚本内部分 4 段,老库可以只跑需要的段落: | 段 | 内容 | 能不能重复执行 | |----|------|----------------| | 1 | jeedp 业务表 `sys_*`(自带 `DROP TABLE IF EXISTS`) | 会把 `sys_*` 现有数据一并清掉,**只在全新库跑** | | 2 | 积木报表 / 积木BI 共 23 张表 | `IF NOT EXISTS`,不动已设计好的报表 | | 3 | 报表中心菜单(`sys_menu`,内链菜单 + 权限码) | `ON CONFLICT DO NOTHING`,可重复跑 | | 4 | 积木表的 DROP 语句 | **整段注释**,仅重建列类型时手动放开(会删光报表/大屏定义) | > 积木报表的列类型必须与实体字段一致:字符串列一律 `text`。若建成 `int4`,PostgreSQL 会在保存数据集时 > 报 `column "db_type" is of type integer but expression is of type character varying`,随后同一事务里的 > 语句全部变成 `SQLSTATE 25P02(current transaction is aborted)`。 > 列类型是怎么从厂商 jar 里推出来的,第 2 段开头的注释写了完整依据;改列类型时按第 4 段先删再建。 ### 3. 启动后端 ```bash cd jeedp-report-server # 方式一:IDEA 直接运行(需配置环境变量) # 环境变量:JASYPT_ENCRYPTOR_PASSWORD=jeedp2026! # 方式二:命令行启动 mvn spring-boot:run -Dspring-boot.run.arguments="--jasypt.encryptor.password=jeedp2026!" ``` 后端服务默认启动在 `http://localhost:8080` ### 4. 启动 Web 前端 ```bash cd jeedp-report-web npm install npm run dev ``` Web 前端默认启动在 `http://localhost:5173` ### 5. 启动移动端(可选) 使用 HBuilderX 打开 `jeedp-app-uniapp` 目录,运行到浏览器或模拟器。 ### 默认账号 | 账号 | 密码 | 角色 | |------|------|------| | admin | admin123 | 超级管理员 | ## 核心特性 ### 权限体系 - **接口权限**:基于 Sa-Token 的 `@SaCheckPermission` 注解,按钮级权限控制 - **数据权限**:`@DataScope` 注解 + AOP 切面,支持全部/自定义/机构/本人等 5 种数据范围 - **子角色机制**:支持角色层级配置,用户管理时自动按子角色范围过滤可选角色 - **超级管理员**:admin 账号自动拥有通配权限 `*:*:*` ### 积木报表与角色授权 报表本身存在厂商自有的表里(`jimu_report`、`onl_drag_page`),jeedp 只读不写。 所谓「把报表挂进系统」就是建一条**内链菜单**:`component=InnerLink` + `is_frame='0'`(是否外链「是」), 后端 `SysLoginController#getInnerLink` 会把 `query` 字段当作 iframe 地址下发到路由 `meta.link`, 前端 `views/InnerLink.vue` 拼上登录 token 后内嵌打开。 #### 前置(一次性) 1. 执行 `sql/jeedp_report_full_postgres.sql`(已跑过初始化就不用重复执行): 其中第 2 段是 23 张厂商表,第 3 段建出「报表中心」目录 + 报表设计器(`/jmreport/list`) + 大屏设计器(`/drag/list`)三个菜单。 2. 后端重启、前端 `npm run dev`(改过 Java/前端必须重启,浏览器再 Ctrl+F5 硬刷新一次)。 3. 从「报表中心 → 积木报表」进设计器把报表做好并**保存**——要挂的是已存在的资源,不是草稿。 #### 一、挂一张报表(推荐用弹窗,不用手抄 id) `系统管理 → 菜单管理 → 新增`,菜单类型先选**菜单**,然后点「路由参数」输入框右侧的 **选择积木报表**,在弹窗里按名称/类型(积木报表、积木BI 大屏)检索并「选用」, 以下字段会被自动回填: | 字段 | 回填值 | |------|--------| | 菜单类型 | `C`(菜单) | | 组件路径 | `InnerLink` | | 是否外链 | 是(`is_frame='0'`) | | 路由地址 | `jmreport-view-{id}` / `drag-view-{id}`(保证同父级下不重复) | | 路由参数 | 报表 `/jmreport/view/{报表id}`;大屏 `/drag/view?pageId={大屏id}` | | 权限标识 | `jmreport:view:{报表id}`;大屏 `drag:view:{大屏id}` | 再补一个菜单名称与显示排序,保存。(菜单名称建议与报表同名,方便回溯。) #### 二、手工填(不用弹窗时的等价做法) 三个硬性条件必须同时满足,少一个后端就不认它是内链(`isInnerLink` 判定): ``` menu_type = C 菜单 component = InnerLink is_frame = 0 是否外链选「是」——与 component 一起才构成内链 query = /jmreport/view/{报表id} 或 /drag/view?pageId={大屏id} perms = jmreport:view:{报表id} 或 drag:view:{大屏id} ``` `perms` 漏填也没关系:新增/修改菜单时 `JimuResourceService#normalizeInnerLinkPerms` 会从 `query` 反推资源并补上(为空则填、已有则追加、已含则不动)。 #### 三、授权与生效 1. `系统管理 → 角色管理 → 菜单权限` 勾选这条菜单,保存。 2. **该用户重新登录**(路由与权限是登录时算的),侧边栏就会出现这张报表。 3. 点开即是内嵌的预览页,带宿主登录态,不需要二次登录积木。 权限码的含义(`JimuViewAuthInterceptor` 按此判定): | 权限码 | 给了谁能干什么 | |--------|----------------| | `jmreport:view:{id}` | 只能看这一张报表;拿到别人的 URL 也打不开(403 提示页) | | `drag:view:{id}` | 只能看这一个大屏 | | `jmreport:list` | 报表设计器入口,能看/改**任意**报表——只给设计员 | | `drag:list` | 大屏设计器入口 | | admin(`user_id=1`) | 全部放行,无需授权 | 批量初始化可以直插 `sys_menu` + `sys_role_menu`(参照 `sql/jeedp_report_full_postgres.sql` 第 3 段末尾的授权示例),效果与界面上操作一致。 几个必须知道的边界: - **不只是“菜单可不可见”**:Sa-Token 对 `/jmreport/**`、`/drag/**` 整段放行,厂商只校验登录态; `JimuViewAuthInterceptor` 补上了入口级判定,拿到 URL 直接访问也会返回 403 页面。 拥有设计器权限(`jmreport:list` / `drag:list`)的人可直接看任意报表,否则设计员点预览会被自己拦下。 - **只管“进页面”这一层**:厂商把 `/jmreport/show`、`getCharData` 等取数接口列为免登白名单, 本层不改它们,因此这是**页面级**而非**数据接口级**的管控;需要按行过滤数据时仍要用报表自身的数据集参数。 - **资源查询接口的位置是有意的**:报表/大屏列表挂在 `/system/jimuResource/**` 而不是 `/jmreport/**` 下, 后者在 Sa-Token 白名单里,挂上去等于绕过鉴权;该接口只读厂商表(`jimu_report` / `onl_drag_page`),不写。 - **排障开关**:`jeedp.jimu.view-auth-enabled: false` 可临时关掉这层拦截(只保留登录态校验)。 - **单点登录靠三种携带方式,缺一不可**:内链页以 iframe 打开,`InnerLink.vue` 会把 token ① 拼到 iframe 地址的 `?token=` 上(入口页自身请求靠它);② 写进同源 localStorage 的 `JmReport-Access-Token`(厂商 `core/request.js` 只从这个键取值拼 `X-Access-Token` 头, 报表设计器 `design.js` 也只读不写,所以必须由宿主系统塞进去);③ 写成同源 Cookie (会话级,不设 expires),给 iframe 直指后端端口、读不到宿主 localStorage 的场景兜底。 后端 `JimuViewAuthInterceptor` 在任何入口页请求上都会把带过来的 token 回写一次 `token` Cookie (与权限判定无关,`view-auth-enabled: false` 时照旧生效), 保证从 `:8080/jmreport/list?token=xxx` 直接打开时,页内 ajax 也带得上登录态。 退出登录时 `removeToken()` 会连带清掉 `JmReport-Access-Token`,避免下一个人沿用上一个账号的凭证。 - **厂商日志 `Token校验失败!请求无权限(GET):/jmreport/xxx` 先看我们补的那行 WARN**: 厂商只说“失败”,不说原因。`JimuReportTokenServiceImpl#verifyToken` 会补一条 `积木报表鉴权失败:请求未携带 token,GET /jmreport/userinfo:X-Access-Token 头缺失/Authorization 头缺失/token 参数缺失/Cookie缺失`, 按它分流即可:**各来路都缺失** → iframe 地址没带 `?token=`、或宿主系统与报表页不同源且 Cookie 被挡; **token 已携带但未通过校验** → 拿的是别的后端/别处登录发的 token(Redis 里查不到)、 或已过 `sa-token.active-timeout`(当前 1800 秒)被冻结,重新登录即可; 入口页 `401 提示页`与它是同一个原因的两个表现。 - **报表设计器报 `getReportConfigJson is not defined` 时先分清是谁的锅**:该函数不在任何 .js 里 (已逐字节扫过 starter jar),而是厂商在服务端渲染页面时写进内联脚本的 `function getReportConfigJson(){ let str = '${reportConfig}'; return JSON.parse(str) }`, `reportConfig` 即 `JmReportBaseConfig`(`jeecg.jmreport.*`)的 JSON。因此报错只有两种来源: 控制台**首个**错误是 `SyntaxError` → 某个配置值里带了单引号或换行,把整段内联脚本打断; 只有 `ReferenceError` → 浏览器拿到的 HTML 根本不是完整的设计器页(代理指向了别的后端、 被入口拦截器换成提示页、或模板渲染中途抛异常导致 HTML 截断)。 本地同时留着两个工程时最容易碰到这种:`vite` 代理固定转发 `localhost:8080`, 若该端口上跑的不是本工程,`/jmreport/**` 会直接 500。 #### 挂完不生效,按这个顺序查 1. **侧边栏没有该菜单** → 角色没勾这条菜单,或用户没重新登录(路由是登录时下发的)。 2. **点开是 403 提示页** → 该用户既没有 `jmreport:view:{id}` 也没有 `jmreport:list`; 确认方法是看后端日志 `用户 xxx 试图访问 /jmreport/view/…,缺少权限 […],已拒绝`。 3. **点开白屏 / 报 `Token校验失败`** → 对应上面两条边界(token 三种携带方式 + 新增的 WARN 分流), 先硬刷新 + 重新登录,再看后端那行 `积木报表鉴权失败:…` 给的结论。 4. **报表在设计器里被删了、或另存/复制成了新报表** → 资源 id 变了,菜单的 `query` 与 `perms` 要重新点一次「选用」回填(菜单不会自动跟随)。 5. **想在一张页里放多张报表** → 每张建一条菜单(一表一权限码是这套集成的授权粒度), 或者在大屏里组合后用 `drag:view:{id}` 挂大屏。 #### 报表自身的两个坑:分页与导出 这两条症状看着像集成出的问题,其实全在厂商自己的配置与数据集上,与宿主系统无关: - **预览「每页显示条数」不生效、一屏出全部** → 分页开关在**数据集**上:厂商取数时判 `"1".equals(jimu_report_db.is_page)`,只有勾了「分页」才会拼分页 SQL(PostgreSQL 在 `dyndb` 方言表里是支持的), 否则一次返回全部行,这个下拉框怎么改都没用。在设计器里给对应数据集勾上分页即可; 同时 `jeecg.jmreport.pageSize`(分页条候选条数,厂商**无默认值**)要配上,否则分页条是空的。 - **导出 Excel 报 `导出失败:undefined`,后端 `JmReportExportExcelImpl` 抛 NPE** → 先查 `jeecg.jmreport.exportExcelPattern` 是否填了非法值:厂商只认 `fast`(默认)/ `primary` / 留空, 代码里只有这三个取值对应的分支会创建 Sheet,填其它值时 Sheet 为 null,导出就 NPE。 另:导出用 `pageSizeNumber`(默认 10000)且前端写死 `pageSize=1E4`, 所以**导出总是全量**,与预览分页无关,这不是 bug。 ### 安全防护 - **配置加密**:数据库密码、Redis 密码等敏感配置采用 Jasypt AES 加密存储 - **敏感字段加密**:手机号、邮箱等字段数据库存储密文,查询时自动加解密 - **脱敏显示**:列表接口自动脱敏手机号、邮箱等敏感信息 - **密码策略**:支持密码强度校验、登录失败锁定、密码过期提醒 ### 依赖治理 - **版本天花板(JDK 1.8)**:本工程运行于 JDK8,依赖不能盲升「仓库最新版」——高版本发行包的字节码可能是 Java9/Java17,装上就是 `UnsupportedClassVersionError`。用 `tools/check-class-version.ps1` 读 class 主版本号, 当前实测上限:`oshi 6.12.0`(7.x 起为 Java9)、`yauaa 7.32.0`(8.x 起为 Java17)。 `module-info.class` 与 `META-INF/versions/` 下的内容已排除(JDK8 不加载它们,否则几乎每个 jar 都会报假警)。 改依赖后可直接扫「产品里实际带的每个 jar」: ```powershell powershell -File jeedp-report-server/tools/check-class-version.ps1 com.github.oshi:oshi-core:6.12.0 # 全量扫打包产物(先跑链接校验把依赖解包到 target\linkcheck\lib) dir jeedp-report-server\target\linkcheck\lib\*.jar | % FullName | set-content jeedp-report-server\target\libs.txt powershell -File jeedp-report-server/tools/check-class-version.ps1 -ListFile jeedp-report-server/target/libs.txt ``` - **版本对齐**:直接声明的依赖会以「最近路径优先」顶掉第三方 starter 的传递依赖,编译与启动都不报错, 只在功能被触发时抛 `NoSuchMethodError`。故对易被厂商传递引入的库统一在 `dependencyManagement` 钉版本: `fastjson2` / `fastjson2-extension` / `com.alibaba:fastjson` 三件套必须同版本(现 2.0.65,压到 2.0.52 会让报表 预览因缺少 `ObjectWriterProvider#clear()` 而失败);`poi` / `poi-ooxml` / `poi-ooxml-full` / `poi-scratchpad` 四件套拉平到 5.2.5(`poi-scratchpad 5.4.1` 引用了 5.3.0 才有的 `org.apache.poi.logging.PoiLogManager`)。 - **空 jar 不一定是依赖丢了**:Sa-Token 1.44+ 把 Redis 集成拆到了 `sa-token-redis-template` + `sa-token-jackson`, `sa-token-redis-jackson` 只剩一个不带任何 class 的聚合包(实现换为 `SaTokenDaoForRedisTemplate`)。 它自建 `StringRedisTemplate`(不走业务侧 `RedisTemplate`),对象序列化改由 `SaJsonTemplateForJackson` 完成, 所以**存储格式与 1.39 不兼容**:升级上线后请清掉 Redis 里旧的 `satoken:*` 键(否则旧会话读不出, 表现是登录态异常而不是报错)。 - **字节码链接校验**:`tools/run-method-link-check.ps1` 解包 `target/jeedp-report-server.jar`,读取各 jar 常量池中的 字段/方法引用,逐一比对运行期 classpath 上是否真实存在,可一次性排查同类版本劈叉。 ```powershell powershell -File jeedp-report-server/tools/run-method-link-check.ps1 # 打包并全量扫描 powershell -File jeedp-report-server/tools/run-method-link-check.ps1 -SkipBuild # 复用已有 jar # 只关心某些包时显式传入;把已移除的库也列进来,可用「引用数为 0」反证它确实不再被需要 powershell -File jeedp-report-server/tools/run-method-link-check.ps1 -SkipBuild ` -Packages "com.alibaba.fastjson2,org.apache.poi,com.alibaba.fastjson.,net.sf.jsqlparser,org.apache.ibatis,com.github.pagehelper,com.baomidou.mybatisplus" ``` ### 分页查询 分页统一由 MyBatis-Plus 的 `PaginationInnerInterceptor` 承担,**不再引入 PageHelper**;`jsqlparser` 也不单独声明, 由 `mybatis-plus-jsqlparser-4.9` 带入(4.9 与 `minidao-spring-boot-starter-jsqlparser-4.9` 的编译版本一致, 若另引一份会同时存在两个 `net.sf.jsqlparser`)。注意 MyBatis-Plus 3.5.9 起做过模块拆分: `PaginationInnerInterceptor` 位于 `mybatis-plus-jsqlparser-4.9`,`IService`/`ServiceImpl` 位于 `mybatis-plus-spring` 的 `com.baomidou.mybatisplus.spring.service[.impl]`。 新增一个列表接口的写法(与代码生成器模板一致): ```java // Controller:只负责组装,分页结果里已带 total public TableDataInfo list(XxxQuery query, PageQuery pageQuery) { return getDataTable(xxxService.selectXxxPage(query, pageQuery)); } // Service:PageQuery.build() 产生分页页,PageQuery.unpaged() 是「取全量」令牌(size < 0 时插件跳过 count 与 limit) public IPage selectXxxPage(XxxQuery query, PageQuery pageQuery) { Page page = pageQuery.build(); page.setRecords(baseMapper.selectXxxList(query, page)); return page; } // Mapper:分页参数以 @Param("page") 传入,XML 中所有取值都要带 query. 前缀 List selectXxxList(@Param("query") XxxQuery query, @Param("page") IPage page); ``` - **必须带 `query.` 前缀**:mapper 方法一旦有多个参数,MyBatis 就会改用 ParamMap,裸写 `#{configName}` 会 **静默解析成 null**——条件消失、列表返回全量,却不抛任何异常。`tools/check-query-params.ps1` 用于守住这条约束。 - **不要设置 `maxLimit`**:分页插件在 `size < 0`(不分页)时若 `maxLimit` 非空会把它当成页大小,导出这类 「一次取全量」的查询会被静默截断。 - **越界行为**:`MybatisPlusConfig` 开了 `setOverflow(true)`,页码越界回到**第一页**(PageHelper 的 `reasonable` 是回到最后一页),迁移时不必再单独配置。 ```powershell powershell -File jeedp-report-server/tools/check-query-params.ps1 ``` ### 操作审计 - **操作日志**:`@Log` 注解自动记录增删改操作,含请求参数和响应结果 - **登录日志**:记录登录 IP、浏览器、操作系统、登录状态 - **在线用户**:实时查看当前活跃会话,支持强制下线 ## 相关文档 - [后端使用说明](jeedp-report-server/README.md) - [Web 前端说明](jeedp-report-web/README.md) ## License 本项目仅供学习和内部使用。