go

方法,函数,接口

闭包是一个非常有用的特性,它允许你在一个函数中定义另一个函数,这个内部函数会访问并操作外部函数中定义的变量。闭包的一个关键特性是即使外部函数已经返回,内部函数(闭包)依然可以访问和修改这些外部变量。

context

context 在 Go 中用于管理和传递请求的元数据、控制请求的生命周期(如取消和超时处理)

http server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

import (
"io"
"net/http"
)

func Ping(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "pong~")
}
//w http.ResponseWriter:这是一个接口,用于向请求发送响应。你可以通过这个接口写入响应体、设置响应状态码和响应头。
//r *http.Request:这个结构体包含了关于当前请求的所有信息,如请求URL、头部、参数等。

func main() {
http.HandleFunc("/ping", Ping)//处理器函数(如示例中的 handle 函数)与服务器的设置是通过服务器的 Handler 字段相关联的
http.ListenAndServe(":8080", nil)
//当你调用 server.ListenAndServe() 方法时,服务器开始在指定的端口监听新的 HTTP 请求。每当一个新的请求到达时,服务器就会使用你指定的 Handler(在这个例子中是 handle 函数)来处理这个请求。这意味着每个请求都会执行 handle 函数,输出相应的响应。
}


也可以新建一个http服务端
server := &http.Server{
Addr: ":8080", // 监听地址
Handler: http.HandlerFunc(handle), // 请求处理器***
ReadTimeout: 10 * time.Second, // 读超时
WriteTimeout: 10 * time.Second, // 写超时
MaxHeaderBytes: 1 << 20, // 最大头部大小,1MB
}
err := server.ListenAndServe()
if err != nil {
fmt.Println("Error starting server:", err)
}
// handle 定义了如何处理 HTTP 请求
func handle(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, you've requested: %s\n", r.URL.Path)
}

通过这种设计,Go 的 HTTP 服务器能够有效地处理并发连接和请求,每个连接都在自己的 goroutine 中处理,从而利用 Go 的并发模型。

并发

Goroutines 是 Go 中实现并发的基础。一个 Goroutine 可以看作是一个轻量级的线程,由 Go 运行时管理。Goroutines 比系统线程更加轻量,启动速度更快,占用的内存更少,切换开销也小得多。

1
go f(x, y, z)

这行代码会启动一个新的 Goroutine 去执行 f(x, y, z) 函数。这意味着 f 会异步执行,调用者可以继续执行下一行代码而不用等待 f 完成。

Channels 是用来在 Goroutines 之间进行通信的管道。你可以把它们想象成在不同的 Goroutines 之间传递消息的方式。Channels 是类型安全的,一个 int 类型的 channel 只能传递 int 类型的数据。

  • 创建 Channels:

    1
    ch := make(chan int)
  • 使用 Channels:

    1
    2
    ch <- v  // 发送 v 到 channel ch。
    v := <-ch // 从 ch 接收数据,并赋值给 v。

如果发送或接收操作未准备好,则操作会阻塞,直到另一端准备好为止。

  • 带缓冲的 Channels:

    1
    ch := make(chan int, 100)  // 创建一个容量为 100 的缓冲 channel。

带缓冲的 channel 在缓冲满之前可以持续发送,而不会阻塞。当缓冲空时,接收操作会阻塞。

Range and Close

你可以关闭一个 channel 来通知接收方不会再发送更多的数据。关闭后,接收操作仍然可以从 channel 接收数据,直到 channel 中的数据被接收完。

1
2
3
4
close(ch)
for i := range ch {
fmt.Println(i) // 从 ch 接收数据,直到 ch 被关闭。
}

Select

Select 语句让你可以等待多个 channel 操作,并继续执行一旦其中一个操作准备好。

1
2
3
4
5
6
7
select {
case c <- x:
// 发送 x 到 channel c。
case <-quit:
// 从 quit channel 接收数据。
return
}

如果所有 channel 操作都没有准备好,select 会阻塞。如果有 default 分支,则会执行 default

Mutex

互斥锁(Mutex)用于确保多个 Goroutine 在访问共享资源时不会发生冲突。sync.Mutex 提供了基本的锁定功能,帮助保护数据结构免受并发访问问题。

1
2
3
4
5
6
7
8
9
10
type SafeCounter struct {
mu sync.Mutex
v map[string]int
}

