引言:
做为前端工程师,尤其是只会前端技术栈的,大概都想成为全栈工程师吧。本人所在的公司前后端为一个组,由后端工程师作为组长,他是非常瞧不起前端工作的,在公司里总是区别对待。 实不相瞒,作者虽是计算机专科出身,但进入公司才开始真正学习计算机相关知识。真tm是纯小白全凭胆子大,一步步混到今天。可能今时不同往日,现在零基础的应届生应该是不能像作者一样工作满天飞了。 本人巨懒,也不想看什么零基础学IT,月薪过万的视频。直接莽夫穿针,上手开搞,边查gpt和文档边撸码。总之就是先有想法,然后上网查怎么用代码实现,后端语言使用go开发,搭配使用gorm和gin框架。这次搞了一个简单的全栈项目《智能运动健康系统》一个大屏项目,总共就两个页面,一个登录页,一个主页。
项目展示
登录页
主页
仓库地址
在文档中可以看到作者对项目的介绍
目录结构
这样子看可能更一目了然一些
Back-end/
├── cmd/
│ └── main.go # 应用程序入口点
│
├── internal/
│ ├── controller/
│ │ └── user_controller.go # 用户控制器
│ │
│ ├── model/
│ │ └── user.go # 用户模型
│ │
│ ├── service/
│ │ └── user_service.go # 用户服务
│ │
│ └── router/
│ └── router.go # 路由配置
│
└── pkg/
└── database/
└── mysql.go # 数据库连接配置
主要目录结构解析:
-
/cmd
-
项目的主要入口点
-
包含 main.go,负责启动和初始化应用程序
-
这是一个常见的Go项目最佳实践,将主程序入口与业务逻辑分开
-
/internal
-
私有应用程序和库代码
-
这个目录下的代码只能被当前项目使用,不能被其他项目导入
-
包含以下重要子目录:
-
/controller:处理HTTP请求,负责接收请求和返回响应
-
/model:定义数据结构和数据库模型
-
/router:设置API路由规则
-
/service:包含业务逻辑处理层
-
/pkg
-
可以被外部应用程序使用的库代码
-
包含 /database 目录,提供数据库连接和操作的功能
-
这里的代码可以被其他项目引用
-
项目配置文件
-
go.mod:定义项目的模块路径和依赖关系
-
go.sum:记录依赖包的版本和哈希值,确保依赖的完整性
分层架构说明:
这是一个典型的三层架构设计:
-
表现层(Controller):处理HTTP请求和响应
-
业务层(Service):处理业务逻辑
-
数据层(Model):处理数据存储和检索
目录结构搭建完成后,开始加入必要的代码
如何搭建后端项目
1. main.go文件,相当于前端中的main.js,一些初始化和设置
package main
import (
"health-system/internal/controller"
"health-system/internal/router"
"health-system/internal/service"
"health-system/pkg/database"
"log"
)
func main() {
// 初始化数据库连接
db, err := database.InitDB()
if err != nil {
log.Fatalf("数据库初始化失败: %v", err)
}
// 初始化服务和控制器
userService := service.NewUserService(db)
userController := controller.NewUserController(userService)
// 初始化身体数据服务和控制器
bodyDataService := service.NewBodyDataService(db)
bodyDataController := controller.NewBodyDataController(bodyDataService)
// 设置路由
r := router.SetupRouter(userController, bodyDataController)
// 启动服务器
if err := r.Run(":8080"); err != nil {
log.Fatalf("服务器启动失败: %v", err)
}
}
2. go.mod文件,相当于前端中的package.json
值得注意的是定义的这个模块名module health-system,在其他页面中引入moudle时根路径 就是这个
3. mysql.go文件
InitDB函数中dsn为数据库信息
DSN(Data Source Name)字符串包含以下部分:
root: 数据库用户名
123456: 数据库密码
127.0.0.1:3306: 数据库服务器地址和端口
intelligent_sport_health_system: 数据库名称
charset=utf8mb4: 使用utf8mb4字符集以支持完整的Unicode字符集
parseTime=True: 允许将MySQL的datetime类型自动转换为Go的time.Time类型
loc=Local: 使用系统本地时区
添加数据自动迁移,这里需要把用到的model都添加进来。有了这个功能你在业务里操作数据库,数据库会自动同步
// 自动迁移数据库结构
err = db.AutoMigrate(&model.User{}, &model.BodyData{}) // 添加 BodyData 模型
if err != nil {
return nil, fmt.Errorf("数据库迁移失败: %v", err)
}
下面为完整mysql.go
package database
import (
"fmt"
"health-system/internal/model"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func InitDB() (*gorm.DB, error) {
dsn := "root:123456@tcp(127.0.0.1:3306)/intelligent_sport_health_system?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
// 禁用默认事务
SkipDefaultTransaction: true,
})
if err != nil {
return nil, fmt.Errorf("连接数据库失败: %v", err)
}
// 自动迁移数据库结构
err = db.AutoMigrate(&model.User{}, &model.BodyData{}) // 添加 BodyData 模型
if err != nil {
return nil, fmt.Errorf("数据库迁移失败: %v", err)
}
return db, nil
}
4. router.go文件,这个好理解就是路由
package router
import (
"health-system/internal/controller"
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func SetupRouter(userController *controller.UserController, bodyDataController *controller.BodyDataController) *gin.Engine {
r := gin.Default()
// CORS 中间件配置
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"*"}, // 允许所有来源
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, // 允许的 HTTP 方法
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"}, // 允许的请求头
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour, // 预检请求结果缓存时间
}))
// 用户相关路由
userGroup := r.Group("/api/users")
{
userGroup.POST("/register", userController.Register)
userGroup.POST("/login", userController.Login)
userGroup.GET("/:id", userController.GetUserInfo) // 获取用户信息
userGroup.PUT("/:id", userController.UpdateUserInfo) // 更新用户信息
}
// 身体数据相关路由
bodyDataGroup := r.Group("/api/body-data")
{
bodyDataGroup.GET("/user/:id", bodyDataController.GetUserBodyData) // 获取用户所有身体数据
bodyDataGroup.GET("/user/:id/latest", bodyDataController.GetUserLatestBodyData) // 获取最新数据和变化
}
return r
}
从代码中可以看出对外提供了哪些接口,就是前端使用axios请求的那些url
在路由中解决了跨域问,在此为了简单对外全部开放
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"*"}, // 允许所有来源
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, // 允许的 HTTP 方法
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"}, // 允许的请求头
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour, // 预检请求结果缓存时间
}))
配置完这些条条框框,真正业务逻辑的地方就开始码了
以user model为例
5. user.go文件
首先要先定义模型,就是你需要用到哪些字段,还有字段类型,是否必填
package model
import "time"
// User 用户模型结构体
type User struct {
ID uint `gorm:"primarykey"`
Username string `gorm:"column:username;type:varchar(255);not:null"`
Password string `gorm:"column:password;type:varchar(255);not null"`
Account string `gorm:"column:account;type:varchar(255);default null"`
Gender string `gorm:"column:gender;type:varchar(10);default:null"`
Birthday time.Time `gorm:"column:birthday;type:date;default:null"`
Phone string `gorm:"column:phone;type:varchar(20);default:null"`
Email string `gorm:"column:email;type:varchar(255);default:null"`
CreatedAt time.Time `gorm:"column:created_at;type:timestamp;not null;default:CURRENT_TIMESTAMP"`
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp;not null;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"`
}
// TableName 指定表名
func (User) TableName() string {
return "users"
}
6. user_service.go文件
定义完结构就该写逻辑业务了
package service
import (
"errors"
"fmt"
"health-system/internal/model"
"time"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
type UserService struct {
db *gorm.DB
}
func NewUserService(db *gorm.DB) *UserService {
return &UserService{db: db}
}
// 注册
func (s *UserService) Register(username, password string) error {
// 检查用户名是否已存在
var existingUser model.User
err := s.db.Where("username = ?", username).First(&existingUser).Error
if err != nil && err != gorm.ErrRecordNotFound {
return fmt.Errorf("检查用户名时出错: %v", err)
}
if err == nil {
return errors.New("用户名已存在")
}
// 密码加密
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return fmt.Errorf("密码加密失败: %v", err)
}
// 创建新用户,只设置用户名和密码
result := s.db.Exec("INSERT INTO users (username, password) VALUES (?, ?)", username, string(hashedPassword))
if result.Error != nil {
return fmt.Errorf("创建用户失败: %v", result.Error)
}
return nil
}
// 登录
func (s *UserService) Login(username, password string) (*model.User, string, error) {
var user model.User
if err := s.db.Where("username = ?", username).First(&user).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, "", errors.New("用户不存在")
}
return nil, "", fmt.Errorf("查询用户失败: %v", err)
}
// 验证密码
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
return nil, "", errors.New("密码错误")
}
// 生成token
token := fmt.Sprintf("user_%d_%d", user.ID, time.Now().Unix())
return &user, token, nil
}
// GetUserInfo 获取用户信息
func (s *UserService) GetUserInfo(userID string) (*model.User, error) {
var user model.User
if err := s.db.First(&user, userID).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, errors.New("用户不存在")
}
return nil, fmt.Errorf("查询用户失败: %v", err)
}
return &user, nil
}
// UpdateUserInfo 更新用户信息
func (s *UserService) UpdateUserInfo(userID, account, gender string, birthday time.Time, phone, email string) error {
result := s.db.Model(&model.User{}).Where("id = ?", userID).Updates(map[string]interface{}{
"account": account,
"gender": gender,
"birthday": birthday,
"phone": phone,
"email": email,
})
if result.Error != nil {
return fmt.Errorf("更新用户信息失败: %v", result.Error)
}
if result.RowsAffected == 0 {
return errors.New("用户不存在")
}
return nil
}
这里就不过多赘述了,即使不懂语法也能知道写了什么东西,都有什么作用。一句话业务逻辑就往这里堆就对了
7. user_controller.go
这里用来把上面写好的业务,处理HTTP请求,负责接收和返回响应。代码一大堆,抽一个展示
// GetUserInfo 获取用户信息
func (c *UserController) GetUserInfo(ctx *gin.Context) {
userID := ctx.Param("id")
user, err := c.userService.GetUserInfo(userID)
if err != nil {
ctx.JSON(http.StatusOK, UserInfoResponse{
Success: false,
Detail: err.Error(),
})
return
}
ctx.JSON(http.StatusOK, UserInfoResponse{
Success: true,
Detail: "获取用户信息成功",
Data: &UserInfo{
Username: user.Username,
Account: user.Account,
Gender: user.Gender,
Birthday: user.Birthday,
Phone: user.Phone,
Email: user.Email,
},
})
}
8. 后续添加model
核心依然是创建model,service和controller,思路和业务逻辑都在这三块
1. 相应的配置需要添加路由
2. 在主函数中引入路由
3. 自动迁移数据库中添加model
做到这里,一个后端项目雏形心中大概有数了。光说不练假把式,还是得动手实践