瑞吉项目笔记

  1. 全局异常处理

package com.xlm.reggie.common;


import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.sql.SQLIntegrityConstraintViolationException;

@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
        log.info(ex.getMessage());
        if (ex.getMessage().contains("Duplicate entry")) {
            String[] s = ex.getMessage().split(" ");
            return R.error(s[2] + "已存在");
        }
        return R.error("未知错误");
    }
}
  1. 静态资源映射

package com.xlm.reggie.comfig;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
    /**
     * 设置静态资源映射
     * @param registry
     */
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("开始进行静态资源映射");
        registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
        registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
    }
}

3.用户登录(session存储登录信息)

@PostMapping("/login")
    public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){
        /**
         1.把前端提交的密码进行md5 加密处理
         2.更具用户名查数据库
         3.没查到就返回失败
         4.密码比对(比对不成功返回错误信息)
         5.查看员工状态,1可用 0禁用
         6.登陆成功 再把id存入session
         */
        //1.把前端提交的密码进行md5 加密处理
        String password = employee.getPassword();
        password = DigestUtils.md5DigestAsHex(password.getBytes());

        //2.更具用户名查数据库
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Employee::getUsername,employee.getUsername());
        Employee emp = employeeService.getOne(queryWrapper);

        //3.没查到就返回失败
        if (emp == null){
            return R.error("用户名不存在");
        }

        //4.密码比对(比对不成功返回错误信息)
        if (!emp.getPassword().equals(password)){
            return R.error("密码错误!");
        }

        //5.查看员工状态,1可用 0禁用
        if (emp.getStatus() == 0){
            return R.error("账号已禁用!");
        }

        //6.登陆成功 再把id存入session
        request.getSession().setAttribute("employee",emp.getId());
        return R.success(emp);

    }

4.拦截器拦截未登录

package com.xlm.reggie.filter;

/**
 * 检查用户是否已经完成登录
 */

@Slf4j
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
    //路径匹配器,支持通配符
    public 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.获取本次请求的URI
        String requestURI = request.getRequestURI();
        log.info("拦截到请求 {}",requestURI);
        String[] urls = new String[]{  //定义不需要被拦截的请求
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**"
        };

        //2.判断本次请求是否需要处理
        boolean check = check(urls, requestURI);

        //3.如果不需要处理,则直接放行
        if (check){
            log.info("本次请求{}不需要处理", requestURI);
            filterChain.doFilter(request,response);
            return;
        }

        //4.判断登录状态,如果已登录,则直接放行
        if (request.getSession().getAttribute("employee") != null){
            log.info("用户已登录,用户id为:",request.getSession().getAttribute("employee"));
            filterChain.doFilter(request,response);
            return;
        }

        //5.如果未登录则返回未登录结果,通过输出流方式像客户端相应数据
        log.info("用户未登录");
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
        return;


//        log.info("拦截到请求: {} ", request.getRequestURI());
//        filterChain.doFilter(request, response);
    }

    public boolean check(String[] urls, String requestURI) {
        for (String url : urls) {
            //把浏览器发过来的请求和我们定义的不拦截的url做比较,匹配则放行
            boolean match = PATH_MATCHER.match(url, requestURI);
            if (match) {
                return true;
            }
        }
        return false;
    }
}

5.Reult格式

package com.xlm.reggie.common;

/**
 * 通用返回结果,服务端返回数据将会封装此对象
 * @param <T>
 */
@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;
    }

}

6.分页查询(需要先添加mp的分页插件,一个拦截器,返回Reult<Page>)

package com.xlm.reggie.comfig;

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}

7.类型转换器

步骤一:自定义消息转换类

package com.xlm.reggie.common;

/**
 * 对象映射器:基于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);
    }
}

步骤二:在前面的webMvcConfig 配置类中扩展spring mvc 的消息转换器,在此消息转换器中使用spring提供的对象转换器进行Java对象到json数据的转换;

相当于把我们创建的新转换器加入到2http那个类转换器中,在加入到converts转换器数据的第一位。

     /**
     * 扩展mvc框架的消息转换器
     * @param converters
     */
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        //log.info("扩展消息转换器...");
        //创建消息转换器对象
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        //设置对象转换器,底层使用Jackson将Java对象转为json
        messageConverter.setObjectMapper(new JacksonObjectMapper());
        //将上面的消息转换器对象追加到mvc框架的转换器集合中
        //转换器是有优先级顺序的,这里我们把自己定义的消息转换器设置为第一优先级,所以会优先使用我们的转换器来进行相关数据进行转换,如果我们的转换器没有匹配到相应的数据来转换,那么就会去寻找第二个优先级的转换器,以此类推
        converters.add(0,messageConverter);
    }

