java瑞吉外卖

环境搭建

一、数据库环境搭建

1.新建数据库reggie,这里字符集一般用utf8mb4,排序规则一般用utf8mb4_general_ci或utf8mb4_unicode_ci

2.然后导入表结构

二、创建springboot工程

然后检查maven仓库设置,jdk

这是我的pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.4.5</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.itheima</groupId><artifactId>reggie_take_out</artifactId><version>0.0.1-SNAPSHOT</version><name>reggie_take_out</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><spring-boot.version>2.7.6</spring-boot.version></properties><dependencies><!-- Spring Boot Web Starter: 提供构建Web应用所需的所有依赖,包括Spring MVC和Tomcat --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><scope>compile</scope></dependency><!-- MyBatis-Plus Boot Starter: MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,简化开发、提高效率 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.2</version></dependency><!-- Fastjson: 一个Java语言编写的高性能功能完善的JSON库,用于将Java对象转换成JSON格式的字符串,以及将JSON字符串还原成Java对象 --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.76</version></dependency><!-- MySQL Connector/J: MySQL的官方JDBC驱动,用于Java应用与MySQL数据库的连接 --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><!-- Lombok: 一个Java库,可以通过简单的注解形式来帮助我们简化Java代码,特别是通过注解的方式消除样板代码,如getter和setter方法等 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.20</version></dependency><!-- Spring Boot Test Starter: 提供对测试框架(如JUnit和Mockito)的支持,以及Spring TestContext Framework的集成 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- Commons Lang: Apache Commons Lang库提供了一系列对java.lang包的扩展,包括字符串操作、数组操作、日期时间操作等实用工具类 --><dependency><groupId>commons-lang</groupId><artifactId>commons-lang</artifactId><version>2.6</version></dependency><!-- Druid Spring Boot Starter: Druid是一个数据库连接池实现,支持多种数据库,性能高且监控功能强大 --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.23</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>2.4.5</version></plugin></plugins></build></project>

再编写application.yml文件

server:port: 8080# 配置服务器端口为 8080spring:application:#应用的名称,可选name: reggie_take_out# 设置应用名称为 reggie_take_outdatasource:druid:driver-class-name: com.mysql.cj.jdbc.Driver# 设置数据库驱动类名为 MySQL 的 JDBC 驱动url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true# 配置数据库连接 URL,连接到本地 MySQL 数据库的 reggie 数据库,并设置时区、字符编码等参数username: root# 数据库用户名是 rootpassword: 123456# 数据库密码是 123456mybatis-plus:configuration:#在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射map-underscore-to-camel-case: true# 开启 MyBatis-Plus 的下划线转驼峰命名映射功能log-impl: org.apache.ibatis.logging.stdout.StdOutImpl# 设置 MyBatis-Plus 的日志实现类为标准输出global-config:db-config:id-type: ASSIGN_ID# 设置 MyBatis-Plus 的全局数据库配置中的 ID 生成策略为自动分配 ID

创建springboot程序入口

导入前端文件,注意前端文件的位置,在Boot项目中,前台默认就只能访问 resource目录下的statictemplates文件夹下的文件;这里我在resources目录下创建了static和templates两个文件夹,并把前端资源导入

后台登录功能开发

创建实体类

先创建一个 包entity,把实体类employee放在这个包下

package com.itheima.reggie.entity;import com.baomidou.mybatisplus.annotation.*;import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.Data;@Data
public class Employee implements Serializable {private static final long serialVersionUID = 1L;private Long id;private String name;private String username;private String password;private String phone;private String sex;private String idNumber;  //身份证号private Integer status;  //状态 0:禁用,1:正常private LocalDateTime createTime;private LocalDateTime updateTime;@TableField(fill = FieldFill.INSERT)private Long createUser;@TableField(fill = FieldFill.UPDATE)private Long updateUser;}

创建Mapper接口EmployeeMapper

package com.itheima.reggie.mapper;import com.itheima.reggie.entity.Employee;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {}

创建Service接口EmployeeService和实现类EmployeeServiceImpl

package com.itheima.reggie.service;import com.itheima.reggie.entity.Employee;
import com.baomidou.mybatisplus.extension.service.IService;public interface EmployeeService extends IService<Employee> {}

