首页 > 技术文章 > go实践之apiserver搭建

0pandas0 2018-12-08 11:31 原文

go实践之apiserver搭建

本文主要记录下博主用gin搭建app server的过程,方便后续学习。web框架用的gin,日志用的zap,数据库连接用的mysql driver,配置文件读取用的是viper。整个项目的框架如下:

.
├── app
│   ├── comment.go
│   ├── server.go
│   └── user.go
├── config.toml
├── Gopkg.lock
├── Gopkg.toml
├── LICENSE
├── main.go
├── README.md
├── tool
│   ├── config
│   │   ├── config.go
│   │   └── config_test.go
│   ├── db
│   │   ├── mysql.go
│   │   └── mysql_test.go
│   └── log
│       ├── log.go
│       └── log_test.go
└── vendor
    ├── github.com
    ......

main.go作为整个api server 的入口文件,tool文件夹下面存放了日志,数据库,配置文件相关代码。app文件夹里面是api实现相关的代码。

1、配置文件读取

配置文件读取的代码如下:

package config

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

func ParseConfig() error {
    viper.SetConfigName("config") // name of config file (without extension)
    viper.AddConfigPath("./conf") // call multiple times to add many search paths
    viper.AddConfigPath(".")    // optionally look for config in the working directory
    err := viper.ReadInConfig() // Find and read the config file
    if err != nil {             // Handle errors reading the config file
        return  fmt.Errorf("Fatal error config file: %s \n", err)
    }
    viper.WatchConfig()
    return nil
}

这里只是简单的一个demo,viper还有更多的功能,比如设置默认值 viper.SetDefault("ContentDir", "content"),可以加载环境变量中的值AutomaticEnv(),热更新配置文件 viper.WatchConfig()。具体可以参考官方文档:链接

2、数据连接

本文件主要实现的是数据库连接,以及增删查改操作。具体代码如下:

package db

import (
	"database/sql"
	_ "github.com/go-sql-driver/mysql"
	"time"
	"github.com/spf13/viper"
	"fmt"
)

type DbConn struct {
	Db *sql.DB
}


func InitDbConn()(*DbConn, error) {
	dbConn := viper.GetString("Database.DbConn")
	db, err := sql.Open("mysql", dbConn)
	if err != nil {
		return nil, fmt.Errorf("get db connection error")
	}
	maxOpenConns := viper.GetInt("Database.MaxOpenConn") //从配置文件中获取配置值
	maxIdleConns := viper.GetInt("Database.MaxIdleConn")
	maxLifetime := viper.GetInt("Database.MaxLifetime")

	......//省略部分代码
    
	err = db.Ping()
	if err != nil {
		return nil,fmt.Errorf("connect to db error")
	}else{
		dbconn := &DbConn{Db:db}
		return dbconn,nil
	}
}

func (c *DbConn) Insert(sql string, args ...interface{}) (lastInsertId int64, err error) {
	res, err := c.Db.Exec(sql, args...)
	if err != nil {
		return
	}
	return res.LastInsertId()
}
......//省略部分代码

主要是调用mysql driver根据配置文件配置值连接数据库。

3、日志初始化

日志主要的是uber的zap日志框架,这是一个高性能的日志框架,在这里有点小用它了。也只是一个简单的使用举例。更多功能需要去学习。

package log

import (
	"fmt"
	"strings"

	"github.com/spf13/viper"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

var Logger *zap.SugaredLogger //global logger

func InitLog() {
	logLevel := viper.GetString("Log.LogLevel")
	logName := viper.GetString("Log.LogName")
	logAge := viper.GetInt("Log.LogAge")
	logSize := viper.GetInt("Log.LogSize")

	w := zapcore.AddSync(&lumberjack.Logger{
		Filename:   logName,
		MaxSize:    logSize, // megabytes
		MaxBackups: 3,
		LocalTime:  true,
		MaxAge:     logAge, // days
	})
	zapLogLevel := zap.NewAtomicLevel()
	if err := zapLogLevel.UnmarshalText([]byte(strings.ToLower(logLevel))); err != nil {
		panic(fmt.Errorf("get config log level:%v config error: %v", logLevel, err))
	}
    
	.....//此处省略部分代码
    
	logger := zap.New(core, zap.AddCaller())
	Logger = logger.Sugar()
	Logger.Info("logger init successful!")
}

4、server初始化

主要就是调用配置读取函数,数据库连接和日志初始化函数,再启动http server

package main

import (
   "fmt"
   "os"
   "os/signal"
   "syscall"

   "github.com/zhanben/go_site/app"
   "github.com/zhanben/go_site/tool/config"
   "github.com/zhanben/go_site/tool/db"
   "github.com/zhanben/go_site/tool/log"

   "go.uber.org/zap"
)

func main() {
   //Read config file
   err := config.ParseConfig()
   if err != nil {
      panic(fmt.Errorf("Failed to read config file: %s \n", err))
   }

   //Init log
   log.InitLog()

   //Init db connection
   db, err := db.InitDbConn()
   if err != nil {
      panic("connect db error!")
   }
   log.Logger.Info("Db init successful!")

   //start http sever
   startServer(db)
}

.....//此处省略部分代码

5、接口编写

下面是简单的两个接口实现的例子:一个是URL中带有参数,一个没有带的。

package app

import (
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"net/http"
	"github.com/zhanben/go_site/tool/db"
)

.....//此处省略部分代码

func (u *User) initRouter(r *gin.RouterGroup) {
	//在此添加接口
	r.GET("/users", u.getAllUsers)      //获取所有用户信息
	r.GET("/users/:name", u.getOneUser) //根据用户名获取用户详细信息
}

func (u *User) getAllUsers(c *gin.Context) {
	//构建返回结构体
	res := map[string]interface{}{
		"Action":  "GetAllUserResponse",
		"RetCode": 0,
	}

	sql := "select * from user limit 10"
	result, err := u.db.Select(sql)
	if err != nil {
		u.Logger.Error("get user info from db error!")
		abortWithError(u.Logger, c, err)
	}
	res["UserInfo"] = result
	c.JSON(http.StatusOK, res)
}

func (u *User) getOneUser(c *gin.Context) {
	//构建返回结构体
	res := map[string]interface{}{
		"Action":  "GetOneUserResponse",
		"RetCode": 0,
	}
	userName, ok :=c.Params.Get("name")
	if !ok {
        u.Logger.Error("parameter name must be fixed!")
    }
	u.Logger.Infof("get user name from url:%s",userName)

    sql := "select * from user where username=?"
    result, err := u.db.Select(sql,userName)
    if err != nil {
        u.Logger.Error("get user info from db error!")
        res["RetCode"]= "-1"
        res["Error"] = "user not exist!"
    }else{
        res["UserInfo"] = result
	    u.Logger.Info("get one user info successful!")
    }
	c.JSON(http.StatusOK, res)
}

至此一个简单的go api sever的框架就搭建好了。当然后续还有单元测试的代码需要补充。本文的全部的代码可以到github下载:git clone --branch v0.1 git@github.com:Zhanben/go_site.git

推荐阅读