package service import ( "bytes" "context" "encoding/json" "fmt" "net/http" "strings" "sync" "time" "github.com/gorilla/websocket" "market-data-service/api" ) // TestService 测试服务接口 type TestService interface { // GetAPITestList 获取API测试列表 GetAPITestList(ctx context.Context) (*api.APITestListData, error) // RunAPITest 执行API测试 RunAPITest(ctx context.Context, baseURL string, req *api.APITestRequest) (*api.APITestResult, error) // GetWSTestList 获取WebSocket测试列表 GetWSTestList(ctx context.Context) (*api.WSTestListData, error) // RunWSTest 执行WebSocket测试 RunWSTest(ctx context.Context, wsURL string, req *api.WSTestRequest) (*api.WSTestResult, error) // GetTestHistory 获取测试历史 GetTestHistory(ctx context.Context, req *api.TestHistoryRequest) (*api.TestHistoryData, error) } // TestServiceImpl 测试服务实现 type TestServiceImpl struct { mu sync.RWMutex apiHistory []api.APITestResult wsHistory []api.WSTestResult historySize int } // NewTestService 创建测试服务 func NewTestService() TestService { return &TestServiceImpl{ apiHistory: make([]api.APITestResult, 0), wsHistory: make([]api.WSTestResult, 0), historySize: 100, // 保留最近100条记录 } } // GetAPITestList 获取API测试列表 func (s *TestServiceImpl) GetAPITestList(ctx context.Context) (*api.APITestListData, error) { categories := []api.APITestCategory{ { Name: "股票接口", Items: []api.APITestCase{ { ID: "stock_klines", Name: "查询股票K线", Method: "GET", Path: "/v1/stock/klines/{symbol}", Description: "查询指定股票的K线数据", Params: map[string]string{ "symbol": "000001.SZ", "start": time.Now().AddDate(0, 0, -30).Format("20060102"), "end": time.Now().Format("20060102"), "freq": "1d", "adjust": "qfq", }, }, { ID: "stock_symbols", Name: "查询股票列表", Method: "GET", Path: "/v1/stock/symbols", Description: "获取所有可用股票标的", Params: map[string]string{ "page": "1", "size": "20", }, }, { ID: "stock_batch", Name: "批量查询股票K线", Method: "POST", Path: "/v1/stock/klines/batch", Description: "批量查询多只股票K线", Body: map[string]interface{}{ "symbols": []string{"000001.SZ", "000002.SZ"}, "start": time.Now().AddDate(0, 0, -7).Format("20060102"), "end": time.Now().Format("20060102"), "freq": "1d", }, }, { ID: "stock_calendar", Name: "查询交易日历", Method: "GET", Path: "/v1/stock/trading-dates", Description: "查询股票交易日历", Params: map[string]string{ "start": time.Now().AddDate(0, 0, -30).Format("20060102"), "end": time.Now().AddDate(0, 0, 30).Format("20060102"), }, }, }, }, { Name: "期货接口", Items: []api.APITestCase{ { ID: "futures_klines", Name: "查询期货K线", Method: "GET", Path: "/v1/futures/klines/{symbol}", Description: "查询指定期货合约的K线数据", Params: map[string]string{ "symbol": "CU2504.SHFE", "start": time.Now().AddDate(0, 0, -30).Format("20060102"), "end": time.Now().Format("20060102"), "freq": "1d", }, }, { ID: "futures_symbols", Name: "查询期货列表", Method: "GET", Path: "/v1/futures/symbols", Description: "获取所有可用期货标的", Params: map[string]string{ "page": "1", "size": "20", }, }, { ID: "futures_batch", Name: "批量查询期货K线", Method: "POST", Path: "/v1/futures/klines/batch", Description: "批量查询多个期货合约K线", Body: map[string]interface{}{ "symbols": []string{"CU2504.SHFE", "RB2505.SHFE"}, "start": time.Now().AddDate(0, 0, -7).Format("20060102"), "end": time.Now().Format("20060102"), "freq": "1d", }, }, { ID: "futures_contracts", Name: "查询合约列表", Method: "GET", Path: "/v1/futures/contracts", Description: "根据品种查询可交易合约", Params: map[string]string{ "underlying": "CU", "exchange": "SHFE", }, }, { ID: "futures_calendar", Name: "查询期货交易日历", Method: "GET", Path: "/v1/futures/trading-dates", Description: "查询期货交易日历", Params: map[string]string{ "start": time.Now().AddDate(0, 0, -30).Format("20060102"), "end": time.Now().AddDate(0, 0, 30).Format("20060102"), }, }, }, }, { Name: "管理接口", Items: []api.APITestCase{ { ID: "admin_health", Name: "健康检查", Method: "GET", Path: "/v1/admin/health", Description: "检查服务健康状态", Params: map[string]string{}, }, { ID: "admin_source_status", Name: "数据源状态", Method: "GET", Path: "/v1/admin/source/status", Description: "获取当前数据源状态", Params: map[string]string{}, }, }, }, } return &api.APITestListData{ Categories: categories, BaseURL: "", }, nil } // RunAPITest 执行API测试 func (s *TestServiceImpl) RunAPITest(ctx context.Context, baseURL string, req *api.APITestRequest) (*api.APITestResult, error) { // 获取测试用例 testList, _ := s.GetAPITestList(ctx) var testCase *api.APITestCase for _, cat := range testList.Categories { for _, item := range cat.Items { if item.ID == req.ID { testCase = &item break } } } if testCase == nil { return nil, fmt.Errorf("test case not found: %s", req.ID) } // 合并参数 params := make(map[string]string) for k, v := range testCase.Params { params[k] = v } for k, v := range req.Params { params[k] = v } // 构建URL url := baseURL + testCase.Path for k, v := range params { url = strings.Replace(url, "{"+k+"}", v, -1) } // 添加查询参数 if testCase.Method == "GET" && len(params) > 0 { queryParts := []string{} for k, v := range params { if !strings.Contains(testCase.Path, "{"+k+"}") { queryParts = append(queryParts, fmt.Sprintf("%s=%s", k, v)) } } if len(queryParts) > 0 { url += "?" + strings.Join(queryParts, "&") } } // 准备请求体 var body interface{} if req.Body != nil { body = req.Body } else { body = testCase.Body } // 创建HTTP客户端 client := &http.Client{ Timeout: 30 * time.Second, } // 构建请求 var httpReq *http.Request var err error if body != nil && testCase.Method != "GET" { jsonBody, _ := json.Marshal(body) httpReq, err = http.NewRequestWithContext(ctx, testCase.Method, url, bytes.NewBuffer(jsonBody)) httpReq.Header.Set("Content-Type", "application/json") } else { httpReq, err = http.NewRequestWithContext(ctx, testCase.Method, url, nil) } if err != nil { return nil, err } httpReq.Header.Set("X-API-Key", "test-api-key") // 执行请求 startTime := time.Now() resp, err := client.Do(httpReq) latency := time.Since(startTime).Milliseconds() result := &api.APITestResult{ ID: int(time.Now().Unix()), CaseID: req.ID, Name: testCase.Name, Latency: latency, Timestamp: time.Now(), Request: map[string]interface{}{ "method": testCase.Method, "url": url, "body": body, }, } if err != nil { result.Success = false result.Error = err.Error() s.addAPIHistory(result) return result, nil } defer resp.Body.Close() result.StatusCode = resp.StatusCode result.Success = resp.StatusCode >= 200 && resp.StatusCode < 300 // 解析响应 var respBody interface{} if err := json.NewDecoder(resp.Body).Decode(&respBody); err == nil { result.Response = respBody } else { result.Response = map[string]string{"raw": "非JSON响应"} } s.addAPIHistory(result) return result, nil } // GetWSTestList 获取WebSocket测试列表 func (s *TestServiceImpl) GetWSTestList(ctx context.Context) (*api.WSTestListData, error) { cases := []api.WSTestCase{ { ID: "ws_subscribe_stock", Name: "订阅股票行情", Description: "订阅单只股票实时行情", Action: "subscribe", Symbols: []string{"000001.SZ"}, }, { ID: "ws_subscribe_futures", Name: "订阅期货行情", Description: "订阅单个期货合约实时行情", Action: "subscribe", Symbols: []string{"CU2504.SHFE"}, }, { ID: "ws_subscribe_multi", Name: "批量订阅", Description: "同时订阅多个标的", Action: "subscribe", Symbols: []string{"000001.SZ", "000002.SZ", "CU2504.SHFE"}, }, { ID: "ws_unsubscribe", Name: "取消订阅", Description: "取消订阅标的", Action: "unsubscribe", Symbols: []string{"000001.SZ"}, }, } return &api.WSTestListData{ Cases: cases, WSURL: "", }, nil } // RunWSTest 执行WebSocket测试 func (s *TestServiceImpl) RunWSTest(ctx context.Context, wsURL string, req *api.WSTestRequest) (*api.WSTestResult, error) { // 获取测试用例 testList, _ := s.GetWSTestList(ctx) var testCase *api.WSTestCase for _, item := range testList.Cases { if item.ID == req.ID { testCase = &item break } } if testCase == nil { return nil, fmt.Errorf("test case not found: %s", req.ID) } // 使用自定义标的 symbols := testCase.Symbols if len(req.Symbols) > 0 { symbols = req.Symbols } result := &api.WSTestResult{ ID: fmt.Sprintf("ws_%d", time.Now().Unix()), CaseID: req.ID, Timestamp: time.Now(), Messages: []api.WSMessage{}, } // 连接WebSocket dialer := websocket.Dialer{ HandshakeTimeout: 10 * time.Second, } // 将 http:// 或 https:// 替换为 ws:// 或 wss:// if strings.HasPrefix(wsURL, "https://") { wsURL = strings.Replace(wsURL, "https://", "wss://", 1) } else if strings.HasPrefix(wsURL, "http://") { wsURL = strings.Replace(wsURL, "http://", "ws://", 1) } startTime := time.Now() conn, resp, err := dialer.Dial(wsURL, http.Header{ "X-API-Key": []string{"test-api-key"}, }) result.Latency = time.Since(startTime).Milliseconds() if err != nil { result.Success = false if resp != nil { result.Error = fmt.Sprintf("连接失败,状态码: %d", resp.StatusCode) } else { result.Error = fmt.Sprintf("连接失败: %v", err) } s.addWSHistory(result) return result, nil } defer conn.Close() result.Success = true // 发送订阅消息 msg := map[string]interface{}{ "action": testCase.Action, "symbols": symbols, } if err := conn.WriteJSON(msg); err != nil { result.Error = fmt.Sprintf("发送消息失败: %v", err) s.addWSHistory(result) return result, nil } // 等待响应 conn.SetReadDeadline(time.Now().Add(5 * time.Second)) for i := 0; i < 3; i++ { // 最多读取3条消息 var msgData map[string]interface{} if err := conn.ReadJSON(&msgData); err != nil { break } result.Messages = append(result.Messages, api.WSMessage{ Type: "received", Data: msgData, Timestamp: time.Now(), }) } s.addWSHistory(result) return result, nil } // GetTestHistory 获取测试历史 func (s *TestServiceImpl) GetTestHistory(ctx context.Context, req *api.TestHistoryRequest) (*api.TestHistoryData, error) { s.mu.RLock() defer s.mu.RUnlock() limit := req.Limit if limit <= 0 || limit > len(s.apiHistory) { limit = len(s.apiHistory) } // 获取最近的数据 apiTests := make([]api.APITestResult, 0) wsTests := make([]api.WSTestResult, 0) if req.Type == "" || req.Type == "api" { start := len(s.apiHistory) - limit if start < 0 { start = 0 } apiTests = append(apiTests, s.apiHistory[start:]...) } if req.Type == "" || req.Type == "ws" { wsLimit := req.Limit if wsLimit <= 0 || wsLimit > len(s.wsHistory) { wsLimit = len(s.wsHistory) } start := len(s.wsHistory) - wsLimit if start < 0 { start = 0 } wsTests = append(wsTests, s.wsHistory[start:]...) } return &api.TestHistoryData{ APITests: apiTests, WSTests: wsTests, }, nil } // addAPIHistory 添加API测试历史 func (s *TestServiceImpl) addAPIHistory(result *api.APITestResult) { s.mu.Lock() defer s.mu.Unlock() s.apiHistory = append(s.apiHistory, *result) // 限制历史记录数量 if len(s.apiHistory) > s.historySize { s.apiHistory = s.apiHistory[len(s.apiHistory)-s.historySize:] } } // addWSHistory 添加WebSocket测试历史 func (s *TestServiceImpl) addWSHistory(result *api.WSTestResult) { s.mu.Lock() defer s.mu.Unlock() s.wsHistory = append(s.wsHistory, *result) // 限制历史记录数量 if len(s.wsHistory) > s.historySize { s.wsHistory = s.wsHistory[len(s.wsHistory)-s.historySize:] } }