당니의 개발자 스토리

1/24 본문

LG CNS/필기노트

1/24

clainy 2025. 3. 6. 22:58

https://lms.wsts.kr/course/101/schedule/416/video?chapterId=1926 

 

서버의 제어권을 탈취하는 대표적인 예가 웹셀(mad shell)이 있다.

 

 

 

 

 

매직 코드

 

크기를 제한
종류를 제한

 

이거를 아래와 같이 수정

 

BoardServiceImpl 수정

BoardServiceImpl

 

 

BoardMapper

 

 

xml

이 쿼리를 위해서 루프를 돌림.

admin으로 그냥 하드코딩 하겠다.

<insert id="insertBoardFileList" parameterType="board.dto.BoardFileDto">
    insert into t_file
        (board_idx, original_file_name, stored_file_path, file_size, created_id, created_dt)
    values
        <foreach collection="list" item="item" separator=",">
            (#{item.boardIdx}, #{item.originalFileName}, #{item.storedFilePath}, #{item.fileSize}, 'admin', now())
        </foreach>
</insert>

 

 

DB에 저장되었는지 확인

그런데 board_idx가 0이죠. 왜 그러냐면

현재 여기에서 board_idx를 반환해주는 게 없기 때문이다.

 

insert into t_board(title, contents, created_dt, created_id)는 게시글의 제목, 내용, 생성일자, 작성자 정보를 t_board 테이블에 넣는 작업을 합니다.

그런데 현재 board_idx라는 필드가 자동으로 생성되지 않으므로, board_idx 값이 쿼리에서 반환되지 않으면, insert가 실행될 때 새로 등록된 게시글의 board_idx를 알 수 없게 됩니다.

 

따라서 select * from t_file을 실행했을 때 board_idx가 0으로 나온는 건, 게시글 등록 시에 board_idx가 생성되지 않았기 때문입니다.

이를 해결하려면 쿼리에서 board_idx반환받아야 합니다.

예를 들어, MyBatis에서는 useGeneratedKeys 속성을 이용해 자동 증가된 board_idx 값을 반환받을 수 있습니다.

 

쉽게 말해서 insertBoard 쿼리에서 board_idx가 들어오지 않으니까 insert 해봐도 테이블은 board_idx가 어떤 값인지 모른다.

그래서 board_idx를 자동 생성 후 반환해서 테이블이 board_idx를 알게 하겠다는 것이다.

 

수정

<!--
    외부 입력값을 쿼리에 반영할 경우, #{ }을 이용해야 SQL 인젝션 공격을 방어할 수 있음.
    useGeneratedKeys : DBMS가 자동 생성한 키를 사용
    keyProperty : 반환하는 키를 지정
-->
<insert id="insertBoard" parameterType="board.dto.BoardDto"
        useGeneratedKeys="true" keyProperty="boardIdx">
    insert into t_board(title, contents, created_dt, created_id)
    values (#{title}, #{contents}, now(), #{createdId})
</insert>

 

DBMS는 Database Management System의 약자로, 데이터베이스 관리 시스템을 의미합니다.

쉽게 말하면, DBMS는 데이터베이스를 관리하고, 데이터를 저장, 수정, 삭제, 조회하는 역할을 하는 소프트웨어입니다.

대표적인 DBMS에는 MySQL, PostgreSQL, Oracle, SQL Server 등이 있습니다. 각 DBMS는 자동 증가(Auto Increment) 같은 기능을 제공하여, 새로운 데이터가 추가될 때 고유한 ID 값(예: board_idx)을 자동으로 생성해주기도 합니다.

 

게시판 번호가 들어가있는 것을 확인할 수 있다.

 

<select id="selectBoardFileList" parameterType="int" resultType="board.dto.BoardFileDto">
    select idx, board_idx, original_file_name, format(round(file_size/1024), 0) as file_size
        from t_file
        where board_idx = #{boardIdx} and deleted_yn = 'N'
</select>

 

 

여기보면 이제 첨부 파일이 보이도록 BoardDto도 수정할 것이다.

 

 

첨부 파일 리스트를 저장하고 수정한 boardDto에 넣음

@Transactional
@Override
public BoardDto selectBoardDetail(int boardIdx) { // 상세 정보 조회
    boardMapper.updateHitCnt(boardIdx);   // 조회 시, 조회수 증가 기능 추가

    BoardDto boardDto = boardMapper.selectBoardDetail(boardIdx);  // 게시판 정보 조회
    List<BoardFileDto> boardFileInfoList = boardMapper.selectBoardFileList(boardIdx);  // 첨부파일 정보 저회
    boardDto.setFileInfoList(boardFileInfoList);  // 그 정보를 게시판 정보에 넣어서

    return boardDto;  // 게시판 정보를 return
}

 

 

이제 뷰를 수정할 차례이다.

<!-- 첨부파일 목록을 제공 -->
    <div class="file_list">
        <a th:each="file : ${board.fileInfoList}" th:text="|${file.originalFileName} (${file.fileSize}kb)|"></a>
    </div>

    <input type="button" id="list" class="btn" value="목록으로" />
    <input type="button" id="update" class="btn" value="수정하기" />
    <input type="button" id="delete" class="btn" value="삭제하기" />

 

 

div 태그는 첨부파일 목록을 감싸는 영역입니다.

 

th:each="file : ${board.fileInfoList}": 이 부분은 Thymeleaf의 반복문입니다.

file은 board.fileInfoList(게시글의 파일 정보 목록)에서 하나씩 파일을 꺼내서 반복하는 변수입니다.

fileInfoList는 board 객체에서 첨부된 파일들의 정보를 담고 있는 리스트입니다.

 

th:text="|${file.originalFileName} (${file.fileSize}kb)|":

이 부분은 첨부파일의 원본 파일 이름(originalFileName)과 파일 크기(fileSize)를 텍스트로 출력합니다.

 

${file.originalFileName}: 파일의 원본 이름을 출력합니다.

${file.fileSize}: 파일 크기를 출력하며, kb 단위로 표시됩니다.

이 값을 | |로 감싸서 출력하므로, 예를 들어 파일명 (파일크기kb) 형태로 출력됩니다.

 

<a> 태그는 링크처럼 보이지만, 여기서는 단순히 텍스트를 출력하는 용도로 사용됩니다. 파일명을 클릭할 수 있는 링크로 만들 수도 있지만, 코드에서는 그냥 텍스트로 표시되는 구조입니다.

 

 

생긴 것을 볼 수 있다.

 

내가 만약 static에 강아지 사진을 넣고

http://localhost:8080/강아지.jpg 하면 강아지 사진이 나온다.

static에 있기 때문에 알아서 사진을 내려줍니다.

 

그런데 우리가

이런식으로는 접근할 수 없죠.

 

그래서 파일에 대한 정보를 주면서 요청을 하면,

요청을 받은 놈이 지정된 디렉토리에 있는 파일을 읽어가지고 요청한 곳으로 내려 보내주는 기능이 필요한 겁니다.

그걸 다운로드 기능이라고 합니다.

 

파일 다운로드 기능 추가

<!-- 첨부파일 목록을 제공 -->
<div class="file_list">
    <a th:each="file : ${board.fileInfoList}" th:text="|${file.originalFileName} (${file.fileSize}kb)|"
       th:href="@{/board/downloadBoardFile.do(idx=${file.idx}, boardIdx=${file.boardIdx})}"></a>
</div>

 

th:href="@{/board/downloadBoardFile.do(idx=${file.idx}, boardIdx=${file.boardIdx})}"

이 부분은 링크의 URL을 설정하는 부분입니다. 이 URL은 첨부파일을 다운로드할 수 있는 서버의 요청 경로를 나타냅니다.

 

@{/board/downloadBoardFile.do(idx=${file.idx}, boardIdx=${file.boardIdx})}는 Thymeleaf의 URL 매핑 기능을 사용한 것입니다.

file.idx는 해당 파일의 고유 ID를 나타냅니다.

file.boardIdx는 해당 파일이 어떤 게시글에 첨부되었는지 나타내는 게시글 ID입니다.

downloadBoardFile.do는 파일을 다운로드하는 서버의 URL입니다. 이 링크를 클릭하면 idx와 boardIdx 값을 전달하면서 서버에 파일 다운로드 요청을 보냅니다.

 

 

sql-board.xml 추가

하나의 파일 정보를 읽어오는 쿼리문을 작성할 것이다.

<select id="selectBoardFileInfo" parameterType="map" resultType="board.dto.BoardFileDto">
    select original_file_name, stored_file_path, file_size
    from t_file
    where idx = #{idx} and board_idx = #{boardIdx} and deleted_yn = 'N'
</select>

 

 

idx: 파일의 고유 ID

boardIdx: 게시글의 ID

두 개를 받아야 하므로, parameterType이 map 이다.

 

 

이제 이 selectBoardFileInfo 메서드를 만들어줘야 겠죠.

boardMapper 수정

이런 식으로 하면 sql 쿼리에서는 파라미터 타입을 하나밖에 지정할 수 없기 때문에 이렇게 넣어주면 안된다.

따라서 여러 개의 값이 들어오는 경우, 항상 쿼리 쪽으로 넘어갈 때는 객체로 묶어줘야 한다.

 

이렇게 하면 두 개의 데이터가 자동으로 map 형태로 묶여서 넘어가게 된다.

@Mapper
public interface BoardMapper {
    List<BoardDto> selectBoardList();     // 게시글 목록 조회
    void insertBoard(BoardDto boardDto);  // 게시글 등록
    BoardDto selectBoardDetail(int boardIdx);  // 게시글 상세 조회
    void updateHitCnt(int boardIdx);      // 조회수 증가 기능
    void updateBoard(BoardDto boardDto);  // 게시글 수정 기능
    void deleteBoard(BoardDto boardDto);  // 게시글 삭제 기능
    void insertBoardFileList(List<BoardFileDto> fileInfoList);  // 게시글에 첨부된 파일 정보들을 DB에 저장
    List<BoardFileDto> selectBoardFileList(int boardIdx);  // 게시글에 첨부된 파일 목록 조회
    // idx와 boardIdx를 파라미터로 받아서 해당 첨부파일 정보를 반환하는 메서드
    BoardFileDto selectBoardFileInfo(@Param("idx") int idx, @Param("boardIdx") int boardIdx);
}

 

 

 

// 파일 다운로드 요청을 처리하는 메서드
@GetMapping("/board/downloadBoardFile.do")
public void downloadBoardFile(@RequestParam("idx") int idx, @RequestParam("boardIdx") int boardIdx, HttpServletResponse response) throws Exception {
    // idx와 boardIdx가 일치하는 파일 정보를 조회
    BoardFileDto boardFileDto = boardService.selectBoardFileInfo(idx, boardIdx);
    // boardFileDto 안에 파일의 위치가 있음. 파일의 위치를 가져와야 한다.

    if (ObjectUtils.isEmpty(boardFileDto)) {   // 먼저 이 파일이 있는지 검사
        return;
    }

    // 파일이 있으면 원본 파일이 저장된 위치에서 파일을 읽어서
    // 파일을 호출(요청)한 곳으로, 파일을 응답으로 전달해줌.
    Path path = Paths.get(boardFileDto.getStoredFilePath()); // 파일이 저장된 경로를 가져와서
    byte[] file = Files.readAllBytes(path);  // path 경로에 있는 파일을 읽어서 byte array로 담음.

    // 응답 헤더를 설정하여 파일 다운로드 준비
    response.setContentType("application/octet-stream");
    // "application/octet-stream"은 바이너리 파일 다운로드를 의미. 모든 종류의 파일 다운로드에 사용

    response.setContentLength(file.length);
    // 파일의 길이(크기)를 응답 헤더에 설정. 클라이언트가 다운로드 중 파일 크기를 알 수 있도록 함

    // 파일 이름을 설정해서 다운로드할 때 파일명으로 나타나게 설정
    response.setHeader("Content-Disposition", "attachment; fileName=\"" + URLEncoder.encode(boardFileDto.getOriginalFileName(), "UTF-8") + "\";");
    // "Content-Disposition" 헤더는 파일을 다운로드로 처리할 수 있게 하고,
    // fileName은 파일 다운로드 시 보여줄 실제 파일명을 설정. 인코딩을 해서 한글 파일명도 깨지지 않도록 함.

    // 파일 전송 시 인코딩 방식 설정 (이 파일은 바이너리 파일임을 알려줌)
    response.setHeader("Content-Transfer-Encoding", "binary");
    // "binary"는 파일을 이진 데이터로 전송한다는 뜻. 이미 바이너리 파일이므로 설정 필요.

    // 파일을 응답 스트림에 작성하여 클라이언트에게 전송
    response.getOutputStream().write(file); // 응답의 출력 스트림에 파일 데이터를 기록하여 클라이언트로 전송
    response.getOutputStream().flush(); // 버퍼에 있는 데이터를 즉시 전송하기 위해 flush() 호출. 전송이 지연되지 않게 한다.
    response.getOutputStream().close(); // 응답 스트림을 닫아서 모든 처리가 끝났음을 알리고 자원을 해제한다.
}

 

 

다운로드가 잘 받아진다.

 

https://www.youtube.com/watch?v=nlIRD1GeIq4

깃에서 프로젝트 다운받는 방법, 그냥 다운 받으면 안 받아지는 현상 해결

 

 

예외 처리

 

 

 

 

'LG CNS > 필기노트' 카테고리의 다른 글

2/11  (0) 2025.03.25
프로젝트  (0) 2025.02.27
문제  (0) 2025.02.19
1/9  (0) 2025.01.09
리액트 컴포넌트 만들기 실습  (0) 2025.01.09