在本篇文章中,我们将一起编写孢子记账的收支记录功能(CURD),同样我们只列出一个具体功能的实现,剩下的功能由读者实现。
一、 需求
需求如下:
编号 | 需求 | 说明 |
---|---|---|
1 | 新增记录 | 1.记录内容包括转换前金额、转换后金额、分类、日期、所属账本、币种、备注; 2.如果选择的币种不是设置的主币种,则将金额转换为主币种的金额 |
2 | 删除记录 | 1.不存在的记录给予提示 |
3 | 修改记录 | 1. 不存在的记录给予提示;2. 如果选择的币种不是设置的主币种,则将金额转换为主币种的金额 |
4 | 查询记录 | 1. 支持查询某一个记录;2. 支持根据开始时间和结束时间分页查询 |
就目前来说这个需求比较简单,一共5个接口,都是简单的CURD操作。并且根据需求我们也分析出来了收支记录表 IncomeExpenditureRecord
的主要结构:转换前金额 BeforAmount
、转换后金额 AfterAmount
、收支分类 IncomeExpenditureClassificationId
、记录日期 RecordDate
、所属账本 AccountBookId
、记录币种 CurrencyId
、备注 Remark
。其中收支分类、所属账本以及记录币种都是外键,备注可以为空。同时这里要注意的是与金额相关的字段都要用decimal
类型来作为字段类型。
二、功能编写
2.1 数据库映射类编写
根据前面的分析,我们的数据库映射类如下:
csharp
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using SporeAccounting.BaseModels;
namespace SporeAccounting.Models;
/// <summary>
/// 收支记录表
/// </summary>
[Table("IncomeExpenditureRecord")]
public class IncomeExpenditureRecord : BaseModel
{
/// <summary>
/// 转换前金额
/// </summary>
[Column(TypeName = "decimal(18,2)")]
[Required]
public decimal BeforAmount { get; set; }
/// <summary>
/// 转换后金额
/// </summary>
[Column(TypeName = "decimal(18,2)")]
[Required]
public decimal AfterAmount { get; set; }
/// <summary>
/// 收支分类Id
/// </summary>
[Column(TypeName = "nvarchar(36)")]
[ForeignKey("FK_IncomeExpenditureRecord_IncomeExpenditureClassification")]
[Required]
public string IncomeExpenditureClassificationId { get; set; }
/// <summary>
/// 记录日期
/// </summary>
[Column(TypeName = "datetime")]
[Required]
public DateTime RecordDate { get; set; } = DateTime.Now;
/// <summary>
/// 账簿Id
/// </summary>
[Column(TypeName = "nvarchar(36)")]
[ForeignKey("FK_IncomeExpenditureRecord_AccountBook")]
[Required]
public string AccountBookId { get; set; }
/// <summary>
/// 转换前币种Id
/// </summary>
[Column(TypeName = "nvarchar(36)")]
[ForeignKey("FK_IncomeExpenditureRecord_Currency")]
[Required]
public string CurrencyId { get; set; }
/// <summary>
/// 备注
/// </summary>
[Column(TypeName = "nvarchar(100)")]
public string? Remark { get; set; }
/// <summary>
/// 用户id
/// </summary>
[Column(TypeName = "nvarchar(36)")]
[ForeignKey("FK_IncomeExpenditureRecord_SysUser")]
[Required]
public string UserId { get; set; }
/// <summary>
/// 导航属性
/// </summary>
public SysUser User { get; set; }
/// <summary>
/// 导航属性
/// </summary>
public Currency Currency { get; set; }
/// <summary>
/// 导航属性
/// </summary>
public IncomeExpenditureClassification IncomeExpenditureClassification { get; set; }
/// <summary>
/// 导航属性
/// </summary>
public AccountBook AccountBook { get; set; }
}
关于外键指向的类的导航属性的增加这里就不展示了,请各位读者自己根据前面文章所讲的内容,以及专栏《轻松学EntityFramework Core》中关于导航属性的内容,自己手动编写相关代码。
2.2 实现需求
这一小节我们一起编写新增收支记录的功能,其他功能大家自己独立编写,然后对比我的代码。由于收支记录服务接口及其实现类和前面其他功能的类大致一样,因此这里我也就不再展示代码了,只展示新增收支记录Action的代码。
csharp
using System.Net;
using AutoMapper;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using SporeAccounting.BaseModels;
using SporeAccounting.BaseModels.ViewModel.Response;
using SporeAccounting.Models;
using SporeAccounting.Models.ViewModels;
using SporeAccounting.Server.Interface;
namespace SporeAccounting.Controllers
{
/// <summary>
/// 收支记录控制器
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class IncomeExpenditureRecordController : BaseController
{
/// <summary>
/// 收支记录服务
/// </summary>
private readonly IIncomeExpenditureRecordServer _incomeExpenditureRecordServer;
/// <summary>
/// 配置服务
/// </summary>
private readonly IConfigServer _configServer;
/// <summary>
/// 汇率记录服务
/// </summary>
private readonly IExchangeRateRecordServer _exchangeRateRecordServer;
/// <summary>
/// 币种服务
/// </summary>
private readonly ICurrencyServer _currencyServer;
/// <summary>
/// 映射器
/// </summary>
private readonly IMapper _mapper;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="incomeExpenditureRecordServer"></param>
/// <param name="configServer"></param>
/// <param name="exchangeRateRecordServer"></param>
/// <param name="currencyServer"></param>
/// <param name="mapper"></param>
public IncomeExpenditureRecordController(IIncomeExpenditureRecordServer incomeExpenditureRecordServer,
IConfigServer configServer,
IExchangeRateRecordServer exchangeRateRecordServer,
ICurrencyServer currencyServer,
IMapper mapper)
{
_incomeExpenditureRecordServer = incomeExpenditureRecordServer;
_configServer = configServer;
_exchangeRateRecordServer = exchangeRateRecordServer;
_currencyServer = currencyServer;
_mapper = mapper;
}
/// <summary>
/// 添加收支记录
/// </summary>
/// <param name="incomeExpenditureRecordAddViewModel"></param>
/// <returns></returns>
[HttpPost]
[Route("Add")]
public ActionResult<ResponseData<bool>> Add(
[FromBody] IncomeExpenditureRecordAddViewModel incomeExpenditureRecordAddViewModel)
{
try
{
string userId = GetUserId();
//获取用户设置的主币种
Config? config = _configServer.Query(userId, ConfigTypeEnum.Currency);
if (config == null)
{
return Ok(new ResponseData<bool>(HttpStatusCode.NotFound, "未设置主币种"));
}
// 如果选择的币种不是设置的主币种,则将金额转换为主币种的金额
if (config.Value != incomeExpenditureRecordAddViewModel.CurrencyId)
{
//查询主币种
Currency? mainCurrency = _currencyServer.Query(config.Value);
if (mainCurrency == null)
{
return Ok(new ResponseData<bool>(HttpStatusCode.NotFound, "币种不存在"));
}
// 查询传入的币种
Currency? recordCurrency = _currencyServer.Query(incomeExpenditureRecordAddViewModel.CurrencyId);
if (recordCurrency == null)
{
return Ok(new ResponseData<bool>(HttpStatusCode.NotFound, "币种不存在"));
}
//获取记录币种和主币种的汇率
ExchangeRateRecord? exchangeRateRecord =
_exchangeRateRecordServer.Query($"{mainCurrency.Abbreviation}_{recordCurrency.Abbreviation}");
if (exchangeRateRecord == null)
{
return Ok(new ResponseData<bool>(HttpStatusCode.NotFound, "汇率不存在"));
}
incomeExpenditureRecordAddViewModel.AfterAmount *= exchangeRateRecord.ExchangeRate;
}
IncomeExpenditureRecord incomeExpenditureRecord =
_mapper.Map<IncomeExpenditureRecord>(incomeExpenditureRecordAddViewModel);
incomeExpenditureRecord.UserId = userId;
incomeExpenditureRecord.CreateDateTime = DateTime.Now;
incomeExpenditureRecord.CreateUserId = userId;
_incomeExpenditureRecordServer.Add(incomeExpenditureRecord);
return Ok(new ResponseData<bool>(HttpStatusCode.OK, data: true));
}
catch (Exception e)
{
return Ok(new ResponseData<bool>(HttpStatusCode.InternalServerError, "服务端异常"));
}
}
}
}
这段代码定义了一个控制器 IncomeExpenditureRecordController
,用于管理与收支记录相关的操作。控制器依赖多个服务接口来完成其逻辑,包括收支记录服务、配置服务、汇率记录服务、币种服务和映射器。
在控制器的 Add
方法中,首先从请求体中接收 IncomeExpenditureRecordAddViewModel
类型的对象。接着,通过 GetUserId
方法获取当前用户的ID,用于后续的用户绑定。
然后,系统会检查用户是否设置了主币种配置,如果没有,则返回"未设置主币种"的响应。如果输入的币种与用户设置的主币种不同,系统会通过 _currencyServer
查询主币种和输入的币种信息。如果任一币种不存在,则返回"币种不存在"的响应。
如果币种存在,则通过 _exchangeRateRecordServer
查询这两个币种的汇率信息。如果找不到汇率记录,则返回"汇率不存在"的响应。若汇率存在,则将输入的金额根据汇率转换为主币种金额。
在所有数据准备完成后,使用 _mapper
将视图模型 IncomeExpenditureRecordAddViewModel
转换为 IncomeExpenditureRecord
实体对象,并设置与用户相关的属性(如 UserId
和 CreateUserId
)。最终,通过 _incomeExpenditureRecordServer
将新记录保存到数据库,并返回操作成功的响应。 如果在过程中发生异常,则返回"服务端异常"的响应。
三、总结
这篇文章我们一起实现了收支记录中的新增记录功能,这功能比前面咱们实现的功能稍显复杂,需要获取主币种、获取主币种与用户选择的币种之间的汇率,最后根据汇率换算成主币种。
下一篇文章我们将结合主币种设置以及收支记录实现切换主币种后重新计算以前记录的转换后的金额。