Browse Source

Apewer-6.5.0

dev
王厅 4 years ago
parent
commit
13668abcd6
  1. 11
      Apewer.Source/Apewer.Source.csproj
  2. 18
      Apewer.Source/Source/Access.cs
  3. 27
      Apewer.Source/Source/ColumnInfo.cs
  4. 250
      Apewer.Source/Source/MySql.cs
  5. 299
      Apewer.Source/Source/SqlClient.cs
  6. 305
      Apewer.Source/Source/Sqlite.cs
  7. 7
      Apewer.Web/Apewer.Web.csproj
  8. BIN
      Apewer.Web/FavIcon.ico
  9. 8
      Apewer.Web/Internals/ApiHelper.cs
  10. 1
      Apewer.Web/Web/ApiEntries.cs
  11. 4
      Apewer.Web/Web/ApiProcessor.cs
  12. 32
      Apewer.Web/Web/CronAttribute.cs
  13. 124
      Apewer.Web/Web/CronInstance.cs
  14. 31
      Apewer.Web/Web/Resources.cs
  15. 30
      Apewer.Web/WebConfig40.xml
  16. 30
      Apewer.Web/WebConfig461.xml
  17. 34
      Apewer.Web/WebConfigStd.xml
  18. 83
      Apewer/CronAttribute.cs
  19. 95
      Apewer/CronInstance.cs
  20. 60
      Apewer/CronInvoker.cs
  21. 2
      Apewer/IndependentAttribute.cs
  22. 155
      Apewer/Json.cs
  23. 12
      Apewer/Logger.cs
  24. 20
      Apewer/NumberUtility.cs
  25. 7
      Apewer/Source/ColumnAttribute.cs
  26. 339
      Apewer/Source/DbClient.cs
  27. 4
      Apewer/Source/IDbClientAdo.cs
  28. 8
      Apewer/Source/IDbClientOrm.cs
  29. 4
      Apewer/Source/IRecordStamp.cs
  30. 76
      Apewer/Source/OrmHelper.cs
  31. 12
      Apewer/Source/Record.cs
  32. 12
      Apewer/Source/TableAttribute.cs
  33. 43
      Apewer/Source/TableStructure.cs
  34. 18
      Apewer/SystemUtility.cs
  35. 9
      Apewer/TextUtility.cs
  36. 83
      Apewer/Web/ApiOptions.cs
  37. 6
      Apewer/Web/ApiProvider.cs
  38. 2
      Apewer/_Common.props
  39. 27
      Apewer/_Extensions.cs
  40. 15
      ChangeLog.md

11
Apewer.Source/Apewer.Source.csproj

@ -32,17 +32,6 @@
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.4.0" /> <PackageReference Include="System.Text.Encoding.CodePages" Version="4.4.0" />
</ItemGroup> </ItemGroup>
<!-- .NET Core 3.1 -->
<PropertyGroup>
<DefineConstants Condition="'$(TargetFramework)'=='netcoreapp3.1'">$(DefineConstants);MYSQL_6_10;</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netcoreapp3.1'">
<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.4.1" />
<PackageReference Include="System.Data.SqlClient" Version="4.4.3" />
<PackageReference Include="System.Security.Permissions" Version="4.4.1" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.4.0" />
</ItemGroup>
<!-- .NET Framework 4.0 --> <!-- .NET Framework 4.0 -->
<PropertyGroup> <PropertyGroup>
<DefineConstants Condition="'$(TargetFramework)'=='net40'">$(DefineConstants);MYSQL_6_9;</DefineConstants> <DefineConstants Condition="'$(TargetFramework)'=='net40'">$(DefineConstants);MYSQL_6_9;</DefineConstants>

18
Apewer.Source/Source/Access.cs

@ -67,8 +67,8 @@ namespace Apewer.Source
} }
/// <summary>连接数据库,若未连接则尝试连接。</summary> /// <summary>连接数据库,若未连接则尝试连接。</summary>
/// <returns>是否已连接。</returns> /// <returns>错误信息。</returns>
public bool Connect() public string Connect()
{ {
if (_connection == null) if (_connection == null)
{ {
@ -77,19 +77,20 @@ namespace Apewer.Source
} }
else else
{ {
if (_connection.State == ConnectionState.Open) return true; if (_connection.State == ConnectionState.Open) return null;
} }
try try
{ {
_connection.Open(); _connection.Open();
if (_connection.State == ConnectionState.Open) return true; if (_connection.State == ConnectionState.Open) return null;
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Error(nameof(Access), "Connect", ex, _connstr); Logger.Error(nameof(Access), "Connect", ex, _connstr);
Close(); Close();
return ex.Message;
} }
return false; return "连接失败。";
} }
/// <summary>释放对象所占用的系统资源。</summary> /// <summary>释放对象所占用的系统资源。</summary>
@ -124,7 +125,8 @@ namespace Apewer.Source
/// <summary>启动事务。</summary> /// <summary>启动事务。</summary>
public string Begin(bool commit, Class<IsolationLevel> isolation) public string Begin(bool commit, Class<IsolationLevel> isolation)
{ {
if (!Connect()) return "未连接。"; var connect = Connect();
if (connect.NotEmpty()) return connect;
if (_transaction != null) return "存在已启动的事务,无法再次启动。"; if (_transaction != null) return "存在已启动的事务,无法再次启动。";
try try
{ {
@ -192,7 +194,7 @@ namespace Apewer.Source
if (sql.IsBlank()) return Example.InvalidQueryStatement; if (sql.IsBlank()) return Example.InvalidQueryStatement;
var connected = Connect(); var connected = Connect();
if (!connected) return Example.InvalidQueryConnection; if (connected.NotEmpty()) return new Query(false, connected);
try try
{ {
@ -236,7 +238,7 @@ namespace Apewer.Source
if (sql.IsBlank()) return Example.InvalidExecuteStatement; if (sql.IsBlank()) return Example.InvalidExecuteStatement;
var connected = Connect(); var connected = Connect();
if (!connected) return Example.InvalidExecuteConnection; if (connected.NotEmpty()) return new Execute(false, connected);
var inTransaction = _transaction != null; var inTransaction = _transaction != null;
if (!inTransaction) Begin(); if (!inTransaction) Begin();

27
Apewer.Source/Source/ColumnInfo.cs

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Source
{
/// <summary>列信息。</summary>
[Serializable]
public sealed class ColumnInfo
{
/// <summary>字段。</summary>
public string Name { get; set; }
/// <summary>类型。</summary>
public string Type { get; set; }
/// <summary>长度。</summary>
public int Length { get; set; }
/// <summary>是主键。</summary>
public int IsKey { get; set; }
}
}

250
Apewer.Source/Source/MySql.cs

@ -1,6 +1,6 @@
#if MYSQL_6_9 || MYSQL_6_10 #if MYSQL_6_9 || MYSQL_6_10
/* 2021.10.14 */ /* 2021.11.07 */
using Externals.MySql.Data.MySqlClient; using Externals.MySql.Data.MySqlClient;
using System; using System;
@ -45,10 +45,10 @@ namespace Apewer.Source
{ {
_timeout = timeout ?? Timeout.Default; _timeout = timeout ?? Timeout.Default;
var a = TextUtility.AntiInject(address); var a = address ?? "";
var s = TextUtility.AntiInject(store); var s = store ?? "";
var u = TextUtility.AntiInject(user); var u = user ?? "";
var p = TextUtility.AntiInject(pass); var p = pass ?? "";
var cs = $"server={a}; database={s}; uid={u}; pwd={p}; "; var cs = $"server={a}; database={s}; uid={u}; pwd={p}; ";
_connectionstring = cs; _connectionstring = cs;
_storename = new Class<string>(s); _storename = new Class<string>(s);
@ -77,7 +77,7 @@ namespace Apewer.Source
public string ConnectionString { get => _connectionstring; } public string ConnectionString { get => _connectionstring; }
/// <summary></summary> /// <summary></summary>
public bool Connect() public string Connect()
{ {
if (_connection == null) if (_connection == null)
{ {
@ -86,7 +86,7 @@ namespace Apewer.Source
} }
else else
{ {
if (_connection.State == ConnectionState.Open) return true; if (_connection.State == ConnectionState.Open) return null;
} }
// try // try
@ -94,8 +94,8 @@ namespace Apewer.Source
_connection.Open(); _connection.Open();
switch (_connection.State) switch (_connection.State)
{ {
case ConnectionState.Open: return true; case ConnectionState.Open: return null;
default: return false; default: return $"连接失败,当前处于 {_connection.State} 状态。";
} }
} }
// catch (Exception ex) // catch (Exception ex)
@ -138,7 +138,7 @@ namespace Apewer.Source
/// <summary>启动事务。</summary> /// <summary>启动事务。</summary>
public string Begin(bool commit, Class<System.Data.IsolationLevel> isolation) public string Begin(bool commit, Class<System.Data.IsolationLevel> isolation)
{ {
if (!Connect()) return "未连接。"; if (Connect() != null) return "未连接。";
if (_transaction != null) return "存在已启动的事务,无法再次启动。"; if (_transaction != null) return "存在已启动的事务,无法再次启动。";
try try
{ {
@ -203,7 +203,7 @@ namespace Apewer.Source
if (sql.IsBlank()) return Example.InvalidQueryStatement; if (sql.IsBlank()) return Example.InvalidQueryStatement;
var connected = Connect(); var connected = Connect();
if (!connected) return Example.InvalidQueryConnection; if (connected.NotEmpty()) return Example.InvalidQueryConnection;
try try
{ {
@ -244,7 +244,7 @@ namespace Apewer.Source
if (sql.IsBlank()) return Example.InvalidExecuteStatement; if (sql.IsBlank()) return Example.InvalidExecuteStatement;
var connected = Connect(); var connected = Connect();
if (!connected) return Example.InvalidExecuteConnection; if (connected.NotEmpty()) return new Execute(false, connected);
var inTransaction = _transaction != null; var inTransaction = _transaction != null;
if (!inTransaction) Begin(); if (!inTransaction) Begin();
@ -290,7 +290,7 @@ namespace Apewer.Source
dps = new List<IDataParameter>(count); dps = new List<IDataParameter>(count);
foreach (var p in parameters) foreach (var p in parameters)
{ {
var dp = CreateDataParameter(p); var dp = Parameter(p);
dps.Add(dp); dps.Add(dp);
} }
} }
@ -385,7 +385,7 @@ namespace Apewer.Source
var lower = column.Field.ToLower(); var lower = column.Field.ToLower();
if (columns.Contains(lower)) continue; if (columns.Contains(lower)) continue;
var type = GetColumnDeclaration(column); var type = Declaration(column);
if (type.IsEmpty()) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); if (type.IsEmpty()) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。");
// alter table `_record` add column `_index` bigint; // alter table `_record` add column `_index` bigint;
@ -407,7 +407,7 @@ namespace Apewer.Source
if (structure.Independent && column.Independent) continue; if (structure.Independent && column.Independent) continue;
// 字段。 // 字段。
var type = GetColumnDeclaration(column); var type = Declaration(column);
if (type.IsEmpty()) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); if (type.IsEmpty()) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。");
columns.Add(type); columns.Add(type);
columnsAdded++; columnsAdded++;
@ -451,10 +451,11 @@ namespace Apewer.Source
} }
// 连接数据库。 // 连接数据库。
if (!Connect()) var connect = Connect();
if (connect.NotEmpty())
{ {
sql = null; sql = null;
return "连接数据库失败。"; return $"连接数据库失败。({connect})";
} }
sql = GetCreateStetement(structure); sql = GetCreateStetement(structure);
@ -476,24 +477,40 @@ namespace Apewer.Source
public string Initialize(Record model) => (model == null) ? "参数无效。" : Initialize(model.GetType()); public string Initialize(Record model) => (model == null) ? "参数无效。" : Initialize(model.GetType());
/// <summary>插入记录。返回错误信息。</summary> /// <summary>插入记录。返回错误信息。</summary>
public string Insert(object record) public string Insert(object record, string table = null)
{ {
if (record == null) return "参数无效。"; if (record == null) return "参数无效。";
OrmHelper.FixProperties(record); OrmHelper.FixProperties(record);
var structure = TableStructure.Parse(record.GetType()); var structure = TableStructure.Parse(record.GetType());
if (structure == null) return "无法解析记录模型。"; if (structure == null) return "无法解析记录模型。";
if (string.IsNullOrEmpty(table)) table = structure.Name;
if (string.IsNullOrEmpty(table)) return "表名称无效。";
var parameters = structure.CreateParameters(record, CreateDataParameter); var ps = structure.CreateParameters(record, Parameter);
var sql = GenerateInsertStatement(structure.Name, parameters); var psc = ps.Length;
var execute = Execute(sql, parameters); if (psc < 1) return "数据模型不包含字段。";
var names = new List<string>(psc);
var values = new List<string>(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; if (execute.Success) return TextUtility.Empty;
return execute.Message; return execute.Message;
} }
/// <summary>更新记录,实体中的 Key 属性不被更新。返回错误信息。</summary> /// <summary>更新记录,实体中的 Key 属性不被更新。返回错误信息。</summary>
/// <remarks>无法更新带有 Independent 特性的模型(缺少 Key 属性)。</remarks> /// <remarks>无法更新带有 Independent 特性的模型(缺少 Key 属性)。</remarks>
public string Update(IRecord record) public string Update(IRecord record, string table = null)
{ {
if (record == null) return "参数无效。"; if (record == null) return "参数无效。";
FixProperties(record); FixProperties(record);
@ -502,10 +519,23 @@ namespace Apewer.Source
var structure = TableStructure.Parse(record.GetType()); var structure = TableStructure.Parse(record.GetType());
if (structure == null) return "无法解析记录模型。"; if (structure == null) return "无法解析记录模型。";
if (structure.Independent) return "无法更新带有 Independent 特性的模型。"; 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<string>(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 execute = Execute(sql, ps);
var sql = GenerateUpdateStatement(structure, record.Key, parameters);
var execute = Execute(sql, parameters);
if (execute.Success) return TextUtility.Empty; if (execute.Success) return TextUtility.Empty;
return execute.Message; return execute.Message;
} }
@ -514,7 +544,16 @@ namespace Apewer.Source
public Result<object[]> Query(Type model, string sql) => OrmHelper.Query(this, model, sql); public Result<object[]> Query(Type model, string sql) => OrmHelper.Query(this, model, sql);
/// <summary></summary> /// <summary></summary>
public Result<T[]> Query<T>(string sql) where T : class, new() => OrmHelper.Query<T>(this, sql); public Result<T[]> Query<T>(string sql) where T : class, new()
{
var query = Query(sql);
if (!query.Success) return new Result<T[]>(query.Message);
var records = query.Fill<T>();
query.Dispose();
var result = new Result<T[]>(records);
return result;
}
/// <summary>获取所有记录。Flag 为 0 时将忽略 Flag 条件。</summary> /// <summary>获取所有记录。Flag 为 0 时将忽略 Flag 条件。</summary>
public Result<object[]> Query(Type model, long flag = 0) => OrmHelper.Query(this, model, (tn) => public Result<object[]> Query(Type model, long flag = 0) => OrmHelper.Query(this, model, (tn) =>
@ -552,7 +591,7 @@ namespace Apewer.Source
} }
/// <summary>获取记录。</summary> /// <summary>获取记录。</summary>
public Result<IRecord> Get(Type model, string key, long flag = 0) => OrmHelper.Get(this, model, key, (tn, sk) => public Result<object> 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;"; if (flag == 0) return $"select * from `{tn}` where `_key`='{sk}' limit 1;";
return $"select * from `{tn}` where `_key`='{sk}' and `_flag`={flag} limit 1;"; return $"select * from `{tn}` where `_key`='{sk}' and `_flag`={flag} limit 1;";
@ -685,14 +724,14 @@ namespace Apewer.Source
/// <summary></summary> /// <summary></summary>
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidOperationException"></exception> /// <exception cref="InvalidOperationException"></exception>
internal static MySqlParameter CreateDataParameter(Parameter parameter) internal static MySqlParameter Parameter(Parameter parameter)
{ {
if (parameter == null) throw new InvalidOperationException("参数无效。"); 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);
} }
/// <summary></summary> /// <summary></summary>
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; if (TextUtility.IsBlank(name)) return null;
@ -756,7 +795,7 @@ namespace Apewer.Source
} }
/// <summary></summary> /// <summary></summary>
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(); var parameter = new MySqlParameter();
parameter.ParameterName = name; parameter.ParameterName = name;
@ -766,7 +805,7 @@ namespace Apewer.Source
return parameter; return parameter;
} }
private static string GetColumnDeclaration(ColumnAttribute column) private static string Declaration(ColumnAttribute column)
{ {
var type = TextUtility.Empty; var type = TextUtility.Empty;
var length = Math.Max(0, (int)column.Length); var length = Math.Max(0, (int)column.Length);
@ -814,156 +853,11 @@ namespace Apewer.Source
return TextUtility.Merge("`", (string)column.Field, "` ", type); 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<string> GetParametersNames(IEnumerable<IDataParameter> parameters)
{
var columns = new List<string>();
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<string> 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<string>(count);
var values = new List<string>(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<string> 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<string>(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;
}
/// <summary>生成 INSERT INTO 语句。表名必须有效,无有效参数时将获取空结果。</summary>
/// <exception cref="System.ArgumentException"></exception>
/// <exception cref="System.ArgumentNullException"></exception>
public static string GenerateInsertStatement(string table, IEnumerable<IDataParameter> 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);
}
/// <summary>生成 UPDATE 语句,键字段名为“_key”。表名必须有效,键值必须有效,无有效参数时将获取空结果。</summary>
/// <exception cref="System.ArgumentException"></exception>
/// <exception cref="System.ArgumentNullException"></exception>
public static string GenerateUpdateStatement(TableStructure structure, string key, IEnumerable<IDataParameter> 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);
}
/// <summary>获取每个数据库中,每个表的容量,单位为字节。</summary> /// <summary>获取每个数据库中,每个表的容量,单位为字节。</summary>
public static Dictionary<string, Dictionary<string, long>> GetTablesCapacity(MySql source) public static Dictionary<string, Dictionary<string, long>> GetTablesCapacity(MySql source)
{ {
var result = new Dictionary<string, Dictionary<string, long>>(); var result = new Dictionary<string, Dictionary<string, long>>();
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`"; 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)) using (var query = (Query)source.Query(sql))

299
Apewer.Source/Source/SqlClient.cs

@ -1,4 +1,4 @@
/* 2021.10.14 */ /* 2021.11.07 */
using Apewer; using Apewer;
using Apewer.Source; using Apewer.Source;
@ -6,15 +6,14 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Data.Common; using System.Data.Common;
using System.Data.SqlClient;
using System.Net; using System.Net;
using System.Text; using System.Text;
using static Apewer.Source.OrmHelper; using static Apewer.Source.OrmHelper;
using System.Data.SqlClient;
#if NETFRAMEWORK #if NETFRAMEWORK
using System.Data.Sql; using System.Data.Sql;
#else
#endif #endif
namespace Apewer.Source namespace Apewer.Source
@ -48,10 +47,10 @@ namespace Apewer.Source
{ {
if (timeout == null) timeout = Timeout.Default; if (timeout == null) timeout = Timeout.Default;
var a = TextUtility.AntiInject(address); var a = address ?? "";
var s = TextUtility.AntiInject(store); var s = store ?? "";
var u = TextUtility.AntiInject(user); var u = user ?? "";
var p = TextUtility.AntiInject(pass); var p = pass ?? "";
var cs = $"data source = {a}; initial catalog = {s}; "; var cs = $"data source = {a}; initial catalog = {s}; ";
if (string.IsNullOrEmpty(u)) cs += "integrated security = sspi; "; if (string.IsNullOrEmpty(u)) cs += "integrated security = sspi; ";
else else
@ -88,7 +87,7 @@ namespace Apewer.Source
} }
/// <summary>连接数据库,若未连接则尝试连接,获取连接成功的状态。</summary> /// <summary>连接数据库,若未连接则尝试连接,获取连接成功的状态。</summary>
public bool Connect() public string Connect()
{ {
if (_db == null) if (_db == null)
{ {
@ -97,22 +96,22 @@ namespace Apewer.Source
} }
else else
{ {
if (_db.State == ConnectionState.Open) return true; if (_db.State == ConnectionState.Open) return null;
} }
try try
{ {
_db.Open(); _db.Open();
switch (_db.State) switch (_db.State)
{ {
case ConnectionState.Open: return true; case ConnectionState.Open: return null;
default: return false; default: return $"连接失败,当前处于 {_db.State} 状态。";
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Error(nameof(SqlClient), "Connection", ex, _db.ConnectionString); Logger.Error(nameof(SqlClient), "Connection", ex, _db.ConnectionString);
Close(); Close();
return false; return ex.Message;
} }
} }
@ -148,7 +147,8 @@ namespace Apewer.Source
/// <summary>启动事务。</summary> /// <summary>启动事务。</summary>
public string Begin(bool commit, Class<IsolationLevel> isolation) public string Begin(bool commit, Class<IsolationLevel> isolation)
{ {
if (!Connect()) return "未连接。"; var connect = Connect();
if (connect.NotEmpty()) return connect;
if (_transaction != null) return "存在已启动的事务,无法再次启动。"; if (_transaction != null) return "存在已启动的事务,无法再次启动。";
try try
{ {
@ -215,7 +215,7 @@ namespace Apewer.Source
{ {
if (TextUtility.IsBlank(sql)) return Example.InvalidQueryStatement; if (TextUtility.IsBlank(sql)) return Example.InvalidQueryStatement;
var connected = Connect(); var connected = Connect();
if (!connected) return Example.InvalidQueryConnection; if (connected.NotEmpty()) return new Query(false, connected);
try try
{ {
@ -259,7 +259,7 @@ namespace Apewer.Source
if (TextUtility.IsBlank(sql)) return Example.InvalidExecuteStatement; if (TextUtility.IsBlank(sql)) return Example.InvalidExecuteStatement;
var connected = Connect(); var connected = Connect();
if (!connected) return Example.InvalidExecuteConnection; if (connected.NotEmpty()) return new Execute(false, connected);
var inTransaction = _transaction != null; var inTransaction = _transaction != null;
if (!inTransaction) Begin(); if (!inTransaction) Begin();
@ -299,7 +299,7 @@ namespace Apewer.Source
public string[] TableNames() public string[] TableNames()
{ {
var list = new List<string>(); var list = new List<string>();
if (Connect()) if (Connect().IsEmpty())
{ {
var sql = "select [name] from [sysobjects] where [type] = 'u' order by [name]; "; var sql = "select [name] from [sysobjects] where [type] = 'u' order by [name]; ";
var query = (Query)Query(sql); var query = (Query)Query(sql);
@ -318,7 +318,7 @@ namespace Apewer.Source
public string[] StoreNames() public string[] StoreNames()
{ {
var list = new List<string>(); var list = new List<string>();
if (Connect()) if (Connect().IsEmpty())
{ {
var sql = "select [name] from [master]..[sysdatabases] order by [name]; "; var sql = "select [name] from [master]..[sysdatabases] order by [name]; ";
var query = (Query)Query(sql); var query = (Query)Query(sql);
@ -341,7 +341,7 @@ namespace Apewer.Source
public string[] ColumnNames(string tableName) public string[] ColumnNames(string tableName)
{ {
var list = new List<string>(); var list = new List<string>();
if (Connect()) if (Connect().IsEmpty())
{ {
var table = TextUtility.AntiInject(tableName); var table = TextUtility.AntiInject(tableName);
var sql = TextUtility.Merge("select [name] from [syscolumns] where [id] = object_id('", table, "'); "); var sql = TextUtility.Merge("select [name] from [syscolumns] where [id] = object_id('", table, "'); ");
@ -357,6 +357,60 @@ namespace Apewer.Source
return list.ToArray(); 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;
}
/// <summary>获取列信息。</summary>
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<ColumnInfo>();
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();
}
}
/// <summary>创建表,当表不存在时创建表,当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。</summary> /// <summary>创建表,当表不存在时创建表,当现存表中缺少模型中属性对应的列时增加列。成功时返回空字符串,发生异常时返回异常信息。</summary>
public string Initialize<T>() where T : class, new() => Initialize(typeof(T)); public string Initialize<T>() where T : class, new() => Initialize(typeof(T));
@ -367,7 +421,8 @@ namespace Apewer.Source
if (structure == null) return "无法解析记录模型。"; if (structure == null) return "无法解析记录模型。";
// 连接数据库。 // 连接数据库。
if (!Connect()) return "连接数据库失败。"; var connect = Connect();
if (connect.NotEmpty()) return connect;
// 检查现存表。 // 检查现存表。
var exists = false; var exists = false;
@ -411,8 +466,8 @@ namespace Apewer.Source
var lower = column.Field.ToLower(); var lower = column.Field.ToLower();
if (columns.Contains(lower)) continue; if (columns.Contains(lower)) continue;
var type = GetColumnDeclaration(column); var type = Declaration(column);
if (type == TextUtility.Empty) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); if (string.IsNullOrEmpty(type)) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。");
var sql = TextUtility.Merge("alter table [", structure.Name, "] add ", type, "; "); var sql = TextUtility.Merge("alter table [", structure.Name, "] add ", type, "; ");
var execute = Execute(sql); var execute = Execute(sql);
@ -428,10 +483,10 @@ namespace Apewer.Source
// 检查 Independent 特性。 // 检查 Independent 特性。
if (structure.Independent && column.Independent) continue; 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 (!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); sqlcolumns.Add(type);
} }
var sql = TextUtility.Merge("create table [", structure.Name, "](", string.Join(", ", sqlcolumns.ToArray()), "); "); var sql = TextUtility.Merge("create table [", structure.Name, "](", string.Join(", ", sqlcolumns.ToArray()), "); ");
@ -442,24 +497,41 @@ namespace Apewer.Source
} }
/// <summary>插入记录。返回错误信息。</summary> /// <summary>插入记录。返回错误信息。</summary>
public string Insert(object record) public string Insert(object record, string table = null)
{ {
if (record == null) return "参数无效。"; if (record == null) return "参数无效。";
FixProperties(record); FixProperties(record);
var structure = TableStructure.Parse(record.GetType()); var structure = TableStructure.Parse(record.GetType());
if (structure == null) return "无法解析记录模型。"; 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<string>(psc);
var values = new List<string>(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 execute = Execute(sql, ps);
var sql = GenerateInsertStatement(structure.Name, parameters);
var execute = Execute(sql, parameters);
if (execute.Success) return TextUtility.Empty; if (execute.Success) return TextUtility.Empty;
return execute.Message; return execute.Message;
} }
/// <summary>更新记录,实体中的 Key 属性不被更新。返回错误信息。</summary> /// <summary>更新记录,实体中的 Key 属性不被更新。返回错误信息。</summary>
/// <remarks>无法更新带有 Independent 特性的模型(缺少 Key 属性)。</remarks> /// <remarks>无法更新带有 Independent 特性的模型(缺少 Key 属性)。</remarks>
public string Update(IRecord record) public string Update(IRecord record, string table = null)
{ {
if (record == null) return "参数无效。"; if (record == null) return "参数无效。";
FixProperties(record); FixProperties(record);
@ -468,10 +540,23 @@ namespace Apewer.Source
var structure = TableStructure.Parse(record.GetType()); var structure = TableStructure.Parse(record.GetType());
if (structure == null) return "无法解析记录模型。"; if (structure == null) return "无法解析记录模型。";
if (structure.Independent) return "无法更新带有 Independent 特性的模型。"; 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<string>();
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 execute = Execute(sql, ps);
var sql = GenerateUpdateStatement(structure.Name, record.Key, parameters);
var execute = Execute(sql, parameters);
if (execute.Success) return TextUtility.Empty; if (execute.Success) return TextUtility.Empty;
return execute.Message; return execute.Message;
} }
@ -480,7 +565,16 @@ namespace Apewer.Source
public Result<object[]> Query(Type model, string sql) => OrmHelper.Query(this, model, sql); public Result<object[]> Query(Type model, string sql) => OrmHelper.Query(this, model, sql);
/// <summary>获取按指定语句查询到的所有记录。</summary> /// <summary>获取按指定语句查询到的所有记录。</summary>
public Result<T[]> Query<T>(string sql) where T : class, new() => OrmHelper.Query<T>(this, sql); public Result<T[]> Query<T>(string sql) where T : class, new()
{
var query = Query(sql);
if (!query.Success) return new Result<T[]>(query.Message);
var records = query.Fill<T>();
query.Dispose();
var result = new Result<T[]>(records);
return result;
}
/// <summary>获取记录。</summary> /// <summary>获取记录。</summary>
public Result<object[]> Query(Type model, long flag = 0) => OrmHelper.Query(this, model, (tn) => public Result<object[]> Query(Type model, long flag = 0) => OrmHelper.Query(this, model, (tn) =>
@ -497,7 +591,7 @@ namespace Apewer.Source
}); });
/// <summary>获取具有指定 Key 的记录。</summary> /// <summary>获取具有指定 Key 的记录。</summary>
public Result<IRecord> Get(Type model, string key, long flag = 0) => OrmHelper.Get(this, model, key, (tn, sk) => public Result<object> 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}'; "; if (flag == 0) return $"select top 1 * from [{tn}] _key='{sk}'; ";
return $"select top 1 * from [{tn}] where _key='{sk}' and _key='{sk}'; "; return $"select top 1 * from [{tn}] where _key='{sk}' and _key='{sk}'; ";
@ -524,7 +618,7 @@ namespace Apewer.Source
#region public static #region public static
#if NETFRAMEWORK #if NET20 || NET40
/// <summary>枚举本地网络中服务器的名称。</summary> /// <summary>枚举本地网络中服务器的名称。</summary>
public static SqlServerSource[] EnumerateServer() public static SqlServerSource[] EnumerateServer()
@ -565,14 +659,14 @@ namespace Apewer.Source
/// <summary>创建参数。</summary> /// <summary>创建参数。</summary>
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidOperationException"></exception> /// <exception cref="InvalidOperationException"></exception>
internal static SqlParameter CreateDataParameter(Parameter parameter) static SqlParameter Parameter(Parameter parameter)
{ {
if (parameter == null) throw new InvalidOperationException("参数无效。"); 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);
} }
/// <summary>创建参数。</summary> /// <summary>创建参数。</summary>
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); var vname = TextUtility.Trim(name);
if (TextUtility.IsBlank(vname)) return null; if (TextUtility.IsBlank(vname)) return null;
@ -600,7 +694,7 @@ namespace Apewer.Source
case ColumnType.NVarChar: case ColumnType.NVarChar:
case ColumnType.NVarChar191: case ColumnType.NVarChar191:
case ColumnType.NVarCharMax: case ColumnType.NVarCharMax:
vtype = SqlDbType.VarChar; vtype = SqlDbType.NVarChar;
break; break;
case ColumnType.Text: case ColumnType.Text:
vtype = SqlDbType.Text; vtype = SqlDbType.Text;
@ -645,7 +739,7 @@ namespace Apewer.Source
} }
/// <summary>创建参数。</summary> /// <summary>创建参数。</summary>
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) if (value is string && value != null && size > 0)
{ {
@ -661,7 +755,7 @@ namespace Apewer.Source
} }
/// <summary>创建参数。</summary> /// <summary>创建参数。</summary>
public static SqlParameter CreateDataParameter(String name, SqlDbType type, Object value) public static SqlParameter Parameter(string name, SqlDbType type, object value)
{ {
var p = new SqlParameter(); var p = new SqlParameter();
p.ParameterName = name ?? ""; p.ParameterName = name ?? "";
@ -670,11 +764,7 @@ namespace Apewer.Source
return p; return p;
} }
#endregion static string Declaration(ColumnAttribute column)
#region private
static string GetColumnDeclaration(ColumnAttribute column)
{ {
var type = TextUtility.Empty; var type = TextUtility.Empty;
var vcolumn = column; var vcolumn = column;
@ -709,7 +799,7 @@ namespace Apewer.Source
type = TextUtility.Merge("nvarchar(", Math.Min(4000, length).ToString(), ")"); type = TextUtility.Merge("nvarchar(", Math.Min(4000, length).ToString(), ")");
break; break;
case ColumnType.NVarChar191: case ColumnType.NVarChar191:
type = TextUtility.Merge("nvarchar(255)"); type = TextUtility.Merge("nvarchar(191)");
break; break;
case ColumnType.NVarCharMax: case ColumnType.NVarCharMax:
type = TextUtility.Merge("nvarchar(max)"); type = TextUtility.Merge("nvarchar(max)");
@ -723,123 +813,6 @@ namespace Apewer.Source
return TextUtility.Merge("[", vcolumn.Field, "] ", type); 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<IDataParameter> parameters)
{
var columns = new List<string>();
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<string>();
var values = new List<string>();
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<string>();
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;
}
/// <summary>生成 INSERT INTO 语句。表名必须有效,无有效参数时将获取空结果。</summary>
/// <exception cref="System.ArgumentException"></exception>
/// <exception cref="System.ArgumentNullException"></exception>
static string GenerateInsertStatement(string table, IEnumerable<IDataParameter> 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);
}
/// <summary>生成 UPDATE 语句,键字段名为“_key”。表名必须有效,键值必须有效,无有效参数时将获取空结果。</summary>
/// <exception cref="System.ArgumentException"></exception>
/// <exception cref="System.ArgumentNullException"></exception>
static string GenerateUpdateStatement(string table, string key, IEnumerable<IDataParameter> 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 #endregion
} }

305
Apewer.Source/Source/Sqlite.cs

@ -1,4 +1,4 @@
/* 2021.10.14 */ /* 2021.11.07 */
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -39,7 +39,7 @@ namespace Apewer.Source
_timeout = timeout ?? Timeout.Default; _timeout = timeout ?? Timeout.Default;
_path = path.IsEmpty() ? Memory : path; _path = path.IsEmpty() ? Memory : path;
_pass = pass; _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; "; else _connstring = $"data source='{_path}'; password={_pass}; version=3; ";
} }
@ -60,7 +60,7 @@ namespace Apewer.Source
/// <summary>连接数据库,若未连接则尝试连接。</summary> /// <summary>连接数据库,若未连接则尝试连接。</summary>
/// <returns>是否已连接。</returns> /// <returns>是否已连接。</returns>
public bool Connect() public string Connect()
{ {
if (_db == null) if (_db == null)
{ {
@ -69,22 +69,22 @@ namespace Apewer.Source
} }
else else
{ {
if (_db.State == ConnectionState.Open) return true; if (_db.State == ConnectionState.Open) return null;
} }
try try
{ {
_db.Open(); _db.Open();
switch (_db.State) switch (_db.State)
{ {
case ConnectionState.Open: return true; case ConnectionState.Open: return null;
default: return false; default: return $"连接失败,当前处于 {_db.State} 状态。";
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Error(nameof(Sqlite), "Connection", ex, _db.ConnectionString); Logger.Error(nameof(Sqlite), "Connection", ex, _db.ConnectionString);
Close(); Close();
return false; return ex.Message;
} }
} }
@ -122,7 +122,8 @@ namespace Apewer.Source
/// <summary>启动事务。</summary> /// <summary>启动事务。</summary>
public string Begin(bool commit, Class<IsolationLevel> isolation) public string Begin(bool commit, Class<IsolationLevel> isolation)
{ {
if (!Connect()) return "未连接。"; var connect = Connect();
if (connect.NotEmpty()) return connect;
if (_transaction != null) return "存在已启动的事务,无法再次启动。"; if (_transaction != null) return "存在已启动的事务,无法再次启动。";
try try
{ {
@ -190,7 +191,7 @@ namespace Apewer.Source
if (string.IsNullOrEmpty(sql)) return Example.InvalidQueryStatement; if (string.IsNullOrEmpty(sql)) return Example.InvalidQueryStatement;
var connected = Connect(); var connected = Connect();
if (!connected) return Example.InvalidQueryConnection; if (connected.NotEmpty()) return new Query(false, connected);
var query = new Query(); var query = new Query();
try try
@ -235,7 +236,7 @@ namespace Apewer.Source
if (string.IsNullOrEmpty(sql)) return Example.InvalidExecuteStatement; if (string.IsNullOrEmpty(sql)) return Example.InvalidExecuteStatement;
var connected = Connect(); var connected = Connect();
if (!connected) return Example.InvalidExecuteConnection; if (connected.NotEmpty()) return new Execute(false, connected);
lock (_locker) lock (_locker)
{ {
@ -306,7 +307,7 @@ namespace Apewer.Source
public string[] TableNames() public string[] TableNames()
{ {
var list = new List<string>(); var list = new List<string>();
if (Connect()) if (Connect().IsEmpty())
{ {
var sql = "select name from sqlite_master where type='table' order by name; "; var sql = "select name from sqlite_master where type='table' order by name; ";
var query = (Query)Query(sql); var query = (Query)Query(sql);
@ -325,7 +326,7 @@ namespace Apewer.Source
public string[] ViewNames() public string[] ViewNames()
{ {
var list = new List<string>(); var list = new List<string>();
if (Connect()) if (Connect().IsEmpty())
{ {
var sql = "select name from sqlite_master where type='view' order by name; "; var sql = "select name from sqlite_master where type='view' order by name; ";
var query = (Query)Query(sql); var query = (Query)Query(sql);
@ -344,7 +345,7 @@ namespace Apewer.Source
public string[] ColumnNames(string table) public string[] ColumnNames(string table)
{ {
var list = new List<string>(); var list = new List<string>();
if (Connect()) if (Connect().IsEmpty())
{ {
var t = TextUtility.AntiInject(table); var t = TextUtility.AntiInject(table);
var sql = TextUtility.Merge("pragma table_info('", TextUtility.AntiInject(t), "'); "); var sql = TextUtility.Merge("pragma table_info('", TextUtility.AntiInject(t), "'); ");
@ -374,7 +375,8 @@ namespace Apewer.Source
if (structure == null) return "无法解析记录模型。"; if (structure == null) return "无法解析记录模型。";
// 连接数据库。 // 连接数据库。
if (!Connect()) return "连接数据库失败。"; var connect = Connect();
if (connect.NotEmpty()) return connect;
// 检查现存表。 // 检查现存表。
var exists = false; var exists = false;
@ -402,8 +404,11 @@ namespace Apewer.Source
var sqlcolumns = new List<string>(); var sqlcolumns = new List<string>();
foreach (var column in structure.Columns) foreach (var column in structure.Columns)
{ {
var type = GetColumnDeclaration(column); var type = Declaration(column);
if (type == TextUtility.Empty) return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。"); if (string.IsNullOrEmpty(type))
{
return TextUtility.Merge("类型 ", column.Type.ToString(), " 不受支持。");
}
sqlcolumns.Add(type); sqlcolumns.Add(type);
} }
var sql = TextUtility.Merge("create table [", structure.Name, "](", TextUtility.Join(", ", sqlcolumns), "); "); var sql = TextUtility.Merge("create table [", structure.Name, "](", TextUtility.Join(", ", sqlcolumns), "); ");
@ -414,36 +419,88 @@ namespace Apewer.Source
} }
/// <summary>插入记录。返回错误信息。</summary> /// <summary>插入记录。返回错误信息。</summary>
public string Insert(object record) public string Insert(object record, string table = null)
{ {
if (record == null) return "参数无效。"; if (record == null) return "参数无效。";
OrmHelper.FixProperties(record); OrmHelper.FixProperties(record);
// 解析模型,获取表名。
var structure = TableStructure.Parse(record.GetType()); var structure = TableStructure.Parse(record.GetType());
if (structure == null) return "无法解析记录模型。"; 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<IDataParameter>)parameters); var execute = Execute(sql, ps);
var execute = Execute(sql, parameters);
if (execute.Success && execute.Rows > 0) return TextUtility.Empty; if (execute.Success && execute.Rows > 0) return TextUtility.Empty;
return execute.Message; return execute.Message;
} }
/// <summary>更新记录,实体中的 Key 属性不被更新。返回错误信息。</summary> /// <summary>更新记录,实体中的 Key 属性不被更新。返回错误信息。</summary>
/// <remarks>无法更新带有 Independent 特性的模型(缺少 Key 属性)。</remarks> /// <remarks>无法更新带有 Independent 特性的模型(缺少 Key 属性)。</remarks>
public string Update(IRecord record) public string Update(IRecord record, string table = null)
{ {
if (record == null) return "参数无效。"; if (record == null) return "参数无效。";
FixProperties(record); FixProperties(record);
SetUpdated(record); SetUpdated(record);
// 解析模型,获取表名。
var structure = TableStructure.Parse(record.GetType()); var structure = TableStructure.Parse(record.GetType());
if (structure == null) return "无法解析记录模型。"; if (structure == null) return "无法解析记录模型。";
if (structure.Independent) return "无法更新带有 Independent 特性的模型。"; if (structure.Independent) return "无法更新带有 Independent 特性的模型。";
if (string.IsNullOrEmpty(table)) table = structure.Name;
var parameters = structure.CreateParameters(record, CreateDataParameter, "_key"); if (string.IsNullOrEmpty(table)) return "表名称无效。";
var sql = GenerateUpdateStatement(structure.Name, record.Key, parameters);
var execute = Execute(sql, parameters); // 准备参数。
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; if (execute.Success && execute.Rows > 0) return TextUtility.Empty;
return execute.Message; return execute.Message;
} }
@ -452,7 +509,16 @@ namespace Apewer.Source
public Result<object[]> Query(Type model, string sql) => OrmHelper.Query(this, model, sql); public Result<object[]> Query(Type model, string sql) => OrmHelper.Query(this, model, sql);
/// <summary>获取按指定语句查询到的所有记录。</summary> /// <summary>获取按指定语句查询到的所有记录。</summary>
public Result<T[]> Query<T>(string sql) where T : class, new() => OrmHelper.Query<T>(this, sql); public Result<T[]> Query<T>(string sql) where T : class, new()
{
var query = Query(sql);
if (!query.Success) return new Result<T[]>(query.Message);
var records = query.Fill<T>();
query.Dispose();
var result = new Result<T[]>(records);
return result;
}
/// <summary>查询多条记录。</summary> /// <summary>查询多条记录。</summary>
public Result<object[]> Query(Type model, long flag = 0) => OrmHelper.Query(this, model, (tn) => public Result<object[]> Query(Type model, long flag = 0) => OrmHelper.Query(this, model, (tn) =>
@ -469,7 +535,7 @@ namespace Apewer.Source
}); });
/// <summary>获取具有指定 Key 的记录。</summary> /// <summary>获取具有指定 Key 的记录。</summary>
public Result<IRecord> Get(Type model, string key, long flag = 0) => OrmHelper.Get(this, model, key, (tn, sk) => public Result<object> 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; "; if (flag == 0) return $"select * from [{tn}] where _key='{sk}' limit 1; ";
return $"select * from [{tn}] where _flag={flag} and _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 type = TextUtility.Empty;
var length = NumberUtility.Restrict(column.Length, 0, 255).ToString(); var length = NumberUtility.Restrict(column.Length, 0, 255).ToString();
@ -568,6 +634,9 @@ namespace Apewer.Source
case ColumnType.Float: case ColumnType.Float:
type = "real"; type = "real";
break; break;
case ColumnType.DateTime:
type = "datetime";
break;
case ColumnType.VarChar: case ColumnType.VarChar:
type = TextUtility.Merge("varchar(", length, ")"); type = TextUtility.Merge("varchar(", length, ")");
break; break;
@ -595,20 +664,20 @@ namespace Apewer.Source
default: default:
return TextUtility.Empty; return TextUtility.Empty;
} }
return TextUtility.Merge("[", (object)column.Field, "] ", type); return TextUtility.Merge("[", column.Field, "] ", type);
} }
/// <summary>创建参数。</summary> /// <summary>创建参数。</summary>
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidOperationException"></exception> /// <exception cref="InvalidOperationException"></exception>
public static SQLiteParameter CreateDataParameter(Parameter parameter) public static SQLiteParameter Parameter(Parameter parameter)
{ {
if (parameter == null) throw new InvalidOperationException("参数无效。"); 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);
} }
/// <summary>创建参数。</summary> /// <summary>创建参数。</summary>
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); var n = TextUtility.Trim(name);
if (TextUtility.IsBlank(n)) return null; if (TextUtility.IsBlank(n)) return null;
@ -652,28 +721,22 @@ namespace Apewer.Source
} }
/// <summary>创建参数。</summary> /// <summary>创建参数。</summary>
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(); var p = new SQLiteParameter();
p.ParameterName = name; p.ParameterName = field;
p.TypeName = type; p.DbType = type;
p.Value = v;
p.Size = size; p.Size = size;
p.Value = value;
return p; return p;
} }
/// <summary>创建参数。</summary> /// <summary>创建参数。</summary>
public static SQLiteParameter CreateDataParameter(string name, string type, object value) public static IDbDataParameter Parameter(string field, DbType type, object value)
{ {
var p = new SQLiteParameter(); var p = new SQLiteParameter();
p.ParameterName = name; p.ParameterName = field;
p.TypeName = type; p.DbType = type;
p.Value = value; p.Value = value;
return p; return p;
} }
@ -681,10 +744,12 @@ namespace Apewer.Source
/// <summary>备份数据库,返回错误信息。</summary> /// <summary>备份数据库,返回错误信息。</summary>
public static string Backup(Sqlite source, Sqlite destination) public static string Backup(Sqlite source, Sqlite destination)
{ {
if (source == null) return "SQLite Backup Failed: Invalid Source"; if (source == null) return "备份失败:源无效。";
if (destination == null) return "SQLite Backup Failed: Invalid Destination"; if (destination == null) return "备份失败:目标无效。";
if (!source.Connect()) return "SQLite Backup Failed: Invalid Source Connection"; var sConnect = source.Connect();
if (!destination.Connect()) return "SQLite Backup Failed: Invalid Destination Connection"; if (sConnect.NotEmpty()) return sConnect;
var dConnect = source.Connect();
if (dConnect.NotEmpty()) return dConnect;
lock (source._locker) lock (source._locker)
{ {
@ -703,27 +768,6 @@ namespace Apewer.Source
} }
} }
/// <summary>创建参数。</summary>
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;
}
/// <summary>创建参数。</summary>
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;
}
/// <summary>整理数据库,压缩未使用的空间。</summary> /// <summary>整理数据库,压缩未使用的空间。</summary>
public const string Vacuum = "vacuum"; public const string Vacuum = "vacuum";
@ -732,127 +776,6 @@ namespace Apewer.Source
#endregion #endregion
#region 生成 SQL 语句
/// <summary>生成 INSERT INTO 语句。表名必须有效,无有效参数时将获取空结果。</summary>
/// <exception cref="System.ArgumentException"></exception>
/// <exception cref="System.ArgumentNullException"></exception>
public static string GenerateInsertStatement(string table, IEnumerable<IDataParameter> 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<string> GetParametersNames(IEnumerable<IDataParameter> parameters)
{
var columns = new List<string>();
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<string> columns)
{
var r = TextUtility.Empty;
var t = TextUtility.AntiInject(table, 255);
if (columns != null && !TextUtility.IsBlank(t))
{
var count = 0;
var names = new List<string>();
var values = new List<string>();
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<string> 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<string>();
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;
}
/// <summary>生成 UPDATE 语句,键字段名为“_key”。表名必须有效,键值必须有效,无有效参数时将获取空结果。</summary>
/// <exception cref="System.ArgumentException"></exception>
/// <exception cref="System.ArgumentNullException"></exception>
public static string GenerateUpdateStatement(string table, string key, IEnumerable<IDataParameter> 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
} }
} }

7
Apewer.Web/Apewer.Web.csproj

@ -16,6 +16,13 @@
<Title>Apewer.Web</Title> <Title>Apewer.Web</Title>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="FavIcon.ico" />
<EmbeddedResource Include="WebConfig40.xml" />
<EmbeddedResource Include="WebConfigStd.xml" />
<EmbeddedResource Include="WebConfig461.xml" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Apewer\Apewer.csproj" /> <ProjectReference Include="..\Apewer\Apewer.csproj" />
</ItemGroup> </ItemGroup>

BIN
Apewer.Web/FavIcon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

8
Apewer.Web/Internals/ApiHelper.cs

@ -400,8 +400,8 @@ namespace Apewer.Internals
internal static void Output(ApiProvider provider, ApiOptions options, string type, byte[] bytes) internal static void Output(ApiProvider provider, ApiOptions options, string type, byte[] bytes)
{ {
var preOutput = provider.PreWrite(); var preWrite = provider.PreWrite();
if (!string.IsNullOrEmpty(preOutput)) return; if (!string.IsNullOrEmpty(preWrite)) return;
var headers = PrepareHeaders(options, null); var headers = PrepareHeaders(options, null);
foreach (var header in headers) provider.SetHeader(header.Key, header.Value); 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) internal static void Output(ApiProvider provider, ApiOptions options, ApiResponse response, ApiRequest request, HttpMethod method)
{ {
var preOutput = provider.PreWrite(); var preWrite = provider.PreWrite();
if (!string.IsNullOrEmpty(preOutput)) return; if (!string.IsNullOrEmpty(preWrite)) return;
// 设置头。 // 设置头。
var headers = PrepareHeaders(options, null); var headers = PrepareHeaders(options, null);

1
Apewer.Web/Web/ApiEntries.cs

@ -127,6 +127,7 @@ namespace Apewer.Web
if (type.IsAbstract) return null; if (type.IsAbstract) return null;
if (type.IsGenericType) return null; if (type.IsGenericType) return null;
if (type.GetGenericArguments().NotEmpty()) return null; if (type.GetGenericArguments().NotEmpty()) return null;
if (!RuntimeUtility.CanNew(type)) return null;
// 检查类型的特性。 // 检查类型的特性。
var apis = type.GetCustomAttributes(typeof(ApiAttribute), false); var apis = type.GetCustomAttributes(typeof(ApiAttribute), false);

4
Apewer.Web/Web/ApiProcessor.cs

@ -60,9 +60,7 @@ namespace Apewer.Web
Provider.Options = Options; Provider.Options = Options;
// 检查执行的前提条件,获取 Method 和 URL。 // 检查执行的前提条件,获取 Method 和 URL。
var check = Provider.PreInvoke(); var check = Check();
if (!string.IsNullOrEmpty(check)) return check;
check = Check();
if (!string.IsNullOrEmpty(check)) return check; if (!string.IsNullOrEmpty(check)) return check;
// 准备请求和响应模型。 // 准备请求和响应模型。

32
Apewer.Web/Web/CronAttribute.cs

@ -1,32 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Apewer.Web
{
/// <summary>Cron 特性。</summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class CronAttribute : Attribute
{
internal const int DefaultInterval = 60000;
private int _internval;
/// <summary>两次 Cron 执行的间隔毫秒数。</summary>
public int Interval
{
get { return _internval; }
private set { _internval = value < 1000 ? 1000 : value; }
}
/// <summary>创建 Cron 特性,可指定两次 Cron 执行的间隔毫秒数。</summary>
public CronAttribute(int interval = DefaultInterval)
{
Interval = interval;
}
}
}

124
Apewer.Web/Web/CronInstance.cs

@ -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<DateTime> _ended = null;
public CronInvoker Invoker { get; set; }
public Thread Thread
{
get { return _thread; }
}
public bool Alive
{
get { return GetAlive(); }
}
/// <summary>再次启动 Cron 的时间间隔。</summary>
public int Interval
{
get { return GetInterval(); }
}
/// <summary>最后一次检查的 Alive 值。</summary>
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<DateTime> 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;
}
}
}

31
Apewer.Web/Web/Resources.cs

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace Apewer.Web
{
/// <summary>程序集资源。</summary>
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();
/// <summary>获取预置的 favicon.ico 文件,。</summary>
public static byte[] FavIcon() => Bytes("Apewer.FavIcon.ico");
/// <summary>获取用于 .NET Framework 4.0 的 web.config 文件。</summary>
public static string WebConfig40() => Text("Apewer.WebConfig40.xml");
/// <summary>获取用于 .NET Framework 4.6.1 的 web.config 文件。</summary>
public static string WebConfig461(bool netstandard = false) => Text(netstandard ? "Apewer.WebConfigStd.xml" : "Apewer.WebConfig461.xml");
}
}

30
Apewer.Web/WebConfig40.xml

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.web>
<compilation targetFramework="4.0" debug="true" />
<customErrors mode="Off" />
<globalization fileEncoding="utf-8" />
<httpRuntime targetFramework="4.0" maxRequestLength="2147483647" executionTimeout="3600" enableVersionHeader="false" requestPathInvalidCharacters="" />
<sessionState mode="Off" />
<pages controlRenderingCompatibilityVersion="4.0" />
</system.web>
<system.webServer>
<handlers>
<add name="HttpHandler" path="*" verb="*" type="Apewer.Web.ApiProgram" />
</handlers>
<modules runAllManagedModulesForAllRequests="true">
<add name="HttpModule" type="Apewer.Web.ApiProgram" />
</modules>
<security>
<requestFiltering allowDoubleEscaping="true">
<fileExtensions>
<clear />
</fileExtensions>
<hiddenSegments>
<clear />
</hiddenSegments>
<requestLimits maxAllowedContentLength="2147483647" />
</requestFiltering>
</security>
</system.webServer>
</configuration>

30
Apewer.Web/WebConfig461.xml

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.web>
<compilation targetFramework="4.6.1" debug="true" />
<customErrors mode="Off" />
<globalization fileEncoding="utf-8" />
<httpRuntime targetFramework="4.0" maxRequestLength="2147483647" executionTimeout="3600" enableVersionHeader="false" requestPathInvalidCharacters="" />
<sessionState mode="Off" />
<pages controlRenderingCompatibilityVersion="4.0" />
</system.web>
<system.webServer>
<handlers>
<add name="HttpHandler" path="*" verb="*" type="Apewer.Web.ApiProgram" />
</handlers>
<modules runAllManagedModulesForAllRequests="true">
<add name="HttpModule" type="Apewer.Web.ApiProgram" />
</modules>
<security>
<requestFiltering allowDoubleEscaping="true">
<fileExtensions>
<clear />
</fileExtensions>
<hiddenSegments>
<clear />
</hiddenSegments>
<requestLimits maxAllowedContentLength="2147483647" />
</requestFiltering>
</security>
</system.webServer>
</configuration>

34
Apewer.Web/WebConfigStd.xml

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.web>
<compilation targetFramework="4.6.1" debug="true">
<assemblies>
<add assembly="netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" />
</assemblies>
</compilation>
<customErrors mode="Off" />
<globalization fileEncoding="utf-8" />
<httpRuntime targetFramework="4.0" maxRequestLength="2147483647" executionTimeout="3600" enableVersionHeader="false" requestPathInvalidCharacters="" />
<sessionState mode="Off" />
<pages controlRenderingCompatibilityVersion="4.0" />
</system.web>
<system.webServer>
<handlers>
<add name="HttpHandler" path="*" verb="*" type="Apewer.Web.ApiProgram" />
</handlers>
<modules runAllManagedModulesForAllRequests="true">
<add name="HttpModule" type="Apewer.Web.ApiProgram" />
</modules>
<security>
<requestFiltering allowDoubleEscaping="true">
<fileExtensions>
<clear />
</fileExtensions>
<hiddenSegments>
<clear />
</hiddenSegments>
<requestLimits maxAllowedContentLength="2147483647" />
</requestFiltering>
</security>
</system.webServer>
</configuration>

83
Apewer/CronAttribute.cs

@ -0,0 +1,83 @@
using Apewer.Web;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace Apewer
{
/// <summary>Cron 特性,默认间隔为 60000 毫秒。</summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class CronAttribute : Attribute
{
internal const int DefaultInterval = 60000;
private int _interval;
/// <summary>两次 Cron 执行的间隔毫秒数。</summary>
public int Interval
{
get { return _interval; }
}
/// <summary>创建 Cron 特性,可指定两次 Cron 执行的间隔毫秒数。</summary>
public CronAttribute(int interval = DefaultInterval)
{
_interval = interval;
}
#region CronInvoker
private static Class<CronInvoker> _invoker = new Class<CronInvoker>();
/// <summary>开始 Cron 调用(不阻塞当前线程)。</summary>
/// <remarks>
/// 参数<br />
/// - assemblies: 包含 Cron 的程序集,不指定此参数时将在 AppDomain 中搜索;<br />
/// - logger: 日志记录程序,不指定此参数时将使用 Logger.Default。<br />
/// </remarks>
public static void Start(IEnumerable<Assembly> 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();
}
/// <summary>在当前线程开始 Cron 调用(阻塞当前线程)。</summary>
/// <remarks>
/// 参数<br />
/// - assemblies: 包含 Cron 的程序集,不指定此参数时将在 AppDomain 中搜索;<br />
/// - logger: 日志记录程序,不指定此参数时将使用 Logger.Default。<br />
/// </remarks>
public static void Start(Logger logger, IEnumerable<Assembly> assemblies = null) => Start(assemblies, logger);
/// <summary>打断 Cron 循环,不打断正在执行的 Cron。</summary>
public static void Break()
{
lock (_invoker)
{
if (!_invoker) return;
_invoker.Value.Break();
}
}
#endregion
}
}

95
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<DateTime> _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<DateTime> Ended { get => _ended; }
#endregion
public CronInstance()
{
_thread = new Thread(Listen);
_thread.IsBackground = true;
}
/// <summary>打断循环。</summary>
public void Break() => _break = true;
/// <summary>启动线程执行任务。</summary>
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);
}
}

60
Apewer.Web/Web/CronInvoker.cs → Apewer/CronInvoker.cs

@ -9,7 +9,7 @@ namespace Apewer.Web
{ {
/// <summary>Cron 调度器。</summary> /// <summary>Cron 调度器。</summary>
public sealed class CronInvoker internal sealed class CronInvoker
{ {
#region Instance #region Instance
@ -17,12 +17,12 @@ namespace Apewer.Web
private List<Assembly> _assemblies = null; private List<Assembly> _assemblies = null;
private List<CronInstance> _instances = null; private List<CronInstance> _instances = null;
private bool _break = false; private bool _break = false;
private Action<string> _log = null; private Logger _logger = null;
/// <summary>获取或设置 Log 处理程序。</summary> /// <summary>获取或设置日志记录器。</summary>
public Action<string> 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);
/// <summary>加载程序集。</summary> /// <summary>加载程序集。</summary>
public void Load(IEnumerable<Assembly> assemblies) public void Load(IEnumerable<Assembly> assemblies)
@ -54,10 +54,7 @@ namespace Apewer.Web
} }
/// <summary>通知打断循环,所有 Cron 执行结束后退出。</summary> /// <summary>通知打断循环,所有 Cron 执行结束后退出。</summary>
public void Break() public void Break() => _break = true;
{
_break = true;
}
/// <summary>开始 Cron 调用。</summary> /// <summary>开始 Cron 调用。</summary>
public void Start() public void Start()
@ -79,39 +76,39 @@ namespace Apewer.Web
if (i.Alive) alive++; if (i.Alive) alive++;
if (_break) if (_break)
{ {
i.Break = true; i.Break();
break; break;
} }
// 当前线程正在活动。 // 当前线程正在活动。
if (i.Alive) if (i.Alive)
{ {
i.Latest = true; i._latest = true;
continue; continue;
} }
// 记录 Cron 结束时间,根据结束时间判断再次启动 Cron。 // 记录 Cron 结束时间,根据结束时间判断再次启动 Cron。
if (i.Latest) if (i.Latest)
{ {
Log($"类型 {i.Type.FullName} 已结束。"); Log($"{i.Type.FullName} Ended");
i.Ended = DateTime.Now; i._ended = new Class<DateTime>(DateTime.Now);
i.Latest = false; i._latest = false;
} }
if (i.Ended == null) if (i.Ended == null)
{ {
Log($"准备开始类型 {i.Type.FullName}。"); Log($"{i.Type.FullName} Beginning");
i.Start(); i.Start();
i.Latest = true; i._latest = true;
} }
else else
{ {
var span = DateTime.Now - i.Ended.Value; var span = DateTime.Now - i.Ended.Value;
if (span.TotalMilliseconds >= Convert.ToDouble(i.Interval)) if (span.TotalMilliseconds >= Convert.ToDouble(i.Interval))
{ {
Log($"准备开始类型 {i.Type.FullName}。"); Log($"{i.Type.FullName} Beginning");
i.Start(); i.Start();
i.Latest = true; i._latest = true;
} }
} }
} }
@ -121,7 +118,7 @@ namespace Apewer.Web
break; break;
} }
Thread.Sleep(1000); Thread.Sleep(500);
GC.Collect(); GC.Collect();
} }
@ -139,9 +136,10 @@ namespace Apewer.Web
if (attribute == null) continue; if (attribute == null) continue;
var instance = new CronInstance(); var instance = new CronInstance();
instance.Invoker = this; instance._invoker = this;
instance.Attribute = attribute; instance._attribute = attribute;
instance.Type = type; instance._type = type;
instance._logger = Logger;
list.Add(instance); list.Add(instance);
} }
@ -174,22 +172,12 @@ namespace Apewer.Web
#region Static #region Static
/// <summary>获取或设置日志记录器。</summary> // 在当前线程开始 Cron 调用 。
public static Logger Logger { get; set; } public static CronInvoker Start(IEnumerable<Assembly> assemblies = null, Logger logger = null)
/// <summary>在当前线程开始 Cron 调用,可能会阻塞当前线程。可指定 Log 处理程序。</summary>
public static CronInvoker Start(Assembly assembly, Action<string> log = null)
{
var assemblies = new Assembly[] { assembly };
return Start(assemblies);
}
/// <summary>在当前线程开始 Cron 调用,可能会阻塞当前线程。可指定 Log 处理程序。</summary>
public static CronInvoker Start(IEnumerable<Assembly> assemblies, Action<string> log = null)
{ {
var instance = new CronInvoker(); var instance = new CronInvoker();
instance.LogAction = log; instance.Logger = logger;
instance.Load(assemblies); instance.Load(assemblies ?? AppDomain.CurrentDomain.GetAssemblies());
instance.Start(); instance.Start();
return instance; return instance;
} }

2
Apewer/IndependentAttribute.cs

@ -13,7 +13,7 @@ namespace Apewer
string _remark = null; string _remark = null;
/// <summary>无依赖特性。</summary> /// <summary>无依赖特性。</summary>
public IndependentAttribute(string remark) => _remark = remark; public IndependentAttribute(string remark = null) => _remark = remark;
/// <summary>备注。</summary> /// <summary>备注。</summary>
public string Remark public string Remark

155
Apewer/Json.cs

@ -9,6 +9,7 @@ using System.Dynamic;
using System.IO; using System.IO;
#endif #endif
using System.Reflection; using System.Reflection;
using System.Runtime.Serialization;
using System.Text; using System.Text;
namespace Apewer 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) internal static void Array(object array, Json json, bool ignoreCase, string ignoreCharacters, bool force)
{ {
if (array == null) return; if (array == null) return;
@ -1524,83 +1544,54 @@ namespace Apewer
if (json.TokenType != JTokenType.Array) return; if (json.TokenType != JTokenType.Array) return;
var type = array.GetType(); var type = array.GetType();
var subtypes = type.GetGenericArguments(); var subtype = null as Type;
if (subtypes.Length < 1) return; if (array is Array)
var subtype = subtypes[0];
var methods = type.GetMethods();
var add = null as MethodInfo;
foreach (var method in methods)
{ {
if (method.Name == "Add") string typeName = array.GetType().FullName.Replace("[]", string.Empty);
{ subtype = array.GetType().Assembly.GetType(typeName);
var parameters = method.GetParameters(); }
if (parameters.Length == 1) else
{ {
if (parameters[0].ParameterType.FullName == subtype.FullName) var subtypes = type.GetGenericArguments();
{ if (subtypes.Length < 1) return;
add = method; subtype = subtypes[0];
break;
}
}
}
} }
if (add == null) return;
var jis = json.GetItems(); var jis = json.GetItems();
foreach (var ji in jis) for (var index = 0; index < jis.Length; index++)
{ {
var parameter = new object[1] { null }; var ji = jis[index];
if (subtype.FullName == typeof(Json).FullName) 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);
parameter[0] = ji; else if (subtype.Equals(typeof(byte))) Add(array, NumberUtility.Byte(ji.Text), index);
add.Invoke(array, parameter); 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 else
{ {
switch (subtype.FullName) var serializable = force ? true : CanSerialize(subtype, false);
if (serializable && (ji is Json))
{ {
case "System.String": switch (ji.TokenType)
parameter[0] = (ji.TokenType == JTokenType.Null) ? null : ji.Text; {
add.Invoke(array, parameter); case JTokenType.Object:
break; var subobject = Activator.CreateInstance(subtype);
case "System.Int32": Object(subobject, ji, ignoreCase, ignoreCharacters, force);
parameter[0] = NumberUtility.Int32(ji.Text); Add(array, subobject, index);
add.Invoke(array, parameter); break;
break; case JTokenType.Array:
case "System.Int64": var subarray = Activator.CreateInstance(subtype);
parameter[0] = NumberUtility.Int64(ji.Text); Array(subarray, ji, ignoreCase, ignoreCharacters, force);
add.Invoke(array, parameter); Add(array, subarray, index);
break; 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;
} }
} }
} }
@ -1615,6 +1606,7 @@ namespace Apewer
var setter = property.GetSetMethod(); var setter = property.GetSetMethod();
if (setter == null) return; if (setter == null) return;
var pt = property.PropertyType;
var ptname = property.PropertyType.FullName; var ptname = property.PropertyType.FullName;
var parameter = new object[1] { null }; var parameter = new object[1] { null };
@ -1665,7 +1657,8 @@ namespace Apewer
setter.Invoke(entity, parameter); setter.Invoke(entity, parameter);
break; break;
default: default:
var serializable = force ? true : CanSerialize(property.PropertyType, false); var serializable = force;
if (!serializable) serializable = CanSerialize(property.PropertyType, false);
if (serializable && (value is Json)) if (serializable && (value is Json))
{ {
switch (((Json)value).TokenType) switch (((Json)value).TokenType)
@ -1677,7 +1670,17 @@ namespace Apewer
setter.Invoke(entity, parameter); setter.Invoke(entity, parameter);
break; break;
case JTokenType.Array: 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); Array(subarray, (Json)value, ignoreCase, ignoreCharacters, force);
parameter[0] = subarray; parameter[0] = subarray;
setter.Invoke(entity, parameter); setter.Invoke(entity, parameter);
@ -1995,6 +1998,14 @@ namespace Apewer
{ {
if (type == null) return false; 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; if (type.Equals(typeof(object))) return false;
var sas = type.GetCustomAttributes(typeof(SerializableAttribute), inherit); var sas = type.GetCustomAttributes(typeof(SerializableAttribute), inherit);
if (sas != null && sas.Length > 0) return true; if (sas != null && sas.Length > 0) return true;

12
Apewer/Logger.cs

@ -114,22 +114,22 @@ namespace Apewer
} }
/// <summary>记录错误。多个 Content 参数将以“ | ”分隔。</summary> /// <summary>记录错误。多个 Content 参数将以“ | ”分隔。</summary>
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);
/// <summary>记录警告。多个 Content 参数将以“ | ”分隔。</summary> /// <summary>记录警告。多个 Content 参数将以“ | ”分隔。</summary>
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);
/// <summary>记录警告。多个 Content 参数将以“ | ”分隔。</summary> /// <summary>记录警告。多个 Content 参数将以“ | ”分隔。</summary>
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);
/// <summary>记录文本。多个 Content 参数将以“ | ”分隔。</summary> /// <summary>记录文本。多个 Content 参数将以“ | ”分隔。</summary>
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);
/// <summary>记录调试。多个 Content 参数将以“ | ”分隔。</summary> /// <summary>记录调试。多个 Content 参数将以“ | ”分隔。</summary>
[Conditional("DEBUG")] [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);
/// <summary>创建新实例。</summary> /// <summary>创建新实例。</summary>
public Logger() public Logger()

20
Apewer/NumberUtility.cs

@ -294,6 +294,26 @@ namespace Apewer
return default(decimal); return default(decimal);
} }
/// <summary>获取布尔对象。</summary>
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;
}
/// <summary>获取单精度浮点对象。</summary> /// <summary>获取单精度浮点对象。</summary>
public static float Float(object number) => GetNumber(number, Convert.ToSingle, (v, d) => v / Convert.ToSingle(d)); public static float Float(object number) => GetNumber(number, Convert.ToSingle, (v, d) => v / Convert.ToSingle(d));

7
Apewer/Source/ColumnAttribute.cs

@ -17,7 +17,7 @@ namespace Apewer.Source
private PropertyInfo _property = null; private PropertyInfo _property = null;
internal string PropertyName = null; internal string PropertyName = null;
private string _field = ""; private string _field = null;
private int _length = 0; private int _length = 0;
private ColumnType _type; private ColumnType _type;
@ -26,8 +26,7 @@ namespace Apewer.Source
private void Init(string field, ColumnType type, int length) private void Init(string field, ColumnType type, int length)
{ {
if (string.IsNullOrEmpty(field)) field = TableStructure.RestrictName(field, string.IsNullOrEmpty(field)); _field = field;
_field = string.IsNullOrEmpty(field) ? "" : TableStructure.RestrictName(field, string.IsNullOrEmpty(field));
_type = type; _type = type;
switch (type) switch (type)
{ {
@ -103,7 +102,7 @@ namespace Apewer.Source
if (setter == null || setter.IsStatic) return null; 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; var pt = property.PropertyType;

339
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
{
/// <summary></summary>
abstract class DbClient
{
/// <summary></summary>
public virtual Logger Logger { get; set; }
#region Connection
DbConnection _conn = null;
string _str = null;
/// <summary></summary>
public Timeout Timeout { get; set; }
/// <summary></summary>
public DbConnection Connection { get => _conn; }
/// <summary></summary>
public bool Online { get => _conn == null ? false : (_conn.State == ConnectionState.Open); }
/// <summary>连接字符串。</summary>
public string ConnectionString { get => _str; }
/// <summary></summary>
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;
}
}
/// <summary></summary>
public void Close()
{
if (_conn != null)
{
if (_transaction != null)
{
if (_autocommit) Commit();
else Rollback();
}
_conn.Close();
_conn.Dispose();
_conn = null;
}
}
/// <summary></summary>
public void Dispose() { Close(); }
#endregion
#region Transaction
private DbTransaction _transaction = null;
private bool _autocommit = false;
/// <summary>启动事务。</summary>
public string Begin(bool commit = true) => Begin(commit, null);
/// <summary>启动事务。</summary>
public string Begin(bool commit, Class<System.Data.IsolationLevel> 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();
}
}
/// <summary>提交事务。</summary>
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();
}
}
/// <summary>从挂起状态回滚事务。</summary>
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
/// <summary>查询。</summary>
public IQuery Query(string sql) => Query(sql, null);
/// <summary>查询。</summary>
public IQuery Query(string sql, IEnumerable<IDataParameter> 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);
}
}
/// <summary>执行。</summary>
public IExecute Execute(string sql) => Execute(sql, null);
/// <summary>执行单条 Transact-SQL 语句,并加入参数。</summary>
public IExecute Execute(string sql, IEnumerable<IDataParameter> 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
/// <summary>查询记录。</summary>
/// <param name="model">记录模型。</param>
/// <param name="sql">SQL 语句。</param>
public Result<object[]> Query(Type model, string sql)
{
if (_conn == null) return new Result<object[]>("连接无效。");
if (model == null) return new Result<object[]>("数据模型类型无效。");
if (string.IsNullOrEmpty(sql)) return new Result<object[]>("SQL 语句无效。");
var query = Query(sql);
var result = null as Result<object[]>;
if (query.Success)
{
try
{
var array = OrmHelper.Fill(query, model);
result = new Result<object[]>(array);
}
catch (Exception ex)
{
result = new Result<object[]>(ex);
}
}
else
{
result = new Result<object[]>(query.Message);
}
query.Dispose();
return result;
}
/// <summary></summary>
public Result<T[]> Query<T>(string sql) where T : class, new()
{
var query = Query(sql);
if (!query.Success) return new Result<T[]>(query.Message);
var records = query.Fill<T>();
query.Dispose();
var result = new Result<T[]>(records);
return result;
}
#endregion
#region Static
/// <summary>对文本转义,符合 SQL 安全性。可根据字段类型限制 UTF-8 字节数,默认为 0 时不限制字节数。</summary>
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
/// <summary></summary>
protected abstract string GetConnectionString();
/// <summary></summary>
protected abstract DbConnection NewConnection();
/// <summary></summary>
protected abstract DbDataAdapter NewDataAdapter(string sql);
/// <summary></summary>
protected abstract DbCommand NewCommand();
}
}

4
Apewer/Source/IDbClientAdo.cs

@ -18,8 +18,8 @@ namespace Apewer.Source
/// <summary>数据库当前在线,表示连接可用。</summary> /// <summary>数据库当前在线,表示连接可用。</summary>
bool Online { get; } bool Online { get; }
/// <summary>连接数据库,若未连接则尝试连接,获取连接成功的状态。</summary> /// <summary>连接数据库,若未连接则尝试连接,返回错误信息。</summary>
bool Connect(); string Connect();
#endregion #endregion

8
Apewer/Source/IDbClientOrm.cs

@ -20,13 +20,15 @@ namespace Apewer.Source
/// <summary>插入记录。</summary> /// <summary>插入记录。</summary>
/// <param name="record">要插入的记录实体。</param> /// <param name="record">要插入的记录实体。</param>
/// <param name="table">插入到指定表。当不指定时,由 record 类型决定。</param>
/// <returns>错误信息。当成功时候返回空字符串。</returns> /// <returns>错误信息。当成功时候返回空字符串。</returns>
public string Insert(object record); public string Insert(object record, string table = null);
/// <summary>更新记录。</summary> /// <summary>更新记录。</summary>
/// <param name="record">要插入的记录实体。</param> /// <param name="record">要插入的记录实体。</param>
/// <param name="table">插入到指定表。当不指定时,由 record 类型决定。</param>
/// <returns>错误信息。当成功时候返回空字符串。</returns> /// <returns>错误信息。当成功时候返回空字符串。</returns>
public string Update(IRecord record); public string Update(IRecord record, string table = null);
/// <summary>获取指定类型的主键,按 Flag 属性筛选。</summary> /// <summary>获取指定类型的主键,按 Flag 属性筛选。</summary>
/// <param name="model">要查询的类型。</param> /// <param name="model">要查询的类型。</param>
@ -41,7 +43,7 @@ namespace Apewer.Source
/// <param name="model">目标记录的类型。</param> /// <param name="model">目标记录的类型。</param>
/// <param name="key">目标记录的主键。</param> /// <param name="key">目标记录的主键。</param>
/// <param name="flag">要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。</param> /// <param name="flag">要求目标记录具有的 Flag 属性,当指定 0 时忽略此要求。</param>
public Result<IRecord> Get(Type model, string key, long flag = 0); public Result<object> Get(Type model, string key, long flag = 0);
/// <summary>获取具有指定 Key 的记录,并要求记录具有指定的 Flag 属性。</summary> /// <summary>获取具有指定 Key 的记录,并要求记录具有指定的 Flag 属性。</summary>
/// <param name="key">目标记录的主键。</param> /// <param name="key">目标记录的主键。</param>

4
Apewer/Source/IRecordStamp.cs

@ -9,10 +9,10 @@ namespace Apewer.Source
public interface IRecordStamp public interface IRecordStamp
{ {
/// <summary>记录的创建时间 UTC 时间戳,值为执行 INSERT INTO 语句时的 UTC 时间戳。</summary> /// <summary>记录的创建时间,默认为本地时间。</summary>
long Created { get; set; } long Created { get; set; }
/// <summary>记录的更新时间 UTC 时间戳,每次对此记录执行 UPDATE 时应更新此值为当前 UTC 时间戳。</summary> /// <summary>记录的更新时间,默认为本地时间。</summary>
long Updated { get; set; } long Updated { get; set; }
} }

76
Apewer/Source/OrmHelper.cs

@ -59,10 +59,10 @@ namespace Apewer.Source
#region IQuery -> IRecord #region IQuery -> IRecord
/// <summary>读取所有行,生成列表。</summary> /// <summary>读取所有行,生成列表。</summary>
public static T[] Fill<T>(IQuery query) where T : class, new() => As<object, T>(Fill(query, typeof(T))); public static T[] Fill<T>(this IQuery query) where T : class, new() => As<object, T>(Fill(query, typeof(T)));
/// <summary>读取所有行填充到 T,组成 T[]。</summary> /// <summary>读取所有行填充到 T,组成 T[]。</summary>
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 (query == null) return new object[0];
if (model == null) return new object[0]; if (model == null) return new object[0];
@ -76,7 +76,7 @@ namespace Apewer.Source
} }
/// <summary>获取指定列的所有值,无效值不加入结果。</summary> /// <summary>获取指定列的所有值,无效值不加入结果。</summary>
public static T[] Column<T>(IQuery query, Func<int, T> filler) public static T[] Column<T>(this IQuery query, Func<int, T> filler)
{ {
if (query == null || filler == null) return new T[0]; if (query == null || filler == null) return new T[0];
@ -105,7 +105,7 @@ namespace Apewer.Source
/// <summary>将 Query 的行,填充到模型实体。</summary> /// <summary>将 Query 的行,填充到模型实体。</summary>
/// <remarks>填充失败时返回 NULL 值。</remarks> /// <remarks>填充失败时返回 NULL 值。</remarks>
/// <exception cref="Exception"></exception> /// <exception cref="Exception"></exception>
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; if (query == null || model == null || structure == null) return null;
@ -210,7 +210,7 @@ namespace Apewer.Source
catch { } catch { }
} }
} }
return record as IRecord; return record;
} }
#endregion #endregion
@ -226,7 +226,7 @@ namespace Apewer.Source
if (database == null) return new Result<object[]>("数据库无效。"); if (database == null) return new Result<object[]>("数据库无效。");
if (model == null) return new Result<object[]>("模型类型无效。"); if (model == null) return new Result<object[]>("模型类型无效。");
if (string.IsNullOrEmpty(sql)) return new Result<object[]>("SQL 语句无效。"); if (string.IsNullOrEmpty(sql)) return new Result<object[]>("SQL 语句无效。");
using (var query = database.Query(sql) as Query) using (var query = database.Query(sql))
{ {
if (query == null) return new Result<object[]>("查询实例无效。"); if (query == null) return new Result<object[]>("查询实例无效。");
if (query.Table == null) if (query.Table == null)
@ -246,11 +246,11 @@ namespace Apewer.Source
} }
} }
/// <summary>查询记录。</summary> // /// <summary>查询记录。</summary>
/// <typeparam name="T">记录模型。</typeparam> // /// <typeparam name="T">记录模型。</typeparam>
/// <param name="database">数据库对象。</param> // /// <param name="database">数据库对象。</param>
/// <param name="sql">SQL 语句。</param> // /// <param name="sql">SQL 语句。</param>
public static Result<T[]> Query<T>(IDbClientAdo database, string sql) where T : class, new() => As<object, T>(Query(database, typeof(T), sql)); // public static Result<T[]> Query<T>(IDbClientAdo database, string sql) where T : class, new() => As<object, T>(Query(database, typeof(T), sql));
/// <summary>查询记录。</summary> /// <summary>查询记录。</summary>
/// <param name="database">数据库对象。</param> /// <param name="database">数据库对象。</param>
@ -282,34 +282,35 @@ namespace Apewer.Source
/// <param name="model">记录模型。</param> /// <param name="model">记录模型。</param>
/// <param name="key">主键。</param> /// <param name="key">主键。</param>
/// <param name="sqlGetter">生成 SQL 语句的函数,传入参数为表名和主键值。</param> /// <param name="sqlGetter">生成 SQL 语句的函数,传入参数为表名和主键值。</param>
public static Result<IRecord> Get(IDbClientAdo database, Type model, string key, Func<string, string, string> sqlGetter) public static Result<object> Get(IDbClientAdo database, Type model, string key, Func<string, string, string> sqlGetter)
{ {
if (sqlGetter == null) return new Result<IRecord>("SQL 语句获取函数无效。"); if (sqlGetter == null) return new Result<object>("SQL 语句获取函数无效。");
var safetyKey = TextUtility.SafeKey(key); var safetyKey = TextUtility.SafeKey(key);
if (string.IsNullOrEmpty(safetyKey)) return new Result<IRecord>("主键无效。"); if (string.IsNullOrEmpty(safetyKey)) return new Result<object>("主键无效。");
var query = null as IQuery; var query = null as IQuery;
var record = null as IRecord; var record = null as object;
try try
{ {
record = Activator.CreateInstance(model);
var ts = TableStructure.Parse(model); var ts = TableStructure.Parse(model);
var tableName = ts.Name; var tableName = ts.Name;
if (string.IsNullOrEmpty(tableName)) return new Result<IRecord>("表名无效。"); if (string.IsNullOrEmpty(tableName)) return new Result<object>("表名无效。");
var sql = sqlGetter(tableName, safetyKey); var sql = sqlGetter(tableName, safetyKey);
query = database.Query(sql); query = database.Query(sql);
if (query.Table == null) return new Result<IRecord>("没有获取到记录。"); if (query.Table == null) return new Result<object>("没有获取到记录。");
record = Row(query, 0, model, ts); record = Row(query, 0, model, ts);
} }
catch (Exception ex) catch (Exception ex)
{ {
RuntimeUtility.Dispose(query); RuntimeUtility.Dispose(query);
return new Result<IRecord>(ex); return new Result<object>(ex);
} }
RuntimeUtility.Dispose(query); RuntimeUtility.Dispose(query);
if (record == null) return new Result<IRecord>("没有获取到记录。"); if (record == null) return new Result<object>("没有获取到记录。");
return new Result<IRecord>(record); return new Result<object>(record);
} }
/// <summary>获取具有指定主键的记录。</summary> /// <summary>获取具有指定主键的记录。</summary>
@ -317,7 +318,7 @@ namespace Apewer.Source
/// <param name="database">数据库对象。</param> /// <param name="database">数据库对象。</param>
/// <param name="key">主键。</param> /// <param name="key">主键。</param>
/// <param name="sqlGetter">生成 SQL 语句的函数,传入参数为表名和主键值。</param> /// <param name="sqlGetter">生成 SQL 语句的函数,传入参数为表名和主键值。</param>
public static Result<T> Get<T>(IDbClientAdo database, string key, Func<string, string, string> sqlGetter) where T : class, IRecord, new() => As<IRecord, T>(Get(database, typeof(T), key, sqlGetter)); public static Result<T> Get<T>(IDbClientAdo database, string key, Func<string, string, string> sqlGetter) where T : class, IRecord, new() => As<object, T>(Get(database, typeof(T), key, sqlGetter));
/// <summary>获取主键。</summary> /// <summary>获取主键。</summary>
/// <param name="database">数据库对象。</param> /// <param name="database">数据库对象。</param>
@ -398,20 +399,22 @@ namespace Apewer.Source
{ {
if (record == null) return; 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) if (record is IRecordMoment moment)
{ {
var now = ClockUtility.LucidNow; if (string.IsNullOrEmpty(moment.Created)) moment.Created = now.Lucid();
if (string.IsNullOrEmpty(moment.Created)) moment.Created = now; if (string.IsNullOrEmpty(moment.Updated)) moment.Updated = now.Lucid();
if (string.IsNullOrEmpty(moment.Updated)) moment.Updated = now;
} }
if (record is IRecordStamp stamp) if (record is IRecordStamp stamp)
{ {
var utc = ClockUtility.UtcStamp; var utc = ClockUtility.UtcStamp;
if (stamp.Created == 0L) stamp.Created = utc; if (stamp.Created == 0L) stamp.Created = now.Stamp();
if (stamp.Updated == 0L) stamp.Updated = utc; if (stamp.Updated == 0L) stamp.Updated = now.Stamp();
} }
} }
@ -420,22 +423,19 @@ namespace Apewer.Source
public static bool SetUpdated(object record) public static bool SetUpdated(object record)
{ {
if (record == null) return false; if (record == null) return false;
var now = DateTime.Now;
var setted = false;
if (record is IRecordMoment moment) if (record is IRecordMoment moment)
{ {
var now = ClockUtility.LucidNow; moment.Updated = now.Lucid();
moment.Updated = now; setted = true;
return true;
} }
if (record is IRecordStamp stamp) if (record is IRecordStamp stamp)
{ {
var utc = ClockUtility.UtcStamp; stamp.Updated = now.Stamp();
stamp.Updated = utc; setted = true;
return true;
} }
return setted;
return false;
} }
#endregion #endregion

12
Apewer/Source/Record.cs

@ -14,15 +14,19 @@ namespace Apewer.Source
public abstract class Record : IRecord public abstract class Record : IRecord
{ {
const int KeyLength = 191; const int KeyLength = 32;
private string _key = null; private string _key = null;
private long _flag = 0; private long _flag = 0;
/// <summary>记录主键,一般使用 GUID 的字符串形式。</summary> /// <summary>记录主键,一般使用 GUID 的字符串形式。</summary>
/// <remarks>带有 Independent 特性的模型不包含此属性。</remarks> /// <remarks>
/// <para>注:</para>
/// <para>1. 默认长度为 32,需要修改长度时应该重写此属性;</para>
/// <para>2. 带有 Independent 特性的模型不包含此属性。</para>
/// </remarks>
[Column("_key", ColumnType.NVarChar, KeyLength)] [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); } }
/// <summary>记录的标记,Int64 类型,区分记录的状态。</summary> /// <summary>记录的标记,Int64 类型,区分记录的状态。</summary>
/// <remarks>带有 Independent 特性的模型不包含此属性。</remarks> /// <remarks>带有 Independent 特性的模型不包含此属性。</remarks>
@ -30,7 +34,7 @@ namespace Apewer.Source
public long Flag { get { return _flag; } set { _flag = value; } } public long Flag { get { return _flag; } set { _flag = value; } }
/// <summary>重置 Key 属性的值。</summary> /// <summary>重置 Key 属性的值。</summary>
public virtual void ResetKey() => _key = TextUtility.Key(); public virtual void ResetKey() => Key = TextUtility.Key();
/// <summary></summary> /// <summary></summary>
public Record() => ResetKey(); public Record() => ResetKey();

12
Apewer/Source/TableAttribute.cs

@ -16,14 +16,14 @@ namespace Apewer.Source
public sealed class TableAttribute : Attribute public sealed class TableAttribute : Attribute
{ {
private string _name; private string _name = null;
private string _store; private string _store = null;
/// <summary>标记表属性。</summary> /// <summary>标记表属性。</summary>
public TableAttribute(string name = null, string store = null) public TableAttribute(string name = null, string store = null)
{ {
_name = TableStructure.RestrictName(name, string.IsNullOrEmpty(name)); _name = name;
_store = string.IsNullOrEmpty(store) ? null : TableStructure.RestrictName(store, false); _store = store;
} }
/// <summary>表名。</summary> /// <summary>表名。</summary>
@ -44,7 +44,7 @@ namespace Apewer.Source
private static Dictionary<string, TableAttribute> _tac = new Dictionary<string, TableAttribute>(); private static Dictionary<string, TableAttribute> _tac = new Dictionary<string, TableAttribute>();
/// <summary>解析表特性,默认使用缓存以提升性能。</summary> /// <summary>解析表特性,默认使用缓存以提升性能。</summary>
public static TableAttribute Parse<T>(bool useCache = true) where T : IRecord => Parse(typeof(T), useCache); public static TableAttribute Parse<T>(bool useCache = true) where T: class, new() => Parse(typeof(T), useCache);
/// <summary>解析表特性,默认使用缓存以提升性能。</summary> /// <summary>解析表特性,默认使用缓存以提升性能。</summary>
public static TableAttribute Parse(Type type, bool useCache = true) public static TableAttribute Parse(Type type, bool useCache = true)
@ -68,7 +68,7 @@ namespace Apewer.Source
if (tas.LongLength < 1L) return null; if (tas.LongLength < 1L) return null;
var ta = (TableAttribute)tas[0]; 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<IndependentAttribute>(type, true); ta.Independent = RuntimeUtility.Contains<IndependentAttribute>(type, true);
if (useCache) if (useCache)

43
Apewer/Source/TableStructure.cs

@ -49,6 +49,9 @@ namespace Apewer.Source
/// <summary>主键。</summary> /// <summary>主键。</summary>
public ColumnAttribute Key { get => _key; } public ColumnAttribute Key { get => _key; }
/// <summary>记录标记。</summary>
public ColumnAttribute Flag { get => _flag; }
/// <summary>列信息。</summary> /// <summary>列信息。</summary>
public ColumnAttribute[] Columns { get => _columns; } public ColumnAttribute[] Columns { get => _columns; }
@ -140,25 +143,33 @@ namespace Apewer.Source
#region TableAttribute #region TableAttribute
/// <summary>限定表名称/列名称。</summary> // 限定表名称/列名称。
/// <param name="name">名称。</param> static string RestrictName(string name, bool underline = false, bool english = false)
/// <param name="startWithUnderline">名称以下划线开始。</param>
internal static string RestrictName(string name, bool startWithUnderline)
{ {
if (name == null || name == Constant.EmptyString) return Constant.EmptyString; if (string.IsNullOrEmpty(name)) return null;
var lower = name.ToLower(); var str = name;
var available = TextUtility.Merge("_", Constant.NumberCollection, Constant.LowerCollection);
var sb = new StringBuilder(); // 限定名称仅使用英文和数字。
foreach (var c in lower) if (english)
{
const string available = "_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
var chars = new ArrayBuilder<char>();
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); return str;
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;
} }
static IDataParameter CreateParameter(object record, ColumnAttribute ca, Func<Parameter, IDataParameter> callback) static IDataParameter CreateParameter(object record, ColumnAttribute ca, Func<Parameter, IDataParameter> callback)

18
Apewer/SystemUtility.cs

@ -22,6 +22,24 @@ namespace Apewer
#endif #endif
/// <summary></summary>
public static void SetConsoleCtrlCancel(Func<bool> 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();
};
}
} }
} }

9
Apewer/TextUtility.cs

@ -644,19 +644,22 @@ namespace Apewer
/// <summary>返回此字符串的安全键副本,只保留数据记录主键中可能出现的字符,默认限制长度为 255 字符。</summary> /// <summary>返回此字符串的安全键副本,只保留数据记录主键中可能出现的字符,默认限制长度为 255 字符。</summary>
public static string SafeKey(string text, int maxLength = 255) public static string SafeKey(string text, int maxLength = 255)
{ {
if (string.IsNullOrEmpty(text)) return Constant.EmptyString; if (string.IsNullOrEmpty(text)) return Empty;
var input = Lower(text); var input = text;
var max = maxLength; var max = maxLength;
if (max < 1 || max > input.Length) max = input.Length; if (max < 1 || max > input.Length) max = input.Length;
// 允许用于主键值的字符。
const string KeyCollection = "-_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
var sb = new StringBuilder(); var sb = new StringBuilder();
var total = input.Length; var total = input.Length;
var length = 0; var length = 0;
for (var i = 0; i < total; i++) for (var i = 0; i < total; i++)
{ {
var c = input[i]; var c = input[i];
if (Constant.KeyCollection.IndexOf(c) < 0) continue; if (KeyCollection.IndexOf(c) < 0) continue;
sb.Append(c); sb.Append(c);
length += 1; length += 1;
if (length >= max) break; if (length >= max) break;

83
Apewer/Web/ApiOptions.cs

@ -10,14 +10,14 @@ namespace Apewer.Web
public class ApiOptions public class ApiOptions
{ {
/// <summary>限制最大请求的字节数。</summary>
/// <remarks>默认值:-1,不使用 ApiOptions 限制。</remarks>
public long MaxRequestBody { get; set; } = -1;
/// <summary>设置 Access-Control-Max-Age 的值。</summary> /// <summary>设置 Access-Control-Max-Age 的值。</summary>
/// <remarks>默认值:60。</remarks> /// <remarks>默认值:60。</remarks>
public int AccessControlMaxAge { get; set; } = 60; public int AccessControlMaxAge { get; set; } = 60;
/// <summary>允许枚举输出 Applications 或 Functions。</summary>
/// <remarks>默认值:不允许,不输出列表。</remarks>
public bool AllowEnumerate { get; set; } = false;
/// <summary>允许解析 favicon.ico 请求。</summary> /// <summary>允许解析 favicon.ico 请求。</summary>
/// <remarks>默认值:不允许,响应空。</remarks> /// <remarks>默认值:不允许,响应空。</remarks>
public bool AllowFavIcon { get; set; } = false; public bool AllowFavIcon { get; set; } = false;
@ -26,18 +26,42 @@ namespace Apewer.Web
/// <remarks>默认值:不允许,拒绝搜索引擎收录根目录。</remarks> /// <remarks>默认值:不允许,拒绝搜索引擎收录根目录。</remarks>
public bool AllowRobots { get; set; } = false; public bool AllowRobots { get; set; } = false;
/// <summary>允许枚举输出 Applications 或 Functions。</summary> // /// <summary>允许同步 IO。</summary>
/// <remarks>默认值:不允许,不输出列表。</remarks> // /// <remarks>
public bool AllowEnumerate { get; set; } = false; // /// <para>默认值:允许。</para>
// /// <para>允许:使用同步方法写入 Response.Body,可能会导致线程不足而崩溃。</para>
// /// <para>不允许:必须用异步方法写入 Response.Body。</para>
// /// </remarks>
// public bool AllowSynchronousIO { get; set; } = true;
/// <summary>允许输出的 Json 对象缩进。</summary> /// <summary>允许输出的 Json 对象缩进。</summary>
/// <remarks>默认值:不缩进。</remarks> /// <remarks>默认值:不缩进。</remarks>
public bool JsonIndent { get; set; } = false; public bool JsonIndent { get; set; } = false;
/// <summary>限制最大请求的字节数。</summary>
/// <remarks>默认值:-1,不使用 ApiOptions 限制。</remarks>
public long MaxRequestBody { get; set; } = -1;
/// <summary>在响应头中设置 Content-Security-Policy,要求浏览器升级资源链接,使用 HTTPS。</summary> /// <summary>在响应头中设置 Content-Security-Policy,要求浏览器升级资源链接,使用 HTTPS。</summary>
/// <remarks>默认值:不要求。在 HTTPS 页面中,不自动升级 HTTP 资源。</remarks> /// <remarks>默认值:不要求。在 HTTPS 页面中,不自动升级 HTTP 资源。</remarks>
public bool UpgradeHttps { get; set; } = false; public bool UpgradeHttps { get; set; } = false;
/// <summary>在响应中包含 Access-Control 属性。</summary>
/// <remarks>默认值:包含。</remarks>
public bool WithAccessControl { get; set; } = true;
/// <summary>在响应中包含时间属性。</summary>
/// <remarks>默认值:不包含。</remarks>
public bool WithClock { get; set; } = false;
/// <summary>允许响应标头中包含 X-Content-Type-Options: nosiff。</summary>
/// <remarks>默认值:不包含。当设置默认控制器时自动启用此属性。</remarks>
public bool WithContentTypeOptions { get; set; } = false;
/// <summary>在响应中包含执行 API 的持续时间。</summary>
/// <remarks>默认值:不包含。</remarks>
public bool WithDuration { get; set; } = false;
/// <summary>允许响应中包含 Exception 对象的属性。</summary> /// <summary>允许响应中包含 Exception 对象的属性。</summary>
/// <remarks>默认值:不允许。</remarks> /// <remarks>默认值:不允许。</remarks>
public bool WithException { get; set; } = false; public bool WithException { get; set; } = false;
@ -50,37 +74,13 @@ namespace Apewer.Web
/// <remarks>默认值:不包含。</remarks> /// <remarks>默认值:不包含。</remarks>
public bool WithParameters { get; set; } = false; public bool WithParameters { get; set; } = false;
/// <summary>允许输出 Application 列表时包含类型名称。</summary>
/// <remarks>默认值:不包含。</remarks>
public bool WithTypeName { get; set; } = false;
/// <summary>在响应中包含时间属性。</summary>
/// <remarks>默认值:不包含。</remarks>
public bool WithClock { get; set; } = false;
/// <summary>在响应中包含执行 API 的持续时间。</summary>
/// <remarks>默认值:不包含。</remarks>
public bool WithDuration { get; set; } = false;
/// <summary>在响应中包含 Application 和 Function 属性。</summary> /// <summary>在响应中包含 Application 和 Function 属性。</summary>
/// <remarks>默认值:不包含。</remarks> /// <remarks>默认值:不包含。</remarks>
public bool WithTarget { get; set; } = false; public bool WithTarget { get; set; } = false;
/// <summary>在响应中包含 Access-Control 属性。</summary> /// <summary>允许输出 Application 列表时包含类型名称。</summary>
/// <remarks>默认值:包含。</remarks> /// <remarks>默认值:不包含。</remarks>
public bool WithAccessControl { get; set; } = true; public bool WithTypeName { get; set; } = false;
/// <summary>允许响应标头中包含 X-Content-Type-Options: nosiff。</summary>
/// <remarks>默认值:不包含。当设置默认控制器时自动启用此属性。</remarks>
public bool WithContentTypeOptions { get; set; } = false;
/// <summary>允许同步 IO。</summary>
/// <remarks>
/// <para>默认值:允许。</para>
/// <para>允许:使用同步方法写入 Response.Body,可能会导致线程不足而崩溃。</para>
/// <para>不允许:必须用异步方法写入 Response.Body。</para>
/// </remarks>
public bool AllowSynchronousIO { get; set; } = true;
#region 默认控制器,可用于静态控制器。 #region 默认控制器,可用于静态控制器。
@ -98,15 +98,22 @@ namespace Apewer.Web
#endregion #endregion
/// <summary>创建默认选项。</summary> /// <summary>创建默认选项。</summary>
public ApiOptions() /// <remarks>
{ /// 在 Debug 模式中默认设置以下选项
Debug(); /// <br />JsonIndent = TRUE
} /// <br />WithException = TRUE
/// <br />WithDuration = TRUE
/// <br />WithParameters = TRUE
/// </remarks>
public ApiOptions() => Debug();
[Conditional("DEBUG")] [Conditional("DEBUG")]
void Debug() void Debug()
{ {
JsonIndent = true;
WithException = true; WithException = true;
WithDuration = true;
WithParameters = true;
} }
} }

6
Apewer/Web/ApiProvider.cs

@ -16,13 +16,13 @@ namespace Apewer.Web
#region Implement #region Implement
/// <summary>调用前的检查,可返回错误信息。</summary> /// <summary>调用前的检查,可返回错误信息以终止调用。</summary>
public virtual string PreInvoke() { return null; } public virtual string PreInvoke() { return null; }
/// <summary>读取请求前的检查,可返回错误信息。</summary> /// <summary>读取请求前的检查,可返回错误信息以忽略 POST 内容。</summary>
public virtual string PreRead() { return null; } public virtual string PreRead() { return null; }
/// <summary>写入响应前的检查,可返回错误信息。</summary> /// <summary>写入响应前的检查,可返回错误信息以终止输出。</summary>
public virtual string PreWrite() { return null; } public virtual string PreWrite() { return null; }
/// <summary>结束本次请求和响应。</summary> /// <summary>结束本次请求和响应。</summary>

2
Apewer/_Common.props

@ -14,7 +14,7 @@
<!-- 程序集信息 --> <!-- 程序集信息 -->
<PropertyGroup> <PropertyGroup>
<Product>Apewer Libraries</Product> <Product>Apewer Libraries</Product>
<Version>6.4.2</Version> <Version>6.5.0</Version>
</PropertyGroup> </PropertyGroup>
<!-- NuGet --> <!-- NuGet -->

27
Apewer/_Extensions.cs

@ -12,7 +12,7 @@ using System.Reflection;
using System.Text; using System.Text;
/// <summary>扩展方法。</summary> /// <summary>扩展方法。</summary>
public static class Extensions public static class Extensions_Apewer
{ {
#region Class Utility #region Class Utility
@ -77,22 +77,25 @@ public static class Extensions
#region String、StringBuilder #region String、StringBuilder
/// <summary>获取 Byte 对象。</summary> /// <summary>转换为 Boolean 值。</summary>
public static bool Boolean(this object @this) => NumberUtility.Boolean(@this);
/// <summary>转换为 Byte 值。</summary>
public static byte Byte(this object @this) => NumberUtility.Byte(@this); public static byte Byte(this object @this) => NumberUtility.Byte(@this);
/// <summary>获取 Int32 对象。</summary> /// <summary>转换为 Int32 值。</summary>
public static int Int32(this object @this) => NumberUtility.Int32(@this); public static int Int32(this object @this) => NumberUtility.Int32(@this);
/// <summary>获取 Int64 对象。</summary> /// <summary>转换为 Int64 值。</summary>
public static long Int64(this object @this) => NumberUtility.Int64(@this); public static long Int64(this object @this) => NumberUtility.Int64(@this);
/// <summary>获取 Decimal 对象。</summary> /// <summary>转换为 Decimal 值。</summary>
public static decimal Decimal(this object @this) => NumberUtility.Decimal(@this); public static decimal Decimal(this object @this) => NumberUtility.Decimal(@this);
/// <summary>获取单精度浮点对象。</summary> /// <summary>转换为单精度浮点值。</summary>
public static float Float(this object @this) => NumberUtility.Float(@this); public static float Float(this object @this) => NumberUtility.Float(@this);
/// <summary>获取双精度浮点对象。</summary> /// <summary>转换为双精度浮点值。</summary>
public static double Double(this object @this) => NumberUtility.Double(@this); public static double Double(this object @this) => NumberUtility.Double(@this);
/// <summary>将文本转换为字节数组,默认使用 UTF-8。</summary> /// <summary>将文本转换为字节数组,默认使用 UTF-8。</summary>
@ -288,20 +291,20 @@ public static class Extensions
public static void Exception(this Logger logger, object sender, Exception exception) => logger?.InnerException(sender, exception); public static void Exception(this Logger logger, object sender, Exception exception) => logger?.InnerException(sender, exception);
/// <summary>记录错误。多个 Content 参数将以“ | ”分隔。</summary> /// <summary>记录错误。多个 Content 参数将以“ | ”分隔。</summary>
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);
/// <summary>记录警告。多个 Content 参数将以“ | ”分隔。</summary> /// <summary>记录警告。多个 Content 参数将以“ | ”分隔。</summary>
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);
/// <summary>记录警告。多个 Content 参数将以“ | ”分隔。</summary> /// <summary>记录警告。多个 Content 参数将以“ | ”分隔。</summary>
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);
/// <summary>记录文本。多个 Content 参数将以“ | ”分隔。</summary> /// <summary>记录文本。多个 Content 参数将以“ | ”分隔。</summary>
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);
/// <summary>记录调试。多个 Content 参数将以“ | ”分隔。</summary> /// <summary>记录调试。多个 Content 参数将以“ | ”分隔。</summary>
[Conditional("DEBUG")] [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 #endregion

15
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 ### 6.4.2
- ArrayBuilder:修正 512 长度无法扩展的问题。 - ArrayBuilder:修正 512 长度无法扩展的问题。

Loading…
Cancel
Save