🎉 初始化项目

Signed-off-by: Echo <1711788888@qq.com>
This commit is contained in:
2026-02-27 21:52:00 +08:00
commit f4e166c5ee
482 changed files with 55079 additions and 0 deletions

View File

@@ -0,0 +1,316 @@
package app
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"time"
"git.echol.cn/loser/st/server/global"
"git.echol.cn/loser/st/server/model/app"
"git.echol.cn/loser/st/server/model/app/request"
"git.echol.cn/loser/st/server/model/app/response"
"gorm.io/datatypes"
"gorm.io/gorm"
)
type AIConfigService struct{}
// CreateAIConfig 创建AI配置
func (s *AIConfigService) CreateAIConfig(req *request.CreateAIConfigRequest) (*response.AIConfigResponse, error) {
// 序列化 JSON 字段
settingsJSON, _ := json.Marshal(req.Settings)
config := app.AIConfig{
Name: req.Name,
Provider: req.Provider,
BaseURL: req.BaseURL,
APIKey: req.APIKey,
DefaultModel: req.DefaultModel,
Settings: datatypes.JSON(settingsJSON),
Models: datatypes.JSON("[]"),
IsActive: true,
IsDefault: false,
}
err := global.GVA_DB.Create(&config).Error
if err != nil {
return nil, err
}
resp := response.ToAIConfigResponse(&config)
return &resp, nil
}
// GetAIConfigList 获取AI配置列表
func (s *AIConfigService) GetAIConfigList() (*response.AIConfigListResponse, error) {
var configs []app.AIConfig
var total int64
db := global.GVA_DB.Model(&app.AIConfig{})
err := db.Count(&total).Error
if err != nil {
return nil, err
}
err = db.Order("is_default DESC, created_at DESC").Find(&configs).Error
if err != nil {
return nil, err
}
list := make([]response.AIConfigResponse, len(configs))
for i, config := range configs {
list[i] = response.ToAIConfigResponse(&config)
}
return &response.AIConfigListResponse{
List: list,
Total: total,
}, nil
}
// UpdateAIConfig 更新AI配置
func (s *AIConfigService) UpdateAIConfig(id uint, req *request.UpdateAIConfigRequest) error {
var config app.AIConfig
err := global.GVA_DB.First(&config, id).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("配置不存在")
}
return err
}
updates := map[string]interface{}{}
if req.Name != "" {
updates["name"] = req.Name
}
if req.BaseURL != "" {
updates["base_url"] = req.BaseURL
}
// 只有当 API Key 不是脱敏格式时才更新
// 脱敏格式: xxxx****xxxx
if req.APIKey != "" && !isMaskedAPIKey(req.APIKey) {
updates["api_key"] = req.APIKey
}
if req.DefaultModel != "" {
updates["default_model"] = req.DefaultModel
}
if req.Settings != nil {
settingsJSON, _ := json.Marshal(req.Settings)
updates["settings"] = datatypes.JSON(settingsJSON)
}
if req.IsActive != nil {
updates["is_active"] = *req.IsActive
}
if req.IsDefault != nil && *req.IsDefault {
// 如果设置为默认,先取消其他配置的默认状态
global.GVA_DB.Model(&app.AIConfig{}).Where("id != ?", id).Update("is_default", false)
updates["is_default"] = true
}
return global.GVA_DB.Model(&config).Updates(updates).Error
}
// DeleteAIConfig 删除AI配置
func (s *AIConfigService) DeleteAIConfig(id uint) error {
result := global.GVA_DB.Delete(&app.AIConfig{}, id)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return errors.New("配置不存在")
}
return nil
}
// GetModels 获取可用模型列表
func (s *AIConfigService) GetModels(req *request.GetModelsRequest) (*response.GetModelsResponse, error) {
client := &http.Client{Timeout: 20 * time.Second}
// 构建请求
modelsURL := req.BaseURL + "/models"
httpReq, err := http.NewRequest("GET", modelsURL, nil)
if err != nil {
return nil, err
}
// 设置认证头
if req.Provider == "openai" || req.Provider == "custom" {
httpReq.Header.Set("Authorization", "Bearer "+req.APIKey)
} else if req.Provider == "anthropic" {
httpReq.Header.Set("x-api-key", req.APIKey)
httpReq.Header.Set("anthropic-version", "2023-06-01")
}
// 发送请求
resp, err := client.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("请求失败: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("API返回错误 %d: %s", resp.StatusCode, string(body))
}
// 解析响应
var result struct {
Data []struct {
ID string `json:"id"`
Object string `json:"object"`
OwnedBy string `json:"owned_by"`
} `json:"data"`
}
err = json.NewDecoder(resp.Body).Decode(&result)
if err != nil {
return nil, fmt.Errorf("解析响应失败: %v", err)
}
// 转换为模型信息
models := make([]response.ModelInfo, 0, len(result.Data))
for _, model := range result.Data {
models = append(models, response.ModelInfo{
ID: model.ID,
Name: model.ID,
OwnedBy: model.OwnedBy,
})
}
return &response.GetModelsResponse{Models: models}, nil
}
// GetModelsByID 通过配置ID获取模型列表使用数据库中的完整API Key
func (s *AIConfigService) GetModelsByID(id uint) (*response.GetModelsResponse, error) {
var config app.AIConfig
err := global.GVA_DB.First(&config, id).Error
if err != nil {
return nil, errors.New("配置不存在")
}
// 使用数据库中的完整 API Key
req := &request.GetModelsRequest{
Provider: config.Provider,
BaseURL: config.BaseURL,
APIKey: config.APIKey,
}
return s.GetModels(req)
}
// TestAIConfig 测试AI配置
func (s *AIConfigService) TestAIConfig(req *request.TestAIConfigRequest) (*response.TestAIConfigResponse, error) {
startTime := time.Now()
client := &http.Client{Timeout: 60 * time.Second}
// 构建测试请求
var requestBody map[string]interface{}
var endpoint string
if req.Provider == "openai" || req.Provider == "custom" {
endpoint = req.BaseURL + "/chat/completions"
model := req.Model
if model == "" {
model = "gpt-3.5-turbo"
}
requestBody = map[string]interface{}{
"model": model,
"messages": []map[string]string{
{"role": "user", "content": "Hello"},
},
"max_tokens": 10,
}
} else if req.Provider == "anthropic" {
endpoint = req.BaseURL + "/messages"
model := req.Model
if model == "" {
model = "claude-3-haiku-20240307"
}
requestBody = map[string]interface{}{
"model": model,
"messages": []map[string]string{
{"role": "user", "content": "Hello"},
},
"max_tokens": 10,
}
}
bodyBytes, _ := json.Marshal(requestBody)
httpReq, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(bodyBytes))
if err != nil {
return &response.TestAIConfigResponse{
Success: false,
Message: fmt.Sprintf("创建请求失败: %v", err),
}, nil
}
// 设置请求头
httpReq.Header.Set("Content-Type", "application/json")
if req.Provider == "openai" || req.Provider == "custom" {
httpReq.Header.Set("Authorization", "Bearer "+req.APIKey)
} else if req.Provider == "anthropic" {
httpReq.Header.Set("x-api-key", req.APIKey)
httpReq.Header.Set("anthropic-version", "2023-06-01")
}
// 发送请求
resp, err := client.Do(httpReq)
latency := time.Since(startTime).Milliseconds()
if err != nil {
return &response.TestAIConfigResponse{
Success: false,
Message: fmt.Sprintf("连接失败: %v", err),
Latency: latency,
}, nil
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return &response.TestAIConfigResponse{
Success: false,
Message: fmt.Sprintf("API返回错误 %d: %s", resp.StatusCode, string(body)),
Latency: latency,
}, nil
}
return &response.TestAIConfigResponse{
Success: true,
Message: "连接成功AI响应正常",
Latency: latency,
}, nil
}
// TestAIConfigByID 通过ID测试AI配置使用数据库中的完整API Key
func (s *AIConfigService) TestAIConfigByID(id uint) (*response.TestAIConfigResponse, error) {
var config app.AIConfig
err := global.GVA_DB.First(&config, id).Error
if err != nil {
return nil, errors.New("配置不存在")
}
// 使用数据库中的完整 API Key 进行测试
req := &request.TestAIConfigRequest{
Provider: config.Provider,
BaseURL: config.BaseURL,
APIKey: config.APIKey, // 使用完整的 API Key而不是脱敏后的
Model: config.DefaultModel,
}
return s.TestAIConfig(req)
}
// isMaskedAPIKey 检查是否是脱敏的 API Key
func isMaskedAPIKey(apiKey string) bool {
// 脱敏格式: xxxx****xxxx 或 ****
return len(apiKey) > 0 && (apiKey == "****" || (len(apiKey) > 8 && apiKey[4:len(apiKey)-4] == "****"))
}