Files
utodo/pkg/jwt/jwt.go
2025-07-14 10:48:21 +08:00

140 lines
2.3 KiB
Go

package jwt
import (
"context"
"errors"
"loveuer/utodo/pkg/logger"
"reflect"
"github.com/golang-jwt/jwt/v5"
)
type PayloadFn[T any] func(data T) map[string]any
type Option[T any] func(*option[T])
type option[T any] struct {
secret string
payloadFn PayloadFn[T]
}
func WithSecret[T any](secret string) Option[T] {
return func(o *option[T]) {
if secret != "" {
o.secret = secret
}
}
}
func WithPayloadFn[T any](payloadFn PayloadFn[T]) Option[T] {
return func(o *option[T]) {
if payloadFn != nil {
o.payloadFn = payloadFn
}
}
}
type app[T any] struct {
secret string
payloadFn func(data T) map[string]any
}
func (a *app[T]) Generate(data T) (string, error) {
type MyClaims struct {
jwt.RegisteredClaims
User T
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, MyClaims{User: data})
return token.SignedString([]byte(a.secret))
}
func (a *app[T]) Parse(token string) (T, error) {
type MyClaims struct {
jwt.RegisteredClaims
User T
}
var (
err error
val T
claims *jwt.Token
)
if claims, err = jwt.ParseWithClaims(token, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(a.secret), nil
}); err != nil {
return val, err
}
if !claims.Valid {
return val, ErrTokenInvalid
}
cv, ok := claims.Claims.(*MyClaims)
if !ok {
return val, ErrTokenInvalid
}
return cv.User, nil
}
var (
ErrTokenInvalid = errors.New("token is invalid")
defaultPayloadFn PayloadFn[any] = func(data any) map[string]any {
ref := reflect.ValueOf(data)
if ref.Kind() == reflect.Pointer {
ref = ref.Elem()
}
if ref.Kind() != reflect.Struct {
logger.WarnCtx(context.TODO(), "data is not a struct")
return nil
}
typ := ref.Type()
num := typ.NumField()
payload := make(map[string]any, num)
for i := 0; i < num; i++ {
field := typ.Field(i)
tag := field.Tag.Get("json")
if tag == "-" {
continue
}
if tag == "" {
tag = field.Name
}
payload[tag] = ref.Field(i).Interface()
}
return payload
}
)
const defaultSecret = "{<Z]h/J>[5-F?s#D;~HGpxuBWi=ezNmb"
func New[T any](opts ...Option[T]) *app[T] {
opt := &option[T]{
secret: defaultSecret,
payloadFn: func(data T) map[string]any {
return defaultPayloadFn(data)
},
}
for _, o := range opts {
o(opt)
}
return &app[T]{
secret: opt.secret,
payloadFn: opt.payloadFn,
}
}