基于Springboot+MybatisPlus+Redis+Rabitmq的高并发商品秒杀系统

本文是基于乐字节的秒杀系统总结出来的笔记,纯属个人兴趣,视频原文链接:视频链接
项目源码地址:项目地址

系统介绍

秒杀,对我们来说,都不是一个陌生的东西。每年的双11,618以及时下流行的直播等等。秒杀然而,这对于我们系统而言是一个巨大的考验。那么,如何才能更好地理解秒杀系统呢?我觉得作为一个程序员,你首先需要从高维度出发,从整体上思考问题。在我看来,秒杀其实主要解决两个问题,一个是并发读,一个是并发写。并发读的核心优化理念是尽量减少用户到服务端来“读”数据,或者让他们读更少的数据;并发写的处理原则也一样,它要求我们在数据库层面独立出来一个库,做特殊的处理。另外,我们还要针对秒杀系统做一些保护,针对意料之外的情况设计兜底方案,以防止最坏的情况发生。

其实,秒杀的整体架构可以概括为“稳、准、快”几个关键字。就是整个系统架构要满足高可用,流量符合预期时肯定要稳定,就是超出预期时也同样不能掉链子,你要保证秒杀活动顺利完成,即秒杀商品顺利地卖出去,这个是最基本的前提。然后就是“准”,就是秒杀 10 台 iPhone,那就只能成交 10 台,多一台少一台都不行。一旦库存不对,那平台就要承担损失,所以“准”就是要求保证数据的一致性。最后再看“快”,“快”其实很好理解,它就是说系统的性能要足够高,否则你怎么支撑这么大的流量呢?不光是服务端要做极致的性能优化,而且在整个请求链路上都要做协同的优化,每个地方快一点,整个系统就完美了。

所以从技术角度上看“稳、准、快”,就对应了我们架构上的高可用、一致性和高性能的要求。

  • 高性能。 秒杀涉及大量的并发读和并发写,因此支持高并发访问这点非常关键。对应的方案比如动静分离方案、热点的发现与隔离、请求的削峰与分层过滤、服务端的极致优化。
  • 一致性。 秒杀中商品减库存的实现方式同样关键。可想而知,有限数量的商品在同一时刻被很多倍的请求同时来减库存,减库存又分为“拍下减库存”“付款减库存”以及预扣等几种,在大并发更新的过程中都要保证数据的准确性,其难度可想而知。
  • 高可用。 现实中总难免出现一些我们考虑不到的情况,所以要保证系统的高可用和正确性,我们还要设计一个 PlanB 来兜底,以便在最坏情况发生时仍然能够从容应对。

前置介绍

  1. 基于Springboot+MybatisPlus+Redis+Rabitmq的高并发商品秒杀系统。
  2. MybatisPlus:MyBatis-Plus简称MP,是国内的针对MyBatis制作的一个增强框架,对原生MyBatis无侵入,只做增强,目的是可以简化简单的CRUD操作,提高开发效率。简单的CRUD完全不需要写SQL语句,也不必编写持久层接口,仅仅需要继承JpaRepository接口即可。
  3. Redis是一个高速缓存数据库,是一种key-value(键值对)形式的存储系统,非关系型数据库。Redis的数据 是放在内存里的,所以读写会很快,Redis才能实现持久化(两种实现方式)。
  4. RabbitMQ是由Erlang语言开发,基于AMQP协议(Advanced Message Queuing Protocol 高级消息队列协议)实现的消息队列,它是一种应用程序之间的通信方法,消息队列在实际开发应用中有着非常广泛的使用。

环境搭建

1.依赖注入

首先,在idea新建一个java项目,利用spring Initializr创建一个spring工程,在依赖那里勾选web、thymeleaf、mysql、lombok等依赖。

 <!-- thymeleaf组件 -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-thymeleaf</artifactId>
         </dependency>
         <!-- web组件 -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
         <!-- mysql依赖-->
         <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
             <scope>runtime</scope>
         </dependency>
         <!--lombok 依赖-->
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <optional>true</optional>
         </dependency>
         <!-- test组件 -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
         </dependency>

2.修改配置文件application.yml

spring:
#thymeleaf配置
  thymeleaf:
  #关闭缓存
    cache: false
    prefix: classpath:/static/web/

 #数据源配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seckill?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
    username: root
    password: root
    hikari:
      # 连接池名
      pool-name: DateHikariCP
      # 最小空闲连接数
      minimum-idle: 5
      # 空闲连接存活最大时间,默认600000(10分钟)
      idle-timeout: 180000
      # 最大连接数,默认10
      maximum-pool-size: 10
      # 从连接池返回的连接的自动提交
      auto-commit: true
      # 连接最大存活时间,0表示永久存活,默认1800000(30分钟)
      max-lifetime: 180000
      # 连接超时时间,默认30000(30秒)
      connection-timeout: 30000
      # 测试连接是否可用的查询语句
      connection-test-query: SELECT 1

前期开发准备

1.逆向工程

逆向工程简单来说就是,我们先创建好了数据库,然后根据数据库使用Mybatis-Plus的生成器代码自动帮我们生成我们需要的类:controller、service、mapper等等,不需要我们自己再手动配置,省去了不少麻烦。

首先添加Mybatis-Plus依赖和代码生成器依赖,这两个依赖都可以在Mybatis-Plus官网中找到。由于此代码生成器使用的是freemarker模板引擎,因此也需要将freemarker的模板引擎的依赖一并导入。

1.1 依赖注入
        <!-- mybatis-plus依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
        </dependency>
        <!-- 代码生成器 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.0</version>
        </dependency>
        <!-- freemarker模板引擎 -->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.30</version>
        </dependency>
1.2 修改配置文件application.yml

在配置文件中添加Mybatis-plus配置信息

#Mybatis-plus配置
mybatis-plus:
 #配置Mapper映射文件
  mapper-locations: classpath*:/mapper/*Mapper.xml
 #配置MyBatis数据返回类型别名(默认别名是类名)
  type-aliases-package: com.yang.seckill.pojo
  
#Mybatis SQL 打印(方法接口所在的包,不是Mapper.xml所在的包)
logging:
  level:
     com.yang.seckill.mapper: debug
1.3 代码生成器
public class CodeGenerator {
   
    
    public static String scanner(String tip) {
   
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入" + tip + ":");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
   
            String ipt = scanner.next();
            if (StringUtils.isNotBlank(ipt)) {
   
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }
    public static void main(String[] args) {
   
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();
        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java");
        //作者
        gc.setAuthor("yang");
        //打开输出目录
        gc.setOpen(false);
        //xml开启 BaseResultMap
        gc.setBaseResultMap(true);
        //xml 开启BaseColumnList
        gc.setBaseColumnList(true);
        //日期格式,采用Date
        gc.setDateType(DateType.ONLY_DATE);
        mpg.setGlobalConfig(gc);
        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/seckill?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia" +"/Shanghai");
        // dsc.setSchemaName("public");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("root");
        mpg.setDataSource(dsc);

        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent("com.yang.seckill")
                .setEntity("pojo")
                .setMapper("mapper")
                .setService("service")
                .setServiceImpl("service.impl")
                .setController("controller");
        mpg.setPackageInfo(pc);

        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
   
            @Override
            public void initMap() {
   
                // to do nothing
                Map<String,Object> map = new HashMap<>();
                map.put("date1","1.0.0");
                this.setMap(map);
            }
        };

        // 如果模板引擎是 freemarker
        String templatePath = "/templates/mapper.xml.ftl";
        // 如果模板引擎是 velocity
        // String templatePath = "/templates/mapper.xml.vm";

        // 自定义输出配置
        List<FileOutConfig> focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
   
            @Override
            public String outputFile(TableInfo tableInfo) {
   
                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
                return projectPath + "/src/main/resources/mapper/" +
                        tableInfo.getEntityName() + "Mapper"
                        + StringPool.DOT_XML;
            }
        });
        /*
        cfg.setFileCreate(new IFileCreate() {
            @Override
            public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
                // 判断自定义文件夹是否需要创建
                checkDir("调用默认方法创建的目录,自定义目录用");
                if (fileType == FileType.MAPPER) {
                    // 已经生成 mapper 文件判断存在,不想重新生成返回 false
                    return !new File(filePath).exists();
                }
                // 允许生成模板文件
                return true;
            }
        });
        */
        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);

        // 配置模板
        TemplateConfig templateConfig = new TemplateConfig()
                .setEntity("templates/entity.java")
                .setMapper("templates/mapper.java")
                .setService("templates/service.java")
                .setServiceImpl("templates/serviceImpl.java")
                .setController("templates/controller.java");

        templateConfig.setXml(null);
        mpg.setTemplate(templateConfig);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        //strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
        strategy.setEntityLombokModel(true);
        //strategy.setRestControllerStyle(true);
        // 公共父类
        // strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
        // 写于父类中的公共字段
        //strategy.setSuperEntityColumns("id");
        strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
        strategy.setControllerMappingHyphenStyle(true);

        strategy.setTablePrefix("t_");
        mpg.setStrategy(strategy);
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
    }

}

2.数据库创建

  • 数据库创建
create database seckill;
  • 用户表
CREATE TABLE t_user(
	`id` BIGINT(20) NOT NULL COMMENT '用户ID shoujihaoma',
	`nickname` VARCHAR(255) not NULL,
	`pasword`  VARCHAR(32) DEFAULT NULL COMMENT 'MD5二次加密',
	`slat` VARCHAR(10) DEFAULT NULL,
	`head` VARCHAR(128) DEFAULT NULL COMMENT '头像',
	`register_date` datetime DEFAULT NULL COMMENT '注册时间',
	`last_login_date` datetime DEFAULT NULL COMMENT '最后一次登录时间',
	`login_count` int(11) DEFAULT '0' COMMENT '登录次数',
	PRIMARY KEY(`id`)
);
  • 商品表
create table `t_goods`(
	`id` BIGINT(20) not null AUTO_INCREMENT COMMENT '商品id',
	`goods_name` VARCHAR(16) DEFAULT NULL COMMENT '商品名称',
	`goods_title` VARCHAR(64) DEFAULT NULL COMMENT '商品标题',
	`goods_img` VARCHAR(64) DEFAULT NULL COMMENT '商品图片',
	`goods_detail` LONGTEXT  COMMENT '商品描述',
	`goods_price` DECIMAL(10, 2) DEFAULT '0.00' COMMENT '商品价格',
	`goods_stock` INT(11) DEFAULT '0' COMMENT '商品库存,-1表示没有限制',
	PRIMARY KEY(`id`)
)ENGINE = INNODB AUTO_INCREMENT = 3 DEFAULT CHARSET = utf8mb4;
  • 订单表
CREATE TABLE `t_order` (
	`id` BIGINT(20) NOT NULL  AUTO_INCREMENT COMMENT '订单ID',
	`user_id` BIGINT(20) DEFAULT NULL COMMENT '用户ID',
	`goods_id` BIGINT(20) DEFAULT NULL COMMENT '商品ID',
	`delivery_addr_id` BIGINT(20) DEFAULT NULL  COMMENT '收获地址ID',
	`goods_name` VARCHAR(16) DEFAULT NULL  COMMENT '商品名字',
	`goods_count` INT(20) DEFAULT '0'  COMMENT '商品数量',
	`goods_price` DECIMAL(10,2) DEFAULT '0.00'  COMMENT '商品价格',
	`order_channel` TINYINT(4) DEFAULT '0'  COMMENT '1 pc,2 android, 3 ios',
	`status` TINYINT(4) DEFAULT '0'  COMMENT '订单状态,0新建未支付,1已支付,2已发货,3已收货,4已退货,5已完成',
	`create_date` datetime DEFAULT NULL  COMMENT '订单创建时间',
	`pay_date` datetime DEFAULT NULL  COMMENT '支付时间',
	PRIMARY KEY(`id`)
)ENGINE = INNODB AUTO_INCREMENT=12 DEFAULT CHARSET = utf8mb4;
  • 秒杀商品表
CREATE TABLE `t_seckill_goods`(
	`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '秒杀商品ID',
	`goods_id` BIGINT(20) NOT NULL COMMENT '商品ID',
	`seckill_price` DECIMAL(10,2) NOT NULL COMMENT '秒杀家',
	`stock_count` INT(10) NOT NULL  COMMENT '库存数量',
	`start_date` datetime NOT NULL  COMMENT '秒杀开始时间',
	`end_date` datetime NOT NULL COMMENT '秒杀结束时间',
	PRIMARY KEY(`id`)
)ENGINE = INNODB AUTO_INCREMENT=3 DEFAULT CHARSET = utf8mb4;
  • 秒杀订单表
CREATE TABLE `t_seckill_order` (
	`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '秒杀订单ID',
	`user_id` BIGINT(20) NOT NULL  COMMENT '用户ID',
	`order_id` BIGINT(20) NOT NULL  COMMENT '订单ID',
	`goods_id` BIGINT(20) NOT NULL  COMMENT '商品ID',
	PRIMARY KEY(`id`)
)ENGINE = INNODB AUTO_INCREMENT=3 DEFAULT CHARSET = utf8mb4;

3.工具类

3.1 2次MD5加密

为了提高用户密码的安全性,密码从前端传入后端,再从后端传入数据库的过程中要经历两次加密过程。此项目中,前端页面js代码已经写好了,在前端已经历一次加密过程,在后端的代码也有两次加密过程,只不过第一次加密的方式与前端代码的加密方式相同,此处只是为了测试加密的准确性,第二次加密是将前端传入后端的密码再次进行加密的过程,此密码将存入数据库,并保留加密的盐值,为后续的密码验证作铺垫。在正式生成中,盐值往往是随机生成的,此项目仅作为学习为主,因此设置所以的盐值为静态盐值。

使用MD5加密,首先要注入相关的依赖:

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
</dependency>

2次MD5加密:

/**
 * MD5工具类
 */
@Component
public class MD5Util {
   

    public static String md5(String src){
   
        return DigestUtils.md5Hex(src);
    }

    public static  final String salt = "1a2b3c4d";

    /**
     * 第一次加密:前端输入的密码加密转为后端密码
     * @param inputPass 前端输入的密码
     * @return 第一次加密后的密码
     */
    public static String inputPassToBackendPass(String inputPass){
   
        String str = "" + salt.charAt(0) + salt.charAt(2) + inputPass + salt.charAt(5) + salt.charAt(4) ;
        return md5(str);
    }

    /**
     * 第二次加密:后端加密后的密码再次加密存入数据库
     * @param backendPass 后端加密后的密码
     * @param salt 存入数据库的盐值
     * @return 第二次加密后的密码
     */
    public static String backendPassToDBPass(String backendPass, String salt){
   
        String str = "" + salt.charAt(0) + salt.charAt(2) + backendPass + salt.charAt(5) + salt.charAt(4);
        return md5(str);
    }


    /**
     * 直接前端输入的密码经过2次加密后存入数据库的密码
     * @param inputPass 前端输入的密码
     * @param salt 存入数据库的盐值
     * @return 存入数据库的密码
     */
    public static String inputPassToDBPass(String inputPass, String salt){
   
        String backendPass = inputPassToBackendPass(inputPass);
        String dbPass = backendPassToDBPass(backendPass,salt);
        return dbPass;
    }


    public static void main(String[] args) {
   
        System.out.println("前端进行加密后的密码:" + inputPassToBackendPass("123456"));
        System.out.println("后端传入数据库进行加密后的密码:" + backendPassToDBPass(inputPassToBackendPass("123456"),"1a2b3c4d"));
        System.out.println("经过2次加密后的密码:" + inputPassToDBPass("123456","1a2b3c4d"));
    }

}
3.2 电话号码校验类
public class ValidatorUtil {
   

    private static final Pattern mobile_pattern = Pattern.compile("[1]([3-9])[0-9]{9}$");

    public static boolean isMobile(String mobile){
   
        if (StringUtils.isEmpty(mobile)){
   
            return false;
        }
        Matcher matcher = mobile_pattern.matcher(mobile);
        return matcher.matches();
    }

}
3.3 异常枚举

为什么会出现异常?比如,用户在进行注册时可能会产生用户名被占用的错误,这时需要抛出一个异常。

  • 公共返回对象枚举

创建一个公共返回对象枚举,列出项目测试过程中可能遇到的异常情况,因项目刚开始,所以只罗列了登录过程中可能出现的异常,后续业务中所出现的异常将后续添加。

/**
 * 公共返回对象枚举
 */
@Getter
@ToString
@AllArgsConstructor
public enum  RespBeanEnum {
   

    //通用
    SUCCESS(200,"SUCCESS"),
    ERROR(500,"服务端异常"),

    //登录模块
    LOGIN_ERROR(500218,"用户名或密码错误"),
    MOBILE_ERROR(500211,"手机号码格式错误"),

    //绑定异常
    BIND_ERROR(500212,"参数校验异常");

    private final Integer code;
    private final String message;

}
  • 公共返回对象

设置公共返回对象成功与失败的方法。

/**
 * 公共返回对象
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RespBean {
   

   private long code;
   private String message;
   private Object obj;

    /**
     * 成功返回结果
     * @return
     */
   public static RespBean success(){
   
       return new RespBean(RespBeanEnum.SUCCESS.getCode(),RespBeanEnum.SUCCESS.getMessage(),null);
   }

    /**
     * 成功返回结果
     * @return
     */
   public static RespBean success(Object obj){
   
       return new RespBean(RespBeanEnum.SUCCESS.getCode(), RespBean.success().getMessage(),obj);
   }

    /**
     * 失败返回结果
     * @return
     */
   public static RespBean error(RespBeanEnum respBeanEnum){
   
       return new RespBean(respBeanEnum.getCode(),respBeanEnum.getMessage(),null);
   }

    /**
     * 失败返回结果
     * @return
     */
    public static RespBean error(RespBeanEnum respBeanEnum, Object obj){
   
        return new RespBean(respBeanEnum.getCode(),respBeanEnum.getMessage(),obj);
    }
    
}
3.4 生成用户工具类

在后面进行压力测试的时候,要生成很多用户对接口进行访问,此工具类能够按照生成规则生成大量用户信息:

/**
 * 生成用户工具类
 */
public class UserUtil {
   
    private static void createUser(int count) throws Exception {
   
        List<User> users = new ArrayList<>(count);
        //生成用户
        for (int i = 0; i < count; i++) {
   
            User user = new User();
            user.setId(13000000000L + i);
            user.setLoginCount(1);
            user.setNickname("user" + i);
            user.setRegisterDate(new Date());
            user.setSlat("1a2b3c4d");
            user.setPassword(MD5Util.inputPassToDBPass("123456", user.getSlat()));
            users.add(user);
        }
        System.out.println("create user");
         // //插入数据库
         Connection conn = getConn();
         String sql = "insert into t_user(login_count, nickname, register_date, slat, password, id)values(?,?,?,?,?,?)";
         PreparedStatement pstmt = conn.prepareStatement(sql);
         for (int i = 0; i < users.size(); i++) {
   
         	User user = users.get(i);
         	pstmt.setInt(1, user.getLoginCount());
         	pstmt.setString(2, user.getNickname());
         	pstmt.setTimestamp(3, new Timestamp(user.getRegisterDate().getTime()));
         	pstmt.setString(4, user.getSlat());
         	pstmt.setString(5, user.getPassword());
         	pstmt.setLong(6, user.getId());
         	pstmt.addBatch();
         }
         pstmt.executeBatch();
         pstmt.close();
         conn.close();
         System.out.println("insert to db");
        //登录,生成userTicket
        String urlString = "http://localhost:8080/login/doLogin";
        File file = new File("C:\\Users\\Administrator\\Desktop\\config.txt");
        if (file.exists()) {
   
            file.delete();
        }
        RandomAccessFile raf = new RandomAccessFile(file, "rw");
        file.createNewFile();
        raf.seek(0);
        for (int i = 0; i < users.size(); i++) {
   
            User user = users.get(i);
            URL url = new URL(urlString);
            HttpURLConnection co = (HttpURLConnection) url.openConnection();
            co.setRequestMethod("POST");
            co.setDoOutput(true);
            OutputStream out = co.getOutputStream();
            String params = "mobile=" + user.getId() + "&password=" + MD5Util.inputPassToBackendPass("123456");
            out.write(params.getBytes());
            out.flush();
            InputStream inputStream = co.getInputStream();
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            byte buff[] = new byte[1024];
            int len = 0;
            while ((len = inputStream.read(buff)) >= 0) {
   
                bout.write(buff, 0, len);
            }
            inputStream.close();
            bout.close();
            String response = new String(bout.toByteArray());
            ObjectMapper mapper = new ObjectMapper();
            RespBean respBean = mapper.readValue(response, RespBean.class);
            String userTicket = ((String) respBean.getObj());
            System.out.println("create userTicket : " + user.getId());

            String row = user.getId() + "," + userTicket;
            raf.seek(raf.length());
            raf.write(row.getBytes());
            raf.write("\r\n".getBytes());
            System.out.println("write to file : " + user.getId());
        }
        raf.close();

        System.out.println("over");
    }

    private static Connection getConn() throws Exception {
   
        String url = "jdbc:mysql://localhost:3306/seckill?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai";
        String username = "root";
        String password = "root";
        String driver = "com.mysql.cj.jdbc.Driver";
        Class.forName(driver);
        return DriverManager.getConnection(url, username, password);
    }

    public static void main(String[] args) throws Exception {
   
        createUser(500);
    }
}
3.5 Json工具类

Json工具类内部有众多方法,有将对象转换成json字符串、将字符串转换为对象等方法:

/**
 * Json工具类
 */
public class JsonUtil {
   
