Reggie

接口


一.每日功能更新

22-05-22 day01

  1. 创建项目,完成后端员工登陆与登出功能
  2. 创建远程仓库,push代码到远程develop分支

22-05-23 day02
1.基于filter实现权限控制
2.新增添加员工功能
3.全局异常处理器,解决员工username注册时重复问题
4.新增员工条件分页查询功能
5.新增修改员工状态/修改员工信息(前端long id问题解决)
6.新增修改页面员工信息回显


22-05-25 day03
1.利用MybatisPlus的公共字段填充功能 解决各个表的冗余字段
2.利用ThreadLocal解决独立线程数据共享问题
3.菜品/套餐分类增删改查
5.菜品/套餐分类删除操作的逻辑外键


22-05-26 day04
1.文件上传与预览接口
2.添加菜品功能(dish+dish_flavor)
3.前端映射对象Dto类的创建
4.dish的分页条件查询(BeanUtils的使用,dishPage对象转dishDtoPage对象)


22-05-28 day05
1.套餐新增功能(套餐+套餐菜品关系)
2.套餐分页展示功能
3.套餐修改功能(套餐+套餐菜品关系)
4.套餐删除功能(套餐+套餐菜品关系)
5.LocalDateTime类型导致前端时间带T


22-05-29 day06
1.前台用户的登陆与权限校验
2.前台主页面的菜品分类与级联显示
3.购物车的增删改查


22-05-31 day07
1.前台新增订单/订单项
2.前台新增用户个人订单列表展示
3.后台新增所有订单展示
4.用redis代替session完成验证码功能
5.用redis完成根据categoryId查询菜品缓存的功能
6.用redis+spring cache 完成categoryId查询套餐缓存的功能


22-06-01 day08
1.使用swagger整理所有接口
2.使用swagger注解描述接口

二.使用技术点

静态资源无法直接访问?

解决方案:添加静态资源映射
创建SpringMvcConfig类继承WebMvcConfigurationSupport,重写addResourceHandlers方法

@Configuration
public class SpringMvcConfig extends WebMvcConfigurationSupport {

    @Override
    //设置静态资源文件映射
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        // /backend/** 这些文件需要使用静态资源映射
        registry.addResourceHandler("/backend/**")
                //静态资源文件映射的位置在/backend/下
                .addResourceLocations("classpath:/backend/");
        registry.addResourceHandler("/front/**")
                .addResourceLocations("classpath:/front/");
    }


密码的MD5加密

密码都是经MD5加密后存于数据库,所以不论是登陆校验,还是用户注册都需要将密码MD5加密后进行存储 比对...

import org.springframework.util.DigestUtils;
//将字节转换为16进制MD5密码
String password = DigestUtils.md5DigestAsHex(password.getBytes());

响应格式R类

定义了标准的响应格式类R<T>,该类提供了返回成功和失败的静态方法
并没有将有参构造对外暴露,保证了传递给前段数据的一致性

package com.itheima.reggie.common;

import lombok.Data;
import java.util.HashMap;
import java.util.Map;

@Data
public class R<T> {

    private Integer code; //编码:1成功,0和其它数字为失败

    private String msg; //错误信息

    private T data; //数据

    private Map map = new HashMap(); //动态数据

    public static <T> R<T> success(T object) {
        R<T> r = new R<T>();
        r.data = object;
        r.code = 1;
        return r;
    }

    public static <T> R<T> error(String msg) {
        R r = new R();
        r.msg = msg;
        r.code = 0;
        return r;
    }

    public R<T> add(String key, Object value) {
        this.map.put(key, value);
        return this;
    }

}


MybatisPlus简化了单表的操作,

mapper接口继承BaseMapper<T>

@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
}

Service接口继承Iservce<T>

public interface EmployeeService extends IService<Employee> {
}

ServiceImpl继承ServiceImpl<mapper,T>实现Service接口

@Service
public class EmployeeServiceImpl 
extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {
}

controller层直接使用ServiceImpl进行查询
使用lambdaQueryWrapper代替硬编码的column字段参数

       //根据用户名查询到员工
        LambdaQueryWrapper<Employee> queryWrapper =new LambdaQueryWrapper();
        queryWrapper.eq(Employee::getUsername,loginEmployee.getUsername());
        Employee emp = employeeService.getOne(queryWrapper);

增加权限过滤器
  • 如何在springboot中使用过滤器
    1.创建MyFilter实现Filter接口,并用@WebFilter标注
    2.在启动类打开Servlet注解扫描@ServletComponentScan
  • 如何设置过滤器放行静态资源/登录页面?
  1. 创建数组 将放行路径存于数组
  2. 在doFilter方法中获取通过request的getRequestUri的方法获取请求路径
    将uri与数组中可以放行的路径进行遍历
    遍历过程中使用AntPathMatcher类的match方法进行路径匹配确认
  3. 被拦截页面进行权限校验,校验不通过传递json字符串给前端,由前端进行页面跳转
@WebFilter
@Slf4j
public class LoginFilter implements Filter {
    //创建uri放行路径数组
    private String[] allowUriArr ={
           "/backend/**",
           "/front/**",
            "/employee/login"
    };

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        //获取请求路径
        String requestURI = request.getRequestURI();
        log.info("拦截请求:{}",requestURI);
        //判断uri是否属于放行数组中的路径
        Boolean result = isAllowUri(requestURI);
        if (result){
            filterChain.doFilter(request,response);
            return;
        }
        //从session拿到用户信息,判断是否为空
        HttpSession session = request.getSession();
        Object employeeId = session.getAttribute("employeeId");
        if (employeeId != null){
            filterChain.doFilter(request,response);
            return;
        }
        //如果为空,则没登录,返回给前端固定的R.error
        log.info("用户未登录");
        R notlogin = R.error("NOTLOGIN");
        String notloginJson = JSON.toJSONString(notlogin);
        response.getWriter().write(notloginJson);
        return;
    }

    /**
     * 判断请求路径是否在放行路径数组中
     * @param requestURI
     * @return
     */
    private Boolean isAllowUri(String requestURI) {
        //遍历allowUriArr数组,挨个比较是否与requestURI匹配
        AntPathMatcher apm =new AntPathMatcher();
        for (String allowUri : allowUriArr) {
            if (apm.match(allowUri,requestURI)){
                return true;
            }
        }
        return false;
    }

    @Override
    public void destroy() {

    }
}

全局异常处理器

在进行用户注册的时候,若用户名已被占用,我们需要捕捉异常
1.创建异常控制类,并用@RestControllerAdvice标识
2.类中方法用@ExceptionHandler(SQLIntegrityConstraintViolationException.class)标识,代表我们要捕捉此异常.
3.我们将异常信息截取,判断是否是用户名重复异常,截取其中的重复用户名
4.将重复用户名作为msg传递给前端
5.若不是我们识别到的异常,就做统一返回.

@RestControllerAdvice
@Slf4j
public class ReggieException {

    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R<String> exceptionHandler(SQLIntegrityConstraintViolationException e){
          log.info("异常信息:{}",e.getMessage());
          String msg = e.getMessage();
          if(msg.contains("Duplicate entry")){
              String[] s = msg.split(" ");
              return R.error("用户名"+s[2]+"已被使用");
          }
          return R.error("未知错误!!!");
    }
}

利用mybatisPlus进行分页+条件查询

分页查询:
1.在mybatisPlusConfig中注册分页插件拦截器
2.以前端传递的page和pageSize作为参数创建page对象
条件查询:
3.创建条件查询对象LambdaQueryWrapper<T>
4.使用方法添加条件
5.将分页对象与条件对象作为参数传入service方法,结果返回一个page对象
6.将page对象作为data返回给前端

@GetMapping("/page")
    public R<Page> getMemberList(@RequestParam("page")String pageStr,
                                 @RequestParam("pageSize")String pageSizeStr,
                                 String name){
        log.info("前端传来的页面查询信息:第{}页,{}条数据,name是{}",pageStr,pageSizeStr,name);
        if (pageStr == null || pageStr == ""){
            pageStr="1";
        }
        if (pageSizeStr == null || pageSizeStr == ""){
            pageSizeStr="10";
        }
        int page = Integer.parseInt(pageStr);
        int pageSize = Integer.parseInt(pageSizeStr);
        //page对象
        Page<Employee> employeePage = new Page<>();
        employeePage.setPages(page);
        employeePage.setSize(pageSize);
        //条件查询对象
        LambdaQueryWrapper<Employee> queryWrapper =new LambdaQueryWrapper<>();
        queryWrapper.like(StringUtils.hasLength(name),Employee::getName,name);
        //针对page对象调用service查询
        Page<Employee> pageResult = employeeService.page(employeePage,queryWrapper);
        return R.success(pageResult);

    }

前端对于Long id精度失真的解决

让分页查询返回的json格式数据库中, long类型的属性, 不直接转换为数字类型, 转换为字符串类型就可以解决这个问题了
1.在启动类中


@SpringBootApplication
@ServletComponentScan
public class ReggieApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReggieApplication.class,args);
    }

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        Jackson2ObjectMapperBuilderCustomizer customizer = jacksonObjectMapperBuilder ->
                jacksonObjectMapperBuilder
                        .serializerByType(Long.class, ToStringSerializer.instance)
                        .serializerByType(Long.TYPE, ToStringSerializer.instance);
        return customizer;
    }
}

在SpringMvcConfig中

package com.itheima.reggie.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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 org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import java.util.List;

@Configuration
@Slf4j
public class SpringMvcConfig extends WebMvcConfigurationSupport {
    @Autowired
    private ObjectMapper objectMapper;

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


Mybatis-Plus的公共字段填充功能

本项目中多数表存在公共字段:createTime/updateTime/createUser/updateUser
我们借助Mybatis-Plus的公共字段填充功能,在进行sql查询前,将在元数据对象处理器中配置好的属性值添加到响应字段
1.在公共字段上添加@TableField(fil = FieldFill.INSERT)或者@TableField(fil = FieldFill.INSERT_UPDATE)代表此公共字段需要被填充

@TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    @TableField(fill = FieldFill.INSERT)
    private Long createUser;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;
}

2.创建元数据异常处理器实现MetaObjectHandler,并重写insertFill和updateFill

@Component
@Slf4j
//元数据对象处理器  为@TbaleField(fill = "xxx")字段赋值
public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        long threadId= Thread.currentThread().getId();
        log.info("当前线程id:{}"+threadId);
        log.info("metaObject:"+metaObject);
        //从ThreadLocal中拿到员工id
        Long empId = BaseContext.getEmpId();
        metaObject.setValue("createTime",LocalDateTime.now());
        metaObject.setValue("updateTime",LocalDateTime.now());
        metaObject.setValue("createUser",new Long(empId));
        metaObject.setValue("updateUser",new Long(empId));
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        long threadId= Thread.currentThread().getId();
        log.info("当前线程id:{}"+threadId);
        //从ThreadLocal中拿到员工id
        Long empId = BaseContext.getEmpId();
        metaObject.setValue("updateTime",LocalDateTime.now());
        metaObject.setValue("updateUser",new Long(empId));

    }
}

信息修改人是通过session中数据获取的,那么作为dao层的MyMetaObjectHandler在使用框架方法时无法获取request对象或者session对象,这是怎么做?看下面这个解决方案...↓

使用ThreadLocal在一个线程中共享数据

通过打印我们发现,浏览器的一次请求与响应都存在于一个线程中,从filter的控制层到model层,都存在于一个线程中,那么我们就可以使用TheadLocal进行线程数据共享

9d0daece70953be0a233c795efa737a.png

地址:https://www.processon.com/view/link/628da5171e08532319e9182a
1.创建BaseContext 存放 ThreadLocal对象
ThreadLocal私有化,不被其他线程使用,封装成方法进行使用
静态 随着类的加载而加载,需要被静态方法调用
final ThreadLocal对象地址值不被修改,但对象属性可以修改

public class BaseContext {
    //ThreadLocal私有化,不被其他线程使用,封装成方法进行使用
    //静态   随着类的加载而加载,需要被静态方法调用
    //final ThreadLocal对象地址值不被修改,但对象属性可以修改
    private static final ThreadLocal<Long> THREAD_LOCAL_EMP_ID =new ThreadLocal<>();

    //提供公共暴露方法,只能存放数据,不能修改对象
    public static void setEmpId(Long id){
        THREAD_LOCAL_EMP_ID.set(id);
    }
    public static Long getEmpId(){
        return THREAD_LOCAL_EMP_ID.get();
    }
}

2.在filter中(控制层)将session数据存到本线程的ThreadLocal中

 HttpSession session = request.getSession();
        Object employeeId = session.getAttribute("employeeId");
        if (employeeId != null){
            long threadId= Thread.currentThread().getId();
            log.info("当前线程id:"+threadId);
            //在线程中的ThreadLocal存放一个emp id的值;在MyMetaObjectHandler中读取
            BaseContext.setEmpId((Long) employeeId);
            //放行
            filterChain.doFilter(request,response);

3.在元数据处理器(模型层)获取当前线程中的ThreadLocal

 //从ThreadLocal中拿到员工id
        Long empId = BaseContext.getEmpId();

So,ThreadLocal用于线程中存储数据的对象,一个线程中有一个ThreadLocalMap,
ThreadLocalMap是以ThreadLocal对象为key,ThreadLocal的值为值的map集合,
所以一个线程可以有多个ThreadLocal对象存储多份数据
ThreadLocal<Long> threadLocal =new ThreadLocal<>();
void threadLocal.set(obj);
Object threadLocal.get();

在业务逻辑中添加逻辑外键

由于外键等特性需要数据库执行额外的工作,而这些操作会占用数据库的计算资源,所以我们可以将大部分的需求都迁移到无状态的服务中完成以降低数据库的工作负载.
当我们想删除分类时,就要考虑是否有dish或者setmeal引用了分类的id作为字段值.
因为我们要在CategoryService中去手动定义删除方法,删除方法中添加逻辑外键,判断当前被删除id是否被另外两张表引用
如果被引用,那么我们抛出自定义用户异常,全局异常处理器捕获异常并返回给前端

删除业务

@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {

    @Autowired
    private DishService dishService;
    @Autowired
    private SetmealService setmealService;

    //根据分类id删除,进行逻辑外键判断
    @Override
    public void deleteCategoryById(Long id) {
        //判断此id是否被dish表引用
        LambdaQueryWrapper<Dish> dishWrapper = new LambdaQueryWrapper<>();
        dishWrapper.eq(Dish::getCategoryId,id);
        int count1 = dishService.count(dishWrapper);
        if (count1 > 0){
            throw new CustomException("该分类下包含菜品,无法删除!!!");
        }
        //判断此id是否被setmeal表引用
        LambdaQueryWrapper<Setmeal> setmealWrapper = new LambdaQueryWrapper<>();
        setmealWrapper.eq(Setmeal::getCategoryId,id);
        int count2 = setmealService.count(setmealWrapper);
        if (count2 > 0){
            throw new CustomException("该分类下包含菜品,无法删除!!!");
        }
        //都没被引用允许删除
        removeById(id);
    }
}

自定义用户异常

public class CustomException extends RuntimeException {
    public CustomException() {
    }

    public CustomException(String message) {
        super(message);
    }
}

拦截用户异常

 //拦截自定义用户异常
    @ExceptionHandler(CustomException.class)
    public R<String> exceptionHandler2(CustomException e) {
        String message = e.getMessage();
        return R.error(message);
    }

文件上传 与 文件预览

文件上传就是将浏览器上传文件保存到服务器固定位置
预览就是从固定位置读取文件再写出
文件上传的前端要求是

  • post提交
  • enctype=multippart/form-data
  • input中type=file
    本项目中使用element ui封装好的文件上传组件,后端接收到前端post请求及请求体中参数名为file的字节文件
  1. 创建公共的controller及方法进行接收文件,后续都使用此控制器方法进行文件上传处理
  2. 编写upload方法接收用户的文件,并保存到固定位置
    2.1 我们在yml中定义了文件存放路径,属性上使用@Value("${reggie.basepath}")进行引用
    2.2 接收文件参数类型固定为MultipartFile,参数名与前端传的参数名一致.这里是file
    2.3解析文件名,截取文件格式,例如.jpg
    2.4为了避免本地文件名重复导致文件覆盖,使用uuid.random.toString作为文件名
    2.5使用file.transferTo(newFile)方法,将内存中文件copy到目标路径
    2.6将新生成的文件名作为返回值回传给前端
public class CommonController {
    @Value("${reggie.basepath}")
    private String basepath;

    @PostMapping("upload")
    public R<String> upload(MultipartFile file){
        String originalFilename = file.getOriginalFilename();
        log.info("上传的文件名:"+originalFilename);
        //截取文件格式
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));

        String prefix = UUID.randomUUID().toString();

        //判断文件夹是否存在 不存在创建
        File dir = new File(basepath);
        if (!dir.exists()){
            dir.mkdirs();
        }
        //目标文件
        File goalFile = new File(basepath,prefix+suffix);
        try {
            file.transferTo(goalFile);

        } catch (IOException e) {
            e.printStackTrace();
            return  R.error("文件上传失败");
        }
        return R.success(prefix+suffix);
    }
}
  1. 前端接收到新的文件名后,申请图片预览
    3.1 接收前端要预览的文件名
    3.2 读取服务器本地文件
    3.3 写到response的字节输出流中
 @GetMapping("/download")
    public void download(String name, HttpServletResponse response){
        //下载文件路径
        File file =new File(basepath,name);

        BufferedInputStream bis = null;
        ServletOutputStream os= null;
        try {
            bis = new BufferedInputStream(new FileInputStream(file));
            os = response.getOutputStream();
            response.setContentType("image/jpeg");

            int len;
            byte[]  buffer = new byte[8*1024];
            while((len = bis.read(buffer))!=-1){
                os.write(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }

Dto类对象 接收前端的对象

拿新建菜品为例,前端传来的json数据中包含了dish对象没有的flavor属性,为了不破坏ORM对应关系,我们创建DishDto类继承Dish类,来接收前端的数据,其中就包含了dish类对象接收不了的flavors属性


page<Dish>转page<DishDto>

在进行dish分页查询时,发现前端需要这个表以外的其他数据,显然我们返回的page<Dish>不能满足
1.在DishDto中新增前端需要的属性:categoryName
2.通过BeanUtils的xxx方法将page<Dish>的属性复制到page<DishDto>,但不复制records,因为records的泛型是Dish,Dish没有我们需要的额外categoryName属性,所以我们需要将records进行加工
3.遍历records数组的每一条dish数据,将dish对象的属性值再复制给新的dishDto对象,
新的dto对象再通过原有的category_id属性去查询category对象,再将category对象的categoryName赋值到dto对象的categoryName属性中,这样我们一个dto对象就完整了.
4.我们把每一个新的dto对象添加到集合中
5.这个新生成的集合就代替了原有的records,将新生成的集合添加到page<DishDto>的records属性
6.我们返回page<DishDto>对象给前端

@GetMapping("/page")
    public R<Page> getAllDishes(Integer page,Integer pageSize,String name){
        //创建分页对象
        Page<Dish> dishPage = new Page<>(page,pageSize);
        //创建条件查询对象
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.like(StringUtils.hasLength(name),Dish::getName,name);
        //执行查询sql
        dishService.page(dishPage,queryWrapper);
        //此时的dishPage并没有前端要的categoryName属性 所以需要转换成dishDtoPage
        Page<DishDto> dishDtoPage = new Page<>();
        //dishPage的属性复制给dishDtoPage,records属性除外,因为我们要对records进行补充
        BeanUtils.copyProperties(dishPage,dishDtoPage,"records");
        List<Dish> dishRecords = dishPage.getRecords();
        //创建一个新的dishDtoPage的Records
        List<DishDto> dishDtoRecords =new ArrayList<>();
        for (Dish dish : dishRecords) {
            DishDto dishDto = new DishDto();
            //将每一个dish对象的属性 赋值给dishDto对象
            BeanUtils.copyProperties(dish,dishDto);
            //此时 dishDto对象的categoryName属性还是空,但是dish对象的caegory_id属性是有的
            Category category = categoryService.getById(dish.getCategoryId());
            //将dishDto对象的categoryName属性赋值
            dishDto.setCategoryName(category.getName());
            //将这个dishDto加入到dishDtoPage的Records中去
            dishDtoRecords.add(dishDto);
        }
        dishDtoPage.setRecords(dishDtoRecords);
        return R.success(dishDtoPage);


    }

LocalDateTime类型导致前端时间带T

在这个时间属性上加上注解 把T可以去掉


    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;


    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;

前端传递的时间字符串后端参数如何接收?

使用@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 注解标识时间类型参数

@GetMapping("/page")
    // @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 转换前端字符串类型
    public R<Page> page(Integer page, Integer pageSize, String number,@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")  Date beginTime,@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endTime){
        Page<OrdersDto> ordersDtoPage= ordersService.getAllOrders(page,pageSize,number,beginTime,endTime);
        return R.success(ordersDtoPage);
    }

AtomicInteger类的使用

AtomicInteger.addAndGet方法具有原子性,不会再累加过程中被其他线程操作数据

AtomicInteger amount = new AtomicInteger(0);
amount.addAndGet();
使用redis缓存实现验证码功能

1.导入redis场景启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.创建redisTmplate对象

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        //默认的Key序列化器为:JdkSerializationRedisSerializer
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        // 值不要随便字符串格式,否则存入的都是字符串格式的
        //redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }
}

3.在yml中配置redis
spring:
redis:
host: 192.168.188.128
port: 6379
database: 0
4.在存储验证码的方法中,将验证码存到redis中,以电话号为key

redisTemplate.opsForValue().set(phone,code+"",120, TimeUnit.SECONDS);

5.用户登陆校验
接收前端的phone和code
根据phone从redis从get redisCode
比对code和redisCode是否一致
一致就删除redis中redisCode,放行
不一致就报错


用redis完成根据categoryId查询菜品缓存的功能

1.判断是否存在缓存
2.存在缓存调用缓存,不存在就执行sql
3.执行sql后将结果存到redis中
4.更新数据的操作删除对应的redis中的缓存

@GetMapping("/list")
    //根据dish对象中的CategoryId属性 查询到对应的dish list
    public R<List<DishDto>> getDishListByCatogoryId(Dish dish){
        //查看redis中是否有缓存,没有的话再去执行数据库
        //以dish_CategoryId作为key 查询结果作为value
        String dishKey = "dish" + dish.getCategoryId().toString();
        List<DishDto> redisList = (List<DishDto>) redisTemplate.opsForValue().get(dishKey);
        if (redisList != null){
            //没走数据库 ,走了缓存查询dishes
            return R.success(redisList);
        }
        //如果缓存中没有,那就查询数据库
        LambdaQueryWrapper<Dish> queryWrapper =new LambdaQueryWrapper<>();
        queryWrapper.eq(Dish::getCategoryId,dish.getCategoryId())
                .eq(Dish::getStatus,1)
                .orderByAsc(Dish::getSort);
        List<Dish> list = dishService.list(queryWrapper);
        //为了迎合前端需求,将dishList转换为dishDtoList
        List<DishDto> dtoList =new ArrayList<>();
        for (Dish dish1 : list) {
            DishDto dishDto = new DishDto();
            BeanUtils.copyProperties(dish1,dishDto);
            //dto填充 flavors属性
            LambdaQueryWrapper<DishFlavor> queryWrapper1 = new LambdaQueryWrapper<>();
            queryWrapper1.eq(DishFlavor::getDishId,dish1.getId());
            List<DishFlavor> flavors = dishFlavorService.list(queryWrapper1);
            dishDto.setFlavors(flavors);
            //dto填充categoryname属性
            Category category = categoryService.getById(dish1.getCategoryId());
            dishDto.setCategoryName(category.getName());
            dtoList.add(dishDto);
        }
        //查询数据库后,将查询结果存到redis内存中
        redisTemplate.opsForValue().set(dishKey,dtoList);
        return R.success(dtoList);
    }
用redis+spring cache 完成categoryId查询套餐缓存的功能

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

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1.启动类开启缓存注解
@EnableCaching
2.yml配置缓存设置

设置缓存数据的过期时间

Spring
cache:
redis:
time-to-live: 1800000
3.使用注解
@Cacheable : 有缓存走缓存,没有就执行方法
value:redis中的缓存组件名字
key:缓存组件的key
unless:什么条件下不去走缓存

@GetMapping("/list")
    @Cacheable(value ="setmealCahce",key="#setmeal.categoryId",unless = "#result == null")
    public R<List<Setmeal>> list(Setmeal setmeal){
        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(setmeal.getCategoryId() != null,Setmeal::getCategoryId,setmeal.getCategoryId());
        queryWrapper.eq(setmeal.getStatus() != null,Setmeal::getStatus,setmeal.getStatus());
        queryWrapper.orderByDesc(Setmeal::getUpdateTime);

        List<Setmeal> list = setmealService.list(queryWrapper);
        return R.success(list);
    }

@CacheEvict
allEntries :删除当前缓存组的所有内容

 @PutMapping
    @CacheEvict(value = "setmealCahce",allEntries = true)
    public R<String> updateSetmeal(@RequestBody SetmealDto setmealDto) {
        //更新setmeal两步骤,先更新setmeal表,在删除setmealdish表再新增
        setmealService.updateSetmeal(setmealDto);
        return R.success("更新成功!!!");
    }

@CachePost 将方法返回值添加到缓存

三.遇到的问题

SpringMvcConfig没有写@Configruation导致静态资源映射失效


LambdaQueryWrapper<T>创建时没有添加泛型,使用方法时无法使用lambda表达式


新建用户未MD5加密密码就保存到数据库


分页查询未创建mybatisPlus分页插件interceptor


前端get请求误以为有请求体,实际是url拼接


在继承了ServiceImpl的serviceImpl中去调用了mapper,其实只要用本身的方法执行sql操作就行了


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容