苍穹外卖07(缓存菜品,SpringCache,缓存套餐,添加购物车菜品和套餐多下单,查看购物车,清除购物车,删除购物车中一个商品)

目录

一、缓存菜品

1 问题说明

2 实现思路

3 代码开发:修改DishServiceImpl

4 功能测试

二、SpringCache

1. 介绍

2. 使用语法

1 起步依赖

2 使用要求

3 常用注解

4 SpEL表达式(了解备用)

5 步骤小结

3.入门案例

1 准备环境

2 使用入门

1 引导类上加@EnableCaching

2 更新缓存加@CachePut

@CachePut 说明

使用示例

3 使用缓存加@Cacheable

测试效果

4 清理缓存加@CacheEvict

4. 使用小结

三、缓存套餐

1 问题说明

2 实现思路

3 代码开发

1 添加依赖坐标

2 修改引导类加@EnableCaching

3 修改SetmealServiceImpl

浏览套餐时使用缓存

套餐变更时清理缓存

4 功能测试

四、添加购物车

1. 需求分析和设计

1 产品原型

2 接口设计

3 表设计

2. 代码开发

1 DTO设计

2 ShoppingCartController

3 ShoppingCartService

4 ShoppingCartServiceImpl

5 ShoppingCartMapper

6 ShoppingCartMapper.xml

3. 功能测试

五、查看购物车

1. 需求分析和设计

1 产品原型

2 接口设计

2. 代码开发

1 ShoppingCartController

2 ShoppingCartService

3 ShoppingCartServiceImpl

4 ShoppingCartMapper

3. 功能测试

六、清空购物车

1. 需求分析和设计

1 产品原型

2 接口设计

2. 代码开发

1 ShoppingCartController

2 ShoppingCartService

3 ShoppingCartServiceImpl

4 ShoppingCartMapper

3. 功能测试

七、删除购物车中一个商品


一、缓存菜品

1 问题说明

用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大。

2 实现思路

通过Redis来缓存菜品数据,减少数据库查询操作。

缓存逻辑分析:

  • 每个分类下的菜品保存一份缓存数据

  • 数据库中菜品数据有变更时清理缓存数据

要做的事情:使用缓存优化菜品相关的性能

  • 给用户端的功能增加缓存:查询菜品时优先使用缓存

  • 管理端功能增删改时要清理缓存:新增、修改、删除、上下架操作都要清理缓存

3 代码开发:修改DishServiceImpl

修改queryUserDishesByCategoryId方法,增加缓存

@Autowired
private StringRedisTemplate redisTemplate;

@Override
public List<DishVO> queryUserDishesByCategoryId(Long categoryId) {
    //=========↓↓↓↓↓增加代码:先从Redis中查询缓存  start↓↓↓↓↓=========
    String cacheKey = "dish:" + categoryId;
    String dishesJson = redisTemplate.opsForValue().get(cacheKey);
    if (dishesJson != null && !"".equals(dishesJson)) {
        return JSON.parseArray(dishesJson, DishVO.class);
    }
    //=========↑↑↑↑↑增加代码:先从Redis中查询缓存  end↑↑↑↑↑=========

    //1. 查询菜品列表,及每个菜品关联的分类名称
    List<DishVO> dishVOList = dishMapper.selectEnableListByCategoryId(categoryId);
    //2. 查询每个菜品关联的口味列表
    for (DishVO dishVO : dishVOList) {
        //查询口味列表
        List<DishFlavor> dishFlavors = dishFlavorMapper.selectListByDishId(dishVO.getId());
        dishVO.setFlavors(dishFlavors);
    }

    //=========↓↓↓↓↓增加代码:把数据缓存到Redis里  start↓↓↓↓↓=========
    redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(dishVOList));
    //=========↑↑↑↑↑增加代码:把数据缓存到Redis里  end↑↑↑↑↑=========    
    return dishVOList;
}

菜品变化时清理缓存

为了保证数据库Redis中的数据保持一致,修改管理端接口 DishController 的相关方法,加入清理缓存逻辑。

需要改造的方法:

  • 新增菜品

  • 修改菜品

  • 批量删除菜品

  • 起售、停售菜品

新增菜品时清理缓存

修改方法,增加清理缓存的代码

