본문 바로가기

Server Programming/Spring Boot Full-Stack Programming

[스프링 풀스택 클론 코딩 - 계정 설정] (2-4) 프로필 이미지 변경

반응형
npm install cropper
npm install jquery-cropper

 

$("#profile-image-file").change(function(e) { if (e.target.files.length === 1) { const reader = new FileReader();
reader.onload = e => { if (e.target.result) { let img = document.createElement("img");
img.id = 'new-profile';
img.src = e.target.result;

img.width = 250;
$newProfileImage.html(img);
$newProfileImage.show();
$currentProfileImage.hide();
let $newImage = $(img);
$newImage.cropper({aspectRatio: 1});
cropper = $newImage.data('cropper');
$cutBtn.show();
$confirmBtn.hide();
$resetBtn.show();
} };
reader.readAsDataURL(e.target.files[0]);
} });

 

https://getbootstrap.com/docs/5.0/components/card/

 

Cards

Bootstrap’s cards provide a flexible and extensible content container with multiple variants and options.

getbootstrap.com

 

이미지 변경 카드 뷰

<!-- 프로필 이미지 -> hidden값으로 사용자가 직접 입력하지 않고, cropper.js를 이용해 이미지 크기 설정 -->
<div class="col-sm-6">
    <div class="card text-center">
        <div class="card-header">프로필 이미지</div>
        <div id="current-profile-image" class="mt-3">
            <svg th:if="${#strings.isEmpty(profile.profileImage)}"
                class="rounded" th:data-jdenticon-value="${account.nickname}"
                width="125" height="125"></svg>
            <img th:if="${!#strings.isEmpty(profile.profileImage)}"
                class="rounded" th:src="${profile.profileImage}" width="125"
                height="125" alt="name" th:alt="${account.nickname}" />
        </div>
        <div id="new-profile-image" class="mt-3"></div>
        <div class="card-body">
            <div class="custom-file">
                <input type="file" class="custom-file-input"
                    id="profile-image-file"> <label
                    class="custom-file-label" for="profile-image-file">프로필
                    이미지 변경</label>
            </div>
            <div id="new-profile-image-control" class="mt-3">
                <button class="btn btn-outline-primary btn-block"
                    id="cut-button">자르기</button>
                <button class="btn btn-outline-success btn-block"
                    id="confirm-button">확인</button>
                <button class="btn btn-outline-warning btn-block"
                    id="reset-button">취소</button>
            </div>
            <div id="cropped-new-profile-image" class="mt-3"></div>
        </div>
    </div>
</div>

 

이미지를 문자열로 저장하는 방법

DataURL

● data: 라는 접두어를 가진 URL로 파일을 문서에 내장 시킬때 사용할 수 있다.
● 이미지를 DataURL로 저장할 수 있다.

 

 

Profile과 AccountService에 profileImage추가

 

Profile

package com.demo.settings;

import com.demo.domain.Account;

import lombok.Data;
import lombok.NoArgsConstructor;

//폼을 채울 객체 생성
//롬복을 이용한 getter, setter 자동생성
@Data
//참조받을 account를 위해 기본 생성자를 만들어주는 어노테이션은 사용한다
@NoArgsConstructor
public class Profile {
	private String bio;
	private String url;
	private String occupation;
	private String location;
	
	//이미지
	private String profileImage;
	
	//@NoArgsConstructor를 대신해 사용한다면
	//public Profile(){};
	
	//프로필 폼에 채울 객체에 정보 넣기 -> ModelMapper를 이용해 생성
	public Profile(Account account) {
		this.bio=account.getBio();
		this.url=account.getUrl();
		this.occupation=account.getOccupation();
		this.location=account.getLocation();
		this.profileImage=account.getProfileImage();
	}


}

