HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
장지원 페이지/
🔧
Development
/fastapi / virtual environment/
1. 이미지/이름 업로드

1. 이미지/이름 업로드

 
notion image
 
main
# uploads 디렉토리 생성 import os os.makedirs("uploads/profile_images", exist_ok=True) # mount app.mount("/uploads", StaticFiles(directory="uploads"), name="uploads")
router
# 프로필 이미지 조회 엔드포인트 @router.get("/me/profile-image") async def get_profile_image( current_user: models.User = Depends(auth.get_current_user) ): if not current_user.profile_img: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Profile image not found" ) return {"profile_img": current_user.profile_img}
 
뭘 하려면 router 설정을 해주면 되는 것 같다.
flutter 환경에서는 server URL을 받아놓고, 그걸 파싱해서 data를 가져오는 것 같다.
 
front
import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import '../hero_quest/hero_quest_main.dart'; import '../daily_quest/daily_quest_main.dart'; import '../store/store_main.dart'; const String API_BASE_URL = 'http://127.0.0.1:8000'; // 실제 API URL로 변경해주세요 const String user = "user@example.com"; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @override State<HomeScreen> createState() => _HomeScreenState(); } class _HomeScreenState extends State<HomeScreen> { String? profileImageUrl; String? profileName; // 프로필 이름을 저장할 변수 추가 final String _token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyQGV4YW1wbGUuY29tIiwiZXhwIjoxNzM4ODIzNTkxfQ.zuj0HDRN5wk2wqhPoeVOMxwm6yAEEBcJm0HTiMtrN-A"; @override void initState() { super.initState(); _fetchProfileImage(); _fetchProfileName(); // 프로필 이름을 가져오는 함수 호출 } Future<void> _fetchProfileImage() async { try { final response = await http.get( Uri.parse('$API_BASE_URL/users/me/profile-image'), headers: { 'Authorization': 'Bearer $_token', 'Content-Type': 'application/json', }, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); // 상대 경로를 전체 URL로 변환 final fullImageUrl = '$API_BASE_URL/${data['profile_img']}'; print('Full image URL: $fullImageUrl'); // 전체 URL 확인용 setState(() { profileImageUrl = fullImageUrl; // 전체 URL을 사용 }); } } catch (e) { print('Error fetching profile image: $e'); } } // 프로필 이름을 가져오는 함수 추가 Future<void> _fetchProfileName() async { try { final response = await http.get( Uri.parse('$API_BASE_URL/users/me/user-name'), // 프로필 정보를 가져오는 엔드포인트 headers: { 'Authorization': 'Bearer $_token', 'Content-Type': 'application/json', }, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); setState(() { profileName = data['name']; // 서버에서 받아온 이름으로 설정 }); } } catch (e) { print('Error fetching profile name: $e'); } } @override Widget build(BuildContext context) { return SingleChildScrollView( child: Container( color: Colors.white, child: Column( children: [ Container( margin: const EdgeInsets.only(top: 40.0), child: Padding( padding: const EdgeInsets.all(20.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ CircleAvatar( radius: 15, backgroundColor: Colors.grey, backgroundImage: profileImageUrl != null ? NetworkImage(profileImageUrl!) : null, ), const SizedBox(width: 8), Text( profileName ?? 'Loading...', // 프로필 이름이 로드되지 않았을 때는 'Loading...' 표시 style: const TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: Colors.black, decoration: TextDecoration.none, ), ), ], ), Column( children: [ IconButton( onPressed: () { Navigator.of(context).push( MaterialPageRoute( builder: (context) => const HeroQuestMainScreen(), ), ); }, icon: const Icon(Icons.stars), color: Colors.black, ), IconButton( onPressed: () { Navigator.of(context).push( MaterialPageRoute( builder: (context) => const DailyQuestMainScreen(), ), ); }, icon: const Icon(Icons.calendar_today), color: Colors.black, ), IconButton( onPressed: () { Navigator.of(context).push( MaterialPageRoute( builder: (context) => const StoreMainScreen(), ), ); }, icon: const Icon(Icons.store), color: Colors.black, ), ], ), ], ), ), ), // 캐릭터 이미지 영역 Container( height: 200, width: double.infinity, margin: const EdgeInsets.symmetric(vertical: 20), child: const Center( child: Text( '게으른 강아지양', style: TextStyle( fontSize: 15, fontWeight: FontWeight.normal, color: Colors.black, decoration: TextDecoration.none, ), ), ), ), // 진행 상태 바 Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '궁극의 경지', style: TextStyle( fontSize: 30, fontWeight: FontWeight.bold, color: Colors.black, decoration: TextDecoration.none, ), ), const SizedBox(height: 8), LinearProgressIndicator( value: 0.7, backgroundColor: Colors.grey[200], valueColor: const AlwaysStoppedAnimation<Color>(Colors.blue), ), ], ), ), // 그래프 영역 Container( height: 200, margin: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(8), ), ), // 텍스트 영역 Container( height: 200, margin: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(8), ), ), // 하단 버튼들 Container( height: 200, margin: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(8), ), ), const SizedBox(height: 32), // 하단 여백 ], ), ), ); } Widget _buildBar(double height, {bool isHighlighted = false}) { return Container( width: 30, height: 150 * height, decoration: BoxDecoration( color: isHighlighted ? Colors.blue : Colors.grey[300], borderRadius: BorderRadius.circular(4), ), ); } Widget _buildButton(String text, Color color) { return Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 16), decoration: BoxDecoration( border: Border.all(color: color), borderRadius: BorderRadius.circular(8), ), child: Center( child: Text( text, style: TextStyle(color: color, fontWeight: FontWeight.bold), ), ), ); } }