秒杀项目01-项目开发环境与框架搭建

技术点介绍

前端: Thymeleaf,Bootstrap,JQuery
后端: SpringBoot,JSR303,MyBatis
中间件: RabbitMQ,Redis,Druid

秒杀系统实现

在这里插入图片描述

学习目标

**应对大并发: **

  1. 如何利用缓存
  2. 如何使用异步
  3. 如何编写优雅的代码

一、项目框架搭建

1.1 SpringBoot环境搭建

父工程pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.hmx</groupId>
    <artifactId>miaosha</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>miaosha01</module>
    </modules>

    <packaging>pom</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <version>2.5.0</version>
        <artifactId>spring-boot-starter-parent</artifactId>
    </parent>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>



</project>

子工程pom.xml

<dependencies>
    
    
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

创建启动类,三层

1.2 集成Thymeleaf,Result结果封装

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

application.properties

# 允许thymeleaf使用缓存
spring.thymeleaf.cache=true
# 是否为web框架启动thymeleaf视图解析
spring.thymeleaf.enabled=true
# 写入 HTTP 响应的 Content-Type 值。
spring.thymeleaf.servlet.content-type=text/html
# 模板文件编码
spring.thymeleaf.encoding=UTF-8
# 要应用于模板的模板模式
spring.thymeleaf.mode=HTML5
# 在构建 URL 时预先添加到查看名称的前缀。
spring.thymeleaf.prefix=classpath:/templates/
# 在构建 URL 时预先添加到查看名称的后缀。
spring.thymeleaf.suffix=.html

Result.java

@Data
public class Result<T> {
    private int code;
    private String msg;
    private T data;

    private Result(T data) {
        this.code = CodeMsg.SUCCESS.getCode();
        this.msg = CodeMsg.SUCCESS.getMsg();
        this.data = data;
    }

    private Result(CodeMsg cm) {
        if (cm == null) {
            return;
        }
        this.code = CodeMsg.SERVER_ERROR.getCode();
        this.msg = CodeMsg.SERVER_ERROR.getMsg();
    }

    /**
     * 成功时候调用
     * @param <T>
     * @return
     */
    public static <T> Result<T> success(T data) {
        return new Result<T>(data);
    }

    /**
     * 失败时候调用
     * @param <T>
     * @return
     */
    public static <T> Result<T> fail(CodeMsg cm) {
        return new Result<T>(cm);
    }
}

CodeMsg.java

public enum CodeMsg {

    //通用异常
    SUCCESS(0,"success"),
    SERVER_ERROR(500100, "服务端异常");
    //登录模块 5002XX

    //商品模块 5003XX

    //订单模块 5004XX

    //秒杀模块 5005XX


    private int code;

    private String msg;

    private CodeMsg(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

1.3 集成Mybatis+Druid

导入依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.6</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.25</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.3</version>
</dependency>

mybatis相关配置

# mybatis
# 搜索类别名的包
mybatis-plus.type-aliases-package=com.hmx.miaosha.domain
# 下划线转成驼峰
mybatis-plus.configuration.map-underscore-to-camel-case=true
# 为驱动程序设置一个提示,以控制返回结果的获取大小。此参数值可以被查询设置覆盖。
mybatis-plus.configuration.default-fetch-size=100
# 设置驱动程序等待数据库响应的秒数。
mybatis-plus.configuration.default-statement-timeout=3000
# Mapper xml配置文件的位置
mybatis-plus.mapper-locations=classpath:com/hmx/miaosha/mapper/*.xml

Druid相关配置

spring.datasource.url=jdbc:mysql:///miaosha?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=hmx123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.initialSize=1
spring.datasource.maxActive=2
spring.datasource.maxWait=60000
spring.datasource.minIdle=1

1.4 集成Jedis+Redis安装+通用缓存Key封装

Redis安装

  1. 官网下载压缩包
  2. 放到linux中
  3. 解压到后移动到/usr/local/redis下
  4. make && makeinstall
    在vim编辑器中查找内容通过"/字符串"查找,n下一个
  5. 修改redis.conf配置文件
允许所有ip访问redis
bind 0.0.0.0
# 后台启动redis
daemonize yes
# 设置redis的密码
requirepass 123456
  1. 启动redis
redis-server ./redis.conf
  1. 客户端连接redis-server
redis-cli
  1. 设置密码后需要验证才能操作
# auth 后面为你设置的密码
auth 123456
  1. 生成redis的系统服务
cd /usr/local/redis/utils
./install_server.sh

若出现如下错误:
Please take a look at the provided example service unit files in this direct

则注释掉该文件中的

#bail if this system is managed by systemd
#_pid_1_exe="$(readlink -f /proc/1/exe)"
#if [ "${_pid_1_exe##*/}" = systemd ]
#then
#       echo "This systems seems to use systemd."
#       echo "Please take a look at the provided example service unit files in this directory, and adapt and install them. Sorry!"
#       exit 1
#fi

在这里插入图片描述
10. 查看系统服务列表

chkconfig --list | grep redis
  1. 启动redis系统服务
systemctl start redis_6379

集成Redis

  1. 添加Jedis依赖
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
  1. 添加Fastjson依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.75</version>
</dependency>
  1. redis相关配置
# redis
spring.redis.host=192.168.174.128
spring.redis.port=6379
spring.redis.database=0
spring.redis.timeout=3
spring.redis.password=123456
spring.redis.jedis.pool.max-active=10
# 最大空闲 10个
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.max-wait=3

通用缓存Key封装

RedisConfig.java

@Component
@ConfigurationProperties(prefix = "spring.redis")
@Data
public class RedisConfig {

    private String host;
    private int port = 6379;
    //秒
    private int timeout;
    // 使用几号库
    private int database = 0;
    private String password;
    private int maxActive = 10;
    private int maxIdle = 10;
    //秒
    private int maxWait = 3;

}

RedisService.java

@Service
public class RedisService {

    @Autowired
    private JedisPool jedisPool;

    public <T>Boolean set(String key, T value) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String str = beanToString(value);
            jedis.set(key, str);
            return true;
        } finally {
            returnToPool(jedis);
        }
    }

    public <T> T get(String key, Class<T> clazz) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String str = jedis.get(key);
            T t = stringToBean(str,clazz);
            return t;
        } finally {
            returnToPool(jedis);
        }
    }

    private <T> String beanToString(T value) {
        if (value == null) {
            return null;
        }

        Class<?> clazz = value.getClass();
        if (clazz == Integer.class) {
            return "" + value;
        } else if (clazz == String.class) {
            return (String) value;
        } else if (clazz == Long.class) {
            return "" + value;
        } else {
            return JSON.toJSONString(value);
        }
    }

    private <T> T stringToBean(String str, Class<T> clazz) {
        if (StringUtils.isBlank(str) || clazz == null) {
            return null;
        }
        if (clazz == Integer.class) {
            return (T)Integer.valueOf(str);
        } else if (clazz == String.class) {
            return (T)str;
        } else if (clazz == Long.class) {
            return (T) Long.valueOf(str);
        } else {
            return JSON.parseObject(str,clazz);
        }

    }

    /**
     * 将Jedis返回到连接池里
     * @param jedis
     */
    private void returnToPool(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }
}

JedisPoolFactory.java

@Service
public class RedisPoolFactory {

    @Autowired
    private RedisConfig redisConfig;

    @Bean
    public JedisPool jedisPoolFactory() {
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxIdle(redisConfig.getMaxIdle());
        poolConfig.setMaxTotal(redisConfig.getMaxActive());
        poolConfig.setMaxWaitMillis(redisConfig.getMaxWait() * 1000L);
        JedisPool jp = new JedisPool(poolConfig, redisConfig.getHost(), redisConfig.getPort(), redisConfig.getTimeout() * 1000, redisConfig.getPassword(), redisConfig.getDatabase());
        return jp;
    }

}

注意: 本来把RedisPoolFactory中的内容放在RedisService中的,启动会报循环依赖,因为RedisService依赖JedisPool,JedisPool依赖RedisService中的RedisConfig

测试

@Controller
@RequestMapping("/demo")
public class DemoController {

    @Autowired
    private UserService userService;

    @Autowired
    private RedisService redisService;

    @RequestMapping("/redis/get")
    @ResponseBody
    public Result<Long> redisGet() {
        return Result.success(redisService.get("key1", Long.class));
    }

    @RequestMapping("/redis/set")
    @ResponseBody
    public Result<Boolean> redisSet() {
        return Result.success(redisService.set("key2", "hello hmx"));
    }

    @RequestMapping("/db/tx")
    @ResponseBody
    public Result<Boolean> tx() {
        return userService.tx();
    }

    @RequestMapping("/thymeleaf")
    public String thymeleaf(Model model) {
        model.addAttribute("name", "HMX");
        return "hello";
    }

    @ResponseBody
    @RequestMapping("/hello")
    public Result<String> hello() {
        return Result.success("hello springboot");
    }

    @ResponseBody
    @RequestMapping("/error")
    public Result<String> error() {
        return Result.fail(CodeMsg.SERVER_ERROR);
    }

}

完善并优化redis相关类

在redis中set key时可能会出现key被覆盖的操作,所以我们使用前缀来避免这个情况,不同的模块使用不同的前缀
KeyPrefix.java

public interface KeyPrefix {
    int expireSeconds();

    String getPrefix();
}

BasePrefix.java

public abstract class BasePrefix implements KeyPrefix{

    private int expireSeconds;

    private String prefix;

    public BasePrefix(String prefix) {
        this(0, prefix);
    }

    public BasePrefix() {
    }

    public BasePrefix(int expireSeconds, String prefix) {
        this.expireSeconds = expireSeconds;
        this.prefix = prefix;
    }

    /**
     * 默认0代表永不过期
     * @return
     */
    @Override
    public int expireSeconds() {
        return expireSeconds;
    }

    @Override
    public String getPrefix() {
        return getClass().getSimpleName() + ":" + prefix;
    }
}

UserKey.java

public class UserKey extends BasePrefix{

    private UserKey(String prefix) {
        super(prefix);
    }

    public static UserKey getById = new UserKey("id");
    public static UserKey getByName = new UserKey("name");

}

RedisService.java

@Service
public class RedisService {

    @Autowired
    private JedisPool jedisPool;

    /**
     * 设置对象
     * @param prefix
     * @param key
     * @param value
     * @param <T>
     * @return
     */
    public <T>Boolean set(KeyPrefix prefix, String key, T value) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //生成真正的key
            String realKey = prefix.getPrefix() + key;
            String str = beanToString(value);
            int seconds = prefix.expireSeconds();
            if (seconds <= 0) {
                jedis.set(realKey, str);
            } else {
                jedis.setex(realKey, (long)seconds, str);
            }
            return true;
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 获取对象
     * @param prefix
     * @param key
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> T get(KeyPrefix prefix, String key, Class<T> clazz) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //生成真正的key
            String realKey = prefix.getPrefix() + key;
            String str = jedis.get(realKey);
            T t = stringToBean(str,clazz);
            return t;
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 判断key是否存在
     * @param prefix
     * @param key
     * @return
     */
    public Boolean exists(KeyPrefix prefix, String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //生成真正的key
            String realKey = prefix.getPrefix() + key;
            return jedis.exists(realKey);
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 增加值
     * @param prefix
     * @param key
     * @return
     */
    public Long incr(KeyPrefix prefix, String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //生成真正的key
            String realKey = prefix.getPrefix() + key;
            return jedis.incr(realKey);
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 减少值(如果是number类型,则减一,否则置value为0再-1)
     * @param prefix
     * @param key
     * @return
     */
    public Long decr(KeyPrefix prefix, String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //生成真正的key
            String realKey = prefix.getPrefix() + key;
            return jedis.decr(realKey);
        } finally {
            returnToPool(jedis);
        }
    }


    /**
     * 对象转成字符串
     * @param value
     * @param <T>
     * @return
     */
    private <T> String beanToString(T value) {
        if (value == null) {
            return null;
        }

        Class<?> clazz = value.getClass();
        if (clazz == Integer.class) {
            return "" + value;
        } else if (clazz == String.class) {
            return (String) value;
        } else if (clazz == Long.class) {
            return "" + value;
        } else {
            return JSON.toJSONString(value);
        }
    }

    /**
     * 字符串转成对象
     * @param str
     * @param clazz
     * @param <T>
     * @return
     */
    private <T> T stringToBean(String str, Class<T> clazz) {
        if (StringUtils.isBlank(str) || clazz == null) {
            return null;
        }
        if (clazz == Integer.class) {
            return (T)Integer.valueOf(str);
        } else if (clazz == String.class) {
            return (T)str;
        } else if (clazz == Long.class) {
            return (T) Long.valueOf(str);
        } else {
            return JSON.parseObject(str,clazz);
        }

    }

    /**
     * 将Jedis返回到连接池里
     * @param jedis
     */
    private void returnToPool(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }



}

DemoController.java

@Controller
@RequestMapping("/demo")
public class DemoController {

    @Autowired
    private UserService userService;

    @Autowired
    private RedisService redisService;

    @RequestMapping("/redis/get")
    @ResponseBody
    public Result<User> redisGet() {
        return Result.success(redisService.get(UserKey.getById, "2", User.class));
    }

    @RequestMapping("/redis/set")
    @ResponseBody
    public Result<Boolean> redisSet() {
        User user = new User();
        user.setId(2);
        user.setName("hmx2");
        return Result.success(redisService.set(UserKey.getById, "2", user));
    }

    @RequestMapping("/db/tx")
    @ResponseBody
    public Result<Boolean> tx() {
        return userService.tx();
    }

    @RequestMapping("/thymeleaf")
    public String thymeleaf(Model model) {
        model.addAttribute("name", "HMX");
        return "hello";
    }

    @ResponseBody
    @RequestMapping("/hello")
    public Result<String> hello() {
        return Result.success("hello springboot");
    }

    @ResponseBody
    @RequestMapping("/error")
    public Result<String> error() {
        return Result.fail(CodeMsg.SERVER_ERROR);
    }

}

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值