70일차

파일에 있는 데이터를 crud aws에서 제공하는
스토리지 서비스 이용중이다.
파일을 올리거나 삭제하는 방법을 알게 된것을 응용한다.

S3Client의 각 메소드를 실행시켜야한다.
putObject(upload) DeleteObject(delete)

PutObjectRequest 버킷이름, acl(공개),키이후 build()
PutObjectRequest.builder()
.bucket(bucketName)
.acl(ObjectCannedACL.PUBLIC_READ)
.key(key)
.build();

https://sdk.amazonaws.com/java/api/latest/

12.18 create 업로드

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

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

@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 objectKey = "board/" + board.getId() + "/" + file.getOriginalFilename();

                // s3에 파일 업로드
                PutObjectRequest por = PutObjectRequest.builder()
                        .bucket(bucketName)
                        .acl(ObjectCannedACL.PUBLIC_READ)
                        .key(objectKey)
                        .build();
                RequestBody rb = RequestBody.fromInputStream(file.getInputStream(), file.getSize());

                s3.putObject(por, rb);

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

12.19 delete

deleteObject메소드를 사용하는데 DeleteObjectRequest객체를 파라미터로 사용한다.
DeleteObjectRequest를 만드는 방법은 또한 api를 검색하면된다.
bucket이름, key가 필요하다.

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

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

    // s3파일 지우기
    for (String fileName : fileNames) {
        String objectKey = "board/" + id + "/" + fileName;
        DeleteObjectRequest dor = DeleteObjectRequest.builder()
                .bucket(bucketName)
                .key(objectKey)
                .build();

        s3.deleteObject(dor);
    }

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

S3는 폴더라는 개념이 사실은 없다.
objectKey가 폴더형식인것처럼 편의상 보여주는 것이다.
실제로 폴더가 존재하는게 아니다.
그래서 board/4132/a.png라는 객체를 지우는 것이라 보여줄 객체가 없어서
폴더 같은 느낌이 아예 없어져서 디렉토리 자체가 없어지는 것처럼 보이는 것이다.
그래서 디렉토리를 만들거나 디렉토리를 지우는 행위를 하지 않아도된다.

12.20 bucketUrl

여러 jsp에서 같은 bucketUrl을 사용하고 있다.
변경될 일이 없으니 application영역에 저장하면 모든 jsp에서 사용할수 있다.
application영역은 servletcontext이다.
스프링이 IOC컨테이너에 넣어놧으니 bean객체를 만들어서 저장하면된다.
CustomConfiguration에 작성하자.

@PostConstruct어노테이션을 사용하면 config가 만들어지는 순간 객체가 만들어진다.

bucketUrl도 변경될 수 있고 유출되지않도록 custom.properties에 넣어서 숨기는게 좋다.
이것을 주입받아서 사용하는 것으로 하자.

@Configuration
public class CustomConfiguration {

    @Autowired
    private ServletContext application;

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

    @PostConstruct
    public void init() {
        application.setAttribute("bucketUrl", bucketUrl);
    }
}

12.21 update

수정은 삭제 업로드를 적절히 사용하면된다.

@Transactional(rollbackFor = Exception.class)
public boolean update(Board board, List<String> removeFileNames, MultipartFile[] files)
        throws Exception {
    if (removeFileNames != null && !removeFileNames.isEmpty()) {
        for (String fileName : removeFileNames) {
            // 파일 삭제
            String objectKey = "board/" + board.getId() + "/" + fileName;
            DeleteObjectRequest dor = DeleteObjectRequest.builder()
                    .bucket(bucketName)
                    .key(objectKey)
                    .build();

            s3.deleteObject(dor);

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

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

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

            // s3에 파일 업로드
            PutObjectRequest por = PutObjectRequest.builder()
                    .bucket(bucketName)
                    .acl(ObjectCannedACL.PUBLIC_READ)
                    .key(objectKey)
                    .build();
                RequestBody rb = RequestBody.fromInputStream(file.getInputStream(), file.getSize());

                s3.putObject(por, rb);

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

12.22 파일업로드 관련 설정

큰 그림파일이 있을 경우 오류가 발생한다.
스프링의 기본설정이 하나의 파일, 여러 파일 총합의 제한이 있기 때문이다.
https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html
공식문서에서보면
spring.servlet.multipart.max-file-size은 1메가
spring.servlet.multipart.max-request-size 10메가가 기본값이다.

이것을 사용하고 싶다면 application.properties에 작성하면된다.

용량 KB MB GB 대문자로 작성해거나 바이트기준으로 *하면된다.

spring.servlet.multipart.max-file-size=1MB
spring.servlet.multipart.max-request-size=10MB

부트스트랩의 Form text로 input박스에 간단하게 설명을 넣어주자

<div class="mb-3">
    <label for="formFile" class="form-label">첨부 파일</label>
    <input class="form-control" name="files" type="file" id="formFile" accept="image/*" multiple>
    <div class="form-text">
        총 10MB, 하나의 파일을 1MB를 초과할 수 없습니다.
    </div>
</div>

13 aws에 공개하기

run as - maven install
maven이 빌드해주는 것이다. 프로젝트를 war파일로 만들어준다.

BUILD SUCCESS를 보면 성공이다.
프로젝트 refresh하면 target파일에 war파일 생겻다.

찾기쉽게 cloud폴더에 복사 붙여넣기 해주기
aws에 넣어줘야한다. cmd가 필요하다.

git bash로 하자.

scp -i 키파일(.pem) war파일명 bitnami@서버주소:.

ssh -i 키파일 bitnami@서버주소 치면
bitnami미 로고가 뜨면서 서버 가상 컴퓨터에 접속하게 된 것이다.
ls쳐서 복사한 파일이 있으면 된것이다.

java -jar 파일명
평소에 하던대로 콘솔창에 spring뜨듯이 나온다.
이러면 서비스 되고 있는 것이다.

방화벽에서 포트를 열어줘야한다.
aws - EC2 - 실행중인 인스턴스
보안 - 보안그룹 - 인바운드 규칙편집 - 규칙추가
사용자지정 TCP - 8081(포트번호) anywhere-ipv4 - 규칙저장

가상컴퓨터 서버 종료방법 그냥 cmd창꺼버리면 동작을 안한다.

다시키려면 git bash
ssh -i 키파일 bitnami@서버주소

서버 키면 명령어 칠 곳이 없는데
컨트롤 + c누르면 종료가되고 명령어 다시작성가능하다.

백그라운드 실행명령어는 &를 붙이면된다. 이러면 콘솔창을 닫아도 유지된다.
java -jar prj1-20230425-0.0.1-SNAPSHOT.war &

이 실행시키는 것이 어떤 번호를 가지고 있는지 알려준다.
프로젝트를 종료시키려면 이 번호를 알고 있어야한다. 프로세스id이다.
나가도 계속 작동한다.

시연하다가 까먹엇다면 git bash
서버접속
ps aux
진행되고 있는 프로세스 목록이 보인다.
서버가 크지 않아서 눈으로 찾을 수 있다.

kill 프로세스id하면 종료된다.

진행하다가 오류가 발생하면 로그를 봐야한다.
그래서 이 로그를 저장하는 방법에 대해서 알아보자.

echo "?" 하면 ""안의 내용이 출력이된다.
이걸 콘솔말고 파일에 저장하고 싶다면 > 파일명을 써주면된다.
화면에 출력이 안되고 출력결과가 파일로 저장된다.
less 파일명 치면 파일 내용이 나온다. q눌러서 나가면된다.

이걸 사용하면 로그를 남길수 있다.

java -jar 프로젝트명 > log.txt &
하면 log.txt에 로그가 쌓이면서 프로젝트가 실행된다.

중간중간 프로젝트가 업데이트 될 때 프로젝트를 배포하려면
run as maven install부터 다시 해주면 된다.

14. 회원 기능 요약

게시판을 만들어서 공개했다. 회원가입을 추가해보자.
로그인기능 작성기능 등등을 추가할 것이다.

C: 회원가입
R: 회원정보보기
U: 회원정보수정
D: 회원탈퇴

15. 회원 테이블

회원정보를 저장하는 테이블을 만들어야한다.
일단은 간단하게 아이디 비밀번호 닉네임 이메일 가입일을 받자.

CREATE TABLE Member (
    id VARCHAR(20) PRIMARY KEY,
    password VARCHAR(100) NOT NULL,
    nickName VARCHAR(20) NOT NULL UNIQUE,
    email VARCHAR(100) UNIQUE,
    inserted DATETIME DEFAULT NOW()
);

15. 회원가입

15.1 컨트롤러

/member/signup으로 회원가입폼을 호출한다.

@Controller
@RequestMapping("member")
public class MemberController {

    @GetMapping("signup")
    public void signupForm() {

    }
}

15.2 view

<div class="container-lg">
    <div class="row justify-content-center">
        <div class="col-12 col-md-10 col-lg-8">
            <h1>회원가입</h1>
            <form method="post">
                <div class="mb-3">
                    <label for="idInput" class="form-label">아이디</label>
                    <input type="text" id="idInput" class="form-control" name="id" />
                </div>
                <div class="mb-3">
                    <label for="pwdInput" class="form-label">비밀번호</label>
                    <input type="password" id="pwdInput" class="form-control" name="password" />
                </div>
                <div class="mb-3">
                    <label for="nickNameInput" class="form-label">별명</label>
                    <input type="text" id="nickNameInput" class="form-control" name="nickName" />
                </div>
                <div class="mb-3">
                    <label for="emailInput" class="form-label">이메일</label>
                    <input type="email" id="emailInput" class="form-control" name="email" />
                </div>
                <div class="mb-3">
                    <input type="submit" class="btn btn-primary" value="가입"/>
                </div>
            </form>
        </div>
    </div>
</div>

15.3 컨트롤러 - 2

포스트 방식으로 넘어온 것을 처리해준다.
여러 값이 넘어오니 javaBean으로 처리해준다.

@Data
public class Member {
    private String id;
    private String password;
    private String email;
    private String nickName;
    private LocalDateTime inserted;
}

@Controller
@RequestMapping("member")
public class MemberController {

    @PostMapping("signup")
    public void signupProcess(Member member) {
        System.out.println(member);
    }
}

15.4 서비스

컨트롤러에 값이 들어왓으니 서비스에게 일을 시켜야한다.
클래스위에 @Transactional어노테이션을 붙이면 서비스 전체에 트렌젝션 처리를 할 수 있다.

@Service
@Transactional(rollbackFor = Exception.class)
public class MemberServiceImpl implements MemberService {

    @Autowired
    MemberMapper mapper;

    @Override
    public boolean signUp(Member member) {
        int cnt = mapper.createMember(member);

        return cnt == 1;
    }
}

15.5 mapper

서비스는 매퍼에게 db에서 값을 가져오라고 시킨다.

public interface MemberMapper {
    @Insert("""
            INSERT INTO Member (id, password, nickName, email)
            VALUES (#{id}, #{password}, #{nickName}, #{email})
            """)
    int createMember(Member member);
}

15.6 컨트롤러 - 3

가입후 리다이렉트를 해준다.

@PostMapping("signup")
public String signupProcess(Member member, RedirectAttributes rttr) {

    try {
        service.signUp(member);
        rttr.addFlashAttribute("message", "회원가입에 성공했습니다.");
        return "redirect:/list";

    } catch (Exception e) {
        rttr.addFlashAttribute("member", member);
        rttr.addFlashAttribute("message", "회원가입 중 문제가 발생했습니다.");
        return "redirect:/member/signup";
    }
}

16 회원목록

16.1 컨트롤러 - 1

/member/list로 요청가게 한다.

@GetMapping("list")
public void memberList(Model model) {
    List<Member> list = service.listMember();
    model.addAttribute("memberList", list);
}

16.2 view

멤버 목록을 받는 view

<div class="container-lg">
    <div class="row justify-content-center">
        <div class="col-12 col-md-10 col-lg-8">
            <h1>회원 목록</h1>
            <!-- table.table>thead>tr>th*5^^tbody -->
            <table class="table">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>PASSWORD</th>
                        <th>별명</th>
                        <th>이메일</th>
                        <th>가입일</th>
                    </tr>
                </thead>
                <tbody>
                    <c:forEach items="${memberList}" var="member">
                        <td>${member.id }</td>
                        <td>${member.password }</td>
                        <td>${member.nickName}</td>
                        <td>${member.email }</td>
                        <td>${member.inserted }</td>
                    </c:forEach>
                </tbody>
            </table>
        </div>
    </div>
</div>

16.3 서비스

@Override
public List<Member> listMember() {
    List<Member> list = mapper.selectAll();
    return list;
}

16.4 mapper

@Select("""
        SELECT * FROM Member ORDER BY inserted DESC
        """)
List<Member> selectAll();

17 회원정보 보기

회원의 디테일한 정보를 보자.

17.1컨트롤러

@GetMapping("info")
public void memberList(String id, Model model) {
    Member member = service.getInfo(id);
    model.addAttribute("member", member);
}

17.2 서비스

@Override
public Member getInfo(String id) {
    Member member = mapper.getMember(id);

    return member;
}

17.3 mapper

@Select("""
        SELECT * FROM Member WHERE id = #{id}
        """)
Member getMember(String id);

17.4 view

<div class="container-lg">
    <div class="row justify-content-center">
        <div class="col-12 col-md-10 col-lg-8">
            <h1>${member.id}님의정보</h1>
            <div class="mb-3">
                <label for="idInput" class="form-label">아이디</label>
                <input type="text" id="idInput" class="form-control" name="id" value="${member.id}" readonly/>
            </div>
            <div class="mb-3">
                <label for="pwdInput" class="form-label">비밀번호</label>
                <input type="text" id="pwdInput" class="form-control" name="password" value="${member.password}" readonly />
            </div>
            <div class="mb-3">
                <label for="nickNameInput" class="form-label">별명</label>
                <input type="text" id="nickNameInput" class="form-control" name="nickName" value="${member.nickName}" readonly/>
            </div>
            <div class="mb-3">
                <label for="emailInput" class="form-label">이메일</label>
                <input type="email" id="emailInput" class="form-control" name="email" value="${member.email}" readonly/>
            </div>
        </div>
    </div>
</div>

2023.05.04

파일이 없어지길래 어떤 원리인가 햇는데 '객체'로 보기때문에 그런것임을 알게 되었다.
aws에 파일을 배포해보았다. 앞으로 자주 사용하게 될 것이다.
이미지를 서버에 저장하고 링크에 서버를 걸어서 가져오는 방법을 기억해두자.

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

2023.05.09 72일차 Project  (0) 2023.05.09
2023.05.08 71일차 Project  (0) 2023.05.09
2023.05.03 69일차 Project  (0) 2023.05.03
2023.05.02 68일차 Project  (0) 2023.05.02
2023.05.01 67일차 Project  (0) 2023.05.01

+ Recent posts