VoteController.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. using Azure.Cosmos;
  2. using Microsoft.AspNetCore.Http;
  3. using Microsoft.AspNetCore.Mvc;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.IdentityModel.Tokens.Jwt;
  7. using System.Linq;
  8. using System.Text;
  9. using System.Text.Json;
  10. using System.Threading.Tasks;
  11. using TEAMModelOS.Models.Dto;
  12. using TEAMModelOS.SDK.Models;
  13. using TEAMModelOS.SDK;
  14. using TEAMModelOS.SDK.Context.Constant.Common;
  15. using TEAMModelOS.SDK.DI;
  16. using TEAMModelOS.SDK.DI.AzureCosmos.Inner;
  17. using TEAMModelOS.SDK.Extension;
  18. using TEAMModelOS.SDK.Helper.Common.CollectionHelper;
  19. using TEAMModelOS.SDK.Helper.Common.StringHelper;
  20. using TEAMModelOS.Models;
  21. using Microsoft.Extensions.Options;
  22. using TEAMModelOS.SDK.Models.Cosmos;
  23. using Microsoft.AspNetCore.Authorization;
  24. using TEAMModelOS.Filter;
  25. using StackExchange.Redis;
  26. using TEAMModelOS.SDK.Models.Cosmos.Common.Inner;
  27. using TEAMModelOS.Services.Common;
  28. namespace TEAMModelOS.Controllers.Learn
  29. {
  30. /// <summary>
  31. /// 投票活动
  32. /// </summary>
  33. [ProducesResponseType(StatusCodes.Status200OK)]
  34. [ProducesResponseType(StatusCodes.Status400BadRequest)]
  35. //[Authorize(Roles = "IES5")]
  36. [Route("common/vote")]
  37. [ApiController]
  38. public class VoteController : ControllerBase
  39. {
  40. private readonly AzureRedisFactory _azureRedis;
  41. private readonly AzureCosmosFactory _azureCosmos;
  42. private readonly SnowflakeId _snowflakeId;
  43. private readonly AzureServiceBusFactory _serviceBus;
  44. private readonly DingDing _dingDing;
  45. private readonly Option _option;
  46. private readonly AzureStorageFactory _azureStorage;
  47. public VoteController(AzureCosmosFactory azureCosmos, AzureServiceBusFactory serviceBus, SnowflakeId snowflakeId, DingDing dingDing, IOptionsSnapshot<Option> option,
  48. AzureRedisFactory azureRedis, AzureStorageFactory azureStorage)
  49. {
  50. _azureCosmos = azureCosmos;
  51. _serviceBus = serviceBus;
  52. _snowflakeId = snowflakeId;
  53. _dingDing = dingDing;
  54. _option = option?.Value;
  55. _azureRedis = azureRedis;
  56. _azureStorage = azureStorage;
  57. }
  58. /// <summary>
  59. /// 新增 或 修改投票活动
  60. /// </summary>
  61. /// <param name="request"></param>
  62. /// <returns></returns>
  63. [ProducesDefaultResponseType]
  64. [HttpPost("upsert")]
  65. public async Task<IActionResult> Upsert(Vote request)
  66. {
  67. try
  68. {
  69. //新增Vote
  70. var client = _azureCosmos.GetCosmosClient();
  71. request.code = request.pk + "-" + request.code;
  72. long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  73. request.createTime = now;
  74. if (string.IsNullOrEmpty(request.id))
  75. {
  76. request.id = Guid.NewGuid().ToString();
  77. if (request.startTime > now)
  78. {
  79. request.progress = "pending";
  80. }
  81. else {
  82. request.progress = "going";
  83. }
  84. request = await client.GetContainer("TEAMModelOS", "Common").CreateItemAsync(request, new PartitionKey($"{request.code}"));
  85. }
  86. else
  87. {
  88. var response = await client.GetContainer("TEAMModelOS", "Common").ReadItemStreamAsync(request.id, new PartitionKey($"{request.code}"));
  89. if (response.Status == 200)
  90. {
  91. using var json = await JsonDocument.ParseAsync(response.ContentStream);
  92. var info = json.ToObject<Vote>();
  93. if (info.progress.Equals("going"))
  94. {
  95. return Ok(new { v = "活动正在进行中" });
  96. }
  97. if (request.startTime > now)
  98. {
  99. request.progress = "pending";
  100. }
  101. else
  102. {
  103. request.progress = "going";
  104. }
  105. request.progress = info.progress;
  106. request = await client.GetContainer("TEAMModelOS", "Common").ReplaceItemAsync(request, info.id, new PartitionKey($"{info.code}"));
  107. }
  108. else
  109. {
  110. if (request.startTime > now)
  111. {
  112. request.progress = "pending";
  113. }
  114. else
  115. {
  116. request.progress = "going";
  117. }
  118. request = await client.GetContainer("TEAMModelOS", "Common").CreateItemAsync(request, new PartitionKey($"{request.code}"));
  119. }
  120. }
  121. return Ok(new { vote = request });
  122. }
  123. catch (Exception e)
  124. {
  125. await _dingDing.SendBotMsg($"OS,{_option.Location},common/vote/save()\n{e.Message}", GroupNames.醍摩豆服務運維群組);
  126. return BadRequest(e.StackTrace);
  127. }
  128. }
  129. /// <summary>
  130. /// 查询投票活动,用于列表,编辑,查看
  131. /// </summary>
  132. /// <data>
  133. ///Vote-学校/教师编码 活动分区 !"code":"hbcn"/1606285227
  134. ///时间筛选范围开始时间 默认30天之前 ?"stime":1608274766154
  135. ///时间筛选范围结束时间 默认当前时间 ?"etime":1608274766666
  136. ///每页大小 ?"count":10/null/Undefined
  137. ///分页Token ?"continuationToken":Undefined/null/"[{\"token\":\"+RID:~omxMAP3ipcSEEwAAAAAAAA==#RT:2#TRC:20#ISV:2#IEO:65551#QCF:1#FPC:AYQTAAAAAAAAiRMAAAAAAAA=\",\"range\":{\"min\":\"\",\"max\":\"FF\"}}]"
  138. /// 当前状态 ?"progress":Undefined/null/"" 表示两种状态都要查询/ "going"/"finish" 表示查询进行中/ 或者已完成 学生端只能查询正在进行或已经结束 going 已发布|finish 已结束
  139. /// </data>
  140. /// <param name="request"></param>
  141. /// <returns></returns>
  142. [ProducesDefaultResponseType]
  143. [HttpPost("find")]
  144. public async Task<IActionResult> Find(JsonElement requert)
  145. {
  146. try
  147. {
  148. //必须有学校或者教师编码
  149. if (!requert.TryGetProperty("code", out JsonElement code)) return BadRequest();
  150. //开始时间,默认最近三十天
  151. var stimestamp = DateTimeOffset.UtcNow.AddDays(-30).ToUnixTimeMilliseconds();
  152. if (requert.TryGetProperty("stime", out JsonElement stime)) {
  153. if (!stime.ValueKind.Equals(JsonValueKind.Undefined)&&stime.TryGetInt64(out long data))
  154. {
  155. stimestamp = data;
  156. };
  157. };
  158. //默认当前时间
  159. var etimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  160. if (requert.TryGetProperty("etime", out JsonElement etime))
  161. {
  162. if (!etime.ValueKind.Equals(JsonValueKind.Undefined)&&etime.TryGetInt64(out long data))
  163. {
  164. etimestamp = data;
  165. };
  166. };
  167. var progresssql = "";
  168. if (!requert.TryGetProperty("progress", out JsonElement progress))
  169. {
  170. if (!progress.ValueKind.Equals(JsonValueKind.Undefined) && !progress.ValueKind.Equals(JsonValueKind.Null) && progress.ValueKind.Equals(JsonValueKind.String))
  171. {
  172. progresssql = $" and c.progress='{progresssql}' ";
  173. }
  174. }
  175. string continuationToken = null;
  176. //默认不指定返回大小
  177. int? topcout=null;
  178. if (requert.TryGetProperty("count", out JsonElement jcount)) {
  179. if(!jcount.ValueKind.Equals(JsonValueKind.Undefined) && jcount.TryGetInt32(out int data))
  180. {
  181. topcout = data;
  182. }
  183. };
  184. //是否需要进行分页查询,默认不分页
  185. bool iscontinuation = false;
  186. //如果指定了返回大小
  187. if (requert.TryGetProperty("continuationToken", out JsonElement continuation))
  188. {
  189. //指定了cancellationToken 表示需要进行分页
  190. if (!continuation.ValueKind.Equals(JsonValueKind.Null) && !continuation.ValueKind.Equals(JsonValueKind.Undefined))
  191. {
  192. continuationToken = continuation.GetString();
  193. iscontinuation = true;
  194. }
  195. };
  196. List<object> votes = new List<object>();
  197. var client = _azureCosmos.GetCosmosClient();
  198. var query =$"select c.id,c.name,c.code,c.startTime,c.endTime,c.progress from c where c.createTime >= {stimestamp} and c.createTime <= {etimestamp} {progresssql } ";
  199. await foreach (var item in client.GetContainer("TEAMModelOS", "Common").GetItemQueryStreamIterator(queryText: query,
  200. requestOptions: new QueryRequestOptions() {MaxItemCount = topcout, PartitionKey = new PartitionKey($"Vote-{code}") }))
  201. {
  202. using var json = await JsonDocument.ParseAsync(item.ContentStream);
  203. if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
  204. {
  205. foreach (var obj in json.RootElement.GetProperty("Documents").EnumerateArray())
  206. {
  207. votes.Add(obj.ToObject<JsonElement>());
  208. }
  209. //如果需要分页则跳出
  210. if (iscontinuation) {
  211. continuationToken = item.GetContinuationToken();
  212. break;
  213. }
  214. }
  215. }
  216. return Ok(new { votes, continuationToken });
  217. }
  218. catch (Exception ex)
  219. {
  220. await _dingDing.SendBotMsg($"OS,{_option.Location},common/vote/find()\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
  221. return BadRequest(ex.StackTrace);
  222. }
  223. }
  224. ///<summary>
  225. /// 查询投票活动,用于创建者列表,编辑,查看,投票人员查看
  226. /// </summary>
  227. /// <data>
  228. /// ! "id":"3c075347-75ef-4bcb-ae03-68678d02d5ef",
  229. /// ! "code":"Vote-hbcn"/"code":"Vote-1606285227"
  230. /// </data>
  231. /// <param name="request"></param>
  232. /// <returns></returns>
  233. [ProducesDefaultResponseType]
  234. [HttpPost("find-id")]
  235. public async Task<IActionResult> FindById(JsonElement requert)
  236. {
  237. try
  238. {
  239. var client = _azureCosmos.GetCosmosClient();
  240. //活动id
  241. if (!requert.TryGetProperty("id", out JsonElement id)) return BadRequest();
  242. //活动分区
  243. if (!requert.TryGetProperty("code", out JsonElement code)) return BadRequest();
  244. Vote vote = await client.GetContainer("TEAMModelOS", "Common").ReadItemAsync<Vote>(id.GetString(), new PartitionKey($"{code}"));
  245. if (vote != null)
  246. {
  247. return Ok(new { vote });
  248. }
  249. else
  250. {
  251. return BadRequest("id,code不存在!");
  252. }
  253. }
  254. catch (Exception ex)
  255. {
  256. await _dingDing.SendBotMsg($"OS,{_option.Location},common/vote/find-id()\n{ex.Message}", GroupNames.醍摩豆服務運維群組);
  257. return BadRequest(ex.StackTrace);
  258. }
  259. }
  260. /// <summary>
  261. /// 删除投票活动 TODO 使用ttl删除,并处理相关事务逻辑
  262. /// </summary>
  263. /// <param name="request"></param>
  264. /// <returns></returns>
  265. [ProducesDefaultResponseType]
  266. [HttpPost("delete")]
  267. [AuthToken(Roles = "admin,teacher")]
  268. public async Task<IActionResult> Delete(JsonElement request)
  269. {
  270. try
  271. {
  272. var (userid, _, _,school) = HttpContext.GetAuthTokenInfo();
  273. if (!request.TryGetProperty("id", out JsonElement id)) return BadRequest();
  274. if (!request.TryGetProperty("code", out JsonElement code)) return BadRequest();
  275. var client = _azureCosmos.GetCosmosClient();
  276. Vote vote =await client.GetContainer("TEAMModelOS", "Common").ReadItemAsync<Vote>(id.GetString(), new PartitionKey($"{code}") );
  277. bool flag = false;
  278. //必须是本人或者这个学校的管理者才能删除
  279. if (vote.creatorId == userid)
  280. {
  281. flag = true;
  282. }
  283. else {
  284. if (vote.scope == "school"&& vote.school.Equals(school)) {
  285. flag = true;
  286. }
  287. }
  288. if (flag)
  289. {
  290. //使用ttl删除,并处理相关事务逻辑
  291. vote.ttl = 1;
  292. vote.status = 404;
  293. vote = await client.GetContainer("TEAMModelOS", "Common").UpsertItemAsync(vote, new PartitionKey($"{vote.code}"));
  294. var adid = vote.id;
  295. var adcode = $"Activity-{vote.school}";
  296. ActivityData data = null;
  297. try
  298. {
  299. if (vote.scope == "school")
  300. {
  301. data = await client.GetContainer("TEAMModelOS", "School").ReadItemAsync<ActivityData>(adid, new Azure.Cosmos.PartitionKey($"{adcode}"));
  302. }
  303. else if (vote.scope == "private")
  304. {
  305. data = await client.GetContainer("TEAMModelOS", "Teacher").ReadItemAsync<ActivityData>(adid, new Azure.Cosmos.PartitionKey($"{adcode}"));
  306. }
  307. }
  308. catch
  309. {
  310. data = null;
  311. }
  312. await _dingDing.SendBotMsg($"投票活动【{vote.name}-{vote.id}-ttl={vote.ttl}】正在操作", GroupNames.成都开发測試群組);
  313. _azureRedis.GetRedisClient(8).KeyDelete($"Vote:Record:{vote.id}");
  314. _azureRedis.GetRedisClient(8).KeyDelete($"Vote:Count:{vote.id}");
  315. if (data != null)
  316. {
  317. data.ttl = 1;
  318. if (vote.scope == "school")
  319. {
  320. data = await client.GetContainer("TEAMModelOS", "School").DeleteItemAsync<ActivityData>(adid, new Azure.Cosmos.PartitionKey($"{data.code}"));
  321. }
  322. else if (vote.scope == "private")
  323. {
  324. data = await client.GetContainer("TEAMModelOS", "Teacher").DeleteItemAsync<ActivityData>(adid, new Azure.Cosmos.PartitionKey($"{data.code}"));
  325. }
  326. }
  327. await _dingDing.SendBotMsg($"投票活动【{vote.name}-{vote.id}】被删除", GroupNames.成都开发測試群組);
  328. return Ok(new { flag });
  329. }
  330. else {
  331. return Ok(new { flag });
  332. }
  333. }
  334. catch (Exception e)
  335. {
  336. return BadRequest(e.StackTrace);
  337. }
  338. }
  339. /// <summary>
  340. /// 投票记录 当活动没结算且没有BlobUrl时则调用此接口
  341. /// </summary>
  342. /// <redis>
  343. /// 投票活动选项计数器 使用SortedSet(有序集合)ZSET 数据集合 使用命令 ZINCRBY key:"Vote:Count:AAA",value:"A",score:1
  344. /// 投票活动 投票记录 使用Hash(哈希表)使用命令 key:"Vote:Record:AAA",feild:15283771540-20210105,value:"{"opt":["A","C","A"],"time":1608274766154}"
  345. /// </redis>
  346. /// <param name="request">
  347. /// !"id":"aaaa"
  348. /// !"code":"Vote-hbcn"/"code":"Vote-1606285227"
  349. /// </param>
  350. /// <returns>
  351. /// </returns>
  352. [ProducesDefaultResponseType]
  353. [HttpPost("record")]
  354. //[AuthToken(Roles = "teacher,student")]
  355. public async Task<IActionResult> Record(JsonElement request)
  356. {
  357. if (!request.TryGetProperty("id", out JsonElement id))
  358. {
  359. return BadRequest();
  360. }
  361. //活动分区
  362. if (!request.TryGetProperty("code", out JsonElement code))
  363. {
  364. return BadRequest();
  365. }
  366. //获取投票活动的所有投票记录
  367. var records = await _azureRedis.GetRedisClient(8).HashGetAllAsync($"Vote:Record:{id}");
  368. //获取投票活动的选项及投票数
  369. var counts = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores($"Vote:Count:{id}");
  370. List<dynamic> options = new List<dynamic>();
  371. if (counts != null && counts.Length > 0)
  372. {
  373. foreach (var count in counts)
  374. {
  375. options.Add(new { code = count.Element.ToString(), count = (int)count.Score });
  376. }
  377. }
  378. List<JsonElement> res = new List<JsonElement>();
  379. foreach (var rcd in records)
  380. {
  381. var value = rcd.Value.ToString().ToObject<JsonElement>();
  382. res.Add(value);
  383. }
  384. return Ok(new { options, records= res});
  385. }
  386. /// <summary>
  387. /// 个人已投票记录查询
  388. /// </summary>
  389. /// <param name="request"></param>
  390. /// <returns></returns>
  391. [ProducesDefaultResponseType]
  392. [HttpPost("decided")]
  393. [AuthToken(Roles = "teacher,student")]
  394. public async Task<IActionResult> Decided(JsonElement request)
  395. {
  396. var (userid, _, _, _) = HttpContext.GetAuthTokenInfo();
  397. if (!request.TryGetProperty("id", out JsonElement id))
  398. {
  399. return BadRequest();
  400. }
  401. //活动分区
  402. if (!request.TryGetProperty("code", out JsonElement code))
  403. {
  404. return BadRequest();
  405. }
  406. //获取投票活动的所有投票记录
  407. var records = await _azureRedis.GetRedisClient(8).HashGetAllAsync($"Vote:Record:{id}:{userid}");
  408. List<JsonElement> res = new List<JsonElement>();
  409. foreach (var rcd in records)
  410. {
  411. var value = rcd.Value.ToString().ToObject<JsonElement>();
  412. res.Add(value);
  413. }
  414. return Ok(new { records= res });
  415. }
  416. /// <summary>
  417. /// 投票
  418. /// </summary>
  419. /// <redis>
  420. /// 投票活动选项计数器 使用SortedSet(有序集合)ZSET 数据集合 使用命令 ZINCRBY key:"Vote:Count:AAA",value:"A",score:1
  421. /// 投票活动 投票记录 使用Hash(哈希表)使用命令 key:"Vote:Record:AAA",feild:15283771540-20210105,value:"{"opt":["A","C","A"],"time":1608274766154}"
  422. /// </redis>
  423. /// <param name="request">
  424. /// !"id":"aaaa"
  425. /// !"code":"Vote-hbcn"/"code":"Vote-1606285227"
  426. /// !"option":{"A":5,"B":6}/{"1":5,"2":8}
  427. /// </param>
  428. /// <returns>
  429. /// msgid=0投票失败,1投票成功,2不在时间范围内,3不在发布范围内,4投票周期内重复投票,5周期内的可投票数不足
  430. ///
  431. /// </returns>
  432. [ProducesDefaultResponseType]
  433. [HttpPost("decide")]
  434. [AuthToken(Roles = "teacher,student")]
  435. public async Task<IActionResult> Decide(JsonElement request)
  436. {
  437. var (userid, _, _, _) = HttpContext.GetAuthTokenInfo();
  438. int msgid = await ActivityStudentService.Decide(request, _azureCosmos, _azureRedis, userid);
  439. return Ok(new { msgid });
  440. }
  441. /// <summary>
  442. /// 投票
  443. /// </summary>
  444. /// <redis>
  445. /// 投票活动选项计数器 使用SortedSet(有序集合)ZSET 数据集合 使用命令 ZINCRBY key:"Vote:Count:AAA",value:"A",score:1
  446. /// 投票活动 投票记录 使用Hash(哈希表)使用命令 key:"Vote:Record:AAA",feild:15283771540-20210105,value:"{"opt":["A","C","A"],"time":1608274766154}"
  447. /// </redis>
  448. /// <param name="request">
  449. /// !"id":"aaaa"
  450. /// !"code":"Vote-hbcn"/"code":"Vote-1606285227"
  451. /// !"option":{"A":5,"B":6}/{"1":5,"2":8}
  452. /// !"userid":"15283771540"
  453. /// </param>
  454. /// <returns>
  455. /// msgid=0投票失败,1投票成功,2不在时间范围内,3不在发布范围内,4投票周期内重复投票,5周期内的可投票数不足,6未设置投票项
  456. /// </returns>
  457. [ProducesDefaultResponseType]
  458. [HttpPost("decide-mock")]
  459. public async Task<IActionResult> DecideMock(JsonElement request)
  460. {
  461. //var (userid, _, _, _) = HttpContext.GetAuthTokenInfo();
  462. //活动id
  463. if (request.TryGetProperty("userid", out JsonElement userid))
  464. {
  465. int msgid = await ActivityStudentService.Decide(request, _azureCosmos, _azureRedis, $"{userid}");
  466. return Ok(new { msgid });
  467. }
  468. else { return Ok(new { msgid = 0 }); }
  469. }
  470. }
  471. }