2024年8月

开心一刻

今天跟我姐聊天

我:我喜欢上了我们公司的一个女同事,她好漂亮,我心动了,怎么办

姐:喜欢一个女孩子不能只看她的外表

我:我知道,还要看她的内在嘛

姐:你想多了,还要看看自己的外表

还要看自己的外表

背景介绍


SpringBoot2.7 霸王硬上弓 Logback1.3 → 不甜但解渴
原理分析那部分,我对
Logback
的表述是很委婉的

委婉表述

后来想想,作为一个软件开发人员,怎能如此不严谨,真是太不应该了,为表示最诚挚的歉意,请允许我自罚三耳光

罗永浩打脸

作为弥补,接下来我会带你们盘一盘
Logback 1.3.14
的部分源码。参考
从源码来理解slf4j的绑定,以及logback对配置文件的加载
,同样基于两个问题

  1. SLF4J 与 Logback 是如何绑定的
  2. Logback 是如何加载配置文件的

来展开分析。在分析之前,我先帮你们解决一个你们可能会有遇到的疑问点

Logback 1.3.14 依赖的 SLF4J 版本怎么是 1.7.36?

假设我们的 pom.xml 内容如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.qsl</groupId>
    <artifactId>spring-boot-2_7_18</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version>
    </parent>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <logback.version>1.3.14</logback.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>spring-boot-starter-logging</artifactId>
                    <groupId>org.springframework.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
        </dependency>
    </dependencies>
</project>

但我们会发现 logback 的依赖树如下

slf4j乱入

无论是 logback
官配

slf4j与logback官配

还是 logback 1.3.14 pom 文件中的依赖

logback-parent_slf4j

slf4j-api
的版本都是
2.0.x

logback 1.3.14
依赖的是
slf4j-api 2.0.7
) ,
slf4j-api 1.7.36
是从哪乱入的?

这是因为引入了父依赖

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.7.18</version>
</parent>


spring-boot-starter-parent
的父依赖

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-dependencies</artifactId>
	<version>2.7.18</version>
</parent>


spring-boot-dependencies
指定了 slf4j 版本

spring-boot-dependencies_slf4j1.7.36

那为什么不是
logback-parent-1.3.14.pom
中的
slf4j.version
生效,而是
spring-boot-dependencies-2.7.18.pom
中的
slf4j.version
生效呢?这就涉及
maven
依赖的优先级了,感兴趣的可以去查阅相关资料,本文就不展开了,因为偏离我们的最初的目标越来越远了

那如何将
slf4j
改成
2.0.7
,提供两种方式

  1. 如果不需要
    spring-boot
    ,那就去掉父依赖
    spring-boot-starter-parent

    这就相当于由 logback 带入 slf4j,引入的就是 logback 所依赖的版本

  2. 在我们的 pom 文件中指定
    <slf4j.version>2.0.7</slf4j.version>

    这里还是涉及 maven 依赖的优先级,我们自己的 pom 文件中的优先级更高

不管采用哪种方式,反正要把版本搞正确

slf4j_2.0.7

SLF4J 绑定 Logback

准备测试代码

public class LogbackTest {

    private static Logger LOGGER = LoggerFactory.getLogger(LogbackTest.class);

    public static void main(String[] args)
    {
        LOGGER.info("......info");
    }
}

应该知道从哪开始跟源码吧,没得选择呀,只能选
getLogger
方法

bind前奏

推荐大家用
debug
的方式去跟,不然容易跟丢;来到
org.slf4j.LoggerFactory#bind
方法,这里完成
slf4j
与具体实现的绑定。bind 方法中有 2 点需要我们自己分析下

bind方法
  1. findServiceProviders

    static List<SLF4JServiceProvider> findServiceProviders() {
    	// retain behaviour similar to that of 1.7 series and earlier. More specifically, use the class loader that
    	// loaded the present class to search for services
    	final ClassLoader classLoaderOfLoggerFactory = LoggerFactory.class.getClassLoader();
    	ServiceLoader<SLF4JServiceProvider> serviceLoader = getServiceLoader(classLoaderOfLoggerFactory);
    	List<SLF4JServiceProvider> providerList = new ArrayList<>();
    	Iterator<SLF4JServiceProvider> iterator = serviceLoader.iterator();
    	while (iterator.hasNext()) {
    		safelyInstantiate(providerList, iterator);
    	}
    	return providerList;
    }
    

    有没有一点熟悉的感觉?大家回顾下
    JDK SPI
    ,是不是恍然大悟了?会去
    classpath
    下的
    META-INF/services
    目录下寻找
    org.slf4j.spi.SLF4JServiceProvider
    文件


    spi

    然后读取其中的内容,并实例化


    LogbackServiceProvider

    这里拿到的是
    Provider
    ,并非
    Logger

  2. initialize


    initialize

    大家注意看下
    defaultLoggerContext
    的类型
    LoggerContext


    public class LoggerContext extends ContextBase implements ILoggerFactory, LifeCycle


    第 2 点与 Logback 加载配置文件有关,后续再细看,暂且先只看第 1 点


    LoggerContext

    注意看下
    Logger
    的类型


    public final class Logger
    implements org.slf4j.Logger, LocationAwareLogger, LoggingEventAware, AppenderAttachable
    , Serializable


    实现了
    org.slf4j.Logger
    ,这就跟
    slf4j
    关联起来了

    接下来出栈,回到

    public static ILoggerFactory getILoggerFactory() {
    	return getProvider().getLoggerFactory();
    }
    

    getProvider()
    已经分析过了,接下来就看
    getLoggerFactory()

        public ILoggerFactory getLoggerFactory() {
            return defaultLoggerContext;
    
    //        if (!initialized) {
    //            return defaultLoggerContext;
    //        
    //
    //        if (contextSelectorBinder.getContextSelector() == null) {
    //            throw new IllegalStateException("contextSelector cannot be null. See also " + NULL_CS_URL);
    //        }
    //        return contextSelectorBinder.getContextSelector().getLoggerContext();
        }
    

    非常简单,直接返回
    defaultLoggerContext
    ,defaultLoggerContext 在前面的
    initialize
    已经讲过,忘记了的小伙伴回到上面看看


    getILoggerFactory()
    继续出栈,来到

    public static Logger getLogger(String name) {
    	ILoggerFactory iLoggerFactory = getILoggerFactory();
    	return iLoggerFactory.getLogger(name);
    }
    

    这里的
    iLoggerFactory
    是不是就是
    defaultLoggerContext
    ?接下来就看
    iLoggerFactory.getLogger(name)

    这个方法虽然略微有点长,但不难,只是有个缓存设计,我就不展开了,你们自行去看

总结下

  1. 通过 SPI 的方式,实现 SLF4JServiceProvider 的绑定(ch.qos.logback.classic.spi.LogbackServiceProvider)
  2. LogbackServiceProvider 的 initialize 方法会实例化 defaultLoggerContext(ch.qos.logback.classic.LoggerContext implement org.slf4j.ILoggerFactory)
  3. 通过 defaultLoggerContext 获取 logger(ch.qos.logback.classic.Logger implements org.slf4j.Logger)
  4. org.slf4j.Logger 绑定 ch.qos.logback.classic.Logger 完成

Logback 加载配置文件

前面已经提到过,
ch.qos.logback.classic.spi.LogbackServiceProvider#initializeLoggerContext
完成对配置文件的加载

private void initializeLoggerContext() {
	try {
		try {
			new ContextInitializer(defaultLoggerContext).autoConfig();
		} catch (JoranException je) {
			Util.report("Failed to auto configure default logger context", je);
		}
		// LOGBACK-292
		if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) {
			StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
		}
		// contextSelectorBinder.init(defaultLoggerContext, KEY);

	} catch (Exception t) { // see LOGBACK-1159
		Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
	}
}

一眼就能看出,下一步直接看
autoConfig
,跟进去 2 步,会来到如下方法

public void autoConfig(ClassLoader classLoader) throws JoranException {

	// see https://github.com/qos-ch/logback/issues/715
	classLoader = Loader.systemClassloaderIfNull(classLoader);

	String versionStr = EnvUtil.logbackVersion();
	if (versionStr == null) {
		versionStr = CoreConstants.NA;
	}
	loggerContext.getStatusManager().add(new InfoStatus(CoreConstants.LOGBACK_CLASSIC_VERSION_MESSAGE + versionStr, loggerContext));
	StatusListenerConfigHelper.installIfAsked(loggerContext);


	// invoke custom configurators
	List<Configurator> configuratorList = ClassicEnvUtil.loadFromServiceLoader(Configurator.class, classLoader);
	configuratorList.sort(rankComparator);
	if (configuratorList.isEmpty()) {
		contextAware.addInfo("No custom configurators were discovered as a service.");
	} else {
		printConfiguratorOrder(configuratorList);
	}

	for (Configurator c : configuratorList) {
		if (invokeConfigure(c) == Configurator.ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY)
			return;
	}

	// invoke internal configurators
	for (String configuratorClassName : INTERNAL_CONFIGURATOR_CLASSNAME_LIST) {
		contextAware.addInfo("Trying to configure with "+configuratorClassName);
		Configurator c = instantiateConfiguratorByClassName(configuratorClassName, classLoader);
		if(c == null)
			continue;
		if (invokeConfigure(c) == Configurator.ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY)
			return;
	}
}

前部分读自定义配置,因为我们没有自定义配置,所以可以忽略,直接看

// invoke internal configurators
for (String configuratorClassName : INTERNAL_CONFIGURATOR_CLASSNAME_LIST) {
	contextAware.addInfo("Trying to configure with "+configuratorClassName);
	Configurator c = instantiateConfiguratorByClassName(configuratorClassName, classLoader);
	if(c == null)
		continue;
	if (invokeConfigure(c) == Configurator.ExecutionStatus.DO_NOT_INVOKE_NEXT_IF_ANY)
		return;
}

INTERNAL_CONFIGURATOR_CLASSNAME_LIST
内容如下

String[] INTERNAL_CONFIGURATOR_CLASSNAME_LIST = {"ch.qos.logback.classic.joran.SerializedModelConfigurator",
            "ch.qos.logback.classic.util.DefaultJoranConfigurator", "ch.qos.logback.classic.BasicConfigurator"}

这个 for 循环是一旦
invoke
上,则直接返回,所以是
INTERNAL_CONFIGURATOR_CLASSNAME_LIST
元素从前往后,逐个
invoke
,一旦成功则直接结束;通过
debug
我们会发现
DefaultJoranConfigurator
invoke 上了,其
performMultiStepConfigurationFileSearch
方法寻找配置文件

performMultiStepConfigurationFileSearch

优先级从高到低,会从
classpath
下寻找三个文件

  1. 寻找
    logback.configurationFile
  2. 寻找
    logback-test.xml
  3. 寻找
    logback.xml

一旦找到,直接返回,不会继续寻找;我们用的是
logback.xml

logback.xml

而没有使用其它两个文件,所以生效的是
logback.xml

再回过头去看
背景介绍
中的不严谨处,我们发现
Logback 1.3.14
对配置文件的加载与
Logback 1.1.7
基本一致,只是少了
logback.groovy
的读取;但话说回来,
SLF4J

Logback
的绑定过程还是有非常大的变动,大家可以和
从源码来理解slf4j的绑定,以及logback对配置文件的加载
仔细对比

愣着干啥,鼓掌