8.公共字段自动填充

问题分析:

step1.在pojo对象中加入TableFiled注解

把相关的注解加在需要mybatis-plus自动帮我们填充的字段上面
    
    @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;

step2.编写一个类实现,实现MetaObjectHandler接口

其中metaObject中会封装执行操作的pojo对象。

package com.xlm.reggie.common;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

/***
 * 自定义一个元数据对象处理器
 */
@Component
@Slf4j
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.get());  //这里的id是不能直接获取的,所以这里先写死,后面教你怎么动态获取员工id
        metaObject.setValue("updateUser",BaseContext.get());
    }

    /**
     * 更新操作,自动填充
     * @param metaObject
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        metaObject.setValue("updateTime",LocalDateTime.now());
        metaObject.setValue("updateUser",BaseContext.get());
    }
}

step3.编写BaseContxt类,内置有静态方法和变量,每个线程共用同一个。

package com.xlm.reggie.common;

public class BaseContext {
    private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    public static void set(Long id){
        threadLocal.set(id);
    }

    public static Long get(){
        return threadLocal.get();
    }
}

step4.在之前的登录过滤器中保存当前用户id

 //4.判断登录状态,如果已登录,则直接放行
        if (request.getSession().getAttribute("employee") != null){
            log.info("用户已登录,用户id为:",request.getSession().getAttribute("employee"));
            Long id = (Long) request.getSession().getAttribute("employee");
            BaseContext.set(id);
            filterChain.doFilter(request,response);
            return;
        }

9.自定义业务异常

step1.自定义异常类

package com.xlm.reggie.common;

public class CustomException extends RuntimeException{
    public CustomException(String message){
        super(message);
    }
}

step2 全局异常处理方法

package com.xlm.reggie.common;


@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
        log.info(ex.getMessage());
        if (ex.getMessage().contains("Duplicate entry")) {
            String[] s = ex.getMessage().split(" ");
            return R.error(s[2] + "已存在");
        }
        return R.error("未知错误");
    }

    /**
     * 异常处理方法
     * @param ex
     * @return
     */
    @ExceptionHandler(CustomException.class)
    public R<String> exceptionHandler(CustomException ex){
        log.info(ex.getMessage());
       
        return R.error(ex.getMessage());
    }
}

step3.业务中抛出异常

@Override
    public void remove(long id) {
        LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
        //添加查询条件
        dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
        //注意:这里使用count方法的时候一定要传入条件查询的对象,否则计数会出现问题,计算出来的是全部的数据的条数
        int count = dishService.count(dishLambdaQueryWrapper);

        //查询当前分类是否关联了菜品,如果已经管理,直接抛出一个业务异常
        if (count > 0){
            //已经关联了菜品,抛出一个业务异常
            throw new CustomException("当前分类项关联了菜品,不能删除");
        }

        //查询当前分类是否关联了套餐,如果已经管理,直接抛出一个业务异常
        LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
        setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
        //注意:这里使用count方法的时候一定要传入条件查询的对象,否则计数会出现问题,计算出来的是全部的数据的条数
        int setmealCount = setmealService.count(setmealLambdaQueryWrapper);
        if (setmealCount > 0){
            //已经关联了套餐,抛出一个业务异常
            throw new CustomException("当前分类项关联了套餐,不能删除");
        }
        //正常删除
        super.removeById(id);
    }

10.文件上传下载

step1.在配置文件添加路径参数

reggie:
  path: D:\img\

step2 编写代码,文件上传 ,创建一个新的CommonController

package com.xlm.reggie.controller;

import com.xlm.reggie.common.R;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.UUID;

@RestController
@RequestMapping("/common")
public class CommonController {

    @Value("${reggie.path}")
    private String basePath;
    /**
     * 文件上传
     * @param file
     * @return
     */
    @PostMapping("/upload")
    public R<String> upload(MultipartFile file) throws IOException {

        //原始文件名称
        String originalFilename = file.getOriginalFilename();

        //使用uid重新生成文件名,防止文件名称重复造成文件覆盖
        String subffix = originalFilename.substring(originalFilename.lastIndexOf("."));
        String fileName = UUID.randomUUID().toString() + subffix;

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

        file.transferTo(new File(basePath + fileName));
        return R.success(fileName);
    }
}

step3.文件下载

package com.xlm.reggie.controller;

import com.xlm.reggie.common.R;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.UUID;

@RestController
@RequestMapping("/common")
public class CommonController {

    @Value("${reggie.path}")
    private String basePath;
    /**
     * 文件上传
     * @param file
     * @return
     */
    @PostMapping("/upload")
    public R<String> upload(MultipartFile file) throws IOException {

        //原始文件名称
        String originalFilename = file.getOriginalFilename();

        //使用uid重新生成文件名,防止文件名称重复造成文件覆盖
        String subffix = originalFilename.substring(originalFilename.lastIndexOf("."));
        String fileName = UUID.randomUUID().toString() + subffix;

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

        file.transferTo(new File(basePath + fileName));
        return R.success(fileName);
    }

    @GetMapping("/download")
    public void dowload(String name, HttpServletResponse response) {

        try {
            FileInputStream in = new FileInputStream(new File(basePath + name));
            ServletOutputStream out = response.getOutputStream();

            //设置回写文件类型
            response.setContentType("image/jpeg");

            int len;
            byte[] buffer = new byte[1024];
            while ((len = in.read(buffer)) != -1){
                out.write(buffer,0,len);
                out.flush();
            }
            in.close();
            out.close();

        } catch (Exception e){
            e.printStackTrace();
        }

    }
}

11.Dto类的理解(类似于学生表-课程表-成绩表)

step1.

setmeal(套餐类):setmeal_id

dish(餐品类):dish_id

setmeal_dish(关联表):setmeal_id、dish_id、id

其中一个套餐包含多个餐品,加入新增一个套餐,也要包含一个List<SetmealDish>、当将setmeal和list<SetmealDish>信息传入后端时,没有可以接受的类,因此创建一个setmealDto类,它继承setmeal而且还给他添加一个属性list<SetmealDish>,这样就可以传入后端了。

@Data
public class SetmealDto extends Setmeal {

    private List<SetmealDish> setmealDishes;

    private String categoryName;
}

step2.但是还需要创建setmeal、dish、setmeal_dish对应的service和mapper,但是controller层只需要setmeal和dish,在conroller层引入他本身的SetmealService和他们的关联类的SetmealDishService即可,如下图所示。

@RestController
@RequestMapping("/setmeal")
@Slf4j
public class SetmealController {

    @Autowired
    private SetmealService setmealService;

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private SetmealDishService setmealDishService;
}

step3.此外,一般还需要在setmealService新建方法来保存数据,因为系统自带的参数是Setmeal而不是SetmealDto

public interface SetmealService extends IService<Setmeal> {
    /**
     * 新增套餐,同时需要保存套餐和菜品的关联关系
     * @param setmealDto
     */
    public void saveWithDish(SetmealDto setmealDto);

    /**
     * 删除套餐,同时需要删除套餐和菜品的关联数据
     * @param ids
     */
    public void removeWithDish(List<Long> ids);
}

step4.实现SetmealService中的新增方法,我们知道传过来的参数为setmealDto类型,他包含setmeal和List<SetmealDish>,因此只需要两步 1. 在setmeal中添加数据(教师类)setmeal 2.在setmealDto(教师-学生类)中添加数据List<SetmealDish> 。 由于List<SetmealDish>中没有自带id,因此要把setmealDto中封装的id取出来给SetmealDto用。

@Service
@Slf4j
public class SetmealServiceImpl extends ServiceImpl<SetmealMapper,Setmeal> implements SetmealService {
    
    @Autowired
    private SetmealDishService setmealDishService;
    @Override
    public void saveWithDish(SetmealDto setmealDto) {
        //1.保存套餐基本信息,操作setmeal,执行insert操作
        this.save(setmealDto);
        
        List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
        setmealDishes.stream().map((item)->{
            item.setSetmealId(setmealDto.getId());
            return item;
        }).collect(Collectors.toList());
        //2.保存套餐和菜品的关联信息,操作setmeal_dish,执行insert操作
        setmealDishService.saveBatch(setmealDishes);
    }
}

step5.在函数头上添加@Transactional 和启动类添加@EnableTransactionManagement

12.套餐信息分页查询(续11.Dto的理解)

当搜索框输入13是,会传递以下分页请求.

在我们的setmealDto类中不仅有setmeal_dish的集合,而且还有继承setmeal的全属性。还有我们新增的categoryName属性,这个是菜品分类名,是category中才有的属性,category中的categoryid和setmeal中的categoryid将这两个表关联起来。

