1、需求分析
产品原型、需求规格说明书
2、设计
产品文档、UI界面设计、概要设计、详细设计、数据库设计
3、编码
项目代码、单元测试
4、测试
测试用例、设施报告
5、上线运维
软件环境安装、配置
项目介绍 本项目〈(瑞吉外卖)是专门为餐饮企业(餐厅、饭店)定制的一款软件产品,包括系统管理后台和移动端应用两部分。其中系统管理后台主要提供给餐饮企业内部员工使用,可以对餐厅的菜品、套餐、订单等进行管理维护。移动端应用主要提供给消费者使用,可以在线浏览菜品、添加购物车、下单等。
本项目共分为3期进行开发:
产品原型
就是一款产品成型之前的一个简单的框架,就是将页面的排版布局展现出来,使产品的初步构思有一个可视化的展示。通过原型展示,可以更加直观的了解项目的需求和提供的功能。
导入pom
xxxxxxxxxx
931
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启动类
xxxxxxxxxx
151package com.os467.reggie;
2
3import org.springframework.boot.SpringApplication;
4import org.springframework.boot.autoconfigure.SpringBootApplication;
5
6
7public 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,需要这样定义
xxxxxxxxxx
11static 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关键字规定了接口方法有方法体并且能被实现类继承和重写)
xxxxxxxxxx
271package 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
7
8
9public class WebMvcConfig implements WebMvcConfigurer {
10
11 /**
12 * 设置静态资源映射
13 * @param registry
14 */
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()
需要映射到的后端资源地址
xxxxxxxxxx
281package 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
8
9
10public class WebMvcConfig extends WebMvcConfigurationSupport {
11
12 /**
13 * 设置静态资源映射
14 * @param registry
15 */
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接口,需要提供一个实体类泛型
xxxxxxxxxx
121package 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
7
8public interface EmployeeMapper extends BaseMapper<Employee> {
9
10
11
12}
表现层
@RequestBody注解 使得接收到的值指定为json串,必须是post方式提交
@PostMapping(“/login”)注解
等价于@RequestMapping(value = “/user/login”,method = RequestMethod.POST)
xxxxxxxxxx
371package 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
15
16
17"/employee") (
18public class EmployeeController {
19
20 //注入业务层实例
21
22 private EmployeeService employeeService;
23
24 /**
25 * 员工登入
26 * @param request
27 * @param employee @RequestBody 注解使得接收到的值指定为json串
28 * @return
29 */
30 "/login") (
31 public R<Employee> login(HttpServletRequest request, Employee employee){
32
33
34 return null;
35 }
36
37}
本次工程内封装的返回结果类,用于给前端响应数据
xxxxxxxxxx
411package com.os467.reggie.common;
2
3import lombok.Data;
4import java.util.HashMap;
5import java.util.Map;
6
7/**
8 * 通用返回结果类,服务端响应的数据最终都会封装成此对象
9 * @param <T>
10 */
11
12public 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域中
xxxxxxxxxx
501/**
2 * 员工登入
3 * @param request
4 * @param employee @RequestBody 注解使得接收到的值指定为json串
5 * @return
6 */
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}
xxxxxxxxxx
141/**
2 * 员工注销方法
3 * 1、清理Session中的用户id
4 * 2、返回结果
5 * @return
6 */
7"/logout") (
8public R<String> logout(HttpServletRequest request){
9
10 //清除Session域中保存的当前登录员工的id
11 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匹配模式
xxxxxxxxxx
331package 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 */
14
15filterName = "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: 路径匹配器,支持通配符
xxxxxxxxxx
831package 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 */
17
18filterName = "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、获取本次请求的URI
33 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 urls
67 * @param requestURI
68 * @return
69 */
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}
为数据库新增员工
xxxxxxxxxx
261/**
2 * 新增员工
3 * @param employee
4 * @return
5 */
6
7public 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 //设置账户的创建更新人id
20 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注解
传入异常类的字节码,修饰方法(异常通知的内容),指定异常的处理类型
xxxxxxxxxx
391package 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 */
14annotations = {RestController.class}) (
15
16
17public class GlobalExceptionHandler {
18
19 /**
20 * 异常处理方法
21 * @return
22 */
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组件展示到页面上
xxxxxxxxxx
291/**
2 * 员工信息的分页查询
3 * @param page
4 * @param pageSize
5 * @param name
6 * @return
7 */
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)
使用以下包
xxxxxxxxxx
21import com.fasterxml.jackson.databind.annotation.JsonSerialize;
2import com.fasterxml.jackson.databind.ser.std.ToStringSerializer
xxxxxxxxxx
21using = ToStringSerializer.class) (
2private Long id;
方法二
1、提供对象转换器JacksonObjectMapper,基于jackson进行java对象到json数据的转换
2、在WebMvcConfig配置类中扩展SpringMVC的消息转换器,在此消息转换器中使用提供的对象转换器进行Java对象到Json数据的转换
直接使用JacksonObjectMapper类
xxxxxxxxxx
541package 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这个类
这些组件之间是如何协助,怎样才能合理的改写组件而实现自定义呢,这就需要了解起原理了
消息转换器创建和生效原理
xxxxxxxxxx
181/**
2 * 扩展mvc框架的消息转换器
3 * @param converters
4 */
5
6public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
7
8 //创建新的消息转换器
9 MappingJackson2CborHttpMessageConverter messageConverter = new MappingJackson2CborHttpMessageConverter();
10
11 //设置对象转换器,底层使用Jackson将java对象转成json
12 messageConverter.setObjectMapper(new JacksonObjectMapper());
13
14 //将上面的消息转换器对象追加到mvc框架的转换器集合中,放在最前面优先使用
15 converters.add(0,messageConverter);
16
17 super.extendMessageConverters(converters);
18}
通用修改员工数据方法
xxxxxxxxxx
191/**
2 * 根据id来修改员工信息
3 * @param employee
4 * @return
5 */
6
7public 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“) 绑定到操作方法的入参中
xxxxxxxxxx
91"/{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接口
xxxxxxxxxx
461package 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 */
13
14
15public class MyMetaObjectHandler implements MetaObjectHandler {
16
17 /**
18 * 插入操作自动填充
19 * @param metaObject
20 */
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 metaObject
35 */
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
编写工具类
xxxxxxxxxx
181package com.os467.reggie.common;
2
3/**
4 * 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
5 */
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值
xxxxxxxxxx
111/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值
xxxxxxxxxx
131/**
2 * 更新操作自动填充
3 * @param metaObject
4 */
5
6public 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}
需求分析
后台系统中可以管理分类信息,分类包括两种类型,分别是菜品分类和套餐分类。当我们在后台系统中添加菜品时需要选择一个菜品分类,当我们在后台系统中添加一个套餐时需要选择一个套餐分类,在移动端也会按照菜品分类和套餐分类来展示对应的菜品和套餐。
xxxxxxxxxx
141/**
2 * 新增分类
3 * @param category
4 * @return
5 */
6
7public R<String> save( Category category){
8
9 log.info("category:{}",category);
10
11 categoryService.save(category);
12
13 return R.success("新增分类成功");
14}
分页列表
xxxxxxxxxx
231/**
2 * 分类分页
3 * @param page
4 * @param pageSize
5 * @return
6 */
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}
删除分类
xxxxxxxxxx
141/**
2 * 删除分类
3 * @param ids
4 * @return
5 */
6
7public 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实现类
根据条件删除分类,同时判断是否抛出异常
xxxxxxxxxx
641package 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
16
17public 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 id
28 */
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}
自定义的异常
xxxxxxxxxx
111package com.os467.reggie.common;
2
3public class CustomException extends RuntimeException {
4
5 public CustomException(String msg){
6
7 super(msg);
8
9 }
10
11}
在全局异常处理类中进行处理
xxxxxxxxxx
111/**
2 * 异常处理方法
3 * @return
4 */
5CustomException.class) (
6public R<String> exceptionHandler(CustomException exception){
7
8 log.error(exception.getMessage());
9
10 return R.error(exception.getMessage());
11}
xxxxxxxxxx
141/**
2 * 根据id修改分类信息
3 * @param category
4 * @return
5 */
6
7public 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来处理平常一些的请求
测试是否能接收到文件
xxxxxxxxxx
291package 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 */
13
14"/common") (
15
16public class CommonController {
17
18 /**
19 * 文件上传
20 * @param file 参数名必须和前端name一致
21 * @return
22 */
23 "/upload") (
24 public R<String> upload(MultipartFile file){
25 log.info(file.toString());
26 return null;
27 }
28
29}
在配置文件中指定文件存放路径
xxxxxxxxxx
11reggie.iconPath=D:\\pythonschool\\PY_lab\\JavaProject\\reggie_take_out\\src\\main\\resources\\localIcon
在表现层通过Value注解获取
xxxxxxxxxx
21"${reggie.iconPath}") (
2private String basePath;
文件上传
xxxxxxxxxx
391/**
2 * 文件上传
3 * @param file 参数名必须和前端name一致
4 * @return
5 */
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的子类,它可以直接输出字节组中的二进制数据
xxxxxxxxxx
561/**
2 * 文件下载
3 * @param name
4 * @param response
5 */
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形式提交到服务端
xxxxxxxxxx
221/**
2 * 根据条件来回显分类数据
3 * @param category
4 * @return
5 */
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
开启事务
需要在启动类上添加注解
xxxxxxxxxx
11
在新增菜品的方法上开启事务
xxxxxxxxxx
11
xxxxxxxxxx
141/**
2 * 新增菜品
3 * @param dishDto
4 * @return
5 */
6
7public R<String> save( DishDto dishDto){
8
9 log.info("dishDto:{}",dishDto);
10
11 dishService.saveWithFlavor(dishDto);
12
13 return R.success("新增菜品成功");
14}
DishServiceImpl
xxxxxxxxxx
251/**
2 * 新增菜品,同时保存对应的口味数据
3 * @param dishDto
4 */
5
6
7public void saveWithFlavor(DishDto dishDto) {
8 //保存菜品的基本信息到菜品表dish
9 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有拷贝功能(底层是反射)
xxxxxxxxxx
691/**
2 * 菜品分页
3 * @param page
4 * @param pageSize
5 * @return
6 */
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 //获取到分类id
50 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}
回显菜品信息以及对应口味信息
代码实现
xxxxxxxxxx
121/**
2 * 根据id来查询菜品信息和对应的口味信息
3 * @param id
4 * @return
5 */
6"/{id}") (
7public R<DishDto> page( ("id") Long id){
8
9 DishDto dishDto = dishService.getByIdWithFlavor(id);
10
11 return R.success(dishDto);
12}
xxxxxxxxxx
251/**
2 * 根据id来查询菜品信息和对应的口味信息
3 * @param id
4 * @return
5 */
6
7public 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}
提交修改
xxxxxxxxxx
141/**
2 * 修改菜品信息
3 * @return
4 */
5
6public R<String> update( DishDto dishDto){
7
8 log.info("dishDto:{}",dishDto);
9
10 dishService.updateWithFlavor(dishDto);
11
12 return R.success("修改菜品成功");
13
14}
xxxxxxxxxx
301/**
2 * 修改菜品信息并且更新对应口味信息
3 * @param dishDto
4 */
5
6
7public 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}
xxxxxxxxxx
1661package 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
25
26"/setmeal") (
27
28public class SetmealController {
29
30
31 private SetmealService setmealService;
32
33
34 private CategoryService categoryService;
35
36 /**
37 * 获取套餐列表,并且显示套餐分类
38 * @param page
39 * @param pageSize
40 * @param name
41 * @return
42 */
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 //获取套餐id
72 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 setmealDto
95 * @return
96 */
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 ids
109 * @return
110 */
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 flag
130 * @param ids
131 * @return
132 */
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 setmealDto
153 * @return
154 */
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
xxxxxxxxxx
1591package 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
22
23public 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 setmealDto
35 */
36
37 public void saveSetmealWithDish(SetmealDto setmealDto) {
38
39 //获取套餐菜品关系列表
40 List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
41
42 //保存套餐信息
43 this.save(setmealDto);
44
45 //获取套餐id
46 Long setmealId = setmealDto.getId();
47
48 //设置套餐id
49 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 ids
65 */
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 setmealId
93 */
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 setmealDto
132 */
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 //重新写入套餐id
148 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次请求即可。
添加不需要处理的路径
xxxxxxxxxx
91//定义不需要处理的请求路径
2String[] urls = new String[]{
3 "/employee/login",
4 "/employee/logout",
5 "/backend/**",
6 "/front/**",
7 "/user/sendMsg",//移动端发送短信
8 "/user/login"//移动端登录
9};
判断移动端登录状态
xxxxxxxxxx
111//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}
手机验证码发送
xxxxxxxxxx
291/**
2 * 发送手机短信验证码
3 * @param user
4 * @return
5 */
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}
移动端登录验证
xxxxxxxxxx
511/**
2 * 移动端用户登录
3 * @param userInfo
4 * @return
5 */
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}
xxxxxxxxxx
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 */
20
21
22"/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 = 1
80 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 desc
103 return R.success(addressBookService.list(queryWrapper));
104 }
105
106 /**
107 * 删除地址
108 * @param ids
109 * @return
110 */
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}
xxxxxxxxxx
2221package 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
24
25
26"/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 page
48 * @param pageSize
49 * @return
50 */
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 page
67 * @param pageSize
68 * @return
69 */
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 //获取订单id
97 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 * @return
122 */
123 "/submit") (
124 public R<String> submit( Orders orders,HttpSession session){
125
126 AddressBook addressBook = addressBookService.getById(orders.getAddressBookId());
127
128 //获取当前用户id
129 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 //设置用户id
153 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 //保存订单id
194 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}
xxxxxxxxxx
1541package 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
21
22
23"/shoppingCart") (
24public class ShoppingCartController {
25
26
27 private ShoppingCartService shoppingCartService;
28
29
30 private DishFlavorService dishFlavorService;
31
32 /**
33 * 向购物车中添加数据
34 * @param shoppingCart
35 * @param session
36 * @return
37 */
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 //向购物车中添加用户id
46 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 * @return
79 */
80 "/list") (
81 public R<List<ShoppingCart>> list(HttpSession session){
82
83 //获取用户id
84 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 shoppingCart
105 * @return
106 */
107 "/sub") (
108 public R<String> sub( ShoppingCart shoppingCart,HttpSession session){
109
110 //获取当前用户id
111 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 session
139 * @return
140 */
141 "/clean") (
142 public R<String> cleanShoppingCart(HttpSession session){
143
144 //获取当前用户id
145 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 */
20
21
22"/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 = 1
80 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 desc
103 return R.success(addressBookService.list(queryWrapper));
104 }
105
106 /**
107 * 删除地址
108 * @param ids
109 * @return
110 */
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}