| | |
| | | package service |
| | | |
| | | import ( |
| | | "basic.com/valib/bhomeclient.git" |
| | | "basic.com/valib/bhomedbapi.git" |
| | | "basic.com/valib/c_bhomebus.git/proto/source/bhome_msg" |
| | | "basic.com/valib/licence.git" |
| | | "basic.com/valib/logger.git" |
| | | "basic.com/valib/version.git" |
| | | "context" |
| | | "crypto/md5" |
| | | "encoding/base64" |
| | | "encoding/json" |
| | | "errors" |
| | | "fmt" |
| | | "github.com/skip2/go-qrcode" |
| | | "io" |
| | | "io/ioutil" |
| | | "net/http" |
| | | "net/url" |
| | | "os" |
| | | "path" |
| | | "runtime" |
| | | "strings" |
| | | "sync" |
| | | "time" |
| | | "vamicro/config" |
| | | "vamicro/extend/util" |
| | | "vamicro/version-control/models" |
| | | "vamicro/version-control/response" |
| | | "vamicro/version-control/utils" |
| | | ) |
| | | |
| | | const ( |
| | | uriVersion string = "/data/api-u/upgrade/findUpgradeVersion" |
| | | uriDownload string = "/data/api-p/download" |
| | | uriAuth string = "/data/api-s/authorization" |
| | | uriMobile string = "/data/api-s/fromQrcode" |
| | | ) |
| | | |
| | | type ( |
| | | Payload struct { |
| | | Arch string `json:"arch"` |
| | | Programs []*models.Program `json:"programs"` |
| | | Version string `json:"version"` |
| | | Intro string `json:"intro"` |
| | | PatchUrl string `json:"patchUrl"` |
| | | } |
| | | |
| | | UpdateInfo struct { |
| | | response.ResponseHead |
| | | Data []Payload `json:"data"` |
| | | } |
| | | |
| | | VersionInfo struct { |
| | | Build string `json:"build"` |
| | | Commit string `json:"commit"` |
| | | Name string `json:"name"` |
| | | Version string `json:"version"` |
| | | } |
| | | |
| | | UpdateNotice struct { |
| | | NoticeUser map[string]int |
| | | HaveNewVersion int64 //是否有新版本,1有,0没有 |
| | | PkgDownloaded int64 //是否下载完成,1有,0没有 |
| | | LastNoticeTime int64 |
| | | NewVersionProgram []*models.Program |
| | | NoticeStatus bool |
| | | } |
| | | ) |
| | | |
| | | var ( |
| | | updateNotice UpdateNotice |
| | | updateNoticeLock sync.Mutex |
| | | LastDownFile []string |
| | | LastDownLock sync.RWMutex |
| | | ) |
| | | |
| | | func Init() { |
| | | backUpPath := GetBackupPath() |
| | | if _, err := os.Stat(backUpPath); os.IsNotExist(err) { |
| | | _ = os.MkdirAll(backUpPath, 0777) |
| | | } |
| | | preDownPath := GetPreDownPath() |
| | | if _, err := os.Stat(preDownPath); os.IsNotExist(err) { |
| | | _ = os.MkdirAll(preDownPath, 0777) |
| | | } |
| | | updateNotice.NoticeUser = make(map[string]int) |
| | | LastDownFile = make([]string, 0) |
| | | } |
| | | |
| | | func OnlineUpgrade() ([]*models.Program, error) { |
| | | |
| | | programMap := make(map[string]*models.Program) |
| | | |
| | | LastDownLock.Lock() |
| | | defer LastDownLock.Unlock() |
| | | //查找预下载升级包 |
| | | preDowns := GetLastDownFile() |
| | | logger.Info("系统开始升级!") |
| | | for _, preDown := range preDowns { |
| | | logger.Info(preDowns) |
| | | ps, err := UpgradeViaZip(preDown) |
| | | if err != nil { |
| | | return []*models.Program{}, err |
| | | } |
| | | for _, p := range ps { |
| | | programMap[p.Name] = p |
| | | } |
| | | } |
| | | logger.Info("系统升级完成!!") |
| | | |
| | | if len(programMap) > 0 { //使用预下载升级完成 |
| | | programs := make([]*models.Program, 0, len(programMap)) |
| | | for _, v := range programMap { |
| | | programs = append(programs, v) |
| | | } |
| | | return programs, nil |
| | | } |
| | | |
| | | info, err := getUpdateInfo() |
| | | if nil != err { |
| | | return []*models.Program{}, errors.New("系统没有更新可用") |
| | | } |
| | | |
| | | logger.Info("系统升级开始!") |
| | | //循环下载,解压,覆盖升级包 |
| | | for _, payload := range info.Data { |
| | | ps, err := HandlerPatchPkg(payload) |
| | | if err != nil { |
| | | return []*models.Program{}, err |
| | | } |
| | | for _, p := range ps { |
| | | programMap[p.Name] = p |
| | | } |
| | | } |
| | | logger.Info("系统升级完成!!") |
| | | |
| | | if len(programMap) > 0 { // |
| | | programs := make([]*models.Program, 0, len(programMap)) |
| | | for _, v := range programMap { |
| | | programs = append(programs, v) |
| | | } |
| | | return programs, nil |
| | | } |
| | | |
| | | return []*models.Program{}, err |
| | | } |
| | | |
| | | //循环下载,解压,覆盖升级包 |
| | | func HandlerPatchPkg(payload Payload) ([]*models.Program, error) { |
| | | if payload.PatchUrl == "" { |
| | | return []*models.Program{}, errors.New("No dist file get") |
| | | } |
| | | |
| | | //u, err := url.Parse("http://" + util.GetShopUrl() + uriDownload) |
| | | u, err := url.Parse(payload.PatchUrl) |
| | | if err != nil { |
| | | logger.Error("parse url failed, url:", payload.PatchUrl, ", err:", err.Error()) |
| | | return []*models.Program{}, err |
| | | } |
| | | upgradePath := GetPreDownPath() |
| | | upfile := upgradePath + "/" + GetMd5(payload.Version) + ".tgz" |
| | | //tmpFile, err := ioutil.TempFile("", "dist-*.zip") |
| | | tmpFile, err := os.OpenFile(upfile, os.O_CREATE|os.O_WRONLY, 0666) |
| | | if nil != err { |
| | | logger.Error("OnlineUpgrade create temp file failed, err:", err.Error()) |
| | | return []*models.Program{}, err |
| | | } |
| | | fmt.Println("OnlineUpgrade tmpFile.Name: ", tmpFile.Name()) |
| | | defer func() { |
| | | tmpFile.Close() |
| | | backupPath := GetBackupPath() |
| | | //文件从upgrade目录移动到backup目录 |
| | | os.Rename(tmpFile.Name(), backupPath+"/"+tmpFile.Name()) |
| | | //os.Remove(tmpFile.Name()) |
| | | }() |
| | | |
| | | //query := u.Query() |
| | | //query.Set("filename", info.Data.Archive) |
| | | //u.RawQuery = query.Encode() |
| | | resp, err := http.Get(u.String()) |
| | | if err != nil { |
| | | logger.Error("OnlineUpgrade parse url failed, url:", u.String(), ", err:", err.Error()) |
| | | return []*models.Program{}, err |
| | | } |
| | | defer resp.Body.Close() |
| | | if resp.StatusCode != 200 { |
| | | logger.Error("OnlineUpgrade incorrect status, url:", u.String(), ", status:", resp.StatusCode) |
| | | return []*models.Program{}, errors.New("Status code not 200") |
| | | } |
| | | |
| | | _, err = io.Copy(tmpFile, resp.Body) |
| | | if err != nil { |
| | | logger.Error("OnlineUpgrade save upgrade file failed, url:", u.String(), ", err:", err.Error()) |
| | | return []*models.Program{}, err |
| | | } |
| | | |
| | | return UpgradeViaZip(tmpFile.Name()) |
| | | } |
| | | |
| | | //循环下载升级包 |
| | | func DownloadPatchPkg(payload Payload) (string, error) { |
| | | if payload.PatchUrl == "" { |
| | | return "", errors.New("no dist file get") |
| | | } |
| | | |
| | | //u, err := url.Parse("http://" + util.GetShopUrl() + uriDownload) |
| | | u, err := url.Parse(payload.PatchUrl) |
| | | if err != nil { |
| | | logger.Error("parse url failed, url:", payload.PatchUrl, ", err:", err.Error()) |
| | | return "", err |
| | | } |
| | | |
| | | upgradePath := GetPreDownPath() |
| | | upfile := upgradePath + "/" + GetMd5(payload.Version) + ".tgz" |
| | | _, err = os.Stat(upfile) |
| | | if nil == err { |
| | | //已下载过 |
| | | logger.Info("PreDownUpgrade predown upgrade file have down:", upfile) |
| | | return upfile, nil |
| | | } |
| | | |
| | | tmpFile, err := os.OpenFile(upfile, os.O_CREATE|os.O_WRONLY, 0666) |
| | | if nil != err { |
| | | logger.Error("OnlineUpgrade create temp file failed, err:", err.Error()) |
| | | return "", err |
| | | } |
| | | |
| | | fmt.Println("OnlineUpgrade tmpFile.Name: ", tmpFile.Name()) |
| | | defer func() { |
| | | tmpFile.Close() |
| | | }() |
| | | |
| | | resp, err := http.Get(u.String()) |
| | | if err != nil { |
| | | logger.Error("OnlineUpgrade parse url failed, url:", u.String(), ", err:", err.Error()) |
| | | return "", err |
| | | } |
| | | defer resp.Body.Close() |
| | | |
| | | if resp.StatusCode != 200 { |
| | | logger.Error("OnlineUpgrade incorrect status, url:", u.String(), ", status:", resp.StatusCode) |
| | | return "", errors.New("status code not 200") |
| | | } |
| | | |
| | | _, err = io.Copy(tmpFile, resp.Body) |
| | | if err != nil { |
| | | logger.Error("PreDownUpgrade parse url failed, url:", u.String(), ", err:", err.Error()) |
| | | tmpFile.Close() |
| | | |
| | | os.Remove(upfile) |
| | | return "", err |
| | | } |
| | | |
| | | return upfile, nil |
| | | } |
| | | |
| | | //检查是否需要更新版本 |
| | | func CheckVersion() (string, string, string) { |
| | | //获取服务端所有程序最新版本信息。 |
| | | info, err := getUpdateInfo() |
| | | if nil != err { |
| | | return "", "", "" |
| | | } |
| | | |
| | | if len(info.Data) <= 0 { |
| | | logger.Error("checkVersion no programs get") |
| | | return "", "", "" |
| | | } |
| | | |
| | | //获取需要更新版本的程序列表 |
| | | //programs := needUpgrade(info.Data.Programs) |
| | | last := len(info.Data) - 1 |
| | | return info.Data[last].PatchUrl, info.Data[last].Version, info.Data[last].Intro |
| | | } |
| | | |
| | | func GetCurVersion() string { |
| | | curEnv, err := GetRunVersionEnv() |
| | | if err != nil { |
| | | return "" |
| | | } |
| | | return curEnv |
| | | } |
| | | |
| | | //获取服务端最新程序及其版本 |
| | | func getUpdateInfo() (*UpdateInfo, error) { |
| | | //const PrtSize = 32 << uintptr(^uintptr(0)>>63) |
| | | //wordSize := strconv.Itoa(PrtSize) |
| | | |
| | | u, err := url.Parse("http://" + util.GetShopUrl() + uriVersion) |
| | | if err != nil { |
| | | logger.Error("parse url failed, url:", "http://"+util.GetShopUrl()+uriVersion, ", err:", err.Error()) |
| | | return nil, err |
| | | } |
| | | |
| | | query := u.Query() |
| | | //query.Set("os", runtime.GOOS) |
| | | query.Set("arch", runtime.GOARCH) |
| | | query.Set("versionNum", GetCurVersion()) |
| | | //query.Set("wordSize", wordSize) |
| | | |
| | | u.RawQuery = query.Encode() |
| | | resp, err := http.Get(u.String()) |
| | | if err != nil { |
| | | logger.Error("checkVersion parse url failed, url:", u.String(), ", err:", err.Error()) |
| | | return nil, err |
| | | } |
| | | |
| | | if resp.StatusCode != 200 { |
| | | logger.Error("checkVersion incorrect status, url:", u.String(), ", status:", resp.StatusCode) |
| | | return nil, errors.New("Status code not 200") |
| | | } |
| | | |
| | | defer resp.Body.Close() |
| | | body, err := ioutil.ReadAll(resp.Body) |
| | | if err != nil { |
| | | logger.Error("checkVersion read body failed, url:", u.String(), ", err:", err.Error()) |
| | | return nil, err |
| | | } |
| | | |
| | | logger.Info("checkVersion dump body:", string(body)) |
| | | var info UpdateInfo |
| | | err = json.Unmarshal(body, &info) |
| | | if err != nil { |
| | | logger.Error("checkVersion json.Unmarshal failed, url:", u.String(), ", err:", err.Error()) |
| | | return nil, err |
| | | } |
| | | |
| | | //b, err := json.Marshal(&info) |
| | | //if err != nil { |
| | | // logger.Error("checkVersion json.Marshal failed, url:", u.String(), ", err:", err.Error()) |
| | | //} else { |
| | | // logger.Info("checkVersion get response, url:", u.String(), ", response:", string(b)) |
| | | //} |
| | | |
| | | return &info, nil |
| | | } |
| | | |
| | | //需要升级的程序 |
| | | func needUpgrade(programs []*models.Program) []*models.Program { |
| | | //获取当前运行版本目录 |
| | | //dir := utils.GetExePath() |
| | | dir := util.GetVamicroPath() |
| | | |
| | | newPrograms := []*models.Program{} |
| | | for _, p := range programs { |
| | | exe := path.Join(dir, p.Name) |
| | | if !utils.PathExists(exe) { |
| | | newPrograms = append(newPrograms, p) |
| | | continue |
| | | } |
| | | |
| | | lVersion, err := dynamicGetVersion(dir, exe) |
| | | if err != nil { |
| | | logger.Error("exe:", exe, "get local version failed, err:", err.Error()) |
| | | continue |
| | | } |
| | | |
| | | //版本名称转换为版本号 |
| | | rVersion, err := vaversion.VersionName2VaVersion(p.Version) |
| | | if err != nil { |
| | | logger.Error("exe:", exe, "get remote version failed:", p.Version, ", err:", err.Error()) |
| | | continue |
| | | } |
| | | |
| | | c, err := lVersion.Compare(rVersion) |
| | | if err != nil { |
| | | logger.Error("exe:", exe, "version compare failed, remote:", p.Version, ", err:", err.Error()) |
| | | continue |
| | | } |
| | | |
| | | if c > 0 { |
| | | logger.Error("exe:", exe, "local version greater, local:", lVersion, "remote:", p.Version) |
| | | continue |
| | | } |
| | | |
| | | if c < 0 { |
| | | newPrograms = append(newPrograms, p) |
| | | } |
| | | } |
| | | |
| | | return newPrograms |
| | | } |
| | | |
| | | func NoticeTick(c context.Context) { |
| | | // tick := time.Tick(1 * time.Second) |
| | | tick := time.Tick(24 * time.Hour) |
| | | for { |
| | | select { |
| | | case <-c.Done(): |
| | | logger.Info("proc close, self update exit") |
| | | return |
| | | case <-tick: |
| | | //logger.Info("NoticeTick !!!") |
| | | updateNoticeLock.Lock() |
| | | for key, _ := range updateNotice.NoticeUser { |
| | | if 0 < updateNotice.NoticeUser[key] { |
| | | updateNotice.NoticeUser[key]-- |
| | | } |
| | | } |
| | | updateNoticeLock.Unlock() |
| | | } |
| | | } |
| | | } |
| | | |
| | | //升级配置处理 |
| | | func SelfUpdateStart(c context.Context, ms *bhomeclient.MicroNode) { |
| | | //三十秒检查一次配置并更新 |
| | | // tick := time.Tick(30 * time.Second) |
| | | tick := time.Tick(24 * time.Hour) |
| | | |
| | | var setting models.SysSetting |
| | | for { |
| | | select { |
| | | case <-c.Done(): |
| | | logger.Info("proc close, self update exit") |
| | | return |
| | | case <-tick: |
| | | settings, err := setting.GetAllSetting() |
| | | if nil != err { |
| | | logger.Error("fetch setting failed") |
| | | continue |
| | | } |
| | | |
| | | for _, set := range settings { |
| | | data, _ := json.Marshal(set) |
| | | var nodes []bhome_msg.BHAddress |
| | | nodes = append(nodes, bhome_msg.BHAddress{}) |
| | | |
| | | go ms.PublishNetTimeout(nodes, SysUpdateConfigTopic, data, 10) |
| | | |
| | | switch set.Name { |
| | | case "sys_auto_clean": |
| | | if "1" == set.Value { |
| | | err := os.RemoveAll(GetBackupPath()) |
| | | if nil != err { |
| | | logger.Error("clean update package failed:", err.Error()) |
| | | } |
| | | _ = os.MkdirAll(GetBackupPath(), 0777) |
| | | } |
| | | case "sys_update_notice": |
| | | { |
| | | // 判断有新版本, 不再重复检测 |
| | | if updateNotice.HaveNewVersion == 1 { |
| | | continue |
| | | } |
| | | |
| | | // 设备更新提醒 |
| | | updateNoticeLock.Lock() |
| | | |
| | | //获取需要更新版本的程序列表 |
| | | _, version, _ := CheckVersion() |
| | | curVersion := GetCurVersion() |
| | | IsLastUpdate := true |
| | | if version != curVersion { |
| | | // 设置更新提醒, 重新下载升级包 |
| | | updateNotice.HaveNewVersion = 1 |
| | | updateNotice.PkgDownloaded = 0 |
| | | IsLastUpdate = true |
| | | } else { |
| | | IsLastUpdate = false |
| | | } |
| | | |
| | | if !IsLastUpdate { //如果没有更新,设置用户延迟时间为0 |
| | | for key, _ := range updateNotice.NoticeUser { |
| | | if 0 > updateNotice.NoticeUser[key] { |
| | | updateNotice.NoticeUser[key] = 0 |
| | | } |
| | | } |
| | | } |
| | | |
| | | if "1" == set.Value { |
| | | updateNotice.NoticeStatus = true |
| | | } else { |
| | | updateNotice.NoticeStatus = false |
| | | } |
| | | |
| | | updateNoticeLock.Unlock() |
| | | } |
| | | case "sys_auto_update": |
| | | { |
| | | if "1" == set.Value && updateNotice.HaveNewVersion > 0 && updateNotice.PkgDownloaded == 0 { |
| | | err := PreDownUpdateFile() |
| | | if nil != err { |
| | | logger.Error("pre download update file failed:", err.Error()) |
| | | } else { |
| | | updateNotice.PkgDownloaded = 1 |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | //检查是否距上次比较有更新 |
| | | func IsLastUpdate(programs []*models.Program, NewVersionProgram []*models.Program) bool { |
| | | for _, program := range programs { |
| | | for _, program2 := range NewVersionProgram { |
| | | if program.Name == program2.Name { |
| | | if program.Version != program2.Version { |
| | | return true |
| | | } |
| | | } |
| | | } |
| | | } |
| | | return true |
| | | } |
| | | |
| | | //获取更新提醒 |
| | | func GetUpdateNotice() UpdateNotice { |
| | | return updateNotice |
| | | } |
| | | |
| | | //延迟提醒 |
| | | func DelayNotice(uid string, second int) UpdateNotice { |
| | | updateNoticeLock.Lock() |
| | | updateNotice.NoticeUser[uid] = second |
| | | updateNoticeLock.Unlock() |
| | | return updateNotice |
| | | } |
| | | |
| | | //获取代码备份位置 |
| | | func GetBackupPath() string { |
| | | dir := util.GetVamicroPath() + "/backup" |
| | | _, err := os.Stat(dir) |
| | | if nil != err { |
| | | if os.IsNotExist(err) { |
| | | os.Mkdir(dir, 0744) |
| | | } else { |
| | | return "./backup" |
| | | } |
| | | } |
| | | |
| | | return dir |
| | | } |
| | | |
| | | //获取解压后的补丁包位置 |
| | | func GetPatchPath() string { |
| | | dir := util.GetVamicroPath() + "/patch" |
| | | _, err := os.Stat(dir) |
| | | if nil != err { |
| | | if os.IsNotExist(err) { |
| | | os.Mkdir(dir, 0744) |
| | | } else { |
| | | return "./patch" |
| | | } |
| | | } |
| | | |
| | | return dir |
| | | } |
| | | |
| | | //获取预下载升级包目录 |
| | | func GetPreDownPath() string { |
| | | dir := util.GetVamicroPath() + "/upgrade" |
| | | _, err := os.Stat(dir) |
| | | if nil != err { |
| | | if os.IsNotExist(err) { |
| | | os.Mkdir(dir, 0744) |
| | | } else { |
| | | return "./upgrade" |
| | | } |
| | | } |
| | | |
| | | return dir |
| | | } |
| | | |
| | | //获取上传升级包目录 |
| | | func GetPreUploadPath() string { |
| | | dir := util.GetVamicroPath() + "/upgrade_manual" |
| | | _, err := os.Stat(dir) |
| | | if nil != err { |
| | | if os.IsNotExist(err) { |
| | | os.Mkdir(dir, 0744) |
| | | } else { |
| | | return "./upgrade_manual" |
| | | } |
| | | } |
| | | |
| | | return dir |
| | | } |
| | | |
| | | func GetMd5(in string) string { |
| | | data := md5.Sum([]byte(in)) |
| | | return fmt.Sprintf("%x", data) |
| | | } |
| | | |
| | | //预下载升级文件 |
| | | func PreDownUpdateFile() error { |
| | | info, err := getUpdateInfo() |
| | | if nil != err { |
| | | return err |
| | | } |
| | | |
| | | LastDownLock.Lock() |
| | | defer LastDownLock.Unlock() |
| | | |
| | | LastDownFile = make([]string, 0) |
| | | |
| | | //循环下载升级包 |
| | | for _, payload := range info.Data { |
| | | logger.Info("正在下载版本补丁【" + payload.Version + "】") |
| | | upfile, err := DownloadPatchPkg(payload) |
| | | logger.Info("下载版本补丁完成【" + payload.Version + "】") |
| | | if err != nil { |
| | | return err |
| | | } |
| | | |
| | | LastDownFile = append(LastDownFile, upfile) |
| | | } |
| | | |
| | | return nil |
| | | } |
| | | |
| | | //获取预下载升级包 |
| | | func GetLastDownFile() []string { |
| | | var res = make([]string, 0) |
| | | if len(LastDownFile) > 0 { |
| | | for _, file := range LastDownFile { |
| | | _, err := os.Stat(file) |
| | | if nil == err { |
| | | res = append(res, file) |
| | | } |
| | | } |
| | | } |
| | | return res |
| | | } |
| | | |
| | | //回滚版本 |
| | | func Rollback(version string) error { |
| | | dir := util.GetVamicroPath() + "/" + version |
| | | _, err := os.Stat(dir) |
| | | if nil != err { |
| | | return err |
| | | } |
| | | SetRunVersionEnv(version) |
| | | versionEnv = version |
| | | return nil |
| | | } |
| | | |
| | | type RegUserInfo struct { |
| | | UserType string `json:"userType"` //个人:personal 公司: company |
| | | PhoneNum string `json:"phoneNum"` //手机号码 |
| | | Name string `json:"name"` //姓名或公司名称 |
| | | ProvinceId string `json:"provinceId"` //省 |
| | | CityId string `json:"cityId"` //市 |
| | | CountyId string `json:"countyId"` //县 |
| | | Email string `json:"email"` //邮箱 |
| | | } |
| | | |
| | | //获取授权 |
| | | func Authorization(code string, isManual bool) (authinfo util.AuthorizationInfo, err error) { |
| | | sn := util.GetVamicroPath() + "/sn.txt" |
| | | authorization := util.GetVamicroPath() + "/auth.txt" |
| | | |
| | | activateCode := "" |
| | | if len(code) == 29 && code[5:6] == "-" { //25位激活码激活 |
| | | activateCode = code |
| | | } |
| | | |
| | | if "" != code && len(code) != 29 && code[5:6] != "-" { |
| | | authinfo, err := util.GetAuthorizationInfo(code) |
| | | if nil == err { |
| | | activateCode = authinfo.ActivateCode |
| | | logger.Debug("code found:" + code) |
| | | if isManual { //手动操作更新授权文件,则立即返回,不用再次访问商城 |
| | | ioutil.WriteFile(sn, []byte(authinfo.ActivateCode), os.ModePerm) |
| | | ioutil.WriteFile(authorization, []byte(code), os.ModePerm) |
| | | return authinfo, nil |
| | | } else { |
| | | //否则以商城为准 |
| | | defer func() { |
| | | if nil != err { |
| | | //ioutil.WriteFile(sn, []byte(authinfo.ActivateCode), os.ModePerm) |
| | | //ioutil.WriteFile(authorization, []byte(code), os.ModePerm) |
| | | } |
| | | }() |
| | | } |
| | | } else { |
| | | return authinfo, errors.New("非法的授权!!") |
| | | } |
| | | } |
| | | devId := config.Server.AnalyServerId |
| | | machineCode := licence.GetMachineCode() |
| | | |
| | | authinfo, authcode, err := postAuthReq(util.GetSn(), activateCode, devId, machineCode, "") |
| | | if nil != err { |
| | | logger.Error("Authorization err:", err.Error(), authcode) |
| | | //ioutil.WriteFile(authorization, []byte(authcode), os.ModePerm) //bug:局域网会把授权清除 |
| | | return authinfo, err |
| | | } |
| | | |
| | | if authinfo.MachineCode != machineCode { |
| | | logger.Error("GetAuthorization machineCode not match, local:", machineCode, " remote:", authinfo.MachineCode) |
| | | return authinfo, errors.New("授权不匹配") |
| | | } |
| | | |
| | | ioutil.WriteFile(sn, []byte(authinfo.ActivateCode), os.ModePerm) |
| | | ioutil.WriteFile(authorization, []byte(authcode), os.ModePerm) |
| | | |
| | | return authinfo, nil |
| | | } |
| | | |
| | | func postAuthReq(sn string, activateCode string, deviceId string, machineCode string, oldDeviceId string) (authinfo util.AuthorizationInfo, authcode string, err error) { |
| | | u, err := url.Parse("http://" + util.GetShopUrl() + uriAuth) |
| | | if err != nil { |
| | | logger.Error("parse url failed, url:", "http://"+util.GetShopUrl()+uriAuth, ", err:", err.Error()) |
| | | return authinfo, "", err |
| | | } |
| | | query := u.Query() |
| | | query.Set("sn", sn) |
| | | query.Set("activateCode", activateCode) |
| | | query.Set("deviceId", deviceId) |
| | | query.Set("machineCode", machineCode) |
| | | query.Set("oldDeviceId", oldDeviceId) |
| | | query.Set("deviceType", config.Server.DeviceType) |
| | | query.Set("deviceMode", config.Server.DeviceModel) |
| | | query.Set("vGpu", util.GetVGpu()) |
| | | |
| | | var sysInitApi bhomedbapi.SysInitApi |
| | | b, rInfo := sysInitApi.GetRegInfo() |
| | | if b { |
| | | rbd, e := json.Marshal(rInfo) |
| | | if e == nil { |
| | | var sysRI RegUserInfo |
| | | if e = json.Unmarshal(rbd, &sysRI); e == nil { |
| | | query.Set("userType", sysRI.UserType) |
| | | query.Set("name", sysRI.Name) |
| | | query.Set("phoneNum", sysRI.PhoneNum) |
| | | query.Set("provinceId", sysRI.ProvinceId) |
| | | query.Set("cityId", sysRI.CityId) |
| | | query.Set("countyId", sysRI.CountyId) |
| | | query.Set("email", sysRI.Email) |
| | | } else { |
| | | logger.Error("json.Unmarshal sysRI e:", e) |
| | | } |
| | | } else { |
| | | logger.Error("json.Marshal rInfo e:", e) |
| | | } |
| | | } |
| | | |
| | | u.RawQuery = query.Encode() |
| | | resp, err := http.Get(u.String()) |
| | | if err != nil { |
| | | logger.Error("GetAuthorization parse url failed, url:", u.String(), ", err:", err.Error()) |
| | | return authinfo, "", err |
| | | } |
| | | |
| | | if resp.StatusCode != 200 { |
| | | logger.Error("GetAuthorization incorrect status, url:", u.String(), ", status:", resp.StatusCode) |
| | | return authinfo, "", errors.New("Status code not 200") |
| | | } |
| | | |
| | | defer resp.Body.Close() |
| | | body, err := ioutil.ReadAll(resp.Body) |
| | | if err != nil { |
| | | logger.Error("GetAuthorization read body failed, url:", u.String(), ", err:", err.Error()) |
| | | return authinfo, "", err |
| | | } |
| | | |
| | | logger.Info("postAuthReq body:", string(body)) |
| | | var info map[string]interface{} |
| | | err = json.Unmarshal(body, &info) |
| | | if err != nil { |
| | | logger.Error("GetAuthorization json.Unmarshal failed, url:", u.String(), ", err:", err.Error()) |
| | | return authinfo, "", err |
| | | } |
| | | |
| | | authinfo, err = util.GetAuthorizationInfo(info["data"].(string)) |
| | | if nil == err { |
| | | logger.Error("err:", err) |
| | | return authinfo, info["data"].(string), nil |
| | | } |
| | | |
| | | if 500 == int(info["code"].(float64)) { |
| | | logger.Error("ret 500") |
| | | return authinfo, "", errors.New(info["msg"].(string)) |
| | | } |
| | | |
| | | if true != info["success"].(bool) { |
| | | logger.Error("not success") |
| | | return authinfo, "", errors.New(info["msg"].(string)) |
| | | } |
| | | |
| | | return authinfo, info["data"].(string), nil |
| | | } |
| | | |
| | | func GenQRCode() ([]byte, string, error) { |
| | | sn := util.GetSn() |
| | | var authInfo util.AuthorizationInfo |
| | | authInfo.Sn = sn |
| | | authInfo.DevId = config.Server.AnalyServerId |
| | | authInfo.MachineCode = licence.GetMachineCode() |
| | | authInfo.DeviceType = config.Server.DeviceType |
| | | authInfo.DeviceMode = config.Server.DeviceModel |
| | | authInfo.VGpu = util.GetVGpu() |
| | | |
| | | var sysInitApi bhomedbapi.SysInitApi |
| | | if bsi, rInfo := sysInitApi.GetRegInfo(); bsi { |
| | | rbd, e := json.Marshal(rInfo) |
| | | if e == nil { |
| | | var sysRI RegUserInfo |
| | | if e = json.Unmarshal(rbd, &sysRI); e == nil { |
| | | authInfo.UserType = sysRI.UserType |
| | | authInfo.Name = sysRI.Name |
| | | authInfo.PhoneNum = sysRI.PhoneNum |
| | | authInfo.ProvinceId = sysRI.ProvinceId |
| | | authInfo.CityId = sysRI.CityId |
| | | authInfo.CountyId = sysRI.CountyId |
| | | authInfo.Email = sysRI.Email |
| | | } else { |
| | | logger.Error("json.Unmarshal sysRI e:", e) |
| | | } |
| | | } else { |
| | | logger.Error("json.Marshal rInfo e:", e) |
| | | } |
| | | } |
| | | |
| | | hackQ, _ := HackAuthorizationInfo(authInfo) |
| | | |
| | | url := "http://" + util.GetShopUrl() + uriMobile + "?q=" + hackQ |
| | | |
| | | logger.Info("qrcode len:", len(url), " content:", url) |
| | | |
| | | // 生成二维码 |
| | | q, err := qrcode.New(url, qrcode.Highest) |
| | | if err != nil { |
| | | return nil, url, err |
| | | } |
| | | |
| | | png, err1 := q.PNG(350) |
| | | |
| | | return png, url, err1 |
| | | } |
| | | |
| | | //刷新授权到其他进程 |
| | | func AuthorizationUpdate(c context.Context, ms *bhomeclient.MicroNode) { |
| | | //三十秒检查一次配置并更新 |
| | | // tick := time.Tick(30 * time.Second) |
| | | tick := time.Tick(24 * time.Hour) |
| | | |
| | | var authInfo util.AuthorizationInfo |
| | | var err error |
| | | for { |
| | | select { |
| | | case <-c.Done(): |
| | | logger.Info("proc close, self update exit") |
| | | return |
| | | case <-tick: |
| | | author := util.GetAuthorization() |
| | | sn := util.GetSn() |
| | | |
| | | authInfo, err = Authorization(sn, false) |
| | | if nil != err && !strings.Contains(err.Error(), "成功") { |
| | | logger.Error("GetAuthorization error:", err.Error()) |
| | | if "" != author { |
| | | authInfo, err = util.GetAuthorizationInfo(author) |
| | | } |
| | | } |
| | | |
| | | logger.Debug("authInfo:", authInfo) |
| | | data, _ := json.Marshal(authInfo) |
| | | var nodes []bhome_msg.BHAddress |
| | | nodes = append(nodes, bhome_msg.BHAddress{}) |
| | | go ms.PublishNetTimeout(nodes, AuthorizationUpdateTopic, data, 10) |
| | | } |
| | | } |
| | | } |
| | | |
| | | func HackAuthorizationInfo(authorizationInfo util.AuthorizationInfo) (string, error) { |
| | | b, err := json.Marshal(authorizationInfo) |
| | | if nil != err { |
| | | return "", err |
| | | } |
| | | logger.Debug("authorInfo", authorizationInfo) |
| | | |
| | | info, err := util.RsaEncrypt(b) |
| | | if nil != err { |
| | | logger.Error("HackAuthorizationInfo utils.RsaEncrypt failed, err:", err.Error()) |
| | | return "", err |
| | | } |
| | | |
| | | return base64.StdEncoding.EncodeToString(info), nil |
| | | } |
| | | |
| | | func GetQ() string { |
| | | sn := util.GetSn() |
| | | var authinfo util.AuthorizationInfo |
| | | authinfo.Sn = sn |
| | | authinfo.DevId = config.Server.AnalyServerId |
| | | authinfo.MachineCode = licence.GetMachineCode() |
| | | authinfo.VGpu = util.GetVGpu() |
| | | q, err := HackAuthorizationInfo(authinfo) |
| | | if nil == err { |
| | | return q |
| | | } |
| | | return err.Error() |
| | | } |
| | | |
| | | func CancelAuthorization(passwd string, q string) (error, string) { |
| | | uApi := bhomedbapi.UserApi{} |
| | | ok, _ := uApi.Login("basic", passwd) |
| | | if !ok { |
| | | return errors.New("密码不正确!"), "" |
| | | } |
| | | //获取请求码里的devId |
| | | qInfo, err := util.GetAuthorizationInfo(q) |
| | | if nil != err { |
| | | return errors.New("请求码格式不正确"), "" |
| | | } |
| | | |
| | | curAuthInfo := util.GetAuthorization() |
| | | |
| | | //获取当前授权码 |
| | | authInfo, err := util.GetAuthorizationInfo(curAuthInfo) |
| | | if nil != err { |
| | | return errors.New("当前服务器授权信息不完整"), "" |
| | | } |
| | | authInfo.OldDeviceId = authInfo.DevId |
| | | authInfo.DevId = qInfo.DevId |
| | | authInfo.VGpu = util.GetVGpu() |
| | | sn := util.GetVamicroPath() + "/sn.txt" |
| | | authorization := util.GetVamicroPath() + "/auth.txt" |
| | | authCode, err := HackAuthorizationInfo(authInfo) |
| | | if nil != err { |
| | | return errors.New("产品密钥导出失败"), "" |
| | | } |
| | | ioutil.WriteFile(sn, []byte(""), os.ModePerm) |
| | | time.Sleep(100 * time.Microsecond) |
| | | ioutil.WriteFile(authorization, []byte(""), os.ModePerm) |
| | | |
| | | _, _, err = postAuthReq(authInfo.Sn, authInfo.ActivateCode, qInfo.DevId, licence.GetMachineCode(), authInfo.OldDeviceId) |
| | | |
| | | if nil != err { |
| | | return err, "" |
| | | } |
| | | |
| | | return nil, authCode |
| | | } |
| | | package service
|
| | |
|
| | | import (
|
| | | "basic.com/valib/bhomeclient.git"
|
| | | "basic.com/valib/bhomedbapi.git"
|
| | | "basic.com/valib/c_bhomebus.git/proto/source/bhome_msg"
|
| | | "basic.com/valib/licence.git"
|
| | | "basic.com/valib/logger.git"
|
| | | "basic.com/valib/version.git"
|
| | | "context"
|
| | | "crypto/md5"
|
| | | "encoding/base64"
|
| | | "encoding/json"
|
| | | "errors"
|
| | | "fmt"
|
| | | "github.com/skip2/go-qrcode"
|
| | | "io"
|
| | | "io/ioutil"
|
| | | "net/http"
|
| | | "net/url"
|
| | | "os"
|
| | | "path"
|
| | | "runtime"
|
| | | "strings"
|
| | | "sync"
|
| | | "time"
|
| | | "vamicro/config"
|
| | | "vamicro/extend/util"
|
| | | "vamicro/version-control/models"
|
| | | "vamicro/version-control/response"
|
| | | "vamicro/version-control/utils"
|
| | | )
|
| | |
|
| | | const (
|
| | | uriVersion string = "/data/api-u/upgrade/findUpgradeVersion"
|
| | | uriDownload string = "/data/api-p/download"
|
| | | uriAuth string = "/data/api-s/authorization"
|
| | | uriMobile string = "/data/api-s/fromQrcode"
|
| | | )
|
| | |
|
| | | type (
|
| | | Payload struct {
|
| | | Arch string `json:"arch"`
|
| | | Programs []*models.Program `json:"programs"`
|
| | | Version string `json:"version"`
|
| | | Intro string `json:"intro"`
|
| | | PatchUrl string `json:"patchUrl"`
|
| | | }
|
| | |
|
| | | UpdateInfo struct {
|
| | | response.ResponseHead
|
| | | Data []Payload `json:"data"`
|
| | | }
|
| | |
|
| | | VersionInfo struct {
|
| | | Build string `json:"build"`
|
| | | Commit string `json:"commit"`
|
| | | Name string `json:"name"`
|
| | | Version string `json:"version"`
|
| | | }
|
| | |
|
| | | UpdateNotice struct {
|
| | | NoticeUser map[string]int
|
| | | HaveNewVersion int64 //是否有新版本,1有,0没有
|
| | | PkgDownloaded int64 //是否下载完成,1有,0没有
|
| | | LastNoticeTime int64
|
| | | NewVersionProgram []*models.Program
|
| | | NoticeStatus bool
|
| | | }
|
| | | )
|
| | |
|
| | | var (
|
| | | updateNotice UpdateNotice
|
| | | updateNoticeLock sync.Mutex
|
| | | LastDownFile []string
|
| | | LastDownLock sync.RWMutex
|
| | | )
|
| | |
|
| | | func Init() {
|
| | | backUpPath := GetBackupPath()
|
| | | if _, err := os.Stat(backUpPath); os.IsNotExist(err) {
|
| | | _ = os.MkdirAll(backUpPath, 0777)
|
| | | }
|
| | | preDownPath := GetPreDownPath()
|
| | | if _, err := os.Stat(preDownPath); os.IsNotExist(err) {
|
| | | _ = os.MkdirAll(preDownPath, 0777)
|
| | | }
|
| | | updateNotice.NoticeUser = make(map[string]int)
|
| | | LastDownFile = make([]string, 0)
|
| | | }
|
| | |
|
| | | func OnlineUpgrade() ([]*models.Program, error) {
|
| | |
|
| | | programMap := make(map[string]*models.Program)
|
| | |
|
| | | LastDownLock.Lock()
|
| | | defer LastDownLock.Unlock()
|
| | | //查找预下载升级包
|
| | | preDowns := GetLastDownFile()
|
| | | logger.Info("系统开始升级!")
|
| | | for _, preDown := range preDowns {
|
| | | logger.Info(preDowns)
|
| | | ps, err := UpgradeViaZip(preDown)
|
| | | if err != nil {
|
| | | return []*models.Program{}, err
|
| | | }
|
| | | for _, p := range ps {
|
| | | programMap[p.Name] = p
|
| | | }
|
| | | }
|
| | | logger.Info("系统升级完成!!")
|
| | |
|
| | | if len(programMap) > 0 { //使用预下载升级完成
|
| | | programs := make([]*models.Program, 0, len(programMap))
|
| | | for _, v := range programMap {
|
| | | programs = append(programs, v)
|
| | | }
|
| | | return programs, nil
|
| | | }
|
| | |
|
| | | info, err := getUpdateInfo()
|
| | | if nil != err {
|
| | | return []*models.Program{}, errors.New("系统没有更新可用")
|
| | | }
|
| | |
|
| | | logger.Info("系统升级开始!")
|
| | | //循环下载,解压,覆盖升级包
|
| | | for _, payload := range info.Data {
|
| | | ps, err := HandlerPatchPkg(payload)
|
| | | if err != nil {
|
| | | return []*models.Program{}, err
|
| | | }
|
| | | for _, p := range ps {
|
| | | programMap[p.Name] = p
|
| | | }
|
| | | }
|
| | | logger.Info("系统升级完成!!")
|
| | |
|
| | | if len(programMap) > 0 { //
|
| | | programs := make([]*models.Program, 0, len(programMap))
|
| | | for _, v := range programMap {
|
| | | programs = append(programs, v)
|
| | | }
|
| | | return programs, nil
|
| | | }
|
| | |
|
| | | return []*models.Program{}, err
|
| | | }
|
| | |
|
| | | //循环下载,解压,覆盖升级包
|
| | | func HandlerPatchPkg(payload Payload) ([]*models.Program, error) {
|
| | | if payload.PatchUrl == "" {
|
| | | return []*models.Program{}, errors.New("No dist file get")
|
| | | }
|
| | |
|
| | | //u, err := url.Parse("http://" + util.GetShopUrl() + uriDownload)
|
| | | u, err := url.Parse(payload.PatchUrl)
|
| | | if err != nil {
|
| | | logger.Error("parse url failed, url:", payload.PatchUrl, ", err:", err.Error())
|
| | | return []*models.Program{}, err
|
| | | }
|
| | | upgradePath := GetPreDownPath()
|
| | | upfile := upgradePath + "/" + GetMd5(payload.Version) + ".tgz"
|
| | | //tmpFile, err := ioutil.TempFile("", "dist-*.zip")
|
| | | tmpFile, err := os.OpenFile(upfile, os.O_CREATE|os.O_WRONLY, 0666)
|
| | | if nil != err {
|
| | | logger.Error("OnlineUpgrade create temp file failed, err:", err.Error())
|
| | | return []*models.Program{}, err
|
| | | }
|
| | | fmt.Println("OnlineUpgrade tmpFile.Name: ", tmpFile.Name())
|
| | | defer func() {
|
| | | tmpFile.Close()
|
| | | backupPath := GetBackupPath()
|
| | | //文件从upgrade目录移动到backup目录
|
| | | os.Rename(tmpFile.Name(), backupPath+"/"+tmpFile.Name())
|
| | | //os.Remove(tmpFile.Name())
|
| | | }()
|
| | |
|
| | | //query := u.Query()
|
| | | //query.Set("filename", info.Data.Archive)
|
| | | //u.RawQuery = query.Encode()
|
| | | resp, err := http.Get(u.String())
|
| | | if err != nil {
|
| | | logger.Error("OnlineUpgrade parse url failed, url:", u.String(), ", err:", err.Error())
|
| | | return []*models.Program{}, err
|
| | | }
|
| | | defer resp.Body.Close()
|
| | | if resp.StatusCode != 200 {
|
| | | logger.Error("OnlineUpgrade incorrect status, url:", u.String(), ", status:", resp.StatusCode)
|
| | | return []*models.Program{}, errors.New("Status code not 200")
|
| | | }
|
| | |
|
| | | _, err = io.Copy(tmpFile, resp.Body)
|
| | | if err != nil {
|
| | | logger.Error("OnlineUpgrade save upgrade file failed, url:", u.String(), ", err:", err.Error())
|
| | | return []*models.Program{}, err
|
| | | }
|
| | |
|
| | | return UpgradeViaZip(tmpFile.Name())
|
| | | }
|
| | |
|
| | | //循环下载升级包
|
| | | func DownloadPatchPkg(payload Payload) (string, error) {
|
| | | if payload.PatchUrl == "" {
|
| | | return "", errors.New("no dist file get")
|
| | | }
|
| | |
|
| | | //u, err := url.Parse("http://" + util.GetShopUrl() + uriDownload)
|
| | | u, err := url.Parse(payload.PatchUrl)
|
| | | if err != nil {
|
| | | logger.Error("parse url failed, url:", payload.PatchUrl, ", err:", err.Error())
|
| | | return "", err
|
| | | }
|
| | |
|
| | | upgradePath := GetPreDownPath()
|
| | | upfile := upgradePath + "/" + GetMd5(payload.Version) + ".tgz"
|
| | | _, err = os.Stat(upfile)
|
| | | if nil == err {
|
| | | //已下载过
|
| | | logger.Info("PreDownUpgrade predown upgrade file have down:", upfile)
|
| | | return upfile, nil
|
| | | }
|
| | |
|
| | | tmpFile, err := os.OpenFile(upfile, os.O_CREATE|os.O_WRONLY, 0666)
|
| | | if nil != err {
|
| | | logger.Error("OnlineUpgrade create temp file failed, err:", err.Error())
|
| | | return "", err
|
| | | }
|
| | |
|
| | | fmt.Println("OnlineUpgrade tmpFile.Name: ", tmpFile.Name())
|
| | | defer func() {
|
| | | tmpFile.Close()
|
| | | }()
|
| | |
|
| | | resp, err := http.Get(u.String())
|
| | | if err != nil {
|
| | | logger.Error("OnlineUpgrade parse url failed, url:", u.String(), ", err:", err.Error())
|
| | | return "", err
|
| | | }
|
| | | defer resp.Body.Close()
|
| | |
|
| | | if resp.StatusCode != 200 {
|
| | | logger.Error("OnlineUpgrade incorrect status, url:", u.String(), ", status:", resp.StatusCode)
|
| | | return "", errors.New("status code not 200")
|
| | | }
|
| | |
|
| | | _, err = io.Copy(tmpFile, resp.Body)
|
| | | if err != nil {
|
| | | logger.Error("PreDownUpgrade parse url failed, url:", u.String(), ", err:", err.Error())
|
| | | tmpFile.Close()
|
| | |
|
| | | os.Remove(upfile)
|
| | | return "", err
|
| | | }
|
| | |
|
| | | return upfile, nil
|
| | | }
|
| | |
|
| | | //检查是否需要更新版本
|
| | | func CheckVersion() (string, string, string) {
|
| | | //获取服务端所有程序最新版本信息。
|
| | | info, err := getUpdateInfo()
|
| | | if nil != err {
|
| | | return "", "", ""
|
| | | }
|
| | |
|
| | | if len(info.Data) <= 0 {
|
| | | logger.Error("checkVersion no programs get")
|
| | | return "", "", ""
|
| | | }
|
| | |
|
| | | //获取需要更新版本的程序列表
|
| | | //programs := needUpgrade(info.Data.Programs)
|
| | | last := len(info.Data) - 1
|
| | | return info.Data[last].PatchUrl, info.Data[last].Version, info.Data[last].Intro
|
| | | }
|
| | |
|
| | | func GetCurVersion() string {
|
| | | curEnv, err := GetRunVersionEnv()
|
| | | if err != nil {
|
| | | return ""
|
| | | }
|
| | | return curEnv
|
| | | }
|
| | |
|
| | | //获取服务端最新程序及其版本
|
| | | func getUpdateInfo() (*UpdateInfo, error) {
|
| | | //const PrtSize = 32 << uintptr(^uintptr(0)>>63)
|
| | | //wordSize := strconv.Itoa(PrtSize)
|
| | |
|
| | | u, err := url.Parse("http://" + util.GetShopUrl() + uriVersion)
|
| | | if err != nil {
|
| | | logger.Error("parse url failed, url:", "http://"+util.GetShopUrl()+uriVersion, ", err:", err.Error())
|
| | | return nil, err
|
| | | }
|
| | |
|
| | | query := u.Query()
|
| | | //query.Set("os", runtime.GOOS)
|
| | | query.Set("arch", runtime.GOARCH)
|
| | | query.Set("versionNum", GetCurVersion())
|
| | | //query.Set("wordSize", wordSize)
|
| | |
|
| | | u.RawQuery = query.Encode()
|
| | | resp, err := http.Get(u.String())
|
| | | if err != nil {
|
| | | logger.Error("checkVersion parse url failed, url:", u.String(), ", err:", err.Error())
|
| | | return nil, err
|
| | | }
|
| | |
|
| | | if resp.StatusCode != 200 {
|
| | | logger.Error("checkVersion incorrect status, url:", u.String(), ", status:", resp.StatusCode)
|
| | | return nil, errors.New("Status code not 200")
|
| | | }
|
| | |
|
| | | defer resp.Body.Close()
|
| | | body, err := ioutil.ReadAll(resp.Body)
|
| | | if err != nil {
|
| | | logger.Error("checkVersion read body failed, url:", u.String(), ", err:", err.Error())
|
| | | return nil, err
|
| | | }
|
| | |
|
| | | logger.Info("checkVersion dump body:", string(body))
|
| | | var info UpdateInfo
|
| | | err = json.Unmarshal(body, &info)
|
| | | if err != nil {
|
| | | logger.Error("checkVersion json.Unmarshal failed, url:", u.String(), ", err:", err.Error())
|
| | | return nil, err
|
| | | }
|
| | |
|
| | | //b, err := json.Marshal(&info)
|
| | | //if err != nil {
|
| | | // logger.Error("checkVersion json.Marshal failed, url:", u.String(), ", err:", err.Error())
|
| | | //} else {
|
| | | // logger.Info("checkVersion get response, url:", u.String(), ", response:", string(b))
|
| | | //}
|
| | |
|
| | | return &info, nil
|
| | | }
|
| | |
|
| | | //需要升级的程序
|
| | | func needUpgrade(programs []*models.Program) []*models.Program {
|
| | | //获取当前运行版本目录
|
| | | //dir := utils.GetExePath()
|
| | | dir := util.GetVamicroPath()
|
| | |
|
| | | newPrograms := []*models.Program{}
|
| | | for _, p := range programs {
|
| | | exe := path.Join(dir, p.Name)
|
| | | if !utils.PathExists(exe) {
|
| | | newPrograms = append(newPrograms, p)
|
| | | continue
|
| | | }
|
| | |
|
| | | lVersion, err := dynamicGetVersion(dir, exe)
|
| | | if err != nil {
|
| | | logger.Error("exe:", exe, "get local version failed, err:", err.Error())
|
| | | continue
|
| | | }
|
| | |
|
| | | //版本名称转换为版本号
|
| | | rVersion, err := vaversion.VersionName2VaVersion(p.Version)
|
| | | if err != nil {
|
| | | logger.Error("exe:", exe, "get remote version failed:", p.Version, ", err:", err.Error())
|
| | | continue
|
| | | }
|
| | |
|
| | | c, err := lVersion.Compare(rVersion)
|
| | | if err != nil {
|
| | | logger.Error("exe:", exe, "version compare failed, remote:", p.Version, ", err:", err.Error())
|
| | | continue
|
| | | }
|
| | |
|
| | | if c > 0 {
|
| | | logger.Error("exe:", exe, "local version greater, local:", lVersion, "remote:", p.Version)
|
| | | continue
|
| | | }
|
| | |
|
| | | if c < 0 {
|
| | | newPrograms = append(newPrograms, p)
|
| | | }
|
| | | }
|
| | |
|
| | | return newPrograms
|
| | | }
|
| | |
|
| | | func NoticeTick(c context.Context) {
|
| | | // tick := time.Tick(1 * time.Second)
|
| | | tick := time.Tick(24 * time.Hour)
|
| | | for {
|
| | | select {
|
| | | case <-c.Done():
|
| | | logger.Info("proc close, self update exit")
|
| | | return
|
| | | case <-tick:
|
| | | //logger.Info("NoticeTick !!!")
|
| | | updateNoticeLock.Lock()
|
| | | for key, _ := range updateNotice.NoticeUser {
|
| | | if 0 < updateNotice.NoticeUser[key] {
|
| | | updateNotice.NoticeUser[key]--
|
| | | }
|
| | | }
|
| | | updateNoticeLock.Unlock()
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | //升级配置处理
|
| | | func SelfUpdateStart(c context.Context, ms *bhomeclient.MicroNode) {
|
| | | //三十秒检查一次配置并更新
|
| | | // tick := time.Tick(30 * time.Second)
|
| | | tick := time.Tick(24 * time.Hour)
|
| | |
|
| | | var setting models.SysSetting
|
| | | for {
|
| | | select {
|
| | | case <-c.Done():
|
| | | logger.Info("proc close, self update exit")
|
| | | return
|
| | | case <-tick:
|
| | | settings, err := setting.GetAllSetting()
|
| | | if nil != err {
|
| | | logger.Error("fetch setting failed")
|
| | | continue
|
| | | }
|
| | |
|
| | | for _, set := range settings {
|
| | | data, _ := json.Marshal(set)
|
| | | var nodes []bhome_msg.BHAddress
|
| | | nodes = append(nodes, bhome_msg.BHAddress{})
|
| | |
|
| | | go ms.PublishNetTimeout(nodes, SysUpdateConfigTopic, data, 10)
|
| | |
|
| | | switch set.Name {
|
| | | case "sys_auto_clean":
|
| | | if "1" == set.Value {
|
| | | err := os.RemoveAll(GetBackupPath())
|
| | | if nil != err {
|
| | | logger.Error("clean update package failed:", err.Error())
|
| | | }
|
| | | _ = os.MkdirAll(GetBackupPath(), 0777)
|
| | | }
|
| | | case "sys_update_notice":
|
| | | {
|
| | | // 判断有新版本, 不再重复检测
|
| | | if updateNotice.HaveNewVersion == 1 {
|
| | | continue
|
| | | }
|
| | |
|
| | | // 设备更新提醒
|
| | | updateNoticeLock.Lock()
|
| | |
|
| | | //获取需要更新版本的程序列表
|
| | | _, version, _ := CheckVersion()
|
| | | curVersion := GetCurVersion()
|
| | | IsLastUpdate := true
|
| | | if version != curVersion {
|
| | | // 设置更新提醒, 重新下载升级包
|
| | | updateNotice.HaveNewVersion = 1
|
| | | updateNotice.PkgDownloaded = 0
|
| | | IsLastUpdate = true
|
| | | } else {
|
| | | IsLastUpdate = false
|
| | | }
|
| | |
|
| | | if !IsLastUpdate { //如果没有更新,设置用户延迟时间为0
|
| | | for key, _ := range updateNotice.NoticeUser {
|
| | | if 0 > updateNotice.NoticeUser[key] {
|
| | | updateNotice.NoticeUser[key] = 0
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | if "1" == set.Value {
|
| | | updateNotice.NoticeStatus = true
|
| | | } else {
|
| | | updateNotice.NoticeStatus = false
|
| | | }
|
| | |
|
| | | updateNoticeLock.Unlock()
|
| | | }
|
| | | case "sys_auto_update":
|
| | | {
|
| | | if "1" == set.Value && updateNotice.HaveNewVersion > 0 && updateNotice.PkgDownloaded == 0 {
|
| | | err := PreDownUpdateFile()
|
| | | if nil != err {
|
| | | logger.Error("pre download update file failed:", err.Error())
|
| | | } else {
|
| | | updateNotice.PkgDownloaded = 1
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | //检查是否距上次比较有更新
|
| | | func IsLastUpdate(programs []*models.Program, NewVersionProgram []*models.Program) bool {
|
| | | for _, program := range programs {
|
| | | for _, program2 := range NewVersionProgram {
|
| | | if program.Name == program2.Name {
|
| | | if program.Version != program2.Version {
|
| | | return true
|
| | | }
|
| | | }
|
| | | }
|
| | | }
|
| | | return true
|
| | | }
|
| | |
|
| | | //获取更新提醒
|
| | | func GetUpdateNotice() UpdateNotice {
|
| | | return updateNotice
|
| | | }
|
| | |
|
| | | //延迟提醒
|
| | | func DelayNotice(uid string, second int) UpdateNotice {
|
| | | updateNoticeLock.Lock()
|
| | | updateNotice.NoticeUser[uid] = second
|
| | | updateNoticeLock.Unlock()
|
| | | return updateNotice
|
| | | }
|
| | |
|
| | | //获取代码备份位置
|
| | | func GetBackupPath() string {
|
| | | dir := util.GetVamicroPath() + "/backup"
|
| | | _, err := os.Stat(dir)
|
| | | if nil != err {
|
| | | if os.IsNotExist(err) {
|
| | | os.Mkdir(dir, 0744)
|
| | | } else {
|
| | | return "./backup"
|
| | | }
|
| | | }
|
| | |
|
| | | return dir
|
| | | }
|
| | |
|
| | | //获取解压后的补丁包位置
|
| | | func GetPatchPath() string {
|
| | | dir := util.GetVamicroPath() + "/patch"
|
| | | _, err := os.Stat(dir)
|
| | | if nil != err {
|
| | | if os.IsNotExist(err) {
|
| | | os.Mkdir(dir, 0744)
|
| | | } else {
|
| | | return "./patch"
|
| | | }
|
| | | }
|
| | |
|
| | | return dir
|
| | | }
|
| | |
|
| | | //获取预下载升级包目录
|
| | | func GetPreDownPath() string {
|
| | | dir := util.GetVamicroPath() + "/upgrade"
|
| | | _, err := os.Stat(dir)
|
| | | if nil != err {
|
| | | if os.IsNotExist(err) {
|
| | | os.Mkdir(dir, 0744)
|
| | | } else {
|
| | | return "./upgrade"
|
| | | }
|
| | | }
|
| | |
|
| | | return dir
|
| | | }
|
| | |
|
| | | //获取上传升级包目录
|
| | | func GetPreUploadPath() string {
|
| | | dir := util.GetVamicroPath() + "/upgrade_manual"
|
| | | _, err := os.Stat(dir)
|
| | | if nil != err {
|
| | | if os.IsNotExist(err) {
|
| | | os.Mkdir(dir, 0744)
|
| | | } else {
|
| | | return "./upgrade_manual"
|
| | | }
|
| | | }
|
| | |
|
| | | return dir
|
| | | }
|
| | |
|
| | | func GetMd5(in string) string {
|
| | | data := md5.Sum([]byte(in))
|
| | | return fmt.Sprintf("%x", data)
|
| | | }
|
| | |
|
| | | //预下载升级文件
|
| | | func PreDownUpdateFile() error {
|
| | | info, err := getUpdateInfo()
|
| | | if nil != err {
|
| | | return err
|
| | | }
|
| | |
|
| | | LastDownLock.Lock()
|
| | | defer LastDownLock.Unlock()
|
| | |
|
| | | LastDownFile = make([]string, 0)
|
| | |
|
| | | //循环下载升级包
|
| | | for _, payload := range info.Data {
|
| | | logger.Info("正在下载版本补丁【" + payload.Version + "】")
|
| | | upfile, err := DownloadPatchPkg(payload)
|
| | | logger.Info("下载版本补丁完成【" + payload.Version + "】")
|
| | | if err != nil {
|
| | | return err
|
| | | }
|
| | |
|
| | | LastDownFile = append(LastDownFile, upfile)
|
| | | }
|
| | |
|
| | | return nil
|
| | | }
|
| | |
|
| | | //获取预下载升级包
|
| | | func GetLastDownFile() []string {
|
| | | var res = make([]string, 0)
|
| | | if len(LastDownFile) > 0 {
|
| | | for _, file := range LastDownFile {
|
| | | _, err := os.Stat(file)
|
| | | if nil == err {
|
| | | res = append(res, file)
|
| | | }
|
| | | }
|
| | | }
|
| | | return res
|
| | | }
|
| | |
|
| | | //回滚版本
|
| | | func Rollback(version string) error {
|
| | | dir := util.GetVamicroPath() + "/" + version
|
| | | _, err := os.Stat(dir)
|
| | | if nil != err {
|
| | | return err
|
| | | }
|
| | | SetRunVersionEnv(version)
|
| | | versionEnv = version
|
| | | return nil
|
| | | }
|
| | |
|
| | | type RegUserInfo struct {
|
| | | UserType string `json:"userType"` //个人:personal 公司: company
|
| | | PhoneNum string `json:"phoneNum"` //手机号码
|
| | | Name string `json:"name"` //姓名或公司名称
|
| | | ProvinceId string `json:"provinceId"` //省
|
| | | CityId string `json:"cityId"` //市
|
| | | CountyId string `json:"countyId"` //县
|
| | | Email string `json:"email"` //邮箱
|
| | | }
|
| | |
|
| | | //获取授权
|
| | | func Authorization(code string, isManual bool) (authinfo util.AuthorizationInfo, err error) {
|
| | | sn := util.GetVamicroPath() + "/sn.txt"
|
| | | authorization := util.GetVamicroPath() + "/auth.txt"
|
| | |
|
| | | activateCode := ""
|
| | | if len(code) == 29 && code[5:6] == "-" { //25位激活码激活
|
| | | activateCode = code
|
| | | }
|
| | |
|
| | | if "" != code && len(code) != 29 && code[5:6] != "-" {
|
| | | authinfo, err := util.GetAuthorizationInfo(code)
|
| | | if nil == err {
|
| | | activateCode = authinfo.ActivateCode
|
| | | logger.Debug("code found:" + code)
|
| | | if isManual { //手动操作更新授权文件,则立即返回,不用再次访问商城
|
| | | ioutil.WriteFile(sn, []byte(authinfo.ActivateCode), os.ModePerm)
|
| | | ioutil.WriteFile(authorization, []byte(code), os.ModePerm)
|
| | | return authinfo, nil
|
| | | } else {
|
| | | //否则以商城为准
|
| | | defer func() {
|
| | | if nil != err {
|
| | | //ioutil.WriteFile(sn, []byte(authinfo.ActivateCode), os.ModePerm)
|
| | | //ioutil.WriteFile(authorization, []byte(code), os.ModePerm)
|
| | | }
|
| | | }()
|
| | | }
|
| | | } else {
|
| | | return authinfo, errors.New("非法的授权!!")
|
| | | }
|
| | | }
|
| | | devId := config.Server.AnalyServerId
|
| | | machineCode := licence.GetMachineCode()
|
| | |
|
| | | authinfo, authcode, err := postAuthReq(util.GetSn(), activateCode, devId, machineCode, "")
|
| | | if nil != err {
|
| | | logger.Error("Authorization err:", err.Error(), authcode)
|
| | | //ioutil.WriteFile(authorization, []byte(authcode), os.ModePerm) //bug:局域网会把授权清除
|
| | | return authinfo, err
|
| | | }
|
| | |
|
| | | if authinfo.MachineCode != machineCode {
|
| | | logger.Error("GetAuthorization machineCode not match, local:", machineCode, " remote:", authinfo.MachineCode)
|
| | | return authinfo, errors.New("授权不匹配")
|
| | | }
|
| | |
|
| | | ioutil.WriteFile(sn, []byte(authinfo.ActivateCode), os.ModePerm)
|
| | | ioutil.WriteFile(authorization, []byte(authcode), os.ModePerm)
|
| | |
|
| | | return authinfo, nil
|
| | | }
|
| | |
|
| | | func postAuthReq(sn string, activateCode string, deviceId string, machineCode string, oldDeviceId string) (authinfo util.AuthorizationInfo, authcode string, err error) {
|
| | | u, err := url.Parse("http://" + util.GetShopUrl() + uriAuth)
|
| | | if err != nil {
|
| | | logger.Error("parse url failed, url:", "http://"+util.GetShopUrl()+uriAuth, ", err:", err.Error())
|
| | | return authinfo, "", err
|
| | | }
|
| | | query := u.Query()
|
| | | query.Set("sn", sn)
|
| | | query.Set("activateCode", activateCode)
|
| | | query.Set("deviceId", deviceId)
|
| | | query.Set("machineCode", machineCode)
|
| | | query.Set("oldDeviceId", oldDeviceId)
|
| | | query.Set("deviceType", config.Server.DeviceType)
|
| | | query.Set("deviceMode", config.Server.DeviceModel)
|
| | | query.Set("vGpu", util.GetVGpu())
|
| | |
|
| | | var sysInitApi bhomedbapi.SysInitApi
|
| | | b, rInfo := sysInitApi.GetRegInfo()
|
| | | if b {
|
| | | rbd, e := json.Marshal(rInfo)
|
| | | if e == nil {
|
| | | var sysRI RegUserInfo
|
| | | if e = json.Unmarshal(rbd, &sysRI); e == nil {
|
| | | query.Set("userType", sysRI.UserType)
|
| | | query.Set("name", sysRI.Name)
|
| | | query.Set("phoneNum", sysRI.PhoneNum)
|
| | | query.Set("provinceId", sysRI.ProvinceId)
|
| | | query.Set("cityId", sysRI.CityId)
|
| | | query.Set("countyId", sysRI.CountyId)
|
| | | query.Set("email", sysRI.Email)
|
| | | } else {
|
| | | logger.Error("json.Unmarshal sysRI e:", e)
|
| | | }
|
| | | } else {
|
| | | logger.Error("json.Marshal rInfo e:", e)
|
| | | }
|
| | | }
|
| | |
|
| | | u.RawQuery = query.Encode()
|
| | | resp, err := http.Get(u.String())
|
| | | if err != nil {
|
| | | logger.Error("GetAuthorization parse url failed, url:", u.String(), ", err:", err.Error())
|
| | | return authinfo, "", err
|
| | | }
|
| | |
|
| | | if resp.StatusCode != 200 {
|
| | | logger.Error("GetAuthorization incorrect status, url:", u.String(), ", status:", resp.StatusCode)
|
| | | return authinfo, "", errors.New("Status code not 200")
|
| | | }
|
| | |
|
| | | defer resp.Body.Close()
|
| | | body, err := ioutil.ReadAll(resp.Body)
|
| | | if err != nil {
|
| | | logger.Error("GetAuthorization read body failed, url:", u.String(), ", err:", err.Error())
|
| | | return authinfo, "", err
|
| | | }
|
| | |
|
| | | logger.Info("postAuthReq body:", string(body))
|
| | | var info map[string]interface{}
|
| | | err = json.Unmarshal(body, &info)
|
| | | if err != nil {
|
| | | logger.Error("GetAuthorization json.Unmarshal failed, url:", u.String(), ", err:", err.Error())
|
| | | return authinfo, "", err
|
| | | }
|
| | |
|
| | | authinfo, err = util.GetAuthorizationInfo(info["data"].(string))
|
| | | if nil == err {
|
| | | logger.Error("err:", err)
|
| | | return authinfo, info["data"].(string), nil
|
| | | }
|
| | |
|
| | | if 500 == int(info["code"].(float64)) {
|
| | | logger.Error("ret 500")
|
| | | return authinfo, "", errors.New(info["msg"].(string))
|
| | | }
|
| | |
|
| | | if true != info["success"].(bool) {
|
| | | logger.Error("not success")
|
| | | return authinfo, "", errors.New(info["msg"].(string))
|
| | | }
|
| | |
|
| | | return authinfo, info["data"].(string), nil
|
| | | }
|
| | |
|
| | | func GenQRCode() ([]byte, string, error) {
|
| | | sn := util.GetSn()
|
| | | var authInfo util.AuthorizationInfo
|
| | | authInfo.Sn = sn
|
| | | authInfo.DevId = config.Server.AnalyServerId
|
| | | authInfo.MachineCode = licence.GetMachineCode()
|
| | | authInfo.DeviceType = config.Server.DeviceType
|
| | | authInfo.DeviceMode = config.Server.DeviceModel
|
| | | authInfo.VGpu = util.GetVGpu()
|
| | |
|
| | | var sysInitApi bhomedbapi.SysInitApi
|
| | | if bsi, rInfo := sysInitApi.GetRegInfo(); bsi {
|
| | | rbd, e := json.Marshal(rInfo)
|
| | | if e == nil {
|
| | | var sysRI RegUserInfo
|
| | | if e = json.Unmarshal(rbd, &sysRI); e == nil {
|
| | | authInfo.UserType = sysRI.UserType
|
| | | authInfo.Name = sysRI.Name
|
| | | authInfo.PhoneNum = sysRI.PhoneNum
|
| | | authInfo.ProvinceId = sysRI.ProvinceId
|
| | | authInfo.CityId = sysRI.CityId
|
| | | authInfo.CountyId = sysRI.CountyId
|
| | | authInfo.Email = sysRI.Email
|
| | | } else {
|
| | | logger.Error("json.Unmarshal sysRI e:", e)
|
| | | }
|
| | | } else {
|
| | | logger.Error("json.Marshal rInfo e:", e)
|
| | | }
|
| | | }
|
| | |
|
| | | hackQ, _ := HackAuthorizationInfo(authInfo)
|
| | |
|
| | | url := "http://" + util.GetShopUrl() + uriMobile + "?q=" + hackQ
|
| | |
|
| | | logger.Info("qrcode len:", len(url), " content:", url)
|
| | |
|
| | | // 生成二维码
|
| | | q, err := qrcode.New(url, qrcode.Highest)
|
| | | if err != nil {
|
| | | return nil, url, err
|
| | | }
|
| | |
|
| | | png, err1 := q.PNG(350)
|
| | |
|
| | | return png, url, err1
|
| | | }
|
| | |
|
| | | //刷新授权到其他进程
|
| | | func AuthorizationUpdate(c context.Context, ms *bhomeclient.MicroNode) {
|
| | | //三十秒检查一次配置并更新
|
| | | // tick := time.Tick(30 * time.Second)
|
| | | tick := time.Tick(24 * time.Hour)
|
| | |
|
| | | var authInfo util.AuthorizationInfo
|
| | | var err error
|
| | | for {
|
| | | select {
|
| | | case <-c.Done():
|
| | | logger.Info("proc close, self update exit")
|
| | | return
|
| | | case <-tick:
|
| | | author := util.GetAuthorization()
|
| | | sn := util.GetSn()
|
| | |
|
| | | authInfo, err = Authorization(sn, false)
|
| | | if nil != err && !strings.Contains(err.Error(), "成功") {
|
| | | logger.Error("GetAuthorization error:", err.Error())
|
| | | if "" != author {
|
| | | authInfo, err = util.GetAuthorizationInfo(author)
|
| | | }
|
| | | }
|
| | |
|
| | | logger.Debug("authInfo:", authInfo)
|
| | | data, _ := json.Marshal(authInfo)
|
| | | var nodes []bhome_msg.BHAddress
|
| | | nodes = append(nodes, bhome_msg.BHAddress{})
|
| | | go ms.PublishNetTimeout(nodes, AuthorizationUpdateTopic, data, 10)
|
| | | }
|
| | | }
|
| | | }
|
| | |
|
| | | func HackAuthorizationInfo(authorizationInfo util.AuthorizationInfo) (string, error) {
|
| | | b, err := json.Marshal(authorizationInfo)
|
| | | if nil != err {
|
| | | return "", err
|
| | | }
|
| | | logger.Debug("authorInfo", authorizationInfo)
|
| | |
|
| | | info, err := util.RsaEncrypt(b)
|
| | | if nil != err {
|
| | | logger.Error("HackAuthorizationInfo utils.RsaEncrypt failed, err:", err.Error())
|
| | | return "", err
|
| | | }
|
| | |
|
| | | return base64.StdEncoding.EncodeToString(info), nil
|
| | | }
|
| | |
|
| | | func GetQ() string {
|
| | | sn := util.GetSn()
|
| | | var authinfo util.AuthorizationInfo
|
| | | authinfo.Sn = sn
|
| | | authinfo.DevId = config.Server.AnalyServerId
|
| | | authinfo.MachineCode = licence.GetMachineCode()
|
| | | authinfo.VGpu = util.GetVGpu()
|
| | | q, err := HackAuthorizationInfo(authinfo)
|
| | | if nil == err {
|
| | | return q
|
| | | }
|
| | | return err.Error()
|
| | | }
|
| | |
|
| | | func CancelAuthorization(passwd string, q string) (error, string) {
|
| | | uApi := bhomedbapi.UserApi{}
|
| | | ok, _ := uApi.Login("basic", passwd)
|
| | | if !ok {
|
| | | return errors.New("密码不正确!"), ""
|
| | | }
|
| | | //获取请求码里的devId
|
| | | qInfo, err := util.GetAuthorizationInfo(q)
|
| | | if nil != err {
|
| | | return errors.New("请求码格式不正确"), ""
|
| | | }
|
| | |
|
| | | curAuthInfo := util.GetAuthorization()
|
| | |
|
| | | //获取当前授权码
|
| | | authInfo, err := util.GetAuthorizationInfo(curAuthInfo)
|
| | | if nil != err {
|
| | | return errors.New("当前服务器授权信息不完整"), ""
|
| | | }
|
| | | authInfo.OldDeviceId = authInfo.DevId
|
| | | authInfo.DevId = qInfo.DevId
|
| | | authInfo.VGpu = util.GetVGpu()
|
| | | sn := util.GetVamicroPath() + "/sn.txt"
|
| | | authorization := util.GetVamicroPath() + "/auth.txt"
|
| | | authCode, err := HackAuthorizationInfo(authInfo)
|
| | | if nil != err {
|
| | | return errors.New("产品密钥导出失败"), ""
|
| | | }
|
| | | ioutil.WriteFile(sn, []byte(""), os.ModePerm)
|
| | | time.Sleep(100 * time.Microsecond)
|
| | | ioutil.WriteFile(authorization, []byte(""), os.ModePerm)
|
| | |
|
| | | _, _, err = postAuthReq(authInfo.Sn, authInfo.ActivateCode, qInfo.DevId, licence.GetMachineCode(), authInfo.OldDeviceId)
|
| | |
|
| | | if nil != err {
|
| | | return err, ""
|
| | | }
|
| | |
|
| | | return nil, authCode
|
| | | }
|