step1.未优化版本

    /**
     * 套餐分页查询
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @GetMapping("/page")
    public R<Page> select(Integer page, Integer pageSize, String name) {
        Page<Setmeal> pageInfo = new Page<>(page, pageSize);
        Page<SetmealDto> pageInfoDto = new Page<>(page, pageSize);
        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper();
        //添加查询条件,根据name进行模糊查询
        queryWrapper.like(name != null,Setmeal::getName,name);
        queryWrapper.orderByDesc(Setmeal::getUpdateTime);

        setmealService.page(pageInfo);

        return null;
    }

可以看出此时没有套餐分类的数据。这个数据在category类中,通过categoryid相关联。因此要引入categoryService来通过id返回Category对象,在找到该对象的名称即可。

 /**
     * 套餐分页查询
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @GetMapping("/page")
    public R<Page> page(int page,int pageSize,String name){
        //分页构造器对象
        Page<Setmeal> pageInfo = new Page<>(page,pageSize);
        Page<SetmealDto> dtoPage = new Page<>();

        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
        //添加查询条件,根据name进行like模糊查询
        queryWrapper.like(name != null,Setmeal::getName,name);
        //添加排序条件,根据更新时间降序排列
        queryWrapper.orderByDesc(Setmeal::getUpdateTime);

        setmealService.page(pageInfo,queryWrapper);

        //对象拷贝
        BeanUtils.copyProperties(pageInfo,dtoPage,"records");
        List<Setmeal> records = pageInfo.getRecords();

        List<SetmealDto> list = records.stream().map((item) -> {
            SetmealDto setmealDto = new SetmealDto();
            //对象拷贝A
            BeanUtils.copyProperties(item,setmealDto);
            //分类id
            Long categoryId = item.getCategoryId();
            //根据分类id查询分类对象
            Category category = categoryService.getById(categoryId);
            if(category != null){
                //分类名称
                String categoryName = category.getName();
                setmealDto.setCategoryName(categoryName);
            }
            return setmealDto;
        }).collect(Collectors.toList());

        dtoPage.setRecords(list);
        return R.success(dtoPage);
    }

setmealService.page(pageInfo,queryWrapper);执行前

执行完分页查询之后,records中就有数据了。

13.批量删除

单个删除和批量删除时的请求

删除的时候不仅要删setmeal表还要删除setmeal_dish这个关联表。

因此需要自己创建service函数去删除。

 /**
     * 删除套餐,同时需要删除套餐和菜品的关联数据
     * @param ids
     */
    @Transactional
    public void removeWithDish(List<Long> ids) {
        //select count(*) from setmeal where id in (1,2,3) and status = 1
        //查询套餐状态,确定是否可用删除
        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.in(Setmeal::getId,ids);
        queryWrapper.eq(Setmeal::getStatus,1);

        int count = this.count(queryWrapper);
        if(count > 0){
            //如果不能删除,抛出一个业务异常
            throw new CustomException("套餐正在售卖中,不能删除");
        }

        //如果可以删除,先删除套餐表中的数据---setmeal
        this.removeByIds(ids);

        //delete from setmeal_dish where setmeal_id in (1,2,3)
        LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.in(SetmealDish::getSetmealId,ids);
        //删除关系表中的数据----setmeal_dish
        setmealDishService.remove(lambdaQueryWrapper);
    }

14.操作Redis

Redis是一个基于内存的key-value数据库

step1.导入坐标

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

step2.设置Redis相关配置,在spring配置下

spring:
  redis:
    host: localhost
    port: 6379
    #password: 123456
    database: 0 #默认操作0号数据库,配置文件中默认有16个数据库 0 - 15号
    jedis:
      pool:
        max-active: 8 #最大连接池
        max-wait: 1ms #连接池中最大阻塞时间
        max-idle: 4 #连接池中最大空闲连接
        min-idle: 0 #连接池中最小空闲连接

step3.对于key-value的操作全靠一个RedisTemplete类来实现,添加配置类。这样我们注入的RedisTemplete就不是默认的,默认的序列化器不适合我们,对于key的默认序列化器是JdkSerializationRedisSerializer,在图形界面为乱码,因此需要设置成新的new StringRedisSerializer()。这样自动装配的RedisTemplate对象就是我们自己的。

/**
 * Redis配置类
 */

@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {

        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();

        //默认的Key序列化器为:JdkSerializationRedisSerializer
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());

        redisTemplate.setConnectionFactory(connectionFactory);

        return redisTemplate;
    }

}

