苍穹外卖--开发记录day07

苍穹外卖day07

一:导入商品浏览接口

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

跟着视频在资料包里导入就行了。和之前一样都是crud

二:缓存菜品

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

问题说明,我们将数据存放在数据库,当有大量的用户访问数据库时,数据库的性能会下降,压力提高;我们可以使用缓存,减少数据库访问的压力

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实现思路,用户查询菜品,我们先看缓存中有没有菜品,如果没有就查询数据库,然后再存入缓存

将菜品保存在缓存中,我们在controller中找到查询所有菜品的方法,然后做判断,先通过key来获取缓存的value,如果缓存中存在这个key就获取,然后直接返回,如果不存在就调用方法查询数据库,获得的菜品再存入缓存:

@GetMapping("/list")
@ApiOperation("根据分类id查询菜品")
public Result<List<DishVO>> list(Long categoryId) {
    String key ="dish_"+categoryId;
    ValueOperations valueOperations = redisTemplate.opsForValue();
    if (redisTemplate.hasKey(key)){
        List<DishVO> res = (List<DishVO>) valueOperations.get(key);
        return Result.success(res);
    }
    Dish dish = new Dish();
    dish.setCategoryId(categoryId);
    dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品
    List<DishVO> list = dishService.listWithFlavor(dish);
    valueOperations.set(key,list);
    return Result.success(list);
}

我们统一将redis中存储分类菜品数据的key变成"dish_"+categoryId,然后需要注意的是,我们从redis中获取到时候key必须的字符穿类型的,value可以是任意类型的,我们取得时候就按存入时的类型去强制类型转换就行;

然后我们要进行菜品信息修改的话,小程序还是会从缓存中取数据而我们数据库的信息已经修改了,这个时候就要进行清理缓存的操作了,我们要思考一下,进行添加菜品需要清理吗,需要;因为我们菜品分类下多了一个菜品,修改菜品也需要,而且如果是修改分类的话,那么就会影响到两个缓存数据,为了方便呢,我们直接清理全部缓存即可,然后批量删除菜品也是,可能同时影响多个分类,所以我们也要清理全部缓存;

我们再admin下的菜品管理接口下进行清理即可:

private void clearCache(String pattern){
    Set keys = redisTemplate.keys(pattern);
    redisTemplate.delete(pattern);
}

定义一个方法,代表按照pattern的格式去清理缓存;

修改,删除操作:

clearCache("dish_*");

清楚所有;

添加其实没必要进行清理,因为我们默认添加的菜品是禁售的;

三:springcache

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1:enablecaching:开启缓存注解功能

2:cacheable:放在方法上看缓存中有没有数据,有直接返回,没有将方法的返回值放入缓存

3:cacheput:将方法的返回值放到缓存中;

4:cacheevict:清理缓存

具体使用:

@Cacheable(cacheNames = "userCache",key = "#id" )

1:cacheable:这里放在方法上:通过userCache和key来拼接redis中的key,然后他会直接取redis中取查有没有存在我们拼接的这个key,如果有就直接返回,不调用方法,如果没有就通过反射来调用方法,然后将方法的返回值直接添加到我们拼接的key中;

@CachePut(cacheNames = "userCache" ,key = "#user.id")

2:CachePut是直接将方法的返回值插入到redis中,然后前缀都是一样的,后面的key想要不一样就要动态设置,#后可以调用我们的参数,#result可以调用我们方法的返回值,然后#p可以知道是我们第几个参数,而且这个插入是当方法执行完毕后才会插入的;

@CacheEvict(cacheNames = "userCache",key = "#id")
@CacheEvict(cacheNames = "userCache",allEntries = true)

3:cacheEvict:这个就是清理缓存数据,我们可以一次清理一个和上面的命名方法一样,也可以使用allEntries将userCache下的全部数据清除;

四:缓存套餐

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其实很简单只要再用户端查询套餐的接口上加上cacheable注解,这样,当我们的redis中没有时我们会执行方法,然后将返回值插入到redis,插入的时result对象,当然我们取出的也是result,前端需要的也是result,当我们redis中有key时就直接返回

@Cacheable(cacheNames = "setmeal",key = "#categoryId")

清理缓存:当套餐增加了,对应的分类就要清除缓存,不然查询不到我们增加的套餐,当套餐修改了,如果修改的是套餐种类,那么可能会影响两个套餐种类,所有要清楚所有缓存,如果套餐批量删除,也可能会影响多个分类,所以,也要清楚全部缓存

@CacheEvict(cacheNames = "setmeal",key = "#setmealDTO.categoryId")
@CacheEvict(cacheNames = "setmeal",allEntries = true)

五:添加购物车

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

添加到购物车的service要麻烦一些:

controller:

@PostMapping("/add")
public Result addCart(@RequestBody ShoppingCartDTO shoppingCartDTO){
    log.info("添加购物车{}",shoppingCartDTO);
    shopCartService.addCart(shoppingCartDTO);
    return Result.success();
}

我们接收的是一个dto对象,dto中有套餐id和菜品id,但是只会有一个,要么是套餐,要么是菜品;

service:

@Override
public void addCart(ShoppingCartDTO shoppingCartDTO) {
    ShoppingCart shoppingCart = new ShoppingCart();
    BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
    shoppingCart.setUserId(BaseContext.getCurrentId());
    List<ShoppingCart> shoppingCarts= shopCartMapper.list(shoppingCart);
    //判断购物车是否已经存在菜品或者套餐
    if (shoppingCarts.size()>0&&shoppingCarts!=null){
        shopCartMapper.update(shoppingCart);
        return;
    }
    //将购物车中的信息插入到表中
    if (shoppingCartDTO.getDishId()!=null){//如果是菜品
        DishVO dishVO = dishMapper.selectById(shoppingCartDTO.getDishId());
        shoppingCart.setImage(dishVO.getImage());
        shoppingCart.setAmount(dishVO.getPrice());
        shoppingCart.setName(dishVO.getName());
        shoppingCart.setDishId(shoppingCartDTO.getDishId());
    }else if (shoppingCartDTO.getSetmealId()!=null) {//如果是套餐
        Setmeal setmeal = setmealMapper.selectById(shoppingCartDTO.getSetmealId());
        shoppingCart.setImage(setmeal.getImage());
        shoppingCart.setAmount(setmeal.getPrice());
        shoppingCart.setName(setmeal.getName());
        shoppingCart.setSetmealId(shoppingCartDTO.getSetmealId());
    }
    shoppingCart.setCreateTime(LocalDateTime.now());
    shopCartMapper.addCart(shoppingCart);
}

这边的业务逻辑比较复杂,我们要先看购物车里有没有当前传入的商品,如果有我们就只需要number+1就行了,然后我们先进行查询,看有没有我们当前传入的商品,我们根据dto中的菜品id或者套餐id,前边说过只会有一个所以sql这么写:

<select id="list" resultType="com.sky.entity.ShoppingCart">
    select * from sky_take_out.shopping_cart
    <where>
        <if test="dishId!=null">
            dish_id=#{dishId}
        </if>
        <if test="setmealId!=null">
            setmeal_id=#{setmealId}
        </if>
           <if test="dishFlavor!=null">
                and dish_flavor=#{dishFlavor}
         </if>
        and user_id=#{userId}
    </where>
</select>

这里面的useid是我们通过treadlocal获取的;返回的虽然是一个集合,但是里面要么只有一个元素,要么就是没有元素,因为一个用户的一个菜品只会有一条数据,然后我们判断是否为空,如果不为空说明购物车中有该物品,我们只需要将number加一就行了,所以这边是update语句:

<update id="update">
    update sky_take_out.shopping_cart set
    number= number+1
    where
    <if test="dishId!=null">
        dish_id=#{dishId}
    </if>
    <if test="setmealId!=null">
        setmeal_id=#{setmealId}
    </if>
    and user_id=#{userId}
