spine连接器和导出修改
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join, normalize, sep } from 'node:path'
|
||||
|
||||
const MAX_JSON_BYTES = 64 * 1024 * 1024
|
||||
const MAX_IMAGE_BYTES = 32 * 1024 * 1024
|
||||
const MAX_TOTAL_IMAGE_BYTES = 128 * 1024 * 1024
|
||||
|
||||
export function safeProjectName(value) {
|
||||
const name = String(value || 'SpineParticle').trim().replace(/[\\/:*?"<>|\x00-\x1f]+/g, '_').replace(/^\.+/, '')
|
||||
return name.slice(0, 100) || 'SpineParticle'
|
||||
}
|
||||
|
||||
function safeImagePath(value) {
|
||||
const raw = String(value || '').replace(/\\/g, '/')
|
||||
if (!raw || raw.startsWith('/') || raw.includes('\0')) throw new Error('图片路径无效')
|
||||
const path = normalize(raw)
|
||||
if (path === '..' || path.startsWith(`..${sep}`)) throw new Error('图片路径不能超出 images 目录')
|
||||
return path
|
||||
}
|
||||
|
||||
function safeImagesDirectory(value) {
|
||||
const raw = String(value || './images/').replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/$/, '') || 'images'
|
||||
if (raw.startsWith('/') || /^[a-z]+:/i.test(raw)) throw new Error('Spine 图片路径必须是工程内的相对路径')
|
||||
return safeImagePath(raw)
|
||||
}
|
||||
|
||||
function decodeBase64(value) {
|
||||
if (typeof value !== 'string' || !/^[A-Za-z0-9+/]*={0,2}$/.test(value)) throw new Error('图片数据格式无效')
|
||||
const data = Buffer.from(value, 'base64')
|
||||
if (data.length > MAX_IMAGE_BYTES) throw new Error('单张图片不能超过 32MB')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function createConversionWorkspace(payload) {
|
||||
if (!payload || payload.schemaVersion !== 1) throw new Error('不支持的连接器请求格式')
|
||||
if (typeof payload.skeletonJson !== 'string' || Buffer.byteLength(payload.skeletonJson) > MAX_JSON_BYTES) throw new Error('Spine JSON 缺失或超过 64MB')
|
||||
let skeleton
|
||||
try { skeleton = JSON.parse(payload.skeletonJson) } catch { throw new Error('Spine JSON 格式无效') }
|
||||
const jsonVersion = String(skeleton?.skeleton?.spine || '')
|
||||
const spineVersion = /^(3\.8|4\.[0-3])(?:\.\d+)?$/.exec(jsonVersion)?.[1]
|
||||
if (!spineVersion) throw new Error(`JSON 中的 Spine 版本不受支持:${jsonVersion || '未知'}`)
|
||||
const requestedVersion = String(payload.spineVersion || spineVersion)
|
||||
if (requestedVersion !== spineVersion) throw new Error(`请求版本 ${requestedVersion} 与 JSON 版本 ${jsonVersion} 不一致`)
|
||||
const imagesDirectory = safeImagesDirectory(skeleton?.skeleton?.images)
|
||||
|
||||
const root = await mkdtemp(join(tmpdir(), 'spine-particle-'))
|
||||
try {
|
||||
const projectName = safeProjectName(payload.projectName)
|
||||
const inputPath = join(root, `${projectName}.json`)
|
||||
const outputPath = join(root, `${projectName}.spine`)
|
||||
await writeFile(inputPath, payload.skeletonJson, 'utf8')
|
||||
let total = 0
|
||||
const archiveImages = []
|
||||
for (const image of Array.isArray(payload.images) ? payload.images : []) {
|
||||
const data = decodeBase64(image?.dataBase64)
|
||||
total += data.length
|
||||
if (total > MAX_TOTAL_IMAGE_BYTES) throw new Error('图片总大小不能超过 128MB')
|
||||
const relativeImagePath = safeImagePath(image?.path)
|
||||
const target = join(root, imagesDirectory, relativeImagePath)
|
||||
await mkdir(dirname(target), { recursive: true })
|
||||
await writeFile(target, data)
|
||||
archiveImages.push({ name: `${imagesDirectory.replace(/\\/g, '/')}/${relativeImagePath.replace(/\\/g, '/')}`, data })
|
||||
}
|
||||
return {
|
||||
root,
|
||||
projectName,
|
||||
spineVersion,
|
||||
archiveImages,
|
||||
inputPath,
|
||||
outputPath,
|
||||
readOutput: () => readFile(outputPath),
|
||||
cleanup: () => rm(root, { recursive: true, force: true }),
|
||||
}
|
||||
} catch (error) {
|
||||
await rm(root, { recursive: true, force: true })
|
||||
throw error
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user