- 新增 data/orders/masking.py (141 行): - mask_phone: 11 位 → 138****1234;支持 +86/86 国家码剥离、空格/横杠分隔符 - mask_id_card: 18 位 → 430102********1234(保留前 6 行政区划 + 末 4);15 位老版兼容;13-14 位非标准也走前 6 后 4 遮罩 - mask_name: 1 字原样 / 2 字 张* / 3 字 张*丰 / 4+ 字 张**;非中日韩字符全遮 - mask_sensitive_dict: 一键遮罩订单字典中所有已知 PII 字段,不动 _enc/_hash 索引/密文 - 默认安全:None/空串/非字符串均不抛错,统一返回 None/'' - 扩展 Order.to_dict(decrypt_sensitive) 三态策略: - True : 完整明文(权限内接口使用,如后台人工核对) - False : 完全移除明文(对外公开统计/审计日志) - 'mask' : 部分遮罩(默认,推荐;列表/详情直接可用) - 未知字符串策略值回退为 mask(防误传 plaintext 导致明文泄露) - 新增 data/orders/tests/test_masking.py (246 行,32 个 pytest 用例): - 覆盖各 mask 函数边界(标准/带 +86/短串/非法输入) - 覆盖 Order.to_dict 三态与默认 mask 的串联通路 - 验证: - data/orders/tests 全套 112 用例通过(原 80 + 新增 32) - 全仓 344/344 通过 - ruff check 0 warning / ruff format --check 已规范 - 与 T4.1 落盘加密的关系:crypto 负责'落盘形态'(密文+hash), masking 负责'展示形态'(部分遮罩),两者正交互补。
订单数据模块 (T4.1)
提供订单的 SQLite schema、AES-256 加密(Fernet)、6 态状态机、Order dataclass。
目录结构
data/orders/
├── __init__.py
├── README.md
├── crypto.py # Fernet 加密/解密/哈希派生
├── masking.py # 展示脱敏工具 (T11.2)
├── state_machine.py # 6 态转换验证
├── models.py # Order dataclass + 序列化(to_dict 支持 mask 策略)
├── schema.py # DDL + apply_schema()
└── tests/
├── __init__.py
├── test_crypto.py
├── test_masking.py
├── test_state_machine.py
└── test_schema.py
字段分类
| 类别 | 处理方式 | 字段 |
|---|---|---|
| 强敏感 PII | AES-256 加密 + 展示遮罩 | customer_phone, candidate_id_card |
| 弱敏感 PII | 脱敏存储 + 展示遮罩 | customer_name, candidate_name |
| 索引用哈希 | SHA-256 hex | customer_phone_hash |
| 业务字段 | 明文 | amount_cents, source, status |
| 元数据 | 明文 | created_at, tags |
T11.2 起,
Order.to_dict()默认走"展示遮罩"(如138****1234), API 响应 / 前端列表无需再做额外处理。
6 态状态机
pending → paid → serving → delivered → completed
│ │ │ │
└──┬─────┴────────┴──────────┘
▼
refunded (终态)
completed / refunded 为终态,不可再转换。
加密密钥
环境变量 GAOKAO_ORDERS_FERNET_KEY 必须配置;缺失时 get_fernet() 抛 MissingEncryptionKey,禁止降级为明文。
生产环境密钥生成示例:
python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
快速使用
import os
os.environ["GAOKAO_ORDERS_FERNET_KEY"] = "your-secret-here"
from data.orders.schema import apply_schema
from data.orders.crypto import encrypt, decrypt, hash_for_index
from data.orders.state_machine import OrderStatus, assert_valid_transition
from data.orders.models import Order, generate_order_id
# 1. 建表
conn = apply_schema("data/orders.db")
# 2. 创建订单
order = Order(
id=generate_order_id(),
source="xianyu",
service_version="standard",
amount_cents=9900,
status=OrderStatus.PENDING.value,
customer_name="张*",
customer_phone="13800001234",
candidate_name="张同学",
)
conn.execute(
"INSERT INTO orders (...) VALUES (...)",
order.to_db_row(),
)
# 3. 状态转换
assert_valid_transition(order.status, "paid")
验收(DoD)
- AES-256 加密(Fernet)对 customer_phone / candidate_id_card 落盘
- 6 态状态机 + 转换合法性校验
- SQLite schema 幂等可重建
- 外键约束启用
- SHA-256 hash 字段支持手机号去重
- CHECK 约束拒绝非法 status
- 展示脱敏(T11.2):Order.to_dict 默认走 mask 策略(手机/身份证/姓名)
展示脱敏 (T11.2)
为防止 API 响应 / 前端 / 日志 / 截图泄露完整 PII,订单数据模型支持"展示遮罩":
from data.orders.models import Order
order = Order(
id="GKO-20260612-AAAA",
source="web",
service_version="basic",
customer_name="张三",
customer_phone="13800001234",
candidate_id_card="430102200501011234",
)
# 默认:mask 模式 — 适合 API 响应 / 前端
order.to_dict()
# {
# "customer_phone": "138****1234",
# "candidate_id_card": "430102********1234",
# "customer_name": "张*",
# ...
# }
# 显式明文(后台人工核对场景)
order.to_dict(decrypt_sensitive=True)
# 完全移除(对外公开统计/审计日志)
order.to_dict(decrypt_sensitive=False)
masking 模块是纯字符串工具,与 crypto 正交,可在任意层(API 序列化、Jinja2
模板过滤器、日志格式化、CSV 导出器)独立复用。
下游衔接
- T4.2 DAO: 调用
apply_schema()建表,使用state_machine.assert_valid_transition()守护状态变更 - T4.3 CLI: 直接 import
Orderdataclass + crypto - T6.1 FastAPI: schema 不变,DAO 复用
版本
v1.1 — 2026-06-12 — T11.2 展示脱敏(mask 策略 + masking.py) v1.0 — 2026-06-12 — T4.1 实施