Initial commit: 网络验证平台
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/pkg/crypto"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func AppCrypto() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
appKey := c.Param("appKey")
|
||||
if appKey == "" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
var encryptType crypto.EncryptType
|
||||
switch app.EncryptType {
|
||||
case "aes":
|
||||
encryptType = crypto.EncryptTypeAES
|
||||
case "rc4":
|
||||
encryptType = crypto.EncryptTypeRC4
|
||||
default:
|
||||
encryptType = crypto.EncryptTypeNone
|
||||
}
|
||||
|
||||
cryptoManager := crypto.NewCryptoManager(encryptType, app.SecretKey)
|
||||
shouldEncrypt := encryptType != crypto.EncryptTypeNone
|
||||
|
||||
fmt.Printf("[AppCrypto] AppKey: %s, EncryptType: %s, SecretKey: %s, ShouldEncrypt: %v\n",
|
||||
appKey, app.EncryptType, app.SecretKey, shouldEncrypt)
|
||||
|
||||
c.Set("should_encrypt_response", shouldEncrypt)
|
||||
c.Set("crypto_manager", cryptoManager)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/pkg/crypto"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type CryptoMiddleware struct {
|
||||
cryptoManager *crypto.CryptoManager
|
||||
shouldEncrypt bool
|
||||
}
|
||||
|
||||
func NewCryptoMiddleware(encryptType crypto.EncryptType, secretKey string) *CryptoMiddleware {
|
||||
shouldEncrypt := encryptType != crypto.EncryptTypeNone
|
||||
return &CryptoMiddleware{
|
||||
cryptoManager: crypto.NewCryptoManager(encryptType, secretKey),
|
||||
shouldEncrypt: shouldEncrypt,
|
||||
}
|
||||
}
|
||||
|
||||
type EncryptedRequest struct {
|
||||
Data string `json:"data" binding:"required"`
|
||||
}
|
||||
|
||||
type EncryptedResponse struct {
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
func (cm *CryptoMiddleware) ProcessRequest() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
contentType := c.GetHeader("Content-Type")
|
||||
|
||||
if strings.Contains(contentType, "application/json") {
|
||||
body, err := io.ReadAll(c.Request.Body)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"code": 400, "message": "读取请求体失败"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
var req interface{}
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
c.JSON(400, gin.H{"code": 400, "message": "解析请求体失败"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
reqMap, ok := req.(map[string]interface{})
|
||||
if ok {
|
||||
if encryptedData, exists := reqMap["data"]; exists {
|
||||
if dataStr, ok := encryptedData.(string); ok {
|
||||
decrypted, err := cm.cryptoManager.Decrypt(dataStr)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"code": 400, "message": "解密失败: " + err.Error()})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
var decryptedData interface{}
|
||||
if err := json.Unmarshal([]byte(decrypted), &decryptedData); err != nil {
|
||||
c.JSON(400, gin.H{"code": 400, "message": "解析解密数据失败"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Set("decrypted_data", decryptedData)
|
||||
c.Set("is_encrypted", true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
}
|
||||
|
||||
c.Set("should_encrypt_response", cm.shouldEncrypt)
|
||||
c.Set("crypto_manager", cm.cryptoManager)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *CryptoMiddleware) ProcessResponse() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
|
||||
c.Writer = blw
|
||||
|
||||
c.Next()
|
||||
|
||||
shouldEncrypt := c.GetBool("should_encrypt_response")
|
||||
fmt.Printf("[ProcessResponse] ShouldEncrypt: %v\n", shouldEncrypt)
|
||||
|
||||
if shouldEncrypt {
|
||||
var response map[string]interface{}
|
||||
if err := json.Unmarshal(blw.body.Bytes(), &response); err == nil {
|
||||
responseJSON, _ := json.Marshal(response)
|
||||
fmt.Printf("[ProcessResponse] Response to encrypt: %s\n", string(responseJSON))
|
||||
|
||||
var cryptoManager *crypto.CryptoManager
|
||||
if cm, ok := c.Get("crypto_manager"); ok {
|
||||
cryptoManager = cm.(*crypto.CryptoManager)
|
||||
} else {
|
||||
appKey := c.Param("appKey")
|
||||
if appKey != "" {
|
||||
var app model.Application
|
||||
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err == nil {
|
||||
var encryptType crypto.EncryptType
|
||||
switch app.EncryptType {
|
||||
case "aes":
|
||||
encryptType = crypto.EncryptTypeAES
|
||||
case "rc4":
|
||||
encryptType = crypto.EncryptTypeRC4
|
||||
default:
|
||||
encryptType = crypto.EncryptTypeNone
|
||||
}
|
||||
cryptoManager = crypto.NewCryptoManager(encryptType, app.SecretKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cryptoManager != nil {
|
||||
encrypted, err := cryptoManager.Encrypt(string(responseJSON))
|
||||
if err == nil {
|
||||
encryptedResponse := EncryptedResponse{Data: encrypted}
|
||||
encryptedJSON, _ := json.Marshal(encryptedResponse)
|
||||
c.Writer.Header().Set("Content-Type", "application/json")
|
||||
c.Writer.Write(encryptedJSON)
|
||||
fmt.Printf("[ProcessResponse] Encrypted response sent\n")
|
||||
return
|
||||
} else {
|
||||
fmt.Printf("[ProcessResponse] Encryption error: %v\n", err)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("[ProcessResponse] CryptoManager is nil\n")
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("[ProcessResponse] JSON unmarshal error: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type bodyLogWriter struct {
|
||||
gin.ResponseWriter
|
||||
body *bytes.Buffer
|
||||
}
|
||||
|
||||
func (w *bodyLogWriter) Write(b []byte) (int, error) {
|
||||
return w.body.Write(b)
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/pkg/jwt"
|
||||
"verification-platform-backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func CheckAppStatus() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
appKey := c.Param("appKey")
|
||||
if appKey == "" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
var app model.Application
|
||||
if err := database.DB.Where("app_key = ?", appKey).First(&app).Error; err != nil {
|
||||
response.Error(c, 404, "应用不存在")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if app.Status == "maintenance" {
|
||||
response.Error(c, 503, "应用维护中,暂时无法访问")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if app.Status == "stopped" {
|
||||
response.Error(c, 403, "应用已停止运营")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Set("app", &app)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// JWT JWT中间件
|
||||
func JWT() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
fmt.Printf("JWT中间件被调用: %s %s\n", c.Request.Method, c.Request.URL.Path)
|
||||
|
||||
var token string
|
||||
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader != "" {
|
||||
parts := strings.SplitN(authHeader, " ", 2)
|
||||
if len(parts) == 2 && parts[0] == "Bearer" {
|
||||
token = parts[1]
|
||||
}
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
token = c.Query("token")
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
fmt.Printf("JWT中间件: Authorization header is required\n")
|
||||
response.Error(c, http.StatusUnauthorized, "Authorization header is required")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := jwt.ParseToken(token)
|
||||
if err != nil {
|
||||
fmt.Printf("JWT中间件: Invalid token: %v\n", err)
|
||||
response.Error(c, http.StatusUnauthorized, "Invalid token")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if time.Now().Unix() > claims.ExpiresAt.Unix() {
|
||||
fmt.Printf("JWT中间件: Token expired\n")
|
||||
response.Error(c, http.StatusUnauthorized, "Token expired")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Set("user_id", claims.UserID)
|
||||
c.Set("username", claims.Username)
|
||||
c.Set("role", claims.Role)
|
||||
|
||||
fmt.Printf("JWT中间件: 验证成功, user_id=%d, role=%s\n", claims.UserID, claims.Role)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// AdminAuth 管理员权限中间件
|
||||
func AdminAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
role, exists := c.Get("role")
|
||||
if !exists {
|
||||
response.Error(c, http.StatusForbidden, "Access denied")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if role != "admin" {
|
||||
response.Error(c, http.StatusForbidden, "Admin access required")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// DeveloperAuth 开发者权限中间件
|
||||
func DeveloperAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
role, exists := c.Get("role")
|
||||
if !exists {
|
||||
response.Error(c, http.StatusForbidden, "Access denied")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if role != "developer" && role != "admin" {
|
||||
response.Error(c, http.StatusForbidden, "Developer access required")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// PackageAuth 套餐授权中间件
|
||||
func PackageAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
role, _ := c.Get("role")
|
||||
if role == "admin" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
var user model.User
|
||||
if err := database.DB.First(&user, userID).Error; err != nil {
|
||||
response.Error(c, 500, "获取用户信息失败")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if user.CurrentPackageID == nil {
|
||||
response.Error(c, 403, "您还没有购买套餐,请先购买套餐后再使用开发者功能")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
var userPackage model.UserPackage
|
||||
if err := database.DB.Where("user_id = ? AND package_id = ? AND status = ?",
|
||||
userID, user.CurrentPackageID, "active").First(&userPackage).Error; err != nil {
|
||||
response.Error(c, 403, "套餐授权已失效,请续费或重新购买套餐")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if userPackage.ExpiredAt != nil && userPackage.ExpiredAt.Before(time.Now()) {
|
||||
response.Error(c, 403, "套餐已过期,请续费后再使用开发者功能")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Set("user_package", &userPackage)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// Logger 日志中间件
|
||||
func Logger() gin.HandlerFunc {
|
||||
return gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
|
||||
return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
|
||||
param.ClientIP,
|
||||
param.TimeStamp.Format(time.RFC1123),
|
||||
param.Method,
|
||||
param.Path,
|
||||
param.Request.Proto,
|
||||
param.StatusCode,
|
||||
param.Latency,
|
||||
param.Request.UserAgent(),
|
||||
param.ErrorMessage,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// Recovery 恢复中间件
|
||||
func Recovery() gin.HandlerFunc {
|
||||
return gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
|
||||
response.Error(c, http.StatusInternalServerError, "Internal server error")
|
||||
})
|
||||
}
|
||||
|
||||
// Cors 跨域中间件
|
||||
func Cors() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "false")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE, PATCH")
|
||||
c.Writer.Header().Set("Access-Control-Max-Age", "86400")
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// RateLimit 限流中间件
|
||||
func RateLimit() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 这里可以实现基于Redis的限流逻辑
|
||||
// 暂时跳过实现
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"verification-platform-backend/internal/database"
|
||||
"verification-platform-backend/internal/model"
|
||||
"verification-platform-backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func CheckApiLimit() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
var user model.User
|
||||
if err := database.DB.Preload("CurrentPackage").First(&user, userID).Error; err != nil {
|
||||
response.Error(c, 500, "获取用户信息失败")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if user.CurrentPackageID == nil {
|
||||
response.Error(c, 403, "您还没有购买套餐,请先购买套餐")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
var permission model.PackagePermission
|
||||
if err := database.DB.Where("package_id = ?", user.CurrentPackageID).First(&permission).Error; err != nil {
|
||||
response.Error(c, 500, "获取套餐权限失败")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if user.ApiCallsResetAt == nil || now.Sub(*user.ApiCallsResetAt) >= 24*time.Hour {
|
||||
user.ApiCallsUsed = 0
|
||||
user.ApiCallsResetAt = &now
|
||||
database.DB.Save(&user)
|
||||
}
|
||||
|
||||
if user.ApiCallsUsed >= permission.MaxApiCalls {
|
||||
response.Error(c, 403, fmt.Sprintf("API调用次数已达上限(%d次/天),请升级套餐", permission.MaxApiCalls))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
|
||||
user.ApiCallsUsed++
|
||||
database.DB.Save(&user)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckStorageLimit(fileSize int64) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, 401, "未授权")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
var user model.User
|
||||
if err := database.DB.Preload("CurrentPackage").First(&user, userID).Error; err != nil {
|
||||
response.Error(c, 500, "获取用户信息失败")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if user.CurrentPackageID == nil {
|
||||
response.Error(c, 403, "您还没有购买套餐,请先购买套餐")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
var permission model.PackagePermission
|
||||
if err := database.DB.Where("package_id = ?", user.CurrentPackageID).First(&permission).Error; err != nil {
|
||||
response.Error(c, 500, "获取套餐权限失败")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
maxStorageBytes := int64(permission.MaxStorage) * 1024 * 1024
|
||||
if user.StorageUsed+fileSize > maxStorageBytes {
|
||||
usedMB := float64(user.StorageUsed) / 1024 / 1024
|
||||
maxMB := float64(permission.MaxStorage)
|
||||
response.Error(c, 403, fmt.Sprintf("存储空间不足,已使用 %.2f MB / %.2f MB", usedMB, maxMB))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func RecordApiUsage() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
startTime := time.Now()
|
||||
|
||||
c.Next()
|
||||
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
||||
appID, _ := c.Get("app_id")
|
||||
|
||||
duration := time.Since(startTime)
|
||||
|
||||
usage := model.ApiUsage{
|
||||
UserID: userID.(uint),
|
||||
ApplicationID: appID.(uint),
|
||||
Endpoint: c.Request.URL.Path,
|
||||
Method: c.Request.Method,
|
||||
IPAddress: c.ClientIP(),
|
||||
UserAgent: c.Request.UserAgent(),
|
||||
ResponseTime: int(duration.Milliseconds()),
|
||||
StatusCode: c.Writer.Status(),
|
||||
Success: c.Writer.Status() < 400,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if len(c.Errors) > 0 {
|
||||
usage.ErrorMessage = c.Errors.String()
|
||||
}
|
||||
|
||||
database.DB.Create(&usage)
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateStorageUsed(userID uint, fileSize int64, action string) error {
|
||||
var user model.User
|
||||
if err := database.DB.First(&user, userID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if action == "upload" {
|
||||
user.StorageUsed += fileSize
|
||||
} else if action == "delete" {
|
||||
user.StorageUsed -= fileSize
|
||||
if user.StorageUsed < 0 {
|
||||
user.StorageUsed = 0
|
||||
}
|
||||
}
|
||||
|
||||
return database.DB.Save(&user).Error
|
||||
}
|
||||
Reference in New Issue
Block a user