@ims-view/server 1.1.0 → 1.2.1

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 (93) hide show
  1. package/README.md +18 -1
  2. package/lib/excel/excel-parse.worker.cjs +279 -0
  3. package/lib/server/src/app.module.js.map +1 -0
  4. package/lib/server/src/config/env.d.ts +40 -0
  5. package/lib/server/src/config/env.js +35 -0
  6. package/lib/server/src/config/env.js.map +1 -0
  7. package/lib/server/src/excel/dto/excel-task-id.dto.d.ts +19 -0
  8. package/lib/server/src/excel/dto/excel-task-id.dto.js +17 -0
  9. package/lib/server/src/excel/dto/excel-task-id.dto.js.map +1 -0
  10. package/lib/server/src/excel/dto/export-excel.dto.d.ts +67 -0
  11. package/lib/server/src/excel/dto/export-excel.dto.js +48 -0
  12. package/lib/server/src/excel/dto/export-excel.dto.js.map +1 -0
  13. package/lib/server/src/excel/excel-storage.service.d.ts +54 -0
  14. package/lib/server/src/excel/excel-storage.service.js +258 -0
  15. package/lib/server/src/excel/excel-storage.service.js.map +1 -0
  16. package/lib/server/src/excel/excel.controller.d.ts +17 -0
  17. package/lib/{excel → server/src/excel}/excel.controller.js +40 -11
  18. package/lib/server/src/excel/excel.controller.js.map +1 -0
  19. package/lib/server/src/excel/excel.converter.d.ts +15 -0
  20. package/lib/server/src/excel/excel.converter.js +53 -0
  21. package/lib/server/src/excel/excel.converter.js.map +1 -0
  22. package/lib/{excel → server/src/excel}/excel.module.js +3 -2
  23. package/lib/server/src/excel/excel.module.js.map +1 -0
  24. package/lib/server/src/excel/excel.service.d.ts +33 -0
  25. package/lib/server/src/excel/excel.service.js +244 -0
  26. package/lib/server/src/excel/excel.service.js.map +1 -0
  27. package/lib/server/src/excel/types.d.ts +6 -0
  28. package/lib/server/src/excel/types.js +44 -0
  29. package/lib/server/src/excel/types.js.map +1 -0
  30. package/lib/server/src/index.d.ts +12 -0
  31. package/lib/server/src/index.js +32 -0
  32. package/lib/server/src/index.js.map +1 -0
  33. package/lib/server/src/main.d.ts +1 -0
  34. package/lib/{main.js → server/src/main.js} +55 -2
  35. package/lib/server/src/main.js.map +1 -0
  36. package/lib/server/src/shared/create-zod-base-model.d.ts +29 -0
  37. package/lib/server/src/shared/create-zod-base-model.js +55 -0
  38. package/lib/server/src/shared/create-zod-base-model.js.map +1 -0
  39. package/lib/server/src/shared/index.d.ts +5 -0
  40. package/lib/server/src/shared/index.js +16 -0
  41. package/lib/server/src/shared/index.js.map +1 -0
  42. package/lib/server/src/shared/response-entity.d.ts +32 -0
  43. package/lib/server/src/shared/response-entity.js +74 -0
  44. package/lib/server/src/shared/response-entity.js.map +1 -0
  45. package/lib/server/src/shared/zod-exception.filter.d.ts +5 -0
  46. package/lib/server/src/shared/zod-exception.filter.js +25 -0
  47. package/lib/server/src/shared/zod-exception.filter.js.map +1 -0
  48. package/lib/server/src/shared/zod-validation.pipe.d.ts +4 -0
  49. package/lib/server/src/shared/zod-validation.pipe.js +25 -0
  50. package/lib/server/src/shared/zod-validation.pipe.js.map +1 -0
  51. package/lib/utils/src/excel/converter.d.ts +8 -0
  52. package/lib/utils/src/excel/converter.js +144 -0
  53. package/lib/utils/src/excel/converter.js.map +1 -0
  54. package/lib/utils/src/excel/exceljsChunked.d.ts +75 -0
  55. package/lib/utils/src/excel/exceljsChunked.js +338 -0
  56. package/lib/utils/src/excel/exceljsChunked.js.map +1 -0
  57. package/lib/utils/src/excel/index.d.ts +9 -0
  58. package/lib/utils/src/excel/index.js +39 -0
  59. package/lib/utils/src/excel/index.js.map +1 -0
  60. package/lib/utils/src/excel/luckyexcel.d.ts +9 -0
  61. package/lib/utils/src/excel/luckyexcel.js +137 -0
  62. package/lib/utils/src/excel/luckyexcel.js.map +1 -0
  63. package/lib/utils/src/excel/sheetjsConverter.d.ts +6 -0
  64. package/lib/utils/src/excel/sheetjsConverter.js +224 -0
  65. package/lib/utils/src/excel/sheetjsConverter.js.map +1 -0
  66. package/lib/utils/src/excel/types.d.ts +88 -0
  67. package/lib/utils/src/excel/types.js +11 -0
  68. package/lib/utils/src/excel/types.js.map +1 -0
  69. package/lib/utils/src/excel/workbookChunked.d.ts +13 -0
  70. package/lib/utils/src/excel/workbookChunked.js +152 -0
  71. package/lib/utils/src/excel/workbookChunked.js.map +1 -0
  72. package/package.json +10 -5
  73. package/lib/app.module.js.map +0 -1
  74. package/lib/excel/excel.controller.d.ts +0 -13
  75. package/lib/excel/excel.controller.js.map +0 -1
  76. package/lib/excel/excel.converter.d.ts +0 -2
  77. package/lib/excel/excel.converter.js +0 -11
  78. package/lib/excel/excel.converter.js.map +0 -1
  79. package/lib/excel/excel.module.js.map +0 -1
  80. package/lib/excel/excel.service.d.ts +0 -9
  81. package/lib/excel/excel.service.js +0 -33
  82. package/lib/excel/excel.service.js.map +0 -1
  83. package/lib/excel/types.d.ts +0 -6
  84. package/lib/excel/types.js +0 -6
  85. package/lib/excel/types.js.map +0 -1
  86. package/lib/index.d.ts +0 -7
  87. package/lib/index.js +0 -14
  88. package/lib/index.js.map +0 -1
  89. package/lib/main.d.ts +0 -1
  90. package/lib/main.js.map +0 -1
  91. /package/lib/{app.module.d.ts → server/src/app.module.d.ts} +0 -0
  92. /package/lib/{app.module.js → server/src/app.module.js} +0 -0
  93. /package/lib/{excel → server/src/excel}/excel.module.d.ts +0 -0
package/README.md CHANGED
@@ -33,12 +33,16 @@ npx ims-view-server
33
33
  IMS_SERVER_PORT=3010 npx ims-view-server
34
34
  ```
35
35
 
36
- 仓库内开发:
36
+ 仓库内开发(默认热重载,改 `packages/server/src` / `packages/utils/src/excel` 会自动重启):
37
37
 
38
38
  ```shell
39
39
  IMS_SERVER_PORT=3010 pnpm start:server
40
40
  ```
41
41
 
42
+ 不需要热重载时:`pnpm --dir packages/server start:once`
43
+
44
+ 请求体校验使用 Zod(`createZodBaseModel` + `ZodValidationPipe`),JSON 接口统一 `ResponseEntity`:`{ code, data, msg }`(`code === 0` 成功)。`POST /excel/export` 仍直接返回 xlsx 二进制。
45
+
42
46
  ## 嵌入 Nest 应用
43
47
 
44
48
  ```ts
@@ -57,9 +61,22 @@ export class AppModule {}
57
61
  | ---- | ---- | ---- |
58
62
  | `IMS_SERVER_PORT` | 服务端口(优先) | `3010` |
59
63
  | `PORT` | 兼容通用端口变量 | - |
64
+ | `IMS_SERVER_BODY_LIMIT` | JSON body 体积上限 | `50mb` |
65
+ | `IMS_SERVER_GZIP_THRESHOLD` | 响应 gzip 压缩阈值(字节) | `1024` |
66
+ | `IMS_EXCEL_UPLOAD_DIR` | 上传文件目录 | `os.tmpdir()/ims-excel-uploads` |
67
+ | `IMS_EXCEL_UPLOAD_TTL_MS` | 上传文件保留时长 | `3600000`(1h) |
68
+ | `IMS_EXCEL_UPLOAD_CLEANUP_MS` | 定时清理间隔 | `600000`(10min) |
69
+ | `IMS_EXCEL_CHUNK_BYTES` | 超过该体积走分块挂载(LuckyExcel 解析后切块) | `2097152`(2MB) |
70
+ | `IMS_EXCEL_BLOCK_ROWS` | 渐进挂载每块行数(不是总行上限) | `5000` |
71
+ | `IMS_EXCEL_MAX_ROWS` | 大文件导入行上限(`0`=不截断) | `150000` |
72
+ | `IMS_EXCEL_PARSE_TIMEOUT_MS` | 单次解析超时 | `600000`(10min) |
60
73
 
61
74
  前端对接:`IMS_EXCHANGE_ENDPOINT=http://localhost:3010`
62
75
 
76
+ ## 导入策略
77
+
78
+ - **小文件**:LuckyExcel → `{id}.snapshot.json`,前端一次 `createWorkbook`
79
+ - **大文件(默认 >2MB)**:同一套 **LuckyExcel** 解析 → `{id}.meta.json` + blocks,前端骨架(含 styles/resources)+ 分批 `setValues`
63
80
  ## API
64
81
 
65
82
  见文档站:
@@ -0,0 +1,279 @@
1
+ /**
2
+ * Worker 入口(纯 CJS):ExcelJS 分块写盘,不依赖 tsx。
3
+ * 由 excel.service 用 worker_threads 拉起。
4
+ */
5
+ const { parentPort, workerData } = require('worker_threads');
6
+ const { readFile, writeFile } = require('fs/promises');
7
+ const path = require('path');
8
+ const { createRequire } = require('module');
9
+
10
+ const requireFromHere = createRequire(__filename);
11
+
12
+ const resolveExcelJS = () => {
13
+ const candidates = [
14
+ () => requireFromHere.resolve('exceljs'),
15
+ () =>
16
+ requireFromHere.resolve('exceljs', {
17
+ paths: [path.resolve(__dirname, '../../../utils')],
18
+ }),
19
+ () =>
20
+ requireFromHere.resolve('exceljs', {
21
+ paths: [path.resolve(__dirname, '../../../../node_modules')],
22
+ }),
23
+ ];
24
+ for (const tryResolve of candidates) {
25
+ try {
26
+ // eslint-disable-next-line import/no-dynamic-require
27
+ return require(tryResolve());
28
+ } catch {
29
+ // continue
30
+ }
31
+ }
32
+ throw new Error('无法解析 exceljs,请确认 monorepo 依赖已安装');
33
+ };
34
+
35
+ const ExcelJS = resolveExcelJS();
36
+
37
+ const CellValueType = {
38
+ STRING: 1,
39
+ NUMBER: 2,
40
+ BOOLEAN: 3,
41
+ };
42
+
43
+ const argbToRgb = (argb) => {
44
+ if (!argb || typeof argb !== 'string') return undefined;
45
+ const hex = argb.replace(/^#/, '');
46
+ if (hex.length === 8) return `#${hex.slice(2)}`;
47
+ if (hex.length === 6) return `#${hex}`;
48
+ return undefined;
49
+ };
50
+
51
+ const excelColorToRgb = (color) => {
52
+ if (!color) return undefined;
53
+ if (color.argb) return argbToRgb(color.argb);
54
+ return undefined;
55
+ };
56
+
57
+ const cellFromExcelJs = (cell) => {
58
+ const value = cell.value;
59
+ if (value == null || value === '') return null;
60
+
61
+ let mapped = null;
62
+
63
+ if (typeof value === 'object' && value !== null && 'formula' in value) {
64
+ const formula = String(value.formula || '').replace(/^=/, '');
65
+ const result = value.result;
66
+ mapped = {
67
+ f: `=${formula}`,
68
+ v: result,
69
+ t:
70
+ typeof result === 'number'
71
+ ? CellValueType.NUMBER
72
+ : typeof result === 'boolean'
73
+ ? CellValueType.BOOLEAN
74
+ : CellValueType.STRING,
75
+ };
76
+ } else if (typeof value === 'object' && value !== null && 'richText' in value) {
77
+ const text = (value.richText || []).map((part) => part.text || '').join('');
78
+ if (!text) return null;
79
+ mapped = { v: text, t: CellValueType.STRING };
80
+ } else if (typeof value === 'object' && value !== null && 'text' in value && 'hyperlink' in value) {
81
+ mapped = { v: String(value.text || ''), t: CellValueType.STRING };
82
+ } else if (typeof value === 'object' && value !== null && 'error' in value) {
83
+ mapped = { v: String(value.error), t: CellValueType.STRING };
84
+ } else if (value instanceof Date) {
85
+ mapped = { v: value.toISOString(), t: CellValueType.STRING };
86
+ } else if (typeof value === 'number') {
87
+ mapped = { v: value, t: CellValueType.NUMBER };
88
+ } else if (typeof value === 'boolean') {
89
+ mapped = { v: value, t: CellValueType.BOOLEAN };
90
+ } else {
91
+ const text = String(value);
92
+ if (!text) return null;
93
+ mapped = { v: text, t: CellValueType.STRING };
94
+ }
95
+
96
+ const style = {};
97
+ const font = cell.font;
98
+ if (font) {
99
+ if (font.bold) style.bl = 1;
100
+ if (font.italic) style.it = 1;
101
+ if (font.size) style.fs = font.size;
102
+ if (font.name) style.ff = font.name;
103
+ const fc = excelColorToRgb(font.color);
104
+ if (fc) style.cl = { rgb: fc };
105
+ }
106
+ const fill = cell.fill;
107
+ if (fill && fill.type === 'pattern' && fill.fgColor) {
108
+ const bg = excelColorToRgb(fill.fgColor);
109
+ if (bg) style.bg = { rgb: bg };
110
+ }
111
+ if (Object.keys(style).length) mapped.s = style;
112
+ return mapped;
113
+ };
114
+
115
+ const collectMerges = (worksheet, maxRowExclusive) => {
116
+ const merges = [];
117
+ const raw = worksheet.model && worksheet.model.merges;
118
+ if (!Array.isArray(raw)) return merges;
119
+ raw.forEach((ref) => {
120
+ try {
121
+ const [start, end] = String(ref).split(':');
122
+ if (!start) return;
123
+ const startCell = worksheet.getCell(start);
124
+ const endCell = worksheet.getCell(end || start);
125
+ const top = Number(startCell.row) - 1;
126
+ const left = Number(startCell.col) - 1;
127
+ const bottom = Number(endCell.row) - 1;
128
+ const right = Number(endCell.col) - 1;
129
+ if (top >= maxRowExclusive) return;
130
+ merges.push({
131
+ startRow: top,
132
+ startColumn: left,
133
+ endRow: Math.min(bottom, maxRowExclusive - 1),
134
+ endColumn: right,
135
+ });
136
+ } catch {
137
+ // ignore
138
+ }
139
+ });
140
+ return merges;
141
+ };
142
+
143
+ const post = (msg) => {
144
+ if (parentPort) parentPort.postMessage(msg);
145
+ };
146
+
147
+ const main = async () => {
148
+ const data = workerData || {};
149
+ const {
150
+ id,
151
+ xlsxPath,
152
+ uploadDir,
153
+ fileName = 'workbook.xlsx',
154
+ blockRowSize = 2000,
155
+ maxRows = 100000,
156
+ } = data;
157
+
158
+ if (!id || !xlsxPath || !uploadDir) {
159
+ throw new Error('workerData 缺少 id / xlsxPath / uploadDir');
160
+ }
161
+
162
+ const effectiveMaxRows = maxRows === 0 ? Number.POSITIVE_INFINITY : maxRows;
163
+ const buffer = await readFile(xlsxPath);
164
+
165
+ post({ type: 'progress', percent: 5, parsedBlocks: 0, totalBlocks: 0 });
166
+
167
+ const workbook = new ExcelJS.Workbook();
168
+ await workbook.xlsx.load(buffer);
169
+
170
+ post({ type: 'progress', percent: 20, parsedBlocks: 0, totalBlocks: 0 });
171
+
172
+ const sheetsMeta = [];
173
+ const sheetOrder = [];
174
+ let truncated = false;
175
+ let emittedBlocks = 0;
176
+
177
+ const sheetInfos = workbook.worksheets.map((worksheet, sheetIndex) => {
178
+ const sheetId = `sheet-${sheetIndex}`;
179
+ const actualRowCount = worksheet.rowCount || 0;
180
+ const cappedRows = Math.min(actualRowCount, effectiveMaxRows);
181
+ if (actualRowCount > effectiveMaxRows) truncated = true;
182
+ const blockCount = Math.max(1, Math.ceil(Math.max(cappedRows, 1) / blockRowSize));
183
+ return { worksheet, sheetIndex, sheetId, cappedRows, blockCount };
184
+ });
185
+
186
+ const totalBlocks = sheetInfos.reduce((sum, info) => sum + info.blockCount, 0) || 1;
187
+
188
+ for (const info of sheetInfos) {
189
+ const { worksheet, sheetIndex, sheetId, cappedRows, blockCount } = info;
190
+ sheetOrder.push(sheetId);
191
+
192
+ let maxCol = 0;
193
+ const mergeData = collectMerges(worksheet, cappedRows);
194
+
195
+ for (let blockIndex = 0; blockIndex < blockCount; blockIndex += 1) {
196
+ const startRow = blockIndex * blockRowSize;
197
+ const endRow = Math.min(cappedRows, startRow + blockRowSize) - 1;
198
+ const cellData = {};
199
+
200
+ if (cappedRows > 0 && endRow >= startRow) {
201
+ for (let excelRow = startRow + 1; excelRow <= endRow + 1; excelRow += 1) {
202
+ const row = worksheet.getRow(excelRow);
203
+ row.eachCell({ includeEmpty: false }, (cell, colNumber) => {
204
+ const mapped = cellFromExcelJs(cell);
205
+ if (!mapped) return;
206
+ const r = excelRow - 1;
207
+ const c = colNumber - 1;
208
+ if (!cellData[r]) cellData[r] = {};
209
+ cellData[r][c] = mapped;
210
+ maxCol = Math.max(maxCol, colNumber);
211
+ });
212
+ }
213
+ }
214
+
215
+ const blockName = `${id}.block.${sheetIndex}.${blockIndex}.json`;
216
+ await writeFile(
217
+ path.join(uploadDir, blockName),
218
+ JSON.stringify({
219
+ sheetId,
220
+ sheetIndex,
221
+ blockIndex,
222
+ startRow: Math.max(0, startRow),
223
+ endRow: Math.max(0, endRow),
224
+ cellData,
225
+ }),
226
+ 'utf8',
227
+ );
228
+
229
+ emittedBlocks += 1;
230
+ const percent = Math.min(95, 20 + Math.round((emittedBlocks / totalBlocks) * 75));
231
+ post({
232
+ type: 'progress',
233
+ percent,
234
+ parsedBlocks: emittedBlocks,
235
+ totalBlocks,
236
+ });
237
+ }
238
+
239
+ sheetsMeta.push({
240
+ id: sheetId,
241
+ name: worksheet.name || `Sheet${sheetIndex + 1}`,
242
+ rowCount: Math.max(cappedRows, 1),
243
+ columnCount: Math.max(maxCol, 1),
244
+ blockCount,
245
+ mergeData: mergeData.length ? mergeData : undefined,
246
+ });
247
+ }
248
+
249
+ const meta = {
250
+ parseEngine: 'exceljs',
251
+ fileName,
252
+ name: String(fileName).replace(/\.xlsx$/i, '') || 'workbook',
253
+ sheetOrder,
254
+ sheets: sheetsMeta,
255
+ blockRowSize,
256
+ truncated: truncated || undefined,
257
+ maxRows: Number.isFinite(effectiveMaxRows) ? effectiveMaxRows : undefined,
258
+ };
259
+
260
+ const metaFileName = `${id}.meta.json`;
261
+ await writeFile(path.join(uploadDir, metaFileName), JSON.stringify(meta), 'utf8');
262
+
263
+ post({
264
+ type: 'done',
265
+ metaPath: metaFileName,
266
+ metaFileName,
267
+ parsedBlocks: emittedBlocks,
268
+ totalBlocks,
269
+ truncated: meta.truncated,
270
+ });
271
+ };
272
+
273
+ main().catch((error) => {
274
+ post({
275
+ type: 'error',
276
+ message: error instanceof Error ? error.message : String(error),
277
+ });
278
+ process.exitCode = 1;
279
+ });
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.module.js","sourceRoot":"","sources":["../../../src/app.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,uDAAmD;AAK5C,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,8BAAS;oBAAT,SAAS;IAHrB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,0BAAW,CAAC;KACvB,CAAC;GACW,SAAS,CAAG"}
@@ -0,0 +1,40 @@
1
+ import { z } from 'zod';
2
+ export declare const serverEnvSchema: z.ZodObject<{
3
+ IMS_SERVER_PORT: z.ZodDefault<z.ZodNumber>;
4
+ PORT: z.ZodEffects<z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>, number | undefined, string | number | undefined>;
5
+ IMS_SERVER_BODY_LIMIT: z.ZodDefault<z.ZodString>;
6
+ IMS_SERVER_GZIP_THRESHOLD: z.ZodDefault<z.ZodNumber>;
7
+ IMS_EXCEL_UPLOAD_DIR: z.ZodOptional<z.ZodString>;
8
+ IMS_EXCEL_UPLOAD_TTL_MS: z.ZodDefault<z.ZodNumber>;
9
+ IMS_EXCEL_UPLOAD_CLEANUP_MS: z.ZodDefault<z.ZodNumber>;
10
+ IMS_EXCEL_CHUNK_BYTES: z.ZodDefault<z.ZodNumber>;
11
+ IMS_EXCEL_BLOCK_ROWS: z.ZodDefault<z.ZodNumber>;
12
+ IMS_EXCEL_MAX_ROWS: z.ZodDefault<z.ZodNumber>;
13
+ IMS_EXCEL_PARSE_TIMEOUT_MS: z.ZodDefault<z.ZodNumber>;
14
+ }, "strip", z.ZodTypeAny, {
15
+ IMS_EXCEL_UPLOAD_TTL_MS: number;
16
+ IMS_EXCEL_UPLOAD_CLEANUP_MS: number;
17
+ IMS_EXCEL_CHUNK_BYTES: number;
18
+ IMS_EXCEL_PARSE_TIMEOUT_MS: number;
19
+ IMS_EXCEL_BLOCK_ROWS: number;
20
+ IMS_EXCEL_MAX_ROWS: number;
21
+ IMS_SERVER_PORT: number;
22
+ IMS_SERVER_BODY_LIMIT: string;
23
+ IMS_SERVER_GZIP_THRESHOLD: number;
24
+ IMS_EXCEL_UPLOAD_DIR?: string | undefined;
25
+ PORT?: number | undefined;
26
+ }, {
27
+ IMS_EXCEL_UPLOAD_DIR?: string | undefined;
28
+ IMS_EXCEL_UPLOAD_TTL_MS?: number | undefined;
29
+ IMS_EXCEL_UPLOAD_CLEANUP_MS?: number | undefined;
30
+ IMS_EXCEL_CHUNK_BYTES?: number | undefined;
31
+ IMS_EXCEL_PARSE_TIMEOUT_MS?: number | undefined;
32
+ IMS_EXCEL_BLOCK_ROWS?: number | undefined;
33
+ IMS_EXCEL_MAX_ROWS?: number | undefined;
34
+ IMS_SERVER_PORT?: number | undefined;
35
+ PORT?: string | number | undefined;
36
+ IMS_SERVER_BODY_LIMIT?: string | undefined;
37
+ IMS_SERVER_GZIP_THRESHOLD?: number | undefined;
38
+ }>;
39
+ export type ServerEnv = z.infer<typeof serverEnvSchema>;
40
+ export declare const loadServerEnv: (env?: NodeJS.ProcessEnv) => ServerEnv;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadServerEnv = exports.serverEnvSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ exports.serverEnvSchema = zod_1.z.object({
6
+ IMS_SERVER_PORT: zod_1.z.coerce.number().int().positive().default(3010),
7
+ PORT: zod_1.z
8
+ .union([zod_1.z.string(), zod_1.z.number()])
9
+ .optional()
10
+ .transform((value) => {
11
+ if (value === undefined || value === '')
12
+ return undefined;
13
+ const num = typeof value === 'number' ? value : Number(value);
14
+ return Number.isFinite(num) && num > 0 ? num : undefined;
15
+ }),
16
+ IMS_SERVER_BODY_LIMIT: zod_1.z.string().default('50mb'),
17
+ IMS_SERVER_GZIP_THRESHOLD: zod_1.z.coerce.number().int().nonnegative().default(1024),
18
+ IMS_EXCEL_UPLOAD_DIR: zod_1.z.string().optional(),
19
+ IMS_EXCEL_UPLOAD_TTL_MS: zod_1.z.coerce.number().int().positive().default(60 * 60 * 1000),
20
+ IMS_EXCEL_UPLOAD_CLEANUP_MS: zod_1.z.coerce.number().int().positive().default(10 * 60 * 1000),
21
+ IMS_EXCEL_CHUNK_BYTES: zod_1.z.coerce.number().int().positive().default(2 * 1024 * 1024),
22
+ IMS_EXCEL_BLOCK_ROWS: zod_1.z.coerce.number().int().min(100).default(5_000),
23
+ IMS_EXCEL_MAX_ROWS: zod_1.z.coerce.number().int().nonnegative().default(150_000),
24
+ IMS_EXCEL_PARSE_TIMEOUT_MS: zod_1.z.coerce.number().int().positive().default(10 * 60 * 1000),
25
+ });
26
+ const loadServerEnv = (env = process.env) => {
27
+ const result = exports.serverEnvSchema.safeParse(env);
28
+ if (!result.success) {
29
+ const message = result.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; ');
30
+ throw new Error(`[@ims-view/server] 环境变量校验失败: ${message}`);
31
+ }
32
+ return result.data;
33
+ };
34
+ exports.loadServerEnv = loadServerEnv;
35
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../../../../src/config/env.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { z } from 'zod';
2
+ export declare const excelTaskIdSchema: z.ZodObject<{
3
+ id: z.ZodString;
4
+ }, "strip", z.ZodTypeAny, {
5
+ id: string;
6
+ }, {
7
+ id: string;
8
+ }>;
9
+ declare const ExcelTaskIdDto_base: import("../../shared/create-zod-base-model").ZodBaseModel<z.ZodObject<{
10
+ id: z.ZodString;
11
+ }, "strip", z.ZodTypeAny, {
12
+ id: string;
13
+ }, {
14
+ id: string;
15
+ }>>;
16
+ export declare class ExcelTaskIdDto extends ExcelTaskIdDto_base {
17
+ }
18
+ export type ExcelTaskIdPlain = z.infer<typeof excelTaskIdSchema>;
19
+ export {};
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExcelTaskIdDto = exports.excelTaskIdSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ const create_zod_base_model_1 = require("../../shared/create-zod-base-model");
6
+ exports.excelTaskIdSchema = zod_1.z.object({
7
+ id: zod_1.z
8
+ .string()
9
+ .trim()
10
+ .min(1, '任务 id 不能为空')
11
+ .regex(/^[a-zA-Z0-9_-]+$/, '任务 id 格式无效')
12
+ .describe('解析任务 id'),
13
+ });
14
+ class ExcelTaskIdDto extends (0, create_zod_base_model_1.createZodBaseModel)(exports.excelTaskIdSchema) {
15
+ }
16
+ exports.ExcelTaskIdDto = ExcelTaskIdDto;
17
+ //# sourceMappingURL=excel-task-id.dto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"excel-task-id.dto.js","sourceRoot":"","sources":["../../../../../src/excel/dto/excel-task-id.dto.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AACxB,8EAAwE;AAE3D,QAAA,iBAAiB,GAAG,OAAC,CAAC,MAAM,CAAC;IACxC,EAAE,EAAE,OAAC;SACF,MAAM,EAAE;SACR,IAAI,EAAE;SACN,GAAG,CAAC,CAAC,EAAE,YAAY,CAAC;SACpB,KAAK,CAAC,kBAAkB,EAAE,YAAY,CAAC;SACvC,QAAQ,CAAC,SAAS,CAAC;CACvB,CAAC,CAAC;AAEH,MAAa,cAAe,SAAQ,IAAA,0CAAkB,EAAC,yBAAiB,CAAC;CAAG;AAA5E,wCAA4E"}
@@ -0,0 +1,67 @@
1
+ import { z } from 'zod';
2
+ export declare const exportExcelSchema: z.ZodEffects<z.ZodEffects<z.ZodObject<{
3
+ data: z.ZodOptional<z.ZodAny>;
4
+ fileName: z.ZodOptional<z.ZodString>;
5
+ }, "strip", z.ZodTypeAny, {
6
+ data?: any;
7
+ fileName?: string | undefined;
8
+ }, {
9
+ data?: any;
10
+ fileName?: string | undefined;
11
+ }>, {
12
+ data?: any;
13
+ fileName?: string | undefined;
14
+ }, {
15
+ data?: any;
16
+ fileName?: string | undefined;
17
+ }>, {
18
+ fileName: string | undefined;
19
+ data: z.objectOutputType<{
20
+ id: z.ZodOptional<z.ZodString>;
21
+ name: z.ZodOptional<z.ZodString>;
22
+ appVersion: z.ZodOptional<z.ZodString>;
23
+ locale: z.ZodOptional<z.ZodString>;
24
+ sheetOrder: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
25
+ sheets: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
26
+ styles: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
27
+ resources: z.ZodOptional<z.ZodArray<z.ZodAny, "many">>;
28
+ }, z.ZodTypeAny, "passthrough">;
29
+ }, {
30
+ data?: any;
31
+ fileName?: string | undefined;
32
+ }>;
33
+ declare const ExportExcelDto_base: import("../../shared/create-zod-base-model").ZodBaseModel<z.ZodEffects<z.ZodEffects<z.ZodObject<{
34
+ data: z.ZodOptional<z.ZodAny>;
35
+ fileName: z.ZodOptional<z.ZodString>;
36
+ }, "strip", z.ZodTypeAny, {
37
+ data?: any;
38
+ fileName?: string | undefined;
39
+ }, {
40
+ data?: any;
41
+ fileName?: string | undefined;
42
+ }>, {
43
+ data?: any;
44
+ fileName?: string | undefined;
45
+ }, {
46
+ data?: any;
47
+ fileName?: string | undefined;
48
+ }>, {
49
+ fileName: string | undefined;
50
+ data: z.objectOutputType<{
51
+ id: z.ZodOptional<z.ZodString>;
52
+ name: z.ZodOptional<z.ZodString>;
53
+ appVersion: z.ZodOptional<z.ZodString>;
54
+ locale: z.ZodOptional<z.ZodString>;
55
+ sheetOrder: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
56
+ sheets: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
57
+ styles: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
58
+ resources: z.ZodOptional<z.ZodArray<z.ZodAny, "many">>;
59
+ }, z.ZodTypeAny, "passthrough">;
60
+ }, {
61
+ data?: any;
62
+ fileName?: string | undefined;
63
+ }>>;
64
+ export declare class ExportExcelDto extends ExportExcelDto_base {
65
+ }
66
+ export type ExportExcelPlain = z.infer<typeof exportExcelSchema>;
67
+ export {};
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExportExcelDto = exports.exportExcelSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ const create_zod_base_model_1 = require("../../shared/create-zod-base-model");
6
+ const workbookDataSchema = zod_1.z
7
+ .object({
8
+ id: zod_1.z.string().optional(),
9
+ name: zod_1.z.string().optional(),
10
+ appVersion: zod_1.z.string().optional(),
11
+ locale: zod_1.z.string().optional(),
12
+ sheetOrder: zod_1.z.array(zod_1.z.string()).optional(),
13
+ sheets: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional(),
14
+ styles: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional(),
15
+ resources: zod_1.z.array(zod_1.z.any()).optional(),
16
+ })
17
+ .passthrough();
18
+ exports.exportExcelSchema = zod_1.z
19
+ .object({
20
+ data: zod_1.z.any().optional().describe('工作簿 snapshot'),
21
+ fileName: zod_1.z.string().trim().min(1, '文件名不能为空').optional().describe('导出文件名'),
22
+ })
23
+ .superRefine((value, ctx) => {
24
+ if (value.data == null || typeof value.data !== 'object' || Array.isArray(value.data)) {
25
+ ctx.addIssue({
26
+ code: zod_1.z.ZodIssueCode.custom,
27
+ path: ['data'],
28
+ message: '缺少工作簿数据 data',
29
+ });
30
+ return;
31
+ }
32
+ const parsed = workbookDataSchema.safeParse(value.data);
33
+ if (!parsed.success) {
34
+ ctx.addIssue({
35
+ code: zod_1.z.ZodIssueCode.custom,
36
+ path: ['data'],
37
+ message: parsed.error.issues[0]?.message || 'data 格式无效',
38
+ });
39
+ }
40
+ })
41
+ .transform((value) => ({
42
+ fileName: value.fileName,
43
+ data: workbookDataSchema.parse(value.data),
44
+ }));
45
+ class ExportExcelDto extends (0, create_zod_base_model_1.createZodBaseModel)(exports.exportExcelSchema) {
46
+ }
47
+ exports.ExportExcelDto = ExportExcelDto;
48
+ //# sourceMappingURL=export-excel.dto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-excel.dto.js","sourceRoot":"","sources":["../../../../../src/excel/dto/export-excel.dto.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AACxB,8EAAwE;AAExE,MAAM,kBAAkB,GAAG,OAAC;KACzB,MAAM,CAAC;IACN,EAAE,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACzB,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,UAAU,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IAC1C,MAAM,EAAE,OAAC,CAAC,MAAM,CAAC,OAAC,CAAC,MAAM,EAAE,EAAE,OAAC,CAAC,GAAG,EAAE,CAAC,CAAC,QAAQ,EAAE;IAChD,MAAM,EAAE,OAAC,CAAC,MAAM,CAAC,OAAC,CAAC,MAAM,EAAE,EAAE,OAAC,CAAC,GAAG,EAAE,CAAC,CAAC,QAAQ,EAAE;IAChD,SAAS,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,GAAG,EAAE,CAAC,CAAC,QAAQ,EAAE;CACvC,CAAC;KACD,WAAW,EAAE,CAAC;AAGJ,QAAA,iBAAiB,GAAG,OAAC;KAC/B,MAAM,CAAC;IACN,IAAI,EAAE,OAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;IACjD,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC;CAC3E,CAAC;KACD,WAAW,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;IAC1B,IAAI,KAAK,CAAC,IAAI,IAAI,IAAI,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACtF,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,OAAC,CAAC,YAAY,CAAC,MAAM;YAC3B,IAAI,EAAE,CAAC,MAAM,CAAC;YACd,OAAO,EAAE,cAAc;SACxB,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IACD,MAAM,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACxD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,OAAC,CAAC,YAAY,CAAC,MAAM;YAC3B,IAAI,EAAE,CAAC,MAAM,CAAC;YACd,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,WAAW;SACxD,CAAC,CAAC;IACL,CAAC;AACH,CAAC,CAAC;KACD,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACrB,QAAQ,EAAE,KAAK,CAAC,QAAQ;IACxB,IAAI,EAAE,kBAAkB,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC;CAC3C,CAAC,CAAC,CAAC;AAEN,MAAa,cAAe,SAAQ,IAAA,0CAAkB,EAAC,yBAAiB,CAAC;CAAG;AAA5E,wCAA4E"}
@@ -0,0 +1,54 @@
1
+ import { OnModuleDestroy, OnModuleInit } from '@nestjs/common';
2
+ export declare const getExcelUploadDir: () => string;
3
+ export declare const EXCEL_STATIC_PREFIX = "/excel/static";
4
+ export declare const preserveUploadFileName: (raw?: string) => string;
5
+ export type StoredExcelFile = {
6
+ id: string;
7
+ fileName: string;
8
+ path: string;
9
+ size: number;
10
+ expiresAt: number;
11
+ };
12
+ export type ExcelParseTaskStatus = 'pending' | 'done' | 'error';
13
+ export type ExcelParseMode = 'snapshot' | 'chunked';
14
+ export type ExcelParseTask = {
15
+ id: string;
16
+ status: ExcelParseTaskStatus;
17
+ mode?: ExcelParseMode;
18
+ fileName: string;
19
+ xlsxPath: string;
20
+ snapshotPath?: string;
21
+ metaPath?: string;
22
+ progress?: number;
23
+ parsedBlocks?: number;
24
+ totalBlocks?: number;
25
+ truncated?: boolean;
26
+ error?: string;
27
+ startedAt: number;
28
+ finishedAt?: number;
29
+ };
30
+ export declare class ExcelStorageService implements OnModuleInit, OnModuleDestroy {
31
+ private readonly logger;
32
+ private readonly uploadDir;
33
+ private cleanupTimer;
34
+ private readonly tasks;
35
+ onModuleInit(): Promise<void>;
36
+ onModuleDestroy(): void;
37
+ getUploadDir(): string;
38
+ getTask(id: string): ExcelParseTask | undefined;
39
+ getXlsxFullPath(id: string): string;
40
+ saveXlsx(buffer: Buffer, originalName: string): Promise<StoredExcelFile>;
41
+ setTaskMode(id: string, mode: ExcelParseMode): void;
42
+ updateTaskProgress(id: string, progress: number, parsedBlocks?: number, totalBlocks?: number): void;
43
+ saveSnapshot(id: string, workbookData: unknown): Promise<string>;
44
+ markTaskChunkedDone(id: string, metaFileName: string, extras?: {
45
+ parsedBlocks?: number;
46
+ totalBlocks?: number;
47
+ truncated?: boolean;
48
+ }): string;
49
+ markTaskError(id: string, error: string): void;
50
+ readSnapshot(id: string): Promise<unknown | null>;
51
+ private isManagedFile;
52
+ private idFromFileName;
53
+ cleanupExpired(): Promise<number>;
54
+ }