betterlife

开发环境vs2017,数据写入到mongodb。思路就是1.提供接口写入日志,2.基于接口封装类库。

1.为什么要写它

很多开源项目像nlog、log4net、elk、exceptionless等都挺好的。就是除了引入所需类库,还要在项目中添加配置,不喜欢。elk在分布式海量数据收集和检索方面可能更能发挥它的优势,单纯记日志也可以,exceptionless就是基于elk的。就想着写一个简单易用的、可以发邮件报警的,直接引入类库就能用的一个记日志工具,所有的配置信息和入库都交给web api。这是当时问的问题,https://q.cnblogs.com/q/109489/。干脆就实现了先

接下里的代码可能有很多可以优化的地方,如果有些地方觉得不妥或者可以用更好的方式实现或组织代码,请告诉说,我改。另外实现完的接口没有加访问限制,先默认内网使用,当然有热心网友给出实现的话就更好了,像ip限制或者签名等等,接下来是web api的实现

2.实现Web Api

2.1 新建.net core web api项目 【LogWebApi】

因为要发邮件和写入mongodb,先改配置文件appsettings.json

{
  "ConnectionStrings": {
    "ConnectionString": "mongodb://yourmongoserver",
    "Database": "logdb",
    "LogCollection": "logdata"
  },
  "AllowedHosts": "*",
  "AppSettings": {
    "SendMailInfo": {
      "SMTPServerName": "smtp.qiye.163.com",
      "SendEmailAdress": "发送人邮箱",
      "SendEmailPwd": "",
      "SiteName": "邮件主题",
      "SendEmailPort": "123"
    }
  }
}

2.2 实现写入mongodb

总体是参考这篇文章的实现 https://www.qappdesign.com/using-mongodb-with-net-core-webapi/

  • 实现依赖注入获取配置文件信息

创建目录结构如下图

AppSettings类

public class AppSettings
    {
        public SendMailInfo SendMailInfo { get; set; }
    }
    public class SendMailInfo
    {
        public string SMTPServerName { get; set; }
        public string SendEmailAdress { get; set; }
        public string SendEmailPwd { get; set; }
        public string SiteName { get; set; }
        public string SendEmailPort { get; set; }
    }
View Code

DBSettings类

    /// <summary>
    /// 数据库配置信息
    /// </summary>
    public class DBSettings
    {
        /// <summary>
        /// mongodb connectionstring
        /// </summary>
        public string ConnectionString { get; set; }
        /// <summary>
        /// mongodb database
        /// </summary>
        public string Database { get; set; }
        /// <summary>
        /// 日志collection
        /// </summary>
        public string LogCollection { get; set; }
    }
View Code

 接下来Here is how we modify Startup.cs to inject Settings in the Options accessor model:

public void ConfigureServices(IServiceCollection services)
        {            
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

            services.Configure<DBSettings>(Configuration.GetSection("ConnectionStrings"));//数据库连接信息
            services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));//其他配置信息            

        }
View Code

在项目中将通过IOptions 接口来获取配置信息,后面看代码吧

IOptions<AppSettings>
IOptions<DBSettings>
View Code

配置文件信息获取算是准备完了

  • 创建日志信息Model

在Model文件夹下创建类LogEventData,也就是存到mongodb的信息

public class LogEventData
    {
        [BsonId]
        public ObjectId Id { get; set; }
        /// <summary>
        /// 时间
        /// </summary>
        [BsonDateTimeOptions(Representation = BsonType.DateTime, Kind = DateTimeKind.Local)]
        public DateTime Date { get; set; }
        /// <summary>
        /// 错误级别
        /// </summary>
        public string Level { get; set; }
        /// <summary>
        /// 日志来源
        /// </summary>
        public string LogSource { get; set; }
        /// <summary>
        /// 日志信息
        /// </summary>
        public string Message { get; set; }
        /// <summary>
        /// 类名
        /// </summary>
        public string ClassName { get; set; }
        /// <summary>
        /// 方法名
        /// </summary>
        public string MethodName { get; set; }
        /// <summary>
        /// 完整信息
        /// </summary>
        public string FullInfo { get; set; }
        /// <summary>
        /// 行号
        /// </summary>        
        public string LineNumber { get; set; }
        /// <summary>
        /// 文件名
        /// </summary>        
        public string FileName { get; set; }
        /// <summary>
        /// ip
        /// </summary>
        public string IP { get; set; }
        /// <summary>
        /// 是否发送邮件,不为空则发送邮件,多个接收人用英文逗号隔开
        /// </summary>
        [JsonIgnore]
        public string Emails { get; set; }

        public override string ToString()
        {
            return JsonConvert.SerializeObject(this);
        }
    }
View Code
  • 定义database Context

站点根目录新建文件夹Context和类,别忘了引用 MongoDB.Driver  nuget包

public class MongoContext
    {
        private readonly IMongoDatabase _database = null;
        private readonly string _logCollection;
        public MongoContext(IOptions<DBSettings> settings)
        {
            var client = new MongoClient(settings.Value.ConnectionString);
            if (client != null)
                _database = client.GetDatabase(settings.Value.Database);
            _logCollection = settings.Value.LogCollection;
        }

        public IMongoCollection<LogEventData> LogEventDatas
        {
            get
            {
                return _database.GetCollection<LogEventData>(_logCollection);
            }
        }
    }
View Code
  • 添加Repository

别纠结为什么叫这个名了,就是数据访问类,像是常用的DAL,创建目录如下,之后可以通过依赖注入来访问具体实现

IRepository类

public interface IRepository<T> where T:class
    {
        Task<IEnumerable<T>> GetAll();
        Task<T> Get(string id);
        Task Add(T item);
        Task<bool> Remove(string id);
        Task<bool> Update(string id, string body);
    }
View Code

LogRepository类

public class LogRepository : IRepository<LogEventData>
    {
        private readonly MongoContext _context = null;
        public LogRepository(IOptions<DBSettings> settings)
        {
            _context = new MongoContext(settings);
        }


        public async Task Add(LogEventData item)
        {
            await _context.LogEventDatas.InsertOneAsync(item);
        }
        public async Task<IEnumerable<LogEventData>> GetList(QueryLogModel model)
        {
            var builder = Builders<LogEventData>.Filter;
            FilterDefinition<LogEventData> filter = builder.Empty;
            if (!string.IsNullOrEmpty(model.Level))
            {
                filter = builder.Eq("Level", model.Level);
            }
            if (!string.IsNullOrEmpty(model.LogSource))
            {
                filter = filter & builder.Eq("LogSource", model.LogSource);
            }
            if (!string.IsNullOrEmpty(model.Message))
            {
                filter = filter & builder.Regex("Message", new BsonRegularExpression(new Regex(model.Message)));
            }
            if (DateTime.MinValue != model.StartTime)
            {
                filter = filter & builder.Gte("Date", model.StartTime);
            }
            if(DateTime.MinValue != model.EndTime)
            {
                filter = filter & builder.Lte("Date", model.EndTime);
            }
            return await _context.LogEventDatas.Find(filter)
                 .SortByDescending(log => log.Date)
                 .Skip((model.PageIndex - 1) * model.PageSize)
                 .Limit(model.PageSize).ToListAsync();
        }
        #region 未实现方法
        public async Task<LogEventData> Get(string id)
        {
            throw new NotImplementedException();
        }

        public async Task<IEnumerable<LogEventData>> GetAll()
        {
            throw new NotImplementedException();
        }

        public Task<bool> Remove(string id)
        {
            throw new NotImplementedException();
        }

        public Task<bool> Update(string id, string body)
        {
            throw new NotImplementedException();
        } 
        #endregion
    }
View Code

 为了通过DI model来访问LogRepository,修改Startup.cs ,ConfigureServices添加如下代码

services.AddTransient<IRepository<LogEventData>, LogRepository>();//数据访问

 到这基本的数据写入和查询算是写完了,下面来实现Controller

  • 创建LogController

[Route("api/[controller]")]
    [ApiController]
    public class LogController : ControllerBase
    {
        private readonly LogRepository _logRepository;
        IOptions<AppSettings> _appsettings;        
        public LogController(IRepository<LogEventData> logRepository,IOptions<AppSettings> appsettings)
        {
            _logRepository = (LogRepository)logRepository;
            _appsettings = appsettings;
        }

        [Route("trace")]
        [HttpPost]
        public void Trace([FromBody] LogEventData value)
        {
            Add(value);
        }
        [Route("debug")]
        [HttpPost]
        public void Debug([FromBody] LogEventData value)
        {
            Add(value);

        }
        [Route("info")]
        [HttpPost]
        public void Info([FromBody] LogEventData value)
        {
            Add(value);
        }
        [Route("warn")]
        [HttpPost]
        public void Warn([FromBody] LogEventData value)
        {
            Add(value);
        }
        [Route("error")]
        [HttpPost]
        public void Error([FromBody] LogEventData value)
        {
            Add(value);
        }
        [Route("fatal")]
        [HttpPost]
        public void Fatal([FromBody] LogEventData value)
        {
            Add(value);
        }
        private async void Add(LogEventData data)
        {
            if (data != null)
            {
                await _logRepository.Add(data);
                if (!string.IsNullOrEmpty(data.Emails))
                {
                    new EmailHelpers(_appsettings).SendMailAsync(data.Emails, "监测邮件", data.ToString());
                }
            }
        }

        [HttpGet("getlist")]
        public async Task<ResponseModel<IEnumerable<LogEventData>>> GetList([FromQuery] QueryLogModel model)
        {
            ResponseModel<IEnumerable<LogEventData>> resp = new ResponseModel<IEnumerable<LogEventData>>();
            resp.Data = await _logRepository.GetList(model);
            return resp;
        }
    }
View Code

控制器里整个逻辑很简单,除了向外提供不同日志级别的写入接口,也实现了日志查询接口给日志查看站点用,基本上够用了。到这编译的话会报错,有一些类还没加上,像发送邮件的类,稍后加上。在Add方法内部,用到了new EmailHelpers。讲道理按.net core 对依赖注入的使用 ,这个 new是不应该出现在这的,就先这么着吧,下面补类:

先创建Model文件夹下的两个类,很简单就不解释了

QueryLogModel类

public class QueryLogModel
    {
        private int _pageindex = 1;
        private int _pagesize = 20;
        public int PageIndex
        {
            get { return _pageindex; }
            set { _pageindex = value; }
        }
        public int PageSize
        {
            get { return _pagesize; }
            set { _pagesize = value; }
        }
        public string Level { get; set; }
        public string LogSource { get; set; }
        public string Message { get; set; }
        public DateTime StartTime { get; set; }
        public DateTime EndTime { get; set; }
    }
View Code

ResponseModel类

public class ResponseModel<T>
    {
        private HttpStatusCode _resultCode = HttpStatusCode.OK;
        private string _message = "请求成功";        
        private T _data = default(T);
        /// <summary>
        /// 返回码
        /// </summary>
        public HttpStatusCode ResultCode
        {
            get { return this._resultCode; }
            set { this._resultCode = value; }
        }
        /// <summary>
        /// 结果说明
        /// </summary>
        public string Message
        {
            get { return this._message; }
            set { this._message = value; }
        }        
        /// <summary>
        /// 返回的数据
        /// </summary>
        public T Data
        {
            get { return this._data; }
            set { this._data = value; }
        }
    }
View Code

 创建EmailHelpers类

public class EmailHelpers
    {
        private SendMailInfo _mailinfo;
        
        public EmailHelpers(IOptions<AppSettings> appsettings)
        {
            _mailinfo = appsettings.Value.SendMailInfo;
        }
        /// <summary>
        /// 异步发送邮件
        /// </summary>
        /// <param name="emails">email地址</param>
        /// <param name="subject">邮件标题</param>
        /// <param name="content">邮件内容</param>
        public void SendMailAsync(string emails, string subject, string content)
        {
            Task.Factory.StartNew(() =>
            {
                SendEmail(emails, subject, content);
            });
        }
        /// <summary>
        /// 邮件发送方法
        /// </summary>
        /// <param name="emails">email地址</param>
        /// <param name="subject">邮件标题</param>
        /// <param name="content">邮件内容</param>
        /// <returns></returns>
        public void SendEmail(string emails, string subject, string content)
        {
            string[] emailArray = emails.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
            string fromSMTP = _mailinfo.SMTPServerName;        //邮件服务器
            string fromEmail = _mailinfo.SendEmailAdress;      //发送方邮件地址
            string fromEmailPwd = _mailinfo.SendEmailPwd;//发送方邮件地址密码
            string fromEmailName = _mailinfo.SiteName;   //发送方称呼
            try
            {
                //新建一个MailMessage对象
                MailMessage aMessage = new MailMessage();
                aMessage.From = new MailAddress(fromEmail, fromEmailName);
                foreach (var item in emailArray)
                {
                    aMessage.To.Add(item);
                }
                aMessage.Subject = subject;
                aMessage.Body = content;
                System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
                aMessage.BodyEncoding = Encoding.GetEncoding("utf-8");
                aMessage.IsBodyHtml = true;
                aMessage.Priority = MailPriority.High;                
                aMessage.ReplyToList.Add(new MailAddress(fromEmail, fromEmailName));
                SmtpClient smtp = new SmtpClient();

                smtp.Host = fromSMTP;
                smtp.Timeout = 20000;
                smtp.UseDefaultCredentials = false;
                smtp.EnableSsl = true;
                smtp.DeliveryMethod = SmtpDeliveryMethod.Network;
                smtp.Credentials = new NetworkCredential(fromEmail, fromEmailPwd); //发邮件的EMIAL和密码
                smtp.Port = int.Parse(_mailinfo.SendEmailPort);                
                smtp.Send(aMessage);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }
View Code

到这接口基本上就可以用了。

但是再加三个东西

  • 2.2.6 扩展

 添加全局异常捕获服务

ExceptionMiddlewareExtensions类

/// <summary>
    /// 全局异常处理中间件
    /// </summary>
    public static class ExceptionMiddlewareExtensions
    {
        public static void ConfigureExceptionHandler(this IApplicationBuilder app, IOptions<DBSettings> settings)
        {
            LogRepository _repository = new LogRepository(settings);
            app.UseExceptionHandler(appError =>
            {
                appError.Run(async context =>
                {
                    context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                    context.Response.ContentType = "application/json";

                    var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
                    if (contextFeature != null)
                    {
                        await _repository.Add(new LogEventData
                        {
                            Message= contextFeature.Error.ToString(),
                            Date=DateTime.Now,
                            Level="Fatal",
                            LogSource= "LogWebApi"
                        }); 
                        await context.Response.WriteAsync(context.Response.StatusCode + "-Internal Server Error.");
                    }
                });
            });
        }
    }
View Code

修改Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env,IOptions<DBSettings> settings)
        {            
            app.ConfigureExceptionHandler(settings);
        }
View Code

添加MessagePack扩展

messagepack可以让我们在post数据的时候序列化数据,“压缩”数据传输大小,这个会结合针对接口封装的类库配合使用。

引用nuget: WebApiContrib.Core.Formatter.MessagePack

在ConfigureServices添加代码

services.AddMvcCore().AddMessagePackFormatters();
services.AddMvc().AddMessagePackFormatters();

扩展了media type,用以支持"application/x-msgpack", "application/msgpack",在接下来封装的类库中会使用"application/x-msgpack",在web api来引入这个东西就是为了能解析从客户端传过来的数据

添加Swagger支持

引用nuget:Swashbuckle.AspNetCore

修改ConfigureServices

services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" });
            });

修改Configure

// Enable middleware to serve generated Swagger as a JSON endpoint.
            app.UseSwagger();

            // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), 
            // specifying the Swagger JSON endpoint.
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
                c.RoutePrefix = string.Empty;//在应用的根 (http://localhost:<port>/) 处提供 Swagger UI
            });
View Code

 

到这整个web api站点算是写完了,编译不出错就ok了。思路上应该很简单清晰了吧

 

接下来会写针对这个web api的类库实现,其实一开始是先写的类库,然后针对类库写的这个接口。。。

相关文章: