07. HTTP接口请求重试怎么处理?

news2024/11/20 4:41:58

 目录

1、前言

2、实现方式

2.1、循环重试

2.2、递归重试

2.3、Spring Retry

2.4、Resilience4j

2.5、http请求网络工具内置重试方式

2.6、自定义重试工具

2.7、并发框架异步重试

2.8、消息队列

3、小结


1、前言

HTTP接口请求重试是指在请求失败时,再次发起请求的机制。在实际应用中,由于网络波动、服务器故障等原因,HTTP接口请求可能会失败。为了保证系统的可用性和稳定性,需要对HTTP接口请求进行重试。

2、实现方式

今天给大家分享一些常见的接口请求重试的方式。本地模拟了一个请求接口,后面的代码示例均模拟请求该接口:

@GetMapping("http_test")
public String getHttpTest(){
    return "接口请求成功,返回:OK";
}

2.1、循环重试

循环重试是最简单最粗暴的方式,就是在请求接口代码中加入循环机制,如果接口请求失败,则循环继续发起接口请求,直到请求成功或接口重试次数达到上限。如果请求成功,则不进行重试。

简单代码示例如下:

@GetMapping("retry_demo_loop")
public String retry_demo_loop(){
    // 重试上限次数为3次
    int maxRetryTime = 3;
    String result = null;
    // 接口循环请求
    for (int i = 1; i <= maxRetryTime; i++) {
        try {
            // 模拟请求接口
            result = HttpUtil.get("http://localhost:8080/http_test");
            // 模拟一次请求失败
            if(i == 1){
                int co = i / 0;
            }
            // 请求成功,跳出循环
            break;
        } catch (Exception e) {
            log.error("接口请求异常,进行第{}次重试", i);
            result = "接口请求失败,请联系管理员";
        }
    }
    return result;
}

请求结果:

重试日志打印:

2.2、递归重试

除了循环,还可以使用递归来实现接口的请求重试。递归是我们都比较熟悉的编程技巧,在请求接口的方法中调用自身,如果请求失败则继续调用,直到请求成功或达到最大重试次数。

@GetMapping("retry_demo_rec")
public String retry_demo_rec(){
    // 重试上限次数为3次
    int maxRetryTime = 3;
    return retryRequest(maxRetryTime);
}

/**
 * 递归方法
 * @param maxRetryTime
 * @return
 */
private String retryRequest(int maxRetryTime){
    if (maxRetryTime <= 0) {
        return "接口请求失败,请联系管理员";
    }

    int retryTime = 0;
    try {
        // 模拟请求接口
        String result = HttpUtil.get("http://localhost:8080/http_test");
        // 模拟一次请求失败
        if(maxRetryTime == 3){
            int co = 1 / 0;
        }
        return result;
    } catch (Exception e) {
        // 处理异常
        log.error("接口请求异常,进行第{}次重试", ++retryTime);
        return retryRequest(maxRetryTime - 1);
    }
}

请求结果:

重试日志打印:

2.3、Spring Retry

第三种便是使用Spring Retry依赖实现。首先我们需要集成相关依赖:

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>
<!-- 由于retry使用到了aop,所以还需要加入aop依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

加入@EnableRetry启动:

@EnableRetry
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

添加retry方法注解:

public interface MyRetryService {

    /**
     * retryable注解表示该方法需要重试
     * value:出现该指定异常后,进行重试
     * maxAttempts:重试次数上限,这里指定为3次
     * backoff:重试策略,这里指定200ms间隔一次
     * @param code
     * @return
     * @throws Exception
     */
    @Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(200))
    String retry(int code) throws Exception;


    /**
     * 当重试达到上限后还是失败,则作为异常回调方法
     * @param th
     * @param code
     * @return
     */
    @Recover
    String recover(Throwable th, int code);

}

MyReretService实现类:

@Slf4j
@Service
public class MyRetryServiceImpl implements MyRetryService {

    @Override
    public String retry(int code) throws Exception {
        log.info("请求retry接口");
        String result = HttpUtil.get("http://localhost:8080/http_test");
        if(code != 200){
            throw new Exception("接口请求异常");
        }

        return result;
    }

