목차
-
MemberController.java 내용추가
-
view/member =>memberModify.jsp
-
WEB-INF/tile-def => member.xml 내용추가
-
MemberController.java 내용추가
-
webapp => image_bundle 폴더 생성 (기본 이미지 처리를 위해)
-
kr.spirng.util => FileUtil
-
MemberController.java 수정
-
kr.spring.view => ImageView
-
nav_mypage.jsp 오타수정
-
static/js=>member.profile.js
-
nav_mypage.jsp 내용추가
-
static/js=>member.profile.js 추가
-
MemberMapper.java와 MemberService.java에 명시
-
MemberMapper.java SQL문 명시
-
MemberAjaxController.java 내용추가
-
MemberMapper.java 내용추가
-
MemberServiceImpl.java 내용추가
-
MemberController.java 내용추가
-
tile-def=>member.xml 내용추가
-
member => memberChangePassword.jsp
-
MemberVO 내용추가(captcha 조건체크를 만들지않아서 만들어줌)
-
captcha 이미지 띄우기
-
MemberController.java 내용추가
MemberController.java 내용추가
/*===============================
* 회원정보 수정
===============================*/
//수정 폼 호출
@GetMapping("/member/update")
public String formUpdate(HttpSession session, Model model) {
MemberVO user = (MemberVO)session.getAttribute("user");
MemberVO memberVO = memberService.selectMember(user.getMem_num());
model.addAttribute("memberVO",memberVO);
return "memberModify";
}
view/member =>memberModify.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!-- 회원정보 수정 시작 -->
<div class="page-main">
<h2>회원정보 수정</h2>
<form:form action="update" id="member_modify"
modelAttribute="memberVO">
<ul>
<li>
<form:label path="name">이름</form:label>
<form:input path="name"/>
<form:errors path="name" cssClass="error-color"/>
</li>
<li>
<form:label path="nick_name">별명</form:label>
<form:input path="nick_name"/>
</li>
<li>
<form:label path="phone">전화번호</form:label>
<form:input path="phone"/>
<form:errors path="phone" cssClass="error-color"/>
</li>
<li>
<form:label path="email">이메일</form:label>
<form:input path="email"/>
<form:errors path="email" cssClass="error-color"/>
</li>
<li>
<form:label path="zipcode">우편번호</form:label>
<form:input path="zipcode"/>
<input type="button" onclick="execDaumPostcode()"
value="우편번호 찾기" class="default-btn">
<form:errors path="zipcode" cssClass="error-color"/>
</li>
<li>
<form:label path="address1">주소</form:label>
<form:input path="address1"/>
<form:errors path="address1" cssClass="error-color"/>
</li>
<li>
<form:label path="address2">상세주소</form:label>
<form:input path="address2"/>
<form:errors path="address2" cssClass="error-color"/>
</li>
</ul>
<div class="align-center">
<form:button class="default-btn">전송</form:button>
<input type="button" value="마이페이지"
class="default-btn"
onclick="location.href='myPage'"><!-- tile-def/member.xml에 있는 mypage를 가져옴 -->
</div>
</form:form>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-3.7.1.min.js"></script>
<!-- 우편번호 시작 -->
<!-- iOS에서는 position:fixed 버그가 있음, 적용하는 사이트에 맞게 position:absolute 등을 이용하여 top,left값 조정 필요 -->
<div id="layer" style="display:none;position:fixed;overflow:hidden;z-index:1;-webkit-overflow-scrolling:touch;">
<img src="//t1.daumcdn.net/postcode/resource/images/close.png" id="btnCloseLayer" style="cursor:pointer;position:absolute;right:-3px;top:-3px;z-index:1" onclick="closeDaumPostcode()" alt="닫기 버튼">
</div>
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<script>
// 우편번호 찾기 화면을 넣을 element
var element_layer = document.getElementById('layer');
function closeDaumPostcode() {
// iframe을 넣은 element를 안보이게 한다.
element_layer.style.display = 'none';
}
function execDaumPostcode() {
new daum.Postcode({
oncomplete: function(data) {
// 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
// 각 주소의 노출 규칙에 따라 주소를 조합한다.
// 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
var addr = ''; // 주소 변수
var extraAddr = ''; // 참고항목 변수
//사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
if (data.userSelectedType === 'R') { // 사용자가 도로명 주소를 선택했을 경우
addr = data.roadAddress;
} else { // 사용자가 지번 주소를 선택했을 경우(J)
addr = data.jibunAddress;
}
// 사용자가 선택한 주소가 도로명 타입일때 참고항목을 조합한다.
if(data.userSelectedType === 'R'){
// 법정동명이 있을 경우 추가한다. (법정리는 제외)
// 법정동의 경우 마지막 문자가 "동/로/가"로 끝난다.
if(data.bname !== '' && /[동|로|가]$/g.test(data.bname)){
extraAddr += data.bname;
}
// 건물명이 있고, 공동주택일 경우 추가한다.
if(data.buildingName !== '' && data.apartment === 'Y'){
extraAddr += (extraAddr !== '' ? ', ' + data.buildingName : data.buildingName);
}
// 표시할 참고항목이 있을 경우, 괄호까지 추가한 최종 문자열을 만든다.
if(extraAddr !== ''){
extraAddr = ' (' + extraAddr + ')';
}
//(주의)address1에 참고항목이 보여지도록 수정
// 조합된 참고항목을 해당 필드에 넣는다.
//(수정) document.getElementById("address2").value = extraAddr;
}
//(수정) else {
//(수정) document.getElementById("address2").value = '';
//(수정) }
// 우편번호와 주소 정보를 해당 필드에 넣는다.
document.getElementById('zipcode').value = data.zonecode;
//(수정) + extraAddr를 추가해서 address1에 참고항목이 보여지도록 수정
document.getElementById("address1").value = addr + extraAddr;
// 커서를 상세주소 필드로 이동한다.
document.getElementById("address2").focus();
// iframe을 넣은 element를 안보이게 한다.
// (autoClose:false 기능을 이용한다면, 아래 코드를 제거해야 화면에서 사라지지 않는다.)
element_layer.style.display = 'none';
},
width : '100%',
height : '100%',
maxSuggestItems : 5
}).embed(element_layer);
// iframe을 넣은 element를 보이게 한다.
element_layer.style.display = 'block';
// iframe을 넣은 element의 위치를 화면의 가운데로 이동시킨다.
initLayerPosition();
}
// 브라우저의 크기 변경에 따라 레이어를 가운데로 이동시키고자 하실때에는
// resize이벤트나, orientationchange이벤트를 이용하여 값이 변경될때마다 아래 함수를 실행 시켜 주시거나,
// 직접 element_layer의 top,left값을 수정해 주시면 됩니다.
function initLayerPosition(){
var width = 300; //우편번호서비스가 들어갈 element의 width
var height = 400; //우편번호서비스가 들어갈 element의 height
var borderWidth = 5; //샘플에서 사용하는 border의 두께
// 위에서 선언한 값들을 실제 element에 넣는다.
element_layer.style.width = width + 'px';
element_layer.style.height = height + 'px';
element_layer.style.border = borderWidth + 'px solid';
// 실행되는 순간의 화면 너비와 높이 값을 가져와서 중앙에 뜰 수 있도록 위치를 계산한다.
element_layer.style.left = (((window.innerWidth || document.documentElement.clientWidth) - width)/2 - borderWidth) + 'px';
element_layer.style.top = (((window.innerHeight || document.documentElement.clientHeight) - height)/2 - borderWidth) + 'px';
}
</script>
<!-- 우편번호 끝 -->
</div>
<!-- 회원정보 수정 끝 -->
WEB-INF/tile-def => member.xml 내용추가
<definition name="memberModify" extends="myPage">
<put-attribute name="title" value="회원정보수정"/>
<put-attribute name="body" value="/WEB-INF/views/member/memberModify.jsp"/>
</definition>

