# How File Upload Handling Works with Qiniu Cloud Storage in Mini‑Shop Server

> Learn how Mini-Shop Server handles file uploads using Qiniu cloud storage. It deduplicates files via MD5, streams to Qiniu, and stores metadata locally.

- Repository: [A粒麦子/mini-shop-server](https://github.com/allen7d/mini-shop-server)
- Tags: how-to-guide
- Published: 2026-02-24

---

**Mini‑Shop Server delegates file uploads to a `QiniuUploader` class that deduplicates files by MD5 hash, streams them to Qiniu object storage via the official SDK, and persists metadata to a local database.**

The `allen7d/mini-shop-server` repository implements a decoupled file upload system for its Flask-based e-commerce backend. When handling **file upload handling with Qiniu cloud storage**, the application uses a dedicated uploader class to stream multipart files directly to Qiniu (七牛云) while maintaining local records for deduplication and retrieval.

## Architecture Overview

The upload pipeline is split between the API layer and the storage extension. The endpoint in [`app/api/cms/file.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/api/cms/file.py) receives `FileStorage` objects, while the heavy lifting is performed by `QiniuUploader` in [`app/extensions/file/qiniu_uploader.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/extensions/file/qiniu_uploader.py). This separation allows the system to switch between cloud and local storage without changing route logic.

## Step‑by‑Step Upload Flow

### 1. File Reception and Deduplication

When a multipart request hits the CMS file endpoint, the system instantiates a `QiniuUploader` (or falls back to `LocalUploader`). For each incoming file, the uploader computes an MD5 hash via `_generate_md5`.

The implementation checks [`app/models/file.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/models/file.py) for existing records matching that hash. If found and the target folder differs, `FileDao.copy_file` in [`app/dao/file.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/dao/file.py) duplicates the database entry instead of re-uploading the binary. This prevents redundant storage costs and network traffic.

### 2. Streaming to Qiniu Object Storage

The `save` method in [`app/extensions/file/qiniu_uploader.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/extensions/file/qiniu_uploader.py) constructs an upload token using the **access key**, **secret key**, and **bucket name** defined in the Flask config under the `QINIU` section (typically set in [`config.ini`](https://github.com/allen7d/mini-shop-server/blob/main/config.ini) or [`app/config/setting.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/config/setting.py)).

Using the official Qiniu Python SDK, the method calls `put_data(token, None, file)` to stream the file bytes directly to the cloud. On success (`info.status_code == 200`), the uploader builds the public URL by concatenating the configured domain with the key returned by Qiniu: `domain + ret.get("key")`.

### 3. Database Record Creation

After a successful upload, the system creates a new `File` model record containing the original filename, a generated UUID filename, the full Qiniu URL as the storage path, file extension, size, MD5 hash, and the marker `UrlFromEnum.NETWORK` to indicate cloud-hosted assets. The API layer returns this metadata to the client as a serialized list of `File` objects.

## Configuration Requirements

To enable cloud storage, the application expects the following keys in the Flask app config:

- `ACCESS_KEY`: Qiniu account access key
- `SECRET_KEY`: Qiniu account secret key  
- `BUCKET_NAME`: Target storage bucket
- `DOMAIN`: Public domain for URL construction

These are typically loaded from [`config.ini`](https://github.com/allen7d/mini-shop-server/blob/main/config.ini) or [`app/config/setting.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/config/setting.py) under the `QINIU` section.

## Example API Usage

You can test the upload endpoint using cURL:

```bash
curl -X POST http://localhost:5000/api/v1/file/upload \
  -H "Authorization: Bearer <token>" \
  -F "file=@/path/to/image.jpg"

```

The server performs MD5 deduplication, uploads the binary to Qiniu via `put_data`, and returns JSON containing the file metadata and public URL (e.g., `https://<your-domain>/<key>`).

## Summary

- **Deduplication by MD5**: The `QiniuUploader` avoids redundant uploads by hashing files and checking against existing records in [`app/models/file.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/models/file.py).
- **Direct streaming**: Files are sent to Qiniu using `put_data` from the official SDK, with authentication tokens built from config keys in the `QINIU` section.
- **Flexible storage**: The design decouples upload logic from API routes via [`app/extensions/file/qiniu_uploader.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/extensions/file/qiniu_uploader.py), enabling easy fallback to `LocalUploader`.
- **Metadata tracking**: Each upload creates a database entry with `UrlFromEnum.NETWORK` to distinguish cloud assets from local files.

## Frequently Asked Questions

### How does Mini‑Shop Server prevent duplicate file uploads?

The system computes an MD5 hash of each file via `_generate_md5` and queries the database for existing matches. If the same hash exists, it either reuses the existing record or copies it to a new folder using `FileDao.copy_file`, skipping the actual network upload to Qiniu.

### What Qiniu SDK method is used to upload files?

The `save` method in [`app/extensions/file/qiniu_uploader.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/extensions/file/qiniu_uploader.py) uses `put_data(token, None, file)` from the official Qiniu Python SDK. This streams the file bytes directly to object storage and returns a public URL constructed from the configured domain and the key returned by Qiniu.

### Where is the Qiniu configuration stored?

Configuration values including `ACCESS_KEY`, `SECRET_KEY`, `BUCKET_NAME`, and `DOMAIN` are stored in the Flask app config under the `QINIU` section, typically defined in [`config.ini`](https://github.com/allen7d/mini-shop-server/blob/main/config.ini) or [`app/config/setting.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/config/setting.py).

### Can the system fall back to local storage?

Yes. The API endpoint in [`app/api/cms/file.py`](https://github.com/allen7d/mini-shop-server/blob/main/app/api/cms/file.py) can instantiate either `QiniuUploader` or `LocalUploader` based on configuration or runtime conditions, allowing the same upload interface to work with both cloud and local filesystems without changing the route code.