.Net 带你从0到1使用C#新建框架【保姆级【一

2023年 9月 16日 96.5k 0

本文将使用C#从0到1带你新建一个简单的框架

注:本文不是最好的框架,只是用于给新手学习的简单框架,当然,小型项目用起来也没有问题。

首先,我们需要以下软件:

  • Visual Studio 2022(本文将使用.Net 6,需要至少为Visual Studio 2022)
  • SQLserver 2008 R2及以上
  • Redis或Redis for Windows
  • Navicat或SQL Server Management Studio(SSMS)
  • 选择性使用:PDManer(PDMan数据库建模 (pdmaner.com))
  • 其次,我们将使用以下开源组件

  • Furion【依赖极少的开源组件,已经帮你完成了绝大部分的帮助类,包括:日志、Swagger,JWT鉴权等】(让 .NET 开发更简单,更通用,更流行。 Furion | Furion (baiqian.ltd))
  • Sqlsugar【国产超级ORM,比EFCore更快,支持所有主流数据库和绝大部分国产数据库,支持百万数据写入和亿级查询,支持自动按照租户分库,支持自动分表分库】(SqlSugar .Net ORM 5.X 官网 、文档、教程 - SqlSugar 5x - .NET果糖网 (donet5.com))
  • CSRedis【由FreeSQL作者开发的开源免费的Redis库,内置了常用的Redis帮助方法】(github.com/2881099/csr…
  • LazyCaptcha【一个开源的图片验证库,支持多种图片验证码】 (LazyCaptcha: 仿EasyCaptcha的.net core 下的图形验证码 (gitee.com))
  • IP2Region.Net【开源IP地址库,可以快速查询IP地址所属地】(ip2region/binding/csharp at master · lionsoul2014/ip2region (github.com))
  • UAParser【开源UA信息读取库,解析当前用户所使用的浏览器、设备等信息】(github.com/ua-parser/u…
  • Yitter.IdGenerator【雪花Id生成库,可以快速生成唯一数字Id】(yitter/IdGenerator: 💎多语言实现,高性能生成唯一数字ID。 💎优化的雪花算法(SnowFlake)——雪花漂移算法,在缩短ID长度的同时,具备极高瞬时并发处理能力(50W/0.1s)。 💎原生支持 C#/Java/Go/Rust/C/JavaScript/TypeScript/Python/Pascal 多语言,提供其它适用于其它语言的多线程安全调用动态库(FFI)。💎支持容器环境自动扩容(自动注册 WorkerId ),单机或分布式唯一IdGenerator。💎顶尖优化,超强效能。 (github.com))
  • 选择性使用:Aliyun.OSS.NetCore或Tencent.QCloud.Cos.Sdk【阿里云或腾讯云对象存储SDK】
  • 本文默认你已经学会了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;
        };
    });//日志格式化
    

    效果如图:

    image.png

    //日志分级写入文件
    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日志写入本地文件
    });//日志分级并根据日期写入文件
    

    效果如图:

    image.png

    【选用】将日志写入数据库

    //日志写入数据库
    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⊙)…木有图,自己想象下,就普通的表】

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论