NewLife.XCode是一个有10多年历史的开源数据中间件,支持nfx/netcore,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode。

整个系列教程会大量结合示例代码和运行日志来进行深入分析,蕴含多年开发经验于其中,代表作有百亿级大数据实时计算项目。

开源地址:https://github.com/NewLifeX/X (求star, 864+)

 

前面讲解了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基本操作实现。

  1. 当前登录用户 GetCurrent、SetCurrent,静态访问 ManageProvider.User
  2. 查找用户 FindByID、FindByName
  1. 注册登录注销 Register、Login、Logout
  2. 当前用户主机(访问者IP)ManageProvider.UserHost
  1. IManageProvider 默认由XCode.Membership中的UserX/Role/Menu支持,如若用户使用自己的用户权限表,可重新实现该接口

 

用户权限

用户 User

用户模块支持常见的系统需求,以Web应用为主,同时支持桌面应用。

超大用户系统中,(用户数超过一千万),推荐使用ID哈希进行分表,分为4/16/64表,已有真实项目案例。

数据模型:

<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等扩展字段。

 

常用功能点:

  1. 初始化时,如果数据表为空,自动插入admin/admin用户账号,角色是“管理员”
  2. 支持注册登录,使用MD5保存密码(2021年前),使用SHA512加随机盐保存密码,确保相同密码多次保存均不相同
  1. 支持编号查询FindByID和名称查询FindByName,分别采用了对象缓存和对象从键,轻松实现千万级账号快速查询
  2. 支持IIdentity接口

 

角色 Role

角色作为权限集存在,每个用户拥有主角色及多个扩展角色,便于灵活控制权限。

数据模型:

<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


角色支持的操作权限:

/// <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,
}

主要功能点:

  1. 数据表为空时初始化4个基本角色:管理员、高级用户、普通用户、游客
  2. 启动时角色权限校验,清理角色中无效的权限项(可能菜单已删除),以及授权管理员访问所有角色都无权访问的新菜单
  1. 支持编号查询FindByID和名称查询FindByID,采用实体缓存,目标系统不会超过1000个角色
  2. 支持权限判断与设置 Has/Get/Set/Reset 等
  1. 重载实体类 Delete/Save/Update/OnLoad/OnPropertyChanged,加载实体对象时展开权限,保存时合并

 

菜单 Menu

菜单模块主要给Web应用提供支持,最大使用者是魔方 NewLife.Cube。

数据模型:

<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 唯一

 

主要功能点:

  1. 支持自动扫描Controller作为菜单,因此魔方只需要增加Controller,即可在菜单表看到新页面
  2. 实体树适用于1000行以内树形数据表,一次性加载数据到内存,在内存中根据ParentID构造实体对象树,最常用树形是Parent/Childs

 

日志统计

日志 Log

日志模块用于记录审计日志,包括管理员在内任何人不允许删除,魔方有完整支持。

数据模型:

  <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="Success" DataType="Boolean" 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="创建地址" />
      <Column Name="CreateTime" DataType="DateTime" Nullable="False" Description="时间" />
      <Column Name="Remark" DataType="String" Length="500" Description="详细信息" />
    </Columns>
    <Indexes>
      <Index Columns="Action,Category,CreateTime" />
      <Index Columns="CreateUserID,CreateTime" />
      <Index Columns="CreateTime" />
    </Indexes>
  </Table>

日志表记录分类、操作和日志内容。

主要功能点:

  1. 日志提供者LogProvider,提供了唯一核心方法 WriteLog,默认实现就是写该日志表。可从对象容器取得日志提供者 ObjectContainer.Resolve<LogProvider>()
  2. 从IManageProvider接口获取当前登录用户以及远程访问IP写入日志相应字段

 

在线 UserOnline(2021年起停用)

数据模型:

<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(2021年起停用)

数据模型:

<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 , 维护用户访问记录,写入日志表,并写入访问统计表。

主要功能要点:

  1. 记录页面访问统计,简单支持IP数和用户数
  2. 支持年月日三级统计,作为XCode日期统计表的标准示例

 

其它

部门 Department

在魔方中,如果经钉钉或企业微信登录,则把用户所属部门直接带入到系统中。

数据模型:

<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

字典参数常用于存储自定义数据,例如钉钉密钥,以及系统统计数据时的时间进度。

数据模型:

  <Table Name="Parameter" Description="字典参数">
    <Columns>
      <Column Name="ID" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
      <Column Name="UserID" DataType="Int32" Description="用户。按用户区分参数,用户0表示系统级" />
      <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="UserID,Category,Name" Unique="True" />
      <Index Columns="Category,Name" />
      <Index Columns="UpdateTime" />
    </Indexes>
  </Table>


地区 Area

地区模块,魔方默认下载并导入省市区乡镇四级行政规划数据。

内置的SearchIP支持根据IP地址查询所在省市区。

数据模型:

  <Table Name="Area" Description="地区。行政区划数据">
    <Columns>
      <Column Name="ID" DataType="Int32" PrimaryKey="True" Description="编码。行政区划编码" />
      <Column Name="Name" DataType="String" Master="True" Description="名称" />
      <Column Name="FullName" DataType="String" Master="True" Description="全名" />
      <Column Name="ParentID" DataType="Int32" Description="父级" />
      <Column Name="Level" DataType="Int32" Description="层级" />
      <Column Name="Longitude" DataType="Double" Description="经度" />
      <Column Name="Latitude" DataType="Double" Description="纬度" />
      <Column Name="Enable" DataType="Boolean" Description="启用" />
      <Column Name="UpdateUser" DataType="String" Description="更新者" />
      <Column Name="UpdateUserID" DataType="Int32" Description="更新用户" />
      <Column Name="UpdateIP" DataType="String" Description="更新地址" />
      <Column Name="UpdateTime" DataType="DateTime" Description="更新时间" />
      <Column Name="Remark" DataType="String" Length="200" Description="备注" />
    </Columns>
    <Indexes>
      <Index Columns="ParentID" />
      <Index Columns="Name" />
      <Index Columns="UpdateTime,ID" />
    </Indexes>
  </Table>