苍穹外卖--day07(缓存商品,购物车)

问题:

如果多个用户点餐,就会加大数据库的访问压力,会导致体验不好,页面卡

所以要将商品存到缓存里面

1.缓存菜品

问题说明:

多个用户点餐,就会加大数据库的访问压力,会导致体验不好,页面卡

实现思路:

使用缓存

访问redis的数据本质是对内存的操作(性能高)

访问Mysql是磁盘IO操作

思路:

查询菜品--->后端服务--->缓存是否存在--->不存在--->查询数据库--->载入缓存(下次就直接读取缓存)

--->存在--->读取缓存

缓存逻辑分析

1.展示页面 的逻辑是每点击一个分类就展示对应的菜品

所以我们的逻辑是每个分类 下的菜品 保存一份缓存数据(不选择所有菜品保存为一份缓存数据)

【即一个分类就是一份缓存数据】

更新缓存时,只改一个分类,不影响其他所有分类(最大好处!)
场景:

后台只修改了 "川菜" 里的一道菜

  • 如果是全部菜品存一份缓存 → 必须删除整个大缓存→ 所有分类都要重新查数据库→ 性能极差

  • 如果是按分类存缓存只删除 dish:100 这一个缓存 → 其他分类(粤菜、汤类)完全不受影响→ 数据库压力极小

总结:

粒度越细,更新越轻,性能越好!

2.注意:数据库中菜品数据有变更时清理缓存数据

key:dish_id(id为动态分类ID)

value:String字符串(java中的对象会序列化为字符串【redis中保存的就是字符串】)

代码开发

按照视频中写了代,出现问题

修改代码(redis序列化)

1.user.ShopController

Redis键不存在,在redis新建一个键

2.DishVO对象中的updateTime字段序列化时出现了问题

可能的原因

  1. updateTime字段类型定义不明确或使用了不支持的类型

  2. 缺少适当的Jackson注解来处理时间类型字段

  3. 类型信息在序列化过程中丢失

(1)修改redis配置类

