69일차 Project

파일 올리는 것을 하고 잇다.
파일이 있는 게시물을 볼 수 있다.
이제 파일 게시물 수정 삭제 하는 방법을 알아보자.

12.10 파일 게시물 삭제

삭제 누르면 게시글만 삭제하는 것이아니라 로컬 드라이브의 사진도 삭제해야한다.
fileName boardId가 board.id를 참조하고 잇어서 외래키 제약사항에 위반되어 글이 삭제가 안된다.
그래서 먼저 FileName테이블의 글을 먼저 삭제하고 board를 삭제해야한다.

12.10.1 service

1.FileName 테이블의 데이터 지우기
2.하드디스크의 파일 지우기
3.게시물 테이블의 데이터 지우기
세가지일을 한번에 처리해야한다.

@Transactional(rollbackFor = Exception.class)
public boolean remove(Integer id) {
    // 파일명 조회
    List<String> fileNames = mapper.selectFileNamesByBoardId(id);

    // FileName 테이블의 데이터 지우기
    mapper.deleteFileNameByBoardId(id);

    // 하드디스크의 파일 지우기
    for (String fileName : fileNames) {
        String path = "F:\\study\\upload\\" + id + File.separator + fileName;
        File file = new File(path);
        if (file.exists()) {
            file.delete();
        }
    }

    // 게시물 테이블의 데이터 지우기
    int cnt = mapper.deleteById(id);
    return cnt == 1;
}

12.10.2 mapper

// 파일명 조회
@Select("SELECT fileName FROM FileName WHERE boardId = #{id}")
List<String> selectFileNamesByBoardId(Integer id);

// FileName 테이블의 데이터 지우기
@Delete("DELETE FROM FileName WHERE boardId = #{id}")
void deleteFileNameByBoardId(Integer id);

12.11 수정

파일이 있는 게시글으 수정해보자.
수정폼
그림을 어떻게 수정할 것인가?
1.있는 그림 삭제 2.새그림 업로드

12.11.1 view

업데이트 앞에 그림을 두고 그림 앞에 체크박스 두기

<div class="mb-3">
    <c:forEach items="${board.fileName }" var="fileName" >
        <input type="checkbox" name="removeFiles" value="${fileName}" />
        <div class="mb-3">
            <img class="img-thumbnail img-fluid " src="http://localhost:8080/image/${board.id }/${fileName}" alt="" />
        </div>
    </c:forEach>
</div>

12.11.2 컨트롤러

requestParam으로 게시물 정보를 얻어온다.
게시물정보를 서비스에게 건네준다

@PostMapping("/update/{id}")
public String updateProcess(Board board,
        @RequestParam(value = "removeFiles", required = false) List<String> removeFileNames,
        RedirectAttributes rttr) {

    boolean ok = service.update(board, removeFileNames);
    if (ok) {
        // 해당게시물 보기로 리디렉션
        rttr.addFlashAttribute("success", "modifySuccess");
        rttr.addFlashAttribute("message", board.getId() + "번 게시물이 수정되었습니다.");
        return "redirect:/detail/" + board.getId();
    } else {
        // 수정폼으로 리디렉션
        rttr.addFlashAttribute("fail", "modifyFail");
        rttr.addFlashAttribute("message", "게시물이 수정되지 않았습니다.");
        return "redirect:/update/" + board.getId();
    }
}

12.11.3 service

1.하드 디스크에서 삭제
2.FileName 테이블의 데이터 삭제
3.게시물(Board) 테이블 수정
세가지 단계를 거쳐야 한다.

public boolean update(Board board, List<String> removeFileNames) {
    if (removeFileNames != null && !removeFileNames.isEmpty()) {
        for (String fileName : removeFileNames) {

            // 하드 디스크에서 삭제
            String path = "F:\\study\\upload\\" + board.getId() + File.separator + fileName;
            File file = new File(path);
            if (file.exists()) {
                file.delete();
            }

            // FileName 테이블의 데이터 삭제
            mapper.deleteFileNameByBoardIdANndFileName(board.getId(), fileName);
        }
    }
    // 게시물(Board) 테이블 수정
    int cnt = mapper.update(board);
    return cnt == 1;
}

12.11.4 mapper

이름과 아이디를 받아 삭제하는 쿼리 작성하기

@Delete("DELETE FROM FileName WHERE boardId = #{boardId} AND fileName = #{fileName}")
void deleteFileNameByBoardIdANndFileName(Integer boardId, String fileName);

12.12 수정하기 - 2

새로 사진을 등록하기

컨트롤러에서는 requestParam으로 MultipartFile[] files객체를 받아주고
서비스에서 파일테이블에 저장하는 mapper를 실행시키면된다.

추가하고 삭제하면 같은 이름의 파일이 없어질 수 있다.
그래서 삭제 후 처리과저응로 새파일 추가를 넣어주자.

public boolean update(Board board, List<String> removeFileNames, MultipartFile[] files)
        throws IllegalStateException, IOException {
    if (removeFileNames != null && !removeFileNames.isEmpty()) {
        for (String fileName : removeFileNames) {

            // 하드 디스크에서 삭제
            String path = "F:\\study\\upload\\" + board.getId() + File.separator + fileName;
            File file = new File(path);
            if (file.exists()) {
                file.delete();
            }

            String path2 = "F:\\study\\upload\\" + board.getId();
            File file2 = new File(path2);
            if (file2.exists()) {
                file2.delete();
            }

            // FileName 테이블의 데이터 삭제
            mapper.deleteFileNameByBoardIdANndFileName(board.getId(), fileName);
        }
    }

    // 게시물(Board) 테이블 수정
    int cnt = mapper.update(board);

    for (MultipartFile file : files) {
        if (file.getSize() > 0) {
            // 폴더만들기
            String folder = "F:\\study\\upload\\" + board.getId();
            File targetFolder = new File(folder);
            if (!targetFolder.exists()) {
                targetFolder.mkdirs();
            }
            // 파일 저장
            String path = folder + File.separator + file.getOriginalFilename();
            File target = new File(path);
            file.transferTo(target);
            // db에 관련정보저장 (insert)
            mapper.insertFileName(board.getId(), file.getOriginalFilename());
        }
    }
    return cnt == 1;
}

12.13 css수정

input박스에 id를 주는데 forEach를 하면 같은 id로 두개가 만들어진다.
하나의 페이지에 같은 id를 가지면 안된다.
그래서 맨처음의 하나만 정해진다. forEach의 varStatus를 설정활용하자.

<div class="mb-3">
    <c:forEach items="${board.fileName }" var="fileName"  varStatus="st">
    <div class="form-check form-switch">
      <input class="form-check-input" type="checkbox" role="switch" id="removeCheckBox${st.index}" name="removeFiles" value="${fileName}" >
      <label class="form-check-label" for="removeCheckBox${st.index}"><i class="fa-solid fa-trash-can"></i></label>
    </div>
        <div class="mb-3">
            <img class="img-thumbnail img-fluid " src="http://localhost:8080/image/${board.id }/${fileName}" alt="" />
        </div>
    </c:forEach>
</div>

12.14 aws 파일

하드디스크에 저장하면 어플리케이션 공개시 공개할 수 없다.
공개서비스에서 이미지를 이용하려면 서버가 필요하다.

1.aws - s3검색
클라우드의 확장 가능한 스토리지
Simple Storage Service 아마존의 간단한 클라우드 스토리지이다.
5기가의 저장공간을 준다.

2.버킷만들기 - 메뉴 중 버킷
파일이 저장되는 하나의 단위가 버킷이다.

2.1.버킷이름
공백불가 대문자포함x 전역에서 공유

2.2.객체 소유권
접근권한을 밖에서 볼수 잇냐? -> 봐야하니 ACL 활성화됨

2.3 차단해제
모든 퍼블릭 액세스 차단해제

2.4 버킷만들기

3.버킷안에 파일 넣기
파일을 객체라고 부른다.

3.1 폴더만들기
이름 지정

3.2 파일업로드
파일을 업로드하면 아래 권한에 퍼블릭읽기 액세스 허용 해주기

4.파일보기
이미지의 객체 URL을 누르면 이미지가 나오게 된다.

여기에 crud할것이다.
버킷이름의 주소로부터 데이터를 받아오면된다.
그림파일을 아마존 클라우드 서버로부터 얻어오는 것이다.

<!-- 그림 파일 출력 -->
<div class="mb-3">
    <c:forEach items="${board.fileName }" var="fileName" >
        <div>
            <%-- http://localhost:8080/image/4122/slamdunk.jfif --%>
            <%-- http://localhost:8080/image/게시물번호/fileName --%>
            <c:set var="bucketUrl" value="https://버킷이.s3.ap-northeast-2.amazonaws.com/board"/>
            <img class="img-thumbnail img-fluid " src="${bucketUrl}/${board.id }/${fileName}" alt="" />
        </div>
    </c:forEach>
</div>

12.15 aws 파일업로드

연습을 위해 아마존의 데이터를 받자
https://docs.aws.amazon.com/ko_kr/sdk-for-java/latest/developer-guide/examples-s3-objects.html

<!-- aws s3 -->
<!-- https://mvnrepository.com/artifact/software.amazon.awssdk/s3 -->
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>s3</artifactId>
    <version>2.20.57</version>
    <scope>test</scope>
</dependency>

region은 지역 한국은 Region.AP_NORTHEAST_2이다.
bucketName은 말그대로 버킷이름
키가 폴더명과 파일명의 조합이다.

그러면 권한이 필요하다고 한다.
credentials
IAM대시보드에서 권한을 얻어야한다.
https://us-east-1.console.aws.amazon.com/iamv2/home?region=ap-northeast-2#/home

1.사용자 추가
2.1직접정책연결
2.2권한정책 AmazonS3FullAccess체크
2.3사용자생성
3.액세스키 생성
https://docs.aws.amazon.com/ko_kr/powershell/latest/userguide/pstools-appendix-sign-up.html

액세스키를 생성한 후 필드에 권한을 주면된다.
@Value("${aws.accessKeyId}")
String accesskey;
@Value("${aws.secretAccessKey}")
String secretKey;

@Value("${aws.accessKeyId}")
private String accessKey;

@Value("${aws.secretAccessKey}")
private String secretKey;

@Value("${aws.s3.bucketName}")
private String bucketName;

private S3Client s3;

//초기화코드
@PostConstruct
public void init() {
    AwsCredentials credentials = AwsBasicCredentials.create(accessKey, secretKey);
    AwsCredentialsProvider provider = StaticCredentialsProvider.create(credentials);
    Region region = Region.AP_NORTHEAST_2;

    this.s3 = S3Client.builder()
            .credentialsProvider(provider)
            .region(region)
            .build();
}

//파일 업로드
@GetMapping("link1")
public void method1() {
    String key = "test/myFile.txt";

    String content = "새로운 파일 내용물";

    // s3에 파일 업로드
    PutObjectRequest objectRequest = PutObjectRequest.builder()
            .bucket(bucketName)
            .acl(ObjectCannedACL.PUBLIC_READ)
            .key(key)
            .build();

    s3.putObject(objectRequest, RequestBody.fromBytes(content.getBytes()));
}

//파일 지우기
@GetMapping("link2")
public void method2() {
    String key = "test/myFile.txt";
    DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder()
            .bucket(bucketName)
            .key(key)
            .build();

    s3.deleteObject(deleteObjectRequest);
}

12.16 aws 파일업로드 - 2

이것을 사용해서 프로젝트에 사용할 것이다.
업데이트는 지우기와 업로드를 적절히 사용하면된다.

파일이름과 내용을 form으로 받아보자.

<form action="/sub32/link4" method="post" enctype="multipart/form-data">
    <input type="file" name="files" multiple accept="image/*" />
    <input type="submit" value="전송" />
</form>

key는 경로 + 파일 이름
putObject는 여러가지로 오버로드 되어있다.
첫번째 파라미터는 PutObjectRequest
두번째파라미터는 RequestBody
RequestBody는 view로부터 데이터를 받앗으니 인풋스트림으로 받아준다.
s3.putObject(por, RequestBody.fromInputStream(파일 인풋스트림, 파일 사이즈));

@PostMapping("link4")
public void method4(@RequestParam("files") MultipartFile[] files) throws Exception{
    // aws s3 업로드

    for (MultipartFile file : files) {
        if (file.getSize() > 0) {
            String key = "test/" + file.getOriginalFilename();

            // s3에 파일 업로드
            PutObjectRequest por = PutObjectRequest.builder()
                    .bucket(bucketName)
                    .acl(ObjectCannedACL.PUBLIC_READ)
                    .key(key)
                    .build();

            s3.putObject(por, RequestBody.fromInputStream(file.getInputStream(), file.getSize()));
        }
    }
}

