【go语言学习笔记】05 Go 语言实战

文章目录

    • 一、 RESTful API 服务
      • 1. RESTful API 定义
        • 1.1 HTTP Method
        • 1.2 RESTful API 规范
      • 2. RESTful API 风格示例
      • 3. RESTful JSON API
      • 4. Gin 框架
        • 4.1 导入 Gin 框架
        • 4.2 使用 Gin 框架
          • 4.2.1 获取特定的用户(GET)
          • 4.2.2 新增一个用户(POST)
          • 4.2.3 删除一个用户(DELETE)
          • 4.2.4 修改一个用户的名字(PATCH)
    • 二、 RPC 跨平台服务
      • 1. 定义
      • 2. Go语言中的RPC
        • 2.1 服务端
        • 2.2 客户端
      • 3. 基于 HTTP 的RPC
      • 3. JSON RPC 跨平台通信
        • 3.1 基于 TCP 的 JSON RPC
        • 3.2 基于 HTTP的JSON RPC
    • 三、Go 语言的发展前景

一、 RESTful API 服务

在做项目开发的时候,要善于借助已经有的轮子,让自己的开发更有效率,也更容易实现。

1. RESTful API 定义

RESTful API 是一套规范,它可以规范如何对服务器上的资源进行操作。和 RESTful API 和密不可分的是 HTTP Method。

1.1 HTTP Method

HTTP Method最常见的就是POST和GET,其实最早在 HTTP 0.9 版本中,只有一个GET方法,该方法是一个幂等方法,用于获取服务器上的资源。

在 HTTP 1.0 版本中又增加了HEAD和POST方法,其中常用的是 POST 方法,一般用于给服务端提交一个资源,导致服务器的资源发生变化。

随着网络越来越复杂,在 HTTP1.1 版本的时候,支持的方法增加到了 9 个,新增的方法有 HEAD、OPTIONS、PUT、DELETE、TRACE、PATCH 和 CONNECT。下面是它们各自的作用:

  1. GET 方法可请求一个指定资源的表示形式,使用 GET 的请求应该只被用于获取数据。
  2. HEAD 方法用于请求一个与 GET 请求的响应相同的响应,但没有响应体。
  3. POST 方法用于将实体提交到指定的资源,通常导致服务器上的状态变化或副作用。
  4. PUT 方法用于请求有效载荷替换目标资源的所有当前表示。
  5. DELETE 方法用于删除指定的资源。
  6. CONNECT 方法用于建立一个到由目标资源标识的服务器的隧道。
  7. OPTIONS 方法用于描述目标资源的通信选项。
  8. TRACE 方法用于沿着到目标资源的路径执行一个消息环回测试。
  9. PATCH 方法用于对资源应用部分修改。

HTTP 规范针对每个方法都给出了明确的定义,所以使用的时候也要尽可能地遵循这些定义,这样在开发中才可以更好地协作。

1.2 RESTful API 规范

RESTful API 规范就是基于 HTTP Method 规范对服务器资源的操作,同时规范了 URL 的样式和 HTTP Status Code。

在 RESTful API 中,使用的主要是以下五种 HTTP 方法:

  1. GET,表示读取服务器上的资源;
  2. POST,表示在服务器上创建资源;
  3. PUT,表示更新或者替换服务器上的资源;
  4. DELETE,表示删除服务器上的资源;
  5. PATCH,表示更新 / 修改资源的一部分。

以上 HTTP 方法在 RESTful API 规范中是一个操作,操作的就是服务器的资源,服务器的资源通过特定的 URL 表示。

(1)GET 方法的示例

HTTP GET https://www.flysnow.org/users
HTTP GET https://www.flysnow.org/users/123

上例中

  • 第一个表示获取所有用户的信息;
  • 第二个表示获取 ID 为 123 用户的信息。

(2) POST 方法的示例

HTTP POST https://www.flysnow.org/users

该示例表示创建一个用户,通过 POST 方法给服务器提供创建这个用户所需的全部信息。

这里 users 是个复数。

(3)PUT 方法的示例

HTTP PUT https://www.flysnow.org/users/123

