地址簿,指的是移动端消费者用户的地址信息,用户登录成功后可以维护自己的地址信息。同一个用户可以有多个地址信息,但是只能有一个默认地址。
对于地址簿管理,我们需要实现以下几个功能:
用户的地址信息会存储在address_book表,即地址簿表中。具体表结构如下:
这里面有一个字段is_default,实际上我们在设置默认地址时,只需要更新这个字段就可以了。
对于这一类的单表的增删改查,我们已经写过很多了,基本的开发思路都是一样的,那么本小节的用户地址簿管理的增删改查功能,我们就不再一一实现了,基本的代码我们都已经提供了,直接导入进来,做一个测试即可。
对于下面的地址管理的代码,我们可以直接从资料拷贝,也可以直接从下面的讲义中复制。
1). 实体类 AddressBook(直接从课程资料中导入即可)
所属包: com.itheima.reggie.entity
package com.itheima.reggie.entity;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 地址簿
*/
@Data
public class AddressBook implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
//用户id
private Long userId;
//收货人
private String consignee;
//手机号
private String phone;
//性别 0 女 1 男
private String sex;
//省级区划编号
private String provinceCode;
//省级名称
private String provinceName;
//市级区划编号
private String cityCode;
//市级名称
private String cityName;
//区级区划编号
private String districtCode;
//区级名称
private String districtName;
//详细地址
private String detail;
//标签
private String label;
//是否默认 0 否 1是
private Integer isDefault;
//创建时间
private LocalDateTime createTime;
//更新时间
private LocalDateTime updateTime;
//创建人
private Long createUser;
//修改人
private Long updateUser;
//是否删除
private Integer isDeleted;
}
2). Mapper接口 AddressBookMapper(直接从课程资料中导入即可)
所属包: com.itheima.reggie.mapper
package com.itheima.reggie.mapper;
import com.itheima.reggie.entity.AddressBook;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
@Mapper
public interface AddressBookMapper {
//添加地址
@Insert("insert into address_book(user_id,consignee,phone,sex,detail,label,create_time,update_time," +
"create_user,update_user) " +
"values(#{userId},#{consignee},#{phone},#{sex},#{detail},#{label},#{createTime},#{updateTime},#{createUser}," +
"#{updateUser})")
void save(AddressBook addressBook);
//查询地址列表
@Select("select * from address_book where user_id=#{userId} order by update_time desc")
List<AddressBook> queryAddressList(Long userId);
@Update("update address_book set is_default=0 where user_id=#{userId}")
void removeDefaultAddress(Long userId);
@Update("update address_book set is_default=1 where id=#{id}")
void updateDefaultAddress(AddressBook addressBook);
@Select("select * from address_book where id=#{id}")
AddressBook getById(Long id);
@Select("select * from address_book where user_id = #{userId} and is_default = 1")
AddressBook getDefaultAddress(Long userId);
}
3). 业务层接口 AddressBookService(直接从课程资料中导入即可)
所属包: com.itheima.reggie.service
package com.itheima.reggie.service;
import com.itheima.reggie.entity.AddressBook;
import java.util.List;
public interface AddressBookService {
//保存地址
void save(AddressBook addressBook);
//查询地址对象
List<AddressBook> queryAddressList(Long currentId);
//更新默认地址
void updateDefaultAddress(AddressBook addressBook);
//根据id查询
AddressBook getById(Long id);
//得到默认地址
AddressBook getDefaultAddress(Long currentId);
}
4). 业务层实现类 AddressBookServiceImpl(直接从课程资料中导入即可)
所属包: com.itheima.reggie.service.impl
package com.itheima.reggie.service.impl;
import com.itheima.reggie.entity.AddressBook;
import com.itheima.reggie.mapper.AddressBookMapper;
import com.itheima.reggie.service.AddressBookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class AddressBookServiceImpl implements AddressBookService{
@Autowired
private AddressBookMapper addressBookMapper;
@Override
public void save(AddressBook addressBook) {
addressBookMapper.save(addressBook);
}
@Override
public List<AddressBook> queryAddressList(Long userId) {
List<AddressBook> addressBookList = addressBookMapper.queryAddressList(userId);
return addressBookList;
}
//更新默认地址
@Override
@Transactional
public void updateDefaultAddress(AddressBook addressBook) {
//1. 把该用户地址全部修改为非默认
addressBookMapper.removeDefaultAddress(addressBook.getUserId());
//2 重新设置当前地址为默认地址
addressBookMapper.updateDefaultAddress(addressBook);
}
@Override
public AddressBook getById(Long id) {
return addressBookMapper.getById(id);
}
@Override
public AddressBook getDefaultAddress(Long currentId) {
return addressBookMapper.getDefaultAddress(currentId);
}
}
5). 控制层 AddressBookController(直接从课程资料中导入即可)
所属包: com.itheima.reggie.controller
controller主要开发的功能:
A. 新增地址逻辑说明:
B. 设置默认地址
每个用户可以有很多地址,但是默认地址只能有一个 ;
先将该用户所有地址的is_default更新为0 , 然后将当前的设置的默认地址的is_default设置为1
C. 根据ID查询地址
D. 查询默认地址
E. 查询指定用户的全部地址
代码实现如下:
package com.itheima.reggie.controller;
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 javax.servlet.http.HttpSession;
import java.time.LocalDateTime;
import java.util.List;
/**
* 地址簿管理
*/
@Slf4j
@RestController
@RequestMapping("/addressBook")
public class AddressBookController {
@Autowired
private AddressBookService addressBookService;
/**
* 新增
*/
@PostMapping
public R<AddressBook> save(@RequestBody AddressBook addressBook, HttpSession session) {
Long userId = (Long) session.getAttribute("user");
addressBook.setUserId(userId);
addressBook.setCreateUser(userId);
addressBook.setUpdateUser(userId);
addressBook.setCreateTime(LocalDateTime.now());
addressBook.setUpdateTime(LocalDateTime.now());
addressBookService.save(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(HttpSession session) {
Long userId = (Long) session.getAttribute("user");
AddressBook addressBook = addressBookService.getDefaultAddress(userId);
if (null == addressBook) {
return R.error("没有找到该对象");
} else {
return R.success(addressBook);
}
}
/**
* 设置默认地址
*/
@PutMapping("default")
public R<AddressBook> setDefault(@RequestBody AddressBook addressBook,HttpSession session) {
Long userId = (Long) session.getAttribute("user");
addressBook.setUserId(userId);
addressBookService.updateDefaultAddress(addressBook);
return R.success(addressBook);
}
/**
* 查询指定用户的全部地址
*/
@GetMapping("/list")
public R<List<AddressBook>> list(HttpSession session) {
Long userId = (Long) session.getAttribute("user");
List<AddressBook> addressList = addressBookService.queryAddressList(userId);
//SQL:select * from address_book where user_id = ? order by update_time desc
return R.success(addressList);
}
}
代码导入进来,并且去阅读了一下地址管理各个功能的逻辑实现,接下来,我们就可以启动项目,进行一个测试。测试过程中,通过debug断点调试观察服务端程序的执行过程,在浏览器中使用调试工具查看页面和服务端的交互过程和请求响应数据。
1). 新增
填写表单数据,点击保存地址,查看网络请求。
测试完毕之后,检查数据库中的数据,是否正常插入。
2). 列表查询
当新增地址完成后,页面会再次发送一个请求,来查询该用户的所有地址列表,在界面进行展示。
3). 设置默认
在地址列表页面中,勾选 “设为默认地址” ,此时会发送PUT请求,来设置默认地址。
测试完毕后,我们再次查看数据库表中的数据:
用户登录成功后跳转到系统首页,在首页需要根据分类来展示菜品和套餐。如果菜品设置了口味信息,需要展示按钮,否则显示按钮。
在开发代码之前,需要梳理一下前端页面和服务端的交互过程:
1). 页面(front/index.html)发送ajax请求,获取分类数据(菜品分类和套餐分类)
该功能在之前的业务开发中,我们都已经实现了。通过请求响应的数据,我们也可以看到数据是可以正确获取到的。
注意:首页加载时,不仅发送请求获取分类列表,还发送了一次ajax请求用于加载购物车数据,而这两次请求必须全部成功,页面才可以正常渲染,而当前购物车列表查询功能还未实现(报404),所以列表目前并未渲染。此处可以将这次请求的地址暂时修改一下,从静态json文件获取数据,等后续开发购物车功能时再修改回来,并且把cartData.json的文件拷贝到front目录下, 如下:
修改之后,我们再次测试:
目前该部分的功能我们已经调通,左侧的分类菜单,和右侧的菜品信息我们都可以看到,后续我们只需要将购物车列表的数据改成调用服务端接口查询即可。
2). 页面发送ajax请求,获取第一个分类下的菜品或者套餐
A. 根据分类ID查询套餐列表:
B. 根据分类ID查询菜品列表:
异步请求,查询分类对应的菜品列表,功能我们已经实现了,但是我们之前查询的只是菜品的基本信息,不包含菜品的口味信息。所以在前端界面中,我们看不到选择菜品分类的信息。
经过上述的分析,我们可以看到,服务端我们主要提供两个方法, 分别用来:
A. 根据分类ID查询菜品列表(包含菜品口味列表), 具体请求信息如下:
请求 | 说明 |
---|---|
请求方式 | GET |
请求路径 | /dish/list |
请求参数 | ?categoryId=1397844263642378242&status=1 |
该功能在服务端已经实现,我们需要修改此方法,在原有方法的基础上增加查询菜品的口味信息。
B. 根据分类ID查询套餐列表, 具体请求信息如下:
请求 | 说明 |
---|---|
请求方式 | GET |
请求路径 | /setmeal/list |
请求参数 | ?categoryId=1397844263642378242&status=1 |
该功能在服务端并未实现。
由于之前我们实现的根据分类查询菜品列表,仅仅查询了菜品的基本信息,未查询菜品口味信息,而移动端用户在点餐时,是需要选择口味信息的,所以我们需要对之前的代码实现进行完善,那么如何完善呢?
我们需要修改DishController的list方法,原来此方法的返回值类型为:R。为了满足移动端对数据的要求(菜品基本信息和菜品对应的口味信息),现在需要将方法的返回值类型改为:R
,因为在DishDto中封装了菜品对应的口味信息:
代码逻辑:
A. 根据分类ID查询,查询目前正在启售的菜品列表 (已实现)
B. 遍历菜品列表,并查询菜品的分类信息及菜品的口味列表
C. 组装数据DishDto,并返回
代码实现:
package com.itheima.reggie.controller;
import com.itheima.reggie.common.R;
import com.itheima.reggie.dto.DishDto;
import com.itheima.reggie.dto.SetmealDto;
import com.itheima.reggie.entity.Dish;
import com.itheima.reggie.entity.Page;
import com.itheima.reggie.service.DishFlavorService;
import com.itheima.reggie.service.DishService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.List;
/**
* 菜品管理
*/
@RestController
@RequestMapping("/dish")
@Slf4j
public class DishController {
@Autowired
private DishService dishService;
/**
* 要求: 返回的菜品必须要携带口味信息回去
* 根据类别查询菜品
* @param categoryId
* @return
*/
@GetMapping("list")
public R<List<DishDto>> list(Long categoryId,Integer status){
R<List<DishDto>> result= dishService.findByCategoryId(categoryId,status);
return result;
}
}
package com.itheima.reggie.service;
import com.itheima.reggie.common.R;
import com.itheima.reggie.dto.DishDto;
import com.itheima.reggie.entity.Dish;
import com.itheima.reggie.entity.Page;
import java.util.List;
public interface DishService {
/**
* 根据类别查询菜品
* @param categoryId
* @return
*/
R<List<DishDto>> findByCategoryId(Long categoryId,Integer status);
}
package com.itheima.reggie.service.impl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.itheima.reggie.common.R;
import com.itheima.reggie.dto.DishDto;
import com.itheima.reggie.entity.*;
import com.itheima.reggie.mapper.DishMapper;
import com.itheima.reggie.service.CategoryService;
import com.itheima.reggie.service.DishFlavorService;
import com.itheima.reggie.service.DishService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class DishServiceImpl implements DishService {
@Autowired(required = false)
private DishMapper dishMapper;
@Autowired
private DishFlavorService dishFlavorService;
@Autowired
private CategoryService categoryService;
/**
* 根据类别查询菜品
* @param categoryId
* @return
*/
@Override
public R<List<DishDto>> findByCategoryId(Long categoryId,Integer status) {
//根据类别查找到该类别对应的菜品
List<Dish> dishList = dishMapper.findByCategoryId(categoryId,status);
//2. 遍历dish,把每一个dish转换为dishDto,并且设置上口味
List<DishDto> dishDtoList = dishList.stream().map(dish -> {
DishDto dishDto = new DishDto();
//属性拷贝
BeanUtils.copyProperties(dish, dishDto);
//查询该菜品的口味
List<DishFlavor> dishFlavorList = dishFlavorMapper.findByDishId(dish.getId());
dishDto.setFlavors(dishFlavorList);
return dishDto;
}).collect(Collectors.toList());
return R.success(dishDtoList);
}
}
DishMapper
package com.itheima.reggie.mapper;
import com.itheima.reggie.dto.DishDto;
import com.itheima.reggie.entity.Dish;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
//菜品
public interface DishMapper {
@Select("select * from dish where category_id=#{categoryId} and status=#{status}")
List<Dish> findByCategoryId(@Param("categoryId") Long categoryId,@Param("status") Integer status);
}
B. 根据分类ID查询套餐列表, 具体请求信息如下:
请求 | 说明 |
---|---|
请求方式 | GET |
请求路径 | /setmeal/list |
请求参数 | ?categoryId=1397844263642378242&status=1 |
package com.itheima.reggie.controller;
import com.itheima.reggie.common.R;
import com.itheima.reggie.dto.SetmealDto;
import com.itheima.reggie.entity.Page;
import com.itheima.reggie.entity.Setmeal;
import com.itheima.reggie.service.SetmealService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.List;
@RestController
@RequestMapping("/setmeal")
@Slf4j
public class SetmealController {
@Autowired
private SetmealService setmealService;
/**
* 根据套餐的类别展示套餐
* @param categoryId
* @param status
* @return
*/
@GetMapping("/list")
public R<List<Setmeal>> list(Long categoryId, Integer status) {
List<Setmeal> setmealList = setmealService.list(categoryId,status);
return R.success(setmealList);
}
}
package com.itheima.reggie.service;
import com.itheima.reggie.dto.SetmealDto;
import com.itheima.reggie.entity.Page;
import com.itheima.reggie.entity.Setmeal;
import org.omg.CORBA.INTERNAL;
import java.util.List;
public interface SetmealService {
/**
* 根据套餐的类别展示套餐
* @param categoryId
* @param status
* @return
*/
List<Setmeal> list(Long categoryId, Integer status);
}
package com.itheima.reggie.service.impl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.itheima.reggie.dto.SetmealDto;
import com.itheima.reggie.entity.Category;
import com.itheima.reggie.entity.Page;
import com.itheima.reggie.entity.Setmeal;
import com.itheima.reggie.entity.SetmealDish;
import com.itheima.reggie.exception.CustomException;
import com.itheima.reggie.mapper.CategoryMapper;
import com.itheima.reggie.mapper.SetMealMapper;
import com.itheima.reggie.mapper.SetmealDishMapper;
import com.itheima.reggie.service.SetmealService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class SetmealServiceImpl implements SetmealService {
@Autowired(required = false)
private SetMealMapper setMealMapper;
@Autowired(required = false)
private SetmealDishMapper setmealDishMapper;
@Autowired(required = false)
private CategoryMapper categoryMapper;
/**
* 根据套餐的类别展示套餐
* @param categoryId
* @param status
* @return
*/
@Override
public List<Setmeal> list(Long categoryId, Integer status) {
return setmealDishMapper.list(categoryId,status);
}
}
@Select("select * from setmeal where category_id=#{categoryId} and status=#{status}")
List<Setmeal> list(@Param("categoryId") Long categoryId,@Param("status") Integer status);
把菜品展示的功能代码完善完成之后,我们重新启动服务,来测试一个菜品展示的功能。测试过程中可以使用浏览器的监控工具查看页面和服务端的数据交互细节。
点击分类,根据分类查询菜品列表/套餐列表:
移动端用户可以将菜品或者套餐添加到购物车。对于菜品来说,如果设置了口味信息,则需要选择规格后才能加入购物车;对于套餐来说,可以直接点击将当前套餐加入购物车。在购物车中可以修改菜品和套餐的数量,也可以清空购物车。
这里面我们需要实现的功能包括:
1). 添加购物车
2). 查询购物车
3). 清空购物车
用户的购物车数据,也是需要保存在数据库中的,购物车对应的数据表为shopping_cart表,具体表结构如下:
说明:
最终shopping_cart表中存储的数据示例:
在开发代码之前,需要梳理一下购物车操作时前端页面和服务端的交互过程:
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 |
分析完毕购物车的业务需求和实现思路之后,在开发业务功能前,先将需要用到的类和接口基本结构创建好:
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;
//用户id
private Long userId;
//菜品id
private Long dishId;
//套餐id
private Long setmealId;
//口味
private String dishFlavor;
//数量
private Integer number;
//金额
private BigDecimal amount;
//图片
private String image;
private LocalDateTime createTime;
}
2). Mapper接口 ShoppingCartMapper
所属包: com.itheima.reggie.mapper
package com.itheima.reggie.mapper;
import com.itheima.reggie.entity.ShoppingCart;
import org.apache.ibatis.annotations.Mapper;
public interface ShoppingCartMapper {
}
3). 业务层接口 ShoppingCartService
所属包: com.itheima.reggie.service
package com.itheima.reggie.service;
public interface ShoppingCartService {
}
4). 业务层实现类 ShoppingCartServiceImpl
所属包: com.itheima.reggie.service.impl
package com.itheima.reggie.service.impl;
import com.itheima.reggie.service.ShoppingCartService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {
@Autowired
private ShoppingCartMapper shoppingCartMapper
}
5). 控制层 ShoppingCartController
所属包: com.itheima.reggie.controller
package 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 {
@Autowired
private ShoppingCartService shoppingCartService;
}
package com.itheima.reggie.controller;
import com.itheima.reggie.entity.R;
import com.itheima.reggie.entity.ShoppingCart;
import com.itheima.reggie.service.ShoppingCartService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpSession;
/**
* 购物车
*/
@Slf4j
@RestController
@RequestMapping("/shoppingCart")
public class ShoppingCartController {
@Autowired
private ShoppingCartService shoppingCartService;
/**
* 作用:添加购物车
* @param shoppingCart 添加菜品的的信息
* @param session 回话域
* @return
*/
@PostMapping("/add")
public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart, HttpSession session){
//1. 获取当前登录的用户
Long userId = (Long) session.getAttribute("user");
//2. 给购物车设置所属的用户
shoppingCart.setUserId(userId);
//3.交给service
R<ShoppingCart> result = shoppingCartService.add(shoppingCart);
//4. 返回结果,结果购物项的信息
return result;
}
}
package com.itheima.reggie.service;
import com.itheima.reggie.entity.R;
import com.itheima.reggie.entity.ShoppingCart;
import org.springframework.transaction.annotation.Transactional;
public interface ShoppingCartService {
/**
* 添加购物车
* @param shoppingCart 本次添加的信息
* @return
*/
R<ShoppingCart> add(ShoppingCart shoppingCart);
}
package com.itheima.reggie.service.impl;
import com.itheima.reggie.entity.R;
import com.itheima.reggie.entity.ShoppingCart;
import com.itheima.reggie.mapper.ShoppingCartMapper;
import com.itheima.reggie.service.ShoppingCartService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {
@Autowired(required = false)
private ShoppingCartMapper shoppingCartMapper;
/**
* 添加购物车
* @param shoppingCart 本次添加的信息
* @return
*/
@Override
public R<ShoppingCart> add(ShoppingCart shoppingCart) {
//1. 根据用户的id与dishId或者是setmealId去查询该用户是否已经购买了该商品。
ShoppingCart shoppingCartOne = shoppingCartMapper.findByUserIdAndDidOrSid(shoppingCart);
//2. 如果是没有购买过该商品,创建一个新的购物项,补全信息,插入。
if(shoppingCartOne==null){
shoppingCart.setNumber(1);
shoppingCart.setCreateTime(LocalDateTime.now());
shoppingCartMapper.save(shoppingCart);
shoppingCartOne = shoppingCart;
}else{
//3. 如果之前就已经购买过该商品,只需要数量+1 ,然后更新该记录
shoppingCartOne.setNumber(shoppingCartOne.getNumber()+1);
shoppingCartMapper.update(shoppingCartOne);
}
return R.success(shoppingCartOne);
}
}
package com.itheima.reggie.mapper;
import com.itheima.reggie.entity.ShoppingCart;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Update;
public interface ShoppingCartMapper {
/**
* 根据用户的id与dishId或者是setmealId去查询该用户是否已经购买了该商品。
* @param shoppingCart
* @return
*/
ShoppingCart findByUserIdAndDidOrSid(ShoppingCart shoppingCart);
/**
* 保存购物车
* @param shoppingCart
*/
@Insert("insert into shopping_cart values(null,#{name},#{image},#{userId},#{dishId},#{setmealId},#{dishFlavor},#{number},#{amount},#{createTime})")
void save(ShoppingCart shoppingCart);
/**
* 更新购物车
* @param shoppingCartOne
*/
@Update("update shopping_cart set number=#{number} where id=#{id}")
void update(ShoppingCart shoppingCartOne);
}
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.itheima.reggie.mapper.ShoppingCartMapper">
<select id="findByUserIdAndDidOrSid" resultType="ShoppingCart">
select * from shopping_cart where user_id=#{userId}
<if test="dishId!=null">
and dish_id =#{dishId}
if>
<if test="setmealId!=null">
and setmeal_id =#{setmealId}
if>
select>
mapper>
在前端js正常发出查看购物车的请求
在ShoppingCartController中创建list方法,根据当前登录用户ID查询购物车列表,并对查询的结果进行创建时间的倒序排序。
代码实现如下:
package com.itheima.reggie.controller;
import com.itheima.reggie.common.BaseContext;
import com.itheima.reggie.common.R;
import com.itheima.reggie.entity.ShoppingCart;
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 {
@Autowired
private ShoppingCartService shoppingCartService;
/**
* 作用:查看该用户的购物车信息
* @return
*/
@GetMapping("/list")
public R<List<ShoppingCart>> list(HttpSession session){
//1. 获取当前登录的用户
Long userId = (Long) session.getAttribute("user");
//2. 查看当前用户逇购物车信息
R<List<ShoppingCart>> result = shoppingCartService.findCartByUserId(userId);
return result;
}
}
package com.itheima.reggie.service;
import com.itheima.reggie.entity.ShoppingCart;
public interface ShoppingCartService {
/**
* 根据用户id查看用户的购物车
* @param userId
* @return
*/
R<List<ShoppingCart>> findCartByUserId(Long userId);
}
package com.itheima.reggie.service.impl;
import com.itheima.reggie.entity.ShoppingCart;
import com.itheima.reggie.mapper.ShoppingCartMapper;
import com.itheima.reggie.service.ShoppingCartService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {
@Autowired(required = false)
private ShoppingCartMapper shoppingCartMapper;
/**
* 根据用户id查看用户的购物车
* @param userId
* @return
*/
@Override
public R<List<ShoppingCart>> findCartByUserId(Long userId) {
List<ShoppingCart> shoppingCartList = shoppingCartMapper.findCartByUserId(userId);
return R.success(shoppingCartList);
}
}
package com.itheima.reggie.mapper;
import com.itheima.reggie.entity.ShoppingCart;
import org.apache.ibatis.annotations.*;
public interface ShoppingCartMapper {
@Select("select * from shopping_cart where user_id=#{userId}")
List<ShoppingCart> findCartByUserId(Long userId);
}
在ShoppingCartController中创建clean方法,在方法中获取当前登录用户,根据登录用户ID,删除购物车数据。
代码实现如下:
package com.itheima.reggie.controller;
import com.itheima.reggie.common.BaseContext;
import com.itheima.reggie.common.R;
import com.itheima.reggie.entity.ShoppingCart;
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 {
@Autowired
private ShoppingCartService shoppingCartService;
/**
* 作用:清空购物车
* @return
*/
@DeleteMapping("/clean")
public R<String> clean(HttpSession session){
//1. 获取当前登录的用户
Long userId = (Long) session.getAttribute("user");
shoppingCartService.cleanCart(userId);
return R.success("清空购物车成功");
}
}
package com.itheima.reggie.service;
import com.itheima.reggie.entity.ShoppingCart;
public interface ShoppingCartService {
//清空购物车
void cleanCart(Long userId);
}
package com.itheima.reggie.service.impl;
import com.itheima.reggie.entity.ShoppingCart;
import com.itheima.reggie.mapper.ShoppingCartMapper;
import com.itheima.reggie.service.ShoppingCartService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {
@Autowired(required = false)
private ShoppingCartMapper shoppingCartMapper;
/**
* 清空购物车
* @param userId
*/
@Override
public void cleanCart(Long userId) {
shoppingCartMapper.cleanCart(userId);
}
}
package com.itheima.reggie.mapper;
import com.itheima.reggie.entity.ShoppingCart;
import org.apache.ibatis.annotations.*;
public interface ShoppingCartMapper {
@Delete("DELETE FROM shopping_cart WHERE user_id=#{userId}")
void cleanCart(Long userId);
}
按照前面分析的操作流程进行测试,测试功能以及数据库中的数据是否是否正常。
1). 添加购物车
当添加的是菜品信息,而这个用户的购物车中当前并没有这个菜品时,添加一条数据,数量为1。
检查数据库数据,由于是菜品保存的是dish_id。
这时在页面上,我们可以继续点击+号,在购物车中增加该菜品,此时,应该是对现有的购物车菜品数量加1,而不应该插入新的记录。
检查数据库数据:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F3AvrtYV-1641022452023)(assets/image-20210814071707767.png)]
如果添加的是套餐,该套餐在当前用户的购物车中并不存在,则添加一条数据,数量为1。
检查数据库数据:
2). 查看购物车
点击页面下面的购物车边栏,查看购物车数据列表是否正常展示。
3). 清空购物车
在购物车列表展示页中点击"清空", 查看购物车是否被清空。
并检查数据库中的数据,可以看到数据已经被删除。
移动端用户将菜品或者套餐加入购物车后,可以点击购物车中的 “去结算” 按钮,页面跳转到订单确认页面,点击 “去支付” 按钮则完成下单操作。
这里,我们需要说明一下,这里并不会去开发支付功能,因为不论是支付宝的支付,还是微信支付,都是需要企业资质的,而我们大家在测试的时候,是没有办法提供企业资质的,所以这一部分支付功能我们就不去实现了。
注意: 下单的本质就是把购物车的数据转移到order表中,并且删除购物车的数据
用户下单业务对应的数据表为orders表和order_detail表(一对多关系,一个订单关联多个订单明细):
表名 | 含义 | 说明 |
---|---|---|
orders | 订单表 | 主要存储订单的基本信息(如: 订单号、状态、金额、支付方式、下单用户、收件地址等) |
order_detail | 订单明细表 | 主要存储订单详情信息(如: 该订单关联的套餐及菜品的信息) |
具体的表结构如下:
A. orders 订单表
B. order_detail
数据示例:
用户提交订单时,需要往订单表orders中插入一条记录,并且需要往order_detail中插入一条或多条记录。
在开发代码之前,需要梳理一下用户下单操作时前端页面和服务端的交互过程:
1). 在购物车中点击按钮,页面跳转到订单确认页面
页面跳转前端已经完成,我们无需操作。
2). 在订单确认页面,发送ajax请求,请求服务端获取当前登录用户的默认地址
该功能在用户地址簿管理功能开发时,已经实现(导入),我们无需操作。
3). 在订单确认页面,发送ajax请求,请求服务端获取当前登录用户的购物车数据
该功能已经实现,我们无需操作。
4). 在订单确认页面点击按钮,发送ajax请求,请求服务端完成下单操作
经过上述的分析,我们看到前三步的功能我们都已经实现了,我们主要需要实现最后一步的下单功能,该功能具体的请求信息如下:
请求 | 说明 |
---|---|
请求方式 | POST |
请求路径 | /order/submit |
请求参数 | {“remark”:“老板,记得带一次性筷子”,“payMethod”:1,“addressBookId”:“1425792459560005634”} |
在开发业务功能前,先将需要用到的类和接口基本结构创建好:
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;
//下单用户id
private Long userId;
//地址id
private 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;
//订单id
private Long orderId;
//菜品id
private Long dishId;
//套餐id
private Long setmealId;
//口味
private String dishFlavor;
//数量
private Integer number;
//金额
private BigDecimal amount;
//图片
private String image;
}
在OrderController中创建submit方法,处理用户下单的逻辑 :
package com.itheima.reggie.controller;
import com.itheima.reggie.entity.Orders;
import com.itheima.reggie.entity.R;
import com.itheima.reggie.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
/**
*
* @param orders
* @param session
* @return
*/
@PostMapping("/submit")
public R<String> submit(@RequestBody Orders orders, HttpSession session){
//获取当前登录的用户
Long userId = (Long) session.getAttribute("user");
//2。把用户的信息设置到orders里面
orders.setUserId(userId);
//3 交给orderService
orderService.addOrder(orders);
return R.success("下单成功");
}
}
由于下单的逻辑相对复杂,我们可以在OrderService中定义submit方法,来处理下单的具体逻辑:
package com.itheima.reggie.service;
import com.itheima.reggie.entity.Orders;
import org.springframework.transaction.annotation.Transactional;
public interface OrderService {
//用户下单
@Transactional
void addOrder(Orders orders);
}
然后在OrderServiceImpl中完成下单功能的具体实现,下单功能的具体逻辑如下:
A. 获得当前用户id, 查询当前用户的购物车数据
B. 根据当前登录用户id, 查询用户数据
C. 根据地址ID, 查询地址数据
D. 组装订单明细数据, 批量保存订单明细
E. 组装订单数据, 批量保存订单数据
F. 删除当前用户的购物车列表数据
具体代码实现如下:
package com.itheima.reggie.service.impl;
import com.itheima.reggie.entity.*;
import com.itheima.reggie.mapper.*;
import com.itheima.reggie.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Service
public class OrderServiceImpl implements OrderService {
@Autowired(required = false)
private OrderMapper orderMapper;
@Autowired(required = false)
private OrderDetailMapper orderDetailMapper;
@Autowired(required = false)
private ShoppingCartMapper shoppingCartMapper;
@Autowired(required = false)
private UserMapper userMapper;
@Autowired(required = false)
private AddressBookMapper addressBookMapper;
@Override
public void addOrder(Orders orders) {
// A. 获得当前用户id, 查询当前用户的购物车数据
List<ShoppingCart> shoppingCartList = shoppingCartMapper.findCartByUserId(orders.getUserId());
// B. 根据当前登录用户id, 查询用户数据
User user = userMapper.findById(orders.getUserId());
// C. 根据地址ID, 查询地址数据
AddressBook addressBook = addressBookMapper.getById(orders.getAddressBookId());
//定义一个变量保存订单id
String orderId = UUID.randomUUID().toString();
//定义一个变量保存总金额
BigDecimal totalAmount = new BigDecimal("0");
// D. 遍历所有的购物车的购物项,每一个购物项就对应一个订单详情数据。 组装订单明细数据, 批量保存订单明细
List<OrderDetail> orderDetailList = new ArrayList<>();
for (ShoppingCart shoppingCart : shoppingCartList) {
OrderDetail orderDetail = new OrderDetail(); //快速生成setter方法,alt+enter
orderDetail.setId(UUID.randomUUID().toString()); //订单详情的id ,注意:这里我们使用的uuid.
orderDetail.setName(shoppingCart.getName()); //菜品名字或者是套餐的名字
orderDetail.setOrderId(orderId); //订单号
orderDetail.setDishId(shoppingCart.getDishId()); //菜品的id
orderDetail.setSetmealId(shoppingCart.getSetmealId()); //套餐的id
orderDetail.setDishFlavor(shoppingCart.getDishFlavor()); //口味
orderDetail.setNumber(shoppingCart.getNumber()); //数量
orderDetail.setAmount(shoppingCart.getAmount()); //单价
orderDetail.setImage(shoppingCart.getImage()); //图片
//计算每一个购物的总金额
BigDecimal itemAmount = new BigDecimal(shoppingCart.getNumber() + "").multiply(shoppingCart.getAmount());
//把每一个购物项的总金额相加
totalAmount = totalAmount.add(itemAmount);
orderDetailList.add(orderDetail);
}
// E. 组装订单数据, 批量保存订单数据
orders.setId(orderId); //订单号
orders.setNumber(orderId); //订单号
orders.setStatus(1); //状态
orders.setOrderTime(LocalDateTime.now()); //下单时间
orders.setCheckoutTime(LocalDateTime.now()); //支付的时间
orders.setAmount(totalAmount); //总金额
orders.setUserName(user.getName()); //订单所属用户的用户名
orders.setPhone(addressBook.getPhone()); //收货人手机
orders.setAddress(addressBook.getDetail()); //收货人地址
orders.setConsignee(addressBook.getConsignee()); //收货人的姓名
//保存订单
orderMapper.save(orders);
//保存订单项
orderDetailMapper.save(orderDetailList);
// F. 删除当前用户的购物车列表数据
shoppingCartMapper.cleanCart(user.getId());
}
}
OrderMapper
package com.itheima.reggie.mapper;
import com.itheima.reggie.entity.Orders;
import org.apache.ibatis.annotations.Insert;
public interface OrderMapper {
@Insert("insert into orders values(#{id},#{number},#{status},#{userId},#{addressBookId},#{orderTime},#{checkoutTime},#{payMethod},#{amount},#{remark},#{phone},#{address},#{userName},#{consignee})")
void save(Orders orders);
}
OrderDetailMapper
package com.itheima.reggie.mapper;
import com.itheima.reggie.entity.OrderDetail;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface OrderDetailMapper {
void save(@Param("orderDetailList") List<OrderDetail> orderDetailList);
}
OrderDetailMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.itheima.reggie.mapper.OrderDetailMapper">
<insert id="save">
insert into order_detail values
<foreach collection="orderDetailList" item="orderDetail" separator=",">
(#{orderDetail.id},#{orderDetail.name},#{orderDetail.image},#{orderDetail.orderId},#{orderDetail.dishId},
#{orderDetail.setmealId},#{orderDetail.dishFlavor},#{orderDetail.number},#{orderDetail.amount})
foreach>
insert>
mapper>
UserMapper
package com.itheima.reggie.mapper;
import com.itheima.reggie.entity.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
public interface UserMapper {
@Select("select * from user where id=#{id}")
User findById(Long userId);
}
代码编写完成,我们重新启动服务,按照前面分析的操作流程进行测试,查看数据是否正常即可。在测试过程中,我们可以通过debug的形式来跟踪代码的正常执行。
检查数据库数据
订单表插入一条记录:
订单明细表插入四条记录():
同时,购物车的数据被删除:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b6zCRAsG-1641022452032)(assets/image-20210814085058814.png)]
},#{number},#{status},#{userId},#{addressBookId},#{orderTime},#{checkoutTime},#{payMethod},#{amount},#{remark},#{phone},#{address},#{userName},#{consignee})")
void save(Orders orders);
}
OrderDetailMapper
```java
package com.itheima.reggie.mapper;
import com.itheima.reggie.entity.OrderDetail;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface OrderDetailMapper {
void save(@Param("orderDetailList") List orderDetailList);
}
OrderDetailMapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.itheima.reggie.mapper.OrderDetailMapper">
<insert id="save">
insert into order_detail values
<foreach collection="orderDetailList" item="orderDetail" separator=",">
(#{orderDetail.id},#{orderDetail.name},#{orderDetail.image},#{orderDetail.orderId},#{orderDetail.dishId},
#{orderDetail.setmealId},#{orderDetail.dishFlavor},#{orderDetail.number},#{orderDetail.amount})
foreach>
insert>
mapper>
UserMapper
package com.itheima.reggie.mapper;
import com.itheima.reggie.entity.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
public interface UserMapper {
@Select("select * from user where id=#{id}")
User findById(Long userId);
}
代码编写完成,我们重新启动服务,按照前面分析的操作流程进行测试,查看数据是否正常即可。在测试过程中,我们可以通过debug的形式来跟踪代码的正常执行。
检查数据库数据
订单表插入一条记录:
[外链图片转存中…(img-LAhSu5dG-1641022452030)]
订单明细表插入四条记录():
[外链图片转存中…(img-EqrLYLd6-1641022452031)]
同时,购物车的数据被删除:
[外链图片转存中…(img-b6zCRAsG-1641022452032)]