    @Override
    public String recover(Throwable th, int code) {
        log.error("回调方法执行!!!!");
        return "异常码为:" + code + ",异常信息:" + th.getMessage();
    }
}

Controller:

@Autowired
private MyRetryService myRetryService;


/**
 * 当请求code参数为200时,直接成功
 * 当code参数!=200时,会出发重试
 * @param code
 * @return
 * @throws Exception
 */
@GetMapping("retry_demo_spring_retry")
public String retry_demo_spring_retry(Integer code) throws Exception {
    return myRetryService.retry(code);
}

访问地址:http://localhost:8080/retry_demo_spring_retry?code=123

查看结果:可以看到接口重试了3次,最后执行了@Recover方法最后的回调。

2.4、Resilience4j

Resilience4j是一个轻量级、易于使用的轻量级“容错”包。它受Neflix Hystrix启发但只有一个依赖(Vavr),而不像Hystrix很多很多的依赖。同时它是一个 Java 库,可以帮助我们构建弹性和容错的应用程序。Resilience4j在“容错”方面提供了各种模式:断路器(Circuit Breaker)、重试(Retry)、限时器(Time Limiter)、限流器(Rate Limiter)、隔板(BulkHead)。我们今天讨论的话题是重试,那么今天就来演示下Retry。

Github地址:GitHub - resilience4j/resilience4j: Resilience4j is a fault tolerance library designed for Java8 and functional programming

首先,添加相应依赖:

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot2</artifactId>
    <version>2.1.0</version>
</dependency>

application.yml配置相关策略,配置官方文档:https://resilience4j.readme.io/docs/retry

resilience4j:
  retry:
    instances:
      retry_demo:
        max-attempts: 3     # 重试的上限次数
        wait-duration: 1s   # 重试的间隔时间,配置为1s

我们改造一下上面spring-retry的demo。

controller:

@GetMapping("retry_demo_spring_retry")
@Retry(name = "retry_demo", fallbackMethod = "recover")
public String retry_demo_spring_retry(Integer code) throws Exception {
    return myRetryService.retry(code);
}

public String recover(Throwable th) {
    log.error("回调方法执行!!!!");
    return "异常信息:" + th.getMessage();
}

myRetryService:

@Override
public String retry(int code) throws Exception {
    log.info("请求retry接口");
    String result = HttpUtil.get("http://localhost:8080/http_test");
    if(code != 200){
        throw new Exception("接口请求异常");
    }
    return result;
}

程序执行,打印结果:

同样接口请求了3次,均失败后执行了fallback回调方法。

2.5、http请求网络工具内置重试方式

通常一些外部的http网络工具,都会内置一些重试的策略。如Apache HttpClient。这里以httpclient5为例。

首先添加依赖:

<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
    <version>5.1.4</version>
</dependency>

定义HttpClient相关类,指定重试策略。可以使用默认的DefaultHttpRequestRetryStrategy,也可以自定义重试策略CustomRetryStrategy。

private static volatile CloseableHttpClient HTTP_CLIENT = null;
    static {
        if(HTTP_CLIENT == null){
            synchronized (HelloWorldController.class) {
                if(HTTP_CLIENT == null){
                    HTTP_CLIENT = HttpClients.custom()
                            // 设置重试策略
//                            .setRetryStrategy(new DefaultHttpRequestRetryStrategy(3, TimeValue.NEG_ONE_SECOND))
                            // 自定义重试策略
                            .setRetryStrategy(new CustomRetryStrategy())
                            .build();
                }
            }
        }
    }

CustomRetryStrategy:

public static class CustomRetryStrategy implements HttpRequestRetryStrategy {

    @Override
    public boolean retryRequest(HttpRequest httpRequest, IOException e, int executeCount, HttpContext httpContext) {
        return false;
    }

    @Override
    public boolean retryRequest(HttpResponse httpResponse, int executeCount, HttpContext httpContext) {
        System.out.println("进入重试策略");
        if(executeCount > 3){
            System.out.println("重试超过3次,终止重试");
            return false;
        }

        if(httpResponse.getCode() != 200){
            System.out.println("http状态码不等于200,进行重试");
            return true;
        }

        // 其他情况,不重试
        return false;
    }

    @Override
    public TimeValue getRetryInterval(HttpResponse httpResponse, int executeCount, HttpContext httpContext) {
        return null;
    }
}

Controller代码:

@GetMapping("retry_demo_httpclient")
public String retry_demo_httpclient(Integer code) throws Exception {
    return httpclientRetry(code);
}

private String httpclientRetry(int code) throws Exception {
    log.info("请求retry接口");
    // 这里模拟了一个不存在的地址
    HttpGet request = new HttpGet("http://localhost:8080/http_test1");
    CloseableHttpResponse httpResponse = HTTP_CLIENT.execute(request);
    String result = IoUtil.read(httpResponse.getEntity().getContent()).toString();
    if(code != 200){
        throw new Exception("接口请求异常");
    }

    return result;
}

访问接口地址:http://localhost:8080/retry_demo_httpclient?code=200。查看控制台日志打印:

2.6、自定义重试工具

装X的话,我们还可以自定义我们的重试工具。其实无非以下几个步骤:

  1. 自定义重试的工具类
  2. 接收一个方法调用,并对该方法进行异常捕获
  3. 如果捕获了该异常,则进行一定间隔,然后重新请求
  4. 记录请求次数,如果超过上限,则提示异常信息

直接定义一个重试的工具类RetryUtil.java:

import cn.hutool.core.thread.ThreadUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Supplier;

@Slf4j
public class RetryUtil {


    /**
     * 重试方法
     * @param invokeFunc   原方法调用
     * @param maxAttempts  重试次数上限
     * @param deplay   重试的间隔时间
     * @param timeUnit   重试的间隔时间单位
     * @param faultFunc   如果超过重试上限次数,那么会执行该错误回调方法
     * @return
     * @param <T>
     */
    public static <T> T retry(Supplier<T> invokeFunc, int maxAttempts, long deplay, TimeUnit timeUnit, Function<Throwable, T> faultFunc) {
        AtomicInteger retryTimes = new AtomicInteger(0);
        for(;;) {
            try{
                return invokeFunc.get();
            } catch (Throwable th) {
                if(retryTimes.get() > maxAttempts){
                    log.error("重试次数超过{}次,进入失败回调", retryTimes.get());
                    return faultFunc.apply(th);
                }
                ThreadUtil.sleep(deplay, timeUnit);
                retryTimes.getAndAdd(1);
            }
        }
    }

}

工具类使用:

@GetMapping("retry_demo_custom")
public String retry_demo_custom(Integer code)  {
    return RetryUtil.retry(() -> {
        String result = null;
        try {
            result = customRetry(code);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }, 3, 1000, TimeUnit.MILLISECONDS, Throwable::getMessage);
}

private String customRetry(int code) throws Exception {
    log.info("请求customRetry接口");
    String result = HttpUtil.get("http://localhost:8080/http_test");
    if(code != 200){
        throw new Exception("接口请求异常");
    }

    return result;
}

执行完后,访问地址:http://localhost:8080/retry_demo_custom?code=2001

这里只是简单的进行了定义,如果项目中使用肯定需要考虑更复杂的因素。如进入重试时不一定只有异常的时候需要重试,可以指定重试策略,然后制定进入重试策略的规则。

2.7、并发框架异步重试

在 Java 并发框架中,异步重试通常涉及到使用线程池和定时器,以便在异步任务失败后进行重试。以下是一个简单的示例,演示了如何使用 CompletableFuture、ScheduledExecutorService 和 CompletableFuture.supplyAsync 来实现异步任务的重试。

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class AsyncRetryExample {

    private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

    public static void main(String[] args) {
        // 示例异步任务,这里使用 supplyAsync,你可以根据实际情况选择其他异步任务
        CompletableFuture<String> asyncTask = CompletableFuture.supplyAsync(() -> performAsyncTask("Task"));

        // 异步任务失败后的重试逻辑
        retryAsyncTask(asyncTask, 3, 1, TimeUnit.SECONDS);
    }

    private static CompletableFuture<String> performAsyncTask(String taskName) {
        // 模拟异步任务,这里可以是任何异步操作
        System.out.println("Performing async task: " + taskName);
        // 这里模拟任务失败的情况
        throw new RuntimeException("Task failed");
    }

    private static <T> void retryAsyncTask(CompletableFuture<T> asyncTask, int maxRetries, long delay, TimeUnit timeUnit) {
        asyncTask.exceptionally(throwable -> {
            // 异步任务失败后的处理逻辑
            System.out.println("Task failed: " + throwable.getMessage());

            // 重试逻辑
            if (maxRetries > 0) {
                System.out.println("Retrying...");

                CompletableFuture<T> retryTask = CompletableFuture.supplyAsync(() -> performAsyncTask("Retry Task"));
                // 递归调用,进行重试
                retryAsyncTask(retryTask, maxRetries - 1, delay, timeUnit);
            } else {
                System.out.println("Max retries reached. Task failed.");
            }

            return null; // 必须返回 null,否则会影响链式调用
        });
    }
}

示例中,performAsyncTask 模拟了一个异步任务,如果任务失败,它会抛出一个运行时异常。retryAsyncTask 方法用于处理异步任务的失败情况,并进行重试。在重试时,它使用 CompletableFuture.supplyAsync 创建一个新的异步任务,模拟了重试的过程。请注意,这只是一个简单的示例,实际应用中可能需要更复杂的重试策略和错误处理逻辑。

2.8、消息队列

网上还有一种消息队列的方式来实现,这里没过多的去研究过,目前以上几种方式应该也是够用的了。这里直接贴出网上的部分代码,使用 RabbitMQ 作为消息队列,演示了请求重试的实现:

首先添加依赖:

<dependencies>
    <dependency>
        <groupId>com.rabbitmq</groupId>
        <artifactId>amqp-client</artifactId>
        <version>5.13.1</version>
    </dependency>
</dependencies>

然后,创建一个发送者和接收者类:

消息发送者(Producer)

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class MessageProducer {

    private static final String QUEUE_NAME = "retry_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");

        try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) {
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);

            // 模拟发送请求
            String request = "Your request data";

            // 将请求发送到队列
            channel.basicPublish("", QUEUE_NAME, null, request.getBytes());
            System.out.println(" [x] Sent '" + request + "'");
        }
    }
}

消息接收者(Consumer)

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class MessageConsumer {

    private static final String QUEUE_NAME = "retry_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");

        try (Connection connection = factory.newConnection(); Channel channel = connection.createChannel()) {
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);

            // 设置消息监听器
            DeliverCallback deliverCallback = (consumerTag, delivery) -> {
                String request = new String(delivery.getBody(), "UTF-8");

                // 模拟处理请求,这里可能会出现处理失败的情况
                boolean processingSucceeded = processRequest(request);

                if (processingSucceeded) {
                    System.out.println(" [x] Received and processed: '" + request + "'");
                } else {
                    // 处理失败,将请求重新放入队列,进行重试
                    channel.basicPublish("", QUEUE_NAME, null, delivery.getBody());
                    System.out.println(" [x] Processing failed. Retrying: '" + request + "'");
                }
            };

            // 消费消息
            channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {
            });
        }
    }

    private static boolean processRequest(String request) {
        // 模拟处理请求的方法
        // 在实际应用中,这里应该是对请求的处理逻辑
        // 返回 true 表示处理成功,返回 false 表示处理失败,需要进行重试
        // 这里简单地模拟了一个失败的情况
        return !request.equals("Your request data");
    }
}

示例中,消息发送者(MessageProducer)将请求发送到名为 "retry_queue" 的队列中。消息接收者(MessageConsumer)监听队列,当接收到消息时,模拟处理请求的逻辑。如果处理失败,将请求重新放入队列进行重试。

3、小结

接口请求重试机制对保证系统高可用非常关键,需要根据业务需求选择合适的重试策略。常用的组合策略包括带最大次数的定时/指数退避重试、故障转移重试等。重试机制需要综合设置以达到容错效果 又避免产生过大的系统负载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1351453.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c++ 填充字符 memset fill

https://blog.csdn.net/sandalphon4869/article/details/105404397 https://blog.csdn.net/liuchuo/article/details/52296646 https://blog.csdn.net/bulebin/article/details/105324515 https://blog.csdn.net/liuchuo/article/details/52296646 通常会填充字符串某些位置的值…

opencv期末练习题(5)附带解析

根据R、G、B的值实时修改图像的颜色 import cv2 import numpy as np""" 滑动块调整图像灰度1. 读取图片&#xff0c;并转为灰度图 2. 定义启动滑块和R、G、B滑块 3. 只有启动滑块的值为1时&#xff0c;拖动R、G、B滑块才生效 4. 根据R、G、B的值实时对修改图片的…

JDK17 - 开发者视角,从 JDK8 ~ JDK17 都增加了哪些新特性

目录 前言 一、站在开发视角&#xff0c;从 JDK8 升级到 JDK17 都有哪些新特性 1.1、JDK8 新特性 1.1.1、Optional 类 a&#xff09;简介 b&#xff09;使用方法 c&#xff09;使用场景 1.2、JDK9 新特性 1.2.1、Optional - ifPresentOrElse 解决 if-else 1.2.2、Opt…

每天刷两道题——第三天

1.1两两交换链表中的节点 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09; 输入&#xff1a;[1,2,3,4] 输出&#xff1a;[2,1,4,3…

【ESP-NOW Web 服务器传感器仪表板 (ESP-NOW + Wi-Fi)】

【ESP-NOW Web 服务器传感器仪表板 &#xff08;ESP-NOW Wi-Fi&#xff09;】 1. 前言2. 同时使用 ESP-NOW 和 Wi-Fi3. 项目概况4. 先决条件4.1 环境配置4.2 DHT 库4.3 ESPAsyncWebSrv服务器库4.4 Arduino_JSON4.5 所需零件4.6 获取接收板 MAC 地址4.7 ESP32 发送电路 5. ESP3…

常见安全概念澄清,Java小白入门(八)

认证 认证 (Identification) 是验证当前用户的身份。 常见的认证技术&#xff1a; 身份证用户名和密码用户手机&#xff1a;手机短信、手机二维码扫描、手势密码用户的电子邮箱用户的生物学特征&#xff1a;指纹、语音、眼睛虹膜 授权 授权 (Authorization) 指赋予用户系统…

js常用事件演示

目录 JS事件的具体方法 窗口事件 表单事件 键盘事件 鼠标事件 知识小拓展 JS事件的具体方法 我们用到JavaScript的时候js的事件就显得特别重要了 事件名说明onsubmit当表单提交时触发该事件onclick鼠标单击事件ondblclick鼠标双击事件onblur元素失去焦点onfocus元素获得…

了解深度学习优化器:Momentum、AdaGrad、RMSProp 和 Adam

slavahead 一、介绍 DEEP学习在人工智能领域迈出了一大步。目前&#xff0c;神经网络在非表格数据&#xff08;图像、视频、音频等&#xff09;上的表现优于其他类型的算法。深度学习模型通常具有很强的复杂性&#xff0c;并提出数百万甚至数十亿个可训练的参数。这就是为什么在…

玩转贝启科技BQ3588C开源鸿蒙系统开发板 —— 编译构建及此过程中的踩坑填坑(1)

接前一篇文章&#xff1a;玩转贝启科技BQ3588C开源鸿蒙系统开发板 —— 代码下载&#xff08;2&#xff09; 本文主要参考&#xff1a; BQ3588C_代码下载 上一回完成了代码下载&#xff0c;本回开始进行编译构建。 1. 编译构建 &#xff08;1&#xff09;执行prebuilts 在源…

SpringBoot 集成支付宝支付

网页操作步骤 1.进入支付宝开发平台—沙箱环境 使用开发者账号登录开放平台控制平台 2.点击沙箱进入沙箱环境 说明&#xff1a;沙箱环境支持的产品&#xff0c;可以在沙箱控制台 沙箱应用 > 产品列表 中查看。 3.进入沙箱&#xff0c;配置接口加签方式 在沙箱进行调试前…

扫地机器人地图与用户终端的同步

以下内容为本人的学习笔记&#xff0c;如需要转载&#xff0c;请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/APaJheSbgTW3jNssWsp5Ng 地图数据来源于机器人算法模块&#xff0c;一般通过SLAM算法完成建图的过程。 建图过程中&#xff0c;基础数据涉及到各…

Android 串口协议

前言 本协议是 Android 应用端与主控板之间的通信协议&#xff0c;是串行通信协议。 协议要求同一时间只能有两个通讯端点在相互通讯&#xff0c;采用小端传输数据。 硬件层基于RS485协议&#xff0c;采取半双工&#xff0c;一主多从的通讯模式。Android定义为主机&#xff0c…

一、初识Redis与分布式系统

目录 一、Redis应用 二、实现方式 三、Redis应用 四、分布式系统 五、分布式系统实现 1、应用服务和数据库服务分离 2、引入负载均衡&#xff0c;应用服务器集群&#xff08;解决高并发&#xff09; 3、引入读写分离&#xff0c;数据库主从结构&#xff08;解决高并发&a…

如何在iPhone设备中查看崩溃日志

​ 目录 如何在iPhone设备中查看崩溃日志 摘要 引言 导致iPhone设备崩溃的主要原因是什么&#xff1f; 使用克魔助手查看iPhone设备中的崩溃日志 奔溃日志分析 总结 摘要 本文介绍了如何在iPhone设备中查看崩溃日志&#xff0c;以便调查崩溃的原因。我们将展示三种不同的…

流媒体学习之路(WebRTC)——GCC分析(4)

流媒体学习之路(WebRTC)——GCC分析&#xff08;4&#xff09; —— 我正在的github给大家开发一个用于做实验的项目 —— github.com/qw225967/Bifrost目标&#xff1a;可以让大家熟悉各类Qos能力、带宽估计能力&#xff0c;提供每个环节关键参数调节接口并实现一个json全配置…

【Java并发】深入浅出 synchronized关键词原理-上

一个问题的思考 建设我们有两个线程&#xff0c;一个进行5000次的相加操作&#xff0c;另一个进行5000次的减操作。那么最终结果是多少 package com.jia.syn;import java.util.concurrent.TimeUnit;/*** author qxlx* date 2024/1/2 10:08 PM*/ public class SynTest {privat…

使用Vue3开发学生管理系统模板1

环境搭建 通过解压之前《Vue3开发后台管理系统模板》的代码&#xff0c;我们能够得到用户增删改查的页面&#xff0c;我们基于用户增删改查的页面做进一步的优化。 创建学生增删改查页面 第一步&#xff1a;复制用户增删改查页面&#xff0c;重命名为StudentCRUD.vue <…

新闻稿发布:媒体重要还是价格重要

在当今信息爆炸的数字时代&#xff0c;企业推广与品牌塑造不可或缺的一环就是新闻稿发布。新闻稿是一种通过媒体渠道传递企业信息、宣传品牌、事件或产品新闻的文本形式。发布新闻稿的过程旨在将企业的声音传递给更广泛的受众&#xff0c;借助媒体平台实现品牌故事的广泛传播。…

exec、execFile、fork、spawn的区别与使用场景

在Node.js中&#xff0c;通过子进程可以实现并行执行任务&#xff0c;处理复杂的操作&#xff0c;以及与外部命令或文件进行交互。Node.js提供了多种子进程创建方法&#xff0c;包括exec、execFile、fork和spawn。本文将对这些方法进行比较&#xff0c;并介绍它们的适用场景和示…

【深度学习-基础学习】Transformer 笔记

本篇文章学习总结 李宏毅 2021 Spring 课程中关于 Transformer 相关的内容。课程链接以及PPT&#xff1a;李宏毅Spring2021ML这篇Blog需要Self-Attention为前置知识。 Transfomer 简介 Transfomer 架构主要是用来解决 Seq2Seq 问题的&#xff0c;也就是 Sequence to Sequence…