You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

520 lines
13 KiB

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:]
}
}