最近前端已死的话题非常热,作为前端就想着学一点后端,还有啥精神内耗?
刚好这个课 山竹记账 Go 后端版 满足我的需求
命令行工具
代码
import "github.com/spf13/cobra"
func Run() {
rootCmd := &cobra.Command{
Use: "mangosteen",
}
severCmd := &cobra.Command{
Use: "server",
Run: func(cmd *cobra.Command, args []string) {
RunServer()
},
}
rootCmd.AddCommand(severCmd)
rootCmd.Execute()
}
使用
go run main.go server
测试
- 新建文件夹
test
- 新建文件
xxx_test.go
- 测试函数
TestXxx(t *testing.T)
- 运行命令
go test ./test/xxx_test.go
构建 http 请求
import (
"go-mangosteen/internal/router"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestPingController(t *testing.T) {
r := router.New()
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/ping", nil)
r.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
assert.Equal(t, "pong", w.Body.String())
}
测试覆盖率
go test -coverprofile=coverage.out ./...
生成 html
文件:go tool cover -html=coverage.out -o coverage.html
数据库连接 —— sqlc
创建 postgres 数据库
docker run -d --name pg-for-go-mangosteen -e POSTGRES_USER=mangosteen -e POSTGRES_PASSWORD=123456 -e POSTGRES_DB=mangosteen_dev -e PGDATA=/var/lib/postgresql/data/pgdata -v pg-go-mangosteen-data:/var/lib/postgresql/data --network=network1 postgres:14
配置 sqlc
配置文件 sqlc.yaml
,放在项目根目录下,内容如下:
version: "2"
sql:
- engine: "postgresql"
queries: "config/sqls" # sql 文件夹,CRUD 语句
schema: "config/schema.sql" # 数据库结构文件,比如创建表的语句
gen:
go:
package: "queries" # 生成的包名
out: "config/gen_queries" # 将 sql 生成的文件放在 config/gen_queries 文件夹下
运行命令 sqlc generate
,会在 config/gen_queries
文件夹下生成 db.go
,models.go
文件
连接数据库
import (
"database/sql"
"fmt"
"log"
_ "github.com/golang-migrate/migrate/v4/database/postgres"
_ "github.com/golang-migrate/migrate/v4/source/file"
)
var SqlcDB *sql.DB
const (
host = "pg-for-go-mangosteen"
port = 5432
username = "mangosteen"
password = "123456"
dbName = "mangosteen_dev"
)
func SqlcConnect() {
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, username, password, dbName)
db, err := sql.Open("postgres", dsn)
if err != nil {
log.Fatalln(err)
}
SqlcDB = db
err = SqlcDB.Ping()
if err != nil {
log.Fatalln(err)
}
}
func SqlcClose() {
SqlcDB.Close()
log.Println("Successfylly close db")
}
安装迁移文件工具 migrate
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
创建迁移文件
在命令行中使用
migrate create -ext sql -dir config/migrations -seq create_users_table
封装 migrate 命令
func SqlcCreateMigration(filename string) {
cmd := exec.Command("migrate", "create", "-ext", "sql", "-dir", "config/migrations", filename)
if err := cmd.Run(); err != nil {
log.Fatalln(err)
}
}
createMigrationCmd := &cobra.Command{
Use: "create:migration",
Run: func(cmd *cobra.Command, args []string) {
database.SqlcCreateMigration(args[0])
},
}
rootCmd.AddCommand(createMigrationCmd)
最终可使用方式
go build ./ && ./mangosteen db create:migration create_users_table
# 或
migrate create -ext sql -dir config/migrations -seq create_users_table
执行迁移文件
在命令行中使用
migrate -database "postgres://mangosteen:123456@pg-for-go-mangosteen:5432/mangosteen_dev?sslmode=disable" -source "file://$(pwd)/config/migrations" up
封装 migrate 命令
func SqlcMigration() {
dir, err := os.Getwd()
if err != nil {
log.Fatalln(err)
}
m, err := migrate.New(
fmt.Sprintf("file://%s/config/migrations", dir),
fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable", username, password, host, port, dbName),
)
if err != nil {
log.Fatalln(err)
}
err = m.Up()
if err != nil {
if strings.Contains(err.Error(), "no change") {
log.Fatalln(err)
}
}
}
migrateCmd := &cobra.Command{
Use: "migrate",
Run: func(cmd *cobra.Command, args []string) {
database.SqlcMigration()
},
}
rootCmd.AddCommand(migrateCmd)
最终可使用的方式
go build ./ && ./mangosteen db migrate
# 或
migrate -database "postgres://mangosteen:123456@pg-for-go-mangosteen:5432/mangosteen_dev?sslmode=disable" -source "file://$(pwd)/config/migrations" up
回滚迁移文件
在命令行中使用
migrate -database "postgres://mangosteen:123456@pg-for-go-mangosteen:5432/mangosteen_dev?sslmode=disable" -source "file://$(pwd)/config/migrations" down 1
封装 migrate 命令
func SqlcMigrationDown() {
dir, err := os.Getwd()
if err != nil {
log.Fatalln(err)
}
m, err := migrate.New(
fmt.Sprintf("file://%s/config/migrations", dir),
fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable", username, password, host, port, dbName),
)
if err != nil {
log.Fatalln(err)
}
err = m.Steps(-1)
if err != nil {
log.Fatalln(err)
}
}
migrateDownCmd := &cobra.Command{
Use: "migrate:down",
Run: func(cmd *cobra.Command, args []string) {
database.SqlcMigrationDown()
},
}
rootCmd.AddCommand(migrateDownCmd)
最终可使用的方式
go build ./ && ./mangosteen db migrate:down
# 或
migrate -database "postgres://mangosteen:123456@pg-for-go-mangosteen:5432/mangosteen_dev?sslmode=disable" -source "file://$(pwd)/config/migrations" down 1
使用方式
- 运行【创建迁移文件】命令
- 运行命令后,会在
config/migrations
文件夹下生成xxxx_create_users_table.up.sql
和xxxx_create_users_table.down.sql
文件xxxx_create_users_table.up.sql
文件内容如下:create table if not exists users( id serial primary key, email varchar(255) not null unique, created_at timestamp not null default now(), updated_at timestamp not null default now() );
xxxx_create_users_table.down.sql
文件内容如下:drop table if exists users;
- 运行命令后,会在
- 运行【执行迁移文件】命令
- 运行命令后,会在数据库中创建
users
表
- 运行命令后,会在数据库中创建
- 运行【回滚迁移文件】命令
- 运行命令后,会在数据库中删除
users
表
- 运行命令后,会在数据库中删除
- 如果要增加字段或者删除字段,上述步骤
- 其中【回滚迁移文件】命令只能回滚一次,如果要回滚多次,需要在执行多次【回滚迁移文件】命令
- 【执行迁移文件】命令,会更新到最新的状态
编写 sql
- 将表结构写在
config/schema.sql
文件中 - 新建
config/sqls/xxx.sql
文件夹,编写sql
- 注释的格式:Query annotations
-- name: ListUsers :many select * from users order by id offset $1 limit $2;
- 运行命令
sqlc generate
生成对应的go
文件
数据库连接 —— sql
连接数据库
import (
"database/sql"
"fmt"
"log"
)
const (
host = "pg-for-go-mangosteen"
port = 5432
user = "mangosteen"
password = "123456"
dbName = "mangosteen_dev"
)
var SqlDB *sql.DB
func SqlConnect() {
connStr := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", host, port, user, password, dbName)
db, err := sql.Open("postgres", connStr)
if err != nil {
log.Fatalln(err)
}
SqlDB = db
err = db.Ping()
if err != nil {
log.Fatal(err)
}
log.Println("Successfully connect to db")
}
func SqlClose() {
SqlDB.Close()
log.Println("Successfylly close db")
}
增删改查直接写 sql
文档:type DB
_, err = DB.Exec(`CREATE TABLE items (
id SERIAL PRIMARY KEY,
amount INT NOT NULL,
happened_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
)`)
if err != nil {
log.Println(err)
}
数据库连接 —— gorm
安装工具
import (
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
连接数据库
import (
"fmt"
"log"
"time"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var GormDB *gorm.DB
func GormConnect() {
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", gormHost, gormPort, gormUser, gormPassword, gormDbname)
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatalln(err)
}
GormDB = db
}
func GormClose() {
gormDB, err := GormDB.DB()
if err != nil {
log.Fatalln(err)
}
gormDB.Close()
}
创建表
需要先指定表的结构体,内容如下
type User struct {
ID int
Email string `gorm:"uniqueIndex"`
Phone int
CreatedAt time.Time
UpdatedAt time.Time
}
type Item struct {
ID int
UserID int
Amount int
HappenedAt time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
type Tag struct {
ID int
Name string
}
var models = []any{&User{}, &Item{}, &Tag{}}
创建表,内容如下:
func GormCreateTables() {
for _, model := range models {
if err := GormDB.Migrator().CreateTable(model); err != nil {
log.Println(err)
}
}
}
修改表结构
先修改表对应的结构体,然后运行 GormDB.AutoMigrate
函数
func GormMigrate() {
GormDB.AutoMigrate(models...)
}
增删改查
文档:Types
users := []User{}
tx := GormDB.Raw("select * from users where id = ?", 1).Scan(&users)
if tx.Error != nil {
log.Println(tx.Error)
} else {
log.Println(users)
}
什么是 migrate
在开发的过程中,由于需求的变动,数据库的结构可能进行修改、新增或删除表、列、约束等操作,这些操作统称为 migrate
migrate
的目的是为了保证数据库的结构和代码的结构保持一致,这样才能保证代码的正常运行
它有点类似于 git
,提供版本控制、回滚操作
api 文档
api
文档使用的是:swag
安装工具:
import (
"github.com/swaggo/swag"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
新建路由
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
编写注释
// @title 山竹记账
// @description 一个简单的记账系统
// @BasePath /api/v1
// @host localhost:8080
func main() {
cmd.Run()
}
// Ping godoc
// @Summary 用来测试 API 是否正常
// @Description 用来测试 API 是否正常
// @Accept json
// @Produce json
// @Success 200
// @Failure 500
// @Router /ping [get]
func PingController(c *gin.Context) {
c.String(200, "pong")
}
生成文档
swag init -g router/router.go # 指定文件
# 或
swag init # 默认生成当前目录下的所有文件
启动服务
go run main.go server
go 安装依赖
go mod tidy
:用于整理和更新依赖列表go install xxx
:当想要直接使用可执行文件时,用此方法,也就是说可以在命令行中使用go get xxx
:通常在项目初始化阶段或首次引入新的包时使用,它会将包添加到项目的依赖列表中