Bläddra i källkod

1. 修复一个Http模块中,异步处理方法的bug。

Yinmany 7 år sedan
förälder
incheckning
498595ca92

+ 1 - 1
Server/App/Program.cs

@@ -105,7 +105,7 @@ namespace App
 						Game.Scene.AddComponent<ConfigComponent>();
 						Game.Scene.AddComponent<ServerFrameComponent>();
 						Game.Scene.AddComponent<ActorManagerComponent>();
-						//Game.Scene.AddComponent<HttpComponent>();
+						// Game.Scene.AddComponent<HttpComponent>();
 						break;
 					case AppType.Benchmark:
 						Game.Scene.AddComponent<NetOuterComponent>();

+ 50 - 43
Server/Hotfix/Module/Http/HttpTest.cs

@@ -1,49 +1,56 @@
 using System.Net;
 using ETModel;
+using System.Threading.Tasks;
 
 namespace ETHotfix
 {
-	public class AccountInfo
-	{
-		public string name;
-		public string pwd;
-	}
-
-	[HttpHandler(AppType.Gate, "/")]
-	public class HttpTest: AHttpHandler
-	{
-		[Get] // url-> /Login?name=11&age=1111
-		public string Login(string name, int age, HttpListenerRequest req, HttpListenerResponse resp)
-		{
-			Log.Info(name);
-			Log.Info($"{age}");
-			return "ok";
-		}
-
-		[Get("t")] // url-> /t
-		public int Test()
-		{
-			return 1;
-		}
-
-		[Post] // url-> /Test1
-		public int Test1(HttpListenerRequest req)
-		{
-			return 1;
-		}
-
-		[Get] // url-> /Test2
-		public int Test2(HttpListenerResponse resp)
-		{
-			return 1;
-		}
-
-		[Post]
-		public object Test3(HttpListenerResponse resp, HttpListenerRequest req, string postBody, AccountInfo accountInfo)
-		{
-			Log.Info(postBody);
-			Log.Info(JsonHelper.ToJson(accountInfo));
-			return new { name = "1111" };
-		}
-	}
+  [HttpHandler(AppType.Gate, "/")]
+  public class HttpTest : AHttpHandler
+  {
+    [Get] // url-> /Login?name=11&age=1111
+    public string Login(string name, int age, HttpListenerRequest req, HttpListenerResponse resp)
+    {
+      Log.Info(name);
+      Log.Info($"{age}");
+      return "ok";
+    }
+
+    [Get("t")] // url-> /t
+    public int Test()
+    {
+      return 1;
+    }
+
+    [Post] // url-> /Test1
+    public int Test1(HttpListenerRequest req)
+    {
+      return 1;
+    }
+
+    [Get] // url-> /Test2
+    public int Test2(HttpListenerResponse resp)
+    {
+      return 1;
+    }
+
+    [Get] // url-> /GetRechargeRecord
+    public async Task<HttpResult> GetRechargeRecord(long id)
+    {
+     // var db = Game.Scene.GetComponent<DBProxyComponent>();
+
+     // var info = await db.Query<RechargeRecord>(id);
+
+      await Task.Delay(1000); // 用于测试
+
+      object info = null;
+      if (info != null)
+      {
+        return Ok(data: info);
+      }
+      else
+      {
+        return Error("ID不存在!");
+      }
+    }
+  }
 }

+ 44 - 42
Server/Model/Entity/Http.cs

@@ -1,45 +1,47 @@
 namespace ETModel
 {
-	// 充值流水
-	public sealed class RechargeRecord : Entity
-	{
-		// 充值玩家
-		public int PlayerNO { get; set; }
-
-		// 充值数量
-		public int CardNumber { get; set; }
-		
-		// 充值时间
-		public long Time { get; set; }
-
-		public RechargeRecord(long id): base(id)
-		{
-		}
-	}
-
-	// 保存玩家充值记录, 每个玩家只有一条
-	public sealed class Recharge : Entity
-	{
-		public int CardNumber { get; set; }
-		
-		public long UpdateTime { get; set; }
-
-		public Recharge(long id): base(id)
-		{
-		}
-	}
-
-	public class HttpResult
-	{
-		public int code;
-		public bool status;
-		public string msg = "";
-	}
-
-	public static class HttpErrorCode
-	{
-		public const int Exception = 999;
-		public const int Success = 1000;
-		public const int RpcFail = 1002;
-	}
+  // 充值流水
+  public sealed class RechargeRecord : Entity
+  {
+    // 充值玩家
+    public int PlayerNO { get; set; }
+
+    // 充值数量
+    public int CardNumber { get; set; }
+
+    // 充值时间
+    public long Time { get; set; }
+
+    public RechargeRecord(long id) : base(id)
+    {
+    }
+  }
+
+  // 保存玩家充值记录, 每个玩家只有一条
+  public sealed class Recharge : Entity
+  {
+    public int CardNumber { get; set; }
+
+    public long UpdateTime { get; set; }
+
+    public Recharge(long id) : base(id)
+    {
+    }
+  }
+
+  public class HttpResult
+  {
+    public int code;
+    public bool status;
+    public string msg = "";
+    [MongoDB.Bson.Serialization.Attributes.BsonIgnoreIfNull]
+    public object data;
+  }
+
+  public static class HttpErrorCode
+  {
+    public const int Exception = 999;
+    public const int Success = 1000;
+    public const int RpcFail = 1002;
+  }
 }

+ 331 - 326
Server/Model/Module/Http/HttpComponent.cs

@@ -3,333 +3,338 @@ using System.Collections.Generic;
 using System.IO;
 using System.Net;
 using System.Reflection;
