Как отображать документы PDF/изображения, хранящиеся в хранилище Azure, с помощью Flask + Flutter

Для создания веб-сайта я использую серверную часть Flask и интерфейс Flutter. Я пытаюсь реализовать функцию загрузки документа на одну страницу upload.dart (в хранилище Azure), а затем отображать документ на странице подтверждения upload.dart. Однако я просто получаю пустой экран, когда запускаю код для отображения документа (появляются другие вещи, но место для изображения/pdf пусто).

Это мой код фляги app.py

from flask import Flask
from flask import send_from_directory, render_template
from flask import redirect, url_for, request, jsonify
import os 
from datetime import datetime, timedelta
from werkzeug.utils import secure_filename
from flask_cors import CORS
from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient
from azure.storage.blob import generate_blob_sas, BlobSasPermissions


app = Flask(__name__)
azure_blob_storage_origin = "https://aishore.blob.core.windows.net"
CORS(app, resources = {r"/*": {"origins": [azure_blob_storage_origin, "*"]}}) 

connect_str = 'DefaultEndpointsProtocol=CONNECTION_STRING_ETC'
if not connect_str:
    raise ValueError("Please set the AZURE_STORAGE_CONNECTION_STRING environment variable")
container_name = "documents" # azure storage account container name

blob_service_client = BlobServiceClient.from_connection_string(conn_str=connect_str) 
# generates container if it doesn't already exist 
try:
    container_client = blob_service_client.get_container_client(container=container_name) 
    container_client.get_container_properties()
except Exception as e:
    container_client = blob_service_client.create_container(container_name) # create a container in the storage account if it does not exist


@app.route('/')
def render_page():
    return render_template('/index.html')

# uploading documents page 
@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        return jsonify({
            "message": "no file available", 
            "status": "fail"
            }), 400
    file = request.files['file']
    if file.filename == '':
        return jsonify({
            "message": "no selected file", 
            "status": "fail"
            }), 400
    if file: 
        timestamp = datetime.now().strftime('%Y-%m-%d_%H:%M:%S')
        filename = secure_filename(f"{timestamp}_{file.filename}")
        try:
            blob_client = container_client.get_blob_client(filename)
            blob_client.upload_blob(file.stream, overwrite=True)
            file_url = blob_client.url

            return jsonify({
                "message": "File uploaded successfully",
                "status": "success",
                "file_url": file_url,
            }), 200

        except Exception as e: 
            return jsonify({
                "message": "Failed to upload file, Error ${e}",
                "status": "fail", 
            }), 500

# rerouting for uploaded files to either display the image or provide a download link to the pdf 
@app.route('/upload/<path:filename>', methods=['GET'])
def uploaded_file(filename):
    try:
        blob_client = container_client.get_blob_client(filename)
        sas_token = generate_blob_sas(
            account_name=blob_service_client.account_name,
            container_name=container_name,
            blob_name=blob_client.blob_name,
            account_key=blob_service_client.credential.account_key,
            permission=BlobSasPermissions(read=True),
            expiry=datetime.now(datetime.UTC)() + timedelta(hours=1)
        )
        signed_url = f"{blob_client.primary_endpoint}/{container_name}/{blob_client.blob_name}?{sas_token}"
        return jsonify({
            "file_url": signed_url,
            "status": "success"
        })

    except Exception as e:
        return jsonify({"message": f"Failed to retrieve file: {str(e)}", "status": "fail"}), 500
if __name__ == '__main__':
    app.run(debug=True)

Это мой загруженный флаттер-код .dart:

import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'dart:html' as html;
import 'package:http/http.dart' as http;

class UploadedPage extends StatefulWidget {
  final String signedUrl;

  const UploadedPage({
    super.key,
    required this.signedUrl,
  });
  @override
  State<UploadedPage> createState() => _UploadedPageState();
}

class _UploadedPageState extends State<UploadedPage> {

Widget _displayPDFInIframe(String url) {
  return SizedBox(
    height: 600,
    width: 800,
    child: HtmlElementView(
      viewType: 'iframe',
      key: UniqueKey(),
      onPlatformViewCreated: (int viewId) {
        final iframe = html.IFrameElement()
          ..src = url
          ..style.border = 'none'
          ..style.width = '100%'
          ..style.height = '100%';
        html.document.getElementById('iframe_$viewId')?.append(iframe);
      },
    ),
  );
}

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text(
                'File uploaded successfully!',
                style: TextStyle(fontSize: 24),
              ),
              const SizedBox(height: 16.0),
              SelectableText('TEMP URL ${widget.signedUrl}'),

              const SizedBox(height: 16.0),
              _displayPDFInIframe(widget.signedUrl),
            ],
          ),
        ),
      ),
    );
  }
}

Извините за спам с кодом, я не уверен, с какой частью кода у меня могут возникнуть проблемы.

Поскольку я использую токен SAS (или пытаюсь это сделать), мой уровень доступа установлен на частный. Однако я заметил, что когда я распечатал widget.signedUrl на веб-сайте, на нем нет токена SAS, и я не уверен, что это является причиной проблемы. Я попытался изменить код в app.py, чтобы у SignedUrl был токен SAS, но при этом возникли ошибки. Я также пробовал не использовать iframe, но что бы я ни пытался, я получаю либо значки ошибок, либо вообще ничего. Я могу предоставить любой другой код, который я пробовал, но я думаю, что использование iframes ближе всего к тому, что мне может понадобиться.

Частично решено! Я понял, что неправильно обозначил переменную в своем коде upload.dart (перед страницей uploaded.dart), а также добавил CORS в свою учетную запись хранения. Сейчас работаю с использованием iframe, но мне удалось открыть PDF-файл на новой странице.

Lax 25.06.2024 11:09
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
87
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проверьте приведенный ниже код сервера Flask для загрузки файлов в хранилище BLOB-объектов Azure и получения подписанных URL-адресов для этих файлов. На стороне клиента есть приложение Flutter, предназначенное для получения и отображения файлов (изображений и PDF-файлов) с сервера Flask.

Приведенный ниже код Flask загружает файлы в хранилище BLOB-объектов Azure и генерирует URL-адреса SAS (подпись общего доступа) для загрузки этих файлов.

Добавлен Cors CORS(app, resources = {r"/upload/*": {"origins": "*"}}) , чтобы разрешить использование Cors со всех URL-адресов. Без этого я столкнулся с проблемой Cors в Flutter.

from flask import Flask, render_template, jsonify, request
.....

app = Flask(__name__)
CORS(app, resources = {r"/upload/*": {"origins": "*"}})  # Specify the resource path for CORS
.....
....

Обязательно добавьте Cors в учетную запись хранения.

Код Flutter извлекает и отображает изображение из конечной точки URL-адреса с помощью HTTP-запросов.

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_cached_pdfview/flutter_cached_pdfview.dart'; 

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Display image from URL',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: DisplayFileFromUrl(),
    );
  }
}

class DisplayFileFromUrl extends StatefulWidget {
  DisplayFileFromUrl({Key? key}) : super(key: key);

  @override
  _DisplayFileFromUrlState createState() => _DisplayFileFromUrlState();
}

class _DisplayFileFromUrlState extends State<DisplayFileFromUrl> {
  String? fileUrl; 
  bool isLoading = false;
  late String baseUrl;
  late String filename;

  @override
  void initState() {
    super.initState();
    baseUrl = 'http://127.0.0.1:5000/upload/';
    filename = 'sample.jpg/.pdf'; // Replace with your filename 
    fetchFile();
  }

  Future<void> fetchFile() async {
    setState(() {
      isLoading = true;
    });

    final String url = '$baseUrl$filename';

    try {
      final response = await http.get(Uri.parse(url));

      if (response.statusCode == 200) {
        final Map<String, dynamic> responseData = json.decode(response.body);
        fileUrl = responseData['file_url']; 

        setState(() {
          isLoading = false;
        });
      } else {
        throw Exception('Failed to load file');
      }
    } catch (error) {
      print('Error fetching file: $error');
      setState(() {
        isLoading = false;
      });
    }
  }

  Widget buildFileWidget() {
    if (isLoading) {
      return Center(child: CircularProgressIndicator());
    } else if (fileUrl != null) {
      if (fileUrl!.toLowerCase().endsWith('.pdf')) {
        
        return PDF().cachedFromUrl(fileUrl!);
      } else {
        
        return CachedNetworkImage(
          imageUrl: fileUrl!,
          placeholder: (context, url) => CircularProgressIndicator(),
          errorWidget: (context, url, error) => Icon(Icons.error),
          fit: BoxFit.contain, 
        );
      }
    } else {
      return Center(child: Text('File not found'));
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Display File from URL'),
      ),
      body: Center(
        child: buildFileWidget(),
      ),
    );
  }
}

Отображение изображения:

При отображении PDF я получаю черную страницу. Я следовал этому документу и использовал flutter_pdfview, но столкнулся с ошибкой «TargetPlatform.windows еще не поддерживается плагином pdfview_flutter».

В документации этого пакета четко сказано, что flutter_pdfview поддерживается только для платформ iOS и Android. Вместо этого рассмотрите пакет syncfusion_flutter_pdfviewer.

Спасибо! Рассмотрю syncfusion, что кажется удивительным, но в первую очередь для корпораций.

Lax 25.06.2024 11:09

Другие вопросы по теме