基于若依框架实现按角色控制 Excel 字段导出功能

一、背景介绍

在我们的项目开发中,采用了若依(RuoYi)的 Java Spring 框架进行系统搭建。若依框架提供了 @Excel 注解,通过在实体类的字段上添加该注解,能够方便地实现 Excel 数据的导出功能。然而,在实际业务场景中,领导提出了根据用户角色来控制某些字段是否导出的需求。但遗憾的是,若依自带的 @Excel 注解并不支持这一功能。为了满足这一业务需求,我们决定自行开发一套解决方案。

二、解决方案思路

为了实现按角色控制 Excel 字段导出的功能,我们的核心思路是自定义一个注解和一个导出工具类。具体步骤如下:

  1. 自定义注解:创建一个新的注解 RoleExcel,该注解能够标识出需要导出的字段,并且可以指定允许导出该字段的角色列表。
  2. 编写导出工具类:开发一个新的导出工具类 RoleExcelUtil,该工具类会根据当前用户的角色信息,过滤掉没有权限导出的字段,然后将有权限导出的字段数据导出到 Excel 文件中。
  3. 实体类注解应用:在需要导出和进行角色控制的实体类 JiheHandledRate 的相应字段上添加 RoleExcel 注解。
  4. 控制器调用:在控制器 JiheStationCommonControllerexportHandledRate 方法中调用 RoleExcelUtil 工具类的导出方法,完成数据的导出操作。

三、具体实现步骤

3.1 自定义 RoleExcel 注解

首先,我们定义了一个 RoleExcel 注解,用于标识需要导出的字段,并支持按角色控制导出。以下是 RoleExcel 注解的代码实现:

package com.sdhs.common.annotation;

import java.lang.annotation.*;

