好久没记录碰到的问题和学习的东西了,最近用学习用golang写东西,用一个客户端发送文件到服务端,服务端按原目录创建文件,并把文件的svn信息,文件内容传入数据库,记录的原因主要是之前在网上找golang执行命令获取svn信息找了好久只有零零星星的信息,最后我是根据这些信息试出来的,所以分享一下,希望能帮到有需要的人。
client
package main
import (
"fmt"
"github.com/axgle/mahonia"
"io"
"io/ioutil"
"net"
"os"
"os/exec"
"regexp"
)
func main() {
dir := "你的文件目录(f:\xx\xx)"
f,err := ioutil.ReadDir(dir)
if err != nil{
fmt.Println(err)
return
}
//等接收方回应
//for {
for fileIndex,file := range f {
if file.IsDir() {
xfiles, _ := GetAllFiles(dir+"\\"+file.Name())
for _, fileName := range xfiles {
createConn(file,fileName)
fmt.Printf("获取的文件为[%s]\n", fileName)
}
fmt.Println(fileIndex)
} else {
conn,err1 := net.Dial("tcp","127.0.0.1:9090")
if err1 != nil{
fmt.Println("dial error:",err1)
return
}
defer conn.Close()
verision := getSvnInfo(dir+"\\"+file.Name())
_, err = conn.Write([]byte(dir+"\\"+file.Name()+"|"+"1"+"|"+verision))
if err != nil {
fmt.Println("conn write server err:", err)
break
}
var n int
buf := make([]byte, 1024)
n, err = conn.Read(buf)
if err != nil {
fmt.Println("conn.read err=", err)
break
}
fmt.Printf("遍历文件[%s]", file.Name())
if "ok" == string(buf[:n]) {
sendFile(dir+"\\"+file.Name(), conn)
}
}
//}
}
//info, err := os.Stat(dir) //获取文件属性
//if err != nil {
// fmt.Println("err = ", err)
// return
//}
先发送一次文件名
//_, err = conn.Write([]byte(info.Name()))
//if err != nil {
// fmt.Println("conn Write server err : ", err)
// return
//}
//
坐等接收对方的回应(最好是 "ok" )
//var n int
//buf := make([]byte, 1024)
//n, err = conn.Read(buf)
//if err != nil {
// fmt.Println("conn.Read err = ", err)
// return
//}
//if "ok" == string(buf[:n]) {
// //发送文件
// sendFile(dir, conn)
//}
}
func createConn(file os.FileInfo,dir string) {
conn,err1 := net.Dial("tcp","127.0.0.1:9090")
if err1 != nil{
fmt.Println("dial error:",err1)
return
}
verision := getSvnInfo(dir)
defer conn.Close()
var err error
_, err = conn.Write([]byte(dir+"|"+"2"+"|"+verision))
if err != nil {
fmt.Println("conn write server err:", err)
return
}
var n int
buf := make([]byte, 1024)
n, err = conn.Read(buf)
if err != nil {
fmt.Println("conn.read err=", err)
return
}
fmt.Printf("遍历文件[%s]", file.Name())
if "ok" == string(buf[:n]) {
sendFile(dir, conn)
}
}
func sendFile(filePath string,conn net.Conn) {
f,err :=os.Open(filePath)
if err != nil{
fmt.Println("os.open is err=",err)
return
}
defer f.Close()
buf:=make([]byte,1024*4)
for {
n,err:=f.Read(buf)
if err!= nil{
if err == io.EOF && n == 0{
fmt.Println("文件发送完毕",filePath)
}else {
fmt.Println("f.Read err =",err)
}
return
}
conn.Write(buf[:n])
}
}
func GetAllFiles(dirPth string) (files []string, err error) {
var dirs []string
dir, err := ioutil.ReadDir(dirPth)
if err != nil {
return nil, err
}
PthSep := string(os.PathSeparator)
for _, fi := range dir {
if fi.IsDir() { // 目录, 递归遍历
dirs = append(dirs, dirPth+PthSep+fi.Name())
GetAllFiles(dirPth + PthSep + fi.Name())
} else {
files = append(files, dirPth+PthSep+fi.Name())
}
}
// 读取子目录下文件
for _, table := range dirs {
temp, _ := GetAllFiles(table)
for _, temp1 := range temp {
files = append(files, temp1)
}
}
return files, nil
}
func GetFilesAndDirs(dirPth string) (files []string, dirs []string, err error) {
dir, err := ioutil.ReadDir(dirPth)
if err != nil {
return nil, nil, err
}
PthSep := string(os.PathSeparator)
//suffix = strings.ToUpper(suffix) //忽略后缀匹配的大小写
for _, fi := range dir {
if fi.IsDir() { // 目录, 递归遍历
dirs = append(dirs, dirPth+PthSep+fi.Name())
GetFilesAndDirs(dirPth + PthSep + fi.Name())
} else {
files = append(files, dirPth+PthSep+fi.Name())
}
}
return files, dirs, nil
}
func getSvnInfo(path string)(str string) {
cmd := exec.Command("svn", "info",path)
out, err := cmd.CombinedOutput()
cmd.Run()
if err != nil{
fmt.Printf("combined out:\n\n",err)
}
output := mahonia.NewDecoder("gbk").ConvertString(string((out)))
version := matchVersion(output)
return version
}
func matchVersion(output string)(version string) {
reg1 := regexp.MustCompile(`(最后修改的版本:|Last Changed Rev:)\s(?s:(.*?))\s\s(最后修改的时间|Last Changed Date)`)
if reg1 == nil { //解释失败,返回nil
fmt.Println("regexp err")
return "noVersion"
}
params := reg1.FindStringSubmatch(output)
//result1 := reg1.FindAllStringSubmatch(output, -1)
//fmt.Println("result1 = ", result1,params[2])
return params[2]
}
重点看下获取svn信息那里就行了,发送文件的代码在网上能找到,提醒一下只装了TortoiseSVN是不行的,还要装一下sliksvn才能执行svn指令,如果dos窗口执行不了svn的话百度一下就行。
server
package main
import (
"bufio"
"bytes"
"compress/zlib"
"database/sql"
"fmt"
"github.com/jthmath/winapi"
_ "github.com/mattn/go-sqlite3"
lua "github.com/yuin/gopher-lua"
"github.com/yuin/gopher-lua/parse"
"html/template"
"io"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
)
var insertStmt *sql.Stmt = nil
var udpateStmt *sql.Stmt = nil
var udpateInfoStmt *sql.Stmt = nil
var removeStmt *sql.Stmt =nil
var dbServe *sql.DB = nil
func main() {
listener,err:= net.Listen("tcp","127.0.0.1:9090")
if err != nil {
fmt.Println("listen fail err=",err)
return
}
defer listener.Close()
initDB()
for {
conn,err1 := listener.Accept()
if err1 != nil{
fmt.Println("listen accept err=",err)
break
}
go recvFile(conn)
}
dbServe.Close()
}
func recvFile(conn net.Conn) {
defer conn.Close()
//读取客户端请求
buf1:= make([]byte,1024)
var n int
n,err := conn.Read(buf1)
if err != nil{
fmt.Println("conn read err=",err)
return
}
fileName := string(buf1[:n])
//stringTable := bytes.Split(buf1,[]byte("|"))
stringTable := strings.Split(fileName,"|")
s1 := string(stringTable[1])
s0 := string(stringTable[0])
s2 := string(stringTable[2])
tempPath := s0
tempPath = string(s0[13:len(s0)])
uiName:=matchFileName(s0)
//文件夹类型
if s1 == "2"{
pathTable := strings.Split("发过来的文件存放的目录",string(os.PathSeparator))
createFile(pathTable)
}
conn.Write([]byte("ok"))
//新建文件
f,err := os.Create("发过来的文件存放的目录 跟上面是同一个")
if err != nil {
fmt.Println("create file fail",err)
return
}
defer f.Close()
//从 conn读取本地文件
buf:= make([]byte,0)
tmp := make([]byte, 256)
//finnalBuf :=make([]byte,4096)
for {
n,err := conn.Read(tmp)
buf = append(buf, tmp[:n]...)
if err != nil && err != io.EOF{
fmt.Println("conn.read err=",err)
}
if n == 0 {
zliBytes := DoZlibCompress(buf)
SaveToDataBase(s2,uiName,zliBytes)
fmt.Println("文件接受完毕")
return
}
f.Write(tmp[:n])
}
}
func SaveToDataBase(s2 string,uiName string,viewInfo []byte) {
version, err := strconv.Atoi(s2)
if err == nil {
lastVersion := findVersionInfo(uiName)
if lastVersion != -1{
if TotalCount == 0 {
TotalCount = GetSqlCount()
}
if version > lastVersion{
fmt.Println("当前版本号大于数据库储存版本号,更新数据库")
UpdateInfo(uiName,version)
UpdateViewInfo(uiName,viewInfo)
}else if lastVersion > version {
if viewInfo != nil{
fmt.Println("当前版本号小于数据库储存版本号,更新文件",lastVersion,version,uiName)
}
}
}else {
InsertInfo(uiName,version,viewInfo)
}
}
}
func isFileExists(path string)(bool,error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func createFile(pathTable []string) {
curDir := ""
for index,Path := range pathTable {
if index != len(pathTable) - 1{
curDir = curDir+Path+string(os.PathSeparator)
}
}
isExist,exitError := isFileExists(curDir)
if exitError != nil{
fmt.Println("获取文件错误")
}else{
if isExist{
fmt.Println("存在",curDir)
}else{
err := os.MkdirAll( curDir , 0777)
if err != nil {
fmt.Println("错误",err)
return
}
fmt.Println("创建",curDir)
}
}
}
func initDB () {
db, err := sql.Open("sqlite3", "./data.db")
//db.Exec("PRAGMA journal_mode=WAL;")
db.SetMaxOpenConns(1)
checkErr(err)
fileInfoTable := `CREATE TABLE IF NOT EXISTS fileInfo(
fileName TEXT PRIMARY KEY,
version INT NOT NULL,
viewInfo BLOB NULL)`
db.Exec(fileInfoTable)
// 插入数据
insertStmt, err = db.Prepare("INSERT OR IGNORE INTO fileInfo(fileName, version,viewInfo) values(?,?,?)")
checkErr(err)
// 更新数据
udpateStmt, err = db.Prepare("UPDATE fileInfo set version=? where fileName=?")
checkErr(err)
// 更新数据
udpateInfoStmt, err = db.Prepare("UPDATE fileInfo set viewInfo=? where fileName=?")
checkErr(err)
//删除数据
removeStmt,err = db.Prepare("DELETE FROM fileInfo where fileName=?")
checkErr(err)
dbServe = db
}
func InsertInfo(fileName string,version int,viewInfo []byte) {
//tx, err := dbServe.Begin()//打开事物
//defer tx.Commit()//事物提交
res, err := insertStmt.Exec(fileName,version,viewInfo)
affect, err := res.RowsAffected()
fmt.Println("affect====",affect)
checkErr(err)
}
func UpdateInfo(fileName string,version int) {
res, err := udpateStmt.Exec(version,fileName)
affect, err := res.RowsAffected()
fmt.Println("affect====",affect)
checkErr(err)
}
func UpdateViewInfo(fileName string,viewInfo []byte) {
//tx, err := dbServe.Begin()//打开事物
//defer tx.Commit()//事物提交
res, err := udpateInfoStmt.Exec(viewInfo,fileName)
affect, err := res.RowsAffected()
fmt.Println("affect====",affect)
checkErr(err)
}
func findVersionInfo(name string)(v int) {
//tx, err := dbServe.Begin()//打开事物
//defer tx.Commit()//事物提交
// 查询数据
rows, err := dbServe.Query("SELECT version FROM fileInfo where fileName = ?",name)
checkErr(err)
var version int = -1
defer rows.Close()
for rows.Next() {
err = rows.Scan(&version)
checkErr(err)
}
return version
}
func findViewInfo(name string)(v []byte) {
//tx, err := dbServe.Begin()//打开事物
//defer tx.Commit()//事物提交
// 查询数据
rows, err := dbServe.Query("SELECT viewInfo FROM fileInfo where fileName = ?",name)
checkErr(err)
var view []byte = nil
defer rows.Close()
for rows.Next() {
err = rows.Scan(&view)
checkErr(err)
}
return view
}
func checkErr(err error) {
if err != nil {
fmt.Println(err)
panic(err)
}
}
func matchFileName(str string)(fileName string) {
pathTable := strings.Split(str,string(os.PathSeparator))
return pathTable[len(pathTable) - 1]
}
//进行zlib压缩
func DoZlibCompress(src []byte) []byte {
var in bytes.Buffer
w := zlib.NewWriter(&in)
w.Write(src)
w.Close()
return in.Bytes()
}
//进行zlib解压缩
func DoZlibUnCompress(compressSrc []byte) []byte {
b := bytes.NewReader(compressSrc)
var out bytes.Buffer
r, _ := zlib.NewReader(b)
io.Copy(&out, r)
return out.Bytes()
}
server是接受文件和存进数据库的,也有几个坑,go协程写入文件信息会报错,印象中查的原因是说协程db有可能边插入update,导致状态冲突锁住,设置db.SetMaxOpenConns(1)解决;第二个是写入文件信息初始化buf:= make([]byte,0)切片大小要为0,不然设置了大小写完后前面会有一大堆空格。如果以上代码不能直接运行的话留意一下目录设置那里,那里的处理是按我本地文件路径设置的。
以上就是这次要分享的,最后想说的是这次弄这个东西查资料的时候发现资料很少,很多说得很简单的,要不就是锁着要付费,然后就感觉很不爽,所以更加要分享出来。程序员本来就很苦逼,如果不是互联网牛逼可以查到很多信息会更苦逼,你当初菜的时候去学人家的,现在会了就藏着掖着,真是狗东西。
真的鼓励各位技术大佬学新东西的时候把遇到的问题大方分享出来,除了能回顾,还能记录,甚至还能帮到有问题的人,这种事要多做啊。
版权声明:本文为x2345com原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。