page2pdf_server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/.babelrc +3 -0
  2. package/.eslintrc +33 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +28 -0
  4. package/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
  5. package/.github/ISSUE_TEMPLATE/refactoring.md +15 -0
  6. package/.github/PULL_REQUEST_TEMPLATE.md +18 -0
  7. package/.github/stale.yml +17 -0
  8. package/.github/workflows/cd.yml +75 -0
  9. package/.github/workflows/ci.yml +36 -0
  10. package/.husky/pre-commit +6 -0
  11. package/.husky/pre-push +4 -0
  12. package/.idea/codeStyles/Project.xml +58 -0
  13. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  14. package/.idea/encodings.xml +7 -0
  15. package/.idea/inspectionProfiles/Project_Default.xml +7 -0
  16. package/.idea/modules.xml +8 -0
  17. package/.idea/page2pdf-server.iml +12 -0
  18. package/.idea/tenstack-starter-main.iml +12 -0
  19. package/.idea/vcs.xml +6 -0
  20. package/.prettierrc +8 -0
  21. package/.vscode/settings.json +3 -0
  22. package/LICENSE +18 -0
  23. package/README.md +238 -0
  24. package/__tests__/UrltoPdf/generatePdf.test.ts +207 -0
  25. package/__tests__/UrltoPdf/pdfSplit.test.ts +69 -0
  26. package/__tests__/helpers/index.ts +21 -0
  27. package/__tests__/home.test.ts +77 -0
  28. package/config/default.json +10 -0
  29. package/config/development.json +3 -0
  30. package/config/production.json +3 -0
  31. package/config/test.json +3 -0
  32. package/ecosystem.config.js +41 -0
  33. package/eslintrc.json +14 -0
  34. package/jest.config.js +35 -0
  35. package/nodemon.json +6 -0
  36. package/package.json +105 -0
  37. package/src/CSS/345/205/274/345/256/271/346/200/247.txt +56 -0
  38. package/src/app.ts +41 -0
  39. package/src/components/home/controller.ts +27 -0
  40. package/src/components/home/index.ts +4 -0
  41. package/src/components/home/pdfController.ts +112 -0
  42. package/src/components/home/services.ts +31 -0
  43. package/src/components/home/splitController.ts +124 -0
  44. package/src/components/home/validators.ts +12 -0
  45. package/src/configEnv/index.ts +62 -0
  46. package/src/db/home.ts +14 -0
  47. package/src/helpers/apiResponse.ts +10 -0
  48. package/src/helpers/dataSanitizers.ts +31 -0
  49. package/src/helpers/error/ApiError.ts +25 -0
  50. package/src/helpers/error/ForbiddenError.ts +15 -0
  51. package/src/helpers/error/NotFoundException.ts +15 -0
  52. package/src/helpers/error/TimeOutError.ts +20 -0
  53. package/src/helpers/error/UnauthorizedError.ts +15 -0
  54. package/src/helpers/error/ValidationError.ts +20 -0
  55. package/src/helpers/error/index.ts +15 -0
  56. package/src/helpers/index.ts +2 -0
  57. package/src/helpers/loggers.ts +73 -0
  58. package/src/index.ts +13 -0
  59. package/src/middlewares/errorHandler.ts +52 -0
  60. package/src/new_tab1.mhtml +722 -0
  61. package/src/routes/index.ts +22 -0
  62. package/src/server.ts +30 -0
  63. package/src/testCSS.html +241 -0
  64. package/src/types/global.d.ts +13 -0
  65. package/src/types/request/home.ts +166 -0
  66. package/src/types/request/split.ts +18 -0
  67. package/src/types/response/AppInformation.ts +9 -0
  68. package/src/types/response/index.ts +5 -0
  69. package/src/utils/array.ts +19 -0
  70. package/src/utils/auth.ts +12 -0
  71. package/src/utils/crypt.ts +26 -0
  72. package/src/utils/filter.ts +59 -0
  73. package/src/utils/object.ts +58 -0
  74. package/src/utils/pdfgen.ts +998 -0
  75. package/src/utils/url.ts +54 -0
  76. package/src//346/265/213/350/257/225.txt +241 -0
  77. package/tsconfig.json +41 -0
  78. package/tslint.json +22 -0
  79. package//346/226/207/344/271/246/346/211/223/345/215/260/350/275/254/346/215/242/345/231/250.bat +2 -0
@@ -0,0 +1,41 @@
1
+ module.exports = {
2
+ apps: [
3
+ {
4
+ name: '<app-name>',
5
+ script: 'dist/src/index.js',
6
+ // Options reference: https://pm2.keymetrics.io/docs/usage/application-declaration/
7
+ exec_mode: 'cluster',
8
+ instances: 2,
9
+ kill_timeout: 5000,
10
+ autorestart: true,
11
+ max_restarts: 10,
12
+ watch: true,
13
+ watch_options: {
14
+ followSymlinks: false,
15
+ },
16
+ max_memory_restart: '1G',
17
+ env: {
18
+ NODE_ENV: 'development',
19
+ },
20
+ env_production: {
21
+ NODE_ENV: 'production',
22
+ port: 8080,
23
+ },
24
+ merge_logs: true,
25
+ log_date_format: 'YYYY-MM-DDTHH:mm:ss.sssZ',
26
+ },
27
+ ],
28
+ // https://pm2.keymetrics.io/docs/usage/deployment/
29
+ deploy: {
30
+ production: {
31
+ key: process.env.EC2_PEM_PATH,
32
+ host: process.env.EC2_HOST,
33
+ user: 'ubuntu',
34
+ ref: 'origin/main',
35
+ repo: 'git@github.com:<username>/<repo-name>.git',
36
+ path: '/home/ubuntu',
37
+ 'post-deploy':
38
+ 'yarn && yarn clean && yarn build && pm2 startOrRestart ecosystem.config.js --env production && pm2 set pm2:autodump true && pm2 save',
39
+ },
40
+ },
41
+ };
package/eslintrc.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "parser": "@typescript-eslint/parser",
3
+ "parserOptions": {
4
+ "ecmaVersion": 2018,
5
+ "sourceType": "module"
6
+ },
7
+ "plugins": ["@typescript-eslint"],
8
+ "extends": [
9
+ "plugin:@typescript-eslint/recommended"
10
+ ],
11
+ "rules": {
12
+ "semi": "off"
13
+ }
14
+ }
package/jest.config.js ADDED
@@ -0,0 +1,35 @@
1
+ const { defaults: tsjPreset } = require("ts-jest/presets");
2
+
3
+ module.exports = {
4
+ preset: "ts-jest",
5
+ moduleFileExtensions: ["ts", "js", "json"],
6
+ transform: {
7
+ "^.+\\.(ts|tsx)$": [
8
+ "ts-jest",
9
+ {
10
+ tsconfig: "tsconfig.json",
11
+ diagnostics: false,
12
+ },
13
+ ],
14
+ ...tsjPreset.transform,
15
+ },
16
+ testPathIgnorePatterns: ["dist"],
17
+ testMatch: ["**/__tests__/**/*.test.(ts|js)"],
18
+ testEnvironment: "node",
19
+ moduleNameMapper: {
20
+ "@/(.*)": "<rootDir>/src/$1",
21
+ },
22
+ watchPlugins: [
23
+ "jest-watch-typeahead/filename",
24
+ "jest-watch-typeahead/testname",
25
+ ],
26
+ coverageThreshold: {
27
+ global: {
28
+ branches: 1,
29
+ functions: 1,
30
+ lines: 1,
31
+ statements: 1,
32
+ },
33
+ },
34
+ coverageReporters: ["json", "lcov", "text", "clover"],
35
+ };
package/nodemon.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "watch": ["src/**/*.ts", ".env"],
3
+ "ext": "ts,js,.env",
4
+ "verbose": false,
5
+ "exec": "ts-node -r tsconfig-paths/register --files src/index.ts"
6
+ }
package/package.json ADDED
@@ -0,0 +1,105 @@
1
+ {
2
+ "name": "page2pdf_server",
3
+ "version": "1.0.0",
4
+ "description": "文书打印转换器",
5
+ "private": false,
6
+ "authors": "herzhang",
7
+ "repository": "https://gitee.com/heerzhang/page2pdf-server",
8
+ "license": "MIT",
9
+ "scripts": {
10
+ "yarn": "yarn",
11
+ "yarn命令行": "yarn add nodemon -D",
12
+ "注意2": "运行prettify来修复代码,免去到处是标记红色的,太严格了",
13
+ "prettify": "prettier --write \"src/**/*.{ts,js,json}\" \"__tests__/**/*.{ts,js,json}\"",
14
+ "start": "NODE_ENV=development run-s prettify lint & nodemon",
15
+ "build": "cross-env NODE_ENV=production run-s prettify clean transpile",
16
+ "lint": "eslint 'src/**/*.{ts,js}' '__tests__/**/*.{ts,js}'",
17
+ "lint:fix": "eslint --fix 'src/**/*.{ts,js}' '__tests__/**/*.{ts,js}' --quiet",
18
+ "有些隐藏错误": "需要运行watch才发现",
19
+ "watch": "tsc --watch",
20
+ "clean": "rimraf dist",
21
+ "test": "cross-env NODE_ENV=test jest --coverage --runInBand --detectOpenHandles",
22
+ "单步调试": "不必运行test,直接到__test__目录文件直接挑选修改测试项并调试",
23
+ "test:watchAll": "cross-env NODE_ENV=test jest --watchAll --runInBand --detectOpenHandles",
24
+ "transpile": "tsc",
25
+ "service:start": "pm2 start ecosystem.config.js",
26
+ "service:reload": "pm2 reload ecosystem.config.js",
27
+ "service:startup": "pm2 startup",
28
+ "service:stop": "pm2 stop ecosystem.config.js",
29
+ "service:list": "pm2 list ecosystem.config.js",
30
+ "service:delete": "pm2 delete ecosystem.config.js",
31
+ "service:logs": "pm2 logs",
32
+ "hook主动运行": "操作系统缘故,将单引号改成双引号,加转义符,提交代码会自动执行;git提交给它勾掉吧,yarn会自动运行prepare",
33
+ "prepare": "husky install"
34
+ },
35
+ "keywords": [
36
+ "dynamic page pdf generator",
37
+ "html to pdf",
38
+ "app url to pdf",
39
+ "pdf generation",
40
+ "node.js",
41
+ "express",
42
+ "print",
43
+ "server"
44
+ ],
45
+ "dependencies": {
46
+ "bcrypt": "^5.1.0",
47
+ "chrome-remote-interface": "^0.32.2",
48
+ "config": "^3.3.9",
49
+ "connect-timeout": "^1.9.0",
50
+ "cors": "^2.8.5",
51
+ "cross-env": "^7.0.3",
52
+ "dotenv": "^16.3.1",
53
+ "express": "^4.18.2",
54
+ "express-pino-logger": "^7.0.0",
55
+ "express-validator": "^7.0.1",
56
+ "helmet": "^7.0.0",
57
+ "http-status": "^1.6.2",
58
+ "jsonwebtoken": "^9.0.0",
59
+ "lodash-es": "^4.17.21",
60
+ "morgan": "^1.10.0",
61
+ "pdf-lib": "^1.17.1",
62
+ "pino-http": "^8.3.3",
63
+ "pino-pretty": "^10.0.0",
64
+ "rimraf": "^5.0.1",
65
+ "save-dev": "^0.0.1-security"
66
+ },
67
+ "devDependencies": {
68
+ "@babel/preset-env": "^7.22.5",
69
+ "@babel/preset-typescript": "^7.22.5",
70
+ "@types/bcrypt": "^5.0.0",
71
+ "@types/connect-timeout": "^0.0.37",
72
+ "@types/cors": "^2.8.13",
73
+ "@types/express": "^4.17.17",
74
+ "@types/express-pino-logger": "^4.0.3",
75
+ "@types/jest": "^29.5.2",
76
+ "@types/jsonwebtoken": "^9.0.2",
77
+ "@types/lodash-es": "^4.17.7",
78
+ "@types/module-alias": "^2.0.1",
79
+ "@types/morgan": "^1.9.4",
80
+ "@types/node": "^20.3.1",
81
+ "@types/supertest": "^2.0.12",
82
+ "@typescript-eslint/eslint-plugin": "^5.59.11",
83
+ "@typescript-eslint/parser": "^5.59.11",
84
+ "eslint": "^8.43.0",
85
+ "eslint-config-prettier": "^8.8.0",
86
+ "eslint-plugin-import": "^2.27.5",
87
+ "eslint-plugin-prettier": "^4.2.1",
88
+ "husky": "^8.0.3",
89
+ "jest": "^29.5.0",
90
+ "jest-watch-typeahead": "^2.2.2",
91
+ "module-alias": "^2.2.3",
92
+ "nodemon": "^3.0.1",
93
+ "npm-run-all": "^4.1.5",
94
+ "pm2": "^5.3.0",
95
+ "prettier": "^2.8.8",
96
+ "supertest": "^6.3.3",
97
+ "ts-jest": "^29.1.0",
98
+ "ts-node": "^10.9.1",
99
+ "tsconfig-paths": "^4.2.0",
100
+ "typescript": "^5.1.6"
101
+ },
102
+ "engines": {
103
+ "node": ">= 18.0.0"
104
+ }
105
+ }
@@ -0,0 +1,56 @@
1
+ CSS内容层次可以输出页脚页眉留白的,浏览器打印给pdf也能输出页脚页眉左右上下留白,最终打印环节真实打印机还可以设置装订线尺寸占用区以及页脚页码输出区域的。
2
+ Chrome浏览器已经 可支持的有这些:
3
+ <style>
4
+ @page :left { #偶数页的 右边流出装订线尺寸位置的
5
+ margin-right: 7cm;
6
+ }
7
+ @page :right { ##封面,奇数页;
8
+ margin-left: 7cm;
9
+ }
10
+ @page :first {
11
+ margin-top: 5cm;
12
+ }
13
+ </style>
14
+
15
+
16
+ 目前浏览器Chrome还不支持的有这些: ! !
17
+ @page:right{
18
+ @bottom-left {
19
+ content: "Our Cats";
20
+ }
21
+ @bottom-right {
22
+ content: counter(page);
23
+ }
24
+ @top-right {
25
+ content: string(doctitle);
26
+ }
27
+ }
28
+ @bottom-center {
29
+ content: "asdaqq7q";
30
+ }
31
+ @page :right {
32
+ @bottom-center {
33
+ content: "aEdaqq7q";
34
+ }
35
+ }
36
+
37
+
38
+ 【局限】 尝试在CDP打印命令Page.printToPDF,往参数headerTemplate footerTemplate加入脚本,可加了脚本<script>全都不会运行啊。
39
+ 也就是说页眉页脚<span class=pageNumber></span>/<span class=totalPages></span>这两个数字Page.printToPDF({生成的话我没法改动它。
40
+ 它只会武断的1/3 2/3 3/3的编号,甚至不会省略掉首页页码的,每一页都要计入总页数的。也没法适应双面打印把输出的位置左右对调一下,左右边上位置如何处置。
41
+
42
+ [后续] 正因为Page.printToPDF()不能支持多个url一次性生成pdf的, @page :right @bottom-right,,
43
+ 这些CSS3标准在这里需要合并多个文档的打印要求中,实际这些都变得没有意义了。
44
+ 奇数页偶数页的不是针对部分某一个url来讲的,右边流出装订线尺寸位置的,双面打印也需要挪到真实物理打印机最后环节去设置才合适。
45
+ 所以,打印靠css不能解决复杂问题。
46
+ printToPDF()追溯上去依赖的Page.navigate({url}),这个操作实际和打印预览完全没一点关系,这个函数完全就是普通网页导航进入一个链接<a/>;
47
+ 还有追溯上去另外一种是Page.setDocumentContent({ frameId, html: html })设置 Page 的 html 内容的,
48
+ 而该操作的html这个参数也不可能随意合并多页面(应用层的事情)文档DOM还有层次结构的, Iframe标签?。终归无法合并多个url内容打印。
49
+ Page.close 可以关闭页面Tab ,Page实际就是浏览器的单一网站窗口; 通过 CDP 来和 chromium 通信。
50
+ 连续2次setDocumentContent(html)只能显示最后一次操作的html内容的;
51
+ 如果是setDocumentContent({html:html1+html2})内容的可能混乱:脚本css以及DOM的id都可能交叉影响了,最终强制合并输出效果可能不是预期的网页内容。
52
+ 连续两次Page.navigate({url})? 明显无法合并输出的。
53
+ .navigate和.setDocumentContent交叉顺序的,也都是会被最后的操作冲掉覆盖前面的url或html的,肯定无法合并后给.printToPDF()搞一次合并打印的。
54
+ CDP({})前后两次CDP打开新的Page若前面没有close(),那么旧Page.printToPDF也会被后面新Page.navigate所覆盖的,浏览器不支持合并打印的!
55
+ Page.printToPDF({})实际也只能类似Chrome打印预览菜单一致,只能打印当前页面,动态内容页面也是随着鼠标用户操作有可能不一样的打印结果。
56
+
package/src/app.ts ADDED
@@ -0,0 +1,41 @@
1
+ import cors from "cors";
2
+ import express from "express";
3
+ import helmet from "helmet";
4
+ import morgan from "morgan";
5
+ import timeout from "connect-timeout";
6
+ import CONFIG from "./configEnv";
7
+ import { expressPinoLogger } from "./helpers";
8
+ import * as errorHandler from "@/middlewares/errorHandler";
9
+ import routes from "@/routes";
10
+
11
+ export const createApp = (): express.Application => {
12
+ const app = express();
13
+
14
+ app.use(cors());
15
+ app.use(helmet());
16
+ app.use(express.json());
17
+ app.use(
18
+ express.urlencoded({
19
+ extended: true,
20
+ }),
21
+ );
22
+
23
+ if (CONFIG.APP.ENV !== "test") {
24
+ app.use(morgan("dev"));
25
+ app.use(expressPinoLogger());
26
+ }
27
+
28
+ app.use(timeout(CONFIG.SERVER.TIMEOUT));
29
+
30
+ // API Routes
31
+ // API Routes 如果代码非常简单,完全可以 app.get('/',....),如果路由比较复杂,需要路由的嵌套分层拆解模式:使用 express.Router() 更合适。
32
+ app.use(`/api`, routes);
33
+ //框架例子有:版本号的插入的。
34
+ //app.use(`/api/${CONFIG.APP.VER}`, routes);
35
+
36
+ // Error Middleware
37
+ app.use(errorHandler.genericErrorHandler);
38
+ app.use(errorHandler.notFoundError);
39
+
40
+ return app;
41
+ };
@@ -0,0 +1,27 @@
1
+ import { OK } from "http-status/lib";
2
+ import { HomeServices } from "./services";
3
+ import { getAppInfoQuery } from "@/types/request/home";
4
+ import { apiResponse } from "@/helpers/apiResponse";
5
+
6
+ /**
7
+ * 为何为了保证单步模式调试运行"test:watchAll": "cross-env NODE_ENV=development jest --watchAll --runInBand --detectOpenHandles",
8
+ * 只能单独创建一个文件和类PdfController了,不能和HomeController合并在一个类class的俩个并行api处理函数啊?
9
+ * */
10
+ export class HomeController {
11
+ /**
12
+ * @description Gets the API information.
13
+ * @param {Req} req
14
+ * @param {Res} res
15
+ */
16
+ static getAppInfo = async (req: Req, res: Res, next: NextFn) => {
17
+ try {
18
+ const appInfoKey = req.query.key as getAppInfoQuery;
19
+ const homeServices = new HomeServices();
20
+ const result = await homeServices.getAppInfo(appInfoKey);
21
+
22
+ res.status(OK).json(apiResponse(result));
23
+ } catch (error) {
24
+ next(error);
25
+ }
26
+ };
27
+ }
@@ -0,0 +1,4 @@
1
+ export { HomeController } from "./controller";
2
+ export { PdfController } from "./pdfController";
3
+ export { appKeyValidator } from "./validators";
4
+ export { SplitController } from "./splitController";
@@ -0,0 +1,112 @@
1
+ import { OK } from "http-status/lib";
2
+ import { ConfigRoot, FileTransform, HeadFooter } from "@/types/request/home";
3
+ import { apiResponse } from "@/helpers/apiResponse";
4
+ import { RenderPDF } from "@/utils/pdfgen";
5
+ import CONFIG from "@/configEnv";
6
+
7
+ export class PdfController {
8
+ /** node.js对比前端App 太严格了,一点小毛病页不行啊。
9
+ * @description 生成pdf.
10
+ * @param {Req} req
11
+ * @param {Res} res
12
+ * 正常分解成两大步:task.files每一个文件生成,最后才是task.merge合并(包括页码生成)。
13
+ * @param next
14
+ */
15
+ static postMakePdf = async (req: Req, res: Res, next: NextFn) => {
16
+ try {
17
+ // const appInfoKey1 = req.query.key as getAppInfoQuery;
18
+ const task = req.body as ConfigRoot<FileTransform>;
19
+ task.lay as HeadFooter;
20
+ //预处理汇总输出的pdf页眉页脚, 转换页眉页脚的html存储
21
+ if (task.lay) {
22
+ const newfoot = "";
23
+ const formfoot =
24
+ task.lay.foot &&
25
+ newfoot.concat(...task.lay.foot).replaceAll(`\\"`, '"');
26
+ task.footerTemplate = formfoot ?? undefined;
27
+ const formhead =
28
+ task.lay.head &&
29
+ newfoot.concat(...task.lay.head).replaceAll(`\\"`, '"');
30
+ task.headerTemplate = formhead ?? undefined;
31
+ const formfootL =
32
+ task.lay.footL &&
33
+ newfoot.concat(...task.lay.footL).replaceAll(`\\"`, '"');
34
+ task.footerTemplateL = formfootL ?? undefined;
35
+ const formheadL =
36
+ task.lay.headL &&
37
+ newfoot.concat(...task.lay.headL).replaceAll(`\\"`, '"');
38
+ task.headerTemplateL = formheadL ?? undefined;
39
+ }
40
+ //const homeServices = new HomeServices();
41
+ //let result = { ["文件名"]: "产生的.pdf" };
42
+ let result;
43
+ //若是用RenderPDF.generateSinglePdf(会导致没有等待该函数执行结果就会去执行下一步了,应答res,客户端立刻获得http回答,无等待=异步模式的。还是由客户端主动取消等待为佳。这里死等的;
44
+ try {
45
+ //const jobs = generateJobList(urls, pdfs); 分解成多个URL文件多个步骤的情况,可能还有个汇总步骤的:
46
+ // generateSinglePdf mergePdfs pageNoForPdf yeMeiyeJiaoPdf
47
+ if (
48
+ (task.merge === undefined || task.merge) &&
49
+ task.files === undefined &&
50
+ !task.lay
51
+ ) {
52
+ await RenderPDF.mergeAllPdfto(); //特殊的特殊? 没有涉及到CDP()函数:
53
+ result = `简易合并输出是: ${CONFIG.APP.MERGE}`;
54
+ } else {
55
+ //有涉及到CDP()函数:
56
+ const renderer = new RenderPDF({}); //提升上来:复用全局的
57
+ if (!task.files || task.files?.length <= 0) {
58
+ console.error("没配置files输入");
59
+ res.status(OK).json(apiResponse({ result: "没配置files输入" }));
60
+ return;
61
+ } else {
62
+ for (const meta of task.files) {
63
+ if (!meta.out) {
64
+ if (meta.url.endsWith(".pdf") && !meta.url.startsWith("http"))
65
+ continue;
66
+ else {
67
+ res
68
+ .status(OK)
69
+ .json(apiResponse({ result: meta.url + " 没配置out" }));
70
+ return;
71
+ }
72
+ } else {
73
+ //本地pdf情形实际会有webview/iframe的?跳转?,实际会看到pdf之外的浏览控件框框的。 避免再次转换pdf实际捕获窗口并非pdf自身内容;
74
+ if (meta.url.endsWith(".pdf") && !meta.url.startsWith("http"))
75
+ throw new Error(
76
+ "url是本地的pdf文件,不应该设置out参数:" + meta.url,
77
+ );
78
+ }
79
+ try {
80
+ result = await renderer.generateSinglePdf(meta, task, {});
81
+ } catch (e) {
82
+ result = "算url失败:" + meta.url;
83
+ console.error(e);
84
+ }
85
+ if (result) {
86
+ result = "URL:" + meta.url + " > " + result;
87
+ break; //说明有某种报错
88
+ }
89
+ }
90
+ }
91
+ //正常的合并阶段的事情: 前一阶段页眉页脚也不打印了。也要放这里了
92
+ if (!result && (task.merge === undefined || task.merge))
93
+ result = await RenderPDF.MergeAllPdfs(task, {});
94
+ //关闭多余窗口
95
+ if (task.closeTab)
96
+ await renderer.closeAllTab(renderer.getCDPhostPort(), task);
97
+ }
98
+ } catch (e) {
99
+ result = "算失败了" + e;
100
+ console.error(e);
101
+ }
102
+
103
+ if (!result || result === "") {
104
+ result = "成功!";
105
+ console.log(result, "输出是:", task.name ?? CONFIG.APP.MERGE);
106
+ }
107
+ res.status(OK).json(apiResponse({ result: result }));
108
+ } catch (error) {
109
+ next(error);
110
+ }
111
+ };
112
+ }
@@ -0,0 +1,31 @@
1
+ import AppInformation from "@/types/response/AppInformation";
2
+ import { HomeDAO } from "@/db/home";
3
+ import { getAppInfoQuery } from "@/types/request/home";
4
+
5
+ export class HomeServices {
6
+ homeDAO!: HomeDAO;
7
+ constructor() {
8
+ this.homeDAO = new HomeDAO();
9
+ }
10
+ // NAME: pkg.name,
11
+ // VERSION: pkg.version,
12
+ // VER: `v${pkg.version[0]}`,
13
+ // DESCRIPTION: pkg.description,
14
+ // AUTHORS: pkg.authors,
15
+ // HOST: process.env.APP_HOST,
16
+ // BASE_URL: process.env.API_BASE_URL,
17
+ // PORT: process.env.NODE_ENV === "test" ? 8888 : process.env.PORT || 8080,
18
+ // ENV: process.env.NODE_ENV,
19
+
20
+ /**
21
+ * @description Get application information.
22
+ * @returns AppInformation
23
+ */
24
+ getAppInfo = async (
25
+ appInfoKey?: getAppInfoQuery,
26
+ ): Promise<AppInformation> => {
27
+ const result = await this.homeDAO.get(appInfoKey);
28
+
29
+ return result;
30
+ };
31
+ }
@@ -0,0 +1,124 @@
1
+ import fs from "fs";
2
+ import { OK } from "http-status/lib";
3
+ import { ConfigRoot, FileTransform, HeadFooter } from "@/types/request/home";
4
+ import { apiResponse } from "@/helpers/apiResponse";
5
+
6
+ import { RenderPDF } from "@/utils/pdfgen";
7
+ import CONFIG from "@/configEnv";
8
+ import { SplitConfig, SplitPdfFile } from "@/types/request/split";
9
+ // import CONFIG from "@/configEnv";
10
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
11
+ const { PDFDocument } = require("pdf-lib");
12
+
13
+ /**只好拆分多个文件:就为了能够jest运行于单步调试模式啊。
14
+ * __test__/./generatePdf.test.ts问题:jest用Run模式正常,可是若Debug模式单步调试的,.post(`/api/pdf`).send就会提前中止!原因xxxController容不下多个接口函数?
15
+ * ?估计同一个.ts文件引入的包有动态编译的问题?非得拆分两个文件。
16
+ * */
17
+ export class SplitController {
18
+ /** node.js对比前端App 太严格了,一点小毛病页不行啊。
19
+ * @description 生成pdf.
20
+ * @param {Req} req
21
+ * @param {Res} res
22
+ * 正常分解成两大步:task.files每一个文件生成,最后才是task.merge合并(包括页码生成)。
23
+ * @param next
24
+ */
25
+ //仅仅测试的
26
+ static postMakePdf = async (req: Req, res: Res, next: NextFn) => {
27
+ try {
28
+ // const appInfoKey1 = req.query.key as getAppInfoQuery;
29
+ const task = req.body as ConfigRoot<FileTransform>;
30
+ task.lay as HeadFooter;
31
+ //预处理汇总输出的pdf页眉页脚
32
+ if (task.lay) {
33
+ const newfoot = "";
34
+ const formfoot =
35
+ task.lay.foot &&
36
+ newfoot.concat(...task.lay.foot).replaceAll(`\\"`, '"');
37
+ task.footerTemplate = formfoot ?? undefined;
38
+ const formhead =
39
+ task.lay.head &&
40
+ newfoot.concat(...task.lay.head).replaceAll(`\\"`, '"');
41
+ task.headerTemplate = formhead ?? undefined;
42
+ }
43
+ //const homeServices = new HomeServices();
44
+ //let result = { ["文件名"]: "产生的.pdf" };
45
+ let result;
46
+ //若是用RenderPDF.generateSinglePdf(会导致没有等待该函数执行结果就会去执行下一步了,应答res,客户端立刻获得http回答,无等待=异步模式的。还是由客户端主动取消等待为佳。这里死等的;
47
+ try {
48
+ //const jobs = generateJobList(urls, pdfs); 分解成多个URL文件多个步骤的情况,可能还有个汇总步骤的:
49
+ // generateSinglePdf mergePdfs pageNoForPdf yeMeiyeJiaoPdf
50
+ if (
51
+ (task.merge === undefined || task.merge) &&
52
+ task.files === undefined &&
53
+ !task.lay
54
+ ) {
55
+ await RenderPDF.mergeAllPdfto(); //特殊的特殊?
56
+ result = `简易合并输出是: ${CONFIG.APP.MERGE}`;
57
+ } else {
58
+ //正常的合并阶段的事情: 前一阶段页眉页脚也不打印了。也要放这里了
59
+ //if(task.merge===undefined || task.merge)
60
+ result = await RenderPDF.MergeAllPdfs(task, {});
61
+ }
62
+ } catch (e) {
63
+ result = "算失败了" + e;
64
+ console.error(e);
65
+ }
66
+
67
+ if (!result || result === "") result = "成功!";
68
+ res.status(OK).json(apiResponse({ result: result }));
69
+ } catch (error) {
70
+ next(error);
71
+ }
72
+ };
73
+
74
+ /**拆分需要:支持pdf源文件的,抽取部分纸张页生成新文件*/
75
+ static postSplitPdf = async (req: Req, res: Res, next: NextFn) => {
76
+ try {
77
+ //也可能?用CDP:printPDF()来进行部分打印的后保存,就等于拆分pdf;
78
+ const task = req.body as SplitConfig;
79
+ task.files as SplitPdfFile[];
80
+ //输入文件 都是pdf了
81
+ const srcDoc = await PDFDocument.load(
82
+ fs.readFileSync(CONFIG.APP.PATH + "/" + task.input),
83
+ );
84
+ let result;
85
+ const allpagesum = await srcDoc.getPages().length;
86
+ //若是用RenderPDF.generateSinglePdf(会导致没有等待该函数执行结果就会去执行下一步了,应答res,客户端立刻获得http回答,无等待=异步模式的。还是由客户端主动取消等待为佳。这里死等的;
87
+ try {
88
+ for (const fileone of task.files) {
89
+ const oneDoc = await PDFDocument.create();
90
+ //可能出现例子是 '1-5, 8, 11-13' 类似打印机常见的选择页码范围。
91
+ const ofstr = fileone.pageRanges;
92
+ const ranges = ofstr.split(",");
93
+ for (const range of ranges) {
94
+ const bgend = range.split("-");
95
+ const beginp = bgend[0];
96
+ let endp = bgend[1];
97
+ if (!endp) {
98
+ endp = beginp;
99
+ }
100
+ //逗号分割开的某一区域 , , 被选定的: 页序数号是从开始的。
101
+ let i = Number(beginp);
102
+ const endpage = Number(endp);
103
+ if (i < 1 || i > allpagesum || endpage < i || endpage > allpagesum)
104
+ throw new Error("选定的页序号超范围:[" + i + "-" + endpage + "]");
105
+ for (; i <= endpage; i++) {
106
+ const [aPage] = await oneDoc.copyPages(srcDoc, [i - 1]);
107
+ oneDoc.addPage(aPage);
108
+ }
109
+ }
110
+ const name = CONFIG.APP.PATH + "/" + fileone.out + ".pdf";
111
+ fs.writeFileSync(name, await oneDoc.save());
112
+ }
113
+ } catch (e) {
114
+ result = "算失败了" + e;
115
+ console.error(e); //这个在最终服务端cmd窗口可输出
116
+ }
117
+
118
+ if (!result || result === "") result = "成功!";
119
+ res.status(OK).json(apiResponse({ result: result }));
120
+ } catch (error) {
121
+ next(error);
122
+ }
123
+ };
124
+ }
@@ -0,0 +1,12 @@
1
+ import { query } from "express-validator";
2
+ import CONFIG from "@/configEnv";
3
+
4
+ const appKeys = Object.keys(CONFIG.APP).map((key) => key.toLocaleLowerCase());
5
+
6
+ export const appKeyValidator = [
7
+ query("key")
8
+ .optional()
9
+ .isString()
10
+ .isIn(appKeys)
11
+ .withMessage("`name` should be string type"),
12
+ ];