/**
* 扩展Excel注解,支持按角色控制导出字段
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RoleExcel {
// 字段显示名称(同若依@Excel的name)
String name(); // 列宽(默认20)
int width() default 20; // 日期格式(如"yyyy-MM-dd HH:mm:ss")
String dateFormat() default ""; // 允许导出该字段的角色标识列表(如"admin","audit_role")
String[] roles() default {}; // 是否允许所有角色导出(默认false)
boolean allowAll() default false; // 权限标识(支持按权限控制,如"jihe:export:vehplate")
String[] permissions() default {};
}

在这个注解中,我们定义了字段显示名称、列宽、日期格式、允许导出的角色列表、是否允许所有角色导出以及权限标识等属性。

3.2 编写 RoleExcelUtil 导出工具类

接下来,我们编写了 RoleExcelUtil 工具类,该工具类会根据用户角色过滤需要导出的字段,并将数据导出到 Excel 文件中。以下是 RoleExcelUtil 工具类的代码实现:

package com.sdhs.common.utils.poi;

import com.sdhs.common.annotation.RoleExcel;
import com.sdhs.common.core.domain.entity.SysRole;
import com.sdhs.common.core.domain.model.LoginUser;
import com.sdhs.common.utils.SecurityUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook; import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors; /*
* @description:支持角色权限的Excel工具类
* @author: 龙谷情
* @date: 2025-07-08 16:39:28
**/
public class RoleExcelUtil<T> { private final Class<T> clazz;
private List<ExcelColumn> excelColumns; public RoleExcelUtil(Class<T> clazz) {
this.clazz = clazz;
this.excelColumns = getFilteredColumns();
} /**
* 获取过滤后的列(根据角色权限)
*/
private List<ExcelColumn> getFilteredColumns() {
List<ExcelColumn> allColumns = new ArrayList<>();
Class<?> currentClass = clazz; while (currentClass != Object.class) {
Field[] fields = currentClass.getDeclaredFields();
for (Field field : fields) {
RoleExcel roleExcel = field.getAnnotation(RoleExcel.class);
if (roleExcel == null) {
continue;
}
if (hasExportPermission(roleExcel)) {
allColumns.add(buildExcelColumn(field, roleExcel));
}
}
currentClass = currentClass.getSuperclass();
}
return allColumns;
} /**
* 检查当前用户是否有权限导出该字段
*/
private boolean hasExportPermission(RoleExcel roleExcel) {
if (roleExcel.allowAll()) {
return true;
} LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser == null) {
return false;
} // 检查角色权限
String[] allowRoles = roleExcel.roles();
if (allowRoles.length > 0) {
List<String> userRoleKeys = loginUser.getUser().getRoles().stream()
.map(SysRole::getRoleKey)
.collect(Collectors.toList());
for (String role : allowRoles) {
if (userRoleKeys.contains(role) || loginUser.getUser().isAdmin()) {
return true;
}
}
} return false;
} /**
* 构建Excel列信息
*/
private ExcelColumn buildExcelColumn(Field field, RoleExcel roleExcel) {
ExcelColumn column = new ExcelColumn();
column.setField(field);
column.setHeader(roleExcel.name());
column.setWidth(roleExcel.width());
column.setDateFormat(roleExcel.dateFormat());
return column;
} /**
* 导出Excel文件
*/
public void exportExcel(HttpServletResponse response, List<T> list, String title) {
try (Workbook workbook = new SXSSFWorkbook(500)) {
Sheet sheet = workbook.createSheet(title);
setSheetStyle(sheet); // 创建表头
Row headerRow = sheet.createRow(0);
createHeader(headerRow); // 填充数据
for (int i = 0; i < list.size(); i++) {
Row dataRow = sheet.createRow(i + 1);
fillDataRow(dataRow, list.get(i));
} // 输出到客户端
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode(title + ".xlsx", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName); try (OutputStream os = response.getOutputStream()) {
workbook.write(os);
os.flush();
}
} catch (Exception e) {
throw new RuntimeException("导出Excel失败", e);
}
} /**
* 设置工作表样式
*/
private void setSheetStyle(Sheet sheet) {
for (int i = 0; i < excelColumns.size(); i++) {
sheet.setColumnWidth(i, excelColumns.get(i).getWidth() * 256);
}
} /**
* 创建表头
*/
private void createHeader(Row headerRow) {
CellStyle headerStyle = headerRow.getSheet().getWorkbook().createCellStyle();
Font headerFont = headerRow.getSheet().getWorkbook().createFont();
headerFont.setBold(true);
headerStyle.setFont(headerFont);
headerStyle.setAlignment(HorizontalAlignment.CENTER); for (int i = 0; i < excelColumns.size(); i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(excelColumns.get(i).getHeader());
cell.setCellStyle(headerStyle);
}
} /**
* 填充数据行
*/
private void fillDataRow(Row dataRow, T obj) throws Exception {
for (int i = 0; i < excelColumns.size(); i++) {
ExcelColumn column = excelColumns.get(i);
Field field = column.getField();
field.setAccessible(true); Cell cell = dataRow.createCell(i);
Object value = field.get(obj); if (value == null) {
cell.setCellValue("");
continue;
} // 处理日期类型
if (value instanceof Date && !column.getDateFormat().isEmpty()) {
CellStyle style = dataRow.getSheet().getWorkbook().createCellStyle();
DataFormat format = dataRow.getSheet().getWorkbook().createDataFormat();
style.setDataFormat(format.getFormat(column.getDateFormat()));
cell.setCellStyle(style);
cell.setCellValue((Date) value);
continue;
} // 处理基本类型
cell.setCellValue(value.toString());
}
} // ExcelColumn类定义
public static class ExcelColumn {
private Field field;
private String header;
private int width;
private String dateFormat; // getters/setters
public Field getField() {
return field;
} public void setField(Field field) {
this.field = field;
} public String getHeader() {
return header;
} public void setHeader(String header) {
this.header = header;
} public int getWidth() {
return width;
} public void setWidth(int width) {
this.width = width;
} public String getDateFormat() {
return dateFormat;
} public void setDateFormat(String dateFormat) {
this.dateFormat = dateFormat;
}
}
}

在这个工具类中,getFilteredColumns 方法用于获取过滤后的列,hasExportPermission 方法用于检查当前用户是否有权限导出该字段,exportExcel 方法用于将数据导出到 Excel 文件中。

3.3 在 JiheHandledRate 实体类中添加注解

JiheHandledRate 实体类中,我们在需要导出和进行角色控制的字段上添加了 RoleExcel 注解。以下是 JiheHandledRate 实体类的部分代码示例:

package com.sdhs.jihe.domain;

import com.sdhs.common.annotation.RoleExcel;
import com.sdhs.common.utils.CalculationUtils;
import lombok.Data; import static com.sdhs.common.utils.CalculationUtils.divide;
import static com.sdhs.common.utils.CalculationUtils.multiplyAndRound; @Data
public class JiheHandledRate {
private static final long serialVersionUID = 1L;
/**
* 运管中心名称
*/
@RoleExcel(name = "运管中心名称", allowAll = true)
private String ygcentername; /**
* 总数量
*/
@RoleExcel(name = "线索总数量", allowAll = true)
private Long totalCount;
package com.sdhs.jihe.domain; import com.sdhs.common.annotation.RoleExcel;
import com.sdhs.common.utils.CalculationUtils;
import lombok.Data; import static com.sdhs.common.utils.CalculationUtils.divide;
import static com.sdhs.common.utils.CalculationUtils.multiplyAndRound; @Data
public class JiheHandledRate {
private static final long serialVersionUID = 1L;
/**
* 运管中心名称
*/
@RoleExcel(name = "运管中心名称", allowAll = true)
private String ygcentername; /**
* 总数量
*/
@RoleExcel(name = "线索总数量", allowAll = true)
private Long totalCount; /**
* 已处理数量
*/
@RoleExcel(name = "已确认线索数量", allowAll = true)
private Long handledCount; /**
* 待处理数量
*/
@RoleExcel(name = "待确认线索数量", allowAll = true)
private Long unhandledCount; /**
* 处理数量占比 描述
*/
@RoleExcel(name = "线索确认占比", allowAll = true)
private String handledPercentDesc; /**
* 工单发起数量
*/
@RoleExcel(name = "工单发起数量", roles = {"internalAuditRole", "admin"})
private Long ticketCount; /**
* 工单发起数量占比 描述
*/
@RoleExcel(name = "工单发起数量占比", roles = {"internalAuditRole", "admin"})
private String ticketPercentDesc; /**
* 应追缴金额
*/
@RoleExcel(name = "工单合计漏征金额(元)", roles = {"internalAuditRole", "admin"})
private double owefeeYuan; // 其他字段...
}

3.4 在 JiheStationCommonController 中调用导出方法

最后,在 JiheStationCommonControllerexportHandledRate 方法中,我们调用了 RoleExcelUtil 工具类的 exportExcel 方法,完成数据的导出操作。以下是 exportHandledRate 方法的代码实现:

/**
* 导出处理率
*/
@Log(title = "导出处理率", businessType = BusinessType.EXPORT)
@PostMapping("/exportHandledRate")
public void exportHandledRate(HttpServletResponse response, JiheStationCommon jiheStationCommon) {
//jiheStationCommon.setIsabnormal(2);
jiheStationCommon.setCurrentstatus("abnormal");
jiheStationCommon.setTicketcodeFlag(2);
List<JiheHandledRate> list = jiheStationCommonService.statisticHandledRate(jiheStationCommon);
RoleExcelUtil<JiheHandledRate> util = new RoleExcelUtil<>(JiheHandledRate.class);
util.exportExcel(response, list, "稽核车辆明细数据");
}

四、总结

通过自定义 RoleExcel 注解和 RoleExcelUtil 导出工具类,我们成功实现了在若依框架中按角色控制 Excel 字段导出的功能。这种实现方式不仅满足了业务需求,还具有良好的扩展性和可维护性。在实际项目中,我们可以根据具体需求对注解和工具类进行进一步的扩展和优化,以适应更多复杂的业务场景。

希望本文对大家在实现类似功能时有所帮助,如果你在实现过程中遇到任何问题,欢迎在评论区留言交流。

扩展若依@Excel注解,使其对字段的控制是否导出更加便捷的更多相关文章

  1. EasyPoi中@Excel注解中numFormat的使用

    需求说明:使用EasyPoi时导出文件中折扣字段是小数,被测试同学提了一个bug,需要转成百分数导出. 个人觉得应该转百分号只要在@Excel注解里面加个属性即可,但是在网上的easypoi教程中没有 ...

  2. 2.自定义@Excel注解实现数据Excel形式导入导出

    前言 这几天在学习如何使用自定义注解实现Excel格式数据导入导出,参考的还是若依框架里面的代码,由于是初学,所以照猫画虎呗,但是难受的是需要复制并根据自己项目修改作者自定义的工具类以及导入这些工具类 ...

  3. Excel VBA入门(四)流程控制2-循环控制

    所谓循环控制,即在循环执行一段代码,用于完成一些重复性任务. VBA中的循环控制语句主要有3种:for.while.loop.对于大多数人来说,for的使用频率最高,而我个人也觉得for是最为灵活的, ...

  4. C#可扩展编程之MEF学习笔记(二):MEF的导出(Export)和导入(Import)

    上一篇学习完了MEF的基础知识,编写了一个简单的DEMO,接下来接着上篇的内容继续学习,如果没有看过上一篇的内容, 请阅读:http://www.cnblogs.com/yunfeifei/p/392 ...

  5. Excel VBA入门(三) 流程控制1-条件选择

    VBA中的流程控制分为两种,其一是条件结构式的,即根据条件判断的结果去选择性执行相应的语句(块):另一种是循环,即循环地执行语句(块).本节介绍第一种. 1. IF if 语句其实包含有几种形式: ① ...

  6. JUnit扩展:引入新注解Annotation

    发现问题 JUnit提供了Test Suite来帮助我们组织case,还提供了Category来帮助我们来给建立大的Test Set,比如BAT,MAT, Full Testing. 那么什么情况下, ...

  7. ribbon的注解使用报错--No instances available for [IP]

    使用RestTemplate类调用其他系统的url的时候,加上ribbon的注解@LoadBalanced上这个注解之后访问,就报错了. 报错如下: 因为这里你不能直接访问地址,需要把地址改成你所调用 ...

  8. SpringBoot 中使用shiro注解使之生效

    在shiroConfig配置类中增加如下代码: /** * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro ...

  9. @AutoWired注解使用时可能会发生的错误

    @Autowired注解:用于对Bean的属性变量.属性的set方法及构造函数进行标注,配合对应的注解处理器完成Bean的自动配置工作.@Autowired注解默认按照Bean类型进行装配.  1.在 ...

  10. 自定义注解扩展springMvc的validation注解

    文章目录 前言 自定义校验注解 使用 后记 前言 我们都知道 springMvc 的检验框架使用的是 hibernate 的 validator ,检验数据,是有那么一点小爽快: 但是,validat ...

随机推荐

  1. MySQL 的查询优化器如何选择执行计划?

    MySQL 的查询优化器负责决定如何执行 SQL 查询,它会根据多个因素选择最优的执行计划.查询优化器的目标是选择一个成本最低.性能最优的执行计划,以便高效地处理查询.执行计划的选择是基于 MySQL ...

  2. Innodb快速复习

    放一张官方架构图: 参考文章: 一文带你了解MySQL之InnoDB_Buffer_Pool-阿里云开发者社区这一篇buffer pool讲解的很好 [动画演示:MySQL的BufferPool和Ch ...

  3. Visual Studio 2022 划词翻译插件!该插件可以方便地翻译变量名、类名、方法名等单词,帮助您更轻松地理解和使用代码。

    EnTranslate一款简单的划词翻译插件 简介 支持划词翻译(鼠标悬浮到单词上方将自动翻译) 支持播放单词发音 支持调用在线接口翻译 强大的单词拆分能力: 支持驼峰, 下划线形式等各种单词拆分 丰 ...

  4. 备份一个迭代查找TreeViewItem的辅助函数

    private TreeViewItem FindTreeItem(TreeViewItem item, Func<TreeViewItem, bool> compare) { if (i ...

  5. P1166题解

    思路 花了半天去理解题意--意思是说给你一个选手的滚球情况,打出他当前的成绩.简单的说这题就是一个模拟(我才不是因为懒才找模拟题写的)思路也很简单,对每一轮进行以下几个判断就行啦: 首先判断有没有在两 ...

  6. VC6.0工具下载安装

    公众号回复:'VC6.0'

  7. WPF 解决PasswordBox 属性Password无法绑定到后台的问题

    在 WPF 中,你可以使用密码框的 Password 属性来绑定到后台,但是由于安全性考虑,WPF 的密码框不直接支持双向绑定.然而,你仍然可以通过其他方式实现将密码框的内容绑定到后台. 一种常见的方 ...

  8. java设置权限过滤器--防止用户未登录访问某些页面

    话不多说,上代码!!! package com.store.web.filter; import java.io.IOException; import javax.servlet.Filter; i ...

  9. 漏洞预警 | WordPress Plugin Radio Player SSRF漏洞

    0x00 漏洞编号 CVE-2024-54385 0x01 危险等级 高危 0x02 漏洞概述 WordPress插件Radio Player是一种简单而有效的解决方案,用于将实时流媒体音频添加到您的 ...

  10. C++11 shared_ptr(智能指针)

    在确保new动态分配的内存空间在使用结束之后,释放是一件麻烦事.C++11模板库的头文件中定义的智能指针,即shared_ptr模板,就是用来解决这个问题的. 它是将new运算符返回的指针p交给一个s ...