最近接手了一个医疗行业的Web系统升级项目,客户需要在前端实现10GB级别大文件的上传功能。医疗影像数据通常体积庞大,传统的单文件上传方式完全无法满足需求。经过技术调研,最终选择基于Vue.js + WebUploader的方案实现分片上传和断点续传。
医疗行业对文件上传有特殊要求:
在评估了多个上传组件后,最终选择了百度开源的WebUploader,主要基于以下考虑:
提示:医疗项目选择上传组件时,安全性应该是首要考虑因素。WebUploader的加密传输特性完美契合医疗数据保护要求。
系统采用前后端分离架构:
code复制前端(Vue.js)
→ WebUploader分片
→ 后端接收分片
→ 合并文件
→ 存储到医疗影像系统
关键流程:
首先安装WebUploader:
bash复制npm install webuploader --save
然后在项目中创建上传组件:
javascript复制// FileUpload.vue
<template>
<div id="uploader-container">
<div id="filePicker">选择文件</div>
<div id="fileList"></div>
</div>
</template>
<script>
import WebUploader from 'webuploader'
import 'webuploader/dist/webuploader.min.css'
export default {
mounted() {
this.initUploader()
},
methods: {
initUploader() {
const uploader = WebUploader.create({
auto: true,
swf: '/static/Uploader.swf', // Flash文件路径
server: '/api/upload', // 上传接口
pick: '#filePicker',
chunked: true, // 开启分片上传
chunkSize: 2 * 1024 * 1024, // 每片2MB
threads: 3, // 并发数
duplicate: true // 允许重复上传
})
// 更多配置...
}
}
}
</script>
医疗项目需要特别关注的配置项:
javascript复制{
// 医疗数据加密配置
crypt: {
enable: true,
type: 'sm4', // 国密算法
key: '医疗数据加密密钥' // 实际项目应从安全渠道获取
},
// 断点续传配置
resume: true,
localStorage: true,
// 文件验证
fileVal: 'medicalFile',
accept: {
title: '医疗文件',
extensions: 'dcm,nii,mhd,jpg,png', // 常见医疗影像格式
mimeTypes: 'application/octet-stream'
},
// 超时设置(医疗网络环境可能不稳定)
timeout: 300000 // 5分钟
}
WebUploader内部处理分片的流程:
关键代码示例:
javascript复制uploader.on('uploadBeforeSend', (object, data, headers) => {
// 添加医疗项目特有的请求头
headers['X-Medical-Project'] = '放射科影像系统'
// 分片信息
data.chunk = object.chunk
data.chunks = object.chunks
data.md5 = uploader.md5File(object.file)
})
后端需要实现三个核心接口:
javascript复制// GET /api/upload/check
{
md5: '文件MD5',
chunk: '当前分片索引'
}
javascript复制// POST /api/upload
{
file: '分片二进制数据',
md5: '文件MD5',
chunk: '当前分片索引',
chunks: '总分片数'
}
javascript复制// POST /api/upload/merge
{
md5: '文件MD5',
filename: '原始文件名',
chunks: '总分片数'
}
医疗数据必须加密传输,WebUploader支持SM4国密算法:
javascript复制uploader.on('uploadStart', (file) => {
// 启用加密
uploader.option('crypt', {
enable: true,
type: 'sm4',
key: this.getEncryptionKey() // 从安全存储获取密钥
})
})
密钥管理:加密密钥不应硬编码在前端,建议:
访问控制:
javascript复制// 添加上传权限验证
uploader.on('uploadBeforeSend', (object, data) => {
data.token = getMedicalAuthToken()
})
javascript复制uploader.on('uploadAccept', (file, response) => {
logMedicalOperation({
action: 'upload',
file: file.name,
user: currentUser,
time: new Date()
})
})
WebUploader默认使用localStorage保存进度:
javascript复制// 自定义存储适配器(医疗项目可能需要更可靠的存储)
uploader.option('localStorage', {
get: (key) => {
return localStorage.getItem(`medical_upload_${key}`)
},
set: (key, value) => {
localStorage.setItem(`medical_upload_${key}`, value)
},
remove: (key) => {
localStorage.removeItem(`medical_upload_${key}`)
}
})
医疗上传可能持续较长时间,需要支持:
解决方案:
javascript复制// 暂停上传
uploader.stop()
// 恢复上传
uploader.upload()
医疗数据通常按病例组织,需要保持目录结构:
javascript复制{
dnd: '#uploader-container',
disableGlobalDnd: true,
paste: document.body,
prepareNextFile: true,
// 文件夹上传
directory: true,
duplicate: true
}
上传的文件夹会转换为以下结构:
json复制{
"name": "病例001",
"files": [
{
"name": "CT扫描",
"path": "病例001/CT扫描/1.dcm",
"size": 2048000
},
{
"name": "MRI影像",
"path": "病例001/MRI影像/1.dcm",
"size": 3072000
}
]
}
javascript复制// 根据网络状况调整分片大小
function getDynamicChunkSize() {
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection
if (connection.effectiveType === '4g') {
return 5 * 1024 * 1024 // 5MB for fast networks
}
return 1 * 1024 * 1024 // 1MB for slow networks
}
javascript复制// 根据CPU核心数设置并发
const threads = navigator.hardwareConcurrency ? Math.min(4, navigator.hardwareConcurrency) : 3
uploader.option('threads', threads)
处理大文件时注意:
javascript复制// 释放文件引用
uploader.on('uploadFinished', (file) => {
uploader.removeFile(file, true)
})
// 分片上传后立即释放内存
uploader.on('uploadComplete', (file) => {
URL.revokeObjectURL(file.source)
})
医疗影像常用的DICOM格式需要特殊处理:
javascript复制// 添加DICOM文件头验证
uploader.on('fileQueued', (file) => {
const reader = new FileReader()
reader.onload = (e) => {
const header = new Uint8Array(e.target.result, 128, 4)
if (String.fromCharCode(...header) !== 'DICM') {
uploader.removeFile(file)
alert('非法的DICOM文件')
}
}
reader.readAsArrayBuffer(file.getNative())
})
医疗上传组件应包含患者隐私保护:
javascript复制uploader.on('uploadBeforeSend', (object, data) => {
// 移除可能包含患者信息的元数据
if (object.file.type === 'application/dicom') {
data.anonymize = true
}
})
javascript复制uploader.on('error', (type) => {
const medicalErrors = {
'Q_EXCEED_SIZE_LIMIT': '文件超过医疗系统限制',
'Q_TYPE_DENIED': '不支持该医疗文件格式',
'F_DUPLICATE': '该医疗文件已存在'
}
alert(medicalErrors[type] || '上传出错')
})
医疗系统需要详细的上传日志:
javascript复制// 实时更新上传状态
uploader.on('uploadProgress', (file, percentage) => {
this.$emit('progress', {
file: file.name,
percentage: (percentage * 100).toFixed(2) + '%',
speed: formatSpeed(file.speed),
remaining: formatTime(file.remaining)
})
})
function formatSpeed(bytes) {
// 转换为MB/s
return (bytes / 1024 / 1024).toFixed(2) + 'MB/s'
}
javascript复制// 模拟分片上传
test('should upload file in chunks', async () => {
const largeFile = new File(['x'.repeat(10 * 1024 * 1024)], 'test.dcm')
await uploadFile(largeFile)
expect(uploader.getFiles('inited').length).toBe(5) // 10MB文件应分成5片
})
javascript复制test('should resume interrupted upload', async () => {
// 上传3片后中断
// 重新初始化后应自动继续
})
javascript复制test('should encrypt medical data', () => {
expect(uploader.options.crypt.enable).toBe(true)
expect(uploader.options.crypt.type).toBe('sm4')
})
javascript复制test('should validate DICOM headers', () => {
// 测试合法和非法DICOM文件
})
javascript复制// 生产环境配置
const config = {
server: {
check: 'https://medical-upload-api/check',
upload: 'https://medical-upload-api/upload',
merge: 'https://medical-upload-api/merge'
},
crypt: {
keyServer: 'https://medical-key-server/getKey'
},
timeout: 600000 // 医疗网络可能需要更长时间
}
在医疗项目中使用Vue实现大文件上传需要特别关注数据安全和可靠性。通过WebUploader的分片上传和断点续传能力,结合医疗行业的特殊要求,可以构建出符合医疗标准的高性能上传组件。实际开发中还需要考虑医院网络环境的特殊性,做好充分的测试和性能优化。