Przeglądaj źródła

新增app新版本检测和下载接口

linhong 3 lat temu
rodzic
commit
2802e2be40

+ 21 - 4
Controllers/L1SvrController.cs

@@ -1,5 +1,7 @@
 using LJLib.Client;
 using LJLib.Net.SPI.Client;
+using LJLib.Net.SPI.Com;
+using LJProxy.Models;
 using LJProxy.Settings;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Options;
@@ -7,7 +9,9 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
-
+using Newtonsoft.Json;
+using System.IO;
+using System.Text;
 
 namespace LJProxy.Controllers
 {
@@ -64,6 +68,7 @@ namespace LJProxy.Controllers
                 return _CRPPool;
             }
         }
+
         [Route("PRO/{apiName}")]
         [HttpPost]
         public string PRO(string apiName, [FromBody] object requestBody)
@@ -71,12 +76,24 @@ namespace LJProxy.Controllers
             var rslt = PROPool.DoExcute(apiName, requestBody.ToString());
             return rslt;
         }
+
         [Route("CRP/{apiName}")]
         [HttpPost]
-        public string CRP(string apiName,[FromBody] object requestBody)
+        [HttpGet]
+        public async Task<IActionResult> CRP(string apiName)
         {
-            var rslt = CRPPool.DoExcute(apiName, requestBody.ToString());
-            return rslt;
+            string requestBody;
+            using(StreamReader reader = new StreamReader(Request.Body,Encoding.UTF8))
+            {
+                requestBody = await reader.ReadToEndAsync();
+            }
+            var excuteResult = GlobalVar.Excute(apiName, requestBody, Request);
+            if (excuteResult.Item1) return Json( excuteResult.Item2);
+            else
+            {
+                var rslt = CRPPool.DoExcute(apiName, requestBody);
+                return Json( rslt);
+            }
         }
     }
 }

+ 63 - 0
Excutor/CheckUpdateExcutor.cs

@@ -0,0 +1,63 @@
+using LJProxy.LJLib.Net.SPI.Server;
+using LJProxy.Models;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.IO;
+using LJLib;
+
+namespace LJProxy.Excutor
+{
+    public class CheckUpdateExcutor : ExcutorBase<CheckUpdateRequest, CheckUpdateResponse>
+    {
+        private static object _syncRoot = new object();
+        protected override void ExcuteInternal(CheckUpdateRequest request, object state, CheckUpdateResponse rslt)
+        {
+            rslt.ErrCode = "0";
+            if (string.IsNullOrEmpty(request.CurrentVersion))
+            {
+                rslt.ErrMsg = "CurrentVersion can not be empty";
+                return;
+            }
+
+            var apkDirs =@$"{AppDomain.CurrentDomain.BaseDirectory}apk\";
+            if (!Directory.Exists(apkDirs))
+            {
+                rslt.ErrMsg = "apk file folder not exists";
+                return;
+            }
+
+            //cache file version
+            string newVersion = string.Empty;
+            string cacheKey = "NewestApkVersion";
+            lock (_syncRoot)
+            {
+                newVersion = CacheHelper.DefaultCache.Get(cacheKey) as string;
+                if (string.IsNullOrEmpty(newVersion))
+                {
+                    var dirinfo = new DirectoryInfo(apkDirs);
+                    var file = dirinfo.GetFiles()
+                        .Where(x => x.Name.EndsWith(".apk", StringComparison.CurrentCultureIgnoreCase))
+                        .OrderByDescending(x => x.LastWriteTime).FirstOrDefault();
+                    if (file == null)
+                    {
+                        rslt.ErrMsg = "can not found apk";
+                        return;
+                    }
+
+                    APKHelper apkHelper = new APKHelper();
+                    newVersion = apkHelper.GetVersionName(file.FullName);
+                    //TODO:文件夹缓存依赖不生效,待扩展
+                    CacheHelper.DefaultCache.Add(cacheKey, newVersion, CacheHelper.CreateCacheItemPolicy(null, DateTime.Now.AddMinutes(30), new List<string>() { file.FullName }));
+                }
+            }
+
+            if (string.Compare(newVersion, request.CurrentVersion) > 0)
+            {
+                rslt.ErrCode = "1";
+                rslt.NewVersion = newVersion;
+            }
+
+        }
+    }
+}

+ 22 - 0
Excutor/GetProxyDomainListExcutor.cs

@@ -0,0 +1,22 @@
+using LJProxy.LJLib.Net.SPI.Server;
+using LJProxy.Models;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace LJProxy.Excutor
+{
+    public class GetProxyDomainListExcutor : ExcutorBase<GetProxyDomainListRequest, GetProxyDomainListResponse>
+    {
+        protected override void ExcuteInternal(GetProxyDomainListRequest request, object state, GetProxyDomainListResponse rslt)
+        {
+            rslt.ErrMsg = "";
+            //TODO:get domain list from config file?
+            rslt.DomainList = new List<string>() {
+                "192.168.0.94","192.168.0"
+            };
+
+        }
+    }
+}

+ 80 - 0
Excutor/GetUpdatePkgExcutor.cs

@@ -0,0 +1,80 @@
+using LJLib;
+using LJProxy.LJLib.Net.SPI.Server;
+using LJProxy.Models;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace LJProxy.Excutor
+{
+    public class GetUpdatePkgExcutor : ExcutorBase<GetUpdatePkgRequest, GetUpdatePkgResponse>
+    {
+        private static object _syncRoot = new object();
+        protected override void ExcuteInternal(GetUpdatePkgRequest request, object state, GetUpdatePkgResponse rslt)
+        {
+            rslt.ErrCode = "0";
+
+            var apkDirs = @$"{AppDomain.CurrentDomain.BaseDirectory}apk\";
+            if (!Directory.Exists(apkDirs))
+            {
+                rslt.ErrMsg = "apk file folder not exists";
+                return;
+            }
+
+            //cache file version
+            ApkInfo apkInfo = null;
+            string cacheKey = "NewestApkData";
+            lock (_syncRoot)
+            {
+                apkInfo = CacheHelper.DefaultCache.Get(cacheKey) as ApkInfo;
+                if (apkInfo == null)
+                {
+                    var dirinfo = new DirectoryInfo(apkDirs);
+                    var file = dirinfo.GetFiles()
+                        .Where(x => x.Name.EndsWith(".apk", StringComparison.CurrentCultureIgnoreCase))
+                        .OrderByDescending(x => x.LastWriteTime).FirstOrDefault();
+                    if (file == null)
+                    {
+                        rslt.ErrMsg = "can not found apk";
+                        return;
+                    }
+
+                    byte[] apkData = null;
+                    using (var fs = file.OpenRead())
+                    using (var ms = new MemoryStream())
+                    {
+                        byte[] buff = new byte[10240];
+                        int read = 0;
+                        int total = 0;
+                        while ((read = fs.Read(buff, 0, buff.Length)) > 0)
+                        {
+                            ms.Write(buff, 0, read);
+                            ms.Flush();
+                            total += read;
+                        }
+                        apkData = ms.ToArray();
+                    }
+
+                    APKHelper apkHelper = new APKHelper();
+                    var clientVer = apkHelper.GetVersionName(apkData);
+                    apkInfo = new ApkInfo();
+                    apkInfo.Version = clientVer;
+                    apkInfo.ApkData = apkData;
+                    //TODO:文件夹缓存依赖不生效,待扩展
+                    CacheHelper.DefaultCache.Add(cacheKey, apkInfo, CacheHelper.CreateCacheItemPolicy(null, DateTime.Now.AddMinutes(30), new List<string>() { file.FullName }));
+                }
+            }
+
+            rslt.Version = apkInfo.Version;
+            rslt.ApkData = apkInfo.ApkData;
+        }
+
+        public class ApkInfo
+        {
+            public string Version { get; set; }
+            public byte[] ApkData { get; set; }
+        }
+    }
+}

+ 48 - 0
GlobalVar/CacheHelper.cs

@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Runtime.Caching;
+
+namespace LJProxy
+{
+    public static class CacheHelper
+    {
+        public static MemoryCache DefaultCache
+        {
+            get
+            {
+                return MemoryCache.Default;
+            }
+        }
+
+        private static MemoryCache _imgCache = new MemoryCache("ImgCache");
+        public static MemoryCache ImgCache
+        {
+            get
+            {
+                return _imgCache;
+            }
+        }
+
+        public static CacheItemPolicy CreateCacheItemPolicy(TimeSpan? slidingExpiration = null, DateTime? absoluteExpiration = null
+            , List<string> dependencyKeys = null, CacheItemPriority? priority = null)
+        {
+            var cachePolicy = new CacheItemPolicy();
+            if (slidingExpiration != null)
+                cachePolicy.SlidingExpiration = slidingExpiration.Value;
+            if (absoluteExpiration != null)
+                cachePolicy.AbsoluteExpiration = absoluteExpiration.Value;
+            if (dependencyKeys !=null && dependencyKeys.Count>0 )
+            {
+                cachePolicy.ChangeMonitors.Add(new HostFileChangeMonitor(dependencyKeys));
+            }
+
+            if (priority != null)
+                cachePolicy.Priority = priority.Value;
+            else
+                cachePolicy.Priority = CacheItemPriority.Default;
+            return cachePolicy;
+        }
+    }
+}

+ 41 - 0
GlobalVar/GlobalVar.cs

@@ -0,0 +1,41 @@
+using LJLib.Net.SPI.Com;
+using LJProxy.Excutor;
+using LJProxy.LJLib.Net.SPI.Server;
+using LJProxy.Models;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Newtonsoft.Json.Linq;
+
+namespace LJProxy
+{
+    public static class GlobalVar
+    {
+        private static ExcutorManager excutorManager = null;
+        static GlobalVar()
+        {
+            InitExcutorMap();
+        }
+
+        public static void InitExcutorMap()
+        {
+            excutorManager = new ExcutorManager();
+
+            excutorManager.AddMap(new CheckUpdateRequest().GetApiName(), typeof(CheckUpdateRequest), new CheckUpdateExcutor());
+            excutorManager.AddMap(new GetUpdatePkgRequest().GetApiName(), typeof(GetUpdatePkgRequest), new GetUpdatePkgExcutor());
+            excutorManager.AddMap(new GetProxyDomainListRequest().GetApiName(), typeof(GetProxyDomainListRequest), new GetProxyDomainListExcutor());
+        }
+
+        public static Tuple<bool,LJResponse> Excute(string apiName,string requestBody,object requestState)
+        {
+            var requestType = excutorManager.GetRequestType(apiName);
+            if (requestType == null) return new Tuple<bool, LJResponse>(false, null);
+            ILJRequest request = null;
+            if(!string.IsNullOrEmpty(requestBody)) request = JObject.Parse(requestBody).ToObject(requestType) as ILJRequest;
+            else request = new JObject().ToObject(requestType) as ILJRequest;
+            var result = excutorManager.DoExcute( request, requestState);
+            return new Tuple<bool, LJResponse>(true, result);
+        }
+    }
+}

+ 12 - 0
LJLib.Net.SPI.Com/ErrResponse.cs

@@ -0,0 +1,12 @@
+
+namespace LJLib.Net.SPI.Com
+{
+    internal sealed class ErrResponse : LJResponse
+    {
+        public ErrResponse() { }
+        public ErrResponse(string errmsg)
+        {
+            this.ErrMsg = errmsg;
+        }
+    }
+}

+ 17 - 0
LJLib.Net.SPI.Com/ILJRequest.cs

@@ -0,0 +1,17 @@
+
+namespace LJLib.Net.SPI.Com
+{
+    public interface ILJRequest
+    {
+        string GetApiName();
+    }
+
+    public abstract class LJRequest<T> : ILJRequest where T : LJResponse
+    {
+        public string GetApiName()
+        {
+            var typeName = this.GetType().Name;
+            return typeName.Substring(0, typeName.Length - 7);
+        }
+    }
+}

+ 20 - 0
LJLib.Net.SPI.Com/LJResponse.cs

@@ -0,0 +1,20 @@
+
+namespace LJLib.Net.SPI.Com
+{
+    public abstract class LJResponse
+    {
+        /// <summary>
+        /// 错误代码
+        /// </summary>
+        public string ErrCode { get; set; }
+        /// <summary>
+        /// 错误信息
+        /// </summary>
+        public string ErrMsg { get; set; }
+        /// <summary>
+        /// 异常详细信息
+        /// </summary>
+        public string DebugMsg { get; set; }
+
+    }
+}

+ 36 - 0
LJLib.Net.SPI.Server/ExcutorBase.cs

