Revel框架防重复提交需前后端协同:前端禁用按钮并嵌入CSRF token,后端通过CSRF过滤器、Redis指纹校验及数据库唯一索引实现幂等性。

在Revel框架中实现表单防重复提交,必须同时解决前端按钮误点和后端请求重放两个层面的问题,否则用户刷新页面或重复点击仍会导致订单创建、支付扣款等关键操作被多次执行。
启用CSRF保护并注入Token
Revel默认不开启CSRF防护,需手动将csrf.CSRFFilter插入filter链顶部,否则所有POST/PUT/DELETE请求都会因token缺失直接返回403错误。
打开app/init.go,在revel.Filters切片中找到revel.ParamsFilter之后的位置,插入csrf.CSRFFilter:
revel.Filters = []revel.Filter{<br> revel.PanicFilter,<br> revel.RouterFilter,<br> revel.FilterConfiguringFilter,<br> revel.ParamsFilter,<br> csrf.CSRFFilter, // 必须放在ParamsFilter之后、SessionFilter之前<br> revel.SessionFilter,<br> revel.FlashFilter,<br> revel.ValidationFilter,<br> revel.I18nFilter,<br> revel.InterceptorFilter,<br> revel.ActionInvoker,<br>}
这一步漏掉或顺序错位,会导致{{.csrf_token}}在模板中始终为空——【CSRF filter必须紧接ParamsFilter之后】,否则参数解析未完成就尝试读取token,必然失败。
前端表单嵌入隐藏Token字段
在任意需要防重的POST表单中,必须显式添加隐藏域携带当前有效CSRF token。
<form action="/order/submit" method="POST"><br> <input type="hidden" name="csrf_token" value="{{.csrf_token}}" /><br> <input type="text" name="amount" /><br> <button type="submit">确认支付</button><br></form>
注意:Revel模板中{{.csrf_token}}仅在CSRF filter成功执行后才可用;若页面由GET直接渲染(如登录页跳转),该值一定存在;但若通过AJAX局部刷新加载表单,则需后端额外提供接口返回token,否则提交必失败。
后端拦截重复请求(基于请求指纹)
CSRF只防跨站伪造,不防用户本地重复提交。需在Controller中增加幂等性校验逻辑。
方法一:使用Redis记录请求指纹
在app/controllers/orders.go的Submit方法开头添加:
func (c Orders) Submit() revel.Result {<br> // 生成请求指纹:用户ID + 表单内容MD5<br> data := fmt.Sprintf("%s:%s", c.CurrentUser.Id, c.Params.Get("amount"))<br> fingerprint := fmt.Sprintf("req:%x", md5.Sum([]byte(data)))<br> <br> // 尝试SETNX,仅当key不存在时设置成功<br> exists, _ := c.Redis.SetNX(fingerprint, "1", 300).Result() // 5分钟有效期<br> if !exists {<br> return c.RenderError("请勿重复提交")<br> }<br> // 后续执行创建订单逻辑<br>}
方法二:数据库唯一约束兜底
在订单表中添加联合唯一索引:UNIQUE INDEX idx_user_amount_time (user_id, amount, DATE(created_at))。即使并发请求绕过Redis,数据库层也会拒绝第二条相同金额的当日订单插入。
前端按钮禁用与状态反馈
第一步:给提交按钮添加id并绑定点击事件
<button id="payBtn" type="submit">立即支付</button>
第二步:在页面底部添加内联脚本(确保DOM已加载)
<script><br>document.getElementById("payBtn").addEventListener("click", function(e) {<br> if (this.disabled) {<br> e.preventDefault();<br> return;<br> }<br> this.disabled = true;<br> this.textContent = "处理中...";<br>});<br></script>
这一步操作起来很简单,直接把脚本贴到form下方就行。但要注意:如果页面使用TurboLinks或SPA路由切换,该监听器可能失效,必须改用事件委托或在每次渲染后重新绑定。
第三步:监听表单submit事件,防止回车键绕过按钮禁用
document.querySelector("form").addEventListener("submit", function(e) {<br> const btn = document.getElementById("payBtn");<br> if (btn.disabled) e.preventDefault();<br>});


















