5

基于.NetCore开发博客项目 StarBlog - (16) 一些新功能 (监控/统计/配置/初始化) - 程...

 2 years ago
source link: https://www.cnblogs.com/deali/p/16523157.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

基于.NetCore开发博客项目 StarBlog

最近好几天都忙着写代码,没更新文章,近期给博客增加了一些功能,本文一次性介绍~

新增的功能如下:

  • 系统监控:sentry、exceptionless、CLRStats

PS:其实前面两篇关于日志收集工具介绍的文章也是为了给本文做铺垫

系统监控这块包括日志收集、性能监测和系统状态监测。

日志收集和性能监测我交给了ExceptionLess和Sentry,这俩开源的工具可以很好的完成这些工作,详情可以看我之前写的这俩篇文章:

然后状态监测我是基于GitHub上的一个开源项目来魔改的:CLRStats

这个组件可以实时查看CPU、GC、线程的状态,不过原版的实现是作为一个中间件嵌入AspNetCore项目,并且访问的地址只能用Basic认证,不适用。

于是我把代码clone下来之后魔改了一下,变成一个可以调用的服务,并且重新写了API接口,终于方便起来了~

这个接口拿到的数据是这样的,后续在管理后台里面做成可视化图表也比较方便。

{
  "server": {
    "machineName": "machineName",
    "systemDateTime": "7/26/2022 11:30:22 PM"
  },
  "application": {
    "cpu": {
      "usagePercent": 0
    },
    "gc": {
      "gen0CollectCount": 24,
      "gen1CollectCount": 23,
      "gen2CollectCount": 22,
      "heapMemory": 38328872,
      "heapMemoryFormat": "36 M",
      "isServerGC": true
    },
    "thread": {
      "availableCompletionPortThreads": 1000,
      "availableWorkerThreads": 32766,
      "usedCompletionPortThreads": 0,
      "usedWorkerThreads": 1,
      "usedThreadCount": 29,
      "maxCompletionPortThreads": 1000,
      "maxWorkerThreads": 32767
    }
  }
}

具体代码就不复制粘贴了,我把它放在StarBlog.Contrib项目中,作为一个独立的组件方便调用。

虽然前面这篇文章有介绍访问统计的实现:基于.NetCore开发博客项目 StarBlog - (11) 实现访问统计

不过只是单纯讲了通过中间件实现访问记录,这些数据存在数据库之后并没有被利用起来

现在就实现了一些简单的统计,目前主要实现了总览数据(总访问、今日、昨日访问)、趋势数据指定日期统计,这几个功能。

逻辑代码在StarBlog.Web/Services/VisitRecordService.cs

总览数据代码在这

PS:我发现FreeSQL的ISelect对象在链式操作时候的行为很奇怪,我知道是懒加载,但是已经执行了.Count()似乎还没执行,下一行调用的代码甚至会把前面的筛选条件加上,无奈我之内在方法内又写了一个嵌套的方法……

也就是这个GetQuerySet,这点让我这种用习惯DjangoORM的人觉得很不适应 = =..

这个接口计算总访问量、今日访问量、昨日访问量,方便做对比(受知乎的创作者中心启发)

public object Overview() {
  ISelect<VisitRecord> GetQuerySet() => _repo.Where(a => !a.RequestPath.StartsWith("/Api"));

  return new {
    TotalVisit = GetQuerySet().Count(),
    TodayVisit = GetQuerySet().Where(a => a.Time.Date == DateTime.Today).Count(),
    YesterdayVisit = GetQuerySet().Where(a => a.Time.Date == DateTime.Today.AddDays(-2).Date).Count(),
  };
}

也就是统计最近n天的访问量

PS:C#的日期处理还是比较舒服的

public object Trend(int days = 7) {
  return _repo.Where(a => !a.RequestPath.StartsWith("/Api"))
    .Where(a => a.Time.Date > DateTime.Today.AddDays(-days).Date)
    .GroupBy(a => a.Time.Date)
    .ToList(a => new {
      time = a.Key,
      date = $"{a.Key.Month}-{a.Key.Day}",
      count = a.Count()
    });
}

按日期统计

这个简单粗暴不用多说

public object Stats(DateTime date) {
  var data = _repo.Where(a => a.Time.Date == date.Date && !a.RequestPath.StartsWith("/Api"));
  return new { Count = data.Count() };
}

注意这里面所有的统计我都过滤了以/Api开头的地址,因为我只需要统计博客前台的访问量就行了。

博客还是有很多需要配置的东西,比如说Host

之前我是写在appsettings.json文件里的,按理说也可以,修改这文件之后也能hot reload,不过问题是没法实现在管理后台中修改并保存

所以我打算实现一个配置管理的功能

一开始是把目光瞄准了KV数据库,甚至要求找一个嵌入式的、C#实现的开源项目,叠了这么多buff,果然没找到合适的

(不过前几天好像看到有个大佬发了篇文章介绍用C#手写一个KV数据库的,大赞!)

于是还是用博客本身的数据来实现好了,也不难

老规矩,继续写Service:StarBlog.Web/Services/ConfigService.cs

这里只把关键代码放出来,完整代码可以看GitHub

public class ConfigService {
    private readonly IConfiguration _conf;
    private readonly IBaseRepository<ConfigItem> _repo;

    public ConfigItem? GetByKey(string key) {
        var item = _repo.Where(a => a.Key == key).First();
        if (item == null) {
            // 尝试读取初始化配置
            var section = _conf.GetSection($"StarBlog:Initial:{key}");
            if (!section.Exists()) return null;
            item = new ConfigItem { Key = key, Value = section.Value, Description = "Initial" };
            item = AddOrUpdate(item);
        }

        return item;
    }

    public ConfigItem AddOrUpdate(ConfigItem item) {
        return _repo.InsertOrUpdate(item);
    }

    public int? Update(string key, string value, string? description = default) {
        var item = GetByKey(key);
        if (item == null) return null;

        item.Value = value;
        if (description != null) item.Description = description;
        return _repo.Update(item);
    }

    public string this[string key] {
        get {
            var item = GetByKey(key);
            return item == null ? "" : item.Value;
        }
        set {
            var item = GetByKey(key) ?? new ConfigItem { Key = key };
            item.Value = value;
            AddOrUpdate(item);
        }
    }
}

这个ConfigService实现了索引器,可以比较方便的实现配置的读取和保存

var conf = xxx; // 注入 ConfigService
// 读取配置
Console.WriteLine(conf["host"]);
// 修改配置
conf["host"] = "http://dealiaxy.com";

同时我也写了几个接口,可以通过HTTP的方式管理配置,代码就不放了~

在我的设计中,这个功能是依赖于配置管理的

所以把配置管理做完之后,我的初始化页面也做出来了

看起来是这样的

866942-20220726235656839-1255977080.png

也就是首次运行本项目的时候,会进入这个页面,目前的初始化配置就只有创建管理、设置Host两个,后续应该会慢慢增加其他的

后台是通过is_init这个字段来判断是否有初始化的

直接上Controller代码

[HttpGet]
public IActionResult Init([FromServices] ConfigService conf) {
    if (conf["is_init"] == "true") {
        _messages.Error("已经完成初始化!");
        return RedirectToAction(nameof(Index));
    }

    return View(new InitViewModel {
        Host = conf["host"]
    });
}

[HttpPost]
public IActionResult Init([FromServices] ConfigService conf, [FromServices] IBaseRepository<User> userRepo, InitViewModel vm) {
    if (!ModelState.IsValid) return View();

    // 保存配置
    conf["host"] = vm.Host;
    conf["is_init"] = "true";

    // 创建用户
    // todo 这里暂时存储明文密码,后期要换成MD5加密存储
    userRepo.Insert(new User {
        Id = Guid.NewGuid().ToString(),
        Name = vm.Username,
        Password = vm.Password
    });

    _messages.Success("初始化完成!");
    return RedirectToAction(nameof(Index));
}

同时还要实现一个View页面,这个就比较简单,代码不放了

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK