springboot整合redis解决订单重复请求的问题

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: springboot整合redis解决订单重复请求的问题

实现思路:将请求的json数据,去除一些可变字段,将key升序排序,拼接成字符串并进行md5加密,再拼接一些用户信息,这样相同的请求参数得到的加密串必然一致,将此字符串作为key,存入redis,设置过期时间为1秒,一般重复提交都是在1000ms以内;

代码部分

import com.alibaba.fastjson.JSONObject;
import com.wang.learn.cloudredis.entity.Book;
import com.wang.learn.cloudredis.utils.ReqDedupHelper;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
/**
 * ClassName RedisController
 * Description
 *
 */
@RestController
@RequestMapping("/redis")
public class RedisController {
    @Resource
    private RedisTemplate<String, Object> stringRedisTemplate;
    @RequestMapping("/repeat")
    public Object repeat(@RequestBody @Valid Book book){
        //用户
        String userId= "12345678";
        //接口名
        String method = "pay";
        //计算请求参数摘要,其中剔除里面请求时间的干扰
        String dedupMD5 = ReqDedupHelper.dedupParamMD5(JSONObject.toJSONString(book),"time");
        String KEY = "dedup:U=" + userId + "M=" + method + "P=" + dedupMD5;
        // 1000毫秒过期,1000ms内的重复请求会认为重复
        long expireTime =  1000;
        long expireAt = System.currentTimeMillis() + expireTime;
        String val = "expireAt@" + expireAt;
        // NOTE:直接SETNX不支持带过期时间,所以设置+过期不是原子操作,极端情况下可能设置了就不过期了,后面相同请求可能会误以为需要去重,所以这里使用底层API,保证SETNX+过期时间是原子操作
        Boolean firstSet = stringRedisTemplate.execute(
                (RedisCallback<Boolean>) connection -> connection.set(KEY.getBytes(), val.getBytes(), Expiration.milliseconds(expireTime),
                RedisStringCommands.SetOption.SET_IF_ABSENT));
        final boolean isConsiderDup;
        if (firstSet != null && firstSet) {
            return book;
        } else {
            return "订单重复!";
        }
    }
}


import com.alibaba.fastjson.JSON;
import com.wang.learn.cloudredis.entity.Book;
import lombok.extern.slf4j.Slf4j;
import javax.xml.bind.DatatypeConverter;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.List;
import java.util.TreeMap;
@Slf4j
public class ReqDedupHelper {
    /**
     *
     * @param reqJSON 请求的参数,这里通常是JSON
     * @param excludeKeys 请求参数里面要去除哪些字段再求摘要(如时间戳字段)
     * @return 去除参数的MD5摘要
     */
    public static String dedupParamMD5(final String reqJSON, String... excludeKeys) {
        String decreptParam = reqJSON;
        TreeMap paramTreeMap = JSON.parseObject(decreptParam, TreeMap.class);
        if (excludeKeys!=null) {
            List<String> dedupExcludeKeys = Arrays.asList(excludeKeys);
            if (!dedupExcludeKeys.isEmpty()) {
                for (String dedupExcludeKey : dedupExcludeKeys) {
                    if(paramTreeMap.containsKey(dedupExcludeKey)){
                        paramTreeMap.remove(dedupExcludeKey);
                    }
                }
            }
        }
        String paramTreeMapJSON = JSON.toJSONString(paramTreeMap);
        String md5deDupParam = jdkMD5(paramTreeMapJSON);
        log.debug("md5deDupParam = {}, excludeKeys = {} {}", md5deDupParam, Arrays.deepToString(excludeKeys), paramTreeMapJSON);
        return md5deDupParam;
    }
    private static String jdkMD5(String src) {
        String res = null;
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            byte[] mdBytes = messageDigest.digest(src.getBytes());
            res = DatatypeConverter.printHexBinary(mdBytes);
        } catch (Exception e) {
            log.error("",e);
        }
        return res;
    }
    public static void main(String[] args) {
        Book book = new Book();
        book.setId(1);
        book.setCount(2);
        book.setName("java编程思想");
        book.setTime(System.currentTimeMillis());
        String key = dedupParamMD5(JSON.toJSONString(book), "time", "456");
        System.out.println(key);
    }
}
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
24天前
|
NoSQL Java Redis
SpringBoot集成Redis解决表单重复提交接口幂等(亲测可用)
SpringBoot集成Redis解决表单重复提交接口幂等(亲测可用)
264 0
|
1月前
|
JavaScript 前端开发 Java
springboot从控制器请求至页面时js失效的解决方法
springboot从控制器请求至页面时js失效的解决方法
15 0
springboot从控制器请求至页面时js失效的解决方法
|
1月前
|
消息中间件 NoSQL Java
springboot redis 实现消息队列
springboot redis 实现消息队列
39 1
|
29天前
|
NoSQL Java Redis
SpringBoot集成Redis
SpringBoot集成Redis
407 0
|
1月前
|
JavaScript 前端开发
springboot+layui从控制器请求至页面时js失效的解决方法
springboot+layui从控制器请求至页面时js失效的解决方法
16 0
|
4天前
|
Web App开发 前端开发 Java
SpringBoot之请求的详细解析
SpringBoot之请求的详细解析
21 0
|
8天前
|
NoSQL 数据可视化 Java
Springboot整合redis
Springboot整合redis
|
8天前
|
人工智能 前端开发 Java
Java语言开发的AI智慧导诊系统源码springboot+redis 3D互联网智导诊系统源码
智慧导诊解决盲目就诊问题,减轻分诊工作压力。降低挂错号比例,优化就诊流程,有效提高线上线下医疗机构接诊效率。可通过人体画像选择症状部位,了解对应病症信息和推荐就医科室。
147 10
|
18天前
|
NoSQL Java Redis
Springboot整合redis
Springboot整合redis
|
25天前
|
NoSQL Java Redis
SpringBoot集成Redis
SpringBoot集成Redis
53 1