[golang Web开发] 3.golang web开发:处理请求

news2025/1/10 21:08:46

简介

Go语音的net/http包提供了一系列用于表示HTTP报文的结构,可以使用它处理请求和发送响应,其中Request结构代表了客户端发送的请求报文,下面是Request讲解

type Request struct {
    // Method指定HTTP方法(GET、POST、PUT等)。对客户端,""代表GET。
    Method string
    // URL在服务端表示被请求的URI,在客户端表示要访问的URL。
    //
    // 在服务端,URL字段是解析请求行的URI(保存在RequestURI字段)得到的,
    // 对大多数请求来说,除了Path和RawQuery之外的字段都是空字符串。
    // (参见RFC 2616, Section 5.1.2)
    //
    // 在客户端,URL的Host字段指定了要连接的服务器,
    // 而Request的Host字段(可选地)指定要发送的HTTP请求的Host头的值。
    URL *url.URL
    // 接收到的请求的协议版本。本包生产的Request总是使用HTTP/1.1
    Proto      string // "HTTP/1.0"
    ProtoMajor int    // 1
    ProtoMinor int    // 0
    // Header字段用来表示HTTP请求的头域。如果头域(多行键值对格式)为:
    //	accept-encoding: gzip, deflate
    //	Accept-Language: en-us
    //	Connection: keep-alive
    // 则:
    //	Header = map[string][]string{
    //		"Accept-Encoding": {"gzip, deflate"},
    //		"Accept-Language": {"en-us"},
    //		"Connection": {"keep-alive"},
    //	}
    // HTTP规定头域的键名(头名)是大小写敏感的,请求的解析器通过规范化头域的键名来实现这点。
    // 在客户端的请求,可能会被自动添加或重写Header中的特定的头,参见Request.Write方法。
    Header Header
    // Body是请求的主体。
    //
    // 在客户端,如果Body是nil表示该请求没有主体买入GET请求。
    // Client的Transport字段会负责调用Body的Close方法。
    //
    // 在服务端,Body字段总是非nil的;但在没有主体时,读取Body会立刻返回EOF。
    // Server会关闭请求的主体,ServeHTTP处理器不需要关闭Body字段。
    Body io.ReadCloser
    // ContentLength记录相关内容的长度。
    // 如果为-1,表示长度未知,如果>=0,表示可以从Body字段读取ContentLength字节数据。
    // 在客户端,如果Body非nil而该字段为0,表示不知道Body的长度。
    ContentLength int64
    // TransferEncoding按从最外到最里的顺序列出传输编码,空切片表示"identity"编码。
    // 本字段一般会被忽略。当发送或接受请求时,会自动添加或移除"chunked"传输编码。
    TransferEncoding []string
    // Close在服务端指定是否在回复请求后关闭连接,在客户端指定是否在发送请求后关闭连接。
    Close bool
    // 在服务端,Host指定URL会在其上寻找资源的主机。
    // 根据RFC 2616,该值可以是Host头的值,或者URL自身提供的主机名。
    // Host的格式可以是"host:port"。
    //
    // 在客户端,请求的Host字段(可选地)用来重写请求的Host头。
    // 如过该字段为"",Request.Write方法会使用URL字段的Host。
    Host string
    // Form是解析好的表单数据,包括URL字段的query参数和POST或PUT的表单数据。
    // 本字段只有在调用ParseForm后才有效。在客户端,会忽略请求中的本字段而使用Body替代。
    Form url.Values
    // PostForm是解析好的POST或PUT的表单数据。
    // 本字段只有在调用ParseForm后才有效。在客户端,会忽略请求中的本字段而使用Body替代。
    PostForm url.Values
    // MultipartForm是解析好的多部件表单,包括上传的文件。
    // 本字段只有在调用ParseMultipartForm后才有效。
    // 在客户端,会忽略请求中的本字段而使用Body替代。
    MultipartForm *multipart.Form
    // Trailer指定了会在请求主体之后发送的额外的头域。
    //
    // 在服务端,Trailer字段必须初始化为只有trailer键,所有键都对应nil值。
    // (客户端会声明哪些trailer会发送)
    // 在处理器从Body读取时,不能使用本字段。
    // 在从Body的读取返回EOF后,Trailer字段会被更新完毕并包含非nil的值。
    // (如果客户端发送了这些键值对),此时才可以访问本字段。
    //
    // 在客户端,Trail必须初始化为一个包含将要发送的键值对的映射。(值可以是nil或其终值)
    // ContentLength字段必须是0或-1,以启用"chunked"传输编码发送请求。
    // 在开始发送请求后,Trailer可以在读取请求主体期间被修改,
    // 一旦请求主体返回EOF,调用者就不可再修改Trailer。
    //
    // 很少有HTTP客户端、服务端或代理支持HTTP trailer。
    Trailer Header
    // RemoteAddr允许HTTP服务器和其他软件记录该请求的来源地址,一般用于日志。
    // 本字段不是ReadRequest函数填写的,也没有定义格式。
    // 本包的HTTP服务器会在调用处理器之前设置RemoteAddr为"IP:port"格式的地址。
    // 客户端会忽略请求中的RemoteAddr字段。
    RemoteAddr string
    // RequestURI是被客户端发送到服务端的请求的请求行中未修改的请求URI
    // (参见RFC 2616, Section 5.1)
    // 一般应使用URI字段,在客户端设置请求的本字段会导致错误。
    RequestURI string
    // TLS字段允许HTTP服务器和其他软件记录接收到该请求的TLS连接的信息
    // 本字段不是ReadRequest函数填写的。
    // 对启用了TLS的连接,本包的HTTP服务器会在调用处理器之前设置TLS字段,否则将设TLS为nil。
    // 客户端会忽略请求中的TLS字段。
    TLS *tls.ConnectionState
}

Request类型代表一个服务端接受到的或者客户端发送出去的HTTP请求。

Request各字段的意义和用途在服务端和客户端是不同的。除了字段本身上方文档,还可参见Request.Write方法和RoundTripper接口的文档

 

一.获取请求url

Request结构体中的URL字段用于表示请求行中包含的URL,该字段是一个指向url.URL结构的指针,下面是URL结构讲解

import "net/url"

url包解析URL并实现了查询的逸码

 (1).Path字段

        * 获取请求的URL

        * eg:http:localhost:8080/hello?username=admin&password=123456, 通过r.URL.Path  只能得到/hello

(2).RawQuery字段

        * 获取请求的URL后面?后面的查询字符串

        * eg:http:localhost:8080/hello?username=admin&password=123456, 通过r.URL.RawQuery得到的是 username=admin&password=123456

二.获取请求头中的信息

通过Request结果中的Header字段来获取请求头中的所有信息,Header字段的类型是Header类型,而Header类型是一个map[string][]string,下面是Header类型及它的方法

三.获取请求体中的信息

请求和响应的主体都是有Request结构中的body字段表示,这个字段的类型是io.ReadCloser接口,该接口包含了Reader接口和Closer接口,Reader接口拥有Read方法,Closer接口拥有Close方法

(1).由于GET请求没有请求体,所以需要在HTML页面创建一个form表单,通过指定method="post"来发送一个POST请求

form.html提交如下:

<form action="http://localhost:8080/getBody" method="POST">
    用户名:<input type="text" name="username" value="admin"><br/>
    密码:<input type="password" name="password" value="123456"><br/>
    <input type="提交">
</form>

 main.go获取客户端提交的信息,并解析如下:

func handler(w http.ResponseWriter, r *http.Request) {
    //获取内容长度
    length := r.ContentLenth
    //创建一个切片
    body := make([]byte, length)
    //读取请求体
    r.Body.Read(body)
    fmt.Fprintln(w, "请求体中的内容是:", string(body))
}

四.获取请求参数

通过net/http库中的Request结构的字段以及方法来获取请求URL后面的请求参数以及form表单提交的请求参数

1.Form字段 

(1).类型是url.Values类型,Form是解析好的表单数据,包括URL字段的query参数和POST或PUT表单数据

 包net/url下url.Values

 (2).Form字段只有在调用Request的ParseForm后才有效,在客户端,会忽略请求中的本字段而使用Body替代

代码: 

func handler(w http.ResponseWriter, r *http.Request) {
    //解析表单
    r.ParseForm()
    //获取请求参数
    fmt.Fprintln(w, "请求参数:", r.Form)
}

