苍穹外卖开发记录以及完善Day6-Day10

Day06-05~12微信小程序

HttpClient

HttpClient 是Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。

HttpClient的maven坐标:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.13</version>
</dependency>

HttpClient的核心API:

  • HttpClient:Http客户端对象类型,使用该类型对象可发起Http请求。

  • HttpClients:可认为是构建器,可创建HttpClient对象。

  • CloseableHttpClient:实现类,实现了HttpClient接口。

  • HttpGet:Get方式请求类型。

  • HttpPost:Post方式请求类型。

HttpClient发送请求步骤:

  • 创建HttpClient对象

  • 创建Http请求对象

  • 调用HttpClient的execute方法发送请求

入门样例测试

复制黑马的代码还报错,应该是太老了,还得自己敲

这个需要把基础库版本改到2.27.1以下. 详情->本地设置->调试基础库

第二次成功,返回数据为:

一个授权码(js_code)只能使用一次

主要就是得到openid

{"session_key":"jV+yNYfBjUvoGHFunQzJgA==","openid":"oK0ST89FECmTNZqeWIKCZ_AkiPl4"}

第二次发送返回

{"errcode":40163,"errmsg":"code been used, rid: 66dc273c-1da05619-46a3d447"}

Day06-14微信登录

个人开发小程序不能获取用户手机号

微信登录业务层设计与分析

  1. 调用微信的接口服务,获得当前用户的openid

  2. 判断openid是否为空

    1. openid为空,抛出异常

    2. 不为空,判断当前用户是否为新用户

      1. 是新用户,自动完成注册,封装user对象存入数据库并返回

      2. 不是新用户,查询数据库对应用户并返回

登录测试成功,返回了数据

{"code":1,"msg":null,"data":{"id":1,"openid":"oK0ST67FECmTNZqeWIKCZ_AomUg4","token":"eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjU3ODgyMzIsInVzZXJJZCI6MX0.QQ8Du4y2IDSPNgocFqMl5imQfWHZDGD4-mm4Z8-eZcs"}}

Day06-18登录令牌校验

1.创建自定义拦截器,即JwtTokenUserInterceptor,获取令牌,检验令牌

继承自HandlerInterceptor

HandlerInterceptor

HandlerInterceptor 是 Spring MVC 框架提供的拦截器接口,用于拦截请求的处理过程。其作用是在请求处理的不同阶段插入自定义的逻辑处理,比如在请求到达 Controller 之前、Controller 处理之后、视图渲染之前等时机执行一些操作。

一般来说,HandlerInterceptor 接口定义了三个方法,分别是 preHandlepostHandleafterCompletion。这些方法的作用如下:

  1. preHandle:在请求处理之前调用,可以进行一些前置处理操作,返回值表示是否继续执行后续的处理。

  2. postHandle:在请求处理之后、视图渲染之前调用,可以对请求的处理结果进行进一步的处理。

  3. afterCompletion:在整个请求处理完成后调用,通常用于资源清理等工作。

使用 HandlerInterceptor 的步骤通常包括:

  1. 创建一个类实现 HandlerInterceptor 接口,实现其中的方法。

  2. 配置拦截器,可以通过配置类或者 XML 文件进行配置。

  3. 在配置中指定拦截器的拦截路径,即哪些请求需要被该拦截器处理。

 

package com.sky.interceptor;

import com.sky.constant.JwtClaimsConstant;
import com.sky.context.BaseContext;
import com.sky.properties.JwtProperties;
import com.sky.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * jwt令牌校验的拦截器
 */
@Component
@Slf4j
public class JwtTokenUserInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 校验jwt
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }

        //1、从请求头中获取令牌(用户)
        String token = request.getHeader(jwtProperties.getUserTokenName());

        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);
            Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
            log.info("当前用户id:{}", userId);
//          存储用户id进入ThreadLocal
            BaseContext.setCurrentId(userId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }
    }
}

Day06-19C端-接口导入

导入并处理bug

实现接口

  1. C端-套餐浏览接口

  2. C端-菜品浏览接口

  3. C端-分类接口

注意根据套餐id查询包含的菜品列表需要封装DishItemVO

需要从两张表获取数据

1.setmeal_dish 封装name和copies

2.dish image和description

Day07-03缓存菜品代码开发

添加缓存

缓存在Controller层开发

先使用RedisTemplate手动缓存

序列化使用string

在user的DishController里添加缓存

package com.sky.controller.user;

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.cache.annotation.Cacheable;
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;
import java.util.stream.Collectors;

@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) {
//      TODO 把手动添加的Redis缓存改为使用Spring Cache

//        构造redis中的key,规则:dish_分类id
        String key = "dish_" + categoryId;

//        查询缓存中是否存在该分类菜品数据
        List<DishVO> list = redisTemplate.opsForList().range(key, 0, -1);
//        如果存在,直接返回
        if (list != null && list.size() > 0){
            return Result.success(list);
        }
//        不存在,则查询数据库,并将数据存入redis
/*        只返回起售的菜品,并把起售的菜品存入缓存 */
        list = dishService.listWithFlavor(categoryId);
        // 过滤出起售菜品
        List<DishVO> availableDishes = list.stream().filter(dishVO -> dishVO.getStatus() == 1)
                .collect(Collectors.toList());

        if (!availableDishes.isEmpty()){
            redisTemplate.opsForList().rightPushAll(key, availableDishes);
        }
        return Result.success(list);
    }
}

清理缓存

😕思考:

  • 是否可以在修改方法增加限制,不能修改起售中的菜品,也就是说必须停售才能修

  • 新增分类/套餐/菜品的话,默认是未起售状态,对用户端也没有影响,可以不用清理缓存

  • 这样新增和修改就不用清除缓存了,只需要在修改状态时清除缓存就可以了

新增菜品、套餐、本来默认就是停售,把分类也停用就可以了

 

    public Integer save(CategoryDTO categoryDTO) {

//        处理不完整的Dto为对象
        Category category = new Category();
        BeanUtils.copyProperties(categoryDTO, category);
//        默认停用
        category.setStatus(0);

        return categoryMapper.save(category);
    }

代码实现很简单,在Service层的修改方法里加个if()判断就可以了,套餐,分类同理

    /**
     * 修改菜品及其口味
     *
     * @param dishDTO
     * @return
     */
    @Override
    @Transactional
    public Integer updateWithFlavors(DishDTO dishDTO) {
//        判断菜品是否已经停售否则不能修改
        if (dishDTO.getStatus() == StatusConstant.ENABLE) {
            throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
        }
//      1.封装dish
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO, dish);
        Integer update = dishMapper.update(dish);
//      2.获取dishFlavors
        List<DishFlavor> flavors = dishDTO.getFlavors();
        Long dishId = dish.getId();
//        TODO 修改数据库,让菜品和口味形成多对多关系,才能修改

//          删除原来所有口味,然后插入新口味
        dishFlavorMapper.deleteByDishId(dishId);
        flavors.stream().forEach(flavor -> {
//            取出每个flavor并修改
            flavor.setDishId(dishId);
            dishFlavorMapper.insertByDishId(flavor);
        });
        return update;
    }

Day07-11Spring Cache

注解开发缓存,也就是把我之前写的那些代码动态封装到注解里面去了

也就是说现在只需要在user/SetmealController的list方法开启缓存

在admin/SetmealController的changeStatus方法清除缓存就可以了

    @GetMapping("/list")
    @ApiOperation("根据套餐分类id查询套餐")
//    框架会动态计算出缓存key,value是这个方法的返回结果
//    根据categoryId来缓存对应套餐分类下的套餐数据
    @Cacheable(cacheNames = "setmealCache", 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);
    }

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

Day07-13添加购物车

🚨小bug:套餐内的菜品没有口味数据

数据库触发器

冗余字段应该是不会经常更改的数据

在规范开发中,数据库通常避免冗余字段,以减少数据一致性问题。然而,在某些情况下,为了优化性能,可能会使用冗余字段。如果主表字段修改了,你需要同时更新相关的冗余字段。常见的方法包括:

  • 触发器:在数据库中设置触发器,当主表的字段更新时,触发器会自动更新冗余字段。

  • 应用逻辑:在应用层中处理更新逻辑,确保每次主表字段更改时,相关的冗余字段也会被同步更新。

选择合适的方法取决于具体的需求和数据库设计。

根据接口文档开发

  • Controller直接复制

  • service接口直接复制

  • serviceImpl自己写

  • mapper直接复制

  • mapper.xml自己写

吐槽一句

购物车真的粗,只能一个一个添加\减少菜品或者套餐

Day08-02地址簿功能导入

只能有一个地址是默认地址

