How to send a file as string to Spring Boot multipart rest api

I have a spring boot controller with

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>@RestController
@Tags(@Tag(name = "app"))
public class MyController {
@Operation(
requestBody = @RequestBody(
content = @Content(
mediaType = "multipart/form-data",
schema = @Schema(type = "object"),
schemaProperties = {
@SchemaProperty(name = "info", schema = @Schema(implementation = Info.class)),
@SchemaProperty(name = "file", schema = @Schema(type = "string", format = "binary"))
}
)
)
)
@PostMapping(value = "/upload")
ResponseEntity<Void> upload(HttpServletRequest request) throws Exception {
UUID uuid = UUID.randomUUID();
byte[] file = request.getPart("file").getInputStream().readAllBytes();
System.out.printf("=================== %s ===========================%n", uuid);
System.out.println(Arrays.toString(file).substring(0, 2750));
Path path = Paths.get(".\file_" + uuid + ".pdf");
try { Files.write(path, file);}
catch (IOException exception) { throw new RuntimeException(exception); }
return ResponseEntity.ok().build();
}
}
</code>
<code>@RestController @Tags(@Tag(name = "app")) public class MyController { @Operation( requestBody = @RequestBody( content = @Content( mediaType = "multipart/form-data", schema = @Schema(type = "object"), schemaProperties = { @SchemaProperty(name = "info", schema = @Schema(implementation = Info.class)), @SchemaProperty(name = "file", schema = @Schema(type = "string", format = "binary")) } ) ) ) @PostMapping(value = "/upload") ResponseEntity<Void> upload(HttpServletRequest request) throws Exception { UUID uuid = UUID.randomUUID(); byte[] file = request.getPart("file").getInputStream().readAllBytes(); System.out.printf("=================== %s ===========================%n", uuid); System.out.println(Arrays.toString(file).substring(0, 2750)); Path path = Paths.get(".\file_" + uuid + ".pdf"); try { Files.write(path, file);} catch (IOException exception) { throw new RuntimeException(exception); } return ResponseEntity.ok().build(); } } </code>
@RestController
@Tags(@Tag(name = "app"))
public class MyController {

    @Operation(
        requestBody = @RequestBody(
            content = @Content(
                mediaType = "multipart/form-data",
                schema = @Schema(type = "object"),
                schemaProperties = {
                    @SchemaProperty(name = "info", schema = @Schema(implementation = Info.class)),
                    @SchemaProperty(name = "file", schema = @Schema(type = "string", format = "binary"))
                }
            )
        )
    )
    @PostMapping(value = "/upload")
    ResponseEntity<Void> upload(HttpServletRequest request) throws Exception {
        UUID uuid = UUID.randomUUID();
        byte[] file = request.getPart("file").getInputStream().readAllBytes();
        System.out.printf("=================== %s ===========================%n", uuid);
        System.out.println(Arrays.toString(file).substring(0, 2750));
        Path path = Paths.get(".\file_" + uuid + ".pdf");
        try { Files.write(path, file);}
        catch (IOException exception) { throw new RuntimeException(exception); }
        return ResponseEntity.ok().build();
    }
    
}

From that controller I generate 2 open api files.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>openapi: 3.0.1
info:
title: OpenAPI definition
version: v0
servers:
- url: http://localhost:8080
description: Generated server url
paths:
/upload:
post:
tags:
- app
operationId: upload
requestBody:
content:
multipart/form-data:
schema:
type: object
properties:
info:
$ref: '#/components/schemas/Info'
file:
type: string
format: binary
responses:
'200':
description: OK
/test:
get:
tags:
- app
operationId: test
responses:
'200':
description: OK
components:
schemas:
Info:
type: object
properties:
firstname:
type: string
lastname:
type: string
</code>
<code>openapi: 3.0.1 info: title: OpenAPI definition version: v0 servers: - url: http://localhost:8080 description: Generated server url paths: /upload: post: tags: - app operationId: upload requestBody: content: multipart/form-data: schema: type: object properties: info: $ref: '#/components/schemas/Info' file: type: string format: binary responses: '200': description: OK /test: get: tags: - app operationId: test responses: '200': description: OK components: schemas: Info: type: object properties: firstname: type: string lastname: type: string </code>
openapi: 3.0.1
info:
  title: OpenAPI definition
  version: v0
servers:
  - url: http://localhost:8080
    description: Generated server url
paths:
  /upload:
    post:
      tags:
        - app
      operationId: upload
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                info:
                  $ref: '#/components/schemas/Info'
                file:
                  type: string
                  format: binary
      responses:
        '200':
          description: OK
  /test:
    get:
      tags:
        - app
      operationId: test
      responses:
        '200':
          description: OK
components:
  schemas:
    Info:
      type: object
      properties:
        firstname:
          type: string
        lastname:
          type: string

And the second with a little change, I remove the format: binary

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> file:
type: string
</code>
<code> file: type: string </code>
                file:
                  type: string

Then, with the openapi-generator-maven-plugin I generate 2 clients.

For the first definition file, the plugin generates a method with that signature

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public void upload(Info info, File _file)
</code>
<code>public void upload(Info info, File _file) </code>
public void upload(Info info, File _file)

And for the second

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public void upload(Info info, String _file)
</code>
<code>public void upload(Info info, String _file) </code>
public void upload(Info info, String _file)

I test all that with

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>File originalPdf;
try { originalPdf = new ClassPathResource("Original.pdf").getFile(); }
catch (IOException exception) { throw new RuntimeException(exception); }
try { appApi.upload(new Info(), originalPdf); }
catch (ApiException exception) { throw new RuntimeException(exception); }
byte[] byteArray = new byte[(int) originalPdf.length()];
try (FileInputStream inputStream = new FileInputStream(originalPdf)) { inputStream.read(byteArray);}
catch (Exception exception) { throw new RuntimeException(exception); }
try { appApiString.upload(new Info(), new String(byteArray)); }
catch (ApiException exception) { throw new RuntimeException(exception); }
</code>
<code>File originalPdf; try { originalPdf = new ClassPathResource("Original.pdf").getFile(); } catch (IOException exception) { throw new RuntimeException(exception); } try { appApi.upload(new Info(), originalPdf); } catch (ApiException exception) { throw new RuntimeException(exception); } byte[] byteArray = new byte[(int) originalPdf.length()]; try (FileInputStream inputStream = new FileInputStream(originalPdf)) { inputStream.read(byteArray);} catch (Exception exception) { throw new RuntimeException(exception); } try { appApiString.upload(new Info(), new String(byteArray)); } catch (ApiException exception) { throw new RuntimeException(exception); } </code>
File originalPdf;
try { originalPdf = new ClassPathResource("Original.pdf").getFile(); }
catch (IOException exception) { throw new RuntimeException(exception); }
try { appApi.upload(new Info(), originalPdf); }
catch (ApiException exception) { throw new RuntimeException(exception); }

byte[] byteArray = new byte[(int) originalPdf.length()];
try (FileInputStream inputStream = new FileInputStream(originalPdf)) { inputStream.read(byteArray);}
catch (Exception exception) { throw new RuntimeException(exception); }
try { appApiString.upload(new Info(), new String(byteArray)); }
catch (ApiException exception) { throw new RuntimeException(exception); }

The original pdf contains (just the begining)

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>00000000: 2550 4446 2d31 2e37 0d0a 25b5 b5b5 b50d 0a31 2030 206f 626a :%PDF-1.7..%......1 0 obj
00000018: 0d0a 3c3c 2f54 7970 652f 4361 7461 6c6f 672f 5061 6765 7320 :..<</Type/Catalog/Pages
00000030: 3220 3020 522f 4c61 6e67 2866 722d 4245 2920 2f53 7472 7563 :2 0 R/Lang(fr-BE) /Struc
00000048: 7454 7265 6552 6f6f 7420 3136 2030 2052 2f4d 6172 6b49 6e66 :tTreeRoot 16 0 R/MarkInf
00000060: 6f3c 3c2f 4d61 726b 6564 2074 7275 653e 3e2f 4d65 7461 6461 :o<</Marked true>>/Metada
00000078: 7461 2034 3120 3020 522f 5669 6577 6572 5072 6566 6572 656e :ta 41 0 R/ViewerPreferen
00000090: 6365 7320 3432 2030 2052 3e3e 0d0a 656e 646f 626a 0d0a 3220 :ces 42 0 R>>..endobj..2
000000a8: 3020 6f62 6a0d 0a3c 3c2f 5479 7065 2f50 6167 6573 2f43 6f75 :0 obj..<</Type/Pages/Cou
000000c0: 6e74 2031 2f4b 6964 735b 2033 2030 2052 5d20 3e3e 0d0a 656e :nt 1/Kids[ 3 0 R] >>..en
000000d8: 646f 626a 0d0a 3320 3020 6f62 6a0d 0a3c 3c2f 5479 7065 2f50 :dobj..3 0 obj..<</Type/P
000000f0: 6167 652f 5061 7265 6e74 2032 2030 2052 2f52 6573 6f75 7263 :age/Parent 2 0 R/Resourc
00000108: 6573 3c3c 2f46 6f6e 743c 3c2f 4631 2035 2030 2052 3e3e 2f45 :es<</Font<</F1 5 0 R>>/E
00000120: 7874 4753 7461 7465 3c3c 2f47 5337 2037 2030 2052 2f47 5338 :xtGState<</GS7 7 0 R/GS8
00000138: 2038 2030 2052 3e3e 2f58 4f62 6a65 6374 3c3c 2f49 6d61 6765 : 8 0 R>>/XObject<</Image
00000150: 3131 2031 3120 3020 522f 496d 6167 6531 3320 3133 2030 2052 :11 11 0 R/Image13 13 0 R
00000168: 3e3e 2f50 726f 6353 6574 5b2f 5044 462f 5465 7874 2f49 6d61 :>>/ProcSet[/PDF/Text/Ima
00000180: 6765 422f 496d 6167 6543 2f49 6d61 6765 495d 203e 3e2f 416e :geB/ImageC/ImageI] >>/An
00000198: 6e6f 7473 5b20 3920 3020 5220 3130 2030 2052 5d20 2f4d 6564 :nots[ 9 0 R 10 0 R] /Med
000001b0: 6961 426f 785b 2030 2030 2035 3935 2e33 3220 3834 312e 3932 :iaBox[ 0 0 595.32 841.92
000001c8: 5d20 2f43 6f6e 7465 6e74 7320 3420 3020 522f 4772 6f75 703c :] /Contents 4 0 R/Group<
000001e0: 3c2f 5479 7065 2f47 726f 7570 2f53 2f54 7261 6e73 7061 7265 :</Type/Group/S/Transpare
000001f8: 6e63 792f 4353 2f44 6576 6963 6552 4742 3e3e 2f54 6162 732f :ncy/CS/DeviceRGB>>/Tabs/
00000210: 532f 5374 7275 6374 5061 7265 6e74 7320 303e 3e0d 0a65 6e64 :S/StructParents 0>>..end
00000228: 6f62 6a0d 0a34 2030 206f 626a 0d0a 3c3c 2f46 696c 7465 722f :obj..4 0 obj..<</Filter/
00000240: 466c 6174 6544 6563 6f64 652f 4c65 6e67 7468 2034 3230 3e3e :FlateDecode/Length 420>>
00000258: 0d0a 7374 7265 616d 0d0a 789c ad95 d16b 1431 10c6 df17 f67f :..stream..x....k.1......
</code>
<code>00000000: 2550 4446 2d31 2e37 0d0a 25b5 b5b5 b50d 0a31 2030 206f 626a :%PDF-1.7..%......1 0 obj 00000018: 0d0a 3c3c 2f54 7970 652f 4361 7461 6c6f 672f 5061 6765 7320 :..<</Type/Catalog/Pages 00000030: 3220 3020 522f 4c61 6e67 2866 722d 4245 2920 2f53 7472 7563 :2 0 R/Lang(fr-BE) /Struc 00000048: 7454 7265 6552 6f6f 7420 3136 2030 2052 2f4d 6172 6b49 6e66 :tTreeRoot 16 0 R/MarkInf 00000060: 6f3c 3c2f 4d61 726b 6564 2074 7275 653e 3e2f 4d65 7461 6461 :o<</Marked true>>/Metada 00000078: 7461 2034 3120 3020 522f 5669 6577 6572 5072 6566 6572 656e :ta 41 0 R/ViewerPreferen 00000090: 6365 7320 3432 2030 2052 3e3e 0d0a 656e 646f 626a 0d0a 3220 :ces 42 0 R>>..endobj..2 000000a8: 3020 6f62 6a0d 0a3c 3c2f 5479 7065 2f50 6167 6573 2f43 6f75 :0 obj..<</Type/Pages/Cou 000000c0: 6e74 2031 2f4b 6964 735b 2033 2030 2052 5d20 3e3e 0d0a 656e :nt 1/Kids[ 3 0 R] >>..en 000000d8: 646f 626a 0d0a 3320 3020 6f62 6a0d 0a3c 3c2f 5479 7065 2f50 :dobj..3 0 obj..<</Type/P 000000f0: 6167 652f 5061 7265 6e74 2032 2030 2052 2f52 6573 6f75 7263 :age/Parent 2 0 R/Resourc 00000108: 6573 3c3c 2f46 6f6e 743c 3c2f 4631 2035 2030 2052 3e3e 2f45 :es<</Font<</F1 5 0 R>>/E 00000120: 7874 4753 7461 7465 3c3c 2f47 5337 2037 2030 2052 2f47 5338 :xtGState<</GS7 7 0 R/GS8 00000138: 2038 2030 2052 3e3e 2f58 4f62 6a65 6374 3c3c 2f49 6d61 6765 : 8 0 R>>/XObject<</Image 00000150: 3131 2031 3120 3020 522f 496d 6167 6531 3320 3133 2030 2052 :11 11 0 R/Image13 13 0 R 00000168: 3e3e 2f50 726f 6353 6574 5b2f 5044 462f 5465 7874 2f49 6d61 :>>/ProcSet[/PDF/Text/Ima 00000180: 6765 422f 496d 6167 6543 2f49 6d61 6765 495d 203e 3e2f 416e :geB/ImageC/ImageI] >>/An 00000198: 6e6f 7473 5b20 3920 3020 5220 3130 2030 2052 5d20 2f4d 6564 :nots[ 9 0 R 10 0 R] /Med 000001b0: 6961 426f 785b 2030 2030 2035 3935 2e33 3220 3834 312e 3932 :iaBox[ 0 0 595.32 841.92 000001c8: 5d20 2f43 6f6e 7465 6e74 7320 3420 3020 522f 4772 6f75 703c :] /Contents 4 0 R/Group< 000001e0: 3c2f 5479 7065 2f47 726f 7570 2f53 2f54 7261 6e73 7061 7265 :</Type/Group/S/Transpare 000001f8: 6e63 792f 4353 2f44 6576 6963 6552 4742 3e3e 2f54 6162 732f :ncy/CS/DeviceRGB>>/Tabs/ 00000210: 532f 5374 7275 6374 5061 7265 6e74 7320 303e 3e0d 0a65 6e64 :S/StructParents 0>>..end 00000228: 6f62 6a0d 0a34 2030 206f 626a 0d0a 3c3c 2f46 696c 7465 722f :obj..4 0 obj..<</Filter/ 00000240: 466c 6174 6544 6563 6f64 652f 4c65 6e67 7468 2034 3230 3e3e :FlateDecode/Length 420>> 00000258: 0d0a 7374 7265 616d 0d0a 789c ad95 d16b 1431 10c6 df17 f67f :..stream..x....k.1...... </code>
00000000:  2550 4446 2d31 2e37 0d0a 25b5 b5b5 b50d 0a31 2030 206f 626a  :%PDF-1.7..%......1 0 obj
00000018:  0d0a 3c3c 2f54 7970 652f 4361 7461 6c6f 672f 5061 6765 7320  :..<</Type/Catalog/Pages 
00000030:  3220 3020 522f 4c61 6e67 2866 722d 4245 2920 2f53 7472 7563  :2 0 R/Lang(fr-BE) /Struc
00000048:  7454 7265 6552 6f6f 7420 3136 2030 2052 2f4d 6172 6b49 6e66  :tTreeRoot 16 0 R/MarkInf
00000060:  6f3c 3c2f 4d61 726b 6564 2074 7275 653e 3e2f 4d65 7461 6461  :o<</Marked true>>/Metada
00000078:  7461 2034 3120 3020 522f 5669 6577 6572 5072 6566 6572 656e  :ta 41 0 R/ViewerPreferen
00000090:  6365 7320 3432 2030 2052 3e3e 0d0a 656e 646f 626a 0d0a 3220  :ces 42 0 R>>..endobj..2 
000000a8:  3020 6f62 6a0d 0a3c 3c2f 5479 7065 2f50 6167 6573 2f43 6f75  :0 obj..<</Type/Pages/Cou
000000c0:  6e74 2031 2f4b 6964 735b 2033 2030 2052 5d20 3e3e 0d0a 656e  :nt 1/Kids[ 3 0 R] >>..en
000000d8:  646f 626a 0d0a 3320 3020 6f62 6a0d 0a3c 3c2f 5479 7065 2f50  :dobj..3 0 obj..<</Type/P
000000f0:  6167 652f 5061 7265 6e74 2032 2030 2052 2f52 6573 6f75 7263  :age/Parent 2 0 R/Resourc
00000108:  6573 3c3c 2f46 6f6e 743c 3c2f 4631 2035 2030 2052 3e3e 2f45  :es<</Font<</F1 5 0 R>>/E
00000120:  7874 4753 7461 7465 3c3c 2f47 5337 2037 2030 2052 2f47 5338  :xtGState<</GS7 7 0 R/GS8
00000138:  2038 2030 2052 3e3e 2f58 4f62 6a65 6374 3c3c 2f49 6d61 6765  : 8 0 R>>/XObject<</Image
00000150:  3131 2031 3120 3020 522f 496d 6167 6531 3320 3133 2030 2052  :11 11 0 R/Image13 13 0 R
00000168:  3e3e 2f50 726f 6353 6574 5b2f 5044 462f 5465 7874 2f49 6d61  :>>/ProcSet[/PDF/Text/Ima
00000180:  6765 422f 496d 6167 6543 2f49 6d61 6765 495d 203e 3e2f 416e  :geB/ImageC/ImageI] >>/An
00000198:  6e6f 7473 5b20 3920 3020 5220 3130 2030 2052 5d20 2f4d 6564  :nots[ 9 0 R 10 0 R] /Med
000001b0:  6961 426f 785b 2030 2030 2035 3935 2e33 3220 3834 312e 3932  :iaBox[ 0 0 595.32 841.92
000001c8:  5d20 2f43 6f6e 7465 6e74 7320 3420 3020 522f 4772 6f75 703c  :] /Contents 4 0 R/Group<
000001e0:  3c2f 5479 7065 2f47 726f 7570 2f53 2f54 7261 6e73 7061 7265  :</Type/Group/S/Transpare
000001f8:  6e63 792f 4353 2f44 6576 6963 6552 4742 3e3e 2f54 6162 732f  :ncy/CS/DeviceRGB>>/Tabs/
00000210:  532f 5374 7275 6374 5061 7265 6e74 7320 303e 3e0d 0a65 6e64  :S/StructParents 0>>..end
00000228:  6f62 6a0d 0a34 2030 206f 626a 0d0a 3c3c 2f46 696c 7465 722f  :obj..4 0 obj..<</Filter/
00000240:  466c 6174 6544 6563 6f64 652f 4c65 6e67 7468 2034 3230 3e3e  :FlateDecode/Length 420>>
00000258:  0d0a 7374 7265 616d 0d0a 789c ad95 d16b 1431 10c6 df17 f67f  :..stream..x....k.1......

On the last line starting at the e of stream it gives 616d 0d0a 789c ad95 so 114, 101, 97, 109, 13, 10, 120, -100

Now looking at the results of the upload api.

With the pdf as File, the same bytes sequence is 114, 101, 97, 109, 13, 10, 120, -100 And it is ok.

With the pdf as String, the sequence is 114, 101, 97, 109, 13, 10, 120, -17

What could make such a difference ? Is it a encoding problem ?

I tried adding utf-8 or cp1252 for the new String(byteArray). For each the sequence (-100) is different but none is right.

And looking at the client log, for the first try (File)

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>Content-Disposition: form-data; name="file"; filename="Original.pdf"
Content-Type: application/pdf
Content-Length: 51375
</code>
<code>Content-Disposition: form-data; name="file"; filename="Original.pdf" Content-Type: application/pdf Content-Length: 51375 </code>
Content-Disposition: form-data; name="file"; filename="Original.pdf"
Content-Type: application/pdf
Content-Length: 51375

and for the second (String)

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>Content-Disposition: form-data; name="file"
Content-Type: text/plain; charset=utf-8
Content-Length: 87293
</code>
<code>Content-Disposition: form-data; name="file" Content-Type: text/plain; charset=utf-8 Content-Length: 87293 </code>
Content-Disposition: form-data; name="file"
Content-Type: text/plain; charset=utf-8
Content-Length: 87293

The length is the same as original for the first and much larger for the second.

What can I do ?

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật