mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-09-17 17:15:28 +08:00
add 新增Excel 导出脱敏处理器
This commit is contained in:
parent
612a965c26
commit
34ee552a5e
@ -22,6 +22,12 @@
|
|||||||
<artifactId>ruoyi-common-json</artifactId>
|
<artifactId>ruoyi-common-json</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 脱敏模块 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dromara</groupId>
|
||||||
|
<artifactId>ruoyi-common-sensitive</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- excel -->
|
<!-- excel -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.fesod</groupId>
|
<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.Cell;
|
||||||
import org.apache.poi.ss.usermodel.Sheet;
|
import org.apache.poi.ss.usermodel.Sheet;
|
||||||
import org.apache.poi.ss.util.CellRangeAddress;
|
import org.apache.poi.ss.util.CellRangeAddress;
|
||||||
|
import org.dromara.common.excel.handler.CellMergeHandler;
|
||||||
|
|
||||||
import java.util.List;
|
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.collection.CollUtil;
|
||||||
import cn.hutool.core.util.ReflectUtil;
|
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.collection.CollUtil;
|
||||||
import cn.hutool.core.convert.Convert;
|
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.ExcelDictFormat;
|
||||||
import org.dromara.common.excel.annotation.ExcelDynamicOptions;
|
import org.dromara.common.excel.annotation.ExcelDynamicOptions;
|
||||||
import org.dromara.common.excel.annotation.ExcelEnumFormat;
|
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.lang.reflect.Field;
|
||||||
import java.util.*;
|
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.convert.ExcelBigNumberConvert;
|
||||||
import org.dromara.common.excel.core.*;
|
import org.dromara.common.excel.core.*;
|
||||||
import org.dromara.common.excel.handler.DataWriteHandler;
|
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.io.*;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
@ -474,6 +476,8 @@ public final class ExcelBuilder<T> {
|
|||||||
.autoCloseStream(false)
|
.autoCloseStream(false)
|
||||||
// 大数值自动转换 防止失真
|
// 大数值自动转换 防止失真
|
||||||
.registerConverter(new ExcelBigNumberConvert())
|
.registerConverter(new ExcelBigNumberConvert())
|
||||||
|
// 敏感字段脱敏
|
||||||
|
.registerWriteHandler(new SensitiveWriteHandler())
|
||||||
// 批注必填项处理
|
// 批注必填项处理
|
||||||
.registerWriteHandler(new DataWriteHandler(headType));
|
.registerWriteHandler(new DataWriteHandler(headType));
|
||||||
if (columnWidth == null) {
|
if (columnWidth == null) {
|
||||||
|
|||||||
@ -1,7 +1,13 @@
|
|||||||
package org.dromara.demo.controller;
|
package org.dromara.demo.controller;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
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.core.domain.R;
|
||||||
|
import org.dromara.common.excel.utils.ExcelBuilder;
|
||||||
import org.dromara.common.sensitive.annotation.Sensitive;
|
import org.dromara.common.sensitive.annotation.Sensitive;
|
||||||
import org.dromara.common.sensitive.core.SensitiveService;
|
import org.dromara.common.sensitive.core.SensitiveService;
|
||||||
import org.dromara.common.sensitive.core.SensitiveStrategy;
|
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.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 测试数据脱敏控制器
|
* 测试数据脱敏控制器
|
||||||
* <p>
|
* <p>
|
||||||
@ -38,39 +46,61 @@ public class TestSensitiveController extends BaseController {
|
|||||||
return R.ok(testSensitive);
|
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
|
@Data
|
||||||
|
@ExcelIgnoreUnannotated
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
static class TestSensitive {
|
static class TestSensitive {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 身份证
|
* 身份证
|
||||||
*/
|
*/
|
||||||
|
@ExcelProperty(value = "身份证", index = 0)
|
||||||
@Sensitive(strategy = SensitiveStrategy.ID_CARD)
|
@Sensitive(strategy = SensitiveStrategy.ID_CARD)
|
||||||
private String idCard;
|
private String idCard;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 电话
|
* 电话
|
||||||
*/
|
*/
|
||||||
|
@ExcelProperty(value = "电话", index = 1)
|
||||||
@Sensitive(strategy = SensitiveStrategy.PHONE, roleKey = "common")
|
@Sensitive(strategy = SensitiveStrategy.PHONE, roleKey = "common")
|
||||||
private String phone;
|
private String phone;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 地址
|
* 地址
|
||||||
*/
|
*/
|
||||||
|
@ExcelProperty(value = "地址", index = 2)
|
||||||
@Sensitive(strategy = SensitiveStrategy.ADDRESS, perms = "system:user:query")
|
@Sensitive(strategy = SensitiveStrategy.ADDRESS, perms = "system:user:query")
|
||||||
private String address;
|
private String address;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 邮箱
|
* 邮箱
|
||||||
*/
|
*/
|
||||||
|
@ExcelProperty(value = "邮箱", index = 3)
|
||||||
@Sensitive(strategy = SensitiveStrategy.EMAIL, roleKey = "common", perms = "system:user:query1")
|
@Sensitive(strategy = SensitiveStrategy.EMAIL, roleKey = "common", perms = "system:user:query1")
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 银行卡
|
* 银行卡
|
||||||
*/
|
*/
|
||||||
|
@ExcelProperty(value = "银行卡", index = 4)
|
||||||
@Sensitive(strategy = SensitiveStrategy.BANK_CARD, roleKey = "common1", perms = "system:user:query")
|
@Sensitive(strategy = SensitiveStrategy.BANK_CARD, roleKey = "common1", perms = "system:user:query")
|
||||||
private String bankCard;
|
private String bankCard;
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user