안녕하세요! 이번엔 파일 업로드 하는 기능과 다운로드까지 가능한 기능을 넣겠습니다!
그리고 시간이 되면 조회수 기능까지 넣고요 만약 안되면 내일에 하면 되겠죠!
.
.
.
시작합니다.
일단 저는 파일을 따로 저장 해둘 디렉터리를 만들겠습니다!
sudo mkdir -p /path/to/upload/directory 만들겠습니다!
이렇게 따로 디렉토리를 만든 이유는
행여나 웹에서 악영향이 되는 코드가 업로드될 때 웹에서 분리시키기 위함입니다!
그리고 업로드 기능을 먼저 만들겠습니다.
일단 write_board.php에 파일 올릴 수 있는 칸을 만들겠습니다!
<form action="write_process_board.php" method="POST" enctype="multipart/form-data">
<p><input type="text" name="title" maxlegth="44" placeholder="제목 입력, 최대 44자까지 가능합니다" required></p>
<p><textarea name="detail" rows="20" cols="20" maxlength="254" placeholder="내용 작성,최대 254자 가능합니다" required></textarea></p>
<p><input type="file" name="file" id="fileToUpload"></p>
<p><input type="submit" value="올리기"></p>
</form>
이렇게 저는 수정했습니다.
form에 enctype="multipart/form-data"는 이 폼에 파일 업로드가 가능하다고 알려주는 겁니다.
일단 확인해보겠습니다
이제 write_process_board.php로 가서 코드 수정하겠습니다.
if ($_SERVER['REQUEST_METHOD'] == 'POST' && !empty($_FILES['file'])) {
// 업로드된 파일 정보 가져오기
$file_name = $_FILES['file']['name'];
$file_tmp_name = $_FILES['file']['tmp_name'];
$file_size = $_FILES['file']['size'];
$file_error = $_FILES['file']['error'];
$allowed_mime_types = ['image/jpeg', 'image/png', 'image/gif'];
// 파일 업로드가 정상적으로 처리되었는지 확인
if ($file_error === UPLOAD_ERR_OK) {
// 파일 MIME 타입 확인
$file_mime_type = mime_content_type($file_tmp_name);
if (in_array($file_mime_type, $allowed_mime_types)) {
// 파일 저장 경로
$upload_path = '/path/to/upload/directory/' . $file_name;
// 파일 이동 및 저장
if (move_uploaded_file($file_tmp_name, $upload_path)) {
$_SESSION['write_error'] = '파일 업로드 성공';
} else {
$_SESSION['write_error'] = '파일 업로드 실패';
}
} else {
$_SESSION['write_error'] = '잘못된 파일 형식입니다';
}
} else {
$_SESSION['write_error'] = '파일 업로드 실패';
}
}
뭔가 어려워 보이죠.. 저도 이거 찾고 이해하느라 오래걸렸..
차근차근 설명 들어가겠습니다.
그리고 조금씩 수정하겠습니다. 최종 코드는 맨 밑에도 있어요!
먼저
if ($_SERVER['REQUEST_METHOD'] == 'POST' && !empty($_FILES['file']))
이 코드에 $_SERVER['REQUEST_METHOD'] 는 REQUEST_METHOD에는 POST, GET인지 확인하는 변수입니다!
그러니깐 어떤 방식으로 요청을 하는지 알기 위해 하는 겁니다. 저희는 POST방식으로 보냈죠?
그리고 !empty($_FILES['file']) 는 파일에 비어 있지 않으면 이 조건절을 실행합니다!
그다음은
$file_name = $_FILES['file']['name'];
$file_tmp_name = $_FILES['file']['tmp_name'];
$file_size = $_FILES['file']['size'];
$file_error = $_FILES['file']['error'];
$allowed_mime_types = ['image/jpeg', 'image/png', 'image/gif','text/plain','application/zip','application/x-hwp','application/msword','application/vnd.ms-excel','application/pdf']; //MIME 허락 된 것
//MIME 형식에 허락된 것
첫 줄은 file의 이름을 가져오는 것입니다.
tmp_name은 서버에 임시 파일을 저장한 경로를 뜻합니다! 잠깐 여기 두었다가 이제 업로드할 때 옮기는 거죠
size는 파일 크기입니다
error는 파일 업로드 중 오류 발생하면 저장하게 됩니다.
allowed_mim_types는 저희가 여러 파일을 받게 되는데 그중에 어느 파일 형식만 받는지 저장하는 겁니다!
이상한 파일을 받는 걸 방지하는 거죠
만약 파일 형식을 추가로 받고 싶거나 다른 걸 받고 싶다면 저 배열에서 추가만 하면 되겠죠!
MIME 형태의 파일형식 이름은 구글에 MIME [파일형식] 치면 나옵니다!
저는 이미지, zip, hwp, word, 엑셀, pdf 받을 수 있게 했습니다
그다음으로
// 파일 업로드가 정상적으로 처리되었는지 확인
if ($file_error === UPLOAD_ERR_OK) {
만약 error에서 UPLOAD_ERR_OK가 나오면 정상적으로 들어와 있을 때 실행 합니다
// 파일 MIME 타입 확인
$file_mime_type = mime_content_type($file_tmp_name);
올린 파일의 형식을 가져옵니다!
if (in_array($file_mime_type, $allowed_mime_types)) {
여기서 in_array() 함수는 배열에 특정 값이 존재하는지 검사해 주는 함수입니다.
즉 file_mime_type변수가 allowed_mime_types에 들어가 있는지 검사하는 겁니다.
저희가 허락된 파일 확장자면 이 조건문을 실행합니다!
// 파일 이동 및 저장
if (move_uploaded_file($file_tmp_name, $upload_path)) {
$_SESSION['file_error'] = '파일 업로드 성공';
} else {
$_SESSION['file_error'] = '파일 업로드 실패';
}
다음은 파일의 저장 경로를 저장하고
move_uploaded_file 함수로 파일을 저장하게 합니다!
.
.
후.. 이렇게 업로드는 이렇게 마치겠습니다.
그런데! 여기서 문제점이 있네요 파일 이름이 겹치면 나중에 가져올 때 문제점이 생기겠죠?
그러니깐 저는 파일에 날짜까지 저장하겠습니다.
if ($_SERVER['REQUEST_METHOD'] == 'POST' && !empty($_FILES['file'])) {
// 업로드된 파일 정보 가져오기
$file_name = $_FILES['file']['name'];
$timestamp = time(); // 현재 시간을 초로 반환
$new_file_name = $timestamp . '_' . $file_name; // 현재 시간과 원래 파일 이름을 합쳐 새로운 파일 이름 생성
$file_tmp_name = $_FILES['file']['tmp_name'];
$file_size = $_FILES['file']['size'];
$file_error = $_FILES['file']['error'];
$allowed_mime_types = ['image/jpeg', 'image/png', 'image/gif','text/plain','application/zip','application/x-hwp','application/msword','application/vnd.ms-excel','application/pdf']; //MIME 허락 된 것
//sql공격 방지용
$new_file_name = mysqli_real_escape_string($conn,$new_file_name);
// 파일 업로드가 정상적으로 처리되었는지 확인
if ($file_error === UPLOAD_ERR_OK) {
// 파일 MIME 타입 확인
$file_mime_type = mime_content_type($file_tmp_name);
if (in_array($file_mime_type, $allowed_mime_types)) {
// 파일 저장 경로
$upload_path = '/path/to/upload/directory/' . $new_file_name;
// 파일 이동 및 저장
if (move_uploaded_file($file_tmp_name, $upload_path)) {
$_SESSION['file_error'] = '파일 업로드 성공';
} else {
$_SESSION['file_error'] = '파일 업로드 실패';
}
} else {
$_SESSION['file_error'] = '잘못된 파일 형식입니다';
}
} else {
$_SESSION['file_error'] = '파일 업로드 실패';
}
}
이러면 초단위로 파일이름이 다 다르겠죠!
그리고 sql 공격 방지용으로 escape 함수 썼습니다.
그리고 view_board.php에 file_error 세션을 받겠습니다
<p>
<?php session_start();
if (isset($_SESSION['file_error'])) {
echo $_SESSION['file_error'];
unset($_SESSION['file_error']);
}
?>
</p>
맨 밑에다가 넣었습니다
.
.
.
이렇게 업로드를 마치겠습니다.. 한번 해보죠!
happy.txt 간단하게 만들고 이렇게 업로드해보겠습니다!
아마 저희가 설정한 디렉터리에 다른 그룹 사용자가 파일 업로드하는 권한이 없어서 일 겁니다
저희가 지정한 디렉터리가 root 꺼라 저희 서버가 이용이 불가능합니다!
그러니 저희는 설정을 해야만 합니다
#sudo chown -R www-data /path/to/upload/directory
아파치는 www-data가 이름입니다! 그러니 이렇게 소유자를 변경하고 난 뒤에 다시 업로드하면?
좋아요!
이렇게 업로드를 마치겠습니다.
다음은 다운로드이죠!
일단은 view_board.php에 이 게시물에 어느 파일이 저장되어 있는지 알아봐야 되겠죠??
그러니깐 그 파일의 경로를 알아야만 되겠죠.
그러니 일단 저는 데이터베이스에 새로운 칼럼 추가하겠습니다. 파일명 저장하기 위해서요!
그리고 write_process_board.php에 파일명도 같이 저장되게 하겠습니다!
//session으로 유저 이름 받기
$user_id = $_SESSION['login_id'];
echo $board_id;
if(isset($board_id)) {
//board_id가 있다는 것은 수정을 의미
$sql = "UPDATE $db_board SET title = '$title', detail='$detail', file_name='$new_file_name' WHERE board_id='$board_id' ";
}
else {
//board_id가 없으니 새로 만드는 sql문
$sql = "INSERT INTO $db_board (id, title, detail,file_name) VALUES ('$user_id', '$title', '$detail','$new_file_name')";
}
이러면 되겠죠
그리고 view_board.php에 파일명도 보이게 하겠습니다.
//게시물 출력
while($row = mysqli_fetch_assoc($result)) {
echo '<p>ID: '.$row['id'].'</p>';
echo '<p>Title: '.$row['title'].'</p>';
echo '<p>Detail: '.$row['detail'].'</p>';
echo '<p>Date: '.$row['date'].'</p>';
if(isset($row['file_name'])) {
echo '<p>FILE: '.$row['file_name'].'</p>';
}
//로그인한 사용자의 정보 가져온다
$user_id = $_SESSION['login_id'];
//게시물 작성자와 로그인한 ID와 일치한 경우
if($row['id'] == $user_id) {
//게시물 수정 버튼을 보여준다
echo "<form method='POST' action='fix_board.php'>
<input type='hidden' name='board_id' value='".$row['board_id']."'>
<p><button type='submit'>게시물 수정</button></p>
</form>";
//게시물 삭제 버튼을 보여준다
echo "<form method='POST' action='delete_board.php'>
<input type='hidden' name='board_id' value='".$row['board_id']."'>
<p><button type='submit'>삭제</button></p>
</form>";
}
}
그리고 한 번 봐보겠습니다!
그런데 시간 초까지 보여주니깐 조금 불편하네요 _ 앞에 있는 건 지우고 나오게 하겠습니다.
if(isset($row['file_name'])) {
$file_name = explode('_', $row['file_name'])[1]; // '_' 문자를 기준으로 분리 후 두번째 요소(파일명)만 가져옵니다.
echo '<p>FILE: '.$file_name.'</p>';
}
저장하고 다시 실행하면?
이제 다운로드 만들겠습니다.
저는 파일 명에 다운로드 링크를 걸겠습니다!
if(isset($row['file_name'])) {
$file_path = 'http://path/to/upload/directory/'.$row['file_name']; // 저장된 파일 경로
$file_name = explode('_', $row['file_name'])[1]; // '_' 문자를 기준으로 분리 후 첫번째 요소(파일명)만 가져옵니다.
echo '<p>FILE: <a href="'.$file_path.'" download>'.$file_name.'</a></p>'; // 다운로드 링크 생성
}
일단 이렇게 하이퍼링크에 download을 걸었습니다.
저장 후 다시 실행해보겠습니다!
그래서 아예 download_process.php을 따로 만들었습니다!!
view board.php엔
$file_name = explode('_', $row['file_name'])[1]; // '_' 문자를 기준으로 분리 후 첫번째 요소(파일명)만 가져옵니다.
$download_url = 'download_process.php?filename='.$row['file_name'];
echo '<p>FILE: <a href="'.$download_url.'">'.$file_name.'</a></p>';
이렇게 download_process.php와 이어지게 걸었고요
<?php
if(isset($_GET['filename'])) {
// 파일 이름 및 경로 가져오기
$filename = $_GET['filename'];
$filepath = '/path/to/upload/directory/'.$filename;
// 파일이 존재하는지 확인
if(file_exists($filepath)) {
// 다운로드 헤더 설정
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.$filename.'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($filepath));
readfile($filepath);
exit;
} else {
echo '파일이 존재하지 않습니다.';
}
} else {
echo '파일 이름이 전달되지 않았습니다.';
}
?>
이렇게 했습니다.
각각 설명하자면
Content-Descrtiption은 파일 전송에 대한 설명을 추가합니다!
Content-Type는 다운로드되는 파일의 MIME 설정합니다! 저 octet-stream은 이진 데이터를 의미합니다!
Content-Disposition은 다운로드 시 다운로드 대화 상자에 표기된 파일 이름을 설정합니다! $변수에 따라 이름이 바뀝니다
Expires는 캐시의 만료일을 설정합니다! 0을 설정하면 브라우저가 항상 새로운 파일을 가져오게 합니다.
Cache-Control은 캐시 제어합니다! must-revalidate는 캐시를 사용하기 전에 서버로부터 새로운 데이터를 가져오라고 명형 합니다
Pragma는 http1.0과 호환되게 합니다.
Content_length는 다운로드되는 파일의 크기를 설정합니다
readfile은 파일을 읽고 출력합니다!
exit은 다운로드가 완료되면 스크립트 실행을 중지합니다
잘 되네요!!
아직 부족합니다..
너무 늦었으니 조회수 기능은 내일 만들고 보안도 신경 써서 다시 코딩하겠습니다!!
파일 검증, 파일 이름 암호화, 세션 방어 이러한 보안 문제는 차차 고치겠습니다!
.
.
.
.
.
.
-view_board.php
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>게시물 보는중</title>
</head>
<body>
<p>
<?php
include 'DB_INFO.php'; //데이터 베이스 정보
//데이터베이스 연결
$conn = mysqli_connect($host,$username,$password,$db_board);
//데이터베이스 오류시 종료
if(mysqli_connect_errno()) {
die("데이터 베이스 오류: ". mysqli_connect_error());
}
session_start(); //세션 시작
if(!isset($_SESSION['login_id'])) {
//로그인하지 않은 사용자
header("Location: login.php"); //login 화면으로 바꾼다
exit(); //이 페이지를 바로 닫는다
}
//GET방식으로 전달된 index 받는다
$board_id = $_GET['index'];
//작성된 게시물들 조회 문
$sql = "SELECT * FROM $db_board WHERE board_id = '$board_id' ";
//쿼리문 실행
$result = mysqli_query($conn, $sql);
//게시물 출력
while($row = mysqli_fetch_assoc($result)) {
echo '<p>ID: '.$row['id'].'</p>';
echo '<p>Title: '.$row['title'].'</p>';
echo '<p>Detail: '.$row['detail'].'</p>';
echo '<p>Date: '.$row['date'].'</p>';
if (isset($row['file_name'])) {
$file_name = explode('_', $row['file_name'])[1]; // '_' 문자를 기준으로 분리 후 첫번째 요소(파일명)만 가져옵니다.
$download_url = 'download_process.php?filename='.$row['file_name'];
echo '<p>FILE: <a href="'.$download_url.'">'.$file_name.'</a></p>';
}
//로그인한 사용자의 정보 가져온다
$user_id = $_SESSION['login_id'];
//게시물 작성자와 로그인한 ID와 일치한 경우
if($row['id'] == $user_id) {
//게시물 수정 버튼을 보여준다
echo "<form method='POST' action='fix_board.php'>
<input type='hidden' name='board_id' value='".$row['board_id']."'>
<p><button type='submit'>게시물 수정</button></p>
</form>";
//게시물 삭제 버튼을 보여준다
echo "<form method='POST' action='delete_board.php'>
<input type='hidden' name='board_id' value='".$row['board_id']."'>
<p><button type='submit'>삭제</button></p>
</form>";
}
}
?>
</p>
<p>
<?php session_start();
if (isset($_SESSION['write_error'])) {
echo $_SESSION['write_error'];
unset($_SESSION['write_error']);
}
?>
</p>
<p>
<?php session_start();
if (isset($_SESSION['file_error'])) {
echo $_SESSION['file_error'];
unset($_SESSION['file_error']);
}
?>
</p>
<p></p>
<form action="write_board.php" method="POST">
<p><input type="submit" name="write" value="게시판 작성"></p>
</form>
<form action="board.php">
<p><input type="submit" value="메인페이지로"></p>
</form>
</body>
</html>
-download_process.php
<?php
if(isset($_GET['filename'])) {
// 파일 이름 및 경로 가져오기
$filename = $_GET['filename'];
$filepath = '/path/to/upload/directory/'.$filename;
// 파일이 존재하는지 확인
if(file_exists($filepath)) {
// 다운로드 헤더 설정
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.$filename.'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($filepath));
readfile($filepath);
exit;
} else {
echo '파일이 존재하지 않습니다.';
}
} else {
echo '파일 이름이 전달되지 않았습니다.';
}
?>
-write_process_board.php
<?php
include 'DB_INFO.php'; //데이터 베이스 정보
session_start(); //세션 시작
if(!isset($_SESSION['login_id'])) {
//로그인하지 않은 사용자
header("Location: login.php"); //login 화면으로 바꾼다
exit(); //이 페이지를 바로 닫는다
}
//게시판 데이터베이스 연결
$conn = mysqli_connect($host,$username,$password, $db_board);
//데이터베이스 오류시 종료
if(mysqli_connect_errno()) {
die("데이터 베이스 오류: ". mysqli_connect_error());
}
//POST로 전달된 정보 받기
if(isset($_POST['board_id'])){
$board_id = mysqli_real_escape_string($conn,$_POST['board_id']);
} else {
$board_id = null;
}
$title = mysqli_real_escape_string($conn,$_POST['title']);
$detail = mysqli_real_escape_string($conn,$_POST['detail']);
if ($_SERVER['REQUEST_METHOD'] == 'POST' && !empty($_FILES['file'])) {
// 업로드된 파일 정보 가져오기
$file_name = $_FILES['file']['name'];
$timestamp = time(); // 현재 시간을 초로 반환
$new_file_name = $timestamp . '_' . $file_name; // 현재 시간과 원래 파일 이름을 합쳐 새로운 파일 이름 생성
$file_tmp_name = $_FILES['file']['tmp_name'];
$file_size = $_FILES['file']['size'];
$file_error = $_FILES['file']['error'];
$allowed_mime_types = ['image/jpeg', 'image/png', 'image/gif','text/plain','application/zip','application/x-hwp','application/msword','application/vnd.ms-excel','application/pdf']; //MIME 허락 된 것
//sql공격 방지용
$new_file_name = mysqli_real_escape_string($conn,$new_file_name);
// 파일 업로드가 정상적으로 처리되었는지 확인
if ($file_error === UPLOAD_ERR_OK) {
// 파일 MIME 타입 확인
$file_mime_type = mime_content_type($file_tmp_name);
if (in_array($file_mime_type, $allowed_mime_types)) {
// 파일 저장 경로
$upload_path = '/path/to/upload/directory/' . $new_file_name;
// 파일 이동 및 저장
if (move_uploaded_file($file_tmp_name, $upload_path)) {
$_SESSION['file_error'] = '파일 업로드 성공';
} else {
$_SESSION['file_error'] = '파일 업로드 실패';
}
} else {
$_SESSION['file_error'] = '잘못된 파일 형식입니다';
}
} else {
$_SESSION['file_error'] = '파일 업로드 실패';
}
}
//session으로 유저 이름 받기
$user_id = $_SESSION['login_id'];
echo $board_id;
if(isset($board_id)) {
//board_id가 있다는 것은 수정을 의미
$sql = "UPDATE $db_board SET title = '$title', detail='$detail', file_name='$new_file_name' WHERE board_id='$board_id' ";
}
else {
//board_id가 없으니 새로 만드는 sql문
$sql = "INSERT INTO $db_board (id, title, detail,file_name) VALUES ('$user_id', '$title', '$detail','$new_file_name')";
}
//sql문 실행
if(mysqli_query($conn,$sql)) {
//수정인 경우 바로 게시판 보이게 함
if(isset($board_id)) {
$_SESSION['write_error'] = '수정되었습니다!';
header("Location: view_board.php?index=$board_id");
}
else {
$board_id = mysqli_insert_id($conn); // 새로 생성한 게시물의 id를 가져옴
$_SESSION['write_error'] = '작성되었습니다!';
header("Location: view_board.php?index=$board_id");
}
} else {
$_SESSION['write_error'] = '작성 중 오류가 발생하였습니다.';
header("Location: view_board.php");
}
exit();
?>
-write_board.php
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>게시판 작성 중</title>
</head>
<body>
<h1>게시판 작성!</h1>
<?php
session_start(); //세션 시작
if(!isset($_SESSION['login_id'])) {
//로그인하지 않은 사용자
header("Location: login.php"); //login 화면으로 바꾼다
exit(); //이 페이지를 바로 닫는다
}
?>
<form action="write_process_board.php" method="POST" enctype="multipart/form-data">
<p><input type="text" name="title" maxlegth="44" placeholder="제목 입력, 최대 44자까지 가능합니다" required></p>
<p><textarea name="detail" rows="20" cols="20" maxlength="254" placeholder="내용 작성,최대 254자 가능합니다" required></textarea></p>
<p><input type="file" name="file" id="fileToUpload"></p>
<p><input type="submit" value="올리기"></p>
</form>
<form action="board.php">
<p><input type="submit" value="돌아가기"></p>
</form>
<p>
<?php session_start();
if (isset($_SESSION['write_error'])) {
echo $_SESSION['write_error'];
unset($_SESSION['write_error']);
}
?>
</p>
</body>
</html>
당연히 질문받고 있고요 제 코드에 이상한 게 있으면 지적해주세요!
'개발과제' 카테고리의 다른 글
[노말틱 모의 해킹 취업반 9주차 개발과제 ] 게시판 만들기(5) - 날짜 검색 (1) | 2023.04.22 |
---|---|
[노말틱 모의 해킹 취업반 8주차 개발과제 ] 게시판 만들기(4) - 조회수 기능 (0) | 2023.04.19 |
[노말틱 모의 해킹 취업반 7주차 개발과제 ] 게시판 만들기(3) - 페이징 (0) | 2023.04.17 |
[노말틱 모의 해킹 취업반 6주차 개발과제 ] 게시판 만들기(2) - 수정, 삭제, 검색 (0) | 2023.04.16 |
[노말틱 모의 해킹 취업반 5주차 개발과제 ] 게시판 만들기(1) - 리스트 확인, 읽기, 쓰기 (0) | 2023.04.13 |