瑞吉外卖学习笔记

一、项目概述

新手入门的SpringBoot+SSM企业级项目

1、软件开发整体介绍

1.1软件开发流程

瑞吉外卖学习笔记_第1张图片
我们一般在编码层

1.2角色分层

瑞吉外卖学习笔记_第2张图片

1.3软件环境

①开发环境(development):开发人员在开发阶段使用的环境,一般外部用户无法访问
②测试环境(testing):专门给测试人员使用的环境,用于测试项目,
一般外部用户无法访问
③生产环境(production):即线上环境,正式提供对外服务的环境

2、瑞吉外卖项目介绍

本项目共分为3期进行开发:
①第一期主要实现基本需求,其中移动端应用通过H5实现,用户可以通过手机浏览器访问。
②第二期主要针对移动端应用进行改进,使用微信小程序实现,用户使用起来更加方便。
③第三期主要针对系统进行优化升级,提高系统的访问性能。

2.1产品原型展示

①产品经理在需求分析后制作,一般都是网页。
②产品原型主要用于展示项目的功能,并不是最终的页面效果。

2.2技术选型

瑞吉外卖学习笔记_第3张图片

2.3功能架构

瑞吉外卖学习笔记_第4张图片

2.4角色

后台系统管理员:登录后台管理系统,拥有后台系统中的所有操作权限
后台系统普通员工:登录后台管理系统,对菜品、套餐、订单等进行管理
C端用户:登录移动端应用,可以浏览菜品、添加购物车、设置地址、在线下单等

二、项目实操

Ⅰ、员工管理

1、开发环境搭建

瑞吉外卖学习笔记_第5张图片

第一步
数据库环境搭建,导入sql文件

第二步
maven项目搭建:骨架创建maven。
pom.xml文件



    4.0.0
    
    
        org.springframework.boot
        spring-boot-starter-parent
        2.4.5
         
    
    com.itheima
    reggie_take_out
    1.0-SNAPSHOT
    
        1.8
    
    

        
            org.springframework.boot
            spring-boot-starter
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
            org.springframework.boot
            spring-boot-starter-web
            compile
        

        
            com.baomidou
            mybatis-plus-boot-starter
            3.4.2
        

        
            org.projectlombok
            lombok
            1.18.20
        

        
            com.alibaba
            fastjson
            1.2.76
        

        
            commons-lang
            commons-lang
            2.6
        

        
            mysql
            mysql-connector-java
            runtime
        

        
            com.alibaba
            druid-spring-boot-starter
            1.1.23
        

    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                2.4.5
            
        
    


第三步
导入SpringBoot配置文件applidation.yml
resources包下导入application.yml

server:
  port: 8080 #tomcat端口号
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: root
mybatis-plus:
  configuration:
    #address book---->AddressBook
    #user_name---->userName
    #在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID

第四步
编写启动类,可尝试启动

@Slf4j
//可以使用log方法,打印info级别日志
@SpringBootApplication
//引导类or启动类
@ServletComponentScan//扫描webFilter注解 进一步创建过滤器
public class ReggieApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReggieApplication.class,args);
        log.info("项目启动成功。。。。");//打印info级别日志
    }
}

注意:静态资源一般放在webapp/static或者webapp/resources等目录下。默认寻找这个目录下的静态资源。不在直接报错。
决::配置映射文件

@Slf4j
//可以使用log方法,打印info级别日志
@Configuration//声明该类是配置类
public class WebMvcConfig extends WebMvcConfigurationSupport {
    /*
    静态资源文件应该都在webapp目录下
    本项目没有webapp目录
    需要设置静态资源映射
     */
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("开始进行静态资源映射。。。");
      registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
      registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
      /*
      tomcat服务器访问路径映射到相应的静态文件下
      pathPatterns:服务器访问路径
      classpath:(指定类文件的路径)此处指读取静态资源的路径resources目录
       */
    }

2、功能开发

1、后台登录功能开发

第一步:
创建实体类Employee,和employee表进行映射
开发习惯:数据库一张表对应一个实体类
从资料直接导入

第二步:
创建Controller、Service、 Mappe

