NewLife.XCode是一个有10多年历史的开源数据中间件,支持nfx/netcore,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode。
整个系列教程会大量结合示例代码和运行日志来进行深入分析,蕴含多年开发经验于其中,代表作有百亿级大数据实时计算项目。
开源地址:https://github.com/NewLifeX/X
前面讲解了XCode的各种用法,这一章我们来讲讲内置的Membership,同时也是XCode的第一标准示例!
设计背景
现代管理信息系统绝大部分采用BS架构,无一例外需要用户角色权限的支持!
结合团队诸多兄弟姐妹的经验,设计了一个大小适中的用户权限系统Membership,目标是满足80%的使用场景,并具备一定的扩展性。
Membership刚开始就采用了角色授权体系,每个用户只有一种角色,角色拥有菜单资源权限集。
随着Membership实用性日益增加,2015年初正式合并进入XCode,作为一个模块存在。
2016年第二代魔方NewLife.Cube采用ASP.Net MVC5重构,让Membership的荣誉达到了鼎峰!
在MVC中,每个Controller就是一个菜单资源,其下的Search/Detail/Insert/Update/Delete等Action作为角色在该菜单资源下的权限子项,保存在角色属性数据中。
2018年为了增强魔方功能,在某些场景下支持单用户多角色,且兼容已有系统,用户表增加RoleIDs字段,保存扩展角色,原来的RoleID作为主角色。
管理提供者
管理提供者接口 IManageProvider ,提供了Membership基本操作实现。
- 当前登录用户 GetCurrent、SetCurrent,静态访问 ManageProvider.User
- 查找用户 FindByID、FindByName
- 注册登录注销 Register、Login、Logout
- 当前用户主机(访问者IP)ManageProvider.UserHost
- IManageProvider 默认由XCode.Membership中的UserX/Role/Menu支持,如若用户使用自己的用户权限表,可重新实现该接口
用户权限
用户 UserX
用户数据模型:
代码语言:javascript复制 <Table Name="User" Description="用户" RenderGenEntity="true">
<Columns>
<Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
<Column Name="Name" DataType="String" Master="True" Nullable="False" Description="名称。登录用户名" />
<Column Name="Password" DataType="String" Description="密码" />
<Column Name="DisplayName" DataType="String" Description="昵称" />
<Column Name="Sex" DataType="Int32" Description="性别。未知、男、女" Type="SexKinds" />
<Column Name="Mail" DataType="String" Description="邮件" />
<Column Name="Mobile" DataType="String" Description="手机" />
<Column Name="Code" DataType="String" Description="代码。身份证、员工编号等" />
<Column Name="Avatar" DataType="String" Length="200" Description="头像" />
<Column Name="RoleID" DataType="Int32" Description="角色。主要角色" />
<Column Name="RoleIDs" DataType="String" Length="200" Description="角色组。次要角色集合" />
<Column Name="DepartmentID" DataType="Int32" Description="部门。组织机构" />
<Column Name="Online" DataType="Boolean" Description="在线" />
<Column Name="Enable" DataType="Boolean" Description="启用" />
<Column Name="Logins" DataType="Int32" Description="登录次数" />
<Column Name="LastLogin" DataType="DateTime" Description="最后登录" />
<Column Name="LastLoginIP" DataType="String" Description="最后登录IP" />
<Column Name="RegisterTime" DataType="DateTime" Description="注册时间" />
<Column Name="RegisterIP" DataType="String" Description="注册IP" />
<Column Name="Ex1" DataType="Int32" Description="扩展1" />
<Column Name="Ex2" DataType="Int32" Description="扩展2" />
<Column Name="Ex3" DataType="Double" Description="扩展3" />
<Column Name="Ex4" DataType="String" Description="扩展4" />
<Column Name="Ex5" DataType="String" Description="扩展5" />
<Column Name="Ex6" DataType="String" Description="扩展6" />
<Column Name="UpdateUser" DataType="String" Description="更新用户" />
<Column Name="UpdateUserID" DataType="Int32" Description="更新用户" />
<Column Name="UpdateIP" DataType="String" Description="更新地址" />
<Column Name="UpdateTime" DataType="DateTime" Nullable="False" Description="更新时间" />
<Column Name="Remark" DataType="String" Length="200" Description="备注" />
</Columns>
<Indexes>
<Index Columns="Name" Unique="True" />
<Index Columns="RoleID" />
<Index Columns="UpdateTime" />
</Indexes>
</Table>
常用字段有ID、用户名和密码,登录注册相关信息;
角色RoleID、RoleIDs用于实现权限集控制;
部分场景需要邮箱Mail、手机Mobile或者工号Code登录;
如果仍然不能满足要求,可以考虑使用Ex1~Ex6等扩展字段。
常用功能点:
- 初始化时,如果数据表为空,自动插入admin/admin用户账号,角色是“管理员”
- 支持注册登录,使用MD5保存密码
- 支持编号查询FindByID和名称查询FindByName,分别采用了对象缓存和对象从键,轻松实现百万级账号快速查询
- 支持IIdentity接口
角色 Role
角色数据模型:
代码语言:javascript复制 <Table Name="Role" Description="角色" RenderGenEntity="true">
<Columns>
<Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
<Column Name="Name" DataType="String" Master="True" Nullable="False" Description="名称" />
<Column Name="Enable" DataType="Boolean" Description="启用" />
<Column Name="IsSystem" DataType="Boolean" Description="系统。用于业务系统开发使用,不受数据权限约束,禁止修改名称或删除" />
<Column Name="Permission" DataType="String" Length="500" Description="权限。对不同资源的权限,逗号分隔,每个资源的权限子项竖线分隔" />
<Column Name="Ex1" DataType="Int32" Description="扩展1" />
<Column Name="Ex2" DataType="Int32" Description="扩展2" />
<Column Name="Ex3" DataType="Double" Description="扩展3" />
<Column Name="Ex4" DataType="String" Description="扩展4" />
<Column Name="Ex5" DataType="String" Description="扩展5" />
<Column Name="Ex6" DataType="String" Description="扩展6" />
<Column Name="CreateUser" DataType="String" Description="创建用户" />
<Column Name="CreateUserID" DataType="Int32" Description="创建用户" />
<Column Name="CreateIP" DataType="String" Description="创建地址" />
<Column Name="CreateTime" DataType="DateTime" Nullable="False" Description="创建时间" />
<Column Name="UpdateUser" DataType="String" Description="更新用户" />
<Column Name="UpdateUserID" DataType="Int32" Description="更新用户" />
<Column Name="UpdateIP" DataType="String" Description="更新地址" />
<Column Name="UpdateTime" DataType="DateTime" Nullable="False" Description="更新时间" />
<Column Name="Remark" DataType="String" Length="200" Description="备注" />
</Columns>
<Indexes>
<Index Columns="Name" Unique="True" />
</Indexes>
</Table>
角色表比较简单主要是名称和启用,以及保存菜单权限数据的Permission
角色支持的操作权限:
代码语言:javascript复制 /// <summary>操作权限</summary>
[Flags]
[Description("操作权限")]
public enum PermissionFlags
{
/// <summary>无权限</summary>
[Description("无权限")]
None = 0,
/// <summary>查看权限</summary>
[Description("查看")]
Detail = 1,
/// <summary>添加权限</summary>
[Description("添加")]
Insert = 2,
/// <summary>修改权限</summary>
[Description("修改")]
Update = 4,
/// <summary>删除权限</summary>
[Description("删除")]
Delete = 8,
/// <summary>所有权限</summary>
[Description("所有")]
All = 0xFF,
}
主要功能点:
- 数据表为空时初始化4个基本角色:管理员、高级用户、普通用户、游客
- 启动时角色权限校验,清理角色中无效的权限项(可能菜单已删除),以及授权管理员访问所有角色都无权访问的新菜单
- 支持编号查询FindByID和名称查询FindByID,采用实体缓存,目标系统不会超过1000个角色
- 支持权限判断与设置 Has/Get/Set/Reset 等
- 重载实体类 Delete/Save/Update/OnLoad/OnPropertyChanged,加载实体对象时展开权限,保存时合并
菜单 Menu
菜单数据模型:
代码语言:javascript复制 <Table Name="Menu" Description="菜单" BaseType="EntityTree" RenderGenEntity="true">
<Columns>
<Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
<Column Name="Name" DataType="String" Master="True" Nullable="False" Description="名称" />
<Column Name="DisplayName" DataType="String" Description="显示名" />
<Column Name="FullName" DataType="String" Length="200" Description="全名" />
<Column Name="ParentID" DataType="Int32" Description="父编号" />
<Column Name="Url" DataType="String" Length="200" Description="链接" />
<Column Name="Sort" DataType="Int32" Description="排序" />
<Column Name="Icon" DataType="String" Description="图标" />
<Column Name="Visible" DataType="Boolean" Description="可见" />
<Column Name="Necessary" DataType="Boolean" Description="必要。必要的菜单,必须至少有角色拥有这些权限,如果没有则自动授权给系统角色" />
<Column Name="Permission" DataType="String" Length="200" Description="权限子项。逗号分隔,每个权限子项名值竖线分隔" />
<Column Name="Ex1" DataType="Int32" Description="扩展1" />
<Column Name="Ex2" DataType="Int32" Description="扩展2" />
<Column Name="Ex3" DataType="Double" Description="扩展3" />
<Column Name="Ex4" DataType="String" Description="扩展4" />
<Column Name="Ex5" DataType="String" Description="扩展5" />
<Column Name="Ex6" DataType="String" Description="扩展6" />
<Column Name="CreateUser" DataType="String" Description="创建用户" />
<Column Name="CreateUserID" DataType="Int32" Description="创建用户" />
<Column Name="CreateIP" DataType="String" Description="创建地址" />
<Column Name="CreateTime" DataType="DateTime" Nullable="False" Description="创建时间" />
<Column Name="UpdateUser" DataType="String" Description="更新用户" />
<Column Name="UpdateUserID" DataType="Int32" Description="更新用户" />
<Column Name="UpdateIP" DataType="String" Description="更新地址" />
<Column Name="UpdateTime" DataType="DateTime" Nullable="False" Description="更新时间" />
<Column Name="Remark" DataType="String" Length="200" Description="备注" />
</Columns>
<Indexes>
<Index Columns="Name" />
<Index Columns="ParentID,Name" Unique="True" />
</Indexes>
</Table>
菜单实体类采用树形实体基类 EntityTree ,通过 ParentID 实现上下级关联,同级 ParentID Name 唯一
主要功能点:
- 支持自动扫描Controller作为菜单,因此魔方只需要增加Controller,即可在菜单表看到新页面
- 实体树适用于1000行以内树形数据表,一次性加载数据到内存,在内存中根据ParentID构造实体对象树,最常用树形是Parent/Childs
日志统计
日志 Log
数据模型:
代码语言:javascript复制 <Table Name="Log" Description="日志" ConnName="Log" RenderGenEntity="true">
<Columns>
<Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
<Column Name="Category" DataType="String" Description="类别" />
<Column Name="Action" DataType="String" Description="操作" />
<Column Name="LinkID" DataType="Int32" Description="链接" />
<Column Name="UserName" DataType="String" Description="用户名" />
<Column Name="Ex1" DataType="Int32" Description="扩展1" />
<Column Name="Ex2" DataType="Int32" Description="扩展2" />
<Column Name="Ex3" DataType="Double" Description="扩展3" />
<Column Name="Ex4" DataType="String" Description="扩展4" />
<Column Name="Ex5" DataType="String" Description="扩展5" />
<Column Name="Ex6" DataType="String" Description="扩展6" />
<Column Name="CreateUser" DataType="String" Description="创建用户" />
<Column Name="CreateUserID" DataType="Int32" Description="用户编号" />
<Column Name="CreateIP" DataType="String" Description="IP地址" />
<Column Name="CreateTime" DataType="DateTime" Nullable="False" Description="时间" />
<Column Name="Remark" DataType="String" Length="500" Description="详细信息" />
</Columns>
<Indexes>
<Index Columns="Category" />
<Index Columns="CreateUserID" />
<Index Columns="CreateTime" />
</Indexes>
</Table>
日志表记录分类、操作和日志内容。
主要功能点:
- 日志提供者LogProvider,提供了唯一核心方法 WriteLog,默认实现就是写该日志表。可从对象容器取得日志提供者 ObjectContainer.Resolve<LogProvider>()
- 从IManageProvider接口获取当前登录用户以及远程访问IP写入日志相应字段
在线 UserOnline
数据模型:
代码语言:javascript复制 <Table Name="UserOnline" Description="用户在线" ConnName="Log">
<Columns>
<Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
<Column Name="UserID" DataType="Int32" Description="用户" />
<Column Name="Name" DataType="String" Master="True" Description="名称" />
<Column Name="SessionID" DataType="String" Description="会话。Web的SessionID或Server的会话编号" />
<Column Name="Times" DataType="Int32" Description="次数" />
<Column Name="Page" DataType="String" Description="页面" />
<Column Name="Status" DataType="String" Length="200" Description="状态" />
<Column Name="OnlineTime" DataType="Int32" Description="在线时间。本次在线总时间,秒" />
<Column Name="CreateIP" DataType="String" Description="创建地址" />
<Column Name="CreateTime" DataType="DateTime" Nullable="False" Description="创建时间" />
<Column Name="UpdateTime" DataType="DateTime" Nullable="False" Description="修改时间" />
</Columns>
<Indexes>
<Index Columns="UserID" />
<Index Columns="SessionID" />
<Index Columns="CreateTime" />
</Indexes>
</Table>
借助用户行为模块 UserBehaviorModule , 维护用户在线记录,持久化在 UserOnline 表
访问统计 VisitStat
代码语言:javascript复制 <Table Name="VisitStat" Description="访问统计" ConnName="Log">
<Columns>
<Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
<Column Name="Level" DataType="Int32" Description="层级" Type="XCode.Statistics.StatLevels" />
<Column Name="Time" DataType="DateTime" Description="时间" />
<Column Name="Page" DataType="String" Nullable="False" Description="页面" />
<Column Name="Title" DataType="String" Master="True" Description="标题" />
<Column Name="Times" DataType="Int32" Description="次数" />
<Column Name="Users" DataType="Int32" Description="用户" />
<Column Name="IPs" DataType="Int32" Description="IP" />
<Column Name="Error" DataType="Int32" Description="错误" />
<Column Name="Cost" DataType="Int32" Description="耗时。毫秒" />
<Column Name="MaxCost" DataType="Int32" Description="最大耗时。毫秒" />
<Column Name="CreateTime" DataType="DateTime" Nullable="False" Description="创建时间" />
<Column Name="UpdateTime" DataType="DateTime" Nullable="False" Description="更新时间" />
<Column Name="Remark" DataType="String" Length="5000" Description="详细信息" />
</Columns>
<Indexes>
<Index Columns="Page,Level,Time" Unique="True" />
<Index Columns="Level,Time" />
</Indexes>
</Table>
借助用户行为模块 UserBehaviorModule , 维护用户访问记录,写入日志表,并写入访问统计表。
主要功能要点:
- 记录页面访问统计,简单支持IP数和用户数
- 支持年月日三级统计,作为XCode日期统计表的标准示例
其它
部门 Department
数据模型:
代码语言:javascript复制 <Table Name="Department" Description="部门。组织机构,多级树状结构" BaseType="EntityTree" RenderGenEntity="true">
<Columns>
<Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
<Column Name="Code" DataType="String" Description="代码" />
<Column Name="Name" DataType="String" Master="True" Nullable="False" Description="名称" />
<Column Name="FullName" DataType="String" Length="200" Description="全名" />
<Column Name="ParentID" DataType="Int32" Description="父级" />
<Column Name="Level" DataType="Int32" Description="层级。树状结构的层级" />
<Column Name="Sort" DataType="Int32" Description="排序。同级内排序" />
<Column Name="Enable" DataType="Boolean" Description="启用" />
<Column Name="Visible" DataType="Boolean" Description="可见" />
<Column Name="Ex1" DataType="Int32" Description="扩展1" />
<Column Name="Ex2" DataType="Int32" Description="扩展2" />
<Column Name="Ex3" DataType="Double" Description="扩展3" />
<Column Name="Ex4" DataType="String" Description="扩展4" />
<Column Name="Ex5" DataType="String" Description="扩展5" />
<Column Name="Ex6" DataType="String" Description="扩展6" />
<Column Name="CreateUser" DataType="String" Description="创建用户" />
<Column Name="CreateUserID" DataType="Int32" Description="创建用户" />
<Column Name="CreateIP" DataType="String" Description="创建地址" />
<Column Name="CreateTime" DataType="DateTime" Nullable="False" Description="创建时间" />
<Column Name="UpdateUser" DataType="String" Description="更新用户" />
<Column Name="UpdateUserID" DataType="Int32" Description="更新用户" />
<Column Name="UpdateIP" DataType="String" Description="更新地址" />
<Column Name="UpdateTime" DataType="DateTime" Nullable="False" Description="更新时间" />
<Column Name="Remark" DataType="String" Length="200" Description="备注" />
</Columns>
<Indexes>
<Index Columns="Name" />
<Index Columns="ParentID,Name" Unique="True" />
<Index Columns="Code" />
<Index Columns="UpdateTime" />
</Indexes>
</Table>
字典参数 Parameter
数据模型:
代码语言:javascript复制 <Table Name="Parameter" Description="字典参数">
<Columns>
<Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
<Column Name="Category" DataType="String" Description="类别" />
<Column Name="Name" DataType="String" Master="True" Description="名称" />
<Column Name="Value" DataType="String" Length="200" Description="数值" />
<Column Name="LongValue" DataType="String" Length="2000" Description="长数值" />
<Column Name="Kind" DataType="Int32" Description="种类。0普通,21列表,22名值" Type="XCode.Membership.ParameterKinds" />
<Column Name="Enable" DataType="Boolean" Description="启用" />
<Column Name="Ex1" DataType="Int32" Description="扩展1" />
<Column Name="Ex2" DataType="Int32" Description="扩展2" />
<Column Name="Ex3" DataType="Double" Description="扩展3" />
<Column Name="Ex4" DataType="String" Description="扩展4" />
<Column Name="Ex5" DataType="String" Description="扩展5" />
<Column Name="Ex6" DataType="String" Description="扩展6" />
<Column Name="CreateUser" DataType="String" Description="创建用户" />
<Column Name="CreateUserID" DataType="Int32" Description="创建用户" />
<Column Name="CreateIP" DataType="String" Description="创建地址" />
<Column Name="CreateTime" DataType="DateTime" Nullable="False" Description="创建时间" />
<Column Name="UpdateUser" DataType="String" Description="更新用户" />
<Column Name="UpdateUserID" DataType="Int32" Description="更新用户" />
<Column Name="UpdateIP" DataType="String" Description="更新地址" />
<Column Name="UpdateTime" DataType="DateTime" Nullable="False" Description="更新时间" />
<Column Name="Remark" DataType="String" Length="200" Description="备注" />
</Columns>
<Indexes>
<Index Columns="Category,Name" Unique="True" />
<Index Columns="Name" />
<Index Columns="UpdateTime" />
</Indexes>
</Table>