Merge branch 'develop' of git.ailuowan.com:zzw/lone-services into develop
CI / changes (push) Successful in 38s
CI / ad (push) Successful in 42s
CI / admin (push) Successful in 43s
CI / bff (push) Successful in 39s
CI / chore (push) Successful in 39s
CI / equipment (push) Successful in 41s
CI / express (push) Successful in 49s
CI / product (push) Successful in 43s
CI / sale (push) Successful in 42s
CI / task (push) Successful in 45s
CI / user (push) Successful in 45s
CI / wecom (push) Successful in 44s
CI / changes (push) Successful in 38s
CI / ad (push) Successful in 42s
CI / admin (push) Successful in 43s
CI / bff (push) Successful in 39s
CI / chore (push) Successful in 39s
CI / equipment (push) Successful in 41s
CI / express (push) Successful in 49s
CI / product (push) Successful in 43s
CI / sale (push) Successful in 42s
CI / task (push) Successful in 45s
CI / user (push) Successful in 45s
CI / wecom (push) Successful in 44s
This commit is contained in:
+67
-49
@@ -2,76 +2,94 @@ package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
type Jwt struct {
|
||||
const (
|
||||
TokenTypeAccess = "access"
|
||||
TokenTypeRefresh = "refresh"
|
||||
)
|
||||
|
||||
type JWT struct {
|
||||
Secret string
|
||||
Ttl int
|
||||
TTL int
|
||||
Issuer string
|
||||
}
|
||||
|
||||
type JwtInfo struct {
|
||||
Id int `gorm:"column:id;type:int(11);primary_key;AUTO_INCREMENT" json:"id"`
|
||||
NickName string `json:"nick_name"`
|
||||
DepartmentId int `gorm:"column:department_id;type:int(11);default:0;comment:部门ID" json:"department_id"`
|
||||
PostId int `gorm:"column:post_id;type:int(11);default:0;comment:岗位ID;NOT NULL" json:"post_id"`
|
||||
LastTime CustomTime `gorm:"column:last_time;type:datetime;comment:最后一次登录时间" json:"last_time"`
|
||||
func NewJWT(secret, issuer string, ttlSeconds int) (*JWT, error) {
|
||||
if secret == "" {
|
||||
return nil, errors.New("jwt secret empty")
|
||||
}
|
||||
if ttlSeconds < 1 {
|
||||
return nil, errors.New("jwt ttl invalid")
|
||||
}
|
||||
return &JWT{
|
||||
Secret: secret,
|
||||
TTL: ttlSeconds,
|
||||
Issuer: issuer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type Claims struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
Name string `json:"name"`
|
||||
Avatar string `json:"avatar"`
|
||||
Mobile string `json:"mobile"`
|
||||
Gender uint8 `json:"gender"`
|
||||
Birthday string `json:"birthday"`
|
||||
Type uint8 `json:"type"` // 1销售 2门店 3普通用户
|
||||
ClientCode string `json:"client_code"`
|
||||
SaleId int64 `json:"sale_id,omitempty"`
|
||||
StoreId int64 `json:"store_id,omitempty"`
|
||||
TokenType string `json:"token_type"` // access | refresh
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
type Token struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
func (j *JWT) Sign(claims Claims) (string, error) {
|
||||
if j == nil {
|
||||
return "", errors.New("jwt not initialized")
|
||||
}
|
||||
now := time.Now()
|
||||
claims.Issuer = j.Issuer
|
||||
claims.IssuedAt = jwt.NewNumericDate(now)
|
||||
claims.ExpiresAt = jwt.NewNumericDate(now.Add(time.Duration(j.TTL) * time.Second))
|
||||
claims.ID = strconv.FormatInt(claims.UserID, NumberTen)
|
||||
|
||||
// GetToken 获取token
|
||||
//
|
||||
// access is true 获取登录token
|
||||
// access is false 获取置换token
|
||||
func (j *Jwt) GetToken(user JwtInfo, access bool) (string, error) {
|
||||
j.setJwtConfig(access)
|
||||
// 创建 Claims
|
||||
user.Issuer = j.Issuer
|
||||
user.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Duration(j.Ttl) * time.Second)) // 过期时间
|
||||
user.ID = strconv.Itoa(user.Id)
|
||||
// 生成token对象
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, user)
|
||||
// 生成签名字符串
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString([]byte(j.Secret))
|
||||
}
|
||||
|
||||
// ParseToken 校验token
|
||||
//
|
||||
// access is true 校验登录token
|
||||
// access is false 校验置换token
|
||||
func (j *Jwt) ParseToken(tokenString string, access bool) (*JwtInfo, error) {
|
||||
j.setJwtConfig(access)
|
||||
// 解析token
|
||||
token, err := jwt.ParseWithClaims(tokenString, &JwtInfo{}, func(token *jwt.Token) (interface{}, error) {
|
||||
func (j *JWT) Parse(tokenString string) (*Claims, error) {
|
||||
if j == nil {
|
||||
return nil, errors.New("jwt not initialized")
|
||||
}
|
||||
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return []byte(j.Secret), nil
|
||||
})
|
||||
if err != nil { // 解析token失败
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//对token对象中的Claim进行类型断言
|
||||
claims, ok := token.Claims.(*JwtInfo)
|
||||
if ok && token.Valid { // 校验token
|
||||
claims.LastTime = Now()
|
||||
return claims, nil
|
||||
claims, ok := token.Claims.(*Claims)
|
||||
if !ok || !token.Valid {
|
||||
return nil, errors.New("invalid token")
|
||||
}
|
||||
return nil, errors.New("is check false")
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func (j *Jwt) setJwtConfig(access bool) {
|
||||
j.Ttl = GetConfigInt("jwt.access_ttl")
|
||||
j.Issuer = GetConfigString("jwt.issuer")
|
||||
j.Secret = GetConfigString("jwt.access_secret")
|
||||
|
||||
if !access {
|
||||
j.Ttl = GetConfigInt("jwt.refresh_ttl")
|
||||
j.Secret = GetConfigString("jwt.refresh_secret")
|
||||
func (j *JWT) ParseExpect(tokenString, expectType string) (*Claims, error) {
|
||||
claims, err := j.Parse(tokenString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if claims.TokenType != expectType {
|
||||
return nil, fmt.Errorf("unexpected token type: %s", claims.TokenType)
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
+48
-59
@@ -27,78 +27,58 @@ type UserInfo struct {
|
||||
GroupId int64 // 分组ID
|
||||
GroupName string // 分组名称
|
||||
Type uint8 // 用户类型
|
||||
ClientCode string
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func firstMD(md metadata.MD, keys ...string) string {
|
||||
for _, key := range keys {
|
||||
vals := md.Get(key)
|
||||
if len(vals) > NumberZero && vals[NumberZero] != StringEmpty {
|
||||
return vals[NumberZero]
|
||||
}
|
||||
}
|
||||
return StringEmpty
|
||||
}
|
||||
|
||||
func parseInt64MD(md metadata.MD, keys ...string) int64 {
|
||||
raw := firstMD(md, keys...)
|
||||
if raw == StringEmpty {
|
||||
return NumberZero
|
||||
}
|
||||
v, _ := strconv.ParseInt(raw, NumberTen, NumberSixtyFourth)
|
||||
return v
|
||||
}
|
||||
|
||||
func GetUserFromCtx(ctx context.Context) UserInfo {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return UserInfo{Valid: false}
|
||||
}
|
||||
uidList := md.Get("x-user-id")
|
||||
nameList := md.Get("x-user-name")
|
||||
refreshList := md.Get("x-refresh")
|
||||
ipList := md.Get("x-client-ip")
|
||||
userAgentList := md.Get("x-user-agent")
|
||||
|
||||
storeName := md.Get("x-store-name")
|
||||
storeId := md.Get("x-store-id")
|
||||
saleName := md.Get("x-sale-name")
|
||||
saleId := md.Get("x-sale-id")
|
||||
groupId := md.Get("x-group-id")
|
||||
groupName := md.Get("x-group-name")
|
||||
Type := md.Get("x-user-type")
|
||||
province := md.Get("x-sale-province")
|
||||
mobile := md.Get("x-sale-mobile")
|
||||
|
||||
if len(refreshList) == NumberZero {
|
||||
refreshList = md.Get("X-Refresh")
|
||||
}
|
||||
var userInfo UserInfo
|
||||
if len(uidList) > NumberZero {
|
||||
userInfo.RawUID = uidList[NumberZero]
|
||||
}
|
||||
if len(nameList) > NumberZero {
|
||||
userInfo.Name = nameList[NumberZero]
|
||||
}
|
||||
if len(storeName) > NumberZero {
|
||||
userInfo.StoreName = storeName[NumberZero]
|
||||
userInfo := UserInfo{
|
||||
RawUID: firstMD(md, "x-user-id"),
|
||||
Name: firstMD(md, "x-user-name"),
|
||||
Refresh: firstMD(md, "x-refresh", "X-Refresh"),
|
||||
ClientIP: firstMD(md, "x-client-ip"),
|
||||
UserAgent: firstMD(md, "x-user-agent"),
|
||||
StoreName: firstMD(md, "x-store-name"),
|
||||
StoreId: parseInt64MD(md, "x-store-id"),
|
||||
SaleName: firstMD(md, "x-sale-name"),
|
||||
SaleId: parseInt64MD(md, "x-sale-id"),
|
||||
SaleMobile: firstMD(md, "x-sale-mobile"),
|
||||
SaleProvince: parseInt64MD(md, "x-sale-province"),
|
||||
GroupId: parseInt64MD(md, "x-group-id"),
|
||||
GroupName: firstMD(md, "x-group-name"),
|
||||
ClientCode: firstMD(md, "x-client-code"),
|
||||
}
|
||||
|
||||
if len(groupName) > NumberZero {
|
||||
userInfo.GroupName = groupName[NumberZero]
|
||||
}
|
||||
if len(Type) > NumberZero {
|
||||
TypeInt, _ := strconv.ParseInt(Type[NumberZero], NumberTen, NumberSixtyFourth)
|
||||
userInfo.Type = uint8(TypeInt)
|
||||
if typeRaw := firstMD(md, "x-user-type"); typeRaw != StringEmpty {
|
||||
typeInt, _ := strconv.ParseInt(typeRaw, NumberTen, NumberSixtyFourth)
|
||||
userInfo.Type = uint8(typeInt)
|
||||
}
|
||||
|
||||
if len(storeId) > NumberZero {
|
||||
userInfo.StoreId, _ = strconv.ParseInt(storeId[NumberZero], NumberTen, NumberSixtyFourth)
|
||||
}
|
||||
if len(saleName) > NumberZero {
|
||||
userInfo.SaleName = saleName[NumberZero]
|
||||
}
|
||||
if len(province) > NumberZero {
|
||||
userInfo.SaleProvince, _ = strconv.ParseInt(province[NumberZero], NumberTen, NumberSixtyFourth)
|
||||
}
|
||||
if len(mobile) > NumberZero {
|
||||
userInfo.SaleMobile = mobile[NumberZero]
|
||||
}
|
||||
if len(saleId) > NumberZero {
|
||||
userInfo.SaleId, _ = strconv.ParseInt(saleId[NumberZero], NumberTen, NumberSixtyFourth)
|
||||
}
|
||||
if len(groupId) > NumberZero {
|
||||
userInfo.GroupId, _ = strconv.ParseInt(groupId[NumberZero], NumberTen, NumberSixtyFourth)
|
||||
}
|
||||
if len(refreshList) > NumberZero {
|
||||
userInfo.Refresh = refreshList[NumberZero]
|
||||
}
|
||||
if len(ipList) > NumberZero {
|
||||
userInfo.ClientIP = ipList[NumberZero]
|
||||
}
|
||||
if len(userAgentList) > NumberZero {
|
||||
userInfo.UserAgent = userAgentList[NumberZero]
|
||||
if userInfo.UserAgent != StringEmpty {
|
||||
userInfo.UserAgent, _ = url.QueryUnescape(userInfo.UserAgent)
|
||||
ua := user_agent.New(userInfo.UserAgent)
|
||||
userInfo.BrowserName, userInfo.BrowserVer = ua.Browser()
|
||||
@@ -116,6 +96,15 @@ func GetUserFromCtx(ctx context.Context) UserInfo {
|
||||
userName, _ := url.QueryUnescape(userInfo.Name)
|
||||
userInfo.ID = uid
|
||||
userInfo.Name = userName
|
||||
if userInfo.SaleName != StringEmpty {
|
||||
userInfo.SaleName, _ = url.QueryUnescape(userInfo.SaleName)
|
||||
}
|
||||
if userInfo.StoreName != StringEmpty {
|
||||
userInfo.StoreName, _ = url.QueryUnescape(userInfo.StoreName)
|
||||
}
|
||||
if userInfo.GroupName != StringEmpty {
|
||||
userInfo.GroupName, _ = url.QueryUnescape(userInfo.GroupName)
|
||||
}
|
||||
userInfo.Valid = true
|
||||
|
||||
return userInfo
|
||||
|
||||
@@ -217,3 +217,23 @@ func ParseCustomTime(s string) (CustomTime, error) {
|
||||
}
|
||||
return ct, nil
|
||||
}
|
||||
|
||||
// ParseDateOnly 解析 YYYY-MM-DD;空串返回零值(写入 DB 为 NULL)
|
||||
func ParseDateOnly(s string) (CustomTime, error) {
|
||||
if s == "" {
|
||||
return CustomTime{}, nil
|
||||
}
|
||||
t, err := time.ParseInLocation(time.DateOnly, s, AsiaShanghai)
|
||||
if err != nil {
|
||||
return CustomTime{}, fmt.Errorf("invalid date format: %s", s)
|
||||
}
|
||||
return CustomTime{Time: t}, nil
|
||||
}
|
||||
|
||||
// DateString 返回 YYYY-MM-DD;零值返回空串
|
||||
func (ct CustomTime) DateString() string {
|
||||
if ct.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return ct.Time.Format(time.DateOnly)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user