实现 jwt、密码加密、casbin 的接入
This commit is contained in:
15
utility/ecode/common.go
Normal file
15
utility/ecode/common.go
Normal file
@ -0,0 +1,15 @@
|
||||
package ecode
|
||||
|
||||
var (
|
||||
OK = New(0, "操作成功")
|
||||
Sub = New(1, "") // 自定义错误信息
|
||||
Fail = New(2, "服务器内部错误")
|
||||
InvalidOperation = New(3, "非法的操作请求")
|
||||
Params = New(4, "请求参数错误")
|
||||
Logout = New(5, "用户未登录")
|
||||
Disabled = New(6, "账户已被禁用")
|
||||
Denied = New(7, "没有权限执行该操作")
|
||||
Auth = New(1000, "账户名或密码不正确")
|
||||
Password = New(1001, "密码不正确")
|
||||
EmailExist = New(1002, "该邮箱已被注册")
|
||||
)
|
||||
58
utility/ecode/ecode.go
Normal file
58
utility/ecode/ecode.go
Normal file
@ -0,0 +1,58 @@
|
||||
package ecode
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
code int
|
||||
message string
|
||||
sub string
|
||||
params []interface{}
|
||||
}
|
||||
|
||||
func New(code int, message string) Error {
|
||||
return Error{
|
||||
code: code,
|
||||
message: message,
|
||||
}
|
||||
}
|
||||
|
||||
func (e Error) Params(params ...interface{}) Error {
|
||||
e.params = append(e.params, params...)
|
||||
return e
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return e.Message()
|
||||
}
|
||||
|
||||
func (e Error) Sub(sub string) Error {
|
||||
e.sub = sub
|
||||
return e
|
||||
}
|
||||
|
||||
func (e Error) Message() string {
|
||||
if e.message != "" && len(e.params) > 0 {
|
||||
e.message = fmt.Sprintf(e.message, e.params...)
|
||||
}
|
||||
if e.sub != "" {
|
||||
if e.message != "" {
|
||||
if len(e.params) > 0 {
|
||||
e.message = fmt.Sprintf(e.message, e.params...)
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", e.message, e.sub)
|
||||
}
|
||||
return e.sub
|
||||
}
|
||||
return e.message
|
||||
}
|
||||
|
||||
func (e Error) Code() gcode.Code {
|
||||
return gcode.New(e.code, e.Message(), "customer")
|
||||
}
|
||||
|
||||
func (e Error) Detail() interface{} {
|
||||
return "customer"
|
||||
}
|
||||
46
utility/encrypt/password.go
Normal file
46
utility/encrypt/password.go
Normal file
@ -0,0 +1,46 @@
|
||||
package utility
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// EncryptPassword 使用 bcrypt 算法对明文密码进行加密。
|
||||
//
|
||||
// 参数:
|
||||
// - password: 明文密码字符串。
|
||||
//
|
||||
// 返回值:
|
||||
// - 加密后的密码哈希(string)。
|
||||
// - 可能出现的错误(error)。
|
||||
//
|
||||
// 示例:
|
||||
//
|
||||
// hashed, err := EncryptPassword("mySecret123")
|
||||
// if err != nil {
|
||||
// // 处理错误
|
||||
// }
|
||||
func EncryptPassword(password string) (string, error) {
|
||||
// 使用 bcrypt 的默认成本因子(10)进行加密
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(hashedPassword), nil
|
||||
}
|
||||
|
||||
// ComparePassword 比较明文密码与加密后的密码哈希是否匹配。
|
||||
//
|
||||
// 参数:
|
||||
// - hashedPassword: 已加密的密码哈希。
|
||||
// - password: 用户输入的明文密码。
|
||||
//
|
||||
// 返回值:
|
||||
// - 如果匹配返回 true,否则返回 false。
|
||||
//
|
||||
// 示例:
|
||||
//
|
||||
// match := ComparePassword(storedHash, "userInput")
|
||||
func ComparePassword(hashedPassword, password string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
77
utility/encrypt/phone.go
Normal file
77
utility/encrypt/phone.go
Normal file
@ -0,0 +1,77 @@
|
||||
package utility
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ValidatePhoneNumber 校验手机号格式是否为中国大陆合法手机号。
|
||||
// 去除空格后匹配以 1 开头的 11 位数字,第二位为 3~9。
|
||||
//
|
||||
// 参数:
|
||||
// - phone: 需要验证的手机号字符串。
|
||||
//
|
||||
// 返回:
|
||||
// - true 表示格式合法,false 表示格式不合法。
|
||||
func ValidatePhoneNumber(phone string) bool {
|
||||
phone = strings.ReplaceAll(phone, " ", "")
|
||||
pattern := `^1[3-9]\d{9}$`
|
||||
matched, err := regexp.MatchString(pattern, phone)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return matched
|
||||
}
|
||||
|
||||
// EncryptPhoneNumber 加密手机号字符串。
|
||||
// 使用 bcrypt 进行加密,适合需要不可逆存储的场景。
|
||||
// ⚠️ 注意:加密结果无法解密,仅能做比对用途。
|
||||
//
|
||||
// 参数:
|
||||
// - phone: 待加密的手机号(需合法)。
|
||||
//
|
||||
// 返回:
|
||||
// - 加密后的哈希字符串;如果手机号无效或加密失败,返回空字符串。
|
||||
func EncryptPhoneNumber(phone string) string {
|
||||
if !ValidatePhoneNumber(phone) {
|
||||
return ""
|
||||
}
|
||||
hashedPhone, err := bcrypt.GenerateFromPassword([]byte(phone), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(hashedPhone)
|
||||
}
|
||||
|
||||
// MaskPhoneNumber 对手机号进行脱敏处理。
|
||||
// 中间四位将被替换为星号,以保护用户隐私。
|
||||
//
|
||||
// 参数:
|
||||
// - phone: 原始手机号字符串(需合法)。
|
||||
//
|
||||
// 返回:
|
||||
// - 脱敏后的手机号,如 138****1234;如果手机号无效,则返回原始输入。
|
||||
func MaskPhoneNumber(phone string) string {
|
||||
if !ValidatePhoneNumber(phone) {
|
||||
return phone
|
||||
}
|
||||
return phone[:3] + "****" + phone[7:]
|
||||
}
|
||||
|
||||
// ComparePhoneNumber 比较明文手机号与加密后的哈希是否匹配。
|
||||
// 用于验证用户输入的手机号是否与加密存储的值一致。
|
||||
//
|
||||
// 参数:
|
||||
// - hashedPhone: 使用 bcrypt 加密后的手机号哈希。
|
||||
// - phone: 用户输入的明文手机号(需合法)。
|
||||
//
|
||||
// 返回:
|
||||
// - true 表示匹配成功;false 表示不匹配或格式错误。
|
||||
func ComparePhoneNumber(hashedPhone, phone string) bool {
|
||||
if !ValidatePhoneNumber(phone) {
|
||||
return false
|
||||
}
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPhone), []byte(phone))
|
||||
return err == nil
|
||||
}
|
||||
115
utility/jwt/jwt.go
Normal file
115
utility/jwt/jwt.go
Normal file
@ -0,0 +1,115 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"server/utility/ecode"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
var (
|
||||
// secretKey 是用于签名 JWT 的密钥。应替换为更加安全的密钥并妥善保管。
|
||||
secretKey = []byte("1a40c1d5b7a1b0f0f835e0b24ca292a6")
|
||||
|
||||
// issuer 表示签发者信息,作为 JWT 标准字段的一部分。
|
||||
issuer = "arenax.com"
|
||||
)
|
||||
|
||||
type (
|
||||
// TokenIn 表示生成 JWT 所需的输入参数。
|
||||
//
|
||||
// 字段:
|
||||
// - UserId: 用户 ID;
|
||||
// - Permission: 权限标识;
|
||||
// - ExpireTime: token 过期时间(可选)。
|
||||
TokenIn struct {
|
||||
UserId int // 用户 ID
|
||||
Permission string // 权限标识
|
||||
ExpireTime time.Duration // 令牌有效期
|
||||
}
|
||||
|
||||
// TokenOut 表示从 JWT 中解析出的用户信息。
|
||||
//
|
||||
// 字段:
|
||||
// - UserId: 用户 ID;
|
||||
// - Permission: 权限标识;
|
||||
// - JTI: JWT 的唯一标识。
|
||||
TokenOut struct {
|
||||
UserId int // 用户 ID
|
||||
Permission string // 权限标识
|
||||
JTI string // JWT 唯一标识
|
||||
}
|
||||
|
||||
// jwtClaims 自定义 JWT 的声明体结构,嵌入标准声明字段。
|
||||
jwtClaims struct {
|
||||
UserId int `json:"user_id"` // 用户 ID
|
||||
Permission string `json:"permission"` // 权限标识
|
||||
JTI string `json:"jti"` // 唯一标识
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
)
|
||||
|
||||
// GenerateToken 生成带有自定义权限和过期时间的 JWT 字符串。
|
||||
//
|
||||
// 参数:
|
||||
// - in: 包含用户 ID、权限、过期时间等信息的 TokenIn 对象。
|
||||
//
|
||||
// 返回:
|
||||
// - 生成的 JWT 字符串;
|
||||
// - 若出错则返回错误信息。
|
||||
func GenerateToken(in *TokenIn) (string, error) {
|
||||
expire := in.ExpireTime
|
||||
if expire <= 0 {
|
||||
expire = 2 * time.Hour
|
||||
}
|
||||
|
||||
claims := jwtClaims{
|
||||
UserId: in.UserId,
|
||||
Permission: in.Permission,
|
||||
JTI: uuid.NewString(),
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(expire)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||
Issuer: issuer,
|
||||
},
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString(secretKey)
|
||||
}
|
||||
|
||||
// ParseToken 解析并验证 JWT 字符串并返回对应的用户信息。
|
||||
// 自动去除开头的 "Bearer " 前缀。
|
||||
//
|
||||
// 参数:
|
||||
// - tokenString: 需要解析的 JWT 字符串。
|
||||
//
|
||||
// 返回:
|
||||
// - 解析后的 TokenOut 对象;
|
||||
// - 若失败则返回相应的错误信息。
|
||||
func ParseToken(tokenString string) (*TokenOut, error) {
|
||||
if strings.HasPrefix(tokenString, "Bearer ") {
|
||||
tokenString = strings.TrimPrefix(tokenString, "Bearer ")
|
||||
}
|
||||
|
||||
token, err := jwt.ParseWithClaims(tokenString, &jwtClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return secretKey, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("解析 token 出现异常")
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(*jwtClaims)
|
||||
if !ok || !token.Valid {
|
||||
return nil, ecode.InvalidOperation.Sub("无效的 Token")
|
||||
}
|
||||
|
||||
return &TokenOut{
|
||||
UserId: claims.UserId,
|
||||
Permission: claims.Permission,
|
||||
JTI: claims.JTI,
|
||||
}, nil
|
||||
}
|
||||
64
utility/myCasbin/casbin.go
Normal file
64
utility/myCasbin/casbin.go
Normal file
@ -0,0 +1,64 @@
|
||||
package myCasbin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"github.com/hailaz/gf-casbin-adapter/v2"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type myCasbin struct {
|
||||
*casbin.Enforcer
|
||||
}
|
||||
|
||||
var (
|
||||
instance *myCasbin
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func init() {
|
||||
ctx := context.Background()
|
||||
once.Do(func() {
|
||||
modelPath := g.Config().MustGet(ctx, "casbin.modelPath").String()
|
||||
enforcer, err := casbin.NewEnforcer(modelPath, adapter.NewAdapter(
|
||||
adapter.Options{
|
||||
GDB: g.DB(),
|
||||
FieldName: &adapter.FieldName{PType: "p_type"},
|
||||
},
|
||||
))
|
||||
if err != nil {
|
||||
glog.Errorf(ctx, "init casbin error: %v", err)
|
||||
}
|
||||
|
||||
instance = &myCasbin{Enforcer: enforcer}
|
||||
})
|
||||
glog.Infof(ctx, "init casbin success")
|
||||
}
|
||||
|
||||
func GetMyCasbin() *myCasbin {
|
||||
if instance == nil {
|
||||
panic("casbin not init")
|
||||
}
|
||||
return instance
|
||||
}
|
||||
|
||||
// HasPermission 判断给定的权限标识是否拥有访问指定 URL 和方法的权限。
|
||||
//
|
||||
// 参数:
|
||||
// - permission: 权限标识(如角色名或用户 ID)
|
||||
// - url: 请求的路径(如 "/api/user/list")
|
||||
// - method: HTTP 请求方法(如 "GET", "POST")
|
||||
//
|
||||
// 返回:
|
||||
// - access: 如果有权限则为 true;否则为 false。
|
||||
// - 若校验过程中发生错误,将记录日志并返回 false。
|
||||
func (m *myCasbin) HasPermission(permission, url, method string) (access bool) {
|
||||
enforce, err := m.Enforcer.Enforce(permission, url, method)
|
||||
if err != nil {
|
||||
glog.Errorf(context.Background(), "enforce error: %v", err)
|
||||
return
|
||||
}
|
||||
return enforce
|
||||
}
|
||||
32
utility/openid.go
Normal file
32
utility/openid.go
Normal file
@ -0,0 +1,32 @@
|
||||
package utility
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GenerateUserID 根据传入的用户类型生成唯一的用户ID。
|
||||
// 用户ID格式为:<PREFIX>_<16位数字字符串>,例如:QQ_2025052712345678。
|
||||
// 前缀由 userType 参数指定(例如 "qq"、"wx"),自动转换为大写。
|
||||
// 数字部分由当前时间戳与随机数拼接而成,确保唯一性。
|
||||
//
|
||||
// 参数:
|
||||
// - userType: 用户类型,如 "qq" 或 "wx"
|
||||
//
|
||||
// 返回值:
|
||||
// - string: 格式为 <大写前缀>_<16位唯一数字> 的用户ID
|
||||
func GenerateUserID(userType string) string {
|
||||
prefix := strings.ToUpper(userType)
|
||||
|
||||
timestamp := fmt.Sprintf("%d", time.Now().UnixNano())
|
||||
core := timestamp[:12]
|
||||
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
suffix := fmt.Sprintf("%04d", rand.Intn(10000))
|
||||
|
||||
idNum := core + suffix
|
||||
|
||||
return fmt.Sprintf("%s_%s", prefix, idNum)
|
||||
}
|
||||
Reference in New Issue
Block a user