channel-test.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. package controller
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "math"
  9. "net/http"
  10. "net/http/httptest"
  11. "net/url"
  12. "one-api/common"
  13. "one-api/constant"
  14. "one-api/dto"
  15. "one-api/middleware"
  16. "one-api/model"
  17. "one-api/relay"
  18. relaycommon "one-api/relay/common"
  19. relayconstant "one-api/relay/constant"
  20. "one-api/relay/helper"
  21. "one-api/service"
  22. "one-api/setting/operation_setting"
  23. "one-api/types"
  24. "strconv"
  25. "strings"
  26. "sync"
  27. "time"
  28. "github.com/bytedance/gopkg/util/gopool"
  29. "github.com/gin-gonic/gin"
  30. )
  31. type testResult struct {
  32. context *gin.Context
  33. localErr error
  34. newAPIError *types.NewAPIError
  35. }
  36. func testChannel(channel *model.Channel, testModel string, endpointType string) testResult {
  37. tik := time.Now()
  38. if channel.Type == constant.ChannelTypeMidjourney {
  39. return testResult{
  40. localErr: errors.New("midjourney channel test is not supported"),
  41. newAPIError: nil,
  42. }
  43. }
  44. if channel.Type == constant.ChannelTypeMidjourneyPlus {
  45. return testResult{
  46. localErr: errors.New("midjourney plus channel test is not supported"),
  47. newAPIError: nil,
  48. }
  49. }
  50. if channel.Type == constant.ChannelTypeSunoAPI {
  51. return testResult{
  52. localErr: errors.New("suno channel test is not supported"),
  53. newAPIError: nil,
  54. }
  55. }
  56. if channel.Type == constant.ChannelTypeKling {
  57. return testResult{
  58. localErr: errors.New("kling channel test is not supported"),
  59. newAPIError: nil,
  60. }
  61. }
  62. if channel.Type == constant.ChannelTypeJimeng {
  63. return testResult{
  64. localErr: errors.New("jimeng channel test is not supported"),
  65. newAPIError: nil,
  66. }
  67. }
  68. if channel.Type == constant.ChannelTypeDoubaoVideo {
  69. return testResult{
  70. localErr: errors.New("doubao video channel test is not supported"),
  71. newAPIError: nil,
  72. }
  73. }
  74. if channel.Type == constant.ChannelTypeVidu {
  75. return testResult{
  76. localErr: errors.New("vidu channel test is not supported"),
  77. newAPIError: nil,
  78. }
  79. }
  80. w := httptest.NewRecorder()
  81. c, _ := gin.CreateTestContext(w)
  82. requestPath := "/v1/chat/completions"
  83. // 如果指定了端点类型,使用指定的端点类型
  84. if endpointType != "" {
  85. if endpointInfo, ok := common.GetDefaultEndpointInfo(constant.EndpointType(endpointType)); ok {
  86. requestPath = endpointInfo.Path
  87. }
  88. } else {
  89. // 如果没有指定端点类型,使用原有的自动检测逻辑
  90. // 先判断是否为 Embedding 模型
  91. if strings.Contains(strings.ToLower(testModel), "embedding") ||
  92. strings.HasPrefix(testModel, "m3e") || // m3e 系列模型
  93. strings.Contains(testModel, "bge-") || // bge 系列模型
  94. strings.Contains(testModel, "embed") ||
  95. channel.Type == constant.ChannelTypeMokaAI { // 其他 embedding 模型
  96. requestPath = "/v1/embeddings" // 修改请求路径
  97. }
  98. // VolcEngine 图像生成模型
  99. if channel.Type == constant.ChannelTypeVolcEngine && strings.Contains(testModel, "seedream") {
  100. requestPath = "/v1/images/generations"
  101. }
  102. }
  103. c.Request = &http.Request{
  104. Method: "POST",
  105. URL: &url.URL{Path: requestPath}, // 使用动态路径
  106. Body: nil,
  107. Header: make(http.Header),
  108. }
  109. if testModel == "" {
  110. if channel.TestModel != nil && *channel.TestModel != "" {
  111. testModel = *channel.TestModel
  112. } else {
  113. if len(channel.GetModels()) > 0 {
  114. testModel = channel.GetModels()[0]
  115. } else {
  116. testModel = "gpt-4o-mini"
  117. }
  118. }
  119. }
  120. cache, err := model.GetUserCache(1)
  121. if err != nil {
  122. return testResult{
  123. localErr: err,
  124. newAPIError: nil,
  125. }
  126. }
  127. cache.WriteContext(c)
  128. //c.Request.Header.Set("Authorization", "Bearer "+channel.Key)
  129. c.Request.Header.Set("Content-Type", "application/json")
  130. c.Set("channel", channel.Type)
  131. c.Set("base_url", channel.GetBaseURL())
  132. group, _ := model.GetUserGroup(1, false)
  133. c.Set("group", group)
  134. newAPIError := middleware.SetupContextForSelectedChannel(c, channel, testModel)
  135. if newAPIError != nil {
  136. return testResult{
  137. context: c,
  138. localErr: newAPIError,
  139. newAPIError: newAPIError,
  140. }
  141. }
  142. // Determine relay format based on endpoint type or request path
  143. var relayFormat types.RelayFormat
  144. if endpointType != "" {
  145. // 根据指定的端点类型设置 relayFormat
  146. switch constant.EndpointType(endpointType) {
  147. case constant.EndpointTypeOpenAI:
  148. relayFormat = types.RelayFormatOpenAI
  149. case constant.EndpointTypeOpenAIResponse:
  150. relayFormat = types.RelayFormatOpenAIResponses
  151. case constant.EndpointTypeAnthropic:
  152. relayFormat = types.RelayFormatClaude
  153. case constant.EndpointTypeGemini:
  154. relayFormat = types.RelayFormatGemini
  155. case constant.EndpointTypeJinaRerank:
  156. relayFormat = types.RelayFormatRerank
  157. case constant.EndpointTypeImageGeneration:
  158. relayFormat = types.RelayFormatOpenAIImage
  159. case constant.EndpointTypeEmbeddings:
  160. relayFormat = types.RelayFormatEmbedding
  161. default:
  162. relayFormat = types.RelayFormatOpenAI
  163. }
  164. } else {
  165. // 根据请求路径自动检测
  166. relayFormat = types.RelayFormatOpenAI
  167. if c.Request.URL.Path == "/v1/embeddings" {
  168. relayFormat = types.RelayFormatEmbedding
  169. }
  170. if c.Request.URL.Path == "/v1/images/generations" {
  171. relayFormat = types.RelayFormatOpenAIImage
  172. }
  173. if c.Request.URL.Path == "/v1/messages" {
  174. relayFormat = types.RelayFormatClaude
  175. }
  176. if strings.Contains(c.Request.URL.Path, "/v1beta/models") {
  177. relayFormat = types.RelayFormatGemini
  178. }
  179. if c.Request.URL.Path == "/v1/rerank" || c.Request.URL.Path == "/rerank" {
  180. relayFormat = types.RelayFormatRerank
  181. }
  182. if c.Request.URL.Path == "/v1/responses" {
  183. relayFormat = types.RelayFormatOpenAIResponses
  184. }
  185. }
  186. request := buildTestRequest(testModel, endpointType)
  187. info, err := relaycommon.GenRelayInfo(c, relayFormat, request, nil)
  188. if err != nil {
  189. return testResult{
  190. context: c,
  191. localErr: err,
  192. newAPIError: types.NewError(err, types.ErrorCodeGenRelayInfoFailed),
  193. }
  194. }
  195. info.InitChannelMeta(c)
  196. err = helper.ModelMappedHelper(c, info, request)
  197. if err != nil {
  198. return testResult{
  199. context: c,
  200. localErr: err,
  201. newAPIError: types.NewError(err, types.ErrorCodeChannelModelMappedError),
  202. }
  203. }
  204. testModel = info.UpstreamModelName
  205. // 更新请求中的模型名称
  206. request.SetModelName(testModel)
  207. apiType, _ := common.ChannelType2APIType(channel.Type)
  208. adaptor := relay.GetAdaptor(apiType)
  209. if adaptor == nil {
  210. return testResult{
  211. context: c,
  212. localErr: fmt.Errorf("invalid api type: %d, adaptor is nil", apiType),
  213. newAPIError: types.NewError(fmt.Errorf("invalid api type: %d, adaptor is nil", apiType), types.ErrorCodeInvalidApiType),
  214. }
  215. }
  216. //// 创建一个用于日志的 info 副本,移除 ApiKey
  217. //logInfo := info
  218. //logInfo.ApiKey = ""
  219. common.SysLog(fmt.Sprintf("testing channel %d with model %s , info %+v ", channel.Id, testModel, info.ToString()))
  220. priceData, err := helper.ModelPriceHelper(c, info, 0, request.GetTokenCountMeta())
  221. if err != nil {
  222. return testResult{
  223. context: c,
  224. localErr: err,
  225. newAPIError: types.NewError(err, types.ErrorCodeModelPriceError),
  226. }
  227. }
  228. adaptor.Init(info)
  229. var convertedRequest any
  230. // 根据 RelayMode 选择正确的转换函数
  231. switch info.RelayMode {
  232. case relayconstant.RelayModeEmbeddings:
  233. // Embedding 请求 - request 已经是正确的类型
  234. if embeddingReq, ok := request.(*dto.EmbeddingRequest); ok {
  235. convertedRequest, err = adaptor.ConvertEmbeddingRequest(c, info, *embeddingReq)
  236. } else {
  237. return testResult{
  238. context: c,
  239. localErr: errors.New("invalid embedding request type"),
  240. newAPIError: types.NewError(errors.New("invalid embedding request type"), types.ErrorCodeConvertRequestFailed),
  241. }
  242. }
  243. case relayconstant.RelayModeImagesGenerations:
  244. // 图像生成请求 - request 已经是正确的类型
  245. if imageReq, ok := request.(*dto.ImageRequest); ok {
  246. convertedRequest, err = adaptor.ConvertImageRequest(c, info, *imageReq)
  247. } else {
  248. return testResult{
  249. context: c,
  250. localErr: errors.New("invalid image request type"),
  251. newAPIError: types.NewError(errors.New("invalid image request type"), types.ErrorCodeConvertRequestFailed),
  252. }
  253. }
  254. case relayconstant.RelayModeRerank:
  255. // Rerank 请求 - request 已经是正确的类型
  256. if rerankReq, ok := request.(*dto.RerankRequest); ok {
  257. convertedRequest, err = adaptor.ConvertRerankRequest(c, info.RelayMode, *rerankReq)
  258. } else {
  259. return testResult{
  260. context: c,
  261. localErr: errors.New("invalid rerank request type"),
  262. newAPIError: types.NewError(errors.New("invalid rerank request type"), types.ErrorCodeConvertRequestFailed),
  263. }
  264. }
  265. case relayconstant.RelayModeResponses:
  266. // Response 请求 - request 已经是正确的类型
  267. if responseReq, ok := request.(*dto.OpenAIResponsesRequest); ok {
  268. convertedRequest, err = adaptor.ConvertOpenAIResponsesRequest(c, info, *responseReq)
  269. } else {
  270. return testResult{
  271. context: c,
  272. localErr: errors.New("invalid response request type"),
  273. newAPIError: types.NewError(errors.New("invalid response request type"), types.ErrorCodeConvertRequestFailed),
  274. }
  275. }
  276. default:
  277. // Chat/Completion 等其他请求类型
  278. if generalReq, ok := request.(*dto.GeneralOpenAIRequest); ok {
  279. convertedRequest, err = adaptor.ConvertOpenAIRequest(c, info, generalReq)
  280. } else {
  281. return testResult{
  282. context: c,
  283. localErr: errors.New("invalid general request type"),
  284. newAPIError: types.NewError(errors.New("invalid general request type"), types.ErrorCodeConvertRequestFailed),
  285. }
  286. }
  287. }
  288. if err != nil {
  289. return testResult{
  290. context: c,
  291. localErr: err,
  292. newAPIError: types.NewError(err, types.ErrorCodeConvertRequestFailed),
  293. }
  294. }
  295. jsonData, err := json.Marshal(convertedRequest)
  296. if err != nil {
  297. return testResult{
  298. context: c,
  299. localErr: err,
  300. newAPIError: types.NewError(err, types.ErrorCodeJsonMarshalFailed),
  301. }
  302. }
  303. requestBody := bytes.NewBuffer(jsonData)
  304. c.Request.Body = io.NopCloser(requestBody)
  305. resp, err := adaptor.DoRequest(c, info, requestBody)
  306. if err != nil {
  307. return testResult{
  308. context: c,
  309. localErr: err,
  310. newAPIError: types.NewOpenAIError(err, types.ErrorCodeDoRequestFailed, http.StatusInternalServerError),
  311. }
  312. }
  313. var httpResp *http.Response
  314. if resp != nil {
  315. httpResp = resp.(*http.Response)
  316. if httpResp.StatusCode != http.StatusOK {
  317. err := service.RelayErrorHandler(c.Request.Context(), httpResp, true)
  318. return testResult{
  319. context: c,
  320. localErr: err,
  321. newAPIError: types.NewOpenAIError(err, types.ErrorCodeBadResponse, http.StatusInternalServerError),
  322. }
  323. }
  324. }
  325. usageA, respErr := adaptor.DoResponse(c, httpResp, info)
  326. if respErr != nil {
  327. return testResult{
  328. context: c,
  329. localErr: respErr,
  330. newAPIError: respErr,
  331. }
  332. }
  333. if usageA == nil {
  334. return testResult{
  335. context: c,
  336. localErr: errors.New("usage is nil"),
  337. newAPIError: types.NewOpenAIError(errors.New("usage is nil"), types.ErrorCodeBadResponseBody, http.StatusInternalServerError),
  338. }
  339. }
  340. usage := usageA.(*dto.Usage)
  341. result := w.Result()
  342. respBody, err := io.ReadAll(result.Body)
  343. if err != nil {
  344. return testResult{
  345. context: c,
  346. localErr: err,
  347. newAPIError: types.NewOpenAIError(err, types.ErrorCodeReadResponseBodyFailed, http.StatusInternalServerError),
  348. }
  349. }
  350. info.PromptTokens = usage.PromptTokens
  351. quota := 0
  352. if !priceData.UsePrice {
  353. quota = usage.PromptTokens + int(math.Round(float64(usage.CompletionTokens)*priceData.CompletionRatio))
  354. quota = int(math.Round(float64(quota) * priceData.ModelRatio))
  355. if priceData.ModelRatio != 0 && quota <= 0 {
  356. quota = 1
  357. }
  358. } else {
  359. quota = int(priceData.ModelPrice * common.QuotaPerUnit)
  360. }
  361. tok := time.Now()
  362. milliseconds := tok.Sub(tik).Milliseconds()
  363. consumedTime := float64(milliseconds) / 1000.0
  364. other := service.GenerateTextOtherInfo(c, info, priceData.ModelRatio, priceData.GroupRatioInfo.GroupRatio, priceData.CompletionRatio,
  365. usage.PromptTokensDetails.CachedTokens, priceData.CacheRatio, priceData.ModelPrice, priceData.GroupRatioInfo.GroupSpecialRatio)
  366. model.RecordConsumeLog(c, 1, model.RecordConsumeLogParams{
  367. ChannelId: channel.Id,
  368. PromptTokens: usage.PromptTokens,
  369. CompletionTokens: usage.CompletionTokens,
  370. ModelName: info.OriginModelName,
  371. TokenName: "模型测试",
  372. Quota: quota,
  373. Content: "模型测试",
  374. UseTimeSeconds: int(consumedTime),
  375. IsStream: info.IsStream,
  376. Group: info.UsingGroup,
  377. Other: other,
  378. })
  379. common.SysLog(fmt.Sprintf("testing channel #%d, response: \n%s", channel.Id, string(respBody)))
  380. return testResult{
  381. context: c,
  382. localErr: nil,
  383. newAPIError: nil,
  384. }
  385. }
  386. func buildTestRequest(model string, endpointType string) dto.Request {
  387. // 根据端点类型构建不同的测试请求
  388. if endpointType != "" {
  389. switch constant.EndpointType(endpointType) {
  390. case constant.EndpointTypeEmbeddings:
  391. // 返回 EmbeddingRequest
  392. return &dto.EmbeddingRequest{
  393. Model: model,
  394. Input: []any{"hello world"},
  395. }
  396. case constant.EndpointTypeImageGeneration:
  397. // 返回 ImageRequest
  398. return &dto.ImageRequest{
  399. Model: model,
  400. Prompt: "a cute cat",
  401. N: 1,
  402. Size: "1024x1024",
  403. }
  404. case constant.EndpointTypeJinaRerank:
  405. // 返回 RerankRequest
  406. return &dto.RerankRequest{
  407. Model: model,
  408. Query: "What is Deep Learning?",
  409. Documents: []any{"Deep Learning is a subset of machine learning.", "Machine learning is a field of artificial intelligence."},
  410. TopN: 2,
  411. }
  412. case constant.EndpointTypeOpenAIResponse:
  413. // 返回 OpenAIResponsesRequest
  414. return &dto.OpenAIResponsesRequest{
  415. Model: model,
  416. Input: json.RawMessage("\"hi\""),
  417. }
  418. case constant.EndpointTypeAnthropic, constant.EndpointTypeGemini, constant.EndpointTypeOpenAI:
  419. // 返回 GeneralOpenAIRequest
  420. maxTokens := uint(10)
  421. if constant.EndpointType(endpointType) == constant.EndpointTypeGemini {
  422. maxTokens = 3000
  423. }
  424. return &dto.GeneralOpenAIRequest{
  425. Model: model,
  426. Stream: false,
  427. Messages: []dto.Message{
  428. {
  429. Role: "user",
  430. Content: "hi",
  431. },
  432. },
  433. MaxTokens: maxTokens,
  434. }
  435. }
  436. }
  437. // 自动检测逻辑(保持原有行为)
  438. // 先判断是否为 Embedding 模型
  439. if strings.Contains(strings.ToLower(model), "embedding") ||
  440. strings.HasPrefix(model, "m3e") ||
  441. strings.Contains(model, "bge-") {
  442. // 返回 EmbeddingRequest
  443. return &dto.EmbeddingRequest{
  444. Model: model,
  445. Input: []any{"hello world"},
  446. }
  447. }
  448. // Chat/Completion 请求 - 返回 GeneralOpenAIRequest
  449. testRequest := &dto.GeneralOpenAIRequest{
  450. Model: model,
  451. Stream: false,
  452. Messages: []dto.Message{
  453. {
  454. Role: "user",
  455. Content: "hi",
  456. },
  457. },
  458. }
  459. if strings.HasPrefix(model, "o") {
  460. testRequest.MaxCompletionTokens = 10
  461. } else if strings.Contains(model, "thinking") {
  462. if !strings.Contains(model, "claude") {
  463. testRequest.MaxTokens = 50
  464. }
  465. } else if strings.Contains(model, "gemini") {
  466. testRequest.MaxTokens = 3000
  467. } else {
  468. testRequest.MaxTokens = 10
  469. }
  470. return testRequest
  471. }
  472. func TestChannel(c *gin.Context) {
  473. channelId, err := strconv.Atoi(c.Param("id"))
  474. if err != nil {
  475. common.ApiError(c, err)
  476. return
  477. }
  478. channel, err := model.CacheGetChannel(channelId)
  479. if err != nil {
  480. channel, err = model.GetChannelById(channelId, true)
  481. if err != nil {
  482. common.ApiError(c, err)
  483. return
  484. }
  485. }
  486. //defer func() {
  487. // if channel.ChannelInfo.IsMultiKey {
  488. // go func() { _ = channel.SaveChannelInfo() }()
  489. // }
  490. //}()
  491. testModel := c.Query("model")
  492. endpointType := c.Query("endpoint_type")
  493. tik := time.Now()
  494. result := testChannel(channel, testModel, endpointType)
  495. if result.localErr != nil {
  496. c.JSON(http.StatusOK, gin.H{
  497. "success": false,
  498. "message": result.localErr.Error(),
  499. "time": 0.0,
  500. })
  501. return
  502. }
  503. tok := time.Now()
  504. milliseconds := tok.Sub(tik).Milliseconds()
  505. go channel.UpdateResponseTime(milliseconds)
  506. consumedTime := float64(milliseconds) / 1000.0
  507. if result.newAPIError != nil {
  508. c.JSON(http.StatusOK, gin.H{
  509. "success": false,
  510. "message": result.newAPIError.Error(),
  511. "time": consumedTime,
  512. })
  513. return
  514. }
  515. c.JSON(http.StatusOK, gin.H{
  516. "success": true,
  517. "message": "",
  518. "time": consumedTime,
  519. })
  520. }
  521. var testAllChannelsLock sync.Mutex
  522. var testAllChannelsRunning bool = false
  523. func testAllChannels(notify bool) error {
  524. testAllChannelsLock.Lock()
  525. if testAllChannelsRunning {
  526. testAllChannelsLock.Unlock()
  527. return errors.New("测试已在运行中")
  528. }
  529. testAllChannelsRunning = true
  530. testAllChannelsLock.Unlock()
  531. channels, getChannelErr := model.GetAllChannels(0, 0, true, false)
  532. if getChannelErr != nil {
  533. return getChannelErr
  534. }
  535. var disableThreshold = int64(common.ChannelDisableThreshold * 1000)
  536. if disableThreshold == 0 {
  537. disableThreshold = 10000000 // a impossible value
  538. }
  539. gopool.Go(func() {
  540. // 使用 defer 确保无论如何都会重置运行状态,防止死锁
  541. defer func() {
  542. testAllChannelsLock.Lock()
  543. testAllChannelsRunning = false
  544. testAllChannelsLock.Unlock()
  545. }()
  546. for _, channel := range channels {
  547. isChannelEnabled := channel.Status == common.ChannelStatusEnabled
  548. tik := time.Now()
  549. result := testChannel(channel, "", "")
  550. tok := time.Now()
  551. milliseconds := tok.Sub(tik).Milliseconds()
  552. shouldBanChannel := false
  553. newAPIError := result.newAPIError
  554. // request error disables the channel
  555. if newAPIError != nil {
  556. shouldBanChannel = service.ShouldDisableChannel(channel.Type, result.newAPIError)
  557. }
  558. // 当错误检查通过,才检查响应时间
  559. if common.AutomaticDisableChannelEnabled && !shouldBanChannel {
  560. if milliseconds > disableThreshold {
  561. err := fmt.Errorf("响应时间 %.2fs 超过阈值 %.2fs", float64(milliseconds)/1000.0, float64(disableThreshold)/1000.0)
  562. newAPIError = types.NewOpenAIError(err, types.ErrorCodeChannelResponseTimeExceeded, http.StatusRequestTimeout)
  563. shouldBanChannel = true
  564. }
  565. }
  566. // disable channel
  567. if isChannelEnabled && shouldBanChannel && channel.GetAutoBan() {
  568. processChannelError(result.context, *types.NewChannelError(channel.Id, channel.Type, channel.Name, channel.ChannelInfo.IsMultiKey, common.GetContextKeyString(result.context, constant.ContextKeyChannelKey), channel.GetAutoBan()), newAPIError)
  569. }
  570. // enable channel
  571. if !isChannelEnabled && service.ShouldEnableChannel(newAPIError, channel.Status) {
  572. service.EnableChannel(channel.Id, common.GetContextKeyString(result.context, constant.ContextKeyChannelKey), channel.Name)
  573. }
  574. channel.UpdateResponseTime(milliseconds)
  575. time.Sleep(common.RequestInterval)
  576. }
  577. if notify {
  578. service.NotifyRootUser(dto.NotifyTypeChannelTest, "通道测试完成", "所有通道测试已完成")
  579. }
  580. })
  581. return nil
  582. }
  583. func TestAllChannels(c *gin.Context) {
  584. err := testAllChannels(true)
  585. if err != nil {
  586. common.ApiError(c, err)
  587. return
  588. }
  589. c.JSON(http.StatusOK, gin.H{
  590. "success": true,
  591. "message": "",
  592. })
  593. }
  594. var autoTestChannelsOnce sync.Once
  595. func AutomaticallyTestChannels() {
  596. autoTestChannelsOnce.Do(func() {
  597. for {
  598. if !operation_setting.GetMonitorSetting().AutoTestChannelEnabled {
  599. time.Sleep(10 * time.Minute)
  600. continue
  601. }
  602. frequency := operation_setting.GetMonitorSetting().AutoTestChannelMinutes
  603. common.SysLog(fmt.Sprintf("automatically test channels with interval %d minutes", frequency))
  604. for {
  605. time.Sleep(time.Duration(frequency) * time.Minute)
  606. common.SysLog("automatically testing all channels")
  607. _ = testAllChannels(false)
  608. common.SysLog("automatically channel test finished")
  609. if !operation_setting.GetMonitorSetting().AutoTestChannelEnabled {
  610. break
  611. }
  612. }
  613. }
  614. })
  615. }