step4.测试

package com.xlm.springdataredis;
@SpringBootTest
@RunWith(SpringRunner.class)
class SpringdataredisApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 操作String类型数据
     */
    @Test
    public void testString(){
        redisTemplate.opsForValue().set("city123","beijing");

        String value = (String) redisTemplate.opsForValue().get("city123");
        System.out.println(value);

        redisTemplate.opsForValue().set("key1","value1",10l, TimeUnit.SECONDS);

        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent("city1234", "nanjing");
        System.out.println(aBoolean);
    }

    /**
     * 操作Hash类型数据
     */
    @Test
    public void testHash(){
        HashOperations hashOperations = redisTemplate.opsForHash();

        //存值
        hashOperations.put("002","name","xiaoming");
        hashOperations.put("002","age","20");
        hashOperations.put("002","address","bj");

        //取值
        String age = (String) hashOperations.get("002", "age");
        System.out.println(age);

        //获得hash结构中的所有字段
        Set keys = hashOperations.keys("002");
        for (Object key : keys) {
            System.out.println(key);
        }

        //获得hash结构中的所有值
        List values = hashOperations.values("002");
        for (Object value : values) {
            System.out.println(value);
        }
    }

    /**
     * 操作List类型的数据
     */
    @Test
    public void testList(){
        ListOperations listOperations = redisTemplate.opsForList();

        //存值
        listOperations.leftPush("mylist","a");
        listOperations.leftPushAll("mylist","b","c","d");

        //取值
        List<String> mylist = listOperations.range("mylist", 0, -1);
        for (String value : mylist) {
            System.out.println(value);
        }

        //获得列表长度 llen
        Long size = listOperations.size("mylist");
        int lSize = size.intValue();
        for (int i = 0; i < lSize; i++) {
            //出队列
            String element = (String) listOperations.rightPop("mylist");
            System.out.println(element);
        }
    }

    /**
     * 操作Set类型的数据
     */
    @Test
    public void testSet(){
        SetOperations setOperations = redisTemplate.opsForSet();

        //存值
        setOperations.add("myset","a","b","c","a");

        //取值
        Set<String> myset = setOperations.members("myset");
        for (String o : myset) {
            System.out.println(o);
        }

        //删除成员
        setOperations.remove("myset","a","b");

        //取值
        myset = setOperations.members("myset");
        for (String o : myset) {
            System.out.println(o);
        }

    }

    /**
     * 操作ZSet类型的数据
     */
    @Test
    public void testZset(){
        ZSetOperations zSetOperations = redisTemplate.opsForZSet();

        //存值
        zSetOperations.add("myZset","a",10.0);
        zSetOperations.add("myZset","b",11.0);
        zSetOperations.add("myZset","c",12.0);
        zSetOperations.add("myZset","a",13.0);

        //取值
        Set<String> myZset = zSetOperations.range("myZset", 0, -1);
        for (String s : myZset) {
            System.out.println(s);
        }

        //修改分数
        zSetOperations.incrementScore("myZset","b",20.0);

        //取值
        myZset = zSetOperations.range("myZset", 0, -1);
        for (String s : myZset) {
            System.out.println(s);
        }

        //删除成员
        zSetOperations.remove("myZset","a","b");

        //取值
        myZset = zSetOperations.range("myZset", 0, -1);
        for (String s : myZset) {
            System.out.println(s);
        }
    }

    /**
     * 通用操作,针对不同的数据类型都可以操作
     */
    @Test
    public void testCommon(){
        //获取Redis中所有的key
        Set<String> keys = redisTemplate.keys("*");
        for (String key : keys) {
            System.out.println(key);
        }

        //判断某个key是否存在
        Boolean itcast = redisTemplate.hasKey("itcast");
        System.out.println(itcast);

        //删除指定key
        redisTemplate.delete("myZset");

        //获取指定key对应的value的数据类型
        DataType dataType = redisTemplate.type("myset");
        System.out.println(dataType.name());

    }
}

15.redis缓存短信验证码

step1.在UserController的sendMsg中加入RedisTemplete

//Redis中保存验证码,时效5min
redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES);

step2.在login

        //从redis中获取验证码
        Object codeInRedis = redisTemplate.opsForValue().get(phone);

step3.登录成功后删除

