基于 DevExpress 从零开始搭建多租户自洽的权限数据配置模块(一)

从零开始设计一个最简单的winform练习程序,在这个过程中会涉及到devexpress、简单数据库设计、简单分层设计、基本操作方法等一些简单知识点的演示。

1、思路设计

基础数据的维护管理,以简单基本操作的形式展开。主要是演示devexpress做基本的增删改查、加载表单、建立多表关联、用户操作动态加载数据等。

2、程序基本代码轮廓

我们先把这个简单工具的代码划分一个模糊的层次。总体上是一个这样的思路:

基本代码结构
UI结构 登录界面 + 主界面 + Mdi子界面管理 + 响应交互的逻辑
视图 winform窗体,主要是基本操作界面
后台控制 数据请求层 + Json转化层(为后续前后端分离做准备)
模型 数据库模型 + DTO模型
数据库 数据库结构设计 以及建库脚本保留

对UI结构做一个基本要求:基础数据的维护操作,必须是基于单个租户作为基本范围进行配置。系统进入的时候,必须强制管理员选择要操作的租户。

这么做的原因是,我们有很多非常小巧的孤岛业务应用,如果每部署一个应用都需要跟着部署一套权限模块的话,有点浪费。多租户可以在同一个程序中进行托管运维,逻辑可以在租户约束的范围内自洽适应。

在这里,我们定义了一个XtraFormBaseTenancy。

