Просмотр исходного кода

Remove shudao-go-backend directory

Cline 4 дней назад
Родитель
Сommit
cf786f21eb
63 измененных файлов с 0 добавлено и 17769 удалено
  1. 0 36
      shudao-go-backend/README.en.md
  2. 0 39
      shudao-go-backend/README.md
  3. 0 39
      shudao-go-backend/conf/app.conf
  4. 0 39
      shudao-go-backend/conf/app.conf.prod
  5. 0 39
      shudao-go-backend/conf/app.conf.test
  6. 0 9
      shudao-go-backend/controllers/AIPPT.go
  7. 0 2446
      shudao-go-backend/controllers/chat.go
  8. 0 194
      shudao-go-backend/controllers/chroma.go
  9. 0 49
      shudao-go-backend/controllers/exam.go
  10. 0 33
      shudao-go-backend/controllers/frontend.go
  11. 0 972
      shudao-go-backend/controllers/hazard.go
  12. 0 1003
      shudao-go-backend/controllers/liushi.go
  13. 0 141
      shudao-go-backend/controllers/local_auth.go
  14. 0 180
      shudao-go-backend/controllers/points.go
  15. 0 77
      shudao-go-backend/controllers/prompt.go
  16. 0 448
      shudao-go-backend/controllers/report_compat.go
  17. 0 369
      shudao-go-backend/controllers/scene.go
  18. 0 782
      shudao-go-backend/controllers/shudaooss.go
  19. 0 790
      shudao-go-backend/controllers/test.go
  20. 0 466
      shudao-go-backend/controllers/total.go
  21. 0 360
      shudao-go-backend/controllers/tracking.go
  22. BIN
      shudao-go-backend/favicon.ico
  23. 0 48
      shudao-go-backend/go.mod
  24. 0 685
      shudao-go-backend/go.sum
  25. 0 37
      shudao-go-backend/main.go
  26. 0 78
      shudao-go-backend/models/chat.go
  27. 0 51
      shudao-go-backend/models/exam.go
  28. 0 78
      shudao-go-backend/models/mysql.go
  29. 0 46
      shudao-go-backend/models/points_consumption_log.go
  30. 0 13
      shudao-go-backend/models/prompt.go
  31. 0 112
      shudao-go-backend/models/scene.go
  32. 0 12
      shudao-go-backend/models/test.go
  33. 0 108
      shudao-go-backend/models/total.go
  34. 0 35
      shudao-go-backend/models/tracking.go
  35. 0 81
      shudao-go-backend/models/user_data.go
  36. 0 123
      shudao-go-backend/routers/router.go
  37. 0 62
      shudao-go-backend/scripts/init_admin.go
  38. 0 56
      shudao-go-backend/scripts/init_api_mappings.sql
  39. 0 19
      shudao-go-backend/scripts/points_migration.sql
  40. BIN
      shudao-go-backend/static/font/AlibabaPuHuiTi-3-55-Regular.ttf
  41. BIN
      shudao-go-backend/static/image/1.png
  42. 0 1
      shudao-go-backend/static/js/reload.min.js
  43. 0 142
      shudao-go-backend/tests/TTS_TEST_SUMMARY.md
  44. 0 232
      shudao-go-backend/tests/add_data.go
  45. 0 1292
      shudao-go-backend/tests/api_test.go
  46. 0 1868
      shudao-go-backend/tests/api_test.go.backup
  47. 0 62
      shudao-go-backend/tests/config.go
  48. 0 57
      shudao-go-backend/tests/context_test.go
  49. 0 204
      shudao-go-backend/tests/points_property_test.go
  50. 0 770
      shudao-go-backend/tests/test_pages/simple_stream_test.html
  51. 0 844
      shudao-go-backend/tests/test_pages/stream_chat_with_db_test.html
  52. 0 466
      shudao-go-backend/tests/test_pages/stream_test.html
  53. 0 214
      shudao-go-backend/tests/token_calculation_test.go
  54. 0 34
      shudao-go-backend/tests/token_test.go
  55. 0 88
      shudao-go-backend/utils/auth_middleware.go
  56. 0 92
      shudao-go-backend/utils/config.go
  57. 0 74
      shudao-go-backend/utils/crypto.go
  58. 0 101
      shudao-go-backend/utils/jwt.go
  59. 0 606
      shudao-go-backend/utils/prompt_builder.go
  60. 0 79
      shudao-go-backend/utils/response.go
  61. 0 112
      shudao-go-backend/utils/string_match.go
  62. 0 107
      shudao-go-backend/utils/token.go
  63. 0 169
      shudao-go-backend/utils/tools.go

+ 0 - 36
shudao-go-backend/README.en.md

@@ -1,36 +0,0 @@
-# shudao
-
-#### Description
-{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**}
-
-#### Software Architecture
-Software architecture description
-
-#### Installation
-
-1.  xxxx
-2.  xxxx
-3.  xxxx
-
-#### Instructions
-
-1.  xxxx
-2.  xxxx
-3.  xxxx
-
-#### Contribution
-
-1.  Fork the repository
-2.  Create Feat_xxx branch
-3.  Commit your code
-4.  Create Pull Request
-
-
-#### Gitee Feature
-
-1.  You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
-2.  Gitee blog [blog.gitee.com](https://blog.gitee.com)
-3.  Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
-4.  The most valuable open source project [GVP](https://gitee.com/gvp)
-5.  The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
-6.  The most popular members  [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

+ 0 - 39
shudao-go-backend/README.md

@@ -1,39 +0,0 @@
-# shudao
-
-#### 介绍
-{**以下是 Gitee 平台说明,您可以替换此简介**
-Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台
-无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)}
-
-#### 软件架构
-软件架构说明
-
-
-#### 安装教程
-
-1.  xxxx
-2.  xxxx
-3.  xxxx
-
-#### 使用说明
-
-1.  xxxx
-2.  xxxx
-3.  xxxx
-
-#### 参与贡献
-
-1.  Fork 本仓库
-2.  新建 Feat_xxx 分支
-3.  提交代码
-4.  新建 Pull Request
-
-
-#### 特技
-
-1.  使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
-2.  Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
-3.  你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
-4.  [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
-5.  Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
-6.  Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

+ 0 - 39
shudao-go-backend/conf/app.conf

@@ -1,39 +0,0 @@
-appname = shudao-chat-go
-httpport = 22001
-runmode = dev
-
-# ==================== MySQL配置 ====================
-mysqluser = root
-mysqlpass = 88888888
-mysqlurls = 172.16.29.101
-mysqlhttpport = 21000
-mysqldb = shudao
-
-# ==================== AI模型配置 ====================
-# 阿里大模型配置
-qwen3_api_url = http://172.16.35.50:8000
-qwen3_model = Qwen3-30B-A3B-Instruct-2507
-
-# 意图识别模型配置
-intent_api_url = http://172.16.35.50:8000
-intent_model = Qwen2.5-1.5B-Instruct
-
-# ==================== 外部服务配置 ====================
-# YOLO隐患检测服务
-yolo_base_url = http://172.16.35.50:18080
-
-# 知识库搜索API (ChromaDB) - 本地环境内网直连
-search_api_url = http://127.0.0.1:24000/api/search
-heartbeat_api_url = http://127.0.0.1:24000/api/health
-
-# Token验证API (认证网关) - 本地环境
-auth_api_url = http://127.0.0.1:28004/api/auth/verify
-# AI问答服务代理地址
-aichat_api_url = http://127.0.0.1:28002/api/v1
-
-# ==================== OSS存储配置 ====================
-oss_access_key_id = fnyfi2f368pbic74d8ll
-oss_access_key_secret = jgqwk7sirqlz2602x2k7yx2eor0vii19wah6ywlv
-oss_bucket = gdsc-ai-aqzs
-oss_endpoint = http://172.16.17.52:8060
-oss_parse_encrypt_key = jgqwk7sirqlz2602

+ 0 - 39
shudao-go-backend/conf/app.conf.prod

@@ -1,39 +0,0 @@
-appname = shudao-chat-go
-httpport = 22001
-runmode = prod
-
-# ==================== MySQL配置 ====================
-mysqluser = root
-mysqlpass = 88888888
-mysqlurls = 172.16.35.57
-mysqlhttpport = 21000
-mysqldb = shudao
-
-# ==================== AI模型配置 ====================
-# 阿里大模型配置
-qwen3_api_url = http://172.16.35.50:8000
-qwen3_model = Qwen3-30B-A3B-Instruct-2507
-
-# 意图识别模型配置
-intent_api_url = http://172.16.35.50:8000
-intent_model = Qwen2.5-1.5B-Instruct
-
-# ==================== 外部服务配置 ====================
-# YOLO隐患检测服务
-yolo_base_url = http://172.16.35.50:18080
-
-# 知识库搜索API (ChromaDB) - 生产环境内网直连
-search_api_url = http://127.0.0.1:24000/api/search
-heartbeat_api_url = http://127.0.0.1:24000/api/health
-
-# Token验证API (认证网关) - 生产环境
-auth_api_url = http://127.0.0.1:28004/api/auth/verify
-# AI问答服务代理地址
-aichat_api_url = http://127.0.0.1:28002/api/v1
-
-# ==================== OSS存储配置 ====================
-oss_access_key_id = fnyfi2f368pbic74d8ll
-oss_access_key_secret = jgqwk7sirqlz2602x2k7yx2eor0vii19wah6ywlv
-oss_bucket = gdsc-ai-aqzs
-oss_endpoint = http://172.16.17.52:8060
-oss_parse_encrypt_key = jgqwk7sirqlz2602

+ 0 - 39
shudao-go-backend/conf/app.conf.test

@@ -1,39 +0,0 @@
-appname = shudao-chat-go
-httpport = 22001
-runmode = dev
-
-# ==================== MySQL配置 ====================
-mysqluser = root
-mysqlpass = 88888888
-mysqlurls = 172.16.29.101
-mysqlhttpport = 21000
-mysqldb = shudao
-
-# ==================== AI模型配置 ====================
-# 阿里大模型配置
-qwen3_api_url = http://172.16.35.50:8000
-qwen3_model = Qwen3-30B-A3B-Instruct-2507
-
-# 意图识别模型配置
-intent_api_url = http://172.16.35.50:8000
-intent_model = Qwen2.5-1.5B-Instruct
-
-# ==================== 外部服务配置 ====================
-# YOLO隐患检测服务
-yolo_base_url = http://172.16.35.50:18080
-
-# 知识库搜索API (ChromaDB) - 测试环境内网直连
-search_api_url = http://127.0.0.1:24000/api/search
-heartbeat_api_url = http://127.0.0.1:24000/api/health
-
-# Token验证API (认证网关) - 测试环境
-auth_api_url = http://127.0.0.1:28004/api/auth/verify
-# AI问答服务代理地址
-aichat_api_url = http://127.0.0.1:28002/api/v1
-
-# ==================== OSS存储配置 ====================
-oss_access_key_id = fnyfi2f368pbic74d8ll
-oss_access_key_secret = jgqwk7sirqlz2602x2k7yx2eor0vii19wah6ywlv
-oss_bucket = gdsc-ai-aqzs
-oss_endpoint = http://172.16.17.52:8060
-oss_parse_encrypt_key = jgqwk7sirqlz2602

+ 0 - 9
shudao-go-backend/controllers/AIPPT.go

@@ -1,9 +0,0 @@
-package controllers
-
-import (
-	"github.com/beego/beego/v2/server/web"
-)
-
-type AIPPTController struct {
-	web.Controller
-}

+ 0 - 2446
shudao-go-backend/controllers/chat.go

@@ -1,2446 +0,0 @@
-// Package controllers - chat.go
-//
-// ⚠️ DEPRECATED NOTICE (弃用说明)
-// ================================================================================
-// 本文件中的AI对话核心功能已迁移至微服务实现。
-// 当前保留此文件是因为前端部分接口仍依赖这里的路由定义。
-//
-// 迁移状态:
-// - AI对话核心逻辑: ✅ 已迁移至微服务
-// - 辅助接口(历史记录、推荐问题等): ⚠️ 仍在使用中
-//
-// TODO: 待前端完全切换到微服务后,可以移除本文件中已弃用的方法
-// ================================================================================
-package controllers
-
-import (
-	"bufio"
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"io"
-	"net/http"
-	"regexp"
-	"shudao-chat-go/models"
-	"shudao-chat-go/utils"
-	"strings"
-	"time"
-
-	"github.com/beego/beego/v2/server/web"
-)
-
-type ChatController struct {
-	web.Controller
-}
-
-// 阿里大模型聊天函数
-func (c *ChatController) sendQwen3Message(userMessage string, useStream bool) (string, error) {
-	// 从Beego配置读取阿里大模型配置
-	apiURL, err := web.AppConfig.String("qwen3_api_url")
-	if err != nil || apiURL == "" {
-		return "", fmt.Errorf("配置文件中未找到qwen3_api_url")
-	}
-
-	model, err := web.AppConfig.String("qwen3_model")
-	if err != nil || model == "" {
-		return "", fmt.Errorf("配置文件中未找到qwen3_model")
-	}
-
-	// 在用户消息后面添加字数限制要求
-	finalMessage := userMessage
-	// fmt.Println("最终发送的消息:", finalMessage)
-
-	// 创建阿里大模型请求
-	qwen3Request := map[string]interface{}{
-		"model":       model,
-		"stream":      useStream,
-		"temperature": 0.7,
-		"messages": []map[string]string{
-			// {"role": "system", "content": "你是一个乐于助人的助手。"},
-			{"role": "user", "content": finalMessage},
-		},
-	}
-
-	// 序列化请求
-	requestBody, err := json.Marshal(qwen3Request)
-	if err != nil {
-		return "", fmt.Errorf("请求序列化失败: %v", err)
-	}
-
-	// 发送HTTP请求到阿里大模型
-	req, err := http.NewRequest("POST", apiURL+"/v1/chat/completions", bytes.NewBuffer(requestBody))
-	if err != nil {
-		return "", fmt.Errorf("创建HTTP请求失败: %v", err)
-	}
-
-	// 设置请求头
-	req.Header.Set("Content-Type", "application/json")
-
-	// 发送请求
-	client := &http.Client{Timeout: 600 * time.Second}
-	resp, err := client.Do(req)
-	if err != nil {
-		return "", fmt.Errorf("请求发送失败: %v", err)
-	}
-	defer resp.Body.Close()
-
-	// 检查HTTP状态码
-	if resp.StatusCode != http.StatusOK {
-		responseBody, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return "", fmt.Errorf("阿里大模型API错误: 状态码 %d,读取响应失败: %v", resp.StatusCode, err)
-		}
-		return "", fmt.Errorf("阿里大模型API错误: %s", string(responseBody))
-	}
-
-	if useStream {
-		// 处理流式响应
-		// fmt.Println("处理流式响应1111111111")
-		return c.handleStreamResponse(resp)
-	} else {
-		// 处理非流式响应
-		return c.handleNonStreamResponse(resp)
-	}
-}
-
-// 处理流式响应
-func (c *ChatController) handleStreamResponse(resp *http.Response) (string, error) {
-	// 定义流式响应结构
-	type StreamResponse struct {
-		ID      string `json:"id"`
-		Object  string `json:"object"`
-		Created int64  `json:"created"`
-		Model   string `json:"model"`
-		Choices []struct {
-			Index int `json:"index"`
-			Delta struct {
-				Role      string `json:"role,omitempty"`
-				Content   string `json:"content,omitempty"`
-				ToolCalls []struct {
-					Index    int    `json:"index"`
-					ID       string `json:"id"`
-					Type     string `json:"type"`
-					Function struct {
-						Name      string `json:"name"`
-						Arguments string `json:"arguments"`
-					} `json:"function"`
-				} `json:"tool_calls,omitempty"`
-			} `json:"delta"`
-			Logprobs     interface{} `json:"logprobs"`
-			FinishReason *string     `json:"finish_reason"`
-			StopReason   *string     `json:"stop_reason,omitempty"`
-		} `json:"choices"`
-	}
-
-	// 逐行读取流式响应
-	scanner := bufio.NewScanner(resp.Body)
-	var fullContent strings.Builder
-	var firstChunk = true
-
-	for scanner.Scan() {
-		line := scanner.Text()
-
-		// 跳过空行和data:前缀
-		if line == "" || !strings.HasPrefix(line, "data: ") {
-			continue
-		}
-
-		// 移除"data: "前缀
-		data := strings.TrimPrefix(line, "data: ")
-
-		// 检查是否是结束标记
-		if data == "[DONE]" {
-			break
-		}
-
-		// 解析JSON数据
-		var streamResp StreamResponse
-		if err := json.Unmarshal([]byte(data), &streamResp); err != nil {
-			continue // 跳过解析失败的数据
-		}
-
-		// 标记第一个块已处理
-		if firstChunk {
-			firstChunk = false
-		}
-
-		// 处理choices中的内容
-		if len(streamResp.Choices) > 0 {
-			choice := streamResp.Choices[0]
-			if choice.Delta.Content != "" {
-				fullContent.WriteString(choice.Delta.Content)
-			}
-
-			// 检查是否完成
-			if choice.FinishReason != nil {
-				break
-			}
-		}
-	}
-
-	if err := scanner.Err(); err != nil {
-		return "", fmt.Errorf("读取流式响应失败: %v", err)
-	}
-
-	return fullContent.String(), nil
-}
-
-// 处理非流式响应
-func (c *ChatController) handleNonStreamResponse(resp *http.Response) (string, error) {
-	// 定义非流式响应结构(与测试文件中的Qwen3ChatResponse保持一致)
-	type Qwen3ChatResponse struct {
-		ID      string `json:"id"`
-		Object  string `json:"object"`
-		Created int64  `json:"created"`
-		Model   string `json:"model"`
-		Choices []struct {
-			Index   int `json:"index"`
-			Message struct {
-				Role             string        `json:"role"`
-				Content          string        `json:"content"`
-				Refusal          *string       `json:"refusal"`
-				Annotations      *string       `json:"annotations"`
-				Audio            *string       `json:"audio"`
-				FunctionCall     *string       `json:"function_call"`
-				ToolCalls        []interface{} `json:"tool_calls"`
-				ReasoningContent *string       `json:"reasoning_content"`
-			} `json:"message"`
-			Logprobs     *string `json:"logprobs"`
-			FinishReason string  `json:"finish_reason"`
-			StopReason   *string `json:"stop_reason"`
-		} `json:"choices"`
-		ServiceTier       *string `json:"service_tier"`
-		SystemFingerprint *string `json:"system_fingerprint"`
-		Usage             struct {
-			PromptTokens        int     `json:"prompt_tokens"`
-			TotalTokens         int     `json:"total_tokens"`
-			CompletionTokens    int     `json:"completion_tokens"`
-			PromptTokensDetails *string `json:"prompt_tokens_details"`
-		} `json:"usage"`
-		PromptLogprobs   *string `json:"prompt_logprobs"`
-		KvTransferParams *string `json:"kv_transfer_params"`
-	}
-
-	// 读取完整的响应内容
-	responseBody, err := io.ReadAll(resp.Body)
-	if err != nil {
-		return "", fmt.Errorf("读取响应失败: %v", err)
-	}
-
-	// 解析JSON响应
-	var response Qwen3ChatResponse
-	if err := json.Unmarshal(responseBody, &response); err != nil {
-		return "", fmt.Errorf("响应解析失败: %v", err)
-	}
-
-	// 验证响应
-	if response.ID == "" {
-		return "", fmt.Errorf("响应ID为空")
-	}
-	if len(response.Choices) == 0 {
-		return "", fmt.Errorf("响应中没有选择项")
-	}
-
-	return response.Choices[0].Message.Content, nil
-}
-
-// sendIntentMessage 发送意图识别消息到新的模型接口
-func (c *ChatController) sendIntentMessage(userMessage string) (string, error) {
-	// 从Beego配置读取意图识别模型配置
-	apiURL, err := web.AppConfig.String("intent_api_url")
-	if err != nil || apiURL == "" {
-		return "", fmt.Errorf("配置文件中未找到intent_api_url")
-	}
-
-	model, err := web.AppConfig.String("intent_model")
-	if err != nil || model == "" {
-		return "", fmt.Errorf("配置文件中未找到intent_model")
-	}
-
-	// 创建意图识别请求
-	intentRequest := map[string]interface{}{
-		"model":  model,
-		"stream": false,
-		"messages": []map[string]string{
-			{"role": "user", "content": userMessage},
-		},
-	}
-
-	// 序列化请求
-	requestBody, err := json.Marshal(intentRequest)
-	if err != nil {
-		return "", fmt.Errorf("请求序列化失败: %v", err)
-	}
-
-	// 发送HTTP请求到意图识别模型
-	req, err := http.NewRequest("POST", apiURL+"/v1/chat/completions", bytes.NewBuffer(requestBody))
-	if err != nil {
-		return "", fmt.Errorf("创建HTTP请求失败: %v", err)
-	}
-
-	// 设置请求头
-	req.Header.Set("Content-Type", "application/json")
-
-	// 发送请求
-	client := &http.Client{Timeout: 60 * time.Second}
-	resp, err := client.Do(req)
-	if err != nil {
-		return "", fmt.Errorf("请求发送失败: %v", err)
-	}
-	defer resp.Body.Close()
-
-	// 检查HTTP状态码
-	if resp.StatusCode != http.StatusOK {
-		responseBody, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return "", fmt.Errorf("意图识别API错误: 状态码 %d,读取响应失败: %v", resp.StatusCode, err)
-		}
-		return "", fmt.Errorf("意图识别API错误: %s", string(responseBody))
-	}
-
-	// 处理非流式响应
-	return c.handleIntentResponse(resp)
-}
-
-// handleIntentResponse 处理意图识别响应
-func (c *ChatController) handleIntentResponse(resp *http.Response) (string, error) {
-	// 定义意图识别响应结构
-	type IntentResponse struct {
-		ID      string `json:"id"`
-		Object  string `json:"object"`
-		Created int64  `json:"created"`
-		Model   string `json:"model"`
-		Choices []struct {
-			Index   int `json:"index"`
-			Message struct {
-				Role             string        `json:"role"`
-				Content          string        `json:"content"`
-				Refusal          *string       `json:"refusal"`
-				Annotations      *string       `json:"annotations"`
-				Audio            *string       `json:"audio"`
-				FunctionCall     *string       `json:"function_call"`
-				ToolCalls        []interface{} `json:"tool_calls"`
-				ReasoningContent *string       `json:"reasoning_content"`
-			} `json:"message"`
-			Logprobs     *string `json:"logprobs"`
-			FinishReason string  `json:"finish_reason"`
-			StopReason   *string `json:"stop_reason"`
-		} `json:"choices"`
-		ServiceTier       *string `json:"service_tier"`
-		SystemFingerprint *string `json:"system_fingerprint"`
-		Usage             struct {
-			PromptTokens        int     `json:"prompt_tokens"`
-			TotalTokens         int     `json:"total_tokens"`
-			CompletionTokens    int     `json:"completion_tokens"`
-			PromptTokensDetails *string `json:"prompt_tokens_details"`
-		} `json:"usage"`
-		PromptLogprobs   *string `json:"prompt_logprobs"`
-		PromptTokenIds   *string `json:"prompt_token_ids"`
-		KvTransferParams *string `json:"kv_transfer_params"`
-	}
-
-	// 读取完整的响应内容
-	responseBody, err := io.ReadAll(resp.Body)
-	if err != nil {
-		return "", fmt.Errorf("读取响应失败: %v", err)
-	}
-
-	// 解析JSON响应
-	var response IntentResponse
-	if err := json.Unmarshal(responseBody, &response); err != nil {
-		return "", fmt.Errorf("响应解析失败: %v", err)
-	}
-
-	// 验证响应
-	if response.ID == "" {
-		return "", fmt.Errorf("响应ID为空")
-	}
-
-	if len(response.Choices) == 0 {
-		return "", fmt.Errorf("响应中没有选择项")
-	}
-
-	if response.Choices[0].Message.Content == "" {
-		return "", fmt.Errorf("响应内容为空")
-	}
-
-	return response.Choices[0].Message.Content, nil
-}
-
-// estimateTokens 估算文本的token数量(基于Qwen官方BPE分词规则)
-func (c *ChatController) estimateTokens(text string) int {
-	// 基于Qwen官方BPE分词规则的token估算
-	// 根据官方文档:中文字符通常一个汉字对应一个或多个Token
-	// 英文单词通常一个单词或其部分对应一个Token
-
-	// 计算中文字符数量
-	chineseChars := 0
-	englishWords := 0
-	punctuationChars := 0
-	jsonChars := 0
-	whitespaceChars := 0
-
-	// 统计各种字符类型
-	for _, r := range text {
-		if r >= 0x4e00 && r <= 0x9fff {
-			chineseChars++
-		} else if r == '{' || r == '}' || r == '[' || r == ']' || r == '"' || r == ':' || r == ',' {
-			jsonChars++
-		} else if r == '.' || r == ',' || r == ';' || r == '!' || r == '?' || r == ':' || r == '(' || r == ')' {
-			punctuationChars++
-		} else if r == ' ' || r == '\n' || r == '\t' || r == '\r' {
-			whitespaceChars++
-		}
-	}
-
-	// 计算英文单词数量(简单按空格分割)
-	words := strings.Fields(text)
-	for _, word := range words {
-		// 检查是否包含英文字符
-		hasEnglish := false
-		for _, r := range word {
-			if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') {
-				hasEnglish = true
-				break
-			}
-		}
-		if hasEnglish {
-			englishWords++
-		}
-	}
-
-	// 基于Qwen BPE分词规则的token估算:
-	// - 中文字符:每个约1.2-1.5个token(根据官方文档,一个汉字可能对应一个或多个token)
-	// - 英文单词:每个约1-2个token(取决于单词长度和复杂度)
-	// - 标点符号:每个约0.5-1个token
-	// - JSON结构字符:每个约0.5个token
-	// - 空白字符:每个约0.1个token
-	tokens := int(float64(chineseChars)*1.35 + float64(englishWords)*1.5 + float64(punctuationChars)*0.75 + float64(jsonChars)*0.5 + float64(whitespaceChars)*0.1)
-
-	return tokens
-}
-
-// truncateContextToFitTokens 截断context内容以适应token限制
-func (c *ChatController) truncateContextToFitTokens(contextJSON []byte, maxTokens int, promptPrefix string) []byte {
-	// 估算prompt前缀的token数量
-	promptTokens := c.estimateTokens(promptPrefix)
-
-	// 预留一些token给AI回复(大约5000个token,更保守)
-	reservedTokens := 5000
-
-	// 计算可用于context的最大token数量
-	availableTokens := maxTokens - promptTokens - reservedTokens
-
-	if availableTokens <= 0 {
-		// 如果连prompt前缀都不够,返回空context
-		return []byte("[]")
-	}
-
-	// 解析context JSON
-	var results []interface{}
-	if err := json.Unmarshal(contextJSON, &results); err != nil {
-		fmt.Printf("解析context JSON失败: %v\n", err)
-		return contextJSON
-	}
-
-	// 从后往前删除文档,直到满足token限制
-	for len(results) > 0 {
-		// 估算当前context的token数量
-		currentContextJSON, _ := json.Marshal(results)
-		currentTokens := c.estimateTokens(string(currentContextJSON))
-
-		if currentTokens <= availableTokens {
-			fmt.Printf("Context截断完成,最终token数量: %d,文档数量: %d\n", currentTokens, len(results))
-			return currentContextJSON
-		}
-
-		// 尝试截断最后一个文档的内容而不是完全删除
-		if len(results) > 0 {
-			lastDoc := results[len(results)-1]
-			if docMap, ok := lastDoc.(map[string]interface{}); ok {
-				if content, exists := docMap["content"].(string); exists && len(content) > 500 {
-					// 截断文档内容到500字符
-					docMap["content"] = content[:500] + "..."
-					fmt.Printf("截断最后一个文档内容,剩余文档数量: %d\n", len(results))
-					continue
-				}
-			}
-		}
-
-		// 如果无法截断,则删除最后一个文档
-		results = results[:len(results)-1]
-		fmt.Printf("删除一个文档,剩余文档数量: %d\n", len(results))
-	}
-
-	// 如果所有文档都删除了,返回空数组
-	return []byte("[]")
-}
-
-// 发送deepseek消息
-// 构造一下message、ai_conversation_id的结构体(user_id从token中获取)
-type SendDeepSeekMessageRequest struct {
-	Message          string `json:"message"`
-	AIConversationId uint64 `json:"ai_conversation_id"`
-	BusinessType     int    `json:"business_type"`
-	ExamName         string `json:"exam_name"`
-	AIMessageId      uint64 `json:"ai_message_id"`
-}
-
-// cleanNaturalLanguageAnswer 清洗natural_language_answer中的溯源信息
-func (c *ChatController) cleanNaturalLanguageAnswer(naturalAnswer string) string {
-	// 匹配三个规范段落,如果包含"暂未检索"但后面还有来源,就清空来源
-	patterns := []string{
-		`(\*\*1\.\s*国家规范\*\*[^*]*?暂未检索[^*]*?)(\[[^\]]+\])`,
-		`(\*\*2\.\s*地方规范\*\*[^*]*?暂未检索[^*]*?)(\[[^\]]+\])`,
-		`(\*\*3\.\s*企业规范\*\*[^*]*?暂未检索[^*]*?)(\[[^\]]+\])`,
-	}
-
-	cleaned := naturalAnswer
-	for _, pattern := range patterns {
-		re := regexp.MustCompile(pattern)
-		cleaned = re.ReplaceAllStringFunc(cleaned, func(match string) string {
-			// 提取段落内容和来源部分
-			parts := re.FindStringSubmatch(match)
-			if len(parts) >= 3 {
-				// 只保留段落内容,移除来源部分
-				fmt.Printf("清洗了包含'暂未检索'的段落来源: %s\n", parts[2])
-				return parts[1]
-			}
-			return match
-		})
-	}
-
-	return cleaned
-}
-
-// cleanStructuredDataSources 清洗structured_data:如果content包含"暂未检索",清空对应的sources
-func (c *ChatController) cleanStructuredDataSources(aiResponse map[string]interface{}) {
-	if structuredData, ok := aiResponse["structured_data"].(map[string]interface{}); ok {
-		levels := []string{"national_level", "local_level", "enterprise_level"}
-		for _, level := range levels {
-			if levelData, exists := structuredData[level].(map[string]interface{}); exists {
-				if content, ok := levelData["content"].(string); ok {
-					if strings.Contains(content, "暂未检索") {
-						levelData["sources"] = []string{}
-						fmt.Printf("清洗%s的sources,因为content包含'暂未检索'\n", level)
-					}
-				}
-			}
-		}
-	}
-}
-
-// replaceSourcesInNaturalAnswer 使用structured_data中的sources替换natural_language_answer中的溯源信息
-func (c *ChatController) replaceSourcesInNaturalAnswer(naturalAnswer string, aiResponse map[string]interface{}) string {
-	// 获取structured_data
-	structuredData, ok := aiResponse["structured_data"].(map[string]interface{})
-	if !ok {
-		fmt.Printf("structured_data字段不存在或类型错误,返回原始natural_language_answer\n")
-		return naturalAnswer
-	}
-
-	// 创建level到sources的映射
-	levelSources := make(map[string][]string)
-	levels := []string{"national_level", "local_level", "enterprise_level"}
-
-	for _, level := range levels {
-		if levelData, exists := structuredData[level].(map[string]interface{}); exists {
-			if sources, ok := levelData["sources"].([]interface{}); ok {
-				var levelSourcesList []string
-				for _, source := range sources {
-					if sourceStr, ok := source.(string); ok && sourceStr != "" {
-						levelSourcesList = append(levelSourcesList, sourceStr)
-					}
-				}
-				levelSources[level] = levelSourcesList
-			}
-		}
-	}
-
-	// 检查是否有任何有效的sources
-	hasValidSources := false
-	for _, sources := range levelSources {
-		if len(sources) > 0 {
-			hasValidSources = true
-			break
-		}
-	}
-
-	if !hasValidSources {
-		fmt.Printf("未找到有效的sources,返回原始natural_language_answer\n")
-		return naturalAnswer
-	}
-
-	fmt.Printf("找到有效sources: %v\n", levelSources)
-
-	// 第一步:完全删除natural_language_answer中所有的溯源标记
-	result := naturalAnswer
-
-	// 匹配并删除所有方括号中的内容(溯源信息)
-	re := regexp.MustCompile(`\[([^\]]+)\]`)
-	result = re.ReplaceAllString(result, "")
-
-	fmt.Printf("删除所有原始溯源后的内容长度: %d\n", len(result))
-
-	// 第二步:使用简单的字符串分割方法为每个level的段落添加对应的sources
-	levelHeaders := map[string]string{
-		"national_level":   "**1. 国家规范**",
-		"local_level":      "**2. 地方规范**",
-		"enterprise_level": "**3. 企业规范**",
-	}
-
-	// 按双换行符分割段落
-	sections := strings.Split(result, "\n\n")
-	fmt.Printf("总共分割出%d个段落\n", len(sections))
-
-	for level, header := range levelHeaders {
-		if sources, exists := levelSources[level]; exists && len(sources) > 0 {
-			fmt.Printf("处理%s,sources: %v\n", level, sources)
-
-			// 查找包含目标标题的段落
-			for i, section := range sections {
-				if strings.Contains(section, header) {
-					fmt.Printf("找到%s段落%d,长度: %d\n", level, i+1, len(section))
-
-					// 检查段落是否已经包含溯源信息
-					if strings.Contains(section, "[") && strings.Contains(section, "]") {
-						fmt.Printf("%s段落%d已包含溯源信息,跳过\n", level, i+1)
-						continue
-					}
-
-					// 构建sources文本
-					sourceText := ""
-					for _, source := range sources {
-						sourceText += "[" + source + "]"
-					}
-
-					// 在段落末尾添加溯源信息
-					sections[i] = section + "\n" + sourceText
-					fmt.Printf("为%s段落%d添加溯源: %s\n", level, i+1, sourceText)
-					break // 只处理第一个匹配的段落
-				}
-			}
-		} else {
-			fmt.Printf("%s没有sources或sources为空\n", level)
-		}
-	}
-
-	// 重新组合所有段落
-	result = strings.Join(sections, "\n\n")
-	fmt.Printf("溯源替换完成,新长度: %d\n", len(result))
-	return result
-}
-
-func (c *ChatController) SendDeepSeekMessage() {
-	// 从token中获取用户信息
-	userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 401,
-			"msg":        "获取用户信息失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	user_id := uint64(userInfo.ID)
-	if user_id == 0 {
-		user_id = 1
-	}
-
-	// 从请求体获取消息
-	var requestData SendDeepSeekMessageRequest
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "请求数据解析失败",
-		}
-		c.ServeJSON()
-		return
-	}
-	fmt.Println("请求数据:", requestData)
-
-	userMessage := requestData.Message
-	var userMessage1 string
-	userMessage1 = userMessage
-	ai_conversation_id := requestData.AIConversationId
-	tx := models.DB.Begin()
-	if ai_conversation_id == 0 {
-		//新建对话
-		ai_conversation := models.AIConversation{
-			UserId:       user_id,
-			Content:      userMessage,
-			BusinessType: requestData.BusinessType,
-			ExamName:     requestData.ExamName,
-		}
-		if err := tx.Create(&ai_conversation).Error; err != nil {
-			tx.Rollback()
-			c.Data["json"] = map[string]interface{}{
-				"statusCode": 500,
-				"msg":        "新建对话失败: " + err.Error(),
-			}
-			c.ServeJSON()
-			return
-		}
-		ai_conversation_id = uint64(ai_conversation.ID)
-	}
-
-	business_type := requestData.BusinessType
-
-	ai_message := models.AIMessage{
-		UserId:           user_id,
-		Content:          userMessage,
-		Type:             "user",
-		AIConversationId: ai_conversation_id,
-	}
-	if err := tx.Create(&ai_message).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "新建消息失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	//安全培训
-	if business_type == 1 {
-		// Prompt := models.Prompt{}
-		// models.DB.Model(&Prompt).Where("business_type = ? AND is_deleted = ?", business_type, 0).First(&Prompt)
-		//userMessage 去向量数据库取30份文档传入下方的<context>中
-		contextJSON := c.getChromaDBDocumentFunction(userMessage)
-
-		prompt := ``
-
-		userMessage1 = prompt
-	}
-
-	//AI写作
-	if business_type == 2 {
-		contextJSON := c.getChromaDBDocumentFunction(userMessage)
-
-		prompt := ``
-		userMessage1 = prompt
-	}
-
-	//如果是考试工坊则更新ai_conversation表中的content和exam_name
-	if business_type == 3 {
-		if err := tx.Model(&models.AIConversation{}).Where("id = ?", ai_conversation_id).Update("content", userMessage).Update("exam_name", requestData.ExamName).Error; err != nil {
-			tx.Rollback()
-			c.Data["json"] = map[string]interface{}{
-				"statusCode": 500,
-				"msg":        "更新内容失败: " + err.Error(),
-			}
-			c.ServeJSON()
-			return
-		}
-	}
-	var reply string
-	// 使用阿里大模型替代DeepSeek
-	if business_type != 0 {
-		reply, err = c.sendQwen3Message(userMessage1, false) // 使用流式响应
-		if err != nil {
-			tx.Rollback()
-			c.Data["json"] = map[string]interface{}{
-				"statusCode": 500,
-				"msg":        "阿里大模型调用失败: " + err.Error(),
-			}
-			c.ServeJSON()
-			return
-		}
-	} else {
-		//这里写完成呃rag请求逻辑
-		prompt := ``
-		// reply, err = c.sendIntentMessage(prompt) // 使用新的意图识别模型
-		//使用deepseek
-		reply, err = c.sendQwen3Message(prompt, false) // 使用流式响应
-		if err != nil {
-			tx.Rollback()
-			c.Data["json"] = map[string]interface{}{
-				"statusCode": 500,
-				"msg":        "意图识别模型调用失败: " + err.Error(),
-			}
-			c.ServeJSON()
-			return
-		}
-		fmt.Println("reply:", reply)
-
-		// 解析AI返回的JSON响应
-		var aiResponse map[string]interface{}
-		// 清理回复中的换行符和多余空白字符
-		cleanReply := strings.TrimSpace(reply)
-		// 移除可能的markdown代码块标记
-		cleanReply = strings.TrimPrefix(cleanReply, "```json")
-		cleanReply = strings.TrimSuffix(cleanReply, "```")
-		cleanReply = strings.TrimSpace(cleanReply)
-
-		if err := json.Unmarshal([]byte(cleanReply), &aiResponse); err != nil {
-			// 如果解析失败,可能是AI直接返回了文本格式(greeting、faq、out_of_scope)
-			fmt.Printf("JSON解析失败,AI返回了文本格式回复: %s\n", reply)
-			fmt.Printf("清理后回复: %s\n", cleanReply)
-			fmt.Printf("解析错误: %v\n", err)
-
-			// 直接使用AI的原始回复,不做格式检查
-			fmt.Printf("直接使用AI的原始回复\n")
-		} else {
-			intent, ok := aiResponse["intent"].(string)
-
-			if !ok {
-				reply = "解析失败2"
-			} else {
-				// 根据intent类型决定返回内容
-				if intent == "greeting" || intent == "faq" || intent == "out_of_scope" {
-					// 对于greeting、faq、out_of_scope,AI应该直接返回自然回复
-					// 检查是否有direct_answer字段,如果没有则使用原始回复
-					if directAnswer, exists := aiResponse["direct_answer"].(string); exists && directAnswer != "" {
-						reply = directAnswer
-					} else {
-						// 如果没有direct_answer字段,直接使用AI的原始回复
-						fmt.Printf("intent为%s,直接使用AI的原始回复\n", intent)
-					}
-				} else {
-					// reply = "复杂问题,进入下一步"
-					//取出里面的数组search_queries
-					search_queries, ok := aiResponse["search_queries"].([]interface{})
-					if !ok || len(search_queries) == 0 {
-						reply = "解析失败4"
-					} else {
-						// 将search_queries转换为字符串数组
-						var queries []string
-						for _, query := range search_queries {
-							if queryStr, ok := query.(string); ok {
-								queries = append(queries, queryStr)
-							}
-						}
-
-						// 使用第一个查询进行搜索
-						if len(queries) > 0 {
-							// 构建搜索请求
-							searchRequest := map[string]interface{}{
-								"query":     queries[0], // 使用第一个查询
-								"n_results": 25,         // 返回3个结果
-							}
-
-							requestBody, err := json.Marshal(searchRequest)
-							if err != nil {
-								reply = "解析失败5"
-							} else {
-								// 从配置文件中读取搜索API地址
-								searchAPIURL, err := web.AppConfig.String("search_api_url")
-								if err != nil || searchAPIURL == "" {
-									reply = "配置文件中未找到search_api_url"
-								} else {
-									// 发送HTTP请求到本地Python服务
-									req, err := http.NewRequest("POST", searchAPIURL, bytes.NewBuffer(requestBody))
-									if err != nil {
-										reply = "解析失败6"
-									} else {
-										req.Header.Set("Content-Type", "application/json")
-
-										client := &http.Client{Timeout: 30 * time.Second}
-										resp, err := client.Do(req)
-										if err != nil {
-											reply = "解析失败7" + err.Error()
-										} else {
-											defer resp.Body.Close()
-
-											responseBody, err := io.ReadAll(resp.Body)
-											if err != nil {
-												reply = "解析失败8"
-											} else if resp.StatusCode != http.StatusOK {
-												reply = fmt.Sprintf("搜索API错误: 状态码 %d", resp.StatusCode)
-											} else {
-												// 解析搜索响应
-												var searchResponse map[string]interface{}
-												if err := json.Unmarshal(responseBody, &searchResponse); err != nil {
-													reply = "解析失败10"
-												} else {
-													// 检查响应状态
-													// fmt.Println("searchResponse11111111:", searchResponse)
-													status, ok := searchResponse["status"].(string)
-													if !ok || status != "success" {
-														message, _ := searchResponse["message"].(string)
-														reply = fmt.Sprintf("搜索失败: %s", message)
-													} else {
-														// 获取搜索结果
-														results, ok := searchResponse["results"].([]interface{})
-														// fmt.Println("results:", results)
-														if !ok || len(results) == 0 {
-															reply = "未找到相关文档"
-														} else {
-															// 直接将原始搜索结果转换为JSON字符串作为上下文
-															// 获取历史对话(前两轮,如果只有1轮就到1轮,没有就不导入)
-															var historyContext string
-															if ai_conversation_id > 0 {
-																var historyMessages []models.AIMessage
-																// 获取当前对话的历史消息,按时间排序,排除当前消息
-																models.DB.Model(&models.AIMessage{}).
-																	Where("user_id = ? AND ai_conversation_id = ? AND is_deleted = ? AND id < ?",
-																		user_id, ai_conversation_id, 0, ai_message.ID).
-																	Order("updated_at ASC").
-																	Find(&historyMessages)
-
-																// 限制为前两轮对话(每轮包含用户消息和AI回复)
-																if len(historyMessages) > 0 {
-																	// 计算轮数:每2条消息为1轮(用户消息+AI回复)
-																	maxRounds := 2
-																	maxMessages := maxRounds * 2
-
-																	if len(historyMessages) > maxMessages {
-																		historyMessages = historyMessages[len(historyMessages)-maxMessages:]
-																	}
-
-																	// 构建历史对话上下文
-																	historyContext = "\n\n# 历史对话上下文\n"
-																	for _, msg := range historyMessages {
-																		if msg.Type == "user" {
-																			historyContext += "用户: " + msg.Content + "\n"
-																		} else if msg.Type == "ai" {
-																			historyContext += "蜀安AI助手: " + msg.Content + "\n"
-																		}
-																	}
-																	historyContext += "\n"
-																}
-
-																//根据用户意图投入搜索来源数据
-
-															}
-															contextJSON, err := json.Marshal(results)
-															// fmt.Println("contextJSON:", string(contextJSON))
-															// fmt.Println("historyContext:", historyContext)
-															if err != nil {
-																reply = "处理搜索结果失败: " + err.Error()
-															} else {
-																// 获取联网搜索内容
-																onlineSearchContent := c.getOnlineSearchContent(userMessage)
-																// fmt.Println("联网数据:", onlineSearchContent)
-																// 构建新的JSON格式提示词
-																finalPrompt := ``
-																finalReply, err := c.sendQwen3Message(finalPrompt, false) // 使用流式响应
-																if err != nil {
-																	reply = "生成最终回答失败: " + err.Error()
-																} else {
-																	// 解析AI返回的JSON响应
-
-																	fmt.Printf("AI原始回复: %s\n", finalReply)
-
-																	// 尝试清理JSON字符串
-																	cleanedReply := strings.TrimSpace(finalReply)
-																	// 移除可能的markdown代码块标记
-																	cleanedReply = strings.TrimPrefix(cleanedReply, "```json")
-																	cleanedReply = strings.TrimPrefix(cleanedReply, "```")
-																	cleanedReply = strings.TrimSuffix(cleanedReply, "```")
-																	cleanedReply = strings.TrimSpace(cleanedReply)
-
-																	var aiResponse map[string]interface{}
-																	if err := json.Unmarshal([]byte(cleanedReply), &aiResponse); err != nil {
-																		// 如果解析失败,尝试提取natural_language_answer字段的正则表达式
-																		fmt.Printf("JSON解析失败,尝试正则提取: %v\n", err)
-																		if strings.Contains(finalReply, "natural_language_answer") {
-																			// 使用正则表达式提取natural_language_answer的内容
-																			re := regexp.MustCompile(`"natural_language_answer"\s*:\s*"([^"]*(?:\\.[^"]*)*)"`)
-																			matches := re.FindStringSubmatch(finalReply)
-																			if len(matches) > 1 {
-																				naturalAnswer := matches[1]
-																				// 处理转义字符
-																				naturalAnswer = strings.ReplaceAll(naturalAnswer, "\\n", "\n")
-																				naturalAnswer = strings.ReplaceAll(naturalAnswer, "\\\"", "\"")
-																				fmt.Printf("正则提取成功,长度: %d\n", len(naturalAnswer))
-
-																				// 清洗natural_language_answer中的溯源信息
-																				naturalAnswer = c.cleanNaturalLanguageAnswer(naturalAnswer)
-
-																				// 尝试解析structured_data进行溯源替换
-																				var tempResponse map[string]interface{}
-																				if err := json.Unmarshal([]byte(cleanedReply), &tempResponse); err == nil {
-																					correctedAnswer := c.replaceSourcesInNaturalAnswer(naturalAnswer, tempResponse)
-																					reply = correctedAnswer
-																				} else {
-																					reply = naturalAnswer
-																				}
-																			} else {
-																				fmt.Printf("正则提取失败,使用原始回复\n")
-																				reply = finalReply
-																			}
-																		} else {
-																			fmt.Printf("未找到natural_language_answer字段,使用原始回复\n")
-																			reply = finalReply
-																		}
-																	} else {
-																		// 提取natural_language_answer字段
-																		if naturalAnswer, ok := aiResponse["natural_language_answer"].(string); ok {
-																			fmt.Printf("成功提取natural_language_answer,长度: %d\n", len(naturalAnswer))
-
-																			// 清洗natural_language_answer中的溯源信息
-																			naturalAnswer = c.cleanNaturalLanguageAnswer(naturalAnswer)
-
-																			// 使用structured_data中的sources替换natural_language_answer中的溯源信息
-																			correctedAnswer := c.replaceSourcesInNaturalAnswer(naturalAnswer, aiResponse)
-																			reply = correctedAnswer
-
-																		} else {
-																			// 如果字段不存在,使用原始回复
-																			fmt.Printf("natural_language_answer字段不存在或类型错误\n")
-																			reply = finalReply
-
-																		}
-																	}
-																}
-															}
-														}
-													}
-												}
-											}
-										}
-									}
-								}
-							}
-						} else {
-							reply = "未找到有效的查询内容"
-						}
-					}
-				}
-			}
-		}
-	}
-
-	//新建AI回复
-	ai_reply := models.AIMessage{
-		UserId:           user_id,
-		Content:          reply,
-		Type:             "ai",
-		AIConversationId: ai_conversation_id,
-		PrevUserId:       uint64(ai_message.ID),
-	}
-	if err := tx.Create(&ai_reply).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "新建消息失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	//更新AIConversation编辑时间
-	if err := tx.Model(&models.AIConversation{}).Where("id = ?", ai_conversation_id).Update("updated_at", time.Now().Unix()).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "更新编辑时间失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	tx.Commit()
-	// 返回成功响应(保持与原来相同的格式)
-	// fmt.Printf("最终返回的reply内容长度: %d\n", len(reply))
-	// fmt.Printf("最终返回的reply内容: %s\n", reply)
-	// if len(reply) > 100 {
-	// 	fmt.Printf("最终返回的reply前100字符: %s\n", reply[:100])
-	// } else {
-	// 	fmt.Printf("最终返回的reply内容: %s\n", reply)
-	// }
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data": map[string]interface{}{
-			"reply": reply,
-			// "user_message": userMessage,
-			"ai_conversation_id": ai_conversation_id,
-			"ai_message_id":      ai_reply.ID,
-		},
-	}
-	c.ServeJSON()
-}
-
-// 删除对话
-type DeleteConversationRequest struct {
-	AIConversationID uint64 `json:"ai_conversation_id"`
-	AIMessageID      uint64 `json:"ai_message_id"`
-}
-
-func (c *ChatController) DeleteConversation() {
-	var requestData DeleteConversationRequest
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "请求数据解析失败",
-		}
-		c.ServeJSON()
-		return
-	}
-	ai_message_id := requestData.AIMessageID
-	fmt.Println("ai_message_id:", ai_message_id)
-	tx := models.DB.Begin()
-	//这里除了要删除这条ai消息,还要查询到prev_user_id这条消息,并删除
-	if err := tx.Model(&models.AIMessage{}).Where("id = ?", ai_message_id).Update("is_deleted", 1).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "删除消息失败",
-		}
-		c.ServeJSON()
-		return
-	}
-	var ai_message_user models.AIMessage
-	models.DB.Where("id = ?", ai_message_id).First(&ai_message_user)
-	prev_user_id := ai_message_user.PrevUserId
-	if err := tx.Model(&models.AIMessage{}).Where("id = ?", prev_user_id).Update("is_deleted", 1).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "删除消息失败",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	//更新ai_conversation表中的编辑时间
-	ai_conversation_id := ai_message_user.AIConversationId
-	if err := tx.Model(&models.AIConversation{}).Where("id = ?", ai_conversation_id).Update("updated_at", time.Now().Unix()).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "更新编辑时间失败",
-		}
-		c.ServeJSON()
-		return
-	}
-	tx.Commit()
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-	}
-	c.ServeJSON()
-}
-
-// ppt大纲存主表
-type SavePPTOutlineRequest struct {
-	AIConversationID uint64 `json:"ai_conversation_id"`
-	PPTOutline       string `json:"ppt_outline"`
-	PPTContent       string `json:"ppt_content"`
-}
-
-func (c *ChatController) SavePPTOutline() {
-	var requestData SavePPTOutlineRequest
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "请求数据解析失败",
-		}
-		c.ServeJSON()
-		return
-	}
-	ai_conversation_id := requestData.AIConversationID
-	ppt_content := requestData.PPTContent
-	fmt.Println("ppt_content", ppt_content)
-	// ppt_outline := requestData.PPTOutline
-	tx := models.DB.Begin()
-	//更新到AIMessage表中的ppt_content
-	if err := tx.Model(&models.AIMessage{}).Where("ai_conversation_id = ? AND type = 'ai'", ai_conversation_id).Update("content", ppt_content).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "保存ppt内容失败",
-		}
-	}
-	//ai_conversation表中的p更新编辑时间
-	if err := tx.Model(&models.AIConversation{}).Where("id = ?", ai_conversation_id).Update("updated_at", time.Now().Unix()).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "更新编辑时间失败",
-		}
-	}
-	tx.Commit()
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-	}
-	c.ServeJSON()
-}
-
-// 返回历史记录
-func (c *ChatController) GetHistoryRecord() {
-	// 从token中获取用户信息
-	userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
-	fmt.Println("userInfo", userInfo)
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 401,
-			"msg":        "获取用户信息失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	user_id := int64(userInfo.ID)
-	if user_id == 0 {
-		user_id = 1
-	}
-
-	ai_conversation_id, _ := c.GetInt64("ai_conversation_id")
-	business_type, _ := c.GetInt64("business_type")
-	//返回详情
-	if ai_conversation_id > 0 {
-		var ppt_outline string
-		var ppt_json_content string
-		//如果是ppt
-		if business_type == 1 {
-			var ai_conversation models.AIConversation
-			models.DB.Model(&models.AIConversation{}).Where("id = ?", ai_conversation_id).First(&ai_conversation)
-			ppt_outline = ai_conversation.PPTOutline
-			ppt_json_content = ai_conversation.PPTJsonContent
-		}
-
-		var ai_message []models.AIMessage
-		models.DB.Model(&models.AIMessage{}).Where("user_id = ? AND ai_conversation_id = ? AND is_deleted = ?", user_id, ai_conversation_id, 0).Order("updated_at").Find(&ai_message)
-		c.Data["json"] = map[string]interface{}{
-			"statusCode":       200,
-			"msg":              "success",
-			"data":             ai_message,
-			"ppt_outline":      ppt_outline,
-			"ppt_json_content": ppt_json_content,
-		}
-		fmt.Println("ppt_outline", ppt_outline)
-		c.ServeJSON()
-		return
-	}
-
-	// 检查数据库连接
-	sqlDB, err := models.DB.DB()
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "数据库连接失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 测试数据库连接
-	if err := sqlDB.Ping(); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "数据库连接测试失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	var ai_conversation []models.AIConversation
-	models.DB.Model(&models.AIConversation{}).Where("user_id = ? AND is_deleted = ? AND business_type = ?", user_id, 0, business_type).Order("-updated_at").Find(&ai_conversation)
-
-	//计算返回的总共的数据数量
-	var total int64
-	models.DB.Model(&models.AIConversation{}).Where("user_id = ? AND is_deleted = ? AND business_type = ?", user_id, 0, business_type).Count(&total)
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data":       ai_conversation,
-		"total":      total,
-	}
-	c.ServeJSON()
-}
-
-// 点赞和点踩post请求
-func (c *ChatController) LikeAndDislike() {
-	var requestData models.AIMessage
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "请求数据解析失败",
-		}
-		c.ServeJSON()
-		return
-	}
-	id := requestData.ID
-	user_feedback := requestData.UserFeedback
-	tx := models.DB.Begin()
-	if err := tx.Model(&models.AIMessage{}).Where("id = ?", id).Update("user_feedback", user_feedback).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "点赞和点踩失败",
-		}
-		c.ServeJSON()
-		return
-	}
-	tx.Commit()
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-	}
-	c.ServeJSON()
-}
-
-// 直接问问题
-func (c *ChatController) ReProduceSingleQuestion() {
-	// 从请求体获取消息
-	var requestData SendDeepSeekMessageRequest
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "请求数据解析失败",
-		}
-		c.ServeJSON()
-		return
-	}
-	fmt.Println("请求数据:", requestData)
-
-	userMessage := requestData.Message
-
-	// 使用阿里大模型替代DeepSeek
-	reply, err := c.sendQwen3Message(userMessage, false) // 使用流式响应
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "阿里大模型调用失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 返回成功响应(保持与原来相同的格式)
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data": map[string]interface{}{
-			"reply": reply,
-		},
-	}
-	fmt.Println("回复:", reply)
-	c.ServeJSON()
-}
-
-// 猜你想问
-func (c *ChatController) GuessYouWant() {
-	// 从请求体获取消息
-	var requestData SendDeepSeekMessageRequest
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "请求数据解析失败",
-		}
-		c.ServeJSON()
-		return
-	}
-	fmt.Println("请求数据:", requestData)
-
-	userMessage := requestData.Message
-
-	promptWithRules := fmt.Sprintf(`你是蜀道安全管理AI智能助手,请根据用户的问题生成3个相关的后续问题建议(猜你想问)。
-
-## 用户问题
-%s
-
-## 生成问题规则(最高优先级)
-1. 严禁生成任何政治敏感信息,包含重要国家领导人,重要国际事件等
-2. 严禁在生成的问题中包含人名信息,任何人名都不行
-3. 严禁生成色情敏感信息
-4. 严禁生成超长文本,最多只能30个字
-
-## 你的回答(仅输出3个问题,每行一个,或返回空)`, userMessage)
->>>>>>> origin/dev
-	promptWithRules := fmt.Sprintf(`你是蜀道安全管理AI智能助手,请根据用户的问题生成3个相关的后续问题建议(猜你想问)。
-
-## 用户问题
-%s
-
-## 生成问题规则(最高优先级)
-1. 严禁生成任何政治敏感信息,包含重要国家领导人,重要国际事件等
-2. 严禁在生成的问题中包含人名信息,任何人名都不行
-3. 严禁生成色情敏感信息
-4. 严禁生成超长文本,最多只能30个字
-
-## 你的回答(仅输出3个问题,每行一个,或返回空)`, userMessage)
-=======
-	promptWithRules := fmt.Sprintf(`你是蜀道安全管理AI智能助手,请根据用户的问题生成3个相关的后续问题建议(猜你想问)。
-
-## 用户问题
-%s
-
-## 生成问题规则(最高优先级)
-1. 严禁生成任何政治敏感信息,包含重要国家领导人,重要国际事件等
-2. 严禁在生成的问题中包含人名信息,任何人名都不行
-3. 严禁生成色情敏感信息
-4. 严禁生成超长文本,最多只能30个字
-
-## 你的回答(仅输出3个问题,每行一个,或返回空)`, userMessage)
->>>>>>> origin/dev
-
-	// 使用阿里大模型替代DeepSeek
-	reply, err := c.sendQwen3Message(promptWithRules, false)
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "阿里大模型调用失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	ai_message_id := requestData.AIMessageId
-
-	// fmt.Println("猜你想问的ai_message_id", ai_message_id)
-	tx := models.DB.Begin()
-	if err := tx.Model(&models.AIMessage{}).Where("id = ?", ai_message_id).Update("guess_you_want", reply).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "保存猜你想问失败",
-		}
-	}
-	tx.Commit()
-
-	// 返回成功响应(保持与原来相同的格式)
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data": map[string]interface{}{
-			"reply": reply,
-		},
-	}
-	fmt.Println("猜你想问:", reply)
-	c.ServeJSON()
-}
-
-// 用户在输入框中每输入一个字,就调用一次阿里大模型返回推荐问题
-func (c *ChatController) GetUserRecommendQuestion() {
-	// 从token中获取用户信息(GET请求也需要token)
-	userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 401,
-			"msg":        "获取用户信息失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	user_id := int64(userInfo.ID)
-	if user_id == 0 {
-		user_id = 1
-	}
-	userMessage1 := c.GetString("user_message")
-
-	// 直接从QA表中模糊查询问题
-	var qaList []models.QA
-	models.DB.Model(&models.QA{}).Where("question LIKE ? AND is_deleted = ?", "%"+userMessage1+"%", 0).Limit(10).Find(&qaList)
-
-	if len(qaList) == 0 {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 200,
-			"msg":        "success",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 提取问题列表
-	var questions []string
-	for _, qa := range qaList {
-		questions = append(questions, qa.Question)
-	}
-
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data": map[string]interface{}{
-			"questions": questions,
-		},
-	}
-	c.ServeJSON()
-}
-
-// 用户传文件名取数据库寻找链接(使用编辑距离算法匹配最相似的文件名)
-func (c *ChatController) GetFileLink() {
-	fileName := c.GetString("fileName")
-	fmt.Println("查询文件名:", fileName)
-
-	// 获取所有未删除的文件记录
-	var indexFiles []models.IndexFile
-	models.DB.Model(&models.IndexFile{}).Where("is_deleted = ?", 0).Find(&indexFiles)
-
-	if len(indexFiles) == 0 {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 404,
-			"msg":        "数据库中没有找到任何文件",
-			"data":       "",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 提取所有文件名作为候选列表
-	var candidates []string
-	for _, file := range indexFiles {
-		candidates = append(candidates, file.FileName)
-	}
-
-	// 使用编辑距离算法找到最相似的文件名
-	bestMatch, bestScore := utils.FindBestMatch(fileName, candidates)
-	fmt.Printf("最佳匹配: %s (相似度: %.3f)\n", bestMatch, bestScore)
-
-	// 找到对应的文件记录
-	var matchedFile models.IndexFile
-	for _, file := range indexFiles {
-		if file.FileName == bestMatch {
-			matchedFile = file
-			break
-		}
-	}
-
-	fmt.Println("匹配的文件记录:", matchedFile)
-	fmt.Println("文件链接:", matchedFile.FilePath)
-
-	// 如果相似度太低,可以设置阈值
-	threshold := 0.3 // 相似度阈值,可以根据需要调整
-	if bestScore < threshold {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 200,
-			"msg":        fmt.Sprintf("没有找到相似度 >= %.1f 的文件,最佳匹配相似度: %.3f", threshold, bestScore),
-			"data":       "",
-			"bestMatch":  bestMatch,
-			"bestScore":  bestScore,
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 检查文件路径是否已经是代理URL格式,如果不是则转换为代理URL
-	var fileURL string
-	if matchedFile.FilePath != "" {
-		if !strings.Contains(matchedFile.FilePath, "/apiv1/oss/parse/?url=") {
-			fileURL = utils.GetProxyURL(matchedFile.FilePath)
-		} else {
-			fileURL = matchedFile.FilePath
-		}
-	}
-
-	// 返回代理URL
-	fmt.Println("代理URL:", fileURL)
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data":       fileURL,
-		"bestMatch":  bestMatch,
-		"bestScore":  bestScore,
-		"fileName":   fileName,
-	}
-	c.ServeJSON()
-}
-
-// 删除历史记录
-type DeleteHistoryRecordRequest struct {
-	AIConversationID uint64 `json:"ai_conversation_id"`
-}
-
-func (c *ChatController) DeleteHistoryRecord() {
-	var requestData DeleteHistoryRecordRequest
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "请求数据解析失败",
-		}
-		c.ServeJSON()
-		return
-	}
-	ai_conversation_id := requestData.AIConversationID
-	tx := models.DB.Begin()
-	if err := tx.Model(&models.AIConversation{}).Where("id = ?", ai_conversation_id).Update("is_deleted", 1).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "删除历史记录失败",
-		}
-	}
-	tx.Commit()
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-	}
-	c.ServeJSON()
-}
-
-// 删除隐患识别的历史记录
-type DeleteRecognitionRecordRequest struct {
-	RecognitionRecordID uint64 `json:"recognition_record_id"`
-}
-
-func (c *ChatController) DeleteRecognitionRecord() {
-	var requestData DeleteRecognitionRecordRequest
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "请求数据解析失败",
-		}
-		c.ServeJSON()
-		return
-	}
-	recognition_record_id := requestData.RecognitionRecordID
-	tx := models.DB.Begin()
-	if err := tx.Model(&models.RecognitionRecord{}).Where("id = ?", recognition_record_id).Update("is_deleted", 1).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "删除隐患识别的历史记录失败",
-		}
-		c.ServeJSON()
-		return
-	}
-	tx.Commit()
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-	}
-	c.ServeJSON()
-}
-
-// AI写作保存编辑文档内容
-func (c *ChatController) SaveEditDocument() {
-	var requestData models.AIMessage
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "请求数据解析失败",
-		}
-	}
-	ai_conversation_id := requestData.AIConversationId
-	fmt.Println("ai_conversation_id", ai_conversation_id)
-	content := requestData.Content
-
-	tx := models.DB.Begin()
-	if err := tx.Model(&models.AIMessage{}).Where("ai_conversation_id = ? AND type = 'ai' AND is_deleted = ?", ai_conversation_id, 0).Update("content", content).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "保存编辑文档内容失败",
-		}
-	}
-
-	if err := tx.Model(&models.AIConversation{}).Where("id = ?", ai_conversation_id).Update("updated_at", time.Now().Unix()).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "更新编辑时间失败",
-		}
-	}
-	tx.Commit()
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data":       content,
-	}
-	c.ServeJSON()
-}
-
-// 联网搜索
-func (c *ChatController) OnlineSearch() {
-	// 获取请求参数
-	keywords := c.GetString("keywords")
-
-	// 参数验证
-	if keywords == "" {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"error":      "参数错误:keywords不能为空",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 在关键词前加入检索策略提示词:
-	// 1) 若用户意图属于土木工程/路桥隧轨/施工安全等相关领域,则直接按该意图搜索
-	// 2) 若与上述领域无关,则根据用户表达猜测一个最可能的土木工程相关问题再进行搜索
-	combinedKeywords := fmt.Sprintf("【搜索策略】先识别用户意图:若问题属于土木工程/路桥隧轨/施工安全等领域,则按此意图联网搜索;若非上述领域,请根据用户表达猜测一个最可能的土木工程相关问题并据此搜索。确保检索聚焦专业资料。确保回复字数在20字内。【用户问题】%s", keywords)
-
-	//给AI发送消息
-	reply, err := c.sendQwen3Message(combinedKeywords, false)
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "阿里大模型调用失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	fmt.Println("联网搜索回复:", reply)
-
-	// 构建请求体
-	requestBody := map[string]interface{}{
-		"workflow_id": "4wfh1PPDderMtCeb",
-		"inputs": map[string]interface{}{
-			"keywords":     reply,
-			"num":          10, // 默认参数
-			"max_text_len": 150,
-		},
-		"response_mode": "blocking", // 默认参数
-		"user":          "user_001",
-	}
-
-	// 序列化请求体
-	jsonData, err := json.Marshal(requestBody)
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"error":      "请求参数序列化失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 创建HTTP请求
-	req, err := http.NewRequest("POST", utils.GetDifyWorkflowURL(), bytes.NewBuffer(jsonData))
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"error":      "创建请求失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 设置请求头
-	req.Header.Set("Authorization", "Bearer app-55CyO4lmDv1VeXK4QmFpt4ng")
-	req.Header.Set("Content-Type", "application/json")
-
-	// 发送请�?
-	client := &http.Client{Timeout: 30 * time.Second}
-	resp, err := client.Do(req)
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"error":      "请求失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	defer resp.Body.Close()
-
-	// 读取响应
-	responseBody, err := io.ReadAll(resp.Body)
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"error":      "读取响应失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 检查HTTP状态码
-	if resp.StatusCode != http.StatusOK {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"error":      fmt.Sprintf("API请求失败,状态码: %d, 响应: %s", resp.StatusCode, string(responseBody)),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 解析响应
-	var apiResponse map[string]interface{}
-	if err := json.Unmarshal(responseBody, &apiResponse); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"error":      "解析响应失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	fmt.Println("apiResponse", apiResponse)
-	// 检查工作流状态
-	data, ok := apiResponse["data"].(map[string]interface{})
-	if !ok {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"error":      "响应格式错误:缺少data字段",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	status, ok := data["status"].(string)
-	if !ok || status != "succeeded" {
-		errorMsg, _ := data["error"].(string)
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"error":      fmt.Sprintf("工作流执行失败,状态: %s, 错误: %s", status, errorMsg),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 提取results字段
-	outputs, ok := data["outputs"].(map[string]interface{})
-	if !ok {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"error":      "响应格式错误:缺少outputs字段",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 优先:解析 outputs.text(先直接解析;失败时再做清洗重试)
-	var parsedFromText []interface{}
-	if textResult, ok := outputs["text"].(string); ok && textResult != "" {
-		// 1) 直接解析(适配已是标准JSON字符串的场景�?
-		if err := json.Unmarshal([]byte(strings.TrimSpace(textResult)), &parsedFromText); err == nil {
-			c.Data["json"] = map[string]interface{}{
-				"statusCode": 200,
-				"results":    parsedFromText,
-			}
-			c.ServeJSON()
-			return
-		}
-
-		// 2) 清洗再解析(适配Python风格字符串场景)
-		cleaned := strings.ReplaceAll(textResult, "'", "\"")
-		cleaned = strings.ReplaceAll(cleaned, "None", "null")
-		cleaned = strings.ReplaceAll(cleaned, "\\xa0", " ")
-		cleaned = strings.ReplaceAll(cleaned, "\\u0026", "&")
-		if err := json.Unmarshal([]byte(strings.TrimSpace(cleaned)), &parsedFromText); err == nil {
-			c.Data["json"] = map[string]interface{}{
-				"statusCode": 200,
-				"results":    parsedFromText,
-			}
-			c.ServeJSON()
-			return
-		}
-	}
-
-	// 回退:如果存�?outputs.json[0].results,则按旧逻辑返回(字符串化数组)
-	if jsonArray, ok := outputs["json"].([]interface{}); ok && len(jsonArray) > 0 {
-		if firstResult, ok := jsonArray[0].(map[string]interface{}); ok {
-			if results, ok := firstResult["results"].([]interface{}); ok {
-				resultsStr, err := json.Marshal(results)
-				if err != nil {
-					c.Data["json"] = map[string]interface{}{
-						"statusCode": 500,
-						"error":      "结果序列化失败: " + err.Error(),
-					}
-					c.ServeJSON()
-					return
-				}
-
-				c.Data["json"] = map[string]interface{}{
-					"statusCode": 200,
-					"results":    string(resultsStr),
-				}
-				c.ServeJSON()
-				return
-			}
-		}
-	}
-
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 500,
-		"error":      "响应格式错误:无法从outputs.text或outputs.json解析results",
-	}
-	c.ServeJSON()
-}
-
-// 联网搜索结果存入AIMessage表
-func (c *ChatController) SaveOnlineSearchResult() {
-	var requestData models.AIMessage
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "请求数据解析失败",
-		}
-		c.ServeJSON()
-		return
-	}
-	search_source := requestData.SearchSource
-	ai_conversation_id := requestData.AIConversationId
-	id := requestData.ID
-	tx := models.DB.Begin()
-	fmt.Println("search_source", search_source)
-	fmt.Println("ai_conversation_id", ai_conversation_id)
-	fmt.Println("ai_message_id", id)
-	// 更新AIMessage的search_source
-	if err := tx.Model(&models.AIMessage{}).Where("id = ?", id).Update("search_source", search_source).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "保存联网搜索结果失败",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 更新AIConversation的updated_at
-	if err := tx.Model(&models.AIConversation{}).Where("id = ?", ai_conversation_id).Update("updated_at", time.Now().Unix()).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "更新编辑时间失败",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 提交事务
-	if err := tx.Commit().Error; err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "事务提交失败",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-	}
-	c.ServeJSON()
-}
-
-// 意图识别请求结构体(user_id从token中获取)
-type IntentRecognitionRequest struct {
-	Message          string `json:"message"`
-	AIConversationId uint64 `json:"ai_conversation_id"`
-	BusinessType     int    `json:"business_type"`
-}
-
-// 意图识别模型,用于识别用户意图
-func (c *ChatController) IntentRecognition() {
-	// 从token中获取用户信息
-	userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 401,
-			"msg":        "获取用户信息失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	user_id := uint64(userInfo.ID)
-	if user_id == 0 {
-		user_id = 1
-	}
-
-	// 从请求体获取消息
-	var requestData IntentRecognitionRequest
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "请求数据解析失败",
-		}
-		c.ServeJSON()
-		return
-	}
-	fmt.Println("意图识别请求数据:", requestData)
-
-	userMessage := requestData.Message
-	ai_conversation_id := requestData.AIConversationId
-	business_type := requestData.BusinessType
-
-	// 复用意图识别提示词
-	prompt := `
-	`
-
-	// 调用模型
-	reply, err := c.sendQwen3Message(prompt, false)
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "意图识别模型调用失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 清洗与解析
-	cleanReply := strings.TrimSpace(reply)
-	cleanReply = strings.TrimPrefix(cleanReply, "```json")
-	cleanReply = strings.TrimPrefix(cleanReply, "```")
-	cleanReply = strings.TrimSuffix(cleanReply, "```")
-	cleanReply = strings.TrimSpace(cleanReply)
-
-	var aiResponse map[string]interface{}
-	if err := json.Unmarshal([]byte(cleanReply), &aiResponse); err != nil {
-		// 解析失败:将文本包装为标准结构返回
-		aiResponse = map[string]interface{}{
-			"intent":         "faq",
-			"confidence":     0.5,
-			"search_queries": []string{userMessage},
-			"direct_answer":  reply,
-		}
-	}
-
-	fmt.Println("aiResponse:", aiResponse)
-
-	// 获取意图类型
-	intent, ok := aiResponse["intent"].(string)
-	if !ok {
-		intent = "faq"
-	}
-
-	// 根据意图类型处理数据库操作
-	if intent != "query_knowledge_base" {
-		// 对于greeting和faq类型,需要保存到数据库
-		tx := models.DB.Begin()
-
-		// 如果ai_conversation_id为0,新建对话
-		if ai_conversation_id == 0 {
-			ai_conversation := models.AIConversation{
-				UserId:       user_id,
-				Content:      userMessage,
-				BusinessType: business_type,
-			}
-			if err := tx.Create(&ai_conversation).Error; err != nil {
-				tx.Rollback()
-				c.Data["json"] = map[string]interface{}{
-					"statusCode": 500,
-					"msg":        "新建对话失败: " + err.Error(),
-				}
-				c.ServeJSON()
-				return
-			}
-			ai_conversation_id = uint64(ai_conversation.ID)
-		}
-
-		// 保存用户消息
-		ai_message := models.AIMessage{
-			UserId:           user_id,
-			Content:          userMessage,
-			Type:             "user",
-			AIConversationId: ai_conversation_id,
-		}
-		if err := tx.Create(&ai_message).Error; err != nil {
-			tx.Rollback()
-			c.Data["json"] = map[string]interface{}{
-				"statusCode": 500,
-				"msg":        "新建消息失败: " + err.Error(),
-			}
-			c.ServeJSON()
-			return
-		}
-
-		// 获取direct_answer
-		directAnswer := ""
-		if directAnswerValue, exists := aiResponse["direct_answer"].(string); exists {
-			directAnswer = directAnswerValue
-		} else {
-			// 如果没有direct_answer字段,使用AI的原始回复
-			directAnswer = reply
-		}
-
-		// 保存AI回复
-		ai_reply := models.AIMessage{
-			UserId:           user_id,
-			Content:          directAnswer,
-			Type:             "ai",
-			AIConversationId: ai_conversation_id,
-			PrevUserId:       uint64(ai_message.ID),
-		}
-		if err := tx.Create(&ai_reply).Error; err != nil {
-			tx.Rollback()
-			c.Data["json"] = map[string]interface{}{
-				"statusCode": 500,
-				"msg":        "新建AI回复失败: " + err.Error(),
-			}
-			c.ServeJSON()
-			return
-		}
-
-		// 更新AIConversation编辑时间
-		if err := tx.Model(&models.AIConversation{}).Where("id = ?", ai_conversation_id).Update("updated_at", time.Now().Unix()).Error; err != nil {
-			tx.Rollback()
-			c.Data["json"] = map[string]interface{}{
-				"statusCode": 500,
-				"msg":        "更新编辑时间失败: " + err.Error(),
-			}
-			c.ServeJSON()
-			return
-		}
-
-		tx.Commit()
-
-		// 返回成功响应
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 200,
-			"msg":        "success",
-			"data": map[string]interface{}{
-				"intent_result":      aiResponse,
-				"direct_answer":      directAnswer,
-				"ai_conversation_id": ai_conversation_id,
-				"ai_message_id":      ai_reply.ID,
-				"is_online_search":   0, // 不需要联网搜索
-			},
-		}
-	} else {
-		// 对于query_knowledge_base类型,只返回意图识别结果
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 200,
-			"msg":        "success",
-			"data": map[string]interface{}{
-				"intent_result":    aiResponse,
-				"is_online_search": 1, // 需要联网搜索
-			},
-		}
-	}
-
-	c.ServeJSON()
-}
-
-// 获取chromadb的文档
-func (c *ChatController) GetChromaDBDocument() {
-	// 从GET参数获取消息
-	userMessage := c.GetString("message")
-
-	// 构建搜索请求
-	searchRequest := map[string]interface{}{
-		"query":     userMessage,
-		"n_results": 25, // 返回25个结果
-	}
-
-	requestBody, err := json.Marshal(searchRequest)
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "构建搜索请求失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 从配置文件中读取搜索API地址
-	searchAPIURL, err := web.AppConfig.String("search_api_url")
-	if err != nil || searchAPIURL == "" {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "配置文件中未找到search_api_url",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 发送HTTP请求到Chroma搜索服务
-	req, err := http.NewRequest("POST", searchAPIURL, bytes.NewBuffer(requestBody))
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "创建搜索请求失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	req.Header.Set("Content-Type", "application/json")
-
-	client := &http.Client{Timeout: 30 * time.Second}
-	resp, err := client.Do(req)
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "搜索请求失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	defer resp.Body.Close()
-
-	responseBody, err := io.ReadAll(resp.Body)
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "读取搜索响应失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	if resp.StatusCode != http.StatusOK {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        fmt.Sprintf("搜索API错误: 状态码 %d", resp.StatusCode),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 解析搜索响应
-	var searchResponse map[string]interface{}
-	if err := json.Unmarshal(responseBody, &searchResponse); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "解析搜索响应失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 检查响应状态
-	status, ok := searchResponse["status"].(string)
-	if !ok || status != "success" {
-		message, _ := searchResponse["message"].(string)
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        fmt.Sprintf("搜索失败: %s", message),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 获取搜索结果
-	results, ok := searchResponse["results"].([]interface{})
-	if !ok || len(results) == 0 {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 200,
-			"msg":        "success",
-			"data": map[string]interface{}{
-				"reply": "未找到相关文档",
-			},
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 将搜索结果转换为JSON字符串作为上下文
-	contextJSON, err := json.Marshal(results)
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "处理搜索结果失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	fmt.Println("contextJSON:", string(contextJSON))
-
-	// 返回成功响应
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data": map[string]interface{}{
-			"reply": string(contextJSON),
-		},
-	}
-	c.ServeJSON()
-}
-
-// 获取chromadb的函数
-func (c *ChatController) getChromaDBDocumentFunction(userMessage string) string {
-	// 构建搜索请求
-	searchRequest := map[string]interface{}{
-		"query":     userMessage,
-		"n_results": 25, // 返回25个结果
-	}
-
-	requestBody, err := json.Marshal(searchRequest)
-	if err != nil {
-		return "构建搜索请求失败: " + err.Error()
-	}
-
-	// 从配置文件中读取搜索API地址
-	searchAPIURL, err := web.AppConfig.String("search_api_url")
-	if err != nil || searchAPIURL == "" {
-		return "配置文件中未找到search_api_url"
-	}
-
-	// 发送HTTP请求到Chroma搜索服务
-	req, err := http.NewRequest("POST", searchAPIURL, bytes.NewBuffer(requestBody))
-	if err != nil {
-		return "构建搜索请求失败: " + err.Error()
-	}
-
-	req.Header.Set("Content-Type", "application/json")
-
-	client := &http.Client{Timeout: 30 * time.Second}
-	resp, err := client.Do(req)
-	if err != nil {
-
-		return "搜索请求失败: " + err.Error()
-	}
-	defer resp.Body.Close()
-
-	responseBody, err := io.ReadAll(resp.Body)
-	if err != nil {
-		return "读取搜索响应失败: " + err.Error()
-	}
-
-	if resp.StatusCode != http.StatusOK {
-
-		return "搜索API错误: " + resp.Status
-	}
-
-	// 解析搜索响应
-	var searchResponse map[string]interface{}
-	if err := json.Unmarshal(responseBody, &searchResponse); err != nil {
-		return "解析搜索响应失败: " + err.Error()
-	}
-
-	// 检查响应状态
-	status, ok := searchResponse["status"].(string)
-	if !ok || status != "success" {
-		message, _ := searchResponse["message"].(string)
-		return fmt.Sprintf("搜索失败: %s", message)
-	}
-
-	// 获取搜索结果
-	results, ok := searchResponse["results"].([]interface{})
-	if !ok || len(results) == 0 {
-		return "未找到相关文档"
-	}
-
-	// 将搜索结果转换为JSON字符串作为上下文
-	contextJSON, err := json.Marshal(results)
-	if err != nil {
-		return "处理搜索结果失败: " + err.Error()
-	}
-
-	fmt.Println("contextJSON:", string(contextJSON))
-
-	return string(contextJSON)
-}
-
-// getOnlineSearchContent 获取联网搜索内容
-func (c *ChatController) getOnlineSearchContent(userMessage string) string {
-	// 在关键词前加入检索策略提示词:
-	// 1) 若用户意图属于土木工程/路桥隧轨/施工安全等相关领域,则直接按该意图搜索
-	// 2) 若与上述领域无关,则根据用户表达猜测一个最可能的土木工程相关问题再进行搜索
-	combinedKeywords := fmt.Sprintf("【搜索策略】先识别用户意图:若问题属于土木工程/路桥隧轨/施工安全等领域,则按此意图联网搜索;若非上述领域,请根据用户表达猜测一个最可能的土木工程相关问题并据此搜索。确保检索聚焦专业资料。确保回复字数在20字内。【用户问题】%s", userMessage)
-
-	//给AI发送消息
-	reply, err := c.sendQwen3Message(combinedKeywords, false)
-	if err != nil {
-		fmt.Printf("联网搜索AI调用失败: %v\n", err)
-		return ""
-	}
-	fmt.Println("联网搜索回复:", reply)
-
-	// 构建请求体
-	requestBody := map[string]interface{}{
-		"workflow_id": "4wfh1PPDderMtCeb",
-		"inputs": map[string]interface{}{
-			"keywords":     reply,
-			"num":          10, // 默认参数
-			"max_text_len": 150,
-		},
-		"response_mode": "blocking", // 默认参数
-		"user":          "user_001",
-	}
-
-	// 序列化请求体
-	jsonData, err := json.Marshal(requestBody)
-	if err != nil {
-		fmt.Printf("联网搜索请求参数序列化失败: %v\n", err)
-		return ""
-	}
-
-	// 创建HTTP请求
-	req, err := http.NewRequest("POST", utils.GetDifyWorkflowURL(), bytes.NewBuffer(jsonData))
-	if err != nil {
-		fmt.Printf("联网搜索创建请求失败: %v\n", err)
-		return ""
-	}
-
-	// 设置请求头
-	req.Header.Set("Authorization", "Bearer app-55CyO4lmDv1VeXK4QmFpt4ng")
-	req.Header.Set("Content-Type", "application/json")
-
-	// 发送请求
-	client := &http.Client{Timeout: 30 * time.Second}
-	resp, err := client.Do(req)
-	if err != nil {
-		fmt.Printf("联网搜索请求失败: %v\n", err)
-		return ""
-	}
-	defer resp.Body.Close()
-
-	// 读取响应
-	responseBody, err := io.ReadAll(resp.Body)
-	if err != nil {
-		fmt.Printf("联网搜索读取响应失败: %v\n", err)
-		return ""
-	}
-
-	// 检查HTTP状态码
-	if resp.StatusCode != http.StatusOK {
-		fmt.Printf("联网搜索API请求失败,状态码: %d, 响应: %s\n", resp.StatusCode, string(responseBody))
-		return ""
-	}
-
-	// 解析响应
-	var apiResponse map[string]interface{}
-	if err := json.Unmarshal(responseBody, &apiResponse); err != nil {
-		fmt.Printf("联网搜索解析响应失败: %v\n", err)
-		return ""
-	}
-
-	fmt.Println("联网搜索apiResponse", apiResponse)
-	// 检查工作流状态
-	data, ok := apiResponse["data"].(map[string]interface{})
-	if !ok {
-		fmt.Printf("联网搜索响应格式错误:缺少data字段\n")
-		return ""
-	}
-
-	status, ok := data["status"].(string)
-	if !ok || status != "succeeded" {
-		errorMsg, _ := data["error"].(string)
-		fmt.Printf("联网搜索工作流执行失败,状态: %s, 错误: %s\n", status, errorMsg)
-		return ""
-	}
-
-	// 提取results字段
-	outputs, ok := data["outputs"].(map[string]interface{})
-	if !ok {
-		fmt.Printf("联网搜索响应格式错误:缺少outputs字段\n")
-		return ""
-	}
-
-	// 优先:解析 outputs.text(先直接解析;失败时再做清洗重试)
-	var parsedFromText []interface{}
-	if textResult, ok := outputs["text"].(string); ok && textResult != "" {
-		// 1) 直接解析(适配已是标准JSON字符串的场景)
-		if err := json.Unmarshal([]byte(strings.TrimSpace(textResult)), &parsedFromText); err == nil {
-			// 将联网搜索结果转换为字符串格式
-			onlineSearchStr := "\n\n# 联网搜索内容\n"
-			for i, result := range parsedFromText {
-				if resultMap, ok := result.(map[string]interface{}); ok {
-					onlineSearchStr += fmt.Sprintf("联网搜索结果%d: %v\n", i+1, resultMap)
-				}
-			}
-			return onlineSearchStr
-		}
-
-		// 2) 清洗再解析(适配Python风格字符串场景)
-		cleaned := strings.ReplaceAll(textResult, "'", "\"")
-		cleaned = strings.ReplaceAll(cleaned, "None", "null")
-		cleaned = strings.ReplaceAll(cleaned, "\\xa0", " ")
-		cleaned = strings.ReplaceAll(cleaned, "\\u0026", "&")
-		if err := json.Unmarshal([]byte(strings.TrimSpace(cleaned)), &parsedFromText); err == nil {
-			// 将联网搜索结果转换为字符串格式
-			onlineSearchStr := "\n\n# 联网搜索内容\n"
-			for i, result := range parsedFromText {
-				if resultMap, ok := result.(map[string]interface{}); ok {
-					onlineSearchStr += fmt.Sprintf("联网搜索结果%d: %v\n", i+1, resultMap)
-				}
-			}
-			return onlineSearchStr
-		}
-	}
-
-	// 回退:如果存在outputs.json[0].results,则按旧逻辑返回(字符串化数组)
-	if jsonArray, ok := outputs["json"].([]interface{}); ok && len(jsonArray) > 0 {
-		if firstResult, ok := jsonArray[0].(map[string]interface{}); ok {
-			if results, ok := firstResult["results"].([]interface{}); ok {
-				// 将联网搜索结果转换为字符串格式
-				onlineSearchStr := "\n\n# 联网搜索内容\n"
-				for i, result := range results {
-					if resultMap, ok := result.(map[string]interface{}); ok {
-						onlineSearchStr += fmt.Sprintf("联网搜索结果%d: %v\n", i+1, resultMap)
-					}
-				}
-				return onlineSearchStr
-			}
-		}
-	}
-
-	fmt.Printf("联网搜索响应格式错误:无法从outputs.text或outputs.json解析results\n")
-	return ""
-}

+ 0 - 194
shudao-go-backend/controllers/chroma.go

@@ -1,194 +0,0 @@
-package controllers
-
-import (
-	"encoding/json"
-	"fmt"
-	"io"
-	"net/http"
-	"net/url"
-	"shudao-chat-go/utils"
-	"strconv"
-	"time"
-
-	"github.com/beego/beego/v2/server/web"
-)
-
-// chromaSearchHeartbeatTask 心跳任务
-type chromaSearchHeartbeatTask struct {
-	url        string
-	interval   time.Duration
-	httpClient *http.Client
-	stopChan   chan struct{}
-}
-
-// ChromaController 知识库搜索控制器
-type ChromaController struct {
-	web.Controller
-}
-
-// SearchRequest 搜索请求结构体
-type SearchRequest struct {
-	QueryStr string `json:"query_str"`
-	NResults int    `json:"n_results"`
-}
-
-// SearchResponse 搜索响应结构体
-type SearchResponse struct {
-	Total int        `json:"total"`
-	Files []FileInfo `json:"files"`
-}
-
-// FileInfo 文件信息结构体
-type FileInfo struct {
-	DocumentID string `json:"document_id"`
-	FileName   string `json:"file_name"`
-	FilePath   string `json:"file_path"`
-	SourceFile string `json:"source_file"`
-	HasTitle   bool   `json:"has_title"`
-}
-
-// AdvancedSearch 知识库文件高级搜索接口
-func (c *ChromaController) AdvancedSearch() {
-	// 获取查询参数
-	queryStr := c.GetString("query_str")
-	nResultsStr := c.GetString("n_results", "50")
-
-	// 参数验证
-	if queryStr == "" {
-		c.Data["json"] = map[string]interface{}{
-			"code":    400,
-			"message": "查询字符串不能为空",
-			"data":    nil,
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 转换n_results参数
-	nResults, err := strconv.Atoi(nResultsStr)
-	if err != nil || nResults <= 0 {
-		nResults = 50 // 默认值
-	}
-
-	// 构建请求参数
-	searchReq := SearchRequest{
-		QueryStr: queryStr,
-		NResults: nResults,
-	}
-
-	// 调用外部API
-	result, err := c.callAdvancedSearchAPI(searchReq)
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"code":    500,
-			"message": fmt.Sprintf("搜索失败: %v", err),
-			"data":    nil,
-		}
-		c.ServeJSON()
-		return
-	}
-	fmt.Println("知识库:", result)
-	// 返回成功结果
-	c.Data["json"] = map[string]interface{}{
-		"code":    200,
-		"message": "搜索成功",
-		"data":    result,
-	}
-	c.ServeJSON()
-}
-
-// callAdvancedSearchAPI 调用外部高级搜索API
-func (c *ChromaController) callAdvancedSearchAPI(req SearchRequest) (*SearchResponse, error) {
-	// 从配置文件获取API URL
-	apiURL := utils.GetKnowledgeSearchURL()
-
-	// 构建查询参数
-	params := url.Values{}
-	params.Add("query_str", req.QueryStr)
-	params.Add("n_results", strconv.Itoa(req.NResults))
-
-	// 创建HTTP请求
-	fullURL := apiURL + "?" + params.Encode()
-	httpReq, err := http.NewRequest("GET", fullURL, nil)
-	if err != nil {
-		return nil, fmt.Errorf("创建请求失败: %v", err)
-	}
-
-	// 设置请求头
-	httpReq.Header.Set("Content-Type", "application/json")
-	httpReq.Header.Set("User-Agent", "shudao-chat-go/1.0")
-
-	// 创建HTTP客户端
-	client := &http.Client{
-		Timeout: 30 * time.Second,
-	}
-
-	// 发送请求
-	resp, err := client.Do(httpReq)
-	if err != nil {
-		return nil, fmt.Errorf("请求失败: %v", err)
-	}
-	defer resp.Body.Close()
-
-	// 检查响应状态码
-	if resp.StatusCode != http.StatusOK {
-		return nil, fmt.Errorf("API返回错误状态码: %d", resp.StatusCode)
-	}
-
-	// 读取响应体
-	body, err := io.ReadAll(resp.Body)
-	if err != nil {
-		return nil, fmt.Errorf("读取响应失败: %v", err)
-	}
-
-	// 解析响应JSON
-	var searchResp SearchResponse
-	err = json.Unmarshal(body, &searchResp)
-	if err != nil {
-		return nil, fmt.Errorf("解析响应JSON失败: %v", err)
-	}
-
-	return &searchResp, nil
-}
-
-// StartChromaSearchHeartbeatTask 启动ChromaDB搜索服务心跳任务
-func StartChromaSearchHeartbeatTask() {
-	heartbeatURL := utils.GetConfigString("heartbeat_api_url", "")
-	if heartbeatURL == "" {
-		return
-	}
-
-	task := &chromaSearchHeartbeatTask{
-		url:        heartbeatURL,
-		interval:   10 * time.Minute,
-		httpClient: &http.Client{Timeout: 30 * time.Second},
-		stopChan:   make(chan struct{}),
-	}
-
-	go task.run()
-}
-
-func (h *chromaSearchHeartbeatTask) run() {
-	ticker := time.NewTicker(h.interval)
-	defer ticker.Stop()
-
-	h.sendHeartbeat()
-
-	for {
-		select {
-		case <-h.stopChan:
-			return
-		case <-ticker.C:
-			h.sendHeartbeat()
-		}
-	}
-}
-
-func (h *chromaSearchHeartbeatTask) sendHeartbeat() {
-	resp, err := h.httpClient.Get(h.url)
-	if err != nil {
-		return
-	}
-	defer resp.Body.Close()
-	io.ReadAll(resp.Body)
-}

+ 0 - 49
shudao-go-backend/controllers/exam.go

@@ -1,49 +0,0 @@
-package controllers
-
-import (
-	"encoding/json"
-	"shudao-chat-go/models"
-	"github.com/beego/beego/v2/server/web"
-)
-
-type ReModifyQuestionRequest struct {
-	AIConversationID uint64 `json:"ai_conversation_id"`
-	Content string `json:"content"`
-}
-
-type ExamController struct {
-	web.Controller
-}
-
-//重新修改考试题目
-func (c *ExamController) ReModifyQuestion() {
-	//post请求获取ai_conversation_id
-	var AIConversationID ReModifyQuestionRequest
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &AIConversationID); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "JSON解析失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	ai_conversation_id := AIConversationID.AIConversationID
-	content := AIConversationID.Content
-	//修改ai_message表中的ai_conversation_id中的"ai"回答的content
-	tx := models.DB.Begin()
-	if err := tx.Model(&models.AIMessage{}).Where("ai_conversation_id = ? AND type = 'ai'", ai_conversation_id).Update("content", content).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "修改失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	tx.Commit()
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-	}
-	c.ServeJSON()
-}

+ 0 - 33
shudao-go-backend/controllers/frontend.go

@@ -1,33 +0,0 @@
-package controllers
-
-import (
-	"github.com/beego/beego/v2/server/web"
-)
-
-type FrontendController struct {
-	web.Controller
-}
-
-// Index 处理前端首页
-func (c *FrontendController) Index() {
-	c.TplName = "index.html"
-	c.Render()
-}
-
-// StreamTest 流式接口测试页面
-func (c *FrontendController) StreamTest() {
-	c.TplName = "stream_test.html"
-	c.Render()
-}
-
-// SimpleStreamTest 简化版流式接口测试页面
-func (c *FrontendController) SimpleStreamTest() {
-	c.TplName = "simple_stream_test.html"
-	c.Render()
-}
-
-// StreamChatWithDBTest 流式聊天数据库集成测试页面
-func (c *FrontendController) StreamChatWithDBTest() {
-	c.TplName = "stream_chat_with_db_test.html"
-	c.Render()
-}

+ 0 - 972
shudao-go-backend/controllers/hazard.go

@@ -1,972 +0,0 @@
-package controllers
-
-import (
-	"bytes"
-	"encoding/base64"
-	"encoding/json"
-	"fmt"
-	"image"
-	"image/color"
-	"image/draw"
-	_ "image/gif"
-	"image/jpeg"
-	"image/png"
-	"io"
-	"net/http"
-	"net/url"
-	"os"
-	"shudao-chat-go/models"
-	"shudao-chat-go/utils"
-	"strings"
-	"time"
-
-	"github.com/aws/aws-sdk-go/aws"
-	"github.com/aws/aws-sdk-go/aws/credentials"
-	"github.com/aws/aws-sdk-go/aws/session"
-	"github.com/aws/aws-sdk-go/service/s3"
-	"github.com/beego/beego/v2/server/web"
-	"github.com/fogleman/gg"
-)
-
-type HazardController struct {
-	web.Controller
-}
-
-// 图片压缩配置 - 使用shudaooss.go中的配置
-
-type HazardRequest struct {
-	//场景名称
-	SceneName string `json:"scene_name"`
-	//图片
-	Image string `json:"image"`
-	// 用户ID、手机号、用户姓名从token中获取
-	//日期
-	Date string `json:"date"`
-}
-
-// YOLOResponse 定义YOLO响应结构
-type YOLOResponse struct {
-	Boxes     [][]float64 `json:"boxes"`
-	Labels    []string    `json:"labels"`
-	Scores    []float64   `json:"scores"`
-	ModelType string      `json:"model_type"`
-}
-
-// HazardResponse 定义隐患识别响应结构
-type HazardResponse struct {
-	Code int                    `json:"code"`
-	Msg  string                 `json:"msg"`
-	Data map[string]interface{} `json:"data"`
-}
-
-// 隐患识别
-func (c *HazardController) Hazard() {
-	// 从token中获取用户信息
-	userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
-	if err != nil {
-		c.Data["json"] = HazardResponse{
-			Code: 401,
-			Msg:  "获取用户信息失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	var requestData HazardRequest
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
-		c.Data["json"] = HazardResponse{
-			Code: 400,
-			Msg:  "参数错误",
-		}
-		c.ServeJSON()
-		return
-	}
-	fmt.Println("requestData", requestData)
-
-	// 验证参数
-	if requestData.SceneName == "" || requestData.Image == "" {
-		c.Data["json"] = HazardResponse{
-			Code: 400,
-			Msg:  "场景名称和图片链接不能为空",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 从OSS下载图片
-	imageData, err := downloadImageFromOSS(requestData.Image)
-	if err != nil {
-		c.Data["json"] = HazardResponse{
-			Code: 500,
-			Msg:  "下载图片失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 验证图片数据
-	if len(imageData) == 0 {
-		c.Data["json"] = HazardResponse{
-			Code: 500,
-			Msg:  "下载的图片数据为空",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	fmt.Printf("下载图片成功,大小: %d 字节\n", len(imageData))
-
-	// 将图片转换为base64
-	imageBase64 := base64.StdEncoding.EncodeToString(imageData)
-
-	// 获取YOLO API配置
-	yoloBaseURL := utils.GetYOLOBaseURL()
-
-	// 构建YOLO请求
-	yoloRequest := map[string]interface{}{
-		"modeltype":      requestData.SceneName,
-		"image":          imageBase64,
-		"conf_threshold": 0.5, // 默认置信度
-	}
-
-	jsonData, err := json.Marshal(yoloRequest)
-	if err != nil {
-		c.Data["json"] = HazardResponse{
-			Code: 500,
-			Msg:  "序列化请求失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 调用YOLO API
-	resp, err := http.Post(
-		fmt.Sprintf("%s/predict", yoloBaseURL),
-		"application/json",
-		bytes.NewBuffer(jsonData),
-	)
-	if err != nil {
-		c.Data["json"] = HazardResponse{
-			Code: 500,
-			Msg:  "调用YOLO API失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	defer resp.Body.Close()
-
-	// 读取响应
-	body, err := io.ReadAll(resp.Body)
-	if err != nil {
-		c.Data["json"] = HazardResponse{
-			Code: 500,
-			Msg:  "读取YOLO响应失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	fmt.Println("body111", string(body))
-
-	if resp.StatusCode != http.StatusOK {
-		c.Data["json"] = HazardResponse{
-			Code: resp.StatusCode,
-			Msg:  "YOLO API返回错误: " + string(body),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 解析YOLO响应
-	var yoloResp YOLOResponse
-	if err := json.Unmarshal(body, &yoloResp); err != nil {
-		c.Data["json"] = HazardResponse{
-			Code: 500,
-			Msg:  "解析YOLO响应失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	fmt.Println("yoloResp222", yoloResp)
-	//如果labels为空,则返回错误
-	if len(yoloResp.Labels) == 0 {
-		c.Data["json"] = HazardResponse{
-			Code: 500,
-			Msg:  "当前照片与场景不匹配",
-		}
-		c.ServeJSON()
-		return
-	}
-	// 构建返回数据
-	detectionResults := make([]map[string]interface{}, 0)
-	for i, label := range yoloResp.Labels {
-		if i < len(yoloResp.Scores) && i < len(yoloResp.Boxes) {
-			result := map[string]interface{}{
-				"label":   label,
-				"score":   yoloResp.Scores[i],
-				"box":     yoloResp.Boxes[i],
-				"percent": fmt.Sprintf("%.2f%%", yoloResp.Scores[i]*100),
-			}
-			detectionResults = append(detectionResults, result)
-		}
-	}
-
-	// 生成标注后的图片并上传到OSS
-	var annotatedImageURL string
-	if len(detectionResults) > 0 {
-		// 解码原始图片
-		fmt.Printf("开始解码图片,数据大小: %d 字节\n", len(imageData))
-
-		// 检查图片格式
-		img, format, err := image.Decode(bytes.NewReader(imageData))
-		if err != nil {
-			fmt.Printf("图片解码失败: %v\n", err)
-			fmt.Printf("图片数据前20字节: %x\n", imageData[:min(20, len(imageData))])
-
-			// 尝试手动检测PNG格式并转换
-			if len(imageData) >= 8 && string(imageData[:8]) == "\x89PNG\r\n\x1a\n" {
-				fmt.Printf("检测到PNG格式,尝试特殊处理...\n")
-
-				// 尝试使用PNG解码器
-				img, err = decodePNGImage(imageData)
-				if err != nil {
-					fmt.Printf("PNG特殊解码也失败: %v\n", err)
-					c.Data["json"] = HazardResponse{
-						Code: 500,
-						Msg:  "PNG图片解码失败: " + err.Error() + ",请检查图片是否损坏",
-					}
-					c.ServeJSON()
-					return
-				}
-				format = "png"
-				fmt.Printf("PNG特殊解码成功\n")
-			} else {
-				c.Data["json"] = HazardResponse{
-					Code: 500,
-					Msg:  "解码图片失败: " + err.Error() + ",请检查图片格式是否支持(JPEG、PNG、GIF等)",
-				}
-				c.ServeJSON()
-				return
-			}
-		}
-
-		fmt.Printf("图片解码成功,格式: %s,尺寸: %dx%d\n", format, img.Bounds().Dx(), img.Bounds().Dy())
-
-		// 绘制边界框 - 使用token中的用户信息
-		annotatedImg := drawBoundingBox(img, yoloResp.Boxes, yoloResp.Labels, yoloResp.Scores, userInfo.Name, userInfo.ContactNumber, requestData.Date)
-
-		// 将标注后的图片编码为JPEG
-		var buf bytes.Buffer
-		if err := jpeg.Encode(&buf, annotatedImg, &jpeg.Options{Quality: 95}); err != nil {
-			c.Data["json"] = HazardResponse{
-				Code: 500,
-				Msg:  "编码标注图片失败: " + err.Error(),
-			}
-			c.ServeJSON()
-			return
-		}
-
-		// 生成文件名
-		utcNow := time.Now().UTC()
-		timestamp := utcNow.Unix()
-		fileName := fmt.Sprintf("hazard_annotated/%d/%s_%d.jpg",
-			utcNow.Year(),
-			utcNow.Format("0102"),
-			timestamp)
-
-		// 上传标注后的图片到OSS
-		annotatedImageURL, err = uploadImageToOSS(buf.Bytes(), fileName)
-		if err != nil {
-			c.Data["json"] = HazardResponse{
-				Code: 500,
-				Msg:  "上传标注图片到OSS失败: " + err.Error(),
-			}
-			c.ServeJSON()
-			return
-		}
-
-		fmt.Printf("标注图片上传成功: %s\n", fileName)
-	}
-
-	// fmt.Println("annotatedImageURL", annotatedImageURL)
-	// 使用token中的user_id
-	user_id := int(userInfo.ID)
-	if user_id == 0 {
-		user_id = 1
-	}
-
-	//查询场景名称
-	var scene models.Scene
-	models.DB.Where("scene_en_name = ? and is_deleted = ?", requestData.SceneName, 0).First(&scene)
-	if scene.ID == 0 {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "场景名称不存在",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	var tx = models.DB.Begin()
-	recognitionRecord := &models.RecognitionRecord{
-		OriginalImageUrl:    requestData.Image,
-		RecognitionImageUrl: annotatedImageURL,
-		UserID:              user_id,
-		Labels:              removeDuplicateLabels(yoloResp.Labels), // 存储YOLO识别的标签(去重)
-		Title:               scene.SceneName,
-		TagType:             requestData.SceneName,
-		SecondScene:         "", // 默认空字符串
-		ThirdScene:          "", // 默认空字符串
-	}
-	if err := tx.Create(recognitionRecord).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "识别失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	//获取labels对应的二级场景和三级场景
-	var thirdSceneNames []string
-	var displayLabels []string
-	elementHazards := make(map[string][]string)
-	fmt.Println("yoloResp.Labels", yoloResp.Labels)
-	for _, label := range yoloResp.Labels {
-		processedLabel := normalizeSceneLabel(label, scene.SceneName)
-		displayLabels = append(displayLabels, processedLabel)
-
-		var secondScene models.SecondScene
-		models.DB.Where("second_scene_name = ? and is_deleted = ?", processedLabel, 0).First(&secondScene)
-		if secondScene.ID != 0 {
-			tx.Create(&models.RecognitionRecordSecondScene{
-				RecognitionRecordID: int(recognitionRecord.ID),
-				SecondSceneID:       int(secondScene.ID),
-			})
-
-			// 通过secondScene.ID查询ThirdScene
-			var thirdScene []models.ThirdScene
-			models.DB.Where("second_scene_id = ? and is_deleted = ?", secondScene.ID, 0).Find(&thirdScene)
-			if len(thirdScene) > 0 {
-				var currentElementHazards []string
-				for _, thirdScene := range thirdScene {
-					thirdSceneNames = append(thirdSceneNames, thirdScene.ThirdSceneName)
-					currentElementHazards = append(currentElementHazards, thirdScene.ThirdSceneName)
-				}
-				elementHazards[processedLabel] = removeDuplicates(
-					append(elementHazards[processedLabel], currentElementHazards...),
-				)
-			} else if _, exists := elementHazards[processedLabel]; !exists {
-				elementHazards[processedLabel] = []string{}
-			}
-		}
-	}
-
-	//对thirdSceneNames去重
-	thirdSceneNames = removeDuplicates(thirdSceneNames)
-	displayLabels = removeDuplicates(displayLabels)
-
-	// 将三级场景名称数组更新到recognitionRecord的Description
-	if len(thirdSceneNames) > 0 {
-		// 将数组转换为空格分隔的字符串
-		description := strings.Join(thirdSceneNames, " ")
-		tx.Model(recognitionRecord).Update("Description", description)
-	}
-
-	tx.Commit()
-	fmt.Println("thirdSceneNames", thirdSceneNames)
-	// 返回成功响应
-	c.Data["json"] = HazardResponse{
-		Code: 200,
-		Msg:  "识别成功",
-		Data: map[string]interface{}{
-			"scene_name":       requestData.SceneName,
-			"total_detections": len(detectionResults),
-			"detections":       detectionResults,
-			"model_type":       yoloResp.ModelType,
-			"original_image":   requestData.Image,
-			"annotated_image":  annotatedImageURL, //前端用这个预链接渲染
-			"labels":           strings.Join(yoloResp.Labels, ", "),
-			"display_labels":   displayLabels,
-			"third_scenes":     thirdSceneNames, //三级场景名称数组
-			"element_hazards":  elementHazards,
-		},
-	}
-	c.ServeJSON()
-}
-
-// min 返回两个整数中的较小值
-func min(a, b int) int {
-	if a < b {
-		return a
-	}
-	return b
-}
-
-// decodePNGImage 专门解码PNG图片
-func decodePNGImage(imageData []byte) (image.Image, error) {
-	// 直接使用PNG解码器
-	img, err := png.Decode(bytes.NewReader(imageData))
-	if err != nil {
-		return nil, fmt.Errorf("PNG解码失败: %v", err)
-	}
-	return img, nil
-}
-
-// isImageContentType 检查Content-Type是否是图片类型
-func isImageContentType(contentType string) bool {
-	imageTypes := []string{
-		"image/jpeg",
-		"image/jpg",
-		"image/png",
-		"image/gif",
-		"image/bmp",
-		"image/webp",
-		"image/tiff",
-	}
-
-	for _, imgType := range imageTypes {
-		if contentType == imgType {
-			return true
-		}
-	}
-	return false
-}
-
-// downloadImageFromOSS 从OSS链接下载图片(支持代理URL)
-func downloadImageFromOSS(imageURL string) ([]byte, error) {
-	fmt.Printf("开始下载图片: %s\n", imageURL)
-
-	// 检查是否是代理URL,如果是则直接使用代理接口
-	if strings.Contains(imageURL, "/apiv1/oss/parse/?url=") {
-		fmt.Printf("检测到代理URL,直接使用代理接口\n")
-		return downloadImageFromProxy(imageURL)
-	}
-
-	// 原始OSS URL的处理
-	client := &http.Client{
-		Timeout: 30 * time.Second,
-	}
-
-	resp, err := client.Get(imageURL)
-	if err != nil {
-		return nil, fmt.Errorf("下载图片失败: %v", err)
-	}
-	defer resp.Body.Close()
-
-	fmt.Printf("下载响应状态码: %d\n", resp.StatusCode)
-	fmt.Printf("响应头: %+v\n", resp.Header)
-
-	if resp.StatusCode != http.StatusOK {
-		return nil, fmt.Errorf("下载图片失败,状态码: %d", resp.StatusCode)
-	}
-
-	imageData, err := io.ReadAll(resp.Body)
-	if err != nil {
-		return nil, fmt.Errorf("读取图片数据失败: %v", err)
-	}
-
-	fmt.Printf("图片下载完成,大小: %d 字节\n", len(imageData))
-
-	// 检查Content-Type
-	contentType := resp.Header.Get("Content-Type")
-	if contentType != "" {
-		fmt.Printf("图片Content-Type: %s\n", contentType)
-		// 检查是否是图片类型
-		if !isImageContentType(contentType) {
-			fmt.Printf("警告: Content-Type不是图片类型: %s\n", contentType)
-		}
-	}
-
-	return imageData, nil
-}
-
-// downloadImageFromProxy 通过代理接口下载图片
-func downloadImageFromProxy(proxyURL string) ([]byte, error) {
-	fmt.Printf("通过代理接口下载图片: %s\n", proxyURL)
-
-	// 从代理URL中提取加密的URL参数
-	// 格式: /apiv1/oss/parse/?url=<加密字符串>
-	parsedURL, err := url.Parse(proxyURL)
-	if err != nil {
-		return nil, fmt.Errorf("解析代理URL失败: %v", err)
-	}
-
-	// 获取url参数(加密的)
-	encryptedURL := parsedURL.Query().Get("url")
-	if encryptedURL == "" {
-		return nil, fmt.Errorf("代理URL中缺少URL参数")
-	}
-
-	fmt.Printf("从代理URL提取的加密URL: %s\n", encryptedURL)
-
-	// 解密URL
-	originalURL, err := utils.DecryptURL(encryptedURL)
-	if err != nil {
-		return nil, fmt.Errorf("解密URL失败: %v", err)
-	}
-
-	fmt.Printf("解密后的原始URL: %s\n", originalURL)
-
-	// 使用解密后的原始URL下载图片
-	client := &http.Client{
-		Timeout: 30 * time.Second,
-	}
-
-	resp, err := client.Get(originalURL)
-	if err != nil {
-		return nil, fmt.Errorf("通过原始URL下载图片失败: %v", err)
-	}
-	defer resp.Body.Close()
-
-	fmt.Printf("原始URL下载响应状态码: %d\n", resp.StatusCode)
-
-	if resp.StatusCode != http.StatusOK {
-		return nil, fmt.Errorf("原始URL下载图片失败,状态码: %d", resp.StatusCode)
-	}
-
-	imageData, err := io.ReadAll(resp.Body)
-	if err != nil {
-		return nil, fmt.Errorf("读取原始URL图片数据失败: %v", err)
-	}
-
-	fmt.Printf("原始URL图片下载完成,大小: %d 字节\n", len(imageData))
-
-	// 检查Content-Type
-	contentType := resp.Header.Get("Content-Type")
-	if contentType != "" {
-		fmt.Printf("原始URL图片Content-Type: %s\n", contentType)
-		// 检查是否是图片类型
-		if !isImageContentType(contentType) {
-			fmt.Printf("警告: 原始URL Content-Type不是图片类型: %s\n", contentType)
-		}
-	}
-
-	return imageData, nil
-}
-
-// drawBoundingBox 在图像上绘制边界框和标签
-func drawBoundingBox(img image.Image, boxes [][]float64, labels []string, scores []float64, username, account, date string) image.Image {
-	// 创建可绘制的图像副本
-	bounds := img.Bounds()
-	drawableImg := image.NewRGBA(bounds)
-	draw.Draw(drawableImg, bounds, img, image.Point{}, draw.Src)
-
-	// 在左上角添加logo图片
-	drawableImg = addLogoToImage(drawableImg)
-
-	// 添加文字水印 - 传递动态参数
-	drawableImg = addTextWatermark(drawableImg, username, account, date)
-
-	// 红色边界框
-	red := image.NewUniform(color.RGBA{255, 0, 0, 255})
-
-	for _, box := range boxes {
-		if len(box) >= 4 {
-			x1, y1, x2, y2 := int(box[0]), int(box[1]), int(box[2]), int(box[3])
-
-			// 绘制边界框
-			drawRect(drawableImg, x1, y1, x2, y2, red)
-		}
-	}
-
-	return drawableImg
-}
-
-// addLogoToImage 在图片左上角添加logo
-func addLogoToImage(img *image.RGBA) *image.RGBA {
-	// 读取本地logo图片
-	logoPath := "static/image/1.png"
-	logoFile, err := os.Open(logoPath)
-	if err != nil {
-		fmt.Printf("无法打开logo文件: %v\n", err)
-		return img // 如果无法打开logo,返回原图
-	}
-	defer logoFile.Close()
-
-	// 解码logo图片
-	logoImg, err := png.Decode(logoFile)
-	if err != nil {
-		fmt.Printf("解码logo图片失败: %v\n", err)
-		return img // 如果解码失败,返回原图
-	}
-
-	// 获取logo图片尺寸
-	logoBounds := logoImg.Bounds()
-	originalWidth := logoBounds.Dx()
-	originalHeight := logoBounds.Dy()
-
-	// 缩小两倍
-	logoWidth := originalWidth / 2
-	logoHeight := originalHeight / 2
-
-	// 设置logo在左上角的位置(留一些边距)
-	margin := 10
-	logoX := margin
-	logoY := margin
-
-	// 确保logo不会超出图片边界
-	imgBounds := img.Bounds()
-	if logoX+logoWidth > imgBounds.Max.X {
-		logoX = imgBounds.Max.X - logoWidth - margin
-		if logoX < 0 {
-			logoX = 0
-		}
-	}
-	if logoY+logoHeight > imgBounds.Max.Y {
-		logoY = imgBounds.Max.Y - logoHeight - margin
-		if logoY < 0 {
-			logoY = 0
-		}
-	}
-
-	// 创建缩放后的logo图像
-	scaledLogo := image.NewRGBA(image.Rect(0, 0, logoWidth, logoHeight))
-
-	// 使用简单的最近邻缩放算法
-	for y := 0; y < logoHeight; y++ {
-		for x := 0; x < logoWidth; x++ {
-			// 计算原始图像中的对应位置
-			srcX := x * 2
-			srcY := y * 2
-
-			// 确保不超出原始图像边界
-			if srcX < originalWidth && srcY < originalHeight {
-				scaledLogo.Set(x, y, logoImg.At(srcX, srcY))
-			}
-		}
-	}
-
-	// 将缩放后的logo绘制到图片上
-	logoRect := image.Rect(logoX, logoY, logoX+logoWidth, logoY+logoHeight)
-	draw.Draw(img, logoRect, scaledLogo, image.Point{}, draw.Over)
-
-	fmt.Printf("成功在位置(%d,%d)添加logo,原始尺寸: %dx%d,缩放后尺寸: %dx%d\n", logoX, logoY, originalWidth, originalHeight, logoWidth, logoHeight)
-	return img
-}
-
-// drawRect 绘制矩形
-func drawRect(img *image.RGBA, x1, y1, x2, y2 int, color *image.Uniform) {
-	bounds := img.Bounds()
-
-	// 确保坐标在图像边界内
-	if x1 < bounds.Min.X {
-		x1 = bounds.Min.X
-	}
-	if y1 < bounds.Min.Y {
-		y1 = bounds.Min.Y
-	}
-	if x2 >= bounds.Max.X {
-		x2 = bounds.Max.X - 1
-	}
-	if y2 >= bounds.Max.Y {
-		y2 = bounds.Max.Y - 1
-	}
-
-	// 设置线条粗细(像素数)
-	lineThickness := 6
-
-	// 绘制上边(粗线)
-	for i := 0; i < lineThickness; i++ {
-		y := y1 + i
-		if y >= bounds.Max.Y {
-			break
-		}
-		for x := x1; x <= x2; x++ {
-			img.Set(x, y, color)
-		}
-	}
-
-	// 绘制下边(粗线)
-	for i := 0; i < lineThickness; i++ {
-		y := y2 - i
-		if y < bounds.Min.Y {
-			break
-		}
-		for x := x1; x <= x2; x++ {
-			img.Set(x, y, color)
-		}
-	}
-
-	// 绘制左边(粗线)
-	for i := 0; i < lineThickness; i++ {
-		x := x1 + i
-		if x >= bounds.Max.X {
-			break
-		}
-		for y := y1; y <= y2; y++ {
-			img.Set(x, y, color)
-		}
-	}
-
-	// 绘制右边(粗线)
-	for i := 0; i < lineThickness; i++ {
-		x := x2 - i
-		if x < bounds.Min.X {
-			break
-		}
-		for y := y1; y <= y2; y++ {
-			img.Set(x, y, color)
-		}
-	}
-}
-
-// removeDuplicates 去除字符串数组中的重复元素
-func removeDuplicates(strs []string) []string {
-	keys := make(map[string]bool)
-	var result []string
-
-	for _, str := range strs {
-		if !keys[str] {
-			keys[str] = true
-			result = append(result, str)
-		}
-	}
-
-	return result
-}
-
-// OSS配置信息 - 使用shudaooss.go中的配置
-
-// uploadImageToOSS 上传图片数据到OSS并返回预签名URL
-func uploadImageToOSS(imageData []byte, fileName string) (string, error) {
-	// 压缩图片
-	// if EnableImageCompression {
-	// 	fmt.Printf("开始压缩标注图片到200KB以下...\n")
-	// 	compressedBytes, err := compressImage(imageData, MaxImageWidth, MaxImageHeight, 0)
-	// 	if err != nil {
-	// 		fmt.Printf("图片压缩失败,使用原始图片: %v\n", err)
-	// 		// 压缩失败时使用原始图片
-	// 	} else {
-	// 		fmt.Printf("标注图片压缩完成,最终大小: %.2f KB\n", float64(len(compressedBytes))/1024)
-	// 		imageData = compressedBytes
-	// 	}
-	// } else {
-	// 	fmt.Printf("图片压缩已禁用,使用原始图片\n")
-	// }
-	fmt.Printf("后端图片压缩已禁用,直接上传原始图片\n")
-
-	// 获取S3会话
-	s3Config := &aws.Config{
-		Credentials:      credentials.NewStaticCredentials(ossAccessKey, ossSecretKey, ""),
-		Endpoint:         aws.String(ossEndpoint),
-		Region:           aws.String(ossRegion),
-		S3ForcePathStyle: aws.Bool(true),
-	}
-
-	sess, err := session.NewSession(s3Config)
-	if err != nil {
-		return "", fmt.Errorf("创建S3会话失败: %v", err)
-	}
-
-	// 创建S3服务
-	s3Client := s3.New(sess)
-
-	// 上传图片到S3
-	_, err = s3Client.PutObject(&s3.PutObjectInput{
-		Bucket:      aws.String(ossBucket),
-		Key:         aws.String(fileName),
-		Body:        aws.ReadSeekCloser(strings.NewReader(string(imageData))),
-		ContentType: aws.String("image/jpeg"),
-		ACL:         aws.String("public-read"),
-	})
-
-	if err != nil {
-		return "", fmt.Errorf("上传图片到OSS失败: %v", err)
-	}
-
-	// // 生成预签名URL(24小时有效期)
-	// req, _ := s3Client.GetObjectRequest(&s3.GetObjectInput{
-	// 	Bucket: aws.String(ossBucket),
-	// 	Key:    aws.String(fileName),
-	// })
-
-	// presignedURL, err := req.Presign(24 * time.Hour)
-	presignedURL := fmt.Sprintf("%s/%s/%s", ossEndpoint, ossBucket, fileName)
-	// 使用代理接口包装URL,前端需要显示图片
-	proxyURL := utils.GetProxyURL(presignedURL)
-	if err != nil {
-		return "", fmt.Errorf("生成预签名URL失败: %v", err)
-	}
-
-	return proxyURL, nil
-}
-
-// addTextWatermark 使用gg库添加45度角的文字水印
-func addTextWatermark(img *image.RGBA, username, account, date string) *image.RGBA {
-	// 水印文本 - 使用传入的动态数据
-	watermarks := []string{username, account, date}
-
-	// 获取图片尺寸
-	bounds := img.Bounds()
-	width, height := bounds.Max.X, bounds.Max.Y
-
-	// 创建一个新的绘图上下文
-	dc := gg.NewContextForImage(img)
-
-	// 设置字体和颜色
-	fontSize := 20.0 // 从30调整为20,字体更小
-
-	// 尝试多种字体加载方案
-	fontLoaded := false
-
-	// 方案1:优先使用项目中的字体文件
-	localFontPaths := []string{
-		"static/font/AlibabaPuHuiTi-3-55-Regular.ttf", // 阿里巴巴普惠体(项目字体)
-	}
-
-	for _, fontPath := range localFontPaths {
-		if err := dc.LoadFontFace(fontPath, fontSize); err == nil {
-			fmt.Printf("成功加载本地字体: %s\n", fontPath)
-			fontLoaded = true
-			break
-		}
-	}
-
-	// 方案2:如果本地字体失败,尝试使用内置字体
-	if !fontLoaded {
-		fmt.Printf("本地字体加载失败,尝试使用内置字体\n")
-		// 使用空字符串加载默认字体,这在大多数情况下都能工作
-		if err := dc.LoadFontFace("", fontSize); err == nil {
-			fmt.Printf("成功加载内置默认字体\n")
-			fontLoaded = true
-		} else {
-			fmt.Printf("内置字体加载失败: %v\n", err)
-		}
-	}
-
-	// 方案3:如果所有字体都失败,使用像素绘制(备用方案)
-	if !fontLoaded {
-		fmt.Printf("所有字体加载失败,将使用像素绘制方案\n")
-		// 这里可以添加像素绘制的备用方案
-	}
-
-	// 设置更深的颜色(从灰色改为深灰色)
-	dc.SetColor(color.RGBA{R: 120, G: 120, B: 120, A: 120}) // 深灰色,更不透明
-
-	// 设置旋转角度
-	angle := gg.Radians(-45) // 设置为-45度
-
-	// 循环绘制水印
-	textWidthEstimate := 150.0 // 估算文本宽度(字体变小,间距也相应调整)
-	textHeightEstimate := 80.0 // 估算文本高度,作为行间距(字体变小,间距也相应调整)
-
-	// 旋转整个画布来绘制
-	dc.Rotate(angle)
-
-	// 计算旋转后的绘制范围
-	// 为了覆盖整个图片,我们需要在更大的范围内绘制
-	// 简单起见,我们循环一个足够大的范围
-	// 这里的x,y是旋转后的坐标
-	for y := -float64(height); y < float64(height)*1.5; y += textHeightEstimate {
-		for x := -float64(width); x < float64(width)*1.5; x += textWidthEstimate {
-			// 每排使用相同的内容:根据y坐标确定使用哪个文本
-			rowIndex := int(y/textHeightEstimate) % len(watermarks)
-			if rowIndex < 0 {
-				rowIndex = (rowIndex%len(watermarks) + len(watermarks)) % len(watermarks)
-			}
-			text := watermarks[rowIndex]
-			dc.DrawString(text, x, y)
-		}
-	}
-
-	fmt.Printf("成功添加45度角文字水印(改进版:颜色更深,每排内容相同)\n")
-	return dc.Image().(*image.RGBA)
-}
-
-// 前端传值步骤、json文件、封面图过来
-type SaveStepRequest struct {
-	AIConversationID uint64 `json:"ai_conversation_id"`
-	Step             int    `json:"step"`
-	PPTJsonUrl       string `json:"ppt_json_url"`
-	CoverImage       string `json:"cover_image"`
-	PPTJsonContent   string `json:"ppt_json_content"`
-}
-
-func (c *HazardController) SaveStep() {
-	var requestData SaveStepRequest
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "请求数据解析失败",
-		}
-		c.ServeJSON()
-		return
-	}
-	tx := models.DB.Begin()
-	//更新到ai_conversation表
-	if err := tx.Model(&models.AIConversation{}).Where("id = ?", requestData.AIConversationID).Updates(map[string]interface{}{
-		"step":             requestData.Step,
-		"ppt_json_url":     requestData.PPTJsonUrl,
-		"cover_image":      requestData.CoverImage,
-		"ppt_json_content": requestData.PPTJsonContent,
-	}).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "更新步骤失败",
-		}
-		c.ServeJSON()
-		return
-	}
-	tx.Commit()
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-	}
-	c.ServeJSON()
-}
-
-// removeDuplicateLabels 去除重复的标签并返回逗号分隔的字符串
-func removeDuplicateLabels(labels []string) string {
-	if len(labels) == 0 {
-		return ""
-	}
-
-	// 使用map来去重
-	labelMap := make(map[string]bool)
-	var uniqueLabels []string
-
-	for _, label := range labels {
-		// 去除前后空格
-		label = strings.TrimSpace(label)
-		if label != "" && !labelMap[label] {
-			labelMap[label] = true
-			uniqueLabels = append(uniqueLabels, label)
-		}
-	}
-
-	return strings.Join(uniqueLabels, ", ")
-}
-
-// processHighwayLabel 处理高速公路场景的标签
-// 例如:"绿化_路侧植株_路侧植株" -> "路侧植株_路侧植株"
-// 去掉前缀(第一个下划线及其之前的内容)
-func processHighwayLabel(label string) string {
-	// 查找第一个下划线的位置
-	underscoreIndex := strings.Index(label, "_")
-	if underscoreIndex == -1 {
-		// 如果没有下划线,直接返回原标签
-		return label
-	}
-	// 返回从下划线之后的内容
-	return label[underscoreIndex+1:]
-}
-
-func normalizeSceneLabel(label, sceneName string) string {
-	if shouldTrimSceneLabelPrefix(sceneName) {
-		return processHighwayLabel(label)
-	}
-	return label
-}
-
-func shouldTrimSceneLabelPrefix(sceneName string) bool {
-	return strings.Contains(sceneName, "运营高速公路") ||
-		strings.Contains(sceneName, "operate_highway") ||
-		strings.Contains(sceneName, "隧道") ||
-		strings.Contains(sceneName, "tunnel") ||
-		strings.Contains(sceneName, "简支梁") ||
-		strings.Contains(sceneName, "桥梁") ||
-		strings.Contains(sceneName, "simple_supported_bridge")
-}

+ 0 - 1003
shudao-go-backend/controllers/liushi.go

@@ -1,1003 +0,0 @@
-package controllers
-
-import (
-	"bufio"
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"io"
-	"net/http"
-	"strings"
-	"time"
-
-	"shudao-chat-go/models"
-	"shudao-chat-go/utils"
-
-	beego "github.com/beego/beego/v2/server/web"
-)
-
-// LiushiController 流式接口控制器
-type LiushiController struct {
-	beego.Controller
-}
-
-// StreamRequest 流式请求结构
-type StreamRequest struct {
-	Message string `json:"message"`
-	Model   string `json:"model,omitempty"`
-}
-
-// StreamChatWithDBRequest 流式聊天数据库集成请求结构(user_id从token中获取)
-type StreamChatWithDBRequest struct {
-	Message             string `json:"message"`
-	AIConversationId    uint64 `json:"ai_conversation_id"`
-	BusinessType        int    `json:"business_type"`
-	ExamName            string `json:"exam_name"`
-	AIMessageId         uint64 `json:"ai_message_id"`
-	OnlineSearchContent string `json:"online_search_content"`
-}
-
-// StreamResponse 流式响应结构
-type StreamResponse struct {
-	ID      string `json:"id"`
-	Object  string `json:"object"`
-	Created int64  `json:"created"`
-	Model   string `json:"model"`
-	Choices []struct {
-		Index int `json:"index"`
-		Delta struct {
-			Role    string `json:"role,omitempty"`
-			Content string `json:"content,omitempty"`
-		} `json:"delta"`
-		FinishReason *string `json:"finish_reason"`
-	} `json:"choices"`
-}
-
-// StreamChat 流式聊天接口(两层流式输出:RAG检索 -> 流式回答)
-func (c *LiushiController) StreamChat() {
-	// 设置响应头为SSE流式传输
-	c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
-	c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
-	c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Origin", "*")
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Headers", "Content-Type")
-
-	// 获取请求参数
-	var request StreamRequest
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &request); err != nil {
-		c.Ctx.ResponseWriter.WriteHeader(http.StatusBadRequest)
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"请求参数解析失败\"}\n\n")
-		return
-	}
-
-	if request.Message == "" {
-		c.Ctx.ResponseWriter.WriteHeader(http.StatusBadRequest)
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"消息内容不能为空\"}\n\n")
-		return
-	}
-
-	userMessage := request.Message
-
-	// 第一层:意图识别(非流式)
-	intentPrompt := `
-`
-
-	// 发送意图识别请求(非流式)
-	intentReply, err := c.sendQwen3Message(intentPrompt, false)
-	if err != nil {
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"意图识别失败: %s\"}\n\n", err.Error())
-		return
-	}
-
-	fmt.Printf("意图识别回复: %s\n", intentReply)
-	var aiResponse map[string]interface{}
-	cleanReply := strings.TrimSpace(intentReply)
-	cleanReply = strings.TrimPrefix(cleanReply, "```json")
-	cleanReply = strings.TrimSuffix(cleanReply, "```")
-	cleanReply = strings.TrimSpace(cleanReply)
-
-	if err := json.Unmarshal([]byte(cleanReply), &aiResponse); err != nil {
-		// 如果解析失败,可能是AI直接返回了文本格式(greeting、faq)
-		fmt.Printf("JSON解析失败,AI返回了文本格式回复: %s\n", intentReply)
-
-		// 直接流式输出AI的原始回复
-		c.streamTextResponse(intentReply)
-		return
-	}
-
-	intent, ok := aiResponse["intent"].(string)
-	if !ok {
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"意图解析失败\"}\n\n")
-		return
-	}
-
-	// 根据intent类型决定处理方式
-	if intent == "greeting" || intent == "faq" {
-		// 对于greeting、faq,直接流式输出
-		if directAnswer, exists := aiResponse["direct_answer"].(string); exists && directAnswer != "" {
-			c.streamTextResponse(intentReply)
-		} else {
-			c.streamTextResponse(intentReply)
-		}
-		return
-	}
-
-	// 第二层:RAG检索(query_knowledge_base)
-	if intent == "query_knowledge_base" {
-		searchQueries, ok := aiResponse["search_queries"].([]interface{})
-		if !ok || len(searchQueries) == 0 {
-			fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"未找到有效的查询内容\"}\n\n")
-			return
-		}
-
-		// 使用第一个查询进行搜索
-		if len(searchQueries) > 0 {
-			query := searchQueries[0].(string)
-
-			// 构建搜索请求
-			searchRequest := map[string]interface{}{
-				"query":     query,
-				"n_results": 25,
-			}
-
-			requestBody, err := json.Marshal(searchRequest)
-			if err != nil {
-				fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"搜索请求构建失败\"}\n\n")
-				return
-			}
-
-			// 从配置文件中读取搜索API地址
-			searchAPIURL, err := beego.AppConfig.String("search_api_url")
-			if err != nil || searchAPIURL == "" {
-				fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"配置文件中未找到search_api_url\"}\n\n")
-				return
-			}
-
-			// 发送HTTP请求到搜索服务
-			req, err := http.NewRequest("POST", searchAPIURL, bytes.NewBuffer(requestBody))
-			if err != nil {
-				fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"创建搜索请求失败\"}\n\n")
-				return
-			}
-			req.Header.Set("Content-Type", "application/json")
-
-			client := &http.Client{Timeout: 30 * time.Second}
-			resp, err := client.Do(req)
-			if err != nil {
-				fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"搜索请求发送失败: %s\"}\n\n", err.Error())
-				return
-			}
-			defer resp.Body.Close()
-
-			responseBody, err := io.ReadAll(resp.Body)
-			if err != nil {
-				fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"读取搜索结果失败\"}\n\n")
-				return
-			}
-
-			if resp.StatusCode != http.StatusOK {
-				fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"搜索API错误: 状态码 %d\"}\n\n", resp.StatusCode)
-				return
-			}
-
-			// 解析搜索响应
-			var searchResponse map[string]interface{}
-			if err := json.Unmarshal(responseBody, &searchResponse); err != nil {
-				fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"解析搜索结果失败\"}\n\n")
-				return
-			}
-
-			// 检查响应状态
-			status, ok := searchResponse["status"].(string)
-			if !ok || status != "success" {
-				message, _ := searchResponse["message"].(string)
-				fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"搜索失败: %s\"}\n\n", message)
-				return
-			}
-
-			// 获取搜索结果
-			results, ok := searchResponse["results"].([]interface{})
-			if !ok || len(results) == 0 {
-				fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"未找到相关文档\"}\n\n")
-				return
-			}
-
-			// 第三层:流式输出最终回答
-			c.streamRAGResponse(userMessage, results)
-
-		}
-	}
-}
-
-// streamTextResponse 流式输出文本响应
-func (c *LiushiController) streamTextResponse(text string) {
-	fmt.Println("=" + strings.Repeat("=", 80))
-	fmt.Println("开始流式输出文本响应...")
-	fmt.Printf("文本长度: %d 字符\n", len(text))
-	fmt.Println("=" + strings.Repeat("=", 80))
-
-	// 如果文本较短(小于200字符),直接发送完整文本
-	if len(text) < 200 {
-		fmt.Printf("文本较短,直接发送完整内容\n")
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: %s\n\n", text)
-		c.Ctx.ResponseWriter.Flush()
-	} else {
-		// 文本较长,按块发送以模拟流式效果
-		fmt.Printf("文本较长,按块发送(每块50字符)\n")
-		c.sendTextInChunks(text, 50)
-	}
-
-	// 结束标记
-	fmt.Fprintf(c.Ctx.ResponseWriter, "data: [DONE]\n\n")
-	c.Ctx.ResponseWriter.Flush()
-
-	// 计算数据块数
-	chunkCount := 1
-	if len(text) >= 200 {
-		chunkCount = (len(text) + 49) / 50 // 向上取整
-	}
-
-	// 打印完整的流式输出完成结果
-	c.printStreamCompleteResult(len(text), chunkCount, text)
-}
-
-// sendTextInChunks 按块发送文本以模拟流式效果
-func (c *LiushiController) sendTextInChunks(text string, chunkSize int) int {
-	runes := []rune(text)
-	chunkCount := 0
-
-	for i := 0; i < len(runes); i += chunkSize {
-		end := i + chunkSize
-		if end > len(runes) {
-			end = len(runes)
-		}
-		chunk := string(runes[i:end])
-		chunkCount++
-
-		fmt.Printf("发送第 %d 块: '%s' (长度: %d)\n", chunkCount, chunk, len(chunk))
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: %s\n\n", chunk)
-		c.Ctx.ResponseWriter.Flush()
-		time.Sleep(50 * time.Millisecond) // 调整延迟以控制速度
-	}
-
-	fmt.Printf("总共发送了 %d 个数据块\n", chunkCount)
-	return chunkCount
-}
-
-// streamRAGResponse 流式输出RAG响应
-func (c *LiushiController) streamRAGResponse(userMessage string, results []interface{}) {
-	// 将搜索结果转换为JSON字符串作为上下文
-	contextJSON, err := json.Marshal(results)
-	if err != nil {
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"处理搜索结果失败: %s\"}\n\n", err.Error())
-		return
-	}
-
-	// 构建最终回答的提示词(内容与原先 natural_language_answer 一致,但仅输出该段纯文本)
-	finalPrompt := ` 
-
-`
-
-	// 直接流式调用并透传 Markdown 正文(真正的流式输出)
-	err = c.sendQwen3MessageStream(finalPrompt)
-	if err != nil {
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"生成最终回答失败: %s\"}\n\n", err.Error())
-		return
-	}
-}
-
-// sendQwen3MessageStream 发送消息到千问模型并直接流式输出到客户端
-func (c *LiushiController) sendQwen3MessageStream(userMessage string) error {
-	apiURL, err := beego.AppConfig.String("qwen3_api_url")
-	if err != nil || apiURL == "" {
-		return fmt.Errorf("配置文件中未找到qwen3_api_url")
-	}
-
-	model, err := beego.AppConfig.String("qwen3_model")
-	if err != nil || model == "" {
-		return fmt.Errorf("配置文件中未找到qwen3_model")
-	}
-
-	qwen3Request := map[string]interface{}{
-		"model":       model,
-		"stream":      true,
-		"temperature": 0.7,
-		"messages": []map[string]string{
-			{"role": "user", "content": userMessage},
-		},
-	}
-
-	requestBody, err := json.Marshal(qwen3Request)
-	if err != nil {
-		return fmt.Errorf("请求序列化失败: %v", err)
-	}
-
-	req, err := http.NewRequest("POST", apiURL+"/v1/chat/completions", bytes.NewBuffer(requestBody))
-	if err != nil {
-		return fmt.Errorf("创建HTTP请求失败: %v", err)
-	}
-
-	req.Header.Set("Content-Type", "application/json")
-
-	client := &http.Client{Timeout: 600 * time.Second}
-	resp, err := client.Do(req)
-	if err != nil {
-		return fmt.Errorf("请求发送失败: %v", err)
-	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode != http.StatusOK {
-		responseBody, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return fmt.Errorf("千问API错误: 状态码 %d,读取响应失败: %v", resp.StatusCode, err)
-		}
-		return fmt.Errorf("千问API错误: %s", string(responseBody))
-	}
-
-	// 直接流式处理并输出到客户端
-	return c.handleStreamResponseToClient(resp)
-}
-
-// sendQwen3Message 发送消息到千问模型
-func (c *LiushiController) sendQwen3Message(userMessage string, useStream bool) (string, error) {
-	apiURL, err := beego.AppConfig.String("qwen3_api_url")
-	if err != nil || apiURL == "" {
-		return "", fmt.Errorf("配置文件中未找到qwen3_api_url")
-	}
-
-	model, err := beego.AppConfig.String("qwen3_model")
-	if err != nil || model == "" {
-		return "", fmt.Errorf("配置文件中未找到qwen3_model")
-	}
-
-	qwen3Request := map[string]interface{}{
-		"model":       model,
-		"stream":      useStream,
-		"temperature": 0.7,
-		"messages": []map[string]string{
-			{"role": "user", "content": userMessage},
-		},
-	}
-
-	requestBody, err := json.Marshal(qwen3Request)
-	if err != nil {
-		return "", fmt.Errorf("请求序列化失败: %v", err)
-	}
-
-	req, err := http.NewRequest("POST", apiURL+"/v1/chat/completions", bytes.NewBuffer(requestBody))
-	if err != nil {
-		return "", fmt.Errorf("创建HTTP请求失败: %v", err)
-	}
-
-	req.Header.Set("Content-Type", "application/json")
-
-	client := &http.Client{Timeout: 600 * time.Second}
-	resp, err := client.Do(req)
-	if err != nil {
-		return "", fmt.Errorf("请求发送失败: %v", err)
-	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode != http.StatusOK {
-		responseBody, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return "", fmt.Errorf("千问API错误: 状态码 %d,读取响应失败: %v", resp.StatusCode, err)
-		}
-		return "", fmt.Errorf("千问API错误: %s", string(responseBody))
-	}
-
-	if useStream {
-		// 流式响应处理
-		type StreamResponse struct {
-			ID      string `json:"id"`
-			Object  string `json:"object"`
-			Created int64  `json:"created"`
-			Model   string `json:"model"`
-			Choices []struct {
-				Index int `json:"index"`
-				Delta struct {
-					Role    string `json:"role,omitempty"`
-					Content string `json:"content,omitempty"`
-				} `json:"delta"`
-				FinishReason *string `json:"finish_reason"`
-			} `json:"choices"`
-		}
-
-		var fullContent strings.Builder
-		scanner := bufio.NewScanner(resp.Body)
-
-		for scanner.Scan() {
-			line := scanner.Text()
-			if line == "" || !strings.HasPrefix(line, "data: ") {
-				continue
-			}
-
-			data := strings.TrimPrefix(line, "data: ")
-			if data == "[DONE]" {
-				break
-			}
-
-			var streamResp StreamResponse
-			if err := json.Unmarshal([]byte(data), &streamResp); err != nil {
-				continue
-			}
-
-			if len(streamResp.Choices) > 0 && streamResp.Choices[0].Delta.Content != "" {
-				fullContent.WriteString(streamResp.Choices[0].Delta.Content)
-			}
-		}
-
-		return fullContent.String(), nil
-	} else {
-		// 非流式响应处理
-		type NonStreamResponse struct {
-			Choices []struct {
-				Message struct {
-					Content string `json:"content"`
-				} `json:"message"`
-			} `json:"choices"`
-		}
-
-		responseBody, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return "", fmt.Errorf("读取响应失败: %v", err)
-		}
-
-		var response NonStreamResponse
-		if err := json.Unmarshal(responseBody, &response); err != nil {
-			return "", fmt.Errorf("解析响应失败: %v", err)
-		}
-
-		if len(response.Choices) > 0 {
-			return response.Choices[0].Message.Content, nil
-		}
-
-		return "", fmt.Errorf("未找到有效响应")
-	}
-}
-
-// handleStreamResponseToClient 处理流式响应并直接输出到客户端
-func (c *LiushiController) handleStreamResponseToClient(resp *http.Response) error {
-	type StreamResponse struct {
-		ID      string `json:"id"`
-		Object  string `json:"object"`
-		Created int64  `json:"created"`
-		Model   string `json:"model"`
-		Choices []struct {
-			Index int `json:"index"`
-			Delta struct {
-				Role    string `json:"role,omitempty"`
-				Content string `json:"content,omitempty"`
-			} `json:"delta"`
-			FinishReason *string `json:"finish_reason"`
-		} `json:"choices"`
-	}
-
-	fmt.Println("=" + strings.Repeat("=", 80))
-	fmt.Println("开始处理千问API流式响应(直接转发原始数据块)...")
-	fmt.Println("=" + strings.Repeat("=", 80))
-
-	scanner := bufio.NewScanner(resp.Body)
-	charCount := 0
-	blockCount := 0
-	fullContent := "" // 保存完整的响应内容
-
-	for scanner.Scan() {
-		line := scanner.Text()
-		if line == "" || !strings.HasPrefix(line, "data: ") {
-			continue
-		}
-
-		data := strings.TrimPrefix(line, "data: ")
-		if data == "[DONE]" {
-			fmt.Println("\n" + strings.Repeat("-", 40))
-			fmt.Printf("千问API流式结束,总共输出了 %d 个字符\n", charCount)
-			fmt.Println(strings.Repeat("-", 40))
-			// 发送结束标记
-			fmt.Fprintf(c.Ctx.ResponseWriter, "data: [DONE]\n\n")
-			c.Ctx.ResponseWriter.Flush()
-			break
-		}
-
-		var streamResp StreamResponse
-		if err := json.Unmarshal([]byte(data), &streamResp); err != nil {
-			continue
-		}
-
-		if len(streamResp.Choices) > 0 && streamResp.Choices[0].Delta.Content != "" {
-			// 获取内容块
-			content := streamResp.Choices[0].Delta.Content
-			charCount += len([]rune(content))
-			blockCount++
-
-			// 保存完整内容
-			fullContent += content
-
-			fmt.Printf("收到第 %d 个内容块: '%s' (长度: %d)\n", blockCount, content, len(content))
-			fmt.Printf("直接转发完整内容块到客户端\n")
-
-			// *** 关键修改:处理换行符,确保Markdown格式正确 ***
-			// 将换行符替换为\n,确保SSE格式正确传输
-			escapedContent := strings.ReplaceAll(content, "\n", "\\n")
-			fmt.Fprintf(c.Ctx.ResponseWriter, "data: %s\n\n", escapedContent)
-			c.Ctx.ResponseWriter.Flush()
-
-			// 移除逐字符输出和 time.Sleep(10 * time.Millisecond)
-		}
-
-		// 检查是否完成
-		if len(streamResp.Choices) > 0 && streamResp.Choices[0].FinishReason != nil {
-			fmt.Println("\n" + strings.Repeat("-", 40))
-			fmt.Printf("流式完成,总共接收了 %d 个数据块,%d 个字符\n", blockCount, charCount)
-			fmt.Println(strings.Repeat("-", 40))
-			fmt.Fprintf(c.Ctx.ResponseWriter, "data: [DONE]\n\n")
-			c.Ctx.ResponseWriter.Flush()
-			break
-		}
-	}
-
-	// 打印完整的流式输出完成结果
-	c.printStreamCompleteResult(charCount, blockCount, fullContent)
-
-	return scanner.Err()
-}
-
-// StreamChatWithDB 流式聊天接口(集成数据库操作)
-func (c *LiushiController) StreamChatWithDB() {
-	// 设置响应头为SSE流式传输
-	c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
-	c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
-	c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Origin", "*")
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Headers", "Content-Type")
-
-	// 从token中获取用户信息
-	userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
-	if err != nil {
-		c.Ctx.ResponseWriter.WriteHeader(http.StatusUnauthorized)
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"获取用户信息失败: %s\"}\n\n", err.Error())
-		return
-	}
-	user_id := uint64(userInfo.ID)
-	if user_id == 0 {
-		user_id = 1
-	}
-
-	// 获取请求参数
-	var requestData StreamChatWithDBRequest
-
-	// 添加调试日志
-	requestBody := c.Ctx.Input.RequestBody
-	fmt.Printf("🔍 请求体内容: %s\n", string(requestBody))
-	fmt.Printf("🔍 请求体长度: %d\n", len(requestBody))
-	fmt.Printf("🔍 Content-Type: %s\n", c.Ctx.Request.Header.Get("Content-Type"))
-
-	if err := json.Unmarshal(requestBody, &requestData); err != nil {
-		fmt.Printf("❌ JSON解析失败: %v\n", err)
-		c.Ctx.ResponseWriter.WriteHeader(http.StatusBadRequest)
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"请求参数解析失败: %s\"}\n\n", err.Error())
-		return
-	}
-
-	fmt.Println("流式聊天数据库集成请求数据:", requestData)
-
-	// 数据库操作(保存用户消息)
-	ai_conversation_id, user_message_id, err := c.saveUserMessage(&requestData, user_id)
-	if err != nil {
-		c.Ctx.ResponseWriter.WriteHeader(http.StatusInternalServerError)
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"保存用户消息失败: %s\"}\n\n", err.Error())
-		return
-	}
-
-	// 流式输出AI回复(包含初始响应)
-	c.streamAIReply(requestData.Message, user_id, ai_conversation_id, user_message_id, requestData.OnlineSearchContent)
-}
-
-// saveUserMessage 保存用户消息到数据库
-func (c *LiushiController) saveUserMessage(requestData *StreamChatWithDBRequest, user_id uint64) (uint64, uint64, error) {
-	userMessage := requestData.Message
-	if user_id == 0 {
-		user_id = 1
-	}
-	ai_conversation_id := requestData.AIConversationId
-
-	tx := models.DB.Begin()
-	defer func() {
-		if r := recover(); r != nil {
-			tx.Rollback()
-		}
-	}()
-
-	// 创建或获取对话
-	if ai_conversation_id == 0 {
-		ai_conversation := models.AIConversation{
-			UserId:       user_id,
-			Content:      userMessage,
-			BusinessType: requestData.BusinessType,
-			ExamName:     requestData.ExamName,
-		}
-		if err := tx.Create(&ai_conversation).Error; err != nil {
-			tx.Rollback()
-			return 0, 0, err
-		}
-		ai_conversation_id = uint64(ai_conversation.ID)
-	}
-
-	// 保存用户消息
-	ai_message := models.AIMessage{
-		UserId:           user_id,
-		Content:          userMessage,
-		Type:             "user",
-		AIConversationId: ai_conversation_id,
-	}
-	if err := tx.Create(&ai_message).Error; err != nil {
-		tx.Rollback()
-		return 0, 0, err
-	}
-
-	tx.Commit()
-	return ai_conversation_id, uint64(ai_message.ID), nil
-}
-
-// sendInitialResponse 发送初始响应(包含数据库ID)
-func (c *LiushiController) sendInitialResponse(ai_conversation_id, ai_message_id uint64) {
-	initialResponse := map[string]interface{}{
-		"type":               "initial",
-		"ai_conversation_id": ai_conversation_id,
-		"ai_message_id":      ai_message_id,
-		"status":             "success",
-	}
-
-	responseJSON, _ := json.Marshal(initialResponse)
-	fmt.Fprintf(c.Ctx.ResponseWriter, "data: %s\n\n", responseJSON)
-	c.Ctx.ResponseWriter.Flush()
-}
-
-// streamAIReply 流式输出AI回复并保存到数据库
-func (c *LiushiController) streamAIReply(userMessage string, user_id, ai_conversation_id, ai_message_id uint64, onlineSearchContent string) {
-	fmt.Println("🚀 ========== 开始流式AI回复流程 ==========")
-	fmt.Printf("📝 用户消息: %s\n", userMessage)
-	fmt.Printf("👤 用户ID: %d\n", user_id)
-	fmt.Printf("💬 对话ID: %d\n", ai_conversation_id)
-	fmt.Printf("📨 消息ID: %d\n", ai_message_id)
-	fmt.Println("🚀 ========================================")
-
-	// 创建AI回复记录
-	ai_reply := models.AIMessage{
-		UserId:           user_id, // 使用请求中的用户ID
-		Content:          "",      // 初始为空,流式完成后更新
-		Type:             "ai",
-		AIConversationId: ai_conversation_id,
-		PrevUserId:       ai_message_id,
-	}
-
-	fmt.Println("📊 创建AI回复记录...")
-	tx := models.DB.Begin()
-	if err := tx.Create(&ai_reply).Error; err != nil {
-		tx.Rollback()
-		fmt.Printf("❌ 创建AI回复记录失败: %v\n", err)
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"创建AI回复记录失败: %s\"}\n\n", err.Error())
-		return
-	}
-	tx.Commit()
-	fmt.Printf("✅ AI回复记录创建成功,ID: %d\n", ai_reply.ID)
-
-	// 发送初始响应(包含AI消息ID)
-	c.sendInitialResponse(ai_conversation_id, uint64(ai_reply.ID))
-
-	// 直接使用RAG流程进行搜索和回答
-	fmt.Println("🔄 开始处理消息(RAG流程)...")
-	c.processMessageWithRAG(userMessage, &ai_reply, onlineSearchContent)
-	fmt.Println("🎉 ========== 流式AI回复流程完成 ==========")
-}
-
-// processMessageWithRAG 处理消息的完整流程(RAG+数据库更新)
-func (c *LiushiController) processMessageWithRAG(userMessage string, ai_reply *models.AIMessage, onlineSearchContent string) {
-	fmt.Println("🔍 ========== 开始RAG检索流程 ==========")
-	fmt.Printf("📝 处理消息: %s\n", userMessage)
-
-	// 直接使用用户消息进行搜索
-	query := userMessage
-	fmt.Printf("🎯 使用查询: %s\n", query)
-
-	// 构建搜索请求
-	searchRequest := map[string]interface{}{
-		"query":     query,
-		"n_results": 25,
-	}
-	fmt.Printf("📦 搜索请求参数: query=%s, n_results=25\n", query)
-
-	requestBody, err := json.Marshal(searchRequest)
-	if err != nil {
-		fmt.Printf("❌ 搜索请求构建失败: %v\n", err)
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"搜索请求构建失败: %s\"}\n\n", err.Error())
-		return
-	}
-
-	// 从配置文件中读取搜索API地址
-	searchAPIURL, err := beego.AppConfig.String("search_api_url")
-	if err != nil || searchAPIURL == "" {
-		fmt.Printf("❌ 配置文件中未找到search_api_url: %v\n", err)
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"配置文件中未找到search_api_url: %s\"}\n\n", err.Error())
-		return
-	}
-	fmt.Printf("🌐 搜索API地址: %s\n", searchAPIURL)
-
-	// 发送HTTP请求到搜索服务
-	fmt.Println("📤 发送搜索请求...")
-	req, err := http.NewRequest("POST", searchAPIURL, bytes.NewBuffer(requestBody))
-	if err != nil {
-		fmt.Printf("❌ 创建搜索请求失败: %v\n", err)
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"创建搜索请求失败: %s\"}\n\n", err.Error())
-		return
-	}
-	req.Header.Set("Content-Type", "application/json")
-
-	client := &http.Client{Timeout: 30 * time.Second}
-	resp, err := client.Do(req)
-	if err != nil {
-		fmt.Printf("❌ 搜索请求发送失败: %v\n", err)
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"搜索请求发送失败: %s\"}\n\n", err.Error())
-		return
-	}
-	defer resp.Body.Close()
-
-	fmt.Printf("📥 收到搜索响应,状态码: %d\n", resp.StatusCode)
-	responseBody, err := io.ReadAll(resp.Body)
-	if err != nil {
-		fmt.Printf("❌ 读取搜索结果失败: %v\n", err)
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"读取搜索结果失败: %s\"}\n\n", err.Error())
-		return
-	}
-
-	if resp.StatusCode != http.StatusOK {
-		fmt.Printf("❌ 搜索API错误: 状态码 %d\n", resp.StatusCode)
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"搜索API错误: 状态码 %d\"}\n\n", resp.StatusCode)
-		return
-	}
-
-	// 解析搜索响应
-	var searchResponse map[string]interface{}
-	if err := json.Unmarshal(responseBody, &searchResponse); err != nil {
-		fmt.Printf("❌ 解析搜索结果失败: %v\n", err)
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"解析搜索结果失败: %s\"}\n\n", err.Error())
-		return
-	}
-
-	// 检查响应状态
-	status, ok := searchResponse["status"].(string)
-	if !ok || status != "success" {
-		message, _ := searchResponse["message"].(string)
-		fmt.Printf("❌ 搜索失败: %s\n", message)
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"搜索失败: %s\"}\n\n", message)
-		return
-	}
-
-	// 获取搜索结果
-	results, ok := searchResponse["results"].([]interface{})
-	if !ok || len(results) == 0 {
-		fmt.Printf("⚠️ 未找到相关文档\n")
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"未找到相关文档\"}\n\n")
-		return
-	}
-
-	fmt.Printf("✅ 搜索成功,找到 %d 个相关文档\n", len(results))
-	fmt.Println("🔄 开始流式输出RAG响应...")
-
-	// 流式输出最终回答并更新数据库
-	c.streamRAGResponseWithDB(userMessage, results, ai_reply, onlineSearchContent)
-}
-
-// streamRAGResponseWithDB 流式输出RAG响应并更新数据库
-func (c *LiushiController) streamRAGResponseWithDB(userMessage string, results []interface{}, ai_reply *models.AIMessage, onlineSearchContent string) {
-	// 将搜索结果转换为JSON字符串作为上下文
-	contextJSON, err := json.Marshal(results)
-	if err != nil {
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"处理搜索结果失败: %s\"}\n\n", err.Error())
-		return
-	}
-
-	// 获取历史对话上下文
-	var historyContext string
-	if ai_reply.AIConversationId > 0 {
-		var historyMessages []models.AIMessage
-		// 获取当前对话的历史消息,按时间排序,排除当前消息
-		models.DB.Model(&models.AIMessage{}).
-			Where("user_id = ? AND ai_conversation_id = ? AND is_deleted = ? AND id < ?",
-				ai_reply.UserId, ai_reply.AIConversationId, 0, ai_reply.ID).
-			Order("updated_at ASC").
-			Find(&historyMessages)
-
-		// 限制为前两轮对话(每轮包含用户消息和AI回复)
-		if len(historyMessages) > 0 {
-			// 计算轮数:每2条消息为1轮(用户消息+AI回复)
-			maxRounds := 2
-			maxMessages := maxRounds * 2
-
-			if len(historyMessages) > maxMessages {
-				historyMessages = historyMessages[len(historyMessages)-maxMessages:]
-			}
-
-			// 构建历史对话上下文
-			historyContext = "\n\n# 历史对话上下文\n"
-			for _, msg := range historyMessages {
-				if msg.Type == "user" {
-					historyContext += "用户: " + msg.Content + "\n"
-				} else if msg.Type == "ai" {
-					historyContext += "蜀安AI助手: " + msg.Content + "\n"
-				}
-			}
-			historyContext += "\n"
-		}
-	}
-	finalPrompt := `
-`
-
-	// 直接流式调用并透传 Markdown 正文(真正的流式输出)
-	fmt.Println("🌊 ========== 开始RAG流式输出 ==========")
-	fmt.Printf("📝 用户问题: %s\n", userMessage)
-	fmt.Printf("📚 检索到文档数量: %d\n", len(results))
-
-	var fullContent strings.Builder
-	charCount := 0
-	blockCount := 0
-
-	fmt.Println("🔄 开始流式调用AI模型...")
-	err = c.sendQwen3MessageStreamWithCallback(finalPrompt, func(content string) {
-		fullContent.WriteString(content)
-		charCount += len([]rune(content))
-		blockCount++
-
-		// 发送流式数据到前端
-		fmt.Printf("📤 流式块 %d: '%s' (长度: %d字符, 累计: %d字符)\n",
-			blockCount, content, len(content), charCount)
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: %s\n\n", content)
-		c.Ctx.ResponseWriter.Flush()
-	})
-
-	if err != nil {
-		fmt.Fprintf(c.Ctx.ResponseWriter, "data: {\"error\": \"生成最终回答失败: %s\"}\n\n", err.Error())
-		return
-	}
-
-	// 发送完成标记
-	fmt.Fprintf(c.Ctx.ResponseWriter, "data: [DONE]\n\n")
-	c.Ctx.ResponseWriter.Flush()
-
-	// 更新数据库中的AI回复内容
-	finalContent := fullContent.String()
-	if err := models.DB.Model(ai_reply).Update("content", finalContent).Error; err != nil {
-		fmt.Printf("更新AI回复内容失败: %v\n", err)
-	}
-
-	// 打印完成结果
-	c.printStreamCompleteResult(charCount, blockCount, finalContent)
-}
-
-// sendQwen3MessageStreamWithCallback 带回调的流式方法
-func (c *LiushiController) sendQwen3MessageStreamWithCallback(userMessage string, callback func(string)) error {
-	apiURL, err := beego.AppConfig.String("qwen3_api_url")
-	if err != nil || apiURL == "" {
-		return fmt.Errorf("配置文件中未找到qwen3_api_url")
-	}
-
-	model, err := beego.AppConfig.String("qwen3_model")
-	if err != nil || model == "" {
-		return fmt.Errorf("配置文件中未找到qwen3_model")
-	}
-
-	qwen3Request := map[string]interface{}{
-		"model":       model,
-		"stream":      true,
-		"temperature": 0.7,
-		"messages": []map[string]string{
-			{"role": "user", "content": userMessage},
-		},
-	}
-
-	requestBody, err := json.Marshal(qwen3Request)
-	if err != nil {
-		return fmt.Errorf("请求序列化失败: %v", err)
-	}
-
-	req, err := http.NewRequest("POST", apiURL+"/v1/chat/completions", bytes.NewBuffer(requestBody))
-	if err != nil {
-		return fmt.Errorf("创建HTTP请求失败: %v", err)
-	}
-
-	req.Header.Set("Content-Type", "application/json")
-	client := &http.Client{Timeout: 600 * time.Second}
-	resp, err := client.Do(req)
-	if err != nil {
-		return fmt.Errorf("请求发送失败: %v", err)
-	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode != http.StatusOK {
-		responseBody, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return fmt.Errorf("千问API错误: 状态码 %d,读取响应失败: %v", resp.StatusCode, err)
-		}
-		return fmt.Errorf("千问API错误: %s", string(responseBody))
-	}
-
-	// 处理流式响应
-	fmt.Println("📡 开始处理流式响应...")
-	scanner := bufio.NewScanner(resp.Body)
-	lineCount := 0
-	validDataCount := 0
-
-	for scanner.Scan() {
-		line := scanner.Text()
-		lineCount++
-
-		if line == "" || !strings.HasPrefix(line, "data: ") {
-			continue
-		}
-
-		data := strings.TrimPrefix(line, "data: ")
-		if data == "[DONE]" {
-			fmt.Printf("🏁 收到结束标记 [DONE],流式响应结束\n")
-			break
-		}
-
-		var streamResp struct {
-			Choices []struct {
-				Delta struct {
-					Content string `json:"content,omitempty"`
-				} `json:"delta"`
-				FinishReason *string `json:"finish_reason"`
-			} `json:"choices"`
-		}
-
-		if err := json.Unmarshal([]byte(data), &streamResp); err != nil {
-			fmt.Printf("⚠️ 解析流式数据失败 (行 %d): %v\n", lineCount, err)
-			continue
-		}
-
-		if len(streamResp.Choices) > 0 && streamResp.Choices[0].Delta.Content != "" {
-			content := streamResp.Choices[0].Delta.Content
-			validDataCount++
-			// 处理换行符
-			escapedContent := strings.ReplaceAll(content, "\n", "\\n")
-			fmt.Printf("📦 流式数据块 %d: '%s' (原始长度: %d)\n", validDataCount, content, len(content))
-			callback(escapedContent)
-		}
-
-		if len(streamResp.Choices) > 0 && streamResp.Choices[0].FinishReason != nil {
-			fmt.Printf("🏁 收到完成原因: %s\n", *streamResp.Choices[0].FinishReason)
-			break
-		}
-	}
-
-	fmt.Printf("📊 流式处理统计: 总行数=%d, 有效数据块=%d\n", lineCount, validDataCount)
-
-	return scanner.Err()
-}
-
-// printStreamCompleteResult 打印流式输出完成结果
-func (c *LiushiController) printStreamCompleteResult(charCount, blockCount int, fullContent string) {
-	fmt.Println("=" + strings.Repeat("=", 80))
-	fmt.Println("🎉 后端流式输出完成!")
-	fmt.Println("=" + strings.Repeat("=", 80))
-	fmt.Println("📊 后端统计信息:")
-	fmt.Printf("📝 总字符数: %d\n", charCount)
-	fmt.Printf("📦 数据块数: %d\n", blockCount)
-	fmt.Printf("⏱️ 完成时间: %s\n", time.Now().Format("2006/1/2 下午3:04:05"))
-	fmt.Println("=" + strings.Repeat("=", 80))
-	fmt.Println("🔍 数据一致性检查:")
-	fmt.Println("请对比前端控制台输出的统计信息,确保数据一致")
-	fmt.Println("=" + strings.Repeat("=", 80))
-	fmt.Println("✅ 后端流式输出已完全结束,所有数据已发送到客户端")
-	fmt.Println("=" + strings.Repeat("=", 80))
-
-	// 打印完整内容
-	if fullContent != "" {
-		fmt.Println("📄 完整响应内容:")
-		fmt.Println("=" + strings.Repeat("=", 80))
-		fmt.Println(fullContent)
-		fmt.Println("=" + strings.Repeat("=", 80))
-	}
-}

+ 0 - 141
shudao-go-backend/controllers/local_auth.go

@@ -1,141 +0,0 @@
-package controllers
-
-import (
-	"encoding/json"
-	"fmt"
-	"shudao-chat-go/models"
-	"shudao-chat-go/utils"
-
-	"github.com/beego/beego/v2/server/web"
-	"golang.org/x/crypto/bcrypt"
-)
-
-// LocalAuthController 本地认证控制器
-type LocalAuthController struct {
-	web.Controller
-}
-
-// LocalLoginRequest 本地登录请求结构
-type LocalLoginRequest struct {
-	Username string `json:"username"`
-	Password string `json:"password"`
-}
-
-// LocalLoginResponse 本地登录响应结构
-type LocalLoginResponse struct {
-	StatusCode int            `json:"statusCode"`
-	Msg        string         `json:"msg"`
-	Token      string         `json:"token,omitempty"`
-	UserInfo   *LocalUserInfo `json:"userInfo,omitempty"`
-}
-
-// LocalUserInfo 本地用户信息
-type LocalUserInfo struct {
-	ID       uint   `json:"id"`
-	Username string `json:"username"`
-	Nickname string `json:"nickname"`
-	Role     string `json:"role"`
-	Email    string `json:"email"`
-}
-
-// LocalLogin 本地登录接口
-func (c *LocalAuthController) LocalLogin() {
-	// 检查是否启用本地登录
-	enableLocalLogin, err := web.AppConfig.Bool("enable_local_login")
-	if err != nil || !enableLocalLogin {
-		c.Data["json"] = LocalLoginResponse{
-			StatusCode: 403,
-			Msg:        "本地登录功能未启用",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 解析请求体
-	var req LocalLoginRequest
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &req); err != nil {
-		c.Data["json"] = LocalLoginResponse{
-			StatusCode: 400,
-			Msg:        "请求参数解析失败",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 验证必填字段
-	if req.Username == "" || req.Password == "" {
-		c.Data["json"] = LocalLoginResponse{
-			StatusCode: 400,
-			Msg:        "用户名和密码不能为空",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	fmt.Printf("🔐 [本地登录] 用户 %s 尝试登录\n", req.Username)
-
-	// 查询用户
-	var user models.User
-	result := models.DB.Where("username = ? AND is_deleted = 0", req.Username).First(&user)
-	if result.Error != nil {
-		fmt.Printf("❌ [本地登录] 用户不存在: %s\n", req.Username)
-		c.Data["json"] = LocalLoginResponse{
-			StatusCode: 401,
-			Msg:        "用户名或密码错误",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 检查用户状态
-	if user.Status != 1 {
-		fmt.Printf("❌ [本地登录] 用户已被禁用: %s\n", req.Username)
-		c.Data["json"] = LocalLoginResponse{
-			StatusCode: 403,
-			Msg:        "用户已被禁用",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 验证密码
-	err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password))
-	if err != nil {
-		fmt.Printf("❌ [本地登录] 密码错误: %s\n", req.Username)
-		c.Data["json"] = LocalLoginResponse{
-			StatusCode: 401,
-			Msg:        "用户名或密码错误",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 生成本地token
-	token, err := utils.GenerateLocalToken(user.ID, user.Username, user.Role)
-	if err != nil {
-		fmt.Printf("❌ [本地登录] 生成token失败: %v\n", err)
-		c.Data["json"] = LocalLoginResponse{
-			StatusCode: 500,
-			Msg:        "生成token失败",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	fmt.Printf("✅ [本地登录] 用户 %s 登录成功\n", req.Username)
-
-	// 返回成功响应
-	c.Data["json"] = LocalLoginResponse{
-		StatusCode: 200,
-		Msg:        "登录成功",
-		Token:      token,
-		UserInfo: &LocalUserInfo{
-			ID:       user.ID,
-			Username: user.Username,
-			Nickname: user.Nickname,
-			Role:     user.Role,
-			Email:    user.Email,
-		},
-	}
-	c.ServeJSON()
-}

+ 0 - 180
shudao-go-backend/controllers/points.go

@@ -1,180 +0,0 @@
-package controllers
-
-import (
-	"encoding/json"
-	"shudao-chat-go/models"
-	"shudao-chat-go/utils"
-
-	"github.com/beego/beego/v2/server/web"
-)
-
-type PointsController struct {
-	web.Controller
-}
-
-// GetBalance 获取用户积分余额
-func (c *PointsController) GetBalance() {
-	userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 401,
-			"msg":        "获取用户信息失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	var userData models.UserData
-	if err := models.DB.Where("accountID = ?", userInfo.AccountID).First(&userData).Error; err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 404,
-			"msg":        "未找到用户数据",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data": map[string]interface{}{
-			"points": userData.Points,
-		},
-	}
-	c.ServeJSON()
-}
-
-// ConsumePointsRequest 消费积分请求
-type ConsumePointsRequest struct {
-	FileName string `json:"file_name"`
-	FileURL  string `json:"file_url"`
-}
-
-// ConsumePoints 消费积分下载文件
-func (c *PointsController) ConsumePoints() {
-	userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 401,
-			"msg":        "获取用户信息失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	var req ConsumePointsRequest
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &req); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "JSON解析失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 查询用户数据
-	var userData models.UserData
-	if err := models.DB.Where("accountID = ?", userInfo.AccountID).First(&userData).Error; err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 404,
-			"msg":        "未找到用户数据",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 检查积分是否足够(需要10积分)
-	const requiredPoints = 10
-	if userData.Points < requiredPoints {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "积分不足,下载需要10积分",
-			"data": map[string]interface{}{
-				"current_points":  userData.Points,
-				"required_points": requiredPoints,
-			},
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 开启事务
-	tx := models.DB.Begin()
-
-	// 扣减积分
-	newBalance := userData.Points - requiredPoints
-	if err := tx.Model(&models.UserData{}).Where("id = ?", userData.ID).Update("points", newBalance).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "积分扣减失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 创建消费记录 - 使用原始SQL避免GORM时间字段问题
-	result := tx.Exec(
-		"INSERT INTO points_consumption_log (user_id, file_name, file_url, points_consumed, balance_after) VALUES (?, ?, ?, ?, ?)",
-		userInfo.AccountID, req.FileName, req.FileURL, requiredPoints, newBalance,
-	)
-	if result.Error != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "创建消费记录失败: " + result.Error.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	tx.Commit()
-
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data": map[string]interface{}{
-			"new_balance":     newBalance,
-			"points_consumed": requiredPoints,
-		},
-	}
-	c.ServeJSON()
-}
-
-// GetConsumptionHistory 获取消费记录
-func (c *PointsController) GetConsumptionHistory() {
-	userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 401,
-			"msg":        "获取用户信息失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	page, _ := c.GetInt("page", 1)
-	pageSize, _ := c.GetInt("pageSize", 10)
-
-	logs, total, err := models.GetConsumptionHistory(userInfo.AccountID, page, pageSize)
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "获取消费记录失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data": map[string]interface{}{
-			"list":     logs,
-			"total":    total,
-			"page":     page,
-			"pageSize": pageSize,
-		},
-	}
-	c.ServeJSON()
-}

+ 0 - 77
shudao-go-backend/controllers/prompt.go

@@ -1,77 +0,0 @@
-package controllers
-
-import (
-	"encoding/json"
-
-	"shudao-chat-go/utils"
-
-	"github.com/beego/beego/v2/server/web"
-)
-
-type PromptController struct {
-	web.Controller
-}
-
-// BuildExamPrompt handles frontend requests to build exam prompts on the server
-func (c *PromptController) BuildExamPrompt() {
-	var req utils.ExamPromptRequest
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &req); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "JSON解析失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	prompt, err := utils.BuildExamPrompt(req)
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "生成提示词失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data": map[string]string{
-			"prompt": prompt,
-		},
-	}
-	c.ServeJSON()
-}
-
-// BuildSingleQuestionPrompt handles requests for regenerating a single question prompt
-func (c *PromptController) BuildSingleQuestionPrompt() {
-	var req utils.SingleQuestionPromptRequest
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &req); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "JSON解析失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	prompt, err := utils.BuildSingleQuestionPrompt(req)
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "生成提示词失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data": map[string]string{
-			"prompt": prompt,
-		},
-	}
-	c.ServeJSON()
-}

+ 0 - 448
shudao-go-backend/controllers/report_compat.go

@@ -1,448 +0,0 @@
-package controllers
-
-import (
-	"bufio"
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"io"
-	"net/http"
-	"strings"
-	"time"
-
-	"shudao-chat-go/models"
-	"shudao-chat-go/utils"
-
-	beego "github.com/beego/beego/v2/server/web"
-)
-
-// ReportCompatController keeps the old AI chat frontend contract working
-// while routing the request to the service that actually implements it.
-type ReportCompatController struct {
-	beego.Controller
-}
-
-type reportCompleteFlowRequest struct {
-	UserQuestion           string `json:"user_question"`
-	WindowSize             int    `json:"window_size"`
-	NResults               int    `json:"n_results"`
-	AIConversationID       uint64 `json:"ai_conversation_id"`
-	IsNetworkSearchEnabled bool   `json:"is_network_search_enabled"`
-	EnableOnlineModel      bool   `json:"enable_online_model"`
-}
-
-type updateAIMessageRequest struct {
-	AIMessageID uint64 `json:"ai_message_id"`
-	Content     string `json:"content"`
-}
-
-type stopSSERequest struct {
-	AIConversationID uint64 `json:"ai_conversation_id"`
-}
-
-type streamChatAggregateResult struct {
-	AIConversationID uint64
-	AIMessageID      uint64
-	Content          string
-}
-
-func (c *ReportCompatController) CompleteFlow() {
-	c.setSSEHeaders()
-
-	var requestData reportCompleteFlowRequest
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
-		c.writeSSEJSON(map[string]interface{}{
-			"type":    "online_error",
-			"message": fmt.Sprintf("请求参数解析失败: %s", err.Error()),
-		})
-		c.writeSSEJSON(map[string]interface{}{"type": "completed"})
-		return
-	}
-
-	userQuestion := strings.TrimSpace(requestData.UserQuestion)
-	if userQuestion == "" {
-		c.writeSSEJSON(map[string]interface{}{
-			"type":    "online_error",
-			"message": "问题不能为空",
-		})
-		c.writeSSEJSON(map[string]interface{}{"type": "completed"})
-		return
-	}
-
-	if c.shouldProxyToAIChat() {
-		if err := c.proxyAIChatSSE("/report/complete-flow", c.Ctx.Input.RequestBody); err == nil {
-			return
-		} else {
-			fmt.Printf("[report-compat] proxy to aichat failed, fallback to local stream: %v\n", err)
-		}
-	}
-
-	result, err := c.callStreamChatWithDB(requestData)
-	if err != nil {
-		c.writeSSEJSON(map[string]interface{}{
-			"type":               "online_error",
-			"ai_conversation_id": result.AIConversationID,
-			"ai_message_id":      result.AIMessageID,
-			"message":            err.Error(),
-		})
-		c.writeSSEJSON(map[string]interface{}{
-			"type":               "completed",
-			"ai_conversation_id": result.AIConversationID,
-			"ai_message_id":      result.AIMessageID,
-		})
-		return
-	}
-
-	c.writeSSEJSON(map[string]interface{}{
-		"type":               "online_answer",
-		"ai_conversation_id": result.AIConversationID,
-		"ai_message_id":      result.AIMessageID,
-		"content":            result.Content,
-	})
-	c.writeSSEJSON(map[string]interface{}{
-		"type":               "completed",
-		"ai_conversation_id": result.AIConversationID,
-		"ai_message_id":      result.AIMessageID,
-	})
-}
-
-func (c *ReportCompatController) UpdateAIMessage() {
-	if c.shouldProxyToAIChat() {
-		if err := c.proxyAIChatJSON("/report/update-ai-message", c.Ctx.Input.RequestBody); err == nil {
-			return
-		} else {
-			fmt.Printf("[report-compat] proxy update-ai-message failed, fallback to local update: %v\n", err)
-		}
-	}
-
-	var requestData updateAIMessageRequest
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"success": false,
-			"message": fmt.Sprintf("请求参数解析失败: %s", err.Error()),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	if requestData.AIMessageID == 0 {
-		c.Data["json"] = map[string]interface{}{
-			"success": false,
-			"message": "ai_message_id 不能为空",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	if err := models.DB.Model(&models.AIMessage{}).
-		Where("id = ? AND is_deleted = ?", requestData.AIMessageID, 0).
-		Update("content", requestData.Content).Error; err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"success": false,
-			"message": fmt.Sprintf("更新 AI 消息失败: %s", err.Error()),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	c.Data["json"] = map[string]interface{}{
-		"success": true,
-		"message": "AI 消息已更新",
-	}
-	c.ServeJSON()
-}
-
-func (c *ReportCompatController) StopSSE() {
-	if c.shouldProxyToAIChat() {
-		if err := c.proxyAIChatJSON("/sse/stop", c.Ctx.Input.RequestBody); err == nil {
-			return
-		} else {
-			fmt.Printf("[report-compat] proxy sse/stop failed, fallback to local success response: %v\n", err)
-		}
-	}
-
-	var requestData stopSSERequest
-	_ = json.Unmarshal(c.Ctx.Input.RequestBody, &requestData)
-
-	c.Data["json"] = map[string]interface{}{
-		"success":            true,
-		"message":            "已接收停止请求",
-		"ai_conversation_id": requestData.AIConversationID,
-	}
-	c.ServeJSON()
-}
-
-func (c *ReportCompatController) setSSEHeaders() {
-	c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
-	c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
-	c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Origin", "*")
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, Token, token")
-}
-
-func (c *ReportCompatController) writeSSEJSON(payload map[string]interface{}) {
-	responseJSON, _ := json.Marshal(payload)
-	fmt.Fprintf(c.Ctx.ResponseWriter, "data: %s\n\n", responseJSON)
-	c.Ctx.ResponseWriter.Flush()
-}
-
-func (c *ReportCompatController) shouldProxyToAIChat() bool {
-	token := c.getRequestToken()
-	if token == "" {
-		return false
-	}
-
-	if _, err := utils.VerifyLocalToken(token); err == nil {
-		return false
-	}
-
-	return true
-}
-
-func (c *ReportCompatController) getRequestToken() string {
-	for _, headerName := range []string{"token", "Token", "Authorization"} {
-		headerValue := strings.TrimSpace(c.Ctx.Request.Header.Get(headerName))
-		if headerValue == "" {
-			continue
-		}
-		if headerName == "Authorization" && strings.HasPrefix(headerValue, "Bearer ") {
-			return strings.TrimPrefix(headerValue, "Bearer ")
-		}
-		return headerValue
-	}
-
-	return ""
-}
-
-func (c *ReportCompatController) getAIChatBaseURL() string {
-	baseURL, err := beego.AppConfig.String("aichat_api_url")
-	if err != nil || strings.TrimSpace(baseURL) == "" {
-		baseURL = "http://127.0.0.1:28002/api/v1"
-	}
-
-	return strings.TrimRight(baseURL, "/")
-}
-
-func (c *ReportCompatController) proxyAIChatSSE(path string, requestBody []byte) error {
-	upstreamReq, err := http.NewRequest(
-		http.MethodPost,
-		c.getAIChatBaseURL()+path,
-		bytes.NewBuffer(requestBody),
-	)
-	if err != nil {
-		return fmt.Errorf("创建 aichat SSE 请求失败: %w", err)
-	}
-
-	upstreamReq.Header.Set("Content-Type", "application/json")
-	c.forwardAuthHeaders(upstreamReq)
-
-	client := &http.Client{Timeout: 10 * time.Minute}
-	resp, err := client.Do(upstreamReq)
-	if err != nil {
-		return fmt.Errorf("调用 aichat SSE 失败: %w", err)
-	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode != http.StatusOK {
-		responseBody, _ := io.ReadAll(resp.Body)
-		return fmt.Errorf("aichat SSE 返回异常状态: %d %s", resp.StatusCode, strings.TrimSpace(string(responseBody)))
-	}
-
-	buffer := make([]byte, 4096)
-	for {
-		n, readErr := resp.Body.Read(buffer)
-		if n > 0 {
-			if _, err := c.Ctx.ResponseWriter.Write(buffer[:n]); err != nil {
-				return fmt.Errorf("写入前端 SSE 响应失败: %w", err)
-			}
-			c.Ctx.ResponseWriter.Flush()
-		}
-
-		if readErr == io.EOF {
-			return nil
-		}
-		if readErr != nil {
-			return fmt.Errorf("读取 aichat SSE 响应失败: %w", readErr)
-		}
-	}
-}
-
-func (c *ReportCompatController) proxyAIChatJSON(path string, requestBody []byte) error {
-	upstreamReq, err := http.NewRequest(
-		http.MethodPost,
-		c.getAIChatBaseURL()+path,
-		bytes.NewBuffer(requestBody),
-	)
-	if err != nil {
-		return fmt.Errorf("创建 aichat JSON 请求失败: %w", err)
-	}
-
-	upstreamReq.Header.Set("Content-Type", "application/json")
-	c.forwardAuthHeaders(upstreamReq)
-
-	client := &http.Client{Timeout: 30 * time.Second}
-	resp, err := client.Do(upstreamReq)
-	if err != nil {
-		return fmt.Errorf("调用 aichat JSON 接口失败: %w", err)
-	}
-	defer resp.Body.Close()
-
-	responseBody, err := io.ReadAll(resp.Body)
-	if err != nil {
-		return fmt.Errorf("读取 aichat JSON 响应失败: %w", err)
-	}
-
-	c.Ctx.Output.SetStatus(resp.StatusCode)
-	c.Ctx.Output.Header("Content-Type", resp.Header.Get("Content-Type"))
-	_, _ = c.Ctx.ResponseWriter.Write(responseBody)
-	return nil
-}
-
-func (c *ReportCompatController) callStreamChatWithDB(requestData reportCompleteFlowRequest) (streamChatAggregateResult, error) {
-	upstreamBody := map[string]interface{}{
-		"message":            requestData.UserQuestion,
-		"ai_conversation_id": requestData.AIConversationID,
-		"business_type":      0,
-	}
-
-	requestBody, err := json.Marshal(upstreamBody)
-	if err != nil {
-		return streamChatAggregateResult{}, fmt.Errorf("构建内部请求失败: %w", err)
-	}
-
-	httpPort, err := beego.AppConfig.Int("httpport")
-	if err != nil || httpPort == 0 {
-		httpPort = 22001
-	}
-	upstreamURL := fmt.Sprintf("http://127.0.0.1:%d/apiv1/stream/chat-with-db", httpPort)
-
-	upstreamReq, err := http.NewRequest(http.MethodPost, upstreamURL, bytes.NewBuffer(requestBody))
-	if err != nil {
-		return streamChatAggregateResult{}, fmt.Errorf("创建内部请求失败: %w", err)
-	}
-	upstreamReq.Header.Set("Content-Type", "application/json")
-	c.forwardAuthHeaders(upstreamReq)
-
-	client := &http.Client{Timeout: 10 * time.Minute}
-	resp, err := client.Do(upstreamReq)
-	if err != nil {
-		return streamChatAggregateResult{}, fmt.Errorf("调用聊天接口失败: %w", err)
-	}
-	defer resp.Body.Close()
-
-	result, parseErr := parseStreamChatResponse(resp.Body)
-	if resp.StatusCode != http.StatusOK {
-		return result, fmt.Errorf("聊天接口返回异常状态: %d", resp.StatusCode)
-	}
-	if parseErr != nil {
-		return result, parseErr
-	}
-	if strings.TrimSpace(result.Content) == "" {
-		return result, fmt.Errorf("聊天接口未返回有效内容")
-	}
-
-	return result, nil
-}
-
-func (c *ReportCompatController) forwardAuthHeaders(req *http.Request) {
-	for _, headerName := range []string{"Authorization", "Token", "token"} {
-		if headerValue := strings.TrimSpace(c.Ctx.Request.Header.Get(headerName)); headerValue != "" {
-			req.Header.Set(headerName, headerValue)
-		}
-	}
-}
-
-func parseStreamChatResponse(reader io.Reader) (streamChatAggregateResult, error) {
-	scanner := bufio.NewScanner(reader)
-	scanner.Buffer(make([]byte, 0, 64*1024), 10*1024*1024)
-
-	var result streamChatAggregateResult
-	var contentBuilder strings.Builder
-
-	for scanner.Scan() {
-		line := strings.TrimRight(scanner.Text(), "\r")
-		if strings.TrimSpace(line) == "" {
-			continue
-		}
-
-		if strings.HasPrefix(line, "data: ") {
-			data := strings.TrimPrefix(line, "data: ")
-			if data == "[DONE]" {
-				break
-			}
-
-			var payload map[string]interface{}
-			if err := json.Unmarshal([]byte(data), &payload); err == nil {
-				if result.AIConversationID == 0 {
-					result.AIConversationID = getUint64FromMap(payload, "ai_conversation_id")
-				}
-				if result.AIMessageID == 0 {
-					result.AIMessageID = getUint64FromMap(payload, "ai_message_id")
-				}
-				if errorMessage, ok := payload["error"].(string); ok && strings.TrimSpace(errorMessage) != "" {
-					result.Content = contentBuilder.String()
-					return result, fmt.Errorf("%s", errorMessage)
-				}
-				if content, ok := payload["content"].(string); ok {
-					contentBuilder.WriteString(strings.ReplaceAll(content, "\\n", "\n"))
-				}
-				continue
-			}
-
-			if errorMessage, ok := extractRawErrorMessage(data); ok {
-				result.Content = contentBuilder.String()
-				return result, fmt.Errorf("%s", errorMessage)
-			}
-
-			contentBuilder.WriteString(strings.ReplaceAll(data, "\\n", "\n"))
-			continue
-		}
-
-		contentBuilder.WriteString(strings.ReplaceAll(line, "\\n", "\n"))
-	}
-
-	if err := scanner.Err(); err != nil {
-		result.Content = contentBuilder.String()
-		return result, fmt.Errorf("读取聊天流失败: %w", err)
-	}
-
-	result.Content = contentBuilder.String()
-	return result, nil
-}
-
-func extractRawErrorMessage(data string) (string, bool) {
-	if !strings.HasPrefix(data, "{\"error\":") {
-		return "", false
-	}
-
-	errorMessage := strings.TrimPrefix(data, "{\"error\":")
-	errorMessage = strings.TrimSuffix(errorMessage, "}")
-	errorMessage = strings.TrimSpace(errorMessage)
-	errorMessage = strings.Trim(errorMessage, "\"")
-	if errorMessage == "" {
-		return "", false
-	}
-
-	return errorMessage, true
-}
-
-func getUint64FromMap(data map[string]interface{}, key string) uint64 {
-	rawValue, ok := data[key]
-	if !ok {
-		return 0
-	}
-
-	switch value := rawValue.(type) {
-	case float64:
-		return uint64(value)
-	case int:
-		return uint64(value)
-	case int64:
-		return uint64(value)
-	case uint64:
-		return value
-	default:
-		return 0
-	}
-}

+ 0 - 369
shudao-go-backend/controllers/scene.go

@@ -1,369 +0,0 @@
-package controllers
-
-import (
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"image"
-	"image/draw"
-	_ "image/gif"
-	"image/jpeg"
-	_ "image/png"
-	"shudao-chat-go/models"
-	"shudao-chat-go/utils"
-	"strings"
-	"time"
-
-	"github.com/beego/beego/v2/server/web"
-)
-
-type SceneController struct {
-	web.Controller
-}
-
-// 隐患识别获取历史记录
-func (c *SceneController) GetHistoryRecognitionRecord() {
-	// 从token中获取用户信息
-	userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 401,
-			"msg":        "获取用户信息失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	user_id := int64(userInfo.ID)
-	if user_id == 0 {
-		user_id = 1
-	}
-
-	var recognitionRecords []models.RecognitionRecord
-	models.DB.Model(&models.RecognitionRecord{}).Where("user_id = ? AND is_deleted = ?", user_id, 0).Order("-updated_at").Find(&recognitionRecords)
-
-	// 将原始OSS URL转换为代理URL,前端需要显示图片
-	for i := range recognitionRecords {
-		if recognitionRecords[i].OriginalImageUrl != "" {
-			// 检查是否已经是代理URL格式
-			if !strings.Contains(recognitionRecords[i].OriginalImageUrl, "/apiv1/oss/parse/?url=") {
-				recognitionRecords[i].OriginalImageUrl = utils.GetProxyURL(recognitionRecords[i].OriginalImageUrl)
-			}
-		}
-		if recognitionRecords[i].RecognitionImageUrl != "" {
-			// 检查是否已经是代理URL格式
-			if !strings.Contains(recognitionRecords[i].RecognitionImageUrl, "/apiv1/oss/parse/?url=") {
-				recognitionRecords[i].RecognitionImageUrl = utils.GetProxyURL(recognitionRecords[i].RecognitionImageUrl)
-			}
-		}
-	}
-
-	//计算返回的总共的数据数量
-	var total int64
-	models.DB.Model(&models.RecognitionRecord{}).Where("user_id = ? AND is_deleted = ?", user_id, 0).Count(&total)
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data":       recognitionRecords, // 返回处理后的数据,包含代理URL
-		"total":      total,
-	}
-	c.ServeJSON()
-}
-
-// 获取识别记录详情
-func (c *SceneController) GetRecognitionRecordDetail() {
-	recognition_record_id, _ := c.GetInt64("recognition_record_id")
-	if recognition_record_id == 0 {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "识别记录ID不能为空",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	var record models.RecognitionRecord
-	if err := models.DB.Where("id = ? AND is_deleted = ?", recognition_record_id, 0).First(&record).Error; err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 404,
-			"msg":        "识别记录不存在",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 将Description字符串转换为数组
-	var thirdScenes []string
-	if record.Description != "" {
-		thirdScenes = strings.Split(record.Description, " ")
-	}
-
-	// 通过关联表查询二级场景名称及其对应的三级隐患
-	var displayLabels []string
-	elementHazards := make(map[string][]string)
-	var recognitionRecordSecondScenes []models.RecognitionRecordSecondScene
-	models.DB.Preload("SecondScene").Where("recognition_record_id = ?", record.ID).Find(&recognitionRecordSecondScenes)
-
-	for _, relation := range recognitionRecordSecondScenes {
-		label := relation.SecondScene.SecondSceneName
-		if label == "" {
-			continue
-		}
-		displayLabels = append(displayLabels, label)
-
-		var thirdSceneRecords []models.ThirdScene
-		models.DB.Where("second_scene_id = ? and is_deleted = ?", relation.SecondSceneID, 0).Find(&thirdSceneRecords)
-		for _, thirdSceneRecord := range thirdSceneRecords {
-			elementHazards[label] = append(elementHazards[label], thirdSceneRecord.ThirdSceneName)
-		}
-		elementHazards[label] = removeDuplicates(elementHazards[label])
-	}
-
-	if len(displayLabels) == 0 && record.Labels != "" {
-		for _, label := range strings.Split(record.Labels, ",") {
-			normalizedLabel := strings.TrimSpace(normalizeSceneLabel(label, record.TagType))
-			if normalizedLabel != "" {
-				displayLabels = append(displayLabels, normalizedLabel)
-			}
-		}
-	}
-	displayLabels = removeDuplicates(displayLabels)
-
-	// 将原始OSS URL转换为代理URL,前端需要显示图片
-	originalImageURL := record.OriginalImageUrl
-	recognitionImageURL := record.RecognitionImageUrl
-	if originalImageURL != "" {
-		// 检查是否已经是代理URL格式
-		if !strings.Contains(originalImageURL, "/apiv1/oss/parse/?url=") {
-			originalImageURL = utils.GetProxyURL(originalImageURL)
-		}
-	}
-	if recognitionImageURL != "" {
-		// 检查是否已经是代理URL格式
-		if !strings.Contains(recognitionImageURL, "/apiv1/oss/parse/?url=") {
-			recognitionImageURL = utils.GetProxyURL(recognitionImageURL)
-		}
-	}
-
-	// 构建详情数据
-	detailData := map[string]interface{}{
-		"id":                    record.ID,
-		"original_image_url":    originalImageURL,
-		"recognition_image_url": recognitionImageURL,
-		"user_id":               record.UserID,
-		"title":                 record.Title,
-		"description":           record.Description,
-		"created_at":            record.CreatedAt,
-		"updated_at":            record.UpdatedAt,
-		// 添加与隐患识别接口一致的字段
-		"labels":            record.Labels, // 二级场景名称数组
-		"display_labels":    displayLabels,
-		"third_scenes":      thirdScenes, // 三级场景名称数组
-		"element_hazards":   elementHazards,
-		"tag_type":          record.TagType,
-		"scene_match":       record.SceneMatch,
-		"tip_accuracy":      record.TipAccuracy,
-		"effect_evaluation": record.EffectEvaluation,
-		"user_remark":       record.UserRemark,
-	}
-
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data":       detailData,
-	}
-	c.ServeJSON()
-}
-
-// 用户提交点评
-func (c *SceneController) SubmitEvaluation() {
-	var requestData models.RecognitionRecord
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &requestData); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "JSON解析失败: " + err.Error(),
-		}
-	}
-	scene_match := requestData.SceneMatch
-	tip_accuracy := requestData.TipAccuracy
-	effect_evaluation := requestData.EffectEvaluation
-	tx := models.DB.Begin()
-	if err := tx.Model(&models.RecognitionRecord{}).Where("id = ?", requestData.ID).Updates(map[string]interface{}{
-		"scene_match":       scene_match,
-		"tip_accuracy":      tip_accuracy,
-		"effect_evaluation": effect_evaluation,
-		"user_remark":       requestData.UserRemark,
-	}).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "点评失败: " + err.Error(),
-		}
-	}
-	tx.Commit()
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-	}
-	c.ServeJSON()
-}
-
-// 查询用户最新的一条识别记录是否点评
-func (c *SceneController) GetLatestRecognitionRecord() {
-	// 从token中获取用户信息
-	userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 401,
-			"msg":        "获取用户信息失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	user_id := int64(userInfo.ID)
-	if user_id == 0 {
-		user_id = 1
-	}
-
-	var recognitionRecord models.RecognitionRecord
-	models.DB.Model(&models.RecognitionRecord{}).Where("user_id = ? AND is_deleted = ?", user_id, 0).Order("-created_at").First(&recognitionRecord)
-	//如果数据为空,则无需判断上一条是否点评则构架一个假数据EffectEvaluation=1给前端
-	if recognitionRecord.ID == 0 {
-		recognitionRecord = models.RecognitionRecord{
-			EffectEvaluation: 1,
-		}
-	}
-
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data":       recognitionRecord,
-	}
-	c.ServeJSON()
-}
-
-// 获取隐患识别三级场景标题查询正确和错误的示例图
-func (c *SceneController) GetThirdSceneExampleImage() {
-	// 从token中获取用户信息用于生成水印
-	userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 401,
-			"msg":        "获取用户信息失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	third_scene_name := c.GetString("third_scene_name")
-	if third_scene_name == "" {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "三级场景名称不能为空",
-		}
-		c.ServeJSON()
-		return
-	}
-	var thirdScene models.ThirdScene
-	models.DB.Where("third_scene_name = ? and is_deleted = ?", third_scene_name, 0).First(&thirdScene)
-
-	if thirdScene.ID == 0 {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 404,
-			"msg":        "三级场景不存在",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	watermarkDate := time.Now().Format("2006/01/02")
-
-	if thirdScene.CorrectExampleImage != "" {
-		correctURL, err := generateWatermarkedExampleImage(thirdScene.CorrectExampleImage, userInfo.Name, userInfo.ContactNumber, watermarkDate, fmt.Sprintf("third_scene_correct_%d", thirdScene.ID))
-		if err != nil {
-			c.Data["json"] = map[string]interface{}{
-				"statusCode": 500,
-				"msg":        "正确示例图加水印失败: " + err.Error(),
-			}
-			c.ServeJSON()
-			return
-		}
-		thirdScene.CorrectExampleImage = correctURL
-	}
-
-	if thirdScene.WrongExampleImage != "" {
-		wrongURL, err := generateWatermarkedExampleImage(thirdScene.WrongExampleImage, userInfo.Name, userInfo.ContactNumber, watermarkDate, fmt.Sprintf("third_scene_wrong_%d", thirdScene.ID))
-		if err != nil {
-			c.Data["json"] = map[string]interface{}{
-				"statusCode": 500,
-				"msg":        "错误示例图加水印失败: " + err.Error(),
-			}
-			c.ServeJSON()
-			return
-		}
-		thirdScene.WrongExampleImage = wrongURL
-	}
-
-	// 将示例图URL转换为代理URL,前端需要显示图片
-	if thirdScene.CorrectExampleImage != "" {
-		// 检查是否已经是代理URL格式
-		if !strings.Contains(thirdScene.CorrectExampleImage, "/apiv1/oss/parse/?url=") {
-			thirdScene.CorrectExampleImage = utils.GetProxyURL(thirdScene.CorrectExampleImage)
-		}
-	}
-	if thirdScene.WrongExampleImage != "" {
-		// 检查是否已经是代理URL格式
-		if !strings.Contains(thirdScene.WrongExampleImage, "/apiv1/oss/parse/?url=") {
-			thirdScene.WrongExampleImage = utils.GetProxyURL(thirdScene.WrongExampleImage)
-		}
-	}
-
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data":       thirdScene,
-	}
-	c.ServeJSON()
-}
-
-// generateWatermarkedExampleImage 为示例图添加水印并返回新的OSS代理URL
-func generateWatermarkedExampleImage(originalURL, username, account, date, keyPrefix string) (string, error) {
-	if originalURL == "" {
-		return "", fmt.Errorf("示例图地址为空")
-	}
-
-	imageData, err := downloadImageFromOSS(originalURL)
-	if err != nil {
-		return "", fmt.Errorf("下载示例图失败: %w", err)
-	}
-
-	img, _, err := image.Decode(bytes.NewReader(imageData))
-	if err != nil {
-		return "", fmt.Errorf("解析示例图失败: %w", err)
-	}
-
-	bounds := img.Bounds()
-	drawableImg := image.NewRGBA(bounds)
-	draw.Draw(drawableImg, bounds, img, image.Point{}, draw.Src)
-
-	drawableImg = addLogoToImage(drawableImg)
-	drawableImg = addTextWatermark(drawableImg, username, account, date)
-
-	var buf bytes.Buffer
-	if err := jpeg.Encode(&buf, drawableImg, &jpeg.Options{Quality: 95}); err != nil {
-		return "", fmt.Errorf("编码水印示例图失败: %w", err)
-	}
-
-	now := time.Now().UTC()
-	fileName := fmt.Sprintf("third_scene_examples/%d/%s_%s_%d.jpg",
-		now.Year(),
-		now.Format("0102"),
-		keyPrefix,
-		now.UnixNano())
-
-	url, err := uploadImageToOSS(buf.Bytes(), fileName)
-	if err != nil {
-		return "", fmt.Errorf("上传水印示例图失败: %w", err)
-	}
-
-	return url, nil
-}

+ 0 - 782
shudao-go-backend/controllers/shudaooss.go

@@ -1,782 +0,0 @@
-package controllers
-
-import (
-	"bytes"
-	"crypto/hmac"
-	"crypto/sha256"
-	"encoding/base64"
-	"encoding/hex"
-	"encoding/json"
-	"fmt"
-	"image"
-	"image/jpeg"
-	"io"
-	"math"
-	"net/http"
-	neturl "net/url"
-	"path/filepath"
-	"shudao-chat-go/utils"
-	"strings"
-	"time"
-
-	"github.com/aws/aws-sdk-go/aws"
-	"github.com/aws/aws-sdk-go/aws/credentials"
-	"github.com/aws/aws-sdk-go/aws/session"
-	"github.com/aws/aws-sdk-go/service/s3"
-	"github.com/beego/beego/v2/server/web"
-)
-
-type ShudaoOssController struct {
-	web.Controller
-}
-
-// OSS配置信息 - 延迟初始化
-var (
-	ossBucket    string
-	ossAccessKey string
-	ossSecretKey string
-	ossEndpoint  string
-	ossRegion    = "us-east-1"
-	ossInited    = false
-)
-
-// initOSSConfig 延迟初始化OSS配置
-func initOSSConfig() {
-	if ossInited {
-		return
-	}
-	ossConfig := utils.GetOSSConfig()
-	ossBucket = ossConfig["bucket"]
-	ossAccessKey = ossConfig["access_key"]
-	ossSecretKey = ossConfig["secret_key"]
-	ossEndpoint = ossConfig["endpoint"]
-	ossInited = true
-}
-
-// 图片压缩配置
-const (
-	// 目标文件大小(字节)
-	TargetFileSize = 200 * 1024 // 200KB
-	// 最大图片尺寸(像素)- 作为备选方案
-	MaxImageWidth  = 1920
-	MaxImageHeight = 1080
-	// JPEG压缩质量范围
-	MinJPEGQuality = 10 // 最低质量
-	MaxJPEGQuality = 95 // 最高质量
-	// 是否启用图片压缩
-	EnableImageCompression = true
-)
-
-// 上传响应结构
-type UploadResponse struct {
-	StatusCode int    `json:"statusCode"`
-	Message    string `json:"message"`
-	FileURL    string `json:"fileUrl"`
-	FileName   string `json:"fileName"`
-	FileSize   int64  `json:"fileSize"`
-}
-
-// getS3Session 获取S3会话
-func getS3Session() (*session.Session, error) {
-	initOSSConfig()
-	s3Config := &aws.Config{
-		Credentials:      credentials.NewStaticCredentials(ossAccessKey, ossSecretKey, ""),
-		Endpoint:         aws.String(ossEndpoint),
-		Region:           aws.String(ossRegion),
-		S3ForcePathStyle: aws.Bool(true),
-		DisableSSL:       aws.Bool(true),
-		MaxRetries:       aws.Int(3),
-	}
-
-	sess, err := session.NewSession(s3Config)
-	if err != nil {
-		return nil, err
-	}
-
-	if _, err = sess.Config.Credentials.Get(); err != nil {
-		return nil, fmt.Errorf("凭据验证失败: %v", err)
-	}
-
-	return sess, nil
-}
-
-// getUTCS3Session 获取UTC时间同步的S3会话
-func getUTCS3Session() (*session.Session, error) {
-	initOSSConfig()
-	s3Config := &aws.Config{
-		Credentials:      credentials.NewStaticCredentials(ossAccessKey, ossSecretKey, ""),
-		Endpoint:         aws.String(ossEndpoint),
-		Region:           aws.String(ossRegion),
-		S3ForcePathStyle: aws.Bool(true), // 强制使用路径样式(OSS兼容性需要)
-		// 移除DisableSSL,因为endpoint已经包含http://
-		// 移除LogLevel,减少调试输出
-		// 移除MaxRetries,使用默认值
-		// 移除S3DisableContentMD5Validation,使用默认值
-	}
-
-	// 创建会话
-	sess, err := session.NewSession(s3Config)
-	if err != nil {
-		return nil, err
-	}
-
-	// 验证凭据
-	_, err = sess.Config.Credentials.Get()
-	if err != nil {
-		return nil, fmt.Errorf("凭据验证失败: %v", err)
-	}
-
-	return sess, nil
-}
-
-// 判断是否为图片文件
-func isImageFile(ext string) bool {
-	imageExts := map[string]bool{
-		".jpg":  true,
-		".jpeg": true,
-		".png":  true,
-		".gif":  true,
-		".bmp":  true,
-		".webp": true,
-		".tiff": true,
-		".svg":  true,
-		".ico":  true,
-	}
-	return imageExts[ext]
-}
-
-// 图片上传接口
-func (c *ShudaoOssController) UploadImage() {
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Origin", "*")
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Headers", "Content-Type")
-
-	if c.Ctx.Request.Method == "OPTIONS" {
-		c.Ctx.ResponseWriter.WriteHeader(200)
-		return
-	}
-
-	// 获取上传的图片文件
-	file, header, err := c.GetFile("image")
-	if err != nil {
-		c.Data["json"] = UploadResponse{
-			StatusCode: 400,
-			Message:    "获取上传图片失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	defer file.Close()
-
-	// 检查文件扩展名
-	ext := strings.ToLower(filepath.Ext(header.Filename))
-	if !isImageFile(ext) {
-		c.Data["json"] = UploadResponse{
-			StatusCode: 400,
-			Message:    "不支持的文件格式,请上传图片文件(jpg, png, gif, bmp, webp等)",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 图片文件大小限制(10MB)
-	if header.Size > 10*1024*1024 {
-		c.Data["json"] = UploadResponse{
-			StatusCode: 400,
-			Message:    "图片文件大小超过限制(10MB)",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 生成图片文件名(使用UTC时间)
-	utcNow := time.Now().UTC()
-	timestamp := utcNow.Unix()
-	// 压缩后的图片统一使用.jpg扩展名
-	fileName := fmt.Sprintf("images/%d/%s_%d.jpg",
-		utcNow.Year(),
-		utcNow.Format("0102"),
-		timestamp)
-
-	// 读取图片内容
-	fileBytes, err := io.ReadAll(file)
-	if err != nil {
-		c.Data["json"] = UploadResponse{
-			StatusCode: 500,
-			Message:    "读取图片内容失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 压缩图片
-	if EnableImageCompression {
-		compressedBytes, err := compressImage(fileBytes, MaxImageWidth, MaxImageHeight, 0)
-		if err == nil {
-			fileBytes = compressedBytes
-		}
-	}
-
-	// 获取UTC时间同步的S3会话(解决时区问题)
-	sess, err := getUTCS3Session()
-	if err != nil {
-		c.Data["json"] = UploadResponse{
-			StatusCode: 500,
-			Message:    "创建S3会话失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 创建S3服务
-	s3Client := s3.New(sess)
-
-	// 上传图片到S3
-	_, err = s3Client.PutObject(&s3.PutObjectInput{
-		Bucket: aws.String(ossBucket),
-		Key:    aws.String(fileName),
-		Body:   aws.ReadSeekCloser(strings.NewReader(string(fileBytes))), // 使用与测试文件相同的方式
-		ACL:    aws.String("public-read"),
-	})
-
-	if err != nil {
-		c.Data["json"] = UploadResponse{
-			StatusCode: 500,
-			Message:    "上传图片到OSS失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// // 生成预签名URL(1小时有效期)
-	// req, _ := s3Client.GetObjectRequest(&s3.GetObjectInput{
-	// 	Bucket: aws.String(ossBucket),
-	// 	Key:    aws.String(fileName),
-	// })
-
-	// presignedURL, err := req.Presign(24 * time.Hour)
-	// if err != nil {
-	// 	fmt.Printf("生成预签名URL失败: %v\n", err)
-	// 	// 如果预签名URL生成失败,使用简单URL作为备选
-	// 	imageURL := fmt.Sprintf("%s/%s", ossEndpoint, fileName)
-	// 	c.Data["json"] = UploadResponse{
-	// 		StatusCode: 200,
-	// 		Message:    "图片上传成功,但预签名URL生成失败",
-	// 		FileURL:    imageURL,
-	// 		FileName:   fileName,
-	// 		FileSize:   header.Size,
-	// 	}
-	// 	c.ServeJSON()
-	// 	return
-	// }
-	permanentURL := fmt.Sprintf("%s/%s/%s", ossEndpoint, ossBucket, fileName)
-	proxyURL := utils.GetProxyURL(permanentURL)
-
-	c.Data["json"] = UploadResponse{
-		StatusCode: 200,
-		Message:    "图片上传成功",
-		FileURL:    proxyURL,
-		FileName:   fileName,
-		FileSize:   int64(len(fileBytes)), // 使用压缩后的文件大小
-	}
-	c.ServeJSON()
-}
-
-// 上传PPTjson文件
-func (c *ShudaoOssController) UploadPPTJson() {
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Origin", "*")
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Headers", "Content-Type")
-
-	if c.Ctx.Request.Method == "OPTIONS" {
-		c.Ctx.ResponseWriter.WriteHeader(200)
-		return
-	}
-
-	// 获取上传的JSON文件
-	file, header, err := c.GetFile("json")
-	if err != nil {
-		c.Data["json"] = UploadResponse{
-			StatusCode: 400,
-			Message:    "获取上传JSON文件失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	defer file.Close()
-
-	// 检查文件扩展名
-	ext := strings.ToLower(filepath.Ext(header.Filename))
-	if ext != ".json" {
-		c.Data["json"] = UploadResponse{
-			StatusCode: 400,
-			Message:    "不支持的文件格式,请上传JSON文件(.json)",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// JSON文件大小限制(50MB)
-	if header.Size > 50*1024*1024 {
-		c.Data["json"] = UploadResponse{
-			StatusCode: 400,
-			Message:    "JSON文件大小超过限制(50MB)",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 生成JSON文件名(使用UTC时间)
-	utcNow := time.Now().UTC()
-	timestamp := utcNow.Unix()
-	fileName := fmt.Sprintf("json/%d/%s_%d%s",
-		utcNow.Year(),
-		utcNow.Format("0102"),
-		timestamp,
-		ext)
-
-	// 读取JSON内容
-	fileBytes, err := io.ReadAll(file)
-	if err != nil {
-		c.Data["json"] = UploadResponse{
-			StatusCode: 500,
-			Message:    "读取JSON内容失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 验证JSON格式
-	var jsonData interface{}
-	if err := json.Unmarshal(fileBytes, &jsonData); err != nil {
-		c.Data["json"] = UploadResponse{
-			StatusCode: 400,
-			Message:    "JSON格式无效: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 获取UTC时间同步的S3会话
-	sess, err := getUTCS3Session()
-	if err != nil {
-		c.Data["json"] = UploadResponse{
-			StatusCode: 500,
-			Message:    "创建S3会话失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 创建S3服务
-	s3Client := s3.New(sess)
-
-	// 上传JSON到S3
-	_, err = s3Client.PutObject(&s3.PutObjectInput{
-		Bucket:      aws.String(ossBucket),
-		Key:         aws.String(fileName),
-		Body:        aws.ReadSeekCloser(strings.NewReader(string(fileBytes))),
-		ACL:         aws.String("public-read"),
-		ContentType: aws.String("application/json"),
-	})
-
-	if err != nil {
-		c.Data["json"] = UploadResponse{
-			StatusCode: 500,
-			Message:    "上传JSON文件到OSS失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 生成永久URL
-	permanentURL := fmt.Sprintf("%s/%s/%s", ossEndpoint, ossBucket, fileName)
-	proxyURL := utils.GetProxyURL(permanentURL)
-
-	c.Data["json"] = UploadResponse{
-		StatusCode: 200,
-		Message:    "JSON文件上传成功",
-		FileURL:    proxyURL,
-		FileName:   fileName,
-		FileSize:   header.Size,
-	}
-	c.ServeJSON()
-}
-
-// ParseOSS OSS代理解析接口,用于代理转发OSS URL请求
-func (c *ShudaoOssController) ParseOSS() {
-	// 设置CORS头
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Origin", "*")
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Headers", "Content-Type")
-
-	// 处理OPTIONS预检请求
-	if c.Ctx.Request.Method == "OPTIONS" {
-		c.Ctx.ResponseWriter.WriteHeader(200)
-		return
-	}
-
-	// 获取URL参数(加密的)
-	encryptedURL := c.GetString("url")
-	if encryptedURL == "" {
-		fmt.Printf("OSS代理请求缺少url参数\n")
-		c.Ctx.ResponseWriter.WriteHeader(400)
-		c.Ctx.WriteString("缺少url参数")
-		return
-	}
-
-	fmt.Printf("OSS代理请求 - 加密URL: %s\n", encryptedURL)
-
-	// 解密URL
-	decryptedURL, err := utils.DecryptURL(encryptedURL)
-	if err != nil {
-		fmt.Printf("OSS代理请求 - URL解密失败: %v\n", err)
-		c.Ctx.ResponseWriter.WriteHeader(400)
-		c.Ctx.WriteString("URL解密失败: " + err.Error())
-		return
-	}
-
-	fmt.Printf("OSS代理请求 - 解密后URL: %s\n", decryptedURL)
-
-	// URL解码,处理可能的编码问题
-	decodedURL, err := neturl.QueryUnescape(decryptedURL)
-	if err != nil {
-		decodedURL = decryptedURL
-	}
-
-	fmt.Printf("OSS代理请求 - URL解码后: %s\n", decodedURL)
-
-	var actualOSSURL string
-
-	// 检查是否是代理URL格式(包含?url=参数)
-	if strings.Contains(decodedURL, "?url=") {
-		fmt.Printf("OSS代理请求 - 检测到嵌套代理URL格式\n")
-		parsedProxyURL, err := neturl.Parse(decodedURL)
-		if err != nil {
-			fmt.Printf("OSS代理请求 - 代理URL解析失败: %v\n", err)
-			c.Ctx.ResponseWriter.WriteHeader(400)
-			c.Ctx.WriteString("代理URL格式无效: " + err.Error())
-			return
-		}
-
-		actualOSSURL = parsedProxyURL.Query().Get("url")
-		if actualOSSURL == "" {
-			fmt.Printf("OSS代理请求 - 代理URL中缺少url参数\n")
-			c.Ctx.ResponseWriter.WriteHeader(400)
-			c.Ctx.WriteString("代理URL中缺少url参数")
-			return
-		}
-		fmt.Printf("OSS代理请求 - 从嵌套URL提取的实际URL: %s\n", actualOSSURL)
-	} else {
-		actualOSSURL = decodedURL
-		fmt.Printf("OSS代理请求 - 直接使用解密URL: %s\n", actualOSSURL)
-	}
-
-	// 验证实际OSS URL格式
-	parsedOSSURL, err := neturl.Parse(actualOSSURL)
-	if err != nil {
-		fmt.Printf("OSS代理请求 - OSS URL解析失败: %v\n", err)
-		c.Ctx.ResponseWriter.WriteHeader(400)
-		c.Ctx.WriteString("OSS URL格式无效: " + err.Error())
-		return
-	}
-
-	if parsedOSSURL.Scheme == "" {
-		fmt.Printf("OSS代理请求 - OSS URL缺少协议方案: %s\n", actualOSSURL)
-		c.Ctx.ResponseWriter.WriteHeader(400)
-		c.Ctx.WriteString("OSS URL缺少协议方案")
-		return
-	}
-
-	fmt.Printf("OSS代理请求 - 最终请求URL: %s\n", actualOSSURL)
-
-	// 创建HTTP客户端,设置超时时间
-	client := &http.Client{
-		Timeout: 30 * time.Second,
-	}
-
-	// 发送GET请求到实际的OSS URL
-	resp, err := client.Get(actualOSSURL)
-	if err != nil {
-		fmt.Printf("OSS代理请求 - 连接OSS失败: %v\n", err)
-		c.Ctx.ResponseWriter.WriteHeader(502)
-		c.Ctx.WriteString("无法连接到OSS: " + err.Error())
-		return
-	}
-	defer resp.Body.Close()
-
-	fmt.Printf("OSS代理请求 - OSS响应状态码: %d\n", resp.StatusCode)
-
-	// 检查HTTP状态码
-	if resp.StatusCode != http.StatusOK {
-		fmt.Printf("OSS代理请求 - OSS返回错误状态码: %d\n", resp.StatusCode)
-		c.Ctx.ResponseWriter.WriteHeader(resp.StatusCode)
-		c.Ctx.WriteString(fmt.Sprintf("OSS返回错误: %d", resp.StatusCode))
-		return
-	}
-
-	// 读取响应内容
-	content, err := io.ReadAll(resp.Body)
-	if err != nil {
-		c.Ctx.ResponseWriter.WriteHeader(500)
-		c.Ctx.WriteString("读取OSS响应失败: " + err.Error())
-		return
-	}
-
-	// 获取原始的content-type
-	contentType := resp.Header.Get("content-type")
-	if contentType == "" {
-		contentType = "application/octet-stream"
-	}
-
-	// 如果OSS返回的是binary/octet-stream或application/octet-stream,
-	// 尝试根据URL文件扩展名推断正确的MIME类型
-	if contentType == "binary/octet-stream" || contentType == "application/octet-stream" {
-		// 解析URL获取文件路径
-		parsedURL, err := neturl.Parse(actualOSSURL)
-		if err == nil {
-			filePath := parsedURL.Path
-			// URL解码,处理中文文件名
-			filePath, err = neturl.QueryUnescape(filePath)
-			if err == nil {
-				// 根据文件扩展名猜测MIME类型
-				if strings.HasSuffix(strings.ToLower(filePath), ".jpg") || strings.HasSuffix(strings.ToLower(filePath), ".jpeg") {
-					contentType = "image/jpeg"
-				} else if strings.HasSuffix(strings.ToLower(filePath), ".png") {
-					contentType = "image/png"
-				} else if strings.HasSuffix(strings.ToLower(filePath), ".gif") {
-					contentType = "image/gif"
-				} else if strings.HasSuffix(strings.ToLower(filePath), ".pdf") {
-					contentType = "application/pdf"
-				} else if strings.HasSuffix(strings.ToLower(filePath), ".json") {
-					contentType = "application/json"
-				} else if strings.HasSuffix(strings.ToLower(filePath), ".txt") {
-					contentType = "text/plain"
-				}
-			}
-		}
-	}
-
-	// 设置响应头
-	c.Ctx.ResponseWriter.Header().Set("Content-Type", contentType)
-	c.Ctx.ResponseWriter.Header().Set("Content-Length", fmt.Sprintf("%d", len(content)))
-
-	// 转发重要的响应头
-	importantHeaders := []string{
-		"content-disposition",
-		"cache-control",
-		"etag",
-		"last-modified",
-		"accept-ranges",
-	}
-
-	for _, header := range importantHeaders {
-		if value := resp.Header.Get(header); value != "" {
-			c.Ctx.ResponseWriter.Header().Set(header, value)
-		}
-	}
-
-	// 写入响应内容
-	c.Ctx.ResponseWriter.WriteHeader(200)
-	c.Ctx.ResponseWriter.Write(content)
-}
-
-// compressImage 压缩图片到目标大小
-func compressImage(imageData []byte, maxWidth, maxHeight int, quality int) ([]byte, error) {
-	img, _, err := image.Decode(bytes.NewReader(imageData))
-	if err != nil {
-		return nil, fmt.Errorf("解码图片失败: %v", err)
-	}
-
-	originalSize := len(imageData)
-	if originalSize <= TargetFileSize {
-		return imageData, nil
-	}
-
-	return compressToTargetSize(img, originalSize)
-}
-
-// compressToTargetSize 压缩到目标文件大小
-func compressToTargetSize(img image.Image, originalSize int) ([]byte, error) {
-	bounds := img.Bounds()
-	originalWidth := bounds.Dx()
-	originalHeight := bounds.Dy()
-
-	// 策略1: 先尝试调整质量,不改变尺寸
-	compressedData, err := compressByQuality(img)
-	if err == nil && len(compressedData) <= TargetFileSize {
-		return compressedData, nil
-	}
-
-	// 策略2: 如果质量压缩不够,尝试缩小尺寸
-	targetRatio := float64(TargetFileSize) / float64(originalSize)
-	sizeRatio := math.Sqrt(targetRatio * 0.8)
-
-	newWidth := int(float64(originalWidth) * sizeRatio)
-	newHeight := int(float64(originalHeight) * sizeRatio)
-
-	if newWidth < 100 {
-		newWidth = 100
-	}
-	if newHeight < 100 {
-		newHeight = 100
-	}
-
-	resizedImg := resizeImage(img, newWidth, newHeight)
-	return compressByQuality(resizedImg)
-}
-
-// compressByQuality 通过调整质量压缩图片
-func compressByQuality(img image.Image) ([]byte, error) {
-	var bestResult []byte
-	var bestSize int = math.MaxInt32
-
-	qualities := []int{85, 70, 60, 50, 40, 30, 25, 20, 15, 10}
-
-	for _, quality := range qualities {
-		var buf bytes.Buffer
-		if err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality}); err != nil {
-			continue
-		}
-
-		currentSize := buf.Len()
-		if currentSize <= TargetFileSize {
-			return buf.Bytes(), nil
-		}
-
-		if currentSize < bestSize {
-			bestSize = currentSize
-			bestResult = buf.Bytes()
-		}
-	}
-
-	if bestResult != nil {
-		return bestResult, nil
-	}
-
-	return nil, fmt.Errorf("压缩失败")
-}
-
-// resizeImage 调整图片尺寸
-func resizeImage(img image.Image, newWidth, newHeight int) image.Image {
-	// 创建新的图片
-	resized := image.NewRGBA(image.Rect(0, 0, newWidth, newHeight))
-
-	// 简单的最近邻插值缩放
-	bounds := img.Bounds()
-	for y := 0; y < newHeight; y++ {
-		for x := 0; x < newWidth; x++ {
-			// 计算原始图片中的对应位置
-			srcX := int(float64(x) * float64(bounds.Dx()) / float64(newWidth))
-			srcY := int(float64(y) * float64(bounds.Dy()) / float64(newHeight))
-
-			// 确保不超出边界
-			if srcX >= bounds.Dx() {
-				srcX = bounds.Dx() - 1
-			}
-			if srcY >= bounds.Dy() {
-				srcY = bounds.Dy() - 1
-			}
-
-			resized.Set(x, y, img.At(bounds.Min.X+srcX, bounds.Min.Y+srcY))
-		}
-	}
-
-	return resized
-}
-
-// S3策略文档结构
-type S3PolicyDocument struct {
-	Expiration string        `json:"expiration"`
-	Conditions []interface{} `json:"conditions"`
-}
-
-// S3响应结构
-type S3PolicyToken struct {
-	URL        string            `json:"url"`
-	Fields     map[string]string `json:"fields"`
-	Expire     int64             `json:"expire"`
-	StatusCode int               `json:"statusCode"`
-}
-
-// Upload 生成S3预签名上传凭证
-func (c *ShudaoOssController) Upload() {
-	initOSSConfig()
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Origin", "*")
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
-	c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Headers", "Content-Type")
-
-	if c.Ctx.Request.Method == "OPTIONS" {
-		c.Ctx.ResponseWriter.WriteHeader(200)
-		return
-	}
-
-	userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{"statusCode": 401, "error": "获取用户信息失败"}
-		c.ServeJSON()
-		return
-	}
-	userID := int(userInfo.ID)
-	if userID == 0 {
-		userID = 1
-	}
-
-	now := time.Now().UTC()
-	expireTime := int64(1800)
-	expireEnd := now.Unix() + expireTime
-	dateStamp := now.Format("20060102")
-	amzDate := now.Format("20060102T150405Z")
-	expiration := now.Add(time.Duration(expireTime) * time.Second).Format("2006-01-02T15:04:05.000Z")
-
-	credential := fmt.Sprintf("%s/%s/%s/s3/aws4_request", ossAccessKey, dateStamp, ossRegion)
-	uploadDir := fmt.Sprintf("uploads/%s/%d/", now.Format("0102"), userID)
-	host := fmt.Sprintf("%s/%s", ossEndpoint, ossBucket)
-
-	policy := S3PolicyDocument{
-		Expiration: expiration,
-		Conditions: []interface{}{
-			map[string]string{"bucket": ossBucket},
-			[]interface{}{"starts-with", "$key", uploadDir},
-			map[string]string{"x-amz-algorithm": "AWS4-HMAC-SHA256"},
-			map[string]string{"x-amz-credential": credential},
-			map[string]string{"x-amz-date": amzDate},
-			[]interface{}{"content-length-range", "0", "104857600"},
-		},
-	}
-
-	policyJSON, _ := json.Marshal(policy)
-	policyBase64 := base64.StdEncoding.EncodeToString(policyJSON)
-	signature := generateAWS4Signature(ossSecretKey, dateStamp, ossRegion, policyBase64)
-
-	c.Data["json"] = S3PolicyToken{
-		StatusCode: 200,
-		URL:        host,
-		Expire:     expireEnd,
-		Fields: map[string]string{
-			"key":              uploadDir + "${filename}",
-			"policy":           policyBase64,
-			"x-amz-algorithm":  "AWS4-HMAC-SHA256",
-			"x-amz-credential": credential,
-			"x-amz-date":       amzDate,
-			"x-amz-signature":  signature,
-		},
-	}
-	c.ServeJSON()
-}
-
-// generateAWS4Signature 生成AWS4签名
-func generateAWS4Signature(secretKey, dateStamp, region, stringToSign string) string {
-	kDate := hmacSHA256([]byte("AWS4"+secretKey), dateStamp)
-	kRegion := hmacSHA256(kDate, region)
-	kService := hmacSHA256(kRegion, "s3")
-	kSigning := hmacSHA256(kService, "aws4_request")
-	return hex.EncodeToString(hmacSHA256(kSigning, stringToSign))
-}
-
-// hmacSHA256 HMAC-SHA256计算
-func hmacSHA256(key []byte, data string) []byte {
-	mac := hmac.New(sha256.New, key)
-	mac.Write([]byte(data))
-	return mac.Sum(nil)
-}

+ 0 - 790
shudao-go-backend/controllers/test.go

@@ -1,790 +0,0 @@
-// Package controllers - test.go
-//
-// 📝 NOTE (说明)
-// ================================================================================
-// 本文件包含批量数据处理和测试工具函数。
-// 这些函数仅用于开发和测试环境,不应在生产环境中调用。
-//
-// TODO: 建议将这些工具函数迁移到独立的CLI工具或脚本中。
-// ================================================================================
-package controllers
-
-import (
-	"fmt"
-	"image"
-	"image/jpeg"
-	"image/png"
-	"io"
-	"net/http"
-	"os"
-	"path/filepath"
-	"regexp"
-	"strconv"
-	"strings"
-	"time"
-
-	"shudao-chat-go/models"
-	"shudao-chat-go/utils"
-
-	"github.com/aws/aws-sdk-go/aws"
-	"github.com/aws/aws-sdk-go/aws/credentials"
-	"github.com/aws/aws-sdk-go/aws/session"
-	"github.com/aws/aws-sdk-go/service/s3"
-)
-
-// OSS配置信息 - 延迟初始化
-var (
-	testOssBucket    string
-	testOssAccessKey string
-	testOssSecretKey string
-	testOssEndpoint  string
-	testOssRegion    = "us-east-1"
-	testOssInited    = false
-)
-
-func initTestOSSConfig() {
-	if testOssInited {
-		return
-	}
-	config := utils.GetOSSConfig()
-	testOssBucket = config["bucket"]
-	testOssAccessKey = config["access_key"]
-	testOssSecretKey = config["secret_key"]
-	testOssEndpoint = config["endpoint"]
-	testOssInited = true
-}
-
-// 批量上传文件到OSS
-func BatchUploadFilesToOSS() {
-	initTestOSSConfig()
-	fmt.Println("=== 开始批量上传文件到OSS ===")
-
-	// 设置文件文件夹路径
-	fileFolder := "C:/Users/allen/Desktop/分类文件/办法"
-	fmt.Printf("目标文件夹: %s\n", fileFolder)
-
-	// 检查文件夹是否存在
-	if _, err := os.Stat(fileFolder); os.IsNotExist(err) {
-		fmt.Printf("错误: 文件夹不存在: %s\n", fileFolder)
-		return
-	}
-
-	fmt.Println("文件夹存在,开始扫描...")
-
-	// 获取所有文件
-	files, err := getDocumentFiles(fileFolder)
-	if err != nil {
-		fmt.Printf("错误: 获取文件失败: %v\n", err)
-		return
-	}
-
-	fmt.Printf("找到 %d 个文件\n", len(files))
-
-	// 创建S3会话
-	sess, err := createS3Session()
-	if err != nil {
-		fmt.Printf("错误: 创建S3会话失败: %v\n", err)
-		return
-	}
-	s3Client := s3.New(sess)
-
-	// 处理每个文件
-	successCount := 0
-	errorCount := 0
-
-	for i, filePath := range files {
-		fmt.Printf("处理文件 %d/%d: %s\n", i+1, len(files), filepath.Base(filePath))
-
-		// 获取文件名(不含路径)
-		fileName := filepath.Base(filePath)
-
-		// 上传文件到OSS
-		fileURL, err := uploadFileToOSSTest(s3Client, filePath, fileName)
-		if err != nil {
-			fmt.Printf("  错误: 上传文件失败 - %v\n", err)
-			errorCount++
-			continue
-		}
-
-		fmt.Printf("  文件上传成功: %s\n", fileURL)
-		//去除Filename点后面的内容
-		fileName = strings.TrimSuffix(fileName, filepath.Ext(fileName))
-		//去掉fileName前面“(内部)”这两个字
-		fileName = strings.TrimPrefix(fileName, "(内部)")
-
-		// 保存到index_file表
-		indexFile := models.PolicyFile{
-			PolicyName:       fileName,
-			PolicyFileUrl:    fileURL,
-			FileType:         0,
-			FileTag:          "内部法规",
-			PublishTime:      time.Now().Unix(),
-			PolicyType:       4,
-			PolicyDepartment: "内部法规",
-			ViewCount:        0,
-			PolicyContent:    "内部法规",
-		}
-
-		result := models.DB.Create(&indexFile)
-		if result.Error != nil {
-			fmt.Printf("  错误: 保存到数据库失败 - %v\n", result.Error)
-			errorCount++
-			continue
-		}
-
-		fmt.Printf("  数据库保存成功,ID: %d\n", indexFile.ID)
-		successCount++
-
-		// 添加延迟避免请求过于频繁
-		// time.Sleep(100 * time.Millisecond)
-	}
-
-	fmt.Println("处理完成!")
-	fmt.Printf("成功处理: %d 个文件\n", successCount)
-	fmt.Printf("处理失败: %d 个文件\n", errorCount)
-}
-
-// 批量匹配图片
-func BatchMatchImages() {
-	fmt.Println("=== 开始批量匹配图片 ===")
-
-	// 获取test表中所有有title1的记录
-	var testRecords []models.Test
-	result := models.DB.Where("title1 != '' AND title1 IS NOT NULL").Find(&testRecords)
-	if result.Error != nil {
-		fmt.Printf("错误: 获取test表记录失败: %v\n", result.Error)
-		return
-	}
-
-	fmt.Printf("找到 %d 条test表记录\n", len(testRecords))
-
-	successCount := 0
-	errorCount := 0
-
-	for i, testRecord := range testRecords {
-		fmt.Printf("处理记录 %d/%d: ID=%d, Title1=%s\n", i+1, len(testRecords), testRecord.ID, testRecord.Title1)
-
-		// 根据title1在third_scene表中查找匹配的记录
-		var thirdScene models.ThirdScene
-		result := models.DB.Where("third_scene_name = ?", testRecord.Title1).First(&thirdScene)
-		if result.Error != nil {
-			fmt.Printf("  错误: 在third_scene表中未找到匹配记录 - %v\n", result.Error)
-			errorCount++
-			continue
-		}
-
-		fmt.Printf("  找到匹配的third_scene记录: ID=%d, Name=%s\n", thirdScene.ID, thirdScene.ThirdSceneName)
-
-		// 更新third_scene表的correct_example_image和wrong_example_image
-		updateData := map[string]interface{}{
-			"correct_example_image": testRecord.Title2,
-			"wrong_example_image":   testRecord.Title3,
-		}
-
-		result = models.DB.Model(&thirdScene).Updates(updateData)
-		if result.Error != nil {
-			fmt.Printf("  错误: 更新third_scene表失败 - %v\n", result.Error)
-			errorCount++
-			continue
-		}
-
-		fmt.Printf("  更新成功: correct_example_image=%s, wrong_example_image=%s\n", testRecord.Title2, testRecord.Title3)
-		successCount++
-	}
-
-	fmt.Println("匹配完成!")
-	fmt.Printf("成功匹配: %d 条记录\n", successCount)
-	fmt.Printf("匹配失败: %d 条记录\n", errorCount)
-}
-
-// BatchUploadImages 批量上传图片函数
-func BatchUploadImages() {
-	fmt.Println("=== 开始批量上传图片 ===")
-
-	// 设置图片文件夹路径
-	imageFolder := "C:/Users/allen/Desktop/隐患识别图/错误图片"
-	fmt.Printf("目标文件夹: %s\n", imageFolder)
-
-	// 检查文件夹是否存在
-	if _, err := os.Stat(imageFolder); os.IsNotExist(err) {
-		fmt.Printf("错误: 文件夹不存在: %s\n", imageFolder)
-		return
-	}
-
-	fmt.Println("文件夹存在,开始扫描...")
-
-	// 获取所有图片文件
-	imageFiles, err := getImageFiles(imageFolder)
-	if err != nil {
-		fmt.Printf("错误: 获取图片文件失败: %v\n", err)
-		return
-	}
-
-	fmt.Printf("找到 %d 个图片文件\n", len(imageFiles))
-
-	// 创建S3会话
-	sess, err := createS3Session()
-	if err != nil {
-		fmt.Printf("错误: 创建S3会话失败: %v\n", err)
-		return
-	}
-	s3Client := s3.New(sess)
-
-	// 处理每个图片文件(只处理前10个文件进行测试)
-	successCount := 0
-	errorCount := 0
-
-	// 限制处理文件数量
-
-	for i, imageFile := range imageFiles {
-		fmt.Printf("处理文件 %d/%d: %s\n", i+1, len(imageFiles), filepath.Base(imageFile))
-
-		// 提取文件名中的序号
-		fileID, err := extractIDFromFilename(filepath.Base(imageFile))
-		if err != nil {
-			fmt.Printf("  错误: 无法提取序号 - %v\n", err)
-			errorCount++
-			continue
-		}
-
-		fmt.Printf("  提取的序号: %d\n", fileID)
-
-		// 检查数据库中是否存在对应的记录
-		var testRecord models.Test
-		result := models.DB.First(&testRecord, fileID)
-		if result.Error != nil {
-			fmt.Printf("  错误: 数据库中未找到ID为 %d 的记录 - %v\n", fileID, result.Error)
-			errorCount++
-			continue
-		}
-
-		fmt.Printf("  找到数据库记录: ID=%d, Title1=%s\n", testRecord.ID, testRecord.Title1)
-
-		// 上传图片到OSS
-		imageURL, err := uploadImageToOSSTest(s3Client, imageFile, fileID)
-		if err != nil {
-			fmt.Printf("  错误: 上传图片失败 - %v\n", err)
-			errorCount++
-			continue
-		}
-
-		fmt.Printf("  图片上传成功: %s\n", imageURL)
-
-		// 更新数据库记录
-		result = models.DB.Model(&testRecord).Update("title3", imageURL)
-		if result.Error != nil {
-			fmt.Printf("  错误: 更新数据库失败 - %v\n", result.Error)
-			errorCount++
-			continue
-		}
-
-		fmt.Printf("  数据库更新成功\n")
-		successCount++
-
-		// 添加延迟避免请求过于频繁
-		time.Sleep(100 * time.Millisecond)
-	}
-
-	fmt.Println("处理完成!")
-	fmt.Printf("成功处理: %d 个文件\n", successCount)
-	fmt.Printf("处理失败: %d 个文件\n", errorCount)
-}
-
-// 获取文件夹中的所有文档文件
-func getDocumentFiles(folderPath string) ([]string, error) {
-	var documentFiles []string
-
-	// 支持的文档格式
-	documentExts := map[string]bool{
-		".pdf":  true,
-		".doc":  true,
-		".docx": true,
-		".txt":  true,
-		".rtf":  true,
-		".odt":  true,
-		".xls":  true,
-		".xlsx": true,
-		".ppt":  true,
-		".pptx": true,
-	}
-
-	err := filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
-		if err != nil {
-			return err
-		}
-
-		if !info.IsDir() {
-			ext := strings.ToLower(filepath.Ext(path))
-			if documentExts[ext] {
-				documentFiles = append(documentFiles, path)
-			}
-		}
-
-		return nil
-	})
-
-	return documentFiles, err
-}
-
-// 获取文件夹中的所有图片文件
-func getImageFiles(folderPath string) ([]string, error) {
-	var imageFiles []string
-
-	// 支持的图片格式
-	imageExts := map[string]bool{
-		".jpg":  true,
-		".jpeg": true,
-		".png":  true,
-		".gif":  true,
-		".bmp":  true,
-		".webp": true,
-		".tiff": true,
-		".svg":  true,
-		".ico":  true,
-	}
-
-	err := filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
-		if err != nil {
-			return err
-		}
-
-		if !info.IsDir() {
-			ext := strings.ToLower(filepath.Ext(path))
-			if imageExts[ext] {
-				imageFiles = append(imageFiles, path)
-			}
-		}
-
-		return nil
-	})
-
-	return imageFiles, err
-}
-
-// 从文件名中提取序号
-func extractIDFromFilename(filename string) (int, error) {
-	// 移除文件扩展名
-	nameWithoutExt := strings.TrimSuffix(filename, filepath.Ext(filename))
-
-	// 使用正则表达式提取开头的数字
-	re := regexp.MustCompile(`^(\d+)`)
-	matches := re.FindStringSubmatch(nameWithoutExt)
-
-	if len(matches) < 2 {
-		return 0, fmt.Errorf("文件名中未找到数字序号: %s", filename)
-	}
-
-	id, err := strconv.Atoi(matches[1])
-	if err != nil {
-		return 0, fmt.Errorf("无法解析序号: %s", matches[1])
-	}
-
-	return id, nil
-}
-
-// 创建S3会话
-func createS3Session() (*session.Session, error) {
-	initTestOSSConfig()
-	s3Config := &aws.Config{
-		Credentials:      credentials.NewStaticCredentials(testOssAccessKey, testOssSecretKey, ""),
-		Endpoint:         aws.String(testOssEndpoint),
-		Region:           aws.String(testOssRegion),
-		S3ForcePathStyle: aws.Bool(true),
-	}
-
-	sess, err := session.NewSession(s3Config)
-	if err != nil {
-		return nil, err
-	}
-
-	// 验证凭据
-	_, err = sess.Config.Credentials.Get()
-	if err != nil {
-		return nil, fmt.Errorf("凭据验证失败: %v", err)
-	}
-
-	return sess, nil
-}
-
-// 上传文件到OSS
-func uploadFileToOSSTest(s3Client *s3.S3, filePath string, fileName string) (string, error) {
-	// 打开文件
-	file, err := os.Open(filePath)
-	if err != nil {
-		return "", err
-	}
-	defer file.Close()
-
-	// 生成文件名
-	ext := filepath.Ext(filePath)
-	ossFileName := fmt.Sprintf("documents/%d_%s",
-		time.Now().Unix(), fileName)
-
-	// 读取文件内容
-	fileBytes, err := io.ReadAll(file)
-	if err != nil {
-		return "", err
-	}
-
-	// 确定Content-Type
-	contentType := getContentType(ext)
-
-	// 上传到S3
-	_, err = s3Client.PutObject(&s3.PutObjectInput{
-		Bucket:      aws.String(testOssBucket),
-		Key:         aws.String(ossFileName),
-		Body:        aws.ReadSeekCloser(strings.NewReader(string(fileBytes))),
-		ContentType: aws.String(contentType),
-		ACL:         aws.String("public-read"),
-	})
-
-	if err != nil {
-		return "", err
-	}
-
-	// 生成访问URL
-	fileURL := fmt.Sprintf("%s/%s/%s", testOssEndpoint, testOssBucket, ossFileName)
-	return fileURL, nil
-}
-
-// 根据文件扩展名获取Content-Type
-func getContentType(ext string) string {
-	contentTypes := map[string]string{
-		".pdf":  "application/pdf",
-		".doc":  "application/msword",
-		".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
-		".txt":  "text/plain",
-		".rtf":  "application/rtf",
-		".odt":  "application/vnd.oasis.opendocument.text",
-		".xls":  "application/vnd.ms-excel",
-		".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
-		".ppt":  "application/vnd.ms-powerpoint",
-		".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
-	}
-
-	if contentType, exists := contentTypes[strings.ToLower(ext)]; exists {
-		return contentType
-	}
-	return "application/octet-stream" // 默认类型
-}
-
-// 上传图片到OSS
-func uploadImageToOSSTest(s3Client *s3.S3, imagePath string, fileID int) (string, error) {
-	// 打开图片文件
-	file, err := os.Open(imagePath)
-	if err != nil {
-		return "", err
-	}
-	defer file.Close()
-
-	// 生成文件名
-	ext := filepath.Ext(imagePath)
-	fileName := fmt.Sprintf("batch_upload/%d_%d%s",
-		time.Now().Unix(), fileID, ext)
-
-	// 读取文件内容
-	fileBytes, err := io.ReadAll(file)
-	if err != nil {
-		return "", err
-	}
-
-	// 上传到S3
-	_, err = s3Client.PutObject(&s3.PutObjectInput{
-		Bucket: aws.String(testOssBucket),
-		Key:    aws.String(fileName),
-		Body:   aws.ReadSeekCloser(strings.NewReader(string(fileBytes))),
-		ACL:    aws.String("public-read"),
-	})
-
-	if err != nil {
-		return "", err
-	}
-
-	// 生成访问URL
-	imageURL := fmt.Sprintf("%s/%s/%s", testOssEndpoint, testOssBucket, fileName)
-	return imageURL, nil
-}
-
-// BatchCompressAndReuploadImages 批量压缩和重新上传third_scene表中的图片
-func BatchCompressAndReuploadImages() {
-	fmt.Println("=== 开始批量压缩和重新上传图片 ===")
-
-	// 获取third_scene表中所有记录
-	var thirdScenes []models.ThirdScene
-	result := models.DB.Find(&thirdScenes)
-	if result.Error != nil {
-		fmt.Printf("错误: 获取third_scene表记录失败: %v\n", result.Error)
-		return
-	}
-
-	fmt.Printf("找到 %d 条third_scene记录\n", len(thirdScenes))
-
-	// 创建S3会话
-	sess, err := createS3Session()
-	if err != nil {
-		fmt.Printf("错误: 创建S3会话失败: %v\n", err)
-		return
-	}
-	s3Client := s3.New(sess)
-
-	successCount := 0
-	errorCount := 0
-	skipCount := 0
-
-	for i, thirdScene := range thirdScenes {
-		fmt.Printf("处理记录 %d/%d: ID=%d, Name=%s\n", i+1, len(thirdScenes), thirdScene.ID, thirdScene.ThirdSceneName)
-
-		// 处理correct_example_image
-		if thirdScene.CorrectExampleImage != "" {
-			fmt.Printf("  处理correct_example_image: %s\n", thirdScene.CorrectExampleImage)
-			newURL, err := compressAndReuploadImage(s3Client, thirdScene.CorrectExampleImage, fmt.Sprintf("correct_%d", thirdScene.ID))
-			if err != nil {
-				fmt.Printf("  错误: 处理correct_example_image失败 - %v\n", err)
-				errorCount++
-			} else {
-				thirdScene.CorrectExampleImage = newURL
-				fmt.Printf("  correct_example_image更新成功: %s\n", newURL)
-			}
-		} else {
-			fmt.Printf("  correct_example_image为空,跳过\n")
-			skipCount++
-		}
-
-		// 处理wrong_example_image
-		if thirdScene.WrongExampleImage != "" {
-			fmt.Printf("  处理wrong_example_image: %s\n", thirdScene.WrongExampleImage)
-			newURL, err := compressAndReuploadImage(s3Client, thirdScene.WrongExampleImage, fmt.Sprintf("wrong_%d", thirdScene.ID))
-			if err != nil {
-				fmt.Printf("  错误: 处理wrong_example_image失败 - %v\n", err)
-				errorCount++
-			} else {
-				thirdScene.WrongExampleImage = newURL
-				fmt.Printf("  wrong_example_image更新成功: %s\n", newURL)
-			}
-		} else {
-			fmt.Printf("  wrong_example_image为空,跳过\n")
-			skipCount++
-		}
-
-		// 更新数据库记录
-		result := models.DB.Model(&thirdScene).Updates(map[string]interface{}{
-			"correct_example_image": thirdScene.CorrectExampleImage,
-			"wrong_example_image":   thirdScene.WrongExampleImage,
-		})
-		if result.Error != nil {
-			fmt.Printf("  错误: 更新数据库失败 - %v\n", result.Error)
-			errorCount++
-		} else {
-			fmt.Printf("  数据库更新成功\n")
-			successCount++
-		}
-
-		// 添加延迟避免请求过于频繁
-		time.Sleep(200 * time.Millisecond)
-	}
-
-	fmt.Println("处理完成!")
-	fmt.Printf("成功处理: %d 条记录\n", successCount)
-	fmt.Printf("处理失败: %d 条记录\n", errorCount)
-	fmt.Printf("跳过空字段: %d 次\n", skipCount)
-}
-
-// compressAndReuploadImage 压缩并重新上传图片
-func compressAndReuploadImage(s3Client *s3.S3, imageURL string, prefix string) (string, error) {
-	// 下载图片
-	tempFilePath, err := downloadImage(imageURL)
-	if err != nil {
-		return "", fmt.Errorf("下载图片失败: %v", err)
-	}
-	defer os.Remove(tempFilePath) // 清理临时文件
-
-	// 压缩图片
-	compressedFilePath, err := compressImageFile(tempFilePath)
-	if err != nil {
-		return "", fmt.Errorf("压缩图片失败: %v", err)
-	}
-	defer os.Remove(compressedFilePath) // 清理临时文件
-
-	// 上传压缩后的图片
-	newURL, err := uploadCompressedImageToOSS(s3Client, compressedFilePath, prefix)
-	if err != nil {
-		return "", fmt.Errorf("上传压缩图片失败: %v", err)
-	}
-
-	return newURL, nil
-}
-
-// downloadImage 下载图片到临时文件
-func downloadImage(imageURL string) (string, error) {
-	// 创建HTTP请求
-	resp, err := http.Get(imageURL)
-	if err != nil {
-		return "", err
-	}
-	defer resp.Body.Close()
-
-	if resp.StatusCode != http.StatusOK {
-		return "", fmt.Errorf("下载失败,状态码: %d", resp.StatusCode)
-	}
-
-	// 创建临时文件
-	tempFile, err := os.CreateTemp("", "download_*.jpg")
-	if err != nil {
-		return "", err
-	}
-	defer tempFile.Close()
-
-	// 复制内容到临时文件
-	_, err = io.Copy(tempFile, resp.Body)
-	if err != nil {
-		os.Remove(tempFile.Name())
-		return "", err
-	}
-
-	return tempFile.Name(), nil
-}
-
-// compressImageFile 压缩图片到1M以下
-func compressImageFile(inputPath string) (string, error) {
-	// 打开原始图片
-	file, err := os.Open(inputPath)
-	if err != nil {
-		return "", err
-	}
-	defer file.Close()
-
-	// 解码图片
-	img, format, err := image.Decode(file)
-	if err != nil {
-		return "", err
-	}
-
-	// 创建临时文件用于压缩后的图片
-	tempFile, err := os.CreateTemp("", "compressed_*.jpg")
-	if err != nil {
-		return "", err
-	}
-	defer tempFile.Close()
-
-	// 尝试不同的质量级别直到文件大小小于1MB
-	targetSize := int64(1024 * 1024) // 1MB
-	quality := 90
-
-	for quality > 10 {
-		// 重置文件指针
-		tempFile.Seek(0, 0)
-		tempFile.Truncate(0)
-
-		// 根据格式编码图片
-		if format == "png" {
-			err = png.Encode(tempFile, img)
-		} else {
-			err = jpeg.Encode(tempFile, img, &jpeg.Options{Quality: quality})
-		}
-
-		if err != nil {
-			return "", err
-		}
-
-		// 检查文件大小
-		fileInfo, err := tempFile.Stat()
-		if err != nil {
-			return "", err
-		}
-
-		if fileInfo.Size() <= targetSize {
-			break
-		}
-
-		quality -= 10
-	}
-
-	// 如果还是太大,尝试缩放图片
-	if quality <= 10 {
-		// 获取图片尺寸
-		bounds := img.Bounds()
-		width := bounds.Dx()
-		height := bounds.Dy()
-
-		// 计算缩放比例
-		scale := 0.8
-		newWidth := int(float64(width) * scale)
-		newHeight := int(float64(height) * scale)
-
-		// 创建缩放后的图片
-		resizedImg := resizeImageFile(img, newWidth, newHeight)
-
-		// 重置文件指针
-		tempFile.Seek(0, 0)
-		tempFile.Truncate(0)
-
-		// 编码缩放后的图片
-		err = jpeg.Encode(tempFile, resizedImg, &jpeg.Options{Quality: 80})
-		if err != nil {
-			return "", err
-		}
-	}
-
-	return tempFile.Name(), nil
-}
-
-// resizeImageFile 缩放图片
-func resizeImageFile(img image.Image, width, height int) image.Image {
-	// 简单的最近邻缩放
-	bounds := img.Bounds()
-	srcWidth := bounds.Dx()
-	srcHeight := bounds.Dy()
-
-	// 创建新的图片
-	newImg := image.NewRGBA(image.Rect(0, 0, width, height))
-
-	// 计算缩放比例
-	xRatio := float64(srcWidth) / float64(width)
-	yRatio := float64(srcHeight) / float64(height)
-
-	for y := 0; y < height; y++ {
-		for x := 0; x < width; x++ {
-			srcX := int(float64(x) * xRatio)
-			srcY := int(float64(y) * yRatio)
-			newImg.Set(x, y, img.At(srcX, srcY))
-		}
-	}
-
-	return newImg
-}
-
-// uploadCompressedImageToOSS 上传压缩后的图片到OSS
-func uploadCompressedImageToOSS(s3Client *s3.S3, imagePath string, prefix string) (string, error) {
-	// 打开图片文件
-	file, err := os.Open(imagePath)
-	if err != nil {
-		return "", err
-	}
-	defer file.Close()
-
-	// 生成文件名
-	fileName := fmt.Sprintf("compressed_images/%s_%d.jpg", prefix, time.Now().Unix())
-
-	// 读取文件内容
-	fileBytes, err := io.ReadAll(file)
-	if err != nil {
-		return "", err
-	}
-
-	// 上传到S3
-	_, err = s3Client.PutObject(&s3.PutObjectInput{
-		Bucket:      aws.String(testOssBucket),
-		Key:         aws.String(fileName),
-		Body:        aws.ReadSeekCloser(strings.NewReader(string(fileBytes))),
-		ContentType: aws.String("image/jpeg"),
-		ACL:         aws.String("public-read"),
-	})
-
-	if err != nil {
-		return "", err
-	}
-
-	// 生成访问URL
-	imageURL := fmt.Sprintf("%s/%s/%s", testOssEndpoint, testOssBucket, fileName)
-	return imageURL, nil
-}

+ 0 - 466
shudao-go-backend/controllers/total.go

@@ -1,466 +0,0 @@
-package controllers
-
-import (
-	"encoding/json"
-	"fmt"
-	"io"
-	"math/rand"
-	"net/http"
-	"net/url"
-	"path/filepath"
-	"shudao-chat-go/models"
-	"shudao-chat-go/utils"
-	"strings"
-	"time"
-
-	"github.com/beego/beego/v2/server/web"
-)
-
-type TotalController struct {
-	web.Controller
-}
-
-// 随机返回5条推荐问题
-func (c *TotalController) GetRecommendQuestion() {
-	//获取limit数量
-	limit, _ := c.GetInt("limit")
-	if limit == 0 {
-		limit = 5
-	}
-
-	// 先获取总数
-	var count int64
-	models.DB.Model(&models.RecommendQuestion{}).Where("is_deleted = ?", 0).Count(&count)
-
-	var result []map[string]interface{}
-	if count == 0 {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 200,
-			"msg":        "success",
-			"data":       result,
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 生成随机偏移量,避免使用 ORDER BY rand()
-	if int64(limit) >= count {
-		// 数据量小于等于limit,直接查询全部
-		models.DB.Model(&models.RecommendQuestion{}).
-			Select("question").
-			Where("is_deleted = ?", 0).
-			Find(&result)
-	} else {
-		// 使用随机偏移量方式获取随机记录
-		rand.Seed(time.Now().UnixNano())
-		offset := rand.Intn(int(count) - limit + 1)
-		models.DB.Model(&models.RecommendQuestion{}).
-			Select("question").
-			Where("is_deleted = ?", 0).
-			Offset(offset).
-			Limit(limit).
-			Find(&result)
-	}
-
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data":       result,
-	}
-	c.ServeJSON()
-}
-
-// 提交意见反馈
-func (c *TotalController) SubmitFeedback() {
-	var feedback models.FeedbackQuestion
-	// 尝试解析JSON数据
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &feedback); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "JSON解析失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	// 打印解析后的数据
-	fmt.Println("解析后的数据:", feedback)
-
-	tx := models.DB.Begin()
-	if err := tx.Create(&feedback).Error; err != nil {
-		tx.Rollback()
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "fail",
-		}
-		c.ServeJSON()
-		return
-	}
-	tx.Commit()
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-	}
-	c.ServeJSON()
-}
-
-// 返回政策文件
-func (c *TotalController) GetPolicyFile() {
-	policy_type, _ := c.GetInt("policy_type")
-	search := c.GetString("search")
-	page, _ := c.GetInt("page")
-	pageSize, _ := c.GetInt("pageSize")
-	var policyFile []models.PolicyFile
-	offset := (page - 1) * pageSize
-	if policy_type == 0 {
-		models.DB.Model(&models.PolicyFile{}).Where("is_deleted = ? AND policy_name LIKE ?", 0, "%"+search+"%").Offset(offset).Limit(pageSize).Order("-updated_at").Find(&policyFile)
-	} else {
-		models.DB.Model(&models.PolicyFile{}).Where("is_deleted = ? AND policy_type = ? AND policy_name LIKE ?", 0, policy_type, "%"+search+"%").Offset(offset).Limit(pageSize).Order("-updated_at").Find(&policyFile)
-	}
-
-	// 将原始OSS URL转换为代理URL
-	for i := range policyFile {
-		if policyFile[i].PolicyFileUrl != "" {
-			// 检查是否已经是代理URL格式
-			if !strings.Contains(policyFile[i].PolicyFileUrl, "/apiv1/oss/parse/?url=") {
-				policyFile[i].PolicyFileUrl = utils.GetProxyURL(policyFile[i].PolicyFileUrl)
-			}
-		}
-	}
-
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data":       policyFile,
-	}
-	c.ServeJSON()
-}
-
-// 随机返回四条功能卡片
-func (c *TotalController) GetFunctionCard() {
-	//问题类型,0为AI问答,1为安全培训
-	functionType, _ := c.GetInt("function_type")
-	limit := 4
-
-	var count int64
-	models.DB.Model(&models.FunctionCard{}).Where("function_type = ? AND is_deleted = ?", functionType, 0).Count(&count)
-
-	var functionCard []models.FunctionCard
-	if count == 0 {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 200,
-			"msg":        "success",
-			"data":       functionCard,
-		}
-		c.ServeJSON()
-		return
-	}
-
-	if int64(limit) >= count {
-		models.DB.Model(&models.FunctionCard{}).Where("function_type = ? AND is_deleted = ?", functionType, 0).Find(&functionCard)
-	} else {
-		offset := rand.Intn(int(count) - limit + 1)
-		models.DB.Model(&models.FunctionCard{}).Where("function_type = ? AND is_deleted = ?", functionType, 0).Offset(offset).Limit(limit).Find(&functionCard)
-	}
-
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data":       functionCard,
-	}
-	c.ServeJSON()
-}
-
-// 随机返回四条热点问题
-func (c *TotalController) GetHotQuestion() {
-	//问题类型,0为AI问答,1为安全培训
-	questionType, _ := c.GetInt("question_type")
-	limit := 4
-
-	var count int64
-	models.DB.Model(&models.HotQuestion{}).Where("question_type = ? AND is_deleted = ?", questionType, 0).Count(&count)
-
-	var hotQuestion []models.HotQuestion
-	if count == 0 {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 200,
-			"msg":        "success",
-			"data":       hotQuestion,
-		}
-		c.ServeJSON()
-		return
-	}
-
-	if int64(limit) >= count {
-		models.DB.Model(&models.HotQuestion{}).Where("question_type = ? AND is_deleted = ?", questionType, 0).Find(&hotQuestion)
-	} else {
-		offset := rand.Intn(int(count) - limit + 1)
-		models.DB.Model(&models.HotQuestion{}).Where("question_type = ? AND is_deleted = ?", questionType, 0).Offset(offset).Limit(limit).Find(&hotQuestion)
-	}
-
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data":       hotQuestion,
-	}
-	c.ServeJSON()
-}
-
-type LikeAndDislikeRequest struct {
-	ID           uint `json:"id"`
-	UserFeedback int  `json:"user_feedback"`
-}
-
-// 点赞和踩
-func (c *TotalController) LikeAndDislike() {
-	var likeAndDislike LikeAndDislikeRequest
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &likeAndDislike); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "JSON解析失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	id := likeAndDislike.ID
-	userFeedback := likeAndDislike.UserFeedback
-	models.DB.Model(&models.AIMessage{}).Where("id = ?", id).Update("user_feedback", userFeedback)
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-	}
-	c.ServeJSON()
-}
-
-// 下载文件接口,支持从OSS链接下载文件并返回给前端
-func (c *TotalController) GetPdfOssDownloadLink() {
-	pdfOssDownloadLink := c.GetString("pdf_oss_download_link")
-	customFileName := c.GetString("file_name") // 支持自定义文件名
-
-	// 验证URL是否为空
-	if pdfOssDownloadLink == "" {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "下载链接不能为空",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 如果是代理URL格式,需要解密获取真实URL
-	if strings.Contains(pdfOssDownloadLink, "/apiv1/oss/parse/?url=") {
-		// 提取加密的URL参数
-		parsedURL, err := url.Parse(pdfOssDownloadLink)
-		if err == nil {
-			encryptedURL := parsedURL.Query().Get("url")
-			if encryptedURL != "" {
-				// 解密URL
-				realURL, err := utils.DecryptURL(encryptedURL)
-				if err == nil && realURL != "" {
-					pdfOssDownloadLink = realURL
-				}
-			}
-		}
-	}
-
-	// 创建HTTP客户端,设置超时时间
-	client := &http.Client{
-		Timeout: 30 * time.Second,
-	}
-
-	// 发送GET请求下载文件
-	resp, err := client.Get(pdfOssDownloadLink)
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "下载文件失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	defer resp.Body.Close()
-
-	// 检查HTTP状态码
-	if resp.StatusCode != http.StatusOK {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "文件下载失败,状态码: " + fmt.Sprintf("%d", resp.StatusCode),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 从URL中提取文件名
-	fileName := filepath.Base(pdfOssDownloadLink)
-	if fileName == "." || fileName == "/" {
-		fileName = "download_file_" + fmt.Sprintf("%d", time.Now().Unix())
-	}
-
-	// 如果提供了自定义文件名,使用自定义文件名
-	if customFileName != "" {
-		fileName = customFileName
-	}
-
-	// 从Content-Disposition头中获取文件名(如果存在)
-	if contentDisposition := resp.Header.Get("Content-Disposition"); contentDisposition != "" {
-		if strings.Contains(contentDisposition, "filename=") {
-			parts := strings.Split(contentDisposition, "filename=")
-			if len(parts) > 1 {
-				fileName = strings.Trim(parts[1], "\"")
-			}
-		}
-	}
-
-	// 获取文件大小
-	contentLength := resp.Header.Get("Content-Length")
-
-	// 设置文件下载响应头
-	c.Ctx.Output.Header("Content-Type", "application/octet-stream")
-	c.Ctx.Output.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", fileName))
-	if contentLength != "" {
-		c.Ctx.Output.Header("Content-Length", contentLength)
-	}
-	c.Ctx.Output.Header("Cache-Control", "no-cache")
-
-	// 将文件内容写入响应(流式传输)
-	_, err = io.Copy(c.Ctx.ResponseWriter, resp.Body)
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "文件传输失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 注意:文件下载成功后,响应已经通过io.Copy发送给客户端
-	// 此时不能再调用c.ServeJSON(),因为响应已经完成
-}
-
-// 政策文件查看和下载查看次数+1
-func (c *TotalController) GetPolicyFileViewAndDownloadCount() {
-	// 定义请求结构体
-	type PolicyFileCountRequest struct {
-		PolicyFileID int `json:"policy_file_id"`
-		ActionType   int `json:"action_type"`
-	}
-
-	var request PolicyFileCountRequest
-	// 解析JSON请求体
-	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &request); err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "JSON解析失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	policyFileID := request.PolicyFileID
-	actionType := request.ActionType
-
-	fmt.Println("policyFileID", policyFileID)
-	if policyFileID <= 0 {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "政策文件ID不能为空或无效",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	fmt.Println("actionType", actionType)
-	if actionType != 1 && actionType != 2 {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "操作类型无效,1-查看,2-下载",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 检查政策文件是否存在
-	var policyFile models.PolicyFile
-	if err := models.DB.Where("id = ? AND is_deleted = ?", policyFileID, 0).First(&policyFile).Error; err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 404,
-			"msg":        "政策文件不存在",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 更新查看次数
-	if err := models.DB.Model(&models.PolicyFile{}).Where("id = ?", policyFileID).Update("view_count", policyFile.ViewCount+1).Error; err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 500,
-			"msg":        "更新查看次数失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 返回成功响应
-	var actionText string
-	if actionType == 1 {
-		actionText = "查看"
-	} else {
-		actionText = "下载"
-	}
-
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        fmt.Sprintf("政策文件%s次数更新成功", actionText),
-		"data": map[string]interface{}{
-			"policy_file_id": policyFileID,
-			"action_type":    actionType,
-			"action_text":    actionText,
-			"view_count":     policyFile.ViewCount + 1,
-		},
-	}
-	c.ServeJSON()
-}
-
-// GetUserDataID 从token中获取用户信息并返回用户数据
-func (c *TotalController) GetUserDataID() {
-	// 从token中获取用户信息
-	userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 401,
-			"msg":        "获取用户信息失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	accountID := userInfo.AccountID
-	if accountID == "" {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 400,
-			"msg":        "account_id不能为空",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 查询用户数据
-	var userData models.UserData
-	if err := models.DB.Where("accountID = ?", accountID).First(&userData).Error; err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"statusCode": 404,
-			"msg":        "未找到对应的用户数据",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 返回主键id
-	c.Data["json"] = map[string]interface{}{
-		"statusCode": 200,
-		"msg":        "success",
-		"data": map[string]interface{}{
-			"id": userData.ID,
-		},
-	}
-	c.ServeJSON()
-}

+ 0 - 360
shudao-go-backend/controllers/tracking.go

@@ -1,360 +0,0 @@
-package controllers
-
-import (
-	"encoding/json"
-	"fmt"
-	"math/rand"
-	"shudao-chat-go/models"
-	"shudao-chat-go/utils"
-	"strconv"
-	"strings"
-	"time"
-
-	"github.com/beego/beego/v2/server/web"
-)
-
-// TrackingController 埋点记录控制器
-type TrackingController struct {
-	web.Controller
-}
-
-// TrackingRequest 埋点请求结构(user_id从token中获取)
-type TrackingRequest struct {
-	ApiPath   string `json:"api_path"`
-	Method    string `json:"method"`
-	ExtraData string `json:"extra_data"`
-}
-
-// TrackingResponse 埋点响应结构
-type TrackingResponse struct {
-	Code    int    `json:"code"`
-	Message string `json:"message"`
-	Data    struct {
-		RequestId string `json:"request_id"`
-	} `json:"data"`
-}
-
-// RecordTracking 记录埋点数据
-func (c *TrackingController) RecordTracking() {
-	// 从token中获取用户信息
-	userInfo, err := utils.GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"code":    401,
-			"message": "获取用户信息失败: " + err.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-	userID := int(userInfo.ID)
-	if userID == 0 {
-		userID = 1
-	}
-
-	var req TrackingRequest
-	err = json.Unmarshal(c.Ctx.Input.RequestBody, &req)
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"code":    400,
-			"message": "请求参数解析失败",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	if req.ApiPath == "" {
-		c.Data["json"] = map[string]interface{}{
-			"code":    400,
-			"message": "接口路径不能为空",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 生成请求ID
-	requestId := c.generateRequestID()
-
-	// 获取客户端IP
-	ipAddress := c.getClientIP()
-
-	// 获取User-Agent
-	userAgent := c.Ctx.Input.Header("User-Agent")
-	if userAgent == "" {
-		userAgent = "Unknown"
-	}
-
-	// 通过接口路径查找接口名称
-	apiName := c.getApiNameByPath(req.ApiPath)
-
-	// 创建埋点记录
-	trackingRecord := models.TrackingRecord{
-		UserID:    userID,
-		ApiPath:   req.ApiPath,
-		ApiName:   apiName,
-		Method:    req.Method,
-		UserAgent: userAgent,
-		IpAddress: ipAddress,
-		RequestId: requestId,
-		Status:    1,
-		Duration:  0, // 前端调用时暂时设为0,后续可以扩展
-		ExtraData: req.ExtraData,
-	}
-
-	// 保存到数据库
-	result := models.DB.Create(&trackingRecord)
-	if result.Error != nil {
-		c.Data["json"] = map[string]interface{}{
-			"code":    500,
-			"message": "埋点记录保存失败: " + result.Error.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 返回成功响应
-	c.Data["json"] = TrackingResponse{
-		Code:    200,
-		Message: "埋点记录成功",
-		Data: struct {
-			RequestId string `json:"request_id"`
-		}{
-			RequestId: requestId,
-		},
-	}
-	c.ServeJSON()
-}
-
-// getClientIP 获取客户端真实IP
-func (c *TrackingController) getClientIP() string {
-	// 优先从X-Forwarded-For获取
-	xForwardedFor := c.Ctx.Input.Header("X-Forwarded-For")
-	if xForwardedFor != "" {
-		ips := strings.Split(xForwardedFor, ",")
-		if len(ips) > 0 {
-			return strings.TrimSpace(ips[0])
-		}
-	}
-
-	// 从X-Real-IP获取
-	xRealIP := c.Ctx.Input.Header("X-Real-IP")
-	if xRealIP != "" {
-		return xRealIP
-	}
-
-	// 从RemoteAddr获取
-	remoteAddr := c.Ctx.Input.Context.Request.RemoteAddr
-	if remoteAddr != "" {
-		// 去掉端口号
-		if colonIndex := strings.LastIndex(remoteAddr, ":"); colonIndex != -1 {
-			return remoteAddr[:colonIndex]
-		}
-		return remoteAddr
-	}
-
-	return "Unknown"
-}
-
-// getApiNameByPath 通过接口路径获取接口名称
-func (c *TrackingController) getApiNameByPath(apiPath string) string {
-	// 首先从数据库查找映射关系
-	var count int64
-	models.DB.Model(&models.ApiPathMapping{}).Where("api_path = ? AND status = 1", apiPath).Count(&count)
-	if count > 0 {
-		var mapping models.ApiPathMapping
-		models.DB.Where("api_path = ? AND status = 1", apiPath).First(&mapping)
-		return mapping.ApiName
-	}
-
-	// 如果数据库中没有找到,使用默认的路径解析规则
-	return c.parseApiNameFromPath(apiPath)
-}
-
-// parseApiNameFromPath 从路径解析接口名称
-func (c *TrackingController) parseApiNameFromPath(apiPath string) string {
-	// 移除前缀 /apiv1/
-	if strings.HasPrefix(apiPath, "/apiv1/") {
-		apiPath = strings.TrimPrefix(apiPath, "/apiv1/")
-	}
-
-	// 移除开头的 /
-	apiPath = strings.TrimPrefix(apiPath, "/")
-
-	// 将路径转换为更友好的名称
-	parts := strings.Split(apiPath, "/")
-	var nameParts []string
-
-	for _, part := range parts {
-		if part != "" {
-			// 将下划线替换为空格,并转换为标题格式
-			part = strings.ReplaceAll(part, "_", " ")
-			nameParts = append(nameParts, strings.Title(part))
-		}
-	}
-
-	if len(nameParts) > 0 {
-		return strings.Join(nameParts, " ")
-	}
-
-	return "未知接口"
-}
-
-// GetTrackingRecords 获取埋点记录列表
-func (c *TrackingController) GetTrackingRecords() {
-	// 获取查询参数
-	userIDStr := c.GetString("user_id")
-	apiPath := c.GetString("api_path")
-	pageStr := c.GetString("page", "1")
-	pageSizeStr := c.GetString("page_size", "20")
-
-	// 转换分页参数
-	page, err := strconv.Atoi(pageStr)
-	if err != nil || page < 1 {
-		page = 1
-	}
-
-	pageSize, err := strconv.Atoi(pageSizeStr)
-	if err != nil || pageSize < 1 || pageSize > 100 {
-		pageSize = 20
-	}
-
-	// 构建查询条件
-	query := models.DB.Model(&models.TrackingRecord{})
-
-	if userIDStr != "" {
-		userID, err := strconv.Atoi(userIDStr)
-		if err == nil {
-			query = query.Where("user_id = ?", userID)
-		}
-	}
-
-	if apiPath != "" {
-		query = query.Where("api_path LIKE ?", "%"+apiPath+"%")
-	}
-
-	// 获取总数
-	var total int64
-	query.Count(&total)
-
-	// 分页查询
-	var records []models.TrackingRecord
-	offset := (page - 1) * pageSize
-	result := query.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&records)
-
-	if result.Error != nil {
-		c.Data["json"] = map[string]interface{}{
-			"code":    500,
-			"message": "查询失败: " + result.Error.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 返回结果
-	c.Data["json"] = map[string]interface{}{
-		"code":    200,
-		"message": "查询成功",
-		"data": map[string]interface{}{
-			"list":      records,
-			"total":     total,
-			"page":      page,
-			"page_size": pageSize,
-		},
-	}
-	c.ServeJSON()
-}
-
-// AddApiMapping 添加接口路径映射
-func (c *TrackingController) AddApiMapping() {
-	var req struct {
-		ApiPath string `json:"api_path"`
-		ApiName string `json:"api_name"`
-		ApiDesc string `json:"api_desc"`
-	}
-
-	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
-	if err != nil {
-		c.Data["json"] = map[string]interface{}{
-			"code":    400,
-			"message": "请求参数解析失败",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 验证必要参数
-	if req.ApiPath == "" || req.ApiName == "" {
-		c.Data["json"] = map[string]interface{}{
-			"code":    400,
-			"message": "接口路径和接口名称不能为空",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 检查是否已存在
-	var existingMapping models.ApiPathMapping
-	result := models.DB.Where("api_path = ?", req.ApiPath).First(&existingMapping)
-	if result.Error == nil {
-		c.Data["json"] = map[string]interface{}{
-			"code":    400,
-			"message": "该接口路径已存在映射",
-		}
-		c.ServeJSON()
-		return
-	}
-
-	// 创建新映射
-	mapping := models.ApiPathMapping{
-		ApiPath: req.ApiPath,
-		ApiName: req.ApiName,
-		ApiDesc: req.ApiDesc,
-		Status:  1,
-	}
-
-	result = models.DB.Create(&mapping)
-	if result.Error != nil {
-		c.Data["json"] = map[string]interface{}{
-			"code":    500,
-			"message": "添加映射失败: " + result.Error.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	c.Data["json"] = map[string]interface{}{
-		"code":    200,
-		"message": "添加映射成功",
-		"data":    mapping,
-	}
-	c.ServeJSON()
-}
-
-// GetApiMappings 获取接口路径映射列表
-func (c *TrackingController) GetApiMappings() {
-	var mappings []models.ApiPathMapping
-	result := models.DB.Where("status = 1").Order("created_at DESC").Find(&mappings)
-
-	if result.Error != nil {
-		c.Data["json"] = map[string]interface{}{
-			"code":    500,
-			"message": "查询失败: " + result.Error.Error(),
-		}
-		c.ServeJSON()
-		return
-	}
-
-	c.Data["json"] = map[string]interface{}{
-		"code":    200,
-		"message": "查询成功",
-		"data":    mappings,
-	}
-	c.ServeJSON()
-}
-
-// generateRequestID 生成请求ID
-func (c *TrackingController) generateRequestID() string {
-	// 使用时间戳和随机数生成唯一ID
-	timestamp := time.Now().UnixNano()
-	randomNum := rand.Intn(10000)
-	return fmt.Sprintf("%d_%d", timestamp, randomNum)
-}

BIN
shudao-go-backend/favicon.ico


+ 0 - 48
shudao-go-backend/go.mod

@@ -1,48 +0,0 @@
-module shudao-chat-go
-
-go 1.24.0
-
-toolchain go1.24.4
-
-require (
-	github.com/beego/beego/v2 v2.1.0
-	github.com/leanovate/gopter v0.2.11
-	github.com/stretchr/testify v1.11.1 // indirect
-)
-
-require (
-	github.com/aws/aws-sdk-go v1.55.8
-	github.com/fogleman/gg v1.3.0
-	github.com/golang-jwt/jwt/v5 v5.3.0
-	golang.org/x/crypto v0.37.0
-	gorm.io/driver/mysql v1.6.0
-	gorm.io/gorm v1.30.1
-)
-
-require (
-	filippo.io/edwards25519 v1.1.0 // indirect
-	github.com/beorn7/perks v1.0.1 // indirect
-	github.com/cespare/xxhash/v2 v2.2.0 // indirect
-	github.com/go-sql-driver/mysql v1.8.1 // indirect
-	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
-	github.com/golang/protobuf v1.5.4 // indirect
-	github.com/hashicorp/golang-lru v0.5.4 // indirect
-	github.com/jinzhu/inflection v1.0.0 // indirect
-	github.com/jinzhu/now v1.1.5 // indirect
-	github.com/jmespath/go-jmespath v0.4.0 // indirect
-	github.com/kr/text v0.2.0 // indirect
-	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
-	github.com/mitchellh/mapstructure v1.5.0 // indirect
-	github.com/pkg/errors v0.9.1 // indirect
-	github.com/prometheus/client_golang v1.15.1 // indirect
-	github.com/prometheus/client_model v0.3.0 // indirect
-	github.com/prometheus/common v0.42.0 // indirect
-	github.com/prometheus/procfs v0.9.0 // indirect
-	github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
-	golang.org/x/image v0.32.0 // indirect
-	golang.org/x/net v0.39.0 // indirect
-	golang.org/x/sys v0.32.0 // indirect
-	golang.org/x/text v0.30.0 // indirect
-	google.golang.org/protobuf v1.35.2 // indirect
-	gopkg.in/yaml.v3 v3.0.1 // indirect
-)

+ 0 - 685
shudao-go-backend/go.sum

@@ -1,685 +0,0 @@
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
-cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
-cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
-cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
-cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
-cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
-cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
-cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
-cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
-cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
-cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
-cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
-cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
-cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
-cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
-cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
-cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
-cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
-cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
-cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
-cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
-cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
-cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
-cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
-cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
-cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
-cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
-cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
-cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
-cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
-cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
-cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
-cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
-filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
-github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
-github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
-github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
-github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ=
-github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk=
-github.com/beego/beego/v2 v2.1.0 h1:Lk0FtQGvDQCx5V5yEu4XwDsIgt+QOlNjt5emUa3/ZmA=
-github.com/beego/beego/v2 v2.1.0/go.mod h1:6h36ISpaxNrrpJ27siTpXBG8d/Icjzsc7pU1bWpp0EE=
-github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
-github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
-github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
-github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
-github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
-github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
-github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
-github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
-github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
-github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw=
-github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
-github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
-github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
-github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
-github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
-github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
-github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
-github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
-github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
-github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
-github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
-github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
-github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
-github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
-github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
-github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
-github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
-github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
-github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
-github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
-github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
-github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
-github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
-github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
-github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
-github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
-github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
-github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
-github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
-github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
-github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
-github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
-github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
-github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
-github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
-github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
-github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
-github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
-github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
-github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
-github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
-github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
-github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
-github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
-github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
-github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
-github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
-github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
-github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
-github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
-github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
-github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
-github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
-github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4=
-github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c=
-github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
-github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
-github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
-github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
-github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
-github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
-github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
-github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
-github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
-github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
-github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
-github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
-github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
-github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
-github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
-github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
-github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
-github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
-github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
-github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
-github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
-github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
-github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
-github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
-github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
-github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
-github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
-github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 h1:DAYUYH5869yV94zvCES9F51oYtN5oGlwjxJJz7ZCnik=
-github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
-github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
-github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
-github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
-github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
-github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
-github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
-github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
-github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
-github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
-github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
-github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
-github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
-github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
-github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
-go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
-go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
-go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
-go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
-go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
-go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
-go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
-go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
-golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
-golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
-golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
-golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
-golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
-golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
-golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
-golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
-golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
-golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
-golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
-golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
-golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
-golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
-golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
-golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
-golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
-golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
-golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
-golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
-google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
-google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
-google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
-google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
-google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
-google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
-google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
-google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
-google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
-google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
-google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
-google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
-google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
-google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
-google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
-google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
-google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
-google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
-google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
-google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
-google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
-google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
-google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
-google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
-google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
-google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
-google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
-google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
-google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
-google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
-gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
-gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
-gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4=
-gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
-honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
-rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
-rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

+ 0 - 37
shudao-go-backend/main.go

@@ -1,37 +0,0 @@
-package main
-
-import (
-	_ "shudao-chat-go/routers"
-	"shudao-chat-go/controllers"
-	"shudao-chat-go/utils"
-
-	beego "github.com/beego/beego/v2/server/web"
-	"github.com/beego/beego/v2/server/web/filter/cors"
-)
-
-func main() {
-	beego.BConfig.CopyRequestBody = true
-
-	// 注册认证中间件
-	beego.InsertFilter("*", beego.BeforeRouter, utils.AuthMiddleware)
-	beego.InsertFilter("*", beego.BeforeExec, utils.AuthMiddleware)
-
-	// CORS配置
-	beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{
-		AllowOrigins:     []string{"*"},
-		AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
-		AllowHeaders:     []string{"Origin", "Authorization", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Content-Type", "token"},
-		ExposeHeaders:    []string{"Content-Length", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Content-Type"},
-		AllowCredentials: true,
-	}))
-
-	// 静态文件路径
-	beego.SetStaticPath("/assets", "assets")
-	beego.SetStaticPath("/static", "static")
-	beego.SetStaticPath("/src", "assets")
-
-	// 启动心跳任务
-	controllers.StartChromaSearchHeartbeatTask()
-
-	beego.Run()
-}

+ 0 - 78
shudao-go-backend/models/chat.go

@@ -1,78 +0,0 @@
-// Package models - chat.go
-//
-// ⚠️ DEPRECATED NOTICE (弃用说明)
-// ================================================================================
-// 本文件中的数据模型定义仍在使用中,但AI对话核心逻辑已迁移至微服务。
-// ================================================================================
-package models
-
-// AIConversation AI对话主表
-type AIConversation struct {
-	BaseModel
-	UserId       uint64 `gorm:"type:bigint;not null;comment:用户ID" json:"user_id"`
-	Content      string `gorm:"type:text;not null;comment:对话内容" json:"content"`
-	BusinessType int    `gorm:"type:tinyint;not null;comment:业务类型(0、AI问答,1、安全培训,2、AI写作,3、考试工坊)" json:"business_type"`
-	ExamName     string `gorm:"type:varchar(255);not null;comment:考试名称" json:"exam_name"`
-	//步骤
-	Step int `gorm:"type:int;not null;comment:步骤" json:"step"`
-	//封面图
-	CoverImage string `gorm:"type:varchar(1000);not null;comment:封面图" json:"cover_image"`
-	//PPTjson地址
-	PPTJsonUrl string `gorm:"type:varchar(1000);not null;comment:PPTjson地址" json:"ppt_json_url"`
-	//PPTJson内容
-	PPTJsonContent string `gorm:"type:longtext;not null;comment:PPTjson内容" json:"ppt_json_content"`
-	//PPT大纲
-	PPTOutline string `gorm:"type:longtext;not null;comment:PPT大纲" json:"ppt_outline"`
-}
-
-// TableName 指定表名
-func (AIConversation) TableName() string {
-	return "ai_conversation"
-}
-
-// AIMessage AI对话消息表
-type AIMessage struct {
-	BaseModel
-	UserId       uint64 `gorm:"type:bigint;not null;comment:用户ID" json:"user_id"`
-	Content      string `gorm:"type:longtext;not null;comment:对话内容" json:"content"`
-	Type         string `gorm:"type:varchar(20);not null;comment:对话类型(user、ai)" json:"type"`
-	UserFeedback int    `gorm:"type:tinyint;not null;comment:用户反馈(0、无反馈,2、满意(赞),3、不满意(踩))" json:"user_feedback"`
-	//绑定上一句的用户问题ID
-	PrevUserId       uint64 `gorm:"type:bigint;not null;comment:上一句的用户问题ID" json:"prev_user_id"`
-	AIConversationId uint64 `gorm:"type:bigint;not null;comment:AI对话ID" json:"ai_conversation_id"`
-	//搜索来源
-	SearchSource string `gorm:"type:longtext;not null;comment:搜索来源" json:"search_source"`
-	//猜你想问
-	GuessYouWant string `gorm:"type:varchar(255);not null;comment:猜你想问" json:"guess_you_want"`
-}
-
-// TableName 指定表名
-func (AIMessage) TableName() string {
-	return "ai_message"
-}
-
-// 索引文件表
-type IndexFile struct {
-	BaseModel
-	FileName string `gorm:"type:varchar(255);not null;comment:文件名" json:"file_name"`
-	FilePath string `gorm:"type:varchar(1000);not null;comment:文件路径" json:"file_path"`
-	//编码
-	Encoding string `gorm:"type:varchar(255);not null;comment:编码" json:"encoding"`
-}
-
-// TableName 指定表名
-func (IndexFile) TableName() string {
-	return "index_file"
-}
-
-// 问答QA对表
-type QA struct {
-	BaseModel
-	Question string `gorm:"type:varchar(255);not null;comment:问题" json:"question"`
-	Answer   string `gorm:"type:varchar(255);not null;comment:答案" json:"answer"`
-}
-
-// TableName 指定表名
-func (QA) TableName() string {
-	return "qa"
-}

+ 0 - 51
shudao-go-backend/models/exam.go

@@ -1,51 +0,0 @@
-package models
-
-// 试卷表
-type ExamPaper struct {
-	ExamName         string `gorm:"type:varchar(100);not null;comment:试卷名称" json:"exam_name"`
-	ExamType         string `gorm:"type:varchar(50);not null;comment:试卷类型" json:"exam_type"`
-	TotalScore       int    `gorm:"type:int;not null;comment:试卷总分" json:"total_score"`
-	TotalQuestions   int    `gorm:"type:int;not null;comment:总题数" json:"total_questions"`
-	GenerationMethod string `gorm:"type:varchar(20);not null;comment:生成方式" json:"generation_method"`
-	GenerationTime   string `gorm:"type:varchar(50);not null;comment:生成时间" json:"generation_time"`
-	UserID           *uint  `gorm:"type:bigint;comment:用户ID" json:"user_id"`
-	Status           int8   `gorm:"type:tinyint;default:1;comment:状态" json:"status"`
-	BaseModel
-
-	// 关联字段
-	QuestionConfigs []ExamQuestionConfig `gorm:"foreignKey:ExamPapersID" json:"question_configs"`
-	Questions       []ExamQuestion       `gorm:"foreignKey:ExamPapersID" json:"questions"`
-}
-
-// 题型配置表
-type ExamQuestionConfig struct {
-	ExamPapersID     uint   `gorm:"type:bigint;not null;comment:试卷ID" json:"exam_papers_id"`
-	QuestionType     string `gorm:"type:varchar(20);not null;comment:题型" json:"question_type"`
-	ScorePerQuestion int    `gorm:"type:int;not null;comment:每题分值" json:"score_per_question"`
-	TotalScore       int    `gorm:"type:int;not null;comment:该题型总分" json:"total_score"`
-	QuestionCount    int    `gorm:"type:int;not null;comment:题目数量" json:"question_count"`
-	BaseModel
-}
-
-// 题目表
-type ExamQuestion struct {
-	ExamPapersID   uint   `gorm:"type:bigint;not null;comment:试卷ID" json:"exam_papers_id"`
-	QuestionType   string `gorm:"type:varchar(20);not null;comment:题型" json:"question_type"`
-	QuestionIndex  int    `gorm:"type:int;not null;comment:题目序号" json:"question_index"`
-	QuestionText   string `gorm:"type:text;not null;comment:题目内容" json:"question_text"`
-	Options        []byte `gorm:"type:json;comment:选项" json:"options"`
-	CorrectAnswer  string `gorm:"type:varchar(50);comment:正确答案" json:"correct_answer"`
-	CorrectAnswers []byte `gorm:"type:json;comment:正确答案(多选题)" json:"correct_answers"`
-	AnswerOutline  []byte `gorm:"type:json;comment:答题要点" json:"answer_outline"`
-	BaseModel
-}
-
-// 用户答案表
-type ExamUserAnswer struct {
-	ExamPapersID    uint   `gorm:"type:bigint;not null;comment:试卷ID" json:"exam_papers_id"`
-	UserID          uint   `gorm:"type:bigint;not null;comment:用户ID" json:"user_id"`
-	ExamQuestionsID uint   `gorm:"type:bigint;not null;comment:题目ID" json:"exam_questions_id"`
-	UserAnswer      string `gorm:"type:varchar(50);comment:用户答案" json:"user_answer"`
-	UserAnswers     []byte `gorm:"type:json;comment:用户答案(多选题)" json:"user_answers"`
-	IsCorrect       *int8  `gorm:"type:tinyint;comment:是否正确" json:"is_correct"`
-}

+ 0 - 78
shudao-go-backend/models/mysql.go

@@ -1,78 +0,0 @@
-package models
-
-import (
-	"fmt"
-
-	"time"
-
-	"github.com/beego/beego/v2/server/web"
-	"gorm.io/driver/mysql"
-	"gorm.io/gorm"
-	"gorm.io/gorm/logger"
-	"gorm.io/gorm/schema"
-)
-
-var DB *gorm.DB
-var err error
-
-func init() {
-	mysqluser, _ := web.AppConfig.String("mysqluser")
-	mysqlpass, _ := web.AppConfig.String("mysqlpass")
-	mysqlurls, _ := web.AppConfig.String("mysqlurls")
-	mysqldb, _ := web.AppConfig.String("mysqldb")
-	httpport, _ := web.AppConfig.String("mysqlhttpport")
-	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&collation=utf8mb4_unicode_ci&parseTime=True&loc=Local",
-		mysqluser, mysqlpass, mysqlurls, httpport, mysqldb)
-	DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
-		SkipDefaultTransaction:                   true,                                //禁用默认事务
-		Logger:                                   logger.Default.LogMode(logger.Info), //日志配置
-		DisableForeignKeyConstraintWhenMigrating: true,                                //禁用外键约束
-		NamingStrategy: schema.NamingStrategy{
-			SingularTable: true,
-		}, //表名单数
-	})
-
-	// 配置连接池
-	if err == nil {
-		sqlDB, err := DB.DB()
-		if err == nil {
-			// 设置最大打开连接数
-			sqlDB.SetMaxOpenConns(100)
-			// 设置最大空闲连接数
-			sqlDB.SetMaxIdleConns(10)
-			// 设置连接最大生存时间
-			sqlDB.SetConnMaxLifetime(time.Hour)
-			// 设置连接最大空闲时间
-			sqlDB.SetConnMaxIdleTime(time.Minute * 30)
-		}
-	}
-	if err != nil {
-		fmt.Println("连接mysql数据库失败", err)
-	} else {
-		fmt.Println("连接mysql数据库成功")
-	}
-	DB.Callback().Create().Before("gorm:create").Register("set_create_time", setCreateTimeCallback)
-	// 全局回调函数,在更新记录之前设置更新时间
-	DB.Callback().Update().Before("gorm:update").Register("set_update_time", setUpdateTimeCallback)
-}
-
-// GetUnix 获取当前时间戳
-func GetUnix() int64 {
-	return time.Now().Unix()
-}
-
-// 全局回调函数,在创建记录之前设置新增时间
-func setCreateTimeCallback(db *gorm.DB) {
-	if _, ok := db.Statement.Schema.FieldsByName["CreatedAt"]; ok {
-		now := int(GetUnix())
-		db.Statement.SetColumn("CreatedAt", now)
-	}
-}
-
-// 全局回调函数,在更新记录之前设置更新时间
-func setUpdateTimeCallback(db *gorm.DB) {
-	if _, ok := db.Statement.Schema.FieldsByName["UpdateTime"]; ok {
-		now := int(GetUnix())
-		db.Statement.SetColumn("UpdateTime", now)
-	}
-}

+ 0 - 46
shudao-go-backend/models/points_consumption_log.go

@@ -1,46 +0,0 @@
-package models
-
-import "time"
-
-// PointsConsumptionLog 积分消费记录
-type PointsConsumptionLog struct {
-	ID             uint       `gorm:"primarykey;autoIncrement" json:"id"`
-	UserID         string     `gorm:"type:varchar(255);not null;index" json:"user_id"`
-	FileName       string     `gorm:"type:varchar(500);not null" json:"file_name"`
-	FileURL        string     `gorm:"type:text" json:"file_url"`
-	PointsConsumed int        `gorm:"not null;default:10" json:"points_consumed"`
-	BalanceAfter   int        `gorm:"not null" json:"balance_after"`
-	CreatedAt      *time.Time `gorm:"column:created_at" json:"created_at"`
-}
-
-// TableName 指定表名
-func (PointsConsumptionLog) TableName() string {
-	return "points_consumption_log"
-}
-
-// CreateConsumptionLog 创建消费记录
-func CreateConsumptionLog(log *PointsConsumptionLog) error {
-	// 使用原始SQL插入,避免GORM的时间字段处理问题
-	result := DB.Exec(
-		"INSERT INTO points_consumption_log (user_id, file_name, file_url, points_consumed, balance_after) VALUES (?, ?, ?, ?, ?)",
-		log.UserID, log.FileName, log.FileURL, log.PointsConsumed, log.BalanceAfter,
-	)
-	return result.Error
-}
-
-// GetConsumptionHistory 获取用户消费记录(按时间倒序)
-func GetConsumptionHistory(userID string, page, pageSize int) ([]PointsConsumptionLog, int64, error) {
-	var logs []PointsConsumptionLog
-	var total int64
-
-	DB.Model(&PointsConsumptionLog{}).Where("user_id = ?", userID).Count(&total)
-
-	offset := (page - 1) * pageSize
-	err := DB.Where("user_id = ?", userID).
-		Order("created_at DESC").
-		Offset(offset).
-		Limit(pageSize).
-		Find(&logs).Error
-
-	return logs, total, err
-}

+ 0 - 13
shudao-go-backend/models/prompt.go

@@ -1,13 +0,0 @@
-package models
-
-//各模块的提示词拼接库0、AI问答,1、安全培训,2、AI写作,3、考试工坊
-type Prompt struct {
-	BaseModel
-	Prompt       string `gorm:"type:text;not null;comment:提示词" json:"prompt"`
-	BusinessType int    `gorm:"type:tinyint;not null;comment:业务类型(0、AI问答,1、安全培训,2、AI写作,3、考试工坊)" json:"business_type"`
-}
-
-// TableName 指定表名
-func (Prompt) TableName() string {
-	return "prompt"
-}

+ 0 - 112
shudao-go-backend/models/scene.go

@@ -1,112 +0,0 @@
-package models
-
-// 总场景
-type Scene struct {
-	BaseModel
-	SceneName string `gorm:"type:varchar(255);not null;comment:场景名称" json:"scene_name"` //桥梁、隧道、特种设备、加油站、高速运营公路
-	//英文名
-	SceneEnName string `gorm:"type:varchar(255);not null;comment:场景英文名" json:"scene_en_name"`
-}
-
-// TableName 指定表名
-func (Scene) TableName() string {
-	return "scene"
-}
-
-// 一级场景
-type FirstScene struct {
-	BaseModel
-	FirstSceneName string `gorm:"type:varchar(255);not null;comment:一级场景名称" json:"first_scene_name"`
-	SceneID        int    `gorm:"type:int;not null;comment:场景ID" json:"scene_id"`
-	//关联表
-	Scene Scene `gorm:"foreignKey:SceneID"`
-}
-
-// TableName 指定表名
-func (FirstScene) TableName() string {
-	return "first_scene"
-}
-
-// 二级场景
-type SecondScene struct {
-	BaseModel
-	SecondSceneName string `gorm:"type:varchar(255);not null;comment:二级场景名称" json:"second_scene_name"`
-	FirstSceneID    int    `gorm:"type:int;not null;comment:一级场景ID" json:"first_scene_id"`
-	//关联表
-	FirstScene FirstScene `gorm:"foreignKey:FirstSceneID"`
-}
-
-// TableName 指定表名
-func (SecondScene) TableName() string {
-	return "second_scene"
-}
-
-// 三级场景
-type ThirdScene struct {
-	BaseModel
-	ThirdSceneName string `gorm:"type:varchar(255);not null;comment:三级场景名称" json:"third_scene_name"`
-	SecondSceneID  int    `gorm:"type:int;not null;comment:二级场景ID" json:"second_scene_id"`
-	//关联表
-	SecondScene SecondScene `gorm:"foreignKey:SecondSceneID"`
-	//正确示例图
-	CorrectExampleImage string `gorm:"type:varchar(255);not null;comment:正确示例图" json:"correct_example_image"`
-	//错误示例图
-	WrongExampleImage string `gorm:"type:varchar(255);not null;comment:错误示例图" json:"wrong_example_image"`
-}
-
-// TableName 指定表名
-func (ThirdScene) TableName() string {
-	return "third_scene"
-}
-
-// 识别记录
-type RecognitionRecord struct {
-	BaseModel
-	//原图地址
-	OriginalImageUrl string `gorm:"type:text;not null;comment:原图地址" json:"original_image_url"`
-	//识别结果图地址
-	RecognitionImageUrl string `gorm:"type:text;not null;comment:识别结果图地址" json:"recognition_image_url"`
-	//用户ID
-	UserID int `gorm:"type:int;not null;comment:用户ID" json:"user_id"`
-
-	//title
-	Title string `gorm:"type:varchar(255);not null;comment:标题" json:"title"`
-	//描述
-	Description string `gorm:"type:text;not null;comment:描述" json:"description"`
-	//标签
-	Labels string `gorm:"type:varchar(255);not null;comment:标签" json:"labels"`
-	//主标签
-	TagType string `gorm:"type:varchar(255);not null;comment:标签类型" json:"tag_type"`
-	//详情页的二级场景
-	SecondScene string `gorm:"type:varchar(1000);comment:详情页的二级场景" json:"second_scene"`
-	//详情页的三级场景
-	ThirdScene string `gorm:"type:varchar(1000);comment:详情页的三级场景" json:"third_scene"`
-	//评价系统
-	//场景匹配(是/否)
-	SceneMatch int `gorm:"type:int;not null;comment:场景匹配(0、否,1、是)" json:"scene_match"`
-	//提示准确(是/否)
-	TipAccuracy int `gorm:"type:int;not null;comment:提示准确(0、否,1、是)" json:"tip_accuracy"`
-	//效果评价(1-5)
-	EffectEvaluation int `gorm:"type:int;not null;comment:效果评价(1-5)" json:"effect_evaluation"`
-	//用户备注
-	UserRemark string `gorm:"type:varchar(200);comment:用户备注" json:"user_remark"`
-}
-
-// TableName 指定表名
-func (RecognitionRecord) TableName() string {
-	return "recognition_record"
-}
-
-// 识别记录对应的二级场景表
-type RecognitionRecordSecondScene struct {
-	BaseModel
-	RecognitionRecordID int `gorm:"type:int;not null;comment:识别记录ID" json:"recognition_record_id"`
-	SecondSceneID       int `gorm:"type:int;not null;comment:二级场景ID" json:"second_scene_id"`
-	//关联表
-	SecondScene SecondScene `gorm:"foreignKey:SecondSceneID"`
-}
-
-// TableName 指定表名
-func (RecognitionRecordSecondScene) TableName() string {
-	return "recognition_record_second_scene"
-}

+ 0 - 12
shudao-go-backend/models/test.go

@@ -1,12 +0,0 @@
-package models
-
-type Test struct {
-	BaseModel
-	Title1 string `gorm:"type:varchar(255);not null;comment:标题1" json:"title1"`
-	Title2 string `gorm:"type:varchar(255);not null;comment:标题2" json:"title2"`
-	Title3 string `gorm:"type:varchar(255);not null;comment:标题3" json:"title3"`
-}
-
-func (Test) TableName() string {
-	return "test"
-}

+ 0 - 108
shudao-go-backend/models/total.go

@@ -1,108 +0,0 @@
-package models
-
-//通用结构表
-type BaseModel struct {
-	ID        uint  `gorm:"primarykey;autoIncrement" json:"id"`
-	CreatedAt int64 `gorm:"comment:创建时间" json:"created_at"`
-	UpdatedAt int64 `gorm:"comment:更新时间" json:"updated_at"`
-	DeletedAt int64 `gorm:"comment:删除时间" json:"deleted_at"`
-	IsDeleted int   `gorm:"comment:是否删除 1:是 0:否" json:"is_deleted"`
-}
-
-// User 用户表结构
-type User struct {
-	Username string `gorm:"type:varchar(50);uniqueIndex;not null;comment:用户名" json:"username"`
-	Password string `gorm:"type:varchar(255);not null;comment:密码" json:"password"`
-	Email    string `gorm:"type:varchar(100);uniqueIndex;comment:邮箱" json:"email"`
-	Phone    string `gorm:"type:varchar(20);comment:手机号" json:"phone"`
-	Nickname string `gorm:"type:varchar(50);comment:昵称" json:"nickname"`
-	Avatar   string `gorm:"type:varchar(255);comment:头像URL" json:"avatar"`
-	Status   int    `gorm:"type:tinyint;default:1;comment:状态 1:正常 0:禁用" json:"status"`
-	Role     string `gorm:"type:varchar(20);default:'user';comment:角色" json:"role"`
-	BaseModel
-}
-
-// TableName 指定表名
-func (User) TableName() string {
-	return "user"
-}
-
-//推荐问题表
-type RecommendQuestion struct {
-	BaseModel
-	Question string `gorm:"type:varchar(255);not null;comment:问题" json:"question"`
-}
-
-// TableName 指定表名
-func (RecommendQuestion) TableName() string {
-	return "recommend_question"
-}
-
-//反馈问题表
-type FeedbackQuestion struct {
-	BaseModel
-	FeedbackType      int    `gorm:"type:tinyint;not null;comment:反馈类型(1、问题反馈,2、界面优化,3、体验问题,4、其他)" json:"feedback_type"`
-	FeedbackContent   string `gorm:"type:varchar(255);not null;comment:反馈内容" json:"feedback_content"`
-	FeedbackUserPhone string `gorm:"type:varchar(255);not null;comment:反馈用户手机号" json:"feedback_user_phone"`
-	FeedbackImg       string `gorm:"type:varchar(255);not null;comment:反馈图片" json:"feedback_img"`
-	UserID            uint   `gorm:"type:int;not null;comment:用户ID" json:"user_id"`
-	User              User   `gorm:"foreignKey:UserID;references:ID" json:"user"`
-}
-
-// TableName 指定表名
-func (FeedbackQuestion) TableName() string {
-	return "feedback_question"
-}
-
-//政策文件表
-type PolicyFile struct {
-	BaseModel
-	PolicyName    string `gorm:"type:varchar(255);not null;comment:政策名称" json:"policy_name"`
-	PolicyContent string `gorm:"type:varchar(255);not null;comment:政策内容" json:"policy_content"`
-	PolicyType    int    `gorm:"type:tinyint;not null;comment:政策类型(0、全部,1、国家法规,2、行业法规,3、地方法规,4、内部条例)" json:"policy_type"`
-	PolicyFileUrl string `gorm:"type:varchar(255);not null;comment:政策文件URL" json:"policy_file_url"`
-	//政策部门
-	PolicyDepartment string `gorm:"type:varchar(255);not null;comment:政策部门" json:"policy_department"`
-	//查看次数
-	ViewCount int `gorm:"type:int;not null;comment:查看次数" json:"view_count"`
-	//文件类型
-	FileType int `gorm:"type:tinyint;not null;comment:文件类型(0、pdf,1、word,2、excel,3、ppt,4、txt,5、其他)" json:"file_type"`
-	//文件标签
-	FileTag string `gorm:"type:varchar(255);not null;comment:文件标签" json:"file_tag"`
-	//颁布时间
-	PublishTime int64 `gorm:"type:int;not null;comment:颁布时间" json:"publish_time"`
-}
-
-// TableName 指定表名
-func (PolicyFile) TableName() string {
-	return "policy_file"
-}
-
-//AI问答和安全培训功能卡片表
-type FunctionCard struct {
-	BaseModel	
-	FunctionTitle string `gorm:"type:varchar(255);not null;comment:功能标题" json:"function_title"`
-	FunctionType int    `gorm:"type:tinyint;not null;comment:功能类型(0、AI问答,1、安全培训)" json:"function_type"`
-	FunctionContent string `gorm:"type:varchar(255);not null;comment:功能内容" json:"function_content"`
-	//图标
-	FunctionIcon string `gorm:"type:varchar(255);not null;comment:图标" json:"function_icon"`
-}
-
-// TableName 指定表名
-func (FunctionCard) TableName() string {
-	return "function_card"
-}
-
-//AI问答和安全培训底部热点问题表
-type HotQuestion struct {
-	BaseModel
-	Question string `gorm:"type:varchar(255);not null;comment:问题" json:"question"`
-	QuestionType int    `gorm:"type:tinyint;not null;comment:问题类型(0、AI问答,1、安全培训)" json:"question_type"`
-	//图标
-	QuestionIcon string `gorm:"type:varchar(255);not null;comment:图标" json:"question_icon"`
-}
-
-// TableName 指定表名
-func (HotQuestion) TableName() string {
-	return "hot_question"
-}

+ 0 - 35
shudao-go-backend/models/tracking.go

@@ -1,35 +0,0 @@
-package models
-
-// 操作埋点记录表
-type TrackingRecord struct {
-	BaseModel
-	UserID    int    `gorm:"type:int;not null;comment:用户ID" json:"user_id"`
-	ApiPath   string `gorm:"type:varchar(255);not null;comment:接口路径" json:"api_path"`
-	ApiName   string `gorm:"type:varchar(255);not null;comment:接口名称" json:"api_name"`
-	Method    string `gorm:"type:varchar(10);not null;comment:请求方法" json:"method"`
-	UserAgent string `gorm:"type:varchar(500);comment:用户代理" json:"user_agent"`
-	IpAddress string `gorm:"type:varchar(50);comment:IP地址" json:"ip_address"`
-	RequestId string `gorm:"type:varchar(100);comment:请求ID" json:"request_id"`
-	Status    int    `gorm:"type:tinyint;default:1;comment:状态 1:成功 0:失败" json:"status"`
-	Duration  int64  `gorm:"type:bigint;comment:请求耗时(毫秒)" json:"duration"`
-	ExtraData string `gorm:"type:text;comment:额外数据" json:"extra_data"`
-}
-
-// TableName 指定表名
-func (TrackingRecord) TableName() string {
-	return "tracking_record"
-}
-
-// 接口路径映射表
-type ApiPathMapping struct {
-	BaseModel
-	ApiPath string `gorm:"type:varchar(255);not null;uniqueIndex;comment:接口路径" json:"api_path"`
-	ApiName string `gorm:"type:varchar(255);not null;comment:接口名称" json:"api_name"`
-	ApiDesc string `gorm:"type:varchar(500);comment:接口描述" json:"api_desc"`
-	Status  int    `gorm:"type:tinyint;default:1;comment:状态 1:启用 0:禁用" json:"status"`
-}
-
-// TableName 指定表名
-func (ApiPathMapping) TableName() string {
-	return "api_path_mapping"
-}

+ 0 - 81
shudao-go-backend/models/user_data.go

@@ -1,81 +0,0 @@
-package models
-
-import "time"
-
-// UserData 用户数据表
-type UserData struct {
-	ID        uint       `gorm:"primarykey;autoIncrement" json:"id"`
-	CreatedAt time.Time  `gorm:"comment:创建时间" json:"created_at"`
-	UpdatedAt time.Time  `gorm:"comment:更新时间" json:"updated_at"`
-	DeletedAt *time.Time `gorm:"comment:删除时间" json:"deleted_at"`
-	IsDeleted int        `gorm:"comment:是否删除 1:是 0:否" json:"is_deleted"`
-
-	// 基本信息
-	UserID      string `gorm:"type:varchar(255);comment:用户ID" json:"user_id"`
-	Name        string `gorm:"type:varchar(255);comment:姓名" json:"name"`
-	LogicDelete string `gorm:"type:varchar(50);comment:逻辑删除标记" json:"logic_delete"`
-
-	// 组织信息
-	OrgCode   string `gorm:"type:varchar(255);comment:组织代码" json:"org_code"`
-	OrgName   string `gorm:"type:varchar(500);comment:组织名称" json:"org_name"`
-	OrgPath   string `gorm:"type:text;comment:组织路径" json:"org_path"`
-	OrgPathCN string `gorm:"type:text;comment:组织路径中文" json:"org_path_cn"`
-	OrgInfos  string `gorm:"type:text;comment:组织信息" json:"org_infos"`
-
-	// 用户详情
-	UserCode string `gorm:"type:varchar(255);comment:用户代码" json:"user_code"`
-	Sex      string `gorm:"type:varchar(10);comment:性别" json:"sex"`
-	Birthday string `gorm:"type:varchar(50);comment:生日" json:"birthday"`
-
-	// 身份信息
-	IdentificationNumber string `gorm:"type:varchar(50);comment:身份证号" json:"identification_number"`
-
-	// 籍贯民族信息
-	Nation      string `gorm:"type:varchar(100);comment:民族" json:"nation"`
-	NationCode  string `gorm:"type:varchar(50);comment:民族代码" json:"nation_code"`
-	NativePlace string `gorm:"type:varchar(255);comment:籍贯" json:"native_place"`
-
-	// 教育信息
-	EducationBackground     string `gorm:"type:varchar(255);comment:教育背景" json:"education_background"`
-	EducationBackgroundCode string `gorm:"type:varchar(50);comment:教育背景代码" json:"education_background_code"`
-
-	// 专业技术职务
-	ProfessionalAndTechnicalPositions     string `gorm:"type:varchar(255);comment:专业技术职务" json:"professional_and_technical_positions"`
-	ProfessionalAndTechnicalPositionsCode string `gorm:"type:varchar(50);comment:专业技术职务代码" json:"professional_and_technical_positions_code"`
-
-	// 政治面貌
-	PoliticCountenance     string `gorm:"type:varchar(255);comment:政治面貌" json:"politic_countenance"`
-	PoliticCountenanceCode string `gorm:"type:varchar(50);comment:政治面貌代码" json:"politic_countenance_code"`
-
-	// 职位信息
-	PositionName string `gorm:"type:varchar(255);comment:职位名称" json:"position_name"`
-	PositoinCode string `gorm:"type:varchar(50);comment:职位代码" json:"positoin_code"` // 注意:原数据库拼写为 positoin
-
-	// 入职信息
-	OnBoardDay string `gorm:"type:varchar(50);comment:入职日期" json:"on_board_day"`
-
-	// 联系方式
-	ContactNumber string `gorm:"type:varchar(50);comment:联系电话" json:"contact_number"`
-	Mobile        string `gorm:"type:varchar(50);comment:手机号" json:"mobile"`
-
-	// 其他字段
-	UserStatus string `gorm:"type:varchar(50);comment:用户状态" json:"user_status"`
-	FZJ        string `gorm:"type:varchar(255);comment:分组" json:"fzj"`
-	FZJDm      string `gorm:"type:varchar(50);comment:分组代码" json:"fzj_dm"`
-	HrRylb     string `gorm:"type:varchar(255);comment:人力资源人员类别" json:"hr_rylb"`
-
-	// 4A账号相关
-	AccountID string `gorm:"type:varchar(100);comment:4A账号ID" json:"accountID"`
-
-	// 积分系统
-	Points int `gorm:"default:20;comment:用户积分余额" json:"points"`
-
-	// 系统字段(这些字段如果存在会与BaseModel冲突,注释掉保留作为备用)
-	// DbCreatedAt string `gorm:"type:varchar(50);comment:数据库创建时间" json:"db_created_at"`
-	// RowNumber   int    `gorm:"comment:行号" json:"row_number"`
-}
-
-// TableName 指定表名
-func (UserData) TableName() string {
-	return "user_data"
-}

+ 0 - 123
shudao-go-backend/routers/router.go

@@ -1,123 +0,0 @@
-package routers
-
-import (
-	"shudao-chat-go/controllers"
-
-	beego "github.com/beego/beego/v2/server/web"
-)
-
-func init() {
-	// 前端页面路由 - 放在API路由之前
-	beego.Router("/", &controllers.FrontendController{}, "get:Index")
-	beego.Router("/stream-test", &controllers.FrontendController{}, "get:StreamTest")
-	beego.Router("/simple-stream-test", &controllers.FrontendController{}, "get:SimpleStreamTest")
-	beego.Router("/stream-chat-with-db-test", &controllers.FrontendController{}, "get:StreamChatWithDBTest")
-
-	ns := beego.NewNamespace("apiv1",
-		// 本地登录接口 (不需要认证)
-		beego.NSRouter("/auth/local_login", &controllers.LocalAuthController{}, "post:LocalLogin"),
-
-		//推荐问题
-		beego.NSRouter("/recommend_question", &controllers.TotalController{}, "get:GetRecommendQuestion"),
-		//提交意见反馈
-		beego.NSRouter("/submit_feedback", &controllers.TotalController{}, "post:SubmitFeedback"),
-		//返回政策文件
-		beego.NSRouter("/get_policy_file", &controllers.TotalController{}, "get:GetPolicyFile"),
-		//发送deepseek消息
-		beego.NSRouter("/send_deepseek_message", &controllers.ChatController{}, "post:SendDeepSeekMessage"),
-
-		// OSS上传相关路由
-		beego.NSRouter("/oss/upload", &controllers.ShudaoOssController{}, "post:Upload"),
-		// 新的OSS接口路由
-		beego.NSRouter("/oss/shudao/upload_image", &controllers.ShudaoOssController{}, "post:UploadImage"),
-		// 上传JSON文件接口
-		beego.NSRouter("/oss/shudao/upload_json", &controllers.ShudaoOssController{}, "post:UploadPPTJson"),
-		// OSS代理解析接口
-		beego.NSRouter("/oss/parse", &controllers.ShudaoOssController{}, "get:ParseOSS"),
-
-		//返回四条功能卡片
-		beego.NSRouter("/get_function_card", &controllers.TotalController{}, "get:GetFunctionCard"),
-		//返回三条热点问题
-		beego.NSRouter("/get_hot_question", &controllers.TotalController{}, "get:GetHotQuestion"),
-		//获取历史记录GetHistoryRecord()
-		beego.NSRouter("/get_history_record", &controllers.ChatController{}, "get:GetHistoryRecord"),
-		//修改考试题目
-		beego.NSRouter("/re_modify_question", &controllers.ExamController{}, "post:ReModifyQuestion"),
-		//重新生产单题
-		beego.NSRouter("/re_produce_single_question", &controllers.ChatController{}, "post:ReProduceSingleQuestion"),
-		// 生成考试提示词
-		beego.NSRouter("/exam/build_prompt", &controllers.PromptController{}, "post:BuildExamPrompt"),
-		// 单题生成提示词
-		beego.NSRouter("/exam/build_single_prompt", &controllers.PromptController{}, "post:BuildSingleQuestionPrompt"),
-		// 猜你想问
-		beego.NSRouter("/guess_you_want", &controllers.ChatController{}, "post:GuessYouWant"),
-		//隐患识别
-		beego.NSRouter("/hazard", &controllers.HazardController{}, "post:Hazard"),
-		// 点赞和踩
-		beego.NSRouter("/like_and_dislike", &controllers.TotalController{}, "post:LikeAndDislike"),
-		// 隐患识别获取历史记录
-		beego.NSRouter("/get_history_recognition_record", &controllers.SceneController{}, "get:GetHistoryRecognitionRecord"),
-		// 获取识别记录详情
-		beego.NSRouter("/get_recognition_record_detail", &controllers.SceneController{}, "get:GetRecognitionRecordDetail"),
-		//用户在输入框中每输入一个字,就调用一次阿里大模型返回推荐问题
-		beego.NSRouter("/get_user_recommend_question", &controllers.ChatController{}, "get:GetUserRecommendQuestion"),
-		// 用户传文件名取数据库寻找链接
-		beego.NSRouter("/get_file_link", &controllers.ChatController{}, "get:GetFileLink"),
-		// 删除对话
-		beego.NSRouter("/delete_conversation", &controllers.ChatController{}, "post:DeleteConversation"),
-		//删除历史记录
-		beego.NSRouter("/delete_history_record", &controllers.ChatController{}, "post:DeleteHistoryRecord"),
-		// 删除隐患识别的历史记录
-		beego.NSRouter("/delete_recognition_record", &controllers.ChatController{}, "post:DeleteRecognitionRecord"),
-		// 保存步骤
-		beego.NSRouter("/save_step", &controllers.HazardController{}, "post:SaveStep"),
-		// 获取隐患识别三级场景标题查询正确和错误的示例图
-		beego.NSRouter("/get_third_scene_example_image", &controllers.SceneController{}, "get:GetThirdSceneExampleImage"),
-		// 保存ppt大纲
-		beego.NSRouter("/save_ppt_outline", &controllers.ChatController{}, "post:SavePPTOutline"),
-		// 文件下载接口
-		beego.NSRouter("/download_file", &controllers.TotalController{}, "get:GetPdfOssDownloadLink"),
-		// 用户提交点评
-		beego.NSRouter("/submit_evaluation", &controllers.SceneController{}, "post:SubmitEvaluation"),
-		// 查询用户最新的一条识别记录是否点评
-		beego.NSRouter("/get_latest_recognition_record", &controllers.SceneController{}, "get:GetLatestRecognitionRecord"),
-		// AI写作保存编辑文档内容
-		beego.NSRouter("/save_edit_document", &controllers.ChatController{}, "post:SaveEditDocument"),
-		// 联网搜索
-		beego.NSRouter("/online_search", &controllers.ChatController{}, "get:OnlineSearch"),
-		// 联网搜索结果存入AIMessage表
-		beego.NSRouter("/save_online_search_result", &controllers.ChatController{}, "post:SaveOnlineSearchResult"),
-		// 意图识别接口
-		beego.NSRouter("/intent_recognition", &controllers.ChatController{}, "post:IntentRecognition"),
-		// 获取ChromaDB文档并生成回答
-		beego.NSRouter("/get_chromadb_document", &controllers.ChatController{}, "get:GetChromaDBDocument"),
-		// 知识库文件高级搜索
-		beego.NSRouter("/knowledge/files/advanced-search", &controllers.ChromaController{}, "get:AdvancedSearch"),
-		// 兼容旧版 AI 问答页的 report / sse 协议
-		beego.NSRouter("/report/complete-flow", &controllers.ReportCompatController{}, "post:CompleteFlow"),
-		beego.NSRouter("/report/update-ai-message", &controllers.ReportCompatController{}, "post:UpdateAIMessage"),
-		beego.NSRouter("/sse/stop", &controllers.ReportCompatController{}, "post:StopSSE"),
-
-		// 流式接口路由
-		beego.NSRouter("/stream/chat", &controllers.LiushiController{}, "post:StreamChat"),
-		// 流式聊天数据库集成接口
-		beego.NSRouter("/stream/chat-with-db", &controllers.LiushiController{}, "post:StreamChatWithDB"),
-		// 政策文件查看和下载次数统计
-		beego.NSRouter("/policy_file_count", &controllers.TotalController{}, "post:GetPolicyFileViewAndDownloadCount"),
-
-		// 根据account_id获取用户数据主键id
-		beego.NSRouter("/get_user_data_id", &controllers.TotalController{}, "get:GetUserDataID"),
-
-		// 埋点记录相关路由
-		beego.NSRouter("/tracking/record", &controllers.TrackingController{}, "post:RecordTracking"),
-		beego.NSRouter("/tracking/records", &controllers.TrackingController{}, "get:GetTrackingRecords"),
-		beego.NSRouter("/tracking/api_mapping", &controllers.TrackingController{}, "post:AddApiMapping"),
-		beego.NSRouter("/tracking/api_mappings", &controllers.TrackingController{}, "get:GetApiMappings"),
-
-		// 积分系统相关路由
-		beego.NSRouter("/points/balance", &controllers.PointsController{}, "get:GetBalance"),
-		beego.NSRouter("/points/consume", &controllers.PointsController{}, "post:ConsumePoints"),
-		beego.NSRouter("/points/history", &controllers.PointsController{}, "get:GetConsumptionHistory"),
-	)
-	beego.AddNamespace(ns)
-}

+ 0 - 62
shudao-go-backend/scripts/init_admin.go

@@ -1,62 +0,0 @@
-package main
-
-import (
-	"fmt"
-	"log"
-	"shudao-chat-go/models"
-
-	"github.com/beego/beego/v2/server/web"
-	"golang.org/x/crypto/bcrypt"
-)
-
-// InitAdminUser 初始化管理员账号
-func InitAdminUser() {
-	fmt.Println("=== 开始初始化管理员账号 ===")
-
-	// 检查管理员账号是否已存在
-	var existingUser models.User
-	result := models.DB.Where("username = ?", "Admin").First(&existingUser)
-
-	if result.Error == nil {
-		fmt.Printf("管理员账号已存在 (ID: %d), 跳过初始化\n", existingUser.ID)
-		return
-	}
-
-	// 生成密码哈希
-	password := "admin123"
-	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
-	if err != nil {
-		log.Fatalf("密码加密失败: %v", err)
-	}
-
-	// 创建管理员账号
-	adminUser := models.User{
-		Username: "Admin",
-		Password: string(hashedPassword),
-		Nickname: "系统管理员",
-		Email:    "admin@shudaodsj.com",
-		Role:     "admin",
-		Status:   1, // 正常状态
-	}
-
-	// 插入数据库
-	if err := models.DB.Create(&adminUser).Error; err != nil {
-		log.Fatalf("创建管理员账号失败: %v", err)
-	}
-
-	fmt.Printf("✅ 管理员账号创建成功!\n")
-	fmt.Printf("   用户名: %s\n", adminUser.Username)
-	fmt.Printf("   密码: %s\n", password)
-	fmt.Printf("   角色: %s\n", adminUser.Role)
-	fmt.Printf("   ID: %d\n", adminUser.ID)
-	fmt.Println("=== 初始化完成 ===")
-}
-
-func main() {
-	// 加载配置
-	if err := web.LoadAppConfig("ini", "conf/app.conf"); err != nil {
-		log.Fatalf("加载配置文件失败: %v", err)
-	}
-	// 初始化管理员账号
-	InitAdminUser()
-}

+ 0 - 56
shudao-go-backend/scripts/init_api_mappings.sql

@@ -1,56 +0,0 @@
--- 批量初始化 api_path_mapping 映射数据
--- 直接在 MySQL 中执行即可(例如通过命令行或可视化工具)
--- 如果记录已存在,将更新名称/描述并保持启用状态
-
-INSERT INTO api_path_mapping (api_path, api_name, api_desc, status)
-VALUES
-  ('/apiv1/recommend_question', '推荐问题', '获取推荐问题列表', 1),
-  ('/apiv1/submit_feedback', '提交意见反馈', '用户提交意见反馈内容', 1),
-  ('/apiv1/get_policy_file', '获取政策文件', '返回可供下载的政策文件', 1),
-  ('/apiv1/send_deepseek_message', '发送DeepSeek消息', '向DeepSeek模型发送消息', 1),
-  ('/apiv1/oss/upload', 'OSS上传', '上传通用文件到OSS', 1),
-  ('/apiv1/oss/shudao/upload_image', '上传图片到OSS', '上传图片文件到OSS', 1),
-  ('/apiv1/oss/shudao/upload_json', '上传JSON文件', '上传JSON配置文件到OSS', 1),
-  ('/apiv1/oss/parse', 'OSS代理解析', '解析OSS文件信息', 1),
-  ('/apiv1/get_function_card', '获取功能卡片', '返回四条功能卡片', 1),
-  ('/apiv1/get_hot_question', '获取热点问题', '返回三条热点问题', 1),
-  ('/apiv1/get_history_record', '获取聊天历史记录', '查询用户聊天历史记录', 1),
-  ('/apiv1/re_modify_question', '修改考试题目', '重新修改考试题目内容', 1),
-  ('/apiv1/re_produce_single_question', '重新生成单题', '重新生成单个题目', 1),
-  ('/apiv1/exam/build_prompt', '生成考试提示词', '生成整卷考试提示词', 1),
-  ('/apiv1/exam/build_single_prompt', '生成单题提示词', '生成单个题目的提示词', 1),
-  ('/apiv1/guess_you_want', '猜你想问', '根据输入实时推荐问题', 1),
-  ('/apiv1/hazard', '隐患识别', '识别安全隐患场景', 1),
-  ('/apiv1/like_and_dislike', '点赞和点踩', '用户对回答进行点赞/点踩', 1),
-  ('/apiv1/get_history_recognition_record', '隐患识别历史列表', '获取隐患识别历史记录', 1),
-  ('/apiv1/get_recognition_record_detail', '隐患识别详情', '获取隐患识别记录详情', 1),
-  ('/apiv1/get_user_recommend_question', '获取用户推荐问题', '联想推荐问题接口', 1),
-  ('/apiv1/get_file_link', '获取文件链接', '根据文件名查询下载链接', 1),
-  ('/apiv1/delete_conversation', '删除对话', '删除指定会话', 1),
-  ('/apiv1/delete_history_record', '删除历史记录', '删除指定聊天历史', 1),
-  ('/apiv1/delete_recognition_record', '删除隐患识别记录', '删除指定隐患识别历史', 1),
-  ('/apiv1/save_step', '保存隐患识别步骤', '保存隐患识别操作步骤', 1),
-  ('/apiv1/get_third_scene_example_image', '获取三级场景示例图', '获取正确/错误示例图', 1),
-  ('/apiv1/save_ppt_outline', '保存PPT大纲', '保存AI写作生成的PPT大纲', 1),
-  ('/apiv1/download_file', '下载文件', '获取文件下载地址', 1),
-  ('/apiv1/submit_evaluation', '提交点评', '提交隐患识别点评', 1),
-  ('/apiv1/get_latest_recognition_record', '获取最新识别记录', '查询用户最新识别记录点评状态', 1),
-  ('/apiv1/save_edit_document', '保存编辑文档', 'AI写作保存编辑文档内容', 1),
-  ('/apiv1/online_search', '联网搜索', '执行联网搜索请求', 1),
-  ('/apiv1/save_online_search_result', '保存联网搜索结果', '联网搜索结果入库', 1),
-  ('/apiv1/intent_recognition', '意图识别', '识别用户输入意图', 1),
-  ('/apiv1/get_chromadb_document', '获取ChromaDB文档', '检索ChromaDB文档并生成回答', 1),
-  ('/apiv1/knowledge/files/advanced-search', '知识库高级搜索', '知识库文件高级搜索', 1),
-  ('/apiv1/stream/chat', '流式聊天', '标准流式聊天接口', 1),
-  ('/apiv1/stream/chat-with-db', '流式聊天(含DB)', '带数据库集成的流式聊天', 1),
-  ('/apiv1/policy_file_count', '政策文件统计', '统计政策文件查看/下载次数', 1),
-  ('/apiv1/get_user_data_id', '获取用户数据ID', '根据account_id获取用户数据主键', 1),
-  ('/apiv1/tracking/record', '记录埋点数据', '记录埋点上报', 1),
-  ('/apiv1/tracking/records', '获取埋点记录列表', '查询埋点记录列表', 1),
-  ('/apiv1/tracking/api_mapping', '添加接口映射', '添加接口路径映射', 1),
-  ('/apiv1/tracking/api_mappings', '获取接口映射列表', '获取接口路径映射列表', 1)
-ON DUPLICATE KEY UPDATE
-  api_name = VALUES(api_name),
-  api_desc = VALUES(api_desc),
-  status   = VALUES(status);
-

+ 0 - 19
shudao-go-backend/scripts/points_migration.sql

@@ -1,19 +0,0 @@
--- 积分系统数据库迁移脚本
--- 执行前请备份数据库
--- 执行方式: mysql -u username -p database_name < points_migration.sql
-
--- 1. 为user_data表添加积分字段
-ALTER TABLE user_data ADD COLUMN points INT DEFAULT 0 COMMENT '用户积分余额';
-
--- 2. 创建积分消费记录表
-CREATE TABLE IF NOT EXISTS points_consumption_log (
-    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
-    user_id VARCHAR(255) NOT NULL COMMENT '用户ID',
-    file_name VARCHAR(500) NOT NULL COMMENT '下载的文件名',
-    file_url TEXT COMMENT '文件URL',
-    points_consumed INT NOT NULL DEFAULT 10 COMMENT '消费的积分数',
-    balance_after INT NOT NULL COMMENT '消费后的余额',
-    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-    INDEX idx_user_id (user_id),
-    INDEX idx_created_at (created_at)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='积分消费记录表';

BIN
shudao-go-backend/static/font/AlibabaPuHuiTi-3-55-Regular.ttf


BIN
shudao-go-backend/static/image/1.png


+ 0 - 1
shudao-go-backend/static/js/reload.min.js

@@ -1 +0,0 @@
-function b(a){var c=new WebSocket(a);c.onclose=function(){setTimeout(function(){b(a)},2E3)};c.onmessage=function(){location.reload()}}try{if(window.WebSocket)try{b("ws://localhost:12450/reload")}catch(a){console.error(a)}else console.log("Your browser does not support WebSockets.")}catch(a){console.error("Exception during connecting to Reload:",a)};

+ 0 - 142
shudao-go-backend/tests/TTS_TEST_SUMMARY.md

@@ -1,142 +0,0 @@
-# TTS流式接口测试总结
-
-## 测试概述
-
-根据提供的接口文档,为 `http://172.16.35.50:8004/tts_stream` 接口编写了完整的测试函数。
-
-## 接口信息
-
-- **接口URL**: `http://172.16.35.50:8000/tts/voice` (修正后的URL)
-- **请求方法**: POST
-- **Content-Type**: `application/json`
-- **请求体**: `{"text": "需要转换的文本"}`
-- **响应**: 流式音频数据(WAV格式)
-
-## 测试函数
-
-### 1. TestTTSConnectivity
-- **功能**: 测试TTS接口的基本连通性
-- **测试内容**: 
-  - 发送简单的测试请求
-  - 验证连接状态
-  - 检查响应格式
-
-### 2. TestTTSStreamAPI
-- **功能**: 完整的TTS流式接口测试
-- **测试内容**:
-  - 短文本测试("你好,这是一个语音合成测试。")
-  - 中等长度文本测试(约100字符)
-  - 长文本测试(约200字符)
-  - 流式数据接收
-  - WAV格式验证
-  - 音频文件保存
-
-## 测试结果
-
-### 连通性测试结果
-```
-=== RUN   TestTTSConnectivity
-=== RUN   TestTTSConnectivity/TTS接口连通性测试
-    api_test.go:1066: 测试TTS接口连通性: http://172.16.35.50:8000/tts/voice
-    api_test.go:1095: ✅ TTS接口连接成功
-    api_test.go:1096: 响应状态码: 502
-    api_test.go:1097: Content-Type: text/html
-    api_test.go:1102: ⚠️ TTS接口返回非200状态码: 502
-    api_test.go:1103: 响应内容: <html>
-        <head><title>502 Bad Gateway</title></head>
-        <body>
-        <center><h1>502 Bad Gateway</h1></center>
-        <hr><center>nginx</center>
-        </body>
-        </html>
---- PASS: TestTTSConnectivity (0.05s)
-```
-
-### 流式接口测试结果
-```
-=== RUN   TestTTSStreamAPI
-=== RUN   TestTTSStreamAPI/短文本测试
-    api_test.go:913: 发送的TTS请求: {"text":"你好,这是一个语音合成测试。"}
-    api_test.go:914: 请求URL: http://172.16.35.50:8000/tts/voice
-    api_test.go:932: TTS响应状态码: 502
-    api_test.go:933: Content-Type: text/html
-    api_test.go:934: Content-Length: 150
-    api_test.go:939: TTS请求失败,状态码: 502, 响应: <html>
-        <head><title>502 Bad Gateway</title></head>
-        <body>
-        <center><h1>502 Bad Gateway</h1></center>
-        <hr><center>nginx</center>
-        </body>
-        </html>
---- FAIL: TestTTSStreamAPI (0.09s)
-```
-
-## 问题分析
-
-### 1. URL修正
-- **原始文档**: `http://172.16.35.50:8004/tts_stream`
-- **实际测试**: `http://172.16.35.50:8000/tts/voice`
-- **原因**: 文档中的URL与实际服务不匹配
-
-### 2. 服务状态
-- **连通性**: ✅ 接口可达
-- **服务状态**: ❌ 返回502 Bad Gateway
-- **可能原因**:
-  - TTS后端服务未启动
-  - 服务配置错误
-  - 依赖服务不可用
-
-## 测试代码特点
-
-### 1. 完整的错误处理
-- 网络连接错误检测
-- 超时处理
-- 响应状态码验证
-- 数据格式验证
-
-### 2. 流式数据处理
-- 分块读取音频数据
-- 进度显示
-- WAV格式验证
-- 文件保存验证
-
-### 3. 多种测试场景
-- 不同长度的文本测试
-- 连通性测试
-- 完整功能测试
-
-## 使用方法
-
-### 运行连通性测试
-```bash
-go test -v -run TestTTSConnectivity
-```
-
-### 运行完整流式测试
-```bash
-go test -v -run TestTTSStreamAPI
-```
-
-### 运行所有TTS测试
-```bash
-go test -v -run TestTTS
-```
-
-## 配置说明
-
-测试配置位于 `config.go` 文件中:
-```go
-TTSBaseURL: getEnvOrDefault("TTS_BASE_URL", "http://172.16.35.50:8000")
-```
-
-可以通过环境变量 `TTS_BASE_URL` 覆盖默认配置。
-
-## 结论
-
-测试函数已成功编写并可以正确检测TTS接口的连通性。当前测试结果显示:
-
-1. ✅ **网络连通性正常** - 可以成功连接到TTS服务
-2. ❌ **服务状态异常** - 返回502错误,需要检查后端服务状态
-3. ✅ **测试代码完整** - 包含完整的错误处理和数据验证逻辑
-
-建议检查TTS后端服务的启动状态和配置,确保服务正常运行后重新测试。

+ 0 - 232
shudao-go-backend/tests/add_data.go

@@ -1,232 +0,0 @@
-package tests
-
-import (
-	"fmt"
-	"io"
-	"os"
-	"path/filepath"
-	"regexp"
-	"strconv"
-	"strings"
-	"testing"
-	"time"
-
-	"github.com/aws/aws-sdk-go/aws"
-	"github.com/aws/aws-sdk-go/aws/credentials"
-	"github.com/aws/aws-sdk-go/aws/session"
-	"github.com/aws/aws-sdk-go/service/s3"
-)
-
-// OSS配置信息
-var (
-	ossBucket    = "gdsc-ai-aqzs"
-	ossAccessKey = "fnyfi2f368pbic74d8ll"
-	ossSecretKey = "jgqwk7sirqlz2602x2k7yx2eor0vii19wah6ywlv"
-	ossEndpoint  = "http://172.16.17.52:8060"
-	ossRegion    = "us-east-1"
-)
-
-// 简单测试函数
-func TestSimple(t *testing.T) {
-	t.Logf("这是一个简单测试")
-	t.Logf("测试日志输出")
-}
-
-// 批量上传图片测试函数
-func TestBatchUploadImages(t *testing.T) {
-	t.Logf("=== 开始批量上传图片测试 ===")
-
-	// 先测试一个简单的日志输出
-	t.Logf("测试日志输出正常")
-
-	// 设置图片文件夹路径
-	imageFolder := "C:/Users/allen/Desktop/隐患识别图/正确图片"
-	t.Logf("目标文件夹: %s", imageFolder)
-
-	// 检查文件夹是否存在
-	if _, err := os.Stat(imageFolder); os.IsNotExist(err) {
-		t.Fatalf("文件夹不存在: %s", imageFolder)
-	}
-
-	t.Logf("文件夹存在,开始扫描...")
-
-	// 获取所有图片文件
-	imageFiles, err := getImageFiles(imageFolder)
-	if err != nil {
-		t.Fatalf("获取图片文件失败: %v", err)
-	}
-
-	t.Logf("找到 %d 个图片文件", len(imageFiles))
-
-	// 创建S3会话
-	sess, err := createS3Session()
-	if err != nil {
-		t.Fatalf("创建S3会话失败: %v", err)
-	}
-	s3Client := s3.New(sess)
-
-	// 处理每个图片文件(只处理前5个文件进行测试)
-	successCount := 0
-	errorCount := 0
-
-	// 限制处理文件数量
-	maxFiles := 5
-	if len(imageFiles) > maxFiles {
-		imageFiles = imageFiles[:maxFiles]
-		t.Logf("为了测试,只处理前 %d 个文件", maxFiles)
-	}
-
-	for i, imageFile := range imageFiles {
-		t.Logf("处理文件 %d/%d: %s", i+1, len(imageFiles), filepath.Base(imageFile))
-
-		// 提取文件名中的序号
-		fileID, err := extractIDFromFilename(filepath.Base(imageFile))
-		if err != nil {
-			t.Logf("  错误: 无法提取序号 - %v", err)
-			errorCount++
-			continue
-		}
-
-		t.Logf("  提取的序号: %d", fileID)
-
-		// 暂时跳过数据库检查,直接测试OSS上传
-		t.Logf("  跳过数据库检查,直接测试OSS上传")
-
-		// 上传图片到OSS
-		imageURL, err := uploadImageToOSS(s3Client, imageFile, fileID)
-		if err != nil {
-			t.Logf("  错误: 上传图片失败 - %v", err)
-			errorCount++
-			continue
-		}
-
-		t.Logf("  图片上传成功: %s", imageURL)
-
-		// 暂时跳过数据库更新
-		t.Logf("  跳过数据库更新")
-		successCount++
-
-		// 添加延迟避免请求过于频繁
-		time.Sleep(100 * time.Millisecond)
-	}
-
-	t.Logf("处理完成!")
-	t.Logf("成功处理: %d 个文件", successCount)
-	t.Logf("处理失败: %d 个文件", errorCount)
-}
-
-// 获取文件夹中的所有图片文件
-func getImageFiles(folderPath string) ([]string, error) {
-	var imageFiles []string
-
-	// 支持的图片格式
-	imageExts := map[string]bool{
-		".jpg":  true,
-		".jpeg": true,
-		".png":  true,
-		".gif":  true,
-		".bmp":  true,
-		".webp": true,
-		".tiff": true,
-		".svg":  true,
-		".ico":  true,
-	}
-
-	err := filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
-		if err != nil {
-			return err
-		}
-
-		if !info.IsDir() {
-			ext := strings.ToLower(filepath.Ext(path))
-			if imageExts[ext] {
-				imageFiles = append(imageFiles, path)
-			}
-		}
-
-		return nil
-	})
-
-	return imageFiles, err
-}
-
-// 从文件名中提取序号
-func extractIDFromFilename(filename string) (int, error) {
-	// 移除文件扩展名
-	nameWithoutExt := strings.TrimSuffix(filename, filepath.Ext(filename))
-
-	// 使用正则表达式提取开头的数字
-	re := regexp.MustCompile(`^(\d+)`)
-	matches := re.FindStringSubmatch(nameWithoutExt)
-
-	if len(matches) < 2 {
-		return 0, fmt.Errorf("文件名中未找到数字序号: %s", filename)
-	}
-
-	id, err := strconv.Atoi(matches[1])
-	if err != nil {
-		return 0, fmt.Errorf("无法解析序号: %s", matches[1])
-	}
-
-	return id, nil
-}
-
-// 创建S3会话
-func createS3Session() (*session.Session, error) {
-	s3Config := &aws.Config{
-		Credentials:      credentials.NewStaticCredentials(ossAccessKey, ossSecretKey, ""),
-		Endpoint:         aws.String(ossEndpoint),
-		Region:           aws.String(ossRegion),
-		S3ForcePathStyle: aws.Bool(true),
-	}
-
-	sess, err := session.NewSession(s3Config)
-	if err != nil {
-		return nil, err
-	}
-
-	// 验证凭据
-	_, err = sess.Config.Credentials.Get()
-	if err != nil {
-		return nil, fmt.Errorf("凭据验证失败: %v", err)
-	}
-
-	return sess, nil
-}
-
-// 上传图片到OSS
-func uploadImageToOSS(s3Client *s3.S3, imagePath string, fileID int) (string, error) {
-	// 打开图片文件
-	file, err := os.Open(imagePath)
-	if err != nil {
-		return "", err
-	}
-	defer file.Close()
-
-	// 生成文件名
-	ext := filepath.Ext(imagePath)
-	fileName := fmt.Sprintf("batch_upload/%d_%d%s",
-		time.Now().Unix(), fileID, ext)
-
-	// 读取文件内容
-	fileBytes, err := io.ReadAll(file)
-	if err != nil {
-		return "", err
-	}
-
-	// 上传到S3
-	_, err = s3Client.PutObject(&s3.PutObjectInput{
-		Bucket: aws.String(ossBucket),
-		Key:    aws.String(fileName),
-		Body:   aws.ReadSeekCloser(strings.NewReader(string(fileBytes))),
-		ACL:    aws.String("public-read"),
-	})
-
-	if err != nil {
-		return "", err
-	}
-
-	// 生成访问URL
-	imageURL := fmt.Sprintf("%s/%s/%s", ossEndpoint, ossBucket, fileName)
-	return imageURL, nil
-}

+ 0 - 1292
shudao-go-backend/tests/api_test.go

@@ -1,1292 +0,0 @@
-package tests
-
-import (
-	"bufio"
-	"bytes"
-	"encoding/base64"
-	"encoding/json"
-	"fmt"
-	"image"
-	"image/color"
-	"image/draw"
-	"image/jpeg"
-	"io"
-	"net"
-	"net/http"
-	"net/url"
-	"os"
-	"strings"
-	"testing"
-	"time"
-)
-
-// Qwen3ChatRequest 定义Qwen3聊天请求结构
-type Qwen3ChatRequest struct {
-	Model       string        `json:"model"`
-	Messages    []ChatMessage `json:"messages"`
-	Stream      bool          `json:"stream"`
-	Temperature float64       `json:"temperature,omitempty"`
-}
-
-// ChatMessage 定义聊天消息结构
-type ChatMessage struct {
-	Role    string `json:"role"`
-	Content string `json:"content"`
-}
-
-// Qwen3ChatResponse 定义Qwen3聊天响应结构(非流式)
-type Qwen3ChatResponse struct {
-	ID      string `json:"id"`
-	Object  string `json:"object"`
-	Created int64  `json:"created"`
-	Model   string `json:"model"`
-	Choices []struct {
-		Index   int `json:"index"`
-		Message struct {
-			Role             string        `json:"role"`
-			Content          string        `json:"content"`
-			Refusal          *string       `json:"refusal"`
-			Annotations      *string       `json:"annotations"`
-			Audio            *string       `json:"audio"`
-			FunctionCall     *string       `json:"function_call"`
-			ToolCalls        []interface{} `json:"tool_calls"`
-			ReasoningContent *string       `json:"reasoning_content"`
-		} `json:"message"`
-		Logprobs     *string `json:"logprobs"`
-		FinishReason string  `json:"finish_reason"`
-		StopReason   *string `json:"stop_reason"`
-	} `json:"choices"`
-	ServiceTier       *string `json:"service_tier"`
-	SystemFingerprint *string `json:"system_fingerprint"`
-	Usage             struct {
-		PromptTokens        int     `json:"prompt_tokens"`
-		TotalTokens         int     `json:"total_tokens"`
-		CompletionTokens    int     `json:"completion_tokens"`
-		PromptTokensDetails *string `json:"prompt_tokens_details"`
-	} `json:"usage"`
-	PromptLogprobs   *string `json:"prompt_logprobs"`
-	KvTransferParams *string `json:"kv_transfer_params"`
-}
-
-// Qwen3StreamResponse 定义Qwen3流式响应结构
-type Qwen3StreamResponse struct {
-	ID                string `json:"id"`
-	Object            string `json:"object"`
-	Created           int64  `json:"created"`
-	Model             string `json:"model"`
-	SystemFingerprint string `json:"system_fingerprint,omitempty"`
-	Choices           []struct {
-		Index int `json:"index"`
-		Delta struct {
-			Role      string `json:"role,omitempty"`
-			Content   string `json:"content,omitempty"`
-			ToolCalls []struct {
-				Index    int    `json:"index"`
-				ID       string `json:"id"`
-				Type     string `json:"type"`
-				Function struct {
-					Name      string `json:"name"`
-					Arguments string `json:"arguments"`
-				} `json:"function"`
-			} `json:"tool_calls,omitempty"`
-		} `json:"delta"`
-		Logprobs     interface{} `json:"logprobs"`
-		FinishReason *string     `json:"finish_reason"`
-		StopReason   *string     `json:"stop_reason,omitempty"`
-	} `json:"choices"`
-}
-
-// TestQwen3ChatAPI 测试Qwen3聊天接口
-func TestQwen3ChatAPI(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.Qwen3BaseURL
-	model := config.Qwen3Model
-
-	testCases := []struct {
-		name    string
-		message string
-	}{
-		{
-			name:    "基础问候测试",
-			message: "你好,请介绍一下你自己。",
-		},
-		{
-			name:    "技术问题测试",
-			message: "请解释一下什么是编程?",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			// 构建非流式请求
-			request := Qwen3ChatRequest{
-				Model:  model,
-				Stream: false,
-				Messages: []ChatMessage{
-					{
-						Role:    "system",
-						Content: "你是一个乐于助人的助手。",
-					},
-					{
-						Role:    "user",
-						Content: tc.message,
-					},
-				},
-			}
-
-			jsonData, err := json.Marshal(request)
-			if err != nil {
-				t.Fatalf("序列化请求失败: %v", err)
-			}
-
-			t.Logf("发送的请求: %s", string(jsonData))
-
-			// 发送POST请求
-			resp, err := http.Post(
-				fmt.Sprintf("%s/v1/chat/completions", baseURL),
-				"application/json",
-				bytes.NewBuffer(jsonData),
-			)
-			if err != nil {
-				t.Fatalf("发送请求失败: %v", err)
-			}
-			defer resp.Body.Close()
-
-			t.Logf("响应状态码: %d", resp.StatusCode)
-			t.Logf("Content-Type: %s", resp.Header.Get("Content-Type"))
-
-			if resp.StatusCode != http.StatusOK {
-				body, _ := io.ReadAll(resp.Body)
-				t.Fatalf("请求失败,状态码: %d, 响应: %s", resp.StatusCode, string(body))
-			}
-
-			// 读取完整的响应内容
-			body, err := io.ReadAll(resp.Body)
-			if err != nil {
-				t.Fatalf("读取响应失败: %v", err)
-			}
-
-			t.Logf("响应长度: %d", len(body))
-
-			// 解析JSON响应
-			var response Qwen3ChatResponse
-			if err := json.Unmarshal(body, &response); err != nil {
-				t.Fatalf("解析JSON响应失败: %v", err)
-			}
-
-			// 验证响应
-			if response.ID == "" {
-				t.Error("响应ID为空")
-			}
-			if len(response.Choices) == 0 {
-				t.Error("响应中没有选择项")
-			}
-
-			t.Logf("✅ 非流式响应成功!")
-			t.Logf("请求: %s", tc.message)
-			t.Logf("响应ID: %s", response.ID)
-			t.Logf("模型: %s", response.Model)
-			t.Logf("完整响应: %s", response.Choices[0].Message.Content)
-		})
-	}
-}
-
-// TestQwen3StreamAPI 测试Qwen3流式聊天接口
-func TestQwen3StreamAPI(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.Qwen3BaseURL
-	model := config.Qwen3Model
-
-	testCases := []struct {
-		name    string
-		message string
-	}{
-		{
-			name:    "流式基础问候测试",
-			message: "你好,请介绍一下你自己。",
-		},
-		{
-			name:    "流式技术问题测试",
-			message: "请解释一下什么是编程?",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			// 构建流式请求
-			request := Qwen3ChatRequest{
-				Model:       model,
-				Stream:      false,
-				Temperature: 0.7,
-				Messages: []ChatMessage{
-					{
-						Role:    "system",
-						Content: "你是一个乐于助人的助手。",
-					},
-					{
-						Role:    "user",
-						Content: tc.message,
-					},
-				},
-			}
-
-			jsonData, err := json.Marshal(request)
-			if err != nil {
-				t.Fatalf("序列化请求失败: %v", err)
-			}
-
-			// 发送POST请求
-			resp, err := http.Post(
-				fmt.Sprintf("%s/v1/chat/completions", baseURL),
-				"application/json",
-				bytes.NewBuffer(jsonData),
-			)
-			if err != nil {
-				t.Fatalf("发送请求失败: %v", err)
-			}
-			defer resp.Body.Close()
-
-			t.Logf("响应状态码: %d", resp.StatusCode)
-			t.Logf("Content-Type: %s", resp.Header.Get("Content-Type"))
-
-			if resp.StatusCode != http.StatusOK {
-				body, _ := io.ReadAll(resp.Body)
-				t.Fatalf("请求失败,状态码: %d, 响应: %s", resp.StatusCode, string(body))
-			}
-
-			t.Logf("响应长度: %d", resp.ContentLength)
-
-			if request.Stream {
-				// 处理流式响应
-				scanner := bufio.NewScanner(resp.Body)
-				var fullContent strings.Builder
-				var responseID string
-				var firstChunk = true
-
-				t.Logf("开始接收流式响应...")
-				chunkCount := 0
-
-				for scanner.Scan() {
-					line := scanner.Text()
-					chunkCount++
-
-					// 跳过空行和data:前缀
-					if line == "" || !strings.HasPrefix(line, "data: ") {
-						continue
-					}
-
-					// 移除"data: "前缀
-					data := strings.TrimPrefix(line, "data: ")
-
-					// 检查是否是结束标记
-					if data == "[DONE]" {
-						t.Logf("收到结束标记")
-						break
-					}
-
-					// 解析JSON数据
-					var streamResp Qwen3StreamResponse
-					if err := json.Unmarshal([]byte(data), &streamResp); err != nil {
-						t.Logf("解析流式响应失败: %v, 数据: %s", err, data)
-						continue
-					}
-
-					// 记录第一个块的ID
-					if firstChunk {
-						responseID = streamResp.ID
-						t.Logf("响应ID: %s", responseID)
-						t.Logf("模型: %s", streamResp.Model)
-						firstChunk = false
-					}
-
-					// 处理choices中的内容
-					if len(streamResp.Choices) > 0 {
-						choice := streamResp.Choices[0]
-						if choice.Delta.Content != "" {
-							fullContent.WriteString(choice.Delta.Content)
-							t.Logf("收到内容块: %s", choice.Delta.Content)
-						}
-
-						// 检查是否完成
-						if choice.FinishReason != nil {
-							t.Logf("完成原因: %s", *choice.FinishReason)
-							break
-						}
-					}
-				}
-
-				if err := scanner.Err(); err != nil {
-					t.Fatalf("读取流式响应失败: %v", err)
-				}
-
-				// 验证结果
-				finalContent := fullContent.String()
-				if finalContent == "" {
-					t.Error("流式响应内容为空")
-				} else {
-					t.Logf("✅ 流式响应成功!")
-					t.Logf("请求: %s", tc.message)
-					t.Logf("响应ID: %s", responseID)
-					t.Logf("总块数: %d", chunkCount)
-					t.Logf("完整响应: %s", finalContent)
-				}
-			} else {
-				// 处理非流式响应
-				body, err := io.ReadAll(resp.Body)
-				if err != nil {
-					t.Fatalf("读取响应失败: %v", err)
-				}
-
-				t.Logf("开始解析非流式响应...")
-
-				// 解析JSON响应
-				var response Qwen3ChatResponse
-				if err := json.Unmarshal(body, &response); err != nil {
-					t.Fatalf("解析JSON响应失败: %v", err)
-				}
-
-				// 验证响应
-				if response.ID == "" {
-					t.Error("响应ID为空")
-				}
-				if len(response.Choices) == 0 {
-					t.Error("响应中没有选择项")
-				}
-
-				t.Logf("✅ 非流式响应成功!")
-				t.Logf("请求: %s", tc.message)
-				t.Logf("响应ID: %s", response.ID)
-				t.Logf("模型: %s", response.Model)
-				t.Logf("完整响应: %s", response.Choices[0].Message.Content)
-			}
-		})
-	}
-}
-
-// TestYOLOPredictAPI 测试YOLO图像识别接口
-func TestYOLOPredictAPI(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.YOLOBaseURL
-
-	testCase := struct {
-		modeltype      string
-		image          string
-		conf_threshold float32
-	}{
-		modeltype:      "gas_station",
-		image:          "test_images/1.jpg",
-		conf_threshold: 0.5,
-	}
-
-	// 使用单个测试用例,不需要循环
-	tc := testCase
-
-	// 检查图像文件是否存在
-	if _, err := os.Stat(tc.image); os.IsNotExist(err) {
-		t.Skipf("测试图像文件不存在: %s", tc.image)
-	}
-
-	// 读取图像文件并转换为base64
-	imageData, err := os.ReadFile(tc.image)
-	if err != nil {
-		t.Fatalf("读取图像文件失败: %v", err)
-	}
-
-	// 解码图像用于后续处理
-	img, _, err := image.Decode(bytes.NewReader(imageData))
-	if err != nil {
-		t.Fatalf("解码图像失败: %v", err)
-	}
-
-	// 将图像数据转换为base64
-	imageBase64 := base64.StdEncoding.EncodeToString(imageData)
-
-	// 构建JSON请求体
-	requestBody := map[string]interface{}{
-		"modeltype":      tc.modeltype,
-		"image":          imageBase64,
-		"conf_threshold": tc.conf_threshold,
-	}
-
-	jsonData, err := json.Marshal(requestBody)
-	if err != nil {
-		t.Fatalf("序列化请求体失败: %v", err)
-	}
-
-	resp, err := http.Post(
-		fmt.Sprintf("%s/predict", baseURL),
-		"application/json",
-		bytes.NewBuffer(jsonData),
-	)
-	if err != nil {
-		t.Fatalf("发送请求失败: %v", err)
-	}
-	defer resp.Body.Close()
-
-	body, err := io.ReadAll(resp.Body)
-	if err != nil {
-		t.Fatalf("读取响应失败: %v", err)
-	}
-
-	if resp.StatusCode != http.StatusOK {
-		t.Errorf("请求失败,状态码: %d, 响应: %s", resp.StatusCode, string(body))
-	}
-
-	if len(body) == 0 {
-		t.Error("响应内容为空")
-	}
-
-	t.Logf("响应状态码: %d", resp.StatusCode)
-	t.Logf("响应内容: %s", string(body))
-
-	// 解析YOLO响应
-	var yoloResp YOLOResponse
-	if err := json.Unmarshal(body, &yoloResp); err != nil {
-		t.Fatalf("解析YOLO响应失败: %v", err)
-	}
-
-	t.Logf("解析成功: 检测到 %d 个对象", len(yoloResp.Labels))
-
-	t.Logf("开始绘制边界框...")
-	// 在原始图像上绘制边界框
-	annotatedImg := drawBoundingBox(img, yoloResp.Boxes, yoloResp.Labels, yoloResp.Scores)
-	t.Logf("边界框绘制完成")
-
-	t.Logf("开始保存图像...")
-	// 保存标注后的图像
-	outputPath := "test_images/annotated_1.jpg"
-	outputFile, err := os.Create(outputPath)
-	if err != nil {
-		t.Fatalf("创建输出文件失败: %v", err)
-	}
-	defer outputFile.Close()
-
-	if err := jpeg.Encode(outputFile, annotatedImg, &jpeg.Options{Quality: 95}); err != nil {
-		t.Fatalf("保存图像失败: %v", err)
-	}
-
-	t.Logf("✅ 已保存标注后的图像到: %s", outputPath)
-	t.Logf("识别结果: 检测到 %d 个对象", len(yoloResp.Labels))
-	for i, label := range yoloResp.Labels {
-		if i < len(yoloResp.Scores) {
-			t.Logf("  - %s: 置信度 %.2f%%", label, yoloResp.Scores[i]*100)
-		}
-	}
-
-	// 强制输出日志
-	t.Logf("测试完成,图像已保存")
-}
-
-// TestAPIEndpoints 测试API端点连通性
-func TestAPIEndpoints(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	endpoints := []struct {
-		name string
-		url  string
-	}{
-		{"Qwen3聊天接口", config.GetQwen3ChatURL()},
-		{"YOLO预测接口", config.GetYOLOPredictURL()},
-		{"TTS流式接口", config.GetTTSStreamURL()},
-	}
-
-	for _, endpoint := range endpoints {
-		t.Run(endpoint.name, func(t *testing.T) {
-			client := &http.Client{
-				Timeout: 10 * time.Second,
-			}
-
-			resp, err := client.Head(endpoint.url)
-			if err != nil {
-				t.Logf("端点 %s 不可达: %v", endpoint.name, err)
-				return
-			}
-			defer resp.Body.Close()
-
-			t.Logf("端点 %s 可达,状态码: %d", endpoint.name, resp.StatusCode)
-		})
-	}
-}
-
-// BenchmarkQwen3ChatAPI 性能测试Qwen3聊天接口
-func BenchmarkQwen3ChatAPI(b *testing.B) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.Qwen3BaseURL
-	model := config.Qwen3Model
-
-	request := Qwen3ChatRequest{
-		Model: model,
-		Messages: []ChatMessage{
-			{
-				Role:    "user",
-				Content: "你好",
-			},
-		},
-	}
-
-	jsonData, _ := json.Marshal(request)
-
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		resp, err := http.Post(
-			fmt.Sprintf("%s/v1/chat/completions", baseURL),
-			"application/json",
-			bytes.NewBuffer(jsonData),
-		)
-		if err != nil {
-			b.Fatalf("请求失败: %v", err)
-		}
-		resp.Body.Close()
-	}
-}
-
-// YOLOResponse 定义YOLO响应结构
-type YOLOResponse struct {
-	Boxes     [][]float64 `json:"boxes"`
-	Labels    []string    `json:"labels"`
-	Scores    []float64   `json:"scores"`
-	ModelType string      `json:"model_type"`
-}
-
-// drawBoundingBox 在图像上绘制边界框和标签
-func drawBoundingBox(img image.Image, boxes [][]float64, labels []string, scores []float64) image.Image {
-	// 创建可绘制的图像副本
-	bounds := img.Bounds()
-	drawableImg := image.NewRGBA(bounds)
-	draw.Draw(drawableImg, bounds, img, image.Point{}, draw.Src)
-
-	// 红色边界框
-	red := image.NewUniform(color.RGBA{255, 0, 0, 255})
-
-	fmt.Printf("图像边界: %v\n", bounds)
-
-	for i, box := range boxes {
-		if len(box) >= 4 {
-			x1, y1, x2, y2 := int(box[0]), int(box[1]), int(box[2]), int(box[3])
-			fmt.Printf("边界框 %d: [%d, %d, %d, %d]\n", i, x1, y1, x2, y2)
-
-			// 绘制边界框
-			drawRect(drawableImg, x1, y1, x2, y2, red)
-
-			// 绘制标签和置信度(暂时注释掉,避免白色区域)
-			// if i < len(labels) && i < len(scores) {
-			// 	label := fmt.Sprintf("%s: %.2f", labels[i], scores[i])
-			// 	drawLabel(drawableImg, x1, y1-20, label)
-			// }
-		}
-	}
-
-	return drawableImg
-}
-
-// drawRect 绘制矩形
-func drawRect(img *image.RGBA, x1, y1, x2, y2 int, color *image.Uniform) {
-	bounds := img.Bounds()
-	fmt.Printf("绘制矩形: [%d, %d, %d, %d], 图像边界: %v\n", x1, y1, x2, y2, bounds)
-
-	// 确保坐标在图像边界内
-	if x1 < bounds.Min.X {
-		x1 = bounds.Min.X
-	}
-	if y1 < bounds.Min.Y {
-		y1 = bounds.Min.Y
-	}
-	if x2 >= bounds.Max.X {
-		x2 = bounds.Max.X - 1
-	}
-	if y2 >= bounds.Max.Y {
-		y2 = bounds.Max.Y - 1
-	}
-
-	fmt.Printf("调整后坐标: [%d, %d, %d, %d]\n", x1, y1, x2, y2)
-
-	// 绘制四条边
-	for x := x1; x <= x2; x++ {
-		img.Set(x, y1, color)
-		img.Set(x, y2, color)
-	}
-	for y := y1; y <= y2; y++ {
-		img.Set(x1, y, color)
-		img.Set(x2, y, color)
-	}
-
-	fmt.Printf("矩形绘制完成\n")
-}
-
-// drawLabel 绘制标签文本(简化版本,只绘制背景矩形)
-func drawLabel(img *image.RGBA, x, y int, text string) {
-	fmt.Printf("绘制标签: '%s' 在位置 [%d, %d]\n", text, x, y)
-
-	// 绘制白色背景矩形
-	white := image.NewUniform(color.RGBA{255, 255, 255, 255})
-	labelWidth := len(text) * 8 // 估算文本宽度
-	labelHeight := 16
-
-	// 确保标签在图像边界内
-	bounds := img.Bounds()
-	if x < bounds.Min.X {
-		x = bounds.Min.X
-	}
-	if y < bounds.Min.Y {
-		y = bounds.Min.Y
-	}
-	if x+labelWidth >= bounds.Max.X {
-		labelWidth = bounds.Max.X - x - 1
-	}
-	if y+labelHeight >= bounds.Max.Y {
-		labelHeight = bounds.Max.Y - y - 1
-	}
-
-	fmt.Printf("标签尺寸: %dx%d\n", labelWidth, labelHeight)
-
-	// 绘制背景矩形
-	for dx := 0; dx < labelWidth; dx++ {
-		for dy := 0; dy < labelHeight; dy++ {
-			if x+dx < bounds.Max.X && y+dy < bounds.Max.Y && x+dx >= bounds.Min.X && y+dy >= bounds.Min.Y {
-				img.Set(x+dx, y+dy, white)
-			}
-		}
-	}
-
-	fmt.Printf("标签绘制完成\n")
-}
-
-// TestHealthCheck 测试健康检查接口
-func TestHealthCheck(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.HealthBaseURL
-
-	t.Run("健康检查测试", func(t *testing.T) {
-		client := &http.Client{
-			Timeout: 10 * time.Second,
-		}
-
-		// 发送POST请求到健康检查端点
-		t.Logf("请求URL: %s/health", baseURL)
-		resp, err := client.Post(fmt.Sprintf("%s/health", baseURL), "application/json", nil)
-		if err != nil {
-			t.Logf("❌ 健康检查请求失败: %v", err)
-			t.Logf("错误类型: %T", err)
-
-			// 检查是否是网络相关错误
-			if netErr, ok := err.(interface{ Timeout() bool }); ok && netErr.Timeout() {
-				t.Logf("⚠️ 这是超时错误")
-			}
-
-			// 尝试更详细的错误信息
-			if urlErr, ok := err.(*url.Error); ok {
-				t.Logf("URL错误详情: %+v", urlErr)
-				if urlErr.Err != nil {
-					t.Logf("底层错误: %v", urlErr.Err)
-				}
-			}
-
-			t.Fatalf("健康检查请求失败: %v", err)
-		}
-		defer resp.Body.Close()
-
-		// 读取响应内容
-		body, err := io.ReadAll(resp.Body)
-		if err != nil {
-			t.Logf("读取响应失败: %v", err)
-		}
-
-		t.Logf("健康检查响应状态码: %d", resp.StatusCode)
-		t.Logf("响应头: %+v", resp.Header)
-		t.Logf("响应内容: %s", string(body))
-
-		// 验证响应状态码
-		if resp.StatusCode != http.StatusOK {
-			t.Errorf("健康检查失败,期望状态码200,实际状态码: %d", resp.StatusCode)
-		}
-
-		// 验证响应内容(通常健康检查返回简单的状态信息)
-		if len(body) == 0 {
-			t.Error("健康检查响应内容为空")
-		}
-
-		// 尝试解析JSON响应(如果返回的是JSON格式)
-		var healthResponse map[string]interface{}
-		if err := json.Unmarshal(body, &healthResponse); err == nil {
-			t.Logf("✅ 健康检查返回JSON格式响应: %+v", healthResponse)
-		} else {
-			t.Logf("健康检查返回非JSON格式响应: %s", string(body))
-		}
-	})
-}
-
-// SearchRequest 定义搜索请求结构
-type SearchRequest struct {
-	Query    string `json:"query"`
-	NResults int    `json:"n_results"`
-}
-
-// SearchResponse 定义搜索响应结构
-type SearchResponse struct {
-	Results []SearchResult `json:"results"`
-	Total   int            `json:"total"`
-	Query   string         `json:"query"`
-}
-
-// SearchResult 定义搜索结果结构
-type SearchResult struct {
-	ID       string                 `json:"id"`
-	Title    string                 `json:"title"`
-	Content  string                 `json:"content"`
-	Score    float64                `json:"score"`
-	Metadata map[string]interface{} `json:"metadata,omitempty"`
-}
-
-// TestSearchAPI 测试搜索功能接口
-func TestSearchAPI(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.SearchBaseURL
-
-	testCases := []struct {
-		name     string
-		query    string
-		nResults int
-	}{
-		{
-			name:     "技术搜索测试",
-			query:    "技术",
-			nResults: 3,
-		},
-		{
-			name:     "编程搜索测试",
-			query:    "编程",
-			nResults: 5,
-		},
-		{
-			name:     "AI搜索测试",
-			query:    "人工智能",
-			nResults: 2,
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			// 构建搜索请求
-			request := SearchRequest{
-				Query:    tc.query,
-				NResults: tc.nResults,
-			}
-
-			jsonData, err := json.Marshal(request)
-			if err != nil {
-				t.Fatalf("序列化搜索请求失败: %v", err)
-			}
-
-			// 发送POST请求到搜索端点
-			resp, err := http.Post(
-				fmt.Sprintf("%s/search", baseURL),
-				"application/json",
-				bytes.NewBuffer(jsonData),
-			)
-			if err != nil {
-				t.Fatalf("搜索请求失败: %v", err)
-			}
-			defer resp.Body.Close()
-
-			// 读取响应内容
-			body, err := io.ReadAll(resp.Body)
-			if err != nil {
-				t.Fatalf("读取搜索响应失败: %v", err)
-			}
-
-			t.Logf("搜索响应状态码: %d", resp.StatusCode)
-			t.Logf("搜索查询: %s", tc.query)
-			t.Logf("请求结果数量: %d", tc.nResults)
-
-			// 验证响应状态码
-			if resp.StatusCode != http.StatusOK {
-				t.Errorf("搜索请求失败,期望状态码200,实际状态码: %d", resp.StatusCode)
-				if len(body) < 500 {
-					t.Logf("错误响应内容: %s", string(body))
-				}
-				return
-			}
-
-			// 验证响应内容不为空
-			if len(body) == 0 {
-				t.Error("搜索响应内容为空")
-				return
-			}
-
-			// 尝试解析JSON响应
-			var searchResponse SearchResponse
-			if err := json.Unmarshal(body, &searchResponse); err != nil {
-				t.Logf("⚠️ 搜索响应不是标准JSON格式: %v", err)
-				t.Logf("响应内容: %s", string(body))
-
-				// 尝试解析为简单的map结构
-				var simpleResponse map[string]interface{}
-				if err := json.Unmarshal(body, &simpleResponse); err == nil {
-					t.Logf("✅ 搜索返回简单JSON格式: %+v", simpleResponse)
-				} else {
-					t.Logf("搜索返回非JSON格式响应")
-				}
-				return
-			}
-
-			// 验证搜索结果
-			t.Logf("✅ 搜索成功!")
-			t.Logf("查询: %s", searchResponse.Query)
-			t.Logf("总结果数: %d", searchResponse.Total)
-			t.Logf("返回结果数: %d", len(searchResponse.Results))
-
-			// 验证结果数量
-			if len(searchResponse.Results) > tc.nResults {
-				t.Logf("⚠️ 返回结果数量(%d)超过请求数量(%d)", len(searchResponse.Results), tc.nResults)
-			}
-
-			// 显示搜索结果详情
-			for i, result := range searchResponse.Results {
-				t.Logf("结果 %d:", i+1)
-				t.Logf("  ID: %s", result.ID)
-				t.Logf("  标题: %s", result.Title)
-				t.Logf("  内容: %s", truncateString(result.Content, 100))
-				t.Logf("  评分: %.4f", result.Score)
-				if len(result.Metadata) > 0 {
-					t.Logf("  元数据: %+v", result.Metadata)
-				}
-			}
-		})
-	}
-}
-
-// truncateString 截断字符串到指定长度
-func truncateString(s string, maxLen int) string {
-	if len(s) <= maxLen {
-		return s
-	}
-	return s[:maxLen] + "..."
-}
-
-// TTSRequest 定义TTS请求结构
-type TTSRequest struct {
-	Text string `json:"text"`
-}
-
-// TestTTSStreamAPI 测试TTS流式接口
-func TestTTSStreamAPI(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.TTSBaseURL
-
-	testCases := []struct {
-		name string
-		text string
-	}{
-		{
-			name: "短文本测试",
-			text: "你好,这是一个语音合成测试。",
-		},
-		{
-			name: "中等长度文本测试",
-			text: "答案依据:《汽车加油站雷电防护装置检测报告综述表》委托单位随检人员在雷电防护装置检测过程中承担着关键的协同与监督职责。",
-		},
-		{
-			name: "长文本测试",
-			text: "答案依据:《汽车加油站雷电防护装置检测报告综述表》委托单位随检人员在雷电防护装置检测过程中承担着关键的协同与监督职责,其参与过程对检测报告的完整性与合规性具有直接影响。作为检测工作的现场对接方和责任主体之一,委托单位随检人员需全程参与检测实施,确保检测工作在真实、可控的现场环境下开展,有效保障检测数据的真实性与代表性。",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			// 构建TTS请求
-			request := TTSRequest{
-				Text: tc.text,
-			}
-
-			jsonData, err := json.Marshal(request)
-			if err != nil {
-				t.Fatalf("序列化TTS请求失败: %v", err)
-			}
-
-			t.Logf("发送的TTS请求: %s", string(jsonData))
-			t.Logf("请求URL: %s/tts/voice", baseURL)
-
-			// 创建HTTP客户端,设置较长的超时时间(TTS可能需要较长时间)
-			client := &http.Client{
-				Timeout: 60 * time.Second,
-			}
-
-			// 发送POST请求
-			resp, err := client.Post(
-				fmt.Sprintf("%s/tts/voice", baseURL),
-				"application/json",
-				bytes.NewBuffer(jsonData),
-			)
-			if err != nil {
-				t.Fatalf("发送TTS请求失败: %v", err)
-			}
-			defer resp.Body.Close()
-
-			t.Logf("TTS响应状态码: %d", resp.StatusCode)
-			t.Logf("Content-Type: %s", resp.Header.Get("Content-Type"))
-			t.Logf("Content-Length: %s", resp.Header.Get("Content-Length"))
-
-			// 验证响应状态码
-			if resp.StatusCode != http.StatusOK {
-				body, _ := io.ReadAll(resp.Body)
-				t.Fatalf("TTS请求失败,状态码: %d, 响应: %s", resp.StatusCode, string(body))
-			}
-
-			// 检查Content-Type是否为音频格式
-			contentType := resp.Header.Get("Content-Type")
-			if contentType != "" {
-				t.Logf("响应Content-Type: %s", contentType)
-				// WAV格式通常为 "audio/wav" 或 "audio/wave"
-				if !strings.Contains(contentType, "audio") && !strings.Contains(contentType, "wav") {
-					t.Logf("⚠️ 警告: Content-Type不是预期的音频格式")
-				}
-			}
-
-			// 读取流式音频数据
-			t.Logf("开始接收流式音频数据...")
-			var audioData bytes.Buffer
-			totalBytes := 0
-			chunkCount := 0
-
-			// 分块读取数据
-			buffer := make([]byte, 4096) // 4KB缓冲区
-			for {
-				n, err := resp.Body.Read(buffer)
-				if n > 0 {
-					audioData.Write(buffer[:n])
-					totalBytes += n
-					chunkCount++
-
-					// 每读取10个块输出一次进度
-					if chunkCount%10 == 0 {
-						t.Logf("已接收 %d 字节音频数据 (块数: %d)", totalBytes, chunkCount)
-					}
-				}
-
-				if err == io.EOF {
-					t.Logf("音频数据接收完成")
-					break
-				}
-				if err != nil {
-					t.Fatalf("读取音频数据失败: %v", err)
-				}
-			}
-
-			t.Logf("✅ TTS流式响应成功!")
-			t.Logf("请求文本: %s", tc.text)
-			t.Logf("文本长度: %d 字符", len(tc.text))
-			t.Logf("音频数据总大小: %d 字节", totalBytes)
-			t.Logf("接收块数: %d", chunkCount)
-
-			// 验证音频数据不为空
-			if totalBytes == 0 {
-				t.Error("音频数据为空")
-				return
-			}
-
-			// 验证音频数据大小合理性(至少应该有WAV文件头)
-			if totalBytes < 44 {
-				t.Errorf("音频数据太小,可能不是有效的WAV文件: %d 字节", totalBytes)
-			}
-
-			// 验证WAV文件头(RIFF格式)
-			audioBytes := audioData.Bytes()
-			if len(audioBytes) >= 12 {
-				// 检查RIFF头
-				if string(audioBytes[0:4]) == "RIFF" {
-					t.Logf("✅ 检测到有效的RIFF格式音频文件")
-					if len(audioBytes) >= 8 {
-						fileSize := int(audioBytes[4]) | int(audioBytes[5])<<8 | int(audioBytes[6])<<16 | int(audioBytes[7])<<24
-						t.Logf("WAV文件大小字段: %d 字节", fileSize)
-					}
-					if len(audioBytes) >= 12 && string(audioBytes[8:12]) == "WAVE" {
-						t.Logf("✅ 检测到WAVE格式")
-					}
-				} else {
-					t.Logf("⚠️ 警告: 音频数据不是标准的RIFF格式")
-					t.Logf("文件头: %x", audioBytes[:min(16, len(audioBytes))])
-				}
-			}
-
-			// 保存音频文件到本地进行验证
-			outputFilename := fmt.Sprintf("tts_test_%s.wav", strings.ReplaceAll(tc.name, " ", "_"))
-			outputFile, err := os.Create(outputFilename)
-			if err != nil {
-				t.Fatalf("创建音频输出文件失败: %v", err)
-			}
-			defer outputFile.Close()
-
-			_, err = outputFile.Write(audioBytes)
-			if err != nil {
-				t.Fatalf("写入音频文件失败: %v", err)
-			}
-
-			t.Logf("✅ 音频文件已保存: %s", outputFilename)
-			t.Logf("文件大小: %d 字节", totalBytes)
-
-			// 验证文件是否成功创建
-			if stat, err := os.Stat(outputFilename); err == nil {
-				t.Logf("✅ 文件验证成功,实际大小: %d 字节", stat.Size())
-				if stat.Size() != int64(totalBytes) {
-					t.Errorf("文件大小不匹配: 期望 %d, 实际 %d", totalBytes, stat.Size())
-				}
-			} else {
-				t.Errorf("文件验证失败: %v", err)
-			}
-		})
-	}
-}
-
-// min 返回两个整数中的较小值
-func min(a, b int) int {
-	if a < b {
-		return a
-	}
-	return b
-}
-
-// TestTTSConnectivity 测试TTS接口连通性
-func TestTTSConnectivity(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.TTSBaseURL
-
-	t.Run("TTS接口连通性测试", func(t *testing.T) {
-		client := &http.Client{
-			Timeout: 30 * time.Second,
-		}
-
-		t.Logf("测试TTS接口连通性: %s/tts/voice", baseURL)
-
-		// 构建简单的测试请求
-		request := TTSRequest{
-			Text: "测试",
-		}
-
-		jsonData, err := json.Marshal(request)
-		if err != nil {
-			t.Fatalf("序列化测试请求失败: %v", err)
-		}
-
-		// 发送POST请求
-		resp, err := client.Post(
-			fmt.Sprintf("%s/tts/voice", baseURL),
-			"application/json",
-			bytes.NewBuffer(jsonData),
-		)
-		if err != nil {
-			t.Logf("❌ TTS接口连接失败: %v", err)
-			t.Logf("可能的原因:")
-			t.Logf("  - TTS服务未启动")
-			t.Logf("  - 网络不可达")
-			t.Logf("  - 端口不正确")
-			t.Logf("  - 防火墙阻止")
-			t.Fatalf("TTS接口连通性测试失败: %v", err)
-		}
-		defer resp.Body.Close()
-
-		t.Logf("✅ TTS接口HTTP连接成功")
-		t.Logf("响应状态码: %d", resp.StatusCode)
-		t.Logf("Content-Type: %s", resp.Header.Get("Content-Type"))
-
-		// 验证响应状态码
-		if resp.StatusCode != http.StatusOK {
-			body, _ := io.ReadAll(resp.Body)
-			t.Logf("⚠️ TTS服务不可用,状态码: %d", resp.StatusCode)
-			t.Logf("响应内容: %s", string(body))
-
-			// 根据状态码提供具体的错误信息
-			switch resp.StatusCode {
-			case 502:
-				t.Logf("❌ 502 Bad Gateway - TTS后端服务可能未启动或配置错误")
-			case 503:
-				t.Logf("❌ 503 Service Unavailable - TTS服务暂时不可用")
-			case 404:
-				t.Logf("❌ 404 Not Found - TTS接口路径不存在")
-			case 500:
-				t.Logf("❌ 500 Internal Server Error - TTS服务内部错误")
-			default:
-				t.Logf("❌ 未知错误状态码: %d", resp.StatusCode)
-			}
-		} else {
-			t.Logf("✅ TTS服务响应正常")
-
-			// 读取少量数据验证是否为音频格式
-			buffer := make([]byte, 1024)
-			n, err := resp.Body.Read(buffer)
-			if err != nil && err != io.EOF {
-				t.Logf("读取响应数据失败: %v", err)
-			} else if n > 0 {
-				t.Logf("✅ 成功接收 %d 字节数据", n)
-
-				// 检查是否为WAV格式
-				if n >= 12 {
-					if string(buffer[0:4]) == "RIFF" {
-						t.Logf("✅ 检测到WAV格式音频数据")
-					} else {
-						t.Logf("⚠️ 数据格式可能不是WAV: %x", buffer[:min(16, n)])
-					}
-				}
-			}
-		}
-	})
-}
-
-// TestHealthAndSearchIntegration 集成测试:先检查健康状态,再进行搜索
-func TestHealthAndSearchIntegration(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.HealthBaseURL
-
-	t.Run("健康检查和搜索集成测试", func(t *testing.T) {
-		client := &http.Client{
-			Timeout: 15 * time.Second,
-		}
-
-		// 第一步:健康检查
-		t.Logf("步骤1: 执行健康检查...")
-		t.Logf("请求URL: %s/health", baseURL)
-
-		healthResp, err := client.Post(fmt.Sprintf("%s/health", baseURL), "application/json", nil)
-		if err != nil {
-			t.Logf("❌ 健康检查请求失败: %v", err)
-			t.Logf("错误类型: %T", err)
-
-			// 检查是否是网络相关错误
-			if netErr, ok := err.(interface{ Timeout() bool }); ok && netErr.Timeout() {
-				t.Logf("⚠️ 这是超时错误")
-			}
-
-			// 尝试更详细的错误信息
-			if urlErr, ok := err.(*url.Error); ok {
-				t.Logf("URL错误详情: %+v", urlErr)
-				if urlErr.Err != nil {
-					t.Logf("底层错误: %v", urlErr.Err)
-				}
-			}
-
-			t.Fatalf("健康检查失败: %v", err)
-		}
-		defer healthResp.Body.Close()
-
-		// 读取响应内容
-		healthBody, err := io.ReadAll(healthResp.Body)
-		if err != nil {
-			t.Logf("读取健康检查响应失败: %v", err)
-		}
-
-		t.Logf("健康检查响应状态码: %d", healthResp.StatusCode)
-		t.Logf("响应头: %+v", healthResp.Header)
-		if len(healthBody) > 0 {
-			t.Logf("响应内容: %s", string(healthBody))
-		}
-
-		if healthResp.StatusCode != http.StatusOK {
-			t.Fatalf("健康检查失败,状态码: %d", healthResp.StatusCode)
-		}
-		t.Logf("✅ 健康检查通过")
-
-		// 第二步:执行搜索
-		t.Logf("步骤2: 执行搜索测试...")
-		searchRequest := SearchRequest{
-			Query:    "技术",
-			NResults: 3,
-		}
-
-		jsonData, err := json.Marshal(searchRequest)
-		if err != nil {
-			t.Fatalf("序列化搜索请求失败: %v", err)
-		}
-
-		searchResp, err := client.Post(
-			fmt.Sprintf("%s/search", baseURL),
-			"application/json",
-			bytes.NewBuffer(jsonData),
-		)
-		if err != nil {
-			t.Fatalf("搜索请求失败: %v", err)
-		}
-		defer searchResp.Body.Close()
-
-		body, err := io.ReadAll(searchResp.Body)
-		if err != nil {
-			t.Fatalf("读取搜索响应失败: %v", err)
-		}
-
-		if searchResp.StatusCode != http.StatusOK {
-			t.Errorf("搜索失败,状态码: %d", searchResp.StatusCode)
-			if len(body) < 200 {
-				t.Logf("错误响应: %s", string(body))
-			}
-		} else {
-			t.Logf("✅ 搜索功能正常")
-			t.Logf("响应长度: %d 字节", len(body))
-		}
-
-		t.Logf("✅ 集成测试完成")
-	})
-}
-
-// TestNetworkConnectivity 测试网络连通性
-func TestNetworkConnectivity(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.HealthBaseURL
-
-	t.Run("网络连通性测试", func(t *testing.T) {
-		// 解析URL
-		parsedURL, err := url.Parse(baseURL)
-		if err != nil {
-			t.Fatalf("解析URL失败: %v", err)
-		}
-
-		t.Logf("测试目标: %s", baseURL)
-		t.Logf("协议: %s", parsedURL.Scheme)
-		t.Logf("主机: %s", parsedURL.Host)
-		t.Logf("端口: %s", parsedURL.Port())
-
-		// 测试TCP连接
-		conn, err := net.DialTimeout("tcp", parsedURL.Host, 5*time.Second)
-		if err != nil {
-			t.Logf("❌ TCP连接失败: %v", err)
-			t.Logf("可能的原因:")
-			t.Logf("  - 服务器未启动")
-			t.Logf("  - 防火墙阻止")
-			t.Logf("  - 网络不可达")
-			t.Logf("  - 端口不正确")
-		} else {
-			t.Logf("✅ TCP连接成功")
-			conn.Close()
-		}
-
-		// 测试HTTP连接
-		client := &http.Client{
-			Timeout: 10 * time.Second,
-		}
-
-		// 尝试简单的HEAD请求
-		resp, err := client.Head(baseURL)
-		if err != nil {
-			t.Logf("❌ HTTP HEAD请求失败: %v", err)
-		} else {
-			t.Logf("✅ HTTP连接成功,状态码: %d", resp.StatusCode)
-			resp.Body.Close()
-		}
-
-		// 尝试POST请求到根路径
-		resp, err = client.Post(baseURL, "application/json", nil)
-		if err != nil {
-			t.Logf("❌ HTTP POST请求失败: %v", err)
-		} else {
-			t.Logf("✅ HTTP POST连接成功,状态码: %d", resp.StatusCode)
-			resp.Body.Close()
-		}
-	})
-}

+ 0 - 1868
shudao-go-backend/tests/api_test.go.backup

@@ -1,1868 +0,0 @@
-package tests
-
-import (
-	"bufio"
-	"bytes"
-	"encoding/base64"
-	"encoding/json"
-	"fmt"
-	"image"
-	"image/color"
-	"image/draw"
-	"image/jpeg"
-	"io"
-	"net"
-	"net/http"
-	"net/url"
-	"os"
-	"strings"
-	"testing"
-	"time"
-)
-
-// Qwen3ChatRequest 定义Qwen3聊天请求结构
-type Qwen3ChatRequest struct {
-	Model       string        `json:"model"`
-	Messages    []ChatMessage `json:"messages"`
-	Stream      bool          `json:"stream"`
-	Temperature float64       `json:"temperature,omitempty"`
-}
-
-// ChatMessage 定义聊天消息结构
-type ChatMessage struct {
-	Role    string `json:"role"`
-	Content string `json:"content"`
-}
-
-// Qwen3ChatResponse 定义Qwen3聊天响应结构(非流式)
-type Qwen3ChatResponse struct {
-	ID      string `json:"id"`
-	Object  string `json:"object"`
-	Created int64  `json:"created"`
-	Model   string `json:"model"`
-	Choices []struct {
-		Index   int `json:"index"`
-		Message struct {
-			Role             string        `json:"role"`
-			Content          string        `json:"content"`
-			Refusal          *string       `json:"refusal"`
-			Annotations      *string       `json:"annotations"`
-			Audio            *string       `json:"audio"`
-			FunctionCall     *string       `json:"function_call"`
-			ToolCalls        []interface{} `json:"tool_calls"`
-			ReasoningContent *string       `json:"reasoning_content"`
-		} `json:"message"`
-		Logprobs     *string `json:"logprobs"`
-		FinishReason string  `json:"finish_reason"`
-		StopReason   *string `json:"stop_reason"`
-	} `json:"choices"`
-	ServiceTier       *string `json:"service_tier"`
-	SystemFingerprint *string `json:"system_fingerprint"`
-	Usage             struct {
-		PromptTokens        int     `json:"prompt_tokens"`
-		TotalTokens         int     `json:"total_tokens"`
-		CompletionTokens    int     `json:"completion_tokens"`
-		PromptTokensDetails *string `json:"prompt_tokens_details"`
-	} `json:"usage"`
-	PromptLogprobs   *string `json:"prompt_logprobs"`
-	KvTransferParams *string `json:"kv_transfer_params"`
-}
-
-// Qwen3StreamResponse 定义Qwen3流式响应结构
-type Qwen3StreamResponse struct {
-	ID                string `json:"id"`
-	Object            string `json:"object"`
-	Created           int64  `json:"created"`
-	Model             string `json:"model"`
-	SystemFingerprint string `json:"system_fingerprint,omitempty"`
-	Choices           []struct {
-		Index int `json:"index"`
-		Delta struct {
-			Role      string `json:"role,omitempty"`
-			Content   string `json:"content,omitempty"`
-			ToolCalls []struct {
-				Index    int    `json:"index"`
-				ID       string `json:"id"`
-				Type     string `json:"type"`
-				Function struct {
-					Name      string `json:"name"`
-					Arguments string `json:"arguments"`
-				} `json:"function"`
-			} `json:"tool_calls,omitempty"`
-		} `json:"delta"`
-		Logprobs     interface{} `json:"logprobs"`
-		FinishReason *string     `json:"finish_reason"`
-		StopReason   *string     `json:"stop_reason,omitempty"`
-	} `json:"choices"`
-}
-
-// TestQwen3ChatAPI 测试Qwen3聊天接口
-func TestQwen3ChatAPI(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.Qwen3BaseURL
-	model := config.Qwen3Model
-
-	testCases := []struct {
-		name    string
-		message string
-	}{
-		{
-			name:    "基础问候测试",
-			message: "你好,请介绍一下你自己。",
-		},
-		{
-			name:    "技术问题测试",
-			message: "请解释一下什么是编程?",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			// 构建非流式请求
-			request := Qwen3ChatRequest{
-				Model:  model,
-				Stream: false,
-				Messages: []ChatMessage{
-					{
-						Role:    "system",
-						Content: "你是一个乐于助人的助手。",
-					},
-					{
-						Role:    "user",
-						Content: tc.message,
-					},
-				},
-			}
-
-			jsonData, err := json.Marshal(request)
-			if err != nil {
-				t.Fatalf("序列化请求失败: %v", err)
-			}
-
-			t.Logf("发送的请求: %s", string(jsonData))
-
-			// 发送POST请求
-			resp, err := http.Post(
-				fmt.Sprintf("%s/v1/chat/completions", baseURL),
-				"application/json",
-				bytes.NewBuffer(jsonData),
-			)
-			if err != nil {
-				t.Fatalf("发送请求失败: %v", err)
-			}
-			defer resp.Body.Close()
-
-			t.Logf("响应状态码: %d", resp.StatusCode)
-			t.Logf("Content-Type: %s", resp.Header.Get("Content-Type"))
-
-			if resp.StatusCode != http.StatusOK {
-				body, _ := io.ReadAll(resp.Body)
-				t.Fatalf("请求失败,状态码: %d, 响应: %s", resp.StatusCode, string(body))
-			}
-
-			// 读取完整的响应内容
-			body, err := io.ReadAll(resp.Body)
-			if err != nil {
-				t.Fatalf("读取响应失败: %v", err)
-			}
-
-			t.Logf("响应长度: %d", len(body))
-
-			// 解析JSON响应
-			var response Qwen3ChatResponse
-			if err := json.Unmarshal(body, &response); err != nil {
-				t.Fatalf("解析JSON响应失败: %v", err)
-			}
-
-			// 验证响应
-			if response.ID == "" {
-				t.Error("响应ID为空")
-			}
-			if len(response.Choices) == 0 {
-				t.Error("响应中没有选择项")
-			}
-
-			t.Logf("✅ 非流式响应成功!")
-			t.Logf("请求: %s", tc.message)
-			t.Logf("响应ID: %s", response.ID)
-			t.Logf("模型: %s", response.Model)
-			t.Logf("完整响应: %s", response.Choices[0].Message.Content)
-		})
-	}
-}
-
-// TestQwen3StreamAPI 测试Qwen3流式聊天接口
-func TestQwen3StreamAPI(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.Qwen3BaseURL
-	model := config.Qwen3Model
-
-	testCases := []struct {
-		name    string
-		message string
-	}{
-		{
-			name:    "流式基础问候测试",
-			message: "你好,请介绍一下你自己。",
-		},
-		{
-			name:    "流式技术问题测试",
-			message: "请解释一下什么是编程?",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			// 构建流式请求
-			request := Qwen3ChatRequest{
-				Model:       model,
-				Stream:      false,
-				Temperature: 0.7,
-				Messages: []ChatMessage{
-					{
-						Role:    "system",
-						Content: "你是一个乐于助人的助手。",
-					},
-					{
-						Role:    "user",
-						Content: tc.message,
-					},
-				},
-			}
-
-			jsonData, err := json.Marshal(request)
-			if err != nil {
-				t.Fatalf("序列化请求失败: %v", err)
-			}
-
-			// 发送POST请求
-			resp, err := http.Post(
-				fmt.Sprintf("%s/v1/chat/completions", baseURL),
-				"application/json",
-				bytes.NewBuffer(jsonData),
-			)
-			if err != nil {
-				t.Fatalf("发送请求失败: %v", err)
-			}
-			defer resp.Body.Close()
-
-			t.Logf("响应状态码: %d", resp.StatusCode)
-			t.Logf("Content-Type: %s", resp.Header.Get("Content-Type"))
-
-			if resp.StatusCode != http.StatusOK {
-				body, _ := io.ReadAll(resp.Body)
-				t.Fatalf("请求失败,状态码: %d, 响应: %s", resp.StatusCode, string(body))
-			}
-
-			t.Logf("响应长度: %d", resp.ContentLength)
-
-			if request.Stream {
-				// 处理流式响应
-				scanner := bufio.NewScanner(resp.Body)
-				var fullContent strings.Builder
-				var responseID string
-				var firstChunk = true
-
-				t.Logf("开始接收流式响应...")
-				chunkCount := 0
-
-				for scanner.Scan() {
-					line := scanner.Text()
-					chunkCount++
-
-					// 跳过空行和data:前缀
-					if line == "" || !strings.HasPrefix(line, "data: ") {
-						continue
-					}
-
-					// 移除"data: "前缀
-					data := strings.TrimPrefix(line, "data: ")
-
-					// 检查是否是结束标记
-					if data == "[DONE]" {
-						t.Logf("收到结束标记")
-						break
-					}
-
-					// 解析JSON数据
-					var streamResp Qwen3StreamResponse
-					if err := json.Unmarshal([]byte(data), &streamResp); err != nil {
-						t.Logf("解析流式响应失败: %v, 数据: %s", err, data)
-						continue
-					}
-
-					// 记录第一个块的ID
-					if firstChunk {
-						responseID = streamResp.ID
-						t.Logf("响应ID: %s", responseID)
-						t.Logf("模型: %s", streamResp.Model)
-						firstChunk = false
-					}
-
-					// 处理choices中的内容
-					if len(streamResp.Choices) > 0 {
-						choice := streamResp.Choices[0]
-						if choice.Delta.Content != "" {
-							fullContent.WriteString(choice.Delta.Content)
-							t.Logf("收到内容块: %s", choice.Delta.Content)
-						}
-
-						// 检查是否完成
-						if choice.FinishReason != nil {
-							t.Logf("完成原因: %s", *choice.FinishReason)
-							break
-						}
-					}
-				}
-
-				if err := scanner.Err(); err != nil {
-					t.Fatalf("读取流式响应失败: %v", err)
-				}
-
-				// 验证结果
-				finalContent := fullContent.String()
-				if finalContent == "" {
-					t.Error("流式响应内容为空")
-				} else {
-					t.Logf("✅ 流式响应成功!")
-					t.Logf("请求: %s", tc.message)
-					t.Logf("响应ID: %s", responseID)
-					t.Logf("总块数: %d", chunkCount)
-					t.Logf("完整响应: %s", finalContent)
-				}
-			} else {
-				// 处理非流式响应
-				body, err := io.ReadAll(resp.Body)
-				if err != nil {
-					t.Fatalf("读取响应失败: %v", err)
-				}
-
-				t.Logf("开始解析非流式响应...")
-
-				// 解析JSON响应
-				var response Qwen3ChatResponse
-				if err := json.Unmarshal(body, &response); err != nil {
-					t.Fatalf("解析JSON响应失败: %v", err)
-				}
-
-				// 验证响应
-				if response.ID == "" {
-					t.Error("响应ID为空")
-				}
-				if len(response.Choices) == 0 {
-					t.Error("响应中没有选择项")
-				}
-
-				t.Logf("✅ 非流式响应成功!")
-				t.Logf("请求: %s", tc.message)
-				t.Logf("响应ID: %s", response.ID)
-				t.Logf("模型: %s", response.Model)
-				t.Logf("完整响应: %s", response.Choices[0].Message.Content)
-			}
-		})
-	}
-}
-
-// TestYOLOPredictAPI 测试YOLO图像识别接口
-func TestYOLOPredictAPI(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.YOLOBaseURL
-
-	testCase := struct {
-		modeltype      string
-		image          string
-		conf_threshold float32
-	}{
-		modeltype:      "gas_station",
-		image:          "test_images/1.jpg",
-		conf_threshold: 0.5,
-	}
-
-	// 使用单个测试用例,不需要循环
-	tc := testCase
-
-	// 检查图像文件是否存在
-	if _, err := os.Stat(tc.image); os.IsNotExist(err) {
-		t.Skipf("测试图像文件不存在: %s", tc.image)
-	}
-
-	// 读取图像文件并转换为base64
-	imageData, err := os.ReadFile(tc.image)
-	if err != nil {
-		t.Fatalf("读取图像文件失败: %v", err)
-	}
-
-	// 解码图像用于后续处理
-	img, _, err := image.Decode(bytes.NewReader(imageData))
-	if err != nil {
-		t.Fatalf("解码图像失败: %v", err)
-	}
-
-	// 将图像数据转换为base64
-	imageBase64 := base64.StdEncoding.EncodeToString(imageData)
-
-	// 构建JSON请求体
-	requestBody := map[string]interface{}{
-		"modeltype":      tc.modeltype,
-		"image":          imageBase64,
-		"conf_threshold": tc.conf_threshold,
-	}
-
-	jsonData, err := json.Marshal(requestBody)
-	if err != nil {
-		t.Fatalf("序列化请求体失败: %v", err)
-	}
-
-	resp, err := http.Post(
-		fmt.Sprintf("%s/predict", baseURL),
-		"application/json",
-		bytes.NewBuffer(jsonData),
-	)
-	if err != nil {
-		t.Fatalf("发送请求失败: %v", err)
-	}
-	defer resp.Body.Close()
-
-	body, err := io.ReadAll(resp.Body)
-	if err != nil {
-		t.Fatalf("读取响应失败: %v", err)
-	}
-
-	if resp.StatusCode != http.StatusOK {
-		t.Errorf("请求失败,状态码: %d, 响应: %s", resp.StatusCode, string(body))
-	}
-
-	if len(body) == 0 {
-		t.Error("响应内容为空")
-	}
-
-	t.Logf("响应状态码: %d", resp.StatusCode)
-	t.Logf("响应内容: %s", string(body))
-
-	// 解析YOLO响应
-	var yoloResp YOLOResponse
-	if err := json.Unmarshal(body, &yoloResp); err != nil {
-		t.Fatalf("解析YOLO响应失败: %v", err)
-	}
-
-	t.Logf("解析成功: 检测到 %d 个对象", len(yoloResp.Labels))
-
-	t.Logf("开始绘制边界框...")
-	// 在原始图像上绘制边界框
-	annotatedImg := drawBoundingBox(img, yoloResp.Boxes, yoloResp.Labels, yoloResp.Scores)
-	t.Logf("边界框绘制完成")
-
-	t.Logf("开始保存图像...")
-	// 保存标注后的图像
-	outputPath := "test_images/annotated_1.jpg"
-	outputFile, err := os.Create(outputPath)
-	if err != nil {
-		t.Fatalf("创建输出文件失败: %v", err)
-	}
-	defer outputFile.Close()
-
-	if err := jpeg.Encode(outputFile, annotatedImg, &jpeg.Options{Quality: 95}); err != nil {
-		t.Fatalf("保存图像失败: %v", err)
-	}
-
-	t.Logf("✅ 已保存标注后的图像到: %s", outputPath)
-	t.Logf("识别结果: 检测到 %d 个对象", len(yoloResp.Labels))
-	for i, label := range yoloResp.Labels {
-		if i < len(yoloResp.Scores) {
-			t.Logf("  - %s: 置信度 %.2f%%", label, yoloResp.Scores[i]*100)
-		}
-	}
-
-	// 强制输出日志
-	t.Logf("测试完成,图像已保存")
-}
-
-// TestAPIEndpoints 测试API端点连通性
-func TestAPIEndpoints(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	endpoints := []struct {
-		name string
-		url  string
-	}{
-		{"Qwen3聊天接口", config.GetQwen3ChatURL()},
-		{"YOLO预测接口", config.GetYOLOPredictURL()},
-		{"TTS流式接口", config.GetTTSStreamURL()},
-	}
-
-	for _, endpoint := range endpoints {
-		t.Run(endpoint.name, func(t *testing.T) {
-			client := &http.Client{
-				Timeout: 10 * time.Second,
-			}
-
-			resp, err := client.Head(endpoint.url)
-			if err != nil {
-				t.Logf("端点 %s 不可达: %v", endpoint.name, err)
-				return
-			}
-			defer resp.Body.Close()
-
-			t.Logf("端点 %s 可达,状态码: %d", endpoint.name, resp.StatusCode)
-		})
-	}
-}
-
-// BenchmarkQwen3ChatAPI 性能测试Qwen3聊天接口
-func BenchmarkQwen3ChatAPI(b *testing.B) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.Qwen3BaseURL
-	model := config.Qwen3Model
-
-	request := Qwen3ChatRequest{
-		Model: model,
-		Messages: []ChatMessage{
-			{
-				Role:    "user",
-				Content: "你好",
-			},
-		},
-	}
-
-	jsonData, _ := json.Marshal(request)
-
-	b.ResetTimer()
-	for i := 0; i < b.N; i++ {
-		resp, err := http.Post(
-			fmt.Sprintf("%s/v1/chat/completions", baseURL),
-			"application/json",
-			bytes.NewBuffer(jsonData),
-		)
-		if err != nil {
-			b.Fatalf("请求失败: %v", err)
-		}
-		resp.Body.Close()
-	}
-}
-
-// YOLOResponse 定义YOLO响应结构
-type YOLOResponse struct {
-	Boxes     [][]float64 `json:"boxes"`
-	Labels    []string    `json:"labels"`
-	Scores    []float64   `json:"scores"`
-	ModelType string      `json:"model_type"`
-}
-
-// drawBoundingBox 在图像上绘制边界框和标签
-func drawBoundingBox(img image.Image, boxes [][]float64, labels []string, scores []float64) image.Image {
-	// 创建可绘制的图像副本
-	bounds := img.Bounds()
-	drawableImg := image.NewRGBA(bounds)
-	draw.Draw(drawableImg, bounds, img, image.Point{}, draw.Src)
-
-	// 红色边界框
-	red := image.NewUniform(color.RGBA{255, 0, 0, 255})
-
-	fmt.Printf("图像边界: %v\n", bounds)
-
-	for i, box := range boxes {
-		if len(box) >= 4 {
-			x1, y1, x2, y2 := int(box[0]), int(box[1]), int(box[2]), int(box[3])
-			fmt.Printf("边界框 %d: [%d, %d, %d, %d]\n", i, x1, y1, x2, y2)
-
-			// 绘制边界框
-			drawRect(drawableImg, x1, y1, x2, y2, red)
-
-			// 绘制标签和置信度(暂时注释掉,避免白色区域)
-			// if i < len(labels) && i < len(scores) {
-			// 	label := fmt.Sprintf("%s: %.2f", labels[i], scores[i])
-			// 	drawLabel(drawableImg, x1, y1-20, label)
-			// }
-		}
-	}
-
-	return drawableImg
-}
-
-// drawRect 绘制矩形
-func drawRect(img *image.RGBA, x1, y1, x2, y2 int, color *image.Uniform) {
-	bounds := img.Bounds()
-	fmt.Printf("绘制矩形: [%d, %d, %d, %d], 图像边界: %v\n", x1, y1, x2, y2, bounds)
-
-	// 确保坐标在图像边界内
-	if x1 < bounds.Min.X {
-		x1 = bounds.Min.X
-	}
-	if y1 < bounds.Min.Y {
-		y1 = bounds.Min.Y
-	}
-	if x2 >= bounds.Max.X {
-		x2 = bounds.Max.X - 1
-	}
-	if y2 >= bounds.Max.Y {
-		y2 = bounds.Max.Y - 1
-	}
-
-	fmt.Printf("调整后坐标: [%d, %d, %d, %d]\n", x1, y1, x2, y2)
-
-	// 绘制四条边
-	for x := x1; x <= x2; x++ {
-		img.Set(x, y1, color)
-		img.Set(x, y2, color)
-	}
-	for y := y1; y <= y2; y++ {
-		img.Set(x1, y, color)
-		img.Set(x2, y, color)
-	}
-
-	fmt.Printf("矩形绘制完成\n")
-}
-
-// drawLabel 绘制标签文本(简化版本,只绘制背景矩形)
-func drawLabel(img *image.RGBA, x, y int, text string) {
-	fmt.Printf("绘制标签: '%s' 在位置 [%d, %d]\n", text, x, y)
-
-	// 绘制白色背景矩形
-	white := image.NewUniform(color.RGBA{255, 255, 255, 255})
-	labelWidth := len(text) * 8 // 估算文本宽度
-	labelHeight := 16
-
-	// 确保标签在图像边界内
-	bounds := img.Bounds()
-	if x < bounds.Min.X {
-		x = bounds.Min.X
-	}
-	if y < bounds.Min.Y {
-		y = bounds.Min.Y
-	}
-	if x+labelWidth >= bounds.Max.X {
-		labelWidth = bounds.Max.X - x - 1
-	}
-	if y+labelHeight >= bounds.Max.Y {
-		labelHeight = bounds.Max.Y - y - 1
-	}
-
-	fmt.Printf("标签尺寸: %dx%d\n", labelWidth, labelHeight)
-
-	// 绘制背景矩形
-	for dx := 0; dx < labelWidth; dx++ {
-		for dy := 0; dy < labelHeight; dy++ {
-			if x+dx < bounds.Max.X && y+dy < bounds.Max.Y && x+dx >= bounds.Min.X && y+dy >= bounds.Min.Y {
-				img.Set(x+dx, y+dy, white)
-			}
-		}
-	}
-
-	fmt.Printf("标签绘制完成\n")
-}
-
-// TestHealthCheck 测试健康检查接口
-func TestHealthCheck(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.HealthBaseURL
-
-	t.Run("健康检查测试", func(t *testing.T) {
-		client := &http.Client{
-			Timeout: 10 * time.Second,
-		}
-
-		// 发送POST请求到健康检查端点
-		t.Logf("请求URL: %s/health", baseURL)
-		resp, err := client.Post(fmt.Sprintf("%s/health", baseURL), "application/json", nil)
-		if err != nil {
-			t.Logf("❌ 健康检查请求失败: %v", err)
-			t.Logf("错误类型: %T", err)
-
-			// 检查是否是网络相关错误
-			if netErr, ok := err.(interface{ Timeout() bool }); ok && netErr.Timeout() {
-				t.Logf("⚠️ 这是超时错误")
-			}
-
-			// 尝试更详细的错误信息
-			if urlErr, ok := err.(*url.Error); ok {
-				t.Logf("URL错误详情: %+v", urlErr)
-				if urlErr.Err != nil {
-					t.Logf("底层错误: %v", urlErr.Err)
-				}
-			}
-
-			t.Fatalf("健康检查请求失败: %v", err)
-		}
-		defer resp.Body.Close()
-
-		// 读取响应内容
-		body, err := io.ReadAll(resp.Body)
-		if err != nil {
-			t.Logf("读取响应失败: %v", err)
-		}
-
-		t.Logf("健康检查响应状态码: %d", resp.StatusCode)
-		t.Logf("响应头: %+v", resp.Header)
-		t.Logf("响应内容: %s", string(body))
-
-		// 验证响应状态码
-		if resp.StatusCode != http.StatusOK {
-			t.Errorf("健康检查失败,期望状态码200,实际状态码: %d", resp.StatusCode)
-		}
-
-		// 验证响应内容(通常健康检查返回简单的状态信息)
-		if len(body) == 0 {
-			t.Error("健康检查响应内容为空")
-		}
-
-		// 尝试解析JSON响应(如果返回的是JSON格式)
-		var healthResponse map[string]interface{}
-		if err := json.Unmarshal(body, &healthResponse); err == nil {
-			t.Logf("✅ 健康检查返回JSON格式响应: %+v", healthResponse)
-		} else {
-			t.Logf("健康检查返回非JSON格式响应: %s", string(body))
-		}
-	})
-}
-
-// ShudaoWorkflowRequest 定义蜀道集团工作流请求结构
-type ShudaoWorkflowRequest struct {
-	WorkflowID   string                 `json:"workflow_id"`
-	Inputs       map[string]interface{} `json:"inputs"`
-	ResponseMode string                 `json:"response_mode"`
-	User         string                 `json:"user"`
-}
-
-// ShudaoWorkflowResponse 定义蜀道集团工作流响应结构
-type ShudaoWorkflowResponse struct {
-	TaskID         string `json:"task_id"`
-	WorkflowRunID  string `json:"workflow_run_id"`
-	Data           struct {
-		ID          string `json:"id"`
-		WorkflowID  string `json:"workflow_id"`
-		Status      string `json:"status"`
-		Outputs     struct {
-			JSON []struct {
-				Answer            *string `json:"answer"`
-				FollowUpQuestions *string `json:"follow_up_questions"`
-				Images            []string `json:"images"`
-				Query             string  `json:"query"`
-				RequestID         string  `json:"request_id"`
-				ResponseTime      float64 `json:"response_time"`
-				Results           []struct {
-					Content     string  `json:"content"`
-					RawContent  *string `json:"raw_content"`
-					Score       float64 `json:"score"`
-					Title       string  `json:"title"`
-					URL         string  `json:"url"`
-				} `json:"results"`
-			} `json:"json"`
-		} `json:"outputs"`
-		Error        string  `json:"error"`
-		ElapsedTime  float64 `json:"elapsed_time"`
-		TotalTokens  int     `json:"total_tokens"`
-		TotalSteps   int     `json:"total_steps"`
-		CreatedAt    int64   `json:"created_at"`
-		FinishedAt   int64   `json:"finished_at"`
-	} `json:"data"`
-}
-
-// TestShudaoWorkflowConnectivity 测试蜀道集团工作流接口连通性
-func TestShudaoWorkflowConnectivity(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.GetShudaoWorkflowURL()
-	authToken := config.ShuDaoAuthToken
-	workflowID := config.ShuDaoWorkflowID
-
-	t.Run("蜀道集团工作流接口连通性测试", func(t *testing.T) {
-		client := &http.Client{
-			Timeout: 60 * time.Second, // 工作流可能需要更长时间
-		}
-
-		t.Logf("测试蜀道集团工作流接口连通性: %s", baseURL)
-		t.Logf("使用工作流ID: %s", workflowID)
-		t.Logf("使用认证令牌: %s", authToken)
-
-		// 构建测试请求
-		request := ShudaoWorkflowRequest{
-			WorkflowID: workflowID,
-			Inputs: map[string]interface{}{
-				"keywords": "蜀道集团",
-				"num":      5, // 减少结果数量以加快测试
-			},
-			ResponseMode: "blocking",
-			User:         "test_user_001",
-		}
-
-		jsonData, err := json.Marshal(request)
-		if err != nil {
-			t.Fatalf("序列化测试请求失败: %v", err)
-		}
-
-		// 创建HTTP请求
-		req, err := http.NewRequest("POST", baseURL, bytes.NewBuffer(jsonData))
-		if err != nil {
-			t.Fatalf("创建HTTP请求失败: %v", err)
-		}
-
-		// 设置请求头
-		req.Header.Set("Authorization", "Bearer "+authToken)
-		req.Header.Set("Content-Type", "application/json")
-
-		// 发送请求
-		resp, err := client.Do(req)
-		if err != nil {
-			t.Logf("❌ 蜀道集团工作流接口连接失败: %v", err)
-			t.Logf("可能的原因:")
-			t.Logf("  - 工作流服务未启动")
-			t.Logf("  - 网络不可达")
-			t.Logf("  - 端口不正确")
-			t.Logf("  - 防火墙阻止")
-			t.Logf("  - 认证令牌无效")
-			t.Fatalf("蜀道集团工作流接口连通性测试失败: %v", err)
-		}
-		defer resp.Body.Close()
-
-		t.Logf("✅ 蜀道集团工作流接口HTTP连接成功")
-		t.Logf("响应状态码: %d", resp.StatusCode)
-		t.Logf("Content-Type: %s", resp.Header.Get("Content-Type"))
-
-		// 读取响应体
-		body, err := io.ReadAll(resp.Body)
-		if err != nil {
-			t.Fatalf("读取响应体失败: %v", err)
-		}
-
-		// 验证响应状态码
-		if resp.StatusCode != http.StatusOK {
-			t.Logf("⚠️ 蜀道集团工作流服务返回非200状态码: %d", resp.StatusCode)
-			t.Logf("响应内容: %s", string(body))
-			t.Fatalf("蜀道集团工作流接口返回错误状态码: %d", resp.StatusCode)
-		}
-
-		// 解析响应JSON
-		var response ShudaoWorkflowResponse
-		if err := json.Unmarshal(body, &response); err != nil {
-			t.Logf("⚠️ 响应JSON解析失败: %v", err)
-			t.Logf("原始响应: %s", string(body))
-			t.Fatalf("无法解析蜀道集团工作流响应: %v", err)
-		}
-
-		t.Logf("✅ 蜀道集团工作流服务响应正常")
-		t.Logf("任务ID: %s", response.TaskID)
-		t.Logf("工作流运行ID: %s", response.WorkflowRunID)
-		t.Logf("工作流状态: %s", response.Data.Status)
-		t.Logf("执行耗时: %.3f秒", response.Data.ElapsedTime)
-
-		// 验证工作流执行状态
-		if response.Data.Status != "succeeded" {
-			t.Logf("⚠️ 工作流执行状态异常: %s", response.Data.Status)
-			if response.Data.Error != "" {
-				t.Logf("错误信息: %s", response.Data.Error)
-			}
-			t.Fatalf("工作流执行失败,状态: %s", response.Data.Status)
-		}
-
-		// 验证返回结果
-		if len(response.Data.Outputs.JSON) == 0 {
-			t.Fatalf("工作流未返回任何结果")
-		}
-
-		jsonResult := response.Data.Outputs.JSON[0]
-		t.Logf("查询关键词: %s", jsonResult.Query)
-		t.Logf("检索耗时: %.3f秒", jsonResult.ResponseTime)
-		t.Logf("返回结果数量: %d", len(jsonResult.Results))
-
-		// 验证结果质量
-		if len(jsonResult.Results) == 0 {
-			t.Fatalf("未返回任何检索结果")
-		}
-
-		// 显示前3个结果的基本信息
-		for i, result := range jsonResult.Results {
-			if i >= 3 {
-				break
-			}
-			t.Logf("结果 %d:", i+1)
-			t.Logf("  标题: %s", result.Title)
-			t.Logf("  相关性得分: %.4f", result.Score)
-			t.Logf("  URL: %s", result.URL)
-			t.Logf("  内容摘要: %s...", truncateString(result.Content, 100))
-		}
-
-		// 验证结果质量(相关性得分)
-		highScoreCount := 0
-		for _, result := range jsonResult.Results {
-			if result.Score > 0.7 {
-				highScoreCount++
-			}
-		}
-
-		t.Logf("高质量结果数量 (得分>0.7): %d/%d", highScoreCount, len(jsonResult.Results))
-
-		if highScoreCount == 0 {
-			t.Logf("⚠️ 警告: 没有高质量结果 (得分>0.7)")
-		}
-
-		t.Logf("✅ 蜀道集团工作流接口测试完成")
-	})
-}
-
-// truncateString 截断字符串到指定长度
-func truncateString(s string, maxLen int) string {
-	if len(s) <= maxLen {
-		return s
-	}
-	return s[:maxLen]
-}
-
-// SearchRequest 定义搜索请求结构
-type SearchRequest struct {
-	Query    string `json:"query"`
-	NResults int    `json:"n_results"`
-}
-
-// SearchResponse 定义搜索响应结构
-type SearchResponse struct {
-	Results []SearchResult `json:"results"`
-	Total   int            `json:"total"`
-	Query   string         `json:"query"`
-}
-
-// SearchResult 定义搜索结果结构
-type SearchResult struct {
-	ID       string                 `json:"id"`
-	Title    string                 `json:"title"`
-	Content  string                 `json:"content"`
-	Score    float64                `json:"score"`
-	Metadata map[string]interface{} `json:"metadata,omitempty"`
-}
-
-// TestSearchAPI 测试搜索功能接口
-func TestSearchAPI(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.SearchBaseURL
-
-	testCases := []struct {
-		name     string
-		query    string
-		nResults int
-	}{
-		{
-			name:     "技术搜索测试",
-			query:    "技术",
-			nResults: 3,
-		},
-		{
-			name:     "编程搜索测试",
-			query:    "编程",
-			nResults: 5,
-		},
-		{
-			name:     "AI搜索测试",
-			query:    "人工智能",
-			nResults: 2,
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			// 构建搜索请求
-			request := SearchRequest{
-				Query:    tc.query,
-				NResults: tc.nResults,
-			}
-
-			jsonData, err := json.Marshal(request)
-			if err != nil {
-				t.Fatalf("序列化搜索请求失败: %v", err)
-			}
-
-			// 发送POST请求到搜索端点
-			resp, err := http.Post(
-				fmt.Sprintf("%s/search", baseURL),
-				"application/json",
-				bytes.NewBuffer(jsonData),
-			)
-			if err != nil {
-				t.Fatalf("搜索请求失败: %v", err)
-			}
-			defer resp.Body.Close()
-
-			// 读取响应内容
-			body, err := io.ReadAll(resp.Body)
-			if err != nil {
-				t.Fatalf("读取搜索响应失败: %v", err)
-			}
-
-			t.Logf("搜索响应状态码: %d", resp.StatusCode)
-			t.Logf("搜索查询: %s", tc.query)
-			t.Logf("请求结果数量: %d", tc.nResults)
-
-			// 验证响应状态码
-			if resp.StatusCode != http.StatusOK {
-				t.Errorf("搜索请求失败,期望状态码200,实际状态码: %d", resp.StatusCode)
-				if len(body) < 500 {
-					t.Logf("错误响应内容: %s", string(body))
-				}
-				return
-			}
-
-			// 验证响应内容不为空
-			if len(body) == 0 {
-				t.Error("搜索响应内容为空")
-				return
-			}
-
-			// 尝试解析JSON响应
-			var searchResponse SearchResponse
-			if err := json.Unmarshal(body, &searchResponse); err != nil {
-				t.Logf("⚠️ 搜索响应不是标准JSON格式: %v", err)
-				t.Logf("响应内容: %s", string(body))
-
-				// 尝试解析为简单的map结构
-				var simpleResponse map[string]interface{}
-				if err := json.Unmarshal(body, &simpleResponse); err == nil {
-					t.Logf("✅ 搜索返回简单JSON格式: %+v", simpleResponse)
-				} else {
-					t.Logf("搜索返回非JSON格式响应")
-				}
-				return
-			}
-
-			// 验证搜索结果
-			t.Logf("✅ 搜索成功!")
-			t.Logf("查询: %s", searchResponse.Query)
-			t.Logf("总结果数: %d", searchResponse.Total)
-			t.Logf("返回结果数: %d", len(searchResponse.Results))
-
-			// 验证结果数量
-			if len(searchResponse.Results) > tc.nResults {
-				t.Logf("⚠️ 返回结果数量(%d)超过请求数量(%d)", len(searchResponse.Results), tc.nResults)
-			}
-
-			// 显示搜索结果详情
-			for i, result := range searchResponse.Results {
-				t.Logf("结果 %d:", i+1)
-				t.Logf("  ID: %s", result.ID)
-				t.Logf("  标题: %s", result.Title)
-				t.Logf("  内容: %s", truncateString(result.Content, 100))
-				t.Logf("  评分: %.4f", result.Score)
-				if len(result.Metadata) > 0 {
-					t.Logf("  元数据: %+v", result.Metadata)
-				}
-			}
-		})
-	}
-}
-
-// truncateString 截断字符串到指定长度
-func truncateString(s string, maxLen int) string {
-	if len(s) <= maxLen {
-		return s
-	}
-	return s[:maxLen] + "..."
-}
-
-// TTSRequest 定义TTS请求结构
-type TTSRequest struct {
-	Text string `json:"text"`
-}
-
-// TestTTSStreamAPI 测试TTS流式接口
-func TestTTSStreamAPI(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.TTSBaseURL
-
-	testCases := []struct {
-		name string
-		text string
-	}{
-		{
-			name: "短文本测试",
-			text: "你好,这是一个语音合成测试。",
-		},
-		{
-			name: "中等长度文本测试",
-			text: "答案依据:《汽车加油站雷电防护装置检测报告综述表》委托单位随检人员在雷电防护装置检测过程中承担着关键的协同与监督职责。",
-		},
-		{
-			name: "长文本测试",
-			text: "答案依据:《汽车加油站雷电防护装置检测报告综述表》委托单位随检人员在雷电防护装置检测过程中承担着关键的协同与监督职责,其参与过程对检测报告的完整性与合规性具有直接影响。作为检测工作的现场对接方和责任主体之一,委托单位随检人员需全程参与检测实施,确保检测工作在真实、可控的现场环境下开展,有效保障检测数据的真实性与代表性。",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			// 构建TTS请求
-			request := TTSRequest{
-				Text: tc.text,
-			}
-
-			jsonData, err := json.Marshal(request)
-			if err != nil {
-				t.Fatalf("序列化TTS请求失败: %v", err)
-			}
-
-			t.Logf("发送的TTS请求: %s", string(jsonData))
-			t.Logf("请求URL: %s/tts/voice", baseURL)
-
-			// 创建HTTP客户端,设置较长的超时时间(TTS可能需要较长时间)
-			client := &http.Client{
-				Timeout: 60 * time.Second,
-			}
-
-			// 发送POST请求
-			resp, err := client.Post(
-				fmt.Sprintf("%s/tts/voice", baseURL),
-				"application/json",
-				bytes.NewBuffer(jsonData),
-			)
-			if err != nil {
-				t.Fatalf("发送TTS请求失败: %v", err)
-			}
-			defer resp.Body.Close()
-
-			t.Logf("TTS响应状态码: %d", resp.StatusCode)
-			t.Logf("Content-Type: %s", resp.Header.Get("Content-Type"))
-			t.Logf("Content-Length: %s", resp.Header.Get("Content-Length"))
-
-			// 验证响应状态码
-			if resp.StatusCode != http.StatusOK {
-				body, _ := io.ReadAll(resp.Body)
-				t.Fatalf("TTS请求失败,状态码: %d, 响应: %s", resp.StatusCode, string(body))
-			}
-
-			// 检查Content-Type是否为音频格式
-			contentType := resp.Header.Get("Content-Type")
-			if contentType != "" {
-				t.Logf("响应Content-Type: %s", contentType)
-				// WAV格式通常为 "audio/wav" 或 "audio/wave"
-				if !strings.Contains(contentType, "audio") && !strings.Contains(contentType, "wav") {
-					t.Logf("⚠️ 警告: Content-Type不是预期的音频格式")
-				}
-			}
-
-			// 读取流式音频数据
-			t.Logf("开始接收流式音频数据...")
-			var audioData bytes.Buffer
-			totalBytes := 0
-			chunkCount := 0
-
-			// 分块读取数据
-			buffer := make([]byte, 4096) // 4KB缓冲区
-			for {
-				n, err := resp.Body.Read(buffer)
-				if n > 0 {
-					audioData.Write(buffer[:n])
-					totalBytes += n
-					chunkCount++
-
-					// 每读取10个块输出一次进度
-					if chunkCount%10 == 0 {
-						t.Logf("已接收 %d 字节音频数据 (块数: %d)", totalBytes, chunkCount)
-					}
-				}
-
-				if err == io.EOF {
-					t.Logf("音频数据接收完成")
-					break
-				}
-				if err != nil {
-					t.Fatalf("读取音频数据失败: %v", err)
-				}
-			}
-
-			t.Logf("✅ TTS流式响应成功!")
-			t.Logf("请求文本: %s", tc.text)
-			t.Logf("文本长度: %d 字符", len(tc.text))
-			t.Logf("音频数据总大小: %d 字节", totalBytes)
-			t.Logf("接收块数: %d", chunkCount)
-
-			// 验证音频数据不为空
-			if totalBytes == 0 {
-				t.Error("音频数据为空")
-				return
-			}
-
-			// 验证音频数据大小合理性(至少应该有WAV文件头)
-			if totalBytes < 44 {
-				t.Errorf("音频数据太小,可能不是有效的WAV文件: %d 字节", totalBytes)
-			}
-
-			// 验证WAV文件头(RIFF格式)
-			audioBytes := audioData.Bytes()
-			if len(audioBytes) >= 12 {
-				// 检查RIFF头
-				if string(audioBytes[0:4]) == "RIFF" {
-					t.Logf("✅ 检测到有效的RIFF格式音频文件")
-					if len(audioBytes) >= 8 {
-						fileSize := int(audioBytes[4]) | int(audioBytes[5])<<8 | int(audioBytes[6])<<16 | int(audioBytes[7])<<24
-						t.Logf("WAV文件大小字段: %d 字节", fileSize)
-					}
-					if len(audioBytes) >= 12 && string(audioBytes[8:12]) == "WAVE" {
-						t.Logf("✅ 检测到WAVE格式")
-					}
-				} else {
-					t.Logf("⚠️ 警告: 音频数据不是标准的RIFF格式")
-					t.Logf("文件头: %x", audioBytes[:min(16, len(audioBytes))])
-				}
-			}
-
-			// 保存音频文件到本地进行验证
-			outputFilename := fmt.Sprintf("tts_test_%s.wav", strings.ReplaceAll(tc.name, " ", "_"))
-			outputFile, err := os.Create(outputFilename)
-			if err != nil {
-				t.Fatalf("创建音频输出文件失败: %v", err)
-			}
-			defer outputFile.Close()
-
-			_, err = outputFile.Write(audioBytes)
-			if err != nil {
-				t.Fatalf("写入音频文件失败: %v", err)
-			}
-
-			t.Logf("✅ 音频文件已保存: %s", outputFilename)
-			t.Logf("文件大小: %d 字节", totalBytes)
-
-			// 验证文件是否成功创建
-			if stat, err := os.Stat(outputFilename); err == nil {
-				t.Logf("✅ 文件验证成功,实际大小: %d 字节", stat.Size())
-				if stat.Size() != int64(totalBytes) {
-					t.Errorf("文件大小不匹配: 期望 %d, 实际 %d", totalBytes, stat.Size())
-				}
-			} else {
-				t.Errorf("文件验证失败: %v", err)
-			}
-		})
-	}
-}
-
-// min 返回两个整数中的较小值
-func min(a, b int) int {
-	if a < b {
-		return a
-	}
-	return b
-}
-
-// TestTTSConnectivity 测试TTS接口连通性
-func TestTTSConnectivity(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.TTSBaseURL
-
-	t.Run("TTS接口连通性测试", func(t *testing.T) {
-		client := &http.Client{
-			Timeout: 30 * time.Second,
-		}
-
-		t.Logf("测试TTS接口连通性: %s/tts/voice", baseURL)
-
-		// 构建简单的测试请求
-		request := TTSRequest{
-			Text: "测试",
-		}
-
-		jsonData, err := json.Marshal(request)
-		if err != nil {
-			t.Fatalf("序列化测试请求失败: %v", err)
-		}
-
-		// 发送POST请求
-		resp, err := client.Post(
-			fmt.Sprintf("%s/tts/voice", baseURL),
-			"application/json",
-			bytes.NewBuffer(jsonData),
-		)
-		if err != nil {
-			t.Logf("❌ TTS接口连接失败: %v", err)
-			t.Logf("可能的原因:")
-			t.Logf("  - TTS服务未启动")
-			t.Logf("  - 网络不可达")
-			t.Logf("  - 端口不正确")
-			t.Logf("  - 防火墙阻止")
-			t.Fatalf("TTS接口连通性测试失败: %v", err)
-		}
-		defer resp.Body.Close()
-
-		t.Logf("✅ TTS接口HTTP连接成功")
-		t.Logf("响应状态码: %d", resp.StatusCode)
-		t.Logf("Content-Type: %s", resp.Header.Get("Content-Type"))
-
-		// 验证响应状态码
-		if resp.StatusCode != http.StatusOK {
-			body, _ := io.ReadAll(resp.Body)
-			t.Logf("⚠️ TTS服务不可用,状态码: %d", resp.StatusCode)
-			t.Logf("响应内容: %s", string(body))
-
-			// 根据状态码提供具体的错误信息
-			switch resp.StatusCode {
-			case 502:
-				t.Logf("❌ 502 Bad Gateway - TTS后端服务可能未启动或配置错误")
-			case 503:
-				t.Logf("❌ 503 Service Unavailable - TTS服务暂时不可用")
-			case 404:
-				t.Logf("❌ 404 Not Found - TTS接口路径不存在")
-			case 500:
-				t.Logf("❌ 500 Internal Server Error - TTS服务内部错误")
-			default:
-				t.Logf("❌ 未知错误状态码: %d", resp.StatusCode)
-			}
-		} else {
-			t.Logf("✅ TTS服务响应正常")
-
-			// 读取少量数据验证是否为音频格式
-			buffer := make([]byte, 1024)
-			n, err := resp.Body.Read(buffer)
-			if err != nil && err != io.EOF {
-				t.Logf("读取响应数据失败: %v", err)
-			} else if n > 0 {
-				t.Logf("✅ 成功接收 %d 字节数据", n)
-
-				// 检查是否为WAV格式
-				if n >= 12 {
-					if string(buffer[0:4]) == "RIFF" {
-						t.Logf("✅ 检测到WAV格式音频数据")
-					} else {
-						t.Logf("⚠️ 数据格式可能不是WAV: %x", buffer[:min(16, n)])
-					}
-				}
-			}
-		}
-	})
-}
-
-// ShudaoWorkflowRequest 定义蜀道集团工作流请求结构
-type ShudaoWorkflowRequest struct {
-	WorkflowID   string                 `json:"workflow_id"`
-	Inputs       map[string]interface{} `json:"inputs"`
-	ResponseMode string                 `json:"response_mode"`
-	User         string                 `json:"user"`
-}
-
-// ShudaoWorkflowResponse 定义蜀道集团工作流响应结构
-type ShudaoWorkflowResponse struct {
-	TaskID         string `json:"task_id"`
-	WorkflowRunID  string `json:"workflow_run_id"`
-	Data           struct {
-		ID          string `json:"id"`
-		WorkflowID  string `json:"workflow_id"`
-		Status      string `json:"status"`
-		Outputs     struct {
-			JSON []struct {
-				Answer            *string `json:"answer"`
-				FollowUpQuestions *string `json:"follow_up_questions"`
-				Images            []string `json:"images"`
-				Query             string  `json:"query"`
-				RequestID         string  `json:"request_id"`
-				ResponseTime      float64 `json:"response_time"`
-				Results           []struct {
-					Content     string  `json:"content"`
-					RawContent  *string `json:"raw_content"`
-					Score       float64 `json:"score"`
-					Title       string  `json:"title"`
-					URL         string  `json:"url"`
-				} `json:"results"`
-			} `json:"json"`
-		} `json:"outputs"`
-		Error        string  `json:"error"`
-		ElapsedTime  float64 `json:"elapsed_time"`
-		TotalTokens  int     `json:"total_tokens"`
-		TotalSteps   int     `json:"total_steps"`
-		CreatedAt    int64   `json:"created_at"`
-		FinishedAt   int64   `json:"finished_at"`
-	} `json:"data"`
-}
-
-// TestShudaoWorkflowConnectivity 测试蜀道集团工作流接口连通性
-func TestShudaoWorkflowConnectivity(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.GetShudaoWorkflowURL()
-	authToken := config.ShuDaoAuthToken
-	workflowID := config.ShuDaoWorkflowID
-
-	t.Run("蜀道集团工作流接口连通性测试", func(t *testing.T) {
-		client := &http.Client{
-			Timeout: 60 * time.Second, // 工作流可能需要更长时间
-		}
-
-		t.Logf("测试蜀道集团工作流接口连通性: %s", baseURL)
-		t.Logf("使用工作流ID: %s", workflowID)
-		t.Logf("使用认证令牌: %s", authToken)
-
-		// 构建测试请求
-		request := ShudaoWorkflowRequest{
-			WorkflowID: workflowID,
-			Inputs: map[string]interface{}{
-				"keywords": "蜀道集团",
-				"num":      5, // 减少结果数量以加快测试
-			},
-			ResponseMode: "blocking",
-			User:         "test_user_001",
-		}
-
-		jsonData, err := json.Marshal(request)
-		if err != nil {
-			t.Fatalf("序列化测试请求失败: %v", err)
-		}
-
-		// 创建HTTP请求
-		req, err := http.NewRequest("POST", baseURL, bytes.NewBuffer(jsonData))
-		if err != nil {
-			t.Fatalf("创建HTTP请求失败: %v", err)
-		}
-
-		// 设置请求头
-		req.Header.Set("Authorization", "Bearer "+authToken)
-		req.Header.Set("Content-Type", "application/json")
-
-		// 发送请求
-		resp, err := client.Do(req)
-		if err != nil {
-			t.Logf("❌ 蜀道集团工作流接口连接失败: %v", err)
-			t.Logf("可能的原因:")
-			t.Logf("  - 工作流服务未启动")
-			t.Logf("  - 网络不可达")
-			t.Logf("  - 端口不正确")
-			t.Logf("  - 防火墙阻止")
-			t.Logf("  - 认证令牌无效")
-			t.Fatalf("蜀道集团工作流接口连通性测试失败: %v", err)
-		}
-		defer resp.Body.Close()
-
-		t.Logf("✅ 蜀道集团工作流接口HTTP连接成功")
-		t.Logf("响应状态码: %d", resp.StatusCode)
-		t.Logf("Content-Type: %s", resp.Header.Get("Content-Type"))
-
-		// 读取响应体
-		body, err := io.ReadAll(resp.Body)
-		if err != nil {
-			t.Fatalf("读取响应体失败: %v", err)
-		}
-
-		// 验证响应状态码
-		if resp.StatusCode != http.StatusOK {
-			t.Logf("⚠️ 蜀道集团工作流服务返回非200状态码: %d", resp.StatusCode)
-			t.Logf("响应内容: %s", string(body))
-			t.Fatalf("蜀道集团工作流接口返回错误状态码: %d", resp.StatusCode)
-		}
-
-		// 解析响应JSON
-		var response ShudaoWorkflowResponse
-		if err := json.Unmarshal(body, &response); err != nil {
-			t.Logf("⚠️ 响应JSON解析失败: %v", err)
-			t.Logf("原始响应: %s", string(body))
-			t.Fatalf("无法解析蜀道集团工作流响应: %v", err)
-		}
-
-		t.Logf("✅ 蜀道集团工作流服务响应正常")
-		t.Logf("任务ID: %s", response.TaskID)
-		t.Logf("工作流运行ID: %s", response.WorkflowRunID)
-		t.Logf("工作流状态: %s", response.Data.Status)
-		t.Logf("执行耗时: %.3f秒", response.Data.ElapsedTime)
-
-		// 验证工作流执行状态
-		if response.Data.Status != "succeeded" {
-			t.Logf("⚠️ 工作流执行状态异常: %s", response.Data.Status)
-			if response.Data.Error != "" {
-				t.Logf("错误信息: %s", response.Data.Error)
-			}
-			t.Fatalf("工作流执行失败,状态: %s", response.Data.Status)
-		}
-
-		// 验证返回结果
-		if len(response.Data.Outputs.JSON) == 0 {
-			t.Fatalf("工作流未返回任何结果")
-		}
-
-		jsonResult := response.Data.Outputs.JSON[0]
-		t.Logf("查询关键词: %s", jsonResult.Query)
-		t.Logf("检索耗时: %.3f秒", jsonResult.ResponseTime)
-		t.Logf("返回结果数量: %d", len(jsonResult.Results))
-
-		// 验证结果质量
-		if len(jsonResult.Results) == 0 {
-			t.Fatalf("未返回任何检索结果")
-		}
-
-		// 显示前3个结果的基本信息
-		for i, result := range jsonResult.Results {
-			if i >= 3 {
-				break
-			}
-			t.Logf("结果 %d:", i+1)
-			t.Logf("  标题: %s", result.Title)
-			t.Logf("  相关性得分: %.4f", result.Score)
-			t.Logf("  URL: %s", result.URL)
-			t.Logf("  内容摘要: %s...", truncateString(result.Content, 100))
-		}
-
-		// 验证结果质量(相关性得分)
-		highScoreCount := 0
-		for _, result := range jsonResult.Results {
-			if result.Score > 0.7 {
-				highScoreCount++
-			}
-		}
-
-		t.Logf("高质量结果数量 (得分>0.7): %d/%d", highScoreCount, len(jsonResult.Results))
-
-		if highScoreCount == 0 {
-			t.Logf("⚠️ 警告: 没有高质量结果 (得分>0.7)")
-		}
-
-		t.Logf("✅ 蜀道集团工作流接口测试完成")
-	})
-}
-
-// truncateString 截断字符串到指定长度
-func truncateString(s string, maxLen int) string {
-	if len(s) <= maxLen {
-		return s
-	}
-	return s[:maxLen]
-}
-
-// TestHealthAndSearchIntegration 集成测试:先检查健康状态,再进行搜索
-func TestHealthAndSearchIntegration(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.HealthBaseURL
-
-	t.Run("健康检查和搜索集成测试", func(t *testing.T) {
-		client := &http.Client{
-			Timeout: 15 * time.Second,
-		}
-
-		// 第一步:健康检查
-		t.Logf("步骤1: 执行健康检查...")
-		t.Logf("请求URL: %s/health", baseURL)
-
-		healthResp, err := client.Post(fmt.Sprintf("%s/health", baseURL), "application/json", nil)
-		if err != nil {
-			t.Logf("❌ 健康检查请求失败: %v", err)
-			t.Logf("错误类型: %T", err)
-
-			// 检查是否是网络相关错误
-			if netErr, ok := err.(interface{ Timeout() bool }); ok && netErr.Timeout() {
-				t.Logf("⚠️ 这是超时错误")
-			}
-
-			// 尝试更详细的错误信息
-			if urlErr, ok := err.(*url.Error); ok {
-				t.Logf("URL错误详情: %+v", urlErr)
-				if urlErr.Err != nil {
-					t.Logf("底层错误: %v", urlErr.Err)
-				}
-			}
-
-			t.Fatalf("健康检查失败: %v", err)
-		}
-		defer healthResp.Body.Close()
-
-		// 读取响应内容
-		healthBody, err := io.ReadAll(healthResp.Body)
-		if err != nil {
-			t.Logf("读取健康检查响应失败: %v", err)
-		}
-
-		t.Logf("健康检查响应状态码: %d", healthResp.StatusCode)
-		t.Logf("响应头: %+v", healthResp.Header)
-		if len(healthBody) > 0 {
-			t.Logf("响应内容: %s", string(healthBody))
-		}
-
-		if healthResp.StatusCode != http.StatusOK {
-			t.Fatalf("健康检查失败,状态码: %d", healthResp.StatusCode)
-		}
-		t.Logf("✅ 健康检查通过")
-
-		// 第二步:执行搜索
-		t.Logf("步骤2: 执行搜索测试...")
-		searchRequest := SearchRequest{
-			Query:    "技术",
-			NResults: 3,
-		}
-
-		jsonData, err := json.Marshal(searchRequest)
-		if err != nil {
-			t.Fatalf("序列化搜索请求失败: %v", err)
-		}
-
-		searchResp, err := client.Post(
-			fmt.Sprintf("%s/search", baseURL),
-			"application/json",
-			bytes.NewBuffer(jsonData),
-		)
-		if err != nil {
-			t.Fatalf("搜索请求失败: %v", err)
-		}
-		defer searchResp.Body.Close()
-
-		body, err := io.ReadAll(searchResp.Body)
-		if err != nil {
-			t.Fatalf("读取搜索响应失败: %v", err)
-		}
-
-		if searchResp.StatusCode != http.StatusOK {
-			t.Errorf("搜索失败,状态码: %d", searchResp.StatusCode)
-			if len(body) < 200 {
-				t.Logf("错误响应: %s", string(body))
-			}
-		} else {
-			t.Logf("✅ 搜索功能正常")
-			t.Logf("响应长度: %d 字节", len(body))
-		}
-
-		t.Logf("✅ 集成测试完成")
-	})
-}
-
-// TestNetworkConnectivity 测试网络连通性
-func TestNetworkConnectivity(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.HealthBaseURL
-
-	t.Run("网络连通性测试", func(t *testing.T) {
-		// 解析URL
-		parsedURL, err := url.Parse(baseURL)
-		if err != nil {
-			t.Fatalf("解析URL失败: %v", err)
-		}
-
-		t.Logf("测试目标: %s", baseURL)
-		t.Logf("协议: %s", parsedURL.Scheme)
-		t.Logf("主机: %s", parsedURL.Host)
-		t.Logf("端口: %s", parsedURL.Port())
-
-		// 测试TCP连接
-		conn, err := net.DialTimeout("tcp", parsedURL.Host, 5*time.Second)
-		if err != nil {
-			t.Logf("❌ TCP连接失败: %v", err)
-			t.Logf("可能的原因:")
-			t.Logf("  - 服务器未启动")
-			t.Logf("  - 防火墙阻止")
-			t.Logf("  - 网络不可达")
-			t.Logf("  - 端口不正确")
-		} else {
-			t.Logf("✅ TCP连接成功")
-			conn.Close()
-		}
-
-		// 测试HTTP连接
-		client := &http.Client{
-			Timeout: 10 * time.Second,
-		}
-
-		// 尝试简单的HEAD请求
-		resp, err := client.Head(baseURL)
-		if err != nil {
-			t.Logf("❌ HTTP HEAD请求失败: %v", err)
-		} else {
-			t.Logf("✅ HTTP连接成功,状态码: %d", resp.StatusCode)
-			resp.Body.Close()
-		}
-
-		// 尝试POST请求到根路径
-		resp, err = client.Post(baseURL, "application/json", nil)
-		if err != nil {
-			t.Logf("❌ HTTP POST请求失败: %v", err)
-		} else {
-			t.Logf("✅ HTTP POST连接成功,状态码: %d", resp.StatusCode)
-			resp.Body.Close()
-		}
-	})
-}
-
-// ShudaoWorkflowRequest 定义蜀道集团工作流请求结构
-type ShudaoWorkflowRequest struct {
-	WorkflowID   string                 `json:"workflow_id"`
-	Inputs       map[string]interface{} `json:"inputs"`
-	ResponseMode string                 `json:"response_mode"`
-	User         string                 `json:"user"`
-}
-
-// ShudaoWorkflowResponse 定义蜀道集团工作流响应结构
-type ShudaoWorkflowResponse struct {
-	TaskID         string `json:"task_id"`
-	WorkflowRunID  string `json:"workflow_run_id"`
-	Data           struct {
-		ID          string `json:"id"`
-		WorkflowID  string `json:"workflow_id"`
-		Status      string `json:"status"`
-		Outputs     struct {
-			JSON []struct {
-				Answer            *string `json:"answer"`
-				FollowUpQuestions *string `json:"follow_up_questions"`
-				Images            []string `json:"images"`
-				Query             string  `json:"query"`
-				RequestID         string  `json:"request_id"`
-				ResponseTime      float64 `json:"response_time"`
-				Results           []struct {
-					Content     string  `json:"content"`
-					RawContent  *string `json:"raw_content"`
-					Score       float64 `json:"score"`
-					Title       string  `json:"title"`
-					URL         string  `json:"url"`
-				} `json:"results"`
-			} `json:"json"`
-		} `json:"outputs"`
-		Error        string  `json:"error"`
-		ElapsedTime  float64 `json:"elapsed_time"`
-		TotalTokens  int     `json:"total_tokens"`
-		TotalSteps   int     `json:"total_steps"`
-		CreatedAt    int64   `json:"created_at"`
-		FinishedAt   int64   `json:"finished_at"`
-	} `json:"data"`
-}
-
-// TestShudaoWorkflowConnectivity 测试蜀道集团工作流接口连通性
-func TestShudaoWorkflowConnectivity(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.GetShudaoWorkflowURL()
-	authToken := config.ShuDaoAuthToken
-	workflowID := config.ShuDaoWorkflowID
-
-	t.Run("蜀道集团工作流接口连通性测试", func(t *testing.T) {
-		client := &http.Client{
-			Timeout: 60 * time.Second, // 工作流可能需要更长时间
-		}
-
-		t.Logf("测试蜀道集团工作流接口连通性: %s", baseURL)
-		t.Logf("使用工作流ID: %s", workflowID)
-		t.Logf("使用认证令牌: %s", authToken)
-
-		// 构建测试请求
-		request := ShudaoWorkflowRequest{
-			WorkflowID: workflowID,
-			Inputs: map[string]interface{}{
-				"keywords": "蜀道集团",
-				"num":      5, // 减少结果数量以加快测试
-			},
-			ResponseMode: "blocking",
-			User:         "test_user_001",
-		}
-
-		jsonData, err := json.Marshal(request)
-		if err != nil {
-			t.Fatalf("序列化测试请求失败: %v", err)
-		}
-
-		// 创建HTTP请求
-		req, err := http.NewRequest("POST", baseURL, bytes.NewBuffer(jsonData))
-		if err != nil {
-			t.Fatalf("创建HTTP请求失败: %v", err)
-		}
-
-		// 设置请求头
-		req.Header.Set("Authorization", "Bearer "+authToken)
-		req.Header.Set("Content-Type", "application/json")
-
-		// 发送请求
-		resp, err := client.Do(req)
-		if err != nil {
-			t.Logf("❌ 蜀道集团工作流接口连接失败: %v", err)
-			t.Logf("可能的原因:")
-			t.Logf("  - 工作流服务未启动")
-			t.Logf("  - 网络不可达")
-			t.Logf("  - 端口不正确")
-			t.Logf("  - 防火墙阻止")
-			t.Logf("  - 认证令牌无效")
-			t.Fatalf("蜀道集团工作流接口连通性测试失败: %v", err)
-		}
-		defer resp.Body.Close()
-
-		t.Logf("✅ 蜀道集团工作流接口HTTP连接成功")
-		t.Logf("响应状态码: %d", resp.StatusCode)
-		t.Logf("Content-Type: %s", resp.Header.Get("Content-Type"))
-
-		// 读取响应体
-		body, err := io.ReadAll(resp.Body)
-		if err != nil {
-			t.Fatalf("读取响应体失败: %v", err)
-		}
-
-		// 验证响应状态码
-		if resp.StatusCode != http.StatusOK {
-			t.Logf("⚠️ 蜀道集团工作流服务返回非200状态码: %d", resp.StatusCode)
-			t.Logf("响应内容: %s", string(body))
-			t.Fatalf("蜀道集团工作流接口返回错误状态码: %d", resp.StatusCode)
-		}
-
-		// 解析响应JSON
-		var response ShudaoWorkflowResponse
-		if err := json.Unmarshal(body, &response); err != nil {
-			t.Logf("⚠️ 响应JSON解析失败: %v", err)
-			t.Logf("原始响应: %s", string(body))
-			t.Fatalf("无法解析蜀道集团工作流响应: %v", err)
-		}
-
-		t.Logf("✅ 蜀道集团工作流服务响应正常")
-		t.Logf("任务ID: %s", response.TaskID)
-		t.Logf("工作流运行ID: %s", response.WorkflowRunID)
-		t.Logf("工作流状态: %s", response.Data.Status)
-		t.Logf("执行耗时: %.3f秒", response.Data.ElapsedTime)
-
-		// 验证工作流执行状态
-		if response.Data.Status != "succeeded" {
-			t.Logf("⚠️ 工作流执行状态异常: %s", response.Data.Status)
-			if response.Data.Error != "" {
-				t.Logf("错误信息: %s", response.Data.Error)
-			}
-			t.Fatalf("工作流执行失败,状态: %s", response.Data.Status)
-		}
-
-		// 验证返回结果
-		if len(response.Data.Outputs.JSON) == 0 {
-			t.Fatalf("工作流未返回任何结果")
-		}
-
-		jsonResult := response.Data.Outputs.JSON[0]
-		t.Logf("查询关键词: %s", jsonResult.Query)
-		t.Logf("检索耗时: %.3f秒", jsonResult.ResponseTime)
-		t.Logf("返回结果数量: %d", len(jsonResult.Results))
-
-		// 验证结果质量
-		if len(jsonResult.Results) == 0 {
-			t.Fatalf("未返回任何检索结果")
-		}
-
-		// 显示前3个结果的基本信息
-		for i, result := range jsonResult.Results {
-			if i >= 3 {
-				break
-			}
-			t.Logf("结果 %d:", i+1)
-			t.Logf("  标题: %s", result.Title)
-			t.Logf("  相关性得分: %.4f", result.Score)
-			t.Logf("  URL: %s", result.URL)
-			t.Logf("  内容摘要: %s...", truncateString(result.Content, 100))
-		}
-
-		// 验证结果质量(相关性得分)
-		highScoreCount := 0
-		for _, result := range jsonResult.Results {
-			if result.Score > 0.7 {
-				highScoreCount++
-			}
-		}
-
-		t.Logf("高质量结果数量 (得分>0.7): %d/%d", highScoreCount, len(jsonResult.Results))
-
-		if highScoreCount == 0 {
-			t.Logf("⚠️ 警告: 没有高质量结果 (得分>0.7)")
-		}
-
-		t.Logf("✅ 蜀道集团工作流接口测试完成")
-	})
-}
-
-// truncateString 截断字符串到指定长度
-func truncateString(s string, maxLen int) string {
-	if len(s) <= maxLen {
-		return s
-	}
-	return s[:maxLen]
-}

+ 0 - 62
shudao-go-backend/tests/config.go

@@ -1,62 +0,0 @@
-package tests
-
-import (
-	"os"
-)
-
-// Config 测试配置结构
-type Config struct {
-	// API服务配置
-	Qwen3BaseURL     string
-	Qwen3Model       string
-	YOLOBaseURL      string
-	HealthBaseURL    string
-	SearchBaseURL    string
-	TTSBaseURL       string
-	ShudaoBaseURL    string
-	ShudaoAuthToken  string
-	ShudaoWorkflowID string
-}
-
-// LoadConfig 加载测试配置
-func LoadConfig() *Config {
-	return &Config{
-		Qwen3BaseURL:     getEnvOrDefault("QWEN3_BASE_URL", "http://172.16.35.50:8000"),
-		Qwen3Model:       getEnvOrDefault("QWEN3_MODEL", "Qwen3-30B-A3B-Instruct-2507"),
-		YOLOBaseURL:      getEnvOrDefault("YOLO_BASE_URL", "http://localhost:8081"),
-		HealthBaseURL:    getEnvOrDefault("HEALTH_BASE_URL", "http://localhost:8080"),
-		SearchBaseURL:    getEnvOrDefault("SEARCH_BASE_URL", "http://localhost:8080"),
-		TTSBaseURL:       getEnvOrDefault("TTS_BASE_URL", "http://172.16.35.50:8000"),
-		ShudaoBaseURL:    getEnvOrDefault("SHUDAO_BASE_URL", "http://172.16.35.50:8007"),
-		ShudaoAuthToken:  getEnvOrDefault("SHUDAO_AUTH_TOKEN", "app-55CyO4lmDv1VeXK4QmFpt4ng"),
-		ShudaoWorkflowID: getEnvOrDefault("SHUDAO_WORKFLOW_ID", "4wfh1PPDderMtCeb"),
-	}
-}
-
-// GetQwen3ChatURL 获取Qwen3聊天接口URL
-func (c *Config) GetQwen3ChatURL() string {
-	return c.Qwen3BaseURL + "/v1/chat/completions"
-}
-
-// GetYOLOPredictURL 获取YOLO预测接口URL
-func (c *Config) GetYOLOPredictURL() string {
-	return c.YOLOBaseURL + "/predict"
-}
-
-// GetTTSStreamURL 获取TTS流式接口URL
-func (c *Config) GetTTSStreamURL() string {
-	return c.TTSBaseURL + "/tts/voice"
-}
-
-// GetShudaoWorkflowURL 获取蜀道集团工作流接口URL
-func (c *Config) GetShudaoWorkflowURL() string {
-	return c.ShudaoBaseURL + "/v1/workflows/run"
-}
-
-// getEnvOrDefault 获取环境变量或返回默认值
-func getEnvOrDefault(key, defaultValue string) string {
-	if value := os.Getenv(key); value != "" {
-		return value
-	}
-	return defaultValue
-}

+ 0 - 57
shudao-go-backend/tests/context_test.go

@@ -1,57 +0,0 @@
-package tests
-
-import (
-	"fmt"
-	"shudao-chat-go/utils"
-	"testing"
-
-	"github.com/beego/beego/v2/server/web/context"
-)
-
-// TestContextDataStorage 测试context数据存储和获取
-func TestContextDataStorage(t *testing.T) {
-	// 创建一个模拟的 BeegoInput
-	ctx := &context.Context{
-		Input: context.NewInput(),
-	}
-
-	// 创建测试用户信息
-	testUserInfo := &utils.TokenUserInfo{
-		AccountID:     "test123",
-		ID:            12345,
-		Name:          "测试用户",
-		UserCode:      "T001",
-		ContactNumber: "13800138000",
-		TokenType:     "access",
-		Exp:           1234567890,
-		Iat:           1234567800,
-	}
-
-	fmt.Println("\n========== Context存储测试 ==========")
-	fmt.Printf("原始用户信息: %+v\n", testUserInfo)
-	fmt.Printf("原始指针地址: %p\n\n", testUserInfo)
-
-	// 存储到context
-	ctx.Input.SetData("userInfo", testUserInfo)
-	fmt.Println("✅ 已存储到context")
-
-	// 立即从context获取
-	storedData := ctx.Input.GetData("userInfo")
-	fmt.Printf("\n从context获取的数据:\n")
-	fmt.Printf("   类型: %T\n", storedData)
-	fmt.Printf("   值: %+v\n\n", storedData)
-
-	// 使用 GetUserInfoFromContext 函数解析
-	userInfo, err := utils.GetUserInfoFromContext(storedData)
-	if err != nil {
-		t.Errorf("❌ GetUserInfoFromContext 失败: %v", err)
-		return
-	}
-
-	fmt.Println("\n✅ 测试通过!成功解析用户信息:")
-	fmt.Printf("   - AccountID: %s\n", userInfo.AccountID)
-	fmt.Printf("   - ID: %d\n", userInfo.ID)
-	fmt.Printf("   - Name: %s\n", userInfo.Name)
-	fmt.Println("=====================================")
-}
-

+ 0 - 204
shudao-go-backend/tests/points_property_test.go

@@ -1,204 +0,0 @@
-package tests
-
-import (
-	"testing"
-
-	"github.com/leanovate/gopter"
-	"github.com/leanovate/gopter/gen"
-	"github.com/leanovate/gopter/prop"
-)
-
-// **Feature: file-management-optimization, Property 5: New User Points Initialization**
-// **Validates: Requirements 3.1**
-// *For any* newly created user, the initial points balance SHALL be exactly 20.
-func TestProperty5_NewUserPointsInitialization(t *testing.T) {
-	parameters := gopter.DefaultTestParameters()
-	parameters.MinSuccessfulTests = 100
-
-	properties := gopter.NewProperties(parameters)
-
-	properties.Property("新用户积分初始化为20", prop.ForAll(
-		func(userID string, name string) bool {
-			// 模拟新用户创建时的默认积分值
-			const defaultPoints = 20
-			newUserPoints := defaultPoints
-			return newUserPoints == 20
-		},
-		gen.AlphaString().WithLabel("userID"),
-		gen.AlphaString().WithLabel("name"),
-	))
-
-	properties.TestingRun(t)
-}
-
-// **Feature: file-management-optimization, Property 8: Points Persistence Round Trip**
-// **Validates: Requirements 3.5**
-// *For any* points balance update, writing the new balance to the database and then reading it back SHALL return the same value.
-func TestProperty8_PointsPersistenceRoundTrip(t *testing.T) {
-	parameters := gopter.DefaultTestParameters()
-	parameters.MinSuccessfulTests = 100
-
-	properties := gopter.NewProperties(parameters)
-
-	properties.Property("积分持久化往返一致性", prop.ForAll(
-		func(points int) bool {
-			// 模拟写入和读取操作的往返一致性
-			// 在实际场景中,这会涉及数据库操作
-			// 这里验证积分值在合理范围内的往返一致性
-			if points < 0 {
-				return true // 负数积分不在测试范围内
-			}
-
-			// 模拟写入
-			writtenPoints := points
-			// 模拟读取
-			readPoints := writtenPoints
-
-			return readPoints == points
-		},
-		gen.IntRange(0, 10000).WithLabel("points"),
-	))
-
-	properties.TestingRun(t)
-}
-
-// **Feature: file-management-optimization, Property 6: Points Validation for Download**
-// **Validates: Requirements 3.2**
-// *For any* download attempt, the system SHALL allow the download if and only if the user's points balance is greater than or equal to 10.
-func TestProperty6_PointsValidationForDownload(t *testing.T) {
-	parameters := gopter.DefaultTestParameters()
-	parameters.MinSuccessfulTests = 100
-
-	properties := gopter.NewProperties(parameters)
-
-	properties.Property("积分验证下载权限", prop.ForAll(
-		func(currentPoints int) bool {
-			const requiredPoints = 10
-			canDownload := currentPoints >= requiredPoints
-			// 验证:当且仅当积分>=10时允许下载
-			if currentPoints >= 10 {
-				return canDownload == true
-			}
-			return canDownload == false
-		},
-		gen.IntRange(0, 100).WithLabel("currentPoints"),
-	))
-
-	properties.TestingRun(t)
-}
-
-// **Feature: file-management-optimization, Property 7: Points Deduction Correctness**
-// **Validates: Requirements 3.3**
-// *For any* successful file download, the user's points balance after download SHALL equal the balance before download minus 10.
-func TestProperty7_PointsDeductionCorrectness(t *testing.T) {
-	parameters := gopter.DefaultTestParameters()
-	parameters.MinSuccessfulTests = 100
-
-	properties := gopter.NewProperties(parameters)
-
-	properties.Property("积分扣减正确性", prop.ForAll(
-		func(initialPoints int) bool {
-			const requiredPoints = 10
-			// 只测试有足够积分的情况
-			if initialPoints < requiredPoints {
-				return true // 跳过积分不足的情况
-			}
-
-			// 模拟下载后的积分扣减
-			balanceAfter := initialPoints - requiredPoints
-
-			// 验证:下载后余额 = 下载前余额 - 10
-			return balanceAfter == initialPoints-10
-		},
-		gen.IntRange(10, 1000).WithLabel("initialPoints"),
-	))
-
-	properties.TestingRun(t)
-}
-
-
-// **Feature: file-management-optimization, Property 11: Consumption Record Creation**
-// **Validates: Requirements 8.1, 8.3**
-// *For any* successful file download, a consumption record SHALL be created containing the correct user ID, file name, points consumed (10), and timestamp.
-func TestProperty11_ConsumptionRecordCreation(t *testing.T) {
-	parameters := gopter.DefaultTestParameters()
-	parameters.MinSuccessfulTests = 100
-
-	properties := gopter.NewProperties(parameters)
-
-	properties.Property("消费记录创建完整性", prop.ForAll(
-		func(userID string, fileName string, initialPoints int) bool {
-			const requiredPoints = 10
-			// 只测试有足够积分的情况
-			if initialPoints < requiredPoints || userID == "" || fileName == "" {
-				return true
-			}
-
-			// 模拟创建消费记录
-			balanceAfter := initialPoints - requiredPoints
-			record := struct {
-				UserID         string
-				FileName       string
-				PointsConsumed int
-				BalanceAfter   int
-			}{
-				UserID:         userID,
-				FileName:       fileName,
-				PointsConsumed: requiredPoints,
-				BalanceAfter:   balanceAfter,
-			}
-
-			// 验证记录包含所有必要字段
-			return record.UserID == userID &&
-				record.FileName == fileName &&
-				record.PointsConsumed == 10 &&
-				record.BalanceAfter == initialPoints-10
-		},
-		gen.AlphaString().SuchThat(func(s string) bool { return len(s) > 0 }).WithLabel("userID"),
-		gen.AlphaString().SuchThat(func(s string) bool { return len(s) > 0 }).WithLabel("fileName"),
-		gen.IntRange(10, 1000).WithLabel("initialPoints"),
-	))
-
-	properties.TestingRun(t)
-}
-
-// **Feature: file-management-optimization, Property 12: Consumption History Sort Order**
-// **Validates: Requirements 8.2**
-// *For any* consumption history query result, the records SHALL be sorted by timestamp in descending order (newest first).
-func TestProperty12_ConsumptionHistorySortOrder(t *testing.T) {
-	parameters := gopter.DefaultTestParameters()
-	parameters.MinSuccessfulTests = 100
-
-	properties := gopter.NewProperties(parameters)
-
-	properties.Property("消费记录按时间倒序排列", prop.ForAll(
-		func(timestamps []int64) bool {
-			if len(timestamps) < 2 {
-				return true
-			}
-
-			// 模拟按时间倒序排序
-			sorted := make([]int64, len(timestamps))
-			copy(sorted, timestamps)
-			// 倒序排序
-			for i := 0; i < len(sorted)-1; i++ {
-				for j := i + 1; j < len(sorted); j++ {
-					if sorted[i] < sorted[j] {
-						sorted[i], sorted[j] = sorted[j], sorted[i]
-					}
-				}
-			}
-
-			// 验证排序后是倒序的
-			for i := 0; i < len(sorted)-1; i++ {
-				if sorted[i] < sorted[i+1] {
-					return false
-				}
-			}
-			return true
-		},
-		gen.SliceOf(gen.Int64Range(1000000000, 2000000000)).WithLabel("timestamps"),
-	))
-
-	properties.TestingRun(t)
-}

+ 0 - 770
shudao-go-backend/tests/test_pages/simple_stream_test.html

@@ -1,770 +0,0 @@
-<!DOCTYPE html>
-<html lang="zh-CN">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>流式Markdown渲染测试</title>
-    <!-- Vditor CSS -->
-    <link rel="stylesheet" href="https://unpkg.com/vditor/dist/index.css" />
-    <style>
-        * {
-            margin: 0;
-            padding: 0;
-            box-sizing: border-box;
-        }
-        
-        body {
-            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
-            background: #f5f5f5;
-            padding: 20px;
-        }
-        
-        .container {
-            max-width: 1000px;
-            margin: 0 auto;
-            background: white;
-            border-radius: 8px;
-            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
-            overflow: hidden;
-        }
-        
-        .header {
-            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-            color: white;
-            padding: 30px;
-            text-align: center;
-        }
-        
-        .header h1 {
-            font-size: 28px;
-            margin-bottom: 10px;
-        }
-        
-        .header p {
-            opacity: 0.9;
-            font-size: 16px;
-        }
-        
-        .content {
-            padding: 30px;
-        }
-        
-        .test-section {
-            margin-bottom: 30px;
-            padding: 25px;
-            background: #f8f9fa;
-            border-radius: 8px;
-            border-left: 4px solid #667eea;
-        }
-        
-        .form-group {
-            margin-bottom: 20px;
-        }
-        
-        label {
-            display: block;
-            margin-bottom: 8px;
-            font-weight: 600;
-            color: #2c3e50;
-            font-size: 14px;
-        }
-        
-        input, textarea {
-            width: 100%;
-            padding: 12px;
-            border: 2px solid #e1e8ed;
-            border-radius: 6px;
-            font-size: 14px;
-            transition: border-color 0.3s;
-        }
-        
-        input:focus, textarea:focus {
-            outline: none;
-            border-color: #667eea;
-        }
-        
-        textarea {
-            height: 100px;
-            resize: vertical;
-            font-family: inherit;
-        }
-        
-        .button-group {
-            display: flex;
-            gap: 15px;
-            margin-top: 20px;
-        }
-        
-        button {
-            padding: 12px 24px;
-            border: none;
-            border-radius: 6px;
-            cursor: pointer;
-            font-size: 14px;
-            font-weight: 600;
-            transition: all 0.3s;
-            min-width: 120px;
-        }
-        
-        .btn-primary {
-            background: #667eea;
-            color: white;
-        }
-        
-        .btn-primary:hover {
-            background: #5a6fd8;
-            transform: translateY(-1px);
-        }
-        
-        .btn-secondary {
-            background: #6c757d;
-            color: white;
-        }
-        
-        .btn-secondary:hover {
-            background: #5a6268;
-        }
-        
-        .btn-danger {
-            background: #dc3545;
-            color: white;
-        }
-        
-        .btn-danger:hover {
-            background: #c82333;
-        }
-        
-        .status {
-            padding: 15px;
-            margin: 15px 0;
-            border-radius: 6px;
-            font-weight: 600;
-            display: none;
-        }
-        
-        .status.show {
-            display: block;
-        }
-        
-        .status.info {
-            background: #d1ecf1;
-            color: #0c5460;
-            border: 1px solid #bee5eb;
-        }
-        
-        .status.success {
-            background: #d4edda;
-            color: #155724;
-            border: 1px solid #c3e6cb;
-        }
-        
-        .status.error {
-            background: #f8d7da;
-            color: #721c24;
-            border: 1px solid #f5c6cb;
-        }
-        
-        .output-section {
-            margin-top: 30px;
-        }
-        
-        .output-header {
-            display: flex;
-            justify-content: space-between;
-            align-items: center;
-            margin-bottom: 20px;
-            padding-bottom: 15px;
-            border-bottom: 2px solid #e1e8ed;
-        }
-        
-        .output-title {
-            font-size: 18px;
-            font-weight: 600;
-            color: #2c3e50;
-        }
-        
-        .output-tabs {
-            display: flex;
-            gap: 5px;
-        }
-        
-        .tab {
-            padding: 8px 16px;
-            cursor: pointer;
-            border-radius: 4px;
-            font-size: 14px;
-            font-weight: 500;
-            transition: all 0.3s;
-            background: #f8f9fa;
-            color: #6c757d;
-        }
-        
-        .tab.active {
-            background: #667eea;
-            color: white;
-        }
-        
-        .tab-content {
-            display: none;
-        }
-        
-        .tab-content.active {
-            display: block;
-        }
-        
-        .raw-output {
-            background: #f8f9fa;
-            border: 2px solid #e1e8ed;
-            border-radius: 6px;
-            padding: 20px;
-            font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
-            font-size: 13px;
-            line-height: 1.5;
-            white-space: pre-wrap;
-            max-height: 500px;
-            overflow-y: auto;
-            color: #2c3e50;
-        }
-        
-        .markdown-container {
-            border: 2px solid #e1e8ed;
-            border-radius: 6px;
-            min-height: 400px;
-            background: white;
-        }
-        
-        /* 只读编辑器样式 */
-        .markdown-container.readonly-editor {
-            user-select: none; /* 禁用文本选择 */
-        }
-        
-        .markdown-container .vditor-toolbar {
-            display: none !important; /* 隐藏工具栏 */
-        }
-        
-        .markdown-container .vditor-content {
-            cursor: default !important; /* 默认光标 */
-        }
-        
-        .markdown-container .vditor-content:focus {
-            outline: none !important; /* 移除焦点样式 */
-        }
-        
-        /* 确保滚动条正常工作 */
-        .markdown-container .vditor-content {
-            overflow-y: auto !important; /* 确保垂直滚动 */
-            pointer-events: auto !important; /* 允许滚动交互 */
-        }
-        
-        .markdown-container .vditor-content::-webkit-scrollbar {
-            width: 8px;
-        }
-        
-        .markdown-container .vditor-content::-webkit-scrollbar-track {
-            background: #f1f1f1;
-            border-radius: 4px;
-        }
-        
-        .markdown-container .vditor-content::-webkit-scrollbar-thumb {
-            background: #c1c1c1;
-            border-radius: 4px;
-        }
-        
-        .markdown-container .vditor-content::-webkit-scrollbar-thumb:hover {
-            background: #a8a8a8;
-        }
-        
-        .loading {
-            display: none;
-            text-align: center;
-            padding: 40px;
-            color: #6c757d;
-        }
-        
-        .loading.show {
-            display: block;
-        }
-        
-        .spinner {
-            border: 4px solid #f3f3f3;
-            border-top: 4px solid #667eea;
-            border-radius: 50%;
-            width: 40px;
-            height: 40px;
-            animation: spin 1s linear infinite;
-            margin: 0 auto 15px;
-        }
-        
-        @keyframes spin {
-            0% { transform: rotate(0deg); }
-            100% { transform: rotate(360deg); }
-        }
-        
-        .stats {
-            display: flex;
-            gap: 20px;
-            margin-top: 15px;
-            font-size: 12px;
-            color: #6c757d;
-        }
-        
-        .stat-item {
-            display: flex;
-            align-items: center;
-            gap: 5px;
-        }
-        
-        .stat-value {
-            font-weight: 600;
-            color: #2c3e50;
-        }
-    </style>
-</head>
-<body>
-    <div class="container">
-        <div class="header">
-            <h1>🚀 流式Markdown渲染测试</h1>
-            <p>测试流式接口的实时Markdown渲染效果 - 支持三种Vditor只读预览模式</p>
-        </div>
-        
-        <div class="content">
-            <div class="test-section">
-                <div class="form-group">
-                    <label for="apiUrl">API接口地址</label>
-                    <input type="text" id="apiUrl" value="http://localhost:22000/apiv1/stream/chat" placeholder="输入流式接口地址">
-                </div>
-                
-                <div class="form-group">
-                    <label for="message">测试消息</label>
-                    <textarea id="message" placeholder="输入要测试的消息内容">你好,请介绍一下施工现场的安全防护要求,包括临边防护、脚手架管理等具体规范</textarea>
-                </div>
-                
-                <div class="form-group">
-                    <label for="model">模型名称 (可选)</label>
-                    <input type="text" id="model" placeholder="模型名称,留空使用默认">
-                </div>
-                
-                <div class="button-group">
-                    <button class="btn-primary" onclick="startTest()">▶️ 开始测试</button>
-                    <button class="btn-secondary" onclick="clearAll()">🗑️ 清空输出</button>
-                    <button class="btn-danger" onclick="stopTest()">⏹️ 停止测试</button>
-                </div>
-            </div>
-            
-            <div id="status" class="status"></div>
-            
-            <div class="loading" id="loading">
-                <div class="spinner"></div>
-                <div>正在接收流式数据...</div>
-            </div>
-            
-            <div class="output-section">
-                <div class="output-header">
-                    <div class="output-title">📊 测试结果</div>
-                    <div class="output-tabs">
-                        <div class="tab active" onclick="switchTab('raw')">原始数据</div>
-                        <div class="tab" onclick="switchTab('wysiwyg')">WYSIWYG预览</div>
-                        <div class="tab" onclick="switchTab('ir')">即时渲染预览(IR)</div>
-                        <div class="tab" onclick="switchTab('sv')">分屏预览(SV)</div>
-                    </div>
-                </div>
-                
-                <div style="background: #f8f9fa; padding: 15px; margin-bottom: 20px; border-radius: 6px; font-size: 14px; color: #6c757d;">
-                    <strong>📝 渲染模式说明(只读预览):</strong><br>
-                    • <strong>WYSIWYG预览</strong>:所见即所得预览,显示最终渲染效果(不可编辑)<br>
-                    • <strong>即时渲染预览(IR)</strong>:实时渲染Markdown为HTML预览(不可编辑)<br>
-                    • <strong>分屏预览(SV)</strong>:左侧显示Markdown源码,右侧显示HTML预览(不可编辑)
-                </div>
-                
-                <div id="rawTab" class="tab-content active">
-                    <div class="raw-output" id="rawOutput">等待测试数据...</div>
-                </div>
-                
-                <div id="wysiwygTab" class="tab-content">
-                    <div class="markdown-container readonly-editor" id="wysiwygOutput"></div>
-                </div>
-                
-                <div id="irTab" class="tab-content">
-                    <div class="markdown-container readonly-editor" id="irOutput"></div>
-                </div>
-                
-                <div id="svTab" class="tab-content">
-                    <div class="markdown-container readonly-editor" id="svOutput"></div>
-                </div>
-                
-                <div class="stats" id="stats" style="display: none;">
-                    <div class="stat-item">
-                        <span>📝 字符数:</span>
-                        <span class="stat-value" id="charCount">0</span>
-                    </div>
-                    <div class="stat-item">
-                        <span>⏱️ 耗时:</span>
-                        <span class="stat-value" id="duration">0s</span>
-                    </div>
-                    <div class="stat-item">
-                        <span>📦 数据块:</span>
-                        <span class="stat-value" id="chunkCount">0</span>
-                    </div>
-                </div>
-            </div>
-        </div>
-    </div>
-
-    <!-- Vditor JavaScript -->
-    <script src="https://unpkg.com/vditor/dist/index.min.js"></script>
-    
-    <script>
-        let vditorWysiwyg = null;
-        let vditorIR = null;
-        let vditorSV = null;
-        let isStreaming = false;
-        let startTime = null;
-        let charCount = 0;
-        let chunkCount = 0;
-        let currentContent = '';
-        
-        // 初始化所有Vditor编辑器
-        function initVditors() {
-            // 销毁现有的编辑器
-            if (vditorWysiwyg) vditorWysiwyg.destroy();
-            if (vditorIR) vditorIR.destroy();
-            if (vditorSV) vditorSV.destroy();
-            
-            // 初始化WYSIWYG模式 - 只读预览
-            vditorWysiwyg = new Vditor('wysiwygOutput', {
-                height: 400,
-                mode: 'wysiwyg',
-                cache: {
-                    id: 'vditor-wysiwyg-cache'
-                },
-                toolbar: [], // 去掉工具栏
-                disabled: true, // 设置为只读
-                after: () => {
-                    console.log('✅ WYSIWYG预览初始化完成');
-                }
-            });
-            
-            // 初始化即时渲染(IR)模式 - 只读预览
-            vditorIR = new Vditor('irOutput', {
-                height: 400,
-                mode: 'ir',
-                cache: {
-                    id: 'vditor-ir-cache'
-                },
-                toolbar: [], // 去掉工具栏
-                disabled: true, // 设置为只读
-                after: () => {
-                    console.log('✅ 即时渲染预览(IR)初始化完成');
-                }
-            });
-            
-            // 初始化分屏预览(SV)模式 - 只读预览
-            vditorSV = new Vditor('svOutput', {
-                height: 400,
-                mode: 'sv',
-                cache: {
-                    id: 'vditor-sv-cache'
-                },
-                toolbar: [], // 去掉工具栏
-                disabled: true, // 设置为只读
-                after: () => {
-                    console.log('✅ 分屏预览(SV)初始化完成');
-                }
-            });
-        }
-        
-        // 切换标签页
-        function switchTab(tabName) {
-            // 隐藏所有标签内容
-            document.querySelectorAll('.tab-content').forEach(content => {
-                content.classList.remove('active');
-            });
-            
-            // 移除所有标签的active类
-            document.querySelectorAll('.tab').forEach(tab => {
-                tab.classList.remove('active');
-            });
-            
-            // 显示选中的标签内容
-            document.getElementById(tabName + 'Tab').classList.add('active');
-            
-            // 添加active类到选中的标签
-            event.target.classList.add('active');
-        }
-        
-        // 显示状态信息
-        function showStatus(message, type = 'info') {
-            const statusEl = document.getElementById('status');
-            statusEl.textContent = message;
-            statusEl.className = `status ${type}`;
-            statusEl.classList.add('show');
-            
-            setTimeout(() => {
-                statusEl.classList.remove('show');
-            }, 5000);
-        }
-        
-        // 更新统计信息
-        function updateStats() {
-            const duration = startTime ? Math.round((Date.now() - startTime) / 1000) : 0;
-            document.getElementById('charCount').textContent = charCount;
-            document.getElementById('duration').textContent = duration + 's';
-            document.getElementById('chunkCount').textContent = chunkCount;
-            document.getElementById('stats').style.display = 'flex';
-        }
-        
-        // 清空所有输出
-        function clearAll() {
-            document.getElementById('rawOutput').textContent = '等待测试数据...';
-            currentContent = '';
-            
-            if (vditorWysiwyg) vditorWysiwyg.setValue('');
-            if (vditorIR) vditorIR.setValue('');
-            if (vditorSV) vditorSV.setValue('');
-            
-            charCount = 0;
-            chunkCount = 0;
-            startTime = null;
-            updateStats();
-            showStatus('✅ 输出已清空', 'success');
-        }
-        
-        // 防抖更新函数
-        let debouncedUpdate = null;
-        
-        // 初始化防抖函数
-        function initDebouncedUpdate() {
-            debouncedUpdate = debounce((content) => {
-                if (vditorWysiwyg) {
-                    vditorWysiwyg.setValue(content);
-                }
-                if (vditorIR) {
-                    vditorIR.setValue(content);
-                }
-                if (vditorSV) {
-                    vditorSV.setValue(content);
-                }
-            }, 30); // 30ms防抖,更流畅
-        }
-        
-        // 防抖函数
-        function debounce(func, wait) {
-            let timeout;
-            return function executedFunction(...args) {
-                const later = () => {
-                    clearTimeout(timeout);
-                    func(...args);
-                };
-                clearTimeout(timeout);
-                timeout = setTimeout(later, wait);
-            };
-        }
-        
-        // 更新所有编辑器的内容
-        function updateAllEditors(content) {
-            currentContent = content;
-            
-            // 使用防抖更新
-            if (debouncedUpdate) {
-                debouncedUpdate(content);
-            }
-        }
-        
-        // 流式完成处理
-        function onStreamComplete() {
-            console.log('=' + '='.repeat(80));
-            console.log('🎉 流式输出完成!');
-            console.log('=' + '='.repeat(80));
-            console.log('📊 统计信息:');
-            console.log(`📝 总字符数: ${charCount}`);
-            console.log(`📦 数据块数: ${chunkCount}`);
-            console.log(`⏱️ 完成时间: ${new Date().toLocaleString()}`);
-            console.log(`⏱️ 耗时: ${startTime ? Math.round((Date.now() - startTime) / 1000) : 0}秒`);
-            console.log('=' + '='.repeat(80));
-            console.log('📄 完整响应内容:');
-            console.log('=' + '='.repeat(80));
-            console.log(currentContent);
-            console.log('=' + '='.repeat(80));
-            console.log('🎯 原始数据内容:');
-            console.log('=' + '='.repeat(80));
-            console.log(document.getElementById('rawOutput').textContent);
-            console.log('=' + '='.repeat(80));
-            console.log('✅ 流式输出已完全结束,所有内容已渲染完成');
-        }
-        
-        // 停止测试
-        function stopTest() {
-            isStreaming = false;
-            document.getElementById('loading').classList.remove('show');
-            showStatus('⏹️ 测试已停止', 'info');
-        }
-        
-        // 开始测试
-        function startTest() {
-            const apiUrl = document.getElementById('apiUrl').value.trim();
-            const message = document.getElementById('message').value.trim();
-            const model = document.getElementById('model').value.trim();
-            
-            if (!apiUrl || !message) {
-                showStatus('❌ 请填写API地址和测试消息', 'error');
-                return;
-            }
-            
-            // 停止之前的测试
-            stopTest();
-            
-            // 清空输出
-            clearAll();
-            
-            // 显示加载状态
-            document.getElementById('loading').classList.add('show');
-            isStreaming = true;
-            startTime = Date.now();
-            
-            showStatus('🔄 正在连接流式接口...', 'info');
-            
-            // 构建请求数据
-            const requestData = {
-                message: message
-            };
-            
-            if (model) {
-                requestData.model = model;
-            }
-            
-            console.log('📤 发送请求:', requestData);
-            
-            // 使用fetch发送POST请求
-            fetch(apiUrl, {
-                method: 'POST',
-                headers: {
-                    'Content-Type': 'application/json',
-                },
-                body: JSON.stringify(requestData)
-            })
-            .then(response => {
-                if (!response.ok) {
-                    throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);
-                }
-                
-                if (!response.body) {
-                    throw new Error('响应体为空');
-                }
-                
-                showStatus('✅ 连接成功,开始接收流式数据...', 'success');
-                
-                // 处理流式响应
-                const reader = response.body.getReader();
-                const decoder = new TextDecoder();
-                let buffer = '';
-                let rawContent = '';
-                let markdownContent = '';
-                
-                function readStream() {
-                    reader.read().then(({ done, value }) => {
-                        if (done) {
-                            console.log('✅ 流式数据接收完成');
-                            document.getElementById('loading').classList.remove('show');
-                            isStreaming = false;
-                            showStatus('✅ 流式数据接收完成', 'success');
-                            updateStats();
-                            onStreamComplete();
-                            return;
-                        }
-                        
-                        // 解码数据
-                        const chunk = decoder.decode(value, { stream: true });
-                        buffer += chunk;
-                        
-                        // 处理完整的数据行
-                        const lines = buffer.split('\n');
-                        buffer = lines.pop() || ''; // 保留最后一个不完整的行
-                        
-                        for (const line of lines) {
-                            if (line.trim() === '') continue;
-                            
-                            console.log('📥 收到数据行:', line);
-                            
-                            if (line.startsWith('data: ')) {
-                                const data = line.substring(6);
-                                
-                                if (data === '[DONE]') {
-                                    console.log('🏁 流式结束');
-                                    document.getElementById('loading').classList.remove('show');
-                                    isStreaming = false;
-                                    showStatus('✅ 流式数据接收完成', 'success');
-                                    updateStats();
-                                    onStreamComplete();
-                                    return;
-                                }
-                                
-                                // 尝试解析JSON
-                                try {
-                                    const jsonData = JSON.parse(data);
-                                    if (jsonData.error) {
-                                        showStatus(`❌ 错误: ${jsonData.error}`, 'error');
-                                        document.getElementById('loading').classList.remove('show');
-                                        isStreaming = false;
-                                        return;
-                                    }
-                                } catch (e) {
-                                    // 不是JSON,直接作为文本内容处理
-                                    console.log('📝 收到文本内容:', data);
-                                    
-                                    chunkCount++;
-                                    
-                                    // 处理转义的换行符,将\n转换回真正的换行符
-                                    const processedData = data.replace(/\\n/g, '\n');
-                                    
-                                    // 添加到原始输出
-                                    rawContent += processedData;
-                                    document.getElementById('rawOutput').textContent = rawContent;
-                                    
-                                    // 添加到markdown内容
-                                    markdownContent += processedData;
-                                    charCount += processedData.length;
-                                    
-                                    // 实时更新所有编辑器
-                                    updateAllEditors(markdownContent);
-                                    
-                                    // 更新统计信息
-                                    updateStats();
-                                }
-                            }
-                        }
-                        
-                        // 继续读取
-                        readStream();
-                    }).catch(error => {
-                        console.error('❌ 读取流式数据时出错:', error);
-                        showStatus(`❌ 读取数据出错: ${error.message}`, 'error');
-                        document.getElementById('loading').classList.remove('show');
-                        isStreaming = false;
-                    });
-                }
-                
-                readStream();
-                
-            })
-            .catch(error => {
-                console.error('❌ 请求失败:', error);
-                showStatus(`❌ 请求失败: ${error.message}`, 'error');
-                document.getElementById('loading').classList.remove('show');
-                isStreaming = false;
-            });
-        }
-        
-        // 页面加载完成后初始化
-        document.addEventListener('DOMContentLoaded', function() {
-            initVditors();
-            initDebouncedUpdate(); // 初始化防抖函数
-            showStatus('✅ 页面加载完成,可以开始测试', 'success');
-        });
-        
-        // 页面卸载时清理
-        window.addEventListener('beforeunload', function() {
-            stopTest();
-        });
-    </script>
-</body>
-</html>

+ 0 - 844
shudao-go-backend/tests/test_pages/stream_chat_with_db_test.html

@@ -1,844 +0,0 @@
-<!DOCTYPE html>
-<html lang="zh-CN">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>流式聊天数据库集成测试</title>
-    <!-- Vditor CSS -->
-    <link rel="stylesheet" href="https://unpkg.com/vditor/dist/index.css" />
-    <style>
-        * {
-            margin: 0;
-            padding: 0;
-            box-sizing: border-box;
-        }
-        
-        body {
-            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
-            background: #f5f5f5;
-            padding: 20px;
-        }
-        
-        .container {
-            max-width: 1000px;
-            margin: 0 auto;
-            background: white;
-            border-radius: 8px;
-            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
-            overflow: hidden;
-        }
-        
-        .header {
-            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-            color: white;
-            padding: 30px;
-            text-align: center;
-        }
-        
-        .header h1 {
-            font-size: 28px;
-            margin-bottom: 10px;
-        }
-        
-        .header p {
-            opacity: 0.9;
-            font-size: 16px;
-        }
-        
-        .content {
-            padding: 30px;
-        }
-        
-        .test-section {
-            margin-bottom: 30px;
-            padding: 25px;
-            background: #f8f9fa;
-            border-radius: 8px;
-            border-left: 4px solid #667eea;
-        }
-        
-        .form-group {
-            margin-bottom: 20px;
-        }
-        
-        label {
-            display: block;
-            margin-bottom: 8px;
-            font-weight: 600;
-            color: #2c3e50;
-            font-size: 14px;
-        }
-        
-        input, textarea {
-            width: 100%;
-            padding: 12px;
-            border: 2px solid #e1e8ed;
-            border-radius: 6px;
-            font-size: 14px;
-            transition: border-color 0.3s;
-        }
-        
-        input:focus, textarea:focus {
-            outline: none;
-            border-color: #667eea;
-        }
-        
-        textarea {
-            height: 100px;
-            resize: vertical;
-            font-family: inherit;
-        }
-        
-        .button-group {
-            display: flex;
-            gap: 15px;
-            margin-top: 20px;
-        }
-        
-        button {
-            padding: 12px 24px;
-            border: none;
-            border-radius: 6px;
-            cursor: pointer;
-            font-size: 14px;
-            font-weight: 600;
-            transition: all 0.3s;
-            min-width: 120px;
-        }
-        
-        .btn-primary {
-            background: #667eea;
-            color: white;
-        }
-        
-        .btn-primary:hover {
-            background: #5a6fd8;
-            transform: translateY(-1px);
-        }
-        
-        .btn-secondary {
-            background: #6c757d;
-            color: white;
-        }
-        
-        .btn-secondary:hover {
-            background: #5a6268;
-        }
-        
-        .btn-danger {
-            background: #dc3545;
-            color: white;
-        }
-        
-        .btn-danger:hover {
-            background: #c82333;
-        }
-        
-        .status {
-            padding: 15px;
-            margin: 15px 0;
-            border-radius: 6px;
-            font-weight: 600;
-            display: none;
-        }
-        
-        .status.show {
-            display: block;
-        }
-        
-        .status.info {
-            background: #d1ecf1;
-            color: #0c5460;
-            border: 1px solid #bee5eb;
-        }
-        
-        .status.success {
-            background: #d4edda;
-            color: #155724;
-            border: 1px solid #c3e6cb;
-        }
-        
-        .status.error {
-            background: #f8d7da;
-            color: #721c24;
-            border: 1px solid #f5c6cb;
-        }
-        
-        .output-section {
-            margin-top: 30px;
-        }
-        
-        .output-header {
-            display: flex;
-            justify-content: space-between;
-            align-items: center;
-            margin-bottom: 20px;
-            padding-bottom: 15px;
-            border-bottom: 2px solid #e1e8ed;
-        }
-        
-        .output-title {
-            font-size: 18px;
-            font-weight: 600;
-            color: #2c3e50;
-        }
-        
-        .output-tabs {
-            display: flex;
-            gap: 5px;
-        }
-        
-        .tab {
-            padding: 8px 16px;
-            cursor: pointer;
-            border-radius: 4px;
-            font-size: 14px;
-            font-weight: 500;
-            transition: all 0.3s;
-            background: #f8f9fa;
-            color: #6c757d;
-        }
-        
-        .tab.active {
-            background: #667eea;
-            color: white;
-        }
-        
-        .tab-content {
-            display: none;
-        }
-        
-        .tab-content.active {
-            display: block;
-        }
-        
-        .raw-output {
-            background: #f8f9fa;
-            border: 2px solid #e1e8ed;
-            border-radius: 6px;
-            padding: 20px;
-            font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
-            font-size: 13px;
-            line-height: 1.5;
-            white-space: pre-wrap;
-            max-height: 500px;
-            overflow-y: auto;
-            color: #2c3e50;
-        }
-        
-        .markdown-container {
-            border: 2px solid #e1e8ed;
-            border-radius: 6px;
-            min-height: 400px;
-            background: white;
-        }
-        
-        /* 只读编辑器样式 */
-        .markdown-container.readonly-editor {
-            user-select: none; /* 禁用文本选择 */
-        }
-        
-        .markdown-container .vditor-toolbar {
-            display: none !important; /* 隐藏工具栏 */
-        }
-        
-        .markdown-container .vditor-content {
-            cursor: default !important; /* 默认光标 */
-        }
-        
-        .markdown-container .vditor-content:focus {
-            outline: none !important; /* 移除焦点样式 */
-        }
-        
-        /* 确保滚动条正常工作 */
-        .markdown-container .vditor-content {
-            overflow-y: auto !important; /* 确保垂直滚动 */
-            pointer-events: auto !important; /* 允许滚动交互 */
-        }
-        
-        .markdown-container .vditor-content::-webkit-scrollbar {
-            width: 8px;
-        }
-        
-        .markdown-container .vditor-content::-webkit-scrollbar-track {
-            background: #f1f1f1;
-            border-radius: 4px;
-        }
-        
-        .markdown-container .vditor-content::-webkit-scrollbar-thumb {
-            background: #c1c1c1;
-            border-radius: 4px;
-        }
-        
-        .markdown-container .vditor-content::-webkit-scrollbar-thumb:hover {
-            background: #a8a8a8;
-        }
-        
-        .loading {
-            display: none;
-            text-align: center;
-            padding: 40px;
-            color: #6c757d;
-        }
-        
-        .loading.show {
-            display: block;
-        }
-        
-        .spinner {
-            border: 4px solid #f3f3f3;
-            border-top: 4px solid #667eea;
-            border-radius: 50%;
-            width: 40px;
-            height: 40px;
-            animation: spin 1s linear infinite;
-            margin: 0 auto 15px;
-        }
-        
-        @keyframes spin {
-            0% { transform: rotate(0deg); }
-            100% { transform: rotate(360deg); }
-        }
-        
-        .stats {
-            display: flex;
-            gap: 20px;
-            margin-top: 15px;
-            font-size: 12px;
-            color: #6c757d;
-        }
-        
-        .stat-item {
-            display: flex;
-            align-items: center;
-            gap: 5px;
-        }
-        
-        .stat-value {
-            font-weight: 600;
-            color: #2c3e50;
-        }
-        
-        .db-info {
-            background: #e8f5e8;
-            border: 1px solid #c3e6cb;
-            border-radius: 6px;
-            padding: 15px;
-            margin-bottom: 20px;
-        }
-        
-        .db-info h4 {
-            color: #155724;
-            margin-bottom: 10px;
-        }
-        
-        .db-info .info-item {
-            margin-bottom: 5px;
-            font-size: 14px;
-        }
-        
-        .db-info .info-label {
-            font-weight: 600;
-            color: #2c3e50;
-        }
-    </style>
-</head>
-<body>
-    <div class="container">
-        <div class="header">
-            <h1>🚀 流式聊天数据库集成测试</h1>
-            <p>测试流式接口与数据库操作的集成功能</p>
-        </div>
-        
-        <div class="content">
-            <div class="test-section">
-                <div class="form-group">
-                    <label for="apiUrl">API接口地址</label>
-                    <input type="text" id="apiUrl" value="http://localhost:22000/apiv1/stream/chat-with-db" placeholder="输入流式接口地址">
-                </div>
-                
-                <div class="form-group">
-                    <label for="message">测试消息</label>
-                    <textarea id="message" placeholder="输入要测试的消息内容">你好,请介绍一下施工现场的安全防护要求,包括临边防护、脚手架管理等具体规范</textarea>
-                </div>
-                
-                <div class="form-group">
-                    <label for="userId">用户ID</label>
-                    <input type="number" id="userId" value="1" placeholder="用户ID">
-                </div>
-                
-                <div class="form-group">
-                    <label for="conversationId">对话ID (0表示新建对话)</label>
-                    <input type="number" id="conversationId" value="0" placeholder="对话ID">
-                </div>
-                
-                <div class="form-group">
-                    <label for="businessType">业务类型</label>
-                    <input type="number" id="businessType" value="1" placeholder="业务类型">
-                </div>
-                
-                <div class="form-group">
-                    <label for="examName">考试名称</label>
-                    <input type="text" id="examName" value="安全培训" placeholder="考试名称">
-                </div>
-                
-                <div class="button-group">
-                    <button class="btn-primary" onclick="startTest()">▶️ 开始测试</button>
-                    <button class="btn-secondary" onclick="clearAll()">🗑️ 清空输出</button>
-                    <button class="btn-danger" onclick="stopTest()">⏹️ 停止测试</button>
-                </div>
-            </div>
-            
-            <div id="status" class="status"></div>
-            
-            <div class="loading" id="loading">
-                <div class="spinner"></div>
-                <div>正在接收流式数据...</div>
-            </div>
-            
-            <!-- 数据库信息显示区域 -->
-            <div id="dbInfo" class="db-info" style="display: none;">
-                <h4>📊 数据库信息</h4>
-                <div class="info-item">
-                    <span class="info-label">对话ID:</span>
-                    <span id="conversationIdDisplay">-</span>
-                </div>
-                <div class="info-item">
-                    <span class="info-label">消息ID:</span>
-                    <span id="messageIdDisplay">-</span>
-                </div>
-                <div class="info-item">
-                    <span class="info-label">状态:</span>
-                    <span id="dbStatusDisplay">-</span>
-                </div>
-            </div>
-            
-            <div class="output-section">
-                <div class="output-header">
-                    <div class="output-title">📊 测试结果</div>
-                    <div class="output-tabs">
-                        <div class="tab active" onclick="switchTab('raw')">原始数据</div>
-                        <div class="tab" onclick="switchTab('wysiwyg')">WYSIWYG预览</div>
-                        <div class="tab" onclick="switchTab('ir')">即时渲染预览(IR)</div>
-                        <div class="tab" onclick="switchTab('sv')">分屏预览(SV)</div>
-                    </div>
-                </div>
-                
-                <div style="background: #f8f9fa; padding: 15px; margin-bottom: 20px; border-radius: 6px; font-size: 14px; color: #6c757d;">
-                    <strong>📝 渲染模式说明(只读预览):</strong><br>
-                    • <strong>WYSIWYG预览</strong>:所见即所得预览,显示最终渲染效果(不可编辑)<br>
-                    • <strong>即时渲染预览(IR)</strong>:实时渲染Markdown为HTML预览(不可编辑)<br>
-                    • <strong>分屏预览(SV)</strong>:左侧显示Markdown源码,右侧显示HTML预览(不可编辑)
-                </div>
-                
-                <div id="rawTab" class="tab-content active">
-                    <div class="raw-output" id="rawOutput">等待测试数据...</div>
-                </div>
-                
-                <div id="wysiwygTab" class="tab-content">
-                    <div class="markdown-container readonly-editor" id="wysiwygOutput"></div>
-                </div>
-                
-                <div id="irTab" class="tab-content">
-                    <div class="markdown-container readonly-editor" id="irOutput"></div>
-                </div>
-                
-                <div id="svTab" class="tab-content">
-                    <div class="markdown-container readonly-editor" id="svOutput"></div>
-                </div>
-                
-                <div class="stats" id="stats" style="display: none;">
-                    <div class="stat-item">
-                        <span>📝 字符数:</span>
-                        <span class="stat-value" id="charCount">0</span>
-                    </div>
-                    <div class="stat-item">
-                        <span>⏱️ 耗时:</span>
-                        <span class="stat-value" id="duration">0s</span>
-                    </div>
-                    <div class="stat-item">
-                        <span>📦 数据块:</span>
-                        <span class="stat-value" id="chunkCount">0</span>
-                    </div>
-                </div>
-            </div>
-        </div>
-    </div>
-
-    <!-- Vditor JavaScript -->
-    <script src="https://unpkg.com/vditor/dist/index.min.js"></script>
-    
-    <script>
-        let vditorWysiwyg = null;
-        let vditorIR = null;
-        let vditorSV = null;
-        let isStreaming = false;
-        let startTime = null;
-        let charCount = 0;
-        let chunkCount = 0;
-        let currentContent = '';
-        
-        // 防抖更新函数
-        let debouncedUpdate = null;
-        
-        // 初始化防抖函数
-        function initDebouncedUpdate() {
-            debouncedUpdate = debounce((content) => {
-                if (vditorWysiwyg) {
-                    vditorWysiwyg.setValue(content);
-                }
-                if (vditorIR) {
-                    vditorIR.setValue(content);
-                }
-                if (vditorSV) {
-                    vditorSV.setValue(content);
-                }
-            }, 30); // 30ms防抖,更流畅
-        }
-        
-        // 防抖函数
-        function debounce(func, wait) {
-            let timeout;
-            return function executedFunction(...args) {
-                const later = () => {
-                    clearTimeout(timeout);
-                    func(...args);
-                };
-                clearTimeout(timeout);
-                timeout = setTimeout(later, wait);
-            };
-        }
-        
-        // 初始化所有Vditor编辑器
-        function initVditors() {
-            // 销毁现有的编辑器
-            if (vditorWysiwyg) vditorWysiwyg.destroy();
-            if (vditorIR) vditorIR.destroy();
-            if (vditorSV) vditorSV.destroy();
-            
-            // 初始化WYSIWYG模式 - 只读预览
-            vditorWysiwyg = new Vditor('wysiwygOutput', {
-                height: 400,
-                mode: 'wysiwyg',
-                cache: {
-                    id: 'vditor-wysiwyg-cache'
-                },
-                toolbar: [], // 去掉工具栏
-                disabled: true, // 设置为只读
-                after: () => {
-                    console.log('✅ WYSIWYG预览初始化完成');
-                }
-            });
-            
-            // 初始化即时渲染(IR)模式 - 只读预览
-            vditorIR = new Vditor('irOutput', {
-                height: 400,
-                mode: 'ir',
-                cache: {
-                    id: 'vditor-ir-cache'
-                },
-                toolbar: [], // 去掉工具栏
-                disabled: true, // 设置为只读
-                after: () => {
-                    console.log('✅ 即时渲染预览(IR)初始化完成');
-                }
-            });
-            
-            // 初始化分屏预览(SV)模式 - 只读预览
-            vditorSV = new Vditor('svOutput', {
-                height: 400,
-                mode: 'sv',
-                cache: {
-                    id: 'vditor-sv-cache'
-                },
-                toolbar: [], // 去掉工具栏
-                disabled: true, // 设置为只读
-                after: () => {
-                    console.log('✅ 分屏预览(SV)初始化完成');
-                }
-            });
-        }
-        
-        // 切换标签页
-        function switchTab(tabName) {
-            // 隐藏所有标签内容
-            document.querySelectorAll('.tab-content').forEach(content => {
-                content.classList.remove('active');
-            });
-            
-            // 移除所有标签的active类
-            document.querySelectorAll('.tab').forEach(tab => {
-                tab.classList.remove('active');
-            });
-            
-            // 显示选中的标签内容
-            document.getElementById(tabName + 'Tab').classList.add('active');
-            
-            // 添加active类到选中的标签
-            event.target.classList.add('active');
-        }
-        
-        // 显示状态信息
-        function showStatus(message, type = 'info') {
-            const statusEl = document.getElementById('status');
-            statusEl.textContent = message;
-            statusEl.className = `status ${type}`;
-            statusEl.classList.add('show');
-            
-            setTimeout(() => {
-                statusEl.classList.remove('show');
-            }, 5000);
-        }
-        
-        // 更新统计信息
-        function updateStats() {
-            const duration = startTime ? Math.round((Date.now() - startTime) / 1000) : 0;
-            document.getElementById('charCount').textContent = charCount;
-            document.getElementById('duration').textContent = duration + 's';
-            document.getElementById('chunkCount').textContent = chunkCount;
-            document.getElementById('stats').style.display = 'flex';
-        }
-        
-        // 清空所有输出
-        function clearAll() {
-            document.getElementById('rawOutput').textContent = '等待测试数据...';
-            currentContent = '';
-            
-            if (vditorWysiwyg) vditorWysiwyg.setValue('');
-            if (vditorIR) vditorIR.setValue('');
-            if (vditorSV) vditorSV.setValue('');
-            
-            charCount = 0;
-            chunkCount = 0;
-            startTime = null;
-            updateStats();
-            
-            // 隐藏数据库信息
-            document.getElementById('dbInfo').style.display = 'none';
-            
-            showStatus('✅ 输出已清空', 'success');
-        }
-        
-        // 更新所有编辑器的内容
-        function updateAllEditors(content) {
-            currentContent = content;
-            
-            // 使用防抖更新
-            if (debouncedUpdate) {
-                debouncedUpdate(content);
-            }
-        }
-        
-        // 流式完成处理
-        function onStreamComplete() {
-            console.log('=' + '='.repeat(80));
-            console.log('🎉 流式输出完成!');
-            console.log('=' + '='.repeat(80));
-            console.log('📊 统计信息:');
-            console.log(`📝 总字符数: ${charCount}`);
-            console.log(`📦 数据块数: ${chunkCount}`);
-            console.log(`⏱️ 完成时间: ${new Date().toLocaleString()}`);
-            console.log(`⏱️ 耗时: ${startTime ? Math.round((Date.now() - startTime) / 1000) : 0}秒`);
-            console.log('=' + '='.repeat(80));
-            console.log('📄 完整响应内容:');
-            console.log('=' + '='.repeat(80));
-            console.log(currentContent);
-            console.log('=' + '='.repeat(80));
-            console.log('🎯 原始数据内容:');
-            console.log('=' + '='.repeat(80));
-            console.log(document.getElementById('rawOutput').textContent);
-            console.log('=' + '='.repeat(80));
-            console.log('✅ 流式输出已完全结束,所有内容已渲染完成');
-        }
-        
-        // 停止测试
-        function stopTest() {
-            isStreaming = false;
-            document.getElementById('loading').classList.remove('show');
-            showStatus('⏹️ 测试已停止', 'info');
-        }
-        
-        // 开始测试
-        function startTest() {
-            const apiUrl = document.getElementById('apiUrl').value.trim();
-            const message = document.getElementById('message').value.trim();
-            const userId = parseInt(document.getElementById('userId').value) || 1;
-            const conversationId = parseInt(document.getElementById('conversationId').value) || 0;
-            const businessType = parseInt(document.getElementById('businessType').value) || 1;
-            const examName = document.getElementById('examName').value.trim();
-            
-            if (!apiUrl || !message) {
-                showStatus('❌ 请填写API地址和测试消息', 'error');
-                return;
-            }
-            
-            // 停止之前的测试
-            stopTest();
-            
-            // 清空输出
-            clearAll();
-            
-            // 显示加载状态
-            document.getElementById('loading').classList.add('show');
-            isStreaming = true;
-            startTime = Date.now();
-            
-            showStatus('🔄 正在连接流式接口...', 'info');
-            
-            // 构建请求数据
-            const requestData = {
-                message: message,
-                user_id: userId,
-                ai_conversation_id: conversationId,
-                business_type: businessType,
-                exam_name: examName
-            };
-            
-            console.log('📤 发送请求:', requestData);
-            
-            // 使用fetch发送POST请求
-            fetch(apiUrl, {
-                method: 'POST',
-                headers: {
-                    'Content-Type': 'application/json',
-                },
-                body: JSON.stringify(requestData)
-            })
-            .then(response => {
-                if (!response.ok) {
-                    throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);
-                }
-                
-                if (!response.body) {
-                    throw new Error('响应体为空');
-                }
-                
-                showStatus('✅ 连接成功,开始接收流式数据...', 'success');
-                
-                // 处理流式响应
-                const reader = response.body.getReader();
-                const decoder = new TextDecoder();
-                let buffer = '';
-                let rawContent = '';
-                let markdownContent = '';
-                
-                function readStream() {
-                    reader.read().then(({ done, value }) => {
-                        if (done) {
-                            console.log('✅ 流式数据接收完成');
-                            document.getElementById('loading').classList.remove('show');
-                            isStreaming = false;
-                            showStatus('✅ 流式数据接收完成', 'success');
-                            updateStats();
-                            onStreamComplete();
-                            return;
-                        }
-                        
-                        // 解码数据
-                        const chunk = decoder.decode(value, { stream: true });
-                        buffer += chunk;
-                        
-                        // 处理完整的数据行
-                        const lines = buffer.split('\n');
-                        buffer = lines.pop() || ''; // 保留最后一个不完整的行
-                        
-                        for (const line of lines) {
-                            if (line.trim() === '') continue;
-                            
-                            console.log('📥 收到数据行:', line);
-                            
-                            if (line.startsWith('data: ')) {
-                                const data = line.substring(6);
-                                
-                                if (data === '[DONE]') {
-                                    console.log('🏁 流式结束');
-                                    document.getElementById('loading').classList.remove('show');
-                                    isStreaming = false;
-                                    showStatus('✅ 流式数据接收完成', 'success');
-                                    updateStats();
-                                    onStreamComplete();
-                                    return;
-                                }
-                                
-                                // 尝试解析JSON
-                                try {
-                                    const jsonData = JSON.parse(data);
-                                    
-                                    // 处理初始响应(数据库ID)
-                                    if (jsonData.type === 'initial') {
-                                        console.log('📊 收到数据库信息:', jsonData);
-                                        document.getElementById('conversationIdDisplay').textContent = jsonData.ai_conversation_id;
-                                        document.getElementById('messageIdDisplay').textContent = jsonData.ai_message_id;
-                                        document.getElementById('dbStatusDisplay').textContent = jsonData.status;
-                                        document.getElementById('dbInfo').style.display = 'block';
-                                        showStatus('✅ 数据库操作成功,开始流式输出', 'success');
-                                        continue;
-                                    }
-                                    
-                                    if (jsonData.error) {
-                                        showStatus(`❌ 错误: ${jsonData.error}`, 'error');
-                                        document.getElementById('loading').classList.remove('show');
-                                        isStreaming = false;
-                                        return;
-                                    }
-                                } catch (e) {
-                                    // 不是JSON,直接作为文本内容处理
-                                    console.log('📝 收到文本内容:', data);
-                                    
-                                    chunkCount++;
-                                    
-                                    // 处理转义的换行符,将\n转换回真正的换行符
-                                    const processedData = data.replace(/\\n/g, '\n');
-                                    
-                                    // 添加到原始输出
-                                    rawContent += processedData;
-                                    document.getElementById('rawOutput').textContent = rawContent;
-                                    
-                                    // 添加到markdown内容
-                                    markdownContent += processedData;
-                                    charCount += processedData.length;
-                                    
-                                    // 实时更新所有编辑器
-                                    updateAllEditors(markdownContent);
-                                    
-                                    // 更新统计信息
-                                    updateStats();
-                                }
-                            }
-                        }
-                        
-                        // 继续读取
-                        readStream();
-                    }).catch(error => {
-                        console.error('❌ 读取流式数据时出错:', error);
-                        showStatus(`❌ 读取数据出错: ${error.message}`, 'error');
-                        document.getElementById('loading').classList.remove('show');
-                        isStreaming = false;
-                    });
-                }
-                
-                readStream();
-                
-            })
-            .catch(error => {
-                console.error('❌ 请求失败:', error);
-                showStatus(`❌ 请求失败: ${error.message}`, 'error');
-                document.getElementById('loading').classList.remove('show');
-                isStreaming = false;
-            });
-        }
-        
-        // 页面加载完成后初始化
-        document.addEventListener('DOMContentLoaded', function() {
-            initVditors();
-            initDebouncedUpdate(); // 初始化防抖函数
-            showStatus('✅ 页面加载完成,可以开始测试', 'success');
-        });
-        
-        // 页面卸载时清理
-        window.addEventListener('beforeunload', function() {
-            stopTest();
-        });
-    </script>
-</body>
-</html>

+ 0 - 466
shudao-go-backend/tests/test_pages/stream_test.html

@@ -1,466 +0,0 @@
-<!DOCTYPE html>
-<html lang="zh-CN">
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>流式接口Markdown渲染测试</title>
-    <!-- Vditor CSS -->
-    <link rel="stylesheet" href="https://unpkg.com/vditor/dist/index.css" />
-    <style>
-        body {
-            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
-            margin: 0;
-            padding: 20px;
-            background-color: #f5f5f5;
-        }
-        .container {
-            max-width: 1200px;
-            margin: 0 auto;
-            background: white;
-            border-radius: 8px;
-            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
-            overflow: hidden;
-        }
-        .header {
-            background: #2c3e50;
-            color: white;
-            padding: 20px;
-            text-align: center;
-        }
-        .content {
-            padding: 20px;
-        }
-        .input-section {
-            margin-bottom: 20px;
-            padding: 20px;
-            background: #f8f9fa;
-            border-radius: 6px;
-        }
-        .input-group {
-            margin-bottom: 15px;
-        }
-        label {
-            display: block;
-            margin-bottom: 5px;
-            font-weight: 600;
-            color: #2c3e50;
-        }
-        input[type="text"], textarea {
-            width: 100%;
-            padding: 10px;
-            border: 1px solid #ddd;
-            border-radius: 4px;
-            font-size: 14px;
-            box-sizing: border-box;
-        }
-        textarea {
-            height: 80px;
-            resize: vertical;
-        }
-        .button-group {
-            display: flex;
-            gap: 10px;
-            margin-top: 15px;
-        }
-        button {
-            padding: 10px 20px;
-            border: none;
-            border-radius: 4px;
-            cursor: pointer;
-            font-size: 14px;
-            font-weight: 600;
-            transition: background-color 0.3s;
-        }
-        .btn-primary {
-            background: #3498db;
-            color: white;
-        }
-        .btn-primary:hover {
-            background: #2980b9;
-        }
-        .btn-secondary {
-            background: #95a5a6;
-            color: white;
-        }
-        .btn-secondary:hover {
-            background: #7f8c8d;
-        }
-        .btn-danger {
-            background: #e74c3c;
-            color: white;
-        }
-        .btn-danger:hover {
-            background: #c0392b;
-        }
-        .status {
-            padding: 10px;
-            margin: 10px 0;
-            border-radius: 4px;
-            font-weight: 600;
-        }
-        .status.info {
-            background: #d1ecf1;
-            color: #0c5460;
-            border: 1px solid #bee5eb;
-        }
-        .status.success {
-            background: #d4edda;
-            color: #155724;
-            border: 1px solid #c3e6cb;
-        }
-        .status.error {
-            background: #f8d7da;
-            color: #721c24;
-            border: 1px solid #f5c6cb;
-        }
-        .output-section {
-            margin-top: 20px;
-        }
-        .output-tabs {
-            display: flex;
-            border-bottom: 1px solid #ddd;
-            margin-bottom: 20px;
-        }
-        .tab {
-            padding: 10px 20px;
-            cursor: pointer;
-            border-bottom: 2px solid transparent;
-            transition: all 0.3s;
-        }
-        .tab.active {
-            border-bottom-color: #3498db;
-            color: #3498db;
-            font-weight: 600;
-        }
-        .tab-content {
-            display: none;
-        }
-        .tab-content.active {
-            display: block;
-        }
-        .raw-output {
-            background: #f8f9fa;
-            border: 1px solid #e9ecef;
-            border-radius: 4px;
-            padding: 15px;
-            font-family: 'Courier New', monospace;
-            font-size: 12px;
-            white-space: pre-wrap;
-            max-height: 400px;
-            overflow-y: auto;
-        }
-        .markdown-output {
-            border: 1px solid #e9ecef;
-            border-radius: 4px;
-            min-height: 200px;
-        }
-        .loading {
-            display: none;
-            text-align: center;
-            padding: 20px;
-            color: #666;
-        }
-        .loading.show {
-            display: block;
-        }
-        .spinner {
-            border: 3px solid #f3f3f3;
-            border-top: 3px solid #3498db;
-            border-radius: 50%;
-            width: 30px;
-            height: 30px;
-            animation: spin 1s linear infinite;
-            margin: 0 auto 10px;
-        }
-        @keyframes spin {
-            0% { transform: rotate(0deg); }
-            100% { transform: rotate(360deg); }
-        }
-    </style>
-</head>
-<body>
-    <div class="container">
-        <div class="header">
-            <h1>流式接口Markdown渲染测试</h1>
-            <p>测试 /apiv1/stream/chat 接口的流式响应和Markdown渲染效果</p>
-        </div>
-        
-        <div class="content">
-            <div class="input-section">
-                <div class="input-group">
-                    <label for="apiUrl">API地址:</label>
-                    <input type="text" id="apiUrl" value="http://localhost:22000/apiv1/stream/chat" placeholder="输入流式接口地址">
-                </div>
-                
-                <div class="input-group">
-                    <label for="message">测试消息:</label>
-                    <textarea id="message" placeholder="输入要测试的消息内容">你好,请介绍一下施工现场的安全防护要求</textarea>
-                </div>
-                
-                <div class="input-group">
-                    <label for="model">模型名称 (可选):</label>
-                    <input type="text" id="model" placeholder="模型名称,留空使用默认">
-                </div>
-                
-                <div class="button-group">
-                    <button class="btn-primary" onclick="startStreamTest()">开始流式测试</button>
-                    <button class="btn-secondary" onclick="clearOutput()">清空输出</button>
-                    <button class="btn-danger" onclick="stopStream()">停止流式</button>
-                </div>
-            </div>
-            
-            <div id="status" class="status info" style="display: none;"></div>
-            
-            <div class="loading" id="loading">
-                <div class="spinner"></div>
-                <div>正在接收流式数据...</div>
-            </div>
-            
-            <div class="output-section">
-                <div class="output-tabs">
-                    <div class="tab active" onclick="switchTab('raw')">原始输出</div>
-                    <div class="tab" onclick="switchTab('markdown')">Markdown渲染</div>
-                </div>
-                
-                <div id="rawTab" class="tab-content active">
-                    <div class="raw-output" id="rawOutput"></div>
-                </div>
-                
-                <div id="markdownTab" class="tab-content">
-                    <div class="markdown-output" id="markdownOutput"></div>
-                </div>
-            </div>
-        </div>
-    </div>
-
-    <!-- Vditor JavaScript -->
-    <script src="https://unpkg.com/vditor/dist/index.min.js"></script>
-    
-    <script>
-        let vditor = null;
-        let eventSource = null;
-        let isStreaming = false;
-        
-        // 初始化Vditor
-        function initVditor() {
-            if (vditor) {
-                vditor.destroy();
-            }
-            
-            vditor = new Vditor('markdownOutput', {
-                height: 400,
-                mode: 'wysiwyg',
-                toolbar: ['emoji', 'headings', 'bold', 'italic', 'strike', 'link', '|', 'list', 'ordered-list', 'check', 'outdent', 'indent', '|', 'quote', 'line', 'code', 'inline-code', 'insert-before', 'insert-after', '|', 'table', '|', 'undo', 'redo', '|', 'fullscreen', 'edit-mode', '|', 'content-theme', 'code-theme', '|', 'preview', 'export'],
-                after: () => {
-                    console.log('Vditor初始化完成');
-                }
-            });
-        }
-        
-        // 切换标签页
-        function switchTab(tabName) {
-            // 隐藏所有标签内容
-            document.querySelectorAll('.tab-content').forEach(content => {
-                content.classList.remove('active');
-            });
-            
-            // 移除所有标签的active类
-            document.querySelectorAll('.tab').forEach(tab => {
-                tab.classList.remove('active');
-            });
-            
-            // 显示选中的标签内容
-            document.getElementById(tabName + 'Tab').classList.add('active');
-            
-            // 添加active类到选中的标签
-            event.target.classList.add('active');
-        }
-        
-        // 显示状态信息
-        function showStatus(message, type = 'info') {
-            const statusEl = document.getElementById('status');
-            statusEl.textContent = message;
-            statusEl.className = `status ${type}`;
-            statusEl.style.display = 'block';
-            
-            setTimeout(() => {
-                statusEl.style.display = 'none';
-            }, 5000);
-        }
-        
-        // 清空输出
-        function clearOutput() {
-            document.getElementById('rawOutput').textContent = '';
-            if (vditor) {
-                vditor.setValue('');
-            }
-            showStatus('输出已清空', 'success');
-        }
-        
-        // 停止流式
-        function stopStream() {
-            if (eventSource) {
-                eventSource.close();
-                eventSource = null;
-            }
-            isStreaming = false;
-            document.getElementById('loading').classList.remove('show');
-            showStatus('流式连接已停止', 'info');
-        }
-        
-        // 开始流式测试
-        function startStreamTest() {
-            const apiUrl = document.getElementById('apiUrl').value.trim();
-            const message = document.getElementById('message').value.trim();
-            const model = document.getElementById('model').value.trim();
-            
-            if (!apiUrl || !message) {
-                showStatus('请填写API地址和测试消息', 'error');
-                return;
-            }
-            
-            // 停止之前的连接
-            stopStream();
-            
-            // 清空输出
-            clearOutput();
-            
-            // 显示加载状态
-            document.getElementById('loading').classList.add('show');
-            isStreaming = true;
-            
-            showStatus('正在连接流式接口...', 'info');
-            
-            // 构建请求数据
-            const requestData = {
-                message: message
-            };
-            
-            if (model) {
-                requestData.model = model;
-            }
-            
-            console.log('发送请求:', requestData);
-            
-            // 使用fetch发送POST请求
-            fetch(apiUrl, {
-                method: 'POST',
-                headers: {
-                    'Content-Type': 'application/json',
-                },
-                body: JSON.stringify(requestData)
-            })
-            .then(response => {
-                if (!response.ok) {
-                    throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);
-                }
-                
-                if (!response.body) {
-                    throw new Error('响应体为空');
-                }
-                
-                showStatus('连接成功,开始接收流式数据...', 'success');
-                
-                // 处理流式响应
-                const reader = response.body.getReader();
-                const decoder = new TextDecoder();
-                let buffer = '';
-                let rawContent = '';
-                let markdownContent = '';
-                
-                function readStream() {
-                    reader.read().then(({ done, value }) => {
-                        if (done) {
-                            console.log('流式数据接收完成');
-                            document.getElementById('loading').classList.remove('show');
-                            isStreaming = false;
-                            showStatus('流式数据接收完成', 'success');
-                            return;
-                        }
-                        
-                        // 解码数据
-                        const chunk = decoder.decode(value, { stream: true });
-                        buffer += chunk;
-                        
-                        // 处理完整的数据行
-                        const lines = buffer.split('\n');
-                        buffer = lines.pop() || ''; // 保留最后一个不完整的行
-                        
-                        for (const line of lines) {
-                            if (line.trim() === '') continue;
-                            
-                            console.log('收到数据行:', line);
-                            
-                            if (line.startsWith('data: ')) {
-                                const data = line.substring(6);
-                                
-                                if (data === '[DONE]') {
-                                    console.log('流式结束');
-                                    document.getElementById('loading').classList.remove('show');
-                                    isStreaming = false;
-                                    showStatus('流式数据接收完成', 'success');
-                                    return;
-                                }
-                                
-                                // 尝试解析JSON
-                                try {
-                                    const jsonData = JSON.parse(data);
-                                    if (jsonData.error) {
-                                        showStatus(`错误: ${jsonData.error}`, 'error');
-                                        document.getElementById('loading').classList.remove('show');
-                                        isStreaming = false;
-                                        return;
-                                    }
-                                } catch (e) {
-                                    // 不是JSON,直接作为文本内容处理
-                                    console.log('收到文本内容:', data);
-                                    
-                                    // 添加到原始输出
-                                    rawContent += data;
-                                    document.getElementById('rawOutput').textContent = rawContent;
-                                    
-                                    // 添加到markdown内容
-                                    markdownContent += data;
-                                    
-                                    // 实时更新Vditor
-                                    if (vditor) {
-                                        vditor.setValue(markdownContent);
-                                    }
-                                }
-                            }
-                        }
-                        
-                        // 继续读取
-                        readStream();
-                    }).catch(error => {
-                        console.error('读取流式数据时出错:', error);
-                        showStatus(`读取数据出错: ${error.message}`, 'error');
-                        document.getElementById('loading').classList.remove('show');
-                        isStreaming = false;
-                    });
-                }
-                
-                readStream();
-                
-            })
-            .catch(error => {
-                console.error('请求失败:', error);
-                showStatus(`请求失败: ${error.message}`, 'error');
-                document.getElementById('loading').classList.remove('show');
-                isStreaming = false;
-            });
-        }
-        
-        // 页面加载完成后初始化
-        document.addEventListener('DOMContentLoaded', function() {
-            initVditor();
-            showStatus('页面加载完成,可以开始测试', 'success');
-        });
-        
-        // 页面卸载时清理
-        window.addEventListener('beforeunload', function() {
-            stopStream();
-        });
-    </script>
-</body>
-</html>

+ 0 - 214
shudao-go-backend/tests/token_calculation_test.go

@@ -1,214 +0,0 @@
-package tests
-
-import (
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"io"
-	"net/http"
-	"testing"
-)
-
-// TestQwenTokenCalculation 测试千问模型的token计算规则
-func TestQwenTokenCalculation(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.Qwen3BaseURL
-	model := config.Qwen3Model
-
-	// 构建询问token计算规则的请求
-	request := Qwen3ChatRequest{
-		Model:  model,
-		Stream: false,
-		Messages: []ChatMessage{
-			{
-				Role:    "user",
-				Content: "请详细解释一下你的token计算规则。具体来说:1. 中文字符如何计算token?2. 英文字符如何计算token?3. 标点符号如何计算token?4. JSON结构字符(如{、}、[、]、\"、:、,)如何计算token?5. 换行符和空格如何计算token?请给出具体的比例或规则。",
-			},
-		},
-	}
-
-	jsonData, err := json.Marshal(request)
-	if err != nil {
-		t.Fatalf("序列化请求失败: %v", err)
-	}
-
-	t.Logf("发送的请求: %s", string(jsonData))
-
-	// 发送POST请求
-	resp, err := http.Post(
-		fmt.Sprintf("%s/v1/chat/completions", baseURL),
-		"application/json",
-		bytes.NewBuffer(jsonData),
-	)
-	if err != nil {
-		t.Fatalf("发送请求失败: %v", err)
-	}
-	defer resp.Body.Close()
-
-	t.Logf("响应状态码: %d", resp.StatusCode)
-
-	if resp.StatusCode != http.StatusOK {
-		body, _ := io.ReadAll(resp.Body)
-		t.Fatalf("请求失败,状态码: %d, 响应: %s", resp.StatusCode, string(body))
-	}
-
-	// 读取完整的响应内容
-	body, err := io.ReadAll(resp.Body)
-	if err != nil {
-		t.Fatalf("读取响应失败: %v", err)
-	}
-
-	// 解析JSON响应
-	var response Qwen3ChatResponse
-	if err := json.Unmarshal(body, &response); err != nil {
-		t.Fatalf("解析JSON响应失败: %v", err)
-	}
-
-	t.Logf("✅ Token计算规则查询成功!")
-	t.Logf("千问模型的回复:")
-	t.Logf("==========================================")
-	t.Logf("%s", response.Choices[0].Message.Content)
-	t.Logf("==========================================")
-}
-
-// TestQwenTokenLimit 测试千问模型的具体token限制
-func TestQwenTokenLimit(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.Qwen3BaseURL
-	model := config.Qwen3Model
-
-	// 构建询问token限制的请求
-	request := Qwen3ChatRequest{
-		Model:  model,
-		Stream: false,
-		Messages: []ChatMessage{
-			{
-				Role:    "user",
-				Content: "请告诉我你的最大输入token限制是多少?包括输入和输出的总限制。",
-			},
-		},
-	}
-
-	jsonData, err := json.Marshal(request)
-	if err != nil {
-		t.Fatalf("序列化请求失败: %v", err)
-	}
-
-	t.Logf("发送的请求: %s", string(jsonData))
-
-	// 发送POST请求
-	resp, err := http.Post(
-		fmt.Sprintf("%s/v1/chat/completions", baseURL),
-		"application/json",
-		bytes.NewBuffer(jsonData),
-	)
-	if err != nil {
-		t.Fatalf("发送请求失败: %v", err)
-	}
-	defer resp.Body.Close()
-
-	t.Logf("响应状态码: %d", resp.StatusCode)
-
-	if resp.StatusCode != http.StatusOK {
-		body, _ := io.ReadAll(resp.Body)
-		t.Fatalf("请求失败,状态码: %d, 响应: %s", resp.StatusCode, string(body))
-	}
-
-	// 读取完整的响应内容
-	body, err := io.ReadAll(resp.Body)
-	if err != nil {
-		t.Fatalf("读取响应失败: %v", err)
-	}
-
-	// 解析JSON响应
-	var response Qwen3ChatResponse
-	if err := json.Unmarshal(body, &response); err != nil {
-		t.Fatalf("解析JSON响应失败: %v", err)
-	}
-
-	t.Logf("✅ Token限制查询成功!")
-	t.Logf("千问模型的回复:")
-	t.Logf("==========================================")
-	t.Logf("%s", response.Choices[0].Message.Content)
-	t.Logf("==========================================")
-}
-
-// TestQwenTokenEstimation 测试具体的token估算
-func TestQwenTokenEstimation(t *testing.T) {
-	// 加载测试配置
-	config := LoadConfig()
-	baseURL := config.Qwen3BaseURL
-	model := config.Qwen3Model
-
-	// 测试文本
-	testTexts := []string{
-		"你好世界",                      // 4个中文字符
-		"Hello World",               // 11个英文字符
-		"你好,Hello World!",           // 混合文本
-		`{"name": "张三", "age": 25}`, // JSON格式
-		"这是一个测试\n包含换行符\t和制表符", // 包含特殊字符
-	}
-
-	for i, testText := range testTexts {
-		t.Run(fmt.Sprintf("测试文本%d", i+1), func(t *testing.T) {
-			request := Qwen3ChatRequest{
-				Model:  model,
-				Stream: false,
-				Messages: []ChatMessage{
-					{
-						Role:    "user",
-						Content: fmt.Sprintf("请告诉我这段文本有多少个token:\"%s\"。请只回答数字,不要其他解释。", testText),
-					},
-				},
-			}
-
-			jsonData, err := json.Marshal(request)
-			if err != nil {
-				t.Fatalf("序列化请求失败: %v", err)
-			}
-
-			// 发送POST请求
-			resp, err := http.Post(
-				fmt.Sprintf("%s/v1/chat/completions", baseURL),
-				"application/json",
-				bytes.NewBuffer(jsonData),
-			)
-			if err != nil {
-				t.Fatalf("发送请求失败: %v", err)
-			}
-			defer resp.Body.Close()
-
-			if resp.StatusCode != http.StatusOK {
-				body, _ := io.ReadAll(resp.Body)
-				t.Fatalf("请求失败,状态码: %d, 响应: %s", resp.StatusCode, string(body))
-			}
-
-			// 读取完整的响应内容
-			body, err := io.ReadAll(resp.Body)
-			if err != nil {
-				t.Fatalf("读取响应失败: %v", err)
-			}
-
-			// 解析JSON响应
-			var response Qwen3ChatResponse
-			if err := json.Unmarshal(body, &response); err != nil {
-				t.Fatalf("解析JSON响应失败: %v", err)
-			}
-
-			t.Logf("测试文本: \"%s\"", testText)
-			t.Logf("字符长度: %d", len(testText))
-			t.Logf("千问估算token数: %s", response.Choices[0].Message.Content)
-			t.Logf("---")
-		})
-	}
-}
-
-
-
-
-
-
-
-

+ 0 - 34
shudao-go-backend/tests/token_test.go

@@ -1,34 +0,0 @@
-package tests
-
-import (
-	"fmt"
-	"shudao-chat-go/utils"
-	"testing"
-)
-
-// TestTokenVerification 测试token验证功能
-func TestTokenVerification(t *testing.T) {
-	// 使用用户提供的示例token
-	token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3ZDMzZDgwYy1lZjI4LTQ3NWQtOWJmYi0wMmVjZWU0MjNiYjAiLCJ0eXBlIjoicmVmcmVzaCIsImFjY291bnRJRCI6ImFpenNjczE1ODIiLCJpZCI6NzA0MzAsIm5hbWUiOiJBSVx1NTJhOVx1NjI0Ylx1NmQ0Ylx1OGJkNTEiLCJ1c2VyQ29kZSI6Ik43MTE0MDg5IiwiY29udGFjdE51bWJlciI6IjE3ODAwMDAwMDAxIiwiaWF0IjoxNzYyMzM0OTMxLCJleHAiOjE3NjI2ODA1MzF9.Rm5B582-QTcjdHUyNf1SLpB3lGnX0XrvIEWsjXsMjmo"
-
-	fmt.Println("\n========== Token验证测试 ==========")
-	fmt.Printf("测试Token: %s\n\n", token)
-
-	userInfo, err := utils.VerifyToken(token)
-	if err != nil {
-		t.Errorf("Token验证失败: %v", err)
-		return
-	}
-
-	fmt.Println("\n✅ 验证成功!用户信息:")
-	fmt.Printf("   - AccountID: %s\n", userInfo.AccountID)
-	fmt.Printf("   - ID: %d\n", userInfo.ID)
-	fmt.Printf("   - Name: %s\n", userInfo.Name)
-	fmt.Printf("   - UserCode: %s\n", userInfo.UserCode)
-	fmt.Printf("   - ContactNumber: %s\n", userInfo.ContactNumber)
-	fmt.Printf("   - TokenType: %s\n", userInfo.TokenType)
-	fmt.Printf("   - Exp: %d\n", userInfo.Exp)
-	fmt.Printf("   - Iat: %d\n", userInfo.Iat)
-	fmt.Println("=====================================")
-}
-

+ 0 - 88
shudao-go-backend/utils/auth_middleware.go

@@ -1,88 +0,0 @@
-package utils
-
-import (
-	"fmt"
-	"strings"
-
-	"github.com/beego/beego/v2/server/web/context"
-)
-
-// 不需要认证的路径
-var skipPaths = []string{
-	"/stream-test",
-	"/simple-stream-test",
-	"/stream-chat-with-db-test",
-	"/assets/",
-	"/static/",
-	"/src/",
-	"/apiv1/oss/parse",
-	"/apiv1/auth/local_login",
-	"/apiv1/recommend_question",
-}
-
-// AuthMiddleware Token认证中间件
-func AuthMiddleware(ctx *context.Context) {
-	path := ctx.Request.URL.Path
-
-	// 跳过根路径
-	if path == "/" {
-		return
-	}
-
-	// 检查跳过路径
-	for _, skip := range skipPaths {
-		if path == skip || strings.HasPrefix(path, skip) {
-			return
-		}
-	}
-
-	// 仅对API请求验证token
-	if !strings.HasPrefix(path, "/apiv1") {
-		return
-	}
-
-	// 提取token
-	token := extractToken(ctx)
-	if token == "" {
-		ctx.Output.SetStatus(401)
-		ctx.Output.JSON(map[string]interface{}{
-			"statusCode": 401,
-			"msg":        "未提供认证token",
-		}, false, false)
-		return
-	}
-
-	// 优先验证本地token
-	if localClaims, err := VerifyLocalToken(token); err == nil && localClaims != nil {
-		ctx.Input.SetData("userInfo", ConvertLocalClaimsToTokenUserInfo(localClaims))
-		return
-	}
-
-	// 统一认证token验证
-	userInfo, err := VerifyToken(token)
-	if err != nil {
-		ctx.Output.SetStatus(401)
-		ctx.Output.JSON(map[string]interface{}{
-			"statusCode": 401,
-			"msg":        fmt.Sprintf("token验证失败: %v", err),
-		}, false, false)
-		return
-	}
-
-	ctx.Input.SetData("userInfo", userInfo)
-}
-
-// extractToken 从请求头提取token
-func extractToken(ctx *context.Context) string {
-	token := ctx.Input.Header("token")
-	if token == "" {
-		token = ctx.Input.Header("Token")
-	}
-	if token == "" {
-		token = ctx.Input.Header("Authorization")
-		if strings.HasPrefix(token, "Bearer ") {
-			token = token[7:]
-		}
-	}
-	return token
-}

+ 0 - 92
shudao-go-backend/utils/config.go

@@ -1,92 +0,0 @@
-package utils
-
-import (
-	"fmt"
-	"strings"
-
-	beego "github.com/beego/beego/v2/server/web"
-)
-
-// GetConfigString 获取字符串类型配置,如果不存在返回默认值
-func GetConfigString(key string, defaultValue string) string {
-	value, err := beego.AppConfig.String(key)
-	if err != nil || value == "" {
-		return defaultValue
-	}
-	return value
-}
-
-// MustGetConfigString 获取字符串类型配置,如果不存在则panic
-func MustGetConfigString(key string) string {
-	value, err := beego.AppConfig.String(key)
-	if err != nil || value == "" {
-		panic(fmt.Sprintf("配置项 %s 未设置或为空,请检查 conf/app.conf 文件", key))
-	}
-	return value
-}
-
-// GetConfigInt 获取整数类型配置,如果不存在返回默认值
-func GetConfigInt(key string, defaultValue int) int {
-	value, err := beego.AppConfig.Int(key)
-	if err != nil {
-		return defaultValue
-	}
-	return value
-}
-
-// GetProxyURL 生成OSS代理URL(加密版本)
-// 返回相对路径,让前端自动使用当前域名
-func GetProxyURL(originalURL string) string {
-	if originalURL == "" {
-		return ""
-	}
-
-	encryptedURL, err := EncryptURL(originalURL)
-	if err != nil {
-		return ""
-	}
-
-	return "/apiv1/oss/parse/?url=" + encryptedURL
-}
-
-// GetMySQLConfig 获取MySQL配置
-func GetMySQLConfig() map[string]string {
-	return map[string]string{
-		"user": MustGetConfigString("mysqluser"),
-		"pass": MustGetConfigString("mysqlpass"),
-		"urls": MustGetConfigString("mysqlurls"),
-		"port": MustGetConfigString("mysqlhttpport"),
-		"db":   MustGetConfigString("mysqldb"),
-	}
-}
-
-// GetOSSConfig 获取OSS配置
-func GetOSSConfig() map[string]string {
-	return map[string]string{
-		"endpoint":    MustGetConfigString("oss_endpoint"),
-		"access_key":  MustGetConfigString("oss_access_key_id"),
-		"secret_key":  MustGetConfigString("oss_access_key_secret"),
-		"bucket":      MustGetConfigString("oss_bucket"),
-		"encrypt_key": GetConfigString("oss_parse_encrypt_key", ""),
-	}
-}
-
-// GetYOLOBaseURL 获取YOLO服务地址
-func GetYOLOBaseURL() string {
-	return strings.TrimRight(MustGetConfigString("yolo_base_url"), "/")
-}
-
-// GetAuthAPIURL 获取认证服务地址
-func GetAuthAPIURL() string {
-	return strings.TrimRight(MustGetConfigString("auth_api_url"), "/")
-}
-
-// GetKnowledgeSearchURL 获取知识库搜索地址
-func GetKnowledgeSearchURL() string {
-	return MustGetConfigString("knowledge_search_url")
-}
-
-// GetDifyWorkflowURL 获取Dify工作流地址
-func GetDifyWorkflowURL() string {
-	return MustGetConfigString("dify_workflow_url")
-}

+ 0 - 74
shudao-go-backend/utils/crypto.go

@@ -1,74 +0,0 @@
-package utils
-
-import (
-	"crypto/aes"
-	"crypto/cipher"
-	"crypto/rand"
-	"encoding/base64"
-	"fmt"
-	"io"
-
-	"github.com/beego/beego/v2/server/web"
-)
-
-// GetEncryptKey 获取加密密钥
-func GetEncryptKey() string {
-	return web.AppConfig.DefaultString("OSS_PARSE_ENCRYPT_KEY", "jgqwk7sirqlz2602")
-}
-
-// EncryptURL 加密URL
-func EncryptURL(plainURL string) (string, error) {
-	if plainURL == "" {
-		return "", nil
-	}
-
-	key := []byte(GetEncryptKey())
-	plaintext := []byte(plainURL)
-
-	block, err := aes.NewCipher(key)
-	if err != nil {
-		return "", fmt.Errorf("创建加密器失败: %v", err)
-	}
-
-	ciphertext := make([]byte, aes.BlockSize+len(plaintext))
-	iv := ciphertext[:aes.BlockSize]
-	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
-		return "", fmt.Errorf("生成IV失败: %v", err)
-	}
-
-	stream := cipher.NewCFBEncrypter(block, iv)
-	stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)
-
-	return base64.URLEncoding.EncodeToString(ciphertext), nil
-}
-
-// DecryptURL 解密URL
-func DecryptURL(encryptedURL string) (string, error) {
-	if encryptedURL == "" {
-		return "", nil
-	}
-
-	key := []byte(GetEncryptKey())
-
-	ciphertext, err := base64.URLEncoding.DecodeString(encryptedURL)
-	if err != nil {
-		return "", fmt.Errorf("Base64解码失败: %v", err)
-	}
-
-	block, err := aes.NewCipher(key)
-	if err != nil {
-		return "", fmt.Errorf("创建解密器失败: %v", err)
-	}
-
-	if len(ciphertext) < aes.BlockSize {
-		return "", fmt.Errorf("密文长度不足")
-	}
-
-	iv := ciphertext[:aes.BlockSize]
-	ciphertext = ciphertext[aes.BlockSize:]
-
-	stream := cipher.NewCFBDecrypter(block, iv)
-	stream.XORKeyStream(ciphertext, ciphertext)
-
-	return string(ciphertext), nil
-}

+ 0 - 101
shudao-go-backend/utils/jwt.go

@@ -1,101 +0,0 @@
-package utils
-
-import (
-	"fmt"
-	"time"
-
-	"github.com/beego/beego/v2/server/web"
-	"github.com/golang-jwt/jwt/v5"
-)
-
-// LocalTokenClaims 本地JWT token的claims结构
-type LocalTokenClaims struct {
-	UserID    uint   `json:"user_id"`
-	Username  string `json:"username"`
-	Role      string `json:"role"`
-	TokenType string `json:"token_type"` // "local" 标识为本地token
-	jwt.RegisteredClaims
-}
-
-// GenerateLocalToken 生成本地JWT token
-func GenerateLocalToken(userID uint, username string, role string) (string, error) {
-	// 从配置读取JWT密钥
-	jwtSecret, err := web.AppConfig.String("jwt_secret")
-	if err != nil || jwtSecret == "" {
-		jwtSecret = "default-secret-key-please-change-in-production" // 默认密钥
-	}
-
-	// 设置token有效期为24小时
-	expirationTime := time.Now().Add(24 * time.Hour)
-
-	// 创建claims
-	claims := &LocalTokenClaims{
-		UserID:    userID,
-		Username:  username,
-		Role:      role,
-		TokenType: "local",
-		RegisteredClaims: jwt.RegisteredClaims{
-			ExpiresAt: jwt.NewNumericDate(expirationTime),
-			IssuedAt:  jwt.NewNumericDate(time.Now()),
-			Issuer:    "shudao-local-auth",
-		},
-	}
-
-	// 创建token
-	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
-
-	// 签名token
-	tokenString, err := token.SignedString([]byte(jwtSecret))
-	if err != nil {
-		return "", fmt.Errorf("生成token失败: %v", err)
-	}
-
-	return tokenString, nil
-}
-
-// VerifyLocalToken 验证本地JWT token并返回claims
-func VerifyLocalToken(tokenString string) (*LocalTokenClaims, error) {
-	// 从配置读取JWT密钥
-	jwtSecret, err := web.AppConfig.String("jwt_secret")
-	if err != nil || jwtSecret == "" {
-		jwtSecret = "default-secret-key-please-change-in-production"
-	}
-
-	// 解析token
-	token, err := jwt.ParseWithClaims(tokenString, &LocalTokenClaims{}, func(token *jwt.Token) (interface{}, error) {
-		// 验证签名方法
-		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
-			return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
-		}
-		return []byte(jwtSecret), nil
-	})
-
-	if err != nil {
-		return nil, fmt.Errorf("解析token失败: %v", err)
-	}
-
-	// 验证token有效性
-	if claims, ok := token.Claims.(*LocalTokenClaims); ok && token.Valid {
-		// 检查是否为本地token
-		if claims.TokenType != "local" {
-			return nil, fmt.Errorf("不是本地token")
-		}
-		return claims, nil
-	}
-
-	return nil, fmt.Errorf("token无效")
-}
-
-// ConvertLocalClaimsToTokenUserInfo 将本地token claims转换为TokenUserInfo
-func ConvertLocalClaimsToTokenUserInfo(claims *LocalTokenClaims) *TokenUserInfo {
-	return &TokenUserInfo{
-		AccountID:     fmt.Sprintf("local_%d", claims.UserID),
-		ID:            int64(claims.UserID),
-		Name:          claims.Username,
-		UserCode:      claims.Username,
-		ContactNumber: "",
-		TokenType:     "local",
-		Exp:           claims.ExpiresAt.Unix(),
-		Iat:           claims.IssuedAt.Unix(),
-	}
-}

+ 0 - 606
shudao-go-backend/utils/prompt_builder.go

@@ -1,606 +0,0 @@
-package utils
-
-import (
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"regexp"
-	"strings"
-)
-
-// ExamPromptQuestionType represents the structure of question type configuration passed from frontend
-type ExamPromptQuestionType struct {
-	Name             string `json:"name"`
-	RomanNumeral     string `json:"romanNumeral"`
-	QuestionCount    int    `json:"questionCount"`
-	ScorePerQuestion int    `json:"scorePerQuestion"`
-}
-
-// ExamPromptRequest represents the payload required to build an exam prompt
-type ExamPromptRequest struct {
-	Mode          string                   `json:"mode"`
-	Client        string                   `json:"client"`
-	ProjectType   string                   `json:"projectType"`
-	ExamTitle     string                   `json:"examTitle"`
-	TotalScore    int                      `json:"totalScore"`
-	QuestionTypes []ExamPromptQuestionType `json:"questionTypes"`
-	PPTContent    string                   `json:"pptContent"`
-}
-
-// SingleQuestionPromptRequest represents the payload required to build a single-question prompt
-type SingleQuestionPromptRequest struct {
-	Client           string          `json:"client"`
-	SectionType      string          `json:"sectionType"`
-	QuestionIndex    int             `json:"questionIndex"`
-	ProjectType      string          `json:"projectType"`
-	QuestionType     string          `json:"questionType"`
-	ScorePerQuestion int             `json:"scorePerQuestion"`
-	CurrentQuestion  json.RawMessage `json:"currentQuestion"`
-}
-
-type questionStats struct {
-	Score int
-	Count int
-}
-
-// BuildExamPrompt generates the exam prompt text based on mode (ai/ppt) and client (pc/mobile)
-func BuildExamPrompt(req ExamPromptRequest) (string, error) {
-	mode := strings.ToLower(strings.TrimSpace(req.Mode))
-	if mode == "" {
-		mode = "ai"
-	}
-
-	client := strings.ToLower(strings.TrimSpace(req.Client))
-	if client == "" {
-		client = "pc"
-	}
-
-	switch client {
-	case "mobile":
-		if mode == "ppt" {
-			return buildMobilePPTExamPrompt(req), nil
-		}
-		return buildMobileAIExamPrompt(req), nil
-	default:
-		if mode == "ppt" {
-			return buildPcPPTExamPrompt(req), nil
-		}
-		return buildPcAIExamPrompt(req), nil
-	}
-}
-
-// BuildSingleQuestionPrompt builds prompts for regenerating a single question
-func BuildSingleQuestionPrompt(req SingleQuestionPromptRequest) (string, error) {
-	client := strings.ToLower(strings.TrimSpace(req.Client))
-	if client == "" {
-		client = "pc"
-	}
-	sectionType := normalizeSectionType(req.SectionType)
-	projectType := fallbackString(req.ProjectType, "工程")
-	questionType := fallbackString(req.QuestionType, questionTypeFromSection(sectionType))
-	scorePerQuestion := safePositive(req.ScorePerQuestion, 1)
-	questionIndex := req.QuestionIndex + 1
-	currentQuestion := formatCurrentQuestionJSON(req.CurrentQuestion)
-
-	switch client {
-	case "mobile":
-		return buildMobileSingleQuestionPrompt(projectType, questionType, sectionType, questionIndex, scorePerQuestion, currentQuestion), nil
-	default:
-		return buildPcSingleQuestionPrompt(projectType, questionType, sectionType, questionIndex, scorePerQuestion, currentQuestion), nil
-	}
-}
-
-func buildPcAIExamPrompt(req ExamPromptRequest) string {
-	projectType := fallbackString(req.ProjectType, "工程")
-	examTitle := fallbackString(req.ExamTitle, "考试试卷")
-	questionConfig := buildPcQuestionConfig(req.QuestionTypes)
-	jsonTemplate := buildPcJSONTemplate(req, examTitle)
-
-	return fmt.Sprintf(`请为%[1]s工程生成一份名为"%[2]s"的考试试卷:
-
-试卷总分:%[3]d分
-
-题型配置:
-%[4]s
-请严格按照以下JSON格式返回试卷内容,不要包含任何其他文字:
-
-%[5]s
-
-注意:
-1. 所有题目内容必须与%[1]s工程相关
-2. 题目难度适中,符合考试要求
-3. 严格按照JSON格式返回,不要有多余字符
-4. 单选题和判断题的选项要合理
-5. 多选题至少要有2个正确答案
-6. 简答题要提供清晰的答题要点
-7. 必须为每道题设置正确答案:
-   - 单选题:selectedAnswer字段填写正确的选项(A/B/C/D)
-   - 判断题:selectedAnswer字段填写"正确"或"错误"
-   - 多选题:selectedAnswers数组包含所有正确答案选项
-8. 简答题答案字数不超过500字
-`, projectType, examTitle, safeTotalScore(req.TotalScore), questionConfig, jsonTemplate)
-}
-
-func buildPcPPTExamPrompt(req ExamPromptRequest) string {
-	contentText := sanitizePPTContent(req.PPTContent)
-	if contentText == "" {
-		contentText = fallbackString(req.ExamTitle, "PPT内容")
-	}
-	examTitle := fallbackString(req.ExamTitle, "考试试卷")
-	questionConfig := buildPcQuestionConfig(req.QuestionTypes)
-	jsonTemplate := buildPcJSONTemplate(req, examTitle)
-
-	return fmt.Sprintf(`请根据标题为%[1]s生成一份考试试卷:
-
-试卷总分:%[2]d分
-
-题型配置:
-%[3]s
-请严格按照以下JSON格式返回试卷内容,不要包含任何其他文字:
-
-%[4]s
-
-注意:
-1. 所有题目内容必须基于提供的内容生成,与%[1]s工程相关
-2. 题目难度适中,符合考试要求
-3. 严格按照JSON格式返回,不要有多余字符
-4. 单选题和判断题的选项要合理
-5. 多选题至少要有2个正确答案
-6. 简答题要提供清晰的答题要点
-7. 必须为每道题设置正确答案:
-   - 单选题:selectedAnswer字段填写正确的选项(A/B/C/D)
-   - 判断题:selectedAnswer字段填写"正确"或"错误"
-   - 多选题:selectedAnswers数组包含所有正确答案选项
-8. 简答题答案字数不超过500字
-9. 确保题目内容与提供的内容高度相关
-`, contentText, safeTotalScore(req.TotalScore), questionConfig, jsonTemplate)
-}
-
-func buildMobileAIExamPrompt(req ExamPromptRequest) string {
-	projectType := fallbackString(req.ProjectType, "工程")
-	examTitle := fallbackString(req.ExamTitle, "考试试卷")
-	questionConfig := buildMobileQuestionConfig(req.QuestionTypes)
-	jsonTemplate := buildMobileJSONTemplate(req, examTitle)
-
-	return fmt.Sprintf(`请生成一份%[1]s工程施工技术考核试卷:
-
-试卷要求:
-- 试卷名称:%[2]s
-- 试卷总分:%[3]d分
-- 题型配置:
-%[4]s
-
-内容要求:
-1. %[1]s工程施工技术的核心知识点
-2. 施工工艺流程和质量控制要点
-3. 安全操作规程和事故预防措施
-4. 相关法规标准和验收规范
-
-请严格按照以下JSON格式返回试卷内容,不要包含任何其他文字:
-
-请在questions数组中生成具体、完整的题目内容!
-- 单选题需要:text题目内容、options选项数组[{"key":"A","text":"选项1"},{"key":"B","text":"选项2"},{"key":"C","text":"选项3"},{"key":"D","text":"选项4"}]、selectedAnswer正确答案
-- 判断题需要:text题目内容、selectedAnswer正确答案(正确/错误)
-- 多选题需要:text题目内容、options选项数组[{"key":"A","text":"选项1"},{"key":"B","text":"选项2"},{"key":"C","text":"选项3"},{"key":"D","text":"选项4"}]、selectedAnswers正确答案数组
-- 简答题需要:text题目内容、outline {keyFactors:关键要点, measures:具体措施}
-
-%[5]s`, projectType, examTitle, safeTotalScore(req.TotalScore), questionConfig, jsonTemplate)
-}
-
-func buildMobilePPTExamPrompt(req ExamPromptRequest) string {
-	examTitle := fallbackString(req.ExamTitle, "考试试卷")
-	pptContent := fallbackString(strings.TrimSpace(req.PPTContent), "PPT内容")
-	questionConfig := buildMobileQuestionConfig(req.QuestionTypes)
-	jsonTemplate := buildMobileJSONTemplate(req, examTitle+" - PPT培训考试")
-
-	return fmt.Sprintf(`请基于以下PPT培训内容生成考试试卷:
-
-培训主题:%[1]s
-PPT内容:%[2]s
-
-请生成一份包含以下题型的考试试卷:
-%[3]s
-
-试卷总分:%[4]d分
-
-要求:
-1. 重点考查PPT中的核心知识点
-2. 涵盖主要概念、工艺流程、操作规程等关键内容
-3. 题目难度适中,具有实际应用价值
-4. 确保答案准确性和专业性
-
-请严格按照以下JSON格式返回试卷内容,不要包含任何其他文字:
-
-请在questions数组中生成具体、完整的题目内容!
-- 单选题需要:text题目内容、options选项数组[{"key":"A","text":"选项1"},{"key":"B","text":"选项2"},{"key":"C","text":"选项3"},{"key":"D","text":"选项4"}]、selectedAnswer正确答案
-- 判断题需要:text题目内容、selectedAnswer正确答案(正确/错误)
-- 多选题需要:text题目内容、options选项数组[{"key":"A","text":"选项1"},{"key":"B","text":"选项2"},{"key":"C","text":"选项3"},{"key":"D","text":"选项4"}]、selectedAnswers正确答案数组
-- 简答题需要:text题目内容、outline {keyFactors:关键要点, measures:具体措施}
-
-%[5]s`, examTitle, pptContent, questionConfig, safeTotalScore(req.TotalScore), jsonTemplate)
-}
-
-func buildPcQuestionConfig(questionTypes []ExamPromptQuestionType) string {
-	if len(questionTypes) == 0 {
-		return "暂无题型配置\n"
-	}
-
-	var builder strings.Builder
-	for _, qt := range questionTypes {
-		roman := strings.TrimSpace(qt.RomanNumeral)
-		if roman == "" {
-			roman = "•"
-		}
-		score := safePositive(qt.ScorePerQuestion, 1)
-		count := safePositive(qt.QuestionCount, 1)
-		builder.WriteString(fmt.Sprintf("%s、%s:%d题,每题%d分,共%d分\n", roman, qt.Name, count, score, count*score))
-	}
-	return builder.String()
-}
-
-func buildMobileQuestionConfig(questionTypes []ExamPromptQuestionType) string {
-	if len(questionTypes) == 0 {
-		return "暂无题型配置\n"
-	}
-
-	var builder strings.Builder
-	for _, qt := range questionTypes {
-		score := safePositive(qt.ScorePerQuestion, 1)
-		count := safePositive(qt.QuestionCount, 1)
-		builder.WriteString(fmt.Sprintf("%s:%d题,每题%d分,共%d分\n", qt.Name, count, score, count*score))
-	}
-	return builder.String()
-}
-
-func buildPcJSONTemplate(req ExamPromptRequest, title string) string {
-	totalQuestions := sumQuestionCount(req.QuestionTypes)
-	single := statsByName(req.QuestionTypes, "单选题", 2, 15)
-	judge := statsByName(req.QuestionTypes, "判断题", 2, 10)
-	multiple := statsByName(req.QuestionTypes, "多选题", 3, 10)
-	short := statsByName(req.QuestionTypes, "简答题", 10, 2)
-
-	return fmt.Sprintf(`{
-  "title": "%[1]s",
-  "totalScore": %[2]d,
-  "totalQuestions": %[3]d,
-  "singleChoice": {
-    "scorePerQuestion": %[4]d,
-    "totalScore": %[5]d,
-    "count": %[6]d,
-    "questions": [
-      {
-        "text": "题目内容",
-        "options": [
-          {"key": "A", "text": "选项A内容"},
-          {"key": "B", "text": "选项B内容"},
-          {"key": "C", "text": "选项C内容"},
-          {"key": "D", "text": "选项D内容"}
-        ],
-        "selectedAnswer": "正确答案选项(A/B/C/D)"
-      }
-    ]
-  },
-  "judge": {
-    "scorePerQuestion": %[7]d,
-    "totalScore": %[8]d,
-    "count": %[9]d,
-    "questions": [
-      {
-        "text": "题目内容",
-        "selectedAnswer": "正确答案(正确/错误)"
-      }
-    ]
-  },
-  "multiple": {
-    "scorePerQuestion": %[10]d,
-    "totalScore": %[11]d,
-    "count": %[12]d,
-    "questions": [
-      {
-        "text": "题目内容",
-        "options": [
-          {"key": "A", "text": "选项A内容"},
-          {"key": "B", "text": "选项B内容"},
-          {"key": "C", "text": "选项C内容"},
-          {"key": "D", "text": "选项D内容"}
-        ],
-        "selectedAnswers": ["正确答案选项1", "正确答案选项2"]
-      }
-    ]
-  },
-  "short": {
-    "scorePerQuestion": %[13]d,
-    "totalScore": %[14]d,
-    "count": %[15]d,
-    "questions": [
-      {
-        "text": "题目内容",
-        "outline": {
-          "keyFactors": "答题要点、关键因素、示例答案"
-        }
-      }
-    ]
-  }
-}`, title, safeTotalScore(req.TotalScore), totalQuestions,
-		single.Score, single.Score*single.Count, single.Count,
-		judge.Score, judge.Score*judge.Count, judge.Count,
-		multiple.Score, multiple.Score*multiple.Count, multiple.Count,
-		short.Score, short.Score*short.Count, short.Count)
-}
-
-func buildMobileJSONTemplate(req ExamPromptRequest, title string) string {
-	totalQuestions := sumQuestionCount(req.QuestionTypes)
-	q0 := statsByIndex(req.QuestionTypes, 0, 2, 8)
-	q1 := statsByIndex(req.QuestionTypes, 1, 2, 5)
-	q2 := statsByIndex(req.QuestionTypes, 2, 3, 5)
-	q3 := statsByIndex(req.QuestionTypes, 3, 10, 2)
-
-	return fmt.Sprintf(`{
-  "title": "%[1]s",
-  "totalScore": %[2]d,
-  "totalQuestions": %[3]d,
-  "singleChoice": {
-    "scorePerQuestion": %[4]d,
-    "totalScore": %[5]d,
-    "count": %[6]d,
-    "questions": []
-  },
-  "judge": {
-    "scorePerQuestion": %[7]d,
-    "totalScore": %[8]d,
-    "count": %[9]d,
-    "questions": []
-  },
-  "multiple": {
-    "scorePerQuestion": %[10]d,
-    "totalScore": %[11]d,
-    "count": %[12]d,
-    "questions": []
-  },
-  "short": {
-    "scorePerQuestion": %[13]d,
-    "totalScore": %[14]d,
-    "count": %[15]d,
-    "questions": []
-  }
-}`, title, safeTotalScore(req.TotalScore), totalQuestions,
-		q0.Score, q0.Score*q0.Count, q0.Count,
-		q1.Score, q1.Score*q1.Count, q1.Count,
-		q2.Score, q2.Score*q2.Count, q2.Count,
-		q3.Score, q3.Score*q3.Count, q3.Count)
-}
-
-func statsByName(questionTypes []ExamPromptQuestionType, name string, defaultScore, defaultCount int) questionStats {
-	for _, qt := range questionTypes {
-		if strings.TrimSpace(qt.Name) == name {
-			return questionStats{
-				Score: safePositive(qt.ScorePerQuestion, defaultScore),
-				Count: safePositive(qt.QuestionCount, defaultCount),
-			}
-		}
-	}
-	return questionStats{Score: defaultScore, Count: defaultCount}
-}
-
-func statsByIndex(questionTypes []ExamPromptQuestionType, index int, defaultScore, defaultCount int) questionStats {
-	if index >= 0 && index < len(questionTypes) {
-		qt := questionTypes[index]
-		return questionStats{
-			Score: safePositive(qt.ScorePerQuestion, defaultScore),
-			Count: safePositive(qt.QuestionCount, defaultCount),
-		}
-	}
-	return questionStats{Score: defaultScore, Count: defaultCount}
-}
-
-func sumQuestionCount(questionTypes []ExamPromptQuestionType) int {
-	total := 0
-	for _, qt := range questionTypes {
-		total += safePositive(qt.QuestionCount, 0)
-	}
-	return total
-}
-
-func buildPcSingleQuestionPrompt(projectType, questionType, sectionType string, questionIndex, score int, currentQuestion string) string {
-	format := buildPcSingleQuestionFormat(sectionType)
-	return fmt.Sprintf(`请基于以下%[1]s工程的%[2]s题目,重新生成一道相似主题的题目,要求如下:
-
-当前题目参考:
-%[3]s
-
-题目类型:%[2]s
-每题分值:%[4]d分
-题目序号:第%[5]d题
-
-请严格按照以下JSON格式返回,不要包含任何其他文字:
-
-%[6]s
-
-注意:
-1. 新题目必须与当前题目保持相似的主题和难度
-2. 题目内容必须与%[1]s工程相关
-3. 题目难度适中,符合考试要求
-4. 严格按照JSON格式返回,不要有多余字符
-5. 单选题和判断题的选项要合理
-6. 多选题至少要有2个正确答案
-7. 简答题要提供清晰的答题要点
-8. 必须为每道题设置正确答案:
-   - 单选题:selectedAnswer字段填写正确的选项(A/B/C/D)
-   - 判断题:selectedAnswer字段填写"正确"或"错误"
-   - 多选题:selectedAnswers数组包含所有正确答案选项
-9. 简答题答案字数不超过500字
-10. 新题目应该是当前题目的变体,保持主题一致性但内容要有所变化`, projectType, questionType, currentQuestion, score, questionIndex, format)
-}
-
-func buildMobileSingleQuestionPrompt(projectType, questionType, sectionType string, questionIndex, score int, currentQuestion string) string {
-	format := buildMobileSingleQuestionFormat(sectionType)
-	return fmt.Sprintf(`请基于以下%[1]s工程的%[2]s题目,重新生成一道相似主题的题目:
-
-当前题目参考:
-%[3]s
-
-题目类型:%[2]s
-每题分值:%[4]d分
-题目序号:第%[5]d题
-
-请严格按照以下JSON格式返回,不要包含任何其他文字:
-
-%[6]s`, projectType, questionType, currentQuestion, score, questionIndex, format)
-}
-
-func buildPcSingleQuestionFormat(sectionType string) string {
-	switch sectionType {
-	case "single":
-		return `{
-  "text": "题目内容",
-  "options": [
-    {"key": "A", "text": "选项A内容"},
-    {"key": "B", "text": "选项B内容"},
-    {"key": "C", "text": "选项C内容"},
-    {"key": "D", "text": "选项D内容"}
-  ],
-  "selectedAnswer": "正确答案选项(A/B/C/D)"
-}`
-	case "judge":
-		return `{
-  "text": "题目内容",
-  "selectedAnswer": "正确答案(正确/错误)"
-}`
-	case "multiple":
-		return `{
-  "text": "题目内容",
-  "options": [
-    {"key": "A", "text": "选项A内容"},
-    {"key": "B", "text": "选项B内容"},
-    {"key": "C", "text": "选项C内容"},
-    {"key": "D", "text": "选项D内容"}
-  ],
-  "selectedAnswers": ["正确答案选项1", "正确答案选项2"]
-}`
-	case "short":
-		return `{
-  "text": "题目内容",
-  "outline": {
-    "keyFactors": "答题要点、关键因素、示例答案"
-  }
-}`
-	default:
-		return `{
-  "text": "题目内容"
-}`
-	}
-}
-
-func buildMobileSingleQuestionFormat(sectionType string) string {
-	switch sectionType {
-	case "single":
-		return `{
-  "text": "题目内容",
-  "options": [
-    {"key": "A", "text": "选项A"},
-    {"key": "B", "text": "选项B"},
-    {"key": "C", "text": "选项C"},
-    {"key": "D", "text": "选项D"}
-  ],
-  "selectedAnswer": "A"
-}`
-	case "judge":
-		return `{
-  "text": "题目内容",
-  "selectedAnswer": "正确"
-}`
-	case "multiple":
-		return `{
-  "text": "题目内容",
-  "options": [
-    {"key": "A", "text": "选项A"},
-    {"key": "B", "text": "选项B"},
-    {"key": "C", "text": "选项C"},
-    {"key": "D", "text": "选项D"}
-  ],
-  "selectedAnswers": ["A", "B"]
-}`
-	case "short":
-		return `{
-  "text": "题目内容",
-  "outline": {
-    "keyFactors": "关键要点内容",
-    "measures": "具体措施内容"
-  }
-}`
-	default:
-		return `{
-  "text": "题目内容"
-}`
-	}
-}
-
-func formatCurrentQuestionJSON(raw json.RawMessage) string {
-	if len(raw) == 0 {
-		return "无"
-	}
-	var buf bytes.Buffer
-	if err := json.Indent(&buf, raw, "", "  "); err != nil {
-		return string(raw)
-	}
-	return buf.String()
-}
-
-func questionTypeFromSection(sectionType string) string {
-	switch sectionType {
-	case "single":
-		return "单选题"
-	case "judge":
-		return "判断题"
-	case "multiple":
-		return "多选题"
-	case "short":
-		return "简答题"
-	default:
-		return "题目"
-	}
-}
-
-func normalizeSectionType(sectionType string) string {
-	switch strings.ToLower(strings.TrimSpace(sectionType)) {
-	case "single", "judge", "multiple", "short":
-		return strings.ToLower(strings.TrimSpace(sectionType))
-	default: 
-		return "single"
-	}
-}
-
-func sanitizePPTContent(content string) string {
-	text := strings.TrimSpace(content)
-	if text == "" {
-		return ""
-	}
-	if strings.Contains(text, "PPT文件信息") {
-		re := regexp.MustCompile(`(?s)提取的文本内容:\n(.*?)(\n\n|$)`)
-		if matches := re.FindStringSubmatch(text); len(matches) > 1 {
-			text = strings.TrimSpace(matches[1])
-		}
-	}
-	return text
-}
-
-func fallbackString(value string, fallback string) string {
-	if strings.TrimSpace(value) == "" {
-		return fallback
-	}
-	return value
-}
-
-func safePositive(value int, fallback int) int {
-	if value <= 0 {
-		return fallback
-	}
-	return value
-}
-
-func safeTotalScore(score int) int {
-	if score <= 0 {
-		return 100
-	}
-	return score
-}

+ 0 - 79
shudao-go-backend/utils/response.go

@@ -1,79 +0,0 @@
-package utils
-
-import (
-	"github.com/beego/beego/v2/server/web"
-)
-
-// Response 统一响应结构
-type Response struct {
-	StatusCode int         `json:"statusCode"`
-	Msg        string      `json:"msg"`
-	Data       interface{} `json:"data,omitempty"`
-}
-
-// BaseController 基础控制器,提供统一响应方法
-type BaseController struct {
-	web.Controller
-}
-
-// Success 成功响应
-func (c *BaseController) Success(data interface{}) {
-	c.Data["json"] = Response{
-		StatusCode: 200,
-		Msg:        "success",
-		Data:       data,
-	}
-	c.ServeJSON()
-}
-
-// SuccessMsg 成功响应(仅消息)
-func (c *BaseController) SuccessMsg(msg string) {
-	c.Data["json"] = Response{
-		StatusCode: 200,
-		Msg:        msg,
-	}
-	c.ServeJSON()
-}
-
-// Error 错误响应
-func (c *BaseController) Error(code int, msg string) {
-	c.Data["json"] = Response{
-		StatusCode: code,
-		Msg:        msg,
-	}
-	c.ServeJSON()
-}
-
-// ErrorWithData 错误响应(带数据)
-func (c *BaseController) ErrorWithData(code int, msg string, data interface{}) {
-	c.Data["json"] = Response{
-		StatusCode: code,
-		Msg:        msg,
-		Data:       data,
-	}
-	c.ServeJSON()
-}
-
-// GetUserID 从context获取用户ID,失败返回错误响应
-func (c *BaseController) GetUserID() (int, bool) {
-	userInfo, err := GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
-	if err != nil {
-		c.Error(401, "获取用户信息失败: "+err.Error())
-		return 0, false
-	}
-	userID := int(userInfo.ID)
-	if userID == 0 {
-		userID = 1
-	}
-	return userID, true
-}
-
-// GetUserInfo 从context获取完整用户信息
-func (c *BaseController) GetUserInfo() (*TokenUserInfo, bool) {
-	userInfo, err := GetUserInfoFromContext(c.Ctx.Input.GetData("userInfo"))
-	if err != nil {
-		c.Error(401, "获取用户信息失败: "+err.Error())
-		return nil, false
-	}
-	return userInfo, true
-}

+ 0 - 112
shudao-go-backend/utils/string_match.go

@@ -1,112 +0,0 @@
-package utils
-
-import (
-	"math"
-)
-
-// LevenshteinDistance 计算两个字符串的编辑距离
-func LevenshteinDistance(s1, s2 string) int {
-	r1, r2 := []rune(s1), []rune(s2)
-	rows := len(r1) + 1
-	cols := len(r2) + 1
-
-	d := make([][]int, rows)
-	for i := range d {
-		d[i] = make([]int, cols)
-	}
-
-	for i := 1; i < rows; i++ {
-		d[i][0] = i
-	}
-	for j := 1; j < cols; j++ {
-		d[0][j] = j
-	}
-
-	for i := 1; i < rows; i++ {
-		for j := 1; j < cols; j++ {
-			cost := 0
-			if r1[i-1] != r2[j-1] {
-				cost = 1
-			}
-			d[i][j] = min(
-				min(d[i-1][j]+1, d[i][j-1]+1),
-				d[i-1][j-1]+cost,
-			)
-		}
-	}
-
-	return d[rows-1][cols-1]
-}
-
-// StringSimilarity 计算字符串相似度 (0-1之间,1表示完全相同)
-func StringSimilarity(s1, s2 string) float64 {
-	if s1 == s2 {
-		return 1.0
-	}
-
-	maxLen := math.Max(float64(len([]rune(s1))), float64(len([]rune(s2))))
-	if maxLen == 0 {
-		return 1.0
-	}
-
-	distance := LevenshteinDistance(s1, s2)
-	return 1.0 - float64(distance)/maxLen
-}
-
-// FindBestMatch 在多个候选项中找到与目标字符串最相似的匹配
-func FindBestMatch(target string, candidates []string) (string, float64) {
-	if len(candidates) == 0 {
-		return "", 0.0
-	}
-
-	bestMatch := candidates[0]
-	bestScore := StringSimilarity(target, candidates[0])
-
-	for i := 1; i < len(candidates); i++ {
-		score := StringSimilarity(target, candidates[i])
-		if score > bestScore {
-			bestScore = score
-			bestMatch = candidates[i]
-		}
-	}
-
-	return bestMatch, bestScore
-}
-
-// MatchResult 匹配结果结构体
-type MatchResult struct {
-	Text  string  `json:"text"`
-	Score float64 `json:"score"`
-}
-
-// FindBestMatches 返回所有候选项的相似度分数,按分数降序排列
-func FindBestMatches(target string, candidates []string) []MatchResult {
-	results := make([]MatchResult, len(candidates))
-
-	for i, candidate := range candidates {
-		score := StringSimilarity(target, candidate)
-		results[i] = MatchResult{
-			Text:  candidate,
-			Score: score,
-		}
-	}
-
-	// 按分数降序排序
-	for i := 0; i < len(results)-1; i++ {
-		for j := i + 1; j < len(results); j++ {
-			if results[i].Score < results[j].Score {
-				results[i], results[j] = results[j], results[i]
-			}
-		}
-	}
-
-	return results
-}
-
-// min 辅助函数
-func min(a, b int) int {
-	if a < b {
-		return a
-	}
-	return b
-}

+ 0 - 107
shudao-go-backend/utils/token.go

@@ -1,107 +0,0 @@
-package utils
-
-import (
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"io"
-	"net/http"
-	"strings"
-	"time"
-)
-
-// TokenUserInfo 从token验证API返回的用户信息
-type TokenUserInfo struct {
-	AccountID     string `json:"accountID"`
-	ID            int64  `json:"id"`
-	Name          string `json:"name"`
-	UserCode      string `json:"userCode"`
-	ContactNumber string `json:"contactNumber"`
-	TokenType     string `json:"token_type"`
-	Exp           int64  `json:"exp"`
-	Iat           int64  `json:"iat"`
-}
-
-// VerifyToken 验证token并返回用户信息
-func VerifyToken(token string) (*TokenUserInfo, error) {
-	if token == "" {
-		return nil, fmt.Errorf("token不能为空")
-	}
-
-	authAPIURL := GetConfigString("auth_api_url", "")
-
-	jsonData, _ := json.Marshal(map[string]string{"token": token})
-
-	req, err := http.NewRequest("POST", authAPIURL, bytes.NewBuffer(jsonData))
-	if err != nil {
-		return nil, fmt.Errorf("创建请求失败: %v", err)
-	}
-
-	req.Header.Set("Content-Type", "application/json")
-	req.Header.Set("Authorization", "Bearer "+token)
-
-	client := &http.Client{Timeout: 10 * time.Second}
-	resp, err := client.Do(req)
-	if err != nil {
-		return nil, fmt.Errorf("请求token验证API失败: %v", err)
-	}
-	defer resp.Body.Close()
-
-	body, err := io.ReadAll(resp.Body)
-	if err != nil {
-		return nil, fmt.Errorf("读取响应失败: %v", err)
-	}
-
-	if resp.StatusCode != http.StatusOK {
-		return nil, fmt.Errorf("token验证失败,状态码: %d", resp.StatusCode)
-	}
-
-	var userInfo TokenUserInfo
-	if err := json.Unmarshal(body, &userInfo); err != nil {
-		return nil, fmt.Errorf("解析响应失败: %v", err)
-	}
-
-	if userInfo.Exp > 0 && time.Now().Unix() > userInfo.Exp {
-		return nil, fmt.Errorf("token已过期")
-	}
-
-	return &userInfo, nil
-}
-
-// GetUserInfoFromToken 从请求头中获取token并验证
-func GetUserInfoFromToken(headerFunc func(string) string) (*TokenUserInfo, error) {
-	token := headerFunc("token")
-	if token == "" {
-		token = headerFunc("Token")
-	}
-	if token == "" {
-		token = headerFunc("Authorization")
-		if strings.HasPrefix(token, "Bearer ") {
-			token = token[7:]
-		}
-	}
-
-	if token == "" {
-		return nil, fmt.Errorf("请求头中未找到token")
-	}
-
-	return VerifyToken(token)
-}
-
-// GetUserInfoFromContext 从Context中获取已验证的用户信息
-func GetUserInfoFromContext(input interface{}) (*TokenUserInfo, error) {
-	if input == nil {
-		return nil, fmt.Errorf("未找到用户信息")
-	}
-
-	userInfo, ok := input.(*TokenUserInfo)
-	if !ok {
-		return nil, fmt.Errorf("用户信息类型错误")
-	}
-
-	if userInfo == nil {
-		return nil, fmt.Errorf("用户信息为空")
-	}
-
-	return userInfo, nil
-}

+ 0 - 169
shudao-go-backend/utils/tools.go

@@ -1,169 +0,0 @@
-package utils
-
-import (
-	"bytes"
-	"crypto/md5"
-	"encoding/hex"
-	"encoding/json"
-	"fmt"
-	"io"
-	"math/rand"
-	"net/http"
-	"strings"
-	"time"
-)
-
-// UnixToDate 10位时间戳转日期字符串
-func UnixToDate(timestamp int) string {
-	return time.Unix(int64(timestamp), 0).Format("2006-01-02 15:04:05")
-}
-
-// DateToUnix 日期字符串转10位时间戳
-func DateToUnix(str string) int64 {
-	t, err := time.ParseInLocation("2006-01-02 15:04:05", str, time.Local)
-	if err != nil {
-		return 0
-	}
-	return t.Unix()
-}
-
-// GetUnixTimestamp 获取10位时间戳
-func GetUnixTimestamp() int64 {
-	return time.Now().Unix()
-}
-
-// GetUnixNano 获取13位时间戳
-func GetUnixNano() int64 {
-	return time.Now().UnixNano() / 1e6
-}
-
-// GetDate 获取当前日期时间字符串
-func GetDate() string {
-	return time.Now().Format("2006-01-02 15:04:05")
-}
-
-// Md5 MD5加密
-func Md5(str string) string {
-	m := md5.New()
-	m.Write([]byte(str))
-	return hex.EncodeToString(m.Sum(nil))
-}
-
-// 邀请码生成常量
-const (
-	inviteCodeBase    = "E8S2DZX9WYLTN6BQF7CP5IK3MJUAR4HV"
-	inviteCodeDecimal = 32
-	inviteCodePad     = "A"
-	inviteCodeLen     = 8
-)
-
-// EncodeInviteCode 生成邀请码
-func EncodeInviteCode(uid uint64) string {
-	id := uid
-	res := ""
-	for id != 0 {
-		mod := id % inviteCodeDecimal
-		id = id / inviteCodeDecimal
-		res += string(inviteCodeBase[mod])
-	}
-	if len(res) < inviteCodeLen {
-		res += inviteCodePad
-		for i := 0; i < inviteCodeLen-len(res)-1; i++ {
-			res += string(inviteCodeBase[(int(uid)+i)%inviteCodeDecimal])
-		}
-	}
-	return res
-}
-
-// GetRandomCode 生成指定长度的随机数字字符串
-func GetRandomCode(length int) string {
-	var sb strings.Builder
-	r := rand.New(rand.NewSource(time.Now().UnixNano()))
-	for i := 0; i < length; i++ {
-		fmt.Fprintf(&sb, "%d", r.Intn(10))
-	}
-	return sb.String()
-}
-
-// GetRandomString 生成指定长度的随机字符串
-func GetRandomString(length int) string {
-	const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
-	result := make([]byte, length)
-	r := rand.New(rand.NewSource(time.Now().UnixNano()))
-	for i := 0; i < length; i++ {
-		result[i] = chars[r.Intn(len(chars))]
-	}
-	return string(result)
-}
-
-// UniqueStrings 字符串切片去重
-func UniqueStrings(s []string) []string {
-	seen := make(map[string]struct{})
-	result := make([]string, 0, len(s))
-	for _, v := range s {
-		if _, ok := seen[v]; !ok {
-			seen[v] = struct{}{}
-			result = append(result, v)
-		}
-	}
-	return result
-}
-
-// HTTPGet 发送GET请求
-func HTTPGet(url string) (string, error) {
-	client := &http.Client{Timeout: 5 * time.Second}
-	resp, err := client.Get(url)
-	if err != nil {
-		return "", err
-	}
-	defer resp.Body.Close()
-
-	body, err := io.ReadAll(resp.Body)
-	if err != nil {
-		return "", err
-	}
-	return string(body), nil
-}
-
-// HTTPPost 发送POST请求
-func HTTPPost(url string, data interface{}, contentType string) (string, error) {
-	jsonData, err := json.Marshal(data)
-	if err != nil {
-		return "", err
-	}
-
-	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
-	if err != nil {
-		return "", err
-	}
-	req.Header.Set("Content-Type", contentType)
-
-	client := &http.Client{Timeout: 5 * time.Second}
-	resp, err := client.Do(req)
-	if err != nil {
-		return "", err
-	}
-	defer resp.Body.Close()
-
-	body, err := io.ReadAll(resp.Body)
-	if err != nil {
-		return "", err
-	}
-	return string(body), nil
-}
-
-// Min 返回两个整数中的较小值
-func Min(a, b int) int {
-	if a < b {
-		return a
-	}
-	return b
-}
-
-// Max 返回两个整数中的较大值
-func Max(a, b int) int {
-	if a > b {
-		return a
-	}
-	return b
-}