package com.itheima.reggie.controller;

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 发送手机短信验证码
     * @param user
     * @return
     */
    @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中保存验证码,时效5min
            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中获取保存的验证码
        //Object codeInSession = session.getAttribute(phone);

        //从redis中获取验证码
        Object codeInRedis = redisTemplate.opsForValue().get(phone);

        //进行验证码的比对(页面提交的验证码和Session中保存的验证码比对)
        //if(codeInSession != null && codeInSession.equals(code))
        if(true){
            //如果能够比对成功,说明登录成功

            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.getId());
            redisTemplate.delete(phone);
            return R.success(user);
        }
        return R.error("登录失败");
    }

}

具体代码可以建仓库分支v1.0

16.redis缓存菜品分类

如下所示,点击每个category分类是不需要在查询数据库,保存到redis中即可

3- 11行和47-49行为新增代码,step1.新建key和value的形式 step2.从redis中获取 step3.如果存在。直接获取,如果不存在按原代码执行 step4.如果不存在,最后要保存到redis中。 step5.在save和update代码中删除redis,防止redis和刚更新的数据库数据不一致。

 @GetMapping("/list")
    public R<List<DishDto>> list(Dish dish){
        String key = "dish_" + dish.getCategoryId() + "_" + dish.getStatus();//dish_1369456161516_1
        List<DishDto> dishDtoList = null;
        //从redis中获取缓存
        dishDtoList = (List<DishDto>) redisTemplate.opsForValue().get(key);
        //如果存在,直接返回,不需要查询数据库
        if(dishDtoList != null){
            return R.success(dishDtoList);
        }
        //如果不存在,需要查询数据库,并存放在redis上

        //构造查询条件
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(dish.getCategoryId() != null ,Dish::getCategoryId,dish.getCategoryId());
        //添加条件,查询状态为1(起售状态)的菜品
        queryWrapper.eq(Dish::getStatus,1);

        //添加排序条件
        queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);

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

        dishDtoList = list.stream().map((item) -> {
            DishDto dishDto = new DishDto();

            BeanUtils.copyProperties(item,dishDto);

            Long categoryId = item.getCategoryId();//分类id
            //根据id查询分类对象
            Category category = categoryService.getById(categoryId);

            if(category != null){
                String categoryName = category.getName();
                dishDto.setCategoryName(categoryName);
            }

            //当前菜品的id
            Long dishId = item.getId();
            LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.eq(DishFlavor::getDishId,dishId);
            //SQL:select * from dish_flavor where dish_id = ?
            List<DishFlavor> dishFlavorList = dishFlavorService.list(lambdaQueryWrapper);
            dishDto.setFlavors(dishFlavorList);
            return dishDto;
        }).collect(Collectors.toList());
        //如果不存在,需要查询数据库,并存放在redis上
        redisTemplate.opsForValue().set(key,dishDtoList,60, TimeUnit.MINUTES);
        return R.success(dishDtoList);
    }

update 和save

    @PostMapping
    public R<String> save(@RequestBody DishDto dishDto){
        log.info(dishDto.toString());

        dishService.saveWithFlavor(dishDto);

        //清理所有缓存数据
        Set dishes = redisTemplate.keys("dish_*");
        redisTemplate.delete(dishes);
//        //清理精确地redis缓存
//        String key = "dish_" + dishDto.getCategoryId() + "_" + dishDto.getStatus();
//        redisTemplate.delete(key);
        return R.success("新增菜品成功");
    }

17.Spring Cache

采用注解的方式来实现redis

17.1 Spring Cache介绍

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

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

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

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

CacheManger

描述

EhCacheCacheManager

使用EhCache作为缓存技术

GuavaCacheManager

使用Googke的GuavaCache作为缓存技术

RedisCacheManager

使用Rdis作为缓存技术

17.2 Spring Cache常用注解

注解

说明

@EnableCaching

开启缓存注解功能

@Cacheable

在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放到缓存中

@CachePut

将方法的返回值放到缓存中

@CacheEvict

将一条或者多条数据从缓存中删除

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

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

17.3使用方法

step1. 导入坐标

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

step 2.加入@EnableCaching注解

@Slf4j
@SpringBootApplication
@ServletComponentScan
@EnableTransactionManagement
@EnableCaching
public class ReggieApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReggieApplication.class,args);
        log.info("项目启动成功...");
    }
}

step3.配置application.yml

server:
  port: 8080
