Thanks to visit codestin.com
Credit goes to github.com

Skip to content

erv2bh/uxplorer-server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

54 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ“– UXPlorer

UXPlorerλŠ” ν…ŒμŠ€νŠΈ μœ μ €λ₯Ό ν…ŒμŠ€νŠΈν•΄ μ£Όκ³  기둝을 뢄석해, 데이터λ₯Ό μ‹œκ°ν™”ν•΄ μ£ΌλŠ” User Test B2B SaaS μž…λ‹ˆλ‹€.


Links

Deployed website | Frontend Repository | Backend Repository

Table of Contents


Features

ν…ŒμŠ€νŠΈ μ§„ν–‰ κΈ°λŠ₯

  • ν…ŒμŠ€ν„°λŠ” ν…ŒμŠ€νŠΈ ν™˜κ²½μ—μ„œ UX ν…ŒμŠ€νŠΈλ₯Ό μ§„ν–‰ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
    • ν™”λ©΄ λ…Ήν™” 곡유 κΈ°λŠ₯을 μ œκ³΅ν•¨μœΌλ‘œμ¨ μ •λŸ‰μ  μ§€ν‘œλ₯Ό λ’·λ°›μΉ¨ ν•΄μ€λ‹ˆλ‹€.
    • ν…ŒμŠ€νŠΈ μ™„λ£Œ ν›„ ν…ŒμŠ€ν„°κ°€ μ„€λ¬Έ μž‘μ„±μ„ ν•  수 μžˆλ„λ‘ μ„€λ¬Έ νŽ˜μ΄μ§€λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€.

μƒˆλ‘œμš΄ ν…ŒμŠ€νŠΈ 생성 κΈ°λŠ₯

  • μ„œλΉ„μŠ€ μ΄μš©μžλŠ” μ„œλΉ„μŠ€λ₯Ό ν…ŒμŠ€νŠΈν•˜κΈ° μœ„ν•œ ν…ŒμŠ€νŠΈλ₯Ό λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.
    • ν…ŒμŠ€νŠΈ 이름, ν…ŒμŠ€νŠΈ 상세 μ„€λͺ…, ν…ŒμŠ€νŠΈ URL, ν…ŒμŠ€ν„° 이메일, 마감 λ‚ μ§œλ₯Ό μž…λ ₯ν•©λ‹ˆλ‹€.
    • ν…ŒμŠ€νŠΈμ—μ„œ μ§„ν–‰ν•  λ―Έμ…˜κ³Ό λ―Έμ…˜λ³„ μ˜ˆμƒ μ†Œμš”μ‹œκ°„μ„ μž…λ ₯ν•©λ‹ˆλ‹€.

ν…ŒμŠ€νŠΈ μ„ΈλΆ€ 정보 및 κ²°κ³Ό 보기 κΈ°λŠ₯

  • μ„œλΉ„μŠ€ μ΄μš©μžλŠ” μƒμ„±ν•œ ν…ŒμŠ€νŠΈμ— λŒ€ν•œ μ„ΈλΆ€ 정보 및 κ²°κ³Όλ₯Ό λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€.
    • 톡합 결과의 경우 λ―Έμ…˜ μˆ˜ν–‰λ₯ , λ―Έμ…˜λ³„ 평균 μ†Œμš”μ‹œκ°„, μ„œλΉ„μŠ€ λ§Œμ‘±λ„ 및 μΆ”μ²œ μ§€μˆ˜μ˜ 톡계가 κ·Έλž˜ν”„λ‘œ λ‚˜νƒ€λ‚©λ‹ˆλ‹€.
    • μœ μ €λ³„ 결과의 경우 μœ μ €λ³„ μ„ΈλΆ€ λ―Έμ…˜ μˆ˜ν–‰μ‚¬ν•­ 및 ν”Όλ“œλ°± 정보가 λ³΄μ—¬μ§‘λ‹ˆλ‹€.

Challenges

1. μ‚¬μš©μ„± ν…ŒμŠ€νŠΈ


ν”„λ‘œν† νƒ€μž… μ œμž‘μ€‘μ— κ³Όμ—° μ‚¬λžŒλ“€μ΄ 이 μ„œλΉ„μŠ€λ₯Ό 잘 μ‚¬μš©ν•  수 μžˆλŠ”μ§€, ν˜Ήμ‹œ μ‚¬μš©ν•˜κΈ° μ–΄λ ΅κ±°λ‚˜ λ³΅μž‘ν•˜μ§€ μ•Šμ„μ§€ 고민이 λ©λ‹ˆλ‹€. μ΄λ•Œ μ‚¬μš©μ„± ν…ŒμŠ€νŠΈλ₯Ό 톡해 μ‚¬μš©μžλ“€μ΄ μ„œλΉ„μŠ€λ₯Ό λͺ©μ μ— 맞게 잘 μ‚¬μš©ν•  수 μžˆλŠ”μ§€ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€. μ’€ 더 μ‚¬μš©μž μΉœν™”μ μΈ μ„œλΉ„μŠ€λ‘œμ˜ λ°œμ „μ„ μœ„ν•΄ ν•„μˆ˜μ μΈ μš”μ†Œμž…λ‹ˆλ‹€.

UXPlorerλŠ” μ˜€ν”„λΌμΈμ—μ„œ μ§„ν–‰λ˜λŠ” μ‚¬μš©μ„± ν…ŒμŠ€νŠΈλ₯Ό 온라인으둜 μ§„ν–‰ν•΄ 보면 νŽΈν•˜μ§€ μ•Šμ„κΉŒ ν•˜λŠ” λ‹¨μˆœν•œ ν˜ΈκΈ°μ‹¬μ—μ„œ μ‹œμž‘λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

μ˜€ν”„λΌμΈμ—μ„œ μ§„ν–‰λ˜λŠ” μ‚¬μš©μ„± ν…ŒμŠ€νŠΈλŠ” λ‹€μ„―κ°€μ§€μ˜ UX KPIs(Key Performance Indicators)λ₯Ό κΈ°μ€€μœΌλ‘œ 톡계λ₯Ό λƒ…λ‹ˆλ‹€.

  • UX KPIs(Key Performance Indicators)
    • Task Success Rate (ν•„μš”ν•œ 것을 μ°Ύμ•„ ν•΄κ²°ν•œ μ‚¬λžŒμ΄ λͺ‡λͺ…인지)
    • Time on Task (ν•„μš”ν•œ 것을 μ–Όλ§ˆλ‚˜ 빨리 μ°Ύμ•„ λλƒˆλŠ”μ§€)
    • System Usability Scale : SUS (μ œν’ˆμ— μ–Όλ§ˆλ‚˜ λ§Œμ‘±μ„ ν•˜λŠ”μ§€) μ§ˆλ¬Έμ§€
    • Use of Search vs. Navigation (ν•„μš”ν•œ 것을 μ°ΎκΈ° μœ„ν•΄ λͺ‡λͺ…이 λ‚΄λΉ„κ²Œμ΄μ…˜μ„ μ‚¬μš©ν•˜κ³  λͺ‡λͺ…이 검색 κΈ°λŠ₯을 μΌλŠ”μ§€)
    • User Error Rate(였λ₯˜κ°€ λͺ‡λ²ˆ λ°œμƒν–ˆλŠ”μ§€ μ–΄λ–»κ²Œ ν•΄μ„œ 그런 였λ₯˜κ°€ λ–΄λŠ”μ§€)

1-1 온라인으둜 μ‚¬μš©μ„± ν…ŒμŠ€νŠΈ μ§„ν–‰ν•˜κΈ°

1-1-1 고렀사항: μ˜¨λΌμΈμ—μ„œ μ°Έκ³ ν• λ§Œν•œ UX KPI 선별

μ˜€ν”„λΌμΈμ—μ„œ μ§„ν–‰λ˜λŠ” μ‚¬μš©μ„± ν…ŒμŠ€νŠΈλ₯Ό μ˜¨λΌμΈμ—μ„œ μ§„ν–‰ν•  λ•Œ, μ„±κ³Ό μ§€ν‘œλ₯Ό μ–΄λ–»κ²Œ 톡계화할 수 μžˆμ„μ§€ 고민이 λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 온라인 ν™˜κ²½μ—μ„œ μ •ν™•ν•œ 데이터 μˆ˜μ§‘κ³Ό 뢄석이 μ–΄λ €μšΈ 수 μžˆλ‹€κ³  μƒκ°ν–ˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ, ν…ŒμŠ€ν„°μ˜ 자발적인 μ°Έμ—¬λ₯Ό μœ λ„ν•˜λŠ” 방법도 κ³ λ €ν•΄μ•Ό ν–ˆμŠ΅λ‹ˆλ‹€.

1-1-2 μ ‘κ·Ό 방법: μ •λŸ‰μ  μ§€ν‘œμ™€ 정성적 μ§€ν‘œ μ„€μ •

μ •λŸ‰μ  μ§€ν‘œμ™€ 정성적 μ§€ν‘œλ₯Ό 기반으둜 ν•œ ν†΅κ³„λŠ” μ‚¬μš©μžλ‘œ ν•˜μ—¬κΈˆ 보닀 객관적인 데이터λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€. λ”μš±μ΄ UXPlorer의 경우 온라인 ν”Œλž«νΌμ΄λ‹€ λ³΄λ‹ˆ 두가지 μ§€ν‘œκ°€ μ ˆλŒ€μ μ΄μ—ˆμŠ΅λ‹ˆλ‹€.

1. μ •λŸ‰μ  μ§€ν‘œ

μ˜¨λΌμΈμ—μ„œ κ΅¬ν˜„ κ°€λŠ₯ν•œ μ§€ν‘œλ₯Ό μΆ”λ €λ³΄λ‹ˆ

  • λ―Έμ…˜ μˆ˜ν–‰λ₯ : λ―Έμ…˜λ³„ 톡과 μ—¬λΆ€ 톡계 μ‹œκ°ν™”
  • λ―Έμ…˜ μ†Œμš”μ‹œκ°„: λ―Έμ…˜λ³„ μ†Œμš”μ‹œκ°„ 톡계 μ‹œκ°ν™”
  • μ„œλΉ„μŠ€ λ§Œμ‘±λ„ 점수: System Usability Scale을 ν† λŒ€λ‘œ μ„œλΉ„μŠ€ λ§Œμ‘±λ„ 톡계 μ‹œκ°ν™”

μœ„μ˜ μ„Έκ°€μ§€ μ§€ν‘œλ₯Ό μ •λŸ‰ν™” ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

각 ν…ŒμŠ€νŠΈ λ³„λ‘œ μ„œλΉ„μŠ€ 이용자(κΈ°μ—…)κ°€ ν…ŒμŠ€ν„°λ“€μ—κ²Œ μ œκ³΅ν•  λ―Έμ…˜μ„ μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€. ν…ŒμŠ€ν„°λŠ” λͺ¨λ‹¬μ„ μ΄μš©ν•˜μ—¬ λ³ΈμΈμ—κ²Œ μ£Όμ–΄μ§„ λ―Έμ…˜μ„ μˆ˜ν–‰ν•˜λ„λ‘ ν–ˆμŠ΅λ‹ˆλ‹€.

  • λ―Έμ…˜ μˆ˜ν–‰λ₯  - μ„œλΉ„μŠ€ μ΄μš©μžλŠ” ν…ŒμŠ€ν„°μ˜ λ―Έμ…˜ 성곡 μ—¬λΆ€λ₯Ό μ–΄λ–»κ²Œ νŒλ‹¨ν•  수 μžˆμ„κΉŒμš”? 이λ₯Ό μœ„ν•΄ 각 λ―Έμ…˜μ§€μ— κ±΄λ„ˆλ›°κΈ° ν•­λͺ©μ„ λΆ€μ—¬ν•΄ μ£Όμ–΄ ν…ŒμŠ€ν„°κ°€ 자발적으둜 λ―Έμ…˜μ„ μˆ˜ν–‰ν•˜μ§€ λͺ»ν•˜μ˜€λ‹€λŠ” 근거둜 μˆ˜ν–‰ μ—¬λΆ€λ₯Ό 데이터화 ν•˜μ˜€μŠ΅λ‹ˆλ‹€. κ±΄λ„ˆλ›°κΈ°μ˜ 경우 μ„œλΉ„μŠ€ μ΄μš©μžκ°€ λ―Έμ…˜μ„ μž‘μ„±ν•  λ•Œ λ―Έμ…˜λ³„ μ˜ˆμƒ μ†Œμš”μ‹œκ°„μ„ μ„€μ •ν•΄ 두고 μ˜ˆμƒ μ†Œμš”μ‹œκ°„μ΄ κ²½κ³Όν•˜λ©΄ ν…ŒμŠ€ν„°κ°€ κ±΄λ„ˆλ›°κΈ° ν•­λͺ©μ„ 선택할 수 있게 ν•˜μ˜€μŠ΅λ‹ˆλ‹€. ν…ŒμŠ€ν„°κ°€ λ―Έμ…˜μ„ κ±΄λ„ˆλ›°μ—ˆμ„ 경우 ν•΄λ‹Ή λ―Έμ…˜μ€ μ‹€νŒ¨λ‘œ κ°„μ£Όλ©λ‹ˆλ‹€.

  • λ―Έμ…˜ μ†Œμš”μ‹œκ°„ - 각 λ―Έμ…˜λ³„λ‘œ ν…ŒμŠ€ν„°κ°€ λ―Έμ…˜μ„ μ™„λ£Œν•œ μ‹œκ°„μ„ μ•Œ 수 μžˆλ‹€λ©΄, 그리고 μ„œλΉ„μŠ€ μ΄μš©μžκ°€ μ„œλΉ„μŠ€ 기획 μ‹œ ν•΄λ‹Ή κΈ°λŠ₯을 μ™„λ£Œν• κ±°λΌλŠ” μ˜ˆμƒ μ†Œμš”μ‹œκ°„μ— λŒ€ν•œ 정보가 μžˆλ‹€λ©΄ λ―Έμ…˜ μ†Œμš”μ‹œκ°„μ„ 톡계화 ν•˜μ—¬ μ •λŸ‰μ  μ§€ν‘œλ‘œ 삼을 수 μžˆμŠ΅λ‹ˆλ‹€. 이 λ•Œ λ―Έμ…˜ μ†Œμš”μ‹œκ°„μ˜ λ°μ΄ν„°λŠ” μ„±κ³΅ν•œ λ―Έμ…˜μ— ν•œμ •ν•˜μ—¬ 톡계에 μ‚¬μš©ν•©λ‹ˆλ‹€. λ”°λΌμ„œ ν…ŒμŠ€ν„°κ°€ κ±΄λ„ˆλ›°κΈ° ν•­λͺ©μ„ μ„ νƒν•˜μ˜€μ„ 경우의 λ°μ΄ν„°λŠ” λ¬΄μ‹œλ©λ‹ˆλ‹€.

  • μ„œλΉ„μŠ€ λ§Œμ‘±λ„ 점수 - μ„€λ¬Έμ§€ μž‘μ„±μ„ ν†΅ν•˜μ—¬ ν…ŒμŠ€ν„°λŠ” μ„œλΉ„μŠ€ μ΄μš©μ— λŒ€ν•œ λ§Œμ‘±λ„λ₯Ό 평가할 수 있고 μ΄λŠ” μˆ˜μΉ˜ν™” μ‹œν‚¬ 수 있으며 λ‹Ήμ—°νžˆ μ„œλΉ„μŠ€ μ΄μš©μžμ—κ²Œ μ œκ³΅λ©λ‹ˆλ‹€. μ„œλΉ„μŠ€ μ΄μš©μžλŠ” μˆ˜μΉ˜ν™”λœ ν…ŒμŠ€ν„°λ³„ λ§Œμ‘±λ„λ₯Ό κ·Έλž˜ν”„ν™” ν•˜μ—¬ μ‹œκ°μ μœΌλ‘œ 비ꡐ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

2. 정성적 μ§€ν‘œ

  • ν…ŒμŠ€ν„°μ˜ 적극적인 μ°Έμ—¬ - ν…ŒμŠ€ν„°λŠ” λ―Έμ…˜ μˆ˜ν–‰ μ‹œ λ―Έμ…˜μ— κ΄€ν•˜μ—¬ ν”Όλ“œλ°±μ„ 자유둭게 남길 수 μžˆμŠ΅λ‹ˆλ‹€. 이 ν”Όλ“œλ°±μ€ μ„œλΉ„μŠ€ μ΄μš©μžμ—κ²Œ 제곡되며 ν…ŒμŠ€ν„°μ˜ μ˜κ²¬μ„ 적극 λ°˜μ˜ν•  수 있게 λ©λ‹ˆλ‹€.

  • NPS 점수 - NPS μ μˆ˜λŠ” κΈ°μ—…μ΄λ‚˜ μ œν’ˆμ˜ 고객 좩성도와 λ§Œμ‘±λ„λ₯Ό κ°„λ‹¨ν•˜λ©΄μ„œλ„ 효과적으둜 μΈ‘μ •ν•˜λŠ” μ§€ν‘œμž…λ‹ˆλ‹€. 이 μ μˆ˜λŠ” 고객이 μ œν’ˆμ΄λ‚˜ μ„œλΉ„μŠ€λ₯Ό λ‹€λ₯Έ μ‚¬λžŒμ—κ²Œ μΆ”μ²œν•  κ°€λŠ₯성을 0λΆ€ν„° 10κΉŒμ§€μ˜ μ²™λ„λ‘œ λ‚˜νƒ€λ‚΄λ©°, 이λ₯Ό 톡해 μΆ”μ²œμž, μ€‘λ¦½μž, λΉ„ν‰μžμ˜ μ„Έ 그룹으둜 λΆ„λ₯˜ν•©λ‹ˆλ‹€.

μ΄λŸ¬ν•œ μ ‘κ·Ό 방법을 톡해 UXPlorerλŠ” μ˜¨λΌμΈμ—μ„œμ˜ μ‚¬μš©μ„± ν…ŒμŠ€νŠΈλ₯Ό 보닀 효과적으둜 μˆ˜ν–‰ν•  수 있게 λ˜μ—ˆκ³ , μ‚¬μš©μž κ²½ν—˜μ„ λ”μš± ν–₯μƒμ‹œν‚¬ 수 μžˆλŠ” μ€‘μš”ν•œ 데이터λ₯Ό μˆ˜μ§‘ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.



2. μ‚¬μš©μž(μ„œλΉ„μŠ€ 이용자)와 ν…ŒμŠ€ν„°μ˜ ꡬ뢄


Figmaλ₯Ό ν™œμš©ν•œ μœ μ € ν”Œλ‘œμš° 섀계

2-1 μ‚¬μš©μžμ™€ ν…ŒμŠ€ν„°λ₯Ό ꡬ뢄할 수 μžˆλŠ” 방법 μ°ΎκΈ°

2-1-1 고렀사항: λ³΄μ•ˆ 및 μ ‘κ·Ό κΆŒν•œ

UXPlorer μ—μ„œ μ‚¬μš©μžμ™€ ν…ŒμŠ€ν„°μ˜ 역할을 λͺ…ν™•ν•˜κ²Œ κ΅¬λΆ„ν•˜λŠ” 것이 ν•„μš”ν–ˆμŠ΅λ‹ˆλ‹€. μ΄μœ λŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  • UXPlorer의 데이터 λ³΄μ•ˆμ„ κ°•ν™”ν•˜κΈ° μœ„ν•΄ μ‚¬μš©μžλŠ” 데이터λ₯Ό 생성, μˆ˜μ •, μ‚­μ œν•  수 μžˆλŠ” κΆŒν•œμ„ κ°€μ§€μ§€λ§Œ, ν…ŒμŠ€ν„°λŠ” μ£Όμ–΄μ§„ ν…ŒμŠ€νŠΈμ— ν•„μš”ν•œ μ •λ³΄λ§Œμ„ μ—΄λžŒν•  수 μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€.
  • λ¬΄λΆ„λ³„ν•œ 데이터 접근을 λ°©μ§€ν•˜κ³ , ν…ŒμŠ€ν„°λŠ” μžμ‹ μ—κ²Œ ν• λ‹Ήλœ ν…ŒμŠ€νŠΈμ—λ§Œ μ ‘κ·Όν•  수 μžˆλ„λ‘ μ œν•œν•΄μ•Ό ν•©λ‹ˆλ‹€.
  • μ‚¬μš©μžλŠ” ν…ŒμŠ€νŠΈλ₯Ό κ΄€λ¦¬ν•˜κ³  λΆ„μ„ν•˜λŠ” 데 ν•„μš”ν•œ λ‹€μ†Œ λ³΅μž‘ν•œ μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ‚¬μš©ν•˜κ³ , ν…ŒμŠ€ν„°λŠ” κ°„λ‹¨ν•˜κ³  직관적인 μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.

2-1-2 μ ‘κ·Ό 방법: μ‚¬μš©μžμ™€ ν…ŒμŠ€ν„°μ˜ 둜그인 방식 ꡬ뢄

둜그인 방식을 달리 μ μš©ν•˜μ—¬ μ‚¬μš©μžμ™€ ν…ŒμŠ€ν„°λ₯Ό κ΅¬λΆ„ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

  • μ‚¬μš©μž 둜그인: μ‚¬μš©μžλŠ” Google Firebaseλ₯Ό μ‚¬μš©ν•˜μ—¬ μ‹œμŠ€ν…œμ— λ‘œκ·ΈμΈν•˜λ„λ‘ μ„€κ³„ν–ˆμŠ΅λ‹ˆλ‹€. 이 방식은 μ‚¬μš©μžλ₯Ό μ‹λ³„ν•˜κ³  μΈμ¦ν•˜λŠ” 데 μ‚¬μš©λ˜λ©°, μ‚¬μš©μžκ°€ μ‹œμŠ€ν…œμ—μ„œ ν…ŒμŠ€νŠΈλ₯Ό μƒμ„±ν•˜κ³  관리할 수 있게 ν•©λ‹ˆλ‹€.

  • ν…ŒμŠ€ν„° 둜그인: ν…ŒμŠ€ν„°λŠ” μƒμ„±λœ ν…ŒμŠ€νŠΈμ˜ title을 기반으둜 ν•œ κ³ μœ ν•œ ν…ŒμŠ€ν„° ID와 λΉ„λ°€λ²ˆν˜Έλ₯Ό μ΄λ©”μΌλ‘œ 뢀여받도둝 μ„€κ³„ν–ˆμŠ΅λ‹ˆλ‹€. ν…ŒμŠ€ν„°λŠ” 이 ID와 λΉ„λ°€λ²ˆν˜Έλ₯Ό μ‚¬μš©ν•˜μ—¬ λ‘œκ·ΈμΈν•˜λ©°, μ΄λŠ” ν…ŒμŠ€ν„°κ°€ νŠΉμ • ν…ŒμŠ€νŠΈμ—λ§Œ μ ‘κ·Όν•  수 μžˆλ„λ‘ μ œν•œν•©λ‹ˆλ‹€. λΉ„λ°€λ²ˆν˜ΈλŠ” λ³΄μ•ˆμ„ μœ„ν•΄ bcryptλ₯Ό μ‚¬μš©ν•˜μ—¬ ν•΄μ‹œλ˜μ–΄ λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯λ©λ‹ˆλ‹€.

    const testerPassword = `${Math.random().toString(36).substring(2, 8)}`;
    const hashedTesterPassword = await bcrypt.hash(testerPassword, saltRounds);

