GenPDFService.cs 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944
  1. using Azure.Messaging.ServiceBus;
  2. using Azure.Storage.Blobs.Models;
  3. using Microsoft.Azure.Cosmos;
  4. using StackExchange.Redis;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.ComponentModel;
  8. using System.IO;
  9. using System.Linq;
  10. using System.Net;
  11. using System.Net.Http;
  12. using System.Net.Http.Json;
  13. using System.Text;
  14. using System.Text.Json;
  15. using System.Text.Json.Nodes;
  16. using System.Threading.Tasks;
  17. using System.Web;
  18. using TEAMModelOS.SDK.DI;
  19. using TEAMModelOS.SDK.Extension;
  20. using static TEAMModelOS.SDK.CoreAPIHttpService;
  21. using TEAMModelOS.SDK.Models;
  22. using Microsoft.Extensions.Configuration;
  23. using System.Net.Http.Headers;
  24. using Azure.Storage.Sas;
  25. using TEAMModelOS.SDK.Models.Service;
  26. using Azure.Core;
  27. using TEAMModelOS.SDK.Models.Cosmos.Common;
  28. using System.Configuration;
  29. namespace TEAMModelOS.SDK
  30. {
  31. public static class GenPDFService
  32. {
  33. public static async Task GenPdf( AzureRedisFactory _azureRedis, AzureCosmosFactory _azureCosmos,
  34. IConfiguration _configuration, IHttpClientFactory _httpClient, AzureStorageFactory _azureStorage,
  35. DingDing _dingDing, SnowflakeId _snowflakeId,string json )
  36. {
  37. long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  38. Console.WriteLine($"開始:{now}");
  39. string apiUri = "http://52.130.252.100:13000";
  40. PDFGenQueue genQueueData =json.ToObject<PDFGenQueue>();
  41. RedisValue redisValue = await _azureRedis.GetRedisClient(8).HashGetAsync($"PDFGen:{genQueueData.sessionId}", genQueueData.id);
  42. PDFGenRedis genRedis = null;
  43. if (redisValue!=default)
  44. {
  45. genRedis = redisValue.ToString().ToObject<PDFGenRedis>();
  46. //计算等待了多久的时间才开始生成。
  47. var wait = now- genRedis.join;
  48. genRedis.wait = wait;
  49. genRedis.status= 1;
  50. await _azureRedis.GetRedisClient(8).HashSetAsync($"PDFGen:{genQueueData.sessionId}", genQueueData.id, genRedis.ToJsonString());
  51. var client = _httpClient.CreateClient();
  52. ///再加5秒
  53. client.Timeout= TimeSpan.FromMilliseconds(genQueueData.timeout+ genQueueData.delay+5000);
  54. try
  55. {
  56. string urlpdf = $"{apiUri}/api/pdf";
  57. string jsonElement = new
  58. {
  59. pageUrl = genQueueData.pageUrl,
  60. timeout = genQueueData.timeout,
  61. delay = genQueueData.delay,
  62. checkPageCompleteJs = genQueueData.checkPageCompleteJs
  63. }.ToJsonString();
  64. var request = new HttpRequestMessage
  65. {
  66. Method = new HttpMethod("POST"),
  67. RequestUri = new Uri(urlpdf),
  68. Content = new StringContent(jsonElement)
  69. };
  70. var mediaTypeHeader = new MediaTypeHeaderValue("application/json")
  71. {
  72. CharSet = "UTF-8"
  73. };
  74. request.Content.Headers.ContentType = mediaTypeHeader;
  75. HttpResponseMessage responseMessage = await client.SendAsync(request);
  76. if (responseMessage.IsSuccessStatusCode)
  77. {
  78. string content = await responseMessage.Content.ReadAsStringAsync();
  79. JsonNode jsonNode = content.ToObject<JsonNode>();
  80. var code = jsonNode["code"];
  81. var file = jsonNode["data"]?["file"];
  82. if (code!=null && $"{code}".Equals("0") && file!= null && !string.IsNullOrWhiteSpace($"{file}"))
  83. {
  84. try
  85. {
  86. Stream stream = await client.GetStreamAsync($"{apiUri}/{file}");
  87. Uri uri = new Uri(genQueueData.pageUrl);
  88. var query = HttpUtility.ParseQueryString(uri.Query);
  89. string? url = query["url"];
  90. if (!string.IsNullOrWhiteSpace(url))
  91. {
  92. url= HttpUtility.UrlDecode(url);
  93. uri = new Uri(url);
  94. string host = uri.Host;
  95. var blobServiceClient = _azureStorage.GetBlobServiceClient();
  96. if (blobServiceClient.Uri.Host.Equals(host))
  97. {
  98. // 获取容器名,它是路径的第一个部分
  99. string containerName = uri.Segments[1].TrimEnd('/');
  100. // 获取文件的完整同级目录,这是文件路径中除了文件名和扩展名之外的部分
  101. // 由于文件名是路径的最后一个部分,我们可以通过连接除了最后一个部分之外的所有部分来获取目录路径
  102. string directoryPath = string.Join("", uri.Segments, 2, uri.Segments.Length - 3);
  103. string? fileName = Path.GetFileNameWithoutExtension(uri.AbsolutePath);
  104. string blobPath = $"{directoryPath}{fileName}.pdf";
  105. var blockBlob = _azureStorage.GetBlobContainerClient(containerName).GetBlobClient(blobPath);
  106. string content_type = "application/octet-stream";
  107. ContentTypeDict.dict.TryGetValue(".pdf", out string? contenttype);
  108. if (!string.IsNullOrEmpty(contenttype))
  109. {
  110. content_type = contenttype;
  111. }
  112. await blockBlob.UploadAsync(stream, true);
  113. blockBlob.SetHttpHeaders(new BlobHttpHeaders { ContentType = content_type });
  114. genRedis.blob= blockBlob.Name;
  115. genRedis.status=2;
  116. }
  117. else
  118. {
  119. genRedis.status=6;
  120. }
  121. }
  122. else
  123. {
  124. genRedis.status=6;
  125. }
  126. }
  127. catch (Exception ex)
  128. {
  129. await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-ServiceBus,GenPDF()\n{ex.Message}\n{ex.StackTrace}\n\n{json}", GroupNames.醍摩豆服務運維群組);
  130. genRedis.status=6;
  131. }
  132. }
  133. else
  134. {
  135. if (code!= null && $"{code}".Equals("99999"))
  136. {
  137. genRedis.status= 4;
  138. }
  139. else
  140. {
  141. genRedis.status= 3;
  142. }
  143. }
  144. }
  145. else
  146. {
  147. genRedis.status= 3;
  148. }
  149. }
  150. catch (TaskCanceledException ex)
  151. {
  152. if (ex.CancellationToken.IsCancellationRequested)
  153. {
  154. // Console.WriteLine("请求被取消。");
  155. genRedis.status= 5;
  156. }
  157. else
  158. {
  159. //Console.WriteLine("请求超时。");
  160. genRedis.status= 4;
  161. }
  162. }
  163. catch (Exception ex)
  164. {
  165. genRedis.status=3;
  166. await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-ServiceBus,GenPDF()\n{ex.Message}\n{ex.StackTrace}\n\n{json}", GroupNames.醍摩豆服務運維群組);
  167. }
  168. }
  169. else
  170. {
  171. genRedis= new PDFGenRedis { id =genQueueData.id, status= 5, cost=0, join=now, wait=0, name= genQueueData.name };
  172. //被取消的
  173. }
  174. long nowNew = DateTimeOffset.Now.ToUnixTimeMilliseconds();
  175. genRedis.cost=nowNew-now;
  176. await _azureRedis.GetRedisClient(8).HashSetAsync($"PDFGen:{genQueueData.sessionId}", genQueueData.id, genRedis.ToJsonString());
  177. //如果全部 生成,需要发送通知
  178. HashEntry[] datas = await _azureRedis.GetRedisClient(8).HashGetAllAsync($"PDFGen:{genQueueData.sessionId}");
  179. List<PDFGenRedis> dbgenRedis = new List<PDFGenRedis>();
  180. List<string> notifyUsers = new List<string>();
  181. string taskName = string.Empty;
  182. string taskType = string.Empty;
  183. if (datas!= null && datas.Length > 0)
  184. {
  185. foreach (var item in datas)
  186. {
  187. if (!$"{item.Name}".Contains("notifyUsers"))
  188. {
  189. dbgenRedis.Add(item.Value.ToString().ToObject<PDFGenRedis>());
  190. }
  191. else
  192. {
  193. var jsonData = item.Value.ToString().ToObject<JsonElement>();
  194. notifyUsers= jsonData.GetProperty("notifyUsers").ToObject<List<string>>();
  195. taskType = jsonData.GetProperty("taskType").ToString();
  196. taskName = jsonData.GetProperty("taskName").ToString();
  197. }
  198. }
  199. }
  200. if (notifyUsers.IsNotEmpty())
  201. {
  202. string lang = Environment.GetEnvironmentVariable("Option:Location")!.Contains("China") ? "zh-cn" : "en-us";
  203. string sql = $"select c.id, c.name ,c.lang as code from c where c.id in ({string.Join(",", notifyUsers.Select(x => $"'{x}'"))})";
  204. List<IdNameCode> idNameCodes = new List<IdNameCode>();
  205. await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher)
  206. .GetItemQueryIteratorSql<IdNameCode>(queryText: sql, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey("Base") }))
  207. {
  208. idNameCodes.Add(item);
  209. }
  210. idNameCodes.FindAll(x => string.IsNullOrWhiteSpace(x.code) || (!x.code.Equals("zh-cn") && !x.code.Equals("zh-tw") && !x.code.Equals("en-us"))).ForEach(x => { x.code = lang; });
  211. var clientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
  212. var clientSecret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
  213. var url = _configuration.GetValue<string>("HaBookAuth:CoreAPI");
  214. string location = "China";
  215. if (Environment.GetEnvironmentVariable("Option:Location")!.Contains("China"))
  216. {
  217. location = "China";
  218. }
  219. else if (Environment.GetEnvironmentVariable("Option:Location")!.Contains("Global"))
  220. {
  221. location = "Global";
  222. }
  223. var token = await CoreTokenExtensions.CreateAccessToken(clientID, clientSecret, location);
  224. var client = _httpClient.CreateClient();
  225. if (client.DefaultRequestHeaders.Contains("Authorization"))
  226. {
  227. client.DefaultRequestHeaders.Remove("Authorization");
  228. client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.AccessToken}");
  229. }
  230. else
  231. {
  232. client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.AccessToken}");
  233. }
  234. //检查dbgenRedis的状态是否全部已经不是0和1
  235. var unfinished = dbgenRedis.FindAll(x => (x.status==0||x.status==1));
  236. if (unfinished==null || unfinished.Count==0)
  237. {
  238. // 数据大于60一个班,发送报告的总耗时等概要信息, 小于60的发送 生成报告的细则消息,小于五个的,可以列出报告的链接.
  239. long joinTime = dbgenRedis.Min(x => x.join);
  240. long totalTime = (now-joinTime)/1000;
  241. long costTime = dbgenRedis.Sum(x => x.cost)/1000;
  242. long avgTime = costTime / dbgenRedis.Count;
  243. long maxTime = dbgenRedis.Max(x => x.cost)/1000;
  244. long minTime = dbgenRedis.Min(x => x.cost)/1000;
  245. long statusOk = dbgenRedis.Where(x => x.status==2).Count();
  246. long statusFailed = dbgenRedis.Where(x => x.status!=2).Count();
  247. string key = "pdf-gen-notify-higher60";
  248. if (dbgenRedis.Count>60)
  249. {
  250. key = "pdf-gen-notify-higher60";
  251. }
  252. else
  253. {
  254. key ="pdf-gen-notify-below60";
  255. }
  256. foreach (var teacher in idNameCodes)
  257. {
  258. string path = Path.Combine("", $"Lang/{teacher.code}.json");
  259. var sampleJson = File.ReadAllText(path);
  260. JsonElement jsonElement = sampleJson.ToObject<JsonElement>();
  261. JsonElement msgsJson = jsonElement.GetProperty(key);
  262. List<string> msgs = msgsJson.ToObject<List<string>>();
  263. string msg1 = msgs[1].Replace("{tmdname}", teacher.name).Replace("{taskName}", taskName).Replace("{totalTime}", $"{totalTime}")
  264. .Replace("{avgTime}", $"{avgTime}").Replace("{maxTime}", $"{maxTime}").Replace("{minTime}", $"{minTime}").Replace("{statusOk}", $"{statusOk}").Replace("{statusFailed}", $"{statusFailed}");
  265. StringBuilder sb = new StringBuilder($"{msg1}");
  266. if (dbgenRedis.Count<=60)
  267. {
  268. string status = string.Empty;
  269. dbgenRedis.ForEach(x => {
  270. switch (x.status)
  271. {
  272. case 0:
  273. status= teacher.code.Equals("zh-cn") ? "未执行" : teacher.code.Equals("zh-tw") ? "未執行" : "unexecuted";
  274. break;
  275. case 1:
  276. status= teacher.code.Equals("zh-cn") ? "执行中" : teacher.code.Equals("zh-tw") ? "執行中" : "executing";
  277. break;
  278. case 2:
  279. status= teacher.code.Equals("zh-cn") ? "成功" : teacher.code.Equals("zh-tw") ? "成功" : "success";
  280. break;
  281. case 3:
  282. status= teacher.code.Equals("zh-cn") ? "失败" : teacher.code.Equals("zh-tw") ? "失敗" : "failed";
  283. break;
  284. case 4:
  285. status= teacher.code.Equals("zh-cn") ? "超时" : teacher.code.Equals("zh-tw") ? "超時" : "timeout";
  286. break;
  287. case 5:
  288. status= teacher.code.Equals("zh-cn") ? "取消" : teacher.code.Equals("zh-tw") ? "取消" : "canceled";
  289. break;
  290. case 6:
  291. status= teacher.code.Equals("zh-cn") ? "存放异常" : teacher.code.Equals("zh-tw") ? "存放異常" : "SaveError";
  292. break;
  293. default:
  294. status= teacher.code.Equals("zh-cn") ? "失败" : teacher.code.Equals("zh-tw") ? "失敗" : "failed";
  295. break;
  296. }
  297. string msg2 = msgs[2].Replace("{studentName}", x.name).Replace("{status}", $"{status}").Replace("{wait}", $"{x.wait/1000}").Replace("{cost}", $"{x.cost/1000}").Replace("{total}", $"{(x.wait+x.cost)/1000}");
  298. sb.Append(msg2);
  299. });
  300. }
  301. NotifyData notifyData = new NotifyData
  302. {
  303. hubName = "hita5",
  304. sender = "IES",
  305. tags = new List<string> { $"{teacher.id}_{Constant.NotifyType_IES5_Course}" },
  306. title = msgs[0],
  307. eventId = $"{key}_{_snowflakeId.NextId()}",
  308. eventName =msgs[0],
  309. data = "{\"value\":{}}",
  310. body=sb.ToString(),
  311. };
  312. string result = "";
  313. try
  314. {
  315. HttpResponseMessage responseMessage = await client.PostAsJsonAsync($"{url}/service/PushNotify", notifyData);
  316. if (responseMessage.StatusCode == HttpStatusCode.OK)
  317. {
  318. string content = await responseMessage.Content.ReadAsStringAsync();
  319. result = content;
  320. }
  321. else
  322. {
  323. result = $"{responseMessage.StatusCode},推送返回的状态码。";
  324. }
  325. }
  326. catch (Exception exm)
  327. {
  328. _= _dingDing.SendBotMsg($"{location}站点发送消息异常,{exm.Message}\n{exm.StackTrace}:\n{url}/service/PushNotify \nheader: {token.AccessToken} \nresult:{result}\n params:{notifyData.ToJsonString()}", GroupNames.成都开发測試群組);
  329. }
  330. }
  331. }
  332. }
  333. Console.WriteLine($"結束:{now}");
  334. return;
  335. }
  336. /// <summary>
  337. /// 加入PDF生成队列服务
  338. /// https://github.com/wuxue107/bookjs-eazy https://github.com/wuxue107/screenshot-api-server
  339. /// </summary>
  340. /// <param name="azureRedis"></param>
  341. /// <param name="data"></param>
  342. /// <returns></returns>
  343. public static async Task<(int total,int add )> AddGenPdfQueue( AzureRedisFactory azureRedis, GenPDFData data)
  344. {
  345. long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  346. List<PDFGenRedis> genRedis = new List<PDFGenRedis>();
  347. List<PDFGenRedis> dbgenRedis = new List<PDFGenRedis>();
  348. HashEntry[] datas = await azureRedis.GetRedisClient(8).HashGetAllAsync($"PDFGen:{data.sessionId}");
  349. List<string> notifyUsers = new List<string>();
  350. if (datas!= null && datas.Length > 0)
  351. {
  352. foreach (var item in datas)
  353. {
  354. if (!$"{item.Name}".Contains("notifyUsers"))
  355. {
  356. dbgenRedis.Add(item.Value.ToString().ToObject<PDFGenRedis>());
  357. }
  358. else {
  359. var json = item.Value.ToString().ToObject<JsonElement>();
  360. notifyUsers= json.GetProperty("notifyUsers").ToObject<List<string>>();
  361. }
  362. }
  363. }
  364. if (data.notifyUsers.IsNotEmpty())
  365. {
  366. notifyUsers.AddRange(data.notifyUsers);
  367. notifyUsers= notifyUsers.Distinct().ToList();
  368. }
  369. var dataVal = new GenPDFSchema { notifyUsers= notifyUsers, taskName= data.taskName, taskType = data.taskType };
  370. //Redis记录需要通知的用户
  371. await azureRedis.GetRedisClient(8).HashSetAsync($"PDFGen:{data.sessionId}", "notifyUsers",JsonSerializer.Serialize(dataVal));
  372. //Redis记录需要生成PDF的数量
  373. int countProcess= 0;
  374. foreach (var item in data.datas)
  375. {
  376. var dbData = dbgenRedis.Find(x => x.id.Equals(item.id));
  377. if (dbData!=null)
  378. {
  379. if (dbData.status==0|| dbData.status==1)
  380. {
  381. //不变的
  382. countProcess+=1;
  383. }
  384. else
  385. {
  386. //需要变更的
  387. dbData.status = 0;
  388. dbData.cost=0;
  389. dbData.join= now;
  390. dbData.wait=0;
  391. dbData.name=item.name;
  392. dbData.url= item.url;
  393. dbData.scope = data.scope;
  394. dbData.owner= data.owner;
  395. dbData.env= data.env;
  396. genRedis.Add(dbData);
  397. }
  398. }
  399. else {
  400. genRedis.Add(new PDFGenRedis {
  401. id = item.id,
  402. status=0,
  403. cost=0,
  404. wait=0,
  405. join=now,
  406. name=item.name,
  407. url= item.url,
  408. scope=data.scope,
  409. owner= data.owner,
  410. env= data.env,
  411. });
  412. }
  413. }
  414. //过期时间 当前个数+ reddis的个数
  415. foreach (var item in genRedis)
  416. {
  417. countProcess+=1;
  418. await azureRedis.GetRedisClient(8).HashSetAsync($"PDFGen:{data.sessionId}", item.id, item.ToJsonString());
  419. PDFGenQueue genQueue = new PDFGenQueue
  420. {
  421. id = item.id,
  422. checkPageCompleteJs= data.checkPageCompleteJs,
  423. delay=data.delay,
  424. html= data.html,
  425. pageUrl= $"{data.pageUrl}?url={HttpUtility.UrlEncode(item.url)}",
  426. sessionId= data.sessionId,
  427. timeout =data.timeout,
  428. name=item.name,
  429. env= data.env,
  430. };
  431. //string message = JsonSerializer.Serialize(genQueue);
  432. //从头部压入元素,队列先进先出
  433. await azureRedis.GetRedisClient(8).ListLeftPushAsync($"PDFGen:Queue", genQueue.ToJsonString());
  434. //var serviceBusMessage = new ServiceBusMessage(message);
  435. //serviceBusMessage.ApplicationProperties.Add("name", "BlobRoot");
  436. //await azureServiceBus.GetServiceBusClient().SendMessageAsync("dep-genpdf", serviceBusMessage);
  437. }
  438. long expire = (data.delay + data.timeout) * countProcess+30*60*1000;
  439. var tiemSpan = TimeSpan.FromMilliseconds(expire);
  440. await azureRedis.GetRedisClient(8).KeyExpireAsync($"PDFGen:{data.sessionId}", tiemSpan);
  441. return ( countProcess , genRedis.Count() );
  442. }
  443. public static async Task<(List<ArtStudentPdf> studentPdfs, List<StudentArtResult> artResults, ArtEvaluation art)> GenArtStudentPdf( AzureRedisFactory _azureRedis, AzureCosmosFactory _azureCosmos,
  444. CoreAPIHttpService _coreAPIHttpService, DingDing _dingDing, AzureStorageFactory _azureStorage, IConfiguration _configuration, List<string> studentIds,string _artId,string _schoolId,string headLang)
  445. {
  446. try
  447. {
  448. string _schoolCode = $"{_schoolId}";
  449. ArtEvaluation art = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Common").ReadItemAsync<ArtEvaluation>($"{_artId}", new PartitionKey($"Art-{_schoolId}"));
  450. (List<ArtStudentPdf> studentPdfs, List<StudentArtResult> artResults) = await ArtService.GenStuArtPDF(studentIds, $"{_artId}", art, $"{_schoolId}", $"{headLang}", _azureCosmos, _coreAPIHttpService, _dingDing);
  451. foreach (var artResult in artResults)
  452. {
  453. if (artResult.pdf == null || string.IsNullOrWhiteSpace(artResult.pdf.blob) || string.IsNullOrWhiteSpace(artResult.pdf.url))
  454. {
  455. artResult.pdf = new Attachment
  456. {
  457. // name = $"{artResult.studentId}.pdf",
  458. extension = "pdf",
  459. type = "doc",
  460. cnt = artResult.school,
  461. prime = false,//此处的作用是判断是否已经生成OK.
  462. };
  463. }
  464. else
  465. {
  466. artResult.pdf.prime = false;//此处的作用是判断是否已经生成OK.
  467. }
  468. await _azureRedis.GetRedisClient(8).HashSetAsync($"ArtPDF:{_artId}:{_schoolCode}", artResult.studentId, artResult.ToJsonString());
  469. }
  470. //2个小时。
  471. await _azureRedis.GetRedisClient(8).KeyExpireAsync($"ArtPDF:{_artId}:{_schoolCode}", new TimeSpan(2, 0, 0));
  472. List<Task<string>> uploads = new List<Task<string>>();
  473. studentPdfs.ForEach(x =>
  474. {
  475. x.blob = $"art/{x.artId}/report/{x.studentId}.json";
  476. var urlSas = _azureStorage.GetBlobSAS($"{_schoolCode}", x.blob, BlobSasPermissions.Write|BlobSasPermissions.Read, hour: 24);
  477. x.blobFullUrl=urlSas.fullUri;
  478. uploads.Add(_azureStorage.GetBlobContainerClient($"{_schoolCode}").UploadFileByContainer(x.ToJsonString(), "art", $"{x.artId}/report/{x.studentId}.json", true));
  479. });
  480. var uploadJsonUrls = await Task.WhenAll(uploads);
  481. return (studentPdfs, artResults,art);
  482. }
  483. catch (Exception ex)
  484. {
  485. await _dingDing.SendBotMsg($"{ex.Message}{ex.StackTrace}", GroupNames.成都开发測試群組);
  486. }
  487. return (null, null, null);
  488. }
  489. public static async Task PushScreenTask(AzureRedisFactory _azureRedis, IConfiguration _configuration, string _artId, ArtEvaluation art, List<ArtStudentPdf> studentPdfs)
  490. {
  491. string env = ScreenConstant.env_release;
  492. if (_configuration.GetValue<string>("Option:Location").Contains("Test", StringComparison.OrdinalIgnoreCase) ||
  493. _configuration.GetValue<string>("Option:Location").Contains("Dep", StringComparison.OrdinalIgnoreCase))
  494. {
  495. env = ScreenConstant.env_develop;
  496. }
  497. var addData = await GenPDFService.AddGenPdfQueue(_azureRedis,
  498. new GenPDFData
  499. {
  500. env =env,
  501. timeout=30000,
  502. delay=1000,
  503. checkPageCompleteJs=true,
  504. sessionId=$"{_artId}",
  505. taskName = art.name,
  506. taskType="Art",
  507. owner=art.owner,
  508. scope=art.scope,
  509. pageUrl="https://teammodeltest.blob.core.chinacloudapi.cn/0-public/bookjs/art/index.html",
  510. datas= studentPdfs.Select(x => new PDFData { id= x.studentId, name=x.studentName, url =x.blobFullUrl }).ToList()
  511. });
  512. //Console.WriteLine($"{addData.total},{addData.add}");
  513. }
  514. }
  515. public class GenPDFData
  516. {
  517. /// <summary>
  518. /// 数据装载后的页面 要制作为PDF的网页 (pageUrl 、html 参数二选一)
  519. /// </summary>
  520. public string pageUrl { get; set; }
  521. /// <summary>
  522. /// 要截图的网页HTML (pageUrl 、html 参数二选一) "html" : "<div>bookjs-eazy</div>",
  523. /// </summary>
  524. public string html { get; set; }
  525. /// <summary>
  526. /// 超时时间
  527. /// </summary>
  528. public long timeout { get; set; } = 30000;
  529. /// <summary>
  530. /// 页面完成后(checkPageCompleteJs返回为true后)延迟的时间,可选,默认:0
  531. /// </summary>
  532. public long delay { get; set; } = 2000;
  533. /// <summary>
  534. /// // 检查页面是否渲染完成的js表达式,可选,默认: "true"
  535. /// </summary>
  536. public bool checkPageCompleteJs { get; set; }
  537. /// <summary>
  538. /// 生成会话id, 活动id
  539. /// </summary>
  540. public string sessionId { get; set; }
  541. public List<PDFData> datas { get; set; } = new List<PDFData>();
  542. /// <summary>
  543. /// 通知用户
  544. /// </summary>
  545. public List<string> notifyUsers { get; set; } = new List<string>();
  546. /// <summary>
  547. /// 活动的名称
  548. /// </summary>
  549. public string taskName { get; set; }
  550. /// <summary>
  551. /// 艺术评测报告Art,评测报告Exam,问卷报告Survey,投票报告Vote,为空 则无法回调更新状态
  552. /// </summary>
  553. public string taskType { get; set;}
  554. /// <summary>
  555. /// 数据所有者
  556. /// </summary>
  557. public string owner { get; set; }
  558. /// <summary>
  559. /// 数据范围
  560. /// </summary>
  561. public string scope { get; set; }
  562. public string env { get; set; }
  563. }
  564. public class GenPDFSchema
  565. {
  566. public List<string> notifyUsers { get; set; } = new List<string>();
  567. public string taskName { get; set; }
  568. public string taskType { get; set; }
  569. }
  570. public class PDFData{
  571. /// <summary>
  572. /// 学生id
  573. /// </summary>
  574. public string id { get; set; }
  575. /// <summary>
  576. /// 数据链接
  577. /// </summary>
  578. public string url { get; set; }
  579. /// <summary>
  580. /// 学生名称
  581. /// </summary>
  582. public string name { get; set; }
  583. }
  584. /// <summary>
  585. /// redis 存储数据,超时时间设置为status=0 的count timeout* count+ delay*count
  586. /// </summary>
  587. public class PDFGenRedis
  588. {
  589. /// <summary>
  590. /// 学生id
  591. /// </summary>
  592. public string id { get; set; }
  593. /// <summary>
  594. /// 学生名称
  595. /// </summary>
  596. public string name { get; set; }
  597. /// <summary>
  598. /// 执行生成 毫秒
  599. /// </summary>
  600. public long cost { get; set;}
  601. /// <summary>
  602. /// 等候时间
  603. /// </summary>
  604. public long wait { get; set;}
  605. /// <summary>
  606. /// 加入时间
  607. /// </summary>
  608. public long join { get; set; }
  609. /// <summary>
  610. /// 上传成功后的文件
  611. /// </summary>
  612. public string blob { get; set; }
  613. /// <summary>
  614. /// 數據的url
  615. /// </summary>
  616. public string url { get; set; }
  617. /// <summary>
  618. /// 0 未执行,1 执行中,2 执行成功,3 执行失败,4超时,5 取消,6 存放异常
  619. /// </summary>
  620. public int status { get; set; } = 0;
  621. /// <summary>
  622. /// 状态信息
  623. /// </summary>
  624. public string msg { get; set; }
  625. /// <summary>
  626. /// 数据所有者
  627. /// </summary>
  628. public string owner { get; set; }
  629. /// <summary>
  630. /// 数据范围
  631. /// </summary>
  632. public string scope { get; set; }
  633. /// <summary>
  634. /// 环境变量
  635. /// </summary>
  636. public string env { get; set; }
  637. }
  638. /// <summary>
  639. /// // 拼接上接口的前缀 http://localhost:3000/ 就是完整PDF地址
  640. // http://localhost:3000/pdf/1614458263411-glduu.pdf
  641. // 拼接上接口的前缀 http://localhost:3000/download/可以就可生成在浏览器上的下载链接
  642. // http://localhost:3000/download/pdf/1614458263411-glduu.pdf
  643. // 拼接上http://localhost:3000/static/js/pdfjs/web/viewer.html?file=/pdf/1614458263411-glduu.pdf
  644. // 可使用pdfjs库进行预览
  645. //
  646. //## -e MAX_BROWSER=[num] 环境变量可选,最大的puppeteer实例数,忽略选项则默认值:1 , 值auto:[可用内存]/200M
  647. //## -e PDF_KEEP_DAY=[num] 自动删除num天之前产生的文件目录,默认0: 不删除文件
  648. //docker run -p 13000:3000 -td --rm -e MAX_BROWSER=2 -e PDF_KEEP_DAY=1 -v ${PWD}:/screenshot-api-server/public --name=screenshot-api-server wuxue107/screenshot-api-server
  649. /// </summary>
  650. public class PDFGenQueue
  651. {
  652. /// <summary>
  653. /// 环境变量
  654. /// </summary>
  655. public string env { get; set;}
  656. /// <summary>
  657. /// 姓名
  658. /// </summary>
  659. public string name { get; set; }
  660. /// <summary>
  661. /// 学生id
  662. /// </summary>
  663. public string id { get; set; }
  664. /// <summary>
  665. /// 数据装载后的页面 要制作为PDF的网页 (pageUrl 、html 参数二选一)
  666. /// </summary>
  667. public string pageUrl { get; set; }
  668. /// <summary>
  669. /// 要截图的网页HTML (pageUrl 、html 参数二选一) "html" : "<div>bookjs-eazy</div>",
  670. /// </summary>
  671. public string html { get; set; }
  672. /// <summary>
  673. /// 超时时间
  674. /// </summary>
  675. public long timeout { get; set; } = 30000;
  676. /// <summary>
  677. /// 页面完成后(checkPageCompleteJs返回为true后)延迟的时间,可选,默认:0
  678. /// </summary>
  679. public long delay { get; set; } = 2000;
  680. /// <summary>
  681. /// // 检查页面是否渲染完成的js表达式,可选,默认: "true"
  682. /// </summary>
  683. public bool checkPageCompleteJs { get; set; }
  684. /// <summary>
  685. /// 生成会话id, 活动id
  686. /// </summary>
  687. public string sessionId { get; set; }
  688. /// <summary>
  689. /// blob的sas
  690. /// </summary>
  691. public string blobSas { get; set; }
  692. /// <summary>
  693. /// blob名称
  694. /// </summary>
  695. public string blobName { get; set; }
  696. /// <summary>
  697. /// 容器名称
  698. /// </summary>
  699. public string cntName { get; set; }
  700. /// <summary>
  701. /// 完整blob地址
  702. /// </summary>
  703. public string blobFullUrl { get; set; }
  704. }
  705. public class ScreenClient : ClientDevice
  706. {
  707. /// <summary>
  708. /// 授权类型,bookjs_api
  709. /// </summary>
  710. public string? grant_type { get; set; }
  711. /// <summary>
  712. /// 客户端id
  713. /// </summary>
  714. public string? clientid { get; set; }
  715. /// <summary>
  716. /// SignalR的连接ID 不建议暴露。
  717. /// </summary>
  718. public string? connid { get; set; }
  719. /// <summary>
  720. /// 状态 busy 忙碌,free 空闲,down 离线,error 错误
  721. /// </summary>
  722. public string? status { get; set; }
  723. /// <summary>
  724. /// 最后更新时间
  725. /// </summary>
  726. public long last_time { get; set; }
  727. /// <summary>
  728. /// 任务完成数
  729. /// </summary>
  730. public int taskComplete { get; set; }
  731. }
  732. public class SignalRClient
  733. {
  734. /// <summary>
  735. /// 授权类型,bookjs_api
  736. /// </summary>
  737. public string? grant_type { get; set; }
  738. /// <summary>
  739. /// 客户端id
  740. /// </summary>
  741. public string? clientid { get; set; }
  742. /// <summary>
  743. /// SignalR的连接ID 不建议暴露。
  744. /// </summary>
  745. public string? connid { get; set; }
  746. }
  747. public interface IClient
  748. {
  749. Task ReceiveMessage(MessageBody message);
  750. Task ReceiveConnection(MessageBody message);
  751. Task ReceiveDisConnection(MessageBody message);
  752. }
  753. public abstract class MessageBody
  754. {
  755. public MessageBody()
  756. {
  757. time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  758. }
  759. /// <summary>
  760. /// 连接id
  761. /// </summary>
  762. public virtual string? connid { get; set; }
  763. /// <summary>
  764. /// 客户端id
  765. /// </summary>
  766. public virtual string? clientid { get; set; }
  767. /// <summary>
  768. /// 状态 busy 忙碌,free 空闲,down 离线,error 错误
  769. /// </summary>
  770. public virtual string? status { get; set; }
  771. /// <summary>
  772. /// 消息内容
  773. /// </summary>
  774. public virtual string? content { get; set; }
  775. /// <summary>
  776. /// 消息创建时间
  777. /// </summary>
  778. public virtual long time { get; }
  779. /// <summary>
  780. /// 授权类型,bookjs_api
  781. /// </summary>
  782. public virtual string? grant_type { get; set; }
  783. /// <summary>
  784. /// 消息类型
  785. /// </summary>
  786. public virtual MessageType message_type { get; set; }
  787. }
  788. /// <summary>
  789. /// 连接消息
  790. /// </summary>
  791. public class ConnectionMessage : MessageBody
  792. {
  793. }
  794. /// <summary>
  795. /// 断开连接消息
  796. /// </summary>
  797. public class DisConnectionMessage : MessageBody
  798. {
  799. }
  800. /// <summary>
  801. /// 业务处理消息
  802. /// </summary>
  803. public class ScreenProcessMessage : MessageBody
  804. {
  805. public string msg { get; set; }
  806. public int result { get; set; }
  807. }
  808. public static class ScreenConstant
  809. {
  810. public static readonly string busy = "busy";
  811. public static readonly string idle = "idle";
  812. public static readonly string error = "error";
  813. public static readonly string offline = "offline";
  814. public static readonly string grant_type = "bookjs_api";
  815. public static readonly string env_release = "release";
  816. public static readonly string env_develop = "develop";
  817. /// <summary>
  818. /// 冗余时间
  819. /// </summary>
  820. public static readonly long time_excess = 5000;
  821. }
  822. public enum MessageType {
  823. conn_success,//连接成功
  824. conn_error,// 连接失败
  825. task_send_success,// 任务发送成功
  826. task_send_error,// 任务发送失败
  827. task_execute_success,// 任务执行成功
  828. task_execute_error,// 任务执行失败
  829. }
  830. public class ClientDevice
  831. {
  832. /// <summary>
  833. /// 机器名
  834. /// </summary>
  835. public string? name { get; set; }
  836. /// <summary>
  837. /// 操作系统
  838. /// </summary>
  839. public string? os { get; set; }
  840. /// <summary>
  841. /// CPU核心数量
  842. /// </summary>
  843. public int cpu { get; set; }
  844. /// <summary>
  845. /// 内存大小
  846. /// </summary>
  847. public long ram { get; set;}
  848. /// <summary>
  849. /// 远程ip
  850. /// </summary>
  851. public string? remote { get; set; }
  852. /// <summary>
  853. /// 端口,可能有多个端口
  854. /// </summary>
  855. public string? port { get; set; }
  856. /// <summary>
  857. /// 地区
  858. /// </summary>
  859. public string? region { get; set; }
  860. /// <summary>
  861. /// 网卡 IP信息
  862. /// </summary>
  863. public List<Network> networks { get; set; } = new List<Network>();
  864. /// <summary>
  865. /// 超时时间,单位毫秒
  866. /// </summary>
  867. public long timeout { get; set; } = 30000;
  868. /// <summary>
  869. /// 延迟时间,单位毫秒
  870. /// </summary>
  871. public long delay { get; set; } = 3000;
  872. /// <summary>
  873. /// PDF服务地址
  874. /// </summary>
  875. public string? screenUrl { get; set; }
  876. }
  877. public class Network
  878. {
  879. public string? name { get; set; }
  880. public string? mac { get; set; }
  881. public string? ip { get; set; }
  882. }
  883. }