【Golang】基于录制,自动生成go test接口自动化用例

news2024/11/15 17:33:38

目录

背景

框架

ginkgo初始化

抓包&运行脚本

目录说明

∮./business

∮./conf

∮./utils

∮./testcase

testcase 用例目录结构规则

¶示例

实现思路

解析Har数据

定义结构体

解析到json

转换请求数据

转换请求

转换请求参数

写业务请求数据

写gotest测试用例数据

初始化写入suit文件

格式化测试文件

install生成的业务请求目录

格式化响应断言

可能遇到的问题

完整代码

详细代码如下,注释已经给得比较清晰:

 资料获取方法


背景

之前写过一篇博客,介绍怎么用Python通过解析抓包数据,完成自动化用例的编写。最近这段时间在使用go test,所以就在想能不能也使用代码来生成自动化用例,快速提升测试用例覆盖率。说干就干。

框架

首先介绍一下我们使用的测框架:

信息安装备注
GO版本go1.12.9 darwin/amd64
测试框架ginkgogo get -u github.com/onsi/ginkgo/ginkgo
断言库testify/assertgo get github.com/stretchr/testify官方配套的断言库是gomega

ginkgo初始化

  • 初始化: cd path/to/package/you/want/to/test && ginkgo bootstrap
  • 创建示例用例:ginkgo generate (需要手动添加测试用例)
  • 运行测试: go testor ginkgo

注:-v加上参数可打印运行信息

抓包&运行脚本

  • 使用抓包工具(如Charles)抓包,把数据包导出为har格式,保存在当前目录下
    • 如何安装抓包工具在本文就不赘述了,抓包,过滤出想要的数据,导出,保存的格式注意选择为har

  • 根据实际情况修改全局变量信息,如bizBaseFolder、serverName、userFile等
  • 使用go run gentest.go运行脚本即可

目录说明

然后我们一起来了解一下我们的目录结构定义。

∮./business

业务封装,封装具体的请求及测试数据

∮./conf

配置信息及接口请求参数初始化封装

∮./utils

公共函数封装

∮./testcase

接口测试用例目录

testcase 用例目录结构规则

基本原则: 根据项目、模块、接口功能逐级区分,建议最多3层目录层级

¶示例
  1. 软件测试论坛项目组/论坛项目/帖子模块/创建帖子接口:
    • CN_TestBBS/bbs/post/post_test.go
  2. 基础账号项目/首页项目/白名单接口:
    • CN_account/homepage/whitelist_test.go

实现思路

按照har文件的JSON结构定义对应的结构体,然后解析数据,生成请求数据,生成断言数据,初始化测试套suite,格式化代码,初始化包引用信息。

解析Har数据

定义结构体
Log struct {
		version string
		creator string
		Entries []struct {
			startedDateTime string
			time            string
			Request         struct {
				...
解析到json
func UnpackHar(har []byte) (logs *Har) {
	err := json.Unmarshal(har, &logs)
	if err != nil {
		fmt.Println(err)
	}
	return
}

转换请求数据

转换请求
转换请求参数

GET

// 格式化请求参数为标准请求string
getReqParam := make(map[string]interface{}, 1)
if len(v.Request.QueryString) > 0 {
    for _, query := range v.Request.QueryString {
        getReqParam[query.Name] = query.Value
    }
}
// 获取postReq数据
postReqParamStr := v.Request.PostData.Text

if v.Request.Method == "GET" {
    paramstr = genGetParam(InterfaceName, getReqParam)
}
func genGetParam(interfaceName string, param map[string]interface{}) (formatParam string) {

	// 对于请求参数的value值为 数组
	if len(param) > 0 {
		for k, v := range param {
			switch vv := v.(type) {
			case []interface{}:
				fmt.Sprintf(k, "is an array:", vv)
				temp, _ := json.Marshal(param)
				formatParam = fmt.Sprintf("%sParam = `%s`", interfaceName, fmt.Sprintf("%v", string(temp)))
				return
			default:
				// fmt.Println(k, "is of a type didn't handle")
			}
		}
	}
	temp, _ := json.Marshal(param)
	formatParam = fmt.Sprintf(`%sParam = map[string]interface{} %s`, interfaceName, fmt.Sprintf("%v", string(temp)))
	return
}

POST

postReqParamStr := v.Request.PostData.Text
    if v.Request.Method == "POST" {
    paramstr = genPostParam(InterfaceName, postReqParamStr)
}
func genPostParam(interfaceName string, postReqParamStr string) (formatParam string) {
	// formatParam = fmt.Sprintf(`%sParam = map[string]interface{} %s`, interfaceName, param)
	// fmt.Sprintf("%v", string(temp))
	postReqParam := make(map[string]interface{}, 1)

	if len(postReqParamStr) > 0 {
		// 判断第一个字符是否为{}, 做传递数据为数组[]的兼容
		if []rune(postReqParamStr)[0] == '{' {
			var x interface{}
			err := json.Unmarshal([]byte(postReqParamStr), &x)
			if err != nil {
				fmt.Println("err", err)
			}

			postReqParam = x.(map[string]interface{})
			// fmt.Println(postReqParam)
			// 判断value中是否存在数组
			for k, v := range postReqParam {
				switch vv := v.(type) {
				// switch vv := v.(type) {
				case []interface{}:
					fmt.Sprintf(k, "is an array:", vv)
					// param[k] = fmt.Sprintf("`%s`", vv)
					temp, _ := json.Marshal(postReqParam)
					formatParam = fmt.Sprintf("%sParam = `%s`", interfaceName, fmt.Sprintf("%v", string(temp)))
					paramType = "string"
					return
				default:
					formatParam = genGetParam(interfaceName, postReqParam)
					// fmt.Println(k, "is of a type didn't handle")
				}
			}
			// 如果为数组,做如下处理
		} else {
			var y []interface{}
			err := json.Unmarshal([]byte(postReqParamStr), &y)
			if err != nil {
				fmt.Println("err", err)
			}

			postReqParam = y[0].(map[string]interface{})
			temp, _ := json.Marshal(postReqParam)

			// 声明请求类型
			paramType = "[]map[string]interface{}"
			formatParam = fmt.Sprintf(`%sParam =[]map[string]interface{}{%s}`, interfaceName, string(temp))
			// 无法使用 判断类型 Param := utils.MapDeepCopy(Hebinz123.XlppcPlaylistApiV1RemarkDelParam)
		}
	}
	// temp, _ := json.Marshal(param)
	// formatParam = fmt.Sprintf(`%sParam = map[string]interface{} %s`, interfaceName, fmt.Sprintf("%v", string(temp)))
	return
}

写业务请求数据

写gotest测试用例数据

格式化请求参数为标准请求string。

初始化写入suit文件

这里有一个注意点,Test后紧接的数据必须是大写。

格式化测试文件

使用goimports库初始化导入数据包。

install生成的业务请求目录

使用go install目录生成导入业务请求目录

格式化响应断言

使用类型判断格式化接口返回数据为标准断言string。

可能遇到的问题

  • 初始化读取文件的存储buf的size和其实际大小不一致时,json 解析出错“invalid character '\x00' after top-level value”
  • go install 执行失败,导致测试用例无法找到其依赖包
  • get请求,post请求参数在har文件中的存储方式不一致,获取数据的方式差别很大
  • 域名及接口命名规则不一致,-.等等风格不一致
  • 测试suite 紧接Test后方的字符需为大写的字母,否则服务无法被发现,所以需要做大小写转换

完整代码

详细代码如下,注释已经给得比较清晰:

package main

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

var (
	baseDomain         = "test.bbs.com"    // 测试域名,用于切割出请求路径
	bizBaseFolder      = "business/CN_bbs" //业务请求目录
	testCaseBaseFolder = "testcase/CN_bbs" // 测试用例目录
	serverName         = "cinecismGo"      // 服务名
	paramType          = ""
)

func main() {
	userFile := "20190917-cinecismgo.har" // 抓包文件地址
	fl, err := os.Open(userFile)
	if err != nil {
		fmt.Println(userFile, err)
		return
	}
	defer fl.Close()

	// 读取har数据
	fileInfo, err := fl.Stat()
	buf := make([]byte, fileInfo.Size()) // “invalid character '\x00' after top-level value”
	fl.Read(buf)
	data := UnpackHar(buf)

	for _, v := range data.Log.Entries {
		// 每一个循环初始化请求参数类型
		paramType = "map[string]interface{}"
		paramstr := ""

		// 初始化 请求path,生成标准请求接口名称
		pathStr, path := initPath(v.Request.URL)
		InterfaceName := formatInterfaceName(pathStr)

		// 格式化请求参数为标准请求string
		getReqParam := make(map[string]interface{}, 1)
		if len(v.Request.QueryString) > 0 {
			for _, query := range v.Request.QueryString {
				getReqParam[query.Name] = query.Value
			}
		}
		// 获取postReq数据
		postReqParamStr := v.Request.PostData.Text

		if v.Request.Method == "GET" {
			paramstr = genGetParam(InterfaceName, getReqParam)
		}
		if v.Request.Method == "POST" {
			paramstr = genPostParam(InterfaceName, postReqParamStr)
		}

		// 格式化接口返回数据为标准断言string
		text, _ := base64.StdEncoding.DecodeString(v.Response.Content.Text)
		responseAssertStr := initAssert(text)

		// 创建业务请求文件、测试用例文件
		run(serverName, path, InterfaceName, v.Request.Method, responseAssertStr, paramstr)

		// 【待补充】handle Headers数据
		// fmt.Println(initHeaders(data))
	}
}

func initAssert(text []byte) (responseAssertStr string) {
	if len(text) > 0 {
		var Response interface{}
		err := json.Unmarshal(text, &Response)
		if err != nil {
			fmt.Println("err", err)
		}

		responseMap := Response.(map[string]interface{})
		res := []string{}
		for k, v := range responseMap {
			switch vv := v.(type) {
			case string:
				// fmt.Println(k, "is string", vv)
				res = append(res, fmt.Sprintf("%s, _ := js.Get(\"%s\").String() \n assert.Equal(%s, `%v`)", k, k, k, string(vv)))
			case int64:
				// fmt.Println(k, "is int", vv)
				res = append(res, fmt.Sprintf("%s, _ := js.Get(\"%s\").Int() \n assert.Equal(%s, %v)", k, k, k, string(vv)))
			case float64:
				// fmt.Println(k, "is float64", vv)
				res = append(res, fmt.Sprintf("%s, _ := js.Get(\"%s\").Int() \n assert.Equal(%s, %v)", k, k, k, vv))
			case bool:
				// fmt.Println(k, "is bool", vv)
				res = append(res, fmt.Sprintf("%s, _ := js.Get(\"%s\").Bool() \n assert.Equal(%s, %v)", k, k, k, vv))
			case []interface{}:
				// fmt.Println(k, "is an array:", vv)
				res = append(res, fmt.Sprintf("// Key【%s】的子层级的value值未生成断言,系多层级数组数据,具体值如下:", k))
				res = append(res, fmt.Sprintf("// %v ", vv))
			case map[string]interface{}:
				// fmt.Println(k, "is an map:", vv)
				temp, _ := json.Marshal(vv)
				res = append(res, fmt.Sprintf("// Key【%s】的子层级value值未生成断言,系多层级Map数据,具体值如下:", k))
				res = append(res, fmt.Sprintf("// %v ", string(temp)))
			default:
				// fmt.Println(k, "is of a type didn't handle", vv)
			}
			responseAssertStr = strings.Join(res, "\n")
		}
	}
	return
}

func initPath(URL string) (pathStr, path string) {
	pathStr = strings.Split(URL, baseDomain)[1]
	if strings.Contains(pathStr, "?") {
		pathStr = strings.Split(pathStr, "?")[0]
		path = strings.Split(pathStr, "?")[0]
	} else {
		path = pathStr
	}
	if strings.Contains(pathStr, ".") {
		pathStr = strings.Replace(pathStr, ".", "/", 10)
		pathStr = strings.Replace(pathStr, "-", "/", 10)
	}
	// fmt.Println(path)
	// fmt.Println("pathStr", pathStr)
	return
}

func run(serverName, path, InterfaceName, method, responseAssertStr string, Param string) {
	// 初始化测试文件
	InterfaceFilepath := filepath.Join(bizBaseFolder, serverName)
	Testcasefilepath := filepath.Join(testCaseBaseFolder, serverName)
	InterfaceFileame := InterfaceName + ".go"
	Testcasefilename := InterfaceName + "_test.go"

	// 创建并写入标准请求信息
	file, err := createFile(InterfaceFilepath, InterfaceFileame)
	if err != nil {
		fmt.Println("createInterfaceFile", err)
	}
	writeParam(file, serverName, []string{Param})
	writeReq(file, InterfaceName, path, method)
	defer file.Close()

	// 创建并写入测试用例信息
	file1, err := createFile(Testcasefilepath, Testcasefilename)
	if err != nil {
		fmt.Println("createTestcasefile", err)
	}

	// 写入suit文件
	initTestsuit(serverName)

	// 写入测试用例
	writeTestcase(file1, serverName, InterfaceName, responseAssertStr)
	defer file1.Close()

	// 格式化测试文件
	exec.Command("goimports", "-w", InterfaceFilepath).Run()
	exec.Command("goimports", "-w", Testcasefilepath).Run()

	// 导入InterfaceFilepath
	exec.Command("go", "install", InterfaceFilepath).Run()
}

func initHeaders(har *Har) map[string]string {
	var headers = make(map[string]string)
	// fmt.Println(len(har.Log.Entries[0].Request.Headers))

	for _, v := range har.Log.Entries[0].Request.Headers {
		headers[v.Name] = v.Value
	}
	return headers
}

func createFile(filepaths, filename string) (file *os.File, err error) {
	os.MkdirAll(filepaths, 0777)
	file, err = os.Create(filepath.Join(filepaths, filename))
	return
}

func createInterfaceFile(path, filename string) (file *os.File, err error) {
	filename = filename + ".go"
	filepath := bizBaseFolder + "/" + path + "/"
	os.MkdirAll(filepath, 0777)
	file, err = os.Create(filepath + filename)
	return
}

func createTestcasefile(path, filename string) (file *os.File, err error) {
	filename = filename + "_test.go"
	filepath := testCaseBaseFolder + "/" + path + "/"
	os.MkdirAll(filepath, 0777)
	file, err = os.Create(filepath + filename)
	return
}

func initTestsuit(serverName string) {
	filename := serverName + "_suite_test.go"
	filepath := testCaseBaseFolder + "/" + serverName + "/"
	os.MkdirAll(filepath, 0777)
	file, err := os.Create(filepath + filename)
	if err != nil {
		fmt.Println("initTestsuit Error", err)
	}
	// Testsuite后的 首字母需大写,否则suite无法正常检索到testcase
	file.WriteString(fmt.Sprintf(
		`package %s_test

		import (
			"testing"

			. "github.com/onsi/ginkgo"
			. "github.com/onsi/gomega"
		)

		func Test%s(t *testing.T) {
			RegisterFailHandler(Fail)
			RunSpecs(t, "%s Suite")
		}`, serverName, Capitalize(serverName), serverName))
}

func writeTestcase(file *os.File, serverName, InterfaceName, responseAssertStr string) {
	// 接口引入路径 【服务名称.接口名称】
	interfaceImportPath := serverName + "." + InterfaceName
	// 接口标准请求参数 【接口名称Param】
	paramImportPath := interfaceImportPath + "Param"

	// 接口标准请求参数拷贝,请求参数为非标准【map[string]interface{}】类型时,该参数为空
	tempParamStr := ""
	// 是否使用mapDeepCopy,请求参数为非标准【map[string]interface{}】类型时 使用
	mapDeepCopy := ""
	if paramType != "map[string]interface{}" {
		tempParamStr = paramImportPath
	}

	if paramType == "map[string]interface{}" {
		tempParamStr = "Param"
		mapDeepCopy = fmt.Sprintf(`Param := utils.MapDeepCopy(%s)`, paramImportPath)
	}

	// fmt.Println("---------------->", paramType)
	file.WriteString(fmt.Sprintf("package %s_test\n\n", serverName))
	file.WriteString(`import . "github.com/onsi/ginkgo"`)
	file.WriteString("\n\n")
	file.WriteString(fmt.Sprintf(`var _ = Describe("%s", func() {
		headers := common.EntireHeaderParam
		assert := assert.New(GinkgoT())
		BeforeEach(func() {
			By("begin test")
		})
		JustBeforeEach(func() {
			By("just say start")
		})
		AfterEach(func() {
			By("end test")
		})
		Context("%s", func() {
			It("正常%s", func() {
				%s
				ret, resp, _ := %s(%s, headers)
				assert.Equal(ret.StatusCode, 200)
				js, errs := simplejson.NewJson(resp)
				if errs != nil {
					panic(errs)
				}
				%s
			})
		})
	})`, serverName, InterfaceName, InterfaceName, mapDeepCopy, interfaceImportPath, tempParamStr, responseAssertStr))
}

func writeParam(file *os.File, serverName string, params []string) {
	file.WriteString(fmt.Sprintf("package %s", serverName))
	file.WriteString("\n\n\n")
	file.WriteString("var (")
	for _, param := range params {
		file.WriteString(param)
	}
	file.WriteString(")")
	file.WriteString("\n\n\n")
}

func writeReq(file *os.File, InterfaceName, path, method string) {
	file.WriteString(fmt.Sprintf(`func %s(param %s, header map[string]string) (ret gorequest.Response, content []byte, result string) {
		path := "%s"
		url := CN_bbs.TESTSERVERDOMAIN + path
		ret, content = common.Common%s(url, param, header)
		fmt.Println(ret.Request.URL)
		// js, _ := simplejson.NewJson([]byte(content))
		//result, _ = js.Get("result").String()
		return
	}`, InterfaceName, paramType, path, method))
}

func genGetParam(interfaceName string, param map[string]interface{}) (formatParam string) {

	// 对于请求参数的value值为 数组
	if len(param) > 0 {
		for k, v := range param {
			switch vv := v.(type) {
			case []interface{}:
				fmt.Sprintf(k, "is an array:", vv)
				temp, _ := json.Marshal(param)
				// 如果是数组格式,直接当作字符串处理(map[]interface{}格式无法表示该类型参数)
				formatParam = fmt.Sprintf("%sParam = `%s`", interfaceName, fmt.Sprintf("%v", string(temp)))
				return
			default:
				// fmt.Println(k, "is of a type didn't handle")
			}
		}
	}
	temp, _ := json.Marshal(param)
	formatParam = fmt.Sprintf(`%sParam = map[string]interface{} %s`, interfaceName, fmt.Sprintf("%v", string(temp)))
	return
}

func genPostParam(interfaceName string, postReqParamStr string) (formatParam string) {
	// formatParam = fmt.Sprintf(`%sParam = map[string]interface{} %s`, interfaceName, param)
	// fmt.Sprintf("%v", string(temp))
	postReqParam := make(map[string]interface{}, 1)

	if len(postReqParamStr) > 0 {
		// 判断第一个字符是否为{}, 做传递数据为数组[]的兼容
		if []rune(postReqParamStr)[0] == '{' {
			var x interface{}
			err := json.Unmarshal([]byte(postReqParamStr), &x)
			if err != nil {
				fmt.Println("err", err)
			}

			postReqParam = x.(map[string]interface{})
			// fmt.Println(postReqParam)
			// 判断value中是否存在数组
			for k, v := range postReqParam {
				switch vv := v.(type) {
				// switch vv := v.(type) {
				case []interface{}:
					fmt.Sprintf(k, "is an array:", vv)
					// param[k] = fmt.Sprintf("`%s`", vv)
					temp, _ := json.Marshal(postReqParam)
					formatParam = fmt.Sprintf("%sParam = `%s`", interfaceName, fmt.Sprintf("%v", string(temp)))
					paramType = "string"
					return
				default:
					formatParam = genGetParam(interfaceName, postReqParam)
					// fmt.Println(k, "is of a type didn't handle")
				}
			}
			// 如果为数组,做如下处理
		} else {
			var y []interface{}
			err := json.Unmarshal([]byte(postReqParamStr), &y)
			if err != nil {
				fmt.Println("err", err)
			}

			postReqParam = y[0].(map[string]interface{})
			temp, _ := json.Marshal(postReqParam)

			// 声明请求类型
			paramType = "[]map[string]interface{}"
			formatParam = fmt.Sprintf(`%sParam =[]map[string]interface{}{%s}`, interfaceName, string(temp))
			// 无法使用 判断类型 Param := utils.MapDeepCopy(Hebinz123.CNppcPlaylistApiV1RemarkDelParam)
		}
	}
	// temp, _ := json.Marshal(param)
	// formatParam = fmt.Sprintf(`%sParam = map[string]interface{} %s`, interfaceName, fmt.Sprintf("%v", string(temp)))
	return
}

func formatInterfaceName(path string) (InterfaceName string) {
	paths := strings.Split(path, "/")

	for k, v := range paths {
		paths[k] = Capitalize(v)
	}

	InterfaceName = strings.Join(paths, "")
	return
}

// Capitalize 字符首字母大写
func Capitalize(str string) string {
	var upperStr string
	vv := []rune(str)
	for i := 0; i < len(vv); i++ {
		if i == 0 {
			if vv[i] >= 97 && vv[i] <= 122 { // 判断是否是小写字母
				vv[i] -= 32 // string的码表相差32位
				upperStr += string(vv[i])
			} else {
				fmt.Println("Not begins with lowercase letter,")
				return str
			}
		} else {
			upperStr += string(vv[i])
		}
	}
	return upperStr
}

// Har Logs 解析
type Har struct {
	Log struct {
		version string
		creator string
		Entries []struct {
			startedDateTime string
			time            string
			Request         struct {
				Method      string
				URL         string
				httpVersion string
				Cookies     []string
				Headers     []struct {
					Name  string
					Value string
				}
				QueryString []struct {
					Name  string
					Value string
				}
				PostData struct {
					MimeType string
					Text     string
				}
				headersSize int32
				bodySize    int32
			}
			Response struct {
				_charlesStatus string
				Status         int32
				StatusText     string
				httpVersion    string
				cookies        []string
				Headers        []struct {
					Name  string
					Value string
				}
				Content struct {
					size     int32
					mimeType string
					Text     string
					Encoding string
				}
				redirectURL string
				headersSize int
				bodySize    int
			}
			serverIPAddress string
			cache           map[string]string
			timings         map[string]int32
		}
	}
}

// UnpackHar 解析 har
func UnpackHar(har []byte) (logs *Har) {
	err := json.Unmarshal(har, &logs)
	if err != nil {
		fmt.Println(err)
	}
	return
}

文中可能存在描述不正确,欢迎大神们指正补充!

感谢阅读,如果觉得对你有帮助,就在右下角点个赞吧,感谢!

合抱之木,生于毫末;九层之台,起于累土;千里之行,始于足下。


 资料获取方法

【留言777】

各位想获取源码等教程资料的朋友请点赞 + 评论 + 收藏,三连!

三连之后我会在评论区挨个私信发给你们~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/823667.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

K8s总结

K8s 是什么 Kubernetes是一个开源的&#xff0c;用于管理云平台中多个主机上的容器化的应用&#xff0c;Kubernetes的目标是让部署容器化的应用简单并且高效&#xff08;powerful&#xff09;,Kubernetes提供了应用部署&#xff0c;规划&#xff0c;更新&#xff0c;维护的机制…

【从零开始学习JAVA | 第三十五篇】IO流综合练习

目录 前言&#xff1a; 1.拷贝文件&#xff08;含子文件&#xff09; 思路&#xff1a; 2.文件加密 思路&#xff1a; 3.修改文件中的数据&#xff1a; 思路&#xff1a; 总结&#xff1a; 前言&#xff1a; 在前面我们为大家介绍了FILE类和IO类。这篇文章我们来练习一…

SQL SERVER 中将数据表中的字段按分隔符分成多行多列

SQL SERVER 中将数据表中的字段按分隔符分成多行多列_sql按逗号拆分列为多行_帅气的苹果果的博客-CSDN博客 SELECTa.name,monitors SUBSTRING ( a.monitors, b.number, charindex( ,, a.monitors ,, b.number ) - b.number ) FROM( SELECT name, monitors FROM [dbo].[ssm_vi…

Packet Tracer - 连接有线和无线 LAN

Packet Tracer - 连接有线和无线 LAN 地址分配表 设备 接口 IP 地址 连接到 云 Eth6 N/A Fa0/0 Coax7 N/A Port0 电缆调制解调器 Port0 N/A Coax7 Port1 N/A 互联网 Router0 控制台端口 N/A RS232 Fa0/0 192.168.2.1/24 Eth6 Fa0/1 10.0.0.1/24 Fa…

安科瑞智能照明系统在医院智能建筑中应用的优势

摘要&#xff1a;现阶段&#xff0c;我国的社会经济的发展水平不断提高&#xff0c;为智能照明系统的发展带来了新的契机。文章主要介绍了几类智能照明系统&#xff0c;分析了其优点&#xff0c;并介绍了智能照明系统在医院建筑中的具体应用&#xff0c;具有一定的参考价值。 …

屏幕取色器Mac版_苹果屏幕取色工具_屏幕取色器工具

Sip for Mac 是Mac系统平台上的一款老牌的颜色拾取工具&#xff0c;是设计师和前端开发工作者必不可少的屏幕取色软件&#xff0c;你只需要用鼠标点一下即可轻松地对屏幕上的任何颜色进行采样和编码&#xff0c;并将颜色数据自动存到剪切板&#xff0c;方便随时粘贴出来。 Sip…

Linux|ubuntu下运行python

参考&#xff1a;ubuntu系统下切换python版本的方法 文章目录 python版本问题查看ubuntu下的所有python版本通过apt-get install可以安装不同版本python查看python版本号更新update-alternatives替代列表查看update-alternatives下的python版本切换python版本删除python版本 p…

Windows7+内网, 安装高版本nodejs,使用vite+vue3+typescript开发项目

前言&#xff1a;vite只支持高版本的nodejs&#xff0c;而高版本的nodejs只支持windows8及以上&#xff0c;且vite还对浏览器版本有兼容问题。以下均为vite官网截图 1、安装好低版本的nodejs win7系统建议安装13.及以下&#xff0c;我的是12.12.0这个版本。nodejs低版本官网下载…

20- C++ STL标准模板库-8 (C++)

第十章 10.1 基本概念 STL&#xff08;Standard Template Library&#xff0c;标准模板库)是惠普实验室开发的一系列软件的统称。现然主要出现在C中&#xff0c;但在被引入C之前该技术就已经存在了很长的一段时间。 STL的从广义上讲分为三类&#xff1a;algorithm&#xff0…

【NLP初级概念】 01-稀疏文档表示(第 1/30 部分)

一、介绍 自然语言处理(NLP)是计算方法的应用,不仅可以从文本中提取信息,还可以在其上对不同的应用程序进行建模。所有基于语言的文本都有系统的结构或规则,通常被称为形态学,例如“跳跃”的过去时总是“跳跃”。对于人类来说,这种形态学的理解是显而易见的。 在这篇介…

kafka总结

Kafka是一种高吞吐量的分布式发布订阅消息系统&#xff08;消息引擎系统&#xff09;&#xff0c;它可以处理消费者在网站中的所有动作流数据。 消息队列应用场景 缓存/削峰 :处理突然激增的大量数据&#xff0c;先放入消息队列&#xff0c;再按照速度去处理&#xff0c; 解…

技术等级 TRL 定义

“不同环境、不同目标下TRL表述不一样” 技术等级 TRL 定义 TRL1 基本原理提出和发现 TRL2 技术应用研究 TRL3 完成概念验证&#xff0c;如叶栅试验、燃烧室头部试验等 TRL4 完成模拟部件试验&#xff0e;如压气机性能试验&#xff0c;燃烧室扇形试验 TRL5 完…

3ds MAX 绘制喷泉

首先绘制一个小圆柱体当做喷头&#xff1a; 在粒子系统内选择【超级喷射】&#xff0c;并设置对应的参数&#xff1a; 轴和平面的扩散代表了我们看到的水柱能占据多大的面积 另外比较重要的参数是粒子运动和粒子计时 前者是粒子移动的时间也就是喷射的速度 后者代表了喷射出的…

渗透测试:Linux提权精讲(三)之sudo方法第三期

目录 写在开头 sudo jjs sudo journalctl sudo knife sudo less sudo man sudo more sudo mount sudo mysql sudo nano sudo neofetch sudo nice sudo nmap sudo node sudo nohup sudo openvpn sudo passwd sudo perl sudo php sudo pico sudo pkexec su…

网络知识介绍

一、TCP 传输控制协议&#xff0c;Transmission Control Protocol。 面向广域网的通信协议&#xff0c;跨域多个网络通信时&#xff0c;为两个通信端点之间提供一条具有如下特点的通信方式&#xff1a; 基于流、面向连接、可靠通信方式、网络状况不佳时尽量降低系统由于重传带…

二十三种设计模式第二十三篇--状态模式

状态模式&#xff0c;是一种行为模式&#xff0c;在软件开发过程中&#xff0c;对象按照不同的情况做出不同的行为&#xff0c;我们把这样的对象称为具有状态的对象&#xff0c;而把影响对象行为的一个或者多个动态变化的属性称为状态。 对这种具有状态的对象变成&#xff0c;…

《Java面向对象程序设计》学习笔记——第 1 章 Java入门

专栏&#xff1a;《Java面向对象程序设计》学习笔记

第28天-Kubernetes架构,集群部署,Ingress,项目部署,Dashboard

1.K8S集群部署 1.1.k8s快速入门 1.1.1.简介 Kubernetes简称k8s&#xff0c;是用于自动部署&#xff0c;扩展和管理容器化应用程序的开源系统。 中文官网&#xff1a;https://kubernetes.io/zh/中文社区&#xff1a;https://www.kubernetes.org.cn/官方文档&#xff1a;https…

git管理工具学习(图解使用git工作流程)

目录 GIT 简介一个最简单的GIT操作流程git的工作流程&命令 GIT 简介 git是什么&#xff0c;在维基百科上是这么介绍的&#xff1a;git是一个分布式的版本控制软件 分布式是相对于集中式而言的&#xff0c;分布式即每一个git库都是一个完整的库。 每个库的地位都是平等的&am…

STM32存储左右互搏 I2C总线读写EEPROM ZD24C1MA

STM32存储左右互搏 I2C总线读写EEPROM ZD24C1MA 在较低容量存储领域&#xff0c;EEPROM是常用的存储介质&#xff0c;不同容量的EEPROM的地址对应位数不同&#xff0c;在发送字节的格式上有所区别。EEPROM是非快速访问存储&#xff0c;因为EEPROM按页进行组织&#xff0c;在连…