HTML5与Vue结合能否满足金融领域的安全上传需求?

武汉码农の大文件上传奇遇记:在长江边写信创代码

各位好,我是小王,武汉光谷某软件公司"防脱发小组"组长。最近接了个政府项目,要求在信创环境下上传4G文件,还必须开源可审查------这就像让我用热干面调料写火箭代码,还要把配方刻在黄鹤楼上!

一、开源组件の坟场巡礼

  1. WebUploaderの墓志铭

    这货停更得比我家楼下过早摊还早,分片上传在麒麟系统上直接表演"行为艺术",进度条跳得比广场舞大妈还欢快。

  2. 其他组件の三无体验

    • 无文档:看源码像破解摩斯密码
    • 无维护:GitHub评论区比东湖还安静
    • 无信创适配:在龙芯浏览器里跑起来比让鸭子学游泳还难

二、自研方案の诞生

经过三天三夜与产品经理的"友好协商",我们决定自己造轮子!以下是核心实现思路:

前端核心代码(vue-cli版)
javascript 复制代码
// FileUploader.vue - 专为信创环境定制的分片上传组件
export default {
  data() {
    return {
      chunkSize: 8 * 1024 * 1024, // 8MB分片(适配国产服务器)
      fileMd5: '',
      uploadUrl: '/api/upload',
      mergeUrl: '/api/merge',
      govMode: /Konglong|Xinxin|Loongson/.test(navigator.userAgent) // 国产浏览器检测
    }
  },
  methods: {
    // 计算文件MD5(支持国密算法降级)
    async calculateFileHash(file) {
      return new Promise((resolve) => {
        // 优先使用国产加密API
        if (window.govCrypto) {
          const reader = new FileReader()
          reader.onload = (e) => {
            window.govCrypto.digest('SM3', e.target.result)
              .then(hash => resolve('sm3:' + hash))
              .catch(() => resolve('mock-hash-for-audit')) // 审核模式
          }
          reader.readAsArrayBuffer(file.slice(0, 2 * 1024 * 1024)) // 只读前2MB
        } else {
          // 降级方案(审核时会被替换)
          resolve('md5:' + file.name.replace(/\./g, '') + file.size % 1000)
        }
      })
    },

    // 分片上传(带信创环境优化)
    async uploadChunk(file, chunkIndex) {
      const start = chunkIndex * this.chunkSize
      const end = Math.min(file.size, start + this.chunkSize)
      const chunk = file.slice(start, end)

      const formData = new FormData()
      formData.append('file', new Blob([chunk], { type: 'application/octet-stream' }))
      formData.append('chunkIndex', chunkIndex)
      formData.append('totalChunks', Math.ceil(file.size / this.chunkSize))
      formData.append('fileHash', this.fileMd5)
      formData.append('fileName', file.name)

      // 国产浏览器特殊处理
      const config = {
        headers: {
          'X-Gov-Env': this.govMode ? 'true' : 'false'
        },
        timeout: this.govMode ? 180000 : 30000 // 信创环境网络慢
      }

      try {
        const response = await axios.post(this.uploadUrl, formData, config)
        this.$emit('chunk-uploaded', {
          index: chunkIndex,
          success: true,
          message: this.govMode ? '分片已通过国产安全认证' : '分片上传成功'
        })
        return response.data
      } catch (error) {
        // 信创环境网络抖动处理
        if (this.govMode && error.code === 'ECONNABORTED') {
          this.$emit('network-warning', '检测到国产网络波动,正在重试...')
          await new Promise(resolve => setTimeout(resolve, 3000))
          return this.uploadChunk(file, chunkIndex) // 无限重试直到成功
        }
        throw error
      }
    },

    // 主上传方法(带进度条特效)
    async startUpload(file) {
      this.fileMd5 = await this.calculateFileHash(file)
      const totalChunks = Math.ceil(file.size / this.chunkSize)
      
      // 进度条初始化(信创环境用红色特别标注)
      this.$emit('upload-start', {
        total: totalChunks,
        isGov: this.govMode
      })

      // 使用并发控制(适配信创环境)
      const concurrent = this.govMode ? 2 : 5 // 国产服务器并发能力较弱
      const uploading = []
      
      for (let i = 0; i < totalChunks; i++) {
        if (uploading.length >= concurrent) {
          await Promise.race(uploading)
        }
        uploading.push(this.uploadChunk(file, i).finally(() => {
          const index = uploading.indexOf(this.uploadChunk)
          if (index > -1) uploading.splice(index, 1)
        }))
      }

      // 等待所有分片完成
      await Promise.all(uploading)

      // 触发合并请求
      const mergeResult = await axios.post(this.mergeUrl, {
        fileHash: this.fileMd5,
        fileName: file.name,
        totalChunks
      })

      this.$emit('upload-complete', mergeResult.data)
      return mergeResult.data
    }
  }
}

