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

2 篇文章 0 订阅
2 篇文章 0 订阅

基于 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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
1.系统特点 本插件框架实现了界面与逻辑的解偶,从此告别在代码中到处判断工具栏上按钮的使能,同时在不修改系统已有代码的前提下也能实现向系统中增加功能即符合开放-封闭原则,避免在扩展新功能时需要大量修改已有代码,从而又引入了新的BUG,且各模块可以相对比较独立,多人同时开发,从而实现快速开发。 2.运行体验 如果你看到本文档说明你已成功下载了本插件框架的运行包和示例源码,请确保你的电脑安装了.net framework4.0,以及ACE引擎(通过OLEDB访问ACCESS数据库,一般安装了OFFICE以后即具有此引擎)。 运行步骤:(1)将压缩包解压 (2)进入OUTPUT目录,直接运行MainForm.exe文件,输入用户名:admin,密码为空,点击登录即可进入软件。 进入软件后,系统会自动打开Customers数据库信息,该界面显示Customers数据库的列表。工具栏上方还有配置权限管理两个主要功能页面,其主要功能如下: 配置:本功能页面主要实现的功能是插件的配置,用于配置插件运行的DLL,系统的名称,插件所包含的功能按钮(在DLL中可以包含多个插件,如果在配置文件中没有配置,系统也不会显示出来),包括工具页面,每个工具页面可以包含多个按钮。也可以设置某个功能插件能够自动运行(即打开软件直接打开工功能,如Customers数据库即是被定义为自启动的示例),PAD面板(示例中的测试面板)。 权限管理权限管理实现了基本的权限管理包括用户管理,用户权限,角色管理,角色权限。特点在于不光能根据角色分配权限,也能对用户单独分配临时权限。 3.开发环境 要编译运行本示例源码请确保你的计算机上已安装: Visual studio 2010 .net framework4.0 DevExpress11.1.6(编译需要安装此包,运行只需要包含相关的DLL文件,已在本包中包含) 解压压缩包,使用visual studio2010打开文件夹中的td.Addin.sln工程文件,点击运行,即可自动编译,如果提示LC.EXE已退出错误,请再次确认你已安装了DevExpress11.1.6。 4.开发体验 任务:向系统中增加一个功能:在customers的工具栏增加一个显示详情按钮,点击此按钮弹出对话框显示当前选中行的信息。本示例展示如何在不修改原有代码的情况下增加功能。 步骤:1.在解决方案中增加一个类库工程:EditNorthWindForm 2.向该工程增加引用:引用位于OUTPUT文件夹中的NorthWind.dll td.Core.dll td.Security.dll 以及所有以DevExpress开头的文件(由于界面使用了DEV控件,所以必须引用) ,添加.NET 的文件System.Drawing System.Windows.Forms。 3.向工程中增加窗口文件:EditNorthWindForm.cs,在工程上点右键,选择添加窗口,如下: 然后将窗口绘制成需要的样式,如增加TEXTBOX等。 将代码修改为: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using DevExpress.XtraEditors; using td.Core; namespace NorthWind { public partial class EditNorthWindForm : DevExpress.XtraEditors.XtraForm { public EditNorthWindForm() { InitializeComponent(); CenterToScreen(); var view= WorkBenchSingleTon.WorkBench.GetTabPage("Customers"); if (view != null) { var control = view.ViewContentControl as NorthWind.NortWindControl; if (control.gridView.gridView1.GetSelectedRow

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

devexpress练习生

你一杯我一杯,一起喝一杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值