【Golang】基于录制,自动生成go test接口自动化用例
背景
之前写过一篇博客,介绍怎么用Python通过解析抓包数据,完成自动化用例的编写。最近这段时间在使用go test,所以就在想能不能也使用代码来生成自动化用例,快速提升测试用例覆盖率。说干就干。
框架
首先介绍一下我们使用的测框架:
项 | 信息 | 安装 | 备注 |
---|---|---|---|
GO版本 | go1.12.9 darwin/amd64 | 略 | |
测试框架 | ginkgo | go get -u github.com/onsi/ginkgo/ginkgo | |
断言库 | testify/assert | go get github.com/stretchr/testify | 官方配套的断言库是gomega |
ginkgo初始化
- 初始化:
cd path/to/package/you/want/to/test && ginkgo bootstrap
- 创建示例用例:
ginkgo generate
(需要手动添加测试用例) - 运行测试:
go test
orginkgo
注:-v
加上参数可打印运行信息
抓包&运行脚本
- 使用抓包工具(如Charles)抓包,把数据包导出为har格式,保存在当前目录下
- 如何安装抓包工具在本文就不赘述了,抓包,过滤出想要的数据,导出,保存的格式注意选择为
har
:
- 如何安装抓包工具在本文就不赘述了,抓包,过滤出想要的数据,导出,保存的格式注意选择为
- 根据实际情况修改全局变量信息,如bizBaseFolder、serverName、userFile等
- 使用
go run gentest.go
运行脚本即可
目录说明
然后我们一起来了解一下我们的目录结构定义。
∮./business
业务封装,封装具体的请求及测试数据
∮./conf
配置信息及接口请求参数初始化封装
∮./utils
公共函数封装
∮./testcase
接口测试用例目录
testcase 用例目录结构规则
基本原则: 根据项目、模块、接口功能逐级区分,建议最多3层目录层级
¶示例
- 软件测试论坛项目组/论坛项目/帖子模块/创建帖子接口:
- CN_TestBBS/bbs/post/post_test.go
- 基础账号项目/首页项目/白名单接口:
- 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
}
【Golang】基于录制,自动生成go test接口自动化用例的更多相关文章
- 【Robot Framework 项目实战 04】基于录制,生成RF关键字及 自动化用例
背景 因为服务的迁移,Jira版本的更新,很多接口文档的维护变少,导致想要编写部分服务的自动化测试变得尤为麻烦,很多服务,尤其是客户端接口需要通过抓包的方式查询参数来编写自动化用例,但是过程中手工重复 ...
- iBatis——自动生成DAO层接口提供操作函数(详解)
iBatis——自动生成DAO层接口提供操作函数(详解) 在使用iBatis进行持久层管理时,发现在使用DAO层的updateByPrimaryKey.updateByPrimaryKeySelect ...
- Groovy元编程应用之自动生成订单搜索接口测试用例集
背景 在 "Groovy元编程简明教程" 一文中,简明地介绍了 Groovy 元编程的特性. 那么,元编程可以应用哪些场合呢?元编程通常可以用来自动生成一些相似的模板代码. 在 & ...
- 文件参数化-utp框架之根据yaml文件自动生成python文件+utp运行用例
根据yaml文件自动生成python文件 utp框架: bin目录:存放执行文件(run.py) cases目录:存放生成的用例的python文件(该目录下的文件为根据data目录下的测试用例生成的p ...
- iOS - WWDC18 iOS 自动生成强密码和自动填充验证码/密码
本文将介绍WWDC18 Automatic Strong Passwords and Security Code Autofill和WWDC17 Introducing Password AutoFi ...
- VS2017+WIN10自动生成类、接口的说明(修改类模板的方法)
微软发布VS2017的时候,我第一时间离线一份专业版,安装到了自己的电脑上,开始体验,但是问题来了,在开发中建立类和接口的时候,说 明注释总要自己写一次,烦!~~于是还是像以前一样改IDE默认的类和接 ...
- 使用swagger实现在线api文档自动生成 在线测试api接口
使用vs nuget包管理工具搜索Swashbuckle 然后安装便可 注释依赖于vs生成的xml注释文件
- 自动生成web api接口文档
然后打开web程序,访问ip:port/Help. 为什么可以直接输入Help就能访问呢,因为这个插件本身已经配置了路径,如下. public class HelpPageAreaRegistrati ...
- 在docker容器中如何自动生成配置文件(以nginx配置为例)
应用场景类似于多个域名要起多个容器,有些参数有些域名需要,有些域名不需要,或者参数的值不太一样,需要去对应的配置文件修改,不太灵活,如果通过变量的方式直接定义在Dockerfile文件中,需要哪些参数 ...
随机推荐
- 私服nexus的权限问题,带admin和带view的区别
admin和view的区别只找到了这个解释: https://blog.csdn.net/tian_111222333/article/details/100159983 最终得出答案,我只需要给他们 ...
- git remote 使用总结
使用场景:新建一个git仓储并与远程关联 1.初始化一个新的空的git仓储,并在仓储下做一些改动 mkdir gitDir cd gitDir/ git init touch file git sta ...
- cygwin_exception::open_stackdumpfile: Dumping stack trace to HttpServer.exe.stackdump错误
本来,我在Windows下使用Cygwin编译运行c程序,在执行*.exe时报出如题错误,我在Linux环境下使用gcc编译运行,则正常. 所以,当你无法解决上述问题时,换系统吧!
- ubuntu16.04安装zabbix-server3.4
一.安装前环境准备 部署zabbix需要安装apache,mysql和php sudo apt-get install apache2 sudo apt-get install mysql-serve ...
- 【Day5】1.Request对象之Header伪装策略
import urllib.request as ur import user_agent request = ur.Request( url='https://edu.csdn.net/', hea ...
- Elasticsearch 9300无法访问,客户端出现NoNodeAvailableException[None of the configured nodes are available: [{#transport#‐1}{exvgJLR‐RlCNMJy‐hzKtnA}
1. 进入容器 docker exec ‐it ID /bin/bash 2. 拷贝配置文件到宿主机 docker cp ID:/usr/share/elasticsearch/config/el ...
- mysql 忘记/修改数据库密码
window mysql 修改密码 方法1: 用SET PASSWORD命令 mysql -u root mysql> SET PASSWORD FOR 'root'@'localhost' = ...
- 实现LAMP架构
LAMP介绍 LAM(M)P: L: linux A: apache (httpd) M: mysql, mariadb M:memcached P: php, perl, python WEB资源类 ...
- Web Api 创建及其使用
由于创建博客,我需要尝试一些新的技术,新的思路,所以我没规规矩矩的写博客,用上了诸多以前没用的东西,比如现在这个(我只是听过web api 我连 web server 都只是用过两三次/手动滑稽) 昨 ...
- Linux学习笔记(十二)VIM编辑器
一.概述 VI Visual interface 可视化接口,类似于Windows中的记事本 VI->VIM 操作模式: (1)Command mode 命令模式 (2)Insert mode ...