liuxiaolong
2019-12-16 63873fc1fea408c201cef5cfedb3a9bdaeab5b4d
add patchUpdate
2个文件已添加
7个文件已修改
462 ■■■■■ 已修改文件
config/dev.yaml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
config/pro.yaml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
config/test.yaml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
controllers/syssetcont.go 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
extend/config/config.go 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
extend/util/util.go 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
router/router.go 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
service/SysService.go 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
updatePatch.sh 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
config/dev.yaml
@@ -19,6 +19,19 @@
    channelCount: 0
    #硬盘个数
    diskCount: 2
    #Exec root command
    sudoPassword: 123
    sysThresholds:
    - value: 60
      color: '#13ce66'
    - value: 80
      color: '#FF9C4A'
    - value: 95
      color: '#f53d3d'
    - value: 100
      color: '#5d0000'
    ptzSpeed: 50
    patchPath: /opt/vasystem/patch
database:
    driver: sqlite
    name: sqlite3
config/pro.yaml
@@ -19,6 +19,19 @@
    channelCount: 0
    #硬盘个数
    diskCount: 2
    #Exec root command
    sudoPassword: 123
    sysThresholds:
    - value: 60
      color: '#13ce66'
    - value: 80
      color: '#FF9C4A'
    - value: 95
      color: '#f53d3d'
    - value: 100
      color: '#5d0000'
    ptzSpeed: 50
    patchPath: /opt/vasystem/patch
database:
    driver: sqlite
    name: sqlite3
config/test.yaml
@@ -35,6 +35,7 @@
    value: 100
    color: '#5d0000'
  ptzSpeed: 50
  patchPath: /opt/vasystem/patch
database:
  driver: sqlite
  name: sqlite3
controllers/syssetcont.go
@@ -1,11 +1,14 @@
package controllers
import (
    "net/http"
    "regexp"
    "strconv"
    "webserver/extend/code"
    "webserver/extend/config"
    "webserver/extend/sys"
    "webserver/extend/util"
    "webserver/middlewares/auth"
    "basic.com/dbapi.git"
    "github.com/gin-gonic/gin"
@@ -429,3 +432,114 @@
    util.ResponseFormat(c, code.Success, "配置成功")
}
// @Security ApiKeyAuth
// @Summary 系统更新包上传(分块检查)
// @Description 系统更新包上传(分块检查)
// @Accept multipart/form-data
// @Produce json
// @Tags sysset
// @Param chunkNumber formData int true "当前是第几个分块"
// @Param chunkSize formData int true "每一块的大小"
// @Param currentChunkSize formData int true "当前块的大小"
// @Param identifier formData string true "整个文件的唯一标识,目前是md5"
// @Param filename formData string true "文件名称"
// @Param relativePath formData string true "文件在客户端电脑的路径"
// @Param totalChunks formData int true "文件总块数"
// @Success 200 {string} json "{"code":200, msg:"", success:true}"
// @Failure 500 {string} json "{"code":500, msg:"", success:false}"
// @Router /data/api-v/sysset/patchUpdate [get]
func (sset SysSetController) PatchUpdateCheck(c *gin.Context) {
    authDriver := auth.GenerateAuthDriver()
    userM := (*authDriver).User(c)
    if userM ==nil {
        util.ResponseFormat(c,code.TokenNotFound,"登录过期,请登录")
        return
    }
    chunkNumber, e1 := strconv.Atoi(c.Request.FormValue("chunkNumber"))
    chunkSize, e2 := strconv.Atoi(c.Request.FormValue("chunkSize"))
    currentChunkSize, e3 := strconv.Atoi(c.Request.FormValue("currentChunkSize"))
    identifier := c.Request.FormValue("identifier")
    filename := c.Request.FormValue("filename")
    relativePath := c.Request.FormValue("relativePath")
    totalChunks, e5 := strconv.Atoi(c.Request.FormValue("totalChunks"))
    if identifier == "" || e1 != nil || e2 != nil || e3 != nil || e5 !=nil {
        util.ResponseFormat(c,code.RequestParamError,"参数有误")
        return
    }
    var arg = service.FileChunkCheckVo {
        UserId: userM["id"].(string),
        FileName: filename,
        Identifier: identifier,
        RelativePath: relativePath,
        TotalChunks: totalChunks,
        ChunkNumber: chunkNumber,
        ChunkSize: chunkSize,
        CurrentChunkSize:currentChunkSize,
    }
    var sv service.SysService
    if b := sv.CheckUpdateFile(&arg);b {
        c.String(http.StatusOK,"found")
    } else {
        c.String(http.StatusNoContent,"")
    }
}
// @Security ApiKeyAuth
// @Summary 系统更新包上传
// @Description 系统更新包上传
// @Accept multipart/form-data
// @Produce json
// @Tags sysset
// @Param chunkNumber formData int true "当前是第几个分块"
// @Param chunkSize formData int true "每一块的大小"
// @Param currentChunkSize formData int true "当前块的大小"
// @Param totalSize formData string true "文件总大小"
// @Param identifier formData string true "整个文件的唯一标识,目前是md5"
// @Param filename formData string true "文件名称"
// @Param relativePath formData string true "文件在客户端电脑的路径"
// @Param totalChunks formData int true "文件总块数"
// @Param file formData file true "文件分片内容"
// @Success 200 {string} json "{"code":200, msg:"", success:true}"
// @Failure 500 {string} json "{"code":500, msg:"", success:false}"
// @Router /data/api-v/sysset/patchUpdate [post]
func (sset SysSetController) PatchUpdate(c *gin.Context) {
    authDriver := auth.GenerateAuthDriver()
    userM := (*authDriver).User(c)
    if userM == nil {
        util.ResponseFormat(c, code.TokenNotFound, "登录过期,请登录")
        return
    }
    chunkNumber, e1 := strconv.Atoi(c.Request.FormValue("chunkNumber"))
    chunkSize, e2 := strconv.Atoi(c.Request.FormValue("chunkSize"))
    currentChunkSize, e3 := strconv.Atoi(c.Request.FormValue("currentChunkSize"))
    totalSize, e4 := strconv.ParseInt(c.Request.FormValue("totalSize"), 10, 64)
    identifier := c.Request.FormValue("identifier")
    filename := c.Request.FormValue("filename")
    relativePath := c.Request.FormValue("relativePath")
    totalChunks, e5 := strconv.Atoi(c.Request.FormValue("totalChunks"))
    file, header, e6 := c.Request.FormFile("file")
    if identifier == "" || e1 != nil || e2 != nil || e3 != nil || e4 != nil || e5 != nil || e6 != nil {
        util.ResponseFormat(c, code.RequestParamError, "参数有误")
        return
    }
    var arg = service.FileUploadVo{
        UserId:           userM["id"].(string),
        FileName:         filename,
        Identifier:       identifier,
        RelativePath:     relativePath,
        TotalSize:        totalSize,
        TotalChunks:      totalChunks,
        ChunkNumber:      chunkNumber,
        ChunkSize:        chunkSize,
        CurrentChunkSize: currentChunkSize,
        File:             &file,
        Header:           header,
    }
    var sv service.SysService
    if b := sv.PatchUpload(&arg); b {
        util.ResponseFormat(c, code.Success, "")
    } else {
        util.ResponseFormat(c, code.ComError, "")
    }
}
extend/config/config.go
@@ -29,6 +29,7 @@
    SudoPassword  string      `mapstructure: "sudoPassword"` //系统密码
    SysThresholds []threshold `mapstructure: "sysThresholds"`
    PTZSpeed      int         `mapstructure: "ptzSpeed"` // 云台移动速度
    PatchPath       string `mapstructure:"patchPath"`//系统更新包路径
}
var Server = &server{}
extend/util/util.go
@@ -1,12 +1,17 @@
package util
import (
    "archive/zip"
    "bytes"
    "encoding/json"
    "image"
    "io"
    "io/ioutil"
    "net/http"
    "os"
    "path/filepath"
    "strconv"
    "time"
    "webserver/extend/code"
    "basic.com/pubsub/protomsg.git"
@@ -208,3 +213,87 @@
    out = out[:n] // compressed data
    return out, nil
}
// 判断所给路径文件/文件夹是否存在
func Exists(path string) bool {
    if path == "" {
        return false
    }
    _, err := os.Stat(path)    //os.Stat获取文件信息
    if err != nil {
        if os.IsExist(err) {
            return true
        }
        return false
    }
    return true
}
func CreateDirectory(path string) bool {
    if path == "" {
        return false
    }
    if Exists(path) {
        return true
    }
    err := os.MkdirAll(path,os.ModePerm)
    if err !=nil {
        return false
    }
    return true
}
//日期转字符串
func FormatDate(date time.Time, layout string) string {
    layout = strings.Replace(layout, "yyyy", "2006", 1)
    layout = strings.Replace(layout, "yy", "06", 1)
    layout = strings.Replace(layout, "MM", "01", 1)
    layout = strings.Replace(layout, "dd", "02", 1)
    layout = strings.Replace(layout, "HH", "15", 1)
    layout = strings.Replace(layout, "mm", "04", 1)
    layout = strings.Replace(layout, "ss", "05", 1)
    layout = strings.Replace(layout, "SSS", "000", -1)
    return date.Format(layout)
}
func ZipCheck(zipPath string) bool {
    f, err := os.Open(zipPath)
    if err != nil {
        return false
    }
    defer f.Close()
    buf := make([]byte, 4)
    if n, err := f.Read(buf); err != nil || n < 4 {
        return false
    }
    return bytes.Equal(buf, []byte("PK\x03\x04"))
}
func UnZip(archive, target string) error {
    reader, err := zip.OpenReader(archive)
    if err != nil {
        return err
    }
    if err := os.MkdirAll(target, 0755); err != nil {
        return err
    }
    for _, file := range reader.File {
        path := filepath.Join(target, file.Name)
        if file.FileInfo().IsDir() {
            os.MkdirAll(path, file.Mode())
            continue
        }
        fileReader, err := file.Open()
        if err != nil {
            return err
        }
        defer fileReader.Close()
        targetFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
        if err != nil {
            return err
        }
        defer targetFile.Close()
        if _, err := io.Copy(targetFile, fileReader); err != nil {
            return err
        }
    }
    return nil
}
router/router.go
@@ -230,6 +230,9 @@
        vsset.GET("/reboot", ssController.RebootOS)
        vsset.GET("/rebootTask", ssController.GetRebootTask)
        vsset.POST("/rebootTask", ssController.SetRebootTask)
        vsset.GET("/patchUpdate", ssController.PatchUpdateCheck)
        vsset.POST("/patchUpdate", ssController.PatchUpdate)
    }
    //算法库操作
service/SysService.go
New file
@@ -0,0 +1,201 @@
package service
import (
    "basic.com/valib/logger.git"
    "bufio"
    "fmt"
    "io/ioutil"
    "mime/multipart"
    "os"
    "os/exec"
    "strconv"
    "strings"
    "webserver/extend/config"
    "webserver/extend/util"
)
type SysService struct {
}
type FileChunkCheckVo struct {
    UserId string
    ChunkNumber int //当前分片下标,从1开始
    ChunkSize int //每一块的大小
    CurrentChunkSize int //当前分块的大小
    FileName string  //文件名称
    Identifier string //整个文件唯一标识,md5
    RelativePath string //文件客户端路径
    TotalSize int64 //文件总大小
    TotalChunks int //总分片数量
}
type FileUploadVo struct {
    Id string
    UserId string
    ChunkNumber int //当前分片下标,从1开始
    ChunkSize int //每一块的大小
    CurrentChunkSize int //当前分块的大小
    FileName string  //文件名称
    Identifier string //整个文件唯一标识,md5
    RelativePath string //文件客户端路径
    TotalSize int64 //文件总大小
    TotalChunks int //总分片数量
    File *multipart.File //当前分片的文件内容
    Header *multipart.FileHeader
}
func (sv SysService) CheckUpdateFile(arg *FileChunkCheckVo) bool {
    configPatchPath := ""
    if config.Server.PatchPath != "" {
        configPatchPath = config.Server.PatchPath
    } else {
        configPatchPath = "/opt/vasystem/patch"
    }
    fileTmpPath := configPatchPath + "/"+arg.Identifier
    if !util.Exists(fileTmpPath) {
        return false
    }
    //判断合成的文件是否存在
    index := strings.LastIndex(arg.FileName, ".")
    subfix := ""
    if index >-1 {//有后缀
        subfix = arg.FileName[index:]
    }
    mergedFilePath := fileTmpPath+subfix
    if util.Exists(mergedFilePath) {
        return true
    }
    //判断分块文件是否存在
    chunkFilePath := fileTmpPath+"/"+arg.Identifier+"_"+strconv.Itoa(arg.ChunkNumber)
    if !util.Exists(chunkFilePath) {
        return false
    }
    if arg.ChunkNumber == arg.TotalChunks {
        dirFiles, _ := ioutil.ReadDir(fileTmpPath)
        if dirFiles != nil && len(dirFiles) == arg.TotalChunks {
            //表示所有分块都上传了,需要merge
            if sv.MergeChunks(fileTmpPath, mergedFilePath) {
                if util.ZipCheck(mergedFilePath) {
                    if !updatePatch(arg.Identifier, subfix) {
                        return false
                    }
                }
            }
        }
    }
    return true
}
func (sv SysService) PatchUpload(arg *FileUploadVo) bool {
    configPatchPath := ""
    if config.Server.PatchPath != "" {
        configPatchPath = config.Server.PatchPath
    } else {
        configPatchPath = "/opt/vasystem/patch"
    }
    defer (*arg.File).Close()
    if !util.CreateDirectory(configPatchPath) {
        return false
    }
    index := strings.LastIndex(arg.Header.Filename, ".")
    if index < 0 {
        return false
    }
    subfix := arg.Header.Filename[index:]
    MD5Str := arg.Identifier
    logger.Debug("Identifier:",MD5Str)
    fileTmpPath := configPatchPath + "/"+MD5Str
    if !util.Exists(fileTmpPath) {
        if !util.CreateDirectory(fileTmpPath) {
            return false
        }
    }
    fileSavePath := fileTmpPath+"/"+MD5Str+"_"+strconv.Itoa(arg.ChunkNumber)
    if util.Exists(fileSavePath) {
        rmErr := os.Remove(fileSavePath)
        if rmErr != nil {
            logger.Debug("rmErr:",rmErr)
            return false
        }
    }
    file, e := os.Create(fileSavePath)
    if e !=nil {
        logger.Debug("os.Create err:",e,"fileSavePath:",fileSavePath)
        return false
    }
    defer file.Close()
    writer := bufio.NewWriter(file)
    chunkData := make([]byte, arg.Header.Size)
    n, err := (*arg.File).ReadAt(chunkData, 0)
    if n ==0 || err !=nil {
        logger.Debug("read chunkData err:",err,"n:",n)
        return false
    }
    nn, err2 := writer.Write(chunkData)
    if nn ==0 || err2 !=nil {
        logger.Debug("write chunkData err:",err2,"nn:",nn)
        return false
    }
    isComplete := false
    dirFiles, _ := ioutil.ReadDir(fileTmpPath)
    if dirFiles != nil && len(dirFiles) == arg.TotalChunks {
        isComplete = true
    }
    if isComplete {
        if sv.MergeChunks(fileTmpPath,fileTmpPath + subfix) {
            logger.Debug("merge all chunks success,identifier:",MD5Str,"fileName:",arg.FileName)
            if util.ZipCheck(fileTmpPath + subfix) {
                if !updatePatch(arg.Identifier, subfix) {
                    return false
                }
            }
        }
    }
    return true
}
//更新系统程序
func updatePatch(identifier string, ext string) bool {
    configPatchPath := ""
    if config.Server.PatchPath != "" {
        configPatchPath = config.Server.PatchPath
    } else {
        configPatchPath = "/opt/vasystem/patch"
    }
    //1.解压缩更新包
    unZipPath := configPatchPath+"/"+identifier+"_basic/"
    if util.Exists(unZipPath) {
        //此版本已经更新过
        return true
    } else {
        if !util.CreateDirectory(unZipPath) {
            return false
        }
    }
    err := util.UnZip(configPatchPath+"/"+identifier+ext, unZipPath)
    if err !=nil {
        logger.Debug("UnZip err:",err,"zipFile:",configPatchPath+"/"+identifier+ext)
        return false
    }
    //2.更新系统
    var cmd *exec.Cmd
    cmd = exec.Command("/bin/sh","-c",fmt.Sprintf("./updatePatch.sh %s",unZipPath))
    logger.Debug("called sh updatePatch.sh ", unZipPath)
    if b, err := cmd.Output(); err != nil {
        logger.Debug("updatePatch err:",err,"result:",string(b))
        return false
    }
    return true
}
func (sv SysService) MergeChunks(chunkPath string, storePath string) bool {
    var cmd *exec.Cmd
    cmd = exec.Command("/bin/sh", "-c", fmt.Sprintf("./mergeAll.sh %s %s", chunkPath, storePath))
    logger.Debug("called sh mergeAll.sh ", chunkPath, storePath)
    if b, err := cmd.Output(); err != nil {
        logger.Debug("mergeChunks err:", err, "result:", string(b))
        return false
    } else {
        return true
    }
}
updatePatch.sh
New file
@@ -0,0 +1,27 @@
#!/bin/bash
cd /opt/vasystem/
#stop
./kill.sh
sleep 3
patchPath=$1
files=$(ls $patchPath)
echo "patchPath: " $patchPath
if [ ! -f $patchPath ];then
    echo "$patchPath not exist"
else
    for item in $files
    do
        if [ -f $item ];then
            cp $patchPath/$item /opt/vasystem/bin
        else
            echo "directory name is ${item}"
            if [[ "$item"=="dist" ]];then
                echo "update web dist"
                cp -r $patchPath/$item /opt/vasystem/web/dist
            fi
        fi
    done
fi
./daemon.sh -shm
sleep 3
echo "patch update success"