公司组织机构是一个树形架构,先前新加盟公司时都是总部直接添加在某个子公司下,因审计须要,要求经过下面公司申请,逐个角色处理来完成新公司的开通,开发任务最后落到我这里,时间紧,任务重,先前也没接触多少审批流程的开发,好在咱们的系统是基于通用权限管理系统的底层来作的开发,角色,权限控制已没什么问题,并且底层也集成有一个审批流程引擎组件,只是先前没多少人使用过,经过与吉日嘎拉老师的沟通,大体了解了这个组件的思想,就像其它系统调用权限功能同样,我只须要完成业务功能的开发,实现审批流的接口便可,通过将近3周的开发,终于完成了新公司建立的审批流程。下面整理一下利用通用审批流程组件进行审批流开发的一些经验:前端
根据全国省份划分,每一个省份设有专人审批下级公司申请建立公司的单据,其中某些省份须要通过片区经理再审核,以下图java
注:其中的"网点管理员"是由系统默认的各个公司的在全部系统中具备最大权限的人员,由其提交新开其下级公司的申请,片区文员是各个省份的负责审批该省全部公司提交新开公司的申请单据的人员。web
从图上能够看到,新开一个公司根据告诉管理员所在省份的不一样会有两个流程中的其中一个处理(流程入口条件),所以咱们须要建立两个审批流。数据库
在审核过程当中,审核状态有待审、经过、退回、完成四种状态,这个是很好理解的。后端
经过分析流程,在通用审批组件中建立对应的流程:两个审批流程。数组
已经建立好的审批流程,打开其中一个看看,以下图:安全
下图是其中一个流程的定义,能够看到流程与表、程序集、类之间的关系,在具体实现时要继承流程处理接口实现服务器
审核步骤是须要先定义的:session
定义审核步骤架构
审核步骤先建立好,建立审核流程时选择其中的几个审核步骤构成一个流程,建立流程过程当中,可设置在流程处理过程当中可编辑的字段,这一块我没使用,字段控制我直接在权限中处理了。
根据上面的流程,我在系统中建立了对应的角色,由于直接是在通用权限系统中开发的,角色配置起来就很是容易了。
在子系统中建立的用于流程审批的角色
上面的角色建立好之后,就须要向角色中添加人员了,根据提供的人员配置到各个角色中,操做也是很简单的
选中角色,点击成员,向角色中添加成员便可。
经过对整个处理流程的分析,定义了以上与审批流程有关系的权限菜单。注意其中将所有要审核的字段也做为菜单项控制了,这样在分配角色权限时处理方便,每一个角色可以修改哪些字段,其中菜单的编号也进行了一些处理,按照实体的名字来命名,这样先后台判断权限时也很方便。
角色及菜单建立完毕后,能够配置各个角色具备的菜单权限了,以下截图:
如上图,是配置片区审核具备的菜单权限,这样片区审核这个角色里的人进入系统就有了对应的权限,只需勾选上要分配的权限菜单便可。
===================================
至此,审批流程、角色、菜单、角色人员,角色菜单权限都配置完毕了,接下来开始具体审批业务功能的开发,能够看到,有了这个通用权限及审批流程的底层管理系统,咱们只需关注业务功能便可。
===================================
上图是公司管理员进入申请建立新公司的界面,能够看到,在界面上控制了哪些项目是必填的,哪些项目是没有权限填写的。填写完毕,能够在“申请进度查询”中看到流程进入哪一个阶段。
上图是其中一个角色进入审核界面的显示内容,点击审核,就能够处理:经过或者退回
点击上面的提交或者退回,就能够对这个审批单据进行处理了
八、审批流程接口实现
底层中处理工做流的业务类
上图是开始申请的处理流程:将申请单据保存起来,启动审批流程,这是在数据库中存储的结果以下,能够看到当前申请的流程要走的步骤:按ACTIVITYID的顺序。
审批过程当中的拒绝操做:再也不保存修改记录,退回到上一步。
审批过程当中经过审批的处理:能够保存当前人员对单据的修改,同时提交到下一步来处理,若是是最后一步,审核经过时,当前审核单据的状态将变为完成。
能够看到,在处理审批流程时,后台审核部分只须要调用底层接口便可,开发人员只需关注业务功能开发,下面把主要的底层接口提供出来,供参考:
权限判断,实现先后端输入项的验证,前端若是有权限,对应的文本框处于可编辑状态,不然不可编辑,后端也会再次验证,有权限必须填写的都须要通过后端再次验证,这样安全问题就能够解决了。
using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Net; using System.Text; using System.Web; using System.Web.Script.Serialization; namespace Infrastructure { using DotNet.Business; using DotNet.Model; using DotNet.Utilities; /// <summary> /// /// 修改纪录 /// /// 2016-03-02 版本:1.0 宋彪 修改文件。 /// /// <author> /// <name>宋彪</name> /// <date>2016-03-02</date> /// </author> /// </summary> public static class WorkFlowBaseUserInfoExt { /// <summary> /// 存储用户权限菜单的sessionKey /// </summary> private static string permissionKey = "UserPermissionListSession"; /// <summary> /// 获取存储在session中的用户权限菜单 /// </summary> /// <param name="userInfo"></param> /// <param name="userId"></param> /// <param name="systemCode"></param> /// <returns></returns> public static List<BaseModuleEntity> GetSessionPermissionList(this BaseUserInfo userInfo, string userId, string systemCode = "Base") { if (HttpContext.Current.Session[permissionKey] == null) { //List<BaseModuleEntity> list = PermissionUtilities.GetPermissionList(userInfo, userId, systemCode); List<BaseModuleEntity> returnResult = new List<BaseModuleEntity>(); try { string url = System.Configuration.ConfigurationManager.AppSettings["LogonService"] + "/PermissionService.ashx"; userInfo.Id = userId; userInfo.SystemCode = systemCode; // 忽略超级管理员 由于超级管理员有所有权限 userInfo.IsAdministrator = false; WebClient webClient = new WebClient(); NameValueCollection postValues = new NameValueCollection(); postValues.Add("Function", "GetPermissionList"); postValues.Add("UserInfo", userInfo.Serialize()); postValues.Add("SystemCode", systemCode); postValues.Add("fromCache", false.ToString()); // 向服务器发送POST数据 byte[] responseArray = webClient.UploadValues(url, postValues); string response = Encoding.UTF8.GetString(responseArray); if (!string.IsNullOrEmpty(response)) { JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer(); returnResult = javaScriptSerializer.Deserialize<List<BaseModuleEntity>>(response); returnResult = returnResult.OrderBy(t => t.SortCode).ToList(); HttpContext.Current.Session[permissionKey] = returnResult; } } catch (Exception ex) { Log.Write(ex.ToString()); } return returnResult; } return (List<BaseModuleEntity>)HttpContext.Current.Session[permissionKey]; } /// <summary> /// 移除session中的用户权限 /// </summary> /// <param name="userInfo"></param> /// <param name="userId"></param> /// <param name="systemCode"></param> public static void RemoveSessionPermissionList(this BaseUserInfo userInfo, string userId, string systemCode = "Base") { if (HttpContext.Current.Session[permissionKey] != null) { HttpContext.Current.Session.Remove(permissionKey); } } /// <summary> /// 判断用户是否有某个菜单的权限,从存储再session中的获取菜单 /// </summary> /// <returns></returns> public static bool IsAuthorizedByCode(this BaseUserInfo userInfo, string code, string systemCode = "Base") { if (string.IsNullOrWhiteSpace(code)) { return false; } List<BaseModuleEntity> list = GetSessionPermissionList(userInfo, userInfo.Id, systemCode); if (list != null && list.Any()) { return list.Any(t => string.Equals(t.Code, code, StringComparison.OrdinalIgnoreCase)); } return false; } /// <summary> /// 经过权限判断渲染用户在前端是否能够输入 /// </summary> /// <param name="userInfo"></param> /// <param name="code"></param> /// <param name="validateInfo"></param> /// <param name="errorMsg"></param> /// <param name="quiFormStyle"></param> /// <param name="modelField"></param> /// <returns></returns> public static string GetAttributesByCode(this BaseUserInfo userInfo, string code, string validateInfo, string errorMsg, string quiFormStyle = null,string modelField=null) { string result = string.Empty; string classInfo = string.Empty; if (!string.IsNullOrWhiteSpace(quiFormStyle)) { classInfo += quiFormStyle; } if (userInfo.IsAuthorizedByCode(code)) { if (!string.IsNullOrWhiteSpace(validateInfo)) { classInfo += " " + validateInfo; } result = " class=\"" + classInfo + "\" "; ; if (!string.IsNullOrWhiteSpace(errorMsg)) { result += " error=\"" + errorMsg + "\" "; } } else { result = " class=\"" + classInfo + "\" "; result += " disabled=\"disabled\" "; ; } if (!string.IsNullOrWhiteSpace(modelField)) { code = modelField; } return result += " name=\"" + code + "\" "; ; } /// <summary> /// 经过权限判断渲染用户在前端是否能够输入 /// </summary> /// <param name="userInfo"></param> /// <param name="code"></param> /// <param name="required"></param> /// <param name="errorMsg"></param> /// <returns></returns> public static string GetAttributesByCode(this BaseUserInfo userInfo, string code, bool required, string errorMsg, string modelField = null) { string result = string.Empty; if (userInfo.IsAuthorizedByCode(code)) { if (required) { result += " class=\"validate[required]\" "; } if (required && !string.IsNullOrWhiteSpace(errorMsg)) { result += " error=\"" + errorMsg + "\" "; } } else { result += " disabled=\"disabled\" "; ; } if (!string.IsNullOrWhiteSpace(modelField)) { code = modelField; } return result += " name=\"" + code + "\" "; ; } } }
前台调用权限判断实现输入项验证
<input <%:Html.Raw(Utils.UserInfo.GetAttributesByCode("siteAudit.UserRealName",true,"请输入真实姓名")) %> maxlength="20" type="text" style="width: 150px;" value="<%: siteAudit.UserRealName %>" />
若是没有权限,输入会增长disabled属性。
单据处理过程当中调用的底层工做流的方法
//----------------------------------------------------------------- // All Rights Reserved , Copyright (C) 2016 , Hairihan TECH, Ltd. //----------------------------------------------------------------- using System; namespace DotNet.Business { using DotNet.IService; using DotNet.Model; using DotNet.Utilities; /// <summary> /// BaseWorkFlowCurrentManager /// 流程管理. /// /// 修改记录 /// /// 2012.04.04 版本:1.0 JiRiGaLa 。 /// /// <author> /// <name>JiRiGaLa</name> /// <date>2012.04.04</date> /// </author> /// </summary> public partial class BaseWorkFlowCurrentManager : BaseManager, IBaseManager { /// <summary> /// (点批量经过时)当批量审核经过时 /// </summary> /// <param name="currentIds">审批流当前主键数组</param> /// <param name="auditIdea">批示</param> /// <returns>成功失败</returns> public int AutoAuditPass(string[] currentIds, string auditIdea) { int result = 0; for (int i = 0; i < currentIds.Length; i++) { result += this.AutoAuditPass(currentIds[i], auditIdea); } return result; } /// <summary> /// 审批经过 /// </summary> /// <param name="currentId"></param> /// <param name="auditIdea"></param> /// <returns></returns> public int AutoAuditPass(string currentId, string auditIdea) { IWorkFlowManager workFlowManager = this.GetWorkFlowManager(currentId); return AutoAuditPass(workFlowManager, currentId, auditIdea); } /// <summary> /// (点经过时)当审核经过时 /// </summary> /// <param name="workFlowManager"></param> /// <param name="currentId">审批流当前主键</param> /// <param name="auditIdea">批示</param> /// <returns>成功失败</returns> public int AutoAuditPass(IWorkFlowManager workFlowManager, string currentId, string auditIdea) { int result = 0; // 这里要加锁,防止并发提交 // 这里用锁的机制,提升并发控制能力 lock (WorkFlowCurrentLock) { // using (TransactionScope transactionScope = new TransactionScope()) //{ //try //{ // 1. 先得到如今的状态?当前的工做流主键、当前的审核步骤主键? BaseWorkFlowCurrentEntity workFlowCurrentEntity = this.GetObject(currentId); // 只有待审核状态的,才能够经过,被退回的也能够从新提交 if (!(workFlowCurrentEntity.AuditStatus.Equals(AuditStatus.StartAudit.ToString()) || workFlowCurrentEntity.AuditStatus.Equals(AuditStatus.AuditPass.ToString()) || workFlowCurrentEntity.AuditStatus.Equals(AuditStatus.WaitForAudit.ToString()) || workFlowCurrentEntity.AuditStatus.Equals(AuditStatus.AuditReject.ToString()) )) { return result; } // 是否是给当前人审核的,或者当前人在委托的人? if (!string.IsNullOrEmpty(workFlowCurrentEntity.ToUserId)) { if (!(workFlowCurrentEntity.ToUserId.ToString().Equals(this.UserInfo.Id) || workFlowCurrentEntity.ToUserId.IndexOf(this.UserInfo.Id) >= 0 // || workFlowCurrentEntity.ToUserId.ToString().Equals(this.UserInfo.TargetUserId) )) { return result; } } // 获取下一步是谁审核。 BaseWorkFlowStepEntity workFlowStepEntity = this.GetNextWorkFlowStep(workFlowCurrentEntity); // 3. 进行下一步流转?转给角色?仍是传给用户? if (workFlowStepEntity == null || workFlowStepEntity.Id == null) { // 4. 若没下一步了,那就得结束流程了?审核结束了 result = this.AuditComplete(workFlowManager, currentId, auditIdea); } else { // 审核进入下一步 // 当前是哪一个步骤? // 4. 是否已经在工做流里了? // 5. 若已经在工做流里了,那就进行更新操做? if (!string.IsNullOrEmpty(workFlowStepEntity.AuditUserId)) { // 如果任意人能够审核的,须要进行一次人工选任的工做 if (workFlowStepEntity.AuditUserId.Equals("Anyone")) { return result; } } // 按用户审核,审核经过 result = AuditPass(workFlowManager, currentId, auditIdea, workFlowStepEntity); } //} //catch (System.Exception ex) //{ // 在本地记录异常 // FileUtil.WriteException(UserInfo, ex); //} //finally //{ //} // transactionScope.Complete(); //} } return result; } #region public int AuditPass(IWorkFlowManager workFlowManager, string currentId, string auditIdea, BaseWorkFlowStepEntity workFlowStepEntity) 审核经过 /// <summary> /// 审核经过 /// </summary> /// <param name="id">当前主键</param> /// <param name="auditIdea">批示</param> /// <returns>影响行数</returns> public int AuditPass(IWorkFlowManager workFlowManager, string currentId, string auditIdea, BaseWorkFlowStepEntity workFlowStepEntity) { int result = 0; // 进行更新操做 result = this.StepAuditPass(currentId, auditIdea, workFlowStepEntity); if (result == 0) { // 数据可能被删除 this.StatusCode = Status.ErrorDeleted.ToString(); } BaseWorkFlowCurrentEntity workFlowCurrentEntity = this.GetObject(currentId); // 发送提醒信息 if (workFlowManager != null) { if (!string.IsNullOrEmpty(workFlowStepEntity.AuditUserId)) { workFlowStepEntity.AuditDepartmentId = null; workFlowStepEntity.AuditRoleId = null; } workFlowManager.OnAutoAuditPass(workFlowCurrentEntity); workFlowManager.SendRemindMessage(workFlowCurrentEntity, AuditStatus.AuditPass, new string[] { workFlowCurrentEntity.CreateUserId, workFlowStepEntity.AuditUserId }, workFlowStepEntity.AuditDepartmentId, workFlowStepEntity.AuditRoleId); } this.StatusMessage = this.GetStateMessage(this.StatusCode); return result; } #endregion #region private int StepAuditPass(string currentId, string auditIdea, BaseWorkFlowStepEntity toStepEntity, BaseWorkFlowAuditInfo workFlowAuditInfo = null) 审核经过(不须要再发给别人了是完成审批了) /// <summary> /// 审核经过(不须要再发给别人了是完成审批了) /// </summary> /// <param name="currentId">当前主键</param> /// <param name="auditIdea">批示</param> /// <param name="toStepEntity">审核到第几步</param> /// <param name="workFlowAuditInfo">当前审核人信息</param> /// <returns>影响行数</returns> private int StepAuditPass(string currentId, string auditIdea, BaseWorkFlowStepEntity toStepEntity, BaseWorkFlowAuditInfo workFlowAuditInfo = null) { BaseWorkFlowCurrentEntity workFlowCurrentEntity = this.GetObject(currentId); // 初始化审核信息,这里是显示当前审核人,能够不是当前操做员的功能 if (workFlowAuditInfo == null) { workFlowAuditInfo = new BaseWorkFlowAuditInfo(this.UserInfo); workFlowAuditInfo.AuditIdea = auditIdea; workFlowAuditInfo.AuditDate = DateTime.Now; workFlowAuditInfo.AuditUserId = this.UserInfo.Id; workFlowAuditInfo.AuditUserRealName = this.UserInfo.RealName; workFlowAuditInfo.AuditStatus = AuditStatus.AuditPass.ToString(); workFlowAuditInfo.AuditStatusName = AuditStatus.AuditPass.ToDescription(); } else { workFlowAuditInfo.AuditIdea = auditIdea; } // 审核意见是什么? workFlowCurrentEntity.AuditIdea = workFlowAuditInfo.AuditIdea; // 1.记录当前的审核时间、审核人信息 // 什么时间审核的? workFlowCurrentEntity.AuditDate = workFlowAuditInfo.AuditDate; // 审核的用户是谁? workFlowCurrentEntity.AuditUserId = workFlowAuditInfo.AuditUserId; // 审核人的姓名是谁? workFlowCurrentEntity.AuditUserRealName = workFlowAuditInfo.AuditUserRealName; // 审核状态是什么? workFlowCurrentEntity.AuditStatus = workFlowAuditInfo.AuditStatus; // 审核状态备注是什么? workFlowCurrentEntity.AuditStatusName = workFlowAuditInfo.AuditStatusName; if (!string.IsNullOrEmpty(workFlowCurrentEntity.ActivityFullName)) { workFlowCurrentEntity.Description = string.Format("从{0}提交到{1}{2}", workFlowCurrentEntity.ActivityFullName, toStepEntity.FullName, !string.IsNullOrEmpty(toStepEntity.Description) ? "," + toStepEntity.Description : string.Empty); } workFlowCurrentEntity.ToUserId = toStepEntity.AuditUserId; workFlowCurrentEntity.ToUserRealName = toStepEntity.AuditUserRealName; workFlowCurrentEntity.ToRoleId = toStepEntity.AuditRoleId; workFlowCurrentEntity.ToRoleRealName = toStepEntity.AuditRoleRealName; workFlowCurrentEntity.ToDepartmentId = toStepEntity.AuditDepartmentId; workFlowCurrentEntity.ToDepartmentName = toStepEntity.AuditDepartmentName; // 2.记录审核日志 this.AddHistory(workFlowCurrentEntity); // 3.上一个审核结束了,新的审核又开始了,更新待审核状况 workFlowCurrentEntity.ActivityId = toStepEntity.ActivityId; workFlowCurrentEntity.ActivityCode = toStepEntity.Code; workFlowCurrentEntity.ActivityFullName = toStepEntity.FullName; workFlowCurrentEntity.ActivityType = toStepEntity.ActivityType; workFlowCurrentEntity.SortCode = toStepEntity.SortCode; return this.UpdateObject(workFlowCurrentEntity); } #endregion } }
审核须要实现的工做流接口
//----------------------------------------------------------------- // All Rights Reserved , Copyright (C) 2016 , Hairihan TECH, Ltd. //----------------------------------------------------------------- namespace DotNet.IService { using DotNet.Model; using DotNet.Utilities; /// <summary> /// IWorkFlowManager /// 可审批化的类接口定义 /// /// 修改记录 /// /// 2011.09.06 版本:1.0 JiRiGaLa 建立文件。 /// /// <author> /// <name>JiRiGaLa</name> /// <date>2011.09.06</date> /// </author> /// </summary> public interface IWorkFlowManager { string CurrentTableName { get; set; } IDbHelper GetDbHelper(); BaseUserInfo GetUserInfo(); void SetUserInfo(BaseUserInfo userInfo); /// <summary> /// 获取待审核单据的网址 /// </summary> /// <param name="currentId">工做流当前主键</param> /// <returns>获取网址</returns> string GetUrl(string currentId); /// <summary> /// 发送即时通信提醒 /// </summary> /// <param name="workFlowCurrentEntity">当前审核流实体信息</param> /// <param name="auditStatus">审核状态</param> /// <param name="userIds">发送给用户主键数组</param> /// <param name="organizeId">发送给部门主键数组</param> /// <param name="roleId">发送给角色主键数组</param> /// <returns>影响行数</returns> int SendRemindMessage(BaseWorkFlowCurrentEntity entity, AuditStatus auditStatus, string[] userIds, string organizeId, string roleId); /// <summary> /// 当工做流开始启动前须要作的工做 /// </summary> /// <param name="workFlowAuditInfo">审核信息</param> /// <returns>成功失败</returns> bool BeforeAutoStatr(BaseWorkFlowAuditInfo workFlowAuditInfo); /// <summary> /// 当工做流开始启动以后须要作的工做 /// </summary> /// <param name="workFlowAuditInfo">审核信息</param> /// <returns>成功失败</returns> bool AfterAutoStatr(BaseWorkFlowAuditInfo workFlowAuditInfo); /// <summary> /// (点经过时)当审核经过时 /// </summary> /// <param name="entity">审批流当前信息</param> /// <returns>成功失败</returns> bool OnAutoAuditPass(BaseWorkFlowCurrentEntity entity); /// <summary> /// (点退回时)当审核退回时 /// </summary> /// <param name="entity">审批流当前信息</param> /// <returns>成功失败</returns> bool OnAutoAuditReject(BaseWorkFlowCurrentEntity entity); /// <summary> /// (点完成时)当审核完成时 /// </summary> /// <param name="entity">审批流当前信息</param> /// <returns>成功失败</returns> bool OnAutoAuditComplete(BaseWorkFlowCurrentEntity entity); // ====================================== // // 下面是用户本身提交单据审核时发生的事件 // // ====================================== // /// <summary> /// 废弃单据 /// (废弃单据时)当废弃审批流时须要作的事情 /// </summary> /// <param name="id">主键</param> /// <param name="auditIdea">批示</param> /// <returns>影响行数</returns> int AuditQuash(string id, string auditIdea); /// <summary> /// 批量废弃单据 /// (批量废弃单据时)当废弃审批流时须要作的事情 /// </summary> /// <param name="ids">主键数组</param> /// <param name="auditIdea">批示</param> /// <returns>影响行数</returns> int AuditQuash(string[] ids, string auditIdea); // ====================================== // // 下面是用户被审核单据被审核时发生的事件 // // ====================================== // /// <summary> /// (点退回时)当审核退回时 /// </summary> /// <param name="entity">当前审批流程</param> /// <returns>成功失败</returns> bool OnAuditReject(BaseWorkFlowCurrentEntity entity); /// <summary> /// 废弃单据 /// (废弃单据时)当废弃审批流时须要作的事情 /// </summary> /// <param name="entity">当前审批流程</param> /// <returns>影响行数</returns> bool OnAuditQuash(BaseWorkFlowCurrentEntity entity); /// <summary> /// 流程完成时 /// 结束审核时,须要回调写入到表里,调用相应的事件 /// 若成功能够执行完成的处理 /// </summary> /// <param name="entity">当前审批流程</param> /// <returns>成功失败</returns> bool OnAuditComplete(BaseWorkFlowCurrentEntity entity); // (退回到某一节点时) 被退回到某个节点 // 当有人评论时的功能实现 /// <summary> /// 重置单据 /// (单据发生错误时)紧急状况下实用 /// </summary> /// <param name="entity">当前审批流程</param> /// <returns>影响行数</returns> bool OnReset(BaseWorkFlowCurrentEntity entity); } }
经过此次公司新开审批流程的开发,让我比较全面的了解了审批业务流程开发的思路,了解到了其核心的实现原理,若是没有权限底层及审批流组件的支持,这么短期,没有任何经验的状况下,完成审批功能的开发是不可想象的,从此我会逐步完善这个审批组件,实现B/S化,经过界面拖动完成审批流程的建立。