MemberController.java 내용추가
//수정 폼에서 전송된 데이터 처리
@PostMapping("/member/update")
public String submitUpdate(@Valid MemberVO memberVO, BindingResult result, HttpSession session) {
log.debug("<<회원정보 수정>> :"+ memberVO);
//유효성 체크 결과 오류가 있으면 폼 호출
if(result.hasErrors()) {
return "memberModify";
}
MemberVO user = (MemberVO)session.getAttribute("user");
memberVO.setMem_num(user.getMem_num());
//회원정보 수정
memberService.updateMember(memberVO);
//세션에 저장된 정보 변경(바꾸지않으면 기존정보로 바꾸면 수정되도록)
user.setNick_name(memberVO.getNick_name());
user.setEmail(memberVO.getEmail());
return "redirect:/member/myPage";
}


수정 및 유효성 처리가 잘 되는 것을 볼 수 있음
webapp => image_bundle 폴더 생성 (기본 이미지 처리를 위해)
기본 이미지 처리를 하려면 webapp에 이미지를 넣어야된다. static/images에 넣으면 결과로 나오지않는다.

kr.spirng.util => FileUtil
package kr.spring.util;
import java.io.FileInputStream;
import java.io.IOException;
import org.springframework.core.style.ToStringCreator;
import lombok.extern.slf4j.Slf4j;
@Slf4j //로그 처리를위해
public class FileUtil {
//지정한 경로의 파일을 읽어들여 byte 배열로 변환
public static byte[] getbytes(String path) {
FileInputStream fis = null;
byte[] readbyte = null;
try {
fis = new FileInputStream(path);
readbyte = new byte[fis.available()];
fis.read(readbyte);
}catch(Exception e) {
log.error(e.toString());
}finally {
if(fis!=null)try {fis.close();}catch(IOException e) {}
}
return readbyte;
}
}
MemberController.java 수정
/*===============================
* 프로필 사진 출력
===============================*/
//프로필 사진출력(로그인 전용)
@GetMapping("/member/photoView")
public String getProfile(HttpSession session,HttpServletRequest request, Model model ) {
MemberVO user = (MemberVO)session.getAttribute("user");
log.debug("<<프로필 사진 출력>>"+user);
if(user==null) {//로그인이 되지 않은 경우 (정확히는 로그인이 풀린경우)
getBasicProfileImage(request, model);
}else {//로그인이 된 경우(로그인이 되어있는 상태)
MemberVO memberVO = memberService.selectMember(user.getMem_num());
viewProfile(memberVO, request, model);
}
return "imageView";
}
//프로필 사진 처리를 위한 공통 코드
public void viewProfile(MemberVO memberVO, HttpServletRequest request, Model model) {
if(memberVO==null || memberVO.getPhoto_name()==null) {
//DB에 저장된 프로필 이미지가 없기 때문에 기본 이미지 호출
getBasicProfileImage(request, model); //바로 아래에 있는 메서드를 호출
}else {
//업로드한 프로필 이미지 읽기
model.addAttribute("imageFile",memberVO.getPhoto());
model.addAttribute("filename",memberVO.getPhoto_name());
}
}
//기본 이미지 읽기
public void getBasicProfileImage(HttpServletRequest request, Model model) {
byte[] readbyte = FileUtil.getbytes(request.getServletContext().getRealPath(
"/image_bundle/face.png"));
model.addAttribute("imageFile",readbyte);
model.addAttribute("filename","face.png");
}
kr.spring.view => ImageView
package kr.spring.view;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.AbstractView;
@Component
public class ImageView extends AbstractView{
@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
byte[] file = (byte[])model.get("imageFile");
String filename= (String)model.get("filename");
String ext = filename.substring(filename.lastIndexOf(".")); //face.png: 확장자 앞에 .이 있기 때문에 .으로 구분하여 확장자 추출
if(ext.equalsIgnoreCase(".gif")) { //확장자가 .GIF인 경우도 있어서 대소문자 구문 없이
ext="image/gif";
}else if(ext.equalsIgnoreCase(".png")){
ext="image/png";
}else {
ext = "image/jpeg";
}
response.setContentType(ext);
response.setContentLength(file.length);
String file_name = new String(filename.getBytes("utf-8"),"iso-8859-1");
response.setHeader("Content-Disposition","attachment; filename=\""+file_name+"\"");
response.setHeader("Content-Transfer-Encoding","binary");
OutputStream out = response.getOutputStream();
InputStream input = null;
try {
input = new ByteArrayInputStream(file);
IOUtils.copy(input, out);
out.flush();
}finally {
if(out!=null)out.close();
if(input!=null)input.close();
}
}
}

nav_mypage.jsp 오타수정
onclick부분에 location을 명시하지않았어서 명시해줌
<ul>
<li><input type="button" class="menu-btn" value="비밀번호변경"
onclick="location.href='changePassword'">
</li>
<li><input type="button" class="menu-btn" value="회원탈퇴"
onclick="location.href='delete'">
</li>
</ul>


static/js=>member.profile.js
$(function(){
/*===================
* MY페이지 프로필 사진 등록 및 수정
*===================*/
//수정 버튼 이벤트 처리
$('#photo_btn').click(function(){
$('#photo_choice').show();
$(this).hide();
});
});
- 버튼을 클릭하면 사진을 보여주고 버튼을 가린다
nav_mypage.jsp 내용추가
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-3.7.1.min.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/member.profile.js"></script>
<!-- MyPage 메뉴 끝 -->


static/js=>member.profile.js 추가
//처음 화면에 보여지는 이미지 읽기
let photo_path = $('.my-photo').attr('src');
let my_photo; //업로드 하고자 선택한 이미지 저장
//파일 선택 이벤트 연결
$('#upload').change(function(){
my_photo = this.files[0]; //선택한 이미지 저장
if(!my_photo){
$('.my-photo').attr('src',photo_path);
return;
}
if(my_photo.size > 1024*1024){
alert(Math.round(my_photo.size/1024)+'kbytes(1024kbytes까지만 업로드 가능)');
$('.my-photo').attr('src',photo_path);
$(this).val('');
return;
}
//이미지 미리보기 처리
const reader = new FileReader();
reader.readAsDataURL(my_photo);
reader.onload=function(){
$('.my-photo').attr('src',reader.result);
};
});//end of change


//파일 업로드 처리
$('#photo_submit').click(function(){
if($('#upload').val()==''){
alert('파일을 선택하세요!');
$('#upload').focus();
return;
}
//서버에 전송할 파일 선택
const form_data = new FormData();
form_data.append('upload',my_photo);
//서버와 통신
$.ajax({
url:'../member/updateMyPhoto',
type:'post',
data:form_data,
dataType:'json',
contentType:false,
processData:false,
success:function(param){
if(param.result == 'logout'){
alert('로그인 후 사용하세요');
}else if(param.result =='success'){
alert('프로필 사진이 수정되었습니다.');
//교체된 이미지 저장
photo_path = $('.my-photo').attr('src');
$('#upload').val('');
$('#photo_choice').hide();
$('#photo_btn').show();
}else{
alert('파일 전송 오류 발생');
}
},
error:function(){
alert('네트워크 오류발생')
}
});
});//end of click - 파일 전송
//취소버튼 처리
$('#photo_reset').click(function(){
$('.my-photo').attr('src',photo_path);
$('#upload').val('');
$('#photo_choice').hide();
$('#photo_btn').show();
});//end of click - 취소 버튼 처리
MemberMapper.java와 MemberService.java에 명시
//자동 로그인 처리
public void updateAu_id(String au_id,Long mem_num);
public void selectAu_id(String au_id);
public void deleteAu_id(Long mem_num);
//비밀번호 찾기
public void updateRandomPassword(MemberVO member);
//프로필 이미지 업데이트
public void updateProfile(MemberVO member);
MemberServiceImpl 역시 add method 추가
MemberMapper.java SQL문 명시
//프로필 이미지 업데이트
@Update("UPDATE spmember_detail SET photo=#{photo},photo_name=#{photo_name} WHERE mem_num =#{mem_num}")
public void updateProfile(MemberVO member);
MemberServiceImpl
@Override
public void updateProfile(MemberVO member) {
memberMapper.updateProfile(member);
}
MemberAjaxController.java 내용추가
//프로필 사진 업로드 작업
@PostMapping("/member/updateMyPhoto")
@ResponseBody
public Map<String,String> processProfile(MemberVO memberVO,HttpSession session){
Map<String,String> mapAjax = new HashMap<String,String>();
MemberVO user = (MemberVO)session.getAttribute("user");
if(user==null) {
mapAjax.put("result","logout");
}else {
memberVO.setMem_num(user.getMem_num());
memberService.updateProfile(memberVO);
mapAjax.put("result","success");
}
return mapAjax;
}

header.jsp
마이페이지 버튼 옆에 프로필사진 보이게하려고함.
링크는 controller에서 지정해둔 /member/photoView를 지정
<img src="${pageContext.request.contextPath}/member/photoView" width="25" height="25" class="my-photo">

