目录
仓储管理系统需求分析
一、登录模块
- 用户通过输入账号密码进行登录。
- 密码使用MD5加密方式进行存储与验证。
- 成功登录后,用户信息应保存至服务器session中,以保持用户的登录状态。
- 使用过滤器确保未登录用户无法访问受保护的页面,并自动跳转到登录页面。
二、菜单模块
- 使用layui mini前端框架动态加载数据库中的菜单数据。
- 菜单数据应展示在菜单栏中。
三、权限模块
- 通过角色与菜单的绑定,为不同角色显示相应权限的菜单。
- 根据用户的角色,控制其对不同功能和信息的访问权限。
四、耗材管理模块
- 支持耗材记录的录入,并支持使用Excel文件进行批量导入。
- 使用NPOI实现耗材数据的导出,支持将数据库中的耗材入库记录导出至Excel文件。
五、耗材出库审批模块
- 耗材出库需经过部门领导、仓库管理员的审批。
- 流程完成且得到批准后,耗材方可出库。
- 若审批过程中有任何一方驳回申请,则审批流程失败。
- 提供功能让用户查看流程的审批过程详情。
六、系统框架与架构
- 使用三层架构搭建系统:数据访问层(Dal)、业务逻辑层(Bll)、视图层(UI层,即web应用层)。
- 使用.NET Core内置的IOC容器,并通过构造函数注入对象。
- 使用异常过滤器,并使用log4net将异常日志写入文件。
七、文件(附件)模块
- 允许用户上传文件并保存至服务器磁盘。
- 将用户上传的文件信息保存至数据库中的文件表。
功能实现
登录管理模块:
-
前端部分:
- 当用户访问登录页面时,页面上会有一个表单,用户需要输入账号和密码。
- 使用JavaScript或类似的前端技术,当用户点击登录按钮或按下Enter键时,触发一个事件,该事件将账号和密码发送到后端。
-
后端部分:
- 使用ASP.NET Core或类似的框架,创建一个接收前端发送的账号和密码的API端点(例如,
POST /api/login
)。 - 在后端代码中,使用MD5或其他加密算法对密码进行加密。
- 验证用户提交的密码是否与数据库中存储的已加密的密码匹配。
- 如果匹配成功,创建一个服务器session并将用户信息(如用户ID、角色等)保存到session中。
- 使用ASP.NET Core或类似的框架,创建一个接收前端发送的账号和密码的API端点(例如,
-
Session管理:
- 使用ASP.NET Core的内置session管理功能,确保用户的登录状态得以维持。
- 在用户会话期间,可以在session中存储必要的信息,如用户角色、权限等。
-
过滤器:
- 使用ASP.NET Core的过滤器(如AuthorizeFilter)来确保只有已登录的用户才能访问某些受保护的页面或端点。
- 如果用户尝试访问受保护的资源而未登录,过滤器将拦截请求并自动重定向到登录页面。
ActionFilter过滤器,它的强大功能让我们可以用来实现登录过滤。通过使用ActionFilter,我们能够确保没有登录的用户无法访问项目中的其他页面或接口。在以前,我们使用session来判断用户是否已经登录,但使用ActionFilter进行登录过滤有着更多的优势。
使用ActionFilter可以更加清晰地表达我们的意图。在之前的session判断登录的方式中,我们需要手动在每个控制器方法中检查session,来判断用户是否登录。这不仅增加了大量的重复代码,而且如果我们需要调整登录策略,就需要修改每个控制器。使用ActionFilter过滤器,我们只需要在过滤器中完成这些检查,就可以确保所有未登录的用户都被阻止访问后续的页面或接口,无需在每个控制器中重复相同的逻辑。总的来说,通过学习并使用ActionFilter过滤器,我们能够实现更为复杂和灵活的登录过滤需求,使得我们的项目在安全性和可维护性方面得到进一步的提升。
菜单模块
-
数据库准备:
- 确保数据库中有一个存储菜单数据的表,该表至少应包含如下字段:菜单ID、菜单名称、URL路径等。
-
前端框架设置:
- 在前端项目中引入layui mini框架。
- 初始化layui,设置相关的配置参数。
-
动态加载数据:
- 使用layui提供的API或自定义的AJAX请求,向后端发送请求,请求中包含获取菜单数据的指令。
- 后端接收到请求后,从数据库中查询菜单数据,并将其格式化为JSON格式。
-
数据处理与展示:
- 前端接收到后端返回的JSON数据后,使用layui的模板引擎或其他方法,将数据渲染到菜单栏中。
- 可以为每个菜单项设置点击事件,当用户点击某个菜单项时,跳转到相应的URL路径。
/// <summary>
/// 获取页面初始化菜单
/// </summary>
/// <returns></returns>
public List<InitMenuInfoVo> GetInitMenuList()
{
#region 所有菜单列表
var allMenu = db.MenuInfos.Select(m => new InitMenuInfoVo
{
Id = m.Id,
Title = m.Title,
Target = m.Target,
Href = m.Href,
Icon = m.Icon,
Level = m.Level,
ParentId = m.ParentId,
}).ToList();
#endregion
//先找到最外层菜单
var parentMenuList = allMenu.Where(m => m.Level == 1).ToList();
//递归找子菜单
GetMenuChildren(parentMenuList, allMenu);
return parentMenuList;
}
权限模块:
-
定义角色与权限:
- 首先,明确系统中的各种角色,如管理员、普通用户、访客等。
- 为每个角色定义其可以访问的功能和信息,即权限。这些权限可以是菜单项、按钮、API接口等。
-
创建角色与权限的关联关系:
- 在数据库中设计角色和权限的关联表。通常,这种关联关系可以是多对多的,因为一个角色可能有多个权限,一个权限也可能被多个角色所拥有。
- 为每个角色分配相应的权限,并记录在关联表中。
-
设计菜单结构:
- 根据系统的功能需求,设计菜单的结构。这通常是一个树形或层次结构。
- 每个菜单项或按钮都应与一个或多个权限相关联。
-
用户登录与角色分配:
- 当用户登录系统时,验证其身份,并确定其所属的角色。
- 根据用户的角色,从数据库中获取该角色所拥有的所有权限。
-
动态生成菜单:
- 根据用户所拥有的权限,动态生成菜单。只显示用户有权限访问的菜单项和按钮。
- 对于没有权限的部分,可以隐藏或显示为不可用状态。
-
请求验证:
- 当用户尝试访问某个功能或信息时,系统应验证其是否有相应的权限。
- 可以通过检查用户的权限列表,确定其是否有权执行该操作。
除此之外,还实现了角色和用户之间的绑定功能,使得可以针对不同用户分配不同的角色,并且根据不同角色显示不同的菜单选项。这个功能对于保护系统的安全性非常重要,能够帮助我们更好地控制用户的访问权限。
耗材管理模块
-
需求分析:
- 识别系统用户:耗材管理员、普通员工(可能需要查看耗材信息)。
- 确定功能需求:耗材记录的录入、Excel批量导入、耗材数据导出至Excel。
-
数据库设计:
- 设计“耗材信息表”,包含字段如耗材ID、名称、类型、规格、库存数量、入库日期、供应商等。
- 若需要记录耗材的使用情况,可以设计“耗材使用记录表”。
-
界面设计:
- 设计耗材录入界面,允许管理员手动添加新的耗材记录。
- 设计批量导入界面,提供上传Excel文件的功能,解析并导入耗材数据。
- 设计耗材数据导出界面,允许用户选择导出条件,如日期范围、耗材类型等。
-
业务逻辑实现:
- 耗材录入:管理员在录入界面填写耗材详细信息,并提交至数据库。
- Excel批量导入:
- 管理员上传Excel文件。
- 系统使用NPOI或类似库解析Excel文件,读取耗材数据。
- 对读取的数据进行验证,确保数据格式正确且与数据库中的现有记录不冲突。
- 将验证通过的数据批量插入到“耗材信息表”中。
/// <summary> /// 文件上传 /// </summary> /// <param name="fs"></param> /// <param name="filePath"></param> /// <param name="userId"></param> /// <returns></returns> /// <exception cref="NotImplementedException"></exception> public bool UploadExcel(Stream fs, string fileName, string userId, out string msg) { //工作簿对象 IWorkbook workbook; //获取路径文件下的拓展名 string extensionName = Path.GetExtension(fileName); if (extensionName == ".xls") { //创建03版本的excel对象 workbook = new HSSFWorkbook(fs); } else { //创建07版本的excel对象 workbook = new XSSFWorkbook(fs); } //获取第一个sheet表格 ISheet sheet = workbook.GetSheetAt(0); //获取第一行数据 IRow rowhead = sheet.GetRow(0); //获取第一格子的数据 ICell cellhead1 = rowhead.GetCell(0); string reshead1 = cellhead1.ToString(); ICell cellhead2 = rowhead.GetCell(1); string reshead2 = cellhead2.ToString(); ICell cellhead3 = rowhead.GetCell(2); string reshead3 = cellhead3.ToString(); if (reshead1 != "商品名字" || reshead2 != "规格" || reshead3 != "数量") { msg = "导入模板错误"; return false; } //开启事务 using (var transaction = db.Database.BeginTransaction()) { int index = 0; for (int i = 1; i <= sheet.LastRowNum; i++) { IRow row = sheet.GetRow(i); //耗材名字 ICell cell1 = row.GetCell(0); string res1 = cell1.ToString(); //规格 ICell cell2 = row.GetCell(1); string res2 = cell2.ToString(); //数量 ICell cell3 = row.GetCell(2); string res3 = cell3.ToString(); int num = int.Parse(res3); //先查询耗材的信息 ConsumableInfo consumableInfo = db.ConsumableInfos.FirstOrDefault(c => c.ConsumableName == res1 && c.Specification == res2); if (consumableInfo == null) { msg = $"第{i + 1}行有误,请检查'{res1}','{res2}'"; //回滚事务 transaction.Rollback(); return false; } //验证库存是否正确 //查询当前耗材的记录(入库记录) var consumableRecordInfos = db.ConsumableRecords.Where(c => c.ConsumableId == consumableInfo.Id) .Select(c => new { c.Type, c.Num }).ToList(); int totalNum = 0; //计算当前耗材入库出库最后的总库存数量 foreach (var item in consumableRecordInfos) { if (item.Type == ConsumableRecordType.入库) //入库 { totalNum += item.Num; } else if (item.Type == ConsumableRecordType.出库) //出库 { totalNum -= item.Num; } } if (totalNum != consumableInfo.Num) { msg = $"{consumableInfo.ConsumableName}库存出错了,请联系管理员"; //回滚事务 transaction.Rollback(); return false; } //更新耗材信息库存 consumableInfo.Num += num; bool isSuccess1 = UpdateInfos(consumableInfo) >= 1; //耗材记录 ConsumableRecord consumableRecord = new ConsumableRecord() { ConsumableId = consumableInfo.Id, Creator = userId, Type = ConsumableRecordType.出库, //入库是1,出库是2 Num = num }; //添加耗材记录到数据库中 bool isSuccess2 = recordService.AddInfo(consumableRecord) >= 1; //成功就加一次 if (isSuccess1 && isSuccess2) { index++; } } if (index == sheet.LastRowNum) { msg = "成功"; //提交事务 transaction.Commit(); return true; } else { msg = "失败"; //回滚事务 transaction.Rollback(); return false; } } }
- 耗材数据导出:
- 用户选择导出条件。
- 系统根据条件从“耗材信息表”中查询符合条件的记录。
- 使用NPOI库创建Excel文件,将查询结果按照预定的格式写入Excel。
- 提供下载链接,允许用户下载生成的Excel文件。
/// <summary> /// 文件下载/导出 /// </summary> /// <returns></returns> public byte[] DonwLoadExcel() { { string path = @"D:\test\demo.xlsx"; FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write); //耗材名字 耗材描述 耗材类别 规格 数量 单位 价格 //工作簿对象 IWorkbook workbook = new XSSFWorkbook(); //创建sheet表 ISheet sheet = workbook.CreateSheet(); #region 表头 //创建表头行 IRow rowHead = sheet.CreateRow(0); //创建格子1 ICell cellHead1 = rowHead.CreateCell(0); //给格子写内容 cellHead1.SetCellValue("耗材名字"); //创建格子2 ICell cellHead2 = rowHead.CreateCell(1); //给格子写内容 cellHead2.SetCellValue("耗材描述"); //创建格子2 ICell cellHead3 = rowHead.CreateCell(2); //给格子写内容 cellHead3.SetCellValue("耗材类别"); //创建格子2 ICell cellHead4 = rowHead.CreateCell(3); //给格子写内容 cellHead4.SetCellValue("规格"); //创建格子2 ICell cellHead5 = rowHead.CreateCell(4); //给格子写内容 cellHead5.SetCellValue("数量"); //创建格子2 ICell cellHead6 = rowHead.CreateCell(5); //给格子写内容 cellHead6.SetCellValue("单位"); //创建格子2 ICell cellHead7 = rowHead.CreateCell(6); //给格子写内容 cellHead7.SetCellValue("价格"); #endregion //查询数据库 var consumableInfos = (from c in db.ConsumableInfos.Where(c => !c.IsDelete) join ca in db.Categories on c.CategoryId equals ca.Id select new { c.ConsumableName, c.Description, ca.CategoryName, c.Specification, c.Num, c.Unit, c.Money }).ToList(); for (int i = 0; i < consumableInfos.Count; i++) { //创建行 IRow row = sheet.CreateRow(i + 1); //创建格子1 ICell cell1 = row.CreateCell(0); //给格子写内容 cell1.SetCellValue(consumableInfos[i].ConsumableName); //创建格子2 ICell cell2 = row.CreateCell(1); //给格子写内容 cell2.SetCellValue(consumableInfos[i].Description); //创建格子2 ICell cell3 = row.CreateCell(2); //给格子写内容 cell3.SetCellValue(consumableInfos[i].CategoryName); //创建格子2 ICell cell4 = row.CreateCell(3); //给格子写内容 cell4.SetCellValue(consumableInfos[i].Specification); //创建格子2 ICell cell5 = row.CreateCell(4); //给格子写内容 cell5.SetCellValue((double)consumableInfos[i].Num); //创建格子2 ICell cell6 = row.CreateCell(5); //给格子写内容 cell6.SetCellValue(consumableInfos[i].Unit); //创建格子2 ICell cell7 = row.CreateCell(6); //给格子写内容 cell7.SetCellValue((double)consumableInfos[i].Money); } //把excel内容写进文件流里面 workbook.Write(fs); //读流 去读已经生成好的excel文件,因为上面的流已经被Write方法关闭了 FileStream readStream = new FileStream(path, FileMode.Open, FileAccess.Read); //负责装文件的二进制数组 byte[] data = new byte[readStream.Length]; //把文件流中的内容读取到data数组中 readStream.Read(data, 0, (int)readStream.Length); return data; } }
-
安全性与权限控制:
- 确保系统的安全性,如使用HTTPS协议、对敏感数据进行加密存储和传输等。
- 实现用户角色和权限控制,确保只有管理员才能进行耗材的录入和导出操作。
耗材出库审批模块
-
需求分析:
- 识别系统用户:部门领导、仓库管理员、普通员工(提交出库申请)。
- 确定功能需求:出库申请、审批流程、审批结果通知、查看审批详情。
-
数据库设计:
- 设计相关表结构,如“耗材库存表”、“出库申请表”、“审批记录表”等。
- 在“出库申请表”中,包含字段如申请ID、申请人、申请时间、申请状态(待审批、已批准、已驳回)等。
- 在“审批记录表”中,记录每次审批的操作,包括审批人、审批时间、审批结果等。
-
界面设计:
- 设计出库申请的界面,允许员工填写出库耗材的详细信息并提交申请。
- 设计审批界面,供部门领导和仓库管理员进行审批操作。
- 设计查看审批详情的界面,方便用户追踪审批过程。
-
业务逻辑实现:
- 出库申请:员工填写出库申请,包括耗材信息、数量等,并提交至系统。
- 审批流程:
- 系统自动通知部门领导有待审批的出库申请。
- 部门领导审批:查看申请详情,选择批准或驳回,并填写审批意见。系统更新申请状态并通知仓库管理员。
- 仓库管理员审批:同样查看申请详情,进行批准或驳回操作。系统更新申请状态。
- 审批结果通知:根据最终审批结果,系统自动通知申请人审批结果。
- 查看审批详情:提供功能让用户(包括申请人、部门领导、仓库管理员)查看审批过程详情,包括每次审批的时间、审批人、审批意见等。
-
安全性与权限控制:
- 确保系统的安全性,如使用HTTPS协议、对敏感数据进行加密存储和传输等。
- 实现用户角色和权限控制,确保不同用户只能进行其角色允许的操作。例如,只有部门领导和仓库管理员才能进行审批操作。
部分代码:
public int UpdateInfos(string wf_stepId, InstanceStepStatusEnum reviewStatus, string reviewReason, string userId,int outNum, out string msg)
{
//查询当前用户的角色
var roleNames = (from userRole in db.RUserInfoRoleInfos.Where(r => r.UserId == userId)
join role in db.RoleInfos.Where(m => !m.IsDelete)
on userRole.RoleId equals role.Id
select role.RoleName).ToList();
//查询仓库管理员的userid
List<string> ckUserIds = (from r in db.RoleInfos.Where(r => r.RoleName == "仓库管理员" && !r.IsDelete)
join ru in db.RUserInfoRoleInfos
on r.Id equals ru.RoleId
select ru.UserId).ToList();
#region 查找当前待审核的流程步骤信息,并修改状态
WorkFlowInstanceStep entity = db.WorkFlowInstanceSteps.FirstOrDefault(u => u.Id == wf_stepId);
if (entity == null)
{
msg = "数据有误";
return -1;
}
if (entity.ReviewStatus != InstanceStepStatusEnum.审核中)
{
msg = "请勿重复审核";
return -1;
}
//判断当前步骤审核人是否为当前登录人
if (entity.ReviewerId != userId)
{
msg = "你无权操作";
return -1;
}
//修改审核状态/理由
entity.ReviewStatus = reviewStatus;
entity.ReviewReason = reviewReason;
entity.ReviewTime = DateTime.Now;
#endregion
if (roleNames.Contains("部门领导"))
{
if (reviewStatus == InstanceStepStatusEnum.同意)
{
//领导审核后,给仓库管理员生成审核步骤信息
#region 有多个仓库管理员
if (ckUserIds == null || ckUserIds.Count == 0)
{
msg = "未找到仓库管理员";
return -1;
}
//开启事务保存领导审核的流程步骤信息和遍历给所有仓库管理员生成工作步骤数据
using (var transactin = db.Database.BeginTransaction())
{
int index = 0;
//遍历给所有仓库管理员生成工作步骤数据
foreach (var ckUserId in ckUserIds)
{
WorkFlowInstanceStep ckWorkStep = new WorkFlowInstanceStep
{
Id = Guid.NewGuid().ToString(),
CreateTime = DateTime.Now,
InstanceId = entity.InstanceId,
ReviewerId = ckUserId,
ReviewStatus = InstanceStepStatusEnum.审核中,
BeforeStepId = entity.Id,
};
db.WorkFlowInstanceSteps.Add(ckWorkStep);
if (db.SaveChanges() >= 1) { index++; }
}
//保存领导审核的流程步骤信息
db.WorkFlowInstanceSteps.Update(entity);
var isSuccess = db.SaveChanges() >= 1;
if (isSuccess && ckUserIds.Count == index)
{
transactin.Commit();
msg = "操作成功";
return 0;
}
else
{
transactin.Rollback();
msg = "操作失败";
return -1;
}
}
#endregion
}
else if (reviewStatus == InstanceStepStatusEnum.驳回)
{
WorkFlowInstance workFlowInstance = db.WorkFlowInstances.FirstOrDefault(i => i.Id == entity.InstanceId);
if (workFlowInstance == null)
{
msg = "实例不存在";
return -1;
}
//2、申请人创建一条工作流步骤数据
WorkFlowInstanceStep workFlowInstanceStep = new WorkFlowInstanceStep()
{
Id = Guid.NewGuid().ToString(),
CreateTime = DateTime.Now,
InstanceId = entity.InstanceId,
ReviewerId = workFlowInstance.Creator,
ReviewStatus = InstanceStepStatusEnum.审核中,
BeforeStepId = entity.Id //仓库管理的上一个步骤id,就是当前领导审核的这个步骤id
};
using (var transaction = db.Database.BeginTransaction())
{
//更新步骤信息
db.WorkFlowInstanceSteps.Update(entity);
bool isSuccess1 = db.SaveChanges() > 0;
//添加审核人步骤
db.WorkFlowInstanceSteps.Add(workFlowInstanceStep);
bool isSuccess2 = db.SaveChanges() > 0;
if (isSuccess1 && isSuccess2)
{
transaction.Commit();
msg = "操作成功";
return 0;
}
else
{
transaction.Rollback();
msg = "操作失败";
return -1;
}
}
}
else
{
msg = "操作失败";
return -1;
}
}
else if (roleNames.Contains("仓库管理员"))
{
//查询出审核人为仓库管理员的所有待审核的当前实例步骤,排除掉当前审核的实例步骤
var otherWorkSteps = db.WorkFlowInstanceSteps.Where(m => m.InstanceId == entity.InstanceId
&& ckUserIds.Contains(m.ReviewerId) && m.Id != wf_stepId).ToList();
if (entity.ReviewStatus == InstanceStepStatusEnum.同意)
{
#region 仓库管理员同意
#region 结束工作流实例状态
//根据当前工作流实例步骤
WorkFlowInstance workFlowInstance = db.WorkFlowInstances.FirstOrDefault(m => m.Id == entity.InstanceId);
if (workFlowInstance == null)
{
msg = "实例不存在";
return -1;
}
workFlowInstance.Status = WorkFlow_InstanceStatusEnum.结束;
#endregion
#region 修改耗材库存
ConsumableInfo consumableInfo = db.ConsumableInfos.FirstOrDefault(m => m.Id == workFlowInstance.OutGoodsId && !m.IsDelete);
if (consumableInfo == null)
{
msg = "耗材不存在";
return -1;
}
consumableInfo.Num -= workFlowInstance.OutNum;
#endregion
#region 创建耗材的导出记录
ConsumableRecord consumableRecord = new ConsumableRecord()
{
Id = Guid.NewGuid().ToString(),
ConsumableId = consumableInfo.Id,
CreateTime = DateTime.Now,
Creator = userId,
Num = (int)workFlowInstance.OutNum,
Type = ConsumableRecordType.出库
};
#endregion
#region 开启事务更新到数据库
using (var transaction = db.Database.BeginTransaction())
{
//保存当前审核的工作流步骤信息(审核通过/驳回)
db.WorkFlowInstanceSteps.Update(entity);
bool isSuccess1 = db.SaveChanges() > 0;
//更新实例状态为结束
db.WorkFlowInstances.Update(workFlowInstance);
bool isSuccess2 = db.SaveChanges() > 0;
//更新耗材库存
db.ConsumableInfos.Update(consumableInfo);
bool isSuccess3 = db.SaveChanges() > 0;
//生成耗材导出记录
db.ConsumableRecords.Add(consumableRecord);
bool isSuccess4 = db.SaveChanges() > 0;
//修改其他仓库管理员的工作流实例步骤状态为 已被他人审批
int index = 0;
foreach (var otherWorkStep in otherWorkSteps)
{
otherWorkStep.ReviewStatus = InstanceStepStatusEnum.已被他人审批;
db.WorkFlowInstanceSteps.Update(otherWorkStep);
if (db.SaveChanges() > 0) { index++; }
}
if (isSuccess1 && isSuccess2 && isSuccess3 && isSuccess4 && index == otherWorkSteps.Count)
{
transaction.Commit();
msg = "操作成功";
return 0;
}
else
{
transaction.Rollback();
msg = "操作失败";
return -1;
}
}
#endregion
#endregion
}
else if (entity.ReviewStatus == InstanceStepStatusEnum.驳回)
{
//查询上一个步骤的信息 ---审核人(领导)
WorkFlowInstanceStep beforeStep = db.WorkFlowInstanceSteps.FirstOrDefault(m => m.Id == entity.BeforeStepId);
if (beforeStep != null)
{
msg = "系统出错,上一条步骤不存在";
return -1;
}
//给领导增加一条新的步骤数据
WorkFlowInstanceStep workFlow_InstanceStep = new WorkFlowInstanceStep()
{
Id = Guid.NewGuid().ToString(),
CreateTime = DateTime.Now,
InstanceId = entity.InstanceId,
ReviewerId = beforeStep.ReviewerId, //领导id
ReviewStatus = InstanceStepStatusEnum.审核中,
BeforeStepId = entity.Id,
};
//开启事务将当前仓库管理员的驳回,和修改其他仓库管理员的步骤信息
using (var transaction = db.Database.BeginTransaction())
{
db.WorkFlowInstanceSteps.Update(entity);
var isSuccess1 = db.SaveChanges();//保存管理员驳回
db.WorkFlowInstanceSteps.Add(workFlow_InstanceStep);
var isSuccess2 = db.SaveChanges();//给领导添加步骤
}
msg = "操作失败";
return -1;
}
msg = "操作失败";
return -1;
}
else
{
//申请人操作(修改数量后再次申请)
//修改工作流实例的申请物品数量
WorkFlowInstance workFlowInstance = db.WorkFlowInstances.FirstOrDefault(i => entity.InstanceId == i.Id);
if (workFlowInstance == null)
{
msg = "实例未找到";
return -1;
}
workFlowInstance.OutNum = outNum;
//查询上一个步骤的信息,就知道那个步骤的审核人(领导)
WorkFlowInstanceStep beforeStep = db.WorkFlowInstanceSteps.FirstOrDefault(w => w.Id == entity.BeforeStepId);
if (beforeStep == null)
{
msg = "步骤有误";
return -1;
}
//申请人修改数量后再次申请是给领导加一条新的步骤数据
WorkFlowInstanceStep workFlowInstanceStep = new WorkFlowInstanceStep()
{
Id = Guid.NewGuid().ToString(),
CreateTime = DateTime.Now,
InstanceId = entity.InstanceId,
ReviewerId = beforeStep.ReviewerId, //领导id
ReviewStatus = InstanceStepStatusEnum.审核中,
BeforeStepId = entity.Id,
};
using (var transaction = db.Database.BeginTransaction())
{
//更新实例的出库数量
db.WorkFlowInstances.Update(workFlowInstance);
bool isSuccess1 = db.SaveChanges() > 0;
//更新步骤信息
db.WorkFlowInstanceSteps.Update(entity);
bool isSuccess2 = db.SaveChanges() > 0;
//添加领导步骤
db.WorkFlowInstanceSteps.Add(workFlowInstanceStep);
bool isSuccess3 = db.SaveChanges() > 0;
if (isSuccess1 && isSuccess2 && isSuccess3)
{
transaction.Commit();
msg = "成功";
return 0;
}
else
{
transaction.Rollback();
msg = "失败";
return -1;
}
}
}
}
文件(附件)模块
-
需求分析:
- 识别系统用户:文件上传者、系统管理员。
- 确定功能需求:文件上传、文件信息保存至数据库、文件存储管理。
-
数据库设计:
- 设计“文件信息表”,包含字段如文件ID、文件名、文件类型、文件大小、上传时间、上传者ID、文件存储路径等。
-
文件存储设计:
- 确定服务器上的文件存储目录结构,例如按日期或用户ID进行分文件夹存储。
- 设置合适的文件和目录权限,确保文件的安全性。
-
界面设计:
- 设计文件上传界面,允许用户选择要上传的文件,并提供必要的文件信息(如文件名、描述等)。
- 设计文件管理界面,展示已上传的文件列表,包括文件名、上传时间等信息。
-
业务逻辑实现:
- 文件上传:
- 用户在上传界面选择文件并提交上传请求。
- 系统接收上传请求,处理文件数据,将文件保存到服务器指定的存储目录。
- 生成文件的唯一标识符(如UUID),作为文件ID。
- 文件信息保存至数据库:
- 提取上传文件的元数据(如文件名、类型、大小等)。
- 在“文件信息表”中插入一条新记录,包括文件ID、元数据、上传时间、上传者ID等。
- 文件管理:
- 提供文件管理界面,列出已上传的文件。
- 支持按条件筛选和搜索文件。
- 可选功能:支持文件的下载、删除等操作。
- 文件上传:
-
安全性与权限控制:
- 对上传的文件进行安全性检查,防止恶意文件的上传(如病毒扫描、文件类型限制等)。
- 实现用户角色和权限控制,确保只有授权用户才能进行文件的上传和管理操作。