瑞吉外卖总结1

196 阅读25分钟
							瑞吉外卖日常总结

一:用到的知识点:

1.

1@Slf4j  //可以直接使用:来输出日志:  log.info("项目启动成功");
2:导入到项目中的静态资源时:(1)放到resources目录下的:static/template文件夹中
     之后才可以被访问到;
  --否则的话就会被拦截:所以我们需要配置(2)" 拦截器 "
 
3.如何接收的参数是"json"格式的:可以在 参数前 +" @ResponseBody"注解;
  !!!:而且 " @ResponseBody"注解也可以加到 "类的最上面" ---->:代表这个的类的数据都以json的形式返回;
前后端交互一般都使用json!!!
  

2.拦截器的配置:



/*
WebMvcConfig是配置类:
 */
@Slf4j
@Configuration //2.代表是配置类:
public class WebMvcConfig extends WebMvcConfigurationSupport { //1.继承类

    //3. 重写方法-->设置资源映射路径(当在浏览器中访问a.index时就会映射到对应的目录(resource)下的静态资源)

    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("访问静态资源");
        registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");//设置静态资源访问路径到"classpath:/backend/"
        registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");//设置静态资源访问路径到"classpath:/front/"

    }
}


4.如果要返回json形式:handler形参处 加上:@RequestBody
5.

3.全局异常处理器:----Aop(面向切面编程)思想:

全局异常处理Aop:底层是基于代理,"代理controller"---将controller的handler方法"拦截"到,如果抛异常的话,统一在全局异常处理类的某一个方法中进行处理;!!!
    
步骤:
    1创建异常处理类
    2.在异常处理类上加@ControllerAdvice注解:
      @ControllerAdvice(annotations={RestController.class,Controller.class}) :代表全局异常处理器会  "拦截" "加了      	 @RestController和@Controller 注解的 类及其方法"!!!!
    
     3.定义全局异常处理器的方法:a:都要加方法上注解:"@ExceptionHandler(异常类型.class)"  b:在方法形参处写相应的异常类对象:用来接收controller的异常信息; eg:Except ex
        之后,类报了相关异常,就会到全局异常处理类匹配对应异常处理方法.
      

@ControllerAdvice(annotations = {RestController.class, Controller.class}) //全局异常处理器拦截的类
@Slf4j
@ResponseBody //返回json形式结果

public class GlobalExceptionHandler {

    //异常处理方法:
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
                                      //接收异常
        log.info(ex.getMessage());
        return R.error("失败了,out");
    }

}

之后Controller层中的handler报异常之后就会进入异常处理器来找对应的异常处理方法;

4.分页查询:

一:定义配置类:"分页操作是在拦截器中执行的,所以先new一个拦截器,再new一个分页插件,然后将分页插件放入拦截器中返回"

    :定义一个配置类:
@Configuration //代表是配置类

public class MybatisPlusConfig {
    //通过拦截器的方式将分页插件加进来
@Bean  //加入Spring中
public MybatisPlusInterceptor mybatisPlusInterceptor(){
    //a:创建拦截器对象
    MybatisPlusInterceptor mybatisPlusInterceptor=new MybatisPlusInterceptor();
    //b:将分页插件加入拦截器
    mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
    //c:返回
    return mybatisPlusInterceptor;
   }
}


二:在Controller中书写handler方法:
    
  "只需要1.构造page对象 2.构造wrapper对象 并 添加过滤“条件” , 3.使用service对象调用page()方法:将page对象和wrapper对象传入即可;
    
    
    
    //(4)分页查询:
      //流程:前端页面发送请求-->将分页查询参数(page,pageSize,name)提交到服务器-->
      // 服务端Controller接收前端提交的数据 + 调用service层查询数据-->:service层调用Mapper层来操作数据库进行分页查询
      // -->Controller层将查询到"分页数据"响应给前端--->页面接收到分页数据并展示

    //handler:接收前端参数
    @GetMapping("/page")
    public R<Page> page(int page,int pageSize,String name){
        log.info("page={},pageSize={},name={}",page,pageSize,name);

        //分页查询:select ... from +条件 (lambda构造器)

        //1:构造分页构造器page对象
        Page pageInfo=new Page(page,pageSize);

        //2.构造条件构造器:()wrapper对象
           //(1):
        LambdaQueryWrapper<Employee>queryWrapper=new LambdaQueryWrapper<>();

        //(2).添加过滤条件: a:name不为null的话才将name作为条件
        queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);//意为:如果name不为null的话就将name作为条件加入sql语句!!!
        //b:添加排序条件:
        queryWrapper.orderByDesc(Employee::getUpdateTime); //将更新时间作为条件加入sql语句

        //3.执行查询语句!!!!!!!:使用service调用"page()方法" 进行分页操作
        employeeService.page(pageInfo,queryWrapper); //不需要返回值,因为它会自己进行分页查询:之后会自动将结果封装到pageInfo对象中;!!!!!!!
        return  R.success(pageInfo);
    }

