123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272 |
- package service
- import (
- "bytes"
- "context"
- "encoding/json"
- "html/template"
- "io"
- "net/url"
- "os"
- "regexp"
- "strconv"
- "strings"
- "time"
- "go-common/app/admin/ep/melloi/conf"
- "go-common/app/admin/ep/melloi/model"
- "go-common/library/ecode"
- "go-common/library/log"
- )
- //QueryScripts query scripts
- func (s *Service) QueryScripts(script *model.Script, pn, ps int) (scripts []*model.Script, err error) {
- if ps <= 0 || pn <= 0 {
- ps = 10
- pn = 1
- }
- if scripts, err = s.dao.QueryScripts(script, pn, ps); err != nil {
- log.Error("s.dao.QueryScripts error :(%v)", err)
- return
- }
- for _, script := range scripts {
- if script.APIHeader != "" {
- if err = json.Unmarshal([]byte(script.APIHeader), &script.Headers); err != nil {
- log.Error("get script header err : (%v), scriptId:(%d)", err, script.ID)
- }
- }
- if script.ArgumentString != "" {
- if err = json.Unmarshal([]byte(script.ArgumentString), &script.ArgumentsMap); err != nil {
- log.Error("get script argument err: (%v), scriptId:(%d)", err, script.ID)
- }
- }
- if script.OutputParams != "" {
- if err = json.Unmarshal([]byte(script.OutputParams), &script.OutputParamsMap); err != nil {
- log.Error("get script OutputParams err: (%v), scriptId:(%d)", err, script.ID)
- }
- }
- }
- return
- }
- //CountQueryScripts count query scripts
- func (s *Service) CountQueryScripts(script *model.Script) (total int) {
- return s.dao.CountQueryScripts(script)
- }
- //QueryScriptSnap query scriptSnap
- func (s *Service) QueryScriptSnap(scriptSnap *model.ScriptSnap) (snaps []*model.ScriptSnap, err error) {
- return s.dao.QueryScriptSnap(scriptSnap)
- }
- //QueryScriptsByPage query script by page
- func (s *Service) QueryScriptsByPage(c context.Context, sessionID string, qsrq *model.QueryScriptRequest) (rsp *model.QueryScriptResponse, err error) {
- // 获取服务树节点
- var (
- treeNodes []string
- treeNodesd []string
- )
- if treeNodesd, err = s.QueryUserRoleNode(c, sessionID); err != nil {
- log.Error("QueryUserRoleNode err (%v):", err)
- }
- treeNodes = append(treeNodesd, "")
- if ExistsInSlice(qsrq.Executor, conf.Conf.Melloi.Executor) {
- //rsp, err = s.dao.QueryScriptsByPageWhiteName(&qsrq.Script, qsrq.PageNum, qsrq.PageSize)
- if rsp, err = s.dao.QueryScriptsByPageWhiteName(&qsrq.Script, qsrq.PageNum, qsrq.PageSize); err != nil {
- return
- }
- } else {
- if rsp, err = s.dao.QueryScriptsByPage(&qsrq.Script, qsrq.PageNum, qsrq.PageSize, treeNodes); err != nil {
- return
- }
- }
- for _, script := range rsp.Scripts {
- if script.APIHeader != "" {
- if err = json.Unmarshal([]byte(script.APIHeader), &script.Headers); err != nil {
- log.Error("get script header err : (%v), scriptId:(%d)", err, script.ID)
- }
- }
- if script.ArgumentString != "" {
- if err = json.Unmarshal([]byte(script.ArgumentString), &script.ArgumentsMap); err != nil {
- log.Error("get script argument err: (%v), scriptId:(%d)", err, script.ID)
- }
- }
- if script.OutputParams != "" {
- if err = json.Unmarshal([]byte(script.OutputParams), &script.OutputParamsMap); err != nil {
- log.Error("get script OutputParams err: (%v), scriptId:(%d)", err, script.ID)
- }
- }
- }
- // 获取label
- for _, script := range rsp.Scripts {
- lr := model.LabelRelation{Type: model.ScriptType, TargetID: int64(script.ID)}
- if script.Labels, err = s.dao.QueryLabelRelation(&lr); err != nil {
- return
- }
- }
- return
- }
- //AddAndExcuScript add script and excute it
- func (s *Service) AddAndExcuScript(c context.Context, script *model.Script, cookie string, scene *model.Scene, fileWrite, convjson bool) (resp model.DoPtestResp, err error) {
- var (
- scriptID int
- groupID int
- runOrder int
- testNameNick string
- message string
- scriptd model.Script
- paramList *model.ParamList
- isEdit = false
- )
- if message, scriptd, err = s.AddScript(script, fileWrite); err != nil {
- log.Error("sorry, addScript fail : (%v)", err)
- return
- }
- headerString := MapToString(scriptd.Headers)
- argumentString := MapToString(scriptd.ArgumentsMap)
- scriptd.ArgumentString = argumentString
- scriptd.APIHeader = headerString
- scriptd.OutputParams = MapToString(scriptd.OutputParamsMap)
- //场景压测中选择已有接口时,去修改默认的熔断成功率的值(新增接口时不修改)
- if scene.IsBatch {
- scriptd.Fusing = scene.Fusing
- }
- if convjson {
- scriptd.ArgumentString = script.ArgumentString
- scriptd.APIHeader = script.APIHeader
- scriptd.OutputParams = script.OutputParams
- }
- //场景压测独有的逻辑
- if scriptd.TestType == model.SCENE_SCRIPT_TYPE {
- //根据接口参数依赖,计算出接口分组与执行顺序
- if paramList, err = s.dao.QueryParams(&scriptd, scene); err != nil {
- log.Error("s.dao.QueryParams err :(%v)", err)
- return
- }
- scriptd.GroupID, scriptd.RunOrder = GroupOrder(isEdit, &scriptd, scene, paramList)
- }
- //非debug 执行的 循环次数为-1 即永久
- if scriptd.Loops == -1 {
- if len(script.OutputParamsMap) == 0 || script.OutputParams == "[]" {
- scriptd.OutputParams = "[{\"\":\"\"}]"
- }
- if scriptID, groupID, runOrder, err = s.dao.AddScript(&scriptd); err != nil {
- log.Error("s.dao.AddScript err : (%v)", err)
- message = "sql excute err"
- return
- }
- }
- //如果不是复制脚本操作 或者 不是保存,则执行jmeter压测
- if !script.IsCopy && !script.IsSave {
- tim := strconv.FormatInt(time.Now().Unix(), 10)
- testNameNick = scriptd.TestName + tim
- log.Info("开始调用压测job-------\n")
- ptestParam := model.DoPtestParam{
- UserName: scriptd.UpdateBy, // 用户名
- LoadTime: scriptd.LoadTime, //运行时间
- TestNames: StringToSlice(scriptd.TestName), //接口名转数组
- FileName: scriptd.SavePath, // jmx文件
- ResJtl: scriptd.ResJtl, // jtl 文件
- JmeterLog: scriptd.JmeterLog, // jmeterlog时间戳
- Department: script.Department,
- Project: script.Project,
- APP: script.App,
- ScriptID: scriptID,
- IsDebug: script.IsDebug, //false
- Cookie: cookie, // 用不到
- URL: scriptd.URL, // 微信通知URL
- LabelIDs: script.LabelIds,
- Domain: scriptd.Domain,
- FileSplit: script.FileSplit, // 文件切割
- SplitNum: script.SplitNum, // 切割数量
- Fusing: script.Fusing,
- APIHeader: scriptd.APIHeader,
- ExecuDockerSum: script.ExecuDockerSum,
- UseBusinessStop: script.UseBusinessStop,
- BusinessStopPercent: script.BusinessStopPercent,
- }
- if resp, err = s.DoPtestByJmeter(c, ptestParam, StringToSlice(testNameNick)); err != nil {
- log.Error("DoPtestByJmeter err :(%v)", err)
- message = "DoPtestByJmeter err "
- return
- }
- resp.Message = message
- }
- resp.ScriptID = scriptID
- resp.GroupID = groupID
- resp.RunOrder = runOrder
- return
- }
- //AddScript add script
- func (s *Service) AddScript(script *model.Script, fileWrite bool) (message string, scriptd model.Script, err error) {
- var (
- buff *template.Template
- file *os.File
- scriptPath string
- u *url.URL
- sinfoJSON []byte
- JSON []byte
- JSONExtractors string
- dataFileName string
- Assertionsd []string
- )
- log.Info("threadsSum :(%d)", script.ThreadsSum)
- if script.LoadTime > s.c.Jmeter.TestTimeLimit {
- script.LoadTime = s.c.Jmeter.TestTimeLimit
- }
- //判断url
- if !strings.Contains(script.URL, "http://") && !strings.Contains(script.URL, "https://") {
- script.URL = "http://" + script.URL
- }
- if script.Delimiter == "" {
- script.Delimiter = ","
- }
- if u, err = url.Parse(script.URL); err != nil || u == nil {
- log.Error("sorry,url is not standard,error(%v)", err)
- err = ecode.MelloiUrlParseError
- return
- }
- if script.Data == "" {
- buff, err = template.ParseFiles(s.c.Jmeter.JmeterScUcodedTmp)
- } else {
- buff, err = template.ParseFiles(s.c.Jmeter.JmeterScTmp)
- }
- if err != nil {
- log.Error("open file failed! error %v", err)
- return
- }
- if script.Assertion != "" {
- script.UseAssertion = true
- Assertions := strings.Split(script.Assertion, "|")
- for _, assert := range Assertions {
- Assertionsd = append(Assertionsd, model.AssertionStart+assert+model.AssertionEnd)
- }
- script.AssertionString = unescaped(SliceToString(Assertionsd, " "))
- }
- if len(script.OutputParamsMap) > 0 && script.OutputParams != "[{\"\":\"\"}]" && script.OutputParams != "[]" && script.OutputParams != "" {
- if JSONExtractors, err = s.SetJSONExtractors(script); err != nil {
- return
- }
- if JSONExtractors != "" {
- script.JSONExtractor = unescaped(JSONExtractors)
- }
- }
- // POST 请求的 binary 逻辑
- if script.MultipartFile != "" && script.MultipartPath != "" {
- script.UseMultipart = true
- multiPartInfo := SetMultiPartInfo(script)
- script.MultiPartInfo = unescaped(multiPartInfo)
- }
- stringHeader := SetHeaders(script.Headers, script.ID)
- ArgmentStr := SetArguments(script.ArgumentsMap, script.ID)
- ArgmentString := unescaped(ArgmentStr)
- script.Arguments = ArgmentString
- stringHeaderd := unescaped(stringHeader)
- script.HeaderString = stringHeaderd
- script.ProcType = u.Scheme
- script.Domain = u.Hostname()
- script.Port = u.Port()
- if u.RawQuery == "" {
- script.Path = u.Path
- script.RowQuery = "appkey=" + conf.Conf.Melloi.AppkeyProd
- } else {
- script.Path = u.Path + "?" + u.RawQuery
- script.RowQuery = u.RawQuery + "&appkey=" + conf.Conf.Melloi.AppkeyProd
- }
- if script.UseSign {
- script.Path = u.Path + "?${params}"
- }
- dataFileName = script.FileName
- script.FileName = script.ScriptPath + script.FileName
- if script.ScriptPath != "" && script.FileName == "" && !script.Upload {
- message = "dataFile is not exists"
- log.Error(message)
- return
- }
- if script.Loops == 0 {
- script.Loops = -1
- }
- //极速模式(线程NIO模式)
- if script.IsAsync {
- script.AsyncInfo = unescaped(model.AsyncInfo)
- }
- //生成压测脚本
- if sinfoJSON, err = json.Marshal(script); err != nil {
- log.Error("script err (%v):", err)
- return
- }
- sc := string(sinfoJSON)
- log.Info("script :(%s)", sc)
- //判断是否使用内置参数
- if strings.Contains(sc, "${mid}") || strings.Contains(sc, "${access_key}") || script.UseSign {
- script.UseBuiltinParam = true
- }
- //fileWrite = true ,需要创建文件,为 false ,不需要创建文件
- if fileWrite {
- //创建脚本保存路径
- if script.ScriptPath == "" {
- if scriptPath, err = s.uniqueFolderPath(script.SavePath); err != nil {
- return
- }
- } else {
- scriptPath = script.ScriptPath
- }
- SavePath := scriptPath + script.TestName + "/"
- log.Info("SavePath****: " + SavePath)
- if err = os.MkdirAll(SavePath, 0755); err != nil {
- log.Error("Create SavePath Err :(%v)", err)
- return
- }
- ///创建脚本保存路径 脚本存放路径:部门/项目/应用/项目名/,须判断路径是否包含.jmx
- if !strings.Contains(script.SavePath, ".jmx") {
- // 创建脚本文件 部门/项目/应用/项目名/**.jmx
- if file, err = os.Create(SavePath + script.TestName + ".jmx"); err != nil {
- log.Error("create file error :(%v)", err)
- message = "create .jmx error"
- return
- }
- defer file.Close()
- //jtl 路径
- script.ResJtl = SavePath + script.TestName + ".jtl"
- //jmeter-log 路径
- script.JmeterLog = SavePath + script.TestName + ".log"
- } else {
- // 如果路径钟包含 .jmx ,则直接根据路径创建
- if file, err = os.Create(script.SavePath); err != nil {
- log.Error("create file error :(%v)", err)
- message = "create .jmx error"
- return
- }
- defer file.Close()
- }
- buff = buff.Funcs(template.FuncMap{"unescaped": unescaped})
- buff.Execute(io.Writer(file), script)
- script.SavePath = file.Name()
- }
- script.Ctime = time.Now()
- script.ProjectName = script.TestName
- if JSON, err = json.Marshal(script); err != nil {
- return
- }
- if err = json.Unmarshal([]byte(JSON), &scriptd); err != nil {
- log.Error("sorry,scriptd err(%v)", err)
- return
- }
- scriptd.HeaderString = stringHeaderd
- scriptd.Arguments = ArgmentString
- scriptd.JSONExtractor = script.JSONExtractor
- scriptd.Data = script.Data
- scriptd.FileName = dataFileName
- if script.FileName == "" || script.ParamsName == "" {
- scriptd.UseDataFile = false
- } else {
- scriptd.UseDataFile = true
- }
- if len(Assertionsd) > 0 {
- scriptd.AssertionString = unescaped(SliceToString(Assertionsd, " "))
- }
- return
- }
- //DelScript sel script
- func (s *Service) DelScript(id int) error {
- return s.dao.DelScript(id)
- }
- //UpdateScript update script info
- func (s *Service) UpdateScript(script *model.Script) (updateResult string, err error) {
- var (
- buff *template.Template
- stringHeader string
- Headers []map[string]string
- stringArgument string
- Arguments []map[string]string
- scriptd *model.Script
- u *url.URL
- file *os.File
- sinfoJSON []byte
- sinfoJSONd []byte
- JSONExtractors string
- Assertionsd []string
- )
- if scriptd, err = s.dao.QueryScriptByID(script.ID); err != nil {
- log.Error("query script fail , error %v", err)
- return
- }
- if script.Data == "" {
- buff, err = template.ParseFiles(s.c.Jmeter.JmeterScUcodedTmp)
- } else {
- buff, err = template.ParseFiles(s.c.Jmeter.JmeterScTmp)
- }
- if err != nil {
- log.Error("open file failed! error %v", err)
- return
- }
- if u, err = url.Parse(script.URL); err != nil || u == nil {
- log.Error("sorry,url is not standard,error(%v)", err)
- err = ecode.MelloiUrlParseError
- return
- }
- // 修改laodTime 和 threadsSum
- // ThreadGroup.ramp_time">{{.ReadyTime}}</stringProp>
- if file, err = os.Create(scriptd.SavePath); err != nil {
- log.Error("create file failed! error %v", err)
- return
- }
- defer file.Close()
- if err = json.Unmarshal([]byte(scriptd.APIHeader), &Headers); err != nil {
- updateResult = " scriptd.APIHeade json 不合法"
- log.Error("scriptd.APIHeader switch map failed! error %v", err)
- return
- }
- if err = json.Unmarshal([]byte(scriptd.ArgumentString), &Arguments); err != nil {
- updateResult = "scriptd.ArgumentString json 不合法"
- log.Error("scriptd.ArgumentString switch map failed! error %v", err)
- return
- }
- if len(scriptd.OutputParamsMap) > 0 && scriptd.OutputParams != "" && scriptd.OutputParams != "[{\"\":\"\"}]" && scriptd.OutputParams != "[]" {
- if err = json.Unmarshal([]byte(scriptd.OutputParams), &scriptd.OutputParamsMap); err != nil {
- log.Error("scriptd.OutputParams switch map failed! error %v", err)
- return
- }
- }
- stringHeader = SetHeaders(Headers, scriptd.ID)
- stringArgument = SetArguments(Arguments, scriptd.ID)
- if len(scriptd.OutputParamsMap) > 0 && scriptd.OutputParams != "[{\"\":\"\"}]" && scriptd.OutputParams != "[]" && script.OutputParams != "" {
- if JSONExtractors, err = s.SetJSONExtractors(scriptd); err != nil {
- return
- }
- if JSONExtractors != "" {
- script.JSONExtractor = unescaped(JSONExtractors)
- }
- }
- scriptd.HeaderString = unescaped(stringHeader)
- scriptd.Arguments = unescaped(stringArgument)
- scriptd.Data = script.Data
- scriptd.Assertion = script.Assertion
- scriptd.ReadyTime = script.ReadyTime
- scriptd.ThreadsSum = script.ThreadsSum
- scriptd.LoadTime = script.LoadTime
- scriptd.ProcType = u.Scheme
- scriptd.Domain = u.Hostname()
- scriptd.Port = u.Port()
- scriptd.TestName = script.TestName
- scriptd.FileName = scriptd.ScriptPath + scriptd.FileName
- if scriptd.Assertion != "" {
- scriptd.UseAssertion = true
- Assertions := strings.Split(scriptd.Assertion, "|")
- for _, assert := range Assertions {
- Assertionsd = append(Assertionsd, model.AssertionStart+assert+model.AssertionEnd)
- }
- scriptd.AssertionString = unescaped(SliceToString(Assertionsd, " "))
- }
- //判断是否使用内置参数
- if sinfoJSON, err = json.Marshal(script); err != nil {
- log.Error("script err :(%s)", err)
- return
- }
- if sinfoJSONd, err = json.Marshal(scriptd); err != nil {
- log.Error("script err :(%s)", err)
- return
- }
- sc := string(sinfoJSON) + string(sinfoJSONd)
- if strings.Contains(sc, "${mid}") || strings.Contains(sc, "${access_key}") || script.UseSign {
- scriptd.UseBuiltinParam = true
- }
- if u.RawQuery == "" {
- scriptd.Path = u.Path
- scriptd.RowQuery = "appkey=" + conf.Conf.Melloi.AppkeyProd
- } else {
- scriptd.Path = u.Path + "?" + u.RawQuery
- scriptd.RowQuery = u.RawQuery + "&appkey=" + conf.Conf.Melloi.AppkeyProd
- }
- if scriptd.UseSign {
- scriptd.Path = u.Path + "?${params}"
- }
- // POST 请求的 binary 逻辑
- if scriptd.MultipartFile != "" && scriptd.MultipartPath != "" {
- scriptd.UseMultipart = true
- multiPartInfo := SetMultiPartInfo(scriptd)
- scriptd.MultiPartInfo = unescaped(multiPartInfo)
- }
- if scriptd.IsAsync {
- scriptd.AsyncInfo = unescaped(model.AsyncInfo)
- }
- buff = buff.Funcs(template.FuncMap{"unescaped": unescaped})
- buff.Execute(io.Writer(file), scriptd)
- script.ProjectName = script.TestName
- script.KeepAlive = scriptd.KeepAlive
- if err = s.dao.UpdateScriptPart(script); err != nil {
- log.Error("s.dao.UpdateScript err :(%v)", err)
- return
- }
- updateResult = "success"
- return
- }
- //UpdateScriptAll update script all
- func (s *Service) UpdateScriptAll(script *model.Script, scene *model.Scene) (updateResult string, err error) {
- var (
- scriptd model.Script
- paramList *model.ParamList
- isEdit = true
- groupId int
- runOrder int
- fileWrite bool
- )
- id := script.ID
- script.ID = 0
- // script test_type 为1 表示 http 脚本
- if script.TestType == model.HTTP_SCRIPT_TYPE {
- fileWrite = true
- }
- if _, scriptd, err = s.AddScript(script, fileWrite); err != nil {
- log.Error("sorry, addScript fail : (%v)", err)
- return
- }
- if script.TestType == model.SCENE_SCRIPT_TYPE {
- //根据接口参数依赖,计算出接口分组与执行顺序
- if paramList, err = s.dao.QueryParams(&scriptd, scene); err != nil {
- log.Error("s.dao.QueryParams err :(%v)", err)
- return
- }
- groupId, runOrder = GroupOrderByEdit(isEdit, id, &scriptd, scene, paramList)
- if groupId != 0 && runOrder != 0 {
- scriptd.GroupID = groupId
- scriptd.RunOrder = runOrder
- }
- }
- scriptd.ID = id
- scriptd.SavePath = ""
- if scriptd.Loops == -1 {
- headerString := MapToString(scriptd.Headers)
- argumentString := MapToString(scriptd.ArgumentsMap)
- scriptd.OutputParams = MapToString(scriptd.OutputParamsMap)
- scriptd.ArgumentString = argumentString
- scriptd.APIHeader = headerString
- if err = s.dao.UpdateScript(&scriptd); err != nil {
- log.Error("s.dao.UpdateScript err : (%v)", err)
- return
- }
- }
- updateResult = "success"
- return
- }
- //AddJmeterSample add jmeter sample
- func (s *Service) AddJmeterSample(script *model.Script) (result string, err error) {
- var (
- buff *template.Template
- u *url.URL
- b = bytes.NewBuffer(nil)
- JSONExtractors string
- Assertionsd []string
- )
- if script.Data == "" {
- buff, err = template.ParseFiles(s.c.Jmeter.JmeterSampleTmp)
- } else {
- buff, err = template.ParseFiles(s.c.Jmeter.JmeterSamplePostTmp)
- }
- if err != nil {
- log.Info("open template failed! (%v)", err)
- return
- }
- //判断url
- if !strings.Contains(script.URL, "http://") && !strings.Contains(script.URL, "https://") {
- script.URL = "http://" + script.URL
- }
- if script.Delimiter == "" {
- script.Delimiter = ","
- }
- if u, err = url.Parse(script.URL); err != nil || u == nil {
- log.Error("sorry,url is not standard,error(%v)", err)
- err = ecode.MelloiUrlParseError
- return
- }
- stringHeader := SetHeaders(script.Headers, script.ID)
- ArgmentStr := SetArguments(script.ArgumentsMap, script.ID)
- ArgmentString := unescaped(ArgmentStr)
- if len(script.OutputParamsMap) > 0 && script.OutputParams != "[{\"\":\"\"}]" && script.OutputParams != "[]" && script.OutputParams != "" {
- if JSONExtractors, err = s.SetJSONExtractors(script); err != nil {
- return
- }
- if JSONExtractors != "" {
- script.JSONExtractor = unescaped(JSONExtractors)
- }
- }
- script.Arguments = ArgmentString
- stringHeaderd := unescaped(stringHeader)
- script.HeaderString = stringHeaderd
- script.ProcType = u.Scheme
- script.Domain = u.Hostname()
- script.Port = u.Port()
- script.FileName = script.ScriptPath + script.FileName
- if u.RawQuery == "" {
- script.Path = u.Path
- script.RowQuery = "appkey=" + conf.Conf.Melloi.AppkeyProd
- } else {
- script.Path = u.Path + "?" + u.RawQuery
- script.RowQuery = u.RawQuery + "&appkey=" + conf.Conf.Melloi.AppkeyProd
- }
- if script.UseSign {
- script.Path = u.Path + "?${params}"
- }
- if script.Assertion != "" {
- script.UseAssertion = true
- Assertions := strings.Split(script.Assertion, "|")
- for _, assert := range Assertions {
- Assertionsd = append(Assertionsd, model.AssertionStart+assert+model.AssertionEnd)
- }
- script.AssertionString = unescaped(SliceToString(Assertionsd, " "))
- }
- if script.ConstTimer > 0 && script.RandomTimer == 0 {
- constTimer := strings.Replace(model.ConstTimer, "1000", strconv.Itoa(script.ConstTimer), -1)
- script.ConstTimerInfo = unescaped(constTimer)
- }
- if script.ConstTimer > 0 && script.RandomTimer > 0 {
- constTimerd := strings.Replace(model.RandomTimer, "1000", strconv.Itoa(script.ConstTimer), -1)
- randomTimerd := strings.Replace(constTimerd, "500", strconv.Itoa(script.RandomTimer), -1)
- script.RandomTimerInfo = unescaped(randomTimerd)
- }
- buff = buff.Funcs(template.FuncMap{"unescaped": unescaped})
- buff.Execute(b, script)
- result = b.String()
- return
- }
- //AddThreadGroup add thread group
- func (s *Service) AddThreadGroup(script *model.Script, sceneType int) (result string, err error) {
- var (
- buff *template.Template
- scriptd model.Script
- b = bytes.NewBuffer(nil)
- )
- if _, scriptd, err = s.AddScript(script, false); err != nil {
- log.Error("s.AddScript err :(%v)", err)
- return
- }
- //并行get
- if sceneType == 1 && script.Data == "" {
- buff, err = template.ParseFiles(s.c.Jmeter.JmeterThGroupTmp)
- }
- //并行post
- if sceneType == 1 && script.Data != "" {
- buff, err = template.ParseFiles(s.c.Jmeter.JmeterThGroupPostTmp)
- }
- //串行get
- if sceneType == 2 && script.Data == "" {
- buff, err = template.ParseFiles(s.c.Jmeter.JmeterThGroupDuliTmp)
- }
- //串行 post
- if sceneType == 2 && script.Data != "" {
- buff, err = template.ParseFiles(s.c.Jmeter.JmeterThGroupPostDuliTmp)
- }
- if err != nil {
- log.Info("open template failed! (%v)", err)
- return
- }
- buff = buff.Funcs(template.FuncMap{"unescaped": unescaped})
- scriptd.FileName = scriptd.ScriptPath + scriptd.FileName
- buff.Execute(b, scriptd)
- result = b.String()
- return
- }
- //GetThreadGroup get thread group
- func (s *Service) GetThreadGroup(scrThreadGroup model.ScrThreadGroup) (threadGroup string, err error) {
- var (
- threadGroups []string
- IsSerial bool
- groups []int
- groupMap = make(map[int]int)
- RunOrders []int
- scriptsd []*model.Script
- scriptsds [][]*model.Script
- threadGroupd string
- threadGroupds []string
- threadSamples []string
- threadSample string
- threadSamplesd string
- threadGrou string
- )
- for _, script := range scrThreadGroup.Scripts {
- RunOrders = append(RunOrders, script.RunOrder)
- groupMap[script.GroupID]++
- if script.APIHeader != "" && script.APIHeader != "[{\"\":\"\"}]" {
- if err = json.Unmarshal([]byte(script.APIHeader), &script.Headers); err != nil {
- log.Error("get script header err : (%v), scriptId:(%d)", err, script.ID)
- }
- }
- if script.ArgumentString != "" && script.ArgumentString != "[{\"\":\"\"}]" {
- if err = json.Unmarshal([]byte(script.ArgumentString), &script.ArgumentsMap); err != nil {
- log.Error("get script argument err: (%v), scriptId:(%d)", err, script.ID)
- }
- }
- if script.OutputParams != "" && script.OutputParams != "[{\"\":\"\"}]" && script.OutputParams != "[]" {
- if err = json.Unmarshal([]byte(script.OutputParams), &script.OutputParamsMap); err != nil {
- log.Error("get script OutputParams err: (%v), scriptId:(%d)", err, script.ID)
- }
- }
- }
- for _, order := range RunOrders {
- if order > 1 {
- IsSerial = true
- break
- }
- }
- for k := range groupMap {
- groups = append(groups, k)
- }
- //并行脚本逻辑
- if !IsSerial {
- for _, script := range scrThreadGroup.Scripts {
- if threadGrou, err = s.AddThreadGroup(script, 1); err != nil {
- log.Error("d.GetThroupGroupJmeter: error(%v)", err)
- return
- }
- threadGroups = append(threadGroups, threadGrou)
- }
- for _, thgroup := range threadGroups {
- threadGroupd = threadGroupd + thgroup
- }
- threadGroup = threadGroupd
- return
- }
- //串行脚本逻辑
- for _, group := range groups {
- for i := 0; i < len(scrThreadGroup.Scripts); i++ {
- if scrThreadGroup.Scripts[i].GroupID == group {
- scriptsd = append(scriptsd, scrThreadGroup.Scripts[i])
- }
- }
- scriptsds = append(scriptsds, scriptsd)
- scriptsd = []*model.Script{}
- }
- // 每个线程组内的接口,按照接口的 RunOrder 属性进行排序
- for _, scriptsd := range scriptsds {
- for i := 0; i < len(scriptsd)-1; i++ {
- for j := i + 1; j < len(scriptsd); j++ {
- if scriptsd[i].RunOrder > scriptsd[j].RunOrder {
- tmp := scriptsd[i]
- scriptsd[i] = scriptsd[j]
- scriptsd[j] = tmp
- }
- }
- }
- }
- for k := 0; k < len(scriptsds); k++ {
- if len(scriptsds[k]) == 1 {
- if threadGroupd, err = s.AddThreadGroup(scriptsds[k][0], 1); err != nil {
- log.Error("d.GetThroupGroupJmeter: error(%v)", err)
- return
- }
- } else {
- //生成一个线程组,再往线程组插入 sample
- if threadGroupd, err = s.AddThreadGroup(scriptsds[k][0], 2); err != nil {
- log.Error(" s.AddScript err :(%v)", err)
- return
- }
- for i := 0; i < len(scriptsds[k]); i++ {
- //从第二个接口开始生成 sample
- if i > 0 {
- if threadSample, err = s.AddJmeterSample(scriptsds[k][i]); err != nil {
- log.Error("s.dao.GetThroupSeriesSample err :(%v)", err)
- return
- }
- threadSamples = append(threadSamples, threadSample)
- }
- }
- }
- for j := 0; j < len(threadSamples); j++ {
- threadSamplesd = threadSamplesd + threadSamples[j]
- }
- //生成一个 线程组
- threadGroupM := strings.Replace(threadGroupd, "+-+-+*", threadSamplesd, -1)
- //多个线程组
- threadGroupds = append(threadGroupds, threadGroupM)
- threadSamples = []string{}
- threadSamplesd = ""
- }
- for _, threadGrou := range threadGroupds {
- threadGroup = threadGroup + threadGrou
- }
- return
- }
- //URLCheck url check
- func (s *Service) URLCheck(script *model.Script) (urlEncode *model.URLEncode, err error) {
- var (
- tempUrl *url.URL
- )
- urlEncode = &model.URLEncode{}
- if strings.Contains(script.URL, "?") {
- if tempUrl, err = url.Parse(script.URL); err != nil {
- return
- }
- params := tempUrl.RawQuery
- paramList := strings.Split(params, "&")
- for _, param := range paramList {
- value := strings.Split(param, "=")[1]
- var myMap map[string]interface{}
- if err = json.Unmarshal([]byte(value), &myMap); err != nil {
- urlEncode.ParamsType = "nojson"
- urlEncode.NewUrl = script.URL
- continue
- }
- urlEncode.ParamsType = "json"
- newParams := tempUrl.Query().Encode()
- urlEncode.NewUrl = strings.Split(script.URL, "?")[0] + "?" + newParams
- return
- }
- }
- return
- }
- //AddTimer add Timer
- func (s *Service) AddTimer(script *model.Script) error {
- return s.dao.AddScriptTimer(script)
- }
- //SetHeaders set headers
- func SetHeaders(Headers []map[string]string, scriptId int) (stringHeader string) {
- var (
- stringHeaders []string
- HeaderString string
- )
- // 表示直接生成的脚本,非修改
- if scriptId == 0 {
- for _, header := range Headers {
- k := header["key"]
- v := header["value"]
- HeaderString = model.HeaderStart + k + model.HeaderMid + v + model.HeaderEnd
- stringHeaders = append(stringHeaders, HeaderString)
- }
- } else {
- //修改脚本,header 从数据库取出的
- for _, header := range Headers {
- for k, v := range header {
- HeaderString = model.HeaderStart + k + model.HeaderMid + v + model.HeaderEnd
- stringHeaders = append(stringHeaders, HeaderString)
- }
- }
- }
- for _, str := range stringHeaders {
- stringHeader = stringHeader + str + "\r\n"
- }
- return
- }
- //SetArguments set argument
- func SetArguments(Arguments []map[string]string, scriptId int) (stringArgument string) {
- var (
- stringArguments []string
- ArgumentString string
- )
- // 表示直接生成的脚本,非修改
- if scriptId == 0 {
- for _, argument := range Arguments {
- k := argument["key"]
- v := argument["value"]
- ArgumentString = model.ElementPropName + k + model.HTTPArgument + model.HTTPArgumentEncode + model.ArgumentStart + v + model.ArgumentMid + k + model.ArgumentEnd
- stringArguments = append(stringArguments, ArgumentString)
- }
- } else {
- //修改脚本,argument 从数据库取出的
- for _, argument := range Arguments {
- for k, v := range argument {
- ArgumentString = model.ElementPropName + k + model.HTTPArgument + model.HTTPArgumentEncode + model.ArgumentStart + v + model.ArgumentMid + k + model.ArgumentEnd
- stringArguments = append(stringArguments, ArgumentString)
- }
- }
- }
- for _, str := range stringArguments {
- stringArgument = stringArgument + str + "\r\n"
- }
- return
- }
- //SetJSONExtractor set JSONExtractorgo
- func (s *Service) SetJSONExtractor(jsonExt model.JSONExtractor) (JSONExtractor string, err error) {
- var (
- buff *template.Template
- b = bytes.NewBuffer(nil)
- )
- buff, err = template.ParseFiles(s.c.Jmeter.JSONExtractorTmp)
- if err != nil {
- log.Info("open template failed! (%v)", err)
- return
- }
- buff = buff.Funcs(template.FuncMap{"unescaped": unescaped})
- buff.Execute(b, jsonExt)
- JSONExtractor = b.String()
- return
- }
- //SetJSONExtractors Set JSONExtractors
- func (s *Service) SetJSONExtractors(script *model.Script) (JSONExtractors string, err error) {
- var (
- JSONExtractords []string
- jsonExt model.JSONExtractor
- JSONExtractord string
- )
- // 表示直接生成的脚本,非修改
- if script.ID == 0 {
- for _, OutputParam := range script.OutputParamsMap {
- jsonExt.JSONName = OutputParam["key"]
- jsonExt.JSONPath = OutputParam["value"]
- if jsonExt.JSONName != "" && jsonExt.JSONPath != "" {
- if JSONExtractord, err = s.SetJSONExtractor(jsonExt); err != nil {
- log.Error("SetJSONExtractor err :(%v) ", err)
- return
- }
- JSONExtractords = append(JSONExtractords, JSONExtractord)
- }
- }
- } else {
- //修改脚本,header 从数据库取出的
- for _, OutputParam := range script.OutputParamsMap {
- for k, v := range OutputParam {
- jsonExt.JSONName = k
- jsonExt.JSONPath = v
- }
- if jsonExt.JSONName != "" && jsonExt.JSONPath != "" {
- if JSONExtractord, err = s.SetJSONExtractor(jsonExt); err != nil {
- log.Error("SetJSONExtractor err :(%v) ", err)
- return
- }
- JSONExtractords = append(JSONExtractords, JSONExtractord)
- }
- }
- }
- JSONExtractors = SliceToString(JSONExtractords, " ")
- return
- }
- //SetMultiPartInfo Set MultiPartInfo
- func SetMultiPartInfo(script *model.Script) (multiPartInfo string) {
- multiPartInfo = model.MultipartName + script.MultipartPath + script.MultipartFile + model.MultipartFilePath +
- script.MultipartPath + script.MultipartFile + model.MultipartFilePathd + script.MultipartParam +
- model.MultipartMimetype + script.MimeType + model.MultipartEnd
- return
- }
- //MapToString map to string
- func MapToString(strMapArr []map[string]string) (headerString string) {
- for _, strMap := range strMapArr {
- k, keyOk := strMap["key"]
- v, valueOK := strMap["value"]
- if keyOk != valueOK {
- return
- }
- if keyOk {
- delete(strMap, "key")
- delete(strMap, "value")
- strMap[k] = v
- }
- }
- headers, _ := json.Marshal(strMapArr)
- headerString = string(headers)
- return
- }
- //StringToMap string to map
- func StringToMap(headerString string) (mapHeaders []map[string]string) {
- if headerString != "[{\"\":\"\"}]" {
- if err := json.Unmarshal([]byte(headerString), &mapHeaders); err != nil {
- return
- }
- }
- return
- }
- func unescaped(x string) interface{} {
- return template.HTML(x)
- }
- //HostInfo add hostinfo
- func HostInfo(domain string, apiHeader string) (hostInfo string) {
- mapHeaders := StringToMap(apiHeader)
- reg := regexp.MustCompile(`.com|.co`)
- strs := reg.FindAllString(domain, -1)
- if len(strs) != 0 {
- for _, str := range strs {
- if (strings.Contains(str, ".com") || strings.Contains(str, ".co")) && strings.Contains(apiHeader, domain) { //绑定至用户指定的ip
- for _, header := range mapHeaders {
- for host := range header {
- conf.Conf.Paas.HostInfo = domain + ":" + host
- hostInfo = conf.Conf.Paas.HostInfo
- }
- }
- } else if strings.Contains(str, ".com") || strings.Contains(str, ".co") { //默认绑定至172.22.22.222
- conf.Conf.Paas.HostInfo = domain + ":" + conf.Conf.Melloi.DefaultHost
- hostInfo = conf.Conf.Paas.HostInfo
- }
- }
- }
- return
- }
- //HostInfoList add hostinfo list
- func HostInfoList(scripts []*model.Script) (hostInfo string) {
- for _, script := range scripts {
- mapHeaders := StringToMap(script.APIHeader)
- reg := regexp.MustCompile(`.com|.co`)
- strs := reg.FindAllString(script.Domain, -1)
- if len(strs) != 0 {
- for _, str := range strs {
- if (strings.Contains(str, ".com") || strings.Contains(str, ".co")) && strings.Contains(script.APIHeader, script.Domain) { //绑定至用户指定的ip
- for _, header := range mapHeaders {
- for host := range header {
- conf.Conf.Paas.HostInfo = script.Domain + ":" + host
- hostInfo = conf.Conf.Paas.HostInfo
- }
- }
- } else if strings.Contains(str, ".com") || strings.Contains(str, ".co") { //默认绑定至172.22.22.222
- hostInfo = hostInfo + script.Domain + ":" + conf.Conf.Melloi.DefaultHost + ","
- }
- }
- }
- }
- conf.Conf.Paas.HostInfo = hostInfo
- return
- }
- //HostInfoByUploadSc host info By UploadSc
- func HostInfoByUploadSc(domain string) (hostInfo string) {
- domains := strings.Split(domain, ",")
- for _, dom := range domains {
- hostInfo = hostInfo + dom + ":" + conf.Conf.Melloi.DefaultHost + ","
- }
- conf.Conf.Paas.HostInfo = hostInfo
- return
- }
- // GroupOrder Group Order
- func GroupOrder(isEdit bool, script *model.Script, scene *model.Scene, paramList *model.ParamList) (groupId int, runOrder int) {
- var (
- flag = 0
- //tmpIndex = 0
- u *url.URL
- err error
- )
- if u, err = url.Parse(script.URL); err != nil || u == nil {
- log.Error("sorry, url is wrong, please check. error(%v)", err)
- err = ecode.MelloiUrlParseError
- return
- }
- if len(paramList.ParamList) > 0 {
- for _, paramList := range paramList.ParamList {
- tempList := strings.Split(u.RawQuery, "&")
- tempHeaderList := strings.Split(script.APIHeader, ",")
- tempArgumentList := strings.Split(script.ArgumentString, ",")
- tempDataList := strings.Split(script.Data, ",")
- if u.RawQuery == "" && (script.APIHeader == "[{\"\":\"\"}]" || script.APIHeader == "[]") && script.Data == "" && (script.ArgumentString == "[{\"\":\"\"}]" || script.ArgumentString == "[]") {
- if scene.SceneType == 1 || scene.SceneType == 0 {
- groupId = paramList.GroupID + 1
- runOrder = 1
- continue
- } else if scene.SceneType == 2 {
- groupId = paramList.GroupID
- runOrder = paramList.RunOrder + 1
- continue
- }
- }
- //url params check
- if u.RawQuery != "" {
- if groupId, runOrder, flag, err = OrderEach(isEdit, tempList, paramList, scene.SceneType); err != nil || (runOrder != 1 && flag == 0) {
- return
- //} else if flag == 2 {
- // tmpIndex = index
- // break
- }
- }
- //APIHeader params check
- if script.APIHeader != "[{\"\":\"\"}]" && script.APIHeader != "[]" {
- if groupId, runOrder, flag, err = OrderEach(isEdit, tempHeaderList, paramList, scene.SceneType); err != nil || (runOrder != 1 && flag == 0) {
- return
- }
- }
- //ArgumentString params check
- if script.ArgumentString != "[{\"\":\"\"}]" && script.ArgumentString != "[]" {
- if groupId, runOrder, flag, err = OrderEach(isEdit, tempArgumentList, paramList, scene.SceneType); err != nil || (runOrder != 1 && flag == 0) {
- return
- }
- }
- //body params check
- if script.Data != "" {
- if groupId, runOrder, flag, err = OrderEach(isEdit, tempDataList, paramList, scene.SceneType); err != nil || (runOrder != 1 && flag == 0) {
- return
- }
- }
- }
- //for i := tmpIndex; i < len(paramList.ParamList); i++ {
- // if err = s.dao.UpdateRunOrder(paramList.ParamList[i].); err != nil {
- // log.Error("s.dao.UpdateScript err :(%v)", err)
- // return
- // }
- //}
- } else if script.GroupID != 0 {
- groupId = script.GroupID
- runOrder = 1
- } else {
- groupId = 1
- runOrder = 1
- }
- return
- }
- // GroupOrderByEdit Group Order By Edit
- func GroupOrderByEdit(isEdit bool, id int, script *model.Script, scene *model.Scene, paramList *model.ParamList) (groupId int, runOrder int) {
- var (
- flag = 0
- //tmpIndex = 0
- u *url.URL
- err error
- )
- if u, err = url.Parse(script.URL); err != nil || u == nil {
- log.Error("sorry, url is wrong, please check. error(%v)", err)
- err = ecode.MelloiUrlParseError
- return
- }
- if len(paramList.ParamList) > 0 {
- for _, paramList := range paramList.ParamList {
- if paramList.ID != id {
- tempList := strings.Split(u.RawQuery, "&")
- tempHeaderList := strings.Split(script.APIHeader, ",")
- tempArgumentList := strings.Split(script.ArgumentString, ",")
- tempDataList := strings.Split(script.Data, ",")
- //url params check
- if u.RawQuery != "" {
- if groupId, runOrder, flag, err = OrderEach(isEdit, tempList, paramList, scene.SceneType); err != nil || (runOrder != 1 && flag == 0) {
- return
- }
- }
- //APIHeader params check
- if script.APIHeader != "[{\"\":\"\"}]" && script.APIHeader != "[]" {
- if groupId, runOrder, flag, err = OrderEach(isEdit, tempHeaderList, paramList, scene.SceneType); err != nil || (runOrder != 1 && flag == 0) {
- return
- }
- }
- //ArgumentString params check
- if script.ArgumentString != "[{\"\":\"\"}]" && script.ArgumentString != "[]" {
- if groupId, runOrder, flag, err = OrderEach(isEdit, tempArgumentList, paramList, scene.SceneType); err != nil || (runOrder != 1 && flag == 0) {
- return
- }
- }
- //body params check
- if script.Data != "" {
- if groupId, runOrder, flag, err = OrderEach(isEdit, tempDataList, paramList, scene.SceneType); err != nil || (runOrder != 1 && flag == 0) {
- return
- }
- }
- }
- }
- }
- return
- }
- // OrderEach Order Each
- func OrderEach(isEdit bool, strList []string, paramList *model.Params, sceneType int) (groupId int, runOrder int, flag int, err error) {
- for _, paramPage := range strList {
- if strings.Contains(paramPage, "$") {
- tempParam := strings.Split(paramPage, "$")[1]
- param := RegularTrans(tempParam)
- //several params
- if strings.Contains(paramList.OutputParams, ",") {
- dbTempList := strings.Split(paramList.OutputParams, ",")
- for _, dbParam := range dbTempList {
- flag = 0
- finalParam := strings.Split(strings.Split(dbParam, "\":\"")[0], "\"")[1]
- if param == finalParam {
- groupId = paramList.GroupID
- runOrder = paramList.RunOrder + 1
- return
- }
- if !isEdit {
- if sceneType == 1 || sceneType == 0 {
- groupId = paramList.GroupID + 1
- runOrder = 1
- } else if sceneType == 2 {
- groupId = paramList.GroupID
- runOrder = paramList.RunOrder + 1
- flag = 1
- }
- }
- }
- } else { //single param
- dbParam := paramList.OutputParams
- finalParam := strings.Split(strings.Split(dbParam, "\":\"")[0], "\"")[1]
- if param == finalParam {
- groupId = paramList.GroupID
- runOrder = paramList.RunOrder + 1
- return
- }
- if !isEdit {
- if sceneType == 1 || sceneType == 0 {
- groupId = paramList.GroupID + 1
- runOrder = 1
- } else if sceneType == 2 {
- groupId = paramList.GroupID
- runOrder = paramList.RunOrder + 1
- flag = 1
- }
- }
- }
- } else {
- if !isEdit {
- if sceneType == 1 || sceneType == 0 {
- groupId = paramList.GroupID + 1
- runOrder = 1
- } else if sceneType == 2 {
- groupId = paramList.GroupID
- runOrder = paramList.RunOrder + 1
- flag = 1
- }
- }
- }
- }
- return
- }
- // RegularTrans Regular Trans
- func RegularTrans(tempStr string) (targetStr string) {
- reg := regexp.MustCompile(`[\w]+`)
- str := reg.FindAllString(tempStr, -1)
- if len(str) != 0 {
- targetStr = str[0]
- }
- return targetStr
- }
|