总结

  1. SLF4J 2.0.x 与 Logback 1.3.x 的绑定,采用了
    SPI
    机制

  2. Logback 1.3.x 默认配置文件优先级


    logback.configurationFile > logback-test.xml > logback.xml


    优先级从高到低一旦读取一个,则直接采用这个,不会继续往下读

    所以
    SpringBoot2.7 霸王硬上弓 Logback1.3 → 不甜但解渴
    中提到的


    配置文件必须是logback.xml

    配置文件必须是 logback.xml
    不够严谨,还可以是哪些,你们应该知道了吧?


    头给你敲破
  3. 尽量选择
    官配
    依赖版本,不要头铁,不要头铁,不要头铁!

在软件开发过程中,尤其是在准备将新功能或修复后的版本上线之前,进行详尽的自测和上线前检查是至关重要的。以下是一个从多个维度综合考量的上线升级检查清单(Checklist),旨在帮助团队确保软件质量、稳定性和安全性:

1、代码质量与构建检查





2、 功能测试



执行全面的功能测试,验证新功能和现有功能是否按预期工作,确保升级没有破坏现有功能,执行相关的回归测试。

3.、性能测试



4、安全测试





5、兼容性测试



6、用户体验测试




7、需求与文档





8、环境、配置和部署






9、监控和日志、告警




10、数据迁移和备份



11、通知和沟通



12、紧急响应




13、法律法规合规性


14、升级与审批



通过以上清单的逐一检查,确保上线升级的每个步骤都经过细致安排和周密考量,从而最大限度地减少风险,提升上线成功率,减少因未预见问题导致的故障和损失。

上述罗列的上线升级检查清单是一个通用的模板,你可以根据具体的项目需求和团队的实际情况进行定制。此外,定期回顾和更新这个清单也是非常重要的,以确保它始终反映最新的最佳实践和项目要求。

相关文章

数据库系列:MySQL慢查询分析和性能优化
数据库系列:MySQL索引优化总结(综合版)
数据库系列:高并发下的数据字段变更
数据库系列:覆盖索引和规避回表
数据库系列:数据库高可用及无损扩容
数据库系列:使用高区分度索引列提升性能
数据库系列:前缀索引和索引长度的取舍
数据库系列:MySQL引擎MyISAM和InnoDB的比较
数据库系列:InnoDB下实现高并发控制
数据库系列:事务的4种隔离级别
数据库系列:RR和RC下,快照读的区别
数据库系列:MySQL InnoDB锁机制介绍
数据库系列:MySQL不同操作分别用什么锁?
数据库系列:业内主流MySQL数据中间件梳理
数据库系列:巨量数据表的分页性能问题

1 介绍

物理服务机的CPU、内存、存储设备、连接数等资源有限,某个时段大量连接同时执行操作,会导致数据库在处理上遇到性能瓶颈。为了解决这个问题,行业先驱门充分发扬了分而治之的思想,对大库表进行分割,
然后实施更好的控制和管理,同时使用多台机器的CPU、内存、存储,提供更好的性能。而分治有两种实现方式:垂直拆分和水平拆分。

  • 垂直拆分(Scale Up)分为垂直分库和垂直分表
    ,主要按功能模块拆分,以解决各个库或者各个表之间的资源竞争。比如分为订单库、商品库、用户库...这种方式,多个数据库之间的表结构是不同的。
  • 水平拆分(Scale Out)又分为库内分表和分库分表
    ,来解决单表中数据量增长出现的压力,这些数据库中的表结构完全相同。

详细可以参考笔者的这两篇文章:

MySQL全面瓦解28:分库分表


MySQL全面瓦解29:分库分表之Partition功能详解

2 常见主流中间件介绍

既然已经是实现了分库分表的方案,那么就需要有便捷的组件来支持管理.
分库分表中间件是数据库架构中用于解决高并发、大数据量等问题的关键组件。这些中间件通过数据分片、路由、负载均衡等功能,提高了数据库的性能和扩展性。以下是一些常见的分库分表中间件介绍:

2.1 ShardingSphere

概述

  • ShardingSphere是一款开源的分布式数据库中间件,提供了分库分表、读写分离、分布式事务等功能。
  • 它支持多种数据库,如MySQL、PostgreSQL、Oracle、SQL Server等,并且可以与现有的数据库系统无缝集成。

架构与原理

ShardingSphere由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar三个主要组件组成。

  • Sharding-JDBC:
    用于实现分库分表功能的模块,它可以在应用层通过简单的配置实现透明的分库分表操作。
  • Sharding-Proxy:
    用于实现数据库代理功能的模块,它可以将数据库请求路由到不同的数据库节点上,实现读写分离和负载均衡。
  • Sharding-Sidecar(规划中):
    定位为Kubernetes的云原生数据库代理,以Sidecar的形式代理所有对数据库的访问。

image

其原理是通过数据分片和路由来实现分库分表。数据分片是将数据划分为多个片段,每个片段存储在不同的数据库实例或数据表中;路由则是根据数据的分片规则将请求路由到对应的数据库实例或数据表上。

优势与适用场景

  • 灵活的扩展性:支持水平扩展和垂直扩展,可以根据业务需求灵活调整数据库的规模和性能。
  • 高可用性:支持主从复制和多活架构,提供高可用的数据库访问和数据保护。
  • 简化开发和维护:提供了简单易用的接口和配置,可以减少开发人员的工作量和维护成本。
  • 适用于高并发访问、大数据量存储、跨地域部署等场景。

2.2 MyCAT

概述

  • MyCAT是一个开源的分布式数据库中间件,基于Java编写,支持MySQL协议,可以作为MySQL的代理服务器使用。
  • 它支持分库分表、读写分离、全局序列号等功能,并且具有跨语言、跨平台、跨数据库的通用性。

架构与原理

  • MyCAT采用代理模式来实现数据库的路由和分片。
  • 它包括MyCAT-Server和MyCAT-DataNode两个主要部分。MyCAT-Server用于接收客户端的数据库请求,并将请求路由到不同的数据库节点上;MyCAT-DataNode则用于实际存储数据的数据库节点。

image

优势与适用场景

  • 易于部署和使用:对于项目来说是透明的,如果遇到升级之类的操作,只需要在中间件层面进行即可。
  • 适用于大规模MySQL集群的管理和扩展问题。
  • 但是,MyCAT的SQL支持相对较弱,可能需要对SQL语句进行一定的改写和优化。

2.3 Vitess

概述

  • Vitess是由YouTube开发的一个开源分布式数据库中间件,主要用于解决大规模MySQL集群的管理和扩展问题。

架构与原理

  • Vitess提供了数据分片、读写分离、水平扩展等功能,并且具有强大的负载均衡和故障恢复能力。
  • 它通过vtgate(Vitess的查询路由器)来实现对数据库的访问控制和负载均衡。

优势与适用场景

  • 适用于大规模MySQL集群的场景,具有强大的水平扩展和负载均衡功能。
  • 但是,Vitess对于非MySQL数据库的支持较弱,可能不适用于其他类型的数据库系统。

2.4 其他中间件介绍

除了上述三种常见的分库分表中间件外,还有其他一些中间件如Cobar(已逐渐被淘汰)、TDDL(淘宝分布式数据层)、Atlas(Qihoo 360开源)等。这些中间件在特定的历史时期或特定的业务场景下有一定的应用价值,但随着技术的发展和市场的变化,它们的使用范围和影响力逐渐减弱。

3 总结

在选择分库分表中间件时,需要根据具体的业务需求、技术栈、性能要求等因素进行综合考虑。ShardingSphere、MyCAT和Vitess是当前较为流行和成熟的中间件选择,它们各自具有不同的优势和适用场景。同时,也需要关注中间件的发展动态和社区支持情况,以便在后续的技术升级和维护中获得更好的支持。


title: 使用 $fetch 进行 HTTP 请求
date: 2024/8/2
updated: 2024/8/2
author:
cmdragon

excerpt:
摘要:文章介绍了Nuxt3中使用
\(fetch进行HTTP请求的方法,它是基于ofetch库,支持SSR和自动缓存。\)
fetch简化了HTTP请求,支持GET、POST等,可结合useAsyncData或useFetch优化数据获取,避免重复请求,适用于服务器端渲染。

categories:

  • 前端开发

tags:

  • Nuxt3
  • $fetch
  • HTTP
  • SSR
  • 缓存
  • Vue
  • API


image
image

扫描
二维码
关注或者微信搜一搜:
编程智域 前端至全栈交流与成长

在 Nuxt3 中,
$fetch
是一个强大的工具,用于在 Vue 应用程序和 API 路由中进行 HTTP 请求。它基于
ofetch
库,并在 Nuxt
中提供了一些增强功能,如支持服务器端渲染(SSR)和自动缓存。

什么是
$fetch

$fetch
是 Nuxt3 中全局暴露的一个辅助函数,用于进行 HTTP 请求。它允许您在 Vue 组件和 API 路由中轻松地发送 GET、POST
等请求,并处理响应。与传统的
axios

fetch
相比,
$fetch
提供了更好的集成和优化,特别是在处理服务器端渲染(SSR)时。

为什么使用
$fetch

1. 简化 HTTP 请求

$fetch
提供了简洁的 API,使得发起 HTTP 请求和处理响应变得更简单。您可以轻松地在 Vue 组件中获取数据或发送请求,而不需要手动管理请求和响应逻辑。

2. 支持服务器端渲染(SSR)

在 Nuxt3 中使用
$fetch
时,如果在服务器端渲染期间调用,它将直接模拟请求,避免了额外的 API 调用。这样可以提高性能并减少不必要的网络请求。

3. 避免重复数据获取

当在组件中使用
$fetch
时,若不结合
useAsyncData

useFetch
使用,可能会导致数据在服务器端和客户端两次获取。为了防止这种情况,推荐使用
useAsyncData

useFetch
来确保数据只在服务器端获取,并在客户端进行优化处理。

如何使用
$fetch

基本用法

$fetch
可以用来发送各种类型的 HTTP 请求。以下是一些常见的示例:

示例 1: 发送 GET 请求


<template>
  <div>
    <p>数据:{{ data }}</p>
  </div>
</template>

<script setup lang="ts">
  const data = await $fetch('/api/data');
</script>

在这个示例中,我们使用
$fetch
发送了一个 GET 请求到
/api/data
,并将响应数据绑定到组件中的
data
变量。

示例 2: 发送 POST 请求


<template>
  <button @click="submitForm">提交</button>
</template>

<script setup lang="ts">
  async function submitForm() {
    const response = await $fetch('/api/submit', {
      method: 'POST',
      body: {name: 'John Doe', email: 'john@example.com'},
    });
    console.log(response);
  }
</script>

在这个示例中,我们定义了一个
submitForm
函数,它会发送一个 POST 请求到
/api/submit
,并传递一个 JSON 对象作为请求体。

结合
useAsyncData

useFetch

为了优化数据获取,并避免在服务器端和客户端两次请求相同的数据,推荐使用
useAsyncData

useFetch

示例 3: 使用
useAsyncData


<template>
  <div>
    <p>数据:{{ data }}</p>
  </div>
</template>

<script setup lang="ts">

  const {data} = await useAsyncData('item', () => $fetch('/api/item'));
</script>

在这个示例中,我们使用
useAsyncData
来获取数据。这将确保数据仅在服务器端获取一次,并将其传递到客户端,避免了重复获取。

示例 4: 使用
useFetch


<template>
  <div>
    <p>数据:{{ data }}</p>
  </div>
</template>

<script setup lang="ts">

  const {data} = await useFetch('/api/item');
</script>

useFetch

useAsyncData

$fetch
的快捷方式,提供了类似的功能,但更为简洁。

使用
$fetch
仅在客户端执行

有时候,您可能只希望在客户端执行某些 HTTP 请求。例如,在用户点击按钮时发送请求:


<template>
  <button @click="contactForm">联系我们</button>
</template>

