⚠️ Alpha内测版本警告:此为早期内部构建版本,尚不完整且可能存在错误,欢迎大家提Issue反馈问题或建议
Skip to content

4.6.3 生产数据不能明文给测试——生产数据脱敏与测试数据生成

一句话破题

生产数据是调试的最佳素材,但直接使用会泄露隐私——脱敏是让数据"可用但不可识别"的关键技术。

为什么需要脱敏?

敏感信息类型

  • 个人身份信息(姓名、身份证、手机号)
  • 认证信息(密码、Token)
  • 财务信息(银行卡、交易记录)
  • 业务敏感数据

脱敏策略

策略说明示例
替换用假数据替换张三 → 用户A
遮蔽部分隐藏13812345678 → 138****5678
哈希不可逆转换email → hash(email)
随机化完全随机真实数据 → faker数据
保留格式保持格式一致有效手机号 → 假手机号

脱敏脚本示例

typescript
// scripts/sanitize.ts
import { PrismaClient } from '@prisma/client'
import { faker } from '@faker-js/faker'
import crypto from 'crypto'

const prisma = new PrismaClient()

async function sanitizeUsers() {
  const users = await prisma.user.findMany()
  
  for (const user of users) {
    await prisma.user.update({
      where: { id: user.id },
      data: {
        // 替换姓名
        name: faker.person.fullName(),
        // 保留格式的邮箱
        email: `user_${hashId(user.id)}@example.com`,
        // 替换手机号
        phone: faker.phone.number(),
        // 重置密码为统一测试密码
        password: await hashPassword('test123456')
      }
    })
  }
}

function hashId(id: string): string {
  return crypto.createHash('md5').update(id).digest('hex').slice(0, 8)
}

async function main() {
  console.log('Starting data sanitization...')
  await sanitizeUsers()
  await sanitizeOrders()
  await sanitizePayments()
  console.log('Sanitization completed!')
}

main()
  .catch(console.error)
  .finally(() => prisma.$disconnect())

分字段脱敏

typescript
// lib/sanitizers.ts
export const sanitizers = {
  // 姓名:完全替换
  name: () => faker.person.fullName(),
  
  // 邮箱:保留域名结构
  email: (original: string) => {
    const domain = original.split('@')[1] || 'example.com'
    return `user_${faker.string.alphanumeric(8)}@${domain}`
  },
  
  // 手机号:遮蔽中间4位
  phone: (original: string) => {
    return original.slice(0, 3) + '****' + original.slice(-4)
  },
  
  // 身份证:保留前6后4
  idCard: (original: string) => {
    return original.slice(0, 6) + '********' + original.slice(-4)
  },
  
  // 地址:只保留城市
  address: (original: string) => {
    const city = original.match(/.+?(市|区)/)?.[0] || ''
    return city + faker.location.streetAddress()
  },
  
  // 金额:保留数量级
  amount: (original: number) => {
    const magnitude = Math.floor(Math.log10(original))
    return faker.number.float({
      min: 10 ** magnitude,
      max: 10 ** (magnitude + 1),
      fractionDigits: 2
    })
  }
}

生产数据导出流程

bash
# 1. 导出生产数据
pg_dump -U postgres -d production > prod_backup.sql

# 2. 导入到临时库
psql -U postgres -d temp_sanitize < prod_backup.sql

# 3. 运行脱敏脚本
DATABASE_URL=postgresql://localhost/temp_sanitize npx tsx scripts/sanitize.ts

# 4. 导出脱敏后的数据
pg_dump -U postgres -d temp_sanitize > sanitized_backup.sql

# 5. 导入到测试环境
psql -U postgres -d test < sanitized_backup.sql

使用 Faker 生成真实感数据

typescript
import { faker } from '@faker-js/faker'

// 设置中文环境
faker.locale = 'zh_CN'

// 生成用户数据
function generateUser() {
  return {
    name: faker.person.fullName(),
    email: faker.internet.email(),
    phone: faker.phone.number(),
    avatar: faker.image.avatar(),
    bio: faker.lorem.paragraph(),
    createdAt: faker.date.past()
  }
}

// 生成订单数据
function generateOrder(userId: string) {
  return {
    userId,
    orderNo: faker.string.alphanumeric(16).toUpperCase(),
    amount: faker.number.float({ min: 10, max: 1000, fractionDigits: 2 }),
    status: faker.helpers.arrayElement(['PENDING', 'PAID', 'SHIPPED', 'COMPLETED']),
    createdAt: faker.date.recent()
  }
}

脱敏验证检查

typescript
async function verifySanitization() {
  // 检查是否还有真实邮箱
  const realEmails = await prisma.user.count({
    where: {
      email: { not: { contains: '@example.com' } }
    }
  })
  
  if (realEmails > 0) {
    throw new Error(`Found ${realEmails} unsanitized emails!`)
  }
  
  // 检查密码是否统一重置
  const users = await prisma.user.findMany({
    select: { password: true }
  })
  
  const uniquePasswords = new Set(users.map(u => u.password))
  if (uniquePasswords.size !== 1) {
    console.warn('Warning: Passwords are not uniformly reset')
  }
  
  console.log('Sanitization verification passed!')
}

自动化脱敏管道

yaml
# .github/workflows/sanitize.yml
name: Sanitize Production Data

on:
  schedule:
    - cron: '0 0 * * 0'  # 每周日执行

jobs:
  sanitize:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Export production data
        run: |
          pg_dump $PROD_DATABASE_URL > backup.sql
        env:
          PROD_DATABASE_URL: ${{ secrets.PROD_DATABASE_URL }}
          
      - name: Create temp database
        run: |
          createdb temp_sanitize
          psql -d temp_sanitize < backup.sql
          
      - name: Run sanitization
        run: npx tsx scripts/sanitize.ts
        env:
          DATABASE_URL: postgresql://localhost/temp_sanitize
          
      - name: Export sanitized data
        run: pg_dump temp_sanitize > sanitized.sql
        
      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: sanitized-data
          path: sanitized.sql

本节小结

  • 生产数据脱敏是合规和安全的必要步骤
  • 根据数据类型选择合适的脱敏策略
  • 使用 Faker 生成真实感的测试数据
  • 建立自动化脱敏管道保持数据新鲜