نحوه آپلود و ارائه تصاویر با استفاده از Cloud Storage، Firestore و Cloud Run

نحوه آپلود و ارائه تصاویر با استفاده از Cloud Storage، Firestore و Cloud Run

درباره این codelab

subjectآخرین به‌روزرسانی: آوریل ۴, ۲۰۲۵
account_circleنویسنده: یکی از کارمندان Google

1. مقدمه

در این کد لبه، نحوه آپلود و سرویس تصاویر را با استفاده از Cloud Storage، Firestore و Cloud Run خواهید آموخت. همچنین یاد خواهید گرفت که چگونه از کتابخانه های سرویس گیرنده Google برای احراز هویت برای برقراری تماس با Gemini استفاده کنید.

  • نحوه استقرار برنامه FastAPI در Cloud Run
  • نحوه استفاده از کتابخانه های سرویس گیرنده گوگل برای احراز هویت
  • نحوه آپلود فایل در فضای ذخیره سازی ابری با استفاده از سرویس Cloud Run
  • نحوه خواندن و نوشتن داده ها در Firestore
  • نحوه بازیابی و نمایش تصاویر از Cloud Storage در سرویس Cloud Run

2. راه اندازی و الزامات

متغیرهای محیطی را تنظیم کنید که در سراسر این کد لبه مورد استفاده قرار خواهند گرفت.

PROJECT_ID=dogfood-gcf-saraford
REGION
=us-central1
GCS_BUCKET_NAME
=dogfood-gcf-saraford-codelab-wietse-2

SERVICE_NAME
=fastapi-storage-firestore
SERVICE_ACCOUNT
=fastapi-storage-firestore-sa
SERVICE_ACCOUNT_ADDRESS
=$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com

API ها را فعال کنید

gcloud services enable run.googleapis.com \
                       storage.googleapis.com \
                       firestore.googleapis.com \
                       cloudbuild.googleapis.com \
                       artifactregistry.googleapis.com

یک سطل Cloud Storage برای ذخیره تصاویر ایجاد کنید

gsutil mb -p dogfood-gcf-saraford -l us-central1 gs://$GCS_BUCKET_NAME

اجازه دسترسی عمومی به سطلی که در آن می توانید تصاویر را در وب سایت آپلود و نمایش دهید:

gsutil iam ch allUsers:objectViewer gs://$GCS_BUCKET_NAME

با اجرای این دستور یک حساب کاربری ایجاد کنید:

gcloud iam service-accounts create $SERVICE_ACCOUNT \
   
--display-name="SA for CR $SERVICE_ACCOUNT"

و به SA اجازه دسترسی به Firestore و سطل GCS را بدهید

gcloud projects add-iam-policy-binding $PROJECT_ID \
   
--member="serviceAccount:$SERVICE_ACCOUNT_ADDRESS" \
   
--role="roles/datastore.user"

gsutil iam ch serviceAccount
:$SERVICE_ACCOUNT_ADDRESS:roles/storage.objectAdmin gs://$GCS_BUCKET_NAME

3. پایگاه داده Firestore را ایجاد کنید

برای ایجاد پایگاه داده Firestore دستور زیر را اجرا کنید

gcloud firestore databases create --location=nam5

4. برنامه را ایجاد کنید

یک دایرکتوری برای کد خود ایجاد کنید.

mkdir codelab-cr-fastapi-firestore-gcs
cd codelab
-cr-fastapi-firestore-gcs

ابتدا با ایجاد دایرکتوری قالب ها، قالب های html را ایجاد می کنید.

mkdir templates
cd templates

یک فایل جدید به نام index.html با محتوای زیر ایجاد کنید:

<!DOCTYPE html>
<html>
<head>
    <title>Cloud Run Image Upload Demo</title>
    <style>
        body { font-family: sans-serif; padding: 20px; }
        .upload-form { margin-bottom: 20px; padding: 15px; border: 1px solid #ccc; border-radius: 5px; background-color: #f9f9f9; }
        .image-list { margin-top: 30px; }
        .image-item { border-bottom: 1px solid #eee; padding: 10px 0; }
        .image-item img { max-width: 100px; max-height: 100px; vertical-align: middle; margin-right: 10px;}
        .error { color: red; font-weight: bold; margin-top: 10px;}
    </style>
</head>
<body>

    <h1>Upload an Image</h1>
    <p>Files will be uploaded to GCS bucket: <strong>{{ bucket_name }}</strong> and metadata stored in Firestore.</p>

    <div class="upload-form">
        <form action="/upload" method="post" enctype="multipart/form-data">
            <input type="file" name="file" accept="image/*" required>
            <button type="submit">Upload Image</button>
        </form>
        {% if error_message %}
            <p class="error">{{ error_message }}</p>
        {% endif %}
    </div>

    <div class="image-list">
        <h2>Recently Uploaded Images:</h2>
        {% if images %}
            {% for image in images %}
            <div class="image-item">
                <a href="{{ image.gcs_url }}" target="_blank">
                   <img src="{{ image.gcs_url }}" alt="{{ image.filename }}" title="Click to view full size">
                </a>
                <span>{{ image.filename }}</span>
                <small>(Uploaded: {{ image.uploaded_at.strftime('%Y-%m-%d %H:%M:%S') if image.uploaded_at else 'N/A' }})</small><br/>
                <small><a href="{{ image.gcs_url }}" target="_blank">{{ image.gcs_url }}</a></small>
            </div>
            {% endfor %}
        {% else %}
            <p>No images uploaded yet or unable to retrieve list.</p>
        {% endif %}
    </div>

</body>
</html>

حالا کد پایتون و فایل های دیگر را در دایرکتوری ریشه ایجاد کنید

cd ..

یک فایل .gcloudignore با محتوای زیر ایجاد کنید:

__pycache__

یک فایل به نام main.py با محتوای زیر ایجاد کنید:

import os
import datetime
from fastapi import FastAPI, File, UploadFile, Request, Form
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from google.cloud import storage, firestore

# --- Configuration ---
# Get bucket name and firestore collection from Cloud Run env vars
GCS_BUCKET_NAME = os.environ.get("GCS_BUCKET_NAME", "YOUR_BUCKET_NAME_DEFAULT")
FIRESTORE_COLLECTION = os.environ.get("FIRESTORE_COLLECTION", "YOUR_FIRESTORE_DEFAULT")

# --- Initialize Google Client Libraries ---
# These client libraries will use the Application Default Credentials
# for your service account within the Cloud Run environment
storage_client = storage.Client()
firestore_client = firestore.Client()

# --- FastAPI App ---
app = FastAPI()
templates = Jinja2Templates(directory="templates")

# --- Routes ---
@app.get("/", response_class=HTMLResponse)
async def read_root(request: Request):
    """Serves the main upload form."""
   
   
# Query Firestore for existing images to display
   
images = []
   
try:
       
docs = firestore_client.collection(FIRESTORE_COLLECTION).order_by(
           
"uploaded_at", direction=firestore.Query.DESCENDING
       
).limit(10).stream() # Get latest 10 images
       
for doc in docs:
           
images.append(doc.to_dict())
   
except Exception as e:
       
print(f"Warning: Could not fetch images from Firestore: {e}")
       
# Continue without displaying images if Firestore query fails

   
return templates.TemplateResponse("index.html", {
       
"request": request,
       
"bucket_name": GCS_BUCKET_NAME,
       
"images": images # Pass images to the template
   
})

@app.post("/upload")
async def handle_upload(request: Request, file: UploadFile = File(...)):
    """Handles file upload, saves to GCS, and records in Firestore."""
   
if not file:
       
return {"message": "No upload file sent"}
   
elif not GCS_BUCKET_NAME or GCS_BUCKET_NAME == "YOUR_BUCKET_NAME_DEFAULT":
         
return {"message": "GCS Bucket Name not configured."}, 500 # Internal Server Error

   
try:
       
# 1. Upload to GCS
       
# note: to keep the demo code short, there are no file verifications
       
# for an actual real-world production app, you will want to add checks
       
gcs_url = upload_to_gcs(file, GCS_BUCKET_NAME)

       
# 2. Save metadata to Firestore
       
save_metadata_to_firestore(file.filename, gcs_url, FIRESTORE_COLLECTION)

       
# Redirect back to the main page after successful upload
       
return RedirectResponse(url="/", status_code=303) # Redirect using See Other

   
except Exception as e:
       
print(f"Upload failed: {e}")

       
return templates.TemplateResponse("index.html", {
           
"request": request,
           
"bucket_name": GCS_BUCKET_NAME,
           
"error_message": f"Upload failed: {e}",
           
"images": [] # Pass empty list on error or re-query
       
}, status_code=500)

# --- Helper Functions ---
def upload_to_gcs(uploadedFile: UploadFile, bucket_name: str) -> str:
    """Uploads a file to Google Cloud Storage and returns the public URL."""
   
try:
       
bucket = storage_client.bucket(bucket_name)

       
# Create a unique blob name (e.g., timestamp + original filename)
       
timestamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%d%H%M%S")
       
blob_name = f"{timestamp}_{uploadedFile.filename}"
       
blob = bucket.blob(blob_name)

       
# Upload the file
       
# Reset file pointer just in case
       
uploadedFile.file.seek(0)
       
blob.upload_from_file(uploadedFile.file, content_type=uploadedFile.content_type)

       
print(f"File {uploadedFile.filename} uploaded to gs://{bucket_name}/{blob_name}")
       
return blob.public_url # Return the public URL

   
except Exception as e:
       
print(f"Error uploading to GCS: {e}")
       
raise  # Re-raise the exception for FastAPI to handle

def save_metadata_to_firestore(filename: str, gcs_url: str, collection_name: str):
    """Saves image metadata to Firestore."""
   
try:
       
doc_ref = firestore_client.collection(collection_name).document()
       
doc_ref.set({
           
'filename': filename,
           
'gcs_url': gcs_url,
           
'uploaded_at': firestore.SERVER_TIMESTAMP # Use server timestamp
       
})
       
print(f"Metadata saved to Firestore collection {collection_name}")
   
except Exception as e:
       
print(f"Error saving metadata to Firestore: {e}")
       
# Consider raising the exception or handling it appropriately
       
raise # Re-raise the exception

یک Dockerfile با محتوای زیر ایجاد کنید:

# Build stage
FROM python:3.12-slim AS builder

WORKDIR /app

# Install poetry
RUN pip install poetry
RUN poetry self add poetry-plugin-export

# Copy poetry files
COPY pyproject.toml poetry.lock* ./

# Copy application code
COPY . .

# Export dependencies to requirements.txt
RUN poetry export -f requirements.txt --output requirements.txt

# Final stage
FROM python:3.12-slim

WORKDIR /app

# Copy files from builder
COPY --from=builder /app/ .

# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Compile bytecode to improve startup latency
# -q: Quiet mode
# -b: Write legacy bytecode files (.pyc) alongside source
# -f: Force rebuild even if timestamps are up-to-date
RUN python -m compileall -q -b -f .

# Expose port
EXPOSE 8080

# Run the application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]

و یک pyproject.toml با موارد زیر ایجاد کرد

[tool.poetry]
name = "cloud-run-fastapi-demo"
version = "0.1.0"
description = "Demo FastAPI app for Cloud Run showing GCS upload and Firestore integration."
authors = ["Your Name <you@example.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.12"
fastapi = "^0.110.0"
uvicorn = {extras = ["standard"], version = "^0.29.0"} # Includes python-multipart
google-cloud-storage = "^2.16.0"
google-cloud-firestore = "^2.16.0"
jinja2 = "^3.1.3"
python-multipart = "^0.0.20"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

5. در Cloud Run مستقر شوید

در زیر فرمان استقرار در Cloud Run آمده است. کد شما فشرده شده و به Cloud Build ارسال می شود که از Dockerfile برای ایجاد تصویر استفاده می کند.

از آنجایی که این یک استقرار مبتنی بر منبع در Cloud Run است، در کنسول Cloud برای این سرویس، یک برگه Source حاوی کد خود را خواهید دید.

gcloud run deploy $SERVICE_NAME \
 --source . \
 --allow-unauthenticated \
 --service-account=$SERVICE_ACCOUNT_ADDRESS \
 --set-env-vars=GCS_BUCKET_NAME=$GCS_BUCKET_NAME \
 --set-env-vars=FIRESTORE_COLLECTION=$FIRESTORE_COLLECTION

6. سرویس خود را تست کنید

URL سرویس را در مرورگر وب خود باز کنید و یک تصویر آپلود کنید. آن را در لیست خواهید دید.

7. مجوزها را در سطل عمومی Cloud Storage تغییر دهید

همانطور که قبلا ذکر شد، این کد لبه از یک سطل GCS عمومی استفاده می کند. توصیه می شود با اجرای دستور زیر، سطل را حذف کنید یا دسترسی allUsers به ​​سطل را حذف کنید:

gsutil iam ch -d allUsers:objectViewer gs://$GCS_BUCKET_NAME

با اجرای این دستور می توانید تأیید کنید که دسترسی allUsers حذف شده است:

gsutil iam get gs://$GCS_BUCKET_NAME

8. تبریک میگم

برای تکمیل کد لبه تبریک می گویم!

آنچه را پوشش داده ایم

  • نحوه استقرار برنامه FastAPI در Cloud Run
  • نحوه استفاده از کتابخانه های سرویس گیرنده گوگل برای احراز هویت
  • نحوه آپلود فایل در فضای ذخیره سازی ابری با استفاده از سرویس Cloud Run
  • نحوه خواندن و نوشتن داده ها در Firestore
  • نحوه بازیابی و نمایش تصاویر از Cloud Storage در سرویس Cloud Run

9. پاک کن

برای حذف سرویس Cloud Run، به Cloud Run Cloud Console در https://console.cloud.google.com/run بروید و سرویس را حذف کنید.

برای حذف سطل Cloud Storage، می توانید دستورات زیر را اجرا کنید:

echo "Deleting objects in gs://$GCS_BUCKET_NAME..."
gsutil rm
-r gs://$GCS_BUCKET_NAME/*

echo
"Deleting bucket gs://$GCS_BUCKET_NAME..."
gsutil rb gs
://$GCS_BUCKET_NAME

اگر تصمیم به حذف کل پروژه دارید، می‌توانید به https://console.cloud.google.com/cloud-resource-manager بروید، پروژه‌ای را که در مرحله ۲ ایجاد کرده‌اید انتخاب کنید و حذف را انتخاب کنید. اگر پروژه را حذف کنید، باید پروژه ها را در Cloud SDK خود تغییر دهید. با اجرای gcloud projects list می توانید لیست تمام پروژه های موجود را مشاهده کنید.