复制代码
public class RedisConfiguration {
​
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
        log.info("开始创建redis模板对象...");
        RedisTemplate redisTemplate = new RedisTemplate();
        //设置redis的连接工厂对象,然后这里工厂对象不需要创建,因为springboot-starter已经创建了,只需要通过Bean声明获取即可
        redisTemplate.setConnectionFactory(redisConnectionFactory);
​
        //修改后的序列化器
        //使用Jackson2JsonRedisSerializer替换GenericJackson2JsonRedisSerializer
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
​
        //配置ObjectMapper以支持Java 8时间类型
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //注册JavaTimeModule以支持LocalDateTime等时间类型
        objectMapper.registerModule(new JavaTimeModule());
        //禁用将日期写为时间戳的特性
        objectMapper.disable(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
​
        serializer.setObjectMapper(objectMapper);
​
        //设置redis value的序列化器
        redisTemplate.setValueSerializer(serializer);
​
        //设置hash的key和value序列化器
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(serializer);
​
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

(2)修改DishVO

复制代码
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
//分类名称
private String categoryName;

1.代码---缓存菜品数据

复制代码
public class DishController {
    @Autowired
    private DishService dishService;
​
    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * 根据分类id查询菜品
     *
     * @param categoryId
     * @return
     */
    @GetMapping("/list")
    @ApiOperation("根据分类id查询菜品")
    public Result<List<DishVO>> list(Long categoryId) {
        //构造redis中的key,规则:dish_分类id
        String 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);
    }
​
}

2.代码---清理缓存数据

缓存要注意保持数的一致性

数据库 的数据变更 时,要及时把缓存数据清理,然后就会重新查询数据库,这样就会一致了

修改管理端接口 DishController 的相关方法,加入清理缓存的逻辑,需要改造的方法:

•新增菜品(精确删除)

•修改菜品(可能也会影响菜品分类,如果还要判断,查询...会比较麻烦,所以直接统一删除,和批量删除的一样)

•批量删除菜品(可能删除多个菜品,而多个菜品可能属于某一个分类,或多个分类,就会影响到多个key,为了不那么复杂,所以直接把dish开头的数据全清理即可)

•起售、停售菜品

代码
注意:匹配模式

cleanCach("*dish_*");

删除操作不能使用匹配模式,但可以插入集合,所以可以配合keys函数进行使用

查询才可以

复制代码
package com.sky.controller.admin;
​
import com.sky.dto.DishDTO;
import com.sky.dto.DishPageQueryDTO;
import com.sky.entity.Dish;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.DishService;
import com.sky.vo.DishVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
​
import java.util.List;
import java.util.Set;
​
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {
    @Autowired
    private DishService dishService;
    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * 新增菜品
     * @param dishDTO
     * @return
     */
    @PostMapping
    @ApiOperation("新增菜品接口")
    public Result save(@RequestBody DishDTO dishDTO) {
        log.info("新增菜品:{}", dishDTO);
        dishService.saveWithFlavor(dishDTO);
​
        //新增模块:清理缓存数据,精确清理
        String key = "dish_" + dishDTO.getCategoryId();
        cleanCach("*dish_*");
        return Result.success();
    }
​
    /**
     * 菜品分页查询
     * @param dishPageQueryDTO
     * @return
     */
    @GetMapping("/page")
    @ApiOperation("菜品分页查询接口")
    public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO) {
        log.info("菜品分页查询:{}",dishPageQueryDTO );
        PageResult pageResult = dishService.page(dishPageQueryDTO);
        return Result.success(pageResult);
    }
    /**
     * 批量删除接口
     * @param ids
     * @return
     */
    @DeleteMapping
    @ApiOperation("菜品批量删除接口")
    public Result delete(@RequestParam List<Long> ids) {
        log.info("菜品批量删除:{}",ids);
        dishService.deleteBatch(ids);
​
        //将所有的菜品缓存数据清理掉,所有以dish_开头的key
        cleanCach("dish_*");
        return Result.success();
    }
​
    /**
     * 根据id查询菜品和对应的口味数据
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    @ApiOperation("根据id查询菜品和对应的口味数据")
    public Result<DishVO> getById(@PathVariable Long id) {
        log.info("根据id查询菜品和对应的口味数据:{}",id);
        //查询菜品还要包含菜品口味数据,所以getByIdWithFlavor,即根据ID查询菜品和口味
        DishVO dishVO = dishService.getByIdWithFlavor(id);
        return Result.success(dishVO);
    }
​
    /**
     * 根据ID修改菜品基本信息和对应的口味信息
     * @param dishDTO
     * @return
     */
    @PutMapping
    @ApiOperation("根据ID修改菜品基本信息和对应的口味信息")
    public Result update(@RequestBody DishDTO dishDTO) {
        log.info("修改菜品信息:{}",dishDTO);
        dishService.updateWithFlavor(dishDTO);
​
        //将所有的菜品缓存数据清理掉,所有以dish_开头的key
        cleanCach("dish_*");
        return Result.success();
    }
    /**
     * 菜品起售停售
     * @param status
     * @param id
     * @return
     */
    @PostMapping("/status/{status}")
    @ApiOperation("菜品起售停售")
    public Result startOrStop(@PathVariable Integer status, Long id) {
        log.info("菜品起售停售:{}",id);
        dishService.startOrStop(status,id);
​
        //将所有的菜品缓存数据清理掉,所有以dish_开头的key
        cleanCach("dish_*");
        return Result.success();
    }
​
    /**
     * Day4:根据分类id查询菜品
     * @param categoryId
     * @return
     */
    @GetMapping("/list")
    @ApiOperation("根据分类id查询菜品")
//要返回列表,所以List
    public Result<List<Dish>> list(Long categoryId){
        List<Dish> list = dishService.list(categoryId);
        return Result.success(list);
    }
​
    /**
     * 清理缓存数据
     * @param pattern
     */
    private void cleanCach(String pattern) {
        Set keys = redisTemplate.keys(pattern);
        redisTemplate.delete(keys);
    }
​
}

功能测试

2.缓存套餐(spring-cach缓存框架:简化编码)

Spring-Cach(类似前面的事务管理,只需要在对应的地方加入注解即可)

概念

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

底层是基于代理技术,加入了对应的注解后,Spring Cache 就会为我们当前这个controller创建一个代理对象,在请求controller之前,其实是先进入代理对象,去查询我们这个redis,查到之后直接返回【此时controller的方法就没有调用到】,查不到就会通过反射调用到真正的目标方法

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

•EHCache

•Caffeine

•Redis

如果想实现别的缓存底层,只需要导入jar包即可【即对应的maven坐标即可】

常用注解

