# Golang 实战项目:构建一个完整的 Web 应用
## 项目概述
本项目将构建一个完整的 Web 应用,包括用户认证、数据存储、API 接口等功能。我们将使用 Go 语言的标准库和一些流行的第三方库来实现这个项目。
## 技术栈
– **语言**:Go 1.20+
– **Web 框架**:Gin
– **数据库**:PostgreSQL
– **认证**:JWT
– **ORM**:GORM
– **配置管理**:viper
– **日志**:zap
## 项目结构
“`
myapp/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── api/
│ │ ├── handlers/
│ │ │ ├── auth.go
│ │ │ ├── user.go
│ │ │ └── post.go
│ │ ├── middleware/
│ │ │ ├── auth.go
│ │ │ └── cors.go
│ │ └── routes.go
│ ├── config/
│ │ └── config.go
│ ├── db/
│ │ └── db.go
│ ├── models/
│ │ ├── user.go
│ │ └── post.go
│ ├── services/
│ │ ├── auth.go
│ │ ├── user.go
│ │ └── post.go
│ └── utils/
│ ├── jwt.go
│ └── password.go
├── pkg/
│ └── logger/
│ └── logger.go
├── .env
├── go.mod
└── go.sum
“`
## 环境搭建
### 安装依赖
“`bash
go mod init myapp
go get github.com/gin-gonic/gin
go get gorm.io/gorm
go get gorm.io/driver/postgres
go get github.com/golang-jwt/jwt/v5
go get github.com/spf13/viper
go get go.uber.org/zap
“`
### 配置数据库
1. 安装 PostgreSQL
2. 创建数据库:`CREATE DATABASE myapp`
3. 创建用户:`CREATE USER myapp WITH PASSWORD ‘password’`
4. 授予权限:`GRANT ALL PRIVILEGES ON DATABASE myapp TO myapp`
### 配置文件
创建 `.env` 文件:
“`env
# Server Configuration
PORT=8080
ENV=development
# Database Configuration
DB_HOST=localhost
DB_PORT=5432
DB_USER=myapp
DB_PASSWORD=password
DB_NAME=myapp
# JWT Configuration
JWT_SECRET=your_jwt_secret_key
JWT_EXPIRATION=24h
“`
## 核心功能实现
### 数据库连接
“`go
// internal/db/db.go
package db
import (
“fmt”
“log”
“gorm.io/driver/postgres”
“gorm.io/gorm”
“myapp/internal/models”
)
var DB *gorm.DB
func InitDB(host, port, user, password, dbname string) error {
dsn := fmt.Sprintf(“host=%s port=%s user=%s password=%s dbname=%s sslmode=disable”,
host, port, user, password, dbname)
var err error
DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
return err
}
// 自动迁移
err = DB.AutoMigrate(&models.User{}, &models.Post{})
if err != nil {
return err
}
log.Println(“Database connected successfully”)
return nil
}
“`
### 模型定义
“`go
// internal/models/user.go
package models
import (
“time”
“gorm.io/gorm”
)
type User struct {
ID uint `json:”id” gorm:”primaryKey”`
CreatedAt time.Time `json:”created_at”`
UpdatedAt time.Time `json:”updated_at”`
DeletedAt gorm.DeletedAt `json:”deleted_at” gorm:”index”`
Username string `json:”username” gorm:”uniqueIndex;size:50;not null”`
Email string `json:”email” gorm:”uniqueIndex;size:100;not null”`
Password string `json:”-” gorm:”size:100;not null”`
Posts []Post `json:”posts,omitempty” gorm:”foreignKey:UserID”`
}
// internal/models/post.go
package models
import (
“time”
“gorm.io/gorm”
)
type Post struct {
ID uint `json:”id” gorm:”primaryKey”`
CreatedAt time.Time `json:”created_at”`
UpdatedAt time.Time `json:”updated_at”`
DeletedAt gorm.DeletedAt `json:”deleted_at” gorm:”index”`
Title string `json:”title” gorm:”size:200;not null”`
Content string `json:”content” gorm:”type:text;not null”`
UserID uint `json:”user_id” gorm:”not null”`
User User `json:”user,omitempty” gorm:”foreignKey:UserID”`
}
“`
### 认证服务
“`go
// internal/services/auth.go
package services
import (
“errors”
“time”
“github.com/golang-jwt/jwt/v5”
“golang.org/x/crypto/bcrypt”
“myapp/internal/models”
)
var jwtSecret []byte
func SetJWTSecret(secret string) {
jwtSecret = []byte(secret)
}
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
}
func CheckPasswordHash(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
func GenerateToken(user models.User) (string, error) {
claims := jwt.MapClaims{
“id”: user.ID,
“username”: user.Username,
“email”: user.Email,
“exp”: time.Now().Add(time.Hour * 24).Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtSecret)
}
func ValidateToken(tokenString string) (jwt.MapClaims, error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New(“unexpected signing method”)
}
return jwtSecret, nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
return claims, nil
}
return nil, errors.New(“invalid token”)
}
“`
### API 路由
“`go
// internal/api/routes.go
package api
import (
“github.com/gin-gonic/gin”
“myapp/internal/api/handlers”
“myapp/internal/api/middleware”
)
func SetupRoutes(router *gin.Engine) {
// 公共路由
public := router.Group(“/api”)
{
auth := public.Group(“/auth”)
{
auth.POST(“/register”, handlers.Register)
auth.POST(“/login”, handlers.Login)
}
}
// 私有路由
private := router.Group(“/api”)
private.Use(middleware.AuthMiddleware())
{
users := private.Group(“/users”)
{
users.GET(“/me”, handlers.GetCurrentUser)
users.PUT(“/me”, handlers.UpdateUser)
}
posts := private.Group(“/posts”)
{
posts.POST(“/”, handlers.CreatePost)
posts.GET(“/”, handlers.GetPosts)
posts.GET(“/:id”, handlers.GetPost)
posts.PUT(“/:id”, handlers.UpdatePost)
posts.DELETE(“/:id”, handlers.DeletePost)
}
}
}
“`
### 主函数
“`go
// cmd/server/main.go
package main
import (
“fmt”
“log”
“github.com/gin-gonic/gin”
“github.com/spf13/viper”
“myapp/internal/api”
“myapp/internal/config”
“myapp/internal/db”
“myapp/internal/services”
)
func main() {
// 加载配置
if err := config.LoadConfig(); err != nil {
log.Fatalf(“Failed to load config: %v”, err)
}
// 初始化数据库
err := db.InitDB(
viper.GetString(“DB_HOST”),
viper.GetString(“DB_PORT”),
viper.GetString(“DB_USER”),
viper.GetString(“DB_PASSWORD”),
viper.GetString(“DB_NAME”),
)
if err != nil {
log.Fatalf(“Failed to connect to database: %v”, err)
}
// 设置 JWT 密钥
services.SetJWTSecret(viper.GetString(“JWT_SECRET”))
// 设置 Gin 模式
if viper.GetString(“ENV”) == “production” {
gin.SetMode(gin.ReleaseMode)
}
// 创建 Gin 路由
router := gin.Default()
// 设置路由
api.SetupRoutes(router)
// 启动服务器
port := viper.GetString(“PORT”)
log.Printf(“Server starting on port %s”, port)
if err := router.Run(fmt.Sprintf(“:%s”, port)); err != nil {
log.Fatalf(“Failed to start server: %v”, err)
}
}
“`
## 运行项目
### 构建和运行
“`bash
# 构建项目
go build -o server ./cmd/server
# 运行项目
./server
“`
### 测试 API
#### 注册用户
“`bash
curl -X POST http://localhost:8080/api/auth/register \
-H “Content-Type: application/json” \
-d ‘{“username”: “testuser”, “email”: “test@example.com”, “password”: “password123”}’
“`
#### 登录
“`bash
curl -X POST http://localhost:8080/api/auth/login \
-H “Content-Type: application/json” \
-d ‘{“email”: “test@example.com”, “password”: “password123”}’
“`
#### 创建帖子
“`bash
curl -X POST http://localhost:8080/api/posts \
-H “Content-Type: application/json” \
-H “Authorization: Bearer YOUR_JWT_TOKEN” \
-d ‘{“title”: “First Post”, “content”: “This is my first post”}’
“`
#### 获取帖子列表
“`bash
curl -X GET http://localhost:8080/api/posts \
-H “Authorization: Bearer YOUR_JWT_TOKEN”
“`
## 总结
本文介绍了如何使用 Go 语言构建一个完整的 Web 应用,包括项目结构、环境搭建、核心功能实现和 API 测试等内容。通过这个项目,你可以学习到 Go 语言的实际应用,包括 Web 开发、数据库操作、认证授权等方面的知识。在实际开发中,你可以根据具体需求对项目进行扩展和优化,例如添加更多功能、优化性能、增强安全性等。