❗️ 문제 사항

ν•˜μ§€λ§Œ ν…ŒμŠ€νŠΈμ˜ title이 쀑볡될 경우 λ™μΌν•œ ν…ŒμŠ€ν„° IDκ°€ μƒμ„±λ˜λŠ” λ¬Έμ œκ°€ μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

πŸ’‘ ν•΄κ²° 방법

이 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄, ν…ŒμŠ€νŠΈ 생성 μ‹œ μ„œλ²„μ˜ λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ 이미 μ‘΄μž¬ν•˜λŠ” ν…ŒμŠ€νŠΈ 제λͺ©μ„ κ²€μ‚¬ν•˜μ—¬ 쀑볡을 λ°©μ§€ν•©λ‹ˆλ‹€. 이 방법을 톡해 λ°μ΄ν„°μ˜ 일관성을 μœ μ§€ν•˜κ³  ν…ŒμŠ€ν„° ID의 κ³ μœ μ„±μ„ 확보할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

const existingTest = await Test.findOne({ title: testName });

if (existingTest) {
  return res.status(400).json({ error: "Same Title already existed" });
}

이 μ ‘κ·Ό 방법을 톡해 데이터 λ³΄μ•ˆμ„ κ°•ν™”ν•˜κ³ , μ‚¬μš©μžμ™€ ν…ŒμŠ€ν„°μ˜ 역할을 λͺ…ν™•ν•˜κ²Œ ꡬ뢄할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό 톡해 λ¬΄λΆ„λ³„ν•œ 데이터 접근을 λ°©μ§€ν•˜κ³ , 각 μ‚¬μš©μžκ°€ μžμ‹ μ˜ 역할에 λ§žλŠ” μΈν„°νŽ˜μ΄μŠ€λ₯Ό 효율적으둜 μ‚¬μš©ν•  수 있게 λ˜μ—ˆμŠ΅λ‹ˆλ‹€.


2-2 ν…ŒμŠ€νŠΈ ν™˜κ²½μ„ λ…λ¦½μ μœΌλ‘œ κ΄€λ¦¬ν•˜κΈ°

2-2-1 고렀사항: 데이터 μ •κ·œν™”μ™€ 관리

UXPlorer의 관건은 μ–Όλ§ˆλ‚˜ 재미있고 μœ μš©ν•œ 데이터λ₯Ό λͺ¨μ„ 수 μžˆλŠλƒμ˜€μŠ΅λ‹ˆλ‹€. μ΄λŸ¬ν•œ λ°μ΄ν„°λŠ” μ‚¬μš©μžμ—κ²Œ λ”μš± 도움이 λ˜λŠ” μš”μ†ŒλΌ μƒκ°ν–ˆμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ 각 ν…ŒμŠ€νŠΈ ν™˜κ²½μ„ λ…λ¦½μ μœΌλ‘œ κ΄€λ¦¬ν•˜λ©΄μ„œλ„ μœ μš©ν•œ 데이터λ₯Ό μˆ˜μ§‘ν•˜λŠ” 것이 μ€‘μš”ν–ˆμŠ΅λ‹ˆλ‹€.

2-2-2 μ ‘κ·Ό 방법: MongoDB μŠ€ν‚€λ§ˆ 섀계

이λ₯Ό μœ„ν•΄ MongoDB의 μŠ€ν‚€λ§ˆ 섀계 μ‹œ Embedded 방식과 Reference 방식을 비ꡐ해 λ³Ό ν•„μš”κ°€ μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

MongoDB κ³΅μ‹λ¬Έμ„œμ— λ”°λ₯΄λ©΄ Embedded 방식과 Reference 방식은 λ‹€μŒκ³Ό 같은 차이점이 μžˆμŠ΅λ‹ˆλ‹€.

방식 μž₯점 단점
Embedded 방식 Document 내에 κ΄€λ ¨ 데이터가 직접 ν¬ν•¨λ˜λ―€λ‘œ 쑰회 속도가 λΉ λ¦…λ‹ˆλ‹€. 데이터 μ—…λ°μ΄νŠΈκ°€ λ³΅μž‘ν•˜κ³ , 쀑볡이 λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€.
Reference 방식 Document κ°„μ˜ 관계λ₯Ό ID μ°Έμ‘°λ₯Ό 톡해 λ§ΊμœΌλ―€λ‘œ, 데이터 μ •κ·œν™”κ°€ μš©μ΄ν•˜κ³ , μ—…λ°μ΄νŠΈκ°€ κ°„λ‹¨ν•©λ‹ˆλ‹€. 쑰회 μ‹œ 좔가적인 쑰인이 ν•„μš”ν•˜λ―€λ‘œ 속도가 λ‹€μ†Œ 느릴 수 μžˆμŠ΅λ‹ˆλ‹€.

결과적으둜 데이터 μ •κ·œν™”κ°€ μš©μ΄ν•˜κ³  μ—…λ°μ΄νŠΈκ°€ κ°„λ‹¨ν•˜κΈ° λ•Œλ¬Έμ— ν…ŒμŠ€νŠΈ, ν…ŒμŠ€ν„°, 그리고 λ―Έμ…˜ λ°μ΄ν„°λŠ” MongoDB의 Reference λ°©μ‹μœΌλ‘œ μ„€κ³„ν•˜μ˜€μŠ΅λ‹ˆλ‹€. 이 방식은 각 ν…ŒμŠ€νŠΈ ν™˜κ²½μ„ λ…λ¦½μ μœΌλ‘œ 관리할 수 있게 ν•©λ‹ˆλ‹€.

  • ν…ŒμŠ€νŠΈ 및 λ―Έμ…˜ 관리: 각 ν…ŒμŠ€νŠΈλŠ” μ—¬λŸ¬ λ―Έμ…˜μ„ ν¬ν•¨ν•˜κ³ , 이 λ―Έμ…˜λ“€μ€ Mission Collection에 λ…λ¦½μ μœΌλ‘œ μ €μž₯λ©λ‹ˆλ‹€. 각 ν…ŒμŠ€ν„°λŠ” Tester Collection에 μ €μž₯되며, 각 ν…ŒμŠ€ν„° μŠ€ν‚€λ§ˆλŠ” μˆ˜ν–‰ν•΄μ•Ό ν•  λ―Έμ…˜μ˜ ID 배열을 μ°Έμ‘°ν•©λ‹ˆλ‹€.

    const missionPromises = missions.map(async (mission, index) => {
      const newMission = await Mission.create({
        description: mission.description,
        expectedDuration: mission.expectedDuration,
        order: index,
        completedBy: testerIds.map((testerId) => ({ tester: testerId })),
      });
      return newMission._id;
    });
  • λ―Έμ…˜ μ°Έμ‘° μ—…λ°μ΄νŠΈ: ν…ŒμŠ€ν„° 및 ν…ŒμŠ€νŠΈ λ¬Έμ„œλŠ” λ―Έμ…˜ IDλ₯Ό μ°Έμ‘°ν•˜μ—¬ 각 ν…ŒμŠ€ν„°μ™€ ν…ŒμŠ€νŠΈκ°€ κ΄€λ ¨ λ―Έμ…˜μ„ 좔적할 수 μžˆλ„λ‘ ν•©λ‹ˆλ‹€. μ΄λŠ” ν…ŒμŠ€ν„°κ°€ μžμ‹ μ—κ²Œ ν• λ‹Ήλœ λ―Έμ…˜λ§Œμ„ λ³Ό 수 μžˆλ„λ‘ ν•˜λ©°, 각 ν…ŒμŠ€νŠΈ ν™˜κ²½μ„ λ…λ¦½μ μœΌλ‘œ μœ μ§€ν•©λ‹ˆλ‹€.

    await Tester.findByIdAndUpdate(testerId, {
      $push: { missions: { $each: missionIds } },
    });

