GenPDFService.cs 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952
  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 GenArtStudentPdf( AzureRedisFactory _azureRedis, AzureCosmosFactory _azureCosmos,
  444. CoreAPIHttpService _coreAPIHttpService, DingDing _dingDing, AzureStorageFactory _azureStorage, IConfiguration _configuration, JsonElement json)
  445. {
  446. try
  447. {
  448. json.TryGetProperty("studentIds", out JsonElement _studentIds);
  449. json.TryGetProperty("artId", out JsonElement _artId);
  450. json.TryGetProperty("schoolCode", out JsonElement _schoolId);
  451. json.TryGetProperty("headLang", out JsonElement headLang);
  452. List<string> studentIds = _studentIds.ToObject<List<string>>();
  453. string _schoolCode = $"{_schoolId}";
  454. ArtEvaluation art = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Common").ReadItemAsync<ArtEvaluation>($"{_artId}", new PartitionKey($"Art-{_schoolId}"));
  455. (List<ArtStudentPdf> studentPdfs, List<StudentArtResult> artResults) = await ArtService.GenStuArtPDF(studentIds, $"{_artId}", art, $"{_schoolId}", $"{headLang}", _azureCosmos, _coreAPIHttpService, _dingDing);
  456. foreach (var artResult in artResults)
  457. {
  458. if (artResult.pdf == null || string.IsNullOrWhiteSpace(artResult.pdf.blob) || string.IsNullOrWhiteSpace(artResult.pdf.url))
  459. {
  460. artResult.pdf = new Attachment
  461. {
  462. // name = $"{artResult.studentId}.pdf",
  463. extension = "pdf",
  464. type = "doc",
  465. cnt = artResult.school,
  466. prime = false,//此处的作用是判断是否已经生成OK.
  467. };
  468. }
  469. else
  470. {
  471. artResult.pdf.prime = false;//此处的作用是判断是否已经生成OK.
  472. }
  473. await _azureRedis.GetRedisClient(8).HashSetAsync($"ArtPDF:{_artId}:{_schoolCode}", artResult.studentId, artResult.ToJsonString());
  474. }
  475. //2个小时。
  476. await _azureRedis.GetRedisClient(8).KeyExpireAsync($"ArtPDF:{_artId}:{_schoolCode}", new TimeSpan(2, 0, 0));
  477. List<Task<string>> uploads = new List<Task<string>>();
  478. studentPdfs.ForEach(x => {
  479. x.blob = $"art/{x.artId}/report/{x.studentId}.json";
  480. var urlSas = _azureStorage.GetBlobSAS($"{_schoolCode}", x.blob, BlobSasPermissions.Write|BlobSasPermissions.Read, hour: 24);
  481. x.blobFullUrl=urlSas.fullUri;
  482. uploads.Add(_azureStorage.GetBlobContainerClient($"{_schoolCode}").UploadFileByContainer(x.ToJsonString(), "art", $"{x.artId}/report/{x.studentId}.json", true));
  483. });
  484. var uploadJsonUrls = await Task.WhenAll(uploads);
  485. var list = uploadJsonUrls.ToList();
  486. //List<string> urls = new List<string>();
  487. //(string uri, string sas) = _azureStorage.GetBlobContainerSAS($"{_schoolCode}", Azure.Storage.Sas.BlobContainerSasPermissions.Read);
  488. //studentPdfs.ForEach(x => {
  489. // string atrUrl = "https://teammodeltest.blob.core.chinacloudapi.cn/0-public/bookjs/art/index.html";
  490. // var s = _azureStorage.GetBlobSAS($"{_schoolCode}", x.blob, BlobSasPermissions.Read);
  491. // s.fullUri = $"{HttpUtility.UrlEncode($"{s}", Encoding.UTF8)}";
  492. // string url = $"{atrUrl}?url={s.fullUri}";
  493. // urls.Add(url);
  494. //});
  495. string env = ScreenConstant.env_release;
  496. if (_configuration.GetValue<string>("Option:Location").Contains("Test", StringComparison.OrdinalIgnoreCase) ||
  497. _configuration.GetValue<string>("Option:Location").Contains("Dep", StringComparison.OrdinalIgnoreCase))
  498. {
  499. env = ScreenConstant.env_develop;
  500. }
  501. var addData = await GenPDFService.AddGenPdfQueue(_azureRedis,
  502. new GenPDFData
  503. {
  504. env =env,
  505. timeout=30000,
  506. delay=1000,
  507. checkPageCompleteJs=true,
  508. sessionId=$"{_artId}",
  509. taskName = art.name,
  510. taskType="Art",
  511. owner=art.owner,
  512. scope=art.scope,
  513. pageUrl="https://teammodeltest.blob.core.chinacloudapi.cn/0-public/bookjs/art/index.html",
  514. datas= studentPdfs.Select(x => new PDFData{ id= x.studentId, name=x.studentName, url =x.blobFullUrl }).ToList()
  515. });
  516. Console.WriteLine($"{addData.total},{addData.add}");
  517. }
  518. catch (Exception ex)
  519. {
  520. await _dingDing.SendBotMsg($"{ex.Message}{ex.StackTrace}", GroupNames.成都开发測試群組);
  521. }
  522. }
  523. }
  524. public class GenPDFData
  525. {
  526. /// <summary>
  527. /// 数据装载后的页面 要制作为PDF的网页 (pageUrl 、html 参数二选一)
  528. /// </summary>
  529. public string pageUrl { get; set; }
  530. /// <summary>
  531. /// 要截图的网页HTML (pageUrl 、html 参数二选一) "html" : "<div>bookjs-eazy</div>",
  532. /// </summary>
  533. public string html { get; set; }
  534. /// <summary>
  535. /// 超时时间
  536. /// </summary>
  537. public long timeout { get; set; } = 30000;
  538. /// <summary>
  539. /// 页面完成后(checkPageCompleteJs返回为true后)延迟的时间,可选,默认:0
  540. /// </summary>
  541. public long delay { get; set; } = 2000;
  542. /// <summary>
  543. /// // 检查页面是否渲染完成的js表达式,可选,默认: "true"
  544. /// </summary>
  545. public bool checkPageCompleteJs { get; set; }
  546. /// <summary>
  547. /// 生成会话id, 活动id
  548. /// </summary>
  549. public string sessionId { get; set; }
  550. public List<PDFData> datas { get; set; } = new List<PDFData>();
  551. /// <summary>
  552. /// 通知用户
  553. /// </summary>
  554. public List<string> notifyUsers { get; set; } = new List<string>();
  555. /// <summary>
  556. /// 活动的名称
  557. /// </summary>
  558. public string taskName { get; set; }
  559. /// <summary>
  560. /// 艺术评测报告Art,评测报告Exam,问卷报告Survey,投票报告Vote,为空 则无法回调更新状态
  561. /// </summary>
  562. public string taskType { get; set;}
  563. /// <summary>
  564. /// 数据所有者
  565. /// </summary>
  566. public string owner { get; set; }
  567. /// <summary>
  568. /// 数据范围
  569. /// </summary>
  570. public string scope { get; set; }
  571. public string env { get; set; }
  572. }
  573. public class GenPDFSchema
  574. {
  575. public List<string> notifyUsers { get; set; } = new List<string>();
  576. public string taskName { get; set; }
  577. public string taskType { get; set; }
  578. }
  579. public class PDFData{
  580. /// <summary>
  581. /// 学生id
  582. /// </summary>
  583. public string id { get; set; }
  584. /// <summary>
  585. /// 数据链接
  586. /// </summary>
  587. public string url { get; set; }
  588. /// <summary>
  589. /// 学生名称
  590. /// </summary>
  591. public string name { get; set; }
  592. }
  593. /// <summary>
  594. /// redis 存储数据,超时时间设置为status=0 的count timeout* count+ delay*count
  595. /// </summary>
  596. public class PDFGenRedis
  597. {
  598. /// <summary>
  599. /// 学生id
  600. /// </summary>
  601. public string id { get; set; }
  602. /// <summary>
  603. /// 学生名称
  604. /// </summary>
  605. public string name { get; set; }
  606. /// <summary>
  607. /// 执行生成 毫秒
  608. /// </summary>
  609. public long cost { get; set;}
  610. /// <summary>
  611. /// 等候时间
  612. /// </summary>
  613. public long wait { get; set;}
  614. /// <summary>
  615. /// 加入时间
  616. /// </summary>
  617. public long join { get; set; }
  618. /// <summary>
  619. /// 上传成功后的文件
  620. /// </summary>
  621. public string blob { get; set; }
  622. /// <summary>
  623. /// 數據的url
  624. /// </summary>
  625. public string url { get; set; }
  626. /// <summary>
  627. /// 0 未执行,1 执行中,2 执行成功,3 执行失败,4超时,5 取消,6 存放异常
  628. /// </summary>
  629. public int status { get; set; } = 0;
  630. /// <summary>
  631. /// 状态信息
  632. /// </summary>
  633. public string msg { get; set; }
  634. /// <summary>
  635. /// 数据所有者
  636. /// </summary>
  637. public string owner { get; set; }
  638. /// <summary>
  639. /// 数据范围
  640. /// </summary>
  641. public string scope { get; set; }
  642. /// <summary>
  643. /// 环境变量
  644. /// </summary>
  645. public string env { get; set; }
  646. }
  647. /// <summary>
  648. /// // 拼接上接口的前缀 http://localhost:3000/ 就是完整PDF地址
  649. // http://localhost:3000/pdf/1614458263411-glduu.pdf
  650. // 拼接上接口的前缀 http://localhost:3000/download/可以就可生成在浏览器上的下载链接
  651. // http://localhost:3000/download/pdf/1614458263411-glduu.pdf
  652. // 拼接上http://localhost:3000/static/js/pdfjs/web/viewer.html?file=/pdf/1614458263411-glduu.pdf
  653. // 可使用pdfjs库进行预览
  654. //
  655. //## -e MAX_BROWSER=[num] 环境变量可选,最大的puppeteer实例数,忽略选项则默认值:1 , 值auto:[可用内存]/200M
  656. //## -e PDF_KEEP_DAY=[num] 自动删除num天之前产生的文件目录,默认0: 不删除文件
  657. //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
  658. /// </summary>
  659. public class PDFGenQueue
  660. {
  661. /// <summary>
  662. /// 环境变量
  663. /// </summary>
  664. public string env { get; set;}
  665. /// <summary>
  666. /// 姓名
  667. /// </summary>
  668. public string name { get; set; }
  669. /// <summary>
  670. /// 学生id
  671. /// </summary>
  672. public string id { get; set; }
  673. /// <summary>
  674. /// 数据装载后的页面 要制作为PDF的网页 (pageUrl 、html 参数二选一)
  675. /// </summary>
  676. public string pageUrl { get; set; }
  677. /// <summary>
  678. /// 要截图的网页HTML (pageUrl 、html 参数二选一) "html" : "<div>bookjs-eazy</div>",
  679. /// </summary>
  680. public string html { get; set; }
  681. /// <summary>
  682. /// 超时时间
  683. /// </summary>
  684. public long timeout { get; set; } = 30000;
  685. /// <summary>
  686. /// 页面完成后(checkPageCompleteJs返回为true后)延迟的时间,可选,默认:0
  687. /// </summary>
  688. public long delay { get; set; } = 2000;
  689. /// <summary>
  690. /// // 检查页面是否渲染完成的js表达式,可选,默认: "true"
  691. /// </summary>
  692. public bool checkPageCompleteJs { get; set; }
  693. /// <summary>
  694. /// 生成会话id, 活动id
  695. /// </summary>
  696. public string sessionId { get; set; }
  697. /// <summary>
  698. /// blob的sas
  699. /// </summary>
  700. public string blobSas { get; set; }
  701. /// <summary>
  702. /// blob名称
  703. /// </summary>
  704. public string blobName { get; set; }
  705. /// <summary>
  706. /// 容器名称
  707. /// </summary>
  708. public string cntName { get; set; }
  709. /// <summary>
  710. /// 完整blob地址
  711. /// </summary>
  712. public string blobFullUrl { get; set; }
  713. }
  714. public class ScreenClient : ClientDevice
  715. {
  716. /// <summary>
  717. /// 授权类型,bookjs_api
  718. /// </summary>
  719. public string? grant_type { get; set; }
  720. /// <summary>
  721. /// 客户端id
  722. /// </summary>
  723. public string? clientid { get; set; }
  724. /// <summary>
  725. /// SignalR的连接ID 不建议暴露。
  726. /// </summary>
  727. public string? connid { get; set; }
  728. /// <summary>
  729. /// 状态 busy 忙碌,free 空闲,down 离线,error 错误
  730. /// </summary>
  731. public string? status { get; set; }
  732. /// <summary>
  733. /// 最后更新时间
  734. /// </summary>
  735. public long last_time { get; set; }
  736. /// <summary>
  737. /// 任务完成数
  738. /// </summary>
  739. public int taskComplete { get; set; }
  740. }
  741. public class SignalRClient
  742. {
  743. /// <summary>
  744. /// 授权类型,bookjs_api
  745. /// </summary>
  746. public string? grant_type { get; set; }
  747. /// <summary>
  748. /// 客户端id
  749. /// </summary>
  750. public string? clientid { get; set; }
  751. /// <summary>
  752. /// SignalR的连接ID 不建议暴露。
  753. /// </summary>
  754. public string? connid { get; set; }
  755. }
  756. public interface IClient
  757. {
  758. Task ReceiveMessage(MessageBody message);
  759. Task ReceiveConnection(MessageBody message);
  760. Task ReceiveDisConnection(MessageBody message);
  761. }
  762. public abstract class MessageBody
  763. {
  764. public MessageBody()
  765. {
  766. time = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  767. }
  768. /// <summary>
  769. /// 连接id
  770. /// </summary>
  771. public virtual string? connid { get; set; }
  772. /// <summary>
  773. /// 客户端id
  774. /// </summary>
  775. public virtual string? clientid { get; set; }
  776. /// <summary>
  777. /// 状态 busy 忙碌,free 空闲,down 离线,error 错误
  778. /// </summary>
  779. public virtual string? status { get; set; }
  780. /// <summary>
  781. /// 消息内容
  782. /// </summary>
  783. public virtual string? content { get; set; }
  784. /// <summary>
  785. /// 消息创建时间
  786. /// </summary>
  787. public virtual long time { get; }
  788. /// <summary>
  789. /// 授权类型,bookjs_api
  790. /// </summary>
  791. public virtual string? grant_type { get; set; }
  792. /// <summary>
  793. /// 消息类型
  794. /// </summary>
  795. public virtual MessageType message_type { get; set; }
  796. }
  797. /// <summary>
  798. /// 连接消息
  799. /// </summary>
  800. public class ConnectionMessage : MessageBody
  801. {
  802. }
  803. /// <summary>
  804. /// 断开连接消息
  805. /// </summary>
  806. public class DisConnectionMessage : MessageBody
  807. {
  808. }
  809. /// <summary>
  810. /// 业务处理消息
  811. /// </summary>
  812. public class ScreenProcessMessage : MessageBody
  813. {
  814. public string msg { get; set; }
  815. public int result { get; set; }
  816. }
  817. public static class ScreenConstant
  818. {
  819. public static readonly string busy = "busy";
  820. public static readonly string idle = "idle";
  821. public static readonly string error = "error";
  822. public static readonly string offline = "offline";
  823. public static readonly string grant_type = "bookjs_api";
  824. public static readonly string env_release = "release";
  825. public static readonly string env_develop = "develop";
  826. /// <summary>
  827. /// 冗余时间
  828. /// </summary>
  829. public static readonly long time_excess = 5000;
  830. }
  831. public enum MessageType {
  832. conn_success,//连接成功
  833. conn_error,// 连接失败
  834. task_send_success,// 任务发送成功
  835. task_send_error,// 任务发送失败
  836. task_execute_success,// 任务执行成功
  837. task_execute_error,// 任务执行失败
  838. }
  839. public class ClientDevice
  840. {
  841. /// <summary>
  842. /// 机器名
  843. /// </summary>
  844. public string? name { get; set; }
  845. /// <summary>
  846. /// 操作系统
  847. /// </summary>
  848. public string? os { get; set; }
  849. /// <summary>
  850. /// CPU核心数量
  851. /// </summary>
  852. public int cpu { get; set; }
  853. /// <summary>
  854. /// 内存大小
  855. /// </summary>
  856. public long ram { get; set;}
  857. /// <summary>
  858. /// 远程ip
  859. /// </summary>
  860. public string? remote { get; set; }
  861. /// <summary>
  862. /// 端口,可能有多个端口
  863. /// </summary>
  864. public string? port { get; set; }
  865. /// <summary>
  866. /// 地区
  867. /// </summary>
  868. public string? region { get; set; }
  869. /// <summary>
  870. /// 网卡 IP信息
  871. /// </summary>
  872. public List<Network> networks { get; set; } = new List<Network>();
  873. /// <summary>
  874. /// 超时时间,单位毫秒
  875. /// </summary>
  876. public long timeout { get; set; } = 30000;
  877. /// <summary>
  878. /// 延迟时间,单位毫秒
  879. /// </summary>
  880. public long delay { get; set; } = 3000;
  881. /// <summary>
  882. /// PDF服务地址
  883. /// </summary>
  884. public string? screenUrl { get; set; }
  885. }
  886. public class Network
  887. {
  888. public string? name { get; set; }
  889. public string? mac { get; set; }
  890. public string? ip { get; set; }
  891. }
  892. }