mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-09-18 01:25:28 +08:00
add 新增Excel 导出脱敏处理器
This commit is contained in:
parent
612a965c26
commit
34ee552a5e
@ -22,6 +22,12 @@
|
||||
<artifactId>ruoyi-common-json</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 脱敏模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-sensitive</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- excel -->
|
||||
<dependency>
|
||||
<groupId>org.apache.fesod</groupId>
|
||||
|
||||
@ -10,6 +10,7 @@ import org.apache.fesod.sheet.write.metadata.holder.WriteWorkbookHolder;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
import org.dromara.common.excel.handler.CellMergeHandler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package org.dromara.common.excel.core;
|
||||
package org.dromara.common.excel.handler;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
@ -1,4 +1,4 @@
|
||||
package org.dromara.common.excel.core;
|
||||
package org.dromara.common.excel.handler;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
@ -26,6 +26,8 @@ import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.excel.annotation.ExcelDictFormat;
|
||||
import org.dromara.common.excel.annotation.ExcelDynamicOptions;
|
||||
import org.dromara.common.excel.annotation.ExcelEnumFormat;
|
||||
import org.dromara.common.excel.core.DropDownOptions;
|
||||
import org.dromara.common.excel.core.ExcelOptionsProvider;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
@ -0,0 +1,165 @@
|
||||
package org.dromara.common.excel.handler;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import org.apache.fesod.sheet.enums.CellDataTypeEnum;
|
||||
import org.apache.fesod.sheet.metadata.Head;
|
||||
import org.apache.fesod.sheet.metadata.data.WriteCellData;
|
||||
import org.apache.fesod.sheet.metadata.property.ExcelContentProperty;
|
||||
import org.apache.fesod.sheet.write.handler.CellWriteHandler;
|
||||
import org.apache.fesod.sheet.write.handler.context.CellWriteHandlerContext;
|
||||
import org.apache.fesod.sheet.write.metadata.holder.WriteSheetHolder;
|
||||
import org.apache.fesod.sheet.write.metadata.holder.WriteTableHolder;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.sensitive.annotation.Sensitive;
|
||||
import org.dromara.common.sensitive.core.SensitiveService;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Excel 导出脱敏处理器。
|
||||
*/
|
||||
public class SensitiveWriteHandler implements CellWriteHandler {
|
||||
|
||||
/**
|
||||
* 脱敏服务 Bean。服务本身只取一次,具体是否脱敏仍由服务在当前线程内按登录态判断。
|
||||
*/
|
||||
private static volatile SensitiveService sensitiveService;
|
||||
|
||||
/**
|
||||
* 是否已尝试加载脱敏服务。没有引入实现时也只查一次,避免每个单元格反复访问 Spring 容器。
|
||||
*/
|
||||
private static volatile boolean sensitiveServiceLoaded;
|
||||
|
||||
/**
|
||||
* 有脱敏注解的列缓存。
|
||||
* <p>
|
||||
* key 同时包含 sheet/table holder 身份与列号,避免同一个写处理器在多 sheet 或多 table 写出时列号相同但字段不同。
|
||||
*/
|
||||
private final Map<ColumnKey, Sensitive> sensitiveColumnMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 无脱敏注解的列缓存。非敏感列命中后直接跳过,避免每行重复解析字段注解。
|
||||
*/
|
||||
private final Set<ColumnKey> noneSensitiveColumnSet = ConcurrentHashMap.newKeySet();
|
||||
|
||||
/**
|
||||
* 延迟获取脱敏服务
|
||||
*
|
||||
* @return 脱敏服务
|
||||
*/
|
||||
private static SensitiveService getSensitiveService() {
|
||||
if (!sensitiveServiceLoaded) {
|
||||
synchronized (SensitiveWriteHandler.class) {
|
||||
if (!sensitiveServiceLoaded) {
|
||||
try {
|
||||
sensitiveService = SpringUtils.getBean(SensitiveService.class);
|
||||
} catch (NoSuchBeanDefinitionException ignored) {
|
||||
sensitiveService = null;
|
||||
}
|
||||
sensitiveServiceLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return sensitiveService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在单元格数据转换后按字段脱敏注解改写导出值。
|
||||
*
|
||||
* @param context 单元格写入上下文
|
||||
*/
|
||||
@Override
|
||||
public void afterCellDataConverted(CellWriteHandlerContext context) {
|
||||
if (Boolean.TRUE.equals(context.getHead()) || context.getColumnIndex() == null
|
||||
|| !(context.getOriginalValue() instanceof String text)) {
|
||||
return;
|
||||
}
|
||||
Sensitive sensitive = getSensitive(context);
|
||||
if (sensitive == null) {
|
||||
return;
|
||||
}
|
||||
SensitiveService service = getSensitiveService();
|
||||
if (ObjectUtil.isNotNull(service) && service.isSensitive(sensitive.roleKey(), sensitive.perms())) {
|
||||
WriteCellData<?> cellData = context.getFirstCellData();
|
||||
if (cellData != null) {
|
||||
String value = sensitive.strategy().desensitizer().apply(text);
|
||||
if (value == null) {
|
||||
cellData.setType(CellDataTypeEnum.EMPTY);
|
||||
return;
|
||||
}
|
||||
cellData.setType(CellDataTypeEnum.STRING);
|
||||
cellData.setStringValue(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前单元格对应字段上的脱敏注解。
|
||||
*
|
||||
* @param context 单元格写入上下文
|
||||
* @return 脱敏注解
|
||||
*/
|
||||
private Sensitive getSensitive(CellWriteHandlerContext context) {
|
||||
ColumnKey columnKey = buildColumnKey(context);
|
||||
Sensitive cached = sensitiveColumnMap.get(columnKey);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
if (noneSensitiveColumnSet.contains(columnKey)) {
|
||||
return null;
|
||||
}
|
||||
Sensitive sensitive = resolveSensitive(context);
|
||||
if (sensitive == null) {
|
||||
noneSensitiveColumnSet.add(columnKey);
|
||||
return null;
|
||||
}
|
||||
sensitiveColumnMap.put(columnKey, sensitive);
|
||||
return sensitive;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析当前列对应字段上的脱敏注解。
|
||||
*
|
||||
* @param context 单元格写入上下文
|
||||
* @return 脱敏注解
|
||||
*/
|
||||
private Sensitive resolveSensitive(CellWriteHandlerContext context) {
|
||||
Head headData = context.getHeadData();
|
||||
Field field = headData == null ? null : headData.getField();
|
||||
if (field == null) {
|
||||
ExcelContentProperty contentProperty = context.getExcelContentProperty();
|
||||
field = contentProperty == null ? null : contentProperty.getField();
|
||||
}
|
||||
return field == null ? null : field.getAnnotation(Sensitive.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建列缓存 key。
|
||||
* <p>
|
||||
* Fesod 在自定义写出时同一个处理器可能服务多个 sheet/table
|
||||
*
|
||||
* @param context 单元格写入上下文
|
||||
* @return 列缓存 key
|
||||
*/
|
||||
private ColumnKey buildColumnKey(CellWriteHandlerContext context) {
|
||||
WriteTableHolder tableHolder = context.getWriteTableHolder();
|
||||
if (tableHolder != null) {
|
||||
return new ColumnKey(System.identityHashCode(tableHolder), context.getColumnIndex());
|
||||
}
|
||||
WriteSheetHolder sheetHolder = context.getWriteSheetHolder();
|
||||
return new ColumnKey(System.identityHashCode(sheetHolder), context.getColumnIndex());
|
||||
}
|
||||
|
||||
/**
|
||||
* 列缓存 key。
|
||||
*
|
||||
* @param holderIdentity sheet/table holder 对象身份
|
||||
* @param columnIndex 列索引
|
||||
*/
|
||||
private record ColumnKey(int holderIdentity, int columnIndex) {
|
||||
}
|
||||
}
|
||||
@ -27,6 +27,8 @@ import org.dromara.common.core.utils.file.FileUtils;
|
||||
import org.dromara.common.excel.convert.ExcelBigNumberConvert;
|
||||
import org.dromara.common.excel.core.*;
|
||||
import org.dromara.common.excel.handler.DataWriteHandler;
|
||||
import org.dromara.common.excel.handler.ExcelDownHandler;
|
||||
import org.dromara.common.excel.handler.SensitiveWriteHandler;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URLEncoder;
|
||||
@ -474,6 +476,8 @@ public final class ExcelBuilder<T> {
|
||||
.autoCloseStream(false)
|
||||
// 大数值自动转换 防止失真
|
||||
.registerConverter(new ExcelBigNumberConvert())
|
||||
// 敏感字段脱敏
|
||||
.registerWriteHandler(new SensitiveWriteHandler())
|
||||
// 批注必填项处理
|
||||
.registerWriteHandler(new DataWriteHandler(headType));
|
||||
if (columnWidth == null) {
|
||||
|
||||
@ -1,7 +1,13 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
|
||||
import org.apache.fesod.sheet.annotation.ExcelProperty;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.excel.utils.ExcelBuilder;
|
||||
import org.dromara.common.sensitive.annotation.Sensitive;
|
||||
import org.dromara.common.sensitive.core.SensitiveService;
|
||||
import org.dromara.common.sensitive.core.SensitiveStrategy;
|
||||
@ -10,6 +16,8 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 测试数据脱敏控制器
|
||||
* <p>
|
||||
@ -38,39 +46,61 @@ public class TestSensitiveController extends BaseController {
|
||||
return R.ok(testSensitive);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试导出 Excel 脱敏
|
||||
*/
|
||||
@GetMapping("/export")
|
||||
public void exporttest(HttpServletResponse response) {
|
||||
TestSensitive testSensitive = new TestSensitive();
|
||||
testSensitive.setIdCard("210397198608215431");
|
||||
testSensitive.setPhone("17640125371");
|
||||
testSensitive.setAddress("北京市朝阳区某某四合院1203室");
|
||||
testSensitive.setEmail("17640125371@163.com");
|
||||
testSensitive.setBankCard("6226456952351452853");
|
||||
ExcelBuilder.of(List.of(testSensitive), TestSensitive.class).sheetName("测试树表").toResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 脱敏测试对象。
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
static class TestSensitive {
|
||||
|
||||
/**
|
||||
* 身份证
|
||||
*/
|
||||
@ExcelProperty(value = "身份证", index = 0)
|
||||
@Sensitive(strategy = SensitiveStrategy.ID_CARD)
|
||||
private String idCard;
|
||||
|
||||
/**
|
||||
* 电话
|
||||
*/
|
||||
@ExcelProperty(value = "电话", index = 1)
|
||||
@Sensitive(strategy = SensitiveStrategy.PHONE, roleKey = "common")
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 地址
|
||||
*/
|
||||
@ExcelProperty(value = "地址", index = 2)
|
||||
@Sensitive(strategy = SensitiveStrategy.ADDRESS, perms = "system:user:query")
|
||||
private String address;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
@ExcelProperty(value = "邮箱", index = 3)
|
||||
@Sensitive(strategy = SensitiveStrategy.EMAIL, roleKey = "common", perms = "system:user:query1")
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 银行卡
|
||||
*/
|
||||
@ExcelProperty(value = "银行卡", index = 4)
|
||||
@Sensitive(strategy = SensitiveStrategy.BANK_CARD, roleKey = "common1", perms = "system:user:query")
|
||||
private String bankCard;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user