0%

手把手教你玩转 Gitea|使用 Helm 在 K3s 上安装 Gitea - Gitea - 博客园

Excerpt

在前面的文章中,演示了如何用 Docker 镜像和 Windows 二进制包来安装运行 Gitea。今天是玩转 Gitea 系列的使用 Helm 在 K3s 上安装 Gitea。


kubectl -n gitea get pods

前言

每个人都有自己习惯的项目结构,有人的喜欢在项目里面建解决方案文件夹;有的人喜欢传统的三层命名;有的人喜欢单一,简单的项目一个csproj就搞定。。

反正就是萝卜青菜,各有所爱。

可能不同的公司对这些会有特定的要求,也可能会随开发自己的想法去实践。

那么,问题就来了。如果有一个新项目,你会怎么去创建?

可能比较多的方式会是下面三种:

  • 简单粗暴型,打开VS就是右键添加,然后引入一堆包,每个项目添加引用。
  • 脚本型,基于dotnet cli,创建解决方案,创建项目,添加包,添加项目引用。
  • 高大上型,VS项目模板,直接集成到VS上面了。

以前我也是基于dotnet cli写好了sh或ps的脚本,然后用这些脚本来生成新项目。

但是呢,这三种方式,始终都有不尽人意的地方。

因为建好的都是空模板,还要做一堆复杂的操作才可以让项目“正常”的跑起来。比如,这个公共类要抄过来,那个公共类要抄过来。。。这不是明摆着浪费时间嘛。。。

下面介绍一个小办法来帮大家省点时间。

基于dotnet cli创建自己的项目模板,也就是大家常说的脚手架。

dotnet cli项目模板预热

开始正题之前,我们先看一下dotnet cli自带的一些模板。

可以看到种类还是很多的,由于工作大部分时间都是在写WebAPI,所以这里就用WebAPI来写个简单的模板。

下面我们就基于dotnet cli写一个自己的模板。

编写自己的模板

既然是模板,就肯定会有一个样例项目。

下面我们建一个样例项目,大致成这样,大家完全可以按照自己习惯来。

这其实就是一个普通的项目,里面添加了NLog,Swagger,Dapper等组件,各个项目的引用关系是建好的。

该有的公共类,里面也都包含了,好比宇内分享的那个WebHostBuilderJexusExtensions。

下面是这个模板跑起来的效果。

就是一个简单的Swagger页面。

现在样例已经有了,要怎么把这个样例变成一个模板呢?

答案就是template.json

在样例的根目录创建一个文件夹.template.config,同时在这个文件夹下面创建template.json

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"author": "Catcher Wong",
"classifications": [ "Web/WebAPI" ],
"name": "TplDemo",
"identity": "TplDemoTemplate",
"shortName": "tpl",
"tags": {
"language": "C#" ,
"type":"project"
},
"sourceName": "TplDemo",
"preferNameDirectory": true
}

在这里,有几个比较重要的东西,一个是shortName,一个是sourceName

  • shortName,简写,偷懒必备,好比能写 -h 就绝对不写 --help
  • sourceName,这是个可选的字段,它的值会替换指定的项目名,正常是把项目名赋值在这里。如果不指定,创建的项目就和样例项目保持一致。

在写完template.json之后,还需要安装一下这个模板到我们的cli中。

使用 dotnet new -i进行模板的安装。

下面是安装示例。

1
dotnet new -i ./content/TplDemo

这里要注意的是,与.template.config文件夹同级的目录,都会被打包进模板中。

在执行安装命令之后,就可以看到我们的模板已经安装好了。

这个时候已经迫不及待的想来试试这个模板了。

先来看看这个模板的帮助信息。

1
dotnet new tpl -h

因为我们目前还没有设置参数,所以这里显示的是还没有参数。

下面来创建一个项目试试。

从创建一个项目,到运行起来,很简单,效果也是我们预期的。

下面来看看,新建的这个HelloTpl这个项目的目录结构和我们的模板是否一样。

可以看到,除了名字,其他的内容都是一样的。

是不是感觉又可以少复制粘贴好多代码了。

虽说,现在建项目,已经能把一个大的模板完整的copy出来了,但是始终不是很灵活!

可能有小伙伴会问,明明已经很方便了呀,为什么还会说它不灵活呢?

且听我慢慢道来。

如果说这个模板是个大而全的模板,包含了中间件A,中间件B,中间件C等N个中间件!

而在建新项目的时候,已经明确了只用中间件A,那么其他的中间件对我们来说,可能就没有太大的存在意义!

很多时候,不会想让这些多余的文件出现在代码中,有没有办法来控制呢?

答案是肯定的!可以把不需要的文件排除掉就可以了。

文件过滤

模板项目中有一个RequestLogMiddleware,就用它来做例子。

我们只需要做下面几件事就可以了。

第一步,在template.json中添加过滤

加入一个名字为EnableRequestLog的symbol。同时指定源文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"author": "Catcher Wong",

"symbols":{

"EnableRequestLog": {
"type": "parameter",
"dataType":"bool",
"defaultValue": "false"
}
},
"sources": [
{
"modifiers": [
{
"condition": "(!EnableRequestLog)",
"exclude": [
"src/TplDemo/Middlewares/RequestLogMiddleware.cs",
"src/TplDemo/Middlewares/RequestLogServiceCollectionExtensions.cs"
]
}
]
}
]
}

第二步,在模板的代码中做一下处理

主要是Startup.cs,因为Middleware就是在这里启用的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    using System;

using TplDemo.Core;
#if (EnableRequestLog)
using TplDemo.Middlewares;
#endif




public class Startup
{
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{

#if (EnableRequestLog)

app.UseRequestLog();
#endif
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}

这样的话,只要EnableRequestLog是true,那么就可以包含这两段代码了。

下面更新一下已经安装的模板。

这个时候再去看它的帮助信息,已经可以看到我们加的参数了。

下面先建一个默认的(不启用RequestLog)

1
dotnet new tpl -n NoLog

这个命令等价于

1
dotnet new tpl -n WithLog -E false

下面是建好之后的目录结构和Startup.cs

可以看到RequestLog相关的东西都已经不见了。

再建一个启用RequestLog的,看看是不是真的起作用了。

1
dotnet new tpl -n WithLog -E true

可以看到,效果已经出来了。

下面在介绍一个比较有用的特性。动态切换,这个其实和上面介绍的内容相似。

动态切换

直接举个例子来说明吧。

假设我们的模板支持MSSQL, MySQL, PgSQL和SQLite四种数据库操作

在新建一个项目的时候,只需要其中一种,好比说要建一个PgSQL的,肯定就不想看到其他三种。

这里不想看到,有两个地方,一个是nuget包的引用,一个是代码。

上一小节是对某个具体的功能进行了开关的操作,这里有了4个,我们要怎么处理呢?

我们可以用类型是choice的参数来完成这个操作。

修改template.json,加入下面的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
{
"author": "Catcher Wong",

"symbols":{
"sqlType": {
"type": "parameter",
"datatype": "choice",
"choices": [
{
"choice": "MsSQL",
"description": "MS SQL Server"
},
{
"choice": "MySQL",
"description": "MySQL"
},
{
"choice": "PgSQL",
"description": "PostgreSQL"
},
{
"choice": "SQLite",
"description": "SQLite"
}
],
"defaultValue": "MsSQL",
"description": "The type of SQL to use"
},
"MsSQL": {
"type": "computed",
"value": "(sqlType == \"MsSQL\")"
},
"MySQL": {
"type": "computed",
"value": "(sqlType == \"MySQL\")"
},
"PgSQL": {
"type": "computed",
"value": "(sqlType == \"PgSQL\")"
},
"SQLite": {
"type": "computed",
"value": "(sqlType == \"SQLite\")"
}
}
}

看了上面的JSON内容之后,相信大家也知道个所以然了。有一个名为sqlType的参数,它有几中数据库选择,默认是MsSQL。

还另外定义了几个计算型的参数,它的取值是和sqlType的值息息相关的。

MsSQL,MySQL,PgSQL和SQLite这4个参数也是我们在代码里要用到的!!

修改csproj文件,让它可以根据sqlType来动态引用nuget包,我们加入下面的内容

1
2
3
4
5
6
7
8
9
10
11
<ItemGroup Condition="'$(MySQL)' == 'True' ">  
<PackageReference Include="MySqlConnector" Version="0.47.1" />
</ItemGroup>

<ItemGroup Condition="'$(PgSQL)' == 'True' ">
<PackageReference Include="Npgsql" Version="4.0.3" />
</ItemGroup>

<ItemGroup Condition="'$(SQLite)' == 'True' ">
<PackageReference Include="Microsoft.Data.Sqlite" Version="2.1.0" />
</ItemGroup>

同样的,代码也要做相应的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#if (MsSQL)
using System.Data.SqlClient;
#elif (MySQL)
using MySql.Data.MySqlClient;
#elif (PgSQL)
using Npgsql;
#else
using Microsoft.Data.Sqlite;
#endif

protected DbConnection GetDbConnection()
{
#if (MsSQL)
return new SqlConnection(_connStr);
#elif (MySQL)
return new MySqlConnection(_connStr);
#elif (PgSQL)
return new NpgsqlConnection(_connStr);
#else
return new SqliteConnection(_connStr);
#endif
}

修改好之后,同样要去重新安装这个模板,安装好之后,就可以看到sqlType这个参数了。

下面分别创建一个MsSQL和PgSQL的项目,用来对比和验证。

先后执行

1
2
dotnet new tpl -n MsSQLTest -s MsSQL 
dotnet new tpl -n PgSQLTest -s PgSQL

然后打开对应的csproj

可以看到,PgSQL的,添加多了NPgsql这个包。而MsSQL的却没有。

同样的,DapperRepositoryBase也是一样的效果。在创建Connection对象的时候,都根据模板来生成了。

当然这个是在我们自己本地安装的模板,其他人是没有办法使用的。

如果想公开,可以发布到nuget上面去。如果是在公司内部共享,可以搭建一个内部的nuget服务,将模板上传到内部服务器里面去。

下面是一些可以开箱即用的模板:

https://dotnetnew.azurewebsites.net/

总结

有一个自己的项目模板(脚手架),还是很方便的。

一建生成自己需要的东西,减少了不必要的代码复制,可以将更多精力放在业务实现上。

在平时还是要有一些积累,当积累足够丰富之后,我们的脚手架可能就会变得十分强大。

参考文档

dotnet new下面默认的模板 https://github.com/aspnet/Templating

templating的源码 https://github.com/dotnet/templating

template.json的说明 https://github.com/dotnet/templating/wiki/Reference-for-template.json

dotnet cli的文档 https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet?tabs=netcore21

最后是文中的示例代码

Template

抛弃EF,20分构建一个属于自己的ORM框架 - Poiuyt_cyc - 博客园

Excerpt

相信EF大家都不陌生了,因为数据库表跟程序实体是一一对应的原因,我们能够通过lambda这种函数式的编程方式进行操作数据库,感觉非常清晰明了。与我们直接写SQL相比,lambda是强类型,拥有更好的扩展性,伸缩性,而且编程更加的方便,快捷。。下面我们就基于Expression和lambda来与大家构


相信EF大家都不陌生了,因为数据库表跟程序实体是一一对应的原因,我们能够通过lambda这种函数式的编程方式进行操作数据库,感觉非常清晰明了。与我们直接写SQL相比,lambda是强类型,拥有更好的扩展性,伸缩性,而且编程更加的方便,快捷。。下面我们就基于Expression和lambda来与大家构建一个属于自己的ORM框架。

思路的话很简单,就是将lambda转换成我们对应的数据库所需的查询条件,然后执行查询,再将结果以反射的方式封装成List返回出去。

Expression

大家使用EF的时候多多少少会留意到有Expression这个东西。特别是查询时会看到要你传入Expression<Func<T,bool>>这样类型的参数,它又和Func<T,bool>有什么比同呢?

Expression<Func<T,bool>>是表达式树,我们可以通过它来分析我们的委托中的函数。当调用Compile方法后就会变成委托,才能执行。

Func<T,bool>只是一个普通的委托。

例如我们现在有个实体类Staff

复制代码

1
2
3
4
5
6
7
8
<span>public</span> <span>class</span><span> Staff
{
</span><span>public</span> <span>string</span> Name { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>int</span> Age { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>string</span> Code { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> DateTime? Birthday { <span>get</span>; <span>set</span><span>; }
</span><span>public</span> <span>bool</span> Deletion { <span>get</span>; <span>set</span><span>; }
}</span>

复制代码

我们还有一个这样的方法

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
  <span>class</span><span> Program
{
</span><span>static</span> <span>void</span> Main(<span>string</span><span>[] args)
{
FindAs</span>&lt;Staff&gt;(x =&gt; x.Code == <span>"</span><span>张三</span><span>"</span> &amp;&amp; x.Name.Contains(<span>"</span><span>张</span><span>"</span><span>));
}

</span><span>public</span> <span>static</span> List&lt;T&gt; FindAs&lt;T&gt;(Expression&lt;Func&lt;T, <span>bool</span>&gt;&gt;<span> func)
{
</span><span>//</span><span>将func转换成对应数据库的查询条件,然后执行查询</span>
<span>return</span> <span>null</span>;<span>//</span><span>将结果返回</span>
<span> }
}</span>

复制代码

我们希望通过 FindAs(x => x.Age <50 && x.Name.Contains(“张”)); 就能查询出Staff表中Age<50并且Name包含有“张”字的人的信息。而生成的sql语句应该是select * from staff where Age<50 and Name like ‘%张%’。现在我们就来分析下这个func

从上面的图我们可以看到当前的Expression是一个lambda表达式,我们点开它的body看看。

我们可以看到body里分为左边和右边,还有NodeType。和我们的lambda对比下看看’x => x.Code ==”张三” && x.Name.Contains(“张”)’是不是找到点灵感了?我们再继续把左边和右边拆开看看。

可以看到我们需要的信息都有了,看来转换成SQL已经不是什么难事了,动手开搞了。

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
 <span>class</span><span> Program
{
</span><span>static</span> <span>void</span> Main(<span>string</span><span>[] args)
{
FindAs</span>&lt;Staff&gt;(x =&gt; x.Code == <span>"</span><span>张三</span><span>"</span> &amp;&amp; x.Name.Contains(<span>"</span><span>张</span><span>"</span><span>));
FindAs</span>&lt;Staff&gt;(x =&gt; x.Age &lt;= <span>12</span> &amp;&amp; x.Name.Contains(<span>"</span><span>张</span><span>"</span><span>));
Console.ReadKey();
}

</span><span>public</span> <span>static</span> List&lt;T&gt; FindAs&lt;T&gt;(Expression&lt;Func&lt;T, <span>bool</span>&gt;&gt;<span> func)
{
BinaryExpression Binary </span>= func.Body <span>as</span><span> BinaryExpression;
</span><span>string</span> left =<span> ResovleFunc(Binary.Left);
</span><span>string</span> right =<span> ResovleLinqToObject(Binary.Right);
</span><span>string</span> oper =<span> GetOperator(Binary.NodeType);
</span><span>string</span> sql = <span>string</span>.Format(<span>"</span><span>select * from {0} where {1}</span><span>"</span>, <span>typeof</span>(T).Name, left + oper +<span> right);
Console.WriteLine(sql);
</span><span>return</span> <span>null</span>;<span>//</span><span>将结果返回</span>
<span> }

</span><span>//</span><span>解析一般的条件,例如x=&gt;x.name==xxxx x.age==xxx</span>
<span>public</span> <span>static</span> <span>string</span><span> ResovleFunc(Expression express)
{
</span><span>var</span> inner = express <span>as</span><span> BinaryExpression;
</span><span>string</span> Name = (inner.Left <span>as</span><span> MemberExpression).Member.Name;
</span><span>object</span> Value = (inner.Right <span>as</span><span> ConstantExpression).Value;
</span><span>var</span> Operator =<span> GetOperator(inner.NodeType);
</span><span>string</span> Result = <span>string</span>.Format(<span>"</span><span>({0} {1} '{2}')</span><span>"</span><span>, Name, Operator, Value);
</span><span>return</span><span> Result;
}

</span><span>//</span><span>解析linq to object这类扩展方法</span>
<span>public</span> <span>static</span> <span>string</span><span> ResovleLinqToObject(Expression expression)
{
</span><span>var</span> MethodCall = expression <span>as</span><span> MethodCallExpression;
</span><span>var</span> MethodName =<span> MethodCall.Method.Name;
</span><span>if</span> (MethodName == <span>"</span><span>Contains</span><span>"</span><span>)
{
</span><span>object</span> Temp_Vale = (MethodCall.Arguments[<span>0</span>] <span>as</span><span> ConstantExpression).Value;
</span><span>string</span> Value = <span>string</span>.Format(<span>"</span><span>%{0}%</span><span>"</span><span>, Temp_Vale);
</span><span>string</span> Name = (MethodCall.Object <span>as</span><span> MemberExpression).Member.Name;
</span><span>string</span> Result = <span>string</span>.Format(<span>"</span><span>{0} like '{1}'</span><span>"</span><span>, Name, Value);
</span><span>return</span><span> Result;
}
</span><span>return</span> <span>null</span><span>;
}

</span><span>public</span> <span>static</span> <span>string</span><span> GetOperator(ExpressionType expressiontype)
{
</span><span>switch</span><span> (expressiontype)
{
</span><span>case</span><span> ExpressionType.And:
</span><span>return</span> <span>"</span><span>and</span><span>"</span><span>;
</span><span>case</span><span> ExpressionType.AndAlso:
</span><span>return</span> <span>"</span><span>and</span><span>"</span><span>;
</span><span>case</span><span> ExpressionType.Or:
</span><span>return</span> <span>"</span><span>or</span><span>"</span><span>;
</span><span>case</span><span> ExpressionType.OrElse:
</span><span>return</span> <span>"</span><span>or</span><span>"</span><span>;
</span><span>case</span><span> ExpressionType.Equal:
</span><span>return</span> <span>"</span><span>=</span><span>"</span><span>;
</span><span>case</span><span> ExpressionType.NotEqual:
</span><span>return</span> <span>"</span><span>&lt;&gt;</span><span>"</span><span>;
</span><span>case</span><span> ExpressionType.LessThan:
</span><span>return</span> <span>"</span><span>&lt;</span><span>"</span><span>;
</span><span>case</span><span> ExpressionType.LessThanOrEqual:
</span><span>return</span> <span>"</span><span>&lt;=</span><span>"</span><span>;
</span><span>case</span><span> ExpressionType.GreaterThan:
</span><span>return</span> <span>"</span><span>&gt;</span><span>"</span><span>;
</span><span>case</span><span> ExpressionType.GreaterThanOrEqual:
</span><span>return</span> <span>"</span><span>&gt;=</span><span>"</span><span>;
</span><span>default</span><span>:
</span><span>throw</span> <span>new</span> Exception(<span>string</span>.Format(<span>"</span><span>不支持{0}此种运算符查找!</span><span>"</span> +<span> expressiontype));
}
}

}</span>

复制代码

已经初步的达到了我们的目的了,但是我们的查询条件不可能固定是2个,有可能是N个,这时左边和右边又要继续再分下去,直到无法再分(想到递归了吧?)。而且我们还需要将查询条件参数化。而且我们的条件删除时也会用到。所以我们应该把它独立出来。传入一个lambda,生成sql where部分的语句,生成sqlparameter[]。这才是关键。。于是我们来构建一个解析Expresstion的类。。下面我就直接给出我自己写的实现代码了。。

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
 <span>public</span> <span>class</span><span> ResolveExpress
{
</span><span>public</span> Dictionary&lt;<span>string</span>, <span>object</span>&gt;<span> Argument;
</span><span>public</span> <span>string</span><span> SqlWhere;
</span><span>public</span><span> SqlParameter[] Paras;

</span><span>///</span> <span>&lt;summary&gt;</span>
<span>///</span><span> 解析lamdba,生成Sql查询条件
</span><span>///</span> <span>&lt;/summary&gt;</span>
<span>///</span> <span>&lt;param name="expression"&gt;&lt;/param&gt;</span>
<span>///</span> <span>&lt;returns&gt;&lt;/returns&gt;</span>
<span>public</span> <span>void</span><span> ResolveExpression(Expression expression)
{
</span><span>this</span>.Argument = <span>new</span> Dictionary&lt;<span>string</span>, <span>object</span>&gt;<span>();
</span><span>this</span>.SqlWhere =<span> Resolve(expression);
</span><span>this</span>.Paras = Argument.Select(x =&gt; <span>new</span><span> SqlParameter(x.Key, x.Value)).ToArray();
}

</span><span>private</span> <span>string</span><span> Resolve(Expression expression)
{
</span><span>if</span> (expression <span>is</span><span> LambdaExpression)
{
LambdaExpression lambda </span>= expression <span>as</span><span> LambdaExpression;
expression </span>=<span> lambda.Body;
</span><span>return</span><span> Resolve(expression);
}
</span><span>if</span> (expression <span>is</span><span> BinaryExpression)
{
BinaryExpression binary </span>= expression <span>as</span><span> BinaryExpression;
</span><span>if</span> (binary.Left <span>is</span> MemberExpression &amp;&amp; binary.Right <span>is</span> ConstantExpression)<span>//</span><span>解析x=&gt;x.Name=="123" x.Age==123这类</span>
<span>return</span><span> ResolveFunc(binary.Left, binary.Right, binary.NodeType);
</span><span>if</span> (binary.Left <span>is</span> MethodCallExpression &amp;&amp; binary.Right <span>is</span> ConstantExpression)<span>//</span><span>解析x=&gt;x.Name.Contains("xxx")==false这类的</span>
<span> {
</span><span>object</span> value = (binary.Right <span>as</span><span> ConstantExpression).Value;
</span><span>return</span><span> ResolveLinqToObject(binary.Left, value, binary.NodeType);
}
</span><span>if</span> (binary.Left <span>is</span> MemberExpression &amp;&amp; binary.Right <span>is</span> MemberExpression)<span>//</span><span>解析x=&gt;x.Date==DateTime.Now这种</span>
<span> {
LambdaExpression lambda </span>=<span> Expression.Lambda(binary.Right);
Delegate fn </span>=<span> lambda.Compile();
ConstantExpression value </span>= Expression.Constant(fn.DynamicInvoke(<span>null</span><span>), binary.Right.Type);
</span><span>return</span><span> ResolveFunc(binary.Left, value, binary.NodeType);
}
}
</span><span>if</span> (expression <span>is</span><span> UnaryExpression)
{
UnaryExpression unary </span>= expression <span>as</span><span> UnaryExpression;
</span><span>if</span> (unary.Operand <span>is</span> MethodCallExpression)<span>//</span><span>解析!x=&gt;x.Name.Contains("xxx")或!array.Contains(x.Name)这类</span>
<span>return</span> ResolveLinqToObject(unary.Operand, <span>false</span><span>);
</span><span>if</span> (unary.Operand <span>is</span> MemberExpression &amp;&amp; unary.NodeType == ExpressionType.Not)<span>//</span><span>解析x=&gt;!x.isDeletion这样的 </span>
<span> {
ConstantExpression constant </span>= Expression.Constant(<span>false</span><span>);
</span><span>return</span><span> ResolveFunc(unary.Operand, constant, ExpressionType.Equal);
}
}
</span><span>if</span> (expression <span>is</span> MemberExpression &amp;&amp; expression.NodeType == ExpressionType.MemberAccess)<span>//</span><span>解析x=&gt;x.isDeletion这样的 </span>
<span> {
MemberExpression member </span>= expression <span>as</span><span> MemberExpression;
ConstantExpression constant </span>= Expression.Constant(<span>true</span><span>);
</span><span>return</span><span> ResolveFunc(member, constant, ExpressionType.Equal);
}
</span><span>if</span> (expression <span>is</span> MethodCallExpression)<span>//</span><span>x=&gt;x.Name.Contains("xxx")或array.Contains(x.Name)这类</span>
<span> {
MethodCallExpression methodcall </span>= expression <span>as</span><span> MethodCallExpression;
</span><span>return</span> ResolveLinqToObject(methodcall, <span>true</span><span>);
}
</span><span>var</span> body = expression <span>as</span><span> BinaryExpression;
</span><span>if</span> (body == <span>null</span><span>)
</span><span>throw</span> <span>new</span> Exception(<span>"</span><span>无法解析</span><span>"</span> +<span> expression);
</span><span>var</span> Operator =<span> GetOperator(body.NodeType);
</span><span>var</span> Left =<span> Resolve(body.Left);
</span><span>var</span> Right =<span> Resolve(body.Right);
</span><span>string</span> Result = <span>string</span>.Format(<span>"</span><span>({0} {1} {2})</span><span>"</span><span>, Left, Operator, Right);
</span><span>return</span><span> Result;
}

</span><span>///</span> <span>&lt;summary&gt;</span>
<span>///</span><span> 根据条件生成对应的sql查询操作符
</span><span>///</span> <span>&lt;/summary&gt;</span>
<span>///</span> <span>&lt;param name="expressiontype"&gt;&lt;/param&gt;</span>
<span>///</span> <span>&lt;returns&gt;&lt;/returns&gt;</span>
<span>private</span> <span>string</span><span> GetOperator(ExpressionType expressiontype)
{
</span><span>switch</span><span> (expressiontype)
{
</span><span>case</span><span> ExpressionType.And:
</span><span>return</span> <span>"</span><span>and</span><span>"</span><span>;
</span><span>case</span><span> ExpressionType.AndAlso:
</span><span>return</span> <span>"</span><span>and</span><span>"</span><span>;
</span><span>case</span><span> ExpressionType.Or:
</span><span>return</span> <span>"</span><span>or</span><span>"</span><span>;
</span><span>case</span><span> ExpressionType.OrElse:
</span><span>return</span> <span>"</span><span>or</span><span>"</span><span>;
</span><span>case</span><span> ExpressionType.Equal:
</span><span>return</span> <span>"</span><span>=</span><span>"</span><span>;
</span><span>case</span><span> ExpressionType.NotEqual:
</span><span>return</span> <span>"</span><span>&lt;&gt;</span><span>"</span><span>;
</span><span>case</span><span> ExpressionType.LessThan:
</span><span>return</span> <span>"</span><span>&lt;</span><span>"</span><span>;
</span><span>case</span><span> ExpressionType.LessThanOrEqual:
</span><span>return</span> <span>"</span><span>&lt;=</span><span>"</span><span>;
</span><span>case</span><span> ExpressionType.GreaterThan:
</span><span>return</span> <span>"</span><span>&gt;</span><span>"</span><span>;
</span><span>case</span><span> ExpressionType.GreaterThanOrEqual:
</span><span>return</span> <span>"</span><span>&gt;=</span><span>"</span><span>;
</span><span>default</span><span>:
</span><span>throw</span> <span>new</span> Exception(<span>string</span>.Format(<span>"</span><span>不支持{0}此种运算符查找!</span><span>"</span> +<span> expressiontype));
}
}


</span><span>private</span> <span>string</span><span> ResolveFunc(Expression left, Expression right, ExpressionType expressiontype)
{
</span><span>var</span> Name = (left <span>as</span><span> MemberExpression).Member.Name;
</span><span>var</span> Value = (right <span>as</span><span> ConstantExpression).Value;
</span><span>var</span> Operator =<span> GetOperator(expressiontype);
</span><span>string</span> CompName =<span> SetArgument(Name, Value.ToString());
</span><span>string</span> Result = <span>string</span>.Format(<span>"</span><span>({0} {1} {2})</span><span>"</span><span>, Name, Operator, CompName);
</span><span>return</span><span> Result;
}

</span><span>private</span> <span>string</span> ResolveLinqToObject(Expression expression, <span>object</span> value, ExpressionType? expressiontype = <span>null</span><span>)
{
</span><span>var</span> MethodCall = expression <span>as</span><span> MethodCallExpression;
</span><span>var</span> MethodName =<span> MethodCall.Method.Name;
</span><span>switch</span> (MethodName)<span>//</span><span>这里其实还可以改成反射调用,不用写switch</span>
<span> {
</span><span>case</span> <span>"</span><span>Contains</span><span>"</span><span>:
</span><span>if</span> (MethodCall.Object != <span>null</span><span>)
</span><span>return</span><span> Like(MethodCall);
</span><span>return</span><span> In(MethodCall, value);
</span><span>case</span> <span>"</span><span>Count</span><span>"</span><span>:
</span><span>return</span><span> Len(MethodCall, value, expressiontype.Value);
</span><span>case</span> <span>"</span><span>LongCount</span><span>"</span><span>:
</span><span>return</span><span> Len(MethodCall, value, expressiontype.Value);
</span><span>default</span><span>:
</span><span>throw</span> <span>new</span> Exception(<span>string</span>.Format(<span>"</span><span>不支持{0}方法的查找!</span><span>"</span><span>, MethodName));
}
}

</span><span>private</span> <span>string</span> SetArgument(<span>string</span> name, <span>string</span><span> value)
{
name </span>= <span>"</span><span>@</span><span>"</span> +<span> name;
</span><span>string</span> temp =<span> name;
</span><span>while</span><span> (Argument.ContainsKey(temp))
{
</span><span>int</span> code =<span> Guid.NewGuid().GetHashCode();
</span><span>if</span> (code &lt; <span>0</span><span>)
code </span>*= -<span>1</span><span>;
temp </span>= name +<span> code;
}
Argument[temp] </span>=<span> value;
</span><span>return</span><span> temp;
}

</span><span>private</span> <span>string</span> In(MethodCallExpression expression, <span>object</span><span> isTrue)
{
</span><span>var</span> Argument1 = (expression.Arguments[<span>0</span>] <span>as</span> MemberExpression).Expression <span>as</span><span> ConstantExpression;
</span><span>var</span> Argument2 = expression.Arguments[<span>1</span>] <span>as</span><span> MemberExpression;
</span><span>var</span> Field_Array =<span> Argument1.Value.GetType().GetFields().First();
</span><span>object</span>[] Array = Field_Array.GetValue(Argument1.Value) <span>as</span> <span>object</span><span>[];
List</span>&lt;<span>string</span>&gt; SetInPara = <span>new</span> List&lt;<span>string</span>&gt;<span>();
</span><span>for</span> (<span>int</span> i = <span>0</span>; i &lt; Array.Length; i++<span>)
{
</span><span>string</span> Name_para = <span>"</span><span>InParameter</span><span>"</span> +<span> i;
</span><span>string</span> Value =<span> Array[i].ToString();
</span><span>string</span> Key =<span> SetArgument(Name_para, Value);
SetInPara.Add(Key);
}
</span><span>string</span> Name =<span> Argument2.Member.Name;
</span><span>string</span> Operator = Convert.ToBoolean(isTrue) ? <span>"</span><span>in</span><span>"</span> : <span>"</span><span> not in</span><span>"</span><span>;
</span><span>string</span> CompName = <span>string</span>.Join(<span>"</span><span>,</span><span>"</span><span>, SetInPara);
</span><span>string</span> Result = <span>string</span>.Format(<span>"</span><span>{0} {1} ({2})</span><span>"</span><span>, Name, Operator, CompName);
</span><span>return</span><span> Result;
}

</span><span>private</span> <span>string</span><span> Like(MethodCallExpression expression)
{
</span><span>object</span> Temp_Vale = (expression.Arguments[<span>0</span>] <span>as</span><span> ConstantExpression).Value;
</span><span>string</span> Value = <span>string</span>.Format(<span>"</span><span>%{0}%</span><span>"</span><span>, Temp_Vale);
</span><span>string</span> Name = (expression.Object <span>as</span><span> MemberExpression).Member.Name;
</span><span>string</span> CompName =<span> SetArgument(Name, Value);
</span><span>string</span> Result = <span>string</span>.Format(<span>"</span><span>{0} like {1}</span><span>"</span><span>, Name, CompName);
</span><span>return</span><span> Result;
}

</span><span>private</span> <span>string</span> Len(MethodCallExpression expression, <span>object</span><span> value, ExpressionType expressiontype)
{
</span><span>object</span> Name = (expression.Arguments[<span>0</span>] <span>as</span><span> MemberExpression).Member.Name;
</span><span>string</span> Operator =<span> GetOperator(expressiontype);
</span><span>string</span> CompName =<span> SetArgument(Name.ToString(), value.ToString());
</span><span>string</span> Result = <span>string</span>.Format(<span>"</span><span>len({0}){1}{2}</span><span>"</span><span>, Name, Operator, CompName);
</span><span>return</span><span> Result;
}

}</span>

复制代码

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
<span>static</span> <span>void</span> Main(<span>string</span><span>[] args)
{
</span><span>string</span>[] Names = { <span>"</span><span>Andy</span><span>"</span>, <span>"</span><span>Amy</span><span>"</span>, <span>"</span><span>Mike</span><span>"</span><span> };
Expression</span>&lt;Func&lt;Staff, <span>bool</span>&gt;&gt; func = x =&gt; (!Names.Contains(x.Name) &amp;&amp; (x.Name == <span>"</span><span>A</span><span>"</span> || x.Name.Count() &gt; <span>5</span><span>));
ResolveExpress resolve </span>= <span>new</span><span> ResolveExpress();
resolve.ResolveExpression(func);
Console.WriteLine(resolve.SqlWhere);
</span><span>foreach</span> (<span>var</span> item <span>in</span><span> resolve.Paras)
{
Console.WriteLine(item.ParameterName </span>+ <span>"</span><span>:</span><span>"</span> +<span> item.Value);
}
Console.ReadKey();
}</span>

复制代码

结果:

这里有几个重要的东西要给大家讲下

string[] Names={“Andy”,”Amy”,”Mike”};

1.)x => Names.Contains(x.Name);

2.)x => Names.Contains(x.Name)==false;

3.)x => !Names.Contains(x.Name);

这3种在Expression中的表现都不一样

1的话会看成是一个静态方法(MethodCallExpression)

2的话会看成是一个2元运算(BinaryExpression)

3的话会看成是一个1元运算(UnaryExpression)

所以我们都要支持,处理都有所不同。

还有

x=>x.Birthday<DateTime.Now;

string name=”123”;

x=>x.Name==name;

x=>x.Name==”123”

的处理也不一样。大家可以在例子中细细的看看。

这样的构造使得我们切换数据库变得非常简单。因为我们程序中的查询都是基于lambda。换了数据库只要添加一个对应的lamdba转数据库查询条件的实现就可以了。写得够多了。至于数据层怎么封装,到了这一步它已经变得没什么难度了。希望大家能从文章中有所启发和帮助

下篇文章将结合解析Expression和IQueryable来实现延迟加载

补充点东西

IEnumerable和IQueryable有什么不同?

为什么EF查询后返回的是IQueryable而不是IEnumerable。我们对着IQueryableF12去看看。

啥都没,就继承了几个接口。鼠标移到IQueryable上。F12

IQueryable中有3个属性。

Type是类型。

Expresstion是表达式。

那IQueryProvider是什么?

再看看IQueryProvider接口的定义。

CreateQuery是创建查询条件

Execute是执行查询(通常在GetEnumerator()中调用)

当我们IQueryable.Where(x=>x.xxx==”123”)时。其实Where方法内部应该是调用了IQueryable接口中的IQueryProvider属性的CreateQuery(Expresstion expresstion)方法,然后将方法的返回值又返回出来。

而参数(Expresstion )呢?则是IQueryable.Where(x=>x.xxx==”123”)2部分的Expresstion相并。所以IQueryable只是创建条件。所以51楼的朋友说得非常对。

那什么时候执行呢?因为我们的IQueryable继承了IEnumabler,所以我们必须实现GetEnumerator()。我们ToList或foreach时,其实就会调用GetEnumerator()。这时我们就调用Execute进行解析Expresstion,从而得到我们想要的结果。

总结就是IQueryable只是创建条件,当我们调用a.Where(x=>xxx)时,其实是将a与后面的条件相并,生成一个新的IQueryable。当我们foreach时就会调用GetEnumerator()。这时我们一般会调用IQueryProvider里的Execute去解析Expresstion并查询出我们想要的结果

我也知道这篇文章介绍的和我们所说的“ORM”相差很远,但是所谓的ORM最复杂的莫非查询部分了,而依照我这思路走下去,我觉得是可以自己完成一个的。。

我刚开始写博客第二天,没想到这文章反响这么大。我承认有点重复造轮子,也非常不成熟,但我还是想通过自己的思考去构造属于自己的东西。

不知道大家有没看过头文字D,里头有个组织叫东堂垫,他们里面的人是拆掉ABS的。因为他们会长说,你要先学会不使用ABS进行刹车才知道ABS的真谛

快速搭建MQTT服务器(MQTTnet和Apache Apollo) - Zeroes - 博客园

Excerpt

前言 MQTT协议是IBM开发的一个即时通讯协议,有可能成为物联网的重要组成部分,http://mqtt.org/。 MQTT is a machine-to-machine (M2M)/“Internet of Things” connectivity protocol. It


前言

MQTT协议是IBM开发的一个即时通讯协议,有可能成为物联网的重要组成部分,http://mqtt.org/。

MQTT is a machine-to-machine (M2M)/“Internet of Things” connectivity protocol. It was designed as an extremely lightweight publish/subscribe messaging transport. It is useful for connections with remote locations where a small code footprint is required and/or network bandwidth is at a premium. For example, it has been used in sensors communicating to a broker via satellite link, over occasional dial-up connections with healthcare providers, and in a range of home automation and small device scenarios. It is also ideal for mobile applications because of its small size, low power usage, minimised data packets, and efficient distribution of information to one or many receivers

通过https://github.com/mqtt/mqtt.github.io/wiki/servers 找到官方推荐的服务端软件,比如:Apache Apollo

通过https://github.com/mqtt/mqtt.github.io/wiki/libraries可以找到推荐的客户端类库,比如:[Eclipse Paho Java](http://git.eclipse.org/c/paho/org.eclipse.paho.mqtt.java.git/)

MQTTnet

MQTTnet 是MQTT协议的.NET 开源类库。

MQTTnet is a .NET library for MQTT based communication. It provides a MQTT client and a MQTT server. The implementation is based on the documentation from http://mqtt.org/.

通过Nuget搜索MQTT找到了MQTTnet,它不是下载量最多的,也不在官方推荐列表中,主要是因为同时支持客户端和服务端,所以开始下载试用,结果证明有坑,源码在vs2015中不能打开,客户端示例接收不到消息。

开源地址:https://github.com/chkr1011/MQTTnet

首先把官方的控制台程序改成winform的,界面如下:

Form1.cs

需要注意的是按照作者说明是

1

2

3

4

5

6

7

8

9

10

11

12

13

while (true)

{

    Console.ReadLine();

    var applicationMessage = new MqttApplicationMessage(

        "A/B/C",

        Encoding.UTF8.GetBytes("Hello World"),

        MqttQualityOfServiceLevel.AtLeastOnce,

        false

    );

    await client.PublishAsync(applicationMessage);

}

 客户端死活都收不到消息,改成 MqttQualityOfServiceLevel.AtMostOnce  就可以了,找问题时尝试下载源码调试因为vs2015打不开项目也折腾了一会。这个问题提交了issues,期待作者回复。

Apache Apollo

1.下载Apollo服务器,我这里用的是Binaries for Windows。下载后解压到一个文件夹,注意路径不要包含中文,安装手册

2.创建Broker Instance,命令行cd到bin目录,执行/bin/apollo create mybroker,执行后就会在bin目录下创建mybroker文件夹。

3.运行Broker Instance,命令行cd到mybroker/bin目录,执行mybroker/bin/apollo-broker.cmd run

4.Web Administrator,地址 http://127.0.0.1:61680/ or https://127.0.0.1:61681/,默认账号 admin,密码 password

参考

搭建了MQTT服务端之后需要在Esp8266模块和手机App中分别实现客户端功能,稍后待续。。。。

强签名:

1. 可以将强签名的dll注册到GAC,不同的应用程序可以共享同一dll。

2. 强签名的库,或者应用程序只能引用强签名的dll,不能引用未强签名的dll,但是未强签名的dll可以引用强签名的dll。

3. 强签名无法保护源代码,强签名的dll是可以被反编译的。

4. 强签名的dll可以防止第三方恶意篡改。

强签名的方法:

1. 有源代码:

1.1 使用vs tool command:snk –k mykey.snk 生成签名公钥。

1.2 将公钥加入项目中,并设置项目属性,设置签名公钥

image

1.3 重新生成项目。

2. 没有源代码。

2.1 创建强签名键:

sn.exe -k key.snk

2.2 反汇编dll为il

ILDASM.exe SomeLibrary.dll /OUTPUT=SomeLibrary.il

该指令会反汇编该dll并生成SomeLibrary.il,如果该dll含有嵌入的resource,

则会有SomeLibrary.res文件产生,并有相应的嵌入资源文件产生。

2.3 重新汇编为dll

ILASM.exe SomeLibrary.il /DLL /OUTPUT=SomeLibrary.dll /KEY=key.snk

如果有嵌入的资源文件,则需要加上 /RESOURCE=SomeLibrary.res

强签名的dll与未签名的在反编译后的区别:

未签名的:

image

强签名的:

image

更多详细信息参考 StringNaming

http://windowsdevcenter.com/pub/a/dotnet/2003/04/28/strongnaming.html

1.打开“Visual Studio 2008 命令提示”命令行工具。

2. 用Sn.exe 生成一个Public/Private Key Pair 文件:Sn -k test.snk. 如果不指定大小,它的大小就是596 bytes(128 publicKey,32 publicKey Header, 436 PrivateKey)。

3. 添加 [assembly: AssemblyKeyFile(@”test.snk”)] 到程序的AssemblyInfo.cs中,也可以在Build Option中指定(/keyfile:test.snk ). 再重新生成test.dll. 在VisualStudio中还可以用工程属性指定.

4. Sn -v test.dll 查一下test.dll是不是已经是一个strongname的程序了,输出:test.dll is valid。表示成功生成了一个具有PublicKey的程序 Sn -T test.dll 可以得到这个assembly的PublickKeyToken。

为什么使用强名称签名:

通过签发具有强名称的程序集,您可以确保名称的全局唯一性。强名称还特别满足以下要求: 

强名称依赖于唯一的密钥对来确保名称的唯一性。任何人都不会生成与您生成的相同的程序集名称,因为用一个私钥生成的程序集的名称与用其他私钥生成的程序集的名称不相同。 
强名称保护程序集的版本沿袭。强名称可以确保没有人能够生成您的程序集的后续版本。用户可以确信,他们所加载的程序集的版本出自创建该版本(应用程序是用该版本生成的)的同一个发行者。 
强名称提供可靠的完整性检查。通过 .NET 框架安全检查后,即可确信程序集的内容在生成后未被更改过。但请注意,强名称中或强名称本身并不暗含某一级别的信任,例如由数字签名和支持证书提供的信任。 
在引用具有强名称的程序集时,您应该能够从中受益,例如版本控制和命名保护。如果此具有强名称的程序集以后引用了具有简单名称的程序集(后者没有这些好 处),则您将失去使用具有强名称的程序集所带来的好处,并依旧会产生 DLL 冲突。因此,具有强名称的程序集只能引用其他具有强名称的程序集。

GAC

一、GAC的作用

      全称是Global Assembly Cache作用是可以存放一些有很多程序都要用到的公共Assembly,例如System.Data、System.Windows.Forms等等。这样,很多程序就可以从GAC里面取得Assembly,而不需要再把所有要用到的Assembly都拷贝到应用程序的执行目录下面。举例而言,如果没有GAC,那么势必每个WinForm程序的目录下就都要从C:\WINDOWS\Microsoft.NET\Framework\vX下面拷贝一份System.Windows.Forms.dll,这样显然不如都从GAC里面取用方便,也有利于Assembly的升级和版本控制。

二、强命名程序集

     因为不同的公司可能会开发出有相同名字的程序集来,如果这些程序集都被复制到同一 个相同的目录下,最后一个安装的程序集将会代替前面的程序集。这就是著名的Windows “DLL Hell”出现的原因。

  很明显,简单的用文件名来区分程序集是不够的,CLR需要支持某种机制来唯一的标识一个程序集。这就是所谓的强命名程序集。

  一个强命名程序集包含四个唯一标志程序集的特性:文件名(没有扩展名),版本号,语言文化信息(如果有的话),公有秘钥。

  这些信息存储在程序集的清单(manifest)中。清单包含了程序集的元数据,并嵌入在程序集的某个文件中。

  下面的字符串标识了四个不同的程序集文件:

  “MyType, Version=1.0.1.0,

  Culture=neutral, PublicKeyToken=bf5779af662fc055”

  “MyType, Version=1.0.1.0,

  Culture=en-us, PublicKeyToken=bf5779af662fc055”

  “MyType, Version=1.0.2.0,

  Culture=neturl, PublicKeyToken=bf5779af662fc055”

  “MyType, Version=1.0.2.0,

  Culture=neutral, PublicKeyToken=dbe4120289f9fd8a”

  如果一个公司想唯一的标识它的程序集,那么它必须首先获取一个公钥/私钥对,然后将共有秘钥和程序集相关联。不存在两个两个公司有同样的公钥/私钥对的情况,正是这种区分使得我们可以创建有着相同名称,版本和语言文化信息的程序集,而不引起任何冲突。

  与强命名程序集对应的就是所谓的弱命名程序集。(其实就是普通的没有被强命名的程序集)。两种程序集在结构上是相同的。都使用相同的PE文件格式,PE表头,CLR表头,元数据,以及清单(manifest)。二者之间真正的区别在于:强命名程序集有一个发布者的公钥/私钥对签名,其中的公钥/私钥对唯一的标识了程序集的发布者。利用公钥/私钥对,我们可以对程序集进行唯一性识别、实施安全策略和版本控制策略,这种唯一标识程序集的能力使得应用程序在试图绑定一个强命名程序集时,CLR能够实施某些“已确知安全”的策略(比如只信任某个公司的程序集)。

三、如何创建强命名程序集, 如何查看强命名程序集的PublicKeyToken

如何创建强命名程序集

===================

1. 在Visual Studio中的class library工程上点右键, 选择properties.

2.  选择左边的Signing选项卡.

3. 勾选Sign the assembly复选框. 在下拉列表中选择<New…>.

2-7-2010 9-09-21 PM

4. 在弹出的对话框中给snk文件起一个名字. 按OK.

2-7-2010 9-10-23 PM

5. 程序集强命名完成.

2-7-2010 9-12-32 PM

如何查看强命名程序集的public key token

=========================

有时候你需要在web.config文件中或者其他地方引用自己写的强命名程序集, 你需要写入像下面这样的fully qualified name:

MyNamespace.MyAssembly, version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

前面三个部分比较容易获得, 因为是你自己写的, 你当然知道assembly的名字, 版本, 还有culture信息. 比较麻烦的部分是如何获得自己签名的程序集的public key token. 一种平常的方法是使用Reflector来打开自己的程序集, 然后获得token(实际上, Reflector会给你如同上面例子那样的完整信息). 但是这有的时候还是显得有点未免杀鸡用牛刀了. 如果你已经打开了Visual Studio, 那么仅仅是在VS的菜单里点一个菜单项就能获得答案不是更好么? 下面就是步骤.

1. 在Visual Studio中, 打开Tools菜单, 然后点击External Tools这个菜单项.

2. 在弹出的External Tools对话框中, 点击Add按钮.

3. 按照下图进行配置. sn.exe这个工具在不同版本的VS下处于不同的文件夹中. 最简单的找到它的方式是在VS Command Prompt中输入”where sn.exe”. 在参数框里写入”-T $(TargetPath)”. 然后勾选”Use Output Window”. 这样的话, 结果就会在VS的output window. 然后点击OK,

2-7-2010 9-27-57 PM

4. 结果如图.

2-7-2010 9-33-28 PM

5. 在输出窗口可以看到结果. 这在你的solution里有多个project的时候也是可以正常工作的. 只需要点击一下Solution Explorer中的Project, 然后点击我们的菜单项就可以了.

2-7-2010 9-35-31 PM

四、如何将自己的dll注册到GAC中

在开发和测试中,最常用的工具就是GACUtil.exe。 在GAC注册程序集跟COM注册差不多,但相对更容易:
    1.把程序集添加到****GAC中: GACUtil /i sample.dll (参数/i是安装的意思)
    2.把程序集移出GAC GACUtil /u sample.dll (参数/u就移除的意思)
注意:不能将一个弱命名程序集安装到****GAC中。
如果你试图把弱命名程序集加入到****GAC中,会收错误信息:”
    Failure adding assembly to the cache: Attempt to install an assembly without a strong name”
    d)强命名程序集的私有部署

例子:

C:\Program Files\Microsoft Visual Studio 8\VC>gacutil -i F:\myweb\BalloonShop\Cl
assLibrary1\bin\Debug\ClassLibrary1.dll

image

在C:\WINDOWS\assembly将会看到ClassLibrary1,注册成功

image

五、查看GAC文件内容以及将DLL复制出来

在项目中我们常常会引入第三方的dll,一般情况下我们都可以将所需的dll文件复制到硬盘上的一个地方,然后在项目中添加引用,这个操作很简单!但有时候我们会遇到这样的情况,就是所要引用的dll在目标机器的GAC里,这时我们就不能手动将它拷贝出来了。

      其实Windows的GAC是有对应的目录的,一般来说为c:\Windows\assembly\,这个目录有一些特殊,它里面存放的是本机已安装和注册的类库dll,并且不允许用户直接对其中的元素进行相关操作(如复制、剪切、粘贴、修改名称等),不过你可以直接将另一位置的dll文件直接拖放到这个目录下进行dll的安装,但是我们不能直接将已经安装进去的dll再拷贝出来。这里我将介绍一种方法来完成这个操作。

6-19-2009 10-12-55 AM

首先我们切换到Windows的命令行方式,即开始-运行-cmd-回车,然后转到GAC所在的目录,利用dir命令查看一下其中的内容,如下图。

6-19-2009 10-24-50 AM 似乎可以明白GAC中的目录结构了,基本上我们可以根据GAC目录中的Processor Architecture列来区分dir的类型,例如我们要找的System.Web.Extensions属于MSIL,在CMD方式下它应该就对应GAC_MSIL,然后切换到这个目录下并dir。

6-19-2009 10-56-42 AM

看到我们要找的System.Web.Extensions程序集了,它也是一个dir,继续切进去并dir。

6-19-2009 10-59-47 AM这时只有一个目录了,继续切进去,然后dir就可以看到我们最终想要的dll文件了,然后通过copy命令将它复制出来就OK了!

6-19-2009 11-02-37 AM

小技巧:在CMD方式下使用命令时,如果要输入的文件名或目录名太长,可以先敲部分字符,然后通过Tab键自动补全,Windows的command工具会自动为你找到相匹配的内容!

六、例子

image

如上图所示,新建了2个类库文件:ClassLibrary1、ClassLibrary2

1、使用上面的第三点创建了强类型程序集ClassLibrary1,并且注册到GAC中(可以使用上面第三点的方法,也可以使用反编译器进行反编译,可以查看到PublicKeyToken值。ClassLibrary2为null,ClassLibrary1为568e03e6162a7a2e)。

2、在DataAccess中引用ClassLibrary1、ClassLibrary2,编译DataAccess。

3、进入DataAccess工程的bin\debug文件夹下,只有ClassLibrary2.dll与DataAccess.dll(没有ClassLibrary1.dll)

由此可知:程序是从GAC中直接取得ClassLibrary1.dll的而不是从ClassLibrary1工程中将ClassLibrary1.dll拷贝到自身的debug中进行引用。

归并排序算法(C#实现) - Eric Sun - 博客园

Excerpt

自顶向下的归并排序:是利用递归和分而治之的技术将数据序列划分成为越来越小的半子表,再对半子表排序,最后再用递归步骤将排好序的半子表合并成为越来越大的有序序列,归并排序包括两个步骤,分别为:1)划分子表 2)合并半子表


     归并排序(Merge Sort)是利用”归并”技术来进行排序。归并是指将若干个已排序的子文件合并成一个有序的文件。归并排序有两种方式:1): 自底向上的方法 2):自顶向下的方法

 1、 自底向上的方法
(1) 自底向上的基本思想
     自底向上的基本思想是:第1趟归并排序时,将待排序的文件R[1..n]看作是n个长度为1的有序子文件,将这些子文件两两归并,若n为偶数,则得到n/2个长度为2的有序子文件;若n为奇数,则最后一个子文件轮空(不参与归并)。故本趟归并完成后,前n/2 - 1个有序子文件长度为2,但最后一个子文件长度仍为1;第2趟归并则是将第1趟归并所得到的n/2个有序的子文件两两归并,如此反复,直到最后得到一个长度为n的有序文件为止。
     上述的每次归并操作,均是将两个有序的子文件合并成一个有序的子文件,故称其为”二路归并排序”。类似地有k(k>2)路归并排序。   

2、自顶向下的方法(本文主要介绍此种方法,下面的文字都是对此种方法的解读)

(1) 自顶向下的基本思想
     采用分治法进行自顶向下的算法设计,形式更为简洁。
     自顶向下的归并排序:是利用递归和分而治之的技术将数据序列划分成为越来越小的半子表,再对半子表排序,最后再用递归步骤将排好序的半子表合并成为越来越大的有序序列,归并排序包括两个步骤,分别为:

      1)划分子表

      2)合并半子表

(1)分治法的三个步骤
     设归并排序的当前区间是R[low..high],分治法的三个步骤是:
①分解:将当前区间一分为二,即求分裂点
②求解:递归地对两个子区间R[low..mid]和R[mid+1..high]进行归并排序;
③组合:将已排序的两个子区间R[low..mid]和R[mid+1..high]归并为一个有序的区间R[low..high]。
  递归的终结条件:子区间长度为1(一个记录自然有序)。

如下演示递归的整个过程:

递归便是深度遍历(如下由左至右进行遍历):假设有这样的一列数组{9,8,7,6,5,4,3,2,1}进行划分的顺序如下:

{9,8,7,6,5,4,3,2,1} –> {9,8,7,6,5},{4,3,2,1}

{9,8,7,6,5} –> {9,8,7},{6,5}

                        {9,8,7} –> {9,8},{7}

                                          {9,8} –> {9},{8}

                        {6,5} –>{6},{5}

{4,3,2,1} –> {4,3},{2,1}

                      {4,3} –>{4},{3}

                      {2,1} –>{2},{1}

当深度划分到左右数组都只剩1个元素的时候,进行上述逆序的合并:

{9},{8} –> {8,9} 然后和 {7} –> {7,8,9}

                                {6},{5} –> {5,6}    然后 {7,8,9}和{5,6} –> {5,6,7,8,9}

                                     {2},{1} –> {1,2}

                                     {4},{3} –> {3,4}   然后 {1,2}和 {3,4} –> {1,2,3,4}

                                                                                                                         最终{5,6,7,8,9}和{1,2,3,4} –> {1,2,3,4,5,6,7,8,9}

具体实现代码如下所示:

复制代码

1
<span>//</span><span>归并排序(目标数组,子表的起始位置,子表的终止位置)</span><span><br></span>        <span>private</span> <span>static</span> <span>void</span> MergeSortFunction(<span>int</span>[] array, <span>int</span> first, <span>int</span> last)<br>        {<br>            <span>try</span><br>            {<br>                <span>if</span> (first &lt; last)   <span>//</span><span>子表的长度大于1,则进入下面的递归处理</span><span><br></span>                {<br>                    <span>int</span> mid = (first + last) / <span>2</span>;   <span>//</span><span>子表划分的位置</span><span><br></span>                    MergeSortFunction(array, first, mid);   <span>//</span><span>对划分出来的左侧子表进行递归划分</span><span><br></span>                    MergeSortFunction(array, mid + <span>1</span>, last);    <span>//</span><span>对划分出来的右侧子表进行递归划分</span><span><br></span>                    MergeSortCore(array, first, mid, last); <span>//</span><span>对左右子表进行有序的整合(归并排序的核心部分)</span><span><br></span>                }<br>            }<br>            <span>catch</span> (Exception ex)<br>            { }<br>        }<br><br>        <span>//</span><span>归并排序的核心部分:将两个有序的左右子表(以mid区分),合并成一个有序的表</span><span><br></span>        <span>private</span> <span>static</span> <span>void</span> MergeSortCore(<span>int</span>[] array, <span>int</span> first, <span>int</span> mid, <span>int</span> last)<br>        {<br>            <span>try</span><br>            {<br>                <span>int</span> indexA = first; <span>//</span><span>左侧子表的起始位置</span><span><br></span>                <span>int</span> indexB = mid + <span>1</span>;   <span>//</span><span>右侧子表的起始位置</span><span><br></span>                <span>int</span>[] temp = <span>new</span> <span>int</span>[last + <span>1</span>]; <span>//</span><span>声明数组(暂存左右子表的所有有序数列):长度等于左右子表的长度之和。</span><span><br></span>                <span>int</span> tempIndex = <span>0</span>;<br>                <span>while</span> (indexA &lt;= mid &amp;&amp; indexB &lt;= last) <span>//</span><span>进行左右子表的遍历,如果其中有一个子表遍历完,则跳出循环</span><span><br></span>                {<br>                    <span>if</span> (array[indexA] &lt;= array[indexB]) <span>//</span><span>此时左子表的数 &lt;= 右子表的数</span><span><br></span>                    {<br>                        temp[tempIndex++] = array[indexA++];    <span>//</span><span>将左子表的数放入暂存数组中,遍历左子表下标++</span><span><br></span>                    }<br>                    <span>else</span><span>//</span><span>此时左子表的数 &gt; 右子表的数</span><span><br></span>                    {<br>                        temp[tempIndex++] = array[indexB++];    <span>//</span><span>将右子表的数放入暂存数组中,遍历右子表下标++</span><span><br></span>                    }<br>                }<br>                <span>//</span><span>有一侧子表遍历完后,跳出循环,将另外一侧子表剩下的数一次放入暂存数组中(有序)</span><span><br></span>                <span>while</span> (indexA &lt;= mid)<br>                {<br>                    temp[tempIndex++] = array[indexA++];<br>                }<br>                <span>while</span> (indexB &lt;= last)<br>                {<br>                    temp[tempIndex++] = array[indexB++];<br>                }<br><br>                <span>//</span><span>将暂存数组中有序的数列写入目标数组的制定位置,使进行归并的数组段有序</span><span><br></span>                tempIndex = <span>0</span>;<br>                <span>for</span> (<span>int</span> i = first; i &lt;= last; i++)<br>                {<br>                    array[i] = temp[tempIndex++];<br>                }<br>            }<br>            <span>catch</span> (Exception ex)<br>            { }<br>        }

复制代码

       对于N个元素的数组来说, 如此划分需要的层数是以2为底N的对数, 每一层中, 每一个元素都要复制到结果数组中, 并复制回来, 所以复制2N次, 那么对于归并排序,它的时间复杂度为O(N*logN), 而比较次数会少得多, 最少需要N/2次,最多为N-1次, 所以平均比较次数在两者之间. 它的主要问题还是在于在内存中需要双倍的空间.

目录

(一)微信公众号开发之VS远程调试
(二)微信公众号开发之基础梳理
(三)微信公众号开发之自动消息回复和自定义菜单
(四)微信公众号开发之网页授权获取用户基本信息
(五)微信公众号开发之网页中及时获取当前用户Openid及注意事项
(六)微信公众号开发之扫码支付
(七)微信公众号开发之公众号支付
(八)微信公众号开发之现金红包
(九)微信公众号开发之回复图文消息(被动)

前言

 如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。

注意:网页授权两种方式

更多网页授权请查阅官网文档:网页授权

静默授权

静默授权即可以在用户关注的的时候,获取用户基本信息,此过程用户无感知。

第一步,通过工厂类 转发请求

复制代码

      
///
public string HandleRequest()
{ string response = string.Empty;
EventMessage em = EventMessage.LoadFromXml(RequestXml); if (em != null)
{ switch (em.Event.ToLower())
{ case (“subscribe”):
response = SubscribeEventHandler(em);//通过工厂类分发过来的请求,匹配到关注事件
break; case (“unsubscribe”):
response = Un_SubscribeEventHandler(em); break; case “click”:
response = ClickEventHandler(em); break;
}
} return response;
}

复制代码

第二步,写用户关注事件

复制代码

///


/// 用户关注 ///

///
///
public string SubscribeEventHandler(EventMessage em)
{ //回复欢迎消息
WeChat.Messages.TextMessage tm = new WeChat.Messages.TextMessage();
tm.ToUserName = em.FromUserName;//OpenId
tm.FromUserName = em.ToUserName;//公众号原始Id
tm.CreateTime = Common.GetNowTime();
tm.Content = “欢迎您关注****,我是服务小二,有事就问我~\n\n”;
tm.GenerateContent(); //如有业务需要,可在此处先判断该用户是否已关注
       //此处得到OpenId
show.ShowUserInfo(em.FromUserName,em.ToUserName);

return tm.GenerateContent();
}

复制代码

第三步,根据得到的OpenId及accesstoken,即可获取用户基本信息(此处演示是将该用户存入数据库中)

复制代码

///


/// 根据OpenId将此条粉丝记录插入数据库中 ///

///
///
public void ShowUserInfo(string FromUserName, string ToUserName)
{ try {
Models.Users user = new Models.Users();
DAL.User userInfo = new DAL.User(); //获取accesstoken,获取用户基本信息需要Openid和accesstoken即可 accesstoken = Utility.Context.AccessToken; string url = string.Format(“https://api.weixin.qq.com/cgi-bin/user/info?access\_token={0}&openid={1}&lang=zh\_CN“, accesstoken, FromUserName); string result = HttpUtility.GetData(url);
XDocument doc = XmlUtility.ParseJson(result, “root”);
XElement root = doc.Root; if (root != null)
{ #region 取值/存值 subscribe = root.Element(“subscribe”).Value;//是否关注 1 是关注
nickname = root.Element(“nickname”).Value; //昵称
sex = root.Element(“sex”).Value; //性别什么
headimgurl = root.Element(“headimgurl”).Value; //头像url
province = root.Element(“province”).Value;//地区
country = root.Element(“country”).Value;
language = root.Element(“language”).Value;
subscribe_time = root.Element(“subscribe_time”).Value;
DateTime create_time = Common.GetTime(subscribe_time);//将时间戳转换为当前时间
city = root.Element(“city”).Value;
user.OpenID = FromUserName;//OpenID即粉丝ID
user.PublicId = ToUserName; user.UserID = FromUserName;
user.NickName = nickname;
user.Sex = int.Parse(sex);
user.Subscribe = int.Parse(subscribe);
user.Country = country;
user.Province = province;
user.City = city;
user.CreateDate = create_time;
user.HeadimgUrl = headimgurl; //将user实体存入数据库中
bool show = _user.Add(user); #endregion }
} catch { throw (new ArgumentNullException());
}

    }

复制代码

上面代码中 AccessToken的实现,新建一个Context类即可

复制代码

private static DateTime GetAccessToken\_Time; /// <summary>
    /// 过期时间为7200秒 /// </summary>
    private static int Expires\_Period = 7200; /// <summary>
    /// 
    /// </summary>
    private static string mAccessToken; public static string AppID = "换成相应公众号的即可"; public static string AppSecret = "换成相应公众号的即可"; /// <summary>
    /// 调用获取ACCESS\_TOKEN,包含判断是否过期 /// </summary>
    public static string AccessToken
    { get { //如果为空,或者过期,需要重新获取
            if (string.IsNullOrEmpty(mAccessToken) || HasExpired())
            { //获取access\_token
                mAccessToken = GetAccessToken(AppID, AppSecret);
            } return mAccessToken;
        }
    } /// <summary>
    /// 获取ACCESS\_TOKEN方法 /// </summary>
    /// <param name="appId"></param>
    /// <param name="appSecret"></param>
    /// <returns></returns>
    private static string GetAccessToken(string appId, string appSecret)
    { string url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant\_type=client\_credential&appid={0}&secret={1}", appId, appSecret); string result = HttpUtility.GetData(url);

        XDocument doc \= XmlUtility.ParseJson(result, "root");
        XElement root \= doc.Root; if (root != null)
        {
            XElement access\_token \= root.Element("access\_token"); if (access\_token != null)
            {
                GetAccessToken\_Time \= DateTime.Now; if (root.Element("expires\_in") != null)
                {
                    Expires\_Period \= int.Parse(root.Element("expires\_in").Value);
                } return access\_token.Value;
            } else {
                GetAccessToken\_Time \= DateTime.MinValue;
            }
        } return null;
    } /// <summary>
    /// 判断Access\_token是否过期 /// </summary>
    /// <returns>bool</returns>
    private static bool HasExpired()
    { if (GetAccessToken\_Time != null)
        { //过期时间,允许有一定的误差,一分钟。获取时间消耗
            if (DateTime.Now > GetAccessToken\_Time.AddSeconds(Expires\_Period).AddSeconds(-60))
            { return true;
            }
        } return false;
    }

复制代码

GetData的实现

public static string GetData(string url)
{ return SendGetHttpRequest(url, “application/x-www-form-urlencoded”);
}

ParseJson的实现

public static XDocument ParseJson(string json, string rootName)
{ return JsonConvert.DeserializeXNode(json, rootName);
}

关于第三步的 HttpUtility类中还有一些其他公用帮助方法,在这里一并放出,调用即可

///


/// 发送请求 ///

/// Url地址
/// 数据
public static string SendHttpRequest(string url, string data)
{ return SendPostHttpRequest(url, “application/x-www-form-urlencoded”, data);
} ///
///
///

///
///
public static string GetData(string url)
{ return SendGetHttpRequest(url, “application/x-www-form-urlencoded”);
} ///
/// 发送请求 ///

/// Url地址
/// 方法(post或get)
/// 数据类型
/// 数据
public static string SendPostHttpRequest(string url, string contentType, string requestData)
{
WebRequest request = (WebRequest)HttpWebRequest.Create(url);
request.Method = “POST”; byte[] postBytes = null;
request.ContentType = contentType;
postBytes = Encoding.UTF8.GetBytes(requestData);
request.ContentLength = postBytes.Length; using (Stream outstream = request.GetRequestStream())
{
outstream.Write(postBytes, 0, postBytes.Length);
} string result = string.Empty; using (WebResponse response = request.GetResponse())
{ if (response != null)
{ using (Stream stream = response.GetResponseStream())
{ using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
{
result = reader.ReadToEnd();
}
}

            }
        } return result;
    } /// <summary>
    /// 发送请求 /// </summary>
    /// <param name="url">Url地址</param>
    /// <param name="method">方法(post或get)</param>
    /// <param name="method">数据类型</param>
    /// <param name="requestData">数据</param>
    public static string SendGetHttpRequest(string url, string contentType)
    {
        WebRequest request \= (WebRequest)HttpWebRequest.Create(url);
        request.Method \= "GET";
        request.ContentType \= contentType; string result = string.Empty; using (WebResponse response = request.GetResponse())
        { if (response != null)
            { using (Stream stream = response.GetResponseStream())
                { using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
                    {
                        result \= reader.ReadToEnd();
                    }
                }
            }
        } return result;
    }

View Code

顺便提下上文中用到的User类如下

复制代码

public class Users
{ ///


/// 全局凭证唯一Id ///

public string OpenID { get; set; } ///
/// 公众号Id ///

public string PublicId { get; set; } ///
/// 用户Id ///

public string UserID { get; set; } ///
/// 昵称 ///

public string NickName { get; set; } ///
/// 性别 1是男 0是女 ///

public int Sex { get; set; } ///
/// 是否关注 1是关注 ///

public int Subscribe { get; set; } ///
/// 国家 ///

public string Country { get; set; } ///
/// 地区 ///

public string Province { get; set; } ///
/// 城市 ///

public string City { get; set; } ///
/// 关注时间 ///

public DateTime CreateDate { get; set; } ///
/// 用户头像 ///

public string HeadimgUrl { get; set; } ///
/// 第三方平台Id,可为空 ///

public string UnionID { get; set; } ///
/// 用户取消关注时间 ///

public DateTime Un_Subscribe_Time { get; set; }
}

复制代码

演示效果

数据库中此时是存在10条数据的,当点击关注此公众号的时候,就将此用户的基本信息存入数据库了,数据库刷新后变成11条数据

 

网页授权流程

具体介绍依然可参考官网文档:网页授权

第一步,判断该用户是否获取授权,若没有授权,则跳转至授权页面,若授权,则获取基本信息

核心代码

复制代码

    /// <summary>
    /// 获取授权用户的基本信息,包括头像,姓名,等等(推荐方法) /// </summary>
    /// <param name="accessToken">用户授权之后的accessToken</param>
    /// <param name="openid">用户授权之后的openid</param>
    /// <returns></returns>
    public static ShouQuanWeiXinUserInfo GetShouQuanMessage()
    { //先判断是否有获取到用户授权的Code,HttpContext.Current.Session\["ShouquanCode"\]
        if (HttpContext.Current.Session\["ShouquanCode"\] == null|| HttpContext.Current.Session\["ShouquanCode"\].ToString()=="")
        {
            HttpContext.Current.Session\["ShouquanCode"\] = "123"; //用户授权的Code

GetShouQuanCodeUrl(HttpContext.Current.Request.Url.AbsoluteUri);
} else if(HttpContext.Current.Request.QueryString[“code”] == null || HttpContext.Current.Request.QueryString[“code”] == “”)
{ //用户授权的Code
GetShouQuanCodeUrl(HttpContext.Current.Request.Url.AbsoluteUri);
} else { var model = ShouQuanAccessToken(HttpContext.Current.Request.QueryString[“code”]); var url = $”https://api.weixin.qq.com/sns/userinfo?access\_token={model.access\_token}&openid={model.openid}&lang=zh\_CN“; string gethtml = MyHttpHelper.HttpGet(url); var ac = JsonHelpers.ToObject(gethtml); return ac;
} return null;
}

复制代码

其中,用户授权的code方法如下:

复制代码

///


/// 重新获取用户授权的Code,可以获取用户的基本信息(头像,姓名,等等)(推荐用的方法) ///

/// 目标Url
///
public static void GetShouQuanCodeUrl(string url)
{ string CodeUrl = “”; //加密过的url
string value = HttpUtility.UrlEncode(url); //用户授权后的Code
CodeUrl = $”https://open.weixin.qq.com/connect/oauth2/authorize?appid={Appid}&redirect\_uri={value}&response\_type=code&scope=snsapi\_userinfo&state=STATE#wechat\_redirect“;
System.Web.HttpContext.Current.Response.Redirect(CodeUrl);//先跳转到微信的服务器,取得code后会跳回来这页面的
}

复制代码

其中ShouQuanAccessToken方法

复制代码

///


//用户授权之后,获取授权的Access_Token与基本的Access_Token是不同的(推荐方法)
///

/// 用户授权之后的Code
///
public static OauthAccessToken ShouQuanAccessToken(string code)
{ var url = $”https://api.weixin.qq.com/sns/oauth2/access\_token?appid={Appid}&secret={Secret}&code={code}&grant\_type=authorization\_code“; string gethtml = MyHttpHelper.HttpGet(url);
OauthAccessToken ac = new OauthAccessToken();
ac = JsonHelpers.ToObject(gethtml); return ac;
}

复制代码

用户实体

复制代码

public class OauthAccessToken
{ public string access_token { get; set; } public string expires_in { get; set; } public string refresh_token { get; set; } public string openid { get; set; } public string scope { get; set; }
}

复制代码

其中用到的MyHttpHelper公众类如下

///


/// Http连接操作帮助类 ///

public class HttpHelpers
{ #region 预定义方法或者变更
//默认的编码
private Encoding encoding = Encoding.Default; //Post数据编码
private Encoding postencoding = Encoding.Default; //HttpWebRequest对象用来发起请求
private HttpWebRequest request = null; //获取影响流的数据对象
private HttpWebResponse response = null; ///
/// 根据相传入的数据,得到相应页面数据 ///

/// 参数类对象
/// 返回HttpResult类型
public HttpResult GetHtml(HttpItem item)
{ //返回参数
HttpResult result = new HttpResult(); try { //准备参数
SetRequest(item);
} catch (Exception ex)
{ return new HttpResult() { Cookie = string.Empty, Header = null, Html = ex.Message, StatusDescription = “配置参数时出错:” + ex.Message };
} try { #region 得到请求的response
using (response = (HttpWebResponse)request.GetResponse())
{
result.StatusCode = response.StatusCode;
result.StatusDescription = response.StatusDescription;
result.Header = response.Headers; if (response.Cookies != null) result.CookieCollection = response.Cookies; if (response.Headers[“set-cookie”] != null) result.Cookie = response.Headers[“set-cookie”]; byte[] ResponseByte = null; using (MemoryStream _stream = new MemoryStream())
{ //GZIIP处理
if (response.ContentEncoding != null && response.ContentEncoding.Equals(“gzip”, StringComparison.InvariantCultureIgnoreCase))
{ //开始读取流并设置编码方式
new GZipStream(response.GetResponseStream(), CompressionMode.Decompress).CopyTo(_stream, 10240);
} else { //开始读取流并设置编码方式
response.GetResponseStream().CopyTo(_stream, 10240);
} //获取Byte
ResponseByte = _stream.ToArray();
} if (ResponseByte != null & ResponseByte.Length > 0)
{ //是否返回Byte类型数据
if (item.ResultType == ResultType.Byte) result.ResultByte = ResponseByte; //从这里开始我们要无视编码了
if (encoding == null)
{
Match meta = Regex.Match(Encoding.Default.GetString(ResponseByte), “<meta([^<]*)charset=([^<]*)[\“‘]“, RegexOptions.IgnoreCase); string c = (meta.Groups.Count > 1) ? meta.Groups[2].Value.ToLower().Trim() : string.Empty; if (c.Length > 2)
{ try { if (c.IndexOf(“ “) > 0) c = c.Substring(0, c.IndexOf(“ “));
encoding = Encoding.GetEncoding(c.Replace(“\“”, “”).Replace(“‘“, “”).Replace(“;”, “”).Replace(“iso-8859-1”, “gbk”).Trim());
} catch { if (string.IsNullOrEmpty(response.CharacterSet)) encoding = Encoding.UTF8; else encoding = Encoding.GetEncoding(response.CharacterSet);
}
} else { if (string.IsNullOrEmpty(response.CharacterSet)) encoding = Encoding.UTF8; else encoding = Encoding.GetEncoding(response.CharacterSet);
}
} //得到返回的HTML
result.Html = encoding.GetString(ResponseByte);
} else { //得到返回的HTML //result.Html = “本次请求并未返回任何数据”;
result.Html = “”;
}
} #endregion } catch (WebException ex)
{ //这里是在发生异常时返回的错误信息
response = (HttpWebResponse)ex.Response;
result.Html = ex.Message; if (response != null)
{
result.StatusCode = response.StatusCode;
result.StatusDescription = response.StatusDescription;
}
} catch (Exception ex)
{
result.Html = ex.Message;
} if (item.IsToLower) result.Html = result.Html.ToLower(); return result;
} ///
/// 为请求准备参数 ///

///参数列表
private void SetRequest(HttpItem item)
{ // 验证证书
SetCer(item); //设置Header参数
if (item.Header != null && item.Header.Count > 0) foreach (string key in item.Header.AllKeys)
{
request.Headers.Add(key, item.Header[key]);
} // 设置代理 //SetProxy(item);

        if (item.ProtocolVersion != null) request.ProtocolVersion = item.ProtocolVersion;
        request.ServicePoint.Expect100Continue \= item.Expect100Continue; //请求方式Get或者Post
        request.Method = item.Method;
        request.Timeout \= item.Timeout;
        request.KeepAlive \= item.KeepAlive;
        request.ReadWriteTimeout \= item.ReadWriteTimeout; if (!string.IsNullOrWhiteSpace(item.Host))
        {
            request.Host \= item.Host;
        } //Accept
        request.Accept = item.Accept; //ContentType返回类型
        request.ContentType = item.ContentType; //UserAgent客户端的访问类型,包括浏览器版本和操作系统信息
        request.UserAgent = item.UserAgent; // 编码
        encoding = item.Encoding; //设置安全凭证
        request.Credentials = item.ICredentials; //设置Cookie

SetCookie(item); //来源地址
request.Referer = item.Referer; //是否执行跳转功能
request.AllowAutoRedirect = item.Allowautoredirect; //设置Post数据
SetPostData(item); //设置最大连接
if (item.Connectionlimit > 0) request.ServicePoint.ConnectionLimit = item.Connectionlimit;
} ///


/// 设置证书 ///

///
private void SetCer(HttpItem item)
{ if (!string.IsNullOrWhiteSpace(item.CerPath))
{ //这一句一定要写在创建连接的前面。使用回调的方法进行证书验证。
ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(CheckValidationResult); //初始化对像,并设置请求的URL地址
request = (HttpWebRequest)WebRequest.Create(item.URL);
SetCerList(item); //将证书添加到请求里
request.ClientCertificates.Add(new X509Certificate(item.CerPath));
} else { //初始化对像,并设置请求的URL地址
request = (HttpWebRequest)WebRequest.Create(item.URL);
SetCerList(item);
}
} ///
/// 设置多个证书 ///

///
private void SetCerList(HttpItem item)
{ if (item.ClentCertificates != null && item.ClentCertificates.Count > 0)
{ foreach (X509Certificate c in item.ClentCertificates)
{
request.ClientCertificates.Add(c);
}
}
} ///
/// 设置Cookie ///

/// Http参数
private void SetCookie(HttpItem item)
{ if (!string.IsNullOrEmpty(item.Cookie)) request.Headers[HttpRequestHeader.Cookie] = item.Cookie; //设置CookieCollection
if (item.ResultCookieType == ResultCookieType.CookieCollection)
{
request.CookieContainer = new CookieContainer(); if (item.CookieCollection != null && item.CookieCollection.Count > 0)
request.CookieContainer.Add(item.CookieCollection);
}
} ///
/// 设置Post数据 ///

/// Http参数
private void SetPostData(HttpItem item)
{ //验证在得到结果时是否有传入数据
if (request.Method.Trim().ToLower().Contains(“post”))
{ if (item.PostEncoding != null)
{
postencoding = item.PostEncoding;
} byte[] buffer = null; //写入Byte类型
if (item.PostDataType == PostDataType.Byte && item.PostdataByte != null && item.PostdataByte.Length > 0)
{ //验证在得到结果时是否有传入数据
buffer = item.PostdataByte;
}//写入文件
else if (item.PostDataType == PostDataType.FilePath && !string.IsNullOrWhiteSpace(item.Postdata))
{
StreamReader r = new StreamReader(item.Postdata, postencoding);
buffer = postencoding.GetBytes(r.ReadToEnd());
r.Close();
} //写入字符串
else if (!string.IsNullOrWhiteSpace(item.Postdata))
{
buffer = postencoding.GetBytes(item.Postdata);
} if (buffer != null)
{
request.ContentLength = buffer.Length;
request.GetRequestStream().Write(buffer, 0, buffer.Length);
}
}
} ///
/// 设置代理 ///

/// 参数对象
private void SetProxy(HttpItem item)
{ bool isIeProxy = item.ProxyIp.ToLower().Contains(“ieproxy”); if (!string.IsNullOrWhiteSpace(item.ProxyIp) && !isIeProxy)
{ //设置代理服务器
if (item.ProxyIp.Contains(“:”))
{ string[] plist = item.ProxyIp.Split(‘:’);
WebProxy myProxy = new WebProxy(plist[0].Trim(), Convert.ToInt32(plist[1].Trim())); //建议连接
myProxy.Credentials = new NetworkCredential(item.ProxyUserName, item.ProxyPwd); //给当前请求对象
request.Proxy = myProxy;
} else {
WebProxy myProxy = new WebProxy(item.ProxyIp, false); //建议连接
myProxy.Credentials = new NetworkCredential(item.ProxyUserName, item.ProxyPwd); //给当前请求对象
request.Proxy = myProxy;
}
} else if (isIeProxy)
{ //设置为IE代理
} else {
request.Proxy = item.WebProxy;
}
} ///
/// 回调验证证书问题 ///

/// 流对象
/// 证书
/// X509Chain
/// SslPolicyErrors
/// bool
public bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) { return true; } #endregion } ///
/// Http请求参考类 ///

public class HttpItem
{ ///
/// 请求URL必须填写 ///

public string URL { get; set; } string _Method = “GET”; ///
/// 请求方式默认为GET方式,当为POST方式时必须设置Postdata的值 ///

public string Method
{ get { return _Method; } set { _Method = value; }
} int _Timeout = 100000; ///
/// 默认请求超时时间 ///

public int Timeout
{ get { return _Timeout; } set { _Timeout = value; }
} int _ReadWriteTimeout = 30000; ///
/// 默认写入Post数据超时间 ///

public int ReadWriteTimeout
{ get { return _ReadWriteTimeout; } set { _ReadWriteTimeout = value; }
} ///
/// 设置Host的标头信息 ///

public string Host { get; set; }
Boolean _KeepAlive = true; ///
/// 获取或设置一个值,该值指示是否与 Internet 资源建立持久性连接默认为true。 ///

public Boolean KeepAlive
{ get { return _KeepAlive; } set { _KeepAlive = value; }
} string _Accept = “text/html, application/xhtml+xml, */*“; ///
/// 请求标头值 默认为text/html, application/xhtml+xml, */* ///

public string Accept
{ get { return _Accept; } set { _Accept = value; }
} string _ContentType = “text/html”; ///
/// 请求返回类型默认 text/html ///

public string ContentType
{ get { return _ContentType; } set { _ContentType = value; }
} string _UserAgent = “Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)”; ///
/// 客户端访问信息默认Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0) ///

public string UserAgent
{ get { return _UserAgent; } set { _UserAgent = value; }
} ///
/// 返回数据编码默认为NUll,可以自动识别,一般为utf-8,gbk,gb2312 ///

public Encoding Encoding { get; set; } private PostDataType _PostDataType = PostDataType.String; ///
/// Post的数据类型 ///

public PostDataType PostDataType
{ get { return _PostDataType; } set { _PostDataType = value; }
} ///
/// Post请求时要发送的字符串Post数据 ///

public string Postdata { get; set; } ///
/// Post请求时要发送的Byte类型的Post数据 ///

public byte[] PostdataByte { get; set; } ///
/// Cookie对象集合 ///

public CookieCollection CookieCollection { get; set; } ///
/// 请求时的Cookie ///

public string Cookie { get; set; } ///
/// 来源地址,上次访问地址 ///

public string Referer { get; set; } ///
/// 证书绝对路径 ///

public string CerPath { get; set; } ///
/// 设置代理对象,不想使用IE默认配置就设置为Null,而且不要设置ProxyIp ///

public WebProxy WebProxy { get; set; } private Boolean isToLower = false; ///
/// 是否设置为全文小写,默认为不转化 ///

public Boolean IsToLower
{ get { return isToLower; } set { isToLower = value; }
} private Boolean allowautoredirect = false; ///
/// 支持跳转页面,查询结果将是跳转后的页面,默认是不跳转 ///

public Boolean Allowautoredirect
{ get { return allowautoredirect; } set { allowautoredirect = value; }
} private int connectionlimit = 1024; ///
/// 最大连接数 ///

public int Connectionlimit
{ get { return connectionlimit; } set { connectionlimit = value; }
} ///
/// 代理Proxy 服务器用户名 ///

public string ProxyUserName { get; set; } ///
/// 代理 服务器密码 ///

public string ProxyPwd { get; set; } ///
/// 代理 服务IP,如果要使用IE代理就设置为ieproxy ///

public string ProxyIp { get; set; } private ResultType resulttype = ResultType.String; ///
/// 设置返回类型String和Byte ///

public ResultType ResultType
{ get { return resulttype; } set { resulttype = value; }
} private WebHeaderCollection header = new WebHeaderCollection(); ///
/// header对象 ///

public WebHeaderCollection Header
{ get { return header; } set { header = value; }
} ///
// 获取或设置用于请求的 HTTP 版本。返回结果:用于请求的 HTTP 版本。默认为 System.Net.HttpVersion.Version11。
///

public Version ProtocolVersion { get; set; } private Boolean _expect100continue = true; ///
/// 获取或设置一个 System.Boolean 值,该值确定是否使用 100-Continue 行为。如果 POST 请求需要 100-Continue 响应,则为 true;否则为 false。默认值为 true。 ///

public Boolean Expect100Continue
{ get { return _expect100continue; } set { _expect100continue = value; }
} ///
/// 设置509证书集合 ///

public X509CertificateCollection ClentCertificates { get; set; } ///
/// 设置或获取Post参数编码,默认的为Default编码 ///

public Encoding PostEncoding { get; set; } private ResultCookieType _ResultCookieType = ResultCookieType.String; ///
/// Cookie返回类型,默认的是只返回字符串类型 ///

public ResultCookieType ResultCookieType
{ get { return _ResultCookieType; } set { _ResultCookieType = value; }
} private ICredentials _ICredentials = CredentialCache.DefaultCredentials; ///
/// 获取或设置请求的身份验证信息。 ///

public ICredentials ICredentials
{ get { return _ICredentials; } set { _ICredentials = value; }
}
} ///
/// Http返回参数类 ///

public class HttpResult
{ ///
/// Http请求返回的Cookie ///

public string Cookie { get; set; } ///
/// Cookie对象集合 ///

public CookieCollection CookieCollection { get; set; } ///
/// 返回的String类型数据 只有ResultType.String时才返回数据,其它情况为空 ///

public string Html { get; set; } ///
/// 返回的Byte数组 只有ResultType.Byte时才返回数据,其它情况为空 ///

public byte[] ResultByte { get; set; } ///
/// header对象 ///

public WebHeaderCollection Header { get; set; } ///
/// 返回状态说明 ///

public string StatusDescription { get; set; } ///
/// 返回状态码,默认为OK ///

public HttpStatusCode StatusCode { get; set; }
} ///
/// 返回类型 ///

public enum ResultType
{ ///
/// 表示只返回字符串 只有Html有数据 ///

String, ///
/// 表示返回字符串和字节流 ResultByte和Html都有数据返回 ///

Byte
} ///
/// Post的数据格式默认为string ///

public enum PostDataType
{ ///
/// 字符串类型,这时编码Encoding可不设置 ///

String, ///
/// Byte类型,需要设置PostdataByte参数的值编码Encoding可设置为空 ///

Byte, ///
/// 传文件,Postdata必须设置为文件的绝对路径,必须设置Encoding的值 ///

FilePath
} ///
/// Cookie返回类型 ///

public enum ResultCookieType
{ ///
/// 只返回字符串类型的Cookie ///

String, ///
/// CookieCollection格式的Cookie集合同时也返回String类型的cookie ///

CookieCollection
} /// HttpHelper的2次封装函数 作者: ///
public class MyHttpHelper
{ #region 公共函数
/// 返回 HTML 字符串的编码结果
/// 字符串
/// 编码结果
public static string HtmlEncode(string str)
{ if (string.IsNullOrEmpty(str))
{ return “”;
} return str.Length > 0 ? HttpUtility.HtmlEncode(str) : “”;
} /// 返回 HTML 字符串的解码结果
/// 字符串
/// 解码结果
public static string HtmlDecode(string str)
{ if (string.IsNullOrEmpty(str))
{ return “”;
} return str.Length > 0 ? HttpUtility.HtmlDecode(str) : “”;
} ///
/// 根据指定的编码对RUl进行解码 ///

/// 要解码的字符串
/// 要进行解码的编码方式
///
public static string UrlDecode(string str, Encoding encoding = null)
{ if (string.IsNullOrEmpty(str))
{ return “”;
} if (str.Length == 0)
{ return “”;
} if (encoding == null)
{ return HttpUtility.UrlDecode(str);
} else { return HttpUtility.UrlDecode(str, encoding);
}
} /// 根据指定的编码对URL进行编码
/// 要编码的URL
/// 要进行编码的编码方式
///
public static string UrlEncode(string str, Encoding encoding = null)
{ if (string.IsNullOrEmpty(str))
{ return “”;
} if (str.Length == 0)
{ return “”;
} if (encoding == null)
{ return HttpUtility.UrlEncode(str);
} else { return HttpUtility.UrlEncode(str, encoding);
}
} ///
/// 根据 charSet 返回 Encoding ///

/// “gb2312” or “utf-8”,默认: “” == “utf-8”
///
public static Encoding GetEncoding(string charSet)
{
Encoding en = Encoding.Default; if (charSet == “gb2312”)
{
en = Encoding.GetEncoding(“gb2312”);
} else if (charSet == “utf-8”)
{
en = Encoding.UTF8;
} return en;
} #endregion

    #region Post

    /// <summary>HTTP Get方式请求数据</summary>
    /// <param name="url">URL</param>
    /// <param name="param">user=123123 & pwd=1231313"</param>
    /// <param name="charSet">"gb2312" or "utf-8",默认: "" ==  "utf-8"</param>
    /// <returns></returns>
    public static string HttpPost(string url, string param, string charSet = "utf-8")
    {
        HttpHelpers http \= new HttpHelpers();
        HttpItem item \= new HttpItem()
        {
            URL \= url,
            Encoding \= GetEncoding(charSet),//编码格式(utf-8,gb2312,gbk)     可选项 默认类会自动识别
            Method = "post",//URL     可选项 默认为Get
            Postdata = param
        }; //得到HTML代码
        HttpResult result = http.GetHtml(item); //取出返回的Cookie //string cookie = result.Cookie; //返回的Html内容
        string html = result.Html; if (result.StatusCode == System.Net.HttpStatusCode.OK)
        { return html;
        } //string statusCodeDescription = result.StatusDescription;
        return "";
    } #endregion

    #region Get
    /// <summary>HTTP Get方式请求数据</summary>
    /// <param name="url">URL</param>
    /// <param name="charSet">"gb2312" or "utf-8",默认: "" ==  "utf-8"</param>
    /// <returns></returns>
    public static string HttpGet(string url, string charSet = "utf-8")
    {
        HttpHelpers http \= new HttpHelpers();
        HttpItem item \= new HttpItem()
        {
            URL \= url,
            Encoding \= GetEncoding(charSet),
            Method \= "get" }; //得到HTML代码
        HttpResult result = http.GetHtml(item); //取出返回的Cookie //string cookie = result.Cookie; //返回的Html内容
        string html = result.Html; if (result.StatusCode == System.Net.HttpStatusCode.OK)
        { return html;
        } //string statusCodeDescription = result.StatusDescription;
        return "";
    } /// <summary>POST客服消息/summary> /// <param name="url">URL</param>
    /// <param name="postData">内容</param>
    /// <returns>消息状态</returns>
    public static string GetPage(string posturl, string postData)
    {
        Stream outstream \= null;
        Stream instream \= null;
        StreamReader sr \= null;
        HttpWebResponse response \= null;
        HttpWebRequest request \= null;
        Encoding encoding \= Encoding.UTF8; byte\[\] data = encoding.GetBytes(postData); // 准备请求... 
        try { // 设置参数 
            request = WebRequest.Create(posturl) as HttpWebRequest;
            CookieContainer cookieContainer \= new CookieContainer();
            request.CookieContainer \= cookieContainer;
            request.AllowAutoRedirect \= true;
            request.Method \= "POST";
            request.ContentType \= "application/x-www-form-urlencoded";
            request.ContentLength \= data.Length;
            outstream \= request.GetRequestStream();
            outstream.Write(data, 0, data.Length);
            outstream.Close(); //发送请求并获取相应回应数据 
            response = request.GetResponse() as HttpWebResponse; //直到request.GetResponse()程序才开始向目标网页发送Post请求 
            instream = response.GetResponseStream();
            sr \= new StreamReader(instream, encoding); //返回结果网页(html)代码 
            string content = sr.ReadToEnd(); string err = string.Empty; return content;
        } catch (Exception ex)
        { string err = ex.Message; return err;
        }
    } #endregion }

View Code

封装的JsonHelpers类如下

#region 通用

    /// <summary>检查字符串是否json格式</summary>
    /// <param name="jText"></param>
    /// <returns></returns>
    public static bool IsJson(string jText)
    { if (string.IsNullOrEmpty(jText) || jText.Length < 3)
        { return false;
        } if (jText.Substring(0, 2) == "{\\"" || jText.Substring(0, 3) == "\[{\\"")
        { return true;
        } return false;
    } /// <summary>检查字符串是否json格式数组</summary>
    /// <param name="jText"></param>
    /// <returns></returns>
    public static bool IsJsonRs(string jText)
    { if (string.IsNullOrEmpty(jText) || jText.Length < 3)
        { return false;
        } if (jText.Substring(0, 3) == "\[{\\"")
        { return true;
        } return false;
    } /// <summary>格式化 json</summary>
    /// <param name="jText"></param>
    /// <returns></returns>
    public static string Fmt\_Null(string jText)
    { return StringHelper.ReplaceString(jText, ":null,", ":\\"\\",", true);
    } /// <summary>格式化 json ,删除左右二边的\[\]</summary>
    /// <param name="jText"></param>
    /// <returns></returns>
    public static string Fmt\_Rs(string jText)
    {
        jText \= jText.Trim();
        jText \= jText.Trim('\[');
        jText \= jText.Trim('\]'); return jText;
    } #endregion

    #region Json序列化

    /// <summary>序列化</summary>
    /// <param name="obj">object </param>
    /// <returns></returns>
    public static string ToJson(object obj)
    { var idtc = new Newtonsoft.Json.Converters.IsoDateTimeConverter { DateTimeFormat = "yyyy-MM-dd hh:mm:ss" }; return JsonConvert.SerializeObject(obj, idtc);
    } /// <summary>序列化--sql</summary>
    /// <param name="dt">DataTable</param>
    /// <returns></returns>   
    public static string ToJson\_FromSQL(DataTable dt)
    { string ss = ToJson(dt);
        dt.Dispose(); return ss;
    } #endregion

    #region Json反序列化

    /// <summary>反序列化</summary>
    /// <param name="jText"></param>
    /// <returns></returns>      
    public static DataTable ToDataTable(string jText)
    { if (string.IsNullOrEmpty(jText))
        { return null;
        } else { try { return JsonConvert.DeserializeObject<DataTable>(jText);
            } catch { return null;
            }
        }
    } /// <summary>反序列化</summary>
    /// <typeparam name="T">类型</typeparam>
    /// <param name="jText">json字符串</param>
    /// <returns>类型数据</returns>
    public static T ToObject<T>(string jText)
    { return (T)JsonConvert.DeserializeObject(jText, typeof(T));
    } #endregion

View Code

其中,如果是VS2015以下的,可以将url字符串改成string.format(“”)方式

调用取值的方式

效果展示

点击公众号链接效果如下:

 

未完待续,持续填坑中。。。

__EOF__

作  者:**潇十一郎**
出  处:https://www.cnblogs.com/zhangxiaoyong/p/6270768.html
关于博主:编程路上的小学生,热爱技术,喜欢专研。评论和私信会在第一时间回复。或者直接私信我。
版权声明:署名 - 非商业性使用 - 禁止演绎,协议普通文本 | 协议法律文本
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!

最近做了一个公司的微信的公众号,对微信的流程清楚了不少,这里记录下,算不上多高深的,只希望能帮助到一部分人吧。

我们公司的测试微信公众号:NPCgo 可以感受下,哈哈~~

闲话少说,开始:

首先大家要看下微信的API文档。

微信网页授权,获取用户的微信官方API文档地址:
http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html

三次握手
微信认证流程(我自己简称三次握手):
1、用户同意授权,获取code
2、通过code换取网页授权access_token,用户openId等信息
3、通过access_token和用户的openId获取该用户的用户信息

思路:
经过研究,我这边的思路是:让所有页面都继承同一个页面,在这个页面里做微信登录授权处理,
因为第一步必须要经过微信的登录授权,不能网页后端请求,所以先要经过用户同意,通过页面网页请求组装的微信请求链接。请求该链接,
获取code后,后端模拟请求。获取用户信息。

微信三次握手的方法(代码)

复制代码

public class WeiXinOAuth
{ ///


/// 获取微信Code ///

///
///
///
public string GetWeiXinCode(string appId,string appSecret,string redirectUrl)
{
Random r = new Random(); //微信登录授权 //string url = “https://open.weixin.qq.com/connect/qrconnect?appid=“ + appId + “&redirect_uri=” + redirectUrl +”&response_type=code&scope=snsapi_login&state=STATE#wechat_redirect”; //微信OpenId授权 //string url = “https://open.weixin.qq.com/connect/oauth2/authorize?appid=“ + appId + “&redirect_uri=” + redirectUrl +”&response_type=code&scope=snsapi_login&state=STATE#wechat_redirect”; //微信用户信息授权
string url = “https://open.weixin.qq.com/connect/oauth2/authorize?appid=“ + appId + “&redirect_uri=” + redirectUrl + “&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect”; return url;
} ///
/// 通过code获取access_token ///

///
///
///
///
public Model.WeiXinAccessTokenResult GetWeiXinAccessToken(string appId,string appSecret,string code)
{ string url = “https://api.weixin.qq.com/sns/oauth2/access\_token?appid="+appId+"&secret="+appSecret+
“&code=”+ code + “&grant_type=authorization_code”; string jsonStr = Tools.GetHttpRequest(url);

    Model.WeiXinAccessTokenResult result \= new Model.WeiXinAccessTokenResult(); if (jsonStr.Contains("errcode"))
    {
        Model.WeiXinErrorMsg errorResult \= new Model.WeiXinErrorMsg();
        errorResult\=JsonHelper.ParseFromJson<Model.WeiXinErrorMsg>(jsonStr);
        result.ErrorResult \= errorResult;
        result.Result \= false;
    } else {
        Model.WeiXinAccessTokenModel model \= new Model.WeiXinAccessTokenModel();
        model \= JsonHelper.ParseFromJson<Model.WeiXinAccessTokenModel>(jsonStr);
        result.SuccessResult \= model;
        result.Result \= true;
    } return result;
} /// <summary>
/// 拉取用户信息 /// </summary>
/// <param name="accessToken"></param>
/// <param name="openId"></param>
/// <returns></returns>
public Model.WeiXinUserInfoResult GetWeiXinUserInfo(string accessToken,string openId)
{ string url = "https://api.weixin.qq.com/sns/userinfo?access\_token="+accessToken+"&openid="+openId+"&lang=zh\_CN"; string jsonStr = Tools.GetHttpRequest(url);
    Model.WeiXinUserInfoResult result \= new Model.WeiXinUserInfoResult(); if(jsonStr.Contains("errcode"))
    {
        Model.WeiXinErrorMsg errorResult \= new Model.WeiXinErrorMsg();
        errorResult \= JsonHelper.ParseFromJson<Model.WeiXinErrorMsg>(jsonStr);
        result.ErrorMsg \= errorResult;
        result.Result \= false;
    } else {
        Model.WeiXinUserInfo userInfo \= new Model.WeiXinUserInfo();
        userInfo \= JsonHelper.ParseFromJson<Model.WeiXinUserInfo>(jsonStr);
        result.UserInfo \= userInfo;
        result.Result \= true;
    } return result;
}

}

复制代码

所需要的对应实体类

WeiXinAccessTokenResult 类:

public class WeiXinAccessTokenResult
{ public WeiXinAccessTokenModel SuccessResult { get; set; } public bool Result { get; set; } public WeiXinErrorMsg ErrorResult { get; set; }

}

View Code

WeiXinAccessTokenModel类:

///


/// 通过code获取access_token 请求成功的实体 ///

public class WeiXinAccessTokenModel
{ ///
/// 接口调用凭证 ///

public string access_token { get; set; } ///
/// access_token接口调用凭证超时时间,单位(秒) ///

public int expires_in { get; set; } ///
/// 用户刷新access_token ///

public string refresh_token { get; set; } ///
/// 授权用户唯一标识 ///

public string openid { get; set; } ///
/// 用户授权的作用域,使用逗号(,)分隔 ///

public string scope { get; set; }
}

View Code

///


/// 微信错误访问的情况 ///

public class WeiXinErrorMsg
{ ///
/// 错误编号 ///

public int errcode { get; set; } ///
/// 错误提示消息 ///

public string errmsg { get; set; }
}

View Code

WeiXinUserInfoResult类:

///


/// 获取微信用户信息 ///

public class WeiXinUserInfoResult
{ ///
/// 微信用户信息 ///

public WeiXinUserInfo UserInfo { get; set; } ///
/// 结果 ///

public bool Result { get; set; } ///
/// 错误信息 ///

public WeiXinErrorMsg ErrorMsg { get; set; }
}

View Code

WeiXinUser 类 :

public class WeiXinUserInfo
{ ///


/// 用户的唯一标识 ///

public string openid { get; set; } ///
/// 用户昵称 ///

public string nickname { get; set; } ///
/// 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知 ///

public string sex { get; set; } ///
/// 用户个人资料填写的省份 ///

public string province { get; set; } ///
/// 普通用户个人资料填写的城市 ///

public string city { get; set; } ///
/// 国家,如中国为CN ///

public string country { get; set; } ///
/// 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空 ///

public string headimgurl { get; set; } ///
/// 用户特权信息,json 数组,如微信沃卡用户为(chinaunicom) ///

public string[] privilege { get; set; }
}

View Code

所有的页面,都会继承BasePage页面,这样方便处理,继承这个页面的其他页面就不需要考虑认证的问题了。

复制代码

public partial class BasePage : System.Web.UI.Page
{ public BasePage()
{ this.Page.Load += new EventHandler(Page_Load); this.Page.Unload += new EventHandler(Page_UnLoad);
} protected void Page_Load(object sender, EventArgs e)
{
DoWith();
} protected void Page_UnLoad(object sender, EventArgs e)
{
} private void DoWith()
{ //用户尚未登录
if (BLL.UserInfoManager.Instance().GetUserId() <= 0)
{ //获取appId,appSecret的配置信息
string appId = System.Configuration.ConfigurationSettings.AppSettings[“appid”]; string appSecret = System.Configuration.ConfigurationSettings.AppSettings[“secret”];
Core.WeiXinOAuth weixinOAuth = new WeiXinOAuth(); //微信第一次握手后得到的code 和state
string _code = Cmn.Request.Get(“code”); string _state = Cmn.Request.Get(“state”); if (_code == “” || _code == “authdeny”)
{ if (_code == “”)
{ //发起授权(第一次微信握手)
string _authUrl = weixinOAuth.GetWeiXinCode(appId, appSecret, HttpContext.Current.Server.UrlEncode(HttpContext.Current.Request.Url.ToString()));
HttpContext.Current.Response.Redirect(_authUrl, true);
} else { // 用户取消授权
HttpContext.Current.Response.Redirect(“~/Error.html”, true);
}
} else { //获取微信的Access_Token(第二次微信握手)
Core.Model.WeiXinAccessTokenResult modelResult = weixinOAuth.GetWeiXinAccessToken(appId, appSecret, _code); //获取微信的用户信息(第三次微信握手)
Core.Model.WeiXinUserInfoResult _userInfo = weixinOAuth.GetWeiXinUserInfo(modelResult.SuccessResult.access_token,modelResult.SuccessResult.openid); //用户信息(判断是否已经获取到用户的微信用户信息)
if (_userInfo.Result && _userInfo.UserInfo.openid != “”)
{ //保存获取到的用户微信用户信息,并保存到数据库中
} else {
GameTradingByPublic.ExceptionLog.writeFile(2, “获取用户OpenId失败”);
}
}
}
}
}

复制代码

 2016-6-16 更新:

关于模拟请求Request相关方法说明。可以参考我另外一篇文章:http://www.cnblogs.com/woaic/p/3942923.html

写个博客不容易,请转载的时候备注下原文出处,谢谢
作者:keepnode
博客地址:http://www.cnblogs.com/woaic
每件事到最后都是好事,如果不是好事,说明还没有到最后
=========================

注:文章内容为摘录性文字,自己阅读的一些笔记,方便日后查看。

微服务(Microservices)

在过去的 2016 年和 2017 年,微服务技术迅猛普及,和容器技术一起成为这两年中最吸引眼球的技术热点。而以 Spring Cloud 为代表的传统侵入式开发框架,占据着微服务市场的主流地位。

微服务(Microservices)是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。

形像一点来说,微服务架构就像搭积木,每个微服务都是一个零件,并使用这些零件组装出不同的形状。通俗来说,微服务架构就是把一个大系统按业务功能分解成多个职责单一的小系统,并利用简单的方法使多个小系统相互协作,组合成一个大系统。

如果学科派一点,微服务架构就是把因相同原因而变化的功能聚合到一起,而把因不同原因而变化的功能分离开,并利用轻量化机制(通常为 HTTP RESTful API)实现通信。

微服务架构 ≈ 模块化开发 + 分布式计算。

需要注意的是“微服务”与“微服务架构”是有本质区别的。“微服务”强调的是服务的大小,它关注的是某一个点。而“微服务架构”则是一种架构思想,需要从整体上对软件系统进行通盘的考虑。

微服务架构示意图:

常见的微服务组件及概念:

  • 服务注册:服务提供方将自己调用地址注册到服务注册中心,让服务调用方能够方便地找到自己。
  • 服务发现:服务调用方从服务注册中心找到自己需要调用的服务的地址。
  • 负载均衡:服务提供方一般以多实例的形式提供服务,负载均衡功能能够让服务调用方连接到合适的服务节点。并且,节点选择的工作对服务调用方来说是透明的。
  • 服务网关:服务网关是服务调用的唯一入口,可以在这个组件是实现用户鉴权、动态路由、灰度发布、A/B 测试、负载限流等功能。
  • 配置中心:将本地化的配置信息(properties, xml, yaml 等)注册到配置中心,实现程序包在开发、测试、生产环境的无差别性,方便程序包的迁移。
  • API 管理:以方便的形式编写及更新 API 文档,并以方便的形式供调用者查看和测试。
  • 集成框架:微服务组件都以职责单一的程序包对外提供服务,集成框架以配置的形式将所有微服务组件(特别是管理端组件)集成到统一的界面框架下,让用户能够在统一的界面中使用系统。
  • 分布式事务:对于重要的业务,需要通过分布式事务技术(TCC、高可用消息服务、最大努力通知)保证数据的一致性。
  • 调用链:记录完成一个业务逻辑时调用到的微服务,并将这种串行或并行的调用关系展示出来。在系统出错时,可以方便地找到出错点。
  • 支撑平台:系统微服务化后,系统变得更加碎片化,系统的部署、运维、监控等都比单体架构更加复杂,那么,就需要将大部分的工作自动化。现在,可以通过 Docker 等工具来中和这些微服务架构带来的弊端。 例如持续集成、蓝绿发布、健康检查、性能健康等等。严重点,以我们两年的实践经验,可以这么说,如果没有合适的支撑平台或工具,就不要使用微服务架构。

微服务架构的优点:

  • 降低系统复杂度:每个服务都比较简单,只关注于一个业务功能。
  • 松耦合:微服务架构方式是松耦合的,每个微服务可由不同团队独立开发,互不影响。
  • 跨语言:只要符合服务 API 契约,开发人员可以自由选择开发技术。这就意味着开发人员可以采用新技术编写或重构服务,由于服务相对较小,所以这并不会对整体应用造成太大影响。
  • 独立部署:微服务架构可以使每个微服务独立部署。开发人员无需协调对服务升级或更改的部署。这些更改可以在测试通过后立即部署。所以微服务架构也使得 CI/CD 成为可能。
  • Docker 容器:和 Docker 容器结合的更好。
  • DDD 领域驱动设计:和 DDD 的概念契合,结合开发会更好。

微服务架构的缺点:

  • 微服务强调了服务大小,但实际上这并没有一个统一的标准:业务逻辑应该按照什么规则划分为微服务,这本身就是一个经验工程。有些开发者主张 10-100 行代码就应该建立一个微服务。虽然建立小型服务是微服务架构崇尚的,但要记住,微服务是达到目的的手段,而不是目标。微服务的目标是充分分解应用程序,以促进敏捷开发和持续集成部署。
  • 微服务的分布式特点带来的复杂性:开发人员需要基于 RPC 或者消息实现微服务之间的调用和通信,而这就使得服务之间的发现、服务调用链的跟踪和质量问题变得的相当棘手。
  • 分区的数据库体系和分布式事务:更新多个业务实体的业务交易相当普遍,不同服务可能拥有不同的数据库。CAP 原理的约束,使得我们不得不放弃传统的强一致性,而转而追求最终一致性,这个对开发人员来说是一个挑战。
  • 测试挑战:传统的单体WEB应用只需测试单一的 REST API 即可,而对微服务进行测试,需要启动它依赖的所有其他服务。这种复杂性不可低估。
  • 跨多个服务的更改:比如在传统单体应用中,若有 A、B、C 三个服务需要更改,A 依赖 B,B 依赖 C。我们只需更改相应的模块,然后一次性部署即可。但是在微服务架构中,我们需要仔细规划和协调每个服务的变更部署。我们需要先更新 C,然后更新 B,最后更新 A。
  • 部署复杂:微服务由不同的大量服务构成。每种服务可能拥有自己的配置、应用实例数量以及基础服务地址。这里就需要不同的配置、部署、扩展和监控组件。此外,我们还需要服务发现机制,以便服务可以发现与其通信的其他服务的地址。因此,成功部署微服务应用需要开发人员有更好地部署策略和高度自动化的水平。
  • 总的来说(问题和挑战):API Gateway、服务间调用、服务发现、服务容错、服务部署、数据调用。

不过,现在很多微服务的框架(比如 Spring Cloud、Dubbo)已经很好的解决了上面的问题。

资料来源:

服务网格(Service Mesh)

2017 年底,非侵入式的 Service Mesh 技术从萌芽到走向了成熟。

Service Mesh 又译作“服务网格”,作为服务间通信的基础设施层

如果用一句话来解释什么是 Service Mesh,可以将它比作是应用程序或者说微服务间的 TCP/IP,负责服务之间的网络调用、限流、熔断和监控。对于编写应用程序来说一般无须关心 TCP/IP 这一层(比如通过 HTTP 协议的 RESTful 应用),同样使用 Service Mesh 也就无须关系服务之间的那些原来是通过应用程序或者其他框架实现的事情,比如 Spring Cloud、OSS,现在只要交给 Service Mesh 就可以了。

Service Mesh 的来龙去脉:

  1. 从最原始的主机之间直接使用网线相连
  2. 网络层的出现
  3. 集成到应用程序内部的控制流
  4. 分解到应用程序外部的控制流
  5. 应用程序的中集成服务发现和断路器
  6. 出现了专门用于服务发现和断路器的软件包/库,如 Twitter 的 Finagle 和 Facebook 的 Proxygen,这时候还是集成在应用程序内部
  7. 出现了专门用于服务发现和断路器的开源软件,如 Netflix OSS、Airbnb 的 synapse 和 nerve
  8. 最后作为微服务的中间层 Service Mesh 出现

Service Mesh 有如下几个特点:

  • 应用程序间通讯的中间层
  • 轻量级网络代理
  • 应用程序无感知
  • 解耦应用程序的重试/超时、监控、追踪和服务发现

Service Mesh 架构图:

目前流行的 Service Mesh 开源软件有 Linkerd、Envoy 和 Istio,而最近 Buoyant(开源 Linkerd 的公司)又发布了基于 Kubernetes 的 Service Mesh 开源项目 Conduit。

Service Mesh 开源项目简介:

关于微服务和服务网格的区别,我的一些理解:微服务更像是一个服务之间的生态,专注于服务治理等方面,而服务网格更专注于服务之间的通信,以及和 DevOps 更好的结合

资料来源:

常见的微服务架构用到的软件&组件:

docker(成熟应用)

spring boot % spring cloud(技术趋势)

Service Fabric(属于后起之秀 背后是微软云的驱动)

四种常用的微服务架构方案,分别是ZeroC IceGrid、Spring Cloud、基于消息队列与Docker Swarm。

实际生产中多半是组合的模式运用例如最佳实践spring cloud+docker。

微服务特性——持续集成(Jenkins,Snap-CI),构建(Maven,Gradle),部署(Docker),持续交付(Jenkins),日志聚合(ELK,Splunk),运维(监控警告Zabbix,Nagios) 

微软的 Azure Service Fabric 的官方博客在2017.3.24日发布了一篇博客 Service Fabric .NET SDK goes open source ,介绍了社区呼声最高的 Service Fabric 开源的情况以及当前的情况,当时开源了 Service Fabric的.NET SDK 部分,社区一直在期盼着 Service Fabric 的正式开源,经过了一年漫长的等待,2018年3月14日微软终于开源了 Service Fabric ,而且是以 MIT 许可下开放源代码,在官方博客宣布 https://blogs.msdn.microsoft.com/azureservicefabric/2018/03/14/service-fabric-is-going-open-source。

目前微软在 Github 上的开源地址是 https://github.com/Microsoft/service-fabric,目前的代码构建适用于 Linux 的 Service Fabric ,运行基本测试,有问题可以在上面提交 issue 和 PR 了,Windows 构建环境以及完整的 CI 环境还没有迁移过来。Windows 内部为 Service Fabric 开发了将近十年的内部服务,其中大部分时间都是微软内部平台,比如 Office365,Azure Stack 平台等,这意味着我们有近十年的内部微软工具可以在迁移之前完成迁移和流程细化,逐步全部开源,以后全部开发都在开源模式下进行开发工作。

Service Fabric基本概念: Node, Application, Service, Partition/Replicas

Azure Service Fabric 是一款分布式系统平台,可方便用户轻松打包、部署和管理可缩放的可靠微服务和容器。 开发人员和管理员不需解决复杂的基础结构问题,只需专注于实现苛刻的任务关键型工作负荷,即那些可缩放、可靠且易于管理的工作负荷。

     本节将为大家介绍Azure Service Fabric的基本概念及相关组件的工作机制, 包括Micro Service, Node type, Node等等。虽然名称叫Azure Service Fabric但其可应用的平台远不止Azure平台本身,我们会在后续章节的使用场景中为大家专门描述Service Fabric在各大平台上的工作形式。

Microsoft Azure Service Fabric是微软开发的一套支撑高可用高伸缩云服务的框架,其核心部分是一个分布式系统平台,用于构建可扩展的可靠应用。在便于封装可部署代码的同时,支持创建无状态和有状态的微服务,通过云平台来伸缩他们,来应对高复杂度、低延迟、数据密集的情况。开发者和系统管理员可以免于处理复杂的基础设施问题,将精力更多地投入到所构建应用程序的实现上。

 

微服务Microservice

在具体介绍Service Fabric之前,不得不先提一下微服务的思想。因为使用Service Fabric的开发过程就是微服务的设计开发过程。有了Service Fabric,您只需要考虑开发微服务的功能,而无需过多考虑部署后的伸缩性和可用性的问题,这些问题都可以交给Service Fabric来帮您实现。

微服务的思想就是将复杂单体式应用程序解耦成多个各个独立的服务,在功能不变的情况下,被分解出来的多个可管理的服务可以通过约定的接口相互通信。这种方法为采用单体式编码很难实现的功能提供了模块化的解决方案。因为,单个服务可以更易于开发、维护。这种架构方式使每个单个服务都可以有专门的团队来开发,每个团队可以各自选择自己擅长的开发技术,通过约定接口来实现相互通信。每个服务可以独立实现、测试、部署和升级,开发者不再需要担心其他服务部署对本服务的影响。AB测试加快了部署的速度,从而实现持续集成持续部署。所有微服务作为一个整体为用户提供服务,同时各个微服务可以根据自身对资源的需求独立扩展,从而最大化服务器的资源利用率。

回到Service Fabric, 一个Service Fabric开发的应用程序由数个服务组成,每个服务可以作为个体独自修改、扩展和管理,同时可以按照一个完整的应用程序来管理。Service fabric的设计目的就是用微服务的方式来简化构建复杂应用的过程。

集群Cluster

集群是一组通过网络连接的虚拟或者物理主机,您的微服务就部署在集群中,集群的大小可以扩展到上千台主机。

节点Node

集群中的一台机器或者VM称为Node, 每个Node会被分配一个名称(string字符串)。Node还有其他一些属性,比如位置属性placement properties。可以通过每台机器或者VM都有一个自启动Windows系统服务FabricHost.exe,它随系统启动后会执行另外两个程序:Fabric.exe 和 FabricGateway.exe, 这两个程序就组成了一个完整的Node。出于测试目的,有时单台机器上也可以通过运行多个Fabric.exe 和 FabricGateway.exe的实例来拥有多个Node。

一个集群中的所有Node相互之间平等且可以直接互相通信。Node除了宿主在物理主机或VM中,还可以宿主在基于Windows的Docker容器中、本地部署的服务器中、其他公有云和私有云中,我们会在后续Service Fabric的使用场景中为大家详细介绍这一内容。

 

应用程序Application:Application Type和Service Type

Service Fabric应用程序(Application)是一组服务(service)的集合,其中一个service是为Application提供指定功能的单元。您将通过定义一个Application Type和对应的几个Service Type来构建一个Service Fabric的Application. 当Application被部署到Service Fabric Cluster里面时,这些类型会被相应地初始化成application实例和service实例。这里类似我们OO地思想。

Application Type和Named Application: Application Type包含一组Service Type的集合,对应上文中的Service Fabric应用程序(Application)是一组服务(service)的集合。 Application Type的name和version定义在ApplicationManifest.xml文件中。在部署的时候,ApplicationManifest.xml会被拷贝到Service Fabric的image store中。通过在Cluster中创建Named Application来初始化Application的实例。Named Application通过”fabric:/MyNamedApp”的形式来命名。

Service Type和Named Service: Service Type的name和version定义在ServiceManifest.xml文件中。当创建好一个Named Application后,就可以创建Named service. 例如您在 “MyNamedApp” Named Application中创建一个 “MyDatabase” Named Service, Name Service被命名为 “fabric:/MyNamedApp/MyDatabase”.

 

分区Partitions和复制replicas

一个service可以包含多个分区Partition,Service Fabric通过使用分区作为扩展的机制来将工作分布到不同的service实例上。

一个分区Partition可以包含一个或者多个复制replicas。Service Fabric通过使用复制来实现可用性。一个分区可以有一个主复制和多个从复制,多个复制之间的状态可以自动同步。当主复制出现错误时,其中一个从复制被自动提升为主复制,以保证系统的可用性。然后将从复制的个数恢复到正常水平,保证足够的从复制冗余。

Service Fabric是微软提供的微服务管理框架,经过了微软Cosmos DB等多个产品的验证。

Service Fabric官方文档只提供了Visual Studio + .net + C#的开发部署方案和Linux + Eclipse + Java的部署方案,但没有Visual Studio+Eclipse+Java的部署方法,通过摸索和文档,发现微软提供了这样的途径,微软真是比以前开放多了。

1. 首先在Visual Studio中创建Service Fabric工程,

2. 选择来宾可执行文件,输入第一个微服务的名称,选择可执行的jar包所在文件目录,Work Folder选为CodeBase,

3. 在VS项目树中,选择Jar包,同时将java运行环境包复制/粘贴到code目录中,

4.  打开”ServiceManifest.xml”, 更改以下参数,

  1. Program: This should point to java.exe file in JRE folder that was copied.
  2. Arguments: This should contain the -jar and path of the JAR filename relative to java.exe. They are arguments passed to java.exe when it starts.
  3. WorkingFolder: This should be CodeBase.
  4. Endpoint: Name a endpoint and provide protocol as HTTP and port no (8080) along with type(input)

5. 点击Start按钮,就可以将此微服务部署搭配本地Service Fabric Cluster上了。

【案例深度讲解】利用Service Fabric承载eShop On Containers

从Pet Shop 到eShop on Container都是Microsoft在技术演进的路径上给开发者展示.Net的开发能力和架构能力的Sample工程,Petshop的时候更多的是展现应用的分层架构,设计的抽象与模块间的通讯。到了eShop on Container更多的关注在架构设计与微服务化的,下面我们先来看看eshop on Container的架构图

在上图,我们可以看到后端服务分成了

  1. Identity microservice(验证服务)
  2. Catalog microservice(商品分类服务)
  3. Ordering microservice(订单服务)
  4. Basket microservice(购物车服务)
  5. Marketing microservice(市场营销服务)
  6. Locations microservice(地理位置信息服务)

在以前的分层架构中,通常这些服务都是以某一模块来体现的,为什么现在要将他们拆分成了各个服务呢?当我们从业务场景上面来看这些服务时,我们会发现每个服务的访问峰值时间区间、容量规划都是不一样的,甚至实现这些服务最方便最简单的技术栈都有可能是不一样的(当然强大的.net core无所不能,但是公司内不同业务线上的技术储备不一样,就有可能选择不同的技术实现)。这是因为如果我们都将这些模块整合到了一个程序或者服务中的时候,就会碰到在不同时间内服务高峰期扩展系统容量困难,要不就是资源不足,要不就是资源过剩。譬如抢购业务开始前大家提前个半小时登录了系统,这时候系统最忙的是登录模块,到了开始抢购时间,系统最忙的是订单模块。不采用微服务架构的话,半小时前准备给登录模块使用的资源不一定能够及时的释放出来给订单模块。如果两个模块都使用单一程序架构的话,很可能出现的情况就是抢购的业务把所有资源都占满了了,连其他正常访问系统的用户资源都被占用掉,导致系统崩溃。在讲究Dev/Ops的今天,开发人员和架构师需要更多的考虑硬件架构层面对程序应用带来的影响。

首先我们先到Azure上申请一个Container Registry来承载eShop各个微服务程序的镜像(image).创建Azure Docker Registry可以参考官方文档:https://docs.microsoft.com/zh-cn/azure/container-registry/

现在最新版本Service Fabric已经可以直接管理编排Docker了。

1.创建一个类型为Container的Service

image

2.在servicemanifest.xml中描述清楚image所在路径

复制代码

<!-- Follow this link for more information about deploying Windows containers to Service Fabric: https://aka.ms/sfguestcontainers -->
<EntryPoint>

  <ContainerHost>
    <ImageName>eshopsample.azurecr.io/catalog:latest</ImageName>       
  </ContainerHost>      
</EntryPoint>
<!-- Pass environment variables to your container: -->   
<EnvironmentVariables>
  <EnvironmentVariable Name="HttpGatewayPort" Value=""/>
</EnvironmentVariables>

复制代码

这里非常简单,指定了image所在位置就好了,如果本身Docker Image里需要很多配置信息譬如:数据库链接串、其他服务的地址等等都可以在EnvironmentVariables里面去配置。

3.配置Registry的访问账号密码,需要在ApplicationManifest.xml上面来配置

复制代码

  </ContainerHostPolicies>
</Policies>

复制代码

整个过程不会太复杂,只要配置好了Catalog microserivce的ServiceManifest.xm和ApplicationManifest.xml文件之后,我们可以用同样的方法将其他服务一一配置完成,然后我们就可以将Service Fabric的配置Publish到Cluster上面了。

image

Service Fabric会自动根据配置在Cluster上面Pull Image和将Docker运行起来。非常简单

Service Fabric本身就是个微服务的开发框架,现在已经直接支持了.net Core 2.0了所以,我们更新了Service Fabric的SDK之后就可以直接创建.net core的服务了

imageimage

eShop on Container的代码都已经是一份成型的.net core 2.0的代码,所以不需要重新编写服务。

1.通过nuget添加最新的Service Fabric最新的SDK。

image

2.修改programe.cs,启动ServiceFabric Runtime而不是直接启动Asp.net WebHost

复制代码

public static void Main(string[] args)
{

        try
        {
            // ServiceManifest.XML 文件定义一个或多个服务类型名称。
            // 注册服务会将服务类型名称映射到 .NET 类型。
            // 在 Service Fabric 创建此服务类型的实例时,
            // 会在此主机进程中创建类的实例。

            ServiceRuntime.RegisterServiceAsync("Catalog.API",
                context => new CatalogAPI(context)).GetAwaiter().GetResult();

            ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(CatalogAPI).Name);

            // 防止此主机进程终止,以使服务保持运行。 
            Thread.Sleep(Timeout.Infinite);
        }
        catch (Exception e)
        {
            ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString());
            throw;
        }

}

复制代码

3.编写

CatalogAPI 类用于启动WebHost

复制代码

internal sealed class CatalogAPI : StatelessService
{
public CatalogAPI(StatelessServiceContext context)
: base(context)
{ }

    /// <summary>
    /// Optional override to create listeners (like tcp, http) for this service instance.
    /// </summary>
    /// <returns>The collection of listeners.</returns>
    protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
    {
        return new ServiceInstanceListener\[\]
        {
            new ServiceInstanceListener(serviceContext =>
                new KestrelCommunicationListener(serviceContext, "ServiceEndpoint", (url, listener) =>
                {
                    ServiceEventSource.Current.ServiceMessage(serviceContext, $"Starting WebListener on {url}");
                                            return new WebHostBuilder()
                                     .UseKestrel()
                                .ConfigureServices(
                                    services => services
                                        .AddSingleton<StatelessServiceContext>(serviceContext))
                                .UseContentRoot(Directory.GetCurrentDirectory())
                                .ConfigureAppConfiguration((builderContext, config) =>
                                {
                                    IHostingEnvironment env = builderContext.HostingEnvironment;

                                    config.AddJsonFile("settings.json", optional: false, reloadOnChange: true)
                                        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
                                   
                                })
                                .UseStartup<Startup>()
                                .UseServiceFabricIntegration(listener, ServiceFabricIntegrationOptions.None)
                                .UseUrls(url)
                                .UseWebRoot("Pics")
                                .Build();                  
                }))
        };
    }
}

复制代码

4.编写serviceManifest.xml描述服务端口等信息

复制代码




Catalog.API.exe CodePackage
<Endpoints>   

  <Endpoint Protocol="http" Name="ServiceEndpoint"  Type="Input"  Port="5101" />
</Endpoints>

复制代码

5.修改AppcationManifest.xml增加几个服务的描述信息

添加ServiceImport节

在DefaultService中描述Service

这样我们就可以将Catalog这个服务改造成可以通过Service Fabric来管理的微服务了。通过Publish,我们可看到几个服务都已经在Service Fabric下面接受管理和编排了。

image

访问localhost:5100

image

学习Azure Service Fabric的总结:

  • Azure Service Fabric其实分为两块:Azure和Service Fabric。
  • Service Fabric只是一套软件分布式系统,理论上它可以使用在非Azure环境。也就是说:非Azure环境的机器集群,进行合理配置,也可以使用Service Fabric 构建分布式系统。
  • 当我们在Azure门户上创建Azure Service Fabric时,会自动创建Azure Load Balancer, Azure Virtual Machine Scale Set, Azure Virtual Machine。这是因为这些组件都是在Azure环境中将Service Fabric接入公网必须的组件和平台。但是理论上如果今后有其他的产品,通过合理的负载均衡和配置逻辑,只要可以让服务器集群面向外部网络提供服务,Service Fabric都可以适用。
  • Service Fabric自行构建了一整套虚拟概念,包括后面会提及的Micro Service, Node type, Node等等。这些概念都是仅在Service Fabric范围内适用。例如Micro Service,可以用C#构建,也可以用Java实现。Node type可以是Azure VMSS, 也可是是硬件物理服务器。
  • Service Fabric帮助架构师将分布式系统和硬件进行脱耦。理想程度下,所有职责如下:
    • 软件开发者只需要关系分布式微服务功能逻辑实现,微服务之间如何调用通过统一接口完成
    • 应用部署者只需要关心如何将微服务部署至各个node,以及考虑应用的升级维护
    • 硬件架构师只需要关心维护虚拟机和网络之间的部署关系,并且在虚拟机性能产生问题是增加虚拟机来分担压力

【参考资料】

1、大话微服务架构之微服务框架微软ServiceFabric正式开源(三) http://baijiahao.baidu.com/s?id=1595179278058800506

2、Service Fabric https://azure.microsoft.com/zh-cn/documentation/learning-paths/service-fabric/

3、DevOps 工具集成 | Microsoft Azure https://azure.microsoft.com/zh-cn/products/devops-tool-integrations/

4、【图文】微服务架构设计_https://wenku.baidu.com/view/eb6467d10408763231126edb6f1aff00bed570a3.html

5、微服务架构与实践摘要-电子发烧友网触屏版 http://m.elecfans.com/article/631884.html

6、微服务架构设计 - PetterLiu - 博客园 https://www.cnblogs.com/wintersun/p/6219259.html

7、微服务实践:从单体式架构迁移到微服务架构 https://blog.csdn.net/gaowenhui2008/article/details/70239716

8、微服务架构 - 老_张 - 博客园 https://www.cnblogs.com/imyalost/p/6792724.html

本文转自:https://blog.csdn.net/enweitech/article/details/80696225