이 μ ‘κ·Ό 방법을 톡해 각 ν…ŒμŠ€νŠΈ ν™˜κ²½μ„ λ…λ¦½μ μœΌλ‘œ κ΄€λ¦¬ν•˜λ©΄μ„œλ„ μœ μš©ν•œ 데이터λ₯Ό μˆ˜μ§‘ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” μ‚¬μš©μžμ—κ²Œ λ”μš± 도움이 λ˜λŠ” μš”μ†Œλ‘œ μž‘μš©ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.



3. ν…ŒμŠ€νŠΈ λ―Έμ…˜ μˆ˜ν–‰ 둜직


ν…ŒμŠ€νŠΈ ν™˜κ²½ 흐름도

ν…ŒμŠ€ν„°λŠ” μ΄λ©”μΌλ‘œ 뢀여받은 ν…ŒμŠ€νŠΈ ID 및 λΉ„λ°€λ²ˆν˜Έλ₯Ό μ‚¬μš©ν•˜μ—¬ ν…ŒμŠ€νŠΈ ν™˜κ²½μ— μ§„μž…ν•  수 있게 μ„€κ³„ν–ˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ ν…ŒμŠ€ν„° λ‘œκ·ΈμΈμ‹œ μ›°μ»΄ λͺ¨λ‹¬μ„ ν™œμš©ν•˜μ—¬ ν˜„μž¬ 진행될 ν…ŒμŠ€νŠΈμ— λŒ€ν•œ μ„€λͺ…을 ν•΄μ£Όμ—ˆμŠ΅λ‹ˆλ‹€.

3-1 ν˜„μž¬ λ―Έμ…˜μ—μ„œ λ‹€μŒ λ―Έμ…˜μœΌλ‘œμ˜ μ›ν• ν•œ 이동

3-1-1 고렀사항: λ―Έμ…˜κ°„ μ›ν• ν•œ 이동

ν…ŒμŠ€νŠΈ μˆ˜ν–‰μ€ λͺ¨λ‹¬μ°½μ— μ˜ν•΄ λ―Έμ…˜λ³„λ‘œ μ§„ν–‰λ©λ‹ˆλ‹€. ν…ŒμŠ€ν„°κ°€ ν˜„μž¬ λ―Έμ…˜μ„ μ™„λ£Œν•œ ν›„ λ‹€μŒ λ―Έμ…˜μœΌλ‘œ μ›ν™œνžˆ 이동할 수 μžˆλ„λ‘ 섀계가 ν•„μš”ν–ˆμŠ΅λ‹ˆλ‹€.

3-1-2 μ ‘κ·Ό 방법: Jotaiλ₯Ό μ΄μš©ν•œ μƒνƒœ 관리

μ „μ—­ μƒνƒœλ‘œ λ―Έμ…˜ 인덱슀λ₯Ό κ΄€λ¦¬ν•˜λŠ” 것은 μ—¬λŸ¬ μ»΄ν¬λ„ŒνŠΈμ—μ„œ λ―Έμ…˜ 데이터λ₯Ό κ³΅μœ ν•˜κ³  μ—…λ°μ΄νŠΈν•  수 있게 ν•΄μ€λ‹ˆλ‹€. 이λ₯Ό 톡해 각 λ―Έμ…˜μ˜ μƒνƒœλ₯Ό μ‰½κ²Œ μΆ”μ ν•˜κ³ , λ―Έμ…˜ μ™„λ£Œ ν›„ λ‹€μŒ λ―Έμ…˜μœΌλ‘œμ˜ μ›ν™œν•œ 이동을 μ„€κ³„ν• μˆ˜ μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

Redux, Justand λ“± μŠ€ν† μ–΄ 기반의 μƒνƒœκ΄€λ¦¬ 라이브러리λ₯Ό 졜초 μƒκ°ν–ˆμœΌλ‚˜, μ„œλ²„ μƒνƒœλ₯Ό react-queryκ°€ 관리해주고 μžˆκΈ°μ— ν΄λΌμ΄μ–ΈνŠΈμ˜ μƒνƒœλŠ” 보닀 κ°„κ²°ν•œ λ””μžμΈ νŒ¨ν„΄μœΌλ‘œ κ΄€λ¦¬ν•˜λŠ”κ²Œ μ’‹κ² λ‹€κ³  νŒλ‹¨ ν–ˆμŠ΅λ‹ˆλ‹€.

이에 따라 atom으둜 μƒνƒœλ₯Ό κ΄€λ¦¬ν•˜λŠ” Jotaiλ₯Ό 선택 ν–ˆμœΌλ©°, μ»΄ν¬λ„ŒνŠΈ λ³„λ‘œ Jotai의 읽기 μ „μš© ν˜Ήμ€ μ“°κΈ° μ „μš© atom을 ν™œμš©ν•˜μ—¬ μƒνƒœλ₯Ό κ΄€λ¦¬ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

1. λ―Έμ…˜ 인덱슀 관리: λ―Έμ…˜λ“€μ˜ ID 배열을 Jotai 기반 μ „μ—­ μƒνƒœ(testerMissionsDataAtom)μ—μ„œ κ΄€λ¦¬ν•˜μ—¬, ν˜„μž¬ λ―Έμ…˜μ˜ 인덱슀λ₯Ό μ‰½κ²Œ 찾을 수 μžˆλ„λ‘ ν–ˆμŠ΅λ‹ˆλ‹€. 이 배열을 μ‚¬μš©ν•˜μ—¬, 각 λ―Έμ…˜μ˜ μ™„λ£Œ ν›„ λ‹€μŒ λ―Έμ…˜μœΌλ‘œμ˜ λΌμš°νŒ… 경둜λ₯Ό λ™μ μœΌλ‘œ 생성할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

λ―Έμ…˜ 인덱슀 관리 μ˜ˆμ‹œ μ½”λ“œ

const missionIds = useAtomValue(testerMissionsDataAtom);
const currentMissionIndex = missionIds.findIndex((id) => id === missionId);

2. μžλ™ λΌμš°νŒ… 둜직: navigateToNextMission ν•¨μˆ˜λŠ” ν˜„μž¬ λ―Έμ…˜ 인덱슀λ₯Ό 기반으둜 λ‹€μŒ λ―Έμ…˜μ˜ 인덱슀λ₯Ό κ³„μ‚°ν•©λ‹ˆλ‹€. λ§Œμ•½ λ‹€μŒ λ―Έμ…˜ μΈλ±μŠ€κ°€ λ°°μ—΄ 길이 내에 μžˆλ‹€λ©΄, ν•΄λ‹Ή λ―Έμ…˜ ID둜 λΌμš°νŒ…μ„ μ—…λ°μ΄νŠΈν•©λ‹ˆλ‹€. λͺ¨λ“  λ―Έμ…˜μ„ μ™„λ£Œν–ˆμ„ 경우, ν…ŒμŠ€ν„°λ₯Ό μ„€λ¬Έ 쑰사 νŽ˜μ΄μ§€λ‘œ λ¦¬λ””λ ‰μ…˜ν•©λ‹ˆλ‹€.