@Override
@Transactional
public Result addDish(DishDTO dto) {
    //1. 把菜品信息存储到dish表里
    Dish dish = new Dish();
    BeanUtils.copyProperties(dto, dish);
    dishMapper.insert(dish);

    //2. 把菜品关联的口味保存到dish_flavors表里:每个dishFlavor都要使用到dish的id
    List<DishFlavor> flavors = dto.getFlavors();
    if (flavors != null && flavors.size() > 0) {
        //把菜品的id,设置给每个DishFlavor对象
        for (DishFlavor flavor : flavors) {
            flavor.setDishId(dish.getId());
        }
        // flavors.forEach(dishFlavor -> dishFlavor.setDishId(dish.getId()));

        //把DishFlavor对象的集合保存到数据库里,才有dishId值
        // INSERT INTO dish_flavor (dish_id, name, value) VALUES (菜品id,?,?),(),(),(),...;
        dishFlavorMapper.batchInsert(flavors);
    }

    //=======↓↓↓↓↓增加代码:清理缓存↓↓↓↓↓=======
    redisTemplate.delete("dish:" + dto.getCategoryId());
    //=======↑↑↑↑↑增加代码:清理缓存↑↑↑↑↑=======

    return Result.success();
}

删除菜品时清理缓存

修改方法,增加清理缓存的代码

@Override
@Transactional
public Result batchDeleteByIds(List<Long> ids) {
    //如果有某个菜品,状态是“起售”的,就抛异常,不允许删除
    int count = dishMapper.selectEnableDishCountByIds(ids);
    if (count > 0) {
        throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
    }
    //如果有某个菜品,关联了套餐,就抛异常,不允许删除
    count = setmeatlDishMapper.selectCountByDishIds(ids);
    if (count > 0) {
        throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
    }
    //删除这些菜品
    dishMapper.batchDeleteByIds(ids);
    //删除这些菜品对应口味列表
    dishFlavorMapper.batchDeleteByDishIds(ids);

    //=======↓↓↓↓↓增加代码:清理缓存↓↓↓↓↓=======
    Set<String> keys = redisTemplate.keys("dish:*");
    redisTemplate.delete(keys);
    //=======↑↑↑↑↑增加代码:清理缓存↑↑↑↑↑=======
    return Result.success();
}

修改菜品时清理缓存

修改方法,增加清理缓存的代码

@Override
@Transactional
public Result updateDishById(DishDTO dto) {
    //1. 先修改菜品信息
    Dish dish = new Dish();
    BeanUtils.copyProperties(dto, dish);
    dishMapper.updateById(dish);
    //2. 删除菜品关联的口味列表:创建一个集合,只放一个元素进去,怎么实现?Collections.singletonList(元素值)
    dishFlavorMapper.batchDeleteByDishIds(Collections.singletonList(dto.getId()));
    //3. 把客户端提交的口味列表,重新添加到数据库表
    List<DishFlavor> flavors = dto.getFlavors();
    for (DishFlavor flavor : flavors) {
        flavor.setDishId(dish.getId());
    }
    dishFlavorMapper.batchInsert(flavors);

    //=======↓↓↓↓↓增加代码:清理缓存↓↓↓↓↓=======
    redisTemplate.delete("dish:" + dto.getCategoryId());
    //=======↑↑↑↑↑增加代码:清理缓存↑↑↑↑↑=======
    return Result.success();
}

4 功能测试

可以通过如下方式进行测试:

  • 查看控制台sql

  • 前后端联调

  • 查看Redis中的缓存数据

浏览菜品时使用缓存

加入缓存菜品修改两个功能测试为例,通过前后端联调方式,查看控制台sql的打印和Redis中的缓存数据变化。

当第一次查询某个分类的菜品时,会从数据为中进行查询,同时将查询的结果存储到Redis中,在后绪的访问,若查询相同分类的菜品时,直接从Redis缓存中查询,不再查询数据库。

登录小程序:选择某一个菜品分类,例如蜀味牛蛙(id=17)。

  • 第一次点击访问:

    • 服务端控制台里输出了SQL语句,说明执行了SQL语句,是从数据库里查询的

    • 查看Redis里有 dish:17 对应的菜品数据,说明数据已经被缓存到Redis里了

  • 刷新小程序,清理一下idea的控制台

  • 再次点击访问“蜀味牛蛙”:

    • 服务端控制台里没有输出SQL语句,说明没有执行SQL,是从Redis里查询的缓存

二、SpringCache

1. 介绍

在企业开发中,缓存对于提升程序性能有非常大的作用,所以已经广泛应用于企业项目开发中。但是缓存技术是多种多样的,例如Redis、Caffeine、MemCache、EhCache等等。而不同的缓存技术,其操作方法并不统一,这就对于开发人员使用缓存造成了一些障碍。

