本文将使用C#从0到1带你新建一个简单的框架
注:本文不是最好的框架,只是用于给新手学习的简单框架,当然,小型项目用起来也没有问题。
首先,我们需要以下软件:
其次,我们将使用以下开源组件
本文默认你已经学会了C#和Visual Studio的基础操作,如果你不会,建议先保存本文,然后先去学一下基础知识。
建立项目
名称 | 作用 | 引用层 |
---|---|---|
YourProject.Core | 此层为各种帮助方法 | YourProject.Globals |
YourProject.Globals | 此层为全局使用的一些静态配置等 | 无 |
YourProject.Entities | 此层为实体和传输对象 | YourProject.Core,YourProject.Globals |
YourProject.Service | 此层为服务所在层 | YourProject.Core,YourProject.Globals,YourProject.Entities |
YourProject.Web.Core | 此层用于API | YourProject.Globals,YourProject.Web.Core,YourProject.Entities,YourProject.Service |
YourProject.Web | 此层仅用于启动和前端页面 | YourProject.Globals,YourProject.Web.Core |
YourProject.CodeFirst | 【选用】此层仅用于使用CodeFirst时自动向数据库注入基础数据 | YourProject.Globals,YourProject.Web.Core |
开始项目
我们需要在YourProject.Web.Core新建类:Startup.cs
///
/// 注册启动服务
///
[AppStartup(1)]//《==此处为Furion中的启动配置,用于在配置启动时需要的注册
public class StartUp : AppStartup
{
public void ConfigureService(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
}
}
其次在YourProject.Web的Program.cs中,将所有代码注释后,写入:
Serve.Run();
接下来,我们需要配置SQLSugar,因为接下来的日志组件需要使用:
///
/// Sqlsugar配置
///
///
public class SugarDbContext where T : class, new()
{
protected static readonly SqlSugarScope DbInstance = new(new ConnectionConfig()
{
DbType = DbType.SqlServer,//你的数据库类型
ConnectionString = App.Configuration["ConnectionStrings:DefaultConnectionString"],//连接字符串
IsAutoCloseConnection = true,
InitKeyType = InitKeyType.Attribute,
LanguageType = LanguageType.Default,
ConfigureExternalServices = new ConfigureExternalServices()//其他配置
{
EntityNameService = (type, entity) =>//表配置
{
entity.IsDisabledUpdateAll = true;//禁用全表升级
entity.IsDisabledDelete = true;//禁用删除
}
}
},
db =>
{
db.CodeFirst.SetStringDefaultLength(255).InitTables();//设置代码优先,并配置默认字段长度255,并初始化
db.Ado.CommandTimeOut = 120;//执行超时时间
db.Aop.OnLogExecuting = (sql, pars) =>//在SQL执行前执行
{
if (App.Configuration["AppSettings:IsDev"].ToBool())
{
var sqlString = WriteSQL(sql, pars);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(sqlString);
sqlString.LogDebug();
Console.ForegroundColor = ConsoleColor.White;
}
};
db.Aop.OnError = (execption) =>//SQL执行出错
{
string errorInfo = $"SQL执行出错!【SQL语句】:{execption.Sql}rn【消息内容】:{execption.Message}";
errorInfo.LogError();
};
db.Aop.OnExecutingChangeSql = (sql, pars) => //可以修改SQL和参数的值
{
return new KeyValuePair(sql, pars);
};
db.Aop.DataExecuting = (oldValue, entityInfo) =>//执行期间自动执行
{
//添加时自动添加创建时间
if (entityInfo.PropertyName == "CreateTime" && entityInfo.OperationType == DataFilterType.InsertByObject)
{
entityInfo.SetValue(DateTime.Now);
}
//修改时自动添加修改时间
if (entityInfo.PropertyName == "UpdateTime" && entityInfo.OperationType == DataFilterType.UpdateByObject)
{
entityInfo.SetValue(DateTime.Now);
}
};
db.Aop.OnDiffLogEvent = it =>//差异日志
{
var log = "差异日志:";
log += "BeforeData:" + it.BeforeData + "rn";//操作前的值(如果是Insert操作则为NULL),
log += "AfterData" + it.AfterData + "rn"; //操作后的值,包含:字段描述 列名 值 表名 表描述
log += "SQL:" + it.Sql + "rn"; //错误SQL
log += "Parameters:" + it.Parameters + "rn";//参数
log += "Data:" + it.BusinessData + "rn"; //传入的值
log += "ExecTime:" + it.Time + "rn"; //执行时间
log += "DiffType:" + it.DiffType + "rn"; //操作类型 Enum类型,有Insert,Update
log.LogInformation();
};
db.Aop.OnLogExecuted = (sql, pars) =>//执行完成后的操作
{
int sqlExecTimeout = App.Configuration["ConnectionStrings:MaxExecTime"].ToInt32(); //在配置文件中设置最大等待SQL执行时间
sqlExecTimeout = sqlExecTimeout > 0 ? sqlExecTimeout : 1;//赋值默认值
double i = db.Ado.SqlExecutionTime.TotalMilliseconds;
if (i > sqlExecTimeout * 1000)//如果SQL执行缓慢,则写入日志
{
string sqlString = WriteSQL(sql, pars);
Console.ForegroundColor = ConsoleColor.Yellow;
("SQL语句:" + sqlString + "执行缓慢,用时" + i + "毫秒,请排查").LogWarning();
Console.ForegroundColor = ConsoleColor.White;
}
};
});
///
/// 输出赋值后的SQL
///
///
///
///
private static string WriteSQL(string sql, SugarParameter[] pars)
{
//查看赋值后的SQL语句,仅供测试时使用
StringBuilder sb_sql = new(sql);
var tempOrderPars = pars.Where(p => p.Value != null).OrderByDescending(p => p.ParameterName.Length).ToList();//防止 @par1错误替换@par12
for (var index = 0; index < tempOrderPars.Count; index++)
{
sb_sql.Replace(tempOrderPars[index].ParameterName, "'" + tempOrderPars[index].Value.ToString() + "'");
}
return sb_sql.ToString();
}
}
其次,我们需要日志组件,使用.Net自带的Log,配合Furion进行配置:
【选用】格式化控制台输出的内容
//添加控制台格式化输出
services.AddConsoleFormatter(options =>
{
options.DateFormat = "yyyy-MM-dd HH:mm:ss.fffffff zzz dddd";//格式化输出日期
options.ColorBehavior = LoggerColorBehavior.Enabled;//启用颜色美化
options.WithTraceId = true;//输出TraceId
options.WithStackFrame = true;//输出日志
options.WriteHandler = (logMsg, scopeProvider, writer, fmtMsg, opt) =>//自定义日志输出程序
{
writer.WriteLine(fmtMsg);
};
options.MessageFormat = (logMsg) =>
{
if (logMsg.LogLevel == LogLevel.Warning)
{
Console.ForegroundColor = ConsoleColor.Yellow;
}
else if (logMsg.LogLevel == LogLevel.Error)
{
Console.ForegroundColor = ConsoleColor.Red;
}
else if (logMsg.LogLevel == LogLevel.Critical)
{
Console.ForegroundColor = ConsoleColor.DarkRed;
}
else
{
Console.ForegroundColor = ConsoleColor.Green;
}
var template = TP.Wrapper("日志内容", $"日志等级:{logMsg.LogLevel}",
$"##【时间】## {logMsg.LogDateTime:yyyy-MM-dd HH:mm:ss:fffff}",
$"##【名称】## {logMsg.LogName}",
$"##【线程】## {logMsg.EventId}",
$"##【消息】## {logMsg.Message}",
$"##【错误内容】## {logMsg.Exception?.ToString()}");
return template;
};
});//日志格式化
效果如图:
//日志分级写入文件
Array.ForEach(new[] { LogLevel.Debug, LogLevel.Information, LogLevel.Warning, LogLevel.Error, LogLevel.Critical }, logLevel =>
{
services.AddFileLogging(Environment.CurrentDirectory + @"LogsDebugworkstation-" + DateTime.Now.ToString("yyyy-MM-dd") + ".log", options =>
{
options.WriteFilter = (logMsg) =>
{
return logMsg.LogLevel == LogLevel.Debug;
};
options.FileNameRule = fileName =>
{
return string.Format(fileName, DateTime.Now);
};
options.MessageFormat = (logMsg) =>
{
StringBuilder log = new();
log.Append("【时间】:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffff") + "rn");
log.Append("【等级】:" + logMsg.LogLevel.ToString() + "rn");
log.Append("【名称】:" + logMsg.LogName + "rn");
log.Append("【线程】" + logMsg.EventId.Id + "rn");
log.Append("【消息】:" + logMsg.Message + "rn");
log.Append("【错误内容】:" + logMsg.Exception?.ToString() + "rn" + "rn");
return log.ToString();
};
options.DateFormat = "yyyy-MM-dd HH:mm:ss.fffff";
options.HandleWriteError = (writeError) =>
{
writeError.UseRollbackFileName(Path.GetFileNameWithoutExtension(writeError.CurrentFileName) + "-oops" + Path.GetExtension(writeError.CurrentFileName));
};
options.FileSizeLimitBytes = 500 * 1024;
options.MaxRollingFiles = 30;
});//Debug日志写入本地文件
services.AddFileLogging(Environment.CurrentDirectory + @"LogsInformationworkstation-" + DateTime.Now.ToString("yyyy-MM-dd") + ".log", options =>
{
options.WriteFilter = (logMsg) =>
{
return logMsg.LogLevel == LogLevel.Information;
};
options.FileNameRule = fileName =>
{
return string.Format(fileName, DateTime.Now);
};
options.MessageFormat = (logMsg) =>
{
StringBuilder log = new();
log.Append("【时间】:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffff") + "rn");
log.Append("【等级】:" + logMsg.LogLevel.ToString() + "rn");
log.Append("【名称】:" + logMsg.LogName + "rn");
log.Append("【线程】" + logMsg.EventId.Id + "rn");
log.Append("【消息】:" + logMsg.Message + "rn");
log.Append("【错误内容】:" + logMsg.Exception?.ToString() + "rn" + "rn");
return log.ToString();
};
options.DateFormat = "yyyy-MM-dd HH:mm:ss.fffff";
options.HandleWriteError = (writeError) =>
{
writeError.UseRollbackFileName(Path.GetFileNameWithoutExtension(writeError.CurrentFileName) + "-oops" + Path.GetExtension(writeError.CurrentFileName));
};
options.FileSizeLimitBytes = 500 * 1024;
options.MaxRollingFiles = 30;
});//information日志写入本地文件
services.AddFileLogging(Environment.CurrentDirectory + @"LogsWarningworkstation-" + DateTime.Now.ToString("yyyy-MM-dd") + ".log", options =>
{
options.WriteFilter = (logMsg) =>
{
return logMsg.LogLevel == LogLevel.Warning;
};
options.FileNameRule = fileName =>
{
return string.Format(fileName, DateTime.Now);
};
options.MessageFormat = (logMsg) =>
{
StringBuilder log = new();
log.Append("【时间】:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffff") + "rn");
log.Append("【等级】:" + logMsg.LogLevel.ToString() + "rn");
log.Append("【名称】:" + logMsg.LogName + "rn");
log.Append("【线程】" + logMsg.EventId.Id + "rn");
log.Append("【消息】:" + logMsg.Message + "rn");
log.Append("【错误内容】:" + logMsg.Exception?.ToString() + "rn" + "rn");
return log.ToString();
};
options.DateFormat = "yyyy-MM-dd HH:mm:ss.fffff";
options.HandleWriteError = (writeError) =>
{
writeError.UseRollbackFileName(Path.GetFileNameWithoutExtension(writeError.CurrentFileName) + "-oops" + Path.GetExtension(writeError.CurrentFileName));
};
options.FileSizeLimitBytes = 500 * 1024;
options.MaxRollingFiles = 30;
});//warning日志写入本地文件
services.AddFileLogging(Environment.CurrentDirectory + @"LogsErrorworkstation-" + DateTime.Now.ToString("yyyy-MM-dd") + ".log", options =>
{
options.WriteFilter = (logMsg) =>
{
return logMsg.LogLevel == LogLevel.Error;
};
options.FileNameRule = fileName =>
{
return string.Format(fileName, DateTime.Now);
};
options.MessageFormat = (logMsg) =>
{
StringBuilder log = new();
log.Append("【时间】:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffff") + "rn");
log.Append("【等级】:" + logMsg.LogLevel.ToString() + "rn");
log.Append("【名称】:" + logMsg.LogName + "rn");
log.Append("【线程】" + logMsg.EventId.Id + "rn");
log.Append("【消息】:" + logMsg.Message + "rn");
log.Append("【错误内容】:" + logMsg.Exception?.ToString() + "rn" + "rn");
return log.ToString();
};
options.DateFormat = "yyyy-MM-dd HH:mm:ss.fffff";
options.HandleWriteError = (writeError) =>
{
writeError.UseRollbackFileName(Path.GetFileNameWithoutExtension(writeError.CurrentFileName) + "-oops" + Path.GetExtension(writeError.CurrentFileName));
};
options.FileSizeLimitBytes = 500 * 1024;
options.MaxRollingFiles = 30;
});//error日志写入本地文件
services.AddFileLogging(Environment.CurrentDirectory + @"LogsCriticalworkstation-" + DateTime.Now.ToString("yyyy-MM-dd") + ".log", options =>
{
options.WriteFilter = (logMsg) =>
{
return logMsg.LogLevel == LogLevel.Critical;
};
options.FileNameRule = fileName =>
{
return string.Format(fileName, DateTime.Now);
};
options.MessageFormat = (logMsg) =>
{
StringBuilder log = new();
log.Append("【时间】:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffff") + "rn");
log.Append("【等级】:" + logMsg.LogLevel.ToString() + "rn");
log.Append("【名称】:" + logMsg.LogName + "rn");
log.Append("【线程】" + logMsg.EventId.Id + "rn");
log.Append("【消息】:" + logMsg.Message + "rn");
log.Append("【错误内容】:" + logMsg.Exception?.ToString() + "rn" + "rn");
return log.ToString();
};
options.DateFormat = "yyyy-MM-dd HH:mm:ss.fffff";
options.HandleWriteError = (writeError) =>
{
writeError.UseRollbackFileName(Path.GetFileNameWithoutExtension(writeError.CurrentFileName) + "-oops" + Path.GetExtension(writeError.CurrentFileName));
};
options.FileSizeLimitBytes = 500 * 1024;
options.MaxRollingFiles = 30;
});//critical日志写入本地文件
});//日志分级并根据日期写入文件
效果如图:
【选用】将日志写入数据库
//日志写入数据库
services.AddDatabaseLogging(options =>
{
options.MinimumLevel = LogLevel.Warning;
options.IgnoreReferenceLoop = true;
});//日志写入数据库
我们需要在YourProject.Core中新建Logger文件夹
新建类:WriteLogToDB,代码如下:
///
/// 将日志写入数据库
///
public class WriteLogToDB : SugarDbContext, IDatabaseLoggingWriter
{
public void Write(LogMessage logMsg, bool flush)
{
Log_LogRecord_Entity log = new()
{
LogName = logMsg.LogName,
LogLevel = (int)logMsg.LogLevel,
EventId = logMsg.EventId.ToString(),
Message = logMsg.Message,
Exception = logMsg.Exception.ToString(),
State = logMsg.State.ToString(),
LogDateTime = logMsg.LogDateTime,
ThreadId = logMsg.ThreadId,
Context = JsonHelper.Serialize(logMsg.Context),
};
DbInstance.Insertable(log).ExecuteCommand();
}
}
新建类:Log_LogRecord_Entity【用于向数据库记录】
using SqlSugar;
/*
* @author : xkdong@163.com
* @date : 2023-4-26
* @desc : 日志记录表
*/
namespace Mofang.Workstation.Core
{
///
/// 日志记录表
///
[SugarTable("Log_LogRecord", TableDescription = "日志记录表")]
public class Log_LogRecord_Entity
{
///
/// 编号
///
[SugarColumn(IsIdentity = true, IsPrimaryKey = true, ColumnDescription = "编号")]
public int Id { get; set; }
///
/// 日志名称
///
[SugarColumn(ColumnDescription = "日志名称")]
public string LogName { get; set; }
///
/// 日志等级
///
[SugarColumn(ColumnDescription = "日志等级")]
public int LogLevel { get; set; }
///
/// 事件Id
///
[SugarColumn(ColumnDescription = "事件Id")]
public string EventId { get; set; }
///
/// 消息内容
///
[SugarColumn(ColumnDescription = "消息内容", ColumnDataType = "TEXT")]
public string Message { get; set; }
///
/// 错误内容
///
[SugarColumn(ColumnDescription = "错误内容", ColumnDataType = "TEXT")]
public string Exception { get; set; }
///
/// 状态
///
[SugarColumn(ColumnDescription = "状态")]
public string State { get; set; }
///
/// 日志时间
///
[SugarColumn(ColumnDescription = "日志时间")]
public DateTime LogDateTime { get; set; }
///
/// 线程Id
///
[SugarColumn(ColumnDescription = "线程Id")]
public int ThreadId { get; set; }
///
/// 日志上下文
///
[SugarColumn(ColumnDescription = "日志上下文", ColumnDataType = "TEXT")]
public string Context { get; set; }
}
}
效果如图:
【(⊙o⊙)…木有图,自己想象下,就普通的表】