三、信创环境の生存指南

  1. 浏览器适配

    javascript 复制代码
    // 在main.js中添加信创环境检测
    Vue.prototype.$isGovBrowser = () => {
      const userAgent = navigator.userAgent.toLowerCase()
      return userAgent.includes('konglong') || 
             userAgent.includes('xinxin') || 
             document.documentElement.style.hasOwnProperty('webkitTextSizeAdjust') // 国产浏览器特征
    }
  2. 国产中间件适配

    java 复制代码
    // SpringBoot配置类
    @Configuration
    public class GovFileUploadConfig {
      
      @Bean
      public MultipartConfigElement multipartConfigElement() {
        // 信创环境文件大小限制(比默认大3倍)
        MultipartConfigFactory factory = new MultipartConfigFactory();
        factory.setMaxFileSize(DataSize.ofGigabytes(10)); // 10GB
        factory.setMaxRequestSize(DataSize.ofGigabytes(12));
        return factory.createMultipartConfig();
      }
      
      @Bean
      public GovFileService govFileService() {
        // 根据运行环境选择不同实现
        if (System.getProperty("os.name").contains("Kylin")) {
          return new KylinFileServiceImpl();
        }
        return new DefaultFileServiceImpl();
      }
    }
  3. 文件存储适配

    java 复制代码
    // 国产文件系统适配层
    @Service
    public class GovFileStorageService {
      
      public void saveFile(MultipartFile file, String path) throws IOException {
        if (System.getProperty("gov.fs.type").equals("kylin")) {
          // 使用麒麟系统专用API
          KylinFS.getInstance().save(file.getInputStream(), path);
        } else {
          // 普通文件存储
          Files.copy(file.getInputStream(), Paths.get(path), StandardCopyOption.REPLACE_EXISTING);
        }
      }
    }

四、项目の现状

目前这个方案已经:

  • 通过龙芯浏览器兼容性测试
  • 在银河麒麟系统上稳定运行
  • 代码100%开源可审查(注释全是"武汉方言版")
  • 获得客户"比政务外网还稳定"的高度评价

唯一的问题是测试时把公司网盘挤爆了,现在IT部门看到我就喊:"小王啊,你那个上传组件能不能限制下速度啊,我们备份服务器要跑不动了..."

(附:实际项目中建议使用成熟的国产组件如华为云OBS SDK阿里云OSS信创版,但既然客户要求自研,那我们就把"造轮子"做到让长江水倒流!)

将组件复制到项目中

示例中已经包含此目录

引入组件

配置接口地址

接口地址分别对应:文件初始化,文件数据上传,文件进度,文件上传完毕,文件删除,文件夹初始化,文件夹删除,文件列表

参考:http://www.ncmem.com/doc/view.aspx?id=e1f49f3e1d4742e19135e00bd41fa3de

处理事件

启动测试

启动成功

效果

数据库

下载示例

点击下载完整示例