 这里EmployeeService继承了Mybatis-Plus的IService,继承了很多crud的抽象方法

package com.itheima.reggie.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.Employee;
import com.itheima.reggie.service.EmployeeService;
import com.itheima.reggie.mapper.EmployeeMapper;
import org.springframework.stereotype.Service;@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService{}

这里EmployeeServiceImpl继承了Mybatis-Plus的ServiceImpl,ServiceImpl帮忙重写了IService里的抽象方法

创建EmployeeController

package com.itheima.reggie.controller;import com.itheima.reggie.service.EmployeeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@Slf4j
@RestController
@RequestMapping("employee")
public class EmployeeController {@Autowiredprivate EmployeeService employeeService;
}

创建一个通用结果类R

package com.itheima.reggie.common;import lombok.Data;
import java.util.HashMap;
import java.util.Map;@Data
public class R<T> {private Integer code; //编码:1成功,0和其它数字为失败private String msg; //错误信息private T data; //数据private Map map = new HashMap(); //动态数据public static <T> R<T> success(T object) {R<T> r = new R<T>();r.data = object;r.code = 1;return r;}public static <T> R<T> error(String msg) {R r = new R();r.msg = msg;r.code = 0;return r;}public R<T> add(String key, Object value) {this.map.put(key, value);return this;}}

在EmpController中创建登录方法

业务逻辑:

1、将页面提交的密码password进行md5加密处理
2、根据页面提交的用户名username查询数据库
3、如果没有查询到则返回登录失败结果
4、密码比对,如果不一致则返回登录失败结果
5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
6、登录成功,将员工id存入Session并返回登录成功结果

package com.itheima.reggie.controller;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.itheima.reggie.common.R;
import com.itheima.reggie.entity.Employee;
import com.itheima.reggie.service.EmployeeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.security.DigestException;@Slf4j
@RestController
@RequestMapping("employee")
public class EmployeeController {@Autowiredprivate EmployeeService employeeService;/*** 员工登录* @param request* @param employee* @return*/@PostMapping("/login")public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee) {//1、将页面提交的密码password进行md5加密处理String password = employee.getPassword();password = DigestUtils.md5DigestAsHex(password.getBytes());//2、根据页面提交的用户名username查询数据库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、查看员工状态,如果为已禁用状态,则返回员工已禁用结果if (emp.getStatus() == 0){return R.error("账号被禁用");}//6、登录成功,将员工id存入Session并返回登录成功结果request.getSession().setAttribute("employee",emp.getId());return R.success(emp);}
}

注意这里字符串比较要用equals方法,不能用== 

在EmpController中创建退出登录方法

业务逻辑:

①在controller中创建对应的处理方法来接受前端的请求,请求方式为post;
②清理session中的用户id
③返回结果(前端页面会进行跳转到登录页面)

 /***员工退出* @param request* @return*/@PostMapping("/logout")public R<String> logout(HttpServletRequest request){//清理Session中保存的当前登录员工的idrequest.getSession().removeAttribute("employee");return R.success("退出登录成功");}

完善登录功能

前面的登陆存在一个问题,如果用户不进行登陆,直接访问系统的首页,照样可以正常访问,这种设计是不合理的,我们希望看到的效果是只有完成了登陆后才可以访问系统中的页面,如果没有登陆则跳转到登陆页面;

创建自定义过滤器LoginCheckFilter

package com.itheima.reggie.filter;import lombok.extern.slf4j.Slf4j;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 = "/*")
public class LoginCheckFilter implements Filter {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;log.info("拦截到了请求:{}",request.getRequestURI());filterChain.doFilter(request,response);}
}

并在启动类上加@ServletComponentScan注解

业务逻辑:

1.获取本次请求的URI

2.判断本次请求是否需要处理

3.如果不需要处理,则直接放行

4.判断登录状态,如果已登录,则直接放行

5.如果未登录则返回未登录状态

具体逻辑的代码实现:

package com.itheima.reggie.filter;import com.alibaba.fastjson.JSON;
import com.itheima.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;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 = "/*")
public class LoginCheckFilter implements Filter {//路径匹配器,支持通配符public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;//1.获取本次请求的urlString requestURI = request.getRequestURI();log.info("拦截到请求:{}",requestURI);//定义不需要拦截的请求路径String[] urls = {"/employee/login","/employee/logout","/backend/**","front/**"};//2.判断本次请求是否需要处理boolean check = check(urls,requestURI);//3.如果不需要处理,则直接方行if (check == true){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;}public boolean check(String[] urls,String requestURI){for (String url : urls) {boolean match = PATH_MATCHER.match(url,requestURI);if(match == true){return true;}}return false;}
}

新增员工

程序执行过程:

1.页面发生ajax请求,将新增员工页面输入的数据以json的形式提交到服务端

2.服务端Controller接收页面提交的数据并调用Service将数据进行保存

