#12. React์—์„œ Drag & Drop์„ ์ด์šฉํ•œ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ

 

#12. React์—์„œ Drag & Drop์„ ์ด์šฉํ•œ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ

๋งํฌ์„œ๋น„์Šค ๋“ฑ๋กํ™”๋ฉด์—์„œ Input Value์„ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•˜๋‹ค. ๊ทธ๋Ÿผ ์ด๋ฏธ์ง€ ๋“ฑ๋ก์„ ์œ„ํ•œ Drag&Drop ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ๋ฅผ ์ œ์ž‘ํ•ด๋ณด์ž. #11. Reactํ™”๋ฉด์—์„œ ๊ฐ ํ•ญ๋ชฉ์˜ Input Value์„ ๊ฐ€์ ธ์˜ค๊ธฐ #11. Reactํ™”๋ฉด์—์„œ

firstvalue.tistory.com

 

React์—์„œ ๋“ฑ๋กํ™”๋ฉด์—์„œ ์ด๋ฏธ์ง€๋ฅผ Drag&Drop ํ• ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„์„ ํ–ˆ๋‹ค.

๊ทธ๋Ÿผ ์ด์   ๋“ฑ๋กํ™”๋ฉด์˜ ๋งˆ๋ฌด๋ฆฌ๋กœ ์ €์žฅ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด๋ณด์ž.

 

 

1.1.  ์ด๋ฏธ์ง€ ์ €์žฅ๊ธฐ๋Šฅ ์ œ์ž‘

์ด๋ฏธ์ง€ ์ €์žฅ๊ธฐ๋Šฅ(linkSevice.uploadImage) ์€ ์•„์ง API ์ œ์ž‘ ๋ฐ Service์„ ์–ธ์„ ํ•˜์ง€ ์•Š์•˜๋‹ค.

     โ˜ž ์„ค๋ช…์ฐจ์›์—์„œ ์„œ๋ฒ„๋‹จ์— API์ œ์ž‘ ์‹œ ์ œ์™ธ ํ–ˆ๋‹ค. ์•ž๋‹จ์—์„œ ๋ญ” ์ง€ ๋ชจ๋ฅธ ์ƒํƒœ์—์„œ ์†Œ์Šค๋ฅผ ๋ฏธ๋ฆฌ ์ œ์ž‘ํ•˜์ง€ ์•Š๊ณ ,

          ์ดํ•ด๋ฅผ ๋•๊ธฐ ์œ„ํ•ด ์ง€๊ธˆ ๋‚ด์šฉ์„ ๋ฐ˜์˜ํ–ˆ๋‹ค.

 

๋งํฌ์ •๋ณด๋ฅผ DB์— ์ €์žฅ ์‹œ ์šฐ๋ฆฌ๋Š” ๋ฏธ๋ฆฌ /api/link/create API ์„ ์‚ฌ์ „์— ์ œ์ž‘ํ–ˆ๋‹ค.

ํ•˜์ง€๋งŒ, ์‹ค์ œ ํ™”๋ฉด์—์„œ DB ์ €์žฅ ์‹œ 2๊ฐ€์ง€๊ฐ€ ๋” ์ถ”๊ฐ€๋˜์–ด์•ผ ํ•œ๋‹ค.

 

์ฒซ์งธ, ์ด๋ฏธ์ง€ ํŒŒ์ผ ์ž์ฒด๋ฅผ ์ €์žฅํ•˜๋Š” ๊ธฐ๋Šฅ

๋‘˜์งธ, ์ด๋ฏธ์ง€ ํŒŒ์ผ๋ช…(URL)์„ ๋งํฌ์ •๋ณด DB์— ์ €์žฅํ•˜๋Š” ๊ธฐ๋Šฅ

 

โŽŸโŽœ์ด๋ฏธ์ง€ ์ €์žฅ ๊ธฐ๋Šฅ

 

