feat:完善项目
This commit is contained in:
114
router/address.go
Normal file
114
router/address.go
Normal file
@ -0,0 +1,114 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"SciencesServer/app/api"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func register(app *gin.Engine) {
|
||||
apiPrefix := "/api"
|
||||
g := app.Group(apiPrefix)
|
||||
// 登录验证
|
||||
g.Use(NeedLogin(AddSkipperURL([]string{
|
||||
apiPrefix + "/captcha",
|
||||
apiPrefix + "/account/login",
|
||||
apiPrefix + "/account/logout",
|
||||
}...)))
|
||||
// 权限验证
|
||||
//g.Use(NeedPermission(AddSkipperURL([]string{
|
||||
// apiPrefix + "/captcha",
|
||||
// apiPrefix + "/account/login",
|
||||
// apiPrefix + "/account/logout",
|
||||
//}...)))
|
||||
// Captcha 验证码
|
||||
g.GET("/captcha", new(api.Captcha).Captcha)
|
||||
// Upload 上传管理
|
||||
g.POST("/upload", new(api.Upload).Upload)
|
||||
// Account 账户管理
|
||||
account := g.Group("/account")
|
||||
{
|
||||
_api := new(api.Account)
|
||||
account.POST("/login", _api.Login)
|
||||
account.POST("/logout", _api.Logout)
|
||||
}
|
||||
// User 用户管理
|
||||
user := g.Group("/user")
|
||||
{
|
||||
_api := new(api.User)
|
||||
user.GET("/info", _api.Info)
|
||||
user.GET("/menu", _api.Menu)
|
||||
user.POST("/list", _api.List)
|
||||
user.POST("/add", _api.Add)
|
||||
user.POST("/edit", _api.Edit)
|
||||
user.POST("/delete", _api.Delete)
|
||||
user.POST("/edit/password", _api.EditPassword)
|
||||
user.POST("/password/quick", _api.QuickPassword)
|
||||
user.POST("/role", _api.Role)
|
||||
user.POST("/role/bind", _api.RoleBind)
|
||||
}
|
||||
// Tenant 租户管理
|
||||
tenant := g.Group("/tenant")
|
||||
{
|
||||
_api := new(api.Tenant)
|
||||
tenant.POST("/list", _api.List)
|
||||
tenant.POST("/add", _api.Add)
|
||||
tenant.POST("/edit", _api.Edit)
|
||||
tenant.POST("/edit/password", _api.EditPassword)
|
||||
tenant.POST("/detail", _api.Detail)
|
||||
tenant.POST("/renewal", _api.Renewal)
|
||||
tenant.POST("/start_up", _api.StartUp)
|
||||
tenant.POST("/disable", _api.Disable)
|
||||
tenant.POST("/member/bind", _api.MemberBind)
|
||||
tenant.POST("/menu", _api.Menu)
|
||||
tenant.POST("/menu/bind", _api.MenuBind)
|
||||
tenant.POST("/auth/bind", _api.AuthBind)
|
||||
}
|
||||
// Menu 菜单管理
|
||||
menu := g.Group("/menu")
|
||||
{
|
||||
_api := new(api.Menu)
|
||||
menu.GET("/list", _api.List)
|
||||
menu.POST("/add", _api.Add)
|
||||
menu.POST("/edit", _api.Edit)
|
||||
menu.POST("/status", _api.Status)
|
||||
menu.POST("/delete", _api.Delete)
|
||||
}
|
||||
// Auth 权限管理
|
||||
auth := g.Group("/auth")
|
||||
{
|
||||
_api := new(api.Auth)
|
||||
auth.POST("/list", _api.List)
|
||||
}
|
||||
// Department 部门管理
|
||||
department := g.Group("/department")
|
||||
{
|
||||
_api := new(api.Department)
|
||||
department.GET("/list", _api.List)
|
||||
department.GET("/select", _api.Select)
|
||||
department.POST("/add", _api.Add)
|
||||
department.POST("/edit", _api.Edit)
|
||||
department.POST("/delete", _api.Delete)
|
||||
}
|
||||
// Role 角色管理
|
||||
role := g.Group("/role")
|
||||
{
|
||||
_api := new(api.Role)
|
||||
role.POST("/list", _api.List)
|
||||
role.POST("/select", _api.Select)
|
||||
role.POST("/add", _api.Add)
|
||||
role.POST("/edit", _api.Edit)
|
||||
role.POST("/status", _api.Status)
|
||||
role.POST("/delete", _api.Delete)
|
||||
role.POST("/menu", _api.Menu)
|
||||
role.POST("/menu/bind", _api.MenuBind)
|
||||
role.POST("/auth", _api.Auth)
|
||||
role.POST("/auth/bind", _api.AuthBind)
|
||||
}
|
||||
// Logs 日志管理
|
||||
log := g.Group("/log")
|
||||
{
|
||||
_api := new(api.Log)
|
||||
log.POST("/login", _api.Login)
|
||||
}
|
||||
}
|
111
router/auth.go
Normal file
111
router/auth.go
Normal file
@ -0,0 +1,111 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"SciencesServer/app/service"
|
||||
"SciencesServer/config"
|
||||
"SciencesServer/serve/cache"
|
||||
cache2 "SciencesServer/serve/cache"
|
||||
"SciencesServer/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()
|
||||
}
|
||||
}
|
||||
|
||||
// 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 !_session.IsAdmin {
|
||||
if _session.TenantID > 0 {
|
||||
if isExist, _ := cache.Cache.SIsMember(config.RedisKeyForTenant, _session.TenantKey); !isExist {
|
||||
c.JSON(http.StatusForbidden, gin.H{"message": "租户/公司信息协议已到期或已被禁用,无权限访问!"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
//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()
|
||||
}
|
||||
}
|
||||
}
|
13
router/handle.go
Normal file
13
router/handle.go
Normal file
@ -0,0 +1,13 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func NeedSysLogInput(c *gin.Context) {
|
||||
//session, _ := c.Get(config.TokenForSession)
|
||||
//_session := session.(*service.Session)
|
||||
//params, _ := c.Get("params")
|
||||
//service.Publish(config.EventForSystemLogs, _session.Community, _session.UID, _session.Name, mode, event, content,
|
||||
// params, c.ClientIP())
|
||||
}
|
98
router/middleware.go
Normal file
98
router/middleware.go
Normal file
@ -0,0 +1,98 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"SciencesServer/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("Recover:request【%s】 error:【%v】\n", c.Request.URL, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": "Internal Server Error!",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}()
|
||||
c.Next()
|
||||
}
|
||||
}
|
74
router/rate/ip.go
Normal file
74
router/rate/ip.go
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
67
router/router.go
Normal file
67
router/router.go
Normal file
@ -0,0 +1,67 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"SciencesServer/config"
|
||||
"SciencesServer/router/rate"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
*Option
|
||||
}
|
||||
|
||||
type (
|
||||
Option struct {
|
||||
Mode string
|
||||
IsCors bool
|
||||
*RateLimitConfig
|
||||
}
|
||||
// RouterLimitConfig 限流配置
|
||||
RateLimitConfig struct {
|
||||
IsRate bool `json:"is_rate"`
|
||||
Limit int `json:"limit"`
|
||||
Capacity int `json:"capacity"`
|
||||
}
|
||||
)
|
||||
|
||||
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"))
|
||||
// 注册路由
|
||||
register(app)
|
||||
|
||||
app.MaxMultipartMemory = 4 << 20
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func NewRouter(option *Option) *Router {
|
||||
return &Router{option}
|
||||
}
|
Reference in New Issue
Block a user