上传初始版,功能未经任何测试,仅为基本框架,目前为完全不可用状态
This commit is contained in:
@@ -0,0 +1,359 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"d:\项目\dobaochet\backend\config"
|
||||
"d:\项目\dobaochet\backend\db"
|
||||
"d:\项目\dobaochet\backend\models"
|
||||
"d:\项目\dobaochet\backend\utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AdminAPI 管理员API结构体
|
||||
type AdminAPI struct {
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
// NewAdminAPI 创建管理员API实例
|
||||
func NewAdminAPI(cfg *config.Config) *AdminAPI {
|
||||
return &AdminAPI{cfg: cfg}
|
||||
}
|
||||
|
||||
// GetUsers 获取所有用户
|
||||
func (api *AdminAPI) GetUsers(c *gin.Context) {
|
||||
// 分页参数
|
||||
page := 1
|
||||
pageSize := 10
|
||||
c.Query("page")
|
||||
c.Query("page_size")
|
||||
|
||||
// 查询用户列表
|
||||
var users []models.User
|
||||
var total int64
|
||||
|
||||
db.GetDB().Model(&models.User{}).Count(&total)
|
||||
db.GetDB().Preload("Quota").Order("created_at desc").Offset((page - 1) * pageSize).Limit(pageSize).Find(&users)
|
||||
|
||||
// 转换为响应格式
|
||||
var userResponses []models.UserResponse
|
||||
for _, user := range users {
|
||||
userResponses = append(userResponses, models.UserResponse{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
IsAdmin: user.IsAdmin,
|
||||
IsActive: user.IsActive,
|
||||
CreatedAt: user.CreatedAt,
|
||||
Quota: user.Quota,
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"users": userResponses,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"total_pages": (total + int64(pageSize) - 1) / int64(pageSize),
|
||||
})
|
||||
}
|
||||
|
||||
// GetUser 获取单个用户
|
||||
func (api *AdminAPI) GetUser(c *gin.Context) {
|
||||
// 获取用户ID
|
||||
id := c.Param("id")
|
||||
|
||||
// 查询用户
|
||||
var user models.User
|
||||
if result := db.GetDB().Preload("Quota").First(&user, id); result.Error != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.UserResponse{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
IsAdmin: user.IsAdmin,
|
||||
IsActive: user.IsActive,
|
||||
CreatedAt: user.CreatedAt,
|
||||
Quota: user.Quota,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateUser 创建用户
|
||||
func (api *AdminAPI) CreateUser(c *gin.Context) {
|
||||
var req struct {
|
||||
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"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
IsActive bool `json:"is_active"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查用户名是否已存在
|
||||
var existingUser models.User
|
||||
if result := db.GetDB().Where("username = ?", req.Username).First(&existingUser); result.Error == nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "用户名已存在"})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在
|
||||
if result := db.GetDB().Where("email = ?", req.Email).First(&existingUser); result.Error == nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "邮箱已存在"})
|
||||
return
|
||||
}
|
||||
|
||||
// 密码加密
|
||||
hashedPassword, err := utils.HashPassword(req.Password)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "密码加密失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
user := models.User{
|
||||
Username: req.Username,
|
||||
Email: req.Email,
|
||||
Password: hashedPassword,
|
||||
IsAdmin: req.IsAdmin,
|
||||
IsActive: req.IsActive,
|
||||
LastLoginAt: time.Now(),
|
||||
Quota: models.Quota{
|
||||
TotalTokens: api.cfg.Quota.DefaultTotalTokens,
|
||||
UsedTokens: 0,
|
||||
ResetAt: time.Now().Add(api.cfg.Quota.ResetInterval),
|
||||
TokenLimit: api.cfg.Quota.DefaultTokenLimit,
|
||||
},
|
||||
}
|
||||
|
||||
if result := db.GetDB().Create(&user); result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "创建用户失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, models.UserResponse{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
IsAdmin: user.IsAdmin,
|
||||
IsActive: user.IsActive,
|
||||
CreatedAt: user.CreatedAt,
|
||||
Quota: user.Quota,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateUser 更新用户
|
||||
func (api *AdminAPI) UpdateUser(c *gin.Context) {
|
||||
// 获取用户ID
|
||||
id := c.Param("id")
|
||||
|
||||
// 查询用户
|
||||
var user models.User
|
||||
if result := db.GetDB().First(&user, id); result.Error != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
// 解析请求
|
||||
var req struct {
|
||||
Email string `json:"email" binding:"omitempty,email"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
IsActive bool `json:"is_active"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
if req.Email != "" && req.Email != user.Email {
|
||||
// 检查邮箱是否已被使用
|
||||
var existingUser models.User
|
||||
if result := db.GetDB().Where("email = ? AND id != ?", req.Email, user.ID).First(&existingUser); result.Error == nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "邮箱已被使用"})
|
||||
return
|
||||
}
|
||||
user.Email = req.Email
|
||||
}
|
||||
|
||||
user.IsAdmin = req.IsAdmin
|
||||
user.IsActive = req.IsActive
|
||||
|
||||
if result := db.GetDB().Save(&user); result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "更新用户失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 重新加载用户信息
|
||||
db.GetDB().Preload("Quota").First(&user, id)
|
||||
|
||||
c.JSON(http.StatusOK, models.UserResponse{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
IsAdmin: user.IsAdmin,
|
||||
IsActive: user.IsActive,
|
||||
CreatedAt: user.CreatedAt,
|
||||
Quota: user.Quota,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteUser 删除用户
|
||||
func (api *AdminAPI) DeleteUser(c *gin.Context) {
|
||||
// 获取用户ID
|
||||
id := c.Param("id")
|
||||
|
||||
// 检查是否是最后一个管理员
|
||||
var user models.User
|
||||
if result := db.GetDB().First(&user, id); result.Error != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
if user.IsAdmin {
|
||||
var adminCount int64
|
||||
db.GetDB().Model(&models.User{}).Where("is_admin = ?", true).Count(&adminCount)
|
||||
if adminCount <= 1 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "不能删除最后一个管理员用户"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 删除用户(级联删除相关数据)
|
||||
if result := db.GetDB().Delete(&user); result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除用户失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "用户删除成功"})
|
||||
}
|
||||
|
||||
// UpdateUserQuota 更新用户配额
|
||||
func (api *AdminAPI) UpdateUserQuota(c *gin.Context) {
|
||||
// 获取用户ID
|
||||
id := c.Param("id")
|
||||
|
||||
// 查询用户
|
||||
var user models.User
|
||||
if result := db.GetDB().Preload("Quota").First(&user, id); result.Error != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
// 解析请求
|
||||
var req models.QuotaUpdateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新配额
|
||||
user.Quota.TotalTokens = req.TotalTokens
|
||||
user.Quota.TokenLimit = req.TokenLimit
|
||||
|
||||
if result := db.GetDB().Save(&user.Quota); result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "更新配额失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "配额更新成功",
|
||||
"quota": user.Quota,
|
||||
})
|
||||
}
|
||||
|
||||
// ResetUserQuota 重置用户配额
|
||||
func (api *AdminAPI) ResetUserQuota(c *gin.Context) {
|
||||
// 获取用户ID
|
||||
id := c.Param("id")
|
||||
|
||||
// 查询用户
|
||||
var user models.User
|
||||
if result := db.GetDB().Preload("Quota").First(&user, id); result.Error != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
// 重置配额
|
||||
user.Quota.UsedTokens = 0
|
||||
user.Quota.ResetAt = time.Now().Add(api.cfg.Quota.ResetInterval)
|
||||
|
||||
if result := db.GetDB().Save(&user.Quota); result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "重置配额失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "配额重置成功",
|
||||
"quota": user.Quota,
|
||||
})
|
||||
}
|
||||
|
||||
// GetSystemStats 获取系统统计信息
|
||||
func (api *AdminAPI) GetSystemStats(c *gin.Context) {
|
||||
// 用户统计
|
||||
var totalUsers, activeUsers, adminUsers int64
|
||||
db.GetDB().Model(&models.User{}).Count(&totalUsers)
|
||||
db.GetDB().Model(&models.User{}).Where("is_active = ?", true).Count(&activeUsers)
|
||||
db.GetDB().Model(&models.User{}).Where("is_admin = ?", true).Count(&adminUsers)
|
||||
|
||||
// 对话统计
|
||||
var totalConversations int64
|
||||
db.GetDB().Model(&models.Conversation{}).Count(&totalConversations)
|
||||
|
||||
// 消息统计
|
||||
var totalMessages int64
|
||||
db.GetDB().Model(&models.Message{}).Count(&totalMessages)
|
||||
|
||||
// 配额统计
|
||||
var totalTokens, usedTokens int64
|
||||
db.GetDB().Model(&models.Quota{}).Select("sum(total_tokens) as total_tokens, sum(used_tokens) as used_tokens").Scan(&struct {
|
||||
TotalTokens int64
|
||||
UsedTokens int64
|
||||
}{TotalTokens: &totalTokens, UsedTokens: &usedTokens})
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"users": gin.H{
|
||||
"total": totalUsers,
|
||||
"active": activeUsers,
|
||||
"admin": adminUsers,
|
||||
},
|
||||
"conversations": totalConversations,
|
||||
"messages": totalMessages,
|
||||
"quota": gin.H{
|
||||
"total_tokens": totalTokens,
|
||||
"used_tokens": usedTokens,
|
||||
"usage_rate": float64(usedTokens) / float64(totalTokens) * 100,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterRoutes 注册管理员路由
|
||||
func (api *AdminAPI) RegisterRoutes(router *gin.RouterGroup) {
|
||||
adminGroup := router.Group("/admin")
|
||||
adminGroup.Use(AuthMiddleware(api.cfg), AdminMiddleware())
|
||||
{
|
||||
// 用户管理
|
||||
adminGroup.GET("/users", api.GetUsers)
|
||||
adminGroup.GET("/users/:id", api.GetUser)
|
||||
adminGroup.POST("/users", api.CreateUser)
|
||||
adminGroup.PUT("/users/:id", api.UpdateUser)
|
||||
adminGroup.DELETE("/users/:id", api.DeleteUser)
|
||||
|
||||
// 配额管理
|
||||
adminGroup.PUT("/users/:id/quota", api.UpdateUserQuota)
|
||||
adminGroup.POST("/users/:id/quota/reset", api.ResetUserQuota)
|
||||
|
||||
// 系统统计
|
||||
adminGroup.GET("/stats", api.GetSystemStats)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"d:\项目\dobaochet\backend\config"
|
||||
"d:\项目\dobaochet\backend\db"
|
||||
"d:\项目\dobaochet\backend\models"
|
||||
"d:\项目\dobaochet\backend\services"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ChatAPI 聊天API结构体
|
||||
type ChatAPI struct {
|
||||
cfg *config.Config
|
||||
aiService *services.AIService
|
||||
}
|
||||
|
||||
// NewChatAPI 创建聊天API实例
|
||||
func NewChatAPI(cfg *config.Config, aiService *services.AIService) *ChatAPI {
|
||||
return &ChatAPI{
|
||||
cfg: cfg,
|
||||
aiService: aiService,
|
||||
}
|
||||
}
|
||||
|
||||
// Chat 处理聊天请求
|
||||
func (api *ChatAPI) Chat(c *gin.Context) {
|
||||
// 从上下文获取用户信息
|
||||
user, exists := c.Get("user")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"})
|
||||
return
|
||||
}
|
||||
|
||||
currentUser := user.(models.User)
|
||||
|
||||
// 解析请求
|
||||
var req models.ChatRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 估算用户输入的令牌数量
|
||||
inputTokens := api.aiService.CountTokens(req.Message)
|
||||
|
||||
// 检查单次请求令牌限制
|
||||
if inputTokens > currentUser.Quota.TokenLimit {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "单次请求令牌数超过限制"})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查配额是否足够
|
||||
if currentUser.Quota.UsedTokens+inputTokens > currentUser.Quota.TotalTokens {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "令牌配额不足"})
|
||||
return
|
||||
}
|
||||
|
||||
// 开始数据库事务
|
||||
tx := db.GetDB().Begin()
|
||||
|
||||
// 获取或创建对话
|
||||
var conversation models.Conversation
|
||||
if req.ConversationID > 0 {
|
||||
// 检查对话是否属于当前用户
|
||||
if result := tx.Where("id = ? AND user_id = ?", req.ConversationID, currentUser.ID).First(&conversation); result.Error != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "对话不存在"})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 创建新对话
|
||||
conversation = models.Conversation{
|
||||
UserID: currentUser.ID,
|
||||
Title: req.Message[:min(50, len(req.Message))],
|
||||
TotalTokens: 0,
|
||||
}
|
||||
if result := tx.Create(&conversation); result.Error != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "创建对话失败"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 保存用户消息
|
||||
userMessage := models.Message{
|
||||
ConversationID: conversation.ID,
|
||||
Role: "user",
|
||||
Content: req.Message,
|
||||
Tokens: inputTokens,
|
||||
Model: api.cfg.AI.ModelName,
|
||||
}
|
||||
if result := tx.Create(&userMessage); result.Error != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存消息失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取对话历史(最近10条消息)
|
||||
var historyMessages []models.Message
|
||||
tx.Where("conversation_id = ?", conversation.ID).Order("created_at desc").Limit(10).Find(&historyMessages)
|
||||
|
||||
// 转换为AI服务需要的格式
|
||||
var aiMessages []services.Message
|
||||
for i := len(historyMessages) - 1; i >= 0; i-- {
|
||||
aiMessages = append(aiMessages, services.Message{
|
||||
Role: historyMessages[i].Role,
|
||||
Content: historyMessages[i].Content,
|
||||
})
|
||||
}
|
||||
|
||||
// 调用AI服务
|
||||
aiResp, err := api.aiService.Chat(aiMessages)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "AI服务调用失败: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查AI响应
|
||||
if len(aiResp.Choices) == 0 {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "AI服务返回空响应"})
|
||||
return
|
||||
}
|
||||
|
||||
// 保存AI回复
|
||||
aiMessage := models.Message{
|
||||
ConversationID: conversation.ID,
|
||||
Role: "assistant",
|
||||
Content: aiResp.Choices[0].Message.Content,
|
||||
Tokens: aiResp.Usage.CompletionTokens,
|
||||
Model: api.cfg.AI.ModelName,
|
||||
}
|
||||
if result := tx.Create(&aiMessage); result.Error != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存AI回复失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新对话总令牌数
|
||||
conversation.TotalTokens += aiResp.Usage.TotalTokens
|
||||
if result := tx.Save(&conversation); result.Error != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "更新对话令牌数失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新用户配额
|
||||
currentUser.Quota.UsedTokens += aiResp.Usage.TotalTokens
|
||||
if result := tx.Save(¤tUser.Quota); result.Error != nil {
|
||||
tx.Rollback()
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "更新配额失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存数据失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 返回响应
|
||||
c.JSON(http.StatusOK, models.ChatResponse{
|
||||
Response: aiMessage.Content,
|
||||
ConversationID: conversation.ID,
|
||||
TokensUsed: aiResp.Usage.TotalTokens,
|
||||
})
|
||||
}
|
||||
|
||||
// GetConversations 获取用户对话列表
|
||||
func (api *ChatAPI) GetConversations(c *gin.Context) {
|
||||
// 从上下文获取用户信息
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取对话列表
|
||||
var conversations []models.Conversation
|
||||
db.GetDB().Where("user_id = ?", userID).Order("created_at desc").Find(&conversations)
|
||||
|
||||
c.JSON(http.StatusOK, conversations)
|
||||
}
|
||||
|
||||
// GetConversation 获取对话详情
|
||||
func (api *ChatAPI) GetConversation(c *gin.Context) {
|
||||
// 从上下文获取用户信息
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取对话ID
|
||||
id := c.Param("id")
|
||||
|
||||
// 检查对话是否属于当前用户
|
||||
var conversation models.Conversation
|
||||
if result := db.GetDB().Preload("Messages").Where("id = ? AND user_id = ?", id, userID).First(&conversation); result.Error != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "对话不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, conversation)
|
||||
}
|
||||
|
||||
// DeleteConversation 删除对话
|
||||
func (api *ChatAPI) DeleteConversation(c *gin.Context) {
|
||||
// 从上下文获取用户信息
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取对话ID
|
||||
id := c.Param("id")
|
||||
|
||||
// 检查对话是否属于当前用户
|
||||
var conversation models.Conversation
|
||||
if result := db.GetDB().Where("id = ? AND user_id = ?", id, userID).First(&conversation); result.Error != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "对话不存在"})
|
||||
return
|
||||
}
|
||||
|
||||
// 删除对话(级联删除消息)
|
||||
if result := db.GetDB().Delete(&conversation); result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "删除对话失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "对话删除成功"})
|
||||
}
|
||||
|
||||
// ClearConversations 清空用户对话
|
||||
func (api *ChatAPI) ClearConversations(c *gin.Context) {
|
||||
// 从上下文获取用户信息
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"})
|
||||
return
|
||||
}
|
||||
|
||||
// 删除用户所有对话
|
||||
if result := db.GetDB().Where("user_id = ?", userID).Delete(&models.Conversation{}); result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "清空对话失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "对话清空成功"})
|
||||
}
|
||||
|
||||
// RegisterRoutes 注册聊天路由
|
||||
func (api *ChatAPI) RegisterRoutes(router *gin.RouterGroup) {
|
||||
chatGroup := router.Group("/chat")
|
||||
chatGroup.Use(AuthMiddleware(api.cfg))
|
||||
{
|
||||
chatGroup.POST("/", api.Chat)
|
||||
chatGroup.GET("/conversations", api.GetConversations)
|
||||
chatGroup.GET("/conversations/:id", api.GetConversation)
|
||||
chatGroup.DELETE("/conversations/:id", api.DeleteConversation)
|
||||
chatGroup.DELETE("/conversations", api.ClearConversations)
|
||||
}
|
||||
}
|
||||
|
||||
// min 返回两个整数中的最小值
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"d:\项目\dobaochet\backend\config"
|
||||
"d:\项目\dobaochet\backend\db"
|
||||
"d:\项目\dobaochet\backend\models"
|
||||
"d:\项目\dobaochet\backend\utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AuthMiddleware 认证中间件
|
||||
func AuthMiddleware(cfg *config.Config) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 从请求头获取Authorization
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "未提供认证令牌"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 检查Bearer前缀
|
||||
parts := strings.SplitN(authHeader, " ", 2)
|
||||
if !(len(parts) == 2 && parts[0] == "Bearer") {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "认证令牌格式错误"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 解析JWT令牌
|
||||
claims, err := utils.ParseToken(parts[1], cfg.JWT.SecretKey)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "无效的认证令牌"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 查询用户信息
|
||||
var user models.User
|
||||
result := db.GetDB().Preload("Quota").First(&user, claims.UserID)
|
||||
if result.Error != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户不存在"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 检查用户是否激活
|
||||
if !user.IsActive {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "用户已被禁用"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 将用户信息设置到上下文
|
||||
c.Set("user", user)
|
||||
c.Set("user_id", user.ID)
|
||||
c.Set("is_admin", user.IsAdmin)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// AdminMiddleware 管理员中间件
|
||||
func AdminMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 从上下文获取用户信息
|
||||
isAdmin, exists := c.Get("is_admin")
|
||||
if !exists || !isAdmin.(bool) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "需要管理员权限"})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// CORSMiddleware CORS中间件
|
||||
func CORSMiddleware(cfg *config.Config) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 设置允许的来源
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", strings.Join(cfg.CORS.AllowOrigins, ","))
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", strings.Join(cfg.CORS.AllowHeaders, ","))
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", strings.Join(cfg.CORS.AllowMethods, ","))
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"d:\项目\dobaochet\backend\config"
|
||||
"d:\项目\dobaochet\backend\db"
|
||||
"d:\项目\dobaochet\backend\models"
|
||||
"d:\项目\dobaochet\backend\utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// UserAPI 用户API结构体
|
||||
type UserAPI struct {
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
// NewUserAPI 创建用户API实例
|
||||
func NewUserAPI(cfg *config.Config) *UserAPI {
|
||||
return &UserAPI{cfg: cfg}
|
||||
}
|
||||
|
||||
// Register 注册用户
|
||||
func (api *UserAPI) Register(c *gin.Context) {
|
||||
var req models.RegisterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查用户名是否已存在
|
||||
var existingUser models.User
|
||||
if result := db.GetDB().Where("username = ?", req.Username).First(&existingUser); result.Error == nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "用户名已存在"})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在
|
||||
if result := db.GetDB().Where("email = ?", req.Email).First(&existingUser); result.Error == nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "邮箱已存在"})
|
||||
return
|
||||
}
|
||||
|
||||
// 密码加密
|
||||
hashedPassword, err := utils.HashPassword(req.Password)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "密码加密失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
user := models.User{
|
||||
Username: req.Username,
|
||||
Email: req.Email,
|
||||
Password: hashedPassword,
|
||||
IsActive: true,
|
||||
LastLoginAt: time.Now(),
|
||||
Quota: models.Quota{
|
||||
TotalTokens: api.cfg.Quota.DefaultTotalTokens,
|
||||
UsedTokens: 0,
|
||||
ResetAt: time.Now().Add(api.cfg.Quota.ResetInterval),
|
||||
TokenLimit: api.cfg.Quota.DefaultTokenLimit,
|
||||
},
|
||||
}
|
||||
|
||||
if result := db.GetDB().Create(&user); result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "创建用户失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 返回用户信息(隐藏敏感信息)
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"id": user.ID,
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
"is_admin": user.IsAdmin,
|
||||
"is_active": user.IsActive,
|
||||
"created_at": user.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
// Login 用户登录
|
||||
func (api *UserAPI) Login(c *gin.Context) {
|
||||
var req models.LoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 查找用户
|
||||
var user models.User
|
||||
if result := db.GetDB().Preload("Quota").Where("username = ?", req.Username).First(&user); result.Error != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if err := utils.VerifyPassword(user.Password, req.Password); err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"})
|
||||
return
|
||||
}
|
||||
|
||||
// 检查用户是否激活
|
||||
if !user.IsActive {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "用户已被禁用"})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新登录时间
|
||||
user.LastLoginAt = time.Now()
|
||||
db.GetDB().Save(&user)
|
||||
|
||||
// 生成JWT令牌
|
||||
token, err := utils.GenerateToken(
|
||||
user.ID,
|
||||
user.Username,
|
||||
user.IsAdmin,
|
||||
api.cfg.JWT.SecretKey,
|
||||
api.cfg.JWT.AccessTokenExpire,
|
||||
)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "生成令牌失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 返回登录结果
|
||||
c.JSON(http.StatusOK, models.LoginResponse{
|
||||
Token: token,
|
||||
User: models.UserResponse{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
IsAdmin: user.IsAdmin,
|
||||
IsActive: user.IsActive,
|
||||
CreatedAt: user.CreatedAt,
|
||||
Quota: user.Quota,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetUserInfo 获取当前用户信息
|
||||
func (api *UserAPI) GetUserInfo(c *gin.Context) {
|
||||
// 从上下文获取用户信息
|
||||
user, exists := c.Get("user")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"})
|
||||
return
|
||||
}
|
||||
|
||||
currentUser := user.(models.User)
|
||||
c.JSON(http.StatusOK, models.UserResponse{
|
||||
ID: currentUser.ID,
|
||||
Username: currentUser.Username,
|
||||
Email: currentUser.Email,
|
||||
IsAdmin: currentUser.IsAdmin,
|
||||
IsActive: currentUser.IsActive,
|
||||
CreatedAt: currentUser.CreatedAt,
|
||||
Quota: currentUser.Quota,
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateUserInfo 更新用户信息
|
||||
func (api *UserAPI) UpdateUserInfo(c *gin.Context) {
|
||||
// 从上下文获取用户信息
|
||||
user, exists := c.Get("user")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"})
|
||||
return
|
||||
}
|
||||
|
||||
currentUser := user.(models.User)
|
||||
|
||||
// 解析请求
|
||||
var req struct {
|
||||
Email string `json:"email" binding:"omitempty,email"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
if req.Email != "" && req.Email != currentUser.Email {
|
||||
// 检查邮箱是否已被使用
|
||||
var existingUser models.User
|
||||
if result := db.GetDB().Where("email = ? AND id != ?", req.Email, currentUser.ID).First(&existingUser); result.Error == nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "邮箱已被使用"})
|
||||
return
|
||||
}
|
||||
currentUser.Email = req.Email
|
||||
}
|
||||
|
||||
if result := db.GetDB().Save(¤tUser); result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "更新用户信息失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.UserResponse{
|
||||
ID: currentUser.ID,
|
||||
Username: currentUser.Username,
|
||||
Email: currentUser.Email,
|
||||
IsAdmin: currentUser.IsAdmin,
|
||||
IsActive: currentUser.IsActive,
|
||||
CreatedAt: currentUser.CreatedAt,
|
||||
Quota: currentUser.Quota,
|
||||
})
|
||||
}
|
||||
|
||||
// ChangePassword 修改密码
|
||||
func (api *UserAPI) ChangePassword(c *gin.Context) {
|
||||
// 从上下文获取用户信息
|
||||
user, exists := c.Get("user")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "未认证"})
|
||||
return
|
||||
}
|
||||
|
||||
currentUser := user.(models.User)
|
||||
|
||||
// 解析请求
|
||||
var req struct {
|
||||
OldPassword string `json:"old_password" binding:"required"`
|
||||
NewPassword string `json:"new_password" binding:"required,min=6"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证旧密码
|
||||
if err := utils.VerifyPassword(currentUser.Password, req.OldPassword); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "旧密码错误"})
|
||||
return
|
||||
}
|
||||
|
||||
// 加密新密码
|
||||
newHashedPassword, err := utils.HashPassword(req.NewPassword)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "密码加密失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
currentUser.Password = newHashedPassword
|
||||
if result := db.GetDB().Save(¤tUser); result.Error != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "修改密码失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "密码修改成功"})
|
||||
}
|
||||
|
||||
// RegisterRoutes 注册用户路由
|
||||
func (api *UserAPI) RegisterRoutes(router *gin.RouterGroup) {
|
||||
userGroup := router.Group("/users")
|
||||
{
|
||||
// 公开路由
|
||||
userGroup.POST("/register", api.Register)
|
||||
userGroup.POST("/login", api.Login)
|
||||
|
||||
// 需要认证的路由
|
||||
authGroup := userGroup.Group("/")
|
||||
authGroup.Use(AuthMiddleware(api.cfg))
|
||||
{
|
||||
authGroup.GET("/me", api.GetUserInfo)
|
||||
authGroup.PUT("/me", api.UpdateUserInfo)
|
||||
authGroup.PUT("/me/password", api.ChangePassword)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user