|
@@ -0,0 +1,281 @@
|
|
1
|
+![](./imgs/znzmsy.jpg)
|
|
2
|
+
|
|
3
|
+> 需求
|
|
4
|
+
|
|
5
|
+- **共性提炼**:因为从首页项目,到楼层、区域、电箱展示内容格式相同,视图模型需要提炼出基类。
|
|
6
|
+- **层级扩展**:设计图中给出的是"项目、楼层、区域、电箱",如果有更多的层级如何处理?这里不能针对每个层级硬编码,这样会写很多接口,应该保持尽量少的接口。
|
|
7
|
+- **分页**:如果列表项很多,需要分页。
|
|
8
|
+- **中介模式**:希望控制器方法内代码尽量简洁,把请求交给一套机制,该机制返回响应。这里采用中介者模式。
|
|
9
|
+
|
|
10
|
+> 视图模型设计
|
|
11
|
+
|
|
12
|
+从前端发来的请求,服务端需要通过类来封装。所有的请求都涉及到分页、集团相关、项目相关、连接字符串相关,提炼出一个基类。
|
|
13
|
+
|
|
14
|
+BaseQuery.cs
|
|
15
|
+```
|
|
16
|
+public SieveModel PagingModel{get;set;}
|
|
17
|
+public string GroupId{get;set;}
|
|
18
|
+public string ProjectId{get;set;}
|
|
19
|
+public string ConnKey{get;set;}
|
|
20
|
+```
|
|
21
|
+以上,`SieveModel`来自一个第三方的有关分页的库,叫做`Sieve`。
|
|
22
|
+
|
|
23
|
+考虑到首页和内页不同之处,在基类基础上再设计:
|
|
24
|
+GeneralSumQuery.cs
|
|
25
|
+```
|
|
26
|
+//IRequest<T>来自一个中介者模式的库MediatR
|
|
27
|
+//意思是说,只要给我GeneralSumQuery,我就返回GeneralSumViewModel给外界
|
|
28
|
+//GeneralSumViewModel这个模型是给手机端,手机端拿着这个模型去填充页面
|
|
29
|
+public class GeneralSumQuery : BaseQuery, IRequest<GeneralSumViewModel>
|
|
30
|
+{
|
|
31
|
+ //首页的Project和内页的Location都需要用到
|
|
32
|
+ //当Level=0,就代表首页Project,当Level=1比如代表楼层,以此类推
|
|
33
|
+ public int Level{get;set;}
|
|
34
|
+
|
|
35
|
+ //这个属性是给Location用的,就是根据不同的LocationId去加载其下的子级Location
|
|
36
|
+ //子级Location有一个属性叫做ParentId,这个属性就指向父级的LocationId
|
|
37
|
+ //所有,根据父级的主键,也就是这里的LocationId能获得其下的所有子级Location
|
|
38
|
+ public string LocationId{get;set;}
|
|
39
|
+}
|
|
40
|
+```
|
|
41
|
+
|
|
42
|
+`GeneralSumViewModel`是返回给手机端用的,它代表如下红框中的部分:
|
|
43
|
+
|
|
44
|
+![](./imgs/vm1.png)
|
|
45
|
+
|
|
46
|
+对应代码:
|
|
47
|
+
|
|
48
|
+```
|
|
49
|
+public class GeneralSumViewModel
|
|
50
|
+{
|
|
51
|
+ //一旦有集合属性,一定要在构造函数内初始化,否则容易报null的错
|
|
52
|
+ public GeneralSumViewModel()
|
|
53
|
+ {
|
|
54
|
+
|
|
55
|
+ Items = new List<GeneralSumItemViewModel>();
|
|
56
|
+ }
|
|
57
|
+
|
|
58
|
+ public List<GeneralSumItemViewmodel> Items{get;set;}
|
|
59
|
+}
|
|
60
|
+```
|
|
61
|
+`GeneralSumItemViewmodel`代表这列表项,即如下红框中的部分:
|
|
62
|
+![](./imgs/vm2.png)
|
|
63
|
+
|
|
64
|
+代码如下:
|
|
65
|
+
|
|
66
|
+```
|
|
67
|
+public class GeneralSumItemViewModel : BaseViewModel
|
|
68
|
+ {
|
|
69
|
+ /// <summary>
|
|
70
|
+ /// 名称:项目名称、楼层名称、区域名称、电箱名称
|
|
71
|
+ /// </summary>
|
|
72
|
+ public string Name { get; set; }
|
|
73
|
+
|
|
74
|
+ /// <summary>
|
|
75
|
+ /// 报警颜色:WarningColorEnum
|
|
76
|
+ /// </summary>
|
|
77
|
+ public short Color { get; set; }
|
|
78
|
+
|
|
79
|
+ /// <summary>
|
|
80
|
+ /// 是否可以进入到下一级
|
|
81
|
+ /// </summary>
|
|
82
|
+ public bool IsContinue { get; set; }
|
|
83
|
+
|
|
84
|
+ /// <summary>
|
|
85
|
+ /// 下一个位置层级
|
|
86
|
+ /// </summary>
|
|
87
|
+ public int NextLevel { get; set; }
|
|
88
|
+
|
|
89
|
+ /// <summary>
|
|
90
|
+ /// 本层Location主键,项目的LocationId=-1
|
|
91
|
+ /// </summary>
|
|
92
|
+ public string LocationId { get; set; }
|
|
93
|
+
|
|
94
|
+ /// <summary>
|
|
95
|
+ /// 报警
|
|
96
|
+ /// </summary>
|
|
97
|
+ public GeneralWarningItemViewModel WarningItem { get; set; }
|
|
98
|
+
|
|
99
|
+ /// <summary>
|
|
100
|
+ /// 负荷
|
|
101
|
+ /// </summary>
|
|
102
|
+ public GeneralFuHeItemViewModel FuHeItem { get; set; }
|
|
103
|
+
|
|
104
|
+ /// <summary>
|
|
105
|
+ /// 开关比例
|
|
106
|
+ /// </summary>
|
|
107
|
+ public GeneralKGBLItemViewModel KGBLItem { get; set; }
|
|
108
|
+```
|
|
109
|
+
|
|
110
|
+视图模型的基类:
|
|
111
|
+```
|
|
112
|
+public class BaseViewModel
|
|
113
|
+ {
|
|
114
|
+ public string ProjectId { get; set; }
|
|
115
|
+ public string ConnKey { get; set; }
|
|
116
|
+ }
|
|
117
|
+```
|
|
118
|
+有关报警的视图模型:
|
|
119
|
+
|
|
120
|
+```
|
|
121
|
+public class GeneralWarningItemViewModel : BaseViewModel
|
|
122
|
+ {
|
|
123
|
+ /// <summary>
|
|
124
|
+ /// 报警描述
|
|
125
|
+ /// </summary>
|
|
126
|
+ public string Description { get; set; }
|
|
127
|
+ /// <summary>
|
|
128
|
+ /// 报警颜色:WarningColorEnum
|
|
129
|
+ /// </summary>
|
|
130
|
+ public short Color { get; set; }
|
|
131
|
+ /// <summary>
|
|
132
|
+ /// 报警数
|
|
133
|
+ /// </summary>
|
|
134
|
+ public string WarningNum { get; set; }
|
|
135
|
+ }
|
|
136
|
+```
|
|
137
|
+有关负荷的视图模型:
|
|
138
|
+
|
|
139
|
+```
|
|
140
|
+public class GeneralFuHeItemViewModel : BaseViewModel
|
|
141
|
+ {
|
|
142
|
+ /// <summary>
|
|
143
|
+ /// 负荷值
|
|
144
|
+ /// </summary>
|
|
145
|
+ public string FuHeValue { get; set; }
|
|
146
|
+ }
|
|
147
|
+```
|
|
148
|
+
|
|
149
|
+有关开关比例的视图模型:
|
|
150
|
+
|
|
151
|
+```
|
|
152
|
+public class GeneralKGBLItemViewModel : BaseViewModel
|
|
153
|
+ {
|
|
154
|
+ public string OpenNum { get; set; }
|
|
155
|
+ public string CloseNum { get; set; }
|
|
156
|
+ }
|
|
157
|
+```
|
|
158
|
+以上,包括接口所接受的模型和接口响应给前端的视图模型。
|
|
159
|
+
|
|
160
|
+输入和输出定下来了,现在需要通过中介者模式让处理请求,输出响应。如下:
|
|
161
|
+
|
|
162
|
+![](./imgs/mediator.png)
|
|
163
|
+
|
|
164
|
+> 处理请求和响应,中介者模式
|
|
165
|
+
|
|
166
|
+MediatR是一个有关中介者模式的第三方库,它的作者就是大名鼎鼎的AutopMapper的作者,而AutoMapper是用来建立DTO和Entity Framework模型之间映射的第三方库。
|
|
167
|
+
|
|
168
|
+HomeController.cs
|
|
169
|
+```
|
|
170
|
+ [Route("api/home")]
|
|
171
|
+ public class HomeController : BaseController
|
|
172
|
+ {
|
|
173
|
+
|
|
174
|
+ private readonly IHostingEnvironment _env;
|
|
175
|
+
|
|
176
|
+ public HomeController(IHostingEnvironment env)
|
|
177
|
+ {
|
|
178
|
+ _env = env;
|
|
179
|
+ }
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+ /// <summary>
|
|
183
|
+ /// 获取首页项目列表
|
|
184
|
+ /// </summary>
|
|
185
|
+ /// <returns></returns>
|
|
186
|
+ /// <response code="200">返回首页项目列表</response>
|
|
187
|
+ [Authorize]
|
|
188
|
+ [Route("projects")]
|
|
189
|
+ [HttpPost]
|
|
190
|
+ [ProducesResponseType(200)]
|
|
191
|
+ public async Task<IActionResult> GetProjects(BaseFlatQuery query)
|
|
192
|
+ {
|
|
193
|
+
|
|
194
|
+ var generalQuery = new GeneralSumQuery
|
|
195
|
+ {
|
|
196
|
+ Level = 0,
|
|
197
|
+ LocationId = string.Empty,
|
|
198
|
+ GroupId = User.FindFirst(GlobalSettings.Auth_GroupId).Value,
|
|
199
|
+ ConnKey = string.Empty,
|
|
200
|
+ ProjectId = string.Empty,
|
|
201
|
+ PagingModel = new SieveModel
|
|
202
|
+ {
|
|
203
|
+ Page=query.Page,
|
|
204
|
+ PageSize = query.PageSize
|
|
205
|
+ }
|
|
206
|
+ };
|
|
207
|
+
|
|
208
|
+ return Ok(await Mediator.Send(generalQuery));
|
|
209
|
+ }
|
|
210
|
+
|
|
211
|
+ /// <summary>
|
|
212
|
+ /// 获取楼层、区域等列表
|
|
213
|
+ /// </summary>
|
|
214
|
+ /// <returns></returns>
|
|
215
|
+ /// <response code="200">返回楼层、区域等列表</response>
|
|
216
|
+ [Authorize]
|
|
217
|
+ [Route("locations")]
|
|
218
|
+ [HttpPost]
|
|
219
|
+ [ProducesResponseType(200)]
|
|
220
|
+ public async Task<IActionResult> GetLocations(GeneralLocationQuery query)
|
|
221
|
+ {
|
|
222
|
+ var generalQuery = new GeneralSumQuery
|
|
223
|
+ {
|
|
224
|
+ Level = query.Level,
|
|
225
|
+ LocationId = query.LocationId,
|
|
226
|
+ GroupId=string.Empty,
|
|
227
|
+ ConnKey=query.ConnKey,
|
|
228
|
+ ProjectId = query.ProjectId,
|
|
229
|
+ PagingModel = new SieveModel
|
|
230
|
+ {
|
|
231
|
+ Page = query.Page,
|
|
232
|
+ PageSize = query.PageSize
|
|
233
|
+ }
|
|
234
|
+ };
|
|
235
|
+
|
|
236
|
+ return Ok(await Mediator.Send(generalQuery));
|
|
237
|
+ }
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+ /// <summary>
|
|
241
|
+ /// 首页测试数据
|
|
242
|
+ /// </summary>
|
|
243
|
+ /// <returns></returns>
|
|
244
|
+ /// <response code="200">返回首页测试数据</response>
|
|
245
|
+ [Authorize]
|
|
246
|
+ [HttpPost]
|
|
247
|
+ [Route("test")]
|
|
248
|
+ [ProducesResponseType(200)]
|
|
249
|
+ public IActionResult Test()
|
|
250
|
+ {
|
|
251
|
+ //return Ok(new { Id = 1, Name="这里是测试数据"});
|
|
252
|
+ return Ok(User.FindFirst(GlobalSettings.Auth_GroupId).Value);
|
|
253
|
+ }
|
|
254
|
+ }
|
|
255
|
+```
|
|
256
|
+
|
|
257
|
+为什么需要`BaseFlatQuery`这个类?为什么不能用`BaseQuery`这个类?因为`BaseQuery`有些属性不是前端输入的,比如GroupId, ConnKey,这些属性是根据用户的相关信息赋值的,前端的请求不需要这个。而且在`BaseQuery`中有关分页的属性SieveModel中的Page和PageSize是可空的,在空的情况Sieve这个分页库会区找全局设置中的相关设置,即到appsettings.json中去找。总之,`BaseFlatQuery`是用来方便前端输入的,尽量让前端输入最简,并且没有嵌套类。同时,使用`BaseFlatQuery`这个类更安全,因为外界无从知晓服务端类的属性结构。
|
|
258
|
+
|
|
259
|
+MediatR的工作原理:
|
|
260
|
+
|
|
261
|
+- 定义请求:
|
|
262
|
+
|
|
263
|
+```
|
|
264
|
+public class MyQuery : IRequest<MyViewMedel>
|
|
265
|
+{}
|
|
266
|
+```
|
|
267
|
+- 定义处理流程
|
|
268
|
+
|
|
269
|
+```
|
|
270
|
+public class MyQueryHandler : IRequestHandler<MyQuery,MyViewModel>
|
|
271
|
+{
|
|
272
|
+
|
|
273
|
+ public async Task<MyViewMedel> Handler(MyQuery request, CancellationToken cancellationToken)
|
|
274
|
+ {
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+ }
|
|
278
|
+}
|
|
279
|
+```
|
|
280
|
+
|
|
281
|
+MediatR的实现细节略去。
|