create table address_book
(
    id            bigint auto_increment comment '主键'  primary key,
    user_id       bigint                                  not null comment '用户id',
    consignee     varchar(50)                             null comment '收货人',
    sex           varchar(2)                              null comment '性别',
    phone         varchar(11)                             not null comment '手机号',
    province_code varchar(12) collate utf8mb4_general_ci  null comment '省级区划编号',
    province_name varchar(32) collate utf8mb4_general_ci  null comment '省级名称',
    city_code     varchar(12) collate utf8mb4_general_ci  null comment '市级区划编号',
    city_name     varchar(32) collate utf8mb4_general_ci  null comment '市级名称',
    district_code varchar(12) collate utf8mb4_general_ci  null comment '区级区划编号',
    district_name varchar(32) collate utf8mb4_general_ci  null comment '区级名称',
    detail        varchar(200) collate utf8mb4_general_ci null comment '详细地址',
    label         varchar(100) collate utf8mb4_general_ci null comment '标签',
    is_default    tinyint(1) default 0                    not null comment '默认 0 否 1是'
)

功能分析与设计

本质都是单表操作

  1. 增删改查

  2. 设置默认地址

  3. 查询默认地址(订单页面)

Day08-04地址簿代码开发

没懂为什么要把其他地址设置为0,不能精确查找吗

    @Override
    @Transactional
    public void setDefault(AddressBook addressBook) {
        //1、将当前用户的所有地址修改为非默认地址 update address_book set is_default = ? where user_id = ?
        addressBook.setIsDefault(0);
        addressBook.setUserId(BaseContext.getCurrentId());
        addressBookMapper.updateIsDefaultByUserId(addressBook);
​
        //2、将当前地址改为默认地址 update address_book set is_default = ? where id = ?
        addressBook.setIsDefault(1);
        addressBookMapper.update(addressBook);
    }

Day08-05订单功能

📜tips:

这个不难,难得是优惠卷和秒杀价还有满减的逻辑

提交订单接口开发分析

数据库层处理

  1. 根据Id查询地址簿表

  2. 根据userId查询购物车列表

  3. 订单表插入一条数据,并且要返回id

  4. 订单详情表根据订单id插入n条数据

  5. 根据userId删除购物车表一条数据

业务逻辑层处理

  1. 处理业务异常

    1. 地址簿是否为空

    2. 购物车为空

  2. 对Order表插入1条数据

    1. 通过Dto构造Order,拷贝后设置其余属性

  3. 对Order_Detail表插入n条数据

    1. 购物车和订单详细的表结构很类似,可以直接拷贝

  4. 清空用户购物车数据

  5. 返回VO返回结果

请求数据分析

{
​
  "addressBookId": 0,  
  "amount": 0,
  "deliveryStatus": 0,
  "estimatedDeliveryTime": "yyyy-MM-dd HH:mm:ss",
  "packAmount": 0,
  "payMethod": 0,
  "remark": "string",
  "tablewareNumber": 0,
  "tablewareStatus": 0
}

很明显是OrderSubmitDTO

响应数据分析

{
  "code": 0,
  "data": {
•    "id": 0,
•    "orderAmount": 0,
•    "orderNumber": "string",
•    "orderTime": "2019-08-24T14:15:22Z"
  },
  "msg": "string"
}

很明显是OrderSubmitVO

后端重新校验金额

金额为什么不需要重新验证呢

我觉得金额需要后端重新验证

金额是前端传过来,非常的不安全

但是还有打包费和配送费,配送费在订单数据库内没有

Day08-12微信支付

什么内网穿透,什么配置啥的全搞好了告诉我个人不能支付

😠凭什么个人用户不能用小程序支付

这里只能直接跳过支付

需要创建一个全局变量

private Orders orders;

然后在用户下单保存之前赋值

this.orders = orders;

然后在订单支付里改变订单的状态

  /**
     * 订单支付
     *
     * @param ordersPaymentDTO
     * @return
     */
    public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {
        // 当前登录用户id
        Long userId = BaseContext.getCurrentId();
        User user = userMapper.getById(userId);

        //调用微信支付接口,生成预支付交易单
/*        JSONObject jsonObject = weChatPayUtil.pay(
                ordersPaymentDTO.getOrderNumber(), //商户订单号
                new BigDecimal("0.01"), //支付金额,单位 元
                "苍穹外卖订单", //商品描述
                user.getOpenid() //微信用户的openid
        );

        if (jsonObject.getString("code") != null && jsonObject.getString("code").equals("ORDERPAID")) {
            throw new OrderBusinessException("该订单已支付");
        }

        OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
        vo.setPackageStr(jsonObject.getString("package"));*/

//      睡眠3秒,模拟支付过程
        Thread.sleep(3000);


        //模拟支付,构造一个模拟的支付成功订单号
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", "ORDERPAID");
        //对商家服务端来说支付成功的本质就是修改订单状态
        OrderPaymentVO vo = jsonObject.toJavaObject(OrderPaymentVO.class);
        vo.setPackageStr(jsonObject.getString("package"));
        Integer OrderPaidStatus = Orders.PAID;//支付状态,已支付
        Integer OrderStatus = Orders.TO_BE_CONFIRMED;  //订单状态,待接单
        LocalDateTime check_out_time = LocalDateTime.now();//更新支付时间
        orderMapper.updateStatus(OrderStatus, OrderPaidStatus, check_out_time, this.orders.getId());

//      模拟支付,提示订单支付成功
        Map map = new HashMap();
        map.put("type",1);
        map.put("orderId",this.orders.getId());
        map.put("content","订单号:"+this.orders.getNumber());
        String jsonString = JSON.toJSONString(map);
        webSocketServer.sendToAllClient(jsonString);
        return vo;
    }

在OrderMapper中写入如下代码:

@Update("update orders set status = #{orderStatus},pay_status = #{orderPaidStatus} ,checkout_time = #{check_out_time} where id = #{id}")
void updateStatus(Integer orderStatus, Integer orderPaidStatus, LocalDateTime check_out_time, Long id);

bug1

前端传来的DTO配送状态始终为0,且没有预计送达时间

bug2

餐具选择状态均为0选择具体数量,即没有1按餐量提供

Day09用户端订单接口

1.查询历史订单

请求地址/user/order/historyOrders

需求分析与设计

业务规则

  • 分页查询历史订单

  • 可以根据订单状态查询

  • 展示订单数据时,需要展示的数据包括:下单时间、订单状态、订单金额、订单明细(商品名称、图片)

请求参数分析

本来以为传的是OrdersPageQueryDTO

结果传的三个string 心态💥了

响应结果分析

多表分页查询,并且返回数据肯定要封装为OrderVO对象,封装为PageResult<OrderVO>

这里太不合理了,应该让前端返回dto的,还让我自己在后端返回dto

  /**
     * 订单分页查询
     * @param page
     * @param pageSize
     * @param status
     * @return
     */
    @Override
    public PageResult orderPageQuery(int page, int pageSize, Integer status) {
//        1.获取页数和页面大小
        PageHelper.startPage(page, pageSize);
//        2.获取userId
        Long userId = BaseContext.getCurrentId();
//        3.封装Dto为分页查询准备参数
        OrdersPageQueryDTO ordersPageQueryDTO = new OrdersPageQueryDTO();
        ordersPageQueryDTO.setUserId(userId);
        ordersPageQueryDTO.setStatus(status);
        // 分页条件查询
        Page<Orders> pageOrder = orderMapper.pageQuery(ordersPageQueryDTO);
//      创建一个空的VO列表
        List<OrderVO> list = new ArrayList();
//        4.为每一个订单查询订单详情并封装到OrderVO中,使用foreach实现
        if (pageOrder != null && pageOrder.getTotal() > 0) {
            if (pageOrder == null || pageOrder.getTotal()>0){
                pageOrder.stream().forEach(orders ->{
                    Long id = orders.getId();
                    List<OrderDetail> orderDetailList = orderDetailMapper.getByOrderId(id);
                    OrderVO orderVO = new OrderVO();
                    BeanUtils.copyProperties(orders,orderVO);
                    orderVO.setOrderDetailList(orderDetailList);
                    list.add(orderVO);
                } );
            }
        }
        return new PageResult(pageOrder.getTotal(), list);
    }

2.查询订单详情

/user/order/orderDetail/{id}

多表查询,把查询结果封装为VO对象返回

3.取消订单

业务规则:

  • 待支付和待接单状态下,用户可直接取消订单

  • 商家已接单状态下,用户取消订单需电话沟通商家(不关我事)

  • 派送中状态下,用户取消订单需电话沟通商家(不关我事)

  • 如果在待接单状态下取消订单,需要给用户退款(不关我事)

  • 取消订单后需要将订单状态修改为“已取消”

实际上只有两个业务

4.再来一单

本质是就是把传过来的订单数据查出来,然后新增对应购物车

Day09商家端订单管理模块

订单搜索

本质:订单VO的分页查询

多表查询:订单表,订单详情表

请求参数分析:OrderQueryPageDto

响应结果分析:Result<PageResult>,PageResult内部应该是List<OrderVO>,并且后端需要VO属性 Orderdishes(订单菜品信息)

订单状态数量统计

疑惑:为什么只统计待接单、待派送、派送中的订单数量,已完成的呢?

数据库本质:多次单表查询

业务规则:直接查

请求参数分析:无参数,自己写状态12345

响应结果分析:OrderStatisticsVO,

  • @Data
    @Builder
    public class OrderStatisticsVO implements Serializable {
        //待接单数量
        private Integer toBeConfirmed;
    ​
        //待派送数量
        private Integer confirmed;
    ​
        //派送中数量
        private Integer deliveryInProgress;
    }

优化思路:封装为一次批量查询所有状态数量,直接得到list,再到业务层处理成前端要的

测试:成功显示

查询订单详情

数据库本质:

  • 根据id查询OrderDetail

  • 多次单表查询

业务规则:

  • 订单详情页面需要展示订单基本信息(状态、订单号、下单时间、收货人、电话、收货地址、金额等)

  • 订单详情页面需要展示订单明细数据(商品名称、数量、单价)

请求参数分析:Order的id

响应结果分析:OrderVO

📜tips:写用户端的订单功能时已经写好,直接复用

测试:Controller参数忘记加@PathVariable收不到id,路径参数一定要加,加了就好了

接受订单

数据库本质:

  • 修改订单状态

  • 单表操作

业务规则:

  • 商家接单其实就是将订单的状态修改为“已接单”

请求参数分析:id和status(不是),Apifox显示未json,就是RequestBody,请求头参数,就是Dto啊

吐槽:

  • 我真是吐了,改个单表status还传Dto

  • 或许是为了严谨,真正的修改订单状态可能涉及多张表的改变

响应请求分析:修改操作无返回

📜tips:Mapper的Update写成动态,方便复用

测试:前端传递的参数为空

写到拒单的时候我懂了,不是不传状态,而是在后台写死了就是变成已接单(3),

思考之后我认为后端写死是合理的,因为前端传来的状态是不可靠的,可能被篡改

拒绝订单

数据库本质:修改订单表,单表操作

业务规则:

  • 商家拒单其实就是将订单状态修改为“已取消”

  • 只有订单处于“待接单”状态时可以执行拒单操作

  • 商家拒单时需要指定拒单原因

  • 商家拒单时,如果用户已经完成了支付,需要为用户退款

请求参数分析:application/json 即 dto,需要@RequestBody

响应结果分析:修改操作无返回结果

取消订单

数据库本质:修改订单表,单表操作

业务规则:

  • 取消订单其实就是将订单状态修改为“已取消”

  • 商家取消订单时需要指定取消原因

  • 商家取消订单时,如果用户已经完成了支付,需要为用户退款

请求参数分析:application/json,OrdersCancelDTO

响应结果分析:无

😓吐槽:这不和拒绝一样吗,还用的着专门写个dto吗,隔离隔离,解耦解耦

优化:不允许取消已经完成的订单,并且自定义原因长度不能小于5

并且我觉得用户和商家取消得区分一下

测试:成功

派送订单

数据库本质:修改订单表,单表操作

业务规则:

  • 派送订单其实就是将订单状态修改为“派送中”

  • 只有状态为“待派送”的订单可以执行派送订单操作

请求参数分析:/admin/order/delivery/{id} string 地址栏参数 @PathVariable

响应结果分析:无

测试:成功

完成订单

数据库本质:修改订单表,单表操作

业务规则:

  • 完成订单其实就是将订单状态修改为“已完成”

  • 只有状态为“派送中”的订单可以执行订单完成操作

请求参数分析:/admin/order/complete/{id} String 地址栏参数 @PathVariable

响应结果分析:无

测试:成功

校验收货地址是否超出配送范围

配置

  shop:
    address: your_address
  baidu:
    ak: your_ak        

Service直接读配置文件

@Value("${sky.shop.address}")
private String shopAddress;
​
@Value("${sky.baidu.ak}")
private String ak;

测试:我累个骚刚,一万九千多米

Day10Spring Task

启动类开启注解@EnableScheduling

创建自定义任务类,需要加注解@Scheduled

像美团这种应该还有个骑手端,由骑手来确认派送完成(或者后台自动判断?)

本来想在员工里设置几个骑手,结果员工根本没有分权限,我不知道搞这个员工表干嘛

/**
 * 处理前一天仍然处于“派送中”状态的订单
 * 我觉得真是业务应该不会这么处理
 */
@Scheduled(cron = "0 0 1 * * ?")
public void processDeliveryOrder(){
    // select * from orders where status = 4 and order_time < 当前时间-1小时
    LocalDateTime time = LocalDateTime.now().plusMinutes(-60);
    List<Orders> ordersList = orderMapper.getByStatusAndOrdertimeLT(Orders.DELIVERY_IN_PROGRESS, time);
​
    if(ordersList != null && ordersList.size() > 0){
        ordersList.forEach(order -> {
            order.setStatus(Orders.COMPLETED);
            orderMapper.update(order);
        });
    }
}

tis:这个超时未支付订单应该用消息队列来做

我觉得这些功能消息队列都能做,但是我该没学

WebSocket

WebSocket是基于TCP的一种新的网络协议。它实现了浏览器域服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的链接,并进行双向数据传输。

HTTP是短连接,是单向的,基于请求响应模式;WebSocket是长连接(有点像打电话,双向消息),支持双向通信。HTTP和WebSocket底层都是TCP连接。

应用:视频弹幕,网页聊天(聊天窗口和客服聊天),体育实况更新,股票基金报价实时更新。

入门案例开发步骤

①直接使用websocket.html页面作为WebSocket客户端

②导入WebSocket的maven坐标(已导入)

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ③导入WebSocket服务端组件WebSocketServer,用于和客户端通信

④导入配置类WebSocketConfiguration,注册WebSocket的服务端组件

⑤导入定时任务类WebSocketTask,定时向客户端推送数据

定时处理未支付订单

  /**
     * 处理待付款(状态1)超时订单
     * 待付款订单(状态1)超过15分钟未支付,则自动取消
     * 模拟支付里不存在这种订单,直接去数据库改一下
     */
    @Scheduled(cron = "0 * * * * ?")// 每分钟执行一次
    public void processTimeoutOrder() {
        log.info("处理支付超时订单:{}", new Date());
        LocalDateTime time = LocalDateTime.now().plusMinutes(-15);
//        查询超时待付款订单
        List<Orders> orders = orderMapper.getByStatusAndOrderTime(Orders.PENDING_PAYMENT, time);
//        对超时订单进行状态更新
        if (orders != null && orders.size() > 0) {
            orders.forEach(order -> {
                order.setStatus(Orders.CANCELLED);
                order.setCancelReason("订单超时,自动取消");
                order.setCancelTime(LocalDateTime.now());
                orderMapper.update(order);
            });
        }
//        批量修改,性能更高
//        orderMapper.updateBatch(orders);
​
    }

批量修改失败,xml问题

来单提醒

数据库操作:无

业务规则:

客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报。

约定服务期发送给客户端浏览器的数据格式位JSON,字段包括:type(消息类型,1来单提醒,2客户催单),orderId,content。

具体操作

  • 在支付成功后调用webSocket

  • 封装map信息,转化为json

//通过websocket向客户端浏览器推送消息 type orderId content
Map map = new HashMap();
map.put("type",1);
map.put("orderId",this.orders.getId());
map.put("content","订单号:"+this.orders.getNumber());
String json = JSON.toJSONString(map);
webSocketServer.sendToAllClient(json);

测试成功

客户催单

请求路径:/user/order/reminder/{id}

业务分析:

  • 通过WebSocket实现管理端页面和服务端保持长连接状态

  • 当用户点击催单按钮后,调用WebSocket的相关API实现服务端向客户端推送消息

  • 客户端浏览器解析服务端推送的消息,判断是来单提醒还是客户催单,进行相应的消息提示和语音播报

  • 约定服务端发送给客户端浏览器的数据格式为JSON,字段包括:type,orderld,content

    • type 为消息类型,1为来单提醒 2为客户催单

    • orderld 为订单id

    • content 为消息内容

请求参数分析:路径参数,订单id

响应结尾分析:无

测试:成功

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值