3. ν™”λ©΄ λ…Ήν™” 쀑지: 각 λ―Έμ…˜μ˜ μ™„λ£Œ μ‹œ, ν™”λ©΄ λ…Ήν™”λ₯Ό 쀑지(screenRecorder.stop())ν•˜κ³  μ„œλ²„μ— 데이터λ₯Ό μ „μ†‘ν•˜λŠ” λ‘œμ§μ„ μˆ˜ν–‰ν•©λ‹ˆλ‹€.

μžλ™ λΌμš°νŒ… 및 ν™”λ©΄ λ…Ήν™” 쀑지 μ˜ˆμ‹œ μ½”λ“œ

function navigateToNextMission() {
  const nextMissionIndex = currentMissionIndex + 1;
  if (nextMissionIndex < missionIds.length) {
    const nextMissionId = missionIds[nextMissionIndex];
    navigate(`/tester/${testerId}/mission/${nextMissionId}`);
  } else {
    screenRecorder.stop(); // ν™”λ©΄ λ…Ήν™” 쀑지
    navigate(`/tester/${testerId}/survey`);
  }
}

이 μ ‘κ·Ό 방법을 톡해 ν…ŒμŠ€ν„°κ°€ λ―Έμ…˜μ„ μ›ν™œν•˜κ²Œ μˆ˜ν–‰ν•˜κ³  λ‹€μŒ λ―Έμ…˜μœΌλ‘œ 이동할 수 μžˆλŠ” 효율적인 μ‹œμŠ€ν…œμ„ ꡬ좕할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ, μ „μ—­ 라이브러리인 Jotaiλ₯Ό 이용, 각 λ―Έμ…˜μ˜ μƒνƒœλ₯Ό 효과적으둜 관리할 수 있게 λ˜μ—ˆμŠ΅λ‹ˆλ‹€.


3-2 ν…ŒμŠ€ν„°μ˜ ν”Όλ“œλ°± λ°˜μ˜ν•˜κΈ°

3-2-1 고렀사항: ν”Όλ“œλ°± μˆ˜μ§‘κ³Ό ν™œμš©

ν…ŒμŠ€ν„°μ˜ ν”Όλ“œλ°±μ„ 효과적으둜 μˆ˜μ§‘ν•˜κ³  μ‹œμŠ€ν…œ κ°œμ„ μ— ν™œμš©ν•˜λŠ” 것이 ν•„μš”ν–ˆμŠ΅λ‹ˆλ‹€.

3-2-2 μ ‘κ·Ό 방법: ν”Όλ“œλ°± μž…λ ₯ 및 μ €μž₯

ν…ŒμŠ€ν„°λŠ” λ―Έμ…˜ μˆ˜ν–‰ μ‹œ λ―Έμ…˜μ— κ΄€ν•˜μ—¬ ν”Όλ“œλ°±μ„ 자유둭게 남길 수 μžˆλ„λ‘ μ„€κ³„ν•˜μ˜€μŠ΅λ‹ˆλ‹€. 이 ν”Όλ“œλ°±μ€ μ‚¬μš©μžμ—κ²Œ 제곡되며 ν…ŒμŠ€ν„°μ˜ μ˜κ²¬μ„ 적극 λ°˜μ˜ν•  수 있게 λ©λ‹ˆλ‹€.

ν…ŒμŠ€ν„°μ˜ ν”Όλ“œλ°±μ„ μˆ˜μ§‘ν•˜κ³  이λ₯Ό μ‹œμŠ€ν…œ κ°œμ„ μ— ν™œμš©ν•˜λŠ” μ ˆμ°¨λŠ” λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€:

1. ν”Όλ“œλ°± μž…λ ₯ μΈν„°νŽ˜μ΄μŠ€: ν…ŒμŠ€ν„°λŠ” FeedbackTextarea μ»΄ν¬λ„ŒνŠΈλ₯Ό 톡해 ν”Όλ“œλ°±μ„ μž…λ ₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€. ν”Όλ“œλ°±μ€ 둜컬 μƒνƒœ(feedback)에 μ €μž₯되며, μ €μž₯ λ²„νŠΌ 클릭 μ‹œ μ„œλ²„λ‘œ 전솑 μ€€λΉ„κ°€ μ™„λ£Œλ©λ‹ˆλ‹€.

ν”Όλ“œλ°± μž…λ ₯ μΈν„°νŽ˜μ΄μŠ€ μ˜ˆμ‹œ μ½”λ“œ

// ν”Όλ“œλ°± μž…λ ₯ μΈν„°νŽ˜μ΄μŠ€
const [feedback, setFeedback] = useState(""); // 둜컬 μƒνƒœ

function handleFeedbackChange(event) {
  setFeedback(event.target.value); // μž…λ ₯ν•œ ν”Όλ“œλ°±μ„ μƒνƒœμ— μ €μž₯
}

function handleFeedbackSubmit() {
  setIsFeedbackOpen(false); // ν”Όλ“œλ°± μž…λ ₯ μΈν„°νŽ˜μ΄μŠ€ λ‹«κΈ°
}

2. μ„œλ²„μ— ν”Όλ“œλ°± 전솑: ν”Όλ“œλ°±κ³Ό κ΄€λ ¨λœ λ°μ΄ν„°λŠ” handleNextMission 및 handleSkipMission ν•¨μˆ˜λ₯Ό 톡해 μ²˜λ¦¬λ©λ‹ˆλ‹€. 이듀 ν•¨μˆ˜λŠ” λ―Έμ…˜ μ™„λ£Œ λ˜λŠ” κ±΄λ„ˆλ›°κΈ° 데이터와 ν•¨κ»˜ ν”Όλ“œλ°±μ„ μ„œλ²„λ‘œ μ „μ†‘ν•©λ‹ˆλ‹€. 전솑 성곡 ν›„, μ‚¬μš©μžλŠ” μžλ™μœΌλ‘œ λ‹€μŒ λ―Έμ…˜μœΌλ‘œ μ΄λ™ν•˜κ±°λ‚˜ μ„€λ¬Έ 쑰사λ₯Ό μˆ˜ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ„œλ²„μ— ν”Όλ“œλ°± 전솑 μ˜ˆμ‹œ μ½”λ“œ

// μ„œλ²„μ— ν”Όλ“œλ°± 전솑
function handleNextMission() {
  const missionCompletionData = {
    completed: true,
    createdAt: new Date(new Date() - missionTime * 1000),
    completedAt: new Date(),
    duration: missionTime,
    feedback,
  };

  setFeedback(""); // ν”Όλ“œλ°± μƒνƒœ μ΄ˆκΈ°ν™”

  updateMissionData(missionCompletionData, {
    onSuccess: () => {
      navigateToNextMission(); // 전솑 성곡 μ‹œ λ‹€μŒ λ―Έμ…˜μœΌλ‘œ 이동
    },
  });
}

이 μ ‘κ·Ό 방법을 톡해 ν…ŒμŠ€ν„°μ˜ ν”Όλ“œλ°±μ„ 효과적으둜 μˆ˜μ§‘ν•˜κ³  이λ₯Ό μ‹œμŠ€ν…œ κ°œμ„ μ— λ°˜μ˜ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” μ‚¬μš©μž κ²½ν—˜μ„ λ”μš± ν–₯μƒμ‹œν‚€λŠ” 데 κΈ°μ—¬ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

4. ν…ŒμŠ€νŠΈ ν™”λ©΄ λ…Ήν™” 및 μ €μž₯

