logan是基于gin的web項目讼昆,集成了sentry、gorm其兴、premetheus等工具顶瞒,旨在高效快速的web開發(fā)。
編譯部署
clone本項目至GOPATH下元旬,執(zhí)行
git clone https://github.com/LiuRoy/logan.git $GOPATH/src
安裝依賴庫榴徐,此項目依賴包列寫在requirements文件中,執(zhí)行命令
go get -u <依賴包>
進行安裝執(zhí)行
go run main.go
法绵,啟動server
gin
gin是基于go語言輕量級web開發(fā)框架(項目地址: https://github.com/gin-gonic/gin)箕速,不僅運行效率很高,而且提供了參數(shù)校驗朋譬、模板渲染、定制化插件等十分有用的功能兴垦,極大簡化了接口開發(fā)難度徙赢。為了提供一款快速上手的后臺開發(fā)模板,logan就用到了gin探越。
添加接口
定義好函數(shù)類型為func(*gin.Context)
的函數(shù)狡赐,然后添加到對應的路徑下即可。
router := gin.New()
router.Use(gin.Logger(), tools.Recovery(sentryClient), tools.Prometheus())
// add endpoint
router.GET(tools.DefaultMetricPath, tools.LatestMetrics)
router.GET("/ping", func(c *gin.Context) {c.String(http.StatusOK, "pong")})
router.POST("/message", apis.AddMessage)
router.GET("/message", apis.GetMessage)
router.Run(address)
參數(shù)獲取
- 針對參數(shù)放在url中的請求钦幔,可以使用
gin.Context.GetQuery
或者gin.Context.DefaultQuery
枕屉,獲取需要的參數(shù)。
func GetMessage(c *gin.Context) {
messageId, exist := c.GetQuery("message_id")
if !exist {
c.JSON(http.StatusBadRequest, gin.H{"result": "bad params"})
} else {
msgId, _ := strconv.ParseUint(messageId, 10, 0)
message := model.GetMessage(uint(msgId))
c.JSON(http.StatusOK, gin.H{"result": "ok", "message": *message})
}
}
- 針對參數(shù)放在body的請求鲤氢,通過
gin.Context.Bind
解析驗證參數(shù)(可以制定哪些參數(shù)是否必需)搀擂。
type message struct {
Type string `json:"type" binding:"required"`
InitiatorId uint `json:"initiator" binding:"required"`
ConsumerId uint `json:"consumer" binding:"required"`
ResourceId string `json:"resource_id"`
IsFollow bool `json:"isfollow"`
Gcid string `json:"gcid"`
Cid uint `json:"cid"`
Response string `json:"response"`
reply string `json:"reply"`
}
func AddMessage(c *gin.Context) {
var param message
if c.BindJSON(¶m) == nil {
switch param.Type {
case "follow":
model.AddMessage(param.Type, param.InitiatorId, "aaa", "bbb", param.ConsumerId, "", "", "", "", "")
default:
panic("unknown type")
}
c.JSON(http.StatusOK, gin.H{"result": "ok"})
} else {
c.JSON(http.StatusBadRequest, gin.H{"result": "bad params"})
}
}
模板渲染
后臺常用的返回格式是json,gin.Context.JSON
很方便的可以把結果序列化為json返回給調(diào)用放卷玉。
c.JSON(http.StatusOK, gin.H{"result": "ok", "message": *message})
配置獲取
logan提供了兩種從consul獲取的方式:啟動時獲取和運行監(jiān)聽哨颂。
啟動時獲取
針對大部分不改變的配置,logan只會在啟動進程的時候訪問consul讀取配置相种。
var SentryDsn string = tools.GetSingle("sentry_dsn")
運行監(jiān)聽
針對某些經(jīng)常改變的配置參數(shù)威恼,希望consul每次改動在不重啟進程的情況下立即生效的場景。每次使用的時候訪問consul肯定會增加接口耗時寝并。logan使用了watch方式箫措,每一個參數(shù)都有一個對應的goroutine阻塞地監(jiān)聽,一旦改動衬潦,馬上更新(建議不要設置太多)斤蔓。
var TestParam tools.WatchedParam
tools.WatchSingle("test_param", &TestParam)
var currentValue string = TestParam.Get()
監(jiān)控
監(jiān)控了各個接口的調(diào)用次數(shù)、出錯次數(shù)别渔、接口耗時附迷。如果需要定制一些特定的監(jiān)控惧互,可以參考prometheus.go文件在業(yè)務代碼中打點。監(jiān)控數(shù)據(jù)通過/logan/metrics獲取喇伯。
sentry
在sentry.go中喊儡,如果發(fā)現(xiàn)接口運行報錯,會將錯誤棧信息發(fā)送給sentry稻据,并更新相關接口的出錯次數(shù)艾猜。
緩存
使用redis作為緩存,只需要將redis集群消息以["10.33.111.4:6379", "10.33.22.4:6380", "10.33.44.4:6381", "10.33.1.55:6382", "10.33.1.54:6383", "10.33.1.74:6384"]
格式配置在consul中即可
數(shù)據(jù)庫
為了簡化業(yè)務代碼捻悯,logan使用了gorm(文檔鏈接: http://jinzhu.me/gorm/)匆赃。
// 對應的表結構見https://github.com/LiuRoy/logan/blob/master/model/mysql.sql
type Message struct {
MsgId uint `gorm:"column:msgid;primary_key"`
Type string `gorm:"column:type;type:varchar(32)"`
InitiatorId uint `gorm:"column:initiatorid"`
InitiatorName string `gorm:"column:initiatorname;type:varchar(255)"`
InitiatorPortrait string `gorm:"column:initiatorportrait;type:varchar(255)"`
ConsumerId uint `gorm:"column:consumerid"`
ResourceId string `gorm:"column:resource_id;type:varchar(255)"`
ExtraInfo1 string `gorm:"column:extra_info1;type:varchar(512)"`
ExtraInfo2 string `gorm:"column:extra_info2;type:varchar(512)"`
ExtraInfo3 string `gorm:"column:extra_info3;type:varchar(512)"`
ExtraInfo4 string `gorm:"column:extra_info4;type:varchar(512)"`
InsertTime time.Time `gorm:"column:insert_time" sql:"DEFAULT:current_timestamp"`
}
func (Message) TableName() string {
return "msgcenter_innodb"
}
func AddMessage(msgType string, initiatorId uint, initiatorName string,
initiatorPortrait string, consumerId uint, resourceId string,
extraInfo1, extraInfo2, extraInfo3, extraInfo4 string) *Message {
message := Message{
Type: msgType,
InitiatorId: initiatorId,
InitiatorName: initiatorName,
InitiatorPortrait: initiatorPortrait,
ConsumerId: consumerId,
ResourceId: resourceId,
ExtraInfo1: extraInfo1,
ExtraInfo2: extraInfo2,
ExtraInfo3: extraInfo3,
ExtraInfo4: extraInfo4,
}
DbConnection.Create(&message)
DbConnection.NewRecord(message)
RedisConnection.Set("aaaaaaa", message.MsgId, 0)
return &message
}
func GetMessage(messageId uint) *Message {
message := Message{}
DbConnection.Where("msgid = ?", messageId).First(&message)
return &message
}