springboot开发银行秒杀后端系统——订单篇(下篇)

800 阅读4分钟

「这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战」。

在上一篇中我简单处理一下超卖问题和限流处理:

# springboot开发银行秒杀后端系统——订单篇(上篇)

完整的项目代码同步上传到GitHub->github.com/yt-King/sec…

接下来实现以下接口的隐藏,不然直接把抢购接口暴露出来的话被一些弔人按个F12直接拿到就玩完了,所以抢购的接口是要做好保护措施的。

抢购接口隐藏(接口加盐)的具体做法:

  • 每次点击秒杀按钮,先从服务器获取一个秒杀验证值(接口内判断是否到秒杀时间等操作)。
  • Redis以缓存用户ID和商品ID为Key,验证值为Value缓存验证值
  • 用户请求秒杀商品的时候,带上秒杀验证值,得到一个临时订单数据
  • 在临时订单支付时进行校验,完成下单写入数据库

根据比赛题目的要求用户在完成支付后才算抢购成功,所以我把原来的下单接口拆分成了两个,变为createTempOrder和createRealOrder两个方法,第一个方法用于在用户点击下单时生成临时订单返回给用户(不写入数据库),在用户成功付款后(点击确认支付按钮时)调用第二个方法将订单写入数据库表示抢购成功,将下单接口拆分后需要确保用户抢购时要按流程走过所有接口,从获取验证时-->获取临时订单-->真正下单,所以在获取验证值时程序会将用户id和商品id配合上盐(防止加密太简单被直接猜到)进行两次MD5加密后存入redis,并返回第一次加密后的值。 image.png

上图是我写的获取验证值的接口,验证值获取后开始进行第二步获取临时订单,在这一步的时候会将传入验证值(第一次加密后的值)进行与第一步操作(获取验证值)的第二次加密一样的操作,从而获得真正的验证值(也就是在redis中存储的值),然后在生成订单时把这个值作为订单号返回。(把这个真正的验证值作为订单号的一个好处是这个值是个一个用户+一个商品所对应的,一般情况下秒杀商品是限购一份的,也就是说根据订单号唯一性(数据库设置该字段不可重复)可以保证每个用户限购一份相同的商品)

进行两次加密的原因: 如果只进行一次加密的话在第一步返回后redis中存储的就是最后的验证值,如果有人把接口都拿到的话可以直接进行第一步拿到验证值然后跳过第二步,进行第三步直接支付下单是可以通过验证的,但如果是两次MD5加密的话就必须依次经过这三个接口。

现阶段的抢购流程大致如下:

image.png

接口隐藏以后还需要对一些重要的接口限制访问次数,比如上面的三个接口都是跟下单有关的,为了防止被恶意攻击导致崩溃,所以需要对这些接口限制访问次数,通常来说可以对用户id做访问次数统计,也可以对ip做访问次数统计,我这里用的是统计ip然后存到redis中,过期时间十分钟。

/**
* 功能描述:
* ip访问接口次数加1
*
* @param request
* @return int
* @author yt
* @date 2022/1/17 18:40
*/
public int addIpCount(HttpServletRequest request) throws Exception {
    String ipAddress = IpUtil.getIpAddr(request);
    String limitKey = CacheKey.LIMIT_KEY.getKey() + "_" + ipAddress;
    String limitNum = (String) ipCountRedisTemplate.opsForValue().get(limitKey);
    int limit = -1;
    if (limitNum == null) {
        ipCountRedisTemplate.opsForValue().set(limitKey, "0", 600, TimeUnit.SECONDS);
    } else {
        limit = Integer.parseInt(limitNum) + 1;
        ipCountRedisTemplate.opsForValue().set(limitKey, String.valueOf(limit), 600, TimeUnit.SECONDS);
    }
    return limit;
}

/**
* 功能描述:
* 检查IP访问接口次数
*
* @param request
* @return void
* @author yt
* @date 2022/1/17 18:40
*/
public void CheckIpIsBanned(HttpServletRequest request) {
    String ipAddress = IpUtil.getIpAddr(request);
    String limitKey = CacheKey.LIMIT_KEY.getKey() + "_" + ipAddress;
    String limitNum = (String) ipCountRedisTemplate.opsForValue().get(limitKey);
    if(null == limitNum) return;
    if (Integer.parseInt(limitNum) >= ALLOW_COUNT)
        throw new RuntimeException("禁止频繁访问,请十分钟后再试");
}