cauto 2 years ago
parent
commit
7a7891bd55

+ 18 - 0
api/v1/login.go

@@ -0,0 +1,18 @@
+package v1
+
+import "github.com/gogf/gf/v2/frame/g"
+
+type LoginReq struct {
+	g.Meta   `path:"/login" tags:"login" method:"post" summary:"登录"`
+	UserName string `p:"username"`
+	PassWord string `p:"password"`
+}
+type LoginRes struct {
+	Token string
+}
+
+type LoginOutReq struct {
+	g.Meta `path:"/loginout" tags:"loginout" method:"post" summary:"退出"`
+}
+type LoginOutRes struct {
+}

+ 1 - 0
go.mod

@@ -12,6 +12,7 @@ require (
 	github.com/mattn/go-runewidth v0.0.14 // indirect
 	github.com/miekg/dns v1.1.50
 	github.com/rivo/uniseg v0.4.2 // indirect
+	github.com/tiger1103/gfast-token v1.0.1
 	go.opentelemetry.io/otel/sdk v1.11.1 // indirect
 	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
 	golang.org/x/net v0.1.0

+ 4 - 0
go.sum

@@ -39,6 +39,8 @@ github.com/gogf/gf/contrib/drivers/mysql/v2 v2.2.1/go.mod h1:z+/0qiOwMroAnj5ESuo
 github.com/gogf/gf/v2 v2.0.0/go.mod h1:apktt6TleWtCIwpz63vBqUnw8MX8gWKoZyxgDpXFtgM=
 github.com/gogf/gf/v2 v2.2.1 h1:SmwGoRbZEBsiRI48tOf+bH0MLidznFPKpHyynHyBW4Q=
 github.com/gogf/gf/v2 v2.2.1/go.mod h1:thvkyb43RWUu/m05sRm4CbH9r7t7/FrW2M56L9Ystwk=
+github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
+github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -109,6 +111,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/tiger1103/gfast-token v1.0.1 h1:507HTgEmq+dZ+TnFndy+hC+Ezj/jTQR74qhM85Szw/E=
+github.com/tiger1103/gfast-token v1.0.1/go.mod h1:TE1qxSWuEbFeBme5hM0x0EIoBNoxGi3J4yUAyKotRSA=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=

+ 55 - 0
internal/base/base_context.go

@@ -0,0 +1,55 @@
+package base
+
+import (
+	"github.com/gogf/gf/v2/net/ghttp"
+	"golang.org/x/net/context"
+	"nodeMonitor/internal/consts"
+	"nodeMonitor/internal/model"
+)
+
+type IContext interface {
+	Init(r *ghttp.Request, customCtx *model.Context)
+	Get(ctx context.Context) *model.Context
+	SetUser(ctx context.Context, ctxUser *model.ContextUser)
+	GetLoginUser(ctx context.Context) *model.ContextUser
+}
+
+// Context 上下文管理服务
+var contextService = contextImpl{}
+
+type contextImpl struct{}
+
+func Context() IContext {
+	return IContext(&contextService)
+}
+
+// Init 初始化上下文对象指针到上下文对象中,以便后续的请求流程中可以修改。
+func (s *contextImpl) Init(r *ghttp.Request, customCtx *model.Context) {
+	r.SetCtxVar(consts.CtxKey, customCtx)
+}
+
+// Get 获得上下文变量,如果没有设置,那么返回nil
+func (s *contextImpl) Get(ctx context.Context) *model.Context {
+	value := ctx.Value(consts.CtxKey)
+	if value == nil {
+		return nil
+	}
+	if localCtx, ok := value.(*model.Context); ok {
+		return localCtx
+	}
+	return nil
+}
+
+// SetUser 将上下文信息设置到上下文请求中,注意是完整覆盖
+func (s *contextImpl) SetUser(ctx context.Context, ctxUser *model.ContextUser) {
+	s.Get(ctx).User = ctxUser
+}
+
+// GetLoginUser 获取当前登陆用户信息
+func (s *contextImpl) GetLoginUser(ctx context.Context) *model.ContextUser {
+	context := s.Get(ctx)
+	if context == nil {
+		return nil
+	}
+	return context.User
+}

+ 74 - 0
internal/base/base_token.go

@@ -0,0 +1,74 @@
+package base
+
+import (
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/net/ghttp"
+	"github.com/gogf/gf/v2/os/gctx"
+	"github.com/tiger1103/gfast-token/gftoken"
+	"golang.org/x/net/context"
+	"nodeMonitor/internal/consts"
+	"nodeMonitor/internal/model"
+	"nodeMonitor/library/liberr"
+	"sync"
+)
+
+type IGfToken interface {
+	GenerateToken(ctx context.Context, key string, data interface{}) (keys string, err error)
+	RemoveToken(ctx context.Context, token string) (err error)
+	GetRequestToken(r *ghttp.Request) (token string)
+	Middleware(group *ghttp.RouterGroup) error
+	ParseToken(r *ghttp.Request) (*gftoken.CustomClaims, error)
+	IsLogin(r *ghttp.Request) (b bool, failed *gftoken.AuthFailed)
+}
+
+type gfTokenImpl struct {
+	*gftoken.GfToken
+}
+
+var gT = gfTokenImpl{
+	GfToken: gftoken.NewGfToken(),
+}
+
+func NewGfToken(options *model.TokenOptions) IGfToken {
+	var fun gftoken.OptionFunc
+	if options.CacheModel == consts.CacheModelRedis {
+		fun = gftoken.WithGRedis()
+	} else {
+		fun = gftoken.WithGCache()
+	}
+	gT.GfToken = gftoken.NewGfToken(
+		gftoken.WithCacheKey(options.CacheKey),
+		gftoken.WithTimeout(options.Timeout),
+		gftoken.WithMaxRefresh(options.MaxRefresh),
+		gftoken.WithMultiLogin(options.MultiLogin),
+		gftoken.WithExcludePaths(options.ExcludePaths),
+		fun,
+	)
+	return IGfToken(&gT)
+}
+
+type gft struct {
+	options *model.TokenOptions
+	gT      IGfToken
+	lock    *sync.Mutex
+}
+
+var gftService = &gft{
+	options: nil,
+	gT:      nil,
+	lock:    &sync.Mutex{},
+}
+
+func GfToken() IGfToken {
+	if gftService.gT == nil {
+		gftService.lock.Lock()
+		defer gftService.lock.Unlock()
+		if gftService.gT == nil {
+			ctx := gctx.New()
+			err := g.Cfg().MustGet(ctx, "gfToken").Struct(&gftService.options)
+			liberr.ErrIsNil(ctx, err)
+			gftService.gT = NewGfToken(gftService.options)
+		}
+	}
+	return gftService.gT
+}

+ 1 - 1
internal/cmd/cmd.go

@@ -80,7 +80,7 @@ func StartPingStart(ctx context.Context) error {
 		_, err = gcron.AddSingleton(ctx, s, func(ctx context.Context) {
 			go task.Ping(ctx)
 		}, "ping_status")
-	} else {
+	} else if nodePing.Int() == 0 {
 
 		taskStatusTime, err := g.Cfg().Get(ctx, "node.taskStatusTime")
 		if err != nil {

+ 5 - 0
internal/consts/consts_token.go

@@ -0,0 +1,5 @@
+package consts
+
+const (
+	CacheModelRedis = "redis"
+)

+ 6 - 0
internal/consts/context.go

@@ -0,0 +1,6 @@
+package consts
+
+var (
+	// CtxKey 上下文变量存储键名,前后端系统共享
+	CtxKey = "GFastContext"
+)

+ 61 - 0
internal/controller/login.go

@@ -0,0 +1,61 @@
+package controller
+
+import (
+	"github.com/gogf/gf/v2/crypto/gmd5"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/net/ghttp"
+	"github.com/gogf/gf/v2/os/glog"
+	"golang.org/x/net/context"
+	v1 "nodeMonitor/api/v1"
+	"nodeMonitor/internal/base"
+	"nodeMonitor/internal/model"
+	"nodeMonitor/library/libUtils"
+)
+
+var Login = sLogin{}
+
+type sLogin struct {
+}
+
+func (c *sLogin) Login(ctx context.Context, req *v1.LoginReq) (*v1.LoginRes, error) {
+	res := new(v1.LoginRes)
+	rootUsername, err := g.Cfg().Get(ctx, "node.rootUsername")
+	if err != nil {
+		glog.Debug(ctx, err.Error())
+		return res, err
+	}
+
+	rootPassword, err := g.Cfg().Get(ctx, "node.rootPassword")
+	if err != nil {
+		glog.Debug(ctx, err.Error())
+		return res, err
+	}
+
+	if req.UserName == rootUsername.String() && req.PassWord == rootPassword.String() {
+		ip := libUtils.GetClientIp(ctx)
+		userAgent := libUtils.GetUserAgent(ctx)
+		key := gmd5.MustEncryptString(rootUsername.String()) + gmd5.MustEncryptString(rootPassword.String())
+		if g.Cfg().MustGet(ctx, "gfToken.multiLogin").Bool() {
+			key = gmd5.MustEncryptString(rootUsername.String()) + gmd5.MustEncryptString(rootPassword.String()+ip+userAgent)
+		}
+		token, err := base.GfToken().GenerateToken(ctx, key, model.LoginUserRes{
+			UserUserName: rootUsername.String(),
+			UserPassword: rootPassword.String(),
+		})
+		if err != nil {
+			return res, err
+		}
+		res.Token = token
+	}
+
+	return res, nil
+}
+
+func (c *sLogin) LoginOut(ctx context.Context, req *v1.LoginOutReq) (*v1.LoginOutRes, error) {
+	res := new(v1.LoginOutRes)
+	err := base.GfToken().RemoveToken(ctx, base.GfToken().GetRequestToken(ghttp.RequestFromCtx(ctx)))
+	if err != nil {
+		return res, err
+	}
+	return res, nil
+}

+ 155 - 0
internal/middleware/auth.go

@@ -0,0 +1,155 @@
+package middleware
+
+import (
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/net/ghttp"
+	"github.com/gogf/gf/v2/os/glog"
+	"github.com/gogf/gf/v2/util/gconv"
+	"nodeMonitor/internal/base"
+	"nodeMonitor/internal/model"
+	"nodeMonitor/library/libResponse"
+)
+
+type IMiddleware interface {
+	CORS(r *ghttp.Request)
+	Ctx(r *ghttp.Request)
+	Auth(r *ghttp.Request)
+}
+type middlewareImpl struct{}
+
+var middlewareService = middlewareImpl{}
+
+func Middleware() IMiddleware {
+	return IMiddleware(&middlewareImpl{})
+}
+
+func (s *middlewareImpl) CORS(r *ghttp.Request) {
+	corsOptions := r.Response.DefaultCORSOptions()
+	// you can set options
+	//corsOptions.AllowDomain = []string{"goframe.org", "baidu.com"}
+	r.Response.CORS(corsOptions)
+	r.Middleware.Next()
+}
+
+// Ctx 自定义上下文对象
+func (s *middlewareImpl) Ctx(r *ghttp.Request) {
+	ctx := r.GetCtx()
+	// 初始化登录用户信息
+	data, err := base.GfToken().ParseToken(r)
+	if err != nil {
+		// 执行下一步请求逻辑
+		g.Log().Error(ctx, err)
+		libResponse.FailJson(true, r, err.Error())
+		r.Middleware.Next()
+	}
+	if data != nil {
+		context := new(model.Context)
+		err = gconv.Struct(data.Data, &context.User)
+		if err != nil {
+			g.Log().Error(ctx, err)
+			// 执行下一步请求逻辑
+			r.Middleware.Next()
+		}
+		base.Context().Init(r, context)
+	}
+	// 执行下一步请求逻辑
+	r.Middleware.Next()
+}
+
+// Auth 权限判断处理中间件
+func (s *middlewareImpl) Auth(r *ghttp.Request) {
+	ctx := r.GetCtx()
+
+	rootUsername, err := g.Cfg().Get(ctx, "node.rootUsername")
+	if err != nil {
+		glog.Debug(ctx, err.Error())
+		r.Middleware.Next()
+		return
+	}
+
+	//rootPassword, err := g.Cfg().Get(ctx, "node.rootPassword")
+	//if err != nil {
+	//	glog.Debug(ctx, err.Error())
+	//	r.Middleware.Next()
+	//	return
+	//}
+
+	//获取登陆用户id
+	user := base.Context().GetLoginUser(ctx)
+	if user.UserUserName != rootUsername.String() {
+		libResponse.FailJson(true, r, "没有访问权限")
+	}
+	//accessParams := r.Get("accessParams").Strings()
+	//accessParamsStr := ""
+	//if len(accessParams) > 0 && accessParams[0] != "undefined" {
+	//	accessParamsStr = "?" + gstr.Join(accessParams, "&")
+	//}
+	//url := gstr.TrimLeft(r.Request.URL.Path, "/") + accessParamsStr
+	/*if r.Method != "GET" && adminId != 1 && !gstr.Contains(url, "api/v1/system/login") {
+		libResponse.FailJson(true, r, "对不起!演示系统,不能删改数据!")
+	}*/
+	//获取无需验证权限的用户id
+	//tagSuperAdmin := false
+	//service.User().NotCheckAuthUserIds(ctx).Iterator(func(v interface{}) bool {
+	//	if gconv.Uint64(v) == adminId {
+	//		tagSuperAdmin = true
+	//		return false
+	//	}
+	//	return true
+	//})
+	//if tagSuperAdmin {
+	//	r.Middleware.Next()
+	//	//不要再往后面执行
+	//	return
+	//}
+	//获取地址对应的菜单id
+	//menuList, err := service.AuthRule().GetIsMenuList(ctx)
+	//if err != nil {
+	//	g.Log().Error(ctx, err)
+	//	libResponse.FailJson(true, r, "请求数据失败")
+	//}
+	//var menu *model.SysAuthRuleInfoRes
+	//for _, m := range menuList {
+	//	ms := gstr.SubStr(m.Name, 0, gstr.Pos(m.Name, "?"))
+	//	if m.Name == url || ms == url {
+	//		menu = m
+	//		break
+	//	}
+	//}
+	////只验证存在数据库中的规则
+	//if menu != nil {
+	//	//若存在不需要验证的条件则跳过
+	//	if gstr.Equal(menu.Condition, "nocheck") {
+	//		r.Middleware.Next()
+	//		return
+	//	}
+	//	menuId := menu.Id
+	//	//菜单没存数据库不验证权限
+	//	if menuId != 0 {
+	//		//判断权限操作
+	//		enforcer, err := service.CasbinEnforcer(ctx)
+	//		if err != nil {
+	//			g.Log().Error(ctx, err)
+	//			libResponse.FailJson(true, r, "获取权限失败")
+	//		}
+	//		groupPolicy := enforcer.GetFilteredGroupingPolicy(0,
+	//			gconv.String(adminId))
+	//		if len(groupPolicy) == 0 {
+	//			libResponse.FailJson(true, r, "没有访问权限")
+	//		}
+	//		hasAccess := false
+	//		for _, v := range groupPolicy {
+	//			if enforcer.HasPolicy(v[1], gconv.String(menuId), "All") {
+	//				hasAccess = true
+	//				break
+	//			}
+	//		}
+	//		if !hasAccess {
+	//			libResponse.FailJson(true, r, "没有访问权限")
+	//		}
+	//	}
+	//} else if menu == nil && accessParamsStr != "" {
+	//	libResponse.FailJson(true, r, "没有访问权限")
+	//}
+	r.Middleware.Next()
+}

+ 23 - 0
internal/model/base_auth.go

@@ -0,0 +1,23 @@
+package model
+
+import "github.com/gogf/gf/v2/frame/g"
+
+type TokenOptions struct {
+	//  server name
+	ServerName string `json:"serverName"`
+	// 缓存key (每创建一个实例CacheKey必须不相同)
+	CacheKey string `json:"cacheKey"`
+	// 超时时间 默认10天(秒)
+	Timeout int64 `json:"timeout"`
+	// 缓存刷新时间 默认5天(秒)
+	// 处理携带token的请求时当前时间大于超时时间并小于缓存刷新时间时token将自动刷新即重置token存活时间
+	// MaxRefresh值为0时,token将不会自动刷新
+	MaxRefresh int64 `json:"maxRefresh"`
+	// 是否允许多点登录
+	MultiLogin bool `json:"multiLogin"`
+	// Token加密key 32位
+	EncryptKey []byte `json:"encryptKey"`
+	// 拦截排除地址
+	ExcludePaths g.SliceStr `json:"excludePaths"`
+	CacheModel   string     `json:"cacheModel"`
+}

+ 9 - 0
internal/model/base_context.go

@@ -0,0 +1,9 @@
+package model
+
+type Context struct {
+	User *ContextUser // User in context.
+}
+
+type ContextUser struct {
+	*LoginUserRes
+}

+ 6 - 0
internal/model/login.go

@@ -0,0 +1,6 @@
+package model
+
+type LoginUserRes struct {
+	UserUserName string `orm:"user_username"    json:"userUserName"` // 用户昵称
+	UserPassword string `orm:"user_password"    json:"userPassword"` // 登录密码;cmf_password加密
+}

+ 18 - 2
internal/router/router.go

@@ -3,6 +3,7 @@ package router
 import (
 	"github.com/gogf/gf/v2/net/ghttp"
 	"nodeMonitor/internal/controller"
+	"nodeMonitor/internal/middleware"
 )
 
 func MiddlewareCORS(r *ghttp.Request) {
@@ -17,12 +18,13 @@ func MiddlewareCORS(r *ghttp.Request) {
 }
 func BindController(group *ghttp.RouterGroup) {
 	group.Group("/api/v1", func(group *ghttp.RouterGroup) {
+
 		group.Middleware(ghttp.MiddlewareHandlerResponse, MiddlewareCORS)
-		//group.Middleware(middleware.Middleware().CORS)
-		//group.Middleware(ghttp.MiddlewareCORS())
+
 		NodeRouter(group)
 		NodeConfigRouter(group)
 		PingConfigRouter(group)
+		LoginRouter(group)
 		DomeRouter(group)
 	})
 
@@ -30,6 +32,8 @@ func BindController(group *ghttp.RouterGroup) {
 
 func NodeRouter(group *ghttp.RouterGroup) {
 	group.Group("/node", func(group *ghttp.RouterGroup) {
+		group.Middleware(middleware.Middleware().Ctx)
+		group.Middleware(middleware.Middleware().Auth)
 		group.Bind(
 			controller.Node,
 		)
@@ -38,6 +42,7 @@ func NodeRouter(group *ghttp.RouterGroup) {
 
 func NodeConfigRouter(group *ghttp.RouterGroup) {
 	group.Group("/node/config", func(group *ghttp.RouterGroup) {
+
 		group.Bind(
 			controller.NodeConfig,
 		)
@@ -52,8 +57,19 @@ func PingConfigRouter(group *ghttp.RouterGroup) {
 	})
 }
 
+func LoginRouter(group *ghttp.RouterGroup) {
+	group.Group("/auth", func(group *ghttp.RouterGroup) {
+
+		group.Bind(
+			controller.Login,
+		)
+	})
+}
+
 func DomeRouter(group *ghttp.RouterGroup) {
 	group.Group("/hello", func(group *ghttp.RouterGroup) {
+		group.Middleware(middleware.Middleware().Ctx)
+		group.Middleware(middleware.Middleware().Auth)
 		group.Bind(
 			controller.Hello,
 		)

+ 22 - 18
manifest/config/config.yaml

@@ -43,26 +43,30 @@ node:
   startTime: 900 #用于查询15分钟内的数据
   taskName: "ping_task" #任务名称
   taskStatusName: "ping_status_task" #任务名称
-  nodePing: 0 #用于表示是不是检测PING的节点
+  nodePing: 2 #用于表示是不是检测PING的节点
   taskStatusTime: 60
-#gfToken:
-#  cacheKey: "gfToken_"
-#  timeOut: 10800
-#  maxRefresh: 5400
-#  multiLogin: true
-#  encryptKey: "49c54195e750b04e74a8429b17896586"
-#  cacheModel: "redis"
-#  excludePaths:
-#    - ""
+  rootUsername: "admin"
+  rootPassword: "qoqoiwooqp@#"
 
-## Redis 配置示例
-#redis:
-#  # 单实例配置
-#  default:
-#    address: 127.0.0.1:6379
-#    db: 1
-#    idleTimeout: 600
-#    maxActive: 100
+gfToken:
+  cacheKey: "gfToken_"
+  timeOut: 10800
+  maxRefresh: 5400
+  multiLogin: true
+  encryptKey: "49c54195e750b04e74a8429b17896586"
+  cacheModel: "redis"
+  excludePaths:
+    - "/api/v1/auth/login"
+    - "api/v1/auth/loginout"
+
+# Redis 配置示例
+redis:
+  # 单实例配置
+  default:
+    address: 127.0.0.1:6379
+    db: 1
+    idleTimeout: 600
+    maxActive: 100
 
 #system:
 #  notCheckAuthAdminIds: [1, 2, 31] #无需验证后台权限的用户id