์ด๋ฏธ์ง€ ์—…๋กœ๋“œ๋Š” ๋ณดํ†ต DB์— ์ง์ ‘ ๋„ฃ๋Š” ๋ฐฉ๋ฒ•๊ณผ ์„œ๋ฒ„์— ์ด๋ฏธ์ง€๋ฅผ ๋ณด๊ด€ํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค. ์žฅ๋‹จ์ ์€ ์žˆ์œผ๋‚˜, ์ด๋ฏธ์ง€๋ฅผ DB์— ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์€ ํ–ฅํ›„ ์ด๋ฏธ์ง€ ํ˜ธ์ถœ ์‹œ ์†๋„์™€ DB์šฉ๋Ÿ‰๊ณผ ์œ ์ง€๋ณด์ˆ˜์— ๋ถˆํŽธํ•จ์ด ์žˆ์–ด, ๋ณ„๋„ ์„œ๋ฒ„์— ๋ณด๊ด€ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ง„ํ–‰ํ•œ๋‹ค.

 

๊ธฐ์—…์˜ ๊ฐœ๋ฐœ์€ CDN์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ๋ณ„๋„ ๋ณด๊ด€ ๋ฐฉ์‹์„ ์ถ”์ฒœํ•œ๋‹ค.

์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ์ œ์ž‘ํ•˜๋ฉด์„œ, ๋‹ค์‹œํ•œ๋ฒˆ ์„œ๋ฒ„๋‹จ๊ณผ ํด๋ผ์ด์–ธํŠธ๋‹จ์˜ API์ œ์ž‘ ๊ด€๋ จํ•ด์„œ ์ •๋ฆฌํ•ด๋ณด์ž.

 

1)   ์„œ๋ฒ„ RESTful API์ œ์ž‘

- ์ด๋ฏธ์ง€ ๋ณด๊ด€ ํด๋” ์ง€์ • ( config.py )

- ์ด๋ฏธ์ง€ ์—…๋กœ๋“œํ›„ ์ด๋ฏธ์ง€ ํŒŒ์ผ ์ด๋ฆ„๊ณผ URL์„ DB์— ๋ณด๊ด€

- ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ / ์‚ญ์ œ CLASS ์ œ์ž‘

- API ์„ ์–ธ

 

2) ํด๋ผ์ด์–ธํŠธ API ํ˜ธ์ถœ

   - API config์— ์„œ๋ฒ„๋‹จ API ํ˜ธ์ถœ url๊ด€๋ฆฌ ( api.config.js )

- ์„œ๋น„์Šค์— API ํ˜ธ์ถœ const ์ง€์ • ( service.js )

 

โŽŸโŽœ์„œ๋ฒ„๋‹จ API ์ œ์ž‘

 

โŒ˜ config.py ( ์œ„์น˜: linkservice/server/config/config.py )

--------------------------------------------------------------------------------------------------

์•ž์—์„œ config.py์— ๊ฐœ๋ฐœ๊ณ„(Development)์™€ ์šด์˜๊ณ„(Production) ์ด๋ฏธ์ง€ ์ €์žฅ ์œ„์น˜๋ฅผ ์ง€์ • ํ–ˆ์—ˆ๋‹ค.

#์ด๋ฏธ์ง€ ์œ„์น˜ ์„ค์ •
    image_folder = os.path.normpath(os.path.join(dir, os.pardir)) + '/client/public/images/'
    COVER_IMAGE_FORDER = image_folder
    IMAGE_FORDER = image_folder
    IMAGE_URL = 'http://127.0.0.1:3000/images/'

image_folder๋Š” ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ์˜ ๋ถ€๋ชจ ๋””๋ ‰ํ† ๋ฆฌ ( /linkservice) ์„ ํ™•์ธํ•ด์„œ /client/public/images ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๊ฐ€๋ฆฌํ‚จ๋‹ค.

( ํ•ด๋‹น ํด๋”์— images ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๋งŒ๋“ค์ž )

โ˜ž NAS๋‚˜ CDN ์ €์žฅ ์‹œ image_folder์— ์œ„์น˜๋ฅผ ์ง€์ •ํ•˜๋ฉด ๋œ๋‹ค.
โ˜ž COVER_IMAGE_FORDER
๋Š” ํ–ฅํ›„ ์ปค๋ฒ„ ์ด๋ฏธ์ง€๋ฅผ ์ถ”๊ฐ€/๋ณ€๊ฒฝ ํ•  ์˜ˆ์ •์ด๋ผ ๋ฏธ๋ฆฌ ์„ค์ •ํ–ˆ๋‹ค.

 

โŒ˜ links_model.py ( ์œ„์น˜: linkservice/server/models/links_model.py )

--------------------------------------------------------------------------------------------------

 

๊ธฐ์กด์— links_model.py์— 2๊ฐ€์ง€๊ฐ€ ์ถ”๊ฐ€์ ์œผ๋กœ ํ•„์š”ํ•˜๋‹ค.

์ด๋ฏธ์ง€ ์—…๋กœ๋“œํ›„ ๋งํฌ์ •๋ณด์— ์ด๋ฏธ์ง€ ์ด๋ฆ„์„ ๋งคํ•‘ ์‹œํ‚ค๊ธฐ ์œ„ํ•ด์„œ ์ด๋ฏธ์ง€ ์ด๋ฆ„์„ ์ €์žฅํ•˜๋Š” method์™€ ํ˜„์žฌ DB์˜  id ์‹œํ€€์Šค ๊ฐ’์„ ์•Œ์•„๋‚ด๋Š” method๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

 

id์˜ ํ˜„์žฌ ์‹œํ€€์Šค๋ฅผ ์•Œ์•„์•ผ ํ•˜๋Š” ์ด์œ ๋Š” ์ €์žฅ์ˆœ์„œ๋ฅผ ์ž˜ ์ƒ๊ฐํ•˜๋ฉด ํ•„์š”์„ฑ์ด ๋ณด์ธ๋‹ค.

์ด๋ฏธ์ง€๊ฐ€ ๋จผ์ € ์ €์žฅ ๋˜๋ฉด id์„ ์•Œ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ, 1)๋งํฌ์ •๋ณด(์ด๋ฆ„,ํƒœ๊ทธ ๋“ฑ)๊ฐ€ ๋จผ์ € ์ €์žฅ๋˜์–ด ์‹œํ€€์Šค์ธ id๊ฐ’์ด ์ƒ์„ฑํ•œ ๋‹ค์Œ 2)์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•ด์•ผ ํ•œ๋‹ค.

from sqlalchemy.sql import text

query์— raw string ํ˜•ํƒœ์˜ SQL๋ฌธ์„ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•ด์„œ sqlalchemy.sql.text ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

์šฐ์„  sqlalchemy.sql.text ์„ import ํ•˜๊ณ , ์•„๋ž˜ ์†Œ์Šค๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

# ๋งํฌ ์ด๋ฏธ์ง€ ์ •๋ณด๋ฅผ DB์— ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ ์ฝ”๋“œ
# id์™€ ํŒŒ์ผ์ด๋ฆ„ ํ˜น์€ ์ด๋ฏธ์ง€ ๋งํฌ๋ฅผ ํŒŒ๋ผ๋ฉ”ํƒ€๋ฅผ ๋ฐ›์•„ ์ฒ˜๋ฆฌ
    @classmethod
    def update_filename(self, id, filename):
        try:
            query = "update links set imageurl = :v2 where id = :v1"
            db.session.execute(text(query), {'v1':id, 'v2':filename})
        except:
            db.session.rollback()
            print('update_filename : ์˜ค๋ฅ˜๋ฐœ์ƒ')
            print(query)
        finally:
            db.session.commit()
            db.session.close()

# ๋งํฌ์ •๋ณด๊ฐ€ ์ €์žฅ๋œ ํ˜„์žฌ ์‹œํ€€์Šค id ๊ฐ’์„ ์•Œ์•„๋‚ธ๋‹ค. 
	@classmethod
	def currval_id(self):
		result = db.session.query(func.max(self.id)).scalar()
		return result

โŒ˜ image.py ( ์œ„์น˜: linkservice/server/src/image.py )

-------------------------------------------------------------------------------------------------

์ด๋ฏธ์ง€ ๊ด€๋ จ Resource์„ ์ œ์ž‘ํ•˜๊ธฐ ์œ„ํ•ด์„œ image.py์„ ํ•˜๋‚˜ ์ƒ์„ฑํ•œ๋‹ค.

image.py์—์„œ๋Š” ์ธ๋„ค์ผ ์ •๋„์˜ ์ด๋ฏธ์ง€๋งŒ ์žˆ์œผ๋ฉด ๋˜๋ฏ€๋กœ, 1) ์ด๋ฏธ์ง€๋ฅผ resizing์„ ํ•˜๊ณ , 2)์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ํด๋”์— ์ €์žฅํ•œ ๋‹ค์Œ, 3) ๋งํฌ์ •๋ณด DB์— ์ด๋ฏธ์ง€ ํŒŒ์ผ๋ช…(URL)์„ ๋งคํ•‘ ์‹œํ‚จ๋‹ค.

โ˜ž DB์— ์ €์žฅ๋˜๋Š” ์ด๋ฏธ์ง€ ํŒŒ์ผ๋ช…์€ ์‹ค์ œ CDNํ˜น์€ NAS ์˜ URLํ˜•ํƒœ๋กœ ์ €์žฅ๋œ๋‹ค.

   config.py์˜ IMAGE_URL ๊ฐ’์„ ์ฐธ๊ณ ํ•˜๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

from flask_restful import Resource
from flask import jsonify, request, current_app
from PIL import Image, ImageOps
from models.links_model import LinkModel

"""
UPLOAD  IMAGE /api/link/image 
"""
class ImageUpload(Resource):
    def post(self):
        f = request.files['image']
        folder = current_app.config.get('IMAGE_FORDER')
        img_url = current_app.config.get('IMAGE_URL')

        img = Image.open(f)
        
        # ์ด๋ฏธ์ง€๋ฅผ ๊ฐ•์ œ์ ์œผ๋กœ Resizingํ•œ๋‹ค.
        # Resizing์„ ์•ˆํ•ด๋„ ์ข‹์œผ๋‚˜, ํŒŒ์ผ ์šฉ๋Ÿ‰์„ ์ค„์ด๊ณ ์ž ๋ฐ˜์˜
        img_resize = img.resize((50,50), Image.LANCZOS)
        img_resize = ImageOps.exif_transpose(img_resize)
        img_resize = img_resize.convert('RGB')

        id = LinkModel.currval_id()
        file_name = str(id) + '_' + str(date_function.today()) + '.' + f.filename.rsplit('.', 1)[1].lower()
        img_resize.save(folder + file_name)
		LinkModel.update_filename(id, img_url + file_name)

        return '์ด๋ฏธ์ง€๊ฐ€ ์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค..'

ํ”„๋ก ํŠธ์—”๋“œ์—์„œ formData๋กœ ๋ณด๋‚ธ image์„ request.files๋กœ ๋ฐ›์•„ Python์˜ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ pillow(PIL) ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฏธ์ง€๋ฅผ Resizingํ•œ๋‹ค.

 

๋˜ํ•œ ํŒŒ์ผ ์ด๋ฆ„์„ ๋ณ€๊ฒฝํ•œ๋‹ค. ์ด์œ ๋Š” ํ•œ๊ธ€ ํ˜น์€ ๊ฐ™์€ ํŒŒ์ผ ์ด๋ฆ„์ด ์žˆ์„ ์ˆ˜ ์žˆ๊ธฐ์— ๋Œ€๋ถ€๋ถ„ ์‚ฌ์ดํŠธ๋ฅผ ๋ณด๋ฉด ๋ช…๋ช…๊ทœ์น™(Naming rule)์— ๋งž๊ฒŒ ๋ณ€๊ฒฝํ•˜์—ฌ ์ž์ฒด ๋ณด๊ด€ํ•œ๋‹ค.

์—ฌ๊ธฐ์„œ๋Š” ํŒŒ์ผ๋ช… Naming rule์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•˜๊ธฐ๋กœ ํ•˜์ž. ( ๊ฐ์ž ์ •ํ•ด๋„ ๋ฌด๋ฐฉ ํ•œ๋‹ค.)

   โ–บ ID_๋‚ ์งœ.ํ™•์žฅ์ž

 

        ๐Ÿ“Œ DB์— ์ด๋ฏธ์ง€ ํŒŒ์ผ ์ €์žฅ ์‹œ image_url + file_name์ž„์„ ์ƒ๊ธฐํ•˜๊ธฐ ๋ฐ”๋ž€๋‹ค.

 

๋˜ํ•œ, ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ๋งํฌ์ •๋ณด๊ฐ€ ์ˆ˜์ •/์‚ญ์ œ ์‹œ ๊ธฐ์กด ์ด๋ฏธ์ง€๋ฅผ ๋ณ€๊ฒฝํ•ด์ฃผ๋Š” ์ž‘์—…๋„ ํ•„์š”ํ•จ์œผ๋กœ ๋ฏธ๋ฆฌ ์ž‘์„ฑํ•œ๋‹ค.

