報表是每個記賬應用所具備的功能,要實現報表功能就需要把賬本的核心功能(記賬)完成,因此報表服務作為本專欄第一部分單體應用開發中最后一個要實現的功能,這一篇文章很簡單,我們一起來實現一個簡單的報表服務。
一、需求
需求很簡單,我們只需要提供一個接口供客戶端查詢時使用,下面是需求。
編號 | 需求 | 說明 |
---|---|---|
1 | 報表查詢 | 1. 傳入報表類型,年份、月份查詢對應的報表數據;2. 月份參數可以為空;3. 報表類型包括:月報表、季度報表、年報表 |
二、功能編寫
-
模型編寫
根據上一小節的需求,我們得出了傳入的參數需要包含:報表類型、年份以及月份,其中月份可以為空,視圖模型如下:
using System.ComponentModel.DataAnnotations;namespace SporeAccounting.Models.ViewModels;/// <summary> /// 報表視圖模型 /// </summary> public class ReportViewModel {/// <summary>/// 報表類型/// </summary>[Required(ErrorMessage = "報表類型不能為空")]public ReportTypeEnum ReportType { get; set; }/// <summary>/// 年份/// </summary>[Required(ErrorMessage = "年份不能為空")]public int Year { get; set; }/// <summary>/// 月份/// </summary>public int? Month { get; set; } }
我們一起來看看數據庫表映射類需要包含哪些屬性吧。首先,需要年份、月份和季度這幾個字段,它們可以提供清晰的時間維度,方便用戶在不同時間范圍內查詢和統計數據,比如生成年度報告、月度賬單或季度分析等。
其次,需要一個主題或用途字段,用于標識報表的具體內容。此外,還需要一個報表類型字段,可以用枚舉值來區分不同類型的報表(如季度報表、月度報表、年度報表)。
因為報表通常涉及金額,所以必須有一個金額字段來準確記錄相關數據。為了幫助用戶清楚了解每個支出分類的具體占比和總金額,還需要一個支出分類字段。
最后,考慮到每個報表只能對應一個用戶,我們還需要一個字段來標識用戶,以確保報表和用戶之間的關聯關系。
這樣設計,既能滿足報表統計和分析的需求,也能保證數據結構清晰合理。通過分析,我們得出如下代碼:using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using SporeAccounting.BaseModels;namespace SporeAccounting.Models;/// <summary> /// 報表 /// </summary> [Table("Report")] public class Report : BaseModel {/// <summary>/// 年份/// </summary>[Required][Column(TypeName = "int")]public int Year { get; set; }/// <summary>/// 月份/// </summary>[Column(TypeName = "int")]public int? Month { get; set; }/// <summary>/// 季度/// </summary>[Column(TypeName = "int")]public int? Quarter { get; set; }/// <summary>/// 報表名稱/// </summary>[Required][Column(TypeName = "nvarchar(100)")]public string Name { get; set; }/// <summary>/// 報表類型/// </summary>[Required][Column(TypeName = "int")]public ReportTypeEnum Type { get; set; }/// <summary>/// 金額/// </summary>[Required][Column(TypeName = "decimal(18,2)")]public decimal Amount { get; set; }/// <summary>/// 用戶Id/// </summary>[Required][Column(TypeName = "nvarchar(36)")][ForeignKey("FK_Report_SysUser_UserId")]public string UserId { get; set; }/// <summary>/// 分類Id/// </summary>[Required][Column(TypeName = "nvarchar(36)")][ForeignKey("FK_Report_Classification_ClassificationId")]public string ClassificationId { get; set; }/// <summary>/// 導航屬性/// </summary>public SysUser User { get; set; }/// <summary>/// 導航屬性/// </summary>public IncomeExpenditureClassification Classification { get; set; } }
-
功能實現
我們這里只展示和查詢報表有關的功能代碼,其他代碼不再展示。首先,我們要定義數據服務接口IReportServer
,在這個接口中添加獲取報表的方法。using SporeAccounting.Models;namespace SporeAccounting.Server.Interface;/// <summary> /// 報表服務 /// </summary> public interface IReportServer { /// <summary>/// 獲取報表數據/// </summary>/// <param name="userId"></param>/// <param name="year"></param>/// <param name="reportType"></param>/// <returns></returns>List<Report> QueryReport(string userId, int year,ReportTypeEnum reportType); }
然后,我們實現這個接口,實現也很簡單很簡單,我就不具體講解了。
using SporeAccounting.Models; using SporeAccounting.Server.Interface;namespace SporeAccounting.Server;/// <summary> /// 報表服務實現 /// </summary> public class ReportImp : IReportServer {private readonly SporeAccountingDBContext _sporeAccountingDbContext;public ReportImp(SporeAccountingDBContext sporeAccountingDbContext){_sporeAccountingDbContext = sporeAccountingDbContext;}/// <summary>/// 獲取報表數據/// </summary>/// <param name="userId"></param>/// <param name="year"></param>/// <returns></returns>public List<Report> QueryReport(string userId, int year, ReportTypeEnum reportType){try{IQueryable<Report> reports = _sporeAccountingDbContext.Reports.Where(p => p.UserId == userId && p.Year == year && p.Type == reportType);return reports.ToList();}catch (Exception e){throw;}} }
最后,我們一起編寫Controller。
using System.Net; using AutoMapper; using Microsoft.AspNetCore.Mvc; using SporeAccounting.BaseModels; using SporeAccounting.Models.ViewModels; using SporeAccounting.Server.Interface;namespace SporeAccounting.Controllers {/// <summary>/// 報表控制器/// </summary>[Route("api/[controller]")][ApiController]public class ReportController : BaseController{/// <summary>/// 報表服務/// </summary>private IReportServer _reportServer;/// <summary>/// 映射/// </summary>private IMapper _mapper;/// <summary>/// 構造函數/// </summary>/// <param name="reportServer"></param>/// <param name="mapper"></param>public ReportController(IReportServer reportServer, IMapper mapper){_reportServer = reportServer;_mapper = mapper;}/// <summary>/// 獲取報表/// </summary>/// <param name="report"></param>/// <returns></returns>[HttpPost][Route("GetReport")]public ActionResult<ResponseData<List<ReportResponseViewModel>>> GetReport([FromBody] ReportViewModel report){try{string userId = GetUserId();var reports = _reportServer.QueryReport(userId, report.Year, report.ReportType);List<ReportResponseViewModel> response = _mapper.Map<List<ReportResponseViewModel>>(reports);return Ok(new ResponseData<List<ReportResponseViewModel>>(HttpStatusCode.OK, data: response));}catch (Exception ex){return Ok(new ResponseData<bool>(HttpStatusCode.BadRequest, errorMessage: ex.Message));}}} }
三、總結
本文介紹了記賬應用中的報表功能實現,作為單體應用開發的最后環節,其核心是提供一個接口供客戶端查詢報表數據。本功能實現以清晰的需求分析為起點,逐步完成了模型設計、服務接口定義、接口實現以及控制器編寫。需求方面,報表查詢需支持按報表類型(包括月報、季報、年報)和時間維度(年份、月份)進行查詢,其中月份參數為可選。功能設計中,視圖模型定義了報表類型、年份和月份的基本字段,并通過校驗屬性確保必填字段的正確性。數據庫模型進一步擴展了字段設計,涵蓋了年份、月份、季度、報表名稱、類型、金額、用戶關聯等內容,滿足多維度數據統計和查詢需求。服務層通過IReportServer
接口和實現類ReportImp
完成核心數據查詢邏輯,利用數據庫上下文篩選符合條件的報表數據。最后,控制器ReportController
提供了一個GetReport
接口,負責接收客戶端請求并返回查詢結果。通過依賴注入服務和自動映射工具,接口實現高效、簡潔。整體設計從需求到實現邏輯清晰,功能模塊化,便于后續擴展和維護。
在下一篇文章中我們將一起編寫報表定時器,實現定時匯總支出數據功能。