19. 【.NET 8 实战--孢子记账--从单体到微服务】--记账模块--收支记录

在本篇文章中,我们将一起编写孢子记账的收支记录功能(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 实体对象,并设置与用户相关的属性(如 UserIdCreateUserId)。最终,通过 _incomeExpenditureRecordServer 将新记录保存到数据库,并返回操作成功的响应。 如果在过程中发生异常,则返回"服务端异常"的响应。

三、总结

这篇文章我们一起实现了收支记录中的新增记录功能,这功能比前面咱们实现的功能稍显复杂,需要获取主币种、获取主币种与用户选择的币种之间的汇率,最后根据汇率换算成主币种。

下一篇文章我们将结合主币种设置以及收支记录实现切换主币种后重新计算以前记录的转换后的金额。

相关推荐
cqths27 分钟前
.NET 9.0 的 Blazor Web App 项目、Bootstrap Blazor 组件库、自定义日志 TLog 使用备忘
数据库·c#·.net·web app
青云交1 小时前
Java 大视界 -- Java 大数据在元宇宙中的关键技术与应用场景(65)
大数据·数据分析·元宇宙·数据存储·实时处理·虚拟身份·虚拟经济
HaoHao_0103 小时前
AWS Outposts
大数据·服务器·数据库·aws·云服务器
HaoHao_0103 小时前
VMware 的 AWS
大数据·服务器·数据库·云计算·aws·云服务器
.NET骚操作5 小时前
Sdcb Chats 技术博客:数据库 ID 选型的曲折之路 - 从 Guid 到自增 ID,再到 Guid
ai·c#·.net·chats
Elastic 中国社区官方博客5 小时前
将 OneLake 数据索引到 Elasticsearch - 第二部分
大数据·数据库·elasticsearch·搜索引擎·信息可视化·全文检索
庄小焱5 小时前
Elasticsearch——Elasticsearch查询实战
大数据·elasticsearch·搜索引擎
金融OG6 小时前
99.17 金融难点通俗解释:归母净利润
大数据·数据库·python·机器学习·金融
豪越大豪6 小时前
智慧消防营区一体化安全管控 2024 年度深度剖析与展望
大数据·运维