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 = "{[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, } }