2023年2月

有时候,基于对一些年份、月份的统计,需要集成多个数值指标进行分析,因此就需要把多种数据放到一个图形里面展现,也成为多重坐标轴,多重坐标轴可以是多个X轴,也可以是Y轴,它们的处理方式类似。本文通过一个例子对这个方面进行介绍,希望给大家有一个很好的参考。

首先我们先来看一个图形例子,我们可以从里面图形的右边看到有多个Y轴,一个Y轴代表一个指标分析,X轴为月份。

上图是采用了DevExpress的ChartControl图表控件来实现的,这个控件提供了SecondaryAxisY对象来处理多重坐标的问题。

1、准备数据并绑定

首先,,拖动ChartControl控件到Form界面上,然后设计好布局。

下面
为了测试
准备了几项数据,绑定在下面列表GridControl对象里面,然后把数据绑定到图表对象里面,如下代码。具体处理的时候,我们从数据库获取对应指标的数据即可实现动态绑定。

        /// <summary>
        ///准备数据内容/// </summary>
        /// <returns></returns>
        privateDataTable CreateData()
{
DataTable dt
= newDataTable();
dt.Columns.Add(
new DataColumn("类型"));
dt.Columns.Add(
new DataColumn("2005-1月", typeof(decimal)));
dt.Columns.Add(
new DataColumn("2005-2月", typeof(decimal)));
dt.Columns.Add(
new DataColumn("2005-3月", typeof(decimal)));
dt.Columns.Add(
new DataColumn("2005-4月", typeof(decimal)));
dt.Columns.Add(
new DataColumn("2005-5月", typeof(decimal)));
dt.Columns.Add(
new DataColumn("2005-6月", typeof(decimal)));

dt.Rows.Add(
new object[] { "员工人数", 437, 437, 414, 397, 387, 378});
dt.Rows.Add(
new object[] { "人均月薪", 3964, 3961, 3979, 3974, 3967, 3972});
dt.Rows.Add(
new object[] { "成本TEU", 3104, 1339, 3595.8, 3154.5, 2499.8, 3026});
dt.Rows.Add(
new object[] { "人均生产率", 7.1, 3.06, 8.69, 7.95, 6.46, 8.01});
dt.Rows.Add(
new object[] { "占2005年3月人数比例", 1.06, 1.06, 1, 0.96, 0.93, 0.91});returndt;
}
private void Form1_Load(objectsender, EventArgs e)
{
DataTable dt
=CreateData();this.gridControl1.DataSource =dt;

CreateChart(dt);
}

2、创建图表图形

        private voidCreateChart(DataTable dt)
{
#region Series //创建几个图形的对象 Series series1 = CreateSeries("员工人数", ViewType.Line, dt, 0);
Series series2
= CreateSeries("人均月薪", ViewType.Line, dt, 1);
Series series3
= CreateSeries("成本TEU", ViewType.Line, dt, 2);
Series series4
= CreateSeries("人均生产率", ViewType.Line, dt, 3);
Series series5
= CreateSeries("占2005年3月人数比例", ViewType.Line, dt, 4);#endregionList<Series> list = new List<Series>() { series1, series2, series3, series4, series5 };
chartControl1.Series.AddRange(list.ToArray());
chartControl1.Legend.Visible
= false;
chartControl1.SeriesTemplate.LabelsVisibility
=DefaultBoolean.True;for (int i = 0; i < list.Count; i++)
{
list[i].View.Color
=colorList[i];

CreateAxisY(list[i]);
}
}

为了简化代码,并方便处理,上面代码中提取了两个函数进行了独立处理。

CreateSeries用于创建一个典型的图形,如一条曲线。CreateAxisY用来创建一个多重坐标轴。

CreateSeries用于创建一个典型的图形的源码如下所示。值得注意的是series.ArgumentScaleType =
ScaleType.Qualitative
;这句代码必须设置,否则默认会把“2005年1月”内容转换为日期类型,显示不恰当的内容。

        /// <summary>
        ///根据数据创建一个图形展现/// </summary>
        /// <param name="caption">图形标题</param>
        /// <param name="viewType">图形类型</param>
        /// <param name="dt">数据DataTable</param>
        /// <param name="rowIndex">图形数据的行序号</param>
        /// <returns></returns>
        private Series CreateSeries(string caption, ViewType viewType, DataTable dt, introwIndex)
{
Series series
= newSeries(caption, viewType);for (int i = 1; i < dt.Columns.Count; i++)
{
string argument = dt.Columns[i].ColumnName;//参数名称 decimal value = (decimal)dt.Rows[rowIndex][i];//参数值 series.Points.Add(newSeriesPoint(argument, value));
}
//必须设置ArgumentScaleType的类型,否则显示会转换为日期格式,导致不是希望的格式显示//也就是说,显示字符串的参数,必须设置类型为ScaleType.Qualitative series.ArgumentScaleType =ScaleType.Qualitative;
series.LabelsVisibility
= DevExpress.Utils.DefaultBoolean.True;//显示标注标签 returnseries;
}

CreateAxisY用来创建一个多重坐标轴的代码如下所示,注意这里多重坐标,使用了和Series一直的
View.Color
颜色,这样方便区分。

        /// <summary>
        ///创建图表的第二坐标系/// </summary>
        /// <param name="series">Series对象</param>
        /// <returns></returns>
        privateSecondaryAxisY CreateAxisY(Series series)
{
SecondaryAxisY myAxis
= newSecondaryAxisY(series.Name);
((XYDiagram)chartControl1.Diagram).SecondaryAxesY.Add(myAxis);
((LineSeriesView)series.View).AxisY
=myAxis;
myAxis.Title.Text
=series.Name;
myAxis.Title.Alignment
= StringAlignment.Far; //顶部对齐 myAxis.Title.Visible = true; //显示标题 myAxis.Title.Font = new Font("宋体", 9.0f);

Color color
= series.View.Color;//设置坐标的颜色和图表线条颜色一致 myAxis.Title.TextColor=color;
myAxis.Label.TextColor
=color;
myAxis.Color
=color;returnmyAxis;
}

3、Web界面的展现和代码处理

本来以为在Web上,使用DevExpress控件实现上图的图表很麻烦,没想到它们的对象关系及属性完全一样,复制代码就基本解决问题了,界面代码变化一点点而已。

<%@ Page Language="C#"AutoEventWireup="true"CodeBehind="Default.aspx.cs"Inherits="WebLineStatisticChart.Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<htmlxmlns="http://www.w3.org/1999/xhtml">
<headrunat="server">
    <title></title>
</head>
<body>
    <formid="form1"runat="server">
    <p><h3align="center"style="text-align: left">D线产量、薪酬、人员、生成率趋势图</h3></p>
    <dx:WebChartControlID="chartControl1"runat="server"Height="452px"Width="1013px">
    </dx:WebChartControl>
    <div>
    
    </div>
    <br/>
    <dx:ASPxGridViewID="ASPxGridView1"runat="server"Theme="Aqua">
        <SettingsPagerVisible="False">
        </SettingsPager>
    </dx:ASPxGridView>
    </form>
</body>
</html>

后台代码,除了绑定数据的代码略有不同外,其他完全一致。

    public partial classDefault : System.Web.UI.Page
{
protected void Page_Load(objectsender, EventArgs e)
{
if (!this.IsPostBack)
{
//D线产量、薪酬、人员、生成率趋势图 DataTable dt =CreateData();//绑定数据源到表格里面 this.ASPxGridView1.DataSource =dt;this.ASPxGridView1.DataBind();

CreateChart(dt);
}
}
.............

Web上图表的效果如下所示,鼠标放到图形节点上,还有动态提示,很友好。

4、 图表的打印

ChartControl提供了很好的打印功能,使用代码很简单。

        private void btnPrint_Click(objectsender, EventArgs e)
{
this.chartControl1.ShowPrintPreview(DevExpress.XtraCharts.Printing.PrintSizeMode.Zoom);
}

得到的效果如下所示,基本上能够满足要求了,当然复杂的定制打印需要另外的处理代码了。

打印也可以使用下面的代码,效果差不多,但是定制性比较强一点。

        private void btnPrint_Click(objectsender, EventArgs e)
{
//this.chartControl1.ShowPrintPreview(DevExpress.XtraCharts.Printing.PrintSizeMode.Zoom); DevExpress.XtraPrintingLinks.CompositeLink compositeLink = newDevExpress.XtraPrintingLinks.CompositeLink();
DevExpress.XtraPrinting.PrintingSystem ps
= newDevExpress.XtraPrinting.PrintingSystem();

compositeLink.PrintingSystem
=ps;
compositeLink.Landscape
= true;
compositeLink.PaperKind
=System.Drawing.Printing.PaperKind.A4;

DevExpress.XtraPrinting.PrintableComponentLink link
= newDevExpress.XtraPrinting.PrintableComponentLink(ps);
ps.PageSettings.Landscape
= true;
link.Component
= this.chartControl1;
compositeLink.Links.Add(link);

link.CreateDocument();
//建立文档 ps.PreviewFormEx.Show();//进行预览 }

最近一直忙于公司的事情,虽然一直在做一些相关的技术研究,但是很久没能静下心来好好写写博客文章了,想想也有半个月之多了,这半个月来,也一直致力于改善我的WInform开发框架,使得自己及客户使用起来更加方便,更加友好,更加高效。本篇文章就是介绍最近框架改善的其中一个闪光点"通用高级查询模块",高级查询模块,在很多程序模块中都很常见,也是给客户扩展查询的一个很好的补充,由于我一直希望我的Winform开发框架能够精益求精,所以做了这个模块,希望对今后我自己所有的项目以及框架本身,都能高效的使用。

1、通用高级查询模块的用途及介绍

既然称之为通用查询模块,那么他就不能与具体的表字段有耦合关系,但是要实现具体的查询,必须通过某种方式进行属性传递,实现更直观友好的字段查询功能。高级查询模块,在很多完善的程序上都会提供,用于满足用户对特定的字段,添加特定的条件进行,因为一般情况下,由于版面的限制界面上查询的内容比较有限,只是把一些很常见、重要的字段作为查询输入,如果表字段比较多,那么对有些特殊的字段就无所适从。

例如,我一个病人资料管理系统的界面如下所示

这个界面已经放置的比较多输入控件进行查询了,如果放置更多,超过3行就会导致感觉比较臃肿了。所以合理的应该是把不常用的放到高级查询里面,这样用户需要更多条件的定制,可以在高级查询中对每个字段都能进行搜索,常规的在主界面即可查询。

我的高级查询查询模块也是基于这个道理,因此,在主界面增加一个高级查询按钮入口,如上图所示,单击后,显示一个所有字段的列表,如下界面。

这样所有表的字段均可进行查询了,但是我们注意到,这个界面并没有输入条件的地方,没错,这个只是显示条件内容,浮在主界面的查询列表上面,如果设置条件后,主界面的列表会根据高级查询条件进行查询,这样也是符合客户实际的期望的。

在介绍输入条件的时候,我们注意到,查询输入,基本上可以分为几类:其一是常规的文本类型,使用文本框替代即可;其二是下拉列表类型,用户从列表下面选择内容;其三是日期类型,需要用户指定开始日期和结束日期;其四是数字类型,需要用户指定起始和结束的数值。

刚才说到,上面的高级查询界面,只是显示,但用户双击列表或者按下回车键,那么根据字段类型,弹出对应上面说到的四种输入框,用于接收用户的输入。

1)常规的文本类型条件输入界面:

下拉列表类型条件输入界面:

日期类型条件输入界面:

数字类型条件输入界面:

输入以上几种条件后,高级查询界面里面会显示友好的条件内容,确保用户能够看懂输入的条件,如下所示是输入几个不同类型的条件的显示内容。

日期类型和数字类型,为了方便,都可以单独输入其中的一部分作为条件进行高级查询。

为了方便,高级查询模块的内容,可以进行查询后在主界面列表中显示,也可以使用传统的查询按钮进行查询,使用传统查询按钮后,高级查询条件将失效,但是保留用户的输入,第二次打开界面后,会加载之前的输入条件,这样比较人性化一些,用户可以随时自由切换两种查询方式。

对于一些字段多的历史资料表,这种高级查询模式更加能够发挥其作用,用户可以在几十个甚至上百个字段中选择合适的条件,然后输入进行查询,这样能够使得客户更加有认同感,例如下面是一个病人历史档案资料表的高级查询界面,里面很多字段,使用这个扩展查询就是最合适不过了。

2、通用高级查询模块的实现思路

1)获取数据库字段名称和类型列表

首先前面说到,我是根据字段类型进行不同的控件显示并输入的,因此需要一个通用的函数获取字段的名称和类型,作为参考,由于这个功能比较通用,因此把它放在我的Winform开发框架的数据库基类的抽象类里面,所有的数据库访问类均继承不用重复写代码。

        /// <summary>
        ///获取表的字段名称和数据类型列表。/// </summary>
        /// <returns></returns>
        publicDataTable GetFieldTypeList()
{
DataTable dt
= DataTableHelper.CreateTable("ColumnName,DataType");
DataTable schemaTable
=GetReaderSchema(tableName);if (schemaTable != null)
{
foreach (DataRow dr inschemaTable.Rows)
{
string columnName = dr["ColumnName"].ToString().ToUpper();string netType = dr["DataType"].ToString().ToLower();

DataRow row
=dt.NewRow();
row[
"ColumnName"] =columnName;
row[
"DataType"] =netType;

dt.Rows.Add(row);
}
}
returndt;
}
/// <summary> ///获取指定表的元数据,包括字段名称、类型等等/// </summary> /// <param name="tableName">数据库表名</param> /// <returns></returns> private DataTable GetReaderSchema(stringtableName)
{
DataTable schemaTable
= null;string sql = string.Format("Select * FROM {0}", tableName);
Database db
=CreateDatabase();
DbCommand command
=db.GetSqlStringCommand(sql);try{using (IDataReader reader =db.ExecuteReader(command))
{
schemaTable
=reader.GetSchemaTable();
}
}
catch(Exception ex)
{
LogTextHelper.Error(ex);
}
returnschemaTable;
}

2)设置高级查询的字段显示(有时候可能需要过滤部分字段)

3)指定下拉列表的数据内容

刚才说到,有一些下拉列表字段的输入,那么下拉列表也就需要指定里面的列表内容了,高级查询模块,指定下拉列表的代码如下所示。

         dlg.AddColumnListItem("MidVideo", GetFieldListItem("MidVideo"));
dlg.AddColumnListItem(
"InDiagnosis", GetFieldListItem("InDiagnosis"));
dlg.AddColumnListItem(
"DirectorSurgeon", GetFieldListItem("DirectorSurgeon"));
dlg.AddColumnListItem(
"TumorPart", GetFieldListItem("TumorPart"));
dlg.AddColumnListItem(
"LeaveDiagnosis", GetFieldListItem("LeaveDiagnosis"));
dlg.AddColumnListItem(
"IsFirstTime", GetFieldListItem("IsFirstTime"));
dlg.AddColumnListItem(
"Professor", GetFieldListItem("Professor"));

对于一些固定的列表项目,我们也可以通过下面代码进行绑定。

                dlg.AddColumnListItem("Sex", new List<CListItem>() { new CListItem(""), new CListItem("") });
dlg.AddColumnListItem(
"HasReferral", new List<CListItem>() { new CListItem(""), new CListItem("") });

4)和普通查询功能并存

为了使得传统查询按钮,和高级查询能够并存,我们需要存储一个高级查询的查询对象,但传统查询的时候,我们把高级查询对象设置为空即可屏蔽高级查询的条件了。

        /// <summary>
        ///高级查询条件语句对象/// </summary>
        privateSearchCondition advanceCondition;/// <summary>
        ///根据查询条件构造查询语句/// </summary> 
        private stringGetConditionSql()
{
//如果存在高级查询对象信息,则使用高级查询条件,否则使用主表条件查询 SearchCondition condition =advanceCondition;if (condition == null)
{
condition
= newSearchCondition();
condition.AddCondition(
"BedNo", this.txtBedNo.Text.Trim(), SqlOperator.Like);
condition.AddCondition(
"TumorPart", this.txtTumorPart.Text.Trim(), SqlOperator.Like);
condition.AddCondition(
"MidVideo", this.txtMidVideo.Text.Trim(), SqlOperator.Like);
condition.AddCondition(
"Name", this.txtName.Text.Trim(), SqlOperator.Like);
condition.AddCondition(
"HospitalNo", this.txtHospitalNo.Text.Trim(), SqlOperator.Like);
condition.AddCondition(
"IDNumber", this.txtIDNumber.Text.Trim(), SqlOperator.Like);
condition.AddCondition(
"InDiagnosis", this.txtInDiagnosis.Text.Trim(), SqlOperator.Like);
condition.AddCondition(
"LeaveDiagnosis", this.txtLeaveDiagnosis.Text.Trim(), SqlOperator.Like);
condition.AddCondition(
"LeaveSpecimens", this.txtLeaveSpecimens.Text.Trim(), SqlOperator.Like);
condition.AddCondition(
"Professor", this.txtProfessor.Text.Trim(), SqlOperator.Like);
condition.AddCondition(
"Note", this.txtNote.Text.Trim(), SqlOperator.Like);
condition.AddCondition(
"Pathology", this.txtPathology.Text.Trim(), SqlOperator.Like);
condition.AddCondition(
"IsFirstTime", this.txtIsFirstTime.Text.Trim(), SqlOperator.Like);
..................

}
string where = condition.BuildConditionSql().Replace("Where", "");return where;
}
        /// <summary>
        ///查询数据操作/// </summary>
        private void btnSearch_Click(objectsender, EventArgs e)
{
advanceCondition
= null;//必须重置查询条件,否则可能会使用高级查询条件了 BindData();
}

5)根据类型转换不同的窗体

为了输入方便,我定义了几个不同输入内容的窗体,然后根据不同的字段类型,构造不同的窗体并进行显示,这样比较合理显示并接收客户的条件输入。

        private void gridControl1_MouseDoubleClick(objectsender, MouseEventArgs e)
{
int[] rowSelected = this.gridView1.GetSelectedRows();if (rowSelected.Length == 0) return;string fieldName = this.gridView1.GetFocusedRowCellValue("字段").ToString();string fieldDisplay = this.gridView1.GetFocusedRowCellValue("字段名称").ToString();
FieldType fieldType
= (FieldType)this.gridView1.GetFocusedRowCellValue("字段类型");string fieldValue = this.gridView1.GetFocusedRowCellValue("查询条件值").ToString();#region 根据类型转换不同的窗体FrmQueryBase dlg= null;if (fieldType ==FieldType.Text)
{
dlg
= newFrmQueryTextEdit();
}
else if (fieldType ==FieldType.Numeric)
{
dlg
= newFrmQueryNumericEdit();
}
else if (fieldType ==FieldType.DateTime)
{
dlg
= newFrmQueryDateEdit();
}
else if (fieldType ==FieldType.DropdownList)
{
dlg
= newFrmQueryDropdown();
}
#endregion

6)主窗体的事件响应

由于弹出的高级查询窗体,里面的查询以及清除操作,都是通过主窗体的事件进行处理,因此,弹出的高级查询窗体,只需要调用相应的事件即可。

            dlg.DataClear += newFrmQueryBase.DataClearEventHandler(dlg_DataClear);if (dlg.ShowDialog() ==System.Windows.Forms.DialogResult.OK)
{
//更新查询界面显示 UpdateFieldValue(fieldName, dlg.ReturnValue, dlg.ReturnDisplay);//更新父窗体的数据显示 ProcessDataSearch(null, null);
}

