#12. React์์ Drag & Drop์ ์ด์ฉํ ์ด๋ฏธ์ง ์
๋ก๋
React์์ ๋ฑ๋กํ๋ฉด์์ ์ด๋ฏธ์ง๋ฅผ Drag&Drop ํ ์ ์๋๋ก ๊ตฌํ์ ํ๋ค.
๊ทธ๋ผ ์ด์ ๋ฑ๋กํ๋ฉด์ ๋ง๋ฌด๋ฆฌ๋ก ์ ์ฅ๊ธฐ๋ฅ์ ๊ตฌํํด๋ณด์.
์ด๋ฏธ์ง ์ ์ฅ๊ธฐ๋ฅ(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์ ์ถ๊ฐํ๋ค.
โ service.js ( ์์น: linkservice/client/src/component/service/service.js )
-------------------------------------------------------------------------------------------------
linkService์์ API์ ํธ์ถํ์ฌ ์ด๋ฏธ์ง๋ฅผ ์ ์ฅํ๋ ํจ์๋ฅผ ์ถ๊ฐํ๋ค.
๋ชจ๋ ์์ฑ๋๋ฉด, ์ด์ ํ
์คํธ๋ฅผ ํด๋ณด์
http://localhost:3000/create ๋ก ์ ์ ํ ํ
์คํธ ๋ฐ์ดํฐ๋ฅผ ๋ฃ์ด๋ณด๊ณ ๋ฑ๋ก ํ๋ค.
์ ์์ ์ผ๋ก ๋ฑ๋ก๋์๋ค๋ฉด ์ด๊ธฐํ๋ฉด์์ ํ์ธํด๋ณด์.
http://locahost:3000
๊ด๋ฆฌ์๋ชจ๋ ํ๋ฉด์ ๋ง๋ค์ด ๋ฑ๋ก/ํธ์ง/์ญ์ /์์๋ณ๊ฒฝ์ด ๊ฐ๋ฅํ๊ฒ Prototype UI์ ๊ตฌ์ฑํด ๋ณด์.