    private static ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 将对象转换成json字符串
     *
     * @param obj
     * @return
     */
    public static String object2JsonStr(Object obj) {
   
        try {
   
            return objectMapper.writeValueAsString(obj)
  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spring Boot是一种用于快速开发Java应用程序的框架,它提供了许多便捷的功能和特性,如自动配置、简化的部署等。MyBatis Plus是一个MyBatis的增强工具,可以更便捷地操作数据库。Shiro是一个强大的Java安全框架,可以提供身份认证、授权、会话管理等安全相关的功能。Redis是一个高性能的键值对存储系统,常用于缓存、分布式锁等场景。Template是Spring框架中用于渲染视图的模板引擎。 综上所述,Spring BootMyBatis Plus、Shiro、Redis Template一起使用可以构建一个功能强大、高效、安全的应用程序。Spring Boot提供了便捷的开发环境和配置,使得整个项目的搭建和部署更加简单。MyBatis Plus提供了简洁的API,可以更方便地操作数据库,减少了开发人员的工作量。Shiro可以提供安全相关的功能,保护应用程序的数据和资源安全。Redis作为缓存可以提高应用程序的访问速度,使用分布式锁等功能可以保证数据一致性和并发控制。Template可以方便地渲染视图,使得前端页面开发更加简单。 总之,Spring BootMyBatis Plus、Shiro、Redis Template的集成可以帮助开发人员快速构建功能完善、高效、安全的应用程序。它们各自的特性和功能相互配合,提供了一种快速开发的解决方案,为开发人员提供了更好的开发体验。 ### 回答2: Spring Boot是一个用于简化Spring应用程序开发的框架,它提供了自动配置和快速开发的特性。MyBatis Plus是基于MyBatis的增强工具,它简化了与数据库的交互,提供了很多便捷的方法和功能。Shiro是一个用于身份认证和授权的安全框架,它可以帮助我们实现用户身份认证、权限控制和会话管理的功能。Redis是一个开源的内存数据库,它提供了对数据的高速缓存和持久化存储的功能。Redis Template是SpringRedis进行操作的一个封装工具,它提供了一系列的方法用于对Redis进行增删改查的操作。 使用Spring Boot可以简化项目的搭建和配置,通过自动配置可以省去很多繁琐的步骤。使用MyBatis Plus可以不用编写繁琐的SQL语句,只需定义实体类和Mapper接口即可完成数据库的操作。使用Shiro可以轻松实现用户的身份认证和权限控制,保障系统的安全性。使用Redis可以提高系统的性能,通过缓存机制减少数据库的访问次数。 结合起来使用,可以构建一个高效、安全和可靠的Web应用程序。Spring Boot提供了集成MyBatis Plus和Shiro的插件,可以方便地使用这两个框架。Redis Template可以与Spring Boot的缓存框架一起使用,实现高速缓存。通过这些技术的使用,我们可以快速开发出功能完善的Web应用,提高开发效率和系统性能。 ### 回答3: SpringBoot是Java中一个开源的应用程序框架,它可以简化开发过程,提供了许多开箱即用的功能和库,使得开发者能够更快速地构建应用程序。 MyBatisPlus是一个基于MyBatis的增强工具,它提供了更方便、更强大的操作数据库的功能,大大简化了数据库操作的代码。 Shiro是Java中一个功能强大且易于使用的安全框架,它提供了身份验证、授权、加密、会话管理等功能,可以帮助开发者实现应用程序的安全控制。 Redis是一个开源的内存数据库,它可以用作缓存、消息队列等,具有高性能、持久化、分布式等特点。 Template是Spring框架中的一个模板引擎,它支持HTML、XML、JSON等多种模板语言,用于将动态数据渲染到模板中,生成最终的静态页面或其他格式的文件。 综合以上技术,可以构建一个高效、安全、可靠的Web应用。使用SpringBoot可以简化项目的搭建和配置,MyBatisPlus可以方便地操作数据库,Shiro可以保护应用程序的安全,Redis可以提高系统的性能和可扩展性,Template可以方便地生成动态页面。 例如,我们可以使用SpringBoot搭建一个基于MyBatisPlus的后台管理系统,使用Shiro完成用户的身份验证和权限控制,使用Redis作为缓存存储用户的会话信息,使用Template将动态数据渲染到页面中。这样的系统具有良好的性能和安全性,提供了友好的用户界面和丰富的功能。 总之,SpringBootMyBatisPlus、Shiro、Redis和Template等技术可以共同协作,帮助我们构建出高质量、高效率的应用程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值