Repository: roadbook2/shop Branch: master Commit: 175815c81986 Files: 94 Total size: 162.0 KB Directory structure: gitextract_kfsect3j/ ├── .gitignore ├── .mvn/ │ └── wrapper/ │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml └── src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── shop/ │ │ ├── ShopApplication.java │ │ ├── config/ │ │ │ ├── AuditConfig.java │ │ │ ├── AuditorAwareImpl.java │ │ │ ├── CsrfCookieFilter.java │ │ │ ├── CustomAuthenticationEntryPoint.java │ │ │ ├── FormLoginAuthenticationFailureHandler.java │ │ │ ├── SecurityConfig.java │ │ │ └── WebMvcConfig.java │ │ ├── constant/ │ │ │ ├── ItemSellStatus.java │ │ │ ├── OrderStatus.java │ │ │ └── Role.java │ │ ├── controller/ │ │ │ ├── CartController.java │ │ │ ├── ItemController.java │ │ │ ├── MainController.java │ │ │ ├── MemberController.java │ │ │ ├── OrderController.java │ │ │ └── ThymeleafExController.java │ │ ├── dto/ │ │ │ ├── CartDetailDto.java │ │ │ ├── CartItemDto.java │ │ │ ├── CartOrderDto.java │ │ │ ├── ItemDto.java │ │ │ ├── ItemFormDto.java │ │ │ ├── ItemImgDto.java │ │ │ ├── ItemSearchDto.java │ │ │ ├── MainItemDto.java │ │ │ ├── MemberFormDto.java │ │ │ ├── OrderDto.java │ │ │ ├── OrderHistDto.java │ │ │ └── OrderItemDto.java │ │ ├── entity/ │ │ │ ├── BaseEntity.java │ │ │ ├── BaseTimeEntity.java │ │ │ ├── Cart.java │ │ │ ├── CartItem.java │ │ │ ├── Item.java │ │ │ ├── ItemImg.java │ │ │ ├── Member.java │ │ │ ├── Order.java │ │ │ └── OrderItem.java │ │ ├── exception/ │ │ │ └── OutOfStockException.java │ │ ├── repository/ │ │ │ ├── CartItemRepository.java │ │ │ ├── CartRepository.java │ │ │ ├── ItemImgRepository.java │ │ │ ├── ItemRepository.java │ │ │ ├── ItemRepositoryCustom.java │ │ │ ├── ItemRepositoryCustomImpl.java │ │ │ ├── MemberRepository.java │ │ │ ├── OrderItemRepository.java │ │ │ └── OrderRepository.java │ │ └── service/ │ │ ├── CartService.java │ │ ├── FileService.java │ │ ├── ItemImgService.java │ │ ├── ItemService.java │ │ ├── MemberService.java │ │ └── OrderService.java │ └── resources/ │ ├── application-test.properties │ ├── application.properties │ ├── static/ │ │ └── css/ │ │ └── layout1.css │ └── templates/ │ ├── cart/ │ │ └── cartList.html │ ├── fragments/ │ │ ├── footer.html │ │ └── header.html │ ├── item/ │ │ ├── itemDtl.html │ │ ├── itemForm.html │ │ └── itemMng.html │ ├── layouts/ │ │ └── layout1.html │ ├── main.html │ ├── member/ │ │ ├── memberForm.html │ │ └── memberLoginForm.html │ ├── order/ │ │ └── orderHist.html │ └── thymeleafEx/ │ ├── thymeleafEx01.html │ ├── thymeleafEx02.html │ ├── thymeleafEx03.html │ ├── thymeleafEx04.html │ ├── thymeleafEx05.html │ ├── thymeleafEx06.html │ └── thymeleafEx07.html └── test/ └── java/ └── com/ └── shop/ ├── ShopApplicationTests.java ├── controller/ │ ├── ItemControllerTest.java │ └── MemberControllerTest.java ├── entity/ │ ├── CartTest.java │ ├── MemberTest.java │ └── OrderTest.java ├── repository/ │ └── ItemRepositoryTest.java └── service/ ├── CartServiceTest.java ├── ItemServiceTest.java ├── MemberServiceTest.java └── OrderServiceTest.java ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ HELP.md target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ !**/src/test/**/target/ ### STS ### .apt_generated .classpath .factorypath .project .settings .springBeans .sts4-cache ### IntelliJ IDEA ### .idea *.iws *.iml *.ipr ### NetBeans ### /nbproject/private/ /nbbuild/ /dist/ /nbdist/ /.nb-gradle/ build/ !**/src/main/**/build/ !**/src/test/**/build/ ### VS Code ### .vscode/ ================================================ FILE: .mvn/wrapper/MavenWrapperDownloader.java ================================================ /* * Copyright 2007-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.net.*; import java.io.*; import java.nio.channels.*; import java.util.Properties; public class MavenWrapperDownloader { private static final String WRAPPER_VERSION = "0.5.6"; /** * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. */ private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; /** * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to * use instead of the default one. */ private static final String MAVEN_WRAPPER_PROPERTIES_PATH = ".mvn/wrapper/maven-wrapper.properties"; /** * Path where the maven-wrapper.jar will be saved to. */ private static final String MAVEN_WRAPPER_JAR_PATH = ".mvn/wrapper/maven-wrapper.jar"; /** * Name of the property which should be used to override the default download url for the wrapper. */ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; public static void main(String args[]) { System.out.println("- Downloader started"); File baseDirectory = new File(args[0]); System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); // If the maven-wrapper.properties exists, read it and check if it contains a custom // wrapperUrl parameter. File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); String url = DEFAULT_DOWNLOAD_URL; if(mavenWrapperPropertyFile.exists()) { FileInputStream mavenWrapperPropertyFileInputStream = null; try { mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); Properties mavenWrapperProperties = new Properties(); mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); } catch (IOException e) { System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); } finally { try { if(mavenWrapperPropertyFileInputStream != null) { mavenWrapperPropertyFileInputStream.close(); } } catch (IOException e) { // Ignore ... } } } System.out.println("- Downloading from: " + url); File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); if(!outputFile.getParentFile().exists()) { if(!outputFile.getParentFile().mkdirs()) { System.out.println( "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); } } System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); try { downloadFileFromURL(url, outputFile); System.out.println("Done"); System.exit(0); } catch (Throwable e) { System.out.println("- Error downloading"); e.printStackTrace(); System.exit(1); } } private static void downloadFileFromURL(String urlString, File destination) throws Exception { if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { String username = System.getenv("MVNW_USERNAME"); char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(username, password); } }); } URL website = new URL(urlString); ReadableByteChannel rbc; rbc = Channels.newChannel(website.openStream()); FileOutputStream fos = new FileOutputStream(destination); fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); fos.close(); rbc.close(); } } ================================================ FILE: .mvn/wrapper/maven-wrapper.properties ================================================ distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar ================================================ FILE: README.md ================================================ # 1.변경사항 - 스프링 시큐리티 설정 수정 (2025-02-13) - 애플리케이션 기동 후 첫 post에서 csrf 토큰이 생성되지 않는 문제 발견 - CsrfCookieFilter에서 csrfToken.getToken() 을 호출하여 csrf 토큰이 없는 경우 강제로 생성 - SecurityConfig에 CsrfCookieFilter 추가 - 스프링 시큐리티 설정 수정 (2025-02-03) - 로그인 후 redirect 오류로 인한 SecurityConfig -> defaultSucceeUrl 항상 "/"로 가도록 true 설정 - SecurityConfig에서 csrfTokenRepository로 CookieCsrfTokenRepository 를 사용하도록 수정. -> 디폴트로 HttpSessionCsrfTokenRepository를 사용하는데 세션이 생성되기 전에 csrf가 필요한 페이지 접근 시 csrf 값을 가져올 수 없는 문제가 있음 -> CookieCsrfTokenRepository를 사용하여 http only cookie에 csrf 토큰을 저장하도록 수정 - 스프링 시큐리티 설정 수정 (2025-01-26) - 상품 상세 페이지에서 order() 실행 시 비로그인 상태의 경우 http status 401로 응답이 되지 않는 버그 발견 - CustomAuthenticationEntryPoint 클래스 신규 생성 및 SecurityConfig 설정 추가 - CustomAuthenticationFailureHandler -> FormLoginAuthenticationFailureHandler 클래스명 리팩토링 진행 - 스프링부트3(2024-08-17) branch - 스프링부트 3.3.2 버전으로 업데이트 - 스프링시큐리티 설정 수정 (SecurityConfig, CustomAuthenticationEntryPoint -> CustomAuthenticationFailureHandler 수정) - thymeleaf-layout-dialect 3.2.1 -> 3.3.0 버전업 진행 - 스프링부트3(2023-07-17) branch - 스프링부트 3.1.1 버전으로 업데이트 - 스프링부트3 버전부터는 자바 17버전 이상을 사용해야합니다. - pom.xml 버전 정보가 많이 바뀌었습니다. 최신 pom.xml 버전을 참고해주세요 - javax에서 jakarta로 변경됨에 따라서 많은 import 들이 jakarta로 수정되었습니다. - javax.validation => jakarta.validation - javax.persistence => jakarta.persistence - CustomAuthenticationEntryPoint.java (javax.servlet => jakarta.servlet) - Security 버전이 수정됨에 따라 기존 메소드가 deprecated 됐습니다. 최신 설정은 추후 올려두도록하겠습니다. - layout1.html 파일 내용 수정 - thymeleaf layout 버전 증가에 따른 코드 수정 - th:replace="fragments/header::header"> => th:replace="~{fragments/header::header}"> - th:replace="fragments/footer::footer"> => th:replace="~{fragments/footer::footer}"> - 2022-06-26 branch - 스프링부트 2.7.1 버전으로 업데이트 - querydsl 5.0.0 버전으로 pom.xml 업데이트 및 ItemRepositoryCustomImpl의 fetchResults() deprecated 대응 (list 조회 및 count 조회 쿼리 분리) - WebSecurityConfigurerAdapter deprecated로 인한 SecurityConfig 파일 수정 - thymeleaf-layout-dialect 3.1.0 버전으로 업데이트 - modelmapper 3.1.0 버전으로 업데이트 # 2.안내사항 - 스프링부트3로 진행 시 기존 소스코드와 import 등 코드가 많이 달라져서 스프링부트 2.7.1 버전 기준으로 진행을 권장드립니다. - 백견불여일타 스프링부트 with JPA 질의응답 게시판 공지사항을 보시면 자주 겪으시는 오류 사항들도 계속 업데이트 중입니다. 참고부탁드립니다. (https://cafe.naver.com/codefirst) ================================================ FILE: mvnw ================================================ #!/bin/sh # ---------------------------------------------------------------------------- # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Maven Start Up Batch script # # Required ENV vars: # ------------------ # JAVA_HOME - location of a JDK home dir # # Optional ENV vars # ----------------- # M2_HOME - location of maven2's installed home dir # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 # MAVEN_SKIP_RC - flag to disable loading of mavenrc files # ---------------------------------------------------------------------------- if [ -z "$MAVEN_SKIP_RC" ] ; then if [ -f /etc/mavenrc ] ; then . /etc/mavenrc fi if [ -f "$HOME/.mavenrc" ] ; then . "$HOME/.mavenrc" fi fi # OS specific support. $var _must_ be set to either true or false. cygwin=false; darwin=false; mingw=false case "`uname`" in CYGWIN*) cygwin=true ;; MINGW*) mingw=true;; Darwin*) darwin=true # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home # See https://developer.apple.com/library/mac/qa/qa1170/_index.html if [ -z "$JAVA_HOME" ]; then if [ -x "/usr/libexec/java_home" ]; then export JAVA_HOME="`/usr/libexec/java_home`" else export JAVA_HOME="/Library/Java/Home" fi fi ;; esac if [ -z "$JAVA_HOME" ] ; then if [ -r /etc/gentoo-release ] ; then JAVA_HOME=`java-config --jre-home` fi fi if [ -z "$M2_HOME" ] ; then ## resolve links - $0 may be a link to maven's home PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG="`dirname "$PRG"`/$link" fi done saveddir=`pwd` M2_HOME=`dirname "$PRG"`/.. # make it fully qualified M2_HOME=`cd "$M2_HOME" && pwd` cd "$saveddir" # echo Using m2 at $M2_HOME fi # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin ; then [ -n "$M2_HOME" ] && M2_HOME=`cygpath --unix "$M2_HOME"` [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --unix "$CLASSPATH"` fi # For Mingw, ensure paths are in UNIX format before anything is touched if $mingw ; then [ -n "$M2_HOME" ] && M2_HOME="`(cd "$M2_HOME"; pwd)`" [ -n "$JAVA_HOME" ] && JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" fi if [ -z "$JAVA_HOME" ]; then javaExecutable="`which javac`" if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. readLink=`which readlink` if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then if $darwin ; then javaHome="`dirname \"$javaExecutable\"`" javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" else javaExecutable="`readlink -f \"$javaExecutable\"`" fi javaHome="`dirname \"$javaExecutable\"`" javaHome=`expr "$javaHome" : '\(.*\)/bin'` JAVA_HOME="$javaHome" export JAVA_HOME fi fi fi if [ -z "$JAVACMD" ] ; then if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi else JAVACMD="`which java`" fi fi if [ ! -x "$JAVACMD" ] ; then echo "Error: JAVA_HOME is not defined correctly." >&2 echo " We cannot execute $JAVACMD" >&2 exit 1 fi if [ -z "$JAVA_HOME" ] ; then echo "Warning: JAVA_HOME environment variable is not set." fi CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { if [ -z "$1" ] then echo "Path not specified to find_maven_basedir" return 1 fi basedir="$1" wdir="$1" while [ "$wdir" != '/' ] ; do if [ -d "$wdir"/.mvn ] ; then basedir=$wdir break fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then wdir=`cd "$wdir/.."; pwd` fi # end of workaround done echo "${basedir}" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then echo "$(tr -s '\n' ' ' < "$1")" fi } BASE_DIR=`find_maven_basedir "$(pwd)"` if [ -z "$BASE_DIR" ]; then exit 1; fi ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. ########################################################################################## if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found .mvn/wrapper/maven-wrapper.jar" fi else if [ "$MVNW_VERBOSE" = true ]; then echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." fi if [ -n "$MVNW_REPOURL" ]; then jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" else jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" fi while IFS="=" read key value; do case "$key" in (wrapperUrl) jarUrl="$value"; break ;; esac done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" if [ "$MVNW_VERBOSE" = true ]; then echo "Downloading from: $jarUrl" fi wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" if $cygwin; then wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` fi if command -v wget > /dev/null; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found wget ... using wget" fi if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then wget "$jarUrl" -O "$wrapperJarPath" else wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" fi elif command -v curl > /dev/null; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found curl ... using curl" fi if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then curl -o "$wrapperJarPath" "$jarUrl" -f else curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f fi else if [ "$MVNW_VERBOSE" = true ]; then echo "Falling back to using Java to download" fi javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" # For Cygwin, switch paths to Windows format before running javac if $cygwin; then javaClass=`cygpath --path --windows "$javaClass"` fi if [ -e "$javaClass" ]; then if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then if [ "$MVNW_VERBOSE" = true ]; then echo " - Compiling MavenWrapperDownloader.java ..." fi # Compiling the Java class ("$JAVA_HOME/bin/javac" "$javaClass") fi if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then # Running the downloader if [ "$MVNW_VERBOSE" = true ]; then echo " - Running MavenWrapperDownloader.java ..." fi ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") fi fi fi fi ########################################################################################## # End of extension ########################################################################################## export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} if [ "$MVNW_VERBOSE" = true ]; then echo $MAVEN_PROJECTBASEDIR fi MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then [ -n "$M2_HOME" ] && M2_HOME=`cygpath --path --windows "$M2_HOME"` [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --windows "$CLASSPATH"` [ -n "$MAVEN_PROJECTBASEDIR" ] && MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` fi # Provide a "standardized" way to retrieve the CLI args that will # work with both Windows and non-Windows executions. MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain exec "$JAVACMD" \ $MAVEN_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" ================================================ FILE: mvnw.cmd ================================================ @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @REM distributed with this work for additional information @REM regarding copyright ownership. The ASF licenses this file @REM to you under the Apache License, Version 2.0 (the @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM @REM https://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @REM KIND, either express or implied. See the License for the @REM specific language governing permissions and limitations @REM under the License. @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- @REM Maven Start Up Batch script @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars @REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files @REM ---------------------------------------------------------------------------- @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' @echo off @REM set title of command window title %0 @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") @REM Execute a user defined script before this one if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre @REM check for pre script, once with legacy .bat ending and once with .cmd ending if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" :skipRcPre @setlocal set ERROR_CODE=0 @REM To isolate internal variables from possible post scripts, we use another setlocal @setlocal @REM ==== START VALIDATION ==== if not "%JAVA_HOME%" == "" goto OkJHome echo. echo Error: JAVA_HOME not found in your environment. >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. goto error :OkJHome if exist "%JAVA_HOME%\bin\java.exe" goto init echo. echo Error: JAVA_HOME is set to an invalid directory. >&2 echo JAVA_HOME = "%JAVA_HOME%" >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. goto error @REM ==== END VALIDATION ==== :init @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". @REM Fallback to current working directory if not found. set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir set EXEC_DIR=%CD% set WDIR=%EXEC_DIR% :findBaseDir IF EXIST "%WDIR%"\.mvn goto baseDirFound cd .. IF "%WDIR%"=="%CD%" goto baseDirNotFound set WDIR=%CD% goto findBaseDir :baseDirFound set MAVEN_PROJECTBASEDIR=%WDIR% cd "%EXEC_DIR%" goto endDetectBaseDir :baseDirNotFound set MAVEN_PROJECTBASEDIR=%EXEC_DIR% cd "%EXEC_DIR%" :endDetectBaseDir IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig @setlocal EnableExtensions EnableDelayedExpansion for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% :endReadAdditionalConfig SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @REM This allows using the maven wrapper in projects that prohibit checking in binary data. if exist %WRAPPER_JAR% ( if "%MVNW_VERBOSE%" == "true" ( echo Found %WRAPPER_JAR% ) ) else ( if not "%MVNW_REPOURL%" == "" ( SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" ) if "%MVNW_VERBOSE%" == "true" ( echo Couldn't find %WRAPPER_JAR%, downloading it ... echo Downloading from: %DOWNLOAD_URL% ) powershell -Command "&{"^ "$webclient = new-object System.Net.WebClient;"^ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ "}"^ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ "}" if "%MVNW_VERBOSE%" == "true" ( echo Finished downloading %WRAPPER_JAR% ) ) @REM End of extension @REM Provide a "standardized" way to retrieve the CLI args that will @REM work with both Windows and non-Windows executions. set MAVEN_CMD_LINE_ARGS=%* %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end :error set ERROR_CODE=1 :end @endlocal & set ERROR_CODE=%ERROR_CODE% if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost @REM check for post script, once with legacy .bat ending and once with .cmd ending if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" :skipRcPost @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' if "%MAVEN_BATCH_PAUSE%" == "on" pause if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% exit /B %ERROR_CODE% ================================================ FILE: pom.xml ================================================ 4.0.0 org.springframework.boot spring-boot-starter-parent 3.3.2 com.shop shop 0.0.1-SNAPSHOT shop Shop Project for Spring Boot 17 org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-web com.h2database h2 runtime mysql mysql-connector-java 8.0.33 org.projectlombok lombok true com.querydsl querydsl-jpa 5.0.0 jakarta com.querydsl querydsl-apt 5.0.0 jakarta com.querydsl querydsl-core 5.0.0 nz.net.ultraq.thymeleaf thymeleaf-layout-dialect 3.3.0 org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-validation org.springframework.security spring-security-test test ${spring-security.version} org.thymeleaf.extras thymeleaf-extras-springsecurity6 3.1.1.RELEASE org.modelmapper modelmapper 3.1.0 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.projectlombok lombok ================================================ FILE: src/main/java/com/shop/ShopApplication.java ================================================ package com.shop; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ShopApplication { public static void main(String[] args) { SpringApplication.run(ShopApplication.class, args); } } ================================================ FILE: src/main/java/com/shop/config/AuditConfig.java ================================================ package com.shop.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.domain.AuditorAware; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @Configuration @EnableJpaAuditing public class AuditConfig { @Bean public AuditorAware auditorProvider() { return new AuditorAwareImpl(); } } ================================================ FILE: src/main/java/com/shop/config/AuditorAwareImpl.java ================================================ package com.shop.config; import org.springframework.data.domain.AuditorAware; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import java.util.Optional; public class AuditorAwareImpl implements AuditorAware { @Override public Optional getCurrentAuditor() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String userId = ""; if(authentication != null){ userId = authentication.getName(); } return Optional.of(userId); } } ================================================ FILE: src/main/java/com/shop/config/CsrfCookieFilter.java ================================================ package com.shop.config; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; public class CsrfCookieFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // HttpServletRequest에서 CsrfToken 속성 조회 CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); if (csrfToken != null) { // csrf 토큰 강제 생성 csrfToken.getToken(); } filterChain.doFilter(request, response); } } ================================================ FILE: src/main/java/com/shop/config/CustomAuthenticationEntryPoint.java ================================================ package com.shop.config; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import java.io.IOException; public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); } } ================================================ FILE: src/main/java/com/shop/config/FormLoginAuthenticationFailureHandler.java ================================================ package com.shop.config; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import java.io.IOException; public class FormLoginAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { response.sendRedirect("/members/login/error"); } } ================================================ FILE: src/main/java/com/shop/config/SecurityConfig.java ================================================ package com.shop.config; import com.shop.service.MemberService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration @EnableWebSecurity public class SecurityConfig { @Autowired MemberService memberService; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .csrf(csrf -> csrf .csrfTokenRepository(new CookieCsrfTokenRepository()) // csrf 토큰 저장소를 http only cookie로 설정 ) .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class) .authorizeHttpRequests(authorizeHttpRequestsCustomizer -> authorizeHttpRequestsCustomizer .requestMatchers("/css/**", "/js/**", "/img/**").permitAll() .requestMatchers("/", "/members/**", "/item/**", "/images/**").permitAll() .requestMatchers("/admin/**").hasRole("ADMIN") .anyRequest() .authenticated() ).formLogin(formLoginCustomizer -> formLoginCustomizer .loginPage("/members/login") .defaultSuccessUrl("/", true) .usernameParameter("email") .failureHandler(new FormLoginAuthenticationFailureHandler()) ).logout( logoutCustomizer -> logoutCustomizer .logoutRequestMatcher(new AntPathRequestMatcher("/members/logout")) .logoutSuccessUrl("/") ).exceptionHandling(e -> e .authenticationEntryPoint(new CustomAuthenticationEntryPoint()) ) .build() ; } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } ================================================ FILE: src/main/java/com/shop/config/WebMvcConfig.java ================================================ package com.shop.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Value("${uploadPath}") String uploadPath; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/images/**") .addResourceLocations(uploadPath); } } ================================================ FILE: src/main/java/com/shop/constant/ItemSellStatus.java ================================================ package com.shop.constant; public enum ItemSellStatus { SELL, SOLD_OUT } ================================================ FILE: src/main/java/com/shop/constant/OrderStatus.java ================================================ package com.shop.constant; public enum OrderStatus { ORDER, CANCEL } ================================================ FILE: src/main/java/com/shop/constant/Role.java ================================================ package com.shop.constant; public enum Role { USER, ADMIN } ================================================ FILE: src/main/java/com/shop/controller/CartController.java ================================================ package com.shop.controller; import com.shop.dto.CartItemDto; import com.shop.service.CartService; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseBody; import jakarta.validation.Valid; import java.security.Principal; import java.util.List; import com.shop.dto.CartDetailDto; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.DeleteMapping; import com.shop.dto.CartOrderDto; @Controller @RequiredArgsConstructor public class CartController { private final CartService cartService; @PostMapping(value = "/cart") public @ResponseBody ResponseEntity order(@RequestBody @Valid CartItemDto cartItemDto, BindingResult bindingResult, Principal principal){ if(bindingResult.hasErrors()){ StringBuilder sb = new StringBuilder(); List fieldErrors = bindingResult.getFieldErrors(); for (FieldError fieldError : fieldErrors) { sb.append(fieldError.getDefaultMessage()); } return new ResponseEntity(sb.toString(), HttpStatus.BAD_REQUEST); } String email = principal.getName(); Long cartItemId; try { cartItemId = cartService.addCart(cartItemDto, email); } catch(Exception e){ return new ResponseEntity(e.getMessage(), HttpStatus.BAD_REQUEST); } return new ResponseEntity(cartItemId, HttpStatus.OK); } @GetMapping(value = "/cart") public String orderHist(Principal principal, Model model){ List cartDetailList = cartService.getCartList(principal.getName()); model.addAttribute("cartItems", cartDetailList); return "cart/cartList"; } @PatchMapping(value = "/cartItem/{cartItemId}") public @ResponseBody ResponseEntity updateCartItem(@PathVariable("cartItemId") Long cartItemId, int count, Principal principal){ if(count <= 0){ return new ResponseEntity("최소 1개 이상 담아주세요", HttpStatus.BAD_REQUEST); } else if(!cartService.validateCartItem(cartItemId, principal.getName())){ return new ResponseEntity("수정 권한이 없습니다.", HttpStatus.FORBIDDEN); } cartService.updateCartItemCount(cartItemId, count); return new ResponseEntity(cartItemId, HttpStatus.OK); } @DeleteMapping(value = "/cartItem/{cartItemId}") public @ResponseBody ResponseEntity deleteCartItem(@PathVariable("cartItemId") Long cartItemId, Principal principal){ if(!cartService.validateCartItem(cartItemId, principal.getName())){ return new ResponseEntity("수정 권한이 없습니다.", HttpStatus.FORBIDDEN); } cartService.deleteCartItem(cartItemId); return new ResponseEntity(cartItemId, HttpStatus.OK); } @PostMapping(value = "/cart/orders") public @ResponseBody ResponseEntity orderCartItem(@RequestBody CartOrderDto cartOrderDto, Principal principal){ List cartOrderDtoList = cartOrderDto.getCartOrderDtoList(); if(cartOrderDtoList == null || cartOrderDtoList.size() == 0){ return new ResponseEntity("주문할 상품을 선택해주세요", HttpStatus.FORBIDDEN); } for (CartOrderDto cartOrder : cartOrderDtoList) { if(!cartService.validateCartItem(cartOrder.getCartItemId(), principal.getName())){ return new ResponseEntity("주문 권한이 없습니다.", HttpStatus.FORBIDDEN); } } Long orderId = cartService.orderCartItem(cartOrderDtoList, principal.getName()); return new ResponseEntity(orderId, HttpStatus.OK); } } ================================================ FILE: src/main/java/com/shop/controller/ItemController.java ================================================ package com.shop.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.ui.Model; import com.shop.dto.ItemFormDto; import com.shop.service.ItemService; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PostMapping; import jakarta.validation.Valid; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile; import java.util.List; import org.springframework.web.bind.annotation.PathVariable; import jakarta.persistence.EntityNotFoundException; import com.shop.dto.ItemSearchDto; import com.shop.entity.Item; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import java.util.Optional; @Controller @RequiredArgsConstructor public class ItemController { private final ItemService itemService; @GetMapping(value = "/admin/item/new") public String itemForm(Model model){ model.addAttribute("itemFormDto", new ItemFormDto()); return "item/itemForm"; } @PostMapping(value = "/admin/item/new") public String itemNew(@Valid ItemFormDto itemFormDto, BindingResult bindingResult, Model model, @RequestParam("itemImgFile") List itemImgFileList){ if(bindingResult.hasErrors()){ return "item/itemForm"; } if(itemImgFileList.get(0).isEmpty() && itemFormDto.getId() == null){ model.addAttribute("errorMessage", "첫번째 상품 이미지는 필수 입력 값 입니다."); return "item/itemForm"; } try { itemService.saveItem(itemFormDto, itemImgFileList); } catch (Exception e){ model.addAttribute("errorMessage", "상품 등록 중 에러가 발생하였습니다."); return "item/itemForm"; } return "redirect:/"; } @GetMapping(value = "/admin/item/{itemId}") public String itemDtl(@PathVariable("itemId") Long itemId, Model model){ try { ItemFormDto itemFormDto = itemService.getItemDtl(itemId); model.addAttribute("itemFormDto", itemFormDto); } catch(EntityNotFoundException e){ model.addAttribute("errorMessage", "존재하지 않는 상품 입니다."); model.addAttribute("itemFormDto", new ItemFormDto()); return "item/itemForm"; } return "item/itemForm"; } @PostMapping(value = "/admin/item/{itemId}") public String itemUpdate(@Valid ItemFormDto itemFormDto, BindingResult bindingResult, @RequestParam("itemImgFile") List itemImgFileList, Model model){ if(bindingResult.hasErrors()){ return "item/itemForm"; } if(itemImgFileList.get(0).isEmpty() && itemFormDto.getId() == null){ model.addAttribute("errorMessage", "첫번째 상품 이미지는 필수 입력 값 입니다."); return "item/itemForm"; } try { itemService.updateItem(itemFormDto, itemImgFileList); } catch (Exception e){ model.addAttribute("errorMessage", "상품 수정 중 에러가 발생하였습니다."); return "item/itemForm"; } return "redirect:/"; } @GetMapping(value = {"/admin/items", "/admin/items/{page}"}) public String itemManage(ItemSearchDto itemSearchDto, @PathVariable("page") Optional page, Model model){ Pageable pageable = PageRequest.of(page.isPresent() ? page.get() : 0, 3); Page items = itemService.getAdminItemPage(itemSearchDto, pageable); model.addAttribute("items", items); model.addAttribute("itemSearchDto", itemSearchDto); model.addAttribute("maxPage", 5); return "item/itemMng"; } @GetMapping(value = "/item/{itemId}") public String itemDtl(Model model, @PathVariable("itemId") Long itemId){ ItemFormDto itemFormDto = itemService.getItemDtl(itemId); model.addAttribute("item", itemFormDto); return "item/itemDtl"; } } ================================================ FILE: src/main/java/com/shop/controller/MainController.java ================================================ package com.shop.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import com.shop.dto.ItemSearchDto; import com.shop.dto.MainItemDto; import com.shop.service.ItemService; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import java.util.Optional; @Controller @RequiredArgsConstructor public class MainController { private final ItemService itemService; @GetMapping(value = "/") public String main(ItemSearchDto itemSearchDto, Optional page, Model model){ Pageable pageable = PageRequest.of(page.isPresent() ? page.get() : 0, 6); Page items = itemService.getMainItemPage(itemSearchDto, pageable); model.addAttribute("items", items); model.addAttribute("itemSearchDto", itemSearchDto); model.addAttribute("maxPage", 5); return "main"; } } ================================================ FILE: src/main/java/com/shop/controller/MemberController.java ================================================ package com.shop.controller; import com.shop.dto.MemberFormDto; import com.shop.service.MemberService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import com.shop.entity.Member; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.validation.BindingResult; import jakarta.validation.Valid; @RequestMapping("/members") @Controller @RequiredArgsConstructor public class MemberController { private final MemberService memberService; private final PasswordEncoder passwordEncoder; @GetMapping(value = "/new") public String memberForm(Model model){ model.addAttribute("memberFormDto", new MemberFormDto()); return "member/memberForm"; } @PostMapping(value = "/new") public String newMember(@Valid MemberFormDto memberFormDto, BindingResult bindingResult, Model model){ if(bindingResult.hasErrors()){ return "member/memberForm"; } try { Member member = Member.createMember(memberFormDto, passwordEncoder); memberService.saveMember(member); } catch (IllegalStateException e){ model.addAttribute("errorMessage", e.getMessage()); return "member/memberForm"; } return "redirect:/"; } @GetMapping(value = "/login") public String loginMember(){ return "/member/memberLoginForm"; } @GetMapping(value = "/login/error") public String loginError(Model model){ model.addAttribute("loginErrorMsg", "아이디 또는 비밀번호를 확인해주세요"); return "/member/memberLoginForm"; } } ================================================ FILE: src/main/java/com/shop/controller/OrderController.java ================================================ package com.shop.controller; import com.shop.dto.OrderDto; import com.shop.service.OrderService; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseBody; import jakarta.validation.Valid; import java.security.Principal; import java.util.List; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import com.shop.dto.OrderHistDto; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.ui.Model; import java.util.Optional; @Controller @RequiredArgsConstructor public class OrderController { private final OrderService orderService; @PostMapping(value = "/order") public @ResponseBody ResponseEntity order(@RequestBody @Valid OrderDto orderDto , BindingResult bindingResult, Principal principal){ if(bindingResult.hasErrors()){ StringBuilder sb = new StringBuilder(); List fieldErrors = bindingResult.getFieldErrors(); for (FieldError fieldError : fieldErrors) { sb.append(fieldError.getDefaultMessage()); } return new ResponseEntity(sb.toString(), HttpStatus.BAD_REQUEST); } String email = principal.getName(); Long orderId; try { orderId = orderService.order(orderDto, email); } catch(Exception e){ return new ResponseEntity(e.getMessage(), HttpStatus.BAD_REQUEST); } return new ResponseEntity(orderId, HttpStatus.OK); } @GetMapping(value = {"/orders", "/orders/{page}"}) public String orderHist(@PathVariable("page") Optional page, Principal principal, Model model){ Pageable pageable = PageRequest.of(page.isPresent() ? page.get() : 0, 4); Page ordersHistDtoList = orderService.getOrderList(principal.getName(), pageable); model.addAttribute("orders", ordersHistDtoList); model.addAttribute("page", pageable.getPageNumber()); model.addAttribute("maxPage", 5); return "order/orderHist"; } @PostMapping("/order/{orderId}/cancel") public @ResponseBody ResponseEntity cancelOrder(@PathVariable("orderId") Long orderId , Principal principal){ if(!orderService.validateOrder(orderId, principal.getName())){ return new ResponseEntity("주문 취소 권한이 없습니다.", HttpStatus.FORBIDDEN); } orderService.cancelOrder(orderId); return new ResponseEntity(orderId, HttpStatus.OK); } } ================================================ FILE: src/main/java/com/shop/controller/ThymeleafExController.java ================================================ package com.shop.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import com.shop.dto.ItemDto; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @Controller @RequestMapping(value="/thymeleaf") public class ThymeleafExController { @GetMapping(value = "/ex01") public String thymeleafExample01(Model model){ model.addAttribute("data", "타임리프 예제 입니다."); return "thymeleafEx/thymeleafEx01"; } @GetMapping(value = "/ex02") public String thymeleafExample02(Model model){ ItemDto itemDto = new ItemDto(); itemDto.setItemDetail("상품 상세 설명"); itemDto.setItemNm("테스트 상품1"); itemDto.setPrice(10000); itemDto.setRegTime(LocalDateTime.now()); model.addAttribute("itemDto", itemDto); return "thymeleafEx/thymeleafEx02"; } @GetMapping(value = "/ex03") public String thymeleafExample03(Model model){ List itemDtoList = new ArrayList<>(); for(int i=1;i<=10;i++){ ItemDto itemDto = new ItemDto(); itemDto.setItemDetail("상품 상세 설명"+i); itemDto.setItemNm("테스트 상품" + i); itemDto.setPrice(1000*i); itemDto.setRegTime(LocalDateTime.now()); itemDtoList.add(itemDto); } model.addAttribute("itemDtoList", itemDtoList); return "thymeleafEx/thymeleafEx03"; } @GetMapping(value = "/ex04") public String thymeleafExample04(Model model){ List itemDtoList = new ArrayList<>(); for(int i=1;i<=10;i++){ ItemDto itemDto = new ItemDto(); itemDto.setItemDetail("상품 상세 설명"+i); itemDto.setItemNm("테스트 상품" + i); itemDto.setPrice(1000*i); itemDto.setRegTime(LocalDateTime.now()); itemDtoList.add(itemDto); } model.addAttribute("itemDtoList", itemDtoList); return "thymeleafEx/thymeleafEx04"; } @GetMapping(value = "/ex05") public String thymeleafExample05(){ return "thymeleafEx/thymeleafEx05"; } @GetMapping(value = "/ex06") public String thymeleafExample06(String param1, String param2, Model model){ model.addAttribute("param1", param1); model.addAttribute("param2", param2); return "thymeleafEx/thymeleafEx06"; } @GetMapping(value = "/ex07") public String thymeleafExample07(){ return "thymeleafEx/thymeleafEx07"; } } ================================================ FILE: src/main/java/com/shop/dto/CartDetailDto.java ================================================ package com.shop.dto; import lombok.Getter; import lombok.Setter; @Getter @Setter public class CartDetailDto { private Long cartItemId; //장바구니 상품 아이디 private String itemNm; //상품명 private int price; //상품 금액 private int count; //수량 private String imgUrl; //상품 이미지 경로 public CartDetailDto(Long cartItemId, String itemNm, int price, int count, String imgUrl){ this.cartItemId = cartItemId; this.itemNm = itemNm; this.price = price; this.count = count; this.imgUrl = imgUrl; } } ================================================ FILE: src/main/java/com/shop/dto/CartItemDto.java ================================================ package com.shop.dto; import lombok.Getter; import lombok.Setter; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; @Getter @Setter public class CartItemDto { @NotNull(message = "상품 아이디는 필수 입력 값 입니다.") private Long itemId; @Min(value = 1, message = "최소 1개 이상 담아주세요") private int count; } ================================================ FILE: src/main/java/com/shop/dto/CartOrderDto.java ================================================ package com.shop.dto; import lombok.Getter; import lombok.Setter; import java.util.List; @Getter @Setter public class CartOrderDto { private Long cartItemId; private List cartOrderDtoList; } ================================================ FILE: src/main/java/com/shop/dto/ItemDto.java ================================================ package com.shop.dto; import lombok.Getter; import lombok.Setter; import java.time.LocalDateTime; @Getter @Setter public class ItemDto { private Long id; private String itemNm; private Integer price; private String itemDetail; private String sellStatCd; private LocalDateTime regTime; private LocalDateTime updateTime; } ================================================ FILE: src/main/java/com/shop/dto/ItemFormDto.java ================================================ package com.shop.dto; import com.shop.constant.ItemSellStatus; import com.shop.entity.Item; import lombok.Getter; import lombok.Setter; import org.modelmapper.ModelMapper; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import java.util.ArrayList; import java.util.List; @Getter @Setter public class ItemFormDto { private Long id; @NotBlank(message = "상품명은 필수 입력 값입니다.") private String itemNm; @NotNull(message = "가격은 필수 입력 값입니다.") private Integer price; @NotBlank(message = "상품 상세는 필수 입력 값입니다.") private String itemDetail; @NotNull(message = "재고는 필수 입력 값입니다.") private Integer stockNumber; private ItemSellStatus itemSellStatus; private List itemImgDtoList = new ArrayList<>(); private List itemImgIds = new ArrayList<>(); private static ModelMapper modelMapper = new ModelMapper(); public Item createItem(){ return modelMapper.map(this, Item.class); } public static ItemFormDto of(Item item){ return modelMapper.map(item,ItemFormDto.class); } } ================================================ FILE: src/main/java/com/shop/dto/ItemImgDto.java ================================================ package com.shop.dto; import com.shop.entity.ItemImg; import lombok.Getter; import lombok.Setter; import org.modelmapper.ModelMapper; @Getter @Setter public class ItemImgDto { private Long id; private String imgName; private String oriImgName; private String imgUrl; private String repImgYn; private static ModelMapper modelMapper = new ModelMapper(); public static ItemImgDto of(ItemImg itemImg) { return modelMapper.map(itemImg,ItemImgDto.class); } } ================================================ FILE: src/main/java/com/shop/dto/ItemSearchDto.java ================================================ package com.shop.dto; import com.shop.constant.ItemSellStatus; import lombok.Getter; import lombok.Setter; @Getter @Setter public class ItemSearchDto { private String searchDateType; private ItemSellStatus searchSellStatus; private String searchBy; private String searchQuery = ""; } ================================================ FILE: src/main/java/com/shop/dto/MainItemDto.java ================================================ package com.shop.dto; import com.querydsl.core.annotations.QueryProjection; import lombok.Getter; import lombok.Setter; @Getter @Setter public class MainItemDto { private Long id; private String itemNm; private String itemDetail; private String imgUrl; private Integer price; @QueryProjection public MainItemDto(Long id, String itemNm, String itemDetail, String imgUrl,Integer price){ this.id = id; this.itemNm = itemNm; this.itemDetail = itemDetail; this.imgUrl = imgUrl; this.price = price; } } ================================================ FILE: src/main/java/com/shop/dto/MemberFormDto.java ================================================ package com.shop.dto; import lombok.Getter; import lombok.Setter; import org.hibernate.validator.constraints.Length; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotEmpty; @Getter @Setter public class MemberFormDto { @NotBlank(message = "이름은 필수 입력 값입니다.") private String name; @NotEmpty(message = "이메일은 필수 입력 값입니다.") @Email(message = "이메일 형식으로 입력해주세요.") private String email; @NotEmpty(message = "비밀번호는 필수 입력 값입니다.") @Length(min=8, max=16, message = "비밀번호는 8자 이상, 16자 이하로 입력해주세요") private String password; @NotEmpty(message = "주소는 필수 입력 값입니다.") private String address; } ================================================ FILE: src/main/java/com/shop/dto/OrderDto.java ================================================ package com.shop.dto; import lombok.Getter; import lombok.Setter; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; @Getter @Setter public class OrderDto { @NotNull(message = "상품 아이디는 필수 입력 값입니다.") private Long itemId; @Min(value = 1, message = "최소 주문 수량은 1개 입니다.") @Max(value = 999, message = "최대 주문 수량은 999개 입니다.") private int count; } ================================================ FILE: src/main/java/com/shop/dto/OrderHistDto.java ================================================ package com.shop.dto; import com.shop.constant.OrderStatus; import com.shop.entity.Order; import lombok.Getter; import lombok.Setter; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; @Getter @Setter public class OrderHistDto { public OrderHistDto(Order order){ this.orderId = order.getId(); this.orderDate = order.getOrderDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); this.orderStatus = order.getOrderStatus(); } private Long orderId; //주문아이디 private String orderDate; //주문날짜 private OrderStatus orderStatus; //주문 상태 private List orderItemDtoList = new ArrayList<>(); //주문 상품리스트 public void addOrderItemDto(OrderItemDto orderItemDto){ orderItemDtoList.add(orderItemDto); } } ================================================ FILE: src/main/java/com/shop/dto/OrderItemDto.java ================================================ package com.shop.dto; import com.shop.entity.OrderItem; import lombok.Getter; import lombok.Setter; @Getter @Setter public class OrderItemDto { public OrderItemDto(OrderItem orderItem, String imgUrl){ this.itemNm = orderItem.getItem().getItemNm(); this.count = orderItem.getCount(); this.orderPrice = orderItem.getOrderPrice(); this.imgUrl = imgUrl; } private String itemNm; //상품명 private int count; //주문 수량 private int orderPrice; //주문 금액 private String imgUrl; //상품 이미지 경로 } ================================================ FILE: src/main/java/com/shop/entity/BaseEntity.java ================================================ package com.shop.entity; import lombok.Getter; import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.LastModifiedBy; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; @EntityListeners(value = {AuditingEntityListener.class}) @MappedSuperclass @Getter public abstract class BaseEntity extends BaseTimeEntity{ @CreatedBy @Column(updatable = false) private String createdBy; @LastModifiedBy private String modifiedBy; } ================================================ FILE: src/main/java/com/shop/entity/BaseTimeEntity.java ================================================ package com.shop.entity; import lombok.Getter; import lombok.Setter; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; import java.time.LocalDateTime; @EntityListeners(value = {AuditingEntityListener.class}) @MappedSuperclass @Getter @Setter public abstract class BaseTimeEntity { @CreatedDate @Column(updatable = false) private LocalDateTime regTime; @LastModifiedDate private LocalDateTime updateTime; } ================================================ FILE: src/main/java/com/shop/entity/Cart.java ================================================ package com.shop.entity; import lombok.Getter; import lombok.Setter; import lombok.ToString; import jakarta.persistence.*; @Entity @Table(name = "cart") @Getter @Setter @ToString public class Cart extends BaseEntity { @Id @Column(name = "cart_id") @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name="member_id") private Member member; public static Cart createCart(Member member){ Cart cart = new Cart(); cart.setMember(member); return cart; } } ================================================ FILE: src/main/java/com/shop/entity/CartItem.java ================================================ package com.shop.entity; import lombok.Getter; import lombok.Setter; import jakarta.persistence.*; @Entity @Getter @Setter @Table(name="cart_item") public class CartItem extends BaseEntity { @Id @GeneratedValue @Column(name = "cart_item_id") private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name="cart_id") private Cart cart; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "item_id") private Item item; private int count; public static CartItem createCartItem(Cart cart, Item item, int count) { CartItem cartItem = new CartItem(); cartItem.setCart(cart); cartItem.setItem(item); cartItem.setCount(count); return cartItem; } public void addCount(int count){ this.count += count; } public void updateCount(int count){ this.count = count; } } ================================================ FILE: src/main/java/com/shop/entity/Item.java ================================================ package com.shop.entity; import com.shop.constant.ItemSellStatus; import lombok.Getter; import lombok.Setter; import lombok.ToString; import jakarta.persistence.*; import com.shop.dto.ItemFormDto; import com.shop.exception.OutOfStockException; @Entity @Table(name="item") @Getter @Setter @ToString public class Item extends BaseEntity { @Id @Column(name="item_id") @GeneratedValue(strategy = GenerationType.AUTO) private Long id; //상품 코드 @Column(nullable = false, length = 50) private String itemNm; //상품명 @Column(name="price", nullable = false) private int price; //가격 @Column(nullable = false) private int stockNumber; //재고수량 @Lob @Column(nullable = false) private String itemDetail; //상품 상세 설명 @Enumerated(EnumType.STRING) private ItemSellStatus itemSellStatus; //상품 판매 상태 public void updateItem(ItemFormDto itemFormDto){ this.itemNm = itemFormDto.getItemNm(); this.price = itemFormDto.getPrice(); this.stockNumber = itemFormDto.getStockNumber(); this.itemDetail = itemFormDto.getItemDetail(); this.itemSellStatus = itemFormDto.getItemSellStatus(); } public void removeStock(int stockNumber){ int restStock = this.stockNumber - stockNumber; if(restStock<0){ throw new OutOfStockException("상품의 재고가 부족 합니다. (현재 재고 수량: " + this.stockNumber + ")"); } this.stockNumber = restStock; } public void addStock(int stockNumber){ this.stockNumber += stockNumber; } } ================================================ FILE: src/main/java/com/shop/entity/ItemImg.java ================================================ package com.shop.entity; import lombok.Getter; import lombok.Setter; import jakarta.persistence.*; @Entity @Table(name="item_img") @Getter @Setter public class ItemImg extends BaseEntity{ @Id @Column(name="item_img_id") @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String imgName; //이미지 파일명 private String oriImgName; //원본 이미지 파일명 private String imgUrl; //이미지 조회 경로 private String repimgYn; //대표 이미지 여부 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "item_id") private Item item; public void updateItemImg(String oriImgName, String imgName, String imgUrl){ this.oriImgName = oriImgName; this.imgName = imgName; this.imgUrl = imgUrl; } } ================================================ FILE: src/main/java/com/shop/entity/Member.java ================================================ package com.shop.entity; import com.shop.constant.Role; import com.shop.dto.MemberFormDto; import lombok.Getter; import lombok.Setter; import lombok.ToString; import org.springframework.security.crypto.password.PasswordEncoder; import jakarta.persistence.*; @Entity @Table(name="member") @Getter @Setter @ToString public class Member extends BaseEntity { @Id @Column(name="member_id") @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String name; @Column(unique = true) private String email; private String password; private String address; @Enumerated(EnumType.STRING) private Role role; public static Member createMember(MemberFormDto memberFormDto, PasswordEncoder passwordEncoder){ Member member = new Member(); member.setName(memberFormDto.getName()); member.setEmail(memberFormDto.getEmail()); member.setAddress(memberFormDto.getAddress()); String password = passwordEncoder.encode(memberFormDto.getPassword()); member.setPassword(password); member.setRole(Role.ADMIN); return member; } } ================================================ FILE: src/main/java/com/shop/entity/Order.java ================================================ package com.shop.entity; import com.shop.constant.OrderStatus; import lombok.Getter; import lombok.Setter; import jakarta.persistence.*; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @Entity @Table(name = "orders") @Getter @Setter public class Order extends BaseEntity { @Id @GeneratedValue @Column(name = "order_id") private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id") private Member member; private LocalDateTime orderDate; //주문일 @Enumerated(EnumType.STRING) private OrderStatus orderStatus; //주문상태 @OneToMany(mappedBy = "order", cascade = CascadeType.ALL , orphanRemoval = true, fetch = FetchType.LAZY) private List orderItems = new ArrayList<>(); public void addOrderItem(OrderItem orderItem) { orderItems.add(orderItem); orderItem.setOrder(this); } public static Order createOrder(Member member, List orderItemList) { Order order = new Order(); order.setMember(member); for(OrderItem orderItem : orderItemList) { order.addOrderItem(orderItem); } order.setOrderStatus(OrderStatus.ORDER); order.setOrderDate(LocalDateTime.now()); return order; } public int getTotalPrice() { int totalPrice = 0; for(OrderItem orderItem : orderItems){ totalPrice += orderItem.getTotalPrice(); } return totalPrice; } public void cancelOrder() { this.orderStatus = OrderStatus.CANCEL; for (OrderItem orderItem : orderItems) { orderItem.cancel(); } } } ================================================ FILE: src/main/java/com/shop/entity/OrderItem.java ================================================ package com.shop.entity; import lombok.Getter; import lombok.Setter; import jakarta.persistence.*; @Entity @Getter @Setter public class OrderItem extends BaseEntity { @Id @GeneratedValue @Column(name = "order_item_id") private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "item_id") private Item item; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "order_id") private Order order; private int orderPrice; //주문가격 private int count; //수량 public static OrderItem createOrderItem(Item item, int count){ OrderItem orderItem = new OrderItem(); orderItem.setItem(item); orderItem.setCount(count); orderItem.setOrderPrice(item.getPrice()); item.removeStock(count); return orderItem; } public int getTotalPrice(){ return orderPrice*count; } public void cancel() { this.getItem().addStock(count); } } ================================================ FILE: src/main/java/com/shop/exception/OutOfStockException.java ================================================ package com.shop.exception; public class OutOfStockException extends RuntimeException{ public OutOfStockException(String message) { super(message); } } ================================================ FILE: src/main/java/com/shop/repository/CartItemRepository.java ================================================ package com.shop.repository; import com.shop.entity.CartItem; import org.springframework.data.jpa.repository.JpaRepository; import com.shop.dto.CartDetailDto; import org.springframework.data.jpa.repository.Query; import java.util.List; public interface CartItemRepository extends JpaRepository { CartItem findByCartIdAndItemId(Long cartId, Long itemId); @Query("select new com.shop.dto.CartDetailDto(ci.id, i.itemNm, i.price, ci.count, im.imgUrl) " + "from CartItem ci, ItemImg im " + "join ci.item i " + "where ci.cart.id = :cartId " + "and im.item.id = ci.item.id " + "and im.repimgYn = 'Y' " + "order by ci.regTime desc" ) List findCartDetailDtoList(Long cartId); } ================================================ FILE: src/main/java/com/shop/repository/CartRepository.java ================================================ package com.shop.repository; import com.shop.entity.Cart; import org.springframework.data.jpa.repository.JpaRepository; public interface CartRepository extends JpaRepository { Cart findByMemberId(Long memberId); } ================================================ FILE: src/main/java/com/shop/repository/ItemImgRepository.java ================================================ package com.shop.repository; import com.shop.entity.ItemImg; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface ItemImgRepository extends JpaRepository { List findByItemIdOrderByIdAsc(Long itemId); ItemImg findByItemIdAndRepimgYn(Long itemId, String repimgYn); } ================================================ FILE: src/main/java/com/shop/repository/ItemRepository.java ================================================ package com.shop.repository; import com.shop.entity.Item; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.data.querydsl.QuerydslPredicateExecutor; public interface ItemRepository extends JpaRepository, QuerydslPredicateExecutor, ItemRepositoryCustom { List findByItemNm(String itemNm); List findByItemNmOrItemDetail(String itemNm, String itemDetail); List findByPriceLessThan(Integer price); List findByPriceLessThanOrderByPriceDesc(Integer price); @Query("select i from Item i where i.itemDetail like " + "%:itemDetail% order by i.price desc") List findByItemDetail(@Param("itemDetail") String itemDetail); @Query(value="select * from item i where i.item_detail like " + "%:itemDetail% order by i.price desc", nativeQuery = true) List findByItemDetailByNative(@Param("itemDetail") String itemDetail); } ================================================ FILE: src/main/java/com/shop/repository/ItemRepositoryCustom.java ================================================ package com.shop.repository; import com.shop.dto.ItemSearchDto; import com.shop.entity.Item; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import com.shop.dto.MainItemDto; public interface ItemRepositoryCustom { Page getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable); Page getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable); } ================================================ FILE: src/main/java/com/shop/repository/ItemRepositoryCustomImpl.java ================================================ package com.shop.repository; import com.querydsl.core.QueryResults; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.Wildcard; import com.querydsl.jpa.impl.JPAQueryFactory; import com.shop.constant.ItemSellStatus; import com.shop.dto.ItemSearchDto; import com.shop.dto.MainItemDto; import com.shop.dto.QMainItemDto; import com.shop.entity.Item; import com.shop.entity.QItem; import com.shop.entity.QItemImg; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.thymeleaf.util.StringUtils; import jakarta.persistence.EntityManager; import java.time.LocalDateTime; import java.util.List; public class ItemRepositoryCustomImpl implements ItemRepositoryCustom{ private JPAQueryFactory queryFactory; public ItemRepositoryCustomImpl(EntityManager em){ this.queryFactory = new JPAQueryFactory(em); } private BooleanExpression searchSellStatusEq(ItemSellStatus searchSellStatus){ return searchSellStatus == null ? null : QItem.item.itemSellStatus.eq(searchSellStatus); } private BooleanExpression regDtsAfter(String searchDateType){ LocalDateTime dateTime = LocalDateTime.now(); if(StringUtils.equals("all", searchDateType) || searchDateType == null){ return null; } else if(StringUtils.equals("1d", searchDateType)){ dateTime = dateTime.minusDays(1); } else if(StringUtils.equals("1w", searchDateType)){ dateTime = dateTime.minusWeeks(1); } else if(StringUtils.equals("1m", searchDateType)){ dateTime = dateTime.minusMonths(1); } else if(StringUtils.equals("6m", searchDateType)){ dateTime = dateTime.minusMonths(6); } return QItem.item.regTime.after(dateTime); } private BooleanExpression searchByLike(String searchBy, String searchQuery){ if(StringUtils.equals("itemNm", searchBy)){ return QItem.item.itemNm.like("%" + searchQuery + "%"); } else if(StringUtils.equals("createdBy", searchBy)){ return QItem.item.createdBy.like("%" + searchQuery + "%"); } return null; } @Override public Page getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable) { List content = queryFactory .selectFrom(QItem.item) .where(regDtsAfter(itemSearchDto.getSearchDateType()), searchSellStatusEq(itemSearchDto.getSearchSellStatus()), searchByLike(itemSearchDto.getSearchBy(), itemSearchDto.getSearchQuery())) .orderBy(QItem.item.id.desc()) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .fetch(); long total = queryFactory.select(Wildcard.count).from(QItem.item) .where(regDtsAfter(itemSearchDto.getSearchDateType()), searchSellStatusEq(itemSearchDto.getSearchSellStatus()), searchByLike(itemSearchDto.getSearchBy(), itemSearchDto.getSearchQuery())) .fetchOne() ; return new PageImpl<>(content, pageable, total); } private BooleanExpression itemNmLike(String searchQuery){ return StringUtils.isEmpty(searchQuery) ? null : QItem.item.itemNm.like("%" + searchQuery + "%"); } @Override public Page getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable) { QItem item = QItem.item; QItemImg itemImg = QItemImg.itemImg; List content = queryFactory .select( new QMainItemDto( item.id, item.itemNm, item.itemDetail, itemImg.imgUrl, item.price) ) .from(itemImg) .join(itemImg.item, item) .where(itemImg.repimgYn.eq("Y")) .where(itemNmLike(itemSearchDto.getSearchQuery())) .orderBy(item.id.desc()) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .fetch(); long total = queryFactory .select(Wildcard.count) .from(itemImg) .join(itemImg.item, item) .where(itemImg.repimgYn.eq("Y")) .where(itemNmLike(itemSearchDto.getSearchQuery())) .fetchOne() ; return new PageImpl<>(content, pageable, total); } } ================================================ FILE: src/main/java/com/shop/repository/MemberRepository.java ================================================ package com.shop.repository; import com.shop.entity.Member; import org.springframework.data.jpa.repository.JpaRepository; public interface MemberRepository extends JpaRepository { Member findByEmail(String email); } ================================================ FILE: src/main/java/com/shop/repository/OrderItemRepository.java ================================================ package com.shop.repository; import com.shop.entity.OrderItem; import org.springframework.data.jpa.repository.JpaRepository; public interface OrderItemRepository extends JpaRepository { } ================================================ FILE: src/main/java/com/shop/repository/OrderRepository.java ================================================ package com.shop.repository; import com.shop.entity.Order; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.List; public interface OrderRepository extends JpaRepository { @Query("select o from Order o " + "where o.member.email = :email " + "order by o.orderDate desc" ) List findOrders(@Param("email") String email, Pageable pageable); @Query("select count(o) from Order o " + "where o.member.email = :email" ) Long countOrder(@Param("email") String email); } ================================================ FILE: src/main/java/com/shop/service/CartService.java ================================================ package com.shop.service; import com.shop.dto.CartItemDto; import com.shop.entity.Cart; import com.shop.entity.CartItem; import com.shop.entity.Item; import com.shop.entity.Member; import com.shop.repository.CartItemRepository; import com.shop.repository.CartRepository; import com.shop.repository.ItemRepository; import com.shop.repository.MemberRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import jakarta.persistence.EntityNotFoundException; import com.shop.dto.CartDetailDto; import java.util.ArrayList; import java.util.List; import org.thymeleaf.util.StringUtils; import com.shop.dto.CartOrderDto; import com.shop.dto.OrderDto; @Service @RequiredArgsConstructor @Transactional public class CartService { private final ItemRepository itemRepository; private final MemberRepository memberRepository; private final CartRepository cartRepository; private final CartItemRepository cartItemRepository; private final OrderService orderService; public Long addCart(CartItemDto cartItemDto, String email){ Item item = itemRepository.findById(cartItemDto.getItemId()) .orElseThrow(EntityNotFoundException::new); Member member = memberRepository.findByEmail(email); Cart cart = cartRepository.findByMemberId(member.getId()); if(cart == null){ cart = Cart.createCart(member); cartRepository.save(cart); } CartItem savedCartItem = cartItemRepository.findByCartIdAndItemId(cart.getId(), item.getId()); if(savedCartItem != null){ savedCartItem.addCount(cartItemDto.getCount()); return savedCartItem.getId(); } else { CartItem cartItem = CartItem.createCartItem(cart, item, cartItemDto.getCount()); cartItemRepository.save(cartItem); return cartItem.getId(); } } @Transactional(readOnly = true) public List getCartList(String email){ List cartDetailDtoList = new ArrayList<>(); Member member = memberRepository.findByEmail(email); Cart cart = cartRepository.findByMemberId(member.getId()); if(cart == null){ return cartDetailDtoList; } cartDetailDtoList = cartItemRepository.findCartDetailDtoList(cart.getId()); return cartDetailDtoList; } @Transactional(readOnly = true) public boolean validateCartItem(Long cartItemId, String email){ Member curMember = memberRepository.findByEmail(email); CartItem cartItem = cartItemRepository.findById(cartItemId) .orElseThrow(EntityNotFoundException::new); Member savedMember = cartItem.getCart().getMember(); if(!StringUtils.equals(curMember.getEmail(), savedMember.getEmail())){ return false; } return true; } public void updateCartItemCount(Long cartItemId, int count){ CartItem cartItem = cartItemRepository.findById(cartItemId) .orElseThrow(EntityNotFoundException::new); cartItem.updateCount(count); } public void deleteCartItem(Long cartItemId) { CartItem cartItem = cartItemRepository.findById(cartItemId) .orElseThrow(EntityNotFoundException::new); cartItemRepository.delete(cartItem); } public Long orderCartItem(List cartOrderDtoList, String email){ List orderDtoList = new ArrayList<>(); for (CartOrderDto cartOrderDto : cartOrderDtoList) { CartItem cartItem = cartItemRepository .findById(cartOrderDto.getCartItemId()) .orElseThrow(EntityNotFoundException::new); OrderDto orderDto = new OrderDto(); orderDto.setItemId(cartItem.getItem().getId()); orderDto.setCount(cartItem.getCount()); orderDtoList.add(orderDto); } Long orderId = orderService.orders(orderDtoList, email); for (CartOrderDto cartOrderDto : cartOrderDtoList) { CartItem cartItem = cartItemRepository .findById(cartOrderDto.getCartItemId()) .orElseThrow(EntityNotFoundException::new); cartItemRepository.delete(cartItem); } return orderId; } } ================================================ FILE: src/main/java/com/shop/service/FileService.java ================================================ package com.shop.service; import lombok.extern.java.Log; import org.springframework.stereotype.Service; import java.io.File; import java.io.FileOutputStream; import java.util.UUID; @Service @Log public class FileService { public String uploadFile(String uploadPath, String originalFileName, byte[] fileData) throws Exception{ UUID uuid = UUID.randomUUID(); String extension = originalFileName.substring(originalFileName.lastIndexOf(".")); String savedFileName = uuid.toString() + extension; String fileUploadFullUrl = uploadPath + "/" + savedFileName; FileOutputStream fos = new FileOutputStream(fileUploadFullUrl); fos.write(fileData); fos.close(); return savedFileName; } public void deleteFile(String filePath) throws Exception{ File deleteFile = new File(filePath); if(deleteFile.exists()) { deleteFile.delete(); log.info("파일을 삭제하였습니다."); } else { log.info("파일이 존재하지 않습니다."); } } } ================================================ FILE: src/main/java/com/shop/service/ItemImgService.java ================================================ package com.shop.service; import com.shop.entity.ItemImg; import com.shop.repository.ItemImgRepository; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import org.thymeleaf.util.StringUtils; import jakarta.persistence.EntityNotFoundException; @Service @RequiredArgsConstructor @Transactional public class ItemImgService { @Value("${itemImgLocation}") private String itemImgLocation; private final ItemImgRepository itemImgRepository; private final FileService fileService; public void saveItemImg(ItemImg itemImg, MultipartFile itemImgFile) throws Exception{ String oriImgName = itemImgFile.getOriginalFilename(); String imgName = ""; String imgUrl = ""; //파일 업로드 if(!StringUtils.isEmpty(oriImgName)){ imgName = fileService.uploadFile(itemImgLocation, oriImgName, itemImgFile.getBytes()); imgUrl = "/images/item/" + imgName; } //상품 이미지 정보 저장 itemImg.updateItemImg(oriImgName, imgName, imgUrl); itemImgRepository.save(itemImg); } public void updateItemImg(Long itemImgId, MultipartFile itemImgFile) throws Exception{ if(!itemImgFile.isEmpty()){ ItemImg savedItemImg = itemImgRepository.findById(itemImgId) .orElseThrow(EntityNotFoundException::new); //기존 이미지 파일 삭제 if(!StringUtils.isEmpty(savedItemImg.getImgName())) { fileService.deleteFile(itemImgLocation+"/"+ savedItemImg.getImgName()); } String oriImgName = itemImgFile.getOriginalFilename(); String imgName = fileService.uploadFile(itemImgLocation, oriImgName, itemImgFile.getBytes()); String imgUrl = "/images/item/" + imgName; savedItemImg.updateItemImg(oriImgName, imgName, imgUrl); } } } ================================================ FILE: src/main/java/com/shop/service/ItemService.java ================================================ package com.shop.service; import com.shop.dto.ItemFormDto; import com.shop.entity.Item; import com.shop.entity.ItemImg; import com.shop.repository.ItemImgRepository; import com.shop.repository.ItemRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.util.List; import com.shop.dto.ItemImgDto; import jakarta.persistence.EntityNotFoundException; import java.util.ArrayList; import com.shop.dto.ItemSearchDto; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import com.shop.dto.MainItemDto; @Service @Transactional @RequiredArgsConstructor public class ItemService { private final ItemRepository itemRepository; private final ItemImgService itemImgService; private final ItemImgRepository itemImgRepository; public Long saveItem(ItemFormDto itemFormDto, List itemImgFileList) throws Exception{ //상품 등록 Item item = itemFormDto.createItem(); itemRepository.save(item); //이미지 등록 for(int i=0;i itemImgList = itemImgRepository.findByItemIdOrderByIdAsc(itemId); List itemImgDtoList = new ArrayList<>(); for (ItemImg itemImg : itemImgList) { ItemImgDto itemImgDto = ItemImgDto.of(itemImg); itemImgDtoList.add(itemImgDto); } Item item = itemRepository.findById(itemId) .orElseThrow(EntityNotFoundException::new); ItemFormDto itemFormDto = ItemFormDto.of(item); itemFormDto.setItemImgDtoList(itemImgDtoList); return itemFormDto; } public Long updateItem(ItemFormDto itemFormDto, List itemImgFileList) throws Exception{ //상품 수정 Item item = itemRepository.findById(itemFormDto.getId()) .orElseThrow(EntityNotFoundException::new); item.updateItem(itemFormDto); List itemImgIds = itemFormDto.getItemImgIds(); //이미지 등록 for(int i=0;i getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable){ return itemRepository.getAdminItemPage(itemSearchDto, pageable); } @Transactional(readOnly = true) public Page getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable){ return itemRepository.getMainItemPage(itemSearchDto, pageable); } } ================================================ FILE: src/main/java/com/shop/service/MemberService.java ================================================ package com.shop.service; import com.shop.entity.Member; import com.shop.repository.MemberRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; @Service @Transactional @RequiredArgsConstructor public class MemberService implements UserDetailsService { private final MemberRepository memberRepository; public Member saveMember(Member member){ validateDuplicateMember(member); return memberRepository.save(member); } private void validateDuplicateMember(Member member){ Member findMember = memberRepository.findByEmail(member.getEmail()); if(findMember != null){ throw new IllegalStateException("이미 가입된 회원입니다."); } } @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { Member member = memberRepository.findByEmail(email); if(member == null){ throw new UsernameNotFoundException(email); } return User.builder() .username(member.getEmail()) .password(member.getPassword()) .roles(member.getRole().toString()) .build(); } } ================================================ FILE: src/main/java/com/shop/service/OrderService.java ================================================ package com.shop.service; import com.shop.dto.OrderDto; import com.shop.entity.*; import com.shop.repository.ItemRepository; import com.shop.repository.MemberRepository; import com.shop.repository.OrderRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import jakarta.persistence.EntityNotFoundException; import java.util.ArrayList; import java.util.List; import com.shop.dto.OrderHistDto; import com.shop.dto.OrderItemDto; import com.shop.repository.ItemImgRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.thymeleaf.util.StringUtils; @Service @Transactional @RequiredArgsConstructor public class OrderService { private final ItemRepository itemRepository; private final MemberRepository memberRepository; private final OrderRepository orderRepository; private final ItemImgRepository itemImgRepository; public Long order(OrderDto orderDto, String email){ Item item = itemRepository.findById(orderDto.getItemId()) .orElseThrow(EntityNotFoundException::new); Member member = memberRepository.findByEmail(email); List orderItemList = new ArrayList<>(); OrderItem orderItem = OrderItem.createOrderItem(item, orderDto.getCount()); orderItemList.add(orderItem); Order order = Order.createOrder(member, orderItemList); orderRepository.save(order); return order.getId(); } @Transactional(readOnly = true) public Page getOrderList(String email, Pageable pageable) { List orders = orderRepository.findOrders(email, pageable); Long totalCount = orderRepository.countOrder(email); List orderHistDtos = new ArrayList<>(); for (Order order : orders) { OrderHistDto orderHistDto = new OrderHistDto(order); List orderItems = order.getOrderItems(); for (OrderItem orderItem : orderItems) { ItemImg itemImg = itemImgRepository.findByItemIdAndRepimgYn (orderItem.getItem().getId(), "Y"); OrderItemDto orderItemDto = new OrderItemDto(orderItem, itemImg.getImgUrl()); orderHistDto.addOrderItemDto(orderItemDto); } orderHistDtos.add(orderHistDto); } return new PageImpl(orderHistDtos, pageable, totalCount); } @Transactional(readOnly = true) public boolean validateOrder(Long orderId, String email){ Member curMember = memberRepository.findByEmail(email); Order order = orderRepository.findById(orderId) .orElseThrow(EntityNotFoundException::new); Member savedMember = order.getMember(); if(!StringUtils.equals(curMember.getEmail(), savedMember.getEmail())){ return false; } return true; } public void cancelOrder(Long orderId){ Order order = orderRepository.findById(orderId) .orElseThrow(EntityNotFoundException::new); order.cancelOrder(); } public Long orders(List orderDtoList, String email){ Member member = memberRepository.findByEmail(email); List orderItemList = new ArrayList<>(); for (OrderDto orderDto : orderDtoList) { Item item = itemRepository.findById(orderDto.getItemId()) .orElseThrow(EntityNotFoundException::new); OrderItem orderItem = OrderItem.createOrderItem(item, orderDto.getCount()); orderItemList.add(orderItem); } Order order = Order.createOrder(member, orderItemList); orderRepository.save(order); return order.getId(); } } ================================================ FILE: src/main/resources/application-test.properties ================================================ # Datasource 설정 spring.datasource.driver-class-name=org.h2.Driver spring.datasource.url=jdbc:h2:mem:test spring.datasource.username=sa spring.datasource.password= # H2 데이터베이스 방언 설정 spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.jpa.hibernate.ddl-auto=create ================================================ FILE: src/main/resources/application.properties ================================================ #애플리케이션 포트 설정 server.port = 80 #MySQL 연결 설정 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/shop?serverTimezone=UTC spring.datasource.username=root spring.datasource.password=1234 #실행되는 쿼리 콘솔 출력 spring.jpa.properties.hibernate.show_sql=true #콘솔창에 출력되는 쿼리를 가독성이 좋게 포맷팅 spring.jpa.properties.hibernate.format_sql=true #쿼리에 물음표로 출력되는 바인드 파라미터 출력 logging.level.org.hibernate.type.descriptor.sql=trace spring.jpa.hibernate.ddl-auto=update spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect #Live Reload 기능 활성화 spring.devtools.livereload.enabled=true #Thymeleaf cache 사용 중지 spring.thymeleaf.cache = false #파일 한 개당 최대 사이즈 spring.servlet.multipart.maxFileSize=20MB #요청당 최대 파일 크기 spring.servlet.multipart.maxRequestSize=100MB #상품 이미지 업로드 경로 itemImgLocation=C:/shop/item #리소스 업로드 경로 uploadPath=file:///C:/shop/ #기본 batch size 설정 spring.jpa.properties.hibernate.default_batch_fetch_size=1000 ================================================ FILE: src/main/resources/static/css/layout1.css ================================================ html { position: relative; min-height: 100%; margin: 0; } body { min-height: 100%; } .footer { position: absolute; left: 0; right: 0; bottom: 0; width: 100%; padding: 15px 0; text-align: center; } .content{ margin-bottom:100px; margin-top: 50px; margin-left: 200px; margin-right: 200px; } ================================================ FILE: src/main/resources/templates/cart/cartList.html ================================================

