[TOC]
写在前面
本人参照黑马程序员瑞吉外卖视频进行学习,并完善了剩余功能。
项目搭建
主要使用的技术
后端:springboot,spring,springmvc,mybatis,mybatis-plus
前端:html,css,js,vue,elementui
搭建数据库(reggie)/表

具体表中的各个字段所代表的意义都已在数据库设计表中的注释提及
表名 |
表的描述 |
employee(员工表) |
用于存放后台管理人员的信息 |
category(分类表) |
分类表中存放的是菜品的分类和套餐的分类 |
dish(菜品表) |
存放的是菜品的信息,一个菜品必属于一个菜品分类,一个菜品分类也可以有不止一种菜品 |
dish_flavor(菜品口味表) |
存放的是菜品口味的信息,一个菜品可以有多种口味,对应着就会有多条数据是隶属于一个菜品的,其中已经用菜品的id 把对应的菜品和菜品口味关联好 |
setmeal(套餐表) |
存放的是套餐的信息,一个套餐必属于一个套餐分类,一个套餐分类也可以有不止一种套餐 |
setmeal_dish(套餐菜品对应表) |
存放的是套餐里所包含的菜品的信息(因为一个套餐是由若干个菜品相组成的),此处存放的信息就是该套餐下所包含的是哪些菜品 |
orders(订单表) |
存放的是用户下单之后的订单的简单信息(包括订单号,订单状态,收货人,联系电话,地址,支付金额,下单时间) |
order_details(订单明细表) |
存放的是用户下单之后的订单的更多信息(包含了用户购买的是哪些菜品或套餐) |
address_book(地址簿表) |
存放的是用户的地址信息,也包含默认地址等 |
shoppingcart(购物车表) |
存放的是用户通过移动端点击对应的菜品或套餐所加入的数据,清空购物车自然就是清空该用户的购物车数据 |
user(用户表) |
存放的是用户的基本信息 |
Idea中的基本配置
通过mybatis-plus来逆向创建对应的pojo,mapper,service接口和serviceImpl实体类即可。
创建通用的R类(通用返回结果类)
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
|
@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; }
}
|
设置静态资源映射
主要是让Spring可以扫描backend
和front
下的静态资源(HTML,CSS.JS)
1 2 3 4 5 6 7 8 9 10 11 12
| @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/"); registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/"); } }
|
后台员工登录登出功能
员工登录功能
员工登录功能
将页面提交过来的password
进行md5
加密
根据用户提交的用户名查询数据库
如果用户不存在则退出
密码比对,如果不成功则退出
查看账号是否已被禁用
登录成功,将员工的id
存入Session
(重点)中,(此处获得这个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
| @PostMapping("/login") public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee) {
String password = employee.getPassword(); password = DigestUtils.md5DigestAsHex(password.getBytes());
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Employee::getUsername, employee.getUsername()); Employee emp = employeeService.getOne(queryWrapper);
if (emp == null) { return R.error("登录失败"); }
if (!password.equals(emp.getPassword())) { return R.error("登录失败"); }
if (emp.getStatus() == 0) { return R.error("账号已禁用"); } request.getSession().setAttribute("employee", emp.getId()); return R.success(emp); }
|
员工登出功能
将登录时存在Session
中的id
释放出来
返回结果(注意:此时业面跳转不显示退出成功的原因是:因为显示退出成功
的html
页面已经关闭,故在登录页看不到退出成功
的显示)
1 2 3 4 5
| @PostMapping("/logout") public R<String> logout(HttpServletRequest request){ request.getSession().removeAttribute("employee"); return R.success("退出成功"); }
|
完善员工登录登出
需求分析
因为可以不通过登录而直接访问员工管理的页面,这显然是不合理的,需要通过设置一个拦截器,去让用户必须先登录才能访问员工管理页面。
获取本次请求的URI
定义不需要处理的请求路径(即一个字符串数组)
创建一个PATH_MATCHER
来比对路径上的通配符
判断本次请求是否需要处理
如果不需要处理,则直接放行
判断登录状态,如果已登录,则直接放行
如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
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
|
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*") @Slf4j public class LoginCheckFilter implements Filter{ public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse;
String requestURI = request.getRequestURI();
log.info("拦截到请求:{}",requestURI);
String[] urls = new String[]{ "/employee/login", "/employee/logout", "/backend/**", "/front/**" };
boolean check = check(urls, requestURI);
if(check){ log.info("本次请求{}不需要处理",requestURI); filterChain.doFilter(request,response); return; }
if(request.getSession().getAttribute("employee") != null){ log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee")); filterChain.doFilter(request,response); return; }
log.info("用户未登录"); response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN"))); return;
}
public boolean check(String[] urls,String requestURI){ for (String url : urls) { boolean match = PATH_MATCHER.match(url, requestURI); if(match){ return true; } } return false; } }
|
踩坑点
若没加.getSession()
则会使登录成功后一直重新回到登录页面
1 2 3
| request.getSession().setAttribute("employee", emp.getId()); return R.success(emp);
|
如果还有问题,可以尝试去清除一下浏览器缓存
新增员工
新增员工功能
需求分析:因为前端页面展示的让用户新增员工时所填的信息有限,一部分employee
分装对象中的属性,即表中的字段需要填入默认值,故该方法用于接收前端页面所传递过来的包装好的employee
对象,并将该对象存进表中。
功能分析:
设置默认密码(使用md5
加密处理)
获取登录时传入Session
中的id
添加创建人信息(该创建人为Session
中存入的id
)
添加修改人信息(该修改人为Session
中存入的id
)
添加创建时间
添加更新时间
将该对象的属性存入表中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @PostMapping public R<String> save(HttpServletRequest request, @RequestBody Employee employee) {
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
Long empId = (Long) request.getSession().getAttribute("employee"); employee.setCreateUser(empId); employee.setUpdateUser(empId); employee.setCreateTime(LocalDateTime.now()); employee.setUpdateTime(LocalDateTime.now()); employeeService.save(employee);
return R.success("新增员工成功"); }
|
完善新增员工功能
需求分析:
因为employee
表中的username
字段被设置为了唯一的约束,故在前端页面填写时输入相同的username
会抛出异常,故需要做出解决,提出错误信息。
功能实现:
配置全局异常的一个类,让所有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
|
@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("未知错误"); } }
|
员工分页查询显示到前端
分页查询功能
需求分析:
前端发送get
请求,把page
当前页,pageSize
每页显示条数,name
查询名等参数传入controller
层,后端进行分页查询和条件查询并把查询对象传回给前端
功能实现:
- 添加mybatis-plus的分页插件
- 构造分页查询器
- 构造条件查询器
- 添加过滤条件(这里使用
like
而不是eq
)
- 添加排序条件
- 执行查询,返回结果(结果中返回的
pageInfo
对象是因为查询完后会将数据封装到该对象中,并且与前端中相响应)
1 2 3 4 5 6 7 8 9 10 11 12 13
|
@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
| @GetMapping("/page") public R<Page> page(int page, int pageSize, String name) {
Page<Employee> pageInfo = new Page<>(page, pageSize); LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.like(StringUtils.isNotBlank(name),Employee::getName,name); queryWrapper.orderByDesc(Employee::getUpdateTime); employeeService.page(pageInfo,queryWrapper);
return R.success(pageInfo); }
|
补充点

该处可去前端页面修改list.html
启用/禁用员工账号
启用/禁用员工账号功能实现
需求分析:
管理员账号admin
可以对员工账号进行启用和禁用操作,而其他用户不可进行该操作
代码实现:
本质上是一个update操作,status
和id
已经从前端传入,需要额外更改的是更新人和更新时间。
1 2 3 4 5 6 7 8
| @PutMapping public R<String> update(HttpServletRequest request, @RequestBody Employee employee) { Long empId = (Long) request.getSession().getAttribute("employee"); employee.setUpdateTime(LocalDateTime.now()); employee.setUpdateUser(empId); employeeService.updateById(employee); return R.success("员工信息修改成功"); }
|
注意点
原因分析:
js
对Long
类型的数据的处理时丢失了精度,导致前端发送过来的id
与数据库中的id
不一致
解决方案:
将json
中的Long
型数据转成字符串,在配置中添加对应的方法即可。
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
|
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 14
|
@Override protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) { log.info("扩展消息转换器..."); MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter(); messageConverter.setObjectMapper(new JacksonObjectMapper()); converters.add(0,messageConverter); }
|
编辑员工信息功能
编辑员工信息功能实现
需求分析:
用户点击编辑时进入编辑界面,此时登录页面时可以看到用户原本的信息,再经过修改信息后保存到数据库。
回显功能实现(进入编辑页面可看到用户原始信息)
1 2 3 4 5 6 7 8
| @GetMapping("/{id}") public R<Employee> getById(@PathVariable Long id){ Employee employee = employeeService.getById(id); if (employee!=null){ return R.success(employee); } return R.error("没有查询到员工信息"); }
|
编辑功能实现
注意:此处直接调用的是启用/禁用员工账号时的方法,因为传入的是一个employee
对象,故该方法是一个通用的 更新方法。
公共字段自动填充
为什么要对公共字段填充
因为不论是在员工管理处还是菜品或者套餐管理处都需要添加诸如createTime
,updateTime
,createUser
,以及updateUser
这四个字段,故可以使用mybatis-plus
提供的公共字段自动填充方法,省去手动为用户填写这四个信息。
如何实现
1.在employee
表中这四个字段上添加相应属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @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;
|
注意:updateTime
和updateUser
需要在创建和更改时都做更改
2.可将原新增员工功能及更改员工信息功能处的相应的方法注释去
3.基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
创建该工具类,因为在一次Http请求中线程是唯一的,故可以通过该方式得到存入Session中存放的id。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
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(); } }
|
4.自定义元数据对象处理器(即公共字段自动填充类)
该类中重写两个方法,分别用于插入操作和更新操作时的自动填充。
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
|
@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",BaseContext.getCurrentId()); metaObject.setValue("updateUser",BaseContext.getCurrentId()); }
@Override public void updateFill(MetaObject metaObject) { log.info("公共字段自动填充[update]..."); log.info(metaObject.toString());
long id = Thread.currentThread().getId(); log.info("线程id为:{}",id);
metaObject.setValue("updateTime",LocalDateTime.now()); metaObject.setValue("updateUser",BaseContext.getCurrentId()); } }
|
5.将Session中存放的id存入线程中
在已登录的情况下将Session中存放的id存入线程中。
1 2 3 4 5 6 7 8
| if (request.getSession().getAttribute("employee") != null) { log.info("用户已登录,用户id为:{}", request.getSession().getAttribute("employee")); Long empId = (Long) request.getSession().getAttribute("employee"); BaseContext.setCurrentId(empId); filterChain.doFilter(request, response); return; }
|
小结(逻辑)
在一次Http请求中线程是不会发生改变的,也就是说,在发送请求给后端时,经过过滤器,再经过Controller,最后在MyMetaObjecthandler实现自动填充方法时的线程的id都是不会发生改变的。
需要使用到线程的原因是因为在自动填充更新人和创建人的时候,需要将用户的id传入,但在MyMetaObjecthandler类中无法直接获得到Session中的id,故需要使用该方法。
新增分类
新增分类功能实现
1 2 3 4 5
| @PostMapping public R<String> save(@RequestBody Category category) { categoryService.save(category); return R.success("新增分类成功"); }
|
分类分页查询显示数据到页面上
分类分页查询功能实现
1 2 3 4 5 6 7 8
| @GetMapping("/page") public R<Page> page(int page,int pageSize){ Page<Category> pageInfo = new Page<>(page,pageSize); LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.orderByAsc(Category::getSort); categoryService.page(pageInfo,queryWrapper); return R.success(pageInfo); }
|
删除分类
注意事项
注意:因为分类表中包含着菜品分类和套餐分类,而对应的菜品表和套餐表中存在着一些关联,故规定菜品表(套餐表)中存在关联的分类不允许删除。
代码功能实现
- 在
CategoryController
类中调用自定义方法
1 2 3 4 5
| @DeleteMapping public R<String> delete(Long ids){ categoryService.remove(ids); return R.success("删除分类成功"); }
|
- 在
CategoryService
接口中创建自定义方法remove
1 2 3
| public interface CategoryService extends IService<Category> { public void remove(Long ids); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| @Service public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
@Autowired DishService dishService;
@Autowired SetmealService setmealService;
@Override public void remove(Long ids) { LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>(); dishLambdaQueryWrapper.eq(Dish::getCategoryId, ids); int count1 = dishService.count(dishLambdaQueryWrapper); if (count1 > 0) { throw new CustomException("当前分类下关联了菜品,不能删除"); } LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>(); setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId, ids); int count2 = setmealService.count(setmealLambdaQueryWrapper); if (count2 > 0) { throw new CustomException("当前分类下关联了套餐,不能删除"); } super.removeById(ids); } }
|
1 2 3 4 5 6 7 8
|
public class CustomException extends RuntimeException { public CustomException(String message){ super(message); } }
|
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
| @PutMapping public R<String> update(@RequestBody Category category){ categoryService.updateById(category); 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 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
|
@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) { e.printStackTrace(); } 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(); }
} }
|
新增菜品
功能分析
点击新建菜品后,页面会立即发送一个请求(获取菜品分类信息列表)
菜品图片得上传和回显由之前完成
此处新增菜品所上传得数据不只有菜品表的信息,还有菜品口味表的信息,故接受数据需要一个新的类
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
@GetMapping("/list") public R<List<Category>> list(Category category){ LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(category.getType()!=null,Category::getType,category.getType()); queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime); List<Category> list = categoryService.list(queryWrapper);
return R.success(list); }
|
1 2 3 4 5 6 7 8
| @Data public class DishDto extends Dish { private List<DishFlavor> flavors = new ArrayList<>();
private String categoryName;
private Integer copies; }
|
1 2 3 4
| public interface DishService extends IService<Dish> { public void saveWithFlavor(DishDto dishDto); }
|
实现该方法
注意:此处需要开始事务,并且由前端传入的菜品口味数据中没有与之对应的菜品id
,故需要拿到菜品口味的列表集合进行id
的赋值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Service public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService{
@Autowired DishFlavorService dishFlavorService;
@Override @Transactional public void saveWithFlavor(DishDto dishDto) { this.save(dishDto);
Long dishId = dishDto.getId();
List<DishFlavor> flavors = dishDto.getFlavors(); for (DishFlavor flavor : flavors) { flavor.setDishId(dishId); } dishFlavorService.saveBatch(flavors); } }
|
菜品分页展示
功能分析
与员工和分类的查询的不同之处在于:Dish
表中所有的只是分类的id
而不是分类的名称,但前端页面需要展示的是分类的名称而不是id
。
代码实现
在基础上加以改变
- 将分页对象pageInfo拷贝给dishDtoPage,但不拷贝records这个集合(该集合存储的是前端传入的数据)
- 将List传给List的同时,将分类的名称也传入List
- 实现方式为用列表存储多个DishDto对象,通过分类id得到分类对象,再通过分类对象得到分类名称,将分类名称放入DishDto对象中,并将Dish中的其他属性拷贝至DishDto中,最后统一由list收集DishDto对象。
- 将list对象传入分页对象的recodes属性中。
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
| @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<>(); queryWrapper.like(name != null,Dish::getName,name); queryWrapper.orderByDesc(Dish::getUpdateTime);
dishService.page(pageInfo,queryWrapper);
BeanUtils.copyProperties(pageInfo,dishDtoPage,"records");
List<Dish> records = pageInfo.getRecords();
List<DishDto> list=new ArrayList<>();
for (Dish record : records) { DishDto dishDto = new DishDto(); Long categoryId = record.getCategoryId(); Category category = categoryService.getById(categoryId); if(category!=null) { String categoryName = category.getName(); dishDto.setCategoryName(categoryName); } BeanUtils.copyProperties(record,dishDto);
list.add(dishDto); }
dishDtoPage.setRecords(list);
return R.success(dishDtoPage); }
|
菜品的批量起售和停售
需要修改:若菜品关联了套餐,则不允许停售
需求分析

注意点:@PathVariable
的使用,在有多个参数时需加上占位符的值才能让值传入
代码实现
1 2 3 4 5 6 7 8 9 10 11
| @PostMapping("/status/{status}") public R<String> changeStatus(@PathVariable("status") Integer status, Long[] ids) { for (Long id : ids) { Dish dish = dishService.getById(id); if (dish != null) { dish.setStatus(status); dishService.updateById(dish); } } return R.success("菜品售卖状态修改成功"); }
|
修改菜品信息
菜品信息回显功能
将已有的菜品信息和菜品口味信息查询出来传回前端。
- 代码实现
- 在
DishService
接口中创建一个方法用于一次查两张表。
1 2
| DishDto getByIdWithFlavor(Long id);
|
实现该方法
将两张表的数据分别查出,再存入同一个dishDto对象中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Override @Transactional public DishDto getByIdWithFlavor(Long id) {
Dish dish = this.getById(id);
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(dish, dishDto);
LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(DishFlavor::getDishId, id); List<DishFlavor> list = dishFlavorService.list(queryWrapper);
dishDto.setFlavors(list);
return dishDto; }
|
1 2 3 4 5 6 7 8
| @GetMapping("/{id}") public R<DishDto> get(@PathVariable Long id) { DishDto dishDto = dishService.getByIdWithFlavor(id); if (dishDto != null) { return R.success(dishDto); } return R.error("没有查询到菜品信息"); }
|
修改菜品功能
- 在
DishService
接口中创建一个方法用于一次修改两张表。
1 2
| void updateWithFlavor(DishDto dishDto);
|
更新菜品基本信息
删除菜品口味原有信息
新增菜品口味信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Override @Transactional public void updateWithFlavor(DishDto dishDto) { this.updateById(dishDto);
LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(DishFlavor::getDishId, dishDto.getId()); dishFlavorService.remove(queryWrapper);
Long dishId = dishDto.getId();
List<DishFlavor> flavors = dishDto.getFlavors(); for (DishFlavor flavor : flavors) { flavor.setDishId(dishId); } dishFlavorService.updateBatchById(flavors); }
|
菜品的批量删除(逻辑删除)
需求分析
需注意:若该菜品正处于起售状态不能删除,若该菜品关联了其他套餐不能删除
代码实现
主要代码:
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
| @Override @Transactional public void removeByIdWithFlavor(Long[] ids) {
for (Long id : ids) {
Dish dish = this.getById(id); if (dish.getStatus().equals(1)){ throw new CustomException("该菜品正处于起售状态不能删除"); }
List<SetmealDish> list = setmealDishService.list(); for (SetmealDish setmealDish : list) { if (id.equals(setmealDish.getDishId())){ throw new CustomException("该菜品关联了其他套餐不能删除"); } }
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(id != null, Dish::getId, id);
this.remove(queryWrapper);
LambdaQueryWrapper<DishFlavor> queryWrapper1 = new LambdaQueryWrapper<>(); queryWrapper1.eq(DishFlavor::getDishId, id); dishFlavorService.remove(queryWrapper1); }
}
|
新增套餐
需求分析
要获得一个套餐分类的下拉列表
将菜品分类的id将菜品组查询出来(注意只查询出起售状态的菜品)
将套餐信息保存至数据库中(保存套餐的基本信息,保存套餐所包含的菜品的信息)
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
@GetMapping("/list") public R<List<Category>> list(Category category){ LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(category.getType()!=null,Category::getType,category.getType()); queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime); List<Category> list = categoryService.list(queryWrapper);
return R.success(list); }
|
- 将菜品分类的id将菜品组查询出来(注意只查询出起售状态的菜品)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @GetMapping("/list") public R<List<Dish>> list(Dish dish){ LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(dish.getCategoryId()!=null,Dish::getCategoryId,dish.getCategoryId()); queryWrapper.eq(Dish::getStatus,1);
queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
List<Dish> list = dishService.list(queryWrapper); return R.success(list); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Override @Transactional public void saveWithDish(SetmealDto setmealDto) { this.save(setmealDto);
String id = String.valueOf(setmealDto.getId());
List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes(); for (SetmealDish setmealDish : setmealDishes) { setmealDish.setSetmealId(id); } setmealDishService.saveBatch(setmealDishes); }
|
套餐分页展示
需求分析
与菜品的分页展示逻辑相同,都需要特别注意前端需要的是套餐的名称而不是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
| @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 = new ArrayList<>();
for (Setmeal record : records) {
SetmealDto setmealDto = new SetmealDto(); Long categoryId = record.getCategoryId(); Category category = categoryService.getById(categoryId); if (category != null) { String categoryName = category.getName(); setmealDto.setCategoryName(categoryName); } BeanUtils.copyProperties(record, setmealDto);
list.add(setmealDto); }
setmealDtoPage.setRecords(list);
return R.success(setmealDtoPage); }
|
套餐的批量起售和停售
需求分析
与菜品的批量起售和停售功能逻辑相同
代码实现
1 2 3 4 5 6 7 8 9 10 11
| @PostMapping("/status/{status}") public R<String> changeStatus(@PathVariable("status") Integer status, Long[] ids) { for (Long id : ids) { Setmeal setmeal = setmealService.getById(id); if (setmeal != null) { setmeal.setStatus(status); setmealService.updateById(setmeal); } } return R.success("套餐售卖状态修改成功"); }
|
修改套餐功能
套餐信息回显功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Override @Transactional
public SetmealDto getByIdWithDish(Long id) { Setmeal setmeal = this.getById(id);
SetmealDto setmealDto = new SetmealDto();
BeanUtils.copyProperties(setmeal, setmealDto);
LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(id != null, SetmealDish::getSetmealId, id); List<SetmealDish> list = setmealDishService.list(queryWrapper);
setmealDto.setSetmealDishes(list);
return setmealDto; }
|
修改套餐功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Override @Transactional public void updateWithDish(SetmealDto setmealDto) {
this.updateById(setmealDto);
LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(SetmealDish::getSetmealId, setmealDto.getId()); setmealDishService.remove(queryWrapper);
String id = String.valueOf(setmealDto.getId());
List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes(); for (SetmealDish setmealDish : setmealDishes) { setmealDish.setSetmealId(id); } setmealDishService.saveBatch(setmealDishes); }
|
套餐的批量删除
注意
处于起售状态的套餐不能删除,与菜品删除不同的是,删除套餐可不用删除菜品
1 2 3 4 5 6 7 8 9 10 11 12
| @Override @Transactional public void removeByIdWithDish(Long[] ids) { for (Long id : ids) { Setmeal setmeal = this.getById(id); if (setmeal.getStatus() == 1) { throw new CustomException("存在套餐正处于起售状态不能删除"); } this.removeById(id); } }
|
订单明细
见39.后台按条件查看订单
移动端短信验证登录(登出)
获取验证码
1 2 3 4 5 6 7 8 9 10 11
| <!--阿里云短信服务--> <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>
|
1 2 3 4 5 6 7 8 9
| String[] urls = new String[]{ "/employee/login", "/employee/logout", "/backend/**", "/front/**", "/user/sendMsg", "/user/login" };
|
1 2 3 4 5 6 7 8 9
| if (request.getSession().getAttribute("user") != null) { log.info("用户已登录,用户id为:{}", request.getSession().getAttribute("user"));
Long userId = (Long) request.getSession().getAttribute("user"); BaseContext.setCurrentId(userId); filterChain.doFilter(request, response); return; }
|
注意:此处因为没有阿里云短信服务的签名,所以真实发送短信的步骤就注释了,但生成的验证码可在控制台通过日志的形式查看。
获取手机号
生成随机的四位验证码
调用阿里云的短信服务API发送短信(已注释)
将验证码存入Session
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @PostMapping("/sendMsg") public R<String> sendMsg(@RequestBody User user, HttpSession session) { String phone = user.getPhone(); if (phone != null) { String code = String.valueOf(ValidateCodeUtils.generateValidateCode(4)); log.info("code={}", code);
session.setAttribute(phone, code);
return R.success("短信验证码发送成功"); }
return R.error("短信验证码发送失败"); }
|
登录移动端
获取前端发送过来的手机号
获取前端发送过来的验证码
从session中取出生成的验证码
如果能比对成功,证明登录成功
根据手机号判断是否为新用户,若是新用户则自动注册
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
| @PostMapping("/login") public R<User> login(@RequestBody Map map, HttpSession session) {
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(phone != null, User::getPhone, phone); User user = userService.getOne(queryWrapper); if (user == null) { user = new User(); user.setPhone(phone); userService.save(user); } session.setAttribute("user", user.getId()); return R.success(user); } return R.error("登录失败"); }
|
登出移动端
注意要释放session
1 2 3 4 5 6
| @PostMapping("/loginout") public R<String> loginout(HttpServletRequest request) { request.getSession().removeAttribute("user"); return R.success("退出成功"); }
|
新增地址
需求分析
注意:要设置地址对应的用户id
代码实现
1 2 3 4 5 6
| @PostMapping public R<String> save(@RequestBody AddressBook addressBook){ addressBook.setUserId(BaseContext.getCurrentId()); addressBookService.save(addressBook); return R.success("新增地址成功"); }
|
地址列表显示
需求分析
将当前登录用户的所有地址显示为列表展示到移动端页面
注意:要设置地址对应的用户id
代码实现
1 2 3 4 5 6 7 8 9 10 11
| @GetMapping("/list") public R<List<AddressBook>> list(AddressBook addressBook) { addressBook.setUserId(BaseContext.getCurrentId()); LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(addressBook.getUserId() != null, AddressBook::getUserId, addressBook.getUserId()); queryWrapper.orderByDesc(AddressBook::getUpdateTime); List<AddressBook> list = addressBookService.list(queryWrapper);
return R.success(list); }
|
设置默认地址
需求分析
先把该用户的所有地址都不设置为默认地址(故把所有该用户的is_default
字段都设置为0)
设置该用户所选择的地址为默认地址
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @PutMapping("/default") public R<AddressBook> setDefault(@RequestBody AddressBook addressBook) { Long userId = BaseContext.getCurrentId(); LambdaUpdateWrapper<AddressBook> updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(userId != null, AddressBook::getUserId, userId); updateWrapper.set(AddressBook::getIsDefault, 0); addressBookService.update(updateWrapper); addressBook.setIsDefault(1); addressBookService.updateById(addressBook); return R.success(addressBook); } }
|
查询用户的默认地址
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @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); } }
|
修改地址(需修改)
地址的回显功能
1 2 3 4 5 6 7 8
| @GetMapping("/{id}") public R<AddressBook> getById(@PathVariable Long id) { AddressBook addressBook = addressBookService.getById(id); if (addressBook != null) { return R.success(addressBook); } return R.error("没有查询到该用户地址"); }
|
修改地址功能
1 2 3 4 5
| @PutMapping public R<String> update(@RequestBody AddressBook addressBook) { addressBookService.updateById(addressBook); return R.success("修改收货地址成功"); }
|
删除地址
需求分析
此处无过多的逻辑分析,即使是默认地址也可以进行删除。
代码实现
1 2 3 4 5
| @DeleteMapping public R<String> delete(Long ids) { addressBookService.removeById(ids); return R.success("删除收货地址成功"); }
|
移动端主页展示(菜品、套餐)
注意点
菜品,套餐的分类功能在前面已经写过了。移动端展示主页面需要该分类功能和购物车展示功能同时成功才能展示出来。
点击对应的菜品分类可查询出该分类下的所有菜品,注意该方法已经在之前新增套餐时的功能2中编写,但是当时只是让DishDto
对象中新保存了菜品的名称,没有保存菜品的口味,故需要对该方法进行加强。
点击对应的套餐分类可查询出该分类下的所有套餐,需要添加该方法。
重新编写查询当前分类下的所有菜品功能
注意:只是新增了菜品口味的功能,不影响前面使用的功能(仅仅是对功能加强)
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
| @GetMapping("/list") public R<List<DishDto>> list(Dish dish) { LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(dish.getCategoryId() != null, Dish::getCategoryId, dish.getCategoryId()); queryWrapper.eq(Dish::getStatus, 1);
queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
List<Dish> list = dishService.list(queryWrapper);
List<DishDto> dishDtoList = new ArrayList<>();
for (Dish dish1 : list) { DishDto dishDto = new DishDto(); BeanUtils.copyProperties(dish1, dishDto); Long categoryId = dish1.getCategoryId(); Category category = categoryService.getById(categoryId); if (category != null) { String categoryName = category.getName(); dishDto.setCategoryName(categoryName); } Long dishId = dish1.getId(); LambdaQueryWrapper<DishFlavor> queryWrapper1 = new LambdaQueryWrapper<>(); queryWrapper1.eq(DishFlavor::getDishId,dishId); List<DishFlavor> list1 = dishFlavorService.list(queryWrapper1); dishDto.setFlavors(list1);
dishDtoList.add(dishDto); }
return R.success(dishDtoList); }
|
编写查询当前分类下的所有套餐功能
1 2 3 4 5 6 7 8 9 10 11
| @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); }
|
添加购物车
需求分析
设置当前购物车是哪个用户的
判断添加的是菜品还是套餐
如果能查找出来对应的符合该用户且符合该菜品(套餐)这两个条件的购物车对象,则在数量上加一
如果查询不出来,则新增该购物车对象,并设置数量为1,且设置创建时间
代码实现
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
| @PostMapping("/add") public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart) { log.info("购物车数据:{}", shoppingCart);
Long userId = BaseContext.getCurrentId(); shoppingCart.setUserId(userId);
LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(ShoppingCart::getUserId, userId);
Long dishId = shoppingCart.getDishId(); if (dishId != null) { queryWrapper.eq(ShoppingCart::getDishId, dishId); } else { queryWrapper.eq(ShoppingCart::getSetmealId, shoppingCart.getSetmealId()); } ShoppingCart shoppingCartOne = shoppingCartService.getOne(queryWrapper);
if (shoppingCartOne != null) { Integer number = shoppingCartOne.getNumber(); shoppingCartOne.setNumber(number + 1); shoppingCartService.updateById(shoppingCartOne); } else { shoppingCart.setNumber(1); shoppingCart.setCreateTime(LocalDateTime.now()); shoppingCartService.save(shoppingCart); shoppingCartOne = shoppingCart; } return R.success(shoppingCartOne); }
|
查看购物车
需求分析
根据每个用户的id
来查,每个用户只能看到自己的购物车
代码实现
1 2 3 4 5 6 7 8 9 10
| @GetMapping("/list") public R<List<ShoppingCart>> list() { Long userId = BaseContext.getCurrentId(); LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(userId != null, ShoppingCart::getUserId, userId); queryWrapper.orderByAsc(ShoppingCart::getCreateTime); List<ShoppingCart> list = shoppingCartService.list(queryWrapper);
return R.success(list); }
|
清空购物车
需求分析
注意:仅删除该用户的所有购物车数据
代码实现
1 2 3 4 5 6 7 8
| @DeleteMapping("/clean") public R<String> clean() { Long userId = BaseContext.getCurrentId(); LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(userId != null, ShoppingCart::getUserId, userId); shoppingCartService.remove(queryWrapper); return R.success("清空购物车成功"); }
|
删减购物车
需求分析
设置当前购物车是哪个用户的
判断删减的是菜品还是套餐(因为前端发送过来的数据要么为DishId
,要么为SetmealId
)
判断要删减的该对象的数量是否为1
若为1,则删除该数据
若不为1,则将数量减一
代码实现
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("/sub") public R<String> sub(@RequestBody ShoppingCart shoppingCart) { log.info("数据:{}", shoppingCart); Long userId = BaseContext.getCurrentId(); shoppingCart.setUserId(userId); LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(ShoppingCart::getUserId, userId);
Long dishId = shoppingCart.getDishId(); if (dishId != null) { queryWrapper.eq(ShoppingCart::getDishId, dishId); } else { queryWrapper.eq(ShoppingCart::getSetmealId, shoppingCart.getSetmealId()); }
ShoppingCart shoppingCartOne = shoppingCartService.getOne(queryWrapper); Integer number = shoppingCartOne.getNumber(); if (number == 1) { Long id = shoppingCartOne.getId(); shoppingCartService.removeById(id); } else { shoppingCartOne.setNumber(number - 1); shoppingCartService.updateById(shoppingCartOne); } return R.success("删减商品成功"); }
|
下单
需求分析
前提:点击去支付后
前端页面自动访问查询默认地址功能(前面已写好)
前端页面自动访问查询购物车信息功能(前面已写好)
具体步骤:
获得当前用户id
查询当前用户的购物车数据
若购物车为空,则抛出异常
查询用户数据
查询地址数据
封装订单表数据(1条)
封装订单明细表数据(多条:原购物车有几条数据这里就有几条)
向订单表插入数据,一条数据
向订单明细表插入数据,多条数据
清空购物车数据
代码实现
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
| @Override @Transactional public void submit(Orders orders) { Long userId = BaseContext.getCurrentId();
LambdaQueryWrapper<ShoppingCart> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(ShoppingCart::getUserId,userId); List<ShoppingCart> shoppingCarts = shoppingCartService.list(wrapper);
if(shoppingCarts == null || shoppingCarts.size() == 0){ throw new CustomException("购物车为空,不能下单"); }
User user = userService.getById(userId);
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.setId(orderId); orders.setOrderTime(LocalDateTime.now()); orders.setCheckoutTime(LocalDateTime.now()); orders.setStatus(2); orders.setAmount(new BigDecimal(amount.get())); orders.setUserId(userId); orders.setNumber(String.valueOf(orderId)); 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(wrapper); }
|
用户查看自己的订单
需求分析
需将订单明细表中的数据也查询出来,故这里需要使用OrderDto
需注意:在遍历的时候直接使用构造条件来查询导致eq
叠加,从而导致后面查询的数据都是null,所有该处选择将方法方法外面进行调用
代码实现
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
| public List<OrderDetail> getOrderDetailListByOrderId(Long orderId) { LambdaQueryWrapper<OrderDetail> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(OrderDetail::getOrderId, orderId); List<OrderDetail> orderDetailList = orderDetailService.list(queryWrapper); return orderDetailList; }
@GetMapping("/userPage") public R<Page> list(int page, int pageSize) { Page<Orders> ordersPage = new Page<>(page, pageSize); Page<OrdersDto> ordersDtoPage = new Page<>();
LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.orderByAsc(Orders::getOrderTime); queryWrapper.eq(Orders::getUserId, BaseContext.getCurrentId()); ordersService.page(ordersPage, queryWrapper);
List<Orders> records = ordersPage.getRecords();
ArrayList<OrdersDto> list = new ArrayList<>();
for (Orders record : records) {
OrdersDto ordersDto = new OrdersDto(); Long orderId = record.getId();
List<OrderDetail> list1 = this.getOrderDetailListByOrderId(orderId);
BeanUtils.copyProperties(record, ordersDto); ordersDto.setOrderDetails(list1);
list.add(ordersDto); }
ordersDtoPage.setRecords(list);
return R.success(ordersDtoPage); }
|
后台按条件查看订单
需求分析
此处需要添加三个条件:
订单号模糊查询
大于某时间
小于某时间
代码实现
1 2 3 4 5 6 7 8 9 10 11 12
| @GetMapping("/page") public R<Page> page(int page, int pageSize, String number, String beginTime, String endTime) { Page<Orders> ordersPage = new Page<>(page, pageSize); LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.orderByAsc(Orders::getOrderTime) .like(number != null, Orders::getId, number) .ge(beginTime != null, Orders::getOrderTime, beginTime) .le(endTime != null, Orders::getOrderTime, endTime);
ordersService.page(ordersPage, queryWrapper); return R.success(ordersPage); }
|
后台修改订单状态
需求分析
前端发送过来的数据包含需改变的status
和订单id
,故可直接更改订单状态。
代码实现
1 2 3 4 5
| @PutMapping public R<String> changeStatus(@RequestBody Orders orders) { ordersService.updateById(orders); return R.success("修改订单状态成功"); }
|
移动端点击套餐图片查看套餐具体菜品
需求分析
通过前端传入的套餐id在套餐菜品关系表中查询出该套餐下的所有菜品
将dish对象拷贝给dishDto
对象
将存dishDto
对象的集合传回前端
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @GetMapping("/dish/{id}") public R<List<DishDto>> dish(@PathVariable("id") Long id) { LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(SetmealDish::getSetmealId, id); List<SetmealDish> list = setmealDishService.list(queryWrapper);
List<DishDto> dishDtos = new ArrayList<>();
for (SetmealDish setmealDish : list) { DishDto dishDto = new DishDto(); String dishId = setmealDish.getDishId(); Dish dish = dishService.getById(dishId); BeanUtils.copyProperties(dish, dishDto);
dishDtos.add(dishDto); }
return R.success(dishDtos); } }
|
再来一单功能
需求分析
只有订单表中的status为4 的时候才能有再来一单的功能
点击再来一单按钮前端页面会直接跳转到购物车页面
根据userId
,删除该用户此时所剩的购物车数据(清空购物车)
需要将原来购物车数据复制到购物车中
代码实现
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
| @PostMapping("/again") public R<String> again(@RequestBody Map<String,String> map) {
Long userId = BaseContext.getCurrentId(); LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(userId != null, ShoppingCart::getUserId, userId); shoppingCartService.remove(queryWrapper);
String id = map.get("id"); LambdaQueryWrapper<OrderDetail> queryWrapper1 = new LambdaQueryWrapper<>(); queryWrapper1.eq(OrderDetail::getOrderId,id); List<OrderDetail> list = orderDetailService.list(queryWrapper1);
ArrayList<ShoppingCart> shoppingCartsLists = new ArrayList<>(); for (OrderDetail orderDetail : list) { ShoppingCart shoppingCart = new ShoppingCart(); shoppingCart.setUserId(userId); shoppingCart.setImage(orderDetail.getImage()); Long dishId = orderDetail.getDishId(); Long setmealId = orderDetail.getSetmealId(); if (dishId != null) { shoppingCart.setDishId(dishId); } else { shoppingCart.setSetmealId(setmealId); } shoppingCart.setName(orderDetail.getName()); shoppingCart.setDishFlavor(orderDetail.getDishFlavor()); shoppingCart.setNumber(orderDetail.getNumber()); shoppingCart.setAmount(orderDetail.getAmount()); shoppingCart.setCreateTime(LocalDateTime.now());
shoppingCartsLists.add(shoppingCart); } shoppingCartService.saveBatch(shoppingCartsLists);
return R.success("操作成功"); }
|
写在后面
该项目后续可用redis
,mysql
主从复制,nginx
等技术优化,还需后续完善…….