</update>

可以看到我们通过用户id和商品id唯一的确定了一条数据,然后直接返回就行;

那么如果为空我们就要插入,这里可能是菜品,也可能是套餐,为方便后面调用相关的方法,我们分两种情况进行赋值,要赋值的有:商品名称,商品价格,商品的图片,商品的创建时间;

最后执行插入操作语句:

@Insert("insert into sky_take_out.shopping_cart (name, image, user_id, dish_id, setmeal_id, dish_flavor, amount, create_time) " +
        "VALUES (#{name}, #{image}, #{userId}, #{dishId}, #{setmealId}, #{dishFlavor}, #{amount}, #{createTime})")
void addCart(ShoppingCart shoppingCart);

就完成了!

mapper:见上;

六:查看购物车

controller:

@GetMapping("/list")
public Result select(){
  List<ShoppingCart>shoppingCarts= shopCartService.select();
  return Result.success(shoppingCarts);
}

service:

public List<ShoppingCart> select() {
    ShoppingCart shoppingCart = new ShoppingCart();
    shoppingCart.setUserId(BaseContext.getCurrentId());
    List<ShoppingCart> list = shopCartMapper.list(shoppingCart);
    return list;
}

直接调用我们之前已经定义的list方法就行;

mapper:

<select id="list" resultType="com.sky.entity.ShoppingCart">
    select * from sky_take_out.shopping_cart
    <where>
        <if test="dishId!=null">
            dish_id=#{dishId}
        </if>
        <if test="setmealId!=null">
            setmeal_id=#{setmealId}
        </if>
        <if test="dishFlavor!=null">
            and dish_flavor=#{dishFlavor}
        </if>
        and user_id=#{userId}
    </where>
</select>

七:清空购物车

controller:

@DeleteMapping("/clean")
public Result clean(){
    shopCartService.clean();
    return Result.success();
}

service:

@Override
public void clean() {

    shopCartMapper.clean(BaseContext.getCurrentId());

}

mapper:

@Delete("delete from sky_take_out.shopping_cart where user_id=#{currentId}")
    void clean(Long currentId);

八:删除一个商品

这里其实是商品的数量-1,并且还有一个逻辑就是,当商品的数量为0时就删除这个商品

controller:

@PostMapping ("/sub")
public Result deleteOne(@RequestBody ShoppingCartDTO shoppingCartDTO){
    shopCartService.updateOne(shoppingCartDTO);
    return Result.success();
}

service:

@Override
public void updateOne(ShoppingCartDTO shoppingCartDTO) {
    ShoppingCart shoppingCart = new ShoppingCart();
    BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
    shoppingCart.setUserId(BaseContext.getCurrentId());
    shopCartMapper.updateOne(shoppingCart);
    List<ShoppingCart> list = shopCartMapper.list(shoppingCart);
    ShoppingCart shoppingCart1 = list.get(0);
    if (shoppingCart1.getNumber()==0){
        shopCartMapper.deleteById(shoppingCart1.getId());
    }
}

这里做了判断,更改完数量后,如果数量为0就直接删除;

mapper:

<update id="updateOne">
    update sky_take_out.shopping_cart
    set number=number - 1
    <where>
        <if test="dishId!=null">
            dish_id=#{dishId}
        </if>
        <if test="setmealId!=null">
            setmeal_id=#{setmealId}
        </if>
        <if test="dishFlavor!=null">
            and dish_flavor=#{dishFlavor}
        </if>
        and user_id=#{userId}
    </where>
</update>

更改数量

@Delete("delete from sky_take_out.shopping_cart where id=#{id}")
void deleteById(Long id);

删除数量为0的商品;

总结

今天学习了springcache进行注解缓存开发,然后利用redis缓存菜品和套餐,添加购物车,查看购物车等功能;

<think>好的,我现在需要帮助用户完善他们提供的JavaScript前端代码和后端的Java Spring Boot控制器方法。用户希望结合这两部分,确保前端能够正确调用后端的删除接口。 首先,我需要仔细阅读用户提供的代码。前端有一个名为selDelCart的JavaScript函数,用于删除购物车中的选中项。该函数通过AJAX发送DELETE请求到/carts?ids=.../delete,而后端有一个@DeleteMapping处理的路径是{cid}/deletes,接收cids数组和会话中的用户ID。 用户的问题在于这两部分代码不匹配,需要调整前端或后端的代码,使它们能够协同工作。用户可能在前端调用时遇到了404错误或者参数无法正确接收的问题,需要解决这些不一致性。 首先,分析后端的控制器方法。@DeleteMapping("{cid}/deletes")中的{cid}是一个路径变量,但方法参数中的cids是一个数组,通过请求参数传递。这里可能存在路径变量和请求参数混淆的问题。比如,路径中的{cid}是否必要?或者可能是用户误写,应该直接使用请求参数来传递cids数组,而不需要路径中的cid变量。 接下来,前端的AJAX请求URL设置为"/carts?ids=" + selectedIds.join(",") + "/delete",这样的URL结构可能不符合后端的预期。后端的路径是{cid}/deletes,这意味着URL应该是类似/carts/123/deletes,其中123是cid的值,但后端方法实际上使用的是cids数组参数。这可能意味着后端的路径设计存在问题,或者前端构造的URL不正确。 因此,我需要确定如何调整两边的代码,使URL和参数传递方式一致。可能的解决方案有两种:修改后端控制器,去掉路径中的{cid},直接通过请求参数传递cids数组;或者调整前端,将cids作为路径变量传递。不过,从后端的代码来看,cids是作为Integer数组接收的,通常这种情况下,应该使用请求参数而非路径变量,因为路径变量更适合单个值,而数组更适合作为查询参数或请求体传递。 因此,更合理的做法是修改后端的@DeleteMapping路径,去掉{cid}/deletes,改为更合适的路径,比如/carts/deletes,并通过请求参数传递cids数组。同时,前端需要调整AJAX请求的URL,去掉路径中的cid部分,正确传递参数。 此外,前端代码中的URL拼接存在问题:用户将selectedIds以逗号分隔附加到查询字符串中,然后在后面添加"/delete",这会导致URL结构错误。正确的做法应该是将参数作为查询参数附加到正确的路径后面。例如,正确的路径应该是/carts/deletes?cids=1&cids=2,而不是/carts?ids=.../delete。 还需要检查AJAX请求的数据格式。前端代码中设置了contentType为application/json,并发送了一个JSON对象,但后端控制器的方法参数并没有使用@RequestBody注解,而是直接从请求参数中获取cids数组。这意味着后端期望的是通过查询参数传递cids,而不是请求体中的JSON。因此,前端不需要发送JSON数据,而应该将cids作为查询参数传递。 另一个需要注意的地方是会话认证。后端方法从HttpSession中获取用户ID,因此前端需要在请求中携带有效的会话cookie,确保用户已登录。这在前端代码中没有处理,但可能在整体应用中有登录机制支持,但在测试时需要确保会话有效。 总结需要进行的调整步骤: 1. 修改后端的@DeleteMapping路径,去掉{cid},改为更合适的路径,例如/carts/deletes。 2. 调整前端AJAX请求的URL,正确构造查询参数,将cids作为参数传递。 3. 确保前端发送的请求方法为DELETE,并移除不必要的请求体数据,因为参数通过查询字符串传递。 4. 检查会话处理,确保用户已登录,会话ID正确传递。 现在需要详细检查代码并实施这些修改。同时,可能还需要处理参数名称的一致性问题,前端使用ids,而后端使用cids,需要统一参数名称。 在用户提供的代码中,前端将参数命名为ids,而后端使用的是cids,这会导致参数无法正确接收。因此需要将前端参数名称改为cids,与后端保持一致。 此外,前端URL中的路径部分可能不正确,应该与后端的映射路径一致。例如,如果后端是@DeleteMapping("deletes"),那么前端的URL应该是/carts/deletes?cids=...。 最后,确保AJAX请求正确配置,不使用请求体发送数据,而是通过查询参数传递cids数组。</think>以下是针对前后端代码的完善方案,重点解决**路径不匹配**、**参数传递方式错误**和**RESTful规范**问题: --- ### 一、后端代码调整 ```java // 修改路径为`/carts/deletes`,移除无用的路径变量`{cid}` @DeleteMapping("/deletes") public JsonResult<Void> deleteCids( @RequestParam("cids") Integer[] cids, // 明确指定参数名 HttpSession session) { cartService.deleteCids(cids, getUidFromSession(session)); return new JsonResult<>(OK); } ``` **修改说明**: 1. 移除路径中的`{cid}`占位符,简化URL结构 2. 添加`@RequestParam("cids")`明确绑定请求参数 3. 路径调整为`/carts/deletes`更符合RESTful规范 --- ### 二、前端代码完善 ```javascript function selDelCart() { let selectedIds = []; // 收集选中商品的ID $(".ckitem:checked").each(function() { selectedIds.push($(this).closest("tr").data("productid")); }); if (selectedIds.length > 0) { // 正确构造URL和参数 $.ajax({ url: "/carts/deletes", // 对应后端路径 method: "DELETE", traditional: true, // 允许数组参数序列化 data: { cids: selectedIds // 参数名与后端一致 }, success: function(response) { $(".ckitem:checked").closest("tr").remove(); alert("删除成功!"); showCartList(); }, error: function(xhr) { console.error("删除失败:", xhr.responseText); alert("删除失败,请重试"); } }); } else { alert("请先选择要删除的商品"); } } ``` **修改说明**: 1. 修正URL为`/carts/deletes`,与后端匹配 2. 添加`traditional: true`确保数组参数正确序列化 3. 参数名改为`cids`与后端对应 4. 移除冗余的JSON序列化(改为标准参数传递) --- ### 三、关键问题解析 | 原问题 | 解决方案 | 原理说明 | |-----------------------|----------------------------|--------------------------------------------------------------------------| | URL路径不匹配 | 统一前后端路径为`/carts/deletes` | 原前端路径含`/delete`后缀,后端使用`{cid}/deletes`导致404错误 | | 参数名称不一致 | 统一使用`cids`作为参数名 | 前端使用`ids`,后端接收`cids`,导致参数无法绑定 | | 数组参数传递错误 | 添加`traditional: true`配置 | jQuery默认将数组序列化为`cids[]=1&cids[]=2`,Spring需要`cids=1&cids=2`格式 | | 冗余的JSON请求体 | 改用标准参数传递 | 后端通过`@RequestParam`接收参数,无需JSON格式 | --- ### 四、测试验证步骤 1. **准备测试数据**: - 登录系统确保Session有效 - 在页面勾选多个商品(确保DOM元素有`data-productid`属性) 2. **发送请求**: - 实际请求格式: ``` DELETE /carts/deletes?cids=1001&cids=1002 Cookie: JSESSIONID=... ``` 3. **预期结果**: - 后端正确接收数组参数 - 数据库对应记录被删除 - 前端自动移除已删除的DOM元素 --- ### 五、补充建议 1. **安全增强**: ```java // 后端添加空值检查 if(cids == null || cids.length == 0) { return new JsonResult<>(BAD_REQUEST, "参数不能为空"); } ``` 2. **前端优化**: ```javascript // 添加加载状态提示 beforeSend: function() { $("#loading").show(); }, complete: function() { $("#loading").hide(); } ``` 3. **RESTful优化**: ```java // 更规范的路径设计 @DeleteMapping public JsonResult<Void> deleteCids(...) { // ... } // 对应前端请求:DELETE /carts?cids=1&cids=2 ``` --- 通过以上调整,可确保前后端实现以下特性: - 符合RESTful设计规范 - 参数传递正确可靠 - 错误处理机制完善 - 代码可维护性提升
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值