如何构建稳健的 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)
}
五、总结
版权声明:
作者:小蓝
链接:https://www.lanmiyun.com/content/1246.html
本站部分内容和图片来源网络,不代表本站观点,如有侵权,可联系我方删除。
作者:小蓝
链接:https://www.lanmiyun.com/content/1246.html
本站部分内容和图片来源网络,不代表本站观点,如有侵权,可联系我方删除。
THE END