完整資料下載
通過網盤分享的文件:蒼穹外賣
鏈接:
https://pan.baidu.com/s/1JJaFOodXOF_lNJSUiZ6qtw?pwd=ps2t
提取碼: ps2t
目錄
1、緩存菜品
(1)問題說明
(2)使用redis緩存部分數據
1-2、代碼完善
(1)user包下DishController完善
(2)RedisConfiguration完善
(3)admin包下DishController
(4)測試功能
2、緩存套餐
Spring Cache知識點
2-2、代碼完善
(1)user/SetmealController完善
(2)admin/SetmealController完善
(3)功能測試
1、緩存菜品
(1)問題說明
(2)使用redis緩存部分數據
注意:
1、訪問mysql是磁盤IO操作,訪問redis是訪問內存
2、用戶點單的購物車中的菜品需要存儲,這個儲存不需要給服務器端,只有最后點付錢才會給服務器提交數據所以這里才有Redis來暫存
3、面試題:如何保持radis與數據庫中的數據一致性:設置過期時間 延遲雙刪
4、在套菜中,套餐是一個集合list,我們將list集合進行序列化,轉成Redis的String類型再存進Redis中;
redis的string不是java的string,redis的string算是object了,外部任何數據傳入redis都會被轉換為string
5、注意:小程序的圖片默認訪問的是老師的oss,直接去數據庫把圖片oss鏈接全改成自己的,或者在管理端(前端頁面)重新上傳圖片
6、 從redis中獲取商鋪營業狀態
?需要提前在redis中設置key為shop_status,value為1或0,1表示營業中,0表示打烊中,否則會報錯,錯誤提示為
“Cannot invoke "java.lang.Integer.intValue()" because "status" is null”
7、現在有兩個問題;
(1)如果數據庫數據變動了怎么辦?
?數據庫的數據變動了,做新增修改的時候都需要先更新數據庫,然后刪除緩存,延時雙刪,每次查詢再把數據存入redis。
(2)如果不斷存入新數據,數據不會老化那么總有一天內存會爆滿的,如何處理?
?設置超時時間,到期了數據就會消失,還可以更改redis的淘汰策略
1-2、代碼完善
(1)user包下DishController完善
位置:sky-server/src/main/java/com/sky/controller/user/DishController.java
完整代碼:
package com.sky.controller.user;import com.sky.constant.StatusConstant;
import com.sky.entity.Dish;
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.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;@RestController("userDishController")
@RequestMapping("/user/dish")
@Slf4j
@Api(tags = "C端-菜品瀏覽接口")
public class DishController {@Autowiredprivate DishService dishService;@Autowiredprivate RedisTemplate redisTemplate;/*** 根據分類id查詢菜品* @param categoryId* @return*/@GetMapping("/list")@ApiOperation("根據分類id查詢菜品")public Result<List<DishVO>> list(Long categoryId) {log.info("根據分類id查詢菜品,categoryId={}", categoryId);// 查詢Redis緩存//構造緩存keyString key = "dish_" + categoryId;//從緩存中獲取數據List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);// 判斷是否存在緩存,如果存在,則直接返回緩存數據,無需再次查詢數據庫if (list != null && list.size() > 0) {log.info("查詢Redis緩存,key={},list={}", key, list);return Result.success(list);}// 查詢數據庫Dish dish = new Dish();dish.setCategoryId(categoryId);dish.setStatus(StatusConstant.ENABLE);//查詢起售中的菜品//如果緩存中沒有數據,則查詢數據庫list = dishService.listWithFlavor(dish);log.info("查詢數據庫,categoryId={},list={}", categoryId, list);// 保存到Redis緩存redisTemplate.opsForValue().set(key, list);return Result.success(list);}}
示意圖:
?
啟動項目時遇見的錯誤:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: java.util.ArrayList[0]->com.sky.vo.DishVO["updateTime"])
原因:
?????????這個錯誤是因為 Jackson 默認不支持 Java 8 的日期時間類型(如LocalDateTime)序列化導致的。錯誤信息已經提示了解決方案:需要添加jackson-datatype-jsr310模塊來支持 Java 8 日期時間類型的處理。
?解決方法一:
????????在RedisTemplate,設置了鍵和值的序列化方式。從錯誤來看,這個配置中缺少了對 Java 8 日期時間類型(如LocalDateTime)的支持,需要添加JavaTimeModule來解決序列化問題。代碼在下面的“(2)RedisConfiguration完善”有步驟
?解決方法二:
????????取消對值的序列化,只保留對key的序列化,這樣就可以避免日期時間類型(如LocalDateTime)序列化導致的錯誤
在小程序查看菜品是,Redis就會緩存對應的數據:值沒有序列化
(2)RedisConfiguration完善
位置:sky-server/src/main/java/com/sky/config/RedisConfiguration.java
添加的代碼:
??? // 注冊JavaTimeModule以支持LocalDateTime等Java 8日期類型objectMapper.registerModule(new JavaTimeModule());
文件完整代碼:
package com.sky.config;import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
@Slf4j
public class RedisConfiguration {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {log.info("初始化創建Redis模板對象...");RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper objectMapper = new ObjectMapper();// 注冊JavaTimeModule以支持LocalDateTime等Java 8日期類型objectMapper.registerModule(new JavaTimeModule());objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(objectMapper);// 設置key的序列化方式redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// 設置hash類型的序列化方式redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);redisTemplate.afterPropertiesSet();return redisTemplate;}
}
示意圖:
查看Redis數據庫:鍵和值都已經序列化
(3)admin包下DishController
?????????傳入的菜品id,redis存的key是類別id,如果我們想刪除菜品對應分類的緩存數據 需要我們去查詢數據庫 也需多次訪問數據庫 我們的目的是減去數據庫操作壓力,故我們選擇清理所有緩存數據
位置:sky-server/src/main/java/com/sky/controller/admin/DishController.java
新增代碼:
/*** 清除所有Redis緩存* @return*/private void clearRedisCache(String pattern) {Set keys = redisTemplate.keys(pattern);redisTemplate.delete(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;/*** 后臺菜品管理* @author sky*/
@RestController//聲明當前類是一個控制器
@RequestMapping("/admin/dish")//聲明當前類下所有請求的前綴
@Api(tags = "后臺菜品管理")
@Slf4j//聲明當前類使用log4j日志
public class DishController {@Autowiredprivate DishService dishService;@Autowiredprivate RedisTemplate redisTemplate;/*** 新增菜品* (1)@RequestBody:注解用于將請求體中的json數據綁定到對象中,這里的對象就是DishDTO* 而且前端發送的數據是Json格式,所以需要將請求體中的json數據綁定到DishDTO對象中* (2)使用DishDTO對象來接收前端發送的json數據的原因是:* 1. 方便后續處理,比如校驗數據,保存到數據庫等* 2. 前端發送的json數據可能有多余的字段,這些字段在后續的業務邏輯中可能并不需要,* 所以使用DishDTO對象來接收前端發送的json數據,可以過濾掉多余的字段,提高后續業務邏輯的效率* @param dishDTO* @return*/@PostMapping//聲明當前方法是一個HTTP POST請求@ApiOperation(value = "新增菜品")public Result save(@RequestBody DishDTO dishDTO){log.info("新增菜品:{}", dishDTO);dishService.saveWithFlavors(dishDTO);//刪除Redis緩存clearRedisCache("dish_" + dishDTO.getCategoryId());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);}/*** 菜品批量刪除*傳入的菜品id,redis存的key是類別id,如果我們想刪除菜品對應分類的緩存數據* 需要我們去查詢數據庫 也需多次訪問數據庫* 我們的目的是減去數據庫操作壓力,故我們選擇清理所有緩存數據* @param ids* @return*/@DeleteMapping@ApiOperation("菜品批量刪除")public Result delete(@RequestParam List<Long> ids) {log.info("菜品批量刪除:{}", ids);dishService.deleteBatch(ids);//刪除所有Redis緩存clearRedisCache("dish_*");return Result.success();}/*** 根據ID查詢菜品詳情* @param id* @return*/@GetMapping("/{id}")@ApiOperation("根據ID查詢菜品詳情")public Result<DishVO> getById(@PathVariable Long id){log.info("查詢菜品:{}", id);DishVO dishVO = dishService.getByIdWithFlavors(id);return Result.success(dishVO);}/*** 更新(修改)菜品* @param dishDTO* @return*/@PutMapping@ApiOperation("更新菜品")public Result update(@RequestBody DishDTO dishDTO){log.info("更新菜品:{}", dishDTO);dishService.updateWithFlavors(dishDTO);
//??????? //刪除所有Redis緩存
//??????? Set keys = redisTemplate.keys("dish_*");
//??????? redisTemplate.delete(keys);//刪除所有Redis緩存clearRedisCache("dish_*");return Result.success();}/*** 根據分類id查詢套餐* @param categoryId* @return*/@GetMapping("/list")@ApiOperation("根據分類id查詢套餐")public Result<List<Dish>> list(Long categoryId){log.info("根據分類id查詢套餐:{}", categoryId);List<Dish> list = dishService.list(categoryId);return Result.success(list);}/*** 菜品起售停售* @param status* @param id* @return*/@PostMapping("/status/{status}")@ApiOperation("菜品起售停售")public Result<String> startOrStop(@PathVariable Integer status, Long id){dishService.startOrStop(status,id);//刪除所有Redis緩存clearRedisCache("dish_*");return Result.success();}/*** 清除所有Redis緩存* @return*/private void clearRedisCache(String pattern) {Set keys = redisTemplate.keys(pattern);redisTemplate.delete(keys);}}
示意圖:
(4)測試功能
小程序查詢菜品分類,如“傳統主食”,打開Redis數據庫,查看緩存的數據,key為“dish_12”
打開前端網頁:菜品管理
找到菜品分類為“傳統主食”的“饅頭”,點擊修改,修改價格,保存
回到Redis數據庫,可以發現緩存全沒了,成功!
????????測試“新增菜品”,清理緩存功能,先出小程序,查詢“傳統主食”及任意多個菜品分類,打開Redis數據庫出現“dish_12”,去前端網頁新增菜品,點擊保存,最后查看數據庫“dish_12”是否還存在,沒有則表示成功!
2、緩存套餐
Spring Cache知識點
?????????SpringCache是一個框架,實現了基于注解的緩存功能,只需要簡單地加一個注解,就能實現緩存功能。SpringCache提供了一層抽象,底層可以切換不同的緩存實現,例如:
1、EHCache
2、Caffeine
3、Redis
依賴導入:(源文件已有)
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId><version>2.7.3</version>
</dependency>
?我們的項目使用的是Redis。pom文件導入對應的緩存類型坐標,即可切換不同的緩存實現
如:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>3.0.0</version>
</dependency>
2-2、代碼完善
?源文件的pom文件已導入Redis和spring cache依賴
(1)user/SetmealController完善
位置:sky-server/src/main/java/com/sky/controller/user/SetmealController.java
新增代碼:
/*** 條件查詢** @param categoryId* @return*/
@Cacheable(cacheNames = "setmealList", key = "#categoryId")
@GetMapping("/list")
@ApiOperation("根據分類id查詢套餐")
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);
}
完整代碼:
package com.sky.controller.user;import com.sky.constant.StatusConstant;
import com.sky.entity.Setmeal;
import com.sky.result.Result;
import com.sky.service.SetmealService;
import com.sky.vo.DishItemVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;@RestController("userSetmealController")
@RequestMapping("/user/setmeal")
@Api(tags = "C端-套餐瀏覽接口")
public class SetmealController {@Autowiredprivate SetmealService setmealService;/*** 條件查詢** @param categoryId* @return*/@Cacheable(cacheNames = "setmealList", key = "#categoryId")@GetMapping("/list")@ApiOperation("根據分類id查詢套餐")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);}
}
示意圖:
注意:爆紅是因為導錯包,選“org.springframework.cache.annotation.Cacheable;”
(2)admin/SetmealController完善
位置:sky-server/src/main/java/com/sky/controller/admin/SetmealController.java
新增代碼:
@CacheEvict(value = "setmealCache", key = "#setmealDTO.categoryId")//精確匹配key,清理緩存
??? @CacheEvict(value = "setmealCache", allEntries = true)//清理所有緩存
完整代碼:
package com.sky.controller.admin;import com.sky.dto.SetmealDTO;
import com.sky.dto.SetmealPageQueryDTO;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.SetmealService;
import com.sky.vo.SetmealVO;
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.cache.annotation.CacheEvict;
import org.springframework.web.bind.annotation.*;import java.util.List;/*** 套餐管理*/
@RestController
@RequestMapping("/admin/setmeal")
@Api(tags = "套餐相關接口")
@Slf4j
public class SetmealController {@Autowiredprivate SetmealService setmealService;/*** 新增套餐* @param setmealDTO* @return*/@CacheEvict(value = "setmealCache", key = "#setmealDTO.categoryId")//精確匹配key,清理緩存@PostMapping@ApiOperation("新增套餐")//由于SetmealDTO中有List<SetmealDish> setmealDishes = new ArrayList<>();,所以這里需要使用@RequestBody注解//使用SetmealDTOwenden @RequestBody注解,將json數據綁定到SetmealDTO對象中public Result 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) {PageResult pageResult = setmealService.pageQuery(setmealPageQueryDTO);return Result.success(pageResult);}/*** 批量刪除套餐* @param ids* @return*/@CacheEvict(value = "setmealCache", allEntries = true)@DeleteMapping@ApiOperation("批量刪除套餐")public Result delete(@RequestParam List<Long> ids) {log.info("批量刪除套餐:{}", ids);setmealService.delete(ids);return Result.success();}/*** 根據id獲取套餐* @param id* @return*/@GetMapping("/{id}")@ApiOperation("根據id獲取套餐")public Result<SetmealVO> getById(@PathVariable("id") Long id){log.info("根據id獲取套餐:{}", id);SetmealVO setmealVO = setmealService.getById(id);return Result.success(setmealVO);}/*** 更新套餐1* @param setmealDTO* @return*/@CacheEvict(value = "setmealCache", allEntries = true)@PutMapping@ApiOperation("更新套餐")public Result Update(@RequestBody SetmealDTO setmealDTO) {log.info("更新套餐:{}", setmealDTO);setmealService.update(setmealDTO);return Result.success();}/*** 套餐起售停售* @param status* @param id* @return*/@CacheEvict(value = "setmealCache", allEntries = true)@PostMapping("/status/{status}")@ApiOperation("套餐起售停售")public Result startOrStop(@PathVariable Integer status, Long id) {setmealService.startOrStop(status, id);return Result.success();}}
(3)功能測試
1、全部清理
打開小程序,任意點擊多個菜品分類,查看Redis數據庫,打開前端網頁,修改菜品信息,保存,查看數據庫,若菜品數據全清空,則表示功能完成!
2、精確清除緩存(新增菜品)
與前面一樣的操作,只刪除對應的dish_?,如dish_12,即算完成!
至此,緩存菜品和緩存套餐功能已完成!