Skip to content
@MinWonHaeSo

MinWonHaeSo

โšฝ Easy Sports Club โšฝ


๐Ÿ“Ž ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ

COVID-19๋กœ ์œ„์ถ•๋˜์—ˆ๋˜ ์Šคํฌ์ธ  ํ™œ๋™์ด ๋‹ค์‹œ ํ™œ์„ฑํ™” ๋˜๋ฉฐ, ๋งŽ์€ ์ฒด์œก๊ด€๋“ค์ด ๋‹ค์‹œ ์šด์˜๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์š”์ฆ˜, ์Šคํฌ์ธ  ํ™œ๋™์„ ์ƒˆ๋กญ๊ฒŒ ์‹œ์ž‘ํ•˜๊ธฐ์— ์•ž์„œ, ์ •๋ณด๊ฐ€ ๋ถ€์กฑํ•˜์—ฌ ๋ถˆํŽธํ•˜์ง„ ์•Š์œผ์…จ๋‚˜์š”?

์œ„์น˜๊ธฐ๋ฐ˜ ์ฒด์œก๊ด€ ๊ฒ€์ƒ‰๋ถ€ํ„ฐ ์˜ˆ์•ฝ, ๋Œ€์—ฌ์šฉํ’ˆ ์„ ํƒ ๊ทธ๋ฆฌ๊ณ  ์„ ํ˜ธํ•˜๋Š” ์ฒด์œก๊ด€ ์ฐœํ•˜๊ธฐ๊นŒ์ง€ ํ•œ ๋ฒˆ์— ์ด์šฉํ•ด ๋ณด์„ธ์š”!



๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘ ํŒ€์› ์†Œ๊ฐœ

Front-End Front-End Back-End Back-End Back-End
๊ถŒํ˜๋ฏผ ์œค์žฌ์› ๐Ÿ‘‘์ฐจ๋™์ค€๐Ÿ‘‘ ๋ฐ•ํ•ด์ฐฌ ์ด์†Œ์•„



๐Ÿ—‚๏ธ Architecture

ESC Structure drawio

๐Ÿ’พ ERD ๊ตฌ์กฐ

db

๐Ÿ› ๏ธ ๊ธฐ์ˆ  ์Šคํƒ

Front-End

Back-End






Production & Deploy


actions

Collaboration tool


๐Ÿ“ฑDemo

๐Ÿš€ ์†Œ์…œ ๋กœ๊ทธ์ธ(์นด์นด์˜ค, ๋„ค์ด๋ฒ„, ๊ตฌ๊ธ€) ๐Ÿ” ์ฃผ๋ณ€ ์ฒด์œก๊ด€ ์ฐพ๊ธฐ
์†Œ์…œ ๋กœ๊ทธ์ธ ์ฃผ๋ณ€ ์ฒด์œก๊ด€ ์ฐพ๊ธฐ
๐Ÿ’ต ์ฒด์œก๊ด€ ์˜ˆ์•ฝ ๋ฐ ๊ฒฐ์ œ ๐Ÿ“ ๋ฆฌ๋ทฐ ์ž‘์„ฑ / ์ˆ˜์ •
์ฒด์œก๊ด€ ์˜ˆ์•ฝ ๋ฐ ๊ฒฐ์ œ ๋ฆฌ๋ทฐ ์ž‘์„ฑ
๐Ÿ”– ์ฐœํ•˜๊ธฐ ๐Ÿ”” ์•Œ๋ฆผ ๋‚ด์—ญ
์ฐœํ•˜๊ธฐ ์•Œ๋ฆผ ๋‚ด์—ญ
๐Ÿง‘๐Ÿปโ€๐Ÿ’ป ํŒ๋งค์ž ํšŒ์›๊ฐ€์ž… ๐Ÿš€ ํŒ๋งค์ž ์ผ๋ฐ˜ ๋กœ๊ทธ์ธ
ํŒ๋งค์ž ํšŒ์›๊ฐ€์ž… ํŒ๋งค์ž ์ผ๋ฐ˜ ๋กœ๊ทธ์ธ
๐Ÿ‹๐Ÿฟ ์ฒด์œก๊ด€ ๋“ฑ๋ก / ์ˆ˜์ • โœ… ์˜ˆ์•ฝ ํ˜„ํ™ฉ
์œ ์ €_๋งž์ถค_์ˆ _์ถ”์ฒœ ๊ด€๋ฆฌ์ž ์ฒด์œก๊ด€ ์˜ˆ์•ฝ ํ˜„ํ™ฉ