注解 说明
@EnableCaching 开启缓存注解功能,通常加在启动类上
@Cacheable(查找) 在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,调用方法并将方法返回值放到缓存中 (相对于第三个:可取可放)
@CachePut(新增或更新 将方法的返回值放到缓存中【仅放】
@CacheEvict (用于删除) 将一条(key=...)或**多条(allEntries【所有键】 = true)**数据从缓存中删除

注解的使用:

通过注解和指定( = "随便写,指定缓存名称",key="参数.id/返回值.id")进行缓存操作

【这里key的写法是多样的,本质就是在动态算字符串,该字符串就会成为整个key的一部分,key使用的是spel表达式】

可以直接在controller中的方法使用

注意:redis双冒号::

可以表示树形结构

实现思路

具体的实现思路如下:

•导入Spring Cache和Redis相关maven坐标

•在启动类上加入@EnableCaching注解

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

开启缓存注解功能【因为用户端要展示菜品数据】

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

(用于数据变更,而且只有管理端有权利)

代码开发

添加启动类注解

admin
复制代码
/**
 * 套餐管理
 */
@RestController
@RequestMapping("/admin/setmeal")
@Api(tags = "套餐相关接口")
@Slf4j
public class SetmealController {
​
    @Autowired
    private SetmealService setmealService;
​
    /**
     * Day4:新增套餐
     *
     * @param setmealDTO
     * @return
     */
    @PostMapping
    @ApiOperation("新增套餐")
    @CacheEvict(value = "setmealCache",key ="#setmealDTO.categoryId")
    public Result save(@RequestBody SetmealDTO setmealDTO) {
        setmealService.saveWithDish(setmealDTO);
        return Result.success();
    }
​
    /**
     * Day4:分页查询
     *
     * @param setmealPageQueryDTO
     * @return
     */
    @GetMapping("/page")
    @ApiOperation("分页查询")
    public Result<PageResult> page(SetmealPageQueryDTO setmealPageQueryDTO) {
        PageResult pageResult = setmealService.pageQuery(setmealPageQueryDTO);
        return Result.success(pageResult);
    }
​
    /**
     * Day4:批量删除套餐
     *
     * @param ids
     * @return
     */
    @DeleteMapping
    @ApiOperation("批量删除套餐")
​
    @CacheEvict(cacheNames = "setmealCache",allEntries = true)
    public Result delete(@RequestParam List<Long> ids) {
        setmealService.deleteBatch(ids);
        return Result.success();
    }
​
    /**
     * Day4:根据id查询套餐,用于修改页面回显数据
     *
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    @ApiOperation("根据id查询套餐")
    public Result<SetmealVO> getById(@PathVariable Long id) {
        SetmealVO setmealVO = setmealService.getByIdWithDish(id);
        return Result.success(setmealVO);
    }
    /**
     * Day4:修改套餐
     *
     * @param setmealDTO
     * @return
     */
    @PutMapping
    @ApiOperation("修改套餐")
    @CacheEvict(cacheNames = "setmealCache",allEntries = true)
    public Result update(@RequestBody SetmealDTO setmealDTO) {
        setmealService.update(setmealDTO);
        return Result.success();
    }
​
    /**
     * Day4:套餐起售停售
     * @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();
    }
User
复制代码
@RestController("userSetmealController")
@RequestMapping("/user/setmeal")
@Api(tags = "C端-套餐浏览接口")
public class SetmealController {
    @Autowired
    private SetmealService setmealService;
​
    /**
     * 条件查询
     *
     * @param categoryId
     * @return
     */
    @GetMapping("/list")
    @ApiOperation("根据分类id查询套餐")
    @Cacheable(cacheNames = "setmealCache",key = "#categoryId")
    public 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);
    }
​
    /**
     * 根据套餐id查询包含的菜品列表
     *
     * @param id
     * @return
     */
    @GetMapping("/dish/{id}")
    @ApiOperation("根据套餐id查询包含的菜品列表")
    public Result<List<DishItemVO>> dishList(@PathVariable("id") Long id) {
        List<DishItemVO> list = setmealService.getDishItemById(id);
        return Result.success(list);
    }

功能测试

先起售套餐再测试

3.添加购物车

1.需求分析与设计

业务需求:

对于套餐,直接点击添加

对应菜品:

1.没显示口味数据,直接点击加号

2.有显示选择规格,选择后再加入购物车

接口设计:

•请求方式:POST

•请求路径:/user/shoppingCart/add

•请求参数:套餐id或菜品id、口味(建议通过json格式)

•返回结果:code、data、msg

数据库设计(shopping_cart表):

•作用:暂时存放所选商品的地方

•选的什么商品

•每个商品都买了几个

不同用户的购物车需要区分开

字段名 数据类型 说明 备注
id bigint 主键 自增
name varchar(32) 商品名称 冗余字段
image varchar(255) 商品图片路径 冗余字段
user_id bigint 用户id 逻辑外键
dish_id bigint 菜品id 逻辑外键
setmeal_id bigint 套餐id 逻辑外键
dish_flavor varchar(50) 菜品口味
number int 商品数量
amount decimal(10,2) 商品单价 冗余字段
create_time datetime 创建时间

冗余字段,空间换时间,但注意,不能大量使用(而且是经常稳定,不经常发生变化的)

连接查询变成单表查询

2.代码开发

ShopingCartDTO(三个属性)

ShopingCartController

复制代码
public class ShoppingCartController {
    @Autowired
    private ShoppingCartService shoppingCartService;
    /**
     * 添加购物车
     * @param shoppingCartDTO
     * @return
     */
    @PostMapping("/add")
    public Result add(@RequestBody ShoppingCartDTO shoppingCartDTO){
        log.info("添加购物车,商品信息为:{}", shoppingCartDTO);
        shoppingCartService.addShoppingCart(shoppingCartDTO);
        return Result.success();
    }
}

ShopingCartService

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

ShopingCartServiceImpl

复制代码
public class ShoppingCartServiceImpl implements ShoppingCartService {

    @Autowired
    private ShoppingCartMapper shoppingCartMapper;
    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private SetmealMapper setmealMapper;

    /**
     * 添加购物车
     *
     * @param shoppingCartDTO
     */
    @Override
    public void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {
        //判断当前商品是否在购物车中
        ShoppingCart shoppingCart = new ShoppingCart();
        BeanUtils.copyProperties(shoppingCartDTO, shoppingCart);
        shoppingCart.setUserId(BaseContext.getCurrentId());
        List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);

        //存在,number加1
        if (list != null && list.size() > 0) {
            //只可能有一条数据
            ShoppingCart cart = list.get(0);
            cart.setNumber(cart.getNumber() + 1);
            //数量加1,再执行update语句
            shoppingCartMapper.updateNumberById(cart);
        } else {
            //不存在,插入一条数据

            //判断当前添加的是菜品还是套餐
            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 {
                //本次添加的是套餐
                Long setmealId = shoppingCartDTO.getSetmealId();
                Setmeal setmeal = setmealMapper.getById(setmealId);
                shoppingCart.setName(setmeal.getName());
                shoppingCart.setImage(setmeal.getImage());
                shoppingCart.setAmount(setmeal.getPrice());
            }

            shoppingCart.setNumber(1);
            shoppingCart.setCreateTime(LocalDateTime.now());
            shoppingCartMapper.insert(shoppingCart);
        }
    }
注意

(1)先判断商品是否存在

不存在:insert

存在:update(number字段加1)

(2)不同用户有不同的购物车,通过user_id来体现

ShopingCartMapper

复制代码
@Mapper
public interface ShoppingCartMapper  {
    /**
     * 动态条件查询
     * @param shoppingCartDTO
     */
    List<ShoppingCart> list(ShoppingCart shoppingCartDTO);

    /**
     * 根据ID修改商品数量
     * @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);
}

功能测试(user_id为空解决)

编写用户端拦截器并在配置类中注册,否则user_id为空

用户端拦截器JwtTokenUserInterceptor

复制代码
/**
 * jwt令牌校验的拦截器
 */
@Component
@Slf4j
public class JwtTokenUserInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 校验jwt
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }

        //1、从请求头中获取令牌(这里是修改为getUserTokenName)
        String token = request.getHeader(jwtProperties.getUserTokenName());

        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);

            //这里的getAdminSecretKey换成getUserSecretKey

            Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);

            //这里的empId换成userId

            Long UserId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
            log.info("当前用户的id:", UserId);
            //在当前线程(ThreadLocal)中设置当前登录的用户id
            BaseContext.setCurrentId(UserId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }
    }
}

配置类WebMvcConfiguration

复制代码
//用户端,注意也要排除获取店铺营业状态的接口,因为他在用户端登录之已经发出请求
registry.addInterceptor(jwtTokenUserInterceptor)
        .addPathPatterns("/user/**")
        .excludePathPatterns("/user/user/login")
        .excludePathPatterns("/user/shop/status");

4.查看购物车

1.需求分析与设计

产品原型

接口请求参数

不需要任何参数,因为是查询当前用户的所有购物车数据,然后传入user_id不是必须的

【因为我们每次业务操作都会在请求头带入token,后端会解析并拿到user_id】

返回结果需要接收多条数据(即一个数组【里面放购物车相关字段】)

2.代码开发

ShopingCartController

复制代码
/**
 * 查看购物车
 * @return
 */
