Go语言脚本示例

1、下面例子为一个猜数字小游戏,生成一个随机数,通过键盘输入数字与随机数对比,判断结果,最多可以猜测10次,失败后打印真实的随机数,如图:

//Print random numbers and compare them by entering numbers
package main

import (
	"bufio"
	"fmt"
	"log"
	"math/rand" //导入包
	"os"
	"strconv"
	"strings"
	"time"
)

func main() {
	rand.Seed(time.Now().UnixNano()) //设置时间种子
	target := rand.Intn(100)         //生成0到100的随机数
        success := false
	for i := 1; i <= 10; i++ {
		reader := bufio.NewReader(os.Stdin)     //从键盘读取
		fmt.Print("请从键盘输入1-100之间的任意数字:")
		input, err := reader.ReadString('\n')   //执行Enter后输入停止
		if err != nil {
			log.Fatal(err)
		}
		input = strings.TrimSpace(input)        //去除字符串两侧空格
		guess, err := strconv.Atoi(input)       //将strings转换为int
		if err != nil {
			log.Fatal(err)
		}
		if guess > target {                     //判断
			fmt.Println("猜高了,剩余猜测测试为:",10-i)
		} else if guess < target {
			fmt.Println("猜低了,剩余猜测测试为:",10-i)
		} else {
			fmt.Println("恭喜你猜对了")
                        success = true
                        break                          //猜对了就结束循环
		}
	}
        if !success {                                //当success不为true时候,打印真正的随机数
             fmt.Println("真正的随机数为:",target)
        } 
}

代码执行结果如下:

2、下面例子通过go脚本判断nginx进程是否存在,如图:

说明:

  • 第10行:声明一个函数,参数为字符串和字符串切片,返回字符串和错误
  • 第11行:通过exec.Command执行命令,参数为命令名字和参数,params…表示切片被打散传入
  • 第12行:输出标准错误输出
  • 第13行:通过Output()函数,执行命令并获取标准输出,返回类型为字节[]byte
  • 第17行:如果没有错误,将输出转换为字符串后返回
  • 第21行:定义一个切片
  • 第22行:调用函数runCmd,传递参数
  • 第26行:打印输出结果

程序运行结果如下:

注意:在go中,如果要通过管道(|)执行命令时,比如ps -ef |grep nginx,推荐的方式是通过exec.Command(“bash”,”-c”,”ps -ef |grep nginx”)的方式执行,bash后只能加脚本 ,bash -c 后面可加字符串,并将字符串作为命令使用

3、通过go脚本检测tomcat进程是否存在

本例子中tomcat运行在windows机器上,通过go脚本简单判断tomcat进程是否存在,如果不存在,执行命令拉起进程,脚本内容如下:

 1 package main
  2 
  3 import (
  4         "bytes"
  5         "fmt"
  6         "os/exec"
  7 )
  8 
  9 func checkProcess(commandName string, param []string) (string, string) {
 10         cmd := exec.Command(commandName, param...)
 11         var stdout, stderr bytes.Buffer
 12         cmd.Stdout = &stdout
 13         cmd.Stderr = &stderr
 14         err := cmd.Run()
 15         if err != nil {
 16                 return "", ""
 17         }
 18         resultOut, resultErr := string(stdout.Bytes()), string(stderr.Bytes())
 19         return resultOut, resultErr
 20 }
 21 func main() {
 22         paramSlice := []string{"/C", "wmic process list brief| findstr java"}
 23         paramSlice1 := []string{"/C", "start D:\\gongguan\\soft\\apache-tomcat-9.0.64\\bin\\startup.bat"}
 24         paramSlice2 := []string{"/C", "D:\\gongguan\\soft\\apache-tomcat-9.0.64\\bin\\shutdown.bat"}
 25         result, _ := checkProcess("cmd", paramSlice)
 26         if result != "" {
 27                 fmt.Println(result)
 28         } else {
 29                 _, stopErr := checkProcess("cmd", paramSlice2)
 30                 if stopErr != "" {
 31                         fmt.Println("Close command execution failed")
 32                 }
 33                 r, e := checkProcess("cmd", paramSlice1)
 34                 if r != "" {
 35                         fmt.Println("Java alredy running...")
 36                 }
 37                 if e != "" {
 38                         fmt.Println(e)
 39                 }
 40         }
 41 }
  • 第9行:声明函数,接收命令名和参数,返回两个字符串
  • 第10行:通过exec包的Command函数执行命令
  • 第11-13行:声明字节缓冲区变量,引用指针地址复制给exec包的Stdout和Stderr参数,之所以用指针,因为当cmd.Stdout和cmd.Stderr改变的时候,字节缓冲区的stdout和stderr也会跟着改变,指针传递
  • 第14行:通过阻塞的方式运行,直到命令结束后才停止
  • 第18行:调用Bytes()方法将字节类型转换为字符串类型
  • 第22-24行:定义切片,元素为要执行命令参数,”/C”为固定写法,后面的wmic命令查找进程是否存在
  • 第25行:调用函数,将命令名和参数传递过去,windows下的命令名为cmd,linux下为bash,固定写法
  • 第26行:如果标准输出不为空,表示进程存在
  • 第29行:执行递归函数,再次执行命令shutdown.bat,关闭tomcat,防止假死
  • 第33行:执行递归函数,执行命令启动tomcat进程

