index.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. const fs = require('fs')
  2. const path = require('path')
  3. const compressing = require('compressing')
  4. const rimrif = require('rimraf')
  5. const shell = require('shelljs');
  6. const crypto = require('crypto');
  7. const { LibreOffice } = require("chromiumly");
  8. const tempDir = path.join(__dirname , "temp")
  9. const OSS = require("ali-oss");
  10. const ALI_OSS_BUCKET = process.env.ALI_OSS_BUCKET || "hep-textbook"
  11. const ALI_OSS_ACCESS_KEY_ID = process.env.ALI_OSS_ACCESS_KEY_ID || "LTAI5t6AbTiAvXmeoVdJZhL3"
  12. const ALI_OSS_ACCESS_KEY_SECRET = process.env.ALI_OSS_ACCESS_KEY_SECRET || "KLtQRdIW69KLP7jnzHNUf7eKmdptxH"
  13. export async function uploadFileToOSS(filePath){
  14. let client = new OSS({
  15. // yourRegion填写Bucket所在地域。以华东1(杭州)为例,yourRegion填写为oss-cn-hangzhou。
  16. region: "oss-cn-beijing",
  17. accessKeyId: ALI_OSS_ACCESS_KEY_ID,
  18. accessKeySecret: ALI_OSS_ACCESS_KEY_SECRET,
  19. // 填写Bucket名称。
  20. bucket: ALI_OSS_BUCKET || "hep-textbook",
  21. });
  22. let now = new Date();
  23. let fileName = getFileName(filePath);
  24. let fileKey = `export/report/${fileName}`;
  25. const r1 = await client?.put(fileKey, filePath);
  26. console.log('put success: %j', r1);
  27. return r1
  28. }
  29. export function getFileName(filePath) {
  30. // 使用 '/' 或 '\' 作为分隔符,分割路径
  31. const parts = filePath.split(/[/\\]/);
  32. // 返回最后一个部分,即文件名
  33. return parts.pop();
  34. }
  35. module.exports.uploadFileToOSS = uploadFileToOSS
  36. /**
  37. * 将给定的文件路径数组打包成指定名称的zip压缩包
  38. * @param {Array<string>} filePathList - 要打包的文件路径数组
  39. * @param {string} outputZipName - 输出的zip文件名称
  40. */
  41. export function createZip(filePathList, outputZipName) {
  42. let zipStream = new compressing.zip.Stream();
  43. return new Promise((resolve)=>{
  44. try {
  45. let outputPath = path.join(tempDir,outputZipName)
  46. // 遍历文件路径列表,将每个文件添加到zip流中
  47. for (const filePath of filePathList) {
  48. // 检查文件是否存在
  49. if (fs.existsSync(filePath)) {
  50. // 将文件添加到zip流中
  51. zipStream.addEntry(filePath);
  52. } else {
  53. console.error(`文件不存在: ${filePath}`);
  54. }
  55. }
  56. // 创建一个写入流
  57. const output = fs.createWriteStream(outputPath);
  58. // 使用 compressing 库的 zip 方法将文件打包
  59. console.log(filePathList)
  60. // await compressing.zip.compressDir(filePathList, output);
  61. // 将zip流写入文件
  62. zipStream.pipe(output);
  63. output.on('finish', () => {
  64. console.log(`成功创建压缩包: ${outputPath}`);
  65. resolve(outputPath)
  66. });
  67. output.on('error', (error) => {
  68. console.error('写入压缩包时出错:', error);
  69. resolve(null)
  70. });
  71. // console.log(`成功创建压缩包: ${outputPath}`);
  72. // return outputPath
  73. } catch (error) {
  74. console.error('创建压缩包时出错:', error);
  75. return null
  76. }
  77. })
  78. }
  79. module.exports.createZip = createZip
  80. /**
  81. * 将 DOCX 文件转换为 PDF
  82. *
  83. * @param {string} docxPath - 要转换的 DOCX 文件的路径
  84. * @param {string} outputPath - 输出 PDF 文件的路径
  85. * @returns {Promise<void>}
  86. */
  87. export async function docsToPdf(docxPath, outputPath) {
  88. try {
  89. const files = [docxPath];
  90. const pdfBuffer = await LibreOffice.convert({
  91. files,
  92. properties: {
  93. // 你可以在这里设置页面属性
  94. },
  95. pdfa: false, // PDF/A 选项
  96. pdfUA: false, // PDF/UA 选项
  97. merge: false, // 是否合并PDF
  98. metadata: {
  99. // 你可以在这里添加元数据
  100. },
  101. // losslessImageCompression: false,
  102. // reduceImageResolution: false,
  103. // quality: 90, // JPG 导出质量
  104. // maxImageResolution: 300 // 最大图像分辨率
  105. });
  106. // 将 Buffer 写入输出文件
  107. fs.writeFileSync(outputPath, pdfBuffer);
  108. console.log(`成功将 <span class="mathjax raw" style="margin-left:10px;margin-right:10px;"><svg style="vertical-align: -0.452ex;" xmlns="http://www.w3.org/2000/svg" width="16.351ex" height="2.149ex" role="img" focusable="false" viewBox="0 -750 7227 950" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="MJX-1-TEX-I-1D451" d="M366 683Q367 683 438 688T511 694Q523 694 523 686Q523 679 450 384T375 83T374 68Q374 26 402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487H491Q506 153 506 145Q506 140 503 129Q490 79 473 48T445 8T417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157Q33 205 53 255T101 341Q148 398 195 420T280 442Q336 442 364 400Q369 394 369 396Q370 400 396 505T424 616Q424 629 417 632T378 637H357Q351 643 351 645T353 664Q358 683 366 683ZM352 326Q329 405 277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q233 26 290 98L298 109L352 326Z"></path><path id="MJX-1-TEX-I-1D45C" d="M201 -11Q126 -11 80 38T34 156Q34 221 64 279T146 380Q222 441 301 441Q333 441 341 440Q354 437 367 433T402 417T438 387T464 338T476 268Q476 161 390 75T201 -11ZM121 120Q121 70 147 48T206 26Q250 26 289 58T351 142Q360 163 374 216T388 308Q388 352 370 375Q346 405 306 405Q243 405 195 347Q158 303 140 230T121 120Z"></path><path id="MJX-1-TEX-I-1D450" d="M34 159Q34 268 120 355T306 442Q362 442 394 418T427 355Q427 326 408 306T360 285Q341 285 330 295T319 325T330 359T352 380T366 386H367Q367 388 361 392T340 400T306 404Q276 404 249 390Q228 381 206 359Q162 315 142 235T121 119Q121 73 147 50Q169 26 205 26H209Q321 26 394 111Q403 121 406 121Q410 121 419 112T429 98T420 83T391 55T346 25T282 0T202 -11Q127 -11 81 37T34 159Z"></path><path id="MJX-1-TEX-I-1D465" d="M52 289Q59 331 106 386T222 442Q257 442 286 424T329 379Q371 442 430 442Q467 442 494 420T522 361Q522 332 508 314T481 292T458 288Q439 288 427 299T415 328Q415 374 465 391Q454 404 425 404Q412 404 406 402Q368 386 350 336Q290 115 290 78Q290 50 306 38T341 26Q378 26 414 59T463 140Q466 150 469 151T485 153H489Q504 153 504 145Q504 144 502 134Q486 77 440 33T333 -11Q263 -11 227 52Q186 -10 133 -10H127Q78 -10 57 16T35 71Q35 103 54 123T99 143Q142 143 142 101Q142 81 130 66T107 46T94 41L91 40Q91 39 97 36T113 29T132 26Q168 26 194 71Q203 87 217 139T245 247T261 313Q266 340 266 352Q266 380 251 392T217 404Q177 404 142 372T93 290Q91 281 88 280T72 278H58Q52 284 52 289Z"></path><path id="MJX-1-TEX-I-1D443" d="M287 628Q287 635 230 637Q206 637 199 638T192 648Q192 649 194 659Q200 679 203 681T397 683Q587 682 600 680Q664 669 707 631T751 530Q751 453 685 389Q616 321 507 303Q500 302 402 301H307L277 182Q247 66 247 59Q247 55 248 54T255 50T272 48T305 46H336Q342 37 342 35Q342 19 335 5Q330 0 319 0Q316 0 282 1T182 2Q120 2 87 2T51 1Q33 1 33 11Q33 13 36 25Q40 41 44 43T67 46Q94 46 127 49Q141 52 146 61Q149 65 218 339T287 628ZM645 554Q645 567 643 575T634 597T609 619T560 635Q553 636 480 637Q463 637 445 637T416 636T404 636Q391 635 386 627Q384 621 367 550T332 412T314 344Q314 342 395 342H407H430Q542 342 590 392Q617 419 631 471T645 554Z"></path><path id="MJX-1-TEX-I-1D44E" d="M33 157Q33 258 109 349T280 441Q331 441 370 392Q386 422 416 422Q429 422 439 414T449 394Q449 381 412 234T374 68Q374 43 381 35T402 26Q411 27 422 35Q443 55 463 131Q469 151 473 152Q475 153 483 153H487Q506 153 506 144Q506 138 501 117T481 63T449 13Q436 0 417 -8Q409 -10 393 -10Q359 -10 336 5T306 36L300 51Q299 52 296 50Q294 48 292 46Q233 -10 172 -10Q117 -10 75 30T33 157ZM351 328Q351 334 346 350T323 385T277 405Q242 405 210 374T160 293Q131 214 119 129Q119 126 119 118T118 106Q118 61 136 44T179 26Q217 26 254 59T298 110Q300 114 325 217T351 328Z"></path><path id="MJX-1-TEX-I-1D461" d="M26 385Q19 392 19 395Q19 399 22 411T27 425Q29 430 36 430T87 431H140L159 511Q162 522 166 540T173 566T179 586T187 603T197 615T211 624T229 626Q247 625 254 615T261 596Q261 589 252 549T232 470L222 433Q222 431 272 431H323Q330 424 330 420Q330 398 317 385H210L174 240Q135 80 135 68Q135 26 162 26Q197 26 230 60T283 144Q285 150 288 151T303 153H307Q322 153 322 145Q322 142 319 133Q314 117 301 95T267 48T216 6T155 -11Q125 -11 98 4T59 56Q57 64 57 83V101L92 241Q127 382 128 383Q128 385 77 385H26Z"></path><path id="MJX-1-TEX-I-210E" d="M137 683Q138 683 209 688T282 694Q294 694 294 685Q294 674 258 534Q220 386 220 383Q220 381 227 388Q288 442 357 442Q411 442 444 415T478 336Q478 285 440 178T402 50Q403 36 407 31T422 26Q450 26 474 56T513 138Q516 149 519 151T535 153Q555 153 555 145Q555 144 551 130Q535 71 500 33Q466 -10 419 -10H414Q367 -10 346 17T325 74Q325 90 361 192T398 345Q398 404 354 404H349Q266 404 205 306L198 293L164 158Q132 28 127 16Q114 -11 83 -11Q69 -11 59 -2T48 16Q48 30 121 320L195 616Q195 629 188 632T149 637H128Q122 643 122 645T124 664Q129 683 137 683Z"></path></defs><g stroke="currentColor" fill="currentColor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="TeXAtom" data-mjx-texclass="ORD"><g data-mml-node="mi"><use data-c="1D451" xlink:href="#MJX-1-TEX-I-1D451"></use></g><g data-mml-node="mi" transform="translate(520,0)"><use data-c="1D45C" xlink:href="#MJX-1-TEX-I-1D45C"></use></g><g data-mml-node="mi" transform="translate(1005,0)"><use data-c="1D450" xlink:href="#MJX-1-TEX-I-1D450"></use></g><g data-mml-node="mi" transform="translate(1438,0)"><use data-c="1D465" xlink:href="#MJX-1-TEX-I-1D465"></use></g><g data-mml-node="mi" transform="translate(2010,0)"><use data-c="1D443" xlink:href="#MJX-1-TEX-I-1D443"></use></g><g data-mml-node="mi" transform="translate(2761,0)"><use data-c="1D44E" xlink:href="#MJX-1-TEX-I-1D44E"></use></g><g data-mml-node="mi" transform="translate(3290,0)"><use data-c="1D461" xlink:href="#MJX-1-TEX-I-1D461"></use></g><g data-mml-node="mi" transform="translate(3651,0)"><use data-c="210E" xlink:href="#MJX-1-TEX-I-210E"></use></g></g><g data-mml-node="mi" transform="translate(4227,0)"><text data-variant="normal" transform="scale(1,-1)" font-size="884px" font-family="serif">转</text></g><g data-mml-node="mi" transform="translate(5227,0)"><text data-variant="normal" transform="scale(1,-1)" font-size="884px" font-family="serif">换</text></g><g data-mml-node="mi" transform="translate(6227,0)"><text data-variant="normal" transform="scale(1,-1)" font-size="884px" font-family="serif">为</text></g></g></g></svg></span>{outputPath}`);
  109. } catch (error) {
  110. console.error('转换失败:', error);
  111. }
  112. }
  113. module.exports.docsToPdf = docsToPdf
  114. /**
  115. * docx 替换模板字符串内容
  116. * @example
  117. // 要替换内容的模板
  118. let inputDocx = 'cs.docx'
  119. // 替换完成的docx文件
  120. let outputDocx = 'dd.docx'
  121. // {{xx}} 处要替换的内容
  122. let replaceData = {
  123. name: '替换name处的内容',
  124. age: '替换age处的内容',
  125. }
  126. replaceDocx(inputDocx, outputDocx, replaceData)
  127. */
  128. export function replaceDocx(inputDocxPath, outputDocxPath, options,eventMap) {
  129. return new Promise((resolve,reject)=>{
  130. // 解压出来的临时目录
  131. let md5 = crypto.createHash('md5');
  132. let outmd5 = md5.update(outputDocxPath).digest('hex')
  133. let tempDocxPath = path.join(tempDir , outmd5)
  134. // 要替换的xml文件位置
  135. let tempDocxXMLName = path.join(tempDocxPath,`word/document.xml`)
  136. // 压缩文件夹为文件
  137. let dir_to_docx = (inputFilePath, outputFilePath) => {
  138. outputFilePath = path.join(tempDir,outputFilePath)
  139. // 创建压缩流
  140. let zipStream = new compressing.zip.Stream()
  141. // 写出流
  142. let outStream = fs.createWriteStream(outputFilePath)
  143. fs.readdir(inputFilePath, null, (err, files) => {
  144. if (!err) {
  145. files.map(file => path.join(inputFilePath, file))
  146. .forEach(file => {
  147. zipStream.addEntry(file)
  148. })
  149. }
  150. })
  151. // 写入文件内容
  152. zipStream.pipe(outStream)
  153. .on('close', () => {
  154. // 打包完成后删除临时目录
  155. // console.log(tempDocxPath)
  156. eventMap["onDocxComplete"]&&eventMap["onDocxComplete"](outputFilePath)
  157. shell.rm("-r",tempDocxPath)
  158. // rimrif.rimrafSync(tempDocxPath)
  159. resolve(true)
  160. })
  161. }
  162. // 替换word/document.xml文件中{{xx}}处的内容
  163. let replaceXML = (data, text) => {
  164. Object.keys(data).forEach(key => {
  165. text = text.replaceAll(`{{${key}}}`, data[key])
  166. })
  167. return text
  168. }
  169. // 解压docx文件替换内容重新打包成docx文件
  170. compressing.zip.uncompress(inputDocxPath, tempDocxPath)
  171. .then(() => {
  172. // 读写要替换内容的xml文件
  173. fs.readFile(tempDocxXMLName, null, (err, data) => {
  174. if (!err) {
  175. let text = data.toString()
  176. text = replaceXML(options, text)
  177. fs.writeFile(tempDocxXMLName, text, (err) => {
  178. if (!err) {
  179. dir_to_docx(tempDocxPath, outputDocxPath)
  180. } else {
  181. reject(err)
  182. }
  183. })
  184. } else {
  185. reject(err)
  186. }
  187. })
  188. }).catch(err => {
  189. reject(err)
  190. })
  191. })
  192. }
  193. module.exports.replaceDocx = replaceDocx