@GetMapping("/list")
public Result<List<ShoppingCart>> list(){
    log.info("查看购物车");
    List<ShoppingCart> list = shoppingCartService.showShoppingCart();
    return Result.success(list);
}

ShopingCartService

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

ShopingCartServiceImpl

复制代码
/**
 * 查看购物车
 *
 * @return
 */
@Override
public List<ShoppingCart> showShoppingCart() {
    //获取当前微信用户的id
    Long userId = BaseContext.getCurrentId();
    ShoppingCart shoppingCart = ShoppingCart.builder()
            .userId(userId)
            .build();
    List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);
    return list;
}

ShopingCartMapper

已经有了

3.功能测试

小程序没有显示数据,看controller层有没有返回数据

5.清空购物车

1.需求分析与设计

产品原型

清空操作

请求参数

不需要,后端可获取用户id,并且直接清空即可

2.代码开发

ShopingCartController

复制代码
/**
 * 清空购物车
 * @return
 */
@DeleteMapping("/clean")
@ApiOperation("清空购物车")
public Result clean() {
    shoppingCartService.cleanShoppingCart();
    return Result.success();
}

ShopingCartService

复制代码
/**
 * 清空购物车
 */
void cleanShoppingCart();

ShopingCartServiceImpl

复制代码
/**
 * 清空购物车
 */
@Override
public void cleanShoppingCart() {
   //只能删除自己用户的购物车数据,所以要获取当前用户的id
   Long userId = BaseContext.getCurrentId();
   shoppingCartMapper.deleteByUserId(userId);
}

ShopingCartMapper

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

6.减去某个购物车(自己写)

1.需求分析与设计

接口页面

请求参数

json,传入购物车DTO

注意

减到0还显示,说明没有考虑到,减到0应该直接从购物车中删除该商品,而不是继续更新number

2.代码开发

ShopingCartController

复制代码
/**
     * 删除购物车中一个商品
     * @param shoppingCartDTO
     * @return
*/
@PostMapping("/sub")
@ApiOperation("删除购物车中一个商品")
public Result sub(@RequestBody ShoppingCartDTO shoppingCartDTO){
    log.info("删除购物车中一个商品,商品:{}", shoppingCartDTO);
    shoppingCartService.subShoppingCart(shoppingCartDTO);
    return Result.success();
}

ShopingCartService

复制代码
/**
     * 删除购物车中一个商品
     * @param shoppingCartDTO
*/
void subShoppingCart(ShoppingCartDTO shoppingCartDTO);

ShopingCartServiceImpl

复制代码
/**
     * 删除购物车中一个商品
     * @param shoppingCartDTO
*/
public void subShoppingCart(ShoppingCartDTO shoppingCartDTO) {
    ShoppingCart shoppingCart = new ShoppingCart();
    BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
    //设置查询条件,查询当前登录用户的购物车数据
    shoppingCart.setUserId(BaseContext.getCurrentId());

    List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);

    if(list != null && list.size() > 0){
        shoppingCart = list.get(0);

        Integer number = shoppingCart.getNumber();
        if(number == 1){
            //当前商品在购物车中的份数为1,直接删除当前记录
            shoppingCartMapper.deleteById(shoppingCart.getId());
        }else {
            //当前商品在购物车中的份数不为1,修改份数即可
            shoppingCart.setNumber(shoppingCart.getNumber() - 1);
            shoppingCartMapper.updateNumberById(shoppingCart);
        }
    }
}

ShopingCartMapper

复制代码
/**
     * 根据id删除购物车数据
     * @param id
*/
@Delete("delete from shopping_cart where id = #{id}")
void deleteById(Long id);
相关推荐
观无9 小时前
若依框架在window的打包部署
java
fengxin_rou9 小时前
【SpringBoot+Elasticsearch 内容搜索系统实战】:架构设计与全流程实现
spring boot·后端·elasticsearch
问心无愧05139 小时前
ctf show web入门 254
java·开发语言·笔记
逸Y 仙X10 小时前
文章三:Elasticsearch 集群恢复和索引分布
java·大数据·linux·服务器·elasticsearch·搜索引擎·全文检索
奋斗的小乌龟17 小时前
动态创建Agent02
java
ZFSS17 小时前
Localization Translate API 集成与使用指南
java·服务器·数据库·人工智能·mysql·ai编程
摇滚侠18 小时前
Java 零基础全套教程,集合框架,笔记 153-163
java·开发语言·笔记
nannan123218 小时前
后端技术栈梳理
java
L、21819 小时前
CANN算子开发调试实战:从“Segmentation Fault“到定位根因的完整流程
java·开发语言