5.过滤器:

package com.itheima.reggie.Filter;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.common.BaseContext;
import com.itheima.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.util.AntPathMatcher;

import javax.print.DocFlavor;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


/**
 * 使用过滤器:检查用户是否登录
 */

@Slf4j
@WebFilter(filterName = "LoginCheckFilter",urlPatterns = "/*") //a:拦截器:设置属性:名称+拦截路径
public class LoginCheckFilter implements Filter {  //b:实现Filter接口 + doFilter方法

    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; //向下转型:因为ServletRequest的子类HttpServletRequest才有getRequestUrl方法:即获取请求的路径!!!!
        HttpServletResponse response=(HttpServletResponse) servletResponse;

        //1.获取到拦截路径:
        String requestUrl=request.getRequestURI();

        log.info("拦截到的请求:{}",request.getRequestURI());

        //2.定义不需要拦截的路径
        String [] urls= new String[]{
                "/employee/login", //登录时不需要拦截
                "/employee/logout",//退出时不需要拦截
                "/backend/**", //访问静态后端页面时不需要拦截:因为只是访问页面,并不能操作其中数据
                "/front/**" //访问静态前端资源时不需要拦截

        };
        //3.进行比较(通过下方定义的方法来比较路径)
        boolean check = check(urls, requestUrl);

        if(check){ //如果匹配成功:代表不需要处理
            log.info("本次请求{}不需要处理",requestUrl);

        filterChain.doFilter(request,response); //放行
        return;
    }

        //4.如果匹配不成功:代表需要进行处理(判断它现在是否登录,如果未登录则返回去登录):
           //判断登录状态:如果已经登录则直接放行:
        Object employee = request.getSession().getAttribute("employee");
        if(employee!=null){ //如果能从session中取出id,说明已经登录了
            log.info("用户已经登录,用户id为{}",request.getSession().getAttribute("employee"));//拿到登录用户的id
            filterChain.doFilter(request,response); //则放行

          Long empId=(Long) employee;
            BaseContext.setCurrentId(empId); //设置empId值
            /**
             * 因为过滤器中可以直接取到empId的值。,所以取到后,将其存入ThreadLocal中
             * 供自动填充类 取得
             */
            "(3)"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

        }

        log.info("用户未登录");

        //5.如果未登录则返回登录结果,通过输出流的方式向客户端页面响应数据
     // response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
        /*
        这里先不写了,写了的话影响后面!!!!!!!!!!!
         */
        return;

    }


    //定义方法:路径匹配:判断是否放行:将拦截到的路径与String数组中需要放行的路径进行比较
    public boolean check(String [] urls,String requestUrl){
        for (String url : urls) {
            boolean match=PATH_MATCHER.match(url,requestUrl);
            if(match){
                return true;
            }
        }
        return false;
    }

}




6.公共字段填充:


    (1)公共字段的自动填充:
       eg:在新增员工时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工时需要设置修改时间和修改人等字段
          这些字段是公共字段,在很多表中都有这些字段----所以我们可以使用Mybatis Plus提供的“公共字段自动填充功能”来将这些公共字段进行统一管理简化开发

       Mybatis Plus 公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码;

"实现步骤":    1..在相应实体类的对应属性上加入@TbaleField注解,指定哪些字段需要自动填充以及自动填充的策略
              2. 按照框架要求编写元数据对象处理器,在此类中为公共字段赋值,这个类要实现MetaObjectHandler接口

  '之后,进行自动填充的字段,我们就不需要每次都管理了,eg:每次新增数据时都要设置更新时间,但是在我们将“更新时间”设置为:插入//更新 自动填充时,在每次插入数据时就不需要再管更新时间了'
1..在相应实体类的对应属性上加入@TbaleField注解,指定哪些字段需要自动填充以及自动填充的策略
    a:insert:在进行插入操作时,进行字段的自动填充   b;update: 在进行更新操作时,进行字段的自动填充 
 eg:"Employee实体类":

     //在实体类上:通过@TableField来指定哪些是公共字段,并指定在什么时机进行字段填充!!!
    @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.具体需要填充什么数据:
     a:创建一个类MyMetaObjectHandler ,implement MetaObjectHandler类,实现两个方法:insertFill(当执行插入操作时,就会进入这个方法)、updateFill(当执行更新操作时,就会进行这个方法)
    
     b:设置自动填充的值:
               meta.setValue("实体类中需要管理的属性名","自动填充的值")
 
 eg:
                   
                   
 "(3)"   !!!!!!!!               

@Component
@Slf4j

public class MyMetaObjectHandler implements MetaObjectHandler {//继承元数据处理器

    //插入操作时,进行自动填充
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("各个字段自动填充[insert]...");
        log.info(metaObject.toString());

        metaObject.setValue("createTime", LocalDateTime.now());
        metaObject.setValue("updateTime", LocalDateTime.now());
        
        /**
         * 因为在过滤器器中已经将获得到的id通过BaseContext中threadLocal的set方法存入了threadLocal
         * 在这里只需要调用其get方法从threadLocal中获得即可;
         */
        metaObject.setValue("createUser",BaseContext.getCurrentId());
        metaObject.setValue("updateUser",BaseContext.getCurrentId());
    }


    //更新操作时,进行自动填充
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("公共字段自动填充[update]...");
        log.info(metaObject.toString());

        long id = Thread.currentThread().getId();
        log.info("线程id为:{}",id);

        //修改的时候只需要填充这两个即可
        metaObject.setValue("updateTime", LocalDateTime.now());
        metaObject.setValue("updateUser",BaseContext.getCurrentId());

    }

}

    1.现在存在的问题是:需要设置 登录用户/更新用户id 自动填充,但是用户id是从前端传过来的,在 MyMetaObjectHandler 类中无法       取到,所以无法设置id自动填充的值,但是我们可以使用ThreadLocal!!!!
        
    2.前端每发送一次http请求,就会在后端开启一个新的线程。
       LoginCheckFilter的doFilter方法
       EmployeeController中update方法
       MyMetaObjectHandler的updateFill方法 "这三个都属于同一个线程"
    
    3.ThreadLocal并不是一个Thread,而是Thread的局部变量 ,ThreadLocal为"每一个线程"提供一份单独的存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问; 作用域是单个线程内
        
      ThreadLocal常用的方法:
                public void set(T value) :设置当前的线程局部变量的值
                public T get() :返回当前线程所对应的线程局部变量的值
    4"!!!":获取id的方法:
            "a:我们可以在  LoginCheckFilter的doFilter方法 中获取当前登录用户的id(即从session中获取),b:并调用ThreadLocal的set方法设置当前线程的线程局部变量的值(用户id), c:然后在  MyMetaObjectHandler的updateFill的updateFill方法 中调用ThreadLocal的get方法来获得当前线程所对应 的线程局部变量的值(用户id)"
        因为是同一个线程,所以可以获得!!!
        
        实现步骤:
           (1)编写BaseConyext工具类,基于ThreadLocal封装的工具类:里面放其两个方法
           (2)在LoginCheckFilter中的doFilter中调用BaseContext方法来设置当前登录用户的id
           (3)在MyMetaObjectHandler的方法中调用BaseContext方法来获取当前登录用户的id
        
        
(1)       !!!!!!!!! 
/**
 * BaseContext类是:基于ThreadLocal封装的工具类,用于保存和获取当前登录用户的id
      setCurrentId:用来设置值
      getCurrentId:用来获取值
 */
public class BaseContext {
    
    private static ThreadLocal<Long> threadLocal=new ThreadLocal<>();//new线程变量对象
    public static void setCurrentId(Long id){ //set方法来设置id
        threadLocal.set(id);
    }
    
    public static Long getCurrentId(){
        
        return threadLocal.get(); //get 方法来获取id
    }
    
}

第(2)在过滤器上!!!!!;
    因为过滤器可以通过session获取id,然后将id用ThreadLocal的set方法设置,以供自动填充类上 通过 get 方法调用

一:登录/退出功能

(1)登录功能:

分析!!!:  用户输入账号、密码-->之后点击登录按钮--->发送登录请求--->请求服务端的controller、service、mapper、数据库-->验证数据库中是否存在(通过" 查询数据库 "

@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {

    @Autowired  //拿到Service对象
    private EmployeeService employeeService;

    //handler方法:(接收前端传入的数据+进行处理+返回)
    @PostMapping("login") //a:前端访问路径
    public R<Employee> login(HttpServletRequest request,@RequestBody Employee employee) { //b:接收前端传入的json形式的参数:username和id(这里直接实验employee对象代替)
        //登录成功之后,需要将employee对象的id存到session一份表示登录成功,然后之后如果想要获取当前的用户的话:直接通过request.getSession()获取



//前端输入的账号、密码、状态需要进行验证!!!


        //1.首先将页面提交的密码进行MD5加密
        String password = employee.getPassword();
        password = DigestUtils.md5DigestAsHex(password.getBytes());

        //2.比对时:首先比用户名:将页面提交用户名username"查询数据库"与数据库中的用户名进行比对
        LambdaQueryWrapper<Employee> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Employee::getUsername, employee.getUsername()); //封装条件:eq:判断实体类的username和数据库中的username是否一致
        Employee emp = employeeService.getOne(lambdaQueryWrapper);//使用service对象调用条件进行查询:
      // !!!!getOne:根据lambda条件是否可以查询到employee对象(后面都得使用这个查询到的emp对象)!!!!!!!!!!!!!!!!!!!

        //3.如果没有查询到用户名就返回失败信息:
        if (emp == null) {
            return R.error("登录失败");
        }

        //4.然后将页面提供的经过加密的md5密码 进行"数据库查询" 与数据库中的密码比较。
        //!!!直接使用上面通过用户名获取到的数据库中的emp对象
        if (!emp.getPassword() .equals(password)) //将数据库对象emp中的密码与前端传入的密码进行比较
        {
            return R.error("登录失败"); //将对象信息返回
        }

        //5.查看员工状态 (看查询到的数据库中对象emp的状态  是否为0!!!)
        if (emp.getStatus() == 0) {
            return R.error("账号已禁用");
        }

        //6.登录成功将员工id存入session并返回
        request.getSession().setAttribute("employee", emp.getId());//将查询到的数据库对象emp的id存入session,之后携带返回前端
        return R.success(emp);  //c:返回给前端对应的结果
    }


}

(2)退出功能:

退出功能实现:
      用户点击页面中的 “退出按钮”-->发送请求到服务端-->服务端在controller中接收请求.
    
Controller中的逻辑:1.接收前端参数+响应结果  2.调用执行相关逻辑:清理session中的用户id() :(因为上一次登录成功之后就将id存到session中并返回前端了,下次请求时仍然会携带session,但是它已经退出了,所以要清楚session中的id)
                                                 
 @PostMapping("/logout")
   public R<String> logout(HttpServletRequest request){ //接收前端发送的请求
        //分析:a:因为返回的结果只需要提升信息即可:所以泛型使用String即可
        request.getSession().removeAttribute("employee"); //b:去除session中的id(就是去除employee属性,因为id存在它里面)

        return R.success("退出成功");
   }

二:员工管理:

(1)完善登录功能

a:存在问题:我们可以直接访问其它页面,即使我们没有登录!!!
         -->用户先登录才能访问应用中的页面以及相关数据;
b:方法:使用拦截器或者过滤器-->:如果没有登录就跳转到登录页面;

---->这里我们使用"过滤器"来实现:
     步骤:
         1.:创建自定义过滤器(类): 
                 a:加@WebFilter("LoginCheckFilter(拦截器名称随便起)",urlPattern="/"("\"代表拦截所有))
                 b:实现implents Filter 接口 + 实现doFilter方法
         2.:在启动类上加入@ServletComponentScan(开启组件扫描,过滤器才会生效)
         3.:完善处理逻辑


/**
 * 使用过滤器:检查用户是否登录
 */

@Slf4j
@WebFilter(filterName = "LoginCheckFilter",urlPatterns = "/*") //a:拦截器:设置属性:名称+拦截路径
public class LoginCheckFilter implements Filter {  //b:实现Filter接口 + doFilter方法

    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; //向下转型:因为ServletRequest的子类HttpServletRequest才有getRequestUrl方法:即获取请求的路径!!!!
        HttpServletResponse response=(HttpServletResponse) servletResponse;

        //1.获取到拦截路径:
        String requestUrl=request.getRequestURI();

        log.info("拦截到的请求:{}",request.getRequestURI());

        //2.定义不需要拦截的路径
        String [] urls= new String[]{
                "/employee/login", //登录时不需要拦截
                "/employee/logout",//退出时不需要拦截
                "/backend/**", //访问静态后端页面时不需要拦截:因为只是访问页面,并不能操作其中数据
                "/front/**" //访问静态前端资源时不需要拦截

        };
        //3.进行比较(通过下方定义的方法来比较路径)
        boolean check = check(urls, requestUrl);

        if(check){ //如果匹配成功:代表不需要处理
            log.info("本次请求{}不需要处理",requestUrl);

        filterChain.doFilter(request,response); //放行
        return;
    }

        //4.如果匹配不成功:代表需要进行处理(判断它现在是否登录,如果未登录则返回去登录):
           //判断登录状态:如果已经登录则直接放行:
        Object employee = request.getSession().getAttribute("employee");
        if(employee!=null){ //如果能从session中取出id,说明已经登录了
            log.info("用户已经登录,用户id为{}",request.getSession().getAttribute("employee"));
            filterChain.doFilter(request,response); //则放行

        }

        log.info("用户未登录");

        //5.如果未登录则返回登录结果,通过输出流的方式向客户端页面响应数据
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
        return;

    }


    //定义方法:路径匹配:判断是否放行:将拦截到的路径与String数组中需要放行的路径进行比较
    public boolean check(String [] urls,String requestUrl){
        for (String url : urls) {
            boolean match=PATH_MATCHER.match(url,requestUrl);
            if(match){
                return true;
            }
        }
        return false;
    }

}




(2)新增员工:

执行流程:
在前端页面填入相关信息-->提交-->前端发送请求到服务端-->服务端controller接收+返回 +调用service将数据进行保存-->Service调用mapper操作数据库,保存数据

    //(3)新增员工;
    /*
    因为登录时:数据库中的密码、创建时间、更新时间、CreatUser和UpdateUser都没有设置
    所以要在这里给它设置值
     */
    @PostMapping
    public R<String> save(HttpServletRequest request, @RequestBody Employee employee) { //因为是json形式:所以加入注解

        log.info("新增员工,员工信息是:{}",employee.toString());
        //(1)接收前端参数:employee对象:现在employee对象中已经有前端传入:账号、姓名、手机号、身份证、性别
        //但是还有一些其它信息没有设置

        //(2):将其它信息存入employee对象
        //a:设置密码:123456,首先需要md5加密:
        employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));

        //b:设置时间
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());
        //c;设置登录用户的id
        Object object = request.getSession().getAttribute("employee"); //首先获取id
        Long empId = (Long) object; //向下转型,因为要的是数字id,把id插入数据库中的CreatUser和UpdateUser
        employee.setCreateUser(empId);
        employee.setUpdateUser(empId);

        //(3)调用service对象的save方法直接存入数据库(因为继承了IService,提供)
        employeeService.save(employee);

        return R.success("新增员工成功");

        //(4)面临问题:因为数据库username设置了唯一,所以当前端插入两条username相同的数据时,就会报异常;
         //--->:所以我们可以使用“异常处理器”进行全局统一捕获!!!!!---Aop!!!

    }


(3)分页查询员工信息:

1)定义配置类:
一:定义配置类:"分页操作是在拦截器中执行的,所以先new一个拦截器,再new一个分页插件,然后将分页插件放入拦截器中返回"

    :定义一个配置类:
@Configuration //代表是配置类

public class MybatisPlusConfig {
    //通过拦截器的方式将分页插件加进来
@Bean  //加入Spring中
public MybatisPlusInterceptor mybatisPlusInterceptor(){
    //a:创建拦截器对象
    MybatisPlusInterceptor mybatisPlusInterceptor=new MybatisPlusInterceptor();
    //b:将分页插件加入拦截器
    mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
    //c:返回
    return mybatisPlusInterceptor;
   }
}
2)在controller中书写handler方法:
 //(4)分页查询:
      //流程:前端页面发送请求-->将分页查询参数(page,pageSize,name)提交到服务器-->
      // 服务端Controller接收前端提交的数据 + 调用service层查询数据-->:service层调用Mapper层来操作数据库进行分页查询
      // -->Controller层将查询到"分页数据"响应给前端--->页面接收到分页数据并展示

    //handler:接收前端参数
    @GetMapping("/page")
    public R<Page> page(int page,int pageSize,String name){
        log.info("page={},pageSize={},name={}",page,pageSize,name);

        //分页查询:select ... from +条件 (lambda构造器)

        //1:构造分页构造器page对象
        Page pageInfo=new Page(page,pageSize);

        //2.构造条件构造器:()wrapper对象
           //(1):
        LambdaQueryWrapper<Employee>queryWrapper=new LambdaQueryWrapper<>();

        //(2).添加过滤条件: a:name不为null的话才将name作为条件
        queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);//意为:如果name不为null的话就将name作为条件加入sql语句!!!
        //b:添加排序条件:
        queryWrapper.orderByDesc(Employee::getUpdateTime); //将更新时间作为条件加入sql语句

        //3.执行查询语句!!!!!!!:使用service调用"page()方法" 进行分页操作
        employeeService.page(pageInfo,queryWrapper); //不需要返回值,因为它会自己进行分页查询:之后会自动将结果封装到pageInfo对象中;!!!!
        return  R.success(pageInfo);
    }

(4)启用/禁用员工账号:

  //(5):启用/禁用员工账号:-->根据”id“!!!
     
      1.账号被禁用的员工不能登录系统,启用后的员工才可以正常登录
      只有管理员admin可以对其它员工进行禁用、启用
       
    2.过程分析:
          前端发送请求-->将id、status(根据id修改其status)提交到服务端--->服务端接收页面提交的参数 + 调用service更新数据
          -->service调用mapper操作数据库
    3. !!!" 启用、禁用员工账号,本质上就是 "更新操作" ,即对status状态字段进行操作,在Controller中创建一个update方法,此方法是一个通用的修改员工信息的方法"
     */
     
    //根据id修改员工信息!!!!!!!
    @PutMapping
    public R<String> update(HttpServletRequest request,@RequestBody Employee employee){
      
  
        //对接收的前端employee对象 的 属性进行修改!!!
        //Object employee1 = request.getSession().getAttribute("employee");//获得当前用户id  --》:
        //Long empId=(Long) employee1; //将id转为数字long类型
        //employee.setUpdateUser(empId); //设置新的时间+用户
        //employee.setUpdateTime(LocalDateTime.now());

        "这些就不需要了,因为我们设置了自动填充,在更新时,会自动进行填充更新时间、更新人等!!!"
       

        employeeService.updateById(employee); //将修改过新的employee对象传入update()方法进行更新
        return R.success("员工信息修改成功");
    }

(5)修改员工信息

(6)编辑员工信息:(1:即先要将已有数据回显:即先去数据库查询数据--->仍是根据id查询!!!)
     之后的更新操作:复用上方的新增员工操作
     执行过程: 前端修改内容,点击“保存”,请求服务端,同时提交员工的id参数!!-->后端接收,然后根据id查询用户信息,然后以json形式返回给页面
     */

    @GetMapping("/{id}")
    public R<Employee> getById(@PathVariable Long id){
        log.info("根据id查询员工信息...");
        Employee employee = employeeService.getById(id);

        //之后编辑完之后,点击保存,其实还是复用的上面的update方法来进行更新的!!!!!!!!
        return R.success(employee);
    }

三:菜品分类管理:

(1)新增菜品分类

 /**
     * (2)新增菜品分类:
     *   需求分析:通过前端的 “新增菜品分类” 和 “新增套餐分类” 按钮 来新增分类;
     *   数据分析:“新增分类”:实际就是将新增的窗口 录入 数据库中的category表 (这个表中存储菜品的分类)
     *   代码开发:
     *       实体类Category
     *       Mapper接口:CategoryMapper
     *       业务层接口CategoryServiceImpl
     *       控制层:CategoryController
     *   新增菜品分类 和 新增套餐分类 的url是一样的,只是type字段值不一样,用来区分类型:1代表'菜品分类",‘2’代表套餐分类
     *   执行流程!!!:前端填写完分类信息之后,点击确定-->会将窗口中输入的数据:{name,sort,type}以json的形式提交到服务端-->服务端接收页面提交的数据并调用Service将数据进行保存-->Service调用Mapper操作数据库,保存数据到category表中(MybatisPlus底层执行)
     */





@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {

    @Autowired //注入service对象
    private CategoryService categoryService;


    @PostMapping
    public R<String> save(@RequestBody Category category){ //json形式

        log.info("新增分类的信息为:{}",category.toString());

        categoryService.save(category); //!!service对象调用其save方法进行保存;(直接使用mybatisplus提供的方法:)
        return R.success("新增分类成功");
    }
    
}

(2)菜品分页查询

/**
     * (3)菜品分页查询:分页查询和前面一样,只不过是操作的表不一样
     *  需求分析:在前端分页进行展示列表数据
     *  执行流程:页面发送请求-->前端将分页参数:page、pageSize 传到服务器-->服务器Controller接收数据,并调用service查询数据,service调用操作数据库,查询分页数据
     *          -->最后controller再将数据返回给前端页面
     */


@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {

    @Autowired //注入service对象
    private CategoryService categoryService;


    @GetMapping("/page")
    public R<Page> page(int page,int pageSize){ //1.接收前端传到分页信息

        Page<Category> pageInfo=new Page<>(page,pageSize); //a:new 分页构造器对象
        //b:new条件构造器
        LambdaQueryWrapper<Category> queryWrapper=new LambdaQueryWrapper<>();
        //添加排序条件:用sort字段:进行升序(Asc)
        queryWrapper.orderByAsc(Category::getSort);

        //3.进行分页查询
        categoryService.page(pageInfo,queryWrapper);

        return R.success(pageInfo);

    }
}

(3)删除菜品分类:!!!



/**
     * (3)删除菜品分类:
     *    需求分析:删除对应的分类,但是当”菜品分类“关联了菜品或者套餐时此分类就不允许删除,给用户提示;
     *            所以需要进行判断!!!
     *    删除分类:肯定是根据唯一标识来查询删除:即id!!!!!!!!!
     *    执行流程: 点击删除按钮-->前端发送请求,并将id发送到服务端--->再服务端通过Controller来接收-->controller调用service删除数据-->service调用数据库来查询数据
     */



1."Controller层"@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {

    @Autowired //注入service对象
    private CategoryService categoryService;


    @DeleteMapping //删除!!
    public R<String> delete(Long id){ //接收前端传的参数 :

          categoryService.remove(id);  //在Service接口定义方法,impl层实现该方法;
       return R.success("删除分类成功");
       
           检查删除的分类是否关联了菜品或者套餐,所以就不能直接使用mybatisPlus直接提供的:categoryService.removeById(id)来            进行直接删除
           因为直接调用这个方法的话,没有 " 判断是否可以删除的逻辑 ",所以需要我们自己在service层中定义逻辑!!!
               
        /**
         * a:准备实体类:Dish、Setmeal
         * b:Mapper接口DishMapper、SetmealMapper
         * c:Service接口:DishService和SetmealService
         * d:Service实现类DishServiceImpl和 SetmealServiceImpl
         */

    }

2.Service接口层:


public interface CategoryService  extends IService<Category> {

    public void remove(Long id); //定义方法;
}

3."ServiceImpl"层:


/**
 * Service接口实现类:CategoryServiceImpl
 */

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


     @Autowired
    private DishService dishService; //拿到DishService对象
     @Autowired
     private SetmealService setmealService; //拿到SetmealService对象

    /**
     * 实现:根据id删除分类,删除之前需要进行判断
     */
    //实现Service接口的删除remove方法:

    @Override
    public void remove(Long id){

        //a:查询当前分类是否关联了菜品,如果已经关联则抛出一个“ 业务异常 ”
            //即查询菜品分分类里面的"菜品"数量,即查询菜品中id和分类id一样的 就代表这个菜品是属于这个分类里面的。-->:所以是查菜品
        LambdaQueryWrapper<Dish>queryWrapper=new LambdaQueryWrapper<>();
        queryWrapper.eq(Dish::getId,id); //判断菜品的id是否和分类的id一样
        int count = dishService.count(queryWrapper); //调用count方法
        if(count>0){ //如果count>0:代表分类里面有菜品,就不能删除-->抛出异常

            throw new CustomException("当前分类下关联了菜品,不能删除"); //后端显示的内容
            //要向前端也显示这个内容:可以在全局异常处理器中新增一个!!!

        }


        //b:查询当前分类是否关联了套餐,如果已经关联则“ 抛出异常 ”
           //即查询分类里面的 “套餐”数量,即看一下有没有套餐id和分类id一样的,如果有,代表这个id分类里面有套餐,则不能删除;->所以是查套餐
        LambdaQueryWrapper<Setmeal>queryWrapper1=new LambdaQueryWrapper<>();
        queryWrapper1.eq(Setmeal::getCategoryId,id);//查询条件:查询是否有套餐id的和分类的id一样的,
        int count1 = setmealService.count(queryWrapper1);//  -->count where id=id
        if(count1>0){ //如果count1>0代表查询到了 套餐id和分类id 一样的 “套餐” -->代表分类里面有套餐,就不能删除

            throw new CustomException("当前分类下关联了套餐,不能删除");
        }

        //c:如果分类既不关联菜品也不关联套餐,则可以删除
        super.removeById(id); //则可以根据传来的分类id进行删除!!!
        //super:即父类IService中提供了增删改查相关方法!!!

        /**
         * 抛出异常:我们可以新建立一个“自定义业务异常类”:CustomException:来定义返回信息
         * 之后,为了让前端也返回相同的提示信息,所以我们把自定义业务异常类 加入全局异常处理类!!!
         */
    }

}


4.因为在service层中:如果不能删除,需要抛出异常,所以我们需要
        a:定义一个 "异常处理类"
        b:为了让前端也返回相同的异常处理信息,我们需要把"异常处理类"加入到"全局异常处理器"中
    
    
    
    eg:
    
  '自定义业务异常类:
    public class CustomException extends RuntimeException{ //继承运行时异常

        public CustomException(String message){ //构造器
            super(message); //调用父类的有参构造器
        }
        之后我们可以在其它类中国通过:"throw new CustomException(提示信息)"来将异常处理信息传进来!!!
    }


  "加入全局异常处理器":
      
    @ControllerAdvice(annotations = {RestController.class, Controller.class}) //全局异常处理器拦截的类
    @Slf4j
    @ResponseBody //返回json形式结果
    public class GlobalExceptionHandler {

        //异常处理方法1:
        @ExceptionHandler(SQLIntegrityConstraintViolationException.class) //Sql异常处理:
        public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
                                          //接收异常
            log.info(ex.getMessage());
            return R.error("失败了,out");  //返回到前端页面!!
        }

        //异常处理方法2:
        @ExceptionHandler(CustomException.class) //自定义异常处理:
        public R<String> exceptionHandler(CustomException ex){
            //接收异常
            log.info(ex.getMessage());
            return R.error(ex.getMessage()); //返回到前端页面!!!
        }
    }

(4)修改菜品分类信息:

(4)修改分类:"即首先让数据回显-->:根据"id"查数据库"
    
    @PutMapping
    public  R<String> update(@RequestBody Category category){ //接收前端发送的json数据

  
        //查数据库:调用service的updateById方法;直接将category对象传进去即可;!!!!
        categoryService.updateById(category);

        //更新时间、更新人就不需要我们自己进行设置了,因为前面已经设置了自动填充:注解+元数据对象处理器:MyMetaObjectHandler         类,所以当我们更新操作时,就可以自动填充了

        return R.success("修改分类信息成功");
    }