wenmo8 发布的文章

Swift语言是一个新的编程语言,用于iOS, macOS, watchOS, 和 tvOS的开发,不过Swift很多部分内容,我们可以从C或者Objective-C的开发经验获得一种熟悉感。Swift提供很多基础类型,如Int,String,Double,Bool等类型,它和Objective-C的相关类型对应,不过他是值类型,而Objective-C的基础类型是引用类型,另外Swift还提供了几个集合类型,如
Array
,
Set
, 和
Dictionary;Swift引入一些Objective-C里面没有的元祖类型,这个在C#里倒是有类似的,也是这个名词。 Swift语言是一种类型安全的强类型语言,不是类似JavaScript的弱类型,能够在提供开发效率的同时,减少常规出错的可能,使我们在开发阶段尽量发现一些类型转换的错误并及时处理。

常量和变量

let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0

常量用let定义,变量用var定义,它们均可以通过自动推导类型,如上面的就是指定为整形的类型。

也可以通过逗号分开多个定义,如下所示

var x = 0.0, y = 0.0, z = 0.0

如果我们的变量没有初始化值来确定它的类型,我们可以通过指定类型来定义变量,如下所示

var welcomeMessage: String

var red, green, blue: Double

变量的打印,可以在输出字符串中用括号包含变量输出,括号前加斜杠 \ 符号。

print(friendlyWelcome)//Prints "Bonjour!"
print("The current value of friendlyWelcome is \(friendlyWelcome)")//Prints "The current value of friendlyWelcome is Bonjour!"

注释符

// This is a comment.

/* This is also a comment
 but is written over multiple lines. */

/* This is the start of the first multiline comment.
 /* This is the second, nested multiline comment. */
 This is the end of the first multiline comment. */

上面分别是常规的的注释,以及Swift支持嵌套的注释符号

分号

Swift语句的划分可以不用分号,不过你加分号也可以,如果加分号,则可以多条语句放在一行。


let cat = "

运算符是用来检查,更改或组合值的特殊符号或短语。Swift提供的很多常规的运算符,如+、-、*、/、%、=、==等,以及逻辑运算的&&、||等等,基本上不需要重复介绍,我们在这里只需要了解一些不太一样的运算符就可以了。如Swift引入的新运算符,范围操作符号,包括..<和...两个,该随笔介绍Swift常规的运算符中,以及和其他语言有所差异的部分。

赋值运算符

let b = 10
var a = 5
a = b
// a is now equal to 10

赋值语句,处理和其他语言一样。

let (x, y) = (1, 2)
// x is equal to 1, and y is equal to 2

这种代码是类似ECMAScript 6的脚本写法,通过把右边元祖对象解构赋值给左边对应的参数。

数学运算符

1 + 2       // equals 3
5 - 3       // equals 2
2 * 3       // equals 6
10.0 / 2.5  // equals 4.0

这些都是和其他语言没有什么不同,循例列出参考下

对于字符,也可以使用+符号进行连接新的字符串

"hello, " + "world"  // equals "hello, world"

一元操作符中的-、+运算,和算术里面的负负得正,正负得负的意思一样了。

let three = 3
let minusThree = -three       // minusThree equals -3
let plusThree = -minusThree   // plusThree equals 3, or "minus minus three"

let minusSix = -6
let alsoMinusSix = +minusSix  // alsoMinusSix equals -6

组合运算符提供+= 、-=的运算符操作

var a = 1
a += 2
// a is now equal to 3

对比运算符和其他语言差不多

  • 等于 (
    a == b
    )

  • 不等于 (
    a != b
    )

  • 大于 (
    a > b
    )

  • 小于 (
    a < b
    )

  • 大于等于 (
    a >= b
    )

  • 小于等于 (
    a <= b
    )

另外值得注意的是,Swift提供了对比引用的两个操作符号,
===

!==,用来检查两个引用是否完全相等;或者不相等的。而==只是用来对比两个对象的值是否一致。

1 == 1   // true because 1 is equal to 1
2 != 1   // true because 2 is not equal to 1
2 > 1    // true because 2 is greater than 1
1 < 2    // true because 1 is less than 2
1 >= 1   // true because 1 is greater than or equal to 1
2 <= 1   // false because 2 is not less than or equal to 1

对比运算符也经常用来If条件语句里面

let name = "world"
if name == "world" {
    print("hello, world")
} else {
    print("I'm sorry \(name), but I don't recognize you")
}
// Prints "hello, world", because name is indeed equal to "world".

三元运算符

三元运算符 ? :和C#里面表现是一样的

question ? answer1 : answer2

let contentHeight = 40
let hasHeader = true
let rowHeight = contentHeight + (hasHeader ? 50 : 20)

空值转换操作符

空值转换符是对可空类型(可选类型)的一个值得转换出来(
a ?? b
)。

let defaultColorName = "red"
var userDefinedColorName: String?   // defaults to nil
 
var colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName is nil, so colorNameToUse is set to the default of "red"

userDefinedColorName = "green"
colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName is not nil, so colorNameToUse is set to "green"

范围操作符

闭合范围运算符 ... 和半闭合范围运算符 ..< 两个

for index in 1...5 {
    print("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25

半闭合的范围运算符

let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
    print("Person \(i + 1) is called \(names[i])")
}
// Person 1 is called Anna
// Person 2 is called Alex
// Person 3 is called Brian
// Person 4 is called Jack

或者如下使用

for name in names[..<2] {
    print(name)
}
// Anna
// Alex

以及一侧范围的运算符,包括左侧和右侧两个部分

for name in names[2...] {
    print(name)
}
// Brian
// Jack
 
for name in names[...2] {
    print(name)
}
// Anna
// Alex
// Brian

let range = ...5
range.contains(7)   // false
range.contains(4)   // true
range.contains(-1)  // true

逻辑运算符

let allowedEntry = false
if !allowedEntry {
    print("ACCESS DENIED")
}
// Prints "ACCESS DENIED"

let enteredDoorCode = true
let passedRetinaScan = false
if enteredDoorCode && passedRetinaScan {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}
// Prints "ACCESS DENIED"

let hasDoorKey = false
let knowsOverridePassword = true
if hasDoorKey || knowsOverridePassword {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}
// Prints "Welcome!"

if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}
// Prints "Welcome!"

或者使用括号使之更加方便阅读

if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
    print("Welcome!")
} else {
    print("ACCESS DENIED")
}
// Prints "Welcome!"

在我们常规的业务处理中,一般内部处理的接口多数都是以数据库相关的,基于混合式开发的Winform开发框架,虽然在客户端调用的时候,一般选择也是基于Web API的调用,不过后端我们可能不仅仅是针对我们业务数据库的处理,也可以能是调用其他外部接口,如物流、供应商接口等接口,本随笔就是主要介绍基于混合式开发框架如何整合外部API接口的调用。

1、混合式框架的结构介绍

我们知道,混合式的框架是可以在客户端访问Web API服务、WCF服务或者直接连接数据库几种方式的综合,而尤以Web API为最广泛的应用,它的整个框架的结构如下所示。

在客户端中,通过统一的工厂类CallerFactory<T>对相应的接口进行访问,这里主要就是服务器端Web API服务接口的处理,以及客户端对Web API接口的封装,两部分通过一些基类进行简化处理,可以极大提高开发效率。

对于外部第三方的Web API接口,我们也可以在自己的Web API接口中进行包装,使得客户端通过相应的接口进行交互即可,不需要理会内部还是外部的接口,从而实现透明的接口调用。

2、RFID外部接口的整合处理

在一个客户的应用案例中,需要整合服务商RFID接口实现相应的数据交互,本篇随笔也是基于这个案例进行整个过程的分析和操作,使得我们了解在混合框架中如何整合第三方Web API接口为我们内部框架所用。

一般来说,Web API接口,需要明确API的URL、数据提交方式(POST/GET)、提交参数、返回集合,以及一些特殊的数据等,而一般接口的操作,也是需要一个访问令牌的,这些都是Web API接口调用的关键。

基本上我们有了上面Web API的1/2/3步骤的信息就可以进行接口编程了,这些是Web API开发非常重要的信息。

我们需要特别主要到,步骤1中的信息

这里面的token是额外的接口信息,是需要设置Http Request请求的头部信息里面的,是用户身份的重要信息,所以我们一般需要先通过指定的授权接口获取这个token信息。

在这个外部的接口集合里面,我们找到统一登录验证的接口定义如下所示。

通过上面的分析,我们首先需要需要处理好登录验证接口,然后通过接口传递令牌token给其他接口进行数据处理的。

结合我们的混合框架结构,这里我以测试项目TestProject项目为例进行介绍,我们调整WHC.TestProject.Caller项目的对应类,如下所示。

其中Facade层接口类IRFIDService.cs代码如下所示。

    /// <summary>
    ///RFID服务外部接口/// </summary>
[ServiceContract]public interfaceIRFIDService 
{
/// <summary> ///终端用户统一登录验证/// </summary> [OperationContract]
CheckinResult CheckIn(
string username, string password, string device_uuid, string device_type, string last_app_version, stringapp_id);/// <summary> ///获取标签发货通知单批量/// </summary> [OperationContract]
TagOrderAsnResult TagOrderAsn(
int brand_id, string factcode, string start_time, string end_time, PagerInfo pagerInfo, stringtoken);/// <summary> ///标签订单出库物流信息回写/// </summary> [OperationContract]
CommonResult TagOutPost(
string docno_asn, string factcode, string dest_factcode, List<FreightInfo> freight, stringtoken);
}

这里面的接口定义,我们是根据输入参数、输出参数进行定义的,另外token是额外增加的令牌参数,用于请求头部写入信息的。

这个接口的定义其实和我们常规的Web API接口定义没有太多的不同,如下是一个内部客户信息接口定义。

    /// <summary>
    ///客户信息的服务接口/// </summary>
[ServiceContract]public interface ICustomerService : IBaseService<CustomerInfo>{/// <summary>
        ///根据客户名称获取客户列表/// </summary>
        /// <param name="name">客户名称</param>
        /// <returns></returns>
[OperationContract]
List
<CustomerInfo> FindByName(stringname);
}

差别就是它们接口继承类有所不同,外部接口由于不需要和数据库打交道,我们不需要继承IBaseService接口

根据这些接口的定义,我们还需要实现我们具体的Web API 服务,逻辑上它是对外部Web API接口的封装,但是对于客户端来说,并不需要知道是内部还是外部接口,客户端只需要知道如果提交参数或者结果即可。

由于Web API涉及多个参数的数据提交,一般来说这种情况都是以POST方式处理的,数据参数则统一在Web API端通过定义一个JObject对象来传递即可,登录认证的Web API接口定义如下所示。

    /// <summary>
    ///基于RFID的应用接口/// </summary>
    public classRFIDController : BaseApiController
{
/// <summary> ///终端用户统一登录验证/// </summary> /// <param name="param">包含多个属性的对象</param> /// <param name="token">访问令牌</param> [HttpPost]publicCheckinResult CheckIn(JObject param)
{
CheckinResult result
= null;dynamic obj =param;if (obj != null)
{
//使用POST数据 var postData =param.ToJson();//使用具体的URL var queryUrl = "https://***.***.***/api/v6/rfid/terminal/checkin/post";var helper = newHttpHelper();
helper.ContentType
= "application/json";var content = helper.GetHtml(queryUrl, postData, true);
RFIDBaseData
<CheckinResult> jsonResult = JsonConvert.DeserializeObject<RFIDBaseData<CheckinResult>>(content);if (jsonResult != null && jsonResult.code == 0)
{
result
=jsonResult.data;
}
returnresult;
}
else{throw new MyApiException("传递参数错误");
}
}

其中输入的参数这里用了JObject param的参数,我们提交给外部Web API接口的时候,我们把这个参数再次序列号为JSON格式的字符串即可:

var postData =param.ToJson();

其中CheckinResult和RFIDBaseData是根据输入参数、输出结果进行的实体类定义,目的是序列化为强类型的实体类,方便数据处理操作。

在客户端,我们只需要对接好和Web API服务端的接口,那么调用起来就非常方便,其中对应的Web API接口客户端封装类 RFIDCaller 如下所示。

    /// <summary>
    ///对RFID控制的接口调用封装/// </summary>
    public classRFIDCaller : NormalApiService, IRFIDService
{
publicRFIDCaller()
{
this.ConfigurationPath = ApiConfig.ConfigFileName; //Web API配置文件 this.configurationName =ApiConfig.RFID;
}
public CheckinResult CheckIn(string username, string password, string device_uuid, string device_type, string last_app_version, stringapp_id)
{
var action =System.Reflection.MethodBase.GetCurrentMethod().Name;string url =GetNormalUrl(action);var postData = new{
username
=username,
password
=password,
device_uuid
=device_uuid,
device_type
=device_type,
last_app_version
=last_app_version,
app_id
=app_id,
}.ToJson();
var result = JsonHelper<CheckinResult>.ConvertJson(url, postData);returnresult;
}

有了这些,我们直接在客户端的界面里面,就可以通过调用CallerFactory<T>进行处理操作了,如下是客户端窗体获取验证身份令牌数据的代码

        private string token = null;//访问RFID接口的token
        /// <summary>
        ///根据终端用户统一登录验证获取相关访问token/// </summary>
        /// <returns></returns>
        private stringGetRFIDToken()
{
string username = "wuhuacong";string password = "123456";string device_uuid = "aaaaaaa";string device_type = "iphone";string last_app_version = "xxxxxxx";string app_id = "ntdf5543581a2f066e74cf2fe456";var result = CallerFactory<IRFIDService>.Instance.CheckIn(username, password, device_uuid, device_type, last_app_version, app_id);if(result != null)
{
token
=result.token;
}
returntoken;
}

上面是认证身份的接口,其他类型的接口类似的处理方式,如增加了一个

获取标签发货通知单批量

操作后,对应的客户端封装类如下所示。

    /// <summary>
    ///对RFID控制的接口调用封装/// </summary>
    public classRFIDCaller : NormalApiService, IRFIDService
{
publicRFIDCaller()
{
this.ConfigurationPath = ApiConfig.ConfigFileName; //Web API配置文件 this.configurationName =ApiConfig.RFID;
}
public CheckinResult CheckIn(string username, string password, string device_uuid, string device_type, string last_app_version, stringapp_id)
{
var action =System.Reflection.MethodBase.GetCurrentMethod().Name;string url =GetNormalUrl(action);var postData = new{
username
=username,
password
=password,
device_uuid
=device_uuid,
device_type
=device_type,
last_app_version
=last_app_version,
app_id
=app_id,
}.ToJson();
var result = JsonHelper<CheckinResult>.ConvertJson(url, postData);returnresult;
}
public TagOrderAsnResult TagOrderAsn(int brand_id, string factcode, string start_time, string end_time, Pager.Entity.PagerInfo pagerInfo, stringtoken)
{
var action =System.Reflection.MethodBase.GetCurrentMethod().Name;string url = GetNormalUrl(action) + string.Format("?token={0}", token);var postData = new{
page
=pagerInfo.CurrenetPageIndex,
pagesize
=pagerInfo.PageSize,
brand_id
=brand_id,
factcode
=factcode,
start_time
=start_time,
end_time
=end_time,
}.ToJson();
var result = JsonHelper<TagOrderAsnResult>.ConvertJson(url, postData);returnresult;
}

获取标签发货通知单批量

的Web API接口如下代码定义

        /// <summary>
        ///获取标签发货通知单批量/// </summary>
        /// <param name="param"></param>
        /// <param name="token"></param>
        /// <returns></returns>
[HttpPost]public TagOrderAsnResult TagOrderAsn(JObject param, stringtoken)
{
TagOrderAsnResult result
= null;dynamic obj =param;if (obj != null)
{
//使用POST方式 var postData =param.ToJson();var queryUrl = "https://***.***.***/api/v6/rfid/tag/tag_order_asn/get"; var helper = newHttpHelper();
helper.ContentType
= "application/json";
helper.Header.Add(
"token", token);var content = helper.GetHtml(queryUrl, postData, true);

RFIDBaseData
<TagOrderAsnResult> jsonResult = JsonConvert.DeserializeObject<RFIDBaseData<TagOrderAsnResult>>(content);if (jsonResult != null && jsonResult.code == 0)
{
result
=jsonResult.data;
}
returnresult;
}
else{throw new MyApiException("传递参数错误");
}

其中表头信息,我们通过下面的代码指定,设置特殊的token表头信息

                var helper = newHttpHelper();
helper.ContentType
= "application/json";
helper.Header.Add(
"token", token);

而在客户端的调用窗体里面,我们调用对应的接口就可以获取该接口的数据了。

        privateTagOrderAsnResult asnResult;/// <summary>
        ///根据参数获取标签生产订单批量信息/// </summary>
        /// <returns></returns>
        privateTagOrderAsnResult GetResult()
{
PagerInfo pagerInfo
= new PagerInfo() { PageSize = 50, CurrenetPageIndex = 1 };//初始化一个分页条件 var brand_id = this.txtbrand_id.Text.ToInt32();var factcode = this.txtfactcode.Text;var start_time = this.txtstart_time.DateTime.ToString("yyyy-MM-dd HH:mm:ss");var end_time = this.txtend_time.DateTime.ToString("yyyy-MM-dd HH:mm:ss");

asnResult
= CallerFactory<IRFIDService>.Instance.TagOrderAsn(brand_id, factcode, start_time, end_time, pagerInfo, Token);returnasnResult;
}

通过上面的代码演示,我们了解了在混合框架基础上增加外部Web API接口的方法,通过增加Facade层接口,增加Web API接口,以及对应的客户端封装类,具体处理参数根据Web API接口的输入参数、输出数据等信息进行综合处理即可。

最后我们来看看数据的展示界面。

在一些应用场景中,我们需要实现多层级的数据表格显示,如常规的二级主从表数据展示,甚至也有多个层级展示的需求,那么我们如何通过DevExpress的GridControl控表格件实现这种业务需求呢?本篇随笔基于这个需求,对二级、三级的主从表数据进行展示,从而揭开对多层级数据展示的神秘面纱。

1、二级主从表数据展示

主从表数据,我们知道,一个主表记录里面关联有多条明细从表记录,在数据定义的层次上我们体现如下所示。

先定义一个实体类信息作为载体。

    /// <summary>
    ///记录基础信息/// </summary>
    public classDetailInfo
{
publicDetailInfo()
{
this.ID =Guid.NewGuid().ToString();
}
/// <summary> ///ID标识/// </summary> public string ID { get; set; }/// <summary> ///名称/// </summary> public string Name { get; set; }/// <summary> ///描述信息/// </summary> public string Description { get; set; }

}

然后主从表的数据实体类就是除了包含这些信息外,再包含一个子列表(列表信息不一定是同一个实体类),如下所示。

    /// <summary>
    ///二级层次的列表/// </summary>
    public classDetail2Result : DetailInfo
{
public List<DetailInfo> Detail2List { get; set; }
}

这个是我们使用继承关系简化了信息的定义,就是这个实体类包含主表信息外,还包含一个列表集合,属于从表数据的。

有了这些数据的定义,我们构建一些测试的数据,如下所示。

            //创建测试数据
            var result = newDetail2Result()
{
Name
= "测试",
Description
= "描述内容",
Detail2List
= new List<DetailInfo>()
{
newDetailInfo()
{
Name
= "666666测试",
Description
= "666666描述内容"},newDetailInfo()
{
Name
= "222测试",
Description
= "222描述内容"},newDetailInfo()
{
Name
= "333测试",
Description
= "333描述内容"}
}
};
//构造一个记录的集合 var list = new List<Detail2Result>() { result };

这样我们就构建了一个主表从表记录的数据源,可以用于表格控件的绑定的了。

首先我们在界面上创建一个空白的窗体用于演示,并在窗体上增加一个GridControl控件用于展示主从表的数据,如下界面所示。

然后,我们可以通过代码创建我们需要的视图信息,如创建主表的GridView显示如下所示。

        /// <summary>
        ///创建第一个视图/// </summary>
        private voidCreateGridView()
{
var grv = this.gridView1;//创建从表显示的列 grv.Columns.Clear();
grv.CreateColumn(
"ID", "ID");//.Visible = false; grv.CreateColumn("Name", "名称");
grv.CreateColumn(
"Description", "描述内容");

grv.OptionsBehavior.ReadOnly
= false;
grv.OptionsBehavior.Editable
= true;
}

很简单,我们创建几个列,并指定它的Caption中文显示属性就可以了,然后我们接着还需要创建从表的GridView显示数据,这个是这篇随笔的关键。

具体的代码一次性贴出来,如下所示。

        GridView grv2 = null;/// <summary>
        ///创建第二个视图/// </summary>
        private voidCreateLevelView()
{
var grv = this.gridView1;var gridControl = this.gridControl1;//创建一个从表的GridView对象 grv2 = newGridView();
grv2.ViewCaption
= "记录明细";
grv2.Name
= "grv2";
grv2.GridControl
=gridControl;//构建GridLevelNode并添加到LevelTree集合里面 var node = newGridLevelNode();
node.LevelTemplate
=grv2;
node.RelationName
= "Detail2List";//这里对应集合的属性名称 gridControl.LevelTree.Nodes.AddRange(newGridLevelNode[]
{
node
});
//添加对应的视图集合显示 gridControl.ViewCollection.Clear();
gridControl.ViewCollection.AddRange(
newBaseView[] { grv, grv2 });//创建从表显示的列 grv2.Columns.Clear();
grv2.CreateColumn(
"ID", "ID");
grv2.CreateColumn(
"Name", "名称");
grv2.CreateColumn(
"Description", "描述内容");//设置非只读、可编辑 grv2.OptionsBehavior.ReadOnly = false;
grv2.OptionsBehavior.Editable
= true;
}

我们这里注意到 GridLevelNode 对象,它是我们主从表节点的关键信息,我们需要了解下面部分的代码

            //构建GridLevelNode并添加到LevelTree集合里面
            var node = newGridLevelNode();
node.LevelTemplate
=grv2;
node.RelationName
= "Detail2List";//这里对应集合的属性名称 gridControl.LevelTree.Nodes.AddRange(newGridLevelNode[]
{
node
});

首先是创建一个节点,然后指定它的 LevelTemplate 为我们新建的GridView,并且他的子集合对象名称为 Detail2List ,最后把这个节点的信息加入到 gridControl.LevelTree.Nodes 里面就可以了,其他的代码就和第一步差不多,指定显示的列和中文显示名称即可。

还有就是我们需要把创建的GridView 加入到指定的集合里面。

            //添加对应的视图集合显示
gridControl.ViewCollection.Clear();
gridControl.ViewCollection.AddRange(
new BaseView[] { grv, grv2 });

到这里基本上就是大功告成了,剩下的就是数据的绑定处理了。前面我们已经介绍了实体类的准备工作和创建测试数据的代码,那么我们这里沿用上面的代码进行数据的绑定就可以了。如下代码所示。

        /// <summary>
        ///绑定数据源/// </summary>
        private voidBindData()
{
//创建测试数据 var result = newDetail2Result()
{
Name
= "测试",
Description
= "描述内容",
Detail2List
= new List<DetailInfo>()
{
newDetailInfo()
{
Name
= "666666测试",
Description
= "666666描述内容"},newDetailInfo()
{
Name
= "222测试",
Description
= "222描述内容"},newDetailInfo()
{
Name
= "333测试",
Description
= "333描述内容"}
}
};
//构造一个记录的集合 var list = new List<Detail2Result>() { result };//绑定数据源 this.gridControl1.DataSource =list;
}
private void FrmTestDetails_Load(objectsender, EventArgs e)
{
BindData();
}

我们来运行下完成的程序界面,可以看到例子的效果界面如下所示。

我们可以看到数据记录是有树形节点的,展开就可以看到明细记录了,这个就是我们这里介绍的二级主从表数据的展示效果。

2、三级主从表数据展示

上面介绍了二级主从表的数据展示,其实GridControl可以用于展示三级以及更多层级的数据展示,只要你的数据设计合理,就可实现多层级的正确展示的。

本小节介绍三级的主从表数据展示,和二级数据展示类似,不过我们进一步实现了多层级的处理而已。

我们在二级层次的数据上定义了一个三级层次的数据实体类,如下所示。

    /// <summary>
    ///二级层次的列表/// </summary>
    public classDetail2Result : DetailInfo
{
public List<DetailInfo> Detail2List { get; set; }
}
/// <summary> ///三级层次的列表/// </summary> public classDetail3Result : DetailInfo
{
public List<Detail2Result> Detail3List { get; set; }
}

三级层次的测试数据初始化如下所示:

            //创建测试数据
            var result = newDetail3Result()
{
Name
= "测试11",
Description
= "描述内容11",//二级列表 Detail3List = new List<Detail2Result>()
{
newDetail2Result()
{
Name
= "测试22",
Description
= "描述内容22",//三级列表 Detail2List = new List<DetailInfo>()
{
newDetailInfo()
{
Name
= "31测试",
Description
= "31描述内容"},newDetailInfo()
{
Name
= "32测试",
Description
= "32描述内容"},newDetailInfo()
{
Name
= "33测试",
Description
= "33描述内容"}
}
}
}
};
//构造一个记录的集合 var list = new List<Detail3Result>() { result };

和二级层次的处理步骤类似,我们先创建主表的信息展示,如下所示。

        /// <summary>
        ///创建第一个视图/// </summary>
        private voidCreateGridView()
{
var grv = this.gridView1;var gridControl = this.gridControl1;//创建从表显示的列 grv.Columns.Clear();
grv.CreateColumn(
"ID", "ID");//.Visible = false; grv.CreateColumn("Name", "名称");
grv.CreateColumn(
"Description", "描述内容");

grv.OptionsBehavior.ReadOnly
= false;
grv.OptionsBehavior.Editable
= true;
}

然后着手创建二级、三级的列表信息展示,

        GridView grv2 = null;
GridView grv3
= null;/// <summary> ///创建第二个视图/// </summary> private voidCreateLevelView()
{
var grv = this.gridView1;var gridControl = this.gridControl1;//创建一个二级从表的GridView对象 grv2 = newGridView();
grv2.ViewCaption
= "记录明细";
grv2.Name
= "grv2";
grv2.GridControl
=gridControl;//创建一个三级从表的GridView对象 grv3 = newGridView();
grv3.ViewCaption
= "记录明细2";
grv3.Name
= "grv3";
grv3.GridControl
= gridControl;

这样我们相当于创建多两个(总共三个GridView对象)用于展示数据列表。

接着最为关键的是主从关系的节点,我们可以简单的理解他的Node节点和我们树形列表的Node处理方式类似即可。

            //构建GridLevelNode
            var topNode = newGridLevelNode();
topNode.LevelTemplate
= grv2; //这里是对应的视图 topNode.RelationName = "Detail3List"; //这里对应集合的属性名称//构建GridLevelNode var secondNode = newGridLevelNode();
secondNode.LevelTemplate
= grv3; //这里是对应的视图 secondNode.RelationName = "Detail2List";//这里对应集合的属性名称//需要添加节点的层级关系,类似Tree节点处理 topNode.Nodes.Add(secondNode);//最后添加节点到集合里面 gridControl.LevelTree.Nodes.Add(topNode);

通过定义两个GridLevelNode,然后指定他们的Node关系( topNode.Nodes.Add(secondNode) ),这样我们就可以很清晰的关联起来它们的节点关系了。

最后是把我们创建的几个视图加入到集合里面,并设定一些关系即可。

            //添加对应的视图集合显示
gridControl.ViewCollection.Clear();
gridControl.ViewCollection.AddRange(
newBaseView[] { grv, grv2, grv3 });//创建从表显示的列 grv2.Columns.Clear();
grv2.CreateColumn(
"ID", "ID");
grv2.CreateColumn(
"Name", "名称");
grv2.CreateColumn(
"Description", "描述内容");//创建从表显示的列 grv3.Columns.Clear();
grv3.CreateColumn(
"ID", "ID");
grv3.CreateColumn(
"Name", "名称");
grv3.CreateColumn(
"Description", "描述内容");//设置非只读、可编辑 grv2.OptionsBehavior.ReadOnly = false;
grv2.OptionsBehavior.Editable
= true;//设置非只读、可编辑 grv3.OptionsBehavior.ReadOnly = false;
grv3.OptionsBehavior.Editable
= true;

整个部分的代码如下所示。

        GridView grv2 = null;
GridView grv3
= null;/// <summary> ///创建第二个视图/// </summary> private voidCreateLevelView()
{
var grv = this.gridView1;var gridControl = this.gridControl1;//创建一个二级从表的GridView对象 grv2 = newGridView();
grv2.ViewCaption
= "记录明细";
grv2.Name
= "grv2";
grv2.GridControl
=gridControl;//创建一个三级从表的GridView对象 grv3 = newGridView();
grv3.ViewCaption
= "记录明细2";
grv3.Name
= "grv3";
grv3.GridControl
=gridControl;//构建GridLevelNode var topNode = newGridLevelNode();
topNode.LevelTemplate
= grv2; //这里是对应的视图 topNode.RelationName = "Detail3List"; //这里对应集合的属性名称//构建GridLevelNode var secondNode = newGridLevelNode();
secondNode.LevelTemplate
= grv3; //这里是对应的视图 secondNode.RelationName = "Detail2List";//这里对应集合的属性名称//需要添加节点的层级关系,类似Tree节点处理 topNode.Nodes.Add(secondNode);//最后添加节点到集合里面 gridControl.LevelTree.Nodes.Add(topNode);//添加对应的视图集合显示 gridControl.ViewCollection.Clear();
gridControl.ViewCollection.AddRange(
newBaseView[] { grv, grv2, grv3 });//创建从表显示的列 grv2.Columns.Clear();
grv2.CreateColumn(
"ID", "ID");
grv2.CreateColumn(
"Name", "名称");
grv2.CreateColumn(
"Description", "描述内容");//创建从表显示的列 grv3.Columns.Clear();
grv3.CreateColumn(
"ID", "ID");
grv3.CreateColumn(
"Name", "名称");
grv3.CreateColumn(
"Description", "描述内容");//设置非只读、可编辑 grv2.OptionsBehavior.ReadOnly = false;
grv2.OptionsBehavior.Editable
= true;//设置非只读、可编辑 grv3.OptionsBehavior.ReadOnly = false;
grv3.OptionsBehavior.Editable
= true;
}

也就是我们在窗体初始化的时候,创建它们的视图关系即可,如下代码所示。

    /// <summary>
    ///测试三级主从明细列表/// </summary>
    public partial classFrmTestDetails2 : BaseForm
{
publicFrmTestDetails2()
{
InitializeComponent();

CreateGridView();
CreateLevelView();
}

最后就是数据源的绑定操作了,这个利用前面介绍过的准备数据即可。

        private void FrmTestDetails2_Load(objectsender, EventArgs e)
{
BindData();
}
/// <summary> ///绑定数据源/// </summary> private voidBindData()
{
//创建测试数据 var result = newDetail3Result()
{
Name
= "测试11",
Description
= "描述内容11",//二级列表 Detail3List = new List<Detail2Result>()
{
newDetail2Result()
{
Name
= "测试22",
Description
= "描述内容22",//三级列表 Detail2List = new List<DetailInfo>()
{
newDetailInfo()
{
Name
= "31测试",
Description
= "31描述内容"},newDetailInfo()
{
Name
= "32测试",
Description
= "32描述内容"},newDetailInfo()
{
Name
= "33测试",
Description
= "33描述内容"}
}
}
}
};
//构造一个记录的集合 var list = new List<Detail3Result>() { result };//绑定数据源 this.gridControl1.DataSource =list;
}

以上就是三级层次的关系处理,如果我们理解了,其他更多层级的数据展示也是依照这个规则,增加节点和视图即可,原理一样。

案例的效果如下所示。

3、利用分页控件实现数据的展示

上面的两个案例是基于DevExpress的内置表格控件GridControl进行处理的,我们在Winform框架的开发过程中,往往为了效率和分页方便,一般都是使用分页控件来展示数据的,那么利用分页控件实现多层级的数据展示是如何的呢?

其实基本步骤也是差不多的,只是主表视图使用分页控件即可,如下所示。

    /// <summary>
    ///数据指定的主从表展示/// </summary>    
    public partial classFrmDictTypeMasterDetail : BaseDock
{
publicFrmDictTypeMasterDetail()
{
InitializeComponent();

InitDictItem();
this.winGridViewPager1.OnPageChanged += newEventHandler(winGridViewPager1_OnPageChanged);this.winGridViewPager1.OnStartExport += newEventHandler(winGridViewPager1_OnStartExport); this.winGridViewPager1.OnDeleteSelected += newEventHandler(winGridViewPager1_OnDeleteSelected);this.winGridViewPager1.OnRefresh += newEventHandler(winGridViewPager1_OnRefresh);this.winGridViewPager1.AppendedMenu = this.contextMenuStrip1;this.winGridViewPager1.ShowLineNumber = true;this.winGridViewPager1.BestFitColumnWith = false;//是否设置为自动调整宽度,false为不设置 this.winGridViewPager1.gridView1.DataSourceChanged += newEventHandler(gridView1_DataSourceChanged);this.winGridViewPager1.gridView1.CustomColumnDisplayText += newDevExpress.XtraGrid.Views.Base.CustomColumnDisplayTextEventHandler(gridView1_CustomColumnDisplayText);this.winGridViewPager1.gridView1.RowCellStyle += newDevExpress.XtraGrid.Views.Grid.RowCellStyleEventHandler(gridView1_RowCellStyle);CreateLevelView();
RegisterEvent();
}

GridView grv2
= null;private voidCreateLevelView()
{
var grv = this.winGridViewPager1.GridView1;var gridControl = this.winGridViewPager1.gridControl1;//创建一个从表的GridView对象 grv2 = newGridView();
grv2.ViewCaption
= "记录明细";
grv2.Name
= "grv2";
grv2.GridControl
=gridControl;//构建GridLevelNode并添加到LevelTree集合里面 var node = newGridLevelNode();
node.LevelTemplate
=grv2;
node.RelationName
= "Children";
gridControl.LevelTree.Nodes.AddRange(
newGridLevelNode[]
{
node
});

gridControl.ViewCollection.Clear();
gridControl.ViewCollection.AddRange(
newBaseView[] { grv, grv2 });//创建从表显示的列 grv2.Columns.Clear();
grv2.CreateColumn(
"ID", "ID").Visible =false; //标识行的关键字,可用于删除处理 grv2.CreateColumn("DictType_ID", "DictType_ID").Visible = false;//创建一个字段,隐藏的,存储记录 grv2.CreateColumn("Name", "项目名称");
grv2.CreateColumn(
"Value", "项目值");
grv2.CreateColumn(
"Seq", "排序");
grv2.CreateColumn(
"Remark", "备注");

grv2.OptionsBehavior.ReadOnly
= false;
grv2.OptionsBehavior.Editable
= true;
grv2.DataSourceChanged
+=grv2_DataSourceChanged;
}

以上就是基于GridControl实现数据的主从关系的处理,可以实现多层级的展示,希望这些案例能够对你展示数据有所帮助。

RabbitMQ是一个由erlang开发的AMQP(Advanved Message Queue)的开源实现,是实现消息队列应用的一个中间件,消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。实现高性能,高可用,可伸缩和最终一致性架构。是大型分布式系统不可缺少的中间件。EasyNetQ则是基于官方.NET组件RabbitMQ.Client 的又一层封装,使用起来更加方便。本篇随笔主要大概介绍下RabbitMQ的基础知识和环境的准备,以及使用EasyNetQ的相关开发调用。

1、RabbitMQ基础知识

AMQP
,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的
中间件
设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。
AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
RabbitMQ
是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

RabbitMQ的特点

强大的
应用
程序消息传递;
使用方便;
运行在所有主要
操作系统上;支持大量开发人员平台;
开源

商业支持。消息队列的模式有两种模式:P2P(Point to Point),P2P模式包含三个角色:消息队列(Queue),发送者(Sender),接收者(Receiver)。每个消息都被发送到一个特定的队列,接收者从队列中获取消息。队列保留着消息,直到他们被消费或超时。Publish/Subscribe(Pub/Sub),包含三个角色主题(Topic),发布者(Publisher),订阅者(Subscriber) 。多个发布者将消息发送到Topic,系统将这些消息传递给多个订阅者。

EasyNetQ
的目标是提供一个使.NET中的RabbitMQ尽可能简单的库。在EasyNetQ中消息应由.NET类型表示,消息应通过其.NET类型进行路由。EasyNetQ按消息类型进行路由。发布消息时,EasyNetQ会检查其类型,并根据类型名称,命名空间和装配体给出一个路由密钥。在消费方面,用户订阅类型。订阅类型后,该类型的消息将路由到订户。默认情况下,EasyNetQ使用Newtonsoft.Json库将.NET类型序列化为JSON。
这具有消息是人类可读的优点,因此您可以使用RabbitMQ管理应用程序等工具来调试消息问题。

EasyNetQ是在RabbitMQ.Client库之上提供服务的组件集合。这些操作可以像序列化,错误处理,线程编组,连接管理等。它们由mini-IoC容器组成。您可以轻松地用自己的实现替换任何组件。因此,如果您希望XML序列化而不是内置的JSON,只需编写一个ISerializer的实现并将其注册到容器。
以下是官方提供的一个结构图,这个结构图可以很好的解析该组件的结构:

2、RabbitMQ的环境准备

本处主要介绍在Windows系统中安装RabbitMQ。

1. 下载安装erlang

下载地址
http://www.erlang.org/downloads
(根据操作系统选择32还64位)

2. 下载安装rabbitmq-server

下载地址
http://www.rabbitmq.com/install-windows.html

下载后获得两个安装文件,按照顺序安装即可

安装erlang环境后,一般会添加了一个ERLANG_HOME的系统变量,指向erlang的安装目录路径,如下所示(
一般都添加了,确认下

安装RabbitMQ后,在程序里面可以看到

我们使用它的命令行来启动RabbitMQ的服务

查看安装是否成功命令 :
rabbitmqctl status

安装成功,在浏览器中输入
http://127.0.0.1:15672/
,可以看到如下界面,使用默认的账号密码均为guest登陆进行管理

guest 账号是管理员账号,可以添加Exchanges,Queues,Admin。但我们一般不使用guest账号,可以选择用命令来添加账号和权限,也可以使用管理界面进行添加相应的内容。

例如我添加相应的用户账号

一般我们还需要添加虚拟机,默认的虚拟机为/,我这里添加了一个虚拟机myvhost。

然后绑定账号到虚拟机上即可。

3、EasyNetQ组件的使用

EasyNetQ组件的使用方式比较简单,跟很多组件都类似,例如:建立连接,进行操作做等等,对于EasyNetQ组件也是如此。

在.NET中使用EasyNetQ,要求至少基于 .NET4.5的框架基础上进行开发,可以直接在VS项目上使用NuGet的程序包进行添加EasyNetQ的引用。

一般添加引用后,至少包含了下面图示的几个引用DLL。

1)创建连接:

使用EasyNetQ连接RabbitMQ,是在应用程序启动时创建一个IBus对象,并且,在应用程序关闭时释放该对象。

RabbitMQ连接是基于IBus接口的,当IBus中的方法被调用,连接才会开启。创建一个IBus对象的方法如下:

var bus = RabbitHutch.CreateBus(“host=myServer;virtualHost=myVirtualHost;username=admin;password=123456”);

与RabbitMQ服务器的延迟连接由IBus接口表示,创建连接的方式连接字符串由格式为key = value的键/值对组成,每一个用分号(;)分隔。

  • host,host=localhost 或者host =192.168.1.102或者host=my.rabbitmq.com,如果用到集群配置的话,那么可以用逗号将服务地址隔开,例如host=a.com,b.com,c.com
  • virtualHost,虚拟主机,默认为'/'
  • username,用户登录名
  • password,用户登录密码
  • requestedHeartbeat,心跳设置,默认是10秒
  • prefetchcount,默认是50
  • pubisherConfirms,默认为false
  • persistentMessages,消息持久化,默认为true
  • product,产品名
  • platform,平台
  • timeout,默认为10秒

一般我们在代码里面测试的话,简化连接代码如下所示。

 //初始化bus对象
 bus = RabbitHutch.CreateBus("host=localhost");

2

关闭连接:

bus.Dispose();

要关闭连接,只需简单地处理总线,这将关闭EasyNetQ使用的连接,渠道,消费者和所有其他资源。

如果我们在Winform窗体里面初始化一个IBus对象,那么在窗体关闭的时候,关闭这个接口即可。

        private void FrmPublisher_FormClosed(objectsender, FormClosedEventArgs e)
{
//关闭IBus接口 if(bus != null)
{
bus.Dispose();
}
}

3

发布消息:

EasyNetQ支持最简单的消息模式是发布和订阅。发布消息后,任意消费者可以订阅该消息,也可以多个消费者订阅。并且不需要额外配置。首先,如上文中需要先创建一个IBus对象,然后,在创建一个可序列化的.NET对象。调用Publish方法即可。

var message = new MyMessage { Text = "Hello Rabbit"};
bus.Publish(message);

4

订阅消息:

EasyNetQ提供了消息订阅,当调用Subscribe方法时候,EasyNetQ会创建一个用于接收消息的队列,不过与消息发布不同的是,消息订阅增加了一个参数,subscribe_id.代码如下:

bus.Subscribe<MyMessage>("my_subscription_id", msg => Console.WriteLine(msg.Text));

第一个参数是订阅id,另外一个是delegate参数,用于处理接收到的消息。这里要注意的是,subscribe_id参数很重要,假如开发者用同一个subscribeid订阅了同一种消息类型两次或者多次,RabbitMQ会以轮训的方式给每个订阅的队列发送消息。接收到之后,其他队列就接收不到该消息。如果用不同的subscribeid订阅同一种消息类型,那么生成的每一个队列都会收到该消息。

需要注意的是,在收到消息处理消息时候,不要占用太多的时间,会影响消息的处理效率,所以,遇到占用长时间的处理方法,最好用异步处理。

为了测试发布和订阅消息,我们可以建立几个不同的项目来进行测试,如发布放在一个Winform项目,订阅放在一个Winform项目,另外一个项目放置共享的消息对象定义,如下所示。

定义消息对象类如下所示。

    /// <summary>
    ///定义的MQ消息类型/// </summary>
    public classTextMessage
{
public string Text { get; set; }
}

然后在发布消息的Winform项目上创建一个处理的窗体,并添加如下代码。

namespaceMyRabbitMQ.Publisher
{
/// <summary> ///测试RabbitMQ消息队列的发布/// </summary> public partial classFrmPublisher : DevExpress.XtraEditors.XtraForm
{
//构建一个IBus公用接口对象 private IBus bus = null;publicFrmPublisher()
{
InitializeComponent();
//初始化bus对象 bus = RabbitHutch.CreateBus("host=localhost");//对指定消息类型进行回应 bus.Respond<MyRequest, MyResponse>(request => new MyResponse { Text = "Responding to:"+request.Text});//收到消息后输出到控制台上显示 bus.Receive("my.queue", x =>x
.Add
<MyMessage>(message =>Console.WriteLine(message.ToJson()))
.Add
<MyOtherMessage>(message =>Console.WriteLine(message.ToJson())));
}

发布消息的处理代码,如下代码所示。

        private void btnSend_Click(objectsender, EventArgs e)
{
if (bus != null)
{
bus.Publish(
newTextMessage
{
Text
= this.txtContent.Text
});
}
}

然后在创建一个类似窗体,用来订阅消息的处理窗体,如下所示代码和窗体。

namespaceMyRabbitMQ.Subcriber
{
/// <summary> ///测试RabbitMQ消息队列的订阅/// </summary> public partial classFrmSubcriber : DevExpress.XtraEditors.XtraForm
{
//构建一个IBus公用接口对象 private IBus bus = null;publicFrmSubcriber()
{
InitializeComponent();
//初始化bus对象 bus = RabbitHutch.CreateBus("host=localhost");if(bus != null)
{
//订阅一个消息,并对接收到的消息进行处理,展示在控件上 bus.Subscribe<TextMessage>("test", (msg) =>{
StringBuilder sb
= newStringBuilder();
sb.AppendLine(msg.Text
+ "," +DateTime.Now.ToString());
sb.AppendLine(
this.txtContent.Text);this.txtContent.Invoke(new MethodInvoker(delegate()
{
this.txtContent.Text =sb.ToString();
}));
});
}
//使用消息发送接口发送消息 bus.Send("my.queue", new MyMessage { Text = "Hello Widgets!"});
bus.Send(
"my.queue", new MyOtherMessage { Text = "Hello wuhuacong!"});
}

发送请求获取响应的代码如下所示。

        private void btnRequest_Click(objectsender, EventArgs e)
{
//定义请求消息的对象 var request = newMyRequest()
{
Text
= string.Format("请求消息,{0}", DateTime.Now)
};
//异步获取请求消息的结果并进行处理,展示应答消息在窗体中的 var task = bus.RequestAsync<MyRequest, MyResponse>(request);
task.ContinueWith(response
=>{
StringBuilder sb
= newStringBuilder();
sb.AppendLine(response.Result.Text);
sb.AppendLine(
this.txtContent.Text);this.txtContent.Invoke(new MethodInvoker(delegate()
{
this.txtContent.Text =sb.ToString();
}));
});
}

两个项目联合进行测试如下界面所示。

发布者多次发送消息的情况下,订阅者中,会进行消息的轮训处理,也就是进行均匀分配。

5)消息发送(Send)和接收(Receive)

与Publish/Subscribe略有不同的是,Send/Receive 可以自己定义队列名称。

//发送端代码
bus.Send("my.queue", new MyMessage{ Text = "Hello Widgets!"});//接收端代码
bus.Receive<MyMessage>("my.queue", message => Console.WriteLine("MyMessage: {0}", message.Text));

并且,也可以在同一个队列上发送不同的消息类型,Receive方法可以这么写:

bus.Receive("my.queue", x =>x
.Add
<MyMessage>(message => deliveredMyMessage =message)
.Add
<MyOtherMessage>(message => deliveredMyOtherMessage = message));

如果消息到达队列,但是没有发现相应消息类型的处理时,EasyNetQ会发送一条消息到error队列,并且,带上一个异常信息:No handler found for message type <message type>。与Subscribe类型,如果在同一个队列,同一个消息类型,多次调用Receive方法时,消息会通过轮询的形式发送给每个Receive端。

6)远程过程调用:

var request = new TestRequestMessage {Text = "Hello from the client! "};
bus.Request<TestRequestMessage, TestResponseMessage>(request, response => 
    Console.WriteLine("Got response: '{0}'", response.Text));

7

RPC服务器:

bus.Respond<TestRequestMessage, TestResponseMessage>(request => 
    new TestResponseMessage{ Text = request.Text + " all done!" });

8

记录器:

var logger = new MyLogger() ;
var bus = RabbitHutch.CreateBus(“my connection string”, x => x.Register<IEasyNetQLogger>(_ => logger));

9

路由:

Publish方法,可以加一个topic参数。

bus.Publish(message, "X.A");

消息订阅方可以通过路由来过滤相应的消息。

* 匹配一个字符

#匹配0个或者多个字符

所以 X.A.2 会匹配到 "#", "X.#", "*.A.*" 但不会匹配 "X.B.*" 或者 "A". 当消息订阅需要用到topic时候,需要调用Subscribe的重载方法

bus.Subscribe("my_id", handlerOfXDotStar, x => x.WithTopic("X.*"));
bus.Subscribe(
"my_id", handlerOfStarDotB, x => x.WithTopic("*.B"));

上述这种方式,会将消息轮询发送给两个订阅者,如果只需要一个订阅者的话,可以这么调用:

bus.Subscribe("my_id", handler, x => x.WithTopic("X.*").WithTopic("*.B"));

RabbitMQ具有非常好的功能,
基于主题的路由
,允许订阅者基于多个标准过滤消息。*(星号)匹配一个字。#(哈希)匹配为零个或多个单词。

RabbitMQ的应用场景,一般在快速处理订单,以及异步的多任务处理中可以得到很好的体现,下面是几个应用场景。

邮件和短消息的处理

订单的解耦处理

RabbitMQ的服务器架构

3、RabbitMQ查询状态出现错误的处理

安装成功之后使用rabbitmqctl status命令之后出现如下错误。

Status of node rabbit@WUHUACONG ...Error: unable to perform an operationon node 'rabbit@WUHUACONG'. Please see diagnostics information and suggestions below.Most common reasonsforthis are:

* Target node is unreachable
(e.g. due to hostname resolution, TCP connection or firewall issues)* CLI tool fails to authenticate with the server(e.g. due to CLI tool's Erlang cookie not matching that of the server)* Target node isnotrunning

In addition to the diagnostics info below:

* See the CLI
, clustering and networking guides on http://rabbitmq.com/documentation.html to learn more
* Consult server logs
onnode rabbit@WUHUACONG

DIAGNOSTICS
===========attempted to contact: [rabbit@WUHUACONG]

rabbit@WUHUACONG:
* connected to epmd
(port 4369) onWUHUACONG
* epmd reports node 'rabbit' uses port
25672 forinter-node and CLI tool traffic
* TCP connection succeeded but Erlang distribution failed

* Authentication failed
(rejected by the remote node),please check the Erlang cookie


Current node details:
* node name: rabbitmqcli100@WUHUACONG
* effective user's home directory: C:
\Users\Administrator
* Erlang cookie hash: RgaUM2coc
+rxIhJrfLS7Jw==

这个问题出现比较常见,主要原因是两个目录的.erlang.cookie文件内容不一样。

要确保.erlang.cookie文件的一致性,不知道什么原因导致了C:\Users\{UserName}\.erlang.cookie和默认情况下C:\WINDOWS\System32\config\systemprofile\.erlang.cookie不一致了,将Windows目录下的拷贝到用户目录下就可以了。

反正无论如何,两个地址的Cookie内容一致就可以了,然后重启下RabbitMQ服务器即可正常运行,并可以正常获取它的状态信息。