COVID-19๋ก ์์ถ๋์๋ ์คํฌ์ธ ํ๋์ด ๋ค์ ํ์ฑํ ๋๋ฉฐ, ๋ง์ ์ฒด์ก๊ด๋ค์ด ๋ค์ ์ด์๋๊ณ ์์ต๋๋ค.
์ด๋ฌํ ์์ฆ, ์คํฌ์ธ ํ๋์ ์๋กญ๊ฒ ์์ํ๊ธฐ์ ์์, ์ ๋ณด๊ฐ ๋ถ์กฑํ์ฌ ๋ถํธํ์ง ์์ผ์ จ๋์?
์์น๊ธฐ๋ฐ ์ฒด์ก๊ด ๊ฒ์๋ถํฐ ์์ฝ, ๋์ฌ์ฉํ ์ ํ ๊ทธ๋ฆฌ๊ณ ์ ํธํ๋ ์ฒด์ก๊ด ์ฐํ๊ธฐ๊น์ง ํ ๋ฒ์ ์ด์ฉํด ๋ณด์ธ์!
![]() |
![]() |
![]() |
![]() |
![]() |
---|---|---|---|---|
Front-End | Front-End | Back-End | Back-End | Back-End |
๊ถํ๋ฏผ | ์ค์ฌ์ | ๐์ฐจ๋์ค๐ | ๋ฐํด์ฐฌ | ์ด์์ |
๐ ์์ ๋ก๊ทธ์ธ(์นด์นด์ค, ๋ค์ด๋ฒ, ๊ตฌ๊ธ) | ๐ ์ฃผ๋ณ ์ฒด์ก๊ด ์ฐพ๊ธฐ |
---|---|
![]() |
![]() |
๐ต ์ฒด์ก๊ด ์์ฝ ๋ฐ ๊ฒฐ์ | ๐ ๋ฆฌ๋ทฐ ์์ฑ / ์์ |
---|---|
![]() |
![]() |
๐ ์ฐํ๊ธฐ | ๐ ์๋ฆผ ๋ด์ญ |
---|---|
![]() |
![]() |
๐ง๐ปโ๐ป ํ๋งค์ ํ์๊ฐ์ | ๐ ํ๋งค์ ์ผ๋ฐ ๋ก๊ทธ์ธ |
---|---|
![]() |
![]() |
๐๐ฟ ์ฒด์ก๊ด ๋ฑ๋ก / ์์ | โ ์์ฝ ํํฉ |
---|---|
![]() |
![]() |
-
์ฝ๋ ์ปจ๋ฒค์
- ํ์ ๋ฐ ๋ถ์ ์ ์ํํ๊ฒ ํ๊ธฐ ์ํด ๊ฐ๋ฐ ์ย ํต์ผ์ฑ์ ๋ถ์ฌํ๊ณ ์ ๋ง์ด ๊ณ ๋ฏผํ์ด์.
- TypeScript, Prettierย ๋๋ถ์ ๋ฒ๊ทธ๋ฅผ ์๋ฐฉํ๊ณ ํ์ ์์ฐ์ฑ์ ๋์ผ ์ ์์์ด์.
Button
Label
Input
Title
๊ณผ ๊ฐ์ ์ฌ ์ฌ์ฉ์ฑ์ด ์๊ตฌ๋๋ UI ์์๋ Atom ๋จ์๋ก ์ค๊ณํ์ฌ ์์ฐ์ฑ์ ๋์ผ ์ ์์์ด์- Type์ ํ์ฅ์ด ์ฉ์ดํ๋๋ก BaseType์ ์ ์ธํด ์ค๋ณต๋๋ Property๋ฅผ ์ค์์ด์.
- ๋๋ถ์ 200์ค์ Type ์ฝ๋๊ฐ 60์ค๋ก ์ค์ด ๋ค ์ ์์์ด์.
- ๊ทธ ์ธ ํต์ผํด์ผ ํ ๋ถ๋ถ์ ๋ฐ๊ฒฌํ๋ฉด ์ฆ์ ํจ๊ป ๊ณ ๋ฏผํ๊ณ ์คํํ์ด์.
-
๊ธฐ์
- RTK ๋ฅผ ์ฌ์ฉํ์ฌ Client ์ํ๋ฅผ ๊ด๋ฆฌํ์ด์.
- RTK Query๋ฅผ ํ์ฉํ์ฌ Server ์ํ๋ฅผ ๊ด๋ฆฌํ์์ผ๋ฉฐ, Caching์ ํ์ฉํ์ฌ ํต์ ๋น์ฉ์ ์ค์ผ ์ ์์์ด์.
- ๋๋ถ์ ์๋ต ๋ค์ ์์ ์ด๋ ์๋ฌ ๋ฐ์ ์์๋ ํต์ผ๋ ์์ ์ ์ํํ ์ ์์์ด์.
- Emotion์ ํ์ฉํ ์คํ์ผ๋ง ์์ ์์ ๊ธ๋ก๋ฒ ์คํ์ผ ์ ์ฉ๊ณผ Typo, Palette๋ก ์ ์ธํ ๋ณ์๋ฅผ ์ด์ฉํ๋๋ก ํ์ํ์ฌ ํต์ผ์ฑ์ ๋ถ์ฌํ์ด์.
๋ณด๋ค ๋น ๋ฅธ ๊ฒ์ ๊ธฐ๋ฅ์ ์ ๊ณตํ๊ธฐ ์ํด ์ฃผ๋ณ ์ฒด์ก๊ด ๊ฒ์์ ElasticSearch๋ฅผ ์ ์ฉํ์์ต๋๋ค.
- RDMS์์ Like ๊ฒ์ ๋ฐ Match ๋ณด๋ค ๋น ๋ฅธ ์๋๋ก ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ๋ฐ์ดํฐ ๊ณต๊ฐ์ ์ ์ฝํ ์ ์์ผ๋ฉฐ, ์ปฌ๋ผ์ ๋์ ์ผ๋ก ์ ์ํ์ฌ ํ์ํ ๋ฐ์ดํฐ๋ง ๋ฃ๊ฒ ๋์ด, ๋ฐ์ดํฐ ๊ณต๊ฐ ๋ฐ CPU ์ฌ์ฉ๋์ ์ ์ฝํ ์ ์์ต๋๋ค.
- ES๋ HTTP๋ฅผ ํตํด
JSON
ํ์์ RESTful API๋ก ํธ์ถํ๊ธฐ ๋๋ฌธ์ ์ฌ๋ฌ ํ๊ฒฝ์์ ์ ์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค.
- ๊ตฌ๊ธ, ๋ค์ด๋ฒ, ์นด์นด์ค์์ ์ ๊ณตํ๋ Authorization Server๋ฅผ ํตํด ํ์ ์ ๋ณด๋ฅผ ์ธ์ฆํ๊ณ
Access Token
์ ๋ฐ๊ธ ๋ฐ์ต๋๋ค. - ์๋ฒ ๊ฐ์ ํต์ ์ด ์ฆ์ ๊ฒฝ์ฐ,
Access Token
์ ์์ฃผ ์ฃผ๊ณ ๋ฐ์ ์ ๋ฐ์ ์๊ณ , ํ ํฐ์ด ์ ํจํ์ง ํ์ธํด ์ฃผ์ด์ผ ํฉ๋๋ค. - ํด๋น ๊ณผ์ ์์ Auth ์๋ฒ์ ์ ํจ์ฑ ๊ฒ์ฆ ํ์ธ์ ์ํด ์์ฒญํ ๋๋ง๋ค ๋ณ๋ชฉ ํ์์ผ๋ก ์ธํด ์๋ฒ์ ๋ถํ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
- Claim ๊ธฐ๋ฐ ๋ฐฉ์์ธ
JWT
๋ฅผ ํตํด Auth ์๋ฒ์ ๊ฒ์ฆ ์์ฒญ์ ๋ณด๋ด์ผํ๋ ๊ณผ์ ์ ์๋ตํ๊ณ , ๊ฐ ์๋ฒ์์ API ์์ฒญ์ด ๋ค์ด์ค๋ฉด Auth ์๋ฒ๊ฐ ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์๋ฒ์์ ํ ํฐ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํตํด ์ฌ์ฉ์ ์ธ์ฆ์ ๊ฑฐ์น๋๋ก ์ค์ ํ์์ต๋๋ค.
-
Problem & Reason
useCallback, useEffect
๋ฅผ ํจ๊ป ์ฌ์ฉํ์ฌ ์ฝ๋์ ๊ฐ๋ ์ฑ์ด ๋จ์ด์ง๋ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค. ๋ํ, ๋ค๋ฅธ ์ปดํฌ๋ํธ์์ Kakao Map ๊ธฐ๋ฅ์ ์ฌ์ฉ ํ ๋, ๋ค์ map ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ฃผ์ด์ผ ํ๋ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค.- Kakao Map(Function)
// ์ปดํฌ๋ํธ.tsx const Map = ({ searchResults, onClickMarker }: MapProps) => { const kakaoMap = useKakaoMapScript(); setMarker({ map: kakaoMap, placeInfo: searchResults, clickHandle: onClickMarker }); return ( <div> <div id="myMap" style={{ width: '100vw', height: '100vh', height: 'calc(100vh - 5rem)', }} ></div> </div> ) } // kakaoScript.ts const { kakao } = window; const useKakaoMapScript = (markerData: any) => { const [kakaoMap, setKakaoMap] = useState(); useEffect(() => { const container = document.getElementById('myMap'); const options = { center: new kakao.maps.LatLng(37.62197524055062, 127.1583774403176), level: 4, }; const map = new kakao.maps.Map(container, options); markerData.forEach((el: any) => { // ๋ง์ปค๋ฅผ ์์ฑํฉ๋๋ค const markers = new kakao.maps.Marker({ //๋ง์ปค๊ฐ ํ์ ๋ ์ง๋ map: map, //๋ง์ปค๊ฐ ํ์ ๋ ์์น position: new kakao.maps.LatLng(el.lat, el.lng), //๋ง์ปค์ hover์ ๋ํ๋ title title: el.title, }); kakao.maps.event.addListener(markers, 'click', function () { console.log(el); }); }); setKakaoMap(map); }, [markerData]); return kakaoMap; }; export const mapPanTo = (map: any, location: any) => { const moveLatLon = new kakao.maps.LatLng(33.45058, 126.574942); map.panTo(moveLatLon); }; export default useKakaoMapScript;
-
To Solve
- ๊ธฐ์กด ํจ์ํ์ผ๋ก ์์ฑ๋๋ KaKao Map Script๋ฅผ Class ๋ฌธ๋ฒ์ผ๋ก ๋ณ๊ฒฝ ํ์ต๋๋ค.
- ์ด๋ก ์ธํ์ฌ ์ฌ์ฌ์ฉ์ฑ์ด ๋ ํธ๋ฆฌํด ์ก์ผ๋ฉฐ ์ฝ๋์ ๋ชฉ์ ์ฑ ๋ํ ๋ช ํํด์ก๊ณ , ์ฝ๋์ ๊ฐ๋ ์ฑ์ด ์ฌ๋ผ๊ฐ์ต๋๋ค.
- ๋ชจ๋ ๋ก์ง์ ๋ฌด๋ถ๋ณํ๊ฒ ํจ์ํ์ผ๋ก ์ถ์ํ ํ๋ ๊ฒ์ ์ง์ํ๊ณ , ์ฝ๋์ ๋ชฉ์ ์ ๋ฐ๋ผ ๋ค์ํ ๋ฐฉ๋ฒ์ผ๋ก ์ถ์ํ ํด์ผ ํ๋ค๊ณ ๋๊ผ์ต๋๋ค.
- Kakao Map (Class)
// ์ปดํฌ๋ํธ.tsx const Map = ({ searchResults, onClickMarker }: MapProps) => { useEffect(() => { kakaoService.initScript(); }, []); useEffect(() => { kakaoService.setMarker({ place: searchResults, handleClick: onClickMarker }); }, [searchResults]); return ( <div> <div id="myMap" style={{ width: '100vw', height: 'calc(100vh - 5rem)', }} ></div> </div> ); }; const StadiumSearch = () => { const handleEnterFetch = (e) => { if (e.key === 'Enter') { searchStadium(search); kakaoService.setClearMarker(); } } } const EditAddress = () => { const handleSelectAdress = async (data: Address) => { // ์ฃผ์ string -> ์๋ ๊ฒฝ๋ ๋ณํ const geoLocation = await kakaoService.getGeoCode(data.address); }; } // kakaoScript.ts const { kakao } = window; class KaKaoMap { map: any = null; markers: any[] = []; initScript() { const container = document.getElementById('myMap'); const options = { center: new kakao.maps.LatLng(ZERO_LOCATION.lat, ZERO_LOCATION.lnt), level: 10, }; const map = new kakao.maps.Map(container, options); const zoomControl = new kakao.maps.ZoomControl(); map.addControl(zoomControl, kakao.maps.ControlPosition.RIGHT); this.map = map; } getGeoCode(address: string) { const geocoder = new kakao.maps.services.Geocoder(); return new Promise((resolve, reject) => { geocoder.addressSearch(address, function (result: any, status: any) { if (status === kakao.maps.services.Status.OK) { resolve({ lat: result[0].y, lnt: result[0].x }); } else { reject(status); } }); }); } goToLocation(location: PanToParam) { if (!this.map) return; const moveLatLon = new kakao.maps.LatLng(location.lat, location.lnt); this.map.panTo(moveLatLon); } setMarker({ place, handleClick }: setMarkerParam) { if (!place) return; place.forEach((el: any) => { // ๋ง์ปค๋ฅผ ์์ฑํฉ๋๋ค const marker = new kakao.maps.Marker({ //๋ง์ปค๊ฐ ํ์ ๋ ์ง๋ map: this.map, //๋ง์ปค๊ฐ ํ์ ๋ ์์น position: new kakao.maps.LatLng(el.lat, el.lnt), //๋ง์ปค์ hover์ ๋ํ๋ title title: el.title, }); kakao.maps.event.addListener(marker, 'click', () => { handleClick(el); this.map.setLevel(8); this.goToLocation({ lat: el.lat, lnt: el.lnt }); }); this.markers.push(marker); }); this.goToLocation({ lat: place[0].lat, lnt: place[0].lnt }); } setClearMarker() { this.markers.forEach(marker => { marker.setMap(null); }); } zoomIn() { // ํ์ฌ ์ง๋์ ๋ ๋ฒจ์ ์ป์ด์ต๋๋ค const level = this.map.getLevel(); // ์ง๋๋ฅผ 1๋ ๋ฒจ ๋ด๋ฆฝ๋๋ค (์ง๋๊ฐ ํ๋๋ฉ๋๋ค) this.map.setLevel(level - 1); } zoomOut() { const level = this.map.getLevel(); // ์ง๋๋ฅผ 1๋ ๋ฒจ ์ฌ๋ฆฝ๋๋ค (์ง๋๊ฐ ์ถ์๋ฉ๋๋ค) this.map.setLevel(level + 1); } } const kakaoService = new KaKaoMap(); export default kakaoService;
-
Problem & Reason
- Image
onchange
Event ํธ์ถ ์ Cloudinary ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด ์๋ต ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์ ์ฅํฉ๋๋ค.- ์ด ๊ฒฝ์ฐ, ์ฌ์ฉ์๊ฐ
onChange
์ ๋ง๋ค ์์ฒญ์ ๋ณด๋ด๋ฏ๋ก, Request Cost๊ฐ ๋์์ง๋๋ค. ๋ํ, Cloudinary ์๋ฒ๋ ์์ฒญ ํ์ 1,000๋ฒ์ ๋์ผ๋ฉด ๊ณผ๊ธ์ด ๋ถ๊ฐ ๋๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.
- ์ด ๊ฒฝ์ฐ, ์ฌ์ฉ์๊ฐ
submit
Event ํธ์ถ ์ Cloudinary ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด ์๋ต ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์จ ํ Submit ๋ก์ง์ ์คํ ํฉ๋๋ค.- ์ด ๊ฒฝ์ฐ, ์์ฒญ ํ์๋ ํ ๋ฒ์ผ๋ก Request Cost๋ ๋ฎ์ง๋ง, ์์ฒญ ์์ ์ด ๋์ผํ๋ฉฐCloudinary Server ์๋ต์ ๊ธฐ๋ค๋ ค์ผ ํ๋ฏ๋ก ์ฌ์ฉ์ ๊ฒฝํ์ด ๋๋น ์ง๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.
- Image
-
To Solve : ๊ณผ๊ธ์ ๋ํ ๋ฌธ์ ๋ฅผ ์ค์ด๊ธฐ ์ํด
submit
Event๋ก ํด๊ฒฐ ํ์ต๋๋ค. -
Etc : โ๋น์ฉ ๋ฌธ์ ๊ฐ ์๋คโ ๋ผ๊ณ ํ๋จ๋๋ค๋ฉด, Image
onChange
Event ์ Upload๋ฅผ ํ์ฌ, Request ์์ ์ ๋๋์ด ์ฌ์ฉ์ ๊ฒฝํ์ ์ฆ๊ฐ์ํฌ ์ ์๋ค๊ณ ์๊ฐํฉ๋๋ค. ์ถ๊ฐ๋ก, ๋ณ๊ฒฝ ์ด์ ์ Image์Delete
์์ฒญ์ ํ๊ฒ ๋๋ค๋ฉด, ํจ์จ์ ์ผ๋ก Image๋ฅผ ๊ด๋ฆฌํ ์ ์๋ ๋ฐฉ๋ฒ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค.
-
Problem
@Override @Cacheable(value = CacheKey.USER, key = "#email") public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { Member member = memberRepository.findByEmail(email).orElseThrow(() -> new AuthException(MemberNotFound)); return PrincipalDetail.of(member); }
- UserDetails๋ฅผ
implement
ํ PrincipalDetail class๋ฅผ serialize(์บ์ ์์ฑ)ํ๋ ๊ฒ์ ์ฑ๊ณตํ์ง๋ง, deserialize(์บ์ ๋ถ๋ฌ์ค๊ธฐ)์์ ๊ณ์ parsing ์๋ฌ๊ฐ ๋ฐ์
SerializationException: Could not read JSON:cannot deserialize from Object value
- UserDetails๋ฅผ
-
Reason
- Userdetails interface์ Override ๋ฉ์๋๊ฐ ํ๋์ ๋ณ์ํํ๋ก
json
ํ์ผ์ ์ ์ฅ๋๊ธฐ ๋๋ฌธ์ Deserializeํ ๋ ํด๋น ๋ณ์๋ค์ Override ๋ฉ์๋๋ก ๋ณ๊ฒฝํ ์ ์์ด parsing error๊ฐ ๋ฐ์ํ์๋ค.
- Userdetails interface์ Override ๋ฉ์๋๊ฐ ํ๋์ ๋ณ์ํํ๋ก
-
To Solve
@JsonIgnore
์ด๋ ธํ ์ด์ ์ ํตํด Override ๋ฉ์๋๋ค์ ์ ์ธํ๊ณjson
ํ์ผ๋ก ์ ์ฅํ์๋ค.- Before
{ "@class": "com.minwonhaeso.esc.security.auth.PrincipalDetails", "member": { "@class": "com.minwonhaeso.esc.member.model.entity.Member", "memberId": 1, "email": "[email protected]", "name": "ํด์ฐฌ", "password": "$2a$10$O4967ICeXCld8U2KRGV3GOn7MyS/dbnxloeqssp2.Q2A3GgSm2//2", "role": "ROLE_USER", "imgUrl": null, "nickname": null, "type": "USER", "status": "ING", "providerType": "LOCAL", "providerId": "gocks0918" }, "attributes": null, "password": "$2a$10$O4967ICeXCld8U2KRGV3GOn7MyS/dbnxloeqssp2.Q2A3GgSm2//2", "name": null, "enabled": true, "authorities": [ "java.util.Collections$SingletonSet", [ { "@class": "org.springframework.security.core.authority.SimpleGrantedAuthority", "authority": "ROLE_USER" } ] ], "username": "[email protected]", "accountNonExpired": true, "accountNonLocked": true, "credentialsNonExpired": true }
- After
{ "@class": "com.minwonhaeso.esc.security.auth.PrincipalDetail", "username": "[email protected]", "password": "$2a$10$Vw77fNcTVYVp2/OaPJ8ZZOUCyiYWP/hhw25jTUCq2EAnDxL4k.R8e", "member": { "@class": "com.minwonhaeso.esc.member.model.entity.Member", "memberId": 1, "email": "[email protected]", "name": "ํด์ฐฌ", "password": "$2a$10$Vw77fNcTVYVp2/OaPJ8ZZOUCyiYWP/hhw25jTUCq2EAnDxL4k.R8e", "role": "ROLE_USER", "imgUrl": null, "nickname": null, "type": "USER", "status": "ING", "providerType": "LOCAL", "providerId": "gocks0918" }, "attributes": null }
-
Problem
- ํ๋ก ํธ ์๋ฒ๊ฐ ๋ฐฐํฌ๋ CloudFront์์ ์๋ฒ์ HTTPS๋ก ์์ฒญ์ ๋ณด๋์ ๋ Connection Refused ํ์ ๋ฐ์
-
Reason
- EC2์๋ SSL ์ธ์ฆ ์ฒ๋ฆฌ๊ฐ ๋์ด์์ง ์์์ HTTP๋ง ๋ฐ๊ณ HTTPS๋ฅผ ๊ฑฐ๋ถ
-
To Solve
- ๋ก๋๋ฐธ๋ฐ์๋ฅผ ์ด์ฉํ์ฌ
HTTPS(443)
์์ฒญ์HTTP(80)
์ผ๋ก ๋ฆฌ๋ค์ด๋ ํธ ํ๋๋ก ์ค์ BUT - ์ด ๊ณผ์ ์์ SSL/TLS ์ธ์ฆ์๊ฐ ํ์ํ์ฌ ACM์์ ์ธ์ฆ์๋ฅผ ๋ฐ๊ธ BUT
- ์ธ์ฆ์ ๋ฐ๊ธ์ ์ํด ๋๋ฉ์ธ์ด ํ์ํ์ฌ ๋๋ฉ์ธ ๊ตฌ์
ํ ์ด๋ฅผ EC2 ํน์ EC2์ ์ฐ๊ฒฐ๋ ๋ก๋๋ฐธ๋ฐ์์ ์ฐ๊ฒฐ
- ๊ฐ๋น์(๋๋ฉ์ธ ๋ฑ๋ก ์ฌ์ดํธ)์์ esc-zero-server.shop ๋๋ฉ์ธ์ ๊ตฌ๋งค
- Route 53์ ๋๋ฉ์ธ์ ๋ฑ๋กํ๊ณ ๋ก๋๋ฐธ๋ฐ์ ๋ฐ EC2(IP์ฃผ์)์ ์ฐ๊ฒฐ
- ๋ก๋๋ฐธ๋ฐ์๋ฅผ ์ด์ฉํ์ฌ