外卖项目技术亮点总结笔记

最近跟着听了苍穹外卖项目,想要梳理一下基础增删改查以外的一些技术亮点,方便融入简历和面试被询问

自动填充功能

1. 基于消息转化器实现自动更改或创建日期

java 复制代码
    /**
     * 扩展Spring MVC框架的消息转化器
     * @param converters
     */
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        log.info("扩展消息转换器...");
        //创建一个消息转换器对象
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        //需要为消息转换器设置一个对象转换器,对象转换器可以将Java对象序列化为json数据
        converter.setObjectMapper(new JacksonObjectMapper());
        //将自己的消息转化器加入容器中
        converters.add(0,converter);
    }

首先在WebMvcConfiguration里定义一个扩展消息转化器的方法,首先看一下什么是消息转换器
核心功能
请求数据转换

将HTTP请求中的原始数据(如JSON、XML)转换为Controller方法参数所需的Java对象。例如,将前端传来的JSON字符串自动映射为@RequestBody注解修饰的POJO对象。

响应数据转换

将Controller方法返回的Java对象转换为HTTP响应所需的格式(如JSON、XML)。例如,通过@ResponseBody将返回的List序列化为JSON字符串。

我们定义一个消息转化器专门转换日期格式,全局统一处理,不需要在实体类上添加JsonFormat注解

我们需要一个类 定义好转换格式和序列化时的处理过程

java 复制代码
/**
 * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
 * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
 */
public class JacksonObjectMapper extends ObjectMapper {

    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    //public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    public JacksonObjectMapper() {
        super();
        //收到未知属性时不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

        //反序列化时,属性不存在的兼容处理
        this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));

        //注册功能模块 例如,可以添加自定义序列化器和反序列化器
        this.registerModule(simpleModule);
    }
}

这样在前后端数据交互序列化的时候,就会自动处理好日期格式。

2. 通过AOP拦截和自定义注解实现字段填充

我们在增 和 改的时候 有一些公共字段需要填充,比如创建时间和创建人id以及修改时间和修改人id,但是在方法里写冗余代码太多,每添加一个增改方法就要写一遍,写成一个公共方法仍然得调用,不如添加注解省事清晰。

AOP的概念

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,旨在通过分离横切关注点(如日志、事务、安全等)来提高代码的模块化。AOP允许将通用功能从业务逻辑中剥离,通过动态代理或字节码操作等技术在运行时将切面代码织入目标方法中。

AOP的核心组成

切面(Aspect):封装横切关注点的模块,通常包含通知和切点。

通知(Advice):定义在切点处执行的逻辑,分为前置(Before)、后置(After)、环绕(Around)等类型。

切点(Pointcut):通过表达式匹配需要注入切面的方法或类。

连接点(Join Point):程序执行过程中可插入切面的点,如方法调用或异常抛出。

先定义一个注解

java 复制代码
/**
 * 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //数据库操作类型:UPDATE INSERT
    OperationType value();
}

表明该注解是用在方法上的,运行时生效,有一个值,我们看一下这个值

java 复制代码
/**
 * 数据库操作类型
 */
public enum OperationType {

    /**
     * 更新操作
     */
    UPDATE,

    /**
     * 插入操作
     */
    INSERT

}

就是在用这个注解上要用这个枚举类里定义好的值,是插入还是更新

然后定义一个切片方法

java 复制代码
/**
 * 自定义切面,实现公共字段自动填充处理逻辑
 */
@Aspect
@Component
@Slf4j
public class AutoFillAspect {

    /**
     * 切入点
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut(){}

    /**
     * 前置通知,在通知中进行公共字段的赋值
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint){
        log.info("开始进行公共字段自动填充...");

        //获取到当前被拦截的方法上的数据库操作类型
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法签名对象
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
        OperationType operationType = autoFill.value();//获得数据库操作类型

        //获取到当前被拦截的方法的参数--实体对象
        Object[] args = joinPoint.getArgs();
        if(args == null || args.length == 0){
            return;
        }

        Object entity = args[0];

        //准备赋值的数据
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();

        //根据当前不同的操作类型,为对应的属性通过反射来赋值
        if(operationType == OperationType.INSERT){
            //为4个公共字段赋值
            try {
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                //通过反射为对象属性赋值
                setCreateTime.invoke(entity,now);
                setCreateUser.invoke(entity,currentId);
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }else if(operationType == OperationType.UPDATE){
            //为2个公共字段赋值
            try {
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                //通过反射为对象属性赋值
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

首先通过切入点表达式找到切入点,也就是满足 mapper包下的所有类,所有方法,所有参数 以及 有这个AutoFill注解 的方法。

在添加注解的方法执行前执行前置通知方法,首先通过连接点反射获取注解里的值,看是插入还是更新

获取当前的时间和当前用户ID (通过BaseContext.getCurrentId(); 底层是基于ThreadLocal 存储用户id,可以避免多线程竞争冲突)

通过反射给属性赋值,属性名定义了一个常量类

java 复制代码
/**
 * 公共字段自动填充相关常量
 */
public class AutoFillConstant {
    /**
     * 实体类中的方法名称
     */
    public static final String SET_CREATE_TIME = "setCreateTime";
    public static final String SET_UPDATE_TIME = "setUpdateTime";
    public static final String SET_CREATE_USER = "setCreateUser";
    public static final String SET_UPDATE_USER = "setUpdateUser";
}

微信登录功能

wx.login获取授权码, 将授权码返回后端,后端拿appid和秘钥 还有 用户授权码请求微信接口服务,

微信接口服务返回session key和open id,后端用token记录下来返回前端,用户请求都带上这个token

java 复制代码
/**
     * 微信登录
     * @param userLoginDTO
     * @return
     */
    @PostMapping("/login")
    @ApiOperation("微信登录")
    public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO){
        log.info("微信用户登录:{}",userLoginDTO.getCode());

        //微信登录
        User user = userService.wxLogin(userLoginDTO);

        //为微信用户生成jwt令牌
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.USER_ID,user.getId());
        String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(), jwtProperties.getUserTtl(), claims);

        UserLoginVO userLoginVO = UserLoginVO.builder()
                .id(user.getId())
                .openid(user.getOpenid())
                .token(token)
                .build();
        return Result.success(userLoginVO);
    }

核心就是Open ID这个是用户唯一标识,根据这个去看是否新用户,不是就注册

java 复制代码
    /**
     * 微信登录
     * @param userLoginDTO
     * @return
     */
    public User wxLogin(UserLoginDTO userLoginDTO) {
        String openid = getOpenid(userLoginDTO.getCode());

        //判断openid是否为空,如果为空表示登录失败,抛出业务异常
        if(openid == null){
            throw new LoginFailedException(MessageConstant.LOGIN_FAILED);
        }

        //判断当前用户是否为新用户
        User user = userMapper.getByOpenid(openid);

        //如果是新用户,自动完成注册
        if(user == null){
            user = User.builder()
                    .openid(openid)
                    .createTime(LocalDateTime.now())
                    .build();
            userMapper.insert(user);
        }

        //返回这个用户对象
        return user;
    }

微信支付功能

先看一下微信支付的流程图

用户下单就根据用户id 地址电话id 菜品id 订单号等创建一个订单信息,状态设置为未支付。

确认支付后先根据 订单号 金额 微信openid 请求jsapi接口,返回解析后返回小程序端 时间戳,签名随机字符串,预支付id,签名方式 和 支付签名

先看一下VO返回前端数据

java 复制代码
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderPaymentVO implements Serializable {
    private String nonceStr; //随机字符串
    private String paySign; //签名
    private String timeStamp; //时间戳
    private String signType; //签名算法
    private String packageStr; //统一下单接口返回的 prepay_id 参数值
}

在看一下组装过程

java 复制代码
    /**
     * 订单支付
     *
     * @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"));

        return vo;
    }

根据传来的参数调用支付工具类发起支付请求,然后组装VO返回结果

java 复制代码
 /**
     * 小程序支付
     *
     * @param orderNum    商户订单号
     * @param total       金额,单位 元
     * @param description 商品描述
     * @param openid      微信用户的openid
     * @return
     */
    public JSONObject pay(String orderNum, BigDecimal total, String description, String openid) throws Exception {
        //统一下单,生成预支付交易单
        String bodyAsString = jsapi(orderNum, total, description, openid);
        //解析返回结果
        JSONObject jsonObject = JSON.parseObject(bodyAsString);
        System.out.println(jsonObject);

        String prepayId = jsonObject.getString("prepay_id");
        if (prepayId != null) {
            String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
            String nonceStr = RandomStringUtils.randomNumeric(32);
            ArrayList<Object> list = new ArrayList<>();
            list.add(weChatProperties.getAppid());
            list.add(timeStamp);
            list.add(nonceStr);
            list.add("prepay_id=" + prepayId);
            //二次签名,调起支付需要重新签名
            StringBuilder stringBuilder = new StringBuilder();
            for (Object o : list) {
                stringBuilder.append(o).append("\n");
            }
            String signMessage = stringBuilder.toString();
            byte[] message = signMessage.getBytes();

            Signature signature = Signature.getInstance("SHA256withRSA");
            signature.initSign(PemUtil.loadPrivateKey(new FileInputStream(new File(weChatProperties.getPrivateKeyFilePath()))));
            signature.update(message);
            String packageSign = Base64.getEncoder().encodeToString(signature.sign());

            //构造数据给微信小程序,用于调起微信支付
            JSONObject jo = new JSONObject();
            jo.put("timeStamp", timeStamp);
            jo.put("nonceStr", nonceStr);
            jo.put("package", "prepay_id=" + prepayId);
            jo.put("signType", "RSA");
            jo.put("paySign", packageSign);

            return jo;
        }
        return jsonObject;
    }

拿到预支付id,然后给出时间戳,随机字符,加密方式,支付方式等数据返回,这些参数要传给前端,前端拿着加密请求微信后台,微信后台返回的时候用这样的方式解密,防止数据在网络传输中被人截胡。

java 复制代码
   /**
     * jsapi下单
     *
     * @param orderNum    商户订单号
     * @param total       总金额
     * @param description 商品描述
     * @param openid      微信用户的openid
     * @return
     */
    private String jsapi(String orderNum, BigDecimal total, String description, String openid) throws Exception {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("appid", weChatProperties.getAppid());
        jsonObject.put("mchid", weChatProperties.getMchid());
        jsonObject.put("description", description);
        jsonObject.put("out_trade_no", orderNum);
        jsonObject.put("notify_url", weChatProperties.getNotifyUrl());

        JSONObject amount = new JSONObject();
        amount.put("total", total.multiply(new BigDecimal(100)).setScale(2, BigDecimal.ROUND_HALF_UP).intValue());
        amount.put("currency", "CNY");

        jsonObject.put("amount", amount);

        JSONObject payer = new JSONObject();
        payer.put("openid", openid);

        jsonObject.put("payer", payer);

        String body = jsonObject.toJSONString();
        return post(JSAPI, body);
    }

具体的请求预支付方法是以上代码,填好这些参数,appid,商户号,描述,订单号,回调函数的url,货币,金额,用户openid,用户id

java 复制代码
    /**
     * 发送post方式请求
     *
     * @param url
     * @param body
     * @return
     */
    private String post(String url, String body) throws Exception {
        CloseableHttpClient httpClient = getClient();

        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.toString());
        httpPost.addHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString());
        httpPost.addHeader("Wechatpay-Serial", weChatProperties.getMchSerialNo());
        httpPost.setEntity(new StringEntity(body, "UTF-8"));

        CloseableHttpResponse response = httpClient.execute(httpPost);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            return bodyAsString;
        } finally {
            httpClient.close();
            response.close();
        }
    }

构建一个post的httpclient请求,然后发送请求。

小程序端根据这些参数直接请求微信后台完成支付,小程序端显示支付成功,后台等待微信根据支付状态推送,接受到推送的密文后先解密,从解密后的json里取出 订单号(之前预支付时候提交的,自己后端的) 和 微信支付交易号,根据订单号修改订单支付状态。

我们看一下后台解密代码

