本文章同時發佈於:
大家好,繼上次Week21 - 用Redis來幫Line bot髒沙發設計一次性功能 - 概念篇後,這次要來說明實作的部分。
因為這篇文章會著重在Redis
,所以Line bot的串接細節就不多著墨。
並且為求方便快速,Redis
也會透過Docker
來直接產生,如果大家不太知道Docker
是什麼的話,廣義簡單的來說,他就是一個「秒啟動的虛擬機」。
Docker
透過把實機的核心掛載至一個隔離環境來當成虛擬機,此虛擬機不用透過Hypervisor
與實機溝通,運作成本非常小,所以效能與啟動速度跟實機差不多。
聽起來很複雜?沒關係你可以看twtrubiks大大的文章或者就直接把它當成一個超強超快的虛擬機就對了XD
使用者Line-APP
傳送的訊息與圖片,Line-Server
都會打一個API
給我們指定的My-Server
,我們俗稱Webhook
。
所以我們只需實作一個POST API api/WebhookHanlder
來接受Line Server
的Webhook
,即可模擬實際狀況。
# POST API api/WebhookHanlder
# API-1
{
"groupID": "69617aa7-9512-44c3-9e10-1626cc8bf585",
"type": "message",
"data": {
"text": "辨識"
}
}
# POST API api/WebhookHanlder
# API-2
{
"groupID": "69617aa7-9512-44c3-9e10-1626cc8bf585",
"type": "image",
"data": {
"imageID": "9b2fcfb3-e39c-4ea2-96a7-a3fb408095d0"
}
}
# Redis-1
key: 3e6e3637-45da-458f-8ade-85b8b9f0af8a:search // 為GroupID+type
value: true
完整程式碼在此: w3school-40-weeks
解釋我會在程式碼旁邊註釋。
首先我們先進入week22
的資料夾,
$ cd week22
利用docker-compose
來啟動Redis
$ docker-compose up
version: '3.5'
services:
redis:
image: redis:alpine
command: redis-server --appendonly yes
ports:
- 6379:6379 # 將docker內的Redis的6379 port對應到實機6379 port
volumes:
- ./data:/data
restart: always
啟動Server
$ go run main.go
主要的main function
將Redis
初始化,並設置API
func initRedis() *libredis.Client {
option, err := libredis.ParseURL("redis://localhost:6379/0")
check(err)
client := libredis.NewClient(option)
return client
}
func main() {
r := gin.Default()
redisClient = initRedis() // 初始化Redis
r.POST("/api/WebhookHanlder", webhookHanlder) // 新增POST API api/WebhookHanlder
r.Run()
}
由webhookHanlder function
來處理POST API api/WebhookHanlder
的請求,並且會比對json body
裡type
是message
還是image
來換到textHandler function
或imageHandler function
來處理。
// Controller
func webhookHanlder(c *gin.Context) {
var postType PostType
readBody(c, &postType) // 讀取request的body,並把struct套上,這個PostType struct是專門用來解析request type的
switch postType.Type { // 如果是message type就以textHandler function處理,如果是image type就以imageHandler function處理
case "message":
textHandler(c)
case "image":
imageHandler(c)
}
}
func textHandler(c *gin.Context) {
var postMessage PostMessage
readBody(c, &postMessage) // 讀取request的body,並把struct套上,這個PostMessage struct是專門用來讀取message json的
key := postMessage.GroupID + ":search" // 將GroupID+type
err := redisClient.Set(ctx, key, true, 0).Err() // 設置key為GroupID+type,value為true
check(err)
err = redisClient.Expire(ctx, key, 24*time.Hour).Err() // 設置key-value的過期時間為24小時
check(err)
c.Status(204) // 回傳204 response
}
func imageHandler(c *gin.Context) {
isThereSearchRequest := func(redisGroupResult string, err error) bool {
if err != nil || redisGroupResult == "" {
return false
}
redisGroupResultBool, err := strconv.ParseBool(redisGroupResult)
check(err)
return redisGroupResultBool
}
var postImage PostImage
readBody(c, &postImage) // 讀取request的body,並把struct套上,這個PPostImage struct是專門用來讀取image json的
key := postImage.GroupID + ":search" // 將GroupID+type
if isThereSearchRequest(redisClient.Get(ctx, key).Result()) { // 如果GroupID+type存在就回傳女優資訊,不存在就回傳「你沒有要求辨識」的error message
c.JSON(200, gin.H{
"AVStar": "佐倉絆",
})
return
}
c.JSON(403, gin.H{
"error": "No search requested",
})
}
謝謝你的閱讀,也歡迎分享討論指正~
*Golang: Read from an io.ReadWriter without losing its content