当前位置: 首页 > article >正文

若依前后端分离版深度集成积木报表与大屏:权限控制与数据源配置实战

1. 为什么需要深度集成从“能用”到“好用”的跨越大家好我是老张在AI和智能硬件领域摸爬滚打了十几年最近几年也一直在做企业级应用开发。我发现很多团队在用若依框架搭建后台管理系统时都会遇到一个共同的痛点数据可视化功能太弱了。系统里跑着那么多业务数据老板想看个报表、大屏要么得手动导出Excel做图表要么就得额外再部署一套BI工具账号体系、权限控制全是两套维护起来特别麻烦。这时候像积木报表JimuReport和积木大屏JimuBI这类开源工具就成了香饽饽。它们功能强大设计器也够直观但问题来了怎么把它们“丝滑”地嵌入到若依这个已经成熟的体系里很多朋友照着网上的教程把依赖一加页面一嵌发现确实能打开了心里正美呢结果一上线就傻眼了——怎么所有登录的用户都能看到所有报表怎么我们自己的业务接口数据死活调不通这其实就是典型的“表面集成”只解决了“有没有”的问题没解决“好不好用、安不安全”的问题。真正的深度集成远不止加个菜单、挂个iframe那么简单。它核心要解决三个问题第一是权限控制得让若依里张三的角色只能看销售报表李四的角色能看财务大屏权限管理必须统一到若依的体系里。第二是数据打通报表和大屏要能安全、方便地调用若依后端已有的业务接口获取实时数据。第三是用户体验用户感觉不到他是在用两个不同的系统登录一次权限、数据、界面都是连贯的。所以今天我就结合自己踩过的坑跟大家详细聊聊怎么在若依前后端分离版里通过自定义Token服务、拦截器和数据转换器把积木报表和大屏真正“揉”进若依的骨子里打造一个权限清晰、数据贯通的企业级数据可视化平台。咱们不搞花架子就讲实战保证你跟着做一定能跑通。2. 环境准备与核心依赖引入工欲善其事必先利其器。在开始动手之前咱们得先把基础环境搭好。我假设你已经有一个可以正常运行的若依前后端分离项目我这里用的是3.8.9版本。如果你还没有可以去若依官网下载最新版按照官方文档初始化数据库、配置Redis把前后端都跑起来。这是所有操作的前提。接下来就是引入积木报表和大屏的核心依赖。这一步看似简单但版本选择很关键用错了版本后面可能会有一堆兼容性问题。打开你项目里ruoyi-common模块下的pom.xml文件在dependencies节点里加上下面这两段!-- 积木报表核心依赖 -- dependency groupIdorg.jeecgframework.jimureport/groupId artifactIdjimureport-spring-boot-starter/artifactId version1.9.2/version /dependency !-- 积木BI大屏核心依赖 -- dependency groupIdorg.jeecgframework.jimureport/groupId artifactIdjimubi-spring-boot-starter/artifactId version1.9.1/version /dependency加完之后记得点一下IDE的Maven刷新按钮把依赖下载下来。这里我多说一句积木报表的社区版是开源的功能对于大多数企业来说已经足够用了。如果你用的是商业版依赖的groupId可能会不一样这个需要你根据实际情况调整。依赖加好了还得告诉Spring Boot一些配置。打开ruoyi-admin模块下的application.yml文件翻到最后加上下面这段配置jeecg: # 权限配置 permission: # 报表权限配置 report: # 查询权限符号配置此权限代表只能查看报表 query: jeecg:report:query # 修改权限符号配置此权限代表拥有报表所有权限设计、删除等 edit: jeecg:report:edit # 大屏权限配置 drag: # 查询权限符号配置此权限代表只能查看大屏 query: jeecg:drag:query # 修改权限符号配置此权限代表拥有大屏所有权限 edit: jeecg:drag:edit jmreport: # 自定义项目前缀。这是开发环境的前端访问后端接口的前缀。 # 非常重要项目发布到生产环境时一定要改成生产环境的前缀比如 /prod-api customPrePath: /dev-api这个配置是咱们整个权限体系的基石。我定义了四种权限标识符分别对应报表的查看和编辑、大屏的查看和编辑。后面写拦截器判断用户有没有权限就是靠比对这几个字符串。customPrePath这个配置特别容易踩坑它决定了积木报表前端页面请求后端API时的基础路径。在开发环境若依前端通常用/dev-api代理后端所以这里配/dev-api。等你打包部署到生产环境前端直接访问后端这个路径很可能就变成了/prod-api或者直接是/千万记得要改不然所有API请求都会404。最后一步先让积木报表和大屏的静态资源能匿名访问不然连登录页面都加载不出来。找到ruoyi-framework模块下src/main/java/com/ruoyi/framework/config包里的SecurityConfig.java文件在antMatchers方法允许匿名访问的路径列表里加上/jmreport/**和/drag/**。这样报表和大屏的设计器页面、JS、CSS等资源文件就能被正常加载了。3. 核心改造一自定义Token服务打通用户体系依赖和配置都搞定了现在我们来啃第一块硬骨头用户体系打通。积木报表和大屏有自己的一套用户认证逻辑而若依用的是另一套通常是JWT Token。我们的目标就是让积木报表能认识并信任若依颁发的Token。首先我们需要一个配置类来集中管理刚才在yml里定义的权限字符串。在ruoyi-framework模块下新建一个包com.ruoyi.framework.report.config然后在里面创建ReportConfig.javapackage com.ruoyi.framework.report.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; Component public class ReportConfig { // 从application.yml注入权限配置 Value(${jeecg.permission.report.query}) private String reportQueryPermission; Value(${jeecg.permission.report.edit}) private String reportEditPermission; Value(${jeecg.permission.drag.query}) private String dragQueryPermission; Value(${jeecg.permission.drag.edit}) private String dragEditPermission; // 这里省略了getter和setter方法实际开发中请加上 }这个类就是个简单的“配置容器”方便我们在其他地方注入使用避免魔法字符串满天飞。接下来是关键操作改造若依的Token服务。积木报表的JmReportTokenServiceI接口要求我们实现几个关键方法比如根据Token获取用户名、角色验证Token有效性等。但若依原生的TokenService.getLoginUser(HttpServletRequest request)方法需要我们传一个HttpServletRequest对象进去而积木报表的接口有时只给我们一个Token字符串。所以我们得先给若依的TokenService打个补丁。找到ruoyi-framework模块下web.service包里的TokenService.java在类里增加一个重载方法// 新增一个方法支持直接通过token字符串获取用户信息 public LoginUser getLoginUser(String token) { if (StringUtils.isNotEmpty(token)) { try { // 解析token Claims claims parseToken(token); String uuid (String) claims.get(Constants.LOGIN_USER_KEY); String userKey getTokenKey(uuid); // 从Redis缓存中获取登录用户信息 LoginUser user redisCache.getCacheObject(userKey); return user; } catch (Exception e) { // 解析失败token无效 log.error(解析token失败, e); } } return null; }补丁打好现在可以创建我们自己的Token服务了。在com.ruoyi.framework.report.service包下新建ReportTokenService.java并实现JmReportTokenServiceI接口package com.ruoyi.framework.report.service; import org.jeecg.modules.jmreport.api.JmReportTokenServiceI; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; Component // 别忘了这个注解让Spring管理它 public class ReportTokenService implements JmReportTokenServiceI { Value(${token.header}) // 若依的Token在请求头中的字段名通常是Authorization private String ryHeader; // 积木报表默认的Token头字段 private String jmHeader X-Access-Token; Autowired private ReportConfig reportConfig; Autowired private TokenService tokenService; // 注入我们刚刚改造过的若依Token服务 Override public String getUsername(String token) { // 直接调用我们新增的getLoginUser(String token)方法 LoginUser loginUser tokenService.getLoginUser(token); return loginUser ! null ? loginUser.getUsername() : null; } Override public String[] getRoles(String token) { LoginUser loginUser tokenService.getLoginUser(token); if (loginUser ! null) { SysUser user loginUser.getUser(); ListSysRole roles user.getRoles(); // 将角色列表转换为角色名称数组 return roles.stream().map(SysRole::getRoleName).toArray(String[]::new); } return new String[0]; } Override public Boolean verifyToken(String token) { // 验证Token是否有效并在此处集成我们的权限校验逻辑 LoginUser loginUser tokenService.getLoginUser(token); if (loginUser ! null){ // 刷新token有效期若依的典型操作 tokenService.refreshToken(loginUser); SysUser user loginUser.getUser(); // 超级管理员拥有所有权限 if (user ! null user.isAdmin()) { return true; } else { // 普通用户检查是否拥有报表或大屏的任意一种权限 SetString permissions loginUser.getPermissions(); if (permissions ! null (permissions.contains(reportConfig.getReportQueryPermission()) || permissions.contains(reportConfig.getReportEditPermission()) || permissions.contains(reportConfig.getDragQueryPermission()) || permissions.contains(reportConfig.getDragEditPermission()))) { return true; } } } return false; // Token无效或无权限 } Override public String getToken(HttpServletRequest request) { // 从请求中提取Token。积木报表可能从参数或请求头传递。 String token request.getParameter(token); if (StringUtils.isNull(token)) { token request.getHeader(jmHeader); // 尝试从X-Access-Token头获取 } // 若依的Token通常有Bearer 前缀需要去掉 if (StringUtils.isNotEmpty(token) token.startsWith(Constants.TOKEN_PREFIX)) { token token.replace(Constants.TOKEN_PREFIX, ); } return token; } Override public MapString, Object getUserInfo(String token) { // 获取用户的扩展信息比如用户ID、部门ID这些信息可以在报表SQL中作为参数使用 token token.replace(Constants.TOKEN_PREFIX, ); LoginUser loginUser tokenService.getLoginUser(token); MapString, Object map new HashMap(); map.put(SYS_USER_CODE, loginUser.getUserId()); map.put(SYS_ORG_CODE, loginUser.getDeptId()); return map; } Override public HttpHeaders customApiHeader() { // 当积木报表内部去调用我们自己的业务API时用这个方法自动在请求头带上Token HttpHeaders headers new HttpHeaders(); // 同时带上若依和积木报表认可的Token头双重保险 headers.add(ryHeader, Constants.TOKEN_PREFIX getToken()); headers.add(jmHeader, getToken()); return headers; } }这个类就是连接若依和积木报表的“桥梁”。它做了几件重要的事第一统一了Token的提取和验证逻辑让积木报表能用若依的Token。第二在verifyToken方法里我们不仅验证Token是否有效还初步判断了用户是否有访问报表/大屏的权限。第三getUserInfo方法提供了用户ID和部门ID这个在后面配置API数据源时非常有用可以实现数据行级权限过滤比如只看到自己部门的数据。第四customApiHeader方法确保了积木报表在向后端业务接口请求数据时能自动携带认证信息。写完这个类积木报表就已经能认识若依的用户了。但这只是第一步权限的精细控制我们还得靠拦截器。4. 核心改造二精细化权限拦截器设计光有Token验证还不够我们需要一个更细粒度的“关卡”来根据用户的具体权限控制他能访问哪些报表、进行哪些操作是只能看还是能设计。这就是拦截器的用武之地。在com.ruoyi.framework.report.interceptor包下我们创建ReportInterceptor.javapackage com.ruoyi.framework.report.interceptor; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; Component public class ReportInterceptor implements HandlerInterceptor { Autowired private TokenService tokenService; Autowired private ReportConfig reportConfig; Autowired private ReportTokenService reportTokenService; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. 提取Token String token reportTokenService.getToken(request); LoginUser loginUser tokenService.getLoginUser(token); if (loginUser null) { // 未登录返回403 return renderError(response, 请先登录); } SysUser user loginUser.getUser(); // 2. 超级管理员放行 if (user ! null user.isAdmin()) { return true; } // 3. 获取用户权限集合 SetString permissions loginUser.getPermissions(); if (permissions null) { return renderError(response, 权限不足); } String uri request.getRequestURI(); // 4. 判断访问的是报表路径还是大屏路径 if (uri.contains(/jmreport/)) { // 访问报表相关接口 return handleReportAccess(permissions, uri, response); } else if (uri.contains(/drag/)) { // 访问大屏相关接口 return handleDragAccess(permissions, uri, response); } // 非报表/大屏路径理论上不应该走到这里如果走到说明配置有误可以放行或拦截 return true; } private boolean handleReportAccess(SetString permissions, String uri, HttpServletResponse response) { // 拥有报表编辑权限所有报表接口都可访问 if (permissions.contains(reportConfig.getReportEditPermission())) { return true; } // 只有查询权限则只能访问特定的“查看”类接口 if (permissions.contains(reportConfig.getReportQueryPermission())) { // 定义允许“查询权限”用户访问的报表接口白名单 SetString queryReportSet new HashSet(); queryReportSet.add(/jmreport/getQueryInfo); // 获取报表查询条件 queryReportSet.add(/jmreport/show); // 预览报表 queryReportSet.add(/jmreport/view/); // 根据编码查看报表注意路径带参数 queryReportSet.add(/jmreport/addViewCount/); queryReportSet.add(/jmreport/checkParam/); // 检查当前请求的URI是否在白名单内 for (String allowedPath : queryReportSet) { if (uri.contains(allowedPath)) { return true; } } } // 既无编辑权限又不在查询权限的白名单内则拒绝访问 return renderError(response, 无权访问此报表); } private boolean handleDragAccess(SetString permissions, String uri, HttpServletResponse response) { // 逻辑类似报表区分编辑和查看权限 if (permissions.contains(reportConfig.getDragEditPermission())) { return true; } if (permissions.contains(reportConfig.getDragQueryPermission())) { SetString queryDragSet new HashSet(); queryDragSet.add(/drag/page/queryById); queryDragSet.add(/drag/page/addVisitsNumber); queryDragSet.add(/drag/share/view/); // 分享查看 queryDragSet.add(/drag/mock/json/); for (String allowedPath : queryDragSet) { if (uri.contains(allowedPath)) { return true; } } } return renderError(response, 无权访问此大屏); } private boolean renderError(HttpServletResponse response, String msg) throws IOException { AjaxResult ajaxResult AjaxResult.error(403, msg); response.setContentType(application/json;charsetutf-8); response.getWriter().write(JSONObject.toJSONString(ajaxResult)); return false; } }这个拦截器的逻辑我写得比较细核心思想是权限分级。拥有edit权限的用户比如报表管理员可以访问所有设计、保存、删除的接口。而只有query权限的用户比如普通业务人员只能访问我明确列在白名单里的那几个“只读”接口。这样权限控制就非常清晰了。白名单里的路径是怎么来的是我一边看浏览器开发者工具里的网络请求一边在后端拦截器里打印uri调试出来的。你在实际项目中可能还需要根据积木报表的版本更新调整这个白名单列表。拦截器写好了还得把它“注册”到Spring MVC里。找到ruoyi-framework模块下config包里的ResourceConfig.java或者叫WebMvcConfig在已有的配置里加上Autowired private ReportInterceptor reportInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { // 其他拦截器配置... registry.addInterceptor(reportInterceptor) .addPathPatterns(/jmreport/**, /drag/**) // 拦截所有报表和大屏路径 .excludePathPatterns(/jmreport/desreport_/**, /drag/static/**) // 排除静态资源 .excludePathPatterns(/*.html, /**/*.css, /**/*.js, /**/*.png, /**/*.jpg); // 继续排除其他静态资源 }注意excludePathPatterns这里要把积木报表和大屏自己的静态资源比如设计器需要的JS、CSS、图片排除掉不然页面样式会加载不出来。这个列表可能需要你根据实际情况调整。5. 核心改造三统一API数据源响应格式权限控制好了下一个难题是数据对接。积木报表和大屏都支持配置API数据源直接调用我们若依后端写好的业务接口来获取动态数据这比连接数据库写SQL更灵活、更安全。但这里有个坑若依后端接口返回的数据格式和积木报表期望的格式往往对不上。若依的AjaxResult统一返回格式大概是这样的{“code”: 200, “msg”: “成功” “data”: {…}}或者{“code”: 200, “msg”: “成功” “rows”: […]}。而积木报表的API数据源默认期望接口直接返回一个数组[...]或者一个包含data字段的对象{“data”: […]}。如果我们不处理直接在积木报表里配置若依的接口地址报表引擎会解析失败因为它找不到它想要的data或直接的数据数组。解决办法就是写一个数据格式转换器ApiDataConvertAdapter。在com.ruoyi.framework.report.adapter包下创建ReportDataConvertAdapter.javapackage com.ruoyi.framework.report.adapter; import com.alibaba.fastjson.JSONObject; import org.jeecg.modules.jmreport.desreport.render.handler.convert.ApiDataConvertAdapter; import org.springframework.stereotype.Component; Component(reportDataConvertAdapter) // 注意这个Bean的名字配置数据源时会用到 public class ReportDataConvertAdapter implements ApiDataConvertAdapter { Override public String getData(JSONObject jsonObject) { // 1. 优先尝试获取“data”字段 if(jsonObject.containsKey(data)){ String data jsonObject.getString(data); // 这里返回的是data字段对应的JSON字符串积木报表会将其解析为数组或对象 return data; } // 2. 若依的分页查询接口有时返回的是“rows”字段 else if(jsonObject.containsKey(rows)){ return jsonObject.getString(rows); } // 3. 如果都不是返回整个JSON字符串适用于返回简单数组的接口 else { return jsonObject.toJSONString(); } } }这个转换器的作用就是做一个“翻译官”。当积木报表调用我们的业务接口拿到AjaxResult格式的响应后会把这个JSON对象交给这个转换器。转换器会判断如果里面有data字段就把data字段的值通常是一个列表或对象抽出来返回如果有rows字段就抽rows如果都没有就把整个JSON返回。这样积木报表就能正确解析到我们真正的业务数据了。这里我用的是fastjson的JSONObject你要注意项目里fastjson的版本。我遇到过若依3.8.5版本用fastjson2而积木报表依赖的版本是fastjson导致冲突启动报错。如果遇到类似问题可以尝试在ruoyi-admin的pom.xml里排除掉有冲突的依赖或者调整版本。6. 前端集成与菜单配置后端的核心改造完成了现在轮到前端。目标是把积木报表和大屏的设计器、查看器页面无缝嵌入到若依的布局框架里。首先我们需要一个专用的请求实例。因为若依自带的request.js对响应数据的处理可能和积木报表的预期不完全一致我们在src/api目录下新建一个py/jeecg/request.js文件py是我个人习惯代表“平台扩展”你可以用任何名字。这个文件的内容基本复制若依的request.js但可以去掉一些若依特有的拦截逻辑确保请求能正确发送到积木报表的后端。核心是确保在请求头里正确设置了X-Access-Token为Bearer ${token}。然后创建对应的API模块文件。在src/api/py/jeecg/下创建report.jsimport request from /api/py/jeecg/request; const jmreportUrl /jmreport; const dragUrl /drag; // 示例获取报表列表 export function listReport(queryParams) { // 注意这里手动拼接了token参数因为积木报表的列表接口有时需要token放在参数里 let params { token: Bearer getToken(), ...queryParams } return request({ url: jmreportUrl /excelQuery, method: get, params: params }) } // 可以继续添加其他报表、大屏相关的API函数接下来是页面组件。我们在src/views目录下建立对应的Vue文件。以报表列表页为例 (src/views/py/jeecg/report/index.vue)template div iframe :srcreportUrl frameborder0 stylewidth:100%; height: calc(100vh - 84px);/iframe /div /template script import { getToken } from /utils/auth; export default { name: JeecgReport, data() { return { // 关键拼接URL将若依的Token传递给积木报表 reportUrl: process.env.VUE_APP_BASE_API /jmreport/list?tokenBearer getToken() }; } }; /script这里用了iframe来嵌入积木报表的原生页面。src的拼接是关键process.env.VUE_APP_BASE_API是你的后端API基础地址如/dev-api后面跟上积木报表的列表页路径/jmreport/list最后通过token参数把若依的Token传过去。这样当这个iframe加载时积木报表后端就能通过我们之前写的ReportTokenService.getToken()方法拿到Token并进行验证。同理创建报表查看页 (src/views/py/jeecg/report/view/index.vue)、大屏列表页、大屏查看页。在查看页中需要从路由参数中获取报表或大屏的编码code动态拼接到URL里。最后在若依的菜单管理里新增四个菜单项分别指向这四个Vue组件。菜单的“组件路径”要填写正确例如报表列表页的路径可以填py/jeecg/report/index。添加完成后刷新页面你就能在侧边栏看到“积木报表”、“积木大屏”等菜单了。点击它们会加载出对应的iframe页面并且因为Token的传递和拦截器的校验只有有权限的用户才能看到。7. 实战演练配置一个带权限的API数据源报表理论说再多不如动手做一遍。我们来实战创建一个简单的“课程信息表”报表数据源来自若依后端的一个业务接口并且体验一下权限控制的效果。第一步准备后端接口。假设我们有一个课程管理的业务模块有一个查询课程列表的接口路径是/system/course/list它返回的就是若依标准的分页格式{“code”:200, “rows”:[…], “total”:10}。确保这个接口已经存在并且能被正常访问。第二步在积木报表设计器中配置API数据源。登录系统进入“积木管理 - 报表管理”。点击“设计”进入一个报表。在右侧“数据集管理”点击“新增”选择“API数据集”。在配置面板中填写数据源名称courseListAPI地址#{domainURL}/system/course/list。这里的#{domainURL}是一个变量会被替换成我们在application.yml里配置的jmreport.customPrePath如/dev-api。所以最终请求的地址是/dev-api/system/course/list。请求方式GET。类转换器填写我们之前创建的转换器Bean的名字reportDataConvertAdapter。这是关键有了它报表引擎才能正确解析我们接口返回的rows字段。点击“Api解析”按钮。如果接口通、转换器生效下面就会显示出接口返回的字段列表比如id,name,teacherName等。为字段分配合适的类型字符串、数字、日期等点击确定保存数据源。第三步设计报表模板。在报表设计画布上拖拽一个表格组件。从左侧“数据集”面板把courseList数据集下的字段如name,teacherName拖到表格的单元格里。你会看到单元格里生成了类似#{courseList.name}的表达式。这里有个易错点积木报表在API数据集模式下有时用#{}取不到值。你需要手动把#改成$变成${courseList.name}。根据我的经验${}的解析成功率更高。保存这个报表模板。第四步配置菜单权限。进入若依的“系统管理 - 角色管理”。编辑一个测试角色比如“业务员”。在“菜单权限”中只勾选“积木报表”的查看菜单不勾选“报表设计”或“报表管理”这类编辑菜单。在“权限字符”中为这个角色添加jeecg:report:query这个权限标识就是我们之前在yml里定义的。用一个属于“业务员”角色的账号登录。点击报表菜单只能进入报表列表和查看页面。如果尝试直接访问设计器的URL或者调用保存报表的API我们的拦截器就会生效返回“无权访问”的错误。而拥有jeecg:report:edit权限的“报表管理员”角色则可以进行所有操作。第五步高级技巧——数据行级权限。有时候我们不仅要做菜单级的权限还要做数据级的。比如教师只能看到自己教授的课程。这可以利用我们之前在ReportTokenService.getUserInfo()方法中返回的userId。在后端的/system/course/list接口中不要直接返回所有数据。先从Security上下文中获取当前登录用户的ID通过SecurityUtils.getLoginUser().getUserId()。在查询数据库时加上where teacher_id #{currentUserId}这样的条件。这样当不同用户查看同一个报表时报表调用同一个API接口但后端根据用户身份返回了不同的数据集自然就实现了行级数据隔离。这个功能在财务、人力等敏感数据场景下非常实用。通过这个完整的实战流程你应该能体会到深度集成的威力用户无感知地使用报表功能权限管理统一在若依平台完成数据来自安全的业务接口一切都井然有序。8. 避坑指南与性能优化建议集成过程中我踩过不少坑这里总结几个常见的帮你省点时间静态资源404问题集成后页面样式错乱或者JS加载失败。检查ResourceConfig中的excludePathPatterns确保积木报表的静态资源路径如/jmreport/desreport_/**,/drag/static/**,/jmreport/view/**/*.js已被正确排除在拦截器之外。浏览器的开发者工具Network标签是定位这类问题的利器。Token失效或传递失败打开报表页面提示未登录。首先检查前端iframe的src属性Token是否正确拼接Bearer前缀和空格。然后在ReportTokenService.getToken()方法中加日志看是否成功从请求头或参数中提取到Token。最后检查若依的Token存储如Redis中对应的用户信息是否还存在。API数据源解析失败报表配置了API但预览时没数据。首先在浏览器直接访问你配置的API地址确认接口本身能返回正确数据。然后在ReportDataConvertAdapter的getData方法里打日志看看积木报表传给转换器的jsonObject到底是什么结构。很可能你的接口返回格式和预想的data或rows不一致需要调整转换逻辑。权限拦截过于严格或宽松明明有权限却访问被拒或者没权限的反而能访问。仔细调试ReportInterceptor中的handleReportAccess和handleDragAccess方法。打印出当前请求的uri和用户的permissions集合核对白名单路径是否匹配权限字符串是否完全一致注意大小写。生产环境路径问题开发时好好的一部署就白屏。反复检查application.yml中的jmreport.customPrePath配置生产环境的前端访问路径很可能变了。同时检查Nginx等网关的代理配置确保对/jmreport/**和/drag/**路径的请求被正确转发到了后端服务。性能优化方面也有一些建议报表数据缓存对于数据量变化不频繁的报表可以在后端接口层或积木报表的数据集层面设置缓存避免每次预览都查询数据库。积木报表支持数据集缓存配置。大屏资源加载复杂的大屏可能包含很多图片、视频资源。确保这些静态资源有独立的CDN或对象存储路径不要都走应用服务器。同时在积木大屏的设计中注意优化图表数据量避免一次性请求过多历史数据。拦截器优化我们的ReportInterceptor会对每个报表/大屏请求进行权限校验和Token解析这里面涉及Redis查询。如果并发很高可以考虑将用户权限信息在Token验证后缓存在本次请求的上下文中避免同一请求内多次查询权限集合。数据库连接池积木报表在直连数据库查询时会占用连接。确保你的数据库连接池配置如HikariCP的最大连接数设置合理避免报表查询拖垮其他业务。这套集成方案我在两个中等规模的内部生产系统上稳定运行了半年多。它最大的好处是维护简单权限和数据源的管理都收口在若依主系统里避免了多套系统用户同步、权限不一致的噩梦。当然如果你的报表需求极其复杂或者对实时性、并发性要求极高可能还需要引入更专业的OLAP引擎或实时计算框架那又是另一个话题了。但对于绝大多数中小型企业的内部运营后台、数据看板需求这套“若依积木报表/大屏”的组合拳已经足够有力且优雅了。

相关文章:

若依前后端分离版深度集成积木报表与大屏:权限控制与数据源配置实战

1. 为什么需要深度集成?从“能用”到“好用”的跨越 大家好,我是老张,在AI和智能硬件领域摸爬滚打了十几年,最近几年也一直在做企业级应用开发。我发现很多团队在用若依框架搭建后台管理系统时,都会遇到一个共同的痛点…...

新手福音:通过快马平台快速上手qun329数据处理库的完整指南

对于刚接触编程的朋友来说,学习一个新的数据处理库,最怕的就是环境配置复杂、示例代码看不懂、运行不起来。最近我在学习一个叫 qun329 的库时,就遇到了类似的问题。不过,我发现了一个特别适合新手的工具——InsCode(快马)平台&am…...

终于微信也能接入OpenClaw了,附手把手教程和案例,感兴趣的可以看看。

你好,我是郭震!最近很多读者在后台留言,说之前的“龙虾(OpenClaw)”本地部署教程非常实用,已经用上了。但随之而来大家提了一个非常现实的问题:“平时工作、发朋友圈、聊客户全在微信上&#xf…...

【CVPR26-美国伊利诺伊大学】视觉-语言模型中的链路追踪:理解多模态思维的内部机制

文章:Circuit Tracing in Vision–Language Models: Understanding the Internal Mechanisms of Multimodal Thinking代码:https://github.com/UIUC-MONET/vlm-circuit-tracing单位:美国伊利诺伊大学厄巴纳-香槟分校、独立研究者一、问题背景…...

数据与智能定义竞争力:智能网联汽车实时数据分析方案白皮书 2026

这份 2026 年智能网联汽车实时数据分析方案白皮书,核心围绕“数据与智能定义智能网联汽车核心竞争力”展开,剖析了汽车产业从电动化向智能化转型中数据体系的变革挑战,提出以 SelectDB 为核心的实时数据底座解决方案,结合实践案例…...

英伟达斥资20亿美元投资Nebius “循环投资”泡沫争议再起

雷递网 乐天 3月11日英伟达(股票代码:NVDA)日前表示,将向人工智能云公司Nebius投资20亿美元,Nebius表示,该合作伙伴关系将帮助Nebius到2030年底部署超过5吉瓦(GW)的英伟达系统,这笔电力大约足以供380万户家庭使用。Neb…...

OpenClaw(龙虾)爆火!27本豆瓣高分Agent、大模型、Transformer书和教程,码住学原理~

2025到2026,AI从大语言模型向智能体Agent发展。回看人工智能领域在过去数十年发展经历了从预定义逻辑到自发涌现能力的深刻范式转移。2017年Transformer架构的诞生改变了2010年以来循环神经网络(RNN)及其变体长短期记忆网络(LSTM&…...

网络安全的本质:用数学建立秩序,用哲学理解混沌

引言网络安全从业者常常自嘲:我们是在和“未知的未知”作战。每天有新的漏洞曝光,有新的攻击手法出现,有新的数据泄露事件发生。防守方似乎永远处于被动,永远在追赶攻击者的脚步。这种困境背后,隐藏着一个深刻的本质&a…...

OpenClaw 小龙虾从安装到实战:Cherry Studio → Codex → Skills

本文整理了一条最简单、最实用的 OpenClaw 上手路径,完整流程分为 三个部分: 通过 Cherry Studio 安装 OpenClaw 下载 Cherry Studio → 配置免费阶跃模型 → 一键安装 OpenClaw → 配置 SOUL / IDENTITY / USER 三个核心文件。使用 ChatGPT 订阅自带的 …...

OpenHarmony Flutter 三方库 dart_windows_service_support 的适配鸿蒙调研 - 探索跨端后台驻留机制与系统服务对接范式

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net OpenHarmony Flutter 三方库 dart_windows_service_support 的适配鸿蒙调研 - 探索跨端后台驻留机制与系统服务对接范式 前言 在大型工业软件中,后台驻留服务是系统的灵魂。开…...

Flutter 三方库 wikipedia_api 的鸿蒙化适配实战 - 一站式获取全球维基百科数据、支持多语言检索与摘要提取

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net Flutter 三方库 wikipedia_api 的鸿蒙化适配实战 - 一站式获取全球维基百科数据、支持多语言检索与摘要提取 前言 开发知识库或智能助手时,维基百科是不可或缺的数据源。手动调…...

6英寸磷化铟晶圆厂在埃因霍温开始建设

获得高达1.5亿欧元的欧洲芯片法案投资,此项目被视作“欧洲未来数字经济的发射台”。荷兰应用科学研究组织(TNO)与埃因霍温高科技园(High Tech Campus Eindhoven)已着手建设一座工厂,该工厂将用于以6英寸晶圆…...

Python基于flask的农产品物流运输系统

目录系统架构设计数据库设计核心功能实现地图集成数据分析模块系统安全措施测试与部署项目技术支持可定制开发之功能创新亮点源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作系统架构设计 采用Flask作为后端框架,搭配SQLAlchemy…...

196.像2FSK这种调制方式可以用星座图表示吗?

...

Python基于flask的角色扮演论坛的设计与实现 可视化

目录需求分析与功能规划技术栈选择数据库设计核心功能实现可视化计划分阶段部署与优化项目技术支持可定制开发之功能创新亮点源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作需求分析与功能规划 角色扮演论坛的核心需求包括用户角色创建、故…...

鸿蒙应用开发UI基础第二十一节:自定义组件与页面的生命周期

【学习目标】 理解生命周期的核心概念,区分自定义组件生命周期和页面生命周期的本质差异;掌握核心生命周期方法,明确各方法的触发时机及使用规范;掌握自定义组件/页面的完整生命周期流程,理解嵌套组件的生命周期调用时…...

Python基于flask的美容美发理发店管理系统 基于JAVAWEB的理发店会员管理系统

目录基于Flask的美容美发理发店管理系统基于JavaWeb的理发店会员管理系统通用建议项目技术支持可定制开发之功能创新亮点源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作基于Flask的美容美发理发店管理系统 技术栈 后端:Python…...

【AI人工智能第3次课-Python3基础系列之01.Python环境搭建与输入输出】001篇

文章目录 🐍 01. Python环境搭建与输入输出 一、环境搭建(Step-by-Step 2026实操版) ✅ 前置共识(必读!避免踩坑) ▶️ 步骤1:下载与安装Python(推荐官方渠道) ▶️ 步骤2:创建隔离的虚拟环境(✅ 2026行业强制规范) ▶️ 步骤3:选择代码编辑器(IDE推荐2026版)…...

安装OpenClaw时,为什么需要先安装Node.js?不装行不行?

## 为什么OpenClaw需要Node.js?不装行不行? 最近在折腾OpenClaw这个工具的时候,发现它的安装文档里第一步就是要求安装Node.js。很多刚接触的朋友可能会纳闷——这俩东西看起来八竿子打不着,为什么非得先装Node.js?不装…...

拒绝“无效创作”!让技术人的每一份付出都有流量回报

做短视频副业的技术人,大概都有过这样的无奈:花3小时写文案、2小时拍视频、1小时剪辑,发布后播放量寥寥无几;明明内容是自己深耕多年的技术干货,却因为不会包装、不懂推流,始终无人问津;粉丝涨得…...

Python基于flask的起点小说数据分析与可视化平台 爬虫

目录爬虫实现目标数据抓取范围技术选型与工具核心实现步骤反爬规避策略数据清洗与存储注意事项项目技术支持可定制开发之功能创新亮点源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作爬虫实现目标 构建一个高效稳定的爬虫系统,用…...

Spring Boot 热配置:让应用灵活升级

一、热配置的概念与重要性热配置,简单来说,就是在应用程序运行过程中,无需重新启动应用,就能对配置进行修改并使其生效。这种方式的优势显而易见:• 减少停机时间:在生产环境中,应用的稳定运行至…...

从零开始学AI:高效学习+2026入行全指南

大家好,我是用AI技术赋能超级个体的实践者,前方预警这篇文章很长也很干,你可以根据自己的情况,看对应的章节 一、本指南脑图二、为什么要读这篇指南?帮你解决这些痛点 很多的朋友是不是想学AI却不知道从哪下手&#xf…...

从土木转行AI经验贴,非常详细收藏我这一篇就好了

最近工作真的太忙太忙了,一到工作日就忙成狗,所以经验贴拖到了现在。 在经验贴前面,我想说的是,每个人的基础和背景是不一样的,我的经验也只能作为参考,而且我并不是只用了半年时间就彻底完成了算法方向所有…...

数据安全治理平台 (DSGP) 安全性测试:防止“守门人”沦陷

前言 技术背景:在现代网络攻防体系中,数据是攻防双方争夺的核心。数据安全治理平台 (DSGP),作为集中管理企业数据分类、权限、脱敏和审计的关键基础设施,是数据防线的指挥中心。传统攻击往往绕过它窃取数据,但更高级的…...

2026,AI创业者的慷慨、残酷与迷雾:从历史规律看价值迁移

当代码被商品化,你的护城河在哪里?2026年2月,前Tesla AI总监、OpenAI创始成员Andrej Karpathy在X上分享了一个个人观察:11月,他的编程工作还是80%手写代码、20%让AI agent处理;到了12月,比例完全…...

MongoDB分片原理:详解水平扩展的核心技术与架构设计

MongoDB分片(Sharding)是MongoDB实现水平扩展的核心技术,能够将海量数据分布到多个服务器上,突破单机存储和性能限制。本文将深入解析分片的工作原理、架构设计和实践建议,帮助你掌握这一分布式数据库的核心技术。一、…...

解决IDEA源根报错

Java文件位于模块的源根目录之外,因此不会被编译。IntelliJ IDEA 需要知道哪些目录包含源代码,以便正确编译和索引。下面提供两种解决方法: 方法一:移动文件到已有的源根目录 通常,标准的源根目录是: src/main/java(主代码) src/test/java(测试代码) 将你的 .java 文…...

红外测距传感器GP2D12与STM32单片机程序,滤波算法,设计步骤和代码流程清晰非常实用

红外测距传感器GP2D12与STM32单片机程序,滤波算法,设计步骤和代码流程清晰非常实用。 是机器人中最常用的红外测距传感器。 程序源码注释详细,非常适合单片机开发人员。GP2D12这玩意儿在机器人圈子里混得风生水起不是没道理的,毕竟…...

企业网站获取视频JS代码调用和通用iframe代码调用

企业网站获取视频JS代码调用和通用iframe代码调用。管理平台,自带生成视频预览地址、调用代码和iframe通用代码,用户可根据自己的实际情况,在企业网站或其他平台中进行视频调用,最常见的应用是在企业官网页面中调用,均…...