这篇文章要讲主数据管理系统(Master Data Management System),是本人创业的其中一个项目,用于管理公司的内部所有系统、系统用户、系统角色、系统权限、服务器管理、系统日志等数据。项目开发只用了1个多月时间,全部由一个人完成的,目前只是个初版本,只满足了初期的需求,如果没时间看下面的架构设计,可以直接下载源码。
在线demo:
用户/密码:test1/test1 test2/test2 (注:同一用户在另一浏览器登录,另一用户在session失效后会被逼下线)
不少朋友下载源码后不怎么知道运行,请看这里补充的说明:
项目使用vs2010打开,数据使用sql server2005/2008
一、数据库创建与初始化数据新建名为 Db_MDMS 的数据库
然后按顺序执行目录 Documents\MDMS.Documents\DB 1.0\Scripts 里的sql
1_tables.sql
2_functions.sql3_stored-procedures.sql4_init_data.sql 二、修改数据库连接信息在目录 build\MSH 里修改 MSH.exe.config 的配置节点 MDMS.Db_ConnectionString 的值三、
在vs2010中运行Web项目 MDMS.Web 或右键选择 default.aspx 页面点击在浏览器中浏览,然后打开目录 build\MSH ,双击MSH.exe运行之 这时,你可以用默认用户/密码 admin/admin888 登录系统了
主数据管理问题存在的根源
对于大多数的企业都存在主数据管理的问题,个人以为这是由于业务发展的渐进性以及IT技术发展的渐进性造成的,正是由于这种渐进性,各大企业的业务系统从经历了从无到有,从简单到复杂,从而形成了一个又一个的业务竖井。从根本上来说,不可能只使用一个业务系统就能覆盖企业的所有业务,即便对一些国际大型的公司提供的套件来说也是一个不可能完成的任务(即便对套件来说,经常也存在一个跨国企业在不同的国家或地区部署多个实例的现象,也就是没有集中部署该套件,而是在很多地方分散部署了该套件)。对企业来说,业务系统的构建更多是以项目为中心,从下而上的构建系统,而不是至上而下的构建系统——这必然缺乏整个企业范围内的统一规划,从而使得一些需要在各个业务中共享的数据(主数据)被分散到了各个业务系统进行分别管理。由于分散管理的主数据不具备一致性、准确性、完整性,使得各个企业普遍存在着产品管理不力、供应商管理不力、订单管理不力等现象。解决这一问题的根本方法就是引入主数据管理(MDM),主数据不光指需要共享的数据,更包含需要共享的业务规则和策略。一、主要功能需求
1.公司内部系统的管理,包括管理系统信息、系统权限定义、系统角色、系统角色拥有的权限等等。
2.统一的用户管理,包括管理用户信息、登录系统的限制、个人在各个系统的角色、个人在各个系统的永久与临时权限等等。
3.各系统的日志记录与查看。
4.服务器的管理。(说明一下,这里只是管理服务器的信息,数据是提供给一个专门管理服务器远程登录的子系统的)
...其它扩展需求(多语言目前不需要,但MTV框架已实现了)
二、主要界面展示
1.主界面
2.用户列表
3.个人登录限制
4.个人角色管理
5.个人永久权限
6.个人临时权限
7.系统列表
8.系统权限定义列表
9.添加权限项
10.系统角色管理
11.系统角色的权限
12.服务器管理
13.系统日志
三、数据表设计
LoginLimit : 用户在各个系统的登录限制
LoginState : 用户在各个系统的登录情况
OperationLog : 用户在各个系统的操作日志
PermissionGroup : 系统的权限定义分组
PermissionItem : 系统权限分组里的项
Role : 系统角色信息
Server : 服务器(组)信息
ServerParameter : 服务器参数信息
System : 系统信息
User :用户信息
UserPermission : 用户在各个系统的永久权限
UserRoleMapping : 用户在各个系统的角色关系
UserTempPermission : 用户在各个系统的临时权限
详细设计内容太多,请下载源码看里面的数据库设计文档。
三、系统架构说明
整个解决方案共20个项目,其中ProviderModules目录下的13个项目是应用WCF服务向业务层提供数据接口,这么做了为了以后把数据接口和网站开发分开来给团队独立管理维护。系统应用了N-tier架构的基本设计模式+WCF服务+MTV框架。其中WCF服务采用了KudySharp里的ModuleFramework,MTV框架也是集成在KudySharp里面的。下面对各个项目作简单的解说。
ModuleFramework相关文章(MTV框架还没有时间相关的文章):
MDMS.Core项目是核心库,IProvider下是数据操作接口的定义,Models下是数据表实体模型类和其它扩展模型类,Modules下是应用ModuleFramework的相关类。DataProviderFactory用于根据配置产生实现数据接口的实例,而DataProviderManager则是管理这些实例的,下面请看代码:
(原先设计是把Data、IProvider、Models分别放在独立项目的,经典多层项目都这么干,但为了减少项目数,本人都放在MDMS.Core项目中了)
////// /// internal static class DataProviderFactory { ////// /// ////// public static TProvider Create () { TProvider provider; Type interfaceType = typeof(TProvider); string interfaceName = interfaceType.FullName; string providerTypeName = string.Concat(MSHConfigs.MDMS_Db_Provider, ".", interfaceType.Name.Substring(1)); try { object instance = Assembly.Load(MSHConfigs.MDMS_Db_Provider).CreateInstance(providerTypeName); provider = (TProvider)instance; } catch (Exception ex) { throw new Exception("Fail to create provider instance of " + providerTypeName, ex); } return provider; } }
////// /// public static class DataProviderManager { private static readonly ReadFreeCacheproviderCache = ReadFreeCache .Create(StringComparer.OrdinalIgnoreCase); /// /// /// ////// public static TProvider Get () { string interfaceName = typeof(TProvider).FullName; object provider = providerCache.Get(interfaceName, key => DataProviderFactory.Create ()); return (TProvider)provider; } }
MDMS.SqlServerProvider项目是数据接口sql server实现类库。里面用了KudySharp里的SqlServerHelper助手类,使数据操作更加简洁,请看下面的基类和用户表的数据接口实现类:
////// 数据提供者基类 /// public abstract class ProviderBase { protected virtual string ConnectionString { get { return MSHConfigs.MDMS_Db_ConnectionString; } } protected virtual object ExecuteNonQueryWithReturnValueParameter(string spName, params object[] parameterValues) { // 自动获取存储过程参数(第二次后会取被缓存的一个拷贝),并设置有返回值参数 SqlParameter[] parameters = SqlServerHelper.GetSpParameterSet(ConnectionString, spName, true, parameterValues); // 执行存储过程 SqlServerHelper.ExecuteNonQuery(ConnectionString, CommandType.StoredProcedure, spName, parameters); // 存储过程执行后的返回值 return parameters[0].Value; } protected virtual TModel GetModel(Func getModel, string spName, params object[] parameterValues) where TModel : new() { TModel entity = new TModel(); // 自动获取存储过程参数(第二次后会取被缓存的一个拷贝),并设置没有返回值参数 SqlParameter[] parameters = SqlServerHelper.GetSpParameterSet(ConnectionString, spName, false, parameterValues); // 执行存储过程 using (var reader = SqlServerHelper.ExecuteReader(ConnectionString, CommandType.StoredProcedure, spName, parameters)) { if (reader.Read()) { // 读取数据 entity = getModel(reader); } } return entity; } protected virtual List GetModelList (Func getModel, string spName, params object[] parameterValues) where TModel : new() { List list = new List (); // 自动获取存储过程参数(第二次后会取被缓存的一个拷贝),并设置没有返回值参数 SqlParameter[] parameters = SqlServerHelper.GetSpParameterSet(ConnectionString, spName, false, parameterValues); // 执行存储过程 using (var reader = SqlServerHelper.ExecuteReader(ConnectionString, CommandType.StoredProcedure, spName, parameters)) { while (reader.Read()) { // 读取数据 TModel entity = getModel(reader); list.Add(entity); } } return list; } public virtual PagedList GetPagedList (Func getModel, PagedSqlParameters pagedSqlParameters) where TModel : new() { if (pagedSqlParameters.PageSize < 1 || pagedSqlParameters.PageSize > 50) { pagedSqlParameters.PageSize = 10; } // 执行存储过程(SqlServerHelper内置的分页存储过程) using (var reader = SqlServerHelper.ExecutePagedReader(ConnectionString, pagedSqlParameters)) { List list = new List (); while (reader.Read()) { // 读取数据 TModel model = getModel(reader); list.Add(model); } return list.ToPagedList(reader.PageSize, reader.PageIndex, reader.RecordCount); } } }
////// 用户接口实现 /// public sealed class UserProvider : ProviderBase, IUserProvider { public bool Add(string userName, string password, string passwordSalt, UserGender gender, string realName, string email, string mobile, UserStatus status) { object value = ExecuteNonQueryWithReturnValueParameter("SP_MDMS_User_Add", userName, password, passwordSalt, gender, realName, email, mobile, status); return (Convert.ToInt32(value) > 0); } public bool EditById(Guid id, string password, string passwordSalt, UserGender gender, string realName, string email, string mobile, UserStatus status) { object value = ExecuteNonQueryWithReturnValueParameter("SP_MDMS_User_EditById", id, password, passwordSalt, gender, realName, email, mobile, status); return (Convert.ToInt32(value) > 0); } public bool EditPasswordByUserName(string userName, string password, string passwordSalt) { object value = ExecuteNonQueryWithReturnValueParameter("SP_MDMS_User_EditPasswordByUserName", userName, password, passwordSalt); return (Convert.ToInt32(value) > 0); } public bool DeleteByIds(Guid[] ids) { object value = ExecuteNonQueryWithReturnValueParameter("SP_MDMS_User_DeleteByIds", SqlServerHelper.ToCommaString(ids)); return (Convert.ToInt32(value) > 0); } public bool SetStatusByIds(Guid[] ids, UserStatus status) { object value = ExecuteNonQueryWithReturnValueParameter("SP_MDMS_User_SetStatusByIds", SqlServerHelper.ToCommaString(ids), (byte)status); return (Convert.ToInt32(value) > 0); } public UserModel GetById(Guid id) { return GetModel(GetUserModel, "SP_MDMS_User_GetById", id); } public UserModel GetByUserName(string userName) { return GetModel(GetUserModel, "SP_MDMS_User_GetByUserName", userName); } public PagedListGetPagedList(string userName, UserGender? gender, UserStatus? status, int pageSize, int pageIndex) { PagedSqlParameters sqlParameters = new PagedSqlParameters() { SelectExpression = "*", FromExpression = "[dbo].[User]", MasterTable = "[dbo].[User]", MasterPrimaryKey = "[Id]", AutoIncrementKeyMode = false, WhereExpression = string.Empty, OrderExpression = "[CreateDate]", Descending = true, MaxPageIndex = 0, PageSize = pageSize, PageIndex = pageIndex }; if (!string.IsNullOrEmpty(userName)) { sqlParameters.WhereExpression += " AND [UserName] LIKE '%" + SqlServerHelper.EscapeLike(userName) + "%'"; } if (gender.HasValue) { sqlParameters.WhereExpression += " AND [Gender]=" + ((byte)gender.Value).ToString(); } if (status.HasValue) { sqlParameters.WhereExpression += " AND [Status]=" + ((byte)status.Value).ToString(); } return GetPagedList(GetUserModel, sqlParameters); } private UserModel GetUserModel(DataReader reader) { return new UserModel { Id = reader.GetGuid("Id"), UserName = reader.GetString("UserName").Trim(), Password = reader.GetString("Password"), PasswordSalt = reader.GetString("PasswordSalt"), Gender = (UserGender)reader.GetByte("Gender"), RealName = reader.GetString("RealName").Trim(), Email = reader.GetString("Email"), Mobile = reader.GetString("Mobile").Trim(), Status = (UserStatus)reader.GetByte("Status"), CreateDate = reader.GetDateTime("CreateDate") }; } }
.....其它省略....
四、子系统怎么与主数据管理系统整合?
只要继承 MDMS.UserApiModule 项目里的ServiceContextBase类,就可以很轻松的实现用户的登录,退出,添加日志,读取权限等操作,其中主数据管理系统也作为了其中的一个子系统来管理了,可以参考此系统是怎么整合的,时间问题,不想再多写解说了。请谅解。
另一文中讲解了此系统的权限设计方案: