1.手机验证码登录
1.1 需求分析
客户输入手机号码,获取验证码后,输入后能成功登录。
1.2 用户登录
首先修改拦截器,将手机端的发送短信和登录请求加到过滤器的放行请求中。
然后在拦截器中加入这些,利用session判断用户是否登录,用户如果没有登录就进行拦截。
//4-2、判断移动端登录状态,如果已登录,则直接放行
if(request.getSession().getAttribute("user") != null){
log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("user"));
//利用基于ThreadLocal的工具类来获取用户id
Long userId=(Long) request.getSession().getAttribute("user");
BaseContext.setCurrentId(userId);
long id = Thread.currentThread().getId();
log.info("线程id:{}",id);
filterChain.doFilter(request,response);
return;
}
然后就是处理手机端发出的请求。首先是发送短信请求,我们在userController中进行处理。
/**
* 发送验证码
* @return
*/
@PostMapping("/sendMsg")
public R<String> sendMsg(@RequestBody User user, HttpSession session){
//获取手机号
String phone = user.getPhone();
//生成4位验证码
if(StringUtils.isNotEmpty(phone)){
String code = ValidateCodeUtils.generateValidateCode(4).toString();
log.info("验证码:{}",code);
//调用阿里云的短信发送api
//将验证码保存到session
session.setAttribute(phone,code);
return R.success("短信发送成功");
}else{
return R.error("短信发送失败");
}
}
然后是登录请求:
请求体:
我们使用Map来接收数据:
总体逻辑就是
1.比对map中获取的验证码和我们在session中暂存的phone对应的验证码是否相等,相等则登录
2.如果是新用户(即user表中没有相同phone的记录),要自动完成注册(即存入user表中)
3.设置session的user
/**
* 移动端用户登录
* @param map
* @param session
* @return
*/
@PostMapping("/login")
public R<User> login(@RequestBody Map map, HttpSession session){
log.info(map.toString());
String phone = (String) map.get("phone");
String code = (String) map.get("code");
String relCode = (String) session.getAttribute(phone);
if(relCode!=null&&relCode.equals(code)){
LambdaQueryWrapper<User> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(User::getPhone,phone);
//如果是新用户就自动完成注册
User user = userService.getOne(queryWrapper);
if(user==null){
user=new User();
user.setPhone(phone);
userService.save(user);
}
session.setAttribute("user",user.getId());
return R.success(user);
}else{
return R.error("验证码错误");
}
}
测试。发现新用户已经存入user表中。
1.3 用户退出
/**
* 员工退出
* @param request
* @return
*/
@PostMapping("/loginout")
public R<String> logout(HttpServletRequest request){
//清理session
request.getSession().removeAttribute("user");
return R.success("退出成功");
}
2.用户地址簿
2.1 需求分析
导入AddressBook实体类,创建Mapper、Service、ServiceImpl,此处不详细赘述
2.2 新增用户地址簿
请求URL:
请求体:
编写controller方法:
利用BaseContext的ThreadLocal与当前线程绑定的userId作为userid属性的值。
/**
* 新增
*/
@PostMapping
public R<AddressBook> save(@RequestBody AddressBook addressBook) {
addressBook.setUserId(BaseContext.getCurrentId());
log.info("userId:{}",BaseContext.getCurrentId());
log.info("addressBook:{}", addressBook);
addressBookService.save(addressBook);
return R.success(addressBook);
}
测试,添加地址成功。
2.3 将某地址设为默认地址
addressBook实体类有一个属性isDefault,为1则是默认地址,为0则不是。
看看请求:
编写controller代码
总体思路为:
1.获取所有userId与当前用户id相同的记录;
2.将这些记录的isDefault字段全部设置为0;
3.再将传进来的addressBook的usDefault设置为1,去更新数据库。
/**
* 设置默认地址
*/
@PutMapping("default")
public R<AddressBook> setDefault(@RequestBody AddressBook addressBook) {
log.info("addressBook:{}", addressBook);
LambdaUpdateWrapper<AddressBook> wrapper = new LambdaUpdateWrapper<>();
log.info(BaseContext.getCurrentId().toString());
wrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
wrapper.set(AddressBook::getIsDefault, 0);
//先将所有这个用户的isdefault字段改为0
//SQL:update address_book set is_default = 0 where user_id = ?
addressBookService.update(wrapper);
//再将所有这个用户的isdefault字段改为1
addressBook.setIsDefault(1);
//SQL:update address_book set is_default = 1 where id = ?
addressBookService.updateById(addressBook);
return R.success(addressBook);
}
测试,变成了默认地址
2.4 查询全部地址
服务代码:
/**
* 查询指定用户的全部地址
*/
@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 desc
return R.success(addressBookService.list(queryWrapper));
}
2.5 修改地址(自己写的)
修改地址总体分两步:
1.根据id查询地址详细信息,用于回显;
2.提交修改后的地址详细信息。
其中第一个请求已经写好了:
controller:
/**
* 根据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("没有找到该对象");
}
}
我们只需要写第二个请求:
/**
* 提交修改
* @param addressBook
* @return
*/
@PutMapping
public R<AddressBook> update(@RequestBody AddressBook addressBook){
addressBookService.updateById(addressBook);
return R.success(addressBook);
}
测试,修改成功
2.6 删除地址(自己写的)
@DeleteMapping
public R<String> delAddress(Long ids){
addressBookService.removeById(ids);
return R.success("删除成功");
}
测试,删除成功。
3.菜品展示
3.1 需求分析
3.2 代码开发
在登录到首页的过程中,前端发出了两次请求:
首先是category/list请求,这个请求之前已经开发好了,能够获取所有的分类数据
第二个请求是获取购物车数据
这里我们先使用假数据代替。
测试
点击每个分类,还会发出按分类条件查询菜品信息的请求,这个请求我们之前也写过了。
搬出来之前的dishcontroller代码
@GetMapping("/list")
public R<List<Dish>> queryList(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> dishList = dishService.list(queryWrapper);
return R.success(dishList);
}
但是这里我们希望每个菜品后面能够选取对应口味,而不是只是加到购物车。而我们目前的这个请求,只能获取到菜品信息,没有办法拿到口味信息返给前端。为此,我们需要改造这个方法,返回值类型从Dish改为DishDto,用stream流修改内容。
@GetMapping("/list")
public R<List<DishDto>> queryList(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> dishList = dishService.list(queryWrapper);
//stream流处理每个dish对象,去口味表中查询对应菜品的口味数据,最终包装为DishDto类型
List<DishDto> dishDtoList = dishList.stream().map((item) -> {
//新建DishDto对象
DishDto dishDto = new DishDto();
//属性拷贝
BeanUtils.copyProperties(item, dishDto);
//根据dishId查出口味list
Long dishId = item.getId();
LambdaQueryWrapper<DishFlavor> queryWrapper1 = new LambdaQueryWrapper<>();
queryWrapper1.eq(DishFlavor::getDishId, dishId);
List<DishFlavor> flavorList = dishFlavorService.list(queryWrapper1);
//赋值
dishDto.setFlavors(flavorList);
return dishDto;
}).collect(Collectors.toList());
return R.success(dishDtoList);
}
现在就能选对应的菜品口味啦。
但是目前仍然有问题,就是选择套餐的话会报400
编写controller代码
/**
* 根据条件查询套餐数据
* @param setmeal
* @return
*/
@GetMapping("/list")
public R<List<Setmeal>> getSetDishes(Setmeal setmeal){
//get请求没有请求体,不能加requestBody注解
LambdaQueryWrapper<Setmeal> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(setmeal.getCategoryId()!=null,Setmeal::getCategoryId,setmeal.getCategoryId());
queryWrapper.eq(setmeal.getStatus()!=null,Setmeal::getStatus,setmeal.getStatus());
queryWrapper.orderByAsc(Setmeal::getUpdateTime);
List<Setmeal> setmealList = setmealService.list(queryWrapper);
return R.success(setmealList);
}
发现套餐也能点开查看了
4.购物车功能
4.1 需求分析
4.2 添加购物车
将菜品添加到购物车请求:
准备工作:
编写添加购物车controller代码
总体步骤为:
1.利用在threadLocal上绑定的userId设置userid;
2.查数据库套餐表中有没有相同的菜或套餐(id);
3.有则获取原数量,update时+1;
4.没有则新增
/**
* 添加购物车
* @param shoppingCart
* @return
*/
@PostMapping("/add")
public R<ShoppingCart> addShop(@RequestBody ShoppingCart shoppingCart){
log.info("购物车数据:{}",shoppingCart.toString());
//设置用户id(利用在threadLocal上绑定的userId)
Long currentId = BaseContext.getCurrentId();
shoppingCart.setUserId(currentId);
//查数据库套餐表中有没有相同的菜或套餐(id)
//根据userId和dishId/setmealId查询
LambdaQueryWrapper<ShoppingCart> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(ShoppingCart::getUserId,currentId);
Long dishId = shoppingCart.getDishId();
if(dishId==null){
//添加的是套餐
queryWrapper.eq(ShoppingCart::getSetmealId,shoppingCart.getSetmealId());
}else {
//添加的是菜品
queryWrapper.eq(ShoppingCart::getDishId,dishId);
}
//查询
ShoppingCart cartGetOne = shoppingCartService.getOne(queryWrapper);
if(cartGetOne !=null){
//如果有,相同记录数量直接加1
Integer number = cartGetOne.getNumber();
cartGetOne.setNumber(number+1);
shoppingCartService.updateById(cartGetOne);
}else {
//没有的话就插入一条数据
shoppingCart.setNumber(1);
shoppingCartService.save(shoppingCart);
cartGetOne =shoppingCart;
}
return R.success(cartGetOne);
}
4.3 查看购物车
然后就是点击购物车图标,需要显示当前用户购物车商品。页面发送ajax请求(假数据改回去):
编写controller代码:
/**
* 查看购物车
* @return
*/
@GetMapping("/list")
public R<List<ShoppingCart>> list(){
//根据userid
Long userId = BaseContext.getCurrentId();
LambdaQueryWrapper<ShoppingCart> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(ShoppingCart::getUserId,userId);
queryWrapper.orderByDesc(ShoppingCart::getCreateTime);
List<ShoppingCart> list = shoppingCartService.list(queryWrapper);
return R.success(list);
}
测试,购物车商品显示成功。
4.4 清空购物车
点击删除按钮,页面发送请求:
本质是根据userid去删除对应购物车数据
编写controller代码:
/**
* 清空购物车
* @return
*/
@DeleteMapping("/clean")
public R<String> del(){
Long userId = BaseContext.getCurrentId();
LambdaQueryWrapper<ShoppingCart> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(ShoppingCart::getUserId,userId);
shoppingCartService.remove(queryWrapper);
return R.success("购物车清空成功");
}
测试,清空成功。
5.用户下单
5.1需求分析
5.2 代码开发
前三个请求功能已经有了:
只有第四个功能,点击“去支付”,完成下单操作:
开发具体代码之前仍然是订单类和订单明细类的基础功能:
接下来就是开发用户下单功能,由于下单业务稍微复杂点,我们在orderService层编写主要的业务逻辑代码。
总体有几个步骤:
1.获取当前用户id;
2.根据当前用户id,查询用户数据、购物车数据、地址簿数据;
3.向订单表插入一条数据;
4.向订单明细表插入多条数据;
5.清空用户购物车数据;
业务代码如下:
/**
* 用户下单
* @param orders
*/
@Transactional
@Override
public void submit(Orders orders) {
//获得当前用户id
Long userId = BaseContext.getCurrentId();
//获取当前用户购物车数据
LambdaQueryWrapper<ShoppingCart> shopQueryWrapper=new LambdaQueryWrapper<>();
shopQueryWrapper.eq(ShoppingCart::getUserId,userId);
List<ShoppingCart> shopList = shoppingCartService.list(shopQueryWrapper);
if(shopList==null||shopList.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("地址信息有误,不能下单");
}
//向订单表插入数据(1条)
//计算购物车总金额,同时获取订单明细list
long orderId = IdWorker.getId();//生成订单号
AtomicInteger amount=new AtomicInteger(0);//原子int
List<OrderDetail> orderDetails=shopList.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赋值
orders.setId(orderId);
orders.setNumber(String.valueOf(orderId));
orders.setOrderTime(LocalDateTime.now());
orders.setCheckoutTime(LocalDateTime.now());
orders.setStatus(2);
orders.setAmount(new BigDecimal(amount.get()));//总金额
orders.setUserId(userId);
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(shopQueryWrapper);
}