作者 yangfu

feat: user auth integrated

@@ -6,6 +6,7 @@ require ( @@ -6,6 +6,7 @@ require (
6 github.com/ajg/form v1.5.1 // indirect 6 github.com/ajg/form v1.5.1 // indirect
7 github.com/beego/beego/v2 v2.0.1 7 github.com/beego/beego/v2 v2.0.1
8 github.com/bwmarrin/snowflake v0.3.0 8 github.com/bwmarrin/snowflake v0.3.0
  9 + github.com/dgrijalva/jwt-go v3.2.0+incompatible
9 github.com/extrame/xls v0.0.1 10 github.com/extrame/xls v0.0.1
10 github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 // indirect 11 github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 // indirect
11 github.com/fatih/structs v1.1.0 // indirect 12 github.com/fatih/structs v1.1.0 // indirect
@@ -20,6 +21,7 @@ require ( @@ -20,6 +21,7 @@ require (
20 github.com/moul/http2curl v1.0.0 // indirect 21 github.com/moul/http2curl v1.0.0 // indirect
21 github.com/onsi/ginkgo v1.16.5 22 github.com/onsi/ginkgo v1.16.5
22 github.com/onsi/gomega v1.18.1 23 github.com/onsi/gomega v1.18.1
  24 + github.com/patrickmn/go-cache v2.1.0+incompatible
23 github.com/sergi/go-diff v1.2.0 // indirect 25 github.com/sergi/go-diff v1.2.0 // indirect
24 github.com/shopspring/decimal v1.3.1 26 github.com/shopspring/decimal v1.3.1
25 github.com/smartystreets/goconvey v1.7.2 // indirect 27 github.com/smartystreets/goconvey v1.7.2 // indirect
@@ -24,6 +24,8 @@ var METADATA_BASTION_HOST = "http://127.0.0.1:8080" @@ -24,6 +24,8 @@ var METADATA_BASTION_HOST = "http://127.0.0.1:8080"
24 24
25 var BYTE_CORE_HOST = "http://192.168.100.34:8303" 25 var BYTE_CORE_HOST = "http://192.168.100.34:8303"
26 26
  27 +var AUTH_SERVER_HOST = "http://digital-platform-dev.fjmaimaimai.com/"
  28 +
27 //var CUSTOMER_ACCOUNT = []int64{3129687560814592, 3129687690100739, 3492238958608384} 29 //var CUSTOMER_ACCOUNT = []int64{3129687560814592, 3129687690100739, 3492238958608384}
28 30
29 //const CUSTOMER_ACCOUNT_DELIMITER = "," 31 //const CUSTOMER_ACCOUNT_DELIMITER = ","
  1 +package constant
  2 +
  3 +var JWTSecretKey = "digital-platform"
  4 +
  5 +var JWTExpiresIn = 60 * 60 * 24 //单位:秒
  6 +
  7 +var JWTCacheExpiresIn = 60 * 60 * 24 //单位:秒
  1 +package domain
  2 +
  3 +import (
  4 + "errors"
  5 + jwt "github.com/dgrijalva/jwt-go"
  6 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/constant"
  7 + "time"
  8 +)
  9 +
  10 +type UserToken struct {
  11 + UserId int64 `json:"userId"`
  12 + CompanyId int64 `json:"companyId"`
  13 + CompanyType int `json:"companyType"`
  14 + jwt.StandardClaims
  15 +}
  16 +
  17 +func (tk UserToken) GenerateToken() (string, error) {
  18 + tk.StandardClaims = jwt.StandardClaims{
  19 + ExpiresAt: time.Now().Add(time.Duration(constant.JWTExpiresIn) * time.Second).Unix(),
  20 + Issuer: "digital-platform",
  21 + }
  22 + token := jwt.NewWithClaims(jwt.SigningMethodHS256, tk)
  23 + return token.SignedString([]byte(constant.JWTSecretKey))
  24 +}
  25 +
  26 +func (tk *UserToken) ParseToken(str string) error {
  27 + tokenClaims, err := jwt.ParseWithClaims(
  28 + str,
  29 + tk,
  30 + func(token *jwt.Token) (interface{}, error) {
  31 + return []byte(constant.JWTSecretKey), nil
  32 + })
  33 + if err != nil {
  34 + return err
  35 + }
  36 + if claim, ok := tokenClaims.Claims.(*UserToken); ok && tokenClaims.Valid {
  37 + *tk = *claim
  38 + return nil
  39 + }
  40 + return errors.New("token 解析失败")
  41 +}
  1 +package authlib
  2 +
  3 +import (
  4 + "github.com/beego/beego/v2/core/logs"
  5 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/api"
  6 + "net/http"
  7 + "time"
  8 +)
  9 +
  10 +type ApiAuthLib struct {
  11 + Token string
  12 + api.BaseServiceGateway
  13 +}
  14 +
  15 +func (gateway *ApiAuthLib) WithToken(token string) *ApiAuthLib {
  16 + gateway.Token = token
  17 + return gateway
  18 +}
  19 +
  20 +func (gateway *ApiAuthLib) DefaultHeader() http.Header {
  21 + var header = make(map[string][]string)
  22 + header["x-mmm-accesstoken"] = []string{gateway.Token}
  23 + return header
  24 +}
  25 +
  26 +func NewApiAuthLib(host string) *ApiAuthLib {
  27 + gt := api.NewBaseServiceGateway(host)
  28 + gt.ConnectTimeout = 360 * time.Second
  29 + gt.ReadWriteTimeout = 360 * time.Second
  30 + gt.Interceptor = func(msg string) {
  31 + //log.Logger.Info(msg)
  32 + logs.Debug(msg)
  33 + }
  34 + gt.ServiceName = "【授权中心】"
  35 + return &ApiAuthLib{
  36 + BaseServiceGateway: gt,
  37 + }
  38 +}
  39 +
  40 +func (gateway *ApiAuthLib) MeInfo(param RequestUserMeQuery) (*DataUserMe, error) {
  41 + url := gateway.Host() + "/v1/user/me"
  42 + method := "get"
  43 + var data DataUserMe
  44 + err := gateway.FastDoRequest(url, method, param, &data, api.WithHeader(gateway.DefaultHeader()))
  45 + if err != nil {
  46 + return nil, err
  47 + }
  48 + return &data, nil
  49 +}
  1 +package authlib
  2 +
  3 +type RequestUserMeQuery struct {
  4 + Token string
  5 +}
  6 +
  7 +type DataUserMe struct {
  8 + User *struct {
  9 + ID string `json:"id"`
  10 + Phone string `json:"phone"`
  11 + NickName string `json:"nickName"`
  12 + Avatar string `json:"avatar"`
  13 + } `json:"user"`
  14 + CompanyList []*struct {
  15 + ID string `json:"id"`
  16 + Name string `json:"name"`
  17 + Logo string `json:"logo"`
  18 + DefaultLogin int `json:"defaultLogin"`
  19 + Types int `json:"types"`
  20 + } `json:"companyList"`
  21 + CurrentCompany *struct {
  22 + ID string `json:"id"`
  23 + Name string `json:"name"`
  24 + Logo string `json:"logo"`
  25 + DefaultLogin int `json:"defaultLogin"`
  26 + Types int `json:"types"`
  27 + } `json:"currentCompany"`
  28 + Workbench []*struct {
  29 + ID int `json:"id"`
  30 + Name string `json:"name"`
  31 + Code string `json:"code"`
  32 + CoverImage string `json:"coverImage"`
  33 + URL string `json:"url"`
  34 + } `json:"workbench"`
  35 + Menus []*struct {
  36 + MenuID int `json:"menuId"`
  37 + ParentID int `json:"parentId"`
  38 + MenuName string `json:"menuName"`
  39 + Code string `json:"code"`
  40 + Types string `json:"types"`
  41 + } `json:"menus"`
  42 +}
@@ -36,7 +36,7 @@ type Request struct { @@ -36,7 +36,7 @@ type Request struct {
36 Param interface{} 36 Param interface{}
37 } 37 }
38 38
39 -func (gateway BaseServiceGateway) CreateRequest(url string, method string) *httplib.BeegoHTTPRequest { 39 +func (gateway BaseServiceGateway) CreateRequest(url string, method string, options *RequestOptions) *httplib.BeegoHTTPRequest {
40 var request *httplib.BeegoHTTPRequest 40 var request *httplib.BeegoHTTPRequest
41 method = strings.ToUpper(method) 41 method = strings.ToUpper(method)
42 switch method { 42 switch method {
@@ -53,6 +53,11 @@ func (gateway BaseServiceGateway) CreateRequest(url string, method string) *http @@ -53,6 +53,11 @@ func (gateway BaseServiceGateway) CreateRequest(url string, method string) *http
53 default: 53 default:
54 request = httplib.Get(url) 54 request = httplib.Get(url)
55 } 55 }
  56 + if len(options.Header) > 0 {
  57 + for k, v := range options.Header {
  58 + request.Header(k, strings.Join(v, ";"))
  59 + }
  60 + }
56 return request.SetTimeout(gateway.ConnectTimeout, gateway.ReadWriteTimeout) 61 return request.SetTimeout(gateway.ConnectTimeout, gateway.ReadWriteTimeout)
57 } 62 }
58 63
@@ -64,7 +69,7 @@ func (gateway BaseServiceGateway) GetResponseData(result Response, data interfac @@ -64,7 +69,7 @@ func (gateway BaseServiceGateway) GetResponseData(result Response, data interfac
64 return nil 69 return nil
65 } 70 }
66 71
67 -func (gateway BaseServiceGateway) FastDoRequest(url, method string, param interface{}, data interface{}) error { 72 +func (gateway BaseServiceGateway) FastDoRequest(url, method string, param interface{}, data interface{}, options ...Option) error {
68 begin := time.Now() 73 begin := time.Now()
69 var err error 74 var err error
70 var result = "success" 75 var result = "success"
@@ -82,19 +87,23 @@ func (gateway BaseServiceGateway) FastDoRequest(url, method string, param interf @@ -82,19 +87,23 @@ func (gateway BaseServiceGateway) FastDoRequest(url, method string, param interf
82 )) 87 ))
83 } 88 }
84 }() 89 }()
  90 + var requestOptions = &RequestOptions{}
  91 + for _, option := range options {
  92 + option(requestOptions)
  93 + }