该示例表示要更新 / 替换 ID 为 123 的这个用户,在更新的时候,会通过 PUT 方法提供更新这个用户需要的全部用户信息。这里 PUT 方法和 POST 方法不太一样的是,从 URL 上看,PUT 方法操作的是单个资源,比如这里 ID 为 123 的用户。

如果要更新一个用户的部分信息,使用 PATCH 方法更恰当。

(4)DELETE 方法的示例

HTTP DELETE https://www.flysnow.org/users/123

DELETE 方法的使用和 PUT 方法一样,也是操作单个资源,这里是删除 ID 为 123 的这个用户。

2. RESTful API 风格示例

Go 语言的一个很大的优势,就是可以很容易地开发出网络后台服务,而且性能快、效率高。Golang 提供了内置的 net/http 包处理 HTTP 请求,让开发者可以比较方便地开发一个 HTTP 服务。

一个简单的 HTTP 服务的 Go 语言实现代码如下所示:

func main() {http.HandleFunc("/users",handleUsers)http.ListenAndServe(":8080", nil)
}
func handleUsers(w http.ResponseWriter, r *http.Request){fmt.Fprintln(w,"ID:1,Name:张三")fmt.Fprintln(w,"ID:2,Name:李四")fmt.Fprintln(w,"ID:3,Name:王五")
}

这个示例运行后,在浏览器中输入 http://localhost:8080/users, 就可以看到如下内容信息:

ID:1,Name:张三
ID:2,Name:李四
ID:3,Name:王五

这并不是一个 RESTful API,因为使用者不仅可以通过 HTTP GET 方法获得所有的用户信息,还可以通过 POST、DELETE、PUT 等 HTTP 方法获得所有的用户信息,这显然不符合 RESTful API 的规范。

对以上示例进行修改,使它符合 RESTful API 的规范,修改后的示例代码如下所示:

func handleUsers(w http.ResponseWriter, r *http.Request){switch r.Method {case "GET":w.WriteHeader(http.StatusOK)fmt.Fprintln(w,"ID:1,Name:张三")fmt.Fprintln(w,"ID:2,Name:李四")fmt.Fprintln(w,"ID:3,Name:王五")default:w.WriteHeader(http.StatusNotFound)fmt.Fprintln(w,"not found")}
}

该示例修改了 handleUsers 函数,在该函数中增加了只在使用 GET 方法时,才获得所有用户的信息的判断,其他情况返回 not found。

3. RESTful JSON API

在项目中最常见的是使用 JSON 格式传输信息,也就是提供的 RESTful API 要返回 JSON 内容给使用者。

用上面的示例改造成可以返回 JSON 内容的方式,示例代码如下所示:

//数据源,类似MySQL中的数据
var users = []User{{ID: 1,Name: "张三"},{ID: 2,Name: "李四"},{ID: 3,Name: "王五"},
}
func handleUsers(w http.ResponseWriter, r *http.Request){switch r.Method {case "GET":users,err:=json.Marshal(users)if err!=nil {w.WriteHeader(http.StatusInternalServerError)fmt.Fprint(w,"{\"message\": \""+err.Error()+"\"}")}else {w.WriteHeader(http.StatusOK)w.Write(users)}default:w.WriteHeader(http.StatusNotFound)fmt.Fprint(w,"{\"message\": \"not found\"}")}
}
//用户
type User struct {ID intName string
}

从以上代码可以看到,这次的改造主要是新建了一个 User 结构体,并且使用 users 这个切片存储所有的用户,然后在 handleUsers 函数中把它转化为一个 JSON 数组返回。这样,就实现了基于 JSON 数据格式的 RESTful API。

运行这个示例,在浏览器中输入 http://localhost:8080/users,可以看到如下信息:

[{"ID":1,"Name":"张三"},{"ID":2,"Name":"李四"},{"ID":3,"Name":"王五"}]

4. Gin 框架

虽然 Go 语言自带的 net/http 包,可以比较容易地创建 HTTP 服务,但是它也有很多不足:

  • 不能单独地对请求方法(POST、GET 等)注册特定的处理函数;
  • 不支持 Path 变量参数;
  • 不能自动对 Path 进行校准;
  • 性能一般;
  • 扩展性不足;
  • ……

基于以上这些不足,出现了很多 Golang Web 框架,如 Mux,Gin、Fiber 等,其中使用最多是 Gin 框架。

4.1 导入 Gin 框架

Gin 框架是一个在 Github 上开源的 Web 框架,封装了很多 Web 开发需要的通用功能,并且性能也非常高,可以很容易地写出 RESTful API。

Gin 框架其实是一个模块,也就是 Go Mod,所以采用 Go Mod 的方法引入即可。

安装代码如下:

$ go get -u github.com/gin-gonic/gin

导入代码如下:

import "github.com/gin-gonic/gin"

4.2 使用 Gin 框架

用 Gin 框架重写上面的示例,修改的代码如下所示:

func main() {r:=gin.Default()r.GET("/users", listUser)r.Run(":8080")
}
func listUser(c *gin.Context)  {c.JSON(200,users)
}

相比 net/http 包,Gin 框架的代码非常简单,通过它的 GET 方法就可以创建一个只处理 HTTP GET 方法的服务,而且输出 JSON 格式的数据也非常简单,使用 c.JSON 方法即可。

最后通过 Run 方法启动 HTTP 服务,监听在 8080 端口。运行这个示例,在浏览器中输入 http://localhost:8080/users,看到的信息和通过 net/http 包实现的效果是一样的。

4.2.1 获取特定的用户(GET)

如果要获得特定用户的信息,需要使用的是 GET 方法,并且 URL 格式如下所示:

http://localhost:8080/users/2

以上示例中的 2 是用户的 ID,也就是通过 ID 来获取特定的用户。

通过 Gin 框架 Path 路径参数可以实现这个功能,示例代码如下:

func main() {//省略没有改动的代码r.GET("/users/:id", getUser)
}
func getUser(c *gin.Context) {id := c.Param("id")var user Userfound := false//类似于数据库的SQL查询for _, u := range users {if strings.EqualFold(id, strconv.Itoa(u.ID)) {user = ufound = truebreak}}if found {c.JSON(200, user)} else {c.JSON(404, gin.H{"message": "用户不存在",})}
}

在 Gin 框架中,路径中使用冒号表示 Path 路径参数,比如示例中的 :id,然后在 getUser 函数中可以通过 c.Param(“id”) 获取需要查询用户的 ID 值。

Param 方法的参数要和 Path 路径参数中的一致,比如示例中都是 ID。

运行这个示例,通过浏览器访问 http://localhost:8080/users/2,就可以获得 ID 为 2 的用户,输出信息如下所示:

{"ID":2,"Name":"李四"}

假如我们访问一个不存在的 ID,得到的结果如下所示:

curl http://localhost:8080/users/99
{"message":"用户不存在"}%
4.2.2 新增一个用户(POST)

根据 RESTful API 规范,实现新增使用的是 POST 方法,并且 URL 的格式为 http://localhost:8080/users ,向这个 URL 发送数据,就可以新增一个用户,然后返回创建的用户信息。

使用 Gin 框架实现新增一个用户,示例代码如下:

func main() {//省略没有改动的代码r.POST("/users", createUser)
}
func createUser(c *gin.Context) {name := c.DefaultPostForm("name", "")if name != "" {u := User{ID: len(users) + 1, Name: name}users = append(users, u)c.JSON(http.StatusCreated,u)} else {c.JSON(http.StatusOK, gin.H{"message": "请输入用户名称",})}
}

以上新增用户的主要逻辑是获取客户端上传的 name 值,然后生成一个 User 用户,最后把它存储到 users 集合中,达到新增用户的目的。

在这个示例中,使用 POST 方法来新增用户,所以只能通过 POST 方法才能新增用户成功。

运行这个示例,通过如下命令发送一个新增用户的请求,查看结果:

curl -X POST -d 'name=小明' http://localhost:8080/users
{"ID":4,"Name":"小明"}
4.2.3 删除一个用户(DELETE)

删除一个用户比较简单,它的 API 格式和获取一个用户一样,但是 HTTP 方法换成了DELETE。示例代码如下所示:

func main() {//省略没有修改的代码r.DELETE("/users/:id", deleteUser)
}
func deleteUser(c *gin.Context) {id := c.Param("id")i := -1//类似于数据库的SQL查询for index, u := range users {if strings.EqualFold(id, strconv.Itoa(u.ID)) {i = indexbreak}}if i >= 0 {users = append(users[:i], users[i+1:]...)c.JSON(http.StatusNoContent, "")} else {c.JSON(http.StatusNotFound, gin.H{"message": "用户不存在",})}
}

这个示例的逻辑就是注册 DELETE 方法,达到删除用户的目的。删除用户的逻辑是通过ID 查询:

  • 如果可以找到要删除的用户,记录索引并跳出循环,然后根据索引删除该用户;
  • 如果找不到要删除的用户,则返回 404。
4.2.4 修改一个用户的名字(PATCH)

修改和删除一个用户非常像,实现代码如下所示:

func main() {//省略没有修改的代码r.PATCH("/users/:id",updateUserName)
}
func updateUserName(c *gin.Context) {id := c.Param("id")i := -1//类似于数据库的SQL查询for index, u := range users {if strings.EqualFold(id, strconv.Itoa(u.ID)) {i = indexbreak}}if i >= 0 {users[i].Name = c.DefaultPostForm("name",users[i].Name)c.JSON(http.StatusOK, users[i])} else {c.JSON(http.StatusNotFound, gin.H{"message": "用户不存在",})}
}

逻辑和删除的差不多的,只不过这里使用的是 PATCH方法。

二、 RPC 跨平台服务

1. 定义

RPC,也就是远程过程调用,是分布式系统中不同节点调用的方式(进程间通信),属于 C/S 模式。RPC 由客户端发起,调用服务端的方法进行通信,然后服务端把结果返回给客户端。

RPC的核心有两个:通信协议序列化。在 HTTP 2 之前,一般采用自定义 TCP 协议的方式进行通信,HTTP 2 出来后,也有采用该协议的,比如流行的gRPC。

序列化反序列化是一种把传输内容编码和解码的方式,常见的编解码方式有 JSON、Protobuf 等。

在大多数 RPC的架构设计中,都有ClientClient StubServerServer Stub这四个组件,Client 和 Server 之间通过 Socket 进行通信。RPC 架构如下图所示:
在这里插入图片描述
RPC 调用的流程:

  1. 客户端(Client)调用客户端存根(Client Stub),同时把参数传给客户端存根;
  2. 客户端存根将参数打包编码,并通过系统调用发送到服务端;
  3. 客户端本地系统发送信息到服务器;
  4. 服务器系统将信息发送到服务端存根(Server Stub);
  5. 服务端存根解析信息,也就是解码;
  6. 服务端存根调用真正的服务端程序(Sever);
  7. 服务端(Server)处理后,通过同样的方式,把结果再返回给客户端(Client)。

RPC 调用常用于大型项目,也就是常说的微服务,而且还会包含服务注册、治理、监控等功能,是一套完整的体系。

2. Go语言中的RPC

在 Go SDK 中,已经内置了 net/rpc 包来帮助开发者实现 RPC。简单来说,net/rpc 包提供了通过网络访问服务端对象方法的能力。

在实际的项目开发中,使用Go 语言自带的 RPC 框架并不多,比较常用的是Google的gRPC 框架,它是通过Protobuf 序列化的,是基于 HTTP/2 协议的二进制传输,并且支持很多编程语言,效率也比较高。

2.1 服务端

一个 RPC 示例的服务端代码如下所示:

package server
type MathService struct {
}
type Args struct {A, B int
}
func (m *MathService) Add(args Args, reply *int) error {*reply = args.A + args.Breturn nil
}

在以上代码中:

  • 定义了MathService,用于表示一个远程服务对象;
  • Args 结构体用于表示参数;
  • Add 这个方法实现了加法的功能,加法的结果通过 replay这个指针变量返回。

定义好服务对象就可以把它注册到暴露的服务列表中,以供其他客户端使用了。在Go 语言中,要注册一个RPC 服务对象可以通过 RegisterName 方法,示例代码如下所示:

package main
import ("server""log""net""net/rpc"
)
func main()  {rpc.RegisterName("MathService",new(server.MathService))l, e := net.Listen("tcp", ":1234")if e != nil {log.Fatal("listen error:", e)}rpc.Accept(l)
}

以上示例代码中,通过 RegisterName 函数注册了一个服务对象,该函数接收两个参数:

  1. 服务名称(MathService);
  2. 具体的服务对象,也就是刚刚定义好的MathService 这个结构体。

然后通过 net.Listen 函数建立一个TCP 链接,在 1234 端口进行监听,最后通过 rpc.Accept 函数在该 TCP 链接上提供 MathService 这个 RPC 服务。现在客户端就可以看到MathService这个服务以及它的Add 方法了。

在 net/rpc 这个RPC框架时,要想把一个对象注册为 RPC 服务,可以让客户端远程访问,那么该对象(类型)的方法必须满足如下条件:

  • 方法的类型是可导出的(公开的);
  • 方法本身也是可导出的;
  • 方法必须有 2 个参数,并且参数类型是可导出或者内建的;
  • 方法必须返回一个 error 类型。

总结来说该方法的格式如下所示:

func (t *T) MethodName(argType T1, replyType *T2) error

这里面的 T1、T2都是可以被 encoding/gob 序列化的。

  • 第一个参数 argType 是调用者(客户端)提供的;
  • 第二个参数 replyType是返回给调用者结果,必须是指针类型。

2.2 客户端

代码如下所示:

package main
import ("fmt""server""log""net/rpc"
)
func main()  {client, err := rpc.Dial("tcp",  "localhost:1234")if err != nil {log.Fatal("dialing:", err)}args := server.Args{A:7,B:8}var reply interr = client.Call("MathService.Add", args, &reply)if err != nil {log.Fatal("MathService.Add error:", err)}fmt.Printf("MathService.Add: %d+%d=%d", args.A, args.B, reply)
}

在以上实例代码中,首先通过 rpc.Dial 函数建立 TCP 链接。TCP 链接建立成功后,就需要准备远程方法需要的参数,也就是示例中的args 和 reply。参数准备好之后,就可以通过 Call 方法调用远程的RPC 服务了。Call 方法有 3 个参数,它们的作用分别如下所示:

  1. 调用的远程方法的名字,这里是MathService.Add,点前面的部分是注册的服务的名称,点后面的部分是该服务的方法;
  2. 客户端为了调用远程方法提供的参数,示例中是args;
  3. 为了接收远程方法返回的结果,必须是一个指针,也就是示例中的 &reply,这样客户端就可以获得服务端返回的结果了。

3. 基于 HTTP 的RPC

RPC 除了可以通过 TCP 协议调用之外,还可以通过HTTP 协议进行调用,而且内置的net/rpc 包已经支持,修改以上示例代码支持 HTTP 协议的调用,服务端代码如下所示:

func main() {rpc.RegisterName("MathService", new(server.MathService))rpc.HandleHTTP()//新增的l, e := net.Listen("tcp", ":1234")if e != nil {log.Fatal("listen error:", e)}http.Serve(l, nil)//换成http的服务
}

客户端修改的代码如下所示:

func main()  {client, err := rpc.DialHTTP("tcp",  "localhost:1234")//省略了其他没有修改的代码
}

可以看到,只需要把建立链接的方法从 Dial 换成 DialHTTP 即可。

Go 语言 net/rpc 包提供的 HTTP 协议的 RPC 还有一个调试的 URL,运行服务端代码后,在浏览器中输入 http://localhost:1234/debug/rpc 回车,即可看到服务端注册的RPC 服务,以及每个服务的方法,如下图所示:
在这里插入图片描述
如上图所示,注册的 RPC 服务、方法的签名、已经被调用的次数都可以看到。

3. JSON RPC 跨平台通信

以上实现的RPC 服务是基于 gob 编码的,这种编码在跨语言调用的时候比较困难,当前在微服务架构中,RPC 服务的实现者和调用者都可能是不同的编程语言,因此实现的 RPC 服务要支持多语言的调用。

3.1 基于 TCP 的 JSON RPC

实现跨语言 RPC 服务的核心在于选择一个通用的编码,这样大多数语言都支持,比如常用的JSON。在 Go 语言中,实现一个 JSON RPC 服务非常简单,只需要使用 net/rpc/jsonrpc 包即可。

以上面的示例为例,改造成支持 JSON的RPC 服务,服务端代码如下所示:

func main() {rpc.RegisterName("MathService", new(server.MathService))l, e := net.Listen("tcp", ":1234")if e != nil {log.Fatal("listen error:", e)}for {conn, err := l.Accept()if err != nil {log.Println("jsonrpc.Serve: accept:", err.Error())return}//json rpcgo jsonrpc.ServeConn(conn)}
}

从以上代码可以看到,相比 gob 编码的RPC 服务,JSON 的 RPC 服务是把链接交给了jsonrpc.ServeConn这个函数处理,达到了基于 JSON 进行 RPC 调用的目的。

JSON RPC 的客户端代码修改的部分如下所示:

func main()  {client, err := jsonrpc.Dial("tcp",  "localhost:1234")//省略了其他没有修改的代码
}

从以上代码可以看到,只需要把建立链接的 Dial方法换成 jsonrpc 包中的即可。

3.2 基于 HTTP的JSON RPC

Go 语言内置的jsonrpc 并没有实现基于 HTTP的传输,需要自己实现,这里参考 gob 编码的HTTP RPC 实现方式,来实现基于 HTTP的JSON RPC 服务。

RPC 服务端代码如下所示:

func main() {rpc.RegisterName("MathService", new(server.MathService))//注册一个path,用于提供基于http的json rpc服务http.HandleFunc(rpc.DefaultRPCPath, func(rw http.ResponseWriter, r *http.Request) {conn, _, err := rw.(http.Hijacker).Hijack()if err != nil {log.Print("rpc hijacking ", r.RemoteAddr, ": ", err.Error())return}var connected = "200 Connected to JSON RPC"io.WriteString(conn, "HTTP/1.0 "+connected+"\n\n")jsonrpc.ServeConn(conn)})l, e := net.Listen("tcp", ":1234")if e != nil {log.Fatal("listen error:", e)}http.Serve(l, nil)//换成http的服务
}

以上代码的实现基于 HTTP 协议的核心,即使用 http.HandleFunc 注册了一个 path,对外提供基于 HTTP 的 JSON RPC 服务。在这个 HTTP 服务的实现中,通过Hijack方法劫持链接,然后转交给 jsonrpc 处理,这样就实现了基于 HTTP 协议的 JSON RPC 服务。

客户端调用的代码如下所示:

func main()  {client, err := DialHTTP("tcp",  "localhost:1234")if err != nil {log.Fatal("dialing:", err)}args := server.Args{A:7,B:8}var reply interr = client.Call("MathService.Add", args, &reply)if err != nil {log.Fatal("MathService.Add error:", err)}fmt.Printf("MathService.Add: %d+%d=%d", args.A, args.B, reply)}// DialHTTP connects to an HTTP RPC server at the specified network address// listening on the default HTTP RPC path.func DialHTTP(network, address string) (*rpc.Client, error) {return DialHTTPPath(network, address, rpc.DefaultRPCPath)}// DialHTTPPath connects to an HTTP RPC server// at the specified network address and path.func DialHTTPPath(network, address, path string) (*rpc.Client, error) {var err errorconn, err := net.Dial(network, address)if err != nil {return nil, err}io.WriteString(conn, "GET "+path+" HTTP/1.0\n\n")// Require successful HTTP response// before switching to RPC protocol.resp, err := http.ReadResponse(bufio.NewReader(conn), &http.Request{Method: "GET"})connected := "200 Connected to JSON RPC"if err == nil && resp.Status == connected {return jsonrpc.NewClient(conn), nil}if err == nil {err = errors.New("unexpected HTTP response: " + resp.Status)}conn.Close()return nil, &net.OpError{Op:   "dial-http",Net:  network + " " + address,Addr: nil,Err:  err,}}

以上这段代码的核心在于通过建立好的TCP 链接,发送 HTTP 请求调用远程的HTTP JSON RPC 服务,这里使用的是 HTTP GET 方法。

三、Go 语言的发展前景

Go 语言就是为云而生的编程语言,所以在云原生的时代,它就具备了天生的优势:易于学习、天然的并发、高效的网络支持、跨平台的二进制文件编译等。

CNCF(云原生计算基金会)对云原生的定义是:

  • 应用容器化;
  • 面向微服务架构;
  • 应用支持容器的编排调度。

对于这三点有代表性的 Docker、K8s 以及 istio 都是采用 Go 语言编写的,所以 Go 语言在云原生中发挥了极大的优势。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

手机app测试

一、安装、卸载、更新、运行 1.安装、卸载 应用是否可以正常安装(命令行安装;apk/ipa安装包安装)(有网,无网是否都正常)卸载过程中出现死机,断电,重启等意外的情况&…

加载并绘制时间域内的心电图信号,并实施Q因子为1的陷波滤波器以去除50 Hz频率研究(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

「量化」快乐:UC Berkeley 利用 AI 追踪多巴胺释放量及释放脑区

内容一览:多巴胺是神经系统中重要的神经递质,与运动、记忆和奖赏系统息息相关,它是快乐的信使,当我们看到令人愉悦的东西时,体内就会分泌多巴胺,诱导我们向它追寻。然而,多巴胺的准确定量分析目…

直接在html中引入Vue.js的cdn来实现一个简单的博客

摘要 其实建立一个博客系统是非常简单的&#xff0c;有很多开源的程序&#xff0c;如果你不喜欢博客系统&#xff0c;也可以自己开发&#xff0c;也可以自己简单做一个。我这次就是用Vue.js和php做后端服务实现一个简单的博客。 界面 代码结构 代码 index.html <!DOCTYP…

2009年下半年 软件设计师 上午试卷

博主介绍&#xff1a;✌全网粉丝3W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

JavaScript应用:五子棋游戏实战开发

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌&#xff0c;CSDN博客专家&#xff0c;阿里云社区专家博主&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;数年电商行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责…

OpenLayers实战,高德GCJ-02坐标系转WGS-84坐标系

专栏目录: OpenLayers实战进阶专栏目录 前言 本章实现高德GCJ-02坐标系转WGS-84坐标系。日常开发中经常遇到源坐标高德的情况,这时候如果地图不是高德,而是使用的wgs84坐标系的地图,或者其他坐标系的情况下,就会导致位置偏移,本章就是解决高德坐标偏移问题。 二、依赖…

Mac下⬇️Git如何下载/上传远程仓库

使用终端检查电脑是否安装Git git --version 通过此文章安装Git ➡️ ​​​​​​​传送门&#x1f310; 方式1⃣️使用终端操作 1.下载——克隆远程仓库到本地 git clone [远程地址] 例&#xff1a;git clone https://gitee.com/lcannal/movie.git​ 2.编…

【MySQL】MySQL不走索引的情况分析

未建立索引 当数据表没有设计相关索引时&#xff0c;查询会扫描全表。 create table test_temp (test_id int auto_incrementprimary key,field_1 varchar(20) null,field_2 varchar(20) null,field_3 bigint null,create_date date null );expl…

好用的免费音频转换器大揭秘

你是否曾经遇到过这样的情况&#xff1a;你有一首喜欢的歌曲或者音频文件&#xff0c;但是你的播放器或设备不支持该文件格式&#xff1f;这时候&#xff0c;你需要一款好用的音频格式转换器来帮助你。说到这&#xff0c;你可能会问&#xff0c;“我都不知道免费的音频格式转换…

案例15 Spring Boot入门案例

1. 选择Spring Initializr快速构建项目 ​ 2. 设置项目信息 ​ 3. 选择依赖 ​ 4. 设置项目名称 ​ 5. 项目结构 ​ 6. 项目依赖 自动配置了Spring MVC、内置了Tomcat、配置了Logback(日志)、配置了JSON。 ​ 7. 创建HelloController类 com.wfit.boot.hello目录下创建HelloCo…

博客网站添加复制转载提醒弹窗Html代码

网站如果是完全禁止右键&#xff08;复制、另存为等&#xff09;操作&#xff0c;对用户来说体验感会降低&#xff0c;但是又不希望自己的原创内容直接被copy&#xff0c;今天飞飞和你们分享几行复制转载提醒弹窗Html代码。 效果展示&#xff1a; 复制以下代码&#xff0c;将其…