회원가입
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);
}
}