Compare commits
4 Commits
3af5009ba8
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 7802dc976b | |||
| 7eca5ac337 | |||
|
|
8344e0f6e0 | ||
|
|
89623306c3 |
255
GIT_FLOW.md
Normal file
255
GIT_FLOW.md
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
# Git Flow 工作流规范
|
||||||
|
|
||||||
|
## 📋 分支策略
|
||||||
|
|
||||||
|
### 主要分支
|
||||||
|
- **`main`** - 生产环境代码,只接受`release/*`和`hotfix/*`的合并
|
||||||
|
- **`develop`** - 开发分支,所有功能开发的基础分支
|
||||||
|
|
||||||
|
### 支持分支
|
||||||
|
- **`feature/*`** - 功能分支,从`develop`创建,完成后合并回`develop`
|
||||||
|
- **`release/*`** - 发布分支,从`develop`创建,用于版本发布
|
||||||
|
- **`hotfix/*`** - 热修复分支,从`main`创建,紧急修复生产问题
|
||||||
|
|
||||||
|
## 🚀 工作流程
|
||||||
|
|
||||||
|
### 1. 功能开发流程
|
||||||
|
```bash
|
||||||
|
# 1. 从develop创建功能分支
|
||||||
|
git checkout develop
|
||||||
|
git pull origin develop
|
||||||
|
git checkout -b feature/user-authentication
|
||||||
|
|
||||||
|
# 2. 开发功能并提交
|
||||||
|
git add .
|
||||||
|
git commit -m "feat: 实现用户认证功能"
|
||||||
|
git push origin feature/user-authentication
|
||||||
|
|
||||||
|
# 3. 开发完成后合并到develop
|
||||||
|
git checkout develop
|
||||||
|
git pull origin develop
|
||||||
|
git merge --no-ff feature/user-authentication
|
||||||
|
|
||||||
|
# 4. 推送到远程develop
|
||||||
|
git push origin develop
|
||||||
|
|
||||||
|
# 5. 删除功能分支
|
||||||
|
git branch -d feature/user-authentication
|
||||||
|
git push origin --delete feature/user-authentication
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 发布流程
|
||||||
|
```bash
|
||||||
|
# 1. 从develop创建发布分支
|
||||||
|
git checkout develop
|
||||||
|
git checkout -b release/v1.0.0
|
||||||
|
|
||||||
|
# 2. 进行发布准备(版本号、文档等)
|
||||||
|
# 3. 合并到main和develop
|
||||||
|
git checkout main
|
||||||
|
git merge --no-ff release/v1.0.0
|
||||||
|
git tag -a v1.0.0 -m "Release v1.0.0"
|
||||||
|
|
||||||
|
git checkout develop
|
||||||
|
git merge --no-ff release/v1.0.0
|
||||||
|
|
||||||
|
# 4. 推送到远程
|
||||||
|
git push origin main --tags
|
||||||
|
git push origin develop
|
||||||
|
|
||||||
|
# 5. 删除发布分支
|
||||||
|
git branch -d release/v1.0.0
|
||||||
|
git push origin --delete release/v1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 热修复流程
|
||||||
|
```bash
|
||||||
|
# 1. 从main创建热修复分支
|
||||||
|
git checkout main
|
||||||
|
git checkout -b hotfix/critical-bug
|
||||||
|
|
||||||
|
# 2. 修复问题并提交
|
||||||
|
git add .
|
||||||
|
git commit -m "fix: 修复关键bug"
|
||||||
|
git push origin hotfix/critical-bug
|
||||||
|
|
||||||
|
# 3. 合并到main和develop
|
||||||
|
git checkout main
|
||||||
|
git merge --no-ff hotfix/critical-bug
|
||||||
|
git tag -a v1.0.1 -m "Hotfix v1.0.1"
|
||||||
|
|
||||||
|
git checkout develop
|
||||||
|
git merge --no-ff hotfix/critical-bug
|
||||||
|
|
||||||
|
# 4. 推送到远程
|
||||||
|
git push origin main --tags
|
||||||
|
git push origin develop
|
||||||
|
|
||||||
|
# 5. 删除热修复分支
|
||||||
|
git branch -d hotfix/critical-bug
|
||||||
|
git push origin --delete hotfix/critical-bug
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 提交规范
|
||||||
|
|
||||||
|
### 提交消息格式
|
||||||
|
```
|
||||||
|
<type>: <subject>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 类型说明
|
||||||
|
- **feat** - 新功能
|
||||||
|
- **fix** - 修复bug
|
||||||
|
- **docs** - 文档更新
|
||||||
|
- **style** - 代码格式调整(不影响功能)
|
||||||
|
- **refactor** - 代码重构
|
||||||
|
- **test** - 测试相关
|
||||||
|
- **chore** - 构建过程或辅助工具变动
|
||||||
|
|
||||||
|
### 示例
|
||||||
|
```bash
|
||||||
|
# 新功能
|
||||||
|
git commit -m "feat: 添加用户注册功能"
|
||||||
|
|
||||||
|
# Bug修复
|
||||||
|
git commit -m "fix: 修复登录页面样式问题"
|
||||||
|
|
||||||
|
# 文档更新
|
||||||
|
git commit -m "docs: 更新API接口文档"
|
||||||
|
|
||||||
|
# 代码重构
|
||||||
|
git commit -m "refactor: 重构用户认证模块"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 分支命名规范
|
||||||
|
|
||||||
|
### 功能分支
|
||||||
|
```
|
||||||
|
feature/<功能名称>
|
||||||
|
feature/user-authentication
|
||||||
|
feature/chat-interface
|
||||||
|
feature/ticket-system
|
||||||
|
```
|
||||||
|
|
||||||
|
### 发布分支
|
||||||
|
```
|
||||||
|
release/<版本号>
|
||||||
|
release/v1.0.0
|
||||||
|
release/v1.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### 热修复分支
|
||||||
|
```
|
||||||
|
hotfix/<问题描述>
|
||||||
|
hotfix/login-error
|
||||||
|
hotfix/database-connection
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 分支生命周期
|
||||||
|
|
||||||
|
### 功能分支
|
||||||
|
```
|
||||||
|
创建 → 开发 → 测试 → 合并 → 删除
|
||||||
|
↓ ↓ ↓ ↓ ↓
|
||||||
|
feature → PR → Review → Merge → Cleanup
|
||||||
|
```
|
||||||
|
|
||||||
|
### 发布分支
|
||||||
|
```
|
||||||
|
创建 → 测试 → 发布 → 合并 → 删除
|
||||||
|
↓ ↓ ↓ ↓ ↓
|
||||||
|
release → QA → Deploy → Tag → Cleanup
|
||||||
|
```
|
||||||
|
|
||||||
|
### 热修复分支
|
||||||
|
```
|
||||||
|
创建 → 修复 → 测试 → 合并 → 删除
|
||||||
|
↓ ↓ ↓ ↓ ↓
|
||||||
|
hotfix → Fix → Test → Deploy → Cleanup
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ 工具支持
|
||||||
|
|
||||||
|
### 常用命令
|
||||||
|
```bash
|
||||||
|
# 查看分支图
|
||||||
|
git log --oneline --graph --all
|
||||||
|
|
||||||
|
# 查看远程分支
|
||||||
|
git branch -r
|
||||||
|
|
||||||
|
# 清理已合并的分支
|
||||||
|
git branch --merged | grep -v "\*" | xargs -n 1 git branch -d
|
||||||
|
|
||||||
|
# 强制更新本地分支
|
||||||
|
git fetch --all
|
||||||
|
git reset --hard origin/develop
|
||||||
|
```
|
||||||
|
|
||||||
|
### 别名配置(可选)
|
||||||
|
```bash
|
||||||
|
# 添加到 ~/.gitconfig
|
||||||
|
[alias]
|
||||||
|
co = checkout
|
||||||
|
br = branch
|
||||||
|
ci = commit
|
||||||
|
st = status
|
||||||
|
lg = log --oneline --graph --all
|
||||||
|
pushdev = push origin develop
|
||||||
|
pushmain = push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚨 注意事项
|
||||||
|
|
||||||
|
### 1. 分支管理
|
||||||
|
- 每个功能一个独立分支
|
||||||
|
- 分支名称要有描述性
|
||||||
|
- 及时删除已合并的分支
|
||||||
|
- 保持分支历史清晰
|
||||||
|
|
||||||
|
### 2. 合并策略
|
||||||
|
- 使用 `--no-ff` 保留分支历史
|
||||||
|
- 先更新基础分支再合并
|
||||||
|
- 解决所有冲突后再推送
|
||||||
|
- 添加有意义的提交消息
|
||||||
|
|
||||||
|
### 3. 代码质量
|
||||||
|
- 每个功能都要有测试
|
||||||
|
- 遵循代码规范
|
||||||
|
- 定期进行代码审查
|
||||||
|
- 保持文档同步更新
|
||||||
|
|
||||||
|
### 4. 协作规范
|
||||||
|
- 定期同步远程分支
|
||||||
|
- 及时处理PR评论
|
||||||
|
- 保持沟通透明
|
||||||
|
- 尊重他人代码
|
||||||
|
|
||||||
|
## 📈 最佳实践
|
||||||
|
|
||||||
|
### 开发前
|
||||||
|
1. 从最新develop分支创建功能分支
|
||||||
|
2. 明确功能需求和验收标准
|
||||||
|
3. 更新相关文档
|
||||||
|
|
||||||
|
### 开发中
|
||||||
|
1. 小步提交,频繁推送
|
||||||
|
2. 保持代码整洁
|
||||||
|
3. 及时更新基础分支
|
||||||
|
|
||||||
|
### 开发后
|
||||||
|
1. 运行所有测试
|
||||||
|
2. 更新文档和注释
|
||||||
|
3. 创建Pull Request
|
||||||
|
4. 进行代码审查
|
||||||
|
5. 合并后删除分支
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**最后更新**: 2026-02-27
|
||||||
|
**维护者**: 小弟 (大哥的AI助手)
|
||||||
|
**状态**: 🟢 已启用
|
||||||
114
backend/internal/models/auth.go
Normal file
114
backend/internal/models/auth.go
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthToken 认证令牌模型
|
||||||
|
type AuthToken struct {
|
||||||
|
ID uint `gorm:"primaryKey" json:"id"`
|
||||||
|
TenantID uint `gorm:"not null;index" json:"tenant_id"`
|
||||||
|
UserID uint `gorm:"not null;index" json:"user_id"`
|
||||||
|
|
||||||
|
// 令牌信息
|
||||||
|
Token string `gorm:"size:512;not null;uniqueIndex" json:"token"`
|
||||||
|
TokenType string `gorm:"size:20;not null" json:"token_type"` // access, refresh
|
||||||
|
ExpiresAt time.Time `gorm:"not null" json:"expires_at"`
|
||||||
|
|
||||||
|
// 设备信息
|
||||||
|
DeviceID string `gorm:"size:100" json:"device_id"`
|
||||||
|
DeviceName string `gorm:"size:100" json:"device_name"`
|
||||||
|
DeviceType string `gorm:"size:50" json:"device_type"` // web, mobile, desktop
|
||||||
|
IPAddress string `gorm:"size:45" json:"ip_address"`
|
||||||
|
UserAgent string `gorm:"type:text" json:"user_agent"`
|
||||||
|
|
||||||
|
// 状态
|
||||||
|
IsRevoked bool `gorm:"default:false" json:"is_revoked"`
|
||||||
|
RevokedAt *time.Time `json:"revoked_at"`
|
||||||
|
RevokedReason string `gorm:"size:200" json:"revoked_reason"`
|
||||||
|
|
||||||
|
// 时间戳
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
|
// 关联
|
||||||
|
Tenant Tenant `gorm:"foreignKey:TenantID" json:"tenant,omitempty"`
|
||||||
|
User User `gorm:"foreignKey:UserID" json:"user,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginAttempt 登录尝试记录
|
||||||
|
type LoginAttempt struct {
|
||||||
|
ID uint `gorm:"primaryKey" json:"id"`
|
||||||
|
TenantID uint `gorm:"not null;index" json:"tenant_id"`
|
||||||
|
UserID *uint `gorm:"index" json:"user_id"`
|
||||||
|
|
||||||
|
// 尝试信息
|
||||||
|
Username string `gorm:"size:100" json:"username"`
|
||||||
|
Email string `gorm:"size:100" json:"email"`
|
||||||
|
IPAddress string `gorm:"size:45;not null" json:"ip_address"`
|
||||||
|
UserAgent string `gorm:"type:text" json:"user_agent"`
|
||||||
|
|
||||||
|
// 结果
|
||||||
|
Success bool `gorm:"not null" json:"success"`
|
||||||
|
FailureReason string `gorm:"size:200" json:"failure_reason"`
|
||||||
|
|
||||||
|
// 时间戳
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
|
||||||
|
// 关联
|
||||||
|
Tenant Tenant `gorm:"foreignKey:TenantID" json:"tenant,omitempty"`
|
||||||
|
User *User `gorm:"foreignKey:UserID" json:"user,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PasswordReset 密码重置请求
|
||||||
|
type PasswordReset struct {
|
||||||
|
ID uint `gorm:"primaryKey" json:"id"`
|
||||||
|
TenantID uint `gorm:"not null;index" json:"tenant_id"`
|
||||||
|
UserID uint `gorm:"not null;index" json:"user_id"`
|
||||||
|
|
||||||
|
// 重置信息
|
||||||
|
Token string `gorm:"size:100;not null;uniqueIndex" json:"token"`
|
||||||
|
ExpiresAt time.Time `gorm:"not null" json:"expires_at"`
|
||||||
|
|
||||||
|
// 状态
|
||||||
|
IsUsed bool `gorm:"default:false" json:"is_used"`
|
||||||
|
UsedAt *time.Time `json:"used_at"`
|
||||||
|
IPAddress string `gorm:"size:45" json:"ip_address"`
|
||||||
|
|
||||||
|
// 时间戳
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
|
||||||
|
// 关联
|
||||||
|
Tenant Tenant `gorm:"foreignKey:TenantID" json:"tenant,omitempty"`
|
||||||
|
User User `gorm:"foreignKey:UserID" json:"user,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session 用户会话
|
||||||
|
type Session struct {
|
||||||
|
ID uint `gorm:"primaryKey" json:"id"`
|
||||||
|
TenantID uint `gorm:"not null;index" json:"tenant_id"`
|
||||||
|
UserID uint `gorm:"not null;index" json:"user_id"`
|
||||||
|
|
||||||
|
// 会话信息
|
||||||
|
SessionID string `gorm:"size:100;not null;uniqueIndex" json:"session_id"`
|
||||||
|
ExpiresAt time.Time `gorm:"not null" json:"expires_at"`
|
||||||
|
|
||||||
|
// 设备信息
|
||||||
|
DeviceID string `gorm:"size:100" json:"device_id"`
|
||||||
|
DeviceName string `gorm:"size:100" json:"device_name"`
|
||||||
|
DeviceType string `gorm:"size:50" json:"device_type"`
|
||||||
|
IPAddress string `gorm:"size:45" json:"ip_address"`
|
||||||
|
UserAgent string `gorm:"type:text" json:"user_agent"`
|
||||||
|
|
||||||
|
// 活动信息
|
||||||
|
LastActivity time.Time `json:"last_activity"`
|
||||||
|
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||||
|
|
||||||
|
// 时间戳
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
|
// 关联
|
||||||
|
Tenant Tenant `gorm:"foreignKey:TenantID" json:"tenant,omitempty"`
|
||||||
|
User User `gorm:"foreignKey:UserID" json:"user,omitempty"`
|
||||||
|
}
|
||||||
425
backend/internal/services/auth_service.go
Normal file
425
backend/internal/services/auth_service.go
Normal file
@@ -0,0 +1,425 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"smart-customer-service/config"
|
||||||
|
"smart-customer-service/internal/models"
|
||||||
|
"smart-customer-service/pkg/database"
|
||||||
|
"smart-customer-service/pkg/jwt"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthService struct {
|
||||||
|
cfg *config.Config
|
||||||
|
db *gorm.DB
|
||||||
|
jwt *jwt.JWTManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAuthService(cfg *config.Config, db *gorm.DB) *AuthService {
|
||||||
|
return &AuthService{
|
||||||
|
cfg: cfg,
|
||||||
|
db: db,
|
||||||
|
jwt: jwt.NewJWTManager(cfg.JWT.Secret),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginRequest 登录请求
|
||||||
|
type LoginRequest struct {
|
||||||
|
Email string `json:"email" binding:"required,email"`
|
||||||
|
Password string `json:"password" binding:"required,min=6"`
|
||||||
|
DeviceID string `json:"device_id"`
|
||||||
|
DeviceName string `json:"device_name"`
|
||||||
|
DeviceType string `json:"device_type"`
|
||||||
|
IPAddress string `json:"ip_address"`
|
||||||
|
UserAgent string `json:"user_agent"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginResponse 登录响应
|
||||||
|
type LoginResponse struct {
|
||||||
|
User *models.User `json:"user"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
ExpiresAt time.Time `json:"expires_at"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterRequest 注册请求
|
||||||
|
type RegisterRequest struct {
|
||||||
|
TenantID uint `json:"tenant_id"`
|
||||||
|
Username string `json:"username" binding:"required,min=3,max=50"`
|
||||||
|
Email string `json:"email" binding:"required,email"`
|
||||||
|
Password string `json:"password" binding:"required,min=6"`
|
||||||
|
FullName string `json:"full_name"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterResponse 注册响应
|
||||||
|
type RegisterResponse struct {
|
||||||
|
User *models.User `json:"user"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login 用户登录
|
||||||
|
func (s *AuthService) Login(req *LoginRequest) (*LoginResponse, error) {
|
||||||
|
// 记录登录尝试
|
||||||
|
loginAttempt := &models.LoginAttempt{
|
||||||
|
Email: req.Email,
|
||||||
|
IPAddress: req.IPAddress,
|
||||||
|
UserAgent: req.UserAgent,
|
||||||
|
Success: false,
|
||||||
|
}
|
||||||
|
defer s.recordLoginAttempt(loginAttempt)
|
||||||
|
|
||||||
|
// 查找用户
|
||||||
|
var user models.User
|
||||||
|
if err := s.db.Where("email = ?", req.Email).First(&user).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
loginAttempt.FailureReason = "用户不存在"
|
||||||
|
return nil, errors.New("用户名或密码错误")
|
||||||
|
}
|
||||||
|
loginAttempt.FailureReason = "数据库错误"
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查用户状态
|
||||||
|
if user.Status != "active" {
|
||||||
|
loginAttempt.FailureReason = "用户状态异常: " + user.Status
|
||||||
|
return nil, errors.New("用户账户不可用")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证密码
|
||||||
|
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
|
||||||
|
loginAttempt.UserID = &user.ID
|
||||||
|
loginAttempt.FailureReason = "密码错误"
|
||||||
|
return nil, errors.New("用户名或密码错误")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新最后登录时间
|
||||||
|
now := time.Now()
|
||||||
|
user.LastLoginAt = &now
|
||||||
|
user.LastIP = req.IPAddress
|
||||||
|
if err := s.db.Save(&user).Error; err != nil {
|
||||||
|
loginAttempt.FailureReason = "更新登录信息失败"
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成JWT令牌
|
||||||
|
accessToken, accessExpiresAt, err := s.jwt.GenerateAccessToken(user.ID, user.TenantID, user.Role)
|
||||||
|
if err != nil {
|
||||||
|
loginAttempt.FailureReason = "生成令牌失败"
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshToken, refreshExpiresAt, err := s.jwt.GenerateRefreshToken(user.ID, user.TenantID)
|
||||||
|
if err != nil {
|
||||||
|
loginAttempt.FailureReason = "生成刷新令牌失败"
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存令牌到数据库
|
||||||
|
authToken := &models.AuthToken{
|
||||||
|
TenantID: user.TenantID,
|
||||||
|
UserID: user.ID,
|
||||||
|
Token: refreshToken,
|
||||||
|
TokenType: "refresh",
|
||||||
|
ExpiresAt: refreshExpiresAt,
|
||||||
|
DeviceID: req.DeviceID,
|
||||||
|
DeviceName: req.DeviceName,
|
||||||
|
DeviceType: req.DeviceType,
|
||||||
|
IPAddress: req.IPAddress,
|
||||||
|
UserAgent: req.UserAgent,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.db.Create(authToken).Error; err != nil {
|
||||||
|
loginAttempt.FailureReason = "保存令牌失败"
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建会话
|
||||||
|
session := &models.Session{
|
||||||
|
TenantID: user.TenantID,
|
||||||
|
UserID: user.ID,
|
||||||
|
SessionID: generateSessionID(),
|
||||||
|
ExpiresAt: refreshExpiresAt,
|
||||||
|
DeviceID: req.DeviceID,
|
||||||
|
DeviceName: req.DeviceName,
|
||||||
|
DeviceType: req.DeviceType,
|
||||||
|
IPAddress: req.IPAddress,
|
||||||
|
UserAgent: req.UserAgent,
|
||||||
|
LastActivity: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.db.Create(session).Error; err != nil {
|
||||||
|
// 不影响主要登录流程,只记录日志
|
||||||
|
fmt.Printf("创建会话失败: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记登录尝试为成功
|
||||||
|
loginAttempt.UserID = &user.ID
|
||||||
|
loginAttempt.Success = true
|
||||||
|
loginAttempt.FailureReason = ""
|
||||||
|
s.recordLoginAttempt(loginAttempt)
|
||||||
|
|
||||||
|
return &LoginResponse{
|
||||||
|
User: &user,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
ExpiresAt: accessExpiresAt,
|
||||||
|
TokenType: "Bearer",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register 用户注册
|
||||||
|
func (s *AuthService) Register(req *RegisterRequest) (*RegisterResponse, error) {
|
||||||
|
// 检查邮箱是否已存在
|
||||||
|
var existingUser models.User
|
||||||
|
if err := s.db.Where("email = ? AND tenant_id = ?", req.Email, req.TenantID).First(&existingUser).Error; err == nil {
|
||||||
|
return nil, errors.New("邮箱已被注册")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查用户名是否已存在
|
||||||
|
if err := s.db.Where("username = ? AND tenant_id = ?", req.Username, req.TenantID).First(&existingUser).Error; err == nil {
|
||||||
|
return nil, errors.New("用户名已被使用")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加密密码
|
||||||
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建用户
|
||||||
|
user := &models.User{
|
||||||
|
TenantID: req.TenantID,
|
||||||
|
Username: req.Username,
|
||||||
|
Email: req.Email,
|
||||||
|
Password: string(hashedPassword),
|
||||||
|
FullName: req.FullName,
|
||||||
|
Phone: req.Phone,
|
||||||
|
Role: "user",
|
||||||
|
Status: "active",
|
||||||
|
IsVerified: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.db.Create(user).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成验证令牌(简化版,实际应发送验证邮件)
|
||||||
|
verificationToken := generateRandomToken(32)
|
||||||
|
|
||||||
|
return &RegisterResponse{
|
||||||
|
User: user,
|
||||||
|
Token: verificationToken,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshToken 刷新访问令牌
|
||||||
|
func (s *AuthService) RefreshToken(refreshToken string) (*LoginResponse, error) {
|
||||||
|
// 查找刷新令牌
|
||||||
|
var authToken models.AuthToken
|
||||||
|
if err := s.db.Where("token = ? AND token_type = 'refresh' AND is_revoked = false", refreshToken).First(&authToken).Error; err != nil {
|
||||||
|
return nil, errors.New("无效的刷新令牌")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查令牌是否过期
|
||||||
|
if time.Now().After(authToken.ExpiresAt) {
|
||||||
|
// 标记令牌为已撤销
|
||||||
|
now := time.Now()
|
||||||
|
authToken.IsRevoked = true
|
||||||
|
authToken.RevokedAt = &now
|
||||||
|
authToken.RevokedReason = "令牌过期"
|
||||||
|
s.db.Save(&authToken)
|
||||||
|
return nil, errors.New("刷新令牌已过期")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找用户
|
||||||
|
var user models.User
|
||||||
|
if err := s.db.First(&user, authToken.UserID).Error; err != nil {
|
||||||
|
return nil, errors.New("用户不存在")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查用户状态
|
||||||
|
if user.Status != "active" {
|
||||||
|
return nil, errors.New("用户账户不可用")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成新的访问令牌
|
||||||
|
accessToken, expiresAt, err := s.jwt.GenerateAccessToken(user.ID, user.TenantID, user.Role)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &LoginResponse{
|
||||||
|
User: &user,
|
||||||
|
AccessToken: accessToken,
|
||||||
|
ExpiresAt: expiresAt,
|
||||||
|
TokenType: "Bearer",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logout 用户登出
|
||||||
|
func (s *AuthService) Logout(token string, userID uint) error {
|
||||||
|
// 撤销刷新令牌
|
||||||
|
var authToken models.AuthToken
|
||||||
|
if err := s.db.Where("token = ? AND user_id = ?", token, userID).First(&authToken).Error; err != nil {
|
||||||
|
return nil // 令牌不存在,无需处理
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
authToken.IsRevoked = true
|
||||||
|
authToken.RevokedAt = &now
|
||||||
|
authToken.RevokedReason = "用户主动登出"
|
||||||
|
|
||||||
|
return s.db.Save(&authToken).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateToken 验证访问令牌
|
||||||
|
func (s *AuthService) ValidateToken(token string) (*jwt.Claims, error) {
|
||||||
|
return s.jwt.ValidateToken(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangePassword 修改密码
|
||||||
|
func (s *AuthService) ChangePassword(userID uint, oldPassword, newPassword string) error {
|
||||||
|
var user models.User
|
||||||
|
if err := s.db.First(&user, userID).Error; err != nil {
|
||||||
|
return errors.New("用户不存在")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证旧密码
|
||||||
|
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(oldPassword)); err != nil {
|
||||||
|
return errors.New("旧密码错误")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加密新密码
|
||||||
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
user.Password = string(hashedPassword)
|
||||||
|
return s.db.Save(&user).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestPasswordReset 请求密码重置
|
||||||
|
func (s *AuthService) RequestPasswordReset(email string, tenantID uint) (string, error) {
|
||||||
|
var user models.User
|
||||||
|
if err := s.db.Where("email = ? AND tenant_id = ?", email, tenantID).First(&user).Error; err != nil {
|
||||||
|
// 出于安全考虑,即使用户不存在也返回成功
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成重置令牌
|
||||||
|
token := generateRandomToken(32)
|
||||||
|
expiresAt := time.Now().Add(24 * time.Hour) // 24小时有效
|
||||||
|
|
||||||
|
resetRequest := &models.PasswordReset{
|
||||||
|
TenantID: tenantID,
|
||||||
|
UserID: user.ID,
|
||||||
|
Token: token,
|
||||||
|
ExpiresAt: expiresAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.db.Create(resetRequest).Error; err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetPassword 重置密码
|
||||||
|
func (s *AuthService) ResetPassword(token, newPassword string) error {
|
||||||
|
var resetRequest models.PasswordReset
|
||||||
|
if err := s.db.Where("token = ? AND is_used = false", token).First(&resetRequest).Error; err != nil {
|
||||||
|
return errors.New("无效的重置令牌")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查令牌是否过期
|
||||||
|
if time.Now().After(resetRequest.ExpiresAt) {
|
||||||
|
return errors.New("重置令牌已过期")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找用户
|
||||||
|
var user models.User
|
||||||
|
if err := s.db.First(&user, resetRequest.UserID).Error; err != nil {
|
||||||
|
return errors.New("用户不存在")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加密新密码
|
||||||
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新密码
|
||||||
|
user.Password = string(hashedPassword)
|
||||||
|
if err := s.db.Save(&user).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记重置令牌为已使用
|
||||||
|
now := time.Now()
|
||||||
|
resetRequest.IsUsed = true
|
||||||
|
resetRequest.UsedAt = &now
|
||||||
|
return s.db.Save(&resetRequest).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助函数
|
||||||
|
|
||||||
|
func (s *AuthService) recordLoginAttempt(attempt *models.LoginAttempt) {
|
||||||
|
if err := s.db.Create(attempt).Error; err != nil {
|
||||||
|
fmt.Printf("记录登录尝试失败: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateSessionID() string {
|
||||||
|
b := make([]byte, 32)
|
||||||
|
rand.Read(b)
|
||||||
|
return base64.URLEncoding.EncodeToString(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateRandomToken(length int) string {
|
||||||
|
b := make([]byte, length)
|
||||||
|
rand.Read(b)
|
||||||
|
return base64.URLEncoding.EncodeToString(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserByToken 通过令牌获取用户信息
|
||||||
|
func (s *AuthService) GetUserByToken(token string) (*models.User, error) {
|
||||||
|
claims, err := s.ValidateToken(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var user models.User
|
||||||
|
if err := s.db.First(&user, claims.UserID).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserSessions 获取用户会话列表
|
||||||
|
func (s *AuthService) GetUserSessions(userID uint) ([]models.Session, error) {
|
||||||
|
var sessions []models.Session
|
||||||
|
err := s.db.Where("user_id = ? AND is_active = true", userID).Find(&sessions).Error
|
||||||
|
return sessions, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RevokeSession 撤销会话
|
||||||
|
func (s *AuthService) RevokeSession(sessionID string, userID uint) error {
|
||||||
|
var session models.Session
|
||||||
|
if err := s.db.Where("session_id = ? AND user_id = ?", sessionID, userID).First(&session).Error; err != nil {
|
||||||
|
return errors.New("会话不存在")
|
||||||
|
}
|
||||||
|
|
||||||
|
session.IsActive = false
|
||||||
|
return s.db.Save(&session).Error
|
||||||
|
}
|
||||||
249
backend/pkg/jwt/jwt_manager.go
Normal file
249
backend/pkg/jwt/jwt_manager.go
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Claims JWT声明
|
||||||
|
type Claims struct {
|
||||||
|
UserID uint `json:"user_id"`
|
||||||
|
TenantID uint `json:"tenant_id"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
||||||
|
|
||||||
|
// JWTManager JWT管理器
|
||||||
|
type JWTManager struct {
|
||||||
|
secretKey []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJWTManager 创建JWT管理器
|
||||||
|
func NewJWTManager(secretKey string) *JWTManager {
|
||||||
|
return &JWTManager{
|
||||||
|
secretKey: []byte(secretKey),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateAccessToken 生成访问令牌
|
||||||
|
func (m *JWTManager) GenerateAccessToken(userID, tenantID uint, role string) (string, time.Time, error) {
|
||||||
|
expiresAt := time.Now().Add(24 * time.Hour) // 24小时有效
|
||||||
|
|
||||||
|
claims := &Claims{
|
||||||
|
UserID: userID,
|
||||||
|
TenantID: tenantID,
|
||||||
|
Role: role,
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
ExpiresAt: jwt.NewNumericDate(expiresAt),
|
||||||
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
Issuer: "smart-customer-service",
|
||||||
|
Subject: "access_token",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
tokenString, err := token.SignedString(m.secretKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", time.Time{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenString, expiresAt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateRefreshToken 生成刷新令牌
|
||||||
|
func (m *JWTManager) GenerateRefreshToken(userID, tenantID uint) (string, time.Time, error) {
|
||||||
|
expiresAt := time.Now().Add(7 * 24 * time.Hour) // 7天有效
|
||||||
|
|
||||||
|
claims := &Claims{
|
||||||
|
UserID: userID,
|
||||||
|
TenantID: tenantID,
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
ExpiresAt: jwt.NewNumericDate(expiresAt),
|
||||||
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
Issuer: "smart-customer-service",
|
||||||
|
Subject: "refresh_token",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
tokenString, err := token.SignedString(m.secretKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", time.Time{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenString, expiresAt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateToken 验证令牌
|
||||||
|
func (m *JWTManager) ValidateToken(tokenString string) (*Claims, error) {
|
||||||
|
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
// 验证签名方法
|
||||||
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
|
return nil, errors.New("无效的签名方法")
|
||||||
|
}
|
||||||
|
return m.secretKey, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("无效的令牌")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseToken 解析令牌(不验证过期)
|
||||||
|
func (m *JWTManager) ParseToken(tokenString string) (*Claims, error) {
|
||||||
|
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
|
||||||
|
token, _, err := parser.ParseUnverified(tokenString, &Claims{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if claims, ok := token.Claims.(*Claims); ok {
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("无法解析令牌声明")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTokenExpiration 获取令牌过期时间
|
||||||
|
func (m *JWTManager) GetTokenExpiration(tokenString string) (time.Time, error) {
|
||||||
|
claims, err := m.ParseToken(tokenString)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return claims.ExpiresAt.Time, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTokenExpired 检查令牌是否过期
|
||||||
|
func (m *JWTManager) IsTokenExpired(tokenString string) (bool, error) {
|
||||||
|
expiresAt, err := m.GetTokenExpiration(tokenString)
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Now().After(expiresAt), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeneratePasswordResetToken 生成密码重置令牌
|
||||||
|
func (m *JWTManager) GeneratePasswordResetToken(userID, tenantID uint) (string, time.Time, error) {
|
||||||
|
expiresAt := time.Now().Add(1 * time.Hour) // 1小时有效
|
||||||
|
|
||||||
|
claims := &Claims{
|
||||||
|
UserID: userID,
|
||||||
|
TenantID: tenantID,
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
ExpiresAt: jwt.NewNumericDate(expiresAt),
|
||||||
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
Issuer: "smart-customer-service",
|
||||||
|
Subject: "password_reset",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
tokenString, err := token.SignedString(m.secretKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", time.Time{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenString, expiresAt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateEmailVerificationToken 生成邮箱验证令牌
|
||||||
|
func (m *JWTManager) GenerateEmailVerificationToken(userID, tenantID uint) (string, time.Time, error) {
|
||||||
|
expiresAt := time.Now().Add(24 * time.Hour) // 24小时有效
|
||||||
|
|
||||||
|
claims := &Claims{
|
||||||
|
UserID: userID,
|
||||||
|
TenantID: tenantID,
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
ExpiresAt: jwt.NewNumericDate(expiresAt),
|
||||||
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
Issuer: "smart-customer-service",
|
||||||
|
Subject: "email_verification",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
tokenString, err := token.SignedString(m.secretKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", time.Time{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenString, expiresAt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateAPIToken 生成API令牌
|
||||||
|
func (m *JWTManager) GenerateAPIToken(tenantID uint, permissions []string) (string, time.Time, error) {
|
||||||
|
expiresAt := time.Now().Add(30 * 24 * time.Hour) // 30天有效
|
||||||
|
|
||||||
|
claims := &Claims{
|
||||||
|
TenantID: tenantID,
|
||||||
|
Role: "api",
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
ExpiresAt: jwt.NewNumericDate(expiresAt),
|
||||||
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
Issuer: "smart-customer-service",
|
||||||
|
Subject: "api_token",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加自定义声明
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
|
||||||
|
// 添加权限声明
|
||||||
|
token.Claims.(jwt.MapClaims)["permissions"] = permissions
|
||||||
|
|
||||||
|
tokenString, err := token.SignedString(m.secretKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", time.Time{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenString, expiresAt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTokenClaims 获取令牌声明(安全版本)
|
||||||
|
func (m *JWTManager) GetTokenClaims(tokenString string) (map[string]interface{}, error) {
|
||||||
|
claims, err := m.ValidateToken(tokenString)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"user_id": claims.UserID,
|
||||||
|
"tenant_id": claims.TenantID,
|
||||||
|
"role": claims.Role,
|
||||||
|
"exp": claims.ExpiresAt.Time.Unix(),
|
||||||
|
"iat": claims.IssuedAt.Time.Unix(),
|
||||||
|
"iss": claims.Issuer,
|
||||||
|
"sub": claims.Subject,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewToken 续期令牌
|
||||||
|
func (m *JWTManager) RenewToken(tokenString string) (string, time.Time, error) {
|
||||||
|
claims, err := m.ValidateToken(tokenString)
|
||||||
|
if err != nil {
|
||||||
|
return "", time.Time{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据令牌类型续期
|
||||||
|
switch claims.Subject {
|
||||||
|
case "access_token":
|
||||||
|
return m.GenerateAccessToken(claims.UserID, claims.TenantID, claims.Role)
|
||||||
|
case "refresh_token":
|
||||||
|
return m.GenerateRefreshToken(claims.UserID, claims.TenantID)
|
||||||
|
case "api_token":
|
||||||
|
// API令牌不自动续期
|
||||||
|
return "", time.Time{}, errors.New("API令牌不支持自动续期")
|
||||||
|
default:
|
||||||
|
return "", time.Time{}, errors.New("未知的令牌类型")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user