Go实战项目【二】搭建项目的基础

项目配置

conf文件夹下创建app.ini文件,这个是整个项目的配置文件
conf/app.ini

#app相关
[app]
PageSize = 20                           #分页每页返回的size
JwtSecret = 1111$1111                #JWT密钥
JwtExpireTime = 100                     #jwt失效时间,单位是小时

RuntimeRootPath = runtime/              #保存文件的跟路径

ImagePrefixUrl = http://127.0.0.1:9999  #图片url
ImageSavePath = upload/images/          #图片路径
ImageMaxSize = 1                        #图片最大尺寸,单位(M)
ImageAllowExts = .jpg,.jpeg,.png        #图片允许的格式

ApkSavePath = upload/apks/              #apk文件路径
ApkAllowExt = .apk                      #apk文件格式
#iOS应用在App Store中的地址,用于版本更新
AppStoreUrl = null

LogSavePath = logs/                     #日志文件路径
LogSaveName = log                       #日志文件名称
LogFileExt = log                        #日志文件后缀
TimeFormat = 20060102                   #时间相关

WechatAppID = null       #微信的appID
WechatSecret = null #微信的secret
QQAppID =  null                           #QQ的appID
QQAppKey =  null   #QQ的appkey

#服务器相关
[server]
RunMode = debug                         #debug or release
HttpPort = 9999                         #端口号
ReadTimeout = 60                        #读取超时时间
WriteTimeout = 60                       #写入超时时间

#数据库mysql相关
[database]
Type = mysql                            #数据库类型
User = root                             #数据库账号
Password = 123456                     #数据库密码
Host = 127.0.0.1:3306                   #数据库地址+端口号
Name = api                            #数据库名称
TablePrefix = api_                    #数据库表名称前缀

每个配置字段的含义都有注释,解释下几个字段,
JwtSecret:json web token加密的密钥,需要客户端和服务端保持一致
ImagePrefixUrl:项目中返还给客户端图片的域名,
AppStoreUrl:项目在AppStore中的地址,用做更新使用

其它例如数据库信息,根据自己的情况自行设置

安装ini配置包
终端执行

go get -u github.com/go-ini/ini

配置文件已经有了,如何使用到配置文件设置的值呢?这里使用了映射结构体的方式,来获取配置文件中设置的值。

首先创建pkg/setting/setting.go文件,编写结构体,结构体与配置文件的值一一对应
pkg/setting/setting.go

package setting

import "time"

//app配置
type App struct {
    JwtSecret string                //jwt密钥
    JwtExpireTime time.Duration     //jwt失效时间,单位是小时
    PageSize int                    //分页返回的数据个数
    RuntimeRootPath string          //保存文件的跟路径

    ImagePrefixUrl string           //图片url
    ImageSavePath string            //图片要保存的路径
    ImageMaxSize int                //图片最大尺寸
    ImageAllowExts []string         //图片允许的格式   jpg jpeg png

    ApkSavePath string              //apk文件路径
    ApkAllowExt string              //apk文件格式
    AppStoreUrl string              //iOS应用在App Store中的地址,用于版本更新

    LogSavePath string              //日志文件保存的路径
    LogSaveName string              //日志文件名称
    LogFileExt string               //日志文件后缀
    TimeFormat string               //文件的日期名称

    WechatAppID string              //微信的appID
    WechatSecret string             //微信的secret
    QQAppID string                  //QQ的appID
    QQAppKey string                 //QQ的appkey
}
var AppSetting = &App{}

//服务配置
type Server struct {
    RunMode string              //运行模式
    HttpPort int                //端口号
    ReadTimeout time.Duration   //读取超时时间
    WriteTimeout time.Duration  //写入超时时间
}
var ServerSetting = &Server{}

//数据库配置
type Database struct {
    Type string                 //数据库类型
    User string                 //数据库用户
    Password string             //数据库密码
    Host string                 //数据库地址+端口号
    Name string                 //数据库名称
    TablePrefix string          //数据库数据表前缀
}
var DatabaseSetting = &Database{}

然后创建一个初始化的方法,方法的作用是把配置文件映射到上面的结构体当中,注意这个方法不能用init方法,因为要保证程序启动后首先要加载配置文件。避免多个包下init方法的执行顺序这种情况。
pkg/setting/setting.go

...  //为了节省文章篇幅,以后将会用这种方式来标示已写过的代码
//将配置选项映射到结构体上
func SetUp()  {
    Cfg,err := ini.Load("conf/app.ini") //加载配置文件ini
    if err != nil {
        log.Fatal("获取.ini配置失败")
    }

    //映射配置
    err = Cfg.Section("app").MapTo(AppSetting)
    if err != nil {
        log.Fatalf("Cfg配置文件映射 AppSetting 错误: %v", err)
    }

    AppSetting.ImageMaxSize = AppSetting.ImageMaxSize * 1024 * 1024     //设置允许上传图片的最大尺寸

    err = Cfg.Section("server").MapTo(ServerSetting)
    if err != nil {
        log.Fatalf("Cfg配置文件映射 ServerSetting 错误: %v", err)
    }

    ServerSetting.ReadTimeout = ServerSetting.ReadTimeout * time.Second
    ServerSetting.WriteTimeout = ServerSetting.ReadTimeout * time.Second

    err = Cfg.Section("database").MapTo(DatabaseSetting)
    if err != nil {
        log.Fatalf("Cfg配置文件映射 DatabaseSetting 错误: %v", err)
    }
}

接下来就是如何使用,在项目根目录下创建main.go文件,作为项目的入口,在main方法中调用SetUp方法
(可以把上篇文章创建的test.go文件删除了)
main.go

package main

import (
    "api/pkg/setting"
    "log"
)

func main() {
    log.Println("Hello, api 正在启动中...")
    setting.SetUp() //初始化配置文件
    log.Println(setting.ServerSetting.HttpPort) //测试能否打印出ini配置文件设置的信息
}

然后终端执行go run main.go,看下能否正常打印出信息。
以后有需要再添加的配置信息,就按照这种方法新增就可以了。

编写自定义api接口的返回码和信息

每个api接口返回的是json数据,定义的json格式如下:

{
    "Code":200,
    "Msg":"Message",
    "Data":Object
}

Code:api接口返回的状态,200代表正常,此外还有其它状态,我们要编写的就是这个
Msg:状态码所对应的消息
Data:返回的数据就在这里,这个值的类型不是固定的。

pkg/e/code.go接口的状态码

package e

const (
    SUCCESS = 200                               //成功响应请求
    ERROR = 500                                 //错误响应请求
    INVALID_PARAMS = 400                        //请求参数无效

    ERROR_EXIST_TAG = 10001                     //标记错误
    ERROR_NOT_EXIST_TAG = 10002                 //错误的不存在的标记
    ERROR_NOT_EXIST_ARTICLE = 10003             //错误的不存在的文章

    ERROR_AUTH_CHECK_TOKEN_FAIL = 20001         //token无效
    ERROR_AUTH_CHECK_TOKEN_TIMEOUT = 20002      //token超时
    ERROR_AUTH_TOKEN = 20003                    //taoken错误
    ERROR_AUTH = 20004                          //无效的用户
    ERROR_EXIST_AUTH = 20005                    //手机号已存在
    ERROR_AUTH_PASSWORD = 20006                 //密码错误

    ERROR_UPLOAD_SAVE_IMAGE_FAIL = 30001        // 保存图片失败
    ERROR_UPLOAD_CHECK_IMAGE_FAIL = 30002       // 检查图片失败
    ERROR_UPLOAD_CHECK_IMAGE_FORMAT = 30003     // 校验图片错误,图片格式或大小有问题
)

同样,每个状态吗所对应的信息也告诉客户端,返回的值在json的Msg值中
pkg/e/msg.go

package e

/*
   编码消息
*/
var MsgFlags = map[int]string{
    SUCCESS : "ok",
    ERROR : "fail",
    INVALID_PARAMS : "请求参数错误",
    ERROR_EXIST_TAG : "已存在该标签名称",
    ERROR_NOT_EXIST_TAG : "该标签不存在",
    ERROR_NOT_EXIST_ARTICLE : "该文章不存在",
    ERROR_AUTH_CHECK_TOKEN_FAIL : "Token鉴权失败",
    ERROR_AUTH_CHECK_TOKEN_TIMEOUT : "Token已超时",
    ERROR_AUTH_TOKEN : "Token错误",
    ERROR_AUTH : "用户不存在",
    ERROR_EXIST_AUTH : "用户已存在",
    ERROR_AUTH_PASSWORD : "密码错误",
    ERROR_UPLOAD_SAVE_IMAGE_FAIL:"保存图片失败",
    ERROR_UPLOAD_CHECK_IMAGE_FAIL:"检查图片失败",
    ERROR_UPLOAD_CHECK_IMAGE_FORMAT :"校验图片错误,图片格式或大小有问题",
}

/*
   根据传入的编码。获取对应的编码消息
*/
func GetMsg(code int)string  {
    msg,ok := MsgFlags[code]
    if ok {
        return msg
    }
    return MsgFlags[ERROR]
}

同样的,以后有需要新增状态,修改以上文件新增就可以了。

启动服务和应用热更新

准备工作完成一部分了,可以先把程序服务启动来。也是在main方法中进行
main.go

...
func main() {
    log.Println("Hello, api 正在启动中...")
    setting.SetUp() //初始化配置文件

    router := gin.Default()

    router.GET("/test", func(context *gin.Context) {
        context.JSON(e.SUCCESS,gin.H{
            "Code":e.SUCCESS,
            "Msg":e.GetMsg(e.SUCCESS),
            "Data":"返回数据成功",
        })
    })

    s := &http.Server{
        Addr:fmt.Sprintf(":%d", setting.ServerSetting.HttpPort),        //设置端口号
        Handler:router,                                         //http句柄,实质为ServeHTTP,用于处理程序响应HTTP请求
        ReadTimeout:setting.ServerSetting.ReadTimeout,          //允许读取的最大时间
        WriteTimeout:setting.ServerSetting.WriteTimeout,        //允许写入的最大时间
        MaxHeaderBytes: 1 << 20,                                //请求头的最大字节数
    }

    /*
       使用 http.Server - Shutdown() 优雅的关闭http服务
    */
    go func() {
        if err := s.ListenAndServe(); err != nil{
            log.Printf("Listen: %s\n", err)
        }
    }()

    quit := make(chan os.Signal)
    signal.Notify(quit,os.Interrupt)
    <- quit

    log.Println("Shutdown Server ...")
    ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second)
    defer cancel()
    if err := s.Shutdown(ctx); err != nil {
        log.Fatal("Server Shutdown:", err)
    }

    log.Println("程序服务关闭退出")
}

可以在浏览器输入http://127.0.0.1:9999/test查看数据是否正确

image.png

封装返回数据方法

由于json的第一层格式是固定的,所以封装一下返回数据的方法,能更简单的调用。
pkg/util/response.go

package util

import (
    "api/pkg/e"
    "github.com/gin-gonic/gin"
)

/*
   数据返回信息的model,格式如下
*/
type Response struct {
    Code int            //自定义编码
    Msg string          //自定义消息
    Data interface{}    //返回的数据
}

func ResponseWithJson(code int, data interface{}, c *gin.Context) {
    c.JSON(200,&Response{
        Code:code,
        Msg:e.GetMsg(code),
        Data:data,
    })
}

以后会经常使用到这个方法,直接调用util.ResponseWithJson()方法即可。

点关注,不迷路

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352