๐Ÿ‘ ํŠน์žฅ์  ๊ธฐ์ˆ 

โœจย ํ”„๋ก ํŠธ์—”๋“œ ์ฝ”๋“œ ํ†ต์ผ์„ฑ์— ๋Œ€ํ•œ ์ง€์†์ ์ธ ๊ณ ๋ฏผ

  • ์ฝ”๋“œ ์ปจ๋ฒค์…˜

    • ํ˜‘์—… ๋ฐ ๋ถ„์—…์„ ์›ํ™œํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ๊ฐœ๋ฐœ ์‹œย ํ†ต์ผ์„ฑ์„ ๋ถ€์—ฌํ•˜๊ณ ์ž ๋งŽ์ด ๊ณ ๋ฏผํ–ˆ์–ด์š”.
    • TypeScript, Prettierย ๋•๋ถ„์— ๋ฒ„๊ทธ๋ฅผ ์˜ˆ๋ฐฉํ•˜๊ณ  ํ˜‘์—… ์ƒ์‚ฐ์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์—ˆ์–ด์š”.
    • Button Label Input Title๊ณผ ๊ฐ™์€ ์žฌ ์‚ฌ์šฉ์„ฑ์ด ์š”๊ตฌ๋˜๋Š” UI ์š”์†Œ๋Š” Atom ๋‹จ์œ„๋กœ ์„ค๊ณ„ํ•˜์—ฌ ์ƒ์‚ฐ์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์—ˆ์–ด์š”
    • Type์€ ํ™•์žฅ์ด ์šฉ์ดํ•˜๋„๋ก BaseType์„ ์„ ์–ธํ•ด ์ค‘๋ณต๋˜๋Š” Property๋ฅผ ์ค„์˜€์–ด์š”.
    • ๋•๋ถ„์— 200์ค„์˜ Type ์ฝ”๋“œ๊ฐ€ 60์ค„๋กœ ์ค„์–ด ๋“ค ์ˆ˜ ์žˆ์—ˆ์–ด์š”.
    • ๊ทธ ์™ธ ํ†ต์ผํ•ด์•ผ ํ•  ๋ถ€๋ถ„์„ ๋ฐœ๊ฒฌํ•˜๋ฉด ์ฆ‰์‹œ ํ•จ๊ป˜ ๊ณ ๋ฏผํ•˜๊ณ  ์‹คํ–‰ํ–ˆ์–ด์š”.
  • ๊ธฐ์ˆ 

    • RTK ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Client ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ–ˆ์–ด์š”.
    • RTK Query๋ฅผ ํ™œ์šฉํ•˜์—ฌ Server ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜์˜€์œผ๋ฉฐ, Caching์„ ํ™œ์šฉํ•˜์—ฌ ํ†ต์‹  ๋น„์šฉ์„ ์ค„์ผ ์ˆ˜ ์žˆ์—ˆ์–ด์š”.
    • ๋•๋ถ„์— ์‘๋‹ต ๋‹ค์Œ ์ž‘์—…์ด๋‚˜ ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ์—๋„ ํ†ต์ผ๋œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์—ˆ์–ด์š”.
    • Emotion์„ ํ™œ์šฉํ•œ ์Šคํƒ€์ผ๋ง ์ž‘์—… ์‹œ์— ๊ธ€๋กœ๋ฒŒ ์Šคํƒ€์ผ ์ ์šฉ๊ณผ Typo, Palette๋กœ ์„ ์–ธํ•œ ๋ณ€์ˆ˜๋ฅผ ์ด์šฉํ•˜๋„๋ก ํ˜‘์˜ํ•˜์—ฌ ํ†ต์ผ์„ฑ์„ ๋ถ€์—ฌํ–ˆ์–ด์š”.

โœจย ElasticSearch๋ฅผ ํ™œ์šฉํ•œ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ

๋ณด๋‹ค ๋น ๋ฅธ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด ์ฃผ๋ณ€ ์ฒด์œก๊ด€ ๊ฒ€์ƒ‰์— ElasticSearch๋ฅผ ์ ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

  • RDMS์—์„œ Like ๊ฒ€์ƒ‰ ๋ฐ Match ๋ณด๋‹ค ๋น ๋ฅธ ์†๋„๋กœ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฐ์ดํ„ฐ ๊ณต๊ฐ„์„ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ปฌ๋Ÿผ์„ ๋™์ ์œผ๋กœ ์ •์˜ํ•˜์—ฌ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ๋„ฃ๊ฒŒ ๋˜์–ด, ๋ฐ์ดํ„ฐ ๊ณต๊ฐ„ ๋ฐ CPU ์‚ฌ์šฉ๋Ÿ‰์„ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ES๋Š” HTTP๋ฅผ ํ†ตํ•ด JSON ํ˜•์‹์˜ RESTful API๋กœ ํ˜ธ์ถœํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์—ฌ๋Ÿฌ ํ™˜๊ฒฝ์—์„œ ์ ์šฉ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

โœจย JWT์™€ OAuth2๋ฅผ ํ™œ์šฉํ•œ ์†Œ์…œ ๋กœ๊ทธ์ธ

  • ๊ตฌ๊ธ€, ๋„ค์ด๋ฒ„, ์นด์นด์˜ค์—์„œ ์ œ๊ณตํ•˜๋Š” Authorization Server๋ฅผ ํ†ตํ•ด ํšŒ์› ์ •๋ณด๋ฅผ ์ธ์ฆํ•˜๊ณ  Access Token์„ ๋ฐœ๊ธ‰ ๋ฐ›์Šต๋‹ˆ๋‹ค.
  • ์„œ๋ฒ„ ๊ฐ„์˜ ํ†ต์‹ ์ด ์žฆ์€ ๊ฒฝ์šฐ, Access Token์„ ์ž์ฃผ ์ฃผ๊ณ  ๋ฐ›์„ ์ˆ˜ ๋ฐ–์— ์—†๊ณ , ํ† ํฐ์ด ์œ ํšจํ•œ์ง€ ํ™•์ธํ•ด ์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ํ•ด๋‹น ๊ณผ์ •์—์„œ Auth ์„œ๋ฒ„์— ์œ ํšจ์„ฑ ๊ฒ€์ฆ ํ™•์ธ์„ ์œ„ํ•ด ์š”์ฒญํ•  ๋•Œ๋งˆ๋‹ค ๋ณ‘๋ชฉ ํ˜„์ƒ์œผ๋กœ ์ธํ•ด ์„œ๋ฒ„์˜ ๋ถ€ํ•˜๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Claim ๊ธฐ๋ฐ˜ ๋ฐฉ์‹์ธ JWT๋ฅผ ํ†ตํ•ด Auth ์„œ๋ฒ„์— ๊ฒ€์ฆ ์š”์ฒญ์„ ๋ณด๋‚ด์•ผํ–ˆ๋˜ ๊ณผ์ •์„ ์ƒ๋žตํ•˜๊ณ , ๊ฐ ์„œ๋ฒ„์—์„œ API ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด Auth ์„œ๋ฒ„๊ฐ€ ์•„๋‹Œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„์—์„œ ํ† ํฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž ์ธ์ฆ์„ ๊ฑฐ์น˜๋„๋ก ์„ค์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๐Ÿš€ ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…

๐Ÿง‘๐Ÿปโ€๐Ÿ’ป ํ”„๋ก ํŠธ์—”๋“œ

๐Ÿ›  Kakao Map SDK ๊ฐ€๋…์„ฑ์— ๋”ฐ๋ฅธ ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง

  • 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;

๐Ÿ›  Cloudinary Server ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์‹œ์  ๋น„์šฉ

  • Problem & Reason

    • Image onchange Event ํ˜ธ์ถœ ์‹œ Cloudinary ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ด ์‘๋‹ต ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
      • ์ด ๊ฒฝ์šฐ, ์‚ฌ์šฉ์ž๊ฐ€ onChange์‹œ ๋งˆ๋‹ค ์š”์ฒญ์„ ๋ณด๋‚ด๋ฏ€๋กœ, Request Cost๊ฐ€ ๋†’์•„์ง‘๋‹ˆ๋‹ค. ๋˜ํ•œ, Cloudinary ์„œ๋ฒ„๋Š” ์š”์ฒญ ํšŸ์ˆ˜ 1,000๋ฒˆ์„ ๋„˜์œผ๋ฉด ๊ณผ๊ธˆ์ด ๋ถ€๊ฐ€ ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
    • submit Event ํ˜ธ์ถœ ์‹œ Cloudinary ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ด ์‘๋‹ต ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ ์˜จ ํ›„ Submit ๋กœ์ง์„ ์‹คํ–‰ ํ•ฉ๋‹ˆ๋‹ค.
      • ์ด ๊ฒฝ์šฐ, ์š”์ฒญ ํšŸ์ˆ˜๋Š” ํ•œ ๋ฒˆ์œผ๋กœ Request Cost๋Š” ๋‚ฎ์ง€๋งŒ, ์š”์ฒญ ์‹œ์ ์ด ๋™์ผํ•˜๋ฉฐCloudinary Server ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ ค์•ผ ํ•˜๋ฏ€๋กœ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ๋‚˜๋น ์ง€๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
  • To Solve : ๊ณผ๊ธˆ์— ๋Œ€ํ•œ ๋ฌธ์ œ๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด submit Event๋กœ ํ•ด๊ฒฐ ํ–ˆ์Šต๋‹ˆ๋‹ค.

  • Etc : โ€˜๋น„์šฉ ๋ฌธ์ œ๊ฐ€ ์—†๋‹คโ€™ ๋ผ๊ณ  ํŒ๋‹จ๋œ๋‹ค๋ฉด, Image onChange Event ์‹œ Upload๋ฅผ ํ•˜์—ฌ, Request ์‹œ์ ์„ ๋‚˜๋ˆ„์–ด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ฆ๊ฐ€์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ์ถ”๊ฐ€๋กœ, ๋ณ€๊ฒฝ ์ด์ „์˜ Image์— Delete ์š”์ฒญ์„ ํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด, ํšจ์œจ์ ์œผ๋กœ Image๋ฅผ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.


๐Ÿง‘๐Ÿปโ€๐Ÿ’ป ๋ฐฑ์—”๋“œ

๐Ÿ› ย UserDetails Interface serializable

  • 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
  • Reason

    • Userdetails interface์˜ Override ๋ฉ”์†Œ๋“œ๊ฐ€ ํ•˜๋‚˜์˜ ๋ณ€์ˆ˜ํ˜•ํƒœ๋กœ json ํŒŒ์ผ์— ์ €์žฅ๋˜๊ธฐ ๋•Œ๋ฌธ์— Deserializeํ•  ๋•Œ ํ•ด๋‹น ๋ณ€์ˆ˜๋“ค์„ Override ๋ฉ”์†Œ๋“œ๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†์–ด parsing error๊ฐ€ ๋ฐœ์ƒํ•˜์˜€๋‹ค.
  • 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
    }

๐Ÿ› ย ๋ฐฐํฌ - HTTP/HTTPS ํ†ต์‹ 

  • 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์ฃผ์†Œ)์™€ ์—ฐ๊ฒฐ

Popular repositories Loading

  1. ESC_CLIENT ESC_CLIENT Public

    ESC Client Server

    TypeScript 1 1

  2. ESC_SERVER ESC_SERVER Public

    โšฝ Easy Sports Club Server โšฝ

    Java 3

  3. Stadium_Random_Generator Stadium_Random_Generator Public

    JavaScript

  4. .github .github Public

    1

Repositories

Showing 4 of 4 repositories
  • ESC_SERVER Public

    โšฝ Easy Sports Club Server โšฝ

    MinWonHaeSo/ESC_SERVERโ€™s past year of commit activity
    Java 0 MIT 3 5 0 Updated Sep 1, 2023
  • ESC_CLIENT Public

    ESC Client Server

    MinWonHaeSo/ESC_CLIENTโ€™s past year of commit activity
    TypeScript 1 MIT 1 0 0 Updated Jul 4, 2023
  • .github Public
    MinWonHaeSo/.githubโ€™s past year of commit activity
    0 1 0 0 Updated Feb 18, 2023
  • MinWonHaeSo/Stadium_Random_Generatorโ€™s past year of commit activity
    JavaScript 0 0 1 0 Updated Dec 20, 2022

Top languages

Loadingโ€ฆ

Most used topics

Loadingโ€ฆ