Java用poi完成Excel导出数据脱敏

开源
脱敏的百度百科的定义:是指对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护。在涉及客户安全数据或者一些商业性敏感数据的情况下,在不违反系统规则条件下,对真实数据进行改造并提供测试使用,如身份证号、手机号、卡号、客户号等个人信息都需要进行数据脱敏。

 近日笔者更新了EasyPoi的4.3版本,主要功能是实现了数据脱敏,方便大家日常的脱敏需求。

脱敏的百度百科的定义:是指对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护。在涉及客户安全数据或者一些商业性敏感数据的情况下,在不违反系统规则条件下,对真实数据进行改造并提供测试使用,如身份证号、手机号、卡号、客户号等个人信息都需要进行数据脱敏。

这块如果严格按照定义,实现身份证等数据的变形,笔者还没实现,因为这个设计脱敏规则对应,需要用户指定规则,我们内部的脱敏系统还是有些逻辑在的,这里就不细说了,这次主要实现的是加*号【后期再加自定义吧,毕竟常见的都是星号】,方便导出数据的时候隐藏非必须字段。


维护数据

主要功能

实现的功能主要分为三种:

  • 1. 隐去收尾,适合固定长度,比如:手机号,身份证
  • 2. 隐去部分,不固定长度,比如:姓名,地址
  • 3. 隐去特定部分,特别表示保留,比如:邮箱

简单地实现的效果为

  • 13112345678 --> 131****1234
  • 张三 -->张*
  • 罗纳尔迪尼奥 --> 罗纳***奥
  • wuyun@163.com -> w****@16com.com

脱敏效果

实现方法

1.注解

注解还是通过@Excel 的属性来实现的 ,在@Excel 这个地方加入了desensitizationRule属性,可以在desensitizationRule属性配置对应的格式遍可以得到对应的结果

  1. /** 
  2.   * 数据脱敏规则 
  3.   * 规则1: 采用保留头和尾的方式,中间数据加星号 
  4.   * 如: 身份证  6_4 则保留 370101********1234 
  5.   *    手机号   3_4 则保留 131****1234 
  6.   * 规则2: 采用确定隐藏字段的进行隐藏,优先保留头 
  7.   * 如: 姓名   1,3 表示最大隐藏3位,最小一位 
  8.   *          李 -->  * 
  9.   *          李三 --> 李* 
  10.   *          张全蛋  --> 张*蛋 
  11.   *          李张全蛋 --> 李**蛋 
  12.   *          尼古拉斯.李张全蛋 -> 尼古拉***张全蛋 
  13.   * 规则3: 特殊符号后保留 
  14.   * 如: 邮箱    1~@ 表示只保留第一位和@之后的字段 
  15.   *        afterturn@wupaas.com -> a********@wupaas.com 
  16.   * 复杂版本请使用接口 
  17.   * {@link cn.afterturn.easypoi.handler.inter.IExcelDataHandler} 
  18.   */ 
  19.  public String   desensitizationRule() default ""

 可以根据注释看到,3个简单规则的使用方法,还是比较清晰的,主要是:

  • 用下划线分隔的保留头尾的第一种格式
  • 该格式首和尾都可以是0,表示不保留
  • 用逗号分隔的隐藏部分数据的第二种格式
  • 保留规则以对称为主
  • 用~号来区分的,保留特殊字符后的格式的
  • 如果存在多个特殊字符,只保留最后一位的数据

具体实现代码如下:

  1. /** 
  2.      * 特定字符分隔,添加星号 
  3.      * @param start 
  4.      * @param mark 
  5.      * @param value 
  6.      * @return 
  7.      */ 
  8.     private static String markSpilt(int start, String mark, String value) { 
  9.         if (value == null) { 
  10.             return null
  11.         } 
  12.         int end = value.lastIndexOf(mark); 
  13.         if (end <= start) { 
  14.             return value; 
  15.         } 
  16.         return StringUtils.left(value, start).concat(StringUtils.leftPad(StringUtils.right(value, value.length() - end), value.length() - start, "*")); 
  17.     } 
  18.  
  19.     /** 
  20.      * 部分数据截取,优先对称截取 
  21.      * @param start 
  22.      * @param end 
  23.      * @param value 
  24.      * @return 
  25.      */ 
  26.     private static String subMaxString(int start, int end, String value) { 
  27.         if (value == null) { 
  28.             return null
  29.         } 
  30.         if (start > end) { 
  31.             throw new IllegalArgumentException("start must less end"); 
  32.         } 
  33.         int len = value.length(); 
  34.         if (len <= start) { 
  35.             return StringUtils.leftPad("", len, "*"); 
  36.         } else if (len > start && len <= end) { 
  37.             if (len == 1) { 
  38.                 return value; 
  39.             } 
  40.             if (len == 2) { 
  41.                 return StringUtils.left(value, 1).concat("*"); 
  42.             } 
  43.             return StringUtils.left(value, 1).concat(StringUtils.leftPad(StringUtils.right(value, 1), StringUtils.length(value) - 1, "*")); 
  44.         } else { 
  45.             start = (int) Math.ceil((len - end + 0.0D) / 2); 
  46.             end = len - start - end
  47.             end = end == 0 ? 1 : end
  48.             return StringUtils.left(value, start).concat(StringUtils.leftPad(StringUtils.right(value, end), len - start, "*")); 
  49.         } 
  50.     } 
  51.  
  52.     /** 
  53.      * 收尾截取数据 
  54.      * @param start 
  55.      * @param end 
  56.      * @param value 
  57.      * @return 
  58.      */ 
  59.     private static String subStartEndString(int start, int end, String value) { 
  60.         if (value == null) { 
  61.             return null
  62.         } 
  63.         if (value.length() <= start + end) { 
  64.             return value; 
  65.         } 
  66.         return StringUtils.left(value, start).concat(StringUtils.leftPad(StringUtils.right(value, end), StringUtils.length(value) - start, "*")); 
  67.     } 

 注解demo

使用起来也比较简单,通过简单的注解配置,就可以实现

  1.    @Excel(name = "姓名", desensitizationRule = "1,6"
  2.     private String name
  3.     @Excel(name = "身份证", desensitizationRule = "6_4"
  4.     private String card; 
  5.     @Excel(name = "手机号", desensitizationRule = "3_4"
  6.     private String phone; 
  7.     @Excel(name = "邮箱", desensitizationRule = "3~@"
  8.     private String email; 
  9.  
  10. // ---------------下面是生成excel的代码------- 
  11. // 都是测试数据,就简单生成了部分 
  12.  List<DesensitizationEntity> list = new ArrayList<>(); 
  13.         for (int i = 0; i < 20; i++) { 
  14.             DesensitizationEntity entity = new DesensitizationEntity(); 
  15.             entity.setCard("37010119900101123" + i % 10); 
  16.             entity.setName("张三"); 
  17.             entity.setPhone("1311234567" + i % 10); 
  18.             entity.setEmail(i % 10 + "ttttt@afterturn.com"); 
  19.             list.add(entity); 
  20.         } 
  21.         Date         start    = new Date(); 
  22.         ExportParams params   = new ExportParams("脱敏测试""脱敏测试", ExcelType.XSSF); 
  23.         Workbook     workbook = ExcelExportUtil.exportExcel(params, DesensitizationEntity.class, list); 
  24.         System.out.println(new Date().getTime() - start.getTime()); 
  25.         File savefile = new File("D:/home/excel/"); 
  26.         if (!savefile.exists()) { 
  27.             savefile.mkdirs(); 
  28.         } 
  29.         FileOutputStream fos = new FileOutputStream("D:/home/excel/ExcelDesensitizationTest.xlsx"); 
  30.         workbook.write(fos); 
  31.         fos.close(); 

 生成的效果如下,可以看到几个场景都有所覆盖,基本上常用的字段也就

PS: 因为变种注解和注解可以通用,所以大家在使用变种注解的时候也可以使用这种方式来脱敏数据。

这里就不举例子了,只需要new ExcelExportEntity的之后调用setDesensitizationRule(rule),把对应的规则设置就好,规则和上面保持一致。

2. 模板

模板脱敏

目前从群里反应和问题数量看,模板已经是最常用的方式了,虽然我一致推荐注解,但明显模板比注解简单的,对代码的侵入一些,我自己写的特殊标签,也日益丰富了。

脱敏的标签取自:desensitizationRule 的两个单词的头两个字符 deru,用来表示这个数据需要脱敏。使用方法也比较简单deru:1_3,即deru:后面跟具体的规则,规则使用方式和上面介绍的 保持一致,这里就不重复赘述了,下面给大家看下例子:

  1. /** 
  2. *  这个包含两个规则 
  3. * 1. deru: 代表这个数据需要脱敏 
  4. * 2. dict: 代表这个需要调用字典接口转移 
  5. **/ 
  6. deru:1_1;dict:mtype;t.supMaterialList.mtype 

 脱敏在标签处理级别中属于最低级,所有标签处理后,才会处理这个标签,和他的书写顺序无关,比如上面的例子他写着第一个,也是先处理了字典之后才处理标签。

核心代码如下:

  1. /** 
  2.      * 根据模板解析函数获取值 
  3.      * @param funStr 
  4.      * @param map 
  5.      * @return 
  6.      */ 
  7.     private Object getValByHandler(String funStr,Map<String, Object> map, Cell cell) throws Exception { 
  8.         // step 2. 判断是否含有解析函数 
  9.         if (isHasSymbol(funStr, NUMBER_SYMBOL)) { 
  10.             funStr = funStr.replaceFirst(NUMBER_SYMBOL, ""); 
  11.         } 
  12.         boolean isStyleBySelf = false
  13.         if (isHasSymbol(funStr, STYLE_SELF)) { 
  14.             isStyleBySelf = true
  15.             funStr = funStr.replaceFirst(STYLE_SELF, ""); 
  16.         } 
  17.         boolean isDict = false
  18.         String  dict   = null
  19.         if (isHasSymbol(funStr, DICT_HANDLER)) { 
  20.             isDict = true
  21.             dict = funStr.substring(funStr.indexOf(DICT_HANDLER) + 5).split(";")[0]; 
  22.             funStr = funStr.replaceFirst(DICT_HANDLER, ""); 
  23.             funStr = funStr.replaceFirst(dict + ";"""); 
  24.         } 
  25.         boolean isI18n = false
  26.         if (isHasSymbol(funStr, I18N_HANDLER)) { 
  27.             isI18n = true
  28.             funStr = funStr.replaceFirst(I18N_HANDLER, ""); 
  29.         } 
  30.        //这里判断是否包含注解规则,如果有注解规则就把注解规则挑出来,然后删除掉规则 
  31.         boolean isDern = false
  32.         String  dern   = null
  33.         if (isHasSymbol(funStr, DESENSITIZATION_RULE)) { 
  34.             isDern = true
  35.             dern = funStr.substring(funStr.indexOf(DESENSITIZATION_RULE) + 5).split(";")[0]; 
  36.             funStr = funStr.replaceFirst(DESENSITIZATION_RULE, ""); 
  37.             funStr = funStr.replaceFirst(dern + ";"""); 
  38.         } 
  39.         if (isHasSymbol(funStr, MERGE)) { 
  40.             String mergeStr = PoiPublicUtil.getElStr(funStr,MERGE); 
  41.             funStr = funStr.replace(mergeStr, ""); 
  42.             mergeStr = mergeStr.replaceFirst(MERGE, ""); 
  43.             try { 
  44.                 int colSpan = (int)Double.parseDouble(PoiPublicUtil.getRealValue(mergeStr, map).toString()); 
  45.                 PoiMergeCellUtil.addMergedRegion(cell.getSheet(), cell.getRowIndex(), 
  46.                         cell.getRowIndex() , cell.getColumnIndex(), cell.getColumnIndex() + colSpan - 1); 
  47.             } catch (Exception e) { 
  48.                 LOGGER.error(e.getMessage(),e); 
  49.             } 
  50.         } 
  51.         Object obj = funStr.indexOf(START_STR) == -1 ? eval(funStr, map) : PoiPublicUtil.getRealValue(funStr, map); 
  52.         if (isDict) { 
  53.             obj = dictHandler.toName(dict, null, funStr, obj); 
  54.         } 
  55.         if (isI18n) { 
  56.             obj = i18nHandler.getLocaleName(obj.toString()); 
  57.         } 
  58.       // 这里就是调用脱敏规则,和注解是一个工具类,所以规则一致 
  59.         if (isDern) { 
  60.             obj = PoiDataDesensitizationUtil.desensitization(dern,obj); 
  61.         } 
  62.         return obj; 
  63.     } 

 给大家看下模板跑出来的效果:

最后结果

 

责任编辑:姜华 来源: 今日头条
相关推荐

2021-11-04 10:45:46

SpringBootExcelJava

2012-02-22 09:44:21

Java

2023-02-25 10:04:21

JavaExcel导出功能

2023-02-03 08:21:30

excelMySQL

2022-12-28 08:17:36

数据库数据导出

2010-06-22 14:58:50

JDOMJavaXML

2018-01-26 07:53:46

数据脱敏数据安全信息安全

2010-07-21 14:17:36

SQL Server数

2021-03-26 07:09:15

Java技术pdfExcel

2011-04-14 09:05:07

ExcelMySQL数据

2017-02-05 17:27:43

2020-12-02 11:56:16

Java注解Excel

2022-05-16 08:50:23

数据脱加密器

2021-10-22 06:53:45

脱敏处理数据

2022-12-29 08:49:40

SpringBootExcel

2010-05-17 16:25:05

MySQL数据

2022-02-09 18:28:46

多线程Excel代码

2024-07-30 15:56:42

2011-11-17 13:04:58

JDOMJavaXML

2018-01-02 17:42:44

数据脱敏数据安全数据泄露
点赞
收藏

51CTO技术栈公众号