4、结构体字段类型为切片类型,实例化并打印值,如图:

5、gin通过POST请求将json传入的多个参数绑定到结构体中,如图:

postman中请求内容如下:

从上图可以看出,请求过程中,传入了两组json数据,表示不同的用户名和密码,因此最外层需要通过中括号[]括起来,请求结果如图:

注:除了Bind(),还可以使用ShouldBind(),两者区别在于:ShouldBind没有绑定成功不报错,就是空值,Bind()会报错

6、将任意类型切片传递到函数中,通过interface{}可接收任意类型,并遍历,如图:

运行结果如下:

从报错可以看出,gong接收的参数类型为interface{},虽然此函数可以接收任意类型的参数,但是遍历的时候,会根据bb定义的类型来遍历, interface{}无法进行遍历,因此报错,解决办法,需要将interface{}转为[]interface{},操作如下:

package main

import (
	"fmt"
	"reflect"
)

type def struct {
	Name string
	Pass string
}

//接收任意参数,最终返回切片类型
func gong(bb interface{}) ([]interface{}, bool) {
	val, ok := isSlice(bb)
	if !ok {
		return nil, false
	}
	//通过val.Len()获取反射值的长度
	sliceLen := val.Len()
	//创建一个新的切片,类型为任意参数空接口
	out := make([]interface{}, sliceLen)
	for i := 0; i < sliceLen; i++ {
		//获取val值的第i个元素
		out[i] = val.Index(i).Interface()
	}
	return out, true
}

//判断是否为切片,通过反射
func isSlice(arg interface{}) (val reflect.Value, ok bool) {
	//使用reflect.ValudOf获取反射值对象reflect.Value
	val = reflect.ValueOf(arg)
	//判断val的种类是不是切片
	if val.Kind() == reflect.Slice {
		ok = true
	}
	return
}
func main() {
	b := []def{
		{Name: "hello", Pass: "111"},
		{Name: "world", Pass: "888"},
	}
	result, _ := gong(b)
	for k, v := range result {
		fmt.Println(k, v)
	}
}

说明:调用gong()函数将结构体切片变量b传递给函数后,首先调用isSlice函数,判断bb的值是否为一个切片,如果不是(!ok),那么返回nil,和false,如果是切片,通过Len()获取反射值切片长度,创建新的切片out,根据反射值切片的长度,循环设置新切片out中元素

7、获取机器的信息,包括主机、系统、CPU、内存、IP等,如下:

package main

import (
	"fmt"
	"os/exec"
	"strconv"
	"strings"
	"time"

	"github.com/shopspring/decimal"
)

//获取机器信息,参数为命令名称和切片,切片中为具体命令
func getInfo(commandName string, params []string, ch chan string) {
	cmd := exec.Command(commandName, params...)
	cmdOut, err := cmd.Output()
	if err != nil {
		fmt.Println(err)
	}
	//将执行结果发送到通道中,类型为字符串
	ch <- string(cmdOut)
}