장바구니 목록

전체선택 상품정보 상품금액

총 주문 금액 : 0원

================================================ FILE: src/main/resources/templates/fragments/footer.html ================================================ ================================================ FILE: src/main/resources/templates/fragments/header.html ================================================ ================================================ FILE: src/main/resources/templates/item/itemDtl.html ================================================
판매중 품절

수량

결제 금액

상품 상세 설명


================================================ FILE: src/main/resources/templates/item/itemForm.html ================================================

상품 등록

상품명

Incorrect data

가격

Incorrect data

재고

Incorrect data

상품 상세 내용

Incorrect data

================================================ FILE: src/main/resources/templates/item/itemMng.html ================================================
상품아이디 상품명 상태 등록자 등록일
================================================ FILE: src/main/resources/templates/layouts/layout1.html ================================================ Title
================================================ FILE: src/main/resources/templates/main.html ================================================ ================================================ FILE: src/main/resources/templates/member/memberForm.html ================================================

Incorrect data

Incorrect data

Incorrect data

Incorrect data

================================================ FILE: src/main/resources/templates/member/memberLoginForm.html ================================================

================================================ FILE: src/main/resources/templates/order/orderHist.html ================================================

구매 이력

(취소 완료)

================================================ FILE: src/main/resources/templates/thymeleafEx/thymeleafEx01.html ================================================ Title

Hello Thymeleaf!!

================================================ FILE: src/main/resources/templates/thymeleafEx/thymeleafEx02.html ================================================ Title

상품 데이터 출력 예제

상품명 :
상품상세설명 :
상품등록일 :
상품가격 :
================================================ FILE: src/main/resources/templates/thymeleafEx/thymeleafEx03.html ================================================ Title

상품 리스트 출력 예제

순번 상품명 상품설명 가격 상품등록일
================================================ FILE: src/main/resources/templates/thymeleafEx/thymeleafEx04.html ================================================ Title

상품 리스트 출력 예제

순번 상품명 상품설명 가격 상품등록일
짝수 홀수
================================================ FILE: src/main/resources/templates/thymeleafEx/thymeleafEx05.html ================================================ Title

Thymeleaf 링크처리 예제 페이지

================================================ FILE: src/main/resources/templates/thymeleafEx/thymeleafEx06.html ================================================ Title

파라미터 전달 예제

================================================ FILE: src/main/resources/templates/thymeleafEx/thymeleafEx07.html ================================================
본문 영역 입니다.
================================================ FILE: src/test/java/com/shop/ShopApplicationTests.java ================================================ package com.shop; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class ShopApplicationTests { @Test void contextLoads() { } } ================================================ FILE: src/test/java/com/shop/controller/ItemControllerTest.java ================================================ package com.shop.controller; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest @AutoConfigureMockMvc @TestPropertySource(locations="classpath:application-test.properties") class ItemControllerTest { @Autowired MockMvc mockMvc; @Test @DisplayName("상품 등록 페이지 권한 테스트") @WithMockUser(username = "admin", roles = "ADMIN") public void itemFormTest() throws Exception{ mockMvc.perform(MockMvcRequestBuilders.get("/admin/item/new")) .andDo(print()) .andExpect(status().isOk()); } @Test @DisplayName("상품 등록 페이지 일반 회원 접근 테스트") @WithMockUser(username = "user", roles = "USER") public void itemFormNotAdminTest() throws Exception{ mockMvc.perform(MockMvcRequestBuilders.get("/admin/item/new")) .andDo(print()) .andExpect(status().isForbidden()); } } ================================================ FILE: src/test/java/com/shop/controller/MemberControllerTest.java ================================================ package com.shop.controller; import com.shop.dto.MemberFormDto; import com.shop.entity.Member; import com.shop.service.MemberService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers; import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.servlet.MockMvc; import org.springframework.transaction.annotation.Transactional; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; @SpringBootTest @AutoConfigureMockMvc @Transactional @TestPropertySource(locations="classpath:application-test.properties") class MemberControllerTest { @Autowired private MemberService memberService; @Autowired private MockMvc mockMvc; @Autowired PasswordEncoder passwordEncoder; public Member createMember(String email, String password){ MemberFormDto memberFormDto = new MemberFormDto(); memberFormDto.setEmail(email); memberFormDto.setName("홍길동"); memberFormDto.setAddress("서울시 마포구 합정동"); memberFormDto.setPassword(password); Member member = Member.createMember(memberFormDto, passwordEncoder); return memberService.saveMember(member); } @Test @DisplayName("로그인 성공 테스트") public void loginSuccessTest() throws Exception{ String email = "test@email.com"; String password = "1234"; this.createMember(email, password); mockMvc.perform(formLogin().userParameter("email") .loginProcessingUrl("/members/login") .user(email).password(password)) .andExpect(SecurityMockMvcResultMatchers.authenticated()); } @Test @DisplayName("로그인 실패 테스트") public void loginFailTest() throws Exception{ String email = "test@email.com"; String password = "1234"; this.createMember(email, password); mockMvc.perform(formLogin().userParameter("email") .loginProcessingUrl("/members/login") .user(email).password("12345")) .andExpect(SecurityMockMvcResultMatchers.unauthenticated()); } } ================================================ FILE: src/test/java/com/shop/entity/CartTest.java ================================================ package com.shop.entity; import com.shop.dto.MemberFormDto; import com.shop.repository.CartRepository; import com.shop.repository.MemberRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.test.context.TestPropertySource; import org.springframework.transaction.annotation.Transactional; import jakarta.persistence.EntityManager; import jakarta.persistence.EntityNotFoundException; import jakarta.persistence.PersistenceContext; import static org.junit.jupiter.api.Assertions.assertEquals; @SpringBootTest @Transactional @TestPropertySource(locations="classpath:application-test.properties") class CartTest { @Autowired CartRepository cartRepository; @Autowired MemberRepository memberRepository; @Autowired PasswordEncoder passwordEncoder; @PersistenceContext EntityManager em; public Member createMember(){ MemberFormDto memberFormDto = new MemberFormDto(); memberFormDto.setEmail("test@email.com"); memberFormDto.setName("홍길동"); memberFormDto.setAddress("서울시 마포구 합정동"); memberFormDto.setPassword("1234"); return Member.createMember(memberFormDto, passwordEncoder); } @Test @DisplayName("장바구니 회원 엔티티 매핑 조회 테스트") public void findCartAndMemberTest(){ Member member = createMember(); memberRepository.save(member); Cart cart = new Cart(); cart.setMember(member); cartRepository.save(cart); em.flush(); em.clear(); Cart savedCart = cartRepository.findById(cart.getId()) .orElseThrow(EntityNotFoundException::new); assertEquals(savedCart.getMember().getId(), member.getId()); } } ================================================ FILE: src/test/java/com/shop/entity/MemberTest.java ================================================ package com.shop.entity; import com.shop.repository.MemberRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.TestPropertySource; import org.springframework.transaction.annotation.Transactional; import jakarta.persistence.EntityManager; import jakarta.persistence.EntityNotFoundException; import jakarta.persistence.PersistenceContext; @SpringBootTest @Transactional @TestPropertySource(locations="classpath:application-test.properties") public class MemberTest { @Autowired MemberRepository memberRepository; @PersistenceContext EntityManager em; @Test @DisplayName("Auditing 테스트") @WithMockUser(username = "gildong", roles = "USER") public void auditingTest(){ Member newMember = new Member(); memberRepository.save(newMember); em.flush(); em.clear(); Member member = memberRepository.findById(newMember.getId()) .orElseThrow(EntityNotFoundException::new); System.out.println("register time : " + member.getRegTime()); System.out.println("update time : " + member.getUpdateTime()); System.out.println("create member : " + member.getCreatedBy()); System.out.println("modify member : " + member.getModifiedBy()); } } ================================================ FILE: src/test/java/com/shop/entity/OrderTest.java ================================================ package com.shop.entity; import com.shop.constant.ItemSellStatus; import com.shop.repository.ItemRepository; import com.shop.repository.OrderRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.TestPropertySource; import org.springframework.transaction.annotation.Transactional; import jakarta.persistence.EntityManager; import jakarta.persistence.EntityNotFoundException; import jakarta.persistence.PersistenceContext; import java.time.LocalDateTime; import static org.junit.jupiter.api.Assertions.assertEquals; import com.shop.repository.MemberRepository; import com.shop.repository.OrderItemRepository; @SpringBootTest @TestPropertySource(locations="classpath:application-test.properties") @Transactional class OrderTest { @Autowired OrderRepository orderRepository; @Autowired ItemRepository itemRepository; @PersistenceContext EntityManager em; @Autowired MemberRepository memberRepository; @Autowired OrderItemRepository orderItemRepository; public Item createItem(){ Item item = new Item(); item.setItemNm("테스트 상품"); item.setPrice(10000); item.setItemDetail("상세설명"); item.setItemSellStatus(ItemSellStatus.SELL); item.setStockNumber(100); item.setRegTime(LocalDateTime.now()); item.setUpdateTime(LocalDateTime.now()); return item; } @Test @DisplayName("영속성 전이 테스트") public void cascadeTest() { Order order = new Order(); for(int i=0;i<3;i++){ Item item = this.createItem(); itemRepository.save(item); OrderItem orderItem = new OrderItem(); orderItem.setItem(item); orderItem.setCount(10); orderItem.setOrderPrice(1000); orderItem.setOrder(order); order.getOrderItems().add(orderItem); } orderRepository.saveAndFlush(order); em.clear(); Order savedOrder = orderRepository.findById(order.getId()) .orElseThrow(EntityNotFoundException::new); assertEquals(3, savedOrder.getOrderItems().size()); } public Order createOrder(){ Order order = new Order(); for(int i=0;i<3;i++){ Item item = createItem(); itemRepository.save(item); OrderItem orderItem = new OrderItem(); orderItem.setItem(item); orderItem.setCount(10); orderItem.setOrderPrice(1000); orderItem.setOrder(order); order.getOrderItems().add(orderItem); } Member member = new Member(); memberRepository.save(member); order.setMember(member); orderRepository.save(order); return order; } @Test @DisplayName("고아객체 제거 테스트") public void orphanRemovalTest(){ Order order = this.createOrder(); order.getOrderItems().remove(0); em.flush(); } @Test @DisplayName("지연 로딩 테스트") public void lazyLoadingTest(){ Order order = this.createOrder(); Long orderItemId = order.getOrderItems().get(0).getId(); em.flush(); em.clear(); OrderItem orderItem = orderItemRepository.findById(orderItemId) .orElseThrow(EntityNotFoundException::new); System.out.println("Order class : " + orderItem.getOrder().getClass()); System.out.println("==========================="); orderItem.getOrder().getOrderDate(); System.out.println("==========================="); } } ================================================ FILE: src/test/java/com/shop/repository/ItemRepositoryTest.java ================================================ package com.shop.repository; import com.shop.constant.ItemSellStatus; import com.shop.entity.Item; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.TestPropertySource; import java.time.LocalDateTime; import java.util.List; import com.querydsl.jpa.impl.JPAQueryFactory; import com.querydsl.jpa.impl.JPAQuery; import com.shop.entity.QItem; import jakarta.persistence.PersistenceContext; import jakarta.persistence.EntityManager; import com.querydsl.core.BooleanBuilder; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.thymeleaf.util.StringUtils; @SpringBootTest @TestPropertySource(locations="classpath:application-test.properties") class ItemRepositoryTest { @Autowired ItemRepository itemRepository; @PersistenceContext EntityManager em; @Test @DisplayName("상품 저장 테스트") public void createItemTest(){ Item item = new Item(); item.setItemNm("테스트 상품"); item.setPrice(10000); item.setItemDetail("테스트 상품 상세 설명"); item.setItemSellStatus(ItemSellStatus.SELL); item.setStockNumber(100); item.setRegTime(LocalDateTime.now()); item.setUpdateTime(LocalDateTime.now()); Item savedItem = itemRepository.save(item); System.out.println(savedItem.toString()); } public void createItemList(){ for(int i=1;i<=10;i++){ Item item = new Item(); item.setItemNm("테스트 상품" + i); item.setPrice(10000 + i); item.setItemDetail("테스트 상품 상세 설명" + i); item.setItemSellStatus(ItemSellStatus.SELL); item.setStockNumber(100); item.setRegTime(LocalDateTime.now()); item.setUpdateTime(LocalDateTime.now()); Item savedItem = itemRepository.save(item); } } @Test @DisplayName("상품명 조회 테스트") public void findByItemNmTest(){ this.createItemList(); List itemList = itemRepository.findByItemNm("테스트 상품1"); for(Item item : itemList){ System.out.println(item.toString()); } } @Test @DisplayName("상품명, 상품상세설명 or 테스트") public void findByItemNmOrItemDetailTest(){ this.createItemList(); List itemList = itemRepository.findByItemNmOrItemDetail("테스트 상품1", "테스트 상품 상세 설명5"); for(Item item : itemList){ System.out.println(item.toString()); } } @Test @DisplayName("가격 LessThan 테스트") public void findByPriceLessThanTest(){ this.createItemList(); List itemList = itemRepository.findByPriceLessThan(10005); for(Item item : itemList){ System.out.println(item.toString()); } } @Test @DisplayName("가격 내림차순 조회 테스트") public void findByPriceLessThanOrderByPriceDesc(){ this.createItemList(); List itemList = itemRepository.findByPriceLessThanOrderByPriceDesc(10005); for(Item item : itemList){ System.out.println(item.toString()); } } @Test @DisplayName("@Query를 이용한 상품 조회 테스트") public void findByItemDetailTest(){ this.createItemList(); List itemList = itemRepository.findByItemDetail("테스트 상품 상세 설명"); for(Item item : itemList){ System.out.println(item.toString()); } } @Test @DisplayName("Querydsl 조회 테스트1") public void queryDslTest(){ this.createItemList(); JPAQueryFactory queryFactory = new JPAQueryFactory(em); QItem qItem = QItem.item; JPAQuery query = queryFactory.selectFrom(qItem) .where(qItem.itemSellStatus.eq(ItemSellStatus.SELL)) .where(qItem.itemDetail.like("%" + "테스트 상품 상세 설명" + "%")) .orderBy(qItem.price.desc()); List itemList = query.fetch(); for(Item item : itemList){ System.out.println(item.toString()); } } public void createItemList2(){ for(int i=1;i<=5;i++){ Item item = new Item(); item.setItemNm("테스트 상품" + i); item.setPrice(10000 + i); item.setItemDetail("테스트 상품 상세 설명" + i); item.setItemSellStatus(ItemSellStatus.SELL); item.setStockNumber(100); item.setRegTime(LocalDateTime.now()); item.setUpdateTime(LocalDateTime.now()); itemRepository.save(item); } for(int i=6;i<=10;i++){ Item item = new Item(); item.setItemNm("테스트 상품" + i); item.setPrice(10000 + i); item.setItemDetail("테스트 상품 상세 설명" + i); item.setItemSellStatus(ItemSellStatus.SOLD_OUT); item.setStockNumber(0); item.setRegTime(LocalDateTime.now()); item.setUpdateTime(LocalDateTime.now()); itemRepository.save(item); } } @Test @DisplayName("상품 Querydsl 조회 테스트 2") public void queryDslTest2(){ this.createItemList2(); BooleanBuilder booleanBuilder = new BooleanBuilder(); QItem item = QItem.item; String itemDetail = "테스트 상품 상세 설명"; int price = 10003; String itemSellStat = "SELL"; booleanBuilder.and(item.itemDetail.like("%" + itemDetail + "%")); booleanBuilder.and(item.price.gt(price)); System.out.println(ItemSellStatus.SELL); if(StringUtils.equals(itemSellStat, ItemSellStatus.SELL)){ booleanBuilder.and(item.itemSellStatus.eq(ItemSellStatus.SELL)); } Pageable pageable = PageRequest.of(0, 5); Page itemPagingResult = itemRepository.findAll(booleanBuilder, pageable); System.out.println("total elements : " + itemPagingResult. getTotalElements ()); List resultItemList = itemPagingResult.getContent(); for(Item resultItem: resultItemList){ System.out.println(resultItem.toString()); } } } ================================================ FILE: src/test/java/com/shop/service/CartServiceTest.java ================================================ package com.shop.service; import com.shop.constant.ItemSellStatus; import com.shop.dto.CartItemDto; import com.shop.entity.CartItem; import com.shop.entity.Item; import com.shop.entity.Member; import com.shop.repository.CartItemRepository; import com.shop.repository.ItemRepository; import com.shop.repository.MemberRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.TestPropertySource; import org.springframework.transaction.annotation.Transactional; import jakarta.persistence.EntityNotFoundException; import static org.junit.jupiter.api.Assertions.assertEquals; @SpringBootTest @Transactional @TestPropertySource(locations="classpath:application-test.properties") class CartServiceTest { @Autowired ItemRepository itemRepository; @Autowired MemberRepository memberRepository; @Autowired CartService cartService; @Autowired CartItemRepository cartItemRepository; public Item saveItem(){ Item item = new Item(); item.setItemNm("테스트 상품"); item.setPrice(10000); item.setItemDetail("테스트 상품 상세 설명"); item.setItemSellStatus(ItemSellStatus.SELL); item.setStockNumber(100); return itemRepository.save(item); } public Member saveMember(){ Member member = new Member(); member.setEmail("test@test.com"); return memberRepository.save(member); } @Test @DisplayName("장바구니 담기 테스트") public void addCart(){ Item item = saveItem(); Member member = saveMember(); CartItemDto cartItemDto = new CartItemDto(); cartItemDto.setCount(5); cartItemDto.setItemId(item.getId()); Long cartItemId = cartService.addCart(cartItemDto, member.getEmail()); CartItem cartItem = cartItemRepository.findById(cartItemId) .orElseThrow(EntityNotFoundException::new); assertEquals(item.getId(), cartItem.getItem().getId()); assertEquals(cartItemDto.getCount(), cartItem.getCount()); } } ================================================ FILE: src/test/java/com/shop/service/ItemServiceTest.java ================================================ package com.shop.service; import com.shop.constant.ItemSellStatus; import com.shop.dto.ItemFormDto; import com.shop.entity.Item; import com.shop.entity.ItemImg; import com.shop.repository.ItemImgRepository; import com.shop.repository.ItemRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.mock.web.MockMultipartFile; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.TestPropertySource; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import jakarta.persistence.EntityNotFoundException; import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; @SpringBootTest @Transactional @TestPropertySource(locations="classpath:application-test.properties") class ItemServiceTest { @Autowired ItemService itemService; @Autowired ItemRepository itemRepository; @Autowired ItemImgRepository itemImgRepository; List createMultipartFiles() throws Exception{ List multipartFileList = new ArrayList<>(); for(int i=0;i<5;i++){ String path = "C:/shop/item/"; String imageName = "image" + i + ".jpg"; MockMultipartFile multipartFile = new MockMultipartFile(path, imageName, "image/jpg", new byte[]{1,2,3,4}); multipartFileList.add(multipartFile); } return multipartFileList; } @Test @DisplayName("상품 등록 테스트") @WithMockUser(username = "admin", roles = "ADMIN") void saveItem() throws Exception { ItemFormDto itemFormDto = new ItemFormDto(); itemFormDto.setItemNm("테스트상품"); itemFormDto.setItemSellStatus(ItemSellStatus.SELL); itemFormDto.setItemDetail("테스트 상품 입니다."); itemFormDto.setPrice(1000); itemFormDto.setStockNumber(100); List multipartFileList = createMultipartFiles(); Long itemId = itemService.saveItem(itemFormDto, multipartFileList); List itemImgList = itemImgRepository.findByItemIdOrderByIdAsc(itemId); Item item = itemRepository.findById(itemId) .orElseThrow(EntityNotFoundException::new); assertEquals(itemFormDto.getItemNm(), item.getItemNm()); assertEquals(itemFormDto.getItemSellStatus(), item.getItemSellStatus()); assertEquals(itemFormDto.getItemDetail(), item.getItemDetail()); assertEquals(itemFormDto.getPrice(), item.getPrice()); assertEquals(itemFormDto.getStockNumber(), item.getStockNumber()); assertEquals(multipartFileList.get(0).getOriginalFilename(), itemImgList.get(0).getOriImgName()); } } ================================================ FILE: src/test/java/com/shop/service/MemberServiceTest.java ================================================ package com.shop.service; import com.shop.dto.MemberFormDto; import com.shop.entity.Member; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.test.context.TestPropertySource; import org.springframework.transaction.annotation.Transactional; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @SpringBootTest @Transactional @TestPropertySource(locations="classpath:application-test.properties") class MemberServiceTest { @Autowired MemberService memberService; @Autowired PasswordEncoder passwordEncoder; public Member createMember(){ MemberFormDto memberFormDto = new MemberFormDto(); memberFormDto.setEmail("test@email.com"); memberFormDto.setName("홍길동"); memberFormDto.setAddress("서울시 마포구 합정동"); memberFormDto.setPassword("1234"); return Member.createMember(memberFormDto, passwordEncoder); } @Test @DisplayName("회원가입 테스트") public void saveMemberTest(){ Member member = createMember(); Member savedMember = memberService.saveMember(member); assertEquals(member.getEmail(), savedMember.getEmail()); assertEquals(member.getName(), savedMember.getName()); assertEquals(member.getAddress(), savedMember.getAddress()); assertEquals(member.getPassword(), savedMember.getPassword()); assertEquals(member.getRole(), savedMember.getRole()); } @Test @DisplayName("중복 회원 가입 테스트") public void saveDuplicateMemberTest(){ Member member1 = createMember(); Member member2 = createMember(); memberService.saveMember(member1); Throwable e = assertThrows(IllegalStateException.class, () -> { memberService.saveMember(member2);}); assertEquals("이미 가입된 회원입니다.", e.getMessage()); } } ================================================ FILE: src/test/java/com/shop/service/OrderServiceTest.java ================================================ package com.shop.service; import com.shop.constant.ItemSellStatus; import com.shop.constant.OrderStatus; import com.shop.dto.OrderDto; import com.shop.entity.Item; import com.shop.entity.Member; import com.shop.entity.Order; import com.shop.entity.OrderItem; import com.shop.repository.ItemRepository; import com.shop.repository.MemberRepository; import com.shop.repository.OrderRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.TestPropertySource; import org.springframework.transaction.annotation.Transactional; import jakarta.persistence.EntityNotFoundException; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; @SpringBootTest @Transactional @TestPropertySource(locations="classpath:application-test.properties") class OrderServiceTest { @Autowired private OrderService orderService; @Autowired private OrderRepository orderRepository; @Autowired ItemRepository itemRepository; @Autowired MemberRepository memberRepository; public Item saveItem(){ Item item = new Item(); item.setItemNm("테스트 상품"); item.setPrice(10000); item.setItemDetail("테스트 상품 상세 설명"); item.setItemSellStatus(ItemSellStatus.SELL); item.setStockNumber(100); return itemRepository.save(item); } public Member saveMember(){ Member member = new Member(); member.setEmail("test@test.com"); return memberRepository.save(member); } @Test @DisplayName("주문 테스트") public void order(){ Item item = saveItem(); Member member = saveMember(); OrderDto orderDto = new OrderDto(); orderDto.setCount(10); orderDto.setItemId(item.getId()); Long orderId = orderService.order(orderDto, member.getEmail()); Order order = orderRepository.findById(orderId) .orElseThrow(EntityNotFoundException::new); List orderItems = order.getOrderItems(); int totalPrice = orderDto.getCount()*item.getPrice(); assertEquals(totalPrice, order.getTotalPrice()); } @Test @DisplayName("주문 취소 테스트") public void cancelOrder(){ Item item = saveItem(); Member member = saveMember(); OrderDto orderDto = new OrderDto(); orderDto.setCount(10); orderDto.setItemId(item.getId()); Long orderId = orderService.order(orderDto, member.getEmail()); Order order = orderRepository.findById(orderId) .orElseThrow(EntityNotFoundException::new); orderService.cancelOrder(orderId); assertEquals(OrderStatus.CANCEL, order.getOrderStatus()); assertEquals(100, item.getStockNumber()); } }