18. 【.NET 8 实战--孢子记账--从单体到微服务】--记账模块--账本

这一篇我们来一起为账本功能编写代码。账本功能的代码很简单,就是一些简单的CURD操作。

一、需求

我们先来看一下需求:

编号 需求 说明
1 新增账本 1. 账本名称不能和用户已有的账本名称重复
2 删除账本 1. 存在收支记录的账本不能删除
3 修改账本 1. 修改的账本名称不能和用户已有的账本名称重复
4 查询站本 1. 支持按照账本名称、账本备注进行分页查询; 2. 支持根据账本id查询

根据上面的需求我们可以分析出5个接口和数据库表映射类所需的字段。5个接口就不多说了,需求上写的清清楚楚,这一小节我们重点看一下数据库表映射类所需的字段。

从需求1和3中分析出我们需要一个账本类 来作为数据库表映射类,并且这个类需要有账本名称 ,以及账本所属用户的Id ,从需求4中得知账本类 还需要有备注 ,同时还需要另一个数据库表映射类收支记录 类。

需求我们分析完了,现在我们就一起来编写实现账本功能的代码吧。和前面的文章一样,在这里我只带领大家实现删除功能,剩余的功能大家自己动手实现。

二、功能编写

2.1 创建数据库映射类

这里我们需要两个数据库映射类:账本类(AccountBook)、收支记录类(IncomeExpenditureRecordIncomeExpenditureRecord 类我们会在下一篇文章中实现,因此在这篇文章中我们只需要把它创建出来并在里面加上AccountBook 类的导航属性即可。代码如下:

  1. AccountBook

    csharp 复制代码
    using System.ComponentModel.DataAnnotations.Schema;
    using Microsoft.Build.Framework;
    using SporeAccounting.BaseModels;
    
    namespace SporeAccounting.Models;
    
    /// <summary>
    /// 账簿
    /// </summary>
    [Table(name: "AccountBook")]
    public class AccountBook : BaseModel
    {
        /// <summary>
        /// 账簿名称
        /// </summary>
        [Column(TypeName = "nvarchar(20)")]
        [Required]
        public string Name { get; set; }
    
        /// <summary>
        /// 备注
        /// </summary>
        [Column(TypeName = "nvarchar(100)")]
        public string? Remarks { get; set; }
    
        /// <summary>
        /// 用户Id
        /// </summary>
        [Column(TypeName = "nvarchar(36)")]
        [ForeignKey("FK_AccountBook_SysUser")]
        [Required]
        public string UserId { get; set; }
    
        /// <summary>
        /// 导航属性
        /// </summary>
        public SysUser User { get; set; }
    
        /// <summary>
        /// 导航属性
        /// </summary>
        public ICollection<IncomeExpenditureRecord> IncomeExpenditureRecords { get; set; } =
            new List<IncomeExpenditureRecord>();
    }
  2. IncomeExpenditureRecord

    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>
        public AccountBook AccountBook { get; set; }
    }

Tip:不明白什么是导航属性的,请先学习我写的专栏《轻松学EntityFramework Core

2.2 实现需求
  1. 创建AccountBook表数据库操作服务
    我们新建** 账本服务接口**IAccountBookServer以及他的实现类AccountBookImp ,并在添加Delete 方法、IsExistById 方法和判断账本是否存在收支记录的方法IsExistIncomeExpenditureRecord

    csharp 复制代码
    ///IAccountBookServer
    using SporeAccounting.Models;
    
    namespace SporeAccounting.Server.Interface;
    
    /// <summary>
    /// 账本服务接口
    /// </summary>
    public interface IAccountBookServer
    {
        /// <summary>
        /// 删除账本
        /// </summary>
        /// <param name="accountBookId"></param>
        void Delete(string accountBookId);
    
        /// <summary>
        /// 账本是否存在
        /// </summary>
        /// <param name="accountBookId"></param>
        /// <returns></returns>
        bool IsExistById(string accountBookId);
    
        /// <summary>
        /// 账本是否存在收支记录
        /// </summary>
        /// <param name="accountBookId"></param>
        /// <returns></returns>
        bool IsExistIncomeExpenditureRecord(string accountBookId);
    }
    
    
    
    ///AccountBookImp 
    using SporeAccounting.Models;
    using SporeAccounting.Server.Interface;
    
    namespace SporeAccounting.Server;
    
    /// <summary>
    /// 账本服务实现
    /// </summary>
    public class AccountBookImp : IAccountBookServer
    {
        /// <summary>
        /// 数据库上下文
        /// </summary>
        private readonly SporeAccountingDBContext _sporeAccountingDbContext;
    
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="sporeAccountingDbContext"></param>
        public AccountBookImp(SporeAccountingDBContext sporeAccountingDbContext)
        {
            _sporeAccountingDbContext = sporeAccountingDbContext;
        }
    
        /// <summary>
        /// 删除账本
        /// </summary>
        /// <param name="accountBookId"></param>
        public void Delete(string accountBookId)
        {
            try
            {
                AccountBook accountBook = _sporeAccountingDbContext.AccountBooks
                    .FirstOrDefault(p => p.Id == accountBookId)!;
                _sporeAccountingDbContext.AccountBooks.Remove(accountBook);
                _sporeAccountingDbContext.SaveChanges();
            }
            catch (Exception e)
            {
                throw;
            }
        }
    
        /// <summary>
        /// 账本是否存在
        /// </summary>
        /// <param name="accountBookId"></param>
        /// <returns></returns>
        public bool IsExistById(string accountBookId)
        {
            try
            {
                return _sporeAccountingDbContext.AccountBooks
                    .Any(p => p.Id == accountBookId);
            }
            catch (Exception e)
            {
                throw;
            }
        }
    
        /// <summary>
        /// 账本是否存在收支记录
        /// </summary>
        /// <param name="accountBookId"></param>
        /// <returns></returns>
        public bool IsExistIncomeExpenditureRecord(string accountBookId)
        {
            try
            {
                return _sporeAccountingDbContext.AccountBooks
                    .Include(p => p.IncomeExpenditureRecords)
                    .Any(p => p.Id == accountBookId);
                
                // 也可以这么查询
                // return _sporeAccountingDbContext.IncomeExpenditureRecords
                //     .Any(p => p.Id == accountBookId);
            }
            catch (Exception e)
            {
                throw;
            }
        }
    }

    IsExistIncomeExpenditureRecord 方法中的注释提供了一个替代实现,直接从 IncomeExpenditureRecords 表进行查询,跳过了通过 AccountBooks 的关联导航加载。这种方式可能效率更高,具体选择依赖业务需求。

3.2 Controller 实现

新建AccountBookController控制器,并添加Delete Action,代码如下:

csharp 复制代码
using System.Net;
using AutoMapper;
using SporeAccounting.BaseModels.ViewModel.Response;
using Microsoft.AspNetCore.Mvc;
using SporeAccounting.BaseModels;
using SporeAccounting.Models;
using SporeAccounting.Models.ViewModels;
using SporeAccounting.Server.Interface;

namespace SporeAccounting.Controllers
{
    /// <summary>
    /// 账本控制器
    /// </summary>
    [Route("api/[controller]")]
    [ApiController]
    public class AccountBookController : BaseController
    {
        /// <summary>
        /// 账本服务
        /// </summary>
        private readonly IAccountBookServer _accountBookServer;

        /// <summary>
        /// 映射
        /// </summary>
        private readonly IMapper _mapper;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="accountBookServer"></param>
        /// <param name="mapper"></param>
        public AccountBookController(IAccountBookServer accountBookServer, IMapper mapper)
        {
            _accountBookServer = accountBookServer;
            _mapper = mapper;
        }


        /// <summary>
        /// 删除账本
        /// </summary>
        /// <param name="accountBookId"></param>
        /// <returns></returns>
        [HttpDelete]
        [Route("Delete/{accountBookId}")]
        public ActionResult<ResponseData<bool>> Delete([FromRoute] string accountBookId)
        {
            try
            {
                //是否存在账本
                bool isExist = _accountBookServer.IsExistById(accountBookId);
                if (!isExist)
                {
                    return Ok(new ResponseData<bool>(HttpStatusCode.BadRequest, errorMessage: "账本不存在"));
                }

                //是否存在收支记录
                bool isExistIncomeExpenditureRecord = _accountBookServer.IsExistIncomeExpenditureRecord(accountBookId);
                if (isExistIncomeExpenditureRecord)
                {
                    return Ok(new ResponseData<bool>(HttpStatusCode.BadRequest, errorMessage: "账本存在收支记录,不能删除"));
                }

                _accountBookServer.Delete(accountBookId);
                return Ok(new ResponseData<bool>(HttpStatusCode.OK, data: true));
            }
            catch (Exception e)
            {
                return Ok(new ResponseData<bool>(HttpStatusCode.InternalServerError, errorMessage: "服务端异常"));
            }
        }

    }
}

这段代码定义了一个名为 AccountBookController 的控制器,用于管理账本操作。该控制器继承自 BaseController,并通过依赖注入方式初始化了两个服务:账本服务 _accountBookServer 和映射服务 _mapper。控制器的路由前缀设置为 api/AccountBook,且标记为 API 控制器,确保符合 RESTful API 的约定。
Delete 方法实现了通过账本 ID 删除账本的逻辑,绑定了 DELETE 请求,并在路由中包含动态参数 accountBookId。方法首先调用账本服务检查指定 ID 的账本是否存在,若不存在则返回带有 400 Bad Request 状态码的响应数据,并附上错误信息。接着检查该账本是否存在收支记录,若存在也返回类似的错误响应,提示无法删除。若上述条件均不满足,则调用账本服务的 Delete 方法删除该账本,并返回成功响应,包含 200 OK 状态码和 true 的数据表示操作成功。在操作过程中若出现异常,捕获后返回服务器错误响应,附带 500 Internal Server Error 状态码和异常提示。

三、总结

这篇文章带领大家一起实现了账本的删除功能,代码比较简单,只要充分了解需求后就能编写出来。账本剩余的功能代码,大家自己动手实现。

相关推荐
黄俊懿21 分钟前
【深入理解SpringCloud微服务】Sentinel功能详解
后端·spring·spring cloud·微服务·中间件·架构·sentinel
运维&陈同学24 分钟前
【zookeeper04】消息队列与微服务之zookeeper客户端访问
linux·后端·微服务·zookeeper·云原生·消息队列·云计算
2401_854391081 小时前
企业OA管理系统:Spring Boot技术架构与应用
spring boot·后端·架构
2401_857636391 小时前
Spring Boot英语知识网站:架构与开发
数据库·spring boot·架构
petaexpress2 小时前
k8s微服务架构就是云原生吗?两者是什么关系
微服务·云原生·架构·kubernetes·k8s
yuguo.im2 小时前
Dkron 架构与设计
架构·golang·go·dkron
HuaLuLemon2 小时前
03-微服务搭建
java·微服务·gulimall
BestandW1shEs2 小时前
谈谈微服务的常用组件
微服务·架构
界面开发小八哥2 小时前
DevExpress WinForms中文教程:Data Grid - 使用服务器模式的大数据源和即时反馈?
.net·界面控件·winform·devexpress·ui开发