/* Sog 游戏基础库 2016 by zouwei */ using System; using System.IO; using System.Collections.Generic; using System.Security.Cryptography; using Sog; using ProtoCSStruct; namespace Version { public class PatchFileMd5Info { public string FileFullPath; public string Md5; public int FileSize; } public class VersionSvc { private VersionServerConfig m_serverConfig; private long m_lastReadVersionTime; private uint m_accountGateSelectSeq = 0; private ServerApp m_app; private const string VERSION_FILE_NAME = "version"; private const string AndroidStr = "android"; public void InitVersionServerConfig() { m_serverConfig = VersionServerUtils.GetServerConfig(); TraceLog.Debug("read serverConfig from {0}", m_app.AppParam.ServerConfig.configfile); TraceLog.Debug("downloadUrl:{0}", m_serverConfig.downloadUrl); TraceLog.Debug("versionFileBasePath:{0}", m_serverConfig.versionFileBasePath); TraceLog.Debug("lobbyGateUrl:{0}", m_serverConfig.accountGateUrl[0]); TraceLog.Debug("packageName:{0}", m_serverConfig.packageName); } //最新版本的cache,以os+language为key private Dictionary m_curVersionTable = new Dictionary(); //补丁文件MD5 cache private Dictionary m_patchFileMd5Table = new Dictionary(); // 热更包MD5 private Dictionary m_hotPatchFileMd5 = new Dictionary(); public VersionSvc(ServerApp app) { m_app = app; } public void ClearAllCache() { m_curVersionTable.Clear(); m_patchFileMd5Table.Clear(); } public void Dispose() { m_app = null; m_serverConfig = null; } public bool IsSkipCheckVersion() { return m_serverConfig.skipCheckVersion; } public bool IsValidOs(ref VersionInfo versionInfo) { if (versionInfo.Os.Equals("android") || versionInfo.Os.Equals("ios") || versionInfo.Os.Equals("win")) { return true; } return false; } public bool CheckValidVersionInfo(ref VersionInfo versionInfo) { //os 非法 if (! IsValidOs(ref versionInfo)) { return false; } //appversion 非法 if (versionInfo.AppVersion.IsEmpty()) { return false; } if(versionInfo.AppVersion.IsEmpty()) { return false; } if(AppVersion.ToIntVersion(versionInfo.AppVersion.GetString()) == 0) { return false; } string[] validLangs = m_serverConfig.validLanguage.Split('|'); for(int i=0; i < validLangs.Length; i++) { if(versionInfo.Language.Equals(validLangs[i])) { return true; } } return false; } public string GetVersionFileFullPath(string Os, string Language) { string strFullPath = string.Format("{0}/{1}/{2}/{3}" ,m_serverConfig.versionFileBasePath ,Os ,Language ,VERSION_FILE_NAME); TraceLog.Trace("VersionSvc.GetVersionFileFullPath {0}", strFullPath); return strFullPath; } //是否需要更新整包 public bool NeedUpdateFullApk(long curVersion, long reqVersion) { ushort vMax = AppVersion.GetMaxFromVersion(curVersion); ushort vMid = AppVersion.GetMidFromVersion(curVersion); //ushort vMin = AppVersion.GetMinFromVersion(curVersion); ushort vMaxReq = AppVersion.GetMaxFromVersion(reqVersion); ushort vMidReq = AppVersion.GetMidFromVersion(reqVersion); //ushort vMinReq = AppVersion.GetMinFromVersion(reqVersion); //大版本号不同需要升级整包 if (vMax > vMaxReq) { return true; } if (vMid > vMidReq) { return true; } //只升级小版本号不需要更新整包 return false; } private string GetApkRelativePath(ref VersionInfo versionInfo, long curVersion) { string strVersion = AppVersion.ToStringVersion(curVersion); // android/en/1.0.0.25/xgame_en_1.0.0.25.apk string strFullPath = string.Format("{0}/{1}/{2}/{3}_{4}_{5}" , versionInfo.Os , versionInfo.Language , strVersion , m_serverConfig.packageName , versionInfo.Language , strVersion); if(versionInfo.Os.Equals("android")) { strFullPath += ".apk"; } else { strFullPath += ".ipa"; } return strFullPath; } private string GetPatchFileRelativePath(ref VersionInfo versionInfo, long curVersion, long reqVersion) { //直接完整包 if (NeedUpdateFullApk(curVersion, reqVersion)) { return GetApkRelativePath(ref versionInfo, curVersion); } //补丁 string strFromVersion = AppVersion.ToStringVersion(reqVersion); string strToVersion = AppVersion.ToStringVersion(curVersion); // android/en/1.0.25/xgame_en_1.0.25_1.0.26.upk string strPatch = string.Format("{0}/{1}/{2}/{3}_{4}_{5}_{6}.upk" , versionInfo.Os , versionInfo.Language , strToVersion , m_serverConfig.packageName , versionInfo.Language , strFromVersion , strToVersion); return strPatch; } public bool HasHotPatch() { return ! string.IsNullOrEmpty(m_serverConfig.hotPatchHideVer); } // 热更patch private string GetHotPatchFileRelativePath(ref VersionInfo versionInfo, string curVersion) { // 当前版本没有热更包 if (string.IsNullOrEmpty(m_serverConfig.hotPatchHideVer)) { return null; } // android/en/hotfix/1.0.12_1_hotpatch.zip return string.Format("{0}/{1}/hotfix/{2}_{3}_hotpatch.zip" , versionInfo.Os , versionInfo.Language , curVersion , m_serverConfig.hotPatchHideVer); } //获取补丁文件下载地址(有可能是完整的apk文件,ios需要去应用商店下载) public string GetPatchFileUrl(ref VersionInfo versionInfo, long curVersion, long reqVersion) { string strPatchFileRelativePath = GetPatchFileRelativePath(ref versionInfo, curVersion, reqVersion); string strPatchUrl = string.Format("{0}/{1}", m_serverConfig.downloadUrl, strPatchFileRelativePath); return strPatchUrl; } public string GetHotPatchFileUrl(ref VersionInfo versionInfo, string curVersion) { string hotPath = GetHotPatchFileRelativePath(ref versionInfo, curVersion); if (string.IsNullOrEmpty(hotPath)) { return null; } return string.Format("{0}/{1}", m_serverConfig.downloadUrl, hotPath); } //获取补丁(upk或者apk的本地完整路径) public string GetPatchFileLocalFullPath(ref VersionInfo versionInfo, long curVersion, long reqVersion) { string strPatchFileRelativePath = GetPatchFileRelativePath(ref versionInfo, curVersion, reqVersion); string strPatchLocalFullPatch = string.Format("{0}/{1}", m_serverConfig.versionFileBasePath, strPatchFileRelativePath); return strPatchLocalFullPatch; } // 获取热更包本地完整路径 public string GetHotPatchFileLocalFullPath(ref VersionInfo versionInfo, string curVersion) { string relPath = GetHotPatchFileRelativePath(ref versionInfo, curVersion); if (string.IsNullOrEmpty(relPath)) { return null; } return string.Format("{0}/{1}", m_serverConfig.versionFileBasePath, relPath); } private PatchFileMd5Info CalcPatchFileMd5(string fullPath) { try { if (!File.Exists(fullPath)) { TraceLog.Error("VersionSvc.CalcPatchFileMd5 file {0} not exist", fullPath); return null; } byte[] filecontent = File.ReadAllBytes(fullPath); MD5 md5 = MD5.Create(); byte[] result = md5.ComputeHash(filecontent); md5.Dispose(); string strmd5 = string.Empty; for (int i = 0; i < result.Length; i++) { strmd5 += result[i].ToString("x2"); } PatchFileMd5Info info = new PatchFileMd5Info(); info.FileFullPath = fullPath; info.FileSize = filecontent.Length; info.Md5 = strmd5; TraceLog.Debug("VersionSvc.CalcPatchFileMd5 fullPath {0} size {1} md5 {2}" , info.FileFullPath, info.FileSize, info.Md5); return info; } catch (Exception ex) { TraceLog.Exception(ex); } return null; } public int GetPatchFileSizeAndMd5(ref VersionInfo versionInfo, long curVersion, long reqVersion, out string strmd5) { strmd5 = string.Empty; string strPatchLocalFullPatch = GetPatchFileLocalFullPath(ref versionInfo, curVersion, reqVersion); PatchFileMd5Info info; if (m_patchFileMd5Table.ContainsKey(strPatchLocalFullPatch)) { info = m_patchFileMd5Table[strPatchLocalFullPatch]; strmd5 = info.Md5; return info.FileSize; } info = CalcPatchFileMd5(strPatchLocalFullPatch); if (info == null) { return -1; } m_patchFileMd5Table.Add(strPatchLocalFullPatch, info); strmd5 = info.Md5; return info.FileSize; } public int GetHotPatchFileSizeAndMd5(ref VersionInfo versionInfo, string curVersion, out string strmd5) { strmd5 = string.Empty; string hotPath = GetHotPatchFileLocalFullPath(ref versionInfo, curVersion); if (string.IsNullOrEmpty(hotPath)) { return -1; } PatchFileMd5Info info; if (m_hotPatchFileMd5.ContainsKey(hotPath)) { info = m_hotPatchFileMd5[hotPath]; strmd5 = info.Md5; return info.FileSize; } info = CalcPatchFileMd5(hotPath); if (info == null) { return -1; } m_hotPatchFileMd5.Add(hotPath, info); strmd5 = info.Md5; return info.FileSize; } //获得对应 os_lang 的最新版本 public long GetCurrentVersion(ref VersionInfo versionInfo) { if (! IsValidOs(ref versionInfo)) { return 0; } string os = versionInfo.Os.GetString(); string lan = versionInfo.Language.GetString(); // windows通知android的版本号, 方便在unity测试, 客户端会在unity下屏蔽掉自动更新功能 if (os.Equals("win")) { os = AndroidStr; } string strKey = os + "_" + lan; bool bInTableAlready = m_curVersionTable.ContainsKey(strKey); long now = AppTime.GetNowSysMs(); //10秒钟读取一次版本文件 //这么做是为了版本服务器不停服的情况下可以在线发新版本,也不需要reloadconfig,方便些 if (bInTableAlready == false || now - m_lastReadVersionTime > 10*1000) { m_lastReadVersionTime = now; try { string versionFile = GetVersionFileFullPath(os, lan); string strVersion = File.ReadAllText(versionFile); if (strVersion == null) { TraceLog.Trace("VersionSvc.GetCurrentVersion file {0} no data", versionFile); return 0; } long iVersion = AppVersion.ToIntVersion(strVersion); if (bInTableAlready == false) { m_curVersionTable.Add(strKey, iVersion); } else { m_curVersionTable[strKey] = iVersion; } } catch(Exception) { return 0; } } return m_curVersionTable[strKey]; } //选择一个lobbyGate的Url public string SelectGateUrl(ref CSVersionCheckReq req, out int forReview) { //缺省是正式版本,审核字段设置成0 forReview = 0; foreach (var specialversion in m_serverConfig.specialVersions) { //特殊版本返回特殊配置,不配置specialOs表示所有specialVersion //注意只有app版本和apk版本一致才行(新包),不一致的表示是审核期间通过自动更新上来的玩家 if (req.Version.AppVersion.Equals(specialversion.version) && req.Version.AppVersion.Equals(req.ApkVersion.GetPtr()) && (string.IsNullOrEmpty(specialversion.os) || req.Version.Os.Equals(specialversion.os)) ) { forReview = specialversion.review; return specialversion.gateUrl; } } //正常版本,轮着来,选择下一个就可以了,客户端和accountgate的连接是短连接,无需复杂的负载均衡 //注意:如果物理机器的网关支持一个对外端口负载均衡到内网多个端口的话(TGW似乎就支持),这个功能尽量不用, //因为对外端口越多,客户端连不上概率越大 int selectIndex = (int)(m_accountGateSelectSeq % m_serverConfig.accountGateUrl.Length); m_accountGateSelectSeq++; return m_serverConfig.accountGateUrl[selectIndex]; } public void AddClientConnectData(uint gateServerID, long sessionID) { var clientConnData = VersionServerUtils.GetVersionServerData().m_ClientConnectDict; if (clientConnData.ContainsKey(sessionID) == false) { ClientConnectInfo data = new ClientConnectInfo(); data.iConnectSessionID = sessionID; data.uGateServerID = gateServerID; data.iConnectTime = AppTime.GetNowSysMs(); clientConnData.Add(sessionID, data); } } public void RemoveClientConnectData(uint gateServerID, long sessionID) { var clientConnData = VersionServerUtils.GetVersionServerData().m_ClientConnectDict; clientConnData.Remove(sessionID); } public bool IsMaintenanceMode() { return m_serverConfig.maintenanceMode == 1; } public string GetMaintenanceNotice() { return m_serverConfig.maintenanceNotice; } } }