From 13668abcd65e385a9414fd55a46f1aa49b82855f Mon Sep 17 00:00:00 2001 From: Elivo Date: Sun, 17 Oct 2021 23:25:40 +0800 Subject: [PATCH] Apewer-6.5.0 --- Apewer.Source/Apewer.Source.csproj | 11 - Apewer.Source/Source/Access.cs | 18 +- Apewer.Source/Source/ColumnInfo.cs | 27 ++ Apewer.Source/Source/MySql.cs | 250 +++++----------- Apewer.Source/Source/SqlClient.cs | 299 +++++++++---------- Apewer.Source/Source/Sqlite.cs | 305 ++++++++----------- Apewer.Web/Apewer.Web.csproj | 7 + Apewer.Web/FavIcon.ico | Bin 0 -> 17542 bytes Apewer.Web/Internals/ApiHelper.cs | 8 +- Apewer.Web/Web/ApiEntries.cs | 1 + Apewer.Web/Web/ApiProcessor.cs | 4 +- Apewer.Web/Web/CronAttribute.cs | 32 -- Apewer.Web/Web/CronInstance.cs | 124 -------- Apewer.Web/Web/Resources.cs | 31 ++ Apewer.Web/WebConfig40.xml | 30 ++ Apewer.Web/WebConfig461.xml | 30 ++ Apewer.Web/WebConfigStd.xml | 34 +++ Apewer/CronAttribute.cs | 83 ++++++ Apewer/CronInstance.cs | 95 ++++++ {Apewer.Web/Web => Apewer}/CronInvoker.cs | 60 ++-- Apewer/IndependentAttribute.cs | 2 +- Apewer/Json.cs | 155 +++++----- Apewer/Logger.cs | 12 +- Apewer/NumberUtility.cs | 20 ++ Apewer/Source/ColumnAttribute.cs | 7 +- Apewer/Source/DbClient.cs | 339 ++++++++++++++++++++++ Apewer/Source/IDbClientAdo.cs | 4 +- Apewer/Source/IDbClientOrm.cs | 8 +- Apewer/Source/IRecordStamp.cs | 4 +- Apewer/Source/OrmHelper.cs | 76 ++--- Apewer/Source/Record.cs | 12 +- Apewer/Source/TableAttribute.cs | 12 +- Apewer/Source/TableStructure.cs | 43 ++- Apewer/SystemUtility.cs | 18 ++ Apewer/TextUtility.cs | 9 +- Apewer/Web/ApiOptions.cs | 83 +++--- Apewer/Web/ApiProvider.cs | 6 +- Apewer/_Common.props | 2 +- Apewer/_Extensions.cs | 27 +- ChangeLog.md | 15 + 40 files changed, 1342 insertions(+), 961 deletions(-) create mode 100644 Apewer.Source/Source/ColumnInfo.cs create mode 100644 Apewer.Web/FavIcon.ico delete mode 100644 Apewer.Web/Web/CronAttribute.cs delete mode 100644 Apewer.Web/Web/CronInstance.cs create mode 100644 Apewer.Web/Web/Resources.cs create mode 100644 Apewer.Web/WebConfig40.xml create mode 100644 Apewer.Web/WebConfig461.xml create mode 100644 Apewer.Web/WebConfigStd.xml create mode 100644 Apewer/CronAttribute.cs create mode 100644 Apewer/CronInstance.cs rename {Apewer.Web/Web => Apewer}/CronInvoker.cs (73%) create mode 100644 Apewer/Source/DbClient.cs diff --git a/Apewer.Source/Apewer.Source.csproj b/Apewer.Source/Apewer.Source.csproj index 1c15c33..922a466 100644 --- a/Apewer.Source/Apewer.Source.csproj +++ b/Apewer.Source/Apewer.Source.csproj @@ -32,17 +32,6 @@ - - - $(DefineConstants);MYSQL_6_10; - - - - - - - - $(DefineConstants);MYSQL_6_9; diff --git a/Apewer.Source/Source/Access.cs b/Apewer.Source/Source/Access.cs index 0863201..f8b34e5 100644 --- a/Apewer.Source/Source/Access.cs +++ b/Apewer.Source/Source/Access.cs @@ -67,8 +67,8 @@ namespace Apewer.Source } /// 连接数据库,若未连接则尝试连接。 - /// 是否已连接。 - public bool Connect() + /// 错误信息。 + public string Connect() { if (_connection == null) { @@ -77,19 +77,20 @@ namespace Apewer.Source } else { - if (_connection.State == ConnectionState.Open) return true; + if (_connection.State == ConnectionState.Open) return null; } try { _connection.Open(); - if (_connection.State == ConnectionState.Open) return true; + if (_connection.State == ConnectionState.Open) return null; } catch (Exception ex) { Logger.Error(nameof(Access), "Connect", ex, _connstr); Close(); + return ex.Message; } - return false; + return "连接失败。"; } /// 释放对象所占用的系统资源。 @@ -124,7 +125,8 @@ namespace Apewer.Source /// 启动事务。 public string Begin(bool commit, Class isolation) { - if (!Connect()) return "未连接。"; + var connect = Connect(); + if (connect.NotEmpty()) return connect; if (_transaction != null) return "存在已启动的事务,无法再次启动。"; try { @@ -192,7 +194,7 @@ namespace Apewer.Source if (sql.IsBlank()) return Example.InvalidQueryStatement; var connected = Connect(); - if (!connected) return Example.InvalidQueryConnection; + if (connected.NotEmpty()) return new Query(false, connected); try { @@ -236,7 +238,7 @@ namespace Apewer.Source if (sql.IsBlank()) return Example.InvalidExecuteStatement; var connected = Connect(); - if (!connected) return Example.InvalidExecuteConnection; + if (connected.NotEmpty()) return new Execute(false, connected); var inTransaction = _transaction != null; if (!inTransaction) Begin(); diff --git a/Apewer.Source/Source/ColumnInfo.cs b/Apewer.Source/Source/ColumnInfo.cs new file mode 100644 index 0000000..eca78cb --- /dev/null +++ b/Apewer.Source/Source/ColumnInfo.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Apewer.Source +{ + + /// 列信息。 + [Serializable] + public sealed class ColumnInfo + { + + /// 字段。 + public string Name { get; set; } + + /// 类型。 + public string Type { get; set; } + + /// 长度。 + public int Length { get; set; } + + /// 是主键。 + public int IsKey { get; set; } + + } + +} diff --git a/Apewer.Source/Source/MySql.cs b/Apewer.Source/Source/MySql.cs index 85965b3..11d1c90 100644 --- a/Apewer.Source/Source/MySql.cs +++ b/Apewer.Source/Source/MySql.cs @@ -1,6 +1,6 @@ #if MYSQL_6_9 || MYSQL_6_10 -/* 2021.10.14 */ +/* 2021.11.07 */ using Externals.MySql.Data.MySqlClient; using System; @@ -45,10 +45,10 @@ namespace Apewer.Source { _timeout = timeout ?? Timeout.Default; - var a = TextUtility.AntiInject(address); - var s = TextUtility.AntiInject(store); - var u = TextUtility.AntiInject(user); - var p = TextUtility.AntiInject(pass); + var a = address ?? ""; + var s = store ?? ""; + var u = user ?? ""; + var p = pass ?? ""; var cs = $"server={a}; database={s}; uid={u}; pwd={p}; "; _connectionstring = cs; _storename = new Class(s); @@ -77,7 +77,7 @@ namespace Apewer.Source public string ConnectionString { get => _connectionstring; } /// - public bool Connect() + public string Connect() { if (_connection == null) { @@ -86,7 +86,7 @@ namespace Apewer.Source } else { - if (_connection.State == ConnectionState.Open) return true; + if (_connection.State == ConnectionState.Open) return null; } // try @@ -94,8 +94,8 @@ namespace Apewer.Source _connection.Open(); switch (_connection.State) { - case ConnectionState.Open: return true; - default: return false; + case ConnectionState.Open: return null; + default: return $"连接失败,当前处于 {_connection.State} 状态。"; } } // catch (Exception ex) @@ -138,7 +138,7 @@ namespace Apewer.Source /// 启动事务。 public string Begin(bool commit, Class isolation) { - if (!Connect()) return "未连接。"; + if (Connect() != null) return "未连接。"; if (_transaction != null) return "存在已启动的事务,无法再次启动。"; try { @@ -203,7 +203,7 @@ namespace Apewer.Source if (sql.IsBlank()) return Example.InvalidQueryStatement; var connected = Connect(); - if (!connected) return Example.InvalidQueryConnection; + if (connected.NotEmpty()) return Example.InvalidQueryConnection; try { @@ -244,7 +244,7 @@ namespace Apewer.Source if (sql.IsBlank()) return Example.InvalidExecuteStatement; var connected = Connect(); - if (!connected) return Example.InvalidExecuteConnection; + if (connected.NotEmpty()) return new Execute(false, connected); var inTransaction = _transaction != null; if (!inTransaction) Begin(); @@ -290,7 +290,7 @@ namespace Apewer.Source dps = new List(count); foreach (var p in parameters) { - var dp = CreateDataParameter(p); + var dp = Parameter(p); dps.Add(dp); } } @@ -385,7 +385,7 @@ namespace Apewer.Source var lower = column.Field.ToLower(); if (columns.Contains(lower)) continue; - var type = GetColumnDeclaration(column); + var type = Declaration(column); if (type.IsEmpty()) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); // alter table `_record` add column `_index` bigint; @@ -407,7 +407,7 @@ namespace Apewer.Source if (structure.Independent && column.Independent) continue; // 字段。 - var type = GetColumnDeclaration(column); + var type = Declaration(column); if (type.IsEmpty()) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); columns.Add(type); columnsAdded++; @@ -451,10 +451,11 @@ namespace Apewer.Source } // 连接数据库。 - if (!Connect()) + var connect = Connect(); + if (connect.NotEmpty()) { sql = null; - return "连接数据库失败。"; + return $"连接数据库失败。({connect})"; } sql = GetCreateStetement(structure); @@ -476,24 +477,40 @@ namespace Apewer.Source public string Initialize(Record model) => (model == null) ? "参数无效。" : Initialize(model.GetType()); /// 插入记录。返回错误信息。 - public string Insert(object record) + public string Insert(object record, string table = null) { if (record == null) return "参数无效。"; OrmHelper.FixProperties(record); var structure = TableStructure.Parse(record.GetType()); if (structure == null) return "无法解析记录模型。"; + if (string.IsNullOrEmpty(table)) table = structure.Name; + if (string.IsNullOrEmpty(table)) return "表名称无效。"; - var parameters = structure.CreateParameters(record, CreateDataParameter); - var sql = GenerateInsertStatement(structure.Name, parameters); - var execute = Execute(sql, parameters); + var ps = structure.CreateParameters(record, Parameter); + var psc = ps.Length; + if (psc < 1) return "数据模型不包含字段。"; + + var names = new List(psc); + var values = new List(psc); + foreach (var p in ps) + { + var pn = p.ParameterName; + names.Add("`" + p + "`"); + values.Add("@" + p); + } + var ns = string.Join(", ", names); + var vs = string.Join(", ", values); + var sql = $"insert into `{table}` ({ns}) values ({vs}); "; + + var execute = Execute(sql, ps); if (execute.Success) return TextUtility.Empty; return execute.Message; } /// 更新记录,实体中的 Key 属性不被更新。返回错误信息。 /// 无法更新带有 Independent 特性的模型(缺少 Key 属性)。 - public string Update(IRecord record) + public string Update(IRecord record, string table = null) { if (record == null) return "参数无效。"; FixProperties(record); @@ -502,10 +519,23 @@ namespace Apewer.Source var structure = TableStructure.Parse(record.GetType()); if (structure == null) return "无法解析记录模型。"; if (structure.Independent) return "无法更新带有 Independent 特性的模型。"; + if (string.IsNullOrEmpty(table)) table = structure.Name; + if (string.IsNullOrEmpty(table)) return "表名称无效。"; + + var ps = structure.CreateParameters(record, Parameter, "_key"); + var psc = ps.Length; + if (psc < 1) return "数据模型不包含字段。"; + + var items = new List(psc); + foreach (var p in ps) + { + var pn = p.ParameterName; + items.Add(TextUtility.Merge("`", pn, "` = @", pn)); + } + var key = record.Key.SafeKey(); + var sql = $"update `{table}` set {string.Join(", ", items)} where `_key`='{key}'; "; - var parameters = structure.CreateParameters(record, CreateDataParameter, "_key"); - var sql = GenerateUpdateStatement(structure, record.Key, parameters); - var execute = Execute(sql, parameters); + var execute = Execute(sql, ps); if (execute.Success) return TextUtility.Empty; return execute.Message; } @@ -514,7 +544,16 @@ namespace Apewer.Source public Result Query(Type model, string sql) => OrmHelper.Query(this, model, sql); /// - public Result Query(string sql) where T : class, new() => OrmHelper.Query(this, sql); + public Result Query(string sql) where T : class, new() + { + var query = Query(sql); + if (!query.Success) return new Result(query.Message); + var records = query.Fill(); + query.Dispose(); + + var result = new Result(records); + return result; + } /// 获取所有记录。Flag 为 0 时将忽略 Flag 条件。 public Result Query(Type model, long flag = 0) => OrmHelper.Query(this, model, (tn) => @@ -552,7 +591,7 @@ namespace Apewer.Source } /// 获取记录。 - public Result Get(Type model, string key, long flag = 0) => OrmHelper.Get(this, model, key, (tn, sk) => + public Result Get(Type model, string key, long flag = 0) => OrmHelper.Get(this, model, key, (tn, sk) => { if (flag == 0) return $"select * from `{tn}` where `_key`='{sk}' limit 1;"; return $"select * from `{tn}` where `_key`='{sk}' and `_flag`={flag} limit 1;"; @@ -685,14 +724,14 @@ namespace Apewer.Source /// /// /// - internal static MySqlParameter CreateDataParameter(Parameter parameter) + internal static MySqlParameter Parameter(Parameter parameter) { if (parameter == null) throw new InvalidOperationException("参数无效。"); - return CreateDataParameter(parameter.Name, parameter.Type, parameter.Size, parameter.Value); + return Parameter(parameter.Name, parameter.Type, parameter.Size, parameter.Value); } /// - internal static MySqlParameter CreateDataParameter(string name, ColumnType type, Int32 size, object value) + internal static MySqlParameter Parameter(string name, ColumnType type, Int32 size, object value) { if (TextUtility.IsBlank(name)) return null; @@ -756,7 +795,7 @@ namespace Apewer.Source } /// - internal static MySqlParameter CreateDataParameter(string name, MySqlDbType type, object value, Int32 size = 0) + internal static MySqlParameter Parameter(string name, MySqlDbType type, object value, Int32 size = 0) { var parameter = new MySqlParameter(); parameter.ParameterName = name; @@ -766,7 +805,7 @@ namespace Apewer.Source return parameter; } - private static string GetColumnDeclaration(ColumnAttribute column) + private static string Declaration(ColumnAttribute column) { var type = TextUtility.Empty; var length = Math.Max(0, (int)column.Length); @@ -814,156 +853,11 @@ namespace Apewer.Source return TextUtility.Merge("`", (string)column.Field, "` ", type); } - private static string GetParameterName(string parameter) - { - var name = TextUtility.AntiInject(parameter, 255); - if (name.StartsWith("@") && name.Length > 1) - { - name = name.Substring(1, name.Length - 1); - } - return name; - } - - private static string GetParameterName(IDataParameter parameter) - { - var name = TextUtility.Empty; - if (parameter != null) - { - name = GetParameterName(parameter.ParameterName); - } - return name; - } - - private static List GetParametersNames(IEnumerable parameters) - { - var columns = new List(); - if (parameters != null) - { - columns.Capacity = parameters.Count(); - var columnsAdded = 0; - foreach (var parameter in parameters) - { - var name = GetParameterName(parameter); - var isblank = TextUtility.IsBlank(name); - if (isblank) continue; - columns.Add(name); - columnsAdded++; - } - columns.Capacity = columnsAdded; - } - return columns; - } - - private static string GenerateInsertStatement(string table, List columns) - { - var sql = TextUtility.Empty; - var tn = TextUtility.AntiInject(table, 255); - if (columns != null && !TextUtility.IsBlank(tn)) - { - var count = columns.Count; - var names = new List(count); - var values = new List(count); - foreach (var column in columns) - { - if (string.IsNullOrEmpty(column)) continue; - names.Add($"`{column}`"); - values.Add($"@{column}"); - } - - var ns = string.Join(", ", names); - var vs = string.Join(", ", values); - - sql = $"insert into `{tn}` ({ns}) values ({vs}); "; - - // var sb = new StringBuilder(); - // if (columns.Count > 0) - // { - // sb.Append("insert into `"); - // sb.Append(tn); - // sb.Append("` ("); - // for (var i = 0; i < columns.Count; i++) - // { - // if (i > 0) sb.Append(", "); - // sb.Append("`"); - // sb.Append(columns[i]); - // sb.Append("`"); - // } - // sb.Append(") values ("); - // for (var i = 0; i < columns.Count; i++) - // { - // if (i > 0) sb.Append(", "); - // sb.Append("@"); - // sb.Append(columns[i]); - // } - // sb.Append("); "); - // } - // sql = sb.ToString(); - } - return sql; - } - - private static string GenerateUpdateStatement(TableStructure structure, string key, List columns) - { - var result = TextUtility.Empty; - - var table = TextUtility.AntiInject(structure.Name, 255); - if (TextUtility.IsEmpty(table)) return result; - - var safekey = TextUtility.AntiInject(key, 255); - if (TextUtility.IsEmpty(safekey)) return result; - - var count = columns == null ? -1 : columns.Count; - if (count < 1) return result; - - var items = new List(count); - foreach (var column in columns) - { - items.Add(TextUtility.Merge("`", column, "`=@", column)); - } - result = TextUtility.Merge("update `", table, "` set ", string.Join(", ", items), " where `_key`='", safekey, "'; "); - return result; - } - - /// 生成 INSERT INTO 语句。表名必须有效,无有效参数时将获取空结果。 - /// - /// - public static string GenerateInsertStatement(string table, IEnumerable parameters) - { - if (table == null) throw new ArgumentNullException("table"); - var tableName = TextUtility.AntiInject(table, 255); - if (TextUtility.IsBlank(tableName)) throw new ArgumentException("表名无效。", "table"); - - var columns = GetParametersNames(parameters); - if (columns.Count < 1) return TextUtility.Empty; - - return GenerateInsertStatement(tableName, columns); - } - - /// 生成 UPDATE 语句,键字段名为“_key”。表名必须有效,键值必须有效,无有效参数时将获取空结果。 - /// - /// - public static string GenerateUpdateStatement(TableStructure structure, string key, IEnumerable parameters) - { - if (structure == null) throw new ArgumentNullException("structure"); - if (key == null) throw new ArgumentNullException("key"); - - var table = TextUtility.AntiInject(structure.Name, 255); - if (TextUtility.IsBlank(table)) throw new ArgumentException("表名无效。", "structure"); - - var safekey = TextUtility.AntiInject(key, 255); - if (TextUtility.IsBlank(safekey)) throw new ArgumentException("键值无效。", "key"); - - var columns = GetParametersNames(parameters); - if (columns.Count < 1) return TextUtility.Empty; - - return GenerateUpdateStatement(structure, safekey, columns); - } - /// 获取每个数据库中,每个表的容量,单位为字节。 public static Dictionary> GetTablesCapacity(MySql source) { var result = new Dictionary>(); - if (source != null && source.Connect()) + if (source != null && source.Connect().IsEmpty()) { var sql = "select `table_schema`, `table_name`, `engine`, `data_length`, `index_length` from `information_schema`.tables order by `table_schema`, `table_name`"; using (var query = (Query)source.Query(sql)) diff --git a/Apewer.Source/Source/SqlClient.cs b/Apewer.Source/Source/SqlClient.cs index b438462..08e1513 100644 --- a/Apewer.Source/Source/SqlClient.cs +++ b/Apewer.Source/Source/SqlClient.cs @@ -1,4 +1,4 @@ -/* 2021.10.14 */ +/* 2021.11.07 */ using Apewer; using Apewer.Source; @@ -6,15 +6,14 @@ using System; using System.Collections.Generic; using System.Data; using System.Data.Common; -using System.Data.SqlClient; using System.Net; using System.Text; using static Apewer.Source.OrmHelper; +using System.Data.SqlClient; #if NETFRAMEWORK using System.Data.Sql; -#else #endif namespace Apewer.Source @@ -48,10 +47,10 @@ namespace Apewer.Source { if (timeout == null) timeout = Timeout.Default; - var a = TextUtility.AntiInject(address); - var s = TextUtility.AntiInject(store); - var u = TextUtility.AntiInject(user); - var p = TextUtility.AntiInject(pass); + var a = address ?? ""; + var s = store ?? ""; + var u = user ?? ""; + var p = pass ?? ""; var cs = $"data source = {a}; initial catalog = {s}; "; if (string.IsNullOrEmpty(u)) cs += "integrated security = sspi; "; else @@ -88,7 +87,7 @@ namespace Apewer.Source } /// 连接数据库,若未连接则尝试连接,获取连接成功的状态。 - public bool Connect() + public string Connect() { if (_db == null) { @@ -97,22 +96,22 @@ namespace Apewer.Source } else { - if (_db.State == ConnectionState.Open) return true; + if (_db.State == ConnectionState.Open) return null; } try { _db.Open(); switch (_db.State) { - case ConnectionState.Open: return true; - default: return false; + case ConnectionState.Open: return null; + default: return $"连接失败,当前处于 {_db.State} 状态。"; } } catch (Exception ex) { Logger.Error(nameof(SqlClient), "Connection", ex, _db.ConnectionString); Close(); - return false; + return ex.Message; } } @@ -148,7 +147,8 @@ namespace Apewer.Source /// 启动事务。 public string Begin(bool commit, Class isolation) { - if (!Connect()) return "未连接。"; + var connect = Connect(); + if (connect.NotEmpty()) return connect; if (_transaction != null) return "存在已启动的事务,无法再次启动。"; try { @@ -215,7 +215,7 @@ namespace Apewer.Source { if (TextUtility.IsBlank(sql)) return Example.InvalidQueryStatement; var connected = Connect(); - if (!connected) return Example.InvalidQueryConnection; + if (connected.NotEmpty()) return new Query(false, connected); try { @@ -259,7 +259,7 @@ namespace Apewer.Source if (TextUtility.IsBlank(sql)) return Example.InvalidExecuteStatement; var connected = Connect(); - if (!connected) return Example.InvalidExecuteConnection; + if (connected.NotEmpty()) return new Execute(false, connected); var inTransaction = _transaction != null; if (!inTransaction) Begin(); @@ -299,7 +299,7 @@ namespace Apewer.Source public string[] TableNames() { var list = new List(); - if (Connect()) + if (Connect().IsEmpty()) { var sql = "select [name] from [sysobjects] where [type] = 'u' order by [name]; "; var query = (Query)Query(sql); @@ -318,7 +318,7 @@ namespace Apewer.Source public string[] StoreNames() { var list = new List(); - if (Connect()) + if (Connect().IsEmpty()) { var sql = "select [name] from [master]..[sysdatabases] order by [name]; "; var query = (Query)Query(sql); @@ -341,7 +341,7 @@ namespace Apewer.Source public string[] ColumnNames(string tableName) { var list = new List(); - if (Connect()) + if (Connect().IsEmpty()) { var table = TextUtility.AntiInject(tableName); var sql = TextUtility.Merge("select [name] from [syscolumns] where [id] = object_id('", table, "'); "); @@ -357,6 +357,60 @@ namespace Apewer.Source return list.ToArray(); } + static string XType(int xtype) + { + switch (xtype) + { + case 34: return "image"; + case 35: return "text"; + case 36: return "uniqueidentifier"; + case 48: return "tinyint"; + case 52: return "smallint"; + case 56: return "int"; + case 58: return "smalldatetime"; + case 59: return "real"; + case 60: return "money"; + case 61: return "datetime"; + case 62: return "float"; + case 98: return "sql_variant"; + case 99: return "ntext"; + case 104: return "bit"; + case 106: return "decimal"; + case 108: return "numeric"; + case 122: return "smallmoney"; + case 127: return "bigint"; + case 165: return "varbinary"; + case 167: return "varchar"; + case 173: return "binary"; + case 175: return "char"; + case 189: return "timestamp"; + case 231: return "nvarchar"; + case 239: return "nchar"; + case 241: return "xml"; + } + return null; + } + + /// 获取列信息。 + public ColumnInfo[] ColumnsInfo(string tableName) + { + if (tableName.IsEmpty()) throw new ArgumentNullException(nameof(tableName)); + var sql = $"select name, xtype, length from syscolumns where id = object_id('{tableName}') "; + using (var query = Query(sql)) + { + var ab = new ArrayBuilder(); + for (var i = 0; i < query.Rows; i++) + { + var info = new ColumnInfo(); + info.Name = query.Text(i, "name"); + info.Type = XType(query.Int32(i, "xtype")); + info.Length = query.Int32(i, "length"); + ab.Add(info); + } + return ab.Export(); + } + } + /// 创建表,当表不存在时创建表,当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。 public string Initialize() where T : class, new() => Initialize(typeof(T)); @@ -367,7 +421,8 @@ namespace Apewer.Source if (structure == null) return "无法解析记录模型。"; // 连接数据库。 - if (!Connect()) return "连接数据库失败。"; + var connect = Connect(); + if (connect.NotEmpty()) return connect; // 检查现存表。 var exists = false; @@ -411,8 +466,8 @@ namespace Apewer.Source var lower = column.Field.ToLower(); if (columns.Contains(lower)) continue; - var type = GetColumnDeclaration(column); - if (type == TextUtility.Empty) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); + var type = Declaration(column); + if (string.IsNullOrEmpty(type)) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); var sql = TextUtility.Merge("alter table [", structure.Name, "] add ", type, "; "); var execute = Execute(sql); @@ -428,10 +483,10 @@ namespace Apewer.Source // 检查 Independent 特性。 if (structure.Independent && column.Independent) continue; - var type = GetColumnDeclaration(column); + var type = Declaration(column); if (!column.Independent && column.Property.Name == "Key") type = type + " primary key"; - if (type == TextUtility.Empty) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); + if (string.IsNullOrEmpty(type)) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); sqlcolumns.Add(type); } var sql = TextUtility.Merge("create table [", structure.Name, "](", string.Join(", ", sqlcolumns.ToArray()), "); "); @@ -442,24 +497,41 @@ namespace Apewer.Source } /// 插入记录。返回错误信息。 - public string Insert(object record) + public string Insert(object record, string table = null) { if (record == null) return "参数无效。"; FixProperties(record); var structure = TableStructure.Parse(record.GetType()); if (structure == null) return "无法解析记录模型。"; + if (string.IsNullOrEmpty(table)) table = structure.Name; + if (string.IsNullOrEmpty(table)) return "表名称无效。"; + + var ps = structure.CreateParameters(record, Parameter); + var psc = ps.Length; + if (psc < 1) return "数据模型不包含字段。"; + + var names = new List(psc); + var values = new List(psc); + foreach (var column in ps) + { + //names.Add(TextGenerator.Merge("[", column, "]")); + names.Add(TextUtility.Merge(column)); + values.Add("@" + column); + } + var sb = new StringBuilder(); + sb.Append("insert into [", table, "](", string.Join(", ", names.ToArray()), ") "); + sb.Append("values(", string.Join(", ", values.ToArray()), "); "); + var sql = sb.ToString(); - var parameters = structure.CreateParameters(record, CreateDataParameter); - var sql = GenerateInsertStatement(structure.Name, parameters); - var execute = Execute(sql, parameters); + var execute = Execute(sql, ps); if (execute.Success) return TextUtility.Empty; return execute.Message; } /// 更新记录,实体中的 Key 属性不被更新。返回错误信息。 /// 无法更新带有 Independent 特性的模型(缺少 Key 属性)。 - public string Update(IRecord record) + public string Update(IRecord record, string table = null) { if (record == null) return "参数无效。"; FixProperties(record); @@ -468,10 +540,23 @@ namespace Apewer.Source var structure = TableStructure.Parse(record.GetType()); if (structure == null) return "无法解析记录模型。"; if (structure.Independent) return "无法更新带有 Independent 特性的模型。"; + if (string.IsNullOrEmpty(table)) table = structure.Name; + if (string.IsNullOrEmpty(table)) return "表名称无效。"; + + var ps = structure.CreateParameters(record, Parameter, "_key"); + var psc = ps.Length; + if (psc < 1) return "数据模型不包含字段。"; + + var items = new List(); + foreach (var p in ps) + { + var pn = p.ParameterName; + items.Add(TextUtility.Merge("[", pn, "] = @", pn)); + } + var key = record.Key.SafeKey(); + var sql = TextUtility.Merge("update [", table, "] set ", string.Join(", ", items.ToArray()), " where [_key]='", key, "'; "); - var parameters = structure.CreateParameters(record, CreateDataParameter, "_key"); - var sql = GenerateUpdateStatement(structure.Name, record.Key, parameters); - var execute = Execute(sql, parameters); + var execute = Execute(sql, ps); if (execute.Success) return TextUtility.Empty; return execute.Message; } @@ -480,7 +565,16 @@ namespace Apewer.Source public Result Query(Type model, string sql) => OrmHelper.Query(this, model, sql); /// 获取按指定语句查询到的所有记录。 - public Result Query(string sql) where T : class, new() => OrmHelper.Query(this, sql); + public Result Query(string sql) where T : class, new() + { + var query = Query(sql); + if (!query.Success) return new Result(query.Message); + var records = query.Fill(); + query.Dispose(); + + var result = new Result(records); + return result; + } /// 获取记录。 public Result Query(Type model, long flag = 0) => OrmHelper.Query(this, model, (tn) => @@ -497,7 +591,7 @@ namespace Apewer.Source }); /// 获取具有指定 Key 的记录。 - public Result Get(Type model, string key, long flag = 0) => OrmHelper.Get(this, model, key, (tn, sk) => + public Result Get(Type model, string key, long flag = 0) => OrmHelper.Get(this, model, key, (tn, sk) => { if (flag == 0) return $"select top 1 * from [{tn}] _key='{sk}'; "; return $"select top 1 * from [{tn}] where _key='{sk}' and _key='{sk}'; "; @@ -524,7 +618,7 @@ namespace Apewer.Source #region public static -#if NETFRAMEWORK +#if NET20 || NET40 /// 枚举本地网络中服务器的名称。 public static SqlServerSource[] EnumerateServer() @@ -565,14 +659,14 @@ namespace Apewer.Source /// 创建参数。 /// /// - internal static SqlParameter CreateDataParameter(Parameter parameter) + static SqlParameter Parameter(Parameter parameter) { if (parameter == null) throw new InvalidOperationException("参数无效。"); - return CreateDataParameter(parameter.Name, parameter.Type, parameter.Size, parameter.Value); + return Parameter(parameter.Name, parameter.Type, parameter.Size, parameter.Value); } /// 创建参数。 - public static SqlParameter CreateDataParameter(string name, ColumnType type, Int32 size, object value) + public static SqlParameter Parameter(string name, ColumnType type, int size, object value) { var vname = TextUtility.Trim(name); if (TextUtility.IsBlank(vname)) return null; @@ -600,7 +694,7 @@ namespace Apewer.Source case ColumnType.NVarChar: case ColumnType.NVarChar191: case ColumnType.NVarCharMax: - vtype = SqlDbType.VarChar; + vtype = SqlDbType.NVarChar; break; case ColumnType.Text: vtype = SqlDbType.Text; @@ -645,7 +739,7 @@ namespace Apewer.Source } /// 创建参数。 - public static SqlParameter CreateDataParameter(String name, SqlDbType type, Int32 size, Object value) + public static SqlParameter Parameter(string name, SqlDbType type, int size, object value) { if (value is string && value != null && size > 0) { @@ -661,7 +755,7 @@ namespace Apewer.Source } /// 创建参数。 - public static SqlParameter CreateDataParameter(String name, SqlDbType type, Object value) + public static SqlParameter Parameter(string name, SqlDbType type, object value) { var p = new SqlParameter(); p.ParameterName = name ?? ""; @@ -670,11 +764,7 @@ namespace Apewer.Source return p; } - #endregion - - #region private - - static string GetColumnDeclaration(ColumnAttribute column) + static string Declaration(ColumnAttribute column) { var type = TextUtility.Empty; var vcolumn = column; @@ -709,7 +799,7 @@ namespace Apewer.Source type = TextUtility.Merge("nvarchar(", Math.Min(4000, length).ToString(), ")"); break; case ColumnType.NVarChar191: - type = TextUtility.Merge("nvarchar(255)"); + type = TextUtility.Merge("nvarchar(191)"); break; case ColumnType.NVarCharMax: type = TextUtility.Merge("nvarchar(max)"); @@ -723,123 +813,6 @@ namespace Apewer.Source return TextUtility.Merge("[", vcolumn.Field, "] ", type); } - static string GetParameterName(string parameter) - { - var name = TextUtility.AntiInject(parameter, 255); - if (name.StartsWith("@") && name.Length > 1) - { - name = name.Substring(1, name.Length - 1); - } - return name; - } - - static string GetParameterName(IDataParameter parameter) - { - var name = TextUtility.Empty; - if (parameter != null) - { - name = GetParameterName(parameter.ParameterName); - } - return name; - } - - static string[] GetParametersNames(IEnumerable parameters) - { - var columns = new List(); - if (parameters != null) - { - foreach (var parameter in parameters) - { - var name = GetParameterName(parameter); - var isblank = TextUtility.IsBlank(name); - if (isblank) continue; - columns.Add(name); - } - } - return columns.ToArray(); - } - - static string GenerateInsertStatement(string table, string[] columns) - { - var result = TextUtility.Empty; - var vtable = TextUtility.AntiInject(table, 255); - if (columns != null && !TextUtility.IsBlank(vtable)) - { - var count = 0; - var names = new List(); - var values = new List(); - foreach (var column in columns) - { - //names.Add(TextGenerator.Merge("[", column, "]")); - names.Add(TextUtility.Merge(column)); - values.Add("@" + column); - count += 1; - } - var text = new StringBuilder(); - if (count > 0) - { - text.Append("insert into [", vtable, "](", string.Join(", ", names.ToArray()), ") "); - text.Append("values(", string.Join(", ", values.ToArray()), "); "); - } - result = text.ToString(); - } - return result; - } - - static string GenerateUpdateStatement(string table, string key, string[] columns) - { - var result = TextUtility.Empty; - var vtable = TextUtility.AntiInject(table, 255); - var vkey = TextUtility.AntiInject(key, 255); - if (columns != null && !TextUtility.IsBlank(vtable) && !TextUtility.IsBlank(vkey)) - { - var items = new List(); - foreach (var column in columns) - { - items.Add(TextUtility.Merge("[", column, "]=@", column)); - } - if (items.Count > 0) - { - result = TextUtility.Merge("update [", vtable, "] set ", string.Join(", ", items.ToArray()), " where [_key]='", vkey, "'; "); - } - } - return result; - } - - /// 生成 INSERT INTO 语句。表名必须有效,无有效参数时将获取空结果。 - /// - /// - static string GenerateInsertStatement(string table, IEnumerable parameters) - { - if (table == null) throw new ArgumentNullException(nameof(table)); - var tableName = TextUtility.AntiInject(table, 255); - if (TextUtility.IsBlank(tableName)) throw new ArgumentException("表名无效。", nameof(table)); - - var vcolumns = GetParametersNames(parameters); - if (vcolumns.Length < 1) return TextUtility.Empty; - - return GenerateInsertStatement(tableName, vcolumns); - } - - /// 生成 UPDATE 语句,键字段名为“_key”。表名必须有效,键值必须有效,无有效参数时将获取空结果。 - /// - /// - static string GenerateUpdateStatement(string table, string key, IEnumerable parameters) - { - if (table == null) throw new ArgumentNullException(nameof(table)); - var t = TextUtility.AntiInject(table, 255); - if (TextUtility.IsBlank(t)) throw new ArgumentException("表名无效。", nameof(table)); - - if (key == null) throw new ArgumentNullException("argKey"); - var k = TextUtility.AntiInject(key, 255); - if (TextUtility.IsBlank(k)) throw new ArgumentException("键值无效。", nameof(table)); - - var columes = GetParametersNames(parameters); - if (columes.Length < 1) return TextUtility.Empty; - - return GenerateUpdateStatement(t, k, columes); - } - #endregion } diff --git a/Apewer.Source/Source/Sqlite.cs b/Apewer.Source/Source/Sqlite.cs index 3effe56..bc3e89e 100644 --- a/Apewer.Source/Source/Sqlite.cs +++ b/Apewer.Source/Source/Sqlite.cs @@ -1,4 +1,4 @@ -/* 2021.10.14 */ +/* 2021.11.07 */ using System; using System.Collections.Generic; @@ -39,7 +39,7 @@ namespace Apewer.Source _timeout = timeout ?? Timeout.Default; _path = path.IsEmpty() ? Memory : path; _pass = pass; - if (pass.IsEmpty()) _connstring = $"data source='{_path}'; password={_pass}; version=3; "; + if (pass.IsEmpty()) _connstring = $"data source='{_path}'; version=3; "; else _connstring = $"data source='{_path}'; password={_pass}; version=3; "; } @@ -60,7 +60,7 @@ namespace Apewer.Source /// 连接数据库,若未连接则尝试连接。 /// 是否已连接。 - public bool Connect() + public string Connect() { if (_db == null) { @@ -69,22 +69,22 @@ namespace Apewer.Source } else { - if (_db.State == ConnectionState.Open) return true; + if (_db.State == ConnectionState.Open) return null; } try { _db.Open(); switch (_db.State) { - case ConnectionState.Open: return true; - default: return false; + case ConnectionState.Open: return null; + default: return $"连接失败,当前处于 {_db.State} 状态。"; } } catch (Exception ex) { Logger.Error(nameof(Sqlite), "Connection", ex, _db.ConnectionString); Close(); - return false; + return ex.Message; } } @@ -122,7 +122,8 @@ namespace Apewer.Source /// 启动事务。 public string Begin(bool commit, Class isolation) { - if (!Connect()) return "未连接。"; + var connect = Connect(); + if (connect.NotEmpty()) return connect; if (_transaction != null) return "存在已启动的事务,无法再次启动。"; try { @@ -190,7 +191,7 @@ namespace Apewer.Source if (string.IsNullOrEmpty(sql)) return Example.InvalidQueryStatement; var connected = Connect(); - if (!connected) return Example.InvalidQueryConnection; + if (connected.NotEmpty()) return new Query(false, connected); var query = new Query(); try @@ -235,7 +236,7 @@ namespace Apewer.Source if (string.IsNullOrEmpty(sql)) return Example.InvalidExecuteStatement; var connected = Connect(); - if (!connected) return Example.InvalidExecuteConnection; + if (connected.NotEmpty()) return new Execute(false, connected); lock (_locker) { @@ -306,7 +307,7 @@ namespace Apewer.Source public string[] TableNames() { var list = new List(); - if (Connect()) + if (Connect().IsEmpty()) { var sql = "select name from sqlite_master where type='table' order by name; "; var query = (Query)Query(sql); @@ -325,7 +326,7 @@ namespace Apewer.Source public string[] ViewNames() { var list = new List(); - if (Connect()) + if (Connect().IsEmpty()) { var sql = "select name from sqlite_master where type='view' order by name; "; var query = (Query)Query(sql); @@ -344,7 +345,7 @@ namespace Apewer.Source public string[] ColumnNames(string table) { var list = new List(); - if (Connect()) + if (Connect().IsEmpty()) { var t = TextUtility.AntiInject(table); var sql = TextUtility.Merge("pragma table_info('", TextUtility.AntiInject(t), "'); "); @@ -374,7 +375,8 @@ namespace Apewer.Source if (structure == null) return "无法解析记录模型。"; // 连接数据库。 - if (!Connect()) return "连接数据库失败。"; + var connect = Connect(); + if (connect.NotEmpty()) return connect; // 检查现存表。 var exists = false; @@ -402,8 +404,11 @@ namespace Apewer.Source var sqlcolumns = new List(); foreach (var column in structure.Columns) { - var type = GetColumnDeclaration(column); - if (type == TextUtility.Empty) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); + var type = Declaration(column); + if (string.IsNullOrEmpty(type)) + { + return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); + } sqlcolumns.Add(type); } var sql = TextUtility.Merge("create table [", structure.Name, "](", TextUtility.Join(", ", sqlcolumns), "); "); @@ -414,36 +419,88 @@ namespace Apewer.Source } /// 插入记录。返回错误信息。 - public string Insert(object record) + public string Insert(object record, string table = null) { if (record == null) return "参数无效。"; OrmHelper.FixProperties(record); + // 解析模型,获取表名。 var structure = TableStructure.Parse(record.GetType()); if (structure == null) return "无法解析记录模型。"; + if (string.IsNullOrEmpty(table)) table = structure.Name; + if (string.IsNullOrEmpty(table)) return "表名称无效。"; + + // 准备参数。 + var ps = structure.CreateParameters(record, Parameter); + var psc = ps.Length; + if (psc < 1) return "数据模型不包含字段。"; + + // 合成 SQL 语句。 + var sb = new StringBuilder(); + sb.Append("insert into ["); + sb.Append(table); + sb.Append("]("); + for (var i = 0; i < psc; i++) + { + if (i > 0) sb.Append(", "); + sb.Append(ps[i].ParameterName); + } + sb.Append(") values("); + for (var i = 0; i < psc; i++) + { + if (i > 0) sb.Append(", "); + sb.Append("@"); + sb.Append(ps[i].ParameterName); + } + sb.Append("); "); + var sql = sb.ToString(); - var parameters = structure.CreateParameters(record, CreateDataParameter); - var sql = GenerateInsertStatement(structure.Name, (IEnumerable)parameters); - var execute = Execute(sql, parameters); + // 执行。 + var execute = Execute(sql, ps); if (execute.Success && execute.Rows > 0) return TextUtility.Empty; return execute.Message; } /// 更新记录,实体中的 Key 属性不被更新。返回错误信息。 /// 无法更新带有 Independent 特性的模型(缺少 Key 属性)。 - public string Update(IRecord record) + public string Update(IRecord record, string table = null) { if (record == null) return "参数无效。"; FixProperties(record); SetUpdated(record); + // 解析模型,获取表名。 var structure = TableStructure.Parse(record.GetType()); if (structure == null) return "无法解析记录模型。"; if (structure.Independent) return "无法更新带有 Independent 特性的模型。"; - - var parameters = structure.CreateParameters(record, CreateDataParameter, "_key"); - var sql = GenerateUpdateStatement(structure.Name, record.Key, parameters); - var execute = Execute(sql, parameters); + if (string.IsNullOrEmpty(table)) table = structure.Name; + if (string.IsNullOrEmpty(table)) return "表名称无效。"; + + // 准备参数。 + var ps = structure.CreateParameters(record, Parameter, "_key"); + var psc = ps.Length; + if (psc < 1) return "数据模型不包含字段。"; + + // 合成 SQL 语句。 + var sb = new StringBuilder(); + sb.Append("update ["); + sb.Append(table); + sb.Append("] set "); + for (var i = 0; i < psc; i++) + { + if (i > 0) sb.Append(", "); + sb.Append("["); + sb.Append(ps[i].ParameterName); + sb.Append("] = @"); + sb.Append(ps[i].ParameterName); + } + sb.Append(" where [_key] = '"); + sb.Append(record.Key.SafeKey()); + sb.Append("'; "); + var sql = sb.ToString(); + + // 执行。 + var execute = Execute(sql, ps); if (execute.Success && execute.Rows > 0) return TextUtility.Empty; return execute.Message; } @@ -452,7 +509,16 @@ namespace Apewer.Source public Result Query(Type model, string sql) => OrmHelper.Query(this, model, sql); /// 获取按指定语句查询到的所有记录。 - public Result Query(string sql) where T : class, new() => OrmHelper.Query(this, sql); + public Result Query(string sql) where T : class, new() + { + var query = Query(sql); + if (!query.Success) return new Result(query.Message); + var records = query.Fill(); + query.Dispose(); + + var result = new Result(records); + return result; + } /// 查询多条记录。 public Result Query(Type model, long flag = 0) => OrmHelper.Query(this, model, (tn) => @@ -469,7 +535,7 @@ namespace Apewer.Source }); /// 获取具有指定 Key 的记录。 - public Result Get(Type model, string key, long flag = 0) => OrmHelper.Get(this, model, key, (tn, sk) => + public Result Get(Type model, string key, long flag = 0) => OrmHelper.Get(this, model, key, (tn, sk) => { if (flag == 0) return $"select * from [{tn}] where _key='{sk}' limit 1; "; return $"select * from [{tn}] where _flag={flag} and _key='{sk}' limit 1; "; @@ -553,7 +619,7 @@ namespace Apewer.Source } } - private static string GetColumnDeclaration(ColumnAttribute column) + private static string Declaration(ColumnAttribute column) { var type = TextUtility.Empty; var length = NumberUtility.Restrict(column.Length, 0, 255).ToString(); @@ -568,6 +634,9 @@ namespace Apewer.Source case ColumnType.Float: type = "real"; break; + case ColumnType.DateTime: + type = "datetime"; + break; case ColumnType.VarChar: type = TextUtility.Merge("varchar(", length, ")"); break; @@ -595,20 +664,20 @@ namespace Apewer.Source default: return TextUtility.Empty; } - return TextUtility.Merge("[", (object)column.Field, "] ", type); + return TextUtility.Merge("[", column.Field, "] ", type); } /// 创建参数。 /// /// - public static SQLiteParameter CreateDataParameter(Parameter parameter) + public static SQLiteParameter Parameter(Parameter parameter) { if (parameter == null) throw new InvalidOperationException("参数无效。"); - return CreateDataParameter(parameter.Name, parameter.Type, parameter.Size, parameter.Value); + return Parameter(parameter.Name, parameter.Type, parameter.Size, parameter.Value); } /// 创建参数。 - public static SQLiteParameter CreateDataParameter(string name, ColumnType type, int size, object value) + public static SQLiteParameter Parameter(string name, ColumnType type, int size, object value) { var n = TextUtility.Trim(name); if (TextUtility.IsBlank(n)) return null; @@ -652,28 +721,22 @@ namespace Apewer.Source } /// 创建参数。 - public static SQLiteParameter CreateDataParameter(string name, string type, int size, object value) + public static IDbDataParameter Parameter(string field, DbType type, int size, object value) { - var v = value; - if (value is string && value != null && size > 0) - { - v = TextUtility.Left((string)value, size); - } - var p = new SQLiteParameter(); - p.ParameterName = name; - p.TypeName = type; - p.Value = v; + p.ParameterName = field; + p.DbType = type; p.Size = size; + p.Value = value; return p; } /// 创建参数。 - public static SQLiteParameter CreateDataParameter(string name, string type, object value) + public static IDbDataParameter Parameter(string field, DbType type, object value) { var p = new SQLiteParameter(); - p.ParameterName = name; - p.TypeName = type; + p.ParameterName = field; + p.DbType = type; p.Value = value; return p; } @@ -681,10 +744,12 @@ namespace Apewer.Source /// 备份数据库,返回错误信息。 public static string Backup(Sqlite source, Sqlite destination) { - if (source == null) return "SQLite Backup Failed: Invalid Source"; - if (destination == null) return "SQLite Backup Failed: Invalid Destination"; - if (!source.Connect()) return "SQLite Backup Failed: Invalid Source Connection"; - if (!destination.Connect()) return "SQLite Backup Failed: Invalid Destination Connection"; + if (source == null) return "备份失败:源无效。"; + if (destination == null) return "备份失败:目标无效。"; + var sConnect = source.Connect(); + if (sConnect.NotEmpty()) return sConnect; + var dConnect = source.Connect(); + if (dConnect.NotEmpty()) return dConnect; lock (source._locker) { @@ -703,27 +768,6 @@ namespace Apewer.Source } } - /// 创建参数。 - public static IDbDataParameter CreateParameter(string field, DbType type, int size, object value) - { - var p = new SQLiteParameter(); - p.ParameterName = field; - p.DbType = type; - p.Size = size; - p.Value = value; - return p; - } - - /// 创建参数。 - public static IDbDataParameter CreateParameter(string field, DbType type, object value) - { - var p = new SQLiteParameter(); - p.ParameterName = field; - p.DbType = type; - p.Value = value; - return p; - } - /// 整理数据库,压缩未使用的空间。 public const string Vacuum = "vacuum"; @@ -732,127 +776,6 @@ namespace Apewer.Source #endregion - #region 生成 SQL 语句 - - /// 生成 INSERT INTO 语句。表名必须有效,无有效参数时将获取空结果。 - /// - /// - public static string GenerateInsertStatement(string table, IEnumerable parameters) - { - if (table == null) throw new ArgumentNullException(nameof(table)); - var t = TextUtility.AntiInject(table, 255); - if (TextUtility.IsBlank(t)) throw new ArgumentException("表名无效。", nameof(table)); - - var cs = GetParametersNames(parameters); - if (cs.Count < 1) return TextUtility.Empty; - - return GenerateInsertStatement(t, cs); - } - - private static string GetParameterName(string parameter) - { - var name = TextUtility.AntiInject(parameter, 255); - if (name.StartsWith("@") && name.Length > 1) - { - name = name.Substring(1, name.Length - 1); - } - return name; - } - - private static string GetParameterName(IDataParameter parameter) - { - var name = TextUtility.Empty; - if (parameter != null) - { - name = GetParameterName(parameter.ParameterName); - } - return name; - } - - private static List GetParametersNames(IEnumerable parameters) - { - var columns = new List(); - if (parameters != null) - { - foreach (var parameter in parameters) - { - var name = GetParameterName(parameter); - var isblank = TextUtility.IsBlank(name); - if (isblank) continue; - columns.Add(name); - } - } - return columns; - } - - private static string GenerateInsertStatement(string table, List columns) - { - var r = TextUtility.Empty; - var t = TextUtility.AntiInject(table, 255); - if (columns != null && !TextUtility.IsBlank(t)) - { - var count = 0; - var names = new List(); - var values = new List(); - foreach (var column in columns) - { - //names.Add(TextGenerator.Merge("[", column, "]")); - names.Add(TextUtility.Merge(column)); - values.Add("@" + column); - count += 1; - } - var sb = new StringBuilder(); - if (count > 0) - { - sb.Append("insert into [", t, "](", TextUtility.Join(", ", names), ") "); - sb.Append("values(", TextUtility.Join(", ", values), "); "); - } - r = sb.ToString(); - } - return r; - } - - private static string GenerateUpdateStatement(string table, string key, List columns) - { - var result = TextUtility.Empty; - var t = TextUtility.AntiInject(table, 255); - var k = TextUtility.AntiInject(key, 255); - if (columns != null && !TextUtility.IsBlank(t) && !TextUtility.IsBlank(k)) - { - var items = new List(); - foreach (var column in columns) - { - items.Add(TextUtility.Merge("[", column, "]=@", column)); - } - if (items.Count > 0) - { - result = TextUtility.Merge("update [", t, "] set ", TextUtility.Join(", ", items), " where [_key]='", k, "'; "); - } - } - return result; - } - - /// 生成 UPDATE 语句,键字段名为“_key”。表名必须有效,键值必须有效,无有效参数时将获取空结果。 - /// - /// - public static string GenerateUpdateStatement(string table, string key, IEnumerable parameters) - { - if (table == null) throw new ArgumentNullException("argTable"); - var t = TextUtility.AntiInject(table, 255); - if (TextUtility.IsBlank(t)) throw new ArgumentException("表名无效。", nameof(table)); - - if (key == null) throw new ArgumentNullException("argKey"); - var k = TextUtility.AntiInject(key, 255); - if (TextUtility.IsBlank(k)) throw new ArgumentException("键值无效。", nameof(key)); - - var columns = GetParametersNames(parameters); - if (columns.Count < 1) return TextUtility.Empty; - - return GenerateUpdateStatement(t, k, columns); - } - - #endregion - } } diff --git a/Apewer.Web/Apewer.Web.csproj b/Apewer.Web/Apewer.Web.csproj index d988989..50cc2ee 100644 --- a/Apewer.Web/Apewer.Web.csproj +++ b/Apewer.Web/Apewer.Web.csproj @@ -16,6 +16,13 @@ Apewer.Web + + + + + + + diff --git a/Apewer.Web/FavIcon.ico b/Apewer.Web/FavIcon.ico new file mode 100644 index 0000000000000000000000000000000000000000..0d7a0223e39becd36864c59f4e6fb63f846b57bb GIT binary patch literal 17542 zcmeI332;@_8G!H0LK3ov0D(X@b^;Q%AOr}jEV5R15W+evVP8XlJV2IeWlF7DI&`Lk zW3{yoR%`8Ou}`t!TC7@^>PTE!iV*eyf!FW9dFS=y=Dp;;mpH96+?hZBx##@b`Oi83 z`R_R|hT$;6jGP<;oMAlK#xU+O3?n1M^PXTBXQ@j`seIoZWf+g-8b$*3LccJ~FwzDX zMtgV=VH5yTFOL(x+s|ze-1b2IdtmqO-I2R??V1Ywp7ICaBfz7S$ATNyzsCBLh<%f= z^KzI?9x z-&gr0yz}4BqXSCP>%I4gqZLg#9+y`JDIa@GOi!CPdX z)8qRuz;^xY&;Mq<_&{U z!?uvZDby$wjU4RN~et;IF`5;9ejL2(cs6E)DY1@zome zQSfy@U$YNcdo0&(iILR_1`TR{(ug8|5(I zGNAJYQvMp=ses>jOIrl}ydStOdcakDq1Q>9=Y6oUkva$Prbc&^vR5DF!!^m&_>}A_ z`aL=ifUE6s)J4Ppl2yKk$LxzP-%~I0+iLdr}o*W zH$LP@yvh(dXi_NO&}pPU@~+p}EK3gA0)9C+F!Pw&Wa;f59WQG3=SiKX-fGiKkNvVf z=Q=MwO{|Dp?8(Z=hv(^5Jm+?+b5$uwL0C7~ZD9tANNe0O{z@2cVAR>Rt$~!>}znqKG$scosx^3 zc7V^EN7te8zD>P1uTgRW!N_`8mmNfTtga7v&x(CEVS&yYOc^}`Z0ho5)93Iu(Dl;) zF4UQOyFXuuKH5G5Z0F*-4!w*lIYTY=$g~x|HM zm-C`3zE^f2OTL|+7MVUk^5W%+wmvZO+5tCw=<=ms>V$S+O}gn@7$E03-+|3oL;0VS zy*WTl{DHP1`#FyuJF%$5u6*Q1BR2=3aJCi4Wq<^(*>6qM!W|{cyOqv-F#Q{w+xS5 z(JZn`TqZ6nahdeaB9~EO7`Kd-E+gGA%8eB+gRjt1qoBYDH4K+vGGM0{yD}=>=*9B$ zCmxp-Z!oJJE-c}3ykcUf<7&An&*82Rj1m9E=qrtI({pe<7(2?+u{Pc?Dx?=+>4g(J z1-r0Y@E7h%T-iUVss1j%)5-t_0N&qeCGc|lsj~;Vb?cU#pP%2WM~@yUbru$YfWdetggRvY=e6Aoa_4)rTb|C3{&f)$TO{F=+kz^_!6;=W^Bh=&8F3 zes9~hZNFK(c=7siM^aLfPd&YR_ij6E*s!7f`}gnGwQJXujEoF+nz}!I`}WP6J9qA~?c2Bi z0so$1Ei_Y7yL9QoZdLhAnKEUDWo+a=@12bsH}0P?V@AQ9cix$*Xlui5+O&y8=X~a_ z+_&j#zM_5m_GWBJO-+@2{1wL7%*Ffl>({}e-#Slj?%cWaa6v)A41D7~+qK#7>*q&y`{Fk>6OWy=;hv#vsG zd4FB;+;`u7FL~OovnOOqJ^fR4HOWwI*NLfeT6%FJHc0^e{$pH>A@Xm-^A8M-Q>o z>oh*g#l^)xL$3U8(`UP0KW*BysiMcT7IgYcQlFij-4J^7{zB)grF@jwR@=Cnx&{s$ zXr3!0?^9hSbHRJw%N%g&Z8pkBmMvSh!v@{i)~#Ecu?O0ZHTr)O1JkAd^05DJ)Y^0( z`7AT%_Y+Tt*2c4A$Bu)Q8VqB_iWLi>m)}jiMI7#=>IGv7s|Jji;`hpuY=XC8a! zO>S;(V|e6uY}M$=Wb}^cYj_Rn9qk8pMY~H z=Ei%v9BY}JWS_2El_yM?(3`m?d)<+>Yu6UDuY316MPmogpFiJk&W3HrI(P0IZAVV9 zG|ihg4`F5M2%afUim1~{Av1NbK~ z1qGCWoKnNcD>I;UI7`YLu9EW5Qg=B*%Mo6|dCz|z`8bh}XgN$#=M)8Xz6C>AJwMFd z@IiwH#gMNg39ui>un&YMCMMP?*G@=CaFDl0kw3K|*4|G}`6loh0+< z?Q6X|_SaqPY`AXSy20dca#wT#kiApj0H16rTy>FHsGg@xI~ z8o7IubA9u^QNwcYd669DGjgrP#KojL?zqEtJm(uDa`o!f#mJC5FZ2FDldW_~{Z(Sy zY2x|;^0_qjRa^Za4oA@U0(j;3Srlo?iJf9|a&oeR@6;&1&(pA{1lvA@SH|@+a01^BVvk4yJCRq2>=dwEJj*!9Sj!!= zjL!x5PUABdx+c@#c)rcUSd$Iev-?s14m9Th`F)+5GbfRkgN?0_`4ROpU*&h!vjDL{ z%_W7^oH~PF_t2k4;2g0TTA3S4uCnVmHe~?|fU~sA|5_9Qxq$qSn@+EE2)+*e8OBI{ zXSf#-yREd+k5_>#K>i;>^k0JS32-L(b+Grh&y;ofQWpBxfENMrwY6R9_5&jSEkNe~ zIr_Q-U#5T`;{6g}U|4_z5PUk)?_UWVorV{w?6l)-m!Oi@wMH0s1>N}d&q z{m;_Rc^*35KNZxUVs7@DIdf(#YqJk?fKQrb8M2P#yny+ZiQX^amHA_pE$_0%*CHdG zzDkUg{wTUD_~0+B;Y9M#5aLBRXTITl3wh5F$YT?j8^47|>{NUTUuSGfk(+(bJ@-U2 z?+dY4=9`SyHOi;y|9JKlzvpAyK4P0SYvwne&=$(sZYuOLhN|x(`!aQJ5g#+8&$5Qu1HWVjt}qrwo#=iVwEi1$Jnmq#u#s2ZOb zTDe~n*m}!hxCPu1a;IeeU%wofW&kP=8{nPgRMzms#l?m4%@N5tRzu=_L~LxV-}z=0 zlai7WLGICv{Yl2`C(PwZ?61klij0YgsZkEOS2+1mYu1&EIWIBfJoD-d^ZqdFeiUOB z&-nYzomj_FN{N!AS?!j0&i5nXUxd!%z$ti6 zVaK=3^S3DUk)~MU{BBUoDCd@?s-7Ym!<9o zwC~Z+Ec$(r*neR5?AfWrGsy!^)9yW|q%8Kh==T!rknxsrzDU3Ja%RzpI26MI%;M~_o!k#HhpOmNw7zg;WTb<6Z|V`OXv*`sqvl?^l#`KCY6V<1NDXxwhQKZt JchrXs<3HK>Gwc8W literal 0 HcmV?d00001 diff --git a/Apewer.Web/Internals/ApiHelper.cs b/Apewer.Web/Internals/ApiHelper.cs index 23e4d8b..9927f37 100644 --- a/Apewer.Web/Internals/ApiHelper.cs +++ b/Apewer.Web/Internals/ApiHelper.cs @@ -400,8 +400,8 @@ namespace Apewer.Internals internal static void Output(ApiProvider provider, ApiOptions options, string type, byte[] bytes) { - var preOutput = provider.PreWrite(); - if (!string.IsNullOrEmpty(preOutput)) return; + var preWrite = provider.PreWrite(); + if (!string.IsNullOrEmpty(preWrite)) return; var headers = PrepareHeaders(options, null); foreach (var header in headers) provider.SetHeader(header.Key, header.Value); @@ -416,8 +416,8 @@ namespace Apewer.Internals internal static void Output(ApiProvider provider, ApiOptions options, ApiResponse response, ApiRequest request, HttpMethod method) { - var preOutput = provider.PreWrite(); - if (!string.IsNullOrEmpty(preOutput)) return; + var preWrite = provider.PreWrite(); + if (!string.IsNullOrEmpty(preWrite)) return; // 设置头。 var headers = PrepareHeaders(options, null); diff --git a/Apewer.Web/Web/ApiEntries.cs b/Apewer.Web/Web/ApiEntries.cs index 4101391..6c64137 100644 --- a/Apewer.Web/Web/ApiEntries.cs +++ b/Apewer.Web/Web/ApiEntries.cs @@ -127,6 +127,7 @@ namespace Apewer.Web if (type.IsAbstract) return null; if (type.IsGenericType) return null; if (type.GetGenericArguments().NotEmpty()) return null; + if (!RuntimeUtility.CanNew(type)) return null; // 检查类型的特性。 var apis = type.GetCustomAttributes(typeof(ApiAttribute), false); diff --git a/Apewer.Web/Web/ApiProcessor.cs b/Apewer.Web/Web/ApiProcessor.cs index f843e38..92f6d0f 100644 --- a/Apewer.Web/Web/ApiProcessor.cs +++ b/Apewer.Web/Web/ApiProcessor.cs @@ -60,9 +60,7 @@ namespace Apewer.Web Provider.Options = Options; // 检查执行的前提条件,获取 Method 和 URL。 - var check = Provider.PreInvoke(); - if (!string.IsNullOrEmpty(check)) return check; - check = Check(); + var check = Check(); if (!string.IsNullOrEmpty(check)) return check; // 准备请求和响应模型。 diff --git a/Apewer.Web/Web/CronAttribute.cs b/Apewer.Web/Web/CronAttribute.cs deleted file mode 100644 index 5e5a9f0..0000000 --- a/Apewer.Web/Web/CronAttribute.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Apewer.Web -{ - - /// Cron 特性。 - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public sealed class CronAttribute : Attribute - { - - internal const int DefaultInterval = 60000; - - private int _internval; - - /// 两次 Cron 执行的间隔毫秒数。 - public int Interval - { - get { return _internval; } - private set { _internval = value < 1000 ? 1000 : value; } - } - - /// 创建 Cron 特性,可指定两次 Cron 执行的间隔毫秒数。 - public CronAttribute(int interval = DefaultInterval) - { - Interval = interval; - } - - } - -} diff --git a/Apewer.Web/Web/CronInstance.cs b/Apewer.Web/Web/CronInstance.cs deleted file mode 100644 index 78ebbee..0000000 --- a/Apewer.Web/Web/CronInstance.cs +++ /dev/null @@ -1,124 +0,0 @@ -using Apewer; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; - -namespace Apewer.Web -{ - - internal sealed class CronInstance - { - - private Thread _thread = null; - private Type _type = null; - private bool _break = false; - private bool _latest = false; - private CronAttribute _attribute = null; - private Nullable _ended = null; - - public CronInvoker Invoker { get; set; } - - public Thread Thread - { - get { return _thread; } - } - - public bool Alive - { - get { return GetAlive(); } - } - - /// 再次启动 Cron 的时间间隔。 - public int Interval - { - get { return GetInterval(); } - } - - /// 最后一次检查的 Alive 值。 - public bool Latest - { - get { return _latest; } - set { _latest = value; } - } - - public Type Type - { - get { return _type; } - set { _type = value; } - } - - public bool Break - { - get { return _break; } - set { _break = value; } - } - - public CronAttribute Attribute - { - get { return _attribute; } - set { _attribute = value; } - } - - public Nullable Ended - { - get { return _ended; } - set { _ended = value; } - } - - public CronInstance() - { - _thread = new Thread(Listen); - _thread.IsBackground = true; - } - - void Log(params object[] content) => Invoker?.Log(content); - - public void Start() - { - if (Alive) return; - _thread = new Thread(Listen); - _thread.IsBackground = true; - _thread.Start(); - } - - public void Abort() - { - if (_thread != null) - { - _thread.Abort(); - _thread = null; - } - } - - int GetInterval() - { - if (Attribute != null) return Attribute.Interval; - return CronAttribute.DefaultInterval; - } - - bool GetAlive() - { - if (_thread == null) return false; - if (_thread.IsAlive != true) return false; - if (Thread.ThreadState != ThreadState.Running) return false; - return true; - } - - void Listen() - { - if (Type == null) return; - try - { - Activator.CreateInstance(Type); - } - catch (Exception exception) - { - Log(Type.FullName, exception.GetType().FullName, exception.Message); - } - _thread = null; - } - - } - -} diff --git a/Apewer.Web/Web/Resources.cs b/Apewer.Web/Web/Resources.cs new file mode 100644 index 0000000..55c84b8 --- /dev/null +++ b/Apewer.Web/Web/Resources.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace Apewer.Web +{ + + /// 程序集资源。 + public static class Resources + { + + static byte[] Bytes(string name) + { + var assembly = Assembly.GetExecutingAssembly(); + using (var stream = assembly.GetManifestResourceStream(name)) return stream.Read(); + } + + static string Text(string name) => BytesUtility.WipeTextBom(Bytes(name)).Text(); + + /// 获取预置的 favicon.ico 文件,。 + public static byte[] FavIcon() => Bytes("Apewer.FavIcon.ico"); + + /// 获取用于 .NET Framework 4.0 的 web.config 文件。 + public static string WebConfig40() => Text("Apewer.WebConfig40.xml"); + + /// 获取用于 .NET Framework 4.6.1 的 web.config 文件。 + public static string WebConfig461(bool netstandard = false) => Text(netstandard ? "Apewer.WebConfigStd.xml" : "Apewer.WebConfig461.xml"); + } + +} diff --git a/Apewer.Web/WebConfig40.xml b/Apewer.Web/WebConfig40.xml new file mode 100644 index 0000000..6726e1e --- /dev/null +++ b/Apewer.Web/WebConfig40.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Apewer.Web/WebConfig461.xml b/Apewer.Web/WebConfig461.xml new file mode 100644 index 0000000..16c6c0b --- /dev/null +++ b/Apewer.Web/WebConfig461.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Apewer.Web/WebConfigStd.xml b/Apewer.Web/WebConfigStd.xml new file mode 100644 index 0000000..1649f27 --- /dev/null +++ b/Apewer.Web/WebConfigStd.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Apewer/CronAttribute.cs b/Apewer/CronAttribute.cs new file mode 100644 index 0000000..73a00e1 --- /dev/null +++ b/Apewer/CronAttribute.cs @@ -0,0 +1,83 @@ +using Apewer.Web; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace Apewer +{ + + /// Cron 特性,默认间隔为 60000 毫秒。 + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public sealed class CronAttribute : Attribute + { + + internal const int DefaultInterval = 60000; + + private int _interval; + + /// 两次 Cron 执行的间隔毫秒数。 + public int Interval + { + get { return _interval; } + } + + /// 创建 Cron 特性,可指定两次 Cron 执行的间隔毫秒数。 + public CronAttribute(int interval = DefaultInterval) + { + _interval = interval; + } + + #region CronInvoker + + private static Class _invoker = new Class(); + + /// 开始 Cron 调用(不阻塞当前线程)。 + /// + /// 参数
+ /// - assemblies: 包含 Cron 的程序集,不指定此参数时将在 AppDomain 中搜索;
+ /// - logger: 日志记录程序,不指定此参数时将使用 Logger.Default。
+ ///
+ public static void Start(IEnumerable assemblies = null, Logger logger = null) + { + CronInvoker instance = null; + lock (_invoker) + { + if (_invoker) return; + instance = new CronInvoker(); + _invoker.Value = instance; + } + instance.Logger = logger ?? Logger.Default; + instance.Load(assemblies ?? AppDomain.CurrentDomain.GetAssemblies()); + + Console.CancelKeyPress += (s, e) => + { + Break(); + e.Cancel = true; + }; + instance.Start(); + } + + /// 在当前线程开始 Cron 调用(阻塞当前线程)。 + /// + /// 参数
+ /// - assemblies: 包含 Cron 的程序集,不指定此参数时将在 AppDomain 中搜索;
+ /// - logger: 日志记录程序,不指定此参数时将使用 Logger.Default。
+ ///
+ public static void Start(Logger logger, IEnumerable assemblies = null) => Start(assemblies, logger); + + /// 打断 Cron 循环,不打断正在执行的 Cron。 + public static void Break() + { + lock (_invoker) + { + if (!_invoker) return; + _invoker.Value.Break(); + } + } + + #endregion + + } + +} diff --git a/Apewer/CronInstance.cs b/Apewer/CronInstance.cs new file mode 100644 index 0000000..c9f8829 --- /dev/null +++ b/Apewer/CronInstance.cs @@ -0,0 +1,95 @@ +using Apewer; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +namespace Apewer.Web +{ + + internal sealed class CronInstance + { + + internal bool _latest = false; + internal Type _type = null; + internal Logger _logger = null; + internal Class _ended = null; + internal CronAttribute _attribute = null; + internal CronInvoker _invoker = null; + + private Thread _thread = null; + private bool _break = false; + + #region properties + + // 当前线程正在运行。 + public bool Alive { get => GetAlive(); } + + // 再次启动 Cron 的时间间隔。 + public int Interval { get => GetInterval(); } + + // 最后一次检查的 Alive 值。 + public bool Latest { get => _latest; } + + // Cron 类型。 + public Type Type { get => _type; } + + public CronAttribute Attribute { get => _attribute; } + + public Class Ended { get => _ended; } + + #endregion + + public CronInstance() + { + _thread = new Thread(Listen); + _thread.IsBackground = true; + } + + /// 打断循环。 + public void Break() => _break = true; + + /// 启动线程执行任务。 + public void Start() + { + if (Alive) return; + _thread = new Thread(Listen); + _thread.IsBackground = true; + _thread.Start(); + } + + int GetInterval() + { + if (_attribute == null) _attribute = new CronAttribute(); + return _attribute.Interval; + } + + bool GetAlive() + { + if (_thread == null) return false; + if (_thread.IsAlive != true) return false; + if (_thread.ThreadState != ThreadState.Running) return false; + return true; + } + + void Listen() + { + if (Type == null) return; + var instance = null as object; + try + { + instance = Activator.CreateInstance(Type); + } + catch (Exception exception) + { + Log(Type.FullName, exception.GetType().FullName, exception.Message); + } + RuntimeUtility.Dispose(instance); + _thread = null; + } + + void Log(params object[] content) => _logger.Text(Type.FullName, content); + + } + +} diff --git a/Apewer.Web/Web/CronInvoker.cs b/Apewer/CronInvoker.cs similarity index 73% rename from Apewer.Web/Web/CronInvoker.cs rename to Apewer/CronInvoker.cs index 872c9ba..0a45106 100644 --- a/Apewer.Web/Web/CronInvoker.cs +++ b/Apewer/CronInvoker.cs @@ -9,7 +9,7 @@ namespace Apewer.Web { /// Cron 调度器。 - public sealed class CronInvoker + internal sealed class CronInvoker { #region Instance @@ -17,12 +17,12 @@ namespace Apewer.Web private List _assemblies = null; private List _instances = null; private bool _break = false; - private Action _log = null; + private Logger _logger = null; - /// 获取或设置 Log 处理程序。 - public Action LogAction { get { return _log; } set { _log = value; } } + /// 获取或设置日志记录器。 + public Logger Logger { get { return _logger; } set { _logger = value; } } - internal void Log(params object[] content) => Logger?.Text(typeof(CronInvoker), content); + private void Log(object content) => _logger?.Text("Cron", content); /// 加载程序集。 public void Load(IEnumerable assemblies) @@ -54,10 +54,7 @@ namespace Apewer.Web } /// 通知打断循环,所有 Cron 执行结束后退出。 - public void Break() - { - _break = true; - } + public void Break() => _break = true; /// 开始 Cron 调用。 public void Start() @@ -79,39 +76,39 @@ namespace Apewer.Web if (i.Alive) alive++; if (_break) { - i.Break = true; + i.Break(); break; } // 当前线程正在活动。 if (i.Alive) { - i.Latest = true; + i._latest = true; continue; } // 记录 Cron 结束时间,根据结束时间判断再次启动 Cron。 if (i.Latest) { - Log($"类型 {i.Type.FullName} 已结束。"); - i.Ended = DateTime.Now; - i.Latest = false; + Log($"{i.Type.FullName} Ended"); + i._ended = new Class(DateTime.Now); + i._latest = false; } if (i.Ended == null) { - Log($"准备开始类型 {i.Type.FullName}。"); + Log($"{i.Type.FullName} Beginning"); i.Start(); - i.Latest = true; + i._latest = true; } else { var span = DateTime.Now - i.Ended.Value; if (span.TotalMilliseconds >= Convert.ToDouble(i.Interval)) { - Log($"准备开始类型 {i.Type.FullName}。"); + Log($"{i.Type.FullName} Beginning"); i.Start(); - i.Latest = true; + i._latest = true; } } } @@ -121,7 +118,7 @@ namespace Apewer.Web break; } - Thread.Sleep(1000); + Thread.Sleep(500); GC.Collect(); } @@ -139,9 +136,10 @@ namespace Apewer.Web if (attribute == null) continue; var instance = new CronInstance(); - instance.Invoker = this; - instance.Attribute = attribute; - instance.Type = type; + instance._invoker = this; + instance._attribute = attribute; + instance._type = type; + instance._logger = Logger; list.Add(instance); } @@ -174,22 +172,12 @@ namespace Apewer.Web #region Static - /// 获取或设置日志记录器。 - public static Logger Logger { get; set; } - - /// 在当前线程开始 Cron 调用,可能会阻塞当前线程。可指定 Log 处理程序。 - public static CronInvoker Start(Assembly assembly, Action log = null) - { - var assemblies = new Assembly[] { assembly }; - return Start(assemblies); - } - - /// 在当前线程开始 Cron 调用,可能会阻塞当前线程。可指定 Log 处理程序。 - public static CronInvoker Start(IEnumerable assemblies, Action log = null) + // 在当前线程开始 Cron 调用 。 + public static CronInvoker Start(IEnumerable assemblies = null, Logger logger = null) { var instance = new CronInvoker(); - instance.LogAction = log; - instance.Load(assemblies); + instance.Logger = logger; + instance.Load(assemblies ?? AppDomain.CurrentDomain.GetAssemblies()); instance.Start(); return instance; } diff --git a/Apewer/IndependentAttribute.cs b/Apewer/IndependentAttribute.cs index 40a6c1f..f0bbde4 100644 --- a/Apewer/IndependentAttribute.cs +++ b/Apewer/IndependentAttribute.cs @@ -13,7 +13,7 @@ namespace Apewer string _remark = null; /// 无依赖特性。 - public IndependentAttribute(string remark) => _remark = remark; + public IndependentAttribute(string remark = null) => _remark = remark; /// 备注。 public string Remark diff --git a/Apewer/Json.cs b/Apewer/Json.cs index 96de6a5..a2afe6c 100644 --- a/Apewer/Json.cs +++ b/Apewer/Json.cs @@ -9,6 +9,7 @@ using System.Dynamic; using System.IO; #endif using System.Reflection; +using System.Runtime.Serialization; using System.Text; namespace Apewer @@ -1517,6 +1518,25 @@ namespace Apewer } } + static void Add(object entity, object item, int index) + { + try + { + if (entity is Array array) + { + array.SetValue(item, index); + } + else if (entity is IList list) + { + list.Add(entity); + } + } + catch (Exception ex) + { + if (_throw) throw ex; + } + } + internal static void Array(object array, Json json, bool ignoreCase, string ignoreCharacters, bool force) { if (array == null) return; @@ -1524,83 +1544,54 @@ namespace Apewer if (json.TokenType != JTokenType.Array) return; var type = array.GetType(); - var subtypes = type.GetGenericArguments(); - if (subtypes.Length < 1) return; - var subtype = subtypes[0]; - - var methods = type.GetMethods(); - var add = null as MethodInfo; - foreach (var method in methods) + var subtype = null as Type; + if (array is Array) { - if (method.Name == "Add") - { - var parameters = method.GetParameters(); - if (parameters.Length == 1) - { - if (parameters[0].ParameterType.FullName == subtype.FullName) - { - add = method; - break; - } - } - } + string typeName = array.GetType().FullName.Replace("[]", string.Empty); + subtype = array.GetType().Assembly.GetType(typeName); + } + else + { + var subtypes = type.GetGenericArguments(); + if (subtypes.Length < 1) return; + subtype = subtypes[0]; } - if (add == null) return; var jis = json.GetItems(); - foreach (var ji in jis) - { - var parameter = new object[1] { null }; - if (subtype.FullName == typeof(Json).FullName) - { - parameter[0] = ji; - add.Invoke(array, parameter); - } + for (var index = 0; index < jis.Length; index++) + { + var ji = jis[index]; + if (subtype.Equals(typeof(Json))) Add(array, ji, index); + else if (subtype.Equals(typeof(string))) Add(array, (ji.TokenType == JTokenType.Null) ? null : ji.Text, index); + else if (subtype.Equals(typeof(byte))) Add(array, NumberUtility.Byte(ji.Text), index); + else if (subtype.Equals(typeof(short))) Add(array, NumberUtility.Int16(ji.Text), index); + else if (subtype.Equals(typeof(int))) Add(array, NumberUtility.Int32(ji.Text), index); + else if (subtype.Equals(typeof(long))) Add(array, NumberUtility.Int64(ji.Text), index); + else if (subtype.Equals(typeof(sbyte))) Add(array, NumberUtility.SByte(ji.Text), index); + else if (subtype.Equals(typeof(ushort))) Add(array, NumberUtility.UInt16(ji.Text), index); + else if (subtype.Equals(typeof(uint))) Add(array, NumberUtility.UInt32(ji.Text), index); + else if (subtype.Equals(typeof(ulong))) Add(array, NumberUtility.UInt64(ji.Text), index); + else if (subtype.Equals(typeof(float))) Add(array, NumberUtility.Single(ji.Text), index); + else if (subtype.Equals(typeof(double))) Add(array, NumberUtility.Double(ji.Text), index); + else if (subtype.Equals(typeof(decimal))) Add(array, NumberUtility.Decimal(ji.Text), index); else { - switch (subtype.FullName) + var serializable = force ? true : CanSerialize(subtype, false); + if (serializable && (ji is Json)) { - case "System.String": - parameter[0] = (ji.TokenType == JTokenType.Null) ? null : ji.Text; - add.Invoke(array, parameter); - break; - case "System.Int32": - parameter[0] = NumberUtility.Int32(ji.Text); - add.Invoke(array, parameter); - break; - case "System.Int64": - parameter[0] = NumberUtility.Int64(ji.Text); - add.Invoke(array, parameter); - break; - case "System.Double": - parameter[0] = NumberUtility.Double(ji.Text); - add.Invoke(array, parameter); - break; - case "System.Decimal": - parameter[0] = NumberUtility.Decimal(ji.Text); - add.Invoke(array, parameter); - break; - default: - var serializable = force ? true : CanSerialize(subtype, false); - if (serializable && (ji is Json)) - { - switch (ji.TokenType) - { - case JTokenType.Object: - var subobject = Activator.CreateInstance(subtype); - Object(subobject, ji, ignoreCase, ignoreCharacters, force); - parameter[0] = subobject; - add.Invoke(array, parameter); - break; - case JTokenType.Array: - var subarray = Activator.CreateInstance(subtype); - Array(subarray, ji, ignoreCase, ignoreCharacters, force); - parameter[0] = subarray; - add.Invoke(array, parameter); - break; - } - } - break; + switch (ji.TokenType) + { + case JTokenType.Object: + var subobject = Activator.CreateInstance(subtype); + Object(subobject, ji, ignoreCase, ignoreCharacters, force); + Add(array, subobject, index); + break; + case JTokenType.Array: + var subarray = Activator.CreateInstance(subtype); + Array(subarray, ji, ignoreCase, ignoreCharacters, force); + Add(array, subarray, index); + break; + } } } } @@ -1615,6 +1606,7 @@ namespace Apewer var setter = property.GetSetMethod(); if (setter == null) return; + var pt = property.PropertyType; var ptname = property.PropertyType.FullName; var parameter = new object[1] { null }; @@ -1665,7 +1657,8 @@ namespace Apewer setter.Invoke(entity, parameter); break; default: - var serializable = force ? true : CanSerialize(property.PropertyType, false); + var serializable = force; + if (!serializable) serializable = CanSerialize(property.PropertyType, false); if (serializable && (value is Json)) { switch (((Json)value).TokenType) @@ -1677,7 +1670,17 @@ namespace Apewer setter.Invoke(entity, parameter); break; case JTokenType.Array: - var subarray = Activator.CreateInstance(property.PropertyType); + object subarray; + if (pt.BaseType != null && pt.BaseType.Equals(typeof(Array))) + { + subarray = new object(); + var length = ((Json)value).GetItems().Length; + subarray = pt.InvokeMember("Set", BindingFlags.CreateInstance, null, subarray, new object[] { length }); + } + else + { + subarray = Activator.CreateInstance(property.PropertyType); + } Array(subarray, (Json)value, ignoreCase, ignoreCharacters, force); parameter[0] = subarray; setter.Invoke(entity, parameter); @@ -1995,6 +1998,14 @@ namespace Apewer { if (type == null) return false; + if (type.BaseType.Equals(typeof(Array))) return true; + + var interfaces = type.GetInterfaces(); + foreach (var i in interfaces) + { + if (i.Equals(typeof(IList))) return true; + } + if (type.Equals(typeof(object))) return false; var sas = type.GetCustomAttributes(typeof(SerializableAttribute), inherit); if (sas != null && sas.Length > 0) return true; diff --git a/Apewer/Logger.cs b/Apewer/Logger.cs index cdc39c5..7c03a43 100644 --- a/Apewer/Logger.cs +++ b/Apewer/Logger.cs @@ -114,22 +114,22 @@ namespace Apewer } /// 记录错误。多个 Content 参数将以“ | ”分隔。 - internal void InnerError(object sender, params object[] content) => Colorful(sender, "Error", DarkRed, content, null, OnError); + internal void InnerError(object sender, object[] content) => Colorful(sender, "Error", DarkRed, content, null, OnError); /// 记录警告。多个 Content 参数将以“ | ”分隔。 - internal void InnerWarning(object sender, params object[] content) => Colorful(sender, "Warning", DarkYellow, content, null, OnWarning); + internal void InnerWarning(object sender, object[] content) => Colorful(sender, "Warning", DarkYellow, content, null, OnWarning); /// 记录警告。多个 Content 参数将以“ | ”分隔。 - internal void InnerInfo(object sender, params object[] content) => Colorful(sender, "Info", DarkBlue, content, null, OnInfo); + internal void InnerInfo(object sender, object[] content) => Colorful(sender, "Info", DarkBlue, content, null, OnInfo); /// 记录文本。多个 Content 参数将以“ | ”分隔。 - internal void InnerText(object sender, params object[] content) => Colorful(sender, "Text", null, content, null, OnText); + internal void InnerText(object sender, object[] content) => Colorful(sender, "Text", null, content, null, OnText); /// 记录调试。多个 Content 参数将以“ | ”分隔。 [Conditional("DEBUG")] - internal void InnerDebug(object sender, params object[] content) => Colorful(sender, "Debug", null, content, null, OnDebug); + internal void InnerDebug(object sender, object[] content) => Colorful(sender, "Debug", null, content, null, OnDebug); - private void Write(object sender, params object[] content) => Colorful(sender, null, null, content, null, null); + private void Write(object sender, object[] content) => Colorful(sender, null, null, content, null, null); /// 创建新实例。 public Logger() diff --git a/Apewer/NumberUtility.cs b/Apewer/NumberUtility.cs index e36e227..25c1301 100644 --- a/Apewer/NumberUtility.cs +++ b/Apewer/NumberUtility.cs @@ -294,6 +294,26 @@ namespace Apewer return default(decimal); } + /// 获取布尔对象。 + public static bool Boolean(object any) + { + if (any is bool _bool) return _bool; + if (any is byte _byte) return _byte == 1; + if (any is sbyte _sbyte) return _sbyte == 1; + if (any is short _short) return _short == 1; + if (any is ushort _ushort) return _ushort == 1; + if (any is int _int) return _int == 1; + if (any is uint _uint) return _uint == 1; + if (any is long _long) return _long == 1; + if (any is ulong _ulong) return _ulong == 1; + if (any is string _string) + { + _string = TextUtility.Lower(_string); + if (_string == "true" || _string == "yes" || _string == "y") return true; + } + return false; + } + /// 获取单精度浮点对象。 public static float Float(object number) => GetNumber(number, Convert.ToSingle, (v, d) => v / Convert.ToSingle(d)); diff --git a/Apewer/Source/ColumnAttribute.cs b/Apewer/Source/ColumnAttribute.cs index c648e4d..ed2cb9f 100644 --- a/Apewer/Source/ColumnAttribute.cs +++ b/Apewer/Source/ColumnAttribute.cs @@ -17,7 +17,7 @@ namespace Apewer.Source private PropertyInfo _property = null; internal string PropertyName = null; - private string _field = ""; + private string _field = null; private int _length = 0; private ColumnType _type; @@ -26,8 +26,7 @@ namespace Apewer.Source private void Init(string field, ColumnType type, int length) { - if (string.IsNullOrEmpty(field)) field = TableStructure.RestrictName(field, string.IsNullOrEmpty(field)); - _field = string.IsNullOrEmpty(field) ? "" : TableStructure.RestrictName(field, string.IsNullOrEmpty(field)); + _field = field; _type = type; switch (type) { @@ -103,7 +102,7 @@ namespace Apewer.Source if (setter == null || setter.IsStatic) return null; // 检查列名称。 - if (TextUtility.IsBlank(ca.Field)) ca._field = "_" + property.Name; + if (TextUtility.IsBlank(ca.Field)) ca._field = property.Name; // 类型兼容。 var pt = property.PropertyType; diff --git a/Apewer/Source/DbClient.cs b/Apewer/Source/DbClient.cs new file mode 100644 index 0000000..375dd76 --- /dev/null +++ b/Apewer/Source/DbClient.cs @@ -0,0 +1,339 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Text; + +namespace Apewer.Source +{ + + /// + abstract class DbClient + { + + /// + public virtual Logger Logger { get; set; } + + #region Connection + + DbConnection _conn = null; + string _str = null; + + /// + public Timeout Timeout { get; set; } + + /// + public DbConnection Connection { get => _conn; } + + /// + public bool Online { get => _conn == null ? false : (_conn.State == ConnectionState.Open); } + + /// 连接字符串。 + public string ConnectionString { get => _str; } + + /// + public virtual string Connect() + { + if (_conn == null) + { + _str = GetConnectionString(); + _conn = NewConnection(); + _conn.ConnectionString = _str; + } + else + { + if (_conn.State == ConnectionState.Open) return null; + } + + try + { + _conn.Open(); + switch (_conn.State) + { + case ConnectionState.Open: return null; + default: return $"连接失败,当前处于 {_conn.State} 状态。"; + } + } + catch (Exception ex) + { + Logger.Error(this, "Connect", ex, _conn.ConnectionString); + Close(); + return ex.Message; + } + } + + /// + public void Close() + { + if (_conn != null) + { + if (_transaction != null) + { + if (_autocommit) Commit(); + else Rollback(); + } + _conn.Close(); + _conn.Dispose(); + _conn = null; + } + } + + /// + public void Dispose() { Close(); } + + #endregion + + #region Transaction + + private DbTransaction _transaction = null; + private bool _autocommit = false; + + /// 启动事务。 + public string Begin(bool commit = true) => Begin(commit, null); + + /// 启动事务。 + public string Begin(bool commit, Class isolation) + { + if (Connect() != null) return "未连接。"; + if (_transaction != null) return "存在已启动的事务,无法再次启动。"; + try + { + _transaction = isolation ? _conn.BeginTransaction(isolation.Value) : _conn.BeginTransaction(); + _autocommit = commit; + return null; + } + catch (Exception ex) + { + Logger.Error(this, "Commit", ex.Message()); + return ex.Message(); + } + } + + /// 提交事务。 + public string Commit() + { + if (_transaction == null) return "事务不存在。"; + try + { + _transaction.Commit(); + RuntimeUtility.Dispose(_transaction); + _transaction = null; + return null; + } + catch (Exception ex) + { + RuntimeUtility.Dispose(_transaction); + _transaction = null; + Logger.Error(this, "Commit", ex.Message()); + return ex.Message(); + } + } + + /// 从挂起状态回滚事务。 + public string Rollback() + { + if (_transaction == null) return "事务不存在。"; + try + { + _transaction.Rollback(); + RuntimeUtility.Dispose(_transaction); + _transaction = null; + return null; + } + catch (Exception ex) + { + RuntimeUtility.Dispose(_transaction); + _transaction = null; + Logger.Error(this, "Rollback", ex.Message()); + return ex.Message(); + } + } + + #endregion + + #region ADO + + /// 查询。 + public IQuery Query(string sql) => Query(sql, null); + + /// 查询。 + public IQuery Query(string sql, IEnumerable parameters) + { + if (TextUtility.IsBlank(sql)) return new Query(false, "语句无效。"); + var connected = Connect(); + if (connected.NotEmpty()) return new Query(false, connected); + + try + { + using (var command = NewCommand()) + { + command.Connection = _conn; + if (Timeout != null) command.CommandTimeout = Timeout.Query; + command.CommandText = sql; + if (parameters != null) + { + foreach (var parameter in parameters) + { + if (parameter != null) command.Parameters.Add(parameter); + } + } + using (var ds = new DataSet()) + { + using (var da = NewDataAdapter(sql)) + { + const string name = "result"; + da.Fill(ds, name); + var table = ds.Tables[name]; + return new Query(table, true); + } + } + } + } + catch (Exception exception) + { + Logger.Error(this, "Query", exception, sql); + return new Query(exception); + } + } + + /// 执行。 + public IExecute Execute(string sql) => Execute(sql, null); + + /// 执行单条 Transact-SQL 语句,并加入参数。 + public IExecute Execute(string sql, IEnumerable parameters) + { + if (TextUtility.IsBlank(sql)) return Example.InvalidExecuteStatement; + + var connected = Connect(); + if (connected.NotEmpty()) return new Execute(false, connected); + + var inTransaction = _transaction != null; + if (!inTransaction) Begin(); + try + { + using (var command = NewCommand()) + { + command.Connection = _conn; + command.Transaction = (DbTransaction)_transaction; + if (Timeout != null) command.CommandTimeout = Timeout.Execute; + command.CommandText = sql; + if (parameters != null) + { + foreach (var parameter in parameters) + { + if (parameter != null) command.Parameters.Add(parameter); + } + } + var rows = command.ExecuteNonQuery(); + if (!inTransaction) Commit(); // todo 此处应该检查事务提交产生的错误。 + return new Execute(true, rows); + } + } + catch (Exception exception) + { + Logger.Error(this, "Execute", exception, sql); + if (!inTransaction) Rollback(); + return new Execute(exception); + } + } + + #endregion + + #region ORM - Query + + /// 查询记录。 + /// 记录模型。 + /// SQL 语句。 + public Result Query(Type model, string sql) + { + if (_conn == null) return new Result("连接无效。"); + if (model == null) return new Result("数据模型类型无效。"); + if (string.IsNullOrEmpty(sql)) return new Result("SQL 语句无效。"); + + var query = Query(sql); + var result = null as Result; + + if (query.Success) + { + try + { + var array = OrmHelper.Fill(query, model); + result = new Result(array); + } + catch (Exception ex) + { + result = new Result(ex); + } + } + else + { + result = new Result(query.Message); + } + + query.Dispose(); + return result; + } + + /// + public Result Query(string sql) where T : class, new() + { + var query = Query(sql); + if (!query.Success) return new Result(query.Message); + var records = query.Fill(); + query.Dispose(); + + var result = new Result(records); + return result; + } + + #endregion + + #region Static + + /// 对文本转义,符合 SQL 安全性。可根据字段类型限制 UTF-8 字节数,默认为 0 时不限制字节数。 + protected static string Escape(string text, int bytes = 0) + { + if (text.IsEmpty()) return ""; + + var t = text ?? ""; + t = t.Replace("\\", "\\\\"); + t = t.Replace("'", "\\'"); + t = t.Replace("\n", "\\n"); + t = t.Replace("\r", "\\r"); + t = t.Replace("\b", "\\b"); + t = t.Replace("\t", "\\t"); + t = t.Replace("\f", "\\f"); + + if (bytes > 5) + { + if (t.Bytes(Encoding.UTF8).Length > bytes) + { + while (true) + { + t = t.Substring(0, t.Length - 1); + if (t.Bytes(Encoding.UTF8).Length <= (bytes - 4)) break; + } + t = t + " ..."; + } + } + + return t; + } + + #endregion + + /// + protected abstract string GetConnectionString(); + + /// + protected abstract DbConnection NewConnection(); + + /// + protected abstract DbDataAdapter NewDataAdapter(string sql); + + /// + protected abstract DbCommand NewCommand(); + + } + +} diff --git a/Apewer/Source/IDbClientAdo.cs b/Apewer/Source/IDbClientAdo.cs index 83e5844..1fa4a40 100644 --- a/Apewer/Source/IDbClientAdo.cs +++ b/Apewer/Source/IDbClientAdo.cs @@ -18,8 +18,8 @@ namespace Apewer.Source /// 数据库当前在线,表示连接可用。 bool Online { get; } - /// 连接数据库,若未连接则尝试连接,获取连接成功的状态。 - bool Connect(); + /// 连接数据库,若未连接则尝试连接,返回错误信息。 + string Connect(); #endregion diff --git a/Apewer/Source/IDbClientOrm.cs b/Apewer/Source/IDbClientOrm.cs index f961360..0e6e2be 100644 --- a/Apewer/Source/IDbClientOrm.cs +++ b/Apewer/Source/IDbClientOrm.cs @@ -20,13 +20,15 @@ namespace Apewer.Source /// 插入记录。 /// 要插入的记录实体。 + /// 插入到指定表。当不指定时,由 record 类型决定。 /// 错误信息。当成功时候返回空字符串。 - public string Insert(object record); + public string Insert(object record, string table = null); /// 更新记录。 /// 要插入的记录实体。 + /// 插入到指定表。当不指定时,由 record 类型决定。 /// 错误信息。当成功时候返回空字符串。 - public string Update(IRecord record); + public string Update(IRecord record, string table = null); /// 获取指定类型的主键,按 Flag 属性筛选。 /// 要查询的类型。 @@ -41,7 +43,7 @@ namespace Apewer.Source /// 目标记录的类型。 /// 目标记录的主键。 /// 要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。 - public Result Get(Type model, string key, long flag = 0); + public Result Get(Type model, string key, long flag = 0); /// 获取具有指定 Key 的记录,并要求记录具有指定的 Flag 属性。 /// 目标记录的主键。 diff --git a/Apewer/Source/IRecordStamp.cs b/Apewer/Source/IRecordStamp.cs index e44b945..da2b757 100644 --- a/Apewer/Source/IRecordStamp.cs +++ b/Apewer/Source/IRecordStamp.cs @@ -9,10 +9,10 @@ namespace Apewer.Source public interface IRecordStamp { - /// 记录的创建时间 UTC 时间戳,值为执行 INSERT INTO 语句时的 UTC 时间戳。 + /// 记录的创建时间,默认为本地时间。 long Created { get; set; } - /// 记录的更新时间 UTC 时间戳,每次对此记录执行 UPDATE 时应更新此值为当前 UTC 时间戳。 + /// 记录的更新时间,默认为本地时间。 long Updated { get; set; } } diff --git a/Apewer/Source/OrmHelper.cs b/Apewer/Source/OrmHelper.cs index 7086974..28ab1b5 100644 --- a/Apewer/Source/OrmHelper.cs +++ b/Apewer/Source/OrmHelper.cs @@ -59,10 +59,10 @@ namespace Apewer.Source #region IQuery -> IRecord /// 读取所有行,生成列表。 - public static T[] Fill(IQuery query) where T : class, new() => As(Fill(query, typeof(T))); + public static T[] Fill(this IQuery query) where T : class, new() => As(Fill(query, typeof(T))); /// 读取所有行填充到 T,组成 T[]。 - public static object[] Fill(IQuery query, Type model) + public static object[] Fill(this IQuery query, Type model) { if (query == null) return new object[0]; if (model == null) return new object[0]; @@ -76,7 +76,7 @@ namespace Apewer.Source } /// 获取指定列的所有值,无效值不加入结果。 - public static T[] Column(IQuery query, Func filler) + public static T[] Column(this IQuery query, Func filler) { if (query == null || filler == null) return new T[0]; @@ -105,7 +105,7 @@ namespace Apewer.Source /// 将 Query 的行,填充到模型实体。 /// 填充失败时返回 NULL 值。 /// - public static IRecord Row(IQuery query, int rowIndex, Type model, TableStructure structure) + public static object Row(IQuery query, int rowIndex, Type model, TableStructure structure) { // 检查参数。 if (query == null || model == null || structure == null) return null; @@ -210,7 +210,7 @@ namespace Apewer.Source catch { } } } - return record as IRecord; + return record; } #endregion @@ -226,7 +226,7 @@ namespace Apewer.Source 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) + using (var query = database.Query(sql)) { if (query == null) return new Result("查询实例无效。"); if (query.Table == null) @@ -246,11 +246,11 @@ namespace Apewer.Source } } - /// 查询记录。 - /// 记录模型。 - /// 数据库对象。 - /// 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, string sql) where T : class, new() => As(Query(database, typeof(T), sql)); /// 查询记录。 /// 数据库对象。 @@ -282,34 +282,35 @@ namespace Apewer.Source /// 记录模型。 /// 主键。 /// 生成 SQL 语句的函数,传入参数为表名和主键值。 - public static Result Get(IDbClientAdo database, Type model, string key, Func sqlGetter) + public static Result Get(IDbClientAdo database, Type model, string key, Func sqlGetter) { - if (sqlGetter == null) return new Result("SQL 语句获取函数无效。"); + if (sqlGetter == null) return new Result("SQL 语句获取函数无效。"); var safetyKey = TextUtility.SafeKey(key); - if (string.IsNullOrEmpty(safetyKey)) return new Result("主键无效。"); + if (string.IsNullOrEmpty(safetyKey)) return new Result("主键无效。"); var query = null as IQuery; - var record = null as IRecord; + var record = null as object; try { + record = Activator.CreateInstance(model); var ts = TableStructure.Parse(model); var tableName = ts.Name; - if (string.IsNullOrEmpty(tableName)) return new Result("表名无效。"); + if (string.IsNullOrEmpty(tableName)) return new Result("表名无效。"); var sql = sqlGetter(tableName, safetyKey); query = database.Query(sql); - if (query.Table == null) return new Result("没有获取到记录。"); + if (query.Table == null) return new Result("没有获取到记录。"); record = Row(query, 0, model, ts); } catch (Exception ex) { RuntimeUtility.Dispose(query); - return new Result(ex); + return new Result(ex); } RuntimeUtility.Dispose(query); - if (record == null) return new Result("没有获取到记录。"); - return new Result(record); + if (record == null) return new Result("没有获取到记录。"); + return new Result(record); } /// 获取具有指定主键的记录。 @@ -317,7 +318,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)); /// 获取主键。 /// 数据库对象。 @@ -398,20 +399,22 @@ namespace Apewer.Source { if (record == null) return; - if (record is IRecord key) key.ResetKey(); + if (record is IRecord key) + { + if (string.IsNullOrEmpty(key.Key)) key.ResetKey(); + } + var now = DateTime.Now; 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 (string.IsNullOrEmpty(moment.Created)) moment.Created = now.Lucid(); + if (string.IsNullOrEmpty(moment.Updated)) moment.Updated = now.Lucid(); } - if (record is IRecordStamp stamp) { var utc = ClockUtility.UtcStamp; - if (stamp.Created == 0L) stamp.Created = utc; - if (stamp.Updated == 0L) stamp.Updated = utc; + if (stamp.Created == 0L) stamp.Created = now.Stamp(); + if (stamp.Updated == 0L) stamp.Updated = now.Stamp(); } } @@ -420,22 +423,19 @@ namespace Apewer.Source public static bool SetUpdated(object record) { if (record == null) return false; - + var now = DateTime.Now; + var setted = false; if (record is IRecordMoment moment) { - var now = ClockUtility.LucidNow; - moment.Updated = now; - return true; + moment.Updated = now.Lucid(); + setted = true; } - if (record is IRecordStamp stamp) { - var utc = ClockUtility.UtcStamp; - stamp.Updated = utc; - return true; + stamp.Updated = now.Stamp(); + setted = true; } - - return false; + return setted; } #endregion diff --git a/Apewer/Source/Record.cs b/Apewer/Source/Record.cs index 45a46d3..b35f8b0 100644 --- a/Apewer/Source/Record.cs +++ b/Apewer/Source/Record.cs @@ -14,15 +14,19 @@ namespace Apewer.Source public abstract class Record : IRecord { - const int KeyLength = 191; + const int KeyLength = 32; private string _key = null; private long _flag = 0; /// 记录主键,一般使用 GUID 的字符串形式。 - /// 带有 Independent 特性的模型不包含此属性。 + /// + /// 注: + /// 1. 默认长度为 32,需要修改长度时应该重写此属性; + /// 2. 带有 Independent 特性的模型不包含此属性。 + /// [Column("_key", ColumnType.NVarChar, KeyLength)] - public string Key { get { return _key; } set { _key = Compact(value, KeyLength); } } + public virtual string Key { get { return _key; } set { _key = Compact(value, KeyLength); } } /// 记录的标记,Int64 类型,区分记录的状态。 /// 带有 Independent 特性的模型不包含此属性。 @@ -30,7 +34,7 @@ namespace Apewer.Source public long Flag { get { return _flag; } set { _flag = value; } } /// 重置 Key 属性的值。 - public virtual void ResetKey() => _key = TextUtility.Key(); + public virtual void ResetKey() => Key = TextUtility.Key(); /// public Record() => ResetKey(); diff --git a/Apewer/Source/TableAttribute.cs b/Apewer/Source/TableAttribute.cs index b9bc15d..d92b33f 100644 --- a/Apewer/Source/TableAttribute.cs +++ b/Apewer/Source/TableAttribute.cs @@ -16,14 +16,14 @@ namespace Apewer.Source public sealed class TableAttribute : Attribute { - private string _name; - private string _store; + private string _name = null; + private string _store = null; /// 标记表属性。 public TableAttribute(string name = null, string store = null) { - _name = TableStructure.RestrictName(name, string.IsNullOrEmpty(name)); - _store = string.IsNullOrEmpty(store) ? null : TableStructure.RestrictName(store, false); + _name = name; + _store = store; } /// 表名。 @@ -44,7 +44,7 @@ namespace Apewer.Source private static Dictionary _tac = new Dictionary(); /// 解析表特性,默认使用缓存以提升性能。 - public static TableAttribute Parse(bool useCache = true) where T : IRecord => Parse(typeof(T), useCache); + public static TableAttribute Parse(bool useCache = true) where T: class, new() => Parse(typeof(T), useCache); /// 解析表特性,默认使用缓存以提升性能。 public static TableAttribute Parse(Type type, bool useCache = true) @@ -68,7 +68,7 @@ namespace Apewer.Source if (tas.LongLength < 1L) return null; var ta = (TableAttribute)tas[0]; - if (string.IsNullOrEmpty(ta.Name)) ta._name = "_" + type.Name; + if (string.IsNullOrEmpty(ta.Name)) ta._name = type.Name; ta.Independent = RuntimeUtility.Contains(type, true); if (useCache) diff --git a/Apewer/Source/TableStructure.cs b/Apewer/Source/TableStructure.cs index bdcff98..388766d 100644 --- a/Apewer/Source/TableStructure.cs +++ b/Apewer/Source/TableStructure.cs @@ -49,6 +49,9 @@ namespace Apewer.Source /// 主键。 public ColumnAttribute Key { get => _key; } + /// 记录标记。 + public ColumnAttribute Flag { get => _flag; } + /// 列信息。 public ColumnAttribute[] Columns { get => _columns; } @@ -140,25 +143,33 @@ namespace Apewer.Source #region TableAttribute - /// 限定表名称/列名称。 - /// 名称。 - /// 名称以下划线开始。 - internal static string RestrictName(string name, bool startWithUnderline) + // 限定表名称/列名称。 + static string RestrictName(string name, bool underline = false, bool english = false) { - if (name == null || name == Constant.EmptyString) return Constant.EmptyString; - var lower = name.ToLower(); - var available = TextUtility.Merge("_", Constant.NumberCollection, Constant.LowerCollection); - var sb = new StringBuilder(); - foreach (var c in lower) + if (string.IsNullOrEmpty(name)) return null; + var str = name; + + // 限定名称仅使用英文和数字。 + if (english) + { + const string available = "_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + var chars = new ArrayBuilder(); + var strChars = str.ToCharArray(); + var strLength = strChars.Length > 255 ? 255 : strChars.Length; + for (var i = 0; i < strLength; i++) + { + if (available.IndexOf(strChars[i]) > -1) chars.Add(strChars[i]); + } + str = new string(chars.Export()); + } + + // 以下划线开始。 + if (underline) { - if (available.IndexOf(c) >= 0) sb.Append(c); + if (!str.StartsWith("_")) str = TextUtility.Merge("_", str); } - lower = sb.ToString(); - if (startWithUnderline && !lower.StartsWith("_")) lower = TextUtility.Merge("_", lower); - while (lower.Length > 2 && lower.StartsWith("__")) lower = lower.Substring(1); - if (lower == "_" || lower == Constant.EmptyString) return Constant.EmptyString; - if (lower.Length > 255) lower = lower.Substring(0, 255); - return lower; + + return str; } static IDataParameter CreateParameter(object record, ColumnAttribute ca, Func callback) diff --git a/Apewer/SystemUtility.cs b/Apewer/SystemUtility.cs index 3ed9a88..2a92d78 100644 --- a/Apewer/SystemUtility.cs +++ b/Apewer/SystemUtility.cs @@ -22,6 +22,24 @@ namespace Apewer #endif + /// + public static void SetConsoleCtrlCancel(Func exit) + { + const string postfix = " - 按 CTRL + C 可安全退出"; + var title = Console.Title; + if (!title.EndsWith(postfix)) + { + title = title + postfix; + Console.Title = title; + } + + if (exit == null) return; + Console.CancelKeyPress += (s, e) => + { + e.Cancel = !exit(); + }; + } + } } diff --git a/Apewer/TextUtility.cs b/Apewer/TextUtility.cs index d2c465b..f8d044e 100644 --- a/Apewer/TextUtility.cs +++ b/Apewer/TextUtility.cs @@ -644,19 +644,22 @@ namespace Apewer /// 返回此字符串的安全键副本,只保留数据记录主键中可能出现的字符,默认限制长度为 255 字符。 public static string SafeKey(string text, int maxLength = 255) { - if (string.IsNullOrEmpty(text)) return Constant.EmptyString; - var input = Lower(text); + if (string.IsNullOrEmpty(text)) return Empty; + var input = text; var max = maxLength; if (max < 1 || max > input.Length) max = input.Length; + // 允许用于主键值的字符。 + const string KeyCollection = "-_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + var sb = new StringBuilder(); var total = input.Length; var length = 0; for (var i = 0; i < total; i++) { var c = input[i]; - if (Constant.KeyCollection.IndexOf(c) < 0) continue; + if (KeyCollection.IndexOf(c) < 0) continue; sb.Append(c); length += 1; if (length >= max) break; diff --git a/Apewer/Web/ApiOptions.cs b/Apewer/Web/ApiOptions.cs index 4244f8d..ac9c255 100644 --- a/Apewer/Web/ApiOptions.cs +++ b/Apewer/Web/ApiOptions.cs @@ -10,14 +10,14 @@ namespace Apewer.Web public class ApiOptions { - /// 限制最大请求的字节数。 - /// 默认值:-1,不使用 ApiOptions 限制。 - public long MaxRequestBody { get; set; } = -1; - /// 设置 Access-Control-Max-Age 的值。 /// 默认值:60。 public int AccessControlMaxAge { get; set; } = 60; + /// 允许枚举输出 Applications 或 Functions。 + /// 默认值:不允许,不输出列表。 + public bool AllowEnumerate { get; set; } = false; + /// 允许解析 favicon.ico 请求。 /// 默认值:不允许,响应空。 public bool AllowFavIcon { get; set; } = false; @@ -26,18 +26,42 @@ namespace Apewer.Web /// 默认值:不允许,拒绝搜索引擎收录根目录。 public bool AllowRobots { get; set; } = false; - /// 允许枚举输出 Applications 或 Functions。 - /// 默认值:不允许,不输出列表。 - public bool AllowEnumerate { get; set; } = false; + // /// 允许同步 IO。 + // /// + // /// 默认值:允许。 + // /// 允许:使用同步方法写入 Response.Body,可能会导致线程不足而崩溃。 + // /// 不允许:必须用异步方法写入 Response.Body。 + // /// + // public bool AllowSynchronousIO { get; set; } = true; /// 允许输出的 Json 对象缩进。 /// 默认值:不缩进。 public bool JsonIndent { get; set; } = false; + /// 限制最大请求的字节数。 + /// 默认值:-1,不使用 ApiOptions 限制。 + public long MaxRequestBody { get; set; } = -1; + /// 在响应头中设置 Content-Security-Policy,要求浏览器升级资源链接,使用 HTTPS。 /// 默认值:不要求。在 HTTPS 页面中,不自动升级 HTTP 资源。 public bool UpgradeHttps { get; set; } = false; + /// 在响应中包含 Access-Control 属性。 + /// 默认值:包含。 + public bool WithAccessControl { get; set; } = true; + + /// 在响应中包含时间属性。 + /// 默认值:不包含。 + public bool WithClock { get; set; } = false; + + /// 允许响应标头中包含 X-Content-Type-Options: nosiff。 + /// 默认值:不包含。当设置默认控制器时自动启用此属性。 + public bool WithContentTypeOptions { get; set; } = false; + + /// 在响应中包含执行 API 的持续时间。 + /// 默认值:不包含。 + public bool WithDuration { get; set; } = false; + /// 允许响应中包含 Exception 对象的属性。 /// 默认值:不允许。 public bool WithException { get; set; } = false; @@ -50,37 +74,13 @@ namespace Apewer.Web /// 默认值:不包含。 public bool WithParameters { get; set; } = false; - /// 允许输出 Application 列表时包含类型名称。 - /// 默认值:不包含。 - public bool WithTypeName { get; set; } = false; - - /// 在响应中包含时间属性。 - /// 默认值:不包含。 - public bool WithClock { get; set; } = false; - - /// 在响应中包含执行 API 的持续时间。 - /// 默认值:不包含。 - public bool WithDuration { get; set; } = false; - /// 在响应中包含 Application 和 Function 属性。 /// 默认值:不包含。 public bool WithTarget { get; set; } = false; - /// 在响应中包含 Access-Control 属性。 - /// 默认值:包含。 - public bool WithAccessControl { get; set; } = true; - - /// 允许响应标头中包含 X-Content-Type-Options: nosiff。 - /// 默认值:不包含。当设置默认控制器时自动启用此属性。 - public bool WithContentTypeOptions { get; set; } = false; - - /// 允许同步 IO。 - /// - /// 默认值:允许。 - /// 允许:使用同步方法写入 Response.Body,可能会导致线程不足而崩溃。 - /// 不允许:必须用异步方法写入 Response.Body。 - /// - public bool AllowSynchronousIO { get; set; } = true; + /// 允许输出 Application 列表时包含类型名称。 + /// 默认值:不包含。 + public bool WithTypeName { get; set; } = false; #region 默认控制器,可用于静态控制器。 @@ -98,15 +98,22 @@ namespace Apewer.Web #endregion /// 创建默认选项。 - public ApiOptions() - { - Debug(); - } + /// + /// 在 Debug 模式中默认设置以下选项 + ///
JsonIndent = TRUE + ///
WithException = TRUE + ///
WithDuration = TRUE + ///
WithParameters = TRUE + ///
+ public ApiOptions() => Debug(); [Conditional("DEBUG")] void Debug() { + JsonIndent = true; WithException = true; + WithDuration = true; + WithParameters = true; } } diff --git a/Apewer/Web/ApiProvider.cs b/Apewer/Web/ApiProvider.cs index 91c82c7..a43b86c 100644 --- a/Apewer/Web/ApiProvider.cs +++ b/Apewer/Web/ApiProvider.cs @@ -16,13 +16,13 @@ namespace Apewer.Web #region Implement - /// 调用前的检查,可返回错误信息。 + /// 调用前的检查,可返回错误信息以终止调用。 public virtual string PreInvoke() { return null; } - /// 读取请求前的检查,可返回错误信息。 + /// 读取请求前的检查,可返回错误信息以忽略 POST 内容。 public virtual string PreRead() { return null; } - /// 写入响应前的检查,可返回错误信息。 + /// 写入响应前的检查,可返回错误信息以终止输出。 public virtual string PreWrite() { return null; } /// 结束本次请求和响应。 diff --git a/Apewer/_Common.props b/Apewer/_Common.props index 01911ad..6a8502c 100644 --- a/Apewer/_Common.props +++ b/Apewer/_Common.props @@ -14,7 +14,7 @@ Apewer Libraries - 6.4.2 + 6.5.0 diff --git a/Apewer/_Extensions.cs b/Apewer/_Extensions.cs index ff3ca5f..ecd808a 100644 --- a/Apewer/_Extensions.cs +++ b/Apewer/_Extensions.cs @@ -12,7 +12,7 @@ using System.Reflection; using System.Text; /// 扩展方法。 -public static class Extensions +public static class Extensions_Apewer { #region Class Utility @@ -77,22 +77,25 @@ public static class Extensions #region String、StringBuilder - /// 获取 Byte 对象。 + /// 转换为 Boolean 值。 + public static bool Boolean(this object @this) => NumberUtility.Boolean(@this); + + /// 转换为 Byte 值。 public static byte Byte(this object @this) => NumberUtility.Byte(@this); - /// 获取 Int32 对象。 + /// 转换为 Int32 值。 public static int Int32(this object @this) => NumberUtility.Int32(@this); - /// 获取 Int64 对象。 + /// 转换为 Int64 值。 public static long Int64(this object @this) => NumberUtility.Int64(@this); - /// 获取 Decimal 对象。 + /// 转换为 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。 @@ -288,20 +291,20 @@ public static class Extensions public static void Exception(this Logger logger, object sender, Exception exception) => logger?.InnerException(sender, exception); /// 记录错误。多个 Content 参数将以“ | ”分隔。 - public static void Error(this Logger logger, object sender, params object[] content) => logger?.InnerError(logger, sender, content); + public static void Error(this Logger logger, object sender, params object[] content) => logger?.InnerError(sender, content); /// 记录警告。多个 Content 参数将以“ | ”分隔。 - public static void Warning(this Logger logger, object sender, params object[] content) => logger?.InnerWarning(logger, sender, content); + public static void Warning(this Logger logger, object sender, params object[] content) => logger?.InnerWarning(sender, content); /// 记录警告。多个 Content 参数将以“ | ”分隔。 - public static void Info(this Logger logger, object sender, params object[] content) => logger?.InnerInfo(logger, sender, content); + public static void Info(this Logger logger, object sender, params object[] content) => logger?.InnerInfo(sender, content); /// 记录文本。多个 Content 参数将以“ | ”分隔。 - public static void Text(this Logger logger, object sender, params object[] content) => logger?.InnerText(logger, sender, content); + public static void Text(this Logger logger, object sender, params object[] content) => logger?.InnerText(sender, content); /// 记录调试。多个 Content 参数将以“ | ”分隔。 [Conditional("DEBUG")] - public static void Debug(this Logger logger, object sender, params object[] content) => logger?.InnerDebug(logger, sender, content); + public static void Debug(this Logger logger, object sender, params object[] content) => logger?.InnerDebug(sender, content); #endregion diff --git a/ChangeLog.md b/ChangeLog.md index e513361..1e330f9 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,21 @@  ### 最新提交 +### 6.5.0 +- Cron:新增 CronAttribute,取代 Source 中的 Cron,并支持 Dispose; +- Json:修正 Json 转为 T[] 的问题; +- Source:修正 FixProperties 强制重置 Key 的问题; +- Source:Record 主键长度调整为 32; +- Soruce:修正 ORM 的 Get 方法; +- Source:不再默认添加下划线; +- Source:Insert 和 Update 支持指定表名; +- Source:SqlClient 支持解析表结构; +- Source:去除 Table 和 Column 默认的下划线; +- Source:Connect 现在返回错误信息; +- Source:IRecordStamp 使用本地时间(原为 UTC 时间); +- Web:ApiEntiry 增加 CanNew 检查; +- Web:增加预置资源。 + ### 6.4.2 - ArrayBuilder:修正 512 长度无法扩展的问题。