黑马苍穹外卖项目笔记07_缓存商品、购物车


一.缓存菜品

1.为什么要提出缓存?

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

2.解决思路

在这里插入图片描述
众所周知,redis是基于内存进行存储和访问的,这导致它能够响应执行的时间消耗很少,这也是redis为什么频繁作为缓存数据库的原因之一

3.实现思路

缓存逻辑分析:
1.每个分类下的菜品保存一份缓存数据
2.数据库中菜品数据有变更时清理缓存数据

故采用redis中的string数据结构来存储数据,key的后缀为分类的id
,value为菜品信息集合,只不过转换成了字符串进行存储
在这里插入图片描述

4.代码开发

既然是对查询分类做缓存,那就应该在对应的controller类中做修改:

@RestController("userDishController")
@RequestMapping("/user/dish")
@Slf4j
@Api(tags = "C端-菜品浏览接口")
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) {
        //拼接字符串,用于设置key
        String key = "dish_" + categoryId;

        //查询redis中是否有缓存数据,有则直接返回,没有则添加菜品集合到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);//查询起售中的菜品

        list = dishService.listWithFlavor(dish);

        redisTemplate.opsForValue().set(key,list);

        return Result.success(list);
    }

}

5.功能测试

前后端联调测试

6.Q&A:为什么在使用redisTemplate存储和获取值的时候,字符串和集合能够自动转换?

redisTemplate.opsForValue().set(key, list)之所以能够自动将list集合转换为字符串是因为 RedisTemplate 在存储数据到 Redis 中时,会根据泛型类型来进行序列化和反序列化操作。默认情况下,RedisTemplate 使用 Java 默认的序列化器,将对象序列化为字节数组,然后存储到 Redis 中。

当调用redisTemplate.opsForValue().set(key, list)时,RedisTemplate 会将 list 对象序列化为字节数组,并将其存储在 Redis 中,通常这个字节数组会以二进制格式存储。Redis本身不知道对象的类型,它只是存储字节数组。当从 Redis 中获取数据时,RedisTemplate 会将存储的字节数组反序列化为对象,根据对象的泛型类型将其转换回相应的 Java 对象。

这行代码List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);之所以能够将从 Redis 中获取的字符串转换为集合的原因是因为 RedisTemplate 在后台执行了反序列化操作。

当你从 Redis 中获取数据时,RedisTemplate 会从 Redis 中检索存储的数据,然后进行反序列化操作,将存储的字节数组转换回相应的 Java 对象类型。在这种情况下,它检测到要获取的对象类型是 List<DishVO>,然后进行反序列化以将存储的字节数组转换为 List<DishVO> 对象。

二.清理缓存菜品

当管理端修改,删除,新增了菜品数据后,导致两个数据库的数据不一致,因为微信小程序只会显示redis缓存的数据,故有了清除缓存菜品
在这里插入图片描述

1.抽取清理缓存的方法

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

2.分别应用到对菜品进行操作的方法上

/**
     * 菜品的起售和停售
     *
     * @param status
     * @param id
     * @return
     */
    @PostMapping("status/{status}")
    @ApiOperation("菜品的起售和停售")
    public Result<String> startOrStop(@PathVariable Integer status, Long id) {
        dishService.startOrStop(status, id);

        //清理缓存
        cleanCache("dish_*");

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

        dishService.updateWithFlavor(dishDTO);

        //清理缓存
        cleanCache("dish_*");

        return Result.success();
    }
/**
     * 菜品批量删除
     *
     * @param ids
     * @return
     */
    @DeleteMapping
    @ApiOperation("菜品批量删除")
    public Result<String> delete(@RequestParam List<Long> ids) {
        log.info("菜品批量删除:{}", ids);

        dishService.deleteBatch(ids);

        //清理缓存
        cleanCache("dish_*");

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

        return Result.success();
    }

3.功能测试

前后端联调测试

三.Spring Cache

