init
This commit is contained in:
157
internal/api/handler/chat.go
Normal file
157
internal/api/handler/chat.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"qd-sc/internal/model"
|
||||
"qd-sc/internal/service"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 使用服务层定义的固定模型名称
|
||||
|
||||
// ChatHandler 聊天处理器
|
||||
type ChatHandler struct {
|
||||
chatService *service.ChatService
|
||||
response *Response
|
||||
}
|
||||
|
||||
// NewChatHandler 创建聊天处理器
|
||||
func NewChatHandler(chatService *service.ChatService) *ChatHandler {
|
||||
return &ChatHandler{
|
||||
chatService: chatService,
|
||||
response: DefaultResponse,
|
||||
}
|
||||
}
|
||||
|
||||
// ChatCompletions 处理聊天completions请求
|
||||
func (h *ChatHandler) ChatCompletions(c *gin.Context) {
|
||||
var req model.ChatCompletionRequest
|
||||
|
||||
// 只支持 JSON 请求(文件通过 image_url 字段以 URL 方式传递)
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.response.Error(c, http.StatusBadRequest, "invalid_request", "无效的请求格式: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 验证请求
|
||||
if req.Model == "" {
|
||||
h.response.Error(c, http.StatusBadRequest, "invalid_request", "缺少model参数")
|
||||
return
|
||||
}
|
||||
// 验证模型名称(只接受固定模型名)
|
||||
if req.Model != service.ExposedModelName {
|
||||
h.response.Error(c, http.StatusBadRequest, "invalid_request", fmt.Sprintf("不支持的模型,请使用: %s", service.ExposedModelName))
|
||||
return
|
||||
}
|
||||
if len(req.Messages) == 0 {
|
||||
h.response.Error(c, http.StatusBadRequest, "invalid_request", "messages不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 根据stream参数决定返回方式
|
||||
if req.Stream {
|
||||
h.handleStreamResponse(c, &req)
|
||||
} else {
|
||||
h.handleNonStreamResponse(c, &req)
|
||||
}
|
||||
}
|
||||
|
||||
// handleNonStreamResponse 处理非流式响应
|
||||
func (h *ChatHandler) handleNonStreamResponse(c *gin.Context, req *model.ChatCompletionRequest) {
|
||||
resp, err := h.chatService.ProcessChatRequest(req)
|
||||
if err != nil {
|
||||
log.Printf("处理聊天请求失败: %v", err)
|
||||
h.response.Error(c, http.StatusInternalServerError, "internal_error", "处理请求失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, resp)
|
||||
}
|
||||
|
||||
// handleStreamResponse 处理流式响应
|
||||
func (h *ChatHandler) handleStreamResponse(c *gin.Context, req *model.ChatCompletionRequest) {
|
||||
// 设置SSE响应头
|
||||
c.Header("Content-Type", "text/event-stream")
|
||||
c.Header("Cache-Control", "no-cache")
|
||||
c.Header("Connection", "keep-alive")
|
||||
c.Header("Transfer-Encoding", "chunked")
|
||||
|
||||
// 传递context以支持取消
|
||||
ctx := c.Request.Context()
|
||||
chunkChan, errChan := h.chatService.ProcessChatRequestStream(ctx, req)
|
||||
|
||||
// 持续发送流式数据
|
||||
for {
|
||||
select {
|
||||
case chunk, ok := <-chunkChan:
|
||||
if !ok {
|
||||
// 通道已关闭,发送[DONE]标记(OpenAI标准格式)
|
||||
if _, err := fmt.Fprintf(c.Writer, "data: [DONE]\n\n"); err != nil {
|
||||
log.Printf("写入[DONE]标记失败: %v", err)
|
||||
}
|
||||
c.Writer.Flush()
|
||||
log.Printf("SSE流已结束,已发送[DONE]标记")
|
||||
return
|
||||
}
|
||||
|
||||
// 发送chunk
|
||||
chunkJSON, err := json.Marshal(chunk)
|
||||
if err != nil {
|
||||
log.Printf("序列化chunk失败: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 写入SSE格式
|
||||
if _, err := fmt.Fprintf(c.Writer, "data: %s\n\n", string(chunkJSON)); err != nil {
|
||||
log.Printf("写入SSE数据失败: %v", err)
|
||||
return
|
||||
}
|
||||
c.Writer.Flush()
|
||||
|
||||
// 如果这个chunk包含finish_reason,记录日志
|
||||
if len(chunk.Choices) > 0 && chunk.Choices[0].FinishReason != "" {
|
||||
log.Printf("已发送finish_reason=%s的chunk", chunk.Choices[0].FinishReason)
|
||||
}
|
||||
|
||||
case err, ok := <-errChan:
|
||||
if ok && err != nil {
|
||||
log.Printf("流式处理错误: %v", err)
|
||||
// 发送错误信息
|
||||
errChunk := model.ChatCompletionChunk{
|
||||
ID: fmt.Sprintf("chatcmpl-%d", time.Now().Unix()),
|
||||
Object: "chat.completion.chunk",
|
||||
Created: time.Now().Unix(),
|
||||
Model: service.ExposedModelName,
|
||||
Choices: []model.ChunkChoice{
|
||||
{
|
||||
Index: 0,
|
||||
Delta: model.Message{
|
||||
Role: "assistant",
|
||||
Content: fmt.Sprintf("\n\n错误:%s", err.Error()),
|
||||
},
|
||||
FinishReason: "error",
|
||||
},
|
||||
},
|
||||
}
|
||||
chunkJSON, _ := json.Marshal(errChunk)
|
||||
fmt.Fprintf(c.Writer, "data: %s\n\n", string(chunkJSON))
|
||||
c.Writer.Flush()
|
||||
|
||||
// 发送DONE
|
||||
fmt.Fprintf(c.Writer, "data: [DONE]\n\n")
|
||||
c.Writer.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
case <-c.Request.Context().Done():
|
||||
// 客户端断开连接
|
||||
log.Printf("客户端断开连接")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
23
internal/api/handler/health.go
Normal file
23
internal/api/handler/health.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// HealthHandler 健康检查处理器
|
||||
type HealthHandler struct{}
|
||||
|
||||
// NewHealthHandler 创建健康检查处理器
|
||||
func NewHealthHandler() *HealthHandler {
|
||||
return &HealthHandler{}
|
||||
}
|
||||
|
||||
// Check 健康检查
|
||||
func (h *HealthHandler) Check(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "ok",
|
||||
"service": "qd-sc-server",
|
||||
})
|
||||
}
|
||||
26
internal/api/handler/metrics.go
Normal file
26
internal/api/handler/metrics.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"qd-sc/pkg/metrics"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// MetricsHandler 指标处理器
|
||||
type MetricsHandler struct {
|
||||
metrics *metrics.Metrics
|
||||
}
|
||||
|
||||
// NewMetricsHandler 创建指标处理器
|
||||
func NewMetricsHandler() *MetricsHandler {
|
||||
return &MetricsHandler{
|
||||
metrics: metrics.GetGlobalMetrics(),
|
||||
}
|
||||
}
|
||||
|
||||
// GetMetrics 获取性能指标
|
||||
func (h *MetricsHandler) GetMetrics(c *gin.Context) {
|
||||
stats := h.metrics.GetStats()
|
||||
c.JSON(http.StatusOK, stats)
|
||||
}
|
||||
33
internal/api/handler/response.go
Normal file
33
internal/api/handler/response.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"qd-sc/internal/model"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Response 统一响应处理器
|
||||
type Response struct{}
|
||||
|
||||
// Error 发送错误响应
|
||||
func (r *Response) Error(c *gin.Context, statusCode int, errorType, message string) {
|
||||
c.JSON(statusCode, model.ErrorResponse{
|
||||
Error: model.ErrorDetail{
|
||||
Message: message,
|
||||
Type: errorType,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Success 发送成功响应
|
||||
func (r *Response) Success(c *gin.Context, data interface{}) {
|
||||
c.JSON(200, data)
|
||||
}
|
||||
|
||||
// NewResponse 创建响应处理器
|
||||
func NewResponse() *Response {
|
||||
return &Response{}
|
||||
}
|
||||
|
||||
// 全局响应处理器实例
|
||||
var DefaultResponse = NewResponse()
|
||||
Reference in New Issue
Block a user