AccountService의 updateProfile메서드

	public void updateProfile(Account account, Profile profile) {
		// TODO Auto-generated method stub
		account.setUrl(profile.getUrl());
		account.setOccupation(profile.getOccupation());
		account.setLocation(profile.getLocation());
		account.setBio(profile.getBio());
		account.setProfileImage(profile.getProfileImage());
		
		//트랜잭션이 끝난 detached상태이기 때문에 직접 DB에 반영을 해준다. 
		//즉 id값이 있으면 merge를 수행한다.
		accountRepository.save(account);
	}

 


profile

<!-- 프로필 값 -->
    <div class="form-group">
        <input id="profileImage" type="hidden" th:field="*{profileImage}"
            class="form-control" />
    </div>

 

자바스크립트 cropper

https://fengyuanchen.github.io/cropperjs/

 

Cropper.js

 

fengyuanchen.github.io

 

자바스크립트를 이용한, 이미지 변경하기 & Cropper를 이용해 자르기

<!--  cropper 스크립트 가져오기 -->
	<link href="/node_modules/cropper/dist/cropper.min.css"
		rel="stylesheet">
	<script src="/node_modules/cropper/dist/cropper.min.js"></script>
	<script src="/node_modules/jquery-cropper/dist/jquery-cropper.min.js"></script>
	<!-- 이미지 변경 jquery  -->
	<script type="application/javascript">
        $(function() {
        	<!-- 버튼 만들기-->
            cropper = '';
            let $confirmBtn = $("#confirm-button");
            let $resetBtn = $("#reset-button");
            let $cutBtn = $("#cut-button");
            let $newProfileImage = $("#new-profile-image");
            let $currentProfileImage = $("#current-profile-image");
            let $resultImage = $("#cropped-new-profile-image");
            let $profileImage = $("#profileImage");

            <!-- 필요 없는 영역과 버튼 숨기기--> 
            $newProfileImage.hide();
            $cutBtn.hide();
            $resetBtn.hide();
            $confirmBtn.hide();

            <!-- 프로필 이미지 선택의 값이 바뀌면-->
            $("#profile-image-file").change(function(e) {
            	<!-- 파일리더 변수를 통해 파일을 읽어온다 -->
                if (e.target.files.length === 1) {
                    const reader = new FileReader();
                    <!-- 파일을 읽어왔으면 -->
                    reader.onload = e => {
                    	<!-- target을 가져온다.-->
                        if (e.target.result) {
                        	<!-- 이미지 파일을 선택하지 않았을 때-->
                            if (!e.target.result.startsWith("data:image")) {
                                alert("이미지 파일을 선택하세요.");
                                return;
                            }
                            <!-- 이미지 파일 선택했을 때 -->
                            <!-- 이미지 태그를 만들어서 가져온 이미지를 채워놓고 값들을 채워놓고, 필요한 부분은 보여주고, 필요없는 부분은 숨긴다.-->
                            let img = document.createElement("img");
                            img.id = 'new-profile';
                            img.src = e.target.result;
                            img.setAttribute('width', '100%');

                            $newProfileImage.html(img);
                            $newProfileImage.show();
                            $currentProfileImage.hide();
							<!-- 현재 이미지 숨기고 새로운 이미지를 보여준다.-->
							
							<!--새이미지를  제이쿼리로 감싸서  크루퍼를 적용한다.-->
                            let $newImage = $(img);
                            $newImage.cropper({aspectRatio: 1});
                            cropper = $newImage.data('cropper');

                            <!-- 새로운 이미지에 크루퍼가 적용이 되었으면 잘라내기 버튼, 리셋버튼 보여주고, 확인버튼은 숨긴다.-->
                            $cutBtn.show();
                            $confirmBtn.hide();
                            $resetBtn.show();
                        }
                    };

                    reader.readAsDataURL(e.target.files[0]);
                    <!-- -->
                }
            });

            <!-- 리셋버튼 클릭시, 최종적으로 프로필이미지 값에 비어있는 값을 채워놓는다.-->
            $resetBtn.click(function() {
                $currentProfileImage.show();
                $newProfileImage.hide();
                $resultImage.hide();
                $resetBtn.hide();
                $cutBtn.hide();
                $confirmBtn.hide();
                $profileImage.val('');
            });

            <!-- 잘라내기 버튼 클릭시-->
            $cutBtn.click(function () {
                let dataUrl = cropper.getCroppedCanvas().toDataURL();

                if (dataUrl.length > 1000 * 1024) {
                    alert("이미지 파일이 너무 큽니다. 1024000 보다 작은 파일을 사용하세요. 현재 이미지 사이즈 " + dataUrl.length);
                    return;
                }

                <!-- 잘라낼 이미지 만큼만 자른다.-->
                let newImage = document.createElement("img");
                newImage.id = "cropped-new-profile-image";
                newImage.src = dataUrl;
                newImage.width = 125;
                $resultImage.html(newImage);
                $resultImage.show();
                $confirmBtn.show();

                $confirmBtn.click(function () {
                    $newProfileImage.html(newImage);
                    $cutBtn.hide();
                    $confirmBtn.hide();
                    $profileImage.val(dataUrl);
                });
            });
        });
    </script>

 

변경을 할 경우 메인 네비게이션바에 바로 적용이 되지 않는다.

-> 유저가 가지고있는 데이터를 반영하는 것이 아니기 때문에 -> 인증 정보의 이름으로 먼저 처리를 하기 때문에

 

 

fragments

<li class="nav-item dropdown" sec:authorize="isAuthenticated()">
    <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
        aria-expanded="false">
        <!-- jenticon을 이용한 프로필 아이콘, 타임리프를 이용해 스프링시큐리티 표현식으로 닉네임-->
        <svg data-jdenticon-value="user127" th:data-jdenticon-value="${#authentication.name}" width="24" height="24" class="rounded border bg-light"></svg>
    </a>
    <div class="dropdown-menu dropdown-menu-sm-right" aria-labelledby="userDropdown">
        <ul>
            <h6 class="dropdown-header">
                <span sec:authentication="name">Username</span>
            </h6>
            <a class="dropdown-item" th:href="@{'/profile/' + ${#authentication.name}}">프로필</a>
            <a class="dropdown-item">스터디</a>
            <div class="dropdown-divider"></div>
            <a class="dropdown-item" th:href="@{'/settings/profile'}">설정</a>
            <form class="form-inline my-2 my-lg-0" action="#" th:action="@{/logout}" method="post">
                <button class="dropdown-item" type="submit">로그아웃</button>
            </form>
        </ul>
    </div>
</li>

 

변경 후

<li class="nav-item dropdown" sec:authorize="isAuthenticated()">
    <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
        aria-expanded="false">
        <!-- jenticon을 이용한 프로필 아이콘, 타임리프를 이용해 스프링시큐리티 표현식으로 닉네임-->
        <!-- 이미지 변경해도 반영할 수 있도록 변경-->
        <svg th:if="${#strings.isEmpty(account?.profileImage)}" th:data-jdenticon-value="${#authentication.name}"
             width="24" height="24" class="rounded border bg-light"></svg>
        <img th:if="${!#strings.isEmpty(account?.profileImage)}" th:src="${account.profileImage}"
             width="24" height="24" class="rounded border"/>
    </a>
    <div class="dropdown-menu dropdown-menu-sm-right" aria-labelledby="userDropdown">
        <ul>
            <h6 class="dropdown-header">
                <span sec:authentication="name">Username</span>
            </h6>
            <a class="dropdown-item" th:href="@{'/profile/' + ${#authentication.name}}">프로필</a>
            <a class="dropdown-item">스터디</a>
            <div class="dropdown-divider"></div>
            <a class="dropdown-item" th:href="@{'/settings/profile'}">설정</a>
            <form class="form-inline my-2 my-lg-0" action="#" th:action="@{/logout}" method="post">
                <button class="dropdown-item" type="submit">로그아웃</button>
            </form>
        </ul>
    </div>
</li>
반응형