+using System.Threading.Tasks;
 
 namespace ETModel
 {
-	[ObjectSystem]
-	public class HttpComponentComponentAwakeSystem: AwakeSystem<HttpComponent>
-	{
-		public override void Awake(HttpComponent self)
-		{
-			self.Awake();
-		}
-	}
-
-	[ObjectSystem]
-	public class HttpComponentComponentLoadSystem: LoadSystem<HttpComponent>
-	{
-		public override void Load(HttpComponent self)
-		{
-			self.Load();
-		}
-	}
-
-	[ObjectSystem]
-	public class HttpComponentComponentStartSystem : StartSystem<HttpComponent>
-	{
-		public override void Start(HttpComponent self)
-		{
-			self.Start();
-		}
-	}
-
-	/// <summary>
-	/// http请求分发器
-	/// </summary>
-	public class HttpComponent: Component
-	{
-		public AppType appType;
-		public HttpListener listener;
-		public HttpConfig HttpConfig;
-		public Dictionary<string, IHttpHandler> dispatcher;
-
-		// 处理方法
-		private Dictionary<MethodInfo, IHttpHandler> handlersMapping;
-
-		// Get处理
-		private Dictionary<string, MethodInfo> getHandlers;
-		private Dictionary<string, MethodInfo> postHandlers;
-
-		public void Awake()
-		{
-			StartConfig startConfig = Game.Scene.GetComponent<StartConfigComponent>().StartConfig;
-			this.appType = startConfig.AppType;
-			this.HttpConfig = startConfig.GetComponent<HttpConfig>();
-
-			this.Load();
-		}
-
-		public void Load()
-		{
-			this.dispatcher = new Dictionary<string, IHttpHandler>();
-			this.handlersMapping = new Dictionary<MethodInfo, IHttpHandler>();
-			this.getHandlers = new Dictionary<string, MethodInfo>();
-			this.postHandlers = new Dictionary<string, MethodInfo>();
-
-			Type[] types = DllHelper.GetMonoTypes();
-
-			foreach (Type type in types)
-			{
-				object[] attrs = type.GetCustomAttributes(typeof(HttpHandlerAttribute), false);
-				if (attrs.Length == 0)
-				{
-					continue;
-				}
-
-				HttpHandlerAttribute httpHandlerAttribute = (HttpHandlerAttribute) attrs[0];
-				if (!httpHandlerAttribute.AppType.Is(this.appType))
-				{
-					continue;
-				}
-
-				object obj = Activator.CreateInstance(type);
-
-				IHttpHandler ihttpHandler = obj as IHttpHandler;
-				if (ihttpHandler == null)
-				{
-					throw new Exception($"HttpHandler handler not inherit IHttpHandler class: {obj.GetType().FullName}");
-				}
-
-				this.dispatcher.Add(httpHandlerAttribute.Path, ihttpHandler);
-
-				LoadMethod(type, httpHandlerAttribute, ihttpHandler);
-			}
-		}
-
-		public void Start()
-		{
-			try
-			{
-				this.listener = new HttpListener();
-
-				if (this.HttpConfig.Url == null)
-				{
-					this.HttpConfig.Url = "";
-				}
-
-				foreach (string s in this.HttpConfig.Url.Split(';'))
-				{
-					if (s.Trim() == "")
-					{
-						continue;
-					}
-
-					this.listener.Prefixes.Add(s);
-				}
-
-				this.listener.Start();
-
-				this.Accept();
-			}
-			catch (HttpListenerException e)
-			{
-				throw new Exception($"http server error: {e.ErrorCode}", e);
-			}
-		}
-
-		public void LoadMethod(Type type, HttpHandlerAttribute httpHandlerAttribute, IHttpHandler httpHandler)
-		{
-			// 扫描这个类里面的方法
-			MethodInfo[] methodInfos = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance);
-			foreach (MethodInfo method in methodInfos)
-			{
-				object[] getAttrs = method.GetCustomAttributes(typeof(GetAttribute), false);
-				if (getAttrs.Length != 0)
-				{
-					GetAttribute get = (GetAttribute) getAttrs[0];
-
-					string path = method.Name;
-					if (!string.IsNullOrEmpty(get.Path))
-					{
-						path = get.Path;
-					}
-
-					getHandlers.Add(httpHandlerAttribute.Path + path, method);
-					//Log.Debug($"add handler[{httpHandler}.{method.Name}] path {httpHandlerAttribute.Path + path}");
-				}
-
-				object[] postAttrs = method.GetCustomAttributes(typeof(PostAttribute), false);
-				if (postAttrs.Length != 0)
-				{
-					// Post处理方法
-					PostAttribute post = (PostAttribute) postAttrs[0];
-
-					string path = method.Name;
-					if (!string.IsNullOrEmpty(post.Path))
-					{
-						path = post.Path;
-					}
-
-					postHandlers.Add(httpHandlerAttribute.Path + path, method);
-					//Log.Debug($"add handler[{httpHandler}.{method.Name}] path {httpHandlerAttribute.Path + path}");
-				}
-
-				if (getAttrs.Length == 0 && postAttrs.Length == 0)
-				{
-					continue;
-				}
-
-				handlersMapping.Add(method, httpHandler);
-			}
-		}
-
-		public async void Accept()
-		{
-			while (true)
-			{
-				if (this.IsDisposed)
-				{
-					return;
-				}
-
-				HttpListenerContext context = await this.listener.GetContextAsync();
-				InvokeHandler(context);
-				context.Response.Close();
-			}
-		}
-
-		/// <summary>
-		/// 调用处理方法
-		/// </summary>
-		/// <param name="context"></param>
-		private void InvokeHandler(HttpListenerContext context)
-		{
-			context.Response.StatusCode = 404;
-
-			MethodInfo methodInfo = null;
-			IHttpHandler httpHandler = null;
-			string postbody = "";
-			switch (context.Request.HttpMethod)
-			{
-				case "GET":
-					this.getHandlers.TryGetValue(context.Request.Url.AbsolutePath, out methodInfo);
-					if (methodInfo != null)
-					{
-						this.handlersMapping.TryGetValue(methodInfo, out httpHandler);
-					}
-					break;
-				case "POST":
-					this.postHandlers.TryGetValue(context.Request.Url.AbsolutePath, out methodInfo);
-					if (methodInfo != null)
-					{
-						this.handlersMapping.TryGetValue(methodInfo, out httpHandler);
-
-						using (StreamReader sr = new StreamReader(context.Request.InputStream))
-						{
-							postbody = sr.ReadToEnd();
-						}
-					}
-					break;
-				default:
-					context.Response.StatusCode = 405;
-					break;
-			}
-
-			if (httpHandler != null)
-			{
-				object[] args = InjectParameters(context, methodInfo, postbody);
-
-				// 自动把返回值,以json方式响应。
-				object resp = methodInfo.Invoke(httpHandler, args);
-
-				if (resp == null)
-				{
-					return;
-				}
-
-				using (StreamWriter sw = new StreamWriter(context.Response.OutputStream))
-				{
-					if (resp is string)
-					{
-						sw.Write(resp.ToString());
-					}
-					else
-					{
-						sw.Write(JsonHelper.ToJson(resp));
-					}
-				}
-			}
-		}
-
-		/// <summary>
-		/// 注入参数
-		/// </summary>
-		/// <param name="context"></param>
-		/// <param name="methodInfo"></param>
-		/// <param name="postbody"></param>
-		/// <returns></returns>
-		private static object[] InjectParameters(HttpListenerContext context, MethodInfo methodInfo, string postbody)
-		{
-			context.Response.StatusCode = 200;
-			ParameterInfo[] parameterInfos = methodInfo.GetParameters();
-			object[] args = new object[parameterInfos.Length];
-			for (int i = 0; i < parameterInfos.Length; i++)
-			{
-				ParameterInfo item = parameterInfos[i];
-
-				if (item.ParameterType == typeof(HttpListenerRequest))
-				{
-					args[i] = context.Request;
-					continue;
-				}
-
-				if (item.ParameterType == typeof(HttpListenerResponse))
-				{
-					args[i] = context.Response;
-					continue;
-				}
-
-				try
-				{
-					switch (context.Request.HttpMethod)
-					{
-						case "POST":
-							if (item.Name == "postBody") // 约定参数名称为postBody,只传string类型。本来是byte[],有需求可以改。
-							{
-								args[i] = postbody;
-							}
-							else if (item.ParameterType.IsClass && item.ParameterType != typeof(string) && !string.IsNullOrEmpty(postbody))
-							{
-								object entity = JsonHelper.FromJson(item.ParameterType, postbody);
-								args[i] = entity;
-							}
-
-							break;
-						case "GET":
-							string query = context.Request.QueryString[item.Name];
-							if (query != null)
-							{
-								object value = Convert.ChangeType(query, item.ParameterType);
-								args[i] = value;
-							}
-
-							break;
-						default:
-							args[i] = null;
-							break;
-					}
-				}
-				catch (Exception e)
-				{
-					Log.Error(e);
-					args[i] = null;
-				}
-			}
-
-			return args;
-		}
-
-		public override void Dispose()
-		{
-			if (this.IsDisposed)
-			{
-				return;
-			}
-
-			base.Dispose();
-
-			this.listener.Stop();
-			this.listener.Close();
-		}
-	}
+  [ObjectSystem]
+  public class HttpComponentComponentAwakeSystem : AwakeSystem<HttpComponent>
+  {
+    public override void Awake(HttpComponent self)
+    {
+      self.Awake();
+    }
+  }
+
+  [ObjectSystem]
+  public class HttpComponentComponentLoadSystem : LoadSystem<HttpComponent>
+  {
+    public override void Load(HttpComponent self)
+    {
+      self.Load();
+    }
+  }
+
+  [ObjectSystem]
+  public class HttpComponentComponentStartSystem : StartSystem<HttpComponent>
+  {
+    public override void Start(HttpComponent self)
+    {
+      self.Start();
+    }
+  }
+
+  /// <summary>
+  /// http请求分发器
+  /// </summary>
+  public class HttpComponent : Component
+  {
+    public AppType appType;
+    public HttpListener listener;
+    public HttpConfig HttpConfig;
+    public Dictionary<string, IHttpHandler> dispatcher;
+
+    // 处理方法
+    private Dictionary<MethodInfo, IHttpHandler> handlersMapping;
+
+    // Get处理
+    private Dictionary<string, MethodInfo> getHandlers;
+    private Dictionary<string, MethodInfo> postHandlers;
+
+    public void Awake()
+    {
+      StartConfig startConfig = Game.Scene.GetComponent<StartConfigComponent>().StartConfig;
+      this.appType = startConfig.AppType;
+      this.HttpConfig = startConfig.GetComponent<HttpConfig>();
+
+      this.Load();
+    }
+
+    public void Load()
+    {
+      this.dispatcher = new Dictionary<string, IHttpHandler>();
+      this.handlersMapping = new Dictionary<MethodInfo, IHttpHandler>();
+      this.getHandlers = new Dictionary<string, MethodInfo>();
+      this.postHandlers = new Dictionary<string, MethodInfo>();
+
+      Type[] types = DllHelper.GetMonoTypes();
+
+      foreach (Type type in types)
+      {
+        object[] attrs = type.GetCustomAttributes(typeof(HttpHandlerAttribute), false);
+        if (attrs.Length == 0)
+        {
+          continue;
+        }
+
+        HttpHandlerAttribute httpHandlerAttribute = (HttpHandlerAttribute)attrs[0];
+        if (!httpHandlerAttribute.AppType.Is(this.appType))
+        {
+          continue;
+        }
+
+        object obj = Activator.CreateInstance(type);
+
+        IHttpHandler ihttpHandler = obj as IHttpHandler;
+        if (ihttpHandler == null)
+        {
+          throw new Exception($"HttpHandler handler not inherit IHttpHandler class: {obj.GetType().FullName}");
+        }
+
+        this.dispatcher.Add(httpHandlerAttribute.Path, ihttpHandler);
+
+        LoadMethod(type, httpHandlerAttribute, ihttpHandler);
+      }
+    }
+
+    public void Start()
+    {
+      try
+      {
+        this.listener = new HttpListener();
+
+        if (this.HttpConfig.Url == null)
+        {
+          this.HttpConfig.Url = "";
+        }
+
+        foreach (string s in this.HttpConfig.Url.Split(';'))
+        {
+          if (s.Trim() == "")
+          {
+            continue;
+          }
+
+          this.listener.Prefixes.Add(s);
+        }
+
+        this.listener.Start();
+
+        this.Accept();
+      }
+      catch (HttpListenerException e)
+      {
+        throw new Exception($"http server error: {e.ErrorCode}", e);
+      }
+    }
+
+    public void LoadMethod(Type type, HttpHandlerAttribute httpHandlerAttribute, IHttpHandler httpHandler)
+    {
+      // 扫描这个类里面的方法
+      MethodInfo[] methodInfos = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance);
+      foreach (MethodInfo method in methodInfos)
+      {
+        object[] getAttrs = method.GetCustomAttributes(typeof(GetAttribute), false);
+        if (getAttrs.Length != 0)
+        {
+          GetAttribute get = (GetAttribute)getAttrs[0];
+
+          string path = method.Name;
+          if (!string.IsNullOrEmpty(get.Path))
+          {
+            path = get.Path;
+          }
+
+          getHandlers.Add(httpHandlerAttribute.Path + path, method);
+          //Log.Debug($"add handler[{httpHandler}.{method.Name}] path {httpHandlerAttribute.Path + path}");
+        }
+
+        object[] postAttrs = method.GetCustomAttributes(typeof(PostAttribute), false);
+        if (postAttrs.Length != 0)
+        {
+          // Post处理方法
+          PostAttribute post = (PostAttribute)postAttrs[0];
+
+          string path = method.Name;
+          if (!string.IsNullOrEmpty(post.Path))
+          {
+            path = post.Path;
+          }
+
+          postHandlers.Add(httpHandlerAttribute.Path + path, method);
+          //Log.Debug($"add handler[{httpHandler}.{method.Name}] path {httpHandlerAttribute.Path + path}");
+        }
+
+        if (getAttrs.Length == 0 && postAttrs.Length == 0)
+        {
+          continue;
+        }
+
+        handlersMapping.Add(method, httpHandler);
+      }
+    }
+
+    public async void Accept()
+    {
+      while (true)
+      {
+        if (this.IsDisposed)
+        {
+          return;
+        }
+
+        HttpListenerContext context = await this.listener.GetContextAsync();
+        await InvokeHandler(context);
+        context.Response.Close();
+      }
+    }
+
+    /// <summary>
+    /// 调用处理方法
+    /// </summary>
+    /// <param name="context"></param>
+    private async Task InvokeHandler(HttpListenerContext context)
+    {
+      context.Response.StatusCode = 404;
+
+      MethodInfo methodInfo = null;
+      IHttpHandler httpHandler = null;
+      string postbody = "";
+      switch (context.Request.HttpMethod)
+      {
+        case "GET":
+          this.getHandlers.TryGetValue(context.Request.Url.AbsolutePath, out methodInfo);
+          if (methodInfo != null)
+          {
+            this.handlersMapping.TryGetValue(methodInfo, out httpHandler);
+          }
+          break;
+        case "POST":
+          this.postHandlers.TryGetValue(context.Request.Url.AbsolutePath, out methodInfo);
+          if (methodInfo != null)
+          {
+            this.handlersMapping.TryGetValue(methodInfo, out httpHandler);
+
+            using (StreamReader sr = new StreamReader(context.Request.InputStream))
+            {
+              postbody = sr.ReadToEnd();
+            }
+          }
+          break;
+        default:
+          context.Response.StatusCode = 405;
+          break;
+      }
+
+      if (httpHandler != null)
+      {
+        object[] args = InjectParameters(context, methodInfo, postbody);
+
+        // 自动把返回值,以json方式响应。
+        object resp = methodInfo.Invoke(httpHandler, args);
+        object result = resp;
+        if (resp is Task t)
+        {
+          await t;
+          result = t.GetType().GetProperty("Result").GetValue(t, null);
+        }
+
+        if (result != null)
+        {
+          using (StreamWriter sw = new StreamWriter(context.Response.OutputStream))
+          {
+            if (result.GetType() == typeof(string))
+            {
+              sw.Write(result.ToString());
+            }
+            else
+            {
+              sw.Write(JsonHelper.ToJson(result));
+            }
+          }
+        }
+      }
+    }
+
+    /// <summary>
+    /// 注入参数
+    /// </summary>
+    /// <param name="context"></param>
+    /// <param name="methodInfo"></param>
+    /// <param name="postbody"></param>
+    /// <returns></returns>
+    private static object[] InjectParameters(HttpListenerContext context, MethodInfo methodInfo, string postbody)
+    {
+      context.Response.StatusCode = 200;
+      ParameterInfo[] parameterInfos = methodInfo.GetParameters();
+      object[] args = new object[parameterInfos.Length];
+      for (int i = 0; i < parameterInfos.Length; i++)
+      {
+        ParameterInfo item = parameterInfos[i];
+
+        if (item.ParameterType == typeof(HttpListenerRequest))
+        {
+          args[i] = context.Request;
+          continue;
+        }
+
+        if (item.ParameterType == typeof(HttpListenerResponse))
+        {
+          args[i] = context.Response;
+          continue;
+        }
+
+        try
+        {
+          switch (context.Request.HttpMethod)
+          {
+            case "POST":
+              if (item.Name == "postBody") // 约定参数名称为postBody,只传string类型。本来是byte[],有需求可以改。
+              {
+                args[i] = postbody;
+              }
+              else if (item.ParameterType.IsClass && item.ParameterType != typeof(string) && !string.IsNullOrEmpty(postbody))
+              {
+                object entity = JsonHelper.FromJson(item.ParameterType, postbody);
+                args[i] = entity;
+              }
+
+              break;
+            case "GET":
+              string query = context.Request.QueryString[item.Name];
+              if (query != null)
+              {
+                object value = Convert.ChangeType(query, item.ParameterType);
+                args[i] = value;
+              }
+
+              break;
+            default:
+              args[i] = null;
+              break;
+          }
+        }
+        catch (Exception e)
+        {
+          Log.Error(e);
+          args[i] = null;
+        }
+      }
+
+      return args;
+    }
+
+    public override void Dispose()
+    {
+      if (this.IsDisposed)
+      {
+        return;
+      }
+
+      base.Dispose();
+
+      this.listener.Stop();
+      this.listener.Close();
+    }
+  }
 }

+ 30 - 10
Server/Model/Module/Http/IHttpHandler.cs

@@ -2,15 +2,35 @@
 
 namespace ETModel
 {
-	public interface IHttpHandler
-	{
-		void Handle(HttpListenerContext context);
-	}
+  public interface IHttpHandler
+  {
+    void Handle(HttpListenerContext context);
+  }
 
-	public abstract class AHttpHandler: IHttpHandler
-	{
-		public virtual void Handle(HttpListenerContext context)
-		{
-		}
-	}
+  public abstract class AHttpHandler : IHttpHandler
+  {
+    public virtual void Handle(HttpListenerContext context)
+    {
+    }
+    public virtual HttpResult Ok(string msg = "", object data = null)
+    {
+      return new HttpResult
+      {
+        code = HttpErrorCode.Success,
+        msg = msg,
+        status = true,
+        data = data
+      };
+    }
+
+    public virtual HttpResult Error(string msg = "")
+    {
+      return new HttpResult
+      {
+        code = HttpErrorCode.Exception,
+        msg = msg,
+        status = false
+      };
+    }
+  }
 }