java 复制代码
    /**
     * 支付成功回调
     *
     * @param request
     */
    @RequestMapping("/paySuccess")
    public void paySuccessNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //读取数据
        String body = readData(request);
        log.info("支付成功回调:{}", body);

        //数据解密
        String plainText = decryptData(body);
        log.info("解密后的文本:{}", plainText);

        JSONObject jsonObject = JSON.parseObject(plainText);
        String outTradeNo = jsonObject.getString("out_trade_no");//商户平台订单号
        String transactionId = jsonObject.getString("transaction_id");//微信支付交易号

        log.info("商户平台订单号:{}", outTradeNo);
        log.info("微信支付交易号:{}", transactionId);

        //业务处理,修改订单状态、来单提醒
        orderService.paySuccess(outTradeNo);

        //给微信响应
        responseToWeixin(response);
    }

    /**
     * 读取数据
     *
     * @param request
     * @return
     * @throws Exception
     */
    private String readData(HttpServletRequest request) throws Exception {
        BufferedReader reader = request.getReader();
        StringBuilder result = new StringBuilder();
        String line = null;
        while ((line = reader.readLine()) != null) {
            if (result.length() > 0) {
                result.append("\n");
            }
            result.append(line);
        }
        return result.toString();
    }

    /**
     * 数据解密
     *
     * @param body
     * @return
     * @throws Exception
     */
    private String decryptData(String body) throws Exception {
        JSONObject resultObject = JSON.parseObject(body);
        JSONObject resource = resultObject.getJSONObject("resource");
        String ciphertext = resource.getString("ciphertext");
        String nonce = resource.getString("nonce");
        String associatedData = resource.getString("associated_data");

        AesUtil aesUtil = new AesUtil(weChatProperties.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        //密文解密
        String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
                nonce.getBytes(StandardCharsets.UTF_8),
                ciphertext);

        return plainText;
    }

    /**
     * 给微信响应
     * @param response
     */
    private void responseToWeixin(HttpServletResponse response) throws Exception{
        response.setStatus(200);
        HashMap<Object, Object> map = new HashMap<>();
        map.put("code", "SUCCESS");
        map.put("message", "SUCCESS");
        response.setHeader("Content-type", ContentType.APPLICATION_JSON.toString());
        response.getOutputStream().write(JSONUtils.toJSONString(map).getBytes(StandardCharsets.UTF_8));
        response.flushBuffer();
    }
}

微信支付的核心需要商获取,必须商家才可以,个人不可以

SpringCache缓存的使用

业务:访问主页菜品套餐查询缓存,提高访问效率,避免大量查询请求冲向数据库,只需要在增 改 删后删除掉缓存,重新查询时在添加回来。

Spring Cache 是 Spring 框架提供的一个 统一缓存抽象层,它屏蔽了底层缓存实现的差异(如 Caffeine、EhCache、Redis 等),让你通过简单注解就能实现方法级的缓存,大幅减少数据库或外部接口的重复调用。
引入方式

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

开启缓存

java 复制代码
@SpringBootApplication
@EnableCaching   // 开启缓存功能
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

常用注解

@Cacheable(value = "usersCache", key = "#id")

作用:首次调用方法时执行并将结果放入缓存;之后相同参数调用直接返回缓存。

id为5的样子
key为 usersCache::5
values为 序列化对象(比如这个id对应的用户序列化对象)

适合放在查询方法上

@CachePut(value = "usersCache", key = "#user.id")

作用:方法每次都会执行,并将结果强制放入缓存(常用于更新数据时同步缓存)。

@CacheEvict(value = "usersCache", key = "#id")

清理缓存

@Caching(

put = { @CachePut(value = "usersCache", key = "#user.id") },

evict = { @CacheEvict(value = "userListCache", allEntries = true) }

)

组合使用

SpringTask和WebSocket推送

业务: 主要是使用websocket推送订单 或 用户催单等消息,SpringTask定时清理未确认的订单 或 发送消息提醒之类的

WebSocket 是一种 基于 TCP 的全双工通信协议,它的主要特点是:

持久连接:不像 HTTP 一次请求一次响应,WebSocket 建立连接后,客户端和服务器都可以主动发送消息。

低延迟:适合实时推送消息(如订单提醒、聊天、股票行情)。

轻量化:握手时用 HTTP,建立后就走 WebSocket 协议,减少了重复的请求开销。

GET ws://localhost:8080/ws/123 HTTP/1.1

不是http开头 而是ws开头

配置一个bean和一个websocket服务类

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * WebSocket配置类,用于注册WebSocket的Bean
 */
@Configuration
public class WebSocketConfiguration {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}
java 复制代码
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * WebSocket服务
 */
@Component
@ServerEndpoint("/ws/{sid}")
public class WebSocketServer {

    //存放会话对象
    private static Map<String, Session> sessionMap = new HashMap();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) {
        System.out.println("客户端:" + sid + "建立连接");
        sessionMap.put(sid, session);
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, @PathParam("sid") String sid) {
        System.out.println("收到来自客户端:" + sid + "的信息:" + message);
    }

    /**
     * 连接关闭调用的方法
     *
     * @param sid
     */
    @OnClose
    public void onClose(@PathParam("sid") String sid) {
        System.out.println("连接断开:" + sid);
        sessionMap.remove(sid);
    }

    /**
     * 群发
     *
     * @param message
     */
    public void sendToAllClient(String message) {
        Collection<Session> sessions = sessionMap.values();
        for (Session session : sessions) {
            try {
                //服务器向客户端发送消息
                session.getBasicRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

spring task主要基于corn表达式

秒 分 时 日 月 星期 [年]

字段 允许值 特殊符号

秒 (Seconds) 0--59 , - * /

分 (Minutes) 0--59 , - * /

时 (Hours) 0--23 , - * /

日 (Day of month) 1--31 , - * ? / L W

月 (Month) 1--12 或 JAN--DEC , - * /

星期 (Day of week) 0--7 (0 或 7 = 周日) 或 SUN--SAT , - * ? / L #

年 (Year, 可选) 1970--2099 , - * /

* 代表所有值(任意)

? 代表不指定(日和星期冲突时用,二者不能同时指定)

, 多个值(如 1,2,3 表示第 1、2、3 秒)

  • 范围(如 10-15 表示 10 到 15 秒)

/ 步长(如 0/5 表示从 0 开始每隔 5)

L 最后(如 L 表示当月最后一天,6L 表示当月最后一个星期五)

W 工作日(如 15W 表示距离 15 号最近的工作日)

指定第几个星期几(如 2#1 表示每月第一个周一)

java 复制代码
 /**
     * 处理超时订单的方法
     */
    @Scheduled(cron = "0 * * * * ? ") //每分钟触发一次
    public void processTimeoutOrder(){
        log.info("定时处理超时订单:{}", LocalDateTime.now());

        LocalDateTime time = LocalDateTime.now().plusMinutes(-15);

        // select * from orders where status = ? and order_time < (当前时间 - 15分钟)
        List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(Orders.PENDING_PAYMENT, time);

        if(ordersList != null && ordersList.size() > 0){
            for (Orders orders : ordersList) {
                orders.setStatus(Orders.CANCELLED);
                orders.setCancelReason("订单超时,自动取消");
                orders.setCancelTime(LocalDateTime.now());
                orderMapper.update(orders);
            }
        }
    }

    /**
     * 处理一直处于派送中状态的订单
     */
    @Scheduled(cron = "0 0 1 * * ?") //每天凌晨1点触发一次
    public void processDeliveryOrder(){
        log.info("定时处理处于派送中的订单:{}",LocalDateTime.now());

        LocalDateTime time = LocalDateTime.now().plusMinutes(-60);

        List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(Orders.DELIVERY_IN_PROGRESS, time);

        if(ordersList != null && ordersList.size() > 0){
            for (Orders orders : ordersList) {
                orders.setStatus(Orders.COMPLETED);
                orderMapper.update(orders);
            }
        }
    }

图表功能的使用

相关推荐
佛系彭哥2 小时前
C语言笔记(2)
c语言·笔记
汐汐咯2 小时前
基于PyTorch实现的MNIST手写数字识别神经网络笔记
pytorch·笔记·神经网络
virtual_k1smet3 小时前
1004BUUCTF-CRYPTO-[HDCTF2019]basic rsa-NOTES
笔记
我命由我123453 小时前
Git 暂存文件警告信息:warning: LF will be replaced by CRLF in XXX.java.
java·linux·笔记·git·后端·学习·java-ee
lingggggaaaa3 小时前
小迪安全v2023学习笔记(九十五讲)—— 云原生篇&Docker安全&权限环境检测&容器逃逸&特权模式&危险挂载
笔记·学习·安全·web安全·网络安全·docker·云原生
聪明的笨猪猪4 小时前
Java 内存模型(JMM)面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
想唱rap5 小时前
Linux指令(1)
linux·运维·服务器·笔记·新浪微博
东方芷兰5 小时前
LLM 笔记 —— 02 大语言模型能力评定
人工智能·笔记·python·神经网络·语言模型·自然语言处理·cnn
71-37 小时前
C语言——循环的嵌套小练习
c语言·笔记·学习·其他