# 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)