GenPDFService.cs 43 KB

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