基於.NetCore開發部落格專案 StarBlog

2022-07-27 06:01:31

系列文章

前言

最近好幾天都忙著寫程式碼,沒更新文章,近期給部落格增加了一些功能,本文一次性介紹~

新增的功能如下:

  • 系統監控: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的方式管理設定,程式碼就不放了~

初始化

在我的設計中,這個功能是依賴於設定管理的

所以把設定管理做完之後,我的初始化頁面也做出來了

看起來是這樣的

也就是首次執行本專案的時候,會進入這個頁面,目前的初始化設定就只有建立管理、設定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頁面,這個就比較簡單,程式碼不放了