전체 스택 Reddit 클론 - 스프링 부트, React, Electron 애플리케이션 - 섹션 9
20980 단어 reactjavascripttypescriptjava
전체 스택 Reddit 클론 - 스프링 부트, React, Electron 애플리케이션 - 섹션 9
소개하다.
Spring Boot을 사용하여 Reddit 클론을 생성하는 섹션 9에 오신 것을 환영합니다.
우리는 이 부분에 무엇을 건설합니까?
중요한 부분
섹션 1: 저장소 업데이트🗄
페이지 나누기와 정렬 지원을 위해 모든 저장소를 업데이트하는 것을 소개합니다.내부 통신.니 이름.백엔드.다음 클래스를 업데이트할 것입니다.
package com.maxicb.backend.repository;
import com.maxicb.backend.model.Comment;
import com.maxicb.backend.model.Post;
import com.maxicb.backend.model.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.List;
public interface CommentRepository extends PagingAndSortingRepository<Comment, Long> {
Page<Comment> findByPost(Post post, Pageable pageable);
List<Comment> findAllByPost(Post post);
Page<Comment> findAllByUser(User user, Pageable pageable);
}
package com.maxicb.backend.repository;
import com.maxicb.backend.model.Post;
import com.maxicb.backend.model.Subreddit;
import com.maxicb.backend.model.User;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.List;
public interface PostRepository extends PagingAndSortingRepository<Post, Long> {
Page<Post> findAllBySubreddit(Subreddit subreddit, Pageable pageable);
Page<Post> findByUser(User user, Pageable pageable);
}
package com.maxicb.backend.repository;
import com.maxicb.backend.model.Subreddit;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.Optional;
public interface SubredditRepository extends PagingAndSortingRepository<Subreddit, Long> {
Optional<Subreddit> findByName(String subredditName);
Optional<Page<Subreddit>> findByNameLike(String subredditName, Pageable pageable);
}
섹션 2: 서비스 업데이트🌎
현재 우리는 이미 우리의 저장소를 갱신하였으며, 이러한 변화를 반영하기 위해 우리의 서비스를 갱신해야 한다.내부 통신.니 이름.백엔드.서비스는 다음 과정을 업데이트합니다.이 절에 전체 클래스를 표시하지 않고 업데이트할 특정한 방법만 표시한다는 것을 기억하십시오.
public Page<CommentResponse> getCommentsForPost(Long id, Integer page) {
Post post = postRepository.findById(id)
.orElseThrow(() -> new PostNotFoundException("Post not found with id: " + id));
return commentRepository.findByPost(post, PageRequest.of(page, 100)).map(this::mapToResponse);
}
public Page<CommentResponse> getCommentsForUser(Long id, Integer page) {
User user = userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found with id: " + id));
return commentRepository.findAllByUser(user, PageRequest.of(page, 100)).map(this::mapToResponse);
}
private PostResponse mapToResponse(Post post) {
return PostResponse.builder()
.postId(post.getPostId())
.postTitle(post.getPostTitle())
.url(post.getUrl())
.description(post.getDescription())
.userName(post.getUser().getUsername())
.subredditName(post.getSubreddit().getName())
.voteCount(post.getVoteCount())
.commentCount(commentRepository.findAllByPost(post).size())
.duration(TimeAgo.using(post.getCreationDate().toEpochMilli()))
.upVote(checkVoteType(post, VoteType.UPVOTE))
.downVote(checkVoteType(post, VoteType.DOWNVOTE))
.build();
}
public Page<PostResponse> getAllPost(Integer page) {
return postRepository.findAll(PageRequest.of(page, 100)).map(this::mapToResponse);
}
public Page<PostResponse> getPostsBySubreddit(Integer page, Long id) {
Subreddit subreddit = subredditRepository.findById(id)
.orElseThrow(() -> new SubredditNotFoundException("Subreddit not found with id: " + id));
return postRepository
.findAllBySubreddit(subreddit, PageRequest.of(page, 100))
.map(this::mapToResponse);
}
public Page<PostResponse> getPostsByUsername(String username, Integer page) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UserNotFoundException("User not found with username: " + username));
return postRepository
.findByUser(user, PageRequest.of(page, 100))
.map(this::mapToResponse);
}
@Transactional(readOnly = true)
public Page<SubredditDTO> getAll(Integer page) {
return subredditRepository.findAll(PageRequest.of(page, 100))
.map(this::mapToDTO);
}
섹션 3: 컨트롤러 업데이트
현재 서비스와 저장소를 업데이트했습니다. 클라이언트가 페이지를 사용할 수 있도록 컨트롤러를 업데이트해야 합니다.내부 통신.니 이름.백엔드.컨트롤러는 다음과 같은 종류를 업데이트할 것입니다.이 절에 전체 클래스를 표시하지 않고 업데이트할 특정한 방법만 표시한다는 것을 기억하십시오.
@GetMapping("/post/{id}")
public ResponseEntity<Page<CommentResponse>> getCommentsByPost(@PathVariable("id") Long id, @RequestParam Optional<Integer> page) {
return new ResponseEntity<>(commentService.getCommentsForPost(id, page.orElse(0)), HttpStatus.OK);
}
@GetMapping("/user/{id}")
public ResponseEntity<Page<CommentResponse>> getCommentsByUser(@PathVariable("id") Long id,@RequestParam Optional<Integer> page) {
return new ResponseEntity<>(commentService.getCommentsForUser(id, page.orElse(0)), HttpStatus.OK);
}
@PostMapping
public ResponseEntity<PostResponse> addPost(@RequestBody PostRequest postRequest) {
return new ResponseEntity<>(postService.save(postRequest), HttpStatus.CREATED);
}
@GetMapping
public ResponseEntity<Page<PostResponse>> getAllPost(@RequestParam Optional<Integer> page) {
return new ResponseEntity<>(postService.getAllPost(page.orElse(0)), HttpStatus.OK);
}
@GetMapping("/sub/{id}")
public ResponseEntity<Page<PostResponse>> getPostsBySubreddit(@PathVariable Long id, @RequestParam Optional<Integer> page) {
return new ResponseEntity<>(postService.getPostsBySubreddit(page.orElse(0), id), HttpStatus.OK);
}
@GetMapping("/user/{name}")
public ResponseEntity<Page<PostResponse>> getPostsByUsername(@PathVariable("name") String username, @RequestParam Optional<Integer> page) {
return new ResponseEntity<>(postService.getPostsByUsername(username, page.orElse(0)), HttpStatus.OK);
}
@GetMapping("/{page}")
public ResponseEntity<Page<SubredditDTO>> getAllSubreddits (@PathVariable("page") Integer page) {
return new ResponseEntity<>(subredditService.getAll(page), HttpStatus.OK);
}
@GetMapping("/sub/{id}")
public ResponseEntity<SubredditDTO> getSubreddit(@PathVariable("id") Long id) {
return new ResponseEntity<>(subredditService.getSubreddit(id), HttpStatus.OK);
}
@PostMapping
public ResponseEntity<SubredditDTO> addSubreddit(@RequestBody @Valid SubredditDTO subredditDTO) throws Exception{
try {
return new ResponseEntity<>(subredditService.save(subredditDTO), HttpStatus.OK);
} catch (Exception e) {
throw new Exception("Error Creating Subreddit");
}
}
현재, 우리 응용 프로그램은 모든 자원의 페이지를 완전히 지원합니다. 이 자원들은 증가할 수 있고, 전방 응용 프로그램의 불러오는 시간이 느려질 수 있습니다.
섹션 5: 토큰류 리셋⏳
현재 우리는 우리의 RefreshToken 클래스를 만들어야 한다. 이 클래스는 ID, token과 그와 관련된creationDate를 만들어서 일정 시간 후에 표시를 무효화시킬 수 있도록 해야 한다.
package com.maxicb.backend.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.time.Instant;
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
public class RefreshToken {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String token;
private Instant creationDate;
}
섹션 5: 토큰 서비스 및 DTO 업데이트🌎
이제 RefreshToken이 생겼으니 우리는 모든 것을 준비하고 우리의 신분 검증 시스템을 업데이트하기 시작할 것이다.프로젝트 내부에서 다음과 같은 종류를 추가하고 업데이트할 것입니다.
package com.maxicb.backend.repository;
import com.maxicb.backend.model.RefreshToken;
import org.springframework.data.repository.PagingAndSortingRepository;
import java.util.Optional;
public interface RefreshTokenRepository extends PagingAndSortingRepository<RefreshToken, Long> {
Optional<RefreshToken> findByToken(String token);
void deleteByToken(String token);
}
package com.maxicb.backend.service;
import com.maxicb.backend.exception.VoxNobisException;
import com.maxicb.backend.model.RefreshToken;
import com.maxicb.backend.repository.RefreshTokenRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
import java.util.UUID;
@Service
@AllArgsConstructor
@Transactional
public class RefreshTokenService {
private RefreshTokenRepository refreshTokenRepository;
RefreshToken generateRefreshToken () {
RefreshToken refreshToken = new RefreshToken();
refreshToken.setToken(UUID.randomUUID().toString());
refreshToken.setCreationDate(Instant.now());
return refreshTokenRepository.save(refreshToken);
}
void validateToken(String token) {
refreshTokenRepository.findByToken(token)
.orElseThrow(() -> new VoxNobisException("Invalid Refresh Token"));
}
public void deleteRefreshToken(String token) {
refreshTokenRepository.deleteByToken(token);
}
}
import lombok.AllArgsConstructor;
import lombok.Data;
import java.time.Instant;
@Data
@AllArgsConstructor
public class AuthResponse {
private String authenticationToken;
private String refreshToken;
private Instant expiresAt;
private String username;
}
package com.maxicb.backend.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RefreshTokenRequest {
@NotBlank
private String refreshToken;
private String username;
}
섹션 6: JWTprovider 업데이트🔏
이제 모든 것이 준비되었으니 JWT 시스템 업데이트를 시작하겠습니다.내부 통신.니 이름.백엔드.서비스는 다음 과정을 업데이트합니다.이 절에 전체 클래스를 표시하지 않고 업데이트할 특정한 방법만 표시한다는 것을 기억하십시오.
@Service
public class JWTProvider {
private KeyStore keystore;
@Value("${jwt.expiration.time}")
private Long jwtExpirationMillis;
...
....
public String generateToken(Authentication authentication) {
org.springframework.security.core.userdetails.User princ = (User) authentication.getPrincipal();
return Jwts.builder()
.setSubject(princ.getUsername())
.setIssuedAt(from(Instant.now()))
.signWith(getPrivKey())
.setExpiration(from(Instant.now().plusMillis(jwtExpirationMillis)))
.compact();
}
public String generateTokenWithUsername(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(from(Instant.now()))
.signWith(getPrivKey())
.setExpiration(from(Instant.now().plusMillis(jwtExpirationMillis)))
.compact();
}
....
...
public Long getJwtExpirationMillis() {
return jwtExpirationMillis;
}
섹션 7: 업데이트된 인증💂♀️
현재 우리는 페이지 나누기를 실현했고, 우리는 우리의 신분 검증 시스템을 업데이트하기 시작할 것이다.Google 프로젝트에서 다음과 같은 종류를 업데이트할 것입니다.이 절에 전체 클래스를 표시하지 않고 업데이트할 특정한 방법만 표시한다는 것을 기억하십시오.
public AuthResponse refreshToken(RefreshTokenRequest refreshTokenRequest) {
refreshTokenService.validateToken(refreshTokenRequest.getRefreshToken());
String token = jwtProvider.generateTokenWithUsername(refreshTokenRequest.getUsername());
return new AuthResponse(token, refreshTokenService.generateRefreshToken().getToken(), Instant.now().plusMillis(jwtProvider.getJwtExpirationMillis()), refreshTokenRequest.getUsername());
}
public AuthResponse login (LoginRequest loginRequest) {
Authentication authenticate = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authenticate);
String authToken = jwtProvider.generateToken(authenticate);
String refreshToken = refreshTokenService.generateRefreshToken().getToken();
return new AuthResponse(authToken, refreshToken, Instant.now().plusMillis(jwtProvider.getJwtExpirationMillis()), loginRequest.getUsername());
}
@PostMapping("/refresh/token")
public AuthResponse refreshToken(@Valid @RequestBody RefreshTokenRequest refreshTokenRequest) {
return authService.refreshToken(refreshTokenRequest);
}
@PostMapping("/logout")
public ResponseEntity<String> logout(@Valid @RequestBody RefreshTokenRequest refreshTokenRequest) {
refreshTokenService.deleteRefreshToken(refreshTokenRequest.getRefreshToken());
return ResponseEntity.status(HttpStatus.OK).body("Refresh Token Deleted");
}
섹션 8: 사용자 정의 예외🚫
package com.maxicb.backend.exception;
public class VoxNobisException extends RuntimeException {
public VoxNobisException(String message) {super(message);}
}
섹션 9: 업데이트된 응용 프로그램.등록 정보
우리는 프로그램이 영패를 만들 때 사용하고자 하는 만료 시간을 추가하고 그것들의 만료 날짜를 설정해야 한다.나는 그것을 15분으로 설정하기로 선택했지만, 미래에 지속 시간이 증가할 것이다.
# JWT Properties
jwt.expiration.time=900000
섹션 10: Swagger UI 구현📃
이제 MVP 백엔드의 끝에 이르렀으며 Swagger UI를 추가합니다.이전에 Swagger를 사용한 적이 없는 경우 API에 대한 문서를 자동으로 생성하는 것이 좋습니다.당신은 더 많은 것을 알 수 있습니다here!
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket voxNobisAPI() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.apiInfo(getAPIInfo());
}
private ApiInfo getAPIInfo(){
return new ApiInfoBuilder()
.title("Vox-Nobis API")
.version("1.0")
.description("API for Vox-Nobis reddit clone")
.build();
}
}
@SpringBootApplication
@EnableAsync
@Import(SwaggerConfig.class)
public class BackendApplication {
...
}
.antMatchers(HttpMethod.GET, "/api/subreddit")
.permitAll()
.antMatchers("/v2/api-docs",
"/configuration/ui",
"/swagger-resources/**",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**")
.permitAll()
결론🔍
다음
열 번째 부분이 발표될 때, 우리는 응용 프로그램의 앞부분에서 작업을 시작할 것이다.
Reference
이 문제에 관하여(전체 스택 Reddit 클론 - 스프링 부트, React, Electron 애플리케이션 - 섹션 9), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/maxicb/full-stack-reddit-clone-spring-boot-react-electron-app-part-9-3pj5텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)