//mapper接口
@Mapper
public interface EmployeeMapper extends BaseMapper {//泛型实体
}
//EmployeeService接口 
@Mapper
public interface EmployeeService extends IService {//继承IService泛型实体
}
//EmployeeServiceImpl继承serviceImpl(对应mapper类,对应实体类)继承 EmployeeService接口
public class EmployeeServiceImpl extends ServiceImpl implements EmployeeService {
}
//EmployeeController类
@Slf4j
@RestController
@RequestMapping("/employee")//路径
public class EmployeeController {

    //注入
    @Autowired
    private EmployeeService employeeService;

第三步:
导入返回类R
此类是一个通用结果类,服务端响应的所有结果最终都会包装成此种类型返回给前端页面

/*
通用返回结果,限务端响应的数据最终部会封装成此对练

 */
@Data
public class R {

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

    private String msg; //错误信息

    private T data; //数据

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

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

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

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

第四步:
在Controller中创建登录方法
处理逻辑:
1、将页面提交的密码password进行md5加密处理
2、根据页面提交的用户名username查询数据库
3、如果没有查询到则返回登录失败结果
4、密码比对,如果不一致则返回登录失败结果
5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
6、登录成功,将员工id存入Session并返回登录成功结果
瑞吉外卖学习笔记_第6张图片

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

    //注入
    @Autowired
    private EmployeeService employeeService;
/*
员工登录
 */
    @PostMapping("/login")
    //json在接收时,要有注解@RequestBody 分装成emplouee对象
    public R login(HttpServletRequest request, @RequestBody Employee employee) {
        //Employee的id存在Session中,表示成功。获取当前用户:request来get
/**
 *         1、将页面提交的密码password进行md5加密处理,已经封装到Employee中
 *         密码已经封装到employee中
 */
        String password = employee.getPassword();//拿到密码
        password = DigestUtils.md5DigestAsHex(password.getBytes());//md5加密处理
//        2、根据页面提交的用户名username查询数据库
        //employee是泛型
        LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Employee::getUsername, employee.getUsername());//指定sql语句条件
        Employee emp = employeeService.getOne(queryWrapper);
        //getOne 这个用户名是唯一的用getOne调用,调用后分装成employee对象
        //3、如果没有查询到则返回登录失败结果
        if (emp == null) {
            //返回结果封装成R对象
            return R.error("登录失败");
        }
        //4、密码比对,如果不一致则返回登录失败结果
        //数据库查的密码和处理后的密码对比
        if (!emp.getPassword().equals(password)) {
            return R.error("登录失败");
        }
        // 5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
        if (emp.getStatus() == 0) {
            //1账户可用。0账号不可用
            return R.error("账户已禁用");
        }
//        6、登录成功,将员工id存入Session并返回登录成功结果
        request.getSession().setAttribute("employee", emp.getId());

        return R.success(emp);
    }

2、后台退出功能开发

直接点击右侧的退出按钮即可退出系统,退出系统后页面应跳转回登录页面

用户点击页面中退出按钮,发送请求,请求地址为/employee/logout,请求方式为POST。

我们只需要在Controller中创建对应的处理方法即可,具体的处理逻辑;
1、清理Session中的用户id
2、返回结果

    /**
     * 员工退出页面
     */
        @PostMapping("/logout")
        public R logout(HttpServletRequest request){
//            清理Session中保存的当前登录员工的id
            request.getSession().removeAttribute("employee");
            //把employee属性移除
            return R.success("退出成功");
    }

3、员工管理业务开发

3.1完善登录功能
3.1.1问题

用户如果不登录,直接访问系统页面,可以直接访问

3.1.2方案

使用过滤器或者拦截器。没有登录跳转到登录界面

3.1.3代码开发

实现步骤
1、创建自定义过滤器LoginCheckFilter
2、在启动类上加入注解@Servletcomponentscan
3、完善过滤器的处理逻辑
创建filter包。给启动类添加扫描注解@ServletComponentScan

/*
检查用户是否已将登陆
 */
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
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);//{}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;
        }
        log.info("用户未登录");
//        5、如果未登录则返回未登录结果通,过输出流方式向客户端页面响应数据
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
        return;
    }
    /**
     * 路径匹配,检查本次请求是否需要放行
     * @param urls
     * 遍历要放行的数组。和请求的数组对比
     * @param requestURL
     * @return
     */
    public boolean check(String[] urls,String requestURL) {
        for (String url : urls){
            boolean match = PATH_MATCHER.match(url,requestURL);
            if (match){
                return true;//放回true表示匹配上
            }
        }
        return false;
    }
}
@Slf4j
//可以使用log方法,打印info级别日志
@SpringBootApplication
//引导类or启动类
@ServletComponentScan//扫描webFilter注解 进一步创建过滤器
public class ReggieApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReggieApplication.class,args);
        log.info("项目启动成功。。。。");//打印info级别日志
    }
}

4、新增员工

4.1数据模型

将新增页面录入的员工数据插入到employee表。
需要注意,employee表中对username字段加入了唯一约束,因为username是员工的登录账号,必须是唯一的

4.2代码开发

整个程序的执行过程:
1、页面发送ajax请求,将新增员工页面中输入的数据以json的形式提交到服务端
2、服务端Controller接收页面提交的数据并调用Service将数据进行保存
3、Service调用Mapper操作数据库,保存数据
瑞吉外卖学习笔记_第7张图片
前面的程序还存在一个问题,就是当我们在新增员工时输入的账号已经存在,由于employee表中对该字段加入了唯
一约束,此时程序会抛出异常:

java sql.SQLIntegrityConstraintviolationException: Duplicate entry
zhangsan for key idx_username

此时需要我们的程序进行异常捕获,通常有两种处理方式

1、在Controller方法中加入try、catch进行异常捕获
2、使用异常处理器进行全局异常捕获

全局异常处理器
*关键在@@ControllerAdvice和@ExceptionHandler两个注解
详解看注释

/**
 * 全局异常处理器
 * 关键在@@ControllerAdvice和@ExceptionHandler两个注解
 */
//
//拦截 类上加restController和controllee类
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody//封住成JSON数据
@Slf4j
public class GlobalExceptionHandler {
    /**
     * 异常处理方法
     */
    //@ExceptionHandler声明要处理的类
    //此处处理的是SQLIntegrityConstraintViolationException的异常
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R exceptionHamdler(SQLIntegrityConstraintViolationException ex){
        log.error(ex.getMessage());
        //异常有多种
        //判断异常信息里边是否有需要的关键字
        //用户名唯一,重复报一下错误
        //Duplicate entry 'zhangsan' for key'idx username
        //关键字=Duplicate entry

        if (ex.getMessage().contains("Duplicate entry")){
            //冬天截取重复的用户名  用空格隔开
            String[] split = ex.getMessage().split(" ");
            //split[2]  “zhangsan的下标”
            String msg = split[2] + "已经存在";
            //通用返回结果
            return R.error(msg);
        }
        return R.error("未知错误");
    }
}

总结:
1、根据产品原型明确业务需求
2、重点份析数据的流转过程和数据格式
3、通过debug断点调试跟踪程序执行过程
+++++++++++++++++++++++++++++++++++++++
其他功能流程和新增功能流程相同。数据格式不同+
+++++++++++++++++++++++++++++++++++++++

瑞吉外卖学习笔记_第8张图片

5、员工信息分页查询

5.1、程序的执行过程:

1、页面发送ajax请求,将分页查询参数(page、pagesize、name)提交到服务端
2、服务端Controller接收页面提交的数据并调用Service查询数据
3、Service调用Mapper操作数据库,查询分页数据
4、Controller将查询到的分页数据响应给页面 5、页面接收到分页数据并通过Elementul的Table组件展示到页面上

5.2、代码开发

第一步:
config包下MP分页插件配置类

/**
 * 配置MP的分页插件
 */
@Configuration//声明这是配置类
public class MybatisPlusConfig {
    @Bean//bean注解 spring来管理
    //通过拦截器的方式把插件加载进来
    public MybatisPlusInterceptor mybatisPlusConfigInterceptor(){
        //创建拦截器对象
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //加入插件
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}

第二步:
controller层EmployeeController代码

