Reggie—Cache优化

文章详细介绍了如何在SpringBoot项目中配置和使用Redis进行缓存管理,包括验证码存储、菜品数据缓存,以及使用SpringCache的条件缓存。此外,还讨论了数据库主从分离的原理和配置,Nginx的使用,如部署静态资源、反向代理和负载均衡,以及前后端分离中的Swagger接口文档生成。
摘要由CSDN通过智能技术生成

一.Redis配置

1.首先通过Git来管理代码

 2.环境搭建

        2.1maven配置

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

        2.2 yml文件配置

        2.3 增加配置类

package com.MskTest.config;


import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * 这个配置文件是用来配置redis序列化器的,通过java存储的redis数据,如果从redis面板直接搜索的话,可能会出现乱码的情况
 * 所以我们设置一个序列化器来处理乱码
 */
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory connectionFactory){
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }
}

3.代码编写

        一. 验证码使用缓存保存

        步骤如下:

        1.在Controller层自动注入RedisTemplate对象,用于操作Redis

        2.在接受验证码的方法中,用redisTemplate接受验证码,使用手机号作为key,同时设置验证时间为5分钟

        3.成功登录之后,删除Redis中的缓存数据

   /**
     * 发送手机验证码
     * @param user
     * @return
     */
    @Autowired
    RedisTemplate redisTemplate;
    @PostMapping("/sendMsg")
    public R<String> sendMsg(@RequestBody User user, HttpSession session) {
        //获取手机号
        String phone = user.getPhone();

        if (StringUtils.isNotEmpty(phone)) {
            //生成随机的4位验证码
            String code = ValidateCodeUtils.generateValidateCode(4).toString();
            log.info("code={}", code);

            //调用阿里云提供的短信服务API完成发送短信
            //SMSUtils.sendMessage("瑞吉外卖", "", phone, code);

            //需要将生成的验证码保存到Session
            //session.setAttribute(phone, code);
            //将验证码用redis保存起来,有效期为5分钟
            redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES);
            return R.success("手机验证码短信发送成功");
        }
        return R.error("手机验证码短信发送失败");

    }
    /**
     * 移动端用户登录
     * @param map
     * @param session
     * @return
     */
    @PostMapping("/login")
    public R<User> login(@RequestBody Map map, HttpSession session) {
        log.info(map.toString());
        //获取手机号
        String phone = map.get("phone").toString();

        //获取验证码
        String code = map.get("code").toString();

        //从Session获取保存的验证码
        //String codeInSession = session.getAttribute(phone).toString();
        //从Redis读取验证码
        Object codeInSession = redisTemplate.opsForValue().get(phone);

        //进行验证码的比对(页面提交的验证码和Session中保存的验证码比对)
        if (codeInSession != null && codeInSession.equals(code)) {
            //如果比对成功,说明登录成功
            LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(User::getPhone, phone);

            User user = userService.getOne(queryWrapper);
            if (user == null) {
                //判断当前手机号对应的用户是否为新用户,如果是新用户就自动完成注册
                user = new User();
                user.setPhone(phone);
                user.setStatus(1);
                userService.save(user);
            }
            session.setAttribute("user",user);
            // 因为登录成功了,所以直接删除Redis中的数据
            redisTemplate.delete(phone);
            return R.success(user);
        }

        return R.error("登录失败");
    }

        二.缓存菜品数据

        思路如下:

        首先是通过菜品ID来查询该数据是否存在,如果不存在就查询MySQL,然后将SQL中的数据存储到Redis中,如果存在则直接从Redis中返回

        需要注意的是:当我们对菜品进行修改或者保存的时候,要删除Redis中的所有数据

   /**
     * 查菜品列表
     * @param categoryId
     * @return
     */
    @GetMapping("/list")
    public R<List<DishDto>> ListCategory(String categoryId){

        List<DishDto> list_dto = null;
        // 首先是确定Redis中是否有我们要查的数据,如果没有就遍历,然后存进去
        Object o = redisTemplate.opsForValue().get(categoryId);
        if (o!=null){
            list_dto = (List<DishDto>) o;
        }else {
            LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
            //通过categoryId查出该数据
            queryWrapper.eq(Dish::getCategoryId, categoryId);
            //同时需要判断该数据是否起售
            queryWrapper.eq(Dish::getStatus, 1);
            List<Dish> list = dishService.list(queryWrapper);
            //最后返回出来
            list_dto = list.stream().map((item) -> {
                DishDto dishDto = new DishDto();
                BeanUtils.copyProperties(item, dishDto);
                Category category = categoryService.getById(categoryId);
                dishDto.setCategoryName(category.getName());
                LambdaQueryWrapper<DishFlavor> queryWrapper1 = new LambdaQueryWrapper<>();
                queryWrapper1.eq(DishFlavor::getDishId, item.getId());
                List<DishFlavor> list_flavor = dishFlavorService.list(queryWrapper1);
                dishDto.setFlavors(list_flavor);
                return dishDto;
            }).collect(Collectors.toList());
            // 将list集合存储到Redis中,方便下次查询
            redisTemplate.opsForValue().set(categoryId,list_dto,1, TimeUnit.HOURS);
        }

            return R.success(list_dto);


    }

    @PutMapping
    public R<String> update(@RequestBody DishDto dishDto){
        dishService.updateBYDTO(dishDto);
        //根据修改的种类删除,精确清理
        redisTemplate.delete(dishDto.getCategoryId());
        return R.success("成功更新");
    }
    @PostMapping
    public R<String> add(@RequestBody DishDto dto){
        dishService.Add(dto);
        redisTemplate.delete(dto.getCategoryId());
        return R.success("成");
    }

