当前位置: 首页 > news >正文

Day7 苍穹外卖项目 缓存菜品、SpringCache框架、缓存套餐、添加购物车、查看购物车、清空购物车

目录

1.缓存菜品

1.1 问题说明

1.2 实现思路

1.3 代码开发

1.3.1 加入缓存

1.3.2 清除缓存

1.3.2.1 新增菜品优化

1.3.2.2 菜品批量删除优化

1.3.2.3 修改菜品优化

1.3.2.4 菜品起售停售优化

1.4 功能测试

1.4.1 加入缓存

1.4.2 菜品修改

1.5 代码提交

2.缓存套餐

2.1 Spring Cache

2.1.1 介绍

2.1.2 常用注解

2.1.3 入门案例

2.1.3.1 环境准备

2.1.3.2 @CachePut注解

2.1.3.3 @Cacheable注解

2.1.3.4 @CacheEvict注解

2.2 实现步骤

2.3 代码开发

2.3.1 导入maven坐标

2.3.2 启动类加@EnableCaching注解

2.3.3 加@Cacheable注解

2.3.4 加@CacheEvict注解

2.4 功能测试

2.5 代码提交

3.添加购物车

3.1 需求分析和设计

3.1.1 产品原型

3.1.2 接口设计

3.1.3 表设计

3.2 代码开发

3.2.1 DTO设计

3.2.2 Controller层

3.2.3 Service层接口

3.2.4 Service层实现类

3.2.5 Mapper层

3.3 功能测试

3.4 代码提交

4.查看购物车

4.1 需求分析和设计

4.1.1 产品原型

4.1.2 接口设计

4.2 代码开发

4.2.1 Controller层

4.2.2 Service层接口

4.2.3 Service层实现类

4.3 功能测试

4.4 代码提交

5.清空购物车

5.1 需求分析和设计

5.1.1 产品原型

5.1.2 接口设计

5.2 代码开发

5.2.1 Controller层

5.2.2 Service层接口

5.2.3 Service层实现类

5.2.4 Mapper层

5.3 功能测试


1.缓存菜品

1.1 问题说明

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

结果:系统响应慢、用户体验差。

1.2 实现思路

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

缓存逻辑分析:

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

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

1.3 代码开发

1.3.1 加入缓存

