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