iT邦幫忙

2

Week22 - 用Redis來幫Line bot髒沙發設計一次性功能 - 實作篇 [Server的終局之戰系列]

本文章同時發佈於:


大家好,繼上次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 ServerWebhook,即可模擬實際狀況。

格式

# 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 functionRedis初始化,並設置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 bodytypemessage還是image來換到textHandler functionimageHandler 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


尚未有邦友留言

立即登入留言