注意:在执行r.Form之前一定要调用r.ParseForm方法对表单进行解析 

结果:请求参数为:map[password:[123456]] username:[admin]] 

2. 如果对form表单做一些修改,在action属性的URL后面也添加相同的请求参数

如果对form表单做一些修改,在action属性的URL后面也添加相同的请求参数,如下: 

        action="http://localhost:8080/getBody?pwd=123456&username=zhangsan"

则执行结果如下:

        map[password:[123456]] pwd:[123456] username:[admin zhangsan]] 

可以发现:

        * 表单中的请求参数 username 和 URL 中的请求参数 username 都获取到了,而且表单中的请求参数的值排在 URL 请求参数值的前面

        * 如果此时只想获取表单中的请求参数该怎么办呢?

        * 那就需要使用 Request 结构中的PostForm字段,但是PostForm字段只支持 application/x-www-form-urlencoded编码,如果 form 表单的enctype属性值为 multipart/form-data,那么使用PostForm字段无法获取表单中的数据,此时需要使用MultipartForm字段

        * 说明: form 表单的enctype属性默认值为application/x-www-form-urlencoded编码.,实现上传文件时需要将该属性的值设置为multipart/form-data编码格式

 3.FormValue方法和PostFormValue方法

代码如下: 

package main

import (
   "fmt"
   "net/http"
)

//创建处理器函数
func handler(w http.ResponseWriter, r *http.Request)  {
   _, err := fmt.Fprintln(w, "请求地址:", r.URL.Path)
   _, err = fmt.Fprintln(w, "请求地址中后面的查询字符串:", r.URL.RawQuery)
   _, err = fmt.Fprintln(w, "请求地址中请求头数据:", r.Header)//返回map[string][][string]
   _, err = fmt.Fprintln(w, "请求地址中请求头Accept-Encoding的信息:", r.Header["Accept-Encoding"])   //返回map切片
   _, err = fmt.Fprintln(w, "请求地址中请求头Accept-Encoding的属性值:", r.Header.Get("Accept-Encoding"))//返回string

   获取请求体中内容长度
   //len := r.ContentLength
   创建byte切片
   //body := make([]byte, len)
   将请求体中的内容读取到byte切片中
   //_, err = r.Body.Read(body)
   浏览器中显示请求体中的内容
   //_, err = fmt.Fprintln(w, "浏览器中显示请求体中的内容:",string(body))

   //在解析表单r.Form之前,需执行以下方法
   //err = r.ParseForm()
   如果form表单的action属性值的地址中也有与form表单参数名相同的请求参数,那么参数值都可以得到
   并且form表单中的参数值会在url参数值的前面
   //_, err = fmt.Fprintln(w, "请求表单请求参数:", r.Form)
   //_, err = fmt.Fprintln(w, "post请求中Form表单请求参数:", r.PostForm)


   //通过直接调用FormValue,PostFormValue直接获取请求参数中的值
   _, err = fmt.Fprintln(w, "Url中username请求参数的值:", r.FormValue("username"))
   _, err = fmt.Fprintln(w, "post请求中Form表单name请求参数:", r.PostFormValue("name"))
   if err != nil {
      fmt.Println(err)
   }
}

func main()  {
   http.HandleFunc("/req", handler)
   err := http.ListenAndServe(":8080", nil)
   if err != nil {
      fmt.Println(err)
   }
}
<html>
<head>
    <meta charset="UTF-8">
</head>
</head>
<body>
<form action="http://127.0.0.1:8080/req?username=张三" method="post">
    用户名:<input type="text" name="name"/><br/>
    用密码:<input type="text" name="password"/><br/>
    <input type="submit">
</form>
</body>
</html>

五.给客户端响应

前面讲解了如何使用处理器中的*http.Request处理用户的请求,下面说一下如何使用http.ResponseWriter来给用户响应        

1.给客户端响应一个字符串

(1).处理器中的代码

func handler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("你的请求服务端已收到"))
}

(2).浏览器中的结果

你的请求服务端已收到

(3).响应报文的内容

HTTP/1.1 200 OK

Date:Fri, 10 Aug 2022 01:01:01 GMT

Content-Length: 27

Content-Type: text/plain; charset=utf-8 

2.给客户端响应一个HTML页面 

(1).处理器中的代码

func handler(w http.ResponseWriter, r *http.Request) {
    html := `<html>
        <head>
            <title>测试响应内容为网页</title>    
            <meta charset="utf-8"/>
        </head>
        <body>
            以网页形式响应
        </body>
</html>`
    w.Write([]byte(html))
}

(2).浏览器中的结果

 以网页形式响应

通过在浏览器中右键->查看网页代码可以发现:有一个html页面

(3).响应报文中的内容

HTTP/1.1 200 OK

Date:Fri, 10 Aug 2022 01:01:01 GMT

Content-Length: 199

Content-Type: text/html; charset=utf-8 

3.给客户端响应JSON格式的数据

(1).处理器中的代码

func handler(w http.ResponseWriter, r *http.Request) {
    //设置响应头中的内容类型
    w.Header().Set("Content-Type", "application/json")
    user := User{
        ID: 1,
        Username: "张三",
        Password: "123456",    
    }
    //将user转换成JSON格式
    json, _ := json.Marshal(user)
    w.Write(json)
}

(2).浏览器中的结果

{"ID":1, "Username":"张三","Password":"123456"}

(3).响应报文中的内容

HTTP/1.1 200 OK

Date:Fri, 10 Aug 2022 01:01:01 GMT

Content-Length: 199

Content-Type: application/json 

4.让客户端重定向

(1).处理器中的代码

func handler(w http.ResponseWriter, r *http.Request) {
    //以下操作必须在WriteHeader之前进行
    w.Header().Set("Location", "https://www.baidu.com")
    w.WriteHeader(302)
}

(2).响应报文中的内容

HTTP/1.1 200 OK

Location: https://www.baidu.com

Date:Fri, 10 Aug 2022 01:01:01 GMT

Content-Length:  0

Content-Type: text/plain; charset=utf-8

以上案例完整代码如下:

package main

import (
   "encoding/json"
   "fmt"
   "go_code/web_app/sql/model"
   "net/http"
)

//创建处理器函数
func handler(w http.ResponseWriter, r *http.Request)  {
   _, err := fmt.Fprintln(w, "请求地址:", r.URL.Path)
   _, err = fmt.Fprintln(w, "请求地址中后面的查询字符串:", r.URL.RawQuery)
   _, err = fmt.Fprintln(w, "请求地址中请求头数据:", r.Header)//返回map[string][][string]
   _, err = fmt.Fprintln(w, "请求地址中请求头Accept-Encoding的信息:", r.Header["Accept-Encoding"])   //返回map切片
   _, err = fmt.Fprintln(w, "请求地址中请求头Accept-Encoding的属性值:", r.Header.Get("Accept-Encoding"))//返回string

   获取请求体中内容长度
   //len := r.ContentLength
   创建byte切片
   //body := make([]byte, len)
   将请求体中的内容读取到byte切片中
   //_, err = r.Body.Read(body)
   浏览器中显示请求体中的内容
   //_, err = fmt.Fprintln(w, "浏览器中显示请求体中的内容:",string(body))

   //在解析表单r.Form之前,需执行以下方法
   //err = r.ParseForm()
   如果form表单的action属性值的地址中也有与form表单参数名相同的请求参数,那么参数值都可以得到
   并且form表单中的参数值会在url参数值的前面
   //_, err = fmt.Fprintln(w, "请求表单请求参数:", r.Form)
   //_, err = fmt.Fprintln(w, "post请求中Form表单请求参数:", r.PostForm)
   //

   //通过直接调用FOrmValue,PostFormValue直接获取请求参数中的值
   _, err = fmt.Fprintln(w, "Url中username请求参数的值:", r.FormValue("username"))
   _, err = fmt.Fprintln(w, "post请求中Form表单name请求参数:", r.PostFormValue("name"))
   if err != nil {
      fmt.Println(err)
   }
}

func handlerJson(w http.ResponseWriter, r *http.Request)  {
   //设置响应内容头
   w.Header().Set("Content-Text", "application/json")
   //创建User
   user := &model.User{
      ID: 3,
      Name: "李四",
      Email: "lisi@qqlcom",
   }
   //转换成json格式
   data, _ := json.Marshal(user)
   //将json格式数据响应给客户端
   _, err := w.Write(data)
   if err != nil {
      fmt.Println(err)
   }
}


func handlerRedirect(w http.ResponseWriter, r *http.Request)  {
   //设置响应头中的Location
   w.Header().Set("Location", "https://www.baidu.com")
   //设置响应状态码
   w.WriteHeader(302)
}

func main()  {
   http.HandleFunc("/req", handler)
   http.HandleFunc("/res/json", handlerJson)
   http.HandleFunc("/res/redirect", handlerRedirect)
   err := http.ListenAndServe(":8080", nil)
   if err != nil {
      fmt.Println(err)
   }
}

[上一节][golang Web开发] 2.golang web开发:操作数据库,增删改查,单元测试

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

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

相关文章

linux系统加固

linux安全加固 linux系统安全包括用户安全、权限安全、文件安全 从那些方面进行加固 身份鉴别 访问控制 安全审计 资源控制 入侵防范 在linux当中一切皆文件 身份鉴别 /etc/login.defs 文件功能 查看密码策略 /etc/login.defs文件定义了与/etc/passwd和/etc/shadow配套的用户限…

VOIP创建

一、介绍VOIP的推送证书的创建方式和普通的证书的创建方式基本一致。二、步骤首先需要生成证书签名的请求文件CerSingingRequest&#xff0c;打开钥匙串应用&#xff0c;点击钥匙串访问->证书助理->从证书颁发机构请求输入电子邮件和名称后保存到本地3.在developer.apple…

服务器防火墙 配置端口号

作为前端的我头次做运维的事情。 现在服务器是的默认端口好像只有80&#xff0c;443&#xff0c;其余端口都需要我们配置 域ping 通了&#xff0c;以为服务配置好了可以撸起袖子加油干&#xff0c;但是 访问公司的服务 出错了400 访问公司的服务xxx.168.30.xxx:8081&#xff…

【字符串】leetcode344.反转字符串(C/C++/Java/Python/Js)

leetcode344.反转字符串1 题目2 思路3 代码3.1 C版本3.2 C版本3.3 Java版本3.4 Python版本3.5 JavaScript版本4 总结打基础的时候&#xff0c;不要太迷恋于库函数。 1 题目 题源链接 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给…

国内最新餐饮品牌全案设计十大排名(2023年榜单)

餐饮全案设计是餐饮店经营过程之前的重要一步&#xff0c;想要打造一家特色餐饮店并不是一件简单的事。一来要满足功能性需求的同时还具有一定的审美性&#xff1b;二来既要尽可能高地提升空间利用率让顾客消费体验愉悦和服务员工作效率高&#xff0c;能够展现餐厅的主题和文化…

分布式锁原理及Redis如何实现分布式锁

一淘模板给大家带来了关于redis的相关知识&#xff0c;其中主要介绍了关于分布式锁是什么&#xff1f;Redis又是怎么实现分布式锁的&#xff1f;需要满足什么条件&#xff1f;下面一起来看一下吧&#xff0c;希望对需要的朋友有帮助。 一、分布式锁基本原理 分布式锁&#xff…

PTA L1-027 出租(详解)

前言&#xff1a;本期是关于出租的详解&#xff0c;内容包括四大模块&#xff1a;题目&#xff0c;代码实现&#xff0c;大致思路&#xff0c;代码解读&#xff0c;今天你c了吗&#xff1f; 题目&#xff1a; 下面是新浪微博上曾经很火的一张图&#xff1a; 一时间网上一片求救…

【python学习笔记】:数据科学库操作(三)

接上一篇&#xff1a; 14、Pandas Pandas 是一个快速、强大、灵活且易于使用的开源数据分析和操作工具&#xff0c; Pandas 可以从各种文件格式比如 CSV、JSON、SQL、Microsoft Excel 导入数据&#xff0c;可以对各种数据进行运算操作&#xff0c;比如归并、再成形、选择&#…

SockJS-client简介

概述 SockJS是一个浏览器JavaScript库&#xff0c;提供了一个类似websocket的对象。SockJS为您提供了一个连贯的&#xff0c;跨浏览器的Javascript API&#xff0c;它在浏览器和web服务器之间创建了一个低延迟&#xff0c;全双工&#xff0c;跨域通信通道。 实际上&#xff0…

计算机网络入门(网络协议篇)

计算机网络分类虽然网络类型的划分标准各种各样&#xff0c;但是从地理范围划分是一种大家都认可的通用网络划分标准。按这种标准可以把各种网络类型划分为局域网、城域网、广域网三种。局域网一般来说只能是一个较小区域内&#xff0c;城域网是不同地区的网络互联&#xff0c;…

什么是进取心?如何提高进取心?

1、什么是进取心&#xff1f;进取心是一种心理状态&#xff0c;说的是积极上进&#xff0c;不断对自己提高要求&#xff0c;促使自己持续发展的心态。不论是学习还是工作&#xff0c;进取心都是我们获取成就的必备。从人的一生来说&#xff0c;进取心是我们探索人生最宝贵的修养…

ADB 开启 USB调试后,无法自动弹出调试授权窗口的解决方法

之前介绍了 Android Device Unauthorized 的解决方案&#xff0c;这次将分享 开启 USB调试后&#xff0c;无法自动弹出调试授权窗口的解决方法。即使选择在 “仅充电” 的情况下去调试&#xff0c;结果都一样。 在我自己的工程机 (荣耀系列的) 连上电脑后&#xff0c;USB 连接方…

《深入浅出计算机组成原理》学习笔记 Day18

冒险和预测&#xff08;二&#xff09;1. NOP 操作和指令对齐2. 操作数前推参考1. NOP 操作和指令对齐 MIPS 体系结构下的 R、I、J 三类指令&#xff1a; 五级流水线“取指令&#xff08;IF&#xff09;— 指令译码&#xff08;ID&#xff09;— 指令执行&#xff08;EX&…

linux_进程间通信 IPC

文章目录1、管道1.1、匿名管道1.2、有名管道2、信号3、共享内存3.1、共享内存接口3.1.1、生成 key 值3.1.2、创建共享内存3.1.3、创建共享内存映射3.1.4、解除共享内存映射3.1.5、修改共享内存属性3.2、例&#xff1a;共享内存4、信号量4.1、信号量的接口4.1.1、创建信号量4.1.…

第二章 RISC-V 指令集架构

前言 提醒&#xff1a;全文10千字&#xff0c;预计阅读时长15分钟&#xff1b;读者&#xff1a;对 RISC-V 架构感兴趣的小伙伴&#xff1b;目的&#xff1a;读者利用15~30 分钟对本文沉浸式阅读理解&#xff0c;能够掌握 RISC-V 架构 80% 的要点&#xff1b;关键词 &#xff1a…

Mysql 高级学习笔记

Mysql 高级学习笔记 文章目录Mysql 高级学习笔记一、Mysql 基础1. 聚合函数2. having3. sql 的执行顺序4. 约束5. 试图二、Mysql 高级1. MySQL中的SQL的执行流程2. 存储引擎介绍2. 索引3. 性能分析工具的使用4. 索引优化与查询优化5、关联查询优化6、事务及日志6、MVCC一、Mysq…

【C++】从0到1入门C++编程学习笔记 - 提高编程篇:STL常用容器(deque容器)

文章目录一、deque容器基本概念二、deque构造函数三、deque赋值操作四、deque 大小操作五、deque 插入和删除六、deque 数据存取七、deque 排序一、deque容器基本概念 功能&#xff1a; 双端数组&#xff0c;可以对头端进行插入删除操作 deque与vector区别&#xff1a; vec…

【Python】在代码中执行终端命令并获取输出和运行状态

文章目录0 前言1 os库1.1 os.system1.2 os.popen2 subprocess库2.1 subprocess.run2.2 subprocess.Popen3 参考链接0 前言 在Python编程过程中&#xff0c;我们可能会遇到需要在终端命令行执行某个命令并获取其输出的操作&#xff0c;我们首先想到可能就是C语言中的system(&quo…

字节青训营——分布式学习笔记

1. 分布式事务 满足ACID&#xff08;原子性、一致性、隔离性、持久性&#xff09;的一组操作&#xff0c;可以被称为一个事务。 同样的&#xff0c;分布式事务也部分遵循 ACID 规范&#xff1a; 原子性&#xff1a;严格遵循一致性&#xff1a;事务完成后的一致性严格遵循&am…