package service
|
|
import (
|
"basic.com/valib/bhomeclient.git"
|
"basic.com/valib/bhomedbapi.git"
|
"basic.com/valib/licence.git"
|
"basic.com/valib/logger.git"
|
"bufio"
|
"context"
|
"encoding/json"
|
"errors"
|
"fmt"
|
"github.com/mitchellh/mapstructure"
|
"io/ioutil"
|
"os"
|
"os/exec"
|
"path"
|
"path/filepath"
|
"strings"
|
"sync"
|
"time"
|
"vamicro/config"
|
"vamicro/extend/util"
|
versionControlM "vamicro/version-control/models"
|
)
|
|
var (
|
/**
|
"app_auto_clean" : "0",
|
"app_update_notice": "0",
|
"app_auto_update": "0",
|
*/
|
VersionUpdateSetting = map[string]string{"app_auto_clean": "0", "app_update_notice": "0", "app_auto_update": "0"}
|
|
updateNoticeInfo UpdateNoticeInfo
|
NoticeLock sync.Mutex
|
PreDownloading bool
|
)
|
|
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 *bhomeclient.FileArg //当前分片的文件内容
|
}
|
|
type UpdateNoticeInfo struct {
|
NoticeUser map[string]int
|
NeedUpdate bool
|
PreDownUpgrade bool
|
NoticeStatus bool
|
AutoClean bool
|
SdkNeedUpgrade []SdkInsOrUpgrade
|
AppNeedUpgrade []AppWithShop
|
LastNoticeTime int
|
}
|
|
func init() {
|
updateNoticeInfo.NoticeUser = make(map[string]int)
|
}
|
|
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
|
}
|
//判断分块文件是否存在
|
chunkAlignNum := util.FormatNum(arg.TotalChunks, arg.ChunkNumber)
|
chunkFilePath := fileTmpPath + "/" + arg.Identifier + "_" + chunkAlignNum
|
if !util.Exists(chunkFilePath) {
|
return false
|
}
|
if arg.ChunkNumber == arg.TotalChunks {
|
dirFiles, _ := ioutil.ReadDir(fileTmpPath)
|
if dirFiles != nil && len(dirFiles) == arg.TotalChunks {
|
//表示所有分块都上传了,需要merge
|
if b, _ := MergeChunks(fileTmpPath, mergedFilePath); !b {
|
return false
|
}
|
}
|
}
|
return true
|
}
|
|
func (sv SysService) PatchUpload(arg *FileUploadVo) (bool, bool, string) {
|
configPatchPath := ""
|
if config.Server.PatchPath != "" {
|
configPatchPath = config.Server.PatchPath
|
} else {
|
configPatchPath = "/opt/vasystem/patch"
|
}
|
|
if !util.CreateDirectory(configPatchPath) {
|
return false, false, "创建文件夹失败"
|
}
|
|
filenameWithSuffix := path.Base(arg.FileName)
|
subfix := path.Ext(filenameWithSuffix)
|
MD5Str := arg.Identifier
|
logger.Debug("Identifier:", MD5Str)
|
fileTmpPath := configPatchPath + "/" + MD5Str
|
if !util.Exists(fileTmpPath) {
|
if !util.CreateDirectory(fileTmpPath) {
|
return false, false, "创建补丁文件夹失败"
|
}
|
}
|
chunkAlignNum := util.FormatNum(arg.TotalChunks, arg.ChunkNumber)
|
fileSavePath := fileTmpPath + "/" + MD5Str + "_" + chunkAlignNum
|
if util.Exists(fileSavePath) {
|
rmErr := os.Remove(fileSavePath)
|
if rmErr != nil {
|
logger.Debug("rmErr:", rmErr)
|
return false, false, rmErr.Error()
|
}
|
}
|
file, e := os.Create(fileSavePath)
|
if e != nil {
|
logger.Debug("os.Create err:", e, "fileSavePath:", fileSavePath)
|
return false, false, e.Error()
|
}
|
defer file.Close()
|
writer := bufio.NewWriter(file)
|
|
nn, err2 := writer.Write(arg.File.Bytes)
|
if nn == 0 || err2 != nil {
|
logger.Debug("write chunkData err:", err2, "nn:", nn)
|
return false, false, "写入补丁包失败"
|
}
|
if err2 = writer.Flush(); err2 != nil {
|
logger.Debug("write flush err:", err2)
|
return false, false, err2.Error()
|
}
|
isComplete := false
|
dirFiles, _ := ioutil.ReadDir(fileTmpPath)
|
if dirFiles != nil && len(dirFiles) == arg.TotalChunks {
|
isComplete = true
|
}
|
if isComplete {
|
if mergeB, mergeE := MergeChunks(fileTmpPath, fileTmpPath+subfix); mergeB {
|
logger.Debug("merge all chunks success,identifier:", MD5Str, "fileName:", arg.FileName)
|
unPackB, unPackErr := unPackPatchPackage(MD5Str, subfix)
|
logger.Debug("unPackB:", unPackB, "unPackErr:", unPackErr)
|
if unPackB {
|
return true, isComplete, "解压补丁包失败,错误的补丁包格式"
|
}
|
} else {
|
return false, isComplete, mergeE.Error()
|
}
|
}
|
return true, isComplete, ""
|
}
|
|
//upgrade
|
//func (sv SysService) Upgrade(identifier string,filename string) (bool,error) {
|
// if !bakBeforeUpgrade() {
|
// return false,errors.New("更新前备份失败")
|
// }
|
// configPatchPath := ""
|
// if config.Server.PatchPath != "" {
|
// configPatchPath = config.Server.PatchPath
|
// } else {
|
// configPatchPath = "/opt/vasystem/patch"
|
// }
|
//
|
// filenameWithSuffix := path.Base(filename)
|
// ext := path.Ext(filenameWithSuffix)
|
//
|
// zipFilePath := configPatchPath + "/"+identifier+ext
|
// if util.Exists(zipFilePath) {
|
// //校验md5
|
// strMd5, e := util.FileMd5(zipFilePath)
|
// if e !=nil || strMd5 == "" {
|
// return false,errors.New("获取升级压缩包md5失败")
|
// }
|
// if strMd5 == identifier {
|
// if !updatePatch(identifier, ext) {
|
// return false,errors.New("执行升级过程异常,请确定上传的补丁是tar.gz格式")
|
// }
|
// return true,nil
|
//
|
// } else {
|
// logger.Debug("strMd5 is", strMd5,"identifier is",identifier,"not equal")
|
// return false,errors.New("校验升级文件失败")
|
// }
|
// } else {
|
// return false,errors.New("升级文件已丢失,请重新上传")
|
// }
|
//}
|
//
|
//func bakBeforeUpgrade() bool {
|
// configBakPath := ""
|
// if config.Server.BakPath != "" {
|
// configBakPath = config.Server.BakPath
|
// } else {
|
// configBakPath = "/opt/vasystem/bak"
|
// }
|
// if util.Exists(configBakPath) {
|
// //只保留最新的版本
|
// if err := os.RemoveAll(configBakPath);err != nil {
|
// return false
|
// }
|
// }
|
// if !util.CreateDirectory(configBakPath) {
|
// return false
|
// }
|
// b, err := ExecCmd("cp -r /opt/vasystem/bin /opt/vasystem/bak")
|
// if err != nil {
|
// logger.Debug("bakBeforeUpgrade result:",string(b),"err:",err)
|
// 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.解压缩更新包
|
// unPackPath := configPatchPath+"/"+identifier+"_basic/"
|
// if util.Exists(unPackPath) {
|
// //此版本已经更新过
|
// rmErr := os.RemoveAll(unPackPath)
|
// if rmErr !=nil {
|
// return false
|
// }
|
// }
|
// if !util.CreateDirectory(unPackPath) {
|
// return false
|
// }
|
//
|
// unPackFilePath := configPatchPath+"/"+identifier+ext
|
// err := util.UnTarGz(unPackFilePath, unPackPath)
|
// if err !=nil {
|
// logger.Debug("UnPack err:",err,"unPackFile:",unPackFilePath)
|
// return false
|
// }
|
//
|
// //如果通用脚本有更新,则更新通用脚本
|
// if util.Exists(unPackPath+"updatePatch.sh") {
|
// cpStr := fmt.Sprintf("cp %s /opt/vasystem/bin",unPackPath+"updatePatch.sh")
|
// b, err := ExecCmd(cpStr)
|
// if err != nil {
|
// logger.Debug("cp updatePatch.sh to bin err:",err,"result:",string(b))
|
// return false
|
// }
|
// }
|
//
|
// //判断更新包里是否有补丁脚本,如果有则执行,否则执行updatePatch.sh
|
// updateCmd := fmt.Sprintf("./updatePatch.sh %s %s %s &",unPackPath,unPackFilePath,configPatchPath+"/"+identifier)
|
// if util.Exists(unPackPath+"upgrade.sh") {
|
// updateCmd = fmt.Sprintf("%supgrade.sh %s %s %s &",unPackPath,unPackPath,unPackFilePath,configPatchPath+"/"+identifier)
|
// }
|
// //2.更新系统
|
// b,err := ExecCmd(updateCmd)
|
// if err != nil {
|
// logger.Debug("upgrade err:",err,"result:",string(b),"cmd:",updateCmd)
|
// return false
|
// } else {
|
// logger.Debug("upgrade result:",string(b),"cmd:",updateCmd)
|
// }
|
// return true
|
//}
|
|
func MergeChunks(chunkPath string, storePath string) (bool, error) {
|
var cmd *exec.Cmd
|
cmd = exec.Command("/bin/sh", "-c", fmt.Sprintf(`
|
filepath=%s
|
filestore=%s
|
echo "filepath: " $filepath
|
echo "filestorepath: " $filestore
|
if [ ! -f $filestore ]; then
|
echo "$filestore not exist"
|
else
|
rm -f $filestore
|
fi
|
|
for item in $(ls $filepath | sort -n)
|
do
|
$(cat ${filepath}/${item} >> ${filestore})
|
echo "merge ${filepath}/${item} to $filestore ok"
|
done
|
|
echo "file store ok"`, chunkPath, storePath))
|
|
if b, err := cmd.Output(); err != nil {
|
logger.Debug("mergeChunks err:", err, "result:", string(b))
|
return false, errors.New("合成压缩包失败")
|
} else {
|
logger.Debug("mergeChunks result:", string(b))
|
return true, nil
|
}
|
|
}
|
|
func ExecCmd(cmdStr string) ([]byte, error) {
|
var cmd *exec.Cmd
|
cmd = exec.Command("/bin/sh", "-c", cmdStr)
|
return cmd.Output()
|
}
|
|
func NoticeTick(c context.Context) {
|
tick := time.Tick(1 * time.Second)
|
for {
|
select {
|
case <-c.Done():
|
logger.Info("proc close, self update exit")
|
return
|
case <-tick:
|
NoticeLock.Lock()
|
for uid, _ := range updateNoticeInfo.NoticeUser {
|
if updateNoticeInfo.NoticeUser[uid] > 0 {
|
updateNoticeInfo.NoticeUser[uid]--
|
}
|
}
|
NoticeLock.Unlock()
|
}
|
}
|
}
|
|
//更新检查
|
func SelfUpdateStart(c context.Context) {
|
tick := time.Tick(30 * time.Second)
|
for {
|
select {
|
case <-c.Done():
|
logger.Info("proc close, self update exit")
|
return
|
case <-tick:
|
NoticeLock.Lock()
|
//清理升级文件
|
appAutoClean, ok := VersionUpdateSetting["app_auto_clean"]
|
if ok && appAutoClean == "1" {
|
updateNoticeInfo.AutoClean = true
|
//configPatchPath := ""
|
//if config.Server.PatchPath != "" {
|
// configPatchPath = config.Server.PatchPath
|
//} else {
|
// configPatchPath = "/opt/vasystem/patch"
|
//}
|
//err := os.RemoveAll(configPatchPath)
|
//if nil != err {
|
// logger.Error("clean update package failed:", err.Error())
|
//}
|
//_ = os.MkdirAll(configPatchPath, 0777)
|
} else {
|
updateNoticeInfo.AutoClean = false
|
}
|
//更新升级提醒
|
appUpdateNotice, ok := VersionUpdateSetting["app_update_notice"]
|
var sdkNeedUp []SdkInsOrUpgrade
|
var appNeedUp []AppWithShop
|
if ok && appUpdateNotice == "1" {
|
sdkNeedUp, appNeedUp = CheckNeedUpgrade()
|
updateNoticeInfo.NeedUpdate = len(sdkNeedUp) > 0 || len(appNeedUp) > 0
|
updateNoticeInfo.NoticeStatus = true
|
SdkHasModify := SdkNewModify(updateNoticeInfo.SdkNeedUpgrade, sdkNeedUp)
|
AppHasModify := AppNewModify(updateNoticeInfo.AppNeedUpgrade, appNeedUp)
|
if SdkHasModify || AppHasModify {
|
for uid, _ := range updateNoticeInfo.NoticeUser {
|
if updateNoticeInfo.NoticeUser[uid] <= 0 {
|
updateNoticeInfo.NoticeUser[uid] = 0
|
}
|
}
|
}
|
updateNoticeInfo.SdkNeedUpgrade = sdkNeedUp
|
updateNoticeInfo.AppNeedUpgrade = appNeedUp
|
} else {
|
updateNoticeInfo.NeedUpdate = false
|
updateNoticeInfo.NoticeStatus = false
|
}
|
//预下载
|
appAutoUpdate, ok := VersionUpdateSetting["app_auto_update"]
|
if ok && appAutoUpdate == "1" {
|
updateNoticeInfo.PreDownUpgrade = true
|
if appUpdateNotice == "0" {
|
sdkNeedUp, appNeedUp = CheckNeedUpgrade()
|
}
|
|
if len(sdkNeedUp) > 0 {
|
for _, item := range sdkNeedUp {
|
haveDown := CheckPreDown(item.RemoteVersion, item.SdkType)
|
if !haveDown {
|
PreDownSdkAndAppUpgradeFile(item.Id)
|
}
|
}
|
}
|
if len(appNeedUp) > 0 {
|
for _, item := range appNeedUp {
|
haveDown := CheckPreDown(item.RemoteVersion, item.Package)
|
if !haveDown {
|
PreDownSdkAndAppUpgradeFile(item.Id)
|
}
|
}
|
}
|
|
} else {
|
updateNoticeInfo.PreDownUpgrade = false
|
}
|
NoticeLock.Unlock()
|
default:
|
time.Sleep(time.Second)
|
}
|
}
|
}
|
|
//是否和上次比较有新的更新
|
func SdkNewModify(SdkNeedUpgrade []SdkInsOrUpgrade, sdkNeedUp []SdkInsOrUpgrade) bool {
|
for _, item := range SdkNeedUpgrade {
|
for _, item2 := range sdkNeedUp {
|
if item.Id == item2.Id {
|
if strings.Compare(item.RemoteVersion, item2.RemoteVersion) == 1 {
|
return true
|
}
|
}
|
}
|
}
|
return false
|
}
|
|
//是否和上次比较有新的更新
|
func AppNewModify(AppNeedUpgrade []AppWithShop, appNeedUp []AppWithShop) bool {
|
for _, item := range AppNeedUpgrade {
|
for _, item2 := range appNeedUp {
|
if item.Id == item2.Id {
|
if strings.Compare(item.RemoteVersion, item2.RemoteVersion) == 1 {
|
return true
|
}
|
}
|
}
|
}
|
return false
|
}
|
|
/**
|
预下载SDK升级包
|
*/
|
func PreDownSdkAndAppUpgradeFile(id string) {
|
if IsInstalling(id) {
|
logger.Debug("系统正在升级")
|
return
|
}
|
PreDownloading = true
|
ip := &InsProgress{
|
Status: InsStatus_Downloading,
|
Progress: 0,
|
}
|
insIngMap.Store(id, ip) //放到正在安装列表中
|
defer func() {
|
insIngMap.Delete(id)
|
PreDownloading = false
|
}()
|
url := "http://" + util.GetShopUrl() + "/data/api-s/sdk/downloadOrUpgrade"
|
machineCode := licence.GetMachineCode()
|
if machineCode == "" {
|
logger.Debug("获取机器码失败")
|
return
|
}
|
paramBody := map[string]interface{}{
|
"modId": id,
|
"machineCode": machineCode,
|
"serverId": config.Server.AnalyServerId,
|
}
|
header := map[string]string{
|
"Authorization": token,
|
}
|
respBody, err := util.DoPostRequest(url, util.CONTENT_TYPE_JSON, paramBody, nil, header, time.Second*60)
|
if err != nil {
|
logger.Debug("DoPostRequest err:", err)
|
return
|
}
|
var res bhomedbapi.Result
|
if err = json.Unmarshal(respBody, &res); err != nil {
|
logger.Debug("unmarshal err:", err)
|
return
|
}
|
if !res.Success {
|
logger.Debug("res.Data:", res.Data)
|
return
|
}
|
logger.Debug("res.Data:", res.Data)
|
var resp downOrUpResp
|
if err := mapstructure.Decode(res.Data.(map[string]interface{}), &resp); err != nil {
|
logger.Debug("mapstructure.Decode err:", err)
|
return
|
}
|
logger.Debug("resp:", resp)
|
if resp.Url == "" || resp.Md5 == "" {
|
return
|
}
|
|
ip.Size = resp.Size
|
//下载安装包
|
configPatchPath := ""
|
if config.Server.PatchPath != "" {
|
configPatchPath = config.Server.PatchPath
|
} else {
|
configPatchPath = "../patch"
|
}
|
if !util.DirExists(configPatchPath) {
|
os.Mkdir(configPatchPath, 0777)
|
}
|
filenameWithSuffix := path.Base(resp.Url)
|
//ext := path.Ext(filenameWithSuffix)
|
downUrl := "http://" + util.GetShopUrl() + "/files/" + resp.Url
|
|
gzFilePath := configPatchPath + "/" + filenameWithSuffix
|
_, err = os.Stat(gzFilePath)
|
if nil != err {
|
if os.IsNotExist(err) {
|
err := DownloadFile(gzFilePath, downUrl, ip)
|
if err != nil {
|
logger.Debug("DownloadFile err:", err, " gzFilePath:", gzFilePath, " downUrl:", downUrl)
|
return
|
}
|
} else {
|
return
|
}
|
}
|
}
|
|
//检查是否需要更新
|
func CheckNeedUpgrade() ([]SdkInsOrUpgrade, []AppWithShop) {
|
sdkNeedUp := make([]SdkInsOrUpgrade, 0)
|
// userId := c.Header("Login_user_id")
|
appNeedUp := make([]AppWithShop, 0)
|
sdks := GetSdkList("", "", "")
|
for _, item := range sdks {
|
if item.Installed && item.IsUpgrade {
|
sdkNeedUp = append(sdkNeedUp, item)
|
}
|
}
|
apps := GetAppList("")
|
for _, item := range apps {
|
if item.Installed && item.IsUpgrade {
|
appNeedUp = append(appNeedUp, item)
|
}
|
}
|
|
return sdkNeedUp, appNeedUp
|
}
|
|
//同步更新设置
|
func PersistentWrapper(topic string, payloads []byte) {
|
var sysSetting versionControlM.SysSetting
|
if err := json.Unmarshal(payloads, &sysSetting); nil != err {
|
logger.Error("handleSubMsg failed to persistent:", topic, string(payloads))
|
}
|
VersionUpdateSetting[sysSetting.Name] = sysSetting.Value
|
}
|
|
//获取更新提醒
|
func GetUpdateNotice() UpdateNoticeInfo {
|
return updateNoticeInfo
|
}
|
|
//延迟提醒
|
func DelayNotice(uid string, second int) UpdateNoticeInfo {
|
updateNoticeInfo.NoticeUser[uid] = second
|
return updateNoticeInfo
|
}
|
|
//获取文件目录
|
func GetDirFiles(path string) ([]string, error) {
|
files, err := filepath.Glob(filepath.Join(path, "*"))
|
return files, err
|
}
|
|
//获取预下载路径
|
func GetPredownPath() string {
|
configPatchPath := ""
|
if config.Server.PatchPath != "" {
|
configPatchPath = config.Server.PatchPath
|
} else {
|
configPatchPath = "../patch"
|
}
|
return configPatchPath
|
}
|
|
//检查是否已经下载
|
func CheckPreDown(version string, sdkName string) bool {
|
predowns, err := GetDirFiles(GetPredownPath())
|
if nil == err {
|
for _, item := range predowns {
|
if strings.Contains(item, sdkName+"-"+version) {
|
return true
|
}
|
}
|
}
|
return false
|
}
|