瑞吉外卖项目学习心得

一、登录功能

1、md5加密算法

是由spring提供的工具

import org.springframework.util.DigestUtils;
//1.将页面提交的password进行md5加密处理
        String password = employee.getPassword();
        password = DigestUtils.md5DigestAsHex(password.getBytes());
2、配置请求过滤器
2.1 实现Filter类
/**
 * 过滤器注解urlPatterns = "/*"表示过滤掉所有请求
 */
@WebFilter(filterName = "LoginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {
    //路径匹配器,支持通配符
    private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        //1.获取当前的请求url
        String requestURI = request.getRequestURI();
        //以下是不需要处理的请求url,直接放行
        String urls[] = new String[]{
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**"
        };
        //2.判断本次请求是否需要处理
        boolean check = check(urls, requestURI);
        //3.如果不需要处理则直接放行
        if(check){
            log.info("本次请求不需要处理:{}",request.getRequestURI());
            filterChain.doFilter(request,response);
            return;
        }
        //4.查看登录状态,如果已登录则放行
        if(request.getSession().getAttribute("employee") != null){
            log.info("用户已登录,id为:"+request.getSession().getAttribute("employee"));
            filterChain.doFilter(request,response);
            return;
        }
        log.info("用户未登录!");
        //5.如果未登录则返回未登录结果,通过输出流方式响应给前端
        //如果未登录,只要返回NOTLOGIN字符串,页面跳转由前端js来控制
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
        return;
    }

    /**
     * 判断本次请求是否需要处理封装方法
     * @param urls
     * @param requestURI
     * @return
     */
    public boolean check(String[] urls,String requestURI){
        for (String url : urls) {
            //如果match为true,说明当前请求路径为不需要处理的请求,则说明路径匹配上
            boolean match = PATH_MATCHER.match(url, requestURI);
            if(match){
                return true;
            }
        }
        return false;
    }
}

2.2 在启动类加@ServletComponentScan
@Slf4j//lombok提供的日志
@SpringBootApplication//启动类
@ServletComponentScan//开启过滤器
public class ReggieApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReggieApplication.class,args);
        log.info("项目启动成功!");
    }
}
3、日志输出@Slf4j

这是由lombok提供的日志输出,方便调试。

@Slf4j//lombok提供的日志
@SpringBootApplication//启动类
@ServletComponentScan//开启过滤器
public class ReggieApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReggieApplication.class,args);
        log.info("项目启动成功!");
    }
}
4、全局异常处理
@ControllerAdvice

@ControllerAdvice是@Controller注解的一个增强,这个注解是Spring里面的东西,可以处理全局异常。当然在Spring Boot中也可以使用,但是Spring Boot中有它全自动化配置的异常处理,因为是全自动化的,因此也可以自己定制,比如定制它的异常页面,异常信息提示,异常视图。这里的话主要看一下,这个注解怎么用。它主要有一下几个功能:

  • 处理全局异常
  • 预设全局数据
  • 请求参数预处理
全局异常处理格式参考
//处理全局异常
@ControllerAdvice(annotations = {RestController.class, Controller.class})
//json格式返回
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
    //捕获异常SQLIntegrityConstraintViolationException(SQL异常)
    @ExceptionHandler({SQLIntegrityConstraintViolationException.class})
    public R<String> exceptionHandler(SQLIntegrityConstraintViolationException e){
        log.error(e.getMessage());
        //由于数据库中账号(username)为unique,所以这里捕获的是SQL异常
        //查看SQL异常信息里面是否包含"Duplicate entry"字符
        if(e.getMessage().contains("Duplicate entry")){
            //将捕获到的SQL异常信息放到split里
            String[] split = e.getMessage().split(" ");
            //将split数组里的字符串进行分割,split[2]里存得是账号信息
            String msg = split[2] + "已存在";
            return R.error(msg);
        }
        return R.error("未知错误!");
    }
}

二、员工管理

5、Mybatis-Plus分页查询

MybatisPlusInterceptor
MybatisPlusInterceptor是一系列的实现InnerInterceptor的拦截器链,也可以理解为一个集合。可以包括如下的一些拦截器:

