springboot+支付宝完成秒杀项目的初体验

思考的问题:

首先是秒杀的商品查询,考虑到是热点数据,所以写一个接口读取当日批次的秒杀商品到redis中(那么接下来对商品的操作都放入redis中)。
当用户抢购商品时,考虑到的是是否在秒杀时间段内以及商品是抢完的问题。首先需要判断该商品是否在秒杀时间内,然后要查询该商品数量是否足够。当然这些还不够,还要有为了防止高并发的解决方案:
  • 对用户限流:对恶意请求通过ip设置访问次数,超过次数则抛出异常。
  • 利用消息队列的异步请求来削峰
  • 利用redis做缓存:提升读的效率,当然在秒杀的时候更改数量也可以在redis中完成,可以保证数据库的安全性。
  • 当然还有nginx方向代理,双击热备份,资源静态化,服务做集群,还有redis的主从集群

最主要的还是数据库的读写问题,强一致问题


当用户抢购时,进入并调用后端服务,这时候首先在redis中设置商品的键值对,然后读出redis中该商品的秒杀时间和数量,当满足条件时,更新redis中该商品的数量并在redis中生成订单。(这里用到了redis的锁,当这个商品的键没有时,则提交事物,当有时则不提交事物)

这时候通过线程来判断该商品是否在一分钟之内付款,付款失败则将商品➕1,好让商品继续可以被秒杀。

同时,为了防止该商品在事物过程中发生错误,导致该商品一直锁住,所以使用了try catch finally代码块,在finally中释放该锁,即是删除该键值对。

核心代码如下:

#######利用任务系统查询当日秒杀商品业务:

public AppResult FindSkProduce() {

//找到今日打折的商品放入redis中

//条件 状态为1 数量大于0 在指定时间内

//用hash(Map)存储 大键为cs1901_sk 小键为商品id 值为商品对象

String s= DateUtils.getCurrentDate();

String s1 = s + " 00:00:00";

String s2 = s+ " 23:59:59";

Date date1 = DateUtils.stringToDate(s1,DateUtils.DATE_TIME_FORMAT);

Date date2 = DateUtils.stringToDate(s2,DateUtils.DATE_TIME_FORMAT);

TbSeckillGoodsExample example=new TbSeckillGoodsExample();

example.createCriteria().andEndTimeBetween(date1,date2).andStatusEqualTo("1").andNumGreaterThan(0);

List glist=tbSeckillGoodsMapper.selectByExample(example);

HashOperations hashOperations =redisTemplate.opsForHash();

for (TbSeckillGoods tbSeckillGoods : glist) {

System.out.println(tbSeckillGoods.getId());

hashOperations.put("cs1901_sk",tbSeckillGoods.getId()+"",tbSeckillGoods);

}

//设置过期时间 过期时间为EndTime减去现在的时间(data2-nowData)

Long expireTime=(date2.getTime()-new Date().getTime())/1000;

System.out.println(expireTime);

//设置过期时间用expire 注意是redisTemplate中的方法 参数(Key,Long 时间,时间单位(TimeUnit))

redisTemplate.expire("cs1901_sk",expireTime,TimeUnit.SECONDS);

    return new AppResult(true,200,null,null);
}
秒杀业务:
public AppResult CreateSkOrder(Long pid) {
//创建订单
//用redis的锁 锁住 该商品(在redis中查出该商品)
//原理:redis 先设置唯一键 , 用redis 的事物和提交 ,再提交之前判断唯一键是否存在
//watch(key) 若有该唯一键 则不提交事物,无则提交事物
RedisLock redisLock=new RedisLock(redisTemplate,"lock"+pid);
try {
//事物
//生成订单之前的判断:1 判断秒杀是否过期 2 判断商品数量是否足够
//生成订单:1 生成唯一ID 将redis中的该商品数量减一 更新进redis
// 2 生成订单 redis设置键位 大键为cs1901_sk_order 小键为订单id 值为订单
//若抛出异常在finally中解锁 防止商品被锁死
//新建线程 若一分钟未付款删除订单
//用 thread //1 判断秒杀是否过期
HashOperations hashOperations=redisTemplate.opsForHash();
TbSeckillGoods tbSeckillGoods= (TbSeckillGoods) hashOperations.get("cs1901_sk",pid+"");
if (redisTemplate.hasKey("cs1901_sk")==false){
throw new AppExeption(200,"秒杀时间已过");
}
//2 判断商品数量是否大于0
if (tbSeckillGoods.getNum()<=0){
throw new AppExeption(201,"商品已抢购完");
}
//1 更新数量
tbSeckillGoods.setNum(tbSeckillGoods.getNum()-1);
hashOperations.put("cs1901_sk",pid+"",tbSeckillGoods);
//2 生成订单
TbSeckillOrder tbSeckillOrder=new TbSeckillOrder();
IDUtils idUtils=new IDUtils();
Long oid=idUtils.nextId();
tbSeckillOrder.setId(oid);
tbSeckillOrder.setStatus("1");
hashOperations.put("cs1901_sk_order",oid+"",tbSeckillOrder); //新建线程
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
TbSeckillOrder tbSeckillOrder1= (TbSeckillOrder) hashOperations.get("cs1901_sk_order",oid+"");
if (tbSeckillOrder1.getStatus().equals("1")){
//将商品数量加一
TbSeckillGoods tbSeckillGoods1= (TbSeckillGoods) hashOperations.get("cs1901_sk",pid+"");
tbSeckillGoods1.setNum(tbSeckillGoods.getNum()+1);
hashOperations.put("cs1901_sk",pid+"",tbSeckillGoods1);
}
}
}).start();
return new AppResult(true,200,"已经抢到,请在一分钟内完成付款",oid+"");
}catch (Exception ex){
return new AppResult(false,201,"没有抢到",null);
}finally {
redisLock.unlock();
}
支付宝创建订单:
@GetMapping("/pay/createsk/{orderid}")
public void createsk(@PathVariable("orderid") Long orderid , HttpServletResponse response) throws Exception {
//System.out.println(orderid+":"+total);
//获得初始化的AlipayClient(生成支付宝客户端)
AppResult result = payService.createSkPay(orderid,1L);
if(result.isSuccess()==true){
//把支付宝返回给我们的字符串 打印到客户端的浏览器
response.getWriter().println(result.getData());
}else{
response.getWriter().println(result.getMessage());
}
}
支付成功同步回调:
@PostMapping("/asynonotice")
@ResponseBody
public String asynonotice(HttpServletRequest request) throws Exception {
// 1: 获得支付宝返回的消息
System.out.println("异步回调");
String result = null;
Map<String,String> params = new HashMap<String,String>();
Map<String,String[]> requestParams = request.getParameterMap();
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i]
: valueStr + values[i] + ",";
}
params.put(name, valueStr);
}
System.out.println(params);
//2: 延签( )
boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key,
AlipayConfig.charset, AlipayConfig.sign_type); //调用SDK验证签名
if(signVerified==false){
//return new AppResult(false,null,null,"fail");
return "fail";
}
//3 判断 支付成功还是退款成功
try {
String trade_status = new String(request.getParameter("trade_status"));
if(trade_status.equals("TRADE_FINISHED")){
// 退款的话
//payService.returnnotice(params);
//...............
}else if (trade_status.equals("TRADE_SUCCESS")){
AppResult appResult = payService.PaySyncNotice(params);
if(appResult.isSuccess()){
result = "success";
}else{
result = "fail";
}
}
} catch (Exception e) {
result = "fail";
e.printStackTrace();
}
System.out.println(result.toString());
return "success";
}
支付成功异步回调:
@PostMapping("/sk/syncnotice")
@ResponseBody
public String sksyncnotice(HttpServletRequest request) throws Exception { String result = null;
// 1: 获得支付宝返回的消息
Map<String,String> params = new HashMap<String,String>();
Map<String,String[]> requestParams = request.getParameterMap();
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i]
: valueStr + values[i] + ",";
}
params.put(name, valueStr);
}
//2: 验签( )
boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key,
AlipayConfig.charset, AlipayConfig.sign_type); //调用SDK验证签名
if(signVerified==false){
//return new AppResult(false,null,null,"fail");
return "fail";
}
//3 判断 支付成功还是退款成功
try {
String trade_status = new String(request.getParameter("trade_status"));
if(trade_status.equals("TRADE_FINISHED")){
// 退款的话
//payService.returnnotice(params);
//...............
}else if (trade_status.equals("TRADE_SUCCESS")){
AppResult appResult = payService.skpayNotice(params);
if(appResult.isSuccess()){
result = "success";
}else{
result = "fail";
}
}
} catch (Exception e) {
result = "fail";
e.printStackTrace();
}
System.out.println(result.toString());
return "success";
}

springboot+支付宝完成秒杀项目的初体验的更多相关文章

  1. (一)SpringBoot基础篇- 介绍及HelloWorld初体验

    1.SpringBoot介绍: 根据官方SpringBoot文档描述,BUILD ANYTHING WITH SPRING BOOT (用SPRING BOOT构建任何东西,很牛X呀!),下面是官方文 ...

  2. SpringBoot项目部署初体验【Docker】

    前言 一个微服务项目,小到几个模块,大到十几二十几个模块,每个模块都是单独的SpringBoot工程,这么多模块的部署,部署成本真的很高,而且每个服务的部署,都是手动部署,打成war或者jar ?,一 ...

  3. 使用 VSCode 编写 .NET Core 项目之初体验

    注:本文在根据 微软官方文档指导下,根据自己的学习中整理,并不完全照搬文档,但也大体和文档学习路线相似,主要为记录学习过程. 官方学习地址: https://code.visualstudio.com ...

  4. GitHub上传项目之初体验

    git工具是很早之前安装的,之前还没有github账号,现在注册了一个,想学一下托管自己的项目和代码. 登录github账号之后,点击绿色的"New repository",输入名 ...

  5. 手动搭建webpack + vue项目之初体验

    在使用vue做开发时,大部分人只会使用官方提供的脚手架搭建项目,脚手架虽然很好用,但想要成为一名优秀的前端开发者,webpack这一道坎是绕不开的,所以我们要学会脱离脚手架,利用webpack手动搭建 ...

  6. SpringBoot初体验及原理解析

    一.前言 ​ 上篇文章,我们聊到了SpringBoot得以实现的幕后推手,这次我们来用SpringBoot开始HelloWorld之旅.SpringBoot是Spring框架对“约定大于配置(Conv ...

  7. .NET平台开源项目速览(15)文档数据库RavenDB-介绍与初体验

    不知不觉,“.NET平台开源项目速览“系列文章已经15篇了,每一篇都非常受欢迎,可能技术水平不高,但足够入门了.虽然工作很忙,但还是会抽空把自己知道的,已经平时遇到的好的开源项目分享出来.今天就给大家 ...

  8. 微信小程序初体验,入门练手项目--通讯录,部署上线(二)

    接上一篇<微信小程序初体验,入门练手项目--通讯录,后台是阿里云服务器>:https://www.cnblogs.com/chengxs/p/9898670.html 开发微信小程序最尴尬 ...

  9. Python+Flask+Gunicorn 项目实战(一) 从零开始,写一个Markdown解析器 —— 初体验

    (一)前言 在开始学习之前,你需要确保你对Python, JavaScript, HTML, Markdown语法有非常基础的了解.项目的源码你可以在 https://github.com/zhu-y ...

随机推荐

  1. Jira 自定义工作流并设置触发器

    一.添加修改工作流 打开 设置--问题--工作流 复制一个工作流,然后进去编辑页面 添加状态 增加转换动作 切换到文本,设置跳转过程中的事件 针对Stop Progress事件,修改跳转界面(界面需先 ...

  2. python线程障碍对象Barrier(34)

    python线程Barrier俗称障碍对象,也称栅栏,也叫屏障. 一.线程障碍对象Barrier简介 # 导入线程模块 import threading # 障碍对象barrier barrier = ...

  3. scrapy框架4——下载中间件的使用

    一.下载中间件 下载中间件是scrapy提供用于用于在爬虫过程中可修改Request和Response,用于扩展scrapy的功能:比如: 可以在请求被Download之前,请求头部加上某些信息(例如 ...

  4. EAFP vs LBYL

    EAFP vs LBYL 检查数据可以让程序更健壮,用术语来说就是防御性编程.检查数据的时候,有EAFP和LBYL两种不同的编程风格,具体的意思如下: LBYL: Look Before You Le ...

  5. page分页问题,根据页码获取对应页面的数据,接口调用

    添加一个log.js文件,进行接口调用. import axios from '@/libs/api.request' const MODULE_URL = '/log'; export const ...

  6. 信息的Raid存储方式,更安全的保障,更花钱的保障!

    raid0 就是把多个(最少2个)硬盘合并成1个逻辑盘使用,数据读写时对各硬盘同时操作,不同硬盘写入不同数据,速度快. raid1就是同时对2个硬盘读写(同样的数据).强调数据的安全性.比较浪费. r ...

  7. 机器学习之逻辑回归(Logistic)笔记

    在说逻辑回归之前,可以先说一说逻辑回归与线性回归的区别: 逻辑回归与线性回归在学习规则形式上是完全一致的,它们的区别在于hθ(x(i))为什么样的函数 当hθ(x(i))=θTx(i)时,表示的是线性 ...

  8. Lucene入门+实现

    Lucene简介详情见:(https://blog.csdn.net/Regan_Hoo/article/details/78802897) lucene实现原理 其实网上很多资料表明了,lucene ...

  9. 如何导出robotframework的工程

    不知道是不是只有我一个小白,自己折腾了很久,也百度了很久,不知道怎么导出哇.现在来扫扫盲罗.我拿自己的项目举例:找到我的RF工程目录可以看到下面有3个项目,直接拷贝你想要的项目就ok啦,是不是so e ...

  10. 1+x证书学习日志——css常用属性

     ## css常用属性:             1:文本属性:                 文本大小:  font-size:18px;                 文本颜色    colo ...