using DevExpress.XtraEditors;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;namespace HiAuth2021
{public partial class XtraFormBaseTenancy : XtraForm{private bool isNeedAutoClose = false;public XtraFormBaseTenancy(){InitializeComponent();this.Activated += XtraFormBase_Activated;this.Shown += XtraFormBase_Shown;}/// <summary>/// 因为Activated事件订阅的优先级顺序要早于Shown事件的订阅契机,因此私有属性会在此生效/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void XtraFormBase_Shown(object sender, EventArgs e){if (isNeedAutoClose) this.Close();}/// <summary>/// 在基类窗体被激活时,判断是否已经进入选定的租户范围,如果没有,提醒用户进行选择,并强制退出继承该基类窗体的子窗体/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void XtraFormBase_Activated(object sender, EventArgs e){if (StaticGlobal.Tenancy == null || string.IsNullOrEmpty(StaticGlobal.Tenancy.CurrentTenancy)){isNeedAutoClose = true;HiAuthStaticSetting.AlertErrorMsg(this, "请先选择您要配置的租户!");}HiAuthStaticSetting.SetCurrentLocationText(this.Text);}}
}

其他的业务操作界面只需要继承上面的基类窗体,就可以实现不选择租户窗体就打不开的效果。变相等于实现了菜单权限的控制逻辑。

public partial class HiAuthUsersManage : XtraFormBaseTenancy
{private bool isNeedPass = false;private List<DbModels.t_auth_users> source;public HiAuthUsersManage(){InitializeComponent();this.Shown += HiAuthUsersManage_Shown;gridView1.RowCellClick += GridView1_RowCellClick;gridView1.FocusedRowChanged += GridView1_FocusedRowChanged;gridView1.SelectionChanged += GridView1_SelectionChanged;...

我们使用一个全局状态管理器,来静态地托管用户登录以后的状态数据。当然,这种方式仅仅是适用于winform形式,web的时候他其实是个会话(Session)

using HiAuth2021.DtoModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace HiAuth2021
{/// <summary>/// 全局静态数据/// </summary>public static class StaticGlobal{public static DtoModels.TenancyCutting Tenancy { get; set; } = new DtoModels.TenancyCutting();public static List<DtoModels.TenancyCutting> TenancySource { get; set; }= RestHelper<DtoModels.TenancyCutting>.GetDataSourceByRestRequest(() =>{return new HiAuthBasicsRestBussiness.HiAuthTenancyRestBusiness().GetTenancyCuttingSource();});}/// <summary>/// 系统登录个人Session加密后记录到缓存服务器/// </summary>public class LoginInfo{/// <summary>/// 主界面窗体/// </summary>public static HiAuthManageSystem MainForm { get; set; }/// <summary>/// 登录窗体/// </summary>public static HiAuthLogin LoginForm { get; set; }/// <summary>/// 登录状态/// </summary>public static LoginState LoginState { get; set; }/// <summary>/// 登录参数/// </summary>public static LoginEntity LoginConfig { get; set; }/// <summary>/// 用户账号/// </summary>public static string UserCode { get; set; }/// <summary>/// 用户姓名/// </summary>public static string UserName { get; set; }/// <summary>/// 性别/// </summary>public static string UserSex { get; set; }/// <summary>/// 部门代码/// </summary>public static string UserDeptCode { get; set; }/// <summary>/// 部门名称/// </summary>public static string UserDeptName { get; set; }/// <summary>/// 岗位/// </summary>public static string UserDuty { get; set; }/// <summary>/// IP地址/// </summary>public static string UserLoginIp { get; set; }/// <summary>/// 当前设备名称/// </summary>public static string Device { get; set; }/// <summary>/// 当前登录域账户/// </summary>public static string WindowsUser { get; set; }/// <summary>/// 认证角色id/// </summary>public static List<int> RoleIds { get; set; }/// <summary>/// 可访问菜单集合/// </summary>public static List<PowerMenus> AccessablePowerMenus { get; set; }/// <summary>/// 共享文件夹根路径/// </summary>public static string ShareDiskRootPath { get => @"***"; }/// <summary>/// 连接共享文件夹的账号/// </summary>public static string ShareDiskUser { get => "***"; }/// <summary>/// 连接共享文件夹的密码/// </summary>public static string ShareDiskPassword { get => "***"; }/// <summary>/// 记住皮肤/// </summary>public static SkinConfig SkinRemember{get{SkinConfig skinConfig = new SkinConfig() { SkinName = "Office 2016 Colorful" };//判断是否存在记住密码文件if (FileOps.IsExistFile("SkinPath")){SkinConfig config = FileOps.LoadObjectFromXml("SkinPath", typeof(SkinConfig)) as SkinConfig;if (config != null){return config;}else{return skinConfig;}}else{return skinConfig;}}}}/// <summary>/// 认证成功返回权限菜单集合/// </summary>public class PowerMenus{/// <summary>/// 父项号/// </summary>public int? ParentId { get; set; }/// <summary>/// 子项号/// </summary>public int MenuId { get; set; }/// <summary>/// 菜单名称/// </summary>public string MenuName { get; set; }/// <summary>/// 角色Id/// </summary>public int RoleId { get; set; }/// <summary>/// 角色名称/// </summary>public string RoleName { get; set; }/// <summary>/// 菜单路径(命名空间加类名)/// </summary>public string MenuPath { get; set; }}/// <summary>/// 登录状态/// </summary>public enum LoginState{LoginFail = -1,          //登录失败Login = 0,               //登录成功Logout = 1,              //注销Close = 2,               //关闭Exit = 3,                //退出LostConnection = 4,      //失去连接}/// <summary>/// 皮肤配置/// </summary>public class SkinConfig{public string SkinName { get; set; }}}

同时,我还需要把一些需要能够被全局控制的控件对象,也放到全局的状态管理器里面,以便于我在程序的任何一个位置,都能够调用到他,使他配合我们显示合适的场景或内容。比如,我当前正在访问哪个页面。

using DevExpress.XtraBars;
using DevExpress.XtraBars.Alerter;
using DevExpress.XtraSplashScreen;
using DevExpress.XtraTabbedMdi;
using DevExpress.XtraTreeList;
using DevExpress.XtraTreeList.Nodes;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;namespace HiAuth2021
{public static class HiAuthStaticSetting{public static HiAuthManageSystem MainForm { get; set; }public static XtraTabbedMdiManager Mdi { get; set; }public static SplashScreenManager SplashManager { get; set; }public static AlertControl Alert { get; set; }public static BarStaticItem CurrentTenancy { get; set; }public static BarStaticItem CurrentLogin { get; set; }public static BarStaticItem CurrentLocation { get; set; }public static void SetCurrentLocationText(string text){HiAuthStaticSetting.CurrentLocation.Caption = $"您正在访问:{text}";}public static void CloseMdiPagesOpenning(){int count = HiAuthStaticSetting.Mdi.Pages.Count;for (int i = 0; i < count; i++) HiAuthStaticSetting.Mdi.Pages[0].MdiChild.Close();}public static void SetWaittingFormOpen(Form frm){SplashManager.ShowWaitForm();}public static void SetWaittingFormClose(Form frm){SplashManager.CloseWaitForm();}public static void AlertMsg(Form frm,string msg){Alert.Show(frm,new AlertInfo("温馨提示",msg));}public static void AlertErrorMsg(Form frm, string msg){Alert.Show(frm, new AlertInfo("错误提示", msg, global::HiAuth2021.Properties.Resources.bug));}/// <summary>/// 系统菜单点击事件处理/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private static void MenuTree_MouseClick(object sender, MouseEventArgs e){TreeListHitInfo hitinfo = (sender as TreeList).CalcHitInfo(e.Location);if (hitinfo.HitInfoType != HitInfoType.Cell || e.Button != MouseButtons.Left){return;}ShowChildForm(hitinfo.Node);}/// <summary>/// 系统菜单树的图标分层显示/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private static void MenuTree_CustomDrawNodeImages(object sender, CustomDrawNodeImagesEventArgs e){if (e.Node.Nodes.Count > 0 && !e.Node.Expanded){e.SelectImageIndex = 0;}else if (e.Node.Nodes.Count > 0 && e.Node.Expanded){e.SelectImageIndex = 1;}else{e.SelectImageIndex = 2;}}/// <summary>/// 根据窗体Text属性获取打开的窗体实例/// </summary>/// <param name="formName"></param>/// <returns></returns>public static dynamic GetOpenedForm(string formName){foreach (dynamic openedForm in Application.OpenForms){if (openedForm.AccessibilityObject.Name == formName){return openedForm;}}return null;}/// <summary>/// 根据窗体Text属性判断是否已经打开窗体/// </summary>/// <param name="formName"></param>/// <returns></returns>public static dynamic IsHasOpened(string formName){foreach (dynamic openedForm in Application.OpenForms){if (!openedForm.IsDisposed && openedForm.AccessibilityObject.Name == formName){return true;}}return false;}/// <summary>/// 连接共享文件夹,连接上后可以像操作本地磁盘的方式操作文件夹和文件/// </summary>/// <param name="path">共享文件夹路径</param>/// <param name="userName">用户名</param>/// <param name="passWord">密码</param>/// <returns>连接成功返回true,否则返回false</returns>public static bool ShareDiskConnect(){string path = LoginInfo.ShareDiskRootPath;           //共享地址string userName = LoginInfo.ShareDiskUser;           //共享用户string passWord = LoginInfo.ShareDiskPassword;       //共享密钥bool Flag = false;                                   //标识Process proc = new Process();try{proc.StartInfo.FileName = "cmd.exe";proc.StartInfo.UseShellExecute = false;proc.StartInfo.RedirectStandardInput = true;proc.StartInfo.RedirectStandardOutput = true;proc.StartInfo.RedirectStandardError = true;proc.StartInfo.CreateNoWindow = true;proc.Start();string dosLine = @"net use " + path + " /User:" + userName + " " + passWord + " /PERSISTENT:YES";proc.StandardInput.WriteLine(dosLine);proc.StandardInput.WriteLine("exit");while (!proc.HasExited){proc.WaitForExit(1000);}string errormsg = proc.StandardError.ReadToEnd();proc.StandardError.Close();if (string.IsNullOrEmpty(errormsg)){Flag = true;}else{//判断是否已经连上if (errormsg.Contains("发生系统错误 1219")){Flag = true;}else{throw new Exception(errormsg);}}}catch (Exception ex){throw ex;}finally{proc.Close();proc.Dispose();}return Flag;}/// <summary>/// 获取样式保存位置/// </summary>/// <returns></returns>public static string GetShareStylePath(){string path = "\\DeepSeaAps\\ShareStyle\\" + LoginInfo.UserCode + LoginInfo.UserName;return Path.Combine(LoginInfo.ShareDiskRootPath, path.StartsWith("\\") ? path.Substring(1) : path) + "\\";}/// <summary>/// 获取固定列保存位置/// </summary>/// <returns></returns>public static string GetFixedColumnConfigPath(){string path = "\\DeepSeaAps\\FixedColumnConfig\\" + LoginInfo.UserCode + LoginInfo.UserName;return Path.Combine(LoginInfo.ShareDiskRootPath, path.StartsWith("\\") ? path.Substring(1) : path) + "\\";}/// <summary>/// 把打开窗体作为Mdi子窗体合并到主界面中/// </summary>/// <param name="frm"></param>public static void ShowAsChildForm(Form frm){if (!IsHasOpened(frm.Text)){frm.MdiParent = MainForm;frm.Show();}else{dynamic form = GetOpenedForm(frm.Text);form.Dispose();frm.MdiParent = MainForm;frm.Show();}}/// <summary>/// 打开窗体/// </summary>/// <param name="node"></param>public static void ShowChildForm(TreeListNode node){if (!node.HasChildren && node.Visible == true){var formtype = node.GetValue("MenuPath") == null ? null : node.GetValue("MenuPath").ToString();var formName = node.GetValue("MenuName") == null ? null : node.GetValue("MenuName").ToString();if (formtype == null || formName == null){AlertErrorMsg(MainForm, "界面加载失败!请确认选择界面是否已经开放进入!如有疑问,请联系系统工程师!");}else{ShowChildForm(formtype, formName);}}}/// <summary>/// 打开窗体/// </summary>/// <param name="formtype">窗体对象的全路径类型名称</param>/// <param name="formName">窗体的Text 需要唯一识别</param>public static void ShowChildForm(string formtype, string formName){if (!IsHasOpened(formName)){try{dynamic frm = Type.GetType(formtype).Assembly.CreateInstance(formtype);frm.MdiParent = MainForm;frm.Show();}catch (Exception){AlertErrorMsg(MainForm, "界面加载失败!请确认选择界面是否已经开放进入!如有疑问,请联系系统工程师!");}}else{dynamic form = GetOpenedForm(formName);form.Activate();}}/// <summary>/// 设置等待框开启/// </summary>public static void SetWaitFormOpen(Form frm){if (SplashManager == null || SplashManager.IsSplashFormVisible != true){SplashManager = new SplashScreenManager(frm, typeof(HiAuthWaitting), true, true);SplashManager.ShowWaitForm();}}/// <summary>/// 设置等待框关闭/// </summary>public static void SetWaitFormClose(Form frm){if (SplashManager != null || SplashManager.IsSplashFormVisible == true){SplashManager.CloseWaitForm();SplashManager.Dispose();}}}
}

3、数据库结构

我们在这里仅仅展示一下表和结构的设计,随着程序的慢慢细化,我们可能会有一些调整。

下一篇开始,我们开始具体业务界面的操作介绍。

从零开始搭建多租户自洽的权限数据配置模块(一)相关推荐

  1. 从零开始搭建多租户自洽的权限数据配置模块(二)- 主界面的跳转管理以及基础数据维护设计

    基于 DevExpress 从零开始搭建多租户自洽的权限数据配置模块(二) 基础数据的维护管理,以简单基本操作的形式展开.主要是演示devexpress做基本的增删改查.加载表单.建立多表关联.用户操 ...

  2. 从零开始搭建一台深度学习服务器及环境配置

    从零开始搭建一台深度学习服务器及环境配置 服务器硬件 2019.12.12 系统安装 环境配置--现有最新版本 服务器硬件 2019.12.12 实验室需求,我找的配置,反正不用我花钱独享一台服务器 ...

  3. Caffe 议事(一):从零开始搭建 ResNet 之 残差网络结构介绍和数据准备

    声明:Caffe 系列文章是我们实验室 黄佳斌 大神所写的内部学习文档,已经获得他的授权允许. 本参考资料是在 Ubuntu14.04 版本下进行,并且默认 Caffe 所需的环境已经配置好,下面教大 ...

  4. 从零开始搭建创业公司后台技术栈!

    原文 : http://ju.outofmemory.cn/entry/351897 前言 说到后台技术栈,脑海中是不是浮现的是这样一幅图? 图 1 有点眼晕,以下只是我们会用到的一些语言的合集,而且 ...

  5. 15分钟从零开始搭建支持10w+用户的生产环境(二)

    上一篇文章,把这个架构的起因,和操作系统的选择进行了详细说明. 原文地址:15分钟从零开始搭建支持10w+用户的生产环境(一)   二.数据库的选择 对于一个10W+用户的系统,数据库选择很重要. 一 ...

  6. umi脚手架搭建的项目_还在从零开始搭建项目?手撸了款快速开发脚手架!

    之前开源了一款项目骨架mall-tiny,完整继承了mall项目的整个技术栈.总感觉mall-tiny集成了太多中间件,过于复杂了.这次对其进行了简化和升级,使它成为了一款拥有完整权限管理功能的快速开 ...

  7. 从零开始搭建公司后台技术栈

    有点眼晕,以下只是我们会用到的一些语言的合集,而且只是语言层面的一部分,就整个后台技术栈来说,这只是一个开始,从语言开始,还有很多很多的内容.今天要说的后台是大后台的概念,放在服务器上的东西都属于后台 ...

  8. 从零开始搭建创业公司全新技术栈

    目录 **1.项目管理/Bug管理/问题管理** **2.DNS** **3.LB(负载均衡)** **4.CDN** **5.RPC 框架** **6.名字发现/服务发现** **7.关系数据库** ...

  9. 从零开始搭建创业公司后台技术栈

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"书",获取 后台回复"k8s",可领取k8s资料 -     前言 ...

最新文章

  1. DeepMind大招,以视觉为媒介,做无监督机器翻译,效果极好
  2. sed线上经典案例之-同时替换多个字符串
  3. 10月第1周中国.COM域名增1万个 涨幅环比缩小82%
  4. python简单代码hello-实现简单的hello/hi程序——python
  5. 剖析ifstream打开含中文路径名文件失败的原因(转)
  6. phpstudy mysql优化_MySQL_MySQL优化之对RAND()的优化方法,众所周知,在MySQL中,如果直 - phpStudy...
  7. 奇怪的电梯(洛谷-P1135)
  8. LVS学习笔记之三种模式的特点概括
  9. python生成api文档_sphinx生成python文档
  10. linux线程相关函数接口
  11. Codeforces Round #728 (Div. 2)
  12. python 画风场 scipy_Python库之SciPy教程
  13. php和python-Python与PHP:有什么区别?
  14. 强化学习在游戏中的作用_游戏中的强化学习
  15. Adobe Flash Builder 四 序列号
  16. ps一点等于多少厘米_PS像素与厘米之间的转换
  17. Crackme之Acid burn.exe
  18. python零基础二
  19. IPv6 地址数量有多少,能够分配到地球上的每一粒尘埃吗
  20. 静态时序分析(sta)/动态时序分析(dta)

热门文章

  1. TextView折叠
  2. 计算机组成TEC4,计算机组成原理实验系统TEC4详细资料
  3. 【Pytorch】带注释的Transformer (各个部件的实现及应用实例)
  4. 一起学英语第一期,Welcome to the real world! It sucks. You're gonna love it.
  5. 4个宝宝晚上不建议出门的原因,你知道吗?
  6. solidworks的界面基础设置
  7. 动易CMS - 设为首页代码和加入收藏代码(兼容各种浏览器)
  8. R数据分析:网络分析的做法,原理和复现方法
  9. Python删除excel首行并合并
  10. 性格测试数据统计(JAVA实现)