这样我们就能把传统查询功能和高级查询功能合并一起,发挥最大的作用,是我们的程序能够尽量满足客户的需求,获得更加好的反馈和支持了。

在很多情况下,我们需要对数据库进行加密,特别是Access数据库、Sqlite数据库,这些直接部署在客户端的数据,因为数据也是客户的资产,数据库总是存在很多相关的秘密或者重要的业务数据,所以一般来说,数据库是比较敏感的。由于我的Winform开发框架数据库访问模块中采用了EnterpriseLibrary模块,因此其实也可以使用其中的
Crytography Application Block进行加解密处理。

由于不想引入太多这些额外的模块,或者为了简化客户端的配置考虑,直接在EnterpriseLibrary模块内部对数据库连接字符串,增加密码部分,也是一个有效的方法,本文通过介绍这种方式,实现对加密
Access数据库连接字符串的
处理,并能顺利访问带密码的Access数据库。

1、数据库选择

如果要加密的是Access数据库,必须选择Access2007以上版本,这个版本的密码是经过128位的特殊处理,目前好像还是比较难破解,也好像没有找到合适的破解工具,如果是Access2000,那破解密码的工具真是多了去的了。

Access2007版本的数据库引擎,一般没有安装Office2007的电脑,是需要特别安装AccessDatabaseEngine这个驱动的。否则会提示“
The 'Microsoft.ACE.OLEDB.12.0' provider is not registered on the local machine.”(未在本地计算机上注册“microsoft.ACE.oledb.12.0”提供程序)的错误。到微软网站下载对应版本的驱动即可。

如果要加密的是Sqlite数据库,那么也是可以比较有效的,他的密码也是比较难于破解的。

上面两种客户端数据库,打开后先通过软件本身的功能进行加密,密码我们在开发初期就设定即可,客户一般不需要了解,他们或者其他人员复制出去,也无法单独使用,确保了数据库的安全性。

2、数据库加密处理

1)Access的数据库加密

如果是对Access的数据库加密,记住选择Access2007以上的格式,然后以独占方式打开Access文件,如下图所示。

然后在数据库工具选项里面,选择用密码进行加密,在弹出的对话框输入你的密码即可。

Office2007的密码强度还是不错的,至少我找了好几个工具,没能顺利破解出我的密码内容。

2)Sqlite数据库加密

Sqlite的数据库加密也很简单,我这里使用Sqlite Developer打开已有数据库,然后在数据库右键上选择Set Encryption Key,在弹出的对话框中输入密码即可。

我曾经专门针对Sqlite数据库机密机制做了一些了解,好像强度还是不错,而且据说性能损失是很少的。我在开发测试的时候,性能方面确实看不出太大的干扰。

3、连接字符串增加加密内容

由于我们希望数据库配置项尽可能简洁,因此可以不用在配置文件中指定访问密码,这样也避免了加密字符串的泄密问题,我们可以通过动态设置数据库访问对象
Database 的数据库访问密码属性进行内存上处理。如下代码所示是对Access数据库连接字符串进行增加加密字符串的操作。

        #region 连接字符串加密
        /// <summary>
        ///根据配置数据库配置名称生成Database对象/// </summary>
        /// <returns></returns>
        protected virtualDatabase CreateDatabase()
{
Database db
= null;if (string.IsNullOrEmpty(dbConfigName))
{
db
=DatabaseFactory.CreateDatabase();
}
else{
db
=DatabaseFactory.CreateDatabase(dbConfigName);
}

DbConnectionStringBuilder sb
=db.DbProviderFactory.CreateConnectionStringBuilder();
sb.ConnectionString
=GetConnectionString();
GenericDatabase newDb
= newGenericDatabase(sb.ToString(), db.DbProviderFactory);
db
=newDb;returndb;
}
/// <summary> ///动态改变或者连接字符串/// </summary> /// <returns></returns> protected virtual stringGetConnectionString()
{
string connectionString = "";
DatabaseSettings setting
= ConfigurationManager.GetSection("dataConfiguration") asDatabaseSettings;if(setting != null)
{
string defaultConnection =setting.DefaultDatabase;
connectionString
=ConfigurationManager.ConnectionStrings[defaultConnection].ConnectionString;//尝试加密或者解密 if(!connectionString.EndsWith(";"))
{
connectionString
+= ";";
}
connectionString
+= string.Format("Jet OLEDB:Database Password=testpassword;");
}
returnconnectionString;
}
#endregion

通过在访问数据库的时候,动态增加加密的连接字符串,在系统的配置文件上,就和普通的没什么差别了,如下所示

  <connectionStrings>
    <!--路径符号|DataDirectory|代表当前运行目录-->
<addname="Access"providerName="System.Data.OleDb"connectionString="Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|DataDirectory|\Database1.accdb;User ID=Admin;"/>
    <addname="sqlite"providerName="System.Data.SQLite"connectionString="Data Source=|DataDirectory|\ForumMis.db;Version=3;"/>

  </connectionStrings>

上面配置文件,Access数据库用的是2007格式的,所以用了Provider=Microsoft.ACE.OLEDB.12.0,如果是Access2000的话,那么就是Provider=Microsoft.Jet.OLEDB.4.0(基于安全原因,不用)。

Sqlite数据库的操作也类似, 它的加密字符串例子如下:
Data Source
=
c:\mydb.db
;
Version
=
3
;
Password
=
myPassword
;

当然密码可以使用自定义规则约定好的加密字符串,如下所示。

<!--路径符号|DataDirectory|代表当前运行目录-->
    <addname="sqlite"providerName="System.Data.SQLite"connectionString="Data Source=|DataDirectory|\HXHospitalDeploy.db;Version=3;Password=a9Da4YAgNxSeeYuXb5vvGw=="/>

