Go项目搭建 | 青训营笔记

77 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天

这篇文章介绍如何搭建一个go 语言的web项目,介绍如何读取配置文件设置路由信息实现一个简单的数据库查询

读取配置文件

导入工具

使用 viper工具 读取配置文件中的内容

导入viper

go get github.com/spf13/viper

读取配置文件

配置文件

在根目录下创建config.yml,作为配置文件

system:
  # 设定模式(debug/release/test,正式版改为release)
  mode: debug
  # url前缀
  url-path-prefix: /douyin
  # 主机名
  host: localhost
  # 程序监听端口
  port: 8080

mysql:
  # 用户名
  username: root
  # 密码
  password: xxx
  # 数据库名
  database: mini_douyin
  # 主机地址
  host: localhost
  # 端口
  port: 3306
  # 连接字符串参数
  query: parseTime=True&loc=Local&timeout=10000ms
  # 是否打印日志
  log-mode: true
  # 数据库表前缀(无需再末尾添加下划线, 程序内部自动处理)
  table-prefix: tb
  # 编码方式
  charset: utf8mb4
  # 字符集(utf8mb4_general_ci速度比utf8mb4_unicode_ci快些)
  collation: utf8mb4_general_ci

config.go

在项目中创建config.go读取配置文件

总体思路:

  1. 创建结构体,对应yml 文件中的格式。示例代码中,创建一个结构体 config,对应配置文件的第一层 mysql ****、system,然后再创建具体的结构体MysqlConfigSystem,对应第一层下所有的键值对;
  1. 使用viper 读取 配置文件的信息。使用viper将配置文件的信息映射到结构体 config中,再将其读到全局配置变量 Conf 中即可,然后就可以通过Conf来获取配置文件中的值
package config

import (
	"fmt"
	"github.com/spf13/viper"
	"os"
)

// Conf 全局配置变量
var Conf = new(config)

type config struct {
	Mysql  *MysqlConfig `mapstructure:"mysql" json:"mysql"`
	System *System      `mapstructure:"system" json:"system"`
}

func InitConfig() {
	workDir, err := os.Getwd()
	if err != nil {
		panic(fmt.Errorf("读取应用目录失败:%s \n", err))
	}

	// 设置文件路径
	viper.SetConfigName("config")
	viper.SetConfigType("yml")
	viper.AddConfigPath(workDir + "./")

	err = viper.ReadInConfig()
	if err != nil {
		panic(fmt.Errorf("读取配置文件失败:%s \n", err))
	}

	// 将viper 中的json 信息映射到 全局Conf中
	if err := viper.Unmarshal(Conf); err != nil {
		panic(fmt.Errorf("初始化配置文件失败:%s \n", err))
	}

}

// MysqlConfig 对应yml的中 mysql
type MysqlConfig struct {
	Username    string `mapstructure:"username" json:"username"`
	Password    string `mapstructure:"password" json:"password"`
	Database    string `mapstructure:"database" json:"database"`
	Host        string `mapstructure:"host" json:"host"`
	Port        int    `mapstructure:"port" json:"port"`
	Query       string `mapstructure:"query" json:"query"`
	LogMode     bool   `mapstructure:"log-mode" json:"logMode"`
	TablePrefix string `mapstructure:"table-prefix" json:"tablePrefix"`
	Charset     string `mapstructure:"charset" json:"charset"`
	Collation   string `mapstructure:"collation" json:"collation"`
}

// System 对应yml中的system
type System struct {
	Mode          string `mapstructure:"mode" json:"mode"`
	UrlPathPrefix string `mapstructure:"url-path-prefix" json:"urlPathPrefix"`
	Host          string `mapstructure:"host" json:"host"`
	Port          int    `mapstructure:"port" json:"port"`
}

database.go

以上读取了文件中的信息,那么我们就可以通过全局的Conf配置我数据库的信息了。

var DB *gorm.DB

func InitMysql() {
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&collation=%s&%s",
		config.Conf.Mysql.Username,
		config.Conf.Mysql.Password,
		config.Conf.Mysql.Host,
		config.Conf.Mysql.Port,
		config.Conf.Mysql.Database,
		config.Conf.Mysql.Charset,
		config.Conf.Mysql.Collation,
		config.Conf.Mysql.Query,
	)
	db, err := gorm.Open("mysql", dsn)
	if err != nil {
		log.Printf("数据库连接错误:%s", err)
		return
	}
	// 全局赋值
	DB = db
	log.Printf("数据库连接成功")
}

设置路由信息

导入gin

go get -u github.com/gin-gonic/gin

建立routes.go文件