μ˜€ν”„λΌμΈμ—μ„œμ˜ λ‹€μ„―κ°€μ§€μ˜ μ„±κ³Ό μ§€ν‘œλ₯Ό κΈ°μ€€μœΌλ‘œ μ˜¨λΌμΈμ—μ„œ κ΅¬ν˜„ν•΄μ•Όν•  λ―Έμ…˜ μˆ˜ν–‰λ₯ , λ―Έμ…˜ μ†Œμš”μ‹œκ°„, 그리고 μ„œλΉ„μŠ€ λ§Œμ‘±λ„ 점수λ₯Ό κ΅¬ν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ 이 μ§€ν‘œλ“€μ€ μ–΄λ””κΉŒμ§€λ‚˜ ν…ŒμŠ€ν„°μ˜ μž¬λŸ‰μ— 맑기게 λ˜μ–΄ μ˜¨λΌμΈμƒμ—μ„œ μ™„λ²½ν•œ 데이터λ₯Ό μΆ”μΆœν•˜λŠ” λ°μ—λŠ” ν•œκ³„κ°€ μžˆλ‹€κ³  μƒκ°ν–ˆμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ UXPlorerλŠ” ν™”λ©΄λ…Ήν™” κΈ°λŠ₯을 μ œκ³΅ν•˜μ—¬ 온라인 μƒμ—μ„œ ν…ŒμŠ€ν„°λ₯Ό Observation ν•˜λŠ” μš©λ„λ‘œ μ‚¬μš©ν•˜κ³  μ„œλΉ„μŠ€ μ΄μš©μžλŠ” ν…ŒμŠ€ν„°μ˜ ν™”λ©΄ 녹화본을 좔가적인 μ •λŸ‰μ  μ§€ν‘œλ₯Ό λ³΄λŠ”λ°μ— μ‚¬μš©ν•  수 μžˆλ„λ‘ 섀계가 ν•„μš”ν–ˆμŠ΅λ‹ˆλ‹€.

4-1 ν…ŒμŠ€νŠΈ ν™”λ©΄μ˜ λ…Ήν™” 데이터 μ €μž₯ν•˜κΈ°

4-1-1 고렀사항: μ•ˆμ •μ μΈ λ―Έλ””μ–΄ 슀트림 μˆ˜μ§‘κ³Ό μ €μž₯

ν…ŒμŠ€νŠΈ ν™”λ©΄ λ…Ήν™” κΈ°λŠ₯을 κ΅¬ν˜„ν•˜κΈ° μœ„ν•΄μ„œλŠ” μ•ˆμ •μ μΈ λ―Έλ””μ–΄ 슀트림 μˆ˜μ§‘κ³Ό μ €μž₯ 방식이 ν•„μš”ν–ˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ ν™”λ©΄ λ…Ήν™” 데이터λ₯Ό 효율적으둜 μ„œλ²„μ— μ „μ†‘ν•˜κ³  μ €μž₯ν•  수 μžˆλŠ” 방법을 μ°Ύμ•„μ•Ό ν–ˆμŠ΅λ‹ˆλ‹€.

4-1-2 μ ‘κ·Ό 방법: MediaDevice μΈν„°νŽ˜μ΄μŠ€ μ‚¬μš©

크둬 λΈŒλΌμš°μ € κΈ°μ€€μœΌλ‘œ νŠΉμ • νƒ­μ˜ ν™”λ©΄ λ…Ήν™” κΈ°λŠ₯을 κ΅¬ν˜„ν•˜κΈ° μœ„ν•΄μ„œ MediaDevice μΈν„°νŽ˜μ΄μŠ€μ˜ getMediaDisplay λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜μ˜€μŠ΅λ‹ˆλ‹€. MediaDevice μΈν„°νŽ˜μ΄μŠ€λŠ” λ―Έλ””μ–΄ 데이터λ₯Ό μ œκ³΅ν•˜λŠ” λͺ¨λ“  ν•˜λ“œμ›¨μ–΄λ‘œ μ ‘κ·Όν•  수 μžˆλŠ” 방법을 μ œκ³΅ν•΄μ€λ‹ˆλ‹€.

1. ν™”λ©΄ λ…Ήν™” μ‹œμž‘

const stream = await navigator.mediaDevices.getDisplayMedia({
  video: true,
  preferCurrentTab: true,
});

ν…ŒμŠ€ν„°μ˜ ν™”λ©΄ λ…Ήν™”λ₯Ό μ‹œμž‘ν•˜λŠ” μ½”λ“œμž…λ‹ˆλ‹€. getDisplayMedia() λ©”μ„œλ“œλŠ” μ‚¬μš©μžμ˜ 전체 ν™”λ©΄ λ˜λŠ” νŠΉμ • νƒ­μ˜ λ―Έλ””μ–΄ μŠ€νŠΈλ¦Όμ„ λ°˜ν™˜ν•΄μ€λ‹ˆλ‹€. μ—¬κΈ°μ„œλŠ” preferCurrentTab: true μ˜΅μ…˜μ„ μ‚¬μš©ν•΄ ν˜„μž¬ ν™œμ„± νƒ­λ§Œ λ…Ήν™”ν•˜λ„λ‘ μ„€μ •ν–ˆμŠ΅λ‹ˆλ‹€.

2. λ―Έλ””μ–΄ λ°μ΄ν„°μ˜ μˆ˜μ§‘

const recorder = new MediaRecorder(stream, { mimeType: "video/webm" });
const chunks = [];
recorder.ondataavailable = (e) => chunks.push(e.data);

MediaRecorder 객체λ₯Ό μ΄μš©ν•˜μ—¬ λ…Ήν™”λœ μŠ€νŠΈλ¦Όμ„ μ²˜λ¦¬ν•˜λ„λ‘ κ΅¬ν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€. 이 κ°μ²΄λŠ” μ„€μ •λœ mimeType에 따라 데이터λ₯Ό μΈμ½”λ”©ν•˜κ³ , ondataavailable 이벀트λ₯Ό 톡해 λ…Ήν™” 데이터λ₯Ό chunks 배열에 μ €μž₯ν•©λ‹ˆλ‹€.

3. λ…Ήν™” μ’…λ£Œ 및 데이터 처리

recorder.onstop = async () => {
  const blob = new Blob(chunks, { type: "video/mp4" });
  const formData = new FormData();
  formData.append("video", blob, `${fileName}.mp4`);
};

λ…Ήν™”κ°€ μ’…λ£Œλ˜λ©΄ (recorder.onstop 이벀트), chunks 배열에 μ €μž₯된 λΉ„λ””μ˜€ λ°μ΄ν„°λŠ” Blob 객체둜 λ³€ν™˜λ˜λ©°, μ΄λŠ” μ„œλ²„λ‘œ 전솑 μ€€λΉ„λ₯Ό μœ„ν•΄ FormData 객체에 μΆ”κ°€λ©λ‹ˆλ‹€.

4. λ¦¬μ†ŒμŠ€ 정리

stream.getTracks().forEach((track) => track.stop());

λ…Ήν™”κ°€ μ™„λ£Œλœ ν›„μ—λŠ” μ‚¬μš©λœ λͺ¨λ“  λ―Έλ””μ–΄ νŠΈλž™μ„ μ’…λ£Œν•©λ‹ˆλ‹€.

이 μ ‘κ·Ό 방법을 톡해 μ•ˆμ •μ μ΄κ³  효율적인 ν™”λ©΄ λ…Ήν™” κΈ°λŠ₯을 κ΅¬ν˜„ν•  수 μžˆμ—ˆμœΌλ©°, λ…Ήν™” 데이터λ₯Ό μ•ˆμ •μ μœΌλ‘œ μ„œλ²„μ— μ €μž₯ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό 톡해 온라인 μƒμ—μ„œ 더 μ •ν™•ν•œ μ‚¬μš©μ„± ν…ŒμŠ€νŠΈ 데이터λ₯Ό μˆ˜μ§‘ν•  수 있게 되고 κ·Έ κ·Όκ±°λ₯Ό λ’·λ°›μΉ¨ ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

4-2 λ‹€μ–‘ν•œ ν”Œλž«νΌμ—μ„œ λ―Έλ””μ–΄μ˜ ν˜Έν™˜μ„± 보μž₯ν•˜κΈ°

4-2-1 고렀사항: λ―Έλ””μ–΄ 파일 ν˜•μ‹ 선택