从Spring3.1版本开始,Spring就利用AOP思想,对不同的缓存技术做了再封装,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。让开发人员只专注于业务,不需要再关心具体的缓存技术。

2. 使用语法

1 起步依赖

起步依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>                                                          
    <version>2.7.3</version> 
</dependency>

如果只添加上述一个依赖的话,springCache默认将会使用ConcurrentHashMap作为缓存容器。但是Spring Cache 其实提供了一层抽象,底层可以切换不同的缓存实现,例如:

  • EHCache,如果添加了EHCache的依赖坐标,SpringCache将会使用EhCache作为缓存容器

  • Caffeine,如果添加了caffeine的依赖坐标,SpringCache将会使用Caffeine作为缓存容器

  • Redis(常用),如果添加了Redis依赖坐标,SpringCache将会使用Redis作为缓存容器

所以实际开发中,通常是添加两个坐标:

<!-- SpringCache起步依赖坐标 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>                                                          
</dependency>
<!-- Redis起步依赖坐标 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2 使用要求

SpringCache结合Redis时,默认会使用JDK序列化方式,将数据序列化成字节数组,再缓存起来。

我们用的就是这种方式,所以我们的实体类要实现Serializable接口

3 常用注解

在SpringCache中提供了很多缓存操作的注解,常见的是以下的几个:

在spring boot项目中,使用缓存技术只需在项目中导入相关缓存技术的依赖包,并在启动类上使用@EnableCaching开启缓存支持即可。

例如,使用Redis作为缓存技术,只需要导入Spring data Redis的maven坐标即可。

4 SpEL表达式(了解备用)

Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:

注意:

  • 使用方法参数时,可以直接写成#参数名,也可以写成:#p参数索引,例如#p0表示索引0的参数

5 步骤小结

  1. 先添加依赖坐标

  2. 修改引导类,加注解@EnableCaching,开启缓存功能

  3. 哪个方法,查询数据时想要优先查缓存,就在方法上加注解:@Cacheable

  4. 哪个方法,执行后想要更新缓存的数据,就在方法上加注解:@CachePut

  5. 哪个方法,执行后想要清理缓存的数据,就在方法上加注解:@CacheEvict

3.入门案例

1 准备环境

导入基础工程:底层已使用Redis缓存实现

基础环境的代码,在我们今天的资料中已经准备好了, 大家只需要将这个工程导入进来就可以了

数据库准备:

CREATE DATABASE spring_cache_demo;
use database spring_cache_demo;
create table user
(
    id   bigint auto_increment primary key,
    name varchar(45) null,
    age  int         null
);

2 使用入门

1 引导类上加@EnableCaching

引导类上加@EnableCaching:

package com.itheima;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@EnableCaching //开启声明式缓存功能
@SpringBootApplication
public class CacheDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(CacheDemoApplication.class, args);
    }
}
2 更新缓存加@CachePut
@CachePut 说明
  • 作用:将方法返回值,放入缓存(更新缓存)

  • 用法:@CachePut(cacheNames="", key="")

    • 缓存的key:Spring将使用 cacheNames的值::key的值作为缓存的key

    • 缓存的值:Spring将方法的返回值作为缓存的value

  • 注意:注解里的key支持SpringEL表达式

使用示例

如果在做新增操作,或者修改操作时,可以更新缓存:当新增或修改操作后,希望把最新的数据缓存起来,方便后续使用。可以在新增或修改方法上加注解@CachePut

/**
 * 新增用户方法
 * 注解@CachePut将会把方法返回值缓存起来:以cacheNames+key作为缓存的key,以方法返回值作为缓存的value
 */
@Override
@CachePut(cacheNames = "user", key = "#user.id")
public User addUser(User user) {
    userMapper.insert(user);
    return user;
}

说明:key的写法如下

#user.id : #user指的是方法形参的名称, id指的是user的id属性 , 也就是使用user的id属性作为key ;

#result.id : #result代表方法返回值,该表达式 代表以返回对象的id属性作为key ;

#p0.id:#p0指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key ;

#a0.id:#a0指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数的id属性作为key ;

#root.args[0].id:#root.args[0]指的是方法中的第一个参数,id指的是第一个参数的id属性,也就是使用第一个参数

的id属性作为key ;

package com.itheima;

import com.itheima.entity.User;
import com.itheima.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class CacheTest {
    @Autowired
    private UserService userService;

    @Test
    public void testCachePut(){
        User user = new User();
        user.setName("pony");
        user.setAge(60);
        //新增完成后,数据库里会多一条数据,使用AnotherRedisDesktopManager连接Redis,会发现也有此用户的缓存
        userService.addUser(user);
    }
}
3 使用缓存加@Cacheable

@Cacheable 说明:

  • 作用:在方法执行前,spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;

    若没有数据,调用方法并将方法返回值放到缓存中

  • 用法:@Cacheable(cacheNames="", key="")

    缓存的key:以cacheNames::key的值作为key,查找对应的值

  • 注意:注解里的key支持SpringEL表达式

使用示例

在getById上加注解@Cacheable

/**
 * 根据id查询用户
 * Spring会优先从缓存中查找:以cacheNames::key作为key,查找对应的值
 *  如果找到了,就会直接返回结果;这个方法是不会执行的
 *  如果找不到,才会执行这个方法,并把方法的返回值缓存起来
 */
@Override
@Cacheable(cacheNames = "user", key = "#id")
public User queryById(Long id) {
    System.out.println(">>>>UserServiceImpl.queryById方法执行了");
    return userMapper.selectById(id);
}
测试效果

打开Another Redis Desktop Manager,先把用户1的缓存清除掉

然后在测试类里增加测试方法,并执行:

@Test
public void testCacheable(){
    System.out.println("--------第一次查询用户1,没有缓存,会执行目标方法查询数据库");
    System.out.println(userService.queryById(1L));

    System.out.println("--------第二次查询用户1,有缓存了,直接取缓存数据,不会执行这个方法");
    System.out.println(userService.queryById(1L));

    System.out.println("--------第三次查询用户1,有缓存了,直接取缓存数据,不会执行这个方法");
    System.out.println(userService.queryById(1L));
}
4 清理缓存加@CacheEvict

@CacheEvict 说明

  • 作用:清理指定缓存

  • 用法:

    • 用法1:CacheEvict(cacheNames="", key=""),清除cacheNames::key对应的缓存

    • 用法2:CacheEvict(cacheNames="", allEntries=true),清理所有以cacheNames::开头的key对应的缓存

  • 注意:注解里的key支持SpringEL表达式

使用示例

@Override
@CacheEvict(cacheNames = "user", key = "#id")
public void deleteUser(Long id) {
    System.out.println(">>>>UserServiceImpl.deleteUser方法执行了");
    userMapper.deleteById(id);
}

在测试类里增加方法并执行:

@Override
@CacheEvict(cacheNames = "user", key = "#id")
public void deleteUser(Long id) {
    System.out.println(">>>>UserServiceImpl.deleteUser方法执行了");
    userMapper.deleteById(id);
}

4. 使用小结

  1. 添加依赖坐标

  2. 修改引导类,加@EnableCaching

  3. 方法上加注解

    • 查询方法时,优先查询缓存:在方法上加@Cacheable(cacheNames="", key="")

      Spring优先找缓存的数据,以cacheNames::key 作为键,去查找缓存;

      如果找到缓存,就直接返回结果;被调用的这个方法,是不执行的

      如果没有缓存:Spring会调用执行这个方法,把方法的返回值 序列化成字节数组,然后缓存起来。

    • 调用一个方法时,想要更新缓存数据:在方法上加@CachePut(cacheNames="", key="")

      Spring会先调用方法,然后把方法的返回值序列化成字节数组

      cacheNames::key作为键,以返回值的序列化结果 作为值,缓存起来

    • 调用一个方法时,想要清理缓存数据:

      • 在方法上加@CacheEvict(cacheNames="", key="")

        Spring会先调用方法,再以cacheNames::key为键清理掉对应的缓存

      • 在方法上加@CacheEvict(cacheNames="", allEntries=true)

        Spring会先调用方法,再删除所有 以cacheNames开头的key

三、缓存套餐

1 问题说明

同缓存菜品一样,我们希望将菜品也缓存到Redis里。这样如果客户端(C端)访问量大了,直接从Redis里读取缓存数据,从而减轻数据库的压力。

2 实现思路

实现步骤:

1). 导入Spring Cache和Redis相关maven坐标

2). 在启动类上加入@EnableCaching注解,开启缓存注解功能

3). 在SetmealServiceImpl的查询套餐(用户端)方法上加入@Cacheable注解

4). 在SetmealServiceImpl的 新增套餐删除套餐修改套餐起用套餐 等管理端方法上加入CacheEvict注解

