尝试使用 Slim Framework 从客户端读取 Json 和图像文件

Trying read a Json and a image file from client using Slim Framework

所以,我有一个客户端移动设备,其中使用 retrofit2 执行请求,并且根据请求我需要发送一个图像文件和一个 json 对象。像这样:

RequestBody requestJson = RequestBody.create(MediaType.parse("application/json"), new Gson().toJson(cidadao));

            MultipartBody.Part fotoBody = MultipartBody.Part.createFormData("foto", file.getName(), requestFile);
            MultipartBody.Part jsonBody = MultipartBody.Part.createFormData("cidadao", null, requestJson);

            Service service = CallService.createService(Service.class);
            Call<Void> cadastrarCidadao = service.postCidadao(UsuarioApplication.getInstance().getToken().getToken(),
                    jsonBody,
                    fotoBody);
            cadastrarCidadao.enqueue(new Callback<Void>() {
                @Override
                public void onResponse(Call<Void> call, Response<Void> response) {
                    if (response.isSuccessful()) {
                        if (response.code() == 201) {
                            dialog.dismiss();
                            FragmentManager fm = getFragmentManager();
                            fm.popBackStack();
                        }
                    } else {
                        APIError error = ErrorUtils.parseError(response);
                        new Throwable(error.getMessage());
                    }
                }

                @Override
                public void onFailure(Call<Void> call, Throwable t) {
                    Log.e("ErrorCadastro", t.getLocalizedMessage());
                    Toasty.error(getContext(), "Erro na conexão").show();
                }
            });

服务接口:

package com.example.administrador.citycaremobile.Services;

import com.example.administrador.citycaremobile.Modelo.Cidadao;
import com.example.administrador.citycaremobile.Modelo.Login;

import okhttp3.MultipartBody;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.Headers;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Part;

public interface Service {

@POST("cidadao/cadastrar")
@Multipart
Call<Void> postCidadao(@Header("X-Token") String token,
                       @Part MultipartBody.Part cidadao,
                       @Part MultipartBody.Part foto);

@PUT("cidadao/put")
Call<Boolean> putUsuario(@Header("Content-type") String content,
                         @Header("X-Token") String token,
                         @Body Cidadao cidadao);

@Headers("content-type:application/json")
@GET("auth")
Call<Token> autentication();

//Login
@POST("login")
Call<Object> login(@Header("Content-type") String content,
                   @Header("X-Token") String token,
                   @Body Login login);
}

在服务端,有一个带有 Slim 框架的 PHP,问题是...当我尝试使用 $request->getParam() 获取 json 时,它们 return 对我来说是一个包含所有字符的数组,其中每个字符都是该数组中的一个项目。

例如:如果显示 return $jsonParam[0],他 return 给我“{”,我试图将所有数组解析为一个字符串但不成功, 尝试了 implode 方法,在参数上尝试了 json_decode,但没有任何效果。

Service端方法有:

$app -> post('/cidadao/cadastrar', function(Request $request, Response $response) use ($app){
        //Container do EntityManager
        $entityManager = $this -> get('em');
        $json = $request -> getParam('cidadao');

        try{
            //Instância da entidade Login
            $login = new Login();
            //recuperando o parâmetro objeto login do json
            $fk_login_cidadao = $json['fk_login_cidadao'];
            //setando valores do objeto login
            $login ->setLogin($fk_login_cidadao['login']);
            $login ->setEmail($fk_login_cidadao['email']);
            $login ->setSenha($fk_login_cidadao['senha']);
            $login ->setStatus_login($fk_login_cidadao['status_login']);
            $login ->setAsAdministrador($fk_login_cidadao['administrador']);

            //salvando login
            $entityManager->persist($login);
            $entityManager->flush();
            //buscando login recém salvo
            $loginRepository = $entityManager->getRepository('App\Models\Entity\Login');
            //pegando login
            $loginCidadao = $loginRepository->find($login->getId_login());
            //Salvar foto
            $files = $request->getUploadedFiles();
            $newimage = $files['foto'];
            if ($newimage->getError() === UPLOAD_ERR_OK) {
                $uploadFileName = $newimage->getClientFilename();
                $type = $newimage->getClientMediaType();
                $name = uniqid('img-' . date('d-m-y') . '-');
                $name .= $newimage->getClientFilename();
                //  $imgs[] = array('url' => '/Photos/' . $name);
                //local server
                $newimage->moveTo("/home/citycare/public_html/Imgs/user/$name");#/home/citycare/Imgs/User/$name
                //localdev
                $photoURL = "/home/citycare/public_html/Imgs/user/$name";#/home/citycare/Imgs/User/$name
            }
            //Instância da entidade Cidadao
            $cidadao = new Cidadao();
            //setando valores do objeto cidadao
            $cidadao ->setFk_login_cidadao($loginCidadao);
            $cidadao->setNome($request->getParam('nome'));
            $cidadao->setSexo($request->getParam('sexo'));
            $cidadao ->setSobrenome($request->getParam('sobrenome'));
            $cidadao ->setEstado($request->getParam('estado'));
            $cidadao ->setCidade($request->getParam('cidade'));
            $cidadao ->setDir_foto_usuario($request->getParam($photoURL));
            $entityManager->persist($cidadao);
            $entityManager->flush();
            //retornando confirmação do evento completo
            $return = $response->withStatus(201)->withHeader('Content-type', 'application/json');


        } catch (Exception $ex){
            //código e mensagem do erro
            throw new Exception($ex->getMessage(), $ex->getCode());
        }
        return $return;
});

图像处理正常。

HTTP 请求:

10-14 03:35:38.772 19151-9871/com.example.administrador.citycaremobile D/OkHttp: --> POST http://servico.projetocitycare.com.br/cidadao/cadastrar http/1.1
10-14 03:35:38.773 19151-9871/com.example.administrador.citycaremobile D/OkHttp: Content-Type: multipart/form-data; boundary=58c96b8f-a56e-4b42-8052-10d48c583cc4
10-14 03:35:38.776 19151-9871/com.example.administrador.citycaremobile D/OkHttp: Content-Length: 69178
10-14 03:35:38.777 19151-9871/com.example.administrador.citycaremobile D/OkHttp: X-Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoicm9vdCIsInNlbmhhIjoiY2FyZWNpdHkiLCJleHAiOjE1MDc5NjMwOTUsImlwIjoiMTc3LjM3LjIyOC4xNDIifQ.ePeHn_YgmRfach6B1v4JC75147ixRwrjdoUvEjpPKRQ
10-14 03:35:38.788 19151-9871/com.example.administrador.citycaremobile D/OkHttp: --58c96b8f-a56e-4b42-8052-10d48c583cc4
10-14 03:35:38.788 19151-9871/com.example.administrador.citycaremobile D/OkHttp: Content-Disposition: form-data; name="cidadao"
10-14 03:35:38.788 19151-9871/com.example.administrador.citycaremobile D/OkHttp: Content-Type: application/json; charset=utf-8
10-14 03:35:38.788 19151-9871/com.example.administrador.citycaremobile D/OkHttp: Content-Length: 239
10-14 03:35:38.788 19151-9871/com.example.administrador.citycaremobile D/OkHttp: {"cidade":"Alto Rio Novo","estado":"Espírito Santo","fk_login_cidadao":{"administrador":false,"email":"ekekejj@mks.xkm","login":"kddjsjn","senha":"jskeieej","status_login":true},"nome":"neenenem","sexo":"Masculino","sobrenome":"memekekk"}
10-14 03:35:38.788 19151-9871/com.example.administrador.citycaremobile D/OkHttp: --58c96b8f-a56e-4b42-8052-10d48c583cc4
10-14 03:35:38.788 19151-9871/com.example.administrador.citycaremobile D/OkHttp: Content-Disposition: form-data; name="foto"; filename="cropped83912673.jpg"
10-14 03:35:38.788 19151-9871/com.example.administrador.citycaremobile D/OkHttp: Content-Type: multipart/form-data
10-14 03:35:38.788 19151-9871/com.example.administrador.citycaremobile D/OkHttp: Content-Length: 68558
10-14 03:35:38.788 19151-9871/com.example.administrador.citycaremobile D/OkHttp: ������JFIF����������������C��
10-14 03:35:38.800 19151-9871/com.example.administrador.citycaremobile D/OkHttp: --58c96b8f-a56e-4b42-8052-10d48c583cc4--
10-14 03:35:38.800 19151-9871/com.example.administrador.citycaremobile D/OkHttp: --> END POST (69178-byte body)

HTTP 响应:

10-14 03:35:39.675 19151-9871/com.example.administrador.citycaremobile D/OkHttp: <-- 500 Internal Server Error http://servico.projetocitycare.com.br/cidadao/cadastrar (874ms)
10-14 03:35:39.676 19151-9871/com.example.administrador.citycaremobile D/OkHttp: Server: nginx
10-14 03:35:39.676 19151-9871/com.example.administrador.citycaremobile D/OkHttp: Date: Sat, 14 Oct 2017 06:35:39 GMT
10-14 03:35:39.676 19151-9871/com.example.administrador.citycaremobile D/OkHttp: Content-Type: application/json;charset=utf-8
10-14 03:35:39.677 19151-9871/com.example.administrador.citycaremobile D/OkHttp: Transfer-Encoding: chunked
10-14 03:35:39.677 19151-9871/com.example.administrador.citycaremobile D/OkHttp: Connection: keep-alive
10-14 03:35:39.678 19151-9871/com.example.administrador.citycaremobile D/OkHttp: Upgrade: h2,h2c
10-14 03:35:39.685 19151-9871/com.example.administrador.citycaremobile D/OkHttp:     {"message":"An exception occurred while executing 'INSERT INTO login (email, login, senha, status_login, administrador) VALUES (?, ?, ?, ?, ?)' with params [\"{\", \"{\", \"ew==\", \"{\", \"{\"]:\n\nSQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '{' for key 'UNIQ_AA08CB10E7927C74'"}
10-14 03:35:39.686 19151-9871/com.example.administrador.citycaremobile D/OkHttp: <-- END HTTP (310-byte body)

Json 发送的是这样的:

    {"cidade":"Brasília",
     "estado":"Distrito Federal",
     "fk_login_cidadao":{ "administrador":false,
                          "email":"b@b.com",
                          "login":"jhon",
                          "senha":"182873728192",
                          "status_login":true},
     "nome":"Jhon",
     "sexo":"Masculino",
     "sobrenome":"James"}

你的请求正文大部分是正确的,除了这个:Content-Transfer-Encoding: binary 我不明白为什么 retrofit2 将它添加到 form-data。但是,我认为这不是您遇到的错误。

起初我认为 PHP 没有正确解析 form-data 因为那个额外的位。但后来我意识到你正在解析 JSON 并将其作为数组访问。您的 json 字符串是一个对象,PHP JSON 解码器将对其进行解释。

总而言之,您应该像这样访问数据 (json) 对象的值:$json->fk_login_cidadao 而不是这样:$json['fk_login_cidadao']。这也适用于您的其余值和密钥对。

如果您想验证我的理论,只需将参数 $request -> getParam('cidadao') 的内容记录或写入文件即可。

所以,经过多次尝试,在 $json 上使用 var_dump() 我看到请求没有被框架解码,我不知道为什么,所以我需要手动做解码并使用 Object 而不是 Array。最后 API 边代码保持这样:

$app ->post('/cidadao/cadastrar', function(Request $request, Response $response) use ($app){
        //Container do EntityManager
        $entityManager = $this -> get('em');

        $json = $request->getParam('cidadao');
        $json = json_decode($json);


        try{
            //Instância da entidade Login
            $login = new Login();
            //recuperando o parâmetro objeto login do json
            $fk_login_cidadao = $json -> fk_login_cidadao;
            //setando valores do objeto login
            $login ->setLogin($fk_login_cidadao -> login);
            $login ->setEmail($fk_login_cidadao -> email);
            $login ->setSenha($fk_login_cidadao -> senha);
            $login ->setStatus_login($fk_login_cidadao -> status_login);
            $login ->setAsAdministrador($fk_login_cidadao -> administrador);

            //salvando login
            $entityManager->persist($login);
            $entityManager->flush();
            //buscando login recém salvo
            $loginRepository = $entityManager->getRepository('App\Models\Entity\Login');
            //pegando login
            $loginCidadao = $loginRepository->find($login->getId_login());
            //Salvar foto
            $files = $request->getUploadedFiles();
            $newimage = $files['foto'];
            if ($newimage->getError() === UPLOAD_ERR_OK) {
                $uploadFileName = $newimage->getClientFilename();
                $type = $newimage->getClientMediaType();
                $name = uniqid('img-' . date('d-m-y') . '-');
                $name .= $newimage->getClientFilename();
                //  $imgs[] = array('url' => '/Photos/' . $name);
                //local server
                $newimage->moveTo("/home/citycare//imgs/$name");#/home/citycare/Imgs/User/$name
                //localdev
                $photoURL = "/home/citycare//imgs/$name";#/home/citycare/Imgs/User/$name
            }
            //Instância da entidade Cidadao
            $cidadao = new Cidadao();
            //setando valores do objeto cidadao
            $cidadao ->setFk_login_cidadao($loginCidadao);
            $cidadao->setNome($json -> nome);
            $cidadao->setSexo($json -> sexo);
            $cidadao ->setSobrenome($json -> sobrenome);
            $cidadao ->setEstado($json -> estado);
            $cidadao ->setCidade($json -> cidade);
            $cidadao ->setDir_foto_usuario($photoURL);
            $entityManager->persist($cidadao);
            $entityManager->flush();
            //retornando confirmação do evento completo
            $return = $response->withStatus(201)->withHeader('Content-type', 'application/json');


        } catch (Exception $ex){
            //código e mensagem do erro
            throw new Exception($ex->getMessage(), $ex->getCode());
        }
        return $return;
});