You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ionic 6如何编写HTTP请求代码实现Firebase Hosting文件上传

Firebase Hosting REST API 文件上传问题解答

问题1:上传请求体格式说明

上传文件时直接将gzip压缩后的文件二进制内容放入请求体即可,不需要封装为JSON、表单等其他结构化对象。

官方示例中标注的content-of-file2.gz,指的就是对应文件经gzip压缩后的原始二进制流,没有额外封装格式。需要注意:URL路径中的fileHash参数是压缩前原文件内容的SHA256哈希值,上传的gzip内容解压后必须和计算哈希的原文件完全一致,否则会触发服务端校验失败。

完整的文件上传流程需要按顺序执行,跳过任意步骤都会报错:

  • 先创建一个新的Hosting站点版本,拿到对应的versionId
  • 调用populateFiles接口,传入所有待上传文件的部署路径和对应SHA256哈希,接口会返回当前版本缺失、需要实际上传的文件列表(已存在于Hosting缓存的文件不需要重复上传)
  • 对每个需要上传的文件,将原文件内容gzip压缩后,调用单文件上传接口,请求体直接放压缩后的二进制流
  • 所有文件上传完成后,调用版本finalize接口将版本标记为完成,再执行发布操作即可

问题2:Ionic 6 实现方案与代码修正

你原有代码存在以下几个直接导致运行失败的问题:

  • HttpHeaders是不可变对象,调用set方法会返回新的实例,你没有接收返回值,实际请求根本没带上你设置的头参数
  • 请求头配置逻辑混乱:单文件上传接口要求Content-Type为application/octet-stream,你额外设置application/json会导致服务端无法解析请求体
  • 作用域错误:你已经在构造函数注入了private http: HttpClient,发起POST请求时应该调用this.http.post,直接写http.post会找不到实例
  • 缺少gzip压缩步骤:直接上传原文件blob会和服务端记录的哈希值不匹配,校验不通过
  • 缺少文件哈希计算逻辑:URL中的fileHash是原文件内容的SHA256值,不是固定占位字符串
  • 嵌套订阅属于RxJS反模式,容易造成内存泄漏和逻辑混乱

可直接参考的实现代码

首先安装需要的依赖:

npm install crypto-js pako
npm install -D @types/crypto-js

核心上传逻辑代码:

import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import * as CryptoJS from 'crypto-js';
import * as pako from 'pako';
import { switchMap } from 'rxjs/operators';
import { from, Observable } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class FirebaseHostingService {
  // 替换为你自己的站点配置
  private readonly siteId = '替换为你的Firebase站点ID';
  private readonly accessToken = '替换为有Hosting写权限的OAuth2令牌';

  constructor(private http: HttpClient) {}

  /**
   * 上传单个文件到指定Hosting版本
   * @param versionId 已创建的Hosting版本ID
   * @param targetPath 文件部署到站点的路径,例如/index.html
   * @param fileBlob 待上传的原文件Blob对象
   */
  uploadFile(versionId: string, targetPath: string, fileBlob: Blob): Observable<any> {
    return from(this.readFileAsArrayBuffer(fileBlob)).pipe(
      switchMap(async (fileBuffer) => {
        // 基于原文件内容计算SHA256哈希
        const wordArray = CryptoJS.lib.WordArray.create(fileBuffer as any);
        const fileHash = CryptoJS.SHA256(wordArray).toString(CryptoJS.enc.Hex);
        // 对原文件做gzip压缩
        const gzippedContent = pako.gzip(new Uint8Array(fileBuffer));
        return { fileHash, gzippedContent };
      }),
      switchMap(({ fileHash, gzippedContent }) => {
        // 配置正确的请求头
        const headers = new HttpHeaders({
          'Authorization': `Bearer ${this.accessToken}`,
          'Content-Type': 'application/octet-stream'
        });
        // 发起上传请求,请求体直接传入gzip后的二进制内容
        const uploadUrl = `https://upload-firebasehosting.googleapis.com/upload/sites/${this.siteId}/versions/${versionId}/files/${fileHash}`;
        return this.http.post(uploadUrl, gzippedContent, { headers });
      })
    );
  }

  /**
   * 工具方法:将Blob读取为ArrayBuffer
   */
  private readFileAsArrayBuffer(blob: Blob): Promise<ArrayBuffer> {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => resolve(reader.result as ArrayBuffer);
      reader.onerror = reject;
      reader.readAsArrayBuffer(blob);
    });
  }

  // 读取本地assets文件并上传的示例
  uploadLocalJsonExample(versionId: string) {
    this.http.get("./assets/file.json", { responseType: "blob" })
      .pipe(switchMap(blob => this.uploadFile(versionId, '/file.json', blob)))
      .subscribe({
        next: res => console.log('上传成功', res),
        error: err => console.error('上传失败', err)
      });
  }
}

额外注意事项

  • 使用的OAuth2令牌必须申请到Firebase Hosting的写权限,否则会返回403错误
  • 不要跳过populateFiles步骤直接上传,否则服务端会返回哈希不存在的错误
  • 批量上传时,先一次性收集所有文件的路径和哈希,调用一次populateFiles接口,再上传返回列表里要求上传的文件即可,不需要每个文件都调用一次populateFiles

内容的提问来源于stack exchange,提问作者Bdna Thirysix

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 13:06:24