然后在SQLite的访问基类BaseDALSQLite.cs 中,对自定义的密码进行解密处理即可。

        #region 设置数据库的密码
        /// <summary>
        ///根据配置数据库配置名称生成Database对象/// </summary>
        /// <returns></returns>
        protected overrideDatabase CreateDatabase()
{
Database db
= null;if (string.IsNullOrEmpty(DbConfigName))
{
db
=DatabaseFactory.CreateDatabase();
}
else{
db
=DatabaseFactory.CreateDatabase(DbConfigName);
}

DbConnectionStringBuilder sb
=db.DbProviderFactory.CreateConnectionStringBuilder();
sb.ConnectionString
=GetConnectionString(DbConfigName);
GenericDatabase newDb
= newGenericDatabase(sb.ToString(), db.DbProviderFactory);
db
=newDb;returndb;
}
/// <summary> ///动态改变或者连接字符串/// </summary> /// <returns></returns> protected override string GetConnectionString(stringdbConfigName)
{
string connectionString = "";
DatabaseSettings setting
= ConfigurationManager.GetSection("dataConfiguration") asDatabaseSettings;if (setting != null)
{
string connection = string.IsNullOrEmpty(dbConfigName) ?setting.DefaultDatabase : dbConfigName;
connectionString
=ConfigurationManager.ConnectionStrings[connection].ConnectionString;#region 加密解密操作 //使用自定义加密 string password = GetSubValue(connectionString, "password");if (!string.IsNullOrEmpty(password))
{
//尝试使用AES解密 string decryptStr =password;try{
decryptStr
=EncodeHelper.AES_Decrypt(password);
}
catch{
decryptStr
=password; }
connectionString
+= string.Format(";Password={0};", decryptStr);
}
#endregion}returnconnectionString;
}
#endregion

通过动态在连接字符串中增加加密字符串的方式,对于这些部署到客户端的数据库,第一个是提高安全性,用户分析不到那种加密类型;第二个是用户连接字符串不受干扰,可以正常阅读;第三是不用引入更多的模块和代码去实现。

以上只是我Winform开发框架用到的数据库字机密机制的一种,供大家参考和指正。

在前面的一些随笔中,介绍了不少我的Winform框架的特性,上篇随笔《
Winform开发框架之通用高级查询模块
》对其中的通用高级模块进了一个整理说明,本篇继续介绍Winform开发框架重要的一个特性之统计图表的实现。统计图表在很多项目都可能用到,集成到框架中,更方便大家对一些图表项目的设计理解以及功能的重用。在一般的传统的框架中,可以采用ZedGraph开源控件或者微软自带的MSChart进行图表设计,DevExpress控件套件有自己的图表控件,本篇主要介绍基于DevExpress控件的图表控件进行图表设计,进一步丰富我的Winform开发框架。

1、普通统计图表模块

这里指的普通统计图表,只是对表某一项目进行单一的统计,可以从饼状图、柱状图的图表中体现这些项目各自所占的比例和数值,在我的普通统计图表模块中,包括了饼状图、柱状图和数据表格,这样更方便对数据进行全面的分析和查看。整个模块是可以重用的,除了制定字段属性就可以比较合理的展现出不同分类项目的统计效果了,具体效果图如下所示。

上面的统计图表中,还包含了下面两个功能模块,如下所示。

通过以上饼图、柱状图以及数据报表,我们可以很清晰地看到各种统计项目的数值和整体直观的展现图表了。

由于对这类型的图表进行了自定义控件的封装,因此调用非常方便,调用代码如下所示。

1)绑定统计树形列表

为了给用户展示框架(或者项目)支持的报表项目,我们需要在左边的树形列表中初始化一些报表项目,具体代码如下所示。

            #region 备件信息统计
            this.treeItemDetail.Nodes.Clear();
TreeNode node;

node
= new TreeNode("备件属类统计", 0, 0);
node.Tag
= "ItemBigType";this.treeItemDetail.Nodes.Add(node);

node
= new TreeNode("备件类别统计", 0, 0);
node.Tag
= "ItemType";this.treeItemDetail.Nodes.Add(node);

node
= new TreeNode("备件材质统计", 0, 0);
node.Tag
= "Material";this.treeItemDetail.Nodes.Add(node);

node
= new TreeNode("备件名称统计", 0, 0);
node.Tag
= "ItemName";this.treeItemDetail.Nodes.Add(node);

node
= new TreeNode("所属库房统计", 0, 0);
node.Tag
= "WareHouse";this.treeItemDetail.Nodes.Add(node);

node
= new TreeNode("所属部门统计", 0, 0);
node.Tag
= "Dept";this.treeItemDetail.Nodes.Add(node);//自定义输入字段统计 node = new TreeNode("备件数据动态统计", 0, 0);
node.Tag
= "Customed";this.treeItemDetail.Nodes.Add(node);#endregion

上面的树形列表中,我们给Tag赋值,一般情况下是表字段的名称,有些特殊的,则采用Customed来表示,我们响应树形列表控件的操作,根据Tag的不同,切换到不同的报表自定义控件进行展现(包括自定义动态项目统计图表和普通统计图表项目)。

由于我们对图表的展现进行了比较合理的封装,因此基本上普通的图表统计项目,只是字段名称的不同。

对于普通统计图表项目FrmCategoryReport,这个是一个自定义控件来的,方便动态加载到右边的展示Panel区域,这样我们就能根据不同类型的报表动态加载。

创建图表的代码如下所示。

        private DataTable dt = null;private voidBindData()
{
if (string.IsNullOrEmpty(FieldName)) return;//设置报表标题 this.Text =ReportTitle;this.lblReportTitle.Text =ReportTitle;this.chartPie.Series.Clear();this.chartBar.Series.Clear();string where =GetConditionSql();
dt
= BLLFactory<ItemDetail>.Instance.GetReportData(FieldName, where);this.gridControl1.DataSource =dt;if (dt != null && dt.Rows.Count > 0)
{
this.chartPie.DataSource =dt;

Series pieSeries
=CreateSeries(dt, DevExpress.XtraCharts.ViewType.Pie3D, NumericFormat.Percent);
chartPie.Series.Add(pieSeries);
chartPie.Legend.Visible
= true;

PieSeriesLabel label
= pieSeries.Label asPieSeriesLabel;
((PiePointOptions)label.PointOptions).PercentOptions.PercentageAccuracy
= 4;
((PiePointOptions)label.PointOptions).PercentOptions.ValueAsPercent
= true;

label.Position
= PieSeriesLabelPosition.TwoColumns; //设置饼图上lable的显示方式,此方式将独立出一个列显示lable (pieSeries.View as DevExpress.XtraCharts.Pie3DSeriesView).ExplodeMode = PieExplodeMode.All; //突出显示饼块 (pieSeries.View as DevExpress.XtraCharts.Pie3DSeriesView).ExplodedDistancePercentage = 5;//(pieSeries.View as DevExpress.XtraCharts.PieSeriesView).RuntimeExploding = true;//设置了他,你就可以把你喜欢的饼块拖出来。。。 this.chartBar.DataSource =dt;
chartBar.Series.Add(CreateSeries(dt, DevExpress.XtraCharts.ViewType.Bar, NumericFormat.General));
chartBar.Legend.Visible
= false;
chartBar.SeriesTemplate.LabelsVisibility
=DefaultBoolean.True;
}
this.xtraTabControl1.SelectedTabPageIndex = 0;
}