1.什么是Spring Cache?

在这里插入图片描述

2.Spring Cache的常用注解

在这里插入图片描述
简而言之,springcache提供的注解简化了我们手动编写缓存代码,取而代之的是一个小小的注解

四.缓存套餐

1.在user.SetmealController类下

@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);
    }

编写注解@Cacheable(cacheNames = “setmealCache”,key = “#categoryId”),其中cacheNmaes属性为键的前缀,key属性为键的后缀,用于动态存储缓存数据的键名称

2.在admin.SetmealController类下

编写清除缓存的注解@CacheEvict,分别用于增删改

@RestController
@RequestMapping("/admin/setmeal")
@Slf4j
@Api(tags = "套餐相关接口")
public class SetmealController {
    @Autowired
    SetmealService setmealService;

    /**
     * 新增套餐
     * @param setmealDTO
     * @return
     */
    @PostMapping
    @ApiOperation("新增套餐")
    @CacheEvict(cacheNames = "setmealCache",key = "#setmealDTO.categoryId")
    public Result<String> save(@RequestBody SetmealDTO setmealDTO){
            log.info("新增套餐:{}",setmealDTO);
            setmealService.saveWithDish(setmealDTO);
            return Result.success();
    }

    /**
     * 分页查询
     * @param setmealPageQueryDTO
     * @return
     */
    @GetMapping("/page")
    @ApiOperation("分页查询")
    public Result<PageResult> page(SetmealPageQueryDTO setmealPageQueryDTO){
            log.info("分页查询数据:{}",setmealPageQueryDTO);

            PageResult pageResult=setmealService.pageQuery(setmealPageQueryDTO);

            return Result.success(pageResult);
    }

    /**
     * 批量删除套餐
     * @return
     */
    @DeleteMapping
    @ApiOperation("批量删除套餐")
    @CacheEvict(cacheNames = "setmealCache",allEntries = true)
    public Result<String> delete(@RequestParam List<Long> ids){
        log.info("批量删除套餐id:{}",ids);

        setmealService.deleteBatch(ids);

        return Result.success();
    }

    /**
     * 根据id查询套餐,用于修改页面回显数据
     * @param id
     * @return
     */
    @GetMapping("/{id}")
    @ApiOperation("根据id查询套餐")
    public Result<SetmealVO> getById(@PathVariable Long id){
        log.info("查询套餐id:{}",id);

        SetmealVO setmealVO=setmealService.getByIdWithDish(id);

        return Result.success(setmealVO);
    }

    /**
     * 修改套餐
     * @param setmealDTO
     * @return
     */
    @PutMapping
    @ApiOperation("修改套餐")
    @CacheEvict(cacheNames = "setmealCache",allEntries = true)
    public Result<String> update(@RequestBody SetmealDTO setmealDTO){
        log.info("修改套餐:{}",setmealDTO);

        setmealService.update(setmealDTO);

        return Result.success();
    }

    /**
     * 套餐起售停售
     * @param status
     * @param id
     * @return
     */
    @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();
    }
}

3.测试

前后端联调测试即可

五.添加购物车

1.需求分析和设计

在这里插入图片描述
在这里插入图片描述

2.代码开发

根据添加购物车接口创建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 lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

    @Autowired
    private ShoppingCartService shoppingCartService;

    /**
     *添加购物车
     * @param shoppingCartDTO
     * @return
     */
    @PostMapping("/add")
    @ApiOperation("添加购物车")
    public Result<String> add(@RequestBody ShoppingCartDTO shoppingCartDTO) {
        log.info("添加购物车:{}", shoppingCartDTO);

        //调用service执行对应的业务代码
        shoppingCartService.addShoppingCart(shoppingCartDTO);

        return Result.success();
    }
}

创建ShoppingCartService接口:

package com.sky.service;

import com.sky.dto.ShoppingCartDTO;

public interface ShoppingCartService {

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

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

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.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;

    /**
     * 添加购物车
     *
     * @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) {
            //有数据,数量加一即可
            shoppingCart = shoppingCartList.get(0);
            shoppingCart.setNumber(shoppingCart.getNumber() + 1);
            shoppingCartMapper.updateNumberById(shoppingCart);
        } 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 {
                //为新增套餐操作

                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);
        }
    }
}

创建ShoppingCartMapper接口:

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;

@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" resultType="ShoppingCart" parameterType="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.功能测试

前后端联调测试即可

五.查看购物车

1.需求分析和设计
在这里插入图片描述
2.代码开发
在ShoppingCartController中创建查看购物车的方法:

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

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

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

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

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

3.功能测试
前后端联调测试即可

六.清空购物车

1.代码开发

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

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

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

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

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

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

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

/**
     * 清空购物车
     *
     * @param userId
     */
    @Delete("delete from shopping_cart where user_id = #{userId}")
    void deleteByUserId(Long userId);

2.功能测试
前后端联调测试即可

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

1.需求分析和设计
接收前端传过来的菜品或者套餐id,根据id来进行删除操作
2.代码开发
ShoppingCartController中:

/**
     * 删除购物车中一个商品
     * @param shoppingCartDTO
     * @return
     */
    @PostMapping("/sub")
    @ApiOperation("删除购物车中一个商品")
    public Result<String> deleteById(@RequestBody ShoppingCartDTO shoppingCartDTO){
        log.info("删除购物车中一个商品,商品:{}", shoppingCartDTO);

        shoppingCartService.deleteById(shoppingCartDTO);

        return Result.success();
    }

ShoppingCartService中:

/**
     * 删除购物车中一个商品
     * @param shoppingCartDTO
     */
    void deleteById(ShoppingCartDTO shoppingCartDTO);

ShoppingCartServiceImpl中:

/**
     * 删除购物车中一个商品
     *
     * @param shoppingCartDTO
     */
    public void deleteById(ShoppingCartDTO shoppingCartDTO) {
        //先查询传过来的id对应的菜品或者套菜对应的数量,如果数量等于1则直接删除菜品或套菜,如果大于1则数量减一
        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);
            if (shoppingCart.getNumber() == 1) {
                shoppingCartMapper.deleteById(shoppingCart.getId());
            } else {
                shoppingCart.setNumber(shoppingCart.getNumber() - 1);
                shoppingCartMapper.updateNumberById(shoppingCart);
            }
        }
    }

ShoppingCartMapper中:

/**
     * 根据id删除购物车中的某个菜品或者套餐数据
     */
    @Delete("delete from shopping_cart where id = #{id}")
    void deleteById(Long id);

3.功能测试
前后端联调测试即可

  • 10
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
黑马程序员苍穹项目中的Nginx配置文件可以根据具体需求进行配置。根据引用\[1\]中的描述,可以通过双击nginx.exe启动Nginx,并在http://localhost/访问前端页面。这意味着Nginx的配置文件应该包含有关前端页面的相关配置。另,根据引用\[2\]中的描述,Nginx还可以用作反向代理和负载均衡,因此配置文件还应包含有关反向代理和负载均衡的相关配置。最后,根据引用\[3\]中的描述,苍穹项目还需要与第三方配送公司进行对接和管理,因此配置文件还应包含有关与第三方配送公司对接的相关配置。综上所述,黑马程序员苍穹项目的Nginx配置文件应包含前端页面的相关配置、反向代理和负载均衡的相关配置以及与第三方配送公司对接的相关配置。 #### 引用[.reference_title] - *1* [黑马程序员_Java项目实战《苍穹》_Day01_开发环境搭建](https://blog.csdn.net/BallerWang9/article/details/131824385)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [最适合新手的SpringBoot+SSM项目苍穹》实战—(一)项目概述](https://blog.csdn.net/qq_20185737/article/details/131575898)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值