반응형

Image.class
- Image 도메인을 만든다.
- postImageUrl: 사진을 전송받아서 그 사진을 서버의 특정 폴더에 저장 -Db에 그 저장된 경로를 insert
- 한명의 유저는 여러 이미지를 업로드를 할 수 있고, 하나의 이미지는 하나의 유저만이 만들 수 있으므로 @ManyToOne
- user는 db에서 User객체가 아닌, FK로 저장되기 때문에 userId라는 이름으로 컬럼명을 설정해준다.

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
public class Image {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private String caption;
    private String postImageUrl;

    @JoinColumn(name = "userId") // DB컬럼명 설정
    @ManyToOne
    private User user;

    //이미지 좋아요 정보-> 추후추가

    //댓글-> 추후추가

    private LocalDateTime createDate;

    @PrePersist
    public void createDate(){
        this.createDate = LocalDateTime.now();
    }

}

 

ImageRepository

public interface ImageRepository extends JpaRepository<Image, Integer> {
}

 

ImageController

- imageUploadDto와 유저정보인 principalDetails를 받도록 한다.
- 업로드가 완료되면 유저의 프로필 화면으로 이동하도록 설정한다.
- MultipartFile은 @Notblank 어노테이션이 지원되지 않으므로 이미지 첨부 유효성 검사는 if문으로 처리해준다.

@RequiredArgsConstructor
@Controller
public class ImageController {

    private final ImageService imageService;

   ...
   
    @PostMapping("/image")
    public String imageUpload(ImageUploadDto imageUploadDto, 
    @AuthenticationPrincipal PrincipalDetails principalDetails){

        if(imageUploadDto.getFile().isEmpty()){
            throw new CustomValidationException("이미지가 첨부되지 않았습니다.", null);
        }
        imageService.upload(imageUploadDto, principalDetails);

        return "redirect:/user/"+principalDetails.getUser().getId();
    }


}

 

ControllerExceptionHandler

- 업로드 이미지가 없을때 CustomValidationException을 throw하고 그것을 ControllerExceptionHandler의 validationException메소드가 낚아챈다.
- 이 경우 errorMap 매개변수를null로 전달하기 때문에 errorMap이 null일 경우, null이 아닐 경우를 분기해야한다.

@RestController
@ControllerAdvice //모든 Exception들을 낚아챔
public class ControllerExceptionHandler {

   
    @ExceptionHandler(CustomValidationException.class) //CustomValidationException 발동하는 모든 Exception을 이 함수가 가로챔
    public String validationException(CustomValidationException e){
        if(e.getErrorMap() ==null){
            return Script.back(e.getMessage());
        }else{
            return Script.back(e.getErrorMap().toString());
        }
    }

....
}

 

CustomValidationException

public class CustomValidationException extends RuntimeException{

    //객체를 구분할때
    private static final long serialVersionUID = 1L;

    private Map<String, String> errorMap;

    public CustomValidationException(String message, Map<String, String> errorMap){
        super(message);
        this.errorMap = errorMap;
    }

    public Map<String, String > getErrorMap(){
        return errorMap;
    }
}

 

ImageUploadDto

- 파일을 받을거기 때문에 요청을 위한 dto(ImageUploadDto)를 만들어준다.
- ImageUploadDto는 file 과 caption을 받을 수 있도록 한다.
- toEntity 메소드를 통해 Image객체로 형변환할 수 있도록 한다. 

@Data
public class ImageUploadDto {

    //MultipartFile 에는 @Notblank가 지원안된다.
    private MultipartFile file;
    private String caption;

    public Image toEntity(String postImagUrl, User user){
        return Image.builder()
                .caption(caption)
                .postImageUrl(postImagUrl)
                .user(user)
                .build();
    }
}

 

ImageService

- upload메소드에는 imageUploadDto와 principalDetails를 받는다.
- imageFilePath : 실제 저장될 경로
- uploadFolder: 이미지가 저장될 로컬경로다, @Value 어노테이션으로 해당 값을 가져올 수 있다. 
- imageUploadDto의 toEntity() 메소드를 통해 db에 저장할Image객체로 변환한다.
  이때 매개변수로 uuid가 포함된 이미지명과 현재 로그인한 유저의 정보를 전달한다. 

@RequiredArgsConstructor
@Service
public class ImageService {

    @Value("${file.path}") //application.properties에서 가져옴
    private String uploadFolder;

    private final ImageRepository imageRepository;

    @Transactional
    public void upload(ImageUploadDto imageUploadDto, PrincipalDetails principalDetails){

        UUID uuid = UUID.randomUUID();
        String imageFilename = uuid + "_"+ imageUploadDto.getFile().getOriginalFilename(); //db에 저장되는 파일명

        Path imageFilePath = Paths.get(uploadFolder+imageFilename);

        // 통신, I/O -> 예외가 발생할 수 있다.
        try {
            Files.write(imageFilePath, imageUploadDto.getFile().getBytes()); //(실제 저장될 경로, 실제 이미지 파일)
        } catch (Exception e){
            e.printStackTrace();
        }

        //Image 테이블에 저장
        Image image = imageUploadDto.toEntity(imageFilename, principalDetails.getUser());
        imageRepository.save(image);


    }
}

 

upload.html
- 파일은 전송하는것이므로 enctype 로 multipart/form-data 로 설정해준다.

<!--사진업로드 Form-->
<form class="upload-form" th:action="@{/image}" method="post" enctype="multipart/form-data">
    <input type="file" name="file"  onchange="imageChoose(this)"/>
    <div class="upload-img">
        <img src="/images/person.jpeg" alt="" id="imageUploadPreview" />
    </div>

    <!--사진설명 + 업로드버튼-->
    <div class="upload-form-detail">
        <input type="text" placeholder="사진설명" name="caption">
        <button class="cta blue">업로드</button>
    </div>
    <!--사진설명end-->

</form>
반응형

Follow.java
- Follow라는 모델을 만든다.
- @JoinColumn 어노테이션으로 DB의 컬럼명을 커스텀한다.
- @Table 컬럼을 통해 한 테이블에 동일한 관계 정보가 중복되지 않게 들어가도록 Uniqe 제약조건을 설정한다.
  (한 컬럼만 걸거면 그냥 @Column(unique = true)하면 되긴하다.)
- User 테이블이 1이고 Follow 테이블이 N 이니까 @ManytoOne 어노테이션을 적는다
- 데이터 생성 정보 컬럼도 넣어준다.

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
@Table(
        uniqueConstraints = {
                @UniqueConstraint(
                        name = "follow_uk",
                        columnNames = {"fromUserId", "toUserId"} //실제 db이 컬럼명이 들어가야한다.
                )
        }
)
public class Follow {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @JoinColumn(name = "fromUserId") // DB컬럼명 설정
    @ManyToOne
    private User fromUser; //팔로우 하는애

    @JoinColumn(name = "toUserId")
    @ManyToOne
    private User toUser; //팔로우 받는애

    private LocalDateTime createDate;

    @PrePersist
    public void createDate(){
        this.createDate = LocalDateTime.now();
    }

}

 

FollowApiController

- 팔로우 메소드, 언팔로우 메소드를 만든다.
- 팔로우(언팔라우) 주체를 알아야 하기 때문에 @AuthenticationPrincipal PrincipalDetails principalDetails 를 매개변수로 넣어준다.
- 또한 팔로우,언팔 대상(toUserId)를 매개변수로 넣어준다.

@RequiredArgsConstructor
@RestController
public class FollowApiController {

    private final FollowService followService;

    @PostMapping("/api/follow/{toUserId}")
    public ResponseEntity<?> follow(@AuthenticationPrincipal PrincipalDetails principalDetails,
    								@PathVariable int toUserId){

        followService.follow(principalDetails.getUser().getId(), toUserId);
        return new ResponseEntity<>(new CMRespDto<>(1, "팔로우성공", null), HttpStatus.OK);
    }

    @DeleteMapping("/api/follow/{toUserId}")
    public ResponseEntity<?> unfollow(@AuthenticationPrincipal PrincipalDetails principalDetails, 
    								  @PathVariable int toUserId){

        followService.unfollow(principalDetails.getUser().getId(), toUserId);

        return new ResponseEntity<>(new CMRespDto<>(1, "언팔로우 성공", null), HttpStatus.OK);
    }
}


FollowService

-실제 팔로우, 언팔로우 로직이 실행되는곳
- DB에 영향을 주는 메소드기 때문에 @Transactional 어노테이션 넣어준다.

@RequiredArgsConstructor
@Service
public class FollowService {

    private final FollowRepositoy followRepositoy;

    @Transactional
    public void follow(int fromUserId, int toUserId){
        try {
            followRepositoy.mFollow(fromUserId,toUserId);
        } catch (Exception e){
            throw new CustomApiException("이미 팔로우 했습니다.");
        }

    }

    @Transactional
    public void unfollow(int fromUserId, int toUserId){
        followRepositoy.mUnFollow(fromUserId,toUserId);
    }
}

 

CustomApiException

public class CustomApiException extends RuntimeException{

    private static final long serialVersionUID = 1L;

    public CustomApiException(String message){
        super(message);
    }
}

 

ControllerExceptionHandler

- apiException 메소드를 만든다. 여기에선 CMRespDto 매개변수에 에러메시지만 받고, errorMap은 null로만 받는다.

@RestController
@ControllerAdvice //모든 Exception들을 낚아챔
public class ControllerExceptionHandler {

  ...

    @ExceptionHandler(CustomApiException.class)
    public ResponseEntity<?> apiException(CustomApiException e){
        return new ResponseEntity<>(new CMRespDto<>(-1, e.getMessage(), null), HttpStatus.BAD_REQUEST);
    }
}

 

FollowRepositoy
- 네이티브 쿼리로 작성
- ':' 은 매개변수에 들어온 값을 바인딩하겠다는 의미다. 

public interface FollowRepositoy extends JpaRepository<Follow,Integer>{

    @Modifying //INSERT, DELETE, UPDATE를 네이티브쿼리로 작성하려면 해당 어노테이션 필요
    @Query(value = "INSERT INTO follow(fromUserId, toUserId, createDate) Values(:fromUserId, :toUserId, now())", nativeQuery = true)
    void mFollow(int fromUserId, int toUserId);

    @Modifying
    @Query(value = "DELETE FROM follow WHERE fromUserId = :fromUserId AND toUserId = :toUserId", nativeQuery = true)
    void mUnFollow(int fromUserId, int toUserId);
}

 

반응형

HTML 파일에서는 PUT 요청을 할 수 없으므로 자바스크립트를 이용해야한다
-> 버튼을 클릭했을때 onsubmit 으로 update() 자바스크립트 함수가 실행된다.

update.html

<html xmlns:th="http://www.thymeleaf.org">

...

            <!--프로필 수정-->
            <form id="profileUpdate" th:object="${principal}"  th:onsubmit="|update(*{id}, event)|">
            <div class="content-item__02">
                <div class="item__title">이름</div>
                <div class="item__input">
                    <input type="text" name="name" placeholder="이름" value="겟인데어" th:field="*{name}" required="required" />
                </div>
            </div>
            <div class="content-item__03">
                <div class="item__title">유저네임</div>
                <div class="item__input">
                    <input type="text" name="username" placeholder="유저네임" value="TherePrograming" readonly="readonly"  th:field="*{username}"/>
                </div>
            </div>
            
            ...
            
            <!--제출버튼-->
            <div class="content-item__11">
                <div class="item__title"></div>
                <div class="item__input">
                    <button>제출</button>
                </div>
            </div>
            <!--제출버튼end-->
            </form>
            <!--프로필수정 form end-->
        </article>
    </section>
</main>

<script src="/js/update.js"></script>

 

update.js

-  $("#profileUpdate").serialize() : form의 profileUpdate테그를 찾아서 그 폼테그가 들고 있는 정보를 시리얼라이즈한다.
- done과 faile은 리턴된 HttpStatus 상태코드에 따라 결정된다.
  200번대 -> done
  200번대가 아닐때 -> fail
- db에 유저정보가 없을 경우, 즉 CustomValidationApiException 에 errorMap 정보 가 없이 전달되는 경우에는 error.data에 정보가 담기지 않는다 그럴 상황을 분기해야한다. 

function update(userId, event) {

    event.preventDefault(); //폼태그 액션을 막기

    let data = $("#profileUpdate").serialize();

    console.log(data);
    $.ajax({
        type:"put",
      url : `/api/user/${userId}`,
        data: data,
        contentType: "application/x-www-form-urlencoded; charset=utf-8",
        dataType: "json"
    }).done(res =>{ //Http상태코드 200번대
        console.log("update 성공", res);
        alert("update 성공");
        location.href = `/user/${userId}`;
    }).fail(error => {
       if(error.data ==null){
           alert(error.responseJSON.message);
       }else{
           //json object를 Json 문자열로 변환
           alert(JSON.stringify(error.responseJSON.data));
    });


}

profileUpdate테그를 찾아서 그 폼테그가 들고 있는 정보를 시리얼라이즈한다

UserApiController

- ajax통신으로 실행되는 update메소드를 구성한다.
- form으로부터 전달되는 값을 받기 위해 data transform 오브젝트가 필요하다, 그래서 dto 안에 
- username, email을 제외한 값을 받도록 한다

1.유효성 검사에 통과하면
-> userService의 update 메소드 실행,
  파라미터에 수정하고자 하는 id값과 userUpdateDto의 toEntity() 메소드를 실행시켜서 User객체로 변환한다.
  여기서 끝나는게 아니라 principalDetails의 setUser 설정으로 수정된 값에 따라 현재 세션 정보를 변경해주도록 한다.

- update메소드의 반환 타입은 CMRespDto로 설정한다. => 수정에 성공했으니 성공코드1, 성공메시지, 데이터에는 userEntity(수정된 유저정보)를 담아준다.

2.유효성 검사에 실패하면
- bindingResult의 에러를 errorMap에 담는다.
- CustomValidationApiException 을 메시지와 errorMap을 담아서 터트린다.

@AllArgsConstructor
@NoArgsConstructor
@Data
public class CMRespDto <T> {
    private int code; //1(성공), -1(실패)
    private String message;
    private T data;
}
@RequiredArgsConstructor
@RestController
public class UserApiController {

    private final UserService userService;

    @PutMapping("/api/user/{id}")
    public CMRespDto<?> update(@PathVariable int id,
                               @Valid UserUpdateDto userUpdateDto,
                               BindingResult bindingResult, // 꼭 @Valid가 적혀있는 다음 파라미터에 적어야됨.
                               @AuthenticationPrincipal PrincipalDetails principalDetails){

        if (bindingResult.hasErrors()) {
            Map<String, String> errorMap = new HashMap<>();

            for (FieldError error : bindingResult.getFieldErrors()) {
                errorMap.put(error.getField(), error.getDefaultMessage());
                            }
            throw new CustomValidationApiException("유효성 검사 실패함", errorMap);

        }else {

            User userEntity = userService.update(id, userUpdateDto.toEntity());
            principalDetails.setUser(userEntity); //수정된 값에 따라 세션정보 변경
            return new CMRespDto<>(1, "회원수정완료", userEntity);


        }
    }


}

 

유효성 검사 실패시

CustomValidationApiException

public class CustomValidationApiException extends RuntimeException{

    private static final long serialVersionUID = 1L;

    private Map<String, String> errorMap;

    public CustomValidationApiException(String message){
        super(message);
    }

    public CustomValidationApiException(String message, Map<String, String> errorMap){
        super(message);
        this.errorMap = errorMap;
    }

    public Map<String, String > getErrorMap(){
        return errorMap;
    }
}

 

ControllerExceptionHandler
- 모든 Exception은 낚아채는 핸들러
- ajax통신할때는 ResponseEntity 를 사용해야지 http 상태코드를 전달할 수 있다. 그래서 분기하기가 쉽다.(done과 fail로)

@RestController
@ControllerAdvice //모든 Exception들을 낚아챔
public class ControllerExceptionHandler {

...
    //object리턴 - api통신때
    @ExceptionHandler(CustomValidationApiException.class)
    public ResponseEntity<?> validationApiException(CustomValidationApiException e){
        return new ResponseEntity<>
        (new CMRespDto<>(-1, e.getMessage(), e.getErrorMap()), HttpStatus.BAD_REQUEST);
    }

...
}

UserUpdateDto

- 유저정보 수정을 위한 DTO다

@Data
public class UserUpdateDto {
    //username, email은 안받는다.
    @NotBlank
    private String name; //필수
    @NotBlank
    private String password; //필수
    private String website;
    private String bio;
    private String phone;
    private String gender;

    //코드 수정이 필요할 예정
    public User toEntity(){
        return User.builder()
                .name(name) //이름을 기재 안하면 문제 -> validation체크
                .password(password) //비번을 기재 안하면 문제 -> validation체크
                .website(website)
                .bio(bio)
                .phone(phone)
                .gender(gender)
                .build();
    }
}

 

UserService.class

- 실제 회원 수정 로직
- @Transactional 어노테이션 붙여준다
- 파라미터로 수정계정의 id와 user객체 가 들어간다.
  여기서 받는 user 객체는 UserDto에서 toEntity()로 반환된 값이다.
- orElesThrow : 못찾았을때 Exception을 발동시킨다.
  -> 만약 db에서 회원 정보를 찾지 못하면 CustomValidationApiException 을 리턴한다.
  기존 CustomValidationApiException 에는 생성자가 message,와 errorMap을 전달해야함으로 message만 받는 생성자를 1개 더 만들어준다.

* 사실 회원정보 수정할때 로그인이 되어있기 때문에 DB에 회원정보가 없을 일은 없지만, 혹시나 없는유저를 찾으면 서버에 문제가 발생할 수 있기에 해당 문제를 원천 차단했다.

@RequiredArgsConstructor
@Service
public class UserService {

    private final UserRepository userRepository;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    @Transactional
    public User update(int id, User user){
        //1.영속화
        //1) get() :무조건 찾았다. 걱정마
        //2) orElseThrow() :못찾았어 exception발동시킬께

        User userEntity = userRepository.findById(id).orElseThrow(() 
        -> {return new CustomValidationApiException("찾을수 없는id입니다."); });

        String rawPassword = user.getPassword();
        String encPassword = bCryptPasswordEncoder.encode(rawPassword);

        //2.영속화된 오브젝를 수정 - 더티체킹(업데이트 완료)
        userEntity.setName(user.getName());
        userEntity.setPassword(encPassword);
        userEntity.setBio(user.getBio());
        userEntity.setWebsite(user.getWebsite());
        userEntity.setPhone(user.getPhone());
        userEntity.setGender(user.getGender());

        return userEntity;
    }   
}
반응형

*로그인 세션정보 가져오는 절차
1. post 요청으로 signin 하면

@EnableWebSecurity
@Configuration //IOC
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   ...
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable(); //csrf 비활성화
        http.authorizeRequests()
                .antMatchers("/", "/user/**", "/image/**", "/subscribe/**", "/comment/**").authenticated() //인증이 필요한 url
                .anyRequest().permitAll() //어떤 요청이라도 허용.
                .and()
                .formLogin()
                .loginPage("/auth/signin")
                .loginProcessingUrl("/auth/signin") //스프링 시큐리티가 로그인 프로세스 진행           
                .defaultSuccessUrl("/"); //로그인 정상됐을때 이동하는 url
    }

}

 

2. 서버에 도달하기 전에 시큐리티가 로그인 정보를 낚아챈다.
   시큐리티는 PrincipalDetailService(UserDetailsService를 상속받음)에게 로그인 정보를 넘긴다.

   - .loginProcessingUrl("/auth/signin") 가 있으면 로그인 요청으로 인식하고
     원래라면 UserDetailsService가 낚아채야하지만 PrincipalDetailsService가 UserDetailsService를 상속 받기때문에         여기서 대신 낚아챔
   - 패스워드는 알아서 체킹하니가 신경x
   - 리턴이 잘되면 자동으로 UserDetails 타입을 세션으로 만든다.
   - 여기서 리턴하는 PrincipalDetails는 UserDetails를 상속받는다(같은 타입이다.).

@RequiredArgsConstructor
@Service
public class PrincipalDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User userEntity = userRepository.findByUsername(username);

        if(userEntity == null){
            return null;
        }else{
            //PrincipalDetails 은 UserDetails을 상속
            return new PrincipalDetails(userEntity);
        }
    }
}
@Data
public class PrincipalDetails implements UserDetails {

    private static final long serialVersionUID = 1L;

    private User user;

    public PrincipalDetails(User user){
        this.user = user;
    }
    //권한이 1개가 이닐수 있어서 Collection타입
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {

        Collection<GrantedAuthority> collector = new ArrayList<>(); //ArrayList의 부모가 Collection
        collector.add(() -> { return user.getRole();});
        return collector;
    }

   ...
}

 

@Controller
public class UserController {

    ...
    @GetMapping({"/user/{id}/update"})
    public String update(@PathVariable int id, @AuthenticationPrincipal PrincipalDetails principalDetails, Model model){

        //principal: 인증주체, 인증된 사람의 오브젝트명
        model.addAttribute("principal", principalDetails.getUser());
        return "user/update";
    }
}

 @AuthenticationPrincipal 어노테이션은
로그인 정보, 즉, Authetication 객체에 바로 접근할 수 있다. 

스프링 시큐리티로 로그인하면 Authentication 객체 내부에 Principaldetails가 담긴다.

다음으로 model에다 principalDetails에 담긴 유저정보를 가져와서 프론트단(수정화면)에 뿌려주면 된다. 

반응형

*현재 validation 미통과시 js로 오류메시지를 alert하도록 설정했는데
추후엔 오류메시지를 타임리프를 활용하여 변경할 예정이다.

SignupDto
- 회원가입 관련 DTO로, 회원가입 폼에서 전달한 데이터를 담는 역할을한다.
- @Valid의 대상이된다.
- toEntity() 메소드는DTO로 받은 회원데이터를 User객체로 변환해준다.

@Data
public class SignupDto {
    @Size(min=2, max = 20)
    @NotBlank
    private  String username;

    @NotBlank
    private  String password;

    @NotBlank
    private  String email;

    @NotBlank
    private  String name;

    public User toEntity(){
        return User.builder()
                .username(username)
                .password(password)
                .email(email)
                .name(name)
                .build();
    }
}

AuthController
- 사실 프론트 단에서 유효성 검사하는것으로도 끝낼 수 있지만 데이터를 프론트가 아닌 다른 방법
(ex postman)으로 전송할 수 있으니까 백엔드 단도 구성했다.
- SignupDto에서의 유효성 검사가 하나라도 실패하면 실패한 것들이 AuthController의 signup 메소드의 bindingResult에 담긴다.
- bindingResult에 에러가 하나라도 있으면 errorMap 이라는 이름에 Map에다 에러 정보를 담는다.
- 다 담기면 CustomValidationException을 throw한다. 이때 메시지("유효성 검사 실패함")와 errorMap을 파라미터로 넘긴다.
- 오류가 없다면 회원가입 진행 후 "auth/signin"페이질 이동

@PostMapping("/auth/signup")
public String signup(@Valid SignupDto signupDto, BindingResult bindingResult) {

    //프론트 단에서 유효성 검사해도 postman 전송할 수 있으니까
    if (bindingResult.hasErrors()) {
        Map<String, String> errorMap = new HashMap<>();

        for (FieldError error : bindingResult.getFieldErrors()) {
            errorMap.put(error.getField(), error.getDefaultMessage());
        }
        throw new CustomValidationException("유효성 검사 실패함", errorMap);
    } else {
        //User < - SingupDto
        User user = signupDto.toEntity();
        User userEntity = authService.register(user);
        return "auth/signin";
    }
}

 

CustomValidationException.class
- CustomValidationException 은 RuntimeException을 상속받는다.
- 생성자는 String 타입의 메시지와 Map 타입의 errorMap을 매개변수로 갖는다.
- getErrorMap() 메소드는 errorMap을 리턴한다.

public class CustomValidationException extends RuntimeException{

    //객체를 구분할때
    private static final long serialVersionUID = 1L;

    private Map<String, String> errorMap;

    public CustomValidationException(String message, Map<String, String> errorMap){
        super(message);
        this.errorMap = errorMap;
    }

    public Map<String, String > getErrorMap(){
        return errorMap;
    }
}

 

ControllerExceptionHandler.class
- @ControllerAdvice 는 모든 Exception들을 낚아챈다.
- @ExceptionHandler(CustomValidationException.class)를 선언해줌으로써 모든 CustomValidationException을 이 함수가 가로챈다.
- 리턴값은 back이라는 정적메소드에 파라미터로 errorMap을 String으로 변환시켜 전달해준다.

@RestController
@ControllerAdvice
public class ControllerExceptionHandler {

    @ExceptionHandler(CustomValidationException.class)
    public String validationException(CustomValidationException e){
        return Script.back(e.getErrorMap().toString());
    }
}


Script.java
- validationException의 리턴값을 설정해준다.
- 자바스크립트 코드를 문자열화해서 리턴해준다. 

public class Script {

    public static String back(String msg){
        StringBuffer sb = new StringBuffer();
        sb.append("<script>");
        sb.append("alert('"+msg+"');");
        sb.append("history.back();");
        sb.append("</script>");
        return sb.toString();
    }
}

+ Recent posts