修改用户端接口 DishController 的 list 方法,加入缓存处理逻辑:

	@Autowiredprivate RedisTemplate redisTemplate;/*** 根据分类id查询菜品** @param categoryId* @return*/@GetMapping("/list")@ApiOperation("根据分类id查询菜品")public Result<List<DishVO>> list(Long categoryId) {//构造redis中的key,规则:dish_分类idString key = "dish_" + categoryId;//查询redis中是否存在菜品数据List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);if(list != null && list.size() > 0){//如果存在,直接返回,无须查询数据库return Result.success(list);}Dish dish = new Dish();dish.setCategoryId(categoryId);dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品//如果不存在,查询数据库,将查询到的数据放入redis中list = dishService.listWithFlavor(dish);redisTemplate.opsForValue().set(key, list);return Result.success(list);}

1.3.2 清除缓存

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

需要改造的方法:

  • 新增菜品(删除dish_categoryId的缓存数据)

  • 修改菜品(删除有关dish_前缀的缓存数据)

  • 批量删除菜品(删除有关dish_前缀的缓存数据)

  • 起售、停售菜品(删除有关dish_前缀的缓存数据)

注意事项:如果加了缓存,要多查数据库,那就得不偿失。直接删除全部缓存、。

抽取清理缓存的方法:

在管理端DishController中添加

	@Autowiredprivate RedisTemplate redisTemplate;/*** 清理缓存数据* @param pattern*/private void cleanCache(String pattern){Set keys = redisTemplate.keys(pattern);redisTemplate.delete(keys);}

调用清理缓存的方法,保证数据一致性:

1.3.2.1 新增菜品优化

	/*** 新增菜品** @param dishDTO* @return*/@PostMapping@ApiOperation("新增菜品")public Result save(@RequestBody DishDTO dishDTO) {log.info("新增菜品:{}", dishDTO);dishService.saveWithFlavor(dishDTO);//清理缓存数据String key = "dish_" + dishDTO.getCategoryId();cleanCache(key);return Result.success();}

1.3.2.2 菜品批量删除优化

	/*** 菜品批量删除** @param ids* @return*/@DeleteMapping@ApiOperation("菜品批量删除")public Result delete(@RequestParam List<Long> ids) {log.info("菜品批量删除:{}", ids);dishService.deleteBatch(ids);//将所有的菜品缓存数据清理掉,所有以dish_开头的keycleanCache("dish_*");return Result.success();}

1.3.2.3 修改菜品优化

	/*** 修改菜品** @param dishDTO* @return*/@PutMapping@ApiOperation("修改菜品")public Result update(@RequestBody DishDTO dishDTO) {log.info("修改菜品:{}", dishDTO);dishService.updateWithFlavor(dishDTO);//将所有的菜品缓存数据清理掉,所有以dish_开头的keycleanCache("dish_*");return Result.success();}

1.3.2.4 菜品起售停售优化

	/*** 菜品起售停售** @param status* @param id* @return*/@PostMapping("/status/{status}")@ApiOperation("菜品起售停售")public Result<String> startOrStop(@PathVariable Integer status, Long id) {dishService.startOrStop(status, id);//将所有的菜品缓存数据清理掉,所有以dish_开头的keycleanCache("dish_*");return Result.success();}

1.4 功能测试

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

  • 查看控制台sql

  • 前后端联调

  • 查看Redis中的缓存数据

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

1.4.1 加入缓存

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

登录小程序:选择蜀味牛蛙(id=17)

查看控制台sql:有查询语句,说明是从数据库中进行查询。

查看Redis中的缓存数据:说明缓存成功。

再次访问:选择蜀味牛蛙(id=17)

说明是从Redis中查询的数据。

1.4.2 菜品修改

当在后台修改菜品数据时,为了保证Redis缓存中的数据和数据库中的数据时刻保持一致,当修改后,需要清空对应的缓存数据。用户再次访问时,还是先从数据库中查询,同时再把查询的结果存储到Redis中,这样,就能保证缓存和数据库的数据保持一致。

进入后台:修改蜀味牛蛙分类下的任意一个菜品,当前分类的菜品数据已在Redis中缓存

修改:

查看Redis中的缓存数据:说明修改时,已清空缓存。

用户再次访问同一个菜品分类时,需要先查询数据库,再把结果同步到Redis中,保证了两者数据一致性。

其它功能测试步骤基本一致,自已测试即可。

1.5 代码提交

2.缓存套餐

2.1 Spring Cache

2.1.1 介绍

Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。

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

Spring Cache 提供了一层抽象,底层可以切换不同的缓存实现,例如:

  • EHCache

  • Caffeine

  • Redis(常用)

注意事项:切换缓存实现,只需要引入其maven坐标,就能实现。

起步依赖:

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

引入Redis中间件依赖:

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

2.1.2 常用注解

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

注解说明
@EnableCaching开启缓存注解功能,通常加在启动类上
@Cacheable在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,调用方法并将方法返回值放到缓存中
@CachePut将方法的返回值放到缓存中
@CacheEvict将一条或多条数据从缓存中删除

2.1.3 入门案例

2.1.3.1 环境准备

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

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

数据库准备:

创建名为spring_cache_demo数据库,将springcachedemo.sql脚本直接导入数据库中。

引导类上加@EnableCaching:

开启缓存注解功能:

​@Slf4j
@SpringBootApplication
@EnableCaching//开启缓存注解功能
public class CacheDemoApplication {public static void main(String[] args) {SpringApplication.run(CacheDemoApplication.class,args);log.info("项目启动成功...");}
}

2.1.3.2 @CachePut注解

@CachePut 说明:

作用: 将方法返回值,放入缓存。

value: 缓存的名称, 每个缓存名称下面可以有很多key。

key: 缓存的key ----------> 支持Spring的表达式语言SPEL语法。

在save方法上加注解@CachePut

当前UserController的save方法是用来保存用户信息的,我们希望在该用户信息保存到数据库的同时,也往缓存中缓存一份数据,我们可以在save方法上加上注解 @CachePut,用法如下:

    /*** CachePut:将方法返回值放入缓存* value:缓存的名称,每个缓存名称下面可以有多个key* key:缓存的key*/@PostMapping@CachePut(value = "userCache", key = "#user.id")//key的生成:userCache::1public User save(@RequestBody User user){userMapper.insert(user);return user;}

注意事项:最终key的值会受到value以及key这两个属性影响;

// key的生成:userCache::1

@CachePut(value = "userCache", key = "#user.id")

Spring Expression Language (SpEL) expression for computing the key dynamically:

说明: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 ;

启动服务,通过swagger接口文档测试,访问UserController的save()方法

因为id是自增,所以不需要设置id属性

查看user表中的数据:

查看Redis中的数据:

2.1.3.3 @Cacheable注解

@Cacheable 说明:

作用: 在方法执行前,spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放到缓存中。

value: 缓存的名称,每个缓存名称下面可以有多个key。

key: 缓存的key ----------> 支持Spring的表达式语言SPEL语法。

在getById上加注解@Cacheable

 /*** Cacheable:在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,    *调用方法并将方法返回值放到缓存中* value:缓存的名称,每个缓存名称下面可以有多个key* key:缓存的key*/@GetMapping@Cacheable(cacheNames = "userCache",key="#id")public User getById(Long id){User user = userMapper.getById(id);return user;}

重启服务,通过swagger接口文档测试,访问UserController的getById()方法。

第一次访问,会请求我们controller的方法,查询数据库。后面再查询相同的id,就直接从Redis中查询数据,不用再查询数据库了,就说明缓存生效了。

提前在redis中手动删除掉id=1的用户数据。

查看控制台sql语句:说明从数据库查询的用户数据。

查看Redis中的缓存数据:说明已成功缓存。

再次查询相同id的数据时,直接从redis中直接获取,不再查询数据库。

2.1.3.4 @CacheEvict注解

@CacheEvict 说明:

作用: 清理指定缓存

value: 缓存的名称,每个缓存名称下面可以有多个key

key: 缓存的key ----------> 支持Spring的表达式语言SPEL语法

删除单个缓存数据:

  @CacheEvict(cacheNames = "userCache",key = "#id")//删除某个key对应的缓存数据

删除所有缓存数据:

 @CacheEvict(value = "userCache",allEntries = true)

在 delete 方法上加注解@CacheEvict

    @DeleteMapping@CacheEvict(cacheNames = "userCache",key = "#id")//删除某个key对应的缓存数据public void deleteById(Long id){userMapper.deleteById(id);}
​@DeleteMapping("/delAll")@CacheEvict(cacheNames = "userCache",allEntries = true)//删除userCache下所有的缓存数据public void deleteAll(){userMapper.deleteAll();}

重启服务,通过swagger接口文档测试,访问UserController的deleteAll()方法。

查看user表:数据清空

查询Redis缓存数据:

2.2 实现步骤

实现步骤:

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

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

3). 在用户端接口SetmealController的 list 方法上加入@Cacheable注解

4). 在管理端接口SetmealController的 save、delete、update、startOrStop等方法上加入CacheEvict注解

2.3 代码开发

管理端有数据变动了必须清理缓存(但是不需要缓存数据),不然用户读到的数据就是缓存里的脏数据(用户端接口缓存数据)。

按照上述实现步骤:

2.3.1 导入maven坐标

<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.3.2 启动类加@EnableCaching注解

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

2.3.3 加@Cacheable注解

用户端接口SetmealController的 list 方法上加入@Cacheable注解

    /*** 条件查询** @param categoryId* @return*/@GetMapping("/list")@ApiOperation("根据分类id查询套餐")@Cacheable(cacheNames = "setmealCache",key = "#categoryId") //key: setmealCache::100public Result<List<Setmeal>> list(Long categoryId) {Setmeal setmeal = new Setmeal();setmeal.setCategoryId(categoryId);setmeal.setStatus(StatusConstant.ENABLE);
​List<Setmeal> list = setmealService.list(setmeal);return Result.success(list);}

2.3.4 加@CacheEvict注解

管理端接口SetmealController的 save、delete、update、startOrStop等方法上加入@CacheEvict注解。

    /*** 新增套餐** @param setmealDTO* @return*/@PostMapping@ApiOperation("新增套餐")@CacheEvict(cacheNames = "setmealCache",key = "#setmealDTO.categoryId")//key: setmealCache::100public Result save(@RequestBody SetmealDTO setmealDTO) {setmealService.saveWithDish(setmealDTO);return Result.success();}/*** 批量删除套餐** @param ids* @return*/@DeleteMapping@ApiOperation("批量删除套餐")@CacheEvict(cacheNames = "setmealCache",allEntries = true)public Result delete(@RequestParam List<Long> ids) {setmealService.deleteBatch(ids);return Result.success();}/*** 修改套餐** @param setmealDTO* @return*/@PutMapping@ApiOperation("修改套餐")@CacheEvict(cacheNames = "setmealCache",allEntries = true)public Result update(@RequestBody SetmealDTO setmealDTO) {setmealService.update(setmealDTO);return Result.success();}
​/*** 套餐起售停售** @param status* @param id* @return*/@PostMapping("/status/{status}")@ApiOperation("套餐起售停售")@CacheEvict(cacheNames = "setmealCache",allEntries = true)public Result startOrStop(@PathVariable Integer status, Long id) {setmealService.startOrStop(status, id);return Result.success();}

2.4 功能测试

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

2.5 代码提交

3.添加购物车

3.1 需求分析和设计

3.1.1 产品原型

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

效果图:

3.1.2 接口设计

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

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

3.1.3 表设计

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

注意事项:这里的冗余字段,可以增加查询效率。但是不可大量使用。一般用于比较固定的数据字段。

字段名数据类型说明备注
idbigint主键自增
namevarchar(32)商品名称冗余字段
imagevarchar(255)商品图片路径冗余字段
user_idbigint用户id逻辑外键
dish_idbigint菜品id逻辑外键
setmeal_idbigint套餐id逻辑外键
dish_flavorvarchar(50)菜品口味
numberint商品数量
amountdecimal(10,2)商品单价冗余字段
create_timedatetime创建时间

说明:

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

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

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

3.1.4 核心逻辑

添加购物车:

//只能查询自己的购物车数据


//判断当前商品是否在购物车中


//如果已经存在,就更新数量,数量加1


//如果不存在,插入数据,数量就是1


//判断当前添加到购物车的是菜品还是套餐


//添加到购物车的是菜品


//添加到购物车的是套餐

3.2 代码开发

3.2.1 DTO设计

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

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

@Data
public class ShoppingCartDTO implements Serializable {private Long dishId;private Long setmealId;private String dishFlavor;}

3.2.2 Controller层

根据添加购物车接口创建ShoppingCartController:

/*** 购物车*/
@RestController
@RequestMapping("/user/shoppingCart")
@Slf4j
@Api(tags = "C端-购物车接口")
public class ShoppingCartController {@Autowiredprivate ShoppingCartService shoppingCartService;/*** 添加购物车* @param shoppingCartDTO* @return*/@PostMapping("/add")@ApiOperation("添加购物车")public Result<String> add(@RequestBody ShoppingCartDTO shoppingCartDTO){log.info("添加购物车:{}", shoppingCartDTO);shoppingCartService.addShoppingCart(shoppingCartDTO);//后绪步骤实现return Result.success();}
}

3.2.3 Service层接口

创建ShoppingCartService接口:

public interface ShoppingCartService {/*** 添加购物车* @param shoppingCartDTO*/void addShoppingCart(ShoppingCartDTO shoppingCartDTO);
}

3.2.4 Service层实现类

创建ShoppingCartServiceImpl实现类,并实现add方法:

@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {@Autowiredprivate ShoppingCartMapper shoppingCartMapper;@Autowiredprivate DishMapper dishMapper;@Autowiredprivate SetmealMapper setmealMapper;/*** 添加购物车** @param shoppingCartDTO*/public void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {ShoppingCart shoppingCart = new ShoppingCart();BeanUtils.copyProperties(shoppingCartDTO, shoppingCart);//只能查询自己的购物车数据shoppingCart.setUserId(BaseContext.getCurrentId());//判断当前商品是否在购物车中List<ShoppingCart> shoppingCartList = shoppingCartMapper.list(shoppingCart);if (shoppingCartList != null && shoppingCartList.size() == 1) {//如果已经存在,就更新数量,数量加1shoppingCart = shoppingCartList.get(0);shoppingCart.setNumber(shoppingCart.getNumber() + 1);shoppingCartMapper.updateNumberById(shoppingCart);} else {//如果不存在,插入数据,数量就是1//判断当前添加到购物车的是菜品还是套餐Long dishId = shoppingCartDTO.getDishId();if (dishId != null) {//添加到购物车的是菜品Dish dish = dishMapper.getById(dishId);shoppingCart.setName(dish.getName());shoppingCart.setImage(dish.getImage());shoppingCart.setAmount(dish.getPrice());} else {//添加到购物车的是套餐Setmeal setmeal = setmealMapper.getById(shoppingCartDTO.getSetmealId());shoppingCart.setName(setmeal.getName());shoppingCart.setImage(setmeal.getImage());shoppingCart.setAmount(setmeal.getPrice());}shoppingCart.setNumber(1);shoppingCart.setCreateTime(LocalDateTime.now());shoppingCartMapper.insert(shoppingCart);}}
}

3.2.5 Mapper层

创建ShoppingCartMapper接口:

@Mapper
public interface ShoppingCartMapper {/*** 条件查询** @param shoppingCart* @return*/List<ShoppingCart> list(ShoppingCart shoppingCart);/*** 更新商品数量** @param shoppingCart*/@Update("update shopping_cart set number = #{number} where id = #{id}")void updateNumberById(ShoppingCart shoppingCart);/*** 插入购物车数据** @param shoppingCart*/@Insert("insert into shopping_cart (name, user_id, dish_id, setmeal_id, dish_flavor, number, amount, image, create_time) " +" values (#{name},#{userId},#{dishId},#{setmealId},#{dishFlavor},#{number},#{amount},#{image},#{createTime})")void insert(ShoppingCart shoppingCart);}

创建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="list" parameterType="ShoppingCart" resultType="ShoppingCart">select * from shopping_cart<where><if test="userId != null">and user_id = #{userId}</if><if test="dishId != null">and dish_id = #{dishId}</if><if test="setmealId != null">and setmeal_id = #{setmealId}</if><if test="dishFlavor != null">and dish_flavor = #{dishFlavor}</if></where>order by create_time desc</select>
</mapper>

3.3 功能测试

进入小程序,添加菜品

加入购物车,查询数据库

因为现在没有实现查看购物车功能,所以只能在表中进行查看。

在前后联调时,后台可通断点方式启动,查看运行的每一步。

3.4 代码提交

4.查看购物车

4.1 需求分析和设计

4.1.1 产品原型

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

4.1.2 接口设计

4.2 代码开发

4.2.1 Controller层

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

	/*** 查看购物车* @return*/@GetMapping("/list")@ApiOperation("查看购物车")public Result<List<ShoppingCart>> list(){return Result.success(shoppingCartService.showShoppingCart());}

4.2.2 Service层接口

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

	/*** 查看购物车* @return*/List<ShoppingCart> showShoppingCart();

4.2.3 Service层实现类

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

	/*** 查看购物车* @return*/public List<ShoppingCart> showShoppingCart() {return shoppingCartMapper.list(ShoppingCart.builder().userId(BaseContext.getCurrentId()).build());}

4.3 功能测试

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

4.4 代码提交

5.清空购物车

5.1 需求分析和设计

5.1.1 产品原型

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

5.1.2 接口设计

5.2 代码开发

5.2.1 Controller层

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

	/*** 清空购物车商品* @return*/@DeleteMapping("/clean")@ApiOperation("清空购物车商品")public Result<String> clean(){shoppingCartService.cleanShoppingCart();return Result.success();}

5.2.2 Service层接口

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

	/*** 清空购物车商品*/void cleanShoppingCart();

5.2.3 Service层实现类

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

	/*** 清空购物车商品*/public void cleanShoppingCart() {shoppingCartMapper.deleteByUserId(BaseContext.getCurrentId());}

5.2.4 Mapper层

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

	/*** 根据用户id删除购物车数据** @param userId*/@Delete("delete from shopping_cart where user_id = #{userId}")void deleteByUserId(Long userId);

5.3 功能测试

进入到购物车页面

点击清空:

相关文章:

Day7 苍穹外卖项目 缓存菜品、SpringCache框架、缓存套餐、添加购物车、查看购物车、清空购物车

目录 1.缓存菜品 1.1 问题说明 1.2 实现思路 1.3 代码开发 1.3.1 加入缓存 1.3.2 清除缓存 1.3.2.1 新增菜品优化 1.3.2.2 菜品批量删除优化 1.3.2.3 修改菜品优化 1.3.2.4 菜品起售停售优化 1.4 功能测试 1.4.1 加入缓存 1.4.2 菜品修改 1.5 代码提交 2.缓存套餐 2.1 Spring C…...

华为路由策略和策略路由学习笔记

控制网络流量可达性 思考&#xff1a;如何控制网络流量可达性&#xff1f; 解决方案一:可通过修改路由条目(即对接收和发布的路由进行过滤)来控制流量可达性&#xff0c;这种方式称为路由策略。 解决方案二:可直接通过依据用户制定的策略进行转发&#xff0c;且该策略优于路由…...

网上图书购物管理系统|Java|SSM|VUE| 前后端分离

【一】可以提供远程部署安装&#xff0c;包扩环境 【二】提供软件相关的安装包 【三】如果需要提供java入门资料可咨询 【技术栈】 1⃣️&#xff1a;架构: B/S、MVC 2⃣️&#xff1a;系统环境&#xff1a;Windowsh/Mac 3⃣️&#xff1a;开发环境&#xff1a;IDEA、JDK1.8、M…...

杂发单的单据类型一个参数的逻辑

【核准中可改】被产线滥用了。它们可以这样做&#xff0c;开立一张杂发单&#xff0c;打印出来交领导层签名。单据要交财务做核算的。然后去修改杂发单的材料。以为可以瞒天过海。2个仓库&#xff0c;一个中掉坑里&#xff0c;一个发现了它们的拙劣的手段&#xff0c;上报之后没…...

AUTOSAR CP中基于通信模块(COM)的Transformer-R24的规范导读

该文档是关于 AUTOSAR CP中基于通信模块&#xff08;COM&#xff09;的Transformer的规范说明&#xff0c;主要内容包括引言、相关文档、约束与假设、功能规范、API 规范、配置规范等&#xff0c;旨在为汽车电子系统开发中基于 COM 的Transformer提供全面的技术规范和指导。 一…...

yarn : 无法加载文件 C:\Users\L\AppData\Roaming\npm\yarn.ps1,因为在此系统上禁

关于执行安装yarn命令后执行yarn -v报错&#xff1a; 先确认执行安装yarn命令是否有误 # 安装yarn npm install yarn -g 终端输入set-ExecutionPolicy RemoteSigned 当然如果yarn -v仍然执行失败&#xff0c;考虑使用管理员方式运行IDEA&#xff0c; 注&#xff1a;如上操作…...

证明直纹面是可展曲面沿着直母线,曲面的切平面不变

目录 证明直纹面是可展曲面的当且仅当沿着直母线&#xff0c;曲面的切平面不变 证明直纹面是可展曲面的当且仅当沿着直母线&#xff0c;曲面的切平面不变 直纹面是可展曲面当且仅当沿着直母线&#xff0c;曲面的切平面不变. 证明&#xff1a;设直纹面 S S S的参数式为 r ( u …...

一种将png图像批量转换为RGB格式的方法

1.前言 大家在做2D图像分类的时候&#xff0c;代码经常要求图像是RGB格式。有时候我们的图像并不是RGB&#xff0c;尤其是处理病理图像切分的pach的时候&#xff0c;这时候就需要批量进行格式转换了。好了&#xff0c;直接上代码&#xff1a; import os from PIL import Image…...

Python 中的继承机制是什么样的?

Python的继承机制允许一个类&#xff08;子类&#xff09;继承另一个类&#xff08;父类或基类&#xff09;的属性和方法。 通过继承&#xff0c;可以实现代码复用&#xff0c;同时也能让代码结构更加清晰、易于维护。 Python支持单继承和多继承&#xff0c;并且有丰富的特性…...

MyBatis注解开发(一)

目录 使用注解完成CRUD 1. SqlMapConfig.xml配置文件 2. UserDao接口方法和注解的编写 3. UserTest测试方法的编写 注解方式比较简单&#xff0c;但是实际开发不推荐使用注解&#xff0c;使用配置文件的方式&#xff0c;不需要改源代码。 Insert:添加 Update:修改 Delete:删…...

挑战用React封装100个组件【010】

Hello&#xff0c;大家好&#xff0c;今天我挑战的组件是这样的&#xff01; 今天这个组件是一个打卡成功&#xff0c;或者获得徽章后的组件。点击按钮后&#xff0c;会弹出礼花。项目中的勋章是我通过AI生成的&#xff0c;还是很厉害的哈&#xff01;稍微抠图直接使用。最后面…...

深入浅出:Go语言中的错误处理

深入浅出&#xff1a;Go语言中的错误处理 引言 在任何编程语言中&#xff0c;错误处理都是一个至关重要的方面。它不仅影响程序的稳定性和可靠性&#xff0c;还决定了用户体验的质量。Go语言以其简洁明了的语法和强大的并发模型而著称&#xff0c;但其错误处理机制同样值得关…...

Spire.doc 合并word,复制word

之前使用的poi来实现这个功能&#xff0c;然后发现在复制chart时&#xff0c;边框样式无法修改&#xff0c;于是就使用了spire.doc 1. 引入依赖 <repositories><repository><id>com.e-iceblue</id><name>e-iceblue</name><url>https…...

《Java核心技术I》线程局部变量

线程局部变量 ThreadLocal辅助类为各个线程提供各自的实例。 public static final THreadLocal dateFormat ThreadLocal.withInitial(()->new SimpleDateFormat("yyyy-MM-dd")); 要访问具体的格式化方法&#xff0c;可以调用&#xff1a; String dateStamp d…...

C++实现排序算法:冒泡排序

目录 前言 冒泡排序性质 C代码实现冒泡排序 冒泡图解 第一趟排序 第二趟排序 第三趟排序 排序结果 结语 前言 冒泡排序的基本思想是通过从前往后&#xff08;从后往前&#xff09;两两比较&#xff0c;若为逆序&#xff08;即arr[i] < arr[i 1]&#xff09;则交换…...

智慧银行反欺诈大数据管控平台方案(八)

智慧银行反欺诈大数据管控平台的核心理念&#xff0c;在于通过整合先进的大数据技术、算法模型和人工智能技术&#xff0c;构建一个全面、智能、动态的反欺诈管理框架&#xff0c;以实现对金融交易的全方位监控、欺诈行为的精准识别和高效处理。这一理念强调数据驱动决策&#…...

阿里云通义千问:全面解析智能云服务先锋

一、技术架构与基础 模型构建基石 采用大规模语料库训练&#xff0c;涵盖多领域知识&#xff0c;如科学、历史、文学等&#xff0c;确保知识储备丰富多样。运用先进的神经网络架构&#xff0c;深度优化模型结构&#xff0c;提高信息处理效率与准确性。持续的语料更新机制&…...

Qt 设置QLineEdit控件placeholderText颜色

Qt 会根据QLineEdit控件显示文本的颜色自动设置placeholderText颜色&#xff0c;如果想自定义placeholderText颜色&#xff0c;可以通过以下方法。 在样式文件中增加以下设置&#xff1a; QLineEdit#lineEdit_userName, QLineEdit#lineEdit_password{border: none;padding: 6…...

opencv光流法推测物体的运动

光流法是计算机视觉中的一种技术&#xff0c;用于估计图像中相邻帧之间的像素位移或运动。它是一种用于追踪图像中物体运动的技术&#xff0c;可以在视频中检测并测量物体的运动轨迹。基本上&#xff0c;光流意味着计算像素的移动向量作为物体在两个相邻图像之间的位移差。光流…...

Vue指令(一)--v-html、v-show、v-if、v-else、v-else-if、v-on、v-bind、v-for、v-model

目录 &#xff08;一&#xff09;初识指令和内容渲染指令v-html 1.v-html 案例&#xff1a; 官网的API文档 &#xff08;二&#xff09;条件渲染指令v-show和v-if 1. v-show 2. v-if &#xff08;三&#xff09;条件渲染指令v-else和v-else-if 案例 &#xff08;四…...

Elixir GenServer

GenServer 是一个用来实现客户端-服务器模式中服务器的行为模块。 GenServer 是一个普通的 Elixir 进程&#xff0c;同其他 Elixir 进程一样&#xff0c;它可以用来保存状态、异步执行代码等。使用这个模块来实现通用服务器进程&#xff08;GenServer&#xff09;的优势在于&a…...

第八节、Bresenham直线插补【51单片机-TB6600驱动器-步进电机教程】

摘要&#xff1a;前面章节主要介绍单个电机控制&#xff0c;本节内容介绍两个电机完成直线插补运动 一、 Bresenham直线算法介绍 Bresenham直线算法由Jack Elton Bresenham于1962年在IBM开发&#xff0c;最初用于计算机显示直线&#xff0c;它确定应该选择的n维光栅的点&#…...

JDK1.8

JDK1.8 1. Lamdba表达式 Lambda表达式是什么&#xff1f; Lambda是一个匿名函数&#xff0c;我们可以将Lambda表达式理解为一段可以传递的代码&#xff08;将代码像数据一样传递&#xff09;。使用它可以写出简洁、灵活的代码。作为一种更紧凑的代码风格&#xff0c;使java语…...

【jvm】讲讲jvm中的gc

目录 1. 说明2. 主要算法2.1 标记-清除算法2.2 复制算法2.3 标记-整理算法3. 主要回收器3.1 Serial GC3.2 Parallel GC3.3 CMS&#xff08;Concurrent Mark-Sweep&#xff09;GC3.4 G1&#xff08;Garbage-First&#xff09;GC 4. 触发条件4.1 Minor GC&#xff08;Young GC&am…...

Oracle 用户管理模式下的恢复案例-不完全恢复

1. 不完全恢复的几种常用方法 01. recover database using backup controlfile 如果丢失当前控制文件&#xff0c;用冷备份的控制文件恢复的时候&#xff0c;用来告诉 oracle&#xff0c;不要以 controlfile 中的 scn 作为恢复的终点&#xff1b; 02. recover database until …...

Leetcode经典题4--查找数组中的多数元素+Boyer-Moore 投票算法

题目描述&#xff1a; 给定一个大小为 n 的数组 nums &#xff0c;返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的&#xff0c;并且给定的数组总是存在多数元素。 输入输出示例 输入&#xff1a;nums [2,2,1,1,1,2,2] 输出…...

ubuntu20.04 如何使用 perf 性能分析工具 以及 hotspot 性能分析可视化工具?

官方文档&#xff1a;https://perfwiki.github.io/main/ 如何安装&#xff1a;https://askubuntu.com/questions/50145/how-to-install-perf-monitoring-tool 首先是安装 perf: sudo apt-get install linux-tools-common sudo apt-get install linux-tools-generic sudo apt-…...

Scala-隐式转换

系统自动完成的&#xff1a;把一种类型的数据转成了另一种类型的数据。 不是所有的类型都可以执行隐式转换 函数的参数类型 和 返回值类型是重要的&#xff0c;它约定了把什么类型转成什么类型 //张三的函数def getAge():Double{8.5}//函数名&#xff1a;tranform 不重要//函…...

基于Java和Vue开发的漫画阅读软件漫画阅读小程序漫画APP

前景分析 受众广泛&#xff1a;漫画的受众群体广泛&#xff0c;不仅限于青少年&#xff0c;还涵盖了成年人等多个年龄层和社会阶层。漫画文化在全球范围内的影响力不断扩大&#xff0c;未来漫画软件创业可以考虑全球市场的拓展。 市场需求大&#xff1a;数字化阅读趋势下&…...

租赁小程序的优势与应用场景解析

内容概要 租赁小程序&#xff0c;听起来是不是很酷&#xff1f;其实&#xff0c;它就是一个让你可以方便地租借各种高成本但用得不频繁的商品的平台。想象一下&#xff0c;当你需要租一件派对用的华丽小礼服&#xff0c;或是想体验一下超酷的运动器材&#xff0c;租赁小程序就…...

CC-Link IEFB转Modbus TCP协议网关(技术分享)

一&#xff0c;设备主要功能 捷米特JM-CCLKIE-TCP网关实现连接到CC-Link IE Field Basic总线中做为从站使用&#xff0c;连接到Modbus TCP总线中做为主站或从站使用。 应用广泛&#xff1a;捷米特JM-CCLKIE-TCP广泛应用于支持Modbus TCP接口的上位机、变频器、仪表、马保等等…...

开发手札:Win+Mac下工程多开联调

最近完成一个Windows/Android/IOS三端多人网络协同项目V1.0版本&#xff0c;进入测试流程了。为了方便自测&#xff0c;需要用unity将一个工程打开多次&#xff0c;分别是Win/IOS/Android版本&#xff0c;进行多角色联调。 在Win开发机上&#xff0c;以Windows版本为主版…...

SSM报错:表现层方法应该返回字符串,但是返回页面

在进行SSM项目时&#xff0c;后端表现层应该返回给前端字符串&#xff0c;但是却跳转页面 1.首先检查是否使用ResponseBody注解 ResponseBody注解 作用&#xff1a;将java对象转为json格式的数据。将controller的方法返回的对象通过适当的转换器转换为指定的格式之后&#xff0…...

Unity中使用Sqlite存储本地数据

sqlite-net sqlite下载页 我的环境&#xff1a;win11、unity团结1.3.4 1.下载sqlite-net&#xff0c;将SQLite.cs脚本导入Unity 2.下载各平台依赖项&#xff0c;如dll、aar等。导入Unity并设置 3.简单列子&#xff0c;打包测试 using System; using System.IO; using SQLi…...

微信直连:商户转账到零钱API开发报错:java.security.InvalidKeyException: Illegal key size

jdk版本 解决方案&#xff1a;下载jce_policy-8&#xff0c;替换jre下的 local_policy.jar 文件 替换jdk内的jre下的 替换jre下的 JDK 8u161 之后默认使用了不限制的Policy...

语言处理程序基础

逻辑运算 正规式 有限自动机 上下文无关文法 表达式&#xff08;前缀、后缀、中缀&#xff09; 将表达式&#xff08;a-b&#xff09;*&#xff08;c5&#xff09;构造成树的步骤为&#xff1a;括号不能出现在树中&#xff1b;按照表达式的计算顺序来依次构造&#xff01;&…...

deepsort复现报错TypeError: tuple indices must be integers or slices, not tuple 解决

deepsort复现中遇到的TypeError: tuple indices must be integers or slices, not tuple问题的解决-CSDN博客 以上为参考教程。 复现的时候出现这个报错&#xff0c;搞了好久试了好多方法&#xff0c;包括降 scikit-learn版本&#xff0c;都不行&#xff0c;最后终于找到这个博…...

「Mac畅玩鸿蒙与硬件42」UI互动应用篇19 - 数字键盘应用

本篇将带你实现一个数字键盘应用&#xff0c;支持用户通过点击数字键输入数字并实时更新显示内容。我们将展示如何使用按钮组件和状态管理来实现一个简洁且实用的数字键盘。 关键词 UI互动应用数字键盘按钮组件状态管理用户交互 一、功能说明 数字键盘应用将实现以下功能&…...

MSSQL SQLi Labs靶场 第一关 (手工版)

一.判断是否存在注入 输入http://172.16.0.87/less-1.asp?id1页面异常 加上闭合点&#xff1a;http://172.16.0.87/less-1.asp?id1--页面正常&#xff0c;说明存在注入 二.判断数据库类型 可通过以下Payload来探测当前站点是否是MSSQL数据库&#xff0c;正常执⾏说明后台数据…...

重磅更新:CnosDB 2.3.5.4 版本上线, 性能提升,问题修复一网打尽

&#x1f4e2; 重磅更新&#xff1a;CnosDB 2.3.5.4 版本上线, 性能提升&#xff0c;问题修复一网打尽 &#x1f4e2; 我们很高兴地向大家介绍最新版本的更新&#xff0c;以下是本次更新的主要内容&#xff1a; &#x1f539; 版本号&#xff1a;2.3.5.4 &#x1f539; 发布…...

后端-编辑按钮的实现

编辑一共要实现两步&#xff1a; 1.点击编辑蹦出来一个弹窗&#xff0c;此时需要回显&#xff0c;根据id查出来这条数据 2.修改某些值之后点击保存的时候调用修改的接口 根据id查询的时候正常操作 修改值的时候要注意一些问题 mapper层的Employee和impl层的接收实体不一样...

Python语言基础入门教程

Python是一种简单易学、功能强大的编程语言&#xff0c;非常适合初学者入门。本篇博客将带你从零开始&#xff0c;学习Python的一些基础知识&#xff0c;并通过详细分析和实例帮助你理解。 一、什么是Python&#xff1f; Python是一种高级编程语言&#xff0c;具有以下特点&am…...

verilog编程规范

verilog编程规范 文章目录 verilog编程规范前言一、代码划分二、verilog编码ABCDEFG 前言 高内聚&#xff0c;低耦合&#xff0c;干净清爽的代码 一、代码划分 高内聚&#xff1a; 一个功能一个模块干净的接口提取公共的代码 低耦合&#xff1a; 模块之间低耦合尽量用少量…...

Spring Boot 的启动原理

Spring Boot 是由 Pivotal 团队发布的一个开源框架&#xff0c;它基于 Spring 框架&#xff0c;旨在简化企业级应用程序的开发过程。与传统的 Spring 项目相比&#xff0c;Spring Boot 提供了一种更加简洁、高效的方式来构建和部署应用程序。其核心理念是“约定优于配置”和“自…...

Oracle 19C RU补丁升级,从19.7to19.25 -单机

1. 环境信息&#xff1a; 角色 数据库 IP地址 数据库版本 数据库名称 源端 单实例 172.30.21.191 19.7 hfzcdb 2. 安装准备 用rman备份数据库&#xff0c;再备份下oracle目录&#xff1a; 命令&#xff1a; tar zavf oracle.tar /oracle 19.25版本关于19C的补丁内…...

Web day08 项目实战(2)

目录 查询员工&#xff1a; 在EmpController层&#xff1a; 在pojo层&#xff1a; 在EmpServiceImpl 层中&#xff1a; 在dao层&#xff1a; 新增员工: pojo层&#xff1a; EmpController层&#xff1a; 在EmpServiceImpl 层中&#xff1a; 在EmpMapper层&#xff1a; …...

生命周期(vue2和vue3的生命周期对比)有哪些?

Vue.js 提供了一套完整的数据驱动和组件化思想&#xff0c;其生命周期钩子是开发者理解组件行为的关键。以下是 Vue 2 和 Vue 3 中生命周期钩子的对比&#xff1a; Vue 2 生命周期钩子 创建阶段 beforeCreate&#xff1a;实例初始化之前调用&#xff0c;此时 data 和 methods …...

智慧信息发系统——控件磁吸

//鍙悜涓婂惛 function fun_鎺т欢纾佸惛_璁&#xff04;畻(鐏典綋y,寮曞姏鍊&#xfffd;,閫夋嫨鍖哄煙,璁&#xff04;畻鍧愭爣绫诲瀷) {const 灞忓箷椤圭洰 document.querySelectorAll(閫夋嫨鍖哄煙);var 璺濈鎴戞渶杩憏 0;var 鏈€杩戜竴娆″紩鍔&#xfffd; 0…...

【时间序列预测】基于PyTorch实现CNN_LSTM算法

文章目录 1. CNN_LSTM模型概述2. 网络结构3. 完整代码实现4.模型解析4.1 CNN层4.2 ReLU层4.3 MaxPooling层4.4 LSTM层4.5 输出层4.6 前向传播 5. 总结 在时间序列预测任务中&#xff0c;CNN&#xff08;卷积神经网络&#xff09;和LSTM&#xff08;长短期记忆网络&#xff09;是…...

Android环境搭建

Android环境搭建 第一步&#xff1a;安装 Homebrew 执行以下命令来安装 Homebrew&#xff1a; /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"检测是否安装成功&#xff1a; brew --version第二步&#xff1a;安装 No…...