我们现阶段就是加了一个张表,订单号唯一主键。请求过来的时候会校验数据库有没有这条单号数据,这个方法是非事务的。还有没有什么其他好的方法了。
我在想要不要放在 redis 里面。。求大佬指教啊
![]() | 1 wysnylc 2020-06-11 12:13:59 +08:00 这就是幂等 |
![]() | 2 gz911122 2020-06-11 12:16:40 +08:00 订单号唯一主键不就天然防重复了... |
![]() | 3 shenlanAZ 2020-06-11 12:25:49 +08:00 前端: 锁住按钮 等请求返回之后再对按钮解锁。 后端: 差不多也是用锁的思路 无非就是锁的粒度,可以锁业务,也可以锁订单。看业务需求。 |
![]() | 4 beryl 2020-06-11 12:26:39 +08:00 关键词:防重入 |
![]() | 5 luckyrayyy 2020-06-11 12:27:32 +08:00 主键的话你插入不进去啊。 |
![]() | 6 tabris17 2020-06-11 12:27:50 +08:00 每次提交都包含一个业务 ID,重复 ID 的请求丢弃 |
![]() | 7 itechify PRO 摘抄网上的幂等解决办法 1.实现幂等性常见的方式有:悲观锁( for update )、乐观锁、唯一约束 2.几种方式,按照最优排序:乐观锁 > 唯一约束 > 悲观锁 |
8 hantsy 2020-06-11 13:20:52 +08:00 cxrf 。。。设置 Http Header,或者 Http Form 。 传统框架大部分自带 Form 重复提交了。 |
![]() | 9 lhx2008 2020-06-11 13:22:52 +08:00 via Android 最简单的方法可以依赖数据库的唯一键,复杂一点就是请求带 token+redis 黑名单 |
10 hantsy 2020-06-11 13:24:17 +08:00 ![]() 修正:cxrf --> csrf 或者叫 xsrf |
11 jinzhongyuan 2020-06-11 13:24:43 +08:00 @gz911122 楼主的意思应该是,指定时间内方法参数一样的请求无法进入方法或者 controller |
![]() | 12 luxinfl OP @jinzhongyuan 不是不是,我就是说两笔相同参数同时提交的问题。。有时候会碰到前台没做防重点按钮点了好多次。 |
15 doudouwu 2020-06-11 13:59:30 +08:00 #14 @ gz911122 唯一索引的是订单号,提交的是不包含的订单号,待生成订单号的数据呢 楼主说的场景很可能存在 |
16 kanepan19 2020-06-11 14:03:54 +08:00 csrf +1 |
18 dayformyjob 2020-06-11 14:08:07 +08:00 threadlocal + 信号量 重入锁,每个线程只能提交一次----单机版情况。 分布式 多机器,就引入中间件--redis 或者 jms 之类的 |
![]() | 19 royan 2020-06-11 14:12:15 +08:00 csrf+1 |
![]() | 20 kiracyan 2020-06-11 14:12:17 +08:00 预先生成 token 然后设置过期时间 提交成功后删除 不存在的不让提交 |
![]() | 21 DJQTDJ 2020-06-11 14:13:56 +08:00 订单号不是自增的吗 |
![]() | 22 wizzer 2020-06-11 14:17:24 +08:00 年月日时分秒+redis incr N 位自增长 |
![]() | 23 pinktu 2020-06-11 14:19:29 +08:00 让前端写,哪边简单哪边做 |
![]() | 24 wizzer 2020-06-11 14:23:52 +08:00 ``` public synchronized String getNewOrderId() { String date = DateUtil.format(new Date(), "yyMMddHHmm"); long ttl = redisService.ttl(RedisConstant.REDIS_KEY_ORDER_ID + date); long id = 0; if (ttl <= 0) { id = redisService.incr(RedisConstant.REDIS_KEY_ORDER_ID + date); redisService.expire(RedisConstant.REDIS_KEY_ORDER_ID + date, 80); } else { id = redisService.incr(RedisConstant.REDIS_KEY_ORDER_ID + date); } return date + Strings.alignRight(id, 6, '0'); } ``` |
25 jinzhongyuan 2020-06-11 14:34:35 +08:00 @luxinfl 额,不是我说的这回事吗? |
![]() | 26 zh841318441 2020-06-11 14:55:56 +08:00 后端做幂等性 一般是基于数据库方面建立唯一索引或者联合索引 基于 redis 也可以,生成订单唯一号存储在 redis 里面,保存的时候先从 reids 取,如果 reids 里面有,存储并删除。如果没有则不保存,但是这种并发情况下要考虑一下锁。 基于 token 也可以,实现情况和 redis 那种差不多. 前端:做放重复点击,点击过后没返回结果,不能重复点击. |
![]() | 27 luxinfl OP @jinzhongyuan 就是两条完全一模一样的请求,同时进到后台了。后台要怎么处理。就这么个意思 |
![]() | 28 luxinfl OP @zh841318441 这个 token 就是上面大佬说的那种 csrf 嘛? |
![]() | 30 Veneris 2020-06-11 15:07:18 +08:00 redis 的 setnx,key 是 业务+业务 id+用户 id,set 成功则执行业务并 delete key,否则直接 return |
32 youxiachai 2020-06-11 15:27:56 +08:00 lz 是听不懂幂等是干嘛的....前面那么多人都给出标准答案了... 为啥还要前端参与.... |
![]() | 33 no1xsyzy 2020-06-11 15:40:11 +08:00 ![]() 你试试看 v2 在一个主题里写点东西,然后切其他标签页访问个十几分钟在其他主题下做点回复,再回到这个主题的时候点回复,会失败一次,变成一个只有回复内容的网页,需要重新点一次回复。 这个就是 CSRF,每次请求主题页面就请求一个 CSRF token,包含在 input[type="hidden"][name="once"] 里,过了有效期再发送请求的话,这个请求就会失效。 |
![]() | 34 luxinfl OP @no1xsyzy 现在是不是都流行这样搞?反正我们前台没要求我们生成个 token 给他们。。而且小公司,以前写页面的时候都是我们自己写的,也没考虑过这个。用的最多的就是 disabled |
35 hantsy 2020-06-11 15:51:26 +08:00 ![]() @luxinfl CSRF 只对当前请求有效。重复提交后的第二次的同样的 CSRF Code 到后端是无法通过验证的。 一般传统 Web 框架都是隐藏字段,比如 JSF, Jakarta MVC 等,这些框架都是会自己有较验机制,不用手动代码检测。 https://github.com/hantsy/jakartaee-mvc-sample/blob/master/src/main/webapp/WEB-INF/views/add.xhtml#L13-L15 https://github.com/hantsy/jakartaee-mvc-sample/blob/master/src/main/java/com/example/web/TaskController.java#L89 Spring Security 也支持 Csrf,可以用于表单验证(使用 Themleaf 等),或者 Http Header (用于 API )。Spring 官方教程与 Angular 结合的 Microservice 例子,https://spring.io/guides/tutorials/spring-security-and-angular-js/ |
![]() | 36 luxinfl OP @no1xsyzy 刚才回复你的时候就碰到你说的这个情况了,跳到了一个回复页面。。是因为我改了标签属性么,加了 disabled 么。。。 |
38 hantsy 2020-06-11 16:11:30 +08:00 Spring 官方那个例子,以前用的 Angularjs,现在换成 Angular ( 2+)了。之前 Angularjs 中 CSRF 的名字是 XSRF,Spring Security 叫 csrf, 不一致还需要转换。 现在 Angular 不用了。 |
![]() | 40 wj5868386 PRO 两种处理方法,后端处理,前端处理完全能绕过去的,业务层 数据库层 业务层:建立提交规则,符合规则的放行,不符合的丢弃。 数据层:锁,唯一约束。 业务层其实也就是锁的思路。 |
![]() | 41 SashaMu 2020-06-11 18:25:23 +08:00 redis +1 |
42 DanielGuo 2020-06-11 18:38:28 +08:00 插入之前判重,这叫幂等你那个订单号可能不太够,最好是由前端传入一个值作 referenceId,前端连续的重复请求都是同一 referenceId 。插入时判断数据库中有没有此 referenceId,有的话就重复,不允许插入。 防止并发插入仅仅做了幂等也不够,若并发时也能重复插入成功。这时要做并发控制,可以在数据库层面将 referenceId 这个字段设置成唯一,由数据库阻止并发;也可以 单机的话直接 java 锁,分布式用 redis (不推荐,因为坑多)或 zookeeper 做分布式锁 |
![]() | 43 qloog 2020-06-11 18:41:34 +08:00 简单点用 redis 的 set nx px 作为分布式锁来做频率控制进行处理。 |
![]() | 44 luzhh 2020-06-11 18:42:27 +08:00 我之前写过一个通用的框架来保证接口的幂等性,你这个问题我讲一下大概的思路: 首先下单接口有若干个参数,n 次接口请求某几个参数值如果一样,那么认为是重复请求,那么把这几个参数拿出来组成一个在业务中可以是认为唯一的一个字符串,然后用 FutureTask 构造一个下单任务,将上面的唯一 key 和 task 保存在一个 map 中,每次请求接口时,先生成 ke 到 map 中看 value 存在不,如果存在那等待 task 的返回结果就行,如果不存在那就保存 task 到 map 中并调用 task.run 执行后续的下单业务逻辑。 这样若干个相同请求进来了只会处理一次。 上面是针对单个项目的情况,如果是集群部署的话 task 怎么保存和后面重复请求过来了怎么拿到请求结果,相应的用其他中间件改一下应该可以的。 |
![]() | 46 pinkrab 2020-06-11 19:52:32 +08:00 这是一个幂等性问题,一般的处理方式有: 数据库的唯一约束,这个最简单了,但是需要看业务场景。 使用 token:提交数据的时候验证 token,一旦提交成功清除,下次来提交必会失败 状态机幂等:一个数据在整个生命周期中所经历的状态,每次进行状态校验。 |
![]() | 47 mmdsun 2020-06-11 19:55:10 +08:00 via Android 网关加的过滤。同一个用户,同一个接口在 1 秒内点击了两次。就过滤掉。 |
![]() | 48 opengps 2020-06-11 19:59:00 +08:00 提交时候就加 requestid,同一个 requestid 都返回同一个结果 |
![]() | 49 luzhh 2020-06-11 20:08:35 +08:00 @luxinfl 就是线程相关的,通过这个可以等待并获取线程的执行结果。多个重复请求进来只有一个下单任务在执行,后续的请求直接等待结果即可。 |
50 spicecch 2020-06-11 20:28:40 +08:00 via iPhone 赞成 3 楼的,这个前端来判断比较方便,服务器没有返回重复提交就给他弹窗,这个前端可以判断出来的,可以去看下 xhr 的状态 |
52 yukiloh 2020-06-11 21:05:13 +08:00 不需要改代码,让用户学会"风怒了,编辑"... |
![]() | 53 gaius 2020-06-12 00:44:12 +08:00 via Android 参数做摘要然后分布式锁 |
54 freebird1994 2020-06-12 00:56:34 +08:00 via Android 分布式锁 |
![]() | 55 Vegetable 2020-06-12 01:41:43 +08:00 后端只要做一部分就行了. 我把所有非幂等操作 md5(body+token)用 redis 做了缓存,ttl 5 秒,重复值丢弃.建立在误点操作两次请求内容完全相同的前提下能正常工作. |
![]() | 56 xcstream 2020-06-12 01:54:11 +08:00 放在 redis 里 key 就是页面生成时随机一个 id |
![]() | 58 wupher 2020-06-12 09:17:19 +08:00 不清楚你的使用场景。 如果是业务类型,比如 A 用做 xx 操作时,禁止 A 用户同时做 xx 操作。比如不允许同时发两个密码不同的改密码请求。类似这种,比如商家转账,此时考虑用分布式锁,用数据库、Redis 、ZooKeeper 、ETCD 看你喜好和资源了。 如果是类似安全类型的,比如重放攻击。可以通过对 Nonce 串进行 Hash 校验,对于重复的请求进行识别与过滤,及至屏蔽转移攻击。 |
![]() | 60 guoyuchuan 2020-06-12 09:22:36 +08:00 百度一下不就知道了吗 |
61 Wuxj 2020-06-12 09:57:31 +08:00 跳转到下单页的时候,将一些唯一性参数先传到后端生成唯一性 requestId,通过后端跳转到下单页,将生成的 requestId 放隐藏字段。提交订单的时候,根据 requestId 生成分布式锁。刚毕业那会做的,好像也没啥问题~仅供参考 |
62 TomatoYuyuko 2020-06-12 10:52:41 +08:00 扔前端做啊,哪有前端不做设置的 |
![]() | 63 sola97 2020-06-12 11:23:19 +08:00 虽然我不懂,但前端处理防不了各种爬虫、脚本、秒杀软件吧 |
64 zhupeng163 2020-06-12 11:44:29 +08:00 定义一个环绕增强的切面,切需要防重提交的 controller 甚至所有 controller,类名+方法名+所有参数拼接起来作为 key,用 redis setNx,给一个失效时间,setNx 返回 false 则抛重复提交异常。 |
65 1107139144 2020-06-12 11:51:13 +08:00 请求加锁 |
![]() | 66 wanguorui123 2020-06-12 12:56:27 +08:00 via iPhone 用户锁+业务判断 |
![]() | 67 pythonee 2020-06-12 14:44:28 +08:00 csrf+1 不过数据库的设计也有必要 |
![]() | 68 pythonee 2020-06-12 14:44:57 +08:00 @guoyuchuan 尽量避免百度哦 |
![]() | 69 guoyuchuan 2020-06-12 14:53:25 +08:00 @pythonee #68 那就谷歌 |
![]() | 70 ningyu1 2020-06-12 14:56:48 +08:00 csrf+1 |
![]() | 71 ningyu1 2020-06-12 15:07:32 +08:00 csrf 能控制一部分重复提交(同一个页面多次点击 button 提交), 另外一部分是相同的业务参数重复提交(刷新页面获取新的 csrftoken 但是提交请求的其他参数不变,多用在录制回放),这个还要配合后端的幂等控制,做法有很多,可以排序后 md5 放到 redis 中去验证是否存在,也可以使用临时表控制幂等。 |
72 BlackBerry999 2020-06-12 16:37:16 +08:00 前端做防抖节流 |
73 wc951 2020-06-13 16:25:33 +08:00 via Android 前端是为了用户体验,后端是为了业务安全,都要做防重放的 |
![]() | 74 reeco 2020-06-14 21:43:42 +08:00 分布式悲观锁 |