<script setup lang="ts">
  async function contactForm() {
    const response = await $fetch('/api/contact', {
      method: 'POST',
      body: {message: 'Hello from the client!'},
    });
    console.log(response);
  }
</script>

在这个示例中,
contactForm
函数仅在客户端触发,通过点击按钮发送一个 POST 请求。

参数说明

  • url
    : 请求的 URL 地址。
  • options
    : 可选的请求选项,如
    method

    body

    headers
    等。

总结

$fetch
是 Nuxt3 中进行 HTTP 请求的推荐方式,它提供了简洁的 API,并支持服务器端渲染(SSR)。通过结合使用
useAsyncData

useFetch
,您可以优化数据获取流程,避免重复请求。希望这篇教程能够帮助您更好地理解和使用
$fetch

余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:
编程智域 前端至全栈交流与成长
,阅读完整的文章:
使用 $fetch 进行 HTTP 请求 | cmdragon's Blog

往期文章归档:

今天又又又刷到一个视频,很想睡觉(昨晚熬了个大夜),但是又临近午饭不能睡,只能水篇随笔来打发时间了。

什么是费曼积分法?

先看看官方解释:

费曼积分法(Feynman integral)是一种求解复变函数定积分的计算方法,由理查德·费曼(Richard P. Feynman)提出。这种方法特别适用于处理物理学中的路径积分问题,但也可以应用于其他领域的积分计算。
在费曼积分法中,被积函数通常表示为路径积分的形式,其中积分变量通常是时间或其他连续参数。费曼积分法的核心思想是将复杂的路径积分转换为简单的线积分,通过使用复变函数理论和分形几何来简化计算过程。
具体来说,费曼积分法的基本步骤如下:

  1. 将路径积分转换为复变函数的积分。
  2. 使用分形几何技术对积分进行分块。
  3. 对每一块进行简单的线积分。
  4. 最后,将所有的线积分相加得到原路径积分的值。

费曼积分法的一个显著优点是其计算效率高,尤其对于那些难以直接用传统方法处理的路径积分问题。然而,它也有一定的局限性,例如,它要求被积函数具有一定的解析性质,而且对于某些类型的路径积分,可能需要使用数值方法而不是解析方法。


一眼看去,看不懂。因为我不是数学专业的还没学复变函数,也不知道以后要不要学。但今天刷到的这个视频的过程,我倒是可以看懂。下面我将以一个例子总结一下我的理解:

先看问题


这个积分该怎么解?可能大部分人会采用分部积分法,但是天才的费曼想到了一种更与众不同的解法——费曼积分法

解题过程

首先,我们可以将被积函数乘以一个
\(e^{-ax}\)
,此时我们来看这个新的方程

此时我们只要求出这个新的积分,然后令a=0,即可求出原积分。
这个积分又该怎么算呢?我们可以对参数a进行求导,根据勒贝格积分的微分定理:如果函数f(x, a)对 a可微,并且其导数
\(\frac{\partial f}{\partial a}\)
在积分区间上绝对收敛,那么积分和导数可以交换次序。我们可以得到:

然后,让我们回顾一下欧拉公式:

我们先将等式的左右两边同时乘以
\(e^{-ax}\)
后积分可以得到:

此时,我们惊喜地发现等式右边的虚部除去i不就是我们要求的吗?

接下来,让我们求解等式左边:

注意最后的化简,采用了乘以共轭来去除分母中的虚数部分
而我们需要积分的虚数部分:

可以得到:

现在只需对这个函数对a积分即可,我们知道对于等式右边,它的积分有对应的公式,故可得:

当a->无穷大时,左边等于0,右边等于
\(C - \frac{\pi}{2}\)
;解得
\(C = \frac{\pi}{2}\)
,得到:

然后再带入a=0,解得:

总结

额,这么一看,好像还挺简单的,但是这个解题思路可不是一般人能想出来的。总的来说,这个解题思路是将一个积分问题转化为另一个与欧拉公式相关的积分问题,利用欧拉公式的特点简化问题复杂度。好了,那么费曼积分就到这吧~