国内可访问服务替换排查:新增蜜罐反垃圾字段 + CDN 加速域名配置

This commit is contained in:
2026-08-07 14:49:00 +08:00
parent d6503bb46f
commit 16e4f97e74
6 changed files with 38 additions and 3 deletions
+2
View File
@@ -20,6 +20,8 @@ OSS_ACCESS_KEY_ID=
OSS_SECRET_ACCESS_KEY=
OSS_BUCKET_NAME=
OSS_ENDPOINT_URL=
# 绑定到 OSS/COS 源站的 CDN 加速域名(阿里云 CDN / 腾讯云 CDN / 又拍云),留空则直接使用 OSS 源站地址
OSS_CDN_DOMAIN=
# 发布后前端 ISR 缓存失效 Webhook
FRONTEND_REVALIDATE_URL=https://www.example.com/api/revalidate
+3
View File
@@ -5,6 +5,9 @@ class LeadSubmitSerializer(serializers.Serializer):
form_id = serializers.IntegerField()
data = serializers.DictField()
source_url = serializers.URLField(required=False, allow_blank=True, default="")
# 蜂蜜陷阱反垃圾注册字段:正常用户不会看到/填写该字段,机器人填写则视为垃圾提交。
# 无需接入极验/腾讯云等第三方验证码服务即可降低自动化垃圾提交,详见设计文档 §2.15。
website = serializers.CharField(required=False, allow_blank=True, default="")
class LeadDeletionRequestSerializer(serializers.Serializer):
+20
View File
@@ -80,6 +80,26 @@ def test_lead_submit_requires_form_id():
assert response.status_code == 400
def test_lead_submit_honeypot_filled_silently_rejects_without_creating_lead(
contact_form,
):
"""蜜罐字段非空视为机器人:返回“成功”假象但不落库、不发通知邮件。"""
client = APIClient()
response = client.post(
"/api/v1/custom/leads/",
{
"form_id": contact_form.pk,
"data": {"name": "张三"},
"website": "http://spam.example.com",
},
format="json",
)
assert response.status_code == 201
assert response.data["success"] is True
assert not Lead.objects.exists()
# --- PIPL 数据删除权(详见设计文档 §2.15) ---
+7
View File
@@ -41,6 +41,13 @@ class LeadSubmitView(APIView):
{"error": {"code": "NOT_FOUND", "message": "表单不存在"}}, status=404
)
# 蜜罐反垃圾:honeypot 字段非空说明是自动化脚本提交,直接返回“成功”假象,
# 不落库、不发通知邮件,避免打草惊蛇(详见设计文档 §2.15 验证码/反垃圾方案)。
if serializer.validated_data.get("website"):
return Response(
{"success": True, "message": form_def.success_message}, status=201
)
lead = Lead.objects.create(
form=form_def,
data=serializer.validated_data["data"],
+3 -3
View File
@@ -348,7 +348,7 @@ CI 中要求单元测试覆盖率不低于 70%,核心 `apps/forms`(涉及线
- [x] 页脚展示备案号并链接工信部备案查询(`frontend/components/layout/Footer.tsx`,实际备案号 冀ICP备2025130506号-1
- [x] 隐私政策内容已上线(`apps.core.SimpleContentPage` 通用富文本内容页模型 + `seed_privacy_policy` 管理命令 + 前端 `/privacy-policy` 页面 + 页脚链接,内容覆盖 PIPL 要求的收集目的/用途/用户权利/Cookie 说明等)
- [x] Cookie 同意条控件(`frontend/components/layout/CookieConsent.tsx``"use client"` 组件,首次访问展示"接受/拒绝"横幅,选择结果存入 `localStorage`,已挂载到根 layout
- [ ] 字体/地图/验证码/CDN 均替换为可在国内正常访问的服务
- [x] 字体/地图/验证码/CDN 排查:字体已用系统字体栈(`frontend/app/layout.tsx`,未引入 `next/font/google` 等国外字体服务);地图/视频等嵌入类 Block 尚未开发,落地时需选用腾讯地图/高德地图、腾讯视频/B站等国内可访问服务替代 Google Maps/YouTube(见 §2.6 待办);验证码此前仅靠限流(`ScopedRateThrottle` forms scope 5/min)防护,未接入任何第三方验证码,现补充蜜罐反垃圾字段 `website``apps/forms/serializers.py`/`views.py` + `frontend/blocks/form/LeadForm.tsx`,正常用户不可见,机器人误填后静默返回"成功"但不落库不发邮件),无需接入极验/腾讯云验证码 API 即可降低自动化垃圾提交,未来如需更强防护可再接入国内验证码厂商;CDN 方面为 `production.py` 新增 `AWS_S3_CUSTOM_DOMAIN``OSS_CDN_DOMAIN` 环境变量)用于绑定阿里云/腾讯云 CDN 加速域名到 OSS/COS 源站,避免直接暴露源站地址;另确认 `sentry-sdk` 虽在 `requirements/production.txt` 中但代码里从未调用 `sentry_sdk.init()`,为休眠依赖,未来若启用需评估国内可访问性(自建 GlitchTip 或国内 APM 替代 sentry.io
- [x] 表单收集的个人信息支持删除请求(PIPL 数据主体权利):新增 `LeadDeletionRequest` 模型(`apps/forms/models.py`),匿名用户凭提交表单时留下的邮箱在 `/privacy-policy/delete-request` 页面发起申请(`POST /api/v1/custom/leads/deletion-requests/`,限流 forms scope 5/min,无论邮箱是否存在关联数据均返回相同提示以避免探测),系统发送含 24 小时有效 token 的确认邮件,用户在 `/privacy-policy/delete-confirm?token=` 页面点击确认后(`POST /api/v1/custom/leads/deletion-requests/confirm/`)按邮箱在 `Lead.data` 中做匹配并删除对应记录;`LeadDeletionRequest` 处理记录通过 Django Admin(只读)供合规审计;隐私政策页面已加入申请入口链接
### 2.16 开发路线图(对齐当前仓库状态)
@@ -370,7 +370,7 @@ CI 中要求单元测试覆盖率不低于 70%,核心 `apps/forms`(涉及线
- [x] `apps/forms``FormDefinition`/`FormDefinitionField`/`Lead`Snippet 方式)+ 提交 API`/api/v1/custom/leads/`,限流 + 邮件通知)
- [x] Wagtail API v2 挂载、分页/过滤/排序(Wagtail 内置)
- [x] Next.js 前端脚手架(独立仓库):Header/Footer/layout、首页、博客/产品/案例/解决方案列表与详情页、`BlockRenderer`、ISR + revalidate route`lint`/`build` 已验证通过
- [x] pytest 基础测试框架(pytest-django + factory_boy):`pytest.ini` + `conftest.py`root_page/home_page fixtures),为 `apps/core`SEOablePage 字段、FormBlock API 表示、SimpleContentPage 页面树、Snippet 模型与只读 API)、`apps/products`、`apps/cases`、`apps/forms`(线索提交 API、PIPL 数据删除权申请/确认流程)编写了基础单元测试,共 30 个用例均通过
- [x] pytest 基础测试框架(pytest-django + factory_boy):`pytest.ini` + `conftest.py`root_page/home_page fixtures),为 `apps/core`SEOablePage 字段、FormBlock API 表示、SimpleContentPage 页面树、Snippet 模型与只读 API)、`apps/products`、`apps/cases`、`apps/forms`(线索提交 API、蜜罐反垃圾字段、PIPL 数据删除权申请/确认流程)编写了基础单元测试,共 31 个用例均通过
- [x] `apps/core.SimpleContentPage`:通用富文本法务/说明类页面模型,用于隐私政策等内容,配套 `seed_privacy_policy` management command 用于幂等创建/更新隐私政策页面(需在 HomePage 实例存在后手动运行)
- [x] Snippet`TeamMember`/`Testimonial`/`Partner`(简单 `@register_snippet`,均含 `order` 排序字段)、`NavigationMenu`+`NavigationMenuItem``ClusterableModel`+`Orderable`+`InlinePanel`,内部页面/外部链接二选一)、`SiteSettings``wagtail.contrib.settings` + `BaseSiteSetting`,公司信息/ICP备案/社交账号全局配置)均已创建;只读 API 挂载于 `/api/v1/custom/core/``team/`、`testimonials/`、`partners/`、`navigation/?name=`、`site-settings/`,限流 public 100/min);前端 `Header`/`Footer` 已接入 `NavigationMenu`/`SiteSettings`(接口不可用时回退静态内容),`TeamMember`/`Testimonial`/`Partner` 已提供 service 层,页面级展示留待对应 StreamField Block(见下)落地时接入
@@ -380,7 +380,7 @@ CI 中要求单元测试覆盖率不低于 70%,核心 `apps/forms`(涉及线
- [ ] RBAC 落地(Wagtail `Group` + `GroupPagePermission` 实际配置)未开始
- [ ] 测试覆盖率仍不完整(已有基础单元测试,但集成测试、前端组件测试、E2E 均未编写,当前无 CI 流水线)
- [x] 生产安全 settings(§2.11 中 `SECURE_*`/CORS/CSRF 白名单等)已在 `production.py` 中逐项落实,并补充 `SECURE_PROXY_SSL_HEADER`(适配国内云厂商 SLB/CLB 边缘终止 TLS 场景);实际域名需在部署时通过 `.env` 填入
- [x] ICP 备案、页脚备案号展示、隐私政策内容、Cookie 同意条控件、表单 PIPL 数据删除权支持均已完成(详见 §2.15),合规清单剩余“字体/地图/验证码/CDN 国内可访问”一项
- [x] ICP 备案、页脚备案号展示、隐私政策内容、Cookie 同意条控件、表单 PIPL 数据删除权支持、字体/地图/验证码/CDN 国内可访问排查均已完成,§2.15 合规与本地化清单已全部完成
- [ ] 中文分词、OpenSearch 集成(Phase 2 提前项)未开始
### 2.17 风险与备选方案
+3
View File
@@ -40,6 +40,9 @@ AWS_ACCESS_KEY_ID = env("OSS_ACCESS_KEY_ID", default="")
AWS_SECRET_ACCESS_KEY = env("OSS_SECRET_ACCESS_KEY", default="")
AWS_STORAGE_BUCKET_NAME = env("OSS_BUCKET_NAME", default="")
AWS_S3_ENDPOINT_URL = env("OSS_ENDPOINT_URL", default="")
# 国内 CDN 加速域名(阿里云 CDN / 腾讯云 CDN / 又拍云,绑定到 OSS/COS 源站后填入),
# 配置后媒体文件 URL 会替换成该加速域名,避免直接暴露 OSS 源站地址(详见 §2.15)。
AWS_S3_CUSTOM_DOMAIN = env("OSS_CDN_DOMAIN", default="")
LOGGING = {
"version": 1,