外卖平台及移动端开发(后台) 一、项目背景介绍 技术选型
功能架构
二、开发环境搭建 1、数据库环境搭建
序号
表名
说明
1
category
菜品和套餐分类表
2
dish
菜品表
3
dish_flavor
菜品口味关系表
4
setmeal
套餐表
5
setmeal_dish
套餐菜品关系表
6
employee
员工表
7
8
9
10
11
2、maven项目搭建 2.1 创建maven项目
检查项目文件编码(UTF-8)
设置maven仓库
jdk配置(1.8)
2.2 导入pom文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 <?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 http://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.itreggie</groupId> <artifactId>reggie_take_out</artifactId> <version>1.0 -SNAPSHOT</version> <properties><java.version>1.8 </java.version></properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <scope>compile</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4 .2 </version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18 .20 </version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2 .76 </version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6 </version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1 .23 </version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.4 .5 </version> </plugin> </plugins> </build> </project>
2.3 创建application.xml文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 server: port: 8090 spring: application: name: reggie_take_out datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql: username: root password: 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:\MyCode\reggie_take_out\img\
2.4 创建boot程序入口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.itreggie.reggie;import lombok.extern.slf4j.Slf4j;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.web.servlet.ServletComponentScan;import org.springframework.transaction.annotation.EnableTransactionManagement;@Slf4j @SpringBootApplication @ServletComponentScan @EnableTransactionManagement public class ReggieApplication { public static void main (String[] args) { SpringApplication.run(ReggieApplication.class,args); log.info("项目启动成功。。。" ); } }
3、导入前端文件 3.1 默认方式 在Boot项目中,前台默认就只能访问 resource目录下的static和template文件夹下的文件;所以如果要使用这种方式,直接创建一个static目录就行,然后把这些前端资源放在这个static目录下就行;
3.2 自定义MVC支持
创建config文件包
创建WebMvcConfig;
记得在启动程序ReggieApplication加上@ServletComponentScan这个注解,否则这个配置类不会生效;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package com.itreggie.reggie.config;import com.itreggie.reggie.common.JacksonObjectMapper;import lombok.extern.slf4j.Slf4j;import org.springframework.context.annotation.Configuration;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;import java.util.List;@Slf4j @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { @Override protected void addResourceHandlers (ResourceHandlerRegistry registry) { log.info("开始静态资源映射。。。" ); registry.addResourceHandler("/backend/**" ).addResourceLocations("classpath:/backend/" ); registry.addResourceHandler("/front/**" ).addResourceLocations("classpath:/front/" ); } }
三、后台登陆功能开发–employee 1、需求分析 需求分析是通过产品原型来进行的,由产品经理负责。
2、代码开发 前端页面访问地址:http://localhost:8090/backend/page/login/login.html
2.1 创建相关的包
2.2 实体类entity和Mapper
在entity内创建employee的实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package com.itreggie.reggie.entity;import com.baomidou.mybatisplus.annotation.FieldFill;import com.baomidou.mybatisplus.annotation.TableField;import lombok.Data;import java.io.Serializable;import java.time.LocalDateTime;@Data public class Employee implements Serializable { private static final long serialVersionUID = 1L ; private Long id; private String username; private String name; private String password; private String phone; private String sex; private String idNumber; private Integer status; @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; }
在Mapper中使用mybatis_plus生成employeeMapper
1 2 3 4 5 6 7 8 9 package com.itreggie.reggie.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.itreggie.reggie.entity.Employee;import org.apache.ibatis.annotations.Mapper;@Mapper public interface EmpolyeeMapper extends BaseMapper <Employee> {}
2.3 service
定义employeeService接口
1 2 3 4 5 6 7 package com.itreggie.reggie.service;import com.baomidou.mybatisplus.extension.service.IService;import com.itreggie.reggie.entity.Employee;public interface EmployeeService extends IService <Employee> {}
定义employeeServiceImpl实现类
1 2 3 4 5 6 7 8 9 10 11 package com.itreggie.reggie.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.itreggie.reggie.entity.Employee;import com.itreggie.reggie.mapper.EmpolyeeMapper;import com.itreggie.reggie.service.EmployeeService;import org.springframework.stereotype.Service;@Service public class EmployeeServiceImpl extends ServiceImpl <EmpolyeeMapper, Employee> implements EmployeeService {}
2.4 返回结果类R 将共同使用的类存放进common包中,将返回结果类R放入common包中进行封装。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package com.itreggie.reggie.common;import lombok.Data;import java.util.HashMap;import java.util.Map;@Data public class R <T> { private Integer code; 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 ; } }
2.5 controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 package com.itreggie.reggie.controller;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;import com.itreggie.reggie.common.R;import com.itreggie.reggie.entity.Employee;import com.itreggie.reggie.service.EmployeeService;import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang.StringUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration;import org.springframework.util.DigestUtils;import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpServletRequest;import java.nio.charset.StandardCharsets;import java.time.LocalDateTime;@Slf4j @RestController @RequestMapping("/employee") public class EmployeeController { @Autowired private EmployeeService employeeService; @PostMapping("/login") public R<Employee> login (HttpServletRequest request,@RequestBody Employee employee) { String password = employee.getPassword(); password = DigestUtils.md5DigestAsHex(password.getBytes()); LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(Employee::getUsername,employee.getUsername()); Employee emp = employeeService.getOne(queryWrapper); if (emp == null ){ return R.error("登陆失败" ); } if (!emp.getPassword().equals(password)){ return R.error("登陆失败" ); } if (emp.getStatus() == 0 ){ return R.error("账号已禁用" ); } request.getSession().setAttribute("employee" ,emp.getId()); return R.success(emp); }
3、功能测试
使用debug启动项目
浏览器F12打开管理员模式查看请求情况
查看“响应”的“local storage”中,“key”和“value”是否正常显示了“userInfo”和employee表中信息
四、后台系统退出功能–employee 1、处理业务逻辑
在controller中创建对应的处理方法来接受前端的请求,请求方式为post;
清理session中的用户id
返回结果(前端页面会进行跳转到登录页面)
前端浏览器清除数据
2、编码处理 后端:在employeeController中编写
1 2 3 4 5 6 7 8 9 @PostMapping("/logout") public R<String> logout (HttpServletRequest request) { request.getSession().removeAttribute("employee" ); return R.success("退出成功" ); }
前端:在”/backend/index.html”中编写方法
1 2 3 4 5 6 7 8 logout() { logoutApi().then((res)=>{ if (res.code === 1 ){ localStorage.removeItem('userInfo' ) window.location.href = '/backend/page/login/login.html' } }) },
3、功能测试 尝试登陆再退出;
查看浏览器中的数据是否会被清除(F12)。
五、员工管理模块–employee 1、完善登陆功能
处理业务逻辑:使用过滤器或者是拦截器,在拦截器或者是过滤器中判断用户是否已经完成了登陆,如果没有登陆则跳转到登陆页面
代码实现
①在Filter包中创建自定义过滤器LongCheckFilter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 package com.itreggie.reggie.filter;import com.alibaba.fastjson.JSON;import com.itreggie.reggie.common.BaseContext;import com.itreggie.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;@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 chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; String requestURI = request.getRequestURI(); log.info("拦截到请求:{}" , requestURI); String[] urls = new String []{ "/employee/login" , "/employee/logout" , "/backend/**" , "/front/**" , "/common/**" }; boolean check = check(urls, requestURI); if (check) { log.info("本次请求{}不需要处理" , requestURI); chain.doFilter(request, response); return ; } if (request.getSession().getAttribute("employee" ) != null ) { log.info("用户已登陆,用户id为:{}" , request.getSession().getAttribute("employee" )); Long empId = (Long) request.getSession().getAttribute("employee" ); BaseContext.setCurrentId(empId); chain.doFilter(request, response); return ; } log.info("用户未登陆" ); 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) { return true ; } } return false ; } }
②在启动类加上注解@ServletComponentScan
③完善过滤器的处理逻辑
2、新增员工 新增员工,其实就是将我们的新增页面录入的员工数据插入到employee表;注意:employee表中对username字段加入了唯一的约束,因为username是员工的登陆账号,必须是唯一的!
2.1 处理业务逻辑 1. 页面发送ajax请求,将新增员工页面中输入的数据以json的形式提交到服务端
2. 服务端controller接收页面提交的数据并调用service将数据进行保存
3. service调用Mapper操作数据库,保存数据到数据库中
2.2 编码处理 在employeeController中编写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @PostMapping public R<String> save (HttpServletRequest request,@RequestBody Employee employee) { log.info("新增员工,员工信息:{}" ,employee.toString()); employee.setPassword(DigestUtils.md5DigestAsHex("123456" .getBytes())); employeeService.save(employee); return R.success("新增员工成功" ); }
2.3 功能测试 登陆之后,点击添加,然后确认,然后去数据库看一下新增数据成功没,新增成功,那就表示代码可以执行;
2.4 补充–全局异常捕获 注意:因为我们把username设置为唯一索引,所以下次再新增用户的时候,就会从MySQL数据库抛出来一个异常;
2.4.1 解决方法:
2.4.2 编码处理 全局异常捕获GlobalExceptionHandler写在common包下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.itreggie.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.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("未知错误" ); } }
2.4.3 功能测试 登陆后,添加一个一个已经存在账号名,看前端页面提示的是什么信息,以及看后台是否输出了报错日志;
2.4.4 总结
3、员工信息分页查询 3.1 处理业务逻辑 1. 页面发送ajax请求,将分页查询参数(page,pageSize,name)提交到服务端
1. 服务端controller接收页面提交的数据并调用service查询数据
1. service调用Mapper操作数据库,查询分页数据
1. controller将查询到的分页数据响应给页面
1. 页面接受到分页数据并通过ElementUI的table组件展示到页面上
3.2 编码处理
3.3 功能测试 分页的三个时机,①用户登录成功时,分页查询一次 ②用户使用条件查询的时候分页一次 ③跳转页面的时候分页查询一次
4、员工账号信息修改(禁用/启用) 4.1 需求分析
只有管理员(admin用户)才可以对其他普通用户进行启用操作,禁用操作;
如果某个员工账号的状态为正常,则按钮显示为’‘禁用’,如果员工账号状态为已禁用,则按钮显示为“启用”;
普通员工登录系统后,启用,禁用按钮不显示。
4.2 处理业务逻辑 1. 页面发送ajax请求,将参数(id,status)提交到服务端
2. 服务端controller接收页面提交的数据并调用service更新数据
3. service调用Mapper操作数据库
4.3 编码处理 前端代码:实现**”管理员(admin用户)才可以对其他普通用户进行启用操作,禁用操作”**
注意:这里修改状态码要反着来,因为正常的用户你只能把它设置为禁用;已经禁用的账号你只能把它设置为正常
后端代码:在employeeController中创建update方法,此方法是一个通用的修改员工信息的方法 ,因为status是employee中的一个属性;这里使用了动态SQL的功能,根据具体的数据修改对应的字段信息;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @PutMapping public R<String> update (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("员工信息修改成功" ); }
4.4 功能测试 测试的时候发现问题,修改员工的状态时,提示信息显示修改成功,但是数据库查验证的时候,发现员工的状态码没有变化。原因是:mybatis-plus对id使用了雪花算法,所以存入数据库中的id是19为长度,但是前端的js只能保证数据的前16位的数据的精度,对我们id后面三位数据进行了四舍五入,所以就出现了精度丢失 ;就会出现前端传过来的id和数据里面的id不匹配,就没办法正确的修改到我们想要的数据。 解决bug的方法:
- 关闭mybatis-plus的雪花算法来处理ID
- 使用自增ID的策略来往数据库添加ID (√)
4.5 补充–使用自定义消息转换器防止精度丢失 4.5.1 需求分析 既然js对long型的数据会进行精度丢失,那么我们就对数据进行转型,我们可以在服务端(Java端)给页面响应json格式的数据时进行处理,将long型的数据统一转换为string字符串;
4.5.2 处理业务逻辑
4.5.3 编码处理 1、在common包中自定义消息转换类JacksonObjectMapper
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 package com.itreggie.reggie.common;import com.fasterxml.jackson.databind.DeserializationFeature;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.module .SimpleModule;import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;import java.math.BigInteger;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.LocalTime;import java.time.format.DateTimeFormatter;import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;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); } }
2、 在WebMvcConfig 配置类中扩展springmvc的消息转换器 ,在消息转换器中使用spring提供的对象转换器进行Java对象到json数据的转换;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package com.itreggie.reggie.config;import com.itreggie.reggie.common.JacksonObjectMapper;import lombok.extern.slf4j.Slf4j;import org.springframework.context.annotation.Configuration;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;import java.util.List;@Slf4j @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { @Override protected void addResourceHandlers (ResourceHandlerRegistry registry) { log.info("开始静态资源映射。。。" ); registry.addResourceHandler("/backend/**" ).addResourceLocations("classpath:/backend/" ); registry.addResourceHandler("/front/**" ).addResourceLocations("classpath:/front/" ); } @Override protected void extendMessageConverters (List<HttpMessageConverter<?>> converters) { log.info("扩展消息转换器。。。" ); MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter (); messageConverter.setObjectMapper(new JacksonObjectMapper ()); converters.add(0 ,messageConverter); } }
4.5.4 功能测试 启动程序,使用f12查看服务器响应到浏览器的用户id是不是变成了字符串,和数据库中是否相对应。发现对应,即消息转换器配置成功;再去测试启用与禁用员工账号 这个功能,检查数据库修改情况。
5、编辑员工信息 5.1 处理业务逻辑 1. 点击编辑按钮时,页面跳转到add.html,并在url中携带参数(员工id)
1. 在add.html页面获取url中的参数(员工id)
1. 发送ajax请求,请求服务端,同时提交员工id参数
1. 服务端接收请求,根据员工id查询员工信息,将员工信息以json形式响应给页面
1. 页面接收服务端响应的json数据,通过Vue的数据绑定进行员工信息回显
1. 点击保存按钮,发送ajax请求,将页面中的员工信息以json方式提交给服务端
1. 服务端接收员工信息,并进行处理,完成后给页面响应
1. 页面接收到服务端响应信息后进行相应处理
5.2 编码处理 后端代码:在employeeController中实现getById方法实现数据回显
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @GetMapping("/{id}") public R<Employee> getById (@PathVariable Long id) { log.info("查询员工信息。。。" ); Employee employee = employeeService.getById(id); if (employee != null ){ return R.success(employee); } return R.error("没有查询到对应员工信息" ); }
修改回显数据后,点击保存,会发送一个update的请求给后端,前面我们已经写了这个update的controller,所以只需要在前端跳转发请求就行;这样就实现了方法的复用,减少了代码量
5.3 功能测试 测试编辑,看能不能数据回显,可不可以修改成功,修改后数据库的数据有没有跟着变化。
5.4 补充–公共字段填充 5.4.1 需求分析
5.4.2 处理业务逻辑 1. 在实体类的属性上加入@tableField注解,指定自动填充的策略
1. 按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口
5.4.3 编码实现 将相关的注解加在需要Mybatis_plus自动填充的字段上
1 2 3 4 5 6 7 8 9 10 11 @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;
在common中设置处理类MyMetaObjectHandler,在此类中为公共字段赋值,并实现接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package com.itreggie.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;@Slf4j @Component 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()); 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()); } }
5.4.4 **功能完善–ThreadLocal动态获取登陆用户id 1. 需求分析:
完成公共字段自动填充功能的代码开发后,仍有一个问题没有解决,就是自动填充createUser和UpdateTime时设置的用户id不是动态的。为此使用ThreadLocal 来解决此问题。
http请求逻辑分析
ThreadLocal简介
处理业务逻辑
编写BaseContext工具类,基于ThreadLocal封装的工具类
在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
在MyMetaObjectHandler的方法中调用BaseContext获取用户登录的id
使用ThreadLocal动态获取并存储员工id
编码实现
在common中定义基于ThreadLocal封装工具类BaseContext ,用于保存和获取当前登录用户的id
注意:要先把数据设置进threadLocal中,才能获取到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.itreggie.reggie.common;public class BaseContext { private static ThreadLocal<Long> threadLocal = new ThreadLocal <>(); public static void setCurrentId (Long id) { threadLocal.set(id); } public static Long getCurrentId () { return threadLocal.get(); } }
在LoginCheckFilter这个过滤器中添加有关登录用户id的代码(添加/保存)
1 2 3 4 5 6 7 8 9 10 if (request.getSession().getAttribute("employee" ) != null ) { log.info("用户已登陆,用户id为:{}" , request.getSession().getAttribute("employee" )); Long empId = (Long) request.getSession().getAttribute("employee" ); BaseContext.setCurrentId(empId); chain.doFilter(request, response); return ; }
再将处理器MyMetaObjectHandler中的静态id改为动态获取
1 2 metaObject.setValue("createUser" , BaseContext.getCurrentId()); metaObject.setValue("updateUser" , BaseContext.getCurrentId());
功能测试
通过F12查看http请求信息,看看信息能否正常传输,若能则修改成功,实现了createUser和createTime的动态更新及存储。
六、菜品分类管理–category 1、新增分类 1.1 处理业务逻辑 1. 页面(backend/page/category/list.html)发送ajax请求,将新增分类窗口输入的数据以json形式提交到服务端
1. 服务端controller接收页面提交的数据并调用service将数据进行保存
1. service调用Mapper操作数据库,保存数据
1.2 编码实现 1.2.1 数据库表category结构
1.2.2 创建Mapper 1 2 3 4 5 6 7 8 9 10 package com.itreggie.reggie.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.itreggie.reggie.entity.Category;import org.apache.ibatis.annotations.Mapper;@Mapper public interface CategoryMapper extends BaseMapper <Category> {}
1.2.3 创建service 1 2 3 4 5 6 7 8 9 package com.itreggie.reggie.service;import com.baomidou.mybatisplus.extension.service.IService;import com.itreggie.reggie.entity.Category;public interface CategoryService extends IService <Category> {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.itreggie.reggie.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.itreggie.reggie.common.CustomException;import com.itreggie.reggie.entity.Category;import com.itreggie.reggie.entity.Dish;import com.itreggie.reggie.entity.Setmeal;import com.itreggie.reggie.mapper.CategoryMapper;import com.itreggie.reggie.service.CategoryService;import com.itreggie.reggie.service.DishService;import com.itreggie.reggie.service.SetmealService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Service public class CategoryServiceImpl extends ServiceImpl <CategoryMapper, Category> implements CategoryService {}
1.2.4 编写controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package com.itreggie.reggie.controller;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;import com.itreggie.reggie.common.R;import com.itreggie.reggie.entity.Category;import com.itreggie.reggie.service.CategoryService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import java.util.List;import java.util.Locale;@RestController @RequestMapping("/category") @Slf4j public class CategoryController { @Autowired private CategoryService categoryService; @PostMapping public R<String> save (@RequestBody Category category) { log.info("category:{}" ,category); categoryService.save(category); return R.success("新增分类成功" ); } }
1.3 功能测试 登录后,点击添加新增菜品分类,看是否成功,数据库的数据是否变化。
2、菜品类分页查询 2.1 处理业务逻辑 1. 页面发送ajax请求,将分页查询参数(page,pageSize)提交到服务端
1. 服务端controller接收页面提交的数据并调用service查询数据
1. service调用Mapper 操作数据库,查询分页数据
1. controller将查询到的分页数据响应给页面
1. 页面接收到分页数据并通过elementUI的table组件展示到页面上
2.2 编码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @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); }
2.3 功能测试 刷新页面,重新登录,查看页面是否展示数据库信息。
3、删除分类 需要删除的数据可能与其他表关联,所以删除之前要先判断该数据是否与其他表中的数据关联;
3.1 编码实现 1 2 3 4 5 6 7 8 9 10 @DeleteMapping() public R<String> delete (@RequestParam("ids") Long ids) { categoryService.removeById(ids); return R.success("分类信息删除成功" ); }
3.2 功能完善
3.2.1 创建实体类dish和setmeal的Mapper 1、在entity中创建实体类dish和setmeal。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 package com.itreggie.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 java.io.Serializable;import java.math.BigDecimal;import java.time.LocalDateTime;@Data public class Dish implements Serializable { private static final long serialVersionUID = 1L ; private Long id; private String name; private Long categoryId; private BigDecimal price; private String code; private String image; private String description; private Integer status; 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; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 package com.itreggie.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 java.io.Serializable;import java.math.BigDecimal;import java.time.LocalDateTime;@Data public class Setmeal implements Serializable { private static final long serialVersionUID = 1L ; private Long id; private Long categoryId; private String name; private BigDecimal price; private Integer status; private String code; private String description; 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; private Integer isDeleted; }
2、创建Mapper
1 2 3 4 5 6 7 8 9 10 package com.itreggie.reggie.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.itreggie.reggie.entity.Dish;import org.apache.ibatis.annotations.Mapper;@Mapper public interface DishMapper extends BaseMapper <Dish> {}
1 2 3 4 5 6 7 8 9 10 package com.itreggie.reggie.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.itreggie.reggie.entity.Setmeal;import org.apache.ibatis.annotations.Mapper;@Mapper public interface SetmealMapper extends BaseMapper <Setmeal> {}
3.2.2 创建service及实现类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.itreggie.reggie.service;import com.baomidou.mybatisplus.extension.service.IService;import com.itreggie.reggie.dto.DishDto;import com.itreggie.reggie.entity.Dish;import java.util.List;public interface DishService extends IService <Dish> { public void saveWithFlavor (DishDto dishDto) ; public DishDto getByIdWithFlavor (Long id) ; public void updateWithFlavor (DishDto dishDto) ; public void deleteByIds (List<Long> ids) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.itreggie.reggie.service;import com.baomidou.mybatisplus.extension.service.IService;import com.itreggie.reggie.dto.SetmealDto;import com.itreggie.reggie.entity.Setmeal;import java.util.List;public interface SetmealService extends IService <Setmeal> { public void saveWithDish (SetmealDto setmealDto) ; public void removeWithDish (List<Long> ids) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.itreggie.reggie.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.itreggie.reggie.common.CustomException;import com.itreggie.reggie.dto.DishDto;import com.itreggie.reggie.entity.Dish;import com.itreggie.reggie.entity.DishFlavor;import com.itreggie.reggie.mapper.DishMapper;import com.itreggie.reggie.service.DishFlavorService;import com.itreggie.reggie.service.DishService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.BeanUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import java.util.List;import java.util.stream.Collectors;@Service @Slf4j public class DishServiceImpl extends ServiceImpl <DishMapper, Dish> implements DishService {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package com.itreggie.reggie.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.itreggie.reggie.common.CustomException;import com.itreggie.reggie.dto.SetmealDto;import com.itreggie.reggie.entity.Setmeal;import com.itreggie.reggie.entity.SetmealDish;import com.itreggie.reggie.mapper.SetmealMapper;import com.itreggie.reggie.service.SetmealDishService;import com.itreggie.reggie.service.SetmealService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import java.util.List;import java.util.stream.Collectors;@Service @Slf4j public class SetmealServiceImpl extends ServiceImpl <SetmealMapper, Setmeal> implements SetmealService {}
3.2.3 添加自定义的service方法 在categoryService接口中添加,并重写实现类方法;
1 public void remove (Long id) ;
由于增加了判定(菜品是否有关联),所以要自定义业务异常,在common中创建CustomException
1 2 3 4 5 6 7 8 9 10 11 package com.itreggie.reggie.common;public class CustomException extends RuntimeException { public CustomException (String message) { super (message); } }
然后在外面前面写的GlobalExceptionHandler全局异常捕获器中添加该异常,这样就可以把相关的异常信息显示给前端操作的人员看见;
1 2 3 4 5 6 7 8 9 10 @ExceptionHandler(CustomException.class) public R<String> exceptionHandler (CustomException ex) { log.error(ex.getMessage()); return R.error(ex.getMessage()); }
在categoryServiceImpl中实现添加了关联判定的remove方法;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 package com.itreggie.reggie.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.itreggie.reggie.common.CustomException;import com.itreggie.reggie.entity.Category;import com.itreggie.reggie.entity.Dish;import com.itreggie.reggie.entity.Setmeal;import com.itreggie.reggie.mapper.CategoryMapper;import com.itreggie.reggie.service.CategoryService;import com.itreggie.reggie.service.DishService;import com.itreggie.reggie.service.SetmealService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Service public class CategoryServiceImpl extends ServiceImpl <CategoryMapper, Category> implements CategoryService { @Autowired private DishService dishService; @Autowired private SetmealService setmealService; @Override public void remove (Long id) { LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper <>(); dishLambdaQueryWrapper.eq(Dish::getCategoryId,id); int count1 = dishService.count(dishLambdaQueryWrapper); if (count1 > 0 ){ throw new CustomException ("当前分类项关联了菜品,删除失败" ); } LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper <>(); setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id); int count2 = setmealService.count(setmealLambdaQueryWrapper); if (count2 > 0 ){ throw new CustomException ("当前分类项关联了套餐,删除失败" ); } super .removeById(id); } }
3.2.4 调用remove方法 最后在categoryController中调用刚刚实现的remove方法,将之前的removeById方法取代。
1 2 3 4 5 6 7 8 9 10 11 12 13 @DeleteMapping public R<String> delete (Long ids) { log.info("删除分类,id为:{}" ,ids); categoryService.remove(ids); return R.success("分页信息删除成功" ); }
3.3 功能测试 自行添加测试数据测试就行;记得一定要测试一下删除有相关联的数据,看会不会删除和在前端提示异常信息。
4、修改分类 在分类管理列表页面点击修改按钮,弹出修改窗口,在修改窗口回显分类信息并进行修改,最后点击确定按钮完成修改。
前端已经完成了编辑的数据回显,这样可以减少对数据库的操作,所以我们就不需要去数据库查询数据了。
4.1 编码实现 在categoryController中实现update方法
1 2 3 4 5 6 7 8 9 10 11 12 13 @PutMapping public R<String> update (@RequestBody Category category) { log.info("修改分类信息:{}" ,category); categoryService.updateById(category); return R.success("修改分类信息成功" ); }
记得在category实体类中加上公共字段的值设置(公共字段填充) ,前面已经配置好了。
七、菜品管理的业务功能–dish&dish_flavor 1、*文件的上传和下载–commonController 1.1 整体介绍 文件上传
文件下载
1.2 前端代码实现
1.3 后端代码实现 在application.yml文件中配置上传图片的储存位置
1 2 reggie: path: D:\MyCode\reggie_take_out\img\
在controller包下创建commonController类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 package com.itreggie.reggie.controller;import com.itreggie.reggie.common.R;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Value;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;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; @PostMapping("/upload") public R<String> upload (MultipartFile file) { log.info(file.toString()); String originalFilename = file.getOriginalFilename(); String suffix = originalFilename.substring(originalFilename.lastIndexOf("." )); String fileName = UUID.randomUUID().toString() + suffix; File dir = new File (basePath); if (!dir.exists()){ dir.mkdirs(); } try { file.transferTo(new File (basePath+fileName)); } catch (IOException e) { throw new RuntimeException (e); } return R.success(fileName); } @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(); } } }
注意:这里上传的文件的文件名要和这个地方的一样,接收文件的参数的名不能随便定义,要和下面的name的值一致 ;
2、新增菜品 2.1 数据模型
2.2 *处理业务逻辑 1. 页面(backend/page/food/add.html)发送ajax请求,请求服务端获取菜品分类数据并展示到下拉框
1. 页面发送请求进行图片上传,请求服务端将图片保存到服务器
1. 页面发送请求进行图片下载,将上传的图片进行回显
1. 点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交到服务端
2.3 编码处理一 2.3.1 创建相关的Mapper和service层 1 2 3 4 5 6 7 8 9 10 package com.itreggie.reggie.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.itreggie.reggie.entity.DishFlavor;import org.apache.ibatis.annotations.Mapper;@Mapper public interface DishFlavorMapper extends BaseMapper <DishFlavor> {}
1 2 3 4 5 6 7 8 package com.itreggie.reggie.service;import com.baomidou.mybatisplus.extension.service.IService;import com.itreggie.reggie.entity.DishFlavor;public interface DishFlavorService extends IService <DishFlavor> {}
1 2 3 4 5 6 7 8 9 10 11 12 package com.itreggie.reggie.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.itreggie.reggie.entity.DishFlavor;import com.itreggie.reggie.mapper.DishFlavorMapper;import com.itreggie.reggie.service.DishFlavorService;import org.springframework.stereotype.Service;@Service public class DishFlavorServiceImpl extends ServiceImpl <DishFlavorMapper, DishFlavor> implements DishFlavorService {}
2.3.2 获取和返回菜品分类列表–CategoryController 前端的主要代码为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 const getCategoryList = (params) => { return $axios({ url: '/category/list' , method: 'get' , params }) } if (res.code === 1 ) { this .dishList = res.data } 这是菜品分类和数据双向绑定的前端代码: 我们返回的是一个集合, </el-form-item> <el-form-item label="菜品分类:" prop="categoryId" > <el-select v-model="ruleForm.categoryId" placeholder="请选择菜品分类" > <el-option v-for ="(item,index) in dishList" :key="index" :label="item.name" :value="item.id" /> </el-select> </el-form-item>
在CategoryController中书写后端查询代码,不过这里的返回值和参数接收值可能和自己想的有点不一样。。。这个的返回值和参数值 值得多思考一下; 这里之所以返回list集合,是因为这个要展示的数据是引用类型的数据集,集合可以存放任意类型的数据;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @GetMapping("/list") public R<List<Category>> list (Category category) { LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper (); queryWrapper.eq(category.getType() != null ,Category::getType,category.getType()); queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime); List<Category> list = categoryService.list(queryWrapper); return R.success(list); }
2.3.3 功能测试一 在浏览器中通过F12检查返回数据,确认页面数据能正常输送。
2.4 编码处理二 2.4.1 接收页面提交的数据 点击保存按钮的时候,把前端的json数据提交到后台,后台接收数据,对数据进行处理;要与两张表打交道,一个是dish一个是dish_flavor表;
先用前端页面向后端发一次请求,看看前端具体的请求是什么,我们好写controller;然后再看前端提交携带的参数是什么,我们好选择用什么类型的数据来接收!!!
从上图传出来的data数据可以看出参数比较复杂,可以使用两种方式进行封装:
- 创建与这些数据对应的实体类(dto)【√】
- 使用map进行接收
2.4.2 **创建DishDto实体类封装参数
创建dto包,并创建DishDto实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.itreggie.reggie.dto;import com.itreggie.reggie.entity.Dish;import com.itreggie.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; }
通过DishDto中的flavors参数封装数据
2.4.3 实现菜品新增功能–dishController 后端代码 在DishService中新增方法saveWithFlavor:
1 2 public void saveWithFlavor (DishDto dishDto) ;
在DishServiceImpl中实现方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Autowired private DishFlavorService dishFlavorService;@Override @Transactional public void saveWithFlavor (DishDto dishDto) { this .save(dishDto); Long dishId = dishDto.getId(); List<DishFlavor> flavors = dishDto.getFlavors(); flavors = flavors.stream().map((item)->{ item.setDishId(dishId); return item; }).collect(Collectors.toList()); dishFlavorService.saveBatch(flavors); }
在启动类ReggieApplication中开启事务: 加上这个注解 @EnableTransactionManagement(上面已经完成)
在controller包中创建DishController并实现新增菜品的save方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package com.itreggie.reggie.controller;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;import com.itreggie.reggie.common.R;import com.itreggie.reggie.dto.DishDto;import com.itreggie.reggie.entity.Category;import com.itreggie.reggie.entity.Dish;import com.itreggie.reggie.entity.DishFlavor;import com.itreggie.reggie.service.CategoryService;import com.itreggie.reggie.service.DishFlavorService;import com.itreggie.reggie.service.DishService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.BeanUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.web.bind.annotation.*;import java.util.List;import java.util.stream.Collectors;@RestController @RequestMapping("/dish") @Slf4j public class DishController { @Autowired private CategoryService categoryService; @Autowired private DishService dishService; @Autowired private DishFlavorService dishFlavorService; @PostMapping public R<String> save (@RequestBody DishDto dishDto) { log.info(dishDto.toString()); dishService.saveWithFlavor(dishDto); return R.success("新增菜品成功" ); } }
前端代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <el-button type="primary" @click ="submitForm('ruleForm')" > 保存 </el-button> let params = {...this .ruleForm}params.status = this .ruleForm ? 1 : 0 params.price *= 100 params.categoryId = this .ruleForm.categoryId params.flavors = this .dishFlavors.map(obj => ({ ...obj, value: JSON.stringify(obj.value) })) if (this .actionType == 'add' ) { delete params.id addDish (params) .then(res => { if (res.code === 1 ) { this .$message.success('菜品添加成功!' ) if (!st) { this .goBack() } else { .... const addDish = (params) => { return $axios({ url: '/dish' , method: 'post' , data: { ...params } }) }
2.4.4 功能测试二 点击保存按钮,查看页面是否弹出’’菜品添加成功’’提示框,数据库表数据是否有更新修改。
3、菜品信息分页查询 3.1 处理业务逻辑 交互流程梳理
1. 页面(backend/page/food/list.html)发送ajax请求,将分页查询参数(page,pageSize,name)提交到服务端,获取分页数据
1. 页面发送请求,请求服务端进行图片下载,用于页面图片展示
开发菜品信息分页查询功能,实际在服务端处理前端页面发送的这2次请求即可。
3.2 编码处理 图片下载的请求前面已经写好了,前端也写好了相关的请求,所以只需要处理第一个请求;
3.2.1 DishController中实现page方法(需完善) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @GetMapping("/page") public R<Page> page (int page,int pageSize,String name) { Page<Dish> dishPage = new Page <>(page,pageSize); LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.like(name != null ,Dish::getName,name); queryWrapper.orderByDesc(Dish::getUpdateTime); dishService.page(dishPage,queryWrapper); return R.success(dishPage); }
3.3 *功能完善 通过引入DishDto,完善分页查询代码,解决菜品分类数据为空 这一问题
DishDto代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.itreggie.reggie.dto;import com.itreggie.reggie.entity.Dish;import com.itreggie.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; }
改进page方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 @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); LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.like(name != null ,Dish::getName,name); queryWrapper.orderByDesc(Dish::getUpdateTime); dishService.page(dishPage,queryWrapper); List<Dish> records = dishPage.getRecords(); List<DishDto> list = records.stream().map((item) ->{ DishDto dishDto = new DishDto (); BeanUtils.copyProperties(item,dishDto); Long categoryId = item.getCategoryId(); Category category = categoryService.getById(categoryId); if ( category != null ){ String categoryName = category.getName(); dishDto.setCategoryName(categoryName); } return dishDto; }).collect(Collectors.toList()); BeanUtils.copyProperties(dishPage,dishDtoPage,"records" ); dishDtoPage.setRecords(list); return R.success(dishDtoPage); }
records的值: protected List records,实现:
3.4 功能测试
刷新页面,重新登录,点击菜品分类按钮,呈现页面实现:
4、修改菜品 4.1 处理业务流程 1. 页面发送ajax请求,请求服务端获取分类数据,用于菜品分类下拉框中数据展示
2. 页面发送ajax请求,请求服务端根据id查询当前菜品信息,用于菜品信息回显
3. 页面发送请求,请求服务端进行图片下载,用于页面图片回显
4. 点击保存按钮,页面发送ajax请求,将修改后的菜品相关数据以json形式提交到服务端
开发修改菜品功能,实际在服务端处理以上前端页面发送的4次请求即可。其中,第一次交互的后端代码已经完成;菜品分类的信息做新增菜品的时候已经完成,这里前端发一个相关接口的请求就行;第三次交互图片的下载前面也已经完成,所以前端直接发生请求就行;所以只需要处理第二次和第四次请求 。
4.2 编码处理 4.2.1 实现菜品信息的回显 在DishService层添加菜品信息回显 的getWithFlavor方法:
1 2 public DishDto getByIdWithFlavor (Long id) ;
在Impl中实现方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public DishDto getByIdWithFlavor (Long id) { Dish dish = this .getById(id); DishDto dishDto = new DishDto (); BeanUtils.copyProperties(dish,dishDto); LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(DishFlavor::getDishId,dish.getId()); List<DishFlavor> flavors = dishFlavorService.list(queryWrapper); dishDto.setFlavors(flavors); return dishDto; }
DishController层的实现:
1 2 3 4 5 6 7 8 9 10 @GetMapping("/{id}") public R<DishDto> get (@PathVariable Long id) { DishDto dishDto = dishService.getByIdWithFlavor(id); return R.success(dishDto); }
4.2.2 **保存修改 在DishService中添加保存修改设计两张表的数据 的updateWithFlavor方法
1 2 public void updateWithFlavor (DishDto dishDto) ;
impl中的实现方法为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Override @Transactional public void updateWithFlavor (DishDto dishDto) { this .updateById(dishDto); LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper (); queryWrapper.eq(DishFlavor::getDishId,dishDto.getId()); dishFlavorService.remove(queryWrapper); List<DishFlavor> flavors = dishDto.getFlavors(); flavors = flavors.stream().map((item)->{ item.setDishId(dishDto.getId()); return item; }).collect(Collectors.toList()); dishFlavorService.saveBatch(flavors); }
4.3 功能测试 点击页面修改按钮,修改数据后点击保存,查看页面数据回显情况
5、菜品的启售和停售 5.1 编码处理 前端发过来的请求(使用的是post方式):http://localhost:8090/dish/status/1?ids=
后端在DishController中通过status方法接收前端请求并进行修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @PostMapping("/status/{status}") public R<String> status (@PathVariable("status") Integer status,Long ids) { log.info("status:{}" ,status); log.info("ids:{}" ,ids); Dish dish = dishService.getById(ids); if (dish != null ){ dish.setStatus(status); dishService.updateById(dish); return R.success("开始启售" ); } return R.error("售卖状态设置异常" ); }
6、菜品的批量启售和批量停售 6.1 编码处理 把上面对单个菜品的售卖状态的方法进行修改;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @PostMapping("/status/{status}") public R<String> status (@PathVariable("status") Integer status,@RequestParam List<Long> ids) { LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper (); queryWrapper.in(ids !=null ,Dish::getId,ids); List<Dish> list = dishService.list(queryWrapper); for (Dish dish : list) { if (dish != null ){ dish.setStatus(status); dishService.updateById(dish); } } return R.success("售卖状态修改成功" ); }
注意:controller层的代码是不可以直接写业务的,建议把它抽离到service层,controller调用一下service的方法就行;
7、删除菜品(批量/单个) 7.1 编码处理 在DishFlavor实体类中,在private Integer isDeleted;字段上加上@TableLogic注解,表示删除是逻辑删除,由mybatis-plus提供的;
1 2 3 @TableLogic private Integer isDeleted;
在DishService中添加相关的方法并在Impl中实现方法
1 public void deleteByIds (List<Long> ids) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Override @Transactional public void deleteByIds (List<Long> ids) { LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.in(ids != null , Dish::getId,ids); List<Dish> list = this .list(queryWrapper); for (Dish dish : list){ Integer status = dish.getStatus(); if (status == 0 ){ this .removeById(dish.getId()); }else { throw new CustomException ("删除菜品中有菜品未下架,删除失败" ); } } }
再在DishController中实现delete方法,调用deleteById方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @DeleteMapping public R<String> delete (@RequestParam List<Long> ids) { dishService.deleteByIds(ids); LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.in(DishFlavor::getDishId,ids); dishFlavorService.remove(queryWrapper); return R.success("菜品删除成功" ); }
7.2 代码优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 @DeleteMapping public R<String> delete (@RequestParam("ids") List<Long> ids) { LambdaQueryWrapper<SetmealDish> setmealDishLambdaQueryWrapper = new LambdaQueryWrapper <>(); setmealDishLambdaQueryWrapper.in(SetmealDish::getDishId,ids); List<SetmealDish> SetmealDishList = setmealDishService.list(setmealDishLambdaQueryWrapper); if (SetmealDishList.size() == 0 ){ dishService.deleteByIds(ids); LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.in(DishFlavor::getDishId,ids); dishFlavorService.remove(queryWrapper); return R.success("菜品删除成功" ); } ArrayList<Long> Setmeal_idList = new ArrayList <>(); for (SetmealDish setmealDish : SetmealDishList) { Long setmealId = setmealDish.getSetmealId(); Setmeal_idList.add(setmealId); } LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper <>(); setmealLambdaQueryWrapper.in(Setmeal::getId,Setmeal_idList); List<Setmeal> setmealList = setmealService.list(setmealLambdaQueryWrapper); for (Setmeal setmeal : setmealList) { Integer status = setmeal.getStatus(); if (status == 1 ){ return R.error("删除的菜品中有关联在售套餐,删除失败!" ); } } dishService.deleteByIds(ids); LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.in(DishFlavor::getDishId,ids); dishFlavorService.remove(queryWrapper); return R.success("菜品删除成功" ); }
八、套餐管理–setmeal&setmeal_dish 1、数据模型
2、新增套餐 2.1 处理业务逻辑 1. 页面(backend/page/combo/add.html)发送ajax请求,请求服务端**获取套餐分类**数据并展示到下拉框中
2. 页面发送ajax请求,请求服务端获取**菜品分类**数据并展示到添加菜品窗口中
3. 页面发送ajax请求,请求服务器,根据菜品分类查询对应的**菜品**数据并展示到添加菜品窗口中
4. 页面发送请求进行**图片上传**,请求服务端将图片保存到服务器
5. 页面发送请求进行**图片下载**,将上传的图像进行回显
6. 点击保存按钮,发送ajax请求,将**套餐**相关数据以json形式提交到服务端
第一个交互前面已经完成:分类管理通过type的值来控制在前端展示的是 菜品分类(type=1) 或者是 套餐分类(type=2);
第二个交互在categorycontroller里面的list方法;
第四和第五个交互也已经实现,则还剩第三和第六个交互需要处理。
2.2 编码处理 2.2.1 创建类和接口的基本结构 创建setmealDishMapper:
1 2 3 4 5 6 7 8 9 10 package com.itreggie.reggie.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.itreggie.reggie.entity.SetmealDish;import org.apache.ibatis.annotations.Mapper;@Mapper public interface SetmealDishMapper extends BaseMapper <SetmealDish> {}
创建setmealDishService:
1 2 3 4 5 6 7 8 package com.itreggie.reggie.service;import com.baomidou.mybatisplus.extension.service.IService;import com.itreggie.reggie.entity.SetmealDish;public interface SetmealDishService extends IService <SetmealDish> {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.itreggie.reggie.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.itreggie.reggie.entity.SetmealDish;import com.itreggie.reggie.mapper.SetmealDishMapper;import com.itreggie.reggie.service.SetmealDishService;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Service;@Service @Slf4j public class SetmealDishServiceImpl extends ServiceImpl <SetmealDishMapper, SetmealDish> implements SetmealDishService {}
2.2.2 添加菜品数据回显(交互三)–DishController 由前端请求的地址可知菜品分类通过categoryID查询
在DishController中实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @GetMapping("/list") public R<List<Dish>> list (Dish dish) { LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(dish.getCategoryId() != null ,Dish::getCategoryId,dish.getCategoryId()); queryWrapper.eq(Dish::getStatus,1 ); queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime); List<Dish> list = dishService.list(queryWrapper); return R.success(list); }
控制台输出的sql语句:
1 SELECT id,name,category_id,price,code,image,description,status,sort,create_time,update_time,create_user,update_user,is_deleted FROM dish WHERE (category_id = ? AND status = ?) ORDER BY sort ASC,update_time DESC
2.2.3 保存添加套餐(交互六)–SetmealController 根据前端传过来的数据我们可以在后端确定我们需要在后端使用什么来接受前端的参数;
创建SetmealController控制类,上面的dishList,我们数据库并不需要这个数据,所以接收数据的实体类没有dishList这个属性也没有关系,前端传过来的数据都是自动映射到接收数据的实体类的属性上的,没有对应起来就不会映射。
在SetmealController控制类中实现save方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package com.itreggie.reggie.controller;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;import com.itreggie.reggie.common.R;import com.itreggie.reggie.dto.SetmealDto;import com.itreggie.reggie.entity.Category;import com.itreggie.reggie.entity.Setmeal;import com.itreggie.reggie.entity.SetmealDish;import com.itreggie.reggie.service.CategoryService;import com.itreggie.reggie.service.SetmealDishService;import com.itreggie.reggie.service.SetmealService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.BeanUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import java.util.List;import java.util.stream.Collectors;@RestController @RequestMapping("/setmeal") @Slf4j public class SetmealController { @Autowired private SetmealService setmealService; @Autowired private SetmealDishService setmealDishService; @Autowired private CategoryService categoryService; @PostMapping public R<String> save (@RequestBody SetmealDto setmealDto) { log.info("套餐信息:{}" ,setmealDto); setmealService.saveWithDish(setmealDto); return R.success("新增套餐成功" ); } }
在SetmealService中添加自定义的方法并在Impl中实现:
1 2 3 4 5 public void saveWithDish (SetmealDto setmealDto) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Autowired SetmealDishService setmealDishService; @Transactional @Override public void saveWithDish (SetmealDto setmealDto) { this .save(setmealDto); log.info(setmealDto.toString()); List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes(); setmealDishes.stream().map((item)->{ item.setSetmealId(setmealDto.getId()); return item; }).collect(Collectors.toList()); setmealDishService.saveBatch(setmealDishes); }
3、套餐信息分页查询 3.1 处理业务逻辑 1. 页面(backend/page/combo/list.html)发送ajax请求,将分页查询参数(page,pageSize,name)提交到服务端,获取分页数据
1. 页面发送请求,请求服务端进行图片下载,用于页面图片展示
3.2 编码处理 在SetmealController中实现page方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 @GetMapping("/page") public R<Page> page (int page, int pageSize, String name) { Page<Setmeal> pageInfo = new Page <>(page,pageSize); Page<SetmealDto> dtoPage = new Page <>(page,pageSize); LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper <>(); 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); Long categoryId = item.getCategoryId(); 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); }
4、套餐的启售和停售(批量/单个) 根据前面菜品模块自己实现的功能 ,我们可以知道,我们只需要写一个批量处理的方法就可以完成单个或者是批量套餐的启售,停售;
4.1 编码处理 4.1.1 SetmealService中添加updateSetmealStatusByIds方法并在Impl中实现 1 2 3 4 5 6 void updateSetmealStatusById (Integer status,List<Long> ids) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override public void updateSetmealStatusById (Integer status, List<Long> ids) { LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper (); queryWrapper.in(ids !=null ,Setmeal::getId,ids); List<Setmeal> list = this .list(queryWrapper); for (Setmeal setmeal : list) { if (setmeal != null ){ setmeal.setStatus(status); this .updateById(setmeal); } } }
4.1.2 SetmealController中实现status代码 1 2 3 4 5 6 7 8 9 10 @PostMapping("/status/{status}") public R<String> status (@PathVariable("status") Integer status,@RequestParam List<Long> ids) { setmealService.updateSetmealStatusById(status,ids); return R.success("售卖状态修改成功" ); }
5、套餐信息修改 5.1 处理业务逻辑 1. 将页面的数据传到后端,在数据库中进行修改
1. 回显数据到页面
5.2 编码处理 5.2.1 setmealService添加getDate方法并在Impl中实现 1 2 3 4 5 SetmealDto getDate (Long id) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Override public SetmealDto getDate (Long id) { Setmeal setmeal = this .getById(id); SetmealDto setmealDto = new SetmealDto (); LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper (); queryWrapper.eq(id!=null ,SetmealDish::getSetmealId,id); if (setmeal != null ){ BeanUtils.copyProperties(setmeal,setmealDto); List<SetmealDish> list = setmealDishService.list(queryWrapper); setmealDto.setSetmealDishes(list); return setmealDto; } return null ; }
5.2.2 SetmealController 中添加getDate方法实现数据回显 1 2 3 4 5 6 7 8 9 10 @GetMapping("/{id}") public R<SetmealDto> getData (@PathVariable Long id) { SetmealDto setmealDto = setmealService.getDate(id); return R.success(setmealDto); }
5.2.3 完善已选菜品中的菜品并没有展示对应的菜品名 丢失问题 修改具体的前端代码:把backend/page/combo/add.html中的335行修改为下面的代码;
出现菜品名丢失的原因是这里的item是代表dish对象,dish实体类是使用name作为菜品名称的
5.2.4 保存数据返回套餐页面 根据前端传过来的数据和需要的返回值,我们就可以知道controller层方法的返回值和用什么参数来接收前端传给我们的数据;注意这个套餐里面的菜品也要保存修改:需要把setealDish保存到seteal_dish表中;
为了不把问题复杂化,直接把相关的setmealDish内容移除然后再重新添加,这样就可以不用考虑dish重复的问题和存在修改遗漏的问题。
在setmealController中实现方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @PutMapping public R<String> edit (@RequestBody SetmealDto setmealDto) { if (setmealDto==null ){ return R.error("请求异常" ); } if (setmealDto.getSetmealDishes()==null ){ return R.error("套餐没有菜品,请添加套餐" ); } List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes(); Long setmealId = setmealDto.getId(); LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(SetmealDish::getSetmealId,setmealId); setmealDishService.remove(queryWrapper); for (SetmealDish setmealDish : setmealDishes) { setmealDish.setSetmealId(setmealId); } setmealDishService.saveBatch(setmealDishes); setmealService.updateById(setmealDto); return R.success("套餐修改成功" ); }
6、删除套餐 6.1 处理业务逻辑
6.2 编码处理 注意:对于正在售卖中的套餐不能删除,需要先停售再删除。
6.2.1 在setmealService中添加removeWithDish方法并在Impl中实现 1 2 3 4 5 public void removeWithDish (List<Long> ids) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Transactional public void removeWithDish (List<Long> ids) { 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 ("套餐正在售卖中,删除失败" ); } this .removeByIds(ids); LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper <>(); lambdaQueryWrapper.in(SetmealDish::getSetmealId,ids); setmealDishService.remove(lambdaQueryWrapper); }
6.2 2 setmealController层调用removeWithDish方法 1 2 3 4 5 6 7 8 9 10 11 12 @DeleteMapping public R<String> delete (@RequestParam List<Long> ids) { log.info("ids: {}" ,ids); setmealService.removeWithDish(ids); return R.success("套餐数据删除成功" ); }
九、后台订单管理 1、按条件查看和展示客户订单 实现移动端下单功能后,后台应实现客户订单的数据展示
1.1 编码处理 在orderController中实现page方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @GetMapping("/page") public R<Page> page (int page, int pageSize, String number,String beginTime,String endTime) { Page<Orders> pageInfo = new Page <>(page,pageSize); LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.like(number!=null ,Orders::getNumber,number) .gt(StringUtils.isNotEmpty(beginTime),Orders::getOrderTime,beginTime) .lt(StringUtils.isNotEmpty(endTime),Orders::getOrderTime,endTime); orderService.page(pageInfo,queryWrapper); return R.success(pageInfo); }
1.2 性能完善 经过功能测试,可以看出展示页面的用户名无法展示,原因是用户名此项的前端代码绑定的是orders表中的user_name列,但用户注册时user_name并未填写,因此查询出来的用户名全为null。
手动修改数据库具体的值负担较大,所以这里使用用户下单的数据库中也有consignee来显示,但是数据库中的consignee是可以为null的,所以在后台代码中帮订单添加该属性的时候要判断是否null!然后就是去修改前端代码就行
在backend/page/order/list.html中,修改第72行的userName改为consignee即可
2、后台订单状态修改 后台订单明细中点击派送按钮:前端会发送下面的请求来:是json格式的数据;
2.1 编码处理 在orderController中实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @PutMapping public R<String> orderStatusChange (@RequestBody Map<String,String> map) { String id = map.get("id" ); Long orderId = Long.parseLong(id); Integer status = Integer.parseInt(map.get("status" )); if (orderId == null || status==null ){ return R.error("传入信息不合法" ); } Orders orders = orderService.getById(orderId); orders.setStatus(status); orderService.updateById(orders); return R.success("订单状态修改成功" ); }