3.Service调用Mapper操作数据库,保存数据

 /*** 新增员工* @param request* @param employee* @return*/@PostMappingpublic R<String> save(HttpServletRequest request,@RequestBody Employee employee){log.info("新增员工信息:{}",employee.toString());//设置初始密码,需要进行md5加密处理employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));employee.setCreateTime(LocalDateTime.now());employee.setUpdateTime(LocalDateTime.now());//获得当前登录用户的idLong empId = (Long) request.getSession().getAttribute("employee");employee.setCreateUser(empId);employee.setUpdateUser(empId);employeeService.save(employee);return R.success("新增用户成功");}

注意:员工id用的是mybatis-plus的雪花算法,自动生成,不用设置

全局异常处理器

因为username我们设置的是唯一索引,所以下次再新增和上一个用户的username相同的时候就会出现异常,这时候我们需要写一个全局异常处理器来捕获异常

在common包下写一个GlobalExceptionHandler

package com.itheima.reggie.common;import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;import java.sql.SQLIntegrityConstraintViolationException;/*** 全局异常处理*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {/*** 异常处理方法* @param ex* @return*/@ExceptionHandler(SQLIntegrityConstraintViolationException.class)public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex) {log.error(ex.getMessage());if (ex.getMessage().contains("Duplicate entry")) {//获取已经存在的用户名,这里是从报错的异常信息中获取的String[] split = ex.getMessage().split(" ");String msg = split[2] + "这个用户名已经存在";return R.error(msg);}return R.error("失败了");}
}

员工信息分页查询

程序执行过程:

先配置Mybatis-Plus提供的分页插件

package com.itheima.reggie.config;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return mybatisPlusInterceptor;}}

然后编写业务逻辑代码

/*** 员工页面分页查询* @param page* @param pageSize* @param name* @return*/@GetMapping("/page")public R<Page> page(int page,int pageSize,String name){log.info("page = {},pageSize = {},name = {}",page,pageSize,name);//构造分页构造器Page pageInfo = new Page(page,pageSize);//构造条件构造器LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();//添加过滤条件queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);//添加排序条件queryWrapper.orderByDesc(Employee::getUpdateTime);//执行查询employeeService.page(pageInfo,queryWrapper);return R.success(pageInfo);}
}

启用/禁用员工账号

程序执行过程:

1.页面发送ajax请求,将参数(id,status)提交到服务端

2.服务端Controller接受页面提交的数据并调用Service更新数据

3.Service调用Mapper操作数据库

启用,禁用的员工账号,本质上就是一个更新操作,也就是对status状态字段进行修改操作;

在controller中创建update方法,此方法是一个通用的修改员工信息的方法

 @PutMappingpublic R<String> upDate(HttpServletRequest request,@RequestBody Employee employee){log.info(employee.toString());employee.setUpdateTime(LocalDateTime.now());Long empId = (Long) request.getSession().getAttribute("employee");employee.setUpdateUser(empId);employeeService.updateById(employee);return R.success("员工信息修改成功");}

mybatis-plus对id使用了雪花算法,所以存入数据库中的id是19为长度,但是前端的js只能保证数据的前16位的数据的精度,对我们id后面三位数据进行了四舍五入,所以就出现了精度丢失;就会出现前度传过来的id和数据里面的id不匹配,就没办法正确的修改到我们想要的数据;

这时候我在employee类的id属性上加上了 @JsonFormat(shape = JsonFormat.Shape.STRING)

 @JsonFormat(shape = JsonFormat.Shape.STRING)private Long id;

编辑员工信息

业务逻辑主要在前端

程序执行流程:

1.点击编辑按钮时,页面跳转到add.html,并在url中携带参数(员工id)

2.在add.html页面中获取url中的参数(员工id)

3.发送ajax请求,请求服务端,同时提交员工id参数

4.服务端接收请求,根据员工id查询员工信息,将员工信息以json格式响应给页面

5.页面接收服务端响应的json数据,通过VUE的数据绑定进行员工信息回显

6.点击保存按钮,发送ajax请求,将页面中的员工信息以json格式提交给服务端

7.服务端接收员工信息,并进行处理,完成后给页面响应

8.页面接收到服务端响应信息后进行相应处理

后端代码:

 @GetMapping("/{id}")public R<Employee> getId(@PathVariable Long id){log.info("根据id查询员工信息");Employee employee = employeeService.getById(id);if(employee != null){return R.success(employee);}return R.error("没有查询到员工信息");}

公共字段填充

这里我们要使用Mybatis-Plus提供的公共字段自动填充功能

实现步骤:

1.在实体类的属性上加上@TableField注解,指定自动填充的策略

2.按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口

把相关的注解加在需要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;
package com.itheima.reggie.common;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {@Autowiredprivate HttpServletRequest request;/*** 插入操作自动填充* @param metaObject*/@Overridepublic void insertFill(MetaObject metaObject) {log.info("公共字段自动填充[insert]");log.info(metaObject.toString());metaObject.setValue("createTime", LocalDateTime.now());metaObject.setValue("createUser",request.getSession().getAttribute("employee"));metaObject.setValue("updateTime",LocalDateTime.now());metaObject.setValue("updateUser",request.getSession().getAttribute("employee"));}/*** 更新操作自动填充* @param metaObject*/@Overridepublic void updateFill(MetaObject metaObject) {log.info("公共字段自动填充[update]");log.info(metaObject.toString());metaObject.setValue("updateTime",LocalDateTime.now());metaObject.setValue("updateUser",request.getSession().getAttribute("employee"));}
}