    /**
     * 分页处理的数据 用page接受
     * 员工信息查询
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @GetMapping("/page")//get请求 所以getmapping
    //设置默认页数和单页数据条数 name:查询的用户
    public R page(int page,int pageSize,String name){
        log.info("page ={},pageSize = {},name = {}",page,pageSize,name);
        //构造分页构造器
        Page pageInfo = new Page(page,pageSize);
        //构造条件构造器
        LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
        //添加过滤添条件
        queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
        //添加排序条件
        queryWrapper.orderByDesc(Employee::getUpdateTime);
        //执行查询
        employeeService.page(pageInfo,queryWrapper);

        return R.success(pageInfo);
    }

6、启用/禁用员工账号

只有管理员能进后台和设置账号的状态 。
某个员工账号状态为正常,则按钮显示为“禁用”,如果员工账号状态为已禁用,则按钮显示为“启用”

6.1、代码开发

页面中是怎么做到只有管理员admin能够看到启用、禁用按钮的?

瑞吉外卖学习笔记_第9张图片

6.2、执行过程

1、页面发送ajax请求,将参数(id、status)提交到服务端
2、服务端Controller接收页面提交的数据并调用Service更新数据
3、Service调用Mapper操作数据库
瑞吉外卖学习笔记_第10张图片

页面中的ajax请求是如果发送的呢?
瑞吉外卖学习笔记_第11张图片

第一步:
controller层EmployeeController代码

    /**
     * 根据id修改员工信息
     * @return
     */
    @PutMapping
    public R updata(HttpServletRequest request,@RequestBody Employee employee){
        log.info(employee.toString());
        //获取修改人信息
        Long empId = (long)request.getSession().getAttribute("employee");
        employee.setUpdateTime(LocalDateTime.now());//当前修改时间
        employee.setUpdateUser(empId);//修改人信息。当前登录的人可以修改,所修改人=当前登陆人
        employeeService.updateById(employee);
        return R.success("员工信息修改成功");
    }

数据库id和禁用启动操作id不同(id 19位)
原因:Long(1前6位)类型丢失精度
瑞吉外卖学习笔记_第12张图片

第二步:
解决上述问题:
在服务端给页面响应json数据时进行处理,将long型数据统一转为String字符串
具体实现步骤:
1)提供对象转换器jacksonobjectMapper,基于jackson进行Java对象到json数据的转换(资料中已经提供,直接复制到项目中使用)。放common包
2)在WebMvcConfig配置类中扩展Springmvc的消息转换器,在此消息转换器中使用提供的对象转换器进行Java对象到json数据的转换


    /**
     * 扩展mve框架的消息转换器
     */
    @Override
    protected void extendMessageConverters(List> converters) {
        //创建消息转换器对象
//  我们自己new 的转换器放入 converters中。默认8个转换器,现在+1
        MappingJackson2CborHttpMessageConverter messageConverter = new MappingJackson2CborHttpMessageConverter();
        //设置对象转换器,底层使用Jackson将java对象转为json
        messageConverter.setObjectMapper(new JacksonObjectMapper());
//        将上面的消息转换器对象追加到mvc框架的转换器集合中
        //注意add导入index类。 0:优先使用自己的转换器
            converters.add(0,messageConverter );
    }

7、编辑员工信息

7.1、执行流程:

1、点击编辑按钮时,页面跳转到add.html,并在url中携带参数[员工id]
http://localhost:8000/backend/page/member/add.htmlid=1644891700080074753

2、在add.html页面获取url中的参数员工id]
3、发送ajax请求,请求服务端,同时提交员工id参数
4、服务端接收请求,根据员工id查询员工信息,将员工信息以json形式响应给页面
5、页面接收服务端响应的json数据,通过VUE的数据绑定进行员工信息回显
6、点击保存按钮,发送ajax请求,将页面中的员工信息以json方式提交给服务端
7、服务端接收员工信息,并进行处理,完成后给页面响应
8、页面接收到服务端响应信息后进行相应处理
注意:add.html页面为公共页面,新增员工和编辑员工都是在此页面操作

7.2、代码开发

controller层EmployeeController代码

    /**
     * 根据id查询员工信息
     * 复用根据id修改员工信息
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    public R getById(@PathVariable Long id){
        log.info("根据id查询员工信息。。。");
        Employee employee = employeeService.getById(id);
        if (employee != null){
            return R.success(employee);
        }
        return R.error("没有查询到对应员工信息");
    }

Ⅱ、分类管理

1、公共字段自动填充

1.1、问题分析

员工管理功能开发时,在新增员工时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工时需要设置修改时间和修改人等字段。这些字段属于公共字段
> 多张表都有的字段

1.2、代码实现

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

实现步骤:
1、在实体类的属性上加入***@TableField***注解,指定自动填充的策略
2、按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口

    /**
    第一步:1、在实体类的属性上加入***@TableField***注解,指定自动填充的策略
     * 那些是公共字段
     * 就加上@TableField注解
     * fill :填充  =号后边是填充策略
     * 详细策略看FieldFill类
     */
    @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;

1.3、功能完善

注意:①当前我们设置createUser和updateUser为固定值 后面我们需要进行改造,改为动态获得当前登录用户的id。
②可以使用ThreadLocal来解决此问题,它是DK中提供的一个类。

在学习ThreadLocal之前,先确认一个事情:客户端发送的每次http请求,对应的在服务端都会分配一个新的线程来处理,在处理过程中涉及到下面类中的方法都属于相同的一个线程:
1、LogincheckFilter的doFilter方法
2、EmployeeController的update方法
3、MyMetaObjectHandler的updateFi方法

/**
 * 测试
 * 可以在上面的三个方法中分别加入下面代码(获取当前线程id):
 * long id = Thread。currentThread().getId();
 * log.info("线程id:{}",id)
 */

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

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

/**
 * ThreadLocal常用方法:
 * public void  set(T value)  设置当前线程的线程局部变量的值
 * public T get() 返回当前线程所对应的线程局部变量的值
 *

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


实现步骤:
1、编写Basecontext工具类,基于ThreadLocal封装的工具类
2、在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
3、在MyMetaobjectHandler的方法中调用BaseContext获取登录用户的id

package com.itheima.reggie.common;
/**
 * 1、编写Basecontext工具类,基于ThreadLocal封装的工具类
 * 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
 */
public class BaseContext {
    //id 是lang类型 所以泛型是long
    private static ThreadLocal threadLocal = new ThreadLocal<>();

    public static void setCurrentId(Long id) {
        threadLocal.set(id);//保存值
    }
    public static Long getCurrentId(){
        return threadLocal.get();//取值
    }
}
// 第二步:在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
//4、判断登录状态,如果已登录,则直接放行
        if (request.getSession().getAttribute("employee") != null){
            log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));//获取id
        //获取id
            Long empId = (Long) request.getSession().getAttribute("employee");
            BaseContext.setCurrentId(empId);

            filterChain.doFilter(request,response);
            return;
        }
        log.info("用户未登录");
3、在MyMetaobjectHandler的方法中调用BaseContext获取登录用户的id        metaObject.setValue("updateUser",new Long(1));
metaObject.setValue("createUser",BaseContext.getCurrentId());

2、新增分类

菜品分类和套餐分类。在后台系统中添加菜品时选择一个菜品分类

在开发业务功能前,先将需要用到的类和接口基本结构创建好
实体类Category(直接从课程资料中导入即可)
Mapper接口CategoryMapper
业务层接口Categorysenvice
业务层实现类Categoryservicelmpl
控制层Categorycontroller

2.1代码开发

2.2程序的执行过程:

1、页面(backend/page/category/list.html)发送ajax请求,将新增分类窗口输入的数据以json形式提交到服务端
2、服务端Controller接收页面提交的数据并调用Service将数据进行保存
3、Service调用Mapper操作数据库,保存数据

瑞吉外卖学习笔记_第13张图片

package com.itheima.reggie.controller;
/**
 * 分类管理
 */
@RestController
@RequestMapping("/categoty")
@Slf4j
public class CategoryController {
    @Autowired
    private CaregoryService caregoryService;
    /**
     * 新增分类成功
     */
    @PutMapping
    public R save(@RequestBody Category category){
        log.info("category:{}",category);
        caregoryService.save(category);
        return R.success("新增分类成功");
    }
}

功能测试:可以新增菜品分类

3、分类信息分页查询

3.1执行过程:

1、页面发送ajax请求,将分页查询参数(page、pagesize)提交到服务端
2、服务端Controller接收页面提交的数据并调用Service查询数据
3、Service调用Mapper操作数据库,查询分页数据
4、Controller将查询到的分页数据响应给页面
5、页面接收到分页数据并通过Elementul的Table组件展示到页面上

package com.itheima.reggie.controller;
    /**
     * 分页查询
     * @param page
     * @param pageSize
     * @return
     */
    @GetMapping("/page")
    public R page(int page, int pageSize){
        //分页构造器
        //页数和一页的条件
        Page pageInfo = new Page<>(page,pageSize);
        //条件构造器
        LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
        //添加排序条件,根据sort进行排序
        queryWrapper.orderByAsc(Category::getSort);

        //进行分页查询
        categoryService.page(pageInfo,queryWrapper);
        return R.success(pageInfo);
    }

