Flutter

스프링 부트 Base64 활용 (스프링, 플러터)

whs5758 2025. 8. 19. 16:30

1. Base64란?

Base64는 바이너리 데이터(이미지, 파일 등)를 텍스트로 변환하는 인코딩 방식입니다. 64개의 ASCII 문자(A-Z, a-z, 0-9, +, /)와 패딩 문자(=)를 사용해 안전하게 전송할 수 있게 합니다.

2. 왜 사용하나요?

  • 텍스트만 허용하는 시스템(예: JSON, 이메일, HTTP)에서 바이너리 데이터를 보낼 때.
  • 데이터 손상 방지: 특수 문자가 바이너리에 섞이지 않음.
  • 예: 플러터 앱에서 파일을 Base64로 변환해 서버로 업로드.

3. 어떻게 작동하나요?

  1. 바이너리 데이터를 8비트 바이트로 봅니다.
  2. 6비트 단위로 나눕니다 (3바이트 = 24비트 = 4개의 6비트).
  3. 각 6비트를 0-63 숫자로 변환 후, Base64 테이블에서 문자로 매핑.
  4. 나머지 비트가 부족하면 =로 패딩.

도식화 (간단 예: "Man" 문자열)

  • 원본: M a n (ASCII: 77, 97, 110 → 바이너리: 01001101 01100001 01101110)
  • 6비트 나누기: 010011 010110 000101 101110
  • 10진수: 19, 22, 5, 46
  • Base64 매핑: T W F u
  • 결과: TWFu
import java.util.Base64;

// "Man"을 Base64로 인코딩
String original = "Man";
String encoded = Base64.getEncoder().encodeToString(original.getBytes());
// 결과: TWFu

 

Base64 역직렬화(디코딩)는 Base64 문자열을 원본 바이너리로 복원합니다.

  1. Base64 문자열을 4문자(24비트) 단위로 나눕니다.
  2. 각 문자를 6비트(0-63)로 매핑 후 결합해 8비트 바이트로 재구성.
  3. 패딩(=) 무시.

도식화 ("TWFu" 디코딩 예):

  • Base64: T W F u (인덱스: 19, 22, 5, 46 → 6비트: 010011 010110 000101 101110)
  • 8비트 재구성: 01001101 01100001 01101110 (ASCII: 77, 97, 110 → "Man")
import java.util.Base64;  
byte[] decoded = Base64.getDecoder().decode("TWFu");  // "Man" 바이트 배열 반환.

# 서버 포트 설정
server:
  port: 8080

# Spring 설정
spring:
  # 애플리케이션 이름
  application:
    name: demo-img-server

  # H2 데이터베이스 설정
  datasource:
    url: jdbc:h2:mem:testdb          # 인메모리 데이터베이스
    driver-class-name: org.h2.Driver
    username: sa                      # 기본 사용자명
    password:                         # 패스워드 없음

  # H2 콘솔 활성화 (개발용)
  h2:
    console:
      enabled: true                   # H2 웹 콘솔 사용
      path: /h2-console              # 콘솔 접근 경로

  # JPA 설정
  jpa:
    hibernate:
      ddl-auto: create-drop          # 앱 시작시 테이블 생성, 종료시 삭제
    show-sql: true                   # SQL 쿼리 로그 출력
    properties:
      hibernate:
        format_sql: true             # SQL 포맷팅
        dialect: org.hibernate.dialect.H2Dialect

# 로깅 설정
logging:
  level:
    com.tenco.class_image_server: DEBUG  # 우리 패키지는 DEBUG 레벨
    org.springframework.web: DEBUG   # 웹 관련 DEBUG
    org.hibernate.SQL: DEBUG         # Hibernate SQL DEBUG

 

사용자의 이미지 저장 요청할 때 사용
package com.tenco.class_image_server.dto;

import com.tenco.class_image_server.entity.Image;
import lombok.Data;

@Data
public class ImageRequestDto {

    /**
     * p_001.jpg, a.png
     */
    private String fileName;

    /**
     * Base64로 인코딩된 이미지 데이터
     * Flutter 에서 이미지를 Base64로 변환하여 전송할 예정
     * 예: "data:image/jpeg;base64,/TwffajADFdadsff"
     *
     */
    private String imageData;

    /**
     *  DTO 를 Entity로 변환하는 메서드 설계
     */
    public Image toEntity() {
        Image image = new Image();
        image.setFileName(this.fileName);
        image.setImageData(this.imageData);
        return image;
    }
}

 

ImageResponseDto - 리스트 만들 때 사용

package com.tenco.class_image_server.dto;

import com.tenco.class_image_server.entity.Image;
import lombok.Data;

@Data
public class ImageResponseDto {