func (s *SafeCounter) Inc(key string) {
s.mu.Lock()
s.v[key]++
s.mu.Unlock()
}

在这个例子中,每次修改 v 的时候,我们都会使用 mu.Lock()mu.Unlock() 来确保只有一个 Goroutine 可以访问 v

异步

JavaScript 的执行环境确实是单线程的,这意味着在任何给定时刻,只能执行一个任务。然而,JavaScript 通过事件循环和回调机制支持异步操作,允许非阻塞的行为,例如处理 I/O、定时事件、执行网络请求等。这里是一些关键的概念和机制,解释 JavaScript 如何在单线程中实现异步操作:

1. 事件循环(Event Loop)

JavaScript 的事件循环是其异步行为的核心。事件循环使得 JavaScript 代码,即使是在单线程中,也能进行异步执行。事件循环工作原理如下:

  • JavaScript 运行时环境中有一个消息队列,其中包含了所有待处理的事件和执行回调的计划。
  • 当 JavaScript 引擎执行完同步代码后(比如你的脚本的初始加载),它将查看队列中是否有任何待处理的任务。
  • 如果队列中有待执行的异步任务,事件循环将一个接一个地取出来执行这些任务的回调
2.Web API 和回调队列

尽管 JavaScript 核心是单线程的,浏览器提供了多种 Web API,如 setTimeout, XMLHttpRequest, 和 DOM 事件等,这些都可以处理异步操作。这些操作在背后实际上可能由浏览器的多线程环境处理:

  • 当这些 API 完成异步操作时(例如,一个时间延迟、一个HTTP请求返回结果、或一个用户交互事件发生),相应的回调函数就会被放入任务队列中。
  • 一旦调用栈清空,事件循环将开始从队列中提取任务并执行它们。
3. Promise 和 Async/Await

JavaScript 的 Promise 是一个代表最终完成或失败的异步操作的对象。它允许你对异步操作的成功或失败进行链式调用处理。

  • Promise 提供 .then().catch() 方法来处理成功或失败的结果。

  • async/await 是建立在 Promises 之上的语法糖,使得异步代码的写法更加接近同步代码的风格。

  • Promise 对象是通过 new Promise() 构造函数创建的,它接受一个称为执行器(executor)函数的参数。这个执行器函数自动启动执行,并接受两个函数作为参数:resolvereject

    • resolve(value):当异步操作成功时调用,并将异步操作的结果作为参数传递。
    • reject(error):当异步操作失败时调用,并将错误作为参数传递。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const myPromise = new Promise((resolve, reject) => {
    const condition = true; // 假设这是异步操作的结果
    if (condition) {
    resolve('Promise is resolved successfully.');
    //resolve解析成果, JavaScript 的 Promise 中,一旦状态被解析(resolve)或拒绝(reject),它就被锁定,后续的任何解析或拒绝调用都将被忽略。

    } else {
    reject('Promise is rejected.');
    }
    });
    使用 Promise

    创建了 Promise 之后,你可以使用 .then(), .catch(), 和 .finally() 方法来处理成功或失败的结果。

    .then()

    .then() 方法接受两个函数作为参数(第二个参数是可选的):

    • 第一个函数是当 Promise 成功解决时执行的。
    • 第二个函数是当 Promise 被拒绝时执行的。
    1
    2
    3
    4
    myPromise.then(
    (value//resolve解析结果) => { console.log(value); }, // 成功处理函数
    (error) => { console.log(error); } // 失败处理函数
    );
    .catch()

    .catch() 方法用于指定当 Promise 被拒绝时执行的函数,是 .then(null, rejection) 的语法糖。

    1
    2
    3
    myPromise.catch(
    (error) => { console.log(error); }
    );
    .finally()

    .finally() 方法用于指定不管 Promise 最后的状态如何,都会执行的操作。这对于清理资源或执行一些完成操作很有用,无论操作成功还是失败。

    1
    2
    3
    myPromise.finally(() => {
    console.log('Completed'); // 总是会执行
    });
    链式调用

    Promise 允许链式调用,这意味着你可以在一个 Promise 解决后立即开始另一个异步操作,并根据前一个操作的结果继续处理。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    myPromise
    .then((value) => {
    console.log(value);
    return 'Doing another async operation';
    })
    .then((secondValue) => {
    console.log(secondValue);
    })
    .catch((error) => {
    console.log(error);
    })
    .finally(() => {
    console.log('Chain completed');
    });
    !异步/等待(async/await

    async/await 是基于 Promise 的一个语法糖,可以使异步代码看起来更像同步代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    async function asyncFunction() {
    try {
    const result = await myPromise;
    console.log(result);
    } catch (error) {
    console.log(error);
    } finally {
    console.log('Async function completed');
    }
    }

    asyncFunction();

    async 函数隐式返回一个 Promise,这使得异步函数可以使用 await 关键字来暂停执行,直到 Promise 解决或被拒绝。这种方式使得异步代码的编写更直观和易于管理。

image-20240904200941602

配置文件

不使用配置文件(硬编码)

在没有使用配置文件的情况下,所有配置(如数据库连接信息、端口号等)都会直接写在代码中。这种方式简单直接,但不易于维护,且存在安全风险。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"net/http"
)

func startServer() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
})
http.ListenAndServe(":8080", nil) // 端口号直接硬编码
}

func main() {
startServer()
}

在这个示例中,HTTP 服务器的端口号 8080 被直接硬编码在代码中。如果你需要更改端口或者这个端口在部署环境中已被占用,你必须修改代码并重新部署应用。

示例:使用配置文件

使用配置文件可以将配置从代码中分离出来,增加应用的灵活性并降低维护难度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import (
"fmt"
"github.com/spf13/viper"
"log"
"net/http"
)

func startServer(port int) {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
})
http.ListenAndServe(fmt.Sprintf(":%d", port), nil) // 使用配置文件中的端口
}

func main() {
viper.SetConfigName("config")
viper.AddConfigPath(".")
viper.SetConfigType("yaml")

if err := viper.ReadInConfig(); err != nil {
log.Fatalf("Error reading config file, %s", err)
}

port := viper.GetInt("server.port") // 从配置文件读取端口号

startServer(port)
}

配置文件 config.yaml 可能看起来像这样:

1
2
server:
port: 8080

router

后端对接前端,httpserver对接fetchAPI

json,在 Go 语言中,处理 JSON 数据主要依赖于 encoding/json 标准库。这个库通过反射(reflection)来匹配结构体的字段与 JSON 对象中的键。当涉及到使用 tags 和处理嵌套结构时,处理方式稍微复杂,但原则是相同的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
URL:{host}/comment/add
method: POST
[request]
body:
{
"name": "string", // 评论者名字
"content": "string" // 评论内容
}
[response]
// 新添加的评论
data:
{
"id": int, // 评论ID
"name": "string", // 评论者名字
"content": "string" // 评论内容
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
type Comment struct {
ID int `json:"id"`
Name string `json:"name"`
Content string `json:"content"`
}

var (
comments []Comment
idCounter int32
)

func addCommentHandler(w http.ResponseWriter, r *http.Request) {
err := json.NewDecoder(r.Body).Decode(&newComment)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
newComment.ID = int(atomic.AddInt32(&idCounter, 1))

comments = append(comments, newComment)

w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(newComment)
}

http.HandleFunc("/comment/add", addCommentHandler)
http.ListenAndServe(":8081", nil)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
document.getElementById('commentForm').addEventListener('submit', function(event) {
event.preventDefault();
const name = document.getElementById('name').value;
const content = document.getElementById('content').value;

fetch('http://localhost:8080/comment/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: name,
content: content
})
})
.then(response => response.json())
.then(data => {
console.log('Success:', data);
alert('Comment added successfully! Comment ID: ' + data.id);
})
.catch((error) => {
console.error('Error:', error);
});
});
1
page, err := strconv.Atoi(r.URL.Query().Get("page"))
1
fetch('/comment/get?page='+currentPage+'&size=10')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Response struct {
Total int `json:"total"`
Comments []Comment `json:"comments"`
}
var (
comments = []Comment{
{ID: 1, Name: "Alice", Content: "Have a try!"},
//{ID: 2, Name: "Bob", Content: "Thanks for sharing!"},
// 添加更多评论
}
commentsLock sync.Mutex
nextID = 2
)
response := Response{
Total: len(comments),
Comments: comments[start:end],
}

js对接html

1
2
3
4
5
<form id="commentForm">
<input type="text" id="name" placeholder="Your name" required>
<textarea id="content" placeholder="Your comment" required></textarea>
<button type="submit">Submit</button>
</form>