- csv 파일을 읽어서 오류가 있는 항목들을 걸러내고, 필요한 내용이 있는 항목에 대해서 계산 결과를 나오게 해주면 된다.
- 숫자에 해당하는 값만 계산 결과를 출력하고 숫자가 아닌 값이 하나라도 존재하면 해당 행에
대해서는 계산 결과를 출력하지 않는다.- 출력 후 숫자가 아닌 값들만 모아서 출력해본다.
해당 csv 파일에 대한 row 열이 front에서는 처리하기 힘들다는 걸 알게됨
1. nextjs Route Handlers 를 활용해서 처리하기로함
-> 해당 csv 파일 컴파일시 url query로 보내려고 했지만 해당 query열이 비정상적으로 길어진다는 걸 깨닫게됨
-> 그러므로 put이나 post로 request body로 보내야 활용이 좋다고 생각하게됨
// GET 메서드 사용 시
export async function GET(request: Request) {
// URL에서 파일 데이터를 쿼리 파라미터로 받아야 함
// 큰 파일의 경우 URL 길이 제한으로 문제 발생
}
// PUT 메서드 사용 시
export async function PUT(request: Request) {
// 리소스를 완전히 대체할 때 사용
// 파일 업로드에도 사용 가능하지만 POST가 더 일반적
}
// POST 메서드 사용시
export async function POST(request: Request) {
// 대용량 파일 및 업로드에는 POST가 일반적
}
csv 파일 형식이 길기 때문에 json 보다는 원본 그대로 읽을 수 있는
// CSV 파일의 원본 텍스트 내용을 그대로 받음
// 예시: "1,2,3\n4,5,6\n7,8,9"
import { NextResponse } from "next/server";
import Papa from "papaparse";
interface StatisticalResult {
minVal: number;
maxVal: number;
total: number;
mean: number;
stdev: number;
median: number;
}
interface ParseResult {
data: string[][];
}
/**
* CSV 파일 처리 API 엔드포인트
* @param {Request} request CSV 파일 데이터
* @returns {Promise<NextResponse>} 처리된 결과와 숫자가 아닌 문자들
*/
export async function POST(request: Request): Promise<NextResponse> {
const text = await request.text();
// request.text() 활용
const results: StatisticalResult[] = [];
// 결과값 노출
const nonNumericChars: string[] = [];
// 해당 문자열인 경우 문자열 저장
//import Papa from "papaparse"; -> csv file 컴파일러 활용 라이 브러리
Papa.parse(text, {
complete: (result: ParseResult) => {
result.data.forEach((row: string[]) => {
if (row.length === 0) return;
-> row 가 없는 경우 return
// 각 행에서 숫자가 아닌 문자들만 필터링
const nonNumericInRow = row.filter((value: string) => {
const trimmed = value.trim();
// 공백제거 문자열 양 끝의 공백을 제거하면서 원본 문자열을 수정하지 않고 새로운 문자열을 반환합니다.
const num = Number(trimmed);
return isNaN(num) || trimmed === "";
});
// 숫자가 아닌 문자가 있다면 저장
if (nonNumericInRow.length > 0) {
nonNumericChars.push(...nonNumericInRow);
}
// 모든 값이 숫자인 경우에만 통계 계산
const isValidRow = row.every((value: string) => {
const num = Number(value.trim());
return !isNaN(num) && value.trim() !== "";
});
if (isValidRow) {
const numbers = row.map(Number);
// 최소값 계산
const minVal = Math.min(...numbers);
// 최댓갑 계산
const maxVal = Math.max(...numbers);
// total 종합값 계산
const total = numbers.reduce((a, b) => a + b, 0);
// 평균값 계산 total / numbers.length
const mean = total / numbers.length;
// 표준편차 (standard deviation)
const stdev = Math.sqrt(
numbers
.map(x => Math.pow(x - mean, 2)) // 각 값에서 평균을 빼고 제곱
.reduce((a, b) => a + b) // 제곱값들의 합계
/ numbers.length // 개수로 나누고
); // 제곱근 구하기
// 중간값 구하기
const median = numbers.sort((a, b) => a - b)[
Math.floor(numbers.length / 2)
];
results.push({ minVal, maxVal, total, mean, stdev, median });
}
});
},
header: false,
skipEmptyLines: true,
});
return NextResponse.json({
results,
nonNumericChars: [...new Set(nonNumericChars)], // 중복 제거
});
}
이게 router handler를 활용해서 작업
client 단
"use client";
import ResultsDisplay, { ITEMS_PER_PAGE } from "./ResultsDisplay";
import NonNumericRowsDisplay from "./NonNumericRowsDisplay";
import { Result } from "@/types/csv";
import { useState } from "react";
const Brick1 = () => {
const [results, setResults] = useState<Result[]>([]);
const [nonNumericChars, setNonNumericChars] = useState<string[]>([]);
const [currentPage, setCurrentPage] = useState(0);
const handleFileChange = async (
event: React.ChangeEvent<HTMLInputElement>
) => {
const file = event.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async (e) => {
const text = e.target?.result;
if (typeof text === "string") {
const response = await fetch("/api/upload", {
method: "POST",
headers: {
"Content-Type": "text/plain",
},
body: text,
});
const data = await response.json();
setResults(data.results);
setNonNumericChars(data.nonNumericChars);
setCurrentPage(0);
}
};
reader.readAsText(file);
};
const handleNextPage = () => {
if ((currentPage + 1) * ITEMS_PER_PAGE < results.length) {
setCurrentPage(currentPage + 1);
}
};
const handlePrevPage = () => {
if (currentPage > 0) {
setCurrentPage(currentPage - 1);
}
};
return (
<div>
<h1>CSV Processing</h1>
<input type="file" accept=".csv" onChange={handleFileChange} />
<ResultsDisplay results={results} currentPage={currentPage} />
<button onClick={handlePrevPage} disabled={currentPage === 0}>
Previous
</button>
<button
onClick={handleNextPage}
disabled={(currentPage + 1) * ITEMS_PER_PAGE >= results.length}>
Next
</button>
<NonNumericRowsDisplay nonNumericChars={nonNumericChars} />
</div>
);
};
export default Brick1;