gitee本例代码
gitee之前写的
先创建目录, 然后在该目录下执行
go mod init quick-start
# 每次运行前执行, 确保项目依赖关系正确
go mod tidy
# 运行
go run main.go
一般web工程采用MVC结构, M就是model, V是view, C是controller
所以一般的目录结构包含
models/controllers/middleware
其中, models存放的是模型文件, controllers存放的是控制器文件, middleware存放的是中间件文件
而views则一般是单独分离出前端项目来开发
- common/ 或 utils/
- controllers/
- initializers/
- middleware/
- migrate/
- models/
- scripts/
- .env
- go.mod
- go.sum
- main.go
- readme.md
我们使用gorm框架操作数据库
gorm支持自动生成数据库表结构, 只需要定义模型结构体, 然后使用gorm的代码来生成就行
先连接到数据库, 为了方便, 我们使用sqlite
// initializers/loadEnv.go
/*
* @Author Malred
* @Date 2025-06-15 21:24:25
* @Description 连接到数据库, 对外提供数据库实例
*/
package initializers
import (
"log"
"os"
"gorm.io/driver/mysql"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
var DB *gorm.DB
func ConnectToDB() {
//ConnectToMysql()
ConnectToSqlite()
}
func ConnectToMysql() {
var err error
dsn := os.Getenv("MYSQL_DB_URL")
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to database")
}
}
func ConnectToSqlite() {
var err error
dbPath := os.Getenv("SQLITE_MDB_URL") // 通过环境变量指定 SQLite 数据库文件路径
DB, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to database")
}
}
数据库等配置, 我们使用.env文件来保存
# web运行端口
PORT=4000
# 随机生成的UUID, 用于生成JWT
SECRET=8d6f7b7d-2f24-4867-ae90-feb4fc646693
# MYSQL数据库连接地址
MYSQL_DB_URL=root:123456@tcp(127.0.0.1:3306)/quick-starter?charset=utf8mb4&parseTime=True&loc=Local
# SQLITE数据库连接地址
SQLITE_MDB_URL=./dev.db
go语言加载.env文件
// initializers/loadEnv.go
/*
* @Author Malred
* @Date 2025-06-15 21:24:15
* @Description 加载.env文件中的环境变量
*/
package initializers
import (
"github.com/joho/godotenv"
"log"
)
func LoadEnvVariables() {
// 加载.env, 里面设置了port, gin会读取并设置端口号
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
}
声明对应数据库表的模型model
// models/user.go
/*
* @Author Malred
* @Date 2025-06-15 21:35:54
* @Description
*/
package models
type User struct {
ID uint `gorm:"primarykey"`
Username string
Password string
Email string
}
// models/post.go
/*
* @Author Malred
* @Date 2025-06-15 21:36:06
* @Description
*/
package models
import "gorm.io/gorm"
type Post struct {
gorm.Model
Title string
Body string
}
用代码生成数据库表
// migrate/migrate.go
/*
* @Author Malred
* @Date 2025-06-15 21:22:42
* @Description 使用gorm框架的方法, 创建数据库表
*/
package main
import (
"quick-start/initializers"
"quick-start/models"
)
func init() {
initializers.LoadEnvVariables()
initializers.ConnectToDB()
}
func main() {
// 创建数据库
initializers.DB.AutoMigrate(&models.User{})
initializers.DB.AutoMigrate(&models.Post{})
}
实现对用户信息的基本操作:增加、删除、修改和查询。
// user_test.go
/*
* @Author Malred
* @Date 2025-06-15 21:43:05
* @Description
*/
package go_quick_starter
import (
"quick-start/initializers"
"quick-start/models"
"testing"
)
func CreateUser(user *models.User) error {
result := initializers.DB.Create(user)
return result.Error
}
func GetUserByID(id uint) (*models.User, error) {
var user models.User
result := initializers.DB.First(&user, id)
return &user, result.Error
}
func GetAllUsers() ([]models.User, error) {
var users []models.User
result := initializers.DB.Find(&users)
return users, result.Error
}
func UpdateUser(user *models.User) error {
result := initializers.DB.Save(user)
return result.Error
}
func DeleteUser(id uint) error {
result := initializers.DB.Delete(&models.User{}, id)
return result.Error
}
// 保存新增的用户的ID
var ID int = 1
func TestCreateUser(t *testing.T) {
initializers.LoadEnvVariables()
initializers.ConnectToDB()
user := &models.User{
Email: "[email protected]",
Password: "password123",
Username: "createuser",
}
err := CreateUser(user)
if err != nil || user.ID == 0 {
t.Errorf("Failed to create user: %v", err)
}
ID = int(user.ID)
}
func TestGetUserByID(t *testing.T) {
initializers.LoadEnvVariables()
initializers.ConnectToDB()
retrievedUser, err := GetUserByID(uint(ID))
if err != nil || retrievedUser.ID == 0 {
t.Errorf("Failed to retrieve user: %v", err)
}
}
func TestGetAllUsers(t *testing.T) {
initializers.LoadEnvVariables()
initializers.ConnectToDB()
// 插入两个用户
user1 := &models.User{Email: "[email protected]", Password: "pass", Username: "user1"}
user2 := &models.User{Email: "[email protected]", Password: "pass", Username: "user2"}
initializers.DB.Create(user1)
initializers.DB.Create(user2)
users, err := GetAllUsers()
if err != nil || len(users) < 2 {
t.Errorf("Failed to get all users: %v", err)
}
}
func TestUpdateUser(t *testing.T) {
initializers.LoadEnvVariables()
initializers.ConnectToDB()
// 更新用户
user := &models.User{
ID: uint(ID),
Email: "[email protected]",
Password: "password123",
Username: "oldname",
}
err := UpdateUser(user)
if err != nil {
t.Errorf("Failed to update user: %v", err)
}
}
func TestDeleteUser(t *testing.T) {
initializers.LoadEnvVariables()
initializers.ConnectToDB()
// 删除用户
err := DeleteUser(uint(ID))
if err != nil {
t.Errorf("Failed to delete user: %v", err)
}
}
为了保证用户信息安全,需要对用户的密码进行加密处理。
import (
"fmt"
"golang.org/x/crypto/bcrypt"
"os"
"quick-start/initializers"
"quick-start/models"
"testing"
"time"
)
func TestBcryptPasswordEncode(t *testing.T) {
user := &models.User{
Email: "[email protected]",
Password: "password123",
Username: "createuser",
}
// 密码加盐(加密)
hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 10)
if err != nil {
t.Errorf("Failed to hash password")
}
fmt.Println(hash)
fmt.Println(string(hash))
}
JWT(JSON Web Token)是一种开放标准,用于在网络应用环境间安全地传递声明。创建JWT令牌是为了实现用户身份验证。
import (
"fmt"
"github.com/golang-jwt/jwt/v4"
"golang.org/x/crypto/bcrypt"
"os"
"quick-start/initializers"
"quick-start/models"
"testing"
"time"
)
func TestJWT(t *testing.T) {
user := &models.User{
Email: "[email protected]",
Password: "password123",
Username: "createuser",
}
// 密码加盐(加密)
hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 10)
if err != nil {
t.Errorf("Failed to hash password")
}
fmt.Println(hash)
fmt.Println(string(hash))
// 校验密码
err = bcrypt.CompareHashAndPassword([]byte(hash), []byte(user.Password))
if err != nil {
t.Errorf("Invalid password")
}
// 生成jwt令牌(传输协议用https,加密传输,防止jwt泄露)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": user.ID,
// 过期时间
"exp": time.Now().Add(time.Hour * 24 * 30).Unix(),
})
// 传入密钥,加密
tokenStr, err := token.SignedString([]byte(os.Getenv("SECRET")))
if err != nil {
t.Errorf("Failed to generate token")
}
fmt.Println(tokenStr)
}
实现对帖子信息的基本操作:增加、删除、修改和查询。
// /post_test.go
package go_quick_starter
import (
"quick-start/initializers"
"quick-start/models"
"testing"
)
func CreatePost(post *models.Post) error {
result := initializers.DB.Create(post)
return result.Error
}
func GetPostByID(id uint) (*models.Post, error) {
var post models.Post
result := initializers.DB.First(&post, id)
return &post, result.Error
}
func GetAllPosts() ([]models.Post, error) {
var posts []models.Post
result := initializers.DB.Find(&posts)
return posts, result.Error
}
func UpdatePost(post *models.Post) error {
result := initializers.DB.Save(post)
return result.Error
}
func DeletePost(id uint) error {
result := initializers.DB.Delete(&models.Post{}, id)
return result.Error
}
func TestCreatePost(t *testing.T) {
initializers.LoadEnvVariables()
initializers.ConnectToDB()
post := &models.Post{
Title: "test",
Body: "test body",
}
err := CreatePost(post)
if err != nil || post.ID == 0 {
t.Errorf("Failed to create user: %v", err)
}
ID = int(post.ID)
}
func TestGetPostByID(t *testing.T) {
initializers.LoadEnvVariables()
initializers.ConnectToDB()
retrievedPost, err := GetPostByID(uint(ID))
if err != nil || retrievedPost.ID == 0 {
t.Errorf("Failed to retrieve user: %v", err)
}
}
func TestGetAllPosts(t *testing.T) {
initializers.LoadEnvVariables()
initializers.ConnectToDB()
// 插入两个用户
post1 := &models.Post{
Title: "test",
Body: "test body",
}
post2 := &models.Post{
Title: "test",
Body: "test body",
}
initializers.DB.Create(post1)
initializers.DB.Create(post2)
users, err := GetAllPosts()
if err != nil || len(users) < 2 {
t.Errorf("Failed to get all users: %v", err)
}
}
func TestUpdatePost(t *testing.T) {
initializers.LoadEnvVariables()
initializers.ConnectToDB()
// 更新用户
post := &models.Post{
Title: "test",
Body: "test body",
}
err := UpdatePost(post)
if err != nil {
t.Errorf("Failed to update user: %v", err)
}
}
func TestDeletePost(t *testing.T) {
initializers.LoadEnvVariables()
initializers.ConnectToDB()
// 删除用户
err := DeletePost(uint(ID))
if err != nil {
t.Errorf("Failed to delete user: %v", err)
}
}
restful风格API:
HTTP 方法|操作类型|示例
GET|查询资源|获取用户列表 /api/users
POST|创建资源|创建新用户 /api/users
PUT|更新资源|更新指定用户 /api/users/{id}
DELETE|删除资源|删除指定用户 /api/users/{id}
// /main.go
/*
* @Author Malred
* @Date 2025-06-16 08:00:23
* @Description
*/
package main
import (
"github.com/gin-gonic/gin"
"quick-start/controllers"
"quick-start/initializers"
"quick-start/middleware"
)
// 启动时自动调用
func init() {
initializers.LoadEnvVariables()
initializers.ConnectToDB()
}
func main() {
r := gin.Default()
r.Use(middleware.Cors())
{
r.GET("posts", controllers.PostsIndex)
r.GET("posts/:id", controllers.PostsShow)
r.GET("posts/page", controllers.PostsPage)
r.POST("posts", middleware.RequireAuthHeader, controllers.PostsCreate)
r.PATCH("posts/:id", middleware.RequireAuthHeader, controllers.PostsUpdate)
r.DELETE("posts/:id", middleware.RequireAuthHeader, controllers.PostsDelete)
}
{
r.GET("auth/profile", middleware.RequireAuthHeader, controllers.Validate)
r.POST("users", controllers.Register)
r.POST("auth/login", controllers.Login)
}
r.Run()
}
实现用户的登录和注册功能,包括身份验证和账户创建。
// controllers/user.go
/*
* @Author Malred
* @Date 2025-06-16 08:00:44
* @Description
*/
package controllers
import (
"net/http"
"os"
"quick-start/initializers"
"quick-start/models"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4"
"golang.org/x/crypto/bcrypt"
)
// 注册
func Register(c *gin.Context) {
var body struct {
Username string `json:"username" validate:"min=5"`
Password string `json:"password" validate:"min=8"`
RetypedPassword string `json:"retypedPassword" validate:"min=8,eqfield=Password"`
Email string `json:"email" validate:"email"`
}
// 将json数据绑定到结构体
c.Bind(&body)
// 校验
err := validate.Struct(body)
if err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
})
return
}
var user models.User
initializers.DB.
Where("email = ?", body.Email).
First(&user)
// 已存在(其实username也是唯一的,设计在表里了)
if user != (models.User{}) {
c.JSON(500, gin.H{
"error": "the email is already exist!",
})
return
}
// hash the password
hash, err := bcrypt.GenerateFromPassword([]byte(body.Password), 10)
if err != nil {
c.JSON(500, gin.H{
"error": "failed to hash password",
})
return
}
user = models.User{
Username: body.Username,
Password: string(hash),
Email: body.Email,
}
result := initializers.DB.
Create(&user)
if result.Error != nil {
c.JSON(500, gin.H{
"error": "failed to create user",
})
return
}
c.JSON(200, gin.H{
"user": user,
})
}
// 登录
func Login(c *gin.Context) {
var body struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := c.Bind(&body); err != nil {
c.JSON(400, gin.H{
"error": "failed to bind the body!",
})
return
}
var user models.User
initializers.DB.
Where("username = ?", body.Username).
Find(&user)
if user == (models.User{}) {
c.JSON(500, gin.H{
"error": "con't find the user!",
})
return
}
// 校验密码
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(body.Password))
if err != nil {
c.JSON(400, gin.H{
"error": "failed to hash password",
})
return
}
// 生成jwt令牌(传输协议用https,加密传输,防止jwt泄露)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": user.ID,
// 过期时间
"exp": time.Now().Add(time.Hour * 24 * 30).Unix(),
})
// 传入密钥,加密
tokenStr, err := token.SignedString([]byte(os.Getenv("SECRET")))
if err != nil {
c.JSON(400, gin.H{
"error": "Failed to create token",
})
return
}
// 设置cookie
c.SetSameSite(http.SameSiteLaxMode)
c.SetCookie("Authorization", tokenStr, 3600*24*30, "", "", false, true)
c.JSON(200, gin.H{
"token": tokenStr,
"userId": user.ID,
})
}
// 校验,返回用户信息
func Validate(c *gin.Context) {
id, _ := c.Get("id")
uname, _ := c.Get("username")
email, _ := c.Get("email")
c.JSON(200, gin.H{
"id": id,
"username": uname,
"email": email,
})
}
提供获取、发布、更新和删除帖子信息的 API 接口。
// controllers/post.go
/*
* @Author Malred
* @Date 2025-06-16 08:00:47
* @Description
*/
package controllers
import (
"quick-start/initializers"
"quick-start/models"
"strconv"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
)
var validate *validator.Validate
func init() {
validate = validator.New()
}
// 添加
func PostsCreate(c *gin.Context) {
// 获取json数据
var body struct {
Body string `json:"body" validate:"min=6,max=255"`
Title string `json:"title" validate:"min=3,max=20"`
}
// 将json数据绑定到结构体
c.Bind(&body)
// 校验
err := validate.Struct(body)
if err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
})
return
}
post := models.Post{
Title: body.Title,
Body: body.Body,
}
// 创建
result := initializers.DB.Create(&post)
if result.Error != nil {
c.Status(400)
return
}
c.JSON(200, gin.H{
"post": post,
})
}
// 查询所有
func PostsIndex(c *gin.Context) {
var posts []models.Post
initializers.DB.Find(&posts)
c.JSON(200, gin.H{
"posts": posts,
})
}
// 分页查询
func PostsPage(c *gin.Context) {
limitStr := c.Query("limit")
curPageStr := c.Query("currentPage")
limit, err := strconv.Atoi(limitStr)
if err != nil {
c.Status(500)
return
}
curPage, err := strconv.Atoi(curPageStr)
if err != nil {
c.Status(500)
return
}
var posts []models.Post
initializers.DB.
Scopes(models.Paginate(curPage, limit)).
Find(&posts)
c.JSON(200, gin.H{
"posts": posts,
})
}
// 根据id查询
func PostsShow(c *gin.Context) {
id := c.Param("id")
var post models.Post
initializers.DB.First(&post, id)
c.JSON(200, gin.H{
"post": post,
})
}
// 修改
func PostsUpdate(c *gin.Context) {
id := c.Param("id")
var body struct {
Body string `json:"body" validate:"min=6,max=255"`
Title string `json:"title" validate:"min=3,max=20"`
}
// 将json数据绑定到结构体
c.Bind(&body)
// 校验
err := validate.Struct(body)
if err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
})
return
}
var post models.Post
initializers.DB.First(&post, id)
initializers.DB.Model(&post).Updates(models.Post{
Title: body.Title,
Body: body.Body,
})
c.JSON(200, gin.H{
"post": post,
})
}
// 删除
func PostsDelete(c *gin.Context) {
id := c.Param("id")
initializers.DB.Delete(&models.Post{}, id)
c.Status(200)
}
鉴权中间件用于验证用户身份,确保只有授权用户才能访问特定资源。
跨域中间件用于解决跨域请求问题,允许来自不同域的请求访问服务器资源。
// middleware/auth.go
/*
* @Author Malred
* @Date 2025-06-16 08:01:36
* @Description
*/
package middleware
import (
"fmt"
"net/http"
"os"
"quick-start/initializers"
"quick-start/models"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4"
)
// 解决跨域问题
func RequireAuthHeader(c *gin.Context) {
// 获取token
tokenStr := c.Request.Header.Get("Authorization")
fmt.Println(tokenStr[:7])
if tokenStr[:6] != "Bearer" {
c.AbortWithStatus(http.StatusUnauthorized)
}
// 验证token
token, err := jwt.Parse(tokenStr[7:], func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return []byte(os.Getenv("SECRET")), nil
})
if err != nil {
fmt.Println(token)
c.AbortWithStatus(http.StatusUnauthorized)
}
if cliams, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
// 过期
if float64(time.Now().Unix()) > cliams["exp"].(float64) {
c.AbortWithStatus(http.StatusUnauthorized)
}
// 根据jwt携带消息(userId)查询数据库
var user models.User
initializers.DB.First(&user, cliams["sub"])
if user.ID == 0 {
c.AbortWithStatus(http.StatusUnauthorized)
}
// c.Set("user", user)
c.Set("id", user.ID)
c.Set("username", user.Username)
c.Set("email", user.Email)
c.Next()
} else {
c.AbortWithStatus(http.StatusUnauthorized)
}
}
// middleware/cors.go
/*
* @Author Malred
* @Date 2025-06-16 08:01:40
* @Description
*/
package middleware
import (
"net/http"
"github.com/gin-gonic/gin"
)
// 解决跨域问题
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
origin := c.Request.Header.Get("Origin")
c.Header("Access-Control-Allow-Origin", origin)
// c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Headers", "*")
c.Header("Access-Control-Allow-Methods", "*")
c.Header("Access-Control-Expose-Headers", "Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Content-Type")
c.Header("Access-Control-Max-Age", "3600")
c.Header("Access-Control-Allow-Credentials", "true")
//放行索引options
if method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
}
//处理请求
c.Next()
}
}
APIfox是一个接口测试工具
apifox
本案例的接口我已经共享了:
接口文档
暂时不了解, 可以看看别人的回答
link
你可以在这些平台联系我: