瑞吉外卖
技术选型
用户层 H5 VUE.js ElementUI 微信小程序
网关层 Nginx
应用层 SpringBoot SpringMVC SpringSession Spring Swagger lombok
数据层 MySql Mybatis MybatisPlus Redis
工具 git maven junit
功能架构
移动端前台(H5、微信小程序)
手机号登录 微信登录 地址管理 历史订单 菜品规格 购物车 下单 菜品浏览
系统管理后台
分类管理 菜品管理 套餐管理 菜品口味管理 员工登录 员工退出 员工管理 订单管理
角色
后台系统管理员:登录后台管理系统,拥有后台系统中的所有操作权限
后台系统普通员工:登录后台管理系统,对菜品、套餐、订单等进行管理
C端用户:登录移动端应用,可以浏览菜品、添加购物车、设置地址、在线下单等
开发环境搭建
数据库
数据库环境搭建
CREATE DATABASE reggie CHARACTER SET utf8mb4;
USE reggie;
source D:\\db_reggie.sql; (路径不要有中文)
SHOW TABLES;
employee 员工表
category 菜品和套餐分类表
dish 菜品表
setmeal 套餐表
setmeal_ dish 套餐菜品关系表
dish_ flavor 菜品口味关系表
user 用户表(C端)
address_ book 地址簿表
shopping_cart 购物车表
orders 订单表
order_ detail 订单明细表
maven项目搭建
pom.xml
<properties>
<!-- 指定JDK版本 -->
<java.version>1.8</java.version>
</properties>
<parent> <!-- 这是SpringBoot项目,需要继承父工程 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.11</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<!-- 插件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.4.5</version>
</plugin>
</plugins>
</build>
application.yml
src/main/resources/application.yml
server:
port: 8080
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/reggie?serverTimezone=UTC
username: root
password: "000000"
mybatis-plus:
global-config:
db-config:
table-prefix:
id-type: ASSIGN_ID
# id-type: auto
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true # #在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
# 自定义参数
reggie:
path: C:\Users\绯世\Desktop\tmp\img\
#path: classpath:// # 测试配置 暂未完善
启动类
启动类
com/YuZai/reggie/ReggieApplication.java
@Slf4j
@SpringBootApplication
@ServletComponentScan
@EnableTransactionManagement // 开启事务注解的支持
public class ReggieApplication {
public static void main(String[] args) {
SpringApplication.run(ReggieApplication.class, args);
log.info("项目启动Running......");
}
}
设置静态资源映射
设置静态资源映射 (页面访问)
@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
/**
* 设置静态资源映射
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("静态资源映射Running.....");
//当访问 /backend/资源名 时,不通过springMVC,直接映射到 /backend/ 下的资源。
registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
}
}
(知识补充) 静态资源映射
SpringBoot
默认的静态资源目录是:
src/main/resources/static、public、resources、templates、META-INF/resources
原理: 静态资源映射/** (所有请求), 也就是说当有请求进来,首先Controller处理,
若处理不了,交给静态资源处理器处理,若还处理不了,响应404。
放在静态资源目录的静态资源默认是无前缀的,直接http://localhost:8080/资源名 就能访问。
可通过 spring.mvc.static-path-pattern=/前缀/** 来设置访问静态资源的前缀,设置后
http://localhost:8080/前缀/资源名 来访问。
静态资源目录路径也可以改变:
spring.resources.static-locations: [classpath:/front/,classpath:/backend/,更多目录]
值得注意的时: 此种配置方式貌似不能与 WebMvcConfig extends WebMvcConfigurationSupport配置类一起使用,否则配置文件这边的配置不起效果。
URL: http://localhost:8080/资源名
也可通过配置类的方法将静态资源请求放行:
@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("静态资源映射Running.....");
//当访问 /backend/资源名 时,不通过springMVC,直接映射到 /backend/ 下的资源。
registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/"); // URL:http://localhost:8080/backend/index.html
registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
}
}
员工管理 页面开发
后台登录功能开发
package com.YuZai.reggie.controller;
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
/**
* 员工登录
* @param request
* @param employee
* @return
*/
@PostMapping("/login")
public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){ //传入HttpServletRequest是因为,如果登陆成功要把employee对象的id存到session一份,表示登录成功,也方便随时获取到登录的用户。
// 1、将页面提交的密码password进行md5加密处理
String password = employee.getPassword();
password = DigestUtils.md5DigestAsHex(password.getBytes()); //MD5加密(DigestUtils工具类)
// 2、根据页面提交的用户名username查询数据库
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Employee::getUsername,employee.getUsername());
Employee emp = employeeService.getOne(queryWrapper);
// 3、如果没有查询到则返回登录失败结果
if (emp==null){
return R.error("登录失败,查无此人!");
}
// 4、密码比对,如果不一-致则返回登录失败结果
if(!emp.getPassword().equals(password)){
return R.error("登录失败,密码错误!");
}
// 5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
if (emp.getStatus()==0){
return R.error("账号已禁用!");
}
// 6、登录成功,将员工id存入Session并返回登录成功结果
request.getSession().setAttribute("employee",emp.getId());
return R.success(emp);
}
}
后台退出功能开发
用户点击页面中退出按钮,发送请求,请求地址为/employee/logout,请求方式为POST。
我们只需要在Controller中创建对应的处理方法即可,具体的处理逻辑:
1、清理Session中的用户id
2、返回结果
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
/**
* 员工退出
* @param request
* @return
*/
@PostMapping("/logout")
public R<String> logout(HttpServletRequest request){
// 1、清理Session中的用户id
request.getSession().removeAttribute("employee");
// 2、返回结果
return R.success("退出成功!");
}
}
员工管理页面开发
1.完善登录功能
2.新增员工
3.员工信息分页查询
4.启用/禁用员工账号
5.编辑员工信息
完善登录功能 (过滤器)
使用过滤器或者拦截器,在过滤器或者拦截器中判断用户是否已经完成登录,如果没有登录则跳转到登录页面。
实现步骤:
1、创建自定义过滤器LoginCheckFilter
2、在启动类上加入注解@ServletComponentScan
3、完善过滤器的处理逻辑
package com.YuZai.reggie.filter;
/**
* 过滤器
* 检查用户是否完成登录
*/
@Slf4j
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*" )
public class LoginCheckFilter implements Filter {
// 路径匹配器,支持通配符; 在项目中主要用来做路径的匹配,在权限模块会用到接口路径的匹配。
private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
log.info("拦截到请求: {}",request.getRequestURI()); // 一种新的语法格式:{}->>>占位符 后面接->>> ,花括号中显示的内容
//1、获取本次请求的URI
String requestURI = request.getRequestURI();
//定义不需要处理的请求路径
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**"
} ;
//2、判断本次请求是否需要处理
boolean check = check(urls, requestURI);
//3、如果不需要处理,则直接放行
if (check) {
//放行
filterChain.doFilter(request,response);
log.info("请求=>>{}不需要处理",requestURI);
return;
}
//4、判断登录状态,如果已登录,则直接放行
if (request.getSession().getAttribute("employee") != null){
//放行
filterChain.doFilter(request,response);
log.info("用户已登录,用户id为=>>>{}",request.getSession().getAttribute("employee"));
return;
}
//5、如果未登录则返回未登录结果 ,通过输出流方式向客户端页面响应数据
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
log.info("用户未登录,请求为=>>>{}",request.getRequestURL());
return;
}
/**
* 路径配置检查本次请求是否需要放行
* @param urls
* @param requestURI
* @return
*/
public boolean check(String[] urls,String requestURI){
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if (match) {
return true;
}
}
return false;
}
}
新增员工
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
/**
* 新增员工
* @param employee
* @return
*/
@PostMapping
public R<String> addEmployee(HttpServletRequest request , @RequestBody Employee employee){
log.info("新增员工,员工信息: {}",employee.toString());
// 设置初始密码123465,需要进行md5加密处理
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//获得当前登录用户ID
Long empID= (Long) request.getSession().getAttribute("employee");
employee.setCreateUser(empID);
employee.setUpdateUser(empID);
employeeService.save(employee); // 可能会抛异常 java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '2144957066' for key 'employee.idx_username'
return R.success("新增员工成功");
}
}
全局异常处理器
全局异常捕获
package com.YuZai.reggie.common;
/**
* 全局异常处理
*/
@ControllerAdvice(annotations = {RestController.class ,Controller.class}) //拦截哪些Controller
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
/**
* 异常处理方法
* @return
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
log.error(ex.getMessage()); //日志输出异常信息
//逻辑处理
if (ex.getMessage().contains("Duplicate entry")){ //判断异常信息: 例:Duplicate entry '2144957066' for key 'employee.idx_username'
String[] split = ex.getMessage().split(" ");
String msg = split[2] + "已存在" ;
return R.error(msg);
}
return R.error("未知错误 ");
}
}
员工信息分页查询
package com.YuZai.reggie.config;
/**
* 配置MyBatisPlus的分页插件
*/
@Configuration
public class MyBatisPlusConfig {
//通过拦截器的方式把分页插件加进来
@Bean // 将方法的返回值添加至IOC
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 加载MyBatisPlus提供的PaginationInnerInterceptor()
return mybatisPlusInterceptor;
}
}
package com.YuZai.reggie.controller;
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
/**
* 员工信息分页查询
*/
@GetMapping("/page")
public R<Page> page(@RequestParam("page") int page,@RequestParam("pageSize") int pageSize ,String name){
log.info("page={},pageSize={},name={}",page,pageSize,name);
// 构造分页构造器
Page pageInfo = new Page(page,pageSize);
// 构造条件构造器
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper();
//添加过滤条件
queryWrapper.like(org.apache.commons.lang.StringUtils.isNotEmpty(name),Employee::getName,name); // 第一个参数:当name不为空过滤条件才会添加进去
//添加排序添加
queryWrapper.orderByDesc(Employee::getUpdateTime);
// 执行查询
employeeService.page(pageInfo, queryWrapper);// MyBatisPlus会自动将查询结果封装到Page对象中
return R.success(pageInfo);
}
}
启用/禁用员工账号
需求分析
在员工管理列表页面,可以对某个员工账号进行启用或者禁用操作。账号禁用的员工不能登录系统,启用后的员工
可以正常登录。
需要注意,只有管理员(admin用户)可以对其他普通用户进行启用、禁用操作,所以普通用户登录系统后启用、
禁用按钮不显示。
package com.YuZai.reggie.controller;
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
/**
* 根据id修改员工信息
* @return
*/
@PutMapping
public R<String> update(HttpServletRequest request ,@RequestBody Employee employee){
log.info("employee=>>>{}",employee);
Long empId = (Long) request.getSession().getAttribute("employee");
employee.setUpdateTime(LocalDateTime.now());
employee.setUpdateUser(empId);
employeeService.updateById(employee); //(long型数据大于16位,js处理会有精度损失)可自定义消息转换器将数据统一转为json格式。
return R.success("员工信息修改成功 ");
}
}
扩展Springmvc的消息转换器
(1)创建对象转换器jacksonObjectMapper,基于jackson进行java对象到json数据的转换。
package com.YuZai.reggie.common;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(BigInteger.class, ToStringSerializer.instance)
.addSerializer(Long.class, ToStringSerializer.instance)
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}
(2)在WebMvcConfig配置类中扩展Springmvc的消息转换器
@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
/**
* 扩展mvc框架的消息转换器
* @param converters
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器Running...");
//创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用Jackson将Java对象转为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将上面的消息转换器对象追加到mvc框架的转换器集合中
converters.add(0,messageConverter); // mvc默认有8个转换器,在这里设置我们自定义的转换器的下标为0,即第一个启动。
}
}
完美解决!
{"code":1,"msg":null,"data":{"records":[{"id":"7","username":"2144957022","name":"方一泽","password":"e10adc398561544549ba59abbe56e057f20f883e","phone":"15926790144","sex":"1","idNumber":"254465588545784125","status":0,"createTime":"2022-07-26 15:14:34","updateTime":"2022-07-27 10:42:40","createUser":"1","updateUser":"1"},],"total":4,"size":10,"current":1,"orders":[],"optimizeCountSql":true,"hitCount":false,"countId":null,"maxLimit":null,"searchCount":true,"pages":1},"map":{}}
编辑员工信息
package com.YuZai.reggie.controller;
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
/**
* 根据id查询用户信息
* @param id
* @return
*/
@GetMapping("/{id}")
public R<Employee> getById(@PathVariable Long id){
log.info("编辑用户id=>>>>{}",id);
Employee empById = employeeService.getById(id);
if (empById!=null){
return R.success(empById);
}else {
return R.error("没有查询到对应员工信息 ");
}
}
}
公共字段自动填充
package com.YuZai.reggie.entity;
/**
* 员工实体类
*/
@Data
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String username;
private String name;
private String password;
private String phone;
private String sex;
private String idNumber; // 身份证号码
private Integer status;
@TableField(fill = FieldFill.INSERT) // 插入时填充字段
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) // 插入和更新时填充字段
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT) // 插入时填充字段
private Long createUser;
@TableField(fill = FieldFill.INSERT_UPDATE) // 插入和更新时填充字段
private Long updateUser;
}
package com.YuZai.reggie.common;
/**
* 自定义元数据对象处理器
*/
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler { // 实现元数据处理器接口
/**
* 插入操作时 ,自动填充
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自动填充[insert] Running...."+metaObject.toString());
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("createUser", new Long(1)); // 此处的id值理应是动态获取
metaObject.setValue("updateUser", new Long(1)); // 暂时写死
}
/**
* 更新操作 ,自动填充
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充[update] insertFill Running....."+metaObject.toString());
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("updateUser", new Long(1)); // 此处的id值理应是动态获取 暂时写死
}
}
功能完善
实现步骤:
1、编写BaseContext工具类,基于ThreadLocal封装的工具类
2、在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
3、在MyMetaObjectHandler的方法中调用BaseContext获取登录用户的id
package com.YuZai.reggie.common;
/**
* 基于ThreadLocal封装工具类 ,用于保存和获取当前登录用户的id
*/
public class BaseContext { // 作用范围:某单个线程内(以线程为作用域)
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
/**
* 设置值
* @param id
*/
public static void setCurrentId(Long id){
threadLocal.set(id);
}
/**
* 获取值
* @return
*/
public static Long getCurrentId(){
return threadLocal.get();
}
}
com/YuZai/reggie/filter/LoginCheckFilter.java
//4、判断登录状态,如果已登录,则直接放行
if (request.getSession().getAttribute("employee") != null){
log.info("用户已登录,用户id为=>>>{}",request.getSession().getAttribute("employee"));
Long employeeCurrentId = (Long) request.getSession().getAttribute("employee"); // 当前已登录用户的id
BaseContext.setCurrentId(employeeCurrentId);
//放行
filterChain.doFilter(request,response);
return;
}
com/YuZai/reggie/common/MyMetaObjectHandler.java
/**
* 自定义元数据对象处理器
*/
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler { // 实现元数据处理器接口
/**
* 插入操作时 ,自动填充
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自动填充[insert] Running...."+metaObject.toString());
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("createUser", BaseContext.getCurrentId() ); // 此处的id值理应是动态获取 / 基于ThreadLocal封装工具类 ,用于保存和获取当前登录用户的id
metaObject.setValue("updateUser", BaseContext.getCurrentId() ); // 暂时写死 / BaseContext 是自定义基于ThreadLocal封装工具类 ,用于保存和获取当前登录用户的id
}
/**
* 更新操作 ,自动填充
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充[update] insertFill Running....."+metaObject.toString());
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("updateUser", BaseContext.getCurrentId() ); // 此处的id值理应是动态获取 暂时写死 / 基于ThreadLocal封装工具类 ,用于保存和获取当前登录用户的id
}
}
分类管理页面开发
在开发业务功能前,先将需要用到的类和接口基本结构创建好:
1.实体类Category
2.Mapper接口CategoryMapper
3.业务层接口CategoryService
4.业务层实现类CategoryServicelmpl
5.控制层CategoryController
package com.YuZai.reggie.entity;
/**
* 分类
*/
@Data
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//类型 1 菜品分类 2 套餐分类
private Integer type;
//分类名称
private String name;
//顺序
private Integer sort;
//创建时间
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
//更新时间
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
//创建人
@TableField(fill = FieldFill.INSERT)
private Long createUser;
//修改人
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
//是否删除
private Integer isDeleted;
}
package com.YuZai.reggie.mapper;
/**
* MyBatis Plus 提供了通用的 Mapper 接口(即 BaseMapper 接口),该接口对应我们的 DAO 层。
* 在该接口中,定义了我们常见的方法签名,这样就可以方便我们对表进行操作。
* 例如:查询(select)、插入(insert)、更新(update)和删除(delete)操作。
*/
@Mapper
public interface CategoryMapper extends BaseMapper<Category> {}
package com.YuZai.reggie.service;
/**
* MyBatis-Plus 提供了一个顶级IService,封装了很多CRUD操作,实际使用时,推荐直接在service层调用。
*/
public interface CategoryService extends IService<Category> {}
package com.YuZai.reggie.service.impl;
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {}
package com.YuZai.reggie.controller;
@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {
@Autowired
private CategoryService categoryService;
}
新增分类功能开发
package com.YuZai.reggie.controller;
@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {
@Autowired
private CategoryService categoryService;
/**
* 新增分类
* @param category
* @return
*/
@PostMapping
public R<String> addCategory(@RequestBody Category category ){
log.info("新增category=>>>>{}",category);
categoryService.save(category);
return R.success("新增分类成功 ");
}
}
分类信息分页查询
package com.YuZai.reggie.controller;
@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {
@Autowired
private CategoryService categoryService;
@GetMapping("/page")
public R<Page> page(@RequestParam("page") int page,@RequestParam("pageSize") int pageSize ){
log.info("page={},pageSize={}",page,pageSize);
//分页构造器
Page<Category> pageInfo = new Page(page, pageSize);
//条件构造器
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
//添加排序条件 根据sort字段来进行升序排序
queryWrapper.orderByAsc(Category::getSort);
categoryService.page(pageInfo,queryWrapper); // MyBatisPlus会自动将查询结果封装到Page对象中
return R.success(pageInfo) ;
}
}
分类删除功能开发
需求:
在分类管理列表页面,可以对某个分类进行删除操作。需要注意的是当分类关联了菜品或者套餐时,此分类不允许删除。
没有做菜品关联验证的 删除分类代码
/**
* 根据id删除分类
* @param ids
* @return
*/
@DeleteMapping
public R<String> deleteCategory(@RequestParam("ids") Long ids ){
log.info("删除分类,ids=>>>>{}",ids);
categoryService.removeById(ids);
return R.success("分类信息删除成功 ") ;
}
分类删除功能完善
mybatisplus提供的方法中没有满足需求的,所以自定义一个删除方法。
com/YuZai/reggie/service/CategoryService.java
public interface CategoryService extends IService<Category> {
public void remove(Long id);
}
com/YuZai/reggie/service/impl/CategoryServiceImpl.java
package com.YuZai.reggie.service.impl;
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
@Autowired
private DishService dishService;
@Autowired
private SetmealService setmealService;
/**
* 根据id删除分类 ,删除之前需要进行判断
* @param id
*/
@Override
public void remove(Long id) {
//条件构造器
LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
//添加查询条件 ,根据分类id进行查询。
dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
int count = dishService.count(dishLambdaQueryWrapper);
// 查询当前分类是否关联了菜品,如果已经关联,抛出一个业务异常
if (count>0){
//关联了菜品 ,抛出业务异常
throw new CustomException("当前分类下关联了菜品 ,不能删除 ");
}
// 查询当前分类是否关联了套餐,如果已经关联,抛出一个业务异常
LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
int count2 = setmealService.count(setmealLambdaQueryWrapper);
if (count2>0){
//关联了套餐 ,抛出业务异常
throw new CustomException("当前分类下关联了套餐 ,不能删除 ");
}
// 正常删除分类
super.removeById(id);
}
}
com/YuZai/reggie/common/CustomException.java
/**
* 自定义业务异常类
*/
public class CustomException extends RuntimeException{
public CustomException(String message) {
super(message);
}
}
com/YuZai/reggie/common/GlobalExceptionHandler.java
/**
* 全局异常处理
*/
@ControllerAdvice(annotations = {RestController.class ,Controller.class}) //拦截哪些Controller
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
/**
* 捕获CustomException.class异常处理方法
* @return
*/
@ExceptionHandler(CustomException.class)
public R<String> exceptionHandler(CustomException ex){
log.error(ex.getMessage()); //日志输出异常信息
return R.error(ex.getMessage());
}
}
@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {
@Autowired
private CategoryService categoryService;
/**
* 根据id删除分类
* @param ids
* @return
*/
@DeleteMapping
public R<String> deleteCategory(@RequestParam("ids") Long ids ){
log.info("删除分类,ids=>>>>{}",ids);
// categoryService.removeById(ids); 单纯的删除分类信息 ,没有做菜品关联验证 ,需要优化 。
categoryService.remove(ids); // 已优化
return R.success("分类信息删除成功 ") ;
}
}
修改分类功能开发
com/YuZai/reggie/controller/CategoryController.java
@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {
@Autowired
private CategoryService categoryService;
/**
* 根据id修改分类信息
* @param category
* @return
*/
@PutMapping
public R<String> update(@RequestBody Category category){
log.info("修改分类信息:{}",category);
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Category::getId,category.getId());
categoryService.update(category,queryWrapper); // 这里也可以直接用 categoryService.updateById(category); 根据id修改数据。
return R.success("修改分类信息成功 ");
}
}
菜品管理 页面开发
文件上传下载
文件下载介绍
文件下载,也称为download,是指将文件从服务器传输到本地计算机的过程。
通过浏览器进行文件下载,通常有两种表现形式:
以附件形式下载,弹出保存对话框,将文件保存到指定磁盘目录
直接在浏览器中打开
通过浏览器进行文件下载,本质上就是服务端将文件以流的形式写回浏览器的过程。
com/YuZai/reggie/filter/LoginCheckFilter.java
//定义不需要处理的请求路径
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**",
"/common/**"
} ;
src/main/resources/application.yml
# 自定义参数
reggie:
path: C:\Users\绯世\Desktop\tmp\img\
com/YuZai/reggie/controller/CommonController.java
/**
* 文件的上传和下载
*/
@RestController
@RequestMapping("/common")
@Slf4j
public class CommonController {
@Value("${reggie.path}") // 动态注入配置文件的变量值
private String basePath;
/**
* 文件上传
* @param file
* @return
*/
@RequestMapping("/upload")
public R<String> upload(MultipartFile file){ // 形参的名字必须与前端的name=""保持一致, 这里用的是file。
// file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除。
log.info("file=->>>{}",file.toString());
String originalFilename = file.getOriginalFilename(); // 获取原始文件名 如果有重名会有覆盖风险。 **.jpg
String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); // 截取原始文件名后缀
// 使用UUID重新生成文件名,防止文件名称重复造成文件覆盖
String fileName = UUID.randomUUID().toString() + suffix; // gfsd245sad144das 没有后缀
//创建一个目录对象
File dir = new File(basePath);
//判断当前目录是否存在
if(!dir.exists()){
//目录不存在,需要创建
dir.mkdir();
}
try {
file.transferTo(new File(basePath+fileName)); // 转存操作
} catch (IOException e) {
e.printStackTrace();
}
return R.success(fileName); // 返回文件名称
}
}
/**
* 文件下载
* 通过输出流向浏览器页面写回数据(二进制数据)
* @param name
* @param response
*/
@GetMapping("/download")
public void download(@RequestParam("name") String name, HttpServletResponse response){
try {
//输入流 ,通过输入流读取文件内容
FileInputStream fileInputStream = new FileInputStream(new File(basePath+name));
//输出流 ,通过输出流将文件写回浏览器 ,在浏览器展示图片
ServletOutputStream outputStream = response.getOutputStream();
// 设置响应过去的文件类型
response.setContentType("image/jpeg"); // image/jpeg 代表图片文件
int len=0;
byte[] bytes = new byte[1024];
while ((len=fileInputStream.read(bytes))!=-1){ // !=-1 表示未读完
outputStream.write(bytes,0,len);
outputStream.flush(); // 清空缓存并输出流
}
//关闭流资源
outputStream.close();
fileInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
新增菜品
菜品分类下拉列表
@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {
@Autowired
private CategoryService categoryService;
/**
* 根据type查询信息
* @param type
* @return
*/
@GetMapping("/list")
public R<List<Category>> selectByType(@RequestParam("type") String type){ // () 中也可直接写 Category实体类,url中的参数值会自动赋于相应的成员变量。
// 条件构造器
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
//添加条件 如果type的值不为空 ,添加 where type=? 的条件
queryWrapper.eq(type!=null,Category::getType,type);
//添加排序条件 根据sort升序后若有相同的根据updateTime降序
queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);
List<Category> list = categoryService.list(queryWrapper);
return R.success(list);
}
}
新增菜品
package com.YuZai.reggie.dto;
import com.YuZai.reggie.entity.Dish;
import com.YuZai.reggie.entity.DishFlavor;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* DTO ,全称为Data Transfer Object ,即数据传输对象 ,一般用于展示层与服务层之间的数据传输。
*/
@Data
public class DishDto extends Dish {
private List<DishFlavor> flavors = new ArrayList<>();
private String categoryName;
private Integer copies;
}
package com.YuZai.reggie.service;
/**
* MyBatis-Plus 提供了一个顶级IService,封装了很多CRUD操作,实际使用时,推荐直接在service层调用。
*/
public interface DishService extends IService<Dish> {
/**
* 新增菜品 ,同时插入菜品对应的口味数据 ,需要操作两张表: dish、dish_flavor。
* @param dishDto
*/
public void addDishWithFlavor(DishDto dishDto);
}
package com.YuZai.reggie.service.impl;
import java.util.List;
import java.util.stream.Collectors;
@Service
@Slf4j
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
@Autowired
private DishFlavorService dishFlavorService;
/**
* 新增菜品 ,同时保存对应的口味数据。
* @param dishDto
*/
@Transactional // 因涉及多张表的操作 ,应加入事务控制。 (记得 ,要想事务生效 ,得再启动类上开启@EnableTransactionManagement )
public void addDishWithFlavor(DishDto dishDto) {
// 保存菜品的基本信息到菜品表dish
this.save(dishDto);
// 处理List<DishFlavor>数据
Long dishId = dishDto.getId(); //菜品Id
// 菜品口味
List<DishFlavor> flavors = dishDto.getFlavors();
flavors = flavors.stream().map((item)->{ //通过stream流来处理数据
item.setDishId(dishId);
return item;
}).collect(Collectors.toList());
// 保存菜品口味数据到菜品口味表dish_flavor
dishFlavorService.saveBatch(flavors);
}
}
package com.YuZai.reggie.controller;
import com.YuZai.reggie.common.R;
import com.YuZai.reggie.dto.DishDto;
import com.YuZai.reggie.entity.Dish;
import com.YuZai.reggie.entity.DishFlavor;
import com.YuZai.reggie.service.DishFlavorService;
import com.YuZai.reggie.service.DishService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 菜品管理
*/
@RestController
@RequestMapping("/dish")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
@Autowired
private DishFlavorService dishFlavorService;
@PostMapping
public R<String> addDish(@RequestBody DishDto dishDto){
log.info("新增菜品信息=>>>{}",dishDto);
dishService.addDishWithFlavor(dishDto);
return R.success("新增菜品成功 ");
}
}
菜品信息分页查询
@RestController
@RequestMapping("/dish")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
@Autowired
private DishFlavorService dishFlavorService;
@Autowired
private CategoryService categoryService;
/**
* 菜品信息分页查询
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> page(int page,int pageSize ,String name){
//分页构造器对象
Page<Dish> pageInfo = new Page<>(page,pageSize);
Page<DishDto> dishDtoPage = new Page<>();
//条件构造器
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
//添加like模糊查询条件
queryWrapper.like( name!=null,Dish::getName,name );
//添加排序条件
queryWrapper.orderByDesc(Dish::getUpdateTime);
//执行分页查询
dishService.page(pageInfo, queryWrapper); //
//对象拷贝
BeanUtils.copyProperties(pageInfo,dishDtoPage,"records"); // 拷贝属性方法copyProperties() ,查询数据列表-records 不拷 , 它需要处理。
// 数据处理 菜品分类Id 转换为=>> 菜品分类名称
List<Dish> records = pageInfo.getRecords(); // 获取pageInfo的records(查询数据列表)。
List<DishDto> list = records.stream().map((item)->{
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(item,dishDto); // 属性拷贝
Long categoryId = item.getCategoryId(); // 菜品分类Id
Category category = categoryService.getById(categoryId); // 根据id查询菜品分类对象
String categoryName = category.getName(); // 菜品分类名称
dishDto.setCategoryName(categoryName);
return dishDto;
}).collect(Collectors.toList()); // 转成集合
dishDtoPage.setRecords(list);
return R.success(dishDtoPage) ;
}
}
修改菜品
数据回显
http://localhost:8080/dish/1552940214419816450 GET
@Service
@Slf4j
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
@Autowired
private DishFlavorService dishFlavorService;
/**
* 根据id查询菜品信息和对应的口味信息
* @param id
* @return
*/
public DishDto getByIdWithFlavor(Long id) {
// 查询菜品基本信息 ,从dish表查询
Dish dishById = this.getById(id);
// 查询当前菜品对应的口味信息 ,从dish_flavor表查询
LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper();
queryWrapper.eq(DishFlavor::getDishId,dishById.getId());
List<DishFlavor> list = dishFlavorService.list(queryWrapper);
// 封装
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(dishById,dishDto); // 普通属性拷贝
dishDto.setFlavors(list); // flavors属性赋值
return dishDto;
}
}
/**
* 根据Id查询 菜品信息 和 口味信息
* @param id
* @return
*/
@GetMapping("/{id}")
public R<DishDto> getById(@PathVariable Long id ){
DishDto dishDto = dishService.getByIdWithFlavor(id);
return R.success(dishDto);
}
数据修改
http://localhost:8080/dish PUT
@Service
@Slf4j
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
@Autowired
private DishFlavorService dishFlavorService;
/**
* 更新菜品信息 ,同时更新口味信息
* @param dishDto
*/
@Transactional // 涉及操作两张表 ,加上事务注解 ,保证数据的一致性。
public void updateWithFlavor(DishDto dishDto) {
// 更新dish表基本信息
this.updateById(dishDto);
// 清理当前菜品对应口味数据---dish_flavor表的delete操作
LambdaQueryWrapper<DishFlavor> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(DishFlavor::getDishId,dishDto.getId());
dishFlavorService.remove(queryWrapper);
// 添加当前提交过来的口味数据---dish_flavor表的insert操作
List<DishFlavor> flavors = dishDto.getFlavors(); // 注意 ,此时的DishFlavor对象的dishId属性并没有被赋值。
// 给dishId属性赋值
flavors = flavors.stream().map((i)->{
i.setDishId(dishDto.getId());
return i ;
}).collect(Collectors.toList());
dishFlavorService.saveBatch(flavors);
}
}
@PutMapping
public R<String> update( @RequestBody DishDto dishDto ){
dishService.updateWithFlavor(dishDto);
return R.success("更新菜品信息成功 ") ;
}
批量删除菜品
/**
* 删除菜品信息 ,及口味信息
* @param ids
*/
@Override
@Transactional
public void deleteWithFlavor(List<String> ids) {
// 删除菜品信息
this.removeByIds(ids);
// 删除口味信息
LambdaQueryWrapper<DishFlavor> queryWrapper=new LambdaQueryWrapper();
queryWrapper.in(DishFlavor::getDishId,ids);
dishFlavorService.remove(queryWrapper);
}
/**
* 批量删除菜品
* @param ids
* @return
*/
@DeleteMapping()
public R<String> delete(@RequestParam String ids){
log.info("批量删除ids=>>{}",ids); // 1552940214419816450,1413384757047271425
// 切割
String[] idsSplit = ids.split(",");
// 数组转换成arrayList集合
List<String> idsList = Arrays.asList(idsSplit);
log.info("批量删除ids=>>{}",idsList.toString()); // [1552940214419816450, 1413384757047271425]
dishService.deleteWithFlavor(idsList);
return R.success("批量删除成功 ");
}
批量起售与停售 (起售,停售)
package com.YuZai.reggie.service;
/**
* MyBatis-Plus 提供了一个顶级IService,封装了很多CRUD操作,实际使用时,推荐直接在service层调用。
*/
public interface DishService extends IService<Dish> {
// 批量修改菜品信息
public void updateForStatus(List<String> ids,int status);
}
package com.YuZai.reggie.service.impl;
@Service
@Slf4j
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
@Autowired
private DishFlavorService dishFlavorService;
/**
* 批量修改菜品信息
* @param ids
*/
@Override
public void updateForStatus(List<String> ids,int status) {
// 返回要修改的菜品实体对象
List<Dish> dishes = this.listByIds(ids);
// 给Status字段修改值
dishes = dishes.stream().map((i)->{
if ( status==1 ){
i.setStatus(1);
return i ;
}else if ( status==0 ){
i.setStatus(0);
return i ;
}else {
new CustomException(" 批量修改菜品信息ForStatus状态错误 ");
return i;
}
}).collect(Collectors.toList());
System.out.println(dishes.toString());
// 批量修改菜品信息
this.updateBatchById(dishes);
}
}
@RestController
@RequestMapping("/dish")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
@Autowired
private DishFlavorService dishFlavorService;
@Autowired
private CategoryService categoryService;
/**
* 批量修改菜品
* @param ids
* @param request
* @return
*/
@PostMapping({"/status/0","/status/1"}) // http://localhost:8080/dish/status/0?ids=1552940214419816450 POST
public R<String> updateByStatus(@RequestParam String ids,HttpServletRequest request){
String servletPath = request.getServletPath();// /dish/status/1
// status 给予值
String statusStr = servletPath.substring(servletPath.length()-1); // 获取字符串最后的一个字符
int status = Integer.parseInt(statusStr);
// 切割
String[] idsSplit = ids.split(",");
// 把ids数组转换成arrayList集合
List<String> idsList = Arrays.asList(idsSplit);
log.info("批量停售ids=>>{}",idsList.toString());
// 批量删除
dishService.updateForStatus(idsList,status);
return R.success("批量修改成功 ");
}
}
套餐管理 页面开发
新建套餐
添加菜品
URL: http://localhost:8080/dish/list?categoryId=1552568162257006593 GET
package com.YuZai.reggie.controller;
@RestController
@RequestMapping("/dish")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
@Autowired
private DishFlavorService dishFlavorService;
@Autowired
private CategoryService categoryService;
/**
* 根据条件查询对应的菜品数据
* @param dish
* @return
*/
@GetMapping("/list")
public R<List<Dish>> list(Dish dish){ // http://localhost:8080/dish/list?categoryId=1552568162257006593 GET
// 构造条件构造器
LambdaQueryWrapper<Dish> queryWrapper=new LambdaQueryWrapper();
// 添加查询条件
queryWrapper.eq(dish.getCategoryId()!=null,Dish::getCategoryId,dish.getCategoryId());
// 添加查询条件 ,查询状态为1(起售状态)的菜品
queryWrapper.eq(Dish::getStatus,1); // where status=1
// 添加排序条件
queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
// 执行查询
List<Dish> dishList = dishService.list(queryWrapper);
return R.success(dishList);
}
}
添加套餐
package com.YuZai.reggie.service;
/**
* MyBatis-Plus 提供了一个顶级IService,封装了很多CRUD操作,实际使用时,推荐直接在service层调用。
*/
public interface SetmealService extends IService<com.YuZai.reggie.entity.Setmeal> {
// 新增套餐 ,及套餐与菜品关系表
public void addSetmealWithSeteamlDish(SetmealDto setmealDto);
}
package com.YuZai.reggie.service.impl;
@Service
@Slf4j
public class SetmealServiceImpl extends ServiceImpl<SetmealMapper, Setmeal> implements SetmealService {
@Autowired
private SetmealDishService setmealDishService;
// 新增套餐 ,及套餐与菜品的关系
@Override
@Transactional // 涉及操作两个表 ,加上事务注解 ,保持数据的一致性。
public void addSetmealWithSeteamlDish(SetmealDto setmealDto) {
// 保存套餐的基本信息 ,操作setmeal表 ,执行insert操作
this.save(setmealDto); // 因为SetmealDto继承了setmeal,所以可以直接insert
// 保存套餐和菜品的关联信息 ,操作setmeal_dish ,执行insert操作
List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes(); // setmealId值为空 ,需要赋值。
setmealDishes = setmealDishes.stream().map((i)->{
i.setSetmealId(setmealDto.getId());
return i ;
}).collect(Collectors.toList());
setmealDishService.saveBatch(setmealDishes);
}
}
package com.YuZai.reggie.controller;
/**
* 套餐管理
*/
@Slf4j
@RestController
@RequestMapping("/setmeal")
public class SetmealController {
@Autowired
private SetmealService setmealService;
@Autowired
private SetmealDishService setmealDishService;
/**
* 新增套餐
* @param setmealDto
* @return
*/
@PostMapping
public R<String> addSetmeal(@RequestBody SetmealDto setmealDto){ // http://localhost:8080/setmeal POST
log.info("新增套餐=>>>>{}",setmealDto.toString());
setmealService.addSetmealWithSeteamlDish(setmealDto);
return R.success(" 新增套餐成功 ");
}
}
套餐信息分页查询
/**
* 套餐管理
*/
@Slf4j
@RestController
@RequestMapping("/setmeal")
public class SetmealController {
@Autowired
private SetmealService setmealService;
@Autowired
private SetmealDishService setmealDishService;
@Autowired
private CategoryService categoryService;
/**
* 套餐管理页面分页查询
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> page( int page, int pageSize , String name ){ // http://localhost:8080/setmeal/page?page=1&pageSize=10&name=666
Page<Setmeal> pageInfo = new Page<>(page,pageSize);
Page<SetmealDto> dtoPage = new Page<>();
LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper();
queryWrapper.like(name!=null,Setmeal::getName,name);
// 根据更新时间降序排
queryWrapper.orderByDesc(Setmeal::getUpdateTime);
setmealService.page(pageInfo,queryWrapper);
BeanUtils.copyProperties(pageInfo,dtoPage,"records"); // 除了records成员数据,其他都拷贝
List<Setmeal> records = pageInfo.getRecords(); // 获取records成员数据
List<SetmealDto> dtoList = records.stream().map((i)->{ // 处理records成员数据
SetmealDto setmealDto = new SetmealDto();
BeanUtils.copyProperties(i,setmealDto); // 拷贝基础属性数据
// 分类Id
Long categoryId = i.getCategoryId();
Category category = categoryService.getById(categoryId); // 根据分类Id查询分类对象
if (category!=null){
String categoryName = category.getName();
setmealDto.setCategoryName(categoryName);
}
return setmealDto;
}).collect(Collectors.toList());
// 数据封装
dtoPage.setRecords(dtoList);
return R.success(dtoPage);
}
}
删除套餐
注意,对于状态为售卖中的套餐不能删除,需要先停售,然后才能删除。
package com.YuZai.reggie.service;
public interface SetmealService extends IService<com.YuZai.reggie.entity.Setmeal> {
// 删除套餐 ,及套餐与菜品关系表
public void removeWithDish(List<Long> ids);
}
package com.YuZai.reggie.service.impl;
@Service
@Slf4j
public class SetmealServiceImpl extends ServiceImpl<SetmealMapper, Setmeal> implements SetmealService {
@Autowired
private SetmealDishService setmealDishService;
// 删除套餐 ,及套餐与菜品关系表
@Override
@Transactional
public void removeWithDish(List<Long> ids) {
// SELECT COUNT(*) FROM setmeal WHERE id IN () AND STATUS=1
// 查询套餐状态 是否可以删除
LambdaQueryWrapper<Setmeal> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.in(Setmeal::getId,ids);
queryWrapper.eq(Setmeal::getStatus,1);
int count = this.count(queryWrapper);
if (count>0){
// 不能删除 抛出业务异常
throw new CustomException(" 该套餐正在售卖中不能删除 ");
}
// 可以删除 先删除套餐表数据--Setmeal表
this.removeByIds(ids);
// 再删除 关系表中数据--Setmeal_dish表
LambdaQueryWrapper<SetmealDish> queryWrapper2 = new LambdaQueryWrapper();
queryWrapper2.in( SetmealDish::getSetmealId, ids );
setmealDishService.remove(queryWrapper2); // DELETE FROM setmeal_dish WHERE setmeal_id IN () ;
}
}
package com.YuZai.reggie.controller;
@Slf4j
@RestController
@RequestMapping("/setmeal")
public class SetmealController {
@Autowired
private SetmealService setmealService;
@Autowired
private SetmealDishService setmealDishService;
@Autowired
private CategoryService categoryService;
/**
* 删除套餐
* @param ids
* @return
*/
@DeleteMapping
public R<String> delete( @RequestParam List<Long> ids ){
log.info("批量删除的套餐ids=>>>{}",ids.toString());
setmealService.removeWithDish(ids);
return R.success(" 套餐数据删除成功 ") ;
}
}
起售 停售
/**
* 套餐管理
*/
@Slf4j
@RestController
@RequestMapping("/setmeal")
public class SetmealController {
@Autowired
private SetmealService setmealService;
@Autowired
private SetmealDishService setmealDishService;
@Autowired
private CategoryService categoryService;
/**
* 起售 停售 套餐
* @param ids
* @param request
* @return
*/
@PostMapping({"/status/0","/status/1"})
public R<String> update(@RequestParam List<Long> ids, HttpServletRequest request){ // http://localhost:8080/setmeal/status/0?ids=1554623655897030658,1415580119015145474 POST
String servletPath = request.getServletPath();// /dish/status/1
// status 给予值
String statusStr = servletPath.substring(servletPath.length()-1); // 获取字符串最后的一个字符
int status = Integer.parseInt(statusStr);
log.info(" status=>>>> {} ",status);
// UPDATE setmeal SET STATUS=1 WHERE id IN (1415580119015145474) ;
UpdateWrapper<Setmeal> updateWrapper = new UpdateWrapper<>();
updateWrapper.in("id",ids);
updateWrapper.set("status",(status==1?1:0));
setmealService.update(updateWrapper);
return R.success(" 修改套餐销售状态成功 ");
}
}
瑞吉外卖前台(用户端)
手机验证码登录
P80-P87
使用阿里云短信服务发送短信 ,可以参照官方提供的文档。
具体开发步骤:
1、导入maven坐标
2、调用API
maven坐标
<!-- 手机验证码登录 -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.16</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>2.1.0</version>
</dependency>
API
package com.YuZai.reggie.utils;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
/**
* 短信发送工具类
*/
public class SMSUtils {
/**
* 发送短信
* @param signName 签名
* @param templateCode 模板
* @param phoneNumbers 手机号
* @param param 参数
*/
public static void sendMessage(String signName, String templateCode,String phoneNumbers,String param){
DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "", "");
IAcsClient client = new DefaultAcsClient(profile);
SendSmsRequest request = new SendSmsRequest();
request.setSysRegionId("cn-hangzhou");
request.setPhoneNumbers(phoneNumbers);
request.setSignName(signName);
request.setTemplateCode(templateCode);
request.setTemplateParam("{\"code\":\""+param+"\"}");
try {
SendSmsResponse response = client.getAcsResponse(request);
System.out.println("短信发送成功");
}catch (ClientException e) {
e.printStackTrace();
}
}
}
package com.YuZai.reggie.utils;
import java.util.Random;
/**
* 随机生成验证码工具类
*/
public class ValidateCodeUtils {
/**
* 随机生成验证码
* @param length 长度为4位或者6位
* @return
*/
public static Integer generateValidateCode(int length){
Integer code =null;
if(length == 4){
code = new Random().nextInt(9999);//生成随机数,最大为9999
if(code < 1000){
code = code + 1000;//保证随机数为4位数字
}
}else if(length == 6){
code = new Random().nextInt(999999);//生成随机数,最大为999999
if(code < 100000){
code = code + 100000;//保证随机数为6位数字
}
}else{
throw new RuntimeException("只能生成4位或6位数字验证码");
}
return code;
}
/**
* 随机生成指定长度字符串验证码
* @param length 长度
* @return
*/
public static String generateValidateCode4String(int length){
Random rdm = new Random();
String hash1 = Integer.toHexString(rdm.nextInt());
String capstr = hash1.substring(0, length);
return capstr;
}
}
浏览器手机模式进行测试
修改LoginCheckFilter
前面我们已经完成了LoginCheckFilter过滤器的开发,此过滤器用于检查用户的登录状态。我们在进行手机验证码
登录时,发送的请求需要在此过滤器处理时直接放行。
//定义不需要处理的请求路径
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**",
"/common/**",
"/user/sendMsg", // 移动端发送短信
"/user/login" // 移动端登录
} ;
及用户是否登录判断逻辑
//4-2、判断移动端用户登录状态,如果已登录,则直接放行
if (request.getSession().getAttribute("user") != null){
log.info("用户已登录,用户id为=>>>{}",request.getSession().getAttribute("user"));
Long userId = (Long) request.getSession().getAttribute("user"); // 当前已登录用户的id
BaseContext.setCurrentId(userId);
//放行
filterChain.doFilter(request,response);
return;
}
发送验证码短信
package com.YuZai.reggie.controller;
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private UserService userService;
/**
* 发送手机短信验证码
* @param user
* @return
*/
@PostMapping("/sendMsg")
public R<String> sendMsg(@RequestBody User user, HttpSession session){
// 获取手机号
String phone = user.getPhone();
if (StringUtils.isNotEmpty(phone)){
// 生成随机的4位验证码
String code = ValidateCodeUtils.generateValidateCode(4).toString();
log.info("code=>>{}",code);
// 调用阿里云提供的短信服务API完成发送短信
// SMSUtils.sendMessage("短信签名","模板code",phone,code);
// 将生成的验证码保存到Session
session.setAttribute(phone,code);
Object codeInSession = session.getAttribute(phone);
return R.success("手机验证码短信发送成功 ");
}
return R.error("短信发送失败 ");
}
}
移动端用户登录
package com.YuZai.reggie.controller;
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
@Autowired
private UserService userService;
/**
* 移动端用户登录
* @param map
* @param session
* @return
*/
@PostMapping("/login") // 测试URL: POST http://localhost:8080/user/login
public R<User> login(@RequestBody Map map, HttpSession session){ // 形参类型不能是User因为没有code属性 ;可以用DTO扩展cide属性 ;还可以使用Map接收。
User FinalUser= new User();
log.info("map=>>>{}",map.toString());
// 获取手机号
String phone = map.get("phone").toString();
// 获取验证码
String code = map.get("code").toString();
// 从Session中获取保存的验证码
Object codeInSession = session.getAttribute(phone);
// 进行验证码的比对 (页面提交的验证码和Session中保存的验证码比对)
if (codeInSession!=null&&codeInSession.equals(code)){
LambdaQueryWrapper<User> queryWrapper= new LambdaQueryWrapper<>();
queryWrapper.eq(User::getPhone,phone);
User userServiceOne = userService.getOne(queryWrapper);// SELECT * FROM reggie.`user` WHERE phone=? ;
session.setAttribute("user",userServiceOne.getId());
FinalUser=userServiceOne;
//判断该手机号是否为新用户
if (userServiceOne==null){
User user = new User();
user.setPhone(phone);
userService.save(user); // 自动注册
// 查询出它的id
LambdaQueryWrapper<User> queryWrapper2= new LambdaQueryWrapper<>();
queryWrapper2.eq(User::getPhone,phone);
User uu2 = userService.getOne(queryWrapper);
// 将id存入Session
session.setAttribute("user",uu2.getId());
FinalUser=uu2;
}
return R.success(FinalUser);
}
return R.error("登录失败 ");
}
}
用户地址簿
地址簿 ,指的是移动端消费者用户的地址信息 ,用户登录成功后可以维护自己的地址信息。同一个用户可以有多个地
址信息 ,但是只能有一个默认地址。
package com.YuZai.reggie.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 地址簿
*/
@Data
public class AddressBook implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//用户id
private Long userId;
//收货人
private String consignee;
//手机号
private String phone;
//性别 0 女 1 男
private String sex;
//省级区划编号
private String provinceCode;
//省级名称
private String provinceName;
//市级区划编号
private String cityCode;
//市级名称
private String cityName;
//区级区划编号
private String districtCode;
//区级名称
private String districtName;
//详细地址
private String detail;
//标签
private String label;
//是否默认 0 否 1是
private Integer isDefault;
//创建时间
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
//更新时间
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
//创建人
@TableField(fill = FieldFill.INSERT)
private Long createUser;
//修改人
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
//是否删除
private Integer isDeleted;
}
package com.YuZai.reggie.controller;
/**
* 用户地址簿
*/
@RestController
@RequestMapping("/addressBook")
@Slf4j
public class AddressBookController {
@Autowired
private AddressBookService addressBookService;
/**
* 新增
* @param addressBook
* @return
*/
@PostMapping
public R<AddressBook> save(@RequestBody AddressBook addressBook ){
addressBook.setUserId(BaseContext.getCurrentId());
log.info("addressBoook:{}",addressBook);
addressBookService.save(addressBook);
return R.success(addressBook) ;
}
/**
* 设置默认地址
* @param addressBook
* @return
*/
@PutMapping("/default")
public R<AddressBook> setDefault (@RequestBody AddressBook addressBook ){
log.info("addressBook:{}",addressBook);
LambdaUpdateWrapper<AddressBook> wrapper=new LambdaUpdateWrapper<>();
wrapper.eq(AddressBook::getUserId,BaseContext.getCurrentId());
wrapper.set(AddressBook::getIsDefault,0);
addressBookService.update(wrapper); // UPDATE address_book SET is_default = 0 WHERE user_id=? ;
addressBook.setIsDefault(1);
addressBookService.updateById(addressBook); // UPDATE address_book SET is_default=1 WHERE id=? ;
return R.success(addressBook) ;
}
/**
* 根据id查询地址
* @param id
* @return
*/
@GetMapping("/{id}")
public R get (@PathVariable Long id){
AddressBook addressBook = addressBookService.getById(id);
if (addressBook != null){
return R.success(addressBook);
}else {
return R.error("没有找到该对象" );
}
}
/**
* 查询默认地址
* @return
*/
@GetMapping("/default")
public R<AddressBook> getDefault(){
LambdaQueryWrapper<AddressBook> queryWrapper= new LambdaQueryWrapper<>();
queryWrapper.eq(AddressBook::getUserId,BaseContext.getCurrentId());
queryWrapper.eq(AddressBook::getIsDefault,1);
AddressBook addressBook = addressBookService.getOne(queryWrapper); // SELECT * FROM address_book WHERE user_id =? AND is_default=1 ;
if (null==addressBook){
return R.error("没有找到该对象 ");
}else {
return R.success(addressBook);
}
}
/**
* 查询指定用户的全部地址
* @param addressBook
* @return
*/
@GetMapping("/list")
public R<List<AddressBook>> list( AddressBook addressBook ){
addressBook.setUserId(BaseContext.getCurrentId());
log.info("addressBook:{}",addressBook);
LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(addressBook.getUserId()!=null,AddressBook::getUserId,addressBook.getUserId());
queryWrapper.orderByDesc(AddressBook::getUpdateTime);
List<AddressBook> list = addressBookService.list(queryWrapper); // SELECT * FROM address_book WHERE user_id= ? ORDER BY update_time DESC ;
return R.success(list);
}
}
菜品展示
菜品分类数据展示
获取分类数据 (菜品分类和套餐分类)
此功能在前面后端套餐管理页面的新建套餐的添加菜品按钮中已实现 :
@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {
/**
* 根据type查询信息
* @param type
* @return
*/
@GetMapping("/list")
// public R<List<Category>> selectByType(@RequestParam("type") String type){ // () 中也可直接写 Category实体类,url中的参数值会自动赋于相应的成员变量。
public R<List<Category>> selectByType(String type){ // () 中也可直接写 Category实体类,url中的参数值会自动赋于相应的成员变量。
// 条件构造器
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
//添加条件 如果type的值不为空 ,添加 where type=? 的条件
queryWrapper.eq(type!=null,Category::getType,type);
//添加排序条件 根据sort升序后若有相同的根据updateTime降序
queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);
List<Category> list = categoryService.list(queryWrapper);
return R.success(list);
}
}
选择规格
package com.YuZai.reggie.controller;
@RestController
@RequestMapping("/dish")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
@Autowired
private DishFlavorService dishFlavorService;
@Autowired
private CategoryService categoryService;
/**
* 根据条件查询对应的菜品数据
* @param dish
* @return
*/
/* 代码优化
@GetMapping("/list")
public R<List<Dish>> list(Dish dish){ // http://localhost:8080/dish/list?categoryId=1552568162257006593 GET
// 构造条件构造器
LambdaQueryWrapper<Dish> queryWrapper=new LambdaQueryWrapper();
// 添加查询条件
queryWrapper.eq(dish.getCategoryId()!=null,Dish::getCategoryId,dish.getCategoryId());
// 添加查询条件 ,查询状态为1(起售状态)的菜品
queryWrapper.eq(Dish::getStatus,1); // where status=1
// 添加排序条件
queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
// 执行查询
List<Dish> dishList = dishService.list(queryWrapper);
return R.success(dishList);
}
*/
@GetMapping("/list")
public R<List<DishDto>> list(Dish dish){ // http://localhost:8080/dish/list?categoryId=1552568162257006593 GET
// 构造条件构造器
LambdaQueryWrapper<Dish> queryWrapper=new LambdaQueryWrapper();
// 添加查询条件
queryWrapper.eq(dish.getCategoryId()!=null,Dish::getCategoryId,dish.getCategoryId());
// 添加查询条件 ,查询状态为1(起售状态)的菜品
queryWrapper.eq(Dish::getStatus,1); // where status=1
// 添加排序条件
queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
// 执行查询
List<Dish> dishList = dishService.list(queryWrapper);
// 数据处理
List<DishDto> dishDtoList = dishList.stream().map((item)->{
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(item,dishDto); // 属性拷贝
Long categoryId = item.getCategoryId(); // 菜品分类Id
Category category = categoryService.getById(categoryId); // 根据id查询菜品分类对象
if (category!=null){
String categoryName = category.getName(); // 菜品分类名称
dishDto.setCategoryName(categoryName);
}
Long dishId = item.getId(); // 菜品Id
LambdaQueryWrapper<DishFlavor> queryWrapper2=new LambdaQueryWrapper();
queryWrapper2.eq(DishFlavor::getDishId,dishId);
int count = dishFlavorService.count(queryWrapper2);
List<DishFlavor> dishFlavorList = dishFlavorService.list(queryWrapper2); // 为什么查不到值
dishDto.setFlavors(dishFlavorList);
return dishDto;
}).collect(Collectors.toList()); // 转成集合
return R.success(dishDtoList);
}
}
套餐分类数据展示
/**
* 套餐管理
*/
@Slf4j
@RestController
@RequestMapping("/setmeal")
public class SetmealController {
@Autowired
private SetmealService setmealService;
@Autowired
private SetmealDishService setmealDishService;
@Autowired
private CategoryService categoryService;
/**
* 根据条件查询套餐数据
* @param setmeal
* @return
*/
@GetMapping("/list")
public R<List<Setmeal>> list(Setmeal setmeal){ // GET http://localhost:8080/setmeal/list?categoryId=1413342269393674242&status=1
LambdaQueryWrapper<Setmeal> queryWrapper=new LambdaQueryWrapper();
queryWrapper.eq(setmeal.getCategoryId()!=null,Setmeal::getCategoryId,setmeal.getCategoryId());
queryWrapper.eq(setmeal.getStatus()!=null,Setmeal::getStatus,setmeal.getStatus());
queryWrapper.orderByDesc(Setmeal::getUpdateTime);
List<Setmeal> list = setmealService.list(queryWrapper);
return R.success(list);
}
}
购物车
购物车添加菜品
package com.YuZai.reggie.controller;
import com.YuZai.reggie.common.BaseContext;
import com.YuZai.reggie.common.R;
import com.YuZai.reggie.entity.ShoppingCart;
import com.YuZai.reggie.service.ShoppingCartService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.injector.methods.UpdateById;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
/**
* @author 鱼仔
* @create 2022-08-05 16:11
*/
@RestController
@RequestMapping("/shoppingCart")
@Slf4j
public class ShoppingCartController {
@Autowired
private ShoppingCartService shoppingCartService;
/**
* 添加菜品进购物车
* @param shoppingCart
* @return
*/
@RequestMapping("/add")
public R<ShoppingCart> addShoppingCart(@RequestBody ShoppingCart shoppingCart){ // http://localhost:8080/shoppingCart/add POST
log.info( "shoppingCart=>>{}",shoppingCart );
//(id=null, name=老八蜜汁小汉堡, userId=null, dishId=1552940214419816450, setmealId=null, dishFlavor=无糖,不要葱,
// number=null, amount=10, image=08d2e0ea-71fc-4152-b13b-03bf1ae3d0fa.jpg, createTime=null)
// 设置用户Id
Long currentId = BaseContext.getCurrentId();
shoppingCart.setUserId(currentId);
LambdaQueryWrapper<ShoppingCart> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(ShoppingCart::getUserId,currentId);
Long dishId = shoppingCart.getDishId(); // dishId添加的便是菜品 ; setmealId添加的是套餐。
if (dishId!=null){
queryWrapper.eq(ShoppingCart::getDishId,dishId);
} else {
Long setmealId = shoppingCart.getSetmealId();
queryWrapper.eq(ShoppingCart::getSetmealId,setmealId);
}
// 查询购物车里有没有当前添加的菜品
ShoppingCart one = shoppingCartService.getOne(queryWrapper);
if (one != null){
// 当前添加的菜品在购物车中已经存在
Integer number = one.getNumber();
one.setNumber(number+1);
shoppingCartService.updateById(one);
} else {
// 若不存在,则添加到购物车,数量默认为一。
shoppingCart.setNumber(1);
shoppingCartService.save(shoppingCart);
one=shoppingCart;
}
return R.success(one);
}
}
查询当前登录用户购物车信息
@RestController
@RequestMapping("/shoppingCart")
@Slf4j
public class ShoppingCartController {
@Autowired
private ShoppingCartService shoppingCartService;
/**
* 查看当前用户的购物车信息
* @return
*/
@GetMapping("/list")
public R<List<ShoppingCart>> list(){
log.info(" 查看购物车 ");
LambdaQueryWrapper<ShoppingCart> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());
queryWrapper.orderByAsc(ShoppingCart::getCreateTime);
List<ShoppingCart> list = shoppingCartService.list(queryWrapper);
return R.success(list);
}
}
清空购物车
@RestController
@RequestMapping("/shoppingCart")
@Slf4j
public class ShoppingCartController {
@Autowired
private ShoppingCartService shoppingCartService;
/**
* 清空购物车
* @return
*/
@DeleteMapping("/clean")
public R<String> deleteAll(){ // http://localhost:8080/shoppingCart/clean DELETE
LambdaQueryWrapper<ShoppingCart> queryWrapper=new LambdaQueryWrapper();
queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());
shoppingCartService.remove(queryWrapper);
return R.success("清空购物车成功 ");
}
}
用户下单
package com.YuZai.reggie.service.impl;
import com.YuZai.reggie.common.BaseContext;
import com.YuZai.reggie.common.CustomException;
import com.YuZai.reggie.entity.*;
import com.YuZai.reggie.mapper.OrdersMapper;
import com.YuZai.reggie.service.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
* @author 鱼仔
* @create 2022-08-06 14:52
*/
@Service
public class OrdersServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements OrdersService {
@Autowired
private ShoppingCartService shoppingCartService;
@Autowired
private UserService userService;
@Autowired
private AddressBookService addressBookService;
@Autowired
private OrdersDetailService ordersDetailService;
// 用户下单
@Transactional // 涉及操作多张 ,需事务控制。
public void submit(Orders orders) {
// 获取当前登录用户id
Long currentId = BaseContext.getCurrentId();
// 查询购物车数据
LambdaQueryWrapper<ShoppingCart> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(ShoppingCart::getUserId,currentId);
List<ShoppingCart> shoppingCarts = shoppingCartService.list(queryWrapper);
//判断购物车数据
if (shoppingCarts == null || shoppingCarts.size()==0 ){
throw new CustomException("购物车为空,不能下单 ");
}
//查询用户数据
User userServiceById = userService.getById(currentId);
//查询地址数据
Long addressBookId = orders.getAddressBookId(); // 地址主键值
AddressBook addressBookServiceById = addressBookService.getById(addressBookId);
if (addressBookServiceById == null){
throw new CustomException("用户地址信息为空,不能下单 ");
}
// 订单号
long id = IdWorker.getId(); // MyBatisPlus提供的工具类
AtomicInteger amount = new AtomicInteger(0); // 原子操作 保证线程安全 ; AtomicInteger的作用,用原子方式更新的int值。主要用于在高并发环境下的高效程序处理。使用非阻塞算法来实现并发控制。
// 计算订单总金额 及 整理出订单明细数据
List<OrderDetail> orderDetails = shoppingCarts.stream().map( (i) ->{
OrderDetail orderDetail = new OrderDetail();
orderDetail.setOrderId(id); //订单号
orderDetail.setNumber(i.getNumber()); // 菜品或套餐的份数
orderDetail.setDishFlavor(i.getDishFlavor()); // 口味
orderDetail.setDishId(i.getDishId()); // 菜品id
orderDetail.setSetmealId(i.getSetmealId() ); // 套餐id
orderDetail.setName(i.getName()); // 菜品或套餐的名称
orderDetail.setImage(i.getImage()); // 图片名
orderDetail.setAmount(i.getAmount()); // 单价
amount.addAndGet(i.getAmount().multiply(new BigDecimal(i.getNumber())).intValue()); //addAndGet() 累加操作 multiply()乘 intValue()转为int值
return orderDetail;
}).collect(Collectors.toList());
orders.setNumber(String.valueOf(id)); // 订单号
orders.setId(id); // 主键id
orders.setOrderTime(LocalDateTime.now()); // 下单时间
orders.setCheckoutTime(LocalDateTime.now()); // 支付时间
orders.setStatus(2); // 订单状态 1待付款,2待派送,3已派送,4已完成,5已取消
orders.setAmount(new BigDecimal(amount.get())); // 实收金额
orders.setUserId(currentId); // 下单用户id
orders.setUserName(userServiceById.getName()); // 用户名
orders.setConsignee(addressBookServiceById.getConsignee()); // 收货人
orders.setPhone(addressBookServiceById.getPhone()); // 手机号
orders.setAddress( ( addressBookServiceById.getProvinceName()==null?"":addressBookServiceById.getProvinceName() ) //省级名称
+(addressBookServiceById.getCityName()==null?"":addressBookServiceById.getCityName() ) //市级名称
+(addressBookServiceById.getDistrictName()==null?"":addressBookServiceById.getDistrictName()) //区级名称
+(addressBookServiceById.getDetail()==null?"":addressBookServiceById.getDetail()) ); //详细地址
// 向订单表插入数据 ,一条数据
this.save(orders);
// 向订单明细表插入数据 ,多条数据
ordersDetailService.saveBatch(orderDetails);
// 清空购物车
shoppingCartService.remove(queryWrapper);
}
}
package com.YuZai.reggie.controller;
import com.YuZai.reggie.common.R;
import com.YuZai.reggie.entity.Orders;
import com.YuZai.reggie.service.OrdersService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 鱼仔
* @create 2022-08-06 14:56
*/
@RestController
@RequestMapping("/order")
@Slf4j
public class OrdersController {
@Autowired
private OrdersService ordersService;
@PostMapping("/submit")
public R<String> submit(@RequestBody Orders orders){ // http://localhost:8080/order/submit POST
log.info("订单数据=>> {}",orders.toString());
ordersService.submit(orders);
return R.success(" 下单成功 ");
}
}