From 2571159fe480b473d4e02be1d0b1ccbaf8bab62d Mon Sep 17 00:00:00 2001 From: Elivo Date: Sun, 17 Oct 2021 23:24:01 +0800 Subject: [PATCH] Apewer-6.4.2 --- Apewer.Source/Source/MySql.cs | 24 ++--- Apewer.Source/Source/SqlClient.cs | 18 ++-- Apewer.Source/Source/Sqlite.cs | 18 ++-- Apewer/ArrayBuilder.cs | 10 +- Apewer/BytesUtility.cs | 100 ------------------- Apewer/Externals/Compression/Fixed.cs | 2 +- Apewer/IndependentAttribute.cs | 17 +++- Apewer/NetworkUtility.cs | 21 +--- Apewer/NumberUtility.cs | 29 +++--- Apewer/RuntimeUtility.cs | 20 ++++ Apewer/Source/Extensions.cs | 39 -------- Apewer/Source/IDbClientOrm.cs | 10 +- Apewer/Source/OrmHelper.cs | 132 +++++++++++++++++++------- Apewer/Source/Record.cs | 28 ------ Apewer/Source/TableStructure.cs | 5 +- Apewer/StorageUtility.cs | 2 +- Apewer/TextUtility.cs | 104 ++++++++++++++++++++ Apewer/Web/StaticController.cs | 44 ++++++++- Apewer/_Common.props | 3 +- Apewer/_Extensions.cs | 41 ++++---- ChangeLog.md | 3 + 21 files changed, 373 insertions(+), 297 deletions(-) delete mode 100644 Apewer/Source/Extensions.cs diff --git a/Apewer.Source/Source/MySql.cs b/Apewer.Source/Source/MySql.cs index 35469e5..85965b3 100644 --- a/Apewer.Source/Source/MySql.cs +++ b/Apewer.Source/Source/MySql.cs @@ -10,6 +10,8 @@ using System.Net; using System.Text; using System.Transactions; +using static Apewer.Source.OrmHelper; + namespace Apewer.Source { @@ -468,16 +470,16 @@ namespace Apewer.Source public string Initialize(Type model) => Initialize(model, out string sql); /// - public string Initialize() where T : class, IRecord, new() => Initialize(typeof(T)); + public string Initialize() where T : class, new() => Initialize(typeof(T)); /// public string Initialize(Record model) => (model == null) ? "参数无效。" : Initialize(model.GetType()); /// 插入记录。返回错误信息。 - public string Insert(IRecord record) + public string Insert(object record) { if (record == null) return "参数无效。"; - record.FixProperties(); + OrmHelper.FixProperties(record); var structure = TableStructure.Parse(record.GetType()); if (structure == null) return "无法解析记录模型。"; @@ -494,8 +496,8 @@ namespace Apewer.Source public string Update(IRecord record) { if (record == null) return "参数无效。"; - record.FixProperties(); - record.SetUpdated(); + FixProperties(record); + SetUpdated(record); var structure = TableStructure.Parse(record.GetType()); if (structure == null) return "无法解析记录模型。"; @@ -509,13 +511,13 @@ namespace Apewer.Source } /// - public Result Query(Type model, string sql) => OrmHelper.Query(this, model, sql); + public Result Query(Type model, string sql) => OrmHelper.Query(this, model, sql); /// - public Result Query(string sql) where T : class, IRecord, new() => OrmHelper.Query(this, sql); + public Result Query(string sql) where T : class, new() => OrmHelper.Query(this, sql); /// 获取所有记录。Flag 为 0 时将忽略 Flag 条件。 - public Result Query(Type model, long flag = 0) => OrmHelper.Query(this, model, (tn) => + public Result Query(Type model, long flag = 0) => OrmHelper.Query(this, model, (tn) => { if (flag == 0) return $"select * from `{tn}`; "; return $"select * from `{tn}` where `_flag`={flag}; "; @@ -532,10 +534,10 @@ namespace Apewer.Source /// 填充的记录模型。 /// 要跳过的记录数,可用最小值为 0。 /// 要获取的记录数,可用最小值为 1。 - public Result Query(Type model, int skip, int count) + public Result Query(Type model, int skip, int count) { - if (skip < 0) return new Result("参数 skip 超出了范围。"); - if (count < 1) return new Result("参数 count 超出了范围。"); + if (skip < 0) return new Result("参数 skip 超出了范围。"); + if (count < 1) return new Result("参数 count 超出了范围。"); return OrmHelper.Query(this, model, (tn) => $"select * from `{tn}` limit {skip}, {count}; "); } diff --git a/Apewer.Source/Source/SqlClient.cs b/Apewer.Source/Source/SqlClient.cs index 6948af9..b438462 100644 --- a/Apewer.Source/Source/SqlClient.cs +++ b/Apewer.Source/Source/SqlClient.cs @@ -10,6 +10,8 @@ using System.Data.SqlClient; using System.Net; using System.Text; +using static Apewer.Source.OrmHelper; + #if NETFRAMEWORK using System.Data.Sql; #else @@ -356,7 +358,7 @@ namespace Apewer.Source } /// 创建表,当表不存在时创建表,当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。 - public string Initialize() where T : class, IRecord, new() => Initialize(typeof(T)); + public string Initialize() where T : class, new() => Initialize(typeof(T)); /// 创建表,当表不存在时创建表,当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。 public string Initialize(Type model) @@ -440,10 +442,10 @@ namespace Apewer.Source } /// 插入记录。返回错误信息。 - public string Insert(IRecord record) + public string Insert(object record) { if (record == null) return "参数无效。"; - record.FixProperties(); + FixProperties(record); var structure = TableStructure.Parse(record.GetType()); if (structure == null) return "无法解析记录模型。"; @@ -460,8 +462,8 @@ namespace Apewer.Source public string Update(IRecord record) { if (record == null) return "参数无效。"; - record.FixProperties(); - record.SetUpdated(); + FixProperties(record); + SetUpdated(record); var structure = TableStructure.Parse(record.GetType()); if (structure == null) return "无法解析记录模型。"; @@ -475,13 +477,13 @@ namespace Apewer.Source } /// 获取按指定语句查询到的所有记录。 - public Result Query(Type model, string sql) => OrmHelper.Query(this, model, sql); + public Result Query(Type model, string sql) => OrmHelper.Query(this, model, sql); /// 获取按指定语句查询到的所有记录。 - public Result Query(string sql) where T : class, IRecord, new() => OrmHelper.Query(this, sql); + public Result Query(string sql) where T : class, new() => OrmHelper.Query(this, sql); /// 获取记录。 - public Result Query(Type model, long flag = 0) => OrmHelper.Query(this, model, (tn) => + public Result Query(Type model, long flag = 0) => OrmHelper.Query(this, model, (tn) => { if (flag == 0) return $"select * from [{tn}]; "; return $"select * from [{tn}] where _flag={flag}; "; diff --git a/Apewer.Source/Source/Sqlite.cs b/Apewer.Source/Source/Sqlite.cs index bb0b09d..3effe56 100644 --- a/Apewer.Source/Source/Sqlite.cs +++ b/Apewer.Source/Source/Sqlite.cs @@ -8,6 +8,8 @@ using System.Data.SQLite; using System.Text; //using Mono.Data.Sqlite; +using static Apewer.Source.OrmHelper; + namespace Apewer.Source { @@ -363,7 +365,7 @@ namespace Apewer.Source public string Initialize(Record model) => model == null ? "参数无效。" : Initialize(model.GetType()); /// 创建表,不修改已存在表。成功时返回空字符串,发生异常时返回异常信息。 - public string Initialize() where T : class, IRecord, new() => Initialize(typeof(T)); + public string Initialize() where T : class, new() => Initialize(typeof(T)); /// 创建表,不修改已存在表。当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。 public string Initialize(Type model) @@ -412,10 +414,10 @@ namespace Apewer.Source } /// 插入记录。返回错误信息。 - public string Insert(IRecord record) + public string Insert(object record) { if (record == null) return "参数无效。"; - record.FixProperties(); + OrmHelper.FixProperties(record); var structure = TableStructure.Parse(record.GetType()); if (structure == null) return "无法解析记录模型。"; @@ -432,8 +434,8 @@ namespace Apewer.Source public string Update(IRecord record) { if (record == null) return "参数无效。"; - record.FixProperties(); - record.SetUpdated(); + FixProperties(record); + SetUpdated(record); var structure = TableStructure.Parse(record.GetType()); if (structure == null) return "无法解析记录模型。"; @@ -447,13 +449,13 @@ namespace Apewer.Source } /// 获取按指定语句查询到的所有记录。 - public Result Query(Type model, string sql) => OrmHelper.Query(this, model, sql); + public Result Query(Type model, string sql) => OrmHelper.Query(this, model, sql); /// 获取按指定语句查询到的所有记录。 - public Result Query(string sql) where T : class, IRecord, new() => OrmHelper.Query(this, sql); + public Result Query(string sql) where T : class, new() => OrmHelper.Query(this, sql); /// 查询多条记录。 - public Result Query(Type model, long flag = 0) => OrmHelper.Query(this, model, (tn) => + public Result Query(Type model, long flag = 0) => OrmHelper.Query(this, model, (tn) => { if (flag == 0) return $"select * from [{tn}]; "; return $"select * from [{tn}] where _flag={flag}; "; diff --git a/Apewer/ArrayBuilder.cs b/Apewer/ArrayBuilder.cs index 352a56a..3730988 100644 --- a/Apewer/ArrayBuilder.cs +++ b/Apewer/ArrayBuilder.cs @@ -63,12 +63,12 @@ namespace Apewer /// 添加元素。 public void Add(T item) { - if (_count >= _capacity) + if (_capacity - _count < 1) { - var capacity = _capacity + _step; - var array = new T[capacity]; - Array.Copy(_array, array, _count); - _array = array; + _capacity += _step; + var temp = new T[_capacity]; + Array.Copy(_array, temp, _count); + _array = temp; } _array[_count] = item; _count++; diff --git a/Apewer/BytesUtility.cs b/Apewer/BytesUtility.cs index e2f0548..074cb3d 100644 --- a/Apewer/BytesUtility.cs +++ b/Apewer/BytesUtility.cs @@ -257,106 +257,6 @@ namespace Apewer catch { return Empty; } } - /// 检查字节数组包含 UTF-8 BOM 头。 - public static bool ContainsBOM(byte[] bytes) - { - if (bytes == null) return false; - if (bytes.LongLength < 3L) return false; - return bytes[0L] == 0xEF && bytes[1L] == 0xBB && bytes[2L] == 0xBF; - } - - /// 检查字节数组是 UTF-8 文本。可指定检查的最大字节长度。 - /// 要检查的字节数组。 - /// 已检查的偏移量。 - /// 检查的最大字节长度。 - public static bool IsUTF8(byte[] bytes, Class offset, int checkLength = 1048576) - { - return IsUTF8(bytes, offset, checkLength); - } - - /// 检查字节数组是 UTF-8 文本,默认最多检测 1MB 数据。 - /// 要检查的字节数组。 - /// 检查的最大字节长度,指定为 0 将不限制检查长度。 - /// 已检查的偏移量,用于调试。 - public static bool IsUTF8(byte[] bytes, int checkLength = 1048576, Class offset = null) - { - // UTF8在Unicode的基础上制定了这样一套规则: - // 1.对于单字节字符,比特位的最高位为0; - // 2.对于多字节字符,第一个字节的比特位中,最高位有n个1,剩下的n - 1个字节的比特位中,最高位都是10。 - // 好了,我知道你一定看不懂,那就先来看看下面例子后,再去看上面定义吧。 - // 比如一个字符(“A”),它在UTF8中的编码为(用二进制表示):01000001。由于比特位的最高位是0,表示它是单字节,它只需要1个字节就可以表示。 - // 再比如一个字符(“判”),它在UTF8中的编码为(用二进制表示):11100101 10001000 10100100。由于在第一个字节中,比特位最高位有3个1,说明这个字符总共需要3个字节来表示,且后3 - 1 = 2位字节中,比特位的最高位为10。 - - if (bytes == null) return false; - var length = bytes.LongLength; - - // 检查 BOM 头。 - if (ContainsBOM(bytes)) return true; - - var hasOffset = offset != null; - - var append = 0; - if (hasOffset) offset.Value = 0; - for (int i = 0; i < length; i++) - { - if (checkLength > 0 && i >= checkLength) break; - - var b = bytes[i]; - if (hasOffset) offset.Value = i; - - // 追加字节最高位为 0。 - if (append > 0) - { - if (b >> 6 != 2) return false; - append -= 1; - continue; - } - - // ASCII 字符。 - if (b < 128) continue; - - // 2 字节 UTF-8。 - if (b >= 0xC0 && b <= 0xDF) - { - append = 1; - continue; - } - - // 3 字节 UTF-8 字符。 - if (b >= 0xE0 && b <= 0xEF) - { - append = 2; - continue; - } - - // 4 字节 UTF-8 字符。 - if (b >= 0xF0 && b <= 0xF7) - { - append = 3; - continue; - } - - // 5 字节 UTF-8 字符。 - if (b >= 0xF8 && b <= 0xFB) - { - append = 4; - continue; - } - - // 6 字节 UTF-8 字符。 - if (b >= 0xFC && b <= 0xFD) - { - append = 5; - continue; - } - - // 未知字节,非 UTF-8 定义。 - return false; - } - - return true; - } - #endregion #region 压缩、解压。 diff --git a/Apewer/Externals/Compression/Fixed.cs b/Apewer/Externals/Compression/Fixed.cs index 98044c0..73a64ea 100644 --- a/Apewer/Externals/Compression/Fixed.cs +++ b/Apewer/Externals/Compression/Fixed.cs @@ -15,7 +15,7 @@ namespace Externals.Compression // 识别参考数据,判断解压的编码。 if (bytes != null || bytes.LongLength > 0L) { - var isUTF8 = BytesUtility.IsUTF8(bytes); + var isUTF8 = TextUtility.IsUTF8(bytes); if (isUTF8) return Encoding.UTF8; // 返回默认编码。 diff --git a/Apewer/IndependentAttribute.cs b/Apewer/IndependentAttribute.cs index 05bc48a..40a6c1f 100644 --- a/Apewer/IndependentAttribute.cs +++ b/Apewer/IndependentAttribute.cs @@ -7,6 +7,21 @@ namespace Apewer /// 无依赖特性。 [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] - public sealed class IndependentAttribute : Attribute { } + public sealed class IndependentAttribute : Attribute + { + + string _remark = null; + + /// 无依赖特性。 + public IndependentAttribute(string remark) => _remark = remark; + + /// 备注。 + public string Remark + { + get { return _remark; } + set { if (!string.IsNullOrEmpty(value)) _remark = value; } + } + + } } diff --git a/Apewer/NetworkUtility.cs b/Apewer/NetworkUtility.cs index bfaa8cb..b6d618f 100644 --- a/Apewer/NetworkUtility.cs +++ b/Apewer/NetworkUtility.cs @@ -142,26 +142,13 @@ namespace Apewer /// 获取本地计算机的计算机名。 public static string LocalHost { - get - { - var hn = Dns.GetHostName(); - return string.IsNullOrEmpty(hn) ? "" : hn; - } + get => Dns.GetHostName() ?? ""; } - /// 本地计算机的 IP 地址。 - public static List LocalIP + /// 本地计算机的所有 IP 地址。 + public static IPAddress[] LocalIP { - get - { - var list = new List(); - var he = Dns.GetHostEntry(Dns.GetHostName()); - foreach (var ip in he.AddressList) - { - list.Add(ip.ToString()); - } - return list; - } + get => Dns.GetHostEntry(Dns.GetHostName()).AddressList; } /// 判断 IPv4 地址格式是否正确。 diff --git a/Apewer/NumberUtility.cs b/Apewer/NumberUtility.cs index d7526f4..e36e227 100644 --- a/Apewer/NumberUtility.cs +++ b/Apewer/NumberUtility.cs @@ -256,8 +256,11 @@ namespace Apewer return new string(chars, 0, output); } - private static T GetNumber(string text, Func convert, Func percent = null) + private static T GetNumber(object @object, Func convert, Func percent = null) { + if (@object == null) return default(T); + if (@object is T) return (T)@object; + var text = (@object is string) ? (string)@object : ""; var trim = Trim(text); if (trim == null) return default; @@ -292,40 +295,40 @@ namespace Apewer } /// 获取单精度浮点对象。 - public static float Float(string text) => GetNumber(text, Convert.ToSingle, (v, d) => v / Convert.ToSingle(d)); + public static float Float(object number) => GetNumber(number, Convert.ToSingle, (v, d) => v / Convert.ToSingle(d)); /// 获取单精度浮点对象。 - public static float Single(string text) => GetNumber(text, Convert.ToSingle, (v, d) => v / Convert.ToSingle(d)); + public static float Single(object number) => GetNumber(number, Convert.ToSingle, (v, d) => v / Convert.ToSingle(d)); /// 获取双精度浮点对象。 - public static double Double(string text) => GetNumber(text, Convert.ToDouble, (v, d) => v / d); + public static double Double(object number) => GetNumber(number, Convert.ToDouble, (v, d) => v / d); /// 获取 Decimal 对象。 - public static decimal Decimal(string text) => GetNumber(text, DecimalAsFloat, (v, d) => v / Convert.ToDecimal(d)); + public static decimal Decimal(object number) => GetNumber(number, DecimalAsFloat, (v, d) => v / Convert.ToDecimal(d)); /// 获取 Byte 对象。 - public static byte Byte(string text) => GetNumber(text, Convert.ToByte); + public static byte Byte(object number) => GetNumber(number, Convert.ToByte); /// 获取 SByte 对象。 - public static sbyte SByte(string text) => GetNumber(text, Convert.ToSByte); + public static sbyte SByte(object number) => GetNumber(number, Convert.ToSByte); /// 获取 Int16 对象。 - public static short Int16(string text) => GetNumber(text, Convert.ToInt16); + public static short Int16(object number) => GetNumber(number, Convert.ToInt16); /// 获取 UInt16 对象。 - public static ushort UInt16(string text) => GetNumber(text, Convert.ToUInt16); + public static ushort UInt16(object number) => GetNumber(number, Convert.ToUInt16); /// 获取 Int32 对象。 - public static int Int32(string text) => GetNumber(text, Convert.ToInt32); + public static int Int32(object number) => GetNumber(number, Convert.ToInt32); /// 获取 UInt32 对象。 - public static uint UInt32(string text) => GetNumber(text, Convert.ToUInt32); + public static uint UInt32(object number) => GetNumber(number, Convert.ToUInt32); /// 获取 Int64 对象。 - public static long Int64(string text) => GetNumber(text, Convert.ToInt64); + public static long Int64(object number) => GetNumber(number, Convert.ToInt64); /// 获取 UInt64 对象。 - public static ulong UInt64(string text) => GetNumber(text, Convert.ToUInt64); + public static ulong UInt64(object number) => GetNumber(number, Convert.ToUInt64); #endregion diff --git a/Apewer/RuntimeUtility.cs b/Apewer/RuntimeUtility.cs index 6010c3f..fc63171 100644 --- a/Apewer/RuntimeUtility.cs +++ b/Apewer/RuntimeUtility.cs @@ -603,6 +603,26 @@ namespace Apewer private static Class _AppPath = null; private static Class _DataPath = null; + private static Class _InIIS = null; + + /// 当前应用程序由 IIS 托管。 + public static bool InIIS() + { +#if NETFRAMEWORK + if (_InIIS != null) return _InIIS.Value; + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (var assembly in assemblies) + { + if (assembly.FullName.StartsWith("System.Web,")) + { + _InIIS = new Class(true); + return true; + } + } + _InIIS = new Class(false); +#endif + return false; + } /// 获取当前应用程序所在目录的路径。 /// diff --git a/Apewer/Source/Extensions.cs b/Apewer/Source/Extensions.cs deleted file mode 100644 index ddc4e34..0000000 --- a/Apewer/Source/Extensions.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Apewer.Source -{ - - /// - public static class Extensions - { - - /// 设置 Updated 属性。 - /// TRUE:设置成功;FALSE:设置失败。 - public static bool SetUpdated(this IRecord record) - { - if (record == null) return false; - - var moment = record as IRecordMoment; - if (moment != null) - { - var now = ClockUtility.LucidNow; - moment.Updated = now; - return true; - } - - var stamp = record as IRecordStamp; - if (stamp != null) - { - var utc = ClockUtility.UtcStamp; - stamp.Updated = utc; - return true; - } - - return false; - } - - } - -} diff --git a/Apewer/Source/IDbClientOrm.cs b/Apewer/Source/IDbClientOrm.cs index 674214b..f961360 100644 --- a/Apewer/Source/IDbClientOrm.cs +++ b/Apewer/Source/IDbClientOrm.cs @@ -16,12 +16,12 @@ namespace Apewer.Source /// 初始化指定类型,以创建表或增加字段。 /// 错误信息。当成功时候返回空字符串。 - public string Initialize() where T : class, IRecord, new(); + public string Initialize() where T : class, new(); /// 插入记录。 /// 要插入的记录实体。 /// 错误信息。当成功时候返回空字符串。 - public string Insert(IRecord record); + public string Insert(object record); /// 更新记录。 /// 要插入的记录实体。 @@ -51,16 +51,16 @@ namespace Apewer.Source /// 使用指定语句查询,获取查询结果。 /// 目标记录的类型。 /// 要执行的 SQL 语句。 - public Result Query(Type model, string sql); + public Result Query(Type model, string sql); /// 使用指定语句查询,获取查询结果。 /// 要执行的 SQL 语句。 - public Result Query(string sql) where T : class, IRecord, new(); + public Result Query(string sql) where T : class, new(); /// 查询所有记录。 /// 目标记录的类型。 /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 - public Result Query(Type model, long flag = 0); + public Result Query(Type model, long flag = 0); /// 查询所有记录。 /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 diff --git a/Apewer/Source/OrmHelper.cs b/Apewer/Source/OrmHelper.cs index 6cd59e8..7086974 100644 --- a/Apewer/Source/OrmHelper.cs +++ b/Apewer/Source/OrmHelper.cs @@ -13,37 +13,45 @@ namespace Apewer.Source #region As - private static T[] As(IRecord[] input) where T : IRecord + /// 转换模型类型。 + public static TDst[] As(this TSrc[] input) where TDst : class { if (input == null) return null; var count = input.Length; - var output = new T[count]; + var output = new TDst[count]; for (var i = 0; i < count; i++) { - var record = input[i]; - if (record == null) continue; - var t = (T)record; - output[i] = t; + var item = input[i]; + if (item == null) continue; + output[i] = item as TDst; // 此处可能抛出异常。 } return output; } - private static Result As(Result input) where T : class, IRecord, new() + /// 转换模型类型。 + public static Result As(this Result input) where TDst : class { if (input == null) return null; - if (!input.HasValue) return new Result(input.Code, input.Message); - var value = input.Value as T; - return new Result(value); + if (!input.HasValue) return new Result(input.Code, input.Message); + var value = input.Value as TDst; + if (value == null) + { + var src = input.Value.GetType().FullName; + var dst = typeof(TDst).FullName; + return new Result($"无法将记录从转换 {src} 到 {dst}。"); + } + return new Result(value); } - private static Result As(Result input) where T : class, IRecord, new() + /// 转换模型类型。 + public static Result As(this Result input) where TDst : class { if (input == null) return null; - if (!input.HasValue) return new Result(input.Code, input.Message); + if (!input.HasValue) return new Result(input.Code, input.Message); var count = input.Value.Length; - var output = new T[count]; - for (var i = 0; i < count; i++) output[i] = input.Value[i] as T; - return new Result(output); + var output = new TDst[count]; + for (var i = 0; i < count; i++) output[i] = input.Value[i] as TDst; + return new Result(output); } #endregion @@ -51,16 +59,18 @@ namespace Apewer.Source #region IQuery -> IRecord /// 读取所有行,生成列表。 - public static T[] Fill(IQuery query) where T : class, IRecord, new() => As(Fill(query, typeof(T))); + public static T[] Fill(IQuery query) where T : class, new() => As(Fill(query, typeof(T))); /// 读取所有行填充到 T,组成 T[]。 - public static IRecord[] Fill(IQuery query, Type model) + public static object[] Fill(IQuery query, Type model) { - if (query == null) return new IRecord[0]; - if (model == null) return new IRecord[0]; + if (query == null) return new object[0]; + if (model == null) return new object[0]; var ts = TableStructure.Parse(model); - var output = new IRecord[query.Rows]; + if (ts == null) return new object[0]; + + var output = new object[query.Rows]; for (int r = 0; r < query.Rows; r++) output[r] = Row(query, r, model, ts); return output; } @@ -94,6 +104,7 @@ namespace Apewer.Source /// 将 Query 的行,填充到模型实体。 /// 填充失败时返回 NULL 值。 + /// public static IRecord Row(IQuery query, int rowIndex, Type model, TableStructure structure) { // 检查参数。 @@ -210,27 +221,27 @@ namespace Apewer.Source /// 数据库对象。 /// 记录模型。 /// SQL 语句。 - public static Result Query(IDbClientAdo database, Type model, string sql) + public static Result Query(IDbClientAdo database, Type model, string sql) { - if (database == null) return new Result("数据库无效。"); - if (model == null) return new Result("模型类型无效。"); - if (string.IsNullOrEmpty(sql)) return new Result("SQL 语句无效。"); + if (database == null) return new Result("数据库无效。"); + if (model == null) return new Result("模型类型无效。"); + if (string.IsNullOrEmpty(sql)) return new Result("SQL 语句无效。"); using (var query = database.Query(sql) as Query) { - if (query == null) return new Result("查询实例无效。"); + if (query == null) return new Result("查询实例无效。"); if (query.Table == null) { - if (!string.IsNullOrEmpty(query.Message)) return new Result(query.Message); - return new Result("查询实例不包含数据表。"); + if (!string.IsNullOrEmpty(query.Message)) return new Result(query.Message); + return new Result("查询实例不包含数据表。"); } try { var array = Fill(query, model); - return new Result(array); + return new Result(array); } catch (Exception ex) { - return new Result(ex); + return new Result(ex); } } } @@ -239,24 +250,24 @@ namespace Apewer.Source /// 记录模型。 /// 数据库对象。 /// SQL 语句。 - public static Result Query(IDbClientAdo database, string sql) where T : class, IRecord, new() => As(Query(database, typeof(T), sql)); + public static Result Query(IDbClientAdo database, string sql) where T : class, new() => As(Query(database, typeof(T), sql)); /// 查询记录。 /// 数据库对象。 /// 记录模型。 /// 生成 SQL 语句的函数,传入参数为表名。 - public static Result Query(IDbClientAdo database, Type model, Func sqlGetter) + public static Result Query(IDbClientAdo database, Type model, Func sqlGetter) { - if (sqlGetter == null) return new Result("SQL 语句获取函数无效。"); + if (sqlGetter == null) return new Result("SQL 语句获取函数无效。"); try { var tableName = TableStructure.Parse(model).Name; - if (string.IsNullOrEmpty(tableName)) return new Result("表名无效。"); + if (string.IsNullOrEmpty(tableName)) return new Result("表名无效。"); return Query(database, model, sqlGetter(tableName)); } catch (Exception ex) { - return new Result(ex); + return new Result(ex); } } @@ -264,7 +275,7 @@ namespace Apewer.Source /// 记录模型。 /// 数据库对象。 /// 生成 SQL 语句的函数,传入参数为表名。 - public static Result Query(IDbClientAdo database, Func sqlGetter) where T : class, IRecord, new() => As(Query(database, typeof(T), sqlGetter)); + public static Result Query(IDbClientAdo database, Func sqlGetter) where T : class, new() => As(Query(database, typeof(T), sqlGetter)); /// 获取具有指定主键的记录。 /// 数据库对象。 @@ -306,7 +317,7 @@ namespace Apewer.Source /// 数据库对象。 /// 主键。 /// 生成 SQL 语句的函数,传入参数为表名和主键值。 - public static Result Get(IDbClientAdo database, string key, Func sqlGetter) where T : class, IRecord, new() => As(Get(database, typeof(T), key, sqlGetter)); + public static Result Get(IDbClientAdo database, string key, Func sqlGetter) where T : class, IRecord, new() => As(Get(database, typeof(T), key, sqlGetter)); /// 获取主键。 /// 数据库对象。 @@ -380,6 +391,55 @@ namespace Apewer.Source #endregion + #region Record + + /// 修复记录属性。 + public static void FixProperties(object record) + { + if (record == null) return; + + if (record is IRecord key) key.ResetKey(); + + if (record is IRecordMoment moment) + { + var now = ClockUtility.LucidNow; + if (string.IsNullOrEmpty(moment.Created)) moment.Created = now; + if (string.IsNullOrEmpty(moment.Updated)) moment.Updated = now; + } + + if (record is IRecordStamp stamp) + { + var utc = ClockUtility.UtcStamp; + if (stamp.Created == 0L) stamp.Created = utc; + if (stamp.Updated == 0L) stamp.Updated = utc; + } + } + + /// 设置 Updated 属性。 + /// TRUE:设置成功;FALSE:设置失败。 + public static bool SetUpdated(object record) + { + if (record == null) return false; + + if (record is IRecordMoment moment) + { + var now = ClockUtility.LucidNow; + moment.Updated = now; + return true; + } + + if (record is IRecordStamp stamp) + { + var utc = ClockUtility.UtcStamp; + stamp.Updated = utc; + return true; + } + + return false; + } + + #endregion + } } diff --git a/Apewer/Source/Record.cs b/Apewer/Source/Record.cs index 446834a..45a46d3 100644 --- a/Apewer/Source/Record.cs +++ b/Apewer/Source/Record.cs @@ -37,34 +37,6 @@ namespace Apewer.Source #region static - internal static void FixProperties(IRecord record) - { - if (record == null) return; - - // if (record.Flag == 0) record.Flag = 1; - if (string.IsNullOrEmpty(record.Key)) - { - var canResetKey = record as Record; - if (canResetKey != null) canResetKey.ResetKey(); - } - - var moment = record as IRecordMoment; - if (moment != null) - { - var now = ClockUtility.LucidNow; - if (string.IsNullOrEmpty(moment.Created)) moment.Created = now; - if (string.IsNullOrEmpty(moment.Updated)) moment.Updated = now; - } - - var stamp = record as IRecordStamp; - if (stamp != null) - { - var utc = ClockUtility.UtcStamp; - if (stamp.Created == 0L) stamp.Created = utc; - if (stamp.Updated == 0L) stamp.Updated = utc; - } - } - /// 枚举带有 Table 特性的 派生类型。 public static List EnumerateTableTypes() where T : IRecord => EnumerateTableTypes(typeof(T)); diff --git a/Apewer/Source/TableStructure.cs b/Apewer/Source/TableStructure.cs index 1b248fe..bdcff98 100644 --- a/Apewer/Source/TableStructure.cs +++ b/Apewer/Source/TableStructure.cs @@ -161,7 +161,7 @@ namespace Apewer.Source return lower; } - static IDataParameter CreateParameter(IRecord record, ColumnAttribute ca, Func callback) + static IDataParameter CreateParameter(object record, ColumnAttribute ca, Func callback) { var property = ca.Property; if (property == null) return null; @@ -207,10 +207,9 @@ namespace Apewer.Source } /// 生成 IDataParameter 列表,用于 Insert 和 Update 方法。 - public IDataParameter[] CreateParameters(IRecord record, Func callback, params string[] excludeds) + public IDataParameter[] CreateParameters(object record, Func callback, params string[] excludeds) { if (record == null || callback == null) return null; - record.FixProperties(); var list = new List(_columns.Length); foreach (var ca in Columns) diff --git a/Apewer/StorageUtility.cs b/Apewer/StorageUtility.cs index cd7546a..eecd01d 100644 --- a/Apewer/StorageUtility.cs +++ b/Apewer/StorageUtility.cs @@ -484,7 +484,7 @@ namespace Apewer { var head = new byte[3]; stream.Read(head, 0, 3); - if (BytesUtility.ContainsBOM(head)) + if (TextUtility.ContainsBOM(head)) { var capacity = length - 3; result = new byte[capacity]; diff --git a/Apewer/TextUtility.cs b/Apewer/TextUtility.cs index 1db8dcd..d2c465b 100644 --- a/Apewer/TextUtility.cs +++ b/Apewer/TextUtility.cs @@ -957,6 +957,110 @@ namespace Apewer return trim ? Trim(middle, trimBlank) : middle; } + #region encoding + + /// 检查字节数组包含 UTF-8 BOM 头。 + public static bool ContainsBOM(byte[] bytes) + { + if (bytes == null) return false; + if (bytes.LongLength < 3L) return false; + return bytes[0L] == 0xEF && bytes[1L] == 0xBB && bytes[2L] == 0xBF; + } + + /// 检查字节数组是 UTF-8 文本。可指定检查的最大字节长度。 + /// 要检查的字节数组。 + /// 已检查的偏移量。 + /// 检查的最大字节长度。 + public static bool IsUTF8(byte[] bytes, Class offset, int checkLength = 1048576) + { + return IsUTF8(bytes, offset, checkLength); + } + + /// 检查字节数组是 UTF-8 文本,默认最多检测 1MB 数据。 + /// 要检查的字节数组。 + /// 检查的最大字节长度,指定为 0 将不限制检查长度。 + /// 已检查的偏移量,用于调试。 + public static bool IsUTF8(byte[] bytes, int checkLength = 1048576, Class offset = null) + { + // UTF8在Unicode的基础上制定了这样一套规则: + // 1.对于单字节字符,比特位的最高位为0; + // 2.对于多字节字符,第一个字节的比特位中,最高位有n个1,剩下的n - 1个字节的比特位中,最高位都是10。 + // 好了,我知道你一定看不懂,那就先来看看下面例子后,再去看上面定义吧。 + // 比如一个字符(“A”),它在UTF8中的编码为(用二进制表示):01000001。由于比特位的最高位是0,表示它是单字节,它只需要1个字节就可以表示。 + // 再比如一个字符(“判”),它在UTF8中的编码为(用二进制表示):11100101 10001000 10100100。由于在第一个字节中,比特位最高位有3个1,说明这个字符总共需要3个字节来表示,且后3 - 1 = 2位字节中,比特位的最高位为10。 + + if (bytes == null) return false; + var length = bytes.LongLength; + + // 检查 BOM 头。 + if (ContainsBOM(bytes)) return true; + + var hasOffset = offset != null; + + var append = 0; + if (hasOffset) offset.Value = 0; + for (int i = 0; i < length; i++) + { + if (checkLength > 0 && i >= checkLength) break; + + var b = bytes[i]; + if (hasOffset) offset.Value = i; + + // 追加字节最高位为 0。 + if (append > 0) + { + if (b >> 6 != 2) return false; + append -= 1; + continue; + } + + // ASCII 字符。 + if (b < 128) continue; + + // 2 字节 UTF-8。 + if (b >= 0xC0 && b <= 0xDF) + { + append = 1; + continue; + } + + // 3 字节 UTF-8 字符。 + if (b >= 0xE0 && b <= 0xEF) + { + append = 2; + continue; + } + + // 4 字节 UTF-8 字符。 + if (b >= 0xF0 && b <= 0xF7) + { + append = 3; + continue; + } + + // 5 字节 UTF-8 字符。 + if (b >= 0xF8 && b <= 0xFB) + { + append = 4; + continue; + } + + // 6 字节 UTF-8 字符。 + if (b >= 0xFC && b <= 0xFD) + { + append = 5; + continue; + } + + // 未知字节,非 UTF-8 定义。 + return false; + } + + return true; + } + + #endregion + } } diff --git a/Apewer/Web/StaticController.cs b/Apewer/Web/StaticController.cs index 1b2cf40..fcd15d6 100644 --- a/Apewer/Web/StaticController.cs +++ b/Apewer/Web/StaticController.cs @@ -20,6 +20,8 @@ namespace Apewer.Web List PathSegments; + Class _root = null; + /// public StaticController() : base((c) => { ((StaticController)c).Initialize(); return false; }) { } @@ -65,9 +67,49 @@ namespace Apewer.Web /// 获取此静态站点的目录。 protected virtual string Root() { + if (_root) return _root.Value; var app = RuntimeUtility.ApplicationPath; + + var paths = StorageUtility.GetSubFiles(app); + foreach (var path in paths) + { + var split = path.Split('/', '\\'); + var lower = split[split.Length - 1]; + switch (lower) + { + case "index.html": + case "index.htm": + case "default.html": + case "default.htm": + case "favicon.ico": + _root = new Class(app); + return app; + } + } + var www = StorageUtility.CombinePath(app, "www"); - return System.IO.Directory.Exists(www) ? www : app; + if (System.IO.Directory.Exists(www)) + { + _root = new Class(www); + return www; + } + + var web = StorageUtility.CombinePath(app, "web"); + if (System.IO.Directory.Exists(web)) + { + _root = new Class(web); + return www; + } + + var @static = StorageUtility.CombinePath(app, "static"); + if (System.IO.Directory.Exists(@static)) + { + _root = new Class(@static); + return www; + } + + _root = new Class(app); + return app; } /// 从扩展名获取内容类型。 diff --git a/Apewer/_Common.props b/Apewer/_Common.props index 71e086c..01911ad 100644 --- a/Apewer/_Common.props +++ b/Apewer/_Common.props @@ -14,12 +14,13 @@ Apewer Libraries - 6.4.1 + 6.4.2 true + ..\ true diff --git a/Apewer/_Extensions.cs b/Apewer/_Extensions.cs index a4a8c4b..ff3ca5f 100644 --- a/Apewer/_Extensions.cs +++ b/Apewer/_Extensions.cs @@ -52,22 +52,22 @@ public static class Extensions #region Number - /// + /// 判断此值为零。 public static bool IsZero(this decimal @this) => @this.Equals(0M); - /// + /// 判断此值为零。 public static bool IsZero(this double @this) => @this.Equals(0D); - /// + /// 判断此值为零。 public static bool IsZero(this float @this) => @this.Equals(0F); - /// + /// 判断此值非零。 public static bool NotZero(this decimal @this) => !@this.Equals(0M); - /// + /// 判断此值非零。 public static bool NotZero(this double @this) => !@this.Equals(0D); - /// + /// 判断此值非零。 public static bool NotZero(this float @this) => !@this.Equals(0F); /// 约束值范围,若源值不在范围中,则修改为接近的值。 @@ -77,17 +77,23 @@ public static class Extensions #region String、StringBuilder - /// - public static Int32 Int32(this string @this) => NumberUtility.Int32(@this); + /// 获取 Byte 对象。 + public static byte Byte(this object @this) => NumberUtility.Byte(@this); - /// - public static Int64 Int64(this string @this) => NumberUtility.Int64(@this); + /// 获取 Int32 对象。 + public static int Int32(this object @this) => NumberUtility.Int32(@this); - /// - public static Decimal Decimal(this string @this) => NumberUtility.Decimal(@this); + /// 获取 Int64 对象。 + public static long Int64(this object @this) => NumberUtility.Int64(@this); - /// - public static Double Double(this string @this) => NumberUtility.Double(@this); + /// 获取 Decimal 对象。 + public static decimal Decimal(this object @this) => NumberUtility.Decimal(@this); + + /// 获取单精度浮点对象。 + public static float Float(this object @this) => NumberUtility.Float(@this); + + /// 获取双精度浮点对象。 + public static double Double(this object @this) => NumberUtility.Double(@this); /// 将文本转换为字节数组,默认使用 UTF-8。 public static byte[] Bytes(this string @this, Encoding encoding = null) => TextUtility.Bytes(@this, encoding); @@ -196,10 +202,10 @@ public static class Extensions public static byte[] Append(this byte[] @this, params byte[] bytes) => BytesUtility.Merge(@this, bytes); /// 检查字节数组是 UTF-8 文本,默认最多检测 1MB 数据(指定为 0 将不限制检查长度)。 - public static bool IsUTF8(this byte[] @this, int checkLength = 1048576) => BytesUtility.IsUTF8(@this, checkLength, null); + public static bool IsUTF8(this byte[] @this, int checkLength = 1048576) => TextUtility.IsUTF8(@this, checkLength, null); /// 检查字节数组包含 UTF-8 BOM 头。 - public static bool ContainsBOM(this byte[] @this) => BytesUtility.ContainsBOM(@this); + public static bool ContainsBOM(this byte[] @this) => TextUtility.ContainsBOM(@this); #endregion @@ -422,9 +428,6 @@ public static class Extensions #region Source - /// 修补基本属性。 - public static void FixProperties(this IRecord @this) => Record.FixProperties(@this); - /// 获取默认表中指定单元格的内容。从第 0 行第 0 列开始。 public static Class DateTime(this IQuery @this, int row = 0, int column = 0) => @this == null ? null : Query.DateTime(@this.Value(row, column)); diff --git a/ChangeLog.md b/ChangeLog.md index 6dca4bf..e513361 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,9 @@  ### 最新提交 +### 6.4.2 +- ArrayBuilder:修正 512 长度无法扩展的问题。 + ### 6.4.1 - Clock:增加 long.DateTime() 方法; - Json:引用的 List 现改为数组;