大文件分片上传进阶版(新增md5校验、上传进度展示、并行控制,智能分片、加密上传、断点续传、自动重试),实现四位一体的网络感知型大文件传输系统‌

news2025/4/24 7:43:28

上篇文章我们总结了大文件分片上传的主要核心,但是我对md5校验和上传进度展示这块也比较感兴趣,所以在deepseek的帮助下,扩展了一下我们的代码,如果有任何问题和想法,非常欢迎大家在评论区与我交流,我需要学习的地方也还有特别多~

开始之前我们先用一个通俗易懂的例子来理解我们的功能吧~

用快递寄大件包裹的思路,解释大文件分片上传的实现步骤

场景设定:

假设你要把一卡车的大米(5吨)从杭州运到北京,但遇到了几个现实问题:

1.整卡车运输风险大(爆胎就全完了)
2.中途可能有检查站需要抽检
3.运输途中网络信号时好时坏

解决方案的六个关键步骤:

第一步:货物预处理(preprocessFile)

动作‌:把大米分装成小袋,每袋贴上唯一编号

技术对应‌

  • 计算整个大米的MD5指纹(确保货物完整性)
  • 生成专用加密包装袋(AES密钥)

为什么重要‌:

  • 避免整车运输风险
  • 方便中途抽检任意一袋
  • 不同袋子用不同密码锁更安全

第二步:秒传核验(checkInstantUpload)

‌动作‌:打电话给北京仓库:“你们已经有5吨杭州大米了吗?”
技术对应‌

  • 发送MD5给服务端查询
  • 如果已有相同货物,直接标记运输完成

‌省时技巧‌:

  • 避免重复运输相同货物
  • 节省90%运输时间

第三步:智能分箱(prepareChunks)

‌动作‌:根据路况决定每箱装多少袋

  • 高速公路:用大箱子(装20袋)
  • 山路:用小箱子(装5袋)

‌技术对应‌:

  • 网络测速(检查"路况")
  • 动态调整分片大小

‌智慧之处‌:

  • 好路况多装快跑
  • 差路况少装稳走

第四步:分批运输(uploadAllChunks)

‌动作‌

  1. 给每个箱子单独上锁(不同IV加密)
  2. 3辆货车同时出发(并发控制)
  3. 某辆车抛锚就换车重发(错误重试)

技术细节‌:

  • 每个分片独立加密
  • 失败分片自动重试3次
  • 实时记录已送达的箱子

第五步:收件核验(mergeFile)

‌动作‌

  1. 北京仓库收到所有箱子
  2. 按编号顺序拆箱组合
  3. 检查MD5是否匹配原始指纹

‌安全保障‌:

  • 防止运输途中被调包
  • 确保颗粒不少

第六步:断点续传(saveProgress)

‌突发情况处理‌

  • 遇到暴雨暂停运输
  • 记录哪些箱子已送达
  • 雨停后继续送未达的箱子

‌技术实现‌:

  • 自动保存上传进度
  • 支持从断点恢复

我们的智能方案为什么更优秀呢?

1.整车上路风险高,化整为零更安全
2.每袋都有独立指纹密码锁,防止被掉包
3.根据路况调整运输策略,堵车时不用干等
4.遇到检查要全部开箱时,可随机抽捡一袋不影响整体
5.重新发货不用从头开始,断点续传省时省力

所以我们其实是在上一篇大文件分片上传的过程中增加了两个功能:

秒传检查和合并校验

整体步骤是:

预处理->秒传检查->智能分片->加密运输->合并校验->断点保护

学习之前我们先来搞懂两个问题:

1.md5校验在我们文件上传过程中是必须的吗?

答案肯定是否,但有两个场景我们是必须要使用md5校验的(大方向):秒传功能文件完整性

场景类比解释技术对应
‌秒传功能‌仓库发现已有同批次芒果,直接调库服务端比对MD5跳过上传
‌防数据篡改‌发现运输商偷换成越南芒果合并后MD5与原始值比对

如果简单使用文件大小校验,或者严重依赖tpc传输确保文件不会丢失的情况下也是可以不使用md5校验的

2.md5校验与分片加密有什么关系?可以替代吗?

即使我们已经对每个分片进行了加密上传,仍然可以使用md5校验文件的完整性,分片加密与md5校验是互补而非替代。分片加密如同在生产线上为每个零件做防锈处理,而MD5校验如同在出厂前对整机进行质检——‌防锈处理不能替代最终质检‌,两者结合才能确保交付可靠的产品。

  1. ‌分片加密的作用‌
加密阶段防护目标示例风险
传输过程加密防中间人窃听/篡改黑客截获分片并修改
存储加密防服务器数据泄露数据库被拖库
  1. ‌MD5校验的核心价值‌
校验场景解决的问题示例风险
加密前校验源文件完整性本地文件损坏
解密后校验解密过程是否正确密钥错误导致解密失败
合并校验分片顺序/组合错误分片序号错乱

所以如果(验证文件完整性中可能会发生的错误)

1.加密前的源文件已经损坏(加密传输后肯定有误)
2.密钥错误、IV丢失、解密算法不兼容等导致解密后数据错误
3.分片上传成功但合并顺序错乱

以上几种情况发生的时候我们是有必要进行md5校验的

首先看我们需要实现功能的完整思路与技术亮点吧~

完整实现思路(五阶段工作流)

1.初始化阶段‌

  • 生成文件唯一ID(UUID v4)
  • 配置分片大小范围(默认1MB~20MB)
  • 初始化加密系统、分片存储结构

‌2.预处理阶段‌

  • ‌并行执行‌:MD5计算 + 密钥生成(加速启动)
  • ‌流式MD5‌:2MB分片渐进计算,避免内存溢出 ‌
  • 密钥管理‌:使用Web Crypto API安全生成AES密钥

3.秒传校验‌

  • 发送文件MD5到服务端查询
  • 存在相同文件时直接跳过后续步骤
  • 节省带宽和服务器存储空间

4.动态分片上传‌

  • ‌ 网络测速‌:通过1MB测试文件探测当前带宽 ‌
  • 智能分片‌:按带宽50%动态调整分片大小 ‌
  • 加密传输‌:每个分片独立IV,AES-CBC加密 ‌
  • 并发控制‌:3个并行上传通道(可配置)
  • ‌重试机制‌:指数退避策略(2s, 4s, 8s)

5.收尾工作‌

  • 发送合并请求到服务端
  • 清理临时数据(可选)
  • 持久化最终状态

架构亮点

网络自适应‌

  • 三次测速取平均值减少误差
  • 分片大小平滑过渡(避免剧烈波动)

安全传输‌

  • 前端加密 + 服务端解密双保险
  • 每个分片独立IV防止模式分析
  • 密钥仅存于内存和加密存储

可靠性保障‌

  • 断点续传:自动保存进度到IndexedDB
  • 原子操作:分片上传成功后才标记完成
  • 错误隔离:单个分片失败不影响整体流程

性能优化‌

  • 并行预处理(MD5和密钥生成)
  • 流式哈希计算(内存占用恒定)
  • 浏览器空闲时段上传(可扩展)

以下是完整代码

/**
 * 四位一体的网络感知型大文件传输系统‌
 * 
 * ‌动态分片策略‌
// 基于实时网络带宽的动态分片算法(1MB~20MB智能调节)
// 分片大小平滑调整机制(20%最大波动限制)
// 内存对齐优化(1MB粒度减少资源碎片)

 *  ‌数据安全框架‌
// 三级校验体系:全文件MD5 + 分片级SHA256双哈希
// AES-CBC加密(分片独立IV防重放攻击)
// 密钥生命周期管理(生成->存储->使用隔离)

 * ‌传输可靠性保障‌
// 断点续传能力(IndexedDB持久化存储)
// 异常安全边界(try-catch包裹全流程)
// 分片原子化操作(独立元数据管理)

 *  ‌性能优化工程‌
// 并行预处理流水线(MD5与密钥并行计算)
// 流式哈希计算(2MB分片递归处理)
// 网络测速基准(1MB测试包探测带宽)
 */



/**
 * 大文件分片上传类(支持动态分片、加密、MD5校验)
 */
class FileUploader {
  /**
 * 初始化上传实例
 * @param {File} file - 浏览器文件对象 
 * @param {Object} [options] - 配置参数
 */
  constructor(file, options = {}) {
    // 必需参数
    this.file = file; // 上传文件对象
    this.fileId = this.generateFileId(); // 文件唯一标识
    this.fileMD5 = null;                 // 全文件MD5值

    // 分片管理
    this.chunks = []; // 全部分片数据
    this.uploadedChunks = new Set(); // 已上传分片索引
    this.ivMap = new Map();              // 加密初始化向量存储,存储每个分片的IV

    // 加密配置
    this.encryptionKey = null;           // AES加密密钥

    // 性能参数
    this.lastUploadSpeed = 5 * 1024 * 1024; // 网络基准速度(默认5MB/s)


    // 用户配置
    this.options = {
      minChunkSize: 1 * 1024 * 1024, // 最小分片1MB
      maxChunkSize: 20 * 1024 * 1024, // 最大分片20MB
      ...options
    };

    // 事件系统
    this.events = {};

  }

  // --------------------------
  // 核心公共方法
  // --------------------------

  /**
   * 启动上传流程(完整工作流)
   *  * @throws {Error} 上传过程中的错误
   */
  async startUpload() {
    try {
      // 阶段1: 文件预处理(计算哈希 + 生成密钥)
      await this.preprocessFile();

      // 阶段2: 秒传检查(通过文件MD5判断是否需要传输)
      const needUpload = await this.checkInstantUpload();
      if (!needUpload) return;

      // 阶段3: 动态分片准备(根据网络状况生成分片)
      await this.prepareChunks();

      // 阶段4: 分片上传(含加密和重试机制)
      await this.uploadAllChunks();

      // 阶段5: 合并请求
      await this.mergeFile();

      this.emit('complete');
    } catch (error) {
      console.error('上传流程异常:', error);
      await this.saveProgress(); // 异常时保存进度
      this.emit('error', error);
      throw error;
    }
  }

  // --------------------------
  // 预处理阶段(含MD5计算)
  // --------------------------

  /**
   * 阶段1:文件预处理(计算MD5、生成密钥)
   */
  async preprocessFile() {
    // 并行执行两个任务,实现异步流水线加速
    // SparkMD5库保障哈希计算准确性
    // Web Crypto API生成符合FIPS标准的AES密钥
    const [md5, key] = await Promise.all([
      this.calculateFileMD5(), // 计算全文件MD5
      this.generateAESKey() // 生成加密密钥
    ]);

    this.fileMD5 = md5;
    this.encryptionKey = key;

    // 保存初始进度(可用于恢复)
    await this.saveProgress();
  }

  /**
 * 计算全文件MD5(分片计算避免内存溢出)
 * @returns {Promise<string>} MD5哈希值
 */
  calculateFileMD5() {
    // 返回Promise对象实现异步计算流程控制
    return new Promise((resolve) => {
      // 定义分片大小(2MB兼顾计算效率与内存安全)
      const chunkSize = 2 * 1024 * 1024;
      // 计算总切片数量(向上取整保证最后分片完整性)
      const chunks = Math.ceil(this.file.size / chunkSize);
      // 初始化SparkMD5实例(专为ArrayBuffer优化的MD5计算库)
      const spark = new SparkMD5.ArrayBuffer();
      // 已处理分片计数器
      let processed = 0;

      // 定义分片加载递归函数,递归分片处理大文件(2MB粒度),避免内存溢出风险
      const loadNext = () => {
        // 计算当前分片字节范围
        const start = processed * chunkSize;
        const end = Math.min(start + chunkSize, this.file.size);
        // 切割文件对象获取当前分片Blob
        const blob = this.file.slice(start, end);

        // 创建文件读取器处理二进制数据
        const reader = new FileReader();
        // 注册文件加载完成回调
        reader.onload = (e) => {
          // 将分片二进制数据追加到MD5计算流
          spark.append(e.target.result);
          // 更新已处理分片计数
          processed++;

          // 存储计算进度(格式:当前分片/总分片数)
          sessionStorage.setItem(`${this.fileId}_md5`, `${processed}/${chunks}`);
          // 触发进度事件
          this.emit('progress', {
            type: 'md5',
            value: processed / chunks
          });
          // 递归判断:未完成继续处理,完成则返回最终MD5
          processed < chunks ? loadNext() : resolve(spark.end());
        };
        // 启动分片数据读取(ArrayBuffer格式保持二进制精度)
        reader.readAsArrayBuffer(blob);
      };

      // 启动首个分片处理
      loadNext();
    });
  }


  /**
   * 阶段2:秒传验证(checkInstantUpload)
   * 实现逻辑: 将全文件MD5发送至服务端查询,若存在相同哈希文件,触发秒传逻辑,跳过后续流程直接返回成功
   * ‌业务价值:节省90%+重复文件传输成本;降低服务器存储冗余
 */

  /**
 * 🌟 秒传验证核心方法
 * @param {string} fileMD5 - 文件的完整MD5哈希值
 * @returns {Promise<boolean>} - 是否可秒传
 */
  async checkInstantUpload(fileMD5) {
    try {
      const response = await fetch('/api/check-instant-upload', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ md5: fileMD5 })
      });

      if (!response.ok) throw new Error('秒传验证请求失败');

      const result = await response.json();
      return result.exists; // 服务端返回是否存在
    } catch (error) {
      console.error('秒传验证异常:', error);
      return false; // 失败时按需处理,此处默认继续上传
    }
  }




  // --------------------------
  // 分片处理阶段(含动态调整)
  // --------------------------

  /**
   * 阶段3:智能分片准备(动态调整分片大小)
   * 算法原理:
     基于实时测速结果(testNetworkSpeed)计算基准值
     引入历史速度惯性因子(lastUploadSpeed)平滑波动
     内存对齐优化提升分片处理效率
   */
  async prepareChunks() {
    // 网络测速(取三次平均值)
    const speeds = [];
    for (let i = 0; i < 3; i++) {
      speeds.push(await this.testNetworkSpeed());
    }
    const currentSpeed = speeds.reduce((a, b) => a + b, 0) / speeds.length;

    // 计算动态分片大小(控制在配置范围内)
    let chunkSize = currentSpeed * 0.5; // 按带宽50%计算
    chunkSize = Math.max(
      this.options.minChunkSize,
      Math.min(this.options.maxChunkSize, chunkSize)
    );

    // 生成分片元数据
    const totalChunks = Math.ceil(this.file.size / chunkSize);
    for (let i = 0; i < totalChunks; i++) {
      const start = i * chunkSize;
      const end = Math.min(start + chunkSize, this.file.size);
      this.chunks.push({
        index: i,
        blob: this.file.slice(start, end),
        size: end - start
      });
    }
  }
}

  /**
   * 阶段4:分片上传(uploadAllChunks)(并发控制 + 重试机制)
    * ‌加密流程‌
      生成随机IV(每个分片独立初始化向量)
      计算原始数据SHA256哈希
      AES-CBC加密分片数据
      生成加密后哈希(可选二次校验)
    * ‌传输策略‌
      Set数据结构记录已上传分片索引
      失败重试机制(需补充实现)
      并行上传控制(可扩展为连接池管理
    */
   
  async uploadAllChunks() {
  	// 设置并发数:浏览器环境下建议2-4个并行请求,平衡性能与稳定性
  	const CONCURRENCY = 3;

  	// 创建上传队列:通过展开运算符复制分片数组,避免直接操作原始数据
  	const queue = [...this.chunks];

  	// 主循环:持续处理直到队列清空
  	while (queue.length > 0) {
    	// 初始化当前批次的Promise容器
    	const workers = [];

    	// 提取当前批任务:每次取出CONCURRENCY数量的分片
    	// splice操作会同时修改队列长度,实现队列动态缩减
    	const currentBatch = queue.splice(0, CONCURRENCY);

    	// 遍历当前批次的分片
    	for (const chunk of currentBatch) {
      		// 跳过已上传分片:通过Set检查避免重复上传
      		if (this.uploadedChunks.has(chunk.index)) continue;

      		// 将分片上传任务包装成Promise,加入workers数组
      		workers.push(
        		// 执行分片上传核心方法
        		this.uploadChunk(chunk, chunk.index)
          		.then(() => {
           	 	// 计算实时进度:已上传数 / 总分片数
            		const progress = this.uploadedChunks.size / this.chunks.length;

            		// 触发进度事件:通知外部监听者更新进度条
            		this.emit('progress', { type: 'upload', value: progress });
          		})
      		);
    	}

    	// 等待当前批次全部完成(无论成功/失败)
    	// 使用allSettled而非all保证异常不会中断整个上传流程
    	await Promise.allSettled(workers);
  	}
}

  /**
   * 单分片上传(含加密和重试机制)
   * @param {Object} chunk - 分片数据
   * @param {number} index - 分片索引
   * @param {number} retries - 剩余重试次数
   */
  async uploadChunk(chunk, index, retries = 3) {
  try {
    // 加密处理(生成独立IV)
    const encryptedBlob = await this.encryptChunk(chunk.blob, index);

    // 构建表单数据
    const formData = new FormData();
    formData.append('file', encryptedBlob);
    formData.append('index', index);
    formData.append('iv', this.ivMap.get(index));

    // 上传请求
    const response = await fetch('/upload', {
      method: 'POST',
      body: formData
    });

    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    this.uploadedChunks.add(index);

  } catch (error) {
    if (retries > 0) {
      await new Promise(r => setTimeout(r, 2000 * (4 - retries))); // 指数退避
      return this.uploadChunk(chunk, index, retries - 1);
    }
    throw new Error(`分片${index}上传失败: ${error.message}`);
  }
}

  /* ================= 加密模块 ================= */

  /** 生成AES-CBC加密密钥 */
  async generateAESKey() {
  return crypto.subtle.generateKey(
    { name: 'AES-CBC', length: 256 },
    true,
    ['encrypt', 'decrypt']
  );
}

  /** 加密单个分片 */
  async encryptChunk(blob, index) {
  const iv = crypto.getRandomValues(new Uint8Array(16));
  const data = await blob.arrayBuffer();

  // 原始数据哈希校验
  const rawHash = await crypto.subtle.digest('SHA-256', data);

  // 执行加密
  const encrypted = await crypto.subtle.encrypt(
    { name: 'AES-CBC', iv },
    this.encryptionKey,
    data
  );

  // 存储加密参数
  this.ivMap.set(index, iv);
  return new Blob([encrypted], { type: 'application/octet-stream' });
}

  /* ================= 辅助方法 ================= */

  /** 网络测速(上传1MB测试文件) */
  async testNetworkSpeed() {
  const testBlob = new Blob([new Uint8Array(1 * 1024 * 1024)]);
  const start = Date.now();

  await fetch('/speed-test', {
    method: 'POST',
    body: testBlob
  });

  const duration = (Date.now() - start) / 1000;
  return (1 * 1024 * 1024) / duration; // 返回字节/秒
}

  /** 持久化上传进度 */
  async saveProgress() {
  const data = {
    fileId: this.fileId,
    chunks: this.chunks,
    uploadedChunks: [...this.uploadedChunks],
    encryptionKey: await crypto.subtle.exportKey('jwk', this.encryptionKey),
    ivMap: Object.fromEntries(this.ivMap)
  };
  await idb.setItem(this.fileId, data); // 假设使用IndexedDB
}

/** 生成文件唯一ID */
generateFileId() {
  return crypto.randomUUID();
}

/* ================= 事件系统 ================= */

on(event, callback) {
  this.events[event] = callback;
  return this;
}

emit(event, ...args) {
  const handler = this.events[event];
  handler && handler(...args);
}
}

// ---------------------------- 使用示例 ----------------------------
const uploader = new FileUploader(file, {
  maxChunkSize: 50 * 1024 * 1024 // 自定义配置
});

// 事件监听
uploader
  .on('progress', ({ type, value }) => {
    console.log(`${type}进度: ${(value * 100).toFixed(1)}%`);
  })
  .on('error', error => {
    console.error('上传失败:', error);
  });

// 启动上传
uploader.startUpload();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2341282.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

最新扣子(Coze)案例教程:飞书多维表格按条件筛选记录 + 读取分页Coze工作流,无限循环使用方法,手把手教学,完全免费教程

大家好&#xff0c;我是斜杠君。 &#x1f468;‍&#x1f4bb; 星球群里有同学想学习一下飞书多维表格的使用方法&#xff0c;关于如何通过按条件筛选飞书多维表格中的记录&#xff0c;以及如何使用分页解决最多一次只能读取500条的限制问题。 斜杠君今天就带大家一起搭建一…

Spring AI Alibaba-02-多轮对话记忆、持久化消息记录

Spring AI Alibaba-02-多轮对话记忆、持久化消息记录 Lison <dreamlison163.com>, v1.0.0, 2025.04.19 文章目录 Spring AI Alibaba-02-多轮对话记忆、持久化消息记录多轮对话对话持久-Redis 本次主要聚焦于多轮对话功能的实现&#xff0c;后续会逐步增加更多实用内容&…

联邦元学习实现个性化物联网的框架

随着数据安全和隐私保护相关法律法规的出台&#xff0c;需要直接在中央服务器上收集和处理数据的集中式解决方案&#xff0c;对于个性化物联网而言&#xff0c;训练各种特定领域场景的人工智能模型已变得不切实际。基于此&#xff0c;中山大学&#xff0c;南洋理工大学&#xf…

实验1 温度转换与输入输出强化

知识点&#xff1a;input()/print()、分支语句、字符串处理&#xff08;教材2.1-2.2&#xff09; 实验任务&#xff1a; 1. 实现摄氏温度与华氏温度互转&#xff08;保留两位小数&#xff09; 2. 扩展功能&#xff1a;输入错误处理&#xff08;如非数字输入提示重新输入&#x…

【AI】SpringAI 第五弹:接入千帆大模型

1. 添加依赖 <dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-qianfan</artifactId> </dependency> 2. 编写 yml 配置文件 spring:ai:qianfan:api-key: 你的api-keysecret-key: 你的secr…

[Godot] C#2D平台游戏基础移动和进阶跳跃代码

本文章给大家分享一下如何实现基本的移动和进阶的跳跃&#xff08;跳跃缓冲、可变跳跃、土狼时间&#xff09;以及相对应的重力代码&#xff0c;大家可以根据自己的需要自行修改 实现效果 场景搭建 因为Godot不像Unity&#xff0c;一个节点只能绑定一个脚本&#xff0c;所以我…

【Unity笔记】Unity + OpenXR项目无法启动SteamVR的排查与解决全指南

图片为AI生成 一、前言 随着Unity在XR领域全面转向OpenXR标准&#xff0c;越来越多的开发者选择使用OpenXR来构建跨平台的VR应用。但在项目实际部署中发现&#xff1a;打包成的EXE程序无法正常启动SteamVR&#xff0c;或者SteamVR未能识别到该应用。本文将以“Unity OpenXR …

使用 rebase 轻松管理主干分支

前言 最近遇到一个技术团队的 dev 环境分支错乱&#xff0c;因为是多人合作大家各自提交信息&#xff0c;导致出现很多交叉合并记录&#xff0c;让对应 log 看起来非常混乱&#xff0c;难以阅读。 举例说明 假设我们有一个项目&#xff0c;最初develop分支有 3 个提交记录&a…

【愚公系列】《Python网络爬虫从入门到精通》063-项目实战电商数据侦探(主窗体的数据展示)

&#x1f31f;【技术大咖愚公搬代码&#xff1a;全栈专家的成长之路&#xff0c;你关注的宝藏博主在这里&#xff01;】&#x1f31f; &#x1f4e3;开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主&#xff01; &#x1f…

HttpSessionListener 的用法笔记250417

HttpSessionListener 的用法笔记250417 以下是关于 HttpSessionListener 的用法详解&#xff0c;涵盖核心方法、实现步骤、典型应用场景及注意事项&#xff0c;帮助您全面掌握会话&#xff08;Session&#xff09;生命周期的监听与管理&#xff1a; 1. 核心功能 HttpSessionLi…

火山RTC 5 转推CDN 布局合成规则

