init
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"time"
|
||||
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
apperrors "goravel/app/errors"
|
||||
"goravel/app/models"
|
||||
"goravel/app/utils/errorlog"
|
||||
"goravel/app/utils/traceid"
|
||||
)
|
||||
|
||||
type TokenService interface {
|
||||
// CreateToken 创建token并存入数据库
|
||||
CreateToken(tokenableType string, tokenableID uint, name string, expiresAt *time.Time, browser, ip, os, sessionID string) (string, *models.PersonalAccessToken, error)
|
||||
// FindToken 根据token值查找token记录
|
||||
FindToken(token string) (*models.PersonalAccessToken, error)
|
||||
// DeleteToken 删除token
|
||||
DeleteToken(token string) error
|
||||
// DeleteTokensByUser 删除用户的所有token
|
||||
DeleteTokensByUser(tokenableType string, tokenableID uint) error
|
||||
// GetTokensByUser 获取用户的所有token
|
||||
GetTokensByUser(tokenableType string, tokenableID uint) ([]models.PersonalAccessToken, error)
|
||||
// UpdateLastUsedAt 更新最后使用时间
|
||||
UpdateLastUsedAt(token string) error
|
||||
}
|
||||
|
||||
type TokenServiceImpl struct {
|
||||
}
|
||||
|
||||
func NewTokenServiceImpl() *TokenServiceImpl {
|
||||
return &TokenServiceImpl{}
|
||||
}
|
||||
|
||||
// CreateToken 创建token并存入数据库
|
||||
func (s *TokenServiceImpl) CreateToken(tokenableType string, tokenableID uint, name string, expiresAt *time.Time, browser, ip, os, sessionID string) (string, *models.PersonalAccessToken, error) {
|
||||
// 生成随机token(类似Laravel Sanctum)
|
||||
plainToken := s.generateRandomToken()
|
||||
tokenHash := s.hashToken(plainToken)
|
||||
|
||||
// 如果没有提供sessionID,使用tokenHash的前16位作为sessionID
|
||||
if sessionID == "" {
|
||||
if len(tokenHash) >= 16 {
|
||||
sessionID = tokenHash[:16]
|
||||
} else {
|
||||
sessionID = tokenHash
|
||||
}
|
||||
}
|
||||
|
||||
// 创建token记录,立即设置last_used_at为当前时间
|
||||
now := time.Now()
|
||||
accessToken := &models.PersonalAccessToken{
|
||||
TokenableType: tokenableType,
|
||||
TokenableID: tokenableID,
|
||||
Name: name,
|
||||
Token: tokenHash,
|
||||
ExpiresAt: expiresAt,
|
||||
LastUsedAt: &now, // 登录时立即设置最后使用时间
|
||||
Browser: browser,
|
||||
IP: ip,
|
||||
OS: os,
|
||||
SessionID: sessionID,
|
||||
}
|
||||
|
||||
if err := facades.Orm().Query().Create(accessToken); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return plainToken, accessToken, nil
|
||||
}
|
||||
|
||||
// FindToken 根据token值查找token记录
|
||||
func (s *TokenServiceImpl) FindToken(token string) (*models.PersonalAccessToken, error) {
|
||||
if token == "" {
|
||||
return nil, apperrors.ErrInvalidArgument.WithMessage("token is empty")
|
||||
}
|
||||
|
||||
tokenHash := s.hashToken(token)
|
||||
var accessToken models.PersonalAccessToken
|
||||
if err := facades.Orm().Query().Where("token", tokenHash).FirstOrFail(&accessToken); err != nil {
|
||||
// 记录调试信息
|
||||
// facades.Log().Debugf("TokenService: FindToken failed, hash: %s, error: %v", tokenHash[:min(20, len(tokenHash))], err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 检查是否过期
|
||||
if accessToken.ExpiresAt != nil && accessToken.ExpiresAt.Before(time.Now()) {
|
||||
if _, err := facades.Orm().Query().Delete(&accessToken); err != nil {
|
||||
// 使用 traceid.EnsureContext 确保有 trace_id,即使使用 context.Background()
|
||||
// 这样可以保证日志的可追踪性
|
||||
ctx, _ := traceid.EnsureContext(context.Background())
|
||||
errorlog.Record(ctx, "token", "Failed to delete expired token", map[string]any{
|
||||
"token_id": accessToken.ID,
|
||||
"tokenable_id": accessToken.TokenableID,
|
||||
"expires_at": accessToken.ExpiresAt,
|
||||
"error": err.Error(),
|
||||
}, "Failed to delete expired token (ID: %d): %v", accessToken.ID, err)
|
||||
}
|
||||
|
||||
return nil, apperrors.ErrInvalidArgument.WithMessage("token expired")
|
||||
}
|
||||
|
||||
return &accessToken, nil
|
||||
}
|
||||
|
||||
// DeleteToken 删除token
|
||||
func (s *TokenServiceImpl) DeleteToken(token string) error {
|
||||
tokenHash := s.hashToken(token)
|
||||
_, err := facades.Orm().Query().Where("token", tokenHash).Delete(&models.PersonalAccessToken{})
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteTokensByUser 删除用户的所有token
|
||||
func (s *TokenServiceImpl) DeleteTokensByUser(tokenableType string, tokenableID uint) error {
|
||||
_, err := facades.Orm().Query().
|
||||
Where("tokenable_type", tokenableType).
|
||||
Where("tokenable_id", tokenableID).
|
||||
Delete(&models.PersonalAccessToken{})
|
||||
return err
|
||||
}
|
||||
|
||||
// GetTokensByUser 获取用户的所有token
|
||||
func (s *TokenServiceImpl) GetTokensByUser(tokenableType string, tokenableID uint) ([]models.PersonalAccessToken, error) {
|
||||
var tokens []models.PersonalAccessToken
|
||||
err := facades.Orm().Query().
|
||||
Where("tokenable_type", tokenableType).
|
||||
Where("tokenable_id", tokenableID).
|
||||
Order("created_at desc").
|
||||
Find(&tokens)
|
||||
return tokens, err
|
||||
}
|
||||
|
||||
// UpdateLastUsedAt 更新最后使用时间
|
||||
func (s *TokenServiceImpl) UpdateLastUsedAt(token string) error {
|
||||
tokenHash := s.hashToken(token)
|
||||
now := time.Now()
|
||||
_, err := facades.Orm().Query().
|
||||
Model(&models.PersonalAccessToken{}).
|
||||
Where("token", tokenHash).
|
||||
Update("last_used_at", now)
|
||||
return err
|
||||
}
|
||||
|
||||
// generateRandomToken 生成随机token(40个字符)
|
||||
func (s *TokenServiceImpl) generateRandomToken() string {
|
||||
// 生成40个随机字节
|
||||
b := make([]byte, 40)
|
||||
_, _ = rand.Read(b)
|
||||
// 转换为十六进制字符串(80个字符),然后取前40个字符
|
||||
return hex.EncodeToString(b)[:40]
|
||||
}
|
||||
|
||||
// hashToken 对token进行SHA256哈希
|
||||
func (s *TokenServiceImpl) hashToken(token string) string {
|
||||
hash := sha256.Sum256([]byte(token))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
Reference in New Issue
Block a user