gin从零搭建属于你自己的go项目(三)
一、模块划分、分层
1、项目组织方式
按照正常的 mvc 模式中,肯定是先分层再去划分模块的,例如一个用户校验的模块,正常来说是先划分为model、controller、service 三层,model 层声明需要操作的数据或信息,例如数据库模型,contorller 负责具体的业务模块流程的控制,调用service层的接口来控制业务流程,而service 层就是具体的处理逻辑了,例如:
├── model
│ ├── userModel.go
├── controller
│ └── userCotroller
│ └── userCotroller.go
├── service
│ └── userService
│ └── userService.go
但是出于个人思考,决定使用先划分模块再分层的约定来组织项目
├── model // model层,用于存放model
│ └── user // user 这张数据库表的数据描述
│ └── user.go
├── src // 具体的代码资源
│ └── userAuth // 用户校验功能模块的代码,包括了controller、service和type
│ └── cotroller.go
│ └── service.go
│ └── type.go // 用于存放数据的类型
│ └── util.go // 用于存放适用该模块的工具方法
二、添加文件处理方法以及配置文件
(1) json 处理
1、安装
// 因为我们这里使用的是自带的包,所以不需要额外安装,直接导入就好
import (
"encoding/json"
)
2、封装一些常用方法
为了方便处理json,我们在根目录下新建一个tool文件夹,在里面再新建一个dataHanding来存放数据处理的相关方法,在它下面再新建一个json文件夹来存放处理json的方法
/tool/dataHanding/json/json.go
package JSONHandle
// 反序列化的方法
import (
"encoding/json"
"fmt"
"os"
)
//解码json文件为map
func jsonFileToMap(fileUrl string) map[string]interface{} {
//打开json文件
srcFile, _ := os.Open(fileUrl)
defer srcFile.Close()
//创建接收数据的map类型数据
datamap := make(map[string]interface{})
//创建解码器
decoder := json.NewDecoder(srcFile)
//解码
err := decoder.Decode(&datamap)
if err != nil {
fmt.Println("解码失败,err=", err)
return datamap
}
fmt.Println("解码成功", datamap)
return datamap
}
//将json数据转为map
func jsonToMap(jsonStr string) map[string]interface{} {
//将json数据转为字节数据
jsonbytes := []byte(jsonStr)
dataMap := make(map[string]interface{})
err := json.Unmarshal(jsonbytes, &dataMap)
if err != nil {
fmt.Println("反序列化失败,err=", err)
}
fmt.Println(dataMap)
return dataMap
}
//将json数据转换为map切片
func jsonToMapSlice(jsonStr string) []map[string]interface{} {
jsonBytes := []byte(jsonStr)
dataSlice := make([]map[string]interface{}, 0)
err := json.Unmarshal(jsonBytes, &dataSlice)
if err != nil {
fmt.Println("反序列化失败,err=", err)
}
fmt.Println(dataSlice)
return dataSlice
}
(2) yaml 文件处理
1、安装
go get gopkg.in/yaml.v2
2、使用
//定义conf类型
//类型里的属性,是配置文件里的属性
type Conf struct {
Service Serivce `yaml:"service"`
}
type Serivce struct {
Url string `yaml:"url"`
Host string `yaml:"host"`
Topics []Topic `yaml:"subscriptions"`
}
//读取Yaml配置文件,
//并转换成conf对象
func (conf *Conf) getConf() *Conf {
//应该是 绝对地址
yamlFile, err := ioutil.ReadFile("conf.yaml")
if err != nil {
fmt.Println(err.Error())
}
err = yaml.Unmarshal(yamlFile, conf)
if err != nil {
fmt.Println(err.Error())
}
return conf
}
(3) .ini 文件处理
1、安装
go get gopkg.in/ini.v1
2、如何使用
// my.ini 文件
name = myname
[user]
account = 123456
// 读取
cfg, err := ini.Load("my.ini")
if err != nil {
fmt.Printf("Fail to read file: %v", err)
os.Exit(1)
}
// 读取操作,默认分区可以使用空字符串表示
cfg.Section("").Key("name").String()
cfg.Section("user").Key("account").String()
// 修改某个值然后进行保存
cfg.Section("").Key("name").SetValue("newName")
cfg.SaveTo("my.ini.local")
参考:官网 (更多操作自行查询)
(5) 添加配置文件,区分不同项目环境
我们可以使用各种各样类型的文件来书写项目中的配置文件,我这里选择的是 .ini 文件
1、添加 godotenv 读取.env文件
任何从开发环境切换到生产环境时需要修改的东西都从代码抽取到环境变量里。但是在实际开发中,如果同一台机器运行多个项目,设置环境变量容易冲突,所以这里我们可以使用godotenv来处理这个问题
1.1 安装
go get github.com/joho/godotenv
1.2 使用
我们可以手动加载,也可以自动加载
// 手动
package main
import (
"fmt"
"log"
"os"
"github.com/joho/godotenv"
)
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal(err)
}
fmt.Println("env: ", os.Getenv("env"))
// 默认情况下,godotenv读取项目根目录下的.env文件,文件中使用key = value的格式,每行一个键值对。
// 调用godotenv.Load()即可加载,可直接调用os.Getenv("key")读取。
// os.Getenv是用来读取环境变量的:
}
// 自动
package main
import (
"fmt"
"os"
_ "github.com/joho/godotenv/autoload"
// 由于代码中没有显式用到godotenv库,需要使用空导入,即导入时包名前添加一个_
)
func main() {
fmt.Println("env: ", os.Getenv("env"))
}
参考:官网
1.3 项目中新增 .env 文件
// 我们在项目中的根目录新建 .env文件
/.env
ENV = dev
1.4 处理.env文件不会打包进执行文件的情况
// 众所周知,go打包只会打.go文件,处理这个问题有两个思路,一个是使用能打进其他文件的解决方案
// 2 是判断如果当前配置文件不存在则新建一个 ,那么在这里为了方便,我决定使用方法2
2、在项目中添加不同环境下的配置文件
在实际开发中我们通常在不同环境下有不同的配置,为了方便切换不同配置,我们可以在根目录下新建一个文件夹config来存放我们不同环境下的配置文件,我这里暂时只新建了三个文件夹来分别存放开发、生产和测试环境下的配置文件
├── config
│ ├── dev.ini
│ ├── pro.ini
│ ├── test.ini
3、判断不同环境下加载不同的配置
因为我们上面引入了godotenv来读取.env文件,所以我们现在可以很方便地去知道我们当前需要加载的是什么环境的配置文件,那么,就让我们来添加一些方法来处理它吧
/config/util.go
package projectConfig
import (
"bytes"
"fmt"
"github.com/joho/godotenv"
"log"
"os"
"path/filepath"
"runtime"
)
// 创建.env文件
func createEnv() {
envConfig := "ENV = pro"
buf := &bytes.Buffer{}
buf.WriteString(envConfig)
env, err := godotenv.Parse(buf)
if err != nil {
log.Fatal(err)
}
path := getAppPath() "/.env"
err = godotenv.Write(env, path)
if err != nil {
log.Fatal(err)
}
}
// 获取当前环境
func getEnv() string {
// 加载.env文件配置
err := godotenv.Load(".env")
// 获取运行环境
env := os.Getenv("ENV")
if err != nil {
fmt.Println("配置文件加载失败,现在新建一个配置文件")
env = "pro"
}
fmt.Println("当前环境开发:", env)
return env
}
// 获取当前路径
func getAppPath() string {
var _, b, _, _ = runtime.Caller(0)
return filepath.Join(filepath.Dir(b), "../")
}
/config/config.go
package projectConfig
import (
"fmt"
"gopkg.in/ini.v1"
)
func InitConfig() {
baseConfig := getBaseConfig()
}
func getBaseConfig() interface{} {
// 读取配置文件路径
path := getAppPath() "/config/" getEnv() ".ini"
baseConfig := new(BaseConfig)
// 映射,一切竟可以如此的简单。
baseConfigStruct := ini.MapTo(baseConfig, path)
return baseConfig
}
4、如果你觉得太麻烦了
如果你认为你不需要这么多环境配置的内容,觉得上述操作过于麻烦,也可以直接定义一个变量来决定当前加载什么环境的配置
/config/dev.ini
// 示例
[BASE_CONFIG]
ENV = dev
HTTP_PORT = 8081
READ_TIMEOUT = 60
WRITE_TIMEOUT = 60
/config/configStruct.go
package projectConfig
type BaseInfo struct {
env string
}
type Config struct {
BaseConfig `ini:"BASE_CONFIG"`
}
type BaseConfig struct {
ENV string `ini:"ENV"`
HTTP_PORT int `ini:"HTTP_PORT"`
READ_TIMEOUT int `ini:"READ_TIMEOUT"`
WRITE_TIMEOUT int `ini:"WRITE_TIMEOUT"`
}
/config/config.go
package projectConfig
import (
"fmt"
"gopkg.in/ini.v1"
)
func InitConfig() {
getBaseConfig()
}
func getBaseConfig() interface{} {
// 基础信息
baseInfo := BaseInfo{env: "dev"}
fmt.Println(baseInfo.env, "baseConfig.env")
// 读取配置文件路径
path := getAppPath() "/config/" baseInfo.env ".ini"
// 无默认值
// config := &Config{BaseConfig: BaseConfig{}}
// 设置默认值
config := &Config{BaseConfig: BaseConfig{
ENV: "pro",
HTTP_PORT: 8081,
READ_TIMEOUT: 60,
WRITE_TIMEOUT: 60,
}}
// 映射,一切竟可以如此的简单。
err := ini.MapTo(config, path)
if err != nil {
fmt.Println("ini文件映射出错啦,错误是:", err)
}
fmt.Println(config, config.BaseConfig, "baseConfig")
return config
}
三、日志处理
1、安装
// 日志轮询机制,(就是将日志定期清理,保存,使之不会不停涨大)
go get -u github.com/lestrrat-go/file-rotatelogs
// lfshook将TextFormatter在写入本地文件时从任何类型的格式化程序中去除颜色,因为文件中的颜色代码看起来不太好。
go get -u github.com/rifflock/lfshook
// GIN日志中间件logrus使用
go get -u github.com/sirupsen/logrus
2、项目配置
(1) 项目配置文件新增日志写入url 以及日志名
/config/dev.ini
[BASE_CONFIG]
ENV = dev
HTTP_PORT = 8081
READ_TIMEOUT = 60
WRITE_TIMEOUT = 60
// 新增
[LOG]
LOG_SAVE_URL = logs/
LOG_SAVE_NAME = log
(2) 新增日志处理中间件
/middleware/global/log/log.go
package logMiddleware
import (
"fmt"
"github.com/gin-gonic/gin"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"github.com/rifflock/lfshook"
"github.com/sirupsen/logrus"
"go-server-template/config"
"os"
"path"
"time"
)
func LogerMiddleware(config *projectConfig.Config) gin.HandlerFunc {
// 日志文件
fileName := path.Join(config.LogConfig.LOG_SAVE_URL, config.LogConfig.LOG_SAVE_NAME)
// 写入文件
src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
if err != nil {
fmt.Println("err", err)
}
// 实例化
logger := logrus.New()
//设置日志级别
logger.SetLevel(logrus.DebugLevel)
//设置输出
logger.Out = src
// 设置 rotatelogs
logWriter, err := rotatelogs.New(
// 分割后的文件名称
fileName ".%Y%m%d.log",
// 生成软链,指向最新日志文件
rotatelogs.WithLinkName(fileName),
// 设置最大保存时间(7天)
rotatelogs.WithMaxAge(7*24*time.Hour),
// 设置日志切割时间间隔(1天)
rotatelogs.WithRotationTime(24*time.Hour),
)
writeMap := lfshook.WriterMap{
logrus.InfoLevel: logWriter,
logrus.FatalLevel: logWriter,
logrus.DebugLevel: logWriter,
logrus.WarnLevel: logWriter,
logrus.ErrorLevel: logWriter,
logrus.PanicLevel: logWriter,
}
logger.AddHook(lfshook.NewHook(writeMap, &logrus.JSONFormatter{
TimestampFormat: "2006-01-02 15:04:05",
}))
return func(c *gin.Context) {
//开始时间
startTime := time.Now()
//处理请求
c.Next()
//结束时间
endTime := time.Now()
// 执行时间
latencyTime := endTime.Sub(startTime)
//请求方式
reqMethod := c.Request.Method
//请求路由
reqUrl := c.Request.RequestURI
//状态码
statusCode := c.Writer.Status()
//请求ip
clientIP := c.ClientIP()
// 日志格式
logger.WithFields(logrus.Fields{
"status_code": statusCode,
"latency_time": latencyTime,
"client_ip": clientIP,
"req_method": reqMethod,
"req_uri": reqUrl,
}).Info()
}
}
// 日志记录到 MongoDB
func LoggerToMongo() gin.HandlerFunc {
return func(c *gin.Context) {
}
}
// 日志记录到 ES
func LoggerToES() gin.HandlerFunc {
return func(c *gin.Context) {
}
}
// 日志记录到 MQ
func LoggerToMQ() gin.HandlerFunc {
return func(c *gin.Context) {
}
}
参考:参考链接
(3) 引入日志中间件
/main.go
package main
import (
"fmt"
"go-server-template/config"
"go-server-template/middleware/global/log"
"go-server-template/routers"
"strconv"
)
func main() {
config := projectConfig.InitConfig()
r := routers.InitRouter()
// 使用日志中间件
r.Use(logMiddleware.LogerMiddleware(config))
// int 转 string
http_port := strconv.Itoa(config.BaseConfig.HTTP_PORT)
err := r.Run(":" http_port)
if err != nil {
fmt.Println("服务器启动失败!")
}
}
四、加入热更新
相信写到这里,各位同学们对每次修改都要重新启动项目感到非常不耐烦了,那么我们就愉快地在项目中引入热更新的功能吧
1、方案对比
go常见的可以支持热更新的包有fresh和realize,两个应该都差不多,因为fresh更省事,所以我们这里选择fresh作为我们的热更新方案。
2、realize
1.1 安装
GO111MODULE=off go get github.com/oxequa/realize
这里有一个坑点就是,我们如果按照官方文档
go get github.com/oxequa/realize
这种方式来安装的时候,我们就会遇到
go get: gopkg.in/urfave/cli.v2@v2.3.0: parsing go.mod:
module declares its path as: github.com/urfave/cli/v2
but was required as: gopkg.in/urfave/cli.v2
这样的报错,原因很简单,我就不细说了,有兴趣可以自己去查
1.2 初始化
安装完后我们可以使用命令来初始化
realize init
在这一步我们可以按照自己的需求去设定规则,也可以一路回车狂奔之后再自定义配置文件的修改
而具体的配置文件可以参考这个链接:配置文件
注: schema 下的 name 和 path 请按照实际情况写。如果你的机器是 Mac,请把杀进程命令换成pkill,这一步很重要,否则重新编译时 Gin 会一直这样提示,导致热更新失败。
[GIN-debug] [ERROR] listen tcp :8080: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted
启动的话我们可以在根目录下使用命令
realize start
3、fresh
与上面同理
GO111MODULE=off go get github.com/oxequa/realize
然后直接在根目录使用
fresh
就可以直接使用啦,因为fresh 会自动运行项目的 main.go
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhbebjff
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01