OnLineController.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. using Azure.Cosmos;
  2. using Microsoft.AspNetCore.Http;
  3. using Microsoft.AspNetCore.Mvc;
  4. using StackExchange.Redis;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Linq;
  8. using System.Text.Json;
  9. using System.Threading.Tasks;
  10. using TEAMModelBI.Tool;
  11. using TEAMModelOS.SDK.DI;
  12. using TEAMModelOS.SDK.Extension;
  13. using TEAMModelOS.SDK.Models;
  14. using TEAMModelOS.SDK.Models.Table;
  15. namespace TEAMModelBI.Controllers.BIHome
  16. {
  17. [Route("online")]
  18. [ApiController]
  19. public class OnLineController : ControllerBase
  20. {
  21. private readonly AzureCosmosFactory _azureCosmos;
  22. private readonly AzureStorageFactory _azureStorage;
  23. private readonly AzureRedisFactory _azureRedis;
  24. public OnLineController(AzureCosmosFactory azureCosmos, AzureStorageFactory azureStorage, AzureRedisFactory azureRedis)
  25. {
  26. _azureCosmos = azureCosmos;
  27. _azureStorage = azureStorage;
  28. _azureRedis = azureRedis;
  29. }
  30. /// <summary>
  31. /// 总数统计
  32. /// </summary>
  33. /// <returns></returns>
  34. [HttpPost("get-count")]
  35. public async Task<IActionResult> GetCount()
  36. {
  37. var cosmosClient = _azureCosmos.GetCosmosClient();
  38. var table = _azureStorage.GetCloudTableClient().GetTableReference("IESLogin");
  39. DateTimeOffset dateTime = DateTimeOffset.UtcNow;
  40. var (daySt, dayEt) = TimeHelper.GetStartOrEnd(dateTime); //今天开始时间 13位
  41. var (daySf, dayEf) = TimeHelper.GetStartOrEnd(dateTime, dateLenth: false); //今天开始时间 10位
  42. var (lastDayS, lastdayE) = TimeHelper.GetStartOrEnd(DateTimeOffset.Parse($"{dateTime.Year}-{dateTime.Month}-{dateTime.Day - 1}")); //昨天开始时间
  43. var near7S = dateTime.AddDays(-7).ToUnixTimeMilliseconds(); //前七天的开始时间
  44. var near7E = dateTime.ToUnixTimeMilliseconds(); //当前结束时间
  45. long hour1 = dateTime.AddHours(-1).ToUnixTimeMilliseconds(); //一小时前时间戳
  46. int areaCnt = 0; //学区总数
  47. int scCnt = 0; //学校总数
  48. int tchCnt = 0; //教师总数
  49. int stuCnt = 0; //学生总数
  50. int onStuCnt = 0; //学生在线人数
  51. int onTchCnt = 0; //教师在线人数
  52. int todayScCnt = 0; //今日新增学校数
  53. int todayTchCnt = 0; //今日新增教师
  54. int todayStuCnt = 0; //今日新增学生数
  55. string currentSql = "select value(count(c.id)) from c";
  56. areaCnt = await CommonFind.GetSqlValueCount(cosmosClient, "Normal", currentSql, "Base-Area");
  57. scCnt = await CommonFind.GetSqlValueCount(cosmosClient, "School", currentSql, "Base");
  58. tchCnt = await CommonFind.GetSqlValueCount(cosmosClient, "Teacher", currentSql, "Base");
  59. stuCnt = await CommonFind.GetSqlValueCount(cosmosClient, "Student", "select value(count(c.id)) from c where c.pk='Base'");
  60. string addSql = $"select value(count(c.id)) from c where c.pk='Base' and c.createTime >={daySf} and c.createTime <= {dayEf}";
  61. todayScCnt = await CommonFind.GetSqlValueCount(cosmosClient, "School", addSql, "Base");
  62. todayTchCnt = await CommonFind.GetSqlValueCount(cosmosClient, "Teacher", addSql, "Base");
  63. todayStuCnt = await CommonFind.GetSqlValueCount(cosmosClient, "Student", addSql, "Base");
  64. string onStuSql = $"select value(count(c.id)) from c join t in c.loginInfos where array_length(c.loginInfos) > 0 and t.expire > {hour1}";
  65. onTchCnt = await CommonFind.GetSqlValueCount(cosmosClient, "Teacher", onStuSql, "Base");
  66. onStuCnt = await CommonFind.GetSqlValueCount(cosmosClient, "Student", onStuSql, "Base");
  67. //List<RecOnLine> recStuOnLines = new();
  68. //await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "Student").GetItemQueryIterator<RecOnLine>(queryText: "select c.id,c.name,c.code,c.loginInfos from c where c.pk='Base' and array_length(c.loginInfos) > 0 ", requestOptions:new QueryRequestOptions() { }))
  69. //{
  70. // recStuOnLines.Add(item);
  71. //}
  72. //List<RecOnLine> recTecOnLines = new();
  73. //await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "Teacher").GetItemQueryIterator<RecOnLine>(queryText: "select c.id,c.name,c.code,c.loginInfos from c where array_length(c.loginInfos) > 0 ", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Base") }))
  74. //{
  75. // recTecOnLines.Add(item);
  76. //}
  77. ////onStuCnt = (from rs in recStuOnLines from l in rs.loginInfos where l.expire >= hour1 select rs).ToList().Count(); //linq查询 学生在线人数
  78. //onStuCnt = recStuOnLines.Select(rss => new RecOnLine { id = rss.id,code=rss.code, name =rss.name,loginInfos = new List<Teacher.LoginInfo> { rss.loginInfos.Find(f => f.expire >= hour1) } }).Where(w => w.loginInfos.FirstOrDefault() != null).ToList().Count(); //lambda 表达式查询 教师查询人数
  79. ////onTchCnt = (from rs in recTecOnLines from l in rs.loginInfos where l.expire >= hour1 select rs).ToList().Count(); //linq查询 教师查询人数
  80. //onTchCnt = recTecOnLines.Select(rss => new RecOnLine { id = rss.id,code=rss.code, name =rss.name,loginInfos = new List<Teacher.LoginInfo> { rss.loginInfos.Find(f => f.expire >= hour1) } }).Where(w => w.loginInfos.FirstOrDefault() != null).ToList().Count(); //lambda 表达式查询 教师查询人数
  81. return Ok(new { state = 200, areaCnt, scCnt, tchCnt, stuCnt, todayScCnt, todayTchCnt, todayStuCnt, onStuCnt, onTchCnt});
  82. }
  83. /// <summary>
  84. /// 在线人数趋势图
  85. /// </summary>
  86. /// <returns></returns>
  87. [HttpPost("get-trend")]
  88. public async Task<IActionResult> GetTrend()
  89. {
  90. var table = _azureStorage.GetCloudTableClient().GetTableReference("IESLogin");
  91. DateTimeOffset dateTime = DateTimeOffset.UtcNow;
  92. var (daySt, dayEt) = TimeHelper.GetStartOrEnd(dateTime); //今天开始时间 13位
  93. var (strDaySt, strDayEt) = TimeHelper.GetUnixToDate(daySt, dayEt, "yyyyMMddHH");
  94. var dateDay = dateTime.ToString("yyyyMMdd"); //获取当天的日期
  95. daySt.ToString("yyyyMMddHH");
  96. Dictionary<int, int> allDays = new(); //所有在线人数
  97. Dictionary<int, int> tchDays = new(); //教师在线人数
  98. Dictionary<int, int> stuDays = new(); //学生在线人数
  99. Dictionary<int, int> tmdDays = new(); //醍摩豆账户学生
  100. SortedSetEntry[] tchDay = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores($"Login:IES:teacher:{dateDay}");
  101. if (tchDay.Length > 0)
  102. {
  103. foreach (var item in tchDay)
  104. {
  105. int val = ((int)item.Score);
  106. int key = ((int)item.Element);
  107. var hour = int.Parse(DateTime.SpecifyKind(Convert.ToDateTime($"{dateTime.Year}/{dateTime.Month}/{ dateTime.Day} {key}:00:00"), DateTimeKind.Utc).ToLocalTime().ToString("HH"));
  108. tchDays.Add(hour, val);
  109. if (allDays.ContainsKey(hour))
  110. allDays[hour] = (allDays[hour] + val);
  111. else
  112. allDays.Add(hour, val);
  113. }
  114. }
  115. else
  116. {
  117. string tableSqlTch = $"PartitionKey eq 'HourLogin' and RowKey ge '{strDaySt}' and RowKey le '{strDayEt}'";
  118. List<HourLogin> hourLoginsTch = await table.QueryWhereString<HourLogin>(tableSqlTch);
  119. if (hourLoginsTch.Count > 0)
  120. {
  121. foreach (var item in hourLoginsTch)
  122. {
  123. await _azureRedis.GetRedisClient(8).SortedSetIncrementAsync($"Login:IES:teacher:{dateDay}", $"{item.Hour}", item.Teacher);//存一天24小时
  124. var hour = int.Parse(DateTime.SpecifyKind(Convert.ToDateTime($"{dateTime.Year}/{dateTime.Month}/{ dateTime.Day} {item.Hour}:00:00"), DateTimeKind.Utc).ToLocalTime().ToString("HH"));
  125. tchDays.Add(hour, item.Teacher);
  126. if (allDays.ContainsKey(hour))
  127. allDays[hour] = (allDays[hour] + item.Teacher);
  128. else
  129. allDays.Add(hour, item.Teacher);
  130. }
  131. }
  132. }
  133. SortedSetEntry[] stuDay = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores($"Login:IES:student:{dateDay}");
  134. if (stuDay.Length > 0)
  135. {
  136. foreach (var item in stuDay)
  137. {
  138. int val = (int)item.Score;
  139. int key = (int)item.Element;
  140. var hour = int.Parse(DateTime.SpecifyKind(Convert.ToDateTime($"{dateTime.Year}/{dateTime.Month}/{ dateTime.Day} {key}:00:00"), DateTimeKind.Utc).ToLocalTime().ToString("HH"));
  141. stuDays.Add(hour, val);
  142. if (allDays.ContainsKey(hour))
  143. allDays[hour] = (allDays[hour] + val);
  144. else
  145. allDays.Add(hour, val);
  146. }
  147. }
  148. else
  149. {
  150. string tableSqlStu = $"PartitionKey eq 'HourLogin' and RowKey ge '{strDaySt}' and RowKey le '{strDayEt}'";
  151. List<HourLogin> hourLoginsStu = await table.QueryWhereString<HourLogin>(tableSqlStu);
  152. //var hourStuCnt = hourLoginsStu.GroupBy(x => x.Hour).Select(k => new { key = int.Parse(k.Key.ToString().Substring(8, 2)), value = k.Count() }).ToList();
  153. if (hourLoginsStu.Count > 0)
  154. {
  155. foreach (var item in hourLoginsStu)
  156. {
  157. await _azureRedis.GetRedisClient(8).SortedSetIncrementAsync($"Login:IES:student:{dateDay}", $"{item.Hour}", item.Student);//存一天24小时
  158. var hour = int.Parse(DateTime.SpecifyKind(Convert.ToDateTime($"{dateTime.Year}/{dateTime.Month}/{ dateTime.Day} {item.Hour}:00:00"), DateTimeKind.Utc).ToLocalTime().ToString("HH"));
  159. stuDays.Add(hour, item.Student);
  160. if (allDays.ContainsKey(hour))
  161. allDays[hour] = (allDays[hour] + item.Student);
  162. else
  163. allDays.Add(hour, item.Student);
  164. }
  165. }
  166. }
  167. SortedSetEntry[] tmdDay = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores($"Login:IES:tmduser:{dateDay}");
  168. if (tmdDay.Length > 0)
  169. {
  170. foreach (var item in stuDay)
  171. {
  172. int val = (int)item.Score;
  173. int key = (int)item.Element;
  174. var hour = int.Parse(DateTime.SpecifyKind(Convert.ToDateTime($"{dateTime.Year}/{dateTime.Month}/{ dateTime.Day} {key}:00:00"), DateTimeKind.Utc).ToLocalTime().ToString("HH"));
  175. tmdDays.Add(hour, val);
  176. if (allDays.ContainsKey(hour))
  177. allDays[hour] = (allDays[hour] + val);
  178. else
  179. allDays.Add(hour, val);
  180. }
  181. }
  182. else
  183. {
  184. string tableSqlTmd = $"PartitionKey eq 'HourLogin' and RowKey ge '{strDaySt}' and RowKey le '{strDayEt}'";
  185. List<HourLogin> hourLoginsTmd = await table.QueryWhereString<HourLogin>(tableSqlTmd);
  186. //var hourTmdCnt = hourLoginsTmd.GroupBy(x => x.Hour).Select(k => new { key = int.Parse(k.Key.ToString().Substring(8, 2)), value = k.Count() }).ToList();
  187. if (hourLoginsTmd.Count > 0)
  188. {
  189. foreach (var item in hourLoginsTmd)
  190. {
  191. await _azureRedis.GetRedisClient(8).SortedSetIncrementAsync($"Login:IES:tmduser:{dateDay}", $"{item.Hour}", item.TmdUser);//存一天24小时
  192. var hour = int.Parse(DateTime.SpecifyKind(Convert.ToDateTime($"{dateTime.Year}/{dateTime.Month}/{ dateTime.Day} {item.Hour}:00:00"), DateTimeKind.Utc).ToLocalTime().ToString("HH"));
  193. tmdDays.Add(hour, item.TmdUser);
  194. if (allDays.ContainsKey(hour))
  195. allDays[hour] = (allDays[hour] + item.TmdUser);
  196. else
  197. allDays.Add(hour, item.TmdUser);
  198. }
  199. }
  200. }
  201. return Ok(new { state = 200,allDays = allDays.OrderBy(o=>o.Key).ToList(), tchDays=tchDays.OrderBy(o => o.Key).ToList(), stuDays= stuDays.OrderBy(o => o.Key).ToList(), tmdDays= tmdDays.OrderBy(o => o.Key).ToList() });
  202. }
  203. /// <summary>
  204. /// 课例趋势图
  205. /// </summary>
  206. /// <returns></returns>
  207. [HttpPost("get-lessontrend")]
  208. public async Task<IActionResult> GetLessonTrend()
  209. {
  210. DateTimeOffset dateTime = DateTimeOffset.UtcNow;
  211. var cosmosClient = _azureCosmos.GetCosmosClient();
  212. int year = dateTime.Year; //当前年
  213. int month = dateTime.Month; //当前月
  214. int day = dateTime.Day; //当天
  215. int hour = int.Parse(DateTime.SpecifyKind(Convert.ToDateTime($"{dateTime.Year}/{dateTime.Month}/{ dateTime.Day} {dateTime.Hour}:00:00"), DateTimeKind.Utc).ToLocalTime().ToString("HH")); //当前小时
  216. Dictionary<int, int> scLessCnt = new(); //学校课例
  217. Dictionary<int, int> tchLessCnt = new(); //教师课例
  218. Dictionary<int, int> yesterdayCnt = new(); //昨天24小时课例
  219. var (daySt, dayEt) = TimeHelper.GetStartOrEnd(dateTime); //今天开始时间 13位
  220. var (lastDayS, lastdayE) = TimeHelper.GetStartOrEnd(DateTimeOffset.Parse($"{dateTime.Year}-{dateTime.Month}-{dateTime.Day - 1}")); //昨天开始时间
  221. List<RecLesn> scRecLesn = new(); //学校课例
  222. List<RecLesn> tchRecLesn = new(); //个人课例
  223. List<RecLesn> allRecLesn = new(); //昨天所有课例
  224. string lesnSql = $"select c.id,c.name,c.code,c.school,c.scope,c.startTime from c where c.pk='LessonRecord' and c.startTime >={daySt} and c.startTime <= {dayEt}";
  225. await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<RecLesn>(queryText: lesnSql, requestOptions: new QueryRequestOptions() { }))
  226. {
  227. scRecLesn.Add(item);
  228. }
  229. await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "Teacher").GetItemQueryIterator<RecLesn>(queryText: lesnSql, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("LessonRecord") }))
  230. {
  231. tchRecLesn.Add(item);
  232. }
  233. string allLesnSql = $"select c.id,c.name,c.code,c.school,c.scope,c.startTime from c where c.pk='LessonRecord' and c.startTime >={lastDayS} and c.startTime <= {lastdayE}";
  234. await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<RecLesn>(queryText: allLesnSql, requestOptions: new QueryRequestOptions() { }))
  235. {
  236. allRecLesn.Add(item);
  237. }
  238. await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "Teacher").GetItemQueryIterator<RecLesn>(queryText: allLesnSql, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("LessonRecord") }))
  239. {
  240. allRecLesn.Add(item);
  241. }
  242. for (int i = 0; i < 24; i++)
  243. {
  244. if (hour >= i)
  245. {
  246. DateTimeOffset timeHour = new DateTime(year, month, day, i, 0, 0);
  247. var (hourS, hourE) = TimeHelper.GetStartOrEnd(timeHour, type: "hour");
  248. var scLesn = scRecLesn.Where(item => item.startTime >= hourS && item.startTime <= hourE).ToList();
  249. scLessCnt.Add(i, scLesn.Count());
  250. var tchLesn = tchRecLesn.Where(item => item.startTime >= hourS && item.startTime <= hourE).ToList();
  251. tchLessCnt.Add(i, tchLesn.Count());
  252. }
  253. DateTimeOffset yesterday = new DateTime(year, month, day - 1, i, 0, 0);
  254. var (yHourS, yHourE) = TimeHelper.GetStartOrEnd(yesterday, type: "hour");
  255. var allLesn = allRecLesn.Where(item => item.startTime >= yHourS && item.startTime <= yHourE).ToList();
  256. yesterdayCnt.Add(i, allLesn.Count());
  257. }
  258. ////通过循环实时查询课例统计
  259. //for (int i = 0; i < 24; i++)
  260. //{
  261. // if (hour >= i)
  262. // {
  263. // DateTimeOffset timeHour = new DateTime(year, month, day, i, 0, 0);
  264. // var (hourS, hourE) = TimeHelper.GetStartOrEnd(timeHour, type: "hour");
  265. // await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<int>(queryText: $"select value(count(c.id)) from c where c.pk='LessonRecord' and c.startTime >={hourS} and c.startTime <= {hourE}", requestOptions: new QueryRequestOptions() { }))
  266. // {
  267. // scLessCnt.Add(i, item);
  268. // }
  269. // await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "Teacher").GetItemQueryIterator<int>(queryText: $"select value(count(c.id)) from c where c.startTime >={hourS} and c.startTime <= {hourE}", requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("LessonRecord") }))
  270. // {
  271. // tchLessCnt.Add(i, item);
  272. // }
  273. // }
  274. // DateTimeOffset yesterday = new DateTime(year, month, day - 1, i, 0, 0);
  275. // var (yHourS, yHourE) = TimeHelper.GetStartOrEnd(yesterday, type: "hour");
  276. // string sql = $"select value(count(c.id)) from c where c.pk='LessonRecord' and c.startTime >= {yHourS} and c.startTime <= {yHourE}";
  277. // int hourLessCnt = await CommonFind.GetSqlValueCount(cosmosClient, new List<string> { "School", "Teacher" }, sql);
  278. // yesterdayCnt.Add(i, hourLessCnt);
  279. //}
  280. return Ok(new { state = 200, scLessCnt = scLessCnt.ToList(), tchLessCnt = tchLessCnt.ToList(), yesterdayCnt = yesterdayCnt.ToList() });
  281. }
  282. /// <summary>
  283. /// 版本数量占比
  284. /// </summary>
  285. /// <returns></returns>
  286. [HttpPost("get-edition")]
  287. public async Task<IActionResult> GetEdition()
  288. {
  289. var cosmosClient = _azureCosmos.GetCosmosClient();
  290. int beCnt = 0; //基础班
  291. int seCnt = 0; //标准版
  292. int peCnt = 0; //专业版
  293. List<RecScEd> scEdCnt = new();
  294. var ScSql = $"select c.id,c.name,c.size,c.scale from c";
  295. await foreach (var item in cosmosClient.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<RecScEd>(queryText: ScSql, requestOptions:new QueryRequestOptions() { PartitionKey= new PartitionKey("Base")}))
  296. {
  297. scEdCnt.Add(item);
  298. }
  299. scEdCnt.ForEach(async scProCnt =>
  300. {
  301. var response = await cosmosClient.GetContainer("TEAMModelOS", "School").ReadItemStreamAsync(scProCnt.id, new PartitionKey("ProductSum"));
  302. if (response.Status == 200)
  303. {
  304. using var json = await JsonDocument.ParseAsync(response.ContentStream);
  305. SchoolProductSum ScProductSum = json.ToObject<SchoolProductSum>();
  306. //scProCnt.serial = ScProductSum.serial.Count();
  307. //scProCnt.service = ScProductSum.service.Count();
  308. //scProCnt.hard = ScProductSum.hard.Count();
  309. int pSeriCnt = ScProductSum.serial.Count();
  310. int pServCnt = ScProductSum.service.Count();
  311. int pHardCnt = ScProductSum.hard.Count();
  312. scProCnt.serial = pSeriCnt;
  313. scProCnt.service = pServCnt;
  314. scProCnt.hard = pHardCnt;
  315. if (scProCnt.scale >= 500 && (pSeriCnt > 0 || pServCnt > 0 || pHardCnt > 0)) peCnt += 1;
  316. }
  317. if (scProCnt.scale >= 500 && scProCnt.serial == 0 && scProCnt.service == 0 && scProCnt.hard == 0) seCnt += 1;
  318. if (scProCnt.scale == 0) beCnt += 1;
  319. });
  320. return Ok(new { state = 200, beCnt, seCnt, peCnt, scEdCnt });
  321. }
  322. /// <summary>
  323. /// 记录在线人数
  324. /// </summary>
  325. public record RecOnLine
  326. {
  327. public string id { get; set; }
  328. public string name { get; set; }
  329. public string code { get; set; }
  330. public List<Teacher.LoginInfo> loginInfos { get; set; }
  331. }
  332. /// <summary>
  333. /// 记录学校版本信息
  334. /// </summary>
  335. public record RecScEd
  336. {
  337. public string id { get; set; }
  338. public string name { get; set; }
  339. public int size { get; set; }
  340. public int scale { get; set; }
  341. public int serial { get; set; } = 0;//软体
  342. public int service { get; set; } = 0; //服务
  343. public int hard { get; set; } = 0; //硬体
  344. }
  345. /// <summary>
  346. /// 记录课例
  347. /// </summary>
  348. public record RecLesn
  349. {
  350. public string id { get; set; }
  351. public string name { get; set; }
  352. public string code { get; set; }
  353. public string school { get; set; }
  354. public string scope{get;set;}
  355. public long startTime { get; set; }
  356. }
  357. }
  358. }