缓存菜品——需求设计与分析
问题说明
用户访问量过大带来的一个直接效果就是响应速度慢,使用体验下降。
实现思路
使用redis缓存菜品数据,减少数据库查询操作。
页面展示上基本就是同一个分类在同一页,所以key-value结构可以使用不同的分类来做key。
缓存菜品——代码开发
在小程序每一次点击不同的分类,后端哪里都会刷刷刷的连接数据库查询返回,对后端压力肥肠的大,因此使用Redis的作用在这里就能体现了。
修改用户端的DishController代码
@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) {
//构造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);
}
}
相对应的在redis里面会有缓存数据
修改管理端的DIshController代码
要保证数据一致性,数据库数据发生改变时要及时修改redis,不然小程序端的数据和数据库数据会不一致。
/**
* 菜品管理
*/
@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();
cleanCache(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);
}
/**
* 菜品批量删除
* 通过@RequestParam注解将字符串转换为数组
* @param ids
* @return
*/
@DeleteMapping
@ApiOperation("菜品批量删除")
public Result delete(@RequestParam List<Long> ids){
log.info("菜品批量删除:{}",ids);
dishService.deleteBatch(ids);
//将所有的菜品缓存数据清理掉,所有以dish_开头的key
cleanCache("dish_*");
return Result.success();
}
/**
* 根据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);
}
/**
* 根据id修改菜品基本信息和对应口味信息
* @param dishDTO
* @return
*/
@PutMapping
@ApiOperation("修改菜品")
public Result update(@RequestBody DishDTO dishDTO){
log.info("修改菜品:{}",dishDTO);
dishService.updateWithFlavor(dishDTO);
//将所有的菜品缓存数据清理掉,所有以dish_开头的key
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);
}
/**
* 菜品起售停售
* @param status
* @param id
* @return
*/
@PostMapping("/status/{status}")
@ApiOperation("菜品起售停售")
public Result<String> startOrStop(@PathVariable Integer status, Long id){
dishService.startOrStop(status,id);
//将所有的菜品缓存数据清理掉,所有以dish_开头的key
cleanCache("dish_*");
return Result.success();
}
/**
* 清理缓存数据
* @param pattern
*/
private void cleanCache(String pattern){
Set keys = redisTemplate.keys(pattern);
redisTemplate.delete(keys);
}
}
缓存菜品——功能测试
不做了,好累啊。
缓存套餐——Spring Cache
在以下这个文章里面。Spring Cache_北岭山脚鼠鼠的博客-CSDN博客
实现思路+代码开发
在启动类上加入注解
@EnableCaching //开启缓存注解的功能
user端
/**
* 条件查询
*
* @param categoryId
* @return
*/
@GetMapping("/list")
@ApiOperation("根据分类id查询套餐")
@Cacheable(cacheNames = "sermealCache",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);
}
admin端
/**
* 套餐管理
*/
@RestController
@RequestMapping("/admin/setmeal")
@Api(tags="套餐相关接口")
@Slf4j
public class SetmealController {
@Autowired
private SetmealService setmealService;
/**
* 新增套餐
* @param setmealDTO
* @return
*/
@PostMapping
@ApiOperation("新增套餐")
@CacheEvict(cacheNames = "setmealCachce",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);
}
/**
* 修改套餐
*
* @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();
}
}
缓存套餐——功能测试
测试无误
虽然但是,明明小程序没有显示东西,但是数据还是进了缓存。
添加购物车——需求分析与设计
产品原型
购物车就是暂时存放所选商品的地方。
没有口味就是直接加入购物车,有口味要先选择口味。
接口设计
数据库设计
冗余字段的存在减少了查询次数。
添加购物车——代码开发(1)
用到的DTO
Controller层中
@RestController
@RequestMapping("/user/shoppingCart")
@Slf4j
@Api(tags = "C端购物车相关接口")
public class ShoppingCartController {
@Autowired
private ShoppingCartService shoppingCartService;
/**
* 添加购物车
* @param shoppingCartDTO
* @return
*/
@PostMapping("/add")
@ApiOperation("添加购物车")
public Result add(@RequestBody ShoppingCartDTO shoppingCartDTO){
log.info("添加购物车:{}",shoppingCartDTO);
shoppingCartService.addShoppingCart(shoppingCartDTO);
return Result.success();
}
}
Service层中
@Service
@Slf4j
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); //属性拷贝
Long userId = BaseContext.getCurrentId(); //拦截器获取到的用户id
shoppingCart.setUserId(userId);
List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);
//如果已经存在了,只需要将数量加一
if(list!=null&&list.size()>0){
//这里list要么没有数据,要么只有一条数据
ShoppingCart cart = list.get(0);
cart.setNumber(cart.getNumber()+1); //update shopping_cart set number=?where id=?
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);
}
}
}
Mapper层中
@Mapper
public interface ShoppingCartMapper {
/**
* 动态条件查询
* @param shoppingCart
* @return
*/
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, setmeal_id, dish_flavor, number, amount,image, create_time)" +
"values(#{name},#{userId},#{dishId},#{setmealId},#{dishFlavor},#{number},#{amount},#{image},#{createTime})")
void insert(ShoppingCart shoppingCart);
}
对应的映射文件
<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>
添加购物车——功能测试
前端点击添加成功有新数据
查看购物车——需求分析和设计+代码开发+功能测试
产品原型
接口设计
Controller中
/**
* 查看购物车
* @return
*/
@GetMapping("/list")
@ApiOperation("查看购物车")
public Result<List<ShoppingCart>> list(){
List<ShoppingCart>list=shoppingCartService.showShoppingCart();
return Result.success(list);
}
Service中
/**
* 查看购物车
* @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;
}
mapper层已经有了
功能测试
清空购物车——需求分析和设计+代码开发+功能测试
产品原型
店家清空直接删除所有数据
接口设计
Controller中
/**
* 清空购物车
* @return
*/
@DeleteMapping("/clean")
@ApiOperation("清空购物车")
public Result clean(){
shoppingCartService.cleanShoppingCart();
return Result.success();
}
Service中
/**
* 清空购物车
*/
@Override
public void cleanShoppingCart() {
//获取当前微信用户的id
Long userId = BaseContext.getCurrentId();
shoppingCartMapper.deleteById(userId);
}
mapper中
/**
* 根据用户id删除购物车数据
* @param userId
*/
@Delete("delete from shopping_cart where user_id = #{userId}")
void deleteById(Long userId);
功能测试不写了,反正没人看
删除购物车——需求分析与设计
产品原型
点击减号可以减少一个或者是直接删除
接口设计
Controller层中
/**
* 删除购物车商品
* @param shoppingCartDTO
* @return
*/
@PostMapping("/sub")
@ApiOperation("/删除购物车商品")
public Result sub(ShoppingCartDTO shoppingCartDTO){
log.info("删除商品信息:{}",shoppingCartDTO);
shoppingCartService.subShoppingCart(shoppingCartDTO);
return Result.success();
}
Service层中
/**
* 删除购物车数据
* @param shoppingCartDTO
*/
@Override
public void subShoppingCart(ShoppingCartDTO shoppingCartDTO) {
ShoppingCart shoppingCart=new ShoppingCart();
BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
//获取用户id,查询当前登录用户的id
shoppingCart.setId(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.deleteById2(shoppingCart.getId());
}else{
//当前商品在购物车中的份数不为1,修改份数即可
shoppingCart.setNumber(shoppingCart.getNumber() - 1);
shoppingCartMapper.updateNumberById(shoppingCart);
}
}
}
Mapper层中
这个删除是根据id删除,上面的是根据userId删除,两者不同的。
/**
* 根据id删除购物车数据
* @param id
*/
@Delete("delete from shopping_cart where id = #{id}")
void deleteById2(Long id);