文章目录
- 一、应用部署准备
- 1. mysql安装部署
- 2. redis安装部署
- 3. nacos安装部署
- 二、数据库准备
- 2.1. 创建数据库
- 2.2. 初始化表结构
- 2.3. 搭建微服务父工程
- 三、商品模块微服务
- 3.1. 搭建product-serv模块
- 3.2. 配置yml
- 3.3. 实体
- 3.4. 接口
- 3.5. service
- 3.6. controller
- 3.7. 启动类
- 四、秒杀模块微服务
- 4.1. 搭建skill-serv模块
- 4.2. 配置yml
- 4.3. 实体
- 4.4. 接口
- 4.5. service
- 4.6. controller
- 4.7. 启动类
前言:为什么单独搭建秒杀微服务呢?
为什么不对订单微服务和库存为负进行复用呢?
- 与正常业务服务隔离避免因秒杀影响主营业务
- 可根据秒杀流量单独扩容和设置参数
一、应用部署准备
1. mysql安装部署
windows 环境
MySQL 8.0.26 简易配置安装教程 (windows 64位)
Linux 环境
Mysql 8.0 安装教程 Linux Centos7
2. redis安装部署
windows 环境
windows下载、安装运行redis
Linux 环境
(单机)Linux环境安装最新版Redis-6.2.0linux环境下redis5.0的安装配置
3. nacos安装部署
版本选择
毕业版本依赖关系(推荐使用)
Spring Cloud Version | Spring Cloud Alibaba Version | Spring Boot Version | Nacos Version |
Spring Cloud Hoxton.SR9 | 2.2.6.RELEASE | 2.3.2.RELEASE | 1.4.2 |
nacos官网:
https://nacos.io/zh-cn/docs/quick-start.html
下载对用版本的nacos
https://github.com/alibaba/nacos/tags
启动nacos
# 启动命令(standalone代表着单机模式运行,非集群模式):
# linux
sh startup.sh -m standalone
# Windows
二、数据库准备
2.1. 创建数据库
创建一个名称为skill
的数据库
2.2. 初始化表结构
-- ----------------------------
-- Table structure for skill_goods
-- ----------------------------
DROP TABLE IF EXISTS `skill_goods`;
CREATE TABLE `skill_goods` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '标题',
`price` decimal(10, 2) NULL DEFAULT NULL COMMENT '原价格',
`cost_price` decimal(10, 2) NULL DEFAULT NULL COMMENT '秒杀价格',
`status` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '审核状态,0未审核,1审核通过,2审核不通过',
`num` int NULL DEFAULT NULL COMMENT '秒杀商品数',
`stock_count` int NULL DEFAULT NULL COMMENT '剩余库存数',
`introduction` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Table structure for skill_order
-- ----------------------------
DROP TABLE IF EXISTS `skill_order`;
CREATE TABLE `skill_order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`skill_id` bigint NULL DEFAULT NULL COMMENT '秒杀商品ID',
`money` decimal(10, 2) NULL DEFAULT NULL COMMENT '支付金额',
`user_id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`pay_time` datetime NULL DEFAULT NULL COMMENT '支付时间',
`status` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '状态0-未支付, 1-已支付',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
2.3. 搭建微服务父工程
由于很多依赖都是一样的,因此,搭建一个父工程引入依赖,子模块集成依赖即可
创建eshop-parent父工程
引入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.gblfy</groupId>
<artifactId>eshop-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!--https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E-->
<properties>
<java.version>1.8</java.version>
<spring.cloud-version>Hoxton.SR9</spring.cloud-version>
</properties>
<dependencies>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--Lombok引入-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Spring Boot JPA 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--mvc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!--spring-cloud 版本控制-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring-cloud-alibaba 版本控制-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
三、商品模块微服务
3.1. 搭建product-serv模块
集成父工程
<parent>
<artifactId>eshop-parent</artifactId>
<groupId>com.gblfy</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>product-serv</artifactId>
3.2. 配置yml
server:
port: 9000
spring:
cloud:
nacos:
discovery:
service: product-serv
server-addr: localhost:8848
datasource:
url: jdbc:mysql://localhost:3306/skill?characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: 123456
redis:
host: localhost
port: 6379
3.3. 实体
package com.gblfy.entity;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
import java.math.BigDecimal;
@Data
@Entity
@Table(name="skill_goods")
public class SkillGood implements Serializable {
public SkillGood() {
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 标题
*/
@Column(name = "name")
private String name;
/**
* 原价格
*/
@Column(name = "price")
private BigDecimal price;
/**
* 秒杀价格
*/
@Column(name = "cost_price")
private BigDecimal costPrice;
/**
* 审核状态
*/
@Column(name = "status")
private String status;
/**
* 秒杀商品数
*/
@Column(name = "num")
private Integer num;
/**
* 剩余库存数
*/
@Column(name = "stock_count")
private Integer stockCount;
/**
* 描述
*/
@Column(name = "introduction")
private String introduction;
private static final long serialVersionUID = 1L;
}
3.4. 接口
package com.gblfy.dao;
import com.gblfy.entity.SkillGood;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface SkillGoodRepository extends JpaRepository<SkillGood,Long> {
@Query(value="select * from skill_goods where status=1 and num>0 and stock_count>0 and id not in (?1)",nativeQuery = true)
List<SkillGood> findSkill(List<Long> ids);
@Query(value="select * from skill_goods where status=1 and num>0 and stock_count>0",nativeQuery = true)
List<SkillGood> findSkillAll();
}
3.5. service
package com.gblfy.service;
import com.gblfy.dao.SkillGoodRepository;
import com.gblfy.entity.SkillGood;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@Component
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class SkillGoodService {
private final RedisTemplate redisTemplate;
private final SkillGoodRepository skillGoodRepository;
public static final String SKILL_GOODS_PHONE = "SKILL_GOODS_PHONE";
/**
* 每五秒执行一次 将需要参与秒杀的商品列表加载到内存
*/
@Scheduled(cron = "0/5 * * * * ?")
public void prepareGood() {
System.out.println("开始加载商品");
//获取所有已经在内存当中的商品ID列表
Set<Long> set = redisTemplate.boundHashOps(SKILL_GOODS_PHONE).keys();
List<Long> ids = new ArrayList<>();
for (Long id : set) {
ids.add(id);
}
List<SkillGood> list = null;
//只查询出不在内存当中的商品信息,并加载到内存
if (CollectionUtils.isEmpty(ids)) {
list = skillGoodRepository.findSkillAll();
} else {
list = skillGoodRepository.findSkill(ids);
}
if (!CollectionUtils.isEmpty(list)) {
for (SkillGood skillGood : list) {
redisTemplate.boundHashOps(SKILL_GOODS_PHONE).put(skillGood.getId(), skillGood);
}
}
// 查看当前缓存中所有的商品信息
Set keys = redisTemplate.boundHashOps(SKILL_GOODS_PHONE).keys();
for (Object s : keys) {
SkillGood skillGood = (SkillGood) redisTemplate.boundHashOps(SKILL_GOODS_PHONE).get(s);
System.out.println(skillGood.getName() + " 库存剩余:" + skillGood.getStockCount());
}
}
// 提供查询商品信息的方法
public SkillGood queryProduct(Long productId) {
return (SkillGood) redisTemplate.boundHashOps(SKILL_GOODS_PHONE).get(productId);
}
public void update(SkillGood skillGood) {
skillGoodRepository.save(skillGood);
}
}
3.6. controller
package com.gblfy.controller;
import com.gblfy.entity.SkillGood;
import com.gblfy.service.SkillGoodService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
public class ProductController {
@Autowired
private SkillGoodService skillGoodService;
@GetMapping("/product/{productId}")
@ResponseBody
public SkillGood getProduct(@PathVariable Long productId){
System.out.println("调用商品服务");
SkillGood skillGood=skillGoodService.queryProduct(productId);
return skillGood;
}
@PostMapping("/product")
public String update(@RequestBody SkillGood skillGood){
System.out.println("更新库存");
skillGoodService.update(skillGood);
return "更新库存成功";
}
}
3.7. 启动类
package com.gblfy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
public class ProductAplication {
public static void main(String[] args) {
SpringApplication.run(ProductAplication.class);
}
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//采用普通的key 为 字符串
template.setKeySerializer(new StringRedisSerializer());
return template;
}
}
四、秒杀模块微服务
4.1. 搭建skill-serv模块
集成父工程
<parent>
<artifactId>eshop-parent</artifactId>
<groupId>com.gblfy</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>skill-serv</artifactId>
4.2. 配置yml
server:
port: 13000
spring:
cloud:
nacos:
discovery:
service: skill-serv
server-addr: localhost:8848
datasource:
url: jdbc:mysql://localhost:3306/skill?characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: 123456
redis:
host: localhost
port: 6379
4.3. 实体
package com.gblfy.entity;
import javax.persistence.*;
import java.io.Serializable;
import java.math.BigDecimal;
@Entity
@Table(name = "skill_goods")
@Data
public class SkillGood implements Serializable {
public SkillGood() {
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 标题
*/
@Column(name = "name")
private String name;
/**
* 原价格
*/
@Column(name = "price")
private BigDecimal price;
/**
* 秒杀价格
*/
@Column(name = "cost_price")
private BigDecimal costPrice;
/**
* 审核状态
*/
@Column(name = "status")
private String status;
/**
* 秒杀商品数
*/
@Column(name = "num")
private Integer num;
/**
* 剩余库存数
*/
@Column(name = "stock_count")
private Integer stockCount;
/**
* 描述
*/
@Column(name = "introduction")
private String introduction;
private static final long serialVersionUID = 1L;
package com.gblfy.entity;
import javax.persistence.*;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
@Entity
@Table(name = "skill_order")
@Data
public class SkillOrder implements Serializable {
/**
* 主键
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* 秒杀商品ID
*/
@Column(name = "skill_id")
private Long skillId;
/**
* 支付金额
*/
@Column(name = "money")
private BigDecimal money;
/**
* 用户
*/
@Column(name = "user_id")
private String userId;
/**
* 创建时间
*/
@Column(name = "create_time")
private Date createTime;
/**
* 支付时间
*/
@Column(name = "pay_time")
private Date payTime;
/**
* 状态
*/
@Column(name = "status")
private String status;
private static final long serialVersionUID = 1L;
4.4. 接口
package com.gblfy.dao;
import com.gblfy.entity.SkillOrder;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface SkillOrderRepository extends JpaRepository<SkillOrder,Long> {
}
4.5. service
package com.gblfy.service;
import com.gblfy.entity.SkillGood;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class ProductService {
private final RestTemplate restTemplate;
public SkillGood getGoodById(Long productId) {
return restTemplate.getForObject("http://product-serv/product/" + productId, SkillGood.class);
}
public void update(SkillGood skillGood) {
ResponseEntity<String> result= restTemplate.postForEntity("http://product-serv/product/",skillGood,String.class);
System.out.println(result.getBody());
}
}
package com.gblfy.service;
import com.gblfy.dao.SkillOrderRepository;
import com.gblfy.entity.SkillGood;
import com.gblfy.entity.SkillOrder;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.Date;
@Service
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class SkillGoodService {
public static final String SKILL_GOODS_PHONE = "SKILL_GOODS_PHONE";
private final RedisTemplate redisTemplate;
private final SkillOrderRepository skillOrderRepository;
private final ProductService productService;
@Transactional
public void add(Long productId, String userId) throws Exception {
SkillGood skillGood = productService.getGoodById(productId);
if (skillGood == null) {
throw new Exception("商品已经被抢光拉");
}
if (skillGood.getStockCount() > 0) {
SkillOrder skillOrder = new SkillOrder();
skillOrder.setMoney(skillGood.getCostPrice());
skillOrder.setPayTime(new Date());
skillOrder.setStatus("0");
skillOrder.setUserId(userId);
skillOrder.setCreateTime(new Date());
skillOrder.setSkillId(productId);
skillOrderRepository.save(skillOrder);
skillGood.setStockCount(skillGood.getStockCount() - 1);
redisTemplate.boundHashOps(SKILL_GOODS_PHONE).put(skillGood.getId(), skillGood);
System.out.println("成功秒杀 剩余库存:" + skillGood.getStockCount());
}
if (skillGood.getStockCount() <= 0) {
System.out.println("库存已经是负数了:" + skillGood.getStockCount());
redisTemplate.boundHashOps(SKILL_GOODS_PHONE).delete(skillGood.getId());
productService.update(skillGood);
}
}
}
4.6. controller
package com.gblfy.controller;
import com.gblfy.service.SkillGoodService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SkillController {
@Autowired
private SkillGoodService skillGoodService;
@GetMapping("/skill")
public String add(Long productId,String userId) {
try{
skillGoodService.add(productId,userId);
return "抢单成功";
}catch (Exception e){
return "商品已经抢光";
}
}
}
4.7. 启动类
package com.gblfy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient
public class SkillServApplication {
public static void main(String[] args) {
SpringApplication.run(SkillServApplication.class, args);
}
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//采用普通的key 为 字符串
template.setKeySerializer(new StringRedisSerializer());
return template;
}
@Bean
@LoadBalanced
public RestTemplate create() {
return new RestTemplate();
}
}