Browse Source

Apewer-6.2.0:统一了日志记录,WindowsUtility 更名 SystemUtility。

dev
Elivo 4 years ago
parent
commit
85cf6c2d08
  1. 4
      Apewer.Run/Batch.cs
  2. 4
      Apewer.Run/Process.cs
  3. 11
      Apewer/.editorconfig
  4. 2
      Apewer/Apewer.csproj
  5. 17
      Apewer/ILogable.cs
  6. 63
      Apewer/Internals/LogProvider.cs
  7. 44
      Apewer/KernelUtility.cs
  8. 43
      Apewer/LogItem.cs
  9. 305
      Apewer/Logger.cs
  10. 22
      Apewer/Models/StringPairs.cs
  11. 34
      Apewer/NetworkUtility.cs
  12. 66
      Apewer/Source/MySql.cs
  13. 83
      Apewer/Source/SqlServer.cs
  14. 29
      Apewer/SystemUtility.cs
  15. 154
      Apewer/Web/ApiInternals.cs
  16. 97
      Apewer/Web/ApiInvoker.cs
  17. 108
      Apewer/Web/ApiOptions.cs
  18. 67
      Apewer/Web/ApiResponse.cs
  19. 80
      Apewer/Web/WebUtility.cs
  20. 7
      Apewer/_ChangeLog.md

4
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;
}
}

4
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);
}
}

11
Apewer/.editorconfig

@ -1,11 +0,0 @@
[*.cs]
# CS3019: CLS 遵从性检查在此程序集外部不可见,因此不会执行它
dotnet_diagnostic.CS3019.severity = none
# CS0414: 字段已被赋值,但从未使用过它的值
dotnet_diagnostic.CS0414.severity = none
# CS0612: 已过时
dotnet_diagnostic.CS0612.severity = none

2
Apewer/Apewer.csproj

@ -7,7 +7,7 @@
<AssemblyName>Apewer</AssemblyName>
<PackageId>Apewer</PackageId>
<Title>Apewer</Title>
<Version>6.1.0</Version>
<Version>6.2.0</Version>
</PropertyGroup>
<!-- Info: Copyright -->

17
Apewer/ILogable.cs

@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer
{
/// <summary>可记录日志。</summary>
public interface ILogable
{
/// <summary>日志记录程序。</summary>
Logger Logger { get; }
}
}

63
Apewer/Internals/LogProvider.cs

@ -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<LogItem> _queue = new Queue<LogItem>();
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();
}
}
}
}
}

44
Apewer/KernelUtility.cs

@ -115,12 +115,30 @@ namespace Apewer
if (milliseconds > 0) Thread.Sleep(milliseconds);
}
/// <summary></summary>
/// <summary>在后台线程中执行,指定 Try 将忽略异常。</summary>
[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
/// <summary>
/// <para>System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase</para>
/// <para>D:\Website\</para>
/// <para>当前应用程序所在的目录。</para>
/// <para>例:D:\App 或 D:\Website</para>
/// </summary>
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
/// <summary>处理当前在消息队列中的所有 Windows 消息。</summary>
public static void DoEvents() => Application.DoEvents();

43
Apewer/LogItem.cs

@ -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;
}
}
}