4.Spring Cache

@CachePut(value String , key String)
//将数据存储到缓存中,其中value代表的是这个缓存的名称!!!只是名字而已
// 这里的key就是key
    @Cacheable(value = "UserName", key = "#id")
    @GetMapping("/{id}")
    public R<Employee> SelectByID(@PathVariable Long id){
        Employee employee = employeeService.getById(id);
        if (employee!=null){
            return R.success(employee);
        }
        return R.error("出错了");
    }
// 使用这种方法的问题就是,如果sql中如果没有数据,也会缓存

改正之后

    //满足条件不为空的时候,才缓存
    @Cacheable(value = "UserName", key = "#id",condition = "#result!=null")
    @GetMapping("/{id}")
    public R<Employee> SelectByID(@PathVariable Long id){
        Employee employee = employeeService.getById(id);
        if (employee!=null){
            return R.success(employee);
        }
        return R.error("出错了");
    }

使用Redis来操作

       <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
  cache:
    
    redis:
      host: localhost
      port: 6379
      database: 0
      # 这一步是选择第0号数据库,因为Redis提供了16个数据库,这里选则第一个
      time-to-live: 18000

因为Redi中不能使用Condition来接受Result结果,所以需要使用Unless

   @Cacheable(value = "UserName", key = "#id",unless = "#result==null")
    @GetMapping("/{id}")
    public R<Employee> SelectByID(@PathVariable Long id){
        Employee employee = employeeService.getById(id);
        if (employee!=null){
            return R.success(employee);
        }
        return R.error("出错了");
    }

使用Spring Cache配合Redis来查询套餐

步骤如下:

        1.第一步配置maven坐标,同时设置好yml文件中的配置

        2.在启动类中添加启动的配置@EnableCaching

        3.在SetmealController层中进行修改

        4.注意在修改和增加时进行删除操作

    @Cacheable(value = "SetmealName",key = "#setmeal.id",unless = "#result.data == null")
    @GetMapping("/list")
    public R<List<SetmealDto>> list(Setmeal setmeal){
        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Setmeal::getCategoryId,setmeal.getCategoryId());
        List<Setmeal> list = setmealService.list(queryWrapper);
        List<SetmealDto> list_dto = list.stream().map((item) ->{
            SetmealDto setmealDto = new SetmealDto();
            BeanUtils.copyProperties(item,setmealDto);
            LambdaQueryWrapper<SetmealDish> queryWrapper1 = new LambdaQueryWrapper<>();
            queryWrapper1.eq(SetmealDish::getSetmealId,item.getId());
            List<SetmealDish> list_setmealdish = setmealDishService.list(queryWrapper1);
            setmealDto.setSetmealDishes(list_setmealdish);
            return setmealDto;
        }).collect(Collectors.toList());


        return R.success(list_dto);

    }

这里需要注意的是,第一次执行该代码时,出现了序列化的问题,因为这里的result是R接口,所以需要把R对象也序列化

   /**
     * 删除操作
     * @param ids
     * @return
     */
    @CacheEvict(value = "SetmealName",allEntries = true)
    @DeleteMapping
    public R<String> deleteIds(String ids){
        //先解析前端传来的String类型转化为List类型
        List<String> list = Arrays.asList(ids.split(","));
        setmealService.DeleteWithDish(list);
        return R.success("删除成功");

    }

三.主从数据库分离设置

        将数据库分成主库与从库,主库执行写数据,例如增删改,从库执行读数据,查操作,同时两个数据库建立数据同步(MySql主从复制),这样做的目的是为了,可以减少一个数据库的压力,防止数据库崩溃,同时增加了安全性

        MySQL主从复制是一个异步的复制过程,底层是基于Mysql数据库自带的二进制日志,就是一台或多台数据库,从主库,进行日志的复制,然后解析日志,并应用到本身。

主库完成操作,并生成二进制日志,从库通过I/O线程读取该日志文件,并生成自己的日志文件,然后通过SQL线程解析该日志,并最后在自己的数据库中完成日志的所有操作

        主从复制:

        首先,搭建两台服务器,并分别安装好Mysql数据并启动成功

        ip地址分别为:192.168.18.100(master),192.168.18.101(slave)

        第二步修改Mysql数据库的配置文件

        vim /etc/my.cnf

        第三步在主库设置一个用户并赋予从库权限给他

       

         查询主库日志信息

 需要注意的是,此时主库不能再进行任何操作,不然这个日志文件的位置和名称会发生变化