3 代码开发

1 添加依赖坐标

修改sky-server的pom.xml文件,添加依赖坐标:已添加过了,不要重复添加

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

2 修改引导类加@EnableCaching

在启动类上加入@EnableCaching注解,开启缓存注解功能

package com.sky;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
@EnableCaching
public class SkyApplication {
    public static void main(String[] args) {
        SpringApplication.run(SkyApplication.class, args);
        log.info("server started");
    }
}

3 修改SetmealServiceImpl

浏览套餐时使用缓存

修改list方法,增加注解@Cacheable

@Override
@Cacheable(cacheNames = "setmeal", key = "#categoryId")
public List<Setmeal> list(Integer categoryId) {
    return setmealMapper.selectEnableListByCategoryId(categoryId);
}
套餐变更时清理缓存

4 功能测试

通过前后端联调方式来进行测试,同时观察redis中缓存的套餐数据。和缓存菜品功能测试基本一致,不再赘述。

四、添加购物车

1. 需求分析和设计

1 产品原型

用户可以将菜品或者套餐添加到购物车。对于菜品来说,如果设置了口味信息,则需要选择规格后才能加入购物车;对于套餐来说,可以直接点击

将当前套餐加入购物车。在购物车中可以修改菜品和套餐的数量,也可以清空购物车。

2 接口设计

通过上述原型图,设计出对应的添加购物车接口。

说明:添加购物车时,有可能添加菜品,也有可能添加套餐。故传入参数要么是菜品id,要么是套餐id。

3 表设计

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

说明:

  • 购物车数据是关联用户的,在表结构中,我们需要记录,每一个用户的购物车数据是哪些

  • 菜品列表展示出来的既有套餐,又有菜品,如果用户选择的是套餐,就保存套餐ID(setmeal_id),如果用户选择的是菜品,就保存菜品ID(dish_id)

  • 对同一个菜品/套餐,如果选择多份不需要添加多条记录,增加数量number即可

2. 代码开发

1 DTO设计

根据添加购物车接口的参数设计DTO:

在sky-pojo模块,ShoppingCartDTO.java已定义

package com.sky.dto;

import lombok.Data;
import java.io.Serializable;

@Data
public class ShoppingCartDTO implements Serializable {

    private Long dishId;
    private Long setmealId;
    private String dishFlavor;

}

2 ShoppingCartController

创建 ShoppingCartController

package com.sky.controller.user;

import com.sky.dto.ShoppingCartDTO;
import com.sky.result.Result;
import com.sky.service.ShoppingCartService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;


@RestController
@Api(tags = "购物车相关接口-C端")
@RequestMapping("/user/shoppingCart")
public class ShoppingCartController {
    @Autowired
    private ShoppingCartService cartService;

    @PostMapping("/add")
    @ApiOperation("添加购物车")
    public Result addCart(@RequestBody ShoppingCartDTO dto){
        return cartService.addCart(dto);
    }
}

3 ShoppingCartService

创建ShoppingCartService接口:

package com.sky.service;

import com.sky.dto.ShoppingCartDTO;
import com.sky.entity.ShoppingCart;
import com.sky.result.Result;


public interface ShoppingCartService {
    /**
     * 添加购物车
     * @param dto
     * @return
     */
    Result addCart(ShoppingCartDTO dto);
}

4 ShoppingCartServiceImpl

创建ShoppingCartServiceImpl实现类

package com.sky.service.impl;

import com.sky.context.BaseContext;
import com.sky.dto.ShoppingCartDTO;
import com.sky.entity.Dish;
import com.sky.entity.Setmeal;
import com.sky.entity.ShoppingCart;
import com.sky.mapper.DishMapper;
import com.sky.mapper.SetmealMapper;
import com.sky.mapper.ShoppingCartMapper;
import com.sky.result.Result;
import com.sky.service.ShoppingCartService;
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;

