前端入坑之零后端基础做项目新手版

240 阅读7分钟

引言:

做为前端工程师,尤其是只会前端技术栈的,大概都想成为全栈工程师吧。本人所在的公司前后端为一个组,由后端工程师作为组长,他是非常瞧不起前端工作的,在公司里总是区别对待。 实不相瞒,作者虽是计算机专科出身,但进入公司才开始真正学习计算机相关知识。真tm是纯小白全凭胆子大,一步步混到今天。可能今时不同往日,现在零基础的应届生应该是不能像作者一样工作满天飞了。 本人巨懒,也不想看什么零基础学IT,月薪过万的视频。直接莽夫穿针,上手开搞,边查gpt和文档边撸码。总之就是先有想法,然后上网查怎么用代码实现,后端语言使用go开发,搭配使用gorm和gin框架。这次搞了一个简单的全栈项目《智能运动健康系统》一个大屏项目,总共就两个页面,一个登录页,一个主页。

项目展示

登录页 image.png

主页 image.png

仓库地址

码云仓库项目地址

在文档中可以看到作者对项目的介绍

image.png

目录结构

这样子看可能更一目了然一些

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时根路径 就是这个

image.png

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. 相应的配置需要添加路由

image.png

2. 在主函数中引入路由

image.png

3. 自动迁移数据库中添加model

image.png

做到这里,一个后端项目雏形心中大概有数了。光说不练假把式,还是得动手实践