基于TypeScript装饰器与AirDesensitize类的前端数据脱敏方案

作者:异步漫步时间:2026-10-31 17:04:39浏览:396

本文介绍如何在TypeScript前端环境中,通过自定义装饰器与AirDesensitize助手类实现表格数据的自动脱敏。方案支持在@Table装饰器中配置desensitize枚举类型及保留位数,并允许自定义脱敏逻辑。通过拦截表格渲染,对敏感信息如手机号、身份证等进行掩码处理,同时保留查看原始数据的功能,平衡安全性与用户体验。

一、需求背景

尽管业界常有一种观点认为前端进行数据脱敏是多此一举,但在实际业务场景中,这一做法仍有其必要性。

当用户需要保存截图,或涉及极高敏感度的数据时,即便后端已进行脱敏处理,前端仍建议保留脱敏机制。

若不进行前端脱敏,用户在处理敏感数据截图时,需先截取屏幕,再借助工具手动涂抹敏感信息后方可发送,流程繁琐。

因此,业务需求明确如下:

前端表格中所有涉及敏感数据的列必须执行脱敏操作,且需支持用户通过交互点击查看原始数据。

二、需求目标

2.1 实现效果

一、需求背景

2.2 如何使用

一、需求背景

2.3 自定义脱敏

只需在目标属性上添加 @Table() 装饰器,并配置相应的脱敏方式即可。此外,系统也支持直接编写自定义脱敏逻辑:

一、需求背景

最终呈现的效果如下所示:

一、需求背景

三、实现过程

3.1 @Table参数声明

我们需要在负责管理表格状态的 @Table() 装饰器参数中,增加如下如 2.3 截图所示的配置项:

3.1.1 desensitize 脱敏类型

启用自动脱敏必须配置此项,该配置为枚举类型:

  static TELEPHONE = new AirDesensitizeType(0, '座机号码', 3, 4)
  static MOBILE = new AirDesensitizeType(1, '手机号码', 3, 4)
  static ID_CARD = new AirDesensitizeType(2, '身份证号', 6, 4)
  static BANK_CARD = new AirDesensitizeType(3, '银行卡号', 4, 4)
  static CAR_NUMBER = new AirDesensitizeType(4, '车牌号', 2, 1)
  static EMAIL = new AirDesensitizeType(5, '邮箱', 2, 2)
  static CHINESE_NAME = new AirDesensitizeType(6, '中文名', 1, 1)
  static ADDRESS = new AirDesensitizeType(7, '地址', 3, 0)
  static IP_V4 = new AirDesensitizeType(8, 'IPv4地址', 0, 0)
  static CUSTOM = new AirDesensitizeType(9, '自定义', 0, 0)

其中,枚举定义末尾的两个数字分别代表脱敏后头部保留的字符数和尾部保留的字符数。

3.1.2 desensitizeHead

指定脱敏开始位置前保留的字符数量,默认值为0,该值可覆盖 3.1.1 中定义的倒数第二个参数。

3.1.3 desensitizeTail

指定脱敏结束位置后保留的字符数量,默认值为0,该值可覆盖 3.1.1 中定义的倒数第一个参数。

3.1.4 desensitizeSymbol

用于替换敏感信息的符号,默认值为 *。

3.2 编写脱敏助手类

我们定义了一个 AirDesensitize 助手类,专门用于封装和实现具体的脱敏逻辑。

/**
 * # 脱敏助手类
 *
 * @author Hamm.cn
 */
export class AirDesensitize {
  /**
   * ## `IPv4` 的块长度
   */
  private static readonly IP_V4_PART_COUNT = 4

  /**
   * ## 字符串替换
   *
   * @param text   原始字符串
   * @param head   头部保留长度
   * @param tail   尾部保留长度
   * @param symbol 中间替换的单个符号
   * @return 替换后的字符串
   */
  public static replace(text: string, head: number, tail: number, symbol: string): string {
    if (head < 0 || tail < 0 || head + tail >= text.length) {
      return text
    }
    let str = ''
    for (let i = 0; i < text.length; i += 1) {
      if (i >= head && i <= text.length - tail - 1) {
        str += symbol
      } else {
        str += text[i]
      }
    }
    return str
  }

  /**
   * ## `IPv4` 地址脱敏
   *
   * @param ipv4   `IPv4` 地址
   * @param symbol [可选]符号
   * @return 脱敏后的 `IPv4` 地址
   */
  public static desensitizeIpv4Address(ipv4: string, symbol = AirConstant.ASTERISK): string {
    const strings = ipv4.split(AirConstant.DOT)
    if (strings.length !== AirDesensitize.IP_V4_PART_COUNT) {
      return ipv4
    }
    const temp = symbol + symbol + symbol
    strings[1] = temp
    strings[2] = temp
    return strings.join(AirConstant.DOT)
  }

  /**
   * ## 文本脱敏
   *
   * @param valueString 原始文本
   * @param type        脱敏类型
   * @param head        头部保留
   * @param tail        尾部保留
   * @param symbol      脱敏符号
   * @return 脱敏后的文本
   */
  public static desensitize(valueString: string, type: AirDesensitizeType, head = 0, tail = 0, symbol = AirConstant.ASTERISK): string {
    switch (type.key) {
      case AirDesensitizeType.BANK_CARD.key:
      case AirDesensitizeType.ID_CARD.key:
      case AirDesensitizeType.MOBILE.key:
      case AirDesensitizeType.ADDRESS.key:
      case AirDesensitizeType.CAR_NUMBER.key:
      case AirDesensitizeType.EMAIL.key:
        head = Math.max(type.head, head)
        tail = Math.max(type.tail, tail)
        break
      case AirDesensitizeType.IP_V4.key:
        return AirDesensitize.desensitizeIpv4Address(valueString, symbol)
      case AirDesensitizeType.CHINESE_NAME.key:
        head = Math.max(type.head, head)
        tail = Math.max(type.tail, tail)
        if (valueString.length <= head + tail) {
          tail = 0
        }
        break
      case AirDesensitizeType.TELEPHONE.key:
        // 包含区号 前后各留4 不包含则各留2
        // eslint-disable-next-line no-case-declarations
        const isContainRegionCode = valueString.length > 8 ? 4 : 2
        head = Math.max(isContainRegionCode, head)
        tail = Math.max(isContainRegionCode, tail)
        break
      default:
    }
    return this.replace(valueString, head, tail, symbol)
  }
}

3.3 给表格组件添加脱敏逻辑

<el-table-column>
  <!-- ...省略... -->
  <template v-if="columnConfig.desensitize">
    {{ AirDesensitize.desensitize(getColumnRowData(row), columnConfig.desensitize, columnConfig.desensitizeHead, columnConfig.desensitizeTail, columnConfig.desensitizeSymbol) }}
  </template>
  <template v-else>
    getColumnRowData(row)
  </template>
  <!-- ...省略...  -->
</el-table-column>

我们在组件中引入 v-if 判断当前列是否需要脱敏,随后调用 AirDesensitize 助手类提供的 desensitize 方法执行脱敏操作。

至此,核心功能开发完成。

后续其他开发者只需在属性上为 @Table() 装饰器标注相应参数,即可轻松应用脱敏功能。

到此关于使用TypeScript和装饰器实现前端数据脱敏的介绍结束,更多相关TypeScript数据脱敏内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

作者最新文章
相关文章
更多