新增分类

先创建实体类Category

package com.itheima.reggie.entity;import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.time.LocalDateTime;/*** 分类*/
@Data
public class Category implements Serializable {private static final long serialVersionUID = 1L;private Long id;//类型 1 菜品分类 2 套餐分类private Integer type;//分类名称private String name;//顺序private Integer sort;//创建时间@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;//是否删除private Integer isDeleted;}

CategoryMapper接口

package com.itheima.reggie.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.Category;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface CategoryMapper extends BaseMapper<Category> {}

CategoryService接口

package com.itheima.reggie.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.Category;public interface CategoryService extends IService<Category> {}

CategoryServiceImpl实现类

package com.itheima.reggie.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.Category;
import com.itheima.reggie.mapper.CategoryMapper;
import com.itheima.reggie.service.CategoryService;
import org.springframework.stereotype.Service;@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {}

CategoryController类

package com.itheima.reggie.controller;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.common.R;
import com.itheima.reggie.entity.Category;
import com.itheima.reggie.service.CategoryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;@RestController
@Slf4j
@RequestMapping("category")
public class CategoryController  {@Autowiredprivate CategoryService categoryService;}

然后在Controller层进行业务逻辑编写

程序执行流程:

1.页面(backend/page/category/list.html)发送ajax请求,将新增分类窗口输入的数据以json格式提交到服务端

2.服务端Controller接收页面提交的数据并调用Service将数据进行保存

3.Service调用Mapper操作数据库,保存数据

@PostMappingpublic R<String> save(@RequestBody Category category){log.info("{category}",category);categoryService.save(category);return R.success("新增分类成功");}

菜品类的分页

程序执行流程:

1.页面发送ajax请求,将分页查询参数(page,pageSize)提交到服务端

2.服务端Controller接收页面提交的数据并调用Service查询数据

3.Service调用Mapper操作数据库,查询分页数据

4.Controller将查询到的分页数据响应给页面

5.页面接收到分页数据并通过ElementUI的Table组件展示到页面上

 @GetMapping("/page")public R<Page> page(int page,int pageSize){//分页构造器Page<Category> pageInfo = new Page<>(page,pageSize);//条件构造器LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.orderByAsc(Category::getSort);//进行分页查询categoryService.page(pageInfo,queryWrapper);return R.success(pageInfo);}

删除分类

前端的js只能保证数据的前16位的数据的精度,对我们id后面三位数据进行了四舍五入,所以就出现了精度丢失,这里我们还是要在Category类的id上加入@JsonFormat(shape = JsonFormat.Shape.STRING)

@JsonFormat(shape = JsonFormat.Shape.STRING)private Long id;
 @DeleteMapping()public R<String> delete(@RequestParam("ids") Long ids){ //注意这里前端传过来的数据是idslog.info("删除分类,id为{}",ids);categoryService.removeById(ids);return R.success("分类信息删除成功");}

因为可能需要删除的数据是与其他菜品表和套餐表关联的,所以删除之前要先判断该条数据是否与菜品表和套餐表中的数据关联;

创建Dish实体类,Setmeal实体类,DishMapper接口,SetmealMappper接口,DishService接口,SetmealService接口,DishServiceImpl实现类,SetmealServiceImpl实现类

添加自定义的service方法:(就是我们需要的业务mybatis没有提供,所以就需要自己另外在service创建新的方法,并且在相关的业务中实现)

package com.itheima.reggie.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.Category;public interface CategoryService extends IService<Category> {public void remove(Long ids);}

自定义一个业务异常类

package com.itheima.reggie.common;/*** 自定义业务异常类*/
public class CustomException extends RuntimeException {public CustomException(String message){super(message);}
}

 在原先写好的全局异常处理中加入

@ExceptionHandler(CustomException.class)public R<String> exceptionHandler(CustomException ex) {log.error(ex.getMessage());return R.error(ex.getMessage());}

然后在CategoryServiceImpl实现类中重写这个方法

package com.itheima.reggie.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.common.CustomException;
import com.itheima.reggie.entity.Category;
import com.itheima.reggie.entity.Dish;
import com.itheima.reggie.entity.Setmeal;
import com.itheima.reggie.mapper.CategoryMapper;
import com.itheima.reggie.service.CategoryService;
import com.itheima.reggie.service.DishService;
import com.itheima.reggie.service.SetmealService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
@Slf4j
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {@Autowiredprivate DishService dishService;@Autowiredprivate SetmealService setmealService;/*** 根据id删除分类,删除之前判断是否和菜品或套餐有关联数据* @param ids*/@Overridepublic void remove(Long ids) {LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();//添加查询条件dishLambdaQueryWrapper.eq(Dish::getCategoryId,ids);int count1 = dishService.count(dishLambdaQueryWrapper);//查询当前分类是否关联了菜品,如果已经关联,则直接抛出异常if (count1 > 0){throw new CustomException("当前分类下关联了菜品,不能删除");}LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();//添加查询条件setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,ids);int count2 = setmealService.count(setmealLambdaQueryWrapper);//查询当前分类是否关联了套餐,如果已经关联,则直接抛出异常if (count2 > 0){throw new CustomException("当前分类下关联了套餐,不能删除");}super.removeById(ids);}
}

最后在CategoryController中调用

@DeleteMapping()public R<String> delete(@RequestParam("ids") Long ids){ //注意这里前端传过来的数据是idslog.info("删除分类,id为{}",ids);//categoryService.removeById(ids);categoryService.remove(ids);return R.success("分类信息删除成功");}

修改分类

这里的编辑的数据回显,前端已经帮我们做好了,所以我们就不需要去数据库查询了,这样可以减少对数据库的操作

@PutMappingpublic R<String> update(@RequestBody Category category){log.info("修改分类信息:{}",category);categoryService.updateById(category);return R.success("修改分类信息成功");}

记得在对应的实体类加上公共字段的值设置:前面我们配置了这个,所以这里只需要加注解就行

    //创建时间@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;

文件上传下载

yml配置文件:配置上传图片的存储位置

reggie:path: D:\reggie\

新建一个CommonController类用于文件上传和下载 

package com.itheima.reggie.controller;import com.itheima.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
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")
@Slf4j
public class CommonController {@Value("${reggie.path}")private String basePath;/*** 文件上传* @param file* @return*/@PostMapping("/upload")public R<String> upload(MultipartFile file){//file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除log.info(file.toString());//原始文件名String originalFilename = file.getOriginalFilename();//abc.jpgString suffix = originalFilename.substring(originalFilename.lastIndexOf("."));//使用UUID重新生成文件名,防止文件名称重复造成文件覆盖String fileName = UUID.randomUUID().toString() + suffix;//dfsdfdfd.jpg//创建一个目录对象File dir = new File(basePath);//判断当前目录是否存在if(!dir.exists()){//目录不存在,需要创建dir.mkdirs();}try {//将临时文件转存到指定位置file.transferTo(new File(basePath + fileName));} catch (IOException e) {e.printStackTrace();}return R.success(fileName);}/*** 文件下载* @param name* @param response*/@GetMapping("/download")public void download(String name, HttpServletResponse response){try {//输入流,通过输入流读取文件内容FileInputStream fileInputStream = new FileInputStream(new File(basePath + name));//输出流,通过输出流将文件写回浏览器ServletOutputStream outputStream = response.getOutputStream();response.setContentType("image/jpeg");int len = 0;byte[] bytes = new byte[1024];while ((len = fileInputStream.read(bytes)) != -1){outputStream.write(bytes,0,len);outputStream.flush();}//关闭资源outputStream.close();fileInputStream.close();} catch (Exception e) {e.printStackTrace();}}
}

新增菜品

创建DishFlavor实体类,DishFlavorMapper,DishFalvorService,DishFlavorServiceImpl

在CategoryController里写代码:

 @GetMapping("/list")//这个接口接收到参数其实就是一个前端传过来的type,这里之所以使用Category这个类来接受前端的数据,是为了以后方便//因为这个Category类里面包含了type这个数据,返回的数据多了,你自己用啥取啥就行private R<List<Category>> list(Category category){//条件构造器LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper();//添加查询条件queryWrapper.eq(category.getType() != null,Category::getType,category.getType());//添加排序条件  使用两个排序条件,如果sort相同的情况下就使用更新时间进行排序queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);List<Category> list = categoryService.list(queryWrapper);return R.success(list);}

点击保存按钮的时候,把前端的json数据提交到后台,后台接收数据,对数据进行处理;要与两张表打交道,一个是dish一个是dish_flavor表;

创建DishDto封装数据

package com.itheima.reggie.dto;import com.itheima.reggie.entity.Dish;
import com.itheima.reggie.entity.DishFlavor;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;@Data
public class DishDto extends Dish {private List<DishFlavor> flavors = new ArrayList<>();private String categoryName;private Integer copies;
}

在DishService中新增一个方法

 //新增菜品,同时插入菜品对应的口味数据,需要操作两张表:dish、dish_flavorpublic void saveWithFlavor(DishDto dishDto);

在DishServiceImpl重写这个方法

 @Transactionalpublic void saveWithFlavor(DishDto dishDto) {//保存菜品的基本信息到菜品表dishthis.save(dishDto);Long dishId = dishDto.getId();//菜品id//菜品口味List<DishFlavor> flavors = dishDto.getFlavors();flavors = flavors.stream().map((item) -> {item.setDishId(dishId);return item;}).collect(Collectors.toList());//保存菜品口味数据到菜品口味表dish_flavordishFlavorService.saveBatch(flavors);}

Controller层调用

 @PostMappingpublic R<String> save(@RequestBody DishDto dishDto){log.info(dishDto.toString());dishService.saveWithFlavor(dishDto);return R.success("新增菜品成功");}

菜品信息分页查询

/*** 菜品信息分页查询* @param page* @param pageSize* @param name* @return*/@GetMapping("/page")public R<Page> page(int page,int pageSize,String name){//构造一个分页构造器对象Page<Dish> dishPage = new Page<>(page,pageSize);Page<DishDto> dishDtoPage = new Page<>(page,pageSize);//上面对dish泛型的数据已经赋值了,这里对DishDto我们可以把之前的数据拷贝过来进行赋值//构造一个条件构造器LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();//添加过滤条件 注意判断是否为空  使用对name的模糊查询queryWrapper.like(name != null,Dish::getName,name);//添加排序条件  根据更新时间降序排queryWrapper.orderByDesc(Dish::getUpdateTime);//去数据库处理分页 和 查询dishService.page(dishPage,queryWrapper);//获取到dish的所有数据 records属性是分页插件中表示分页中所有的数据的一个集合List<Dish> records = dishPage.getRecords();List<DishDto> list = records.stream().map((item) ->{//对实体类DishDto进行categoryName的设值DishDto dishDto = new DishDto();//这里的item相当于Dish  对dishDto进行除categoryName属性的拷贝BeanUtils.copyProperties(item,dishDto);//获取分类的idLong categoryId = item.getCategoryId();//通过分类id获取分类对象Category category = categoryService.getById(categoryId);if ( category != null){//设置实体类DishDto的categoryName属性值String categoryName = category.getName();dishDto.setCategoryName(categoryName);}return dishDto;}).collect(Collectors.toList());//对象拷贝  使用框架自带的工具类,第三个参数是不拷贝到属性BeanUtils.copyProperties(dishPage,dishDtoPage,"records");dishDtoPage.setRecords(list);//因为上面处理的数据没有分类的id,这样直接返回R.success(dishPage)虽然不会报错,但是前端展示的时候这个菜品分类这一数据就为空//所以进行了上面的一系列操作return R.success(dishDtoPage);}

修改菜品

菜品信息的回显

在DishService里添加自己要实现的方法

//根据id查询菜品信息和对应的口味信息public DishDto getByIdWithFlavor(Long id);

方法的实现

public DishDto getByIdWithFlavor(Long id) {//查询菜品基本信息,从dish表查询Dish dish = this.getById(id);DishDto dishDto = new DishDto();BeanUtils.copyProperties(dish,dishDto);//查询当前菜品对应的口味信息,从dish_flavor表查询LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(DishFlavor::getDishId,dish.getId());List<DishFlavor> flavors = dishFlavorService.list(queryWrapper);dishDto.setFlavors(flavors);return dishDto;}

Controller层调用

/*** 根据id来查询菜品信息和对应的口味信息* @param id* @return*/
@GetMapping("/{id}")
public R<DishDto> get(@PathVariable Long id){  //这里返回什么数据是要看前端需要什么数据,不能直接想当然的就返回Dish对象DishDto dishDto = dishService.getByIdWithFlavor(id);return R.success(dishDto);
}

保存修改

在DishService添加自己想要的方法

//更新菜品信息,同时更新对应的口味信息public void updateWithFlavor(DishDto dishDto);

方法的实现

 @Override@Transactionalpublic void updateWithFlavor(DishDto dishDto) {//更新dish表基本信息this.updateById(dishDto);//清理当前菜品对应口味数据---dish_flavor表的delete操作LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper();queryWrapper.eq(DishFlavor::getDishId,dishDto.getId());dishFlavorService.remove(queryWrapper);//添加当前提交过来的口味数据---dish_flavor表的insert操作List<DishFlavor> flavors = dishDto.getFlavors();flavors = flavors.stream().map((item) -> {item.setDishId(dishDto.getId());return item;}).collect(Collectors.toList());dishFlavorService.saveBatch(flavors);}

套餐管理

新增套餐时涉及两个表,setmeal套餐表,setmeal_dish套餐菜品分类表

创建SetmealDish实体类,SetmealService,SetmealServiceImpl,SetmealController,SetmealDto

新增套餐

新增套餐时前端页面和服务端的交互过程:

1.页面(backend/page/combo/add.html),请求服务端获取套餐分类数据并展示到下拉框中

2.页面发送ajax请求,请求服务端获取菜品分类数据并展示到添加菜品窗口中

3.页面发送ajax请求,请求服务端,根据菜品分类查询对应的菜品数据并展示到添加类窗口中

4.页面发送请求进行图片上传,请求服务端将图片保存到服务器

5.页面发送请求进行图片下载,将上传的图片进行回显

6.点击保存按钮,发送ajax请求,将套餐相关数据以json格式提交到服务端

在DishController中书写代码:

/*** 根据条件查询对应的菜品数据* @param dish* @return*/@GetMapping("/list")public R<List<DishDto>> list(Dish dish) {log.info("dish:{}", dish);//条件构造器LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.like(StringUtils.isNotEmpty(dish.getName()), Dish::getName, dish.getName());queryWrapper.eq(null != dish.getCategoryId(), Dish::getCategoryId, dish.getCategoryId());//添加条件,查询状态为1(起售状态)的菜品queryWrapper.eq(Dish::getStatus,1);queryWrapper.orderByDesc(Dish::getUpdateTime);List<Dish> dishs = dishService.list(queryWrapper);List<DishDto> dishDtos = dishs.stream().map(item -> {DishDto dishDto = new DishDto();BeanUtils.copyProperties(item, dishDto);Category category = categoryService.getById(item.getCategoryId());if (category != null) {dishDto.setCategoryName(category.getName());}LambdaQueryWrapper<DishFlavor> wrapper = new LambdaQueryWrapper<>();wrapper.eq(DishFlavor::getDishId, item.getId());dishDto.setFlavors(dishFlavorService.list(wrapper));return dishDto;}).collect(Collectors.toList());

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/60833.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

App Store用户评论如何影响ASO优化

您是否专注于提高应用的知名度&#xff0c;并想知道应用商店评分和用户评论如何发挥作用&#xff1f;应用商店用户评论和评分对于塑造应用的成功至关重要&#xff0c;并且可以显著影响您的应用商店优化 (ASO) 策略。本文提供了利用这些元素为您带来优势的见解和策略。 如今&…

我谈二值形态学基本运算——腐蚀、膨胀、开运算、闭运算

Gonzalez从集合角度定义膨胀和腐蚀&#xff0c;不易理解。 Through these definitions, you can interpret dilation and erosion as sliding neighborhood operations analogous to convolution (or spatial filtering). 禹晶、肖创柏、廖庆敏《数字图像处理&#xff08;面向…

【AIGC】如何通过ChatGPT提示词Prompt定制个性学习计划

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | 提示词Prompt应用实例 文章目录 &#x1f4af;前言&#x1f4af;提示词&#x1f4af;配置信息使用方法 &#x1f4af;指令/language/plan/start/test/continue/config &#x1f4af;小结 &#x1f4af;前言 在这篇文章中…

RFID被装信息化监控:物联网解决方案深入分析

被装物联网信息化监控构成了一套复杂而高效的处理方案&#xff0c;它巧妙地将物联网技术与被装资源管理的具体需求相结合&#xff0c;实现了对被装资源实时监控、智能化调控和优化分配。以下是对被装物联网信息化监控的详细说明&#xff1a; 一、被装物联网信息化监控的定义 …

C++ 关于类与对象(中篇)一篇详解!(运算符重载)

赋值运算符重载 运算符重载 C 为了 增强代码的可读性 引入了运算符重载 &#xff0c; 运算符重载是具有特殊函数名的函数 &#xff0c;也具有其返回值类型&#xff0c;函数名字以及参数列表&#xff0c;其返回值类型与参数列表与普通的函数类似。 函数名字为&#xff1a;关键…

有效对接礼顿销售单:从数据获取到金蝶云存储

礼顿销售单对接项目&#xff1a;轻松实现数据集成 礼顿销售单对接&#xff08;91-零售业务/5-代销售(供货商发货)&#xff09; 在礼顿销售单对接项目中&#xff0c;我们面临的主要任务是将吉客云奇门的数据集成到金蝶云星空平台。这个过程不仅需要确保数据的准确性和完整性&am…

【C++】—— map 与 set 深入浅出:设计原理与应用对比

不要只因一次失败&#xff0c;就放弃你原来决心想达到的目的。 —— 莎士比亚 目录 1、序列式容器与关联式容器的概述与比较 2、set 与 multiset 2.1 性质分析&#xff1a;唯一性与多重性的差异 2.2 接口解析&#xff1a;功能与操作的全面解读 3、map 与 multimap 3.1 性…

基于微信小程序的平安驾校预约平台的设计与实现(源码+LW++远程调试+代码讲解等)

摘 要 互联网发展至今&#xff0c;广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对高校教师成果信息管理混乱&#xff0c;出错率高&#xff0c;信息安全性差&#xff0c;劳动强度大&#xff0c;费时费力…

SpringBoot+Vue3开发会议管理系统

1 项目介绍 会议管理系统&#xff0c;简化公司内会议方面的流程&#xff0c;提供便捷。实现对会议室的管理、会议的管理、会议预约的管理&#xff0c;三大主流程模块。 系统分为三种角色&#xff0c;分别是员工、管理员和超级管理员。 员工角色功能&#xff1a;查看会议室占…

Docker环境搭建Cloudreve网盘服务(附shell脚本一键搭建)

Docker搭建Cloudreve Cloudreve介绍&#xff1a; Cloudreve 是一个基于 ThinkPHP 框架构建的开源网盘系统&#xff0c;旨在帮助用户以较低的成本快速搭建起既能满足个人也能满足企业需求的网盘服务。Cloudreve 支持多种存储介质&#xff0c;包括但不限于本地存储、阿里云OSS、…

Cadence安装

记录一下安装过程&#xff0c;方便以后安装使用Cadence。 去吴川斌的博客下载安装包&#xff0c;吴川斌博客&#xff1a; https://www.mr-wu.cn/cadence-orcad-allegro-resource-downloads/ 下载阿狸狗破戒大师 我这边下载的是版本V3.2.6&#xff0c;同样在吴川斌的博客下载安装…

系统架构设计师:系统架构设计基础知识

从第一个程序被划分成模块开始&#xff0c;软件系统就有了架构。 现在&#xff0c;有效的软件架构及其明确的描述和设计&#xff0c;已经成为软件工程领域中重要的主题。 由于不同人对Software Architecture (简称SA) 的翻译不尽相同&#xff0c;企业界喜欢叫”软件架构“&am…

Java Web 工程全貌

通过下图&#xff0c;我们可以一览 Java Web 工程的全貌 通过上图&#xff0c;我们能够基本窥探整个 Java Web 工程的面貌&#xff0c;包括前端&#xff0c;后端&#xff0c;甚至是运维。 接下来&#xff0c;我们就结合文字描述&#xff0c;加深理解。 部署Vue前端和Spring…

Linux入门:环境变量与进程地址空间

一. 环境变量 1. 概念 1️⃣基本概念&#xff1a; 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数 如&#xff1a;我们在编写C/C代码的时候&#xff0c;在链接的时候&#xff0c;从来不知道我们的所链接的动态静态库在哪里&#x…

【优选算法 — 滑动窗口】水果成篮 找到字符串中所有字母异位词

水果成篮 水果成篮 题目描述 因为只有两个篮子&#xff0c;每个篮子装的水果种类相同&#xff0c;如果从 0 开始摘&#xff0c;则只能摘 0 和 1 两个种类 &#xff1b; 因为当我们在两个果篮都装有水果的情况下&#xff0c;如果再走到下一颗果树&#xff0c;果树的水果种类…

Java 中使用Mockito 模拟对象的单元测试的快速示例

Mockito是一个流行的Java模拟框架&#xff0c;它允许你在单元测试中创建和配置模拟对象&#xff0c;以便在测试过程中替换那些不容易构造或获取的对象。 Mockito可以与JUnit无缝集成&#xff0c;下面的示例演示 Mockito JUnit实现模拟对象的单元测试。 依赖导入 这里使用Mav…

STM32 创建一个工程文件(寄存器、标准库)

首先到官网下载对应型号的固件包&#xff1a; 像我的STM32F103C8T6的就下载这个&#xff1a; 依次打开&#xff1a; .\STM32F10x_StdPeriph_Lib_V3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm 可以看到&#xff1a; 这…

vue-h5:在h5中实现相机拍照加上身份证人相框和国徽框

方案1&#xff1a;排出来照片太糊了&#xff0c;效果不好 1.基础功能 参考&#xff1a; https://blog.csdn.net/weixin_45148022/article/details/135696629 https://juejin.cn/post/7327353533618978842?searchId20241101133433B2BB37A081FD6A02DA60 https://www.freesio…

初识GIS

文章目录 一、什么叫地理信息1、定义2、主要特点3、分类 二、什么叫GIS1、定义2、GIS对空间信息的储存2.1、矢量数据模型2.2、栅格数据模型 3、离散栅格和连续栅格的区别 三、坐标系统1、为什么要存在坐标系统&#xff1f;2、地理坐标系2.1、定义与特点2.2、分类 3、投影坐标系…

Android 开发指南:初学者入门

Android 是全球最受欢迎的移动操作系统之一&#xff0c;为开发者提供了丰富的工具和资源来创建各种类型的应用程序。本文将为你提供一个全面的入门指南&#xff0c;帮助你从零开始学习 Android 开发。 目录 1. 了解 Android 平台[1]2. 设置开发环境[2]3. 学习基础知识[3]4. 创…