From 85cf6c2d089bcdda402380c860be184b3f9732e7 Mon Sep 17 00:00:00 2001 From: Elivo Date: Mon, 25 Jan 2021 22:33:32 +0800 Subject: [PATCH] =?UTF-8?q?Apewer-6.2.0=EF=BC=9A=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E4=BA=86=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95=EF=BC=8CWindowsUt?= =?UTF-8?q?ility=20=E6=9B=B4=E5=90=8D=20SystemUtility=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Apewer.Run/Batch.cs | 4 +- Apewer.Run/Process.cs | 4 +- Apewer/.editorconfig | 11 - Apewer/Apewer.csproj | 2 +- Apewer/ILogable.cs | 17 - Apewer/Internals/LogProvider.cs | 63 ---- Apewer/KernelUtility.cs | 44 ++- Apewer/LogItem.cs | 43 --- Apewer/Logger.cs | 305 +++++++++--------- Apewer/Models/StringPairs.cs | 22 ++ Apewer/NetworkUtility.cs | 34 +- Apewer/Source/MySql.cs | 66 ++-- Apewer/Source/SqlServer.cs | 83 +++-- .../{WindowsUtility.cs => SystemUtility.cs} | 29 +- Apewer/Web/ApiInternals.cs | 154 +++++---- Apewer/Web/ApiInvoker.cs | 97 +++--- Apewer/Web/ApiOptions.cs | 108 +++---- Apewer/Web/ApiResponse.cs | 67 ++-- Apewer/Web/WebUtility.cs | 80 ++--- Apewer/_ChangeLog.md | 7 + 20 files changed, 608 insertions(+), 632 deletions(-) delete mode 100644 Apewer/.editorconfig delete mode 100644 Apewer/ILogable.cs delete mode 100644 Apewer/Internals/LogProvider.cs delete mode 100644 Apewer/LogItem.cs rename Apewer/{WindowsUtility.cs => SystemUtility.cs} (96%) diff --git a/Apewer.Run/Batch.cs b/Apewer.Run/Batch.cs index db2dd87..548b91e 100644 --- a/Apewer.Run/Batch.cs +++ b/Apewer.Run/Batch.cs @@ -29,7 +29,7 @@ namespace Apewer.Run var cmd = "ffmpeg.exe"; var arg = $"-i \"{path}\" -vcodec copy -acodec copy \"{mp4path}\""; - WindowsUtility.RunConsole(cmd, arg, (s) => Console.WriteLine(s)); + SystemUtility.RunConsole(cmd, arg, (s) => Console.WriteLine(s)); continue; } @@ -40,7 +40,7 @@ namespace Apewer.Run var vttpath = Path.Combine(outdir, name.Replace(".srt", ".vtt")); var text = StorageUtility.ReadFile(path).ToText(Encoding.Default); StorageUtility.WriteFile(srtpath, text.ToBinary()); - WindowsUtility.RunConsole("ffmpeg.exe", $"-i \"{srtpath}\" \"{vttpath}\"", (s) => Console.WriteLine(s)); + SystemUtility.RunConsole("ffmpeg.exe", $"-i \"{srtpath}\" \"{vttpath}\"", (s) => Console.WriteLine(s)); continue; } } diff --git a/Apewer.Run/Process.cs b/Apewer.Run/Process.cs index 19619ce..5be123d 100644 --- a/Apewer.Run/Process.cs +++ b/Apewer.Run/Process.cs @@ -18,9 +18,9 @@ namespace Apewer.Run return; } var args = TextUtility.MergeProcessArgument(@"f:\ewq.txt", "title\nbody", "", "quote\"'quote"); - Console.WriteLine(WindowsUtility.ExecutablePath); + Console.WriteLine(SystemUtility.ExecutablePath); Console.WriteLine(args); - WindowsUtility.StartProcess(WindowsUtility.ExecutablePath, args); + SystemUtility.StartProcess(SystemUtility.ExecutablePath, args); } } diff --git a/Apewer/.editorconfig b/Apewer/.editorconfig deleted file mode 100644 index 74c5994..0000000 --- a/Apewer/.editorconfig +++ /dev/null @@ -1,11 +0,0 @@ -[*.cs] - -# CS3019: CLS 遵从性检查在此程序集外部不可见,因此不会执行它 -dotnet_diagnostic.CS3019.severity = none - -# CS0414: 字段已被赋值,但从未使用过它的值 -dotnet_diagnostic.CS0414.severity = none - -# CS0612: 已过时 -dotnet_diagnostic.CS0612.severity = none - diff --git a/Apewer/Apewer.csproj b/Apewer/Apewer.csproj index 10fa249..10f34e2 100644 --- a/Apewer/Apewer.csproj +++ b/Apewer/Apewer.csproj @@ -7,7 +7,7 @@ Apewer Apewer Apewer - 6.1.0 + 6.2.0 diff --git a/Apewer/ILogable.cs b/Apewer/ILogable.cs deleted file mode 100644 index ec7f9bb..0000000 --- a/Apewer/ILogable.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Apewer -{ - - /// 可记录日志。 - public interface ILogable - { - - /// 日志记录程序。 - Logger Logger { get; } - - } - -} diff --git a/Apewer/Internals/LogProvider.cs b/Apewer/Internals/LogProvider.cs deleted file mode 100644 index d31a907..0000000 --- a/Apewer/Internals/LogProvider.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Apewer; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; - -namespace Apewer.Internals -{ - - internal sealed class LogProvider - { - - private static bool _running = false; - private static Thread _thread = null; - private static Queue _queue = new Queue(); - - private static void Listener() - { - while (_running) - { - var item = (LogItem)null; - lock (_queue) - { - if (_queue.Count > 0) item = _queue.Dequeue(); - else - { - _running = false; - break; - } - } - - if (item == null) continue; - if (item.Logger == null) continue; - try - { - // item.Logger.Raise(item); - } - catch { } - } - } - - public static void Queue(LogItem argItem) - { - if (argItem == null) return; - lock (_queue) - { - _queue.Enqueue(argItem); - if (_thread == null) - { - _thread = new Thread(Listener); - _thread.IsBackground = false; - } - if (!_running) - { - _running = true; - _thread.Start(); - } - } - } - - } - -} diff --git a/Apewer/KernelUtility.cs b/Apewer/KernelUtility.cs index 4da155c..4b7bdcd 100644 --- a/Apewer/KernelUtility.cs +++ b/Apewer/KernelUtility.cs @@ -115,12 +115,30 @@ namespace Apewer if (milliseconds > 0) Thread.Sleep(milliseconds); } - /// + /// 在后台线程中执行,指定 Try 将忽略异常。 [MethodImpl(MethodImplOptions.NoInlining)] - public static Thread InBackground(Action action) + public static Thread InBackground(Action action, bool @try = false) { if (action == null) return null; - var thread = new Thread(delegate (object v) { ((Action)v)(); }); + Thread thread; + if (@try) + { + thread = new Thread(delegate (object v) + { + try + { + ((Action)v)(); + } + catch { } + }); + } + else + { + thread = new Thread(delegate (object v) + { + ((Action)v)(); + }); + } thread.IsBackground = true; thread.Start(action); return thread; @@ -215,13 +233,23 @@ namespace Apewer #region Application -#if NETFX || NETCORE - /// - /// System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase - /// D:\Website\ + /// 当前应用程序所在的目录。 + /// 例:D:\App 或 D:\Website /// - public static string ApplicationBasePath {get=> AppDomain.CurrentDomain.SetupInformation.ApplicationBase; } + public static string ApplicationPath + { + get + { +#if NETSTD + return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); +#else + return AppDomain.CurrentDomain.SetupInformation.ApplicationBase; +#endif + } + } + +#if NETFX || NETCORE /// 处理当前在消息队列中的所有 Windows 消息。 public static void DoEvents() => Application.DoEvents(); diff --git a/Apewer/LogItem.cs b/Apewer/LogItem.cs deleted file mode 100644 index ffd82c9..0000000 --- a/Apewer/LogItem.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Apewer; -using Apewer.Internals; -using System; -using System.Collections.Generic; -using System.Text; - -namespace Apewer -{ - - internal class LogItem - { - - private Logger _logger; - private LogType _type; - - private Exception _exception = null; - private DateTime _triggered = DateTime.Now; - private string _content = null; - private string _target = null; - private object _custom = null; - - public DateTime Triggered { get { return _triggered; } } - - internal LogType Type { get { return _type; } } - - public Logger Logger { get { return _logger; } set { _logger = value; } } - - public Exception Exception { get { return _exception; } set { _exception = value; } } - - public string Content { get { return _content; } set { _content = value ?? ""; } } - - public string Target { get { return _target; } set { _target = value ?? ""; } } - - public object Custom { get { return _custom; } set { _custom = value; } } - - internal LogItem(LogType argType) - { - _type = argType; - } - - } - -} diff --git a/Apewer/Logger.cs b/Apewer/Logger.cs index 0badc14..c7d827e 100644 --- a/Apewer/Logger.cs +++ b/Apewer/Logger.cs @@ -1,6 +1,7 @@ using Apewer.Internals; using System; using System.Collections.Generic; +using System.IO; using System.Text; using System.Threading; @@ -14,221 +15,207 @@ namespace Apewer private string _key = Guid.NewGuid().ToString("n"); private string _target = ""; private bool _enabled = true; + private Func _preoutput = null; - /// 异常。 - public event Event ExceptionEvent; - - /// 调试。 - public event Event DebugEvent; + /// 已启用。 + public virtual bool Enabled { get; set; } - /// 文本。 - public event Event TextEvent; + /// 使用控制台输出。默认值:TRUE。 + public virtual bool UseConsole { get; set; } = true; - /// 信息。 - public event Event InfomationEvent; + /// 使用日志文件。默认值:FALSE。 + public virtual bool UseFile { get; set; } = false; - /// 注意。 - public event Event WarningEvent; + /// 输出前的检查,返回值将确认输出。 + public virtual Func PreOutput { get; set; } - /// 错误。 - public event Event ErrorEvent; + /// 异常。设置处理方法以替代 UseConsole 和 UseFile。 + public virtual Event OnException { get; set; } - /// 自定义。 - public event Event CustomEvent; + /// 错误。设置处理方法以替代 UseConsole 和 UseFile。 + public virtual Event OnError { get; set; } - /// 已启用。 - public bool Enabled { get { return _enabled; } set { _enabled = value; } } + /// 注意。设置处理方法以替代 UseConsole 和 UseFile。 + public virtual Event OnWarning { get; set; } - /// 唯一标识。 - public string Key { get { return _key; } } + /// 文本。设置处理方法以替代 UseConsole 和 UseFile。 + public virtual Event OnText { get; set; } - /// 目标。 - public string Target { get { return _target; } protected set { _target = value ?? ""; } } + /// 调试。设置处理方法以替代 UseConsole 和 UseFile。 + public virtual Event OnDebug { get; set; } - internal void Invoke(LogItem item) + private void Call(string text, Event defined, Action action) { - if (item == null) return; - switch (item.Type) + var pre = PreOutput; + if (pre != null) { - case LogType.Debug: - if (DebugEvent != null) DebugEvent(this, item.Content); - break; - case LogType.Text: - if (TextEvent != null) TextEvent(this, item.Content); - break; - case LogType.Infomation: - if (InfomationEvent != null) InfomationEvent(this, item.Content); - break; - case LogType.Warning: - if (WarningEvent != null) WarningEvent(this, item.Content); - break; - case LogType.Error: - if (ErrorEvent != null) ErrorEvent(this, item.Content); - break; - case LogType.Exception: - if (ExceptionEvent != null) ExceptionEvent(this, item.Exception); - break; - case LogType.Custom: - if (CustomEvent != null) CustomEvent(this, item.Custom); - break; + var @continue = PreOutput(text); + if (!@continue) return; + } + if (defined == null) + { + if (UseConsole) ToConsole(text); + if (UseFile) ToFile(text); + } + else + { + try + { + action(text); + } + catch { } } } - /// 异常。 - public void Exception(Exception value) + /// 记录异常。 + public virtual void Exception(object sender, Exception exception) { - if (value == null) return; - var item = new LogItem(LogType.Exception); - item.Logger = this; - item.Target = _target; - try { item.Content = value.ToString(); } catch { } + string text; + if (exception == null) + { + text = "无效异常。"; + } + else + { + try { text = TextUtility.Join(" | ", exception.GetType().FullName, exception.Message); } + catch { text = "获取异常时再次引发了异常。"; } + } + Call(MergeText(true, "EXCEPTION", text), OnException, (t) => OnException(sender, exception)); } - /// 自定义。 - public void Custom(object value) + /// 记录错误。多个 Content 参数将以“ | ”分隔。 + public virtual void Error(object sender, params object[] content) { - if (value == null) return; - var item = new LogItem(LogType.Custom); - item.Logger = this; - item.Target = _target; - item.Custom = value; + Call(MergeText(true, "ERROR", content), OnError, (t) => OnError(sender, t)); } - /// 调试。 - public void Debug(string value) + /// 记录警告。多个 Content 参数将以“ | ”分隔。 + public virtual void Warning(object sender, params object[] content) { - if (value == null) return; - var item = new LogItem(LogType.Debug); - item.Logger = this; - item.Target = _target; - item.Content = value; + Call(MergeText(true, "WARNING", content), OnWarning, (t) => OnWarning(sender, t)); } - /// 文本。 - public void Text(string value) + /// 记录文本。多个 Content 参数将以“ | ”分隔。 + public virtual void Text(object sender, params object[] content) { - if (value == null) return; - var item = new LogItem(LogType.Text); - item.Logger = this; - item.Target = _target; - item.Content = value; + Call(MergeText(true, "TEXT", content), OnText, (t) => OnText(sender, t)); } - /// 信息。 - public void Infomation(string value) + /// 记录调试。多个 Content 参数将以“ | ”分隔。 + public virtual void Debug(object sender, params object[] content) { - if (value == null) return; - var item = new LogItem(LogType.Infomation); - item.Logger = this; - item.Target = _target; - item.Content = value; + Call(MergeText(true, "DEBUG", content), OnDebug, (t) => OnDebug(sender, t)); } - /// 注意。 - public void Warning(string value) - { - if (value == null) return; - var item = new LogItem(LogType.Warning); - item.Logger = this; - item.Target = _target; - item.Content = value; - } + #region 默认实列。 - /// 错误。 - public void Error(string value) - { - if (value == null) return; - var item = new LogItem(LogType.Error); - item.Logger = this; - item.Target = _target; - item.Content = value; - } + private static Logger _default = new Logger(); - private Logger(string target) - { - _target = target ?? ""; - } + /// 默认的日志记录程序,将信息写入控制台。 + public static Logger Default { get => _default; } - private static Event ExceptionDefaultCallback = null; - private static Event CustomDefaultCallback = null; - private static Event DebugDefaultCallback = null; - private static Event TextDefaultCallback = null; - private static Event InfomationDefaultCallback = null; - private static Event WarningDefaultCallback = null; - private static Event ErrorDefaultCallback = null; + #endregion - private static void Logger_ExceptionEvent(object sender, Exception value) - { - if (ExceptionDefaultCallback != null) ExceptionDefaultCallback(sender, value); - } + #region 输出。 - private static void Logger_CustomEvent(object sender, object value) - { - if (ExceptionDefaultCallback != null) CustomDefaultCallback(sender, value); - } + private static object FileLocker = new object(); + private static object ConsoleLocker = new object(); - private static void Logger_DebugEvent(object sender, string value) + private static string MergeText(bool withClock, string tag, params object[] content) { - if (ExceptionDefaultCallback != null) DebugDefaultCallback(sender, value); + var sb = new StringBuilder(); + if (withClock) + { + sb.Append(DateTimeUtility.NowLucid); + sb.Append(" "); + } + if (!string.IsNullOrEmpty(tag)) + { + sb.Append(DateTimeUtility.NowLucid); + sb.Append(tag); + sb.Append(" "); + } + sb.Append(TextUtility.Join(" | ", content)); + sb.Append("\r\n"); + return sb.ToString(); } - private static void Logger_TextEvent(object sender, string value) + /// 向控制台输出文本。 + public static void ToConsole(string text) { - throw new NotImplementedException(); + lock (ConsoleLocker) + { + if (string.IsNullOrEmpty(text)) Console.WriteLine(); + else Console.WriteLine(text); + } } - private static void Logger_InfomationEvent(object sender, string value) - { - throw new NotImplementedException(); - } + /// 向日志文件输出文本,文件名按日期自动生成。输出失败时返回错误信息。 + public static string ToFile(string text) => ToFile(text, false); - private static void Logger_WarningEvent(object sender, string value) + /// 向日志文件输出文本,文件名按日期自动生成。输出失败时返回错误信息。 + public static string ToFile(string text, bool withConsole) { - throw new NotImplementedException(); + if (withConsole) ToConsole(text); + lock (FileLocker) + { + var path = GetFileDir(); + if (string.IsNullOrEmpty(path)) + { + var msg = "写入日志文件失败:无法获取日志文件路径。"; + if (withConsole) ToConsole(msg); + return msg; + } + + var bytes = TextUtility.ToBinary(TextUtility.Merge(text, "\r\n")); + if (!StorageUtility.AppendFile(path, bytes)) + { + var msg = "写入日志文件失败。"; + if (withConsole) ToConsole(msg); + return msg; + } + } + return null; } - private static void Logger_ErrorEvent(object sender, string value) + /// 获取日志文件路径发生错误时返回 NULL 值。 + /// d:\app\log\2020-02-02.log + /// d:\www\app_data\log\2020-02-02.log + public static string GetFileDir() { + // 找到 App_Data 目录。 + var appDir = KernelUtility.ApplicationPath; + var dataDir = Path.Combine(appDir, "app_data"); + if (StorageUtility.DirectoryExists(dataDir)) appDir = dataDir; - } + // 检查 Log 目录,不存在时创建,创建失败时返回。 + var logDir = Path.Combine(appDir, "log"); + if (!StorageUtility.AssureDirectory(logDir)) return null; - private Logger(Type target) - { - try - { - if (target != null) - { - _target = target.FullName; - } - } - catch { } + // 文件不存在时创建新文件,无法创建时返回。 + var date = DateTime.Now.ToLucid(true, false, false, false); + var filePath = Path.Combine(logDir, date + ".log"); + StorageUtility.CreateFile(filePath, 0, false); + if (!StorageUtility.FileExists(filePath)) return null; + + // 返回 log 文件路径。 + return filePath; } - private Logger(object target) + /// 使用 Logger.Default 写入日志,自动添加时间和日期,多个 Content 参数将以“ | ”分隔。 + public static void Write(params object[] content) { - try + var console = Default.UseConsole; + var file = Default.UseFile; + if (console || file) { - if (target != null) - { - _target = target.GetType().FullName; - } + var text = MergeText(true, null, content); + if (console) ToConsole(text); + if (file) ToFile(text); } - catch { } } - /// 默认的日志记录程序。 - public static Logger Default(object target) - { - var logger = new Logger(target); - logger.CustomEvent += Logger_CustomEvent; - logger.ExceptionEvent += Logger_ExceptionEvent; - logger.DebugEvent += Logger_DebugEvent; - logger.TextEvent += Logger_TextEvent; - logger.InfomationEvent += Logger_InfomationEvent; - logger.WarningEvent += Logger_WarningEvent; - logger.ErrorEvent += Logger_ErrorEvent; - return logger; - } + #endregion } diff --git a/Apewer/Models/StringPairs.cs b/Apewer/Models/StringPairs.cs index b10dbf9..7ba8bdf 100644 --- a/Apewer/Models/StringPairs.cs +++ b/Apewer/Models/StringPairs.cs @@ -111,6 +111,28 @@ namespace Apewer.Models base.Sort(new Comparison>((b, a) => a.Key.CompareTo(b.Key))); } + /// 检查拥有指定的 Key。 + public bool HasKey(string key, bool ignoreCase = false) + { + var a = ignoreCase ? key.SafeLower() : key; + if (string.IsNullOrEmpty(a)) + { + foreach (var k in GetAllKeys()) + { + if (string.IsNullOrEmpty(k)) return true; + } + } + else + { + foreach (var k in GetAllKeys()) + { + var b = ignoreCase ? k.SafeLower() : k; + if (a == b) return true; + } + } + return false; + } + } } diff --git a/Apewer/NetworkUtility.cs b/Apewer/NetworkUtility.cs index ba5ae8d..b119774 100644 --- a/Apewer/NetworkUtility.cs +++ b/Apewer/NetworkUtility.cs @@ -1,11 +1,11 @@ -using Apewer; -using Apewer.Internals.Interop; +using Apewer.Internals.Interop; using Apewer.Network; using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Net; +using System.Net.NetworkInformation; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Text; @@ -406,6 +406,36 @@ namespace Apewer #endregion + #region Port + + private static List ListActivePort(IPEndPoint[] endpoints) + { + var list = new List(endpoints.Length); + foreach (var endpoint in endpoints) + { + var port = endpoint.Port; + if (list.Contains(port)) continue; + list.Add(port); + } + list.Sort(); + list.Capacity = list.Count; + return list; + } + + /// 列出活动的 TCP 端口。 + public static List ListActiveTcpPort() + { + return ListActivePort(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners()); + } + + /// 列出活动的 UDP 端口。 + public static List ListActiveUdpPort() + { + return ListActivePort(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners()); + } + + #endregion + } } diff --git a/Apewer/Source/MySql.cs b/Apewer/Source/MySql.cs index ad41c46..b06be9a 100644 --- a/Apewer/Source/MySql.cs +++ b/Apewer/Source/MySql.cs @@ -70,6 +70,28 @@ namespace Apewer.Source #endregion + #region 日志。 + + /// 获取或设置日志记录。 + public Logger Logger { get; set; } + + private void Log(Action action) + { + if (action == null) return; + var logger = Logger; +#if DEBUG + if (logger == null) logger = Logger.Default; +#endif + if (logger != null) action(logger); + } + + private void LogError(string action, Exception ex, string addtion) + { + Log((logger) => logger.Error(this, "MySQL", action, ex.GetType().FullName, ex.Message, addtion)); + } + + #endregion + #region methods private string CombineString() @@ -99,8 +121,9 @@ namespace Apewer.Source default: return false; } } - catch (Exception) + catch (Exception ex) { + LogError("Connection", ex, _connection.ConnectionString); Close(); return false; } @@ -121,9 +144,9 @@ namespace Apewer.Source public void Dispose() { Close(); } /// - public IQuery Query(string tsql, IEnumerable parameters) + public IQuery Query(string sql, IEnumerable parameters) { - if (tsql.IsBlank()) return Example.InvalidQueryStatement; + if (sql.IsBlank()) return Example.InvalidQueryStatement; const string table = "queryresult"; @@ -136,18 +159,17 @@ namespace Apewer.Source var command = new MySqlCommand(); command.Connection = _connection; command.CommandTimeout = _timeout.Query; - command.CommandText = tsql; + command.CommandText = sql; if (parameters != null) { - foreach (var parameter in parameters) + foreach (var p in parameters) { - if (parameter == null) continue; - command.Parameters.Add(parameter); + if (p != null) command.Parameters.Add(p); } } using (var ds = new DataSet()) { - using (var da = new MySqlDataAdapter(tsql, _connection)) + using (var da = new MySqlDataAdapter(sql, _connection)) { da.Fill(ds, table); query.Table = ds.Tables[table]; @@ -158,6 +180,7 @@ namespace Apewer.Source } catch (Exception exception) { + LogError("Query", exception, sql); query.Success = false; query.Exception = exception; } @@ -165,9 +188,9 @@ namespace Apewer.Source } /// - public IExecute Execute(string tsql, IEnumerable parameters) + public IExecute Execute(string sql, IEnumerable parameters) { - if (tsql.IsBlank()) return Example.InvalidExecuteStatement; + if (sql.IsBlank()) return Example.InvalidExecuteStatement; var connected = Connect(); if (!connected) return Example.InvalidExecuteConnection; @@ -180,7 +203,7 @@ namespace Apewer.Source command.Connection = _connection; command.Transaction = transaction; command.CommandTimeout = _timeout.Execute; - command.CommandText = tsql; + command.CommandText = sql; if (parameters != null) { foreach (var parameter in parameters) @@ -196,6 +219,7 @@ namespace Apewer.Source } catch (Exception exception) { + LogError("Execute", exception, sql); try { transaction.Rollback(); } catch { } execute.Success = false; execute.Exception = exception; @@ -205,13 +229,10 @@ namespace Apewer.Source } /// - public IQuery Query(string tsql) - { - return Query(tsql, null); - } + public IQuery Query(string sql) => Query(sql, null); /// - public IExecute Execute(string tsql, IEnumerable parameters) + public IExecute Execute(string sql, IEnumerable parameters) { var dps = null as List; if (parameters != null) @@ -224,14 +245,11 @@ namespace Apewer.Source dps.Add(dp); } } - return Execute(tsql, dps); + return Execute(sql, dps); } /// - public IExecute Execute(string tsql) - { - return Execute(tsql, null as IEnumerable); - } + public IExecute Execute(string sql) => Execute(sql, null as IEnumerable); #endregion @@ -456,15 +474,15 @@ namespace Apewer.Source } /// - public Result> QueryRecords(string tsql) where T : Record + public Result> QueryRecords(string sql) where T : Record { - if (tsql.IsEmpty()) return new Result>(new ArgumentException()); + if (sql.IsEmpty()) return new Result>(new ArgumentException()); try { // 解析模型,抛出 Exception。 TableStructure.ParseModel(typeof(T)); - var query = Query(tsql) as Query; + var query = Query(sql) as Query; var list = query.Fill(); query.Dispose(); return new Result>(list); diff --git a/Apewer/Source/SqlServer.cs b/Apewer/Source/SqlServer.cs index 078bf9a..5f459c2 100644 --- a/Apewer/Source/SqlServer.cs +++ b/Apewer/Source/SqlServer.cs @@ -22,7 +22,6 @@ namespace Apewer.Source #region 变量定义。 - private Logger _logger = null; private SqlConnection _db = null; private Timeout _timeout; @@ -77,10 +76,29 @@ namespace Apewer.Source #endregion - #region 实现接口。 + #region 日志。 /// 获取或设置日志记录。 - public Logger Logger { get { if (_logger == null) _logger = Logger.Default(this); return _logger; } } + public Logger Logger { get; set; } + + private void Log(Action action) + { + if (action == null) return; + var logger = Logger; +#if DEBUG + if (logger == null) logger = Logger.Default; +#endif + if (logger != null) action(logger); + } + + private void LogError(string action, Exception ex, string addtion) + { + Log((logger) => logger.Error(this, "SQL Server", action, ex.GetType().FullName, ex.Message, addtion)); + } + + #endregion + + #region 实现接口。 /// 数据库是否已经连接。 public bool Online @@ -106,7 +124,6 @@ namespace Apewer.Source } try { - Logger.Debug("Open: " + ConnectionString); _db.Open(); switch (_db.State) { @@ -116,7 +133,7 @@ namespace Apewer.Source } catch (Exception ex) { - Logger.Exception(ex); + LogError("Connection", ex, _db.ConnectionString); Close(); return false; } @@ -127,7 +144,6 @@ namespace Apewer.Source { if (_db != null) { - Logger.Debug("Close: " + ConnectionString); _db.Close(); _db.Dispose(); _db = null; @@ -145,9 +161,13 @@ namespace Apewer.Source _pass = ""; } - private IQuery PrivateQuery(string statement, IEnumerable parameters) + /// 查询。 + public IQuery Query(string sql) => Query(sql, null); + + /// 查询。 + public IQuery Query(string sql, IEnumerable parameters) { - if (string.IsNullOrWhiteSpace(statement)) return Example.InvalidQueryStatement; + if (string.IsNullOrWhiteSpace(sql)) return Example.InvalidQueryStatement; const string tablename = "queryresult"; @@ -160,7 +180,7 @@ namespace Apewer.Source var command = new SqlCommand(); command.Connection = _db; command.CommandTimeout = Timeout.Query; - command.CommandText = statement; + command.CommandText = sql; if (parameters != null) { foreach (var parameter in parameters) @@ -170,7 +190,7 @@ namespace Apewer.Source } using (var dataset = new DataSet()) { - using (var dataadapter = new SqlDataAdapter(statement, _db)) + using (var dataadapter = new SqlDataAdapter(sql, _db)) { dataadapter.Fill(dataset, tablename); query.Table = dataset.Tables[tablename]; @@ -181,17 +201,20 @@ namespace Apewer.Source } catch (Exception exception) { - Logger.Exception(exception); + LogError("Query", exception, sql); query.Success = false; query.Exception = exception; } return query; } + /// 执行。 + public IExecute Execute(string sql) => Execute(sql, null); + /// 执行单条 Transact-SQL 语句,并加入参数。 - public IExecute PrivateExecute(string statement, IEnumerable parameters) + public IExecute Execute(string sql, IEnumerable parameters) { - if (string.IsNullOrWhiteSpace(statement)) return Example.InvalidExecuteStatement; + if (string.IsNullOrWhiteSpace(sql)) return Example.InvalidExecuteStatement; var connected = Connect(); if (!connected) return Example.InvalidExecuteConnection; @@ -204,7 +227,7 @@ namespace Apewer.Source command.Connection = _db; command.Transaction = transaction; command.CommandTimeout = Timeout.Execute; - command.CommandText = statement; + command.CommandText = sql; if (parameters != null) { foreach (var parameter in parameters) @@ -220,7 +243,7 @@ namespace Apewer.Source catch (Exception exception) { try { transaction.Rollback(); } catch { } - Logger.Exception(exception); + LogError("Execute", exception, sql); execute.Success = false; execute.Exception = exception; } @@ -228,32 +251,6 @@ namespace Apewer.Source return execute; } - /// 查询。 - public IQuery Query(string statement) - { - return PrivateQuery(statement, null); - } - - /// 查询。 - public IQuery Query(string statement, IEnumerable parameters) - { - if (parameters == null) return Example.InvalidQueryParameters; - return PrivateQuery(statement, parameters); - } - - /// 执行。 - public IExecute Execute(string statement) - { - return PrivateExecute(statement, null); - } - - /// 执行。 - public IExecute Execute(string statement, IEnumerable parameters) - { - if (parameters == null) return Example.InvalidExecuteParameters; - return PrivateExecute(statement, parameters); - } - #endregion #region 属性。 @@ -582,9 +579,9 @@ namespace Apewer.Source } /// 获取按指定语句查询到的所有记录。 - public Result> Query(string statement) where T : Record + public Result> Query(string sql) where T : Record { - var query = (Query)Query(statement); + var query = (Query)Query(sql); if (query.Exception == null) { var list = query.Fill(); diff --git a/Apewer/WindowsUtility.cs b/Apewer/SystemUtility.cs similarity index 96% rename from Apewer/WindowsUtility.cs rename to Apewer/SystemUtility.cs index c875e9c..29250cb 100644 --- a/Apewer/WindowsUtility.cs +++ b/Apewer/SystemUtility.cs @@ -22,9 +22,34 @@ namespace Apewer { /// Windows 实用工具。 - public class WindowsUtility + public class SystemUtility { + #region 系统信息。 + + private static int CheckOsType() + { +#if NETFX + return 1; +#else + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return 1; + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return 2; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return 3; + return 0; +#endif + } + + /// 当前操作系统是 Windows。 + public static bool IsWindows { get => CheckOsType() == 1; } + + /// 当前操作系统是 OS X 或 macOS。 + public static bool IsOSX { get => CheckOsType() == 2; } + + /// 当前操作系统是 Linux。 + public static bool IsLinux { get => CheckOsType() == 3; } + + #endregion + #region 进程。 #if NETFX @@ -682,7 +707,7 @@ namespace Apewer #endregion - #region COM + #region Windows COM #if NETFX diff --git a/Apewer/Web/ApiInternals.cs b/Apewer/Web/ApiInternals.cs index f53dee7..e63ec73 100644 --- a/Apewer/Web/ApiInternals.cs +++ b/Apewer/Web/ApiInternals.cs @@ -404,66 +404,83 @@ namespace Apewer.Web response.RedirectUrl = url; } - internal static string ExportJson(ApiResponse response, bool indented = true, bool exception = false) + internal static string ExportJson(ApiResponse response) { if (response == null) return "{}"; + var json = Json.NewObject(); - json.SetProperty("beginning", response.Beginning ?? TextUtility.EmptyString); - json.SetProperty("ending", response.Ending ?? TextUtility.EmptyString); - json.SetProperty("random", response.Random); - json.SetProperty("application", response.Application); - json.SetProperty("function", response.Function); - json.SetProperty("status", (TextUtility.IsBlank(response.Status) ? TextUtility.EmptyString : response.Status.ToLower())); - json.SetProperty("message", response.Message); - if (exception) + + // 执行时间。 + if (ApiOptions.WithClock) { - if (response.Exception == null) json.SetProperty("exception"); - else - { - var exmessage = null as string; - var exstacktrace = null as string; - var exsource = null as string; - var exhelplink = null as string; - try - { - exmessage = response.Exception.Message; - exstacktrace = response.Exception.StackTrace; - exsource = response.Exception.Source; - exhelplink = response.Exception.HelpLink; - } - catch { } + json.SetProperty("clock", response.Ending.ToLucid()); + } - var exjson = Json.NewObject(); - exjson.SetProperty("type", response.Exception.GetType().FullName); - exjson.SetProperty("message", exmessage); - exjson.SetProperty("stack", Json.Parse((exstacktrace ?? "").Replace("\r", "").Split('\n'), false)); - exjson.SetProperty("source", exsource); - exjson.SetProperty("helplink", exhelplink); + // 持续时间。 + if (ApiOptions.WithDuration) + { + var seconds = Math.Floor((response.Ending - response.Beginning).TotalMilliseconds) / 1000D; + json.SetProperty("duration", seconds); + } - if (response.Exception is System.Net.WebException) - { - var webex = response.Exception as System.Net.WebException; - - { - var array = Json.NewArray(); - foreach (var k in webex.Data.Keys) - { - var item = Json.NewObject(); - var v = webex.Data[k]; - item.SetProperty(k.ToString(), Json.Parse(v.ToString())); - } - exjson.SetProperty("data", array); - } - - exjson.SetProperty("", Json.Parse(webex.Response, true)); - } + // 随机值。 + if (response.Random.NotEmpty()) json.SetProperty("random", response.Random); + + // 调用。 + if (ApiOptions.WithTarget) + { + json.SetProperty("application", response.Application); + json.SetProperty("function", response.Function); + } + + // 状态。 + json.SetProperty("status", (TextUtility.IsBlank(response.Status) ? TextUtility.EmptyString : response.Status.ToLower())); + json.SetProperty("message", response.Message); - json.SetProperty("exception", exjson); + // Ticket。 + if (response.Ticket != null) json.SetProperty("ticket", response.Ticket); + + // 异常。 + if (ApiOptions.AllowException && response.Exception != null) + { + try + { + var exMessage = null as string; + var exStackTrace = null as string; + var exSource = null as string; + var exHelpLink = null as string; + + exMessage = response.Exception.Message; + exStackTrace = response.Exception.StackTrace; + exSource = response.Exception.Source; + exHelpLink = response.Exception.HelpLink; + + // Exception 对象的主要属性。 + var exJson = Json.NewObject(); + exJson.SetProperty("type", response.Exception.GetType().FullName); + exJson.SetProperty("message", exMessage); + exJson.SetProperty("stack", Json.Parse((exStackTrace ?? "").Replace("\r", "").Split('\n'), false)); + exJson.SetProperty("source", exSource); + exJson.SetProperty("helplink", exHelpLink); + + // WebException 附加数据。 + var webex = response.Exception as System.Net.WebException; + if (webex != null) exJson.SetProperty("data", Json.Parse(webex.Data)); + + json.SetProperty("exception", exJson); + } + catch (Exception ex) + { + var exJson = Json.NewObject(); + exJson.SetProperty("message", TextUtility.Merge("设置 Exception 时再次发生异常:", ex.Message)); + json.SetProperty("exception", exJson); } } + + // 用户数据。 json.SetProperty("data", response.Data); - var text = json.ToString(indented); + var text = json.ToString(ApiOptions.JsonIndent); return text; } @@ -497,6 +514,16 @@ namespace Apewer.Web } } + public static void SetCORS(HttpResponse response) + { + AddHeader(response, "Access-Control-Allow-Headers", "Content-Type"); + AddHeader(response, "Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + AddHeader(response, "Access-Control-Allow-Origin", "*"); + + var maxage = ApiOptions.AccessControlMaxAge; + if (maxage > 0) AddHeader(response, "Access-Control-Max-Age", maxage.ToString()); + } + #if NETFX private static FieldInfo CacheControlField = null; @@ -516,9 +543,7 @@ namespace Apewer.Web #endif /// 设置缓存时间,单位为秒,最大为 2592000 秒(30 天)。 - public static void SetCacheControl - - (HttpResponse response, int seconds = 0) + public static void SetCacheControl(HttpResponse response, int seconds = 0) { if (response == null) return; var s = seconds; @@ -527,14 +552,20 @@ namespace Apewer.Web #if NETFX if (s > 0) { - SetCacheControlField(response, $"public, max-age={s}, s-maxage={s}"); + response.CacheControl = "public"; + response.Cache.SetCacheability(HttpCacheability.Public); + response.Cache.SetMaxAge(TimeSpan.FromSeconds(seconds)); + response.Cache.SetProxyMaxAge(TimeSpan.FromSeconds(seconds)); + response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches); } else { var minutes = s < 60 ? 0 : (s / 60); - try { response.CacheControl = "no-cache"; } catch { } - try { response.Expires = minutes; } catch { } - AddHeader(response, "Pragma", "no-cache"); + response.CacheControl = "no-cache"; + response.Cache.SetCacheability(HttpCacheability.NoCache); + response.Cache.SetNoStore(); + // try { response.Expires = minutes; } catch { } + // AddHeader(response, "Pragma", "no-cache"); } #else if (s > 0) @@ -545,8 +576,8 @@ namespace Apewer.Web { var minutes = s < 60 ? 0 : (s / 60); AddHeader(response, "Cache-Control", "no-cache, no-store, must-revalidate"); - AddHeader(response, "Expires", minutes.ToString()); - AddHeader(response, "Pragma", "no-cache"); + // AddHeader(response, "Expires", minutes.ToString()); + // AddHeader(response, "Pragma", "no-cache"); } #endif } @@ -560,6 +591,13 @@ namespace Apewer.Web response.ContentType = string.IsNullOrEmpty(value) ? plain : value; } + public static void SetContentType(HttpResponse response, string value) + { + var text = value.SafeTrim(); + if (text.IsEmpty()) text = "application/octet-stream"; + response.ContentType = text; + } + public static void SetContentLength(HttpResponse response, long value) { if (response == null) return; diff --git a/Apewer/Web/ApiInvoker.cs b/Apewer/Web/ApiInvoker.cs index 34b4f66..3c4710f 100644 --- a/Apewer/Web/ApiInvoker.cs +++ b/Apewer/Web/ApiInvoker.cs @@ -1,6 +1,5 @@ #if NETFX || NETCORE -using Apewer; using Apewer.Network; using System; using System.Collections.Generic; @@ -14,8 +13,6 @@ using System.Web; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; -using System.IO; -using System.Threading.Tasks; #endif namespace Apewer.Web @@ -64,20 +61,6 @@ namespace Apewer.Web return true; } - private bool CheckMethod() - { - var method = WebUtility.GetMethod(Context.Request); - switch (method) - { - case HttpMethod.GET: - case HttpMethod.POST: - return true; - case HttpMethod.OPTIONS: - default: - return false; // 返回空内容; - } - } - private bool CheckFavIcon() { if (ApiOptions.AllowFavIcon) return true; @@ -122,16 +105,36 @@ namespace Apewer.Web // 检查依赖的属性,若不通过,则无法执行。 if (!CheckDependents()) return "缺少必要属性。"; + // 检查方法。 + var method = WebUtility.GetMethod(Context.Request); + switch (method) + { + case HttpMethod.GET: + case HttpMethod.POST: + break; + case HttpMethod.OPTIONS: + if (ApiOptions.WithAccessControl) ApiInternals.SetCORS(Context.Response); + return ""; + default: + return "已阻止方法。"; + } + // 过滤请求。 - if (!CheckMethod()) return "已阻止方法。"; - if (!CheckFavIcon()) return "已阻止请求 favicon.ico 路径。"; - if (!CheckRobot()) return "已阻止请求 robot.txt 路径。"; + if (!CheckFavIcon()) + { + if (ApiOptions.WithAccessControl) ApiInternals.SetCORS(Context.Response); + return "已阻止请求 favicon.ico 路径。"; + } + if (!CheckRobot()) + { + if (ApiOptions.WithAccessControl) ApiInternals.SetCORS(Context.Response); + return "已阻止请求 robot.txt 路径。"; + } // 准备变量。 ApiRequest = ApiInternals.GetRequest(Context.Request); ApiRequest.Context = Context; ApiResponse = new ApiResponse(); - ApiResponse.Context = Context; // 准备向 Controller 传递的属性。 var an = ApiRequest.Application; @@ -159,22 +162,22 @@ namespace Apewer.Web else { ApiResponse.Error("未指定有效的 Function 名称。"); - if (fn.IsEmpty() && ApiOptions.AllowNumerate) Enumerate(ae.Functions); + if (fn.IsEmpty() && ApiOptions.AllowEnumerate) Enumerate(ae.Functions); } } } else { ApiResponse.Error("未指定有效的 Application 名称。"); - if (an.IsEmpty() && ApiOptions.AllowNumerate) Enumerate(Entries); + if (an.IsEmpty() && ApiOptions.AllowEnumerate) Enumerate(Entries); } // 记录结束时间。 Ending = DateTime.Now; // 调整响应。 - ApiResponse.Beginning = Beginning.ToLucid(); - ApiResponse.Ending = Ending.ToLucid(); + ApiResponse.Beginning = Beginning; + ApiResponse.Ending = Ending; ApiResponse.Application = an; ApiResponse.Function = fn; ApiResponse.Random = r; @@ -195,8 +198,8 @@ namespace Apewer.Web var item = Json.NewObject(); item["name"] = i.Value.Name; item["caption"] = i.Value.Caption; - if (ApiOptions.ShowModule) item["module"] = i.Value.Module; - if (ApiOptions.ShowClass) item["class"] = i.Value.Type.FullName; + if (ApiOptions.WithModuleName) item["module"] = i.Value.Module; + if (ApiOptions.WithTypeName) item["type"] = i.Value.Type.FullName; list.AddItem(item); count = count + 1; } @@ -301,6 +304,18 @@ namespace Apewer.Web { var response = Context.Response; + // Ticket。 + if (ApiRequest.Method == HttpMethod.GET) + { + if (ApiResponse.Ticket != null && !ApiResponse.Cookies.HasKey("ticket", true)) + { + ApiResponse.Cookies.Add("ticket", ApiResponse.Ticket); + } + } + + // AccessControl。 + if (ApiOptions.WithAccessControl) ApiInternals.SetCORS(Context.Response); + // 设置自定义头。 ApiInternals.AddHeaders(response, ApiResponse.Headers); var setCookies = WebUtility.SetCookies(response, ApiResponse.Cookies); @@ -320,7 +335,7 @@ namespace Apewer.Web { case ApiFormat.Json: { - var text = ApiInternals.ExportJson(ApiResponse, ApiOptions.JsonIndent, ApiOptions.AllowException); + var text = ApiInternals.ExportJson(ApiResponse); var data = TextUtility.ToBinary(text); ApiInternals.SetTextPlain(response); ApiInternals.SetContentLength(response, data.LongLength); @@ -515,28 +530,28 @@ namespace Apewer.Web return invoker.Run(); } + /// 运行 Kestrel 服务器,可指定端口和最大请求 Body 长度。默认同步运行,阻塞当前线程。 /// 注意:启动前应使用 SetKestrelEntries 方法设置 Kestrel Entries。 public static IHost RunKestrel(int port = 80, int request = 1073741824, bool async = false) { if (KestrelEntries == null) SetKestrelEntries(Assembly.GetCallingAssembly()); - var builder1 = Host.CreateDefaultBuilder(); - var builder2 = builder1.ConfigureWebHostDefaults((builder3) => + var builder = Host.CreateDefaultBuilder(); + var builder2 = builder.ConfigureWebHostDefaults((b) => { - var builder4 = builder3.ConfigureKestrel((options) => - { - options.ListenAnyIP(port); - options.AllowSynchronousIO = true; - if (request > 0) options.Limits.MaxRequestBodySize = request; - }); - var builder5 = builder4.UseStartup(); + b.ConfigureKestrel((options) => + { + options.ListenAnyIP(port); + options.AllowSynchronousIO = true; + if (request > 0) options.Limits.MaxRequestBodySize = request; + }).UseStartup(); }); - var built = builder2.Build(); + var host = builder2.Build(); - if (async) built.RunAsync(); - else built.Run(); - return built; + if (async) host.RunAsync(); + else host.Run(); + return host; } #endif diff --git a/Apewer/Web/ApiOptions.cs b/Apewer/Web/ApiOptions.cs index b19c0a1..f84a98c 100644 --- a/Apewer/Web/ApiOptions.cs +++ b/Apewer/Web/ApiOptions.cs @@ -9,67 +9,53 @@ namespace Apewer.Web public static class ApiOptions { -#if DEBUG - private static bool _allowexception = true; - private static bool _jsonindent = true; -#else - private static bool _allowexception = false; - private static bool _jsonindent = false; -#endif - - private static bool _allowfavicon = false; - private static bool _allowrobot = false; - private static bool _allowenumerate = true; - private static bool _showmodule = false; - private static bool _showclass = false; - private static int _port = 80; - - /// - /// 允许 Invoker 解析 favicon.ico 请求。 - /// 默认值:不允许,响应空。 - public static bool AllowFavIcon { get { return _allowfavicon; } set { _allowfavicon = value; } } - - /// - /// 允许 Invoker 解析 robot.txt 请求。 - /// 默认值:不允许,拒绝搜索引擎收录根目录。 - /// - public static bool AllowRobot { get { return _allowrobot; } set { _allowrobot = value; } } - - /// - /// 允许 Invoker 枚举输出 Applications 或 Functions。 - /// 默认值:允许,输出列表。 - /// - public static bool AllowNumerate { get { return _allowenumerate; } set { _allowenumerate = value; } } - - /// - /// 允许 Invoker 输出 Exception。 - /// 默认值:允许,输出 Exception 对象的属性。 - /// - public static bool AllowException { get { return _allowexception; } set { _allowexception = value; } } - - /// - /// 允许 Invoker 输出的 Json 对象缩进。 - /// 默认值:不允许,不缩进。 - /// - public static bool JsonIndent { get { return _jsonindent; } set { _jsonindent = value; } } - - /// - /// 允许 Invoker 输出 Application 列表时包含模块信息。 - /// 默认值:不允许。 - /// - public static bool ShowModule { get { return _showmodule; } set { _showmodule = value; } } - - /// - /// 允许 Invoker 输出 Application 列表时包含类型信息。 - /// 默认值:不允许。 - /// - public static bool ShowClass { get { return _showclass; } set { _showclass = value; } } - - /// - /// 获取或设置站点的端口,范围为 0 ~ 65535。 - /// 默认值:80。 - /// - public static int Port { get { return _port; } set { _port = value < 0 ? 0 : (value > 65535 ? 65535 : value); } } + /// 允许 Invoker 解析 favicon.ico 请求。 + /// 默认值:不允许,响应空。 + public static bool AllowFavIcon { get; set; } = false; + + /// 允许 Invoker 解析 robot.txt 请求。 + /// 默认值:不允许,拒绝搜索引擎收录根目录。 + public static bool AllowRobot { get; set; } = false; + + /// 允许 Invoker 枚举输出 Applications 或 Functions。 + /// 默认值:允许,输出列表。 + public static bool AllowEnumerate { get; set; } = true; + + /// 允许 Invoker 输出 Exception 对象的属性。 + /// 默认值:不允许输出。 + public static bool AllowException { get; set; } = false; + + /// 允许 Invoker 输出的 Json 对象缩进。 + /// 默认值:不缩进。 + public static bool JsonIndent { get; set; } = false; + + /// 允许 Invoker 输出 Application 列表时包含模块名称。 + /// 默认值:不包含。 + public static bool WithModuleName { get; set; } = false; + + /// 允许 Invoker 输出 Application 列表时包含类型名称。 + /// 默认值:不包含。 + public static bool WithTypeName { get; set; } = false; + + /// 在响应中包含时间属性。 + /// 默认值:不包含。 + public static bool WithClock { get; set; } = false; + + /// 在响应中包含执行 API 的持续时间。 + /// 默认值:包含。 + public static bool WithDuration { get; set; } = true; + + /// 在响应中包含 Application 和 Function 属性。 + /// 默认值:不包含。 + public static bool WithTarget { get; set; } = false; + + /// 在响应中包含 Access-Control 属性。 + /// 默认值:包含。 + public static bool WithAccessControl { get; set; } = true; + + /// 设置 Access-Control-Max-Age 的值。 + /// 默认值:60。 + public static int AccessControlMaxAge { get; set; } = 60; } diff --git a/Apewer/Web/ApiResponse.cs b/Apewer/Web/ApiResponse.cs index 5cd5f9e..552bf04 100644 --- a/Apewer/Web/ApiResponse.cs +++ b/Apewer/Web/ApiResponse.cs @@ -1,8 +1,5 @@ -using Apewer; -using Apewer.Models; +using Apewer.Models; using System; -using System.Collections.Generic; -using System.Collections.Specialized; using System.IO; namespace Apewer.Web @@ -14,29 +11,17 @@ namespace Apewer.Web { private Json _data = Json.NewObject(); + internal DateTime Beginning; + internal DateTime Ending; + internal ApiFormat Type = ApiFormat.Json; + internal Exception Exception; - /// HTTP 头。 + /// 头。 public StringPairs Headers { get; set; } = new StringPairs(); /// Cookies。 public StringPairs Cookies { get; set; } = new StringPairs(); -#if NETFX - internal System.Web.HttpContext Context { get; set; } -#endif - -#if NETCORE - internal Microsoft.AspNetCore.Http.HttpContext Context { get; set; } -#endif - - #region ApiInvoker。 - - /// 开始时间。 - public string Beginning { get; set; } - - /// 结束时间。 - public string Ending { get; set; } - /// Application。 public string Application { get; set; } @@ -46,63 +31,61 @@ namespace Apewer.Web /// Random。 public string Random { get; set; } - #endregion - - #region API 功能。 - /// 状态。 public string Status { get; set; } /// 消息。 public string Message { get; set; } - /// Data。 - public Json Data { get { if (_data == null) _data = Json.NewObject(); return _data; } set { _data = value; } } - - internal ApiFormat Type = ApiFormat.Json; - internal Exception Exception { get; set; } + /// 获取或设置 Ticket。 + public string Ticket { get; set; } /// 设置缓存过期时间,单位为秒。默认值:0,立即过期,不缓存。 /// 在 .NET Framework 中,此设置可能无效。 public int Expires { get; set; } - #endregion + /// 自定义数据。 + public Json Data + { + get { return _data; } + set { _data = value ?? Json.NewObject(); } + } #region 输出纯文本。 - internal string TextString { get; set; } + internal string TextString; - internal string TextType { get; set; } + internal string TextType; #endregion #region 输出字节数组。 - internal Stream BinaryStream { get; set; } + internal Stream BinaryStream; - internal byte[] BinaryBytes { get; set; } + internal byte[] BinaryBytes; - internal string BinaryType { get; set; } + internal string BinaryType; #endregion #region 输出文件。 - internal Stream FileStream { get; set; } + internal Stream FileStream; - internal byte[] FileBytes { get; set; } + internal byte[] FileBytes; - internal string FileType { get; set; } + internal string FileType; - internal string FileName { get; set; } + internal string FileName; #endregion #region 重定向。 - internal string RedirectCode { get; set; } + internal string RedirectCode; - internal string RedirectUrl { get; set; } + internal string RedirectUrl; #endregion diff --git a/Apewer/Web/WebUtility.cs b/Apewer/Web/WebUtility.cs index 24020e1..77a676e 100644 --- a/Apewer/Web/WebUtility.cs +++ b/Apewer/Web/WebUtility.cs @@ -23,8 +23,6 @@ namespace Apewer.Web public static class WebUtility { - #region url - /// 按 & 拆分多个参数。 public static StringPairs ParseParameters(string query, bool decode = true) { @@ -109,10 +107,19 @@ namespace Apewer.Web return url == null ? null : GetParameter(url.Query, names); } - #endregion - #if NETFX || NETCORE + /// 获取程序目录的路径。 + public static string AppDirectory + { + get + { + // AppDomain.CurrentDomain.BaseDirectory + // AppDomain.CurrentDomain.SetupInformation.ApplicationBase + return AppDomain.CurrentDomain.SetupInformation.ApplicationBase; + } + } + #region http /// 获取直连端的 IP。 @@ -448,6 +455,21 @@ namespace Apewer.Web return null; } + /// 从 Response 头中移除 Server 属性。 + public static void RemoveServer(HttpResponse response) + { + if (response == null) return; + +#if NETFX + var keys = new List(response.Headers.AllKeys); +#else + var keys = new List(response.Headers.Keys); +#endif + + if (keys.Contains("Server")) response.Headers.Remove("Server"); + if (keys.Contains("X-Powered-By")) response.Headers.Remove("X-Powered-By"); + } + #endregion #region api @@ -602,55 +624,7 @@ namespace Apewer.Web } #endregion - - #region Log - - private static object LogLocker = new object(); - - /// 获取日志文件路径发生错误时返回 NULL 值。 - /// d:\app\log\2020-02-02.log - /// d:\www\app_data\log\2020-02-02.log - public static string GetLogPath() - { - // 找到 App_Data 目录。 - var appDir = KernelUtility.ApplicationBasePath; - var dataDir = Path.Combine(appDir, "app_data"); - if (StorageUtility.DirectoryExists(dataDir)) appDir = dataDir; - - // 检查 Log 目录,不存在时创建,创建失败时返回。 - var logDir = Path.Combine(appDir, "log"); - if (!StorageUtility.AssureDirectory(logDir)) return null; - - // 文件不存在时创建新文件,无法创建时返回。 - var date = DateTime.Now.ToLucid(true, false, false, false); - var filePath = Path.Combine(logDir, date + ".log"); - StorageUtility.CreateFile(filePath, 0, false); - if (!StorageUtility.FileExists(filePath)) return null; - - // 返回 log 文件路径。 - return filePath; - } - - /// 写入日志。 - public static string WriteLog(params object[] content) - { - lock (LogLocker) - { - var path = GetLogPath(); - if (path.IsEmpty()) return "无法获取日志文件路径。"; - - var text = TextUtility.Join(" | ", content); - text = TextUtility.Merge(DateTimeUtility.NowLucid, " ", text, "\r\n"); - - var bytes = TextUtility.ToBinary(text); - if (!StorageUtility.AppendFile(path, bytes)) return "写日志文件失败。"; - - return null; - } - } - - #endregion - + #region Cookies /// 获取 Cookies。 diff --git a/Apewer/_ChangeLog.md b/Apewer/_ChangeLog.md index 2a441de..48a808c 100644 --- a/Apewer/_ChangeLog.md +++ b/Apewer/_ChangeLog.md @@ -5,6 +5,13 @@ ### 最新提交 +### 6.2.0 +- Logger:重写了功能,统一日志的调用方法; +- NetworkUtility:新增检测 TCP 端口 UDP 端口的方法; +- StringPairs:支持检查 Key 是否存在; +- WebAPI:优化参数和流程,提高运行效率,并增加定制选项; +- WindowsUtility:更名为 SystemUtility,增加判断操作系统的方法。 + ### 6.1.0 - DateTime:修正了超出范围值的问题; - TextUtility:新增 RenderMarkdown 方法,支持将 Markdown 文本转换为 HTML 文本;