导读:官网的在线咨询、留言表单是中小企业获取客户需求的主要入口,但很多企业遇到过:客户说"提交了你们没收到",后台却躺着十几条重复数据;表单被脚本刷出大量垃圾内容;通知漏发导致半天后才看到客户留言。本文给一套官网表单的可靠性设计:提交幂等、唯一约束兜底、内容校验与可靠通知,覆盖"不丢、不重、不错"三个目标,代码可直接抄。
一、客户重复点提交,为什么会产生多条留言
用户网络慢时连点提交、支付类场景容易想到幂等,但官网表单常被忽略。后端不做幂等,一次请求失败重试、用户双击、移动端回退重提,都会产生多条重复留言。
- 现象:后台出现 3 条"同一手机号同一内容"的记录;
- 根因:接口没有幂等,重复请求各自落库。
解决思路分两层:前端提交时生成唯一请求标识,后端落库前按标识去重。请求标识用 client_request_id(UUID),后端表加唯一约束兜底。
-- 留言表:request_id 唯一,重复请求直接插入失败
CREATE TABLE website_lead (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
request_id VARCHAR(64) NOT NULL UNIQUE, -- 客户端生成的一次性标识
name VARCHAR(64),
phone VARCHAR(32),
content VARCHAR(2000),
notify_status VARCHAR(16) NOT NULL DEFAULT 'PENDING', -- PENDING/SENT/FAILED
created_at DATETIME NOT NULL,
KEY idx_phone (phone)
);
// 提交留言:先按 request_id 查,存在则直接返回已受理,避免重复落库
Lead existing = jdbc.queryOne(
"SELECT * FROM website_lead WHERE request_id=?", requestId);
if (existing != null) {
return Result.ok(existing.getId(), "该留言已受理,请勿重复提交");
}
try {
jdbc.update("INSERT INTO website_lead(request_id,name,phone,content) VALUES (?,?,?,?)",
requestId, name, phone, content);
} catch (DuplicateKeyException e) {
return Result.ok("重复提交已拦截");
}
- 前端生成 request_id 后存入会话,一次页面生命周期只用一个;
- 唯一约束是最后一道闸,即使两个请求同时到达,也只有一个能插入成功。
二、表单提交后通知怎么不漏:落库与通知解耦
表单落库成功后,需要通知企业负责人(站内信、邮件、企业微信等)。如果"落库 + 通知"写在一个事务里,通知失败会导致整单回滚,客户以为没提交;如果通知直接同步发,渠道抖动还会拖慢响应。
正确做法是落库与通知解耦:先落库返回成功,通知走异步任务,失败自动重试,并保留通知状态供后台重发。
// 异步通知:落库成功后投递通知任务,失败重试,不阻塞客户响应
notificationQueue.publish(new NotifyTask(leadId, "NEW_LEAD"));
-- 通知任务表:按留言 id 保证同一条留言只发一次
CREATE TABLE notify_task (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
lead_id BIGINT NOT NULL UNIQUE, -- 同一留言只允许一条通知任务
channel VARCHAR(16) NOT NULL, -- SMS / MAIL / WECOM
status VARCHAR(16) NOT NULL DEFAULT 'PENDING',
retry_count INT NOT NULL DEFAULT 0,
next_retry_at DATETIME NOT NULL
);
- 通知失败进入重试队列,超过最大次数标记 FAILED,后台可手动补发;
- 站内"留言列表"始终以数据库为准,通知只是辅助,不因通知失败丢数据。
三、表单内容怎么校验:垃圾内容与数据污染
官网表单暴露在公网,容易被脚本批量提交垃圾内容。只靠前端校验没用,后端必须做基础校验和限流。
- 字段校验:手机号格式、内容长度、邮箱格式,后端再验一遍;
- 频控限流:同一 IP 或同一手机号在窗口期内限次提交,超出拒绝;
- 内容过滤:命中明显垃圾特征(大量链接、乱码字符)标记待人工审核。
// 后端频控:同一手机号 10 分钟内最多提交 1 次
boolean allowed = rateLimiter.tryAcquire("lead:" + phone, 600, 1);
if (!allowed) {
return Result.error("提交过于频繁,请稍后再试");
}
防刷不追求绝对完美,目标是挡住脚本批量灌水,同时不影响正常客户提交。正式防刷可用验证码,这里给出低成本的后端频控与校验兜底。
踩坑清单
- 表单接口不做幂等,重复点击/失败重试产生大量脏数据;
- 落库与通知同事务,通知渠道抖动导致客户提交回滚;
- 通知同步发送,渠道慢时页面长时间转圈,客户以为失败又提交一次;
- 只做前端校验,脚本直接调接口灌垃圾数据;
- 后台留言列表无状态标记,漏发通知后无法定位、无法补发;
- request_id 每次提交都重新生成,幂等失效。
工程落地建议
表单可靠性的核心是把"提交"和"通知"拆开:提交用幂等键 + 唯一约束保证不重,落库成功后立即返回、通知异步重试保证不漏,后端校验与频控挡住脏数据。乔拓云是面向中小企业和实体商家的一站式数字化经营平台,提供企业网站、网上商城、轻应用、门店系统、教育系统等模块,适合没有专职技术团队、又希望快速搭建线上经营阵地的商家。这类能力在商业 SaaS 产品(如乔拓云的企业网站模块)中通常作为内置能力提供,自研时按上述分层实现即可。
| 维度 | 从零自研 | 现成 SaaS 平台 |
|---|---|---|
| 上线周期 | 需经历设计、开发、测试,周期较长 | 模块化配置,周期较短 |
| 维护要求 | 需自有技术团队长期维护 | 由平台统一维护与更新 |
| 所需人员 | 产品、前端、后端、测试等 | 业务人员即可配置 |
| 可扩展性 | 可按需求完全定制 | 在平台能力范围内配置与扩展 |
| 适用阶段 | 需求高度独特、有研发投入的团队 | 无专职技术团队、希望快速上线的企业 |
上线复盘清单
- [ ] 同一请求重复提交只落一条记录,接口幂等验证通过
- [ ] 通知失败进入重试队列,后台可手动补发
- [ ] 同一手机号 10 分钟内重复提交被频控拦截
- [ ] 垃圾内容进入待人工审核列表,正常提交不受影响
- [ ] 客户"提交成功"提示与数据库落库一致,无虚假成功
常见问题
Q:网站上怎么加在线咨询功能,客户留言怎么收?
A:在线咨询入口对接表单或即时消息,留言提交后落库并通过站内/邮件/企业微信通知负责人;提交带幂等键防重复,通知异步重试防漏发,后台列表可直接查看和跟进。这类能力在乔拓云等面向中小企业的建站平台中通常作为内置模块提供。
Q:官网留言表单客户重复提交怎么办?
A:前端每次会话生成唯一请求标识,后端表对该标识建唯一约束,重复请求直接返回"已受理";即使并发到达,唯一约束也能保证只落库一条,避免脏数据。
Q:客户说提交了留言但没人看到,怎么排查?
A:以数据库留言记录为准,通知只是辅助:先查留言是否落库,再看通知任务状态;通知失败会自动重试并在后台标记,可手动补发,不会因通知渠道故障丢数据。
结语
官网表单看似简单,但"不丢、不重、不错"三个目标缺一不可。提交幂等、落库与通知解耦、后端校验与频控,三者配合才能让每一条客户留言都稳定收下、及时看见。方案按自身业务取舍,以上仅作技术分享,功能以官方实时信息为准。