fix(server): 使用 lossless-json 无损处理 summary.json Long 精度
This commit is contained in:
@@ -37,6 +37,7 @@
|
||||
"@tanstack/react-start": "catalog:",
|
||||
"drizzle-orm": "catalog:",
|
||||
"jszip": "catalog:",
|
||||
"lossless-json": "catalog:",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"systeminformation": "catalog:",
|
||||
|
||||
@@ -10,12 +10,28 @@ import {
|
||||
} from '@furtherverse/crypto'
|
||||
import { ORPCError } from '@orpc/server'
|
||||
import JSZip from 'jszip'
|
||||
import {
|
||||
isInteger,
|
||||
isSafeNumber,
|
||||
LosslessNumber,
|
||||
parse as losslessParse,
|
||||
stringify as losslessStringify,
|
||||
} from 'lossless-json'
|
||||
import { z } from 'zod'
|
||||
import { extractSafeZipFiles, ZipValidationError } from '@/server/safe-zip'
|
||||
import { getUxConfig } from '@/server/ux-config'
|
||||
import { db } from '../middlewares'
|
||||
import { os } from '../server'
|
||||
|
||||
const safeNumberParser = (value: string): number | string => {
|
||||
if (isSafeNumber(value)) return Number(value)
|
||||
if (isInteger(value)) return value
|
||||
return Number(value)
|
||||
}
|
||||
|
||||
const toLosslessNumber = (value: string): LosslessNumber | string =>
|
||||
value !== '' && /^-?\d+$/.test(value) ? new LosslessNumber(value) : value
|
||||
|
||||
const summaryPayloadSchema = z
|
||||
.object({
|
||||
taskId: z.string().min(1, 'summary.json must contain a non-empty taskId'),
|
||||
@@ -94,7 +110,7 @@ export const signAndPackReport = os.crypto.signAndPackReport.use(db).handler(asy
|
||||
|
||||
let rawJson: unknown
|
||||
try {
|
||||
rawJson = JSON.parse(Buffer.from(summaryFile.bytes).toString('utf-8'))
|
||||
rawJson = losslessParse(Buffer.from(summaryFile.bytes).toString('utf-8'), undefined, safeNumberParser)
|
||||
} catch {
|
||||
throw new ORPCError('BAD_REQUEST', {
|
||||
message: 'summary.json in the ZIP is not valid JSON',
|
||||
@@ -138,15 +154,21 @@ export const signAndPackReport = os.crypto.signAndPackReport.use(db).handler(asy
|
||||
|
||||
// Build final summary.json with flat structure (matching Kotlin reference)
|
||||
const finalSummary = {
|
||||
orgId,
|
||||
checkId,
|
||||
orgId: toLosslessNumber(String(orgId)),
|
||||
checkId: toLosslessNumber(checkId),
|
||||
taskId: summaryPayload.taskId,
|
||||
licence: config.licence,
|
||||
fingerprint: config.fingerprint,
|
||||
deviceSignature,
|
||||
summary: summaryPayload.summary ?? '',
|
||||
}
|
||||
const summaryBytes = Buffer.from(JSON.stringify(finalSummary), 'utf-8')
|
||||
const summaryJson = losslessStringify(finalSummary)
|
||||
if (!summaryJson) {
|
||||
throw new ORPCError('INTERNAL_SERVER_ERROR', {
|
||||
message: 'Failed to serialize summary.json',
|
||||
})
|
||||
}
|
||||
const summaryBytes = Buffer.from(summaryJson, 'utf-8')
|
||||
|
||||
// Build manifest.json (fixed file list, matching Kotlin reference)
|
||||
const manifestFiles: Record<string, string> = {
|
||||
|
||||
Reference in New Issue
Block a user