MemberMapper.java 내용추가
@Update("UPDATE spmember_detail SET passwd=#{passwd} WHERE mem_num=#{mem_num}")
public void updatePassword(MemberVO member);
MemberServiceImpl.java 내용추가
@Override
public void updatePassword(MemberVO member) {
memberMapper.updatePassword(member);
}
MemberController.java 내용추가
/*===============================
* 비밀번호 변경
===============================*/
//비밀번호 변경 폼 호출
@GetMapping("/member/changePassword")
public String formChangePasswd() {
return "memberChangePassword";
}
tile-def=>member.xml 내용추가
<definition name="memberChangePassword" extends="myPage">
<put-attribute name="title" value="비밀번호 변경"/>
<put-attribute name="body" value="/WEB-INF/views/member/memberChangePassword.jsp"/>
</definition>
member => memberChangePassword.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!-- 비밀번호 변경 시작 -->
<div class="page-main">
<h2>비밀번호 변경</h2>
<form:form action="changePassword" id="member_change" modelAttribute="memberVO">
<form:errors element="div" cssClass="error-color"/>
<ul>
<li>
<form:label path="now_passwd">현재 비밀번호</form:label>
<form:password path="now_passwd"/>
<form:errors path="now_passwd" cssClass="error-color"/>
</li>
<li>
<form:label path="passwd">비밀번호</form:label>
<form:password path="passwd"/>
<form:errors path="passwd" cssClass="error-color"/>
</li>
<li>
<!-- 자바빈에 없기 때문에 기본 태그를 사용해야됨. 비밀번호와 같은지 확인용도. JavaScript로 확인 -->
<label for="confirm_passwd">새비밀번호 확인</label>
<input type="password" id="confirm_passwd">
<span id="message_password"></span>
</li>
<li>
<div id="captcha_div">
<img src="getCaptcha" id="captcha_img" width="200" height="90">
</div>
<input type="button" value="새로고침" id="reload_btn">
<!-- 새로고침을 누르면 위 이미지 태그가 리로드되도록 -->
<script>
$(function() {
$('#reload_btn').click(function() {
$.ajax({
url: 'getCaptcha',
type: 'get',
success: function() { //captcha_div는 캡챠의 이미지주소가 있는 div이다.
//' #captcha_div' #앞에 공백이 없으면 페이지 전체로 인식하기 때문에
//한 칸 띄우고 작성해야된다.
$('#captcha_div').load(location.href + ' #captcha_div');
},
error: function() {
alert('네트워크 오류 발생');
}
});
});
});
</script>
</li>
<li>
<form:label path="captcha_chars">캡챠 문자 확인</form:label>
<form:input path="captcha_chars"/>
<form:errors path="captcha_chars" cssClass="error-color"/>
</li>
</ul>
<div class="align-center">
<form:button>전송</form:button>
<input type="button" value="MY페이지" onclick="location.href='myPage'">
</div>
</form:form>
</div>
<!-- 비밀번호 변경 끝 -->
MemberVO 내용추가(captcha 조건체크를 만들지않아서 만들어줌)
//비밀번호 변경에만 조건체크
@Pattern(regexp="^[A-za-z0-9]$")
private String captcha_chars;

captcha 이미지 띄우기
CaptchaUtil.java
0.00MB
네이버에서 공유하는 captchaUtil. api키만 받으면 사용가능
kr.spring.util에 넣으면된다.




MemberController.java 내용추가
//비밀번호 변경 폼에서 전송된 데이터 처리
/*===============================
* 네이버 captcha(캡챠) API 사용
===============================*/
//캡챠 이미지 호출
@GetMapping("/member/getCaptcha")
public String getCaptcha(Model model, HttpSession session) {
String clientId = "Cc1wS4rT2GXzIGRPGXca";
String clientSecret = "AHEsY00rCk";
String code ="0"; //키 발급시 0, 캡챠 이미지 비교시 1로 세팅
String key_apiURL = "https://openapi.naver.com/v1/captcha/nkey?code="+code;
Map<String,String> requestHeaders = new HashMap<String,String>();
requestHeaders.put("X-Naver-Client-Id",clientId);
requestHeaders.put("X-Naver-Client-Secret",clientSecret);
String responseBody = CaptchaUtil.get(key_apiURL, requestHeaders);
log.debug("<<responseBody>> :"+responseBody);
JSONObject jObject = new JSONObject(responseBody);
try {
//https://openapi.naver.com/v1/captcha/nkey 호출로 받은 키값("key")을 넣어준다.
String key = jObject.getString("key");
//이미지의 나온 값과 내가입력한 값이 맞는지 확인하기 위해 키값을 세션에 저장해둠
session.setAttribute("captcha_key", key);
String apiURL = "https://openapi.naver.com/v1/captcha/ncaptcha.bin?key="+key;
Map<String,String> requestHeaders2 = new HashMap<String,String>();
requestHeaders.put("X-Naver-Client-Id",clientId);
requestHeaders.put("X-Naver-Client-Secret",clientSecret);
byte[] reponse_byte = CaptchaUtil.getCaptchaImage(apiURL, requestHeaders2);
model.addAttribute("imageFile",reponse_byte);
model.addAttribute("filename","captcha.jpg");
}catch(Exception e) {
log.error(e.toString());
}
return "imageView";
}
MemberController 코드 중 JSONObject jObject = new JSONObject();
이부분이 라이브러리가 없어서 pom.xml에 라이브러리를 추가해야됨.
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20230227</version>
</dependency>
<!-- 라이브러리 추가 끝 -->


'쌍용교육(JAVA) > SpringBoot' 카테고리의 다른 글
쌍용교육 -JSP수업 94일차 ch15SpringPage(5) (0) | 2024.07.01 |
---|---|
쌍용교육 -JSP수업 93일차 ch15SpringPage(4) (0) | 2024.06.28 |
쌍용교육 -JSP수업 91일차 ch15SpringPage(2) (0) | 2024.06.26 |
쌍용교육 -JSP수업 90일차 ch15SpringPage (0) | 2024.06.25 |
쌍용교육 -JSP수업 89일차 ch14SpringThymeleadf(2) (0) | 2024.06.25 |
MemberController.java 내용추가
/*===============================
* 회원정보 수정
===============================*/
//수정 폼 호출
@GetMapping("/member/update")
public String formUpdate(HttpSession session, Model model) {
MemberVO user = (MemberVO)session.getAttribute("user");
MemberVO memberVO = memberService.selectMember(user.getMem_num());
model.addAttribute("memberVO",memberVO);
return "memberModify";
}
view/member =>memberModify.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!-- 회원정보 수정 시작 -->
<div class="page-main">
<h2>회원정보 수정</h2>
<form:form action="update" id="member_modify"
modelAttribute="memberVO">
<ul>
<li>
<form:label path="name">이름</form:label>
<form:input path="name"/>
<form:errors path="name" cssClass="error-color"/>
</li>
<li>
<form:label path="nick_name">별명</form:label>
<form:input path="nick_name"/>
</li>
<li>
<form:label path="phone">전화번호</form:label>
<form:input path="phone"/>
<form:errors path="phone" cssClass="error-color"/>
</li>
<li>
<form:label path="email">이메일</form:label>
<form:input path="email"/>
<form:errors path="email" cssClass="error-color"/>
</li>
<li>
<form:label path="zipcode">우편번호</form:label>
<form:input path="zipcode"/>
<input type="button" onclick="execDaumPostcode()"
value="우편번호 찾기" class="default-btn">
<form:errors path="zipcode" cssClass="error-color"/>
</li>
<li>
<form:label path="address1">주소</form:label>
<form:input path="address1"/>
<form:errors path="address1" cssClass="error-color"/>
</li>
<li>
<form:label path="address2">상세주소</form:label>
<form:input path="address2"/>
<form:errors path="address2" cssClass="error-color"/>
</li>
</ul>
<div class="align-center">
<form:button class="default-btn">전송</form:button>
<input type="button" value="마이페이지"
class="default-btn"
onclick="location.href='myPage'"><!-- tile-def/member.xml에 있는 mypage를 가져옴 -->
</div>
</form:form>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-3.7.1.min.js"></script>
<!-- 우편번호 시작 -->
<!-- iOS에서는 position:fixed 버그가 있음, 적용하는 사이트에 맞게 position:absolute 등을 이용하여 top,left값 조정 필요 -->
<div id="layer" style="display:none;position:fixed;overflow:hidden;z-index:1;-webkit-overflow-scrolling:touch;">
<img src="//t1.daumcdn.net/postcode/resource/images/close.png" id="btnCloseLayer" style="cursor:pointer;position:absolute;right:-3px;top:-3px;z-index:1" onclick="closeDaumPostcode()" alt="닫기 버튼">
</div>
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<script>
// 우편번호 찾기 화면을 넣을 element
var element_layer = document.getElementById('layer');
function closeDaumPostcode() {
// iframe을 넣은 element를 안보이게 한다.
element_layer.style.display = 'none';
}
function execDaumPostcode() {
new daum.Postcode({
oncomplete: function(data) {
// 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
// 각 주소의 노출 규칙에 따라 주소를 조합한다.
// 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
var addr = ''; // 주소 변수
var extraAddr = ''; // 참고항목 변수
//사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
if (data.userSelectedType === 'R') { // 사용자가 도로명 주소를 선택했을 경우
addr = data.roadAddress;
} else { // 사용자가 지번 주소를 선택했을 경우(J)
addr = data.jibunAddress;
}
// 사용자가 선택한 주소가 도로명 타입일때 참고항목을 조합한다.
if(data.userSelectedType === 'R'){
// 법정동명이 있을 경우 추가한다. (법정리는 제외)
// 법정동의 경우 마지막 문자가 "동/로/가"로 끝난다.
if(data.bname !== '' && /[동|로|가]$/g.test(data.bname)){
extraAddr += data.bname;
}
// 건물명이 있고, 공동주택일 경우 추가한다.
if(data.buildingName !== '' && data.apartment === 'Y'){
extraAddr += (extraAddr !== '' ? ', ' + data.buildingName : data.buildingName);
}
// 표시할 참고항목이 있을 경우, 괄호까지 추가한 최종 문자열을 만든다.
if(extraAddr !== ''){
extraAddr = ' (' + extraAddr + ')';
}
//(주의)address1에 참고항목이 보여지도록 수정
// 조합된 참고항목을 해당 필드에 넣는다.
//(수정) document.getElementById("address2").value = extraAddr;
}
//(수정) else {
//(수정) document.getElementById("address2").value = '';
//(수정) }
// 우편번호와 주소 정보를 해당 필드에 넣는다.
document.getElementById('zipcode').value = data.zonecode;
//(수정) + extraAddr를 추가해서 address1에 참고항목이 보여지도록 수정
document.getElementById("address1").value = addr + extraAddr;
// 커서를 상세주소 필드로 이동한다.
document.getElementById("address2").focus();
// iframe을 넣은 element를 안보이게 한다.
// (autoClose:false 기능을 이용한다면, 아래 코드를 제거해야 화면에서 사라지지 않는다.)
element_layer.style.display = 'none';
},
width : '100%',
height : '100%',
maxSuggestItems : 5
}).embed(element_layer);
// iframe을 넣은 element를 보이게 한다.
element_layer.style.display = 'block';
// iframe을 넣은 element의 위치를 화면의 가운데로 이동시킨다.
initLayerPosition();
}
// 브라우저의 크기 변경에 따라 레이어를 가운데로 이동시키고자 하실때에는
// resize이벤트나, orientationchange이벤트를 이용하여 값이 변경될때마다 아래 함수를 실행 시켜 주시거나,
// 직접 element_layer의 top,left값을 수정해 주시면 됩니다.
function initLayerPosition(){
var width = 300; //우편번호서비스가 들어갈 element의 width
var height = 400; //우편번호서비스가 들어갈 element의 height
var borderWidth = 5; //샘플에서 사용하는 border의 두께
// 위에서 선언한 값들을 실제 element에 넣는다.
element_layer.style.width = width + 'px';
element_layer.style.height = height + 'px';
element_layer.style.border = borderWidth + 'px solid';
// 실행되는 순간의 화면 너비와 높이 값을 가져와서 중앙에 뜰 수 있도록 위치를 계산한다.
element_layer.style.left = (((window.innerWidth || document.documentElement.clientWidth) - width)/2 - borderWidth) + 'px';
element_layer.style.top = (((window.innerHeight || document.documentElement.clientHeight) - height)/2 - borderWidth) + 'px';
}
</script>
<!-- 우편번호 끝 -->
</div>
<!-- 회원정보 수정 끝 -->
WEB-INF/tile-def => member.xml 내용추가
<definition name="memberModify" extends="myPage">
<put-attribute name="title" value="회원정보수정"/>
<put-attribute name="body" value="/WEB-INF/views/member/memberModify.jsp"/>
</definition>

