회원가입


GET /users/join - 회원가입 폼이 보여진다. name,nickName, passwd, email을 입력
POST /users/join - 회원정보가 저장된다. 이때 USER권한을 가지도록 한다. // config에 permitAll 해야함 .antMatchers(“/users/join”).permitAll()
GET /users/welcome - 회원가입후 redirect하여 보여지는 페이지.

POST /users/join 를 처리하는 컨트롤러에서는 폼 파라미터를 받아들이기 위해 다음을 사용할 수 있다.

@RequestParam 을 이용하여 값을 받을 수 있다.
@ModelAttribute - DTO에 값을 담을 수 있다.
@Valid - DTO에 Validation관련 어노테이션을 사용할 수 있다. BidingResult를 파라미터로 함께 사용한다.

컨트롤러에서는 파라미터의 값이 올바른 형태인지 검사를 한다. 문제가 있다면 Exception을 발생하거나,
redirect 또는 포워딩을 한다.

AccountService에 Account정보를 넘겨서 저장해달라고 한다.

  • “User” Role정보를 읽어온다.
  • Account에 Role을 추가한다.
  • Account를 저장한다. 이때 account테이블에 1건 저장, account_roles에도 저장

    @GetMapping("/join")
    public String joinform(){
        return "users/joinform";
    }

    // Form데이터를 DTO로 파라미터를 받아들일 경우엔 @ModelAttribute JoinForm joinForm
    // DTO에 Validation관련 어노테이션을 사용했을 경우에는 @Valid를 사용한다.
    @PostMapping("/join")
    public String join(@Valid JoinForm joinForm, BindingResult bindingResult){
        if(bindingResult.hasErrors()){ // joinfoam에 작성한 Valid 조건을 비교해서 에러 출력
            throw new IllegalArgumentException(bindingResult.toString());
        }
        if(!joinForm.getPassword1().equals(joinForm.getPassword2()))
            throw new IllegalArgumentException("암호와 암호확인이 틀려요.");

        Account account = new Account();
        account.setEmail(joinForm.getEmail());
        account.setName(joinForm.getName());
        account.setNickName(joinForm.getNickName());
        PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
        account.setPasswd(passwordEncoder.encode(joinForm.getPassword1()));

        Account result = accountService.join(account);

        return "redirect:/users/welcome";
    }

    @GetMapping("/welcome")
    public String welcome(){
        return "users/welcome";
    }

    @GetMapping("/delete")
    public String delete(@RequestParam(name = "id")Long id){
        accountService.deleteAccount(id);
        return "users/welcome";
    }
}
------------------------------------------------------------------------
//dto의 @Vaild 옵션
@Getter
@Setter
public class JoinForm {
    @NotNull
    @Size(min=2, max=30)
    private String name;
    @NotNull
    @Size(min=2, max=20)
    private String nickName;
    @NotNull
    @Size(min=4, max=255)
    private String email;
    @NotNull
    @Size(min=4, max=12)
    private String password1;
    @NotNull
    @Size(min=4, max=12)
    private String password2;
}

글쓰기


GET /main : 모든 카테고리에 대한 목록이 보여진다.
GET /main?categoryId=1 : category가 1인 목록이 보여진다.
GET /posts/write : 글쓰기 폼 , 카테고리 목록을 읽어와서 select박스에 값을 넣어준다.
title, content, 파일을 2건 업로드
글작성사자는 로그인한 정보가 보여진다.
POST /posts/write : 글을 저장
title, content, 카테고리id, 이미지정보2건, 로그인정보
DB에 저장
/main으로 redirect 한다.

