diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..ffbb6ec --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,87 @@ +name: Java Servlet CI/CD Workflow + +on: + push: + branches: + - dev + +env: + REGION: ${{ secrets.REGION }} + ECS_CLUSTER: luckyweeky-ecs-cluster + SERVICE_NAME: luckyweeky-service + +jobs: + build-and-deploy: + name: Build and Deploy Docker Image + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Set up Java + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: Build with Maven (Skip Tests) + run: mvn clean package -DskipTests + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Log in to Docker Hub + run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + + - name: Build and Push Docker Image + run: | + TAG=${{ github.sha }} + IMAGE_NAME=${{ secrets.DOCKER_USERNAME }}/luckyweeky-server + docker build -t $IMAGE_NAME:${TAG} -t $IMAGE_NAME:latest . + docker push $IMAGE_NAME:${TAG} + docker push $IMAGE_NAME:latest + + - name: Install jq + run: sudo apt-get update && sudo apt-get install -y jq + + - name: Update Task Definition + run: | + TAG=${{ github.sha }} + IMAGE_NAME=${{ secrets.DOCKER_USERNAME }}/luckyweeky-server + echo "Updating Task Definition with IMAGE: $IMAGE_NAME:$TAG" + + # Using jq to update the image field + jq '.containerDefinitions[0].image = "'$IMAGE_NAME:$TAG'"' ecs-task-definition.json > ecs-task-definition-updated.json + sed -i '/"taskRoleArn":/d' ecs-task-definition-updated.json + + - name: Verify Updated Task Definition + run: | + echo "Updated Task Definition:" + cat ecs-task-definition-updated.json + + - name: Register Task Definition + id: register_task_definition + run: | + TASK_DEF_ARN=$(aws ecs register-task-definition \ + --cli-input-json file://ecs-task-definition-updated.json \ + --query 'taskDefinition.taskDefinitionArn' \ + --output text \ + --region $REGION) + echo "TASK_DEF_ARN=$TASK_DEF_ARN" >> $GITHUB_ENV + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + REGION: ${{ secrets.REGION }} + + - name: Update ECS Service + run: | + aws ecs update-service \ + --cluster $ECS_CLUSTER \ + --service $SERVICE_NAME \ + --task-definition $TASK_DEF_ARN \ + --region ${{ secrets.REGION }} + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + TASK_DEF_ARN: ${{ env.TASK_DEF_ARN }} diff --git a/.gitignore b/.gitignore index 7f9f6c5..9d24837 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,50 @@ -src/main/webapp/WEB-INF/.env \ No newline at end of file + +# Ignore target directories +/target/ +/**/target/ + +# IntelliJ IDEA +.idea/ +/*.iml +*.ipr +*.iws + + + +# Exclude specific IDEA settings explicitly if needed +!.idea/misc.xml +!.idea/modules.xml +!.idea/jarRepositories.xml +!.idea/compiler.xml +!.idea/libraries/ + +# Eclipse +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +# NetBeans +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +# Build directories +/build/ +/**/build/ + +# VS Code +.vscode/ + +# macOS system files +.DS_Store + +# Environment files +src/main/webapp/WEB-INF/.env +src/main/webapp/WEB-INF/local-secrets.json + diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index cb2be2e..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# 디폴트 무시된 파일 -/shelf/ -/workspace.xml -# 에디터 기반 HTTP 클라이언트 요청 -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 29cf596..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -Controller.java \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 75a2b5a..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index a468a99..0000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index f0f8287..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml deleted file mode 100644 index edc4eb9..0000000 --- a/.idea/sqldialects.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml deleted file mode 100644 index 6d50cd4..0000000 --- a/.idea/uiDesigner.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 9661ac7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..772d7b6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +# 1. Base Image +FROM tomcat:10.1.30-jdk17-temurin + +# 2. Set timezone +ENV JAVA_OPTS="-Duser.timezone=Asia/Seoul" + +# 3. Copy Maven build artifact to Tomcat webapps +COPY target/ROOT.war /usr/local/tomcat/webapps/ + +# 4. Expose application port +EXPOSE 8080 + +# 5. Start Tomcat +CMD ["catalina.sh", "run"] diff --git a/ecs-task-definition.json b/ecs-task-definition.json new file mode 100644 index 0000000..a929683 --- /dev/null +++ b/ecs-task-definition.json @@ -0,0 +1,34 @@ +{ + "family": "luckyweeky-task", + "networkMode": "awsvpc", + "requiresCompatibilities": ["FARGATE"], + "cpu": "256", + "memory": "512", + "executionRoleArn": "arn:aws:iam::397064606679:role/luckyweeky-ecs-task-execution-role", + "containerDefinitions": [ + { + "name": "app", + "image": "law10000hours/luckyweeky-server:${TAG}", + "essential": true, + "portMappings": [ + { + "containerPort": 8080 + } + ], + "secrets": [ + { + "name": "LUCKYWEEKY_ENV_VARS", + "valueFrom": "arn:aws:secretsmanager:ap-northeast-2:397064606679:secret:luckyweeky-env-vars-eb8zE2" + } + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "/ecs/luckyweeky", + "awslogs-region": "ap-northeast-2", + "awslogs-stream-prefix": "ecs" + } + } + } + ] +} diff --git a/pom.xml b/pom.xml index 7afe800..43f13f9 100644 --- a/pom.xml +++ b/pom.xml @@ -6,6 +6,9 @@ 0.0.1-SNAPSHOT war + + + ROOT maven-compiler-plugin @@ -35,10 +38,15 @@ provided + + + + + com.mysql mysql-connector-j - 8.3.0 + 8.0.33 @@ -75,5 +83,76 @@ dotenv-java 2.2.4 + + + + + com.squareup.okhttp3 + okhttp + 4.11.0 + + + + + org.json + json + 20230618 + + + + + io.github.cdimascio + dotenv-java + 2.2.4 + + + + + org.slf4j + slf4j-api + 2.0.9 + + + + + + + + + + + + io.jsonwebtoken + jjwt-api + 0.11.5 + + + io.jsonwebtoken + jjwt-impl + 0.11.5 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.11.5 + runtime + + + com.nimbusds + nimbus-jose-jwt + 9.22 + + + redis.clients + jedis + 4.3.1 + + + + com.fasterxml.jackson.core + jackson-databind + 2.18.1 + \ No newline at end of file diff --git a/src/main/java/io/ssafy/luckyweeky/application/image/ImageController.java b/src/main/java/io/ssafy/luckyweeky/application/image/ImageController.java deleted file mode 100644 index a8ce748..0000000 --- a/src/main/java/io/ssafy/luckyweeky/application/image/ImageController.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.ssafy.luckyweeky.application.image; - -import com.google.gson.JsonObject; -import io.ssafy.luckyweeky.application.Controller; -import io.ssafy.luckyweeky.infrastructure.storage.S3Fileloader; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import java.io.InputStream; -import java.io.OutputStream; - -public class ImageController implements Controller { - @Override - public void service(HttpServletRequest request, HttpServletResponse response, JsonObject respJson) throws Exception{ - // URL 경로에서 keyName 추출 - String keyName = request.getRequestURI().split("/")[4]; // "/image/{keyName}"에서 {keyName} 추출 - - // S3에서 파일 스트림 가져오기 - InputStream inputStream = S3Fileloader.getInstance().download(keyName); - - // 응답 헤더 설정 - response.setContentType("image/jpeg"); // JPEG 이미지를 기본으로 설정 - - // 클라이언트로 스트리밍 - try (OutputStream outputStream = response.getOutputStream()) { - byte[] buffer = new byte[1024]; - int bytesRead; - while ((bytesRead = inputStream.read(buffer)) != -1) { - outputStream.write(buffer, 0, bytesRead); - } - }catch (Exception e){ - throw new Exception("Error downloading image 에러 코드 작성"); - } - } -} diff --git a/src/main/java/io/ssafy/luckyweeky/application/schedule/ScheduleController.java b/src/main/java/io/ssafy/luckyweeky/application/schedule/ScheduleController.java deleted file mode 100644 index 5942be1..0000000 --- a/src/main/java/io/ssafy/luckyweeky/application/schedule/ScheduleController.java +++ /dev/null @@ -1,4 +0,0 @@ -package io.ssafy.luckyweeky.application.schedule; - -public class ScheduleController { -} diff --git a/src/main/java/io/ssafy/luckyweeky/application/schedule/SubScheduleController.java b/src/main/java/io/ssafy/luckyweeky/application/schedule/SubScheduleController.java deleted file mode 100644 index 262b87b..0000000 --- a/src/main/java/io/ssafy/luckyweeky/application/schedule/SubScheduleController.java +++ /dev/null @@ -1,4 +0,0 @@ -package io.ssafy.luckyweeky.application.schedule; - -public class SubScheduleController { -} diff --git a/src/main/java/io/ssafy/luckyweeky/application/user/ChangePasswordController.java b/src/main/java/io/ssafy/luckyweeky/application/user/ChangePasswordController.java deleted file mode 100644 index 48fb157..0000000 --- a/src/main/java/io/ssafy/luckyweeky/application/user/ChangePasswordController.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.ssafy.luckyweeky.application.user; - -import com.google.gson.JsonObject; -import io.ssafy.luckyweeky.application.Controller; -import io.ssafy.luckyweeky.domain.user.service.UserService; -import io.ssafy.luckyweeky.infrastructure.config.bean.XmlBeanFactory; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import java.io.IOException; - -public class ChangePasswordController implements Controller { - private final UserService userService; - - public ChangePasswordController() { - this.userService = (UserService) XmlBeanFactory.getBean("userService"); - } - - @Override - public void service(HttpServletRequest request, HttpServletResponse response, - JsonObject respJson) throws ServletException, IOException { - respJson.addProperty("msg","ChangePasswordController"); - } - -} diff --git a/src/main/java/io/ssafy/luckyweeky/application/user/GoogleLoginController.java b/src/main/java/io/ssafy/luckyweeky/application/user/GoogleLoginController.java deleted file mode 100644 index b6c7b01..0000000 --- a/src/main/java/io/ssafy/luckyweeky/application/user/GoogleLoginController.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.ssafy.luckyweeky.application.user; - -import com.google.gson.JsonObject; -import io.ssafy.luckyweeky.application.Controller; -import io.ssafy.luckyweeky.domain.user.service.UserService; -import io.ssafy.luckyweeky.infrastructure.config.bean.XmlBeanFactory; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import java.io.IOException; - -public class GoogleLoginController implements Controller { - private final UserService userService; - - public GoogleLoginController() { - this.userService = (UserService) XmlBeanFactory.getBean("userService"); - } - - @Override - public void service(HttpServletRequest request, HttpServletResponse response, - JsonObject respJson) throws ServletException, IOException { - respJson.addProperty("msg","GoogleLoginController"); - } - -} diff --git a/src/main/java/io/ssafy/luckyweeky/application/user/GoogleSignupController.java b/src/main/java/io/ssafy/luckyweeky/application/user/GoogleSignupController.java deleted file mode 100644 index 356e441..0000000 --- a/src/main/java/io/ssafy/luckyweeky/application/user/GoogleSignupController.java +++ /dev/null @@ -1,25 +0,0 @@ -package io.ssafy.luckyweeky.application.user; - -import com.google.gson.JsonObject; -import io.ssafy.luckyweeky.application.Controller; -import io.ssafy.luckyweeky.domain.user.service.UserService; -import io.ssafy.luckyweeky.infrastructure.config.bean.XmlBeanFactory; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import java.io.IOException; - -public class GoogleSignupController implements Controller { - private final UserService userService; - - public GoogleSignupController() { - this.userService = (UserService) XmlBeanFactory.getBean("userService"); - } - - @Override - public void service(HttpServletRequest request, HttpServletResponse response, - JsonObject respJson) throws ServletException, IOException { - respJson.addProperty("msg","GoogleJoinController"); - } -} diff --git a/src/main/java/io/ssafy/luckyweeky/application/user/LoginController.java b/src/main/java/io/ssafy/luckyweeky/application/user/LoginController.java deleted file mode 100644 index 2789950..0000000 --- a/src/main/java/io/ssafy/luckyweeky/application/user/LoginController.java +++ /dev/null @@ -1,27 +0,0 @@ -package io.ssafy.luckyweeky.application.user; - -import java.io.IOException; - -import com.google.gson.JsonObject; - -import io.ssafy.luckyweeky.application.Controller; -import io.ssafy.luckyweeky.domain.user.service.UserService; -import io.ssafy.luckyweeky.infrastructure.config.bean.XmlBeanFactory; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -public class LoginController implements Controller { - private final UserService userService; - - public LoginController() { - this.userService = (UserService) XmlBeanFactory.getBean("userService"); - } - - @Override - public void service(HttpServletRequest request, HttpServletResponse response, - JsonObject respJson) throws ServletException, IOException { - respJson.addProperty("msg","LoginController"); - } - -} diff --git a/src/main/java/io/ssafy/luckyweeky/application/user/LogoutController.java b/src/main/java/io/ssafy/luckyweeky/application/user/LogoutController.java deleted file mode 100644 index 2f61b04..0000000 --- a/src/main/java/io/ssafy/luckyweeky/application/user/LogoutController.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.ssafy.luckyweeky.application.user; - -import java.io.IOException; - -import com.google.gson.JsonObject; - -import io.ssafy.luckyweeky.application.Controller; -import io.ssafy.luckyweeky.domain.user.service.UserService; -import io.ssafy.luckyweeky.infrastructure.config.bean.XmlBeanFactory; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; - -public class LogoutController implements Controller { - private final UserService userService; - - public LogoutController() { - this.userService = (UserService) XmlBeanFactory.getBean("userService"); - } - - @Override - public void service(HttpServletRequest request, HttpServletResponse response, - JsonObject respJson) throws ServletException, IOException { - respJson.addProperty("msg","LoginController"); - } - -} diff --git a/src/main/java/io/ssafy/luckyweeky/application/user/SignupController.java b/src/main/java/io/ssafy/luckyweeky/application/user/SignupController.java deleted file mode 100644 index f22166d..0000000 --- a/src/main/java/io/ssafy/luckyweeky/application/user/SignupController.java +++ /dev/null @@ -1,78 +0,0 @@ -package io.ssafy.luckyweeky.application.user; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import io.ssafy.luckyweeky.application.Controller; -import io.ssafy.luckyweeky.dispatcher.dto.GeneralSignupUser; -import io.ssafy.luckyweeky.dispatcher.validator.FileValidator; -import io.ssafy.luckyweeky.domain.user.service.UserService; -import io.ssafy.luckyweeky.infrastructure.config.bean.XmlBeanFactory; -import io.ssafy.luckyweeky.infrastructure.util.RequestJsonParser; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.Part; - -import java.util.UUID; - -public class SignupController implements Controller { - private final UserService userService; - - public SignupController() { - this.userService = (UserService) XmlBeanFactory.getBean("userService"); - } - - @Override - public void service(HttpServletRequest request, HttpServletResponse response, - JsonObject respJson) throws Exception { - // 1. JSON 데이터 파싱 - Part jsonPart = request.getPart("user"); - if (jsonPart == null || jsonPart.getSize() == 0) { - throw new IllegalArgumentException("회원가입 필수 데이터 누락 에러코드 작성"); - } - - // 2. JSON 데이터 파싱 - JsonObject jsonObject = RequestJsonParser.getInstance().parse(jsonPart); - - // 기본 프로필 이미지 경로 설정 - String profileImageKey = "profile-images/default.png"; - - // 2. 멀티파트 요청에서 파일 데이터 처리 - Part filePart = null; - if (request.getContentType() != null && request.getContentType().startsWith("multipart/form-data")) { - // 파일 파트 가져오기 - filePart = request.getPart("file"); - if (filePart != null && filePart.getSize() > 0) { - // 파일 MIME 타입, 사이즈, 정상 이미지 검증 - FileValidator.getInstance().isValid(filePart); - - // 파일 이름과 확장자 추출 - String fileName = filePart.getSubmittedFileName(); - String fileExtension = ""; - int lastDotIndex = fileName.lastIndexOf("."); - if (lastDotIndex != -1) { - fileExtension = fileName.substring(lastDotIndex); - } - - // 고유한 파일 이름 생성 - profileImageKey = "profile-images/" + UUID.randomUUID() + fileExtension; - } - } - - // 3. GeneralSignupUser 객체 생성 - GeneralSignupUser user = new GeneralSignupUser( - jsonObject.get("email").getAsString(), - jsonObject.get("password").getAsString(), - jsonObject.get("username").getAsString(), - jsonObject.get("birthDate").getAsString(), - profileImageKey - ); - - // 4. 회원 가입 서비스 호출 - if (userService.generalRegister(user, filePart)) { - respJson.addProperty("email", user.getEmail()); - } else { - throw new Exception("회원가입 실패 코드 작성"); - } - } - -} diff --git a/src/main/java/io/ssafy/luckyweeky/dispatcher/DispatcherServlet.java b/src/main/java/io/ssafy/luckyweeky/common/DispatcherServlet.java similarity index 56% rename from src/main/java/io/ssafy/luckyweeky/dispatcher/DispatcherServlet.java rename to src/main/java/io/ssafy/luckyweeky/common/DispatcherServlet.java index 516b8c0..7cb6e80 100644 --- a/src/main/java/io/ssafy/luckyweeky/dispatcher/DispatcherServlet.java +++ b/src/main/java/io/ssafy/luckyweeky/common/DispatcherServlet.java @@ -1,10 +1,11 @@ -package io.ssafy.luckyweeky.dispatcher; +package io.ssafy.luckyweeky.common; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import io.ssafy.luckyweeky.application.Controller; -import io.ssafy.luckyweeky.infrastructure.config.bean.XmlBeanFactory; -import io.ssafy.luckyweeky.infrastructure.storage.S3Fileloader; +import io.ssafy.luckyweeky.common.config.XmlBeanFactory; +import io.ssafy.luckyweeky.common.implement.Controller; +import io.ssafy.luckyweeky.common.util.stream.ImageStreamUtil; +import io.ssafy.luckyweeky.common.util.url.RequestUrlPath; +import io.ssafy.luckyweeky.common.infrastructure.s3.S3Fileloader; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; @@ -23,6 +24,7 @@ public static String getWebInfPath(){ @Override public void init() throws ServletException { + webInfPath = getServletContext().getRealPath("/WEB-INF"); try { String[] xmlPaths = new String[2]; @@ -43,44 +45,52 @@ public void destroy() { } @Override - protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - process(request, response); + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{ + String uri = request.getRequestURI(); + if(uri.equals("/")){ + // health check, HTTP OK return + response.setStatus(HttpServletResponse.SC_OK); + return; + } + + String path = RequestUrlPath.getURI(request.getRequestURI())[0]; + if(path.equals("qweSJqwo")){ + try { + ImageStreamUtil.streamImage(request,response); + } catch (Exception e) { + JsonObject respJson = new JsonObject(); + respJson.addProperty("result","false"); + respJson.addProperty("error", e.getMessage()); + response.getWriter().append(respJson.toString()); + response.getWriter().close(); + } + } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - process(request, response); - } - - private void process(HttpServletRequest request, HttpServletResponse response) throws IOException { request.setCharacterEncoding("UTF-8"); response.setContentType("application/json;charset=UTF-8"); + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {// CLOVA STT를 위한 설정 + response.setStatus(HttpServletResponse.SC_OK); + return; + } + + PrintWriter out = response.getWriter(); response.setStatus(HttpServletResponse.SC_OK); JsonObject respJson = new JsonObject(); respJson.addProperty("result","true"); try { - String uri = request.getRequestURI(); - String contextPath = "/api/v1/"; - if (!uri.contains(contextPath)) { - respJson.addProperty("errCode","C01"); - throw new IllegalArgumentException("Invalid URI: " + uri); - } - String path = uri.substring(contextPath.length()); - if(path.contains("qweSJqwo")){ - path = "qweSJqwo"; - } - + String path = RequestUrlPath.getURI(request.getRequestURI())[0]; //aB12Xz Controller controller = (Controller) XmlBeanFactory.getBean(path); if (controller == null) { - respJson.addProperty("errCode","C02"); - throw new IllegalArgumentException("Controller not found: " + path); + throw new IllegalArgumentException("C02"); } - controller.service(request, response, respJson); + controller.handleRequest(request, response, respJson); } catch (Exception e) { respJson.addProperty("result","false"); respJson.addProperty("errCode",e.getMessage()); - e.printStackTrace(); }finally { out.append(respJson.toString()); out.close(); diff --git a/src/main/java/io/ssafy/luckyweeky/infrastructure/config/bean/BeanDefinition.java b/src/main/java/io/ssafy/luckyweeky/common/config/BeanDefinition.java similarity index 81% rename from src/main/java/io/ssafy/luckyweeky/infrastructure/config/bean/BeanDefinition.java rename to src/main/java/io/ssafy/luckyweeky/common/config/BeanDefinition.java index b31ed57..7000c7d 100644 --- a/src/main/java/io/ssafy/luckyweeky/infrastructure/config/bean/BeanDefinition.java +++ b/src/main/java/io/ssafy/luckyweeky/common/config/BeanDefinition.java @@ -1,4 +1,4 @@ -package io.ssafy.luckyweeky.infrastructure.config.bean; +package io.ssafy.luckyweeky.common.config; public class BeanDefinition { private String id; diff --git a/src/main/java/io/ssafy/luckyweeky/infrastructure/config/bean/XmlBeanFactory.java b/src/main/java/io/ssafy/luckyweeky/common/config/XmlBeanFactory.java similarity index 90% rename from src/main/java/io/ssafy/luckyweeky/infrastructure/config/bean/XmlBeanFactory.java rename to src/main/java/io/ssafy/luckyweeky/common/config/XmlBeanFactory.java index e28bd75..51b651d 100644 --- a/src/main/java/io/ssafy/luckyweeky/infrastructure/config/bean/XmlBeanFactory.java +++ b/src/main/java/io/ssafy/luckyweeky/common/config/XmlBeanFactory.java @@ -1,4 +1,4 @@ -package io.ssafy.luckyweeky.infrastructure.config.bean; +package io.ssafy.luckyweeky.common.config; import java.lang.reflect.Constructor; import java.util.HashMap; diff --git a/src/main/java/io/ssafy/luckyweeky/infrastructure/config/bean/XmlParser.java b/src/main/java/io/ssafy/luckyweeky/common/config/XmlParser.java similarity index 92% rename from src/main/java/io/ssafy/luckyweeky/infrastructure/config/bean/XmlParser.java rename to src/main/java/io/ssafy/luckyweeky/common/config/XmlParser.java index 2b10442..1487c4a 100644 --- a/src/main/java/io/ssafy/luckyweeky/infrastructure/config/bean/XmlParser.java +++ b/src/main/java/io/ssafy/luckyweeky/common/config/XmlParser.java @@ -1,4 +1,4 @@ -package io.ssafy.luckyweeky.infrastructure.config.bean; +package io.ssafy.luckyweeky.common.config; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/io/ssafy/luckyweeky/common/env/SecretManagerContextListener.java b/src/main/java/io/ssafy/luckyweeky/common/env/SecretManagerContextListener.java new file mode 100644 index 0000000..7a7ee2e --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/common/env/SecretManagerContextListener.java @@ -0,0 +1,67 @@ +package io.ssafy.luckyweeky.common.env; + +import jakarta.servlet.ServletContextEvent; +import jakarta.servlet.ServletContextListener; +import org.json.JSONObject; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.Scanner; + +public class SecretManagerContextListener implements ServletContextListener { + @Override + public void contextInitialized(ServletContextEvent sce) { + try { + String secretJson = System.getenv("LUCKYWEEKY_ENV_VARS"); + + // 환경 변수 확인 + if (secretJson == null) { +// System.out.println("환경 변수 'LUCKYWEEKY_ENV_VARS'가 설정되지 않았습니다. 로컬 설정 파일을 사용합니다."); + secretJson = readLocalSecrets(sce); // 로컬 JSON 파일 읽기 + } + + if (secretJson == null) { + throw new IllegalStateException("환경 변수와 로컬 설정 파일이 모두 설정되지 않았습니다."); + } + + // JSON 파싱 및 System.setProperty 설정 + JSONObject secrets = new JSONObject(secretJson); + secrets.keySet().forEach(key -> { + String value = secrets.getString(key); + System.setProperty(key, value); +// System.out.println("환경 변수 '" + key + "' 값이 설정되었습니다"); + }); + + } catch (Exception e) { + throw new RuntimeException("환경 변수 초기화에 실패했습니다.", e); + } + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + // 필요 시 리소스 정리 + } + + // 로컬 JSON 파일 읽기 + private String readLocalSecrets(ServletContextEvent sce) { + try { + String realPath = sce.getServletContext().getRealPath("/WEB-INF/local-secrets.json"); +// System.out.println("로컬 설정 파일 경로: " + realPath); + + File file = new File(realPath); + if (!file.exists()) { +// System.err.println("로컬 설정 파일이 존재하지 않습니다: " + realPath); + return null; + } + + try (Scanner scanner = new Scanner(new FileReader(file))) { + scanner.useDelimiter("\\Z"); // 파일 끝까지 읽기 + return scanner.next(); + } + } catch (IOException e) { +// System.err.println("로컬 설정 파일을 읽는 중 오류가 발생했습니다: " + e.getMessage()); + return null; + } + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/common/filter/CORSFilter.java b/src/main/java/io/ssafy/luckyweeky/common/filter/CORSFilter.java new file mode 100644 index 0000000..8cd66e0 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/common/filter/CORSFilter.java @@ -0,0 +1,48 @@ +package io.ssafy.luckyweeky.common.filter; + +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.util.Set; + +public class CORSFilter implements Filter { + // 허용할 Origin 집합 + private static final Set ALLOWED_ORIGINS = Set.of( + "http://localhost:3000", + "http://localhost:5173", + "https://luckyweeky.store", + "http://luckyweeky.store" + ); + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + + // 요청의 Origin 가져오기 + String origin = httpRequest.getHeader("Origin"); + // Origin이 허용된 목록에 있는지 확인 + if (origin == null || !ALLOWED_ORIGINS.contains(origin)) { + httpResponse.setStatus(HttpServletResponse.SC_OK); + httpResponse.getWriter().write("{\"error\": \"This request is not allowed\"}"); + return; + } + // CORS 헤더 설정 + httpResponse.setHeader("Access-Control-Allow-Origin", origin); + // 브라우저가 CORS 정책을 확인하기 위해 서버에 OPTIONS 요청을 보냄 + httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); + httpResponse.setHeader("Access-Control-Allow-Credentials", "true"); // 쿠키 전송을 허용하려면 추가 + + // OPTIONS 요청에 대한 처리 (Preflight 요청) + if ("OPTIONS".equalsIgnoreCase(httpRequest.getMethod())) { + httpResponse.setStatus(HttpServletResponse.SC_OK); + return; + } + + // 다음 필터나 서블릿으로 요청 전달 + chain.doFilter(request, response); + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/common/filter/JwtAuthenticationFilter.java b/src/main/java/io/ssafy/luckyweeky/common/filter/JwtAuthenticationFilter.java new file mode 100644 index 0000000..f6e7752 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/common/filter/JwtAuthenticationFilter.java @@ -0,0 +1,59 @@ +package io.ssafy.luckyweeky.common.filter; + +import io.jsonwebtoken.Claims; +import io.ssafy.luckyweeky.common.infrastructure.provider.JwtTokenProvider; + +import io.ssafy.luckyweeky.common.util.security.CookieUtil; +import io.ssafy.luckyweeky.common.util.url.RequestUrlPath; +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.util.Set; + +public class JwtAuthenticationFilter implements Filter { + // 제외할 URL 목록 + private static final Set EXCLUDE_URLS = Set.of + ( + "aB12Xz/LWyAtd", + "aB12Xz/RClmJ", + "qweSJqwo" + ); + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletResponse httpResponse = (HttpServletResponse) response; + + String path = RequestUrlPath.url(httpRequest.getRequestURI()); + + // 토큰 검증이 필요없는 url + if (EXCLUDE_URLS.contains(path)) { + chain.doFilter(request, response); + return; + } + + try { + // 1-1. 헤더에서 Authorization 값 추출 + // 1-2. 토큰 재발급 요청일때 Cookie에서 refreshToken 추출 + String token = EXCLUDE_URLS.contains("aB12Xz/TGCOwi")? CookieUtil.getRefreshToken(httpRequest) :JwtTokenProvider.getInstance().resolveToken(httpRequest); + // 2. 토큰 검증 + if (token != null && JwtTokenProvider.getInstance().validateToken(token)) { + // 토큰이 유효한 경우: 사용자 정보를 SecurityContext에 저장하거나 클레임 추출 + Long userId = Long.parseLong(JwtTokenProvider.getInstance().getSubject(token)); + httpRequest.setAttribute("userId", userId); // 요청 속성에 사용자 정보 저장 + chain.doFilter(request, response); + return; + } + httpResponse.setStatus(HttpServletResponse.SC_OK); + httpResponse.getWriter().write("{\"error\": \"token validation failed\"}"); + } catch (Exception e) { + httpResponse.setStatus(HttpServletResponse.SC_OK); + httpResponse.getWriter().write("{\"error\": \"token validation failed\"}"); + } + } + + +} diff --git a/src/main/java/io/ssafy/luckyweeky/application/Controller.java b/src/main/java/io/ssafy/luckyweeky/common/implement/Controller.java similarity index 54% rename from src/main/java/io/ssafy/luckyweeky/application/Controller.java rename to src/main/java/io/ssafy/luckyweeky/common/implement/Controller.java index ae94de1..d0ef1c9 100644 --- a/src/main/java/io/ssafy/luckyweeky/application/Controller.java +++ b/src/main/java/io/ssafy/luckyweeky/common/implement/Controller.java @@ -1,16 +1,12 @@ -package io.ssafy.luckyweeky.application; - -import java.io.IOException; +package io.ssafy.luckyweeky.common.implement; import com.google.gson.JsonObject; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; + public interface Controller { - public void service( - HttpServletRequest request, - HttpServletResponse response, - JsonObject respJson - ) throws Exception; + void handleRequest(HttpServletRequest request, HttpServletResponse response, JsonObject respJson) throws ServletException, IOException; } diff --git a/src/main/java/io/ssafy/luckyweeky/common/implement/Converter.java b/src/main/java/io/ssafy/luckyweeky/common/implement/Converter.java new file mode 100644 index 0000000..331bd11 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/common/implement/Converter.java @@ -0,0 +1,5 @@ +package io.ssafy.luckyweeky.common.implement; + +public interface Converter { + T convert(S source); +} diff --git a/src/main/java/io/ssafy/luckyweeky/infrastructure/cache/ImageCacheManager.java b/src/main/java/io/ssafy/luckyweeky/common/infrastructure/cache/ImageCacheManager.java similarity index 90% rename from src/main/java/io/ssafy/luckyweeky/infrastructure/cache/ImageCacheManager.java rename to src/main/java/io/ssafy/luckyweeky/common/infrastructure/cache/ImageCacheManager.java index 91b3153..7ed230d 100644 --- a/src/main/java/io/ssafy/luckyweeky/infrastructure/cache/ImageCacheManager.java +++ b/src/main/java/io/ssafy/luckyweeky/common/infrastructure/cache/ImageCacheManager.java @@ -1,4 +1,4 @@ -package io.ssafy.luckyweeky.infrastructure.cache; +package io.ssafy.luckyweeky.common.infrastructure.cache; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/src/main/java/io/ssafy/luckyweeky/common/infrastructure/cache/RedisManager.java b/src/main/java/io/ssafy/luckyweeky/common/infrastructure/cache/RedisManager.java new file mode 100644 index 0000000..6872946 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/common/infrastructure/cache/RedisManager.java @@ -0,0 +1,46 @@ +package io.ssafy.luckyweeky.common.infrastructure.cache; + +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; + +import java.util.Set; + +public class RedisManager { + private static JedisPool pool = null; + + static { + String redisHost = getPropertyVariable("REDIS_ENDPOINT"); // AWS에서 확인한 엔드포인트 + int redisPort = 6379; // 기본 포트 + + JedisPoolConfig poolConfig = new JedisPoolConfig(); + poolConfig.setMaxTotal(50); // 최대 연결 수 설정 + // JedisPool 객체를 생성하여 Redis 연결 풀 초기화 + // 파라미터: 설정(config), 호스트, 포트, 연결 시간 초과(ms), 암호, SSL 사용 여부 + pool = new JedisPool(poolConfig, redisHost, redisPort, 2000, null, false); + } + + public static JedisPool getPool() { + return pool; + } + private static String getPropertyVariable(String key) { + String value = System.getProperty(key); + System.out.println(value); + if (value == null || value.isEmpty()) { + throw new IllegalStateException("Environment Variable '" + key + "' is empty."); + } + return value; + } + + public static boolean invalidateCacheBasedOnTitle(String prefix) { + try (Jedis jedis = getPool().getResource()) { + Set keys = jedis.keys(prefix+":*"); + for (String key : keys) { + jedis.del(key); // 관련 캐시 삭제 + } + return true; + } catch (Exception e) { + return false; + } + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/common/infrastructure/persistence/MyBatisSqlSessionFactory.java b/src/main/java/io/ssafy/luckyweeky/common/infrastructure/persistence/MyBatisSqlSessionFactory.java new file mode 100644 index 0000000..0561bc7 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/common/infrastructure/persistence/MyBatisSqlSessionFactory.java @@ -0,0 +1,70 @@ +package io.ssafy.luckyweeky.common.infrastructure.persistence; + +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +public class MyBatisSqlSessionFactory { + private static volatile SqlSessionFactory sqlSessionFactory; + + public static SqlSessionFactory getSqlSessionFactory() { + if (sqlSessionFactory == null) { + synchronized (MyBatisSqlSessionFactory.class) { + if (sqlSessionFactory == null) { + sqlSessionFactory = initializeFactory(); + } + } + } + return sqlSessionFactory; + } + + private static SqlSessionFactory initializeFactory() { + try { + // 드라이버 명시적 로드 + Class.forName("com.mysql.cj.jdbc.Driver"); +// System.out.println("MySQL JDBC Driver loaded successfully."); + + // 환경 변수에서 값을 가져오고 유효성 검사 + String DB_URL = getPropertyVariable("DB_URL"); + String DB_USERNAME = getPropertyVariable("DB_USERNAME"); + String DB_PASSWORD = getPropertyVariable("DB_PASSWORD"); + + // Properties 객체에 환경 변수 추가 + Properties props = new Properties(); + props.setProperty("DB_URL", DB_URL); + props.setProperty("DB_USERNAME", DB_USERNAME); + props.setProperty("DB_PASSWORD", DB_PASSWORD); +// +// System.out.println("Environment variables loaded:"); +// System.out.println("DB_URL: " + DB_URL); +// System.out.println("DB_USERNAME: " + DB_USERNAME); + // 비밀번호는 보안상 로깅하지 않음 + + // MyBatis 설정 파일 읽기 + String resource = "mybatis-config.xml"; // 설정 파일 경로 + try (InputStream inputStream = Resources.getResourceAsStream(resource)) { + SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream, props); +// System.out.println("MyBatis SqlSessionFactory initialized successfully."); + return factory; + } + } catch (IOException e) { + throw new RuntimeException("Failed to initialize SqlSessionFactory due to IO error. Check your MyBatis configuration file.", e); + } catch (IllegalStateException e) { + throw new RuntimeException("Failed to initialize SqlSessionFactory due to missing environment variables.", e); + } catch (ClassNotFoundException e) { + throw new RuntimeException("ClassNotFoundException.MYSQL Driver not found.", e); + } + } + + private static String getPropertyVariable(String key) { + String value = System.getProperty(key); + if (value == null || value.isEmpty()) { + throw new IllegalStateException("Environment Variable '" + key + "' is empty."); + } + return value; + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/common/infrastructure/provider/JwtTokenProvider.java b/src/main/java/io/ssafy/luckyweeky/common/infrastructure/provider/JwtTokenProvider.java new file mode 100644 index 0000000..b328437 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/common/infrastructure/provider/JwtTokenProvider.java @@ -0,0 +1,97 @@ +package io.ssafy.luckyweeky.common.infrastructure.provider; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; + +import java.security.Key; +import java.util.Date; + +public class JwtTokenProvider { + private final static JwtTokenProvider instance = new JwtTokenProvider(); + private final Key SECREATKEY; // JWT 서명을 위한 Secret Key + + private JwtTokenProvider() { + String secretKey = System.getProperty("SECREATKEY"); // 환경 변수에서 Secret Key 가져오기 + if (secretKey == null || secretKey.isEmpty()) { + throw new IllegalArgumentException("Secret key is not defined in environment variables."); + } + this.SECREATKEY = Keys.hmacShaKeyFor(secretKey.getBytes()); // Secret Key 생성 + } + + public static JwtTokenProvider getInstance() { + return instance; + } + + /** + * JWT 토큰 생성 + * @param subject 토큰의 Subject (예: 사용자 ID) + * @param claims 추가 클레임 (예: 권한 정보) + * @return 생성된 JWT 토큰 + */ + public String createToken(String subject, Claims claims, long validityInMilliseconds) { + Date now = new Date(); + Date validity = new Date(now.getTime() + validityInMilliseconds); + + return Jwts.builder() + .setClaims(claims) // 클레임 설정 + .setSubject(subject) // Subject 설정 (예: 사용자 ID) + .setIssuedAt(now) // 생성 시간 + .setExpiration(validity) // 만료 시간 + .signWith(SECREATKEY, SignatureAlgorithm.HS256) // 서명 알고리즘 + .compact(); // 토큰 생성 + } + + /** + * JWT 토큰에서 클레임 추출 + * @param token JWT 토큰 + * @return 토큰에 포함된 클레임 + */ + public Claims getClaims(String token) { + return Jwts.parserBuilder() + .setSigningKey(SECREATKEY) + .build() + .parseClaimsJws(token) + .getBody(); + } + + /** + * 토큰 유효성 검사 + * @param token JWT 토큰 + * @return 토큰이 유효하면 true, 그렇지 않으면 false + */ + public boolean validateToken(String token) { + try { + Claims claims = getClaims(token); + return !claims.getExpiration().before(new Date()); // 만료 시간 확인 + } catch (Exception e) { + return false; + } + } + + /** + * 토큰에서 Subject 추출 + * @param token JWT 토큰 + * @return Subject (예: 사용자 ID) + */ + public String getSubject(String token) { + return getClaims(token).getSubject(); + } + + /** + * request header에서 Authorization 값 추출 + * + * @param request HttpServletRequest + * @return JWT 토큰 + */ + public String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (bearerToken != null && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); // "Bearer " 이후의 토큰 값 추출 + } + return null; + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/infrastructure/storage/S3Fileloader.java b/src/main/java/io/ssafy/luckyweeky/common/infrastructure/s3/S3Fileloader.java similarity index 65% rename from src/main/java/io/ssafy/luckyweeky/infrastructure/storage/S3Fileloader.java rename to src/main/java/io/ssafy/luckyweeky/common/infrastructure/s3/S3Fileloader.java index 0c4009d..c642c1b 100644 --- a/src/main/java/io/ssafy/luckyweeky/infrastructure/storage/S3Fileloader.java +++ b/src/main/java/io/ssafy/luckyweeky/common/infrastructure/s3/S3Fileloader.java @@ -1,7 +1,5 @@ -package io.ssafy.luckyweeky.infrastructure.storage; +package io.ssafy.luckyweeky.common.infrastructure.s3; -import io.github.cdimascio.dotenv.Dotenv; -import io.ssafy.luckyweeky.dispatcher.DispatcherServlet; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.regions.Region; @@ -11,6 +9,7 @@ import software.amazon.awssdk.services.s3.model.S3Exception; import java.io.File; +import java.io.IOException; import java.io.InputStream; public class S3Fileloader { @@ -27,21 +26,30 @@ private S3Fileloader(String accessKey, String secretKey) { .credentialsProvider(StaticCredentialsProvider.create(awsCreds)) .build(); } + public static synchronized S3Fileloader getInstance() { if (instance == null) { - Dotenv dotenv = Dotenv.configure() - .directory(DispatcherServlet.getWebInfPath()+ File.separatorChar) - .filename(".env") - .load(); - String accessKey = dotenv.get("AWS_ACCESS_KEY"); - String secretKey = dotenv.get("AWS_SECRET_KEY"); - BUCKET_NAME = dotenv.get("BUCKET_NAME"); + // 환경 변수에서 값 가져오기 + String accessKey = System.getProperty("AWS_ACCESS_KEY"); + String secretKey = System.getProperty("AWS_SECRET_KEY"); + BUCKET_NAME = System.getProperty("BUCKET_NAME"); + + // 유효성 검사 + if (accessKey == null || secretKey == null || BUCKET_NAME == null) { + throw new IllegalStateException( + "환경 변수 누락: " + + (accessKey == null ? "AWS_ACCESS_KEY " : "") + + (secretKey == null ? "AWS_SECRET_KEY " : "") + + (BUCKET_NAME == null ? "BUCKET_NAME" : "") + ); + } + instance = new S3Fileloader(accessKey, secretKey); } return instance; } - public String upload(File file, String keyName) throws Exception { + public String upload(File file, String keyName) throws IOException { try { PutObjectRequest putObjectRequest = PutObjectRequest.builder() .bucket(BUCKET_NAME) @@ -50,7 +58,7 @@ public String upload(File file, String keyName) throws Exception { s3Client.putObject(putObjectRequest, file.toPath()); return "https://" + BUCKET_NAME + ".s3." + REGION + ".amazonaws.com/" + keyName; } catch (S3Exception e) { - throw new Exception("S3이미지업로드에러코드작성"); + throw new IOException("S3 이미지 업로드 에러", e); } } @@ -64,7 +72,7 @@ public InputStream download(String keyName) throws Exception { return s3Client.getObject(getObjectRequest); } catch (S3Exception e) { e.printStackTrace(); - throw new Exception("S3이미지 파일 not found에러"); + throw new Exception("S3 이미지 파일 not found 에러", e); } } diff --git a/src/main/java/io/ssafy/luckyweeky/infrastructure/util/SnowflakeIdGenerator.java b/src/main/java/io/ssafy/luckyweeky/common/util/generator/SnowflakeIdGenerator.java similarity index 75% rename from src/main/java/io/ssafy/luckyweeky/infrastructure/util/SnowflakeIdGenerator.java rename to src/main/java/io/ssafy/luckyweeky/common/util/generator/SnowflakeIdGenerator.java index 8a1ff43..84309d2 100644 --- a/src/main/java/io/ssafy/luckyweeky/infrastructure/util/SnowflakeIdGenerator.java +++ b/src/main/java/io/ssafy/luckyweeky/common/util/generator/SnowflakeIdGenerator.java @@ -1,9 +1,4 @@ -package io.ssafy.luckyweeky.infrastructure.util; - -import io.github.cdimascio.dotenv.Dotenv; -import io.ssafy.luckyweeky.dispatcher.DispatcherServlet; - -import java.io.File; +package io.ssafy.luckyweeky.common.util.generator; public class SnowflakeIdGenerator { private static SnowflakeIdGenerator instance; // 싱글톤 인스턴스 @@ -23,16 +18,16 @@ public class SnowflakeIdGenerator { // private 생성자 private SnowflakeIdGenerator() { - // 1. .env 파일 로드 - Dotenv dotenv = Dotenv.configure() - .directory(DispatcherServlet.getWebInfPath()+ File.separatorChar) - .filename(".env") // 파일 이름 지정 (기본값: ".env") - .load(); - String machineIdStr = dotenv.get("MACHINE_ID"); + // 환경 변수에서 MACHINE_ID 가져오기 + String machineIdStr = System.getProperty("MACHINE_ID"); if (machineIdStr == null) { - throw new IllegalArgumentException("Machine ID not set"); + throw new IllegalStateException("환경 변수 'MACHINE_ID'가 설정되지 않았습니다."); } this.machineId = Long.parseLong(machineIdStr); + + if (this.machineId < 0 || this.machineId > maxMachineId) { + throw new IllegalArgumentException("Machine ID는 0 이상 " + maxMachineId + " 이하이어야 합니다."); + } } // 싱글톤 인스턴스 반환 메서드 @@ -47,7 +42,7 @@ public synchronized long nextId() { long currentTimestamp = System.currentTimeMillis(); if (currentTimestamp < lastTimestamp) { - throw new RuntimeException("Clock moved backwards. Refusing to generate ID."); + throw new RuntimeException("시계가 역방향으로 이동했습니다. ID 생성을 거부합니다."); } if (currentTimestamp == lastTimestamp) { diff --git a/src/main/java/io/ssafy/luckyweeky/infrastructure/util/RequestJsonParser.java b/src/main/java/io/ssafy/luckyweeky/common/util/parser/RequestJsonParser.java similarity index 59% rename from src/main/java/io/ssafy/luckyweeky/infrastructure/util/RequestJsonParser.java rename to src/main/java/io/ssafy/luckyweeky/common/util/parser/RequestJsonParser.java index ab3a180..18b810d 100644 --- a/src/main/java/io/ssafy/luckyweeky/infrastructure/util/RequestJsonParser.java +++ b/src/main/java/io/ssafy/luckyweeky/common/util/parser/RequestJsonParser.java @@ -1,4 +1,4 @@ -package io.ssafy.luckyweeky.infrastructure.util; +package io.ssafy.luckyweeky.common.util.parser; import com.google.gson.Gson; @@ -34,8 +34,25 @@ public JsonObject parse(Part jsonPart) throws IOException { // JSON 데이터 유효성 확인 후 파싱 return JsonParser.parseString(jsonString).getAsJsonObject(); - }catch (Exception e){ + }catch (IOException e) { throw new IOException("Failed to parse JSON에러코드"); } } + + // 바디에서 JSON 데이터를 직접 파싱 + public JsonObject parseFromBody(BufferedReader bodyReader) throws IOException { + try { + StringBuilder stringBuilder = new StringBuilder(); + String line; + while ((line = bodyReader.readLine()) != null) { + stringBuilder.append(line); + } + String jsonString = stringBuilder.toString(); + + // JSON 데이터 유효성 확인 후 파싱 + return JsonParser.parseString(jsonString).getAsJsonObject(); + } catch (Exception e) { + throw new IOException("Failed to parse JSON from body"); + } + } } diff --git a/src/main/java/io/ssafy/luckyweeky/common/util/security/CookieUtil.java b/src/main/java/io/ssafy/luckyweeky/common/util/security/CookieUtil.java new file mode 100644 index 0000000..540a477 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/common/util/security/CookieUtil.java @@ -0,0 +1,34 @@ +package io.ssafy.luckyweeky.common.util.security; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class CookieUtil { + public static String getRefreshToken(HttpServletRequest request) { + if (request.getCookies() != null) { + for (Cookie cookie : request.getCookies()) { + if ("refreshToken".equals(cookie.getName())) { + return cookie.getValue(); + } + } + } + return null; + } + + public static void addRefreshTokenCookie(HttpServletResponse response, String refreshToken) { + Cookie cookie = new Cookie("refreshToken", refreshToken); + cookie.setHttpOnly(true); + cookie.setSecure(true); + cookie.setMaxAge(7 * 24 * 60 * 60); // 7일 + response.addCookie(cookie); + } + + public static void deleteRefreshTokenCookie(HttpServletResponse response) { + Cookie cookie = new Cookie("refreshToken", null); + cookie.setHttpOnly(true); + cookie.setSecure(true); + cookie.setMaxAge(0); // 즉시 만료 + response.addCookie(cookie); + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/infrastructure/util/OpenCrypt.java b/src/main/java/io/ssafy/luckyweeky/common/util/security/OpenCrypt.java similarity index 95% rename from src/main/java/io/ssafy/luckyweeky/infrastructure/util/OpenCrypt.java rename to src/main/java/io/ssafy/luckyweeky/common/util/security/OpenCrypt.java index 859a14d..6d5cedb 100644 --- a/src/main/java/io/ssafy/luckyweeky/infrastructure/util/OpenCrypt.java +++ b/src/main/java/io/ssafy/luckyweeky/common/util/security/OpenCrypt.java @@ -1,103 +1,102 @@ -package io.ssafy.luckyweeky.infrastructure.util; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; - -import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -public class OpenCrypt { - - public static String createEncryptSalt() { - SecureRandom sr = new SecureRandom(); - - byte[] saltbyte = new byte[20]; - sr.nextBytes(saltbyte); - - StringBuffer sb = new StringBuffer(); - for(byte b : saltbyte) { - sb.append(String.format("%02x", b)); - } - - String salt = sb.toString(); - return salt; - } - - public static String getEncryptPassword(String password, String salt) { - return byteArrayToHex(getSHA256(password,salt)); - } - - public static byte[] getSHA256(String source, String salt) { - byte byteData[] = null; - try { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - md.update(source.getBytes()); - md.update(salt.getBytes()); - byteData = md.digest(); -// System.out.println("원문: " + source + " SHA-256: " + byteData.length + "," + byteArrayToHex(byteData)); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } - return byteData; - } - - public static byte[] generateKey(String algorithm, int keySize) throws NoSuchAlgorithmException { - - KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm); - keyGenerator.init(keySize); - SecretKey key = keyGenerator.generateKey(); - return key.getEncoded(); - } - - public static String aesEncrypt(String msg, byte[] key) throws Exception { - SecretKeySpec skeySpec = new SecretKeySpec(key, "AES"); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - String iv = "AAAAAAAAAAAAAAAA"; - cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(iv.getBytes())); - byte[] encrypted = cipher.doFinal(msg.getBytes()); - return byteArrayToHex(encrypted); - } - - public static String aesDecrypt(String msg, byte[] key) throws Exception { - SecretKeySpec skeySpec = new SecretKeySpec(key, "AES"); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - String iv = "AAAAAAAAAAAAAAAA"; - cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(iv.getBytes())); - byte[] encrypted = hexToByteArray(msg); - byte[] original = cipher.doFinal(encrypted); - return new String(original); - } - - public static byte[] hexToByteArray(String hex) { - if (hex == null || hex.length() == 0) { - return null; - } - - byte[] ba = new byte[hex.length() / 2]; - for (int i = 0; i < ba.length; i++) { - ba[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16); - } - return ba; - } - - // byte[] to hex - public static String byteArrayToHex(byte[] ba) { - if (ba == null || ba.length == 0) { - return null; - } - - StringBuffer sb = new StringBuffer(ba.length * 2); - String hexNumber; - for (int x = 0; x < ba.length; x++) { - hexNumber = "0" + Integer.toHexString(0xff & ba[x]); - - sb.append(hexNumber.substring(hexNumber.length() - 2)); - } - return sb.toString(); - } - +package io.ssafy.luckyweeky.common.util.security; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class OpenCrypt { + + public static String createEncryptSalt() { + SecureRandom sr = new SecureRandom(); + + byte[] saltbyte = new byte[20]; + sr.nextBytes(saltbyte); + + StringBuffer sb = new StringBuffer(); + for(byte b : saltbyte) { + sb.append(String.format("%02x", b)); + } + + String salt = sb.toString(); + return salt; + } + + public static String getEncryptPassword(String password, String salt) { + return byteArrayToHex(getSHA256(password,salt)); + } + + public static byte[] getSHA256(String source, String salt) { + byte byteData[] = null; + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(source.getBytes()); + md.update(salt.getBytes()); + byteData = md.digest(); +// System.out.println("원문: " + source + " SHA-256: " + byteData.length + "," + byteArrayToHex(byteData)); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + return byteData; + } + + public static byte[] generateKey(String algorithm, int keySize) throws NoSuchAlgorithmException { + KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm); + keyGenerator.init(keySize); + SecretKey key = keyGenerator.generateKey(); + return key.getEncoded(); + } + + public static String aesEncrypt(String msg, byte[] key) throws Exception { + SecretKeySpec skeySpec = new SecretKeySpec(key, "AES"); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + String iv = "AAAAAAAAAAAAAAAA"; + cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(iv.getBytes())); + byte[] encrypted = cipher.doFinal(msg.getBytes()); + return byteArrayToHex(encrypted); + } + + public static String aesDecrypt(String msg, byte[] key) throws Exception { + SecretKeySpec skeySpec = new SecretKeySpec(key, "AES"); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + String iv = "AAAAAAAAAAAAAAAA"; + cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(iv.getBytes())); + byte[] encrypted = hexToByteArray(msg); + byte[] original = cipher.doFinal(encrypted); + return new String(original); + } + + public static byte[] hexToByteArray(String hex) { + if (hex == null || hex.length() == 0) { + return null; + } + + byte[] ba = new byte[hex.length() / 2]; + for (int i = 0; i < ba.length; i++) { + ba[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16); + } + return ba; + } + + // byte[] to hex + public static String byteArrayToHex(byte[] ba) { + if (ba == null || ba.length == 0) { + return null; + } + + StringBuffer sb = new StringBuffer(ba.length * 2); + String hexNumber; + for (int x = 0; x < ba.length; x++) { + hexNumber = "0" + Integer.toHexString(0xff & ba[x]); + + sb.append(hexNumber.substring(hexNumber.length() - 2)); + } + return sb.toString(); + } + } \ No newline at end of file diff --git a/src/main/java/io/ssafy/luckyweeky/common/util/stream/FileHandler.java b/src/main/java/io/ssafy/luckyweeky/common/util/stream/FileHandler.java new file mode 100644 index 0000000..fcf8b69 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/common/util/stream/FileHandler.java @@ -0,0 +1,35 @@ +package io.ssafy.luckyweeky.common.util.stream; + +import io.ssafy.luckyweeky.user.application.validator.FileValidator; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.Part; + +import java.io.IOException; +import java.util.UUID; + +public class FileHandler { + private static final String DEFAULT_PROFILE_IMAGE = "profile-images/default.png"; + + public static Part getFilePart(HttpServletRequest request, String partName) throws IOException, ServletException { + if (request.getContentType() != null && request.getContentType().startsWith("multipart/form-data")) { + return request.getPart(partName); + } + return null; + } + + public static String processFilePart(Part filePart) { + if (filePart != null && filePart.getSize() > 0) { + FileValidator.getInstance().isValid(filePart); + String uniqueFileName = UUID.randomUUID() + getExtension(filePart); + return "profile-images/" + uniqueFileName; + } + return DEFAULT_PROFILE_IMAGE; + } + + private static String getExtension(Part filePart) { + String fileName = filePart.getSubmittedFileName(); + int lastDotIndex = fileName.lastIndexOf("."); + return (lastDotIndex != -1) ? fileName.substring(lastDotIndex) : ""; + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/common/util/stream/ImageStreamUtil.java b/src/main/java/io/ssafy/luckyweeky/common/util/stream/ImageStreamUtil.java new file mode 100644 index 0000000..ce364ea --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/common/util/stream/ImageStreamUtil.java @@ -0,0 +1,61 @@ +package io.ssafy.luckyweeky.common.util.stream; + +import io.ssafy.luckyweeky.common.infrastructure.s3.S3Fileloader; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class ImageStreamUtil { + private static final String PREFIX = "/api/v1/qweSJqwo/"; + private static final int BUFFER_SIZE = 8192; + + // InputStream에서 OutputStream으로 스트리밍 처리 + public static void streamImage(HttpServletRequest request, HttpServletResponse response) throws IOException { + String keyName = request.getRequestURI().substring(PREFIX.length()); + + // 확장자 추출 및 MIME 타입 설정 + String extension = keyName.substring(keyName.lastIndexOf(".") + 1).toLowerCase(); + String contentType = getMimeType(extension); + response.setContentType(contentType); + + // S3에서 파일 스트림 가져오기 + try (InputStream inputStream = S3Fileloader.getInstance().download(keyName)) { + if (inputStream == null) { + throw new IOException("이미지를 찾을 수 없습니다."); + } + // 클라이언트로 스트리밍 + try (OutputStream outputStream = response.getOutputStream()) { + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + outputStream.flush(); + } + }catch (Exception e) { + throw new IOException("이미지를 찾을 수 없습니다."); + } + } + + // 확장자에 따른 MIME 타입 반환 + private static String getMimeType(String extension) { + switch (extension) { + case "jpg": + case "jpeg": + return "image/jpeg"; + case "png": + return "image/png"; + case "gif": + return "image/gif"; + case "bmp": + return "image/bmp"; + case "webp": + return "image/webp"; + default: + return "application/octet-stream"; // 기본 MIME 타입 + } + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/common/util/url/RequestUrlPath.java b/src/main/java/io/ssafy/luckyweeky/common/util/url/RequestUrlPath.java new file mode 100644 index 0000000..37933c0 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/common/util/url/RequestUrlPath.java @@ -0,0 +1,20 @@ +package io.ssafy.luckyweeky.common.util.url; + +public class RequestUrlPath { + private static final String COMMON_URL="/api/v1/"; + private static final int COMMON_URL_LENGTH = COMMON_URL.length(); + + public static String[] getURI(String path) throws IllegalArgumentException{ + if(!path.contains(COMMON_URL)){ + throw new IllegalArgumentException("잘못된요청url"); + } + return path.substring(COMMON_URL_LENGTH).split("/"); + } + + public static String url(String path) throws IllegalArgumentException{ + if(!path.contains(COMMON_URL)){ + throw new IllegalArgumentException("잘못된요청url"); + } + return path.substring(COMMON_URL_LENGTH); + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/common/util/validator/StringValidator.java b/src/main/java/io/ssafy/luckyweeky/common/util/validator/StringValidator.java new file mode 100644 index 0000000..43cf705 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/common/util/validator/StringValidator.java @@ -0,0 +1,33 @@ +package io.ssafy.luckyweeky.common.util.validator; + +public class StringValidator { + // 비속어 목록 + private static final String[] PROHIBITED_WORDS = {"씨발", "병신", "개새끼"}; + + private static final String SQL_INJECTION_PATTERN = "('.+--)|(\\|)|(%7C)|(;)|(--|\\/\\*|\\*\\/)"; + private static final String PROHIBITED_CHARACTERS = "[<>\"']"; + + // true : 유효성 성공 + public static boolean isValid(String input) { + return !containsProhibitedWords(input) && + !containsSQLInjectionPattern(input) && + !containsProhibitedCharacters(input); + } + + private static boolean containsProhibitedWords(String input) { + for (String word : PROHIBITED_WORDS) { + if (input.toLowerCase().contains(word)) { + return true; // 비속어 포함 + } + } + return false; + } + + private static boolean containsSQLInjectionPattern(String input) { + return input.matches(SQL_INJECTION_PATTERN); + } + + private static boolean containsProhibitedCharacters(String input) { + return input.matches(".*" + PROHIBITED_CHARACTERS + ".*"); + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/dispatcher/dto/LoginUser.java b/src/main/java/io/ssafy/luckyweeky/dispatcher/dto/LoginUser.java deleted file mode 100644 index 442aca1..0000000 --- a/src/main/java/io/ssafy/luckyweeky/dispatcher/dto/LoginUser.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.ssafy.luckyweeky.dispatcher.dto; - -public class LoginUser { - private String email; - private String password; - - public LoginUser(String email, String password) { - this.email = email; - this.password = password; - } - - public String getPassword() { - return password; - } - - public String getEmail() { - return email; - } -} diff --git a/src/main/java/io/ssafy/luckyweeky/dispatcher/filter/AuthFilter.java b/src/main/java/io/ssafy/luckyweeky/dispatcher/filter/AuthFilter.java deleted file mode 100644 index e80d9e0..0000000 --- a/src/main/java/io/ssafy/luckyweeky/dispatcher/filter/AuthFilter.java +++ /dev/null @@ -1,4 +0,0 @@ -package io.ssafy.luckyweeky.dispatcher.filter; - -public class AuthFilter { -} diff --git a/src/main/java/io/ssafy/luckyweeky/dispatcher/filter/CORSFilter.java b/src/main/java/io/ssafy/luckyweeky/dispatcher/filter/CORSFilter.java deleted file mode 100644 index a82b69e..0000000 --- a/src/main/java/io/ssafy/luckyweeky/dispatcher/filter/CORSFilter.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.ssafy.luckyweeky.dispatcher.filter; - -import jakarta.servlet.*; -import jakarta.servlet.http.HttpServletResponse; - -import java.io.IOException; - -public class CORSFilter implements Filter { - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - HttpServletResponse httpResponse = (HttpServletResponse) response; - - // CORS 헤더 설정 - httpResponse.setHeader("Access-Control-Allow-Origin", "*"); - httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST"); - httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); - httpResponse.setHeader("Access-Control-Allow-Credentials", "true"); - - // 다음 필터나 서블릿으로 요청 전달 - chain.doFilter(request, response); - } -} diff --git a/src/main/java/io/ssafy/luckyweeky/dispatcher/filter/LogginFilter.java b/src/main/java/io/ssafy/luckyweeky/dispatcher/filter/LogginFilter.java deleted file mode 100644 index c3a037e..0000000 --- a/src/main/java/io/ssafy/luckyweeky/dispatcher/filter/LogginFilter.java +++ /dev/null @@ -1,4 +0,0 @@ -package io.ssafy.luckyweeky.dispatcher.filter; - -public class LogginFilter { -} diff --git a/src/main/java/io/ssafy/luckyweeky/domain/user/repository/UserMapper.java b/src/main/java/io/ssafy/luckyweeky/domain/user/repository/UserMapper.java deleted file mode 100644 index 16d5a64..0000000 --- a/src/main/java/io/ssafy/luckyweeky/domain/user/repository/UserMapper.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.ssafy.luckyweeky.domain.user.repository; - -import io.ssafy.luckyweeky.domain.user.model.UserEntity; -import io.ssafy.luckyweeky.domain.user.model.UserSaltEntity; - -public interface UserMapper { - UserEntity findById(long userId); - UserEntity findByEmail(String email); - void insertUser(UserEntity user); - void insertUserSalt(UserSaltEntity userSaltEntity); - String findSaltByEmail(String email); -} diff --git a/src/main/java/io/ssafy/luckyweeky/domain/user/service/UserService.java b/src/main/java/io/ssafy/luckyweeky/domain/user/service/UserService.java deleted file mode 100644 index edeba0a..0000000 --- a/src/main/java/io/ssafy/luckyweeky/domain/user/service/UserService.java +++ /dev/null @@ -1,106 +0,0 @@ -package io.ssafy.luckyweeky.domain.user.service; - -import io.ssafy.luckyweeky.dispatcher.DispatcherServlet; -import io.ssafy.luckyweeky.dispatcher.dto.GeneralSignupUser; -import io.ssafy.luckyweeky.dispatcher.dto.LoginUser; -import io.ssafy.luckyweeky.domain.user.model.UserEntity; -import io.ssafy.luckyweeky.domain.user.model.UserSaltEntity; -import io.ssafy.luckyweeky.domain.user.repository.UserRepository; -import io.ssafy.luckyweeky.infrastructure.config.bean.XmlBeanFactory; -import io.ssafy.luckyweeky.infrastructure.storage.S3Fileloader; -import io.ssafy.luckyweeky.infrastructure.util.OpenCrypt; -import io.ssafy.luckyweeky.infrastructure.util.SnowflakeIdGenerator; -import jakarta.servlet.http.Part; - -import java.io.File; - -public class UserService { - private final UserRepository userRepository; - - public UserService() { - this.userRepository = (UserRepository) XmlBeanFactory.getBean("userRepository"); - } - - /** - * 이메일 존재 여부 체크 - * - * @param email 확인할 이메일 - * @return 이메일 존재 시 true, 존재하지 않으면 false - */ - public boolean isEmailExists(String email) { - return userRepository.findByEmail(email) != null; - } - - /** - * 로그인 처리 - * - * @param loginUser 사용자 정보(email,password) - * @return 로그인 성공 시 User 객체 반환, 실패 시 null 반환 - */ - public UserEntity login(LoginUser loginUser) { - String salt = userRepository.getUserSalt(loginUser.getEmail()); - UserEntity user = userRepository.findByEmail(loginUser.getEmail()); - if (salt != null && user != null && user.getPasswordHash().equals(OpenCrypt.getEncryptPassword(loginUser.getPassword(), salt))) { - return user; // 로그인 성공 - } - return null; // 로그인 실패 - } - - /** - * 회원가입 처리 - * - * @param generalSignupUser 사용자 회원가입 정보 - * @param filePart 사용자 프로필 이미지정보 - * @return 회원가입 성공 시 true, 실패 시 false - */ - public boolean generalRegister(GeneralSignupUser generalSignupUser, Part filePart) throws Exception { - // 이미 이메일이 존재하면 회원가입 실패 - if (isEmailExists(generalSignupUser.getEmail())) { - return false; - } - - if (filePart != null) { - File tempFile = null; - try { - // 파일 확장자 추출 - int lastDotIndex = generalSignupUser.getProfileImageKey().lastIndexOf("."); - String extension = (lastDotIndex != -1) ? generalSignupUser.getProfileImageKey().substring(lastDotIndex) : ".jpg"; - - // 프로젝트 디렉토리 기준으로 임시 파일 저장 경로 설정 - String tempDirPath = DispatcherServlet.getWebInfPath()+"/temp"; - File tempDir = new File(tempDirPath); - if (!tempDir.exists()) { - tempDir.mkdirs(); // 디렉토리가 없으면 생성 - } - - // 임시 파일 생성 - tempFile = File.createTempFile("upload-", extension, tempDir); - - // 파일 쓰기 - filePart.write(tempFile.getAbsolutePath()); - - // S3에 파일 등록 - S3Fileloader.getInstance().upload(tempFile, generalSignupUser.getProfileImageKey()); - } catch (Exception e) { - throw new Exception("파일 업로드 에러 코드 작성"); - } finally { - if (tempFile != null && tempFile.exists()) { - // 임시 파일 삭제 - tempFile.delete(); - } - } - } - - String salt = OpenCrypt.createEncryptSalt(); - UserEntity userEntity = new UserEntity.Builder() - .userId(SnowflakeIdGenerator.getInstance().nextId()) - .passwordHash(OpenCrypt.getEncryptPassword(generalSignupUser.getPassword(),salt)) - .username(generalSignupUser.getUsername()) - .email(generalSignupUser.getEmail()) - .birthDate(generalSignupUser.getBirthDate()) - .profileImageKey(generalSignupUser.getProfileImageKey()) - .build(); - UserSaltEntity userSaltEntity = new UserSaltEntity(userEntity.getUserId(), salt); - return userRepository.insertUser(userEntity, userSaltEntity); - } -} diff --git a/src/main/java/io/ssafy/luckyweeky/infrastructure/persistence/MyBatisSqlSessionFactory.java b/src/main/java/io/ssafy/luckyweeky/infrastructure/persistence/MyBatisSqlSessionFactory.java deleted file mode 100644 index 22a99db..0000000 --- a/src/main/java/io/ssafy/luckyweeky/infrastructure/persistence/MyBatisSqlSessionFactory.java +++ /dev/null @@ -1,49 +0,0 @@ -package io.ssafy.luckyweeky.infrastructure.persistence; - -import io.github.cdimascio.dotenv.Dotenv; -import io.ssafy.luckyweeky.dispatcher.DispatcherServlet; -import org.apache.ibatis.io.Resources; -import org.apache.ibatis.session.SqlSessionFactory; -import org.apache.ibatis.session.SqlSessionFactoryBuilder; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.Properties; - -public class MyBatisSqlSessionFactory { - private static SqlSessionFactory sqlSessionFactory; - - static { - try { - // 1. .env 파일 로드 - Dotenv dotenv = Dotenv.configure() - .directory(DispatcherServlet.getWebInfPath()+ File.separatorChar) - .filename(".env") // 파일 이름 지정 (기본값: ".env") - .load(); - - // 2. 환경 변수 가져오기 - String url = dotenv.get("DB_URL"); - String username = dotenv.get("DB_USERNAME"); - String password = dotenv.get("DB_PASSWORD"); - - // 3. Properties 객체에 환경 변수 추가 - Properties props = new Properties(); - props.setProperty("url", url); - props.setProperty("username", username); - props.setProperty("password", password); - // 4. MyBatis 설정 파일 읽기 - String resource = "mybatis-config.xml"; // 설정 파일 경로 - InputStream inputStream = Resources.getResourceAsStream(resource); - - // 5. SqlSessionFactory 생성 시 Properties 전달 - sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, props); - } catch (IOException e) { - throw new RuntimeException("Failed to init SqlSessionFactory."); - } - } - - public static SqlSessionFactory getSqlSessionFactory() { - return sqlSessionFactory; - } -} diff --git a/src/main/java/io/ssafy/luckyweeky/schedule/application/converter/ScheduleDtoToScheduleEntity.java b/src/main/java/io/ssafy/luckyweeky/schedule/application/converter/ScheduleDtoToScheduleEntity.java new file mode 100644 index 0000000..0803516 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/schedule/application/converter/ScheduleDtoToScheduleEntity.java @@ -0,0 +1,43 @@ +package io.ssafy.luckyweeky.schedule.application.converter; + +import io.ssafy.luckyweeky.common.implement.Converter; +import io.ssafy.luckyweeky.common.util.generator.SnowflakeIdGenerator; +import io.ssafy.luckyweeky.schedule.application.dto.ScheduleDto; +import io.ssafy.luckyweeky.schedule.domain.model.MainScheduleEntity; +import io.ssafy.luckyweeky.schedule.domain.model.ScheduleEntity; +import io.ssafy.luckyweeky.schedule.domain.model.SubScheduleEntity; + +import java.util.ArrayList; +import java.util.List; + +public class ScheduleDtoToScheduleEntity implements Converter { + private final static ScheduleDtoToScheduleEntity instance = new ScheduleDtoToScheduleEntity(); + + public static ScheduleDtoToScheduleEntity getInstance() { + return instance; + } + + @Override + public ScheduleEntity convert(ScheduleDto source) { + MainScheduleEntity mainScheduleEntity = new MainScheduleEntity( + SnowflakeIdGenerator.getInstance().nextId(), + source.getUserId(), + source.getMainTitle(), + source.getStartTime(), + source.getEndTime(), + source.getColor() + ); + List subScheduleEntityList = new ArrayList<>(); + source.getSubSchedules().forEach(subScheduleDto -> { + subScheduleEntityList.add(new SubScheduleEntity( + SnowflakeIdGenerator.getInstance().nextId(), + mainScheduleEntity.getMainScheduleId(), + subScheduleDto.getTitle(), + subScheduleDto.getDescription(), + subScheduleDto.getStartTime(), + subScheduleDto.getEndTime() + )); + }); + return new ScheduleEntity(mainScheduleEntity, subScheduleEntityList); + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/schedule/application/converter/ScheduleEntityToScheduleDto.java b/src/main/java/io/ssafy/luckyweeky/schedule/application/converter/ScheduleEntityToScheduleDto.java new file mode 100644 index 0000000..ec592b7 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/schedule/application/converter/ScheduleEntityToScheduleDto.java @@ -0,0 +1,40 @@ +package io.ssafy.luckyweeky.schedule.application.converter; + +import io.ssafy.luckyweeky.common.implement.Converter; +import io.ssafy.luckyweeky.common.util.generator.SnowflakeIdGenerator; +import io.ssafy.luckyweeky.schedule.application.dto.ScheduleDto; +import io.ssafy.luckyweeky.schedule.application.dto.SubScheduleDto; +import io.ssafy.luckyweeky.schedule.domain.model.MainScheduleEntity; +import io.ssafy.luckyweeky.schedule.domain.model.ScheduleEntity; +import io.ssafy.luckyweeky.schedule.domain.model.SubScheduleEntity; + +import java.util.ArrayList; +import java.util.List; + +public class ScheduleEntityToScheduleDto implements Converter { + private final static ScheduleEntityToScheduleDto instance = new ScheduleEntityToScheduleDto(); + + public static ScheduleEntityToScheduleDto getInstance() { + return instance; + } + + @Override + public ScheduleDto convert(ScheduleEntity source) { + List subSchedules = new ArrayList<>(); + source.getSubScheduleEntityList().forEach(subScheduleEntity -> { + subSchedules.add(new SubScheduleDto( + subScheduleEntity.getTitle(), + subScheduleEntity.getDescription(), + subScheduleEntity.getStartTime(), + subScheduleEntity.getEndTime() + )); + }); + return new ScheduleDto( + source.getMainScheduleEntity().getTitle(), + source.getMainScheduleEntity().getColor(), + source.getMainScheduleEntity().getStartTime(), + source.getMainScheduleEntity().getEndTime(), + subSchedules + ); + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/schedule/application/dto/ScheduleDto.java b/src/main/java/io/ssafy/luckyweeky/schedule/application/dto/ScheduleDto.java new file mode 100644 index 0000000..5729e19 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/schedule/application/dto/ScheduleDto.java @@ -0,0 +1,73 @@ +package io.ssafy.luckyweeky.schedule.application.dto; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +public class ScheduleDto { + private final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private Long userId; + private final String mainTitle; + private final String color; + private final LocalDateTime startTime; // 시작 시간 + private final LocalDateTime endTime; // 종 + private final List subSchedules; + + public ScheduleDto(Long userId,String mainTitle, String color, String startTime, String endTime, List subSchedules) { + this.userId = userId; + this.mainTitle = mainTitle; + this.color = color; + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + this.startTime = LocalDateTime.parse(startTime,formatter); + this.endTime = LocalDateTime.parse(endTime,formatter); + this.subSchedules = subSchedules; + } + + public ScheduleDto(String mainTitle, String color, LocalDateTime startTime, LocalDateTime endTime, List subSchedules) { + this.userId = 0L; + this.mainTitle = mainTitle; + this.color = color; + this.startTime = startTime; + this.endTime = endTime; + this.subSchedules = subSchedules; + } + + public Long getUserId() { + return userId; + } + + public String getMainTitle() { + return mainTitle; + } + + public String getColor() { + return color; + } + + public LocalDateTime getStartTime() { + return startTime; + } + + public LocalDateTime getEndTime() { + return endTime; + } + + public List getSubSchedules() { + return subSchedules; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + @Override + public String toString() { + return "{" + + " mainTitle:'" + mainTitle + '\'' + + ", color:'" + color + '\'' + + ", startTime:'" + startTime.format(FORMATTER) +'\'' + + ", endTime:'" + endTime.format(FORMATTER) +'\'' + + ", subSchedules:" + subSchedules + + '}'; + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/schedule/application/dto/SubScheduleDto.java b/src/main/java/io/ssafy/luckyweeky/schedule/application/dto/SubScheduleDto.java new file mode 100644 index 0000000..1e91660 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/schedule/application/dto/SubScheduleDto.java @@ -0,0 +1,52 @@ +package io.ssafy.luckyweeky.schedule.application.dto; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class SubScheduleDto { + private final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private final String title; + private final String description; + private final LocalDateTime startTime; + private final LocalDateTime endTime; + + public SubScheduleDto(String title, String description, String startTime, String endTime) { + this.title = title; + this.description = description; + this.startTime = LocalDateTime.parse(startTime,FORMATTER); + this.endTime = LocalDateTime.parse(endTime,FORMATTER); + } + + public SubScheduleDto(String title, String description, LocalDateTime startTime, LocalDateTime endTime) { + this.title = title; + this.description = description; + this.startTime = startTime; + this.endTime = endTime; + } + + public String getTitle() { + return title; + } + + public String getDescription() { + return description; + } + + public LocalDateTime getStartTime() { + return startTime; + } + + public LocalDateTime getEndTime() { + return endTime; + }; + + @Override + public String toString() { + return "{" + + "title:'" + title + '\'' + + ", description:'" + description + '\'' + + ", startTime:'" + startTime.format(FORMATTER) +'\'' + + ", endTime:'" + endTime.format(FORMATTER) +'\'' + + '}'; + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/schedule/application/service/ScheduleService.java b/src/main/java/io/ssafy/luckyweeky/schedule/application/service/ScheduleService.java new file mode 100644 index 0000000..30076e6 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/schedule/application/service/ScheduleService.java @@ -0,0 +1,84 @@ +package io.ssafy.luckyweeky.schedule.application.service; + +import com.fasterxml.jackson.core.type.TypeReference; +import io.ssafy.luckyweeky.common.config.XmlBeanFactory; +import io.ssafy.luckyweeky.common.infrastructure.cache.RedisManager; +import io.ssafy.luckyweeky.schedule.application.converter.ScheduleDtoToScheduleEntity; +import io.ssafy.luckyweeky.schedule.application.converter.ScheduleEntityToScheduleDto; +import io.ssafy.luckyweeky.schedule.application.dto.ScheduleDto; +import io.ssafy.luckyweeky.schedule.domain.model.ScheduleEntity; +import io.ssafy.luckyweeky.schedule.infrastructure.repository.ScheduleRepository; +import redis.clients.jedis.Jedis; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class ScheduleService { + private final ScheduleRepository scheduleRepository; + + public ScheduleService() { + this.scheduleRepository = (ScheduleRepository) XmlBeanFactory.getBean("scheduleRepository"); + } + + public boolean addSchedule(ScheduleDto scheduleDto) { + ScheduleEntity scheduleEntity = ScheduleDtoToScheduleEntity.getInstance().convert(scheduleDto); + if (scheduleRepository.insertSchedule(scheduleEntity)) { + RedisManager.invalidateCacheBasedOnTitle(Long.toString(scheduleDto.getUserId())); + return true; + } + return false; + } + + public List getSchedulesByDateRange(Map params) { + List scheduleEntities = null; + try (Jedis jedis = RedisManager.getPool().getResource()) { + ObjectMapper objectMapper = new ObjectMapper(); + + String key = String.join(":", + Objects.requireNonNullElse(params.get("userId"), "defaultUser").toString(), + Objects.requireNonNullElse(params.get("startDate"), "defaultDate").toString(), + "schedule"); + + // 캐시에서 데이터 조회 + String scheduleData = jedis.get(key); + System.out.println(scheduleData); + scheduleEntities = scheduleData == null ? + scheduleRepository.getSchedulesByDateRange(params) : + objectMapper.readValue(scheduleData, new TypeReference<>() {}); + + if (scheduleData == null) { + scheduleData = objectMapper.writeValueAsString(scheduleEntities); + // 캐시에 데이터 저장 (TTL: 1시간 = 3600초)c + jedis.setex(key, 3600, scheduleData); + } + } catch (Exception e) { + e.printStackTrace(); + System.out.println("redis connection error"); + scheduleEntities = scheduleRepository.getSchedulesByDateRange(params); + } + // 검증 로직 + + + // + List scheduleDtos = new ArrayList<>(); + scheduleEntities.forEach(scheduleEntity -> { + scheduleDtos.add(ScheduleEntityToScheduleDto.getInstance().convert(scheduleEntity)); + }); + return scheduleDtos; + } + + + public boolean deleteSubSchedule(Map params) { + String subScheduleTitle = params.get("subScheduleTitle"); + if (scheduleRepository.deleteSubSchedule(subScheduleTitle)) { + String userId = params.get("userId"); + RedisManager.invalidateCacheBasedOnTitle(userId); + return true; + } + return false; + } + +} diff --git a/src/main/java/io/ssafy/luckyweeky/schedule/domain/model/MainScheduleEntity.java b/src/main/java/io/ssafy/luckyweeky/schedule/domain/model/MainScheduleEntity.java new file mode 100644 index 0000000..38c655d --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/schedule/domain/model/MainScheduleEntity.java @@ -0,0 +1,104 @@ +package io.ssafy.luckyweeky.schedule.domain.model; + +import java.time.LocalDateTime; + +public class MainScheduleEntity { + private Long mainScheduleId; // 대일정 고유 ID + private Long userId; // 유저 ID + private String title; // 대일정 제목 + private LocalDateTime startTime; // 시작 시간 + private LocalDateTime endTime; // 종료 시간 + private String color; // 색상 코드 (기본값: #cccccc) + private LocalDateTime createdAt; // 생성 날짜 + private LocalDateTime updatedAt; // 업데이트 날짜 + + public MainScheduleEntity(Long mainScheduleId, Long userId, String title, LocalDateTime startTime, LocalDateTime endTime, String color) { + this.mainScheduleId = mainScheduleId; + this.userId = userId; + this.title = title; + this.startTime = startTime; + this.endTime = endTime; + this.color = (color != null) ? color : "#cccccc"; + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + } + + // Getters and Setters + public Long getMainScheduleId() { + return mainScheduleId; + } + + public void setMainScheduleId(Long mainScheduleId) { + this.mainScheduleId = mainScheduleId; + } + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public LocalDateTime getStartTime() { + return startTime; + } + + public void setStartTime(LocalDateTime startTime) { + this.startTime = startTime; + } + + public LocalDateTime getEndTime() { + return endTime; + } + + public void setEndTime(LocalDateTime endTime) { + this.endTime = endTime; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public String toString() { + return "MainScheduleEntity{" + + "mainScheduleId=" + mainScheduleId + + ", userId=" + userId + + ", title='" + title + '\'' + + ", startTime=" + startTime + + ", endTime=" + endTime + + ", color='" + color + '\'' + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/schedule/domain/model/ScheduleEntity.java b/src/main/java/io/ssafy/luckyweeky/schedule/domain/model/ScheduleEntity.java new file mode 100644 index 0000000..f0e3547 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/schedule/domain/model/ScheduleEntity.java @@ -0,0 +1,25 @@ +package io.ssafy.luckyweeky.schedule.domain.model; + +import io.ssafy.luckyweeky.schedule.domain.model.SubScheduleEntity; + +import java.util.List; + +public class ScheduleEntity { + private final MainScheduleEntity mainScheduleEntity; + private final List subScheduleEntityList; + + public ScheduleEntity(MainScheduleEntity mainScheduleEntity, List subScheduleEntityList) { + this.mainScheduleEntity = mainScheduleEntity; + this.subScheduleEntityList = subScheduleEntityList; + } + + public MainScheduleEntity getMainScheduleEntity() { + return mainScheduleEntity; + } + + public List getSubScheduleEntityList() { + return subScheduleEntityList; + } + + +} diff --git a/src/main/java/io/ssafy/luckyweeky/schedule/domain/model/SubScheduleEntity.java b/src/main/java/io/ssafy/luckyweeky/schedule/domain/model/SubScheduleEntity.java new file mode 100644 index 0000000..2585a56 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/schedule/domain/model/SubScheduleEntity.java @@ -0,0 +1,100 @@ +package io.ssafy.luckyweeky.schedule.domain.model; + +import java.time.LocalDateTime; + +public class SubScheduleEntity { + private Long subScheduleId; // 소일정 고유 ID + private Long mainScheduleId; // 대일정 ID + private String title; // 소일정 제목 + private String description; // 소일정 내용 + private LocalDateTime startTime; // 시작 시간 + private LocalDateTime endTime; // 종료 시간 + private boolean isCompleted; // 완료 여부 + private LocalDateTime createdAt; // 생성 날짜 + private LocalDateTime updatedAt; // 업데이트 날짜 + + public SubScheduleEntity(Long subScheduleId, Long mainScheduleId, String title, String description, LocalDateTime startTime, LocalDateTime endTime) { + this.subScheduleId = subScheduleId; + this.mainScheduleId = mainScheduleId; + this.title = title; + this.description = description; + this.startTime = startTime; + this.endTime = endTime; + this.isCompleted = false; + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + } + + // Getters and Setters + public Long getSubScheduleId() { + return subScheduleId; + } + + public void setSubScheduleId(Long subScheduleId) { + this.subScheduleId = subScheduleId; + } + + public Long getMainScheduleId() { + return mainScheduleId; + } + + public void setMainScheduleId(Long mainScheduleId) { + this.mainScheduleId = mainScheduleId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public LocalDateTime getStartTime() { + return startTime; + } + + public void setStartTime(LocalDateTime startTime) { + this.startTime = startTime; + } + + public LocalDateTime getEndTime() { + return endTime; + } + + public void setEndTime(LocalDateTime endTime) { + this.endTime = endTime; + } + + public boolean isCompleted() { + return isCompleted; + } + + public void setCompleted(boolean completed) { + isCompleted = completed; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/schedule/domain/repository/MainScheduleMapper.java b/src/main/java/io/ssafy/luckyweeky/schedule/domain/repository/MainScheduleMapper.java new file mode 100644 index 0000000..2856c28 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/schedule/domain/repository/MainScheduleMapper.java @@ -0,0 +1,13 @@ +package io.ssafy.luckyweeky.schedule.domain.repository; + +import io.ssafy.luckyweeky.schedule.domain.model.MainScheduleEntity; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +public interface MainScheduleMapper { + void insertMainSchedule(MainScheduleEntity mainScheduleEntity); + MainScheduleEntity selectMainScheduleById(Long id); + List selectMainSchedulesByDateRange(Map params); +} diff --git a/src/main/java/io/ssafy/luckyweeky/schedule/domain/repository/SubScheduleMapper.java b/src/main/java/io/ssafy/luckyweeky/schedule/domain/repository/SubScheduleMapper.java new file mode 100644 index 0000000..131eb0a --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/schedule/domain/repository/SubScheduleMapper.java @@ -0,0 +1,15 @@ +package io.ssafy.luckyweeky.schedule.domain.repository; + +import io.ssafy.luckyweeky.schedule.domain.model.SubScheduleEntity; + +import java.util.List; +import java.util.Map; + +public interface SubScheduleMapper { + void insertSubSchedule(SubScheduleEntity subScheduleEntity); + List selectSubSchedulesByMainScheduleIdAndDateRange(Map params); + + void deleteSubScheduleByMainScheduleId(Long mainScheduleId); + + void deleteSubSchedule(String subScheduleTitle); +} diff --git a/src/main/java/io/ssafy/luckyweeky/schedule/infrastructure/repository/ScheduleRepository.java b/src/main/java/io/ssafy/luckyweeky/schedule/infrastructure/repository/ScheduleRepository.java new file mode 100644 index 0000000..b499cc3 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/schedule/infrastructure/repository/ScheduleRepository.java @@ -0,0 +1,81 @@ +package io.ssafy.luckyweeky.schedule.infrastructure.repository; + +import io.ssafy.luckyweeky.common.infrastructure.persistence.MyBatisSqlSessionFactory; +import io.ssafy.luckyweeky.schedule.domain.model.MainScheduleEntity; +import io.ssafy.luckyweeky.schedule.domain.model.ScheduleEntity; +import io.ssafy.luckyweeky.schedule.domain.repository.MainScheduleMapper; +import io.ssafy.luckyweeky.schedule.domain.repository.SubScheduleMapper; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class ScheduleRepository { + private final SqlSessionFactory sqlSessionFactory; + + public ScheduleRepository() { + this.sqlSessionFactory = MyBatisSqlSessionFactory.getSqlSessionFactory(); + } + + public boolean insertSchedule(ScheduleEntity scheduleEntity) { + try (SqlSession session = sqlSessionFactory.openSession(false)) { + MainScheduleMapper mainScheduleMapper = session.getMapper(MainScheduleMapper.class); + SubScheduleMapper subScheduleMapper = session.getMapper(SubScheduleMapper.class); + mainScheduleMapper.insertMainSchedule(scheduleEntity.getMainScheduleEntity()); + scheduleEntity.getSubScheduleEntityList().forEach(subScheduleEntity -> { + subScheduleMapper.insertSubSchedule(subScheduleEntity); + }); + session.commit(); + return true; + }catch (RuntimeException e){ + return false; + } + } + + public List getSchedulesByDateRange(Map params) { + List scheduleEntities = new ArrayList<>(); + try (SqlSession session = sqlSessionFactory.openSession()) { + MainScheduleMapper mainScheduleMapper = session.getMapper(MainScheduleMapper.class); + SubScheduleMapper subScheduleMapper = session.getMapper(SubScheduleMapper.class); + List mainScheduleEntities = mainScheduleMapper.selectMainSchedulesByDateRange(params); + mainScheduleEntities.forEach(mainScheduleEntity -> { + params.put("mainScheduleId",mainScheduleEntity.getMainScheduleId()); + scheduleEntities.add(new ScheduleEntity( + mainScheduleEntity, + subScheduleMapper.selectSubSchedulesByMainScheduleIdAndDateRange(params) + )); + }); + return scheduleEntities; + }catch (RuntimeException e){ + return scheduleEntities; + } + } + + public boolean deleteSchedule(Long scheduleId) { + try (SqlSession session = sqlSessionFactory.openSession(false)) { + SubScheduleMapper subScheduleMapper = session.getMapper(SubScheduleMapper.class); + MainScheduleMapper mainScheduleMapper = session.getMapper(MainScheduleMapper.class); + + subScheduleMapper.deleteSubScheduleByMainScheduleId(scheduleId); + + session.commit(); + return true; + } catch (RuntimeException e) { + return false; + } + } + + public boolean deleteSubSchedule(String subScheduleTitle) { + try (SqlSession session = sqlSessionFactory.openSession(false)) { + SubScheduleMapper subScheduleMapper = session.getMapper(SubScheduleMapper.class); + subScheduleMapper.deleteSubSchedule(subScheduleTitle); + session.commit(); + return true; + } catch (RuntimeException e) { + return false; + } + } + +} diff --git a/src/main/java/io/ssafy/luckyweeky/schedule/presentation/controller/ScheduleController.java b/src/main/java/io/ssafy/luckyweeky/schedule/presentation/controller/ScheduleController.java new file mode 100644 index 0000000..bcc4625 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/schedule/presentation/controller/ScheduleController.java @@ -0,0 +1,134 @@ +package io.ssafy.luckyweeky.schedule.presentation.controller; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import io.ssafy.luckyweeky.common.config.XmlBeanFactory; +import io.ssafy.luckyweeky.common.implement.Controller; +import io.ssafy.luckyweeky.common.util.parser.RequestJsonParser; +import io.ssafy.luckyweeky.common.util.url.RequestUrlPath; +import io.ssafy.luckyweeky.schedule.application.dto.ScheduleDto; +import io.ssafy.luckyweeky.schedule.application.service.ScheduleService; +import io.ssafy.luckyweeky.schedule.presentation.converter.JsonObjectToScheduleDto; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.time.DayOfWeek; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; + +public class ScheduleController implements Controller {//UAKRPCjN + private final ScheduleService scheduleService; + public ScheduleController() { + this.scheduleService =(ScheduleService) XmlBeanFactory.getBean("scheduleService"); + } + + @Override + public void handleRequest(HttpServletRequest request, HttpServletResponse response, JsonObject respJson) throws ServletException, IOException { + String action = RequestUrlPath.getURI(request.getRequestURI())[1]; + + switch (action){ + case "OqwSjA":{ + addSchedule(request, response,respJson); + break; + } + case "WJsdDo":{ + getThisWeekSchedules(request, response,respJson); + break; + } + case "lCSZB":{ + getSchedulesByDate(request,response,respJson); + break; + }case "SHVLC": { + deleteSubSchedule(request, response, respJson); + break; + }case "BDSdE": { + saveAiSchedule(request, response, respJson); + break; + } + } + } + + private void saveAiSchedule(HttpServletRequest request, HttpServletResponse response, JsonObject respJson) throws ServletException, IOException { + JsonObject jsonObject = RequestJsonParser.getInstance().parseFromBody(request.getReader()); + // userId를 JSON 데이터에 추가 + jsonObject.addProperty("userId", (Long) request.getAttribute("userId")); + + System.out.println("Received JSON Object: " + jsonObject); + ScheduleDto scheduleDto = JsonObjectToScheduleDto.getInstance().convert(jsonObject); + if (scheduleDto == null) { + System.err.println("Converted ScheduleDto is null"); + } + scheduleDto.setUserId((Long) request.getAttribute("userId")); + + if(scheduleDto == null){ + throw new IllegalArgumentException("request body is invalid"); + } + if(!scheduleService.addSchedule(scheduleDto)){ + throw new IllegalArgumentException("schedule register failed"); + } + // 등록된 일정 데이터 가져오기 + Map params = new HashMap<>(); + params.put("userId", scheduleDto.getUserId()); + params.put("startDate", scheduleDto.getStartTime()); + params.put("endDate", scheduleDto.getEndTime()); + + // 일정 데이터를 JSON 형식으로 변환 후 응답에 추가 + String scheduleData = scheduleService.getSchedulesByDateRange(params).toString(); + respJson.add("schedule", JsonParser.parseString(scheduleData).getAsJsonArray()); + + } + + private void addSchedule(HttpServletRequest request, HttpServletResponse response, JsonObject respJson) throws ServletException, IOException { + // 요청 본문에서 JSON 데이터를 파싱 + JsonObject jsonObject = RequestJsonParser.getInstance().parseFromBody(request.getReader()); + jsonObject.addProperty("userId", (Long) request.getAttribute("userId")); + ScheduleDto scheduleDto = JsonObjectToScheduleDto.getInstance().convert(jsonObject); + if(scheduleDto==null){ + throw new IllegalArgumentException("request body is invalid"); + } + if(!scheduleService.addSchedule(scheduleDto)){ + throw new IllegalArgumentException("schedule register failed"); + } + } + + private void getThisWeekSchedules(HttpServletRequest request, HttpServletResponse response, JsonObject respJson) throws ServletException, IOException { + LocalDateTime now = LocalDateTime.now(); + Map params = new HashMap<>( + Map.of( + "userId", request.getAttribute("userId"), + "startDate", now.with(DayOfWeek.MONDAY).withHour(0).withMinute(0).withSecond(0), + "endDate", now.with(DayOfWeek.SUNDAY).withHour(23).withMinute(59).withSecond(59) + ) + ); + respJson.add("schedules", JsonParser.parseString(scheduleService.getSchedulesByDateRange(params).toString()).getAsJsonArray()); + } + + private void getSchedulesByDate(HttpServletRequest request, HttpServletResponse response, JsonObject respJson) throws IOException { + JsonObject jsonObject = RequestJsonParser.getInstance().parseFromBody(request.getReader()); + LocalDateTime date = LocalDateTime.parse(jsonObject.get("date").getAsString(),DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + Map params = new HashMap<>( + Map.of( + "userId", request.getAttribute("userId"), + "startDate", date.with(DayOfWeek.MONDAY).withHour(0).withMinute(0).withSecond(0), + "endDate", date.with(DayOfWeek.SUNDAY).withHour(23).withMinute(59).withSecond(59) + ) + ); + respJson.add("schedules", JsonParser.parseString(scheduleService.getSchedulesByDateRange(params).toString()).getAsJsonArray()); + System.out.println(respJson.get("schedules").toString()); + } + +// 임시메서드 + private void deleteSubSchedule(HttpServletRequest request, HttpServletResponse response, JsonObject respJson) throws IOException { + JsonObject jsonObject = RequestJsonParser.getInstance().parseFromBody(request.getReader()); + String userId = request.getAttribute("userId").toString(); + String subScheduleTitle = jsonObject.get("subScheduleTitle").getAsString(); + + if (!scheduleService.deleteSubSchedule(Map.of("subScheduleTitle",subScheduleTitle,"userId",userId))) { + throw new IllegalArgumentException("Failed to delete sub-schedule"); + } + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/schedule/presentation/converter/JsonObjectToScheduleDto.java b/src/main/java/io/ssafy/luckyweeky/schedule/presentation/converter/JsonObjectToScheduleDto.java new file mode 100644 index 0000000..af19d1c --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/schedule/presentation/converter/JsonObjectToScheduleDto.java @@ -0,0 +1,51 @@ +package io.ssafy.luckyweeky.schedule.presentation.converter; + +import com.google.gson.JsonObject; +import io.ssafy.luckyweeky.common.implement.Converter; +import io.ssafy.luckyweeky.schedule.application.dto.ScheduleDto; +import io.ssafy.luckyweeky.schedule.application.dto.SubScheduleDto; +import jakarta.servlet.http.HttpServletRequest; + +import java.util.ArrayList; +import java.util.List; + +public class JsonObjectToScheduleDto implements Converter { + private final static JsonObjectToScheduleDto instance = new JsonObjectToScheduleDto(); + + public static JsonObjectToScheduleDto getInstance() { + return instance; + } + + @Override + public ScheduleDto convert(JsonObject source) { + try { + List subSchedules = new ArrayList<>(); + source.get("subSchedules").getAsJsonArray().forEach(subScheduleElement -> { + JsonObject subSchedule = subScheduleElement.getAsJsonObject(); + subSchedules.add(new SubScheduleDto( + getAsStringOrThrow(subSchedule, "title"), + getAsStringOrThrow(subSchedule, "description"), + getAsStringOrThrow(subSchedule, "startTime"), + getAsStringOrThrow(subSchedule, "endTime") + )); + }); + return new ScheduleDto( + Long.parseLong(getAsStringOrThrow(source, "userId")), + getAsStringOrThrow(source, "mainTitle"), + getAsStringOrThrow(source, "color"), + getAsStringOrThrow(source, "startTime"), + getAsStringOrThrow(source, "endTime"), + subSchedules + ); + } catch (Exception e) { + return null; + } + } + private String getAsStringOrThrow(JsonObject jsonObject, String key) { + + if (!jsonObject.has(key) || jsonObject.get(key).isJsonNull()) { + throw new IllegalArgumentException("필수요소 존재하지 않음"); + } + return jsonObject.get(key).getAsString(); + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/scheduleAi/application/dto/request/CreateAiScheduleRequestDTO.java b/src/main/java/io/ssafy/luckyweeky/scheduleAi/application/dto/request/CreateAiScheduleRequestDTO.java new file mode 100644 index 0000000..c638c78 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/scheduleAi/application/dto/request/CreateAiScheduleRequestDTO.java @@ -0,0 +1,50 @@ +package io.ssafy.luckyweeky.scheduleAi.application.dto.request; + +import java.time.LocalDateTime; + +public class CreateAiScheduleRequestDTO { + private LocalDateTime startDate; + private LocalDateTime endDate; + private String task; + private String availableTime; + private String additionalRequest; + + public CreateAiScheduleRequestDTO(LocalDateTime startDate, LocalDateTime endDate, String task, String availableTime, String additionalRequest) { + this.startDate = startDate; + this.endDate = endDate; + this.task = task; + this.availableTime = availableTime; + this.additionalRequest = additionalRequest; + } + + public LocalDateTime getStartDate() { + return startDate; + } + + public LocalDateTime getEndDate() { + return endDate; + } + + public String getTask() { + return task; + } + + public String getAvailableTime() { + return availableTime; + } + + public String getAdditionalRequest() { + return additionalRequest; + } + + @Override + public String toString() { + return "CreateAiScheduleRequestDTO{" + + "startDate=" + startDate + + ", endDate=" + endDate + + ", task='" + task + '\'' + + ", availableTime='" + availableTime + '\'' + + ", additionalRequest='" + additionalRequest + '\'' + + '}'; + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/scheduleAi/application/dto/request/ReRequestAiScheduleDTO.java b/src/main/java/io/ssafy/luckyweeky/scheduleAi/application/dto/request/ReRequestAiScheduleDTO.java new file mode 100644 index 0000000..2aec122 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/scheduleAi/application/dto/request/ReRequestAiScheduleDTO.java @@ -0,0 +1,19 @@ +package io.ssafy.luckyweeky.scheduleAi.application.dto.request; + +public class ReRequestAiScheduleDTO { + private final String originSchedule; + private final String newAdditionalRequest; + + public ReRequestAiScheduleDTO(String originSchedule, String newAdditionalRequest) { + this.originSchedule = originSchedule; + this.newAdditionalRequest = newAdditionalRequest; + } + + public String getOriginalPrompt() { + return originSchedule; + } + + public String getNewAdditionalRequest() { + return newAdditionalRequest; + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/scheduleAi/application/service/ChatgptService.java b/src/main/java/io/ssafy/luckyweeky/scheduleAi/application/service/ChatgptService.java new file mode 100644 index 0000000..3b9f78c --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/scheduleAi/application/service/ChatgptService.java @@ -0,0 +1,93 @@ +package io.ssafy.luckyweeky.scheduleAi.application.service; + +import io.ssafy.luckyweeky.scheduleAi.domain.prompt.AIPromptGenerator; +import okhttp3.*; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +public class ChatgptService { + private final String OPENAI_API_KEY; + private final String GPT_API_ENDPOINT; + private final OkHttpClient client; + + public ChatgptService() { + // 시스템 환경 변수에서 API 키와 엔드포인트 가져오기 + this.OPENAI_API_KEY = System.getProperty("OPENAI_API_KEY"); + this.GPT_API_ENDPOINT = System.getProperty("GPT_API_ENDPOINT"); + + if (this.OPENAI_API_KEY == null || this.OPENAI_API_KEY.isEmpty()) { + throw new IllegalStateException("환경 변수 'OPENAI_API_KEY'가 설정되지 않았습니다."); + } + + if (this.GPT_API_ENDPOINT == null || this.GPT_API_ENDPOINT.isEmpty()) { + throw new IllegalStateException("환경 변수 'GPT_API_ENDPOINT'가 설정되지 않았습니다."); + } + + // OkHttpClient 초기화 + this.client = new OkHttpClient.Builder() + .connectTimeout(260, TimeUnit.SECONDS) + .readTimeout(260, TimeUnit.SECONDS) + .writeTimeout(260, TimeUnit.SECONDS) + .build(); + } + + // ChatGPT API 호출 + public String createChat(String prompt) throws IOException { + // JSON 요청 생성 + JSONObject json = new JSONObject(); + json.put("model", "gpt-4"); // 변경된 모델 이름 + json.put("messages", new JSONArray() + .put(new JSONObject() + .put("role", "system") + .put("content", AIPromptGenerator.INITIAL_PROMPT_TEMPLATE)) + .put(new JSONObject() + .put("role", "user") + .put("content", prompt)) + ); + + // 입력 토큰 수 추정값 + int estimatedInputTokens = prompt.length() / 4 + AIPromptGenerator.INITIAL_PROMPT_TEMPLATE.length() / 4; + + // max_tokens 설정 + json.put("max_tokens", Math.min(8000 - estimatedInputTokens, 4000)); // 안전 범위 내 설정 + json.put("temperature", 0.6); + + RequestBody body = RequestBody.create( + MediaType.parse("application/json"), + json.toString() + ); + + // HTTP 요청 생성 및 실행 + Request request = new Request.Builder() + .url(GPT_API_ENDPOINT) + .addHeader("Authorization", "Bearer " + OPENAI_API_KEY.trim()) + .post(body) + .build(); + + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new IOException("Unexpected response code: " + response.code()); + } + + // 응답에서 메시지 추출 + return extractMessageFromChatResponse(new String(response.body().bytes(), "UTF-8")); + } + } + + private String extractMessageFromChatResponse(String responseBody) { + JSONObject jsonResponse = new JSONObject(responseBody); + JSONArray choices = jsonResponse.optJSONArray("choices"); + + if (choices != null && choices.length() > 0) { + JSONObject messageObject = choices.getJSONObject(0).optJSONObject("message"); + if (messageObject != null) { + return messageObject.optString("content", "No content available"); + } + } + + return "No content available"; + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/scheduleAi/application/service/ClovaService.java b/src/main/java/io/ssafy/luckyweeky/scheduleAi/application/service/ClovaService.java new file mode 100644 index 0000000..80fe60e --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/scheduleAi/application/service/ClovaService.java @@ -0,0 +1,95 @@ +package io.ssafy.luckyweeky.scheduleAi.application.service; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; + +public class ClovaService { + private final String CLOVA_API_URL; + private final String CLOVA_ACCESS_KEY; // 발급받은 Access Key + private final String CLOVA_SECRET_KEY; // 발급받은 Secret Key + + public ClovaService() { + // 시스템 환경 변수에서 값 로드 + this.CLOVA_API_URL = System.getProperty("CLOVA_API_URL"); + this.CLOVA_ACCESS_KEY = System.getProperty("CLOVA_ACCESS_KEY"); + this.CLOVA_SECRET_KEY = System.getProperty("CLOVA_SECRET_KEY"); + + // 환경 변수 유효성 검사 + if (this.CLOVA_API_URL == null || this.CLOVA_API_URL.isEmpty()) { + throw new IllegalStateException("환경 변수 'CLOVA_API_URL'이 설정되지 않았습니다."); + } + if (this.CLOVA_ACCESS_KEY == null || this.CLOVA_ACCESS_KEY.isEmpty()) { + throw new IllegalStateException("환경 변수 'CLOVA_ACCESS_KEY'가 설정되지 않았습니다."); + } + if (this.CLOVA_SECRET_KEY == null || this.CLOVA_SECRET_KEY.isEmpty()) { + throw new IllegalStateException("환경 변수 'CLOVA_SECRET_KEY'가 설정되지 않았습니다."); + } + } + + // Clova Speech API 호출 + public String callClovaSTT(InputStream audioStream) throws IOException { + HttpURLConnection connection = null; + BufferedReader reader = null; + + try { + // HTTP 연결 설정 + URL url = new URL(CLOVA_API_URL + "?lang=Kor"); // 언어 설정 추가 + connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/octet-stream"); + connection.setRequestProperty("X-NCP-APIGW-API-KEY-ID", CLOVA_ACCESS_KEY); + connection.setRequestProperty("X-NCP-APIGW-API-KEY", CLOVA_SECRET_KEY); + connection.setDoOutput(true); + + // 오디오 데이터 전송 + try (OutputStream outputStream = connection.getOutputStream()) { + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = audioStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + } + + // 응답 처리 + int responseCode = connection.getResponseCode(); + System.out.println("Clova API Response Code: " + responseCode); + + if (responseCode == HttpURLConnection.HTTP_OK) { + reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + StringBuilder responseBuilder = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + responseBuilder.append(line); + } + System.out.println("Clova API Response: " + responseBuilder.toString()); + + // JSON 응답에서 텍스트 추출 + JsonObject jsonResponse = new Gson().fromJson(responseBuilder.toString(), JsonObject.class); + return jsonResponse.get("text").getAsString(); + } else { + InputStream errorStream = connection.getErrorStream(); + if (errorStream != null) { + BufferedReader errorReader = new BufferedReader(new InputStreamReader(errorStream)); + StringBuilder errorBuilder = new StringBuilder(); + String errorLine; + while ((errorLine = errorReader.readLine()) != null) { + errorBuilder.append(errorLine); + } + System.err.println("Clova API Error Response: " + errorBuilder.toString()); + } + throw new IOException("Clova Speech API 호출 실패: 응답 코드 " + responseCode); + } + } finally { + if (reader != null) { + reader.close(); + } + if (connection != null) { + connection.disconnect(); + } + } + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/scheduleAi/application/service/ScheduleAiService.java b/src/main/java/io/ssafy/luckyweeky/scheduleAi/application/service/ScheduleAiService.java new file mode 100644 index 0000000..d52a858 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/scheduleAi/application/service/ScheduleAiService.java @@ -0,0 +1,38 @@ +package io.ssafy.luckyweeky.scheduleAi.application.service; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import io.ssafy.luckyweeky.common.config.XmlBeanFactory; +import io.ssafy.luckyweeky.scheduleAi.application.dto.request.CreateAiScheduleRequestDTO; +import io.ssafy.luckyweeky.scheduleAi.application.dto.request.ReRequestAiScheduleDTO; +import io.ssafy.luckyweeky.scheduleAi.domain.prompt.AIPromptGenerator; + +import java.io.IOException; + +public class ScheduleAiService { + private final ChatgptService chatgptService; + + public ScheduleAiService() { + this.chatgptService = (ChatgptService) XmlBeanFactory.getBean("chatgptService"); + + } + public String generateSchedule(CreateAiScheduleRequestDTO createAiScheduleRequestDTO) throws IOException { + // 프롬프트 생성 + String prompt = AIPromptGenerator.generateInitialPrompt(createAiScheduleRequestDTO); + + // ChatGPT 호출 + return chatgptService.createChat(prompt); + } + + public String reGenerateSchedule(ReRequestAiScheduleDTO reRequestAiScheduleDTO) throws IOException { + String prompt = AIPromptGenerator.generateReRequestPrompt(reRequestAiScheduleDTO); + // ChatGPT 호출 + return chatgptService.createChat(prompt); + } + + public String generateClovaSchedule(String sttResult) throws IOException { + // Clova 결과를 기반으로 프롬프트 생성 + String generatedPrompt = AIPromptGenerator.generateVoicePrompt(sttResult); + return chatgptService.createChat(generatedPrompt); + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/scheduleAi/application/validator/AnalyticalDataValidator.java b/src/main/java/io/ssafy/luckyweeky/scheduleAi/application/validator/AnalyticalDataValidator.java new file mode 100644 index 0000000..4494f0d --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/scheduleAi/application/validator/AnalyticalDataValidator.java @@ -0,0 +1,56 @@ +package io.ssafy.luckyweeky.scheduleAi.application.validator; + +import io.ssafy.luckyweeky.common.util.validator.StringValidator; + +import java.time.LocalDate; +import java.time.format.DateTimeParseException; + +public class AnalyticalDataValidator { + + public static boolean isValid(String startDate, String endDate, String task, String availableTime, String additionalRequest) { + return isValidDate(startDate) && + isValidDate(endDate) && + isValidTask(task) && + isValidAvailableTime(availableTime)&& + isValidAdditionalRequest(additionalRequest); + } + + public static void validate(String startDate, String endDate, String task, String availableTime, String additionalRequest) throws IllegalArgumentException { + if(!isValid(startDate, endDate, task, availableTime, additionalRequest)) { + throw new IllegalArgumentException("분석 요청 데이터 유효성 에러코드작성"); + } + } + + private static boolean isValidDate(String date) { + if (date == null || date.isBlank()) { + return false; + } + + try { + LocalDate.parse(date); // 날짜 형식 유효성 검사 + return true; + } catch (DateTimeParseException e) { + return false; + } + } + + private static boolean isValidTask(String task) { + return task != null && !task.isBlank() && StringValidator.isValid(task); // 작업(task)이 null 또는 비어 있지 않은지 확인 + } + + private static boolean isValidAvailableTime(String availableTime) { + if (availableTime == null || availableTime.isBlank()) { + return false; + } + + return StringValidator.isValid(availableTime); + } + + private static boolean isValidAdditionalRequest(String additionalRequest) { + if (additionalRequest == null || additionalRequest.isBlank()) { + return true; + } + + return StringValidator.isValid(additionalRequest); + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/scheduleAi/domain/prompt/AIPromptGenerator.java b/src/main/java/io/ssafy/luckyweeky/scheduleAi/domain/prompt/AIPromptGenerator.java new file mode 100644 index 0000000..e2f2485 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/scheduleAi/domain/prompt/AIPromptGenerator.java @@ -0,0 +1,116 @@ +package io.ssafy.luckyweeky.scheduleAi.domain.prompt; + +import io.ssafy.luckyweeky.scheduleAi.application.dto.request.CreateAiScheduleRequestDTO; +import io.ssafy.luckyweeky.scheduleAi.application.dto.request.ReRequestAiScheduleDTO; + +import java.time.LocalDateTime; + +public class AIPromptGenerator { + //=============================== JSON 응답 템플릿 =========================================== + private static final String RESULT_TEMPLATE = "{\n" + + " \"mainTitle\": {주요 목적},\n" + + " \"startTime\": String (\"yyyy-MM-ddTHH:mm:ss\"),\n" + + " \"endTime\": String (\"yyyy-MM-ddTHH:mm:ss\"),\n" + + " \"subSchedules\": [\n" + + " {\n" + + " \"title\": {이부분은 특히 아주 **구체적**으로, 다양할수록 좋음.}\n" + + " \"startTime\": String (\"yyyy-MM-ddTHH:mm:ss\"),\n" + + " \"endTime\": String (\"yyyy-MM-ddTHH:mm:ss\")\n" + + " }\n" + + " ]\n" + + "}\n"; +//=============================================================================================== + + + // 1. AI 일정 생성 + public static final String INITIAL_PROMPT_TEMPLATE = "너는 일정계획 전문가이고," + + "요청에 정확히 맞는 세부 일정을 계획해야해. 2번 json 응답 형식에 맞게, 요청을 제외한 응답를 반환해야해.\n" + + "요청====== \n" + + "시작 날짜:%s \n" + + "종료 날짜:%s \n" + + "목표(할 일):%s \n" + + "투자가능한 시간:%s \n" + + "추가 요청사항:%s \n\n" + + "(응답는 앞뒤 아무말도 없이 반드시 아래처럼 ***json***으로만 출력해야함.) \n" + + RESULT_TEMPLATE; + + // 2. AI 일정 재요청 + public static final String FOLLOW_UP_PROMPT_TEMPLATE = "아래 1번 데이터를 기반으로 2번 추가 정보를 반영해서, 3번 json 응답 형식에 맞게 응답를 반환해야해.**\n\n" + + "1번: %s\n\n" + + "2번: %s\n\n" + + "3번응답:(응답는 앞뒤 아무말도 없이 ***json***으로만 응답해야함.예시({\n" + + " \"mainTitle\": \"독서하기\",\n" + + " \"startTime\": \"2022-03-01T00:00:00\",\n" + + " \"endTime\": \"2022-03-31T23:59:59\",\n" + + " \"subSchedules\": [\n" + + " {\n" + + " \"title\": \"독서 - '모비딕' 3장부터 4장까지\",\n" + + " \"startTime\": \"2022-03-02T10:00:00\",\n" + + " \"endTime\": \"2022-03-02T11:00:00\"\n" + + " },...이후는너무길어서생략): \n\n"+RESULT_TEMPLATE; + + + // 3. AI 음성 일정 생성 + public static final String CLOVA_INITIAL_PROMPT_TEMPLATE = "너는 일정계획 전문가이고," + + "아래 1번 요청에 정확히 맞는 세부 일정을 계획해야해. 만약 정보가 부족하거나, 맥락이 이상하다면 맥락을 유추해서 정확한정보를 반환해야해.특히 2번 json 응답 형식에 맞게 응답를 반환해야해."+ + "유의할점은, 일정생성시 1번의 투자가능시간과 추가 요청사항을 충분히 반영해야해. 아무리 추상적이어도 맥락을 이해하고 구체적인 정보를 줘야해.\n\n" + +"그리고 구체적인 날짜정보가 없다면, "+ LocalDateTime.now() +"부터 1주일이야. \n\n" + + "1. 요청: %s\n" + + "2. 응답:\n(응답는 앞뒤 아무말도 없이 반드시 아래처럼 ***json***으로만 출력해야함.)====== \n" + + RESULT_TEMPLATE; + + /** + * AnalyticalData 데이터를 기반으로 첫 요청 프롬프트를 생성합니다. + * + * @param data 프롬프트 생성에 필요한 데이터 + * @return 생성된 프롬프트 문자열 + * @throws IllegalArgumentException 데이터가 유효하지 않을 경우 + */ + public static String generateInitialPrompt(CreateAiScheduleRequestDTO data) { + validateData(data); + return String.format( + INITIAL_PROMPT_TEMPLATE, + data.getStartDate(), + data.getEndDate(), + data.getTask(), + data.getAvailableTime(), + data.getAdditionalRequest() != null ? data.getAdditionalRequest() : "없음" + ); + } + + public static String generateReRequestPrompt(ReRequestAiScheduleDTO data) { + validateData(data); + return String.format( + FOLLOW_UP_PROMPT_TEMPLATE, + data.getOriginalPrompt(), + data.getNewAdditionalRequest() + ); + } + + public static String generateVoicePrompt(String sttResult) { + validateData(sttResult); + return String.format( + CLOVA_INITIAL_PROMPT_TEMPLATE, + sttResult + ); + } + + private static void validateData(String sttResult) { + if (sttResult == null) { + throw new NullPointerException("A03"); + } + } + + + // === 유효성 검사 === + public static void validateData(CreateAiScheduleRequestDTO data) { + if (data == null) { + throw new NullPointerException("A01"); + } + } + public static void validateData(ReRequestAiScheduleDTO data) { + if (data == null) { + throw new NullPointerException("A02"); + } + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/scheduleAi/presentation/ScheduleAiController.java b/src/main/java/io/ssafy/luckyweeky/scheduleAi/presentation/ScheduleAiController.java new file mode 100644 index 0000000..6dd170c --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/scheduleAi/presentation/ScheduleAiController.java @@ -0,0 +1,148 @@ +package io.ssafy.luckyweeky.scheduleAi.presentation; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import io.ssafy.luckyweeky.common.config.XmlBeanFactory; +import io.ssafy.luckyweeky.common.implement.Controller; +import io.ssafy.luckyweeky.common.util.parser.RequestJsonParser; +import io.ssafy.luckyweeky.common.util.url.RequestUrlPath; +import io.ssafy.luckyweeky.scheduleAi.application.dto.request.CreateAiScheduleRequestDTO; +import io.ssafy.luckyweeky.scheduleAi.application.dto.request.ReRequestAiScheduleDTO; +import io.ssafy.luckyweeky.scheduleAi.application.service.ClovaService; +import io.ssafy.luckyweeky.scheduleAi.application.service.ScheduleAiService; +import io.ssafy.luckyweeky.scheduleAi.domain.prompt.AIPromptGenerator; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.Part; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.time.LocalDateTime; + +public class ScheduleAiController implements Controller { + + private final ScheduleAiService scheduleAiService; + private final ClovaService clovaService; + + public ScheduleAiController() { + this.scheduleAiService = (ScheduleAiService) XmlBeanFactory.getBean("scheduleAiService"); + this.clovaService = (ClovaService) XmlBeanFactory.getBean("clovaService"); + } + + @Override + public void handleRequest(HttpServletRequest request, HttpServletResponse response, JsonObject respJson) throws ServletException, IOException { + String action = RequestUrlPath.getURI(request.getRequestURI())[1]; // URI에서 액션 추출 + + try { + switch (action) { + case "SmdBid": { // 일정 생성 요청 + generateSchedule(request, response, respJson); + break; + } + case "LdslbEd": { // 다른 액션 처리 + reRequestGenerateSchedule(request, response, respJson); + break; + } + case "DnbDiw": { // 다른 액션 처리 + processClova(request, response, respJson); + break; + } + default: { + throw new IllegalArgumentException("Unsupported action: " + action); + } + } + } catch (Exception e) { + respJson.addProperty("result", "false"); + respJson.addProperty("error", e.getMessage()); + } + } + + + + +// AI 일정 재요청============================================== + private void reRequestGenerateSchedule(HttpServletRequest request, HttpServletResponse response, JsonObject respJson) throws IOException { + // JSON 데이터 파싱 + JsonObject requestData = null; + try (BufferedReader reader = request.getReader()) { + requestData = RequestJsonParser.getInstance().parseFromBody(reader); + } + + // validation + String newAdditionalRequest = requestData.get("newAdditionalRequest").getAsString(); + String originSchedule = requestData.get("originSchedule").getAsString(); + if (originSchedule == null || newAdditionalRequest == null) throw new NullPointerException("R01"); + + // DTO 생성 + ReRequestAiScheduleDTO reRequestAiScheduleDTO = new ReRequestAiScheduleDTO(originSchedule, newAdditionalRequest); + + // 서비스 호출 + String aiReGeneratedResult = scheduleAiService.reGenerateSchedule(reRequestAiScheduleDTO); + System.out.println("aiReGeneratedResult: " + aiReGeneratedResult); + // 응답 데이터 구성 + respJson.addProperty("result", "true"); + respJson.add("schedule", JsonParser.parseString(aiReGeneratedResult).getAsJsonObject()); + } + + // AI 일정 생성============================================== + private void generateSchedule(HttpServletRequest request, HttpServletResponse response, JsonObject respJson) throws IOException, ServletException { + // JSON 데이터 파싱 + JsonObject requestData = null; + try (BufferedReader reader = request.getReader()) { + requestData = RequestJsonParser.getInstance().parseFromBody(reader); + } + + + // DTO 생성 + CreateAiScheduleRequestDTO createAiScheduleRequestDTO = new CreateAiScheduleRequestDTO( + LocalDateTime.parse(requestData.get("startDate").getAsString()), // LocalDateTime으로 변환 + LocalDateTime.parse(requestData.get("endDate").getAsString()), // LocalDateTime으로 변환 + requestData.get("task").getAsString(), + requestData.get("availableTime").getAsString(), + requestData.has("additionalRequest") ? requestData.get("additionalRequest").getAsString() : null + ); + + System.out.println("createAiScheduleRequestDTO: " + createAiScheduleRequestDTO); + // 서비스 호출 + String aiGeneratedResult = scheduleAiService.generateSchedule(createAiScheduleRequestDTO); + System.out.println("====aiGeneratedResult====\n"+aiGeneratedResult); + + // 응답 데이터 구성 + respJson.addProperty("result", "true"); + respJson.add("schedule", JsonParser.parseString(aiGeneratedResult).getAsJsonObject()); + } + + // AI 일정 생성============================================== + + private void processClova(HttpServletRequest request, HttpServletResponse response, JsonObject respJson) { + try { + String sttResult = speachToTextClova(request, response, respJson); +// String sttResult = "헬스장가려고하는데, 일주일 내내 하루 한 시간 삼분할로 운동할 예정이야"; + System.out.println("STT Result: " + sttResult); + if (sttResult == null) throw new NullPointerException("A03"); + + // AI서비스 호출 + String aiGeneratedResult = scheduleAiService.generateClovaSchedule(sttResult); + System.out.println("aiGeneratedResult: " + aiGeneratedResult); + + + // 응답 데이터 구성 + respJson.addProperty("result", "true"); + respJson.add("schedule", JsonParser.parseString(aiGeneratedResult).getAsJsonObject()); + + } catch (Exception e) { + e.printStackTrace(); + respJson.addProperty("result", "false"); + respJson.addProperty("error", e.getMessage()); + } + } + private String speachToTextClova(HttpServletRequest request, HttpServletResponse response, JsonObject respJson) throws IOException, ServletException { + + Part audioFile = request.getPart("audioFile"); + InputStream audioStream = audioFile.getInputStream(); + + return clovaService.callClovaSTT(audioStream); // Naver Clova 호출 + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/user/application/converter/GeneralSignupUserDtoToUserEntityWithSaltConverter.java b/src/main/java/io/ssafy/luckyweeky/user/application/converter/GeneralSignupUserDtoToUserEntityWithSaltConverter.java new file mode 100644 index 0000000..ba7aba3 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/user/application/converter/GeneralSignupUserDtoToUserEntityWithSaltConverter.java @@ -0,0 +1,31 @@ +package io.ssafy.luckyweeky.user.application.converter; + +import io.ssafy.luckyweeky.common.implement.Converter; +import io.ssafy.luckyweeky.common.util.security.OpenCrypt; +import io.ssafy.luckyweeky.common.util.generator.SnowflakeIdGenerator; +import io.ssafy.luckyweeky.user.application.dto.GeneralSignupUserDto; +import io.ssafy.luckyweeky.user.domain.model.UserEntity; + +public class GeneralSignupUserDtoToUserEntityWithSaltConverter implements Converter { + private static final GeneralSignupUserDtoToUserEntityWithSaltConverter INSTANCE = new GeneralSignupUserDtoToUserEntityWithSaltConverter(); + + private GeneralSignupUserDtoToUserEntityWithSaltConverter() {} + + public static GeneralSignupUserDtoToUserEntityWithSaltConverter getInstance() { + return INSTANCE; + } + @Override + public UserEntityWithSalt convert(GeneralSignupUserDto generalSignupUser) { + String salt = OpenCrypt.createEncryptSalt(); + UserEntity userEntity = new UserEntity.Builder() + .userId(SnowflakeIdGenerator.getInstance().nextId()) + .passwordHash(OpenCrypt.getEncryptPassword(generalSignupUser.getPassword(), salt)) + .username(generalSignupUser.getUsername()) + .email(generalSignupUser.getEmail()) + .birthDate(generalSignupUser.getBirthDate()) + .profileImageKey(generalSignupUser.getProfileImageKey()) + .build(); + + return new UserEntityWithSalt(userEntity, salt); + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/user/application/converter/UserEntityToLoginUserDto.java b/src/main/java/io/ssafy/luckyweeky/user/application/converter/UserEntityToLoginUserDto.java new file mode 100644 index 0000000..b942b0a --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/user/application/converter/UserEntityToLoginUserDto.java @@ -0,0 +1,24 @@ +package io.ssafy.luckyweeky.user.application.converter; + +import io.ssafy.luckyweeky.common.implement.Converter; +import io.ssafy.luckyweeky.user.application.dto.LoginUserDto; +import io.ssafy.luckyweeky.user.domain.model.UserEntity; + +public class UserEntityToLoginUserDto implements Converter { + private final static UserEntityToLoginUserDto INSTANCE = new UserEntityToLoginUserDto(); + + public static UserEntityToLoginUserDto getInstance() { + return INSTANCE; + } + + @Override + public LoginUserDto convert(UserEntity source) { + return new LoginUserDto( + source.getUserId(), + source.getUsername(), + source.getEmail(), + source.getBirthDate(), + source.getProfileImageKey() + ); + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/user/application/converter/UserEntityWithSalt.java b/src/main/java/io/ssafy/luckyweeky/user/application/converter/UserEntityWithSalt.java new file mode 100644 index 0000000..9945353 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/user/application/converter/UserEntityWithSalt.java @@ -0,0 +1,21 @@ +package io.ssafy.luckyweeky.user.application.converter; + +import io.ssafy.luckyweeky.user.domain.model.UserEntity; + +public class UserEntityWithSalt { + private final UserEntity userEntity; + private final String salt; + + public UserEntityWithSalt(UserEntity userEntity, String salt) { + this.userEntity = userEntity; + this.salt = salt; + } + + public UserEntity getUserEntity() { + return userEntity; + } + + public String getSalt() { + return salt; + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/dispatcher/dto/GeneralSignupUser.java b/src/main/java/io/ssafy/luckyweeky/user/application/dto/GeneralSignupUserDto.java similarity index 77% rename from src/main/java/io/ssafy/luckyweeky/dispatcher/dto/GeneralSignupUser.java rename to src/main/java/io/ssafy/luckyweeky/user/application/dto/GeneralSignupUserDto.java index 1620aaf..ecb9c92 100644 --- a/src/main/java/io/ssafy/luckyweeky/dispatcher/dto/GeneralSignupUser.java +++ b/src/main/java/io/ssafy/luckyweeky/user/application/dto/GeneralSignupUserDto.java @@ -1,15 +1,15 @@ -package io.ssafy.luckyweeky.dispatcher.dto; +package io.ssafy.luckyweeky.user.application.dto; import java.time.LocalDate; -public class GeneralSignupUser { +public class GeneralSignupUserDto { private String email; // 이메일 private String password; // 비밀번호 private String username; // 사용자 이름 private LocalDate birthDate; // 생년월일 private String profileImageKey; // 프로필 이미지 URL - public GeneralSignupUser(String email, String password, String username, String birthDate, String profileImageKey) { + public GeneralSignupUserDto(String email, String password, String username, String birthDate, String profileImageKey) { this.email = email; this.password = password; this.username = username; diff --git a/src/main/java/io/ssafy/luckyweeky/dispatcher/dto/GoogleSignupUser.java b/src/main/java/io/ssafy/luckyweeky/user/application/dto/LoginUserDto.java similarity index 51% rename from src/main/java/io/ssafy/luckyweeky/dispatcher/dto/GoogleSignupUser.java rename to src/main/java/io/ssafy/luckyweeky/user/application/dto/LoginUserDto.java index e373418..efc78ec 100644 --- a/src/main/java/io/ssafy/luckyweeky/dispatcher/dto/GoogleSignupUser.java +++ b/src/main/java/io/ssafy/luckyweeky/user/application/dto/LoginUserDto.java @@ -1,51 +1,51 @@ -package io.ssafy.luckyweeky.dispatcher.dto; - -import java.time.LocalDate; - -public class GoogleSignupUser { - private String email; // 이메일 - private String password; // 비밀번호 - private String username; // 사용자 이름 - private LocalDate birthDate; // 생년월일 - private String profileImageKey; // 프로필 이미지 URL - private String oauthId; // Google OAuth에서 제공한 고유 ID - private String provider; // OAuth 제공자 정보 (예: "google") - - public GoogleSignupUser(String email, String password, String username, String birthDate, String profileImageKey, String oauthId, String provider) { - this.email = email; - this.password = password; - this.username = username; - this.birthDate = LocalDate.parse(birthDate); - this.profileImageKey = profileImageKey; - this.oauthId = oauthId; - this.provider = provider; - } - - public String getEmail() { - return email; - } - - public String getPassword() { - return password; - } - - public String getUsername() { - return username; - } - - public LocalDate getBirthDate() { - return birthDate; - } - - public String getProfileImageKey() { - return profileImageKey; - } - - public String getOauthId() { - return oauthId; - } - - public String getProvider() { - return provider; - } -} +package io.ssafy.luckyweeky.user.application.dto; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +public class LoginUserDto { + private long userId; // 유저 고유 ID + private String username; // 사용자 이름 + private String email; // 이메일 + private String password; // 비밀번호 해시값 + private LocalDate birthDate; // 생년월일 + private String profileImageKey; // 프로필 이미지 URL + + public LoginUserDto(String email, String password) { + this.email = email; + this.password = password; + } + + public LoginUserDto(long userId, String username, String email, LocalDate birthDate, String profileImageKey) { + this.userId = userId; + this.username = username; + this.email = email; + this.password = ""; + this.birthDate = birthDate; + this.profileImageKey = profileImageKey; + } + + public long getUserId() { + return userId; + } + + public String getUsername() { + return username; + } + + public String getEmail() { + return email; + } + + public String getPassword() { + return password; + } + + public LocalDate getBirthDate() { + return birthDate; + } + + public String getProfileImageKey() { + return profileImageKey; + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/user/application/service/UserService.java b/src/main/java/io/ssafy/luckyweeky/user/application/service/UserService.java new file mode 100644 index 0000000..81fe31f --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/user/application/service/UserService.java @@ -0,0 +1,169 @@ +package io.ssafy.luckyweeky.user.application.service; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.ssafy.luckyweeky.common.DispatcherServlet; +import io.ssafy.luckyweeky.common.config.XmlBeanFactory; +import io.ssafy.luckyweeky.common.infrastructure.provider.JwtTokenProvider; +import io.ssafy.luckyweeky.user.application.converter.GeneralSignupUserDtoToUserEntityWithSaltConverter; +import io.ssafy.luckyweeky.user.application.converter.UserEntityToLoginUserDto; +import io.ssafy.luckyweeky.user.application.converter.UserEntityWithSalt; +import io.ssafy.luckyweeky.common.infrastructure.s3.S3Fileloader; +import io.ssafy.luckyweeky.common.util.security.OpenCrypt; +import io.ssafy.luckyweeky.user.application.dto.GeneralSignupUserDto; +import io.ssafy.luckyweeky.user.application.dto.LoginUserDto; +import io.ssafy.luckyweeky.user.domain.model.UserEntity; +import io.ssafy.luckyweeky.user.domain.model.UserSaltEntity; +import io.ssafy.luckyweeky.user.infrastructure.repository.UserRepository; +import jakarta.servlet.http.Part; +import org.apache.ibatis.annotations.One; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +public class UserService { + private final UserRepository userRepository; + // Access Token 유효 시간 (30분) + private static final long ACCESS_TOKEN_VALIDITY = 30 * 60 * 1000; + // Refresh Token 유효 시간 (7일) + private static final long REFRESH_TOKEN_VALIDITY = 7 * 24 * 60 * 60 * 1000; + + public UserService() { + this.userRepository = (UserRepository) XmlBeanFactory.getBean("userRepository"); + } + + /** + * 이메일 존재 여부 체크 + * + * @param email 확인할 이메일 + * @return 이메일 존재 시 true, 존재하지 않으면 false + */ + public boolean isEmailExists(String email) { + return userRepository.findByEmail(email) != null; + } + + /** + * 로그인 처리 + * + * @param loginUser 사용자 정보(email,password) + * @return 로그인 성공 시 access_token, refresh_token담긴 map반환 + */ + public Map login(LoginUserDto loginUser) { + String salt = userRepository.getUserSalt(loginUser.getEmail()); + UserEntity user = userRepository.findByEmail(loginUser.getEmail()); + + if (salt != null && user != null && user.getPasswordHash().equals(OpenCrypt.getEncryptPassword(loginUser.getPassword(), salt))) { + // Access Token Claims 생성 + Claims accessClaims = Jwts.claims(); + accessClaims.put("name", user.getUsername()); + Claims refreshClaims = Jwts.claims(); + + String userId = user.getUserId() + ""; + // Access Token 생성 + String accessToken = JwtTokenProvider.getInstance().createToken(userId, accessClaims, ACCESS_TOKEN_VALIDITY); + // Refresh Token 생성 + String refreshToken = JwtTokenProvider.getInstance().createToken(userId, refreshClaims, REFRESH_TOKEN_VALIDITY); + + return userRepository.updateRefreshToken(user.getUserId(), refreshToken) + ? Map.of("accessToken", accessToken, "refreshToken", refreshToken) + : null; + } + return null; // 로그인 실패 + } + + /** + * 회원가입 처리 + * + * @param generalSignupUser 사용자 회원가입 정보 + * @param filePart 사용자 프로필 이미지정보 + * @return 회원가입 성공 시 true, 실패 시 false + */ + public boolean generalRegister(GeneralSignupUserDto generalSignupUser, Part filePart) throws IOException { + // 이미 이메일이 존재하면 회원가입 실패 + if (isEmailExists(generalSignupUser.getEmail())) { + return false; + } + + if (filePart != null) { + File tempFile = null; + try { + // 파일 확장자 추출 + int lastDotIndex = generalSignupUser.getProfileImageKey().lastIndexOf("."); + String extension = (lastDotIndex != -1) ? generalSignupUser.getProfileImageKey().substring(lastDotIndex) : ".jpg"; + + // 프로젝트 디렉토리 기준으로 임시 파일 저장 경로 설정 + String tempDirPath = DispatcherServlet.getWebInfPath() + "/temp"; + File tempDir = new File(tempDirPath); + if (!tempDir.exists()) { + tempDir.mkdirs(); // 디렉토리가 없으면 생성 + } + + // 임시 파일 생성 + tempFile = File.createTempFile("upload-", extension, tempDir); + + // 파일 쓰기 + filePart.write(tempFile.getAbsolutePath()); + + // S3에 파일 등록 + S3Fileloader.getInstance().upload(tempFile, generalSignupUser.getProfileImageKey()); + } catch (IOException e) { + throw new IOException("File Upload Error"); + } finally { + if (tempFile != null && tempFile.exists()) { + // 임시 파일 삭제 + tempFile.delete(); + } + } + } + UserEntityWithSalt result = GeneralSignupUserDtoToUserEntityWithSaltConverter.getInstance().convert(generalSignupUser); + String salt = result.getSalt(); + UserEntity userEntity = result.getUserEntity(); + UserSaltEntity userSaltEntity = new UserSaltEntity(userEntity.getUserId(), salt); + + System.out.println(salt); + System.out.println(userEntity); + System.out.println(userSaltEntity); + + return userRepository.insertUser(userEntity, userSaltEntity); + } + + /** + * 사용자 refresh_token무효화 + * + * @param refreshToken + */ + public void invalidateRefreshToken(String refreshToken) { + // redis 로직 추가 + String userId = JwtTokenProvider.getInstance().getSubject(refreshToken); + if (userId != null) { + userRepository.deleteTokenById(Long.parseLong(userId)); + } + } + + /** + * 사용자 tokens 검사 and 생성 + * + * @param Map + * @return 유효시 시 새로운access_token, refresh_token + * 유효하지 않을 시 null + */ + public Map createTokens(Map params) { + String userId = (String) params.get("userId"); + String refreshToken = (String) params.get("refreshToken"); + if (userId == null || refreshToken == null) { + return null; + } + UserEntity user = userRepository.findById(Long.parseLong(userId)); + + Claims accessClaims = Jwts.claims(); + accessClaims.put("name", user.getUsername()); + String newAccessToken = JwtTokenProvider.getInstance().createToken(userId, accessClaims, ACCESS_TOKEN_VALIDITY); + String newRefreshToken = JwtTokenProvider.getInstance().createToken(userId, Jwts.claims(), REFRESH_TOKEN_VALIDITY); + + return userRepository.updateRefreshToken(user.getUserId(), newRefreshToken) + ? Map.of("accessToken", newAccessToken, "refreshToken", newRefreshToken) + : null; + } +} + diff --git a/src/main/java/io/ssafy/luckyweeky/dispatcher/validator/FileValidator.java b/src/main/java/io/ssafy/luckyweeky/user/application/validator/FileValidator.java similarity index 57% rename from src/main/java/io/ssafy/luckyweeky/dispatcher/validator/FileValidator.java rename to src/main/java/io/ssafy/luckyweeky/user/application/validator/FileValidator.java index 94302c5..53330f0 100644 --- a/src/main/java/io/ssafy/luckyweeky/dispatcher/validator/FileValidator.java +++ b/src/main/java/io/ssafy/luckyweeky/user/application/validator/FileValidator.java @@ -1,12 +1,11 @@ -package io.ssafy.luckyweeky.dispatcher.validator; +package io.ssafy.luckyweeky.user.application.validator; import jakarta.servlet.http.Part; import java.awt.image.BufferedImage; +import java.io.IOException; import java.io.InputStream; -import java.nio.file.Files; import javax.imageio.ImageIO; -import jakarta.servlet.http.Part; public class FileValidator { private static FileValidator instance; @@ -36,40 +35,33 @@ public static FileValidator getInstance() { * @param part 업로드된 파일의 Part 객체 * @return 파일이 유효한지 여부 */ - public boolean isValid(Part part) throws Exception{ + public boolean isValid(Part part){ return part==null||(isValidSize(part) && isValidMimeType(part) && isValidImageContent(part)); } - private boolean isValidSize(Part part) throws Exception{ - if(part.getSize() > maxFileSizeInBytes){ - throw new Exception("파일크기에러코드작성"); - } - return true; + private boolean isValidSize(Part part){ + return part.getSize() <= maxFileSizeInBytes; } - private boolean isValidMimeType(Part part) throws Exception{ - try { - String mimeType = part.getContentType(); // Part에서 MIME 타입 직접 가져오기 - if (mimeType == null) { - return false; - } - for (String allowedMimeType : allowedMimeTypes) { - if (mimeType.equals(allowedMimeType)) { - return true; - } + private boolean isValidMimeType(Part part){ + String mimeType = part.getContentType(); // Part에서 MIME 타입 직접 가져오기 + if (mimeType == null) { + return false; + } + for (String allowedMimeType : allowedMimeTypes) { + if (mimeType.equals(allowedMimeType)) { + return true; } - } catch (Exception e) { - throw new Exception("마인타입에라코드작성"); } return false; } - private boolean isValidImageContent(Part part) throws Exception{ + private boolean isValidImageContent(Part part){ try (InputStream inputStream = part.getInputStream()) { BufferedImage image = ImageIO.read(inputStream); return image != null; // 이미지 데이터를 정상적으로 읽을 수 있는지 확인 - } catch (Exception e) { - throw new Exception("비정상적이미지데이터에러코드작성"); + }catch (IOException e){ + return false; } } } diff --git a/src/main/java/io/ssafy/luckyweeky/dispatcher/validator/LoginUserValidator.java b/src/main/java/io/ssafy/luckyweeky/user/application/validator/LoginUserValidator.java similarity index 78% rename from src/main/java/io/ssafy/luckyweeky/dispatcher/validator/LoginUserValidator.java rename to src/main/java/io/ssafy/luckyweeky/user/application/validator/LoginUserValidator.java index 099696e..911e7cf 100644 --- a/src/main/java/io/ssafy/luckyweeky/dispatcher/validator/LoginUserValidator.java +++ b/src/main/java/io/ssafy/luckyweeky/user/application/validator/LoginUserValidator.java @@ -1,6 +1,6 @@ -package io.ssafy.luckyweeky.dispatcher.validator; +package io.ssafy.luckyweeky.user.application.validator; -import io.ssafy.luckyweeky.dispatcher.dto.LoginUser; +import io.ssafy.luckyweeky.user.application.dto.LoginUserDto; import java.util.regex.Pattern; @@ -8,7 +8,7 @@ public class LoginUserValidator { private String errorCode; // 유효성 검사 - public boolean isValid(LoginUser request) { + public boolean isValid(LoginUserDto request) { if (request.getEmail() == null || !isValidEmail(request.getEmail())) { errorCode = "U01"; return false; diff --git a/src/main/java/io/ssafy/luckyweeky/domain/user/model/UserEntity.java b/src/main/java/io/ssafy/luckyweeky/user/domain/model/UserEntity.java similarity index 95% rename from src/main/java/io/ssafy/luckyweeky/domain/user/model/UserEntity.java rename to src/main/java/io/ssafy/luckyweeky/user/domain/model/UserEntity.java index a3fcd67..248e5d9 100644 --- a/src/main/java/io/ssafy/luckyweeky/domain/user/model/UserEntity.java +++ b/src/main/java/io/ssafy/luckyweeky/user/domain/model/UserEntity.java @@ -1,198 +1,199 @@ -package io.ssafy.luckyweeky.domain.user.model; - -import java.io.Serializable; -import java.time.LocalDate; -import java.time.LocalDateTime; - -public class UserEntity implements Serializable { - private long userId; // 유저 고유 ID - private String username; // 사용자 이름 - private String email; // 이메일 - private String passwordHash; // 비밀번호 해시값 - private String oauthProvider; // OAuth 제공자 (예: Google) - private String oauthId; // OAuth 제공자가 발급한 고유 사용자 ID - private LocalDate birthDate; // 생년월일 - private String profileImageKey; // 프로필 이미지 URL - private LocalDateTime lastLoginAt; // 마지막 로그인 시간 - private LocalDateTime createdAt; // 생성 날짜 - private LocalDateTime updatedAt; // 업데이트 날짜 - - public UserEntity() {} - - // Private Constructor - private UserEntity(Builder builder) { - this.userId = builder.userId; - this.username = builder.username; - this.email = builder.email; - this.passwordHash = builder.passwordHash; - this.oauthProvider = builder.oauthProvider; - this.oauthId = builder.oauthId; - this.birthDate = builder.birthDate; - this.profileImageKey = builder.profileImageKey; - this.lastLoginAt = builder.lastLoginAt; - this.createdAt = builder.createdAt; - this.updatedAt = builder.updatedAt; - } - - // Builder Class - public static class Builder { - private long userId; - private String username; - private String email; - private String passwordHash; - private String oauthProvider; - private String oauthId; - private LocalDate birthDate; - private String profileImageKey; - private LocalDateTime lastLoginAt; - private LocalDateTime createdAt; - private LocalDateTime updatedAt; - - public Builder userId(long userId) { - this.userId = userId; - return this; - } - - public Builder username(String username) { - this.username = username; - return this; - } - - public Builder email(String email) { - this.email = email; - return this; - } - - public Builder passwordHash(String passwordHash) { - this.passwordHash = passwordHash; - return this; - } - - public Builder oauthProvider(String oauthProvider) { - this.oauthProvider = oauthProvider; - return this; - } - - public Builder oauthId(String oauthId) { - this.oauthId = oauthId; - return this; - } - - public Builder birthDate(LocalDate birthDate) { - this.birthDate = birthDate; - return this; - } - - public Builder profileImageKey(String profileImageKey) { - this.profileImageKey = profileImageKey; - return this; - } - - public Builder lastLoginAt(LocalDateTime lastLoginAt) { - this.lastLoginAt = lastLoginAt; - return this; - } - - public Builder createdAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - return this; - } - - public Builder updatedAt(LocalDateTime updatedAt) { - this.updatedAt = updatedAt; - return this; - } - - public UserEntity build() { - return new UserEntity(this); - } - } - - public long getUserId() { - return userId; - } - - public void setUserId(long userId) { - this.userId = userId; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getPasswordHash() { - return passwordHash; - } - - public void setPasswordHash(String passwordHash) { - this.passwordHash = passwordHash; - } - - public String getOauthProvider() { - return oauthProvider; - } - - public void setOauthProvider(String oauthProvider) { - this.oauthProvider = oauthProvider; - } - - public String getOauthId() { - return oauthId; - } - - public void setOauthId(String oauthId) { - this.oauthId = oauthId; - } - - public LocalDate getBirthDate() { - return birthDate; - } - - public void setBirthDate(LocalDate birthDate) { - this.birthDate = birthDate; - } - - public String getProfileImageKey() { - return profileImageKey; - } - - public void setProfileImageKey(String profileImageKey) { - this.profileImageKey = profileImageKey; - } - - public LocalDateTime getLastLoginAt() { - return lastLoginAt; - } - - public void setLastLoginAt(LocalDateTime lastLoginAt) { - this.lastLoginAt = lastLoginAt; - } - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public LocalDateTime getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(LocalDateTime updatedAt) { - this.updatedAt = updatedAt; - } -} + +package io.ssafy.luckyweeky.user.domain.model; + +import java.io.Serializable; +import java.time.LocalDate; +import java.time.LocalDateTime; + +public class UserEntity implements Serializable { + private long userId; // 유저 고유 ID + private String username; // 사용자 이름 + private String email; // 이메일 + private String passwordHash; // 비밀번호 해시값 + private String oauthProvider; // OAuth 제공자 (예: Google) + private String oauthId; // OAuth 제공자가 발급한 고유 사용자 ID + private LocalDate birthDate; // 생년월일 + private String profileImageKey; // 프로필 이미지 URL + private LocalDateTime lastLoginAt; // 마지막 로그인 시간 + private LocalDateTime createdAt; // 생성 날짜 + private LocalDateTime updatedAt; // 업데이트 날짜 + + public UserEntity() {} + + // Private Constructor + private UserEntity(Builder builder) { + this.userId = builder.userId; + this.username = builder.username; + this.email = builder.email; + this.passwordHash = builder.passwordHash; + this.oauthProvider = builder.oauthProvider; + this.oauthId = builder.oauthId; + this.birthDate = builder.birthDate; + this.profileImageKey = builder.profileImageKey; + this.lastLoginAt = builder.lastLoginAt; + this.createdAt = builder.createdAt; + this.updatedAt = builder.updatedAt; + } + + // Builder Class + public static class Builder { + private long userId; + private String username; + private String email; + private String passwordHash; + private String oauthProvider; + private String oauthId; + private LocalDate birthDate; + private String profileImageKey; + private LocalDateTime lastLoginAt; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + public Builder userId(long userId) { + this.userId = userId; + return this; + } + + public Builder username(String username) { + this.username = username; + return this; + } + + public Builder email(String email) { + this.email = email; + return this; + } + + public Builder passwordHash(String passwordHash) { + this.passwordHash = passwordHash; + return this; + } + + public Builder oauthProvider(String oauthProvider) { + this.oauthProvider = oauthProvider; + return this; + } + + public Builder oauthId(String oauthId) { + this.oauthId = oauthId; + return this; + } + + public Builder birthDate(LocalDate birthDate) { + this.birthDate = birthDate; + return this; + } + + public Builder profileImageKey(String profileImageKey) { + this.profileImageKey = profileImageKey; + return this; + } + + public Builder lastLoginAt(LocalDateTime lastLoginAt) { + this.lastLoginAt = lastLoginAt; + return this; + } + + public Builder createdAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + return this; + } + + public Builder updatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + return this; + } + + public UserEntity build() { + return new UserEntity(this); + } + } + + public long getUserId() { + return userId; + } + + public void setUserId(long userId) { + this.userId = userId; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPasswordHash() { + return passwordHash; + } + + public void setPasswordHash(String passwordHash) { + this.passwordHash = passwordHash; + } + + public String getOauthProvider() { + return oauthProvider; + } + + public void setOauthProvider(String oauthProvider) { + this.oauthProvider = oauthProvider; + } + + public String getOauthId() { + return oauthId; + } + + public void setOauthId(String oauthId) { + this.oauthId = oauthId; + } + + public LocalDate getBirthDate() { + return birthDate; + } + + public void setBirthDate(LocalDate birthDate) { + this.birthDate = birthDate; + } + + public String getProfileImageKey() { + return profileImageKey; + } + + public void setProfileImageKey(String profileImageKey) { + this.profileImageKey = profileImageKey; + } + + public LocalDateTime getLastLoginAt() { + return lastLoginAt; + } + + public void setLastLoginAt(LocalDateTime lastLoginAt) { + this.lastLoginAt = lastLoginAt; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/domain/user/model/UserSaltEntity.java b/src/main/java/io/ssafy/luckyweeky/user/domain/model/UserSaltEntity.java similarity index 87% rename from src/main/java/io/ssafy/luckyweeky/domain/user/model/UserSaltEntity.java rename to src/main/java/io/ssafy/luckyweeky/user/domain/model/UserSaltEntity.java index ba6998b..ffb4772 100644 --- a/src/main/java/io/ssafy/luckyweeky/domain/user/model/UserSaltEntity.java +++ b/src/main/java/io/ssafy/luckyweeky/user/domain/model/UserSaltEntity.java @@ -1,4 +1,4 @@ -package io.ssafy.luckyweeky.domain.user.model; +package io.ssafy.luckyweeky.user.domain.model; public class UserSaltEntity { private long userId; diff --git a/src/main/java/io/ssafy/luckyweeky/user/domain/repository/UserMapper.java b/src/main/java/io/ssafy/luckyweeky/user/domain/repository/UserMapper.java new file mode 100644 index 0000000..cc503e6 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/user/domain/repository/UserMapper.java @@ -0,0 +1,18 @@ +package io.ssafy.luckyweeky.user.domain.repository; + +import io.ssafy.luckyweeky.user.domain.model.UserEntity; +import io.ssafy.luckyweeky.user.domain.model.UserSaltEntity; + +import java.util.Map; + +public interface UserMapper { + UserEntity findById(long userId); + UserEntity findByEmail(String email); + void insertUser(UserEntity user); + void insertUserSalt(UserSaltEntity userSaltEntity); + String findSaltByEmail(String email); + void insertUserToken(long userId); + int updateUserToken(Map params); + String findTokenById(long userId); + void deleteTokenById(Long userId); +} diff --git a/src/main/java/io/ssafy/luckyweeky/domain/user/repository/UserRepository.java b/src/main/java/io/ssafy/luckyweeky/user/infrastructure/repository/UserRepository.java similarity index 51% rename from src/main/java/io/ssafy/luckyweeky/domain/user/repository/UserRepository.java rename to src/main/java/io/ssafy/luckyweeky/user/infrastructure/repository/UserRepository.java index db260ad..df96e32 100644 --- a/src/main/java/io/ssafy/luckyweeky/domain/user/repository/UserRepository.java +++ b/src/main/java/io/ssafy/luckyweeky/user/infrastructure/repository/UserRepository.java @@ -1,52 +1,84 @@ -package io.ssafy.luckyweeky.domain.user.repository; - -import io.ssafy.luckyweeky.domain.user.model.UserEntity; -import io.ssafy.luckyweeky.domain.user.model.UserSaltEntity; -import io.ssafy.luckyweeky.infrastructure.persistence.MyBatisSqlSessionFactory; -import org.apache.ibatis.session.SqlSession; -import org.apache.ibatis.session.SqlSessionFactory; - -public class UserRepository { - private final SqlSessionFactory sqlSessionFactory; - - public UserRepository() { - this.sqlSessionFactory = MyBatisSqlSessionFactory.getSqlSessionFactory(); - } - - // user_id로 사용자 조회 - public UserEntity findById(long userId) { - try (SqlSession session = sqlSessionFactory.openSession()) { - UserMapper mapper = session.getMapper(UserMapper.class); - return mapper.findById(userId); - } - } - - // 이메일로 사용자 조회 - public UserEntity findByEmail(String email) { - try (SqlSession session = sqlSessionFactory.openSession()) { - UserMapper mapper = session.getMapper(UserMapper.class); - return mapper.findByEmail(email); - } - } - - // 사용자 추가 - public boolean insertUser(UserEntity userEntity, UserSaltEntity userSalt) { - try (SqlSession session = sqlSessionFactory.openSession()) { - UserMapper mapper = session.getMapper(UserMapper.class); - mapper.insertUser(userEntity); - mapper.insertUserSalt(userSalt); - session.commit(); // 트랜잭션 커밋 - return true; - }catch (Exception e) { - e.printStackTrace(); - return false; - } - } - - public String getUserSalt(String email) { - try (SqlSession session = sqlSessionFactory.openSession()) { - UserMapper mapper = session.getMapper(UserMapper.class); - return mapper.findSaltByEmail(email); - } - } -} +package io.ssafy.luckyweeky.user.infrastructure.repository; + +import io.ssafy.luckyweeky.common.infrastructure.persistence.MyBatisSqlSessionFactory; +import io.ssafy.luckyweeky.user.domain.model.UserEntity; +import io.ssafy.luckyweeky.user.domain.model.UserSaltEntity; +import io.ssafy.luckyweeky.user.domain.repository.UserMapper; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; + +import java.io.Serializable; +import java.util.Map; + +public class UserRepository { + private final SqlSessionFactory sqlSessionFactory; + + public UserRepository() { + this.sqlSessionFactory = MyBatisSqlSessionFactory.getSqlSessionFactory(); + } + + // user_id로 사용자 조회 + public UserEntity findById(long userId) { + try (SqlSession session = sqlSessionFactory.openSession()) { + UserMapper mapper = session.getMapper(UserMapper.class); + return mapper.findById(userId); + } + } + + // 이메일로 사용자 조회 + public UserEntity findByEmail(String email) { + try (SqlSession session = sqlSessionFactory.openSession()) { + UserMapper mapper = session.getMapper(UserMapper.class); + return mapper.findByEmail(email); + } + } + + // 사용자 추가 + public boolean insertUser(UserEntity userEntity, UserSaltEntity userSalt) { + try (SqlSession session = sqlSessionFactory.openSession(false)) { + UserMapper mapper = session.getMapper(UserMapper.class); + mapper.insertUser(userEntity); + mapper.insertUserSalt(userSalt); + mapper.insertUserToken(userEntity.getUserId()); + session.commit(); // 트랜잭션 커밋 + return true; + }catch (RuntimeException e) { + return false; + } + } + + public String getUserSalt(String email) { + try (SqlSession session = sqlSessionFactory.openSession()) { + UserMapper mapper = session.getMapper(UserMapper.class); + return mapper.findSaltByEmail(email); + } + } + + public boolean updateRefreshToken(long userId, String token) { + try (SqlSession session = sqlSessionFactory.openSession(false)) { + UserMapper mapper = session.getMapper(UserMapper.class); + mapper.updateUserToken(Map.of( + "userId",userId, + "token",token + )); + session.commit(); + return true; + }catch (RuntimeException e) { + return false; + } + } + + public String getUserToken(long userId) { + try (SqlSession session = sqlSessionFactory.openSession()) { + UserMapper mapper = session.getMapper(UserMapper.class); + return mapper.findTokenById(userId); + } + } + + public void deleteTokenById(Long userId) { + try (SqlSession session = sqlSessionFactory.openSession(false)) { + UserMapper mapper = session.getMapper(UserMapper.class); + mapper.deleteTokenById(userId); + } + } +} diff --git a/src/main/java/io/ssafy/luckyweeky/user/presentation/UserController.java b/src/main/java/io/ssafy/luckyweeky/user/presentation/UserController.java new file mode 100644 index 0000000..63e59e4 --- /dev/null +++ b/src/main/java/io/ssafy/luckyweeky/user/presentation/UserController.java @@ -0,0 +1,110 @@ +package io.ssafy.luckyweeky.user.presentation; + +import com.google.gson.JsonObject; +import io.ssafy.luckyweeky.common.config.XmlBeanFactory; +import io.ssafy.luckyweeky.common.implement.Controller; +import io.ssafy.luckyweeky.common.util.parser.RequestJsonParser; +import io.ssafy.luckyweeky.common.util.security.CookieUtil; +import io.ssafy.luckyweeky.common.util.stream.FileHandler; +import io.ssafy.luckyweeky.common.util.url.RequestUrlPath; +import io.ssafy.luckyweeky.user.application.dto.GeneralSignupUserDto; +import io.ssafy.luckyweeky.user.application.dto.LoginUserDto; +import io.ssafy.luckyweeky.user.application.service.UserService; +import io.ssafy.luckyweeky.user.application.validator.FileValidator; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.Part; + +import java.io.IOException; +import java.util.Map; +import java.util.UUID; + +public class UserController implements Controller { + private static final String USER_PART = "user"; + private static final String FILE_PART = "file"; + + private final UserService userService; + + public UserController() { + this.userService = (UserService) XmlBeanFactory.getBean("userService"); + } + + @Override + public void handleRequest(HttpServletRequest request, HttpServletResponse response, JsonObject respJson) throws ServletException, IOException { + String action = RequestUrlPath.getURI(request.getRequestURI())[1]; + + switch (action){ + case "RClmJ"://signup + signUp(request, response,respJson); + break; + case "LWyAtd"://login + login(request, response,respJson); + break; + case "TGCOwi": + refreshToken(request, response, respJson); + break; + case "odsQk": + logout(request, response, respJson); + break; + } + } + + public void signUp(HttpServletRequest request, HttpServletResponse response, JsonObject respJson) throws ServletException, IOException { + JsonObject jsonObject = RequestJsonParser.getInstance().parse(FileHandler.getFilePart(request,USER_PART)); + Part filePart = FileHandler.getFilePart(request,FILE_PART); + String profileImageKey = FileHandler.processFilePart(filePart); + + GeneralSignupUserDto user = new GeneralSignupUserDto( + jsonObject.get("email").getAsString(), + jsonObject.get("password").getAsString(), + jsonObject.get("username").getAsString(), + jsonObject.get("birthDate").getAsString(), + profileImageKey + ); + + // 4. 회원 가입 서비스 호출 + if (userService.generalRegister(user, filePart)) { + respJson.addProperty("email", user.getEmail()); + } else { + throw new IllegalArgumentException("회원가입 실패 코드 작성"); + } + } + + public void login(HttpServletRequest request, HttpServletResponse response, JsonObject respJson) throws ServletException, IOException { + JsonObject jsonObject = RequestJsonParser.getInstance().parseFromBody(request.getReader()); + LoginUserDto loginUserDto = new LoginUserDto( + jsonObject.get("email").getAsString(), + jsonObject.get("password").getAsString() + ); + Map tokens = userService.login(loginUserDto); + if(tokens==null){ + throw new IOException("login fail"); + } + System.out.println("accessToken:"+tokens.get("accessToken")); + respJson.addProperty("accessToken", tokens.get("accessToken")); + CookieUtil.addRefreshTokenCookie(response,tokens.get("refreshToken")); + } + + public void refreshToken(HttpServletRequest request, HttpServletResponse response, JsonObject respJson) throws ServletException, IOException { + String refreshToken = CookieUtil.getRefreshToken(request); + Map newTokens = userService.createTokens(Map.of( + "refreshToken",refreshToken, + "userId",request.getAttribute("userId") + )); + respJson.addProperty("accessToken", newTokens.get("accessToken")); + CookieUtil.addRefreshTokenCookie(response,newTokens.get("refreshToken")); + } + + + public void logout(HttpServletRequest request, HttpServletResponse response,JsonObject respJson) { + // 1. Refresh Token 무효화 + String refreshToken = CookieUtil.getRefreshToken(request); + + if (refreshToken != null) { + userService.invalidateRefreshToken(refreshToken); // Refresh Token 무효화 로직 (예: DB에서 삭제) + } + CookieUtil.deleteRefreshTokenCookie(response); + } +} + diff --git a/src/main/resources/mappers/MainScheduleMapper.xml b/src/main/resources/mappers/MainScheduleMapper.xml index e69de29..1516c86 100644 --- a/src/main/resources/mappers/MainScheduleMapper.xml +++ b/src/main/resources/mappers/MainScheduleMapper.xml @@ -0,0 +1,99 @@ + + + + + + + + INSERT INTO MainSchedule ( + main_schedule_id, + user_id, + title, + start_time, + end_time, + color, + created_at, + updated_at + ) VALUES ( + #{mainScheduleId}, + #{userId}, + #{title}, + #{startTime}, + #{endTime}, + #{color}, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + ) + + + + + + + + + + + UPDATE MainSchedule + SET + title = #{title}, + start_time = #{startTime}, + end_time = #{endTime}, + color = #{color}, + updated_at = CURRENT_TIMESTAMP + WHERE main_schedule_id = #{mainScheduleId} + + + + + DELETE FROM MainSchedule + WHERE main_schedule_id = #{mainScheduleId} + + + + + diff --git a/src/main/resources/mappers/SubScheduleMapper.xml b/src/main/resources/mappers/SubScheduleMapper.xml index e69de29..97e0fdd 100644 --- a/src/main/resources/mappers/SubScheduleMapper.xml +++ b/src/main/resources/mappers/SubScheduleMapper.xml @@ -0,0 +1,109 @@ + + + + + + + + INSERT INTO SubSchedule ( + sub_schedule_id, + main_schedule_id, + title, + description, + start_time, + end_time, + is_completed, + created_at, + updated_at + ) VALUES ( + #{subScheduleId}, + #{mainScheduleId}, + #{title}, + #{description}, + #{startTime}, + #{endTime}, + #{isCompleted}, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + ) + + + + + + + + + + + UPDATE SubSchedule + SET + title = #{title}, + description = #{description}, + start_time = #{startTime}, + end_time = #{endTime}, + is_completed = #{isCompleted}, + updated_at = CURRENT_TIMESTAMP + WHERE sub_schedule_id = #{subScheduleId} + + + + + DELETE FROM SubSchedule + WHERE title = #{subScheduleTitle} + LIMIT 1 + + + + + + + + + diff --git a/src/main/resources/mappers/UserMapper.xml b/src/main/resources/mappers/UserMapper.xml index 644daca..41f957a 100644 --- a/src/main/resources/mappers/UserMapper.xml +++ b/src/main/resources/mappers/UserMapper.xml @@ -1,7 +1,7 @@ - + INSERT INTO User ( @@ -36,5 +36,22 @@ WHERE u.email = #{email} + + + INSERT INTO usertoken (user_id) VALUES (#{userId}) + + + + + + + + update usertoken set token = #{token} where user_id = #{userId} + + + update usertoken set token = null where user_id = #{userId} + diff --git a/src/main/resources/mybatis-config.xml b/src/main/resources/mybatis-config.xml index 2351dc8..5b55c01 100644 --- a/src/main/resources/mybatis-config.xml +++ b/src/main/resources/mybatis-config.xml @@ -1,35 +1,36 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/beans/controller.xml b/src/main/webapp/WEB-INF/beans/controller.xml index 842353f..5f05abb 100644 --- a/src/main/webapp/WEB-INF/beans/controller.xml +++ b/src/main/webapp/WEB-INF/beans/controller.xml @@ -1,10 +1,6 @@ - - - - - - - + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/beans/model.xml b/src/main/webapp/WEB-INF/beans/model.xml index b2a3243..fe2c21a 100644 --- a/src/main/webapp/WEB-INF/beans/model.xml +++ b/src/main/webapp/WEB-INF/beans/model.xml @@ -1,5 +1,10 @@ - - + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/lib/mysql-connector-j-8.0.33.jar b/src/main/webapp/WEB-INF/lib/mysql-connector-j-8.0.33.jar new file mode 100644 index 0000000..3f741f5 Binary files /dev/null and b/src/main/webapp/WEB-INF/lib/mysql-connector-j-8.0.33.jar differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/lib/mysql-connector-j-8.3.0.jar b/src/main/webapp/WEB-INF/lib/mysql-connector-j-8.3.0.jar similarity index 100% rename from target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/lib/mysql-connector-j-8.3.0.jar rename to src/main/webapp/WEB-INF/lib/mysql-connector-j-8.3.0.jar diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 680ec77..1a621ef 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -4,11 +4,16 @@ xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd" version="6.0"> + + + + io.ssafy.luckyweeky.common.env.SecretManagerContextListener + + DispatcherServlet - io.ssafy.luckyweeky.dispatcher.DispatcherServlet + io.ssafy.luckyweeky.common.DispatcherServlet - C:/portfolio/temp 10485760 52428800 1048576 @@ -21,10 +26,18 @@ CORSFilter - io.ssafy.luckyweeky.dispatcher.filter.CORSFilter + io.ssafy.luckyweeky.common.filter.CORSFilter CORSFilter /* + + JWTFilter + io.ssafy.luckyweeky.common.filter.JwtAuthenticationFilter + + + JWTFilter + /* + diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/META-INF/MANIFEST.MF b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/META-INF/MANIFEST.MF deleted file mode 100644 index 37d4b73..0000000 --- a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/META-INF/MANIFEST.MF +++ /dev/null @@ -1,5 +0,0 @@ -Manifest-Version: 1.0 -Created-By: IntelliJ IDEA -Built-By: 우성문 -Build-Jdk: Oracle OpenJDK 22.0.1 - diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/beans/controller.xml b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/beans/controller.xml deleted file mode 100644 index 842353f..0000000 --- a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/beans/controller.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/beans/model.xml b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/beans/model.xml deleted file mode 100644 index b2a3243..0000000 --- a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/beans/model.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/Controller.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/Controller.class deleted file mode 100644 index f70cd8f..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/Controller.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/schedule/ScheduleController.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/schedule/ScheduleController.class deleted file mode 100644 index 21a3587..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/schedule/ScheduleController.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/schedule/SubScheduleController.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/schedule/SubScheduleController.class deleted file mode 100644 index b3008e9..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/schedule/SubScheduleController.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/ChangePasswordController.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/ChangePasswordController.class deleted file mode 100644 index 7b9cd7d..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/ChangePasswordController.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/GoogleJoinController.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/GoogleJoinController.class deleted file mode 100644 index 7083678..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/GoogleJoinController.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/GoogleLoginController.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/GoogleLoginController.class deleted file mode 100644 index 84ddc71..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/GoogleLoginController.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/JoinController.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/JoinController.class deleted file mode 100644 index 335fd3e..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/JoinController.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/LoginController.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/LoginController.class deleted file mode 100644 index d8ec870..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/LoginController.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/LogoutController.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/LogoutController.class deleted file mode 100644 index eb4d21c..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/LogoutController.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/UserController.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/UserController.class deleted file mode 100644 index 2825446..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/application/user/UserController.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/dispatcher/ControllerMethod.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/dispatcher/ControllerMethod.class deleted file mode 100644 index 4157fe4..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/dispatcher/ControllerMethod.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/dispatcher/DispatcherServlet.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/dispatcher/DispatcherServlet.class deleted file mode 100644 index 479117a..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/dispatcher/DispatcherServlet.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/dispatcher/filter/AuthFilter.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/dispatcher/filter/AuthFilter.class deleted file mode 100644 index 1e09e60..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/dispatcher/filter/AuthFilter.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/dispatcher/filter/LogginFilter.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/dispatcher/filter/LogginFilter.class deleted file mode 100644 index 7363860..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/dispatcher/filter/LogginFilter.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/User.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/User.class deleted file mode 100644 index 39cdb70..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/User.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/UserRepository.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/UserRepository.class deleted file mode 100644 index 9ea7657..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/UserRepository.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/UserService.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/UserService.class deleted file mode 100644 index 99b9d3d..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/UserService.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/dto/UserDTO.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/dto/UserDTO.class deleted file mode 100644 index 6f42505..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/dto/UserDTO.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/model/User.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/model/User.class deleted file mode 100644 index 07ee984..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/model/User.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/model/UserSalt.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/model/UserSalt.class deleted file mode 100644 index b7e506a..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/model/UserSalt.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/repository/UserMapper.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/repository/UserMapper.class deleted file mode 100644 index 811abd4..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/repository/UserMapper.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/repository/UserRepository.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/repository/UserRepository.class deleted file mode 100644 index c4dadcc..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/repository/UserRepository.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/service/UserService.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/service/UserService.class deleted file mode 100644 index 9a6b762..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/domain/user/service/UserService.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/AppConfig.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/AppConfig.class deleted file mode 100644 index b948122..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/AppConfig.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/BeanDefinition.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/BeanDefinition.class deleted file mode 100644 index d598d7a..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/BeanDefinition.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/ConfigException.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/ConfigException.class deleted file mode 100644 index 77dc20a..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/ConfigException.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/XmlBeanFactory.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/XmlBeanFactory.class deleted file mode 100644 index fd15c9b..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/XmlBeanFactory.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/XmlParser$1.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/XmlParser$1.class deleted file mode 100644 index 7c52fc5..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/XmlParser$1.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/XmlParser.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/XmlParser.class deleted file mode 100644 index 5893fac..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/XmlParser.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/bean/BeanDefinition.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/bean/BeanDefinition.class deleted file mode 100644 index 38a110c..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/bean/BeanDefinition.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/bean/XmlBeanFactory.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/bean/XmlBeanFactory.class deleted file mode 100644 index 17f79b2..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/bean/XmlBeanFactory.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/bean/XmlParser$1.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/bean/XmlParser$1.class deleted file mode 100644 index 3b94dbd..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/bean/XmlParser$1.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/bean/XmlParser.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/bean/XmlParser.class deleted file mode 100644 index 852b241..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/config/bean/XmlParser.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/util/JsonUtils.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/util/JsonUtils.class deleted file mode 100644 index a8fd900..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/util/JsonUtils.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/util/RequestBodyParser.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/util/RequestBodyParser.class deleted file mode 100644 index 50f87c9..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/util/RequestBodyParser.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/util/RequestUtils.class b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/util/RequestUtils.class deleted file mode 100644 index b6f7b01..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/io/ssafy/luckyweeky/infrastructure/util/RequestUtils.class and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/mapper/MainScheduleMapper.xml b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/mapper/MainScheduleMapper.xml deleted file mode 100644 index e69de29..0000000 diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/mapper/SubScheduleMapper.xml b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/mapper/SubScheduleMapper.xml deleted file mode 100644 index e69de29..0000000 diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/mapper/UserMapper.xml b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/mapper/UserMapper.xml deleted file mode 100644 index 17cd697..0000000 --- a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/classes/mapper/UserMapper.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - INSERT INTO User (user_id, username, email, password_hash, created_at, updated_at) - VALUES (#{userId}, #{username}, #{email}, #{passwordHash}, #{createdAt}, #{updatedAt}) - - diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/lib/error_prone_annotations-2.27.0.jar b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/lib/error_prone_annotations-2.27.0.jar deleted file mode 100644 index 4ea471f..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/lib/error_prone_annotations-2.27.0.jar and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/lib/gson-2.11.0.jar b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/lib/gson-2.11.0.jar deleted file mode 100644 index 18e59c8..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/lib/gson-2.11.0.jar and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/lib/mybatis-3.5.16.jar b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/lib/mybatis-3.5.16.jar deleted file mode 100644 index 3347062..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/lib/mybatis-3.5.16.jar and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/lib/protobuf-java-3.25.1.jar b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/lib/protobuf-java-3.25.1.jar deleted file mode 100644 index e7b795c..0000000 Binary files a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/lib/protobuf-java-3.25.1.jar and /dev/null differ diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/mybatis-config.xml b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/mybatis-config.xml deleted file mode 100644 index cdc7a66..0000000 --- a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/mybatis-config.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/web.xml b/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/web.xml deleted file mode 100644 index 680ec77..0000000 --- a/target/LuckyWeeky_server-0.0.1-SNAPSHOT/WEB-INF/web.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - DispatcherServlet - io.ssafy.luckyweeky.dispatcher.DispatcherServlet - - C:/portfolio/temp - 10485760 - 52428800 - 1048576 - - - - DispatcherServlet - / - - - - CORSFilter - io.ssafy.luckyweeky.dispatcher.filter.CORSFilter - - - CORSFilter - /* - - diff --git a/target/classes/io/ssafy/luckyweeky/application/Controller.class b/target/classes/io/ssafy/luckyweeky/application/Controller.class deleted file mode 100644 index f70cd8f..0000000 Binary files a/target/classes/io/ssafy/luckyweeky/application/Controller.class and /dev/null differ diff --git a/target/classes/io/ssafy/luckyweeky/application/schedule/ScheduleController.class b/target/classes/io/ssafy/luckyweeky/application/schedule/ScheduleController.class deleted file mode 100644 index 21a3587..0000000 Binary files a/target/classes/io/ssafy/luckyweeky/application/schedule/ScheduleController.class and /dev/null differ diff --git a/target/classes/io/ssafy/luckyweeky/application/schedule/SubScheduleController.class b/target/classes/io/ssafy/luckyweeky/application/schedule/SubScheduleController.class deleted file mode 100644 index b3008e9..0000000 Binary files a/target/classes/io/ssafy/luckyweeky/application/schedule/SubScheduleController.class and /dev/null differ diff --git a/target/classes/io/ssafy/luckyweeky/application/user/ChangePasswordController.class b/target/classes/io/ssafy/luckyweeky/application/user/ChangePasswordController.class deleted file mode 100644 index 7b9cd7d..0000000 Binary files a/target/classes/io/ssafy/luckyweeky/application/user/ChangePasswordController.class and /dev/null differ diff --git a/target/classes/io/ssafy/luckyweeky/application/user/GoogleLoginController.class b/target/classes/io/ssafy/luckyweeky/application/user/GoogleLoginController.class deleted file mode 100644 index 84ddc71..0000000 Binary files a/target/classes/io/ssafy/luckyweeky/application/user/GoogleLoginController.class and /dev/null differ diff --git a/target/classes/io/ssafy/luckyweeky/application/user/LoginController.class b/target/classes/io/ssafy/luckyweeky/application/user/LoginController.class deleted file mode 100644 index d8ec870..0000000 Binary files a/target/classes/io/ssafy/luckyweeky/application/user/LoginController.class and /dev/null differ diff --git a/target/classes/io/ssafy/luckyweeky/application/user/LogoutController.class b/target/classes/io/ssafy/luckyweeky/application/user/LogoutController.class deleted file mode 100644 index eb4d21c..0000000 Binary files a/target/classes/io/ssafy/luckyweeky/application/user/LogoutController.class and /dev/null differ diff --git a/target/classes/io/ssafy/luckyweeky/dispatcher/DispatcherServlet.class b/target/classes/io/ssafy/luckyweeky/dispatcher/DispatcherServlet.class deleted file mode 100644 index 479117a..0000000 Binary files a/target/classes/io/ssafy/luckyweeky/dispatcher/DispatcherServlet.class and /dev/null differ diff --git a/target/classes/io/ssafy/luckyweeky/dispatcher/filter/AuthFilter.class b/target/classes/io/ssafy/luckyweeky/dispatcher/filter/AuthFilter.class deleted file mode 100644 index 1e09e60..0000000 Binary files a/target/classes/io/ssafy/luckyweeky/dispatcher/filter/AuthFilter.class and /dev/null differ diff --git a/target/classes/io/ssafy/luckyweeky/dispatcher/filter/LogginFilter.class b/target/classes/io/ssafy/luckyweeky/dispatcher/filter/LogginFilter.class deleted file mode 100644 index 7363860..0000000 Binary files a/target/classes/io/ssafy/luckyweeky/dispatcher/filter/LogginFilter.class and /dev/null differ diff --git a/target/classes/io/ssafy/luckyweeky/domain/user/repository/UserMapper.class b/target/classes/io/ssafy/luckyweeky/domain/user/repository/UserMapper.class deleted file mode 100644 index 811abd4..0000000 Binary files a/target/classes/io/ssafy/luckyweeky/domain/user/repository/UserMapper.class and /dev/null differ diff --git a/target/classes/io/ssafy/luckyweeky/domain/user/repository/UserRepository.class b/target/classes/io/ssafy/luckyweeky/domain/user/repository/UserRepository.class deleted file mode 100644 index c4dadcc..0000000 Binary files a/target/classes/io/ssafy/luckyweeky/domain/user/repository/UserRepository.class and /dev/null differ diff --git a/target/classes/io/ssafy/luckyweeky/domain/user/service/UserService.class b/target/classes/io/ssafy/luckyweeky/domain/user/service/UserService.class deleted file mode 100644 index 9a6b762..0000000 Binary files a/target/classes/io/ssafy/luckyweeky/domain/user/service/UserService.class and /dev/null differ diff --git a/target/classes/io/ssafy/luckyweeky/infrastructure/config/bean/BeanDefinition.class b/target/classes/io/ssafy/luckyweeky/infrastructure/config/bean/BeanDefinition.class deleted file mode 100644 index 38a110c..0000000 Binary files a/target/classes/io/ssafy/luckyweeky/infrastructure/config/bean/BeanDefinition.class and /dev/null differ diff --git a/target/classes/io/ssafy/luckyweeky/infrastructure/config/bean/XmlBeanFactory.class b/target/classes/io/ssafy/luckyweeky/infrastructure/config/bean/XmlBeanFactory.class deleted file mode 100644 index 17f79b2..0000000 Binary files a/target/classes/io/ssafy/luckyweeky/infrastructure/config/bean/XmlBeanFactory.class and /dev/null differ diff --git a/target/classes/io/ssafy/luckyweeky/infrastructure/config/bean/XmlParser$1.class b/target/classes/io/ssafy/luckyweeky/infrastructure/config/bean/XmlParser$1.class deleted file mode 100644 index 3b94dbd..0000000 Binary files a/target/classes/io/ssafy/luckyweeky/infrastructure/config/bean/XmlParser$1.class and /dev/null differ diff --git a/target/classes/io/ssafy/luckyweeky/infrastructure/config/bean/XmlParser.class b/target/classes/io/ssafy/luckyweeky/infrastructure/config/bean/XmlParser.class deleted file mode 100644 index 852b241..0000000 Binary files a/target/classes/io/ssafy/luckyweeky/infrastructure/config/bean/XmlParser.class and /dev/null differ diff --git a/target/m2e-wtp/web-resources/META-INF/MANIFEST.MF b/target/m2e-wtp/web-resources/META-INF/MANIFEST.MF deleted file mode 100644 index b087487..0000000 --- a/target/m2e-wtp/web-resources/META-INF/MANIFEST.MF +++ /dev/null @@ -1,4 +0,0 @@ -Manifest-Version: 1.0 -Build-Jdk-Spec: 17 -Created-By: Maven Integration for Eclipse - diff --git a/target/m2e-wtp/web-resources/META-INF/maven/LuckyWeeky_server/LuckyWeeky_server/pom.properties b/target/m2e-wtp/web-resources/META-INF/maven/LuckyWeeky_server/LuckyWeeky_server/pom.properties deleted file mode 100644 index 7220ce1..0000000 --- a/target/m2e-wtp/web-resources/META-INF/maven/LuckyWeeky_server/LuckyWeeky_server/pom.properties +++ /dev/null @@ -1,7 +0,0 @@ -#Generated by Maven Integration for Eclipse -#Mon Nov 18 10:43:58 KST 2024 -m2e.projectLocation=C\:\\ssafy\\backend\\LuckyWeeky_server -m2e.projectName=LuckyWeeky_server -groupId=LuckyWeeky_server -artifactId=LuckyWeeky_server -version=0.0.1-SNAPSHOT diff --git a/target/m2e-wtp/web-resources/META-INF/maven/LuckyWeeky_server/LuckyWeeky_server/pom.xml b/target/m2e-wtp/web-resources/META-INF/maven/LuckyWeeky_server/LuckyWeeky_server/pom.xml deleted file mode 100644 index e286751..0000000 --- a/target/m2e-wtp/web-resources/META-INF/maven/LuckyWeeky_server/LuckyWeeky_server/pom.xml +++ /dev/null @@ -1,58 +0,0 @@ - - 4.0.0 - LuckyWeeky_server - LuckyWeeky_server - 0.0.1-SNAPSHOT - war - - - - maven-compiler-plugin - 3.8.1 - - 17 - - - - maven-war-plugin - 3.2.3 - - - - - - org.junit.jupiter - junit-jupiter - 5.10.0 - test - - - - jakarta.servlet - jakarta.servlet-api - 5.0.0 - provided - - - - com.mysql - mysql-connector-j - 8.3.0 - - - - - com.google.code.gson - gson - 2.11.0 - - - - org.mybatis - mybatis - 3.5.16 - - - - \ No newline at end of file