分层架构系统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对于关键流程进行打印
对于数据进行校验,避免直接污染数据库,如果数据有误就不发送请求了
前端应该对用户的输入,增删查改等各个操作给出相应
都只针对数据量少的情况,当数据量大时会出现错误
都应该提供分页的功能
类似按钮部分功能使用新建页面,部分功能使用弹窗,应该统一风格
统一的规范
规范命名风格