初始化v1

This commit is contained in:
2025-08-03 00:11:15 +08:00
commit b88c8b8afa
171 changed files with 22353 additions and 0 deletions

80
api/middleware/auth.go Normal file
View File

@@ -0,0 +1,80 @@
package middleware
import (
"nl-video-api/internal/consts"
"nl-video-api/utility/jwt"
"nl-video-api/utility/response"
"strings"
"github.com/gogf/gf/v2/net/ghttp"
)
// Auth JWT认证中间件
func Auth(r *ghttp.Request) {
// 获取Authorization头
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
response.Error(r, response.CodeUnauthorized, "请提供认证令牌")
return
}
// 检查Bearer前缀
if !strings.HasPrefix(authHeader, "Bearer ") {
response.Error(r, response.CodeUnauthorized, "认证令牌格式错误")
return
}
// 提取token
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
if tokenString == "" {
response.Error(r, response.CodeUnauthorized, "认证令牌不能为空")
return
}
// 解析token
claims, err := jwt.ParseToken(tokenString)
if err != nil {
response.Error(r, response.CodeTokenInvalid, "认证令牌无效")
return
}
// 将用户信息存储到上下文
r.SetCtxVar("user_id", claims.UserID)
r.SetCtxVar("username", claims.Username)
r.SetCtxVar("user_type", claims.UserType)
r.Middleware.Next()
}
// AdminAuth 管理员认证中间件
func AdminAuth(r *ghttp.Request) {
// 先执行基础认证
Auth(r)
if r.Response.Status >= 400 {
return
}
// 检查用户类型
userType := r.GetCtxVar("user_type").String()
if userType != consts.UserTypeAdmin {
response.Error(r, response.CodeForbidden, "需要管理员权限")
return
}
r.Middleware.Next()
}
// OptionalAuth 可选认证中间件(不强制要求登录)
func OptionalAuth(r *ghttp.Request) {
authHeader := r.Header.Get("Authorization")
if authHeader != "" && strings.HasPrefix(authHeader, "Bearer ") {
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
if claims, err := jwt.ParseToken(tokenString); err == nil {
r.SetCtxVar("user_id", claims.UserID)
r.SetCtxVar("username", claims.Username)
r.SetCtxVar("user_type", claims.UserType)
}
}
r.Middleware.Next()
}

19
api/middleware/cors.go Normal file
View File

@@ -0,0 +1,19 @@
package middleware
import (
"github.com/gogf/gf/v2/net/ghttp"
)
// CORS 跨域处理中间件
func CORS(r *ghttp.Request) {
// 设置CORS头
r.Response.CORSDefault()
// 处理预检请求
if r.Method == "OPTIONS" {
r.Response.WriteHeader(200)
return
}
r.Middleware.Next()
}

54
api/middleware/log.go Normal file
View File

@@ -0,0 +1,54 @@
package middleware
import (
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gtime"
)
// RequestLog 请求日志中间件
func RequestLog(r *ghttp.Request) {
start := time.Now()
// 记录请求开始
g.Log().Info(r.Context(),
"请求开始",
"method", r.Method,
"uri", r.RequestURI,
"ip", r.GetClientIp(),
"user_agent", r.Header.Get("User-Agent"),
)
r.Middleware.Next()
// 记录请求结束
duration := time.Since(start)
g.Log().Info(r.Context(),
"请求结束",
"method", r.Method,
"uri", r.RequestURI,
"status", r.Response.Status,
"duration", duration.String(),
"ip", r.GetClientIp(),
)
}
// ErrorLog 错误日志中间件
func ErrorLog(r *ghttp.Request) {
r.Middleware.Next()
// 如果有错误,记录详细信息
if r.Response.Status >= 400 {
g.Log().Error(r.Context(),
"请求错误",
"method", r.Method,
"uri", r.RequestURI,
"status", r.Response.Status,
"ip", r.GetClientIp(),
"user_agent", r.Header.Get("User-Agent"),
"time", gtime.Now().String(),
)
}
}

View File