305
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<string, bool> _preoutput = null;
/// <summary>异常。</summary>
public event Event<Exception> ExceptionEvent;
/// <summary>调试。</summary>
public event Event<string> DebugEvent;
/// <summary>已启用。</summary>
public virtual bool Enabled { get; set; }
/// <summary>文本。</summary>
public event Event<string> TextEvent;
/// <summary>使用控制台输出。默认值:TRUE。</summary>
public virtual bool UseConsole { get; set; } = true;
/// <summary>信息。</summary>
public event Event<string> InfomationEvent;
/// <summary>使用日志文件。默认值:FALSE。</summary>
public virtual bool UseFile { get; set; } = false;
/// <summary>注意。</summary>
public event Event<string> WarningEvent;
/// <summary>输出前的检查,返回值将确认输出。</summary>
public virtual Func<string, bool> PreOutput { get; set; }
/// <summary>错误。</summary>
public event Event<string> ErrorEvent;
/// <summary>异常。设置处理方法以替代 UseConsole 和 UseFile。</summary>
public virtual Event<Exception> OnException { get; set; }
/// <summary>自定义。</summary>
public event Event<object> CustomEvent;
/// <summary>错误。设置处理方法以替代 UseConsole 和 UseFile。</summary>
public virtual Event<string> OnError { get; set; }
/// <summary>已启用。</summary>
public bool Enabled { get { return _enabled; } set { _enabled = value; } }
/// <summary>注意。设置处理方法以替代 UseConsole 和 UseFile。</summary>
public virtual Event<string> OnWarning { get; set; }
/// <summary>唯一标识。</summary>
public string Key { get { return _key; } }
/// <summary>文本。设置处理方法以替代 UseConsole 和 UseFile。</summary>
public virtual Event<string> OnText { get; set; }
/// <summary>目标。</summary>
public string Target { get { return _target; } protected set { _target = value ?? ""; } }
/// <summary>调试。设置处理方法以替代 UseConsole 和 UseFile。</summary>
public virtual Event<string> OnDebug { get; set; }
internal void Invoke(LogItem item)
private void Call<T>(string text, Event<T> defined, Action<string> 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 { }
}
}
/// <summary>异常。</summary>
public void Exception(Exception value)
/// <summary>记录异常。</summary>
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));
}
/// <summary>自定义。</summary>
public void Custom(object value)
/// <summary>记录错误。多个 Content 参数将以“ | ”分隔。</summary>
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));
}
/// <summary>调试。</summary>
public void Debug(string value)
/// <summary>记录警告。多个 Content 参数将以“ | ”分隔。</summary>
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));
}
/// <summary>文本。</summary>
public void Text(string value)
/// <summary>记录文本。多个 Content 参数将以“ | ”分隔。</summary>
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));
}
/// <summary>信息。</summary>
public void Infomation(string value)
/// <summary>记录调试。多个 Content 参数将以“ | ”分隔。</summary>
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));
}
/// <summary>注意。</summary>
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 默认实列。
/// <summary>错误。</summary>
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 ?? "";
}
/// <summary>默认的日志记录程序,将信息写入控制台。</summary>
public static Logger Default { get => _default; }
private static Event<Exception> ExceptionDefaultCallback = null;
private static Event<object> CustomDefaultCallback = null;
private static Event<string> DebugDefaultCallback = null;
private static Event<string> TextDefaultCallback = null;
private static Event<string> InfomationDefaultCallback = null;
private static Event<string> WarningDefaultCallback = null;
private static Event<string> 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)
/// <summary>向控制台输出文本。</summary>
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();
}
/// <summary>向日志文件输出文本,文件名按日期自动生成。输出失败时返回错误信息。</summary>
public static string ToFile(string text) => ToFile(text, false);
private static void Logger_WarningEvent(object sender, string value)
/// <summary>向日志文件输出文本,文件名按日期自动生成。输出失败时返回错误信息。</summary>
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)
/// <summary>获取日志文件路径发生错误时返回 NULL 值。</summary>
/// <remarks>d:\app\log\2020-02-02.log</remarks>
/// <remarks>d:\www\app_data\log\2020-02-02.log</remarks>
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)
/// <summary>使用 Logger.Default 写入日志,自动添加时间和日期,多个 Content 参数将以“ | ”分隔。</summary>
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 { }
}
/// <summary>默认的日志记录程序。</summary>
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
}

22
Apewer/Models/StringPairs.cs

@ -111,6 +111,28 @@ namespace Apewer.Models
base.Sort(new Comparison<KeyValuePair<string, string>>((b, a) => a.Key.CompareTo(b.Key)));
}
/// <summary>检查拥有指定的 Key。</summary>
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;
}
}
}

34
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<int> ListActivePort(IPEndPoint[] endpoints)
{
var list = new List<int>(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;
}
/// <summary>列出活动的 TCP 端口。</summary>
public static List<int> ListActiveTcpPort()
{
return ListActivePort(IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners());
}
/// <summary>列出活动的 UDP 端口。</summary>
public static List<int> ListActiveUdpPort()
{
return ListActivePort(IPGlobalProperties.GetIPGlobalProperties().GetActiveUdpListeners());
}
#endregion
}
}

66
Apewer/Source/MySql.cs

@ -70,6 +70,28 @@ namespace Apewer.Source
#endregion
#region 日志。
/// <summary>获取或设置日志记录。</summary>
public Logger Logger { get; set; }
private void Log(Action<Logger> 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(); }
/// <summary></summary>
public IQuery Query(string tsql, IEnumerable<IDataParameter> parameters)
public IQuery Query(string sql, IEnumerable<IDataParameter> 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
}
/// <summary></summary>
public IExecute Execute(string tsql, IEnumerable<IDataParameter> parameters)
public IExecute Execute(string sql, IEnumerable<IDataParameter> 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
}
/// <summary></summary>
public IQuery Query(string tsql)
{
return Query(tsql, null);
}
public IQuery Query(string sql) => Query(sql, null);
/// <summary></summary>
public IExecute Execute(string tsql, IEnumerable<Parameter> parameters)
public IExecute Execute(string sql, IEnumerable<Parameter> parameters)
{
var dps = null as List<IDataParameter>;
if (parameters != null)
@ -224,14 +245,11 @@ namespace Apewer.Source
dps.Add(dp);
}
}
return Execute(tsql, dps);
return Execute(sql, dps);
}
/// <summary></summary>
public IExecute Execute(string tsql)
{
return Execute(tsql, null as IEnumerable<IDataParameter>);
}
public IExecute Execute(string sql) => Execute(sql, null as IEnumerable<IDataParameter>);
#endregion
@ -456,15 +474,15 @@ namespace Apewer.Source
}
/// <summary></summary>
public Result<List<T>> QueryRecords<T>(string tsql) where T : Record
public Result<List<T>> QueryRecords<T>(string sql) where T : Record
{
if (tsql.IsEmpty()) return new Result<List<T>>(new ArgumentException());
if (sql.IsEmpty()) return new Result<List<T>>(new ArgumentException());
try
{
// 解析模型,抛出 Exception。
TableStructure.ParseModel(typeof(T));
var query = Query(tsql) as Query;
var query = Query(sql) as Query;
var list = query.Fill<T>();
query.Dispose();
return new Result<List<T>>(list);

83
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 日志
/// <summary>获取或设置日志记录。</summary>
public Logger Logger { get { if (_logger == null) _logger = Logger.Default(this); return _logger; } }
public Logger Logger { get; set; }
private void Log(Action<Logger> 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 实现接口。
/// <summary>数据库是否已经连接。</summary>
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<IDataParameter> parameters)
/// <summary>查询。</summary>
public IQuery Query(string sql) => Query(sql, null);
/// <summary>查询。</summary>
public IQuery Query(string sql, IEnumerable<IDataParameter> 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;
}
/// <summary>执行。</summary>
public IExecute Execute(string sql) => Execute(sql, null);
/// <summary>执行单条 Transact-SQL 语句,并加入参数。</summary>
public IExecute PrivateExecute(string statement, IEnumerable<IDataParameter> parameters)
public IExecute Execute(string sql, IEnumerable<IDataParameter> 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;
}
/// <summary>查询。</summary>
public IQuery Query(string statement)
{
return PrivateQuery(statement, null);
}
/// <summary>查询。</summary>
public IQuery Query(string statement, IEnumerable<IDataParameter> parameters)
{
if (parameters == null) return Example.InvalidQueryParameters;
return PrivateQuery(statement, parameters);
}
/// <summary>执行。</summary>
public IExecute Execute(string statement)
{
return PrivateExecute(statement, null);
}
/// <summary>执行。</summary>
public IExecute Execute(string statement, IEnumerable<IDataParameter> parameters)
{
if (parameters == null) return Example.InvalidExecuteParameters;
return PrivateExecute(statement, parameters);
}
#endregion
#region 属性。
@ -582,9 +579,9 @@ namespace Apewer.Source
}
/// <summary>获取按指定语句查询到的所有记录。</summary>
public Result<List<T>> Query<T>(string statement) where T : Record
public Result<List<T>> Query<T>(string sql) where T : Record
{
var query = (Query)Query(statement);
var query = (Query)Query(sql);
if (query.Exception == null)
{
var list = query.Fill<T>();

29
Apewer/WindowsUtility.cs → Apewer/SystemUtility.cs

@ -22,9 +22,34 @@ namespace Apewer
{
/// <summary>Windows 实用工具。</summary>
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
}
/// <summary>当前操作系统是 Windows。</summary>
public static bool IsWindows { get => CheckOsType() == 1; }
/// <summary>当前操作系统是 OS X 或 macOS。</summary>
public static bool IsOSX { get => CheckOsType() == 2; }
/// <summary>当前操作系统是 Linux。</summary>
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

154
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
/// <summary>设置缓存时间,单位为秒,最大为 2592000 秒(30 天)。</summary>
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;

97
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();
}
/// <summary>运行 Kestrel 服务器,可指定端口和最大请求 Body 长度。默认同步运行,阻塞当前线程。</summary>
/// <remarks>注意:启动前应使用 SetKestrelEntries 方法设置 Kestrel Entries。</remarks>
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<ApiStartup>();
b.ConfigureKestrel((options) =>
{
options.ListenAnyIP(port);
options.AllowSynchronousIO = true;
if (request > 0) options.Limits.MaxRequestBodySize = request;
}).UseStartup<ApiStartup>();
});
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

108
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;
/// <summary>
/// <para>允许 Invoker 解析 favicon.ico 请求。</para>
/// <para>默认值:不允许,响应空。</para></summary>
public static bool AllowFavIcon { get { return _allowfavicon; } set { _allowfavicon = value; } }
/// <summary>
/// <para>允许 Invoker 解析 robot.txt 请求。</para>
/// <para>默认值:不允许,拒绝搜索引擎收录根目录。</para>
/// </summary>
public static bool AllowRobot { get { return _allowrobot; } set { _allowrobot = value; } }
/// <summary>
/// <para>允许 Invoker 枚举输出 Applications 或 Functions。</para>
/// <para>默认值:允许,输出列表。</para>
/// </summary>
public static bool AllowNumerate { get { return _allowenumerate; } set { _allowenumerate = value; } }
/// <summary>
/// <para>允许 Invoker 输出 Exception。</para>
/// <para>默认值:允许,输出 Exception 对象的属性。</para>
/// </summary>
public static bool AllowException { get { return _allowexception; } set { _allowexception = value; } }
/// <summary>
/// <para>允许 Invoker 输出的 Json 对象缩进。</para>
/// <para>默认值:不允许,不缩进。</para>
/// </summary>
public static bool JsonIndent { get { return _jsonindent; } set { _jsonindent = value; } }
/// <summary>
/// <para>允许 Invoker 输出 Application 列表时包含模块信息。</para>
/// <para>默认值:不允许。</para>
/// </summary>
public static bool ShowModule { get { return _showmodule; } set { _showmodule = value; } }
/// <summary>
/// <para>允许 Invoker 输出 Application 列表时包含类型信息。</para>
/// <para>默认值:不允许。</para>
/// </summary>
public static bool ShowClass { get { return _showclass; } set { _showclass = value; } }
/// <summary>
/// <para>获取或设置站点的端口,范围为 0 ~ 65535。</para>
/// <para>默认值:80。</para>
/// </summary>
public static int Port { get { return _port; } set { _port = value < 0 ? 0 : (value > 65535 ? 65535 : value); } }
/// <summary>允许 Invoker 解析 favicon.ico 请求。</summary>
/// <remarks>默认值:不允许,响应空。</remarks>
public static bool AllowFavIcon { get; set; } = false;
/// <summary>允许 Invoker 解析 robot.txt 请求。</summary>
/// <remarks>默认值:不允许,拒绝搜索引擎收录根目录。</remarks>
public static bool AllowRobot { get; set; } = false;
/// <summary>允许 Invoker 枚举输出 Applications 或 Functions。</summary>
/// <remarks>默认值:允许,输出列表。</remarks>
public static bool AllowEnumerate { get; set; } = true;
/// <summary>允许 Invoker 输出 Exception 对象的属性。</summary>
/// <remarks>默认值:不允许输出。</remarks>
public static bool AllowException { get; set; } = false;
/// <summary>允许 Invoker 输出的 Json 对象缩进。</summary>
/// <remarks>默认值:不缩进。</remarks>
public static bool JsonIndent { get; set; } = false;
/// <summary>允许 Invoker 输出 Application 列表时包含模块名称。</summary>
/// <remarks>默认值:不包含。</remarks>
public static bool WithModuleName { get; set; } = false;
/// <summary>允许 Invoker 输出 Application 列表时包含类型名称。</summary>
/// <remarks>默认值:不包含。</remarks>
public static bool WithTypeName { get; set; } = false;
/// <summary>在响应中包含时间属性。</summary>
/// <remarks>默认值:不包含。</remarks>
public static bool WithClock { get; set; } = false;
/// <summary>在响应中包含执行 API 的持续时间。</summary>
/// <remarks>默认值:包含。</remarks>
public static bool WithDuration { get; set; } = true;
/// <summary>在响应中包含 Application 和 Function 属性。</summary>
/// <remarks>默认值:不包含。</remarks>
public static bool WithTarget { get; set; } = false;
/// <summary>在响应中包含 Access-Control 属性。</summary>
/// <remarks>默认值:包含。</remarks>
public static bool WithAccessControl { get; set; } = true;
/// <summary>设置 Access-Control-Max-Age 的值。</summary>
/// <remarks>默认值:60。</remarks>
public static int AccessControlMaxAge { get; set; } = 60;
}

67
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;
/// <summary>HTTP 头。</summary>
/// <summary>头。</summary>
public StringPairs Headers { get; set; } = new StringPairs();
/// <summary>Cookies。</summary>
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。
/// <summary>开始时间。</summary>
public string Beginning { get; set; }
/// <summary>结束时间。</summary>
public string Ending { get; set; }
/// <summary>Application。</summary>
public string Application { get; set; }
@ -46,63 +31,61 @@ namespace Apewer.Web
/// <summary>Random。</summary>
public string Random { get; set; }
#endregion
#region API 功能。
/// <summary>状态。</summary>
public string Status { get; set; }
/// <summary>消息。</summary>
public string Message { get; set; }
/// <summary>Data。</summary>
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; }
/// <summary>获取或设置 Ticket。</summary>
public string Ticket { get; set; }
/// <summary>设置缓存过期时间,单位为秒。默认值:0,立即过期,不缓存。</summary>
/// <remarks>在 .NET Framework 中,此设置可能无效。</remarks>
public int Expires { get; set; }
#endregion
/// <summary>自定义数据。</summary>
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

80
Apewer/Web/WebUtility.cs

@ -23,8 +23,6 @@ namespace Apewer.Web
public static class WebUtility
{
#region url
/// <summary>按 & 拆分多个参数。</summary>
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
/// <summary>获取程序目录的路径。</summary>
public static string AppDirectory
{
get
{
// AppDomain.CurrentDomain.BaseDirectory
// AppDomain.CurrentDomain.SetupInformation.ApplicationBase
return AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
}
}
#region http
/// <summary>获取直连端的 IP。</summary>
@ -448,6 +455,21 @@ namespace Apewer.Web
return null;
}
/// <summary>从 Response 头中移除 Server 属性。</summary>
public static void RemoveServer(HttpResponse response)
{
if (response == null) return;
#if NETFX
var keys = new List<string>(response.Headers.AllKeys);
#else
var keys = new List<string>(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();
/// <summary>获取日志文件路径发生错误时返回 NULL 值。</summary>
/// <remarks>d:\app\log\2020-02-02.log</remarks>
/// <remarks>d:\www\app_data\log\2020-02-02.log</remarks>
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;
}
/// <summary>写入日志。</summary>
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
/// <summary>获取 Cookies。</summary>

7
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 文本;

Loading…
Cancel
Save