DUICUO

Java は POI を使用して、Excel エクスポートのデータの匿名化を実行します。

最近、EasyPoi をバージョン 4.3 にアップデートしました。その主な機能はデータの匿名化を実装し、誰もが日常的に匿名化を必要とする場合に便利に使えるようにすることです。

Baidu Baikeは、データ匿名化を、匿名化ルールを用いて機密情報を変換し、機密データや個人情報を確実に保護するプロセスと定義しています。顧客のセキュリティデータや特定の商業上の機密データを扱う場合、システムルールに違反することなく、実際のデータを加工してテストに提供します。IDカード番号、携帯電話番号、カード番号、顧客番号などの個人情報はすべて、データの匿名化が必要です。

IDカードなどのデータを定義に厳密に従って変換する機能は、まだ実装していません。これは、データ改ざん防止ルールが設計に対応しており、ユーザーがルールを指定する必要があるためです。社内の改ざん防止システムにもまだロジックが残っていますが、ここでは詳細は割愛します。今回実装する主な機能は、アスタリスク(*)の追加です(最も一般的なのは星印なので、後でカスタムアスタリスクを追加します)。これにより、データのエクスポート時に不要なフィールドを非表示にしやすくなります。


データの維持

主な機能

実装されている機能は主に3つの種類に分けられます。

  • 1. 末尾の文字を非表示にします。電話番号や ID カードなどの固定長テキストに適しています。
  • 2. 名前、住所など、長さが異なる部分を非表示にする
  • 3. 電子メール アドレスなど、保持する必要がある部分を指定して、特定の部分を非表示にします。

単純な効果は

  • 13112345678 --> 131****1234
  • 張三 --> 張*
  • ロナウジーニョ --> ロナウジーニョ
  • [email protected] -> w****@16com.com

脱感作効果

実施方法

1. 注釈

アノテーションは引き続き `@Excel` プロパティを通じて実装されます。`@Excel` に `desensitizationRule` プロパティが追加され、対応するフォーマットを設定して目的の結果を得ることができます。

  1. /**
  2. データ匿名化ルール
  3. * ルール 1: 先頭と末尾を保持する方式を使用し、中間のデータにアスタリスクを追加します。
  4. * 例: IDカード番号が6_4の場合、370101********1234のままにしてください
  5. ※末尾が3または4の電話番号の場合は、131****1234をそのまま使用してください。
  6. * ルール 2: ヘッダーの保持を優先して、明示的に非表示にされているフィールドを非表示にします。
  7. * 例: 名前 1,3 は、最大 3 文字、最小 1 文字を非表示にできることを意味します。
  8. * 李--> *  
  9. * 李三--> 李 *  
  10. *張全丹--> 張*丹 
  11. * 李張泉丹--> 李**丹 
  12. * ニコラス・リー・チャン・クアンダン -> ニコラス*** チャン・クアンダン
  13. * ルール3: 特殊文字を保持する
  14. * 例: メールアドレス 1~@ は最初の数字のみが保持され、@ の後のフィールドが保持されることを意味します。
  15. * [email protected] -> a********@wupaas.com
  16. * 複雑なバージョンの場合は、インターフェースを使用してください。
  17. * {@link cn.afterturn.easypoi.handler.inter.IExcelDataHandler}
  18. /
  19. パブリック文字列 desensitizationRule()デフォルト  "" ;

コメントからわかるように、3 つの簡単なルールの使用法は主に次のとおり非常に明確です。

  • アンダースコアで区切られた最初の形式では、開始と終了の両方が保持されます。
  • この形式では、開始と終了の両方を 0 にすることができ、何も保持されないことを示します。
  • カンマで区切られた隠しデータの2番目の形式
  • 保存のルールは主に対称性に基づいています。
  • 文字を区切るにはチルダ (~) を使用し、特殊文字の書式を保持します。
  • 複数の特殊文字が存在する場合は、最後の文字のみが保持されます。

具体的な実装コードは次のとおりです。

  1. /**
  2. * アスタリスクを追加して文字を区切ります。
  3. * @param 開始
  4. * @param マーク
  5. * @パラメータ値
  6. * @戻る 
  7. /
  8. プライベート静的文字列 markSpilt( int開始, 文字列マーク, 文字列値) {
  9. 値 == null場合
  10. 戻る ヌル;
  11. }
  12. 整数  end = value.lastIndexOf(マーク);
  13. if (終了<= 開始) {
  14. 戻り値;
  15. }
  16. StringUtils.left (value, start).concat(StringUtils.leftPad(StringUtils.right ( value, value.length() - end ) , value.length() - start, "*" ));を返します
  17. }
  18.  
  19. /**
  20. * 対称的な切り捨てを優先して、部分的なデータ切り捨てを行います。
  21. * @param 開始
  22. * @param終了 
  23. * @パラメータ値
  24. * @戻る 
  25. /
  26. プライベート静的文字列subMaxString( int start, int  終了、文字列値){
  27. 値 == null場合
  28. 戻る ヌル;
  29. }
  30. 開始 >終了場合
  31. 新しい IllegalArgumentException( "開始は終了より小さくなければなりません" );
  32. }
  33. int len ​​= 値.長さ();
  34. if (長さ <= 開始) {
  35. StringUtils.leftPad( "" , len, "*" )を返します
  36. }そうでなければ (len > start && len <= end ) {
  37. 長さ == 1 の場合
  38. 戻り値;
  39. }
  40. 長さ == 2 の場合
  41. StringUtils.left (value, 1).concat( "*" );返します
  42. }
  43. StringUtils.left ( value, 1).concat(StringUtils.leftPad(StringUtils.right ( value, 1), StringUtils.length(value) - 1, "*" ));を返します
  44. }それ以外{
  45. 開始 = ( int ) Math.ceil((len - end + 0.0D) / 2);
  46. end = len - start - end ;
  47. end = end == 0 ? 1 : end ;
  48. StringUtils.left (value, start) .concat (StringUtils.leftPad(StringUtils.right ( value, end ), len - start, "*" ));を返します
  49. }
  50. }
  51.  
  52. /**
  53. * 行末データの抽出
  54. * @param 開始
  55. * @param終了 
  56. * @パラメータ値
  57. * @戻る 
  58. /
  59. プライベート静的文字列subStartEndString( int start, int  終了、文字列値){
  60. 値 == null場合
  61. 戻る ヌル;
  62. }
  63. if (value.length() <= 開始 +終了) {
  64. 戻り値;
  65. }
  66. StringUtils.left (value, start).concat(StringUtils.leftPad(StringUtils.right ( value, end ), StringUtils.length(value) - start, "*" ));返します
  67. }

注釈デモ

使い方も非常に簡単で、簡単な注釈設定で実現できます。

  1. @Excel( name = "名前" , desensitizationRule = "1,6" )
  2. プライベート文字列;
  3. @Excel( name = "IDカード" , desensitizationRule = "6_4" )
  4. プライベート文字列カード。
  5. @Excel( name = "電話番号" , desensitizationRule = "3_4" )
  6. プライベートストリング電話;
  7. @Excel( name = "メールアドレス" , desensitizationRule = "3~@" )
  8. プライベート文字列メール;
  9.  
  10. // ---------------以下はExcelファイルを生成するコードです-------  
  11. // これらはすべてテスト データなので、一部のみが生成されました。
  12. リスト<DesensitizationEntity> リスト = 新しい ArrayList<>();
  13. ( int i = 0; i < 20; i++)の場合{
  14. DesensitizationEntity エンティティ = 新しい DesensitizationEntity();
  15. エンティティ.setCard( "37010119900101123" + i % 10);
  16. entity.setName( "張三" );
  17. エンティティ.setPhone( "1311234567" + i % 10);
  18. entity.setEmail(i % 10 + "[email protected]" );
  19. list.add (エンティティ);
  20. }
  21. 日付の開始 = 新しい日付();
  22. ExportParams params = new ExportParams( "脱感作テスト" , "脱感作テスト" , ExcelType.XSSF);
  23. ワークブック workbook = ExcelExportUtil.exportExcel(params, DesensitizationEntity.class, list);
  24. システム.out.println (new Date ().getTime() - start.getTime());
  25. ファイル savefile = new File( "D:/home/excel/" );
  26. 保存ファイルが存在する場合(){
  27. 保存ファイル.mkdirs();
  28. }
  29. ファイル出力ストリーム fos = 新しいファイル出力ストリーム ( "D:/home/excel/ExcelDesensitizationTest.xlsx" );
  30. workbook.write(fos);
  31. fos.close ();

生成された結果は以下の通りです。ご覧のとおり、複数のシナリオがカバーされており、基本的によく使用されるフィールドはすべて含まれています。

PS: バリアント アノテーションとアノテーションは互換性があるため、バリアント アノテーションを使用するときにこの方法を使用してデータを匿名化することもできます。

ここでは例は示しません。新しいExcelExportEntityを作成した後、setDesensitizationRule(rule)を呼び出して対応するルールを設定してください。ルールは上記のものと一致している必要があります。

2. テンプレート

テンプレート脱感作

グループでのフィードバックと質問の数から判断すると、現在最もよく使われている方法はテンプレートです。私は一貫してアノテーションを推奨していますが、テンプレートはアノテーションよりも明らかにシンプルで、コードへの影響も少ないです。私自身も独自の特殊タグをどんどん増やしています。

脱感作タグは「desensitizationRule」の最初の2文字から取得され、データの脱感作が必要であることを示します。使い方は非常に簡単で、「deru:1_3」のように記述します。「deru:」の後に具体的なルールが続きます。ルールの使い方は上記の説明と一致するため、ここでは繰り返しません。以下に例を示します。

  1. /**
  2. これには 2 つのルールが含まれます。
  3. * 1. deru: このデータは匿名化する必要があることを示します。
  4. * 2. dict: 転送には辞書インターフェースを呼び出す必要があることを示します。
  5. **/
  6. deru:1_1;dict:mtype;t.supMaterialList.mtype

脱感作はタグ処理における最下層です。タグの記述順序に関係なく、他のすべてのタグが処理された後にのみ処理されます。例えば、上記の例では、脱感作が最初のタグであるにもかかわらず、タグが処理される前に辞書が処理されます。

コアコードは次のとおりです。

  1. /**
  2. * テンプレート解析関数に基づいて値を取得する
  3. * @param funStr
  4. * @param マップ
  5. * @戻る 
  6. /
  7. プライベート Object getValByHandler(String funStr,Map<String, Object> map, Cell cell) は例外をスローします {
  8. // ステップ 2. 分析関数が存在するかどうかを判断します。
  9. if (isHasSymbol(funStr, NUMBER_SYMBOL)) {
  10. funStr = funStr.replaceFirst(NUMBER_SYMBOL, "" );
  11. }
  12. ブール値 isStyleBySelf = false ;
  13. if (isHasSymbol(funStr, STYLE_SELF)) {
  14. isStyleBySelf = true ;
  15. funStr = funStr.replaceFirst(STYLE_SELF, "" );
  16. }
  17. ブール値 isDict = false ;
  18. 文字列 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. ブール値 isI18n = false ;
  26. if (isHasSymbol(funStr, I18N_HANDLER)) {
  27. isI18n = true ;
  28. funStr = funStr.replaceFirst(I18N_HANDLER、 "" );
  29. }
  30. //このセクションでは、アノテーションルールが存在するかどうかを確認します。存在する場合は、アノテーションルールが選択され、削除されます。
  31. ブール値 isDern = false ;
  32. 文字列 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. 文字列 mergeStr = PoiPublicUtil.getElStr(funStr,MERGE);
  41. funStr = funStr.replace (mergeStr, "" );
  42. mergeStr = mergeStr.replaceFirst(MERGE, "" );
  43. 試す {
  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. } キャッチ (例外 e) {
  48. LOGGER.error(e.getMessage(),e);
  49. }
  50. }
  51. オブジェクト 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. objを返します
  63. }

テンプレートがどのように機能するかを以下に示します。

最終結果