1
0

feat: 实现分层架构,包含 domain、service、repository 和 pkg 层

- 新增 domain 层:model、provider、route、stats 实体
- 新增 service 层:models、providers、routing、stats 业务逻辑
- 新增 repository 层:models、providers、stats 数据访问
- 新增 pkg 工具包:errors、logger、validator
- 新增中间件:CORS、logging、recovery、request ID
- 新增数据库迁移:初始 schema 和索引
- 新增单元测试和集成测试
- 新增规范文档:config-management、database-migration、error-handling、layered-architecture、middleware-system、request-validation、structured-logging、test-coverage
- 移除 config 子包和 model_router(已迁移至分层架构)
This commit is contained in:
2026-04-16 00:47:20 +08:00
parent 915b004924
commit f18904af1e
77 changed files with 5727 additions and 1257 deletions

View File

@@ -7,30 +7,33 @@ import (
"github.com/gin-gonic/gin"
"nex/backend/internal/config"
appErrors "nex/backend/pkg/errors"
"nex/backend/internal/domain"
"nex/backend/internal/protocol/anthropic"
"nex/backend/internal/protocol/openai"
"nex/backend/internal/provider"
"nex/backend/internal/router"
"nex/backend/internal/service"
)
// AnthropicHandler Anthropic 协议处理器
type AnthropicHandler struct {
client *provider.Client
router *router.Router
client provider.ProviderClient
routingService service.RoutingService
statsService service.StatsService
}
// NewAnthropicHandler 创建 Anthropic 处理器
func NewAnthropicHandler() *AnthropicHandler {
func NewAnthropicHandler(client provider.ProviderClient, routingService service.RoutingService, statsService service.StatsService) *AnthropicHandler {
return &AnthropicHandler{
client: provider.NewClient(),
router: router.NewRouter(),
client: client,
routingService: routingService,
statsService: statsService,
}
}
// HandleMessages 处理 Messages 请求
func (h *AnthropicHandler) HandleMessages(c *gin.Context) {
// 解析 Anthropic 请求
var req anthropic.MessagesRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, anthropic.ErrorResponse{
@@ -43,7 +46,19 @@ func (h *AnthropicHandler) HandleMessages(c *gin.Context) {
return
}
// 检查多模态内容
// 请求验证
if validationErrors := anthropic.ValidateRequest(&req); validationErrors != nil {
errMsg := formatValidationErrors(validationErrors)
c.JSON(http.StatusBadRequest, anthropic.ErrorResponse{
Type: "error",
Error: anthropic.ErrorDetail{
Type: "invalid_request_error",
Message: errMsg,
},
})
return
}
if err := h.checkMultimodalContent(&req); err != nil {
c.JSON(http.StatusBadRequest, anthropic.ErrorResponse{
Type: "error",
@@ -55,7 +70,6 @@ func (h *AnthropicHandler) HandleMessages(c *gin.Context) {
return
}
// 转换为 OpenAI 请求
openaiReq, err := anthropic.ConvertRequest(&req)
if err != nil {
c.JSON(http.StatusBadRequest, anthropic.ErrorResponse{
@@ -68,14 +82,12 @@ func (h *AnthropicHandler) HandleMessages(c *gin.Context) {
return
}
// 路由到供应商
routeResult, err := h.router.Route(openaiReq.Model)
routeResult, err := h.routingService.Route(openaiReq.Model)
if err != nil {
h.handleError(c, err)
return
}
// 根据是否流式选择处理方式
if req.Stream {
h.handleStreamRequest(c, openaiReq, routeResult)
} else {
@@ -83,9 +95,7 @@ func (h *AnthropicHandler) HandleMessages(c *gin.Context) {
}
}
// handleNonStreamRequest 处理非流式请求
func (h *AnthropicHandler) handleNonStreamRequest(c *gin.Context, openaiReq *openai.ChatCompletionRequest, routeResult *router.RouteResult) {
// 发送请求到供应商
func (h *AnthropicHandler) handleNonStreamRequest(c *gin.Context, openaiReq *openai.ChatCompletionRequest, routeResult *domain.RouteResult) {
openaiResp, err := h.client.SendRequest(c.Request.Context(), openaiReq, routeResult.Provider.APIKey, routeResult.Provider.BaseURL)
if err != nil {
c.JSON(http.StatusInternalServerError, anthropic.ErrorResponse{
@@ -98,7 +108,6 @@ func (h *AnthropicHandler) handleNonStreamRequest(c *gin.Context, openaiReq *ope
return
}
// 转换为 Anthropic 响应
anthropicResp, err := anthropic.ConvertResponse(openaiResp)
if err != nil {
c.JSON(http.StatusInternalServerError, anthropic.ErrorResponse{
@@ -111,18 +120,14 @@ func (h *AnthropicHandler) handleNonStreamRequest(c *gin.Context, openaiReq *ope
return
}
// 记录统计
go func() {
_ = config.RecordRequest(routeResult.Provider.ID, openaiReq.Model)
_ = h.statsService.Record(routeResult.Provider.ID, openaiReq.Model)
}()
// 返回响应
c.JSON(http.StatusOK, anthropicResp)
}
// handleStreamRequest 处理流式请求
func (h *AnthropicHandler) handleStreamRequest(c *gin.Context, openaiReq *openai.ChatCompletionRequest, routeResult *router.RouteResult) {
// 发送流式请求到供应商
func (h *AnthropicHandler) handleStreamRequest(c *gin.Context, openaiReq *openai.ChatCompletionRequest, routeResult *domain.RouteResult) {
eventChan, err := h.client.SendStreamRequest(c.Request.Context(), openaiReq, routeResult.Provider.APIKey, routeResult.Provider.BaseURL)
if err != nil {
c.JSON(http.StatusInternalServerError, anthropic.ErrorResponse{
@@ -135,24 +140,19 @@ func (h *AnthropicHandler) handleStreamRequest(c *gin.Context, openaiReq *openai
return
}
// 设置 SSE 响应头
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
// 创建流写入器
writer := bufio.NewWriter(c.Writer)
// 创建流式转换器
converter := anthropic.NewStreamConverter(
fmt.Sprintf("msg_%s", routeResult.Provider.ID),
openaiReq.Model,
)
// 流式转发事件
for event := range eventChan {
if event.Error != nil {
fmt.Printf("流错误: %v\n", event.Error)
break
}
@@ -160,25 +160,19 @@ func (h *AnthropicHandler) handleStreamRequest(c *gin.Context, openaiReq *openai
break
}
// 解析 OpenAI 流块
chunk, err := openai.NewAdapter().ParseStreamChunk(event.Data)
if err != nil {
fmt.Printf("解析流块失败: %v\n", err)
continue
}
// 转换为 Anthropic 事件
anthropicEvents, err := converter.ConvertChunk(chunk)
if err != nil {
fmt.Printf("转换事件失败: %v\n", err)
continue
}
// 写入事件
for _, ae := range anthropicEvents {
eventStr, err := anthropic.SerializeEvent(ae)
if err != nil {
fmt.Printf("序列化事件失败: %v\n", err)
continue
}
writer.WriteString(eventStr)
@@ -186,13 +180,11 @@ func (h *AnthropicHandler) handleStreamRequest(c *gin.Context, openaiReq *openai
}
}
// 记录统计
go func() {
_ = config.RecordRequest(routeResult.Provider.ID, openaiReq.Model)
_ = h.statsService.Record(routeResult.Provider.ID, openaiReq.Model)
}()
}
// checkMultimodalContent 检查多模态内容
func (h *AnthropicHandler) checkMultimodalContent(req *anthropic.MessagesRequest) error {
for _, msg := range req.Messages {
for _, block := range msg.Content {
@@ -204,40 +196,22 @@ func (h *AnthropicHandler) checkMultimodalContent(req *anthropic.MessagesRequest
return nil
}
// handleError 处理路由错误
func (h *AnthropicHandler) handleError(c *gin.Context, err error) {
switch err {
case router.ErrModelNotFound:
c.JSON(http.StatusNotFound, anthropic.ErrorResponse{
if appErr, ok := appErrors.AsAppError(err); ok {
c.JSON(appErr.HTTPStatus, anthropic.ErrorResponse{
Type: "error",
Error: anthropic.ErrorDetail{
Type: "not_found_error",
Message: "模型未找到",
},
})
case router.ErrModelDisabled:
c.JSON(http.StatusNotFound, anthropic.ErrorResponse{
Type: "error",
Error: anthropic.ErrorDetail{
Type: "not_found_error",
Message: "模型已禁用",
},
})
case router.ErrProviderDisabled:
c.JSON(http.StatusNotFound, anthropic.ErrorResponse{
Type: "error",
Error: anthropic.ErrorDetail{
Type: "not_found_error",
Message: "供应商已禁用",
},
})
default:
c.JSON(http.StatusInternalServerError, anthropic.ErrorResponse{
Type: "error",
Error: anthropic.ErrorDetail{
Type: "internal_error",
Message: "内部错误: " + err.Error(),
Message: appErr.Message,
},
})
return
}
c.JSON(http.StatusInternalServerError, anthropic.ErrorResponse{
Type: "error",
Error: anthropic.ErrorDetail{
Type: "internal_error",
Message: "内部错误: " + err.Error(),
},
})
}