ProbeTask.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. using PuppeteerSharp;
  2. namespace StationProbe
  3. {
  4. internal enum ElementType
  5. {
  6. Page,
  7. Frame
  8. }
  9. internal static class ElementConverter
  10. {
  11. public static T? As<T>(this object obj) where T : class
  12. {
  13. return obj as T;
  14. }
  15. }
  16. internal class ElementCollection
  17. {
  18. private readonly Dictionary<ElementType, object> _objects = new();
  19. public object this[ElementType type] => _objects[type];
  20. public void AddObject(ElementType element, object obj)
  21. {
  22. _objects.Add(element, obj);
  23. }
  24. }
  25. internal interface IExamStore:IDisposable
  26. {
  27. Exam? GetLastExam(int batchTaskId);
  28. void AddExam(Exam exam);
  29. void AddImage(Image image);
  30. void UpdateBatchTask(BatchTask batchTask);
  31. }
  32. internal abstract class ProbeTask
  33. {
  34. public static bool DebugMode { get; set; }
  35. private static string _debugFolder = string.Empty;
  36. protected ScreenshotOptions _snapshotOpts = new ScreenshotOptions() { Type = ScreenshotType.Png };
  37. protected readonly IExamStore _examStore;
  38. public ProbeTask(IExamStore examStore)
  39. {
  40. _examStore = examStore;
  41. }
  42. public static void InitDebugFolder()
  43. {
  44. var drives = DriveInfo.GetDrives();
  45. var largestDriver = drives.OrderByDescending(x => x.TotalFreeSpace).First();
  46. var debugFolder = largestDriver.Name + "debug";
  47. if (!Directory.Exists(debugFolder))
  48. {
  49. Directory.CreateDirectory(debugFolder);
  50. }
  51. var i = 1;
  52. while (Directory.Exists(Path.Combine(debugFolder, i.ToString("D6"))))
  53. {
  54. i++;
  55. }
  56. _debugFolder = Path.Combine(debugFolder, i.ToString("D6"));
  57. Directory.CreateDirectory(_debugFolder);
  58. }
  59. public void SaveDebugSnapshot(string name, byte[] imageData)
  60. {
  61. if (Directory.Exists(_debugFolder))
  62. {
  63. var file = Path.Combine(_debugFolder, name);
  64. File.WriteAllBytes(file, imageData);
  65. }
  66. }
  67. public abstract Task<ElementCollection> ExecuteAsync(ElementCollection inputs);
  68. }
  69. internal class LoginTask : ProbeTask
  70. {
  71. private readonly string _loginUrl;
  72. public LoginTask(IExamStore examStore, string loginUrl) : base(examStore)
  73. {
  74. _loginUrl = loginUrl;
  75. }
  76. public override async Task<ElementCollection> ExecuteAsync(ElementCollection inputs)
  77. {
  78. var page = inputs[ElementType.Page].As<IPage>();
  79. if (page != null)
  80. {
  81. await page.GoToAsync(_loginUrl);
  82. if (DebugMode)
  83. {
  84. var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
  85. var fileName = "100000000.jpg";
  86. SaveDebugSnapshot(fileName, imageData);
  87. }
  88. //username
  89. await using (var userElement = await page.WaitForSelectorAsync("input[id=\"userName\"]"))
  90. {
  91. await userElement.FocusAsync();
  92. await page.Keyboard.TypeAsync("justin.xing");
  93. }
  94. if (DebugMode)
  95. {
  96. var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
  97. var fileName = "200000000.jpg";
  98. SaveDebugSnapshot(fileName, imageData);
  99. }
  100. //password
  101. await using (var passwordElement = await page.WaitForSelectorAsync("input[id=\"password\"]"))
  102. {
  103. await passwordElement.FocusAsync();
  104. await page.Keyboard.TypeAsync("vinno@123");
  105. }
  106. if (DebugMode)
  107. {
  108. var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
  109. var fileName = "300000000.jpg";
  110. SaveDebugSnapshot(fileName, imageData);
  111. }
  112. //login
  113. await using (var loginButtonElement = await page.WaitForSelectorAsync("button[b-kvd270uhz9]"))
  114. {
  115. await loginButtonElement.ClickAsync();
  116. await page.WaitForNetworkIdleAsync();
  117. }
  118. if (DebugMode)
  119. {
  120. var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
  121. var fileName = "400000000.jpg";
  122. SaveDebugSnapshot(fileName, imageData);
  123. }
  124. }
  125. return inputs;
  126. }
  127. }
  128. internal class MenuTask : ProbeTask
  129. {
  130. public MenuTask(IExamStore examStore) : base(examStore)
  131. {
  132. }
  133. public override async Task<ElementCollection> ExecuteAsync(ElementCollection inputs)
  134. {
  135. Logger.WriteLine("Finding menu...");
  136. var page = inputs[ElementType.Page].As<IPage>();
  137. if (page != null)
  138. {
  139. await using (var menuElement = await page.WaitForSelectorAsync("a[href=\"feedback/list\"]"))
  140. {
  141. await menuElement.ClickAsync();
  142. if (DebugMode)
  143. {
  144. var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
  145. var fileName = "500000000.jpg";
  146. SaveDebugSnapshot(fileName, imageData);
  147. }
  148. }
  149. }
  150. return inputs;
  151. }
  152. }
  153. internal class FilterTask : ProbeTask
  154. {
  155. private readonly DateTime _start;
  156. private readonly DateTime _end;
  157. public int PageCount { get; private set; }
  158. public int ExamCount { get; private set; }
  159. public FilterTask(IExamStore examStore, DateTime start, DateTime end) : base(examStore)
  160. {
  161. _start = start;
  162. _end = end;
  163. }
  164. public override async Task<ElementCollection> ExecuteAsync(ElementCollection inputs)
  165. {
  166. var page = inputs[ElementType.Page].As<IPage>();
  167. //var frame = inputs[ElementType.Frame].As<IFrame>();
  168. if (page != null) //&& frame != null)
  169. {
  170. //var filterElements = await page.QuerySelectorAllAsync("div[class=\"me-3 my-1\"]");
  171. //var filterElement1 = filterElements[3];
  172. //var filterElement2 = filterElements[4];
  173. ////show filter dialog
  174. //if (DebugMode)
  175. //{
  176. // var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
  177. // var fileName = "600000000.jpg";
  178. // SaveDebugSnapshot(fileName, imageData);
  179. //}
  180. ////input start
  181. //var openElement1 = await filterElement1.WaitForSelectorAsync("div[class=\"multi-select\"]");
  182. //await openElement1.ClickAsync();
  183. //var optionListElement1 = await filterElement1.WaitForSelectorAsync("div[class=\"dropdown-menu shadow\"]");
  184. //var optionElements = await optionListElement1.QuerySelectorAllAsync("div[class=\"dropdown-item\"]");
  185. //var targetElement = optionElements[0];
  186. //await targetElement.ClickAsync();
  187. //if (DebugMode)
  188. //{
  189. // var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
  190. // var fileName = "700000000.jpg";
  191. // SaveDebugSnapshot(fileName, imageData);
  192. //}
  193. //var openlement2 = await filterElement2.WaitForSelectorAsync("div[class=\"multi-select\"]");
  194. //await openlement2.ClickAsync();
  195. //var optionListElement2 = await filterElement2.WaitForSelectorAsync("div[class=\"dropdown-menu shadow\"]");
  196. //optionElements = await optionListElement2.QuerySelectorAllAsync("div[class=\"dropdown-item\"]");
  197. //targetElement = optionElements[0];
  198. //await targetElement.ClickAsync();
  199. ////input end
  200. //if (DebugMode)
  201. //{
  202. // var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
  203. // var fileName = "800000000.jpg";
  204. // SaveDebugSnapshot(fileName, imageData);
  205. //}
  206. //var searchTableElement = await page.QuerySelectorAsync("div[class=\"table-search\"]");
  207. //var searchButtonElement = await searchTableElement.QuerySelectorAsync("button[class=\"btn btn-primary btn-xs ms-2\"]");
  208. //await searchButtonElement.ClickAsync();
  209. //await page.WaitForNetworkIdleAsync();
  210. //if (DebugMode)
  211. //{
  212. // var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
  213. // var fileName = "900000000.jpg";
  214. // SaveDebugSnapshot(fileName, imageData);
  215. //}
  216. await using (var pageinationElement = await page.QuerySelectorAsync("div[class=\"table-pagination\"]"))
  217. {
  218. await using (var examCountElement = await pageinationElement.QuerySelectorAsync("div[class=\"d-none d-sm-inline-block\"]"))
  219. {
  220. var examCountStr = await page.EvaluateFunctionAsync<string>("(element) => element.textContent", examCountElement);
  221. var words = examCountStr.Split(' ');
  222. ExamCount = int.Parse(words[1]);
  223. }
  224. }
  225. await using (var pagesElement = await page.QuerySelectorAsync("ul[class=\"pagination\"]"))
  226. {
  227. var pageItemElements = await pagesElement.QuerySelectorAllAsync("li");
  228. var lastPageElement = pageItemElements[pageItemElements.Length - 2];
  229. await using (var textElement = await lastPageElement.QuerySelectorAsync("a"))
  230. {
  231. var lastPageStr = await page.EvaluateFunctionAsync<string>("(element) => element.textContent", textElement);
  232. PageCount = int.Parse(lastPageStr);
  233. }
  234. foreach (var pageItemElement in pageItemElements)
  235. {
  236. await pageItemElement.DisposeAsync();
  237. }
  238. }
  239. }
  240. return inputs;
  241. }
  242. }
  243. internal class PageTask : ProbeTask
  244. {
  245. private readonly int _page;
  246. public PageTask(IExamStore examStore, int page) : base(examStore)
  247. {
  248. _page = page;
  249. }
  250. private async Task JumpToPageAsync(IPage page)
  251. {
  252. while (true)
  253. {
  254. await using (var pagesElement = await page.WaitForSelectorAsync("ul[class=\"pagination\"]"))
  255. {
  256. var pageItemElements = await pagesElement.QuerySelectorAllAsync("li");
  257. await using (var currentPageElemment = await pagesElement.QuerySelectorAsync("li[class=\"page-item active\"]"))
  258. {
  259. await using (var textElement = await currentPageElemment.QuerySelectorAsync("a"))
  260. {
  261. var currentPageStr = await page.EvaluateFunctionAsync<string>("(element) => element.textContent", textElement);
  262. var currentPage = int.Parse(currentPageStr);
  263. if (currentPage != _page + 1)
  264. {
  265. var nextPageElement = pageItemElements[pageItemElements.Length - 1];
  266. await nextPageElement.ClickAsync();
  267. foreach(var pageItemElement in pageItemElements)
  268. {
  269. await pageItemElement.DisposeAsync();
  270. }
  271. }
  272. else
  273. {
  274. foreach (var pageItemElement in pageItemElements)
  275. {
  276. await pageItemElement.DisposeAsync();
  277. }
  278. break;
  279. }
  280. }
  281. }
  282. }
  283. await Task.Delay(DelayConfig.JumpDelay);
  284. }
  285. }
  286. public override async Task<ElementCollection> ExecuteAsync(ElementCollection inputs)
  287. {
  288. var page = inputs[ElementType.Page].As<IPage>();
  289. //var frame = inputs[ElementType.Frame].As<IFrame>();
  290. if (page != null) //&& frame != null)
  291. {
  292. await JumpToPageAsync(page);
  293. if (DebugMode)
  294. {
  295. var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
  296. var fileName = "9" + _page.ToString("D5") + "000.jpg";
  297. SaveDebugSnapshot(fileName, imageData);
  298. }
  299. }
  300. return inputs;
  301. }
  302. }
  303. internal class ExamTask : ProbeTask
  304. {
  305. private readonly int _batchTaskId;
  306. private readonly int _pageIndex;
  307. private readonly int _examIndex;
  308. private readonly int _examIndexInPage;
  309. public ExamTask(IExamStore examStore, int batchTaskId, int pageIndex, int examIndex, int examIndexInPage) : base(examStore)
  310. {
  311. _batchTaskId = batchTaskId;
  312. _pageIndex = pageIndex;
  313. _examIndex = examIndex;
  314. _examIndexInPage = examIndexInPage;
  315. }
  316. private async Task JumpToPageAsync(IPage page)
  317. {
  318. while (true)
  319. {
  320. await using (var pagesElement = await page.WaitForSelectorAsync("ul[class=\"pagination\"]"))
  321. {
  322. var pageItemElements = await pagesElement.QuerySelectorAllAsync("li");
  323. await using (var currentPageElemment = await pagesElement.QuerySelectorAsync("li[class=\"page-item active\"]"))
  324. {
  325. await using (var textElement = await currentPageElemment.QuerySelectorAsync("a"))
  326. {
  327. var currentPageStr = await page.EvaluateFunctionAsync<string>("(element) => element.textContent", textElement);
  328. var currentPage = int.Parse(currentPageStr);
  329. if (currentPage != _pageIndex + 1)
  330. {
  331. var nextPageElement = pageItemElements[pageItemElements.Length - 1];
  332. await nextPageElement.ClickAsync();
  333. foreach (var pageItemElement in pageItemElements)
  334. {
  335. await pageItemElement.DisposeAsync();
  336. }
  337. }
  338. else
  339. {
  340. foreach (var pageItemElement in pageItemElements)
  341. {
  342. await pageItemElement.DisposeAsync();
  343. }
  344. break;
  345. }
  346. }
  347. }
  348. }
  349. await Task.Delay(DelayConfig.JumpDelay);
  350. }
  351. }
  352. private async Task EnterExamAsync(IPage page)
  353. {
  354. await JumpToPageAsync(page);
  355. await using (var tableElement = await page.QuerySelectorAsync("table[class=\"table table-bordered table-striped table-hover\"]"))
  356. {
  357. var examLinkElements = await tableElement.QuerySelectorAllAsync("div[style=\"cursor:pointer;\"]");
  358. var eaxmLinkElement = examLinkElements[_examIndexInPage];
  359. await eaxmLinkElement.ClickAsync();
  360. foreach(var examLinkElement in examLinkElements)
  361. {
  362. await examLinkElement.DisposeAsync();
  363. }
  364. }
  365. }
  366. private async Task ExitExamAsync(IPage page)
  367. {
  368. //Wait for the back button.
  369. await using (var backButtonElement = await page.WaitForSelectorAsync("button[class=\"btn fix-item feedback-common-button\"]"))
  370. {
  371. await page.EvaluateFunctionAsync("b => b.click()", backButtonElement);
  372. }
  373. }
  374. private async void DoSaveExamImageAsync(IPage page, string examId, int imageIndex, string imageUrl, ManualResetEvent finishEvent)
  375. {
  376. var response = await page.WaitForResponseAsync(imageUrl);
  377. var imageData = await response.BufferAsync();
  378. var image = new Image()
  379. {
  380. Id = Guid.NewGuid().ToString("N"),
  381. ExamId = examId,
  382. ImageIndex = imageIndex,
  383. Data = imageData,
  384. CreateTime = DateTime.Now,
  385. };
  386. _examStore.AddImage(image);
  387. Logger.WriteLine($"Image:{imageUrl} stored.");
  388. finishEvent.Set();
  389. }
  390. private async Task SaveExamImageAsync(IPage page, string examId, int imageIndex, string imageUrl)
  391. {
  392. await using (var imagePage = await page.Browser.NewPageAsync())
  393. {
  394. var finishEvent = new ManualResetEvent(false);
  395. finishEvent.Reset();
  396. DoSaveExamImageAsync(imagePage, examId, imageIndex, imageUrl, finishEvent);
  397. await imagePage.GoToAsync(imageUrl);
  398. finishEvent.WaitOne();
  399. finishEvent.Dispose();
  400. }
  401. }
  402. private async Task<List<string>> GetImageUrlsAsync(IPage page)
  403. {
  404. //Get all image urls.
  405. var imageUrls = new List<string>();
  406. await using (var backButtonElement = await page.WaitForSelectorAsync("button[class=\"btn fix-item feedback-common-button\"]"))
  407. {
  408. var imageDivElements = await page.QuerySelectorAllAsync("div[class=\"bb-img is-preview\"]");
  409. if (imageDivElements != null)
  410. {
  411. foreach (var imageDivElement in imageDivElements)
  412. {
  413. await using (var imageElement = await imageDivElement.QuerySelectorAsync("img"))
  414. {
  415. await using (var imageUrl = await imageElement.GetPropertyAsync("src"))
  416. {
  417. var url = await imageUrl.JsonValueAsync<string>();
  418. imageUrls.Add(url);
  419. }
  420. }
  421. await imageDivElement.DisposeAsync();
  422. }
  423. }
  424. }
  425. Logger.WriteLine($"Get image url count:{imageUrls.Count}");
  426. return imageUrls;
  427. }
  428. private async Task<string> GetReportAsync(IPage page)
  429. {
  430. var report = "";
  431. var reportElements = await page.QuerySelectorAllAsync("div[class=\"col\"]");
  432. foreach (var reportElement in reportElements)
  433. {
  434. await using (var strong = await reportElement.QuerySelectorAsync("strong"))
  435. {
  436. if (strong != null)
  437. {
  438. var content = await page.EvaluateFunctionAsync<string>("(element) => element.textContent", strong);
  439. report += " " + content;
  440. }
  441. }
  442. await reportElement.DisposeAsync();
  443. }
  444. Logger.WriteLine($"Get report:{report}");
  445. return report;
  446. }
  447. public override async Task<ElementCollection> ExecuteAsync(ElementCollection inputs)
  448. {
  449. var page = inputs[ElementType.Page].As<IPage>();
  450. //var frame = inputs[ElementType.Frame].As<IFrame>();
  451. if (page != null) //&& frame != null)
  452. {
  453. var examId = Guid.NewGuid().ToString("N");
  454. //First time enter exam.
  455. await EnterExamAsync(page);
  456. //wait for page loaded.
  457. await Task.Delay(DelayConfig.ExamContentDelay);
  458. var imageUrls = await GetImageUrlsAsync(page);
  459. for (var i = 0; i < imageUrls.Count; i++)
  460. {
  461. await SaveExamImageAsync(page, examId, i, imageUrls[i]);
  462. //Do not save to fast to avoid network trouble.
  463. await Task.Delay(DelayConfig.SaveImageDelay);
  464. }
  465. if (DebugMode)
  466. {
  467. var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
  468. var fileName = "9" + _pageIndex.ToString("D5") + _examIndexInPage.ToString("D3") + ".jpg";
  469. SaveDebugSnapshot(fileName, imageData);
  470. }
  471. //Get patient info
  472. //var patientName = "";
  473. var patientSex = "";
  474. var patientAge = 0;
  475. //var examDate = DateTime.Now;
  476. var report = await GetReportAsync(page);
  477. //Save exam.
  478. var exam = new Exam()
  479. {
  480. Id = examId,
  481. PatientName = "",
  482. PatientSex = patientSex,
  483. PatientAge = patientAge,
  484. ExamDate = DateTime.Now,
  485. Report = report,
  486. BatchTaskId = _batchTaskId,
  487. ExamIndex = _examIndex,
  488. PageIndex = _pageIndex,
  489. ExamIndexInPage = _examIndexInPage,
  490. CreateTime = DateTime.Now
  491. };
  492. _examStore.AddExam(exam);
  493. //Back to examl list page.
  494. await ExitExamAsync(page);
  495. if (DebugMode)
  496. {
  497. var imageData = await page.ScreenshotDataAsync(_snapshotOpts);
  498. var fileName = "9" + _pageIndex.ToString("D5") + "999.jpg";
  499. SaveDebugSnapshot(fileName, imageData);
  500. }
  501. }
  502. return inputs;
  503. }
  504. }
  505. }