spring:
  application:
    #应用的名称,可选
    name: reggie_take_out
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
      username: root
      password: 1234
  redis:
    host: localhost
    port: 6379
    #password: 123456
    database: 0 #默认操作0号数据库,配置文件中默认有16个数据库 0 - 15号
    jedis:
      pool:
        max-active: 8 #最大连接池
        max-wait: 1ms #连接池中最大阻塞时间
        max-idle: 4 #连接池中最大空闲连接
        min-idle: 0 #连接池中最小空闲连接
  cache:
    redis:
      time-to-live: 1800000 #单位ms 180w毫秒就是30min
mybatis-plus:
  configuration:
    #在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID
reggie:
  path: D:\img\

step4.修改代码

/**
 * 新增套餐
 * @param setmealDto
 * @return
 */
@CacheEvict(value = "setmealCache",allEntries = true)
@PostMapping
public R<String> save(@RequestBody SetmealDto setmealDto){
    log.info("套餐信息:{}",setmealDto);

    setmealService.saveWithDish(setmealDto);

    return R.success("新增套餐成功");
}


/**
 * 删除套餐
 * @param ids
 * @return
 */
@CacheEvict(value = "setmealCache",allEntries = true)
@DeleteMapping
public R<String> delete(@RequestParam List<Long> ids){
    log.info("ids:{}",ids);

    setmealService.removeWithDish(ids);

    return R.success("套餐数据删除成功");
}

/**
 * 根据条件查询套餐数据
 * @param setmeal
 * @return
 */
@Cacheable(value = "setmealCache", key = "#setmeal.categoryId + '_' + #setmeal.status")
@GetMapping("/list")
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);
}
}

18. MySQL读写分离

主从复制的介绍

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

MySQL复制过程分成三步:

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

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

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

其中从库可以有多个

19.Nginx

下载与安装

可以到Nginx官方网站下载Nginx的安装包,地址为:https://nginx.org/en/download.html

安装过程:

1、安装依赖包 yum -y install gcc pcre-devel zlib-devel openssl openssl-devel

先yum install wget ,这样就能使用wget命令

2、下载Nginx安装包wget https://nginx.org/download/nginx-1.16.1.tar.gz

3、解压 tar -zxvf nginx-1.16.1.tar.gz

4、cd nginx-1.16.1

5、./configure --prefix=/usr/local/nginx

6、make && make install

安装好nginx

conf文件夹存放配置文件、html存放静态页面文件、logs存放日志文件、sbin存放脚本文件(比如启动停止Nginx等)。conf/nginx.cong 是nginx配置文件。sbin/nginx用于启动停止nginx服务

查看版本

检查配置文件正确性

启动和停止

在sbin目录下启动Nginx服务使用如下命令: ./nginx

如果启动的时候端口被占用了 我们可以:

sudo netstat -ntlp 查看运行中端口

sudo kill 进程id

然后再次启动服务就可以了

停止Nginx服务使用如下命令: ./nginx -s stop

启动完成后可以查看Nginx进程: ps -ef | grep nginx

要在电脑访问 我们先关闭防火墙: systemctl stop firewalld

这样我们直接 访问linux的静态ip就可以了

由于nginx命令这些都太枯燥,我也听不下去,就不写了。可以看其他人的博客,啊呜呜

20.Nginx的具体应用

  • 部署静态资源

  • 反向代理

  • 负载均衡

1.部署静态资源

nginx服务器部署静态资源要比Tomcat更加高效。只需要将静态资源放进html文件夹中即可。

默认访问80端口,也可以在conf文件中修改端口号。

2.正向代理和反向代理

正向代理

Nginx 不仅可以做反向代理,实现负载均衡。还能用作正向代理来进行上网等功能。 正向代理:如果把局域网外的 Internet 想象成一个巨大的资源库,则局域网中的客户端要访 问 Internet,则需要通过代理服务器来访问,这种代理服务就称为正向代理。

  • 简单一点:通过代理服务器来访问服务器的过程 就叫 正向代理。

  • 需要在客户端配置代理服务器进行指定网站访问

反向代理

  • 反向代理,其实客户端对代理是无感知的,因为客户端不需要任何配置就可以访问。

  • 我们只 需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,在返 回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器 地址,隐藏了真实服务器 IP 地址。

客户端可以知道正向代理服务器的存在,而反向代理客户端并不知道真正目标服务器的存在。

客户端以为反向代理服务器就是自己的目标服务器。

配置反向代理

只需要添加一句转发的话

3.负载均衡

也相当于反向代理,但代理的不是一台服务器,而是多台服务器,负载均衡器内置算法,将客户端命令转发给web服务器

负载均衡配置

weight表示权重,表示分发到这个web服务器的概率权重。默认为1

负载均衡策略

21.前后端分离

1.YApi的介绍

主要是制作api文档,了解即可

2.Swagger介绍
  • 使用Swagger你只需要安装它的规范去定义接口以及接口相关的信息,再通过Swagger衍生出来的一些列项目和工具就可以做到生成各种格式的接口文档,以及在线接口调试页面等

使用方式

step1.导入meven坐标

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

step2.导入knife4j相关配置(WebMvcConfig)

添加两个注解 + 两个静态资源映射 + 两个方法

@Slf4j
@Configuration
@EnableSwagger2
@EnableKnife4j
public class WebMvcConfig extends WebMvcConfigurationSupport {

    /**
     * 设置静态资源映射
     * @param registry
     */
    @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/");
        registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
        registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
    }

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

    @Bean
    public Docket createRestApi() {
        // 文档类型
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.itheima.reggie.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("瑞吉外卖")
                .version("1.0")
                .description("瑞吉外卖接口文档")
                .build();
    }
}

step3.定义不需要拦截器处理

后四个为新添加的

 //定义不需要处理的请求路径
        String[] urls = new String[]{
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**",
                "/common/**",
                "/user/sendMsg",
                "/user/login",
                "/doc.html",
                "/webjars/**",
                "/swagger-resources",
                "/v2/api-docs"
        };

step4.访问 http://localhost:8080/doc.html

注解介绍:

为了解决上述的问题,Swagger提供了很多的注解,通过这些注解,我们可以更好更清晰的描述我们的接口,包含接口的请求参数、响应数据、数据模型等。核心的注解,主要包含以下几个:

@RestController
@RequestMapping("/setmeal")
@Slf4j
@Api(tags = "套餐相关接口")
public class SetmealController {
@CacheEvict(value = "setmealCache",allEntries = true)
    @PostMapping
    @ApiOperation(value = "新增套餐接口")
    public R<String> save(@RequestBody SetmealDto setmealDto){
        log.info("套餐信息:{}",setmealDto);

        setmealService.saveWithDish(setmealDto);

        return R.success("新增套餐成功");
    }
@Data
@ApiModel("返回结果")
public class R<T> implements Serializable{
/**
 * 套餐
 */
@Data
@ApiModel("套餐")
public class Setmeal implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty("主键")
    private Long id;


    //分类id
    @ApiModelProperty("分类id")
    private Long categoryId;


    //套餐名称
    @ApiModelProperty("套餐名称")
    private String name;


    //套餐价格
    @ApiModelProperty("套餐价格")
    private BigDecimal price;


    //状态 0:停用 1:启用
    @ApiModelProperty("状态")
    private Integer status;


    //编码
    @ApiModelProperty("套餐编号")
    private String code;


    //描述信息
    @ApiModelProperty("描述信息")
    private String description;


    //图片
    @ApiModelProperty("图片")
    private String image;


    @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;

}

   @GetMapping("/page")
    @ApiOperation(value = "套餐分页查询接口")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "page",value = "页码",required = true),
            @ApiImplicitParam(name = "pageSize",value = "每页记录数",required = true),
            @ApiImplicitParam(name = "name",value = "套餐名称",required = false)
    })
    public R<Page> page(int page,int pageSize,String name){
        //分页构造器对象
        Page<Setmeal> pageInfo = new Page<>(page,pageSize);
        Page<SetmealDto> dtoPage = new Page<>();

        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
        //添加查询条件,根据name进行like模糊查询
        queryWrapper.like(name != null,Setmeal::getName,name);
        //添加排序条件,根据更新时间降序排列
        queryWrapper.orderByDesc(Setmeal::getUpdateTime);

        setmealService.page(pageInfo,queryWrapper);

        //对象拷贝
        BeanUtils.copyProperties(pageInfo,dtoPage,"records");
        List<Setmeal> records = pageInfo.getRecords();

        List<SetmealDto> list = records.stream().map((item) -> {
            SetmealDto setmealDto = new SetmealDto();
            //对象拷贝
            BeanUtils.copyProperties(item,setmealDto);
            //分类id
            Long categoryId = item.getCategoryId();
            //根据分类id查询分类对象
            Category category = categoryService.getById(categoryId);
            if(category != null){
                //分类名称
                String categoryName = category.getName();
                setmealDto.setCategoryName(categoryName);
            }
            return setmealDto;
        }).collect(Collectors.toList());

        dtoPage.setRecords(list);
        return R.success(dtoPage);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值