@@ -0,0 +1,36 @@
+using LJLib.Net.SPI.Com;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace LJProxy.LJLib.Net.SPI.Server
+{
+    public abstract class ExcutorBase
+    {
+        public abstract LJResponse Excute(ILJRequest request,object state);
+    }
+
+    public abstract class ExcutorBase<T1,T2>:ExcutorBase where T1: LJRequest<T2> where T2:LJResponse,new()
+    {
+        protected abstract void ExcuteInternal(T1 request, object state, T2 rslt);
+        public override LJResponse Excute(ILJRequest request, object state)
+        {
+            T2 rslt = new T2();
+            try
+            {
+                T1 req = request as T1;
+                if(req == null)
+                {
+                    rslt.ErrMsg = string.Format("request 不能转换成类型[{0}]",typeof(T1).Name);
+                    return rslt;
+                }
+                ExcuteInternal(req, state, rslt);
+            }catch(Exception e)
+            {
+                rslt.ErrMsg = e.Message.ToString();
+            }
+            return rslt;
+        }
+    }
+}

+ 32 - 0
LJLib.Net.SPI.Server/ExcutorManager.cs

@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace LJProxy.LJLib.Net.SPI.Server
+{
+    public sealed class ExcutorManager:LJServerBase
+    {
+        private Dictionary<string, ExcutorBase> excutors = new Dictionary<string, ExcutorBase>();
+        private Dictionary<string, string> dynamicHtmlDic = new Dictionary<string, string>();
+
+        public void AddMap(string apiName, Type requestType, ExcutorBase excutor, string dynamicHtml = null)
+        {
+            excutors[apiName] = excutor;
+            this.AddMap(apiName, requestType, excutor.Excute);
+            if (!string.IsNullOrEmpty(dynamicHtml))
+            {
+                dynamicHtmlDic[dynamicHtml] = apiName;
+            }
+        }
+
+        public string getDynamicHtmlApi(string url)
+        {
+            if (dynamicHtmlDic.ContainsKey(url))
+            {
+                return dynamicHtmlDic[url];
+            }
+            return null;
+        }
+    }
+}

+ 14 - 0
LJLib.Net.SPI.Server/ILJServer.cs

@@ -0,0 +1,14 @@
+using LJLib.Net.SPI.Com;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace LJProxy.LJLib.Net.SPI.Server
+{
+    public interface ILJServer
+    {
+        LJResponse DoExcute(ILJRequest request, object state);
+        Type GetRequestType(string apiName);
+    }
+}

+ 47 - 0
LJLib.Net.SPI.Server/LJServerBase.cs

@@ -0,0 +1,47 @@
+using LJLib.Net.SPI.Com;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace LJProxy.LJLib.Net.SPI.Server
+{
+    public abstract class LJServerBase : ILJServer
+    {
+        public delegate LJResponse DHandle(ILJRequest request, object state);
+        protected Dictionary<string, DHandle> handlers = new Dictionary<string, DHandle>(StringComparer.OrdinalIgnoreCase);
+        protected Dictionary<string, Type> requestTypes = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
+
+        public LJResponse DoExcute(ILJRequest request, object state)
+        {
+            if (!handlers.ContainsKey(request.GetApiName()))
+            {
+                throw new Exception("接口未定义:" + request.GetApiName().ToString());
+            }
+            var handler = handlers[request.GetApiName()];
+            if (handler == null)
+            {
+                throw new Exception("接口未定义:" + request.GetApiName().ToString());
+            }
+            return handler(request, state);
+        }
+
+        public Type GetRequestType(string apiName)
+        {
+            if (requestTypes.ContainsKey(apiName))
+            {
+                return requestTypes[apiName];
+            }
+            else
+            {
+                return null;
+            }
+        }
+
+        public void AddMap(string apiName,Type requestType,DHandle handler)
+        {
+            requestTypes[apiName] = requestType;
+            handlers[apiName] = handler;
+        }
+    }
+}

+ 4 - 0
LJProxy.csproj

@@ -9,7 +9,11 @@
   </ItemGroup>
 
   <ItemGroup>
+    <PackageReference Include="AndroidXml" Version="1.1.22" />
     <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.18" />
+    <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
+    <PackageReference Include="SharpZipLib.NETStandard" Version="1.0.7" />
+    <PackageReference Include="System.Runtime.Caching" Version="5.0.0" />
   </ItemGroup>
 
 

+ 1 - 1
LJProxy.csproj.user

@@ -3,7 +3,7 @@
   <PropertyGroup>
     <Controller_SelectedScaffolderID>MvcControllerEmptyScaffolder</Controller_SelectedScaffolderID>
     <Controller_SelectedScaffolderCategoryPath>root/Common/MVC/Controller</Controller_SelectedScaffolderCategoryPath>
-    <ShowAllFiles>true</ShowAllFiles>
+    <ShowAllFiles>false</ShowAllFiles>
     <NameOfLastUsedPublishProfile>D:\ImportantData\Develop\ProjectCode\LJProxy\Properties\PublishProfiles\FolderProfile.pubxml</NameOfLastUsedPublishProfile>
   </PropertyGroup>
 </Project>

+ 18 - 0
Models/CheckUpdate.cs

@@ -0,0 +1,18 @@
+using LJLib.Net.SPI.Com;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace LJProxy.Models
+{
+    public class CheckUpdateRequest : LJRequest<CheckUpdateResponse>
+    {
+        public string CurrentVersion { get; set; }
+    }
+
+    public class CheckUpdateResponse:LJResponse
+    {
+        public string NewVersion { get; set; }
+    }
+}

+ 18 - 0
Models/GetProxyDomainList.cs

@@ -0,0 +1,18 @@
+using LJLib.Net.SPI.Com;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace LJProxy.Models
+{
+    public class GetProxyDomainListRequest : LJRequest<GetProxyDomainListResponse>
+    {
+        
+    }
+
+    public class GetProxyDomainListResponse:LJResponse
+    {
+        public List<string> DomainList { get; set; }
+    }
+}

+ 19 - 0
Models/GetUpdatePkg.cs

@@ -0,0 +1,19 @@
+using LJLib.Net.SPI.Com;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace LJProxy.Models
+{
+    public class GetUpdatePkgRequest : LJRequest<GetUpdatePkgResponse>
+    {
+        
+    }
+
+    public class GetUpdatePkgResponse : LJResponse
+    {
+        public string Version { get; set; }
+        public byte[] ApkData { get; set; }
+    }
+}

+ 3 - 1
Startup.cs

@@ -26,7 +26,9 @@ namespace LJProxy
         // This method gets called by the runtime. Use this method to add services to the container.
         public void ConfigureServices(IServiceCollection services)
         {
-            services.AddControllers().AddNewtonsoftJson();
+            services.AddControllers().AddNewtonsoftJson(opt => {
+                opt.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver();
+            });
             services.Configure<AppSettings>(Configuration.GetSection("Appsettings"));
         }
 

+ 78 - 0
Utilities/APKHelper.cs

@@ -0,0 +1,78 @@
+using AndroidXml;
+using ICSharpCode.SharpZipLib.Zip;
+using LJLib.Tools.Helper;
+using System.IO;
+using System.Xml;
+
+namespace LJLib
+{
+    public class APKHelper
+    {
+        public string GetVersionName(string apkPath)
+        {
+            using (var fs = new FileStream(apkPath, FileMode.Open, FileAccess.Read))
+            {
+                return GetVersionName(fs);
+            }
+        }
+
+        public string GetVersionName(byte[] apkbytes)
+        {
+            using (var fs = new MemoryStream(apkbytes))
+            {
+                return GetVersionName(fs);
+            }
+        }
+
+        public string GetVersionName(Stream apkStream)
+        {
+            byte[] xmlbytes;
+
+            using (var zipfile = new ZipFile(apkStream))
+            {
+                var item = zipfile.GetEntry("AndroidManifest.xml");
+                using (var ms = new MemoryStream())
+                using (var strm = zipfile.GetInputStream(item))
+                {
+                    StreamHelper.StreamCopy(ms, strm);
+                    xmlbytes = ms.ToArray();
+                }
+            }
+
+            using (var ms = new MemoryStream(xmlbytes))
+            {
+                XmlReader reader;
+                if (xmlbytes[0] == '<' || char.IsWhiteSpace((char) xmlbytes[0]))
+                {
+                    // Normal XML file
+                    reader = new XmlTextReader(ms)
+                    {
+                        WhitespaceHandling = WhitespaceHandling.None
+                    };
+                }
+                else
+                {
+                    // Android binary XML
+                    reader = new AndroidXmlReader(ms);
+                }
+                using (reader)
+                while (reader.Read())
+                {
+                    if (reader.NodeType == XmlNodeType.Element && reader.Name.ToLower() == "manifest")
+                    {
+                        for (int i = 0; i < reader.AttributeCount; i++)
+                        {
+                            reader.MoveToAttribute(i);
+                            if (reader.LocalName.ToLower() == "versionname")
+                            {
+                                return reader.Value;
+                            }
+                        }
+                        reader.MoveToElement();
+                    }
+                }
+                return string.Empty;
+            }
+        }
+    }
+}