使用 iOS 快捷指令上传文件到 MinIO(S3)

有点特殊需求,偶尔需要将 iPhone 上的照片传到 Windows 电脑上。基于现实考虑,采用了 MinIO 作为存储。

一、Docker 搭建 MinIO 服务

编写 compose.yml

services:
  minio:
    image: minio/minio
    container_name: minio
    ports:
      - "127.0.0.1:9000:9000"
      - "127.0.0.1:9001:9001"
    volumes:
      - ./data:/data
    environment:
      - MINIO_ROOT_USER=yipai_minio
      - MINIO_ROOT_PASSWORD=yipai_minio_password
      - MINIO_BROWSER_REDIRECT_URL=https://minio.yipai.me/minio/ui
    command: server /data --console-address ":9001"
    restart: unless-stopped

启动服务:sudo docker compose up -d

二、配置 nginx 反向代理

先把域名指向 VPS,然后获取证书:

sudo certbot certonly --nginx --agree-tos --no-eff-email --staple-ocsp --preferred-challenges http -m [email protected] -d minio.yipai.me

编写 nginx 配置:

upstream minio_s3 {
   least_conn;
   server 127.0.0.1:9000;
}

upstream minio_console {
   least_conn;
   server 127.0.0.1:9001;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name minio.yipai.me;

    access_log  /var/log/nginx/minio.yipai.me.access.log;
    error_log   /var/log/nginx/minio.yipai.me.error.log;

    ssl_certificate /etc/letsencrypt/live/minio.yipai.me/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/minio.yipai.me/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/minio.yipai.me/fullchain.pem;

    ssl_session_timeout  5m;
    ssl_session_cache shared:MozSSL:10m;
    ssl_session_tickets off;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_dhparam /etc/ssl/certs/dhparam.pem;

   # Allow special characters in headers
   ignore_invalid_headers off;
   # Allow any size file to be uploaded.
   # Set to a value such as 1000m; to restrict file size to a specific value
   client_max_body_size 0;
   # Disable buffering
   proxy_buffering off;
   proxy_request_buffering off;

   location / {
      proxy_set_header Host $http_host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;

      proxy_connect_timeout 300;
      # Default is HTTP/1, keepalive is only enabled in HTTP/1.1
      proxy_http_version 1.1;
      proxy_set_header Connection "";
      chunked_transfer_encoding off;

      proxy_pass http://minio_s3; # This uses the upstream directive definition to load balance
   }

   location /minio/ui/ {
      rewrite ^/minio/ui/(.*) /$1 break;
      proxy_set_header Host $http_host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_set_header X-NginX-Proxy true;

      # This is necessary to pass the correct IP to be hashed
      real_ip_header X-Real-IP;

      proxy_connect_timeout 300;

      # To support websockets in MinIO versions released after January 2023
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
      # Some environments may encounter CORS errors (Kubernetes + Nginx Ingress)
      # Uncomment the following line to set the Origin request to an empty string
      # proxy_set_header Origin '';

      chunked_transfer_encoding off;

      proxy_pass http://minio_console; # This uses the upstream directive definition to load balance
   }
}

重启 nginx:sudo systemctl restart nginx

此时应该能通过域名 minio.yipai.me 访问 minio 网页控制台了。

三、搭建 MinIO 预签名服务

MinIO 不支持传统的 Basic Auth 或 Query String 签名方式,需要使用 AWS 的 AWS4-HMAC-SHA256 签名方法进行认证。但我们用快捷指令的方式,很难实现 AWS4-HMAC-SHA256 签名。所以需要用另外一个服务生成预签名 URL。我这里使用 Python + Vercel 的方式。

编写 app.py

from flask import Flask, request, jsonify
import boto3
import os

app = Flask(__name__)

# 从环境变量中读取配置
MINIO_ENDPOINT = os.environ.get('MINIO_ENDPOINT')
MINIO_ACCESS_KEY = os.environ.get('MINIO_ACCESS_KEY')
MINIO_SECRET_KEY = os.environ.get('MINIO_SECRET_KEY')
BUCKET_NAME = os.environ.get('BUCKET_NAME')
API_KEY = os.environ.get('API_KEY')  # 用于 API 身份验证

# 创建 S3 客户端
s3_client = boto3.client(
    's3',
    endpoint_url=MINIO_ENDPOINT,
    aws_access_key_id=MINIO_ACCESS_KEY,
    aws_secret_access_key=MINIO_SECRET_KEY
)

@app.route('/upload', methods=['POST'])
def upload_file():
    # 验证 API 密钥
    provided_api_key = request.headers.get('X-API-KEY')
    if provided_api_key != API_KEY:
        return jsonify({'error': 'Unauthorized'}), 401

    # 检查是否包含文件
    if 'file' not in request.files:
        return jsonify({'error': 'No file part in the request'}), 400

    file = request.files['file']

    if file.filename == '':
        return jsonify({'error': 'No selected file'}), 400

    # 上传到 MinIO
    try:
        s3_client.upload_fileobj(
            file,
            BUCKET_NAME,
            file.filename,
            ExtraArgs={'ContentType': file.content_type}
        )
        file_url = f"{MINIO_ENDPOINT}/{BUCKET_NAME}/{file.filename}"
        return jsonify({'url': file_url}), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 500

if __name__ == '__main__':
    port = int(os.environ.get('PORT', 5000))
    app.run(debug=False, port=port)

部署到 Vercel 还需要 requirements.txtvercel.json,详见:https://github.com/robustmaster/minio-pre-signed-url

也可以直接克隆我这个仓库。

这个程序从环境变量里面拿 MINIO_ENDPOINT、MINIO_ACCESS_KEY、MINIO_SECRET_KEY、BUCKET_NAME、API_KEY,部署的时候需要在 Vercel 的环境变量里面添加这几项。

  • MINIO_ENDPOINT,就是 MinIO 的网址,比如:https://minio.yipai.me
  • MINIO_ACCESS_KEY,在 MinIO 的 Web 控制台添加。
  • MINIO_SECRET_KEY,在 MinIO 的 Web 控制台添加。
  • BUCKET_NAME,在 MinIO 的 Web 控制台添加存储。
  • API_KEY,通过预签名网址上传文件时候的身份验证,防止被别人调用。自己生成一个,后续会用到。

这样就可以部署成功了,可以添加个自定义域名,比如:https://minio-psk.yipai.me。

四、创建 iOS 快捷指令

参考下图:

需要注意“获取网页内容”这个步骤,:

  1. 填写的预签名网址要在绑定的域名后增加 /upload 路径,比如: https://minio-psk.yipai.me/upload。
  2. 选择 POST 方法。
  3. 头部添加 X-API-KEY 的请求头,其值为上一步中添加的 API_KEY 环境变量的值。
  4. 请求体选择“文件”,key 填 file,值选择“照片”。iOS 快捷指令应用的这步交互很奇怪,值选择“照片”后,值的字段显示不出来,但实际是选中了的。

如此,就可以使用快捷指令上传文件到 MinIO 指定的存储中了。

# # #


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注