Go Zero入门:新手必读指南
2024/10/17 23:08:36
本文主要是介绍Go Zero入门:新手必读指南,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
Go Zero 是一个基于 Go 语言的服务开发框架,旨在简化微服务的构建和运维,提供了服务发现、负载均衡、健康检查等丰富功能。本文将详细介绍 Go Zero 的安装方法、项目结构、基本概念以及如何使用 Go Zero 创建和运行简单的 HTTP 服务。通过本文,读者可以全面了解并掌握 Go Zero 入门所需的知识和技能。
Go Zero 是一个基于 Go 语言的服务开发框架,专注于微服务的构建,旨在简化服务的开发、部署和运维。它提供了丰富的功能,包括服务发现、负载均衡、健康检查、日志与监控等。Go Zero 可以帮助开发者快速构建高性能的微服务应用。
Go Zero 的设计理念是简单易用,它提供了一系列工具和库来帮助开发者处理各种常见任务,例如创建 HTTP 服务、处理数据库操作、缓存数据等。此外,Go Zero 还支持多种数据库适配器和缓存适配器,使得开发者可以灵活地选择适合自己的技术栈。
Go Zero 是基于 Go 语言开发的,因此要安装 Go Zero,首先需要安装 Go 语言环境。
安装 Go 语言环境
-
下载 Go 安装包
访问 Go 官方网站(https://golang.org/dl/)下载最新版本的 Go 安装包。 -
安装 Go
根据下载的平台(Windows, macOS, Linux)进行安装。例如,在 Linux 上可以使用以下命令安装:sudo tar -xvf go1.17.6.linux-amd64.tar.gz -C /usr/local
-
设置环境变量
需要将 Go 的安装路径添加到系统环境变量中。修改.bashrc
或.zshrc
文件,添加以下内容:export PATH=$PATH:/usr/local/go/bin export GOPATH=$HOME/go export PATH=$PATH:$GOPATH/bin
-
验证安装
安装完成后,通过以下命令验证 Go 安装是否成功:go version
安装 Go Zero
安装 Go Zero 可以通过 Go 的 go get
命令来完成:
go get -u github.com/zeromicro/go-zero
安装完成后,可以通过创建项目来使用 Go Zero。
项目结构
一个 Go Zero 项目通常包含以下几个主要部分:
- main.go:主入口文件,包含服务启动逻辑。
- config:配置文件目录,通常包含环境变量配置文件(如
env
、test.env
)。 - cmd:命令目录,用于存放自定义命令。
- internal:内部目录,用于存放自定义的服务逻辑代码。
- proxy:代理目录,用于存放服务代理代码。
- router:路由目录,用于存放路由配置。
- service:服务目录,用于存放服务模块。
- test:测试目录,用于存放测试代码。
配置文件
Go Zero 通常使用 YAML 格式的配置文件来定义服务的各种配置。例如,一个简单的 HTTP 服务配置文件 http-config.yaml
可能包含如下内容:
type: http name: app host: 0.0.0.0 port: 8080 router: - name: hello path: /hello handler: handler.Hello
路由配置
路由配置定义了服务对外提供的 API 接口。例如,上述配置文件中的 router
部分定义了一个名为 hello
的路由,该路由对应于 /hello
路径,并将请求转发给 handler.Hello
处理函数。
服务发现
服务发现是指在分布式系统中自动发现和注册服务的过程。Go Zero 内置了服务发现支持,可以通过配置文件指定服务注册中心(如 Etcd)。
日志与监控
Go Zero 提供了丰富的日志和监控功能,可以方便地集成第三方监控工具(如 Prometheus)。
其他术语
- Handler:处理 HTTP 请求的具体函数。
- Service:服务模块,通常包含多个处理函数。
- Env:环境变量配置。
- HealthCheck:服务健康检查。
创建一个 Go Zero 项目需要以下几个步骤:
-
初始化项目目录结构:
使用模板创建项目目录结构,例如使用go mod init
命令创建一个新的 Go 模块,并初始化项目目录。go mod init gozero-example mkdir -p internal/handler mkdir -p cmd/gozero-example
-
编写主入口文件:
在cmd/gozero-example/main.go
中编写启动服务的代码。package main import ( "github.com/zeromicro/go-zero/core/conf" "github.com/zeromicro/go-zero/core/service" "github.com/zeromicro/go-zero/core/service/cfg" "github.com/zeromicro/go-zero/core/service/http" "github.com/zeromicro/go-zero/core/service/startup" "github.com/zeromicro/go-zero/core/service/starter" "github.com/zeromicro/go-zero/core/service/http/handler" _ "github.com/zeromicro/go-zero/core/service/http/handler/hello" ) func main() { cfg, err := conf.LoadPath("etc") if err != nil { panic(err) } srv := &http.Server{ Protocol: "http", Host: "0.0.0.0", Port: 8080, Handler: handler.New(), Router: "etc/router", Recovery: true, PProf: true, DisableLog: false, StackTrace: false, Timeout: 30, MaxHeaderSize: 1024, IdleTimeout: 30, } service.Serve(startup.Default, srv, cfg) }
-
编写处理函数:
创建一个处理函数,例如在internal/handler/hello/hello.go
中编写一个简单的 Hello World 处理函数。package hello import ( "net/http" ) type HelloHandler struct{} func (h *HelloHandler) Hello(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello, World!")) }
-
配置路由:
在etc/router.yaml
中配置路由。type: http name: app host: 0.0.0.0 port: 8080 router: - name: hello path: /hello handler: hello.Hello
创建处理函数
在 internal/handler/hello/hello.go
文件中已经创建了一个简单的 HelloHandler
处理函数:
package hello import ( "net/http" ) type HelloHandler struct{} func (h *HelloHandler) Hello(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello, World!")) }
配置路由
在 etc/router.yaml
文件中定义了路由:
type: http name: app host: 0.0.0.0 port: 8080 router: - name: hello path: /hello handler: hello.Hello
运行服务
在项目根目录下,可以通过以下命令启动服务:
go run cmd/gozero-example/main.go
测试服务
服务启动后,可以通过浏览器或命令行工具(如 curl
)访问服务:
curl http://localhost:8080/hello
输出结果应该为:
Hello, World!
创建服务
Go Zero 中的服务通过配置文件定义。服务配置文件通常位于 etc
目录下,例如 http-config.yaml
文件可以定义一个 HTTP 服务。
路由配置
路由配置定义了服务对外提供的 API 接口。例如,可以在 etc/router.yaml
中定义一个路由:
type: http name: app host: 0.0.0.0 port: 8080 router: - name: hello path: /hello handler: handler.Hello
path
指定了路由路径,handler
指定了处理该路径请求的处理函数。
示例代码
假设有一个处理函数 internal/handler/hello/hello.go
:
package hello import ( "net/http" ) type HelloHandler struct{} func (h *HelloHandler) Hello(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello, World!")) }
在路由配置文件中,定义如下:
type: http name: app host: 0.0.0.0 port: 8080 router: - name: hello path: /hello handler: hello.Hello
启动服务
在 main.go
中启动服务:
package main import ( "github.com/zeromicro/go-zero/core/conf" "github.com/zeromicro/go-zero/core/service" "github.com/zeromicro/go-zero/core/service/cfg" "github.com/zeromicro/go-zero/core/service/http" "github.com/zeromicro/go-zero/core/service/startup" "github.com/zeromicro/go-zero/core/service/starter" "github.com/zeromicro/go-zero/core/service/http/handler" _ "github.com/zeromicro/go-zero/core/service/http/handler/hello" ) func main() { cfg, err := conf.LoadPath("etc") if err != nil { panic(err) } srv := &http.Server{ Protocol: "http", Host: "0.0.0.0", Port: 8080, Handler: handler.New(), Router: "etc/router", Recovery: true, PProf: true, DisableLog: false, StackTrace: false, Timeout: 30, MaxHeaderSize: 1024, IdleTimeout: 30, } service.Serve(startup.Default, srv, cfg) }
创建处理函数
处理函数定义了如何处理 HTTP 请求。例如,可以创建一个处理 GET 请求的函数:
package handler import ( "net/http" ) type Handler struct{} func (h *Handler) Hello(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello, World!")) } func (h *Handler) HelloPost(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello, POST request!")) }
路由配置
在路由配置文件中,为不同的 HTTP 方法定义路由:
type: http name: app host: 0.0.0.0 port: 8080 router: - name: hello path: /hello handler: handler.Hello - name: helloPost path: /hello method: POST handler: handler.HelloPost
示例代码
在 main.go
中启动服务:
package main import ( "github.com/zeromicro/go-zero/core/conf" "github.com/zeromicro/go-zero/core/service" "github.com/zeromicro/go-zero/core/service/cfg" "github.com/zeromicro/go-zero/core/service/http" "github.com/zeromicro/go-zero/core/service/startup" "github.com/zeromicro/go-zero/core/service/starter" "github.com/zeromicro/go-zero/core/service/http/handler" _ "github.com/zeromicro/go-zero/core/service/http/handler/hello" ) func main() { cfg, err := conf.LoadPath("etc") if err != nil { panic(err) } srv := &http.Server{ Protocol: "http", Host: "0.0.0.0", Port: 8080, Handler: handler.New(), Router: "etc/router", Recovery: true, PProf: true, DisableLog: false, StackTrace: false, Timeout: 30, MaxHeaderSize: 1024, IdleTimeout: 30, } service.Serve(startup.Default, srv, cfg) }
数据库操作
Go Zero 提供了多种数据库适配器,例如 MySQL、PostgreSQL、MongoDB 等。
创建数据库连接
在 internal/db
目录中创建一个数据库连接文件 db.go
:
package db import ( "github.com/zeromicro/go-zero/core/stores/sqlx" "github.com/zeromicro/go-zero/core/stores/sqlc" "github.com/zeromicro/go-zero/core/stores/sqlx/sqlcgen" ) func NewDB(dataSourceName string) *sqlx.DB { return sqlx.New(dataSourceName) } func NewSQLC(dataSourceName string) *sqlc.Connection { db, _ := NewDB(dataSourceName) return sqlcgen.New(db) }
使用数据库连接
在处理函数中使用数据库连接:
package handler import ( "github.com/zeromicro/go-zero/core/stores/sqlx" "github.com/zeromicro/go-zero/core/stores/sqlc" "internal/db" ) type Handler struct { db *sqlc.Connection } func NewHandler(db *sqlc.Connection) *Handler { return &Handler{db: db} } func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) { user, err := h.db.GetUser(r.URL.Query().Get("id")) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Write([]byte(user.Name)) }
数据缓存
Go Zero 提供了多种缓存适配器,例如 Redis、Memcached 等。
创建缓存连接
在 internal/cache
目录中创建一个缓存连接文件 cache.go
:
package cache import ( "github.com/zeromicro/go-zero/core/stores/cache" ) func NewCache(dataSourceName string) *cache.Cache { return cache.New(dataSourceName) }
使用缓存连接
在处理函数中使用缓存连接:
package handler import ( "github.com/zeromicro/go-zero/core/stores/cache" "internal/cache" ) type Handler struct { cache *cache.Cache } func NewHandler(cache *cache.Cache) *Handler { return &Handler{cache: cache} } func (h *Handler) GetCachedUser(w http.ResponseWriter, r *http.Request) { id := r.URL.Query().Get("id") user, err := h.cache.Get(id) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Write([]byte(user.Name)) }
示例代码
在 main.go
中启动服务,并初始化数据库和缓存连接:
package main import ( "github.com/zeromicro/go-zero/core/conf" "github.com/zeromicro/go-zero/core/service" "github.com/zeromicro/go-zero/core/service/cfg" "github.com/zeromicro/go-zero/core/service/http" "github.com/zeromicro/go-zero/core/service/startup" "github.com/zeromicro/go-zero/core/service/starter" "github.com/zeromicro/go-zero/core/service/http/handler" _ "github.com/zeromicro/go-zero/core/service/http/handler/hello" "internal/db" "internal/cache" ) func main() { cfg, err := conf.LoadPath("etc") if err != nil { panic(err) } dataSourceName := "mysql://root:password@tcp(127.0.0.1:3306)/test" dbConn := db.NewDB(dataSourceName) sqlC := db.NewSQLC(dataSourceName) cacheDataSourceName := "redis://127.0.0.1:6379" cacheConn := cache.NewCache(cacheDataSourceName) srv := &http.Server{ Protocol: "http", Host: "0.0.0.0", Port: 8080, Handler: handler.New(sqlC, cacheConn), Router: "etc/router", Recovery: true, PProf: true, DisableLog: false, StackTrace: false, Timeout: 30, MaxHeaderSize: 1024, IdleTimeout: 30, } service.Serve(startup.Default, srv, cfg) }
服务发现是指在分布式系统中自动发现和注册服务的过程。Go Zero 内置了服务发现支持,可以通过配置文件指定服务注册中心(如 Etcd)。
创建服务注册中心配置
在配置文件中指定服务注册中心:
type: http name: app host: 0.0.0.0 port: 8080 router: - name: hello path: /hello handler: handler.Hello register: type: etcd endpoints: - 127.0.0.1:2379
示例代码
在 main.go
中启动服务:
package main import ( "github.com/zeromicro/go-zero/core/conf" "github.com/zeromicro/go-zero/core/service" "github.com/zeromicro/go-zero/core/service/cfg" "github.com/zeromicro/go-zero/core/service/http" "github.com/zeromicro/go-zero/core/service/startup" "github.com/zeromicro/go-zero/core/service/starter" "github.com/zeromicro/go-zero/core/service/http/handler" _ "github.com/zeromicro/go-zero/core/service/http/handler/hello" ) func main() { cfg, err := conf.LoadPath("etc") if err != nil { panic(err) } srv := &http.Server{ Protocol: "http", Host: "0.0.0.0", Port: 8080, Handler: handler.New(), Router: "etc/router", Recovery: true, PProf: true, DisableLog: false, StackTrace: false, Timeout: 30, MaxHeaderSize: 1024, IdleTimeout: 30, Register: "etc/register", } service.Serve(startup.Default, srv, cfg) }
服务注册
服务启动后,会自动向配置的注册中心注册服务信息。
配置负载均衡
Go Zero 支持多种负载均衡策略,可以通过配置文件指定负载均衡器(如 Consul)。
示例代码
配置文件 loadbalance-config.yaml
:
type: http name: app host: 0.0.0.0 port: 8080 router: - name: hello path: /hello handler: handler.Hello loadbalance: type: consul endpoints: - 127.0.0.1:8500
main.go
中启动服务:
package main import ( "github.com/zeromicro/go-zero/core/conf" "github.com/zeromicro/go-zero/core/service" "github.com/zeromicro/go-zero/core/service/cfg" "github.com/zeromicro/go-zero/core/service/http" "github.com/zeromicro/go-zero/core/service/startup" "github.com/zeromicro/go-zero/core/service/starter" "github.com/zeromicro/go-zero/core/service/http/handler" _ "github.com/zeromicro/go-zero/core/service/http/handler/hello" ) func main() { cfg, err := conf.LoadPath("etc") if err != nil { panic(err) } srv := &http.Server{ Protocol: "http", Host: "0.0.0.0", Port: 8080, Handler: handler.New(), Router: "etc/router", Recovery: true, PProf: true, DisableLog: false, StackTrace: false, Timeout: 30, MaxHeaderSize: 1024, IdleTimeout: 30, Loadbalance: "etc/loadbalance", } service.Serve(startup.Default, srv, cfg) }
服务发现与注册
配置文件 register-config.yaml
:
type: http name: app host: 0.0.0.0 port: 8080 router: - name: hello path: /hello handler: handler.Hello register: type: etcd endpoints: - 127.0.0.1:2379
main.go
中启动服务:
package main import ( "github.com/zeromicro/go-zero/core/conf" "github.com/zeromicro/go-zero/core/service" "github.com/zeromicro/go-zero/core/service/cfg" "github.com/zeromicro/go-zero/core/service/http" "github.com/zeromicro/go-zero/core/service/startup" "github.com/zeromicro/go-zero/core/service/starter" "github.com/zeromicro/go-zero/core/service/http/handler" _ "github.com/zeromicro/go-zero/core/service/http/handler/hello" ) func main() { cfg, err := conf.LoadPath("etc") if err != nil { panic(err) } srv := &http.Server{ Protocol: "http", Host: "0.0.0.0", Port: 8080, Handler: handler.New(), Router: "etc/router", Recovery: true, PProf: true, DisableLog: false, StackTrace: false, Timeout: 30, MaxHeaderSize: 1024, IdleTimeout: 30, Register: "etc/register", } service.Serve(startup.Default, srv, cfg) }
日志配置
Go Zero 支持多种日志输出方式,例如文件、标准输出等。可以配置日志文件路径和格式。
示例代码
配置文件 log-config.yaml
:
type: http name: app host: 0.0.0.0 port: 8080 router: - name: hello path: /hello handler: handler.Hello log: level: debug name: app file: ./logs/app.log
main.go
中启动服务:
package main import ( "github.com/zeromicro/go-zero/core/conf" "github.com/zeromicro/go-zero/core/service" "github.com/zeromicro/go-zero/core/service/cfg" "github.com/zeromicro/go-zero/core/service/http" "github.com/zeromicro/go-zero/core/service/startup" "github.com/zeromicro/go-zero/core/service/starter" "github.com/zeromicro/go-zero/core/service/http/handler" _ "github.com/zeromicro/go-zero/core/service/http/handler/hello" ) func main() { cfg, err := conf.LoadPath("etc") if err != nil { panic(err) } srv := &http.Server{ Protocol: "http", Host: "0.0.0.0", Port: 8080, Handler: handler.New(), Router: "etc/router", Recovery: true, PProf: true, DisableLog: false, StackTrace: false, Timeout: 30, MaxHeaderSize: 1024, IdleTimeout: 30, Log: "etc/log", } service.Serve(startup.Default, srv, cfg) }
监控配置
Go Zero 支持与第三方监控工具集成,例如 Prometheus、Grafana 等。可以通过配置文件指定监控工具的地址和端口。
示例代码
配置文件 metrics-config.yaml
:
type: http name: app host: 0.0.0.0 port: 8080 router: - name: hello path: /hello handler: handler.Hello metrics: type: prometheus endpoint: /metrics
main.go
中启动服务:
package main import ( "github.com/zeromicro/go-zero/core/conf" "github.com/zeromicro/go-zero/core/service" "github.com/zeromicro/go-zero/core/service/cfg" "github.com/zeromicro/go-zero/core/service/http" "github.com/zeromicro/go-zero/core/service/startup" "github.com/zeromicro/go-zero/core/service/starter" "github.com/zeromicro/go-zero/core/service/http/handler" _ "github.com/zeromicro/go-zero/core/service/http/handler/hello" ) func main() { cfg, err := conf.LoadPath("etc") if err != nil { panic(err) } srv := &http.Server{ Protocol: "http", Host: "0.0.0.0", Port: 8080, Handler: handler.New(), Router: "etc/router", Recovery: true, PProf: true, DisableLog: false, StackTrace: false, Timeout: 30, MaxHeaderSize: 1024, IdleTimeout: 30, Metrics: "etc/metrics", } service.Serve(startup.Default, srv, cfg) }
代码结构
Go Zero 提供了推荐的项目结构,如 internal
目录用于存放服务逻辑代码,cmd
目录用于存放启动命令。遵循推荐的结构可以提高代码的可读性和可维护性。
依赖管理
使用 go mod
进行依赖管理,确保项目依赖的版本一致。在项目根目录下使用以下命令:
go mod init gozero-example go mod tidy
错误处理
Go Zero 提供了丰富的错误处理工具,如 Error
和 Recovery
。在处理函数中使用适当的错误处理逻辑,确保服务的健壮性。
代码风格
遵循 Go 语言的代码风格指南,如使用 golangci-lint
进行代码检查。
go get -u golangci-lint/cmd/golangci-lint golangci-lint run
单元测试
编写单元测试,确保代码的正确性。Go Zero 提供了丰富的测试工具,如 testing
包和 mock
模拟库。
示例代码
编写单元测试 internal/handler/hello/hello_test.go
:
package handler import ( "net/http" "testing" ) func TestHello(t *testing.T) { handler := NewHandler(nil) w := httptest.NewRecorder() r := httptest.NewRequest(http.MethodGet, "/hello", nil) handler.Hello(w, r) if w.Body.String() != "Hello, World!" { t.Errorf("expected 'Hello, World!', got %s", w.Body.String()) } }
错误排查
使用 PProf
进行性能分析,定位性能瓶颈。在 main.go
中启用 PProf:
PProf: true,
调试技巧
使用 log
包记录详细的调试信息。在处理函数中添加调试日志:
import ( "log" "net/http" ) func (h *Handler) Hello(w http.ResponseWriter, r *http.Request) { log.Println("Received request: /hello") w.Write([]byte("Hello, World!")) }
示例代码
在 main.go
中启用日志:
DisableLog: false,
优化策略
- 缓存优化:使用缓存减少数据库访问。
- 异步处理:使用
goroutines
提升并发处理能力。 - 数据库优化:使用连接池优化数据库连接。
- 代码优化:使用 Go 语言特性(如
sync
包)提高代码性能。
示例代码
使用连接池
在 db.go
中使用连接池:
package db import ( "github.com/zeromicro/go-zero/core/stores/sqlx" "github.com/zeromicro/go-zero/core/stores/sqlc" "github.com/zeromicro/go-zero/core/stores/sqlcgen" ) func NewDB(dataSourceName string) *sqlx.DB { return sqlx.NewWithPool(dataSourceName, 10, 100) }
异步处理
在处理函数中使用 goroutines
:
package handler import ( "net/http" ) func (h *Handler) AsyncHello(w http.ResponseWriter, r *http.Request) { go func() { // 异步处理逻辑 }() }
使用缓存
在处理函数中使用缓存:
package handler import ( "github.com/zeromicro/go-zero/core/stores/cache" ) func (h *Handler) GetCachedUser(w http.ResponseWriter, r *http.Request) { id := r.URL.Query().Get("id") user, err := h.cache.Get(id) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Write([]byte(user.Name)) }
Go Zero 提供了详细的文档和在线教程,可以帮助开发者快速上手。文档包括安装指南、配置说明、API 文档等。
参考文档
- 官方文档
- 配置文件文档
- API 文档
在线教程
- 入门教程
- 高级教程
Go Zero 拥有一个活跃的社区,开发者可以在这里交流经验、解决问题。社区支持包括官方论坛、邮件列表、GitHub 仓库等。
官方论坛
- GitHub Issues
- GitHub Discussions
邮件列表
- 邮件列表
Go Zero 已经被多个公司和项目使用,并且取得了显著的成功。例如,一些公司使用 Go Zero 构建了高效、可靠的微服务架构,提高了系统的稳定性和可扩展性。
典型案例
- 公司A:使用 Go Zero 构建了一套完整的电商平台,支持数百万并发用户。
- 公司B:使用 Go Zero 实现了一个高可用的分布式日志系统,确保了日志数据的可靠性和一致性。
这些案例展示了 Go Zero 在实际生产环境中的强大功能和优异表现。
这篇关于Go Zero入门:新手必读指南的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-20go-zero 框架的 RPC 服务 启动start和停止 底层是怎么实现的?-icode9专业技术文章分享
- 2024-12-19Go-Zero 框架的 RPC 服务启动和停止的基本机制和过程是怎么实现的?-icode9专业技术文章分享
- 2024-12-18怎么在golang中使用gRPC测试mock数据?-icode9专业技术文章分享
- 2024-12-15掌握PageRank算法核心!你离Google优化高手只差一步!
- 2024-12-15GORM 中的标签 gorm:"index"是什么?-icode9专业技术文章分享
- 2024-12-11怎么在 Go 语言中获取 Open vSwitch (OVS) 的桥接信息(Bridge)?-icode9专业技术文章分享
- 2024-12-11怎么用Go 语言的库来与 Open vSwitch 进行交互?-icode9专业技术文章分享
- 2024-12-11怎么在 go-zero 项目中发送阿里云短信?-icode9专业技术文章分享
- 2024-12-11怎么使用阿里云 Go SDK (alibaba-cloud-sdk-go) 发送短信?-icode9专业技术文章分享
- 2024-12-10搭建个人博客网站之一、使用hugo创建个人博客网站