8

productElixir/phoenix バックエンドに2 つのコントローラーがあります。最初の API エンドポイント ( pipe_through :api) と 2 番目のコントローラーpiping through :browser:

# router.ex
scope "/api", SecretApp.Api, as: :api do
  pipe_through :api

  resources "products", ProductController, only: [:create, :index]
end

scope "/", SecretApp do
  pipe_through :browser # Use the default browser stack

  resources "products", ProductController, only: [:new, :create, :index]
end

ProductControllerelixir フォーム ヘルパーによって生成されたフォームからのリクエストを処理し、いくつかの添付ファイルを受け入れます。すべて問題ありません。このアクションによって処理される作成アクションとパラメーターは次のとおりです。

def create(conn, %{"product" => product_params}) do
  changeset = Product.changeset(%Product{}, product_params)

  case Repo.insert(changeset) do
    {:ok, _product} ->
      conn
      |> put_flash(:info, "Product created successfully.")
      |> redirect(to: product_path(conn, :index))
    {:error, changeset} ->
      render(conn, "new.html", changeset: changeset)
  end
end

ログからのパラメーター (エリクサーコードで画像のアップロードを処理するためにアークを使用しています)

[debug] Processing by SecretApp.ProductController.create/2
  Parameters: %{"_csrf_token" => "Zl81JgdhIQ8GG2c+ei0WCQ9hTjI+AAAA0fwto+HMdQ7S7OCsLQ9Trg==", "_utf8" => "✓", 
              "product" => %{"description" => "description_name", 
                "image" => %Plug.Upload{content_type: "image/png", 
                  filename: "wallpaper-466648.png", 
                  path: "/tmp/plug-1460/multipart-754282-298907-1"}, 
                "name" => "product_name", "price" => "100"}}
  Pipelines: [:browser]

Api.ProductControllerredux-from からのリクエストを処理します。このアクションによって処理されるアクション、ビュー、およびパラメーターは次のとおりです。

# action in controller
def create(conn, %{"product" => product_params}) do
  changeset = Product.changeset(%Product{}, product_params)

  case Repo.insert(changeset) do
    {:ok, _product} ->
      conn
      |> render("index.json", status: :ok)
    {:error, changeset} ->
      conn
      |> put_status(:unprocessable_entity)
      |> render("error.json", changeset: changeset)
  end
end

# product_view.ex
def render("index.json", resp=%{status: status}) do
  %{status: status}
end

def render("error.json", %{changeset: changeset}) do
  errors = Enum.into(changeset.errors, %{})

  %{
    errors: errors
  }
end

[info] POST /api/products/
[debug] Processing by SecretApp.Api.ProductController.create/2
  Parameters: %{"product" => %{"description" => "product_description", "image" => "wallpaper-466648.png", "name" => "product_name", "price" => "100"}}
  Pipelines: [:api]
[info] Sent 422 in 167ms

これらのパラメーターを使用してイメージを保存できないため、作成アクションは 422 ステータスで失敗します。バックエンドコードから画像にアクセスできないという私の問題は、JSコードにFileListオブジェクトとしてしかありません。画像をバックエンド コードに渡す方法がわかりません。この添付ファイルが私の JS コード (アップロードされた画像に関する情報を含む FileList) でどのように表されているかを次に示します。

value:FileList
  0: File
    lastModified: 1381593256801
    lastModifiedDate: Sat Oct 12 2013 18:54:16 GMT+0300 
    name: "wallpaper-466648.png"
    size: 1787293
    type: "image/png"
    webkitRelativePath: ""

私は WebkitRelativePath しか持っていません (最初のコントローラーの場合、イメージへのパスは "/tmp/plug-1460/multipart-754282-298907-1" です)。この JS オブジェクトで何ができるのか、どのようにすればよいのかわかりません。この JS オブジェクトによって表される実際の画像にアクセスします (ファイルのアップロードに関するredux-form リファレンスです)。

私たちを手伝ってくれますか?画像を見つける方法をエリクサーに説明する方法は? JSコードを使用してバックエンドに添付ファイルを送信したいだけです(非同期検証などの興味深い機能がたくさんあるため)。

役立つ場合は、完全なアプリへのリンクを次に示します

4

2 に答える 2

3

最後に、この問題を解決することができました。解決策は、送信されたredux-form パラメーターの正しいシリアル化にあります。

リクエストの開始点である私のreduxフォームは次のとおりです。

// product_form.js

import React, { PropTypes } from 'react';
import {reduxForm} from 'redux-form';

class ProductForm extends React.Component {
  static propTypes = {
    fields: PropTypes.object.isRequired,
    handleSubmit: PropTypes.func.isRequired,
    error: PropTypes.string,
    resetForm: PropTypes.func.isRequired,
    submitting: PropTypes.bool.isRequired
  };

  render() {
    const {fields: {name, description, price, image}, handleSubmit, resetForm, submitting, error} = this.props;

    return (
      <div className="product_form">
        <div className="inner">
          <form onSubmit={handleSubmit} encType="multipart/form-data">
            <div className="form-group">
              <label className="control-label"> Name </label>
              <input type="text" className="form-control" {...name} />
              {name.touched && name.error && <div className="col-xs-3 help-block">{name.error}</div>}
            </div>

            <div className="form-group">
              <label className="control-label"> Description </label>
              <input type="textarea" className="form-control" {...description} />
              {description.touched && description.error && <div className="col-xs-3 help-block">{description.error}</div>}
            </div>

            <div className="form-group">
              <label className="control-label"> Price </label>
              <input type="number" step="any" className="form-control" {...price} />
              {price.touched && price.error && <div className="col-xs-3 help-block">{price.error}</div>}
            </div>

            <div className="form-group">
              <label className="control-label"> Image </label>
              <input type="file" className="form-control" {...image} value={ null } />
              {image.touched && image.error && <div className="col-xs-3 help-block">{image.error}</div>}
            </div>

            <div className="form-group">
              <button type="submit" className="btn btn-primary" >Submit</button>
            </div>
          </form>
        </div>
      </div>
    );
  }
}

ProductForm = reduxForm({
  form: 'new_product_form',
  fields: ['name', 'description', 'price', 'image']
})(ProductForm);

export default ProductForm;

handleSubmitこのフォームは、ユーザーが「送信」ボタンを押した後、次のパラメーターを関数に渡します。

# values variable
Object {name: "1", description: "2", price: "3", image: FileList}

# where image value is 
value:FileList
  0: File
    lastModified: 1381593256801
    lastModifiedDate: Sat Oct 12 2013 18:54:16 GMT+0300 
    name: "wallpaper-466648.png"
    size: 1787293
    type: "image/png"
    webkitRelativePath: ""

これらのパラメーターをバックエンドに渡すために、FormData Web APIisomorphic-fetch npm モジュールを使用したファイル アップロード リクエストを使用しています。

トリックを行ったコードは次のとおりです。

// product_form_container.js (where form submit processed, see _handleSubmit function)

import React                   from 'react';
import ProductForm             from '../components/product_form';
import { Link }                from 'react-router';
import { connect }             from 'react-redux';
import Actions                 from '../actions/products';
import * as form_actions            from 'redux-form';
import {httpGet, httpPost, httpPostForm} from '../utils';

class ProductFormContainer extends React.Component {
  _handleSubmit(values) {
    return new Promise((resolve, reject) => {
      let form_data = new FormData();

      Object.keys(values).forEach((key) => {
        if (values[key] instanceof FileList) {
          form_data.append(`product[${key}]`, values[key][0], values[key][0].name);
        } else {
          form_data.append(`product[${key}]`, values[key]);
        }
      });

      httpPostForm(`/api/products/`, form_data)
      .then((response) => {
        resolve();
      })
      .catch((error) => {
        error.response.json()
        .then((json) => {
          let responce = {};
          Object.keys(json.errors).map((key) => {
            Object.assign(responce, {[key] : json.errors[key]});
          });

          if (json.errors) {
            reject({...responce, _error: 'Login failed!'});
          } else {
            reject({_error: 'Something went wrong!'});
          };
        });
      });
    });
  }

  render() {
    const { products } = this.props;

    return (
      <div>
        <h2> New product </h2>
        <ProductForm title="Add product" onSubmit={::this._handleSubmit} />

        <Link to='/admin/products'> Back </Link>
      </div>
    );
  }
}

export default connect()(ProductFormContainer);

whereはfetchhttpPostFormのラッパーです:

export function httpPostForm(url, data) {
  return fetch(url, {
    method: 'post',
    headers: {
      'Accept': 'application/json'
    },
    body: data,
  })
  .then(checkStatus)
  .then(parseJSON);
}

以上です。私のエリクサーコードには何も修正する必要はなくApi.ProductController、同じままです (最初の投稿を参照)。しかし、今では次のパラメータでリクエストを受け取ります:

[info] POST /api/products/
[debug] Processing by SecretApp.Api.ProductController.create/2
  Parameters: %{"product" => %{
                "description" => "2", 
                "image" => %Plug.Upload{
                  content_type: "image/jpeg",
                  filename: "monkey_in_jungle-t3.jpg", 
                  path: "/tmp/plug-1461/multipart-853391-603088-1"
                }, 
               "name" => "1", 
               "price" => "3"}}
  Pipelines: [:api]

私を助けようとしてくれたみんなに感謝します。これが、同様のシリアライゼーションの問題に苦しんでいる人の助けになることを願っています.

于 2016-04-28T15:03:07.083 に答える
2

ログから、イメージがブラウザーからコントローラーに送信されていることは明らかです。

Phoenix ドキュメントのファイル アップロード ガイドが役立ちます: http://www.phoenixframework.org/docs/file-uploads

ドキュメントから:

Plug.Upload 構造体をコントローラーで使用できるようにしたら、必要な操作を実行できます。File.exists?/1 を使用してファイルが存在することを確認し、File.cp/2 を使用してファイル システムの別の場所にコピーし、外部ライブラリを使用して S3 に送信するか、Plug を使用してクライアントに返送することもできます。 .Conn.send_file/5.

あなたの場合、一時的なバージョンを別の場所に保存しなかったため、プロセスが終了するとアップロードされたファイルが削除されると思います。(まだDBに隠していないと思います。)変更セットが有効であることを確認した後、これを行うコードをコントローラーに書き込みます。

于 2016-04-24T22:28:06.207 に答える