摘要:本博客将介绍如何使用 Spring Boot 实现一个简单的商城秒杀系统,并通过使用 Redis 和 MySQL 来增强其性能和可靠性。
本文分享自华为云社区《Spring Boot实现商城高并发秒杀案例》,作者:林欣。
随着经济的发展和人们消费观念的转变,电子商务逐渐成为人们购物的主要方式之一。高并发是电子商务网站面临的一个重要挑战。本博客将介绍如何使用 Spring Boot 实现一个简单的商城秒杀系统,并通过使用 Redis 和 MySQL 来增强其性能和可靠性。
在开始之前,您需要准备以下工具和环境:
首先,我们需要创建一个数据库来存储商品信息、订单信息和秒杀活动信息。在这里,我们使用 MySQL 数据库,创建一个名为 shop 的数据库,并建立三个表 goods、order 和 seckill。
表 goods 存储了所有的商品信息,包括商品编号、名称、描述、价格和库存数量等等。
CREATE TABLE `goods` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品ID',
`name` varchar(50) NOT NULL COMMENT '商品名称',
`description` varchar(100) NOT NULL COMMENT '商品描述',
`price` decimal(10,2) NOT NULL COMMENT '商品价格',
`stock_count` int(11) NOT NULL COMMENT '商品库存',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';
表 order 存储了所有的订单信息,包括订单编号、用户ID、商品ID、秒杀活动ID 和订单状态等等。
CREATE TABLE `order` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '订单ID',
`user_id` BIGINT(20) NOT NULL COMMENT '用户ID',
`goods_id` BIGINT(20) NOT NULL COMMENT '商品ID',
`seckill_id` BIGINT(20) DEFAULT NULL COMMENT '秒杀活动ID',
`status` TINYINT(4) NOT NULL COMMENT '订单状态,0-未支付,1-已支付,2-已发货,3-已收货,4-已退款,5-已完成',
`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_order` (`user_id`,`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
表 seckill 存储了所有的秒杀活动信息,包括秒杀活动编号、商品ID、开始时间和结束时间等等。
CREATE TABLE `seckill` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '秒杀活动ID',
`goods_id` BIGINT(20) NOT NULL COMMENT '商品ID',
`start_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '开始时间',
`end_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '结束时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='秒杀活动表';
接下来,我们需要创建一个 Spring Boot 项目,用于实现商城高并发秒杀案例。可以使用 Spring Initializr 来快速创建一个基本的 Spring Boot 项目。
在 Spring Boot 项目中,我们需要配置 Redis 和 MySQL 的连接信息。可以在 application.properties 文件中设置以下属性:
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shop?serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=123456
在这一步中,我们需要定义三个实体类分别对应数据库中的 goods、order 和 seckill 表。同时,我们需要编写相应的 DAO 接口,用于操作这些实体类。
// 商品实体类
@Data
public class Goods {
private Long id;
private String name;
private String description;
private BigDecimal price;
private Integer stockCount;
}
// 商品 DAO 接口
@Mapper
public interface GoodsDao {
@Select("SELECT * FROM goods WHERE id = #{id}")
Goods getGoodsById(Long id);
@Update("UPDATE goods SET stock_count = stock_count - 1 WHERE id = #{id} AND stock_count > 0")
int reduceStockCount(Long id);
}
// 订单实体类
@Data
public class Order {
private Long id;
private Long userId;
private Long goodsId;
private Long seckillId;
private Byte status;
private Date createTime;
private Date updateTime;
}
// 订单 DAO 接口
@Mapper
public interface OrderDao {
@Select("SELECT * FROM `order` WHERE user_id = #{userId} AND goods_id = #{goodsId}")
Order getOrderByUserIdAndGoodsId(@Param("userId") Long userId, @Param("goodsId") Long goodsId);
@Insert("INSERT INTO `order` (user_id, goods_id, seckill_id, status, create_time, update_time) VALUES (#{userId}, #{goodsId}, #{seckillId}, #{status},#{createTime},#{updateTime})")
int insertOrder(Order order);
@Select("SELECT o.*, g.name, g.price FROM `order` o LEFT JOIN goods g ON o.goods_id = g.id WHERE o.user_id = #{userId}")
List<OrderVo> getOrderListByUserId(Long userId);
}
// 秒杀活动实体类
@Data
public class Seckill {
private Long id;
private Long goodsId;
private Date startTime;
private Date endTime;
}
// 秒杀活动 DAO 接口
@Mapper
public interface SeckillDao {
@Select("SELECT * FROM seckill WHERE id = #{id}")
Seckill getSeckillById(Long id);
@Update("UPDATE seckill SET end_time = #{endTime} WHERE id = #{id}")
int updateSeckillEndTime(@Param("id") Long id, @Param("endTime") Date endTime);
}
在这一步中,我们需要编写 Service 层和 Controller 类,用于实现商城高并发秒杀案例的核心功能。
@Service
public class GoodsService {
private final GoodsDao goodsDao;
@Autowired
public GoodsService(GoodsDao goodsDao) {
this.goodsDao = goodsDao;
}
public Goods getGoodsById(Long id) {
return goodsDao.getGoodsById(id);
}
public boolean reduceStockCount(Long id) {
return goodsDao.reduceStockCount(id) > 0;
}
}
@Service
public class OrderService {
private final OrderDao orderDao;
@Autowired
public OrderService(OrderDao orderDao) {
this.orderDao = orderDao;
}
public Order createOrder(Long userId, Long goodsId, Long seckillId) {
Order order = new Order();
order.setUserId(userId);
order.setGoodsId(goodsId);
order.setSeckillId(seckillId);
order.setStatus((byte) 0);
order.setCreateTime(new Date());
order.setUpdateTime(new Date());
orderDao.insertOrder(order);
return order;
}
public List<OrderVo> getOrderListByUserId(Long userId) {
return orderDao.getOrderListByUserId(userId);
}
}
@Service
public class SeckillService {
private final SeckillDao seckillDao;
@Autowired
public SeckillService(SeckillDao seckillDao) {
this.seckillDao = seckillDao;
}
public Seckill getSeckillById(Long id) {
return seckillDao.getSeckillById(id);
}
public boolean updateSeckillEndTime(Long id, Date endTime) {
return seckillDao.updateSeckillEndTime(id, endTime) > 0;
}
}
@RestController
@RequestMapping("/order")
public class OrderController {
private final OrderService orderService;
@Autowired
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@PostMapping("/create")
public CommonResult<Order> createOrder(@RequestParam("userId") Long userId,
@RequestParam("goodsId") Long goodsId,
@RequestParam("seckillId") Long seckillId) {
Order order = orderService.createOrder(userId, goodsId, seckillId);
if (order == null) {
return CommonResult.failed(ResultCode.FAILURE);
}
return CommonResult.success(order);
}
@GetMapping("/list")
public CommonResult<List<OrderVo>> getOrderListByUserId(@RequestParam("userId") Long userId) {
List<OrderVo> orderList = orderService.getOrderListByUserId(userId);
return CommonResult.success(orderList);
}
}
秒杀活动 Controller:用于处理秒杀活动相关的请求。
@RestController
@RequestMapping("/seckill")
public class SeckillController {
private final SeckillService seckillService;
private final GoodsService goodsService;
private final OrderService orderService;
@Autowired
public SeckillController(SeckillService seckillService, GoodsService goodsService, OrderService orderService) {
this.seckillService = seckillService;
this.goodsService = goodsService;
this.orderService = orderService;
}
@PostMapping("/start")
public CommonResult<Object> startSeckill(@RequestParam("userId") Long userId,
@RequestParam("goodsId") Long goodsId,
@RequestParam("seckillId") Long seckillId) {
// 查询秒杀活动是否有效
Seckill seckill = seckillService.getSeckillById(seckillId);
if (seckill == null || seckill.getStartTime().after(new Date()) || seckill.getEndTime().before(new Date())) {
return CommonResult.failed(ResultCode.FAILURE, "秒杀活动不存在或已结束");
}
// 判断商品库存是否充足
Goods goods = goodsService.getGoodsById(goodsId);
if (goods == null || goods.getStockCount() <= 0) {
return CommonResult.failed(ResultCode.FAILURE, "商品库存不足");
}
// 生成订单
Order order = orderService.createOrder(userId, goodsId, seckillId);
if (order == null) {
return CommonResult.failed(ResultCode.FAILURE, "订单创建失败,请稍后再试");
}
// 减少商品库存
boolean success = goodsService.reduceStockCount(goodsId);
if (!success) {
return CommonResult.failed(ResultCode.FAILURE, "减少商品库存失败,请稍后再试");
}
return CommonResult.success("秒杀成功");
}
}
在商城高并发秒杀案例中,一个重要的问题是如何保证商品库存数量的一致性和秒杀结果的正确性。为了解决这个问题,我们可以使用 Redis 实现分布式锁。
在 RedisService 类中实现分布式锁:
@Service
public class RedisService {
private final RedisTemplate<String, Object> redisTemplate;
@Autowired
public RedisService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public boolean lock(String key, String value, long expire) {
Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, Duration.ofSeconds(expire));
return result != null && result;
}
public void unlock(String key, String value) {
if (value.equals(redisTemplate.opsForValue().get(key))) {
redisTemplate.delete(key);
}
}
}
在 SeckillService 中使用分布式锁实现秒杀接口:
@Service
public class SeckillService {
private final RedisService redisService;
private final SeckillDao seckillDao;
private final GoodsDao goodsDao;
private final OrderDao orderDao;
@Autowired
public SeckillService(RedisService redisService, SeckillDao seckillDao, GoodsDao goodsDao, OrderDao orderDao) {
this.redisService = redisService;
this.seckillDao = seckillDao;
this.goodsDao = goodsDao;
this.orderDao = orderDao;
}
public CommonResult<Object> startSeckill(Long userId, Long goodsId, Long seckillId) {
String lockKey = "seckill:lock:" + goodsId;
String lockValue = UUID.randomUUID().toString();
try {
// 获取分布式锁
if (!redisService.lock(lockKey, lockValue, 10)) {
return CommonResult.failed(ResultCode.FAILURE, "当前请求太过频繁,请稍后再试");
}
// 查询秒杀活动是否有效
Seckill seckill = seckillDao.getSeckillById(seckillId);
if (seckill == null || seckill.getStartTime().after(new Date()) || seckill.getEndTime().before(new Date())) {
return CommonResult.failed(ResultCode.FAILURE, "秒杀活动不存在或已结束");
}
// 判断商品库存是否充足
Goods goods = goodsDao.getGoodsById(goodsId);
if (goods == null || goods.getStockCount() <= 0) {
return CommonResult.failed(ResultCode.FAILURE, "商品库存不足");
}
// 创建订单
Order order = new Order();
order.setUserId(userId);
order.setGoodsId(goodsId);
order.setSeckillId(seckillId);
order.setStatus((byte) 0);
order.setCreateTime(new Date());
order.setUpdateTime(new Date());
int count = orderDao.insertOrder(order);
if (count <= 0) {
return CommonResult.failed(ResultCode.FAILURE, "订单创建失败,请稍后再试");
}
// 减少商品库存
boolean success = goodsDao.reduceStockCount(goodsId) > 0;
if (!success) {
throw new Exception("减少商品库存失败,请稍后再试");
}
return CommonResult.success("秒杀成功");
} catch (Exception e) {
e.printStackTrace();
return CommonResult.failed(ResultCode.FAILURE, "秒杀失败," + e.getMessage());
} finally {
// 释放分布式锁
redisService.unlock(lockKey, lockValue);
}
}
}
电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。 准备工作: 1、U盘一个(尽量使用8G以上的U盘)。 2、一台正常联网可使用的电脑。 3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。 4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。 U盘启动盘制作步骤: 注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc
是否可以在PyYAML或Ruby的Psych引擎中禁用创建anchor和引用(并有效地显式列出冗余数据)?也许我在网上搜索时遗漏了一些东西,但在Psych中似乎没有太多可用的选项,而且我也无法确定PyYAML是否允许这样做.基本原理是我必须序列化一些数据并将其以可读的形式传递给一个不是真正的技术同事进行手动验证。有些数据是多余的,但我需要以最明确的方式列出它们以提高可读性(anchor和引用是提高效率的好概念,但不是人类可读性)。Ruby和Python是我选择的工具,但如果有其他一些相当简单的方法来“展开”YAML文档,它可能就可以了。 最佳答案
因为我现在正在做一些时间测量,我想知道是否可以在不使用Benchmark类或命令行实用程序time的情况下测量用户时间或系统时间。使用Time类只显示挂钟时间,而不显示系统和用户时间,但是我正在寻找具有相同灵active的解决方案,例如time=TimeUtility.now#somecodeuser,system,real=TimeUtility.now-time原因是我有点不喜欢Benchmark,因为它不能只返回数字(编辑:我错了-它可以。请参阅下面的答案。)。当然,我可以解析输出,但感觉不对。*NIX系统的time实用程序也应该可以解决我的问题,但我想知道是否已经在Ruby中实
在Ruby中,以毫秒为单位获取自纪元(1970)以来的当前系统时间的正确方法是什么?我试过了Time.now.to_i,好像不是我想要的结果。我需要结果显示毫秒并且使用long类型,而不是float或double。 最佳答案 (Time.now.to_f*1000).to_iTime.now.to_f显示包含十进制数字的时间。要获得毫秒数,只需将时间乘以1000。 关于ruby-以毫秒为单位获取当前系统时间,我们在StackOverflow上找到一个类似的问题:
在我的路线文件中我有:match'graphs/(:id(/:action))'=>'graphs#(:action)'如果是GET请求(工作)或POST请求(不工作),我想匹配它我知道我可以使用以下方法在资源中声明POST请求:post'/'=>:show,:on=>:member但是我怎样才能为比赛做到这一点呢?谢谢。 最佳答案 如果你同时想要POST和GETmatch'graphs/(:id(/:action))'=>'graphs#(:action)',:via=>[:get,:post]编辑默认值可以设置如下match'g
关闭。这个问题需要更多focused.它目前不接受答案。想改进这个问题吗?更新问题,使其只关注一个问题editingthispost.关闭8年前。Improvethisquestion我们有以下(以及更多)系统,我们将数据从一个应用推送/拉取到另一个:托管CRM(InsideSales.com)Asterisk电话系统(内部)横幅广告系统(openx,我们托管)潜在客户生成系统(自行开发)电子商务商店(spree,我们托管)工作板(本土)一些工作网站抓取+入站工作提要电子邮件传送系统(如Mailchimp,自主开发)事件管理系统(如eventbrite,自主开发)仪表板系统(大量图表和
我正在尝试找出一种方法来显示来自不在RAILS_ROOT下(在RedHat或Ubuntu环境中)的已安装文件系统的图像。我不想使用符号链接(symboliclink),因为这个应用程序实际上是通过Tomcat部署的,而当我关闭Tomcat时,Tomcat会尝试跟随符号链接(symboliclink)并删除挂载中的所有图像。由于这些文件的数量和大小,将图像放在public/images下也不是一种选择。我查看了send_file,但它只会显示一张图片。我需要在一个格式良好的页面中显示6个请求的图像。由于膨胀,我宁愿不使用Base64编码,但我不知道如何将图像数据与呈现的页面一起传递下去。
当您在Ruby脚本中使用系统调用时,您可以像这样获得该命令的输出:output=`ls`putsoutput这就是thisquestion是关于。但是有没有办法显示系统调用的连续输出?例如,如果您运行此安全复制命令,以通过SSH从服务器获取文件:scpuser@someserver:remoteFile/some/local/folder/...它显示随着下载进度的连续输出。但是这个:output=`scpuser@someserver:remoteFile/some/local/folder/`putsoutput...不捕获该输出。如何从我的Ruby脚本中显示正在进行的下载进度?