4、删除分类

需求分析

在分类管理列表页面,可以对某个分类进行删除操作。需要注意的是当分类关联了菜品或者套餐时,此分类不允许删除。

代码开发

1、页面发送ajax请求,将参数(id)提交到服务端
2、服务端Controller接收页面提交的数据并调用Service删除数据
3、Service调用Mapper操作数据库

CategoryController层
   /**
     * 根据id删除分类
     * @param id
     * @return
     */
    @DeleteMapping
    public R delete(Long id){
        log.info("删除分类,id为:{}",id);

        categoryService.removeById(id);
        return R.success("分类信息删除成功");
    }

前面我们已经实现了根据id删除分类的功能,但是并没有检查删除的分类是否关联了菜品或者套餐,所以我们需要进行功
能完善。

要完善分类删除功能,需要先准备基础的类和接口:
1、实体类Dish(菜单实体类)和Setmeal(套餐实体类)(从课程资料中复制即可
2、Mapper接口DishMapper和SetmealMapper
3、Service接口DishService和Setmealservice
4、Service实现类DishServicelmpl和SetmealServicelmpl


service包下impl

@Service
public class CategoryServiceImpl extends ServiceImpl implements CaregoryService {
    //注入,
    //菜品分类id   private Long categoryId;
    @Autowired
    private DishService dishService;
    @Autowired
    private SetmealService setmealService;
    /**
     * 根据id删除分类,删除之前需要经进行判断
      * @param id
     */
    @Override
    public void remove(Long id) {
        //构造查询条件
        LambdaQueryWrapper dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
        //添加查询条件,根据分类categoryId 数据库是:category_Id 进行查询
        dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
        //count 统计数据量
        int count1 = dishService.count(dishLambdaQueryWrapper);

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

//        查询当前分类是否关联了套餐,如果已经关联,抛出一个业务异常
        LambdaQueryWrapper setmealLambdQueryWrapper = new LambdaQueryWrapper<>();
        //添加查询条件,根据分类id进行查询
        setmealLambdQueryWrapper.eq(Setmeal::getCategoryId,id);
        int count2= setmealService.count();
        if (count2 > 0){
            //已经关联套餐,抛出一个业务异常
            throw new CustomException("当前分类下关联了套餐,不能删除");
        }
        //菜品和套餐都没有关联
        //可以正常删除分类   调用删除方法
        super.removeById(id);
    }
}

common包下自定义异常类


/**
 * 自定义业务异常
 */
public class CustomException extends RuntimeException{
    public CustomException(String message){
        super(message);
    }
}

common包下全局异常类

    /**
     * 异常处理方法
     */
    //@ExceptionHandler声明要处理的类
    //此处处理的是SQLIntegrityConstraintViolationException的异常
    @ExceptionHandler(CustomException.class)
    public R exceptionHamdler(CustomException ex){
        log.error(ex.getMessage());

        return R.error(ex.getMessage());
    }

5、修改分类

service层impl下CategoryServiceImpl

    /**
     * 根据id修改分类信息
     * @param category
     * @return
     */
    @PutMapping
    public R update(@RequestBody Category category){
        log.info("修改分类信息:{}",category);

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

Ⅲ、菜品管理业务开发

1、文件上传下载

文件上传介绍
瑞吉外卖学习笔记_第14张图片

  • 服务端要接收客户端页面上传的文件,通常都会使用Apache的两个组件:
    ①commons-fileupload
    ②commons-io
    /**
     * 文件上传
     * Spring框架在spring-web包中对文件上传进行了封装,简化服务端代码
     * 我们只需要在Controller的方法中声明
     * 一个MultipartFile类型的参数即可接收上传的文件,如下:
     * 本质还是上边的两种方式
     * @param file
     * @return
     */
    @PostMapping(value = "/upload")
    public R upload (MultipartFile file){
        System.out.println(file);
        return null;
    }
}

文件下载介绍:
文件下载,download,指将文件从服务器传输到本地计算机的过程。


通过浏览器进行文件下载,通常有两种表现形式:
①以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录
②直接在浏览器中打开

  • 通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程。

1.1、文件上传代码实现

文件下载代码实现
第一步:瑞吉外卖学习笔记_第15张图片

第二步:controller层创建CommonController菜品管理类

/**
 * 文件上传和下载
 */
@RestController
@RequestMapping("/common")
@Slf4j
public class CommonController {
    //读取配置文件中转存位置
    //application.yml文件
    //reggie:
    //  path: D:\
    @Value("${reggie.path}")
    private String basePath;
    /**
     * 文件上传
     * @return
     */
    @PostMapping("/upload")
    public R upload(MultipartFile file){
        //file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除
        log.info(file.toString());
        /**
         * 防止总是被拦截器拦截,在拦截器类设置不需要拦截直接放行
         *         String[] urls = new String[]{
         *                 //菜品管理
         *                 "/common"
         *         };
         */
        //原始文件名
        String originalFilename = file.getOriginalFilename();//abc.jpg
        //动态截取文件后缀  suffix接受新的文件名 .之后的
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));

        //使用UUID重新生成文件名,防止文件名称重复造成文件覆盖  新文件 + 后缀(.jpg)
        String fileName = UUID.randomUUID().toString() + suffix;//qwertt.jpg

        //判断application.yml设置的指定位置目录是否存在
        File dir = new File(basePath);
        //判断当前目录是否存在
        if (!dir.exists()){
            //目录不存在 ,需要创建
            dir.mkdirs();
        }

        try {
            //将临时文件转存到指定位置.
            //转存位置可在application.ym中设置
            file.transferTo(new File(basePath + fileName));
        } catch (IOException e) {
            e.printStackTrace();
        }
        //文件名称需要放在数据库中  返回fileaName
        return R.success(fileName);
    }

    /**
     * 文件下载
     * @param name
     * @param response
     */
    //通过流写回数据  不需要返回值
//    输出流需要respinse获得
    @GetMapping("/download")
    public void download(String name , HttpServletResponse response){
        try {
            //输入流,通过输入流读取文件内容
            FileInputStream fileInputStream = new FileInputStream(new File(basePath + name));

            //输出流,通过输出流将文件写回浏览器,在浏览器展示图片了  输出流需要respinse获得
            ServletOutputStream outputStream = response.getOutputStream();

//            设置想要返回什么类型文件
                    response.setContentType("image/jpeg");

//            将读到的内容,放到数组中去
            int len = 0;
            byte[] bytes = new byte[1024];
            //-1 说明没有读完
            while ((fileInputStream.read(bytes)) != -1){
//                用过输出流向浏览器写。从第一个写 写len这么长
                outputStream.write(bytes,0,len);
                outputStream.flush();//刷新
            }
//            关闭资源
            outputStream.close();
            fileInputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2、新增菜品

数据模型:
新增菜品,其实就是将新增页面录入的菜品信息插入到dish表,如果添加了口味做法,还需要向dish_flavor表插入数据
所以在新增菜品时,涉及到两个表:
o dish
菜品表
dish flavor
菜品口味表

准备工作:
实体类DishFlavor(直接从课程资料中导入即可,Dish实体前面课程中已经导入过了)
Mapper接口DishFlavorMapper
业务层接口DishFlavorService
业务层实现类DishFlavorservicelmpl
控制层Dishcontroller

1、Mapper接口DishFlavorMapper
@Mapper
public interface DishFlavorMapper extends BaseMapper {
}
2、业务层接口DishFlavorService
public interface DishFlavorService extends IService {
}
3、业务层实现类DishFlavorservicelmpl
public class DishFlavorServiceImpl extends ServiceImpl {
}
4、控制层Dishcontroller
/**
 * 菜品管理
 */
@RestController
@RequestMapping("/dish")
public class DishController {
    @Autowired
    private DishService dishService;

    @Autowired
    private DishFlavorService dishFlavorService;
}

2.1梳理交互过程

1、页面(backend/page/food/add.html)发送ajax请求,请求服务端获取菜品分类数据并展示到下拉框中
2、页面发送请求进行图片上传,请求服务端将图片保存到服务器
3、页面发送请求进行图片下载,将上传的图片进行回显
4、点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交到服务端


开发新增菜品功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可

代码开发
功能测试

菜品信息分页查询
修改菜品

你可能感兴趣的:(项目学习笔记,学习,java,微信小程序)