오늘이라도
[Spring] 8. 웹사이트 만들기 ⑦ : 회원가입 완성, 축하 메일 발송 본문
반응형
https://github.com/upcake/Class_Examples
교육 중에 작성한 예제들은 깃허브에 올려두고 있습니다.
gif 파일은 클릭해서 보는 것이 정확합니다.
- 웹사이트 만들기 ⑦ : 회원가입 완성, 축하 메일 발송 -
package com.hanul.iot;
import java.util.HashMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import common.CommonService;
import member.MemberServiceImpl;
import member.MemberVO;
@Controller
public class MemberController {
@Autowired private MemberServiceImpl service;
@Autowired private CommonService common;
//로그인 요청
@ResponseBody @RequestMapping("/login")
public String login(String id, String pw, HttpSession session) {
//화면에서 입력한 아이디와 비밀번호가 일치하는 회원 정보가 DB에 있는지 확인하여
HashMap<String, String> map = new HashMap<String, String>();
map.put("id", id);
map.put("pw", pw);
MemberVO vo = service.member_login(map);
//일치하는 회원 정보가 있다면 회원 정보를 세션에 담는다
session.setAttribute("login_info", vo);
return vo == null ? "false" : "true";
}
//로그아웃 요청
@ResponseBody @RequestMapping("/logout")
public void logout(HttpSession session) {
session.removeAttribute("login_info");
}
//회원가입 화면 요청
@RequestMapping("/member")
public String member(HttpSession session) {
session.setAttribute("category", "join");
return "member/join";
}
//아이디 중복확인 요청
@ResponseBody @RequestMapping("/id_check")
public boolean id_check(String id) {
return service.member_id_check(id);
}
//회원가입 처리 요청
//RequestMapping에 한글이 깨지지않게 utf-8 설정
@ResponseBody @RequestMapping(value= "/join", produces= "text/html; charset= utf-8")
public String join(MemberVO vo, HttpServletRequest request) {
String msg = "<script type='text/javascript'>";
//화면에서 입력한 정보를 DB에 저장한 후 홈 화면으로 연결
if(service.member_insert(vo)) {
//메일 전송
common.sendEmail(vo.getEmail(), vo.getName());
msg += "alert('회원가입을 축하드립니다!'); location='" + request.getContextPath() + "'";
} else {
msg += "alert('회원가입에 실패했습니다!'); history.go(-1)";
}
msg += "</script>";
return msg;
}
}
/* 이클립스 디버깅
F5 해당 라인 시작, 함수가 있다면 함수 속으로 들어간다.
F6 해당 라인 시작, 함수가 있어도 들어가지 않고 결과만 가지고 현재 라인 끝까지 실행.
F7 현재 함수의 끝까지 실행하여 현재 함수를 벗어난다.
F8 다음 브레이크 포인트까지 한번엔 실행
*/
▲MemberControler.java
package member;
import java.util.HashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MemberServiceImpl implements MemberService {
@Autowired private MemberDAO dao;
@Override
public boolean member_insert(MemberVO vo) {
return dao.member_insert(vo);
}
@Override
public MemberVO member_select(String id) {
// TODO Auto-generated method stub
return null;
}
@Override
public MemberVO member_login(HashMap<String, String> map) {
return dao.member_login(map);
}
@Override
public boolean member_id_check(String id) {
return dao.member_id_check(id);
}
@Override
public boolean member_update(MemberVO vo) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean member_delete(String id) {
// TODO Auto-generated method stub
return false;
}
}
▲MemberSerbiceImpl.java
package member;
import java.util.HashMap;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class MemberDAO implements MemberService {
@Autowired private SqlSession sql; //이미 만들어둔 SqlSession을 이용하기 위해 Autowired를 사용한다
@Override
public boolean member_insert(MemberVO vo) {
return sql.insert("member.mapper.join", vo) == 0 ? false : true;
}
@Override
public MemberVO member_select(String id) {
// TODO Auto-generated method stub
return null;
}
@Override
public MemberVO member_login(HashMap<String, String> map) {
return sql.selectOne("member.mapper.login", map);
}
@Override
public boolean member_id_check(String id) {
return (Integer) sql.selectOne("member.mapper.id_check", id) == 0 ? true : false;
}
@Override
public boolean member_update(MemberVO vo) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean member_delete(String id) {
// TODO Auto-generated method stub
return false;
}
}
▲MemberDAO.java
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="member.mapper">
<select id="login" resultType="member.MemberVO">
SELECT * FROM member WHERE id=#{id} AND pw=#{pw}
</select>
<select id="id_check" resultType="integer">
SELECT COUNT(*) FROM member WHERE id=#{id}
</select>
<insert id="join">
INSERT INTO member(name, id, pw, gender, email, birth, post, addr, tel)
VALUES (#{name }, #{id }, #{pw }, #{gender }, #{email }, #{birth }, #{post }, #{addr }, #{tel })
</insert>
</mapper>
▲member-mapper.xml
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>join JSP</title>
<style type="text/css">
table tr td {
text-align: left;
}
table tr td input[name=tel] {
width: 40px;
}
table tr td input[name=addr] {
width: calc(100% - 14px);
/* {속성값 !important } 우선순위 지정 */
}
.ui-datepicker select {
vertical-align: middle;
height: 28px;
}
.valid, .invalid {
font-size: 11px;
font-weight: bold;
}
.valid { color: green; }
.invalid { color: red; }
}
</style>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
</head>
<body>
<h3>회원가입</h3>
<p class="w-pct60 right" style="margin: 0 auto; padding-bottom: 5px; font-size: 13px;">*는 필수 입력 항목입니다.</p>
<form action='join' method='post'>
<table class="w-pct60">
<tr>
<th class="w-px160">* 성명</th>
<td><input type="text" name="name" /></td>
</tr>
<tr>
<th>* 아이디</th>
<td>
<input type="text" title="아이디" name="id" class="chk"/><a id="btn-id" class='btn-fill-s'>중복 확인</a><br>
<div class='valid'>아이디를 입력하세요. (영문 소문자, 숫자만 입력 가능)</div>
</td>
</tr>
<tr>
<th>* 비밀번호</th>
<td>
<input type="password" title="비밀번호" name="pw" class="chk" />
<div class="valid">비밀번호를 입력하세요. (영문 대/소문자, 숫자를 모두 포함)</div>
</td>
</tr>
<tr>
<th>* 비밀번호 확인</th>
<td>
<input type="password" title="비밀번호 확인" name="pw_ck" class="chk" />
<div class="valid">비밀번호를 다시 입력하세요. </div>
</td>
</tr>
<tr>
<th>* 성별</th>
<td>
<label><input type="radio" name="gender" value="남" checked/>남</label>
<label><input type="radio" name="gender" value="여" />여</label>
</td>
</tr>
<tr>
<th>* 이메일</th>
<td>
<input type="text" title="이메일" name="email" class="chk" />
<div class="valid">이메일을 입력하세요.</div>
</td>
</tr>
<tr>
<th>생년월일</th>
<td>
<input type="text" name="birth" readonly />
<span id="delete" style="color: red; position: relative; right: 25px; display: none;"><i class="fas fa-times font-img"></i></span>
<!-- fontawesome에서 가져온 무료 아이콘 -->
</td>
</tr>
<tr>
<th>전화번호</th>
<td>
<input type="text" name="tel" /> -
<input type="text" name="tel" /> -
<input type="text" name="tel" />
</td>
</tr>
<tr>
<th>주소</th>
<td>
<a class='btn-fill-s' onclick="daum_post()">우편번호 찾기</a>
<input type="text" name="post" class="w-px60" readonly />
<input type="text" name="addr" readonly/>
<input type="text" name="addr" />
</td>
</tr>
</table>
</form>
<div class="btnSet">
<a class="btn-fill" onclick="go_join()">회원가입</a>
<a class="btn-empty" onclick="history.go(-1)">취소</a>
</div>
<!-- ?v=<new java.util.Date().getTime()>을 붙이면 기다릴 필요 없이 수정사항이 바로바로 새로고침이 된다.-->
<script type="text/javascript" src="js/join_check.js?v=<%=new java.util.Date().getTime()%>"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.1/js/all.min.js"></script> <!-- cdnjs.com에서 가져온 fontawesome cdn 라이브러리 -->
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> <!-- jQuery ui -->
<script src="https://t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script> <!-- 다음 우편번호 api -->
<script type="text/javascript">
//아이디 중복 검사
$('#btn-id').on('click', function() {
id_check();
});
//올바른 아이디 입력 형태인지 파악하여 유효하지 않다면 중복확인 불필요
function id_check() {
var $id = $('[name=id]');
if($id.hasClass('chked')) return;
console.log('go check');
var data = join.tag_status($id);
if(data.code != 'valid') {
alert('아이디 중복 확인 불필요\n' + data.desc);
$id.focus();
return;
}
$.ajax({
type: 'post',
url: 'id_check',
data: {id: $id.val()},
success: function(data) {
data = join.id_usable(data);
display_status($id.siblings('div'), data);
$id.addClass('chked');
},
error: function(req, text) {
alert(text + ': ' + req.status);
}
});
}
//유효성 검사
$('.chk').on('keyup', function(){
if($(this).attr('name') == 'id') {
if(event.keyCode == 13) { id_check(); }
else {
$(this).removeClass('chked');
validate( $(this) );
}
} else {
validate($(this));
}
});
function validate(t) {
var data = join.tag_status(t);
display_status(t.siblings('div'), data);
}
function display_status(div, data) {
div.text(data.desc);
div.removeClass();
div.addClass(data.code)
}
// 만 13세 이상만 선택 가능하게 처리
var today = new Date();
var endDay = new Date( today.getFullYear()-13, today.getMonth(), today.getDate() );
$('[name=birth]').datepicker({
dateFormat: 'yy-mm-dd',
changeYear: true,
changeMonth: true,
showMonthAfterYear: true,
dayNamesMin: ['일', '월', '화', '수', '목', '금', '토'],
monthNamesShort: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
maxDate: endDay
//beforeShowDay: after //오늘 이후로 선택 못하게 하는 함수
});
$('[name=birth]').change(function() {
$('#delete').css('display', 'inline-block');
});
$('#delete').click(function(){
$('[name=birth]').val('');
$('#delete').css('display', 'none');
});
function after(date) {
if(date > new Date()) {
return [false];
} else {
return [true];
}
}
function daum_post() {
new daum.Postcode({
oncomplete: function(data) {
$('[name=post]').val( data.zonecode ); //우편번호
//지번 주소 : J, 도로명 주소 : R
var address = data.userSelectedType == 'J' ? data.jibunAddress : data.roadAddress; //클릭한 지번주소나, 도로명주소가 저장됨
if(data.buildingName != '') {
address += ' (' + data.buildingName + ')'; //건물 명이 있으면 건물 명을 붙여줌
}
$('[name=addr]').eq(0).val( address );
}
}).open();
}
function go_join() {
if( $('[name = name]').val() == '') {
alert('성명을 입력하세요!');
$('[name = name]').focus();
return;
}
//필수 항목의 유효성을 판단하도록 한다.
//중복확인 한 경우
if($('[name=id]').hasClass('chked') ) {
//이미 사용중인 경우는 회원가입 불가
if($('[name = id]').siblings('div').hasClass('invalid')) {
alert('회원가입 불가\n' + join.id.unusable.desc);
$('[name=id]').focus();
return;
}
} else {
//중복확인 하지 않은 경우
if( !item_check($('[name=id]')) ) return;
else {
alert('회원가입 불가\n' + join.id.valid.desc);
$('[name=id]').focus();
return;
}
}
if(!item_check($('[name=pw]'))) return;
if(!item_check($('[name=pw_ck]'))) return;
if(!item_check($('[name=email]'))) return;
$('form').submit();
}
function item_check(item) {
var data = join.tag_status(item);
if(data.code == 'invalid') {
alert('회원가입 불가! \n' + data.desc);
item.focus();
return false;
} else return true;
}
</script>
</body>
</html>
▲join.jsp
package common;
import org.apache.commons.mail.MultiPartEmail;
import org.apache.commons.mail.SimpleEmail;
import org.springframework.stereotype.Service;
@Service
public class CommonService {
public void sendEmail(String email, String name) {
//1. 기본 이메일 전송 처리
//sendSimple(email, name);
//2. 첨부 파일 있는 이메일 전송 처리
sendAttach(email, name);
};
private void sendSimple(String email, String name) {
SimpleEmail mail = new SimpleEmail();
mail.setHostName("smtp.naver.com"); //메일 전송 서버 지정, 네이버 메일 - 환경설정 - pop3 설정
mail.setCharset("utf-8"); //인코딩 설정
mail.setDebug(true); //메일 전송 과정 추적해서 콘솔에 띄워줌
mail.setAuthentication("아이디", "비밀번호"); //로그인하기 위해 정보 입력
mail.setSSLOnConnect(true); //입력한 정보로 로그인 요청
try {
mail.setFrom("보내는 메일", "관리자"); //보내는 사람 메일 / 이름 설정
mail.addTo(email, name); //받는 사람 메일 / 이름, 회원가입 페이지에에서 가져온다.
mail.addTo("받을 메일", "수신자"); //복수의 사람 지정 가능
mail.setSubject("회원가입 축하"); //메일 제목
mail.setMsg(name + "님! 가입을 축하드립니다!"); //메일 내용
mail.send(); //메일 발송
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
private void sendAttach(String email, String name) {
MultiPartEmail mail = new MultiPartEmail();
mail.setHostName("smtp.naver.com"); //메일 전송 서버 지정, 네이버 메일 - 환경설정 - pop3 설정
mail.setCharset("utf-8"); //인코딩 설정
mail.setDebug(true); //메일 전송 과정 추적해서 콘솔에 띄워줌
mail.setAuthentication("아이디", "비밀번호"); //로그인하기 위해 정보 입력
mail.setSSLOnConnect(true); //입력한 정보로 로그인 요청
try {
mail.setFrom("보내는 메일", "관리자"); //보내는 사람 메일 / 이름 설정
mail.addTo(email, name); //받는 사람 메일 / 이름, 회원가입 페이지에에서 가져온다.
mail.addTo("받을 메일", "수신자"); //복수의 사람 지정 가능
mail.setSubject("회원가입 축하"); //메일 제목
mail.setMsg(name + "님! 가입을 축하드립니다!"); //메일 내용
mail.send(); //메일 발송
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
▲CommonService.java
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
<import resource="classpath:data/*.xml" />
<!-- context에 member를 적음으로써 Autowired의 스캔 범위에 member가 추가된다. -->
<!-- 20/07/09 common 추가 -->
<context:component-scan base-package="common, member, customer"/>
</beans>
▲root-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hanul</groupId>
<artifactId>iot</artifactId>
<name>iot</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<java-version>1.8</java-version>
<org.springframework-version>5.2.2.RELEASE</org.springframework-version>
<org.aspectj-version>1.6.10</org.aspectj-version>
<org.slf4j-version>1.6.6</org.slf4j-version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
<scope>runtime</scope>
</dependency>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<!-- 스프링에서 JDBC를 사용하기 위한 dependency -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp -->
<!-- DBCP dependency -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.oracle.ojdbc/ojdbc8 -->
<!-- 오라클용 jdbc ojdbc8 -->
<dependency>
<groupId>com.oracle.ojdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>19.3.0.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<!-- Mybatis 프레임워크를 사용하기 위한 dependency -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<!-- MyBatis 스프링 dependency -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.3</version>
</dependency>
<!-- 화면에 template를 지정하는 라이브러리 -->
<!-- https://mvnrepository.com/artifact/org.apache.tiles/tiles-jsp -->
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-jsp</artifactId>
<version>3.0.8</version>
</dependency>
<!-- boolean 타입 데이터를 문자 타입으로 자동 변환해주는 라이브러리 -->
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.0</version>
</dependency>
<!-- 이메일 전송 라이브러리 -->
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-email -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-email</artifactId>
<version>1.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<additionalProjectnatures>
<projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
</additionalProjectnatures>
<additionalBuildcommands>
<buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
</additionalBuildcommands>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.8</source>
<target>1.6</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<mainClass>org.test.int1.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
▲pom.xml
반응형
'취업성공패키지 SW 개발자 교육 > Spring' 카테고리의 다른 글
[Spring] 10. 웹사이트 만들기 ⑨ : 공지사항에 첨부 파일 기능 추가 (2) | 2020.07.13 |
---|---|
[Spring] 9. 웹사이트 만들기 ⑧ : 첨부 파일 메일, HTML 메일 발송, 공지사항 목록 조회 (1) | 2020.07.10 |
[Spring] 7. 웹사이트 만들기 ⑥ : 회원가입 양식 유효성 검사 (4) | 2020.07.08 |
[Spring] 6. 웹사이트 만들기 ⑤ : 회원가입 화면 구성, 우편 번호 API, 입력 칸 밑의 알림 문장, jQuery Ui Datepicker, fontawesome, cdnjs.com (0) | 2020.07.07 |
[Spring] 5. 웹사이트 만들기 ④ : 로그인, 로그아웃, 탭 이름 (0) | 2020.07.06 |