@@ -0,0 +1,221 @@
package middleware
import (
"strings"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"nl-video-api/internal/service/auth"
"nl-video-api/utility/jwt"
"nl-video-api/utility/response"
)
// PermissionMiddleware 权限验证中间件
func PermissionMiddleware(r *ghttp.Request) {
// 获取请求路径和方法
apiPath := r.URL.Path
method := r.Method
// 跳过不需要权限验证的路径
skipPaths := []string{
"/api/v1/auth/login",
"/api/v1/auth/register",
"/api/v1/admin/login",
"/api/v1/admin/register",
}
for _, skipPath := range skipPaths {
if apiPath == skipPath {
r.Middleware.Next()
return
}
}
// 获取Token
token := r.Header.Get("Authorization")
if token == "" {
response.Error(r, 1002, "请先登录")
return
}
// 移除Bearer前缀
if strings.HasPrefix(token, "Bearer ") {
token = strings.TrimPrefix(token, "Bearer ")
}
// 验证Token
claims, err := jwt.ParseToken(token)
if err != nil {
response.Error(r, 1003, "Token无效")
return
}
// 获取用户ID
userId := int(claims.UserID)
if userId <= 0 {
response.Error(r, 1003, "Token无效")
return
}
// 检查API权限
hasPermission, err := auth.Permission.CheckApiPermission(r.Context(), userId, apiPath, method)
if err != nil {
g.Log().Errorf(r.Context(), "权限检查失败: %v", err)
response.Error(r, 1004, "权限检查失败")
return
}
if !hasPermission {
response.Error(r, 1004, "没有访问权限")
return
}
// 将用户ID存储到上下文中
r.SetCtxVar("user_id", userId)
r.Middleware.Next()
}
// RolePermissionMiddleware 角色权限验证中间件
func RolePermissionMiddleware(requiredRole string) func(r *ghttp.Request) {
return func(r *ghttp.Request) {
// 获取Token
token := r.Header.Get("Authorization")
if token == "" {
response.Error(r, 1002, "请先登录")
return
}
// 移除Bearer前缀
if strings.HasPrefix(token, "Bearer ") {
token = strings.TrimPrefix(token, "Bearer ")
}
// 验证Token
claims, err := jwt.ParseToken(token)
if err != nil {
response.Error(r, 1003, "Token无效")
return
}
// 获取用户角色
userRole := claims.UserType
if userRole == "" {
response.Error(r, 1004, "用户角色信息无效")
return
}
// 检查角色权限
if userRole != requiredRole && userRole != "super_admin" {
response.Error(r, 1004, "没有访问权限")
return
}
r.Middleware.Next()
}
}
// AdminPermissionMiddleware 管理员权限验证中间件
func AdminPermissionMiddleware(r *ghttp.Request) {
// 获取Token
token := r.Header.Get("Authorization")
if token == "" {
response.Error(r, 1002, "请先登录")
return
}
// 移除Bearer前缀
if strings.HasPrefix(token, "Bearer ") {
token = strings.TrimPrefix(token, "Bearer ")
}
// 验证Token
claims, err := jwt.ParseToken(token)
if err != nil {
response.Error(r, 1003, "Token无效")
return
}
// 检查是否为管理员Token
userType := claims.UserType
if userType != "admin" {
response.Error(r, 1004, "需要管理员权限")
return
}
// 获取管理员ID
adminId := int(claims.UserID)
if adminId <= 0 {
response.Error(r, 1003, "Token无效")
return
}
// 将管理员ID存储到上下文中
r.SetCtxVar("admin_id", adminId)
r.Middleware.Next()
}
// CheckPermissionCode 检查权限编码中间件
func CheckPermissionCode(permissionCode string) func(r *ghttp.Request) {
return func(r *ghttp.Request) {
// 获取用户ID
userId := r.GetCtxVar("user_id")
if userId == nil {
response.Error(r, 1002, "请先登录")
return
}
userIdInt := userId.Int()
if userIdInt <= 0 {
response.Error(r, 1002, "用户信息无效")
return
}
// 检查权限
hasPermission, err := auth.Permission.CheckUserPermission(r.Context(), userIdInt, permissionCode)
if err != nil {
g.Log().Errorf(r.Context(), "权限检查失败: %v", err)
response.Error(r, 1004, "权限检查失败")
return
}
if !hasPermission {
response.Error(r, 1004, "没有访问权限")
return
}
r.Middleware.Next()
}
}
// SuperAdminMiddleware 超级管理员权限验证中间件
func SuperAdminMiddleware(r *ghttp.Request) {
// 获取Token
token := r.Header.Get("Authorization")
if token == "" {
response.Error(r, 1002, "请先登录")
return
}
// 移除Bearer前缀
if strings.HasPrefix(token, "Bearer ") {
token = strings.TrimPrefix(token, "Bearer ")
}
// 验证Token
claims, err := jwt.ParseToken(token)
if err != nil {
response.Error(r, 1003, "Token无效")
return
}
// 检查是否为超级管理员
role := claims.UserType
if role != "super_admin" {
response.Error(r, 1004, "需要超级管理员权限")
return
}
r.Middleware.Next()
}

View File

@@ -0,0 +1,60 @@
package middleware
import (
"fmt"
"nl-video-api/utility/response"
"time"
"github.com/gogf/gf/v2/database/gredis"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
// RateLimit 限流中间件
func RateLimit(maxRequests int, window time.Duration) func(r *ghttp.Request) {
return func(r *ghttp.Request) {
var (
ctx = r.Context()
client = g.Redis()
key = fmt.Sprintf("rate_limit:%s", r.GetClientIp())
)
// 获取当前请求次数
count, err := client.Get(ctx, key)
if err != nil {
g.Log().Error(ctx, "Redis获取失败:", err)
r.Middleware.Next()
return
}
// 检查是否超过限制
if count.Int() >= maxRequests {
response.Error(r, response.CodeError, "请求过于频繁,请稍后再试")
return
}
// 增加计数
if count.Int() == 0 {
// 第一次请求,设置过期时间
seconds := int64(window.Seconds())
client.Set(ctx, key, 1, gredis.SetOption{
TTLOption: gredis.TTLOption{EX: &seconds},
})
} else {
// 增加计数
client.Incr(ctx, key)
}
r.Middleware.Next()
}
}
// APIRateLimit API接口限流每分钟60次
func APIRateLimit(r *ghttp.Request) {
RateLimit(60, time.Minute)(r)
}
// LoginRateLimit 登录接口限流每分钟5次
func LoginRateLimit(r *ghttp.Request) {
RateLimit(5, time.Minute)(r)
}

View File

@@ -0,0 +1,160 @@
package middleware
import (
"context"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/util/gconv"
"nl-video-api/utility/logger"
)
// RequestLogger 请求日志中间件
func RequestLogger(r *ghttp.Request) {
// 记录请求开始时间
startTime := time.Now()
// 获取请求信息
method := r.Method
uri := r.RequestURI
ip := r.GetClientIp()
userAgent := r.Header.Get("User-Agent")
// 读取请求体
var requestBody []byte
if r.GetBodyString() != "" {
requestBody = []byte(r.GetBodyString())
}
// 记录请求开始日志
logger.LogInfo(r.Context(), "请求开始 | Method: %s | URI: %s | IP: %s | UserAgent: %s | Body: %s",
method, uri, ip, userAgent, string(requestBody))
// 继续处理请求
r.Middleware.Next()
// 计算请求处理时间
duration := time.Since(startTime)
// 获取响应状态码
status := r.Response.Status
// 获取响应内容(简化处理)
responseBody := ""
// 记录请求完成日志
logger.LogInfo(r.Context(), "请求完成 | Method: %s | URI: %s | IP: %s | Status: %d | Duration: %v | Response: %s",
method, uri, ip, status, duration, responseBody)
// 如果是错误状态码,记录错误日志
if status >= 400 {
logger.LogError(r.Context(), "请求错误 | Method: %s | URI: %s | IP: %s | Status: %d | Duration: %v | Response: %s",
method, uri, ip, status, duration, responseBody)
}
}
// ErrorLogger 错误日志中间件
func ErrorLogger(r *ghttp.Request) {
// 使用defer捕获panic
defer func() {
if err := recover(); err != nil {
// 记录panic错误
logger.LogPanic(r.Context(), "请求panic | Method: %s | URI: %s | IP: %s | Error: %v",
r.Method, r.RequestURI, r.GetClientIp(), err)
// 返回统一错误响应
r.Response.WriteJsonExit(g.Map{
"code": 5000,
"msg": "服务器内部错误",
"result": nil,
})
}
}()
// 继续处理请求
r.Middleware.Next()
}
// BusinessLogger 业务日志记录器
func LogBusinessOperation(ctx context.Context, module string, operation string, userId interface{}, params interface{}, result interface{}, err error) {
fields := g.Map{
"module": module,
"operation": operation,
"user_id": userId,
"params": gconv.String(params),
"result": gconv.String(result),
}
if err != nil {
fields["error"] = err.Error()
logger.LogWithFields(ctx, "error", "error", "业务操作失败", fields)
} else {
logger.LogWithFields(ctx, "log", "info", "业务操作成功", fields)
}
}
// AuthLogger 认证日志记录器
func LogAuthOperation(ctx context.Context, operation string, username string, ip string, userAgent string, success bool, err error) {
fields := g.Map{
"operation": operation,
"username": username,
"ip": ip,
"user_agent": userAgent,
"success": success,
}
if err != nil {
fields["error"] = err.Error()
logger.LogWithFields(ctx, "error", "error", "认证操作失败", fields)
} else {
logger.LogWithFields(ctx, "log", "info", "认证操作成功", fields)
}
}
// APILogger API调用日志记录器
func LogAPICall(ctx context.Context, api string, method string, params interface{}, response interface{}, duration time.Duration, err error) {
fields := g.Map{
"api": api,
"method": method,
"params": gconv.String(params),
"response": gconv.String(response),
"duration": duration.String(),
}
if err != nil {
fields["error"] = err.Error()
logger.LogWithFields(ctx, "error", "error", "API调用失败", fields)
} else {
logger.LogWithFields(ctx, "log", "info", "API调用成功", fields)
}
}
// ValidationLogger 参数验证日志记录器
func LogValidationError(ctx context.Context, field string, value interface{}, rule string, message string) {
fields := g.Map{
"field": field,
"value": gconv.String(value),
"rule": rule,
"message": message,
}
logger.LogWithFields(ctx, "error", "warn", "参数验证失败", fields)
}
// PerformanceLogger 性能日志记录器
func LogPerformance(ctx context.Context, operation string, duration time.Duration, threshold time.Duration) {
fields := g.Map{
"operation": operation,
"duration": duration.String(),
"threshold": threshold.String(),
"slow": duration > threshold,
}
if duration > threshold {
logger.LogWithFields(ctx, "error", "warn", "慢操作检测", fields)
} else {
logger.LogWithFields(ctx, "log", "debug", "性能监控", fields)
}
}

View File

@@ -0,0 +1,74 @@
package middleware
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"nl-video-api/utility/response"
)
// ResponseHandler 全局响应处理中间件
// 确保所有响应都返回HTTP 200状态码错误信息通过code和message字段传递
func ResponseHandler(r *ghttp.Request) {
// 设置默认响应头
r.Response.Header().Set("Content-Type", "application/json")
// 继续执行后续中间件和处理函数
r.Middleware.Next()
// 检查响应状态码如果不是200则转换为统一格式
if r.Response.Status != 200 {
// 获取原始状态码
originalStatus := r.Response.Status
// 根据HTTP状态码映射到业务错误码
var businessCode int
var message string
switch originalStatus {
case 400:
businessCode = response.CodeInvalidParam
message = "请求参数错误"
case 401:
businessCode = response.CodeUnauthorized
message = "未授权访问"
case 403:
businessCode = response.CodeForbidden
message = "权限不足"
case 404:
businessCode = response.CodeNotFound
message = "资源不存在"
case 500:
businessCode = response.CodeServerError
message = "服务器内部错误"
default:
businessCode = response.CodeError
message = "请求处理失败"
}
// 清空原有响应内容
r.Response.ClearBuffer()
// 返回统一格式的错误响应HTTP状态码为200
response.Error(r, businessCode, message)
}
}
// ErrorHandler 错误处理中间件
// 捕获panic和其他未处理的错误统一返回格式
func ErrorHandler(r *ghttp.Request) {
defer func() {
if err := recover(); err != nil {
// 记录错误日志
g.Log().Errorf(r.Context(), "Panic recovered: %v", err)
// 清空响应缓冲区
r.Response.ClearBuffer()
// 返回统一格式的错误响应
response.Error(r, response.CodeServerError, "系统内部错误")
}
}()
// 继续执行后续中间件和处理函数
r.Middleware.Next()
}