瑞吉外卖项目实战 第一章 项目准备 开发环境搭建 数据库环境搭建 创建数据库reggie
1 create database reggie character set utf8mb4;
运行sql文件,进行表结构的快速创建。
maven环境搭建 创建项目reggie_take_out,检查maven、jre、jdk
maven使用本地仓库
maven换源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 <?xml version="1.0" encoding="UTF-8" ?> <settings xmlns ="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd" > <pluginGroups /> <proxies /> <servers /> <localRepository > D:\maven\apache-maven-3.8.6\repository</localRepository > <mirrors > <mirror > <id > alimaven</id > <mirrorOf > central</mirrorOf > <name > aliyun maven</name > <url > http://maven.aliyun.com/nexus/content/repositories/central/</url > </mirror > <mirror > <id > alimaven</id > <name > aliyun maven</name > <url > http://maven.aliyun.com/nexus/content/groups/public/</url > <mirrorOf > central</mirrorOf > </mirror > <mirror > <id > central</id > <name > Maven Repository Switchboard</name > <url > http://repo1.maven.org/maven2/</url > <mirrorOf > central</mirrorOf > </mirror > <mirror > <id > repo2</id > <mirrorOf > central</mirrorOf > <name > Human Readable Name for this Mirror.</name > <url > http://repo2.maven.org/maven2/</url > </mirror > <mirror > <id > ibiblio</id > <mirrorOf > central</mirrorOf > <name > Human Readable Name for this Mirror.</name > <url > http://mirrors.ibiblio.org/pub/mirrors/maven2/</url > </mirror > <mirror > <id > jboss-public-repository-group</id > <mirrorOf > central</mirrorOf > <name > JBoss Public Repository Group</name > <url > http://repository.jboss.org/nexus/content/groups/public</url > </mirror > <mirror > <id > google-maven-central</id > <name > Google Maven Central</name > <url > https://maven-central.storage.googleapis.com </url > <mirrorOf > central</mirrorOf > </mirror > <mirror > <id > maven.net.cn</id > <name > oneof the central mirrors in china</name > <url > http://maven.net.cn/content/groups/public/</url > <mirrorOf > central</mirrorOf > </mirror > </mirrors > </settings >
编写项目配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 <?xml version="1.0" encoding="UTF-8" ?> </img/Java/17ReggieProject xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion > 4.0.0</modelVersion > <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.4.5</version > <relativePath /> </parent > <groupId > com.nuaa</groupId > <artifactId > reggie_take_out</artifactId > <version > 1.0-SNAPSHOT</version > <properties > <java.version > 1.8</java.version > </properties > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > <scope > compile</scope > </dependency > <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-boot-starter</artifactId > <version > 3.4.2</version > </dependency > <dependency > <groupId > org./img/Java/17ReggieProjectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.20</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.76</version > </dependency > <dependency > <groupId > commons-lang</groupId > <artifactId > commons-lang</artifactId > <version > 2.6</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <scope > runtime</scope > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid-spring-boot-starter</artifactId > <version > 1.1.23</version > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > <version > 2.4.5</version > </plugin > </plugins > </build > <//img/Java/17ReggieProject>
配置spring配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 server: port: 8080 spring: application: name: reggie_take_out datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true username: root password: qqy.2520 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: ASSIGN_ID
编写项目启动类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.nuaa.reggie;import lombok.extern.slf4j.Slf4j;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@Slf4j @SpringBootApplication public class ReggieApplication { public static void main (String[] args) { SpringApplication.run(ReggieApplication.class, args); log.info("项目启动成功!" ); } }
webmvc映射配置 编写一个配置类,实现搜索路径和静态资源的映射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.nuaa.reggie.config;import lombok.extern.slf4j.Slf4j;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;@Slf4j @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { @Override protected void addResourceHandlers (ResourceHandlerRegistry registry) { log.info("开始静态资源映射...." ); registry.addResourceHandler("/backend/**" ).addResourceLocations("classpath:/backend/" ); registry.addResourceHandler("/front/**" ).addResourceLocations("classpath:/front/" ); } }
后台系统登录功能 导入员工实体类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package com.nuaa.reggie.entity;import com.baomidou.mybatisplus.annotation.FieldFill;import com.baomidou.mybatisplus.annotation.TableField;import lombok.Data;import java.io.Serializable;import java.time.LocalDateTime;@Data public class Employee implements Serializable { private static final long serialVersionUID = 1L ; private Long id; private String username; private String name; private String password; private String phone; private String sex; private String idNumber; private Integer status; private LocalDateTime createTime; private LocalDateTime updateTime; @TableField(fill = FieldFill.INSERT) private Long createUser; @TableField(fill = FieldFill.INSERT_UPDATE) private Long updateUser; }
创建对应的mapper、service、controller 1 2 3 4 5 6 7 8 9 10 11 12 13 package com.nuaa.reggie.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.nuaa.reggie.entity.Employee;import org.apache.ibatis.annotations.Mapper;@Mapper public interface EmployeeMapper extends BaseMapper <Employee> {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.nuaa.reggie.service;import com.baomidou.mybatisplus.extension.service.IService;import com.nuaa.reggie.entity.Employee;import org.springframework.stereotype.Service;public interface EmployeeService extends IService <Employee> { }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.nuaa.reggie.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.nuaa.reggie.entity.Employee;import com.nuaa.reggie.mapper.EmployeeMapper;import com.nuaa.reggie.service.EmployeeService;import org.springframework.stereotype.Service;@Service public class EmployeeServiceImpl extends ServiceImpl <EmployeeMapper, Employee> implements EmployeeService {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package com.nuaa.reggie.controller;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.nuaa.reggie.common.R;import com.nuaa.reggie.entity.Employee;import com.nuaa.reggie.service.EmployeeService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Controller;import org.springframework.util.DigestUtils;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;import javax.servlet.http.HttpServletRequest;@Slf4j @RestController @RequestMapping("/employee") public class EmployeeController { @Autowired private EmployeeService employeeService; }
编写服务端返回结果类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package com.nuaa.reggie.common;import lombok.Data;import java.util.HashMap;import java.util.Map;@Data public class R <T> { private Integer code; private String msg; private T data; private Map map = new HashMap (); public static <T> R<T> success (T object) { R<T> r = new R <T>(); r.data = object; r.code = 1 ; return r; } public static <T> R<T> error (String msg) { R r = new R (); r.msg = msg; r.code = 0 ; return r; } public R<T> add (String key, Object value) { this .map.put(key, value); return this ; } }
编写业务逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 @PostMapping("/login") public R<Employee> login (HttpServletRequest request, @RequestBody Employee employee) { String username = employee.getUsername(); String password = employee.getPassword(); password = DigestUtils.md5DigestAsHex(password.getBytes()); LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(Employee::getUsername, employee.getUsername()); Employee emp = employeeService.getOne(queryWrapper); if (emp == null ) { return R.error("登录失败" ); } if (!emp.getPassword().equals(password)) { return R.error("登录失败" ); } if (emp.getStatus() == 0 ){ return R.error("账号已禁用" ); } request.getSession().setAttribute("employee" , emp.getId()); return R.success(emp); }
后台系统退出功能 直接在controller中编写业务逻辑即可
1 2 3 4 5 6 7 8 9 10 11 12 @PostMapping("/logout") public R<String> logout (HttpServletRequest request) { request.getSession().removeAttribute("employee" ); return R.success("退出成功" ); }
第二章 员工管理 完善登录功能 业务需求 :需要保证只有在登录后才可以访问主页面,未登录时则默认跳转至登录页面
具体实现方法 :使用 拦截器 或者 过滤器 ,在过滤器或者拦截器中判断用户是否已经完成登录,如果没有登录则跳转至登录页面。
本项目使用过滤器进行完善
注意 :需要在springboot启动类中需要添加@ServletComponentScan
,才能在每次项目启动时先运行过滤器。
登录页面过滤器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 package com.nuaa.reggie.filter;import com.alibaba.fastjson.JSON;import com.nuaa.reggie.common.R;import lombok.extern.slf4j.Slf4j;import org.springframework.boot.web.servlet.ServletComponentScan;import org.springframework.util.AntPathMatcher;import javax.servlet.*;import javax.servlet.annotation.WebFilter;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;@Slf4j @WebFilter(filterName = "loginCheckFilter", urlPatterns = "/*") public class LoginCheckFilter implements Filter { public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher (); private String[] urls = new String []{ "/employee/login" , "/employee/logout" , "/backend/**" , "/front/**" }; @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; String requestURI = request.getRequestURI(); log.info("拦截到请求:{}" ,requestURI); boolean check = check(requestURI); if (check) { log.info("本次请求{}不需要处理" ,requestURI); filterChain.doFilter(request, response); return ; } if (request.getSession().getAttribute("employee" ) != null ) { log.info("用户{}已登录" ,request.getSession().getAttribute("employee" )); filterChain.doFilter(request, response); return ; } log.info("用户未登录" ); response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN" ))); } public boolean check (String requestURI) { for (String url : urls) { boolean match = PATH_MATCHER.match(url, requestURI); if (match == true ) return true ; } return false ; } }
新增员工功能
编写新增员工的业务逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @PostMapping public R<String> save (HttpServletRequest request, @RequestBody Employee employee) { log.info("新增员工信息:{}" , employee.toString()); employee.setPassword(DigestUtils.md5DigestAsHex("123456" .getBytes())); employee.setCreateTime(LocalDateTime.now()); employee.setUpdateTime(LocalDateTime.now()); Long empId = (Long) request.getSession().getAttribute("employee" ); employee.setCreateUser(empId); employee.setUpdateUser(empId); employeeService.save(employee); return R.success("新增员工成功" ); }
上面的代码可能存在问题,加入新增的员工用户名如果重复,则会报异常,因此需要对异常进行处理。
编写全局异常处理⭐ 通常有两种处理方式:
使用全局异常捕获
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.nuaa.reggie.common;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;import java.sql.SQLIntegrityConstraintViolationException;@ControllerAdvice(annotations = {RestController.class, Controller.class}) @ResponseBody @Slf4j public class GlobalExceptionHandler { @ExceptionHandler(SQLIntegrityConstraintViolationException.class) public R<String> exceptionHandler (SQLIntegrityConstraintViolationException ex) { log.error(ex.getMessage()); if (ex.getMessage().contains("Duplicate entry" )) { String[] split = ex.getMessage().split(" " ); String msg = split[2 ] + "已存在" ; return R.error(msg); } return R.error("未知错误" ); } }
员工信息分页查询 代码开发流程
配置mybatis-plus分页插件⭐ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.nuaa.reggie.config;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor () { MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor (); mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor ()); return mybatisPlusInterceptor; } }
完成员工信息分页查询的业务逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @GetMapping("/page") public R<Page> getEmployee (int page, int pageSize, String name) { log.info("page = {}, pageSize = {}, name = {}" , page, pageSize, name); Page<Employee> pageInfo = new Page (page, pageSize); LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.like(StringUtils.isNotEmpty(name), Employee::getName, name); queryWrapper.orderByDesc(Employee::getUpdateTime); employeeService.page(pageInfo, queryWrapper); return R.success(pageInfo); }
启用/禁用员工账号 代码开发
代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @PutMapping public R<String> update (HttpServletRequest request, @RequestBody Employee employee) { log.info(employee.toString()); Long empId = (Long) request.getSession().getAttribute("employee" ); employee.setUpdateTime(LocalDateTime.now()); employee.setUpdateUser(empId); employeeService.updateById(employee); return R.success("员工信息修改成功" ); }
存在的问题 数据库中的id值是19位的long型数据,发送给前端页面时,前端js损失了精度,只保证了前16位数字,所以修改时获取的id和数据库中的不一致。
解决办法是,服务端给页面响应数据时,将 long型的数据统一转为string类型。
配置对象映射器⭐ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package com.nuaa.reggie.common;import com.fasterxml.jackson.databind.DeserializationFeature;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.databind.module .SimpleModule;import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;import java.math.BigInteger;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.LocalTime;import java.time.format.DateTimeFormatter;import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;public class JacksonObjectMapper extends ObjectMapper { public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd" ; public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss" ; public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss" ; public JacksonObjectMapper () { super (); this .configure(FAIL_ON_UNKNOWN_PROPERTIES, false ); this .getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); SimpleModule simpleModule = new SimpleModule () .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer (DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))) .addDeserializer(LocalDate.class, new LocalDateDeserializer (DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))) .addDeserializer(LocalTime.class, new LocalTimeDeserializer (DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))) .addSerializer(BigInteger.class, ToStringSerializer.instance) .addSerializer(Long.class, ToStringSerializer.instance) .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer (DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))) .addSerializer(LocalDate.class, new LocalDateSerializer (DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))) .addSerializer(LocalTime.class, new LocalTimeSerializer (DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))); this .registerModule(simpleModule); } }
扩展消息转换器 在项目启动时就会调用
1 2 3 4 5 6 7 8 9 10 11 12 13 @Override protected void extendMessageConverters (List<HttpMessageConverter<?>> converters) { MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter (); messageConverter.setObjectMapper(new JacksonObjectMapper ()); converters.add(0 , messageConverter); }
编辑员工信息 根据员工 id查询员工信息
1 2 3 4 5 6 7 8 9 10 11 12 13 @GetMapping("/{id}") public R<Employee> getById (@PathVariable Long id) { log.info("根据id查询员工信息" ); Employee employee = employeeService.getById(id); if (employee != null ) return R.success(employee); return R.error("没有查询到对应员工信息" ); }
第三章 分类管理 公共字段自动填充 公共字段 当若干表中存在相同字段,例如创建时间、创建人、修改时间、修改人等字段,则将这些字段称为公共字段,因此需要将这些公共字段在某个地方统一处理,来简化开发。可以使用mybatis-plus提供的公共字段自动填充功能。
实现步骤
步骤1:在相应的实体类中添加@TableField
注解
1 2 3 4 5 6 7 8 9 10 11 12 @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;
步骤2:自定义元数据对象处理器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package com.nuaa.reggie.common;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;import lombok.extern.slf4j.Slf4j;import org.apache.ibatis.reflection.MetaObject;import org.springframework.stereotype.Component;import java.time.LocalDateTime;@Component @Slf4j public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill (MetaObject metaObject) { log.info("公共字段自动填充【insert】....." ); log.info(metaObject.toString()); metaObject.setValue("createTime" , LocalDateTime.now()); metaObject.setValue("updateTime" , LocalDateTime.now()); metaObject.setValue("createUser" , new Long (1 )); metaObject.setValue("updateUser" , new Long (1 )); } @Override public void updateFill (MetaObject metaObject) { log.info("公共字段自动填充【update】....." ); log.info(metaObject.toString()); metaObject.setValue("updateTime" , LocalDateTime.now()); metaObject.setValue("updateUser" , new Long (1 )); } }
功能完善⭐ 上述代码中updateUser
和createUser
两个字段的值是写死的,但是在实际中,需要获取当前执行操作的用户id。因此需要将这部分功能进行完善。
可以使用ThreadLocal
来解决此类问题,它是jdk提供的一个类。
ThreadLocal
介绍
具体实现方法 :在LoginCheckFilter
的doFilter
方法中获取当前登录用户的id,并调用ThreadLocal
的set
方法来设置当前线程局部变量的值(用户id),然后在MyMetaObjectHandler
的updateFill
方法中调用ThreadLocal
的get
方法来获得当前线程所对应的线程局部变量的值。
实现步骤
步骤1:编写工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.nuaa.reggie.common;public class BaseContext { private static ThreadLocal<Long> threadLocal = new ThreadLocal <>(); public static void setCurrentId (Long id) { threadLocal.set(id); } public static Long getCurrentId () { return threadLocal.get(); } }
步骤2:绑定当前线程的用户id
步骤3:取出绑定的线程id
新增分类 导入实体类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 package com.nuaa.reggie.entity;import com.baomidou.mybatisplus.annotation.FieldFill;import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.annotation.TableField;import com.baomidou.mybatisplus.annotation.TableId;import lombok.Data;import lombok.Getter;import lombok.Setter;import java.io.Serializable;import java.time.LocalDateTime;@Data public class Category implements Serializable { private static final long serialVersionUID = 1L ; private Long id; 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; }
创建mapper、service、controller 1 2 3 4 5 6 7 8 9 10 11 12 13 package com.nuaa.reggie.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.nuaa.reggie.entity.Category;import org.apache.ibatis.annotations.Mapper;@Mapper public interface CategoryMapper extends BaseMapper <Category> {}
1 2 3 4 5 6 7 8 9 10 11 12 package com.nuaa.reggie.service;import com.baomidou.mybatisplus.extension.service.IService;import com.nuaa.reggie.entity.Category;public interface CategoryService extends IService <Category> {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.nuaa.reggie.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.nuaa.reggie.entity.Category;import com.nuaa.reggie.mapper.CategoryMapper;import com.nuaa.reggie.service.CategoryService;import org.springframework.stereotype.Service;@Service public class CategoryServiceImpl extends ServiceImpl <CategoryMapper, Category> implements CategoryService {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.nuaa.reggie.controller;import com.nuaa.reggie.service.CategoryService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@Slf4j @RestController @RequestMapping("/category") public class CategoryController { @Autowired private CategoryService categoryService; }
代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 @PostMapping public R<String> add (@RequestBody Category category) { log.info("category: {}" , category); categoryService.save(category); return R.success("新增分类成功" ); }
分类信息分页查询 和之前员工信息分页查询类似
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @GetMapping("/page") public R<Page> getCategory (int page, int pageSize) { log.info("page: {}, pageInfo: {}" , page, pageSize); Page<Category> pageInfo = new Page (page, pageSize); LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.orderByAsc(Category::getSort); categoryService.page(pageInfo, queryWrapper); return R.success(pageInfo); }
删除分类 需求分析 在分类管理列表页面,可以对某个分类进行删除操作;需要注意的是当分类关联了菜品或者套餐时,此分类不允许删除。
代码实现 1 2 3 4 5 6 7 8 9 10 11 @DeleteMapping public R<String> delete (Long id) { log.info("删除的id是: {}" , id); categoryService.removeById(id); return R.success("删除成功" ); }
代码完善⭐
创建两个实体类:菜品Dish,套餐Detmeal
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 package com.nuaa.reggie.entity;import com.baomidou.mybatisplus.annotation.FieldFill;import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.annotation.TableField;import com.baomidou.mybatisplus.annotation.TableId;import lombok.Data;import java.io.Serializable;import java.math.BigDecimal;import java.time.LocalDateTime;@Data public class Dish implements Serializable { private static final long serialVersionUID = 1L ; private Long id; private String name; private Long categoryId; private BigDecimal price; private String code; private String image; private String description; private Integer status; private Integer sort; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; @TableField(fill = FieldFill.INSERT) private Long createUser; @TableField(fill = FieldFill.INSERT_UPDATE) private Long updateUser; private Integer isDeleted; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package com.nuaa.reggie.entity;import com.baomidou.mybatisplus.annotation.FieldFill;import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.annotation.TableField;import com.baomidou.mybatisplus.annotation.TableId;import lombok.Data;import java.io.Serializable;import java.math.BigDecimal;import java.time.LocalDateTime;@Data public class Setmeal implements Serializable { private static final long serialVersionUID = 1L ; private Long id; private Long categoryId; private String name; private BigDecimal price; private Integer status; private String code; private String description; private String image; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; @TableField(fill = FieldFill.INSERT) private Long createUser; @TableField(fill = FieldFill.INSERT_UPDATE) private Long updateUser; private Integer isDeleted; }
实现对应的mapper、service、controller接口或者类
代码完善
首先在CategoryServiceImpl
中重新写remove
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Override public void remove (Long id) { LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper <>(); dishLambdaQueryWrapper.eq(Dish::getCategoryId, id); int count1 = dishService.count(dishLambdaQueryWrapper); if (count1 > 0 ) { throw new CustomException ("当前分类下已经关联菜品,不能删除" ); } LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper <>(); setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId, id); int count2 = setmealService.count(setmealLambdaQueryWrapper); if (count2 > 0 ) { throw new CustomException ("当前分类下已经关联套餐,不能删除" ); } super .removeById(id); }
然后进行异常处理,先自定义一个异常处理函数
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.nuaa.reggie.common;public class CustomException extends RuntimeException { public CustomException (String message) { super (message); } }
再在全局的异常处理函数GlobalExceptionHandler
中加入方法,主要是为了将异常信息显示在页面上。
1 2 3 4 5 6 7 8 9 10 @ExceptionHandler(CustomException.class) public R<String> exceptionHandler (CustomException ex) { log.error(ex.getMessage()); return R.error(ex.getMessage()); }
修改分类 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 @PutMapping public R<String> update (@RequestBody Category category) { log.info("修改分类信息:{}" , category); categoryService.updateById(category); return R.success("修改成功" ); }
第四章 菜品管理 文件上传下载⭐⭐ 文件上传❗ 页面上传组件设置
服务端接收文件配置
文件下载
代码实现 在resources/application.yml
中添加配置。
实现代码接收并保存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 package com.nuaa.reggie.controller;import com.nuaa.reggie.common.R;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Value;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.multipart.MultipartFile;import java.io.File;import java.io.IOException;import java.util.UUID;@RestController @RequestMapping("/common") @Slf4j public class CommonController { @Value("${reggie.path}") private String basePath; @PostMapping("/upload") public R<String> upload (MultipartFile file) { log.info(file.toString()); String originalFilename = file.getOriginalFilename(); String suffix = originalFilename.substring(originalFilename.lastIndexOf("." )); String fileName = UUID.randomUUID().toString() + suffix; File dir = new File (basePath); if (!dir.exists()) { dir.mkdirs(); } try { file.transferTo(new File (basePath + fileName)); } catch (IOException e) { throw new RuntimeException (e); } return R.success(fileName); } @GetMapping("/download") public void download (String name, HttpServletResponse response) { try { FileInputStream fileInputStream = new FileInputStream (new File (basePath + name)); ServletOutputStream outputStream = response.getOutputStream(); response.setContentType("image/jpeg" ); int len = 0 ; byte [] bytes = new byte [1024 ]; while ((len = fileInputStream.read(bytes)) != -1 ) { outputStream.write(bytes, 0 , len); outputStream.flush(); } outputStream.close(); fileInputStream.close(); } catch (Exception e) { e.printStackTrace(); } } }
新增菜品 导入DishFlavor
菜品口味实体类。创建对应的mapper
、service
、controller
类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 package com.nuaa.reggie.entity;import com.baomidou.mybatisplus.annotation.FieldFill;import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.annotation.TableField;import com.baomidou.mybatisplus.annotation.TableId;import lombok.Data;import java.io.Serializable;import java.time.LocalDateTime;@Data public class DishFlavor implements Serializable { private static final long serialVersionUID = 1L ; private Long id; private Long dishId; private String name; private String value; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; @TableField(fill = FieldFill.INSERT) private Long createUser; @TableField(fill = FieldFill.INSERT_UPDATE) private Long updateUser; private Integer isDeleted; }
交互过程
步骤1:先完成菜品分类信息的显示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @GetMapping("/list") public R<List<Category>> list (Category category) { LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(category.getType() != null , Category::getType, category.getType()); queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime); List<Category> categories = categoryService.list(queryWrapper); return R.success(categories); }
步骤2:图片上传下载,之前已经完成。
步骤3:菜品信息保存
因为前端传递的信息包含数据库中两个表,所以需要重新导入一个实体类DishDto
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.nuaa.reggie.dto;import com.nuaa.reggie.entity.Dish;import com.nuaa.reggie.entity.DishFlavor;import lombok.Data;import java.util.ArrayList;import java.util.List;@Data public class DishDto extends Dish { private List<DishFlavor> flavors = new ArrayList <>(); private String categoryName; private Integer copies; }
因为需要操作两张表,所以需要开启事务支持 ⭐,首先需要在启动类中添加@EnableTransactionManagement
注解, 开启事务支持。然后在DishServiceImpl
中实现具体的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Transactional public void saveWithFlavor (DishDto dishDto) { this .save(dishDto); Long dishId = dishDto.getId(); List<DishFlavor> flavors = dishDto.getFlavors(); flavors.stream().map((item) -> { item.setDishId(dishId); return item; }).collect(Collectors.toList()); dishFlavorService.saveBatch(dishDto.getFlavors()); }
菜品信息分页查询
代码实现⭐
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 @GetMapping("/page") public R<Page> getDish (int page, int pageSize, String name) { Page<Dish> pageInfo = new Page <>(page, pageSize); Page<DishDto> dishDtoPage = new Page <>(); LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.like(name != null , DishDto::getName, name); queryWrapper.orderByDesc(Dish::getUpdateTime); dishService.page(pageInfo, queryWrapper); BeanUtils.copyProperties(pageInfo, dishDtoPage, "records" ); List<Dish> records = pageInfo.getRecords(); List<DishDto> list = records.stream().map((item) ->{ DishDto dishDto = new DishDto (); BeanUtils.copyProperties(item, dishDto); Long categoryId = item.getCategoryId(); Category category = categoryService.getById(categoryId); String categoryName = category.getName(); dishDto.setCategoryName(categoryName); return dishDto; }).collect(Collectors.toList()); dishDtoPage.setRecords(list); return R.success(dishDtoPage); }
修改菜品
菜品信息回显 需要查询两张表:dish
和dish_flavor
两张表,所以在DishService
中实现这个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Override public DishDto geByIdWithFlavor (Long id) { Dish dish = this .getById(id); DishDto dishDto = new DishDto (); BeanUtils.copyProperties(dish, dishDto); LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(DishFlavor::getDishId, dish.getId()); List<DishFlavor> dishFlavors = dishFlavorService.list(queryWrapper); dishDto.setFlavors(dishFlavors); return dishDto; }
实现对应controller
层代码
1 2 3 4 5 6 7 8 9 10 @GetMapping("/{id}") public R<DishDto> getById (@PathVariable Long id) { DishDto dishDto = dishService.geByIdWithFlavor(id); return R.success(dishDto); }
菜品信息修改 同样是修改两个表,同时还需要开启事务@Transactional
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Override @Transactional public void updateWithFlavor (DishDto dishDto) { this .updateById(dishDto); LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(DishFlavor::getDishId, dishDto.getId()); dishFlavorService.remove(queryWrapper); List<DishFlavor> flavors = dishDto.getFlavors(); flavors.stream().map((item) -> { item.setDishId(dishDto.getId()); return item; }).collect(Collectors.toList()); dishFlavorService.saveBatch(flavors); }
controller
层代码实现
1 2 3 4 5 6 7 8 9 10 @PutMapping public R<String> update (@RequestBody DishDto dishDto) { dishService.updateWithFlavor(dishDto); return R.success("菜品信息修改成功" ); }
第五章 套餐管理 新增套餐 需要两张表setmeal
和setmeal_dish
表
实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package com.nuaa.reggie.entity;import com.baomidou.mybatisplus.annotation.FieldFill;import com.baomidou.mybatisplus.annotation.TableField;import lombok.Data;import java.io.Serializable;import java.math.BigDecimal;import java.time.LocalDateTime;@Data public class SetmealDish implements Serializable { private static final long serialVersionUID = 1L ; private Long id; private Long setmealId; private Long dishId; private String name; private BigDecimal price; private Integer copies; private Integer sort; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; @TableField(fill = FieldFill.INSERT) private Long createUser; @TableField(fill = FieldFill.INSERT_UPDATE) private Long updateUser; private Integer isDeleted; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.nuaa.reggie.dto;import com.nuaa.reggie.entity.Setmeal;import com.nuaa.reggie.entity.SetmealDish;import lombok.Data;import java.util.List;@Data public class SetmealDto extends Setmeal { private List<SetmealDish> setmealDishes; private String categoryName; }
对应的mapper、service
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.nuaa.reggie.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.nuaa.reggie.entity.SetmealDish;import org.apache.ibatis.annotations.Mapper;@Mapper public interface SetmealDishMapper extends BaseMapper <SetmealDish> {}
1 2 3 4 5 6 7 8 9 10 11 package com.nuaa.reggie.service;import com.baomidou.mybatisplus.extension.service.IService;import com.nuaa.reggie.entity.SetmealDish;public interface SetmealDishService extends IService <SetmealDish> {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.nuaa.reggie.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.nuaa.reggie.entity.SetmealDish;import com.nuaa.reggie.mapper.SetmealDishMapper;import com.nuaa.reggie.service.SetmealDishService;import org.springframework.stereotype.Service;@Service public class SetmealDishServiceImpl extends ServiceImpl <SetmealDishMapper, SetmealDish> implements SetmealDishService {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.nuaa.reggie.controller;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController @RequestMapping("/setmeal") @Slf4j public class SetmealController { @Autowired private SetmealService setmealService; @Autowired private SetmealDishService setmealDishService; }
代码开发
步骤1:/category/list?type=2
在CategoryController
中已经实现了对应的功能。
步骤2:/category/list?type=1
在CategoryController
中已经实现了对应的功能。
步骤3:/dish/list?categoryId=
请求菜品分类查询对应的菜品数据并展示到菜品中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @GetMapping("/list") public R<List<Dish>> list (Dish dish) { LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(Dish::getStatus, 1 ); queryWrapper.eq(dish.getCategoryId() != null , Dish::getCategoryId, dish.getCategoryId()); queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime); List<Dish> list = dishService.list(queryWrapper); return R.success(list); }
步骤4:/common/upload
已经在CommonController
实现了
步骤5:/common/download
已经在CommonController
实现。
步骤6:/setmeal
在SetmealController
中实现,因为需要同时向两个表中添加数据,所以需要现在SetmealService
中实现方法。然后再controller
中调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Override public void saveWithDish (SetmealDto setmealDto) { this .save(setmealDto); List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes(); setmealDishes.stream().map((item) ->{ item.setSetmealId(setmealDto.getId()); return item; }).collect(Collectors.toList()); setmealDishService.saveBatch(setmealDishes); }
1 2 3 4 5 6 7 8 9 10 11 @PostMapping public R<String> save (@RequestBody SetmealDto setmealDto) { log.info("套餐信息:{}" , setmealDto); setmealService.saveWithDish(setmealDto); return R.success("套餐添加成功" ); }
套餐信息分页查询 实现方法和菜品页面分页查询一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @GetMapping("/page") public R<Page> page (int page, int pageSize, String name) { Page<Setmeal> pageInfo = new Page <>(page, pageSize); Page<SetmealDto> setmealDtoPage = 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, setmealDtoPage, "records" ); List<Setmeal> records = pageInfo.getRecords(); List<SetmealDto> list = records.stream().map((item) -> { SetmealDto setmealDto = new SetmealDto (); BeanUtils.copyProperties(item, setmealDto); Long categoryId = item.getCategoryId(); Category category = categoryService.getById(categoryId); if (category != null ) { String categoryName = category.getName(); setmealDto.setCategoryName(categoryName); } return setmealDto; }).collect(Collectors.toList()); setmealDtoPage.setRecords(list); return R.success(setmealDtoPage); }
删除套餐 需要删除两个表中的数据,所以需要现在service中实现具体的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Override @Transactional public void removeWithDish (List<Long> ids) { LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.in(Setmeal::getId, ids); queryWrapper.eq(Setmeal::getStatus, 1 ); int count = this .count(queryWrapper); if (count > 0 ) { throw new CustomException ("套餐正在售卖中,不能删除" ); } this .removeByIds(ids); LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper <>(); lambdaQueryWrapper.in(SetmealDish::getSetmealId, ids); setmealDishService.remove(lambdaQueryWrapper); }
controller层代码实现
1 2 3 4 5 6 7 8 9 10 @DeleteMapping public R<String> delete (@RequestParam List<Long> ids) { setmealService.removeWithDish(ids); return R.success("套餐删除成功" ); }
第六章 手机验证码登录 短信发送 服务开启
本项目使用阿里云验证码服务
代码开发 步骤1:导入maven
坐标
1 2 3 4 5 6 7 8 9 10 <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 >
步骤2:调入API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package com.nuaa.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 { 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(); } } }
业务功能开发
交互过程梳理
代码开发 前期准备
导入user
表对应的实体类User
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package com.nuaa.reggie.entity;import lombok.Data;import java.time.LocalDateTime;import java.util.Date;import java.util.List;import java.io.Serializable;import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.annotation.TableId;@Data public class User implements Serializable { private static final long serialVersionUID = 1L ; private Long id; private String name; private String phone; private String sex; private String idNumber; private String avatar; private Integer status; }
创建对应的mapper、service、controller类
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.nuaa.reggie.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.nuaa.reggie.entity.User;import org.apache.ibatis.annotations.Mapper;@Mapper public interface UserMapper extends BaseMapper <User> {}
1 2 3 4 5 6 7 8 9 10 11 12 package com.nuaa.reggie.service;import com.baomidou.mybatisplus.extension.service.IService;import com.nuaa.reggie.entity.User;import com.nuaa.reggie.mapper.UserMapper;public interface UserService extends IService <User> {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.nuaa.reggie.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.nuaa.reggie.entity.User;import com.nuaa.reggie.mapper.UserMapper;import com.nuaa.reggie.service.UserService;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Service;@Service @Slf4j public class UserServiceImpl extends ServiceImpl <UserMapper, User> implements UserService {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.nuaa.reggie.controller;import com.nuaa.reggie.service.UserService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;@RestController @Slf4j @RequestMapping("/user") public class UserController { @Autowired private UserService userService; }
导入工具类:短信发送工具类SMSUtils
、随机生成验证码工具类ValidateCodeUtils
⭐
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package com.nuaa.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 { public static void sendMessage (String signName, String templateCode,String phoneNumbers,String param) { DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou" , "LTAI5tCmkkEhE21W4tvjS1dd" , "xU2cXGMutcASkZjR185QMmORRpiqSA" ); 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(); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package com.nuaa.reggie.utils;import java.util.Random;public class ValidateCodeUtils { public static Integer generateValidateCode (int length) { Integer code = null ; if (length == 4 ){ code = new Random ().nextInt(9999 ); if (code < 1000 ){ code = code + 1000 ; } }else if (length == 6 ){ code = new Random ().nextInt(999999 ); if (code < 100000 ){ code = code + 100000 ; } }else { throw new RuntimeException ("只能生成4位或6位数字验证码" ); } return code; } 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
1 2 3 4 5 6 7 8 9 10 private String[] urls = new String []{ "/employee/login" , "/employee/logout" , "/backend/**" , "/front/**" , "/common/**" , "/user/sendMsg" , "/user/login" };
在doFilter
函数中添加用户登录状态判断
1 2 3 4 5 6 7 8 9 if (request.getSession().getAttribute("user" ) != null ) { log.info("用户{}已登录" ,request.getSession().getAttribute("user" )); Long userId = (Long) request.getSession().getAttribute("user" ); BaseContext.setCurrentId(userId); filterChain.doFilter(request, response); return ; }
在UserController
中添加发送验证码消息业务代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @PostMapping("/sendMsg") public R<String> sendMsg (@RequestBody User user, HttpSession session) { String phone = user.getPhone(); if (StringUtils.isNotEmpty(phone)) { String code = ValidateCodeUtils.generateValidateCode(4 ).toString(); log.info("code: {}" , code); session.setAttribute(phone, code); return R.success("手机验证码发送成功" ); } return R.error("手机验证码发送失败" ); }
实现对应的用户登录功能,需要判断数据库中是否有该用户
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @PostMapping("/login") public R<User> login (@RequestBody Map map, HttpSession session) { log.info("map: {}" , map.toString()); String phone = map.get("phone" ).toString(); String code = map.get("code" ).toString(); Object codeInSession = session.getAttribute(phone); if (codeInSession != null && codeInSession.equals(code)) { LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(User::getPhone, phone); User user = userService.getOne(queryWrapper); if (user == null ) { user = new User (); user.setPhone(phone); user.setStatus(1 ); userService.save(user); } session.setAttribute("user" , user.getId()); return R.success(user); } return R.error("登录失败" ); }
第七章 移动端菜品管理 导入用户地址簿相关功能代码 需求分析 地址簿,指的是移动端消费者用户的地址信息,用户登录成功后可以维护自己的地址信息。同一个用户可以有多个地址信息,但是只能有一个默认地址 。
数据模型 用户的地址信息会存储在address_book表,即地址簿表中。具体表结构如下:
导入功能代码 功能代码清单:
实体类AddressBook(直接从课程资料中导入即可)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 package com.nuaa.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; private Long userId; private String consignee; private String phone; 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; 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; }
Mapper接口AddressBookMapper
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.nuaa.reggie.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.nuaa.reggie.entity.AddressBook;import org.apache.ibatis.annotations.Mapper;@Mapper public interface AddressBookMapper extends BaseMapper <AddressBook> {}
业务层接口AddressBookService
1 2 3 4 5 6 7 8 9 10 11 package com.nuaa.reggie.service;import com.baomidou.mybatisplus.extension.service.IService;import com.nuaa.reggie.entity.AddressBook;public interface AddressBookService extends IService <AddressBook> {}
业务层实现类AddressBookServicelmpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.nuaa.reggie.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.nuaa.reggie.entity.AddressBook;import com.nuaa.reggie.mapper.AddressBookMapper;import com.nuaa.reggie.service.AddressBookService;import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Service;@Service @Slf4j public class AddressBookServiceImpl extends ServiceImpl <AddressBookMapper, AddressBook> implements AddressBookService {}
控制层AddressBookController(直接从课程资料中导入即可)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 package com.nuaa.reggie.controller;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;import com.nuaa.reggie.common.BaseContext;import com.nuaa.reggie.common.R;import com.nuaa.reggie.entity.AddressBook;import com.nuaa.reggie.service.AddressBookService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;import java.util.List;@Slf4j @RestController @RequestMapping("/addressBook") public class AddressBookController { @Autowired private AddressBookService addressBookService; @PostMapping public R<AddressBook> save (@RequestBody AddressBook addressBook) { addressBook.setUserId(BaseContext.getCurrentId()); log.info("addressBook:{}" , addressBook); addressBookService.save(addressBook); return R.success(addressBook); } @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); addressBook.setIsDefault(1 ); addressBookService.updateById(addressBook); return R.success(addressBook); } @GetMapping("/{id}") public R get (@PathVariable Long id) { AddressBook addressBook = addressBookService.getById(id); if (addressBook != null ) { return R.success(addressBook); } else { return R.error("没有找到该对象" ); } } @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); if (null == addressBook) { return R.error("没有找到该对象" ); } else { return R.success(addressBook); } } @GetMapping("/list") public R<List<AddressBook>> list (AddressBook addressBook) { addressBook.setUserId(BaseContext.getCurrentId()); log.info("addressBook:{}" , addressBook); LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(null != addressBook.getUserId(), AddressBook::getUserId, addressBook.getUserId()); queryWrapper.orderByDesc(AddressBook::getUpdateTime); return R.success(addressBookService.list(queryWrapper)); } }
功能测试
菜品展示 需求分析 用户登录成功后跳转到系统首页,在首页需要根据分类来展示菜品和套餐。如果菜品设置了口味信息需要展示 [选择规格] 按钮,否则显示 [+] 按钮。
代码开发 梳理交互过程 在开发代码之前,需要梳理一下前端页面和服务端的交互过程:
1、页面(front/index.html
)发送ajax请求,获取分类数据(菜品分类和套餐分类)
2、页面发送ajax请求,获取第一个分类下的菜品或者套餐
开发菜品展示功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。
注意 :首页加载完成后,还发送了一次ajax请求用于加载购物车数据,此处可以将这次请求的地址暂时修改一下,从静态json文件获取数据,等后续开发购物车功能时再修改回来,如下:
1 2 3 4 5 6 7 8 9 function cartListApi (data ) { return $axios({ 'url' : '/front/cartData.json' , 'method' : 'get' , params :{...data} }) }
cartData.json:
1 { "code" : 1 , "msg" : null , "data" : [ ] , "map" : { } }
改造DishController
中的list
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @GetMapping("/list") public R<List<DishDto>> list (Dish dish) { LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(Dish::getStatus, 1 ); queryWrapper.eq(dish.getCategoryId() != null , Dish::getCategoryId, dish.getCategoryId()); queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime); List<Dish> list = dishService.list(queryWrapper); List<DishDto> dishDtoList = list.stream().map((item) -> { DishDto dishDto = new DishDto (); BeanUtils.copyProperties(item, dishDto); Long categoryId = item.getCategoryId(); Category category = categoryService.getById(categoryId); if (category != null ) { String categoryName = category.getName(); dishDto.setCategoryName(categoryName); } Long dishId = item.getId(); LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper <>(); lambdaQueryWrapper.eq(DishFlavor::getDishId, dishId); List<DishFlavor> dishFlavorList = dishFlavorService.list(lambdaQueryWrapper); dishDto.setFlavors(dishFlavorList); return dishDto; }).collect(Collectors.toList()); return R.success(dishDtoList); }
在SetmealController
里添加list
方法显示套餐信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @GetMapping("/list") public R<List<Setmeal>> list (Setmeal setmeal) { 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); }
运行效果
购物车 需求分析 移动端用户可以将菜品或者套餐添加到购物车。对于菜品来说,如果设置了口味信息,则需要选择规格后才能加入购物车;对于套餐来说,可以直接点击 [+] 将当前套餐加入购物车。在购物车中可以修改菜品和套餐的数量,也可以清空购物车。
数据模型
代码开发 梳理交互过程 在开发代码之前,需要梳理一下购物车操作时前端页面和服务端的交互过程:
点击 [加入购物车] 或者 [+] 按钮,页面发送ajax请求,请求服务端,将菜品或者套餐添加到购物车
点击购物车图标,页面发送ajax请求,请求服务端查询购物车中的菜品和套餐
点击清空购物车按钮,页面发送ajax请求,请求服务端来执行清空购物车操作
开发购物车功能,其实就是在服务端编写代码去处理前端页面发送的这3次请求即可。
准备工作 在开发业务功能前,先将需要用到的类和接口基本结构创建好:
实体类ShoppingCart(直接从课程资料中导入即可)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package com.nuaa.reggie.entity;import lombok.Data;import java.io.Serializable;import java.math.BigDecimal;import java.time.LocalDateTime;@Data public class ShoppingCart implements Serializable { private static final long serialVersionUID = 1L ; private Long id; private String name; private Long userId; private Long dishId; private Long setmealId; private String dishFlavor; private Integer number; private BigDecimal amount; private String image; private LocalDateTime createTime; }
Mapper接口ShoppingCartMapper
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.nuaa.reggie.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.nuaa.reggie.entity.ShoppingCart;import org.apache.ibatis.annotations.Mapper;@Mapper public interface ShoppingCartMapper extends BaseMapper <ShoppingCart> {}
业务层接口ShoppingCartService
1 2 3 4 5 6 7 8 9 10 11 package com.nuaa.reggie.service;import com.baomidou.mybatisplus.extension.service.IService;import com.nuaa.reggie.entity.ShoppingCart;public interface ShoppingCartService extends IService <ShoppingCart> {}
业务层实现类ShoppingCartServicelmpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.nuaa.reggie.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.nuaa.reggie.entity.ShoppingCart;import com.nuaa.reggie.mapper.ShoppingCartMapper;import com.nuaa.reggie.service.ShoppingCartService;import org.springframework.stereotype.Service;@Service public class ShoppingCartServicelmpl extends ServiceImpl <ShoppingCartMapper, ShoppingCart> implements ShoppingCartService {}
控制层ShoppingCartController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.nuaa.reggie.controller;import com.nuaa.reggie.service.ShoppingCartService;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@Slf4j @RestController @RequestMapping("/shoppingcart") public class ShoppingCartController { @Autowired private ShoppingCartService shoppingCartService; }
添加购物车 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @PostMapping("/add") public R<ShoppingCart> add (@RequestBody ShoppingCart shoppingCart) { log.info("购物车数据:{}" , shoppingCart); Long currentId = BaseContext.getCurrentId(); shoppingCart.setUserId(currentId); Long dishId = shoppingCart.getDishId(); LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(ShoppingCart::getUserId, currentId); if (dishId != null ) { queryWrapper.eq(ShoppingCart::getDishId, dishId); } else { queryWrapper.eq(ShoppingCart::getSetmealId, shoppingCart.getSetmealId()); } ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper); if (cartServiceOne != null ) { Integer number = cartServiceOne.getNumber(); cartServiceOne.setNumber(number + 1 ); shoppingCartService.updateById(cartServiceOne); } else { shoppingCart.setNumber(1 ); shoppingCartService.save(shoppingCart); cartServiceOne = shoppingCart; } return R.success(cartServiceOne); }
查看购物车 把前端数据改回来
1 2 3 4 5 6 7 8 9 function cartListApi (data) { return $axios({ 'url' : '/shoppingCart/list' , 'method' : 'get' , params:{...data} }) }
查看购物车业务代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 @GetMapping("/list") public R<List<ShoppingCart>> list () { log.info("查看购物车" ); LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId()); queryWrapper.orderByDesc(ShoppingCart::getCreateTime); List<ShoppingCart> list = shoppingCartService.list(queryWrapper); return R.success(list); }
清空购物车 1 2 3 4 5 6 7 8 9 10 11 @DeleteMapping("/clean") public R<String> clean () { LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId()); shoppingCartService.remove(queryWrapper); return R.success("清空购物车成功" ); }
减少菜品 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @PostMapping("/sub") public R<ShoppingCart> sub (@RequestBody ShoppingCart shoppingCart) { Long dishId = shoppingCart.getDishId(); Long setmealId = shoppingCart.getSetmealId(); LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId()); if (dishId != null ) { queryWrapper.eq(ShoppingCart::getDishId, dishId); } else { queryWrapper.eq(ShoppingCart::getSetmealId, setmealId); } ShoppingCart one = shoppingCartService.getOne(queryWrapper); Integer number = one.getNumber(); if (number == 1 ){ shoppingCartService.remove(queryWrapper); } else { one.setNumber(number - 1 ); shoppingCartService.updateById(one); } return R.success(one); }
用户下单 需求分析 移动端用户将菜品或者套餐加入购物车后,可以点击购物车中的 【去结算】 按钮,页面跳转到订单确认页面,点击 【去支付】 按钮则完成下单操作。
数据模型 用户下单业务对应的数据表为orders
表和order_detail
表:
orders订单表
order_detail订单明细表
代码开发 梳理交互过程 在开发代码之前,需要梳理一下用户下单操作时前端页面和服务端的交互过程:
在购物车中点击 【去结算】 按钮,页面跳转到订单确认页面
在订单确认页面,发送ajax请求,请求服务端获取当前登录用户的默认地址
在订单确认页面,发送ajax请求,请求服务端获取当前登录用户的购物车数据
在订单确认页面点击 【去支付】 按钮,发送ajax请求,请求服务端完成下单操作
开发用户下单功能,其实就是在服务端编写代码去处理前端页面发送的请求即可。
准备工作 在开发业务功能前,先将需要用到的类和接口基本结构创建好:
实体类Orders、OrderDetail(直接从课程资料中导入即可)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package com.nuaa.reggie.entity;import lombok.Data;import java.io.Serializable;import java.math.BigDecimal;import java.time.LocalDateTime;@Data public class Orders implements Serializable { private static final long serialVersionUID = 1L ; private Long id; private String number; private Integer status; private Long userId; private Long addressBookId; private LocalDateTime orderTime; private LocalDateTime checkoutTime; private Integer payMethod; private BigDecimal amount; private String remark; private String userName; private String phone; private String address; private String consignee; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package com.nuaa.reggie.entity;import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.annotation.TableId;import lombok.Data;import java.io.Serializable;import java.math.BigDecimal;@Data public class OrderDetail implements Serializable { private static final long serialVersionUID = 1L ; private Long id; private String name; private Long orderId; private Long dishId; private Long setmealId; private String dishFlavor; private Integer number; private BigDecimal amount; private String image; }
Mapper接口OrderMapper、OrderDetailMapper
业务层接口OrderService、OrderDetailService
业务层实现类OrderServicelmpl、OrderDetailServicelmpl
控制层OrderController、OrderDetailController
代码开发 在OrderService
添加submit
方法用于用户下单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 package com.nuaa.reggie.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.baomidou.mybatisplus.core.toolkit.IdWorker;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.nuaa.reggie.common.BaseContext;import com.nuaa.reggie.common.CustomException;import com.nuaa.reggie.entity.*;import com.nuaa.reggie.mapper.OrderMapper;import com.nuaa.reggie.service.*;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.core.annotation.Order;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;@Slf4j @Service public class OrderServicelmpl extends ServiceImpl <OrderMapper, Orders> implements OrderService { @Autowired private ShoppingCartService shoppingCartService; @Autowired private OrderDetailService orderDetailService; @Autowired private UserService userService; @Autowired private AddressBookService addressBookService; @Override @Transactional public void submit (Orders orders) { 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 user = userService.getById(currentId); Long addressBookId = orders.getAddressBookId(); AddressBook addressBook = addressBookService.getById(addressBookId); if (addressBook == null ) { throw new CustomException ("用户地址信息有误,不能下单" ); } long orderId = IdWorker.getId(); AtomicInteger amount = new AtomicInteger (0 ); List<OrderDetail> orderDetails = shoppingCarts.stream().map((item) -> { OrderDetail orderDetail = new OrderDetail (); orderDetail.setOrderId(orderId); orderDetail.setNumber(item.getNumber()); orderDetail.setDishFlavor(item.getDishFlavor()); orderDetail.setDishId(item.getDishId()); orderDetail.setSetmealId(item.getSetmealId()); orderDetail.setName(item.getName()); orderDetail.setImage(item.getImage()); orderDetail.setAmount(item.getAmount()); amount.addAndGet(item.getAmount().multiply(new BigDecimal (item.getNumber())).intValue()); return orderDetail; }).collect(Collectors.toList()); orders.setNumber(String.valueOf(orderId)); orders.setId(orderId); orders.setOrderTime(LocalDateTime.now()); orders.setCheckoutTime(LocalDateTime.now()); orders.setStatus(2 ); orders.setAmount(new BigDecimal (amount.get())); orders.setUserId(currentId); orders.setUserName(user.getName()); orders.setConsignee(addressBook.getConsignee()); orders.setPhone(addressBook.getPhone()); orders.setAddress((addressBook.getProvinceName()==null ?"" :addressBook.getProvinceName()) +(addressBook.getCityName()==null ?"" :addressBook.getCityName()) +(addressBook.getDistrictName()==null ?"" :addressBook.getDistrictName()) +(addressBook.getDetail()==null ?"" :addressBook.getDetail())); this .save(orders); orderDetailService.saveBatch(orderDetails); shoppingCartService.remove(queryWrapper); } }
在OrderController
的submit
方法处理post
请求实现上面的方法
1 2 3 4 5 6 7 8 9 10 11 @PostMapping("/submit") public R<String> submit (@RequestBody Orders orders) { log.info("订单数据:{}" , orders); orderService.submit(orders); return R.success("下单成功" ); }
功能测试
功能补充 用户退出 1 2 3 4 5 6 7 8 9 10 11 @PostMapping("/loginout") public R<String> logout (HttpServletRequest request) { request.getSession().removeAttribute("user" ); return R.success("退出成功" ); }
订单管理 导入OrderDto需手动添加private int sumNum;
前端会计算数量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package com.nuaa.reggie.entity;import com.nuaa.reggie.entity.OrderDetail;import com.nuaa.reggie.entity.Orders;import lombok.Data;import java.util.List;@Data public class OrdersDto extends Orders { private String userName; private String phone; private String address; private String consignee; private List<OrderDetail> orderDetails; private int sumNum; }
在OrderController
添加userPage方法
❗
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @GetMapping("/userPage") public R<Page> getOrder (int page, int pageSize) { Page<Orders> pageInfo = new Page <>(page, pageSize); Page<OrdersDto> ordersDtoPage = new Page <>(); LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(Orders::getUserId, BaseContext.getCurrentId()); queryWrapper.orderByDesc(Orders::getOrderTime); orderService.page(pageInfo, queryWrapper); BeanUtils.copyProperties(pageInfo, ordersDtoPage, "records" ); List<Orders> records = pageInfo.getRecords(); List<OrdersDto> list = records.stream().map((item) -> { OrdersDto ordersDto = new OrdersDto (); BeanUtils.copyProperties(item, ordersDto); Long id = item.getId(); Orders orders = orderService.getById(id); String number = orders.getNumber(); LambdaQueryWrapper<OrderDetail> lambdaQueryWrapper = new LambdaQueryWrapper <>(); lambdaQueryWrapper.eq(OrderDetail::getOrderId, number); List<OrderDetail> orderDetailList = orderDetailService.list(lambdaQueryWrapper); ordersDto.setOrderDetails(orderDetailList); return ordersDto; }).collect(Collectors.toList()); ordersDtoPage.setRecords(list); return R.success(ordersDtoPage); }
管理员端订单信息管理分页查询 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 @GetMapping("/page") public R<Page> page (int page, int pageSize, String number, String beginTime, String endTime) { Page<Orders> pageInfo = new Page <>(page, pageSize); Page<OrdersDto> ordersDtoPage = new Page <>(); LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.like(!StringUtils.isEmpty(number), Orders::getNumber, number); if (beginTime != null && endTime != null ) { queryWrapper.ge(Orders::getOrderTime, beginTime); queryWrapper.le(Orders::getOrderTime, endTime); } queryWrapper.orderByDesc(Orders::getOrderTime); orderService.page(pageInfo, queryWrapper); BeanUtils.copyProperties(pageInfo, ordersDtoPage); List<Orders> records = pageInfo.getRecords(); List<OrdersDto> list = records.stream().map((item) -> { OrdersDto ordersDto = new OrdersDto (); BeanUtils.copyProperties(item, ordersDto); String name = "用户" + item.getUserId(); ordersDto.setUserName(name); return ordersDto; }).collect(Collectors.toList()); ordersDtoPage.setRecords(list); return R.success(ordersDtoPage); }
外卖订单派送 在OrderController
处理post
请求修改status
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @PutMapping public R<String> send (@RequestBody Orders orders) { Long id = orders.getId(); Integer status = orders.getStatus(); LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(Orders::getId, id); Orders one = orderService.getOne(queryWrapper); one.setStatus(status); orderService.updateById(one); return R.success("派送成功" ); }
再来一单 用户可以通过该方法快速再下一单
因为设计两个表,所以需要再OrderServiceImpl
中实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @Override @Transactional public void again (Orders orders) { Long id = orders.getId(); Orders newOrders = this .getById(id); long orderId = IdWorker.getId(); newOrders.setId(orderId); newOrders.setNumber(String.valueOf(orderId)); newOrders.setOrderTime(LocalDateTime.now()); newOrders.setCheckoutTime(LocalDateTime.now()); newOrders.setStatus(2 ); LambdaQueryWrapper<OrderDetail> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(OrderDetail::getOrderId, id); List<OrderDetail> list = orderDetailService.list(queryWrapper); list.stream().map((item) -> { Long detailId = IdWorker.getId(); item.setOrderId(orderId); item.setId(detailId); return item; }).collect(Collectors.toList()); this .save(newOrders); orderDetailService.saveBatch(list); }
在OrderController
中进行调用
1 2 3 4 5 6 7 8 9 10 11 @PostMapping("/again") public R<String> again (@RequestBody Orders orders) { log.info("再来一单:{}" , orders); orderService.again(orders); return R.success("已成功下单" ); }