85 err = gateway.DoRequest(Request{ 94 err = gateway.DoRequest(Request{
86 Url: url, 95 Url: url,
87 Method: method, 96 Method: method,
88 Param: param, 97 Param: param,
89 - }, &data) 98 + }, &data, requestOptions)
90 if err != nil { 99 if err != nil {
91 return err 100 return err
92 } 101 }
93 return nil 102 return nil
94 } 103 }
95 104
96 -func (gateway BaseServiceGateway) DoRequest(requestParam Request, val interface{}) error {  
97 - r := gateway.CreateRequest(requestParam.Url, requestParam.Method) 105 +func (gateway BaseServiceGateway) DoRequest(requestParam Request, val interface{}, options *RequestOptions) error {
  106 + r := gateway.CreateRequest(requestParam.Url, requestParam.Method, options)
98 req, err := r.JSONBody(requestParam.Param) 107 req, err := r.JSONBody(requestParam.Param)
99 if err != nil { 108 if err != nil {
100 return err 109 return err
@@ -123,3 +132,15 @@ func NewBaseServiceGateway(host string) BaseServiceGateway { @@ -123,3 +132,15 @@ func NewBaseServiceGateway(host string) BaseServiceGateway {
123 host: host, 132 host: host,
124 } 133 }
125 } 134 }
  135 +
  136 +type RequestOptions struct {
  137 + Header http.Header
  138 +}
  139 +
  140 +type Option func(o *RequestOptions)
  141 +
  142 +func WithHeader(header http.Header) Option {
  143 + return func(o *RequestOptions) {
  144 + o.Header = header
  145 + }
  146 +}
  1 +package cache
  2 +
  3 +import (
  4 + "fmt"
  5 + cache "github.com/patrickmn/go-cache"
  6 + "time"
  7 +)
  8 +
  9 +var DefaultCache = cache.New(12*time.Hour, 1*time.Hour)
  10 +
  11 +func KeyCompanyUser(companyId int, userId int) string {
  12 + return fmt.Sprintf("local:cache:user:%d:%d", companyId, userId)
  13 +}
@@ -6,8 +6,11 @@ import ( @@ -6,8 +6,11 @@ import (
6 "github.com/beego/beego/v2/server/web" 6 "github.com/beego/beego/v2/server/web"
7 "github.com/beego/beego/v2/server/web/context" 7 "github.com/beego/beego/v2/server/web/context"
8 "github.com/linmadan/egglib-go/web/beego/filters" 8 "github.com/linmadan/egglib-go/web/beego/filters"
  9 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
  10 + "net/http"
9 "os" 11 "os"
10 "strconv" 12 "strconv"
  13 + "strings"
11 14
12 . "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/log" 15 . "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/log"
13 _ "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/port/beego/routers" 16 _ "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/port/beego/routers"
@@ -48,6 +51,7 @@ func init() { @@ -48,6 +51,7 @@ func init() {
48 } 51 }
49 52
50 web.InsertFilter("/*", web.BeforeRouter, filters.AllowCors()) 53 web.InsertFilter("/*", web.BeforeRouter, filters.AllowCors())
  54 + web.InsertFilter("/*", web.BeforeRouter, JwtFilter())
51 web.InsertFilter("/*", web.BeforeExec, CreateRequestLogFilter(true)) // filters.CreateRequstLogFilter(Logger) 55 web.InsertFilter("/*", web.BeforeExec, CreateRequestLogFilter(true)) // filters.CreateRequstLogFilter(Logger)
52 web.InsertFilter("/*", web.AfterExec, filters.CreateResponseLogFilter(Logger), web.WithReturnOnOutput(false)) 56 web.InsertFilter("/*", web.AfterExec, filters.CreateResponseLogFilter(Logger), web.WithReturnOnOutput(false))
53 } 57 }
@@ -61,3 +65,24 @@ func CreateRequestLogFilter(console bool) func(ctx *context.Context) { @@ -61,3 +65,24 @@ func CreateRequestLogFilter(console bool) func(ctx *context.Context) {
61 } 65 }
62 } 66 }
63 } 67 }
  68 +
  69 +func JwtFilter() func(ctx *context.Context) {
  70 + return func(ctx *context.Context) {
  71 + token := ctx.Request.Header.Get("Authorization")
  72 + if len(token) > 0 {
  73 + token = strings.TrimPrefix(token, "Bearer ")
  74 + userToken := &domain.UserToken{}
  75 + err := userToken.ParseToken(token)
  76 + if err != nil {
  77 + ctx.Output.SetStatus(http.StatusOK)
  78 + ctx.Output.JSON(map[string]interface{}{
  79 + "msg": "token 过期或无效,需刷新令牌",
  80 + "code": 901,
  81 + "data": struct{}{},
  82 + }, false, false)
  83 + return
  84 + }
  85 + ctx.Input.SetData("UserToken", userToken)
  86 + }
  87 + }
  88 +}
@@ -4,7 +4,10 @@ import ( @@ -4,7 +4,10 @@ import (
4 "github.com/beego/beego/v2/server/web/context" 4 "github.com/beego/beego/v2/server/web/context"
5 "github.com/linmadan/egglib-go/web/beego" 5 "github.com/linmadan/egglib-go/web/beego"
6 "github.com/linmadan/egglib-go/web/beego/utils" 6 "github.com/linmadan/egglib-go/web/beego/utils"
  7 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/constant"
7 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain" 8 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
  9 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/api/authlib"
  10 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/cache"
8 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/log" 11 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/log"
9 "strconv" 12 "strconv"
10 ) 13 )
@@ -39,10 +42,37 @@ func Must(err error) { @@ -39,10 +42,37 @@ func Must(err error) {
39 } 42 }
40 43
41 func ParseContext(c beego.BaseController) *domain.Context { 44 func ParseContext(c beego.BaseController) *domain.Context {
  45 + var companyId int = 1
  46 + var userId int = 1
  47 + var userName string = "管理员"
  48 + if token := c.Ctx.Input.GetData("UserToken"); token != nil {
  49 + userToken, ok := token.(*domain.UserToken)
  50 + if ok {
  51 + // cache user info
  52 + key := cache.KeyCompanyUser(companyId, userId)
  53 + if cacheItem, ok := cache.DefaultCache.Get(key); ok {
  54 + v := cacheItem.(*authlib.DataUserMe)
  55 + userName = v.User.NickName
  56 + } else {
  57 + requestToken, _ := userToken.GenerateToken()
  58 + authLib := authlib.NewApiAuthLib(constant.AUTH_SERVER_HOST).WithToken(requestToken)
  59 + userInfo, err := authLib.MeInfo(authlib.RequestUserMeQuery{})
  60 + if err != nil || userInfo == nil || userInfo.User == nil {
  61 + goto END
  62 + }
  63 + userName = userInfo.User.NickName
  64 + cache.DefaultCache.SetDefault(key, userInfo)
  65 + }
  66 + // assign user
  67 + companyId = int(userToken.CompanyId)
  68 + userId = int(userToken.UserId)
  69 + }
  70 + }
  71 +END:
42 ctx := &domain.Context{ 72 ctx := &domain.Context{
43 - CompanyId: 1,  
44 - OperatorId: 1,  
45 - OperatorName: "管理员", 73 + CompanyId: companyId,
  74 + OperatorId: userId,
  75 + OperatorName: userName,
46 TenantId: 1, 76 TenantId: 1,
47 } 77 }
48 return ctx 78 return ctx