# ManagementSystem **Repository Path**: zsc8917zsc_admin/management-system ## Basic Information - **Project Name**: ManagementSystem - **Description**: 这是一个基于ASP.NET MVC5的后台管理系统基础框架,目前只是做了RBAC的基础实现,但是代码是完整的,可以做为一个例子进行学习和参考,也可以在次基础上进行功能开发。 - **Primary Language**: C# - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 5 - **Forks**: 1 - **Created**: 2021-03-29 - **Last Updated**: 2021-05-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ManagementSystem #### 介绍 #####   概况:   这是一个基于ASP.NET MVC5的后台管理系统基础框架,页面框架基于笔下光年大佬的项目[Light-Year-Admin-Using-Iframe-v4](https://gitee.com/yinqi/Light-Year-Admin-Using-Iframe-v4)。目前只是做了RBAC的基础实现,可以做为一个例子进行学习和参考,也可以在次基础上进行功能开发。未来还会实现ASP.NET Core版本和php版本。前端的页面也尽可能的做了分离,以便未来方便迁移到vue或者react。   演示地址:[ManagementSystem](http://ms.juglz.com/BMS/Login) #####   单元测试和接口:   每个自定义函数都配有单元测试,接口使用的是postman进行测试。postman的配置存放在PostmanConfig目录下,可直接导入使用。 #####   数据库表   为了方便学习和使用我把数据库文件做了一个备份,放到了DB/Sql目录下。为了兼容性,我使用的mssql2008备份的,高版本的也可以直接使用。数据库说明文件参见:[数据库说明](DB/README.md) #### 解决方案结构 ``` ManagementSystem ├─ Common 类库文件夹 │ ├─ MS.Cache 缓存类 │ ├─ MS.Controls 自定义MVC控件库,目前只是自定义了菜单组件 │ ├─ MS.DataAccess 数据类库,代码一般自动生成,很少改动。 │ ├─ MS.Entities 实体类库,代码一般自动生成,很少改动。 │ ├─ MS.Enums 枚举类库,存放项目中用到的枚举 │ ├─ MS.Identity 统一登陆标识库,用于登录信息凭证的逻辑实现 │ ├─ MS.Logic 逻辑类库,存放业务逻辑代码 │ ├─ MS.Utility 自定义公共函数库,常用的自定义公共函数实现 ├─ Test 单元测试文件夹 │ ├─ ManagementSystem.Tests 站点单元测试项目,主要用于接口测试,由于接口测试的解决方案有很多,因此本项目并没有使用这个测试 │ ├─ MS.Logic.Tests 逻辑类库的单元测试 │ ├─ MS.Utility.Tests 自定义函数库单元测试 ├─ Tools 工具文件夹 │ ├─CodeGenerator 代码生成器 使用T4模板实现 ├─ WebSite 站点文件夹 │ ├─ManagementSystem ``` #### 开发规范 ##### 一、接口开发规范 ##### (一)、MS.Cache 缓存类库   缓存的配置写在了Web.config中,可根据配置选择启用本地缓存还是Redis缓存 ``` ``` ##### (二)、MS.Controls 自定义MVC控件   自定义的MVC控件可以写这里,定义好后可以直接在视图中使用,使用方式: ``` ``` ##### (三)、CRUD (MS.Entities、MS.Logic、MS.DataAccess 实体、逻辑、数据层 ) ###### 自动生成代码说明: 1) 项目中的目录结构会按照数据库的命名规范自动生成,自动生成的代码放在对应模块的AutoCode文件夹中。例如MS_Base_Function数据库表生成的对应目录就是 ``` MS.Entities\Base\AutoCode\MS_Base_Function.cs MS.Logic\Base\AutoCode\MS_Base_FunctionLogic.cs MS.DataAccess\Base\AutoCode\MS_Base_FunctionDataAccess.cs ``` 2) 自动生成的文件不要去修改,因为每次生成时都将会覆盖你的改动。如果你想修改自动生成的文件,请修改CodeGenerator项目中的模板后重新生成。 3) 自动生成的代码都带有partial关键字,方便扩展。可在对应模块的目录下创建同名类文件并使用partial关键字进行扩展。例如: ``` MS.DataAccess\Base\MS_Base_UserRoleDataAccess.cs namespace MS.DataAccess { public partial class MS_Base_UserRoleDataAccess { /// /// 用户角色表-批量增加方法 /// public void AddRange(List list) { using (MSDbContext context = new MSDbContext(WriteRead.Write)) { context.MS_Base_UserRole.AddRange(list); context.SaveChanges(); } foreach (MS_Base_UserRole ms_Base_UserRole in list) { LogHelper.AddOperate("Id = " + ms_Base_UserRole.Id, ms_Base_UserRole); } } } } ``` 4) MSDbContext.cs 数据库访问上下文,此文件自动生成,不能修改。 ###### 基本CRUD方法说明: 方法名|参数名|参数类型|返回类型|描述 :---:|:---:|:---:|:---:|:--- Add|T|object|void|添加一条数据 Save|T|object|void|修改一条数据 Remove|id|int|void|删除指定id的数据 Remove|condition|Expression|void|删除指定linq条件的数据 GetById|id|int|T or null|根据主键查询一条实体 GetItemByQuery|condition|Expression|T or null|根据指定linq条件查找,若有多个元素满足条件,此方法会引发异常。 IsExists|condition|Expression|bool|判断指定linq条件是否满足 GetCountByQuery|condition|Expression|bool|获取满足指定linq条件数据的数量 GetAll|||List<T>|获取全部数据 GetPagedList|startRowIndex|int|List<T>|分页查询 ||maximumRows|int||每页显示的数据条数 ||condition|Expression||查询条件 ||orderBy|string|| 排序字段 例如:```"id desc,name asc"``` ||isDesc|bool||是否倒叙(已废弃) ||totalRecords|out int||满足指定条件数据的总数 GetPagedListExpression|startRowIndex|int|List<T>|分页查询,只支持单字段排序 ||maximumRows|int||每页显示的数据条数 ||condition|Expression||查询条件 ||orderBy|Expression|| 排序字段 ||isDesc|bool||是否倒叙 ||totalRecords|out int||满足指定条件数据的总数 GetListByQuery|condition|Expression|List<T>|根据指定linq条件查询集合 ||orderBy|string|| 排序字段 例如:```"id desc,name asc"``` ||isDesc|bool||是否倒叙(已废弃) GetListByQueryExpression|condition|Expression|List<T>|根据指定linq条件查询集合 ||orderBy|Expression|| 排序字段 ||isDesc|bool||是否倒叙 ###### 条件查询分页列表示例: ``` /// /// 获取用户列表 /// /// bootstraptable 分页查询基本参数 /// 查询条件类型 0:uid查询,1用户名查询,2手机号查询,3昵称查询,4姓名查询 /// 查询条件值 /// [HttpPost] public JsonResult GetUserList(BootstrapTablePageParamModel param, int searchType, string searchText) { //bootstraptable 响应json数据模型 PageListModel pageListModel = new PageListModel(); int totalRecords = 0; MS_UserCenter_UserAccountLogic userAccountLogic = MS_UserCenter_UserAccountLogic.GetInstance(); MS_UserCenter_UserAttrLogic userAttrLogic = MS_UserCenter_UserAttrLogic.GetInstance(); List userAttrList = new List(); //创建linq条件,根据参数进行条件组合 Expression> expression = PredicateBuilder.True(); expression = expression.And(x => x.Status >= (int)Status.Normal); if (!string.IsNullOrEmpty(searchText)) { switch (searchType) { //0:uid查询,1用户名查询,2手机号查询,3邮箱查询,4昵称查询,5姓名查询 case 0: //uid long tmp = 0; long.TryParse(searchText, out tmp); expression = expression.And(x => x.Uid == tmp); break; case 1: //用户名 expression = expression.And(x => x.Account.Contains(searchText)); break; case 2: //手机号 expression = expression.And(x => x.Phone.Contains(searchText)); break; case 3: //邮箱 expression = expression.And(x => x.Email.Contains(searchText)); break; case 4: //昵称 userAttrList = userAttrLogic.GetListByQuery(x => x.NickName.Contains(searchText) && x.Status == (int)Status.Normal, null, false); if (userAttrList.Count > 0) { List uids = (from o in userAttrList select o.Uid).ToList(); expression = expression.And(x => uids.Contains(x.Uid)); } else { expression = expression.And(x => x.Uid == -1); } break; case 5: //姓名 userAttrList = userAttrLogic.GetListByQuery(x => x.RealName.Contains(searchText) && x.Status == (int)Status.Normal, null, false); if (userAttrList.Count > 0) { List uids = (from o in userAttrList select o.Uid).ToList(); expression = expression.And(x => uids.Contains(x.Uid)); } else { expression = expression.And(x => x.Uid == -1); } break; } } List userAccountList = userAccountLogic.GetPagedList(param.offset, param.limit, expression, param.sort, param.sortOrder == "desc", out totalRecords); if (userAttrList.Count == 0) { List uids = (from o in userAccountList select o.Uid).ToList(); userAttrList = userAttrLogic.GetListByQuery(x => uids.Contains(x.Uid), null, false); } var tableJson = from userAccount in userAccountList join userAttr in userAttrList.DefaultIfEmpty() on userAccount.Uid equals userAttr.Uid select new { uid = userAccount.Uid, account = Utils.Htmls(userAccount.Account), phone = Utils.Htmls(userAccount.Phone), nickname = Utils.Htmls(userAttr.NickName), realname = Utils.Htmls(userAttr.RealName), gender = EnumHelper.GetEnumDescription((Gender)userAttr.Gender), portrait = ConfigSetting.GetUserLogoAbsoluteUrl(userAttr.Portrait), registTime = DateTimeHelper.GetDateTime(userAccount.CreateTime.ToString()).ToString("yyyy-MM-dd HH:mm:ss"), status = userAccount.Status, email = userAccount.Email }; if (totalRecords > 0) { pageListModel.rows = tableJson; } else { pageListModel.rows = userAccountList; } pageListModel.total = totalRecords; return Json(pageListModel); } ``` ###### 关联查询示例: 每次查询建议只操作一张表,在内存中进行关联查询。 ``` List userList = userAttrLogic.GetListByQuery((x => uidArr.Contains(x.Uid)), string.Empty, false); List userAccountList = userAccountLogic.GetListByQuery(x => uids.Contains(x.Uid), null, false); List userAttrList = userAttrLogic.GetListByQuery(x => uids.Contains(x.Uid), null, false); var tableJson = from userRole in userRoleList join userAccount in userAccountList on userRole.Uid equals userAccount.Uid //内连接 等效于inner join join userAttr in userAttrList on userRole.Uid equals userAttr.Uid into u from uList in u.DefaultIfEmpty() //左连接 等效于left join select new { id = userRole.Id, uid = userRole.Uid, nickname = Utils.Htmls(uList.NickName), realname = Utils.Htmls(uList.RealName), portrait = ConfigSetting.GetUserLogoAbsoluteUrl(uList.Portrait), account = Utils.Htmls(userAccount.Account), phone = userAccount.Phone, lastTime = DateTimeHelper.GetDateTime(userRole.UpdateTime.ToString()).ToString("yyyy-MM-dd HH:mm:ss"), lastUser = from u in userList where u.Uid == userRole.UpdateUid select string.IsNullOrEmpty(u.RealName) ? Utils.Htmls(u.NickName) : Utils.Htmls(u.RealName), }; ``` ###### 删除   项目中删除一般使用逻辑删除,把数据的status状态改为-1即可 ##### (四)、MS.Enums   项目中使用的枚举类型,均放到这个类库下统一维护 项目中所有使用的类型值,状态值最好使用对应的枚举类型进行表示。 通过枚举类型进行值与字符串之间的转换,如: ``` string genderText = EnumHelper.GetEnumDescription((Gender)userAttr.Gender), ``` ##### (五)、MS.Identity   用户登录身份验证,记录用户登录标识 **SecurityIdentity类说明** 方法名|参数名|参数类型|返回类型|描述 :---:|:---:|:---:|:---:|:--- Login|uid|int|void|生成用户登录标识,并且记录到cookie中 ||platform|Platform|void|默认```Platform.BMS```,可根据不同的平台类型记录用户登录标识 GetLoginUser|platform|Platform|LoginUser|获取已登录的用户信息 Logout|platform|Platform|LoginUser|退出登录 GetLoginUid|platform|Platform|LoginUser|获取已登录的uid **LoginUser类说明** 属性名|类型|描述 :---:|:---:|:--- UserAttr|T|用户属性 UserAccount|T|用户账号信息 RoleList|List<T>|用户角色的集合 CurrentRole|T|用户当前所使用的角色 FunctionList|List<T>|用户角色权限 ##### (六)、MS.Utility   公共类库包含文件操作,配置的读取,字符串的处理等等,具体的方法可以见代码里的注释 ##### 二、单元测试规范 - 单元测试项目存放于解决方案文件夹Test中。 - 单元测试项目命名规则为:```项目名称.Tests```,例如```MS.Logic.Tests```。 - 单元测试类命名规范为:```类名Test.cs```,例如```MS_UserCenter_UserAccountLogicTest```。 - 理论上每个函数都要覆盖全面的单元测试,具体开发的时候根据实际情况做取舍 ##### 三、Web应用开发规范   web应用存放于解决方案文件夹WebSite中,以项目中的ManagementSystem为例: - 本地化资源文件存放在```App_LocalResources``` - 通过区域将不同应用分开,例如BMS存放在 ``` Areas/BMS ``` 下 - API接口返回类型为Json,返回数据结构见 ```ManagementSystem\Models\JsonResultModel.cs``` ,除了bootstraptable的接口,都是如下格式 名称|类型|描述 :---:|:---:|:--- code|string| 错误码类似http响应码:200 正常、 401 未登录、 403 未授权、 500 错误 message|string|提示信息 data|dynamic|返回的数据 - bootstraptable API接口的返回数据结构见 ``` ManagementSystem\Models\PageListModel.cs ``` 名称|类型|描述 :---:|:---:|:--- rows|dynamic| 返回的数据 total|int|数据的总数 - 样式文件存放在 ``` Content ``` 目录 公共样式文件放在``` Content\lib\ ``` 页面样式文件放在``` Content\page\ ``` 需要手动引用, 例如```Content\page\bms\login\index.css ``` - JavaScript脚本文件放在```Script```目录 公共脚本文件放在``` Scripts\lib\ ``` 页面脚本文件放在``` Scripts\page\ ``` 会自动引用,例如```ManagementSystem\Scripts\page\bms\function\index.js``` - 脚本加载使用require.js ,AMD规范 ``` defune([ 'domReady!', //依赖项 ... ],function(domReady){ //初始化函数 function init(){ //数据绑定函数 dataBind() //TODO: 一些控件的初始化方法 } /** * 数据绑定函数 * @param {any} isRefresh 刷新还是重载 */ function dataBind(isRefresh){ //TODO:...页面的数据绑定 } //事件绑定 function bind(){ $(document).on('click','.btn',function(){ }) } $(document).ready(function(){ init(); bind(); }) }) ``` - 对ajax请求进行了封装,文件在```ManagementSystem\Scripts\lib\jquery.ajaxrequest.js``` ``` /** * 封装项目中用到的ajax请求 * @param {any} url ajax请求路径 * @param {any} data 参数 * @param {any} successFn 默认请求成功后会弹出提示操作成功,若传入此参数则代替原有方法 * @param {any} isLoading 是否显示loading * @param {any} isOpacity loading背景是否透明 * @param {any} errorFn 请求失败后会弹出提示:操作失败,然后执行此函数 * @param {any} type 请求类型 * @param {any} async 是否异步 * @param {any} dataType 返回数据类型 * @param {any} success $.ajax的success * @param {any} error $.ajax的error */ $.fn.ajaxRequest = function (url, data, successFn, isLoading = true, isOpacity = false, errorFn = null, type = "post", async = true, dataType = "json", success = null, error = null) 调用方式: $('.page-loading').ajaxRequest("/BMS/User/GetUser", { uid: uid }, function (data) { $(".img-avatar").attr("src", data.data.portrait) $("#portrait").val(data.data.portrait); setValue("#account", data.data.account) setValue("#phone", data.data.phone) setValue("#email", data.data.email) setValue("#nickname", data.data.nickname) setValue("#realname", data.data.realname) setValue("#gender", data.data.gender) setValue("#birthday", data.data.birthday) setValue("#registerTime", data.data.registerTime) }) ``` - 权限验证文件存放于 ```ManagementSystem\Filter\LoginFilterAttribute.cs``` 使用的时候在Controller或者Action中加上```[LoginFilter(Platform.BMS)]```,例如: ``` [LoginFilter(Platform.BMS)] public class FunctionController : Controller { } ``` #### 数据库设计规范 为了结合代码生成器以提高开发效率,数据库命名需要遵循以下规范 - 数据库表名称命名规范为: ```前缀_模块_表名称``` 例如: ```MS_Base_Function``` - 数据库字段名规则为首字母大写的驼峰命名规则,例如: ``` [Id] [int] IDENTITY(1,1) NOT NULL, [Pid] [int] NOT NULL, [Name] [nvarchar](100) NOT NULL, [Url] [varchar](1024) NOT NULL, [Level] [int] NOT NULL, [Path] [varchar](100) NOT NULL, [Type] [int] NOT NULL, [IconClassName] [varchar](50) NOT NULL, [SeqId] [int] NOT NULL, [Status] [int] NOT NULL, [CreateUid] [bigint] NOT NULL, [CreateTime] [bigint] NOT NULL, [UpdateUid] [bigint] NOT NULL, [UpdateTime] [bigint] NOT NULL, ``` - 时间日期统一使用long类型 - 所有的表都要有状态、创建人、创建时间、修改人、修改时间 ``` [Status] [int] NOT NULL, [CreateUid] [bigint] NOT NULL, [CreateTime] [bigint] NOT NULL, [UpdateUid] [bigint] NOT NULL, [UpdateTime] [bigint] NOT NULL, ``` #### 使用说明 - 数据库备份文件见```DB\Sql\managementsystem```,mssql2008备份文件,可以还原到mssql2008以上的版本上。 - 创建日志数据库```ManagementSystemLog```,执行```DB\LogDB\```下的文件创建日志数据库表 - 修改```web.config```和```Config\Log4Net.config```中的数据库配置 - 还原Nuget包后F5就能跑起来了 - 前台页面没写,后台地址是``` https://localhost:44370/BMS``` - 演示地址:[ManagementSystem](http://ms.juglz.com/BMS/Login) #### 参与贡献 - 期待你来... #### 特别鸣谢 [笔下光年](https://gitee.com/yinqi) [Light-Year-Admin-Using-Iframe-v4](https://gitee.com/yinqi/Light-Year-Admin-Using-Iframe-v4)