ShareController.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. using Azure.Cosmos;
  2. using HTEXLib.COMM.Helpers;
  3. using Microsoft.AspNetCore.Http;
  4. using Microsoft.AspNetCore.Mvc;
  5. using Microsoft.Extensions.Configuration;
  6. using Microsoft.Extensions.Options;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Linq;
  10. using System.Text;
  11. using System.Text.Json;
  12. using System.Threading.Tasks;
  13. using TEAMModelOS.Filter;
  14. using TEAMModelOS.Models;
  15. using TEAMModelOS.SDK.DI;
  16. using TEAMModelOS.SDK.Extension;
  17. using TEAMModelOS.SDK.Models;
  18. using TEAMModelOS.SDK.Models.Cosmos;
  19. using TEAMModelOS.SDK.Models.Cosmos.Common;
  20. using TEAMModelOS.SDK.Models.Service;
  21. using TEAMModelOS.Services.Common;
  22. namespace TEAMModelOS.Controllers
  23. {
  24. [ProducesResponseType(StatusCodes.Status200OK)]
  25. [ProducesResponseType(StatusCodes.Status400BadRequest)]
  26. //[Authorize(Roles = "IES5")]
  27. [Route("teacher/share")]
  28. [ApiController]
  29. public class ShareController : ControllerBase
  30. {
  31. private readonly AzureCosmosFactory _azureCosmos;
  32. private readonly SnowflakeId _snowflakeId;
  33. private readonly DingDing _dingDing;
  34. private readonly Option _option;
  35. private readonly IConfiguration _configuration;
  36. private readonly NotificationService _notificationService;
  37. public ShareController(AzureCosmosFactory azureCosmos, SnowflakeId snowflakeId, DingDing dingDing, IOptionsSnapshot<Option> option, IConfiguration configuration, NotificationService notificationService)
  38. {
  39. _azureCosmos = azureCosmos;
  40. _snowflakeId = snowflakeId;
  41. _dingDing = dingDing;
  42. _option = option?.Value;
  43. _configuration = configuration;
  44. _notificationService = notificationService;
  45. }
  46. /*
  47. {
  48. "school": "学校编码",
  49. "scope":"school|private",
  50. "tmdInfo":["tmdid":"id1","tmdname":"name1"],
  51. "type":"coedit共编,share分享",
  52. "agree":0,1,
  53. "issuer":"权限颁发者tmdid",
  54. "opt":"add/del/edit"
  55. "syllabusId":"分享的课纲章节id",
  56. "syllabusName":"章节名称",
  57. "volumeId":"册别id",
  58. "volumeName":"册别name"
  59. }
  60. */
  61. /// <summary>
  62. /// 分享及邀请共编,并设置TTL过期时间
  63. /// </summary>
  64. /// <param name="request"></param>
  65. /// <returns></returns>
  66. [ProducesDefaultResponseType]
  67. [HttpPost("to")]
  68. // [AuthToken(Roles = "Teacher")]
  69. public async Task<IActionResult> To(ShareData request) {
  70. // var (id, _, _, _) = HttpContext.GetAuthTokenInfo();
  71. try {
  72. var client = _azureCosmos.GetCosmosClient();
  73. //需要判断id== req.issuer 才能进行授权操作
  74. if (request.scope.Equals("school"))
  75. {
  76. Syllabus syllabusD = await client.GetContainer("TEAMModelOS", "School").ReadItemAsync<Syllabus>(request.syllabusId, new PartitionKey($"Syllabus-{request.volumeId}"));
  77. if (request.opt == "del")
  78. {
  79. if (syllabusD.auth.IsNotEmpty())
  80. {
  81. List<SyllabusAuth> syllabusAuths = new List<SyllabusAuth>();
  82. syllabusD.auth.ForEach(x => {
  83. if (request.tmdInfo.Select(tmd => tmd.tmdid).Contains(x.tmdid)) {
  84. syllabusAuths.Add(x);
  85. }
  86. });
  87. syllabusAuths.ForEach(x => {
  88. syllabusD.auth.Remove(x);
  89. });
  90. await client.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<Syllabus>(syllabusD, request.syllabusId, new PartitionKey($"Syllabus-{request.volumeId}"));
  91. request.tmdInfo.ForEach(async x => {
  92. await client.GetContainer("TEAMModelOS", "Teacher").DeleteItemAsync<Share>(request.syllabusId, new PartitionKey($"Share-{x.tmdid}"));
  93. });
  94. }
  95. }
  96. else if (request.opt.Equals("add") || request.opt.Equals("edit"))
  97. {
  98. (Syllabus syllabus, List<Share> shares) = DoAuth(request, syllabusD);
  99. shares.ForEach(async x => {
  100. await client.GetContainer("TEAMModelOS", "Teacher").UpsertItemAsync<Share>(x, new PartitionKey($"{x.code}"));
  101. //发送共编或分享通知
  102. Notification notification = new Notification
  103. {
  104. hubName = "hita",
  105. type = "msg",
  106. from = $"ies5:{request.school}",
  107. to = new List<string>() { x.code.Replace("Share-", "") },
  108. label = $"{x.type}_syllabus",
  109. body = new { biz = x.type, tmdid = x.issuer, schoolcode = $"{request.school}", status = 1, time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() }.ToJsonString(),
  110. expires = DateTimeOffset.UtcNow.AddDays(7).ToUnixTimeSeconds()
  111. };
  112. var url = _configuration.GetValue<string>("HaBookAuth:CoreService:sendnotification");
  113. var clientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
  114. var clientSecret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
  115. var location = _option.Location;
  116. var code = await _notificationService.SendNotification(clientID, clientSecret, location, url, notification);
  117. });
  118. await client.GetContainer("TEAMModelOS", "School").UpsertItemAsync<Syllabus>(syllabus, new PartitionKey($"Syllabus-{request.volumeId}"));
  119. }
  120. }
  121. else if (request.scope.Equals("private"))
  122. {
  123. Syllabus syllabusD = await client.GetContainer("TEAMModelOS", "Teacher").ReadItemAsync<Syllabus>(request.syllabusId, new PartitionKey($"Syllabus-{request.volumeId}"));
  124. if (request.opt == "del")
  125. {
  126. if (syllabusD.auth.IsNotEmpty())
  127. {
  128. List<SyllabusAuth> syllabusAuths = new List<SyllabusAuth>();
  129. syllabusD.auth.ForEach(x => {
  130. if (request.tmdInfo.Select(tmd => x.tmdid).Contains(x.tmdid))
  131. {
  132. syllabusAuths.Add(x);
  133. }
  134. });
  135. syllabusAuths.ForEach(x => {
  136. syllabusD.auth.Remove(x);
  137. });
  138. await client.GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Syllabus>(syllabusD, request.syllabusId, new PartitionKey($"Syllabus-{request.volumeId}"));
  139. request.tmdInfo.ForEach(async x => {
  140. await client.GetContainer("TEAMModelOS", "Teacher").DeleteItemAsync<Share>(request.syllabusId, new PartitionKey($"Share-{x.tmdid}"));
  141. });
  142. }
  143. }
  144. else if (request.opt.Equals("add") || request.opt.Equals("edit"))
  145. {
  146. (Syllabus vlm, List<Share> shares) = DoAuth(request, syllabusD);
  147. shares.ForEach(async x => {
  148. await client.GetContainer("TEAMModelOS", "Teacher").UpsertItemAsync<Share>(x, new PartitionKey($"{x.code}"));
  149. });
  150. await client.GetContainer("TEAMModelOS", "Teacher").UpsertItemAsync<Syllabus>(syllabusD, new PartitionKey($"Syllabus-{request.volumeId}"));
  151. }
  152. }
  153. return Ok(new { code = 200 });
  154. }
  155. catch (Exception ex) {
  156. await _dingDing.SendBotMsg($"OS,{_option.Location},teacher/share/to\n{ex.Message}{ex.StackTrace}", GroupNames.成都开发測試群組);
  157. }
  158. return Ok(new { code = 500 });
  159. }
  160. private (Syllabus, List<Share>) DoAuth(ShareData request, Syllabus syllabus)
  161. {
  162. long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  163. List<Share> shares = new List<Share>();
  164. request.tmdInfo.ForEach(xmd => {
  165. var share = new Share
  166. {
  167. id = request.syllabusId,
  168. volumeId = request.volumeId,
  169. volumeName = request.volumeName,
  170. syllabusName = request.syllabusName,
  171. code = $"Share-{request.type}-{xmd.tmdid}",
  172. pk = "Share",
  173. ttl = -1,
  174. issuer = request.issuer,
  175. createTime = now,
  176. school = request.school,
  177. scope = request.scope,
  178. type = request.type,
  179. agree = request.agree
  180. };
  181. shares.Add(share);
  182. });
  183. if (syllabus.auth.IsNotEmpty())
  184. {
  185. request.tmdInfo.ForEach(xmd => {
  186. bool flag = false;
  187. int indx = 0;
  188. for (int index = 0; index < syllabus.auth.Count; index++)
  189. {
  190. if (syllabus.auth[index].tmdid == xmd.tmdid && request.type == syllabus.auth[index].type)
  191. {
  192. flag = true;
  193. indx = index;
  194. break;
  195. }
  196. }
  197. ///更新位置上的授权信息
  198. if (flag)
  199. {
  200. syllabus.auth[indx] = new SyllabusAuth
  201. {
  202. tmdid = xmd.tmdid,
  203. tmdname = xmd.tmdname,
  204. type = request.type,
  205. agree = request.agree,
  206. };
  207. }
  208. //新增
  209. else
  210. {
  211. syllabus.auth.Add(new SyllabusAuth
  212. {
  213. tmdid = xmd.tmdid,
  214. tmdname = xmd.tmdname,
  215. type = request.type,
  216. agree = request.agree,
  217. });
  218. }
  219. });
  220. }
  221. else
  222. {
  223. request.tmdInfo.ForEach(xmd => {
  224. syllabus.auth = new List<SyllabusAuth>() {
  225. new SyllabusAuth {
  226. tmdid = xmd.tmdid,
  227. tmdname = xmd.tmdname,
  228. type = request.type,
  229. agree = request.agree,
  230. }
  231. };
  232. });
  233. }
  234. return (syllabus, shares);
  235. }
  236. /// <summary>
  237. /// {"code":"教师编码","id":"章节id","type":"coedit/share","opt":"ignore/agree"}
  238. ///
  239. /// 教师操作收到的分享及课纲共编, ignore 忽略,需要删除Share 数据 并更新syllabus.auth
  240. /// </summary>
  241. /// <param name="request"></param>
  242. /// <returns></returns>
  243. [ProducesDefaultResponseType]
  244. [HttpPost("agree-share")]
  245. // [AuthToken(Roles = "Teacher")]
  246. public async Task<IActionResult> AgreeShare(JsonElement request) {
  247. try {
  248. if (!request.TryGetProperty("type", out JsonElement type)) { return BadRequest(); }
  249. if (!request.TryGetProperty("code", out JsonElement code)) { return BadRequest(); }
  250. if (!request.TryGetProperty("id", out JsonElement id)) { return BadRequest(); }
  251. if (!request.TryGetProperty("opt", out JsonElement opt)) { return BadRequest(); }
  252. var client = _azureCosmos.GetCosmosClient();
  253. Share share = await client.GetContainer("TEAMModelOS", "Teacher").ReadItemAsync<Share>($"{id}", new PartitionKey($"Share-{type}-{code}"));
  254. if ($"{opt}".Equals("agree"))
  255. {
  256. share.agree = 1;
  257. await client.GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Share>(share, $"{id}", new PartitionKey($"Share-{type}-{code}"));
  258. }
  259. else if ($"{opt}".Equals("ignore"))
  260. {
  261. await client.GetContainer("TEAMModelOS", "Teacher").DeleteItemAsync<Share>($"{id}", new PartitionKey($"Share-{type}-{code}"));
  262. }
  263. Syllabus syllabus = null;
  264. if (share.scope == "school")
  265. {
  266. try
  267. {
  268. syllabus = await client.GetContainer("TEAMModelOS", "School").ReadItemAsync<Syllabus>($"{id}", new PartitionKey($"Syllabus-{share.volumeId}"));
  269. } catch {
  270. //仅处理差不到数据的情况
  271. }
  272. }
  273. else if (share.scope == "private")
  274. {
  275. try
  276. {
  277. syllabus = await client.GetContainer("TEAMModelOS", "Teacher").ReadItemAsync<Syllabus>($"{id}", new PartitionKey($"Syllabus-{share.volumeId}"));
  278. } catch
  279. {
  280. }
  281. }
  282. if (syllabus != null) {
  283. if (syllabus.auth.IsNotEmpty())
  284. {
  285. syllabus.auth.ForEach(x =>
  286. {
  287. if (x.tmdid == $"{code}" && x.type == $"{type}")
  288. {
  289. if ($"{opt}".Equals("ignore"))
  290. {
  291. x.agree = 0;
  292. }
  293. else if ($"{opt}".Equals("ignore"))
  294. {
  295. x.agree = 1;
  296. }
  297. }
  298. });
  299. }
  300. if (share.scope == "school")
  301. {
  302. syllabus = await client.GetContainer("TEAMModelOS", "School").ReplaceItemAsync<Syllabus>(syllabus, $"{id}", new PartitionKey($"Syllabus-{share.volumeId}"));
  303. }
  304. else if (share.scope == "private")
  305. {
  306. syllabus = await client.GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Syllabus>(syllabus, $"{id}", new PartitionKey($"Syllabus-{share.volumeId}"));
  307. }
  308. }
  309. return Ok(new { status = 200 });
  310. } catch (Exception ex) {
  311. await _dingDing.SendBotMsg($"OS,{_option.Location},teacher/share/agree-share()\n{ex.Message}{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
  312. return Ok(new { status = 500 });
  313. }
  314. }
  315. /// <summary>
  316. /// 删除共享记录
  317. /// {"id":"章节id","type:"coedit/share","code":"教师tmdid"}
  318. /// </summary>
  319. /// <param name="request"></param>
  320. /// <returns></returns>
  321. [ProducesDefaultResponseType]
  322. [HttpPost("del-share")]
  323. public async Task<IActionResult> DelShare(JsonElement request) {
  324. try
  325. {
  326. var client = _azureCosmos.GetCosmosClient();
  327. if (!request.TryGetProperty("type", out JsonElement type)) { return BadRequest(); }
  328. if (!request.TryGetProperty("code", out JsonElement code)) { return BadRequest(); }
  329. if (!request.TryGetProperty("id", out JsonElement id)) { return BadRequest(); }
  330. await client.GetContainer("TEAMModelOS", "Teacher").DeleteItemAsync<Share>($"{id}", new PartitionKey($"Share-{type}-{code}"));
  331. return Ok(new { status = 200 });
  332. } catch (Exception ex)
  333. {
  334. await _dingDing.SendBotMsg($"OS,{_option.Location},teacher/share/del-share()\n{ex.Message}{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
  335. return Ok(new { status = 500 });
  336. }
  337. }
  338. /// <summary>
  339. /// {"code":"教师编码","type":"coedit/share","id":"册别id"}
  340. /// 教师拉取自己收到的分享及共编
  341. /// </summary>
  342. /// <param name="request"></param>
  343. /// <returns></returns>
  344. [ProducesDefaultResponseType]
  345. [HttpPost("find")]
  346. // [AuthToken(Roles = "Teacher")]
  347. public async Task<IActionResult> Find(JsonElement request)
  348. {
  349. try
  350. {
  351. List<Share> shares = new List<Share>();
  352. if (!request.TryGetProperty("code", out JsonElement code)) { return BadRequest(); }
  353. if (!request.TryGetProperty("type", out JsonElement type)) { return BadRequest(); }
  354. request.TryGetProperty("id", out JsonElement id);
  355. var client = _azureCosmos.GetCosmosClient();
  356. StringBuilder queryText = new StringBuilder("select value(c) from c");
  357. queryText.Append($" where c.type='{type}' ");
  358. if (id.ValueKind.Equals(JsonValueKind.String) && !string.IsNullOrEmpty(id.GetString())) {
  359. queryText.Append($" and c.id='{id}'");
  360. }
  361. await foreach (var item in client.GetContainer("TEAMModelOS", "Teacher").GetItemQueryIterator<Share>(queryText: queryText.ToString(),
  362. requestOptions: new QueryRequestOptions() { PartitionKey = new Azure.Cosmos.PartitionKey($"Share-{type}-{code}") }))
  363. {
  364. shares.Add(item);
  365. }
  366. var sharesGp = shares.GroupBy(x => new {id= x.volumeId ,code=x.scope=="school"?x.school:x.issuer}).Select(y=>new { id=y.Key.id,code=y.Key.code,list=y.ToList()});
  367. return Ok(new { shares = sharesGp ,status=200});
  368. }
  369. catch (Exception ex)
  370. {
  371. await _dingDing.SendBotMsg($"OS,{_option.Location},teacher/share/find()\n{ex.Message}{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
  372. return Ok(new { status = 404 });
  373. }
  374. }
  375. public record ShareView
  376. {
  377. public string scope { get; set; }
  378. // public string code { get; set; }
  379. public string school { get; set; }
  380. public string issuer { get; set; }
  381. public string volumeId { get; set; }
  382. public List<string> syllabusId { get; set; }
  383. }
  384. /*
  385. {
  386. "scope": "school/private",
  387. "school": "学校编码",
  388. "issuer": "权限颁发者",
  389. "volumeId": "册别id",
  390. "syllabusId": ["id1课纲章节节点id","id2课纲章节节点id"],
  391. }
  392. */
  393. /// <summary>
  394. /// 查看分享
  395. /// </summary>
  396. /// <param name="request"></param>
  397. /// <returns></returns>
  398. [ProducesDefaultResponseType]
  399. [HttpPost("view-share")]
  400. [AuthToken(Roles = "teacher")]
  401. public async Task<IActionResult> View(ShareView request)
  402. {
  403. try
  404. {
  405. List<SyllabusTreeNode> treeNodes = new List<SyllabusTreeNode>();
  406. Volume volume;
  407. var client = _azureCosmos.GetCosmosClient();
  408. string code = null;
  409. List<string> sid = new List<string>();
  410. request.syllabusId.ForEach(x => { sid.Add($"'{x}'"); });
  411. var sidSql= string.Join(",", sid);
  412. if (request.scope == "school")
  413. {
  414. code = request.school;
  415. var queryslt = $"SELECT value(c) FROM c where c.id in ({sidSql})";
  416. await foreach (var item in client.GetContainer("TEAMModelOS", "School").GetItemQueryIterator<Syllabus>(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Syllabus-{request.volumeId}") }))
  417. {
  418. List<SyllabusTree> trees = SyllabusService.ListToTree(item.children);
  419. SyllabusTreeNode tree = new SyllabusTreeNode() { id = item.id, scope = item.scope, trees = trees, volumeId = item.volumeId, auth = item.auth };
  420. treeNodes.Add(tree);
  421. }
  422. volume = await client.GetContainer("TEAMModelOS", "School").ReadItemAsync<Volume>(request.volumeId, new PartitionKey($"Volume-{code}"));
  423. }
  424. else if (request.scope == "private")
  425. {
  426. code = request.issuer;
  427. var queryslt = $"SELECT value(c) FROM c where c.id in ({sidSql})";
  428. await foreach (var item in client.GetContainer("TEAMModelOS", "Teacher").GetItemQueryIterator<Syllabus>(queryText: queryslt, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Syllabus-{request.volumeId}") }))
  429. {
  430. List<SyllabusTree> trees = SyllabusService.ListToTree(item.children);
  431. SyllabusTreeNode tree = new SyllabusTreeNode() { id = item.id, scope = item.scope, trees = trees, volumeId = item.volumeId, auth = item.auth };
  432. treeNodes.Add(tree);
  433. }
  434. volume = await client.GetContainer("TEAMModelOS", "Teacher").ReadItemAsync<Volume>(request.volumeId, new PartitionKey($"Volume-{code}"));
  435. }
  436. else
  437. {
  438. return BadRequest();
  439. }
  440. return Ok(new { volume,tree= treeNodes });
  441. }
  442. catch (Exception ex) {
  443. await _dingDing.SendBotMsg($"OS,{_option.Location},teacher/share/view()\n{ex.Message}", GroupNames.醍摩豆服務運維群組);
  444. return BadRequest();
  445. }
  446. }
  447. /// <summary>
  448. ///二维码扫码方式
  449. /// </summary>
  450. /// <param name="request"></param>
  451. /// <returns></returns>
  452. [ProducesDefaultResponseType]
  453. [HttpPost("qrcode")]
  454. //[AuthToken(Roles = "Teacher")]
  455. public async Task<IActionResult> Qrcode(Favorite request)
  456. {
  457. return Ok();
  458. }
  459. }
  460. }