func main() {
	ch := make(chan string, 5)
	data := [][]string{} //创建一个二维切片
	systemSlice := []string{"-c", "cat /etc/redhat-release"}
	hostSlice := []string{"-c", "hostname"}
	cpuSlice := []string{"-c", "lscpu | sed -n '4p' | awk -F  ':' '{print $2}'"}
	memSlice := []string{"-c", "cat /proc/meminfo |sed -n '1p' | awk '{print $2}'"}
	ipSlice := []string{"-c", "hostname -I | awk '{print $1}'"}
	data = append(data, systemSlice)
	data = append(data, hostSlice)
	data = append(data, cpuSlice)
	data = append(data, memSlice)
	data = append(data, ipSlice)
	//循环遍历二维切片,每个元素都是一个一维切片
	for i := 0; i < len(data); i++ {
		go getInfo("bash", data[i], ch)
		time.Sleep(time.Second)
	}
	//发送完成后关闭通道,这样下方循环遍历结束后才能正常退出
	close(ch)
	a := []string{}
	//循环遍历通道
	for res := range ch {
		a = append(a, strings.TrimSpace(res))
	}
	//切片的第四个元素为内存信息,将其转换为整数
	strconvInt, _ := strconv.Atoi(a[3])
	//进行除法运算并四舍五入,将kb转换为GB
	r, _ := decimal.NewFromFloat(float64(strconvInt) / float64(1024) / float64(1024)).Round(0).Float64()
	fmt.Println("系统类型:", a[0])
	fmt.Println("主机名:", a[1])
	fmt.Println("CPU数量:", a[2])
	mem := fmt.Sprintf("%.f", r)
	fmt.Println("内存大小:", mem+"G")
	fmt.Println("IP地址为:", a[4])
}

注:上面例子中通过close(ch)来关闭通道,这样下面循环遍历通道的时候,当通道数据遍历完成后即可顺利退出,否则将发生阻塞(for在等待遍历,实际通道中已没有数据)

8、实现定时任务,如图:

运行结果如下:

  • select{}:{}里面没有任何参数,此时select{]将对程序进行阻塞以便进行下一轮循环,因此如果要想从通道中接收值,需要将for循环放在select{}前面,如果放在select{}后面,select{}已经进行了阻塞,那就永远无法遍历通道的值,更多select用法,参考文章:Go语言之并发与通道

9、检测json文件格式,如下:

package main

import (
	"fmt"
	"io/fs"
	"io/ioutil"
	"log"
	"os"
	"os/exec"

	"github.com/gin-gonic/gin"
)

func checkJSON(commandName string, params []string) string {
	cmd := exec.Command(commandName, params...)
	s, _ := cmd.CombinedOutput()
	return string(s)
}
func main() {
	r := gin.New()
	var f fs.FileInfo
	r.POST("/json", func(c *gin.Context) {
		b, err := c.GetRawData()
		if err != nil {
			c.JSON(400, gin.H{
				"data": err.Error(),
			})
		}
		err = ioutil.WriteFile("linshi.json", b, 0644)
		if err != nil {
			fmt.Println("写入文件失败:", err)
			return
		}
		f, err = os.Stat("linshi.json")
		if err != nil {
			log.Fatal(err)
		}
		cSlice := []string{"-c", fmt.Sprintf("cat %#v | jq", f.Name())}
		res := checkJSON("bash", cSlice)
		c.JSON(200, gin.H{
			"data": res,
		})
	})
	r.Run()
}

10、读取csv文件,并输出整数列的最大值,csv文件内容如图:

  • os.Open返回结构为File结构体指针,File结构体实现了接口Reader
  • NewReader()的参数为Reader接口,由于File实现了接口,因此可以进行赋值

运行结果如下:

上面的代码也可以修改为如下方式,通过for无限循环,如图:

注意:如果csv文件不是以逗号分隔或者文件中包含注释的行,此时可以使用csv.Reader.Comma和csv.Reader.Comment来处理

11、Golang配置wget实现批量下载url

下载流程:

  • 首先读取本地的url.txt文件,将其中的url数据读取出来,并追加到切片中
  • 接下来调用函数checkurl来检查这些url中哪些可以下载,哪些不能下载,并将不能下载的单独封装到一个切片中
  • 在原始的url数据中,找到上面封装的不能下载的url,并将这些url删除,从而获取可以正常下载的url
  • 最后调用downurl,来下载文件

首先查看url.txt文件,图中黄色框的url为网络不可达的一个地址,其余都是正常的,如图:

编写wget.go文件,内容如下:


package main

import (
  "bufio"
  "fmt"
  "io"
  "log"
  "os"
  "os/exec"
  "strings"
)

func main() {
//打开本地url文件,获取url数据
  file, err := os.Open("url.txt")
  if err != nil {
    log.Fatal(err)
  }
  //创建一个缓存读取对象
  rd := bufio.NewReader(file)
  var urlSlice []string
  for {
    //逐行读取数据并追加到切片urlSlice中
    by, _, errs := rd.ReadLine()
    if string(by) != "" {
      urlSlice = append(urlSlice, string(by))
    }
    if errs == io.EOF {
      break
    }
  }
  //检查url是否可以访问,如果不可达是不能下载的,此处是以切片形式封装
  errURL := checkurl(urlSlice)
  //将不可达的url从源切片中删除
  okURL := deleteSlice(urlSlice, errURL)
  if len(okURL) != 0 {
  //删除了异常的url后如果源切片中还有正常的url则执行下载
    downpath, err := downurl(okURL)
    if err != nil {
      log.Fatal(err)
    }
    fmt.Printf("文件已下载完成,存储路径为:%s\n", downpath)
  }
  fmt.Printf("无法下载的URL地址为:%v\n", errURL)
}
//此函数主要功能就是检查url是否正常
func checkurl(urls []string) []string {
  var errURL []string
  for _, urlValue := range urls {
  //执行wget命令检测url是否正常,如果异常后面的err中会有exit status提示
    cmd := exec.Command("wget", "--spider", "--timeout=5", "--tries=1", urlValue)
    _, err := cmd.CombinedOutput()
    if err != nil {
    //如果err中有exit,说明此url异常,将异常的url添加到errURL中
      if strings.Contains(err.Error(), "exit") {
        errURL = append(errURL, urlValue)
      }
    }
  }
  return errURL
}

//下载url
func downurl(url []string) (string, error) {
//文件下载路径,可自定义
  downpath := "/root/test"
  for _, v := range url {
    cmd := exec.Command("wget", "-c", "-P", fmt.Sprintf("%s", downpath), v)
    stdout, _ := cmd.StdoutPipe()
    stderr, _ := cmd.StderrPipe()
    go func() {
      io.Copy(os.Stdout, stdout)
    }()
    go func() {
      io.Copy(os.Stderr, stderr)
    }()
    err := cmd.Start()
    if err != nil {
      return "", err
    }
    err = cmd.Wait()
    if err != nil {
      return "", err
    }
  }
  return downpath, nil
}

//删除切片url中异常的url
func deleteSlice(url, delslice []string) []string {
//从url切片中删除delslice切片中的元素,因此需要借助map
  m := make(map[string]bool)
  var res []string
  //先遍历url,判断map中是否有此元素,如果没有就添加
  for _, v := range url {
    if !m[v] {
      m[v] = true
    }
  }
  //遍历delslice,通过map的delete函数删除元素
  for _, v1 := range delslice {
    delete(m, v1)
  }
  for k, _ := range m {
    res = append(res, k)
  }
  return res
}

运行脚本,如图:

脚本执行后会显示下载过程,下载进度,并在最后提示下载后的路径在哪里以及哪个url不能下载

验证无问题后即可通过如下命令将脚本制作成二进制文件了

go build wget.go

注意:有些url并不一定真的是不能下载,可能是被墙给挡住了,破解此法就是将此脚本放在墙外面运行

12、版本发布插件

在用 jenkins 实现远程发布的时候,除了通过 ansible 外,还可以通过自定义接口的方式来实现文件传输与版本发布,本例子演示通过 golang 自定义接口来实现文件发布

golang 服务端定义两个接口/upload/fparam,其中/upload接口主要用于将本地文件发送到远程机器上,/fparam 接口用于调用远程机器上的命令和脚本来实现发布,详细步骤如下:

  • 用户端调用服务端的/upload接口将要发布的文件传送到服务端(远程机器)
  • 服务端在接收到文件后,储存起来并计算文件的md5值,计算后将此md5的值返回给用户端,用户端通过返回的md5值和自己发送的文件作对比,如果一样,说明文件发送过程中无损失
  • 用户端再次调用/fpram接口,将刚才发送的文件名再次发送给服务端,服务端接收到文件名后,根据名字去调用函数,函数中通过文件名已经路径并使用命令将此文件发送到要发布的机器上,发送方式有ansible和scp两种方式(纯内网发送一般不会存在文件不完整),发送后再次调用脚本执行备份和发布操作

server端内容如下:

package main

import (
	"crypto/md5"
	"encoding/hex"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"io"
	"os"
	"os/exec"
)

// 获取文件的md5值
func md5Value(filename string) string {
	f, err := os.Open(filename)
	if err != nil {
		fmt.Println("open file error:", err)
		os.Exit(-1)
	}
	defer f.Close()
	hash := md5.New()
	if _, err := io.Copy(hash, f); err != nil {
		fmt.Println("io copy error:", err)
		os.Exit(-1)
	}
	hashBytes := hash.Sum(nil)
	hashString := hex.EncodeToString(hashBytes)
	return hashString
}

// app函数中包含了app0和app2节点
func app(filename string) (string, error) {
	//api-order.jar如何部署在了多个节点上,那么就循环节点信息,将文件发送到多个节点,app0和app2表示节点主机名
	orderSlice := []string{"app0", "app2"}
	str := ""
	for _, node := range orderSlice {
		out, err := execAnsible(node, fmt.Sprintf("/data/deploy/%s", filename), "/data/deploy", filename)
		if err != nil {
			return out, err
		}
		str = out
	}
	return str, nil
}

// app0函数中目前只包含app0节点,后续可根据需要在切片中增加即可
func app0(filename string) (string, error) {
	orderSlice := []string{"app0"}
	str := ""
	for _, node := range orderSlice {
		out, err := execAnsible(node, fmt.Sprintf("/data/deploy/%s", filename), "/data/deploy", filename)
		if err != nil {
			return out, err
		}
		str = out
	}
	return str, nil
}

// app2函数中目前只包含app2节点,后续可根据需要在切片中增加即可
func app2(filename string) (string, error) {
	orderSlice := []string{"app2"}
	str := ""
	for _, node := range orderSlice {
		out, err := execAnsible(node, fmt.Sprintf("/data/deploy/%s", filename), "/data/deploy", filename)
		if err != nil {
			return out, err
		}
		str = out
	}
	return str, nil
}

// nginx01包含nginx0和nginx1节点,可用来发布前端应用
func nginx01(filename string) (string, error) {
	orderSlice := []string{"nginx0", "nginx1"}
	str := ""
	for _, node := range orderSlice {
		out, err := execAnsible(node, fmt.Sprintf("/data/deploy/%s", filename), "/data/deploy", filename)
		if err != nil {
			return out, err
		}
		str = out
	}
	return str, nil
}

// 通过scp和ssh方式
func execAnsible(hostname, srcpath, destpath, filename string) (string, error) {
	str := ""
	//文件通过/upload上传到后默认就是到达nginx1的,因此如果要将文件发布到nginx1(本机),就不需要使用scp和ssh -t了,直接在本机执行即可
	if hostname == "nginx1" {
		backSlice := []string{"/data/script/backup.sh"}
		deploySlice := []string{"/data/script/deploy.sh"}
		mapp := make(map[int][]string)
		mapp[0] = backSlice
		mapp[1] = deploySlice
		for _, v := range mapp {
			fmt.Println("nginx1脚本:",v)
			cmd := exec.Command(v[0], filename)
			out, err := cmd.Output()
			if err != nil {
				return string(out), err
			}
			str = string(out)
		}
	} else {
		scpSlice := []string{fmt.Sprintf("%s", srcpath), fmt.Sprintf("cyyzops@%s:%s", hostname, destpath)}
		backSlice := []string{"-t", fmt.Sprintf("cyyzops@%s", hostname), fmt.Sprintf("/data/script/backup.sh %s", filename)}
		deploySlice := []string{"-t", fmt.Sprintf("cyyzops@%s", hostname), fmt.Sprintf("/data/script/deploy.sh %s", filename)}
		mapp := make(map[int][]string)
		mapp[0] = scpSlice
		mapp[1] = backSlice
		mapp[2] = deploySlice
		for k, v := range mapp {
			if k == 0 {
				cmd := exec.Command("scp", v...)
				out, err := cmd.Output()
				if err != nil {
					return string(out), err
				}
				str = string(out)
			} else {
				fmt.Println(v)
				cmd := exec.Command("ssh", v...)
				out, err := cmd.Output()
				if err != nil {
					return string(out), err
				}
				str = string(out)
			}
		}
	}
	return str, nil
}

// 通过ansible方式,跟上面的通过scp和ssh方式一样,根据情况选择
//
//	func execAnsible(hostname, srcpath, destpath string) error {
//		cmdSlice := [][]string{
//			{fmt.Sprintf("%s", hostname), "-m", "copy", "-a", fmt.Sprintf("src=%s dest=%s", srcpath, destpath)},
//			{fmt.Sprintf("%s", hostname), "-m", "shell", "-a", "bash /data/script/backup.sh"},
//			{fmt.Sprintf("%s", hostname), "-m", "shell", "-a", "bash /data/script/deploy.sh"},
//		}
//		for _, v := range cmdSlice {
//			//v是一个一维切片,...可以将切片中元素拆出来作为参数传递给Command,Command至少需要两个参数
//			cmd := exec.Command("ansible", v...)
//			_, err := cmd.CombinedOutput()
//			if err != nil {
//				fmt.Println("errrs:", err)
//				return err
//			}
//			//		continue
//		}
//		return nil
//	}
func checkFilename(filename string) (string, error) {
	if filename == "api-order.jar" {
		s, err := app(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else if filename == "platform-gateway.jar" {
		s, err := app(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else if filename == "api-rush-order.jar" {
		s, err := app(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else if filename == "platform-open.jar" {
		s, err := app(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else if filename == "api-ocr.jar" {
		s, err := app0(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else if filename == "api-unit.jar" {
		s, err := app(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else if filename == "api-user.jar" {
		s, err := app(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else if filename == "api-seal.jar" {
		s, err := app(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else if filename == "api-comm.jar" {
		s, err := app(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else if filename == "platform-open-security.jar" {
		s, err := app2(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else if filename == "platform-eureka.jar" {
		s, err := app0(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else if filename == "api-encode.jar" {
		s, err := app0(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else if filename == "api-shop.jar" {
		s, err := app(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else if filename == "platform-logs.jar" {
		s, err := app0(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else if filename == "platform-task.jar" {
		s, err := app0(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else if filename == "api-province.jar" {
		s, err := app0(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	
	} else if filename == "platform-commerce-gateway.jar" {
		s, err := app0(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else if filename == "sync-outward.jar" {
		s, err := app0(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else if filename == "sync-result-foshan.jar" {
		s, err := app0(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else if filename == "api-guest.jar" {
		s, err := app2(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else if filename == "html.tar.gz" {
		s, err := nginx01(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else if filename == "html_police.tar.gz" {
		s, err := nginx01(filename)
		if err != nil {
			return s, err
		}
		return s, nil
	} else {
		return "", errors.New("不存在这个文件!!!")
	}
}
func main() {
	router := gin.Default()
	router.POST("/upload", func(c *gin.Context) {
		_, file, err := c.Request.FormFile("filename")
		if err != nil {
			fmt.Println("/upload_error:", err)
			return
		}
		err = c.SaveUploadedFile(file, "/data/deploy/"+file.Filename)
		if err != nil {
			fmt.Println("/upload_error:", err)
			return
		}
		//获取md5值后返回给客户端做对比
		md5v := md5Value(fmt.Sprintf("/data/deploy/%s", file.Filename))
		c.JSON(200, gin.H{
			"code": 200,
			"data": md5v,
		})
		fmt.Println("remote machine md5:", md5v)
	})
	//客户端传递文件名过来,根据文件名在服务端做文件copy和备份发布操作
	router.POST("/fparam", func(c *gin.Context) {
		filename := c.PostForm("fname")
		out, err := checkFilename(filename)
		if err != nil {
			c.JSON(200, gin.H{"code": 100, "data": "", "error": err})
		} else {
			c.JSON(200, gin.H{
				"code": 200, "data": "", "error": err})
		}
		fmt.Println("/fparam_stdout:", out)
		fmt.Println("/fparam_error:", err)
	})
	router.Run(":8888")
}

agent内容如下:

package main

import (
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
//	"time"
)

type jtos struct {
	Code int    `json: code` //结构体标签加和不加,影响不大
	Data string `json: data`
}

// 将json反序列化到结构体中,为了获取json中的data内容,也就是远程文件的md5值
func jsonToStruct(j string) string {
	jts := jtos{}
	err := json.Unmarshal([]byte(j), &jts)
	if err != nil {
		log.Fatal(err)
	}
	return jts.Data

}
func md5Value(filename string) string {
	f, err := os.Open(filename)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
	hash := md5.New()
	if _, err := io.Copy(hash, f); err != nil {
		log.Fatal(err)
	}
	hashBytes := hash.Sum(nil)
	hashString := hex.EncodeToString(hashBytes)
	return hashString

}
func dproject(filename string) {
	client := &http.Client{}          //创建一个http的client实例,用于发送http请求
	bf := &bytes.Buffer{}             //创建Buffer实例,用于在内存中存储发送的nultipart数据
	writer := multipart.NewWriter(bf) //创建writer实例,写入到buffer中,writer后续用于构建multipart/form-data请求体
	req, err := http.NewRequest("POST", "https://opssys.yinzhangcloud.com/upload", nil)
	if err != nil {
		log.Fatal(err)
	}
	//设置请求头,writer.FormDataContentType方法返回一个Content-Type,格式为multipart/form-data;boundary=<boundary>,<boundary> 是一个随机生成的唯一字符串,用于分隔请求体中的不同部分
	req.Header.Set("Content-Type", writer.FormDataContentType())
	md5value := md5Value(filename)
	file, err := os.Open(filename)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()
	//创建表单文件字段,字段名为filename,filepath.Base可以截取路径最后的文件名
	part, err := writer.CreateFormFile("filename", filepath.Base(filename))
	if err != nil {
		log.Fatal(err)
	}
	//将文件数据流复制到表单字段part中
	_, err = io.Copy(part, file)
	if err != nil {
		log.Fatal(err)
	}
	err = writer.Close()
	if err != nil {
		log.Fatal(err)
	}
	//writer.WriteField("fieldName","fieldValue")  //添加其他字段
	req.Body = io.NopCloser(bf)
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal("cuowu:", err)
	}
	defer resp.Body.Close()
	bodys, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	r := jsonToStruct(string(bodys))
	//发送之前的文件和发送到远程机器文件的md5值相同,说明文件传输正常
	if md5value == r {
	       data := fmt.Sprintf("fname=%s",filepath.Base(filename))
	       requestBody := bytes.NewBuffer([]byte(data))
	       reqq,err := http.NewRequest("POST","https://opssys.yinzhangcloud.com/fparam",requestBody)
	       if err != nil {
                    log.Fatal(err)
	       }
	       reqq.Header.Set("Content-Type","application/x-www-form-urlencoded")
	       resps,err := client.Do(reqq)
	       if err != nil {
                   log.Fatal(err)
	       }
	       defer reqq.Body.Close()
               bodyss,err := ioutil.ReadAll(resps.Body)
	       if err != nil {
                   log.Fatal(err)
	       }
	       fmt.Println(string(bodyss))
	}
}
func main() {
	s := os.Args[1:]
	for _, v := range s {
		dproject(v)
	}
//	time.Sleep(time.Second * 10)
}

13、批量删除fastdfs文件,如下:

package main

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"
)

func findFdfs(filepath, filetype, filetime string) {
	cmd := exec.Command("find", fmt.Sprintf("%s", filepath), "-type", "f", "-name", fmt.Sprintf("*%s", filetype), "-mtime", fmt.Sprintf("+%s", filetime))
	out, err := cmd.CombinedOutput()
	if err != nil {
		log.Fatal("查找文件错误:", err)
	}
	f, err := os.OpenFile("fdfs.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0655)
	if err != nil {
		log.Fatal("打开fdfs.txt文件错误:", err)
	}
	defer f.Close()
	_, err = f.Write(out)
	if err != nil {
		log.Fatal(err)
	}
}
func del(id string) {
	cmd := exec.Command("fdfs_delete_file", "/etc/fdfs/client.conf", id)
	_, err := cmd.CombinedOutput()
	if err != nil {
		fmt.Println("文件名称:", id)
		log.Fatal("删除文件报错:", err)
	}
}

func main() {
	fileparam := os.Args[1:]
	findFdfs(fileparam[0], fileparam[1], fileparam[2])
	f, err := os.Open("fdfs.txt")
	if err != nil {
		log.Fatal("打开文件报错:", err)
	}
	defer f.Close()
	scan := bufio.NewScanner(f)
	res := ""
	for scan.Scan() {
		line := scan.Text()
		if strings.HasSuffix(fileparam[0], "/") {
			res = strings.Replace(line, fmt.Sprintf("%s", fileparam[0]), "group1/M00/", 1)
		} else {
			res = strings.Replace(line, fmt.Sprintf("%s", fileparam[0]), "group1/M00", 1)
		}
		del(res)
	}
}

注:最后的if流程控制的意思就是判断你在输入的文件路径参数的时候后面有没有加”/”,如果加了,那么在替换的时候就要写为group1/M00/,否则就会变成group1/M0000/06/….这种,如果没加,就替换为group1/M00

标签