    // 이미지의 고유 ID
    private Long id;
    private String fileName;
    // 1단계에서는 imageData 를 안 내려줄 예정

    // 엔티티를 DTO로 변환하는 기능을 추가
    public static ImageResponseDto fromEntity(Image image) {
        ImageResponseDto dto = new ImageResponseDto();
        dto.setId(image.getId());
        dto.setFileName(image.getFileName());
        return dto;
    }

}

 

ImageDetailResponseDto - 상세보기 화면 만들 때 사용

package com.tenco.class_image_server.dto;

import com.tenco.class_image_server.entity.Image;
import lombok.Data;

@Data
public class ImageDetailResponseDto {

    private Long id;
    private String fileName;
    private String imageData; // Base64 데이터 포함

    // Image 엔티티에서 ---> ImageDetailResponseDto 객체를 생성하는 메서드 만들기
    public static ImageDetailResponseDto fromEntity(Image image) {
        ImageDetailResponseDto dto = new ImageDetailResponseDto();
        dto.setId(image.getId());
        dto.setFileName(image.getFileName());
        dto.setImageData(image.getImageData());
        return  dto;
    }

}

 

ImageService
package com.tenco.class_image_server.services;

import com.tenco.class_image_server.dto.ImageDetailResponseDto;
import com.tenco.class_image_server.dto.ImageRequestDto;
import com.tenco.class_image_server.dto.ImageResponseDto;
import com.tenco.class_image_server.entity.Image;
import com.tenco.class_image_server.repository.ImageRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class ImageService {

    private final ImageRepository imageRepository;

    // 이미지 저장
    public ImageResponseDto saveImage(ImageRequestDto requestDto) {
       Image image = requestDto.toEntity();
       // 데이터베이스 저장
       Image savedImage = imageRepository.save(image);
       // 엔티티에 들어가 데이터를 사용자에게 응답 --> DTO 로 변환해서 응답해야 함
       return ImageResponseDto.fromEntity(savedImage);
    }

    // 모든 이미지 조회 서비스
    public List<ImageResponseDto> getAllImage() {
       List<Image> images = imageRepository.findAll();
       List<ImageResponseDto> dtoList = new ArrayList<>();
       for (Image image : images) {
            dtoList.add(ImageResponseDto.fromEntity(image));
       }
       return dtoList;
    }
    //  1, 2, 3
    // id 요청된 특정 이미지 상세 조회 서비스
    public Optional<ImageDetailResponseDto> getImageDetailById(Long id) {
       Optional<Image> optionalImage = imageRepository.findById(id);
       if (optionalImage.isPresent()) {
           return Optional.of(ImageDetailResponseDto.fromEntity(optionalImage.get()));
       } else {
           return Optional.empty();
       }
    }

    public void deleteImage(Long id) {
        imageRepository.deleteById(id);
    }
}

 

ImageController
package com.tenco.class_image_server.controller;

import com.tenco.class_image_server.dto.ImageDetailResponseDto;
import com.tenco.class_image_server.dto.ImageRequestDto;
import com.tenco.class_image_server.dto.ImageResponseDto;
import com.tenco.class_image_server.services.ImageService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.w3c.dom.stylesheets.LinkStyle;

import javax.swing.text.html.Option;
import java.util.List;
import java.util.Optional;

@RestController // @Controller + @ResponseBody
@RequestMapping("/api/images")
@RequiredArgsConstructor
@CrossOrigin(origins = "*")
public class ImageController {

    private final ImageService imageService;

    // 이미지 업로드
    @PostMapping
    public ResponseEntity<ImageResponseDto> uploadImage(
            @RequestBody ImageRequestDto requestDto) {

        ImageResponseDto savedImageDto = imageService.saveImage(requestDto);
        return ResponseEntity.ok(savedImageDto);
    }

    // 전체 이미지 조회
    @GetMapping
    public ResponseEntity<List<ImageResponseDto>> getAllImages() {
        List<ImageResponseDto> images = imageService.getAllImage();
        return ResponseEntity.ok(images);
    }

    // 특정 id로 이미지 상세 조회
    // api/images/1
    @GetMapping("/{id}")
    public ResponseEntity<ImageDetailResponseDto> getImageById(@PathVariable Long id) {
        Optional<ImageDetailResponseDto> image = imageService.getImageDetailById(id);
        if(image.isPresent()) {
            return ResponseEntity.ok(image.get());
        } else {
            return ResponseEntity.notFound().build();
        }
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteImage(@PathVariable Long id) {
        imageService.deleteImage(id);
        return ResponseEntity.ok().build();
    }

}

 

Base64 이미지 인코딩

https://www.base64-image.de/

 

Base64 Image Encoder

show code copy image copy css ✘

www.base64-image.de

 

이미지 저장 테스트

 

이미지 저장 DB 확인