爱花花爱莎莎 發表於 2025-7-8 18:05:00

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

<h1 id="基于若依框架实现按角色控制-excel-字段导出功能">基于若依框架实现按角色控制 Excel 字段导出功能</h1>
<h2 id="一背景介绍">一、背景介绍</h2>
<p>在我们的项目开发中,采用了若依(RuoYi)的 Java Spring 框架进行系统搭建。若依框架提供了 <code>@Excel</code> 注解,通过在实体类的字段上添加该注解,能够方便地实现 Excel 数据的导出功能。然而,在实际业务场景中,领导提出了根据用户角色来控制某些字段是否导出的需求。但遗憾的是,若依自带的 <code>@Excel</code> 注解并不支持这一功能。为了满足这一业务需求,我们决定自行开发一套解决方案。</p>
<h2 id="二解决方案思路">二、解决方案思路</h2>
<p>为了实现按角色控制 Excel 字段导出的功能,我们的核心思路是自定义一个注解和一个导出工具类。具体步骤如下:</p>
<ol>
<li><strong>自定义注解</strong>:创建一个新的注解 <code>RoleExcel</code>,该注解能够标识出需要导出的字段,并且可以指定允许导出该字段的角色列表。</li>
<li><strong>编写导出工具类</strong>:开发一个新的导出工具类 <code>RoleExcelUtil</code>,该工具类会根据当前用户的角色信息,过滤掉没有权限导出的字段,然后将有权限导出的字段数据导出到 Excel 文件中。</li>
<li><strong>实体类注解应用</strong>:在需要导出和进行角色控制的实体类 <code>JiheHandledRate</code> 的相应字段上添加 <code>RoleExcel</code> 注解。</li>
<li><strong>控制器调用</strong>:在控制器 <code>JiheStationCommonController</code> 的 <code>exportHandledRate</code> 方法中调用 <code>RoleExcelUtil</code> 工具类的导出方法,完成数据的导出操作。</li>
</ol>
<h2 id="三具体实现步骤">三、具体实现步骤</h2>
<h3 id="31-自定义-roleexcel-注解">3.1 自定义 <code>RoleExcel</code> 注解</h3>
<p>首先,我们定义了一个 <code>RoleExcel</code> 注解,用于标识需要导出的字段,并支持按角色控制导出。以下是 <code>RoleExcel</code> 注解的代码实现:</p>
<pre><code class="language-java">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 {};
}
</code></pre>
<p>在这个注解中,我们定义了字段显示名称、列宽、日期格式、允许导出的角色列表、是否允许所有角色导出以及权限标识等属性。</p>
<h3 id="32-编写-roleexcelutil-导出工具类">3.2 编写 <code>RoleExcelUtil</code> 导出工具类</h3>
<p>接下来,我们编写了 <code>RoleExcelUtil</code> 工具类,该工具类会根据用户角色过滤需要导出的字段,并将数据导出到 Excel 文件中。以下是 <code>RoleExcelUtil</code> 工具类的代码实现:</p>
<pre><code class="language-java">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&lt;T&gt; {

    private final Class&lt;T&gt; clazz;
    private List&lt;ExcelColumn&gt; excelColumns;

    public RoleExcelUtil(Class&lt;T&gt; clazz) {
      this.clazz = clazz;
      this.excelColumns = getFilteredColumns();
    }

    /**
   * 获取过滤后的列(根据角色权限)
   */
    private List&lt;ExcelColumn&gt; getFilteredColumns() {
      List&lt;ExcelColumn&gt; allColumns = new ArrayList&lt;&gt;();
      Class&lt;?&gt; 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 &gt; 0) {
            List&lt;String&gt; 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&lt;T&gt; 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 &lt; 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 &lt; 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 &lt; 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 &lt; 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 &amp;&amp; !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;
      }
    }
}
</code></pre>
<p>在这个工具类中,<code>getFilteredColumns</code> 方法用于获取过滤后的列,<code>hasExportPermission</code> 方法用于检查当前用户是否有权限导出该字段,<code>exportExcel</code> 方法用于将数据导出到 Excel 文件中。</p>
<h3 id="33-在-jihehandledrate-实体类中添加注解">3.3 在 <code>JiheHandledRate</code> 实体类中添加注解</h3>
<p>在 <code>JiheHandledRate</code> 实体类中,我们在需要导出和进行角色控制的字段上添加了 <code>RoleExcel</code> 注解。以下是 <code>JiheHandledRate</code> 实体类的部分代码示例:</p>
<pre><code class="language-java">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;

    // 其他字段...
}
</code></pre>
<h3 id="34-在-jihestationcommoncontroller-中调用导出方法">3.4 在 <code>JiheStationCommonController</code> 中调用导出方法</h3>
<p>最后,在 <code>JiheStationCommonController</code> 的 <code>exportHandledRate</code> 方法中,我们调用了 <code>RoleExcelUtil</code> 工具类的 <code>exportExcel</code> 方法,完成数据的导出操作。以下是 <code>exportHandledRate</code> 方法的代码实现:</p>
<pre><code class="language-java">/**
* 导出处理率
*/
@Log(title = "导出处理率", businessType = BusinessType.EXPORT)
@PostMapping("/exportHandledRate")
public void exportHandledRate(HttpServletResponse response, JiheStationCommon jiheStationCommon) {
    //jiheStationCommon.setIsabnormal(2);
    jiheStationCommon.setCurrentstatus("abnormal");
    jiheStationCommon.setTicketcodeFlag(2);
    List&lt;JiheHandledRate&gt; list = jiheStationCommonService.statisticHandledRate(jiheStationCommon);
    RoleExcelUtil&lt;JiheHandledRate&gt; util = new RoleExcelUtil&lt;&gt;(JiheHandledRate.class);
    util.exportExcel(response, list, "稽核车辆明细数据");
}
</code></pre>
<h2 id="四总结">四、总结</h2>
<p>通过自定义 <code>RoleExcel</code> 注解和 <code>RoleExcelUtil</code> 导出工具类,我们成功实现了在若依框架中按角色控制 Excel 字段导出的功能。这种实现方式不仅满足了业务需求,还具有良好的扩展性和可维护性。在实际项目中,我们可以根据具体需求对注解和工具类进行进一步的扩展和优化,以适应更多复杂的业务场景。</p>
<p>希望本文对大家在实现类似功能时有所帮助,如果你在实现过程中遇到任何问题,欢迎在评论区留言交流。</p>


</div>
<div id="MySignature" role="contentinfo">
    昔日我曾苍老,如今风华正茂(ง •̀_•́)ง<br><br>
来源:https://www.cnblogs.com/lgqrlchinese/p/18973540
頁: [1]
查看完整版本: 扩展若依@Excel注解,使其对字段的控制是否导出更加便捷