支付,OSS 服务重构完成

This commit is contained in:
GeekMaster
2025-08-24 19:32:45 +08:00
parent 7fb0aad3c7
commit 536b4b8056
57 changed files with 1663 additions and 1358 deletions

View File

@@ -0,0 +1,107 @@
package midware
import (
"context"
"fmt"
"geekai/core/types"
"geekai/utils"
"geekai/utils/resp"
"time"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
"github.com/golang-jwt/jwt"
)
// 用户授权验证
func UserAuthMiddleware(secretKey string, redis *redis.Client) gin.HandlerFunc {
return func(c *gin.Context) {
tokenString := c.GetHeader(types.UserAuthHeader)
if tokenString == "" {
resp.NotAuth(c, "无效的授权令牌")
c.Abort()
return
}
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("不支持的令牌签名方法: %v", token.Header["alg"])
}
return []byte(secretKey), nil
})
if err != nil {
resp.NotAuth(c, fmt.Sprintf("解析授权令牌失败: %v", err))
c.Abort()
return
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok || !token.Valid {
resp.NotAuth(c, "令牌无效")
c.Abort()
return
}
expr := utils.IntValue(utils.InterfaceToString(claims["expired"]), 0)
if expr > 0 && int64(expr) < time.Now().Unix() {
resp.NotAuth(c, "令牌过期")
c.Abort()
return
}
key := fmt.Sprintf("users/%v", claims["user_id"])
if _, err := redis.Get(context.Background(), key).Result(); err != nil {
resp.NotAuth(c, "当前用户已退出登录")
c.Abort()
return
}
c.Set(types.LoginUserID, claims["user_id"])
}
}
func AdminAuthMiddleware(secretKey string, redis *redis.Client) gin.HandlerFunc {
return func(c *gin.Context) {
tokenString := c.GetHeader(types.AdminAuthHeader)
if tokenString == "" {
resp.NotAuth(c, "无效的授权令牌")
c.Abort()
return
}
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("不支持的令牌签名方法: %v", token.Header["alg"])
}
return []byte(secretKey), nil
})
if err != nil {
resp.NotAuth(c, fmt.Sprintf("解析授权令牌失败: %v", err))
c.Abort()
return
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok || !token.Valid {
resp.NotAuth(c, "令牌无效")
c.Abort()
return
}
expr := utils.IntValue(utils.InterfaceToString(claims["expired"]), 0)
if expr > 0 && int64(expr) < time.Now().Unix() {
resp.NotAuth(c, "令牌过期")
c.Abort()
return
}
key := fmt.Sprintf("admin/%v", claims["user_id"])
if _, err := redis.Get(context.Background(), key).Result(); err != nil {
resp.NotAuth(c, "当前用户已退出登录")
c.Abort()
return
}
c.Set(types.AdminUserID, claims["user_id"])
}
}

View File

@@ -0,0 +1,80 @@
package midware
import (
"bytes"
"geekai/utils"
"io"
"strings"
"github.com/gin-gonic/gin"
)
// 统一参数处理
func ParameterHandlerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// GET 参数处理
params := c.Request.URL.Query()
for key, values := range params {
for i, value := range values {
params[key][i] = strings.TrimSpace(value)
}
}
// update get parameters
c.Request.URL.RawQuery = params.Encode()
// skip file upload requests
contentType := c.Request.Header.Get("Content-Type")
if strings.Contains(contentType, "multipart/form-data") {
c.Next()
return
}
if strings.Contains(contentType, "application/json") {
// process POST JSON request body
bodyBytes, err := io.ReadAll(c.Request.Body)
if err != nil {
c.Next()
return
}
// 还原请求体
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
// 将请求体解析为 JSON
var jsonData map[string]any
if err := c.ShouldBindJSON(&jsonData); err != nil {
c.Next()
return
}
// 对 JSON 数据中的字符串值去除两端空格
trimJSONStrings(jsonData)
// 更新请求体
c.Request.Body = io.NopCloser(bytes.NewBufferString(utils.JsonEncode(jsonData)))
}
c.Next()
}
}
// 递归对 JSON 数据中的字符串值去除两端空格
func trimJSONStrings(data any) {
switch v := data.(type) {
case map[string]any:
for key, value := range v {
switch valueType := value.(type) {
case string:
v[key] = strings.TrimSpace(valueType)
case map[string]any, []any:
trimJSONStrings(value)
}
}
case []any:
for i, value := range v {
switch valueType := value.(type) {
case string:
v[i] = strings.TrimSpace(valueType)
case map[string]any, []any:
trimJSONStrings(value)
}
}
}
}

View File

@@ -0,0 +1,43 @@
package midware
import (
"context"
"fmt"
"geekai/core/types"
"geekai/utils"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
)
// RateLimitEvery 使用 Redis 做固定间隔限流:在 interval 内仅允许一次请求
// Key 优先使用登录用户ID若没有则退化为 route + IP
func RateLimitEvery(redisClient *redis.Client, interval time.Duration) gin.HandlerFunc {
return func(c *gin.Context) {
keyID := ""
if userID, ok := c.Get(types.LoginUserID); ok {
keyID = fmt.Sprintf("user:%s", utils.InterfaceToString(userID))
} else {
keyID = fmt.Sprintf("ip:%s", c.ClientIP())
}
fullPath := c.FullPath()
if fullPath == "" {
fullPath = c.Request.URL.Path
}
key := fmt.Sprintf("rl:%s:%s", fullPath, keyID)
okSet, err := redisClient.SetNX(context.Background(), key, 1, interval).Result()
if err != nil {
// Redis 异常时放行,避免误伤可用性
return
}
if !okSet {
c.JSON(http.StatusTooManyRequests, types.BizVo{Code: types.Failed, Message: "请求过于频繁,请稍后重试"})
c.Abort()
return
}
}
}