feat: 新增 GET /api/v1/profile 用户信息查询接口,支持前端凭据查询当前用户
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/yourname/cyrene-ai/gateway/internal/middleware"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
@@ -91,7 +93,7 @@ func (h *AuthHandler) Register(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 存入 users 表
|
// 存入 users 表
|
||||||
_, err = store.CreateUser(h.db, req.Username, string(passwordHash), false)
|
_, err = store.CreateUser(h.db, req.Username, req.Nickname, string(passwordHash), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "注册失败: " + err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "注册失败: " + err.Error()})
|
||||||
return
|
return
|
||||||
@@ -143,6 +145,7 @@ func (h *AuthHandler) Login(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var userID string
|
var userID string
|
||||||
|
var nickname string
|
||||||
|
|
||||||
// 尝试从 users 表查询用户
|
// 尝试从 users 表查询用户
|
||||||
authenticated, err := h.verifyUserPassword(req.Username, req.Password)
|
authenticated, err := h.verifyUserPassword(req.Username, req.Password)
|
||||||
@@ -158,6 +161,12 @@ func (h *AuthHandler) Login(c *gin.Context) {
|
|||||||
} else {
|
} else {
|
||||||
userID = "user_" + req.Username
|
userID = "user_" + req.Username
|
||||||
}
|
}
|
||||||
|
// 获取用户昵称
|
||||||
|
if h.db != nil {
|
||||||
|
if u, err := store.GetUserByUsername(h.db, req.Username); err == nil && u != nil {
|
||||||
|
nickname = u.Nickname
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if req.Username == h.cfg.AdminUsername && h.db != nil {
|
} else if req.Username == h.cfg.AdminUsername && h.db != nil {
|
||||||
// 管理员用户尚未迁移到 users 表,尝试用配置中的密码验证
|
// 管理员用户尚未迁移到 users 表,尝试用配置中的密码验证
|
||||||
if req.Password != h.cfg.AdminPassword {
|
if req.Password != h.cfg.AdminPassword {
|
||||||
@@ -169,13 +178,14 @@ func (h *AuthHandler) Login(c *gin.Context) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Printf("⚠ 迁移管理员密码哈希失败: %v", err)
|
logger.Printf("⚠ 迁移管理员密码哈希失败: %v", err)
|
||||||
} else {
|
} else {
|
||||||
if _, err := store.CreateUser(h.db, req.Username, string(passwordHash), true); err != nil {
|
if _, err := store.CreateUser(h.db, req.Username, "管理员", string(passwordHash), true); err != nil {
|
||||||
logger.Printf("⚠ 迁移管理员到 users 表失败: %v", err)
|
logger.Printf("⚠ 迁移管理员到 users 表失败: %v", err)
|
||||||
} else {
|
} else {
|
||||||
logger.Println("✅ 管理员已迁移到 users 表")
|
logger.Println("✅ 管理员已迁移到 users 表")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
userID = "admin"
|
userID = "admin"
|
||||||
|
nickname = "管理员"
|
||||||
} else if req.Username == h.cfg.AdminUsername {
|
} else if req.Username == h.cfg.AdminUsername {
|
||||||
// 数据库不可用时的回退:使用配置中的管理员密码
|
// 数据库不可用时的回退:使用配置中的管理员密码
|
||||||
if req.Password != h.cfg.AdminPassword {
|
if req.Password != h.cfg.AdminPassword {
|
||||||
@@ -183,6 +193,7 @@ func (h *AuthHandler) Login(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
userID = "admin"
|
userID = "admin"
|
||||||
|
nickname = "管理员"
|
||||||
} else {
|
} else {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"})
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"})
|
||||||
return
|
return
|
||||||
@@ -203,6 +214,7 @@ func (h *AuthHandler) Login(c *gin.Context) {
|
|||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"user_id": userID,
|
"user_id": userID,
|
||||||
|
"nickname": nickname,
|
||||||
"token": token,
|
"token": token,
|
||||||
"refresh_token": refreshToken,
|
"refresh_token": refreshToken,
|
||||||
"expires": time.Now().Add(h.cfg.JWTExpiryHours).Unix(),
|
"expires": time.Now().Add(h.cfg.JWTExpiryHours).Unix(),
|
||||||
@@ -288,3 +300,63 @@ func (h *AuthHandler) RefreshToken(c *gin.Context) {
|
|||||||
"expires": time.Now().Add(h.cfg.JWTExpiryHours).Unix(),
|
"expires": time.Now().Add(h.cfg.JWTExpiryHours).Unix(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetProfile 查询当前登录用户信息
|
||||||
|
// GET /api/v1/profile
|
||||||
|
func (h *AuthHandler) GetProfile(c *gin.Context) {
|
||||||
|
userID := middleware.GetUserID(c)
|
||||||
|
isAdmin := middleware.GetIsAdmin(c)
|
||||||
|
|
||||||
|
var username string
|
||||||
|
var nickname string
|
||||||
|
|
||||||
|
if isAdmin {
|
||||||
|
username = "admin"
|
||||||
|
} else if strings.HasPrefix(userID, "user_") {
|
||||||
|
username = strings.TrimPrefix(userID, "user_")
|
||||||
|
} else {
|
||||||
|
username = userID
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从数据库查询详细信息
|
||||||
|
if h.db != nil {
|
||||||
|
u, err := store.GetUserByUsername(h.db, username)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "服务器内部错误"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if u != nil {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"user_id": userID,
|
||||||
|
"username": u.Username,
|
||||||
|
"nickname": u.Nickname,
|
||||||
|
"is_admin": u.IsAdmin,
|
||||||
|
"created_at": u.CreatedAt.UTC().Format(time.RFC3339),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 用户不存在(可能 admin 未迁移或数据库问题)
|
||||||
|
if !isAdmin {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据库不可用时的回退
|
||||||
|
if isAdmin {
|
||||||
|
nickname = h.cfg.AdminNickname
|
||||||
|
if nickname == "" {
|
||||||
|
nickname = "管理员"
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"user_id": userID,
|
||||||
|
"username": username,
|
||||||
|
"nickname": nickname,
|
||||||
|
"is_admin": true,
|
||||||
|
"created_at": nil,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
|
||||||
|
}
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ func Setup(r *gin.Engine, hub *ws.Hub, cfg *config.Config, sessionStore *store.S
|
|||||||
// Token刷新
|
// Token刷新
|
||||||
protected.POST("/auth/refresh", authHandler.RefreshToken)
|
protected.POST("/auth/refresh", authHandler.RefreshToken)
|
||||||
|
|
||||||
|
// 当前用户信息
|
||||||
|
protected.GET("/profile", authHandler.GetProfile)
|
||||||
|
|
||||||
// 会话管理
|
// 会话管理
|
||||||
sessions := protected.Group("/sessions")
|
sessions := protected.Group("/sessions")
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -107,6 +107,33 @@ Auth: JWT(可接受已过期的 token,或在 body 中提供 refresh_token)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### GET /profile — 查询当前用户
|
||||||
|
|
||||||
|
Auth: JWT。根据 token 返回当前登录用户的信息。
|
||||||
|
|
||||||
|
```json
|
||||||
|
// 响应 200
|
||||||
|
{
|
||||||
|
"user_id": "admin",
|
||||||
|
"username": "admin",
|
||||||
|
"nickname": "叶酱",
|
||||||
|
"is_admin": true,
|
||||||
|
"created_at": "2024-01-01T00:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `user_id` | string | 用户 ID(admin 为 `"admin"`,普通用户为 `"user_<username>"`) |
|
||||||
|
| `username` | string | 用户名 |
|
||||||
|
| `nickname` | string | 显示昵称 |
|
||||||
|
| `is_admin` | bool | 是否管理员 |
|
||||||
|
| `created_at` | string | 注册时间 (RFC3339),数据库不可用时为 null |
|
||||||
|
|
||||||
|
错误: 401 `未提供认证令牌`, 404 `用户不存在`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2. WebSocket 实时通信
|
## 2. WebSocket 实时通信
|
||||||
|
|
||||||
### GET /ws/chat — 建立连接
|
### GET /ws/chat — 建立连接
|
||||||
|
|||||||
Reference in New Issue
Block a user