u
This commit is contained in:
1 parent
c7a1f83c67
commit
6849579652
19 files changed
+600
-249
No files matched your search
@@ -1,77 +0,0 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"base-go-v2/internal/config"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTokenExpired = errors.New("token expired")
|
||||
ErrTokenInvalid = errors.New("token invalid")
|
||||
)
|
||||
|
||||
// CustomClaims 定义自己的 payload 结构
|
||||
type CustomClaims struct {
|
||||
UserID string `json:"user_id"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// CreateToken 创建一个 JWT token
|
||||
// 支持 config.App.JWT.Expiry == -1 永不过期
|
||||
func CreateToken(orgID, userID string) (string, error) {
|
||||
claims := CustomClaims{
|
||||
UserID: userID,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
},
|
||||
}
|
||||
|
||||
expiry := config.App.JWT.Expiry
|
||||
|
||||
switch {
|
||||
case expiry > 0:
|
||||
expireAt := time.Now().Add(expiry)
|
||||
claims.ExpiresAt = jwt.NewNumericDate(expireAt)
|
||||
case expiry == -1:
|
||||
// 永不过期,ExpiresAt 设置为 nil
|
||||
claims.ExpiresAt = nil
|
||||
default:
|
||||
// 默认过期时间,例如 1 小时
|
||||
expireAt := time.Now().Add(time.Hour)
|
||||
claims.ExpiresAt = jwt.NewNumericDate(expireAt)
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString([]byte(config.App.JWT.Secret))
|
||||
}
|
||||
|
||||
// VerifyToken 验证并解析 JWT token
|
||||
func VerifyToken(tokenString string) (*CustomClaims, error) {
|
||||
signKey := []byte(config.App.JWT.Secret)
|
||||
|
||||
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
// 校验签名算法
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, ErrTokenInvalid
|
||||
}
|
||||
return signKey, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, ErrTokenInvalid
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(*CustomClaims)
|
||||
if !ok || !token.Valid {
|
||||
return nil, ErrTokenInvalid
|
||||
}
|
||||
|
||||
// 检查过期:ExpiresAt 为 nil 时永不过期
|
||||
if claims.ExpiresAt != nil && time.Now().After(claims.ExpiresAt.Time) {
|
||||
return nil, ErrTokenExpired
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package jwtx
|
||||
|
||||
import (
|
||||
"base-go-v2/internal/config"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
// CustomClaims 自定义 payload,可扩展其他信息
|
||||
type CustomClaims struct {
|
||||
UserID int `json:"user_id"` // 改为 int
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// TokenResult 用于返回验证结果
|
||||
type TokenResult struct {
|
||||
Claims *CustomClaims
|
||||
Valid bool
|
||||
Expired bool
|
||||
}
|
||||
|
||||
// CreateAccessToken 创建 Access Token,只返回 token 和 error
|
||||
func CreateAccessToken(userID int) (string, error) {
|
||||
return createToken(userID, config.App.JWT.AccessExpiry)
|
||||
}
|
||||
|
||||
// CreateRefreshToken 创建 Refresh Token,只返回 token 和 error
|
||||
func CreateRefreshToken(userID int) (string, error) {
|
||||
return createToken(userID, config.App.JWT.RefreshExpiry)
|
||||
}
|
||||
|
||||
// 内部方法,根据过期时间生成 token
|
||||
func createToken(userID int, expiry time.Duration) (string, error) {
|
||||
claims := &CustomClaims{
|
||||
UserID: userID,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
},
|
||||
}
|
||||
|
||||
switch {
|
||||
case expiry > 0:
|
||||
expireAt := time.Now().Add(expiry)
|
||||
claims.ExpiresAt = jwt.NewNumericDate(expireAt)
|
||||
case expiry == -1:
|
||||
claims.ExpiresAt = nil // 永不过期
|
||||
default:
|
||||
expireAt := time.Now().Add(time.Hour)
|
||||
claims.ExpiresAt = jwt.NewNumericDate(expireAt)
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
signed, err := token.SignedString([]byte(config.App.JWT.Secret))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return signed, nil
|
||||
}
|
||||
|
||||
// VerifyToken 验证 token 并返回结构化结果
|
||||
func VerifyToken(tokenString string) TokenResult {
|
||||
result := TokenResult{Valid: false}
|
||||
|
||||
token, _ := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(config.App.JWT.Secret), nil
|
||||
})
|
||||
|
||||
claims, ok := token.Claims.(*CustomClaims)
|
||||
if !ok || !token.Valid {
|
||||
return result // token 无效
|
||||
}
|
||||
|
||||
result.Claims = claims
|
||||
result.Valid = true
|
||||
if claims.ExpiresAt != nil && time.Now().After(claims.ExpiresAt.Time) {
|
||||
result.Expired = true
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
package mapx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// M 是 map[string]interface{} 的别名
|
||||
type M map[string]interface{}
|
||||
|
||||
@@ -29,6 +34,35 @@ func (m M) SetKV(kvs ...interface{}) M {
|
||||
return m
|
||||
}
|
||||
|
||||
// ---------------- 新增方法 ----------------
|
||||
|
||||
// SetIntToString 将 int 或 int64 直接转为 string 存储,用于前端安全传输
|
||||
func (m M) SetIntToString(key string, val interface{}) M {
|
||||
var s string
|
||||
switch v := val.(type) {
|
||||
case int:
|
||||
s = strconv.Itoa(v) // 直接转为 string
|
||||
case int64:
|
||||
s = strconv.FormatInt(v, 10)
|
||||
default:
|
||||
panic("SetIntToString: unsupported type")
|
||||
}
|
||||
m[key] = s
|
||||
return m
|
||||
}
|
||||
|
||||
// GetIntFromString 将前端传来的字符串解析为 int64
|
||||
func (m M) GetIntFromString(key string) int64 {
|
||||
if v, ok := m[key]; ok {
|
||||
if s, ok := v.(string); ok {
|
||||
if i, err := strconv.ParseInt(s, 10, 64); err == nil {
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Get 获取值
|
||||
func (m M) Get(key string) interface{} {
|
||||
return m[key]
|
||||
@@ -62,44 +96,15 @@ func (m M) GetInt(key string) int {
|
||||
return int(val)
|
||||
case float64:
|
||||
return int(val)
|
||||
case string:
|
||||
if i, err := strconv.ParseInt(val, 10, 64); err == nil {
|
||||
return int(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetFloat 获取浮点数类型
|
||||
func (m M) GetFloat(key string) float64 {
|
||||
if v, ok := m[key]; ok {
|
||||
switch val := v.(type) {
|
||||
case float32:
|
||||
return float64(val)
|
||||
case float64:
|
||||
return val
|
||||
case int:
|
||||
return float64(val)
|
||||
case int8:
|
||||
return float64(val)
|
||||
case int16:
|
||||
return float64(val)
|
||||
case int32:
|
||||
return float64(val)
|
||||
case int64:
|
||||
return float64(val)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetBool 获取布尔类型
|
||||
func (m M) GetBool(key string) bool {
|
||||
if v, ok := m[key]; ok {
|
||||
if b, ok := v.(bool); ok {
|
||||
return b
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Merge 合并另一个 M,后者会覆盖前者同名字段
|
||||
func (m M) Merge(other M) M {
|
||||
for k, v := range other {
|
||||
@@ -107,3 +112,8 @@ func (m M) Merge(other M) M {
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// MarshalJSON 支持 M 自定义 JSON 序列化
|
||||
func (m M) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]interface{}(m))
|
||||
}
|
||||
@@ -1,42 +1,65 @@
|
||||
package validate
|
||||
|
||||
import (
|
||||
"base-go-v2/internal/errs"
|
||||
"errors"
|
||||
"reflect"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ValidateNotEmpty 校验结构体中指定字段不能为空
|
||||
func ValidateNotEmpty(s interface{}, fields ...string) error {
|
||||
v := reflect.ValueOf(s)
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
// ValidationError 用于收集多个字段错误,同时实现 error 接口
|
||||
type ValidationError struct {
|
||||
Errors map[string]string
|
||||
}
|
||||
|
||||
// Error 实现 error 接口
|
||||
func (v *ValidationError) Error() string {
|
||||
msgs := make([]string, 0, len(v.Errors))
|
||||
for field, err := range v.Errors {
|
||||
msgs = append(msgs, fmt.Sprintf("%s: %s", field, err))
|
||||
}
|
||||
return strings.Join(msgs, "; ")
|
||||
}
|
||||
|
||||
// NotEmpty 校验 map[string]interface{} 中指定字段不能为空
|
||||
func NotEmpty(data map[string]interface{}, fields ...string) error {
|
||||
errs := make(map[string]string)
|
||||
|
||||
for _, field := range fields {
|
||||
f := v.FieldByName(field)
|
||||
if !f.IsValid() {
|
||||
return errors.New("field " + field + " does not exist")
|
||||
}
|
||||
|
||||
switch f.Kind() {
|
||||
case reflect.String:
|
||||
if strings.TrimSpace(f.String()) == "" {
|
||||
return errs.NewValidationError(field, "不能为空")
|
||||
}
|
||||
case reflect.Slice, reflect.Array, reflect.Map:
|
||||
if f.Len() == 0 {
|
||||
return errs.NewValidationError(field, "不能为空")
|
||||
}
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
if f.IsNil() {
|
||||
return errs.NewValidationError(field, "不能为空")
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
collectErrors(data[field], field, errs)
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return &ValidationError{Errors: errs}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// collectErrors 递归收集字段错误
|
||||
func collectErrors(v interface{}, path string, errs map[string]string) {
|
||||
if v == nil {
|
||||
errs[path] = fmt.Sprintf("字段 %s 不能为空", path)
|
||||
return
|
||||
}
|
||||
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
if strings.TrimSpace(val) == "" {
|
||||
errs[path] = fmt.Sprintf("字段 %s 不能为空", path)
|
||||
}
|
||||
case []interface{}:
|
||||
if len(val) == 0 {
|
||||
errs[path] = fmt.Sprintf("字段 %s 不能为空", path)
|
||||
}
|
||||
for i, elem := range val {
|
||||
collectErrors(elem, fmt.Sprintf("%s[%d]", path, i), errs)
|
||||
}
|
||||
case map[string]interface{}:
|
||||
if len(val) == 0 {
|
||||
errs[path] = fmt.Sprintf("字段 %s 不能为空", path)
|
||||
}
|
||||
for k, elem := range val {
|
||||
collectErrors(elem, fmt.Sprintf("%s.%s", path, k), errs)
|
||||
}
|
||||
default:
|
||||
// 其他类型暂不校验
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user