"""
CHANGE  IMAGE /api/link/image/<int:id>
"""
class ImageChange(Resource):
    def put(self, id):
        f = request.files['image']
        folder = current_app.config.get('IMAGE_FORDER')
        img_url = current_app.config.get('IMAGE_URL')

        img = Image.open(f)
        # ์ด๋ฏธ์ง€๋ฅผ ๊ฐ•์ œ์ ์œผ๋กœ Resizingํ•œ๋‹ค.
        # Resizing์„ ์•ˆํ•ด๋„ ์ข‹์œผ๋‚˜, ํŒŒ์ผ ์šฉ๋Ÿ‰์„ ์ค„์ด๊ณ ์ž ๋ฐ˜์˜
        img_resize = img.resize((50,50), Image.LANCZOS)
        img_resize = ImageOps.exif_transpose(img_resize)
        img_resize = img_resize.convert('RGB')

        file_name = str(id) + '_' + str(date_function.today()) + '.' + f.filename.rsplit('.', 1)[1].lower()
        img_resize.save(folder + file_name)

        LinkModel.update_filename(id, img_url + file_name)

        return '์ด๋ฏธ์ง€๊ฐ€ ์ˆ˜์ •๋˜์—ˆ์Šต๋‹ˆ๋‹ค..'

ImageUpload() ์™€ ImageChange() ์˜ ์ฐจ์ด์ ์€ id ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š๋ƒ ์•„๋‹ˆ๋ฉด currval_id๊ฐ€์ง€๊ณ  ์ƒ์„ฑํ•˜๋Š๋ƒ ์ฐจ์ด๋‹ค.

์†Œ์Šค๋ฅผ ํ•˜๋‚˜๋กœ ๋ณ‘ํ•ฉํ•ด๋„ ๋ ์ง€ ์—ฌ๋Ÿฌ๋ถ„๋“ค์ด ์ง์ ‘ ํ•ด๋ณด์‹œ๋ฉด ์ข‹์„ ๋“ฏ ํ•˜๋‹ค.

 

โŒ˜ server.py ( ์œ„์น˜: linkservice/server/server.py )

--------------------------------------------------------------------------------------------------

API์— ImageUpload Resource์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

server.py์— ImageUpload์„ importํ•˜๊ณ ,

from src.image import ImageUpload

api resource์— ์ถ”๊ฐ€ํ•œ๋‹ค.

api.add_resource(ImageUpload, '/api/link/image')
api.add_resource(ImageChange, '/api/link/image/<int:id>')

 

โŽŸโŽœํด๋ผ์ด์–ธํŠธ๋‹จ(ํ”„๋ก ํŠธ ์—”๋“œ) API ํ˜ธ์ถœ

 

โŒ˜ api.config.js ( ์œ„์น˜: linkservice/client/src/component/api/api.config.js )

-------------------------------------------------------------------------------------------------

์„œ๋น„์Šค๋ช… API์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

api.config.js

โŒ˜ service.js ( ์œ„์น˜: linkservice/client/src/component/service/service.js )

-------------------------------------------------------------------------------------------------

linkService์—์„œ API์„ ํ˜ธ์ถœํ•˜์—ฌ ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

service.js

๋ชจ๋‘ ์ž‘์„ฑ๋˜๋ฉด, ์ด์   ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด์ž

http://localhost:3000/create ๋กœ ์ ‘์† ํ›„ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด๋ณด๊ณ  ๋“ฑ๋ก ํ•œ๋‹ค.

 

์ •์ƒ์ ์œผ๋กœ ๋“ฑ๋ก๋˜์—ˆ๋‹ค๋ฉด ์ดˆ๊ธฐํ™”๋ฉด์—์„œ ํ™•์ธํ•ด๋ณด์ž.

http://locahost:3000

 

 

 

๊ด€๋ฆฌ์ž๋ชจ๋“œ ํ™”๋ฉด์„ ๋งŒ๋“ค์–ด ๋“ฑ๋ก/ํŽธ์ง‘/์‚ญ์ œ/์ˆœ์„œ๋ณ€๊ฒฝ์ด ๊ฐ€๋Šฅํ•˜๊ฒŒ Prototype UI์„ ๊ตฌ์„ฑํ•ด ๋ณด์ž.

+ Recent posts