πŸ“Œβ€œμ‚¬μš©μž κ²½ν—˜μ„ λŒμ–΄μ˜¬λ¦° μ§„λ„μœ¨ Ajax μ—…λ°μ΄νŠΈ μ»¨νŠΈλ‘€λŸ¬β€

πŸ—“ 2025-07-02 ✍️ 박찬희

πŸ”§ κ΅¬ν˜„ λͺ©ν‘œ

κΈ°μ‘΄ ν•™μŠ΅ μ½˜ν…μΈ μ—μ„œ μ‚¬μš©μžμ˜ μ§„λ„μœ¨(progress)을 μ €μž₯ν•  λ•Œλ§ˆλ‹€ 전체 νŽ˜μ΄μ§€κ°€ λ¦¬λ‘œλ“œλ˜λŠ” 방식은

UX κ΄€μ μ—μ„œ λ„ˆλ¬΄λ‚˜ κ³ ν†΅μŠ€λŸ¬μ› λ‹€.

ν•™μŠ΅μ΄ μ™„λ£Œλ  λ•Œλ§ˆλ‹€ νŽ˜μ΄μ§€κ°€ λ‹€μ‹œ 뜨고, 수료 체크가 λ”œλ ˆμ΄λ˜λŠ” ꡬ쑰.

λ‚˜λŠ” 이 문제λ₯Ό λ‹¨λ²ˆμ— μ—†μ• κ³  μ‹Άμ—ˆλ‹€.

κ·Έλž˜μ„œ μ΄λ²ˆμ— μƒˆλ‘œ μ„€κ³„ν•œ 것이

/lecture/progress/update 경둜둜 μž‘λ™ν•˜λŠ” Ajax 기반 μ§„λ„μœ¨ μ—…λ°μ΄νŠΈ μ»¨νŠΈλ‘€λŸ¬λ‹€.


πŸ“Œ μ£Όμš” μ‹œλ„: ν•œ μš”μ²­μ— λ‹΄κΈ΄ μΌκ΄€λœ 흐름

이 흐름은 λ‹¨μˆœνžˆ 기술적인 κ΅¬ν˜„μ„ λ„˜μ–΄μ„œ

β€œμ‚¬μš©μžκ°€ ν•™μŠ΅ν•˜λŠ” λ™μ•ˆ μ‹œμŠ€ν…œμ€ 쑰용히, μ •ν™•ν•˜κ²Œ λ°˜μ‘ν•΄μ•Ό ν•œλ‹€β€λŠ” 철학을 κ΅¬ν˜„ν•œ 흐름이닀.


πŸ“ ꡬ쑰 μš”μ•½

계측 클래슀/파일 μ—­ν• 
Controller ProgressAjaxController Ajax μš”μ²­ 핸듀링, 응닡 포맷 처리
Service ProgressService μ§„λ„μœ¨ μ €μž₯ 및 수료 μƒνƒœ 전이
DAO/Mapper ProgressMapper (MyBatis) DB Upsert, 수료 μ—¬λΆ€ 검사 λ“±

πŸš€ κ΅¬ν˜„ 흐름 상세

1️⃣ 둜그인 검증 – λ³΄μ•ˆμ€ κ°€μž₯ λ¨Όμ €

HttpSession session = req.getSession();
UserDTO user = (UserDTO) session.getAttribute("user");

if (user == null) {
    resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    result.put("success", false);
    result.put("message", "둜그인이 ν•„μš”ν•©λ‹ˆλ‹€.");
    ...
}

πŸ“ 싀무 팁: Ajax μš”μ²­μΌμˆ˜λ‘ λ³΄μ•ˆ μ²˜λ¦¬λŠ” λ”μš± 엄격해야 ν•œλ‹€.


2️⃣ JSON λ°”λ”” νŒŒμ‹± – 직접 읽고, 직접 ν™•μΈν–ˆλ‹€

BufferedReader reader = req.getReader();
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
    sb.append(line);
}
Map<String, Object> data = gson.fromJson(sb.toString(), Map.class);
int contentId = ((Double) data.get("contentId")).intValue();
int progress = ((Double) data.get("progress")).intValue();

πŸ“ μ‹€μ „μ—μ„œ μ€‘μš”ν•œ 건 νƒ€μž… μ•ˆμ •μ„±μ΄λ‹€.

ν”„λ‘ νŠΈμ—”λ“œλŠ” float을 보내고, λ°±μ—”λ“œλŠ” intλ₯Ό λ°›λŠ”λ‹€κ³  μ°©κ°ν•˜λ©΄β€¦ 500은 예고 없이 λ‚ μ•„μ˜¨λ‹€.


3️⃣ μ§„λ„μœ¨ μ €μž₯ – upsert + 수료 μ²΄ν¬κΉŒμ§€ ν•œ λ²ˆμ—

progressService.saveOrUpdateProgress(user.getUserId(), contentId, progress);

πŸ“ 핡심은 μ»¨νŠΈλ‘€λŸ¬λŠ” νλ¦„λ§Œ μ•Œκ³ , 도메인 μƒνƒœλŠ” μ„œλΉ„μŠ€κ°€ μ±…μž„μ§„λ‹€.


4️⃣ JSON 응닡 – 일관성 μžˆλŠ” 성곡/μ‹€νŒ¨ ꡬ쑰

result.put("success", true);
result.put("message", "μ§„λ„μœ¨ μ—…λ°μ΄νŠΈ 성곡");
resp.getWriter().write(gson.toJson(result));

πŸ“ 이런 ꡬ쑰 덕뢄에 ν”„λ‘ νŠΈλŠ” μ‹€νŒ¨ μΌ€μ΄μŠ€λ„ UI둜 λͺ…ν™•ν•˜κ²Œ μ²˜λ¦¬ν•  수 μžˆλ‹€.


πŸ§ͺ ν…ŒμŠ€νŠΈ μ‹œλ‚˜λ¦¬μ˜€

ν…ŒμŠ€νŠΈ 상황 κΈ°λŒ€ λ™μž‘
βœ… 둜그인 μ‚¬μš©μž 200 OK + { success: true }
❌ λΉ„λ‘œκ·ΈμΈ μƒνƒœ 401 Unauthorized + "둜그인이 ν•„μš”ν•©λ‹ˆλ‹€."
❌ JSON νŒŒλΌλ―Έν„° λˆ„λ½ 500 + 였λ₯˜ λ©”μ‹œμ§€
βœ… 수료 쑰건 만쑱 markEnrollmentComplete() 호좜됨

🚧 νŠΈλŸ¬λΈ”μŠˆνŒ… & κ³ λ―Ό

πŸ” 문제: JSμ—μ„œ 보낸 μˆ«μžκ°€ 자꾸 Double둜 λ“€μ–΄μ˜¨λ‹€

β†’ ν•΄κ²°: ((Double) obj).intValue() λͺ…μ‹œ λ³€ν™˜

β†’ 원인: JS numberλŠ” float이며, Gson은 λͺ¨λ“  숫자λ₯Ό Double둜 처리


🧠 배운 점


✍️ 회고

β€œλ‹¨μˆœ 진도 μ €μž₯μ΄μ—ˆμ§€λ§Œ, 이 μ»¨νŠΈλ‘€λŸ¬λŠ” μ‚¬μš©μž κ²½ν—˜κ³Ό 도메인 μƒνƒœ 전이λ₯Ό μ™„μ „νžˆ μžλ™ν™”ν•œ μ „ν™˜μ μ΄μ—ˆλ‹€.”

이 κ΅¬μ‘°λŠ” 이후 λ‹€μŒμ„ μœ„ν•œ 기반이 λœλ‹€: