こんにちは、KOUKIです。
GolangのWebフレームワークであるfiberを使ってAPIを開発しています。
前回は、リクエストスコープを設定し、APIのリクエストにスコープ(Ambassador/Amdin)を持たせました。
今回は、Aliasの設定をします。
尚、Udemyの「React, NextJS and Golang: A Rapid Guide – Advanced」コースを参考にしています。解釈は私が勝手に付けているので、本物をみたい場合は受講をお勧めします!
<目次>
前回
作るもの
Ambassdor機能を作りたいと思います。エンドポイントは、次の通りです。
- POST /api/ambassador/register
- POST /api/ambassador/login
- GET /api/ambassador/user
- POST /api/ambassador/logout
- PUT /api/ambassador/users/info
- PUT /api/ambassador/users/password
Aliasesの設定
本APIでは、AdminとAmbassadorの2種類のユーザーが存在するので、それぞれを区別するためにAliasを設定をする必要があります。
AdminとAmbassadorのリクエストパスは、異なるルートを指定していますが、リクエスト先のコントローラーは同じなので、ユーザーごとに処理を分けたいときにAliasesを設定します。
1 2 3 4 5 6 7 8 9 10 |
// routes/routes.go ... func Setup(app *fiber.App) { ... // admin adminAuthenticated.Get("user", controllers.User) <<< Userコントローラーへ // Ambassador ambassadorAuthentication.Get("user", controllers.User) <<< Userコントローラーへ } |
Userモデルの修正
Revenueパラメータをつけてください。
1 2 3 4 5 6 7 8 9 10 11 |
// models/user.go ... type User struct { Model FirstName string `json:"first_name"` LastName string `json:"last_name"` Email string `json:"email" gorm:"unique"` Password []byte `json:"-"` IsAmbassador bool `json:"-"` Revenue *float64 `json:"revenue,omitempty" gorm:"-"` } |
Revenueを計算するロジックをユーザーごとに分けたいと思います。
Alias
Alisaseは、typeで設定しましょう。
1 2 3 |
// models/user.go type Admin Use type Ambassador Use |
Revenueの計算
Admin,AmbassadorそれぞれにRevenueを計算するメソッドを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
// models/user.go func (a *Admin) CalculateRevenue(db *gorm.DB) { var orders []Order // Preloadで他のSQL内のリレーションを事前読み込む db.Preload("OrderItems").Find(&orders, &Order{ UserID: a.ID, Complete: true, }) var revenue float64 = 0.0 for _, order := range orders { for _, orderItem := range order.OrderItems { revenue += orderItem.AdminRevenue } } a.Revenue = &revenue } func (a *Ambassador) CalculateRevenue(db *gorm.DB) { var orders []Order // Preloadで他のSQL内のリレーションを事前読み込む db.Preload("OrderItems").Find(&orders, &Order{ UserID: a.ID, Complete: true, }) var revenue float64 = 0.0 for _, order := range orders { for _, orderItem := range order.OrderItems { revenue += orderItem.AmbassadorRevenue } } a.Revenue = &revenue } |
Aliasを作ると、それぞれにメソッドを作れる -> 処理を振り分けられるのでとてもいい感じになりますね。
CalculateRevenueメソッドを呼び出す
先ほど実装したメソッドは、authController.goのUserメソッドから呼び出します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// controllers/auathController.go func User(ctx *fiber.Ctx) error { id, _ := middleware.GetUserID(ctx) // ユーザー検索 var user models.User database.DB.Where("id = ?", id).First(&user) if strings.Contains(ctx.Path(), "/api/ambassador") { ambassador := models.Ambassador(user) ambassador.CalculateRevenue(database.DB) return ctx.JSON(ambassador) } return ctx.JSON(user) } |
UserモデルのRevenueには、「omitempty」が設定されており、ambassadorにだけCalculateRevenueメソッドを呼び出しているので、Revenueの戻り値はambassadorのみ取得できることになります。
※omitemptyは空の値の場合、呼び出し元にデータを返さない
Alisaseを貼ることで、モデルが持つ情報の意味合いを切り替えることができるので、大変便利です。
検証
以下のパラメータで、検証してみましょう。
- URL: http://localhost:8000/api/ambassador/user
- 形式: GET

ambassadorは、revenueが0の値を取得できました。
ログインしなおして、以下のパラメータでリクエストを送りましょう。
- URL:http://localhost:8000/api/admin/user
- 形式: GET

AdminにはRevenueが見えていない(omitemptyのおかげ)ので、成功です!
次回
次回は、Redisを導入しましょう。
Go言語まとめ
ソースコード
ここまでのソースコードを以下に記載します。
auth.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
// auth.go package middleware import ( "fmt" "strconv" "strings" "time" "github.com/dgrijalva/jwt-go" "github.com/gofiber/fiber/v2" ) const SecretKey = "secret" type ClaimsWithScope struct { jwt.StandardClaims Scope string } func IsAuthenticate(ctx *fiber.Ctx) error { // cookieから情報を取得 cookie := ctx.Cookies("jwt") // token取得 token, err := jwt.ParseWithClaims( cookie, &ClaimsWithScope{}, func(token *jwt.Token) (interface{}, error) { return []byte(SecretKey), nil }, ) if err != nil || !token.Valid { ctx.Status(fiber.StatusUnauthorized) // 401 return ctx.JSON(fiber.Map{ "message": "認証がされていません", }) } payload := token.Claims.(*ClaimsWithScope) IsAmbassador := strings.Contains(ctx.Path(), "/api/ambassador") if (payload.Scope == "admin" && IsAmbassador) || (payload.Scope == "ambassador" && !IsAmbassador) { ctx.Status(fiber.StatusUnauthorized) // 401 return ctx.JSON(fiber.Map{ "message": "認証が許可されません", }) } return ctx.Next() } func GenerateJWT(id uint, scope string) (string, error) { // トークンの発行 payload := ClaimsWithScope{} payload.Subject = strconv.Itoa(int(id)) payload.ExpiresAt = time.Now().Add(time.Hour * 24).Unix() payload.Scope = scope return jwt.NewWithClaims(jwt.SigningMethodHS256, payload).SignedString([]byte(SecretKey)) } func GetUserID(ctx *fiber.Ctx) (uint, error) { // cookieから情報を取得 cookie := ctx.Cookies("jwt") // token取得 token, err := jwt.ParseWithClaims( cookie, &ClaimsWithScope{}, func(token *jwt.Token) (interface{}, error) { return []byte(SecretKey), nil }, ) if err != nil { fmt.Println(err) return 0, err } payload := token.Claims.(*ClaimsWithScope) id, _ := strconv.Atoi(payload.Subject) return uint(id), nil } |
auathController.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
// controllers/auathController.go package controllers import ( "admin/src/database" "admin/src/middleware" "admin/src/models" "strings" "time" "github.com/gofiber/fiber/v2" "golang.org/x/crypto/bcrypt" ) func Register(ctx *fiber.Ctx) error { var data map[string]string if err := ctx.BodyParser(&data); err != nil { return err } if data["password"] != data["password_confirm"] { ctx.Status(fiber.StatusBadRequest) // 400 return ctx.JSON(fiber.Map{ "message": "パスワードに誤りがあります", }) } // ハッシュパスワードを作成 pwd, _ := bcrypt.GenerateFromPassword([]byte(data["password"]), 12) user := models.User{ FirstName: data["first_name"], LastName: data["last_name"], Email: data["email"], Password: pwd, IsAmbassador: strings.Contains(ctx.Path(), "/api/ambassador"), } // パスワードセット user.SetPassword(data["password"]) // ユーザー作成 result := database.DB.Create(&user) if result.Error != nil { ctx.Status(fiber.StatusBadRequest) return ctx.JSON(fiber.Map{ "message": "そのEmailは既に登録されています", }) } return ctx.JSON(user) } func Login(ctx *fiber.Ctx) error { var data map[string]string if err := ctx.BodyParser(&data); err != nil { return err } var user models.User database.DB.Where("email = ?", data["email"]).First(&user) if user.ID == 0 { ctx.Status(fiber.StatusBadRequest) return ctx.JSON(fiber.Map{ "message": "ログイン情報に誤りがあります", }) } // パスワードチェック err := user.ComparePassword(data["password"]) if err != nil { ctx.Status(fiber.StatusBadRequest) return ctx.JSON(fiber.Map{ "message": "ログイン情報に誤りがあります", }) } // ambassador判定 isAmbassador := strings.Contains(ctx.Path(), "/api/ambassador") var scope string if isAmbassador { scope = "ambassador" } else { scope = "admin" } if !isAmbassador && user.IsAmbassador { ctx.Status(fiber.StatusUnauthorized) // 401 return ctx.JSON(fiber.Map{ "message": "認証が許可されていません", }) } token, err := middleware.GenerateJWT(user.ID, scope) if err != nil { ctx.Status(fiber.StatusBadRequest) return ctx.JSON(fiber.Map{ "message": "ログイン情報に誤りがあります", }) } // Cookieに保存 cookie := fiber.Cookie{ Name: "jwt", Value: token, Expires: time.Now().Add(time.Hour * 24), HTTPOnly: true, } ctx.Cookie(&cookie) return ctx.JSON(fiber.Map{ "message": "success", }) } func User(ctx *fiber.Ctx) error { id, _ := middleware.GetUserID(ctx) // ユーザー検索 var user models.User database.DB.Where("id = ?", id).First(&user) return ctx.JSON(user) } func Logout(ctx *fiber.Ctx) error { // cookieをクリアする cookie := fiber.Cookie{ Name: "jwt", Value: "", Expires: time.Now().Add(-time.Hour * 24), // -を指定 HTTPOnly: true, } ctx.Cookie(&cookie) return ctx.JSON(fiber.Map{ "message": "success", }) } func UpdateInfo(ctx *fiber.Ctx) error { var data map[string]string // リクエストデータをパースする if err := ctx.BodyParser(&data); err != nil { return err } // cookieからidを取得する id, _ := middleware.GetUserID(ctx) user := models.User{ FirstName: data["first_name"], LastName: data["last_name"], Email: data["email"], } user.ID = id // ユーザー情報更新 database.DB.Model(&user).Updates(&user) return ctx.JSON(user) } func UpdatePassword(ctx *fiber.Ctx) error { var data map[string]string // リクエストデータをパースする if err := ctx.BodyParser(&data); err != nil { return err } // パスワードチェック if data["password"] != data["password_confirm"] { ctx.Status(fiber.StatusBadRequest) // 400 return ctx.JSON(fiber.Map{ "message": "パスワードに誤りがあります", }) } // cookieからidを取得する id, _ := middleware.GetUserID(ctx) user := models.User{} user.ID = id // パスワードセット user.SetPassword(data["password"]) // ユーザー情報更新 database.DB.Model(&user).Updates(&user) return ctx.JSON(user) } |
コメントを残す
コメントを投稿するにはログインしてください。