| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
- 42서울
- 알고리즘특강
- 18869
- 다이나믹 프로그래밍
- pushswap
- 삼성b형
- 백준
- 9465
- 42서울 #개발 #대외활동
- festify
- gdg스터디
- 16719
- 3967
- 최소 스패닝 트리
- 최소지식원칙
- 파사드패턴
- 16974
- 레벨 햄버거
- 이미지저장
- 자바
- 도시 분할 계획
- 오늘의 문제
- born2beroot
- 삼성전자dx
- 브루트포스
- 이미지삭제
- 삼성전자 dx 알고리즘 특강
- 19951
- 분할정복
- 13905
- Today
- Total
그거 기능이에요
FastifyStatic을 이용해서 이미지를 서빙해보자!! 본문
ft_transcendence를 진행하면서 mypage를 구현할 때 프로필 이미지를 지정하고, 원하는 이미지로 변경할 수 있도록 하는 기능을 만들고 있다. OAuth를 이용해서 42인트라에 로그인하면 user의 프로필 사진을 받아올 수 있는데 이를 바이너리로 받아서 writeFileSync을 이용해서 로컬과 도커 볼륨에 저장한다. 파일 이름은 고유한 값인 user_id로 하고 특정 경로에 저장하는 방식으로 구현을 완료했다. 코드는 다음과 같다.
async function downloadAndSaveImage(imageUrl: string, userId: string, uploadsDir: string): Promise<string | null> {
try {
// URL에서 이미지 파일을 다운로드
const imageResponse = await axios({
url: imageUrl,
method: 'GET',
responseType: 'arraybuffer', // 바이너리 데이터로 받기
});
// 파일명 생성
const fileName = `${userId}.jpg`;
const filePath = path.join(uploadsDir, fileName);
// 파일을 로컬 서버에 저장
fs.writeFileSync(filePath, imageResponse.data);
console.log('이미지 저장 완료:', filePath);
return filePath;
} catch (error) {
console.error('이미지 저장 실패:', error);
return null;
}
}
자 그럼 이걸 어떻게 프론트로 넘겨주지..? 다운받을 때는 42에서 url로 올려둔 사진을 writeFileSync 이용해서 쉽게 구현했는데 이제는 반대로 내가 url로 올려서 프론트에서 이 경로로 파일 데이터를 받을 수 있게 변경해야했다. 내가 찾은 답은 @fastify/static 플러그인에서 제공하는 fastifyStatic 함수이다.
이 함수는 "정적 파일 서빙"기능을 추가해주는데 이 기능은 root로 지정한 서버 내 디렉토리에 클라이언트 요청 URL에 연결해준다. 내가 지정해둔 uploadDir에 파일을 저장하고 프론트에서 get요청이 왔을 때 해당 경로를 프론트에 url을 넘겨주면 Fastify 서버가 해당 경로에서 실제 파일을 찾아 응답 바디로 전송한다. 세상에. 정리하면 사진 저장, 전송 기능의 핵심은 writeFileSync 함수와 fastifyStatic 함수라고 볼 수 있다. static 경로를 반환하는 코드는 다음과 같다.
fastify.register(fastifyStatic, {
root: '/app/uploads', // 실제 이미지 저장 경로
prefix: '/uploads/', // 브라우저에서 접근할 경로 (URL)
});
fastify.get('/', async function (request, reply) {
//파일 경로만 만들어서 반환하면 됨 (backend_user/id)
const userCookie = request.cookies.auth_token; // 쿠키 받아오기
const decoded = fastify.jwt.verify<{ id: number }>(userCookie || ''); // JWT 검증 및 디코딩
const userId = decoded.id; //유저 id 받기
const imageUrl = `/api/user/mypage/avatar/uploads/${userId}.jpg`; // 정적 경로 구성
return { avatar_url: imageUrl };
})
이제 저장이랑 전송은 해결했고, 변경은 어떡하지?? 그냥 지우고 다시 저장하고 해당 이미지 static 경로로 반환하면 되잖아?? 그럼 이제 새로 들어가는 기능은 지우기다. fs패키지의 unlinkSync 함수를 이용해서 파일을 지웠고 다시 위의 과정을 반복해서 새 파일로 변환하여 경로를 반환했다. 사실 저장, 업로드가 어려웠고 수정은 쉬웠다. 라고 생각하고 싶었다. 우선 구현한 코드는 다음과 같다.
fastify.patch('/', async function (request, reply) {
const file = await request.file(); // FormData의 'file' 필드
if (!file) {
return reply.status(400).send({ error: 'No file uploaded' });
}
const userCookie = request.cookies.auth_token; // 쿠키 받아오기
const decoded = fastify.jwt.verify<{ id: number }>(userCookie || ''); // JWT 검증 및 디코딩
const userId = decoded.id; //유저 id 받기
const uploadsDir = path.join(process.cwd(), 'uploads'); // 사진 파일 경로
const filePath = path.join(uploadsDir, `${userId}.jpg`); // 사진 파일 이름 추가
try {
await fs.unlinkSync(filePath);
console.log('파일 삭제 성공!');
} catch (err) {
console.error('파일 삭제 실패:', err);
}
const pump = util.promisify(pipeline);
try {
// 스트림 저장
await pump(file.file, fs.createWriteStream(filePath));
return reply.status(200).send();
} catch (err) {
console.error(err);
return reply.status(500).send({ error: 'File upload failed' });
}
})
여기서 문제는 분명 await을 붙이고 스트림을 저장했는데 아마 스트림이 저장되는 중에 프론트로 url요청이 가서 프론트에서 해당 url에서 정상적인 파일을 찾지 못하고 오류를 반환했고, 이후 저장이 완료된 후 새로고침을 하면 정상적으로 변경된 이미지가 출력되었다. 동기 선언했는데 왜 비동기로 동작할까??
사실 아직도 잘 모르겠다. 지금 임시로 프론트에서 patch 요청을 보내면 settimeof로 2초 기다리고 reload함수를 호출하도록 땜빵해둔 상태다. 이번주 안에 수정하고 오류 원인과 해결 방법을 다시 포스팅해야겠다....
'42서울 > 6서클' 카테고리의 다른 글
| ft_transcendence 42Oauth 구현 내용 (0) | 2025.04.09 |
|---|