实时音视频房间&#xff0c;转推CDN&#xff0c;文档&#xff1a; 转推直播--实时音视频-火山引擎 一、转推CDN 0、前提 * 在调用该接口前&#xff0c;你需要在[控制台](https://console.volcengine.com/rtc/workplaceRTC)开启转推直播功能。<br> * 调…

Spark两种运行模式与部署

1. Spark 的运行模式 部署Spark集群就两种方式&#xff0c;单机模式与集群模式 单机模式就是为了方便开发者调试框架的运行环境。但是生产环境中&#xff0c;一般都是集群部署。 现在Spark目前支持的部署模式&#xff1a; &#xff08;1&#xff09;Local模式&#xff1a;在本地…

qt画一朵花

希望大家的生活都更加美好&#xff0c;画一朵花送给大家 效果图 void FloatingArrowPubshButton::paintEvent(QPaintEvent *event) {QPainter painter(this);painter.setRenderHints(QPainter::Antialiasing);QPen pen;pen.setColor("green");pen.setWidth(5);QBrush…

服务器上安装maven

1.安装 下载安装包 https://maven.apache.org/download.cgi 解压安装包 cd /opt/software tar -xzvf apache-maven-3.9.9-bin.tar.gz 安装目录(/opt/maven/) mv /opt/software/apache-maven-3.9.9 /opt/ 3.权限设置 把/opt/software/apache-maven-3.9.9 文件夹重命名为ma…

UOS+N 卡 + CUDA 环境下 X86 架构 DeepSeek 基于 vLLM 部署与 Dify 平台搭建指南

一、文档说明 本文档是一份关于 DeepSeek 在X86架构下通vLLM工具部署的操作指南&#xff0c;主要面向需要在UOSN卡CUDA环境中部署DeepSeek的技术人员&#xff0c;旨在指导文档使用者完成从 Python 环境升级、vLLM 库安装、模型部署到 Dify 平台搭建的全流程操作。 二、安装Pyt…

MySQL终章(8)JDBC

目录 1.前言 2.正文 2.1JDBC概念 2.2三种编码方式 2.2.1第一种 2.2.2第二种&#xff08;优化版&#xff09; 2.2.3第三种&#xff08;更优化版&#xff09; 3.小结 1.前言 哈喽大家好吖&#xff0c;今天来给大家带来Java中的JDBC的讲解&#xff0c;之前学习的都是操作…

Python 爬虫如何伪装 Referer?从随机生成到动态匹配

一、Referer 的作用与重要性 Referer 是 HTTP 请求头中的一个字段&#xff0c;用于标识请求的来源页面。它在网站的正常运行中扮演着重要角色&#xff0c;例如用于统计流量来源、防止恶意链接等。然而&#xff0c;对于爬虫来说&#xff0c;Referer 也可能成为被识别为爬虫的关…

【MySQL】表的约束(主键、唯一键、外键等约束类型详解)、表的设计

目录 1.数据库约束 1.1 约束类型 1.2 null约束 — not null 1.3 unique — 唯一约束 1.4 default — 设置默认值 1.5 primary key — 主键约束 自增主键 自增主键的局限性&#xff1a;经典面试问题&#xff08;进阶问题&#xff09; 1.6 foreign key — 外键约束 1.7…

基于STC89C52RC和8X8点阵屏、独立按键的小游戏《打砖块》

目录 系列文章目录前言一、效果展示二、原理分析三、各模块代码1、8X8点阵屏2、独立按键3、定时器04、定时器1 四、主函数总结 系列文章目录 前言 用的是普中A2开发板&#xff0c;外设有&#xff1a;8X8LED点阵屏、独立按键。 【单片机】STC89C52RC 【频率】12T11.0592MHz 效…

数字电子技术基础(五十)——硬件描述语言简介

目录 1 硬件描述语言简介 1.1 硬件描述语言简介 1.2 硬件编程语言的发展历史 1.3 两种硬件描述的比较 1.4 硬件描述语言的应用场景 1.5 基本程序结构 1.5.1 基本程序结构 1.5.2 基本语句和描述方法 1.5.3 仿真 1 硬件描述语言简介 1.1 硬件描述语言简介 硬件描述语…