/posts/** 는 로그인할 경우에만 접근할 수 있도록 설정

thymeleaf에서 로그인한 정보를 읽어들이려면?

https://www.baeldung.com/spring-security-thymeleaf
https://github.com/thymeleaf/thymeleaf-extras-springsecurity


		<dependency>
			<groupId>org.thymeleaf.extras</groupId>
			<artifactId>thymeleaf-extras-springsecurity5</artifactId>
			<version>3.0.4.RELEASE</version>
		</dependency>

로그인한 BlogSecurityUser 정보에서 name값을 출력한다.

@Controller
@RequestMapping("/posts")
@RequiredArgsConstructor
public class PostController {
    private final CategoryService categoryService;
    private final PostService postService;

    @GetMapping("/write")
    public String writeform(Model model){
        List<Category> categories = categoryService.getAll();
        model.addAttribute("categories", categories);
        return "posts/writeform";
    }

    @PostMapping("/write")
    public String write(
            @RequestParam(name = "title") String title,
            @RequestParam(name = "content") String content,
            @RequestParam(name = "categoryId") Long categoryId,
            @RequestParam(name = "image")MultipartFile[] images
            ){
        Assert.hasText(title, "제목을 입력하세요.");
        Assert.hasText(content, "내용을 입력하세요.");

        // 로그인을 한 사용자 정보는 Security필터에서 SecurityContextHolder의 ThreadLocal에 저장된다.
        // 그래서 같은 쓰레드상이라면 로그인한 정보를 읽어들일 수 있다.
        // Authentication.getPrincipal 이걸로 가져온다.
        BlogSecurityUser securityUser =
                (BlogSecurityUser)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); //principal로 저장하고 읽어오고.  쓰레드로칼에 현재 로그인정보가 들어가있다.

        Post post = new Post();
        post.setContent(content);
        post.setTitle(title);

        if(images != null && images.length > 0) {
            for (MultipartFile image : images) {
                ImageFile imageFile = new ImageFile();
                imageFile.setLength(image.getSize());
                imageFile.setMimeType(image.getContentType());
                imageFile.setName(image.getOriginalFilename());
                // 파일 저장
                // /tmp/2019/2/12/123421-12341234-12341234-123423142
                String saveFileName = saveFile(image);

                imageFile.setSaveFileName(saveFileName); // save되는 파일명
                post.addImageFile(imageFile);
            }
        }

        postService.addPost(post, categoryId, securityUser.getId());

        return "redirect:/main";
    }

    private String saveFile(MultipartFile image){
        String dir = "/DEVEL/tmp/";
        Calendar calendar = Calendar.getInstance();
        dir = dir + calendar.get(Calendar.YEAR);
        dir = dir + "/";
        dir = dir + (calendar.get(Calendar.MONTH) + 1);
        dir = dir + "/";
        dir = dir + calendar.get(Calendar.DAY_OF_MONTH);
        dir = dir + "/";
        File dirFile = new File(dir);
        dirFile.mkdirs(); // 디렉토리가 없을 경우 만든다. 퍼미션이 없으면 생성안될 수 있다.
        dir = dir + UUID.randomUUID().toString();

        try(FileOutputStream fos = new FileOutputStream(dir);
            InputStream in = image.getInputStream()
        ){
            byte[] buffer = new byte[1024];
            int readCount = 0;
            while((readCount = in.read(buffer)) != -1){
                fos.write(buffer, 0, readCount);
            }
        }catch(Exception ex){
            ex.printStackTrace();
        }

        return dir;
    }
}
public interface PostRepository extends JpaRepository<Post, Long> {
    @Query(value = "SELECT p FROM Post p INNER JOIN FETCH p.category ORDER BY p.id DESC",
           countQuery = "SELECT count(p) FROM Post p")
    public Page<Post> getPosts(Pageable pageable);
}
@Service
@RequiredArgsConstructor
public class PostService {
    private final PostRepository postRepository;
    private final AccountRepository accountRepository;
    private final CategoryRepository categoryRepository;

    @Transactional
    public Post addPost(Post post,Long categoryId, Long accountId){
        Optional<Category> optionalCategory
                = categoryRepository.findById(categoryId);
        Optional<Account> optionalAccount
                = accountRepository.findById(accountId);
        post.setAccount(optionalAccount.get());
        post.setCategory(optionalCategory.get());

        return postRepository.save(post);
    }
}