MemberController.java 내용추가
//수정 폼에서 전송된 데이터 처리
@PostMapping("/member/update")
public String submitUpdate(@Valid MemberVO memberVO, BindingResult result, HttpSession session) {
log.debug("<<회원정보 수정>> :"+ memberVO);
//유효성 체크 결과 오류가 있으면 폼 호출
if(result.hasErrors()) {
return "memberModify";
}
MemberVO user = (MemberVO)session.getAttribute("user");
memberVO.setMem_num(user.getMem_num());
//회원정보 수정
memberService.updateMember(memberVO);
//세션에 저장된 정보 변경(바꾸지않으면 기존정보로 바꾸면 수정되도록)
user.setNick_name(memberVO.getNick_name());
user.setEmail(memberVO.getEmail());
return "redirect:/member/myPage";
}


수정 및 유효성 처리가 잘 되는 것을 볼 수 있음
webapp => image_bundle 폴더 생성 (기본 이미지 처리를 위해)
기본 이미지 처리를 하려면 webapp에 이미지를 넣어야된다. static/images에 넣으면 결과로 나오지않는다.

kr.spirng.util => FileUtil
package kr.spring.util;
import java.io.FileInputStream;
import java.io.IOException;
import org.springframework.core.style.ToStringCreator;
import lombok.extern.slf4j.Slf4j;
@Slf4j //로그 처리를위해
public class FileUtil {
//지정한 경로의 파일을 읽어들여 byte 배열로 변환
public static byte[] getbytes(String path) {
FileInputStream fis = null;
byte[] readbyte = null;
try {
fis = new FileInputStream(path);
readbyte = new byte[fis.available()];
fis.read(readbyte);
}catch(Exception e) {
log.error(e.toString());
}finally {
if(fis!=null)try {fis.close();}catch(IOException e) {}
}
return readbyte;
}
}
MemberController.java 수정
/*===============================
* 프로필 사진 출력
===============================*/
//프로필 사진출력(로그인 전용)
@GetMapping("/member/photoView")
public String getProfile(HttpSession session,HttpServletRequest request, Model model ) {
MemberVO user = (MemberVO)session.getAttribute("user");
log.debug("<<프로필 사진 출력>>"+user);
if(user==null) {//로그인이 되지 않은 경우 (정확히는 로그인이 풀린경우)
getBasicProfileImage(request, model);
}else {//로그인이 된 경우(로그인이 되어있는 상태)
MemberVO memberVO = memberService.selectMember(user.getMem_num());
viewProfile(memberVO, request, model);
}
return "imageView";
}
//프로필 사진 처리를 위한 공통 코드
public void viewProfile(MemberVO memberVO, HttpServletRequest request, Model model) {
if(memberVO==null || memberVO.getPhoto_name()==null) {
//DB에 저장된 프로필 이미지가 없기 때문에 기본 이미지 호출
getBasicProfileImage(request, model); //바로 아래에 있는 메서드를 호출
}else {
//업로드한 프로필 이미지 읽기
model.addAttribute("imageFile",memberVO.getPhoto());
model.addAttribute("filename",memberVO.getPhoto_name());
}
}
//기본 이미지 읽기
public void getBasicProfileImage(HttpServletRequest request, Model model) {
byte[] readbyte = FileUtil.getbytes(request.getServletContext().getRealPath(
"/image_bundle/face.png"));
model.addAttribute("imageFile",readbyte);
model.addAttribute("filename","face.png");
}
kr.spring.view => ImageView
package kr.spring.view;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.AbstractView;
@Component
public class ImageView extends AbstractView{
@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
byte[] file = (byte[])model.get("imageFile");
String filename= (String)model.get("filename");
String ext = filename.substring(filename.lastIndexOf(".")); //face.png: 확장자 앞에 .이 있기 때문에 .으로 구분하여 확장자 추출
if(ext.equalsIgnoreCase(".gif")) { //확장자가 .GIF인 경우도 있어서 대소문자 구문 없이
ext="image/gif";
}else if(ext.equalsIgnoreCase(".png")){
ext="image/png";
}else {
ext = "image/jpeg";
}
response.setContentType(ext);
response.setContentLength(file.length);
String file_name = new String(filename.getBytes("utf-8"),"iso-8859-1");
response.setHeader("Content-Disposition","attachment; filename=\""+file_name+"\"");
response.setHeader("Content-Transfer-Encoding","binary");
OutputStream out = response.getOutputStream();
InputStream input = null;
try {
input = new ByteArrayInputStream(file);
IOUtils.copy(input, out);
out.flush();
}finally {
if(out!=null)out.close();
if(input!=null)input.close();
}
}
}

nav_mypage.jsp 오타수정
onclick부분에 location을 명시하지않았어서 명시해줌
<ul>
<li><input type="button" class="menu-btn" value="비밀번호변경"
onclick="location.href='changePassword'">
</li>
<li><input type="button" class="menu-btn" value="회원탈퇴"
onclick="location.href='delete'">
</li>
</ul>


static/js=>member.profile.js
$(function(){
/*===================
* MY페이지 프로필 사진 등록 및 수정
*===================*/
//수정 버튼 이벤트 처리
$('#photo_btn').click(function(){
$('#photo_choice').show();
$(this).hide();
});
});
- 버튼을 클릭하면 사진을 보여주고 버튼을 가린다
nav_mypage.jsp 내용추가
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-3.7.1.min.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/member.profile.js"></script>
<!-- MyPage 메뉴 끝 -->


static/js=>member.profile.js 추가
//처음 화면에 보여지는 이미지 읽기
let photo_path = $('.my-photo').attr('src');
let my_photo; //업로드 하고자 선택한 이미지 저장
//파일 선택 이벤트 연결
$('#upload').change(function(){
my_photo = this.files[0]; //선택한 이미지 저장
if(!my_photo){
$('.my-photo').attr('src',photo_path);
return;
}
if(my_photo.size > 1024*1024){
alert(Math.round(my_photo.size/1024)+'kbytes(1024kbytes까지만 업로드 가능)');
$('.my-photo').attr('src',photo_path);
$(this).val('');
return;
}
//이미지 미리보기 처리
const reader = new FileReader();
reader.readAsDataURL(my_photo);
reader.onload=function(){
$('.my-photo').attr('src',reader.result);
};
});//end of change


//파일 업로드 처리
$('#photo_submit').click(function(){
if($('#upload').val()==''){
alert('파일을 선택하세요!');
$('#upload').focus();
return;
}
//서버에 전송할 파일 선택
const form_data = new FormData();
form_data.append('upload',my_photo);
//서버와 통신
$.ajax({
url:'../member/updateMyPhoto',
type:'post',
data:form_data,
dataType:'json',
contentType:false,
processData:false,
success:function(param){
if(param.result == 'logout'){
alert('로그인 후 사용하세요');
}else if(param.result =='success'){
alert('프로필 사진이 수정되었습니다.');
//교체된 이미지 저장
photo_path = $('.my-photo').attr('src');
$('#upload').val('');
$('#photo_choice').hide();
$('#photo_btn').show();
}else{
alert('파일 전송 오류 발생');
}
},
error:function(){
alert('네트워크 오류발생')
}
});
});//end of click - 파일 전송
//취소버튼 처리
$('#photo_reset').click(function(){
$('.my-photo').attr('src',photo_path);
$('#upload').val('');
$('#photo_choice').hide();
$('#photo_btn').show();
});//end of click - 취소 버튼 처리
MemberMapper.java와 MemberService.java에 명시
//자동 로그인 처리
public void updateAu_id(String au_id,Long mem_num);
public void selectAu_id(String au_id);
public void deleteAu_id(Long mem_num);
//비밀번호 찾기
public void updateRandomPassword(MemberVO member);
//프로필 이미지 업데이트
public void updateProfile(MemberVO member);
MemberServiceImpl 역시 add method 추가
MemberMapper.java SQL문 명시
//프로필 이미지 업데이트
@Update("UPDATE spmember_detail SET photo=#{photo},photo_name=#{photo_name} WHERE mem_num =#{mem_num}")
public void updateProfile(MemberVO member);
MemberServiceImpl
@Override
public void updateProfile(MemberVO member) {
memberMapper.updateProfile(member);
}
MemberAjaxController.java 내용추가
//프로필 사진 업로드 작업
@PostMapping("/member/updateMyPhoto")
@ResponseBody
public Map<String,String> processProfile(MemberVO memberVO,HttpSession session){
Map<String,String> mapAjax = new HashMap<String,String>();
MemberVO user = (MemberVO)session.getAttribute("user");
if(user==null) {
mapAjax.put("result","logout");
}else {
memberVO.setMem_num(user.getMem_num());
memberService.updateProfile(memberVO);
mapAjax.put("result","success");
}
return mapAjax;
}

header.jsp
마이페이지 버튼 옆에 프로필사진 보이게하려고함.
링크는 controller에서 지정해둔 /member/photoView를 지정
<img src="${pageContext.request.contextPath}/member/photoView" width="25" height="25" class="my-photo">

MemberMapper.java 내용추가
@Update("UPDATE spmember_detail SET passwd=#{passwd} WHERE mem_num=#{mem_num}")
public void updatePassword(MemberVO member);
MemberServiceImpl.java 내용추가
@Override
public void updatePassword(MemberVO member) {
memberMapper.updatePassword(member);
}
MemberController.java 내용추가
/*===============================
* 비밀번호 변경
===============================*/
//비밀번호 변경 폼 호출
@GetMapping("/member/changePassword")
public String formChangePasswd() {
return "memberChangePassword";
}
tile-def=>member.xml 내용추가
<definition name="memberChangePassword" extends="myPage">
<put-attribute name="title" value="비밀번호 변경"/>
<put-attribute name="body" value="/WEB-INF/views/member/memberChangePassword.jsp"/>
</definition>
member => memberChangePassword.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!-- 비밀번호 변경 시작 -->
<div class="page-main">
<h2>비밀번호 변경</h2>
<form:form action="changePassword" id="member_change" modelAttribute="memberVO">
<form:errors element="div" cssClass="error-color"/>
<ul>
<li>
<form:label path="now_passwd">현재 비밀번호</form:label>
<form:password path="now_passwd"/>
<form:errors path="now_passwd" cssClass="error-color"/>
</li>
<li>
<form:label path="passwd">비밀번호</form:label>
<form:password path="passwd"/>
<form:errors path="passwd" cssClass="error-color"/>
</li>
<li>
<!-- 자바빈에 없기 때문에 기본 태그를 사용해야됨. 비밀번호와 같은지 확인용도. JavaScript로 확인 -->
<label for="confirm_passwd">새비밀번호 확인</label>
<input type="password" id="confirm_passwd">
<span id="message_password"></span>
</li>
<li>
<div id="captcha_div">
<img src="getCaptcha" id="captcha_img" width="200" height="90">
</div>
<input type="button" value="새로고침" id="reload_btn">
<!-- 새로고침을 누르면 위 이미지 태그가 리로드되도록 -->
<script>
$(function() {
$('#reload_btn').click(function() {
$.ajax({
url: 'getCaptcha',
type: 'get',
success: function() { //captcha_div는 캡챠의 이미지주소가 있는 div이다.
//' #captcha_div' #앞에 공백이 없으면 페이지 전체로 인식하기 때문에
//한 칸 띄우고 작성해야된다.
$('#captcha_div').load(location.href + ' #captcha_div');
},
error: function() {
alert('네트워크 오류 발생');
}
});
});
});
</script>
</li>
<li>
<form:label path="captcha_chars">캡챠 문자 확인</form:label>
<form:input path="captcha_chars"/>
<form:errors path="captcha_chars" cssClass="error-color"/>
</li>
</ul>
<div class="align-center">
<form:button>전송</form:button>
<input type="button" value="MY페이지" onclick="location.href='myPage'">
</div>
</form:form>
</div>
<!-- 비밀번호 변경 끝 -->
MemberVO 내용추가(captcha 조건체크를 만들지않아서 만들어줌)
//비밀번호 변경에만 조건체크
@Pattern(regexp="^[A-za-z0-9]$")
private String captcha_chars;

captcha 이미지 띄우기
CaptchaUtil.java
0.00MB
네이버에서 공유하는 captchaUtil. api키만 받으면 사용가능
kr.spring.util에 넣으면된다.




MemberController.java 내용추가
//비밀번호 변경 폼에서 전송된 데이터 처리
/*===============================
* 네이버 captcha(캡챠) API 사용
===============================*/
//캡챠 이미지 호출
@GetMapping("/member/getCaptcha")
public String getCaptcha(Model model, HttpSession session) {
String clientId = "Cc1wS4rT2GXzIGRPGXca";
String clientSecret = "AHEsY00rCk";
String code ="0"; //키 발급시 0, 캡챠 이미지 비교시 1로 세팅
String key_apiURL = "https://openapi.naver.com/v1/captcha/nkey?code="+code;
Map<String,String> requestHeaders = new HashMap<String,String>();
requestHeaders.put("X-Naver-Client-Id",clientId);
requestHeaders.put("X-Naver-Client-Secret",clientSecret);
String responseBody = CaptchaUtil.get(key_apiURL, requestHeaders);
log.debug("<<responseBody>> :"+responseBody);
JSONObject jObject = new JSONObject(responseBody);
try {
//https://openapi.naver.com/v1/captcha/nkey 호출로 받은 키값("key")을 넣어준다.
String key = jObject.getString("key");
//이미지의 나온 값과 내가입력한 값이 맞는지 확인하기 위해 키값을 세션에 저장해둠
session.setAttribute("captcha_key", key);
String apiURL = "https://openapi.naver.com/v1/captcha/ncaptcha.bin?key="+key;
Map<String,String> requestHeaders2 = new HashMap<String,String>();
requestHeaders.put("X-Naver-Client-Id",clientId);
requestHeaders.put("X-Naver-Client-Secret",clientSecret);
byte[] reponse_byte = CaptchaUtil.getCaptchaImage(apiURL, requestHeaders2);
model.addAttribute("imageFile",reponse_byte);
model.addAttribute("filename","captcha.jpg");
}catch(Exception e) {
log.error(e.toString());
}
return "imageView";
}
MemberController 코드 중 JSONObject jObject = new JSONObject();
이부분이 라이브러리가 없어서 pom.xml에 라이브러리를 추가해야됨.
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20230227</version>
</dependency>
<!-- 라이브러리 추가 끝 -->


'쌍용교육(JAVA) > SpringBoot' 카테고리의 다른 글
쌍용교육 -JSP수업 94일차 ch15SpringPage(5) (0) | 2024.07.01 |
---|---|
쌍용교육 -JSP수업 93일차 ch15SpringPage(4) (0) | 2024.06.28 |
쌍용교육 -JSP수업 91일차 ch15SpringPage(2) (0) | 2024.06.26 |
쌍용교육 -JSP수업 90일차 ch15SpringPage (0) | 2024.06.25 |
쌍용교육 -JSP수업 89일차 ch14SpringThymeleadf(2) (0) | 2024.06.25 |