建一家網(wǎng)站多少錢濰坊網(wǎng)站建設(shè)方案咨詢
這篇文章是《.NET 8 實(shí)戰(zhàn)–孢子記賬–從單體到微服務(wù)》系列專欄的《單體應(yīng)用》專欄的最后一片和開發(fā)有關(guān)的文章。在這片文章中我們一起來實(shí)現(xiàn)一個(gè)數(shù)據(jù)統(tǒng)計(jì)的功能:報(bào)表數(shù)據(jù)匯總。這個(gè)功能為用戶查看月度、年度、季度報(bào)表提供數(shù)據(jù)支持。
一、需求
數(shù)據(jù)統(tǒng)計(jì)方面,我們應(yīng)該考慮一個(gè)問題:用戶是否需要看到實(shí)時(shí)數(shù)據(jù)。一般來說個(gè)人記賬軟件的數(shù)據(jù)統(tǒng)計(jì)是不需要實(shí)時(shí)的,因此我們可以將數(shù)據(jù)統(tǒng)計(jì)時(shí)間設(shè)置為每天統(tǒng)計(jì)或者每天每月統(tǒng)計(jì),這樣我們不僅可以減少統(tǒng)計(jì)數(shù)據(jù)時(shí)受到正在寫入的數(shù)據(jù)的影響,也能提升用戶體驗(yàn)。在數(shù)據(jù)更新方面,我們要在每次新增、刪除、更新幾張記錄時(shí)進(jìn)行更新統(tǒng)計(jì)報(bào)表。整理后的需求如下:
編號 | 需求 | 說明 |
---|---|---|
1 | 統(tǒng)計(jì)支出報(bào)表 | 1. 每月、每季度、每年定時(shí)統(tǒng)計(jì)支出數(shù)據(jù) |
2 | 報(bào)表更新 | 1. 新增、刪除、更新支出記錄時(shí)更新報(bào)表數(shù)據(jù); 2. 如果報(bào)表數(shù)據(jù)不存在則不進(jìn)行任何處理 |
二、功能編寫
根據(jù)前面的需求,我們分別實(shí)現(xiàn)這兩個(gè)功能。
1. 支出數(shù)據(jù)統(tǒng)計(jì)
因?yàn)閿?shù)據(jù)每天都定時(shí)更新,因此我們要?jiǎng)?chuàng)建一個(gè)定時(shí)器來實(shí)現(xiàn)這個(gè)功能,定時(shí)器我們依然使用Quartz
來實(shí)現(xiàn)(以月度報(bào)表定時(shí)器為例)。我們在Task\Timer
文件夾下新建ReportMonthTimer
類來實(shí)現(xiàn)定時(shí)器。代碼如下:
using Quartz;
using SporeAccounting.Models;
using SporeAccounting.Server.Interface;namespace SporeAccounting.Task.Timer;/// <summary>
/// 月度報(bào)表定時(shí)器
/// </summary>
public class ReportMonthTimer : IJob
{private readonly IServiceScopeFactory _serviceScopeFactory;/// <summary>/// 構(gòu)造函數(shù)/// </summary>/// <param name="serviceScopeFactory"></param>public ReportMonthTimer(IServiceScopeFactory serviceScopeFactory){_serviceScopeFactory = serviceScopeFactory;}/// <summary>/// 執(zhí)行/// </summary>/// <param name="context"></param>/// <returns></returns>public System.Threading.Tasks.Task Execute(IJobExecutionContext context){using var scope = _serviceScopeFactory.CreateScope();// 獲取每個(gè)用戶最近一次報(bào)表記錄日期var reportServer = scope.ServiceProvider.GetRequiredService<IReportServer>();var incomeExpenditureRecordServer = scope.ServiceProvider.GetRequiredService<IIncomeExpenditureRecordServer>();var reportLogServer = scope.ServiceProvider.GetRequiredService<IReportLogServer>();var reportLogs = reportLogServer.Query();var reportLogDic = reportLogs.GroupBy(x => x.UserId).ToDictionary(x => x.Key,x => x.Max(x => x.CreateDateTime));// 查詢上次日期以后的記賬記錄List<Report> dbReports = new();List<ReportLog> dbReportLogs = new();foreach (var log in reportLogDic){var incomeExpenditureRecords = incomeExpenditureRecordServer.QueryByUserId(log.Key);incomeExpenditureRecords = incomeExpenditureRecords.Where(x => x.RecordDate > log.Value).Where(p => p.IncomeExpenditureClassification.Type == IncomeExpenditureTypeEnmu.Income).ToList();// 生成報(bào)表// 按照月度創(chuàng)建報(bào)表數(shù)據(jù),根據(jù)支出類型統(tǒng)計(jì)var monthlyReports = incomeExpenditureRecords.GroupBy(x => new { x.RecordDate.Year, x.RecordDate.Month }).Select(g => new Report{Year = g.Key.Year,Month = g.Key.Month,Name = $"{g.Key.Year}年{g.Key.Month}月報(bào)表",Type = ReportTypeEnum.Month,Amount = g.Sum(x => x.AfterAmount),UserId = log.Key,ClassificationId = g.First().IncomeExpenditureClassificationId,CreateDateTime = DateTime.Now,CreateUserId = log.Key}).ToList();dbReports.AddRange(monthlyReports);// 記錄日志var reportLogEntries = dbReports.Select(report => new ReportLog{UserId = report.UserId,ReportId = report.Id,CreateDateTime = DateTime.Now,CreateUserId = report.UserId}).ToList();dbReportLogs.AddRange(reportLogEntries);// 保存報(bào)表和日志到數(shù)據(jù)庫reportServer.Add(dbReports);reportLogServer.Add(dbReportLogs);}return System.Threading.Tasks.Task.CompletedTask;}
}
這段代碼實(shí)現(xiàn)了一個(gè)定時(shí)任務(wù)類ReportMonthTimer
,用于生成和記錄用戶的月度報(bào)表。它實(shí)現(xiàn)了Quartz
庫中的IJob
接口,定期執(zhí)行Execute
方法。首先,構(gòu)造函數(shù)接收IServiceScopeFactory
,用于創(chuàng)建服務(wù)作用域,確保每次任務(wù)執(zhí)行時(shí)獲得正確的服務(wù)實(shí)例。然后,Execute
方法通過依賴注入獲取IReportServer
、IIncomeExpenditureRecordServer
和IReportLogServer
,分別用于處理報(bào)表生成、收入支出記錄和報(bào)表日志。
代碼從reportLogServer
查詢所有報(bào)表日志,并根據(jù)每個(gè)用戶的最新報(bào)表日期篩選出新的收入支出記錄。接著,通過GroupBy
按年和月對收入支出記錄進(jìn)行分組,生成月度報(bào)表,并將報(bào)表數(shù)據(jù)保存到dbReports
中。同時(shí),為每份報(bào)表創(chuàng)建日志記錄,并保存到dbReportLogs
中。最后,報(bào)表和日志通過reportServer.Add()
和reportLogServer.Add()
方法存儲到數(shù)據(jù)庫。
Tip:這段代碼中涉及到了一個(gè)新表報(bào)表日志,這個(gè)用于記錄報(bào)表數(shù)據(jù)生成記錄的。在這里就不把這個(gè)表的結(jié)構(gòu)、操作類列出來了,大家自己動手來實(shí)現(xiàn)一下。
2. 報(bào)表更新
報(bào)表更新邏輯很簡單,在這里我們只展示新增的邏輯,其他邏輯大家自己動手實(shí)現(xiàn)。我們在IncomeExpenditureRecordImp
類的Add
方法中添加如下代碼:
// 獲取包含支出記錄記錄日期的報(bào)表記錄
var reports = _sporeAccountingDbContext.Reports.Where(x => x.UserId == incomeExpenditureRecord.UserId&& x.Year <= incomeExpenditureRecord.RecordDate.Year &&x.Month >= incomeExpenditureRecord.RecordDate.Month &&x.ClassificationId==incomeExpenditureRecord.IncomeExpenditureClassificationId);
// 如果沒有就說明程序還未將其寫入報(bào)表,那么就不做任何處理
for (int i = 0; i < reports.Count(); i++)
{var report = reports.ElementAt(i);report.Amount += incomeExpenditureRecord.AfterAmount;_sporeAccountingDbContext.Reports.Update(report);
}
這段代碼添加在了if (classification.Type == IncomeExpenditureTypeEnmu.Income)
分支中,當(dāng)新增的類型時(shí)支出項(xiàng)目時(shí),我們就執(zhí)行這段代碼。在這段代碼中,當(dāng)沒有查詢到支出記錄的話就認(rèn)為對應(yīng)該日期的指出記錄沒有進(jìn)行數(shù)據(jù)統(tǒng)計(jì),因此不進(jìn)行任何處理。
三、總結(jié)
在這篇文章中,我們介紹了如何在.NET 8環(huán)境下實(shí)現(xiàn)定時(shí)生成財(cái)務(wù)報(bào)表的功能。首先,分析了需求,確定了報(bào)表數(shù)據(jù)統(tǒng)計(jì)的時(shí)間和更新策略。然后,通過使用Quartz
庫創(chuàng)建了ReportTimer
定時(shí)器類,該類實(shí)現(xiàn)了IJob
接口,并在其Execute
方法中實(shí)現(xiàn)了報(bào)表數(shù)據(jù)的生成和更新邏輯。在實(shí)現(xiàn)過程中,通過依賴注入獲取必要的服務(wù)實(shí)例,查詢用戶的收入和支出記錄,生成季度、年度和月度報(bào)表,并將這些報(bào)表和日志條目保存到數(shù)據(jù)庫中,實(shí)現(xiàn)了報(bào)表數(shù)據(jù)的定期更新和持久化存儲。此外,還展示了如何在新增支出記錄時(shí)更新報(bào)表數(shù)據(jù),確保報(bào)表數(shù)據(jù)的實(shí)時(shí)性和準(zhǔn)確性。通過這種設(shè)計(jì),提高了報(bào)表生成的效率,確保了數(shù)據(jù)的一致性和完整性。希望讀者能掌握相關(guān)技術(shù)并應(yīng)用到實(shí)際項(xiàng)目中。
在下一篇文章,也就是這個(gè)專欄的最后一篇文章,我們將一起把這個(gè)服務(wù)端部署到服務(wù)器上。