はじめに
こんにちは。
連続でGo言語頑張っています。
今回は、APIGateway、DynamoDBとSlackのSlashCommandを駆使して、ブログのSlackチームの手動管理をなくす対応を行いました。
やってみた
APIGateway
ブログ登録用のAPIを作成します。
API作成
特に説明すること無いんですが、事前に用意しているLambdaと連携してます。
ステージは、1こだけです。
使用量プラン
使用量プランをつくります。
そんなにガンっとユーザーが増えるわけはないと思うんですが、念の為です。
先程作ったステージと紐づけます。
とりあえず、これでアクセス出来ます。
次は、ブログを登録するようのパラメータのマッピングテンプレートを登録します。
APIGateWayはこれで終わり。
DynamoDB
テーブルだけ登録しました。
idはstringでslackのUserIDを保存するようにしておきます。
Lambda
現状実装しているものに加えて、今回作成した、ブログ登録用のlambdaを作成しました。
- 水・金・日のリマインダー
- ブログの登録
1. 水・金・日のリマインダー
こちらは、すでに作っていたので、処理の流れは基本的には変えてないのですが、tomlファイルで管理していた各ユーザーとRSSフィードURLのマッピングをDynamoDBから取得するように変更しました。
package database import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/guregu/dynamo" config "../config" ) type WriteBlogEveryWeek struct { UserID string `dynamo:"user_id"` UserName string `dynamo:"user_name"` FeedURL string `dynamo:"feed_url"` RequireCount int `dynamo:"require_count"` } /** * DynamoDBからデータを全取得する */ func FindAll(configData config.ConfigData) []WriteBlogEveryWeek { var writeBlogEveryWeek []WriteBlogEveryWeek table := getTableObject(configData) err := table.Scan().All(&writeBlogEveryWeek) if err != nil { panic("データの読み込みエラー => " + err.Error()) } return writeBlogEveryWeek } /** * DynamoDBのテーブルオブジェクトを取得する */ func getTableObject(configData config.ConfigData) dynamo.Table { credential := credentials.NewStaticCredentials(configData.AWS.AccessKey, configData.AWS.SecretKey, "") db := dynamo.New(session.New(), &aws.Config{ Credentials: credential, Region: aws.String(configData.AWS.Region), }) table := db.Table(configData.AWS.DataBase) return table }
参考サイトがゴロゴロ転がっていたので、そこまで迷うことはありませんでした!
ユーザーデータはDynamoDBからとるようになったので、tomlファイルは、AWSとかSlackの設定だけを管理するようになり、スッキリしました。
2. ブログの登録
前述したとおり、今までは tomlファイルで各メンバーのブログを管理していました。
なので、メンバーが増えるごとに自分がtomlファイルを修正していました。
現在メンバーが20人超えしていて、流石に辛くなったんで、この管理方法をやめるために、各ユーザーで勝手に追加してもらうように対応しました。
使ったのは、 Slash Commands です。
Slash Command -> API Gateway -> Lambda -> DynamoDB登録 という流れです。
こちらの記事をほぼほぼ使わせていただきました。
ありがたい・・・
slack.go
package slack import ( "errors" "net/url" "strings" config "../config" ) /** * Slackからのパラメータ格納構造体 */ type SlackParams struct { Token string UserID string UserName string Text string } /** * Slackから送られたパラメータをパースする */ func ParseSlackParams(rawParams interface{}) (result *SlackParams, err error) { result = &SlackParams{} tmp := rawParams.(map[string]interface{}) if _, ok := tmp["body"]; !ok { err = errors.New("params body does not exists") return } rawQueryString := tmp["body"].(string) parsed, err := url.QueryUnescape(rawQueryString) if err != nil { err = errors.New("params body unescape failed. body: " + rawQueryString) return } params, err := url.ParseQuery(parsed) if err != nil { err = errors.New("params body parse failed. body: " + rawQueryString) return } result.Token = params["token"][0] result.UserID = params["user_id"][0] result.UserName = params["user_name"][0] result.Text = params["text"][0] // Slash CommandでURL形式を送ると <URL>という形式になるので、先頭と末尾をtrimする result.Text = strings.TrimLeft(result.Text, "<") result.Text = strings.TrimRight(result.Text, ">") return }
database.go
package database import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/guregu/dynamo" config "../config" slack "../slack" ) type WriteBlogEveryWeek struct { UserID string `dynamo:"user_id"` UserName string `dynamo:"user_name"` FeedURL string `dynamo:"feed_url"` RequireCount int `dynamo:"require_count"` } /** * Pkを指定して1件取得 */ func FindByPK(configData config.ConfigData, pk string) WriteBlogEveryWeek { var writeBlogEveryWeek WriteBlogEveryWeek table := getTableObject(configData) table.Get("user_id", pk).One(&writeBlogEveryWeek) return writeBlogEveryWeek } /** * 新しいユーザーデータを作成する */ func CreateUser(configData config.ConfigData, slackParams *slack.SlackParams) { var writeBlogEveryWeek WriteBlogEveryWeek writeBlogEveryWeek.UserID = slackParams.UserID writeBlogEveryWeek.UserName = slackParams.UserName writeBlogEveryWeek.FeedURL = slackParams.Text writeBlogEveryWeek.RequireCount = 1 table := getTableObject(configData) err := table.Put(writeBlogEveryWeek).Run() if err != nil { panic("登録エラー => " + err.Error()) } } /** * DynamoDBのテーブルオブジェクトを取得する */ func getTableObject(configData config.ConfigData) dynamo.Table { credential := credentials.NewStaticCredentials(configData.AWS.AccessKey, configData.AWS.SecretKey, "") db := dynamo.New(session.New(), &aws.Config{ Credentials: credential, Region: aws.String(configData.AWS.Region), }) table := db.Table(configData.AWS.DataBase) return table }
config.go
package config import "github.com/BurntSushi/toml" type ConfigData struct { Slack Slack AWS AWS } type Slack struct { SendAPIURL string ChannelName string RegsiterToken string } type AWS struct { AccessKey string SecretKey string Region string DataBase string } /** * 設定データを取得する */ func GetConfigData() ConfigData { var configData ConfigData _, err := toml.DecodeFile("config.toml", &configData) if err != nil { panic("tomlファイルを読み込めません") } return configData }
main.go
/** * ブログの登録ロジックを実行 */ func blogRegister(_ context.Context, rawParams interface{}) (interface{}, error) { configData := config.GetConfigData() envToken := os.Getenv("SLACK_TOKEN") params, err := slack.ParseSlackParams(rawParams) if err != nil { return "スラックのパラメータが取得できませんでした。 error: " + err.Error(), nil } if envToken != params.Token { return "トークンの不一致", nil } userData := database.FindByPK(configData, params.UserID) if userData.UserID != "" { return "あなたのブログはすでに登録済みです feedURL: " + userData.FeedURL, nil } database.CreateUser(configData, params) return "ブログを登録しました。これからは妥協は許しませんよ。", nil }
実際に実行するとこんな感じです。
特にバリデートしてないのは許してください。
アイコンはこちらから一枚使わせていただいています。。。
これで、メンバー登録を自分の手動運用から離す事ができました。
終わりに
開いてる時間でやってたんですが、結構時間かかりました。
Go言語慣れてきたんですが、ポインターとか使ってなくて、全く使いこなせていません。
もうちょっと本格的に触らないとなー。と思っていたら、知り合いからGoの仕事のお誘いが来ました。
受けられるほどの技術はまだないんですが、とりあえず本当にやるとなった想定で、素振りをブンブンしておきます。