此时在从库操作

        在从库配置文件中修改数据库ID

         然后在从库的mysql中执行以下命令

 最后通过

        show slave status来展示

最后显示这个就完成了

项目测试:

       1.先导入maven坐标

        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.0.0-RC1</version>
        </dependency>

        2.yml文件的配置

spring:
  sharding: 
    datasource:
      names:
        master,slave
      master:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql//192.168.18.100:3306/rw?characterEncoding=utf-8
        username: root
        password: 123123
        
      slave:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql//192.168.18.101:3306/rw?characterEncoding=utf-8
        username: root
        password: 123123
      masterslave:
        load-blance-algorithm-type: round_robin
        name: datasource
        master-data-source-name: master
        slave-data-source-name: slave
        
      props:
        sql:
          show:true

运行就可以了

四.Nginx学习

安装好之后,这是文件夹分类

conf是配置文件,html是存放静态页面的,logs存放日志信息,sbin是存放脚本文件的,用于启动和停止nginx服务

        Nginx命令:

        1.检查conf文件的正确性,在sbin文件夹中,执行./nginx -t

         2.启动nginx,./nginx

        3.可以通过 ps -ef | grep nginx 查看进程

        4.停止nginx服务,./nginx -s stop

        5.修改配置文件之后,要重新加载 ./nginx -s reload

        Nginx配置文件的整体结构:

        

全局块,和Nginx运行相关的全局配置

        

 events块,和网络连接有关的配置

http块 代理,缓存,日志记录,虚拟主机配置

        http块又分为

        http全局块

         Server块

五.Nginx的具体应用

               

        1.部署静态资源:Nginx可以作为静态web服务器来部署静态资源,相对于Tomcat来说,Nginx的效率更高

        步骤如下:

        1.将需要的静态资源复制到nginx的Html包下

        2.启动nginx,即可

配置如下

         2. 反向代理

        要了解反向代理,必须先知道什么是正向代理

 简单的说,就是用户在访问目标服务器时,会先通过一个代理服务器来转发请求,这里的代理服务器有点像自己家的路由器什么的

       

 反向代理与正向代理的不同在于,用户直接访问反向代理的这个服务器就可以返回请求了,不需要知道目标服务器,这个有点感觉像直接访问自己的路由器,然后路由器直接返回我要的数据。

 这里的listen是用来监听端口号的

下面是配置反向代理的路径

3.负载均衡

        随着目前前端资源的增多,一台服务器可能处理不过来那么多的数据,所以将资源分别配置好几台服务器,这个服务器群就是集群,然后负载均衡和上面的反向代理很像的地方就在于,反向代理只是一个服务器,但是负载均衡是很多服务器

         配置文件如下:

        

 上面的upstream是用来存放服务器IP地址以及对应的端口

 下面的server是用来代理上面配置好的集群的IP地址的

        负载均衡的各种方式  

六.前后端分离

        1.Swagger

        用于生成接口文档文件,使用框架会快一点

        步骤如下:

        1.导入Maven坐标

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>3.0.2</version>
        </dependency>

        2.编写Swagger配置类

        在WebMvcConfig中添加配置注解

package com.MskTest.config;

import com.MskTest.Common.JacksonObjectMapper;
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.List;

@Configuration
@Slf4j
@EnableSwagger2
@EnableKnife4j
public class WebMvcConfigurationSupport extends org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport {

    /**
     * 所有的静态页面在resources下,需要放行,所以编写一个配置文件
     * @param registry
     */
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("开始静态映射");
        //第一步使用addResourceHandler来标识什么情况下要映射,第二步addResourceLocations来标识映射到哪里
        registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
        registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
    }


    /**
     * 增加一个消息转换器
     * @param converters
     */
   @Override
   protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
       //1.创建一个消息转换器
       MappingJackson2HttpMessageConverter MessageConverter = new MappingJackson2HttpMessageConverter();
       //2,设置对象转换器,底层使用Jackson将Java对象转成json
       MessageConverter.setObjectMapper(new JacksonObjectMapper());
       //3.将上面设置的转换器增加到转换器中,并放置第一位,优先使用该转换器
       converters.add(0,MessageConverter);
   }

}

 同时在该WebMvcConfig类下编写这两个方法

   @Bean
    public Docket createRestApi(){
       return new Docket(DocumentationType.SWAGGER_2)
               .apiInfo(apiInfo())
               .select()
               .apis(RequestHandlerSelectors.basePackage("com.MskTest.reggie.controller"))
               .paths(PathSelectors.any())
               .build();
   }
   
   private ApiInfo apiInfo(){
       return new ApiInfoBuilder()
               .title("瑞吉外卖")
               .version("1.0")
               .description("API文档")
               .build();
       
   }

        3.设置静态资源映射

        

    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("开始静态映射");
        registry.addResourceHandler("doc.html").addResourceLocations("classpath:META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:META-INF/resources/webjars/");

        4.设置过滤器

        

七.Swagger的常用注解

               

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值