@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {
    @Autowired
    private ShoppingCartMapper shoppingCartMapper;
    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private SetmealMapper setmealMapper;

    @Override
    public Result addCart(ShoppingCartDTO dto) {
        //查询当前商品是否在购物一中
        Long currentUser = BaseContext.getCurrentId();
        ShoppingCart cart = shoppingCartMapper.selectOne(dto, currentUser);
        if (cart == null) {
            //不在购物车中,要新增到购物车里。准备一个entity对象
            cart = new ShoppingCart();
            BeanUtils.copyProperties(dto, cart);
            cart.setUserId(currentUser);
            cart.setCreateTime(LocalDateTime.now());
            cart.setNumber(1);

            //还需要判断添加的是套餐还是菜品,补全不同的数据
            if (dto.getDishId() != null) {
                //添加的是菜品,查询菜品信息
                Dish dish = dishMapper.selectById(dto.getDishId());
                cart.setName(dish.getName());
                cart.setImage(dish.getImage());
                cart.setAmount(dish.getPrice());
            }else{
                //添加的是套餐,查询套餐信息
                Setmeal setmeal = setmealMapper.selectById(dto.getSetmealId());
                cart.setName(setmeal.getName());
                cart.setImage(setmeal.getImage());
                cart.setAmount(setmeal.getPrice());
            }

            //保存到数据库里
            shoppingCartMapper.insert(cart);
        }else{
            //在购物车中,要修改购物车中商品的数量+1
            shoppingCartMapper.updateNumber(cart.getId(), 1);
        }
        
        return Result.success();
    }
}

5 ShoppingCartMapper

创建SShoppingCartMapper接口:

package com.sky.mapper;

import com.sky.dto.ShoppingCartDTO;
import com.sky.entity.ShoppingCart;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Update;

@Mapper
public interface ShoppingCartMapper {
    ShoppingCart selectOne(ShoppingCartDTO dto, Long userId);

    @Update("update shopping_cart set number = number + #{increment} where id = #{id}")
    void updateNumber(Long id, int increment);

    @Insert("insert into shopping_cart (name, image, user_id, dish_id, setmeal_id, dish_flavor, number, amount, create_time)" +
            "values (#{name}, #{image}, #{userId}, #{dishId}, #{setmealId}, #{dishFlavor}, #{number}, #{amount}, #{createTime})")
    void insert(ShoppingCart cart);
}

6 ShoppingCartMapper.xml

创建ShoppingCartMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sky.mapper.ShoppingCartMapper">
    <select id="selectOne" resultType="com.sky.entity.ShoppingCart">
        select * from shopping_cart
        <where>
            <if test="userId!=null">
                and user_id = #{userId}
            </if>
            <if test="dto.dishId!=null">
                and dish_id = #{dto.dishId}
            </if>
            <if test="dto.setmealId!=null">
                and setmeal_id = #{dto.setmealId}
            </if>
            <if test="dto.dishFlavor!=null and dto.dishFlavor.length()>0">
                and dish_flavor = #{dto.dishFlavor}
            </if>
        </where>
    </select>
</mapper>

3. 功能测试

进入小程序,添加菜品

五、查看购物车

1. 需求分析和设计

1 产品原型

当用户添加完菜品和套餐后,可进入到购物车中,查看购物中的菜品和套餐。

2 接口设计

2. 代码开发

1 ShoppingCartController

在ShoppingCartController中创建查看购物车的方法:

@GetMapping("/list")
@ApiOperation("查看购物车")
public Result queryCart(){
    return cartService.queryCart();
}

2 ShoppingCartService

在ShoppingCartService接口中声明查看购物车的方法:

    /**
     * 查询当前用户的购物车
     * @return
     */
    Result queryCart();

3 ShoppingCartServiceImpl

在ShoppingCartServiceImpl中实现查看购物车的方法:

    @Override
    public Result queryCart() {
        Long currentUser = BaseContext.getCurrentId();

        List<ShoppingCart> cartList = shoppingCartMapper.selectListByUser(currentUser);

        return Result.success(cartList);
    }

4 ShoppingCartMapper

@Select("select * from shopping_cart where user_id = #{userId}")
List<ShoppingCart> selectListByUser(Long userId);

3. 功能测试

当进入小程序时,就会发起查看购物车的请求

六、清空购物车

1. 需求分析和设计

1 产品原型

当点击清空按钮时,会把购物车中的数据全部清空。

2 接口设计

2. 代码开发

1 ShoppingCartController

在ShoppingCartController中创建清空购物车的方法:

@DeleteMapping("/clean")
@ApiOperation("清空购物车")
public Result cleanCart(){
    return cartService.cleanCart();
}

2 ShoppingCartService

在ShoppingCartService接口中声明清空购物车的方法:

    /**
     * 清空购物车
     * @return
     */
    Result cleanCart();

3 ShoppingCartServiceImpl

在ShoppingCartServiceImpl中实现清空购物车的方法:

@Override
public Result cleanCart() {
    shoppingCartMapper.deleteByUser(BaseContext.getCurrentId());
    return Result.success();
}

4 ShoppingCartMapper

在ShoppingCartMapper接口中创建删除购物车数据的方法:

@Delete("delete from shopping_cart where user_id = #{userId}")
void deleteByUser(Long userId);

3. 功能测试

进入到购物车页面

点击清空

七、删除购物车中一个商品

//controller

@PostMapping("/sub")
    @ApiOperation("删除购物车中一个商品")
    public Result deleteOne(@RequestBody ShoppingCartDTO dto){
        return cartService.deleteOne(dto);
}


-----------------
//ShoppingCartService

Result deleteOne(ShoppingCartDTO dto);

-----------------
//ShoppingCartServiceImpl

 @Override
    public Result deleteOne(ShoppingCartDTO dto) {
        Long currentUser = BaseContext.getCurrentId();

        ShoppingCart cart = shoppingCartMapper.selectOne(dto, currentUser);

        if (cart.getNumber()==1){
            shoppingCartMapper.deleteById(cart.getId());
        }

        shoppingCartMapper.updateNumber(cart.getId(), -1);
        return Result.success();
}

-------------------
//mapper
ShoppingCart selectOne(ShoppingCartDTO dto, Long userId);

@Update("update shopping_cart set number=number+#{increment} where id=#{id}")
    void updateNumber(Long id, int increment);

------------
//XML
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.ShoppingCartMapper">

    <select id="selectOne" resultType="com.sky.entity.ShoppingCart">
        select * from shopping_cart
        <where>
            <if test="userId!=null">
                and user_id = #{userId}
            </if>
            <if test="dto.dishId!=null">
                and dish_id = #{dto.dishId}
            </if>
            <if test="dto.setmealId!=null">
                and setmeal_id = #{dto.setmealId}
            </if>
            <if test="dto.dishFlavor!=null and dto.dishFlavor.length()>0">
                and dish_flavor = #{dto.dishFlavor}
            </if>
        </where>
    </select>
</mapper>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/516590.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

b站评论词频统计绘制词云图

一、评论爬取 在笔者之前的文章中&#xff0c;已经专门介绍了b站评论的爬取&#xff08;传送门&#xff09;&#xff0c;这里只对b站评论的文本数据做展示。如下图所示&#xff1a; 二、分词、去停用词、词频统计 Python中的Jieba分词作为应用广泛的分词工具之一&#xff0c;其…

【面试经典150 | 动态规划】最长回文子串

文章目录 写在前面Tag题目来源解题方法方法一&#xff1a;动态规划 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题涉及到的数据结构等内容进行…

【OJ】动规练习六

个人主页 &#xff1a; zxctscl 如有转载请先通知 题目 1. 413. 等差数列划分1.1 分析1.2 代码 2. 978. 最长湍流子数组2.1 分析2.2 代码 3. 139. 单词拆分3.1 分析3.2 代码 1. 413. 等差数列划分 1.1 分析 一、题目解析&#xff1a; 至少有三个元素才能构成等差数列&#xff…

【开发环境】Mac 安装 Visual Studio Code ( VSCode 简介 | 下载 VSCode | 安装 VSCode | 安装中文语言包 )

文章目录 一、Visual Studio Code 简介二、MAC 安装 Visual Studio Code1、下载 Visual Studio Code2、安装 Visual Studio Code3、安装中文语言包4、编辑 html 并运行 一、Visual Studio Code 简介 Visual Studio Code 简称 VSCode , 是 微软 开发的一款 轻量级 / 跨平台 的代…

前视声呐目标识别定位(五)-代码解析之修改声呐参数

前视声呐目标识别定位&#xff08;一&#xff09;-基础知识 前视声呐目标识别定位&#xff08;二&#xff09;-目标识别定位模块 前视声呐目标识别定位&#xff08;三&#xff09;-部署至机器人 前视声呐目标识别定位&#xff08;四&#xff09;-代码解析之启动识别模块 …

arm裸机-1、定时器pwm

时钟配置 我们使用s3c2440&#xff0c;主频12M&#xff0c;查看用户手册 通过锁相环抬升到400MHZ&#xff0c;分成三条通路&#xff0c;通过HHDIVN和PDIVN配置频率比&#xff0c;这个频率比配置手册已经给出。 配置MPLL主频400Mhz&#xff0c; 通过这个公式算出MPLL s、p、m都…

Redis从入门到精通(四)Redis实战(二)商户查询缓存

↑↑↑请在文章头部下载测试项目原代码↑↑↑ 文章目录 前言4.2 商户查询缓存4.2.1 缓存介绍4.2.2 查询商户信息的传统做法4.2.2.1 接口文档4.2.2.2 代码实现4.2.2.3 功能测试 4.2.3 查询商户信息添加Redis缓存4.2.3.1 逻辑分析4.2.3.2 代码实现4.2.3.3 功能测试 4.2.3 数据一致…

动力云客-B站(第一天)

一 项目技术选型及开发工具 前后端分离的项目&#xff08;前端项目 后端项目&#xff09; 前端&#xff1a;Html、CSS、JavaScript、Vue、Axios、Element Plus后端&#xff1a;Spring Boot、Spring Security、MyBatis、MySQL、Redis 相关组件&#xff1a;HiKariCP&#x…

【C++进阶】带你手撕红黑树(红与黑的爱恨厮杀)

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 主厨&#xff1a;邪王真眼 主厨的主页&#xff1a;Chef‘s blog 所属专栏&#xff1a;c大冒险 总有光环在陨落&#xff0c;总有新星在闪烁 引言&#xff1a; 之前我们…

K8S- Deployment 的滚动更新 Rolling Update

滚动更新 这里的更新指的不是更新deployment 本身的属性(label/ replicas)等&#xff0c; 而是更新POD 的container 的版本 更新方法通常有两种 是直接update deployment配置&#xff0c; 注意只有update了template中的内容(与container相关) 才会触发更新用kubectl set ima…

【蓝桥杯】GCD与LCM

一.概述 最大公约数&#xff08;GCD&#xff09;和最小公倍数&#xff08;Least Common Multiple&#xff0c;LCM&#xff09; 在C中&#xff0c;可以使用 std::__gcd(a, b)来计算最大公约数 1.欧几里德算法/辗转相除法 int gcd(int a,int b){return b?gcd(b, a%b):a; } 2…

CATIA软件 焊点坐标(BiW Welding SpotPoint)导出txt文本的操作方法

通常我们客户给的工件是带焊点球的形式&#xff0c;且可导出焊点坐标。如下图所示的焊点位置标识及坐标&#xff0c;即是CATIA中Automotive BiW Fastening模块下的BiW Welding SpotPoint焊点定义。 遇到这样的形式&#xff0c;要导出焊点坐标txt文本&#xff0c;可按如下图片找…

ssm023实验室耗材管理系统设计与实现+jsp

实验室耗材管理系统的设计与实现 摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对实验室耗材信息管理混乱&#xff…

深入浅出 -- 系统架构之单体架构和微服务架构的区别

在软件开发中&#xff0c;架构设计是非常重要的一环。架构设计不仅决定了软件系统的性能、可维护性和扩展性&#xff0c;还直接关系到开发成本和项目进度。目前&#xff0c;主流的架构设计模式有两种&#xff0c;一种是单体架构&#xff0c;另一种是微服务架构。本文将详细介绍…

【C语言】从零开始:用C语言实现顺序表

欢迎来CILMY23的博客 本篇主题为 从零开始&#xff1a;用C语言实现顺序表 个人主页&#xff1a;CILMY23-CSDN博客 C语言专栏&#xff1a; http://t.csdnimg.cn/hQ5a9 Python系列专栏&#xff1a;http://t.csdnimg.cn/HqYo8 上一篇C语言博客&#xff1a; http://t.csdnimg.…

pwn学习笔记(7)--堆相关源码

相关源码&#xff1a; 1. chunk 相关源码&#xff1a; ​ 对于用户来说&#xff0c;只需要确保malloc()函数返回的内存不会发生溢出&#xff0c;并且在不用的时候使用free() 函数将其释放&#xff0c;以后也不再做任何操作即可。而对于glibc来说’它要在用户第一次调用malloc…

C语言数据结构初阶-顺序表

什么是数据结构 数据结构是由数据和结构两个词结合而来&#xff0c; 那么数据由是什么 就比如我们日常生活中的1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;a,b,c,d,e文字信息图片等&#xff0c;这些就是数据 那么结构又是什么&#xff1f; 想像一下如…

Collection与数据结构 Stack与Queue(一): 栈与Stack

1. 栈 1.1 概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则。 压栈&…

【029】基于ssm+小程序实现的理发店预约系统

作者主页&#xff1a;Java码库 主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】&#xff1a;Java 【框架】&#xff1a;ssm 【…

Hadoop: word count,并将reduce结果写入ES

一、依赖&#xff0c;其中ES版本为7.6.2 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http…