收支分类

从这篇文章开始我们就进入到了业务功能的开发阶段,首先来开发的就是收支分类功能,这个功能是最简单的,同时也是孢子记账业务的基础。下面我们就来一起开发吧!

一、需求

收支分类的需求看似很简单,但是里面条件和限制比较多,我们来看一看。

编号
功能
描述

1

新增

1.分类名称不能重名;2. 如果分类是子类型那么子类型的收支类型必须和父类型的收支类型一致;3. 子类型不能有子类型

2

删除

1.系统预置的收支分类不能删除;2. 删除父类型时,如果存在子类型则不允许删除;

3

修改

1.分类名称不能重名;2. 如果分类是子类型那么子类型的收支类型必须和父类型的收支类型一致;3. 子类型不能有子类型

4

查询

1.根据父级分类Id查询;2. 根据收支类型查询;3. 分页查询,过滤条件包含:收支类型、分类名称、腹肌分类Id

二、编写代码

下面我们就以新增收支分类为例,带领大家一起编写代码。和前面的套路一样,我们先编写数据库映射类,然后再编写业务逻辑。

2.1 编写数据库映射类

这个没什么可讲的,直接上代码。

using System.ComponentModel.DataAnnotations;
using SporeAccounting.BaseModels;
using System.ComponentModel.DataAnnotations.Schema;

namespace SporeAccounting.Models;

/// <summary>
/// 收支分类
/// </summary>
[Table(name: "IncomeExpenditureClassification")]
public class IncomeExpenditureClassification : BaseModel
{
    /// <summary>
    /// 类型名称
    /// </summary>
    [Required(ErrorMessage = "类型名称不能为空")]
    [MaxLength(20, ErrorMessage = "类型名称不能超过20字")]
    [Column(TypeName = "nvarchar(20)")]
    public string Name { get; set; }

    /// <summary>
    /// 收支类型
    /// </summary>
    [Required(ErrorMessage = "收支类型能为空")]
    [Column(TypeName = "int")]
    public IncomeExpenditureTypeEnmu Type { get; set; }

    /// <summary>
    /// 是否可以删除
    /// </summary>
    [Required(ErrorMessage = "是否可以删除")]
    [Column(TypeName = "tinyint(1)")]
    public bool CanDelete { get; set; } = true;

    /// <summary>
    /// 父级分类ID
    /// </summary>
    [Column(TypeName = "nvarchar(36)")]
    [ForeignKey("FK_Classification_ClassificationId")]
    public string? ParentClassificationId { get; set; }

    /// <summary>
    /// 导航属性,用于指向父项
    /// </summary>
    public IncomeExpenditureClassification? Parent { get; set; }

    /// <summary>
    /// 导航属性,用于指向子项集合
    /// </summary>
    public ICollection<IncomeExpenditureClassification> Children { get; set; } =
        new List<IncomeExpenditureClassification>();
}

编写完数据库表映射类后一定不要忘记在我们的DBContext类中添加属性,然后数据库迁移。

2.2 编写业务逻辑

我们新建收支分类的接口IIncomeExpenditureClassificationServer和实现类IncomeExpenditureClassificationImp,然后新增Add方法,以及QueryByIdIsExist方法,并实现他们。

  1. IIncomeExpenditureClassificationServer

    using SporeAccounting.Models;
    
    namespace SporeAccounting.Server.Interface;
    
    /// <summary>
    /// 收支分类数据库操作接口
    /// </summary>
    public interface IIncomeExpenditureClassificationServer
    {
        /// <summary>
        /// 新增收支分类
        /// </summary>
        /// <param name="classification"></param>
        void Add(IncomeExpenditureClassification classification);
    
        /// <summary>
        /// 根据分类Id查询
        /// </summary>
        /// <param name="classificationId"></param>
        /// <returns></returns>
        IncomeExpenditureClassification QueryById(string classificationId);
    
        /// <summary>
        /// 分类名称是否存在
        /// </summary>
        /// <param name="classificationName"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        bool IsExist(string classificationName, string userId);
    }
  2. IncomeExpenditureClassificationImp

    using SporeAccounting.Models;
    using SporeAccounting.Models.ViewModels;
    using SporeAccounting.Server.Interface;
    
    namespace SporeAccounting.Server;
    
    /// <summary>
    /// 收支分类数据库操作实现
    /// </summary>
    public class IncomeExpenditureClassificationImp : IIncomeExpenditureClassificationServer
    {
        private readonly SporeAccountingDBContext _sporeAccountingDbContext;
    
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="sporeAccountingDbContext"></param>
        public IncomeExpenditureClassificationImp(SporeAccountingDBContext sporeAccountingDbContext)
        {
            _sporeAccountingDbContext = sporeAccountingDbContext;
        }
    
        /// <summary>
        /// 新增收支分类
        /// </summary>
        /// <param name="classification"></param>
        public void Add(IncomeExpenditureClassification classification)
        {
            try
            {
                _sporeAccountingDbContext.IncomeExpenditureClassifications.Add(classification);
                _sporeAccountingDbContext.SaveChanges();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
    
        /// <summary>
        /// 根据分类Id查询
        /// </summary>
        /// <param name="classificationId"></param>
        /// <returns></returns>
        public IncomeExpenditureClassification QueryById(string classificationId)
        {
            try
            {
                return _sporeAccountingDbContext.IncomeExpenditureClassifications
                    .FirstOrDefault(p => p.Id == classificationId);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
    
        /// <summary>
        /// 分类名称是否存在
        /// </summary>
        /// <param name="classificationName"></param>
        /// <param name="userId"></param>
        /// <returns></returns>
        public bool IsExist(string classificationName, string userId)
        {
            try
            {
                return _sporeAccountingDbContext.IncomeExpenditureClassifications
                    .Any(p => p.Name == classificationName && p.CreateUserId == userId);
            }
            catch (Exception e)
            {
                throw;
            }
        }
    }

在编写完服务代码之后,我们就要开始编写Controller了,首先新建IncomeExpenditureClassificationController,它继承自我们自定义的BaseController,然后在其中新增Add Action 实现新增收支分类功能接口。

using Microsoft.AspNetCore.Mvc;
using SporeAccounting.BaseModels;
using SporeAccounting.Models;
using SporeAccounting.Models.ViewModels;
using SporeAccounting.Server.Interface;
using System.Net;
using AutoMapper;

namespace SporeAccounting.Controllers
{
    /// <summary>
    /// 收支分类接口
    /// </summary>
    [Route("api/[controller]")]
    [ApiController]
    public class IncomeExpenditureClassificationController : BaseController
    {
        private readonly IIncomeExpenditureClassificationServer _incomeExpenditureClassificationService;
        private readonly IMapper _mapper;

        public IncomeExpenditureClassificationController(
            IIncomeExpenditureClassificationServer incomeExpenditureClassificationService, IMapper mapper)
        {
            _incomeExpenditureClassificationService = incomeExpenditureClassificationService;
            _mapper = mapper;
        }
       
        /// <summary>
        /// 新增收支分类
        /// </summary>
        /// <param name="classificationAddViewModel"></param>
        /// <returns></returns>
        [HttpPost]
        [Route("Add")]
        public ActionResult<ResponseData<bool>> Add([FromBody] IncomeExpenditureClassificationViewModel classificationAddViewModel)
        {
            try
            {
                //是否存在
                bool isExist = _incomeExpenditureClassificationService.IsExist(classificationAddViewModel.Name, GetUserId());
                if (isExist)
                {
                    return Ok(new ResponseData<bool>(HttpStatusCode.Conflict, $"分类{classificationAddViewModel.Name}已存在!", false));
                }
                
                if (!string.IsNullOrEmpty(classificationAddViewModel.ParentClassificationId))
                {
                    //判断类型是否和父级的类型一样
                    IncomeExpenditureClassification parentClassification =
                        _incomeExpenditureClassificationService.QueryById(classificationAddViewModel.ParentClassificationId);
                    if (parentClassification.Type != classificationAddViewModel.Type)
                    {
                        return Ok(new ResponseData<bool>(HttpStatusCode.Conflict, $"分类{classificationAddViewModel.Name}的类型和父级类型不一致!", false));
                    }
                    //判断父级是否是子集
                    if (parentClassification.ParentClassificationId != null)
                    {
                        return Ok(new ResponseData<bool>(HttpStatusCode.Conflict, $"子分类不能再创建子类!", false));
                    }
                }
                IncomeExpenditureClassification classification = _mapper.Map<IncomeExpenditureClassification>(classificationAddViewModel);
                classification.CreateUserId = GetUserId();
                classification.CanDelete = true;
                classification.CreateDateTime = DateTime.Now;
                _incomeExpenditureClassificationService.Add(classification);
                return Ok(new ResponseData<bool>(HttpStatusCode.OK, data: true));
            }
            catch (Exception e)
            {
                return Ok(new ResponseData<bool>(HttpStatusCode.InternalServerError, "服务器异常", false));
            }
        }
}

我们在前面的代码中发现Add Action 传入的参数类型是视图模型IncomeExpenditureClassificationViewModel ,并且通过AutoMapper将其转换成了数据库表映射类IncomeExpenditureClassification类,因此我们需要编写这个视图模型,然后给AutoMapper添加一条转换规则。

  1. IncomeExpenditureClassificationViewModel

    using System.ComponentModel.DataAnnotations;
    
    namespace SporeAccounting.Models.ViewModels;
    
    public class IncomeExpenditureClassificationViewModel
    {
        /// <summary>
        /// 分类名称
        /// </summary>
        [Required(ErrorMessage = "分类名称不能为空")]
        [MaxLength(20)]
        public string Name { get; set; }
    
        /// <summary>
        /// 收支类型
        /// </summary>
        [Required(ErrorMessage = "收支类型不能为空")]
        [EnumDataType(typeof(IncomeExpenditureTypeEnmu))]
        public IncomeExpenditureTypeEnmu Type { get; set; }
    
        /// <summary>
        /// 腹肌分类Id
        /// </summary>
        [MaxLength(36)]
        public string? ParentClassificationId { get; set; }
    }
  2. SporeAccountingProfile

    CreateMap<IncomeExpenditureClassificationViewModel, IncomeExpenditureClassification>();

到此为止新增收支分类功能的代码我们就全部编写完成了,剩余的需求请大家自己动手来编写一下,编写完成后下载我写的代码进行对比,有问题的话欢迎留言、私信或者加我的微信询问。

三、总结

这篇文章以新增收支分类功能为例,带领大家完成了第一个业务功能。需求比较简单,就是一些基本的CURD,但是大家要细心的查看需求,防止遗漏掉一些功能点。

Last updated