├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── README.md ├── imgs ├── img.png ├── img_1.png ├── img_10.png ├── img_11.png ├── img_12.png ├── img_13.png ├── img_14.png ├── img_15.png ├── img_2.png ├── img_3.png ├── img_4.png ├── img_5.png ├── img_6.png ├── img_7.png ├── img_8.png ├── img_9.png └── main.png ├── mvnw ├── mvnw.cmd ├── pom.xml └── src ├── main ├── java │ └── kr │ │ └── co │ │ └── codewiki │ │ └── shoppingmall │ │ ├── ShoppingmallApplication.java │ │ ├── config │ │ ├── AuditConfig.java │ │ ├── AuditorAwareImpl.java │ │ ├── CustomAuthenticationEntryPoint.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 │ ├── static │ ├── css │ │ └── layout1.css │ ├── img │ │ └── main.jpg │ └── item │ │ ├── 03717933-2b8a-4947-9686-62f8b36d3ed7.png │ │ ├── 28544370-dfd1-45a7-906e-41db3a5c3a22.png │ │ ├── 28be6789-2c71-43e1-987b-258cbdc9beb4.jpg │ │ ├── 35a743f9-1099-4027-bd1d-58cf7686bb91.jpg │ │ ├── 83491805-5237-456b-9c56-e83c470859ad.jfif │ │ ├── 923b2e53-4280-4c92-99c1-3e04ddd481e2.gif │ │ ├── 970bd5a0-f0f5-4a68-8adc-dc7282b89990.jpg │ │ ├── 9eddf375-d0e4-4ae8-8b75-3d0ec56896c0.jfif │ │ ├── d05fa5f8-697e-41b8-9f4f-b5f60b2a2786.png │ │ ├── d7344109-4c09-4a7f-80e5-1f2b84420ff1.png │ │ ├── f13e166d-5a77-4cb0-a2ce-ee8be29070f4.jfif │ │ └── f942ed93-0d79-4875-a3f6-4b01651a5f1a.jfif │ └── 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 │ ├── thymeleafEx02.html │ ├── thymeleafEx03.html │ └── thymeleafEx07.html └── test └── java └── kr └── co └── codewiki └── shoppingmall ├── ShoppingmallApplicationTests.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 /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | src/main/resources/application-test.properties 22 | src/main/resources/application.properties 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | build/ 31 | !**/src/main/**/build/ 32 | !**/src/test/**/build/ 33 | 34 | ### VS Code ### 35 | .vscode/ 36 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🛒SpringBoot ShoppingMall Project 2 | 3 | 4 | 5 | 6 | ## 🗨 서비스 설명 7 | 8 | springboot 로 구현한 쇼핑몰 입니다. 9 | 10 | ## ⚙ 기술 스택 11 | ### FE 12 | - thymeleaf 13 | - html, css, js 14 | - bootStrap 15 | 16 | ### BE 17 | - SpringBoot 18 | - JPA 19 | - MySQL 20 | 21 | ### 사용기술 22 | - MVC pattern 23 | - CRUD 24 | - spring security 25 | - image 업로드 26 | 27 | 28 | ## 👀 View Page 29 | ### signUp 30 | 쇼핑몰의 회원가입 페이지 입니다. 31 | 32 | ![img_1.png](imgs/img_1.png) 33 | 34 | ### logIn 35 | 쇼핑몰의 로그인 페이지 입니다. 36 | 37 | ![img_2.png](imgs/img_2.png) 38 | 39 | ### 상품등록 40 | 인가된 회원은 쇼핑몰에서 상품을 등록할 수 있습니다. 41 | 42 | 상품이미지1 은 not null 입니다. 43 | 상품이미지1 는 상품의 대표이미지로 자동 등록 됩니다. 44 | 45 | ![img_8.png](imgs/img_8.png) 46 | 47 | ### 상품관리 48 | 등록한 상품들의 목록을 볼 수 있습니다. 49 | 50 | ![img_9.png](imgs/img_9.png) 51 | 52 | ### 상품수정 53 | 상품 관리 페이지에서 상품 이름을 클릭하면 54 | 상품 수정 페이지로 이동합니다. 55 | 56 | ![img_10.png](imgs/img_10.png) 57 | 58 | ### 상품디테일 59 | 메인 화면에서 상품을 클릭하면 60 | 상품 디테일 화면으로 이동합니다. 61 | ![img.png](imgs/img.png) 62 | 63 | 상품 디테일 화면에서는 64 | 상품 등록 시 설정한 이미지 파일들이 순서대로 출력됩니다. 65 | 66 | ![img_13.png](imgs/img_13.png) 67 | 68 | 원하는 수량을 담은 후 장바구니 담기를 누르면 69 | 상품이 장바구니에 담깁니다. 70 | 장바구니에 담지 않고 바로 주문하기를 누를 수도 있습니다. 71 | 72 | ### 장바구니 73 | 현재 장바구니에 담긴 상품들을 볼 수 있습니다. 74 | 체크박스로 주문 상품을 고를 수 있으며 75 | x 버튼을 눌러 상품을 제거할 수도 있습니다. 76 | 77 | 상품을 다 고르면 하단에 총 주문 금액이 출력됩니다. 78 | 79 | ![img_14.png](imgs/img_14.png) 80 | 81 | ### 구매이력 82 | 상품을 구매했을 때의 이력을 확인할 수 있습니다. 83 | 주문 취소 버튼을 클릭해서 주문을 취소할 수 있습니다. 84 | 85 | ![img_15.png](imgs/img_15.png) -------------------------------------------------------------------------------- /imgs/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/imgs/img.png -------------------------------------------------------------------------------- /imgs/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/imgs/img_1.png -------------------------------------------------------------------------------- /imgs/img_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/imgs/img_10.png -------------------------------------------------------------------------------- /imgs/img_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/imgs/img_11.png -------------------------------------------------------------------------------- /imgs/img_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/imgs/img_12.png -------------------------------------------------------------------------------- /imgs/img_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/imgs/img_13.png -------------------------------------------------------------------------------- /imgs/img_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/imgs/img_14.png -------------------------------------------------------------------------------- /imgs/img_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/imgs/img_15.png -------------------------------------------------------------------------------- /imgs/img_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/imgs/img_2.png -------------------------------------------------------------------------------- /imgs/img_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/imgs/img_3.png -------------------------------------------------------------------------------- /imgs/img_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/imgs/img_4.png -------------------------------------------------------------------------------- /imgs/img_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/imgs/img_5.png -------------------------------------------------------------------------------- /imgs/img_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/imgs/img_6.png -------------------------------------------------------------------------------- /imgs/img_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/imgs/img_7.png -------------------------------------------------------------------------------- /imgs/img_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/imgs/img_8.png -------------------------------------------------------------------------------- /imgs/img_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/imgs/img_9.png -------------------------------------------------------------------------------- /imgs/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/imgs/main.png -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /usr/local/etc/mavenrc ] ; then 40 | . /usr/local/etc/mavenrc 41 | fi 42 | 43 | if [ -f /etc/mavenrc ] ; then 44 | . /etc/mavenrc 45 | fi 46 | 47 | if [ -f "$HOME/.mavenrc" ] ; then 48 | . "$HOME/.mavenrc" 49 | fi 50 | 51 | fi 52 | 53 | # OS specific support. $var _must_ be set to either true or false. 54 | cygwin=false; 55 | darwin=false; 56 | mingw=false 57 | case "`uname`" in 58 | CYGWIN*) cygwin=true ;; 59 | MINGW*) mingw=true;; 60 | Darwin*) darwin=true 61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 63 | if [ -z "$JAVA_HOME" ]; then 64 | if [ -x "/usr/libexec/java_home" ]; then 65 | export JAVA_HOME="`/usr/libexec/java_home`" 66 | else 67 | export JAVA_HOME="/Library/Java/Home" 68 | fi 69 | fi 70 | ;; 71 | esac 72 | 73 | if [ -z "$JAVA_HOME" ] ; then 74 | if [ -r /etc/gentoo-release ] ; then 75 | JAVA_HOME=`java-config --jre-home` 76 | fi 77 | fi 78 | 79 | if [ -z "$M2_HOME" ] ; then 80 | ## resolve links - $0 may be a link to maven's home 81 | PRG="$0" 82 | 83 | # need this for relative symlinks 84 | while [ -h "$PRG" ] ; do 85 | ls=`ls -ld "$PRG"` 86 | link=`expr "$ls" : '.*-> \(.*\)$'` 87 | if expr "$link" : '/.*' > /dev/null; then 88 | PRG="$link" 89 | else 90 | PRG="`dirname "$PRG"`/$link" 91 | fi 92 | done 93 | 94 | saveddir=`pwd` 95 | 96 | M2_HOME=`dirname "$PRG"`/.. 97 | 98 | # make it fully qualified 99 | M2_HOME=`cd "$M2_HOME" && pwd` 100 | 101 | cd "$saveddir" 102 | # echo Using m2 at $M2_HOME 103 | fi 104 | 105 | # For Cygwin, ensure paths are in UNIX format before anything is touched 106 | if $cygwin ; then 107 | [ -n "$M2_HOME" ] && 108 | M2_HOME=`cygpath --unix "$M2_HOME"` 109 | [ -n "$JAVA_HOME" ] && 110 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 111 | [ -n "$CLASSPATH" ] && 112 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 113 | fi 114 | 115 | # For Mingw, ensure paths are in UNIX format before anything is touched 116 | if $mingw ; then 117 | [ -n "$M2_HOME" ] && 118 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 119 | [ -n "$JAVA_HOME" ] && 120 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 121 | fi 122 | 123 | if [ -z "$JAVA_HOME" ]; then 124 | javaExecutable="`which javac`" 125 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 126 | # readlink(1) is not available as standard on Solaris 10. 127 | readLink=`which readlink` 128 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 129 | if $darwin ; then 130 | javaHome="`dirname \"$javaExecutable\"`" 131 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 132 | else 133 | javaExecutable="`readlink -f \"$javaExecutable\"`" 134 | fi 135 | javaHome="`dirname \"$javaExecutable\"`" 136 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 137 | JAVA_HOME="$javaHome" 138 | export JAVA_HOME 139 | fi 140 | fi 141 | fi 142 | 143 | if [ -z "$JAVACMD" ] ; then 144 | if [ -n "$JAVA_HOME" ] ; then 145 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 146 | # IBM's JDK on AIX uses strange locations for the executables 147 | JAVACMD="$JAVA_HOME/jre/sh/java" 148 | else 149 | JAVACMD="$JAVA_HOME/bin/java" 150 | fi 151 | else 152 | JAVACMD="`\\unset -f command; \\command -v java`" 153 | fi 154 | fi 155 | 156 | if [ ! -x "$JAVACMD" ] ; then 157 | echo "Error: JAVA_HOME is not defined correctly." >&2 158 | echo " We cannot execute $JAVACMD" >&2 159 | exit 1 160 | fi 161 | 162 | if [ -z "$JAVA_HOME" ] ; then 163 | echo "Warning: JAVA_HOME environment variable is not set." 164 | fi 165 | 166 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 167 | 168 | # traverses directory structure from process work directory to filesystem root 169 | # first directory with .mvn subdirectory is considered project base directory 170 | find_maven_basedir() { 171 | 172 | if [ -z "$1" ] 173 | then 174 | echo "Path not specified to find_maven_basedir" 175 | return 1 176 | fi 177 | 178 | basedir="$1" 179 | wdir="$1" 180 | while [ "$wdir" != '/' ] ; do 181 | if [ -d "$wdir"/.mvn ] ; then 182 | basedir=$wdir 183 | break 184 | fi 185 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 186 | if [ -d "${wdir}" ]; then 187 | wdir=`cd "$wdir/.."; pwd` 188 | fi 189 | # end of workaround 190 | done 191 | echo "${basedir}" 192 | } 193 | 194 | # concatenates all lines of a file 195 | concat_lines() { 196 | if [ -f "$1" ]; then 197 | echo "$(tr -s '\n' ' ' < "$1")" 198 | fi 199 | } 200 | 201 | BASE_DIR=`find_maven_basedir "$(pwd)"` 202 | if [ -z "$BASE_DIR" ]; then 203 | exit 1; 204 | fi 205 | 206 | ########################################################################################## 207 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 208 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 209 | ########################################################################################## 210 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Found .mvn/wrapper/maven-wrapper.jar" 213 | fi 214 | else 215 | if [ "$MVNW_VERBOSE" = true ]; then 216 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 217 | fi 218 | if [ -n "$MVNW_REPOURL" ]; then 219 | jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 220 | else 221 | jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 222 | fi 223 | while IFS="=" read key value; do 224 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 225 | esac 226 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 227 | if [ "$MVNW_VERBOSE" = true ]; then 228 | echo "Downloading from: $jarUrl" 229 | fi 230 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 231 | if $cygwin; then 232 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 233 | fi 234 | 235 | if command -v wget > /dev/null; then 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Found wget ... using wget" 238 | fi 239 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 240 | wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 241 | else 242 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 243 | fi 244 | elif command -v curl > /dev/null; then 245 | if [ "$MVNW_VERBOSE" = true ]; then 246 | echo "Found curl ... using curl" 247 | fi 248 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 249 | curl -o "$wrapperJarPath" "$jarUrl" -f 250 | else 251 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 252 | fi 253 | 254 | else 255 | if [ "$MVNW_VERBOSE" = true ]; then 256 | echo "Falling back to using Java to download" 257 | fi 258 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 259 | # For Cygwin, switch paths to Windows format before running javac 260 | if $cygwin; then 261 | javaClass=`cygpath --path --windows "$javaClass"` 262 | fi 263 | if [ -e "$javaClass" ]; then 264 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 265 | if [ "$MVNW_VERBOSE" = true ]; then 266 | echo " - Compiling MavenWrapperDownloader.java ..." 267 | fi 268 | # Compiling the Java class 269 | ("$JAVA_HOME/bin/javac" "$javaClass") 270 | fi 271 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 272 | # Running the downloader 273 | if [ "$MVNW_VERBOSE" = true ]; then 274 | echo " - Running MavenWrapperDownloader.java ..." 275 | fi 276 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 277 | fi 278 | fi 279 | fi 280 | fi 281 | ########################################################################################## 282 | # End of extension 283 | ########################################################################################## 284 | 285 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 286 | if [ "$MVNW_VERBOSE" = true ]; then 287 | echo $MAVEN_PROJECTBASEDIR 288 | fi 289 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 290 | 291 | # For Cygwin, switch paths to Windows format before running java 292 | if $cygwin; then 293 | [ -n "$M2_HOME" ] && 294 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 295 | [ -n "$JAVA_HOME" ] && 296 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 297 | [ -n "$CLASSPATH" ] && 298 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 299 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 300 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 301 | fi 302 | 303 | # Provide a "standardized" way to retrieve the CLI args that will 304 | # work with both Windows and non-Windows executions. 305 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 306 | export MAVEN_CMD_LINE_ARGS 307 | 308 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 309 | 310 | exec "$JAVACMD" \ 311 | $MAVEN_OPTS \ 312 | $MAVEN_DEBUG_OPTS \ 313 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 314 | "-Dmaven.home=${M2_HOME}" \ 315 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 316 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 317 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.6.3 9 | 10 | 11 | kr.co.codewiki 12 | shoppingmall 13 | 0.0.1-SNAPSHOT 14 | shoppingmall 15 | Shoppingmall project for Spring Boot 16 | 17 | 11 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-data-jpa 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-thymeleaf 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-web 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-security 36 | 37 | 38 | 39 | 40 | com.querydsl 41 | querydsl-jpa 42 | 43 | 44 | 45 | com.querydsl 46 | querydsl-apt 47 | 48 | 49 | 50 | 51 | 52 | com.h2database 53 | h2 54 | runtime 55 | 56 | 57 | mysql 58 | mysql-connector-java 59 | runtime 60 | 61 | 62 | org.projectlombok 63 | lombok 64 | true 65 | 66 | 67 | org.springframework.boot 68 | spring-boot-starter-test 69 | test 70 | 71 | 72 | org.springframework.boot 73 | spring-boot-devtools 74 | 75 | 76 | 77 | nz.net.ultraq.thymeleaf 78 | thymeleaf-layout-dialect 79 | 80 | 81 | 83 | 84 | org.springframework.boot 85 | spring-boot-starter-validation 86 | 87 | 88 | 89 | org.springframework.security 90 | spring-security-test 91 | test 92 | ${spring-security.version} 93 | 94 | 95 | 96 | org.thymeleaf.extras 97 | thymeleaf-extras-springsecurity5 98 | 99 | 100 | 101 | 102 | org.modelmapper 103 | modelmapper 104 | 2.3.9 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | org.springframework.boot 113 | spring-boot-maven-plugin 114 | 115 | 116 | 117 | org.projectlombok 118 | lombok 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | com.mysema.maven 128 | apt-maven-plugin 129 | 1.1.3 130 | 131 | 132 | 133 | process 134 | 135 | 136 | target/generated-sources/java 137 | com.querydsl.apt.jpa.JPAAnnotationProcessor 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/ShoppingmallApplication.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class ShoppingmallApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(ShoppingmallApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/config/AuditConfig.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.data.domain.AuditorAware; 6 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing; 7 | 8 | @Configuration 9 | @EnableJpaAuditing // jpa auditing 기능 활성화 10 | public class AuditConfig { 11 | @Bean 12 | public AuditorAware auditorProvider() { // auditoraware: 등록자와 수정자를 처리 13 | return new AuditorAwareImpl(); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/config/AuditorAwareImpl.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.config; 2 | 3 | import org.springframework.data.domain.AuditorAware; 4 | import org.springframework.security.core.Authentication; 5 | import org.springframework.security.core.context.SecurityContextHolder; 6 | 7 | import java.util.Optional; 8 | 9 | public class AuditorAwareImpl implements AuditorAware { 10 | 11 | @Override 12 | public Optional getCurrentAuditor() { 13 | Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); 14 | String userId = ""; 15 | if(authentication != null){ 16 | userId = authentication.getName(); // 현재 로그인한 사용자의 정보를 조회해서 사용자의 이름을 등록자와 수정자로 지정함 17 | } 18 | return Optional.of(userId); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/config/CustomAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.config; 2 | 3 | import org.springframework.security.core.AuthenticationException; 4 | import org.springframework.security.web.AuthenticationEntryPoint; 5 | 6 | import javax.servlet.ServletException; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | import java.io.IOException; 10 | 11 | // AuthenticationEntryPoint: 인증되지 않은 사용자가 리소스를 요청할 경우 Unauthorized 에러를 발생시킴 => 401 에러 12 | public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { 13 | 14 | @Override 15 | public void commence 16 | (HttpServletRequest request, HttpServletResponse response, 17 | AuthenticationException authException) throws IOException, ServletException { 18 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"Unauthorized"); 19 | // HttpServletResponse.SC_UNAUTHORIZED: 401 에러 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.config; 2 | 3 | import kr.co.codewiki.shoppingmall.service.MemberService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 8 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 9 | import org.springframework.security.config.annotation.web.builders.WebSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 11 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 12 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 13 | import org.springframework.security.crypto.password.PasswordEncoder; 14 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 15 | 16 | @Configuration // 설정파일을 만들기 위한 애노테이션 or Bean을 등록하기 위한 애노테이션 17 | @EnableWebSecurity // 보안 설정을 커스터마이징 할 수 있는 애노테이션. SpringSecurityFilterChane 이 자동으로 포함됨 18 | public class SecurityConfig extends WebSecurityConfigurerAdapter { // WebSecurityConfigurerAdapter 를 상속받아서 보안 설정 19 | 20 | @Autowired 21 | MemberService memberService; 22 | 23 | // http 요청에 대한 페이지 설정 (권한, 로그인 페이지, 로그아웃 메소드 등등) 24 | @Override 25 | protected void configure(HttpSecurity http) throws Exception{ 26 | http.formLogin() 27 | .loginPage("/members/login") // 로그인 페이지 url 28 | .defaultSuccessUrl("/") // 로그인 성공 시 url 29 | .usernameParameter("email") // 로그인 시 사용할 파라미터 이름 지정 (username 에 들어갈 변수?) 30 | .failureUrl("/members/login/error") // 로그인 실패 시 url 31 | .and() 32 | .logout() 33 | .logoutRequestMatcher(new AntPathRequestMatcher("/members/logout")) // 로그아웃 페이지 url 34 | .logoutSuccessUrl("/"); // 로그아웃 성공 시 url 35 | 36 | http.authorizeRequests() 37 | .mvcMatchers("/","/members/**","/item/**","/images/**").permitAll() // 여기는 모두가 다 접근 38 | .mvcMatchers("/admin/**").hasRole("ADMIN") // admin 만 접근 39 | .anyRequest().authenticated(); // 나머지 경로들은 인증만 요구! (로그인 하면 다 접근 ㄱㄴ) 40 | 41 | http.exceptionHandling() // 인증 안된 사용자는 ㄴㄴ 42 | .authenticationEntryPoint(new CustomAuthenticationEntryPoint()); 43 | 44 | } 45 | 46 | // static 하위 파일들은 인증 무시! 47 | public void configure(WebSecurity web) throws Exception{ 48 | web.ignoring().antMatchers("/css/**","/js/**","/img/**"); 49 | } 50 | 51 | // http 요청에 대한 보안 설정 (권한, 로그인 페이지, 로그아웃 메소드 등등) 52 | @Override 53 | protected void configure(AuthenticationManagerBuilder auth) throws Exception{ 54 | 55 | auth.userDetailsService(memberService) 56 | .passwordEncoder(passwordEncoder()); 57 | } 58 | // AuthenticationManager 로 인증함 59 | // AuthenticationManager 는 ~Builder 로 만듦 60 | // auth 에 이거를 다 담아서 인증을 하는거지 61 | // memberService 를 저장한다.(로그인 로그아웃 + 중복가입방지 + 중복 아니면 member save) 62 | // 비번 암호화까지 해버림 63 | 64 | 65 | // 비번 암호화 66 | @Bean // BCryptPasswordEncoder 를 Bean 으로 등록해서 사용 67 | public PasswordEncoder passwordEncoder(){ 68 | // 해킹 시 회원 정보 노출 방지 (비번같은거 털릴 때 대비)_ BCryptPasswordEncoder 의 해시 함수를 이용해서 비번 암호화 69 | return new BCryptPasswordEncoder(); 70 | } 71 | 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/config/WebMvcConfig.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.config; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | @Configuration 9 | public class WebMvcConfig implements WebMvcConfigurer { 10 | 11 | // 아까 application.properties 에 적었던 uploadPath 여기다가 대입 12 | @Value("${uploadPath}") 13 | String uploadPath; 14 | 15 | @Override 16 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 17 | registry.addResourceHandler("/images/**") // uploadPath 경로들/~ 18 | .addResourceLocations(uploadPath); // 로컬에 저장된 파일을 읽어 올 root 경로 설정 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/constant/ItemSellStatus.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.constant; 2 | 3 | public enum ItemSellStatus { 4 | SELL, SOLD_OUT 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/constant/OrderStatus.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.constant; 2 | 3 | public enum OrderStatus { 4 | ORDER, CANCEL 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/constant/Role.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.constant; 2 | 3 | public enum Role { 4 | USER, ADMIN // Role 값으로 USER 와 ADMIN 2개를 입력함 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/controller/CartController.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.controller; 2 | 3 | import kr.co.codewiki.shoppingmall.dto.CartDetailDto; 4 | import kr.co.codewiki.shoppingmall.dto.CartItemDto; 5 | import kr.co.codewiki.shoppingmall.dto.CartOrderDto; 6 | import kr.co.codewiki.shoppingmall.service.CartService; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.stereotype.Controller; 11 | import org.springframework.ui.Model; 12 | import org.springframework.validation.BindingResult; 13 | import org.springframework.validation.FieldError; 14 | import org.springframework.web.bind.annotation.*; 15 | 16 | import javax.validation.Valid; 17 | import java.security.Principal; 18 | import java.util.List; 19 | 20 | @Controller 21 | @RequiredArgsConstructor 22 | public class CartController { 23 | 24 | private final CartService cartService; 25 | 26 | // 장바구니에 상품을 담는 로직 27 | @PostMapping(value = "/cart") 28 | public @ResponseBody 29 | ResponseEntity order(@RequestBody @Valid CartItemDto cartItemDto, BindingResult bindingResult, Principal principal){ 30 | 31 | if(bindingResult.hasErrors()){ // cartItemDto 객체에 데이터 바인딩 시 에러있는지 검사 32 | StringBuilder sb = new StringBuilder(); 33 | List fieldErrors = bindingResult.getFieldErrors(); 34 | 35 | for (FieldError fieldError : fieldErrors) { 36 | sb.append(fieldError.getDefaultMessage()); 37 | } 38 | 39 | return new ResponseEntity(sb.toString(), HttpStatus.BAD_REQUEST); 40 | } 41 | 42 | String email = principal.getName(); 43 | Long cartItemId; 44 | 45 | try { 46 | cartItemId = cartService.addCart(cartItemDto, email); //dto -> entity 47 | } catch(Exception e){ 48 | return new ResponseEntity(e.getMessage(), HttpStatus.BAD_REQUEST); // 장바구니에 잘 안담겼으면 404 49 | } 50 | 51 | return new ResponseEntity(cartItemId, HttpStatus.OK); // 장바구니에 상품이 잘 담기면 200 52 | } 53 | 54 | // 유저 이메일을 받아와서, 카트 데이터들을 받아온다 55 | // 장바구니 get 페이지 56 | @GetMapping(value = "/cart") 57 | public String orderHist(Principal principal, Model model){ 58 | List cartDetailList = cartService.getCartList(principal.getName()); 59 | model.addAttribute("cartItems", cartDetailList); 60 | return "cart/cartList"; 61 | } 62 | 63 | // 장바구니 상품의 개수 수정 patch 64 | @PatchMapping(value = "/cartItem/{cartItemId}") 65 | public @ResponseBody ResponseEntity updateCartItem(@PathVariable("cartItemId") Long cartItemId, int count, Principal principal){ 66 | 67 | if(count <= 0){ // 수량 0개 이하로 요청할 경우 오류 ㄲ 68 | return new ResponseEntity("최소 1개 이상 담아주세요", HttpStatus.BAD_REQUEST); 69 | } else if(!cartService.validateCartItem(cartItemId, principal.getName())){ // cartService 에서 검증 로직 발동! 70 | return new ResponseEntity("수정 권한이 없습니다.", HttpStatus.FORBIDDEN); 71 | } 72 | 73 | cartService.updateCartItemCount(cartItemId, count); // 다 되면은 수정 74 | return new ResponseEntity(cartItemId, HttpStatus.OK); // 응답 리턴 75 | } 76 | 77 | // 장바구니에서 삭제할 요소 제거 delete 78 | @DeleteMapping(value = "/cartItem/{cartItemId}") 79 | public @ResponseBody ResponseEntity deleteCartItem(@PathVariable("cartItemId") Long cartItemId, Principal principal){ 80 | 81 | if(!cartService.validateCartItem(cartItemId, principal.getName())){// cartService 에서 검증 로직 발동! 82 | return new ResponseEntity("수정 권한이 없습니다.", HttpStatus.FORBIDDEN); 83 | } 84 | 85 | cartService.deleteCartItem(cartItemId); // 다 되면은 삭제 86 | return new ResponseEntity(cartItemId, HttpStatus.OK); // 응답 리턴 87 | } 88 | 89 | // 장바구니 상품 수량 반영 (주문하면은 빼야됨) post 90 | @PostMapping(value = "/cart/orders") 91 | public @ResponseBody ResponseEntity orderCartItem(@RequestBody CartOrderDto cartOrderDto, Principal principal){ 92 | // orderDto 에 여러 Order 들이 있다. 93 | // 이 많은 주문들을 반영해야 하려고 새로 메소드를 만들었다. (patch 는 id 값을 받아와서 수정을 해준거고) 94 | List cartOrderDtoList = cartOrderDto.getCartOrderDtoList(); 95 | 96 | if(cartOrderDtoList == null || cartOrderDtoList.size() == 0){ 97 | return new ResponseEntity("주문할 상품을 선택해주세요", HttpStatus.FORBIDDEN); 98 | } 99 | 100 | for (CartOrderDto cartOrder : cartOrderDtoList) { 101 | if(!cartService.validateCartItem(cartOrder.getCartItemId(), principal.getName())){ 102 | return new ResponseEntity("주문 권한이 없습니다.", HttpStatus.FORBIDDEN); 103 | } 104 | } 105 | 106 | Long orderId = cartService.orderCartItem(cartOrderDtoList, principal.getName()); // 상품 주문 + 장바구니에서 주문한거 제거 107 | return new ResponseEntity(orderId, HttpStatus.OK); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/controller/ItemController.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.controller; 2 | 3 | import kr.co.codewiki.shoppingmall.dto.ItemFormDto; 4 | import kr.co.codewiki.shoppingmall.dto.ItemSearchDto; 5 | import kr.co.codewiki.shoppingmall.entity.Item; 6 | import kr.co.codewiki.shoppingmall.service.ItemService; 7 | import lombok.RequiredArgsConstructor; 8 | 9 | import org.springframework.data.domain.Page; 10 | import org.springframework.data.domain.PageRequest; 11 | import org.springframework.data.domain.Pageable; 12 | 13 | import org.springframework.stereotype.Controller; 14 | import org.springframework.ui.Model; 15 | import org.springframework.validation.BindingResult; 16 | import org.springframework.web.bind.annotation.GetMapping; 17 | import org.springframework.web.bind.annotation.PathVariable; 18 | import org.springframework.web.bind.annotation.PostMapping; 19 | import org.springframework.web.bind.annotation.RequestParam; 20 | import org.springframework.web.multipart.MultipartFile; 21 | 22 | import javax.persistence.EntityNotFoundException; 23 | import javax.validation.Valid; 24 | import java.util.List; 25 | import java.util.Optional; 26 | 27 | 28 | @Controller 29 | @RequiredArgsConstructor 30 | public class ItemController { 31 | 32 | private final ItemService itemService; 33 | 34 | // 상품 등록 get 페이지 35 | @GetMapping("/admin/item/new") 36 | public String itemForm(Model model){ 37 | model.addAttribute("itemFormDto", new ItemFormDto()); 38 | return "item/itemForm"; 39 | } 40 | 41 | 42 | // 상품 등록 post 43 | @PostMapping(value = "/admin/item/new") 44 | public String itemNew(@Valid ItemFormDto itemFormDto, BindingResult bindingResult, 45 | Model model, @RequestParam("itemImgFile") List itemImgFileList){ 46 | 47 | // 상품 등록 시 필수 값이 없을 때 애러 발생 48 | if(bindingResult.hasErrors()){ 49 | return "item/itemForm"; // 에러가 발생하면 상품 등록 get 페이지로 이동 50 | } 51 | 52 | // 상품 등록 시 첫번째 이미지가 없으면 애러 발생 (첫 번째 이미지는 대표 상품 이미지여서 꼭 있어야함!) 53 | if(itemImgFileList.get(0).isEmpty() && itemFormDto.getId() == null){ 54 | 55 | model.addAttribute("errorMessage", "첫번째 상품 이미지는 필수 입력 값 입니다."); 56 | return "item/itemForm";// 에러가 발생하면 상품 등록 get 페이지로 이동 57 | } 58 | 59 | 60 | try { // 상품 저장 로직 호출 61 | itemService.saveItem(itemFormDto, itemImgFileList); // itemFormDto: 상품 정보, itemImgFileList: 상품 이미지 정보들 리스트 62 | } 63 | catch (Exception e){ 64 | model.addAttribute("errorMessage", "상품 등록 중 에러가 발생하였습니다."); 65 | return "item/itemForm"; 66 | } 67 | 68 | return "redirect:/"; // 메인 페이지로 리다이렉트 69 | } 70 | 71 | // 상품 수정 get 페이지 72 | @GetMapping(value = "/admin/item/{itemId}") 73 | public String itemDtl(@PathVariable("itemId") Long itemId, Model model){ 74 | 75 | try { 76 | ItemFormDto itemFormDto = itemService.getItemDtl(itemId); 77 | model.addAttribute("itemFormDto", itemFormDto); // 조회한 상품 데이터를 model 에 담아서 뷰로 전달함 78 | } 79 | 80 | catch(EntityNotFoundException e){ // 상품 엔티티가 존재하지 않으면은 에러메세지 + 상품 등록페이지로 다시 ㄱㄱ 81 | model.addAttribute("errorMessage", "존재하지 않는 상품 입니다."); 82 | model.addAttribute("itemFormDto", new ItemFormDto()); 83 | return "item/itemForm"; 84 | } 85 | 86 | return "item/itemForm"; 87 | } 88 | 89 | // 상품 수정 post 90 | @PostMapping(value = "/admin/item/{itemId}") 91 | public String itemUpdate(@Valid ItemFormDto itemFormDto, BindingResult bindingResult, 92 | @RequestParam("itemImgFile") List itemImgFileList, Model model){ 93 | 94 | // 상품 수정 시 필수 값이 없을 때 애러 발생 95 | if(bindingResult.hasErrors()){ 96 | return "item/itemForm"; // 에러가 발생하면 상품 수정 get 페이지로 이동 97 | } 98 | 99 | // 상품 수정 시 첫번째 이미지가 없으면 애러 발생 (첫 번째 이미지는 대표 상품 이미지여서 꼭 있어야함!) 100 | if(itemImgFileList.get(0).isEmpty() && itemFormDto.getId() == null){ 101 | model.addAttribute("errorMessage", "첫번째 상품 이미지는 필수 입력 값 입니다."); 102 | return "item/itemForm";// 에러가 발생하면 상품 수정 get 페이지로 이동 103 | } 104 | 105 | try {// 상품 수정 로직 호출 106 | itemService.updateItem(itemFormDto, itemImgFileList); // itemFormDto: 상품 정보, itemImgFileList: 상품 이미지 정보들 리스트 107 | } catch (Exception e){ 108 | model.addAttribute("errorMessage", "상품 수정 중 에러가 발생하였습니다."); 109 | return "item/itemForm"; 110 | } 111 | 112 | return "redirect:/"; // 메인 페이지로 리다이렉트 113 | } 114 | 115 | // 상품 관리 화면 get 페이지 116 | @GetMapping(value = {"/admin/items", "/admin/items/{page}"}) // url 에 페이지 번호가 없는거랑, 페이지 번호가 있는거 둘 다 매핑해줌 117 | public String itemManage(ItemSearchDto itemSearchDto, @PathVariable("page") Optional page, Model model){ 118 | 119 | // 한 페이지 당 3개만 보여줄거임 120 | Pageable pageable = PageRequest.of(page.isPresent() ? page.get() : 0, 3); 121 | // 0: 조회할 페이지 번호, 3: 한 번에 가지고 올 데이터 수 122 | // url 에 페이지 번호가 있으면은 그 페이지를 보여주고, url 에 번호가 없으면 0 페이지 보여줌 123 | 124 | Page items = itemService.getAdminItemPage(itemSearchDto, pageable); // itemSearchDto: 조회 조건 pageable: 페이징 정보 125 | 126 | model.addAttribute("items", items); // item: 조회한 상품 데이터 127 | model.addAttribute("itemSearchDto", itemSearchDto); // 페이지 전환 시 기존 검색 조건을 유지한 채 이동할 수 있게 뷰에 전달 128 | model.addAttribute("maxPage", 5); // 최대 5개의 이동할 페이지 번호를 보여줌줌 129 | 130 | return "item/itemMng"; // 조회한 상품 데이터 전달받는 페이지 131 | } 132 | 133 | // 상품 상세 get 페이지 134 | @GetMapping(value = "/item/{itemId}") 135 | public String itemDtl(Model model, @PathVariable("itemId") Long itemId){ 136 | 137 | // getItemDtl: service 에 있는 메소드. 상품이랑, 상품이미지의 entity -> dto 로 바꾸기만 하는 service 138 | ItemFormDto itemFormDto = itemService.getItemDtl(itemId); 139 | 140 | model.addAttribute("item", itemFormDto); 141 | 142 | return "item/itemDtl"; 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/controller/MainController.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.controller; 2 | 3 | import kr.co.codewiki.shoppingmall.dto.ItemSearchDto; 4 | import kr.co.codewiki.shoppingmall.dto.MainItemDto; 5 | import kr.co.codewiki.shoppingmall.service.ItemService; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.data.domain.Page; 8 | import org.springframework.data.domain.PageRequest; 9 | import org.springframework.data.domain.Pageable; 10 | import org.springframework.stereotype.Controller; 11 | import org.springframework.ui.Model; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | 14 | import java.util.Optional; 15 | // 회원가입 후 보여줄 메인 페이지 16 | 17 | @Controller 18 | @RequiredArgsConstructor 19 | public class MainController { 20 | 21 | private final ItemService itemService; 22 | 23 | // 메인 페이지에 상품 데이터를 보여줄 get 페이지 24 | @GetMapping(value = "/") 25 | public String main(ItemSearchDto itemSearchDto, Optional page, Model model){ 26 | 27 | Pageable pageable = PageRequest.of(page.isPresent() ? page.get() : 0, 6); // 처음 페이지(0) 을 보여주고, 한 페이지에 6개 보여줄거임 28 | Page items = itemService.getMainItemPage(itemSearchDto, pageable); 29 | 30 | model.addAttribute("items", items); 31 | model.addAttribute("itemSearchDto", itemSearchDto); 32 | model.addAttribute("maxPage", 5); 33 | 34 | return "main"; 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/controller/MemberController.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.controller; 2 | 3 | import kr.co.codewiki.shoppingmall.dto.MemberFormDto; 4 | import kr.co.codewiki.shoppingmall.entity.Member; 5 | import kr.co.codewiki.shoppingmall.service.MemberService; 6 | import lombok.Getter; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.security.crypto.password.PasswordEncoder; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.ui.Model; 11 | import org.springframework.validation.BindingResult; 12 | import org.springframework.web.bind.annotation.GetMapping; 13 | import org.springframework.web.bind.annotation.PostMapping; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | 16 | import javax.validation.Valid; 17 | // 회원가입 controller 18 | 19 | @Controller 20 | @RequiredArgsConstructor // @Autowired 대신 21 | @RequestMapping("/members") 22 | public class MemberController { 23 | 24 | private final MemberService memberService; // final 로 해야함 25 | private final PasswordEncoder passwordEncoder; 26 | 27 | // 회원가입 FORM 28 | @GetMapping("/new") 29 | public String memberForm(Model model){ 30 | 31 | MemberFormDto memberFormDto = new MemberFormDto(); 32 | 33 | model.addAttribute("memberFormDto", memberFormDto); 34 | return "member/memberForm"; 35 | } 36 | 37 | // 회원가입 성공 시 메인으로 리다이렉트 38 | // 실패하면 다시 회원가입 페이지로 돌아감 39 | @PostMapping("/new") 40 | public String newMember (@Valid MemberFormDto memberFormDto, BindingResult bindingResult, Model model){ 41 | // Valid 검증하려는 객체(memberFormDto) 앞에 붙임 42 | // 검증 완료 되면은 결과를 bindingResult(TF) 에다가 담아줌 43 | 44 | //BindingResult는 검증 오류가 발생할 경우 오류 내용을 보관하는 스프링 프레임워크에서 제공하는 객체입니다. 45 | // bindingResult.addError 를 해줘야 하지만, DTO 에서 어노테이션으로 처리했기 때문에 페이지 리턴만 해주면 된다!!! 46 | 47 | //출처: https://jaimemin.tistory.com/1874 [꾸준함] 48 | 49 | 50 | if (bindingResult.hasErrors()){ // bindingResult.hasErrors 를 호출해서, 에러가 있으면 회원가입 페이지로 return 함 51 | return "member/memberForm"; 52 | } 53 | try{ 54 | Member member = Member.createMember(memberFormDto, passwordEncoder); // member: entity, createMember 에서 dto-> entity 55 | memberService.saveMember(member); // saveMember: 중복가입막는거 처리해주고 save 56 | 57 | } catch (IllegalStateException e){ 58 | model.addAttribute("errorMessage", e.getMessage()); // 뷰 페이지에다가 이거 보여주고 59 | return "member/memberForm"; // 여기로 return 한다고?? 60 | } 61 | return "redirect:/"; // 다 되면은 리다이렉트???! 62 | } 63 | 64 | // 로그인 페이지 65 | @GetMapping("/login") 66 | public String loginMember(){ 67 | return "member/memberLoginForm"; 68 | } 69 | 70 | // 로그인 페이지_뭔가 오류났을 때 71 | public String loginError(Model model){ 72 | model.addAttribute("loginErrorMsg","아이디 또는 비번 확인ㄱ"); 73 | return "member/memberLoginForm"; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/controller/OrderController.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.controller; 2 | 3 | import kr.co.codewiki.shoppingmall.dto.OrderDto; 4 | import kr.co.codewiki.shoppingmall.dto.OrderHistDto; 5 | import kr.co.codewiki.shoppingmall.service.OrderService; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.data.domain.Page; 8 | import org.springframework.data.domain.PageRequest; 9 | import org.springframework.data.domain.Pageable; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.stereotype.Controller; 13 | import org.springframework.ui.Model; 14 | import org.springframework.validation.BindingResult; 15 | import org.springframework.validation.FieldError; 16 | import org.springframework.web.bind.annotation.*; 17 | 18 | import javax.validation.Valid; 19 | import java.security.Principal; 20 | import java.util.List; 21 | import java.util.Optional; 22 | 23 | // 주문 관련 요청들을 처리 24 | // 비동기 방식 사용_상품 주문에서 웹 페이지의 새로 고침 없이 서버에서 주문을 요청하기 위해서 25 | @Controller 26 | @RequiredArgsConstructor 27 | public class OrderController { 28 | 29 | private final OrderService orderService; 30 | 31 | // 상품 구매 post (REST Api) 32 | @PostMapping(value = "/order") // @ResponseBody, @RequestBody: 비동기 처리를 할 때 사용 33 | public @ResponseBody ResponseEntity order(@RequestBody @Valid OrderDto orderDto, BindingResult bindingResult, Principal principal){ 34 | // @ResponseBody: Http 요청의 본문 body 에 담긴 내용을 자바 객체로 전달 35 | // @RequestBody: 자바 객체를 HTTP 요청의 body 로 전달 36 | // Principal: 파라미터로 넘어온 principal 객체에 데이터를 넣으면, 해당 객체에 직접 접근할 수 있다. (@Controller 가 선언되어있어야함) 37 | 38 | // 주문 정보를 받는 orderDto 객체에 데이터 바인딩 시 에러가 있는지 검사 39 | if(bindingResult.hasErrors()){ 40 | StringBuilder sb = new StringBuilder(); 41 | List fieldErrors = bindingResult.getFieldErrors(); 42 | 43 | for (FieldError fieldError : fieldErrors) { 44 | sb.append(fieldError.getDefaultMessage()); 45 | } 46 | 47 | return new ResponseEntity(sb.toString(), HttpStatus.BAD_REQUEST); 48 | // 에러 정보는 ResponseEntity 객체에 담겨져서 전달됨. 그니까 에러정보는 entity 49 | } 50 | 51 | 52 | // 현재 로그인한 회원의 이메일 정보 (로그인 유저 정보 얻을려고) 53 | String email = principal.getName(); // principal 객체에서 현재 로그인한 회원의 이메일 정보를 조회함 54 | Long orderId; 55 | 56 | try { //주문 로직 호출!!! 57 | orderId = orderService.order(orderDto, email); // orderDto: 화면으로 넘어온 주문 정보, email: 회원 이메일 정보 58 | } catch(Exception e){ 59 | return new ResponseEntity(e.getMessage(), HttpStatus.BAD_REQUEST); 60 | } 61 | 62 | return new ResponseEntity(orderId, HttpStatus.OK); // http 응답 코드를 반환! 63 | } 64 | 65 | // 구매이력 조회 get 페이지 66 | @GetMapping(value = {"/orders", "/orders/{page}"}) 67 | public String orderHist(@PathVariable("page") Optional page, Principal principal, Model model){ 68 | 69 | Pageable pageable = PageRequest.of(page.isPresent() ? page.get() : 0, 4); // 한 번에 가지고 올 주문 개수 4개!!! 70 | 71 | // 현재 로그인한 회원은 화면에 전달한 주문 목록 데이터를 리턴 값으로 받음 (이메일과 페이징 객체를 파라미터로 전달) 72 | Page ordersHistDtoList = orderService.getOrderList(principal.getName(), pageable); 73 | // ordersHistDtoList: 주문 목록 데이터 74 | // principal.getName(): 이메일(현재 로그인한 회원) 75 | // pageable: 페이징 객체? 76 | 77 | model.addAttribute("orders", ordersHistDtoList); 78 | model.addAttribute("page", pageable.getPageNumber()); 79 | model.addAttribute("maxPage", 5); 80 | 81 | return "order/orderHist"; 82 | } 83 | 84 | // 주문 취소를 위한 컨트롤러 85 | // @ResponseBody 는 return 을 json 처럼 되게 해줌 == @RestController 86 | @PostMapping("/order/{orderId}/cancel") 87 | public @ResponseBody ResponseEntity cancelOrder(@PathVariable("orderId") Long orderId , Principal principal){ 88 | 89 | if(!orderService.validateOrder(orderId, principal.getName())){ 90 | return new ResponseEntity("주문 취소 권한이 없습니다.", HttpStatus.FORBIDDEN); 91 | } 92 | 93 | orderService.cancelOrder(orderId); 94 | return new ResponseEntity(orderId, HttpStatus.OK); 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/controller/ThymeleafExController.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.controller; 2 | 3 | import kr.co.codewiki.shoppingmall.dto.ItemDto; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.ui.Model; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | 9 | import java.time.LocalDateTime; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | @Controller 14 | @RequestMapping("/thymeleaf") // 클라이언트의 요청에 대해서 어떤 컨트롤러가 처리할지 매핑하는 어노테이션 (/thymeleaf 로 오는 요청을 controller 가 처리하도록 해줌) 15 | public class ThymeleafExController { 16 | 17 | @GetMapping("/ex02") 18 | public String thymeleafExample02(Model model){ // model: view 에 데이터를 넘길 때 사용 19 | ItemDto itemDto = ItemDto.builder() 20 | .itemDetail("상품 상세 설명") 21 | .itemNm("테스트 상품1") 22 | .price(10000) 23 | .regTime(LocalDateTime.now()) 24 | .build(); 25 | 26 | model.addAttribute("itemDto",itemDto); 27 | return "thymeleafEx/thymeleafEx02"; 28 | } 29 | 30 | @GetMapping("/ex03") 31 | public String thymeleafExample03(Model model){ // model: view 에 데이터를 넘길 때 사용 32 | 33 | List itemDtos = new ArrayList<>(); 34 | 35 | for (int i=1; i<=10; i++){ 36 | ItemDto itemDto = ItemDto.builder() 37 | .itemDetail("상품 상세 설명"+i) 38 | .itemNm("테스트 상품"+i) 39 | .price(1000*i) 40 | .regTime(LocalDateTime.now()) 41 | .build(); 42 | 43 | itemDtos.add(itemDto); 44 | } 45 | 46 | model.addAttribute("itemDtos",itemDtos); 47 | return "thymeleafEx/thymeleafEx03"; 48 | } 49 | 50 | @GetMapping(value = "/ex07") 51 | public String thymeleafExample07(){ 52 | return "thymeleafEx/thymeleafEx07"; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/dto/CartDetailDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | // 장바구니 조회 페이지에 전달 9 | // 장바구니에 들어있는 상품들을 조회하기 위해서! 10 | @Getter @Setter 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class CartDetailDto { 14 | 15 | private Long cartItemId; //장바구니 상품 아이디 16 | 17 | private String itemNm; //상품명 18 | 19 | private int price; //상품 금액 20 | 21 | private int count; //수량 22 | 23 | private String imgUrl; //상품 이미지 경로 24 | 25 | // public CartDetailDto(Long cartItemId, String itemNm, int price, int count, String imgUrl){ // 장바구니에 전달할 애들인데, 그냥 어노테이션 씀 26 | // this.cartItemId = cartItemId; 27 | // this.itemNm = itemNm; 28 | // this.price = price; 29 | // this.count = count; 30 | // this.imgUrl = imgUrl; 31 | // } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/dto/CartItemDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import javax.validation.constraints.Min; 7 | import javax.validation.constraints.NotNull; 8 | // 장바구니에 담을 상품의 id 와 수량 전달 받음 9 | @Getter @Setter 10 | public class CartItemDto { 11 | 12 | @NotNull(message = "상품 아이디는 필수 입력 값 입니다.") 13 | private Long itemId; 14 | 15 | @Min(value = 1, message = "최소 1개 이상 담아주세요") 16 | private int count; 17 | 18 | } -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/dto/CartOrderDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import java.util.List; 7 | 8 | // 주문할 상품 데이터 전달용 9 | @Getter 10 | @Setter 11 | public class CartOrderDto { 12 | 13 | private Long cartItemId; 14 | 15 | private List cartOrderDtoList; 16 | 17 | } -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/dto/ItemDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.dto; 2 | 3 | import lombok.*; 4 | 5 | import java.time.LocalDateTime; 6 | 7 | @Getter 8 | @AllArgsConstructor 9 | @NoArgsConstructor 10 | @Builder 11 | @ToString 12 | public class ItemDto { 13 | 14 | private Long id; 15 | 16 | private String itemNm; 17 | 18 | private Integer price; 19 | 20 | private String itemDetail; 21 | 22 | private String sellStatCd; 23 | 24 | private LocalDateTime regTime; 25 | 26 | private LocalDateTime updateTime; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/dto/ItemFormDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.dto; 2 | 3 | import kr.co.codewiki.shoppingmall.constant.ItemSellStatus; 4 | import kr.co.codewiki.shoppingmall.entity.Item; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import org.modelmapper.ModelMapper; 8 | 9 | import javax.validation.constraints.NotBlank; 10 | import javax.validation.constraints.NotNull; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | // 여기에는 상품이랑, 상품 이미지가 다 있다. 15 | @Getter @Setter 16 | public class ItemFormDto { 17 | 18 | private Long id; 19 | 20 | @NotBlank(message = "상품명은 필수 입력 값입니다.") 21 | private String itemNm; 22 | 23 | @NotNull(message = "가격은 필수 입력 값입니다.") 24 | private Integer price; 25 | 26 | @NotBlank(message = "상품 상세는 필수 입력 값입니다.") 27 | private String itemDetail; 28 | 29 | @NotNull(message = "재고는 필수 입력 값입니다.") 30 | private Integer stockNumber; 31 | 32 | private ItemSellStatus itemSellStatus; 33 | 34 | // 상품 저장 후 수정할 때 상품 이미지 정보를 저장하는 리스트 35 | private List itemImgDtoList = new ArrayList<>(); 36 | 37 | // 상품의 이미지 아이디를 저장하는 리스트 38 | // 상품 등록 전에는 이미지가 없으니까 비어있음(이미지도 공백, 아이디도 공백!) 39 | // 그냥 수정할 때 이미지 아이디 저장해둘 용도 40 | private List itemImgIds = new ArrayList<>(); 41 | 42 | // ModelMapper: 서로 다른 클래스의 값을! 필드의 이름과 자료형이 같을 때! getter, setter 를 통해 값을 복사해서 객체를 반환함 43 | private static ModelMapper modelMapper = new ModelMapper(); 44 | 45 | 46 | public Item createItem(){ 47 | return modelMapper.map(this, Item.class); 48 | // this_entity 를 dto 로 반환함 49 | } 50 | 51 | public static ItemFormDto of(Item item){ 52 | return modelMapper.map(item,ItemFormDto.class); 53 | // entity 를 파라미터로 받아서 dto 로 반환함 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/dto/ItemImgDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.dto; 2 | 3 | import kr.co.codewiki.shoppingmall.entity.ItemImg; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import org.modelmapper.ModelMapper; 7 | 8 | @Getter 9 | @Setter 10 | public class ItemImgDto { 11 | 12 | private Long id; 13 | 14 | private String imgName; 15 | 16 | private String oriImgName; 17 | 18 | private String imgUrl; 19 | 20 | private String repImgYn; 21 | 22 | // entity <-> dto 과정이 너무 복잡하고 귀찮아서 이 라이브러리를 사용함.(의존성 추가 해야 함) 23 | private static ModelMapper modelMapper = new ModelMapper(); 24 | 25 | // ModelMapper: 서로 다른 클래스의 값을! 필드의 이름과 자료형이 같을 때! getter, setter 를 통해 값을 복사해서 객체를 반환함 26 | public static ItemImgDto of(ItemImg itemImg) { 27 | return modelMapper.map(itemImg,ItemImgDto.class); 28 | // entity 를 파라미터로 받아서 dto 로 반환함 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/dto/ItemSearchDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.dto; 2 | 3 | import kr.co.codewiki.shoppingmall.constant.ItemSellStatus; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | // 상품 데이터 조회 시 상품 조건을 갖고 있음 8 | @Getter 9 | @Setter 10 | public class ItemSearchDto { 11 | 12 | // 현재 시간과 상품 등록일을 비교해서 상품 데이터를 조회함 13 | private String searchDateType; 14 | /* 15 | all: 상품 등록일 전체 16 | 1d/1w/1m/6m: 최근 하루/1주/한달/6개월 동안 등록된 상품 17 | */ 18 | 19 | // 상품의 판매상태를 기준으로 상품 데이터를 조회 20 | private ItemSellStatus searchSellStatus; 21 | 22 | // 상품을 조회할 때 어떤 유형으로 조회할 지 선택 23 | private String searchBy; 24 | /* 25 | itemNm: 상품명 26 | createBy: 상품 등록자 아이디 27 | */ 28 | 29 | // 조회할 검색어를 저장할 변수 30 | private String searchQuery = ""; 31 | /* 32 | itemNm: 상품명 기준 검색 33 | createBy: 상품 등록자 아이디 검색 34 | */ 35 | 36 | } 37 | 38 | /* 39 | * 조회 조건이 엄청 복잡함!! (상품 등록일, 상품 판매 상태, 상품명, 상품 등록자 id...) 40 | * 41 | * 이렇게 조회 조건이 복잡한 화면은 Querydsl 을 이용한다. 42 | * 1. 조건에 맞는 쿼리를 동적으로 쉽게 생성 가능 43 | * 2. 비슷한 쿼리 재활용 가능 44 | * 3. IDE 가 문법 오류 바로 잡아줌 (쿼리쓰면은 db 문법이기 때문에 안잡아줌) 45 | * */ -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/dto/MainItemDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.dto; 2 | 3 | 4 | import com.querydsl.core.annotations.QueryProjection; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | // 메인 페이지에서 상품을 보여줄 때 사용할 dto 9 | @Getter 10 | @Setter 11 | public class MainItemDto { 12 | 13 | private Long id; 14 | 15 | private String itemNm; 16 | 17 | private String itemDetail; 18 | 19 | private String imgUrl; 20 | 21 | private Integer price; 22 | 23 | 24 | @QueryProjection // entity 로 원래 바꿨었는데, 이 어노테이션을 쓰면은 dto 로 객체를 변환할 수 있다. 25 | public MainItemDto(Long id, String itemNm, String itemDetail, String imgUrl,Integer price){ 26 | this.id = id; 27 | this.itemNm = itemNm; 28 | this.itemDetail = itemDetail; 29 | this.imgUrl = imgUrl; 30 | this.price = price; 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/dto/MemberFormDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.dto; 2 | 3 | import lombok.*; 4 | import org.hibernate.validator.constraints.Length; 5 | 6 | import javax.validation.constraints.Email; 7 | import javax.validation.constraints.NotBlank; 8 | import javax.validation.constraints.NotEmpty; 9 | 10 | @Getter 11 | @Builder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @Setter 15 | public class MemberFormDto { 16 | 17 | // (validated annotation) 18 | @NotBlank(message = "이름입력ㄱ") // null, .length=0, "" 19 | private String name; 20 | 21 | @NotEmpty(message = "이메일입력ㄱ") // null, .length=0 22 | @Email // 이메일 형식? 23 | private String email; 24 | 25 | @NotEmpty(message = "비번입력ㄱ") // null, .length=0 26 | @Length(min=8, max=16, message = "8~16ㄱ") 27 | private String password; 28 | 29 | @NotEmpty(message = "주소입력ㄱ") // null, .length=0 30 | private String address; 31 | 32 | // 홍팍은 여기다가 toEntity 메소드 만들어서 33 | // new 해서 파라미터로 저기저거 선언되어져있는 저거 받아와서 34 | // entity 를 return 해줬었ㅇㅓ 35 | 36 | // 근데!(Member.java) 37 | 38 | 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/dto/OrderDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import javax.validation.constraints.Max; 7 | import javax.validation.constraints.Min; 8 | import javax.validation.constraints.NotNull; 9 | 10 | // 주문할 상품의 아이디와 주문 수량을 전달받을 dto 임 (상품 상세 페이지에서 전달받음) 11 | @Getter 12 | @Setter 13 | public class OrderDto { 14 | 15 | @NotNull(message = "상품 아이디는 필수 입력 값입니다.") 16 | private Long itemId; 17 | 18 | @Min(value = 1, message = "최소 주문 수량은 1개 입니다.") 19 | @Max(value = 999, message = "최대 주문 수량은 999개 입니다.") 20 | private int count; 21 | 22 | } -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/dto/OrderHistDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.dto; 2 | 3 | import kr.co.codewiki.shoppingmall.constant.OrderStatus; 4 | import kr.co.codewiki.shoppingmall.entity.Order; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import java.time.format.DateTimeFormatter; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | // 주문 정보를 담을 dto 13 | @Getter 14 | @Setter 15 | public class OrderHistDto { 16 | 17 | public OrderHistDto(Order order){ 18 | this.orderId = order.getId(); 19 | this.orderDate = order.getOrderDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); 20 | this.orderStatus = order.getOrderStatus(); 21 | } 22 | 23 | private Long orderId; //주문아이디 24 | private String orderDate; //주문날짜 25 | private OrderStatus orderStatus; //주문 상태 26 | 27 | private List orderItemDtoList = new ArrayList<>(); 28 | 29 | //주문 상품리스트 30 | public void addOrderItemDto(OrderItemDto orderItemDto){ // '현재 주문한 상품 정보' 를 주문 상품 리스트에 담음음 31 | orderItemDtoList.add(orderItemDto); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/dto/OrderItemDto.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.dto; 2 | 3 | import kr.co.codewiki.shoppingmall.entity.OrderItem; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | // 주문 내역을 조회할 수 있는 페이지 구현 dto 8 | // 현재 주문한 상품 정보를 볼 수 있음 9 | @Getter 10 | @Setter 11 | public class OrderItemDto { 12 | 13 | public OrderItemDto(OrderItem orderItem, String imgUrl){ // 주문상품, 이미지경로를 파라미터로 받음 14 | this.itemNm = orderItem.getItem().getItemNm(); 15 | this.count = orderItem.getCount(); 16 | this.orderPrice = orderItem.getOrderPrice(); 17 | this.imgUrl = imgUrl; 18 | } 19 | 20 | private String itemNm; //상품명 21 | private int count; //주문 수량 22 | 23 | private int orderPrice; //주문 금액 24 | private String imgUrl; //상품 이미지 경로 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/entity/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.entity; 2 | 3 | import lombok.Getter; 4 | import org.springframework.data.annotation.CreatedBy; 5 | import org.springframework.data.annotation.LastModifiedBy; 6 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 7 | 8 | import javax.persistence.Column; 9 | import javax.persistence.EntityListeners; 10 | import javax.persistence.MappedSuperclass; 11 | 12 | // 등록한사람, 수정한사람만 있는 entity + 상속받은 등록일 수정일 entity 도 있음 13 | @EntityListeners(value = {AuditingEntityListener.class}) 14 | @MappedSuperclass 15 | @Getter 16 | public abstract class BaseEntity extends BaseTimeEntity{ 17 | 18 | @CreatedBy 19 | @Column(updatable = false) 20 | private String createdBy; 21 | 22 | @LastModifiedBy 23 | private String modifiedBy; 24 | 25 | } -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/entity/BaseTimeEntity.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.data.annotation.CreatedDate; 6 | import org.springframework.data.annotation.LastModifiedDate; 7 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 8 | 9 | import javax.persistence.Column; 10 | import javax.persistence.EntityListeners; 11 | import javax.persistence.MappedSuperclass; 12 | import java.time.LocalDateTime; 13 | 14 | // 등록일, 수정일만 있는 entity 15 | @EntityListeners(value = {AuditingEntityListener.class}) 16 | @MappedSuperclass // 공통 매핑 정보가 필요할 때 사용 (자식 클래스에 매핑 정보만 제공함) 17 | @Getter @Setter 18 | public abstract class BaseTimeEntity { 19 | 20 | @CreatedDate // 엔티티 생성 시 시간 자동 저장 21 | @Column(updatable = false) 22 | private LocalDateTime regTime; 23 | 24 | @LastModifiedDate // 엔티티 수정 시 시간 자동 저장 25 | private LocalDateTime updateTime; 26 | 27 | } -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/entity/Cart.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.entity; 2 | 3 | 4 | import lombok.*; 5 | 6 | import javax.persistence.*; 7 | 8 | @Getter 9 | @Setter 10 | @Entity 11 | @Table(name = "cart") 12 | @ToString 13 | @Builder 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class Cart extends BaseEntity{ 17 | 18 | @Id 19 | @Column(name="cart_id") 20 | @GeneratedValue(strategy = GenerationType.AUTO) 21 | private Long id; 22 | 23 | // @OneToOne(fetch = FetchType.EAGER) // 즉시 로딩: 회원 엔티티를 조회할 때 장바구니 엔티티도 같이 조회함 24 | @OneToOne(fetch = FetchType.LAZY) // 지연 로딩 25 | @JoinColumn(name = "member_id") // 외래키 지정! 26 | private Member member; 27 | 28 | // 카트에 유저 할당하여 넣어줌 29 | // 회원 한명당 장바구니를 하나씩 갖기 때문에 할당해주는거임 30 | public static Cart createCart(Member member){ 31 | Cart cart = new Cart(); 32 | cart.setMember(member); 33 | return cart; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/entity/CartItem.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import javax.persistence.*; 7 | 8 | @Entity 9 | @Getter @Setter 10 | @Table(name="cart_item") 11 | public class CartItem extends BaseEntity { 12 | 13 | @Id 14 | @GeneratedValue 15 | @Column(name="cart_item_id") 16 | private Long id; 17 | 18 | @ManyToOne(fetch = FetchType.LAZY) // 카트에 아이템 많이 담을 수 있어 19 | @JoinColumn(name = "cart_id") 20 | private Cart cart; 21 | 22 | @ManyToOne(fetch = FetchType.LAZY) // 장바구니 몇개 담을지! 23 | @JoinColumn(name = "item_id") 24 | private Item item; 25 | 26 | private int count; 27 | 28 | // 장바구니에 담을 상품 엔티티 생성 메소드 29 | public static CartItem createCartItem(Cart cart, Item item, int count) { 30 | CartItem cartItem = new CartItem(); 31 | cartItem.setCart(cart); 32 | cartItem.setItem(item); 33 | cartItem.setCount(count); 34 | return cartItem; 35 | } 36 | 37 | // 장바구니에 담을 상품 수량 증가 38 | public void addCount(int count){ 39 | this.count += count; 40 | } 41 | 42 | // 장바구니에 담을 수량 반영 43 | public void updateCount(int count){ 44 | this.count = count; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/entity/Item.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.entity; 2 | 3 | import kr.co.codewiki.shoppingmall.constant.ItemSellStatus; 4 | import kr.co.codewiki.shoppingmall.dto.ItemFormDto; 5 | import kr.co.codewiki.shoppingmall.exception.OutOfStockException; 6 | import lombok.*; 7 | 8 | import javax.persistence.*; 9 | import java.time.LocalDateTime; 10 | 11 | @Entity 12 | @Table(name = "item") 13 | @Getter 14 | @Setter 15 | @ToString 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | @Builder 19 | public class Item extends BaseEntity{ 20 | 21 | @Id 22 | @Column(name="item_id") 23 | @GeneratedValue(strategy = GenerationType.AUTO) 24 | private Long id; 25 | 26 | // @Column() 27 | @Column(nullable = false, length = 50) 28 | private String itemNm; // 상품명 29 | 30 | // @Column(name="price") 31 | @Column(name="price", nullable = false) 32 | private int price; 33 | 34 | // @Column 35 | @Column(nullable = false) 36 | private int stockNumber; // 재고수량 37 | 38 | @Lob 39 | @Column(nullable = false) 40 | // @Column 41 | private String itemDetail; // 상품 상세 설명 42 | 43 | @Enumerated(EnumType.STRING) 44 | private ItemSellStatus itemSellStatus; // 상품 판매 상태 45 | 46 | 47 | // 상품 데이터를 업데이트 하는 로직 생성 48 | public void updateItem(ItemFormDto itemFormDto){ // 49 | this.itemNm = itemFormDto.getItemNm(); 50 | this.price = itemFormDto.getPrice(); 51 | this.stockNumber = itemFormDto.getStockNumber(); 52 | this.itemDetail = itemFormDto.getItemDetail(); 53 | this.itemSellStatus = itemFormDto.getItemSellStatus(); 54 | } 55 | /* 56 | 엔티티 클래스에 비즈니스 로직을 추가하면 객체지향적으로 코딩을 할 수 있고, 코드를 재활용 할 수도 있다. 57 | 데이터 변경 포인트를 여기로 지정할 수 있움(데이터 수정 변경 여기서만 한다는 뜻) 58 | 엔티티 클래스에 비즈니스 로직(데이터 처리) 을 추가하면 59 | 1. 객체지향적으로 코딩을 할 수 있고, 60 | 2. 코드를 재활용 할 수도 있다. 61 | 3. 데이터 변경 포인트를 여기로 지정할 수 있움(데이터 수정 변경 여기서만 한다는 뜻) 62 | */ 63 | 64 | // 상품 주문 -> 상품 재고 감소 로직 생성 65 | public void removeStock(int stockNumber){ 66 | 67 | int restStock = this.stockNumber - stockNumber; // stockNumber: 상품의 재고 수량 restStock: 주문 후 남은 재고 수량 68 | 69 | if(restStock<0){ // 상품 재고가 주문 수량보다 작을 경우, 재고 부족 예외 발생 70 | throw new OutOfStockException("상품의 재고가 부족 합니다. (현재 재고 수량: " + this.stockNumber + ")"); 71 | } 72 | this.stockNumber = restStock; // 주문 후 남은 재고 수량 = 상품의 현재 재고 값 73 | } 74 | 75 | // 주문 취소 시 상품 개수 늘림 76 | public void addStock(int stockNumber){ 77 | this.stockNumber += stockNumber; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/entity/ItemImg.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import javax.persistence.*; 7 | 8 | @Entity 9 | @Table(name="item_img") 10 | @Getter 11 | @Setter 12 | public class ItemImg extends BaseEntity{ 13 | 14 | @Id 15 | @Column(name="item_img_id") 16 | @GeneratedValue(strategy = GenerationType.AUTO) 17 | private Long id; 18 | 19 | private String imgName; //이미지 파일명 20 | 21 | private String oriImgName; //원본 이미지 파일명 22 | 23 | private String imgUrl; //이미지 조회 경로 24 | 25 | private String repimgYn; //대표 이미지 여부 26 | 27 | @ManyToOne(fetch = FetchType.LAZY) 28 | @JoinColumn(name = "item_id") // 상품 엔티티와 다대일 단방향 관계로 매핑 29 | private Item item; 30 | 31 | public void updateItemImg(String oriImgName, String imgName, String imgUrl){ // 이거 세개만 받는거 뭐 쓰겠지?? ItemImgService 에서 이미지 수정할 때 사용하는 거였다. 32 | this.oriImgName = oriImgName; 33 | this.imgName = imgName; 34 | this.imgUrl = imgUrl; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/entity/Member.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.entity; 2 | 3 | import kr.co.codewiki.shoppingmall.constant.Role; 4 | import kr.co.codewiki.shoppingmall.dto.MemberFormDto; 5 | import lombok.*; 6 | import org.springframework.security.crypto.password.PasswordEncoder; 7 | 8 | import javax.persistence.*; 9 | 10 | @Setter 11 | @Entity 12 | @Getter 13 | @ToString 14 | @Builder 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | @Table(name="member") 18 | public class Member extends BaseEntity{ 19 | 20 | @Id 21 | @Column(name="member_id") 22 | @GeneratedValue(strategy = GenerationType.AUTO) 23 | private Long id; 24 | 25 | private String name; 26 | 27 | @Column(unique = true) 28 | private String email; 29 | 30 | private String password; 31 | 32 | private String address; 33 | 34 | @Enumerated(EnumType.STRING) 35 | private Role role; 36 | 37 | public static Member createMember(MemberFormDto memberFormDto, PasswordEncoder passwordEncoder){ 38 | 39 | Member member = Member.builder() 40 | .name(memberFormDto.getName()) 41 | .email(memberFormDto.getEmail()) 42 | .address(memberFormDto.getAddress()) 43 | .password( passwordEncoder.encode( memberFormDto.getPassword() ) ) // BCryptPasswordEncoder Bean 을 파라미터로 넘겨서 비번을 암호화함 44 | // .role(Role.USER) 45 | .role(Role.ADMIN) 46 | .build(); 47 | 48 | return member; 49 | } 50 | // 여기서는 반대로 ! 이렇게 해줌 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/entity/Order.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.entity; 2 | 3 | import kr.co.codewiki.shoppingmall.constant.OrderStatus; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import javax.persistence.*; 8 | import java.time.LocalDateTime; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | @Entity 13 | @Table(name="orders") 14 | @Getter @Setter 15 | public class Order extends BaseEntity{ // 등록한사람, 수정한사람만 있는 entity + 상속받은 등록일 수정일 entity 도 있음 16 | 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.AUTO) 19 | @Column(name = "order_id") 20 | private Long id; 21 | 22 | @ManyToOne(fetch = FetchType.LAZY) // 한 명의 회원은 여러 번 주문을 할 수 있다 23 | @JoinColumn(name = "member_id") 24 | private Member member; 25 | 26 | private LocalDateTime orderDate; // 주문일 27 | 28 | @Enumerated(EnumType.STRING) 29 | private OrderStatus orderStatus; // 주문상태 30 | 31 | // BaseEntity 를 상속받았기 때무네 필요없어짐 32 | // private LocalDateTime regTime; 33 | // 34 | // private LocalDateTime updateTime; 35 | 36 | // cascade: 부모가 바뀌면 자식도 바뀌고! 부모랑 자식이랑 연동됨. 몰아일체라고 할 수 있지 37 | // orphanRemoval = true 를 하면은 고아 객체를 지울 수 있다. 38 | @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true) // 원래 디비에는 one to many 가 없다. 단방향이기 때문에! => 양방향 매핑을 위해 적음 (order - orderId) 39 | private List orderItems = new ArrayList<>(); 40 | 41 | 42 | 43 | // 생성한 주문 상품 객체를 이용해서 주문 객체를 만들거임 44 | public void addOrderItem(OrderItem orderItem) { 45 | 46 | orderItems.add(orderItem); // orderItems 에 주문 상품 정보들 넣어줌 47 | orderItem.setOrder(this); // orderItems 과 양방향 매핑이기 때문에 orderItem 에다가도 order 객체를 넣어줌 (orderItems 은 order 객체임) 48 | 49 | } 50 | 51 | public static Order createOrder(Member member, List orderItemList) { 52 | 53 | Order order = new Order(); 54 | order.setMember(member); // 상품을 주문한 회원의 정보 setter 55 | 56 | for(OrderItem orderItem : orderItemList) { 57 | order.addOrderItem(orderItem); 58 | // 상품 페이지에서는 1개의 상품을 주문하지만, 장바구니에는 여러 상품을 주문할 수 있다. 59 | // 그래서 장바구니에 여러 상품을 담을 수 있게 리스트 형태로 파라미터 값을 받아야 한다. 파라미터는 아까 주문한 orderItem 임! 60 | 61 | } 62 | 63 | order.setOrderStatus(OrderStatus.ORDER); // 주문 상태를 ORDER 로 바꿈 64 | order.setOrderDate(LocalDateTime.now()); // 현재 시간을 주문 시간으로 바꿈 65 | return order; 66 | } 67 | 68 | // 총 주문 금액 69 | public int getTotalPrice() { 70 | int totalPrice = 0; 71 | for(OrderItem orderItem : orderItems){ 72 | totalPrice += orderItem.getTotalPrice(); 73 | } 74 | return totalPrice; 75 | } 76 | 77 | // 주문 상태를 취소 상태로 바꿔줌 78 | public void cancelOrder() { 79 | this.orderStatus = OrderStatus.CANCEL; 80 | for (OrderItem orderItem : orderItems) { 81 | orderItem.cancel(); 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/entity/OrderItem.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.entity; 2 | 3 | import kr.co.codewiki.shoppingmall.constant.OrderStatus; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import javax.persistence.*; 8 | import java.time.LocalDateTime; 9 | 10 | @Entity 11 | @Getter @Setter 12 | public class OrderItem extends BaseEntity{ // BaseEntity: 등록한사람, 수정한사람만 있는 entity + 상속받은 등록일 수정일 entity 도 있음 13 | 14 | @Id 15 | @GeneratedValue(strategy = GenerationType.AUTO) 16 | @Column(name = "order_item_id") 17 | private Long id; 18 | 19 | @ManyToOne(fetch = FetchType.LAZY) // 한 아이템은 여러 주문에 들어갈 수 있다. 20 | @JoinColumn(name = "item_id") 21 | private Item item; 22 | 23 | @ManyToOne (fetch = FetchType.LAZY) // 한 주문에 여러 아이템이 들어갈 수 있다. 24 | @JoinColumn(name = "order_id") 25 | private Order order; 26 | 27 | private int orderPrice; // 주문가격 28 | 29 | private int count; // 수량 30 | 31 | // BaseEntity 를 상속받았기 때무네 필요없어짐 32 | // private LocalDateTime regTime; 33 | // 34 | // private LocalDateTime updateTime; 35 | /* 36 | 즉시 로딩: 연관된 모든 엔티티 (ex,order 엔티티와 다대일 매핑된 member 엔티티 등등...)을 함께 가지고 옴 37 | 현재 사용하지 않아도 될 로직을 다 불러오기 때문에 보기 힘들다. 38 | 지연 로딩: 그래서 지연 로딩 사용 39 | one to many 는 기본 지연 로딩 40 | 41 | */ 42 | 43 | // 주문할 상품,주문 수량으로 orderItem 객체 생성 44 | public static OrderItem createOrderItem(Item item, int count){ 45 | 46 | OrderItem orderItem = new OrderItem(); 47 | orderItem.setItem(item); // 주문할 상품 setter 48 | orderItem.setCount(count); // 주문 수량 setter 49 | orderItem.setOrderPrice(item.getPrice()); // 현재 시간 기준으로 상품가격=주문가격 (상품가격은 관리자가 세팅하는 값에 따라 달라지니까 현재 주문한 시간으로 딱 정해야 함1!!) 50 | item.removeStock(count); // 상품 재고 수량에서 주문 수량을 뺌 51 | 52 | return orderItem; 53 | } 54 | 55 | public int getTotalPrice(){ 56 | return orderPrice*count; // 총 가격: 주문가격*주문수량 57 | } 58 | 59 | // Item 클래스에서 주문 취소 시 주문 수량을 상품의 재고에 더해주는 로직 60 | // == 주문 취소 시 주문 수량만큼 상품의 재고를 증가 (item 의 메소드 호출) 61 | public void cancel() { 62 | this.getItem().addStock(count); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/exception/OutOfStockException.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.exception; 2 | 3 | // 상품의 주문 수량이 재고 수보다 많을 때 오류 4 | public class OutOfStockException extends RuntimeException{ 5 | 6 | public OutOfStockException(String message) { 7 | super(message); 8 | } 9 | 10 | } -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/repository/CartItemRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.repository; 2 | 3 | import kr.co.codewiki.shoppingmall.dto.CartDetailDto; 4 | import kr.co.codewiki.shoppingmall.entity.CartItem; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.data.jpa.repository.Query; 7 | 8 | import java.util.List; 9 | 10 | public interface CartItemRepository extends JpaRepository { 11 | 12 | // 카트 아이디와 아이템 아이디를 이용하여 카트 아이템 레퍼지토리의 엔티티 조회 13 | CartItem findByCartIdAndItemId(Long cartId, Long itemId); 14 | 15 | // 장바구니 페이지에 전달할 CartDetailDto 를 쿼리로 조회해서 CartDetailDtoList 에 담아줌 16 | @Query("select new kr.co.codewiki.shoppingmall.dto.CartDetailDto(ci.id, i.itemNm, i.price, ci.count, im.imgUrl) " + 17 | "from CartItem ci, ItemImg im " + 18 | "join ci.item i " + 19 | "where ci.cart.id = :cartId " + 20 | "and im.item.id = ci.item.id " + 21 | "and im.repimgYn = 'Y' " + // 장바구니에 담겨있는 상품의 대표 이미지만 가져옴 22 | "order by ci.regTime desc" 23 | ) 24 | List findCartDetailDtoList(Long cartId); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/repository/CartRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.repository; 2 | 3 | import kr.co.codewiki.shoppingmall.entity.Cart; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface CartRepository extends JpaRepository { 7 | // 현재 로그인 된 memberId를 이용하여 유저 카트 찾기 8 | Cart findByMemberId(Long memberId); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/repository/ItemImgRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.repository; 2 | 3 | import kr.co.codewiki.shoppingmall.entity.ItemImg; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.List; 7 | 8 | // 상품의 이미지 정보 쿼리 repository 9 | public interface ItemImgRepository extends JpaRepository { 10 | 11 | List findByItemIdOrderByIdAsc(Long itemId); // itemServiceTest 를 위해서 만들었움 12 | 13 | ItemImg findByItemIdAndRepimgYn(Long itemId, String repimgYn); // 상품 대표 이미지를 찾는 쿼리 메소드 추가 14 | // 하는 이유는, 구매 이력 페이지에서 주문 상품 대표 이미지 출력할려고 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/repository/ItemRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.repository; 2 | 3 | import kr.co.codewiki.shoppingmall.entity.Item; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | import org.springframework.data.querydsl.QuerydslPredicateExecutor; 7 | import org.springframework.data.repository.query.Param; 8 | 9 | import java.util.List; 10 | /* 11 | * Querydsl 을 SpringDataJpa 와 함께 사용하기 위해서는 사용자 정의 repository 를 작성해야 함 12 | * 사용자 정의 repository 는 총 3단계임 13 | * 1. 사용자 정의 인터페이스 작성 14 | * 2. 사용자 정의 인터페이스 구현 15 | * 3. Spring Data Jpa repository 에서 사용자 정의 인터페이스 상속 ** (ItemRepository) 16 | * */ 17 | 18 | // 3. ItemRepository 에서 ItemRepositoryCustom 상속 19 | public interface ItemRepository extends JpaRepository , QuerydslPredicateExecutor, ItemRepositoryCustom { 20 | 21 | // 쿼리 메소드: find + (엔티티이름) + By+ 변수이름 22 | // 조건 하나 추가할 때 마다 By 를 넣어줘야 한다!!! 23 | /* 24 | * 쿼리 메소드는 조건이 많아지면 25 | * 쿼리 메소드의 이름을 보고 어떻게 동작하는지 해석하기 어렵다. 26 | * */ 27 | 28 | // 상품명으로 데이터 조회하기 29 | List findByItemNm(String itemNm); 30 | 31 | // or 조건 32 | List findByItemNmOrItemDetail(String itemNm, String itemDetail); 33 | 34 | // lessThan 조건 35 | List findByPriceLessThan(Integer price); 36 | 37 | // orderBy 조건 38 | List findAllByOrderByPriceDesc(); 39 | 40 | // orderBy 조건 + 가격 조건 41 | List findByPriceLessThanOrderByPriceDesc(Integer price); 42 | 43 | 44 | 45 | // JPQL 46 | // 쿼리 어노테이션 47 | 48 | // 특정 문자가 포함된 아이템 설명 + 가격 내림차순 검색 49 | // select i from Item i where i.itemDetail like %:itemDetail% order by i.price desc 50 | 51 | 52 | // nativeQuery 53 | // 기존의 db 에서 사용하던 쿼리를 그대로 사용해야 할 때 복잡한 쿼리를 그대로 사용 가능 54 | // 얘는 데이터베이스 종속적임임 55 | @Query(value = "select * from item i where i.item_detail like %:itemDetail% order by i.price desc", nativeQuery = true) 56 | List findByItemDetail(@Param("itemDetail") String itemDetail); 57 | 58 | 59 | // JPQL Querydsl 60 | // 동적으로 쿼리 생성 가능 61 | // 자바 소스코드로 쿼리를 작성하기 때문에 컴파일 시점에서 오류를 발견할 수 있음 62 | } 63 | /* 64 | * save (S entity): 엔티티 저장 및 수정 65 | * void delete(T entity): 엔티티 삭제 66 | * count(): 엔티티 총 개수 반환 67 | * Iterable findAll(): 모든 엔티티 조회 68 | * */ -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/repository/ItemRepositoryCustom.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.repository; 2 | 3 | import kr.co.codewiki.shoppingmall.dto.ItemSearchDto; 4 | import kr.co.codewiki.shoppingmall.dto.MainItemDto; 5 | import kr.co.codewiki.shoppingmall.entity.Item; 6 | import org.springframework.data.domain.Page; 7 | import org.springframework.data.domain.Pageable; 8 | /* 9 | * Querydsl 을 SpringDataJpa 와 함께 사용하기 위해서는 사용자 정의 repository 를 작성해야 함 10 | * 사용자 정의 repository 는 총 3단계임 11 | * 1. 사용자 정의 인터페이스 작성 *** 12 | * 2. 사용자 정의 인터페이스 구현 13 | * 3. Spring Data Jpa repository 에서 사용자 정의 인터페이스 상속 (ItemRepository) 14 | * */ 15 | 16 | // 1. 사용자 정의 인터페이스 작성 17 | public interface ItemRepositoryCustom { 18 | Page getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable); 19 | 20 | Page getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable); 21 | // 메인 페이지에 보여줄 상품 리스트 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/repository/ItemRepositoryCustomImpl.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.repository; 2 | 3 | // BooleanExpression: where 절에서 사용할 수 있는 값을 지원해줌 4 | import com.querydsl.core.QueryResults; 5 | import com.querydsl.core.types.dsl.BooleanExpression; 6 | import com.querydsl.jpa.impl.JPAQueryFactory; 7 | import kr.co.codewiki.shoppingmall.constant.ItemSellStatus; 8 | import kr.co.codewiki.shoppingmall.dto.ItemSearchDto; 9 | import kr.co.codewiki.shoppingmall.dto.MainItemDto; 10 | import kr.co.codewiki.shoppingmall.dto.QMainItemDto; 11 | import kr.co.codewiki.shoppingmall.entity.Item; 12 | import kr.co.codewiki.shoppingmall.entity.QItem; 13 | import kr.co.codewiki.shoppingmall.entity.QItemImg; 14 | import org.springframework.data.domain.Page; 15 | import org.springframework.data.domain.PageImpl; 16 | import org.springframework.data.domain.Pageable; 17 | import org.thymeleaf.util.StringUtils; 18 | 19 | import javax.persistence.EntityManager; 20 | import java.time.LocalDateTime; 21 | import java.util.List; 22 | /* 23 | * Querydsl 을 SpringDataJpa 와 함께 사용하기 위해서는 사용자 정의 repository 를 작성해야 함 24 | * 사용자 정의 repository 는 총 3단계임 25 | * 1. 사용자 정의 인터페이스 작성 26 | * 2. 사용자 정의 인터페이스 구현 *** 27 | * 3. Spring Data Jpa repository 에서 사용자 정의 인터페이스 상속 (ItemRepository) 28 | * */ 29 | 30 | //2. 사용자 정의 인터페이스 구현 (클래스 뒤에 Impl 을 꼭 붙여야 한다.) 31 | public class ItemRepositoryCustomImpl implements ItemRepositoryCustom{ 32 | 33 | private JPAQueryFactory queryFactory; // JPAQueryFactory 클래스: 동적으로 쿼리를 생성해줌 34 | 35 | public ItemRepositoryCustomImpl(EntityManager em){ // EntityManager: JPAQueryFactory 의 생성자로 많이 사용함 36 | this.queryFactory = new JPAQueryFactory(em); 37 | } 38 | 39 | 40 | private BooleanExpression searchSellStatusEq(ItemSellStatus searchSellStatus){ 41 | // 상품 판매 조건이 전체일 경우, null 리턴 (이러면은 where 절에서 조건이 무시됨) 42 | // 상품 판매 조건이 판매중 or 품절일 경우, where 조건 활성화 43 | return searchSellStatus == null ? null : QItem.item.itemSellStatus.eq(searchSellStatus); 44 | } 45 | 46 | private BooleanExpression regDtsAfter(String searchDateType){ 47 | // searchDateType 에 따라서 dateTime 의 값을 세팅 후, 그 세팅한 시간 이후로 등록된 상품만 조회함 48 | // 그니까 searchDateType 1m 이면은 dateTime 가 1달 전으로 세팅되고 상품 조회함 49 | 50 | LocalDateTime dateTime = LocalDateTime.now(); 51 | 52 | if(StringUtils.equals("all", searchDateType) || searchDateType == null){ 53 | return null; 54 | } else if(StringUtils.equals("1d", searchDateType)){ 55 | dateTime = dateTime.minusDays(1); 56 | } else if(StringUtils.equals("1w", searchDateType)){ 57 | dateTime = dateTime.minusWeeks(1); 58 | } else if(StringUtils.equals("1m", searchDateType)){ 59 | dateTime = dateTime.minusMonths(1); 60 | } else if(StringUtils.equals("6m", searchDateType)){ 61 | dateTime = dateTime.minusMonths(6); 62 | } 63 | 64 | return QItem.item.regTime.after(dateTime); 65 | } 66 | 67 | private BooleanExpression searchByLike(String searchBy, String searchQuery){ 68 | // searchBy 값에 따라 (where 조건으로) 상품을 조회 하도록 조건값을 반환함 69 | 70 | if(StringUtils.equals("itemNm", searchBy)){ 71 | return QItem.item.itemNm.like("%" + searchQuery + "%"); 72 | } else if(StringUtils.equals("createdBy", searchBy)){ 73 | return QItem.item.createdBy.like("%" + searchQuery + "%"); 74 | } 75 | 76 | return null; 77 | } 78 | 79 | 80 | // queryFactory 로 쿼리 동적 생성 81 | @Override 82 | public Page getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable) { 83 | 84 | QueryResults results = queryFactory 85 | .selectFrom(QItem.item) 86 | .where(regDtsAfter(itemSearchDto.getSearchDateType()), 87 | searchSellStatusEq(itemSearchDto.getSearchSellStatus()), 88 | searchByLike(itemSearchDto.getSearchBy(), 89 | itemSearchDto.getSearchQuery())) // , 는 and 조건임 90 | .orderBy(QItem.item.id.desc()) 91 | .offset(pageable.getOffset()) // 데이터를 가지고 올 시작 인덱스를 지정 92 | .limit(pageable.getPageSize()) // 한 번에 가지고 올 최대 개수 지정 93 | .fetchResults(); // QueryResults 를 반환 (상품 데이터 리스트 조회 , 상품 데이터 전체 개수 조회함) 94 | 95 | List content = results.getResults(); 96 | long total = results.getTotal(); 97 | 98 | return new PageImpl<>(content, pageable, total); // 조회한 데이터를 Page 클래스의 구현체인 PageImpl 객체로 반환 (?) 99 | } 100 | 101 | // item like 검색 (밑에서 쓰려구) 102 | private BooleanExpression itemNmLike(String searchQuery){ 103 | return StringUtils.isEmpty(searchQuery) ? null : QItem.item.itemNm.like("%" + searchQuery + "%"); 104 | } 105 | 106 | // queryFactory 로 쿼리 동적 생성 107 | @Override 108 | public Page getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable) { 109 | QItem item = QItem.item; 110 | QItemImg itemImg = QItemImg.itemImg; 111 | 112 | QueryResults results = queryFactory 113 | .select( 114 | new QMainItemDto( // 원래는 entity 로 변환해줘야 하는데, mainitemdto 의 어노테이션 (QueryProjection)덕분에 dto 로 그냥 사용 115 | item.id, 116 | item.itemNm, 117 | item.itemDetail, 118 | itemImg.imgUrl, 119 | item.price) 120 | ) 121 | .from(itemImg) 122 | .join(itemImg.item, item) // 내부 조인 123 | .where(itemImg.repimgYn.eq("Y")) // 대표 상품인 경우에는 Y 124 | .where(itemNmLike(itemSearchDto.getSearchQuery())) 125 | .orderBy(item.id.desc()) 126 | .offset(pageable.getOffset()) 127 | .limit(pageable.getPageSize()) 128 | .fetchResults(); 129 | 130 | List content = results.getResults(); 131 | long total = results.getTotal(); 132 | return new PageImpl<>(content, pageable, total); 133 | } 134 | 135 | /* 136 | * Querydsl 결과 조회 메소드 137 | * 1. QueryResults fetchResults(): 조회 대상 리스트 및 전체 개수 return (QueryResults)_위에서 사용함 138 | * 2. List fetch(): 조회 대상 리스트 반환 139 | * 3. T fetchOne(): 조회 대상이 1건이면 해당 타입 반환, 1개 이상이면 애러 반환 140 | * 4. T fetchFirst(): 조회 대상이 1건 or 1건 이상이면 1건만 반환 141 | * 5. long fetchCount(): 해당 데이터 전체 개수 반환, count 쿼리 실행 142 | * */ 143 | 144 | 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/repository/MemberRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.repository; 2 | 3 | import kr.co.codewiki.shoppingmall.entity.Member; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface MemberRepository extends JpaRepository { 7 | 8 | Member findByEmail(String email); // 회원가입 할 때 중복인지 확인하려고 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/repository/OrderItemRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.repository; 2 | 3 | import kr.co.codewiki.shoppingmall.entity.OrderItem; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface OrderItemRepository extends JpaRepository { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/repository/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.repository; 2 | 3 | import kr.co.codewiki.shoppingmall.entity.Order; 4 | import org.springframework.data.domain.Pageable; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.data.jpa.repository.Query; 7 | import org.springframework.data.repository.query.Param; 8 | 9 | import java.util.List; 10 | 11 | public interface OrderRepository extends JpaRepository { 12 | 13 | 14 | @Query("select o from Order o " + 15 | "where o.member.email = :email " + 16 | "order by o.orderDate desc") 17 | List findOrders(@Param("email") String email, Pageable pageable);// 현재 로그인한 사용자의 주문 데이터를 조회함 (위의 조건에 맞춰서) 18 | 19 | @Query("select count(o) from Order o " + 20 | "where o.member.email = :email") 21 | Long countOrder(@Param("email") String email); // 현재 로그인한 사용자의 주문 개수를 조회함 (위의 조건에 맞춰서) 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/service/CartService.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.service; 2 | 3 | import kr.co.codewiki.shoppingmall.dto.CartDetailDto; 4 | import kr.co.codewiki.shoppingmall.dto.CartItemDto; 5 | import kr.co.codewiki.shoppingmall.dto.CartOrderDto; 6 | import kr.co.codewiki.shoppingmall.dto.OrderDto; 7 | import kr.co.codewiki.shoppingmall.entity.Cart; 8 | import kr.co.codewiki.shoppingmall.entity.CartItem; 9 | import kr.co.codewiki.shoppingmall.entity.Item; 10 | import kr.co.codewiki.shoppingmall.entity.Member; 11 | import kr.co.codewiki.shoppingmall.repository.CartItemRepository; 12 | import kr.co.codewiki.shoppingmall.repository.CartRepository; 13 | import kr.co.codewiki.shoppingmall.repository.ItemRepository; 14 | import kr.co.codewiki.shoppingmall.repository.MemberRepository; 15 | import lombok.RequiredArgsConstructor; 16 | import org.springframework.stereotype.Service; 17 | import org.springframework.transaction.annotation.Transactional; 18 | import org.thymeleaf.util.StringUtils; 19 | 20 | import javax.persistence.EntityNotFoundException; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | @Service 25 | @RequiredArgsConstructor 26 | @Transactional 27 | public class CartService { 28 | 29 | private final ItemRepository itemRepository; 30 | private final MemberRepository memberRepository; 31 | private final CartRepository cartRepository; 32 | private final CartItemRepository cartItemRepository; 33 | private final OrderService orderService; 34 | 35 | // 장바구니에 상품을 담는 로직 36 | public Long addCart(CartItemDto cartItemDto, String email){ 37 | 38 | Item item = itemRepository.findById(cartItemDto.getItemId()) //장바구니에 담을 상품 엔티티 조회 39 | .orElseThrow(EntityNotFoundException::new); 40 | Member member = memberRepository.findByEmail(email); // 현재 로그인한 회원 엔티티 조회 41 | 42 | Cart cart = cartRepository.findByMemberId(member.getId()); // 현재 로그인한 회원의 장바구니 엔티티 조회 43 | 44 | if(cart == null){ // 회원에게 장바구니가 없으면, 만들어줌 45 | cart = Cart.createCart(member); 46 | cartRepository.save(cart); 47 | } 48 | 49 | // 상품이 장바구니에 들어가있는지 아닌지 조회 50 | CartItem savedCartItem = cartItemRepository.findByCartIdAndItemId(cart.getId(), item.getId()); 51 | 52 | // 만약 상품이 이미 있으면은 개수를 + 53 | if(savedCartItem != null){ 54 | savedCartItem.addCount(cartItemDto.getCount()); 55 | return savedCartItem.getId(); 56 | } else { // 아니면은 CartItem 에 상품 저장 57 | CartItem cartItem = CartItem.createCartItem(cart, item, cartItemDto.getCount()); 58 | cartItemRepository.save(cartItem); 59 | return cartItem.getId(); 60 | } 61 | } 62 | 63 | // 이메일을 이용하여 카트 리스트를 조회합니다. 64 | @Transactional(readOnly = true) 65 | public List getCartList(String email){ 66 | 67 | List cartDetailDtoList = new ArrayList<>(); 68 | 69 | Member member = memberRepository.findByEmail(email); 70 | Cart cart = cartRepository.findByMemberId(member.getId()); 71 | 72 | if(cart == null){ // 위에서 유저 카트 조회해서, 없으면은 그냥 반환하고 73 | return cartDetailDtoList; 74 | } 75 | 76 | cartDetailDtoList = cartItemRepository.findCartDetailDtoList(cart.getId()); 77 | return cartDetailDtoList; // 카트 있으면은 cartItemRepository 의 JPQL 쿼리로 걸러진 아이템들을 담영서 반환 78 | } 79 | 80 | // 카트 아이템이 유효한지 확인 81 | // 회원 검증 82 | @Transactional(readOnly = true) 83 | public boolean validateCartItem(Long cartItemId, String email){ 84 | 85 | Member curMember = memberRepository.findByEmail(email); // 현재 로그인한 회원 조회 86 | 87 | CartItem cartItem = cartItemRepository.findById(cartItemId) 88 | .orElseThrow(EntityNotFoundException::new); // 장바구니의 상품을 조회해서 89 | 90 | Member savedMember = cartItem.getCart().getMember(); // 그 상품을 저장한 회원 조회 91 | 92 | if(!StringUtils.equals(curMember.getEmail(), savedMember.getEmail())){ // 로그인한 회원 == 장바구니에 상품을 저장한 회원 93 | return false; 94 | } 95 | return true; 96 | } 97 | 98 | // 장바구 상품의 수량을 업데이트함 99 | public void updateCartItemCount(Long cartItemId, int count){ 100 | 101 | CartItem cartItem = cartItemRepository.findById(cartItemId) 102 | .orElseThrow(EntityNotFoundException::new);// 장바구니의 상품을 조회해서 103 | 104 | cartItem.updateCount(count); // 장바구니 상품 개수 ++ 105 | } 106 | 107 | // 장바구니 아이템 제거 108 | public void deleteCartItem(Long cartItemId) { 109 | 110 | CartItem cartItem = cartItemRepository.findById(cartItemId) 111 | .orElseThrow(EntityNotFoundException::new);// 장바구니의 상품을 조회해서 112 | 113 | cartItemRepository.delete(cartItem); // 장바구니 상품 제거 114 | } 115 | 116 | 117 | // 상품 주문 + 장바구니에서 주문한거 제거 118 | public Long orderCartItem(List cartOrderDtoList, String email){ 119 | List orderDtoList = new ArrayList<>(); 120 | 121 | // 주문한 상품을 orderDtoList 에 담아줌 122 | for (CartOrderDto cartOrderDto : cartOrderDtoList) { 123 | CartItem cartItem = cartItemRepository 124 | .findById(cartOrderDto.getCartItemId()) 125 | .orElseThrow(EntityNotFoundException::new); 126 | 127 | OrderDto orderDto = new OrderDto(); 128 | orderDto.setItemId(cartItem.getItem().getId()); 129 | orderDto.setCount(cartItem.getCount()); 130 | orderDtoList.add(orderDto); 131 | // 여러 개의 상품을 하나의 주문에 담아야 함 132 | } 133 | 134 | // 주문한 상품은 장바구니에서 제거함 135 | Long orderId = orderService.orders(orderDtoList, email); 136 | for (CartOrderDto cartOrderDto : cartOrderDtoList) { 137 | CartItem cartItem = cartItemRepository 138 | .findById(cartOrderDto.getCartItemId()) 139 | .orElseThrow(EntityNotFoundException::new); 140 | cartItemRepository.delete(cartItem); 141 | } 142 | return orderId; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/service/FileService.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.service; 2 | 3 | import lombok.extern.java.Log; 4 | import org.springframework.stereotype.Service; 5 | import java.io.File; 6 | import java.io.FileOutputStream; 7 | import java.util.UUID; 8 | /* 9 | UUID: Universally Unique Identifier 10 | 서로 다른 개체들을 구별하기 위해서 이름을 부여할 때 사용 11 | 실제로는 중복될 가능성이 거의 없어서 그냥 파일 이름으로 사용하면 됨 12 | */ 13 | 14 | // 파일을 처리하는 파일 서비스! 15 | @Service 16 | @Log 17 | public class FileService { 18 | 19 | public String uploadFile(String uploadPath, String originalFileName, byte[] fileData) throws Exception{ 20 | 21 | UUID uuid = UUID.randomUUID(); // 완전 랜덤하게 해시나 솔트처럼! 22 | String extension = originalFileName.substring(originalFileName.lastIndexOf(".")); 23 | 24 | // UUID 로 받은 값 + 원래 파일 확장자를 조합. (savedFileName: 저장될 파일 이름) 25 | String savedFileName = uuid.toString() + extension; 26 | String fileUploadFullUrl = uploadPath + "/" + savedFileName; 27 | 28 | // (FileOutputStream: 출력값이 바이트) 29 | // 생성자로 파일이 저장될 위치와, 파일의 이름을 넘겨서, 파일에 쓸 파일 출력 스트림을 만듦 30 | FileOutputStream fos = new FileOutputStream(fileUploadFullUrl); 31 | 32 | fos.write(fileData); // fileData 를 파일 스트림에 입력함 33 | fos.close(); 34 | 35 | return savedFileName; // 파일 이름만 반환 36 | } 37 | 38 | public void deleteFile(String filePath) throws Exception{ 39 | 40 | File deleteFile = new File(filePath); // 파일 저장 경로를 이용해서 파일 객체 생성 41 | 42 | if(deleteFile.exists()) { // 해당 파일이 존재하면은 파일을 삭제한닷! 43 | deleteFile.delete(); 44 | log.info("파일을 삭제하였습니다."); 45 | } else { 46 | log.info("파일이 존재하지 않습니다."); 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/service/ItemImgService.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.service; 2 | 3 | import kr.co.codewiki.shoppingmall.entity.ItemImg; 4 | import kr.co.codewiki.shoppingmall.repository.ItemImgRepository; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.stereotype.Service; 8 | 9 | import javax.transaction.Transactional; 10 | 11 | import org.springframework.web.multipart.MultipartFile; 12 | import org.thymeleaf.util.StringUtils; 13 | import javax.persistence.EntityNotFoundException; 14 | 15 | // 상품 이미지를 업로드하고, 상품 이미지 정보를 저장 16 | @Service 17 | @RequiredArgsConstructor 18 | @Transactional 19 | public class ItemImgService { 20 | 21 | @Value("${itemImgLocation}") // application.properties 에 적었던 itemImgLocation 값을 불러와서 itemImgLocation 변수에다가 넣어줌 22 | private String itemImgLocation; 23 | 24 | private final ItemImgRepository itemImgRepository; 25 | 26 | private final FileService fileService; 27 | 28 | public void saveItemImg(ItemImg itemImg, MultipartFile itemImgFile) throws Exception{ 29 | 30 | // 일단 비어있는 변수 만들어두고 밑에서 넣어줌 31 | String oriImgName = itemImgFile.getOriginalFilename(); 32 | String imgName = ""; 33 | String imgUrl = ""; 34 | 35 | //파일 업로드 36 | if(!StringUtils.isEmpty(oriImgName)){ 37 | 38 | // 상품 이미지 이름 = 저장할 경로 + 파일이름 + 파일크기(byte) 39 | imgName = fileService.uploadFile(itemImgLocation, oriImgName, 40 | itemImgFile.getBytes()); 41 | 42 | // 저장한 상품 이미지를 불러올 경로 43 | imgUrl = "/images/item/" + imgName; 44 | } 45 | 46 | //상품 이미지 정보 저장 47 | itemImg.updateItemImg(oriImgName, imgName, imgUrl); 48 | itemImgRepository.save(itemImg); 49 | /* 50 | imgName: 실제 로컬에 저장된 상품 이미지 파일 이름 51 | oriImgName: 업로드했던 상품 이미지 파일 초기 이름 52 | imgUrl: 업로드 결과 로컬에 저장된 상품 이미지 파일을 불러올 경로 53 | */ 54 | 55 | } 56 | 57 | // 상품 이미지 수정 58 | public void updateItemImg(Long itemImgId, MultipartFile itemImgFile) throws Exception{ 59 | 60 | if(!itemImgFile.isEmpty()){ // 상품 이미지를 수정한 경우, 상품 이미지를 업데이트함 61 | ItemImg savedItemImg = itemImgRepository.findById(itemImgId) // 상품 이미지 아이디를 이용해서 기존에 저장했던 상품 이미지 엔티티를 조회 62 | .orElseThrow(EntityNotFoundException::new); 63 | 64 | //기존 이미지 파일 삭제 (수정했으니까 원래 있던거 지워줘야지) 65 | if(!StringUtils.isEmpty(savedItemImg.getImgName())) { 66 | fileService.deleteFile(itemImgLocation+"/"+ 67 | savedItemImg.getImgName()); 68 | } 69 | 70 | String oriImgName = itemImgFile.getOriginalFilename(); 71 | String imgName = fileService.uploadFile(itemImgLocation, oriImgName, itemImgFile.getBytes()); // 업데이트한 상품 이미지 파일을 업로드 72 | 73 | // 변경된 상품 이미지 정보를 setting_updateItemImg 메소드를 이용함! 74 | String imgUrl = "/images/item/" + imgName; 75 | savedItemImg.updateItemImg(oriImgName, imgName, imgUrl); 76 | 77 | /* 78 | 처음 이미지 등록할 때는 itemImgRepository.save(itemImg); 이거를 썼자나 79 | 근데 updateItemImg 을 쓰는 이유는 80 | savedItemImg 엔티티는 현재 영속 상태임. 데이터 변경하는거로도 변경 감지 기능이 동작해서!!! transaction 이 끝날 때 update 쿼리가 실행되기 때문 81 | (중요한건 엔티티가 영속 상태라는 것!) 82 | */ 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/service/ItemService.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.service; 2 | 3 | // 상품 등록 4 | 5 | import kr.co.codewiki.shoppingmall.dto.ItemFormDto; 6 | import kr.co.codewiki.shoppingmall.dto.ItemImgDto; 7 | import kr.co.codewiki.shoppingmall.dto.ItemSearchDto; 8 | import kr.co.codewiki.shoppingmall.dto.MainItemDto; 9 | import kr.co.codewiki.shoppingmall.entity.Item; 10 | import kr.co.codewiki.shoppingmall.entity.ItemImg; 11 | import kr.co.codewiki.shoppingmall.repository.ItemImgRepository; 12 | import kr.co.codewiki.shoppingmall.repository.ItemRepository; 13 | import lombok.RequiredArgsConstructor; 14 | import org.springframework.stereotype.Service; 15 | import org.springframework.transaction.annotation.Transactional; 16 | import org.springframework.web.multipart.MultipartFile; 17 | import javax.persistence.EntityNotFoundException; 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | import org.springframework.data.domain.Page; 22 | import org.springframework.data.domain.Pageable; 23 | 24 | // 상품 등록 service 25 | @Service 26 | @Transactional 27 | @RequiredArgsConstructor 28 | public class ItemService { 29 | 30 | private final ItemRepository itemRepository; 31 | 32 | private final ItemImgService itemImgService; 33 | 34 | private final ItemImgRepository itemImgRepository; 35 | 36 | public Long saveItem(ItemFormDto itemFormDto, List itemImgFileList) throws Exception{ 37 | 38 | // 상품 등록 39 | Item item = itemFormDto.createItem(); 40 | itemRepository.save(item); 41 | 42 | //이미지 등록 43 | for(int i=0;i dto 로 바꾸기만 하는 service 60 | @Transactional(readOnly = true) // 트랜젝션을 readOnly 로 설정할 경우, JPA 가 변경감지(더티체킹)를 수행하지 않아서 성능 향상됨_데이터 수정이 일어나지 않기 때문에 (수정은 밑에서 함) 61 | public ItemFormDto getItemDtl(Long itemId){ 62 | 63 | List itemImgList = itemImgRepository.findByItemIdOrderByIdAsc(itemId); 64 | List itemImgDtoList = new ArrayList<>(); 65 | 66 | for (ItemImg itemImg : itemImgList) { 67 | ItemImgDto itemImgDto = ItemImgDto.of(itemImg); 68 | itemImgDtoList.add(itemImgDto); // entity -> dto 69 | } 70 | 71 | Item item = itemRepository.findById(itemId) 72 | .orElseThrow(EntityNotFoundException::new); 73 | 74 | ItemFormDto itemFormDto = ItemFormDto.of(item); 75 | itemFormDto.setItemImgDtoList(itemImgDtoList); 76 | return itemFormDto; //itemFormDto 에는 상품이랑 상품 이미지 다 있움 77 | } 78 | 79 | 80 | // 상품 데이터 수정 81 | // dto 로 변환된 상품이랑 상품이미지를 수정 82 | public Long updateItem(ItemFormDto itemFormDto, List itemImgFileList) throws Exception{ 83 | 84 | //상품 수정 85 | Item item = itemRepository.findById(itemFormDto.getId()) // 상품 아이디로 상품 엔티티를 조회 (아이디는 상품 등록 화면에서 전달 받음) 86 | .orElseThrow(EntityNotFoundException::new); 87 | 88 | item.updateItem(itemFormDto); // itemFormDto 로 상품 엔티티 수정 (itemFormDto 는 상품 등록 화면에서 전달 받음) 89 | 90 | List itemImgIds = itemFormDto.getItemImgIds(); // 상품 이미지 아이디 리스트 조회 91 | 92 | //이미지 등록 93 | for(int i=0;i getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable){ 107 | return itemRepository.getAdminItemPage(itemSearchDto, pageable); 108 | } 109 | 110 | // 메인 페이지에 보여줄 상품 데이테 조회 111 | @Transactional(readOnly = true) 112 | public Page getMainItemPage(ItemSearchDto itemSearchDto, Pageable pageable){ 113 | return itemRepository.getMainItemPage(itemSearchDto, pageable); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/service/MemberService.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.service; 2 | 3 | import kr.co.codewiki.shoppingmall.entity.Member; 4 | import kr.co.codewiki.shoppingmall.repository.MemberRepository; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.security.core.userdetails.User; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | import org.springframework.security.core.userdetails.UserDetailsService; 9 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 10 | import org.springframework.stereotype.Service; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | @Service 14 | @Transactional // 롤백! 15 | @RequiredArgsConstructor // @AutoWired 없이 new 해주기 16 | public class MemberService implements UserDetailsService { 17 | 18 | private final MemberRepository memberRepository; 19 | 20 | public Member saveMember(Member member){ // 중복 가입 막는거 + 중복가입 아니면 save 21 | validateDuplicateMember(member); 22 | return memberRepository.save(member); 23 | } 24 | 25 | private void validateDuplicateMember(Member member){ 26 | Member findMember = memberRepository.findByEmail(member.getEmail()); // 이메일 체크하고_repository 에 있는 메소드로로 27 | if(findMember != null){ 28 | throw new IllegalStateException("이가회"); // IllegalStateException: 이미 가입된 회원이면 메세지 날림 29 | } 30 | } 31 | 32 | // 로그인 로그아웃 구현! 33 | @Override // loadUserByUsername 오버라이딩 34 | public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { // user 의 이메일을 전달받는다.(중복ㄴㄴ인애) 35 | Member member = memberRepository.findByEmail(email); 36 | 37 | if (member == null){ 38 | throw new UsernameNotFoundException(email); 39 | } 40 | 41 | return User.builder() 42 | .username(member.getEmail()) 43 | .password(member.getPassword()) 44 | .roles(member.getRole().toString()) // enum 이니까 toString 해준다. 45 | .build(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/kr/co/codewiki/shoppingmall/service/OrderService.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.service; 2 | 3 | import kr.co.codewiki.shoppingmall.dto.OrderDto; 4 | import kr.co.codewiki.shoppingmall.dto.OrderHistDto; 5 | import kr.co.codewiki.shoppingmall.dto.OrderItemDto; 6 | import kr.co.codewiki.shoppingmall.entity.*; 7 | import kr.co.codewiki.shoppingmall.repository.ItemImgRepository; 8 | import kr.co.codewiki.shoppingmall.repository.ItemRepository; 9 | import kr.co.codewiki.shoppingmall.repository.MemberRepository; 10 | import kr.co.codewiki.shoppingmall.repository.OrderRepository; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.data.domain.Page; 13 | import org.springframework.data.domain.PageImpl; 14 | import org.springframework.data.domain.Pageable; 15 | import org.springframework.stereotype.Service; 16 | import org.springframework.transaction.annotation.Transactional; 17 | import org.thymeleaf.util.StringUtils; 18 | 19 | import javax.persistence.EntityNotFoundException; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | // 상품 주문 service 24 | @Service 25 | @Transactional 26 | @RequiredArgsConstructor 27 | public class OrderService { 28 | 29 | private final ItemRepository itemRepository; 30 | 31 | private final MemberRepository memberRepository; 32 | 33 | private final OrderRepository orderRepository; 34 | 35 | private final ItemImgRepository itemImgRepository; 36 | 37 | 38 | // 주문을 위한 로직 39 | public Long order(OrderDto orderDto, String email){ 40 | 41 | Item item = itemRepository.findById(orderDto.getItemId()) // 주문할 상품을 조회 42 | .orElseThrow(EntityNotFoundException::new); 43 | 44 | Member member = memberRepository.findByEmail(email); // 현재 로그인한 회원의 회원 조회 (이메일 정보로) 45 | 46 | List orderItemList = new ArrayList<>(); 47 | 48 | // orderItem: 주문 상태 엔티티 49 | OrderItem orderItem = OrderItem.createOrderItem(item, orderDto.getCount()); // item: 주문할 상품 엔티티, .getCount(): 주문 수량 50 | orderItemList.add(orderItem); 51 | 52 | // order: 주문 엔티티 53 | Order order = Order.createOrder(member, orderItemList); // member: 회원 정보 엔티티, orderItemList: 상품 리스트 54 | orderRepository.save(order); 55 | 56 | 57 | return order.getId(); // 생성한 주문 엔티티의 id 값 리턴! 58 | } 59 | 60 | 61 | // 주문 목록을 조회하기 위한 로직 62 | @Transactional(readOnly = true) 63 | public Page getOrderList(String email, Pageable pageable) { 64 | 65 | List orders = orderRepository.findOrders(email, pageable); // 주문 목록을 조회 66 | Long totalCount = orderRepository.countOrder(email); // 주문 총 개수 67 | 68 | List orderHistDtos = new ArrayList<>(); 69 | 70 | for (Order order : orders) { 71 | OrderHistDto orderHistDto = new OrderHistDto(order); 72 | List orderItems = order.getOrderItems(); 73 | 74 | for (OrderItem orderItem : orderItems) { // entity -> dto 75 | 76 | ItemImg itemImg = itemImgRepository.findByItemIdAndRepimgYn 77 | (orderItem.getItem().getId(), "Y"); // 대표상품인지 보는거 (상품 이력 페이지에 출력해야 하니까) 78 | OrderItemDto orderItemDto = 79 | new OrderItemDto(orderItem, itemImg.getImgUrl()); // entity-> dto 80 | orderHistDto.addOrderItemDto(orderItemDto); 81 | } 82 | 83 | orderHistDtos.add(orderHistDto); 84 | } 85 | 86 | return new PageImpl(orderHistDtos, pageable, totalCount); 87 | } 88 | 89 | 90 | // DB 에 있는 email 과 주문자 email 대조함 91 | @Transactional(readOnly = true) 92 | public boolean validateOrder(Long orderId, String email){ 93 | Member curMember = memberRepository.findByEmail(email); // 유저 이메일 받아옴 94 | Order order = orderRepository.findById(orderId) // 주문 데이터 받아오고 95 | .orElseThrow(EntityNotFoundException::new); 96 | Member savedMember = order.getMember(); // 위의 주문을 한 유저의 정보를 받아옴 97 | 98 | if(!StringUtils.equals(curMember.getEmail(), savedMember.getEmail())){ 99 | return false; // 그 둘이 같지 않으면은 false 100 | } 101 | 102 | return true; // 같으면 true 103 | } 104 | 105 | // 주문 취소하는 로직 구현 service 106 | public void cancelOrder(Long orderId){ 107 | Order order = orderRepository.findById(orderId) 108 | .orElseThrow(EntityNotFoundException::new); 109 | order.cancelOrder(); 110 | } 111 | 112 | // 장바구니에서 주문할 상품 데이터를 전달받아서 주문 생성 113 | public Long orders(List orderDtoList, String email){ 114 | 115 | Member member = memberRepository.findByEmail(email); 116 | List orderItemList = new ArrayList<>(); 117 | 118 | // 주문할 상품 리스트 119 | for (OrderDto orderDto : orderDtoList) { 120 | Item item = itemRepository.findById(orderDto.getItemId()) 121 | .orElseThrow(EntityNotFoundException::new); 122 | 123 | OrderItem orderItem = OrderItem.createOrderItem(item, orderDto.getCount()); 124 | orderItemList.add(orderItem); // 밑에 담아줌 125 | } 126 | 127 | Order order = Order.createOrder(member, orderItemList); // 회원이랑 주문할 상품 리스트들을 주문에 담음 128 | orderRepository.save(order); 129 | 130 | return order.getId(); 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/main/resources/static/css/layout1.css: -------------------------------------------------------------------------------- 1 | html { 2 | position: relative; 3 | min-height: 100%; 4 | margin: 0; 5 | 6 | } 7 | body { 8 | min-height: 100%; 9 | font-family: 'Gowun Dodum', sans-serif; 10 | } 11 | a{ 12 | text-decoration: none; 13 | } 14 | .footer { 15 | left: 0; 16 | right: 0; 17 | bottom: 0; 18 | width: 100%; 19 | padding: 15px 0; 20 | text-align: center; 21 | } 22 | .content{ 23 | margin-bottom:100px; 24 | margin-top: 50px; 25 | margin-left: 200px; 26 | margin-right: 200px; 27 | } 28 | 29 | .jumbotron{ 30 | background-image: url('/img/main.jpg'); 31 | color:white; 32 | height: 500px; 33 | } 34 | .a-color{ 35 | background-color: #5A6268 !important; 36 | border-color: #5A6268 !important; 37 | } 38 | .display-4{ 39 | font-size: 80px; 40 | font-weight: 600; 41 | font-family: 'Do Hyeon', sans-serif; 42 | letter-spacing: 7px; 43 | 44 | } 45 | .lead, p{ 46 | font-weight: 600; 47 | font-size: larger; 48 | } 49 | .container-mt{ 50 | margin-top: 50px; 51 | } 52 | .table-mb{ 53 | margin-bottom:70px; 54 | } 55 | .detail-mt-md{ 56 | margin-top: 100px; 57 | margin-bottom: 100px; 58 | } 59 | .img-size{ 60 | width: 250px; 61 | } 62 | -------------------------------------------------------------------------------- /src/main/resources/static/img/main.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/src/main/resources/static/img/main.jpg -------------------------------------------------------------------------------- /src/main/resources/static/item/03717933-2b8a-4947-9686-62f8b36d3ed7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/src/main/resources/static/item/03717933-2b8a-4947-9686-62f8b36d3ed7.png -------------------------------------------------------------------------------- /src/main/resources/static/item/28544370-dfd1-45a7-906e-41db3a5c3a22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/src/main/resources/static/item/28544370-dfd1-45a7-906e-41db3a5c3a22.png -------------------------------------------------------------------------------- /src/main/resources/static/item/28be6789-2c71-43e1-987b-258cbdc9beb4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/src/main/resources/static/item/28be6789-2c71-43e1-987b-258cbdc9beb4.jpg -------------------------------------------------------------------------------- /src/main/resources/static/item/35a743f9-1099-4027-bd1d-58cf7686bb91.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/src/main/resources/static/item/35a743f9-1099-4027-bd1d-58cf7686bb91.jpg -------------------------------------------------------------------------------- /src/main/resources/static/item/83491805-5237-456b-9c56-e83c470859ad.jfif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/src/main/resources/static/item/83491805-5237-456b-9c56-e83c470859ad.jfif -------------------------------------------------------------------------------- /src/main/resources/static/item/923b2e53-4280-4c92-99c1-3e04ddd481e2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/src/main/resources/static/item/923b2e53-4280-4c92-99c1-3e04ddd481e2.gif -------------------------------------------------------------------------------- /src/main/resources/static/item/970bd5a0-f0f5-4a68-8adc-dc7282b89990.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/src/main/resources/static/item/970bd5a0-f0f5-4a68-8adc-dc7282b89990.jpg -------------------------------------------------------------------------------- /src/main/resources/static/item/9eddf375-d0e4-4ae8-8b75-3d0ec56896c0.jfif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/src/main/resources/static/item/9eddf375-d0e4-4ae8-8b75-3d0ec56896c0.jfif -------------------------------------------------------------------------------- /src/main/resources/static/item/d05fa5f8-697e-41b8-9f4f-b5f60b2a2786.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/src/main/resources/static/item/d05fa5f8-697e-41b8-9f4f-b5f60b2a2786.png -------------------------------------------------------------------------------- /src/main/resources/static/item/d7344109-4c09-4a7f-80e5-1f2b84420ff1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/src/main/resources/static/item/d7344109-4c09-4a7f-80e5-1f2b84420ff1.png -------------------------------------------------------------------------------- /src/main/resources/static/item/f13e166d-5a77-4cb0-a2ce-ee8be29070f4.jfif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/src/main/resources/static/item/f13e166d-5a77-4cb0-a2ce-ee8be29070f4.jfif -------------------------------------------------------------------------------- /src/main/resources/static/item/f942ed93-0d79-4875-a3f6-4b01651a5f1a.jfif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minji0123/SpringBootShoppingMall/d226d8ba8bf2d008869bfd511af1eb765a4f7890/src/main/resources/static/item/f942ed93-0d79-4875-a3f6-4b01651a5f1a.jfif -------------------------------------------------------------------------------- /src/main/resources/templates/cart/cartList.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 173 | 174 | 175 | 176 | 177 | 178 | 181 | 182 | 183 |
184 |
185 |

186 | 장바구니 목록 187 |

188 | 189 |
190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 211 | 233 | 238 | 239 | 240 |
200 | 전체선택 201 | 상품정보상품금액
209 | 210 | 212 |
213 | 214 |
215 |
216 | 217 |
218 | 219 | 222 | 223 | 226 | 229 | 230 |
231 |
232 |
234 | 236 | 237 |
241 | 242 |

243 | 총 주문 금액 : 0원 244 |

245 | 246 |
247 | 248 |
249 | 250 |
251 |
252 | 253 | 254 |
255 | 256 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments/header.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 |
6 | 41 | 42 | 43 |
44 | -------------------------------------------------------------------------------- /src/main/resources/templates/item/itemDtl.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 114 | 115 | 116 | 117 | 118 | 121 | 122 | 123 |
124 | 125 |
126 | 127 | 128 |
129 |
130 | 131 |
132 |
133 | 134 | 판매중 135 | 136 | 137 | 품절 138 | 139 |
140 |
141 | 142 |
143 |
144 | 145 | 원 146 |
147 |
148 |
149 | 수량 150 |
151 | 152 |
153 |
154 |
155 | 156 |
157 |
결제 금액
158 |

159 |
160 |
161 | 162 | 163 |
164 |
165 | 166 |
167 |
168 |
169 | 170 | 171 |
172 | 173 |
174 |
175 | 176 | 177 |
178 | 179 | -------------------------------------------------------------------------------- /src/main/resources/templates/item/itemForm.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 38 | 39 | 40 | 41 | 42 | 43 | 54 | 55 | 56 |
57 | 58 | 59 |
60 |
61 | 62 |

63 | 상품 등록 64 |

65 |
66 | 67 | 68 |
69 | 73 |
74 | 75 |
76 |
77 | 상품명 78 |
79 | 80 |
81 |

Incorrect data

82 | 83 |
84 |
85 | 가격 86 |
87 | 88 |
89 |

Incorrect data

90 | 91 |
92 |
93 | 재고 94 |
95 | 96 |
97 |

Incorrect data

98 | 99 |
100 |
101 | 상품 상세 내용 102 |
103 | 104 |
105 |

Incorrect data

106 | 107 | 108 |
109 |
110 |
111 | 112 | 113 |
114 |
115 |
116 | 117 | 118 |
119 |
120 |
121 | 122 | 123 | 124 | 125 | 126 |
127 |
128 |
129 | 130 | 131 |
132 | 133 |
134 | 135 |
136 | 137 |
138 | 139 | 140 |
141 |
142 | 143 | 144 | 145 |
146 | 147 | -------------------------------------------------------------------------------- /src/main/resources/templates/item/itemMng.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 30 | 31 | 32 | 33 | 34 | 39 | 40 | 41 |
42 | 43 |
44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 65 | 66 | 67 | 68 | 69 | 70 |
상품아이디상품명상태등록자등록일
63 | 64 |
71 | 72 | 73 |
74 | 75 | 76 | 97 |
98 | 99 |
100 | 107 | 112 | 116 | 117 | 118 |
119 |
120 |
121 | 122 | 123 | 124 |
125 | 126 | -------------------------------------------------------------------------------- /src/main/resources/templates/layouts/layout1.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 8 | 9 | Title 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 |
35 | 36 | -------------------------------------------------------------------------------- /src/main/resources/templates/main.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 33 | 34 | 35 |
36 | 37 |
38 |

Shopping Mall

39 |

This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.

40 |
41 |

It uses utility classes for typography and spacing to space content out within the larger container.

42 |
43 | 44 | 45 | 46 |
47 |

48 |
49 |
50 | 68 | 69 |
70 | 89 |
90 |
91 |
-------------------------------------------------------------------------------- /src/main/resources/templates/member/memberForm.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 26 | 27 | 28 | 29 |
30 |
31 |
32 |
33 | 34 | 35 |

Incorrect data

36 |
37 |
38 | 39 | 40 |

Incorrect data

41 |
42 |
43 | 44 | 45 |

Incorrect data

46 |
47 |
48 | 49 | 50 |

Incorrect data

51 |
52 |
53 | 54 |
55 | 56 | 59 | 60 | 62 | 63 | 66 | 67 |
68 |
69 | 70 |
71 | 72 | -------------------------------------------------------------------------------- /src/main/resources/templates/member/memberLoginForm.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 13 | 14 | 15 |
16 | 17 |
18 |
19 |
20 | 21 | 22 |
23 |
24 | 25 | 26 |
27 |

28 | 29 | 30 | 31 |
32 |
33 | 34 |
35 | 36 | -------------------------------------------------------------------------------- /src/main/resources/templates/order/orderHist.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 52 | 53 | 54 | 55 | 56 | 57 | 86 | 87 | 88 |
89 | 90 |

91 | 구매 이력 92 |

93 | 94 |
95 | 96 |
97 |

98 |
99 | 100 | 101 | 102 | 103 |

(취소 완료)

104 |
105 |
106 |
107 |
108 |
109 |
110 | 111 |
112 |
113 | 114 |
115 | 116 | 117 |
118 |
119 |
120 |
121 | 122 |
123 | 124 |
125 | 143 |
144 | 145 |
146 | 147 | -------------------------------------------------------------------------------- /src/main/resources/templates/thymeleafEx/thymeleafEx02.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 |

상품 data

10 |
11 | 상품명: 12 |
13 | 14 |
15 | 상품상세설명: 16 |
17 | 18 |
19 | 상품등록일: 20 |
21 | 22 |
23 | 상품가격: 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/resources/templates/thymeleafEx/thymeleafEx03.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 |

상품 List

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
순번상품명상품설명가격상품등록일
31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/resources/templates/thymeleafEx/thymeleafEx07.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 |
8 | 본문 영역 입니다!!! 9 |
10 | 11 | -------------------------------------------------------------------------------- /src/test/java/kr/co/codewiki/shoppingmall/ShoppingmallApplicationTests.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ShoppingmallApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/kr/co/codewiki/shoppingmall/controller/ItemControllerTest.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.controller; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.security.test.context.support.WithMockUser; 9 | import org.springframework.test.context.TestPropertySource; 10 | import org.springframework.test.web.servlet.MockMvc; 11 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 12 | 13 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 14 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 15 | 16 | @SpringBootTest 17 | @AutoConfigureMockMvc 18 | @TestPropertySource(locations="classpath:application-test.properties") 19 | class ItemControllerTest { 20 | 21 | @Autowired 22 | MockMvc mockMvc; 23 | 24 | @Test 25 | @DisplayName("상품 등록 페이지 권한 테스트") 26 | @WithMockUser(username = "admin", roles = "ADMIN") 27 | public void itemFormTest() throws Exception{ 28 | mockMvc.perform(MockMvcRequestBuilders.get("/admin/item/new")) 29 | .andDo(print()) 30 | .andExpect(status().isOk()); 31 | } 32 | 33 | @Test 34 | @DisplayName("상품 등록 페이지 일반 회원 접근 테스트") 35 | @WithMockUser(username = "user", roles = "USER") 36 | public void itemFormNotAdminTest() throws Exception{ 37 | mockMvc.perform(MockMvcRequestBuilders.get("/admin/item/new")) 38 | .andDo(print()) 39 | .andExpect(status().isForbidden()); 40 | } 41 | } -------------------------------------------------------------------------------- /src/test/java/kr/co/codewiki/shoppingmall/controller/MemberControllerTest.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.controller; 2 | 3 | import kr.co.codewiki.shoppingmall.dto.MemberFormDto; 4 | import kr.co.codewiki.shoppingmall.entity.Member; 5 | import kr.co.codewiki.shoppingmall.service.MemberService; 6 | import org.junit.jupiter.api.DisplayName; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.security.crypto.password.PasswordEncoder; 12 | import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers; 13 | import org.springframework.test.context.TestPropertySource; 14 | import org.springframework.test.web.servlet.MockMvc; 15 | import org.springframework.transaction.annotation.Transactional; 16 | 17 | import static org.junit.jupiter.api.Assertions.*; 18 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; 19 | 20 | @SpringBootTest 21 | @AutoConfigureMockMvc // 웹 브라우저 요청을 mockMVC 로 요청할 수 있다. 22 | @Transactional 23 | @TestPropertySource(locations="classpath:application-test.properties") 24 | class MemberControllerTest { 25 | 26 | @Autowired 27 | private MemberService memberService; 28 | 29 | @Autowired 30 | private MockMvc mockMvc; 31 | 32 | @Autowired 33 | PasswordEncoder passwordEncoder; 34 | 35 | public Member createMember(String email, String password){ 36 | MemberFormDto memberFormDto = new MemberFormDto(); 37 | memberFormDto.setEmail(email); 38 | memberFormDto.setName("홍길동"); 39 | memberFormDto.setAddress("서울시 마포구 합정동"); 40 | memberFormDto.setPassword(password); 41 | Member member = Member.createMember(memberFormDto, passwordEncoder); 42 | 43 | return memberService.saveMember(member); 44 | } 45 | 46 | @Test 47 | @DisplayName("로그인 성공 테스트") 48 | public void loginSuccessTest() throws Exception{ 49 | String email = "test@email.com"; 50 | String password = "1234"; 51 | this.createMember(email, password); 52 | mockMvc.perform(formLogin().userParameter("email") 53 | .loginProcessingUrl("/members/login") 54 | .user(email).password(password)) 55 | .andExpect(SecurityMockMvcResultMatchers.authenticated()); 56 | } 57 | 58 | @Test 59 | @DisplayName("로그인 실패 테스트") 60 | public void loginFailTest() throws Exception{ 61 | String email = "test@email.com"; 62 | String password = "1234"; 63 | this.createMember(email, password); 64 | mockMvc.perform(formLogin().userParameter("email") 65 | .loginProcessingUrl("/members/login") 66 | .user(email).password("12345")) 67 | .andExpect(SecurityMockMvcResultMatchers.unauthenticated()); 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /src/test/java/kr/co/codewiki/shoppingmall/entity/CartTest.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.entity; 2 | 3 | 4 | import kr.co.codewiki.shoppingmall.dto.MemberFormDto; 5 | import kr.co.codewiki.shoppingmall.repository.CartRepository; 6 | import kr.co.codewiki.shoppingmall.repository.MemberRepository; 7 | import org.junit.jupiter.api.DisplayName; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.security.crypto.password.PasswordEncoder; 12 | import org.springframework.test.context.TestPropertySource; 13 | import org.springframework.transaction.annotation.Transactional; 14 | 15 | import javax.persistence.EntityManager; 16 | import javax.persistence.EntityNotFoundException; 17 | import javax.persistence.PersistenceContext; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | 21 | @SpringBootTest 22 | @Transactional 23 | @TestPropertySource(locations="classpath:application-test.properties") 24 | class CartTest { 25 | @Autowired 26 | CartRepository cartRepository; 27 | 28 | @Autowired 29 | MemberRepository memberRepository; 30 | 31 | @Autowired 32 | PasswordEncoder passwordEncoder; 33 | 34 | @PersistenceContext 35 | EntityManager em; // 아줌마 36 | // 엔티티는 과자, 매니저는 아줌마, 팩토리는 공장 37 | // 영속성 컨텍스트: 엔티티를 영구히 저장하는 환경 _램! (영속성 컨텍스트를 관리하는 모든 엔티티 매니저가 초기화 및 종료되지 않는 한) 38 | 39 | public Member createMember(){ 40 | MemberFormDto memberFormDto = new MemberFormDto(); 41 | memberFormDto.setEmail("test@email.com"); 42 | memberFormDto.setName("홍길동"); 43 | memberFormDto.setAddress("서울시 마포구 합정동"); 44 | memberFormDto.setPassword("1234"); 45 | return Member.createMember(memberFormDto, passwordEncoder); 46 | } 47 | 48 | 49 | // 영속성: 기본적으로 컴퓨터 공학에서 영속성이라고 하면 비휘발성 50 | 51 | @Test 52 | @DisplayName("장바구니 회원 엔티티 매핑 조회 테스트") 53 | public void findCartAndMemberTest(){ 54 | Member member = createMember(); 55 | memberRepository.save(member); 56 | 57 | Cart cart = new Cart(); 58 | cart.setMember(member); 59 | cartRepository.save(cart); 60 | 61 | em.flush(); // 아줌마가 과자를 db에 집어넣어주고 62 | em.clear(); // 아줌마가 과자를 db 로부터 비워준다. 왜 비워주냐면 db 에서 장바구니 엔티티를 가지고 올 때 회원 엔티티도 같이 가져오는지 63 | 64 | Cart savedCart = cartRepository.findById(cart.getId()) 65 | .orElseThrow(EntityNotFoundException::new); 66 | assertEquals(savedCart.getMember().getId(), member.getId()); 67 | } 68 | } -------------------------------------------------------------------------------- /src/test/java/kr/co/codewiki/shoppingmall/entity/MemberTest.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.entity; 2 | 3 | import kr.co.codewiki.shoppingmall.repository.MemberRepository; 4 | import org.junit.jupiter.api.DisplayName; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.security.test.context.support.WithMockUser; 9 | import org.springframework.test.context.TestPropertySource; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | import javax.persistence.EntityManager; 13 | import javax.persistence.EntityNotFoundException; 14 | import javax.persistence.PersistenceContext; 15 | 16 | import static org.junit.jupiter.api.Assertions.*; 17 | 18 | @SpringBootTest 19 | @Transactional 20 | @TestPropertySource(locations="classpath:application-test.properties") 21 | class MemberTest { 22 | 23 | @Autowired 24 | MemberRepository memberRepository; 25 | 26 | @PersistenceContext 27 | EntityManager em; 28 | 29 | @Test 30 | @DisplayName("Auditing 테스트") 31 | @WithMockUser(username = "gildong", roles = "USER") 32 | public void auditingTest(){ 33 | Member newMember = new Member(); 34 | memberRepository.save(newMember); 35 | 36 | em.flush(); 37 | em.clear(); 38 | 39 | Member member = memberRepository.findById(newMember.getId()) 40 | .orElseThrow(EntityNotFoundException::new); 41 | 42 | System.out.println("register time : " + member.getRegTime()); 43 | System.out.println("update time : " + member.getUpdateTime()); 44 | System.out.println("create member : " + member.getCreatedBy()); 45 | System.out.println("modify member : " + member.getModifiedBy()); 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /src/test/java/kr/co/codewiki/shoppingmall/entity/OrderTest.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.entity; 2 | 3 | import kr.co.codewiki.shoppingmall.constant.ItemSellStatus; 4 | import kr.co.codewiki.shoppingmall.constant.OrderStatus; 5 | import kr.co.codewiki.shoppingmall.repository.ItemRepository; 6 | import kr.co.codewiki.shoppingmall.repository.MemberRepository; 7 | import kr.co.codewiki.shoppingmall.repository.OrderItemRepository; 8 | import kr.co.codewiki.shoppingmall.repository.OrderRepository; 9 | import org.junit.jupiter.api.DisplayName; 10 | import org.junit.jupiter.api.Test; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.test.context.TestPropertySource; 14 | 15 | import javax.persistence.EntityManager; 16 | import javax.persistence.EntityNotFoundException; 17 | import javax.persistence.PersistenceContext; 18 | import javax.transaction.Transactional; 19 | 20 | import java.time.LocalDateTime; 21 | 22 | import static org.junit.jupiter.api.Assertions.*; 23 | 24 | @SpringBootTest 25 | @TestPropertySource(locations="classpath:application-test.properties") 26 | @Transactional 27 | class OrderTest { 28 | 29 | @Autowired 30 | OrderRepository orderRepository; 31 | 32 | @Autowired 33 | ItemRepository itemRepository; 34 | 35 | @PersistenceContext 36 | EntityManager em; 37 | 38 | @Autowired 39 | MemberRepository memberRepository; 40 | 41 | @Autowired 42 | OrderItemRepository orderItemRepository; 43 | 44 | public Item createItem(){ 45 | Item item = new Item(); 46 | item.setItemNm("테스트 상품"); 47 | item.setPrice(10000); 48 | item.setItemDetail("상세설명"); 49 | item.setItemSellStatus(ItemSellStatus.SELL); 50 | item.setStockNumber(100); 51 | item.setRegTime(LocalDateTime.now()); 52 | 53 | item.setUpdateTime(LocalDateTime.now()); 54 | return item; 55 | } 56 | 57 | @Test 58 | @DisplayName("영속성 전이 테스트") 59 | public void cascadeTest() { 60 | 61 | Order order = new Order(); 62 | 63 | for(int i=0;i<3;i++){ 64 | Item item = this.createItem(); 65 | itemRepository.save(item); 66 | OrderItem orderItem = new OrderItem(); 67 | orderItem.setItem(item); 68 | orderItem.setCount(10); 69 | orderItem.setOrderPrice(1000); 70 | orderItem.setOrder(order); 71 | order.getOrderItems().add(orderItem); // 영속성 컨텍스트에 저장되지 않은 oderitem 엔티티를 order 엔티티에 담아 72 | } 73 | 74 | 75 | // Member member = new Member(); 76 | // memberRepository.save(member); 77 | // order.setMember(member); 78 | // order.setOrderDate(LocalDateTime.now()); 79 | // order.setRegTime(LocalDateTime.now()); 80 | // order.setUpdateTime(LocalDateTime.now()); 81 | // order.setOrderStatus(OrderStatus.ORDER); 82 | 83 | orderRepository.saveAndFlush(order); 84 | em.clear(); 85 | 86 | Order savedOrder = orderRepository.findById(order.getId()) 87 | .orElseThrow(EntityNotFoundException::new); 88 | assertEquals(3, savedOrder.getOrderItems().size()); 89 | } 90 | 91 | 92 | public Order createOrder(){ 93 | Order order = new Order(); 94 | for(int i=0;i<3;i++){ 95 | Item item = createItem(); 96 | itemRepository.save(item); 97 | OrderItem orderItem = new OrderItem(); 98 | orderItem.setItem(item); 99 | orderItem.setCount(10); 100 | orderItem.setOrderPrice(1000); 101 | orderItem.setOrder(order); 102 | order.getOrderItems().add(orderItem); 103 | } 104 | Member member = new Member(); 105 | memberRepository.save(member); 106 | order.setMember(member); 107 | order.setOrderDate(LocalDateTime.now()); 108 | order.setRegTime(LocalDateTime.now()); 109 | order.setUpdateTime(LocalDateTime.now()); 110 | order.setOrderStatus(OrderStatus.ORDER); 111 | orderRepository.save(order); 112 | return order; 113 | } 114 | 115 | @Test 116 | @DisplayName("고아객체 제거 테스트") 117 | public void orphanRemovalTest(){ 118 | Order order = this.createOrder(); 119 | order.getOrderItems().remove(0); 120 | em.flush(); // 영속성 컨텍스트 -> db 반영 121 | } 122 | 123 | 124 | @Test 125 | @DisplayName("지연 로딩 테스트") 126 | public void lazyLoadingTest(){ 127 | Order order = this.createOrder(); 128 | Long orderItemId = order.getOrderItems().get(0).getId(); // 0 번째 인덱스 데이터 가져오기 129 | em.flush(); // 영속성 컨텍스트 -> db 반영 130 | em.clear(); 131 | 132 | OrderItem orderItem = orderItemRepository.findById(orderItemId) 133 | .orElseThrow(EntityNotFoundException::new); 134 | System.out.println("Order class : " + orderItem.getOrder().getClass()); 135 | System.out.println("==========================="); 136 | orderItem.getOrder().getOrderDate(); 137 | System.out.println("==========================="); 138 | } 139 | } -------------------------------------------------------------------------------- /src/test/java/kr/co/codewiki/shoppingmall/repository/ItemRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.repository; 2 | 3 | import kr.co.codewiki.shoppingmall.constant.ItemSellStatus; 4 | import kr.co.codewiki.shoppingmall.entity.Item; 5 | import org.junit.jupiter.api.DisplayName; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.TestPropertySource; 10 | 11 | import java.time.LocalDateTime; 12 | import java.util.List; 13 | 14 | @SpringBootTest 15 | @TestPropertySource(locations = "classpath:application-test.properties") 16 | class ItemRepositoryTest { 17 | 18 | @Autowired 19 | ItemRepository itemRepository; 20 | 21 | @Test 22 | @DisplayName("상품 저장 테스트") 23 | public void createItemTest(){ 24 | 25 | // Item item = new Item(); 26 | // item.setItemNm("테스트 상품"); 27 | // item.setPrice(10000); 28 | // item.setItemDetail("테스트 상품 상세 설명"); 29 | // item.setItemSellStatus(ItemSellStatus.SELL); // enum 은 new 안해도 되는 듯?!? 30 | // item.setStockNum(100); 31 | // item.setRegTime(LocalDateTime.now()); 32 | // item.setUpdateTime(LocalDateTime.now()); 33 | // Item savedItem = itemRepository.save(item); 34 | // System.out.println(savedItem.toString()); 35 | 36 | 37 | Item item = Item.builder() 38 | .itemNm("테스트 상품") 39 | .price(10000) 40 | .itemDetail("테스트 상품 상세 설명") 41 | .itemSellStatus(ItemSellStatus.SELL) 42 | .stockNumber(100) 43 | // .regTime(LocalDateTime.now()) 44 | // .updateTime(LocalDateTime.now()) 45 | .build(); 46 | Item savedItem = itemRepository.save(item); 47 | System.out.println(savedItem.toString()); 48 | 49 | } 50 | 51 | public void createItemList(){ 52 | for(int i=1; i<=10; i++){ 53 | Item item = Item.builder() // static 함수 54 | .itemNm("테스트 상품" +i) 55 | .price(10000+i) 56 | .itemDetail("테스트 상품 상세 설명"+i) 57 | .itemSellStatus(ItemSellStatus.SELL) 58 | .stockNumber(100) 59 | // .regTime(LocalDateTime.now()) 60 | // .updateTime(LocalDateTime.now()) 61 | .build(); 62 | Item savedItem = itemRepository.save(item); 63 | } 64 | } 65 | 66 | @Test 67 | @DisplayName("상품명 조회 테스트") 68 | public void findByItemNmTest(){ 69 | this.createItemList(); 70 | List itemList = itemRepository.findByItemNm("테스트 상품1"); 71 | for (Item item : itemList ){ 72 | System.out.println("findByItemNmTest: "+item.toString()); 73 | } 74 | System.out.println(""); 75 | } 76 | 77 | @Test 78 | @DisplayName("상품명, 상품상세설명 조회 테스트") 79 | public void findByItemNmOrItemDetailTest(){ 80 | createItemList(); 81 | List itemList = itemRepository.findByItemNmOrItemDetail("테스트 상품1", "테스트 상품 상세 설명5"); 82 | for (Item item : itemList ){ 83 | System.out.println("[findByItemNmOrItemDetailTest]: "+item.toString()); 84 | } 85 | System.out.println(""); 86 | } 87 | 88 | @Test 89 | @DisplayName("가격 범위 조회 테스트_lessThan") 90 | public void findByPriceLessThanTest(){ 91 | createItemList(); 92 | List itemList = itemRepository.findByPriceLessThan(10003); 93 | for (Item item : itemList ){ 94 | System.out.println("[findByPriceLessThanTest]: "+item.toString()); 95 | } 96 | System.out.println(""); 97 | } 98 | 99 | @Test 100 | @DisplayName("가격 내림차순 정렬") 101 | public void findAllByOrderByPriceDescTest(){ 102 | createItemList(); 103 | List itemList = itemRepository.findAllByOrderByPriceDesc(); 104 | for (Item item : itemList ){ 105 | System.out.println("[findAllByOrderByPriceDescTest]: "+item.toString()); 106 | } 107 | System.out.println(""); 108 | } 109 | 110 | // JPQL 111 | @Test 112 | @DisplayName("@Query 를 이용한 상품 조회 테스트") 113 | public void findByItemDetailTest(){ 114 | createItemList(); 115 | List itemList = itemRepository.findByItemDetail("테스트 상품 상세 설명"); 116 | for (Item item : itemList ){ 117 | System.out.println("[findByItemDetailTest]: "+item.toString()); 118 | } 119 | System.out.println(""); 120 | } 121 | 122 | 123 | // // JPQL Querydsl 124 | // @PersistenceContext 125 | // EntityManager em; 126 | // 127 | // @Test 128 | // @DisplayName("Querydsl 조회 테스트1") 129 | // public void queryDslTest(){ 130 | // createItemList(); 131 | // JPAQuery 132 | // } 133 | 134 | 135 | 136 | 137 | } -------------------------------------------------------------------------------- /src/test/java/kr/co/codewiki/shoppingmall/service/CartServiceTest.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.service; 2 | 3 | import kr.co.codewiki.shoppingmall.constant.ItemSellStatus; 4 | import kr.co.codewiki.shoppingmall.dto.CartItemDto; 5 | import kr.co.codewiki.shoppingmall.entity.CartItem; 6 | import kr.co.codewiki.shoppingmall.entity.Item; 7 | import kr.co.codewiki.shoppingmall.entity.Member; 8 | import kr.co.codewiki.shoppingmall.repository.CartItemRepository; 9 | import kr.co.codewiki.shoppingmall.repository.ItemRepository; 10 | import kr.co.codewiki.shoppingmall.repository.MemberRepository; 11 | import org.junit.jupiter.api.DisplayName; 12 | import org.junit.jupiter.api.Test; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.test.context.SpringBootTest; 15 | import org.springframework.transaction.annotation.Transactional; 16 | 17 | import javax.persistence.EntityNotFoundException; 18 | 19 | import static org.junit.jupiter.api.Assertions.*; 20 | 21 | @SpringBootTest 22 | @Transactional 23 | class CartServiceTest { 24 | @Autowired 25 | ItemRepository itemRepository; 26 | 27 | @Autowired 28 | MemberRepository memberRepository; 29 | 30 | @Autowired 31 | CartService cartService; 32 | 33 | @Autowired 34 | CartItemRepository cartItemRepository; 35 | 36 | public Item saveItem(){ 37 | Item item = new Item(); 38 | item.setItemNm("테스트 상품"); 39 | item.setPrice(10000); 40 | item.setItemDetail("테스트 상품 상세 설명"); 41 | item.setItemSellStatus(ItemSellStatus.SELL); 42 | item.setStockNumber(100); 43 | return itemRepository.save(item); 44 | } 45 | 46 | public Member saveMember(){ 47 | Member member = new Member(); 48 | member.setEmail("test@test.com"); 49 | return memberRepository.save(member); 50 | } 51 | 52 | @Test 53 | @DisplayName("장바구니 담기 테스트") 54 | public void addCart(){ 55 | Item item = saveItem(); 56 | Member member = saveMember(); 57 | 58 | CartItemDto cartItemDto = new CartItemDto(); 59 | cartItemDto.setCount(5); // 상품을 장바구니에 5개 담음 60 | cartItemDto.setItemId(item.getId()); 61 | 62 | Long cartItemId = cartService.addCart(cartItemDto, member.getEmail()); // 장바구니 상품 아이디 (실제 담긴 거) 63 | CartItem cartItem = cartItemRepository.findById(cartItemId) 64 | .orElseThrow(EntityNotFoundException::new); // 장바구니에 담긴 상품 아이디 (db 조회 값) 65 | 66 | assertEquals(item.getId(), cartItem.getItem().getId()); // 상품 아이디 == 장바구니 안에 있는 상품 아이디 67 | assertEquals(cartItemDto.getCount(), cartItem.getCount()); // 실제로 담겨있는 개수 == 장바구니 안에 있는 상품 개수 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /src/test/java/kr/co/codewiki/shoppingmall/service/ItemServiceTest.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.service; 2 | 3 | import kr.co.codewiki.shoppingmall.constant.ItemSellStatus; 4 | import kr.co.codewiki.shoppingmall.dto.ItemFormDto; 5 | import kr.co.codewiki.shoppingmall.entity.Item; 6 | import kr.co.codewiki.shoppingmall.entity.ItemImg; 7 | import kr.co.codewiki.shoppingmall.repository.ItemImgRepository; 8 | import kr.co.codewiki.shoppingmall.repository.ItemRepository; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | import org.junit.jupiter.api.DisplayName; 13 | import org.junit.jupiter.api.Test; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.mock.web.MockMultipartFile; 16 | import org.springframework.security.test.context.support.WithMockUser; 17 | import org.springframework.test.context.ContextConfiguration; 18 | import org.springframework.test.context.TestPropertySource; 19 | import org.springframework.transaction.annotation.Transactional; 20 | import org.springframework.web.multipart.MultipartFile; 21 | import javax.persistence.EntityNotFoundException; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | 26 | @SpringBootTest 27 | @Transactional 28 | @TestPropertySource(locations="classpath:application-test.properties") 29 | class ItemServiceTest { 30 | 31 | @Autowired 32 | ItemService itemService; 33 | 34 | @Autowired 35 | ItemRepository itemRepository; 36 | 37 | @Autowired 38 | ItemImgRepository itemImgRepository; 39 | 40 | List createMultipartFiles() throws Exception{ // MockMultipartFile 클래스를 이용해서 가짜 MultipartFile 리스트를 만들어서 반환해줌 (?) 41 | 42 | List multipartFileList = new ArrayList<>(); 43 | 44 | for(int i=0;i<5;i++){ // 상품 이미지 경로 + 이미지 이름 저장해서 add 45 | String path = "C:/shop/item/"; 46 | String imageName = "image" + i + ".jpg"; 47 | MockMultipartFile multipartFile = 48 | new MockMultipartFile(path, imageName, "image/jpg", new byte[]{1,2,3,4}); 49 | multipartFileList.add(multipartFile); 50 | } 51 | 52 | return multipartFileList; 53 | } 54 | 55 | @Test 56 | @DisplayName("상품 등록 테스트") 57 | @WithMockUser(username = "admin", roles = "ADMIN") 58 | void saveItem() throws Exception { 59 | 60 | ItemFormDto itemFormDto = new ItemFormDto(); // 상품 등록 화면에서 입력 받는 상품 데이터 setter 61 | itemFormDto.setItemNm("테스트상품"); 62 | itemFormDto.setItemSellStatus(ItemSellStatus.SELL); 63 | itemFormDto.setItemDetail("테스트 상품 입니다."); 64 | itemFormDto.setPrice(1000); 65 | itemFormDto.setStockNumber(100); 66 | 67 | List multipartFileList = createMultipartFiles(); 68 | 69 | // 상품 데이터와 이미지 정보를 파라미터로 넘겨서 저장 후 저장된 상품의 아이디 값을 반환 70 | Long itemId = itemService.saveItem(itemFormDto, multipartFileList); 71 | List itemImgList = itemImgRepository.findByItemIdOrderByIdAsc(itemId); 72 | 73 | Item item = itemRepository.findById(itemId) 74 | .orElseThrow(EntityNotFoundException::new); 75 | 76 | assertEquals(itemFormDto.getItemNm(), item.getItemNm()); 77 | assertEquals(itemFormDto.getItemSellStatus(), item.getItemSellStatus()); 78 | assertEquals(itemFormDto.getItemDetail(), item.getItemDetail()); 79 | assertEquals(itemFormDto.getPrice(), item.getPrice()); 80 | assertEquals(itemFormDto.getStockNumber(), item.getStockNumber()); 81 | assertEquals(multipartFileList.get(0).getOriginalFilename(), itemImgList.get(0).getOriImgName()); // 상품 이미지는 첫번째 파일의 원본 이미지 파일 이름만 같은지 확인함 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /src/test/java/kr/co/codewiki/shoppingmall/service/MemberServiceTest.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.service; 2 | 3 | import kr.co.codewiki.shoppingmall.dto.MemberFormDto; 4 | import kr.co.codewiki.shoppingmall.entity.Member; 5 | import org.junit.jupiter.api.DisplayName; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.security.crypto.password.PasswordEncoder; 10 | import org.springframework.test.context.TestPropertySource; 11 | 12 | import javax.transaction.Transactional; 13 | 14 | import static org.junit.jupiter.api.Assertions.*; 15 | 16 | @SpringBootTest 17 | @Transactional 18 | @TestPropertySource(locations="classpath:application-test.properties") 19 | class MemberServiceTest { 20 | 21 | @Autowired 22 | MemberService memberService; 23 | 24 | @Autowired 25 | PasswordEncoder passwordEncoder; 26 | 27 | public Member createMemberTest(){ 28 | MemberFormDto memberFormDto = new MemberFormDto(); 29 | memberFormDto.setEmail("test@email.com"); 30 | memberFormDto.setName("홍길동"); 31 | memberFormDto.setAddress("서울시 마포구 합정동"); 32 | memberFormDto.setPassword("1234"); 33 | // 여기까지는 dto 34 | 35 | return Member.createMember(memberFormDto, passwordEncoder); // 여기서 지금 dto -> entity 인 거지 36 | } 37 | 38 | @Test 39 | @DisplayName("회원가입 테스트") 40 | public void saveMemberTest(){ 41 | 42 | Member member = createMemberTest(); // member: dto -> entity 43 | Member savedMember = memberService.saveMember(member);// savedMember: 중복 가입 막는거 + 중복가입 아니면 save 44 | 45 | 46 | // 두개 같나 확인하기 47 | assertEquals(member.getEmail(), savedMember.getEmail()); 48 | assertEquals(member.getName(), savedMember.getName()); 49 | assertEquals(member.getAddress(), savedMember.getAddress()); 50 | assertEquals(member.getPassword(), savedMember.getPassword()); 51 | assertEquals(member.getRole(), savedMember.getRole()); 52 | } 53 | 54 | @Test 55 | @DisplayName("중복 회원 가입 테스트") 56 | public void saveDuplicateMemberTest(){ 57 | 58 | Member member1 = createMemberTest();// member1: dto -> entity 59 | memberService.saveMember(member1); // member1: 중복 가입 막는거 + 중복가입 아니면 save 60 | 61 | 62 | Member member2 = createMemberTest(); // dto -> entity 63 | Throwable e = assertThrows(IllegalStateException.class, () -> { // 중복 가입 막는거 => 이메일이 같아! fail 64 | memberService.saveMember(member2); // 다 다른데 이메일이 같아서 오류가 나버렸어!! 65 | }); 66 | 67 | assertEquals("이가회", e.getMessage()); 68 | 69 | } 70 | } -------------------------------------------------------------------------------- /src/test/java/kr/co/codewiki/shoppingmall/service/OrderServiceTest.java: -------------------------------------------------------------------------------- 1 | package kr.co.codewiki.shoppingmall.service; 2 | 3 | import kr.co.codewiki.shoppingmall.constant.ItemSellStatus; 4 | import kr.co.codewiki.shoppingmall.constant.OrderStatus; 5 | import kr.co.codewiki.shoppingmall.dto.OrderDto; 6 | import kr.co.codewiki.shoppingmall.entity.Item; 7 | import kr.co.codewiki.shoppingmall.entity.Member; 8 | import kr.co.codewiki.shoppingmall.entity.Order; 9 | import kr.co.codewiki.shoppingmall.entity.OrderItem; 10 | import kr.co.codewiki.shoppingmall.repository.ItemRepository; 11 | import kr.co.codewiki.shoppingmall.repository.MemberRepository; 12 | import kr.co.codewiki.shoppingmall.repository.OrderRepository; 13 | import org.junit.jupiter.api.DisplayName; 14 | import org.junit.jupiter.api.Test; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.boot.test.context.SpringBootTest; 17 | import org.springframework.test.context.TestPropertySource; 18 | import org.springframework.transaction.annotation.Transactional; 19 | 20 | import javax.persistence.EntityNotFoundException; 21 | import java.util.List; 22 | 23 | import static org.junit.jupiter.api.Assertions.assertEquals; 24 | import static org.junit.jupiter.api.Assertions.*; 25 | 26 | 27 | @SpringBootTest 28 | @Transactional 29 | @TestPropertySource(locations="classpath:application-test.properties") 30 | class OrderServiceTest { 31 | @Autowired 32 | private OrderService orderService; 33 | 34 | @Autowired 35 | private OrderRepository orderRepository; 36 | 37 | @Autowired 38 | ItemRepository itemRepository; 39 | 40 | @Autowired 41 | MemberRepository memberRepository; 42 | 43 | // 주문할 상품 저장 44 | public Item saveItem(){ 45 | Item item = new Item(); 46 | item.setItemNm("테스트 상품"); 47 | item.setPrice(10000); 48 | item.setItemDetail("테스트 상품 상세 설명"); 49 | item.setItemSellStatus(ItemSellStatus.SELL); 50 | item.setStockNumber(100); 51 | return itemRepository.save(item); 52 | } 53 | 54 | // 회원 정보 저장 55 | public Member saveMember(){ 56 | Member member = new Member(); 57 | member.setEmail("test@test.com"); 58 | return memberRepository.save(member); 59 | 60 | } 61 | 62 | @Test 63 | @DisplayName("주문 테스트") 64 | public void order(){ 65 | Item item = saveItem(); 66 | Member member = saveMember(); 67 | 68 | OrderDto orderDto = new OrderDto(); 69 | orderDto.setCount(10); // 주문할 상품 개수 10개로 지정 70 | orderDto.setItemId(item.getId()); // 지정한 개수 oderDto 에 넣어놓음 71 | 72 | Long orderId = orderService.order(orderDto, member.getEmail());// orderId: 주문 로직 호출 후 생성된 주문 번호 73 | Order order = orderRepository.findById(orderId) 74 | .orElseThrow(EntityNotFoundException::new); 75 | 76 | List orderItems = order.getOrderItems(); 77 | 78 | int totalPrice = orderDto.getCount()*item.getPrice(); // 총 가격 79 | 80 | assertEquals(totalPrice, order.getTotalPrice()); // 총 가격 == db 에 저장된 상품 가격 이면은 성공! 81 | } 82 | 83 | 84 | @Test 85 | @DisplayName("주문 취소 테스트") 86 | public void cancelOrder(){ 87 | Item item = saveItem(); 88 | Member member = saveMember(); 89 | 90 | OrderDto orderDto = new OrderDto(); 91 | orderDto.setCount(10); // 10개 주문 넣음 92 | orderDto.setItemId(item.getId()); 93 | Long orderId = orderService.order(orderDto, member.getEmail()); 94 | 95 | Order order = orderRepository.findById(orderId) 96 | .orElseThrow(EntityNotFoundException::new); 97 | orderService.cancelOrder(orderId); // 10개 주문 취소 98 | 99 | assertEquals(OrderStatus.CANCEL, order.getOrderStatus()); // cancel == 주문상태 100 | assertEquals(100, item.getStockNumber()); // 처음개수 == 주문넣다뺐다 한 결과값 101 | } 102 | 103 | } --------------------------------------------------------------------------------