Skip to content
bitzorcas
中EN

Guide

Website 联系表单、线索与敏感数据

解释公开 CAPTCHA、Visitor 绑定、字段边界、Data Protection、通知失败、归因、Lead 状态机与未交付管理面。

Last updated

联系表单是公开 Endpoint 到 Lead 聚合的真实写路径。它有专用限流、32 KiB 请求上限、一次性 CAPTCHA 和字段保护,但没有幂等键、事务性通知或 Lead 管理 API。

1. 调用链

"Notification publisher""Lead store""CAPTCHA provider""Public endpoint""Browser""Notification publisher""Lead store""CAPTCHA provider""Public endpoint""Browser"GET captchaGenerate(visitor-bound)POST contactVerify onceSave protected LeadPublish lead ID + source

2. Visitor 与 CAPTCHA

生成时 CaptchaContext 使用 purpose public-website、服务端 VisitorId 和远端 IP。提交时 Handler 要求 ChallengeId 前缀为 captcha:public-website:{VisitorId}:,随后调用默认 Provider 验证。

前缀检查只是防止跨 purpose/Visitor 混用;真正的一次性、过期、答案比较和重放保护由 RiskControl Provider 合同决定。Visitor Cookie 本身可替换,不是用户身份。

3. 字段验证

Name、Contact、InquiryContent、Source 必填。Name 120、Contact 200、Inquiry 4,000;UTM Source/Medium 120、Campaign 160;ChallengeId 512、Answer 128。

Source 在聚合层只允许 website、mini-program、referral、ad。Endpoint 直接接受字符串,Handler trim/lowercase 后由 Lead.Create 拒绝未知值。

4. 敏感数据保护

Name、Contact、InquiryContent 在保存前用 ASP.NET Core Data Protection purpose BitzOrcas.Website.Lead.Pii.v1 加密。通知只携带 leadId 与 source,不携带明文。

Data Protection 的强度取决于生产 Key Ring:持久位置、静态保护、实例共享、轮换、备份与灾备恢复都必须验证。丢失 Key Ring 会让历史 Lead 不可解密。

当前 ILeadSensitiveDataProtector 只公开 Protect,没有解密读取 Port,这也印证了管理读取面尚未交付。

5. 归因

Visitor Journey 来自 Redis,保存 FirstTouch/CurrentTouch 与 30 分钟 Session;Lead 优先使用 journey 首次来源,并把 term/content 和双触点 JSON 写入聚合。

Journey 失败会在中间件记 warning 并继续,请求仍可能创建没有归因的 Lead。UTM 使用启发式 PII 过滤,但不是完整 DLP;归因值仍属于受治理数据。

6. 评分不是机器学习

当前评分从 30 开始:有 Campaign 加 15;咨询正文 ≥100 字加 25,否则加 10;Referral 加 20;最后 clamp 到 0..100。

它是确定性启发式,不应表述为智能评分。版本、解释、偏差审查和业务效果都没有持久化,规则变化会导致新旧 Lead 不可比较。

7. 保存与通知的裂缝

Handler 先 repository.SaveAsync(lead),随后 notifications.PublishAsync。若保存成功而通知抛错,请求会失败;客户端重试会创建新 Lead,因为命令没有 idempotency key 或唯一业务键。

当前保存后通知顺序
// ① Lead 已分配并保存;这里之后的失败不会自动回滚该事实。
Result saved = await repository.SaveAsync(lead, cancellationToken);
if (saved.IsFailure) return Result.Failure<SubmitContactResult>(saved.Error);
// ② 通知仅含 ID/来源,但异常可能让 HTTP 表现为失败。
await notifications.PublishAsync(new NotificationMessage(
"website.lead.created",
new Dictionary<string, string> { ["leadId"] = lead.Id, ["source"] = lead.Source }),
cancellationToken);
// ③ 当前没有幂等记录,失败重试可能重复创建 Lead。
return Result.Success(new SubmitContactResult(lead.Id));

GA 应把 LeadCreated 作为与保存同事务的 outbox 事实,或保存可重试通知状态;表单接受 idempotency token,并定义响应重放窗口。

8. Lead 状态机

领域已有 New→Contacted→Qualified→Converted;非终态可 Disqualify。还支持 AssignedOwnerId、AddFollowUp 和归因快照。

但源码没有 Lead list/detail/decrypt/assign/follow-up/transition Handler 或 Endpoint。website.lead.read/manage 只是目录声明;不能在产品说明中宣称已提供销售/咨询 CRM。

9. 租户与全局性

Lead 没有 TenantId。公开表单写入平台全局 Lead 表,权限目录也没有表达租户分区。如果商业部署要求每租户独立站点/律所线索,模型和查询必须增加可信 tenant/owner 边界,不能只在 Host 配置 PublicTenantId。

全局模式也要明确哪个运营角色可读取所有 Lead,以及 Data Protection purpose/key ring 是否跨产品实例共享。

10. 滥用与内容安全

CAPTCHA 与限流降低自动滥用,但 InquiryContent 是外部不可信文本。管理 UI 展示时必须做上下文输出编码;不要把正文拼进邮件 HTML、日志、搜索查询或提示词。

还需垃圾内容/恶意链接策略、病毒附件策略(当前表单无附件)、投诉/删除、IP 风险信号最小化和人工处置 Runbook。

11. 测试矩阵

覆盖 Visitor 前缀、错误/过期/重放 CAPTCHA、每个长度边界、未知 Source、Data Protection 非明文/错误 Key Ring、Journey 不可用、UTM PII、评分边界、保存失败、通知失败后重复提交、敏感数据不入通知/日志、限流多实例、请求体上限和全局/租户设计决策。

通知只允许最小参数
// ① 捕获发布的消息,不读取加密字段。
await handler.Handle(validCommand, cancellationToken);
await notifications.Received(1).PublishAsync(
Arg.Is<NotificationMessage>(message =>
message.Parameters.Keys.Order().SequenceEqual(new[] { "leadId", "source" })),
cancellationToken);
// ② 测试同时断言参数值不含 Name、Contact 或 InquiryContent。

12. 运维与核查

指标包含 CAPTCHA 拒绝、字段拒绝、Lead 保存、通知失败/积压、重复 token、Journey 缺失和 Data Protection 失败;标签禁止姓名、联系方式或正文。

Terminal window
# ① 当前 Lead 业务面应只命中聚合与 SubmitContact。
rg -n "Lead(Read|Manage)|MarkContacted|Qualify|AddFollowUp|AssignedOwner" \
src/Platform/Website src/Hosts -g '*.cs'

13. Lead 管理落地前的决策

  • Lead 是平台全局还是租户拥有?
  • 幂等 token 由浏览器生成还是 CAPTCHA challenge 派生?
  • 重放窗口和成功响应保留多久?
  • 通知使用 outbox 事件还是独立投递表?
  • 通知失败是否仍向访客返回成功?
  • 哪些角色可解密哪些字段?
  • 解密是否需要目的、工单和二次审批?
  • 数据访问是否写不可抵赖审计?
  • Key Ring 轮换/恢复演练多久一次?
  • Lead 与 Visitor Journey 的保留是否一致?
  • 删除请求是否清理归因、跟进和通知副本?
  • Score 规则版本如何持久化并解释?
  • Assign/FollowUp 是否需要乐观并发?
  • 导出和搜索如何避免明文索引泄漏?
  • 垃圾线索与误报如何申诉和恢复?

返回 Website 总览

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%