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

苍穹外卖 项目记录 day07 商品缓存-购物车模块开发

文章目录

  • 前言
  • 清理缓存数据
  • Spring Cache 和常用注解
  • 缓存套餐
  • 添加购物车
  • 查看购物车
  • 清空购物车


前言

将商品信息放进redis缓存 Spring Cache技术

系统查询性能 用户端访问量过大 数据库访问压力随之增大 系统响应慢

使用Redis 缓存菜品数据 减少数据库查询 基于内存保存数据

前端 发请求 查询 后端服务 查询缓存是否存在 (存在缓存 读取缓存 不存在缓存 查询数据库 查询到数据 载入缓存)

key-value 键值对匹配

缓存逻辑分析 根据分类缓存数据 每个分类下菜品保存一份缓存数据

key dish_id value string List对象序列化成字符串 存储

菜品数据变更时 清理缓存数据 (延迟双删)

每次切换查询数据 都会发请求 查询数据库

public class DishController {
    @Autowired
    private DishService dishService;

    @Autowired
    //  已创建RedisConfiguration  直接注入即可
    private RedisTemplate redisTemplate;
    /**
     * 根据分类id查询菜品
     *
     * @param categoryId
     * @return
     */
    @GetMapping("/list")
    @ApiOperation("根据分类id查询菜品")
    public Result<List<DishVO>> list(Long categoryId) {
        //查询Redis 中是否存在菜品数据  操作redis  注入对象

        //构造redis的 key
        String key = "dish_" + categoryId;
        List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);
        
        if(list != null && list.size() > 0){
            // 如果存在 直接返回 不查询数据库
            return Result.success(list);
        }

        //如果不存在 查询数据库 将查询到的数据存入Redis中
        Dish dish = new Dish();
        dish.setCategoryId(categoryId);
        dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品

        list = dishService.listWithFlavor(dish);
        redisTemplate.opsForValue().set(key, list);

        return Result.success(list);
    }
}

清理缓存数据

保证数据一致性 缓存和数据库

修改 删除时 状态变化 新增时 清理缓存

改造对应方法 (DishController) 修改 admin 下 DishController 方法

清理缓存数据

package com.sky.controller.admin;

/**
 * @author Admin
 * @title: DishController
 * @projectName minjiang-takeaway
 * @description: DishController
 * @date 2024/1/20 18:17
 */

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;
    /**
     * 新增菜品
     */
    @PostMapping
    @ApiOperation("新增菜品")
    public Result save(@RequestBody DishDTO dishDTO){
        log.info("新增菜品:{}",dishDTO.toString());
        dishService.saveWithFlavor(dishDTO);
        
        //清理缓存数据
        String key = "dish_" + dishDTO.getCategoryId();
        redisTemplate.delete(key);
        return Result.success();
    }


    /**
     * 菜品分页查询
     *
     * @param dishPageQueryDTO
     * @return
     */
    @GetMapping("/page")
    @ApiOperation("菜品分页查询")
    public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO) {
        log.info("菜品分页查询:{}", dishPageQueryDTO);
        //调用分页查询接口
        PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);
        return Result.success(pageResult);
    }

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


    /**
     * 根据id查询菜品  先由id进行菜品数据回显 才能进行菜品修改
     *
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    @ApiOperation("根据id查询菜品")
    public Result<DishVO> getById(@PathVariable Long id) {
        log.info("根据id查询菜品:{}", id);
        DishVO dishVO = dishService.getByIdWithFlavor(id);
        return Result.success(dishVO);
    }

    /**
     * 修改菜品
     *
     * @param dishDTO
     * @return
     */
    @PutMapping
    @ApiOperation("修改菜品")
    public Result update(@RequestBody DishDTO dishDTO) {
        log.info("修改菜品:{}", dishDTO);
        dishService.updateWithFlavor(dishDTO);

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

    /**
     *
     * 根据分类id查询菜品列表
     * @param categoryId
     * @return
     */
    @GetMapping("/list")
    @ApiOperation("根据分类id查询菜品列表")
    public Result<List<Dish>> list(Long categoryId){
        List<Dish> list = dishService.list(categoryId);
        return Result.success(list);
    }

    /**
     * 抽取统一清理缓存数据方法
     */
    
    private void cleanCache(String pattern){
        //将所有菜品缓存数据清理掉  所有以dish_开头的key
        Set keys = redisTemplate.keys(pattern);
        redisTemplate.delete(keys);
    }
}

Spring Cache 和常用注解

Spring Cache spring缓存框架 简单加一个注解 实现缓存功能

Spring Cache 提供一层抽象 底层可切换不同缓存实现 EHCache Caffeine Redis

导入pom.xml

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

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

Spring Cache 常用注解

@EnableCaching 开启缓存注解 加载启动类上

@Cacheable 方法执行前 查询缓存是否有数据 有数据返回缓存数据 无数据 调用方法 将方法换回值 放到缓存中

@CachePut 将方法返回值放到缓存中

@CacheEvict 将一条或多条数据从缓存中删除

