pdf_service.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. package pdf
  2. import (
  3. "eta/eta_data_analysis/utils"
  4. "fmt"
  5. "os"
  6. "os/exec"
  7. "path"
  8. "strconv"
  9. "sync"
  10. )
  11. const (
  12. Mobile = "mobile"
  13. PC = "pc"
  14. FreeLayout = "freeLayout"
  15. )
  16. var (
  17. PdfParamsMap = map[string]PDFParams{
  18. PC: {
  19. Top: 20,
  20. Bottom: 20,
  21. Left: 20,
  22. Right: 20,
  23. Width: 1200,
  24. },
  25. Mobile: {
  26. Top: 20,
  27. Bottom: 20,
  28. Left: 20,
  29. Right: 20,
  30. Width: 600,
  31. },
  32. FreeLayout: {
  33. Top: 0,
  34. Bottom: 0,
  35. Left: 0,
  36. Right: 0,
  37. Width: 1200,
  38. },
  39. }
  40. pdfPathTemplate = "./static/%s_%d.pdf"
  41. jpegPathTemplate = "./static/%s_%d.jpg"
  42. )
  43. type PDFParams struct {
  44. Top int
  45. Bottom int
  46. Left int
  47. Right int
  48. Width int
  49. }
  50. func uploadFile(ossClient utils.OssClient, filePath string, fileType string) (resourceUrl string, err error) {
  51. file, err := os.Open(filePath)
  52. if err != nil {
  53. utils.FileLog.Info("Open failed: , error: \n" + err.Error())
  54. return
  55. }
  56. defer func() { _ = file.Close() }()
  57. ext := path.Ext(file.Name())
  58. randStr := utils.GetRandStringNoSpecialChar(28)
  59. fileName := randStr + ext
  60. resourceUrl, err = ossClient.UploadFile(fileName, filePath, fileType)
  61. if err != nil {
  62. utils.FileLog.Info("文件上传失败, Err: \n" + err.Error())
  63. return
  64. }
  65. defer func() {
  66. _ = os.Remove(filePath)
  67. }()
  68. return
  69. }
  70. func generateAndUploadPDF(params PDFParams, reportUrl, pdfPath string, ossClient utils.OssClient) (resourceUrl string, err error) {
  71. if reportUrl == "" {
  72. return
  73. }
  74. err = ReportToPdf(params.Width, reportUrl, pdfPath, params.Top, params.Bottom, params.Left, params.Right)
  75. if err != nil {
  76. utils.FileLog.Info("ReportToPdf failed: , error: \n" + err.Error())
  77. return
  78. }
  79. resourceUrl, err = uploadFile(ossClient, pdfPath, "pdf")
  80. if err != nil {
  81. utils.FileLog.Info("文件上传失败, Err: \n" + err.Error())
  82. return
  83. }
  84. return
  85. }
  86. func generateAndUploadJPEG(params PDFParams, reportUrl, pdfPath string, ossClient utils.OssClient) (resourceUrl string, err error) {
  87. if reportUrl == "" {
  88. return
  89. }
  90. err = ReportToJpeg(params.Width, reportUrl, pdfPath)
  91. if err != nil {
  92. utils.FileLog.Info("ReportToJpeg failed: , error: \n" + err.Error())
  93. return
  94. }
  95. resourceUrl, err = uploadFile(ossClient, pdfPath, "jpg")
  96. if err != nil {
  97. utils.FileLog.Info("文件上传失败, Err: \n" + err.Error())
  98. return
  99. }
  100. return
  101. }
  102. func Report2pdfAndJpeg(reportUrl string, reportId int, ReportLayout string) (pdfUrl, jpegUrl string, err error) {
  103. var params PDFParams
  104. if _, ok := PdfParamsMap[ReportLayout]; !ok {
  105. err = fmt.Errorf("报告类型 %s 不存在,生成PDF失败", ReportLayout)
  106. } else {
  107. params = PdfParamsMap[ReportLayout]
  108. }
  109. reportCode := utils.MD5(strconv.Itoa(reportId))
  110. pdfPath := fmt.Sprintf(pdfPathTemplate, reportCode, params.Width)
  111. jpegPath := fmt.Sprintf(jpegPathTemplate, reportCode, params.Width)
  112. ossClient, err := utils.OssClientHandler(utils.OssType)
  113. if err != nil {
  114. utils.FileLog.Info("获取OSS客户端失败")
  115. err = fmt.Errorf("获取OSS客户端未初始化,生成PDF失败")
  116. }
  117. defer func() {
  118. if err != nil {
  119. utils.FileLog.Info("生成PDF失败,error: \n" + err.Error())
  120. }
  121. }()
  122. // 并发执行
  123. var wg sync.WaitGroup
  124. wg.Add(2)
  125. pdfChan := make(chan result, 1)
  126. jpegChan := make(chan result, 1)
  127. go func() {
  128. defer wg.Done()
  129. url, pdfErr := generateAndUploadPDF(params, reportUrl, pdfPath, ossClient)
  130. pdfChan <- result{url, pdfErr}
  131. }()
  132. go func() {
  133. defer wg.Done()
  134. url, jpgErr := generateAndUploadJPEG(params, reportUrl, jpegPath, ossClient)
  135. jpegChan <- result{url, jpgErr}
  136. }()
  137. wg.Wait()
  138. close(pdfChan)
  139. close(jpegChan)
  140. pdfResult := <-pdfChan
  141. jpegResult := <-jpegChan
  142. if pdfResult.err != nil || jpegResult.err != nil {
  143. utils.FileLog.Info("生成PDF失败,error: \n" + pdfResult.err.Error() + jpegResult.err.Error())
  144. err = fmt.Errorf("生成PDF失败,error: \n" + pdfResult.err.Error() + jpegResult.err.Error())
  145. return
  146. }
  147. pdfUrl, jpegUrl = pdfResult.url, jpegResult.url
  148. return
  149. }
  150. type result struct {
  151. url string
  152. err error
  153. }
  154. func ReportToPdf(width int, reportUrl, filePath string, top, bottom, left, right int) (err error) {
  155. pyCode := `
  156. import asyncio
  157. from pyppeteer import launch
  158. @asyncio.coroutine
  159. async def main():
  160. # 异步代码
  161. browser = await launch({
  162. 'executablePath': '%s',
  163. 'headless': True,
  164. 'args': ['--disable-infobars', '--no-sandbox']
  165. })
  166. page = await browser.newPage()
  167. await page.setViewport({
  168. 'width': %d,
  169. 'height': 1697
  170. })
  171. await page.goto('%s', {
  172. 'waitUntil': 'networkidle0',
  173. 'timeout': 3000000 # 设置超时时间为 100 秒
  174. })
  175. # 在生成PDF之前等待2秒
  176. await asyncio.sleep(15)
  177. await page.pdf({
  178. 'width': %d,
  179. 'height': 1697,
  180. 'path': "%s",
  181. 'printBackground': True,
  182. 'margin': {
  183. 'top': '%dpx',
  184. 'bottom': '%dpx',
  185. 'left': '%dpx',
  186. 'right': '%dpx'
  187. }
  188. })
  189. await browser.close()
  190. # 创建事件循环
  191. loop = asyncio.get_event_loop()
  192. # 使用事件循环运行main函数
  193. try:
  194. loop.run_until_complete(main())
  195. finally:
  196. # 关闭事件循环
  197. loop.close()
  198. `
  199. pyCode = fmt.Sprintf(pyCode, utils.ChromePath, width, reportUrl, width+left+right, filePath, top, bottom, left, right)
  200. utils.FileLog.Info("pdf pyCode: \n" + pyCode)
  201. cmd := exec.Command(utils.CommandPython, "-c", pyCode)
  202. output, e := cmd.CombinedOutput()
  203. if e != nil {
  204. err = e
  205. fmt.Println("ReportToPdf failed: , error:" + string(output))
  206. utils.FileLog.Info("ReportToPdf failed: , error: \n" + err.Error())
  207. utils.FileLog.Info("Output: %s\n", string(output))
  208. }
  209. defer func() {
  210. cmd.Process.Kill()
  211. }()
  212. return
  213. }
  214. func ReportToJpeg(width int, reportUrl, filePath string) (err error) {
  215. pyCode := `
  216. import asyncio
  217. from pyppeteer import launch, errors
  218. async def main():
  219. try:
  220. # 启动浏览器
  221. browser = await launch({
  222. 'executablePath': '%s',
  223. 'headless': True,
  224. 'args': ['--disable-infobars', '--no-sandbox']
  225. })
  226. # 新建页面
  227. page = await browser.newPage()
  228. # 设置视口大小
  229. await page.setViewport({
  230. 'width': %d,
  231. 'height': 1697
  232. })
  233. # 导航到页面
  234. await page.goto('%s', {
  235. 'waitUntil': 'networkidle0',
  236. 'timeout': 3000000 # 设置超时时间为 100 秒
  237. })
  238. # Customizing footer for page numbers starting from page 2
  239. # 在这里添加两秒的等待
  240. await asyncio.sleep(5)
  241. await page.screenshot({
  242. 'path': "%s",
  243. 'fullPage': True,
  244. 'quality':80
  245. })
  246. except errors.BrowserError as e:
  247. print('Browser closed unexpectedly:', e)
  248. except Exception as e:
  249. print('An error occurred:', e)
  250. finally:
  251. # 确保浏览器关闭
  252. if browser is not None:
  253. await browser.close()
  254. # 获取当前事件循环
  255. loop = asyncio.get_event_loop()
  256. # 运行事件循环直到main协程完成
  257. try:
  258. loop.run_until_complete(main())
  259. except Exception as e:
  260. print('Error during event loop execution:', e)
  261. finally:
  262. # 关闭事件循环
  263. loop.close()
  264. `
  265. pyCode = fmt.Sprintf(pyCode, utils.ChromePath, width, reportUrl, filePath)
  266. utils.FileLog.Info("jpeg pyCode: \n" + pyCode)
  267. cmd := exec.Command(utils.CommandPython, "-c", pyCode)
  268. output, e := cmd.CombinedOutput()
  269. if e != nil {
  270. err = e
  271. utils.FileLog.Info("ReportToJpeg failed: , error: \n" + err.Error())
  272. utils.FileLog.Info("Output: %s\n", string(output))
  273. }
  274. defer func() {
  275. _ = cmd.Process.Kill()
  276. }()
  277. return
  278. }