1、需求分析
产品原型、需求规格说明书
2、设计
产品文档、UI界面设计、概要设计、详细设计、数据库设计
3、编码
项目代码、单元测试
4、测试
测试用例、设施报告
5、上线运维
软件环境安装、配置
项目介绍 本项目〈(瑞吉外卖)是专门为餐饮企业(餐厅、饭店)定制的一款软件产品,包括系统管理后台和移动端应用两部分。其中系统管理后台主要提供给餐饮企业内部员工使用,可以对餐厅的菜品、套餐、订单等进行管理维护。移动端应用主要提供给消费者使用,可以在线浏览菜品、添加购物车、下单等。
本项目共分为3期进行开发:
产品原型
就是一款产品成型之前的一个简单的框架,就是将页面的排版布局展现出来,使产品的初步构思有一个可视化的展示。通过原型展示,可以更加直观的了解项目的需求和提供的功能。
导入pom
xxxxxxxxxx931 2<project xmlns="http://maven.apache.org/POM/4.0.0"3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">5 <modelVersion>4.0.0</modelVersion>6
7 <parent>8 <groupId>org.springframework.boot</groupId>9 <artifactId>spring-boot-starter-parent</artifactId>10 <version>2.4.5</version>11 <relativePath/> <!-- lookup parent from repository -->12 </parent>13
14 <properties>15 <java.version>1.8</java.version>16 </properties>17
18 <groupId>com.os467</groupId>19 <artifactId>reggie_take_out</artifactId>20 <version>1.0-SNAPSHOT</version>21
22 <dependencies>23
24 <dependency>25 <groupId>org.springframework.boot</groupId>26 <artifactId>spring-boot-starter</artifactId>27 </dependency>28
29 <dependency>30 <groupId>org.springframework.boot</groupId>31 <artifactId>spring-boot-starter-test</artifactId>32 <scope>test</scope>33 </dependency>34
35 <dependency>36 <groupId>org.springframework.boot</groupId>37 <artifactId>spring-boot-starter-web</artifactId>38 <scope>compile</scope>39 </dependency>40
41 <dependency>42 <groupId>com.baomidou</groupId>43 <artifactId>mybatis-plus-boot-starter</artifactId>44 <version>3.4.2</version>45 </dependency>46
47 <dependency>48 <groupId>org.projectlombok</groupId>49 <artifactId>lombok</artifactId>50 <version>1.18.20</version>51 </dependency>52
53 <dependency>54 <groupId>com.alibaba</groupId>55 <artifactId>fastjson</artifactId>56 <version>1.2.76</version>57 </dependency>58
59 <dependency>60 <groupId>commons-lang</groupId>61 <artifactId>commons-lang</artifactId>62 <version>2.6</version>63 </dependency>64
65 <dependency>66 <groupId>mysql</groupId>67 <artifactId>mysql-connector-java</artifactId>68 <scope>runtime</scope>69 </dependency>70
71 <dependency>72 <groupId>com.alibaba</groupId>73 <artifactId>druid-spring-boot-starter</artifactId>74 <version>1.1.23</version>75 </dependency>76
77 </dependencies>78
79
80 <build>81 <plugins>82 <plugin>83 <groupId>org.springframework.boot</groupId>84 <artifactId>spring-boot-maven-plugin</artifactId>85 <version>2.3.5.RELEASE</version>86 </plugin>87 </plugins>88 </build>89
90
91
92
93</project>
配置springboot启动类
xxxxxxxxxx151package com.os467.reggie;2
3import org.springframework.boot.SpringApplication;4import org.springframework.boot.autoconfigure.SpringBootApplication;5
67public class ReggieApplication {8
9 public static void main(String[] args) {10
11 SpringApplication.run(ReggieApplication.class,args);12
13 }14
15}
Simple Logging Facade for Java(SLF4J)作为各种日志框架(例如Java.util.Logging、logback、log4j)的简单外观或抽象
首先slf4j可以理解为规则的制定者,是一个抽象层,定义了日志相关的接口。log4j,logback,JDK Logging都是slf4j的实现层,只是出处不同,当然使用起来也就各有千秋
为什么加了@slf4j注解 就可以直接使用log了呢?
如果不使用注解,那我们使用log,需要这样定义
xxxxxxxxxx11static Logger logger = LoggerFactory.getLogger("Update的日志")其实在编译完成生成字节码文件的时候idea就已经帮我们生成了这段代码
这就得益于lombok插件了
其实这就是因为Lombok项目是一种自动接通你的编辑器和构建工具的一个Java库
可以理解为有了@slf4j,编辑器里就会给你加log的智能提示,但这并不代表log对象已生成
Lombok项目是一个java库,它可以自动插入到编辑器和构建工具中,增强java的性能,不需要再写getter、setter或equals方法,只要有一个注解,你的类就有一个功能齐全的构建器、自动记录变量等等
@Data
整合了Getter、Setter、ToString、EqualsAndHashCode、RequiredArgsConstructor注解
@Getter
快速构建Getter方法
@Setter
快速构建Setter方法
@ToString
快速将当前对象转换成字符串类型,便于log
@EqualsAndHashCode
快速进行相等判断
@NonNull
判断变量(对象)是否为空
https://blog.csdn.net/weixin_45433031/article/details/121846207
方法1 实现WebMvcConfigurer接口
WebMvcConfigurer 接口其实是Spring内部的一种配置方式,我们需要去实现这个接口
(接口中的default关键字规定了接口方法有方法体并且能被实现类继承和重写)
xxxxxxxxxx271package com.os467.reggie.config;2
3import lombok.extern.slf4j.Slf4j;4import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;5import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;6
78
9public class WebMvcConfig implements WebMvcConfigurer {10
11 /**12 * 设置静态资源映射13 * @param registry14 */15 16 public void addResourceHandlers(ResourceHandlerRegistry registry) {17
18 log.info("开始进行静态资源映射");19
20 //将前端拦截到的资源路径映射到本地服务器静态资源21 registry.addResourceHandler("/backend/**").addResourceLocations("classpath:backend/");22
23 registry.addResourceHandler("/front/**").addResourceLocations("classpath:front/");24 }25
26 27}
方法2 继承WebMvcConfigurationSupport类
由于服务器无法访问后端的backend和front资源,我们需要配置一个WebMvcConfig配置类,我们需要让这个类去继承WebMvcConfigurationSupport这个类
这个类为我们提供了一个资源拦截器方法
我们想自定义静态资源映射目录的话,只需重写addResourceHandlers方法即可
SpringBoot拦截器addResourceHandlers()
addResourceLocations() 需要映射到的后端资源地址
xxxxxxxxxx281package com.os467.reggie.config;2
3import lombok.extern.slf4j.Slf4j;4import org.springframework.context.annotation.Configuration;5import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;6import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;7
8910public class WebMvcConfig extends WebMvcConfigurationSupport {11
12 /**13 * 设置静态资源映射14 * @param registry15 */16 17 protected void addResourceHandlers(ResourceHandlerRegistry registry) {18
19 log.info("开始进行静态资源映射");20
21 //将前端拦截到的资源路径映射到本地服务器静态资源22 registry.addResourceHandler("/backend/**").addResourceLocations("classpath:backend/");23
24 registry.addResourceHandler("/front/**").addResourceLocations("classpath:front/");25 26 }27}28
我们在持久层接口上加Mapper标签
让EmployeeMapper去继承MyBatisPlus提供的BaseMapper接口,需要提供一个实体类泛型
xxxxxxxxxx121package com.os467.reggie.mapper;2
3import com.baomidou.mybatisplus.core.mapper.BaseMapper;4import com.os467.reggie.entity.Employee;5import org.apache.ibatis.annotations.Mapper;6
78public interface EmployeeMapper extends BaseMapper<Employee> {9
10
11
12}
表现层
@RequestBody注解 使得接收到的值指定为json串,必须是post方式提交
@PostMapping(“/login”)注解
等价于@RequestMapping(value = “/user/login”,method = RequestMethod.POST)
xxxxxxxxxx371package com.os467.reggie.controller;2
3import com.os467.reggie.common.R;4import com.os467.reggie.entity.Employee;5import com.os467.reggie.service.EmployeeService;6import lombok.extern.slf4j.Slf4j;7import org.springframework.beans.factory.annotation.Autowired;8import org.springframework.web.bind.annotation.PostMapping;9import org.springframework.web.bind.annotation.RequestBody;10import org.springframework.web.bind.annotation.RequestMapping;11import org.springframework.web.bind.annotation.RestController;12
13import javax.servlet.http.HttpServletRequest;14
151617("/employee")18public class EmployeeController {19
20 //注入业务层实例21 22 private EmployeeService employeeService;23
24 /**25 * 员工登入26 * @param request27 * @param employee @RequestBody 注解使得接收到的值指定为json串28 * @return29 */30 ("/login")31 public R<Employee> login(HttpServletRequest request, Employee employee){32
33
34 return null;35 }36
37}
本次工程内封装的返回结果类,用于给前端响应数据
xxxxxxxxxx411package com.os467.reggie.common;2
3import lombok.Data;4import java.util.HashMap;5import java.util.Map;6
7/**8 * 通用返回结果类,服务端响应的数据最终都会封装成此对象9 * @param <T>10 */1112public class R<T> {13
14 private Integer code; //编码:1成功,0和其它数字为失败15
16 private String msg; //错误信息17
18 private T data; //数据19
20 private Map map = new HashMap(); //动态数据21
22 public static <T> R<T> success(T object) {23 R<T> r = new R<T>();24 r.data = object;25 r.code = 1;26 return r;27 }28
29 public static <T> R<T> error(String msg) {30 R r = new R();31 r.msg = msg;32 r.code = 0;33 return r;34 }35
36 public R<T> add(String key, Object value) {37 this.map.put(key, value);38 return this;39 }40
41}
需要对前端传来的用户密码进行md5加密
在后端查询是否有此用户名(用户名在数据库中设置了不能重)
进行密码校验
需要将用户数据存入到session域中
xxxxxxxxxx501/**2 * 员工登入3 * @param request4 * @param employee @RequestBody 注解使得接收到的值指定为json串5 * @return6 */7("/login")8public R<Employee> login(HttpServletRequest request, Employee employee){9
10 //1、将页面提交的密码password进行md5加密处理11 String password = employee.getPassword();12
13 password = DigestUtils.md5DigestAsHex(password.getBytes());14
15 //2、根据页面提交的用户名username查询数据库16 String username = employee.getUsername();17
18 LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();19
20 queryWrapper.eq(Employee::getUsername, username);21
22 Employee anEmployee = employeeService.getOne(queryWrapper);23
24 //3、如果没有查询到则返回登录失败结果25 if (anEmployee == null){26
27 return R.error("登录失败");28
29 }30
31 //4、密码比对,如果不一致则返回登陆失败结果32 if (!anEmployee.getPassword().equals(password)){33
34 return R.error("登录失败");35
36 }37
38 //5、查看员工状态,如果已禁用状态,则返回员工已禁用结果39 if (anEmployee.getStatus() == 0){40
41 return R.error("账号已禁用");42
43 }44
45 //6、登录成功,将员工id存入Session域中并返回登入成功结果46
47 request.getSession().setAttribute("employee",employee.getId());48
49 return R.success(anEmployee);50}
xxxxxxxxxx141/**2 * 员工注销方法3 * 1、清理Session中的用户id4 * 2、返回结果5 * @return6 */7("/logout")8public R<String> logout(HttpServletRequest request){9
10 //清除Session域中保存的当前登录员工的id11 request.getSession().removeAttribute("employee");12
13 return R.success("退出成功");14}
如果用户不登陆,直接访问系统页面,照样可以正常访问
这种设计并不合理,我们希望看到的效果应该是,只有登录成功后才可以访问系统中的页面,如果没有登录则跳转登录页面
具体如何实现?
使用过滤器或者拦截器,在过滤器或拦截器中判断用户是否已经无参登录,如果没有登录则跳转登录页面
步骤
1、创建自定义过滤器LoginCheckFilter
2、在启动类上加入注解@ServletCompontentScan
3、完善过滤器的处理逻辑
创建过滤器
首先在springboot启动类上加@ServletComponentScan注解
在servlet3.0以后,我们可以不用在web.xml文件内配置Filter,只需要加上@WebFilter注解就可以实现,以达到简化配置的目的
该注解用来声明servlet过滤器,将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器
filterName属性
指定过滤器的名字
urlPatterns属性
指定一组过滤器的URL匹配模式
xxxxxxxxxx331package com.os467.reggie.Filter;2
3import lombok.extern.slf4j.Slf4j;4
5import javax.servlet.*;6import javax.servlet.annotation.WebFilter;7import javax.servlet.http.HttpServletRequest;8import javax.servlet.http.HttpServletResponse;9import java.io.IOException;10
11/**12 * 检查用户是否完成登录13 */1415(filterName = "loginCheckFilter",urlPatterns = "/*")16public class LoginCheckFilter implements Filter {17
18 19 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {20
21 //需要先向下转型22 HttpServletRequest request = (HttpServletRequest) servletRequest;23
24 HttpServletResponse response = (HttpServletResponse) servletResponse;25
26 //日志27 log.info("拦截到请求:{}",request.getRequestURI());28
29 filterChain.doFilter(request,response);30
31 }32
33}
过滤器具体的处理逻辑如下:
1、获取本次请求的URI
2、判断本次请求是否需要处理
3、如果不需要处理,则直接放行
4、判断登录状态,如果已登录,则直接放行
5、如果未登录则返回未登录结果
chain.doFilter(request, response)这个方法,如果还有别的过滤器,那么将处理好的请求传给下个过滤器,依此类推
AntPathMatcher: 路径匹配器,支持通配符
xxxxxxxxxx831package com.os467.reggie.Filter;2
3import com.alibaba.fastjson.JSON;4import com.os467.reggie.common.R;5import lombok.extern.slf4j.Slf4j;6import org.springframework.util.AntPathMatcher;7
8import javax.servlet.*;9import javax.servlet.annotation.WebFilter;10import javax.servlet.http.HttpServletRequest;11import javax.servlet.http.HttpServletResponse;12import java.io.IOException;13
14/**15 * 检查用户是否完成登录16 */1718(filterName = "loginCheckFilter",urlPatterns = "/*")19public class LoginCheckFilter implements Filter {20
21 //路径匹配器,支持通配符22 private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();23
24 25 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {26
27 //需要先向下转型28 HttpServletRequest request = (HttpServletRequest) servletRequest;29
30 HttpServletResponse response = (HttpServletResponse) servletResponse;31
32 //1、获取本次请求的URI33 String requestURI = request.getRequestURI();34
35 //定义不要被处理的请求路径36 String[] urls = new String[]{37 "/employee/login",38 "/employee/logout",39 "backend/**",40 "/front/**"41 };42
43 //2、判断本次请求是否需要处理44 boolean check = check(urls, requestURI);45
46 //3、如果不需要处理,则直接放行47 if (check){48 filterChain.doFilter(request,response);49 return;50 }51
52 //4、判断登录状态,如果已登录,则直接放行53 if(request.getSession().getAttribute("employee") != null){54 filterChain.doFilter(request,response);55 return;56 }57
58 //5、如果未登录则返回未登录结果,通过输出流的方式向客户端来响应数据59 response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));60 return;61 62 }63
64 /**65 * 路径匹配,检查本次请求是否需要放行66 * @param urls67 * @param requestURI68 * @return69 */70 public boolean check(String[] urls,String requestURI){71
72 for (String url : urls) {73
74 boolean match = PATH_MATCHER.match(url, requestURI);75 if (match){76 return true;77 }78 }79
80 return false;81 }82
83}
为数据库新增员工
xxxxxxxxxx261/**2 * 新增员工3 * @param employee4 * @return5 */67public R<String> save(HttpServletRequest request, Employee employee){8 log.info("新增员工,员工信息:{}",employee.toString());9
10 //设置初始密码123456,并且md5加密11 employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));12
13 //设置创建和更新时间14 employee.setCreateTime(LocalDateTime.now());15 employee.setUpdateTime(LocalDateTime.now());16
17 Long empId = (Long)request.getSession().getAttribute("employee");18
19 //设置账户的创建更新人id20 employee.setCreateUser(empId);21 employee.setUpdateUser(empId);22
23 employeeService.save(employee);24
25 return R.success("新增员工成功");26}
前面的程序还存在一个问题,就是当我们在新增员工时输入的账号已经存在,由于employee表中对该字段加入了唯一约束,此时程序会抛出异常
此时需要我们的程序进行异常捕获,通常由两种处理方式:
1、在Controller方法中加入try、catch进行异常捕获(不推荐)
2、使用异常处理器进行全局异常捕获
第二种方法其实就是通过AOP底层动态代理来对目标方法的增强(异常通知)
annotations属性: 被其中存放的注解修饰的方法会被拦截,传入注解字节码数组
@ExceptionHandler注解
传入异常类的字节码,修饰方法(异常通知的内容),指定异常的处理类型
xxxxxxxxxx391package com.os467.reggie.common;2
3import lombok.extern.slf4j.Slf4j;4import org.springframework.web.bind.annotation.ControllerAdvice;5import org.springframework.web.bind.annotation.ExceptionHandler;6import org.springframework.web.bind.annotation.ResponseBody;7import org.springframework.web.bind.annotation.RestController;8
9import java.sql.SQLIntegrityConstraintViolationException;10
11/**12 * 全局异常捕获13 */14(annotations = {RestController.class})151617public class GlobalExceptionHandler {18
19 /**20 * 异常处理方法21 * @return22 */23 (SQLIntegrityConstraintViolationException.class)24 public R<String> exceptionHandler(SQLIntegrityConstraintViolationException exception){25
26 log.error(exception.getMessage());27
28 //根据异常提示定位到具体异常处理29 if (exception.getMessage().contains(("Duplicate entry"))){30
31 String msg = exception.getMessage().split(" ")[2] + "已存在";32
33 return R.error(msg);34 }35
36 return R.error("未知错误");37 }38}39
1、根据产品原型明确业务需求
2、重点分析数据的流转过程和数据格式
3、通过debug断点调试跟踪程序执行过程
系统中的员工很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据
代码开发
1、页面发送ajax请求,将分页查询参数(page,pageSize,name)提交到服务器
2、服务器Controller接收页面提交的数据并调用Service查询数据
3、Service调用Mapper操作数据库,查询分页数据
4、Controller将查询到的分页数据响应给页面
5、页面接收到分页数据并通过ElementUI的Table组件展示到页面上
xxxxxxxxxx291/**2 * 员工信息的分页查询3 * @param page4 * @param pageSize5 * @param name6 * @return7 */8("/page")9public R<Page> page(int page,int pageSize,String name){10
11 log.info("page = {},pageSize={},name={}",page,pageSize,name);12
13 //构造分页构造器14 Page pageConstructor = new Page(page,pageSize);15
16 //构造条件构造器,需要提供泛型17 LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper();18
19 //添加过滤条件20 queryWrapper.like(StringUtils.isNotBlank(name),Employee::getUsername,name);21
22 //排序条件23 queryWrapper.orderByDesc(Employee::getUpdateTime);24
25 //执行查询26 employeeService.page(pageConstructor,queryWrapper);27
28 return R.success(pageConstructor);29}
需求分析
在员工管理列表页面,可以对某个员工账号进行启用或者禁用操作,账号禁用的员工不能登录系统,启用后的员工可以正常登录
需要注意,只有管理员(admin用户)可以对其他普通用户进行启用、禁用操作,所以普通用户登录系统后启用、禁用按钮不显示
管理员admin登录系统可以对所有员工账号进行启用,禁用操作
如果某个员工账号状态为正常,则按钮显示“禁用”,如果员工账号状态为已禁用,则按钮显示为“启用”
普通员工登录系统后,启用、禁用按钮不显示
代码开发
启用、禁用员工账号,本质上就是一个更新操作,也就是对status状态字段进行操作,在Controller中创建update方法,此方法时一个通用的修改员工信息的方法
代码修复
通过观察控制台输出的SQL发现页面传递过来的员工id的值和数据库中的id值不一致,这是怎么回事?
如何解决这个问题?
我们可以在服务端给页面响应json数据时进行处理,将long型数据统一转为String字符串
方法一
使用@JsonSerialize注解
该注解用于属性或者getter⽅法上,⽤于在序列化时嵌⼊开发者⾃定义的代码
@JsonSerialize(using = ToStringSerializer.class)
使用以下包
xxxxxxxxxx21import com.fasterxml.jackson.databind.annotation.JsonSerialize;2import com.fasterxml.jackson.databind.ser.std.ToStringSerializerxxxxxxxxxx21(using = ToStringSerializer.class)2private Long id;
方法二
1、提供对象转换器JacksonObjectMapper,基于jackson进行java对象到json数据的转换
2、在WebMvcConfig配置类中扩展SpringMVC的消息转换器,在此消息转换器中使用提供的对象转换器进行Java对象到Json数据的转换
直接使用JacksonObjectMapper类
xxxxxxxxxx541package com.os467.reggie.common;2
3import com.fasterxml.jackson.databind.DeserializationFeature;4import com.fasterxml.jackson.databind.ObjectMapper;5import com.fasterxml.jackson.databind.module.SimpleModule;6import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;7import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;8import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;9import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;10import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;11import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;12import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;13import java.math.BigInteger;14import java.time.LocalDate;15import java.time.LocalDateTime;16import java.time.LocalTime;17import java.time.format.DateTimeFormatter;18import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;19
20/**21 * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象22 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]23 * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]24 */25public class JacksonObjectMapper extends ObjectMapper {26
27 public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";28 public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";29 public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";30
31 public JacksonObjectMapper() {32 super();33 //收到未知属性时不报异常34 this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);35
36 //反序列化时,属性不存在的兼容处理37 this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);38
39
40 SimpleModule simpleModule = new SimpleModule()41 .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))42 .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))43 .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))44
45 .addSerializer(BigInteger.class, ToStringSerializer.instance)46 .addSerializer(Long.class, ToStringSerializer.instance)47 .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))48 .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))49 .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));50
51 //注册功能模块 例如,可以添加自定义序列化器和反序列化器52 this.registerModule(simpleModule);53 }54}
我们需要去扩展mvc框架的消息转换器
在WebMvcConfig配置类中重写extendMessageConverters方法
添加自定义消息转换器不覆盖默认转换器
使用springboot中默认的Json消息转换器MappingJackson2HttpMessageConverter类创建转换器
这个类的主要实现逻辑是在AbstractJackson2HttpMessageConverter抽象类中实现的
这个列实现序列化与反序列化的最核心组件是ObjectMapper这个类
这些组件之间是如何协助,怎样才能合理的改写组件而实现自定义呢,这就需要了解起原理了
消息转换器创建和生效原理
xxxxxxxxxx181/**2 * 扩展mvc框架的消息转换器3 * @param converters4 */56public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {7
8 //创建新的消息转换器9 MappingJackson2CborHttpMessageConverter messageConverter = new MappingJackson2CborHttpMessageConverter();10
11 //设置对象转换器,底层使用Jackson将java对象转成json12 messageConverter.setObjectMapper(new JacksonObjectMapper());13
14 //将上面的消息转换器对象追加到mvc框架的转换器集合中,放在最前面优先使用15 converters.add(0,messageConverter);16
17 super.extendMessageConverters(converters);18}
通用修改员工数据方法
xxxxxxxxxx191/**2 * 根据id来修改员工信息3 * @param employee4 * @return5 */67public R<String> update(HttpServletRequest request, Employee employee){8
9 log.info(employee+"");10
11 Long empId = (Long) request.getSession().getAttribute("employee");12
13 employee.setUpdateUser(empId);14 employee.setUpdateTime(LocalDateTime.now());15
16 employeeService.updateById(employee);17
18 return R.success("修改成功");19}
在员工管理列表页面点击编辑按钮,跳转到编辑页面,在编辑页面回显员工信息并进行修改,最后点击保存按钮完成编辑操作
代码开发
1、点击编辑按钮时,页面跳转到add.html,并在url中携带参数[员工id]
2、在add.html页面获取url中的参数[员工id]
3、发送ajax请求,请求服务端,同时提交员工id参数
4、服务端接收请求,根据员工id查询员工信息,将员工信息以json形式响应给页面
5、页面接收服务端响应的json数据,通过VUE的数据绑定进行员工信息回显
6、点击保存按钮,发送ajax请求,将页面中的员工信息以json方式提交给服务端
7、服务端接收员工信息,并进行处理,完成后给页面响应
8、页面接收到服务端响应信息后进行相应处理
@PathVariable注解
通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中:URL 中的 {xxx} 占位符可以通过@PathVariable(“xxx“) 绑定到操作方法的入参中
xxxxxxxxxx91("/{id}")2public R<Employee> getById( Long id){3
4 log.info("根据id来查询员工信息");5
6 Employee employee = employeeService.getById(id);7
8 return R.success(employee);9}
前面我们已经完成了后台系统的员工管理功能开发,在新增员工时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工时需要设置修改时间和修改人等字段,这些字段属于公共字段,也就是很多表中都有这些字段
能不能对于这些公共字段在某个地方统一处理,来简化开发呢?
使用MybatisPlus提供的公共字段自动填充功能
也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码
实现步骤:
1、在实体类的属性上加入@TableField注解,指定自动填充的策略
2、按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,需要实现MetaObjectHandler接口
使用@TableField注解
@TableField(fill = FieldFill.INSERT)
指定填充策略
元数据对象处理器(填充策略的具体实现)
要实现一个MetaObjectHandler接口
xxxxxxxxxx461package com.os467.reggie.common;2
3import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;4import lombok.extern.slf4j.Slf4j;5import org.apache.ibatis.reflection.MetaObject;6import org.springframework.stereotype.Component;7
8import java.time.LocalDateTime;9
10/**11 * 元数据对象处理器12 */131415public class MyMetaObjectHandler implements MetaObjectHandler {16
17 /**18 * 插入操作自动填充19 * @param metaObject20 */21 22 public void insertFill(MetaObject metaObject) {23 log.info("公共字段自动填充[insert]...");24 log.info(metaObject+"");25
26 metaObject.setValue("createTime", LocalDateTime.now());27 metaObject.setValue("updateTime", LocalDateTime.now());28 metaObject.setValue("createUser", new Long(1));29 metaObject.setValue("updateUser", new Long(1));30 }31
32 /**33 * 更新操作自动填充34 * @param metaObject35 */36 37 public void updateFill(MetaObject metaObject) {38 log.info("公共字段自动填充[update]...");39 log.info(metaObject+"");40
41 metaObject.setValue("updateTime", LocalDateTime.now());42 metaObject.setValue("updateUser", new Long(1));43
44 }45}46
注意:当前我们设置createUser和updateUser为固定值,后面我们需要进行改造,改为动态获得当前登录用户的id
前面我们已经完成了公共字段自动填充功能的代码开发,但是还有一个问题没有解决
有的同学可能想到,用户登录成功后我们将用户id存入了HttpSession中,现在我从HttpSession中获取不就行了?
在学习ThreadLocal之前,我们需要先确认一个事情,就是客户端发送的每次http请求,对应的在服务端都会分配一个新的线程来处理,在处理过程中涉及到下面类中的方法都属于相同的一个线程:
1、LoginCheckFilter的doFilter方法
2、EmployeeController的update方法
3、MyMetaObjectHandler的updateFill方法
可以在上面的三个方法中分别加入下面代码(获取当前线程id):
log.info("线程id:{}",Thread.currentThread().getId());
什么是ThreadLocal?
ThreadLocal并不是一个线程,而是线程的局部变量,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,而不会影响其它线程所对应的副本,ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问
ThreadLocal常用方法:
public void set(T value) 设置当前线程的线程局部变量的值
public T get() 返回当前线程所对应的线程局部变量的值
我们可以在LoginCheckFilter的doFilter方法中获取当前登录用户id,并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值(用户id),然后在MyMetaObjectHandler的updateFill方法中调用ThreadLocal的get方法来获得当前线程所对应的线程局部变量的值(用户id)。
实现步骤
1、编写BaseContext工具类,基于ThreadLocal封装的工具类
2、在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
3、在MyMetaObjectHandler的方法中调用BaseContext获取登录用户的id
编写工具类
xxxxxxxxxx181package com.os467.reggie.common;2
3/**4 * 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id5 */6public class BaseContext {7
8 private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();9
10 public static void setCurrentId(Long id){11 threadLocal.set(id);12 }13
14 public static Long getCurrentId(){15 return threadLocal.get();16 }17
18}
在doFilter方法中获取id并且存储id值
xxxxxxxxxx111/4、判断登录状态,如果已登录,则直接放行2if(request.getSession().getAttribute("employee") != null){3
4 Long empId = (Long)request.getSession().getAttribute("employee");5
6 BaseContext.setCurrentId(empId);7
8 log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));9 filterChain.doFilter(request,response);10 return;11}
在MyMetaObjectHandler类中获取需要的id值
xxxxxxxxxx131/**2 * 更新操作自动填充3 * @param metaObject4 */56public void updateFill(MetaObject metaObject) {7 log.info("公共字段自动填充[update]...");8 log.info(metaObject+"");9
10 metaObject.setValue("updateTime", LocalDateTime.now());11 metaObject.setValue("updateUser", BaseContext.getCurrentId());12
13}
需求分析
后台系统中可以管理分类信息,分类包括两种类型,分别是菜品分类和套餐分类。当我们在后台系统中添加菜品时需要选择一个菜品分类,当我们在后台系统中添加一个套餐时需要选择一个套餐分类,在移动端也会按照菜品分类和套餐分类来展示对应的菜品和套餐。
xxxxxxxxxx141/**2 * 新增分类3 * @param category4 * @return5 */67public R<String> save( Category category){8
9 log.info("category:{}",category);10
11 categoryService.save(category);12
13 return R.success("新增分类成功");14}
分页列表
xxxxxxxxxx231/**2 * 分类分页3 * @param page4 * @param pageSize5 * @return6 */7("page")8public R<Page> page(int page,int pageSize){9
10 log.info("page = {},pageSize={},",page,pageSize);11
12 Page<Category> pageConstructor = new Page<>(page,pageSize);13
14 //条件构造器15 LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();16
17 //根据sort字段排序18 queryWrapper.orderByAsc(Category::getSort);19
20 categoryService.page(pageConstructor,queryWrapper);21
22 return R.success(pageConstructor);23}
删除分类
xxxxxxxxxx141/**2 * 删除分类3 * @param ids4 * @return5 */67public R<String> delete(Long ids){8
9 log.info("删除分类,id为{}",ids);10
11 categoryService.removeById(ids);12
13 return R.success("分类信息删除成功");14}
功能完善
前面我们已经实现了根据id删除分类的功能,但是并没有检查删除的分类是否关联了菜品或者套餐,所以我们需要进行功能完善
要完善分类删除功能,需要准备基础的类和接口:
实体类Dish和Setmeal
Mapper接口
Service实接口
Service实现类
根据条件删除分类,同时判断是否抛出异常
xxxxxxxxxx641package com.os467.reggie.service.impl;2
3import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;4import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;5import com.os467.reggie.common.CustomException;6import com.os467.reggie.entity.Category;7import com.os467.reggie.entity.Dish;8import com.os467.reggie.entity.Setmeal;9import com.os467.reggie.mapper.CategoryMapper;10import com.os467.reggie.service.CategoryService;11import com.os467.reggie.service.DishService;12import com.os467.reggie.service.SetmealService;13import org.springframework.beans.factory.annotation.Autowired;14import org.springframework.stereotype.Service;15
1617public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {18
19 20 private SetmealService setmealService;21
22 23 private DishService dishService;24
25 /**26 * 根据id来删除分类,删除之前进行判断27 * @param id28 */29 30 public void remove(Long id) {31
32 LambdaQueryWrapper<Dish> queryWrapperDish = new LambdaQueryWrapper<>();33 //添加查询条件,根据分类id进行查询34 queryWrapperDish.eq(Dish::getCategoryId, id);35
36 //查询当前分类是否关联了菜品37 int count1 = dishService.count(queryWrapperDish);38
39 if (count1 > 0){40
41 //已经关联了菜品,抛出一个业务异常42 throw new CustomException("当前分类下关联了菜品,不能删除");43
44 }45
46 //查询当前的分类是否关联了相应的套餐47 LambdaQueryWrapper<Setmeal> queryWrapperSetmeal = new LambdaQueryWrapper<>();48
49 queryWrapperSetmeal.eq(Setmeal::getCategoryId,id);50
51 int count2 = setmealService.count(queryWrapperSetmeal);52
53 if (count2 > 0){54
55 //已经关联了套餐,抛出一个业务异常56 throw new CustomException("当前分类下关联了套餐,不能删除");57
58 }59
60 //正常删除分类61 super.removeById(id);62
63 }64}
自定义的异常
xxxxxxxxxx111package com.os467.reggie.common;2
3public class CustomException extends RuntimeException {4
5 public CustomException(String msg){6
7 super(msg);8
9 }10
11}
在全局异常处理类中进行处理
xxxxxxxxxx111/**2 * 异常处理方法3 * @return4 */5(CustomException.class)6public R<String> exceptionHandler(CustomException exception){7
8 log.error(exception.getMessage());9
10 return R.error(exception.getMessage());11}
xxxxxxxxxx141/**2 * 根据id修改分类信息3 * @param category4 * @return5 */67public R<String> update( Category category){8
9 log.info("修改分类信息:{}",category);10
11 categoryService.updateById(category);12
13 return R.success("修改分类信息成功");14}
文件上传介绍
文件上传,也称为upload,是指将本地图片、视频、音频等文件上传到服务器上,可以供其它用户浏览或下载的过程,文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传功能
文件上传时,对页面的form表单有如下要求(固定要求):
method="post" 采用post方式提交数据enctype="multipart/form-data" 采用multipart格式上传文件type="file" 使用input的file控件上传
目前一些前端组件库也提供了相应的上传组件,但是底层原理还是基于form表单的文件上传
例如ElementUI中提供的upload上传组件
服务端要接收客户端页面上传的文件,通常都会使用Apache的两个组件:
Spring框架在spring-web包中对文件上传进行了封装,大大简化了服务端代码,我们只需要在Controller的方法中声明一个MultipartFile类型的参数即可接收上传的文件,例如:
文件下载介绍
文件下载,也称为download,是指将文件从服务器传输到本地计算机的过程
通过浏览器进行文件下载,通常有两种表现形式:
通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程
文件上传代码实现
MultipartFile类:用于接收文件类型数据
创建一个CommonController来处理平常一些的请求
测试是否能接收到文件
xxxxxxxxxx291package com.os467.reggie.controller;2
3import com.os467.reggie.common.R;4import lombok.extern.slf4j.Slf4j;5import org.springframework.web.bind.annotation.PostMapping;6import org.springframework.web.bind.annotation.RequestMapping;7import org.springframework.web.bind.annotation.RestController;8import org.springframework.web.multipart.MultipartFile;9
10/**11 * 文件的上传和下载12 */1314("/common")1516public class CommonController {17
18 /**19 * 文件上传20 * @param file 参数名必须和前端name一致21 * @return22 */23 ("/upload")24 public R<String> upload(MultipartFile file){25 log.info(file.toString());26 return null;27 }28
29}
在配置文件中指定文件存放路径
xxxxxxxxxx11reggie.iconPath=D:\\pythonschool\\PY_lab\\JavaProject\\reggie_take_out\\src\\main\\resources\\localIcon在表现层通过Value注解获取
xxxxxxxxxx21("${reggie.iconPath}")2private String basePath;
文件上传
xxxxxxxxxx391/**2 * 文件上传3 * @param file 参数名必须和前端name一致4 * @return5 */6("/upload")7public R<String> upload( MultipartFile file){8 //file是一个临时文件,需要转存到指定位置,否则本子请求完成后临时文件就会删除9 log.info(file.toString());10
11 //获取原始文件名12 String originalFilename = file.getOriginalFilename();13
14 String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));15
16 //使用UUID重新生成文件名称,防止文件名称重复造成文件覆盖17 String fileName = UUID.randomUUID().toString() + suffix;18
19 //创建一个目录对象20 File dir = new File(basePath);21
22 //判断当前目录是否存在23 if (!dir.exists()){24
25 dir.mkdirs();26
27 }28
29 try {30
31 //将临时文件转存到指定位置32 file.transferTo(new File(basePath+fileName));33
34 } catch (IOException e) {35 e.printStackTrace();36 }37
38 return R.success(fileName);39}
文件下载代码实现
getOutputStream()
该方法所获得的的字节流对象为ServletOutputStream类型, 由于ServletOutputStream是OutputStream的子类,它可以直接输出字节组中的二进制数据
xxxxxxxxxx561/**2 * 文件下载3 * @param name4 * @param response5 */6("/download")7public void download(String name, HttpServletResponse response){8
9 FileInputStream fileInputStream = null;10
11 ServletOutputStream outputStream = null;12
13 try {14
15 //输入流,通过输入流读取文件类容16 fileInputStream = new FileInputStream(new File(basePath+name));17
18 //输出流,通过输出流将文件写回浏览器,在浏览器展示图片19 outputStream = response.getOutputStream();20
21 byte[] bytes = new byte[1024];22
23 //设置响应文件类型24 response.setContentType("image/jpeg");25
26 int read = 0;27
28 while ((read = fileInputStream.read(bytes)) != -1){29
30 outputStream.write(bytes,0,read);31 outputStream.flush();32 }33
34
35 } catch (FileNotFoundException e) {36 e.printStackTrace();37 } catch (IOException e) {38 e.printStackTrace();39 }finally {40
41 try {42 if (fileInputStream != null){43 fileInputStream.close();44 }45
46 if (outputStream != null){47 outputStream.close();48 }49 } catch (IOException e) {50 e.printStackTrace();51 }52
53 }54
55
56}
新增菜品,其实就是将新增页面录入的菜品信息插入到dish表
如果添加了口味做法,还需要向dish_flavor表插入数据
所以在新增菜品时,涉及到两个表:
交互过程
1、页面(backend/page/food/add.html)发送ajax请求,请求服务端获取菜品分类数据并展示到下拉框中
2、页面发送请求进行图片上传,请求服务端将图片保存到服务器
3、页面发送请求进行图片下载,将上传的图片进行回显
4、点击保存按钮,发送ajax请求,将菜品相关数据以json形式提交到服务端
xxxxxxxxxx221/**2 * 根据条件来回显分类数据3 * @param category4 * @return5 */6("list")7public R<List<Category>> list(Category category){8
9 //条件构造器10 LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();11
12 //添加条件13 queryWrapper.eq(category.getType() != null,Category::getType,category.getType());14
15 //添加排序条件16 queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);17
18 //查询19 List<Category> list = categoryService.list(queryWrapper);20
21 return R.success(list);22}
导入DishDto,用于封装页面提交的数据
DTO,全称为Data Transfer Object,即数据传输对象,一般用于展示与服务层之间的数据传输
Stream流的特性
相比forEach效率高,数据多会启用多线程
Intermediate(中间操作):map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 skip、 parallel、 sequential、 unordered
Terminal(终止操作):forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、iterator
Short-circuiting(短路):anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
开启事务
需要在启动类上添加注解
xxxxxxxxxx11在新增菜品的方法上开启事务
xxxxxxxxxx11
xxxxxxxxxx141/**2 * 新增菜品3 * @param dishDto4 * @return5 */67public R<String> save( DishDto dishDto){8
9 log.info("dishDto:{}",dishDto);10
11 dishService.saveWithFlavor(dishDto);12
13 return R.success("新增菜品成功");14}
DishServiceImpl
xxxxxxxxxx251/**2 * 新增菜品,同时保存对应的口味数据3 * @param dishDto4 */567public void saveWithFlavor(DishDto dishDto) {8 //保存菜品的基本信息到菜品表dish9 this.save(dishDto);10
11 Long id = dishDto.getId();12
13 //菜品口味14 List<DishFlavor> flavors = dishDto.getFlavors();15
16 //使用stream流遍历17 flavors = flavors.stream().map((item) ->{18 item.setDishId(id);19 return item;20 }).collect(Collectors.toList());21
22 //保存菜品口味数据到菜品口味表23 dishFlavorService.saveBatch(flavors);24
25}
再查询菜品信息的同时要查询到菜品分类名
我们使用DishDto来构造分页,通过拷贝工具将查询好的菜品信息复制给DishDto模型
spring提供的BeansUtil有拷贝功能(底层是反射)
xxxxxxxxxx691/**2 * 菜品分页3 * @param page4 * @param pageSize5 * @return6 */7("page")8public R<Page> page(int page, int pageSize,String name){9
10 log.info("page = {},pageSize={},",page,pageSize);11
12 log.info("name:{}",name);13
14 //菜品分页构造器15 Page<Dish> dishPage = new Page<>(page,pageSize);16
17 //模型分页构造器18 Page<DishDto> dishDtoPage = new Page<>(page,pageSize);19
20 //条件构造器21 LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();22
23 //查询条件24 queryWrapper.like(StringUtils.isNotBlank(name),Dish::getName,name);25
26 //根据sort字段排序27 queryWrapper.orderByAsc(Dish::getSort);28
29 dishService.page(dishPage,queryWrapper);30
31 //对象拷贝32 BeanUtils.copyProperties(dishPage,dishDtoPage,"records");33
34 //获取菜品分页构造器内部查询到的菜品列表信息35 List<Dish> dishList = dishPage.getRecords();36
37 //创建模型列表38 List<DishDto> list = null;39
40 //遍历菜品数据流,修改成我们想要的泛型41 list = dishList.stream().map((item) -> {42
43 //创建模型对象44 DishDto dishDto = new DishDto();45
46 //将菜品对象信息拷贝到模型对象47 BeanUtils.copyProperties(item, dishDto);48
49 //获取到分类id50 Long categoryId = item.getCategoryId();51
52 //查询分类信息53 Category category = categoryService.getById(categoryId);54
55 //获取分类名56 String categoryName = category.getName();57
58 //为模型赋值分类名59 dishDto.setCategoryName(categoryName);60
61 return dishDto;62
63 }).collect(Collectors.toList());64
65 //为模型分页赋值66 dishDtoPage.setRecords(list);67
68 return R.success(dishDtoPage);69}
回显菜品信息以及对应口味信息
代码实现
xxxxxxxxxx121/**2 * 根据id来查询菜品信息和对应的口味信息3 * @param id4 * @return5 */6("/{id}")7public R<DishDto> page(("id") Long id){8
9 DishDto dishDto = dishService.getByIdWithFlavor(id);10
11 return R.success(dishDto);12}
xxxxxxxxxx251/**2 * 根据id来查询菜品信息和对应的口味信息3 * @param id4 * @return5 */67public DishDto getByIdWithFlavor(Long id) {8 //查询菜品基本信息9 Dish dish = this.getById(id);10
11 DishDto dishDto = new DishDto();12
13 BeanUtils.copyProperties(dish,dishDto);14
15 //查询菜品对应的口味信息16 LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();17
18 queryWrapper.eq(DishFlavor::getDishId,dish.getId());19
20 List<DishFlavor> flavors = dishFlavorService.list(queryWrapper);21
22 dishDto.setFlavors(flavors);23
24 return dishDto;25}
提交修改
xxxxxxxxxx141/**2 * 修改菜品信息3 * @return4 */56public R<String> update( DishDto dishDto){7
8 log.info("dishDto:{}",dishDto);9
10 dishService.updateWithFlavor(dishDto);11
12 return R.success("修改菜品成功");13
14}
xxxxxxxxxx301/**2 * 修改菜品信息并且更新对应口味信息3 * @param dishDto4 */567public void updateWithFlavor(DishDto dishDto) {8
9 //更新菜品表信息10 this.updateById(dishDto);11
12 //清理当前菜品对应的口味数据---dish_flavor表的delete操作13 LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper();14
15 queryWrapper.eq(DishFlavor::getDishId,dishDto.getId());16
17 dishFlavorService.remove(queryWrapper);18
19 List<DishFlavor> flavors = dishDto.getFlavors();20
21 //使用stream流遍历22 flavors = flavors.stream().map((item) ->{23 item.setDishId(dishDto.getId());24 return item;25 }).collect(Collectors.toList());26
27 //添加当前提交过来的口味数据28 dishFlavorService.saveBatch(flavors);29
30}
xxxxxxxxxx1661package com.os467.reggie.controller;2
3import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;4import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;5import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;6import com.baomidou.mybatisplus.core.toolkit.StringUtils;7import com.baomidou.mybatisplus.extension.plugins.pagination.Page;8import com.os467.reggie.common.R;9import com.os467.reggie.dto.SetmealDto;10import com.os467.reggie.entity.Category;11import com.os467.reggie.entity.Dish;12import com.os467.reggie.entity.Setmeal;13import com.os467.reggie.entity.SetmealDish;14import com.os467.reggie.service.CategoryService;15import com.os467.reggie.service.SetmealService;16import lombok.extern.slf4j.Slf4j;17import org.springframework.beans.BeanUtils;18import org.springframework.beans.factory.annotation.Autowired;19import org.springframework.web.bind.annotation.*;20
21import java.util.List;22import java.util.stream.Collectors;23
24
2526("/setmeal")2728public class SetmealController {29
30 31 private SetmealService setmealService;32
33 34 private CategoryService categoryService;35
36 /**37 * 获取套餐列表,并且显示套餐分类38 * @param page39 * @param pageSize40 * @param name41 * @return42 */43 ("/page")44 public R<Page<SetmealDto>> page(Integer page,Integer pageSize,String name){45
46 //创建分页构造器47 Page<Setmeal> setmealPage = new Page<>(page,pageSize);48
49 LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();50
51 //根据条件模糊查询52 queryWrapper.like(StringUtils.isNotBlank(name),Setmeal::getName,name);53
54 //分页查询55 setmealService.page(setmealPage,queryWrapper);56
57 //创建SetmealDto分页构造器58 Page<SetmealDto> setmealDtoPage = new Page<SetmealDto>();59
60 //拷贝分页构造器61 BeanUtils.copyProperties(setmealPage,setmealDtoPage,"records");62
63 //获取套餐记录64 List<Setmeal> setmealList = setmealPage.getRecords();65
66 //获取Dto格式的套餐信息67 List<SetmealDto> setmealDtoList = setmealList.stream().map((item) -> {68
69 SetmealDto setmealDto = new SetmealDto();70
71 //获取套餐id72 Long categoryId = item.getCategoryId();73
74 //获取该套餐信息75 Category category = categoryService.getById(categoryId);76
77 //拷贝套餐信息78 BeanUtils.copyProperties(item, setmealDto);79
80 //设置套餐名称81 setmealDto.setCategoryName(category.getName());82
83 return setmealDto;84 }).collect(Collectors.toList());85
86 //为分页构造器添加信息87 setmealDtoPage.setRecords(setmealDtoList);88
89 return R.success(setmealDtoPage);90 }91
92 /**93 * 添加套餐并且保存套餐对应菜品信息94 * @param setmealDto95 * @return96 */97 98 public R<String> save( SetmealDto setmealDto){99
100 //添加套餐101 setmealService.saveSetmealWithDish(setmealDto);102
103 return R.success("添加成功");104 }105
106 /**107 * 删除套餐,并且删除套餐对应的菜品108 * @param ids109 * @return110 */111 112 public R<String> delete(String ids){113
114 setmealService.deleteSetmealWithDish(ids);115
116 return R.success("删除成功");117 }118
119 ("/{setmealId}")120 public R<SetmealDto> getSetmeal(("setmealId") Long setmealId){121
122 SetmealDto setmealDto = setmealService.getSetmeal(setmealId);123
124 return R.success(setmealDto);125 }126
127 /**128 * 起售、停售菜品129 * @param flag130 * @param ids131 * @return132 */133 ("/status/{flag}")134 public R<String> stopSale(("flag") Integer flag,String ids){135
136 LambdaUpdateWrapper<Setmeal> updateWrapper = new LambdaUpdateWrapper<>();137
138 String[] idList = ids.split(",");139
140 updateWrapper.in(Setmeal::getId,idList);141
142 updateWrapper.set(Setmeal::getStatus,flag);143
144 setmealService.update(updateWrapper);145
146 return R.success("状态更新成功");147 }148
149
150 /**151 * 更新套餐数据152 * @param setmealDto153 * @return154 */155 156 public R<String> update( SetmealDto setmealDto){157
158 log.info("setmealDto:{}",setmealDto);159
160 setmealService.updateWithDish(setmealDto);161
162 return R.success("更新成功");163 }164
165}166
xxxxxxxxxx1591package com.os467.reggie.service.impl;2
3import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;4import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;5import com.os467.reggie.dto.SetmealDto;6import com.os467.reggie.entity.Dish;7import com.os467.reggie.entity.Setmeal;8import com.os467.reggie.entity.SetmealDish;9import com.os467.reggie.mapper.SetmealMapper;10import com.os467.reggie.service.CategoryService;11import com.os467.reggie.service.DishService;12import com.os467.reggie.service.SetmealDishService;13import com.os467.reggie.service.SetmealService;14import lombok.extern.slf4j.Slf4j;15import org.springframework.beans.BeanUtils;16import org.springframework.beans.factory.annotation.Autowired;17import org.springframework.stereotype.Service;18
19import java.util.List;20import java.util.stream.Collectors;21
2223public class SetmealServiceImpl extends ServiceImpl<SetmealMapper, Setmeal> implements SetmealService {24
25 26 private SetmealDishService setmealDishService;27
28 29 private CategoryService categoryService;30
31
32 /**33 * 设置套餐,并且保存套餐菜品信息34 * @param setmealDto35 */36 37 public void saveSetmealWithDish(SetmealDto setmealDto) {38
39 //获取套餐菜品关系列表40 List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();41
42 //保存套餐信息43 this.save(setmealDto);44
45 //获取套餐id46 Long setmealId = setmealDto.getId();47
48 //设置套餐id49 List<SetmealDish> setmealDishList = setmealDishes.stream().map((item) -> {50
51 item.setSetmealId(setmealId);52
53 return item;54
55 }).collect(Collectors.toList());56
57 //保存套餐菜品信息58 setmealDishService.saveBatch(setmealDishList);59
60 }61
62 /**63 * 删除套餐,以及对应的菜品64 * @param ids65 */66 67 public void deleteSetmealWithDish(String ids) {68
69 //获取需要删除的套餐id列表70 String[] idList = ids.split(",");71
72 LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();73
74 //需要删除id列表包含的菜套餐75 queryWrapper.in(Setmeal::getId,idList);76
77 //删除套餐78 this.remove(queryWrapper);79
80 //删除套餐对应的菜品81 LambdaQueryWrapper<SetmealDish> queryWrapperDish = new LambdaQueryWrapper<>();82
83 queryWrapperDish.in(SetmealDish::getSetmealId,idList);84
85 //删除套餐对应菜品86 setmealDishService.remove(queryWrapperDish);87
88 }89
90 /**91 * 回显套餐信息92 * @param setmealId93 */94 95 public SetmealDto getSetmeal(Long setmealId) {96
97 //根据id查询出套餐信息98 Setmeal setmeal = this.getById(setmealId);99
100 Long categoryId = setmeal.getCategoryId();101
102 //获取套餐分类名103 String categoryName = categoryService.getById(categoryId).getName();104
105 //创建Dto模型106 SetmealDto setmealDto = new SetmealDto();107
108 //拷贝套餐信息109 BeanUtils.copyProperties(setmeal,setmealDto);110
111 //获取套餐对应菜品列表112 LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>();113
114 //查询条件115 queryWrapper.eq(SetmealDish::getSetmealId,setmealId);116
117 //查询出符合条件的套餐菜品表118 List<SetmealDish> setmealDishList = setmealDishService.list(queryWrapper);119
120 //存放套餐菜品列表121 setmealDto.setSetmealDishes(setmealDishList);122
123 //存放套餐名124 setmealDto.setCategoryName(categoryName);125
126 return setmealDto;127 }128
129 /**130 * 更新套餐信息以及套餐菜品131 * @param setmealDto132 */133 134 public void updateWithDish(SetmealDto setmealDto) {135
136 this.updateById(setmealDto);137
138 Long setmealId = setmealDto.getId();139
140 LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>();141
142 queryWrapper.eq(SetmealDish::getSetmealId,setmealId);143
144 //清除相关菜品信息145 setmealDishService.remove(queryWrapper);146
147 //重新写入套餐id148 List<SetmealDish> setmealDishList = setmealDto.getSetmealDishes().stream().map((item) -> {149
150 item.setSetmealId(setmealId);151
152 return item;153 }).collect(Collectors.toList());154
155 //重新写入菜品信息156 setmealDishService.saveBatch(setmealDishList);157
158 }159}
目前市面上有很多第三方提供的短信服务,这些第三方短信服务会和各个运营商(移动、联通、电信)对接,我们只要注册成为会员并且按照提供的开发文档进行调用就可以发送短信,需要说明的是,这些短信服务一般都是收费服务
常用短信服务:
阿里云短信服务-介绍
阿里云短信服务(Short Message Service)是广大企业客户快速触达手机用户所优选使用的通信能力。调用API或用群发助手,即可发送验证码、通知类和营销类短信;国内验证短信秒级触达,到达率最高可达99%;国际/港澳台短信覆盖200多个国家和地区,安全稳定,广受出海企业选用。
应用场景:
使用阿里云短信服务发送短信,可以参照官方提供的文档即可
具体开发步骤:
1、导入maven坐标
2、调用API
在开发代码之前,需要梳理一下登录时前端页面和服务端的交互过程:
1、在登录页面(front/page/login.html)输入手机号,点击【获取验证码】按钮,页面发送ajax请求,在服务端调用短信服务API给指定手机号发送验证码短信
2、在登录页面输入验证码,点击【登录】按钮,发送ajax请求,在服务端处理登录请求开发手机验证码登录功能,其实就是在服务端编
写代码去处理前端页面发送的这2次请求即可。
添加不需要处理的路径
xxxxxxxxxx91//定义不需要处理的请求路径2String[] urls = new String[]{3 "/employee/login",4 "/employee/logout",5 "/backend/**",6 "/front/**",7 "/user/sendMsg",//移动端发送短信8 "/user/login"//移动端登录9};
判断移动端登录状态
xxxxxxxxxx111//4-2、判断移动端登录状态,如果已登录,则直接放行2if(request.getSession().getAttribute("user") != null){3
4 Long userId = (Long)request.getSession().getAttribute("user");5
6 BaseContext.setCurrentId(userId);7
8 log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("user"));9 filterChain.doFilter(request,response);10 return;11}
手机验证码发送
xxxxxxxxxx291/**2 * 发送手机短信验证码3 * @param user4 * @return5 */6("/sendMsg")7public R<String> sendMsg( User user, HttpSession session){8 //获取手机号9 String phone = user.getPhone();10
11 if (StringUtils.isNotBlank(phone)){12
13 //生成随机的四位的验证码14 String code = ValidateCodeUtils.generateValidateCode(4).toString();15 log.info("code={}",code);16
17 //调用阿里云提供的短信服务API提供短信服务18 //SMSUtils.sendMessage("短信验证码","",phone,code);19
20 //需要将生成的验证码保存到Session中21 session.setAttribute(phone,code);22
23 return R.success("手机验证码短信发送成功");24
25 }26
27 return R.error("手机验证码短信发送失败");28
29}
移动端登录验证
xxxxxxxxxx511/**2 * 移动端用户登录3 * @param userInfo4 * @return5 */6("/login")7public R<User> login( Map userInfo, HttpSession session){8
9 log.info("Map:{}",userInfo);10
11 //获取手机号12 String phone = userInfo.get("phone").toString();13
14 //获取验证码15 String code = userInfo.get("code").toString();16
17 //从Session中获取保存的验证码18 Object codeInSession = session.getAttribute("code");19
20 log.info("前端:{},后端:{}",code,codeInSession);21
22 //验证码的比对(页面提交的验证码和Session中保存的验证码比对)23 if (codeInSession != null && codeInSession.equals(code)){24 //能够比对成功,则说明登录成功25
26 LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();27
28 queryWrapper.eq(User::getPhone,phone);29
30 User user = userService.getOne(queryWrapper);31
32 //判断当前手机号对应的用户是否为新用户,如果是新用户就自动完成注册33 if (user == null){34
35 log.info("新用户注册");36
37 user = new User();38
39 user.setPhone(phone);40
41 userService.save(user);42 }43
44 session.setAttribute("user",user.getId());45
46 return R.success(user);47
48 }49
50 return R.error("登录失败");51}
xxxxxxxxxx1401package com.os467.reggie.controller;2
3import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;4import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;5import com.baomidou.mybatisplus.extension.plugins.pagination.Page;6import com.os467.reggie.common.BaseContext;7import com.os467.reggie.common.R;8import com.os467.reggie.entity.AddressBook;9import com.os467.reggie.service.AddressBookService;10import lombok.extern.slf4j.Slf4j;11import org.springframework.beans.factory.annotation.Autowired;12import org.springframework.util.CollectionUtils;13import org.springframework.web.bind.annotation.*;14
15import java.util.List;16
17/**18 * 地址簿管理19 */202122("/addressBook")23public class AddressBookController {24
25 26 private AddressBookService addressBookService;27
28 /**29 * 新增30 */31 32 public R<AddressBook> save( AddressBook addressBook) {33 addressBook.setUserId(BaseContext.getCurrentId());34 log.info("addressBook:{}", addressBook);35 addressBookService.save(addressBook);36 return R.success(addressBook);37 }38
39 /**40 * 设置默认地址41 */42 ("default")43 public R<AddressBook> setDefault( AddressBook addressBook) {44 log.info("addressBook:{}", addressBook);45 LambdaUpdateWrapper<AddressBook> wrapper = new LambdaUpdateWrapper<>();46 wrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());47 wrapper.set(AddressBook::getIsDefault, 0);48 //SQL:update address_book set is_default = 0 where user_id = ?49 addressBookService.update(wrapper);50
51 addressBook.setIsDefault(1);52 //SQL:update address_book set is_default = 1 where id = ?53 addressBookService.updateById(addressBook);54 return R.success(addressBook);55 }56
57 /**58 * 根据id查询地址59 */60 ("/{id}")61 public R get( Long id) {62 AddressBook addressBook = addressBookService.getById(id);63 if (addressBook != null) {64 return R.success(addressBook);65 } else {66 return R.error("没有找到该对象");67 }68 }69
70 /**71 * 查询默认地址72 */73 ("default")74 public R<AddressBook> getDefault() {75 LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();76 queryWrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());77 queryWrapper.eq(AddressBook::getIsDefault, 1);78
79 //SQL:select * from address_book where user_id = ? and is_default = 180 AddressBook addressBook = addressBookService.getOne(queryWrapper);81
82 if (null == addressBook) {83 return R.error("没有找到该对象");84 } else {85 return R.success(addressBook);86 }87 }88
89 /**90 * 查询指定用户的全部地址91 */92 ("/list")93 public R<List<AddressBook>> list(AddressBook addressBook) {94 addressBook.setUserId(BaseContext.getCurrentId());95 log.info("addressBook:{}", addressBook);96
97 //条件构造器98 LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();99 queryWrapper.eq(null != addressBook.getUserId(), AddressBook::getUserId, addressBook.getUserId());100 queryWrapper.orderByDesc(AddressBook::getUpdateTime);101
102 //SQL:select * from address_book where user_id = ? order by update_time desc103 return R.success(addressBookService.list(queryWrapper));104 }105
106 /**107 * 删除地址108 * @param ids109 * @return110 */111 112 public R<String> deleteById(String ids){113
114 if (ids != null){115
116 addressBookService.removeById(ids);117
118 return R.success("删除成功");119
120 }121
122 return R.error("删除失败");123
124 }125
126 127 public R<String> update( AddressBook addressBook){128
129 //如果存在地址簿信息则修改130 if (addressBook != null){131
132 addressBookService.updateById(addressBook);133
134 return R.success("保存成功");135 }136
137 return R.error("修改失败");138 }139
140}
xxxxxxxxxx2221package com.os467.reggie.controller;2
3import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;4import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;5import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;6import com.baomidou.mybatisplus.extension.plugins.pagination.Page;7import com.os467.reggie.common.R;8import com.os467.reggie.dto.OrdersDto;9import com.os467.reggie.entity.*;10import com.os467.reggie.service.*;11import lombok.extern.slf4j.Slf4j;12import org.springframework.beans.BeanUtils;13import org.springframework.beans.factory.annotation.Autowired;14import org.springframework.web.bind.annotation.*;15
16import javax.servlet.http.HttpSession;17import java.math.BigDecimal;18import java.time.LocalDateTime;19import java.util.ArrayList;20import java.util.List;21import java.util.UUID;22import java.util.stream.Collectors;23
242526("/order")27public class OrdersController {28
29 30 private OrdersService ordersService;31
32 33 private AddressBookService addressBookService;34
35 36 private ShoppingCartService shoppingCartService;37
38 39 private OrderDetailService orderDetailService;40
41 42 private UserService userService;43
44
45 /**46 * 回显用户信息47 * @param page48 * @param pageSize49 * @return50 */51 ("/userPage")52 public R<Page<User>> userPage(Integer page, Integer pageSize, HttpSession session){53
54 //创建分页构造器55 Page<User> userPage = new Page<>(page,pageSize);56
57 Long userId = (Long)session.getAttribute("user");58
59 LambdaQueryWrapper<Orders> ordersLambdaQueryWrapper = new LambdaQueryWrapper<>();60
61 return R.success(userPage);62 }63
64 /**65 * 回显用户信息66 * @param page67 * @param pageSize68 * @return69 */70 ("/page")71 public R<Page<OrdersDto>> page(Integer page, Integer pageSize, HttpSession session){72
73 //创建分页构造器74 Page<Orders> orderPage = new Page<>(page,pageSize);75
76 //创建分页构造器77 Page<OrdersDto> orderDtoPage = new Page<>(page,pageSize);78
79 Long userId = (Long)session.getAttribute("user");80
81 LambdaQueryWrapper<Orders> ordersLambdaQueryWrapper = new LambdaQueryWrapper<>();82
83 ordersLambdaQueryWrapper.orderByDesc(Orders::getOrderTime);84
85 ordersService.page(orderPage,ordersLambdaQueryWrapper);86
87 //拷贝分页结构88 BeanUtils.copyProperties(orderPage,orderDtoPage,"records");89
90 List<OrdersDto> ordersDtoList = orderPage.getRecords().stream().map((item) -> {91
92 OrdersDto ordersDto = new OrdersDto();93
94 BeanUtils.copyProperties(item, ordersDto);95
96 //获取订单id97 Long ordersId = ordersDto.getId();98
99 LambdaQueryWrapper<OrderDetail> orderDetailLambdaQueryWrapper = new LambdaQueryWrapper<>();100
101 //与订单匹配的订单明细102 orderDetailLambdaQueryWrapper.eq(OrderDetail::getOrderId, ordersId);103
104 //获取订单明细列表105 List<OrderDetail> orderDetailList = orderDetailService.list(orderDetailLambdaQueryWrapper);106
107 ordersDto.setOrderDetails(orderDetailList);108
109 return ordersDto;110
111 }).collect(Collectors.toList());112
113 //设置分页内数据114 orderDtoPage.setRecords(ordersDtoList);115
116 return R.success(orderDtoPage);117 }118
119 /**120 * 提交订单,清空购物车121 * @return122 */123 ("/submit")124 public R<String> submit( Orders orders,HttpSession session){125
126 AddressBook addressBook = addressBookService.getById(orders.getAddressBookId());127
128 //获取当前用户id129 Long userId = (Long)session.getAttribute("user");130
131 LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();132
133 //根据用户id查询购物车134 queryWrapper.eq(ShoppingCart::getUserId,userId);135
136 //获取购物车金额137 List<ShoppingCart> shoppingCartList = shoppingCartService.list(queryWrapper);138
139 //累计金额140 BigDecimal amountSum = new BigDecimal(0);141
142 for (ShoppingCart shoppingCart : shoppingCartList) {143
144 amountSum.add(shoppingCart.getAmount().multiply(new BigDecimal(shoppingCart.getNumber())));145 }146
147 //设置时间148 orders.setOrderTime(LocalDateTime.now());149 orders.setCheckoutTime(LocalDateTime.now());150 //设置总金额151 orders.setAmount(amountSum);152 //设置用户id153 orders.setUserId(userId);154 orders.setConsignee(addressBook.getConsignee()+(addressBook.getSex().equals("1")?"先生":"女士"));155 orders.setPhone(addressBook.getPhone());156 orders.setAddress(addressBook.getDetail());157
158 //获取用户信息159 User user = userService.getById(userId);160
161 orders.setUserName(user.getName());162
163 int count = ordersService.count();164
165 if (count == 0){166
167 orders.setNumber("1");168
169 }else {170
171 LambdaQueryWrapper<Orders> ordersLambdaQueryWrapper = new LambdaQueryWrapper<>();172
173 ordersLambdaQueryWrapper.orderByDesc(Orders::getNumber);174
175 ordersLambdaQueryWrapper.last("limit 1");176
177 Orders one = ordersService.getOne(ordersLambdaQueryWrapper);178
179 //生成订单号180 orders.setNumber(Integer.parseInt(one.getNumber())+1+"");181
182 }183
184 ordersService.save(orders);185
186 List<OrderDetail> orderDetailList = new ArrayList<>();187
188 //保存订单明细189 for (ShoppingCart shoppingCart : shoppingCartList) {190
191 OrderDetail orderDetail = new OrderDetail();192
193 //保存订单id194 orderDetail.setOrderId(orders.getId());195
196 //保存订单明细数据197 orderDetail.setAmount(shoppingCart.getAmount());198 orderDetail.setNumber(shoppingCart.getNumber());199 orderDetail.setDishId(shoppingCart.getDishId());200 orderDetail.setSetmealId(shoppingCart.getSetmealId());201 orderDetail.setDishFlavor(shoppingCart.getDishFlavor());202 orderDetail.setImage(shoppingCart.getImage());203 orderDetail.setName(shoppingCart.getName());204
205 orderDetailList.add(orderDetail);206
207 }208
209 //保存订单明细210 orderDetailService.saveBatch(orderDetailList);211
212 //清除购物车记录213 LambdaQueryWrapper<ShoppingCart> shoppingCartLambdaQueryWrapper = new LambdaQueryWrapper<>();214
215 shoppingCartLambdaQueryWrapper.eq(ShoppingCart::getUserId,userId);216
217 shoppingCartService.remove(shoppingCartLambdaQueryWrapper);218
219 return R.success("下单成功");220 }221
222}
xxxxxxxxxx1541package com.os467.reggie.controller;2
3import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;4import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;5import com.baomidou.mybatisplus.core.toolkit.StringUtils;6import com.os467.reggie.common.R;7import com.os467.reggie.entity.DishFlavor;8import com.os467.reggie.entity.Setmeal;9import com.os467.reggie.entity.ShoppingCart;10import com.os467.reggie.service.DishFlavorService;11import com.os467.reggie.service.SetmealService;12import com.os467.reggie.service.ShoppingCartService;13import com.sun.org.apache.regexp.internal.RE;14import lombok.extern.slf4j.Slf4j;15import org.springframework.beans.factory.annotation.Autowired;16import org.springframework.web.bind.annotation.*;17
18import javax.servlet.http.HttpSession;19import java.util.List;20
212223("/shoppingCart")24public class ShoppingCartController {25
26 27 private ShoppingCartService shoppingCartService;28
29 30 private DishFlavorService dishFlavorService;31
32 /**33 * 向购物车中添加数据34 * @param shoppingCart35 * @param session36 * @return37 */38 ("/add")39 public R<String> add( ShoppingCart shoppingCart, HttpSession session){40
41 log.info("ShoppingCart:{}",shoppingCart);42
43 Long userId = (Long)session.getAttribute("user");44
45 //向购物车中添加用户id46 shoppingCart.setUserId(userId);47
48 LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();49
50 //检查购物车里是否有相同的菜品或套餐51 queryWrapper.eq(ShoppingCart::getUserId,userId).52 eq(ShoppingCart::getDishId,shoppingCart.getDishId()).or().53 eq(ShoppingCart::getSetmealId,shoppingCart.getSetmealId());54
55 ShoppingCart one = shoppingCartService.getOne(queryWrapper);56
57 if (one != null && one.getNumber() >= 1){58
59 //更新数量60 one.setNumber(one.getNumber()+1);61 //更新口味62 one.setDishFlavor(shoppingCart.getDishFlavor());63
64 shoppingCartService.updateById(one);65
66 return R.success("添加成功");67
68 }else {69
70 return shoppingCartService.save(shoppingCart)?R.success("添加成功"):R.error("添加失败");71
72 }73
74 }75
76 /**77 * 根据用户id获取购物车列表78 * @return79 */80 ("/list")81 public R<List<ShoppingCart>> list(HttpSession session){82
83 //获取用户id84 Long userId = (Long)session.getAttribute("user");85
86 LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();87
88 //查询该用户的购物车89 queryWrapper.eq(ShoppingCart::getUserId, userId);90
91 List<ShoppingCart> shoppingCartList = shoppingCartService.list(queryWrapper);92
93 if (shoppingCartList != null){94
95 return R.success(shoppingCartList);96
97 }98
99 return R.error("未找到数据");100 }101
102 /**103 * 根据id删除该用户购物车中的内容104 * @param shoppingCart105 * @return106 */107 ("/sub")108 public R<String> sub( ShoppingCart shoppingCart,HttpSession session){109
110 //获取当前用户id111 Long userId = (Long)session.getAttribute("user");112
113 LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();114
115 queryWrapper.eq(shoppingCart.getDishId() != null,ShoppingCart::getDishId,shoppingCart.getDishId());116 queryWrapper.eq(shoppingCart.getSetmealId() != null,ShoppingCart::getSetmealId,shoppingCart.getSetmealId());117 queryWrapper.eq(userId != null,ShoppingCart::getUserId,userId);118
119 ShoppingCart one = shoppingCartService.getOne(queryWrapper);120
121 if (one.getNumber() == 1){122 shoppingCartService.removeById(one.getId());123 }else {124
125 //数量减一126 one.setNumber(one.getNumber()-1);127
128 shoppingCartService.updateById(one);129
130 }131
132 return R.success("更新成功");133
134 }135
136 /**137 * 根据当前用户id清空购物车138 * @param session139 * @return140 */141 ("/clean")142 public R<String> cleanShoppingCart(HttpSession session){143
144 //获取当前用户id145 Long userId = (Long)session.getAttribute("user");146
147 LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();148
149 queryWrapper.eq(userId != null,ShoppingCart::getUserId,userId);150
151 return shoppingCartService.remove(queryWrapper)?R.success("清空成功"):R.error("清空失败");152 }153
154}
1401package com.os467.reggie.controller;2
3import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;4import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;5import com.baomidou.mybatisplus.extension.plugins.pagination.Page;6import com.os467.reggie.common.BaseContext;7import com.os467.reggie.common.R;8import com.os467.reggie.entity.AddressBook;9import com.os467.reggie.service.AddressBookService;10import lombok.extern.slf4j.Slf4j;11import org.springframework.beans.factory.annotation.Autowired;12import org.springframework.util.CollectionUtils;13import org.springframework.web.bind.annotation.*;14
15import java.util.List;16
17/**18 * 地址簿管理19 */202122("/addressBook")23public class AddressBookController {24
25 26 private AddressBookService addressBookService;27
28 /**29 * 新增30 */31 32 public R<AddressBook> save( AddressBook addressBook) {33 addressBook.setUserId(BaseContext.getCurrentId());34 log.info("addressBook:{}", addressBook);35 addressBookService.save(addressBook);36 return R.success(addressBook);37 }38
39 /**40 * 设置默认地址41 */42 ("default")43 public R<AddressBook> setDefault( AddressBook addressBook) {44 log.info("addressBook:{}", addressBook);45 LambdaUpdateWrapper<AddressBook> wrapper = new LambdaUpdateWrapper<>();46 wrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());47 wrapper.set(AddressBook::getIsDefault, 0);48 //SQL:update address_book set is_default = 0 where user_id = ?49 addressBookService.update(wrapper);50
51 addressBook.setIsDefault(1);52 //SQL:update address_book set is_default = 1 where id = ?53 addressBookService.updateById(addressBook);54 return R.success(addressBook);55 }56
57 /**58 * 根据id查询地址59 */60 ("/{id}")61 public R get( Long id) {62 AddressBook addressBook = addressBookService.getById(id);63 if (addressBook != null) {64 return R.success(addressBook);65 } else {66 return R.error("没有找到该对象");67 }68 }69
70 /**71 * 查询默认地址72 */73 ("default")74 public R<AddressBook> getDefault() {75 LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();76 queryWrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());77 queryWrapper.eq(AddressBook::getIsDefault, 1);78
79 //SQL:select * from address_book where user_id = ? and is_default = 180 AddressBook addressBook = addressBookService.getOne(queryWrapper);81
82 if (null == addressBook) {83 return R.error("没有找到该对象");84 } else {85 return R.success(addressBook);86 }87 }88
89 /**90 * 查询指定用户的全部地址91 */92 ("/list")93 public R<List<AddressBook>> list(AddressBook addressBook) {94 addressBook.setUserId(BaseContext.getCurrentId());95 log.info("addressBook:{}", addressBook);96
97 //条件构造器98 LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();99 queryWrapper.eq(null != addressBook.getUserId(), AddressBook::getUserId, addressBook.getUserId());100 queryWrapper.orderByDesc(AddressBook::getUpdateTime);101
102 //SQL:select * from address_book where user_id = ? order by update_time desc103 return R.success(addressBookService.list(queryWrapper));104 }105
106 /**107 * 删除地址108 * @param ids109 * @return110 */111 112 public R<String> deleteById(String ids){113
114 if (ids != null){115
116 addressBookService.removeById(ids);117
118 return R.success("删除成功");119
120 }121
122 return R.error("删除失败");123
124 }125
126 127 public R<String> update( AddressBook addressBook){128
129 //如果存在地址簿信息则修改130 if (addressBook != null){131
132 addressBookService.updateById(addressBook);133
134 return R.success("保存成功");135 }136
137 return R.error("修改失败");138 }139
140}