ν™”λ©΄ λ…Ήν™” κΈ°λŠ₯ κ΅¬ν˜„ μ‹œ λ―Έλ””μ–΄ 파일 ν˜•μ‹μ˜ 선택은 λ‹€μ–‘ν•œ ν”Œλž«νΌμ—μ„œ ν˜Έν™˜μ„±μ„ 보μž₯ν•˜λŠ” 데 μ€‘μš”ν•œ 역할을 ν•œλ‹€κ³  μƒκ°ν–ˆμŠ΅λ‹ˆλ‹€. λŒ€ν‘œμ μœΌλ‘œ MP4와 WebM이 μžˆλŠ”λ°, 두 파일 ν˜•μ‹μ˜ 기술적 이해가 ν•„μš”ν–ˆμŠ΅λ‹ˆλ‹€.

4-2-2 μ ‘κ·Ό 방법: WebMκ³Ό MP4 비ꡐ

1. WebMκ³Ό MP4의 비ꡐ

  • WebM: Google에 μ˜ν•΄ 개발된 μ˜€ν”ˆ μ†ŒμŠ€ λΉ„λ””μ˜€ 포맷으둜, 주둜 VP8 및 VP9 λΉ„λ””μ˜€ 코덱과 Vorbis λ˜λŠ” Opus μ˜€λ””μ˜€ 코덱을 μ‚¬μš©ν•©λ‹ˆλ‹€. WebM은 특히 HTML5 μ›Ή λΉ„λ””μ˜€ ν”Œλ ˆμ΄μ–΄μ—μ„œ 널리 μ§€μ›λ˜λ©°, μ›Ή 기반 μŠ€νŠΈλ¦¬λ°μ— μ΅œμ ν™”λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.
  • MP4: H.264/AVC λ˜λŠ” H.265/HEVC λΉ„λ””μ˜€ 코덱과 AAC μ˜€λ””μ˜€ 코덱을 μ‚¬μš©ν•˜λŠ” 보닀 λ²”μš©μ μΈ λ―Έλ””μ–΄ μ»¨ν…Œμ΄λ„ˆ ν¬λ§·μž…λ‹ˆλ‹€. MP4λŠ” 거의 λͺ¨λ“  μ‹œμŠ€ν…œ 및 μž₯μΉ˜μ—μ„œ 널리 μ§€μ›λ˜λ©°, κ³ ν’ˆμ§ˆ 슀트리밍 및 μ €μž₯ μ˜΅μ…˜μ„ μ œκ³΅ν•©λ‹ˆλ‹€.

2. ν”Œλž«νΌ 및 λ””λ°”μ΄μŠ€ ν˜Έν™˜μ„±

  • λΈŒλΌμš°μ € ν˜Έν™˜μ„±: WebM은 주둜 Firefox, Chrome 및 Opera와 같은 μ›Ή λΈŒλΌμš°μ €μ—μ„œ 널리 μ§€μ›λ˜μ§€λ§Œ, Safari와 일뢀 λͺ¨λ°”일 λΈŒλΌμš°μ €μ—μ„œλŠ” μ œν•œμ μΈ 지원을 λ°›μŠ΅λ‹ˆλ‹€. 반면, MP4 (특히 H.264 코덱 μ‚¬μš© μ‹œ)λŠ” 거의 λͺ¨λ“  μ£Όμš” λ°μŠ€ν¬νƒ‘ 및 λͺ¨λ°”일 λΈŒλΌμš°μ €μ—μ„œ μ§€μ›λ©λ‹ˆλ‹€.

  • μ €μž₯ 및 λ‹€μš΄λ‘œλ“œ κ°€λŠ₯μ„±: ν΄λΌμš°λ“œ μŠ€ν† λ¦¬μ§€ μ„œλΉ„μŠ€ 및 μ½˜ν…μΈ  전솑 λ„€νŠΈμ›Œν¬μ—μ„œ MP4 νŒŒμΌμ€ ν‘œμ€€μ μΈ λΉ„λ””μ˜€ ν˜•μ‹μœΌλ‘œ κ°„μ£Όλ˜μ–΄ λ‹€μš΄λ‘œλ“œ 및 슀트리밍이 μš©μ΄ν•©λ‹ˆλ‹€. Amazon S3와 같은 μ„œλΉ„μŠ€μ—μ„œλŠ” MP4 νŒŒμΌμ„ μš°μ„ μ μœΌλ‘œ μ§€μ›ν•˜λ©°, μ΄λŠ” WebM ν˜•μ‹μ— λΉ„ν•΄ μ ‘κ·Όμ„± 및 ν˜Έν™˜μ„± λ©΄μ—μ„œ 큰 이점을 μ œκ³΅ν•©λ‹ˆλ‹€.

3. 기술적 κ΅¬ν˜„

μ½”λ“œ μ˜ˆμ‹œμ—μ„œ getDisplayMedia λ₯Ό μ‚¬μš©ν•˜μ—¬ λ…Ήν™”λœ μŠ€νŠΈλ¦Όμ„ video/webm 둜 초기 μ„€μ •ν•˜μ˜€μœΌλ‚˜, μ΅œμ’…μ μœΌλ‘œλŠ” Blob 생성 μ‹œ video/mp4 ν˜•μ‹μœΌλ‘œ λ³€ν™˜ν•˜μ—¬ μ €μž₯ν•˜λ„λ‘ κ΅¬ν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€. μ΄λŠ” MP4 ν˜•μ‹μ˜ λ²”μš©μ„±κ³Ό ν˜Έν™˜μ„±μ„ ν™œμš©ν•˜κΈ° μœ„ν•¨μž…λ‹ˆλ‹€. μ„œλ²„λ‘œ μ „μ†‘λœ ν›„, MP4 νŒŒμΌμ€ λ‹€μ–‘ν•œ μž₯치 및 ν”Œλž«νΌμ—μ„œ 문제 없이 μž¬μƒλ  수 있으며, ν•„μš”ν•œ 경우 λ‹€λ₯Έ ν˜•μ‹μœΌλ‘œμ˜ μΆ”κ°€ λ³€ν™˜ 없이 λ°”λ‘œ μ‚¬μš©λ  수 μžˆλ„λ‘ ν•˜κΈ° μœ„ν•¨μ΄μ—ˆμŠ΅λ‹ˆλ‹€.

이 μ ‘κ·Ό 방법을 톡해 λ‹€μ–‘ν•œ ν”Œλž«νΌμ—μ„œ ν˜Έν™˜μ„±μ΄ 보μž₯λ˜λŠ” λ―Έλ””μ–΄ 파일 ν˜•μ‹μ„ μ‚¬μš©ν•˜μ—¬ μ•ˆμ •μ μΈ ν™”λ©΄ λ…Ήν™” κΈ°λŠ₯을 μ œκ³΅ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό 톡해 더 λ§Žμ€ μ‚¬μš©μžμ—κ²Œ μΌκ΄€λœ κ²½ν—˜μ„ μ œκ³΅ν•  수 있게 λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

Tech Stacks

Client

JavaScript React Tanstack Query Jotai Styled-Components

Server

JavaScript NodeJS Express.js MongoDB & Mongoose

Test

React Dom Testing Vitest

Deployment

Netlify AWS Elastic Beanstalk



Schedule

ν”„λ‘œμ νŠΈ κΈ°κ°„: 2024.03.04(μ›”) ~ 2024.03.27(수)


1 μ£Όμ°¨: 아이디어 μ„ μ •, POC, λͺ©μ—… 및 DBμŠ€ν‚€λ§ˆ 섀계


2 - 3μ£Όμ°¨: ν΄λΌμ΄μ–ΈνŠΈ κ΅¬ν˜„ 및 μ„œλ²„ κ΅¬ν˜„

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors