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. WIN10桌面无创建文件夹选项,无法创建文件

    在桌面或其他磁盘,右键没有新建选项,无法新建文件夹或文档.   右键桌面左下角开始按钮,点击:命令提示符(管理员)   弹出,Windows命令处理程序对话框,点击是   粘贴内容: cmd /k r ...

  2. idea快速查看api文档

    第一种: 显示结果:点击箭头可以跳转到网页上查看 第二种:ctrl+q 快捷键

  3. [转帖]实时流处理系统反压机制(BackPressure)综述

    实时流处理系统反压机制(BackPressure)综述 https://blog.csdn.net/qq_21125183/article/details/80708142 2018-06-15 19 ...

  4. Spring Boot配置文件yml讲解--行内对象的配置方式

    yml行内对象的配置方法,一般是采取 上面的缩进方式,我只想配置在一行怎么处?——

  5. ES与关系型数据库的通俗比较

    1.在Elasticsearch中,文档归属于一种类型(type),而这些类型存在于索引(index)中,我们可以画一些简单的对比图来类比传统关系型数据库: Relational DB -> D ...

  6. javascript序列化表单追加参数

    js序列化表单后追加参数方式: 追加参数:token,status var data = $.param({"token":token, "status":st ...

  7. robot framework 接口测试 http协议post请求json格式

    robot framework 接口测试 http协议post请求json格式 讲解一个基础版本.注意区分url地址和uri地址. rf和jmeter在添加服务器地址也就是ip地址的时候,只能url地 ...

  8. java线程的五种状态

    五种状态 开始状态(new) 就绪状态(runnable) 运行状态(running) 阻塞状态(blocked) 结束状态(dead) 状态变化 1.线程刚创建时,是new状态 2.线程调用了sta ...

  9. git本地下载github上的某项目的部分资源

    注意以下命令要在git bash下进行,不要是cmd,或者是powershell. cd 到某个目录下 git init git  remote add -f origin <url> g ...

  10. js 单线程 异步

    线程与进程: 进程是系统资源分配和调度的单元.一个运行着的程序就对应一个进程.在windows中,每一个打开的运行的应用程序或后台程序,比如运行中的qq,谷歌浏览器,网易云音乐,资源管理器等都是一个进 ...