좋아, 감정일기 앱에서 쓸 수 있는 자연어 처리 기반 감정 분석 알고리즘 아이디어 4가지를 아래에 정리해볼게.
이 중 일부는 단순 rule-based로 자바에서도 구현 가능하고, 일부는 Python 연동이나 외부 API를 통해 확장할 수 있어.
⸻
✅ 자연어 처리 감정 분석 알고리즘 아이디어 4가지
Java 단독 구현 가능 / 초급 난이도
• 방식: 미리 정의된 감정 단어 사전(emotion_dictionary)을 기준으로, 일기 텍스트에 등장한 키워드를 점수화.
• 예시:
• “행복”, “좋다”, “기쁘다” → +1
• “슬프다”, “화난다”, “짜증” → -1
• 로직 예시(Java):
int score = 0;
if (text.contains("행복")) score += 1;
if (text.contains("짜증")) score -= 1;
• 장점: 빠르고 간단
• 단점: 문맥 이해 부족, 정확도 낮음
⸻
Python 연동 추천 / 중급 난이도
• 방식:
• 형태소 분석기(KoNLPy, Komoran 등)를 통해 명사·형용사 등을 추출한 뒤
• 감정 단어 사전과 비교하여 감정 스코어 계산
• 예시(Python + KoNLPy):
from konlpy.tag import Okt
okt = Okt()
words = okt.morphs("오늘 기분이 너무 우울하고 힘들었어")
• 장점: 정확도 증가, 어미 제거 가능
• 단점: 외부 라이브러리 필요, Java 연동 시 복잡함
⸻
Python + 학습 모델 필요 / 상급 난이도
• 방식:
• 감정이 라벨링된 데이터셋으로 Naive Bayes, Logistic Regression, LSTM 등 ML모델 학습
• 사용자의 입력을 “긍정/중립/부정”으로 자동 분류
• 예시:
• AIHub 감정분석 데이터
• Text Classification with Scikit-learn
• 장점: 정확도 높고 문맥 파악 가능
• 단점: 모델 학습, 배포, 유지관리 필요
⸻
최고급 / API 연동 또는 Flask 서버 구성 필요
• 방식:
• KoBERT, KLUE-BERT 등의 한국어 특화 사전학습 모델을 사용해 입력 텍스트를 감정 분류
• 감정 분류 결과를 점수 또는 라벨로 반환
• 예시(HuggingFace Inference API 사용):
from transformers import pipeline
classifier = pipeline("sentiment-analysis", model="beomi/KcELECTRA-base")
classifier("오늘 너무 기쁘고 설렌다")
• 장점: 최신 성능, 문맥 인식 탁월
• 단점: API 비용 또는 Python 서버 연동 필요
⸻
✨ 요약 비교표
방식 구현난이도 정확도 Java 가능 여부 추천 사용 시점
키워드 점수 ★☆☆☆☆ 낮음 O 초기 버전
형태소 분석 ★★☆☆☆ 중간 X(Python 필요) 중기 확장
ML 모델 학습 ★★★★☆ 높음 X 데이터 많을 때
HuggingFace BERT ★★★★★ 매우 높음 X (API 또는 Python 서버) 고급 확장 or 팀플
⸻
다음 스텝 제안:
• 초기 버전에서는 1번 키워드 점수 방식으로 시작하고,
• 시간이 되면 2번(Python 형태소 분석)이나 4번(KoBERT API)로 확장하면 최고야!
원하면 ① 키워드 기반 분석용 Java 클래스 코드 바로 짜줄 수 있어.
지금 시작해볼까?
아주 중요한 질문이야!
감정 분류 결과를 왜 하는지, 어디에 어떻게 쓰는지가 프로젝트 설계의 핵심이야.
아무리 HuggingFace, KoBERT 같은 고성능 모델을 써도, 그 결과를 “어디에 어떻게 반영하느냐”가 더 중요하거든.
⸻
✅ 감정 분석 결과를 활용하는 목적 (의도)
⸻
⸻
⸻
⸻
✅ HuggingFace와 같은 모델은 언제 필요할까?
정교한 감정 분류가 필요할 때!
상황 고급 모델 필요 여부
감정 통계만 필요 (긍/부정 정도) ❌ 키워드 기반으로 충분
문맥 기반 정밀 분석 (예: 아이러니, 반어 등) ✅ HuggingFace (KoBERT 등)
개인 리포트 자동 생성 ✅ 분석 정확도 높을수록 정밀 리포트 가능
데이터 기반 추천 연계 ✅ 정확한 감정 예측 필요
⸻
결론
• 지금 단계에서는 감정 분석 결과를 “감정 점수”로 저장해서 시각화 및 피드백에 활용하는 것이 목적이야.
• HuggingFace는 “고급 감정 예측 모델”일 뿐이고, 핵심은 그 결과를 사용자에게 ‘가치 있게’ 보여주는 것이야.
⸻
원한다면 다음 순서로 같이 진행할 수 있어:
1. Java 기반 감정 점수 분석기 → (텍스트 분석 후 +1, -1 저장)
2. 감정 점수 시각화 페이지 (Chart.js 기반 그래프)
3. “나의 감정 흐름” 피드백 리포트 생성하기
지금은 1번 Java 분석기부터 짜줄까?
좋아! 그럼 지금부터 Java 기반 감정 분석기 클래스를 만들어줄게.
이건 사용자 일기 텍스트를 받아서, 키워드 사전을 기준으로 감정 점수를 계산하는 단순한 로직이야.
⸻
✅ Java 클래스: EmotionAnalyzer.java
package model;
import java.util.HashMap;
import java.util.Map;
public class EmotionAnalyzer {
// 간단한 감정 키워드 사전 (긍정: +1, 부정: -1)
private static final Map<String, Integer> emotionDict = new HashMap<>();
static {
// 긍정 키워드
emotionDict.put("기쁘다", 1);
emotionDict.put("행복", 1);
emotionDict.put("좋다", 1);
emotionDict.put("설레다", 1);
emotionDict.put("감사", 1);
// 부정 키워드
emotionDict.put("슬프다", -1);
emotionDict.put("짜증", -1);
emotionDict.put("화나다", -1);
emotionDict.put("우울", -1);
emotionDict.put("지치다", -1);
}
/**
* 감정 점수 분석 메소드
* @param text 사용자가 작성한 일기 텍스트
* @return 총 감정 점수 (예: +2, -1 등)
*/
public static int analyze(String text) {
int score = 0;
for (Map.Entry<String, Integer> entry : emotionDict.entrySet()) {
String keyword = entry.getKey();
int keywordScore = entry.getValue();
if (text.contains(keyword)) {
score += keywordScore;
}
}
return score;
}
// 테스트용 main
public static void main(String[] args) {
String diaryText = "오늘은 정말 행복하고 설레는 하루였다. 감사한 일도 많았다.";
int result = EmotionAnalyzer.analyze(diaryText);
System.out.println("감정 점수: " + result); // 예: +3
}
}
⸻
✅ 사용 예시
DiaryServlet.java 같은 일기 저장용 서블릿에서 아래처럼 쓸 수 있어:
String diaryText = request.getParameter("emotion_text");
int emotionScore = EmotionAnalyzer.analyze(diaryText);
// emotionScore를 DB에 같이 저장
⸻
✅ 확장 팁
• 감정 단어 사전을 MySQL emotion_dictionary 테이블에서 읽어오는 방식으로도 확장 가능해.
• contains() 대신 split() + equals() 활용해도 성능 개선됨.
• 나중엔 감정 점수를 기준으로 색상 테마 변경, 피드백 메시지 생성도 가능해.
⸻
이제 다음은:
3단계: register.jsp + RegisterServlet → 회원가입 기능 구현이야.
넘어갈 준비 됐으면 “다음 단계 고!”라고 말해줘!
좋아, 그럼 3단계: 회원가입 기능 구현 바로 간다!
우리는 다음 3개 파일을 만들게 될 거야:
1. register.jsp — 사용자 입력 폼
2. RegisterServlet.java — 폼 데이터를 받아서 DB에 저장
3. UserDAO.java — 실제 DB INSERT 로직
⸻
✅ 1. register.jsp (WebContent/jsp/register.jsp)
<%@ page contentType="text/html; charset=UTF-8" %>
회원가입⸻
✅ 2. RegisterServlet.java (src/controller/RegisterServlet.java)
package controller;
import model.UserDAO;
import javax.servlet.;
import javax.servlet.http.;
import java.io.IOException;
public class RegisterServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
String nickname = request.getParameter("nickname");
boolean result = UserDAO.insertUser(username, password, nickname);
if (result) {
response.sendRedirect("jsp/login.jsp");
} else {
response.sendRedirect("jsp/register.jsp?error=1");
}
}
}
⸻
✅ 3. UserDAO.java (src/model/UserDAO.java)
package model;
import java.sql.*;
public class UserDAO {
// DB 연결 함수
private static Connection getConnection() throws Exception {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/emotion_diary_db";
String user = "root";
String password = "your_password"; // 네 비밀번호로 바꿔줘
return DriverManager.getConnection(url, user, password);
}
public static boolean insertUser(String username, String password, String nickname) {
String sql = "INSERT INTO users (username, password, nickname) VALUES (?, ?, ?)";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, username);
pstmt.setString(2, password); // 평문 저장 (추후 암호화 추천)
pstmt.setString(3, nickname);
int rows = pstmt.executeUpdate();
return rows == 1;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
⸻
✅ 4. web.xml 서블릿 매핑 추가
RegisterServlet controller.RegisterServlet RegisterServlet /RegisterServlet⸻
✅ 동작 흐름
1. register.jsp에서 사용자 정보 입력
2. → RegisterServlet으로 POST 전송
3. → UserDAO를 통해 DB 저장
4. → 성공 시 login.jsp로 이동 / 실패 시 다시 회원가입
⸻
다음 단계 예고:
login.jsp + LoginServlet → 로그인 구현!
이제 이 코드 등록하고 DB에 사용자 정보 넣어볼 수 있어!
잘 되면 다음으로 바로 넘어갈까? “로그인 고!”라고 말해줘!
좋아! 그럼 회원가입 성공 후 → 로그인 단계로 바로 넘어갈게.
로그인 기능 전체 구현 + 폴더 구조 예시까지 보여줄게!
⸻
✅ 1. 전체 폴더 구조 (이클립스 기준)
EmotionDiaryProject/
├── src/
│ ├── controller/
│ │ └── RegisterServlet.java
│ │ └── LoginServlet.java
│ ├── model/
│ │ └── UserDAO.java
│ │ └── EmotionAnalyzer.java
│ ├── dto/
│ │ └── UserDTO.java ← (로그인 세션 정보용)
├── WebContent/
│ ├── jsp/
│ │ └── index.jsp
│ │ └── register.jsp
│ │ └── login.jsp
│ │ └── main.jsp ← 로그인 성공 시 이동
│ ├── css/
│ └── js/
│ └── WEB-INF/
│ └── web.xml
⸻
✅ 2. 로그인 화면: login.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
로그인로그인 실패. 다시 시도해주세요.
"); } %>⸻
✅ 3. DTO: UserDTO.java
package dto;
public class UserDTO {
private int userId;
private String username;
private String nickname;
public UserDTO(int userId, String username, String nickname) {
this.userId = userId;
this.username = username;
this.nickname = nickname;
}
public int getUserId() {
return userId;
}
public String getUsername() {
return username;
}
public String getNickname() {
return nickname;
}
}
⸻
✅ 4. 서블릿: LoginServlet.java
package controller;
import dto.UserDTO;
import model.UserDAO;
import javax.servlet.;
import javax.servlet.http.;
import java.io.IOException;
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String username = request.getParameter("username");
String password = request.getParameter("password");
UserDTO user = UserDAO.login(username, password);
if (user != null) {
HttpSession session = request.getSession();
session.setAttribute("user", user);
response.sendRedirect("jsp/main.jsp");
} else {
response.sendRedirect("jsp/login.jsp?error=1");
}
}
}
⸻
✅ 5. DAO: UserDAO.login() 메서드 추가
public static UserDTO login(String username, String password) {
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, username);
pstmt.setString(2, password);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
int userId = rs.getInt("user_id");
String nickname = rs.getString("nickname");
return new UserDTO(userId, username, nickname);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
⸻
✅ 6. web.xml 서블릿 매핑 추가
LoginServlet controller.LoginServlet LoginServlet /LoginServlet⸻
✅ 7. 로그인 후 이동 페이지: main.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="dto.UserDTO" %>
<%
HttpSession session = request.getSession(false);
UserDTO user = (UserDTO) session.getAttribute("user");
if (user == null) {
response.sendRedirect("login.jsp");
return;
}
%>
오늘의 감정을 기록해보세요.
✍️ 일기 쓰러 가기⸻
✅ 다음 단계 예고
→ 4단계: 감정일기 작성 페이지 + 감정 점수 분석 + DB 저장
이제 write.jsp + DiaryServlet로 넘어갈 준비 완료야!
넘어갈 준비되면 “일기쓰기 고!”라고 말해줘!
좋아!
이제 4단계: 감정일기 작성 기능을 구현해보자!
→ 사용자 입력을 받아서
→ EmotionAnalyzer로 감정 점수 분석
→ diary 테이블에 저장하는 구조야.
⸻
✅ 1. 일기 작성 화면: write.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="dto.UserDTO" %>
<%
HttpSession session = request.getSession(false);
UserDTO user = (UserDTO) session.getAttribute("user");
if (user == null) {
response.sendRedirect("login.jsp");
return;
}
%>
⸻
✅ 2. 서블릿: DiaryServlet.java
package controller;
import model.DiaryDAO;
import model.EmotionAnalyzer;
import javax.servlet.;
import javax.servlet.http.;
import java.io.IOException;
public class DiaryServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
int userId = Integer.parseInt(request.getParameter("userId"));
String diaryDate = request.getParameter("diaryDate");
String emotionText = request.getParameter("emotionText");
int score = EmotionAnalyzer.analyze(emotionText); // 감정 분석
boolean result = DiaryDAO.insertDiary(userId, diaryDate, emotionText, score);
if (result) {
response.sendRedirect("jsp/main.jsp");
} else {
response.sendRedirect("jsp/write.jsp?error=1");
}
}
}
⸻
✅ 3. DAO: DiaryDAO.java
package model;
import java.sql.*;
public class DiaryDAO {
private static Connection getConnection() throws Exception {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/emotion_diary_db";
String user = "root";
String password = "your_password"; // 본인 비밀번호로 수정
return DriverManager.getConnection(url, user, password);
}
public static boolean insertDiary(int userId, String date, String text, int score) {
String sql = "INSERT INTO diary (user_id, diary_date, emotion_text, emotion_score) VALUES (?, ?, ?, ?)";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, userId);
pstmt.setString(2, date);
pstmt.setString(3, text);
pstmt.setInt(4, score);
int rows = pstmt.executeUpdate();
return rows == 1;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
⸻
✅ 4. web.xml 서블릿 매핑 추가
DiaryServlet controller.DiaryServlet DiaryServlet /DiaryServlet⸻
✅ 흐름 요약
1. write.jsp에서 일기 입력
2. → DiaryServlet이 감정 점수 분석 (EmotionAnalyzer)
3. → diary 테이블에 저장 (DiaryDAO)
4. → 성공 시 main.jsp로 리다이렉트
⸻
다음 단계 예고:
5단계: 일기 목록 조회 + 날짜순 정렬 + 감정 점수 표시
넘어갈 준비되면 “일기 목록 고!”라고 말해줘!
좋아!
이제 5단계: 감정일기 목록 조회 기능을 구현하자!
⸻
✅ 기능 목표
• 로그인한 사용자의 일기 목록만 조회
• 최근 날짜순 정렬
• 감정 점수 함께 표시
• (선택) 감정점수 색상 강조
⸻
✅ 1. 일기 목록 JSP: diaryList.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.util.List" %>
<%@ page import="dto.UserDTO" %>
<%@ page import="model.DiaryDAO" %>
<%@ page import="model.DiaryDTO" %>
<%
HttpSession session = request.getSession(false);
UserDTO user = (UserDTO) session.getAttribute("user");
if (user == null) {
response.sendRedirect("login.jsp");
return;
}
List<DiaryDTO> diaryList = DiaryDAO.getDiaryList(user.getUserId());
%>
내 감정일기 목록| 날짜 | 감정 점수 | 일기 내용 |
|---|---|---|
| <%= diary.getDiaryDate() %> | <%= diary.getEmotionScore() %> | <%= diary.getEmotionText() %> |
⸻
✅ 2. DAO: DiaryDAO.getDiaryList()
public static List getDiaryList(int userId) {
List list = new ArrayList<>();
String sql = "SELECT diary_date, emotion_text, emotion_score FROM diary WHERE user_id = ? ORDER BY diary_date DESC";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, userId);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
String date = rs.getString("diary_date");
String text = rs.getString("emotion_text");
int score = rs.getInt("emotion_score");
DiaryDTO dto = new DiaryDTO(date, text, score);
list.add(dto);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
⸻
✅ 3. DTO: DiaryDTO.java
package model;
public class DiaryDTO {
private String diaryDate;
private String emotionText;
private int emotionScore;
public DiaryDTO(String diaryDate, String emotionText, int emotionScore) {
this.diaryDate = diaryDate;
this.emotionText = emotionText;
this.emotionScore = emotionScore;
}
public String getDiaryDate() {
return diaryDate;
}
public String getEmotionText() {
return emotionText;
}
public int getEmotionScore() {
return emotionScore;
}
}
⸻
✅ 4. main.jsp에 버튼 추가 (이동)
⸻
✅ 다음 단계 예고
6단계: 감정 점수 기반 그래프 시각화 (Chart.js)
→ 날짜별 감정 변화, 긍/부정 비율 파이차트 만들기!
준비되면 “그래프 고!!”라고 말해줘!
좋아!
이제 6단계: 감정 점수 시각화 (그래프) 기능으로 갑니다.
⸻
✅ 목표
• 사용자의 감정일기 데이터를 가져와
• 날짜별 감정 점수 추이 → Line Chart
• 감정 비율 분포 → Pie Chart
⸻
✅ 1. 그래프 전용 JSP: emotionChart.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.util.List" %>
<%@ page import="dto.UserDTO" %>
<%@ page import="model.DiaryDAO" %>
<%@ page import="model.DiaryDTO" %>
<%
HttpSession session = request.getSession(false);
UserDTO user = (UserDTO) session.getAttribute("user");
if (user == null) {
response.sendRedirect("login.jsp");
return;
}
List<DiaryDTO> diaryList = DiaryDAO.getDiaryList(user.getUserId());
%>
감정 통계<canvas id="lineChart" width="500" height="250"></canvas>
<canvas id="pieChart" width="400" height="250"></canvas>
<script>
const dates = [<% for (DiaryDTO d : diaryList) { %>"<%= d.getDiaryDate() %>",<% } %>];
const scores = [<% for (DiaryDTO d : diaryList) { %><%= d.getEmotionScore() %>,<% } %>];
// Line Chart (감정 점수 추이)
new Chart(document.getElementById('lineChart'), {
type: 'line',
data: {
labels: dates,
datasets: [{
label: '감정 점수',
data: scores,
borderColor: 'blue',
fill: false
}]
},
options: {
scales: {
y: {
min: -5,
max: 5
}
}
}
});
// Pie Chart (긍정/중립/부정 비율)
const pos = scores.filter(s => s > 0).length;
const neg = scores.filter(s => s < 0).length;
const neu = scores.filter(s => s === 0).length;
new Chart(document.getElementById('pieChart'), {
type: 'pie',
data: {
labels: ['긍정', '중립', '부정'],
datasets: [{
data: [pos, neu, neg],
backgroundColor: ['#4CAF50', '#9E9E9E', '#F44336']
}]
}
});
</script>
<br>
<a href="main.jsp">← 돌아가기</a>
⸻
✅ main.jsp에 버튼 추가
⸻
✅ 지금까지 완성된 기능
단계 기능
1 DB 설계
2 JSP + Servlet 구조
3 회원가입
4 로그인
5 일기 작성 + 감정 분석
6 일기 목록
7 감정 그래프 (완료!)
⸻
다음 스텝 예고
7단계: 마이페이지 기능 (비번 변경, 로그아웃)
또는
8단계: 감정 분석 자동 피드백 메시지 출력하기
또는
React 전환 or 디자인 입히기
무엇부터 하고 싶어? “마이페이지 고!” 또는 “피드백 고!” 또는 “리액트 고!” 골라줘!
완벽해!
이제 7단계: 마이페이지 기능으로 간다!
⸻
✅ 마이페이지 기능 목표
1. 현재 로그인된 사용자 정보 보여주기
2. 비밀번호 변경
3. 로그아웃
⸻
✅ 1. 마이페이지 화면: mypage.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="dto.UserDTO" %>
<%
HttpSession session = request.getSession(false);
UserDTO user = (UserDTO) session.getAttribute("user");
if (user == null) {
response.sendRedirect("login.jsp");
return;
}
%>
아이디: <%= user.getUsername() %>
닉네임: <%= user.getNickname() %>
<h3>비밀번호 변경</h3>
<form action="<%= request.getContextPath() %>/UpdatePasswordServlet" method="post">
현재 비밀번호: <input type="password" name="currentPassword" required><br>
새 비밀번호: <input type="password" name="newPassword" required><br>
<input type="submit" value="변경하기">
</form>
<form action="<%= request.getContextPath() %>/LogoutServlet" method="post" style="margin-top: 30px;">
<input type="submit" value="로그아웃">
</form>
<br>
<a href="main.jsp">← 돌아가기</a>
⸻
✅ 2. 비밀번호 변경 서블릿: UpdatePasswordServlet.java
package controller;
import dto.UserDTO;
import model.UserDAO;
import javax.servlet.;
import javax.servlet.http.;
import java.io.IOException;
public class UpdatePasswordServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
HttpSession session = request.getSession(false);
UserDTO user = (UserDTO) session.getAttribute("user");
if (user == null) {
response.sendRedirect("login.jsp");
return;
}
String currentPassword = request.getParameter("currentPassword");
String newPassword = request.getParameter("newPassword");
boolean result = UserDAO.updatePassword(user.getUsername(), currentPassword, newPassword);
if (result) {
response.sendRedirect("jsp/main.jsp");
} else {
response.sendRedirect("jsp/mypage.jsp?error=1");
}
}
}
⸻
✅ 3. 로그아웃 서블릿: LogoutServlet.java
package controller;
import javax.servlet.;
import javax.servlet.http.;
import java.io.IOException;
public class LogoutServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
response.sendRedirect("jsp/login.jsp");
}
}
⸻
✅ 4. DAO: UserDAO.updatePassword()
public static boolean updatePassword(String username, String currentPassword, String newPassword) {
String checkSql = "SELECT * FROM users WHERE username = ? AND password = ?";
String updateSql = "UPDATE users SET password = ? WHERE username = ?";
try (Connection conn = getConnection()) {
PreparedStatement checkStmt = conn.prepareStatement(checkSql);
checkStmt.setString(1, username);
checkStmt.setString(2, currentPassword);
ResultSet rs = checkStmt.executeQuery();
if (rs.next()) {
PreparedStatement updateStmt = conn.prepareStatement(updateSql);
updateStmt.setString(1, newPassword);
updateStmt.setString(2, username);
return updateStmt.executeUpdate() == 1;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
⸻
✅ 5. web.xml 매핑
UpdatePasswordServlet controller.UpdatePasswordServlet UpdatePasswordServlet /UpdatePasswordServlet LogoutServlet controller.LogoutServlet LogoutServlet /LogoutServlet⸻
✅ main.jsp에 버튼 추가
⸻
다음 단계 예고:
• ✨ 감정 분석 결과 기반 피드백 메시지 생성
• 또는
• ✨ UI 개선 / 디자인 입히기
• 또는
• ✨ React 전환
하고 싶은 방향 말해줘!
“피드백 고!”, “디자인 고!”, “리액트 고!” 중 골라줘!
좋아, 이제 React 전환 단계로 진입이야!
이제부터는 프론트엔드를 React로 새로 만들고, 백엔드는 JSP 대신 REST API로 동작하게 리팩토링할 거야.
⸻
✅ React 전환 전체 전략
1단계: React 앱 초기화 및 구조 잡기
• CRA(Create React App) 또는 Vite로 기본 셋업
• 폴더 구조: pages, components, services 등
2단계: 주요 화면 React로 구현
• LoginPage.jsx, RegisterPage.jsx, DiaryWritePage.jsx, DiaryListPage.jsx, EmotionChartPage.jsx, MyPage.jsx
3단계: API 통신 구조 만들기
• Java 서블릿 → JSON 반환 API로 리팩토링
• React에서 Axios로 데이터 fetch
4단계: 세션 or JWT 토큰 인증 연동
• 로그인 시 토큰 발급 or 세션 유지
• 로그인 상태 유지 및 자동 리다이렉션 처리
5단계: 디자인 + 상태관리
• Tailwind, MUI, Styled-Components 중 선택
• Context API or Redux로 로그인 상태 공유
⸻
✅ 지금 바로 시작할 1단계: React 앱 초기 셋업
npx create-react-app emotion-diary-react
cd emotion-diary-react
npm install axios react-router-dom
emotion-diary-react/
├── public/
├── src/
│ ├── pages/
│ │ ├── LoginPage.jsx
│ │ ├── RegisterPage.jsx
│ │ ├── DiaryWritePage.jsx
│ │ ├── DiaryListPage.jsx
│ │ ├── EmotionChartPage.jsx
│ │ └── MyPage.jsx
│ ├── components/
│ │ └── Header.jsx
│ ├── services/
│ │ └── api.js
│ ├── App.js
│ └── index.js
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import LoginPage from './pages/LoginPage';
import RegisterPage from './pages/RegisterPage';
import DiaryWritePage from './pages/DiaryWritePage';
import DiaryListPage from './pages/DiaryListPage';
import EmotionChartPage from './pages/EmotionChartPage';
import MyPage from './pages/MyPage';
function App() {
return (
<Route path="/" element={} />
<Route path="/register" element={} />
<Route path="/write" element={} />
<Route path="/list" element={} />
<Route path="/chart" element={} />
<Route path="/mypage" element={} />
);
}
export default App;
⸻
✅ 다음 단계
“LoginPage부터 시작할래”라고 말하면 → LoginPage.jsx + API 연동 코드부터 바로 써줄게!
또는, 원하면 RegisterPage, DiaryWritePage 등부터 시작해도 OK!
어느 페이지부터 React 전환을 시작할까?
“로그인부터!” / “일기쓰기부터!” / “페이지 전부 틀 먼저 줘!” 중 하나 선택해줘!
좋아, 그러면 React 전환 중요도 순서대로 단계별로 진행해줄게.
우선, 중요한 건 “기능 흐름”이 끊기지 않게 하는 것이야.
그래서 로그인 → 일기 쓰기 → 일기 목록 → 감정 분석 그래프 → 마이페이지 → 회원가입 순서로 전환하는 게 베스트야.
⸻
✅ React 전환 우선순위 목록
우선순위 페이지 이유
1 LoginPage 세션/인증 기반. 다른 모든 기능의 시작점
2 DiaryWritePage 핵심 데이터 입력. 감정 분석과 DB 저장 기능 포함
3 DiaryListPage 작성한 일기 확인. 사용자 피드백 핵심
4 EmotionChartPage 통계 기반 시각화. 핵심 차별화 요소
5 MyPage 사용자 정보 변경 및 로그아웃
6 RegisterPage 진입 기능이지만 중요도는 상대적으로 낮음
⸻
✅ 지금 할 일: LoginPage.jsx + 백엔드 연동
React에서 로그인 정보를 받고 → Java API로 POST 요청 → 성공 시 세션 or 토큰 저장 → 페이지 이동
이 흐름을 만들자.
다음 메시지에서:
• LoginPage.jsx
• api.js (axios 모듈)
• 백엔드 LoginServlet → JSON 반환 버전
모두 코드를 완성해서 줄게. 준비됐으면 “로그인페이지 고!!”라고 말해줘!
좋아, React 로그인 페이지 구현 지금 바로 간다!
우리는 다음 3가지를 구현할 거야:
⸻
✅ 구현 목록
1. LoginPage.jsx — 사용자 입력 폼 + 로그인 요청
2. api.js — Axios 설정 및 login 함수
3. LoginServlet — React에서 요청하면 JSON으로 응답하도록 수정
⸻
✅ 1. LoginPage.jsx
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { loginUser } from '../services/api';
function LoginPage() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [errorMsg, setErrorMsg] = useState('');
const navigate = useNavigate();
const handleLogin = async (e) => {
e.preventDefault();
try {
const res = await loginUser({ username, password });
if (res.success) {
localStorage.setItem('user', JSON.stringify(res.user)); // 세션 대용
navigate('/write'); // 로그인 성공 시 일기쓰기 페이지로 이동
} else {
setErrorMsg('로그인 실패. 아이디/비밀번호를 확인하세요.');
}
} catch (error) {
console.error(error);
setErrorMsg('서버 오류');
}
};
return (
<div>
<h2>로그인</h2>
<form onSubmit={handleLogin}>
<input
type="text"
placeholder="아이디"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/><br />
<input
type="password"
placeholder="비밀번호"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/><br />
<button type="submit">로그인</button>
</form>
{errorMsg && <p style={{ color: 'red' }}>{errorMsg}</p>}
</div>
);
}
export default LoginPage;
⸻
✅ 2. api.js
// src/services/api.js
import axios from 'axios';
const BASE_URL = 'http://localhost:8080/EmotionDiaryProject';
export const loginUser = async ({ username, password }) => {
const response = await axios.post(${BASE_URL}/LoginServlet, {
username,
password
}, {
headers: {
'Content-Type': 'application/json'
}
});
return response.data;
};
⸻
✅ 3. 수정된 LoginServlet.java (JSON 응답용)
package controller;
import dto.UserDTO;
import model.UserDAO;
import com.google.gson.Gson;
import javax.servlet.;
import javax.servlet.http.;
import java.io.*;
public class LoginServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=UTF-8");
BufferedReader reader = request.getReader();
StringBuilder jsonBuffer = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
jsonBuffer.append(line);
}
Gson gson = new Gson();
UserDTO input = gson.fromJson(jsonBuffer.toString(), UserDTO.class);
UserDTO user = UserDAO.login(input.getUsername(), input.getPassword());
PrintWriter out = response.getWriter();
if (user != null) {
out.print(gson.toJson(new LoginResult(true, user)));
} else {
out.print(gson.toJson(new LoginResult(false, null)));
}
}
private static class LoginResult {
boolean success;
UserDTO user;
public LoginResult(boolean success, UserDTO user) {
this.success = success;
this.user = user;
}
}
}
⸻
✅ 4. DTO: UserDTO에 기본 생성자 추가
public UserDTO() {} // Gson 역직렬화용 생성자
⸻
✅ 5. web.xml 매핑 확인
LoginServlet controller.LoginServlet LoginServlet /LoginServlet⸻
다음 스텝 예고:
→ DiaryWritePage.jsx 구현 + EmotionAnalyzer 연동 + POST로 감정일기 저장!
넘어갈 준비됐으면 “일기쓰기 리액트 고!”라고 말해줘!
좋아!
이제 React에서 일기 작성 페이지 (DiaryWritePage.jsx) 구현 + 감정 분석 POST 전송을 진행하자!
Java 백엔드에서는 감정 점수를 분석해서 DB에 저장하는 서블릿(DiaryServlet)을 그대로 재활용하거나 JSON 기반으로 수정할 거야.
⸻
✅ 구현 목록
1. DiaryWritePage.jsx — 일기 작성 + 날짜 선택 + 서버에 POST 요청
2. api.js — postDiary 함수 추가
3. DiaryServlet — JSON 받기 + 감정 분석 + 저장 후 응답
⸻
✅ 1. DiaryWritePage.jsx
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { postDiary } from '../services/api';
function DiaryWritePage() {
const navigate = useNavigate();
const user = JSON.parse(localStorage.getItem('user'));
const [diaryDate, setDiaryDate] = useState('');
const [emotionText, setEmotionText] = useState('');
const [message, setMessage] = useState('');
if (!user) {
navigate('/');
return null;
}
const handleSubmit = async (e) => {
e.preventDefault();
try {
const res = await postDiary({
userId: user.userId,
diaryDate,
emotionText
});
if (res.success) {
setMessage('일기 저장 성공!');
setDiaryDate('');
setEmotionText('');
} else {
setMessage('저장 실패...');
}
} catch (err) {
console.error(err);
setMessage('서버 오류');
}
};
return (
<div>
<h2>감정일기 작성</h2>
<form onSubmit={handleSubmit}>
날짜: <input type="date" value={diaryDate} onChange={(e) => setDiaryDate(e.target.value)} required /><br />
일기:<br />
<textarea value={emotionText} onChange={(e) => setEmotionText(e.target.value)} rows="6" cols="50" required />
<br />
<button type="submit">저장하기</button>
</form>
{message && <p>{message}</p>}
</div>
);
}
export default DiaryWritePage;
⸻
✅ 2. api.js에 함수 추가
export const postDiary = async ({ userId, diaryDate, emotionText }) => {
const response = await axios.post(${BASE_URL}/DiaryServlet, {
userId,
diaryDate,
emotionText
}, {
headers: {
'Content-Type': 'application/json'
}
});
return response.data;
};
⸻
✅ 3. 수정된 DiaryServlet.java (React JSON POST 대응)
package controller;
import model.DiaryDAO;
import model.EmotionAnalyzer;
import com.google.gson.Gson;
import javax.servlet.;
import javax.servlet.http.;
import java.io.*;
public class DiaryServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("application/json; charset=UTF-8");
request.setCharacterEncoding("UTF-8");
BufferedReader reader = request.getReader();
StringBuilder jsonBuffer = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
jsonBuffer.append(line);
}
Gson gson = new Gson();
DiaryRequest diary = gson.fromJson(jsonBuffer.toString(), DiaryRequest.class);
int score = EmotionAnalyzer.analyze(diary.emotionText);
boolean result = DiaryDAO.insertDiary(
diary.userId,
diary.diaryDate,
diary.emotionText,
score
);
PrintWriter out = response.getWriter();
out.print("{\"success\":" + result + "}");
}
private static class DiaryRequest {
int userId;
String diaryDate;
String emotionText;
}
}
⸻
✅ 확인 사항
• React → 백엔드 POST: /DiaryServlet
• 감정 점수 분석: EmotionAnalyzer 클래스 그대로 사용
• DB INSERT: DiaryDAO.insertDiary()
⸻
다음 스텝 예고:
→ DiaryListPage.jsx 구현: 감정일기 목록 조회 + 날짜순 출력 + 점수 보기
“일기 목록 리액트 고!”라고 말하면 바로 리스트 구현 코드 넘겨줄게!
훌륭해!
이제 React 일기 목록 페이지 (DiaryListPage.jsx)로 갑니다!
우리는 다음 흐름을 만들 거야:
⸻
✅ 일기 목록 기능 구성
1. 로그인된 사용자의 일기 데이터 GET 요청
2. 날짜순 정렬된 목록 출력
3. 감정 점수도 함께 출력 (색상 강조는 선택)
⸻
✅ 1. DiaryListPage.jsx
import React, { useEffect, useState } from 'react';
import { getDiaryList } from '../services/api';
import { useNavigate } from 'react-router-dom';
function DiaryListPage() {
const [diaries, setDiaries] = useState([]);
const user = JSON.parse(localStorage.getItem('user'));
const navigate = useNavigate();
useEffect(() => {
if (!user) {
navigate('/');
return;
}
async function fetchData() {
try {
const res = await getDiaryList(user.userId);
setDiaries(res);
} catch (err) {
console.error(err);
}
}
fetchData();
}, [user, navigate]);
return (
<div>
<h2>내 감정일기 목록</h2>
<table border="1">
<thead>
<tr>
<th>날짜</th>
<th>감정 점수</th>
<th>내용</th>
</tr>
</thead>
<tbody>
{diaries.map((d, index) => (
<tr key={index}>
<td>{d.diaryDate}</td>
<td style={{ color: d.emotionScore > 0 ? 'green' : d.emotionScore < 0 ? 'red' : 'gray' }}>
{d.emotionScore}
</td>
<td>{d.emotionText}</td>
</tr>
))}
</tbody>
</table>
<br />
<button onClick={() => navigate('/write')}>← 일기 쓰러 가기</button>
</div>
);
}
export default DiaryListPage;
⸻
✅ 2. api.js에 함수 추가
export const getDiaryList = async (userId) => {
const response = await axios.get(${BASE_URL}/DiaryListServlet?userId=${userId});
return response.data;
};
⸻
✅ 3. 새로운 서블릿: DiaryListServlet.java (React용 JSON 반환)
package controller;
import com.google.gson.Gson;
import model.DiaryDAO;
import model.DiaryDTO;
import javax.servlet.;
import javax.servlet.http.;
import java.io.*;
import java.util.List;
public class DiaryListServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("application/json; charset=UTF-8");
int userId = Integer.parseInt(request.getParameter("userId"));
List<DiaryDTO> diaryList = DiaryDAO.getDiaryList(userId);
Gson gson = new Gson();
PrintWriter out = response.getWriter();
out.print(gson.toJson(diaryList));
}
}
⸻
✅ DTO에 toJSON 가능한 필드명 유지
public class DiaryDTO {
private String diaryDate;
private String emotionText;
private int emotionScore;
public DiaryDTO(String diaryDate, String emotionText, int emotionScore) {
this.diaryDate = diaryDate;
this.emotionText = emotionText;
this.emotionScore = emotionScore;
}
public String getDiaryDate() { return diaryDate; }
public String getEmotionText() { return emotionText; }
public int getEmotionScore() { return emotionScore; }
}
⸻
✅ web.xml 매핑 추가
DiaryListServlet controller.DiaryListServlet DiaryListServlet /DiaryListServlet⸻
다음 스텝 예고:
→ EmotionChartPage.jsx로 감정 점수 그래프 시각화 (Line + Pie)
→ Chart.js 연동
시각화 준비됐으면 “감정 그래프 고!”라고 말해줘!
완벽해!
이제 React에서 감정 그래프 시각화 (EmotionChartPage.jsx) 단계로 들어갑니다.
⸻
✅ 목표
1. 사용자의 감정일기 목록을 가져와
2. 날짜별 감정 점수 → Line Chart
3. 감정 분포 (긍정/중립/부정) → Pie Chart
⸻
✅ 1. 설치 필요
먼저 Chart.js + React wrapper 설치:
npm install chart.js react-chartjs-2
⸻
✅ 2. EmotionChartPage.jsx
import React, { useEffect, useState } from 'react';
import { getDiaryList } from '../services/api';
import { Line, Pie } from 'react-chartjs-2';
import { Chart as ChartJS, LineElement, PointElement, CategoryScale, LinearScale, Title, Tooltip, Legend, ArcElement } from 'chart.js';
import { useNavigate } from 'react-router-dom';
ChartJS.register(
LineElement, PointElement, CategoryScale, LinearScale,
Title, Tooltip, Legend, ArcElement
);
function EmotionChartPage() {
const user = JSON.parse(localStorage.getItem('user'));
const [diaryData, setDiaryData] = useState([]);
const navigate = useNavigate();
useEffect(() => {
if (!user) {
navigate('/');
return;
}
async function fetchData() {
const data = await getDiaryList(user.userId);
setDiaryData(data);
}
fetchData();
}, [user, navigate]);
const dates = diaryData.map(d => d.diaryDate);
const scores = diaryData.map(d => d.emotionScore);
const lineChartData = {
labels: dates,
datasets: [{
label: '감정 점수',
data: scores,
borderColor: 'blue',
backgroundColor: 'lightblue',
tension: 0.3,
}]
};
const pos = scores.filter(s => s > 0).length;
const neu = scores.filter(s => s === 0).length;
const neg = scores.filter(s => s < 0).length;
const pieChartData = {
labels: ['긍정', '중립', '부정'],
datasets: [{
data: [pos, neu, neg],
backgroundColor: ['#4CAF50', '#9E9E9E', '#F44336']
}]
};
return (
<div>
<h2>감정 통계 시각화</h2>
<div style={{ width: '600px', margin: '0 auto' }}>
<Line data={lineChartData} />
<Pie data={pieChartData} style={{ marginTop: '40px' }} />
</div>
</div>
);
}
export default EmotionChartPage;
⸻
✅ 3. API는 그대로 getDiaryList() 사용
서버에서 /DiaryListServlet?userId=...으로 일기 전체 불러오기!
⸻
✅ 요약
• Line Chart: 날짜별 감정 추이
• Pie Chart: 감정 분포 (긍/부정/중립)
• react-chartjs-2 기반
• getDiaryList()로 기존 DB 그대로 재활용
⸻
다음 스텝 예고:
• MyPage.jsx: 닉네임 보기 + 비밀번호 변경 + 로그아웃
• 또는 감정 피드백 메시지 생성
• 또는 디자인 개선, React Router 보호, JWT 연동
다음으로 뭘 하고 싶어?
“마이페이지 리액트 고!”, “피드백 메시지 고!”, “UI 고!” 중 골라줘!
좋아, 이제 React 기반 마이페이지 (MyPage.jsx) 구현을 시작하자!
⸻
✅ MyPage 기능 목표
1. 사용자 정보(아이디, 닉네임) 표시
2. 비밀번호 변경 기능
3. 로그아웃 기능 (localStorage 제거 + 페이지 이동)
⸻
✅ 1. MyPage.jsx
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { updatePassword } from '../services/api';
function MyPage() {
const navigate = useNavigate();
const user = JSON.parse(localStorage.getItem('user'));
const [currentPassword, setCurrentPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
const [message, setMessage] = useState('');
if (!user) {
navigate('/');
return null;
}
const handleLogout = () => {
localStorage.removeItem('user');
navigate('/');
};
const handlePasswordChange = async (e) => {
e.preventDefault();
try {
const res = await updatePassword({
username: user.username,
currentPassword,
newPassword
});
if (res.success) {
setMessage('비밀번호 변경 완료!');
setCurrentPassword('');
setNewPassword('');
} else {
setMessage('현재 비밀번호가 틀립니다.');
}
} catch (err) {
console.error(err);
setMessage('서버 오류');
}
};
return (
<div>
<h2>마이페이지</h2>
<p><strong>아이디:</strong> {user.username}</p>
<p><strong>닉네임:</strong> {user.nickname}</p>
<form onSubmit={handlePasswordChange}>
<h3>비밀번호 변경</h3>
<input
type="password"
placeholder="현재 비밀번호"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
required
/><br />
<input
type="password"
placeholder="새 비밀번호"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
required
/><br />
<button type="submit">변경하기</button>
</form>
<button onClick={handleLogout} style={{ marginTop: '30px' }}>로그아웃</button>
{message && <p>{message}</p>}
</div>
);
}
export default MyPage;
⸻
✅ 2. api.js에 비밀번호 변경 API 추가
export const updatePassword = async ({ username, currentPassword, newPassword }) => {
const response = await axios.post(${BASE_URL}/UpdatePasswordServlet, {
username,
currentPassword,
newPassword
}, {
headers: {
'Content-Type': 'application/json'
}
});
return response.data;
};
⸻
✅ 3. 백엔드: UpdatePasswordServlet.java (React용 JSON 처리)
package controller;
import model.UserDAO;
import com.google.gson.Gson;
import javax.servlet.;
import javax.servlet.http.;
import java.io.*;
public class UpdatePasswordServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("application/json; charset=UTF-8");
request.setCharacterEncoding("UTF-8");
BufferedReader reader = request.getReader();
StringBuilder buffer = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
Gson gson = new Gson();
PasswordUpdateRequest req = gson.fromJson(buffer.toString(), PasswordUpdateRequest.class);
boolean success = UserDAO.updatePassword(req.username, req.currentPassword, req.newPassword);
PrintWriter out = response.getWriter();
out.print("{\"success\":" + success + "}");
}
private static class PasswordUpdateRequest {
String username;
String currentPassword;
String newPassword;
}
}
⸻
✅ web.xml 매핑 추가
UpdatePasswordServlet controller.UpdatePasswordServlet UpdatePasswordServlet /UpdatePasswordServlet⸻
✅ 흐름 요약
• React가 JSON POST → Java 서블릿 → 비밀번호 검사 후 변경
• 성공 시 메시지 출력
• 로그아웃은 localStorage 삭제 + 메인 이동
⸻
다음 스텝 예고:
• 회원가입 페이지 (RegisterPage)
• 감정 피드백 자동 생성
• UI 스타일링 (디자인 입히기)
• React Router 인증 보호 / JWT 전환
어디로 갈까?
“회원가입 고!”, “피드백 메시지 고!”, “UI 고!”, “JWT 고!” 중 선택해줘!
좋아!
지금부터 1) 회원가입 페이지 구현 → 2) React Router 인증 보호 + JWT 연동 순서로 진행할게!
⸻
✅ 1단계: RegisterPage.jsx 구현 (회원가입)
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { registerUser } from '../services/api';
function RegisterPage() {
const navigate = useNavigate();
const [form, setForm] = useState({
username: '',
password: '',
nickname: ''
});
const [message, setMessage] = useState('');
const handleChange = (e) => {
setForm({ ...form, [e.target.name]: e.target.value });
};
const handleRegister = async (e) => {
e.preventDefault();
try {
const res = await registerUser(form);
if (res.success) {
setMessage('회원가입 성공! 로그인으로 이동합니다.');
setTimeout(() => navigate('/'), 1500);
} else {
setMessage('이미 존재하는 아이디입니다.');
}
} catch (err) {
console.error(err);
setMessage('서버 오류');
}
};
return (
<div>
<h2>회원가입</h2>
<form onSubmit={handleRegister}>
아이디: <input name="username" value={form.username} onChange={handleChange} required /><br />
비밀번호: <input type="password" name="password" value={form.password} onChange={handleChange} required /><br />
닉네임: <input name="nickname" value={form.nickname} onChange={handleChange} required /><br />
<button type="submit">가입하기</button>
</form>
{message && <p>{message}</p>}
</div>
);
}
export default RegisterPage;
⸻
export const registerUser = async ({ username, password, nickname }) => {
const response = await axios.post(${BASE_URL}/RegisterServlet, {
username,
password,
nickname
}, {
headers: {
'Content-Type': 'application/json'
}
});
return response.data;
};
⸻
package controller;
import com.google.gson.Gson;
import model.UserDAO;
import javax.servlet.;
import javax.servlet.http.;
import java.io.*;
public class RegisterServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=UTF-8");
BufferedReader reader = request.getReader();
StringBuilder buffer = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
Gson gson = new Gson();
UserDTO user = gson.fromJson(buffer.toString(), UserDTO.class);
boolean result = UserDAO.insertUser(user.getUsername(), user.getPassword(), user.getNickname());
PrintWriter out = response.getWriter();
out.print("{\"success\":" + result + "}");
}
}
⸻
✅ 다음 단계: 2단계 – React Router 인증 보호 + JWT 연동 설계
곧 이어서 설명할 내용:
1. JWT 발급 방식으로 백엔드 수정 (로그인 시 토큰 발급)
2. React에서 토큰 저장 + 자동 로그인 처리
3. ProtectedRoute 컴포넌트 작성 → 로그인 안하면 막기
⸻
이제 회원가입까지 끝났어!
곧바로 “JWT 기반 로그인 + 라우터 인증 보호” 로 넘어갈게. 준비됐지?
곧 이어서 JWT 단계 시작합니다!
좋아, 이제 JWT 기반 인증 + React Router 보호 단계로 간다!
우리는 다음 3가지 흐름을 만들 거야:
⸻
✅ 목표 요약
1. Java 백엔드에서 로그인 시 JWT 토큰 발급
2. React에서 JWT를 localStorage에 저장
3. React Router에서 토큰 없으면 접근 제한
⸻
✅ 1단계: Java에서 JWT 토큰 발급
1.1 의존성 추가 (Maven 기준)
io.jsonwebtoken jjwt 0.9.1⸻
1.2 JwtUtil.java 클래스 생성
package util;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class JwtUtil {
private static final String SECRET_KEY = "emotion_diary_secret";
private static final long EXPIRATION_TIME = 1000 60 60; // 1시간
public static String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
}
⸻
1.3 LoginServlet.java 수정
import util.JwtUtil;
if (user != null) {
String token = JwtUtil.generateToken(user.getUsername());
out.print("{\"success\":true, \"token\":\"" + token + "\", \"user\":"
+ gson.toJson(user) + "}");
} else {
out.print("{\"success\":false}");
}
⸻
✅ 2단계: React에서 JWT 저장 및 사용
2.1 LoginPage.jsx 수정 (토큰 저장)
if (res.success) {
localStorage.setItem('token', res.token);
localStorage.setItem('user', JSON.stringify(res.user));
navigate('/write');
}
⸻
2.2 Axios 인터셉터 추가 (api.js 수정)
axios.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = Bearer ${token};
}
return config;
});
⸻
✅ 3단계: React Router 인증 보호
3.1 ProtectedRoute.jsx 생성
import React from 'react';
import { Navigate } from 'react-router-dom';
const ProtectedRoute = ({ children }) => {
const token = localStorage.getItem('token');
return token ? children : ;
};
export default ProtectedRoute;
⸻
3.2 App.js 라우팅 수정
import ProtectedRoute from './components/ProtectedRoute';
} /> } /> } /> } /> } /> } />⸻
✅ 4단계(선택): 토큰 만료 처리
• 토큰 만료 시 백엔드가 401 반환 → React에서 감지하여 자동 로그아웃
• 이건 다음 단계에서 확장 가능!
⸻
마무리
이제 너의 앱은 완전한 JWT 인증 기반 로그인/보호 구조를 갖췄어!
다음 단계로는:
• 토큰 만료 시간 체크
• 리프레시 토큰 전략
• UI 개선 (Tailwind, MUI 등)
다음엔 뭘 하고 싶어? “UI 고!” or “토큰 만료 체크 고!” or “다른 프로젝트 고!” 중 골라줘!
좋아! 지금부터 JWT 토큰 만료 체크 → 자동 로그아웃 처리 흐름 먼저 만들고, 그다음에 UI로 넘어가자!
⸻
✅ 목표:
1. 토큰 만료 여부를 React에서 감지
2. 만료된 토큰이면 localStorage 제거 + 로그인 페이지로 리다이렉트
3. 옵션: 토큰 남은 시간 보여주기 타이머도 가능!
⸻
✅ 1단계: checkTokenValidity() 유틸 함수 추가
// src/utils/tokenUtils.js
export const isTokenExpired = (token) => {
if (!token) return true;
try {
const payload = JSON.parse(atob(token.split('.')[1])); // JWT의 payload decode
const now = Math.floor(Date.now() / 1000); // 현재 시간 (초)
return payload.exp < now;
} catch (e) {
console.error('토큰 디코딩 실패', e);
return true;
}
};
⸻
✅ 2단계: ProtectedRoute.jsx에서 체크 적용
import React from 'react';
import { Navigate } from 'react-router-dom';
import { isTokenExpired } from '../utils/tokenUtils';
const ProtectedRoute = ({ children }) => {
const token = localStorage.getItem('token');
if (!token || isTokenExpired(token)) {
localStorage.removeItem('token');
localStorage.removeItem('user');
return ;
}
return children;
};
export default ProtectedRoute;
⸻
✅ 3단계(선택): 남은 시간 타이머 보여주기 (마이페이지 등에서)
export const getTokenRemainingSeconds = (token) => {
if (!token) return 0;
try {
const payload = JSON.parse(atob(token.split('.')[1]));
const now = Math.floor(Date.now() / 1000);
return payload.exp - now;
} catch {
return 0;
}
};
⸻
예시: MyPage.jsx에서 남은 시간 출력
import { getTokenRemainingSeconds } from '../utils/tokenUtils';
const remaining = getTokenRemainingSeconds(localStorage.getItem('token'));
세션 만료까지 {remaining}초 남음
⸻
✅ 4단계: 추가 UX 전략 (선택)
• setTimeout(() => logout(), 남은시간 * 1000) 으로 자동 로그아웃 예약도 가능!
• Alert("세션이 만료되었습니다") 후 리다이렉트 처리
⸻
다음 스텝 예고: UI 스타일링 고!
• Tailwind CSS or Material UI 도입
• 전체 앱을 감성 있는 감정일기로 리디자인
Tailwind vs MUI 중 선호하는 거 있어?
“Tailwind 고!” 또는 “MUI 고!” 말해줘!
완벽해!
이제 Tailwind CSS 기반 감정일기 UI 리디자인 들어간다!
React에 테일윈드 연동하고, 우리가 만든 페이지들에 차분하고 감성적인 디자인을 입혀볼 거야.
⸻
✅ 1단계: Tailwind CSS 설치 및 설정
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
tailwind.config.js
module.exports = {
content: ['./src/*/.{js,jsx,ts,tsx}'],
theme: {
extend: {},
},
plugins: [],
};
src/index.css
@tailwind base;
@tailwind components;
@tailwind utilities;
index.js
import './index.css';
⸻
✅ 2단계: 로그인 페이지 예시 (LoginPage.jsx) 리디자인
return (
{errorMsg}
}⸻
✅ 3단계: 공통 스타일 구조 제안
구성요소 Tailwind 적용 예시
배경 bg-gradient-to-br from-pink-100 to-rose-200
입력창 border px-4 py-2 rounded focus:outline-none
버튼 bg-emerald-500 hover:bg-emerald-600 text-white font-semibold rounded
카드 bg-white shadow-md rounded-lg p-6
⸻
✅ 4단계: 페이지별 Tailwind 적용 제안
페이지 주요 색상 추천 UI 요소
로그인 / 회원가입 인디고, 보라 단색 배경 + 가운데 카드
일기쓰기 연노랑, 크림 입력창 + 날짜 picker + 감정 태그
목록 감정 점수별 색 강조 표 형태 or 카드
그래프 넓은 여백, 둥근 카드 Chart.js + 감성 타이틀
마이페이지 분홍, 연보라 프로필 느낌 + 정보 정리
⸻
✅ 다음 스텝
→ 내가 각 페이지별 Tailwind 적용 버전 예시 템플릿 쭉 정리해줄 수도 있어!
또는, 지금부터 전체 앱을 감정일기 디자인으로 리디자인 통일해줄까?
“전체 Tailwind 통일 디자인 GO!”라고 말하면 각 페이지 리디자인 순서대로 들어간다!
좋아!
지금부터 전체 페이지를 Tailwind 기반 감성 디자인으로 통일하는 리디자인을 시작할게.
하나씩 예쁘고 깔끔하게, 감정일기다운 분위기로 바꿔줄게.
⸻
✅ 전체 Tailwind 리디자인 순서
순서 페이지 이름 디자인 스타일 주요 포인트
1 LoginPage 인디고+하얀 카드 진입의 첫인상, 차분함
2 RegisterPage 민트+둥근 폼 환영하는 느낌
3 DiaryWritePage 노랑+크림+글쓰기 느낌 글쓰기 공간 강조
4 DiaryListPage 테이블 or 카드 스타일 감정 점수 색상별 표시
5 EmotionChartPage 넓은 여백+차트 강조 통계 느낌 살리기
6 MyPage 연보라+설정화면 느낌 사용자 정보 중심
⸻
✅ 1. LoginPage.jsx Tailwind 최종 템플릿 (정리)
return (
계정이 없으신가요?{' '} 회원가입
{errorMsg &&{errorMsg}
}⸻
✅ 다음 순서
곧바로 다음 페이지 UI 리디자인 해줄게!
계속해서 “다음 페이지 고!”라고 해줘!
(멈추고 싶으면 언제든 “다음은 내일!“이라고 말해도 돼)
좋아! 이제 Tailwind 적용된 RegisterPage.jsx 리디자인 들어간다.
톤은 로그인과 통일되면서도 따뜻하고 환영받는 느낌으로 구성할게.
⸻
✅ RegisterPage.jsx (Tailwind 리디자인 완성본)
return (
⸻
✅ 구성 설명
• 배경: 민트계열 그라디언트 (신선한 첫 진입 느낌)
• 폼: 깔끔한 둥근 입력창 + 라운드 버튼
• 메시지/링크: 색상 통일, 시각 피드백 깔끔
⸻
다음 순서
진행할까?
“다음 페이지 고!” 하면 바로 감정일기 UI 리디자인 시작해!
좋아!
이제 감정일기의 핵심 — DiaryWritePage.jsx를 감성적으로 리디자인해볼게.
이번엔 따뜻한 노랑/크림톤 + 글쓰기 일기장 느낌으로 연출한다.
⸻
✅ DiaryWritePage.jsx (Tailwind 리디자인 완성본)
return (
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label className="block mb-1 text-gray-700 font-semibold">날짜 선택</label>
<input
type="date"
value={diaryDate}
onChange={(e) => setDiaryDate(e.target.value)}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-yellow-300"
required
/>
</div>
<div>
<label className="block mb-1 text-gray-700 font-semibold">오늘의 이야기</label>
<textarea
value={emotionText}
onChange={(e) => setEmotionText(e.target.value)}
rows="8"
placeholder="오늘 있었던 감정이나 사건을 자유롭게 기록해보세요..."
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-yellow-300 resize-none"
required
/>
</div>
<button
type="submit"
className="w-full bg-yellow-400 hover:bg-yellow-500 text-white font-bold py-2 rounded-lg"
>
저장하기
</button>
</form>
{message && <p className="text-center mt-4 text-green-700 font-medium">{message}</p>}
</div>
⸻
✅ 디자인 요소 요약
요소 스타일 설명
배경 from-yellow-50 to-yellow-100: 따뜻한 크림색
입력폼 여백 넉넉 + 라운드 처리
버튼 진한 노랑 버튼으로 집중
placeholder 글쓰기 유도 문구
⸻
다음 순서
진행할까?
“다음 페이지 고!” 해주면 바로 예쁘게 구성해줄게!
완벽해!
이제 감정일기 앱의 일기 아카이브 — DiaryListPage.jsx 리디자인 들어갑니다.
이번 컨셉은 심플 + 감정 점수별 색상 강조 + 카드 레이아웃!
⸻
✅ DiaryListPage.jsx (Tailwind 카드 스타일 리디자인)
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-5xl mx-auto">
{diaries.length === 0 && (
<p className="col-span-2 text-center text-gray-500">작성한 일기가 없습니다.</p>
)}
{diaries.map((d, index) => (
<div key={index} className="bg-white rounded-xl shadow-md p-6">
<div className="flex justify-between items-center mb-2">
<span className="text-sm text-gray-500">{d.diaryDate}</span>
<span className={`font-bold text-lg ${
d.emotionScore > 0
? 'text-green-600'
: d.emotionScore < 0
? 'text-red-500'
: 'text-gray-500'
}`}>
감정: {d.emotionScore}
</span>
</div>
<p className="text-gray-800 whitespace-pre-line">{d.emotionText}</p>
</div>
))}
</div>
<div className="text-center mt-10">
<button
onClick={() => navigate('/write')}
className="bg-orange-400 hover:bg-orange-500 text-white px-6 py-2 rounded-lg font-semibold"
>
✍️ 일기 쓰러 가기
</button>
</div>
⸻
✅ 디자인 설명
요소 스타일
전체 배경 감성 핑크 + 오렌지 그라디언트
카드 레이아웃 grid-cols-1 md:grid-cols-2
감정 점수 강조 긍정=green, 부정=red, 중립=gray
글꼴 whitespace-pre-line 처리로 줄바꿈 살림
⸻
다음 스텝
진행할까?
“다음 페이지 고!” 해줘!
좋아!
이제 감정일기의 하이라이트 — EmotionChartPage.jsx 시각화 페이지
우리는 깔끔한 카드 레이아웃 + 따뜻한 배경 + 넓은 그래프 여백으로 감성적인 느낌을 살릴 거야.
⸻
✅ EmotionChartPage.jsx (Tailwind + Chart.js 리디자인 완성본)
import React, { useEffect, useState } from 'react';
import { getDiaryList } from '../services/api';
import { Line, Pie } from 'react-chartjs-2';
import { Chart as ChartJS, LineElement, PointElement, CategoryScale, LinearScale, Title, Tooltip, Legend, ArcElement } from 'chart.js';
import { useNavigate } from 'react-router-dom';
ChartJS.register(
LineElement, PointElement, CategoryScale, LinearScale,
Title, Tooltip, Legend, ArcElement
);
function EmotionChartPage() {
const user = JSON.parse(localStorage.getItem('user'));
const [diaryData, setDiaryData] = useState([]);
const navigate = useNavigate();
useEffect(() => {
if (!user) {
navigate('/');
return;
}
async function fetchData() {
const data = await getDiaryList(user.userId);
setDiaryData(data);
}
fetchData();
}, [user, navigate]);
const dates = diaryData.map(d => d.diaryDate);
const scores = diaryData.map(d => d.emotionScore);
const pos = scores.filter(s => s > 0).length;
const neu = scores.filter(s => s === 0).length;
const neg = scores.filter(s => s < 0).length;
const lineChartData = {
labels: dates,
datasets: [{
label: '감정 점수',
data: scores,
borderColor: '#6366f1',
backgroundColor: 'rgba(99, 102, 241, 0.3)',
tension: 0.3,
}]
};
const pieChartData = {
labels: ['긍정', '중립', '부정'],
datasets: [{
data: [pos, neu, neg],
backgroundColor: ['#4CAF50', '#9E9E9E', '#F44336']
}]
};
return (
<div className="min-h-screen bg-gradient-to-br from-indigo-50 to-blue-100 py-12 px-4">
<h2 className="text-3xl font-bold text-center text-indigo-600 mb-10">감정 통계 시각화</h2>
<div className="bg-white max-w-5xl mx-auto p-8 rounded-xl shadow-md space-y-12">
<div>
<h3 className="text-xl font-semibold text-gray-700 mb-4">감정 점수 변화 (일별)</h3>
<Line data={lineChartData} />
</div>
<div>
<h3 className="text-xl font-semibold text-gray-700 mb-4">감정 분포 (긍/부정/중립)</h3>
<Pie data={pieChartData} />
</div>
</div>
</div>
);
}
export default EmotionChartPage;
⸻
✅ 디자인 포인트
요소 디자인
배경 파스텔 블루 톤 (신뢰 + 통계 느낌)
컨테이너 bg-white + rounded-xl + shadow-md
텍스트 text-gray-700 + text-indigo-600
여백 space-y-12로 차트간 시각 여유 확보
⸻
다음 스텝
마지막 리디자인!
“마이페이지 리디자인 고!” 해줘!
좋아!
이제 감정일기 앱 마이페이지 (MyPage.jsx) 리디자인 들어간다.
이번 컨셉은 연보라/연핑크 배경 + 부드러운 프로필 느낌 + 설정 화면처럼 따뜻하게!
⸻
✅ MyPage.jsx (Tailwind 리디자인 완성본)
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { updatePassword } from '../services/api';
import { getTokenRemainingSeconds } from '../utils/tokenUtils';
function MyPage() {
const navigate = useNavigate();
const user = JSON.parse(localStorage.getItem('user'));
const [currentPassword, setCurrentPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
const [message, setMessage] = useState('');
const remaining = getTokenRemainingSeconds(localStorage.getItem('token'));
if (!user) {
navigate('/');
return null;
}
const handleLogout = () => {
localStorage.removeItem('user');
localStorage.removeItem('token');
navigate('/');
};
const handlePasswordChange = async (e) => {
e.preventDefault();
try {
const res = await updatePassword({
username: user.username,
currentPassword,
newPassword
});
if (res.success) {
setMessage('비밀번호가 변경되었습니다.');
setCurrentPassword('');
setNewPassword('');
} else {
setMessage('현재 비밀번호가 일치하지 않습니다.');
}
} catch (err) {
console.error(err);
setMessage('서버 오류');
}
};
return (
<div className="min-h-screen bg-gradient-to-br from-purple-50 to-pink-100 flex items-center justify-center">
<div className="bg-white p-10 rounded-2xl shadow-lg w-full max-w-md">
<h2 className="text-3xl font-bold text-center text-purple-600 mb-6">마이페이지</h2>
<div className="space-y-2 mb-6 text-center">
<p><strong>아이디:</strong> {user.username}</p>
<p><strong>닉네임:</strong> {user.nickname}</p>
<p className="text-sm text-gray-500">세션 만료까지 {remaining}초 남음</p>
</div>
<form onSubmit={handlePasswordChange} className="space-y-4">
<h3 className="text-lg font-semibold text-gray-700 mb-2">비밀번호 변경</h3>
<input
type="password"
placeholder="현재 비밀번호"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
required
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-300"
/>
<input
type="password"
placeholder="새 비밀번호"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
required
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-300"
/>
<button
type="submit"
className="w-full bg-purple-500 hover:bg-purple-600 text-white py-2 rounded-lg font-semibold"
>
변경하기
</button>
</form>
{message && <p className="mt-4 text-center text-sm text-green-600">{message}</p>}
<button
onClick={handleLogout}
className="w-full mt-8 bg-red-400 hover:bg-red-500 text-white py-2 rounded-lg font-semibold"
>
로그아웃
</button>
</div>
</div>
);
}
export default MyPage;
⸻
✅ 감성 포인트 요약
요소 디자인
배경 연보라 → 핑크 그라디언트
카드 rounded-2xl, shadow-lg
세션 안내 토큰 만료 시간 안내 메시지
버튼 보라색 버튼 + 빨간 로그아웃 버튼
⸻
✅ 전체 Tailwind 리디자인 마무리!
너의 감정일기 앱은 이제:
• 모든 페이지 리액트 + API 통신 + 감정 분석
• JWT 인증 기반 + 자동 만료 처리
• 감성 디자인 완성 (Tailwind 기반)
⸻
이제 마지막으로 프로젝트 구조 정리, README 작성, 배포(Vercel + Render) 준비해줄 수 있어!
그 다음으로 “배포 고!” 또는 “README 정리 고!” 중 선택해줘!
좋아!
이제 감정일기 앱을 깔끔하게 정리한 README 작성 → Vercel + Render 배포 단계로 마무리하자.
먼저 README.md 템플릿부터 제공할게!
⸻
✅ 감정일기 앱 README 템플릿 (README.md)
나의 하루를 감정과 함께 기록하고, 통계로 돌아보는 감성 일기장
React + Spring + MySQL + JWT 기반 풀스택 프로젝트
| 영역 | 기술 |
|---|---|
| Frontend | React, React Router, Axios, Chart.js, Tailwind CSS |
| Backend | Java Servlet, JSP, Gson, JWT (jjwt), MySQL JDBC |
| Database | MySQL |
| Auth | JWT 기반 토큰 인증 + 만료 체크 |
| 배포 | Vercel (React), Render (Spring API) |
frontend/
├── pages/
├── components/
├── services/api.js
├── utils/tokenUtils.js
└── App.js
backend/
├── controller/
├── model/
├── dto/
├── util/JwtUtil.java
└── web.xml
cd frontend
npm install
npm start
백엔드 (Java + MySQL)
1. MySQL에서 DB 및 테이블 생성
2. Eclipse에서 EmotionDiaryProject 실행 (Tomcat 9)
3. localhost:8080으로 서블릿 연결 확인
⸻
📝 향후 개선 예정
• 감정 피드백 자동 문장 생성 (GPT 연동)
• 일기 공개/비공개 설정
• 친구와 감정 비교 그래프
• 모바일 최적화 / 반응형 UI
⸻
✨ 데모 링크
• 프론트엔드(Vercel)
• 백엔드 API(Render)
⸻
👤 개발자
이름 역할
[Your Name] 풀스택 개발, 감정분석 설계, JWT 인증, UI 디자인
---
## ✅ 다음: 배포 순서 요약 (Vercel + Render)
**React + Java 동시 배포용 구조**
| 플랫폼 | 내용 |
|--------|------|
| **Vercel** | React 앱 배포 (Git 연동) |
| **Render** | Java 서블릿 API 배포 (War or JAR) |
| CORS | 백엔드에 `Access-Control-Allow-Origin: *` 설정 필수 |
---
**다음 단계는?**
“배포 고!”라고 하면 Vercel & Render 단계별 배포 가이드 바로 작성해줄게!
완벽해!
이제 감정일기 앱 Git 기반 Vercel + Render 배포
React 프론트엔드 → Vercel,
Java 서블릿 백엔드 → Render (GitHub 연동) 으로 진행할게.
⸻
✅ [1단계] GitHub 저장소 분리 or 통합 구조
방식 폴더 구조 추천
분리형 emotion-diary-frontend/ + emotion-diary-backend/ 각각 독립 repo ✅ 안정적
통합형 frontend/, backend/ 한 repo 내 폴더 분리 가능하나 Vercel 자동 배포 세팅 주의 필요
→ 여기선 분리형으로 안내할게!
⸻
✅ [2단계] 프론트엔드 Vercel 배포
2.1 GitHub에 React 저장소 푸시
cd emotion-diary-react
git init
git remote add origin https://github.com/yourname/emotion-diary-frontend.git
git add .
git commit -m "Initial commit: Emotion Diary React"
git push -u origin main
2.2 Vercel 배포
1. https://vercel.com 접속
2. GitHub 계정 연동
3. emotion-diary-frontend 저장소 Import
4. 프레임워크 자동 감지: React
5. 빌드 명령어: npm run build
6. 루트 디렉토리 설정: 기본값 (/)
7. 배포 완료 → URL 생성
⸻
✅ [3단계] 백엔드 Render 배포 (서블릿 기반 WAR 배포)
3.1 GitHub에 백엔드 푸시
cd EmotionDiaryProject # .project, .classpath 포함된 이클립스 Web 프로젝트 루트
git init
git remote add origin https://github.com/yourname/emotion-diary-backend.git
git add .
git commit -m "Initial commit: Emotion Diary Java Backend"
git push -u origin main
3.2 Render 배포 설정 (Java WAR)
1. https://render.com 접속
2. New Web Service → GitHub 연동
3. 저장소 선택: emotion-diary-backend
4. Environment: Java
5. Build Command: Gradle or Maven 설정에 따라 입력 (mvn clean package 등)
6. Start Command: 자동 감지 (Tomcat 내장형이면 java -jar ... 형태)
7. Port: 기본값 8080
8. 배포 완료 → API base URL 확인 (https://emotion-api.onrender.com)
⸻
✅ [4단계] React → Render API 주소 연결 (CORS 주의)
• src/services/api.js에서 BASE_URL 수정:
const BASE_URL = 'https://emotion-api.onrender.com';
• 백엔드 Java 서블릿 응답에 CORS 설정 추가:
response.setHeader("Access-Control-Allow-Origin", "*");
⸻
✅ [5단계] 최종 확인
• Vercel 주소로 접속 → 회원가입/로그인 → 감정일기 작성 → 그래프 확인
• 개발자 도구 → API 요청 → 200 OK + JWT 정상 전달 확인
⸻
🔒 추천 후속 작업
• .env 기반 BASE_URL 분리
• 백엔드 HTTPS 적용, 도메인 연동
• CORS 제한 (Allow-Origin: emotion-diary.vercel.app)
• JWT 리프레시 토큰 전략 적용
⸻
배포 성공하면 알려줘!
원하면 최종 제출용 발표 슬라이드, Pitch Deck 템플릿, 포트폴리오 PDF 형식도 만들어줄 수 있어!
필요하면 “발표자료 고!” 또는 “포폴 고!“라고 말해줘!