如何構建穩健的 API 鑒權與計費系統:Golang 全流程實戰
一、收費 API 系統的核心問題
二、API 的開通與接入
package main
import (
"crypto/rand"
"encoding/hex"
"fmt"
"net/http"
"sync"
)
// 模擬數據庫存儲開發者信息
var developerData = map[string]struct {
Email string
APIKey string
IsActive bool
}{}
var mu sync.Mutex
// 生成唯一的 API 密鑰
func generateAPIKey() string {
key := make([]byte, 16)
rand.Read(key)
return hex.EncodeToString(key)
}
// 注冊新開發者
func registerDeveloper(w http.ResponseWriter, r *http.Request) {
email := r.FormValue("email")
if email == "" {
http.Error(w, "Email is required", http.StatusBadRequest)
return
}
mu.Lock()
defer mu.Unlock()
// 生成 API 密鑰
apiKey := generateAPIKey()
// 假設開發者注冊成功
developerData[email] = struct {
Email string
APIKey string
IsActive bool
}{
Email: email,
APIKey: apiKey,
IsActive: true,
}
fmt.Fprintf(w, "Registration successful! Your API Key: %s", apiKey)
}
func main() {
http.HandleFunc("/register", registerDeveloper)
fmt.Println("Server running on :8080")
http.ListenAndServe(":8080", nil)
}
三、API 的鑒權機制
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"net/http"
"strconv"
"time"
)
const secretKey = "your-secret-key"
func validateSignature(timestamp, nonce, signature string) bool {
data := timestamp + nonce
mac := hmac.New(sha256.New, []byte(secretKey))
mac.Write([]byte(data))
expectedSignature := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(signature), []byte(expectedSignature))
}
func handler(w http.ResponseWriter, r *http.Request) {
timestamp := r.Header.Get("X-Timestamp")
nonce := r.Header.Get("X-Nonce")
signature := r.Header.Get("X-Signature")
if!validateSignature(timestamp, nonce, signature) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
fmt.Fprintf(w, "Request is valid!")
}
func main() {
http.HandleFunc("/api", handler)
http.ListenAndServe(":8080", nil)
}
以下是客戶端結合時間戳和隨機字符串的請求簽名示例:
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"math/rand"
"net/http"
"strconv"
"time"
)
const secretKey = "your-secret-key"
func generateSignature(timestamp, nonce string) string {
data := timestamp + nonce
mac := hmac.New(sha256.New, []byte(secretKey))
mac.Write([]byte(data))
return hex.EncodeToString(mac.Sum(nil))
}
func generateNonce(length int) string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, length)
rand.Read(b)
for i := range b {
b[i] = charset[b[i]%byte(len(charset))]
}
return string(b)
}
func main() {
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
nonce := generateNonce(16)
signature := generateSignature(timestamp, nonce)
client := &http.Client{}
req, err := http.NewRequest("GET", "http://localhost:8080/api", nil)
if err!= nil {
fmt.Println("Error creating request:", err)
return
}
req.Header.Set("X-Signature", signature)
req.Header.Set("X-Timestamp", timestamp)
req.Header.Set("X-Nonce", nonce)
resp, err := client.Do(req)
if err!= nil {
fmt.Println("Error sending request:", err)
return
}
defer resp.Body.Close()
fmt.Println("Response status:", resp.Status)
}
四、API 計費策略與實現
package main
import (
"fmt"
"log"
"net/http"
"sync"
)
// 模擬數據庫,保存用戶的 API 調用次數和剩余額度
var userQuota = map[string]int{
"user1": 100, // 初始免費調用次數
}
var mu sync.Mutex
func recordCall(userID string) {
mu.Lock()
defer mu.Unlock()
if quota, exists := userQuota[userID]; exists && quota > 0 {
userQuota[userID]--
log.Printf("User %s called the API. Remaining quota: %d\n", userID, userQuota[userID])
} else {
log.Printf("User %s has no quota left.\n", userID)
}
}
func handler(w http.ResponseWriter, r *http.Request) {
userID := r.Header.Get("X-User-ID")
recordCall(userID)
if userQuota[userID] <= 0 {
http.Error(w, "Payment Required", http.StatusPaymentRequired)
return
}
fmt.Fprintf(w, "API Call Recorded!")
}
func main() {
http.HandleFunc("/api", handler)
http.ListenAndServe(":8080", nil)
}
package main
import (
"fmt"
"log"
"net/http"
"sync"
)
var userBalance = map[string]float64{
"user1": 50.00, // 用戶初始余額
}
var serviceCosts = map[string]float64{
"/api/simple-test": 0.1, // 簡單功能費用
"/api/advanced-test": 0.5, // 高級功能費用
}
var mu sync.Mutex
func recordCall(userID, endpoint string) bool {
mu.Lock()
defer mu.Unlock()
cost, exists := serviceCosts[endpoint]
if!exists {
return false
}
if balance, exists := userBalance[userID]; exists && balance >= cost {
userBalance[userID] -= cost
log.Printf("User %s used %s. Remaining balance: %.2f\n", userID, endpoint, userBalance[userID])
return true
}
return false
}
func handler(w http.ResponseWriter, r *http.Request) {
userID := r.Header.Get("X-User-ID")
endpoint := r.URL.Path
if!recordCall(userID, endpoint) {
http.Error(w, "Insufficient Funds", http.StatusPaymentRequired)
return
}
fmt.Fprintf(w, "Service %s Called!", endpoint)
}
func main() {
http.HandleFunc("/api/simple-test", handler)
http.HandleFunc("/api/advanced-test", handler)
http.ListenAndServe(":8080", nil)
}
package main
import (
"fmt"
"log"
"net/http"
"sync"
"time"
)
type Subscription struct {
Plan string
ExpiryDate time.Time
CallQuota int
}
var userSubscriptions = map[string]Subscription{
"user1": {
Plan: "monthly",
ExpiryDate: time.Now().AddDate(0, 1, 0), // 有效期 1 個月
CallQuota: 1000, // 每月調用額度
},
}
var mu sync.Mutex
func checkSubscription(userID string) bool {
mu.Lock()
defer mu.Unlock()
sub, exists := userSubscriptions[userID]
if!exists || time.Now().After(sub.ExpiryDate) {
return false
}
if sub.CallQuota > 0 {
sub.CallQuota--
userSubscriptions[userID] = sub
log.Printf("User %s used API. Remaining quota: %d\n", userID, sub.CallQuota)
return true
}
return false
}
func handler(w http.ResponseWriter, r *http.Request) {
userID := r.Header.Get("X-User-ID")
if!checkSubscription(userID) {
http.Error(w, "Subscription Expired or Quota Exceeded", http.StatusPaymentRequired)
return
}
fmt.Fprintf(w, "API Call within Subscription!")
}
func main() {
http.HandleFunc("/api", handler)
http.ListenAndServe(":8080", nil)
}
五、總結
版權聲明:
作者:小藍
鏈接:http://m.huanchou.cn/content/1246.html
本站部分內容和圖片來源網絡,不代表本站觀點,如有侵權,可聯系我方刪除。
作者:小藍
鏈接:http://m.huanchou.cn/content/1246.html
本站部分內容和圖片來源網絡,不代表本站觀點,如有侵權,可聯系我方刪除。
THE END