浏览代码

feat:完成基本功能迁移

Cheng Jian 2 年之前
父节点
当前提交
7f4dd9f551
共有 15 个文件被更改,包括 1437 次插入22 次删除
  1. 5 4
      go.mod
  2. 62 0
      go.sum
  3. 591 0
      gtoken/gtoken.go
  4. 122 0
      gtoken/gtoken_cache.go
  5. 53 0
      gtoken/gtoken_conts.go
  6. 54 0
      gtoken/gtoken_group.go
  7. 14 0
      gtoken/gtoken_msg_test.go
  8. 89 0
      gtoken/gtoken_resp.go
  9. 227 0
      gtoken/gtoken_test.go
  10. 38 0
      micro/client.go
  11. 4 0
      micro/request.go
  12. 34 0
      micro/response.go
  13. 144 0
      micro/server.go
  14. 0 11
      net/request.go
  15. 0 7
      net/response.go

+ 5 - 4
go.mod

@@ -2,7 +2,8 @@ module dashoo.cn/micro_libary
 
 go 1.18
 
-require github.com/gogf/gf/v2 v2.4.4 // indirect
-
-
-
+require (
+	github.com/gogf/gf/v2 v2.4.4 // indirect
+	github.com/rpcxio/rpcx-consul v0.0.0-20220730062257-1ff0472e730f // indirect
+	github.com/smallnest/rpcx v1.8.9 // indirect
+)

+ 62 - 0
go.sum

@@ -0,0 +1,62 @@
+github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/gogf/gf/v2 v2.4.4/go.mod h1:tsbmtwcAl2chcYoq/fP9W2FZf06aw4i89X34nbSHo9Y=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
+github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
+github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rpcxio/rpcx-consul v0.0.0-20220730062257-1ff0472e730f h1:2lAWNSEM9KGGnHlkNjLGjsWPCxHOCQJmRRD1iCFraE4=
+github.com/rpcxio/rpcx-consul v0.0.0-20220730062257-1ff0472e730f/go.mod h1:N4SjBS0M9HpVdq3CIIXm/MwWv6euXQ39ybhfH2iTh1c=
+github.com/smallnest/rpcx v1.8.9 h1:lkbIO/wcs/5uQg/Q/RyxysIUIlMQOZjzUd+jPrz7D2g=
+github.com/smallnest/rpcx v1.8.9/go.mod h1:wiuDDa+0itp06KJiUMJHs9sCTS6efrU1hBLnWQUlI+w=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
+go.opentelemetry.io/otel/sdk v1.7.0/go.mod h1:uTEOTwaqIVuTGiJN7ii13Ibp75wJmYUDe374q6cZwUU=
+go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.8-0.20211105212822-18b340fc7af2/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

+ 591 - 0
gtoken/gtoken.go

@@ -0,0 +1,591 @@
+package gtoken
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"github.com/gogf/gf/v2/crypto/gaes"
+	"github.com/gogf/gf/v2/crypto/gmd5"
+	"github.com/gogf/gf/v2/encoding/gbase64"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/net/ghttp"
+	"github.com/gogf/gf/v2/os/gtime"
+	"github.com/gogf/gf/v2/text/gstr"
+	"github.com/gogf/gf/v2/util/gconv"
+	"github.com/gogf/gf/v2/util/grand"
+	"net/http"
+	"strings"
+)
+
+var GFToken *GfToken
+
+// GfToken gtoken结构体
+type GfToken struct {
+	// GoFrame server name
+	ServerName string
+	// 缓存模式 1 gcache 2 gredis 默认1
+	CacheMode int8
+	// 缓存key
+	CacheKey string
+	// 超时时间 默认10天(毫秒)
+	Timeout int
+	// 缓存刷新时间 默认为超时时间的一半(毫秒)
+	MaxRefresh int
+	// Token分隔符
+	TokenDelimiter string
+	// Token加密key
+	EncryptKey []byte
+	// 认证失败中文提示
+	AuthFailMsg string
+	// 是否支持多端登录,默认false
+	MultiLogin bool
+	// 是否是全局认证,兼容历史版本,已废弃
+	GlobalMiddleware bool
+	// 中间件类型 1 GroupMiddleware 2 BindMiddleware  3 GlobalMiddleware
+	MiddlewareType uint
+
+	// 登录路径
+	LoginPath string
+	// 登录验证方法 return userKey 用户标识 如果userKey为空,结束执行
+	LoginBeforeFunc func(r *ghttp.Request) (string, interface{})
+	// 登录返回方法
+	LoginAfterFunc func(r *ghttp.Request, respData Resp)
+	// 登出地址
+	LogoutPath string
+	// 登出验证方法 return true 继续执行,否则结束执行
+	LogoutBeforeFunc func(r *ghttp.Request) bool
+	// 登出返回方法
+	LogoutAfterFunc func(r *ghttp.Request, respData Resp)
+
+	// 拦截地址
+	AuthPaths g.SliceStr
+	// 拦截排除地址
+	AuthExcludePaths g.SliceStr
+	// 认证验证方法 return true 继续执行,否则结束执行
+	AuthBeforeFunc func(r *ghttp.Request) bool
+	// 认证返回方法
+	AuthAfterFunc func(r *ghttp.Request, respData Resp)
+}
+
+// Login 登录
+func (m *GfToken) Login(r *ghttp.Request) {
+	userKey, data := m.LoginBeforeFunc(r)
+	if userKey == "" {
+		g.Log().Error(r.Context(), msgLog(MsgErrUserKeyEmpty))
+		return
+	}
+
+	if m.MultiLogin {
+		// 支持多端重复登录,返回相同token
+		userCacheResp := m.getToken(r.Context(), userKey)
+		if userCacheResp.Success() {
+			respToken := m.EncryptToken(r.Context(), userKey, userCacheResp.GetString(KeyUuid))
+			m.LoginAfterFunc(r, respToken)
+			return
+		}
+	}
+
+	// 生成token
+	respToken := m.genToken(r.Context(), userKey, data)
+	m.LoginAfterFunc(r, respToken)
+
+}
+
+// Logout 登出
+func (m *GfToken) Logout(r *ghttp.Request) {
+	if !m.LogoutBeforeFunc(r) {
+		return
+	}
+
+	// 获取请求token
+	respData := m.getRequestToken(r)
+	if respData.Success() {
+		// 删除token
+		m.RemoveToken(r.Context(), respData.DataString())
+	}
+
+	m.LogoutAfterFunc(r, respData)
+}
+
+// AuthMiddleware 认证拦截
+func (m *GfToken) authMiddleware(r *ghttp.Request) {
+	urlPath := r.URL.Path
+	if !m.AuthPath(r.Context(), urlPath) {
+		// 如果不需要认证,继续
+		r.Middleware.Next()
+		return
+	}
+
+	// 不需要认证,直接下一步
+	if !m.AuthBeforeFunc(r) {
+		r.Middleware.Next()
+		return
+	}
+
+	// 获取请求token
+	tokenResp := m.getRequestToken(r)
+	if tokenResp.Success() {
+		// 验证token
+		tokenResp = m.validToken(r.Context(), tokenResp.DataString())
+	}
+
+	m.AuthAfterFunc(r, tokenResp)
+}
+
+// GetTokenData 通过token获取对象
+func (m *GfToken) GetTokenData(r *ghttp.Request) Resp {
+	respData := m.getRequestToken(r)
+	if respData.Success() {
+		// 验证token
+		respData = m.validToken(r.Context(), respData.DataString())
+	}
+
+	return respData
+}
+
+// AuthPath 判断路径是否需要进行认证拦截
+// return true 需要认证
+func (m *GfToken) AuthPath(ctx context.Context, urlPath string) bool {
+	// 去除后斜杠
+	if strings.HasSuffix(urlPath, "/") {
+		urlPath = gstr.SubStr(urlPath, 0, len(urlPath)-1)
+	}
+	// 分组拦截,登录接口不拦截
+	if m.MiddlewareType == MiddlewareTypeGroup {
+		if (m.LoginPath != "" && gstr.HasSuffix(urlPath, m.LoginPath)) ||
+			(m.LogoutPath != "" && gstr.HasSuffix(urlPath, m.LogoutPath)) {
+			return false
+		}
+	}
+
+	// 全局处理,认证路径拦截处理
+	if m.MiddlewareType == MiddlewareTypeGlobal {
+		var authFlag bool
+		for _, authPath := range m.AuthPaths {
+			tmpPath := authPath
+			if strings.HasSuffix(tmpPath, "/*") {
+				tmpPath = gstr.SubStr(tmpPath, 0, len(tmpPath)-2)
+			}
+			if gstr.HasPrefix(urlPath, tmpPath) {
+				authFlag = true
+				break
+			}
+		}
+
+		if !authFlag {
+			// 拦截路径不匹配
+			return false
+		}
+	}
+
+	// 排除路径处理,到这里nextFlag为true
+	for _, excludePath := range m.AuthExcludePaths {
+		tmpPath := excludePath
+		// 前缀匹配
+		if strings.HasSuffix(tmpPath, "/*") {
+			tmpPath = gstr.SubStr(tmpPath, 0, len(tmpPath)-2)
+			if gstr.HasPrefix(urlPath, tmpPath) {
+				// 前缀匹配不拦截
+				return false
+			}
+		} else {
+			// 全路径匹配
+			if strings.HasSuffix(tmpPath, "/") {
+				tmpPath = gstr.SubStr(tmpPath, 0, len(tmpPath)-1)
+			}
+			if urlPath == tmpPath {
+				// 全路径匹配不拦截
+				return false
+			}
+		}
+	}
+
+	return true
+}
+
+// getRequestToken 返回请求Token
+func (m *GfToken) getRequestToken(r *ghttp.Request) Resp {
+	authHeader := r.Header.Get("Authorization")
+	if authHeader != "" {
+		parts := strings.SplitN(authHeader, " ", 2)
+		if !(len(parts) == 2 && parts[0] == "Bearer") {
+			g.Log().Warning(r.Context(), msgLog(MsgErrAuthHeader, authHeader))
+			return Unauthorized(fmt.Sprintf(MsgErrAuthHeader, authHeader), "")
+		} else if parts[1] == "" {
+			g.Log().Warning(r.Context(), msgLog(MsgErrAuthHeader, authHeader))
+			return Unauthorized(fmt.Sprintf(MsgErrAuthHeader, authHeader), "")
+		}
+
+		return Succ(parts[1])
+	}
+
+	authHeader = r.Get(KeyToken).String()
+	if authHeader == "" {
+		return Unauthorized(MsgErrTokenEmpty, "")
+	}
+	return Succ(authHeader)
+
+}
+
+// genToken 生成Token
+func (m *GfToken) genToken(ctx context.Context, userKey string, data interface{}) Resp {
+	token := m.EncryptToken(ctx, userKey, "")
+	if !token.Success() {
+		return token
+	}
+
+	cacheKey := m.CacheKey + userKey
+	userCache := g.Map{
+		KeyUserKey:     userKey,
+		KeyUuid:        token.GetString(KeyUuid),
+		KeyData:        data,
+		KeyCreateTime:  gtime.Now().TimestampMilli(),
+		KeyRefreshTime: gtime.Now().TimestampMilli() + gconv.Int64(m.MaxRefresh),
+	}
+
+	cacheResp := m.setCache(ctx, cacheKey, userCache)
+	if !cacheResp.Success() {
+		return cacheResp
+	}
+
+	return token
+}
+
+// validToken 验证Token
+func (m *GfToken) validToken(ctx context.Context, token string) Resp {
+	if token == "" {
+		return Unauthorized(MsgErrTokenEmpty, "")
+	}
+
+	decryptToken := m.DecryptToken(ctx, token)
+	if !decryptToken.Success() {
+		return decryptToken
+	}
+
+	userKey := decryptToken.GetString(KeyUserKey)
+	uuid := decryptToken.GetString(KeyUuid)
+
+	userCacheResp := m.getToken(ctx, userKey)
+	if !userCacheResp.Success() {
+		return userCacheResp
+	}
+
+	if uuid != userCacheResp.GetString(KeyUuid) {
+		g.Log().Debug(ctx, msgLog(MsgErrAuthUuid)+", decryptToken:"+decryptToken.Json()+" cacheValue:"+gconv.String(userCacheResp.Data))
+		return Unauthorized(MsgErrAuthUuid, "")
+	}
+
+	return userCacheResp
+}
+
+// getToken 通过userKey获取Token
+func (m *GfToken) getToken(ctx context.Context, userKey string) Resp {
+	cacheKey := m.CacheKey + userKey
+
+	userCacheResp := m.getCache(ctx, cacheKey)
+	if !userCacheResp.Success() {
+		return userCacheResp
+	}
+	userCache := gconv.Map(userCacheResp.Data)
+
+	nowTime := gtime.Now().TimestampMilli()
+	refreshTime := userCache[KeyRefreshTime]
+
+	// 需要进行缓存超时时间刷新
+	if gconv.Int64(refreshTime) == 0 || nowTime > gconv.Int64(refreshTime) {
+		userCache[KeyCreateTime] = gtime.Now().TimestampMilli()
+		userCache[KeyRefreshTime] = gtime.Now().TimestampMilli() + gconv.Int64(m.MaxRefresh)
+		return m.setCache(ctx, cacheKey, userCache)
+	}
+
+	return Succ(userCache)
+}
+
+// RemoveToken 删除Token
+func (m *GfToken) RemoveToken(ctx context.Context, token string) Resp {
+	decryptToken := m.DecryptToken(ctx, token)
+	if !decryptToken.Success() {
+		return decryptToken
+	}
+
+	cacheKey := m.CacheKey + decryptToken.GetString(KeyUserKey)
+	return m.removeCache(ctx, cacheKey)
+}
+
+// EncryptToken token加密方法
+func (m *GfToken) EncryptToken(ctx context.Context, userKey string, uuid string) Resp {
+	if userKey == "" {
+		return Fail(MsgErrUserKeyEmpty)
+	}
+
+	if uuid == "" {
+		// 重新生成uuid
+		newUuid, err := gmd5.Encrypt(grand.Letters(10))
+		if err != nil {
+			g.Log().Error(ctx, msgLog(MsgErrAuthUuid), err)
+			return Error(MsgErrAuthUuid)
+		}
+		uuid = newUuid
+	}
+
+	tokenStr := userKey + m.TokenDelimiter + uuid
+
+	token, err := gaes.Encrypt([]byte(tokenStr), m.EncryptKey)
+	if err != nil {
+		g.Log().Error(ctx, msgLog(MsgErrTokenEncrypt), tokenStr, err)
+		return Error(MsgErrTokenEncrypt)
+	}
+
+	return Succ(g.Map{
+		KeyUserKey: userKey,
+		KeyUuid:    uuid,
+		KeyToken:   gbase64.EncodeToString(token),
+	})
+}
+
+// DecryptToken token解密方法
+func (m *GfToken) DecryptToken(ctx context.Context, token string) Resp {
+	if token == "" {
+		return Fail(MsgErrTokenEmpty)
+	}
+
+	token64, err := gbase64.Decode([]byte(token))
+	if err != nil {
+		g.Log().Error(ctx, msgLog(MsgErrTokenDecode), token, err)
+		return Error(MsgErrTokenDecode)
+	}
+	decryptToken, err2 := gaes.Decrypt(token64, m.EncryptKey)
+	if err2 != nil {
+		g.Log().Error(ctx, msgLog(MsgErrTokenEncrypt), token, err2)
+		return Error(MsgErrTokenEncrypt)
+	}
+	tokenArray := gstr.Split(string(decryptToken), m.TokenDelimiter)
+	if len(tokenArray) < 2 {
+		g.Log().Error(ctx, msgLog(MsgErrTokenLen), token)
+		return Error(MsgErrTokenLen)
+	}
+
+	return Succ(g.Map{
+		KeyUserKey: tokenArray[0],
+		KeyUuid:    tokenArray[1],
+	})
+}
+
+// InitConfig 初始化配置信息
+func (m *GfToken) InitConfig() bool {
+	if m.CacheMode == 0 {
+		m.CacheMode = CacheModeCache
+	}
+
+	if m.CacheKey == "" {
+		m.CacheKey = DefaultCacheKey
+	}
+
+	if m.Timeout == 0 {
+		m.Timeout = DefaultTimeout
+	}
+
+	if m.MaxRefresh == 0 {
+		m.MaxRefresh = m.Timeout / 2
+	}
+
+	if m.TokenDelimiter == "" {
+		m.TokenDelimiter = DefaultTokenDelimiter
+	}
+
+	if len(m.EncryptKey) == 0 {
+		m.EncryptKey = []byte(DefaultEncryptKey)
+	}
+
+	if m.AuthFailMsg == "" {
+		m.AuthFailMsg = DefaultAuthFailMsg
+	}
+
+	// 设置中间件模式,未设置说明历史版本,通过GlobalMiddleware兼容
+	if m.MiddlewareType == 0 {
+		if m.GlobalMiddleware {
+			m.MiddlewareType = MiddlewareTypeGlobal
+		} else {
+			m.MiddlewareType = MiddlewareTypeBind
+		}
+	}
+
+	if m.LoginAfterFunc == nil {
+		m.LoginAfterFunc = func(r *ghttp.Request, respData Resp) {
+			if !respData.Success() {
+				r.Response.WriteJson(respData)
+			} else {
+				r.Response.WriteJson(Succ(g.Map{
+					KeyToken: respData.GetString(KeyToken),
+				}))
+			}
+		}
+	}
+
+	if m.LogoutBeforeFunc == nil {
+		m.LogoutBeforeFunc = func(r *ghttp.Request) bool {
+			return true
+		}
+	}
+
+	if m.LogoutAfterFunc == nil {
+		m.LogoutAfterFunc = func(r *ghttp.Request, respData Resp) {
+			if respData.Success() {
+				r.Response.WriteJson(Succ(MsgLogoutSucc))
+			} else {
+				r.Response.WriteJson(respData)
+			}
+		}
+	}
+
+	if m.AuthBeforeFunc == nil {
+		m.AuthBeforeFunc = func(r *ghttp.Request) bool {
+			// 静态页面不拦截
+			if r.IsFileRequest() {
+				return false
+			}
+
+			return true
+		}
+	}
+	if m.AuthAfterFunc == nil {
+		m.AuthAfterFunc = func(r *ghttp.Request, respData Resp) {
+			if respData.Success() {
+				r.Middleware.Next()
+			} else {
+				var params map[string]interface{}
+				if r.Method == http.MethodGet {
+					params = r.GetMap()
+				} else if r.Method == http.MethodPost {
+					params = r.GetMap()
+				} else {
+					r.Response.Writeln(MsgErrReqMethod)
+					return
+				}
+
+				no := gconv.String(gtime.TimestampMilli())
+
+				g.Log().Warning(r.Context(), fmt.Sprintf("[AUTH_%s][url:%s][params:%s][data:%s]",
+					no, r.URL.Path, params, respData.Json()))
+				respData.Msg = m.AuthFailMsg
+				r.Response.WriteJson(respData)
+				r.ExitAll()
+			}
+		}
+	}
+
+	return true
+}
+
+// Start 启动
+func (m *GfToken) Start() error {
+	if !m.InitConfig() {
+		return errors.New(MsgErrInitFail)
+	}
+
+	ctx := context.Background()
+	g.Log().Info(ctx, msgLog("[params:"+m.String()+"]start... "))
+
+	s := g.Server(m.ServerName)
+
+	// 缓存模式
+	if m.CacheMode > CacheModeFile {
+		g.Log().Error(ctx, msgLog(MsgErrNotSet, "CacheMode"))
+		return errors.New(fmt.Sprintf(MsgErrNotSet, "CacheMode"))
+	}
+
+	// 初始化文件缓存
+	if m.CacheMode == 3 {
+		m.initFileCache(ctx)
+	}
+
+	// 认证拦截器
+	if m.AuthPaths == nil {
+		g.Log().Error(ctx, msgLog(MsgErrNotSet, "AuthPaths"))
+		return errors.New(fmt.Sprintf(MsgErrNotSet, "AuthPaths"))
+	}
+
+	// 是否是全局拦截
+	if m.MiddlewareType == MiddlewareTypeGlobal {
+		s.BindMiddlewareDefault(m.authMiddleware)
+	} else {
+		for _, authPath := range m.AuthPaths {
+			tmpPath := authPath
+			if !strings.HasSuffix(authPath, "/*") {
+				tmpPath += "/*"
+			}
+			s.BindMiddleware(tmpPath, m.authMiddleware)
+		}
+	}
+
+	// 登录
+	if m.LoginPath == "" {
+		g.Log().Error(ctx, msgLog(MsgErrNotSet, "LoginPath"))
+		return errors.New(fmt.Sprintf(MsgErrNotSet, "LoginPath"))
+	}
+	if m.LoginBeforeFunc == nil {
+		g.Log().Error(ctx, msgLog(MsgErrNotSet, "LoginBeforeFunc"))
+		return errors.New(fmt.Sprintf(MsgErrNotSet, "LoginBeforeFunc"))
+	}
+	s.BindHandler(m.LoginPath, m.Login)
+
+	// 登出
+	if m.LogoutPath == "" {
+		g.Log().Error(ctx, msgLog(MsgErrNotSet, "LogoutPath"))
+		return errors.New(fmt.Sprintf(MsgErrNotSet, "LogoutPath"))
+	}
+	s.BindHandler(m.LogoutPath, m.Logout)
+
+	return nil
+}
+
+// Stop 结束
+func (m *GfToken) Stop(ctx context.Context) error {
+	g.Log().Info(ctx, "[GToken]stop. ")
+	return nil
+}
+
+// String token解密方法
+func (m *GfToken) String() string {
+	return gconv.String(g.Map{
+		// 缓存模式 1 gcache 2 gredis 默认1
+		"CacheMode":        m.CacheMode,
+		"CacheKey":         m.CacheKey,
+		"Timeout":          m.Timeout,
+		"TokenDelimiter":   m.TokenDelimiter,
+		"AuthFailMsg":      m.AuthFailMsg,
+		"MultiLogin":       m.MultiLogin,
+		"MiddlewareType":   m.MiddlewareType,
+		"LoginPath":        m.LoginPath,
+		"LogoutPath":       m.LogoutPath,
+		"AuthPaths":        gconv.String(m.AuthPaths),
+		"AuthExcludePaths": gconv.String(m.AuthExcludePaths),
+	})
+}
+
+// ValidToken 验证Token
+func (m *GfToken) ValidToken(ctx context.Context, token string) Resp {
+	if token == "" {
+		return Unauthorized(MsgErrTokenEmpty, "")
+	}
+
+	decryptToken := m.DecryptToken(ctx, token)
+	if !decryptToken.Success() {
+		return decryptToken
+	}
+
+	userKey := decryptToken.GetString(KeyUserKey)
+	uuid := decryptToken.GetString(KeyUuid)
+
+	userCacheResp := m.getToken(ctx, userKey)
+	if !userCacheResp.Success() {
+		return userCacheResp
+	}
+
+	if uuid != userCacheResp.GetString(KeyUuid) {
+		g.Log().Debug(ctx, msgLog(MsgErrAuthUuid)+", decryptToken:"+decryptToken.Json()+" cacheValue:"+gconv.String(userCacheResp.Data))
+		return Unauthorized(MsgErrAuthUuid, "")
+	}
+
+	return userCacheResp
+}

+ 122 - 0
gtoken/gtoken_cache.go

@@ -0,0 +1,122 @@
+package gtoken
+
+import (
+	"context"
+	"github.com/gogf/gf/v2/encoding/gjson"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/os/gcache"
+	"github.com/gogf/gf/v2/os/gfile"
+	"github.com/gogf/gf/v2/util/gconv"
+	"time"
+)
+
+// setCache 设置缓存
+func (m *GfToken) setCache(ctx context.Context, cacheKey string, userCache g.Map) Resp {
+	switch m.CacheMode {
+	case CacheModeCache, CacheModeFile:
+		gcache.Set(ctx, cacheKey, userCache, gconv.Duration(m.Timeout)*time.Millisecond)
+		if m.CacheMode == CacheModeFile {
+			m.writeFileCache(ctx)
+		}
+	case CacheModeRedis:
+		cacheValueJson, err1 := gjson.Encode(userCache)
+		if err1 != nil {
+			g.Log().Error(ctx, "[GToken]cache json encode error", err1)
+			return Error("cache json encode error")
+		}
+		_, err := g.Redis().Do(ctx, "SETEX", cacheKey, m.Timeout/1000, cacheValueJson)
+		if err != nil {
+			g.Log().Error(ctx, "[GToken]cache set error", err)
+			return Error("cache set error")
+		}
+	default:
+		return Error("cache model error")
+	}
+
+	return Succ(userCache)
+}
+
+// getCache 获取缓存
+func (m *GfToken) getCache(ctx context.Context, cacheKey string) Resp {
+	var userCache g.Map
+	switch m.CacheMode {
+	case CacheModeCache, CacheModeFile:
+		userCacheValue, err := gcache.Get(ctx, cacheKey)
+		if err != nil {
+			g.Log().Error(ctx, "[GToken]cache get error", err)
+			return Error("cache get error")
+		}
+		if userCacheValue.IsNil() {
+			return Unauthorized("login timeout or not login", "")
+		}
+		userCache = gconv.Map(userCacheValue)
+	case CacheModeRedis:
+		userCacheJson, err := g.Redis().Do(ctx, "GET", cacheKey)
+		if err != nil {
+			g.Log().Error(ctx, "[GToken]cache get error", err)
+			return Error("cache get error")
+		}
+		if userCacheJson.IsNil() {
+			return Unauthorized("login timeout or not login", "")
+		}
+
+		err = gjson.DecodeTo(userCacheJson, &userCache)
+		if err != nil {
+			g.Log().Error(ctx, "[GToken]cache get json error", err)
+			return Error("cache get json error")
+		}
+	default:
+		return Error("cache model error")
+	}
+
+	return Succ(userCache)
+}
+
+// removeCache 删除缓存
+func (m *GfToken) removeCache(ctx context.Context, cacheKey string) Resp {
+	switch m.CacheMode {
+	case CacheModeCache, CacheModeFile:
+		_, err := gcache.Remove(ctx, cacheKey)
+		if err != nil {
+			g.Log().Error(ctx, err)
+		}
+		if m.CacheMode == CacheModeFile {
+			m.writeFileCache(ctx)
+		}
+	case CacheModeRedis:
+		var err error
+		_, err = g.Redis().Do(ctx, "DEL", cacheKey)
+		if err != nil {
+			g.Log().Error(ctx, "[GToken]cache remove error", err)
+			return Error("cache remove error")
+		}
+	default:
+		return Error("cache model error")
+	}
+
+	return Succ("")
+}
+
+func (m *GfToken) writeFileCache(ctx context.Context) {
+	file := gfile.Temp(CacheModeFileDat)
+	data, e := gcache.Data(ctx)
+	if e != nil {
+		g.Log().Error(ctx, "[GToken]cache writeFileCache error", e)
+	}
+	gfile.PutContents(file, gjson.New(data).MustToJsonString())
+}
+
+func (m *GfToken) initFileCache(ctx context.Context) {
+	file := gfile.Temp(CacheModeFileDat)
+	if !gfile.Exists(file) {
+		return
+	}
+	data := gfile.GetContents(file)
+	maps := gconv.Map(data)
+	if maps == nil || len(maps) <= 0 {
+		return
+	}
+	for k, v := range maps {
+		gcache.Set(ctx, k, v, gconv.Duration(m.Timeout)*time.Millisecond)
+	}
+}

+ 53 - 0
gtoken/gtoken_conts.go

@@ -0,0 +1,53 @@
+package gtoken
+
+import (
+	"fmt"
+)
+
+const (
+	CacheModeCache   = 1
+	CacheModeRedis   = 2
+	CacheModeFile    = 3
+	CacheModeFileDat = "gtoken.dat"
+
+	MiddlewareTypeGroup  = 1
+	MiddlewareTypeBind   = 2
+	MiddlewareTypeGlobal = 3
+
+	DefaultTimeout        = 10 * 24 * 60 * 60 * 1000
+	DefaultCacheKey       = "GToken:"
+	DefaultTokenDelimiter = "_"
+	DefaultEncryptKey     = "12345678912345678912345678912345"
+	DefaultAuthFailMsg    = "请求错误或登录超时"
+
+	TraceId = "d5dfce77cdff812161134e55de3c5207"
+
+	KeyUserKey     = "userKey"
+	KeyRefreshTime = "refreshTime"
+	KeyCreateTime  = "createTime"
+	KeyUuid        = "uuid"
+	KeyData        = "data"
+	KeyToken       = "token"
+)
+
+const (
+	DefaultLogPrefix   = "[GToken]" // 日志前缀
+	MsgLogoutSucc      = "Logout success"
+	MsgErrInitFail     = "InitConfig fail"
+	MsgErrNotSet       = "%s not set, error"
+	MsgErrUserKeyEmpty = "userKey is empty"
+	MsgErrReqMethod    = "request method is error! "
+	MsgErrAuthHeader   = "Authorization : %s get token key fail"
+	MsgErrTokenEmpty   = "token is empty"
+	MsgErrTokenEncrypt = "token encrypt error"
+	MsgErrTokenDecode  = "token decode error"
+	MsgErrTokenLen     = "token len error"
+	MsgErrAuthUuid     = "user auth uuid error"
+)
+
+func msgLog(msg string, params ...interface{}) string {
+	if len(params) == 0 {
+		return DefaultLogPrefix + msg
+	}
+	return DefaultLogPrefix + fmt.Sprintf(msg, params...)
+}

+ 54 - 0
gtoken/gtoken_group.go

@@ -0,0 +1,54 @@
+package gtoken
+
+import (
+	"context"
+	"errors"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/net/ghttp"
+	"github.com/gogf/gf/v2/text/gstr"
+)
+
+// Middleware 绑定group
+func (m *GfToken) Middleware(ctx context.Context, group *ghttp.RouterGroup) error {
+	if !m.InitConfig() {
+		return errors.New("InitConfig fail")
+	}
+
+	// 设置为Group模式
+	m.MiddlewareType = MiddlewareTypeGroup
+	g.Log().Info(ctx, "[GToken][params:"+m.String()+"]start... ")
+
+	// 缓存模式
+	if m.CacheMode > CacheModeFile {
+		g.Log().Error(ctx, "[GToken]CacheMode set error")
+		return errors.New("CacheMode set error")
+	}
+	// 登录
+	if m.LoginPath == "" || m.LoginBeforeFunc == nil {
+		g.Log().Error(ctx, "[GToken]LoginPath or LoginBeforeFunc not set")
+		return errors.New("LoginPath or LoginBeforeFunc not set")
+	}
+	// 登出
+	if m.LogoutPath == "" {
+		g.Log().Error(ctx, "[GToken]LogoutPath not set")
+		return errors.New("LogoutPath not set")
+	}
+
+	group.Middleware(m.authMiddleware)
+
+	registerFunc(ctx, group, m.LoginPath, m.Login)
+	registerFunc(ctx, group, m.LogoutPath, m.Logout)
+
+	return nil
+}
+
+// 如果包含请求方式,按照请求方式注册;默认注册所有
+func registerFunc(ctx context.Context, group *ghttp.RouterGroup, pattern string, object interface{}) {
+	if gstr.Contains(pattern, ":") || gstr.Contains(pattern, "@") {
+		group.Map(map[string]interface{}{
+			pattern: object,
+		})
+	} else {
+		group.ALL(pattern, object)
+	}
+}

+ 14 - 0
gtoken/gtoken_msg_test.go

@@ -0,0 +1,14 @@
+package gtoken
+
+import (
+	"testing"
+)
+
+func TestMsg(t *testing.T) {
+	if msgLog("123") != "[GToken]123" {
+		t.Error("msg err")
+	}
+	if msgLog("123-%s-%d", "123", 44) != "[GToken]123-123-44" {
+		t.Error("msg sprintf err")
+	}
+}

+ 89 - 0
gtoken/gtoken_resp.go

@@ -0,0 +1,89 @@
+package gtoken
+
+import (
+	"encoding/json"
+	"github.com/gogf/gf/v2/container/gvar"
+	"github.com/gogf/gf/v2/util/gconv"
+)
+
+const (
+	SUCCESS      = 0
+	FAIL         = -1
+	ERROR        = -99
+	UNAUTHORIZED = -401
+)
+
+type Resp struct {
+	Code int         `json:"code"`
+	Msg  string      `json:"msg"`
+	Data interface{} `json:"data"`
+}
+
+// Success 获取Data值转字符串
+func (resp Resp) Success() bool {
+	return resp.Code == SUCCESS
+}
+
+// DataString 获取Data转字符串
+func (resp Resp) DataString() string {
+	return gconv.String(resp.Data)
+}
+
+// DataInt 获取Data转Int
+func (resp Resp) DataInt() int {
+	return gconv.Int(resp.Data)
+}
+
+// GetString 获取Data值转字符串
+func (resp Resp) GetString(key string) string {
+	return gconv.String(resp.Get(key))
+}
+
+// GetInt 获取Data值转Int
+func (resp Resp) GetInt(key string) int {
+	return gconv.Int(resp.Get(key))
+}
+
+// Get 获取Data值
+func (resp Resp) Get(key string) *gvar.Var {
+	m := gconv.Map(resp.Data)
+	if m == nil {
+		return nil
+	}
+	return gvar.New(m[key])
+}
+
+func (resp Resp) Json() string {
+	str, _ := json.Marshal(resp)
+	return string(str)
+}
+
+// Succ 成功
+func Succ(data interface{}) Resp {
+	return Resp{SUCCESS, "success", data}
+}
+
+// Fail 失败
+func Fail(msg string) Resp {
+	return Resp{FAIL, msg, ""}
+}
+
+// FailData 失败设置Data
+func FailData(msg string, data interface{}) Resp {
+	return Resp{FAIL, msg, data}
+}
+
+// Error 错误
+func Error(msg string) Resp {
+	return Resp{ERROR, msg, ""}
+}
+
+// ErrorData 错误设置Data
+func ErrorData(msg string, data interface{}) Resp {
+	return Resp{ERROR, msg, data}
+}
+
+// Unauthorized 认证失败
+func Unauthorized(msg string, data interface{}) Resp {
+	return Resp{UNAUTHORIZED, msg, data}
+}

+ 227 - 0
gtoken/gtoken_test.go

@@ -0,0 +1,227 @@
+package gtoken_test
+
+import (
+	"context"
+	"dashoo.cn/micro_libary/gtoken"
+	"github.com/gogf/gf/v2/frame/g"
+	"testing"
+)
+
+func TestAuthPathGlobal(t *testing.T) {
+	ctx := context.Background()
+
+	t.Log("Global auth path test ")
+	// 启动gtoken
+	gfToken := &gtoken.GfToken{
+		//Timeout:         10 * 1000,
+		AuthPaths:        g.SliceStr{"/user", "/system"},             // 这里是按照前缀拦截,拦截/user /user/list /user/add ...
+		AuthExcludePaths: g.SliceStr{"/user/info", "/system/user/*"}, // 不拦截路径  /user/info,/system/user/info,/system/user,
+		MiddlewareType:   gtoken.MiddlewareTypeGlobal,                // 开启全局拦截
+	}
+
+	authPath(gfToken, t)
+	flag := gfToken.AuthPath(ctx, "/test")
+	if flag {
+		t.Error("error:", "/test auth path error")
+	}
+
+}
+
+func TestBindAuthPath(t *testing.T) {
+	t.Log("Bind auth path test ")
+	// 启动gtoken
+	gfToken := &gtoken.GfToken{
+		//Timeout:         10 * 1000,
+		AuthPaths:        g.SliceStr{"/user", "/system"},             // 这里是按照前缀拦截,拦截/user /user/list /user/add ...
+		AuthExcludePaths: g.SliceStr{"/user/info", "/system/user/*"}, // 不拦截路径  /user/info,/system/user/info,/system/user,
+		MiddlewareType:   gtoken.MiddlewareTypeBind,                  // 开启局部拦截
+	}
+
+	authPath(gfToken, t)
+}
+
+func TestGroupAuthPath(t *testing.T) {
+	ctx := context.Background()
+
+	t.Log("Group auth path test ")
+	// 启动gtoken
+	gfToken := &gtoken.GfToken{
+		//Timeout:         10 * 1000,
+		AuthExcludePaths: g.SliceStr{"/user/info", "/system/user/*"}, // 不拦截路径  /user/info,/system/user/info,/system/user,
+		LoginPath:        "/login",                                   // 登录路径
+		MiddlewareType:   gtoken.MiddlewareTypeGroup,                 // 开启组拦截
+	}
+
+	flag := gfToken.AuthPath(ctx, "/login")
+	if flag {
+		t.Error("error:", "/login auth path error")
+	}
+
+	flag = gfToken.AuthPath(ctx, "/user/info")
+	if flag {
+		t.Error("error:", "/user/info auth path error")
+	}
+
+	flag = gfToken.AuthPath(ctx, "/system/user/info")
+	if flag {
+		t.Error("error:", "/system/user/info auth path error")
+	}
+
+	flag = gfToken.AuthPath(ctx, "/system/test")
+	if !flag {
+		t.Error("error:", "/system/test auth path error")
+	}
+}
+
+func TestAuthPathNoExclude(t *testing.T) {
+	ctx := context.Background()
+
+	t.Log("auth no exclude path test ")
+	// 启动gtoken
+	gfToken := &gtoken.GfToken{
+		//Timeout:         10 * 1000,
+		AuthPaths:      g.SliceStr{"/user", "/system"}, // 这里是按照前缀拦截,拦截/user /user/list /user/add ...
+		MiddlewareType: gtoken.MiddlewareTypeGlobal,    // 关闭全局拦截
+	}
+
+	authFlag := gfToken.AuthPath
+	if authFlag(ctx, "/test") {
+		t.Error(ctx, "error:", "/test auth path error")
+	}
+	if !authFlag(ctx, "/system/dept") {
+		t.Error(ctx, "error:", "/system/dept auth path error")
+	}
+
+	if !authFlag(ctx, "/user/info") {
+		t.Error(ctx, "error:", "/user/info auth path error")
+	}
+
+	if !authFlag(ctx, "/system/user") {
+		t.Error(ctx, "error:", "/system/user auth path error")
+	}
+}
+
+func TestAuthPathExclude(t *testing.T) {
+	ctx := context.Background()
+
+	t.Log("auth path test ")
+	// 启动gtoken
+	gfToken := &gtoken.GfToken{
+		//Timeout:         10 * 1000,
+		AuthPaths:        g.SliceStr{"/*"},                           // 这里是按照前缀拦截,拦截/user /user/list /user/add ...
+		AuthExcludePaths: g.SliceStr{"/user/info", "/system/user/*"}, // 不拦截路径  /user/info,/system/user/info,/system/user,
+		MiddlewareType:   gtoken.MiddlewareTypeGlobal,                // 开启全局拦截
+	}
+
+	authFlag := gfToken.AuthPath
+	if !authFlag(ctx, "/test") {
+		t.Error("error:", "/test auth path error")
+	}
+	if !authFlag(ctx, "//system/dept") {
+		t.Error("error:", "/system/dept auth path error")
+	}
+
+	if authFlag(ctx, "/user/info") {
+		t.Error("error:", "/user/info auth path error")
+	}
+
+	if authFlag(ctx, "/system/user") {
+		t.Error("error:", "/system/user auth path error")
+	}
+
+	if authFlag(ctx, "/system/user/info") {
+		t.Error("error:", "/system/user/info auth path error")
+	}
+
+}
+
+func authPath(gfToken *gtoken.GfToken, t *testing.T) {
+	ctx := context.Background()
+
+	flag := gfToken.AuthPath(ctx, "/user/info")
+	if flag {
+		t.Error("error:", "/user/info auth path error")
+	}
+
+	flag = gfToken.AuthPath(ctx, "/system/user")
+	if flag {
+		t.Error("error:", "/system/user auth path error")
+	}
+
+	flag = gfToken.AuthPath(ctx, "/system/user/info")
+	if flag {
+		t.Error("error:", "/system/user/info auth path error")
+	}
+
+	flag = gfToken.AuthPath(ctx, "/system/dept")
+	if !flag {
+		t.Error("error:", "/system/dept auth path error")
+	}
+
+	flag = gfToken.AuthPath(ctx, "/user/list")
+	if !flag {
+		t.Error("error:", "/user/list auth path error")
+	}
+
+	flag = gfToken.AuthPath(ctx, "/user/add")
+	if !flag {
+		t.Error("error:", "/user/add auth path error")
+	}
+}
+
+func TestEncryptDecryptToken(t *testing.T) {
+	t.Log("encrypt and decrypt token test ")
+	ctx := context.Background()
+
+	gfToken := gtoken.GfToken{}
+	gfToken.InitConfig()
+
+	userKey := "123123"
+	token := gfToken.EncryptToken(ctx, userKey, "")
+	if !token.Success() {
+		t.Error(token.Json())
+	}
+	t.Log(token.DataString())
+
+	token2 := gfToken.DecryptToken(ctx, token.GetString("token"))
+	if !token2.Success() {
+		t.Error(token2.Json())
+	}
+	t.Log(token2.DataString())
+	if userKey != token2.GetString("userKey") {
+		t.Error("error:", "token decrypt userKey error")
+	}
+	if token.GetString("uuid") != token2.GetString("uuid") {
+		t.Error("error:", "token decrypt uuid error")
+	}
+
+}
+
+func BenchmarkEncryptDecryptToken(b *testing.B) {
+	b.Log("encrypt and decrypt token test ")
+
+	ctx := context.Background()
+	gfToken := gtoken.GfToken{}
+	gfToken.InitConfig()
+
+	userKey := "123123"
+	token := gfToken.EncryptToken(ctx, userKey, "")
+	if !token.Success() {
+		b.Error(token.Json())
+	}
+	b.Log(token.DataString())
+
+	for i := 0; i < b.N; i++ {
+		token2 := gfToken.DecryptToken(ctx, token.GetString("token"))
+		if !token2.Success() {
+			b.Error(token2.Json())
+		}
+		b.Log(token2.DataString())
+		if userKey != token2.GetString("userKey") {
+			b.Error("error:", "token decrypt userKey error")
+		}
+		if token.GetString("uuid") != token2.GetString("uuid") {
+			b.Error("error:", "token decrypt uuid error")
+		}
+	}
+}

+ 38 - 0
micro/client.go

@@ -0,0 +1,38 @@
+package micro
+
+import (
+	"context"
+	"github.com/gogf/gf/v2/container/gvar"
+	"github.com/gogf/gf/v2/frame/g"
+	consul "github.com/rpcxio/rpcx-consul/client"
+	"github.com/smallnest/rpcx/client"
+	"strings"
+)
+
+// InitMicroSrvClient 获取微服务客户端,arg为可选参数,若有必须是两个,分别是:reg string, serverAddr string
+func InitMicroSrvClient(ctx context.Context, serviceName, key string, args ...string) (c client.XClient) {
+	reg, _ := g.Config().Get(ctx, "service_registry.registry")
+	etcdAddr, _ := g.Config().Get(ctx, "service_registry.server-addr")
+	if len(args) == 2 {
+		reg = gvar.New(args[0])
+		etcdAddr = gvar.New(args[1])
+	}
+
+	config, _ := g.Config().Get(ctx, key)
+	arr := strings.Split(config.String(), ",")
+	srvName := arr[0]
+	if len(arr) == 2 { // 点对点 直连
+		d, _ := client.NewPeer2PeerDiscovery("tcp@"+arr[1], "")
+		c = client.NewXClient(serviceName, client.Failtry, client.RandomSelect, d, client.DefaultOption)
+		return c
+	} else {
+		if reg.String() == "consul" { // 服务发现使用consul
+			//d, _ := etcd_client.NewEtcdV3Discovery(srvName, serviceName, []string{etcdAddr}, nil)
+			d, _ := consul.NewConsulDiscovery(srvName, serviceName, []string{etcdAddr.String()}, nil)
+			//d, _ := client.NewConsulDiscovery(srvName, serviceName, []string{etcdAddr}, nil)
+			c = client.NewXClient(serviceName, client.Failover, client.RoundRobin, d, client.DefaultOption)
+			return c
+		}
+	}
+	return nil
+}

+ 4 - 0
micro/request.go

@@ -0,0 +1,4 @@
+package micro
+
+type Request struct {
+}

+ 34 - 0
micro/response.go

@@ -0,0 +1,34 @@
+package micro
+
+const (
+	SUCCESS      = 200
+	FAIL         = 500
+	ERROR        = 400
+	UNAUTHORIZED = 401
+)
+
+type Response struct {
+	Code    int64  `json:"code"`
+	Message string `json:"message"`
+	Data    any    `json:"data"`
+}
+
+// Success 成功
+func Success(data interface{}) Response {
+	return Response{SUCCESS, "success", data}
+}
+
+// Fail 失败
+func Fail(msg string) Response {
+	return Response{FAIL, msg, ""}
+}
+
+// Error 错误
+func Error(msg string) Response {
+	return Response{ERROR, msg, ""}
+}
+
+// Unauthorized 认证失败
+func Unauthorized(msg string, data interface{}) Response {
+	return Response{UNAUTHORIZED, msg, data}
+}

+ 144 - 0
micro/server.go

@@ -0,0 +1,144 @@
+package micro
+
+import (
+	"context"
+	"dashoo.cn/micro_libary/gtoken"
+	"github.com/gogf/gf/v2/errors/gerror"
+	"github.com/gogf/gf/v2/frame/g"
+	"github.com/gogf/gf/v2/os/gctx"
+	"github.com/gogf/gf/v2/text/gstr"
+	"github.com/rcrowley/go-metrics"
+	"github.com/rpcxio/rpcx-consul/serverplugin"
+	"github.com/smallnest/rpcx/protocol"
+	"github.com/smallnest/rpcx/server"
+	"github.com/smallnest/rpcx/share"
+	"strings"
+	"time"
+)
+
+const Tenant = "Tenant"
+
+func CreateAndInitService(basePath string) *server.Server {
+	var ctx = gctx.New()
+	bindAddr, _ := g.Config().Get(ctx, "setting.bind-addr")
+	registryType, _ := g.Config().Get(ctx, "setting.registry-type")
+	registryAddr, _ := g.Config().Get(ctx, "setting.registry-addr")
+
+	s := server.NewServer()
+
+	g.Log().Infof(context.Background(), "服务启动, BindName: %v, BindAddr: %s", basePath, bindAddr.String())
+
+	if registryType.String() == "consul" {
+		addConsulRegistryPlugin(ctx, s, basePath, bindAddr.String(), registryAddr.String())
+	}
+
+	return s
+}
+
+func addConsulRegistryPlugin(ctx context.Context, s *server.Server, basePath, srvAddr, consulAddr string) {
+	r := &serverplugin.ConsulRegisterPlugin{
+		ServiceAddress: "tcp@" + srvAddr,
+		ConsulServers:  []string{consulAddr},
+		BasePath:       basePath,
+		Metrics:        metrics.NewRegistry(),
+		UpdateInterval: time.Minute,
+	}
+	err := r.Start()
+	if err != nil {
+		g.Log().Fatal(ctx, err)
+	}
+	g.Log().Infof(ctx, "注册到Consul: %v, basePath: %v, MicorSrv: %v", consulAddr, basePath, srvAddr)
+	s.Plugins.Add(r)
+}
+
+// HandleAuth 处理身份验证
+func HandleAuth(ctx context.Context, req *protocol.Message, token string, authExcludePaths []string) error {
+	tenant := getTenant(req)
+	g.Log().Infof(ctx, " ServicePath: %s, ServiceMethod: %s,Tenant:%s", req.ServicePath, req.ServiceMethod, tenant)
+
+	reqPath := "/" + req.ServicePath + "/" + req.ServiceMethod
+	if authPath(reqPath, authExcludePaths) {
+		req.Metadata["authExclude"] = "false"
+		var rsp gtoken.Resp
+		notAuthSrv := ctx.Value("NotAuthSrv")
+		if notAuthSrv != nil && notAuthSrv.(bool) {
+			rsp = gtoken.GFToken.ValidToken(ctx, token)
+		} else {
+			ctx = context.WithValue(ctx, share.ReqMetaDataKey, map[string]string{"tenant": tenant})
+			rsp = validToken(ctx, token)
+		}
+
+		if rsp.Code != 0 && rsp.Code != 200 {
+			return gerror.New("Token 认证失败!")
+		}
+		if req.Metadata != nil {
+			req.Metadata["userInfo"] = rsp.DataString()
+		}
+		return nil
+	}
+	return nil
+}
+
+// 判断路径是否需要进行认证拦截
+// return true 需要认证
+func authPath(urlPath string, authExcludePaths []string) bool {
+	// 去除后斜杠
+	if strings.HasSuffix(urlPath, "/") {
+		urlPath = gstr.SubStr(urlPath, 0, len(urlPath)-1)
+	}
+
+	// 排除路径处理,到这里nextFlag为true
+	for _, excludePath := range authExcludePaths {
+		tmpPath := excludePath
+		// 前缀匹配
+		if strings.HasSuffix(tmpPath, "/*") {
+			tmpPath = gstr.SubStr(tmpPath, 0, len(tmpPath)-2)
+			if gstr.HasPrefix(urlPath, tmpPath) {
+				// 前缀匹配不拦截
+				return false
+			}
+		} else {
+			// 全路径匹配
+			if strings.HasSuffix(tmpPath, "/") {
+				tmpPath = gstr.SubStr(tmpPath, 0, len(tmpPath)-1)
+			}
+			if urlPath == tmpPath {
+				// 全路径匹配不拦截
+				return false
+			}
+		}
+	}
+	return true
+}
+
+// 验证token
+func validToken(ctx context.Context, token string) gtoken.Resp {
+	grsp := gtoken.Resp{}
+	if token == "" {
+		grsp.Code = 401
+		grsp.Msg = "valid token empty"
+		return grsp
+	}
+
+	authService := InitMicroSrvClient(ctx, "Auth", "micro_srv.auth")
+	defer authService.Close()
+	rsp := &gtoken.Resp{}
+	err := authService.Call(ctx, "ValidToken", token, rsp)
+	if err != nil {
+		g.Log().Error(ctx, err)
+		grsp.Code = 401
+		return grsp
+	}
+	grsp.Code = int(rsp.Code)
+	grsp.Msg = rsp.Msg
+	grsp.Data = rsp.Data
+	return grsp
+}
+
+func getTenant(msg *protocol.Message) string {
+	var tenant string
+	if msg.Metadata != nil {
+		tenant = msg.Metadata[Tenant]
+	}
+	return tenant
+}

+ 0 - 11
net/request.go

@@ -1,11 +0,0 @@
-package net
-
-import "github.com/gogf/gf/v2/net/ghttp"
-
-type Request struct {
-}
-
-// GetTenant 获取租户码
-func GetTenant(r *ghttp.Request) string {
-	return r.Header.Get("Tenant")
-}

+ 0 - 7
net/response.go

@@ -1,7 +0,0 @@
-package net
-
-type Response struct {
-	Code    int64  `json:"code"`
-	Message string `json:"message"`
-	Data    any    `json:"data"`
-}