文档管理系统重构

分层架构系统Controller 业务层 数据层

对于服务端的Controller,业务层和数据层应该有各自该执行的任务

Controller 负责对数据合法性的校验之后传递给业务层

业务层 业务层进行业务逻辑的实现之后传递给数据层

数据层 对数据库进行操作

对于原有的项目查询为例,进行重构

原有的DocumentController

[HttpPost]
public JsonResult ClassifyGet()
{
  var id = HttpContext.User.Claims.First().Value.ToInt();
  return Json(_documentsService.GetClassifyList(id));
}

原有的Service

public ResponseModel GetClassifyList(int id)
{
  if (id <= 0)
      return GetClassifyList();
  var classifys = _db.Classify.Where(r => _db.Roles.Where(r => r.UserId == id).Select(r => r.ClassifyId).Contains(r.Id)).OrderByDescending(c => c.Sort).ToList();
  var response = new ResponseModel { code = 200, result = "文档类别获取成功" };
  response.data = new List<ResClassifyModel>();
  foreach (var gclassify in classifys)
  {
      response.data.Add(new ResClassifyModel
      {
          Id = gclassify.Id,
          Name = gclassify.Name,
          Sort = gclassify.Sort,
          Remark = gclassify.Remark,
          CreateTime = gclassify.CreateTime.ToString("yyyy-MM-dd HH:mm:ss"),
      });
  }
  return response;
}
public ResponseModel GetClassifyList()
{
  var classifys = _db.Classify.OrderByDescending(c => c.Sort).ToList();
  var response = new ResponseModel { code = 200, result = "文档类别获取成功" };
  response.data = new List<ResClassifyModel>();
  foreach (var gclassify in classifys)
  {
      response.data.Add(new ResClassifyModel
      {
          Id = gclassify.Id,
          Name = gclassify.Name,
          Sort = gclassify.Sort,
          Remark = gclassify.Remark,
          CreateTime = gclassify.CreateTime.ToString("yyyy-MM-dd HH:mm:ss")
      });
  }
  return response;
}

拆分为

Controller

[HttpPost]
public JsonResult ClassifyGet()
{
  var id = HttpContext.User.Claims.First().Value.ToInt();
  return Json(_documentsService.GetClassifyList(id));
}

业务层

public ResponseModel GetClassifyList(int id)
{
  if (id <= 0)
      return GetClassifyListAdmin(id);
  else
  return GetClassifyListUser(id);
}

数据层

public ResponseModel GetClassifyListAdmin()
{
var classifys = _db.Classify.OrderByDescending(c => c.Sort).ToList();
  var response = new ResponseModel { code = 200, result = "文档类别获取成功" };
  response.data = new List<ResClassifyModel>();
  foreach (var gclassify in classifys)
  {
      response.data.Add(new ResClassifyModel
      {
          Id = gclassify.Id,
          Name = gclassify.Name,
          Sort = gclassify.Sort,
          Remark = gclassify.Remark,
          CreateTime = gclassify.CreateTime.ToString("yyyy-MM-dd HH:mm:ss")
      });
  }
  return response;
}
public ResponseModel GetClassifyListUser()
{
  var classifys = _db.Classify.Where(r => _db.Roles.Where(r => r.UserId == id).Select(r => r.ClassifyId).Contains(r.Id)).OrderByDescending(c => c.Sort).ToList();
  var response = new ResponseModel { code = 200, result = "文档类别获取成功" };
  response.data = new List<ResClassifyModel>();
  foreach (var gclassify in classifys)
  {
      response.data.Add(new ResClassifyModel
      {
          Id = gclassify.Id,
          Name = gclassify.Name,
          Sort = gclassify.Sort,
          Remark = gclassify.Remark,
          CreateTime = gclassify.CreateTime.ToString("yyyy-MM-dd HH:mm:ss"),
      });
  }
  return response;
}
数据模型 DTO PO DAO

数据库操作层 使用DAO与数据库进行交互

业务层 以PO的形式与数据库操作层进行交互

Controller 以DTO的形式与业务层进行交互

可以在能进行拓展的时候容易进行拓展,方便维护

原有的数据类型返回

[HttpGet]
public JsonResult GetRole(int pageIndex, int pageSize, int classifyId, string keyword)
{
  var guser = _roleService.GetRoleList(pageSize, pageIndex, classifyId, keyword);
  return new JsonResult(guser.data);
}
public ResponseModel GetRoleList(int pageSize, int pageIndex, int ClassifyId, string? userName)
{
    var rolelist = _db.Roles.Where(p => p.ClassifyId == ClassifyId);
    if(userName != null)
    {
        rolelist = rolelist.Where(p => p.UserName.Contains(userName));
    }
    var pageData = rolelist.OrderByDescending(c => c.UserId).Skip(pageSize * (pageIndex - 1)).Take(pageSize).ToList();
    return  new ResponseModel { code = 200, result = "Role添加成功",data=pageData };
}

直接将DAO作为HTTP请求的返回结果,不方便进行业务的拓展维护,并且容易携带应该隐藏的数据带来不安全性

修改,转换成对应的数据模型进行返还

拆分方法 加强复用性 降低耦合性

提高代码的复用性,将重复使用的模块,和方法体较大的模块作为独立的方法抽取出来

public ResponseModel EditDocument(EditDocument document,int id)
{
    var edocument = _db.Documents.FirstOrDefault(c => c.Id == document.Id);
    if (edocument == null)
        return new ResponseModel { code = 0, result = "该文档不存在" };  
            
    // 抽取成 ConvertToDbModel    
    edocument.ClassifyId = document.ClassifyId;
    edocument.Title = document.Title;
    edocument.Remark = document.Remark;
    edocument.Contents = document.Contents;
    edocument.UpdateTime = DateTime.Now;
    edocument.Image = document.Image;
    edocument.Version = Guid.NewGuid();
        
    _db.Documents.Update(edocument);
    
}
public ResponseModel EditDocument(EditDocument document,int id)
{
    var edocument = _db.Documents.FirstOrDefault(c => c.Id == document.Id);
    if (edocument == null)
        return new ResponseModel { code = 0, result = "该文档不存在" };  
            
    var DbModel = ConvertToDbModel(document);
        
    _db.Documents.Update(edocument);
    
}
全局异常拦截器,避免出现异常导致项目整体无法继续进行,并记录错误日志
using Microsoft.AspNetCore.Mvc.Filters;
public class CustomExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        // 在异常发生时执行的逻辑
        var exception = context.Exception;
        // 可以记录日志、返回自定义错误页面或错误响应等
        context.ExceptionHandled = true; // 表示异常已被处理
    }
}
将RabbitMQ Consumer单独作为Windows Service项目进行部署

将 RabbitMQ Consumer作为单独Windows Service项目部署

方便进行开发,测试,部署,更好的维护

并且Windows Service更适合这种长时间运行的任务

RESTFUL风格

可以采用RESTFUL风格设计接口

对于数据加入校验,避免直接污染数据库

 

数据库

数据库设计无法精确实现业务需求

对于Document表格

-- hb.documents definition

CREATE TABLE `documents` (
  `Id` int NOT NULL AUTO_INCREMENT,
  `ClassifyId` int NOT NULL,
  `Title` varchar(1000) DEFAULT NULL,
  `Image` varchar(200) DEFAULT NULL,
  `Contents` text,
  `PublishDate` datetime DEFAULT NULL,
  `Remark` varchar(200) DEFAULT NULL,
  `SeeCount` int DEFAULT NULL,
  `Version` char(36) DEFAULT NULL,
  `UpdateTime` datetime DEFAULT NULL,
  PRIMARY KEY (`Id`),
  KEY `FK_Classify_Documents` (`ClassifyId`)
) ENGINE=InnoDB AUTO_INCREMENT=272 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

没有办法实现目录功能,当文档增多的时候没办法层级展示

加入ParentDocmentId

-- hb.documents definition

CREATE TABLE `documents` (
  `Id` int NOT NULL AUTO_INCREMENT,
  `ClassifyId` int NOT NULL,
  `ParentDocmentId` int NOT NULL,
  `Title` varchar(1000) DEFAULT NULL,
  `Image` varchar(200) DEFAULT NULL,
  `Contents` text,
  `PublishDate` datetime DEFAULT NULL,
  `Remark` varchar(200) DEFAULT NULL,
  `SeeCount` int DEFAULT NULL,
  `Version` char(36) DEFAULT NULL,
  `UpdateTime` datetime DEFAULT NULL,
  PRIMARY KEY (`Id`),
  KEY `FK_Classify_Documents` (`ClassifyId`)
) ENGINE=InnoDB AUTO_INCREMENT=272 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
关键字段逻辑删除

同时对于文档等关键的数据,应该加入isDel字段,并且在接口的实现上均采用逻辑删除,避免出现错误删除的情况

字段设计不太明确,不宜维护
-- hb.adminor definition

CREATE TABLE `adminor` (
  `Id` int NOT NULL,
  `Account` varchar(45) DEFAULT NULL,
  `Password` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `user` (
  `Id` int NOT NULL AUTO_INCREMENT,
  `Account` varchar(45) DEFAULT NULL,
  `Password` varchar(45) DEFAULT NULL,
  `Reg_Time` datetime DEFAULT NULL,
  `LastLogin_Time` datetime DEFAULT NULL,
  `isDel` tinyint DEFAULT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=353 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

原本使用 id<0 判断权限

对于是用户还是管理员,可以在数据表中添加Role字段,而且因为字段是相同的可以作为一张表存储

-- hb.`user` definition

CREATE TABLE `user` (
  `Id` int NOT NULL AUTO_INCREMENT,
  `Role` tinyint DEFAULT NULL,
  `Account` varchar(45) DEFAULT NULL,
  `Password` varchar(45) DEFAULT NULL,
  `Reg_Time` datetime DEFAULT NULL,
  `LastLogin_Time` datetime DEFAULT NULL,
  `isDel` tinyint DEFAULT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=353 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

 

 

前端

规范前端发送Ajax的逻辑,同样的请求没有必要的多次调用
function search() {
	pageIndex = 1;
	getUser(pageIndex, pageSize);
}
 function getUser(pageIndex, pageSize, keyword) {
	$.ajax({
        data: { pageIndex: pageIndex, pageSize: pageSize },
        success: function (result) {
        	currentPageCount = result.data.length;
            var totalPage = parseInt(result.total / pageSize + 1);
            pageNav.fn = function (p, pn) {
            	$("#pageinfo").text("当前页:" + p + " 总页: " + totalPage);
            	if(p != lastPage)
                	getUser(p,pageSize,keyword);
                };
                pageNav.go(pageIndex, totalPage);
                lastPage = pageIndex;
            }
        });
}

对分页进行监听,当产生监听是进行,ajax请求更换页面渲染数据,避免重复请求

CSS,JS提取

抽取成CSS,JS文件,可以重复使用,并且在Lay_out界面一次引入公共部分,使得代码更加简洁易懂

规范编码风格,不同的前端页面使用了不同框架,部分使用了原生HTML,部分使用了Vue
完善日志等执行信息,前端加入Console.log对于关键流程进行打印
对于数据进行校验,避免直接污染数据库,如果数据有误就不发送请求了
前端应该对用户的输入,增删查改等各个操作给出相应
都只针对数据量少的情况,当数据量大时会出现错误

都应该提供分页的功能

类似按钮部分功能使用新建页面,部分功能使用弹窗,应该统一风格

 

统一的规范

规范命名风格

 

数据增加时

Redis缓存热点数据
ElasticSearch快速搜索
部署为多体项目,数据库不再使用自增id,单独建立数据库存储全局自增id
对于冲突性高的业务采用分布式锁,冲突性低的业务使用乐观锁进行并发的考虑,乐观锁不建议使用
posted @ 2023-09-27 08:54  朦朦胧胧的月亮最美好  阅读(40)  评论(0)    收藏  举报