上一章,咱们对http请求进行了一些优化,本章节我们将组成场景去运行。首先场景就是一连串的http接口的请求,我们使用list(列表)来组装成一个场景。
首先我们在model目录下,新建test_scene.go文件。
// Package model -----------------------------
// @file : test_scene.go
// @author : 被测试耽误的大厨
// @contact : 13383088061@163.com
// @time : 2023/7/6 15:53
// -------------------------------------------
package model
import "sync"
// TestScene 业务流程组成的测试场景
type TestScene struct {
Name string `json:"name"` // 场景名称
Id string `json:"id"` // 唯一id
ParentId string `json:"parent_id"` // 父id
ItemId string `json:"item_id"` // 项目Id
TeamId string `json:"team_id"` // 团队Id
SourceId string `json:"source_id"` // 源Id
ChannelId string `json:"channel_id"` // 渠道Id比如YApi,postman等
ChannelType string `json:"channel_type"` // 渠道类型
TestObjects []TestObject `json:"test_objects"` // 场景
}
/*
处理测试场景
*/
func (testScene TestScene) Dispose() {
// 从testScene.TestObjects的最外层开始循环
for _, testObject := range testScene.TestObjects {
// 声明一个waitGroup控制等待内层循环请求内部完成
wg := sync.WaitGroup{}
// 从一层级的list中读取每个TestObject对象
wg.Add(1)
go func(object TestObject) {
defer wg.Done()
response := TestObjectResponse{
Name: object.Name,
Id: object.Id,
SceneId: testScene.Id,
ItemId: object.ItemId,
ObjectType: object.ObjectType,
}
// 开始进行请求处理
object.Dispose(&response)
}(testObject)
// 等待内层的list完成后,再开始下一次外层循环
wg.Wait()
}
}
上面的内容,我们定义了一个TestScene结构体,来表示我们的测试场景,然后定义了TestScene的结构体来处理场景。
然后我们新建一个reponse_data.go文件,用来声明我们需要的测试结果信息
// Package model -----------------------------
// @file : response_data.go
// @author : 被测试耽误的大厨
// @contact : 13383088061@163.com
// @time : 2023/7/6 16:21
// -------------------------------------------
package model
// TestObjectResponse 响应信息, 用于返回给调用方
type TestObjectResponse struct {
Name string `json:"name"` // 对象名称
Id string `json:"id"` // 对象id
SceneId string `json:"scene_id"` // 场景id
ObjectType string `json:"object_type"` // 对象类型http、websocket、dubbo等
ItemId string `json:"item_id"` // 项目Id
TeamId string `json:"team_id"` // 团队Id
Code int `json:"code"`
RequestHeaders map[string]string `json:"request_headers"`
ResponseHeaders map[string]string `json:"response_headers"`
Response string `json:"response"`
RequestTime int64 `json:"request_time"`
}
我们将原来test_object.go中的结构体单独提取到reponse_data.go文件中,并对一些无用的字段进行优化。
// Package model -----------------------------
// @file : test_object.go
// @author : 被测试耽误的大厨
// @contact : 13383088061@163.com
// @time : 2023/6/11 20:38
// -------------------------------------------
package model
// TestObject 测试对象结构体
type TestObject struct {
Name string `json:"name"` // 对象名称
Id string `json:"id"` // 唯一id
ObjectType string `json:"object_type"` // 对象类型http、websocket、dubbo等
ItemId string `json:"item_id"` // 项目Id
TeamId string `json:"team_id"` // 团队Id
HttpRequest HttpRequest `json:"http_request"`
}
// Dispose 测试对象的处理函数,在go语言中 Dispose方法是TestObject对象的方法,其他对象不能使用
func (to TestObject) Dispose(response *TestObjectResponse) {
switch to.ObjectType {
case HTTP1: // 由于我们有个多类型,为了方便统计,我们定义好变量,直接进行比对即可
to.HttpRequest.Request(response)
return
}
return
}
然后我们在service目录下新建scene_api.go文件,新建RunTestScene方法来实现请求读取。
// Package service -----------------------------
// @file : scene_api.go
// @author : 被测试耽误的大厨
// @contact : 13383088061@163.com
// @time : 2023/7/6 16:06
// -------------------------------------------
package service
import (
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"kitchen-engine/global/common"
"kitchen-engine/global/log"
"kitchen-engine/model"
"net/http"
)
/*
RunTestScene 实现run/testObject/接口
*/
func RunTestScene(c *gin.Context) {
// 声明一个TO对象
var testScene model.TestScene
// 接收json格式的请求数据
err := c.ShouldBindJSON(&testScene)
id := uuid.New().String()
// 如果请求格式错误
if err != nil {
log.Logger.Error("请求数据格式错误", err.Error())
common.ReturnResponse(c, http.StatusBadRequest, id, "请求数据格式错误!", err.Error())
return
}
// 使用json包解析以下TO对象, 解析出来为[]byte类型
requestJson, _ := json.Marshal(testScene)
// 打印以下日志, 使用fmt.Sprintf包格式花数据,%s 表示string(requestJson)为字符串类型,如果不确定类型,可以使用%v表示
log.Logger.Debug(fmt.Sprintf("测试对象: %s", string(requestJson)))
// 如果场景为空,直接返回
if testScene.TestObjects == nil || len(testScene.TestObjects) == 0 {
common.ReturnResponse(c, http.StatusBadRequest, id, "请求错误!", "场景不能为空")
return
}
// 开始处理TO
testScene.Dispose()
// 返回响应消息
common.ReturnResponse(c, http.StatusOK, id, "请求成功!", "")
return
}
同时,将service目录下的api.go更名为object_api.go,并对其中的无用字段进行优化:
// Package service -----------------------------
// @file : object_api.go
// @author : 被测试耽误的大厨
// @contact : 13383088061@163.com
// @time : 2023/7/4 18:05
// -------------------------------------------
package service
import (
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"kitchen-engine/global/common"
"kitchen-engine/global/log"
"kitchen-engine/model"
"net/http"
)
/*
RunTestObject 实现run/testObject/接口
*/
func RunTestObject(c *gin.Context) {
// 声明一个TO对象
var testObject model.TestObject
// 接收json格式的请求数据
err := c.ShouldBindJSON(&testObject)
id := uuid.New().String()
// 如果请求格式错误
if err != nil {
log.Logger.Error("请求数据格式错误", err.Error())
common.ReturnResponse(c, http.StatusBadRequest, id, "请求数据格式错误!", err.Error())
return
}
// 使用json包解析以下TO对象, 解析出来为[]byte类型
requestJson, _ := json.Marshal(testObject)
// 打印以下日志, 使用fmt.Sprintf包格式花数据,%s 表示string(requestJson)为字符串类型,如果不确定类型,可以使用%v表示
log.Logger.Debug(fmt.Sprintf("测试对象: %s", string(requestJson)))
response := model.TestObjectResponse{
Name: testObject.Name,
Id: testObject.Id,
ObjectType: testObject.ObjectType,
ItemId: testObject.ItemId,
}
// 开始处理TO
testObject.Dispose(&response)
// 返回响应消息
common.ReturnResponse(c, http.StatusOK, id, "请求成功!", response)
return
}
我们再对Http请求添加一个timeout字段,并在发送请求是设置超时时间。
// HttpRequest http请求的结构
type HttpRequest struct {
Url string `json:"url"` // 接口uri
Method string `json:"method"` // 接口方法,Get Post Update...
RequestTimeout int64 `json:"request_timeout"` // 请求超时时长,默认为30秒,防止请求阻塞
Headers []Header `json:"headers"` // 接口请求头
Querys []Query `json:"querys"` // get请求时的url
Cookies []Cookie `json:"cookies"` // cookie
Body *Body `json:"body"` // 请求提
HttpClientSettings HttpClientSettings `json:"http_client_settings"` // http客户端配置
}
在http_request.go文件中,新建HttpRequest结构体的setReqTime方法
// 设置请求超时时间
func (hr *HttpRequest) setReqTimeout(req *fasthttp.Request) {
if hr.RequestTimeout <= 0 {
hr.RequestTimeout = 30
}
req.SetTimeout(time.Duration(hr.RequestTimeout) * time.Second)
}
并对HttpRequest结构体的Request方法进行优化
func (hr *HttpRequest) Request(response *TestObjectResponse) {
// 使用fasthttp 协程池
// 新建一个http请求
req := fasthttp.AcquireRequest()
defer fasthttp.ReleaseRequest(req)
// 新建一个http响应接受服务端的返回
resp := fasthttp.AcquireResponse()
defer fasthttp.ReleaseResponse(resp)
// 新建一个http的客户端, newHttpClient是一个方法,在下面
client := newHttpClient(hr.HttpClientSettings)
// 设置请求方法
hr.methodInit(req)
// 设置header
hr.headerInit(req)
// 设置query
hr.queryInit(req)
// 设置cookie
hr.cookieInit(req)
// 设置url
hr.urlInit(req)
// 设置body
hr.bodyInit(req)
// 设置请求超时时间
hr.setReqTimeout(req)
// 记录开始时间
startTime := time.Now()
// 开始请求
err := client.Do(req, resp)
// 计算响应时间差值
requestTime := time.Since(startTime)
response.RequestTime = requestTime.Milliseconds()
response.Code = resp.StatusCode()
if err != nil {
response.Response = err.Error()
return
}
// 如果响应中有压缩类型,防止压缩类型出现乱码
switch string(resp.Header.ContentEncoding()) {
case "br", "deflate", "gzip":
b, _ := resp.BodyUncompressed()
response.Response = string(b)
default:
response.Response = string(resp.Body())
}
}
同时,我们添加run/testScene接口,修改router.go中的initRouters方法。
/*
路由配置
*/
func initRouters(groups *gin.RouterGroup) {
{
groups.POST("/run/testObject/", service.RunTestObject) //运行测试对象接口, url: http://*****/engine/run/testObject/
groups.POST("/run/testScene/", service.RunTestScene) //运行测试对象接口, url: http://*****/engine/run/testObject/
}
}
这样,我们对场景的简单封装就完成了,下一步我们将对场景以及请求进行更全面的适配。