创建 InitRoutes 函数,通过Conf获取前置路径,建立路由分组

package routes

import (
	"github.com/gin-gonic/gin"
	"mini-douyin/config"
	"mini-douyin/middleware"
)

func InitRoutes() *gin.Engine {
	engine := gin.Default()

	// 配置全局跨域中间件
	engine.Use(middleware.CORSMiddleware())

	// 路由分组 添加前缀
	group := engine.Group(config.Conf.System.UrlPathPrefix)

	InitUserRoutes(group)
	return engine
}

建立user_route.go文件

设置详细的路由信息即可,建立InitUserRoutes函数,然后在routes.go中调用即可

package routes

import (
	"github.com/gin-gonic/gin"
	"mini-douyin/controller"
)

// InitUserRoutes 注册用户路由
func InitUserRoutes(r *gin.RouterGroup) gin.IRoutes {
	userController := controller.NewUserController()
	router := r.Group("/user")

	{
		router.GET("", userController.GetUserInfo)
	}

	return r
}

启动项目

编写main.go文件,调用 InitRoutesInitConfig , InitMysql方法,然后使用gin启动服务即可。

package main

func main() {

	// 加载配置文件到全局配置结构体
	config.InitConfig()

	// 初始化数据库
	common.InitMysql()

	// 注册所有路由
	r := routes.InitRoutes()

	host := config.Conf.System.Host
	port := config.Conf.System.Port
	srv := &http.Server{
		Addr:    fmt.Sprintf("%s:%d", host, port),
		Handler: r,
	}

	go func() {
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Printf("listen: %s\n", err)
		}
	}()
	
	quit := make(chan os.Signal)

	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	log.Printf("Shutting down server...")
	
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		log.Printf("Server forced to shutdown: %s", err)
	}

	log.Printf("Server exiting!")

}

实现简单查询

建立controllerservicerepository 文件夹

其实就是类似于Java的目录结构,有点类似依赖注入的实现,这里就不展开赘述了。附上各层的代码:

controller层代码

package controller

import (
	"github.com/gin-gonic/gin"
	"log"
	"mini-douyin/model/request/user"
	"mini-douyin/service"
	"net/http"
)

type IUserController interface {
	GetUserInfo(c *gin.Context) // 获取当前登录用户信息
}

type UserController struct {
	UserService service.IUserService
}

// NewUserController 构造函数
func NewUserController() IUserController {
	userService := service.NewUserService()
	userController := UserController{UserService: userService}
	return userController
}

func (uc UserController) GetUserInfo(c *gin.Context) {
	var userInfo user.InfoRequest
	err := c.ShouldBindQuery(&userInfo)
	if err != nil {
		log.Printf("参数错误: %v", err)
		return
	}
	infoResponse, err := uc.UserService.GetUserById(userInfo)
	if err != nil {
		return
	}
	c.JSON(http.StatusOK, infoResponse)
}

service 层代码

package service

import (
	"log"
	req_user "mini-douyin/model/request/user"
	resp_user "mini-douyin/model/response/user"
	"mini-douyin/repository"
	"strconv"
)

type IUserService interface {
	GetUserById(request req_user.InfoRequest) (resp_user.InfoResponse, error)
}

type UserService struct {
	UserRepository repository.IUserRepository
}

// NewUserService  构造函数
func NewUserService() IUserService {
	userRepository := repository.NewUserRepository()
	userService := UserService{UserRepository: userRepository}
	return userService
}

func (u UserService) GetUserById(request req_user.InfoRequest) (resp_user.InfoResponse, error) {
	id, i2 := strconv.Atoi(request.UserId)
	infoResponse := resp_user.InfoResponse{}

	if i2 != nil {
		log.Printf("格式转化错误: %v", request)
		return infoResponse, i2
	}

	userInfo, i2 := u.UserRepository.GetUserById(int64(id))
	log.Printf("用户信息为:%v", userInfo)

	infoResponse.User = userInfo

	return infoResponse, nil
}

repository层代码

package repository

import (
	"fmt"
	"mini-douyin/common"
	"mini-douyin/model/domain"
)

type IUserRepository interface {
	GetUserById(id int64) (domain.User, error) // 获取单个用户
}

// UserRepository 定义一个结构体
type UserRepository struct {
}

// NewUserRepository UserRepository构造函数
func NewUserRepository() IUserRepository {
	return UserRepository{}
}

// GetUserById 获取单个用户
func (ur UserRepository) GetUserById(id int64) (domain.User, error) {
	fmt.Println("GetUserById---")
	var user domain.User
	err := common.DB.Where("user_id = ?", id).First(&user).Error
	return user, err
}