@PostMapping
//@CachePut(cacheNames = "userCache",key = "#user.id")
//@CachePut(cacheNames = "userCache",key = "#p0.id")
//@CachePut(cacheNames = "userCache",key = "#root.args[0].id")
@CachePut(cacheNames = "userCache",key = "#result.id")
//存到Redis的key值 userCache::user.id
public User save(@RequestBody User user){
    userMapper.insert(user);
    //插入数据时 保存数据到redis
    return user;
}

@GetMapping
@Cacheable(cacheNames = "userCache",key = "#id")
//获取user前 先从redis里找
public User getById(Long id){
    User user = userMapper.getById(id);
    return user;
}

@DeleteMapping
@CacheEvict(cacheNames = "userCache",key="#id")
public void deleteById(Long id){
    userMapper.deleteById(id);
}

@DeleteMapping("/delAll")
@CacheEvict(cacheNames = "userCache",allEntries = true)
public void deleteAll(){
    userMapper.deleteAll();
}

缓存套餐

导入 Spring Cache 和 Redis 相关pom.xml坐标

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

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

管理端接口 SetmealController save delete update startOrStop 加上CacheEvict注解

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


@GetMapping("/list")
@ApiOperation("根据分类id查询套餐")
@Cacheable(cacheNames = "setmealCache", key = "#categoryId") // key: setmealCache::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);
}



@RestController
@RequestMapping("/admin/setmeal")
@Api(tags = "套餐管理相关接口")
@Slf4j
public class SetmealController {

    @Autowired
    private SetmealService setmealService;
    @PostMapping
    @ApiOperation("新增套餐")
    @CacheEvict(cacheNames = "setmealCache",key="#setmealDto.categoryId")
    public  Result save(@RequestBody SetmealDTO setmealDto){
        setmealService.saveWithDish(setmealDto);
        return Result.success("新增套餐成功");
    }

    /**
     * 分页查询
     * @param setmealPageQueryDTO
     * @return
     */
    @GetMapping("/page")
    @ApiOperation("套餐分页查询")
    public Result<PageResult> page(SetmealPageQueryDTO setmealPageQueryDTO){
        PageResult pageResult = setmealService.pageQuery(setmealPageQueryDTO);
        return Result.success(pageResult);
    }

    /**
     *  删除套餐
     * @param ids
     * @return
     */
    @DeleteMapping
    @ApiOperation("批量删除套餐")
    @CacheEvict(cacheNames = "setmealCache",allEntries = true)
    public Result delete(@RequestParam List<Long> ids){
        //删除套餐表
        setmealService.deleteBatch(ids);
        return Result.success();
    }

    /**
     *  根据id查询套餐 实现修改回显
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    @ApiOperation("根据id查询套餐")
    public Result<SetmealVO> getById(@PathVariable Long id){
        SetmealVO setmealVO = setmealService.getByIdWithDish(id);
        return Result.success(setmealVO);
    }

    @PutMapping
    @ApiOperation("修改套餐")
    @CacheEvict(cacheNames = "setmealCache",allEntries = true)
    public Result update(@RequestBody  SetmealDTO setmealDTO){
        setmealService.update(setmealDTO);
        return Result.success("修改成功");
    }

    @PostMapping("/status/{status}")
    @ApiOperation("套餐起售停售")
    @CacheEvict(cacheNames = "setmealCache",allEntries = true)
    public Result<String> startOrStop(@PathVariable Integer status,Long id){
        setmealService.startOrStop(status,id);
        return Result.success();
    }
}

添加购物车

增删改查

POST请求 接口设计

添加购物车 /user/shoppingCart/add

数据库设计 shopping_cart (冗余字段 逻辑外键) 设计 冗余字段 提高查询速度

不同用户 购物车区分开

创建ShoppingCartController ShoppingCartMapper.java SshoppingCartMapper.xml

ShoppingCartService ShoppingCartServiceImpl

@RestController
@RequestMapping("/user/shoppingCart")
@Slf4j
@Api(tags = "C端购物车接口")
public class ShoppingCartController {

    @Autowired
    private ShoppingCartService shoppingCartService;

    @PostMapping("/add")
    @ApiOperation("添加购物车")
    public Result add(@RequestBody ShoppingCartDTO shoppingCartDTO){
        log.info("添加购物车:{}",shoppingCartDTO);
        //业务操作一般封装到service中
        shoppingCartService.addShoppingCart(shoppingCartDTO);

        return Result.success("添加购物车成功");
    }
}
package com.sky.mapper;

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

import java.util.List;

/**
 * @author Admin
 * @title: ShoppingCartMapper
 * @projectName minjiang-takeaway
 * @description: ShoppingCartMapper
 * @date 2024/6/23 3:24
 */
@Mapper
public interface ShoppingCartMapper {
    //动态条件 查询购物车数据

    List<ShoppingCart> list(ShoppingCart shoppingCart);

    /**
     * 根据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,dish_favor,number,amount,image,create_time)" +
            " values(#{name},#{userId},#{dishId},#{setmealId},#{dishFlavor},#{number}#{amount},#{image},#{createTime})")
    void insert(ShoppingCart shoppingCart);
}

<?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" resultType="com.sky.entity.ShoppingCart">
        select * from shopping_cart
        <where>
            <if test="userId != null">
                and user_id = #{userId}
            </if>
            <if test="setmealId != null">
                and setmeal_id = #{setmealId}
            </if>
            <if test="dishId != null">
                and dish_id = #{dishId}
            </if>
            <if test="dishFlavor != null">
                and dish_flavor = #{dishFlavor}
            </if>
        </where>
    </select>
</mapper>
public interface ShoppingCartService {
    /**
     *  添加购物车
     * @param shoppingCartDTO
     */
    void addShoppingCart(ShoppingCartDTO shoppingCartDTO);
}


@Service
@Slf4j
public class ShoppingCartServiceImpl implements ShoppingCartService {
    @Autowired
    private ShoppingCartMapper shoppingCartMapper;
    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private SetmealMapper setmealMapper;
    @Override
    public void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {
        /**
         *  添加购物车逻辑
         */

        //判断商品是否已存在与购物车
        //查询是否已存在  套餐id + userId  select * from shopping_cart where setmeal_id = ? and user_id = ?
        // select * from shopping_cart where dish_id = ? and user_id = ? and dish_flavor = ?
        //动态sql
        ShoppingCart shoppingCart = new ShoppingCart();
        //属性拷贝
        BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
        Long userId = BaseContext.getCurrentId();
        //添加userId属性
        shoppingCart.setUserId(userId);
        List<ShoppingCart> list  = shoppingCartMapper.list(shoppingCart);
        //如果存在,则更新数量

        if(list != null && list.size() > 0){
            ShoppingCart cart = list.get(0);
            cart.setNumber(cart.getNumber()+1); // update shopping_cart set number = ? where id = ?

            shoppingCartMapper.updateNumberById(cart);
        }
        else{
            //如果不存在,则添加到购物车
            //前端上送id
            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);
        }

    }
}

查看购物车

Get /user/shoppingCart/list

/**
     * 查询购物车数据
     */
@GetMapping("/list")
@ApiOperation("查询购物车数据")
public Result<List<ShoppingCart>> list(){
    List<ShoppingCart> list = shoppingCartService.showShoppingCart();

    return Result.success(list);
}



/**
     * 查看购物车
     * @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;
}

清空购物车

user/shoppingCart/clean

删除购物车某一项商品

/**
     * 删除购物车商品
     */

@PostMapping("/sub")
@ApiOperation("删除购物车商品")
public Result deleteById(@RequestBody  ShoppingCartDTO shoppingCartDTO){
    shoppingCartService.deleteShoppingCart(shoppingCartDTO);
    return Result.success("删除成功");
}

/**
     * 删除购物车商品
     * @param shoppingCartDTO
     */
void deleteShoppingCart(ShoppingCartDTO shoppingCartDTO);


@Override
public void deleteShoppingCart(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{
            shoppingCart.setNumber(shoppingCart.getNumber() -1);
            shoppingCartMapper.updateNumberById(shoppingCart);
        }
    }
}

http://www.kler.cn/a/512055.html

相关文章:

  • 2024年度总结:从后端Java到全栈成长的蜕变
  • 通信协议—WebSocket
  • 设计和优化用于 AR、HUD 和高级显示系统的表面浮雕光栅
  • Android SystemUI——CarSystemBar视图解析(十一)
  • 利用 LNMP 实现 WordPress 站点搭建
  • 二进制/源码编译安装mysql 8.0
  • [实战]Ubuntu使用工具和命令无法ssh,但使用另一台Ubuntu机器可以用命令ssh,非root用户。
  • 『 实战项目 』Cloud Backup System - 云备份
  • Kotlin 2.1.0 入门教程(五)
  • 【useImperativeHandle Hook】通过子组件暴露相应的属性和方法,实现在父组件中访问子组件的属性和方法
  • React 中hooks之useDeferredValue用法总结
  • 深度学习 | 基于 LSTM 模型的多电池健康状态对比及预测
  • 【柱状图】——18
  • 【玩转全栈】----Django制作部门管理页面
  • 基于SpringBoot的智能家居系统的设计与实现(源码+SQL脚本+LW+部署讲解等)
  • XAMPP运行没有创建桌面图标
  • ChatGPT被曝存在爬虫漏洞,OpenAI未公开承认
  • 2025年美国大学生数学建模竞赛赛前准备计划
  • 【技术杂谈】Arcgis调用天地图和卫星影像
  • Spring Web MVC 探秘
  • Nginx location 和 proxy_pass 配置详解
  • 后端开发流程学习笔记
  • Linux 高级路由与流量控制-用 tc qdisc 管理 Linux 网络带宽
  • 【通信协议】TCP通信
  • 记录一个简单小需求,大屏组件的收缩与打开,无脑写法
  • 【日志篇】(7.6) ❀ 01. 在macOS下刷新FortiAnalyzer固件 ❀ FortiAnalyzer 日志分析