This commit is contained in:
henry
2021-11-02 09:43:19 +08:00
parent 570bb3c772
commit 4734344985
78 changed files with 4798 additions and 0 deletions

127
router/auth.go Normal file
View File

@ -0,0 +1,127 @@
package router
import (
"Edu/app/service"
"Edu/config"
cache2 "Edu/serve/cache"
"Edu/utils"
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
// SkipperURL 跳过验证
type SkipperURL func(*gin.Context) bool
// PermissionHandle 权限验证
type PermissionHandle func(key string) gin.HandlerFunc
// AddSkipperURL 添加路由
func AddSkipperURL(url ...string) SkipperURL {
return func(c *gin.Context) bool {
path := c.Request.URL.Path
return utils.InArray(path, url)
}
}
// NeedLogin 需要登录
func NeedLogin(skipperURL ...SkipperURL) gin.HandlerFunc {
return func(c *gin.Context) {
if len(skipperURL) > 0 && skipperURL[0](c) {
c.Next()
return
}
token := c.GetHeader(config.APIRequestToken)
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{"message": "Token异常"})
c.Abort()
return
}
tokenInfo := utils.JWTDecrypt(token)
if tokenInfo == nil || len(tokenInfo) <= 0 {
c.JSON(http.StatusUnauthorized, gin.H{"message": "Token无效"})
c.Abort()
return
}
expTimestamp := utils.StringToInt64(fmt.Sprintf("%v", tokenInfo["exp"]))
expTime := time.Unix(expTimestamp, 0)
ok := expTime.After(time.Now())
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"message": "Token过期"})
c.Abort()
return
}
cache, _ := cache2.Cache.HGet(config.RedisKeyForAccount, fmt.Sprintf("%v", tokenInfo[config.TokenForUID]))
if cache == "" {
c.JSON(http.StatusUnauthorized, gin.H{"message": "用户未登录或已退出"})
c.Abort()
return
}
session := new(service.Session)
_ = session.UnmarshalBinary([]byte(cache))
if !config.SettingInfo.MultipleLogin && session.Token != token {
c.JSON(http.StatusUnauthorized, gin.H{"message": "登录失效,已在其他地方登录!"})
c.Abort()
return
}
c.Set(config.TokenForSession, session)
c.Next()
}
}
// NeedGradeParam 需要年级参数
func NeedGradeParam(skipperURL ...SkipperURL) gin.HandlerFunc {
return func(c *gin.Context) {
if len(skipperURL) > 0 && skipperURL[0](c) {
c.Next()
return
}
param := c.GetHeader(config.APIRequestGrade)
if param == "" {
c.JSON(http.StatusBadRequest, gin.H{"message": "参数异常"})
c.Abort()
return
}
grade := utils.StringToInt(param)
if grade <= 0 {
c.JSON(http.StatusBadRequest, gin.H{"message": "Grade异常"})
c.Abort()
return
}
c.Set(config.TokenForGrade, grade)
c.Next()
}
}
// NeedPermission 需要权限验证
func NeedPermission(skipperURL ...SkipperURL) PermissionHandle {
return func(key string) gin.HandlerFunc {
return func(c *gin.Context) {
if len(skipperURL) > 0 && skipperURL[0](c) {
c.Next()
return
}
//session, _ := c.Get(config.TokenForSession)
//_session := session.(*service.Session)
//if pass, _ := service.NewPermission(nil, &service.AuthRequest{
// Url: key,
// Method: c.Request.Method,
//})(_session.TenantKey, fmt.Sprintf("%d", _session.UID)).Enforce(); !pass {
// c.JSON(http.StatusOK, gin.H{"code": http.StatusForbidden, "msg": "无权限访问!"})
// c.Abort()
// return
//}
c.Next()
}
}
}

115
router/middleware.go Normal file
View File

@ -0,0 +1,115 @@
package router
import (
"Edu/config"
"Edu/serve/logger"
"context"
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Headers", "access-control-allow-origin, access-control-allow-headers, application/octet-stream")
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
c.Header("Access-Control-Allow-Credentials", "true")
if method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}
// NoMethodHandler 未找到请求方法的处理函数
func NoMethodHandler() gin.HandlerFunc {
return func(c *gin.Context) {
c.JSON(http.StatusMethodNotAllowed, gin.H{
"message": "未找到请求路由的处理函数",
})
c.Abort()
return
}
}
// NoRouteHandler 未找到请求路由的处理函数
func NoRouteHandler() gin.HandlerFunc {
return func(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{
"message": "未找到请求路由的处理函数",
})
c.Abort()
return
}
}
func LoggerHandle(log string, leastDay uint) gin.HandlerFunc {
_logger := logger.NewLogger().Init(&logger.Option{File: log, LeastDay: leastDay, Level: "debug", IsStdout: false}).Logger
return func(c *gin.Context) {
_logger.Log(logrus.InfoLevel, map[string]interface{}{
"Status": c.Writer.Status(),
"IP": c.ClientIP(),
"Method": c.Request.Method,
"Url": c.Request.RequestURI,
})
}
}
// TimeoutHandle 超时处理
func TimeoutHandle(timeout time.Duration) gin.HandlerFunc {
return func(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), timeout)
defer func() {
if ctx.Err() == context.DeadlineExceeded {
c.Writer.WriteHeader(http.StatusGatewayTimeout)
c.Abort()
}
cancel()
}()
c.Request = c.Request.WithContext(ctx)
c.Next()
}
}
// RecoveryHandler 崩溃恢复中间件
func RecoveryHandler() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
fmt.Printf("Recoverrequest【%s】 error【%v】\n", c.Request.URL, err)
c.JSON(http.StatusInternalServerError, gin.H{
"message": "Internal Server Error",
})
c.Abort()
return
}
}()
c.Next()
}
}
// AnalysisParamsHandler 解析参数
func AnalysisParamsHandler() gin.HandlerFunc {
return func(c *gin.Context) {
url := c.Request.URL
var data string
value, has := config.SystemConfig[url.String()]
if has {
data = value.(string)
}
c.Set(config.ContentForLocal, data)
}
}

74
router/rate/ip.go Normal file
View File

@ -0,0 +1,74 @@
package rate
import (
"net/http"
"sync"
"github.com/gin-gonic/gin"
"golang.org/x/time/rate"
)
// IPRateLimiter IP限流器
type IPRateLimiter struct {
ips map[string]*rate.Limiter // IP地址库
limit int
capacity int
mutex *sync.RWMutex // 锁
}
type AllowHandle func(ctx *gin.Context) bool
type IPRateLimiterConfig func(limit, capacity int) gin.HandlerFunc
var IPRateLimiterHandle *IPRateLimiter
// Handle
func (this *IPRateLimiter) Handle() AllowHandle {
return func(ctx *gin.Context) bool {
ip := ctx.ClientIP()
this.mutex.RLock()
defer this.mutex.RUnlock()
limiter, exists := this.ips[ip]
if !exists {
limiter = rate.NewLimiter(rate.Limit(this.limit), this.capacity)
this.ips[ip] = limiter
}
return limiter.Allow()
}
}
// NewIPRateLimiter 初始化限流器
func NewIPRateLimiter() IPRateLimiterConfig {
return func(limit, capacity int) gin.HandlerFunc {
IPRateLimiterHandle = &IPRateLimiter{
ips: make(map[string]*rate.Limiter, 0),
limit: limit,
capacity: capacity,
mutex: new(sync.RWMutex),
}
return nil
}
}
// RequestIPRateLimiter 请求限流
func RequestIPRateLimiter() IPRateLimiterConfig {
return func(limit, capacity int) gin.HandlerFunc {
return func(c *gin.Context) {
if IPRateLimiterHandle == nil {
NewIPRateLimiter()(limit, capacity)
}
if !IPRateLimiterHandle.Handle()(c) {
c.JSON(http.StatusOK, gin.H{
"code": http.StatusTooManyRequests,
"msg": "访问频率过快,请稍后访问!",
})
c.Abort()
return
}
c.Next()
}
}
}

126
router/router.go Normal file
View File

@ -0,0 +1,126 @@
package router
import (
"Edu/app/api"
"Edu/config"
"Edu/router/rate"
"io"
"net/http"
"os"
"time"
"github.com/gin-gonic/gin"
)
type Router struct {
*Option
handler *gin.Engine
}
type (
Option struct {
Mode string
IsCors bool
*RateLimitConfig
}
// RateLimitConfig 限流配置
RateLimitConfig struct {
IsRate bool `json:"is_rate"`
Limit int `json:"limit"`
Capacity int `json:"capacity"`
}
)
func (this *Router) registerAPI() {
apiPrefix := "/api"
g := this.handler.Group(apiPrefix)
g.Use()
// 登录验证
g.Use(NeedLogin(AddSkipperURL([]string{
apiPrefix + "/v1/account/authorize",
apiPrefix + "/v1/account/tourist",
apiPrefix + "/v1/index",
apiPrefix + "/v1/grade",
apiPrefix + "/v1/book/list",
}...)))
g.Use(NeedGradeParam(AddSkipperURL([]string{
apiPrefix + "/v1/account/authorize",
apiPrefix + "/v1/account/tourist",
apiPrefix + "/v1/grade",
}...)))
v1 := g.Group("/v1")
// Account 接口管理
account := v1.Group("/account")
{
_api := new(api.Account)
account.POST("/authorize", _api.Authorize)
if config.IsDebug() {
account.GET("/tourist", _api.Tourist)
}
}
// Banner 接口管理
v1.GET("/index", new(api.Index).Index)
v1.GET("/grade", new(api.Index).Grade)
v1.GET("/banner", new(api.Banner).List)
v1.GET("/book/list", new(api.Book).List)
// User 接口管理
user := v1.Group("/user")
{
_api := new(api.User)
user.GET("/info", _api.Info)
}
// Book 接口管理
book := v1.Group("/book")
{
_api := new(api.Book)
book.GET("/home", _api.Home)
//book.GET("/list", _api.List)
book.GET("/detail", _api.Detail)
book.POST("/play", _api.Play)
}
// Pay 支付管理
pay := v1.Group("/pay")
{
_api := new(api.Pay)
pay.POST("/launch", _api.Launch)
pay.POST("/notice", _api.Notify)
}
}
func (this *Router) Init() *gin.Engine {
gin.SetMode(this.Mode)
app := gin.New()
if this.IsCors {
app.Use(Cors())
}
app.NoRoute(NoRouteHandler())
app.NoMethod(NoMethodHandler())
app.Use(LoggerHandle("log/gin.log", 3))
app.Use(TimeoutHandle(time.Second * 30))
app.Use(RecoveryHandler())
if this.RateLimitConfig != nil {
if this.RateLimitConfig.IsRate {
rate.NewIPRateLimiter()(this.RateLimitConfig.Limit, this.RateLimitConfig.Capacity)
app.Use(rate.RequestIPRateLimiter()(this.RateLimitConfig.Limit, this.RateLimitConfig.Capacity))
}
}
if config.IsDebug() {
gin.DefaultWriter = io.MultiWriter(os.Stdout)
app.StaticFS("/api-docs", http.Dir("./doc"))
}
app.StaticFS("/upload", http.Dir("./upload"))
app.MaxMultipartMemory = 4 << 20
this.handler = app
// 注册路由
this.registerAPI()
return app
}
func NewRouter(option *Option) *Router {
return &Router{Option: option}
}