其中我们注意到,创建图表的Series对象的方法,我进行了进一步的封装。

Series pieSeries = CreateSeries(dt, DevExpress.XtraCharts.ViewType.Pie3D, NumericFormat.Percent);

其中CreateSeries方法代码如下所示。

        /// <summary>
        ///创建图表的Series对象,可以指定相应的类型/// </summary>
        /// <param name="dt">数据源</param>
        /// <param name="viewType">图表类型,如DevExpress.XtraCharts.ViewType.Pie3D,DevExpress.XtraCharts.ViewType.Bar</param>
        /// <param name="format">显示格式,如百分比NumericFormat.Percent,NumericFormat.General</param>
        /// <returns></returns>
        privateSeries CreateSeries(DataTable dt, DevExpress.XtraCharts.ViewType viewType, NumericFormat format)
{
Series series
= new Series("Serices1", viewType);
series.DataSource
=dt;
series.ArgumentScaleType
=ScaleType.Qualitative;
series.ArgumentDataMember
= "argument";
series.ValueScaleType
=ScaleType.Numerical;
series.ValueDataMembers.AddRange(
new string[] { "datavalue"});
series.PointOptions.PointView
=PointView.ArgumentAndValues;
series.PointOptions.ValueNumericOptions.Format
=format;if (format ==NumericFormat.Number)
{
//series.PointOptions.ValueNumericOptions.Precision = 0; }

series.LabelsVisibility
= DevExpress.Utils.DefaultBoolean.True;//显示标注标签 returnseries;
}

在Winform开发框架中,我试图对备件仓库中不同类型的设备进行一个库存统计,也得到了这类型的图表如下所示。

2、动态项目统计图表模块

有时候,我们对于表里面的数据,可能要对不同类型的内容进行动态的统计,以确定他们各自的比例情况,那么这些动态项目的统计图表就比较合适了,例如,对于病人资料的管理,我们可能需要统计各种病种所占的比例或者各种职业类型的犯病率,这些不太确定的统计项目,就需要一个能够支持动态项目的统计图表进行支撑,对于本Winform框架,为了较好呈现这个类型报表的意义,我选择了对备件类型所占的比例进行一个统计分析,得到下面的统计图表,如下所示。

上面的图表统计,除了能够根据一些条件进行限定查询范围外,还可以对一些预设的统计字段进行动态选取,然后根据字段里面的各种内容(统计项目)进行统计,这样就可以比较有效的统计出各种类型的数值和比例了。

动态项目的统计,主要是对不同字段,以及对应不同字段的内容进行一个条件分析,把它们的统计数字构造一个数据表格即可进行合理展现,如下部门代码所示。

                string fieldName =fieldItem.Value;int totalCount = BLLFactory<ItemDetail>.Instance.GetRecordCount(where);//计算总人数
                foreach (string searchItem in this.lstItems.Items)
{
string condition = string.Format("{0} like '%{1}%'", fieldName, searchItem);if (!string.IsNullOrEmpty(where))
{
condition
+= string.Format("AND {0}", where);
}
int countValue = BLLFactory<ItemDetail>.Instance.GetRecordCount(condition);//计算总人数 countRepeat +=countValue;

row
=dt.NewRow();
row[
0] =searchItem;
row[
1] =countValue;
dt.Rows.Add(row);
}

3、多重坐标对比统计图表模块

有时候我们可能需要对某年各个月份的数值进行对比统计,已看出各种统计项目的发展趋势或者对比效果,这就要求可以对多份数据进行合并展现,这种在图表展现中可以使用多重坐标对比的统计图表模块进行展现,如我上篇随笔《
DevExpress控件使用之多重坐标图形的绘制
》就对这类型的图表统计进行了比较细致的分析和说明,相关的效果图如下所示。

在Winform框架里面,可以对某一年各月份的出入库数量进行一个分析,得到下面的统计图。

以上数据不多,展现可能不太好看,下面我给出我另一个软件系统的界面,其中对病人的出入院记录进行一个统计对比分析,统计报表如下所示。

4、打印及导出报表

很多时候,我们可能需要对呈现的报表进行一个打印和导出操作,对于DevExpress的ChartControl控件,打印操作很简单,你甚至可以用一行代码进行打印操作。

this.chartControl1.ShowPrintPreview(DevExpress.XtraCharts.Printing.PrintSizeMode.Zoom);

或者增加更复杂的定制操作,代码如下所示。

        private void btnPrint_Click(objectsender, EventArgs e)
{
//this.chartControl1.ShowPrintPreview(DevExpress.XtraCharts.Printing.PrintSizeMode.Zoom); DevExpress.XtraPrintingLinks.CompositeLink compositeLink = newDevExpress.XtraPrintingLinks.CompositeLink();
DevExpress.XtraPrinting.PrintingSystem ps
= newDevExpress.XtraPrinting.PrintingSystem();

compositeLink.PrintingSystem
=ps;
compositeLink.Landscape
= true;
compositeLink.PaperKind
=System.Drawing.Printing.PaperKind.A4;

DevExpress.XtraPrinting.PrintableComponentLink link
= newDevExpress.XtraPrinting.PrintableComponentLink(ps);
ps.PageSettings.Landscape
= true;
link.Component
= this.chartControl1;
compositeLink.Links.Add(link);

link.CreateDocument();
//建立文档 ps.PreviewFormEx.Show();//进行预览 }

对于导出报表,我们 一般要求图文并茂,纯粹导出图表的图片或者列表,都是一件很简单的事情,但是要把它们整合一起,并进行合理的排版,这需要费一点心思才能做好。

首先我们来介绍一下,一般图表控件都有导出Image图片类型的操作,DevExpress控件也不例外,它的操作代码如下所示。

                    //插入图片到Excel里面
                    using (MemoryStream stream = newMemoryStream())
{
stream.Position
= 0;
ChartControl chart
=(ChartControl)chartControl1.Clone();
chart.Size
= new Size(800, 400);

chart.ExportToImage(stream, ImageFormat.Jpeg);
//进行存储操作//worksheet.Pictures.Add(startRow, 0, stream); }

为了更好的整合几个图表和数据报表,我们这里采用了Aspose.Cell控件进行代码操作,把这些图表动态整合到一个Excel文档里面进行导出,全部代码如下所示。

        private void btnExport_Click(objectsender, EventArgs e)
{
try{
DataTable dt
= this.gridControl1.DataSource asDataTable;if (dt == null || dt.Rows.Count == 0)
{
MessageDxUtil.ShowTips(
"没有数据需要导出!");return;
}
string saveDocFile = FileDialogHelper.SaveExcel(string.Format("{0}.xls", ReportTitle), "C:\\");if (!string.IsNullOrEmpty(saveDocFile))
{
Workbook workbook
= newWorkbook();
Worksheet worksheet
= workbook.Worksheets[0];
worksheet.PageSetup.Orientation
= PageOrientationType.Landscape;//横向打印 worksheet.PageSetup.Zoom = 100;//以100%的缩放模式打开 worksheet.PageSetup.PaperSize =PaperSizeType.PaperA4;#region 表头及说明信息Range range; Cell cell;stringcontent;int colSpan = 3;
range
= worksheet.Cells.CreateRange(0, 0, 1, colSpan);
range.Merge();
range.RowHeight
= 20;
range.Style
=CreateTitleStyle(workbook);
cell
= range[0, 0];
cell.PutValue(ReportTitle);

range
= worksheet.Cells.CreateRange(1, 0, 1, colSpan);
range.Merge();
range.RowHeight
= 15;
cell
= range[0, 0];
content
= string.Format("统计报表详细列表如下:");
cell.PutValue(content);
#endregion #region 生成报表头部表格Style headStyle= CreateStyle(workbook, true);
Style normalStyle
= CreateStyle(workbook, false);int startRow = 2;int startCol = 0;int index = 0;foreach (DataColumn col indt.Columns)
{
range
= worksheet.Cells.CreateRange(startRow, index, 2, 1);
range.Merge();
range.Style
=headStyle;
cell
= range[0, 0];
cell.PutValue(col.ColumnName);
cell.Style
=headStyle;
index
++;
}
#endregion //写入数据到Excel startRow = startRow + 2;for (int i = 0; i < dt.Rows.Count; i++)
{
startCol
= 0;for (int j = 0; j < dt.Columns.Count; j++)
{
DataRow dr
=dt.Rows[i];
cell
=worksheet.Cells[startRow, startCol];
cell.PutValue(dr[j]);
cell.Style
=normalStyle;

startCol
++;
}
startRow
++;
}
//写入图注 startRow += 1;//跳过1行 range = worksheet.Cells.CreateRange(startRow++, 0, 1, colSpan);
range.Merge();
range.RowHeight
= 15;
cell
= range[0, 0];
cell.PutValue(
"以曲线图展示如下:");//插入图片到Excel里面 using (MemoryStream stream = newMemoryStream())
{
stream.Position
= 0;
ChartControl chart
=(ChartControl)chartControl1.Clone();
chart.Size
= new Size(800, 400);

chart.ExportToImage(stream, ImageFormat.Jpeg);
worksheet.Pictures.Add(startRow,
0, stream);
}

workbook.Save(saveDocFile);
if (MessageUtil.ShowYesNoAndTips("保存成功,是否打开文件?") ==System.Windows.Forms.DialogResult.Yes)
{
System.Diagnostics.Process.Start(saveDocFile);
}
}
}
catch(Exception ex)
{
LogTextHelper.Error(ex);
MessageUtil.ShowError(ex.Message);
return;
}
}

导出Excel的效果如下所示。

5、整体Winform开发框架的特点介绍

下面是我对Winform开发框架大的方面的特性进行一个整理,希望能够概括整个框架的一些常用特性,较之前的图形,增加了高级查询模块,统计图表模块等内容。希望大家批评指正。

权限管理系统,一直是很多Mis系统和一些常见的管理系统所需要的,所以一般可以作为独立的模块进行开发,需要的时候进行整合即可,不需要每次从头开发,除非特殊的系统需求。我在Winform开发框架介绍中的随笔中,很早之前在《
Winform开发框架之权限管理系统
》就写过关于我的通用权限管理系统的一些介绍,当时这个版本的还是传统样式的,界面如下所示。


由于我的Winform开发框架需要,我把权限管理系统、字典管理模块、分页控件等都扩展了支持传统样式、DotNetBar控件样式,以及DevExpress控件样式。这些样式的界面之前也已经介绍不少了,本文主要介绍使用DevExpress控件样式的权限管理系统,对一些新的功能扩展以及完善,包括系统类型管理、菜单管理、功能管理、登陆日志管理等模块进行介绍。

本权限管理系统包括用户管理、组织机构管理、角色管理、系统类型定义、功能管理、菜单管理、用户登录日志管理,以及对相应对象的权限控制管理等功能。整个权限管理系统是基于RBAC(基于角色的访问控制)方式进行权限控制,是一个独立的权限管理系统,需要的时候业务系统与之进行整合即可,实现通用模块的高效利用和统一管理等方面。

1、系统类型管理

系统类型,是我们在权限系统中定义的一个系统分类,对于不同的系统,我们通过这个定义进行区分,可以实现多个业务系统的管理(虽然一般情况下,我们只是管理一个系统)。系统类型管理界面如下所示。

这个对象的管理,以前一直在权限系统中,通过修改数据库进行初始化,不过我觉得把它作为一个独立的数据进行维护,然后为功能定义、菜单、登陆日志等和系统类型关联的数据进行设置管理,应该是比较方便的。

因为,功能定义是基于某个系统而增加的一系列功能控制ID的定义,方便实现按钮级别和数据级别的权限控制的基础。

菜单的定义,是用来实现基于动态配置功能模块的菜单,引入该模块的初衷是用来方便动态配置Winform或者Web的功能菜单,这个也是不同的系统肯定有不同的菜单了。

登陆日志管理,是用来方便记录来自业务系统对权限系统接口的重要接口调用的记录,如登陆、修改密码等操作,一般情况下,我们用不同的业务系统来区分它们的数据。

2、菜单管理

刚才说到,这里的菜单管理,是指用来实现基于动态配置功能模块的菜单,用来实现基于Winform、Web菜单的集成统一,对于不同的系统,菜单定义是不同的,因此他们是基于某个系统类型下面的数据管理。

为了有效管理菜单数据,我把菜单的关系用树形控件进行展示,并通过SplitContainer控件实现布局的合理切分,可以实现自由拖动,界面效果如下所示。

菜单的数据显示,左边使用了树形控件,数据通过递归方式进行绑定。右边数据是通过我的分页控件进行数据绑定,并且绑定的数据是通过了层级的缩进格式化了的。

树菜单绑定的核心递归代码如下所示。

        /// <summary>
        ///绑定树形数据/// </summary>
        private voidInitTree()
{
treeView1.Nodes.Clear();
treeView1.BeginUpdate();
Cursor.Current
=Cursors.WaitCursor;//先获取系统类型,然后对不同的系统类型下的菜单进行绑定显示 List<SystemTypeInfo> typeList = BLLFactory<SystemType>.Instance.GetAll();foreach (SystemTypeInfo typeInfo intypeList)
{
TreeNode pNode
= newTreeNode();
pNode.Text
= typeInfo.Name;//系统类型节点 pNode.Name =typeInfo.OID;
pNode.ImageIndex
= 0;
pNode.SelectedImageIndex
= 0;this.treeView1.Nodes.Add(pNode);string systemType = typeInfo.OID;//系统标识ID//绑定树控件//一般情况下,对Ribbon样式而言,一级菜单表示RibbonPage;二级菜单表示PageGroup;三级菜单才是BarButtonItem最终的菜单项。 List<MenuNodeInfo> menuList = BLLFactory<SysMenu>.Instance.GetTree(systemType);foreach (MenuNodeInfo info inmenuList)
{
TreeNode item
= newTreeNode();
item.Name
=info.ID;
item.Text
= info.Name;//一级菜单节点 item.Tag = info;//对菜单而言,记录其MenuNodeInfo到Tag中,作为判断依据 item.ImageIndex = 1;
item.SelectedImageIndex
= 1;
pNode.Nodes.Add(item);

AddChildNode(info.Children, item);
}
}

Cursor.Current
=Cursors.Default;
treeView1.EndUpdate();
this.treeView1.ExpandAll();
}
private void AddChildNode(List<MenuNodeInfo>list, TreeNode fnode)
{
foreach (MenuNodeInfo info inlist)
{
TreeNode item
= newTreeNode();
item.Name
=info.ID;
item.Text
= info.Name;//二、三级菜单节点 item.Tag = info;//对菜单而言,记录其MenuNodeInfo到Tag中,作为判断依据 int index = (fnode.ImageIndex + 1 > 3) ? 3 : fnode.ImageIndex + 1;
item.ImageIndex
=index;
item.SelectedImageIndex
=index;
fnode.Nodes.Add(item);

AddChildNode(info.Children, item);
}
}

菜单的列表数据,通过分页控件绑定的代码如下所示。

        /// <summary>
        ///根据查询条件构造查询语句/// </summary> 
        private stringGetConditionSql()
{
SearchCondition condition
= newSearchCondition();
condition.AddCondition(
"Name", this.txtName.Text, SqlOperator.Like);
condition.AddCondition(
"FunctionId", this.txtFunctionId.Text, SqlOperator.Like);
condition.AddCondition(
"Visible", this.txtVisible.Checked ? 1 : 0, SqlOperator.Equal);
condition.AddCondition(
"WinformType", this.txtWinformType.Text, SqlOperator.Like);
condition.AddCondition(
"Url", this.txtUrl.Text, SqlOperator.Like);string where = condition.BuildConditionSql().Replace("Where", "");return where;
}
/// <summary> ///绑定列表数据/// </summary> private voidBindData()
{
//entity this.winGridViewPager1.DisplayColumns = "Name,Icon,Seq,FunctionId,Visible,WinformType,Url";#region 添加别名解析 this.winGridViewPager1.AddColumnAlias("ID", "");this.winGridViewPager1.AddColumnAlias("Name", "显示名称");this.winGridViewPager1.AddColumnAlias("Icon", "图标");this.winGridViewPager1.AddColumnAlias("Seq", "排序");this.winGridViewPager1.AddColumnAlias("FunctionId", "功能ID");this.winGridViewPager1.AddColumnAlias("Visible", "菜单可见");this.winGridViewPager1.AddColumnAlias("WinformType", "Winform窗体类型");this.winGridViewPager1.AddColumnAlias("Url", "Web界面Url地址");#endregion string where =GetConditionSql();
List
<MenuInfo> list = BLLFactory<SysMenu>.Instance.FindWithPager(where, this.winGridViewPager1.PagerInfo);
list
= CollectionHelper<MenuInfo>.Fill("-1", 0, list, "PID", "ID", "Name");this.winGridViewPager1.DataSource = new WHC.Pager.WinControl.SortableBindingList<MenuInfo>(list);this.winGridViewPager1.PrintTitle = "功能菜单信息报表";
}

菜单的编辑界面如下所示。

本篇主要是介绍菜单的管理,对于菜单的动态加载管理,我会在另外一篇Winform开发框架中进行介绍。菜单定义数据里面的功能控件ID,是来自功能模块的功能控件ID,是用来控制不同用户所能访问的菜单资源的。

3、功能管理

功能定义,也是基于某个系统类型下面的,对于不同的业务系统,我们可以集中放在一个权限管理系统里面进行管理,但是功能的定义,根据不同的系统类型进行区分即可,这样的目标是用来实现多种企业应用的有效整合。

功能的管理,是整个权限系统的核心,因为通过对他们的定义,以及权限分配,都会影响各个角色用户可访问的功能;功能定义的数据,其实也是一个树形结构,可以用树控件进行展示出来,如下所示。

4、日志管理

我们知道,一般权限系统,管理用户很常见的,因此用户的登陆日志,一般情况下是由权限管理系统记录即可, 如每次用户登陆的时候,我们记录用户的登陆日志;如果用户修改密码,我们也做一个重要记录,这样对于在业务系统端的管理,我们就不需要管理他们的登陆方面的事件了。

日志管理分为两个部分,一个是通过权限管理系统本身的登录入口进行登录的,一个是通过与之集成的业务系统,通过API调用方式进行登录验证的, 他们的登录接口基本一样,只是部分数据不同。

            try{//判断用户是否登录成功
                string ip =NetworkUtil.GetLocalIP();string macAddr =HardwareInfoHelper.GetMacAddress();string identity = BLLFactory<User>.Instance.VerifyUser(this.txtLogin.Text, this.txtPassword.Text, "WareMis", ip, macAddr);if (!string.IsNullOrEmpty(identity))
{
//进一步判断用户角色 if (BLLFactory<User>.Instance.UserInRole(this.txtLogin.Text, RoleInfo.AdminName))
{
MessageUtil.ShowTips(
string.Format("用户【{0}】身份验证正确", this.txtLogin.Text));
}
else{
MessageUtil.ShowWarning(
"该用户没有管理员权限");return;
}
}
else{
MessageUtil.ShowWarning(
"用户名或密码错误");return;
}
}
catch(Exception err)
{
MessageUtil.ShowError(err.Message);
}

权限管理系统对日志进行统一管理和展示,具体界面如下所示。

整个权限管理系统,目的提高系统开发速度和效率,因此通过独立开发,模块重用,易于集成等方式实现我们生产效率的最大化。