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 }