基于若依框架实现按角色控制 Excel 字段导出功能
一、背景介绍
在我们的项目开发中,采用了若依(RuoYi)的 Java Spring 框架进行系统搭建。若依框架提供了 @Excel 注解,通过在实体类的字段上添加该注解,能够方便地实现 Excel 数据的导出功能。然而,在实际业务场景中,领导提出了根据用户角色来控制某些字段是否导出的需求。但遗憾的是,若依自带的 @Excel 注解并不支持这一功能。为了满足这一业务需求,我们决定自行开发一套解决方案。
二、解决方案思路
为了实现按角色控制 Excel 字段导出的功能,我们的核心思路是自定义一个注解和一个导出工具类。具体步骤如下:
- 自定义注解:创建一个新的注解
RoleExcel,该注解能够标识出需要导出的字段,并且可以指定允许导出该字段的角色列表。 - 编写导出工具类:开发一个新的导出工具类
RoleExcelUtil,该工具类会根据当前用户的角色信息,过滤掉没有权限导出的字段,然后将有权限导出的字段数据导出到 Excel 文件中。 - 实体类注解应用:在需要导出和进行角色控制的实体类
JiheHandledRate的相应字段上添加RoleExcel注解。 - 控制器调用:在控制器
JiheStationCommonController的exportHandledRate方法中调用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 中调用导出方法
最后,在 JiheStationCommonController 的 exportHandledRate 方法中,我们调用了 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 字段导出的功能。这种实现方式不仅满足了业务需求,还具有良好的扩展性和可维护性。在实际项目中,我们可以根据具体需求对注解和工具类进行进一步的扩展和优化,以适应更多复杂的业务场景。
希望本文对大家在实现类似功能时有所帮助,如果你在实现过程中遇到任何问题,欢迎在评论区留言交流。