//파일지우기

@PostMapping("link5")
public void method5(String fileName) throws Exception {
    // 파일 삭제
    String key = "test/" + fileName;
    DeleteObjectRequest dor = DeleteObjectRequest.builder()
            .bucket(bucketName)
            .key(key)
            .build();

    s3.deleteObject(dor);
}

파일이름으로 키를 얻어서 삭제 할 수 있다.

12.17 프로젝트 적용

config에 S3Client을 만들어놔야 서비스에 주입해서 사용할 수 있다.

@Configuration
public class CustomConfiguration {

    @Value("${aws.accessKeyId}")
    private String accessKey;

    @Value("${aws.secretAccessKey}")
    private String secretKey;


    @Bean
    public S3Client S3Client() {
        AwsCredentials credentials = AwsBasicCredentials.create(accessKey, secretKey);
        AwsCredentialsProvider provider = StaticCredentialsProvider.create(credentials);
        Region region = Region.AP_NORTHEAST_2;

        S3Client s3client = S3Client.builder()
                .credentialsProvider(provider)
                .region(region)
                .build();

        return s3client;
    }
}

실제 파일 업로드를 해보자.
service에서 글쓰기를 고쳐보자.
S3Client은 config에서 bean으로 정의해서 ioc컨테이너에 올라와있는객체가 들어가게된다.
key는 파일이름/board.id/이미지이름이다.

파일을 얻고 폴더를 만들어서 하드디스크에 적용했던 것을
대신 PutObjectRequest객체를 이용해서 aws에 연결해서 저장하면된다.

@Service
public class BoardServiceImpl implements BoardService {

    @Value("${aws.s3.bucketName}")
    private String bucketName;

    @Autowired
    private S3Client s3;

    @Autowired
    private BoardMapper mapper;

    @Transactional(rollbackFor = Exception.class)
    public boolean create(Board board, MultipartFile[] files) throws Exception {
        // 게시물 insert
        int cnt = mapper.insert(board);

        for (MultipartFile file : files) {
            if (file.getSize() > 0) {
                // System.out.println(file.getOriginalFilename());
                // System.out.println(file.getSize());
                // 파일 저장 (파일 시스템 하드디스크에 저장)
                // 폴더만들기
                /*
                 * String folder = "F:\\study\\upload\\" + board.getId(); File targetFolder =
                 * new File(folder); if (!targetFolder.exists()) { targetFolder.mkdirs(); } //
                 * 파일 저장 String path = folder + File.separator + file.getOriginalFilename();
                 * File target = new File(path); file.transferTo(target);
                 */

                String key = "board/" + board.getId() + "/" + file.getOriginalFilename();

                // s3에 파일 업로드
                PutObjectRequest por = PutObjectRequest.builder()
                        .bucket(bucketName)
                        .acl(ObjectCannedACL.PUBLIC_READ)
                        .key(key)
                        .build();

                s3.putObject(por, RequestBody.fromInputStream(file.getInputStream(), file.getSize()));

                // db에 관련정보저장 (insert)
                mapper.insertFileName(board.getId(), file.getOriginalFilename());
            }
        }
        return cnt == 1;
    }
}

2023.05.03

이클립스 자체 오류 해결했댜.
임시방편으로 해결하는 것이라 완벽하게 해결한지는 모르겠지만 일단 빨간줄은 없앴다.

<!-- jsp첫줄빨간줄 없애는 임시방편 -->
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>

<!-- tags첫줄빨간줄 없애는 임시방편 -->
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.1</version>
    <scope>provided</scope>
</dependency>

aws에 파일을 업로드하고 삭제하는 방법에 대해서 배웠다.
좋은점은 하드디스크 파일 다운로드와 다르게 삭제시 파일 까지 통채로 삭제된다는점이 좋다.
코드가 복잡하지만 같은 것을 반복하고 있기 때문에 반복을 통해서 익숙해질 수 있을 듯하다.

'국비 > Project - 1 게시판' 카테고리의 다른 글

2023.05.08 71일차 Project  (0) 2023.05.09
2023.05.04 70일차 Project  (0) 2023.05.04
2023.05.02 68일차 Project  (0) 2023.05.02
2023.05.01 67일차 Project  (0) 2023.05.01
2023.04.28 66일차 Project  (0) 2023.05.01

+ Recent posts