自动分页: PaginationInnerInterceptor(最常用)
多租户: TenantLineInnerInterceptor
动态表名: DynamicTableNameInnerInterceptor
乐观锁: OptimisticLockerInnerInterceptor
sql性能规范: IllegalSQLInnerInterceptor
防止全表更新与删除: BlockAttackInnerInterceptor

5.1 分页插件配置类
/**
 * 配置MP的分页插件
 */
@Configuration
public class MybatisPlusPageConfig {
    @Bean//交给spring管理
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}
5.2 分页查询
/**
     * 员工信息分页查询
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @GetMapping("/page")
    public R<Page> page(int page,int pageSize,String name){
        log.info("page={},pageSize={},name={}",page,pageSize,name);
        //构造分页构造器
        Page pageInfo = new Page(page,pageSize);
        //构造条件构造器
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        //添加过滤条件(模糊查询)
        queryWrapper.like(!StringUtils.isEmpty(name),Employee::getName,name);
        //添加排序条件
        queryWrapper.orderByDesc(Employee::getUpdateTime);
        //执行查询
        employeeService.page(pageInfo,queryWrapper);
        return R.success(pageInfo);
    }

5.3 分页前端代码
<el-pagination
        class="pageList"
        :page-sizes="[10, 20, 30, 40]"
        :page-size="pageSize"
        layout="total, sizes, prev, pager, next, jumper"
        :total="counts"
        :current-page.sync="page"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      ></el-pagination>

new Vue({
        el: '#member-app',
        data() {
          return {
             input: '',
             counts: 0,
             page: 1,
             pageSize: 10,
			}
		methods:{
			handleSizeChange (val) {
            	this.pageSize = val
            	this.init()
          		},
          	 handleCurrentChange (val) {
            	this.page = val
            	this.init()
          		}
			}

6、禁用、启用 Id 精度丢失bug
6.1 bug现象

用户信息通过id进行修改,但是浏览器传过来的id与数据库中的id匹配不一致,无法进行用户信息修改。

id精度丢失,由于数据库中的id为长整型,浏览器只能精确到前16位,后几位按四舍五入取整,有精度损失。

6.2 解决方案:

我们可以在服务端给页面响应json数据时进行处理,将Long型数据全部转换为String字符串。

6.3 代码修复

具体实现步骤:

1)提供对象转换器jacksonObjectMapper,基于jackson进行java对象到json数据的转换。

/**
 * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
 * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
 */
public class JacksonObjectMapper extends ObjectMapper {

    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    public JacksonObjectMapper() {
        super();
        //收到未知属性时不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

        //反序列化时,属性不存在的兼容处理
        this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);


        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))

                .addSerializer(BigInteger.class, ToStringSerializer.instance)
                .addSerializer(Long.class, ToStringSerializer.instance)
                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));

        //注册功能模块 例如,可以添加自定义序列化器和反序列化器
        this.registerModule(simpleModule);
    }
}

2)在WebMvcConfig配置类中扩展Spring mvc的消息转换器,在此消息转换器中使用提供的对象转换器进行java对象到json数据的转换。

/**
     * 扩展mvc框架的消息转换器
     * @param converters
     */
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        //创建消息转换器对象
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        //设置对象转换器,底层使用jackson将java对象转为json
        messageConverter.setObjectMapper(new JacksonObjectMapper());
        //将上面的消息转换器对象追加到mvc框架的转换器集合中
        converters.add(0,messageConverter);
    }

三、分类管理

7、Mybatis-Plus自动填充公共字段
7.1使用注解@TableField
	@TableField(fill = FieldFill.INSERT)//新增时自动填充
    private LocalDateTime createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)//新增或修改时自动填充
    private LocalDateTime updateTime;
7.2 编写元数据对象处理器
/**
 * 自动填充公共字段处理器
 */
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    /**
     * 更新时填充
     * @param metaObject
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        metaObject.setValue("createTime", LocalDateTime.now());
        metaObject.setValue("updateTime",LocalDateTime.now());
        metaObject.setValue("createUser",BaseContext.getCurrentId());
        metaObject.setValue("updateUser",BaseContext.getCurrentId());
    }

    /**
     * 修改时自动填充
     * @param metaObject
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        metaObject.setValue("updateTime",LocalDateTime.now());
        metaObject.setValue("updateUser",BaseContext.getCurrentId());
    }
}

7.3 ThreadLocal使用
7.3.1 使用前景

因为自动填充公共字段时需要获取当前登录用户的Id,所以引入了ThreadLocal。

在学习ThreadLocal之前,我们需要确认一个事情,就是客户端发送的每次http请求,对应的在服务端都会分配一个新的线程来处理,要确保以下所执行到的方法都是同一个线程:

1)LoginCheckFilter的doFilter方法
2)EmployeeController的update方法
3)MyMetaObjectHandler的updateFill方法
可以使用以下代码查看当前线程Id
long id = ThreadLocal.currentThread().getId();
7.3.2 什么是ThreadLocal?

由JDK所提供。ThreadLocal并不是一个Thread,而是Thread的局部变量,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

ThreadLocal为每一个线程提供单独的一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。

ThreadLocal常用方法:

public void set(T value) 设置当前线程的线程局部变量的值
public T get()           返回当前线程所对应的线程局部变量的值

具体使用:

我们可以在LoginCheckFilter的doFilter方法中获取当前登录的用户Id,并调用ThreadLocal的set方法设置当前线程的线程局部变量的值 ( 用户Id ) ,然后在MyMetaObjectHandler的updateFill方法中调用ThreadLocal的get方法来获取当前的线程所对应的线程局部变量的值 ( 用户Id )。

代码实现:

在过滤器方法里面一旦用户登录就设置用户Id

//4.查看登录状态,如果已登录则放行
        if(request.getSession().getAttribute("employee") != null){
            log.info("用户已登录,id为:"+request.getSession().getAttribute("employee"));
            Long empId = (Long) request.getSession().getAttribute("employee");
            //同一个线程保存用户Id
            BaseContext.setCurrentId(empId);
            filterChain.doFilter(request,response);
            return;
        }

四、菜品管理业务开发

8、文件上传、下载介绍

前端Form表单要求:

method="post" 采用post方式提交数据
enctype="multipart/form-data" 采用multipart格式上传文件
type="file" 使用input的file控件上传

后端代码实现:

properties文件配置:

#文件上传位置
reggie.path=D:/img/
/**
 * 文件上传、下载功能
 */
@RestController
@RequestMapping("/common")
public class CommonController {

    //获取配置文件中的路径
    @Value("${reggie.path}")
    private String basePath;

    @PostMapping("/upload")
    public R<String> upload(MultipartFile file) throws IOException {
        //file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会被删除。
        //获取原始文件名
        String originalFilename = file.getOriginalFilename();//abc.jpg
        //截取后缀.jpg
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));

        //使用UUID重新生成文件名,防止上传同名文件被覆盖
        String finalFileName = UUID.randomUUID().toString() + suffix;//skfjd.jpg

        //创建一个目录对象
        File dir = new File(basePath);
        if(!dir.exists()){
            //此目录不存在则创建
            dir.mkdirs();
        }

        //将临时文件转存到指定位置
        file.transferTo(new File(basePath + finalFileName));

        return R.success(finalFileName);
    }
}

文件下载具体实现

	/**
     * 文件下载
     * @param name
     * @param response
     */
    @GetMapping("/download")
    public void downLoad(String name, HttpServletResponse response){
        //通过输入流读取指定文件中的File
        FileInputStream fileInputStream = null;
        ServletOutputStream outputStream = null;
        //设置下载格式
        response.setContentType("image/jpeg");
        try {
            fileInputStream = new FileInputStream(basePath + name);
            outputStream = response.getOutputStream();

            //读写文件
            int len = 0;
            byte[] bytes = new byte[1024];
            while ((len = fileInputStream.read(bytes)) != -1 ){
                outputStream.write(bytes,0,len);
                outputStream.flush();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        //关闭流
        try {
            fileInputStream.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        try {
            outputStream.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

9、对象复制
BeanUtils.copyProperties(dish,dishDto);
10、多表查询添加事务
	/**
     * 保存新增菜品同时保存菜品口味
     * @param dishDto
     */
    @Transactional
    public void saveWithFlavor(DishDto dishDto)
    
    
@Slf4j//lombok提供的日志
@SpringBootApplication//启动类
@ServletComponentScan//开启过滤器
@EnableTransactionManagement//开启事务注解
public class ReggieApplication

五、套餐业务管理开发

六、手机移动端业务开发

11、手机短信验证码使用
11.1 使用阿里云的短信服务,需要签名,模板code,设置AccessKey

  

11.2 代码实现

导入maven依赖

<dependency>
      <groupId>com.aliyun</groupId>
      <artifactId>aliyun-java-sdk-core</artifactId>
      <version>4.5.16</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.aliyun/aliyun-java-sdk-dysmsapi -->
    <dependency>
      <groupId>com.aliyun</groupId>
      <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
      <version>2.1.0</version>
    </dependency>

两个工具类

/**
 * 短信发送工具类
 */
public class SMSUtils {

	/**
	 * 发送短信
	 * @param signName 签名
	 * @param templateCode 模板
	 * @param phoneNumbers 手机号
	 * @param param 参数
	 */
	public static void sendMessage(String signName, String templateCode,String phoneNumbers,String param){
		DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "", "");//填短信服务id和密码
		IAcsClient client = new DefaultAcsClient(profile);

		SendSmsRequest request = new SendSmsRequest();
		request.setSysRegionId("cn-hangzhou");
		request.setPhoneNumbers(phoneNumbers);
		request.setSignName(signName);
		request.setTemplateCode(templateCode);
		request.setTemplateParam("{\"code\":\""+param+"\"}");
		try {
			SendSmsResponse response = client.getAcsResponse(request);
			System.out.println("短信发送成功");
		}catch (ClientException e) {
			e.printStackTrace();
		}
	}

}
/**
 * 随机生成验证码工具类
 */
public class ValidateCodeUtils {
    /**
     * 随机生成验证码
     * @param length 长度为4位或者6位
     * @return
     */
    public static Integer generateValidateCode(int length){
        Integer code =null;
        if(length == 4){
            code = new Random().nextInt(9999);//生成随机数,最大为9999
            if(code < 1000){
                code = code + 1000;//保证随机数为4位数字
            }
        }else if(length == 6){
            code = new Random().nextInt(999999);//生成随机数,最大为999999
            if(code < 100000){
                code = code + 100000;//保证随机数为6位数字
            }
        }else{
            throw new RuntimeException("只能生成4位或6位数字验证码");
        }
        return code;
    }

    /**
     * 随机生成指定长度字符串验证码
     * @param length 长度
     * @return
     */
    public static String generateValidateCode4String(int length){
        Random rdm = new Random();
        String hash1 = Integer.toHexString(rdm.nextInt());
        String capstr = hash1.substring(0, length);
        return capstr;
    }
}

具体业务代码实现

	@PostMapping("/sendMsg")
    public R<String> sendMsg(@RequestBody User user, HttpServletRequest request){
        //获取手机号
        String phone = user.getPhone();
        if(!StringUtils.isEmpty(phone)){
            //生成随机的4位验证码
            String code = ValidateCodeUtils.generateValidateCode(4).toString();
            //调用阿里云提供的短信服务API完成发送短信
            //SMSUtils.sendMessage("ruigi","SMS_269495445",phone,code);
            //需要将生成的验证码保存到Session
            request.getSession().setAttribute(phone,code);
            return R.success("短信发送成功!");
        }
        return R.error("短信发送失败!");
    }

七、Linux常用命令

ls           查看当前目录下的内容
pwd          查看当前所在目录
touch        如果文件不存在,创建文件
cd           切换目录
mkdir        创建目录
rm           删除文件
Linux命令使用技巧

Tab键自动补全
连续两次Tab键,给出操作提示
使用上下键快速调出曾经使用过的命令
使用clear命令或者Ctrl+L快捷键实现清屏

八、路径分析

1.@PathVariable使用场景
path: http://localhost:8081/page/12
2.@RequestParam使用场景
path: http://localhost:8081/page?ids=12346545
3.@RequestBody使用场景
path: http://localhost:8081/page
data{
	id:12,
	name:'jack',
	phone:123465789
}

九、redis优化

1.导入maven坐标
<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置文件
#redis配置
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.database=0
3.缓存菜品数据(查询功能)

实现思路如下:

1)改造DishController的list方法,先从Redis中获取数据,如果有则直接返回,无需查询数据库;如果没有则查询数据库,并将查询的菜品数据放入Redis.

/**
     * 根据条件获取菜品
     * @return
     */
    @GetMapping("/list")
    public R<List<DishDto>> list(Dish dish){

        List<DishDto> dishDtoList = null;
        //动态构造redis存储的key:dish_1397844303408574465_1//根据菜品分类查询
        String key = "dish_" + dish.getCategoryId() + "_" + dish.getStatus();
        //先从redis获取缓存数据
        dishDtoList = (List<DishDto>) redisTemplate.opsForValue().get(key);

        if(dishDtoList != null){
            //如果存在,直接返回,无需查询数据库
            return R.success(dishDtoList);
        }

        //构造查询条件
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(dish.getCategoryId() != null,Dish::getCategoryId,dish.getCategoryId());
        queryWrapper.eq(Dish::getStatus,1);
        //添加排序条件
        queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);

        List<Dish> dishList = dishService.list(queryWrapper);

        dishDtoList = dishList.stream().map((item) ->{
            DishDto dishDto = new DishDto();
            //对象复制
            BeanUtils.copyProperties(item,dishDto);
            Long categoryId = item.getCategoryId();
            //根据categoryId查询分类对象
            Category category = categoryService.getById(categoryId);
            if(category != null){
                String categoryName = category.getName();
                dishDto.setCategoryName(categoryName);
            }
            //根据dishId查询菜品对应的口味信息
            Long dishId = item.getId();
            LambdaQueryWrapper<DishFlavor> dishDtoLambdaQueryWrapper = new LambdaQueryWrapper<>();
            dishDtoLambdaQueryWrapper.eq(DishFlavor::getDishId,dishId);
            List<DishFlavor> flavorList = dishFlavorService.list(dishDtoLambdaQueryWrapper);
            dishDto.setFlavors(flavorList);
            return dishDto;
        }).collect(Collectors.toList());

        //如果不存在,则查询数据库,将查询到的菜品数据缓存到redis中(60分钟清理一次缓存)
        redisTemplate.opsForValue().set(key,dishDtoList,60, TimeUnit.MINUTES);

        return R.success(dishDtoList);
    }

2)改造DishController的save和update方法,加入清理缓存的逻辑。

    @PutMapping
    public R<String> updateWithFlavor(@RequestBody DishDto dishDto){
        dishService.updateWithFlavor(dishDto);
        //1.清理所有菜品的缓存数据
//        Set keys = redisTemplate.keys("dish_*");
//        redisTemplate.delete(keys);

        //2.精准清理某个分类下的菜品数据
        String key = "dish_" + dishDto.getCategoryId() + "_1";
        redisTemplate.delete(key);

        return R.success("修改菜品成功!");
    }

注意事项:

​ 在使用缓存过程中,要注意保证数据库中的数据和缓存中的数据一致,如果数据库中的数据发生变化,需要及时清理缓存数据。

十、Spring Cache优化

1.介绍

Spring Cache是一个框架,实现了基于注解的缓存功能,只需要简单的加一个注解,就能实现缓存功能。

Spring Cache提供了一层抽象,底层可以切换不同的cache实现。具体就是通过CacheManager接口统一不同的缓存技术。

CacheManager是Spring提供的各种缓存技术抽象接口。

针对不同的缓存技术需要不同的CacheManager:

CacheManager                       描述
EhCacheCacheManager                使用EhCache作为缓存技术
GuavaCacheManager                  使用Google的GuavaCache作为缓存技术
RedisCacheManager                  使用Redis作为缓存技术
2.Spring Cache 常用注解
注解               说明
@EnableCaching     开启缓存注解功能
@Cacheable         在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;
@CachePut           将方法的返回值放到缓存中
@CacheEvict         将一条或多条数据从缓存中删除

在spring boot项目中,使用缓存技术只需要在项目中导入相关缓存技术的依赖包,并在启动类上使用@EnableCaching开启缓存支持即可。

例如:使用Redis作为缓存技术,只需要导入Spring data Redis的maven坐标即可。

3.缓存套餐数据
1.导入maven依赖
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

2.配置文件
#Spring Cache配置设置缓存数据的过期时间大约30分钟
spring.cache.redis.time-to-live=1800000
3.启动类配置@EnableCaching
@Slf4j//lombok提供的日志
@SpringBootApplication//启动类
@ServletComponentScan//开启过滤器
@EnableTransactionManagement//开启事务注解
@EnableCaching //开启缓存功能
public class ReggieApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReggieApplication.class,args);
        log.info("项目启动成功!");
    }
}
4.代码实现
/**
     * 移动端展示套餐数据
     * @return
     */
    @GetMapping("/list")
    //在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;
    @Cacheable(value = "setmealCache",key = "#setmeal.categoryId + '_' + #setmeal.id")
    public R<List<Setmeal>> list(Setmeal setmeal){
        //根据CategoryId和status查询
        LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
        setmealLambdaQueryWrapper.eq(setmeal.getCategoryId() != null,Setmeal::getCategoryId,setmeal.getCategoryId());
        setmealLambdaQueryWrapper.eq(setmeal.getStatus() !=null,Setmeal::getStatus,setmeal.getStatus());
        setmealLambdaQueryWrapper.orderByDesc(Setmeal::getUpdateTime);
        List<Setmeal> setmealList = setmealService.list(setmealLambdaQueryWrapper);
        return R.success(setmealList);
    }



/**
     * 新增套餐
     * @return
     */
    @PostMapping
    //将一条或多条数据从缓存中删除,新增或删除或修改时,需要将套餐下的所有缓存数据删除
    @CacheEvict(value = "setmealCache",allEntries = true)
    public R<String> saveWithDish(@RequestBody SetmealDto setmealDto){
        setmealService.saveWithDish(setmealDto);
        return R.success("新增套餐成功!");
    }

    /**
     * 删除套餐
     * @return
     */
    @DeleteMapping
    //将一条或多条数据从缓存中删除,新增或删除或修改时,需要将套餐下的所有缓存数据删除
    @CacheEvict(value = "setmealCache",allEntries = true)
    public R<String> delete(@RequestParam List<Long> ids){
        setmealService.removeWithDish(ids);
        return R.success("套餐删除成功!");
    }


    /**
     * 修改套餐数据同时修改菜品数据
     * @param setmealDto
     * @return
     */
    @PutMapping
    //将一条或多条数据从缓存中删除,新增或删除或修改时,需要将套餐下的所有缓存数据删除
    @CacheEvict(value = "setmealCache",allEntries = true)
    public R<String> update(@RequestBody SetmealDto setmealDto){
        setmealService.updateWithDish(setmealDto);
        return R.success("修改成功!");
    }

十一、Mysql主从复制(读写分离)

1.介绍

Mysql主从复制是一个异步的复制过程,底层是基于Mysql数据库自带的二进制日志功能。就是一台或多台Mysql数据库(slave,即从库)从另一台Mysql数据库(master,即主库)进行日志的复制然后再解析日志并应用到自身,最终实现从库的数据和主库的数据保持一致。Mysql主从复制是Mysql数据库自带功能,无需借助第三方工具。

Mysql复制过程分成三步:

1)master将改变记录到二进制日志(binary log)

2)slave将master的binary log 拷贝到它的中继日志(relay log)

3)slave重做中继日志的事件,将改变应用到自己的数据库中

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值