#230927 수업 복습
Data <-> Application <-> Presentation
✔️ 데이터를 보관하고 사용하는 방법에 대한 설계가 들어가는 계층(영속, 백엔드)
데이터베이스와 데이터베이스에 접근하여 데이터를 읽고 쓰는것을 관리하는 방법들이 포함
DB서버가 담당하는 영역
✔️ 요청되는 정보를 처리하고 가공하는 계층(미들웨어, 백엔드)
비즈니스 로직 계층이라고도 하며,
동적인 데이터들을 제공하기 위한 순수한 비즈니스 로직이 포함
JAVA를 포함한 WAS 서버가 담당하는 영역
✔️ 사용자와 직접적으로 소통하는 계층(프론트 엔드)
사용자의 응답 처리를 진행하고, 화면을 표현하는 방법들이 포함
HTML 엔진(Thymeleaf), 웹 서버가 담당하는 영역
최상위도메인명.그룹명.프로젝트명
ex) com.kh.demo
config 프로젝트와 관련된 설정 클래스들의 보관 패키지
controller 스프링 MVC의 Controller들의 보관 패키지
service 스프링의 Service 인터페이스와 구현 클래스 패키지
domain VO, DAO 클래스들의 패키지
mapper MyBatis Mapper 인터페이스 패키지
repository 레포지토리 패턴 이용 시 레포지토리들의 패키지
exception 웹 관련 예외 처리 클래스들의 보관 패키지
aop 스프링의 AOP 관련 패키지
security Spring Security 관련 패키지
util 각종 유틸리티 클래스 관련 패키지


package com.kh.demo.domain.dto;
import lombok.Data;
@Data
public class UserDTO {
private int useridx;
private String userid;
private String userpw;
private String username;
private String usergender;
private String zipcode;
private String addr;
private String addrdetail;
private String addretc;
private String userhobby;
}
package com.kh.demo.domain.dto;
import lombok.Data;
@Data
public class BoardDTO {
private Long boardnum;
private String boardtitle;
private String boardcontents;
private String regdate;
private String updatedate;
private int readcount;
private String userid;
}
package com.kh.demo.domain.dto;
import lombok.Data;
@Data
public class FileDTO {
private String systemname;
private String orgname;
private Long boardnum;
}
package com.kh.demo.domain.dto;
import lombok.Data;
@Data
public class ReplyDTO {
private Long replynum;
private String replycontents;
private String regdate;
private String updatedate;
private String userid;
private Long boardnum;
}
package com.kh.demo.mapper;
import org.apache.ibatis.annotations.Mapper;
import com.kh.demo.domain.dto.UserDTO;
@Mapper // Mapper 어노테이션
public interface UserMapper {
int insertUser(UserDTO user);
UserDTO findById(String userid);
}
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kh.demo.mapper.UserMapper">
<insert id="insertUser">
insert into t_user (userid,userpw,username,usergender,zipcode,addr,addrdetail,addretc,userhobby)
values(#{userid},#{userpw},#{username},#{usergender},#{zipcode},#{addr},#{addrdetail},#{addretc},#{userhobby})
</insert>
<select id="findById">
select * from t_user where userid=#{userid}
</select>
</mapper>
package com.kh.demo.mapper;
import org.junit.jupiter.api.Test;
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import com.kh.demo.domain.dto.UserDTO;
@MybatisTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class UserMapperTests {
@Autowired
private UserMapper mapper;
// @Test
// public void insertUserTest() {
// UserDTO user = new UserDTO();
// user.setUserid("testid");
// user.setUserpw("testpw");
// user.setUsername("testname");
// user.setZipcode("testzipcode");
// user.setAddrdetail("testaddrdetail");
//
// boolean result = mapper.insertUser(user) == 1;
// System.out.println("Result : "+result);
// }
@Test
public void findByIdTest() {
System.out.println(mapper.findById("apple"));
}
}
package com.kh.demo.service;
import com.kh.demo.domain.dto.UserDTO;
public interface UserService {
boolean join(UserDTO user);
boolean checkId(String userid);
UserDTO login(String userid, String userpw);
}
package com.kh.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.kh.demo.domain.dto.UserDTO;
import com.kh.demo.mapper.UserMapper;
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserMapper umapper;
// UserService에서 설계한 메소드 오버라이드해서 구현
@Override
public boolean checkId(String userid) {
return umapper.findById(userid) == null;
}
//Id 중복검사 메소드
// => umapper에 있는 findById 쿼리문을 돌렸을 때 null이 나오면 중복된 Id가 없으므로 중복 검사 성공
@Override
public boolean join(UserDTO user) {
return umapper.insertUser(user) == 1;
}
//회원가입 메소드
// => umapper에 있는 insertUser 쿼리문을 돌렸을 때 1개 행의 갯수가 잘 돌아 왔으면 성공
@Override
public UserDTO login(String userid, String userpw) {
UserDTO user = umapper.findById(userid);
if(user != null) {
if(user.getUserpw().equals(userpw)) {
return user;
}
}
return null;
}
//로그인 메소드
// => umapper에 았는 findById 쿼리문을 돌렸을 때 userid를 찾아서 user 변수에 담고,
// user의 userid와 동일한 행의 userpw와 넘겨진 userpw가 동일한지 체크
// user가 null이 아니면 user return, user가 null이면 null 리턴
}
package com.kh.demo.service;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.kh.demo.domain.dto.UserDTO;
@SpringBootTest
public class UserServiceTests {
@Autowired
private UserService service;
// @Test
// public void joinTest() {
// UserDTO user = new UserDTO();
// user.setUserid("testid");
// user.setUserpw("testpw");
// user.setUsername("testname");
// user.setZipcode("testzipcode");
// user.setAddrdetail("testaddrdetail");
//
// System.out.println("Result : "+service.join(user));
// }
// @Test
// public void checkIdTest() {
// System.out.println(service.checkId("testid"));
// System.out.println(service.checkId("no exist"));
// }
@Test
public void loginTest() {
System.out.println("Result : "+service.login("testid", "testpw"));
System.out.println("Result : "+service.login("testid", "wrong pw"));
System.out.println("Result : "+service.login("wrong id", "testpw"));
System.out.println("Result : "+service.login("wrong id", "wrong pw"));
}
}
package com.kh.demo.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.kh.demo.domain.dto.UserDTO;
import com.kh.demo.service.UserService;
import jakarta.servlet.http.HttpServletRequest;
@Controller
@RequestMapping("/user/*")
public class UserController {
@Autowired
private UserService service;
// GET
// /user/login 로그인 페이지로 이동(login.html)
// /user/join 회원가입 페이지로 이동(join.html)
// /user/checkid 넘겨진 파라미터로 아이디 중복 체크
// /user/logout 로그아웃 처리
//
// POST
// /user/login 넘겨진 파라미터로 로그인 처리
// /user/join 넘겨진 데이터들로 회원가입 처리
@GetMapping("join")
public void replace() {}
@PostMapping("join")
public String join(UserDTO user, RedirectAttributes ra) {
if(service.join(user)) {
ra.addAttribute("joinid",user.getUserid());
}
return "redirect:/"; //첫화면으로 이동
}
@PostMapping("login")
public String login(String userid, String userpw, HttpServletRequest req) {
UserDTO loginUser = service.login(userid, userpw);
if(loginUser != null) {
req.getSession().setAttribute("loginUser", loginUser.getUserid()); //세션 셋팅
return "redirect:/board/list"; //로그인 성공 후 list.html로 이동
}
else {
return "redirect:/"; //로그인 실패 시 첫화면으로 이동
}
}
@GetMapping("logout")
public String logout(HttpServletRequest req) {
req.getSession().invalidate(); //세션 초기화
return "redirect:/";
}
@GetMapping("checkid")
@ResponseBody //ajax 통신 => 페이지 이동 x => @ResponseBody : 리턴시 문자열(데이터) 자체를 body로 돌려줌
public String checkId(String userid) {
if(service.checkId(userid)) {
return "O"; //checkId가 true => O
}
return "X"; //checkId가 flase => X
}
}
=> Postman으로 test
package com.kh.demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HomeController {
@RequestMapping("/") // '/' 요청이 오면 index로 이동되게 매핑
public String home() {
return "index";
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>최종 예제 게시판</title>
<style>
#wrap{
width:600px;
height:400px;
margin:200px auto;
}
body{ background-color: rgb(245,246,247); }
input{
box-sizing: border-box;
cursor: pointer;
}
table{ border-collapse: collapse; margin:0 auto;}
td,th{ padding: 10px; text-align: center;}
.btn_area{
margin:0 auto;
display: flex;
justify-content: center;
}
.btn_area > *{
display:inline-block;
margin-left:10px;
margin-right:10px;
}
input[type=text], input[type=password]{
padding:10px;
width:200px;
margin-left:20px;
border:1px solid #ccc;
border-radius: 5px;
outline: none;
}
input[type=text]:focus, input[type=password]:focus{
border:1px solid rgb(0,200,80);
}
input[type=submit]{
margin-top:10px;
padding:10px 20px;
width:120px;
border:none;
background-color:rgb(0,200,80);
border-radius: 5px;
color:#fff;
font-weight: bold;
font-size: 18px;
}
a{
text-decoration:none;
box-sizing:border-box;
margin-top:10px;
padding:10px 20px;
width:120px;
border:none;
background-color:rgb(0,200,80);
border-radius: 5px;
color:#fff;
font-weight: bold;
font-size: 18px;
}
</style>
</head>
<body>
<th:block th:if="${joinid != null and joinid != ''}">
<script th:inline="javascript">
<!-- 스크립트문 안에 th:inline="javascript"를 적으면
[[${joinid}]]가 타임리프 문법으로 해석됨 -->
window.onload = function(){
alert("가입을 축하합니다!");
document.getElementById("userid").value = /*[[${joinid}]]*/'';
<!-- /*[[${joinid}]]*/ 주석 처리 하는 이유
: joinid가 실제로 없는 문자열이 들어올 경우
빈 상태로 렌더링이 되어 자바스크립트 문법상 오류 발생
=> 주석처리함으로써 비어있어도 문제가 없게 함
주석처리한 상태로만 끝나면 비어있는 상태이기 때문에 오류 발생
=> 옆에 '' 빈 문자열 넣어줌 => joinid가 안들어온 경우 빈문자열로 처리됨 -->
document.getElementById("userpw").focus();
}
</script>
</th:block>
<div id="wrap">
<form name="loginForm" action="/user/login" method="post" onsubmit="return sendit()">
<table>
<tr>
<th>아이디</th>
<td>
<input type="text" id="userid" name="userid" value="">
</td>
</tr>
<tr>
<th>비밀번호</th>
<td>
<input type="password" id="userpw" name="userpw">
</td>
</tr>
<tr>
<td colspan="2">
<div class="btn_area">
<input type="submit" value="로그인">
<a href="/user/join">회원가입</a>
</div>
</td>
</tr>
</table>
</form>
</div>
</body>
<script>
function sendit(){
const loginForm = document.loginForm;
const userid = loginForm.userid;
const userpw = loginForm.userpw;
if(userid.value == ""){
alert("아이디를 입력하세요!");
return false;
}
if(userpw.value == ""){
alert("비밀번호를 입력하세요!");
return false;
}
return true;
}
</script>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Join</title>
<style>
*{
box-sizing: border-box;
}
#wrap{
width:520px;
margin:0 auto;
}
body{ background-color:rgb(245,246,247); }
input{
box-sizing: border-box;
}
input[type=button], input[type=submit]{
cursor: pointer;
}
table{ border-collapse: collapse; }
th{ width:120px; text-align: left; }
th::after{
content:"";
display: inline-block;
box-sizing: border-box;
width:1px;
height:14px;
}
th, td{ padding: 0px; height:48px;}
td{
padding-left: 20px;
width:400px;
height:40px;
}
input[type=text], input[type=password]{
position:relative;
display:inline-block;
padding:5px 15px 5px 10px;
border:1px solid #ccc;
width:250px;
height:40px;
line-height:40px;
border-radius: 5px;
}
input:focus{
outline: none;
border:1px solid rgb(0,200,80);
}
td input[type=text] + input[type=button]{
margin-left:10px;
padding:0px 10px;
background-color: rgb(0,200,80);
color:#fff;
font-size: 14px;
font-weight: bold;
border:none;
border-radius: 5px;
width:80px;
height:40px;
line-height:40px;
}
#result {
color: #e91e63;
font-weight: bold;
}
.pw_check{
padding:10px 15px;
margin:10px 0 0 0;
border:1px solid rgb(0,200,80);
background:#fff;
font-size:80%;
font-weight: bold;
width:530px;
}
.pw_check span{
display: block;
margin-left:10.45px;
}
.pw_check span::before{
content:'';
}
.pw_check .pct, .pw_check .pcf{
margin-left:0px;
}
.pw_check .pct::before{
content:'✔';
position: relative;
left:-5px;
}
.pw_check .pcf::before{
content:'✖';
position: relative;
left:-5px;
}
.pct{
color:rgb(0,200,80);
}
.pcf{
color:red;
}
.gender_area > td{
font-size:16px;
padding:5px 40px 5px 20px;
}
.gender_area > td > div{
display:flex;
align-items: center;
position: relative;
width:340px;
}
.gender_area ul{
list-style: none;
width:50%;
font-size:0;
margin: 0;
padding: 0;
}
.gender_area ul:last-of-type{
margin-left: 10px;
}
.gender_area li{
display: inline-block;
width:50%;
margin:0;
padding:0;
border-radius: 5px 0 0 5px;
}
.gender_area ul li:last-of-type{
border-radius: 0 5px 5px 0;
}
.gender_area li > input[type=radio]{
width:1px;
height:1px;
margin:-1px;
overflow:hidden;
position:absolute;
-webkit-appearance:none;
-moz-appearance:none;
appearance:none;
border: none;
}
.gender_area li > label{
display: block;
position: relative;
width: 100%;
padding: 6px 0;
border-radius: inherit;
border: 1px solid #ccc;
font-size: 13px;
line-height: 18px;
color: #757575;
text-align: center;
cursor: pointer;
}
.gender_area li > input[type=radio]:checked+label{
border: 1px solid rgb(0,200,80);
color:rgb(0,200,80);
}
.zipcode_area > td > input[type=text]{ width:200px; }
.zipcode_area > td > input[type=button]{ width:130px !important; }
.addr_area > td > input[type=text],
.addr_area + tr > td > input[type=text],
.addr_area + tr + tr > td > input[type=text]{
width:340px;
}
.hobby_area > td > div{
display:flex;
width:360px;
flex-wrap:wrap;
}
.hobby_input {
border-bottom: 1px solid #ccc;
padding-bottom: 10px;
}
.hobby_input > input[type=text]{
position:relative;
top:0;
}
.hobby_input > input[type=button] {
margin-left:20px;
padding:10px 10px;
background-color:rgb(0,200,80);
color:#fff;
font-size:14px;
font-weight: bold;
border:none;
border-radius:5px;
width:70px;
}
.hobby_area + tr > th{ text-align:center; }
.hobby_list{
display: flex;
flex-wrap: wrap;
}
.userhobby{
color: #666;
height:30px;
display:flex;
background-color: white;
margin-left:10px;
margin-top:5px;
border:1px solid #ccc;
border-radius:50px;
padding:3px 10px;
cursor: pointer;
align-items: center;
}
.xBox{
border:1px solid #ccc;
width:16px;
height:16px;
border-radius: 50%;
margin-left: 5px;
background:url("/user/images/xBox2.png") 3px 50% / 60% no-repeat;
}
.submit{
padding:10px 10px;
margin:30px;
background-color:rgb(0,200,80);
color:#fff;
font-size:20px;
font-weight: bold;
border:none;
border-radius:5px;
width:300px;
}
</style>
</head>
<body>
<div id="wrap">
<form name="joinForm" method="post" action="/user/join"
onsubmit="return sendit()">
<table>
<tr>
<td id="result" colspan="2"> </td>
</tr>
<tr>
<th><label for="userid">아이디</label></th>
<td><input type="text" name="userid" id="userid"><input type="button" value="중복검사" onclick="checkId()"></td>
</tr>
<tr>
<th><label for="userpw">비밀번호</label></th>
<td><input type="password" name="userpw" id="userpw" onkeyup="pwcheck()"></td>
</tr>
<tr>
<th><label for="userpw_re">비밀번호 확인</label></th>
<td><input type="password" name="userpw_re" id="userpw_re" onkeyup="pwcheck()"></td>
</tr>
<tr>
<th colspan="2">
<div class="pw_check">
<span>영어 대문자, 소문자, 숫자, 특수문자(~,?,!,@,-)를 모두 하나 이상 포함해야 해요 😃</span>
<span>최소 8자 이상의 비밀번호가 보안에 안전해요 😄</span>
<span>같은 문자가 연속해서 사용되지 않았어요 😆</span>
<span>사용할 수 없는 문자가 포함되지 않았어요 🙂</span>
<span>비밀번호 확인이 완료되었어요! 😊</span>
</div>
</th>
</tr>
<tr>
<th><label for="username">이름</label></th>
<td><input type="text" name="username" id="username"></td>
</tr>
<tr class="gender_area">
<th>성별</th>
<td>
<div>
<ul>
<li class="radio_item">
<input type="radio" id="usergender1" name="usergender" value="M"><label for="usergender1">남자</label>
</li>
<li class="radio_item">
<input type="radio" id="usergender2" name="usergender" value="W"><label for="usergender2">여자</label>
</li>
</ul>
<ul>
<li class="radio_item">
<input type="radio" id="foreigner1" name="foreigner" value="K"><label for="foreigner1">내국인</label>
</li>
<li class="radio_item">
<input type="radio" id="foreigner2" name="foreigner" value="F"><label for="foreigner2">외국인</label>
</li>
</ul>
</div>
</td>
</tr>
<tr class="zipcode_area">
<th>우편번호</th>
<td><input readonly name="zipcode" type="text"
id="sample6_postcode" placeholder="우편번호" onclick="sample6_execDaumPostcode()"><input
type="button" onclick="sample6_execDaumPostcode()" value="우편번호 찾기">
</td>
</tr>
<tr class="addr_area">
<th>주소</th>
<td><input readonly name="addr" type="text"
id="sample6_address" placeholder="주소"></td>
</tr>
<tr>
<th>상세주소</th>
<td><input name="addrdetail" type="text"
id="sample6_detailAddress" placeholder="상세주소"></td>
</tr>
<tr>
<th>참고항목</th>
<td><input readonly name="addretc" type="text"
id="sample6_extraAddress" placeholder="참고항목"></td>
</tr>
<tr class="hobby_area">
<th>취미</th>
<td>
<div>
<div class="hobby_input">
<input type="text" name="hobby" onkeyup="hobbyKeyup()"><input type="button" onclick="addHobby()" value="추가">
</div>
<div class="hobby_list"></div>
<input type="hidden" value="" name="userhobby">
</div>
</td>
</tr>
<tr>
<th colspan="2"><input class="submit" type="button" value="가입 완료" onclick="sendit();"></th>
</tr>
</table>
</form>
</div>
</body>
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<script th:src="@{/user/js/user.js}"></script>
<!-- 타임리프에서 경로 쓸 때는 th:src="@{경로}"-->
</html>
src/main/resources/static/js
src/main/resources/static/css
src/main/resources/static/images