瑞吉外卖-第六天

课程内容

  • 用户地址簿功能
  • 菜品展示
  • 购物车
  • 下单

1. 用户地址簿功能

1.1 需求分析

地址簿,指的是移动端消费者用户的地址信息,用户登录成功后可以维护自己的地址信息。同一个用户可以有多个地址信息,但是只能有一个默认地址

对于地址簿管理,我们需要实现以下几个功能:

  • 新增地址
  • 地址列表查询
  • 设置默认地址
  • 编辑地址
  • 删除地址

1.2 数据模型

用户的地址信息会存储在address_book表,即地址簿表中。具体表结构如下:

这里面有一个字段is_default,实际上我们在设置默认地址时,只需要更新这个字段就可以了。

1.3 导入功能代码

对于这一类的单表的增删改查,我们已经写过很多了,基本的开发思路都是一样的,那么本小节的用户地址簿管理的增删改查功能,我们就不再一一实现了,基本的代码我们都已经提供了,直接导入进来,做一个测试即可。

对于下面的地址管理的代码,我们可以直接从资料拷贝,也可以直接从下面的讲义中复制。

1). 实体类 AddressBook(直接从课程资料中导入即可)

所属包: com.itheima.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;//用户idprivate Long userId;//收货人private String consignee;//手机号private String phone;//性别 0 女 1 男private String sex;//省级区划编号private String provinceCode;//省级名称private String provinceName;//市级区划编号private String cityCode;//市级名称private String cityName;//区级区划编号private String districtCode;//区级名称private String districtName;//详细地址private String detail;//标签private String label;//是否默认 0 否 1是private Integer isDefault;//创建时间@TableField(fill = FieldFill.INSERT)private LocalDateTime createTime;//更新时间@TableField(fill = FieldFill.INSERT_UPDATE)private LocalDateTime updateTime;//创建人@TableField(fill = FieldFill.INSERT)private Long createUser;//修改人@TableField(fill = FieldFill.INSERT_UPDATE)private Long updateUser;//是否删除private Integer isDeleted;
}

2). Mapper接口 AddressBookMapper(直接从课程资料中导入即可)

所属包: com.itheima.reggie.mapper

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.AddressBook;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;@Mapper
public interface AddressBookMapper extends BaseMapper<AddressBook> {}

3). 业务层接口 AddressBookService(直接从课程资料中导入即可)

所属包: com.itheima.reggie.service

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.AddressBook;public interface AddressBookService extends IService<AddressBook> {}

4). 业务层实现类 AddressBookServiceImpl(直接从课程资料中导入即可)

所属包: com.itheima.reggie.service.impl

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.AddressBook;
import com.itheima.reggie.mapper.AddressBookMapper;
import com.itheima.reggie.service.AddressBookService;
import org.springframework.stereotype.Service;@Service
public class AddressBookServiceImpl extends ServiceImpl<AddressBookMapper, AddressBook> implements AddressBookService {}

5). 控制层 AddressBookController(直接从课程资料中导入即可)

所属包: com.itheima.reggie.controller

controller主要开发的功能:

A. 新增地址逻辑说明:

  • 需要记录当前是哪个用户的地址(关联当前登录用户)

B. 设置默认地址

  • 每个用户可以有很多地址,但是默认地址只能有一个 ;

  • 先将该用户所有地址的is_default更新为0 , 然后将当前的设置的默认地址的is_default设置为1

C. 根据ID查询地址

D. 查询默认地址

  • 根据当前登录用户ID 以及 is_default进行查询,查询当前登录用户is_default为1的地址信息

E. 查询指定用户的全部地址

  • 根据当前登录用户ID,查询所有的地址列表

代码实现如下:

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.itheima.reggie.common.BaseContext;
import com.itheima.reggie.common.R;
import com.itheima.reggie.entity.AddressBook;
import com.itheima.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 {@Autowiredprivate AddressBookService addressBookService;/*** 新增*/@PostMappingpublic 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);//SQL:update address_book set is_default = 0 where user_id = ?addressBookService.update(wrapper);addressBook.setIsDefault(1);//SQL:update address_book set is_default = 1 where id = ?addressBookService.updateById(addressBook);return R.success(addressBook);}/*** 根据id查询地址*/@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);//SQL:select * from address_book where user_id = ? and is_default = 1AddressBook 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);//SQL:select * from address_book where user_id = ? order by update_time descreturn R.success(addressBookService.list(queryWrapper));}
}

1.4 功能测试

代码导入进来,并且去阅读了一下地址管理各个功能的逻辑实现,接下来,我们就可以启动项目,进行一个测试。测试过程中,通过debug断点调试观察服务端程序的执行过程,在浏览器中使用调试工具查看页面和服务端的交互过程和请求响应数据。

1). 新增

填写表单数据,点击保存地址,查看网络请求。

测试完毕之后,检查数据库中的数据,是否正常插入。

2). 列表查询

当新增地址完成后,页面会再次发送一个请求,来查询该用户的所有地址列表,在界面进行展示。

3). 设置默认

在地址列表页面中,勾选 “设为默认地址” ,此时会发送PUT请求,来设置默认地址。

测试完毕后,我们再次查看数据库表中的数据:

2. 菜品展示

2.1 需求分析

用户登录成功后跳转到系统首页,在首页需要根据分类来展示菜品和套餐。如果菜品设置了口味信息,需要展示按钮,否则显示按钮。

2.2 前端页面分析

在开发代码之前,需要梳理一下前端页面和服务端的交互过程:

1). 页面(front/index.html)发送ajax请求,获取分类数据(菜品分类和套餐分类)

该功能在之前的业务开发中,我们都已经实现了。通过请求响应的数据,我们也可以看到数据是可以正确获取到的。

注意:首页加载时,不仅发送请求获取分类列表,还发送了一次ajax请求用于加载购物车数据,而这两次请求必须全部成功,页面才可以正常渲染,而当前购物车列表查询功能还未实现(报404),所以列表目前并未渲染。此处可以将这次请求的地址暂时修改一下,从静态json文件获取数据,等后续开发购物车功能时再修改回来,如下:

修改之后,我们再次测试:

目前该部分的功能我们已经调通,左侧的分类菜单,和右侧的菜品信息我们都可以看到,后续我们只需要将购物车列表的数据改成调用服务端接口查询即可。

2). 页面发送ajax请求,获取第一个分类下的菜品或者套餐

A. 根据分类ID查询套餐列表:

B. 根据分类ID查询菜品列表:

异步请求,查询分类对应的菜品列表,功能我们已经实现了,但是我们之前查询的只是菜品的基本信息,不包含菜品的口味信息。所以在前端界面中,我们看不到选择菜品分类的信息。

经过上述的分析,我们可以看到,服务端我们主要提供两个方法, 分别用来:

A. 根据分类ID查询菜品列表(包含菜品口味列表), 具体请求信息如下:

请求 说明
请求方式 GET
请求路径 /dish/list
请求参数 ?categoryId=1397844263642378242&status=1

该功能在服务端已经实现,我们需要修改此方法,在原有方法的基础上增加查询菜品的口味信息。

B. 根据分类ID查询套餐列表, 具体请求信息如下:

请求 说明
请求方式 GET
请求路径 /setmeal/list
请求参数 ?categoryId=1397844263642378242&status=1

该功能在服务端并未实现。

2.3 代码开发

2.3.1 查询菜品方法修改

由于之前我们实现的根据分类查询菜品列表,仅仅查询了菜品的基本信息,未查询菜品口味信息,而移动端用户在点餐时,是需要选择口味信息的,所以我们需要对之前的代码实现进行完善,那么如何完善呢?

我们需要修改DishController的list方法,原来此方法的返回值类型为:R<List>。为了满足移动端对数据的要求(菜品基本信息和菜品对应的口味信息),现在需要将方法的返回值类型改为:R<List> ,因为在DishDto中封装了菜品对应的口味信息:

代码逻辑:

A. 根据分类ID查询,查询目前正在启售的菜品列表 (已实现)

B. 遍历菜品列表,并查询菜品的分类信息及菜品的口味列表

C. 组装数据DishDto,并返回

代码实现:

@GetMapping("/list")public R<List<DishDto>> list(Dish dish){//构造查询条件LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(dish.getCategoryId() != null ,Dish::getCategoryId,dish.getCategoryId());//添加条件,查询状态为1(起售状态)的菜品queryWrapper.eq(Dish::getStatus,1);//添加排序条件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();//分类id//根据id查询分类对象Category category = categoryService.getById(categoryId);if(category != null){String categoryName = category.getName();dishDto.setCategoryName(categoryName);}//当前菜品的idLong dishId = item.getId();LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.eq(DishFlavor::getDishId,dishId);//SQL:select * from dish_flavor where dish_id = ?List<DishFlavor> dishFlavorList = dishFlavorService.list(lambdaQueryWrapper);dishDto.setFlavors(dishFlavorList);return dishDto;}).collect(Collectors.toList());return R.success(dishDtoList);}

2.3.2 根据分类ID查询套餐

在SetmealController中创建list方法,根据条件查询套餐数据。

/*** 根据条件查询套餐数据* @param setmeal* @return*/
@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);
}

2.4 功能测试

把菜品展示的功能代码完善完成之后,我们重新启动服务,来测试一个菜品展示的功能。测试过程中可以使用浏览器的监控工具查看页面和服务端的数据交互细节。

点击分类,根据分类查询菜品列表/套餐列表:

3. 购物车

3.1 需求分析

移动端用户可以将菜品或者套餐添加到购物车。对于菜品来说,如果设置了口味信息,则需要选择规格后才能加入购物车;对于套餐来说,可以直接点击将当前套餐加入购物车。在购物车中可以修改菜品和套餐的数量,也可以清空购物车。

这里面我们需要实现的功能包括:

1). 添加购物车

2). 查询购物车

3). 清空购物车

3.2 数据模型

用户的购物车数据,也是需要保存在数据库中的,购物车对应的数据表为shopping_cart表,具体表结构如下:

说明:

  • 购物车数据是关联用户的,在表结构中,我们需要记录,每一个用户的购物车数据是哪些
  • 菜品列表展示出来的既有套餐,又有菜品,如果APP端选择的是套餐,就保存套餐ID(setmeal_id),如果APP端选择的是菜品,就保存菜品ID(dish_id)
  • 对同一个菜品/套餐,如果选择多份不需要添加多条记录,增加数量number即可

最终shopping_cart表中存储的数据示例:

3.3 前端页面分析

在开发代码之前,需要梳理一下购物车操作时前端页面和服务端的交互过程:

1). 点击 “加入购物车” 或者 “+” 按钮,页面发送ajax请求,请求服务端,将菜品或者套餐添加到购物车

2). 点击购物车图标,页面发送ajax请求,请求服务端查询购物车中的菜品和套餐

此时,我们就需要将查询购物车的代码放开,不用再加载静态的json数据了。

3). 点击清空购物车按钮,页面发送ajax请求,请求服务端来执行清空购物车操作

经过上述的分析,我们可以看到,对于购物车的功能,我们主要需要开发以下几个功能,具体的请求信息如下:

1). 加入购物车

请求 说明
请求方式 POST
请求路径 /shoppingCart/add
请求参数 json格式
菜品数据:
{"amount":118,"dishFlavor":"不要蒜,微辣","dishId":"1397851099502260226","name":"全家福","image":"a53a4e6a-3b83-4044-87f9-9d49b30a8fdc.jpg"}套餐数据:
{"amount":38,"setmealId":"1423329486060957698","name":"营养超值工作餐","image":"9cd7a80a-da54-4f46-bf33-af3576514cec.jpg"}

2). 查询购物车列表

请求 说明
请求方式 GET
请求路径 /shoppingCart/list

3). 清空购物车功能

请求 说明
请求方式 DELETE
请求路径 /shoppingCart/clean

3.4 准备工作

分析完毕购物车的业务需求和实现思路之后,在开发业务功能前,先将需要用到的类和接口基本结构创建好:

1). 实体类 ShoppingCart(直接从课程资料中导入即可)

所属包: com.itheima.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;//用户idprivate Long userId;//菜品idprivate Long dishId;//套餐idprivate Long setmealId;//口味private String dishFlavor;//数量private Integer number;//金额private BigDecimal amount;//图片private String image;private LocalDateTime createTime;
}

2). Mapper接口 ShoppingCartMapper

所属包: com.itheima.reggie.mapper

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.ShoppingCart;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface ShoppingCartMapper extends BaseMapper<ShoppingCart> {}

3). 业务层接口 ShoppingCartService

所属包: com.itheima.reggie.service

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.ShoppingCart;public interface ShoppingCartService extends IService<ShoppingCart> {}

4). 业务层实现类 ShoppingCartServiceImpl

所属包: com.itheima.reggie.service.impl

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.ShoppingCart;
import com.itheima.reggie.mapper.ShoppingCartMapper;
import com.itheima.reggie.service.ShoppingCartService;
import org.springframework.stereotype.Service;@Service
public class ShoppingCartServiceImpl extends ServiceImpl<ShoppingCartMapper, ShoppingCart> implements ShoppingCartService {}

5). 控制层 ShoppingCartController

所属包: com.itheima.reggie.controller

import com.itheima.reggie.service.ShoppingCartService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;/*** 购物车*/
@Slf4j
@RestController
@RequestMapping("/shoppingCart")
public class ShoppingCartController {@Autowiredprivate ShoppingCartService shoppingCartService;}

3.5 代码开发

3.5.1 添加购物车

在ShoppingCartController中创建add方法,来完成添加购物车的逻辑实现,具体的逻辑如下:

A. 获取当前登录用户,为购物车对象赋值

B. 根据当前登录用户ID 及 本次添加的菜品ID/套餐ID,查询购物车数据是否存在

C. 如果已经存在,就在原来数量基础上加1

D. 如果不存在,则添加到购物车,数量默认就是1

代码实现如下:

/**
* 添加购物车
* @param shoppingCart
* @return
*/
@PostMapping("/add")
public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart){log.info("购物车数据:{}",shoppingCart);//设置用户id,指定当前是哪个用户的购物车数据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());}//查询当前菜品或者套餐是否在购物车中//SQL:select * from shopping_cart where user_id = ? and dish_id/setmeal_id = ?ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper);if(cartServiceOne != null){//如果已经存在,就在原来数量基础上加一Integer number = cartServiceOne.getNumber();cartServiceOne.setNumber(number + 1);shoppingCartService.updateById(cartServiceOne);}else{//如果不存在,则添加到购物车,数量默认就是一shoppingCart.setNumber(1);shoppingCart.setCreateTime(LocalDateTime.now());shoppingCartService.save(shoppingCart);cartServiceOne = shoppingCart;}return R.success(cartServiceOne);
}

3.5.2 查询购物车

在ShoppingCartController中创建list方法,根据当前登录用户ID查询购物车列表,并对查询的结果进行创建时间的倒序排序。

代码实现如下:

/**
* 查看购物车
* @return
*/
@GetMapping("/list")
public R<List<ShoppingCart>> list(){log.info("查看购物车...");LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());queryWrapper.orderByAsc(ShoppingCart::getCreateTime);List<ShoppingCart> list = shoppingCartService.list(queryWrapper);return R.success(list);
}

3.5.3 清空购物车

在ShoppingCartController中创建clean方法,在方法中获取当前登录用户,根据登录用户ID,删除购物车数据。

代码实现如下:

/**
* 清空购物车
* @return
*/
@DeleteMapping("/clean")
public R<String> clean(){//SQL:delete from shopping_cart where user_id = ?LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());shoppingCartService.remove(queryWrapper);return R.success("清空购物车成功");
}

3.6 功能测试

按照前面分析的操作流程进行测试,测试功能以及数据库中的数据是否是否正常。

1). 添加购物车

当添加的是菜品信息,而这个用户的购物车中当前并没有这个菜品时,添加一条数据,数量为1。

检查数据库数据,由于是菜品保存的是dish_id。

这时在页面上,我们可以继续点击+号,在购物车中增加该菜品,此时,应该是对现有的购物车菜品数量加1,而不应该插入新的记录。

检查数据库数据:

如果添加的是套餐,该套餐在当前用户的购物车中并不存在,则添加一条数据,数量为1。

检查数据库数据:

2). 查看购物车

点击页面下面的购物车边栏,查看购物车数据列表是否正常展示。

3). 清空购物车

在购物车列表展示页中点击"清空", 查看购物车是否被清空。

并检查数据库中的数据,可以看到数据已经被删除。

4. 下单

4.1 需求分析

移动端用户将菜品或者套餐加入购物车后,可以点击购物车中的 “去结算” 按钮,页面跳转到订单确认页面,点击 “去支付” 按钮则完成下单操作。

这里,我们需要说明一下,这里并不会去开发支付功能,因为不论是支付宝的支付,还是微信支付,都是需要企业资质的,而我们大家在测试的时候,是没有办法提供企业资质的,所以这一部分支付功能我们就不去实现了。

4.2 数据模型

用户下单业务对应的数据表为orders表和order_detail表(一对多关系,一个订单关联多个订单明细):

表名 含义 说明
orders 订单表 主要存储订单的基本信息(如: 订单号、状态、金额、支付方式、下单用户、收件地址等)
order_detail 订单明细表 主要存储订单详情信息(如: 该订单关联的套餐及菜品的信息)

具体的表结构如下:

A. orders 订单表

B. order_detail

数据示例:

用户提交订单时,需要往订单表orders中插入一条记录,并且需要往order_detail中插入一条或多条记录。

4.3 前端页面分析

在开发代码之前,需要梳理一下用户下单操作时前端页面和服务端的交互过程:

1). 在购物车中点击按钮,页面跳转到订单确认页面

页面跳转前端已经完成,我们无需操作。

2). 在订单确认页面,发送ajax请求,请求服务端获取当前登录用户的默认地址

该功能在用户地址簿管理功能开发时,已经实现(导入),我们无需操作。

3). 在订单确认页面,发送ajax请求,请求服务端获取当前登录用户的购物车数据

该功能已经实现,我们无需操作。

4). 在订单确认页面点击按钮,发送ajax请求,请求服务端完成下单操作

经过上述的分析,我们看到前三步的功能我们都已经实现了,我们主要需要实现最后一步的下单功能,该功能具体的请求信息如下:

请求 说明
请求方式 POST
请求路径 /order/submit
请求参数 {“remark”:“老板,记得带一次性筷子”,“payMethod”:1,“addressBookId”:“1425792459560005634”}

4.4 准备工作

在开发业务功能前,先将需要用到的类和接口基本结构创建好:

1). 实体类 Orders、OrderDetail(直接从课程资料中导入即可)

所属包: com.itheima.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;//订单状态 1待付款,2待派送,3已派送,4已完成,5已取消private Integer status;//下单用户idprivate Long userId;//地址idprivate Long addressBookId;//下单时间private LocalDateTime orderTime;//结账时间private LocalDateTime checkoutTime;//支付方式 1微信,2支付宝private Integer payMethod;//实收金额private BigDecimal amount;//备注private String remark;//用户名private String userName;//手机号private String phone;//地址private String address;//收货人private String consignee;
}
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;//订单idprivate Long orderId;//菜品idprivate Long dishId;//套餐idprivate Long setmealId;//口味private String dishFlavor;//数量private Integer number;//金额private BigDecimal amount;//图片private String image;
}

2). Mapper接口 OrderMapper、OrderDetailMapper

所属包: com.itheima.reggie.mapper

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.Orders;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface OrderMapper extends BaseMapper<Orders> {}
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.OrderDetail;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface OrderDetailMapper extends BaseMapper<OrderDetail> {}

3). 业务层接口 OrderService、OrderDetailService

所属包: com.itheima.reggie.service

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.Orders;public interface OrderService extends IService<Orders> {}
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.OrderDetail;public interface OrderDetailService extends IService<OrderDetail> {}

4). 业务层实现类 OrderServiceImpl、OrderDetailServiceImpl

所属包: com.itheima.reggie.service.impl

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.*;
import com.itheima.reggie.mapper.OrderMapper;
import com.itheima.reggie.service.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Orders> implements OrderService {}
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.OrderDetail;
import com.itheima.reggie.mapper.OrderDetailMapper;
import com.itheima.reggie.service.OrderDetailService;
import org.springframework.stereotype.Service;@Service
public class OrderDetailServiceImpl extends ServiceImpl<OrderDetailMapper, OrderDetail> implements OrderDetailService {}

5). 控制层 OrderController、OrderDetailController

所属包: com.itheima.reggie.controller

import com.itheima.reggie.service.OrderService;
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("/order")
public class OrderController {@Autowiredprivate OrderService orderService;
}
import com.itheima.reggie.service.OrderDetailService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;/*** 订单明细*/
@Slf4j
@RestController
@RequestMapping("/orderDetail")
public class OrderDetailController {@Autowiredprivate OrderDetailService orderDetailService;
}

4.5 代码开发

在OrderController中创建submit方法,处理用户下单的逻辑 :

/*** 用户下单* @param orders* @return*/
@PostMapping("/submit")
public R<String> submit(@RequestBody Orders orders){log.info("订单数据:{}",orders);orderService.submit(orders);return R.success("下单成功");
}

由于下单的逻辑相对复杂,我们可以在OrderService中定义submit方法,来处理下单的具体逻辑:

/**
* 用户下单
* @param orders
*/
public void submit(Orders orders);

然后在OrderServiceImpl中完成下单功能的具体实现,下单功能的具体逻辑如下:

A. 获得当前用户id, 查询当前用户的购物车数据

B. 根据当前登录用户id, 查询用户数据

C. 根据地址ID, 查询地址数据

D. 组装订单明细数据, 批量保存订单明细

E. 组装订单数据, 批量保存订单数据

F. 删除当前用户的购物车列表数据

具体代码实现如下:

@Autowired
private ShoppingCartService shoppingCartService;@Autowired
private UserService userService;@Autowired
private AddressBookService addressBookService;@Autowired
private OrderDetailService orderDetailService;/**
* 用户下单
* @param orders
*/
@Transactional
public void submit(Orders orders) {//获得当前用户idLong 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);
}

备注:

​ 上述逻辑处理中,计算购物车商品的总金额时,为保证我们每一次执行的累加计算是一个原子操作,我们这里用到了JDK中提供的一个原子类 AtomicInteger

4.6 功能测试

代码编写完成,我们重新启动服务,按照前面分析的操作流程进行测试,查看数据是否正常即可。在测试过程中,我们可以通过debug的形式来跟踪代码的正常执行。

检查数据库数据

订单表插入一条记录:

订单明细表插入四条记录():

同时,购物车的数据被删除:

rders.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);

}


> 备注:
>
> ​  上述逻辑处理中,计算购物车商品的总金额时,为保证我们每一次执行的累加计算是一个原子操作,我们这里用到了JDK中提供的一个原子类 AtomicInteger### 4.6 功能测试代码编写完成,我们重新启动服务,按照前面分析的操作流程进行测试,查看数据是否正常即可。在测试过程中,我们可以通过debug的形式来跟踪代码的正常执行。<img src="http://img.sucaiwa.cn/0543f3efc37f11ec9c1b7085c2acd95b.png" alt="image-20210814084822573" style="zoom: 67%;" /> **检查数据库数据**订单表插入一条记录: [外链图片转存中...(img-xi9G9IKZ-1650771105842)] 订单明细表插入四条记录(): [外链图片转存中...(img-KS91BP72-1650771105843)] 同时,购物车的数据被删除: [外链图片转存中...(img-JWohU6QP-1650771105844)]  

JAVA外卖项目第六天 购物车和订单模块相关推荐

  1. JAVA外卖项目第五天 套餐模块和短信验证码

    瑞吉外卖-第五天 课程内容 新增套餐 套餐分页查询 删除套餐 短信发送 手机验证码登录 1. 新增套餐 1.1 需求分析 套餐就是菜品的集合. 后台系统中可以管理套餐信息,通过新增套餐功能来添加一个新 ...

  2. JAVA外卖项目第四天 菜品模块和上传下载

    瑞吉外卖-Day04 课程内容 文件上传下载 菜品新增 菜品分页查询 菜品修改 1. 文件上传下载 1.1 上传介绍 1.1.1 概述 文件上传,也称为upload,是指将本地图片.视频.音频等文件上 ...

  3. JAVA外卖项目第一天 技术选型和包结构

    瑞吉外卖-Day01 课程内容 软件开发整体介绍 瑞吉外卖项目介绍 开发环境搭建 后台登录功能开发 后台退出功能开发 1. 软件开发整体介绍 作为一名软件开发工程师,我们需要了解在软件开发过程中的开发 ...

  4. JAVA外卖项目第九天 前后端分离和项目部署优化

    瑞吉外卖项目优化-Day03 课程内容 前后端分离开发 Yapi Swagger 项目部署 前言 当前项目中,前端代码和后端代码混合在一起,是存在问题的,存在什么问题呢? 主要存在以下几点问题: 1) ...

  5. Java物流项目第三天 订单服务中心和规则引擎

    品达物流TMS项目 第4章 订单中心服务开发(pd-oms) 1. 订单中心服务数据模型 本章要开发的是订单中心微服务,对应的maven工程为pd-oms.订单中心微服务提供TMS中订单的维护功能. ...

  6. Java小型项目:购物车小程序

    package Demo;import java.util.Scanner;/*如何完成添加商品的功能?创建Goods类的对象代表的商品对象,封装用户输入的商品信息把商品对象存入到表示购物车的数组中去 ...

  7. Java物流项目第六天 车辆轨迹服务开发(pd-netty、pd-druid)

    品达物流TMS项目 第7章 车辆轨迹服务开发(pd-netty.pd-druid) 1. apache druid概述 1.1 druid介绍 Apache Druid是一个开源.分布式.高性能的实时 ...

  8. 瑞吉外卖项目的购物车sub操作

    初始的代码: @PostMapping("/sub")public R<String> sub(@RequestBody ShoppingCart shoppingCa ...

  9. SSM实战-外卖项目-06-用户地址簿功能、菜品展示、购物车、下单(一个业务涉及5张表)

    文章目录 外卖项目-第六天 课程内容 1. 用户地址簿功能 1.1 需求分析 1.2 数据模型 1.3 导入功能代码 1.4 功能测试 (其实需求分析里我就自己写了一份代码,而且测试过了,下面再测试了 ...

最新文章

  1. Java查询spark中生成的文件_java+spark-sql查询excel
  2. php面向对象mysqli,php+mysqli使用面向对象方式更新数据库实例
  3. 重定位(搜索KERNEL32.DLL得到API地址)
  4. 014_Collections常用方法
  5. 【OpenCV】IplImage类型图像ROI矩形区域的快速获取
  6. 不符合核销规则条件_对不起!您不符合2020年初级报考条件
  7. node.js JavaScript 严格模式
  8. 80-040-000-原理-MySQL的 ICP
  9. JavaScript-输入输出语法
  10. keepalived+mysql双主高可用配置
  11. 【收藏】李纪为:初入NLP领域的一些小建议
  12. jstat 监控调整GC很好用
  13. 洛谷oj---P1896 [SCOI2005] 互不侵犯
  14. Java编程中常见异常有哪些?
  15. Java poi 表格居中
  16. linux peek,Peek - Gif 录制软件
  17. 【软件测试工程师】App 应用测试方法以及测试思路
  18. 高德地图 街道范围_高德地图发布交通“评诊治”系统:让城市上班族每天多睡十分钟...
  19. Android Gradle权威指南
  20. java输出abba_面试-字符串abba匹配问题-java8解决方案

热门文章

  1. pytorch 梯度累积(gradient accumulation)
  2. PAT 甲级 1016
  3. 用java设置复选框_Java Swing组件复选框JCheckBox用法示例
  4. 服务器连接显示18456,服务器:消息18456,级别16,状态1 用户‘sa’登录失败解决方法...
  5. Linux杂谈之nsswitch.conf配置文件
  6. 电脑系统更新后,连不上网
  7. 用Python制作粒子烟花,提前开始跨年了~
  8. Qt利用布局,widget和ScrollArea实现抽屉效果
  9. YII2 GridView展示图片
  10. 理工大学ACM平台题答案关于C语言 2555 压岁钱