Gin 项目实战
1、项目设置
使用 Docker-compose 创建 MongoDB 和 Redis 数据库
docker-compose.yml
version: '3'
services:
mongodb:
image: mongo
container_name: mongodb
restart: always
env_file:
- ./app.env
ports:
- '6000:27017'
volumes:
- mongodb:/data/db
redis:
image: redis:alpine
container_name: redis
ports:
- '6379:6379'
volumes:
- redisDB:/data
volumes:
mongodb:
redisDB:
app.env
PORT=8000
MONGO_INITDB_ROOT_USERNAME=root
MONGO_INITDB_ROOT_PASSWORD=password123
MONGODB_LOCAL_URI=mongodb://root:password123@localhost:6000
REDIS_URL=localhost:6379
启动
docker-compose up -d
如何将 Golang App 连接到 Redis 和 MongoDB
// 依赖包
go get github.com/spf13/viper
go get github.com/go-redis/redis/v8
go get go.mongodb.org/mongo-driver/mongo
go get github.com/gin-gonic/gin
go get golang.org/x/crypto/bcrypt
go get github.com/golang-jwt/jwt
go get github.com/go-redis/redis/v8
go get github.com/gin-contrib/cors
// 创建一个文件夹
mkdir api-golang-mongodb-redis-mysql-gin-project
cd api-golang-mongodb-redis-mysql-gin-project
// 初始化一个新的 Golang 项目。
go mod init github.com/example/golang-test
config/default.go
定义一个结构,该结构将包含 app.env 文件中的所有变量并提供适当的值类型。
package config
import "github.com/spf13/viper"
type Config struct {
DBUri string `mapstructure:"MONGODB_LOCAL_URI"`
RedisUri string `mapstructure:"REDIS_URL"`
Port string `mapstructure:"PORT"`
}
// 👈 Struct Config
func LoadConfig(path string) (config Config, err error) {
viper.AddConfigPath(path)
viper.SetConfigType("env")
viper.SetConfigName("app")
viper.AutomaticEnv()
err = viper.ReadInConfig()
if err != nil {
return
}
err = viper.Unmarshal(&config)
return
}
package main
// 👈 Require the packages
import (
"context"
"fmt"
"log"
"net/http"
"github.com/example/golang-test/config"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
)
// 👈 创建所需的变量,我们稍后会重新分配这些变量
var (
server *gin.Engine
ctx context.Context
mongoclient *mongo.Client
redisclient *redis.Client
)
// 每个 go 文件会先运行 init 函数
func init() {
// 加载 .env 变量
config, err := config.LoadConfig(".")
if err != nil {
log.Fatal("Could not load environment variables", err)
}
// 创建上下文
ctx = context.TODO()
// 连接 MongoDB
mongoconn := options.Client().ApplyURI(config.DBUri)
mongoclient, err := mongo.Connect(ctx, mongoconn)
if err != nil {
panic(err)
}
if err := mongoclient.Ping(ctx, readpref.Primary()); err != nil {
panic(err)
}
fmt.Println("MongoDB successfully connected...")
// 连接 Redis
redisclient = redis.NewClient(&redis.Options{
Addr: config.RedisUri,
})
if _, err := redisclient.Ping(ctx).Result(); err != nil {
panic(err)
}
err = redisclient.Set(ctx, "test", "Welcome to Golang with Redis and MongoDB", 0).Err()
if err != nil {
panic(err)
}
fmt.Println("Redis client connected successfully...")
// 创建Gin引擎实例
server = gin.Default()
}
func main() {
config, err := config.LoadConfig(".")
if err != nil {
log.Fatal("Could not load config", err)
}
// 延迟关闭 mongoclient
defer mongoclient.Disconnect(ctx)
value, err := redisclient.Get(ctx, "test").Result()
if err == redis.Nil {
fmt.Println("key: test does not exist")
} else if err != nil {
panic(err)
}
// 定义 API 前缀
router := server.Group("/api")
// 测试 redis 连接是否正常
router.GET("/healthchecker", func(ctx *gin.Context) {
ctx.JSON(http.StatusOK, gin.H{"status": "success", "message": value})
})
log.Fatal(server.Run(":" + config.Port))
}
测试 Golang API
docker-compose up -d && go run main.go
>curl localhost:8000/api/healthchecker
{"message":"Welcome to Golang with Redis and MongoDB","status":"success"}
当前项目结构
wang@1:~$ tree api-golang-mongodb-redis-mysql-gin-project/
api-golang-mongodb-redis-mysql-gin-project/
├── app.env
├── config
│ └── default.go
├── docker-compose.yml
├── go.mod
├── go.sum
└── main.go
2、JWT 身份验证和授权
使用此 Golang JSON Web Token 身份验证 API,用户将能够执行以下操作:
注册一个新帐户
使用注册的凭据登录
过期时刷新访问令牌
仅在经过身份验证后获取配置文件信息
users
GET
/api/users/me
返回登录用户的信息
auth
POST
/api/auth/register
注册一个新用户
auth
POST
/api/auth/login
登录注册用户
auth
GET
/api/auth/refresh
刷新过期的访问令牌
auth
GET
/api/auth/logout
注销用户
1、 如何生成公钥和私钥
http://travistidwell.com/jsencrypt/demo/
app.env 添加
ACCESS_TOKEN_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCUEFJQkFBSkJBTzVIKytVM0xrWC91SlRvRHhWN01CUURXSTdGU0l0VXNjbGFFKzlaUUg5Q2VpOGIxcUVmCnJxR0hSVDVWUis4c3UxVWtCUVpZTER3MnN3RTVWbjg5c0ZVQ0F3RUFBUUpCQUw4ZjRBMUlDSWEvQ2ZmdWR3TGMKNzRCdCtwOXg0TEZaZXMwdHdtV3Vha3hub3NaV0w4eVpSTUJpRmI4a25VL0hwb3piTnNxMmN1ZU9wKzVWdGRXNApiTlVDSVFENm9JdWxqcHdrZTFGY1VPaldnaXRQSjNnbFBma3NHVFBhdFYwYnJJVVI5d0loQVBOanJ1enB4ckhsCkUxRmJxeGtUNFZ5bWhCOU1HazU0Wk1jWnVjSmZOcjBUQWlFQWhML3UxOVZPdlVBWVd6Wjc3Y3JxMTdWSFBTcXoKUlhsZjd2TnJpdEg1ZGdjQ0lRRHR5QmFPdUxuNDlIOFIvZ2ZEZ1V1cjg3YWl5UHZ1YStxeEpXMzQrb0tFNXdJZwpQbG1KYXZsbW9jUG4rTkVRdGhLcTZuZFVYRGpXTTlTbktQQTVlUDZSUEs0PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQ==
ACCESS_TOKEN_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBTzVIKytVM0xrWC91SlRvRHhWN01CUURXSTdGU0l0VQpzY2xhRSs5WlFIOUNlaThiMXFFZnJxR0hSVDVWUis4c3UxVWtCUVpZTER3MnN3RTVWbjg5c0ZVQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ==
ACCESS_TOKEN_EXPIRED_IN=15m
ACCESS_TOKEN_MAXAGE=15
REFRESH_TOKEN_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT1FJQkFBSkJBSWFJcXZXeldCSndnYjR1SEhFQ01RdHFZMTI5b2F5RzVZMGlGcG51a0J1VHpRZVlQWkE4Cmx4OC9lTUh3Rys1MlJGR3VxMmE2N084d2s3TDR5dnY5dVY4Q0F3RUFBUUpBRUZ6aEJqOUk3LzAxR285N01CZUgKSlk5TUJLUEMzVHdQQVdwcSswL3p3UmE2ZkZtbXQ5NXNrN21qT3czRzNEZ3M5T2RTeWdsbTlVdndNWXh6SXFERAplUUloQVA5UStrMTBQbGxNd2ZJbDZtdjdTMFRYOGJDUlRaZVI1ZFZZb3FTeW40YmpBaUVBaHVUa2JtZ1NobFlZCnRyclNWZjN0QWZJcWNVUjZ3aDdMOXR5MVlvalZVRlVDSUhzOENlVHkwOWxrbkVTV0dvV09ZUEZVemhyc3Q2Z08KU3dKa2F2VFdKdndEQWlBdWhnVU8yeEFBaXZNdEdwUHVtb3hDam8zNjBMNXg4d012bWdGcEFYNW9uUUlnQzEvSwpNWG1heWtsaFRDeWtXRnpHMHBMWVdkNGRGdTI5M1M2ZUxJUlNIS009Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t
REFRESH_TOKEN_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBSWFJcXZXeldCSndnYjR1SEhFQ01RdHFZMTI5b2F5Rwo1WTBpRnBudWtCdVR6UWVZUFpBOGx4OC9lTUh3Rys1MlJGR3VxMmE2N084d2s3TDR5dnY5dVY4Q0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ==
REFRESH_TOKEN_EXPIRED_IN=60m
REFRESH_TOKEN_MAXAGE=60
2、添加如下内容
config/default.go
type Config struct {
// 添加以下如下
AccessTokenPrivateKey string `mapstructure:"ACCESS_TOKEN_PRIVATE_KEY"`
AccessTokenPublicKey string `mapstructure:"ACCESS_TOKEN_PUBLIC_KEY"`
RefreshTokenPrivateKey string `mapstructure:"REFRESH_TOKEN_PRIVATE_KEY"`
RefreshTokenPublicKey string `mapstructure:"REFRESH_TOKEN_PUBLIC_KEY"`
AccessTokenExpiresIn time.Duration `mapstructure:"ACCESS_TOKEN_EXPIRED_IN"`
RefreshTokenExpiresIn time.Duration `mapstructure:"REFRESH_TOKEN_EXPIRED_IN"`
AccessTokenMaxAge int `mapstructure:"ACCESS_TOKEN_MAXAGE"`
RefreshTokenMaxAge int `mapstructure:"REFRESH_TOKEN_MAXAGE"`
}
3、创建用户模型
mkdir models
models/user.model.go
package models
import (
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// 1、创建一个 SignUpInput 结构来指定注册新用户所需的字段
// MongoDB 将使用该bson标签,因为 MongoDB 将数据存储为 BSON 文档
type SignUpInput struct {
Name string `json:"name" bson:"name" binding:"required"`
Email string `json:"email" bson:"email" binding:"required"`
Password string `json:"password" bson:"password" binding:"required,min=8"`
PasswordConfirm string `json:"passwordConfirm" bson:"passwordConfirm,omitempty" binding:"required"`
Role string `json:"role" bson:"role"`
Verified bool `json:"verified" bson:"verified"`
CreatedAt time.Time `json:"created_at" bson:"created_at"`
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
}
// 2、用户登录逻辑,只需要用户提供电子邮件和密码
type SignInInput struct {
Email string `json:"email" bson:"email" binding:"required"`
Password string `json:"password" bson:"password" binding:"required"`
}
// 3、创建一个 DBResponse 结构来定义 MongoDB 将返回的字段
type UserDBResponse struct {
ID primitive.ObjectID `json:"id" bson:"_id"`
Name string `json:"name" bson:"name"`
Email string `json:"email" bson:"email"`
Password string `json:"password" bson:"password"`
PasswordConfirm string `json:"passwordConfirm,omitempty" bson:"passwordConfirm,omitempty"`
Role string `json:"role" bson:"role"`
Verified bool `json:"verified" bson:"verified"`
CreatedAt time.Time `json:"created_at" bson:"created_at"`
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
}
// 4、创建一个结构来指定应该包含在 JSON 响应中的字段和一个过滤掉敏感字段的函数。
type UserResponse struct {
ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"`
Name string `json:"name,omitempty" bson:"name,omitempty"`
Email string `json:"email,omitempty" bson:"email,omitempty"`
Role string `json:"role,omitempty" bson:"role,omitempty"`
CreatedAt time.Time `json:"created_at" bson:"created_at"`
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
}
func FilteredResponse(user *DBResponse) UserResponse {
return UserResponse{
ID: user.ID,
Email: user.Email,
Name: user.Name,
Role: user.Role,
CreatedAt: user.CreatedAt,
UpdatedAt: user.UpdatedAt,
}
}
5、创建身份接口和用户接口
在典型的 Golang RESTful API 中,有控制器和服务。控制器不能直接访问数据库,而是调用服务来改变或查询数据库。
1、身份认证接口
让我们定义一个 AuthService 指定 SignUpUser和SignInUser方法的接口
services/auth.service.go
package services
import "github.com/example/golang-test/models"
type AuthService interface {
// 注册登录接口, 并返回指定格式 json 数据
SignUpUser(*models.SignUpInput) (*models.UserDBResponse, error)
SignInUser(*models.SignInInput) (*models.UserDBResponse, error)
}
2、用户认证接口
定义一个UserService具有 FindUserById 和 FindUserByEmail 方法的接口,方法在后面
services/user.service.go
package services
import "github.com/example/golang-test/models"
type UserService interface {
FindUserById(string) (*models.UserDBResponse, error)
FindUserByEmail(string) (*models.UserDBResponse, error)
}
6、加密和解密用户密码
数据库存储的密码不可能是明文, 所以加密,这里定义两个函数进行加密和解密
utils/password.go
package utils
import (
"fmt"
"golang.org/x/crypto/bcrypt"
)
// 哈希加密密码
func HashPassword(password string) (string, error) {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", fmt.Errorf("could not hash password %w", err)
}
return string(hashedPassword), nil
}
// 哈希解密密码
func VerifyPassword(hashedPassword string, candidatePassword string) error {
return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(candidatePassword))
}
7、创建与数据库交互的服务
定义一个 SignUpUser 将由控制器调用以在数据库中创建新用户的服务。
认证接口实现
我在结构中包含了 MongoDB Collection 结构和 context 包的 Context 接口,AuthServiceImpl以帮助我们对 MongoDB 数据库执行基本的 CRUD 操作。
然后我使用NewAuthService构造函数来实例化 MongoDB 集合结构和 Context 接口。此外,NewAuthService构造函数实现了AuthService我们上面定义的接口。
略......
身份验证中间件
Last updated