";
+ msgg+= "CODE :
";
+ msgg+= ePw+"
";
+ msgg+= "
";
+ message.setText(msgg, "utf-8", "html");//내용
+ message.setFrom(new InternetAddress("projectstockholm0@gmail.com","Stockholm"));//보내는 사람
+
+ return message;
+ }
+
+ public static String createKey() {
+ StringBuffer key = new StringBuffer();
+ Random rnd = new Random();
+
+ for (int i = 0; i < 8; i++) { // 인증코드 8자리
+ int index = rnd.nextInt(3); // 0~2 까지 랜덤
+
+ switch (index) {
+ case 0:
+ key.append((char) ((int) (rnd.nextInt(26)) + 97));
+ // a~z (ex. 1+97=98 => (char)98 = 'b')
+ break;
+ case 1:
+ key.append((char) ((int) (rnd.nextInt(26)) + 65));
+ // A~Z
+ break;
+ case 2:
+ key.append((rnd.nextInt(10)));
+ // 0~9
+ break;
+ }
+ }
+ return key.toString();
+ }
+ @Override
+ public String sendSimpleMessage(String to)throws Exception {
+ // TODO Auto-generated method stub
+ MimeMessage message = createMessage(to);
+ try{//예외처리
+ emailSender.send(message);
+ }catch(MailException es){
+ es.printStackTrace();
+ throw new IllegalArgumentException();
+ }
+ return ePw;
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/auth/memberdetail/MemberDetailsService.java b/server/008main_project/src/main/java/com/stockholm/main_project/auth/memberdetail/MemberDetailsService.java
new file mode 100644
index 00000000..3f442e80
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/auth/memberdetail/MemberDetailsService.java
@@ -0,0 +1,72 @@
+package com.stockholm.main_project.auth.memberdetail;
+
+import com.stockholm.main_project.auth.utils.CustomAuthorityUtils;
+import com.stockholm.main_project.member.entity.Member;
+import com.stockholm.main_project.member.repository.MemberRepository;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Component;
+
+import java.util.Collection;
+import java.util.Optional;
+
+@Component
+public class MemberDetailsService implements UserDetailsService {
+ private final MemberRepository memberRepository;
+ private final CustomAuthorityUtils authorityUtils;
+
+ public MemberDetailsService(MemberRepository memberRepository, CustomAuthorityUtils authorityUtils) {
+ this.memberRepository = memberRepository;
+ this.authorityUtils = authorityUtils;
+ }
+
+ @Override
+ public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
+ Optional
optionalMember = memberRepository.findByEmail(email);
+ Member findMember = optionalMember.orElseThrow(() -> new UsernameNotFoundException("User not found"));
+
+ return new MemberDetails(findMember);
+ }
+
+ private final class MemberDetails extends Member implements UserDetails {
+
+ MemberDetails(Member member) {
+ setMemberId(member.getMemberId());
+ setEmail(member.getEmail());
+ setPassword(member.getPassword());
+ setRoles(member.getRoles());
+ }
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ return authorityUtils.createAuthorities(this.getRoles());
+ }
+
+ @Override
+ public String getUsername() {
+ return getEmail();
+ }
+
+ @Override
+ public boolean isAccountNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ return true;
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/auth/utils/CustomAuthorityUtils.java b/server/008main_project/src/main/java/com/stockholm/main_project/auth/utils/CustomAuthorityUtils.java
new file mode 100644
index 00000000..542cc7bd
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/auth/utils/CustomAuthorityUtils.java
@@ -0,0 +1,28 @@
+package com.stockholm.main_project.auth.utils;
+
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Component
+public class CustomAuthorityUtils {
+
+ private final List USER_ROLES = AuthorityUtils.createAuthorityList("ROLE_USER");
+ private final List USER_ROLES_STRING = List.of("USER");
+
+ public List createRoles(String email) {
+
+ return USER_ROLES_STRING;
+ }
+
+ public List createAuthorities(List roles) {
+ List authorities = roles.stream()
+ .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
+ .collect(Collectors.toList());
+ return authorities;
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/auth/utils/OAuth2MemberService.java b/server/008main_project/src/main/java/com/stockholm/main_project/auth/utils/OAuth2MemberService.java
new file mode 100644
index 00000000..e56c44c2
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/auth/utils/OAuth2MemberService.java
@@ -0,0 +1,57 @@
+package com.stockholm.main_project.auth.utils;
+
+import com.stockholm.main_project.auth.attribute.OAuthAttributes;
+import com.stockholm.main_project.member.entity.Member;
+import com.stockholm.main_project.member.repository.MemberRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
+import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
+import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
+import org.springframework.security.oauth2.core.user.OAuth2User;
+import org.springframework.stereotype.Service;
+
+import javax.servlet.http.HttpSession;
+import java.util.Collections;
+
+@Service
+public class OAuth2MemberService extends DefaultOAuth2UserService {
+
+ private final MemberRepository memberRepository;
+
+ private final HttpSession httpSession;
+ @Autowired
+ public OAuth2MemberService(MemberRepository memberRepository, HttpSession httpSession) {
+ this.memberRepository = memberRepository;
+ this.httpSession = httpSession;
+ }
+
+ @Override
+ public OAuth2User loadUser(OAuth2UserRequest memberRequest) throws OAuth2AuthenticationException {
+ OAuth2UserService delegate = new DefaultOAuth2UserService();
+ OAuth2User oauthMember = delegate.loadUser(memberRequest);
+
+ String registrationId = memberRequest.getClientRegistration().getRegistrationId();
+ String userNameAttributeName = memberRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
+
+ OAuthAttributes attributes = OAuthAttributes.of(registrationId, userNameAttributeName, oauthMember.getAttributes());
+
+ Member member = saveOAuth(attributes);
+
+ return new DefaultOAuth2User(
+ Collections.singleton(new SimpleGrantedAuthority("USER")),
+ attributes.getAttributes(),
+ "email"
+ );
+
+ }
+
+ private Member saveOAuth(OAuthAttributes attributes) {
+ Member member = memberRepository.findByEmail(attributes.getEmail())
+ .orElse(attributes.toEntity());
+
+ return memberRepository.save(member);
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/awss3/AwsS3Controller.java b/server/008main_project/src/main/java/com/stockholm/main_project/awss3/AwsS3Controller.java
new file mode 100644
index 00000000..3758e953
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/awss3/AwsS3Controller.java
@@ -0,0 +1,59 @@
+package com.stockholm.main_project.awss3;
+
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import io.swagger.v3.oas.annotations.Operation;
+import java.net.URL;
+
+
+@RestController
+@RequestMapping("/s3")
+public class AwsS3Controller {
+
+ private final AwsS3Service s3Service;
+
+ public AwsS3Controller(AwsS3Service s3Service) {
+ this.s3Service = s3Service;
+ }
+
+
+ // 이미지 업로드
+ @PostMapping("/upload/{folderName}")
+ @Operation(summary = "파일 업로드", description = "파일을 업로드합니다.")
+ @ApiResponses({
+ @ApiResponse(responseCode = "201", description = "파일 업로드 성공", content = @Content(schema = @Schema(implementation = URL.class))),
+ @ApiResponse(responseCode = "400", description = "잘못된 요청"),
+ @ApiResponse(responseCode = "500", description = "S3 파일 업로드 중 오류 발생")
+ })
+ public URL uploadFile(@PathVariable String folderName, @RequestParam MultipartFile file) throws Exception {
+ return s3Service.uploadFile(folderName, file);
+ }
+
+ @DeleteMapping("/delete/{folderName}/{fileName}")
+ @Operation(summary = "파일 삭제", description = "파일을 삭제합니다.")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "파일 삭제 성공"),
+ @ApiResponse(responseCode = "500", description = "S3 파일 삭제 중 오류 발생")
+ })
+ public ResponseEntity deleteFile(@PathVariable String folderName, @PathVariable String fileName) {
+ s3Service.deleteFile(folderName, fileName);
+ return ResponseEntity.ok("파일이 삭제 되었습니다.");
+ }
+
+ @GetMapping("/url/{folderName}/{fileName}")
+ @Operation(summary = "파일 URL 가져오기", description = "파일의 URL을 가져옵니다.")
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "URL 가져오기 성공", content = @Content(schema = @Schema(implementation = URL.class))),
+ @ApiResponse(responseCode = "500", description = "S3에서 파일 URL 검색 중 오류 발생")
+ })
+ public ResponseEntity getFileUrl(@PathVariable String folderName, @PathVariable String fileName) {
+ URL url = s3Service.getFileUrl(folderName, fileName);
+ return ResponseEntity.ok(url);
+ }
+
+}
\ No newline at end of file
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/awss3/AwsS3Service.java b/server/008main_project/src/main/java/com/stockholm/main_project/awss3/AwsS3Service.java
new file mode 100644
index 00000000..f39b3c0b
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/awss3/AwsS3Service.java
@@ -0,0 +1,79 @@
+package com.stockholm.main_project.awss3;
+
+
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import com.stockholm.main_project.exception.BusinessLogicException;
+import com.stockholm.main_project.exception.ExceptionCode;
+import org.springframework.web.multipart.MultipartFile;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.model.*;
+import software.amazon.awssdk.core.sync.RequestBody;
+
+import java.net.URL;
+
+@Service
+@Slf4j
+public class AwsS3Service {
+ private final String bucketName = "stockholm-server";
+ private final S3Client s3Client;
+
+ public AwsS3Service() {
+ try {
+ String awsAccessKeyId = System.getenv("AWS_S3_ACCESS_KEY");
+ String awsSecretAccessKey = System.getenv("AWS_S3_SECRET_KEY");
+
+ AwsBasicCredentials awsCredentials = AwsBasicCredentials.create(
+ awsAccessKeyId,
+ awsSecretAccessKey
+ );
+
+ this.s3Client = S3Client.builder()
+ .region(Region.AP_NORTHEAST_2)
+ .credentialsProvider(StaticCredentialsProvider.create(awsCredentials))
+ .build();
+ } catch (Exception e) {
+ throw new BusinessLogicException(ExceptionCode.AWS_CREDENTIALS_ERROR);
+ }
+ }
+ public URL uploadFile(String folderName, MultipartFile file) throws Exception {
+ try {
+ String fileName = folderName + "/" + file.getOriginalFilename();
+
+ PutObjectRequest putObjectRequest = PutObjectRequest.builder()
+ .bucket(bucketName)
+ .contentType("image/jpeg")
+ .key(fileName)
+ .build();
+
+ s3Client.putObject(putObjectRequest, RequestBody.fromInputStream(file.getInputStream(), file.getSize()));
+
+ return s3Client.utilities().getUrl(builder -> builder.bucket(bucketName).key(fileName));
+ } catch (Exception e) {
+ throw new BusinessLogicException(ExceptionCode.S3_UPLOAD_ERROR);
+ }
+ }
+
+ public void deleteFile(String folderName, String fileName) {
+ try {
+ String fullFileName = folderName + "/" + fileName;
+ s3Client.deleteObject(builder -> builder.bucket(bucketName).key(fullFileName));
+ } catch (Exception e) {
+ throw new BusinessLogicException(ExceptionCode.S3_DELETE_ERROR);
+ }
+ }
+
+ public URL getFileUrl(String folderName, String fileName) {
+ try {
+ String fullFileName = folderName + "/" + fileName;
+ return s3Client.utilities().getUrl(builder -> builder.bucket(bucketName).key(fullFileName));
+ } catch (Exception e) {
+ throw new BusinessLogicException(ExceptionCode.S3_URL_RETRIEVE_ERROR);
+ }
+ }
+ }
\ No newline at end of file
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/controller/CommentController.java b/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/controller/CommentController.java
new file mode 100644
index 00000000..9e3f1092
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/controller/CommentController.java
@@ -0,0 +1,66 @@
+package com.stockholm.main_project.board.commnet.controller;
+
+import com.stockholm.main_project.board.commnet.dto.CommentRequestDto;
+import com.stockholm.main_project.board.commnet.dto.CommentResponseDto;
+import com.stockholm.main_project.board.commnet.entity.Comment;
+import com.stockholm.main_project.board.commnet.mapper.CommentMapper;
+import com.stockholm.main_project.board.commnet.service.CommentService;
+import com.stockholm.main_project.board.dto.responseDto.SingleBoardResponseDto;
+import com.stockholm.main_project.board.entity.Board;
+
+import com.stockholm.main_project.board.service.BoardService;
+import com.stockholm.main_project.member.entity.Member;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/boards/{boardId}/comments")
+public class CommentController {
+
+ private final CommentService commentService;
+ private final BoardService boardService;
+ private final CommentMapper mapper;
+
+
+ public CommentController(CommentService commentService, BoardService boardService, CommentMapper mapper) {
+ this.commentService = commentService;
+ this.boardService = boardService;
+ this.mapper = mapper;
+ }
+
+ @PostMapping
+ public ResponseEntity postComment(@PathVariable long boardId, @RequestBody CommentRequestDto commentRequestDto, @AuthenticationPrincipal Member member){
+
+ Comment comment = mapper.commentRequestDtoToComment(commentRequestDto);
+ comment.setBoard(boardService.findBoard(boardId));
+ comment.setMember(member);
+
+ Comment createdComment = commentService.createComment(comment);
+ CommentResponseDto responseDto = mapper.commentToCommentResponseDto(createdComment);
+ return new ResponseEntity<>(responseDto, HttpStatus.CREATED);
+ }
+ @PatchMapping("{commentId}")
+ public ResponseEntity updateComment(@PathVariable long boardId,
+ @PathVariable long commentId,
+ @RequestBody CommentRequestDto commentRequestDto,
+ @AuthenticationPrincipal Member member){
+
+ Comment comment = mapper.commentRequestDtoToComment(commentRequestDto);
+ comment.setCommentId(commentId);
+
+ Comment updatedComment = commentService.updateComment(commentId, comment, member);
+ CommentResponseDto responseDto = mapper.commentToCommentResponseDto(updatedComment);
+
+ return new ResponseEntity<>(responseDto, HttpStatus.OK);
+ }
+
+ @DeleteMapping("{commentId}")
+ public ResponseEntity deleteComment(@PathVariable long boardId, @PathVariable long commentId, @AuthenticationPrincipal Member member) {
+
+ commentService.deleteComment(commentId, member);
+
+ return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/dto/CommentRequestDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/dto/CommentRequestDto.java
new file mode 100644
index 00000000..75f2a640
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/dto/CommentRequestDto.java
@@ -0,0 +1,10 @@
+package com.stockholm.main_project.board.commnet.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class CommentRequestDto {
+ private String content;
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/dto/CommentResponseDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/dto/CommentResponseDto.java
new file mode 100644
index 00000000..d8a36a84
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/dto/CommentResponseDto.java
@@ -0,0 +1,14 @@
+package com.stockholm.main_project.board.commnet.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class CommentResponseDto {
+ private Long commentId;
+ private String member;
+ private String content;
+ private String createdAt;
+ private String ModifiedAt;
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/entity/Comment.java b/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/entity/Comment.java
new file mode 100644
index 00000000..e4c5cadd
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/entity/Comment.java
@@ -0,0 +1,41 @@
+package com.stockholm.main_project.board.commnet.entity;
+
+import com.stockholm.main_project.audit.Auditable;
+import com.stockholm.main_project.board.entity.Board;
+import com.stockholm.main_project.member.entity.Member;
+import lombok.Getter;
+import lombok.Setter;
+import org.hibernate.annotations.OnDelete;
+import org.hibernate.annotations.OnDeleteAction;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+
+@Entity
+@Getter
+@Setter
+public class Comment extends Auditable {
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Id
+ private Long commentId;
+
+ @Column(length = 255, nullable = false)
+ private String content;
+
+ @JoinColumn(name = "MEMBER_ID")
+ @ManyToOne(fetch = FetchType.LAZY)
+ @OnDelete(action = OnDeleteAction.CASCADE)
+ @NotNull
+ private Member member;
+
+ @JoinColumn(name = "BOARD_ID")
+ @ManyToOne(fetch = FetchType.LAZY)
+ @OnDelete(action = OnDeleteAction.CASCADE)
+ private Board board;
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/mapper/CommentMapper.java b/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/mapper/CommentMapper.java
new file mode 100644
index 00000000..db9f848c
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/mapper/CommentMapper.java
@@ -0,0 +1,16 @@
+package com.stockholm.main_project.board.commnet.mapper;
+
+import com.stockholm.main_project.board.commnet.dto.CommentRequestDto;
+import com.stockholm.main_project.board.commnet.dto.CommentResponseDto;
+import com.stockholm.main_project.board.commnet.entity.Comment;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface CommentMapper {
+
+ Comment commentRequestDtoToComment(CommentRequestDto commentRequestDto);
+
+ @Mapping(source = "member.name", target = "member")
+ CommentResponseDto commentToCommentResponseDto(Comment comment);
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/repository/CommentRepository.java b/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/repository/CommentRepository.java
new file mode 100644
index 00000000..d738b187
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/repository/CommentRepository.java
@@ -0,0 +1,15 @@
+package com.stockholm.main_project.board.commnet.repository;
+
+import com.stockholm.main_project.board.commnet.entity.Comment;
+import com.stockholm.main_project.board.entity.Board;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface CommentRepository extends JpaRepository {
+
+ List findAllByBoardBoardId(long boardId);
+ Optional findByCommentId(Long commentId);
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/service/CommentService.java b/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/service/CommentService.java
new file mode 100644
index 00000000..a3fe6e20
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/board/commnet/service/CommentService.java
@@ -0,0 +1,64 @@
+package com.stockholm.main_project.board.commnet.service;
+
+import com.stockholm.main_project.board.commnet.dto.CommentRequestDto;
+import com.stockholm.main_project.board.commnet.entity.Comment;
+import com.stockholm.main_project.board.commnet.repository.CommentRepository;
+import com.stockholm.main_project.board.entity.Board;
+import com.stockholm.main_project.exception.BusinessLogicException;
+import com.stockholm.main_project.exception.ExceptionCode;
+import com.stockholm.main_project.member.entity.Member;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+@Service
+public class CommentService {
+
+ private final CommentRepository commentRepository;
+
+ public CommentService(CommentRepository commentRepository) {
+ this.commentRepository = commentRepository;
+ }
+
+ public Comment createComment(Comment comment){
+ return commentRepository.save(comment);
+ }
+
+ public Comment updateComment(long commentId ,Comment comment, Member member){
+ Comment existingComment = findComment(commentId);
+
+ if (existingComment.getMember().getMemberId() != member.getMemberId()) {
+ throw new BusinessLogicException(ExceptionCode.INVALID_FAILED);
+ }
+
+ existingComment.setContent(comment.getContent());
+
+ return commentRepository.save(existingComment);
+
+ }
+
+ public List findComments(long commentId) {
+ return commentRepository.findAllByBoardBoardId(commentId);
+ }
+
+ public void deleteComment (long commentId, Member member) {
+ Comment comment = findComment(commentId);
+
+ if (comment.getMember().getMemberId() != member.getMemberId()) {
+ throw new BusinessLogicException(ExceptionCode.INVALID_FAILED);
+ }
+
+ commentRepository.delete(comment);
+ }
+
+ public Comment findComment(long commentId) {
+ return findVerifiedComment(commentId);
+ }
+
+ public Comment findVerifiedComment(long commentId) {
+ Optional optionalComment = commentRepository.findByCommentId(commentId);
+ return optionalComment.orElseThrow(() -> new BusinessLogicException(ExceptionCode.COMMENT_NOT_FOUND));
+ }
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/board/controller/BoardController.java b/server/008main_project/src/main/java/com/stockholm/main_project/board/controller/BoardController.java
new file mode 100644
index 00000000..0de6d76d
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/board/controller/BoardController.java
@@ -0,0 +1,143 @@
+package com.stockholm.main_project.board.controller;
+
+import com.stockholm.main_project.board.commnet.dto.CommentResponseDto;
+import com.stockholm.main_project.board.commnet.entity.Comment;
+import com.stockholm.main_project.board.commnet.service.CommentService;
+import com.stockholm.main_project.board.dto.responseDto.BoardCommentDto;
+import com.stockholm.main_project.board.service.BoardService;
+import com.stockholm.main_project.board.dto.BoardRequestDto;
+import com.stockholm.main_project.board.dto.responseDto.AllBoardResponseDto;
+import com.stockholm.main_project.board.dto.responseDto.SingleBoardResponseDto;
+import com.stockholm.main_project.board.entity.Board; // Board 클래스를 참조하도록 수정
+import com.stockholm.main_project.board.mapper.BoardMapper;
+import com.stockholm.main_project.member.dto.MemberResponseDto;
+import com.stockholm.main_project.member.entity.Member;
+import com.stockholm.main_project.member.service.MemberService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.validation.Valid;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RestController
+@RequestMapping("/api/boards")
+public class BoardController {
+
+
+ private final BoardService boardService;
+
+ private final BoardMapper mapper;
+ private final CommentService commentService;
+
+ public BoardController(BoardService boardService, BoardMapper mapper, CommentService commentService) {
+ this.boardService = boardService;
+ this.mapper = mapper;
+ this.commentService = commentService;
+ }
+
+ // 게시물 생성
+ @PostMapping
+ @Operation(summary = "게시물 생성", description = "새로운 게시물을 생성합니다.", tags = { "Board" })
+ @ApiResponses({
+ @ApiResponse(responseCode = "201", description = "Created", content = @Content(schema = @Schema(implementation = Board.class))),
+ @ApiResponse(responseCode = "400", description = "Bad Request"),
+ @ApiResponse(responseCode = "405", description = "Method Not Allowed"),
+ @ApiResponse(responseCode = "500", description = "Internal Server Error")
+ })
+ public ResponseEntity createBoard(@Valid @RequestBody BoardRequestDto boardPostDto, @AuthenticationPrincipal Member member) throws Exception {
+ Board boardToCreate = mapper.boardRequestToBoard(boardPostDto);
+ boardToCreate.setMember(member);
+
+ Board createdBoard = boardService.createBoard(boardToCreate);
+ SingleBoardResponseDto responseDto = mapper.boardToBoardResponseDto(createdBoard);
+
+ return new ResponseEntity<>(responseDto, HttpStatus.CREATED);
+ }
+
+ @Operation(summary = "게시물 정보 변경", description = "게시물을 수정합니다.", tags = { "Board" })
+ @ApiResponse(responseCode = "200", description = "OK",
+ content = @Content(schema = @Schema(implementation = BoardRequestDto.class)))
+ @ApiResponse(responseCode = "400", description = "BAD REQUEST")
+ @ApiResponse(responseCode = "404", description = "NOT FOUND")
+ @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR")
+ @PatchMapping("{boardId}")
+ public ResponseEntity updateBoard(@Valid @PathVariable long boardId,@RequestBody BoardRequestDto boardRequestDto,
+ @AuthenticationPrincipal Member member) throws Exception {
+ Board boardToUpdate = mapper.boardRequestToBoard(boardRequestDto);
+
+ Board board = boardService.updateBoard(boardId, boardToUpdate, member);
+
+ SingleBoardResponseDto responseDto = mapper.boardToBoardResponseDto(board);
+
+ return new ResponseEntity<>(responseDto, HttpStatus.OK);
+ }
+
+ @Operation(summary = "게시물 정보 조회", description = "게시물을 조회합니다.", tags = { "Board" })
+ @ApiResponse(responseCode = "200", description = "OK",
+ content = @Content(schema = @Schema(implementation = SingleBoardResponseDto.class)))
+ @ApiResponse(responseCode = "400", description = "BAD REQUEST")
+ @ApiResponse(responseCode = "404", description = "NOT FOUND")
+ @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR")
+ @GetMapping("{boardId}")
+ public ResponseEntity getBoard(@PathVariable long boardId){
+ Board response = boardService.findBoard(boardId);
+ SingleBoardResponseDto responseDto = mapper.boardToBoardResponseDto(response);
+
+ List comments = commentService.findComments(boardId);
+ List boardCommentDtos = comments
+ .stream()
+ .map(mapper::boardCommentToBoardCommentsDto)
+ .collect(Collectors.toList());
+ responseDto.setComments(boardCommentDtos);
+
+ return new ResponseEntity<>(responseDto, HttpStatus.OK);
+ }
+
+ @Operation(summary = "전체 게시물 조회", description = "모든 게시물을 조회합니다.", tags = { "Board" })
+ @ApiResponse(responseCode = "200", description = "OK",
+ content = @Content(schema = @Schema(implementation = AllBoardResponseDto.class)))
+ @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR")
+ @GetMapping
+ public ResponseEntity> getBoards(){
+ List foundBoards = boardService.getAllBoards();
+
+ List responseDtos = foundBoards.stream()
+ .map(board -> {
+ AllBoardResponseDto responseDto = mapper.boardToAllBoardResponseDto(board);
+ List comments = commentService.findComments(board.getBoardId());
+ List boardCommentDtos = comments.stream()
+ .map(mapper::boardCommentToBoardCommentsDto)
+ .collect(Collectors.toList());
+ responseDto.setComments(boardCommentDtos);
+ return responseDto;
+ })
+ .collect(Collectors.toList());
+ return new ResponseEntity<>(responseDtos, HttpStatus.OK);
+ }
+
+ @Operation(summary = "게시물 삭제", description = "게시물을 삭제합니다.", tags = { "Board" })
+ @ApiResponses({
+ @ApiResponse(responseCode = "204", description = "No Content"),
+ @ApiResponse(responseCode = "400", description = "Bad Request"),
+ @ApiResponse(responseCode = "404", description = "BOARD_NOT_FOUND"),
+ @ApiResponse(responseCode = "500", description = "Internal Server Error")})
+ @DeleteMapping("{boardId}")
+ public ResponseEntity deleteBoard(@PathVariable long boardId, @AuthenticationPrincipal Member member){
+ boardService.deleteBoard(boardId, member);
+
+ return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+
+ }
+
+}
\ No newline at end of file
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/board/dto/BoardRequestDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/board/dto/BoardRequestDto.java
new file mode 100644
index 00000000..dd3580e8
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/board/dto/BoardRequestDto.java
@@ -0,0 +1,26 @@
+package com.stockholm.main_project.board.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Size;
+
+@Getter
+@Setter
+@NoArgsConstructor
+public class BoardRequestDto {
+ @Schema(description = "게시물 제목", defaultValue = "TestBoard")
+ @NotBlank(message = "제목을 입력해 주세요")
+ @Size(max = 30, message = "제목의 길이는 30자를 넘을 수 없습니다")
+ private String title;
+
+ @Schema(description = "게시물 내용", defaultValue = "TestContent")
+ @NotBlank(message = "내용을 입력해 주세요")
+ @Size(max = 100, message = "내용은 100자를 넘을 수 없습니다")
+ private String content;
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/board/dto/responseDto/AllBoardResponseDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/board/dto/responseDto/AllBoardResponseDto.java
new file mode 100644
index 00000000..cbdb989d
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/board/dto/responseDto/AllBoardResponseDto.java
@@ -0,0 +1,21 @@
+package com.stockholm.main_project.board.dto.responseDto;
+
+import com.stockholm.main_project.board.commnet.dto.CommentResponseDto;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Getter
+@Setter
+public class AllBoardResponseDto {
+ private Long boardId;
+ private String title;
+ private String content;
+ private String member;
+ private List comments;
+ private String createdAt;
+ private String modifiedAt;
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/board/dto/responseDto/BoardCommentDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/board/dto/responseDto/BoardCommentDto.java
new file mode 100644
index 00000000..540b9093
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/board/dto/responseDto/BoardCommentDto.java
@@ -0,0 +1,14 @@
+package com.stockholm.main_project.board.dto.responseDto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class BoardCommentDto {
+ private long commentId; // 추가
+ private String content;
+ private String member;
+ private String createdAt;
+ private String ModifiedAt;
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/board/dto/responseDto/SingleBoardResponseDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/board/dto/responseDto/SingleBoardResponseDto.java
new file mode 100644
index 00000000..26d9bd2c
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/board/dto/responseDto/SingleBoardResponseDto.java
@@ -0,0 +1,24 @@
+package com.stockholm.main_project.board.dto.responseDto;
+
+
+
+import com.stockholm.main_project.board.commnet.dto.CommentResponseDto;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+// 게시판 응답 형식 클래스
+@Getter
+@Setter
+public class SingleBoardResponseDto {
+ private Long boardId;
+ private String title;
+ private String content;
+ private String member;
+ private String createdAt;
+ private String modifiedAt;
+ private List comments;
+
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/board/entity/Board.java b/server/008main_project/src/main/java/com/stockholm/main_project/board/entity/Board.java
new file mode 100644
index 00000000..f8ae2a4b
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/board/entity/Board.java
@@ -0,0 +1,42 @@
+package com.stockholm.main_project.board.entity;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.stockholm.main_project.audit.Auditable;
+import com.stockholm.main_project.member.entity.Member;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.hibernate.annotations.OnDelete;
+import org.hibernate.annotations.OnDeleteAction;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import java.util.ArrayList;
+import java.util.List;
+
+
+@Entity
+@Getter
+@Setter
+@NoArgsConstructor
+public class Board extends Auditable {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long boardId;
+
+ @Column
+ private String title;
+
+ @Column
+ private String content;
+
+ @JoinColumn(name = "MEMBER_ID")
+ @ManyToOne(fetch = FetchType.LAZY)
+ @OnDelete(action = OnDeleteAction.CASCADE)
+ @NotNull
+ private Member member;
+
+ private String imageUrl; // 이미지 URL
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/board/mapper/BoardMapper.java b/server/008main_project/src/main/java/com/stockholm/main_project/board/mapper/BoardMapper.java
new file mode 100644
index 00000000..3b1d1bf6
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/board/mapper/BoardMapper.java
@@ -0,0 +1,30 @@
+package com.stockholm.main_project.board.mapper;
+
+import com.stockholm.main_project.board.commnet.entity.Comment;
+import com.stockholm.main_project.board.dto.BoardRequestDto;
+import com.stockholm.main_project.board.dto.responseDto.AllBoardResponseDto;
+import com.stockholm.main_project.board.dto.responseDto.BoardCommentDto;
+import com.stockholm.main_project.board.dto.responseDto.SingleBoardResponseDto;
+import com.stockholm.main_project.board.entity.Board;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface BoardMapper {
+
+
+
+ Board boardRequestToBoard(BoardRequestDto requestBody);
+
+ @Mapping(source = "member.name", target = "member")
+ SingleBoardResponseDto boardToBoardResponseDto(Board board);
+
+ @Mapping(source = "member.name", target = "member")
+ AllBoardResponseDto boardToAllBoardResponseDto(Board board);
+
+ @Mapping(source = "member.name", target = "member")
+ BoardCommentDto boardCommentToBoardCommentsDto(Comment comment);
+
+}
+
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/board/repository/BoardRepository.java b/server/008main_project/src/main/java/com/stockholm/main_project/board/repository/BoardRepository.java
new file mode 100644
index 00000000..b2dee158
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/board/repository/BoardRepository.java
@@ -0,0 +1,13 @@
+package com.stockholm.main_project.board.repository;
+
+import com.stockholm.main_project.board.entity.Board;
+import com.stockholm.main_project.member.entity.Member;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface BoardRepository extends JpaRepository {
+ Optional findByBoardId(Long boardId);
+}
\ No newline at end of file
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/board/service/BoardService.java b/server/008main_project/src/main/java/com/stockholm/main_project/board/service/BoardService.java
new file mode 100644
index 00000000..bd0381f6
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/board/service/BoardService.java
@@ -0,0 +1,71 @@
+package com.stockholm.main_project.board.service;
+
+import com.stockholm.main_project.awss3.AwsS3Service;
+import com.stockholm.main_project.board.repository.BoardRepository;
+import com.stockholm.main_project.board.entity.Board;
+import com.stockholm.main_project.exception.BusinessLogicException;
+import com.stockholm.main_project.exception.ExceptionCode;
+import com.stockholm.main_project.member.entity.Member;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.net.URL;
+import java.util.List;
+import java.util.Optional;
+
+@Service
+@Slf4j
+public class BoardService {
+
+ @Autowired
+ private BoardRepository boardRepository;
+
+ public Board createBoard(Board board) {
+
+ return boardRepository.save(board);
+ }
+
+ public Board updateBoard(long boardId, Board updatedBoard, Member member) {
+ Board board = findBoard(boardId);
+
+ if (board.getMember().getMemberId() != member.getMemberId()) {
+ throw new BusinessLogicException(ExceptionCode.INVALID_FAILED);
+ }
+
+ board.setTitle(updatedBoard.getTitle());
+ board.setContent(updatedBoard.getContent());
+
+ return boardRepository.save(board);
+ }
+
+ public Board findBoard(long boardId) {
+
+ return findVerifiedBoard(boardId);
+ }
+
+ public List getAllBoards() {
+ return boardRepository.findAll();
+ }
+
+ public void deleteBoard(long boardId, Member member) {
+ Board board = findBoard(boardId);
+
+ if (board.getMember().getMemberId() != member.getMemberId()) {
+ throw new BusinessLogicException(ExceptionCode.INVALID_FAILED);
+ }
+
+ boardRepository.delete(board);
+ }
+
+ public Board findVerifiedBoard(long boardId) {
+
+ Optional optionalBoard =
+ boardRepository.findByBoardId(boardId);
+ Board findBoard =
+ optionalBoard.orElseThrow(() ->
+ new BusinessLogicException(ExceptionCode.BOARD_NOT_FOUND));
+ return findBoard;
+ }
+}
\ No newline at end of file
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/cash/controller/CashController.java b/server/008main_project/src/main/java/com/stockholm/main_project/cash/controller/CashController.java
new file mode 100644
index 00000000..73b46d55
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/cash/controller/CashController.java
@@ -0,0 +1,94 @@
+package com.stockholm.main_project.cash.controller;
+
+import com.stockholm.main_project.cash.dto.CashPatchDto;
+import com.stockholm.main_project.cash.dto.CashPostDto;
+import com.stockholm.main_project.cash.dto.CashResponseDto;
+import com.stockholm.main_project.cash.entity.Cash;
+import com.stockholm.main_project.cash.mapper.CashMapper;
+import com.stockholm.main_project.cash.service.CashService;
+import com.stockholm.main_project.member.entity.Member;
+import com.stockholm.main_project.member.service.MemberService;
+import com.stockholm.main_project.stock.service.StockHoldService;
+import com.stockholm.main_project.stock.service.StockOrderService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+
+@RestController
+@RequestMapping("/cash")
+public class CashController {
+
+ private final CashMapper mapper;
+ private final CashService cashService;
+ private final MemberService memberService;
+ private final StockHoldService stockHoldService;
+ private final StockOrderService stockOrderService;
+
+ public CashController(CashMapper mapper, CashService cashService, MemberService memberService, StockHoldService stockHoldService, StockOrderService stockOrderService) {
+ this.mapper = mapper;
+ this.cashService = cashService;
+ this.memberService = memberService;
+ this.stockHoldService = stockHoldService;
+ this.stockOrderService = stockOrderService;
+ }
+ @PostMapping
+ @Operation(summary = "현금 정보 생성", description = "새로운 현금 정보를 생성합니다.", tags = { "Cash" })
+ @ApiResponse(responseCode = "201", description = "Created",
+ content = @Content(mediaType = "application/json", schema = @Schema(implementation = CashResponseDto.class)))
+ @ApiResponse(responseCode = "400", description = "이미 보유한 현금이 있습니다.")
+ @ApiResponse(responseCode = "401", description = "Not Enough Money")
+ public ResponseEntity postCash(@Schema(implementation = CashPostDto.class)@Valid @RequestBody CashPostDto cashPostDto,
+ @AuthenticationPrincipal Member member){
+
+ Cash cashToCreate = mapper.cashPostToCash(cashPostDto);
+
+ cashToCreate.setMember(member);
+
+ Cash createdCash = cashService.createCash(cashToCreate);
+ CashResponseDto responseDto = mapper.cashToCashResponseDto(createdCash);
+
+ return new ResponseEntity<>(responseDto, HttpStatus.CREATED);
+ }
+
+ @PatchMapping("{cashId}")
+ @Operation(summary = "현금 정보 업데이트", description = "현금 정보를 업데이트합니다.", tags = { "Cash" })
+ @ApiResponse(responseCode = "200", description = "OK",
+ content = @Content(mediaType = "application/json", schema = @Schema(implementation = CashResponseDto.class)))
+ @ApiResponse(responseCode = "400", description = "Bad Request")
+ @ApiResponse(responseCode = "401", description = "Invalid Cash")
+ @ApiResponse(responseCode = "404", description = "Not Found")
+ public ResponseEntity patchCash(@Schema(implementation = CashPatchDto.class)@PathVariable long cashId, @Valid @RequestBody CashPatchDto requestBody,
+ @AuthenticationPrincipal Member member){
+
+ Cash cashToUpdate = mapper.cashPatchToCash(requestBody);
+
+ cashToUpdate.setMember(member);
+
+ requestBody.setCashId(cashId);
+
+ Cash cash = cashService.updateCash(cashId, member, requestBody);
+ stockHoldService.deleteStockHolds(member.getMemberId());
+ stockOrderService.deleteStockOrders(member);
+
+ return new ResponseEntity<>(mapper.cashToCashResponseDto(cash), HttpStatus.OK);
+ }
+
+ @Operation(summary = "현금 정보 조회", description = "현금 정보를 조회합니다.", tags = { "Cash" })
+ @ApiResponse(responseCode = "200", description = "OK",
+ content = @Content(mediaType = "application/json", schema = @Schema(implementation = CashResponseDto.class)))
+ @ApiResponse(responseCode = "401", description = "Invalid Cash")
+ @ApiResponse(responseCode = "404", description = "Not Found")
+ @GetMapping
+ private ResponseEntity getCash(@AuthenticationPrincipal Member member){
+ Cash response = cashService.findCash(member);
+
+ return new ResponseEntity<>(mapper.cashToCashResponseDto(response), HttpStatus.OK);
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/cash/dto/CashPatchDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/cash/dto/CashPatchDto.java
new file mode 100644
index 00000000..002164d7
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/cash/dto/CashPatchDto.java
@@ -0,0 +1,23 @@
+package com.stockholm.main_project.cash.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.Pattern;
+
+
+@Getter
+@Setter
+public class CashPatchDto {
+
+ @Schema(description = "CashId", defaultValue = "1")
+ private long cashId;
+ @Schema(description = "금액", defaultValue = "5000000")
+ @Min(value = 1000000, message = "금액은 최소 1,000,000 이상이어야 합니다.") // 최소값 설정
+ @Max(value = 500000000, message = "금액은 최대 500,000,000 이하여야 합니다.") // 최대값 설정
+ private long money;
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/cash/dto/CashPostDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/cash/dto/CashPostDto.java
new file mode 100644
index 00000000..a219c644
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/cash/dto/CashPostDto.java
@@ -0,0 +1,21 @@
+package com.stockholm.main_project.cash.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+
+@Getter
+@Setter
+public class CashPostDto {
+
+
+ @Schema(description = "금액", defaultValue = "5000000")
+ @Min(value = 1000000, message = "금액은 최소 1,000,000 이상이어야 합니다.") // 최소값 설정
+ @Max(value = 500000000, message = "금액은 최대 500,000,000 이하여야 합니다.") // 최대값 설정
+ private long money;
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/cash/dto/CashResponseDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/cash/dto/CashResponseDto.java
new file mode 100644
index 00000000..ffff8818
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/cash/dto/CashResponseDto.java
@@ -0,0 +1,20 @@
+package com.stockholm.main_project.cash.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+@Getter
+@Setter
+public class CashResponseDto {
+ @Schema(description = "CashId", defaultValue = "1")
+ private long cashId;
+ @Schema(description = "금액", defaultValue = "5000000")
+ private long money;
+ @Schema(description = "생성 시간", defaultValue = "2023-09-04T12:00:00")
+ private LocalDateTime createdAt;
+ @Schema(description = "수정 시간", defaultValue = "2023-09-04T12:00:00")
+ private LocalDateTime modifiedAt;
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/cash/entity/Cash.java b/server/008main_project/src/main/java/com/stockholm/main_project/cash/entity/Cash.java
new file mode 100644
index 00000000..e4a93259
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/cash/entity/Cash.java
@@ -0,0 +1,33 @@
+package com.stockholm.main_project.cash.entity;
+
+import com.stockholm.main_project.audit.Auditable;
+import com.stockholm.main_project.member.entity.Member;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+
+@Entity
+@Getter
+@Setter
+@NoArgsConstructor
+@EntityListeners(AuditingEntityListener.class)
+public class Cash extends Auditable {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private long cashId;
+
+ @Column(nullable = false)
+ private long money;
+
+ @JoinColumn(name = "MEMBER_ID")
+ @OneToOne(fetch = FetchType.LAZY)
+ private Member member;
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/cash/mapper/CashMapper.java b/server/008main_project/src/main/java/com/stockholm/main_project/cash/mapper/CashMapper.java
new file mode 100644
index 00000000..128a4fef
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/cash/mapper/CashMapper.java
@@ -0,0 +1,20 @@
+package com.stockholm.main_project.cash.mapper;
+
+import com.stockholm.main_project.cash.dto.CashPatchDto;
+import com.stockholm.main_project.cash.dto.CashPostDto;
+import com.stockholm.main_project.cash.dto.CashResponseDto;
+import com.stockholm.main_project.cash.entity.Cash;
+import com.stockholm.main_project.member.dto.MemberPatchDto;
+import com.stockholm.main_project.member.dto.MemberPostDto;
+import com.stockholm.main_project.member.dto.MemberResponseDto;
+import com.stockholm.main_project.member.entity.Member;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring")
+public interface CashMapper {
+
+ Cash cashPostToCash(CashPostDto requestBody);
+
+ Cash cashPatchToCash(CashPatchDto requestBody);
+ CashResponseDto cashToCashResponseDto(Cash cash);
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/cash/repository/CashRepository.java b/server/008main_project/src/main/java/com/stockholm/main_project/cash/repository/CashRepository.java
new file mode 100644
index 00000000..de12ccdc
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/cash/repository/CashRepository.java
@@ -0,0 +1,13 @@
+package com.stockholm.main_project.cash.repository;
+
+import com.stockholm.main_project.cash.entity.Cash;
+import com.stockholm.main_project.member.entity.Member;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+public interface CashRepository extends JpaRepository {
+
+
+ List findByMemberAndMoney(Member member, long money);
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/cash/service/CashService.java b/server/008main_project/src/main/java/com/stockholm/main_project/cash/service/CashService.java
new file mode 100644
index 00000000..14e08a65
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/cash/service/CashService.java
@@ -0,0 +1,81 @@
+package com.stockholm.main_project.cash.service;
+
+import com.stockholm.main_project.cash.dto.CashPatchDto;
+import com.stockholm.main_project.cash.entity.Cash;
+import com.stockholm.main_project.cash.repository.CashRepository;
+import com.stockholm.main_project.exception.BusinessLogicException;
+import com.stockholm.main_project.exception.ExceptionCode;
+import com.stockholm.main_project.member.entity.Member;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+@Service
+@Slf4j
+public class CashService {
+
+ private final CashRepository cashRepository;
+
+
+ public CashService(CashRepository cashRepository) {
+ this.cashRepository = cashRepository;
+ }
+
+ public Cash createCash(Cash cash) {
+
+ if (isCashAlreadyExists(cash)) {
+ throw new BusinessLogicException(ExceptionCode.CASH_DUPLICATION);
+ }
+
+ Cash saveCash = cashRepository.save(cash);
+ System.out.println("# Create Cash");
+
+ return saveCash;
+ }
+
+ public Cash updateCash(long cashId, Member member, CashPatchDto patchDto){
+ Cash cash = findCash(member);
+
+// validateAuthor(cash, member);
+ cash.setMoney(patchDto.getMoney());
+
+ return cashRepository.save(cash);
+
+ }
+
+ public Cash findCash(Member member) {
+
+ Cash cash = member.getCash();
+
+ if (cash == null) {
+ throw new BusinessLogicException(ExceptionCode.INVALID_CASH);
+ }
+
+ return cash;
+ }
+
+ public void checkCash(long price, Member member) {
+ if(price > member.getCash().getMoney())
+ throw new BusinessLogicException(ExceptionCode.NOT_ENOUGH_MONEY);
+ else
+ return;
+ }
+
+ private void validateAuthor(Cash cash, Member member) {
+
+ if (!cash.getMember().equals(member)) {
+ throw new BusinessLogicException(ExceptionCode.INVALID_CASH);
+ }
+ }
+ private boolean isCashAlreadyExists(Cash cash) {
+ Member member = cash.getMember();
+ long money = cash.getMoney();
+
+ List existingCashList = cashRepository.findByMemberAndMoney(member, money);
+
+ return !existingCashList.isEmpty();
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/config/SecurityConfiguration.java b/server/008main_project/src/main/java/com/stockholm/main_project/config/SecurityConfiguration.java
new file mode 100644
index 00000000..32c438bc
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/config/SecurityConfiguration.java
@@ -0,0 +1,132 @@
+package com.stockholm.main_project.config;
+
+import com.stockholm.main_project.auth.filter.JwtAuthenticationFilter;
+import com.stockholm.main_project.auth.filter.JwtVerificationFilter;
+import com.stockholm.main_project.auth.handler.MemberAuthenticationFailureHandler;
+import com.stockholm.main_project.auth.handler.MemberAuthenticationSuccessHandler;
+import com.stockholm.main_project.auth.handler.OAuth2AuthenticationSuccessHandler;
+import com.stockholm.main_project.auth.jwt.JwtTokenizer;
+import com.stockholm.main_project.auth.utils.CustomAuthorityUtils;
+import com.stockholm.main_project.auth.utils.OAuth2MemberService;
+import com.stockholm.main_project.member.service.MemberService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+
+import java.util.List;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfiguration {
+ private final JwtTokenizer jwtTokenizer;
+ private final CustomAuthorityUtils authorityUtils;
+ @Autowired
+ public final OAuth2MemberService oAuth2MemberService;
+
+ private final MemberService memberService;
+
+ public SecurityConfiguration(JwtTokenizer jwtTokenizer, CustomAuthorityUtils authorityUtils, OAuth2MemberService oAuth2MemberService, @Lazy MemberService memberService) {
+ this.jwtTokenizer = jwtTokenizer;
+ this.authorityUtils = authorityUtils;
+ this.oAuth2MemberService = oAuth2MemberService;
+ this.memberService = memberService;
+ }
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
+ httpSecurity
+ .headers().frameOptions().sameOrigin() //H2 웹 콘솔에 정상적으로 접근 가능하도록 설정
+ .and()
+ .cors().configurationSource(corsConfigurationSource())// CORS 설정을 추가
+ .and()
+ .csrf().disable() //CSRF 공격에 대한 설정
+ .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)//.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)를 통해서 세션을 생성하지 않도록 설정
+ .and()
+ .formLogin().disable() // JSON 포맷 전달 방식 사용을 위해 비활성화
+ .httpBasic().disable() // request 전송마다 로그인 정보를 받지 않을 것임으로 비활성화
+ .apply(new CustomFilterConfigurer())
+ .and()
+ .authorizeHttpRequests(authorize -> authorize
+ .antMatchers("members/login").permitAll()
+ .antMatchers(HttpMethod.POST, "/cash").hasRole("USER")
+ .antMatchers(HttpMethod.PATCH, "/cash/{cashId}").hasRole("USER")
+ .antMatchers(HttpMethod.GET, "/cash").hasRole("USER")
+ .antMatchers(HttpMethod.POST, "/stock/buy").hasRole("USER")
+ .antMatchers(HttpMethod.POST, "/stock/sell").hasRole("USER")
+ .antMatchers(HttpMethod.GET, "/stock/stockholds").hasRole("USER")
+ .antMatchers(HttpMethod.GET, "/stock/stockorders").hasRole("USER")
+ .antMatchers(HttpMethod.DELETE, "/stock/stockorders").hasRole("USER")
+ .antMatchers(HttpMethod.GET, "long-polling/listen").hasRole("USER")
+ .antMatchers(HttpMethod.POST, "/api/boards").hasRole("USER")
+ .antMatchers(HttpMethod.PATCH, "/api/boards/{boardId}").hasRole("USER")
+ .antMatchers(HttpMethod.DELETE, "/api/boards/{boardId}").hasRole("USER")
+ .antMatchers(HttpMethod.GET, "api/boards").permitAll()
+ .antMatchers(HttpMethod.GET, "api/boards/{boardId}").permitAll() //질문을 선택해 조회하는 기능은 인증된 사용자에게만 혀용
+ .antMatchers(HttpMethod.POST, "/api/boards/{boardId}/comment").hasRole("USER")
+ .antMatchers(HttpMethod.PATCH, "/api/boards/{boardId}/comment/{commentId}").hasRole("USER")
+ .antMatchers(HttpMethod.DELETE, "/api/boards/{boardId}/comment/{commentId}").hasRole("USER")
+ .anyRequest().permitAll()
+ )
+ .oauth2Login(oauth2 -> oauth2
+ .userInfoEndpoint()
+ .userService(oAuth2MemberService)
+ .and()
+ .successHandler(new OAuth2AuthenticationSuccessHandler(jwtTokenizer, authorityUtils, memberService))
+ );
+
+ return httpSecurity.build();
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return PasswordEncoderFactories.createDelegatingPasswordEncoder();
+ }
+ @Bean
+ CorsConfigurationSource corsConfigurationSource() {
+ CorsConfiguration configuration = new CorsConfiguration();
+
+ configuration.setAllowCredentials(true);
+ configuration.setAllowedOrigins(List.of(
+ "http://seb008stockholm.s3-website.ap-northeast-2.amazonaws.com/",
+ "http://localhost:5173", "http://localhost:3000", "http://localhost:8080"
+ ,"chrome-extension://ggnhohnkfcpcanfekomdkjffnfcjnjam"
+ ));
+ configuration.setAllowedMethods(List.of("GET","POST", "PATCH", "DELETE"));
+ configuration.setAllowedHeaders(List.of("*"));
+ configuration.setExposedHeaders(List.of("*"));
+
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/**", configuration);
+ return source;
+ }
+ public class CustomFilterConfigurer extends AbstractHttpConfigurer {
+ @Override
+ public void configure(HttpSecurity builder) throws Exception {
+ AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
+
+ JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager, jwtTokenizer);
+ jwtAuthenticationFilter.setFilterProcessesUrl("/members/login");
+ jwtAuthenticationFilter.setAuthenticationSuccessHandler(new MemberAuthenticationSuccessHandler());
+ jwtAuthenticationFilter.setAuthenticationFailureHandler(new MemberAuthenticationFailureHandler());
+ builder.addFilter(jwtAuthenticationFilter);
+
+ JwtVerificationFilter jwtVerificationFilter = new JwtVerificationFilter(jwtTokenizer, authorityUtils, memberService);
+
+ builder
+ .addFilter(jwtAuthenticationFilter)
+ .addFilterAfter(jwtVerificationFilter, JwtAuthenticationFilter.class);
+ }
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/config/SwaggerConfig.java b/server/008main_project/src/main/java/com/stockholm/main_project/config/SwaggerConfig.java
new file mode 100644
index 00000000..cc3252ec
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/config/SwaggerConfig.java
@@ -0,0 +1,53 @@
+package com.stockholm.main_project.config;
+
+import io.swagger.v3.oas.annotations.OpenAPIDefinition;
+import io.swagger.v3.oas.annotations.info.Info;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import lombok.RequiredArgsConstructor;
+import org.springdoc.core.GroupedOpenApi;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpHeaders;
+
+@OpenAPIDefinition(
+ info = @Info(title = "Stockholm API 명세서",
+ description = "모의주식 투자 연습 사이트",
+ version = "v1"))
+@RequiredArgsConstructor
+@Configuration
+public class SwaggerConfig {
+
+ @Bean
+ public OpenAPI openAPI() {
+
+
+ // SecuritySecheme명
+ String jwtSchemeName = "jwtAuth";
+ // API 요청헤더에 인증정보 포함
+ SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwtSchemeName);
+ // SecuritySchemes 등록
+ Components components = new Components()
+ .addSecuritySchemes(jwtSchemeName, new SecurityScheme()
+ .name(jwtSchemeName)
+ .type(SecurityScheme.Type.HTTP) // HTTP 방식
+ .scheme("bearer")
+ .bearerFormat("JWT")); // 토큰 형식을 지정하는 임의의 문자(Optional)
+
+ return new OpenAPI()
+ .addSecurityItem(securityRequirement)
+ .components(components);
+ }
+
+ @Bean
+ public GroupedOpenApi chatOpenApi() {
+ String[] paths = {"/**"};
+
+ return GroupedOpenApi.builder()
+ .group("stockholm API v1")
+ .pathsToMatch(paths)
+ .build();
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/exception/BusinessLogicException.java b/server/008main_project/src/main/java/com/stockholm/main_project/exception/BusinessLogicException.java
new file mode 100644
index 00000000..5258f9ed
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/exception/BusinessLogicException.java
@@ -0,0 +1,13 @@
+package com.stockholm.main_project.exception;
+
+import lombok.Getter;
+
+public class BusinessLogicException extends RuntimeException {
+ @Getter
+ private ExceptionCode exceptionCode;
+
+ public BusinessLogicException(ExceptionCode exceptionCode) {
+ super(exceptionCode.getMessage());
+ this.exceptionCode = exceptionCode;
+ }
+}
\ No newline at end of file
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/exception/ExceptionCode.java b/server/008main_project/src/main/java/com/stockholm/main_project/exception/ExceptionCode.java
new file mode 100644
index 00000000..90f2c843
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/exception/ExceptionCode.java
@@ -0,0 +1,40 @@
+package com.stockholm.main_project.exception;
+
+import com.stockholm.main_project.stock.entity.StockAsBi;
+import lombok.Getter;
+
+public enum ExceptionCode {
+ MEMBER_NOT_FOUND(404, "회원을 찾을 수 없습니다"),
+ INVALID_EMAIL(404, "유효하지 않은 이메일 형식입니다"),
+ INVALID_NAME(400, "이름이 유효하지 않습니다"),
+ INVALID_CASH(404, "금액을 조회할 수 없습니다."),
+ EMAIL_DUPLICATION(400, "이미 존재하는 이메일입니다."),
+ CASH_DUPLICATION(400, "이미 보유한 현금이 있습니다."),
+ INVALID_PASSWORD(404, "비밀번호가 일치하지 않거나 유효하지 않습니다."),
+ BOARD_NOT_FOUND(404, "게시물을 찾을 수 없습니다."),
+ COMMENT_NOT_FOUND(404, "댓글을 찾을 수 없습니다."),
+ INVALID_FAILED(404, "작성자만 접근할 수 있습닌다."),
+ STOCKASBI_NOT_FOUND(404, "호가 정보를 찾을 수 없습니다"),
+ STOCKHOLD_NOT_FOUND(404, "보유 주식 정보가 없습니다."),
+ INSUFFICIENT_STOCK(422,"보유 주식이 부족합니다"),
+ NOT_ENOUGH_MONEY(422, "보유 금액이 부족합니다"),
+ STOCKORDER_NOT_FOUND(404, "주식 거래내역이 존재하지 않습니다"),
+ STOCKORDER_PERMISSION_DENIED(400,"잘못된 삭제 요청입니다."),
+ STOCKORDER_ALREADY_FINISH(400, "이미 완료된 거래입니다"),
+ AWS_CREDENTIALS_ERROR(401, "AWS 인증 오류"),
+ S3_UPLOAD_ERROR(500, "S3 파일 업로드 중 오류 발생"),
+ S3_DELETE_ERROR(500, "S3 파일 삭제 중 오류 발생"),
+ S3_URL_RETRIEVE_ERROR(500, "S3에서 파일 URL 검색 중 오류 발생");
+
+
+ @Getter
+ private int status;
+
+ @Getter
+ private String message;
+
+ ExceptionCode(int code, String message) {
+ this.status = code;
+ this.message = message;
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/member/controller/MemberController.java b/server/008main_project/src/main/java/com/stockholm/main_project/member/controller/MemberController.java
new file mode 100644
index 00000000..fc63c964
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/member/controller/MemberController.java
@@ -0,0 +1,97 @@
+package com.stockholm.main_project.member.controller;
+
+import com.stockholm.main_project.member.dto.MemberPatchDto;
+import com.stockholm.main_project.member.dto.MemberPostDto;
+import com.stockholm.main_project.member.dto.MemberResponseDto;
+import com.stockholm.main_project.member.entity.Member;
+import com.stockholm.main_project.member.mapper.MemberMapper;
+import com.stockholm.main_project.member.service.MemberService;
+import com.stockholm.main_project.swaggersample.HelloResponse;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+
+@RestController
+@RequestMapping("/members")
+@Slf4j
+public class MemberController {
+ private final MemberService memberService;
+ private final MemberMapper mapper;
+
+
+ public MemberController(MemberService memberService, MemberMapper mapper) {
+ this.memberService = memberService;
+ this.mapper = mapper;
+ }
+
+ @Operation(summary = "회원가입", description = "자체 회원가입 요청이 POST됩니다.", tags = { "Member" })
+ @ApiResponse(responseCode = "201", description = "CREATED",
+ content = @Content(schema = @Schema(implementation = MemberResponseDto.class)))
+ @ApiResponse(responseCode = "400", description = "EMAIL_DUPLICATION")
+ @ApiResponse(responseCode = "404", description = "INVALID_PASSWORD")
+ @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR")
+ @PostMapping
+ public ResponseEntity postMember(@Schema(implementation = MemberPostDto.class)@Valid @RequestBody MemberPostDto memberPostDto){
+ Member member = mapper.memberPostToMember(memberPostDto);
+
+ Member response = memberService.createMember(member);
+
+ return new ResponseEntity<>(mapper.memberToMemberResponseDto(response),
+ HttpStatus.CREATED);
+ }
+
+ @Operation(summary = "회원 정보 변경", description = "가입한 계정의 이름을 PATCH합니다.", tags = { "Member" })
+ @ApiResponse(responseCode = "200", description = "OK",
+ content = @Content(schema = @Schema(implementation = MemberResponseDto.class)))
+ @ApiResponse(responseCode = "400", description = "BAD REQUEST")
+ @ApiResponse(responseCode = "404", description = "MEMBER NOT FOUND")
+ @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR")
+ @PatchMapping
+ private ResponseEntity patchMember(@Schema(implementation = MemberPatchDto.class)@RequestBody MemberPatchDto memberPatchDto, @AuthenticationPrincipal Member member){
+
+ memberPatchDto.setMemberId(member.getMemberId());
+
+ Member patchedMember = new Member();
+ patchedMember.setMemberId(memberPatchDto.getMemberId());
+ patchedMember.setName(memberPatchDto.getName());
+ patchedMember.setEmail(memberPatchDto.getEmail());
+
+ Member response = memberService.updateMember(patchedMember);
+
+ return new ResponseEntity<>(mapper.memberToMemberResponseDto(response), HttpStatus.OK);
+ }
+
+
+ @Operation(summary = "회원 조회", description = "가입한 계정 중 하나가 GET됩니다.", tags = { "Member" })
+ @ApiResponse(responseCode = "200", description = "OK",
+ content = @Content(schema = @Schema(implementation = MemberResponseDto.class)))
+ @ApiResponse(responseCode = "400", description = "BAD REQUEST")
+ @ApiResponse(responseCode = "404", description = "NOT FOUND")
+ @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR")
+ @GetMapping
+ private ResponseEntity getMember(@AuthenticationPrincipal Member member){
+ Member response = memberService.findMember(member.getMemberId());
+
+ return new ResponseEntity<>(mapper.memberToMemberResponseDto(response), HttpStatus.OK);
+ }
+
+ @Operation(summary = "회원 삭제", description = "가입한 계정을 DELETE합니다.", tags = { "Member" })
+ @ApiResponse(responseCode = "204", description = "NO CONTENT")
+ @ApiResponse(responseCode = "404", description = "NOT FOUND")
+ @ApiResponse(responseCode = "400", description = "BAD REQUEST")
+ @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR")
+ @DeleteMapping
+ private ResponseEntity deleteMember(@AuthenticationPrincipal Member member){
+ memberService.deleteMember(member.getMemberId());
+
+ return new ResponseEntity(HttpStatus.NO_CONTENT);
+ }
+}
\ No newline at end of file
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/member/dto/MemberPatchDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/member/dto/MemberPatchDto.java
new file mode 100644
index 00000000..68b94853
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/member/dto/MemberPatchDto.java
@@ -0,0 +1,17 @@
+package com.stockholm.main_project.member.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class MemberPatchDto {
+ @Schema(description = "MemberId", defaultValue = "1")
+ private long memberId;
+ @Schema(description = "Email 변경 불가", defaultValue = "Test@example.com")
+ private String email;
+ @Schema(description = "변경을 원하는 이름", defaultValue = "TestName12")
+ private String name;
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/member/dto/MemberPostDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/member/dto/MemberPostDto.java
new file mode 100644
index 00000000..36e58b1e
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/member/dto/MemberPostDto.java
@@ -0,0 +1,37 @@
+package com.stockholm.main_project.member.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.validation.constraints.*;
+
+@Getter
+@Setter
+public class MemberPostDto {
+ @Email
+ @NotBlank
+ @Pattern(regexp = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]+$",
+ message = "올바른 이메일 구성이 아닙니다.")
+ @Schema(description = "Email", defaultValue = "Test@example.com")
+ private String email;
+
+ @NotBlank
+ @Schema(description = "이름", defaultValue = "TestName")
+ private String name;
+
+ @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]{8,}$",
+ message = "1자 이상의 문자, 1개 이상의 숫자, 1개 이상의 특수문자를 포함하고 8자리 이상이어야 합니다.")
+ @NotBlank
+ @Schema(description = "비밀번호", defaultValue = "test123!@#")
+ private String password;
+
+ @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]{8,}$",
+ message = "1자 이상의 문자, 1개 이상의 숫자, 1개 이상의 특수문자를 포함하고 8자리 이상이어야 합니다.")
+ @NotBlank
+ @Schema(description = "비밀번호 확인", defaultValue = "test123!@#")
+ private String confirmPassword;
+
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/member/dto/MemberResponseDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/member/dto/MemberResponseDto.java
new file mode 100644
index 00000000..94ae1c2f
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/member/dto/MemberResponseDto.java
@@ -0,0 +1,22 @@
+package com.stockholm.main_project.member.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+@Getter
+@Setter
+public class MemberResponseDto {
+ @Schema(description = "MemberId", defaultValue = "1")
+ public long memberId;
+ @Schema(description = "Email", defaultValue = "Test@example.com")
+ public String email;
+ @Schema(description = "이름", defaultValue = "TestName")
+ private String name;
+ @Schema(description = "CashID", defaultValue = "1")
+ private String cash;
+ @Schema(description = "생성 시간", defaultValue = "LocalDateTime")
+ private LocalDateTime createdAt;
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/member/entity/Member.java b/server/008main_project/src/main/java/com/stockholm/main_project/member/entity/Member.java
new file mode 100644
index 00000000..1568826e
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/member/entity/Member.java
@@ -0,0 +1,71 @@
+package com.stockholm.main_project.member.entity;
+
+import com.stockholm.main_project.audit.Auditable;
+import com.stockholm.main_project.cash.entity.Cash;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.persistence.*;
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity
+@Getter
+@Setter
+@NoArgsConstructor
+public class Member extends Auditable {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private long memberId;
+
+ @Column(length = 30, nullable = false)
+ private String email;
+
+ @Column(length = 10, nullable = false)
+ private String name;
+
+ @Column(length = 255, nullable = true)
+ private String password;
+
+ @OneToOne(mappedBy = "member", cascade = CascadeType.ALL)
+ private Cash cash;
+
+ @Transient
+ private String confirmPassword; //실제 저장을 하지 않기 위해 @Transient 사용
+
+ @Enumerated(value = EnumType.STRING)
+ @Column(length = 20, nullable = false)
+ private MemberStatus memberStatus = MemberStatus.MEMBER_ACTIVE;
+
+ @ElementCollection(fetch = FetchType.EAGER)
+ private List roles = new ArrayList<>();
+
+ public enum MemberStatus {
+ MEMBER_ACTIVE("활동중"),
+ MEMBER_QUIT("탈퇴 상태");
+
+ @Getter
+ private String status;
+
+ MemberStatus(String status) {
+ this.status = status;
+ }
+ }
+// @Builder MemberMapper에서 PostDto의 confirmPassword 객체를 인식하지 못해 아래와 같이 변경
+// public Member(String name, String email, String password, List roles) {
+// this.name = name;
+// this.email = email;
+// this.password = password;
+// this.roles = roles;
+// }
+ @Builder // Mapper에서 사용하도록 추가
+ public Member(String name, String email, String password, List roles, String confirmPassword) {
+ this.name = name;
+ this.email = email;
+ this.password = password;
+ this.roles = roles;
+ this.confirmPassword = confirmPassword;
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/member/mapper/MemberMapper.java b/server/008main_project/src/main/java/com/stockholm/main_project/member/mapper/MemberMapper.java
new file mode 100644
index 00000000..312965f9
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/member/mapper/MemberMapper.java
@@ -0,0 +1,20 @@
+package com.stockholm.main_project.member.mapper;
+
+import com.stockholm.main_project.member.dto.MemberPatchDto;
+import com.stockholm.main_project.member.dto.MemberPostDto;
+import com.stockholm.main_project.member.dto.MemberResponseDto;
+import com.stockholm.main_project.member.entity.Member;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+@Mapper(componentModel = "spring")
+public interface MemberMapper {
+
+
+ Member memberPostToMember(MemberPostDto requestBody);
+ Member memberPatchToMember(MemberPatchDto requestBody);
+
+ @Mapping(source = "cash.cashId", target = "cash")
+ MemberResponseDto memberToMemberResponseDto(Member member);
+}
+
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/member/repository/MemberRepository.java b/server/008main_project/src/main/java/com/stockholm/main_project/member/repository/MemberRepository.java
new file mode 100644
index 00000000..3122c5e9
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/member/repository/MemberRepository.java
@@ -0,0 +1,12 @@
+package com.stockholm.main_project.member.repository;
+
+import com.stockholm.main_project.member.entity.Member;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+
+public interface MemberRepository extends JpaRepository {
+
+ Optional findByEmail(String email);
+ Optional findByMemberId(Long memberId);
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/member/service/MemberService.java b/server/008main_project/src/main/java/com/stockholm/main_project/member/service/MemberService.java
new file mode 100644
index 00000000..f669dbee
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/member/service/MemberService.java
@@ -0,0 +1,124 @@
+package com.stockholm.main_project.member.service;
+
+import com.stockholm.main_project.auth.utils.CustomAuthorityUtils;
+import com.stockholm.main_project.exception.BusinessLogicException;
+import com.stockholm.main_project.exception.ExceptionCode;
+import com.stockholm.main_project.member.entity.Member;
+import com.stockholm.main_project.member.repository.MemberRepository;
+import com.stockholm.main_project.stock.entity.StockOrder;
+import com.stockholm.main_project.stock.repository.StockOrderRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+@Service
+@Slf4j
+public class MemberService {
+
+ private final MemberRepository memberRepository;
+ private final PasswordEncoder passwordEncoder;
+ private final CustomAuthorityUtils authorityUtils;
+ private final StockOrderRepository stockOrderRepository;
+
+ public MemberService(MemberRepository memberRepository, PasswordEncoder passwordEncoder, CustomAuthorityUtils authorityUtils, StockOrderRepository stockOrderRepository) {
+ this.memberRepository = memberRepository;
+ this.passwordEncoder = passwordEncoder;
+ this.authorityUtils = authorityUtils;
+ this.stockOrderRepository = stockOrderRepository;
+ }
+
+ public Member createMember(Member member) {
+
+ verifyExistsEmail(member.getEmail());
+
+ String password = member.getPassword();
+ String confirmPassword = member.getConfirmPassword();
+
+ if (!password.equals(confirmPassword)) {
+ throw new BusinessLogicException(ExceptionCode.INVALID_PASSWORD);
+ }// 암호 재확인 기능
+
+ String encryptedPassword = passwordEncoder.encode(member.getPassword());
+ member.setPassword(encryptedPassword);
+
+ List roles = authorityUtils.createRoles(member.getEmail());
+ member.setRoles(roles);
+
+ member.setMemberStatus(Member.MemberStatus.MEMBER_ACTIVE);
+ Member saveMember = memberRepository.save(member);
+ System.out.println("# Create Member in DB");
+
+ return saveMember;
+ }
+
+ public Member updateMember(Member member) {
+
+
+ Member findMember = findVerifiedMember(member.getMemberId());
+
+ Optional.ofNullable(member.getName())
+ .ifPresent(name -> findMember.setName(name));
+
+ return memberRepository.save(findMember);
+ }
+
+ public Member findMember(long memberId) {
+
+ Member findMember = findVerifiedMember(memberId);
+
+ if (findMember.getMemberId() != memberId) {
+ throw new BusinessLogicException(ExceptionCode.INVALID_FAILED);
+ }
+
+ return findVerifiedMember(findMember.getMemberId());
+ }
+
+ public void deleteMember(long memberId) {
+
+ memberRepository.deleteById(memberId);
+
+ List orders = stockOrderRepository.findByMemberMemberId(memberId);
+ stockOrderRepository.deleteAll(orders);
+ }
+
+ public Member findVerifiedMember(long memberId) {
+
+ Optional optionalMember =
+ memberRepository.findByMemberId(memberId);
+ Member findMember =
+ optionalMember.orElseThrow(() ->
+ new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND));
+ return findMember;
+ }
+
+ public void verifyExistsEmail(String email) {
+ Optional member = memberRepository.findByEmail(email);
+ if (member.isPresent())
+ throw new BusinessLogicException(ExceptionCode.EMAIL_DUPLICATION);
+ }
+
+ public Member findMemberByEmail(String email) {
+ Optional optionalUser = memberRepository.findByEmail(email);
+
+ return optionalUser.orElse(null);
+ }
+
+ public int findMemberIdByEmail(String email) {
+ Optional optionalMember = memberRepository.findByEmail(email);
+
+ if (optionalMember.isPresent()) {
+ Member member = optionalMember.get();
+ return (int) member.getMemberId();
+ } else {
+
+ throw new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND);
+ }
+ }
+
+// public void deleteStockOrdersByMemberId(Long memberId) {
+// stockOrderRepository.deleteAllByMemberId(memberId);
+// }
+}
\ No newline at end of file
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/scheduler/SchedulerConfig.java b/server/008main_project/src/main/java/com/stockholm/main_project/scheduler/SchedulerConfig.java
new file mode 100644
index 00000000..5301d419
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/scheduler/SchedulerConfig.java
@@ -0,0 +1,21 @@
+package com.stockholm.main_project.scheduler;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.SchedulingConfigurer;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.scheduling.config.ScheduledTaskRegistrar;
+
+@Configuration
+public class SchedulerConfig implements SchedulingConfigurer {
+ private final int POOL_SIZE = 10;
+
+ @Override
+ public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
+ final ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
+ threadPoolTaskScheduler.setPoolSize(POOL_SIZE);
+ threadPoolTaskScheduler.setThreadNamePrefix("test-scheduled-task-pool-");
+ threadPoolTaskScheduler.initialize();
+
+ taskRegistrar.setTaskScheduler(threadPoolTaskScheduler);
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/scheduler/StockScheduler.java b/server/008main_project/src/main/java/com/stockholm/main_project/scheduler/StockScheduler.java
new file mode 100644
index 00000000..b080e51f
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/scheduler/StockScheduler.java
@@ -0,0 +1,248 @@
+package com.stockholm.main_project.scheduler;
+
+import com.stockholm.main_project.stock.service.*;
+import com.stockholm.main_project.websocket.WebSocketController;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import java.time.Duration;
+import java.time.LocalDateTime;
+
+
+@Slf4j
+@Service
+public class StockScheduler {
+ private final StockAsBiService stockAsBiService;
+ private final StockMinService stockMinService;
+ private final CompanyService companyService;
+ private final TokenService tokenService;
+ private final StockOrderService stockOrderService;
+ private final WebSocketController webSocketController;
+
+ public StockScheduler(StockAsBiService stockAsBiService, StockMinService stockMinService, CompanyService companyService, TokenService tokenService, StockOrderService stockOrderService, WebSocketController webSocketController) {
+ this.stockAsBiService = stockAsBiService;
+ this.stockMinService = stockMinService;
+ this.companyService = companyService;
+ this.tokenService = tokenService;
+ this.stockOrderService = stockOrderService;
+ this.webSocketController = webSocketController;
+ }
+
+ @Scheduled(cron = "0 30 9-15 * * MON-FRI")
+ public void myScheduledStockAsBiMethod() throws InterruptedException {
+ // 이 메소드는 매주 월요일부터 금요일까지 9:30부터 15:30까지 30분 간격으로 실행됩니다.
+ // 원하는 작업을 여기에 추가하세요.
+ LocalDateTime start = LocalDateTime.now();
+ stockAsBiService.updateStockAsBi();
+ stockOrderService.checkOrder();
+ LocalDateTime end = LocalDateTime.now();
+ Duration duration = Duration.between(start, end);
+ System.out.println(duration.getSeconds());
+
+ }
+
+ @Scheduled(cron = "0 0 10-15 * * MON-FRI")
+ public void myScheduledStockAsBiMethod2() throws InterruptedException {
+ // 이 메소드는 매주 월요일부터 금요일까지 9:30부터 15:30까지 30분 간격으로 실행됩니다.
+ // 원하는 작업을 여기에 추가하세요.
+ LocalDateTime start = LocalDateTime.now();
+ stockAsBiService.updateStockAsBi();
+ stockOrderService.checkOrder();
+ LocalDateTime end = LocalDateTime.now();
+ Duration duration = Duration.between(start, end);
+ System.out.println(duration.getSeconds());
+
+ }
+
+ @Scheduled(cron = "0 30 9-15 * * MON-FRI")
+ public void myScheduledStockMinMethod() throws InterruptedException {
+ // 이 메소드는 매주 월요일부터 금요일까지 9:30부터 15:30까지 30분 간격으로 실행됩니다.
+ // 원하는 작업을 여기에 추가하세요.
+ LocalDateTime start = LocalDateTime.now();
+ stockMinService.updateStockMin();
+ LocalDateTime end = LocalDateTime.now();
+ Duration duration = Duration.between(start, end);
+ System.out.println(duration.getSeconds());
+
+ }
+
+ @Scheduled(cron = "0 0 10-15 * * MON-FRI")
+ public void myScheduledStockMinMethod2() throws InterruptedException {
+ // 이 메소드는 매주 월요일부터 금요일까지 9:30부터 15:30까지 30분 간격으로 실행됩니다.
+ // 원하는 작업을 여기에 추가하세요.
+ LocalDateTime start = LocalDateTime.now();
+ stockMinService.updateStockMin();
+ LocalDateTime end = LocalDateTime.now();
+ Duration duration = Duration.between(start, end);
+ System.out.println(duration.getSeconds());
+
+ }
+
+// @Scheduled(fixedRate = 1000)
+// public void run() throws InterruptedException {
+// webSocketController.check();
+// }
+
+// @Scheduled(fixedRate = 10000000)
+// public void secondSchedule() throws InterruptedException {
+// LocalDateTime start = LocalDateTime.now();
+// stockAsBiService.updateStockAsBi();
+// stockMinService.updateStockMin();
+// LocalDateTime end = LocalDateTime.now();
+// Duration duration = Duration.between(start, end);
+// System.out.println(duration.getSeconds());
+//
+// }
+
+
+// @Scheduled(fixedRate = 10000000)
+// public void firstSchedule() throws InterruptedException {
+// LocalDateTime start = LocalDateTime.now();
+// stockAsBiService.updateStockAsBi();
+// LocalDateTime end = LocalDateTime.now();
+// Duration duration = Duration.between(start, end);
+// System.out.println(duration.getSeconds());
+// }
+
+// @Scheduled(fixedRate = 10000000)
+// public void secondSchedule() throws InterruptedException {
+// LocalDateTime start = LocalDateTime.now();
+// stockMinService.updateStockMin();
+// LocalDateTime end = LocalDateTime.now();
+// Duration duration = Duration.between(start, end);
+// System.out.println(duration.getSeconds());
+// }
+
+}
+
+
+
+// @Scheduled(fixedRate = 10000000)
+// public void secondSchedule() throws InterruptedException {
+// LocalDateTime start = LocalDateTime.now();
+// stockMinService.updateStockMin();
+// LocalDateTime end = LocalDateTime.now();
+// Duration duration = Duration.between(start, end);
+// System.out.println(duration.getSeconds());
+// }
+
+// @Scheduled(fixedRate = 10000000)
+// public void secondSchedule() throws InterruptedException {
+// LocalDateTime start = LocalDateTime.now();
+// stockAsBiService.updateStockAsBi();
+// stockMinService.updateStockMin();
+// LocalDateTime end = LocalDateTime.now();
+// Duration duration = Duration.between(start, end);
+// System.out.println(duration.getSeconds());
+// }
+
+
+
+
+
+// @Scheduled(fixedRate = 60000)
+// public void run() {
+// List stockasbiDataDtos = stockService.getStockasbiData();
+//
+// System.out.println("run1");
+//
+// for(StockasbiDataDto stockasbiDataDto : stockasbiDataDtos) {
+// log.info(stockasbiDataDto.getOutput1().getAskp1());
+// log.info(stockasbiDataDto.getOutput1().getAskp2());
+// log.info(stockasbiDataDto.getOutput1().getAskp3());
+// log.info(stockasbiDataDto.getOutput1().getAskp4());
+// log.info(stockasbiDataDto.getOutput1().getAskp5());
+// }
+// }
+//
+// @Scheduled(fixedRate = 60000, initialDelay = 1000)
+// public void run2() {
+// List stockasbiDataDtos = stockService.getStockasbiData();
+//
+// System.out.println("run2");
+//
+// for(StockasbiDataDto stockasbiDataDto : stockasbiDataDtos) {
+// log.info(stockasbiDataDto.getOutput1().getAskp1());
+// log.info(stockasbiDataDto.getOutput1().getAskp2());
+// log.info(stockasbiDataDto.getOutput1().getAskp3());
+// log.info(stockasbiDataDto.getOutput1().getAskp4());
+// log.info(stockasbiDataDto.getOutput1().getAskp5());
+// }
+// }
+//
+// @Scheduled(fixedRate = 60000, initialDelay = 2000)
+// public void run3() {
+// List stockasbiDataDtos = stockService.getStockasbiData();
+//
+// System.out.println("run3");
+//
+// for(StockasbiDataDto stockasbiDataDto : stockasbiDataDtos) {
+// log.info(stockasbiDataDto.getOutput1().getAskp1());
+// log.info(stockasbiDataDto.getOutput1().getAskp2());
+// log.info(stockasbiDataDto.getOutput1().getAskp3());
+// log.info(stockasbiDataDto.getOutput1().getAskp4());
+// log.info(stockasbiDataDto.getOutput1().getAskp5());
+// }
+// }
+//
+// @Scheduled(fixedRate = 60000, initialDelay = 3000)
+// public void run4() {
+// List stockasbiDataDtos = stockService.getStockasbiData();
+//
+// System.out.println("run4");
+//
+// for(StockasbiDataDto stockasbiDataDto : stockasbiDataDtos) {
+// log.info(stockasbiDataDto.getOutput1().getAskp1());
+// log.info(stockasbiDataDto.getOutput1().getAskp2());
+// log.info(stockasbiDataDto.getOutput1().getAskp3());
+// log.info(stockasbiDataDto.getOutput1().getAskp4());
+// log.info(stockasbiDataDto.getOutput1().getAskp5());
+// }
+// }
+//
+// @Scheduled(fixedRate = 60000, initialDelay = 4000)
+// public void run5() {
+// List stockasbiDataDtos = stockService.getStockasbiData();
+//
+// System.out.println("run5");
+//
+// for(StockasbiDataDto stockasbiDataDto : stockasbiDataDtos) {
+// log.info(stockasbiDataDto.getOutput1().getAskp1());
+// log.info(stockasbiDataDto.getOutput1().getAskp2());
+// log.info(stockasbiDataDto.getOutput1().getAskp3());
+// log.info(stockasbiDataDto.getOutput1().getAskp4());
+// log.info(stockasbiDataDto.getOutput1().getAskp5());
+// }
+// }
+//
+// @Scheduled(fixedRate = 60000, initialDelay = 5000)
+// public void run6() {
+// List stockasbiDataDtos = stockService.getStockasbiData();
+//
+// System.out.println("run6");
+//
+// for(StockasbiDataDto stockasbiDataDto : stockasbiDataDtos) {
+// log.info(stockasbiDataDto.getOutput1().getAskp1());
+// log.info(stockasbiDataDto.getOutput1().getAskp2());
+// log.info(stockasbiDataDto.getOutput1().getAskp3());
+// log.info(stockasbiDataDto.getOutput1().getAskp4());
+// log.info(stockasbiDataDto.getOutput1().getAskp5());
+// }
+// }
+//
+// @Scheduled(fixedRate = 60000, initialDelay = 6000)
+// public void run7() {
+// List stockasbiDataDtos = stockService.getStockasbiData();
+//
+// System.out.println("run7");
+//
+// for(StockasbiDataDto stockasbiDataDto : stockasbiDataDtos) {
+// log.info(stockasbiDataDto.getOutput1().getAskp1());
+// log.info(stockasbiDataDto.getOutput1().getAskp2());
+// log.info(stockasbiDataDto.getOutput1().getAskp3());
+// log.info(stockasbiDataDto.getOutput1().getAskp4());
+// log.info(stockasbiDataDto.getOutput1().getAskp5());
+// }
+// }
+
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/controller/CompanyController.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/controller/CompanyController.java
new file mode 100644
index 00000000..a76e6eb6
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/controller/CompanyController.java
@@ -0,0 +1,92 @@
+package com.stockholm.main_project.stock.controller;
+
+import com.stockholm.main_project.stock.dto.CompanyResponseDto;
+import com.stockholm.main_project.stock.dto.StockMinResponseDto;
+import com.stockholm.main_project.stock.entity.Company;
+import com.stockholm.main_project.stock.mapper.StockMapper;
+import com.stockholm.main_project.stock.service.CompanyService;
+import com.stockholm.main_project.stock.service.StockMinService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+import java.util.List;
+
+@RestController
+@RequestMapping("/companies")
+@Valid
+@Slf4j
+public class CompanyController {
+
+ private final CompanyService companyService;
+ private final StockMapper stockMapper;
+ private StockMinService stockMinService;
+
+ public CompanyController(CompanyService companyService, StockMapper stockMapper, StockMinService stockMinService) {
+ this.companyService = companyService;
+ this.stockMapper = stockMapper;
+ this.stockMinService = stockMinService;
+ }
+ // swagger 추가
+ @Operation(summary = "CompanyList 가져오기", description = "CompanyList를 Get해 옵니다", tags = { "Company" })
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK",
+ content = @Content(array = @ArraySchema(schema = @Schema(implementation = CompanyResponseDto.class)))),
+ @ApiResponse(responseCode = "400", description = "BAD REQUEST"),
+ @ApiResponse(responseCode = "404", description = "NOT FOUND"),
+ @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR")
+ })
+ // 전체 회사 리스트
+ @GetMapping
+ public ResponseEntity getCompanyList() {
+ List companyList = companyService.findCompanies();
+ List companyResponseDtoList = stockMapper.CompaniesToCompanyResponseDtos(companyList);
+
+ return new ResponseEntity<>(companyResponseDtoList, HttpStatus.OK);
+ }
+
+ // swagger 추가
+ @Operation(summary = "특정 회사의 주식 호가 정보 가져오기", description = "특정 회사의 주식 호가 정보를 Get해 옵니다", tags = { "Stock" })
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK",
+ content = @Content(schema = @Schema(implementation = CompanyResponseDto.class))),
+ @ApiResponse(responseCode = "400", description = "BAD REQUEST"),
+ @ApiResponse(responseCode = "404", description = "NOT FOUND"),
+ @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR")
+ })
+ // 주식 호가 정보
+ @GetMapping("/{companyId}")
+ public ResponseEntity getCompanyStockAsBi(@PathVariable("companyId") Long comanyId) {
+ Company company = companyService.findCompanyById(comanyId);
+ CompanyResponseDto companyResponseDto = stockMapper.companyToCompanyResponseDto(company);
+
+ return new ResponseEntity<>(companyResponseDto, HttpStatus.OK);
+ }
+
+ @Operation(summary = "주식 한개 분봉 차트 불러오기", description = "주식 하나의 분봉 420개를 불러옵니다", tags = { "Stock" })
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK",
+ content = @Content(array = @ArraySchema(schema = @Schema(implementation = StockMinResponseDto.class)))),
+ @ApiResponse(responseCode = "400", description = "BAD REQUEST"),
+ @ApiResponse(responseCode = "404", description = "NOT FOUND"),
+ @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR")
+ })
+ // 차트 하나 호출
+ @GetMapping("/charts/{companyId}")
+ public ResponseEntity getCompanyChart(@PathVariable("companyId") long companyId) {
+ List stockMinList = stockMinService.getRecent420StockMin(companyId);
+
+ return new ResponseEntity(stockMinList, HttpStatus.OK);
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/controller/LongPollingController.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/controller/LongPollingController.java
new file mode 100644
index 00000000..63737be0
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/controller/LongPollingController.java
@@ -0,0 +1,82 @@
+package com.stockholm.main_project.stock.controller;
+
+import com.stockholm.main_project.member.entity.Member;
+import com.stockholm.main_project.stock.dto.StockOrderResponseDto;
+import com.stockholm.main_project.stock.entity.StockOrder;
+import com.stockholm.main_project.stock.mapper.StockMapper;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RestController
+@RequestMapping("long-polling")
+public class LongPollingController {
+ private List updateBuyStockOrders;
+ private List updateSellStockOrders;
+ private final StockMapper stockMapper;
+
+ public LongPollingController(StockMapper stockMapper) {
+ this.stockMapper = stockMapper;
+ }
+
+ @Operation(summary = "예약된 매수, 매도가 완료되면 StockOrder를 반환한다", description = "완료된 StockOrder를 반환한다", tags = { "Stock" })
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK",
+ content = @Content(schema = @Schema(implementation = StockOrderResponseDto.class))),
+ @ApiResponse(responseCode = "400", description = "BAD REQUEST"),
+ @ApiResponse(responseCode = "404", description = "NOT FOUND"),
+ @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR")
+ })
+ @GetMapping("/listen")
+ public ResponseEntity listenForUpdate(@AuthenticationPrincipal Member member) throws InterruptedException {
+ updateBuyStockOrders = new ArrayList<>();
+ updateSellStockOrders = new ArrayList<>();
+
+ waitForStockOrdersToUpdate();
+
+ List memberBuyStockOrder = updateBuyStockOrders.stream()
+ .filter(stockOrder -> stockOrder.getMember().getMemberId() == member.getMemberId())
+ .collect(Collectors.toList());
+
+ List memberSellStockOrder = updateSellStockOrders.stream()
+ .filter(stockOrder -> stockOrder.getMember().getMemberId() == member.getMemberId())
+ .collect(Collectors.toList());
+
+ List buyStockOrderResponseDtos = stockMapper.stockOrdersToStockOrderResponseDtos(memberBuyStockOrder);
+ List sellStockOrderResponseDtos = stockMapper.stockOrdersToStockOrderResponseDtos(memberSellStockOrder);
+
+ List> updateStockOrders = new ArrayList<>();
+ updateStockOrders.add(buyStockOrderResponseDtos);
+ updateStockOrders.add(sellStockOrderResponseDtos);
+
+ return new ResponseEntity(updateStockOrders, HttpStatus.OK);
+ }
+
+ private void waitForStockOrdersToUpdate() throws InterruptedException {
+ // 변경된 데이터가 도착할 때까지 대기
+ synchronized (this) {
+ if (updateBuyStockOrders.isEmpty() && updateSellStockOrders.isEmpty()) {
+ wait();
+ }
+ }
+ }
+
+ public synchronized void notifyDataUpdated(List buyStockOrders,
+ List sellStockOrders) {
+ updateBuyStockOrders = buyStockOrders;
+ updateSellStockOrders = sellStockOrders;
+ notify(); // 대기 중인 스레드를 깨워 응답을 보냄
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/controller/StarController.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/controller/StarController.java
new file mode 100644
index 00000000..667cbb0c
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/controller/StarController.java
@@ -0,0 +1,44 @@
+package com.stockholm.main_project.stock.controller;
+
+import com.stockholm.main_project.member.entity.Member;
+import com.stockholm.main_project.stock.dto.StarResponseDto;
+import com.stockholm.main_project.stock.service.StarService;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/stars")
+public class StarController {
+ private final StarService starService;
+
+ public StarController(StarService starService) {
+ this.starService = starService;
+ }
+
+ @PostMapping
+ public ResponseEntity setStar(@RequestParam long companyId,
+ @AuthenticationPrincipal Member member) {
+ starService.saveStar(member, companyId);
+
+ return new ResponseEntity(HttpStatus.CREATED);
+ }
+
+ @GetMapping
+ public ResponseEntity getStarList(@AuthenticationPrincipal Member member) {
+ List starResponseDtos = starService.getStarResponseDtoList(member);
+
+ return new ResponseEntity<>(starResponseDtos, HttpStatus.OK);
+ }
+
+ @DeleteMapping
+ public ResponseEntity deleteStar(@RequestParam long companyId,
+ @AuthenticationPrincipal Member member) {
+ starService.deleteStar(member, companyId);
+
+ return new ResponseEntity<>(HttpStatus.OK);
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/controller/StockController.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/controller/StockController.java
new file mode 100644
index 00000000..926df199
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/controller/StockController.java
@@ -0,0 +1,43 @@
+package com.stockholm.main_project.stock.controller;
+
+
+import com.stockholm.main_project.stock.dto.StockMinResponseDto;
+import com.stockholm.main_project.stock.dto.StockasbiDataDto;
+import com.stockholm.main_project.stock.service.ApiCallService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class StockController {
+
+ private final ApiCallService apiCallService;
+
+ public StockController(ApiCallService apiCallService) {
+ this.apiCallService = apiCallService;
+ }
+
+
+ @Operation(summary = "코스피 월봉 정보 가져오기", description = "월 초부터 현재 달까지의 월 코스피 정보를 불러옵니다.", tags = { "Stock" })
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK",
+ content = @Content(array = @ArraySchema(schema = @Schema(implementation = String.class)))),
+ @ApiResponse(responseCode = "400", description = "BAD REQUEST"),
+ @ApiResponse(responseCode = "404", description = "NOT FOUND"),
+ @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR")
+ })
+ @GetMapping("/kospi")
+ public ResponseEntity getKospiMonth() {
+ String kospi = apiCallService.getKospiMonthFromApi();
+ return ResponseEntity.ok(kospi);
+ }
+
+
+}
+
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/controller/StockOrderController.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/controller/StockOrderController.java
new file mode 100644
index 00000000..d63d516c
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/controller/StockOrderController.java
@@ -0,0 +1,141 @@
+package com.stockholm.main_project.stock.controller;
+
+import com.stockholm.main_project.member.dto.MemberResponseDto;
+import com.stockholm.main_project.member.entity.Member;
+import com.stockholm.main_project.stock.dto.CompanyResponseDto;
+import com.stockholm.main_project.stock.dto.StockHoldResponseDto;
+import com.stockholm.main_project.stock.dto.StockOrderResponseDto;
+import com.stockholm.main_project.stock.entity.StockOrder;
+import com.stockholm.main_project.stock.mapper.StockMapper;
+import com.stockholm.main_project.stock.service.StockHoldService;
+import com.stockholm.main_project.stock.service.StockOrderService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/stock")
+public class StockOrderController {
+
+ private final StockOrderService stockOrderService;
+ private final StockMapper stockMapper;
+ private final StockHoldService stockHoldService;
+
+ public StockOrderController(StockOrderService stockOrderService, StockMapper stockMapper, StockHoldService stockHoldService) {
+ this.stockOrderService = stockOrderService;
+ this.stockMapper = stockMapper;
+ this.stockHoldService = stockHoldService;
+ }
+
+ @Operation(summary = "주식을 매수 한다", description = "회사 주식을 매수하는 api", tags = { "Stock" })
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK",
+ content = @Content(schema = @Schema(implementation = StockOrderResponseDto.class))),
+ @ApiResponse(responseCode = "400", description = "BAD REQUEST"),
+ @ApiResponse(responseCode = "404", description = "NOT FOUND"),
+ @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR")
+ })
+ // 매수 api
+ @PostMapping("/buy")
+ public ResponseEntity buyStocks(@RequestParam(name = "companyId") long companyId,
+ @RequestParam(name = "price") long price,
+ @RequestParam(name = "stockCount") int stockCount,
+ @AuthenticationPrincipal Member member) {
+ StockOrder stockOrder = stockOrderService.buyStocks(member, companyId, price, stockCount);
+ StockOrderResponseDto stockOrderResponseDto = stockMapper.stockOrderToStockOrderResponseDto(stockOrder);
+
+ return new ResponseEntity<>(stockOrderResponseDto, HttpStatus.CREATED);
+ }
+
+ @Operation(summary = "주식을 매도 한다", description = "회사 주식을 매도하는 api", tags = { "Stock" })
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK",
+ content = @Content(schema = @Schema(implementation = StockOrderResponseDto.class))),
+ @ApiResponse(responseCode = "400", description = "BAD REQUEST"),
+ @ApiResponse(responseCode = "404", description = "NOT FOUND"),
+ @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR")
+ })
+ // 매도 api
+ @PostMapping("/sell")
+ public ResponseEntity sellStocks(@RequestParam(name = "companyId") long companyId,
+ @RequestParam(name = "price") long price,
+ @RequestParam(name = "stockCount") int stockCount,
+ @AuthenticationPrincipal Member member) {
+ StockOrder stockOrder = stockOrderService.sellStocks(member, companyId, price, stockCount);
+ StockOrderResponseDto stockOrderResponseDto = stockMapper.stockOrderToStockOrderResponseDto(stockOrder);
+
+ return new ResponseEntity<>(stockOrderResponseDto, HttpStatus.CREATED);
+ }
+
+ @Operation(summary = "멤버의 stockHold를 반환한다", description = "멤버의 stockHold를 반환하는 api", tags = { "Stock" })
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK",
+ content = @Content(array = @ArraySchema(schema = @Schema(implementation = StockHoldResponseDto.class)))),
+ @ApiResponse(responseCode = "400", description = "BAD REQUEST"),
+ @ApiResponse(responseCode = "404", description = "NOT FOUND"),
+ @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR")
+ })
+ // 보유 주식 정보들 반환하는 api
+ @GetMapping("/stockholds")
+ public ResponseEntity getStockHolds(@AuthenticationPrincipal Member member) {
+ List stockHoldResponseDtos = stockHoldService.findStockHolds(member.getMemberId());
+ //vList stockHoldResponseDtos = companyMapper.stockHoldToStockHoldResponseDto(stockHoldList);
+ stockHoldResponseDtos = stockHoldService.setPercentage(stockHoldResponseDtos);
+
+ return new ResponseEntity<>(stockHoldResponseDtos, HttpStatus.OK);
+ }
+
+ @Operation(summary = "멤버의 stockOrder를 반환한다", description = "멤버의 stockOrder를 반환하는 api", tags = { "Stock" })
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK",
+ content = @Content(array = @ArraySchema(schema = @Schema(implementation = StockOrderResponseDto.class)))),
+ @ApiResponse(responseCode = "400", description = "BAD REQUEST"),
+ @ApiResponse(responseCode = "404", description = "NOT FOUND"),
+ @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR")
+ })
+ // 멤버의 stockOrder를 반환하는 api
+ @GetMapping("/stockorders")
+ public ResponseEntity getStockOrders(@AuthenticationPrincipal Member member) {
+ List stockOrderResponseDtos = stockOrderService.getMemberStockOrders(member.getMemberId());
+
+ return new ResponseEntity<>(stockOrderResponseDtos, HttpStatus.OK);
+ }
+
+ @Operation(summary = "미체결된 매수, 매도 StockOrder 삭제", description = "미 체결된 매수, 매도 삭제하는 api", tags = { "Stock" })
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK"),
+ @ApiResponse(responseCode = "400", description = "BAD REQUEST"),
+ @ApiResponse(responseCode = "404", description = "NOT FOUND"),
+ @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR")
+ })
+ // 미 체결된 매수, 매도 삭제하는 api
+ @DeleteMapping("/stockorders")
+ public void deleteStockOrders(@AuthenticationPrincipal Member member,
+ @RequestParam("stockOrderId") long stockOrderId,
+ @RequestParam("stockCount") int stockCount) {
+ stockOrderService.deleteStockOrder(member, stockOrderId, stockCount);
+ }
+
+ @Operation(summary = "예약 매도 매수 기능 실행", description = "30분 마다 실행되는 예약 매도 매수기능 실행하는 api", tags = { "Stock" })
+ @ApiResponses({
+ @ApiResponse(responseCode = "200", description = "OK"),
+ @ApiResponse(responseCode = "400", description = "BAD REQUEST"),
+ @ApiResponse(responseCode = "404", description = "NOT FOUND"),
+ @ApiResponse(responseCode = "500", description = "INTERNAL SERVER ERROR")
+ })
+ @GetMapping("checkOrder")
+ public ResponseEntity checkOrder() {
+ stockOrderService.checkOrder();
+
+ return new ResponseEntity(HttpStatus.OK);
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/CompanyResponseDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/CompanyResponseDto.java
new file mode 100644
index 00000000..505d67ee
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/CompanyResponseDto.java
@@ -0,0 +1,21 @@
+package com.stockholm.main_project.stock.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class CompanyResponseDto {
+ @Schema(description = "회사 Id", defaultValue = "데이터 베이스 Id")
+ private long companyId;
+ @Schema(description = "회사의 고유 주식 코드", defaultValue = "6자리로 이루어진 회사의 고유 주식 코드 (ex. 005930)")
+ private String code;
+ @Schema(description = "회사 한글 이름", defaultValue = "회사 한글 이름 (ex. 삼성전자)")
+ private String korName;
+ @Schema(description = "주식 매수/매도 호가 가격, 수량", defaultValue = "as = 매도 호가, bi = 매수 호가")
+ private StockAsBiResponseDto stockAsBiResponseDto;
+ @Schema(description = "주식 정보", defaultValue = "prpr = 주식 현재가, vrss = 전일 대비 상승, 하락률, ctcr = 누적 거래량, vol = 누적 거래량, pbmn = 누적 거래 대금")
+ private StockInfResponseDto stockInfResponseDto;
+}
+
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StarResponseDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StarResponseDto.java
new file mode 100644
index 00000000..a56a832d
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StarResponseDto.java
@@ -0,0 +1,21 @@
+package com.stockholm.main_project.stock.dto;
+
+import com.stockholm.main_project.member.dto.MemberResponseDto;
+import com.stockholm.main_project.member.entity.Member;
+import com.stockholm.main_project.stock.entity.Company;
+import lombok.Getter;
+import lombok.Setter;
+import org.hibernate.annotations.OnDelete;
+import org.hibernate.annotations.OnDeleteAction;
+
+import javax.persistence.*;
+
+@Getter
+@Setter
+public class StarResponseDto {
+ private long starId;
+
+ private long memberId;
+
+ private CompanyResponseDto companyResponseDto;
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockAsBiResponseDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockAsBiResponseDto.java
new file mode 100644
index 00000000..bebc0048
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockAsBiResponseDto.java
@@ -0,0 +1,61 @@
+package com.stockholm.main_project.stock.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class StockAsBiResponseDto {
+
+ private long stockAsBiId;
+
+ private long companyId;
+
+ //매도 호가
+ private String askp1;
+ private String askp2;
+ private String askp3;
+ private String askp4;
+ private String askp5;
+ private String askp6;
+ private String askp7;
+ private String askp8;
+ private String askp9;
+ private String askp10;
+
+ //매도 잔량
+ private String askp_rsqn1;
+ private String askp_rsqn2;
+ private String askp_rsqn3;
+ private String askp_rsqn4;
+ private String askp_rsqn5;
+ private String askp_rsqn6;
+ private String askp_rsqn7;
+ private String askp_rsqn8;
+ private String askp_rsqn9;
+ private String askp_rsqn10;
+
+ //매수 호가
+ private String bidp1;
+ private String bidp2;
+ private String bidp3;
+ private String bidp4;
+ private String bidp5;
+ private String bidp6;
+ private String bidp7;
+ private String bidp8;
+ private String bidp9;
+ private String bidp10;
+
+ //매수 잔량
+ private String bidp_rsqn1;
+ private String bidp_rsqn2;
+ private String bidp_rsqn3;
+ private String bidp_rsqn4;
+ private String bidp_rsqn5;
+ private String bidp_rsqn6;
+ private String bidp_rsqn7;
+ private String bidp_rsqn8;
+ private String bidp_rsqn9;
+ private String bidp_rsqn10;
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockHoldResponseDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockHoldResponseDto.java
new file mode 100644
index 00000000..0c9050b0
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockHoldResponseDto.java
@@ -0,0 +1,26 @@
+package com.stockholm.main_project.stock.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class StockHoldResponseDto {
+ private long stockHoldId;
+
+ private long memberId;
+
+ private long companyId;
+
+ private String companyKorName;
+
+ private int stockCount;
+
+ private long totalPrice;
+
+ private double percentage;
+
+ private long stockReturn;
+
+ private long reserveSellStockCount;
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockInfResponseDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockInfResponseDto.java
new file mode 100644
index 00000000..f65927d3
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockInfResponseDto.java
@@ -0,0 +1,25 @@
+package com.stockholm.main_project.stock.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.persistence.*;
+
+@Getter
+@Setter
+public class StockInfResponseDto {
+ private long stockInfId;
+
+ private long companyId;
+
+ //주식 현재가
+ private String stck_prpr;
+ //전일 대비
+ private String prdy_vrss;
+ //전일 대비율
+ private String prdy_ctrt;
+ //누적 거래량
+ private String acml_vol;
+ //누적 거래대금
+ private String acml_tr_pbmn;
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockMinDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockMinDto.java
new file mode 100644
index 00000000..abd8e924
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockMinDto.java
@@ -0,0 +1,51 @@
+package com.stockholm.main_project.stock.dto;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import java.util.List;
+
+@Getter
+@Setter
+@NoArgsConstructor
+public class StockMinDto {
+ private StockMinOutput1 output1;
+ private List output2;
+
+ @Getter
+ @Setter
+ @NoArgsConstructor
+ public class StockMinOutput1 {
+ //한글 종목 명
+ private String hts_kor_isnm;
+ //주식 현재가
+ private String stck_prpr;
+ //전일 대비
+ private String prdy_vrss;
+ //전일 대비율
+ private String prdy_ctrt;
+ //누적 거래량
+ private String acml_vol;
+ //누적 거래대금
+ private String acml_tr_pbmn;
+ }
+
+ @Getter
+ @Setter
+ @NoArgsConstructor
+ public static class StockMinOutput2 {
+ //주식 체결 시간
+ private String stck_cntg_hour;
+ //주식 현재가
+ private String stck_prpr;
+ //주식 시가
+ private String stck_oprc;
+ //주식 최고가
+ private String stck_hgpr;
+ //주식 최저가
+ private String stck_lwpr;
+ //체결 거래량
+ private String cntg_vol;
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockMinResponseDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockMinResponseDto.java
new file mode 100644
index 00000000..5601dced
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockMinResponseDto.java
@@ -0,0 +1,36 @@
+package com.stockholm.main_project.stock.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+@Getter
+@Setter
+public class StockMinResponseDto {
+ @Schema(description = "주식 분봉 아이디", defaultValue = "1")
+ private long stockMinId;
+ @Schema(description = "주식 회사 아이디", defaultValue = "1")
+ private long companyId;
+ @Schema(description = "LocalDateTime 시간", defaultValue = "2023-09-04 10:01:00")
+ private LocalDateTime stockTradeTime;
+
+ //주식 체결 시간(문자열)
+ @Schema(description = "HHMMSS 시간", defaultValue = "100100")
+ private String stck_cntg_hour;
+ //주식 현재가
+ @Schema(description = "주식 현재가", defaultValue = "7600")
+ private String stck_prpr;
+ //주식 시가
+ @Schema(description = "주식 시가", defaultValue = "7400")
+ private String stck_oprc;
+ //주식 최고가
+ @Schema(description = "주식 최고가", defaultValue = "8200")
+ private String stck_hgpr;
+ //주식 최저가
+ @Schema(description = "주식 최저가", defaultValue = "7200")
+ private String stck_lwpr;
+ @Schema(description = "체결 거래량", defaultValue = "32000")
+ private String cntg_vol;
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockOrderResponseDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockOrderResponseDto.java
new file mode 100644
index 00000000..2b3722d6
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockOrderResponseDto.java
@@ -0,0 +1,27 @@
+package com.stockholm.main_project.stock.dto;
+
+import com.stockholm.main_project.stock.entity.StockOrder;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+@Getter
+@Setter
+public class StockOrderResponseDto {
+ private long stockOrderId;
+
+ private int stockCount;
+
+ private long memberId;
+
+ private long companyId;
+
+ private StockOrder.OrderTypes OrderTypes;
+
+ private com.stockholm.main_project.stock.entity.StockOrder.OrderStates OrderStates;
+
+ private long price;
+
+ private LocalDateTime modifiedAt;
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockasbiDataDto.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockasbiDataDto.java
new file mode 100644
index 00000000..da44f428
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/dto/StockasbiDataDto.java
@@ -0,0 +1,87 @@
+package com.stockholm.main_project.stock.dto;
+
+import lombok.*;
+
+
+@Data
+@NoArgsConstructor
+public class StockasbiDataDto {
+ private StockAsBiOutput1 output1;
+ //private StockAsBiOutput2 output2;
+
+ @Getter
+ @Setter
+ @NoArgsConstructor
+ public class StockAsBiOutput1 {
+
+ // 호가 접수 시간
+ private String aspr_acpt_hour;
+
+ //매도 호가
+ private String askp1;
+ private String askp2;
+ private String askp3;
+ private String askp4;
+ private String askp5;
+ private String askp6;
+ private String askp7;
+ private String askp8;
+ private String askp9;
+ private String askp10;
+
+ //매도 잔량
+ private String askp_rsqn1;
+ private String askp_rsqn2;
+ private String askp_rsqn3;
+ private String askp_rsqn4;
+ private String askp_rsqn5;
+ private String askp_rsqn6;
+ private String askp_rsqn7;
+ private String askp_rsqn8;
+ private String askp_rsqn9;
+ private String askp_rsqn10;
+
+ //매수 호가
+ private String bidp1;
+ private String bidp2;
+ private String bidp3;
+ private String bidp4;
+ private String bidp5;
+ private String bidp6;
+ private String bidp7;
+ private String bidp8;
+ private String bidp9;
+ private String bidp10;
+
+ //매수 잔량
+ private String bidp_rsqn1;
+ private String bidp_rsqn2;
+ private String bidp_rsqn3;
+ private String bidp_rsqn4;
+ private String bidp_rsqn5;
+ private String bidp_rsqn6;
+ private String bidp_rsqn7;
+ private String bidp_rsqn8;
+ private String bidp_rsqn9;
+ private String bidp_rsqn10;
+
+ }
+
+// @Getter
+// @Setter
+// @NoArgsConstructor
+// public class StockAsBiOutput2 {
+//
+// //주식 현재가
+// private String stck_prpr;
+// //주식 시가
+// private String stck_oprc;
+// //주식 최고가
+// private String stck_hgpr;
+// //주식 최저가
+// private String stck_lwpr;
+// //주식 기준가
+// private String stck_sdpr;
+// }
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/Company.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/Company.java
new file mode 100644
index 00000000..68d9a4d6
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/Company.java
@@ -0,0 +1,31 @@
+package com.stockholm.main_project.stock.entity;
+
+import com.stockholm.main_project.audit.Auditable;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.persistence.*;
+
+@Entity
+@Getter
+@Setter
+@NoArgsConstructor
+public class Company extends Auditable {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private long companyId;
+
+ @Column
+ private String code;
+
+ @Column
+ private String korName;
+
+ @OneToOne(mappedBy = "company", cascade = CascadeType.ALL)
+ private StockAsBi stockAsBi;
+
+ @OneToOne(mappedBy = "company", cascade = CascadeType.ALL)
+ private StockInf stockInf;
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/Star.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/Star.java
new file mode 100644
index 00000000..12ea5421
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/Star.java
@@ -0,0 +1,30 @@
+package com.stockholm.main_project.stock.entity;
+
+import com.stockholm.main_project.audit.Auditable;
+import com.stockholm.main_project.member.entity.Member;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.hibernate.annotations.OnDelete;
+import org.hibernate.annotations.OnDeleteAction;
+
+import javax.persistence.*;
+
+@Entity
+@Getter
+@Setter
+@NoArgsConstructor
+public class Star extends Auditable {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private long starId;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "MEMBER_ID")
+ @OnDelete(action = OnDeleteAction.CASCADE)
+ private Member member;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "COMPANY_ID")
+ private Company company;
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/StockAsBi.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/StockAsBi.java
new file mode 100644
index 00000000..7d50008d
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/StockAsBi.java
@@ -0,0 +1,110 @@
+package com.stockholm.main_project.stock.entity;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.persistence.*;
+
+@Entity
+@Getter
+@Setter
+@NoArgsConstructor
+public class StockAsBi {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private long stockAsBiId;
+
+ @OneToOne
+ @JoinColumn(name = "COMPANY_ID")
+ private Company company;
+
+ //매도 호가
+ @Column
+ private String askp1;
+ @Column
+ private String askp2;
+ @Column
+ private String askp3;
+ @Column
+ private String askp4;
+ @Column
+ private String askp5;
+ @Column
+ private String askp6;
+ @Column
+ private String askp7;
+ @Column
+ private String askp8;
+ @Column
+ private String askp9;
+ @Column
+ private String askp10;
+
+ //매도 잔량
+ @Column
+ private String askp_rsqn1;
+ @Column
+ private String askp_rsqn2;
+ @Column
+ private String askp_rsqn3;
+ @Column
+ private String askp_rsqn4;
+ @Column
+ private String askp_rsqn5;
+ @Column
+ private String askp_rsqn6;
+ @Column
+ private String askp_rsqn7;
+ @Column
+ private String askp_rsqn8;
+ @Column
+ private String askp_rsqn9;
+ @Column
+ private String askp_rsqn10;
+
+ //매수 호가
+ @Column
+ private String bidp1;
+ @Column
+ private String bidp2;
+ @Column
+ private String bidp3;
+ @Column
+ private String bidp4;
+ @Column
+ private String bidp5;
+ @Column
+ private String bidp6;
+ @Column
+ private String bidp7;
+ @Column
+ private String bidp8;
+ @Column
+ private String bidp9;
+ @Column
+ private String bidp10;
+
+ //매수 잔량
+ @Column
+ private String bidp_rsqn1;
+ @Column
+ private String bidp_rsqn2;
+ @Column
+ private String bidp_rsqn3;
+ @Column
+ private String bidp_rsqn4;
+ @Column
+ private String bidp_rsqn5;
+ @Column
+ private String bidp_rsqn6;
+ @Column
+ private String bidp_rsqn7;
+ @Column
+ private String bidp_rsqn8;
+ @Column
+ private String bidp_rsqn9;
+ @Column
+ private String bidp_rsqn10;
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/StockHold.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/StockHold.java
new file mode 100644
index 00000000..182dd559
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/StockHold.java
@@ -0,0 +1,40 @@
+package com.stockholm.main_project.stock.entity;
+
+import com.stockholm.main_project.audit.Auditable;
+import com.stockholm.main_project.member.entity.Member;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.hibernate.annotations.OnDelete;
+import org.hibernate.annotations.OnDeleteAction;
+
+import javax.persistence.*;
+
+@Entity
+@Getter
+@Setter
+@NoArgsConstructor
+public class StockHold extends Auditable {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private long stockHoldId;
+
+ @ManyToOne
+ @JoinColumn(name = "MEMBER_ID")
+ @OnDelete(action = OnDeleteAction.CASCADE)
+ private Member member;
+
+ @ManyToOne
+ @JoinColumn(name = "COMPANY_ID")
+ private Company company;
+
+ @Column
+ private int stockCount;
+
+ @Column
+ private int reserveStockCount;
+
+ @Column
+ private long price;
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/StockInf.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/StockInf.java
new file mode 100644
index 00000000..0de5ecbf
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/StockInf.java
@@ -0,0 +1,37 @@
+package com.stockholm.main_project.stock.entity;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.persistence.*;
+
+@Entity
+@Getter
+@Setter
+@NoArgsConstructor
+public class StockInf {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private long stockInfId;
+
+ @OneToOne
+ @JoinColumn(name = "COMPANY_ID")
+ private Company company;
+
+ //주식 현재가
+ @Column
+ private String stck_prpr;
+ //전일 대비
+ @Column
+ private String prdy_vrss;
+ //전일 대비율
+ @Column
+ private String prdy_ctrt;
+ //누적 거래량
+ @Column
+ private String acml_vol;
+ //누적 거래대금
+ @Column
+ private String acml_tr_pbmn;
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/StockMin.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/StockMin.java
new file mode 100644
index 00000000..ae2393bf
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/StockMin.java
@@ -0,0 +1,63 @@
+package com.stockholm.main_project.stock.entity;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+
+@Entity
+@Getter
+@Setter
+@NoArgsConstructor
+public class StockMin {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private long stockMinId;
+
+ @ManyToOne
+ @JoinColumn(name = "COMPANY_ID")
+ private Company company;
+
+ @Column
+ private LocalDateTime stockTradeTime;
+
+ //주식 체결 시간(문자열)
+ @Column
+ private String stck_cntg_hour;
+ //주식 현재가
+ @Column
+ private String stck_prpr;
+ //주식 시가
+ @Column
+ private String stck_oprc;
+ @Column
+ //주식 최고가
+ private String stck_hgpr;
+ @Column
+ //주식 최저가
+ private String stck_lwpr;
+ @Column
+ //체결 거래량
+ private String cntg_vol;
+ public void setTradeTime(LocalDateTime now) {
+
+ // 문자열에서 시, 분, 초 추출
+ int hour = Integer.parseInt(this.stck_cntg_hour.substring(0, 2));
+ int minute = Integer.parseInt(this.stck_cntg_hour.substring(2, 4));
+ int second = Integer.parseInt(this.stck_cntg_hour.substring(4, 6));
+
+ // LocalDateTime 객체 생성
+ LocalDateTime customDateTime = LocalDateTime.of(
+ now.getYear(),
+ now.getMonth(),
+ now.getDayOfMonth(),
+ hour,
+ minute,
+ second
+ );
+
+ this.stockTradeTime = customDateTime;
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/StockOrder.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/StockOrder.java
new file mode 100644
index 00000000..513355b6
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/StockOrder.java
@@ -0,0 +1,67 @@
+package com.stockholm.main_project.stock.entity;
+
+import com.stockholm.main_project.audit.Auditable;
+import com.stockholm.main_project.member.entity.Member;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.hibernate.annotations.OnDelete;
+import org.hibernate.annotations.OnDeleteAction;
+
+import javax.persistence.*;
+
+@Entity
+@Getter
+@Setter
+@NoArgsConstructor
+public class StockOrder extends Auditable{
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private long stockOrderId;
+
+ @Column
+ private int stockCount;
+
+ @ManyToOne()
+ @JoinColumn(name = "MEMBER_ID")
+ @OnDelete(action = OnDeleteAction.CASCADE)
+ private Member member;
+
+ @ManyToOne()
+ @JoinColumn(name = "COMPANY_ID")
+ private Company company;
+
+ @Enumerated(value = EnumType.STRING)
+ @Column(length = 20, nullable = false)
+ private OrderTypes orderTypes;
+
+ @Enumerated(value = EnumType.STRING)
+ @Column(length = 20, nullable = false)
+ private OrderStates orderStates;
+
+ @Column
+ private long price;
+
+ public enum OrderTypes {
+ SELL("매도"),
+ BUY("매수");
+ @Getter
+ @Setter
+ private String types;
+
+ OrderTypes(String types) { this.types = types; }
+ }
+
+ public enum OrderStates {
+ ORDER_COMPLETE("채결 완료"),
+ ORDER_WAIT("체결 대기");
+ @Getter
+ @Setter
+ private String states;
+
+ OrderStates(String status) {
+ this.states = status;
+ }
+ }
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/Token.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/Token.java
new file mode 100644
index 00000000..a95c042d
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/entity/Token.java
@@ -0,0 +1,25 @@
+package com.stockholm.main_project.stock.entity;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+import javax.persistence.*;
+import java.time.LocalDateTime;
+
+@Entity
+@Getter
+@Setter
+@NoArgsConstructor
+public class Token {
+ @Id
+ long tokenId;
+
+ @Column(length = 500)
+ private String token;
+
+ @Column
+ private LocalDateTime expired;
+
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/mapper/ApiMapper.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/mapper/ApiMapper.java
new file mode 100644
index 00000000..c243cd22
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/mapper/ApiMapper.java
@@ -0,0 +1,16 @@
+package com.stockholm.main_project.stock.mapper;
+
+import com.stockholm.main_project.stock.dto.StockMinDto;
+import com.stockholm.main_project.stock.dto.StockasbiDataDto;
+import com.stockholm.main_project.stock.entity.StockAsBi;
+import com.stockholm.main_project.stock.entity.StockInf;
+import com.stockholm.main_project.stock.entity.StockMin;
+import org.mapstruct.Mapper;
+
+@Mapper(componentModel = "spring")
+public interface ApiMapper {
+ StockAsBi stockAsBiOutput1ToStockAsBi(StockasbiDataDto.StockAsBiOutput1 stock);
+ StockMin stockMinOutput2ToStockMin(StockMinDto.StockMinOutput2 stock);
+ StockInf stockMinOutput1ToStockInf(StockMinDto.StockMinOutput1 stock);
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/mapper/StockMapper.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/mapper/StockMapper.java
new file mode 100644
index 00000000..804b37d7
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/mapper/StockMapper.java
@@ -0,0 +1,91 @@
+package com.stockholm.main_project.stock.mapper;
+
+import com.stockholm.main_project.stock.dto.*;
+import com.stockholm.main_project.stock.entity.*;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Mapper(componentModel = "spring")
+public interface StockMapper {
+ default List CompaniesToCompanyResponseDtos(List companyList) {
+
+ List companyResponseDtoList = new ArrayList<>();
+
+ for(Company company : companyList) {
+ CompanyResponseDto companyResponseDto = companyToCompanyResponseDto(company);
+ companyResponseDtoList.add(companyResponseDto);
+ }
+ return companyResponseDtoList;
+ }
+
+ default CompanyResponseDto companyToCompanyResponseDto(Company company) {
+ CompanyResponseDto companyResponseDto = new CompanyResponseDto();
+
+ companyResponseDto.setCompanyId(company.getCompanyId());
+ companyResponseDto.setCode(company.getCode());
+ companyResponseDto.setKorName(company.getKorName());
+ companyResponseDto.setStockAsBiResponseDto(stockAsBiToStockAsBiResponseDto(company.getStockAsBi()));
+ companyResponseDto.setStockInfResponseDto(stockInfToStockInfResponseDto(company.getStockInf()));
+
+ return companyResponseDto;
+ };
+
+ @Mapping(source = "company.companyId", target = "companyId")
+ StockInfResponseDto stockInfToStockInfResponseDto(StockInf stockInf);
+ @Mapping(source = "company.companyId", target = "companyId")
+ StockAsBiResponseDto stockAsBiToStockAsBiResponseDto(StockAsBi stockAsBi);
+ @Mapping(source = "company.companyId", target = "companyId")
+ StockMinResponseDto stockMinToStockMinResponseDto(StockMin stockMin);
+ @Mapping(source = "company.companyId", target = "companyId")
+ @Mapping(source = "member.memberId", target = "memberId")
+ StockOrderResponseDto stockOrderToStockOrderResponseDto(StockOrder stockOrder);
+ default List stockOrdersToStockOrderResponseDtos(List stockOrders) {
+ List stockOrderResponseDtos = new ArrayList<>();
+
+ for(StockOrder stockOrder : stockOrders) {
+ StockOrderResponseDto stockOrderResponseDto = stockOrderToStockOrderResponseDto(stockOrder);
+ stockOrderResponseDtos.add(stockOrderResponseDto);
+ }
+
+ return stockOrderResponseDtos;
+ }
+ default List stockHoldToStockHoldResponseDto(List stockHolds) {
+ List stockHoldResponseDtos = new ArrayList<>();
+
+ for(StockHold stockHold : stockHolds) {
+ StockHoldResponseDto stockHoldResponseDto = new StockHoldResponseDto();
+
+ stockHoldResponseDto.setStockHoldId(stockHold.getStockHoldId());
+ stockHoldResponseDto.setCompanyId(stockHold.getCompany().getCompanyId());
+ stockHoldResponseDto.setCompanyKorName(stockHold.getCompany().getKorName());
+ stockHoldResponseDto.setMemberId(stockHold.getMember().getMemberId());
+ stockHoldResponseDto.setStockCount(stockHold.getStockCount());
+ stockHoldResponseDto.setReserveSellStockCount(stockHold.getReserveStockCount());
+ stockHoldResponseDto.setTotalPrice(stockHold.getPrice());
+ stockHoldResponseDto.setPercentage(0D);
+ stockHoldResponseDto.setStockReturn(0);
+
+
+ stockHoldResponseDtos.add(stockHoldResponseDto);
+ }
+ return stockHoldResponseDtos;
+ }
+
+ default List starsToStarResponseDtos(List stars) {
+ List starResponseDtos = new ArrayList<>();
+
+ for(Star star : stars) {
+ StarResponseDto starResponseDto = new StarResponseDto();
+
+ starResponseDto.setStarId(star.getStarId());
+ starResponseDto.setMemberId(star.getMember().getMemberId());
+ starResponseDto.setCompanyResponseDto(companyToCompanyResponseDto(star.getCompany()));
+
+ starResponseDtos.add(starResponseDto);
+ }
+ return starResponseDtos;
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/CompanyRepository.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/CompanyRepository.java
new file mode 100644
index 00000000..ace6e976
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/CompanyRepository.java
@@ -0,0 +1,19 @@
+package com.stockholm.main_project.stock.repository;
+
+import com.stockholm.main_project.stock.entity.Company;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+import java.util.List;
+
+public interface CompanyRepository extends JpaRepository {
+
+ @Query("SELECT c FROM Company c JOIN FETCH c.stockAsBi JOIN FETCH c.stockInf WHERE c.code = :code")
+ Company findByCode(@Param("code") String code);
+ @Query("SELECT c FROM Company c JOIN FETCH c.stockAsBi JOIN FETCH c.stockInf WHERE c.companyId = :companyId")
+ Company findByCompanyId(@Param("companyId") long companyId);
+ @Query("SELECT c from Company c join fetch c.stockAsBi JOIN FETCH c.stockInf")
+ List findAll();
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/StarRepository.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/StarRepository.java
new file mode 100644
index 00000000..5d1c8bac
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/StarRepository.java
@@ -0,0 +1,14 @@
+package com.stockholm.main_project.stock.repository;
+
+import com.stockholm.main_project.stock.entity.Star;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+
+import java.util.List;
+
+public interface StarRepository extends JpaRepository {
+ @Query("SELECT s FROM Star s JOIN FETCH s.member JOIN FETCH s.company WHERE s.member.memberId = :memberId")
+ List findAllByMember_MemberId(long memberId);
+
+ Star findByMember_MemberIdAndCompanyCompanyId(long memberId, long companyId);
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/StockAsBiRepository.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/StockAsBiRepository.java
new file mode 100644
index 00000000..17a53a0a
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/StockAsBiRepository.java
@@ -0,0 +1,8 @@
+package com.stockholm.main_project.stock.repository;
+
+import com.stockholm.main_project.stock.entity.StockAsBi;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface StockAsBiRepository extends JpaRepository {
+}
+
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/StockHoldRepository.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/StockHoldRepository.java
new file mode 100644
index 00000000..decf8d92
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/StockHoldRepository.java
@@ -0,0 +1,12 @@
+package com.stockholm.main_project.stock.repository;
+
+import com.stockholm.main_project.stock.entity.StockHold;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+public interface StockHoldRepository extends JpaRepository {
+ StockHold findByCompanyCompanyIdAndMemberMemberId(long companyId, long memberId);
+ List findAllByMember_MemberId(long memberId);
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/StockMinRepository.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/StockMinRepository.java
new file mode 100644
index 00000000..f435ab62
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/StockMinRepository.java
@@ -0,0 +1,13 @@
+package com.stockholm.main_project.stock.repository;
+
+import com.stockholm.main_project.stock.entity.StockMin;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+
+import java.util.List;
+
+public interface StockMinRepository extends JpaRepository {
+ List findAllByCompanyCompanyId(long companyId);
+ @Query(value = "SELECT * FROM stock_min s WHERE s.company_id = ?1 ORDER BY s.stock_min_id DESC LIMIT 420", nativeQuery = true)
+ List findTop420ByCompanyIdOrderByStockMinIdDesc(long companyId);
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/StockOrderRepository.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/StockOrderRepository.java
new file mode 100644
index 00000000..b4a17578
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/StockOrderRepository.java
@@ -0,0 +1,19 @@
+package com.stockholm.main_project.stock.repository;
+
+import com.stockholm.main_project.stock.entity.StockOrder;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+public interface StockOrderRepository extends JpaRepository {
+ List findAllByCompanyCompanyIdAndOrderStates(long company_companyId, StockOrder.OrderStates orderStates);
+ List findAllByMember_MemberId(long memberId);
+ List findAllByMember_MemberIdOrderByModifiedAtDesc(long memberId);
+ List findAllByMember_MemberIdAndCompany_CompanyIdAndOrderStatesAndOrderTypes(long memberId, long companyId, StockOrder.OrderStates orderStates, StockOrder.OrderTypes orderTypes);
+
+ List findByMemberMemberId(long memberId);
+ // MEMBER_ID로 주식 주문을 모두 삭제하는 JPQL 쿼리
+// @Modifying
+// @Query("DELETE FROM StockOrder so WHERE so.memberId = :memberId")
+// void deleteAllByMemberId(@Param("memberId") Long memberId);
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/TokenRepository.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/TokenRepository.java
new file mode 100644
index 00000000..9f071ef6
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/repository/TokenRepository.java
@@ -0,0 +1,7 @@
+package com.stockholm.main_project.stock.repository;
+
+import com.stockholm.main_project.stock.entity.Token;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface TokenRepository extends JpaRepository {
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/ApiCallService.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/ApiCallService.java
new file mode 100644
index 00000000..5e165814
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/ApiCallService.java
@@ -0,0 +1,145 @@
+package com.stockholm.main_project.stock.service;
+
+import com.stockholm.main_project.stock.dto.StockasbiDataDto;
+import com.stockholm.main_project.stock.dto.StockMinDto;
+import com.stockholm.main_project.stock.repository.CompanyRepository;
+import com.stockholm.main_project.utils.Time;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.stereotype.Service;
+import org.springframework.http.*;
+import org.springframework.web.client.RestTemplate;
+
+import javax.transaction.Transactional;
+import java.time.LocalDateTime;
+
+
+@Service
+@Transactional
+@Slf4j
+public class ApiCallService {
+ @Getter
+ @Value("${token.app-key}")
+ private String APP_KEY;
+
+ @Getter
+ @Value("${token.app-secret}")
+ private String APP_SECRET;
+
+ @Getter
+ @Value("${stock-url.token}")
+ private String TOKEN_URL;
+
+ @Getter
+ @Value("${stock-url.stockasbi}")
+ private String STOCKASBI_URL;
+
+ @Getter
+ @Value("${stock-url.stockhour}")
+ private String STOCKHOUR_URL;
+
+ @Getter
+ @Value("${stock-url.kospi}")
+ private String KOSPI_URL;
+
+
+ private final String FID_ETC_CLS_CODE = "";
+ private final String FID_COND_MRKT_DIV_CODE = "J";
+ // private final String FID_INPUT_HOUR_1 = "153000";
+ private final String FID_PW_DATA_INCU_YN = "Y";
+
+ private RestTemplate restTemplate = new RestTemplate();
+
+ public ApiCallService(TokenService tokenService, CompanyRepository companyRepository) {
+ this.tokenService = tokenService;
+ this.companyRepository = companyRepository;
+ }
+
+ private final TokenService tokenService;
+
+ private final CompanyRepository companyRepository;
+
+ public StockasbiDataDto getStockasbiDataFromApi(String stockCode){
+ String token = tokenService.getAccessToken();
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.add("Authorization", "Bearer " + token);
+ headers.add("appkey", APP_KEY);
+ headers.add("appsecret", APP_SECRET);
+ headers.add("tr_id", "FHKST01010200");
+
+ String uri = STOCKASBI_URL + "?FID_COND_MRKT_DIV_CODE=J&FID_INPUT_ISCD=" + stockCode;
+
+ HttpEntity entity = new HttpEntity<>("parameters", headers);
+
+ ResponseEntity response = restTemplate.exchange(uri, HttpMethod.GET, entity, new ParameterizedTypeReference() {});
+
+ if (response.getStatusCode().is2xxSuccessful()) {
+ StockasbiDataDto stockasbiDataDto = response.getBody();
+ return stockasbiDataDto;
+ } else {
+ log.info("error");
+ return null;
+ }
+
+ }
+
+ public StockMinDto getStockMinDataFromApi(String stockCode, String strHour) {
+ String token = tokenService.getAccessToken();
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.add("Authorization", "Bearer " + token);
+ headers.add("appkey", APP_KEY);
+ headers.add("appsecret", APP_SECRET);
+ headers.add("tr_id", "FHKST03010200");
+
+ String uri = STOCKHOUR_URL + "?FID_COND_MRKT_DIV_CODE=" + FID_COND_MRKT_DIV_CODE + "&FID_INPUT_ISCD=" + stockCode + "&FID_ETC_CLS_CODE=" + FID_ETC_CLS_CODE
+ + "&FID_INPUT_HOUR_1=" + strHour + "&FID_PW_DATA_INCU_YN=" + FID_PW_DATA_INCU_YN;
+
+ HttpEntity entity = new HttpEntity<>("parameters", headers);
+
+ ResponseEntity response = restTemplate.exchange(uri, HttpMethod.GET, entity, new ParameterizedTypeReference() {});
+
+ if (response.getStatusCode().is2xxSuccessful()) {
+ StockMinDto stockMinDto = response.getBody();
+ return stockMinDto;
+
+ } else {
+ log.info("error");
+ return null;
+ }
+
+ }
+
+ public String getKospiMonthFromApi(){
+ String token = tokenService.getAccessToken();
+
+ LocalDateTime localDateTime = LocalDateTime.now();
+
+ String strMonth = Time.strMonth(localDateTime);
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.add("Authorization", "Bearer " + token);
+ headers.add("appkey", APP_KEY);
+ headers.add("appsecret", APP_SECRET);
+ headers.add("tr_id", "FHKUP03500100");
+
+ String uri = KOSPI_URL + "?FID_COND_MRKT_DIV_CODE=U&FID_INPUT_ISCD=" + "0001" + "&FID_INPUT_DATE_1=" + "20230101"
+ +"&FID_INPUT_DATE_2=" + strMonth + "&FID_PERIOD_DIV_CODE=" + "M";
+
+ HttpEntity entity = new HttpEntity<>("parameters", headers);
+
+ ResponseEntity response = restTemplate.exchange(uri, HttpMethod.GET, entity, String.class);
+
+ if (response.getStatusCode().is2xxSuccessful()) {
+ return response.getBody();
+ } else {
+ log.info("error");
+ return null;
+ }
+
+ }
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/CompanyService.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/CompanyService.java
new file mode 100644
index 00000000..f506e914
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/CompanyService.java
@@ -0,0 +1,85 @@
+package com.stockholm.main_project.stock.service;
+
+import com.stockholm.main_project.stock.dto.StockasbiDataDto;
+import com.stockholm.main_project.stock.entity.Company;
+import com.stockholm.main_project.stock.entity.StockAsBi;
+import com.stockholm.main_project.stock.mapper.ApiMapper;
+import com.stockholm.main_project.stock.repository.CompanyRepository;
+import org.springframework.stereotype.Service;
+
+import javax.transaction.Transactional;
+import java.util.List;
+
+@Service
+@Transactional
+public class CompanyService {
+ private final CompanyRepository companyRepository;
+ private final ApiCallService apiCallService;
+ private final ApiMapper apiMapper;
+
+ public CompanyService(CompanyRepository companyRepository, ApiCallService apiCallService, ApiMapper apiMapper) {
+ this.companyRepository = companyRepository;
+ this.apiCallService = apiCallService;
+ this.apiMapper = apiMapper;
+ }
+
+ // 특정 회사 리턴
+ public Company findCompanyByCode(String stockCode) {
+ Company company = companyRepository.findByCode(stockCode);
+ return company;
+ }
+
+ public Company findCompanyById(long companyId) {
+ Company company = companyRepository.findByCompanyId(companyId);
+ return company;
+ }
+
+ // 모든 회사 리턴
+ public List findCompanies() {
+ List companies = companyRepository.findAll();
+
+ return companies;
+ }
+
+
+ // 특정 회사 저장
+ public Company saveCompany(Company company) {
+ return companyRepository.save(company);
+ }
+
+ // 모든 회사 저장
+ public List saveCompanies(List companies) {
+ return companyRepository.saveAll(companies);
+ }
+
+ public void fillCompany() {
+ Company company = new Company();
+
+ }
+
+ public void fillCompaines() throws InterruptedException {
+ List korName = List.of("삼성전자", "POSCO홀딩스", "셀트리온", "에코프로", "에코프로비엠", "디와이", "쿠쿠홀딩스", "카카오뱅크", "한세엠케이", "KG케미칼", "LG화학", "현대차", "LG전자", "기아");
+ List code = List.of("005930", "005490", "068270", "086520", "247540", "013570", "192400", "323410", "069640", "001390", "051910", "005380", "066570", "000270");
+
+ for(int i = 0; i < code.size(); i++) {
+ Company company = new Company();
+ company.setCode(code.get(i));
+ company.setKorName(korName.get(i));
+ company.setStockAsBi(new StockAsBi());
+
+ StockasbiDataDto stockasbiDataDto = apiCallService.getStockasbiDataFromApi(company.getCode());
+ // mapper로 정리 된 값 받기
+ StockAsBi stockAsBi = apiMapper.stockAsBiOutput1ToStockAsBi(stockasbiDataDto.getOutput1());
+
+ company.setStockAsBi(stockAsBi);
+ stockAsBi.setCompany(company);
+
+ Thread.sleep(500);
+
+ companyRepository.save(company);
+ }
+ }
+
+}
+
+
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/StarService.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/StarService.java
new file mode 100644
index 00000000..366dec53
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/StarService.java
@@ -0,0 +1,47 @@
+package com.stockholm.main_project.stock.service;
+
+import com.stockholm.main_project.member.entity.Member;
+import com.stockholm.main_project.stock.dto.StarResponseDto;
+import com.stockholm.main_project.stock.entity.Star;
+import com.stockholm.main_project.stock.mapper.StockMapper;
+import com.stockholm.main_project.stock.repository.StarRepository;
+import org.springframework.stereotype.Service;
+
+import javax.transaction.Transactional;
+import java.util.List;
+
+@Service
+@Transactional
+public class StarService {
+ private final StarRepository starRepository;
+ private final CompanyService companyService;
+ private final StockMapper stockMapper;
+
+ public StarService(StarRepository starRepository, CompanyService companyService, StockMapper stockMapper) {
+ this.starRepository = starRepository;
+ this.companyService = companyService;
+ this.stockMapper = stockMapper;
+ }
+
+ public void saveStar(Member member, long companyId) {
+ Star star = new Star();
+
+ star.setMember(member);
+ star.setCompany(companyService.findCompanyById(companyId));
+
+ starRepository.save(star);
+ }
+
+ public void deleteStar(Member member, long companyId) {
+ Star star = starRepository.findByMember_MemberIdAndCompanyCompanyId(member.getMemberId(), companyId);
+
+ starRepository.delete(star);
+ }
+
+ public List getStarResponseDtoList(Member member) {
+ List stars = starRepository.findAllByMember_MemberId(member.getMemberId());
+ List starResponseDtos = stockMapper.starsToStarResponseDtos(stars);
+
+ return starResponseDtos;
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/StockAsBiService.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/StockAsBiService.java
new file mode 100644
index 00000000..6c9e9acf
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/StockAsBiService.java
@@ -0,0 +1,70 @@
+package com.stockholm.main_project.stock.service;
+
+import com.stockholm.main_project.exception.BusinessLogicException;
+import com.stockholm.main_project.exception.ExceptionCode;
+import com.stockholm.main_project.stock.dto.StockasbiDataDto;
+import com.stockholm.main_project.stock.entity.Company;
+import com.stockholm.main_project.stock.entity.StockAsBi;
+import com.stockholm.main_project.stock.mapper.ApiMapper;
+import com.stockholm.main_project.stock.repository.StockAsBiRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.transaction.Transactional;
+import java.util.List;
+import java.util.Optional;
+
+@Service
+@Transactional
+@Slf4j
+public class StockAsBiService {
+
+ private final StockAsBiRepository stockAsBiRepository;
+ private final ApiCallService apiCallService;
+ private final ApiMapper apiMapper;
+ private final CompanyService companyService;
+
+ public StockAsBiService(StockAsBiRepository stockAsBiRepository, ApiCallService apiCallService, ApiMapper apiMapper, CompanyService companyService) {
+ this.stockAsBiRepository = stockAsBiRepository;
+ this.apiCallService = apiCallService;
+ this.apiMapper = apiMapper;
+ this.companyService = companyService;
+ }
+
+ public StockAsBi saveStockAsBi(StockAsBi stockAsBi) {
+ return stockAsBiRepository.save(stockAsBi);
+ }
+
+ public void updateStockAsBi() throws InterruptedException {
+ List companyList = companyService.findCompanies();
+
+ for(int i = 0; i < companyList.size(); i++) {
+ // 주식 코드로 회사 불러오기
+ Company company = companyService.findCompanyByCode(companyList.get(i).getCode());
+ // api 호출하기
+ StockasbiDataDto stockasbiDataDto = apiCallService.getStockasbiDataFromApi(company.getCode());
+ // mapper로 정리 된 값 받기
+ StockAsBi stockAsBi = apiMapper.stockAsBiOutput1ToStockAsBi(stockasbiDataDto.getOutput1());
+
+ // 회사 등록
+ stockAsBi.setCompany(company);
+ // 호가 컬럼을 새로운 호가 컬럼으로 변경한다
+ StockAsBi oldStockAsBi = company.getStockAsBi();
+ stockAsBi.setStockAsBiId(oldStockAsBi.getStockAsBiId());
+ company.setStockAsBi(stockAsBi);
+
+ // 저장한다
+ companyService.saveCompany(company);
+
+ Thread.sleep(500);
+ }
+ }
+
+ public StockAsBi getStockAsBi(long companyId) {
+ Optional stock = stockAsBiRepository.findById(companyId);
+ stock.orElseThrow(() -> new BusinessLogicException(ExceptionCode.STOCKASBI_NOT_FOUND));
+
+ return stock.get();
+ }
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/StockHoldService.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/StockHoldService.java
new file mode 100644
index 00000000..5513ce61
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/StockHoldService.java
@@ -0,0 +1,115 @@
+package com.stockholm.main_project.stock.service;
+
+import com.stockholm.main_project.exception.BusinessLogicException;
+import com.stockholm.main_project.exception.ExceptionCode;
+import com.stockholm.main_project.member.repository.MemberRepository;
+import com.stockholm.main_project.stock.dto.StockHoldResponseDto;
+import com.stockholm.main_project.stock.entity.Company;
+import com.stockholm.main_project.stock.entity.StockHold;
+import com.stockholm.main_project.stock.entity.StockOrder;
+import com.stockholm.main_project.stock.mapper.StockMapper;
+import com.stockholm.main_project.stock.repository.CompanyRepository;
+import com.stockholm.main_project.stock.repository.StockHoldRepository;
+import com.stockholm.main_project.stock.repository.StockOrderRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.transaction.Transactional;
+import java.util.List;
+
+@Service
+@Transactional
+@Slf4j
+public class StockHoldService {
+ private final StockHoldRepository stockHoldRepository;
+ private final MemberRepository memberRepository;
+ private final CompanyRepository companyRepository;
+ private final StockOrderRepository stockOrderRepository;
+ private final StockMapper stockMapper;
+
+
+ public StockHoldService(StockHoldRepository stockHoldRepository, MemberRepository memberRepository, CompanyRepository companyRepository, StockOrderRepository stockOrderRepository, StockMapper stockMapper) {
+ this.stockHoldRepository = stockHoldRepository;
+ this.memberRepository = memberRepository;
+ this.companyRepository = companyRepository;
+ this.stockOrderRepository = stockOrderRepository;
+ this.stockMapper = stockMapper;
+ }
+
+ // 없으면 새로운 스톡 홀드를 생성해서 반환해준다
+ public StockHold checkStockHold(long companyId, long memberId) {
+ StockHold stockHold = stockHoldRepository.findByCompanyCompanyIdAndMemberMemberId(companyId, memberId);
+ if(stockHold == null) {
+ StockHold newStockHold = new StockHold();
+ newStockHold.setMember(memberRepository.findById(memberId).get());
+ newStockHold.setCompany(companyRepository.findById(companyId).get());
+
+ return newStockHold;
+ }
+ else
+ return stockHold;
+ }
+
+ // 보유중인 회사 주식 정보 반환
+ public StockHold findStockHold(long companyId, long memberId) {
+ StockHold stockHold = stockHoldRepository.findByCompanyCompanyIdAndMemberMemberId(companyId, memberId);
+ if(stockHold == null)
+ throw new BusinessLogicException(ExceptionCode.STOCKHOLD_NOT_FOUND);
+ else
+ return stockHold;
+ }
+
+ public List findStockHolds(long memberId) {
+ List stockHoldList = stockHoldRepository.findAllByMember_MemberId(memberId);
+ List stockHoldResponseDtos = stockMapper.stockHoldToStockHoldResponseDto(stockHoldList);
+// for(StockHoldResponseDto stockHold : stockHoldResponseDtos) {
+//
+// List stockOrders = stockOrderRepository
+// .findAllByMember_MemberIdAndCompany_CompanyIdAndOrderStatesAndOrderTypes(
+// stockHold.getMemberId(),
+// stockHold.getCompanyId(),
+// StockOrder.OrderStates.ORDER_WAIT,
+// StockOrder.OrderTypes.SELL
+// );
+// int orderWaitCount = stockOrders.stream().mapToInt(StockOrder::getStockCount).sum();
+// stockHold.setReserveSellStockCount(orderWaitCount);
+// }
+
+ return stockHoldResponseDtos;
+ }
+
+ //수익률 계산하는 로직
+ public List setPercentage(List stockHoldResponseDtos) {
+ for(StockHoldResponseDto stockHoldResponseDto : stockHoldResponseDtos) {
+ // 이름으로 회사를 불러온다
+ Company company = companyRepository.findByCompanyId(stockHoldResponseDto.getCompanyId());
+ // 주식 현재가를 불러온다
+ String nowPrice = company.getStockInf().getStck_prpr();
+ // 주식 수익 = 전체 주식 가치 - 전체 투자 금액
+ double totalRevenue =
+ Double.valueOf(nowPrice)
+ * (stockHoldResponseDto.getStockCount()+stockHoldResponseDto.getReserveSellStockCount())
+ - stockHoldResponseDto.getTotalPrice();
+ // 주식 수익률(%) = (주식 수익 / 전체 투자 금액) × 100
+ double percentage = (totalRevenue / (double)stockHoldResponseDto.getTotalPrice()) * 100;
+
+ stockHoldResponseDto.setPercentage(percentage);
+ stockHoldResponseDto.setStockReturn((long) totalRevenue);
+ }
+ return stockHoldResponseDtos;
+ }
+
+ //보유 주식 전부 삭제하는 로직
+ public void deleteStockHolds(long memberId) {
+ List stockHolds = getMemberStockHolds(memberId);
+
+ stockHoldRepository.deleteAll(stockHolds);
+ }
+
+ public List getMemberStockHolds(long memberId) {
+ List stockHolds = stockHoldRepository.findAllByMember_MemberId(memberId);
+
+ return stockHolds;
+ }
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/StockMinService.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/StockMinService.java
new file mode 100644
index 00000000..f6f7f25e
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/StockMinService.java
@@ -0,0 +1,94 @@
+package com.stockholm.main_project.stock.service;
+
+import com.stockholm.main_project.stock.dto.StockMinDto;
+import com.stockholm.main_project.stock.dto.StockMinResponseDto;
+import com.stockholm.main_project.stock.entity.Company;
+import com.stockholm.main_project.stock.entity.StockInf;
+import com.stockholm.main_project.stock.entity.StockMin;
+import com.stockholm.main_project.stock.mapper.StockMapper;
+import com.stockholm.main_project.stock.mapper.ApiMapper;
+import com.stockholm.main_project.stock.repository.StockMinRepository;
+import com.stockholm.main_project.utils.Time;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.transaction.Transactional;
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+@Transactional
+@Slf4j
+public class StockMinService {
+
+ private final CompanyService companyService;
+ private final ApiCallService apiCallService;
+ private final ApiMapper apiMapper;
+ private final StockMinRepository stockMinRepository;
+ private final StockMapper stockMapper;
+
+ public StockMinService(CompanyService companyService, ApiCallService apiCallService, ApiMapper apiMapper, StockMinRepository stockMinRepository, StockMapper stockMapper) {
+ this.companyService = companyService;
+ this.apiCallService = apiCallService;
+ this.apiMapper = apiMapper;
+ this.stockMinRepository = stockMinRepository;
+ this.stockMapper = stockMapper;
+ }
+
+ public void updateStockMin() throws InterruptedException {
+ List companyList = companyService.findCompanies();
+ LocalDateTime now = LocalDateTime.now();
+ String strHour = Time.strHour(now);
+
+
+ for(int i = 0; i < companyList.size(); i++) {
+ // 주식 코드로 회사 불러오기
+ Company company = companyService.findCompanyByCode(companyList.get(i).getCode());
+ // 분봉 api 호출하기
+ StockMinDto stockMinDto = apiCallService.getStockMinDataFromApi(company.getCode(), strHour);
+ // mapper로 정리 된 값 받기
+ List stockMinList = stockMinDto.getOutput2().stream()
+ .map(stockMinOutput2 -> {
+ StockMin stockMin = apiMapper.stockMinOutput2ToStockMin(stockMinOutput2);
+ stockMin.setCompany(company);
+ stockMin.setTradeTime(now);
+ return stockMin;
+ }).collect(Collectors.toList());
+ // 빠른 시간 순으로 정렬
+ Collections.sort(stockMinList, Comparator.comparing(StockMin::getStockTradeTime));
+ // 회사 정보 저장
+ StockInf stockInf = apiMapper.stockMinOutput1ToStockInf(stockMinDto.getOutput1());
+ stockInf.setCompany(company);
+ StockInf oldStockInf = company.getStockInf();
+ stockInf.setStockInfId(oldStockInf.getStockInfId());
+ company.setStockInf(stockInf);
+
+ // 저장한다
+ stockMinRepository.saveAll(stockMinList);
+ companyService.saveCompany(company);
+
+
+ Thread.sleep(500);
+ }
+ }
+
+ public List getChart(long companyId) {
+ List stockMinList = stockMinRepository.findAllByCompanyCompanyId(companyId);
+
+ return stockMinList;
+ }
+
+ public List getRecent420StockMin(long companyId) {
+ List stockMinList = stockMinRepository.findTop420ByCompanyIdOrderByStockMinIdDesc(companyId);
+
+ List stockMinResponseDtos = stockMinList.stream()
+ .map(stockMin -> stockMapper.stockMinToStockMinResponseDto(stockMin)).collect(Collectors.toList());
+ Collections.reverse(stockMinResponseDtos);
+ return stockMinResponseDtos;
+ }
+
+
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/StockOrderService.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/StockOrderService.java
new file mode 100644
index 00000000..9b8e9c72
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/StockOrderService.java
@@ -0,0 +1,455 @@
+package com.stockholm.main_project.stock.service;
+
+import java.lang.management.ManagementFactory;
+
+import com.stockholm.main_project.cash.entity.Cash;
+import com.stockholm.main_project.cash.service.CashService;
+import com.stockholm.main_project.exception.BusinessLogicException;
+import com.stockholm.main_project.exception.ExceptionCode;
+import com.stockholm.main_project.member.entity.Member;
+import com.stockholm.main_project.member.repository.MemberRepository;
+import com.stockholm.main_project.stock.controller.LongPollingController;
+import com.stockholm.main_project.stock.dto.StockOrderResponseDto;
+import com.stockholm.main_project.stock.entity.Company;
+import com.stockholm.main_project.stock.entity.StockAsBi;
+import com.stockholm.main_project.stock.entity.StockHold;
+import com.stockholm.main_project.stock.entity.StockOrder;
+import com.stockholm.main_project.stock.mapper.StockMapper;
+import com.stockholm.main_project.stock.repository.StockHoldRepository;
+import com.stockholm.main_project.stock.repository.StockOrderRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.messaging.simp.SimpMessagingTemplate;
+import org.springframework.stereotype.Service;
+
+import javax.transaction.Transactional;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+@Transactional
+@Slf4j
+public class StockOrderService {
+ private final StockAsBiService stockAsBiService;
+ private final CompanyService companyService;
+ private final StockOrderRepository stockOrderRepository;
+ private final MemberRepository memberRepository;
+ private final StockHoldService stockHoldService;
+ private final StockHoldRepository stockHoldRepository;
+ private final CashService cashService;
+ private final StockMapper stockMapper;
+ private final LongPollingController longPollingController;
+ private final SimpMessagingTemplate messagingTemplate;
+
+ public StockOrderService(StockAsBiService stockAsBiService, CompanyService companyService, StockOrderRepository stockOrderRepository, MemberRepository memberRepository, StockHoldService stockHoldService, StockHoldRepository stockHoldRepository, CashService cashService, StockMapper stockMapper, LongPollingController longPollingController, SimpMessagingTemplate messagingTemplate) {
+ this.stockAsBiService = stockAsBiService;
+ this.companyService = companyService;
+ this.stockOrderRepository = stockOrderRepository;
+ this.memberRepository = memberRepository;
+ this.stockHoldService = stockHoldService;
+ this.stockHoldRepository = stockHoldRepository;
+ this.cashService = cashService;
+ this.stockMapper = stockMapper;
+ this.longPollingController = longPollingController;
+ this.messagingTemplate = messagingTemplate;
+ }
+
+ // 멤버, 회사 id, 가격
+ public StockOrder buyStocks(Member member, long companyId, long price, int stockCount) {
+ //회원 캐쉬 잔량 비교
+ cashService.checkCash(price * stockCount, member); // -> 부족할 시 예외 처리
+ //호가 불러오기
+ StockAsBi stockAsBi = stockAsBiService.getStockAsBi(companyId);
+ // 예약 구매인지 바로 구매인지 판별
+ return buyDiscrimination(member, price, stockAsBi, stockCount, companyId);
+ }
+
+ private StockOrder buyDiscrimination(Member member, long price, StockAsBi stockAsBi, int stockCount, long companyId) {
+ // 매도 호가와 가격이 같고, 잔량이 남아 있을 경우
+ if(Long.parseLong(stockAsBi.getAskp1()) == price && Integer.parseInt(stockAsBi.getAskp_rsqn1()) > stockCount)
+ return buyStock(member, price, stockCount, companyId); // 구매 로직
+ else if(Long.parseLong(stockAsBi.getAskp2()) == price && Integer.parseInt(stockAsBi.getAskp_rsqn2()) > stockCount)
+ return buyStock(member, price, stockCount, companyId); // 구매 로직
+ else if(Long.parseLong(stockAsBi.getAskp3()) == price && Integer.parseInt(stockAsBi.getAskp_rsqn3()) > stockCount)
+ return buyStock(member, price, stockCount, companyId); // 구매 로직
+ else if(Long.parseLong(stockAsBi.getAskp4()) == price && Integer.parseInt(stockAsBi.getAskp_rsqn4()) > stockCount)
+ return buyStock(member, price, stockCount, companyId); // 구매 로직
+ else if(Long.parseLong(stockAsBi.getAskp5()) == price && Integer.parseInt(stockAsBi.getAskp_rsqn5()) > stockCount)
+ return buyStock(member, price, stockCount, companyId); // 구매 로직
+ else if(Long.parseLong(stockAsBi.getAskp6()) == price && Integer.parseInt(stockAsBi.getAskp_rsqn6()) > stockCount)
+ return buyStock(member, price, stockCount, companyId); // 구매 로직
+ else if(Long.parseLong(stockAsBi.getAskp7()) == price && Integer.parseInt(stockAsBi.getAskp_rsqn7()) > stockCount)
+ return buyStock(member, price, stockCount, companyId); // 구매 로직
+ else if(Long.parseLong(stockAsBi.getAskp8()) == price && Integer.parseInt(stockAsBi.getAskp_rsqn8()) > stockCount)
+ return buyStock(member, price, stockCount, companyId); // 구매 로직
+ else if(Long.parseLong(stockAsBi.getAskp9()) == price && Integer.parseInt(stockAsBi.getAskp_rsqn9()) > stockCount)
+ return buyStock(member, price, stockCount, companyId); // 구매 로직
+ else if(Long.parseLong(stockAsBi.getAskp10()) == price && Integer.parseInt(stockAsBi.getAskp_rsqn10()) > stockCount)
+ return buyStock(member, price, stockCount, companyId); // 구매 로직
+ else
+ return reserveStock(member, price, stockCount, companyId, StockOrder.OrderTypes.BUY); //예약 구매 로직
+ }
+
+ public StockOrder buyStock(Member member, long price, int stockCount, long companyId) {
+ // 보유 주식 설정
+ StockHold stockHold = stockHoldService.checkStockHold(companyId, member.getMemberId());
+ stockHold.setStockCount(stockHold.getStockCount() + stockCount);
+ stockHold.setPrice(stockHold.getPrice() + (stockCount * price));
+
+ // 스톡 오더 작성
+ StockOrder stockOrder = new StockOrder();
+ stockOrder.setOrderStates(StockOrder.OrderStates.ORDER_COMPLETE);
+ stockOrder.setOrderTypes(StockOrder.OrderTypes.BUY);
+ stockOrder.setStockCount(stockCount);
+ stockOrder.setPrice(price);
+ stockOrder.setCompany(companyService.findCompanyById(companyId));
+
+ // 현금량 감소
+ Cash cash = member.getCash();
+ cash.setMoney(cash.getMoney()-(price * stockCount));
+ member.setCash(cash);
+ stockOrder.setMember(member);
+
+ stockOrderRepository.save(stockOrder);
+ memberRepository.save(member);
+ stockHoldRepository.save(stockHold);
+
+
+ return stockOrder;
+ }
+
+ // 예약 매도 일 때는 보유 주식 줄어들게(완료)
+ public StockOrder reserveStock(Member member, long price, int stockCount, long companyId, StockOrder.OrderTypes types) {
+ if(StockOrder.OrderTypes.SELL.equals(types)) {
+ // 보유 주식 설정
+ StockHold stockHold = stockHoldService.findStockHold(companyId, member.getMemberId());
+ stockHold.setStockCount(stockHold.getStockCount() - stockCount);
+ stockHold.setReserveStockCount(stockCount);
+
+ }
+ StockOrder stockOrder = new StockOrder();
+ stockOrder.setOrderStates(StockOrder.OrderStates.ORDER_WAIT);
+ stockOrder.setOrderTypes(types);
+ stockOrder.setStockCount(stockCount);
+ stockOrder.setPrice(price);
+ stockOrder.setCompany(companyService.findCompanyById(companyId));
+ stockOrder.setMember(member);
+
+ stockOrderRepository.save(stockOrder);
+
+ return stockOrder;
+ }
+
+ public void checkOrder() {
+ // 회사 리스트를 받아온다
+ List companyList = companyService.findCompanies();
+ List updateBuyStockOrders = new ArrayList<>();
+ List updateSellStockOrders = new ArrayList<>();
+ // for문(회사별로)
+ for(Company company : companyList) {
+ // 회사 호가 리스트를 받아온다
+ StockAsBi stockAsBi = stockAsBiService.getStockAsBi(company.getCompanyId());
+ // 회사Id에 있는 stockOrder 중 체결 대기 상태인 stockOrder를 큐로 받아온다
+ Queue stockOrderQueue = getStockOrderQueue(company.getCompanyId(), StockOrder.OrderStates.ORDER_WAIT);
+ //큐가 비어있지 않으면
+ if(!stockOrderQueue.isEmpty()) {
+ // for문(큐가 다 빌 때 까지 실행한다)
+ while(!stockOrderQueue.isEmpty()) {
+ StockOrder stockOrder = stockOrderQueue.poll();
+ // 예약 매수 실행
+ if(stockOrder.getOrderTypes().equals(StockOrder.OrderTypes.BUY)) {
+ // 호가 리스트 안에 체결 대기중인 stockOrder의 조건이 맞는 것이 있으면 buyStock으로 간다
+ StockOrder buyStock = reserveBuyDiscrimination(stockAsBi, stockOrder);
+ // 클라이언트로 StockOrder를 보낸다(값이 있으면)
+ if(buyStock != null)
+ updateBuyStockOrders.add(buyStock);
+ }
+ // 예약 매도 실행
+ else {
+ StockOrder sellStock = reserveSellDiscrimination(stockAsBi, stockOrder);
+ // 클라이언트로 StockOrder를 보낸다(값이 있으면)
+ if(sellStock != null)
+ updateSellStockOrders.add(sellStock);
+ }
+ }
+ }
+ }
+ long activeThreadCount = ManagementFactory.getThreadMXBean().getThreadCount();
+ System.out.println("현재 활성 스레드 수: " + activeThreadCount);
+ sendStockOrder(updateBuyStockOrders, updateSellStockOrders);
+ //longPollingController.notifyDataUpdated(updateBuyStockOrders, updateSellStockOrders);
+ }
+
+
+
+ private StockOrder reserveBuyDiscrimination(StockAsBi stockAsBi, StockOrder stockOrder) {
+ long price = stockOrder.getPrice();
+ int stockCount = stockOrder.getStockCount();
+ // 매도 호가와 가격이 같고, 잔량이 남아 있을 경우
+ if(Long.parseLong(stockAsBi.getAskp1()) == price && Integer.parseInt(stockAsBi.getAskp_rsqn1()) > stockCount)
+ return reserveBuyStock(stockOrder); // 구매 로직
+ else if(Long.parseLong(stockAsBi.getAskp2()) == price && Integer.parseInt(stockAsBi.getAskp_rsqn2()) > stockCount)
+ return reserveBuyStock(stockOrder); // 구매 로직
+ else if(Long.parseLong(stockAsBi.getAskp3()) == price && Integer.parseInt(stockAsBi.getAskp_rsqn3()) > stockCount)
+ return reserveBuyStock(stockOrder); // 구매 로직
+ else if(Long.parseLong(stockAsBi.getAskp4()) == price && Integer.parseInt(stockAsBi.getAskp_rsqn4()) > stockCount)
+ return reserveBuyStock(stockOrder); // 구매 로직
+ else if(Long.parseLong(stockAsBi.getAskp5()) == price && Integer.parseInt(stockAsBi.getAskp_rsqn5()) > stockCount)
+ return reserveBuyStock(stockOrder); // 구매 로직
+ else if(Long.parseLong(stockAsBi.getAskp6()) == price && Integer.parseInt(stockAsBi.getAskp_rsqn6()) > stockCount)
+ return reserveBuyStock(stockOrder); // 구매 로직
+ else if(Long.parseLong(stockAsBi.getAskp7()) == price && Integer.parseInt(stockAsBi.getAskp_rsqn7()) > stockCount)
+ return reserveBuyStock(stockOrder); // 구매 로직
+ else if(Long.parseLong(stockAsBi.getAskp8()) == price && Integer.parseInt(stockAsBi.getAskp_rsqn8()) > stockCount)
+ return reserveBuyStock(stockOrder); // 구매 로직
+ else if(Long.parseLong(stockAsBi.getAskp9()) == price && Integer.parseInt(stockAsBi.getAskp_rsqn9()) > stockCount)
+ return reserveBuyStock(stockOrder); // 구매 로직
+ else if(Long.parseLong(stockAsBi.getAskp10()) == price && Integer.parseInt(stockAsBi.getAskp_rsqn10()) > stockCount)
+ return reserveBuyStock(stockOrder); // 구매 로직
+ else {
+ return null; // 아무것도 안함
+ }
+
+ }
+
+ private StockOrder reserveSellDiscrimination(StockAsBi stockAsBi, StockOrder stockOrder) {
+ long price = stockOrder.getPrice();
+ int stockCount = stockOrder.getStockCount();
+ // 매도 호가와 가격이 같고, 잔량이 남아 있을 경우
+ if(Long.parseLong(stockAsBi.getBidp1()) == price && Integer.parseInt(stockAsBi.getBidp_rsqn1()) > stockCount)
+ return reserveSellStock(stockOrder); // 판매 로직
+ else if(Long.parseLong(stockAsBi.getBidp2()) == price && Integer.parseInt(stockAsBi.getBidp_rsqn2()) > stockCount)
+ return reserveSellStock(stockOrder); // 판매 로직
+ else if(Long.parseLong(stockAsBi.getBidp3()) == price && Integer.parseInt(stockAsBi.getBidp_rsqn3()) > stockCount)
+ return reserveSellStock(stockOrder); // 판매 로직
+ else if(Long.parseLong(stockAsBi.getBidp4()) == price && Integer.parseInt(stockAsBi.getBidp_rsqn4()) > stockCount)
+ return reserveSellStock(stockOrder); // 판매 로직
+ else if(Long.parseLong(stockAsBi.getBidp5()) == price && Integer.parseInt(stockAsBi.getBidp_rsqn5()) > stockCount)
+ return reserveSellStock(stockOrder); // 판매 로직
+ else if(Long.parseLong(stockAsBi.getBidp6()) == price && Integer.parseInt(stockAsBi.getBidp_rsqn6()) > stockCount)
+ return reserveSellStock(stockOrder); // 판매 로직
+ else if(Long.parseLong(stockAsBi.getBidp7()) == price && Integer.parseInt(stockAsBi.getBidp_rsqn7()) > stockCount)
+ return reserveSellStock(stockOrder); // 판매 로직
+ else if(Long.parseLong(stockAsBi.getBidp8()) == price && Integer.parseInt(stockAsBi.getBidp_rsqn8()) > stockCount)
+ return reserveSellStock(stockOrder); // 판매 로직
+ else if(Long.parseLong(stockAsBi.getBidp9()) == price && Integer.parseInt(stockAsBi.getBidp_rsqn9()) > stockCount)
+ return reserveSellStock(stockOrder); // 판매 로직
+ else if(Long.parseLong(stockAsBi.getBidp10()) == price && Integer.parseInt(stockAsBi.getBidp_rsqn10()) > stockCount)
+ return reserveSellStock(stockOrder); // 판매 로직
+ else
+ return null; // 아무것도 안함
+
+ }
+
+ // 예약된 매수 -> 구매 상태로 바구기
+ public StockOrder reserveBuyStock(StockOrder stockOrder) {
+ Optional optionalStockOrder = stockOrderRepository.findById(stockOrder.getStockOrderId());
+ StockOrder updateStockOrder = optionalStockOrder.get();
+ updateStockOrder.setOrderStates(StockOrder.OrderStates.ORDER_COMPLETE);
+ updateStockOrder.setOrderTypes(StockOrder.OrderTypes.BUY);
+ // 보유 주식 설정
+ StockHold stockHold = stockHoldService.checkStockHold(stockOrder.getCompany().getCompanyId(), stockOrder.getMember().getMemberId());
+ stockHold.setStockCount(stockHold.getStockCount() + stockOrder.getStockCount());
+ stockHold.setPrice(stockHold.getPrice() + (stockOrder.getStockCount() * stockOrder.getPrice()));
+ // 현금량 감소
+ Member member = updateStockOrder.getMember();
+ Cash cash = member.getCash();
+ cash.setMoney(cash.getMoney()-(stockOrder.getPrice() * stockOrder.getStockCount()));
+ member.setCash(cash);
+ stockOrder.setMember(member);
+
+ stockOrderRepository.save(stockOrder);
+ memberRepository.save(member);
+ stockHoldRepository.save(stockHold);
+
+ return stockOrder;
+ }
+
+ // 예약 매도 -> 판매로 바뀔 때 금액 늘어나게, 보유 주식은 예약 할 때 줄어들도록
+ // Price 줄어드는 금액은 주식 투자금액 - (주식 투자 금액 / 보유 주식 개수) * 팔 주식 개수 (완료)
+ public StockOrder reserveSellStock(StockOrder stockOrder) {
+ Optional optionalStockOrder = stockOrderRepository.findById(stockOrder.getStockOrderId());
+ StockOrder updateStockOrder = optionalStockOrder.get();
+ updateStockOrder.setOrderStates(StockOrder.OrderStates.ORDER_COMPLETE);
+ updateStockOrder.setOrderTypes(StockOrder.OrderTypes.SELL);
+ // 보유 주식 설정
+ StockHold stockHold = stockHoldService.findStockHold(stockOrder.getCompany().getCompanyId(), stockOrder.getMember().getMemberId());
+ stockHold.setPrice(stockHold.getPrice() - (stockHold.getPrice() / (stockHold.getStockCount()+stockHold.getReserveStockCount())) * stockOrder.getStockCount());
+ stockHold.setReserveStockCount(stockHold.getReserveStockCount() - stockOrder.getStockCount());
+ // 현금량 증가
+ Member member = updateStockOrder.getMember();
+ Cash cash = member.getCash();
+ cash.setMoney(cash.getMoney() + (stockOrder.getPrice() * stockOrder.getStockCount()));
+ member.setCash(cash);
+ stockOrder.setMember(member);
+
+ stockOrderRepository.save(stockOrder);
+ memberRepository.save(member);
+ if(stockHold.getStockCount() + stockHold.getReserveStockCount() == 0)
+ stockHoldRepository.delete(stockHold);
+ else
+ stockHoldRepository.save(stockHold);
+
+ return stockOrder;
+ }
+
+ // 멤버, 회사 id, 가격
+ public StockOrder sellStocks(Member member, long companyId, long price, int stockCount) {
+ // 내가 주식을 가지고 있는지 없는지 판별
+ StockHold stockHold = stockHoldService.findStockHold(companyId, member.getMemberId());
+ if(stockHold.getStockCount() < stockCount)
+ throw new BusinessLogicException(ExceptionCode.INSUFFICIENT_STOCK);
+ else {
+ //호가 불러오기
+ StockAsBi stockAsBi = stockAsBiService.getStockAsBi(companyId);
+ // 예약 판매인지 바로 판매인지 판별
+ return sellDiscrimination(member, price, stockAsBi, stockCount, companyId);
+ }
+
+ }
+
+ // 매도 판별해서 실행
+ public StockOrder sellDiscrimination(Member member, long price, StockAsBi stockAsBi, int stockCount, long companyId) {
+ // 매도 호가와 가격이 같고, 잔량이 남아 있을 경우
+ if(Long.parseLong(stockAsBi.getBidp1()) == price && Integer.parseInt(stockAsBi.getBidp1()) > stockCount)
+ return sellStock(member, price, stockCount, companyId); // 판매 로직
+ else if(Long.parseLong(stockAsBi.getBidp2()) == price && Integer.parseInt(stockAsBi.getBidp_rsqn2()) > stockCount)
+ return sellStock(member, price, stockCount, companyId); // 판매 로직
+ else if(Long.parseLong(stockAsBi.getBidp3()) == price && Integer.parseInt(stockAsBi.getBidp_rsqn3()) > stockCount)
+ return sellStock(member, price, stockCount, companyId); // 판매 로직
+ else if(Long.parseLong(stockAsBi.getBidp4()) == price && Integer.parseInt(stockAsBi.getBidp_rsqn4()) > stockCount)
+ return sellStock(member, price, stockCount, companyId); // 판매 로직
+ else if(Long.parseLong(stockAsBi.getBidp5()) == price && Integer.parseInt(stockAsBi.getBidp_rsqn5()) > stockCount)
+ return sellStock(member, price, stockCount, companyId); // 판매 로직
+ else if(Long.parseLong(stockAsBi.getBidp6()) == price && Integer.parseInt(stockAsBi.getBidp_rsqn6()) > stockCount)
+ return sellStock(member, price, stockCount, companyId); // 판매 로직
+ else if(Long.parseLong(stockAsBi.getBidp7()) == price && Integer.parseInt(stockAsBi.getBidp_rsqn7()) > stockCount)
+ return sellStock(member, price, stockCount, companyId); // 판매 로직
+ else if(Long.parseLong(stockAsBi.getBidp8()) == price && Integer.parseInt(stockAsBi.getBidp_rsqn8()) > stockCount)
+ return sellStock(member, price, stockCount, companyId); // 판매 로직
+ else if(Long.parseLong(stockAsBi.getBidp9()) == price && Integer.parseInt(stockAsBi.getBidp_rsqn9()) > stockCount)
+ return sellStock(member, price, stockCount, companyId); // 판매 로직
+ else if(Long.parseLong(stockAsBi.getBidp10()) == price && Integer.parseInt(stockAsBi.getBidp_rsqn10()) > stockCount)
+ return sellStock(member, price, stockCount, companyId); // 판매 로직
+ else
+ return reserveStock(member, price, stockCount, companyId, StockOrder.OrderTypes.SELL); //예약 판매 로직
+ }
+
+ public StockOrder sellStock(Member member, long price, int stockCount, long companyId) {
+ // 보유 주식 설정
+ StockHold stockHold = stockHoldService.findStockHold(companyId, member.getMemberId());
+ stockHold.setPrice(stockHold.getPrice() - (stockHold.getPrice() / (stockHold.getStockCount()+stockHold.getReserveStockCount())) * stockCount);
+ stockHold.setStockCount(stockHold.getStockCount() - stockCount);
+
+ // 스톡 오더 작성
+ StockOrder stockOrder = new StockOrder();
+ stockOrder.setOrderStates(StockOrder.OrderStates.ORDER_COMPLETE);
+ stockOrder.setOrderTypes(StockOrder.OrderTypes.SELL);
+ stockOrder.setStockCount(stockCount);
+ stockOrder.setPrice(price);
+ stockOrder.setCompany(companyService.findCompanyById(companyId));
+
+ // 현금량 증가
+ Cash cash = member.getCash();
+ cash.setMoney(cash.getMoney()+(price * stockCount));
+ member.setCash(cash);
+ stockOrder.setMember(member);
+
+
+
+ stockOrderRepository.save(stockOrder);
+ memberRepository.save(member);
+ // 여기서 stockhold 삭제됨
+ // 예약 매도 걸 때 reserveStockCount 늘어가네
+ // 예약 매도 취소 할 때 reserveStockCount 줄어들게
+ if(stockHold.getStockCount() + stockHold.getReserveStockCount() == 0)
+ stockHoldRepository.delete(stockHold);
+ else
+ stockHoldRepository.save(stockHold);
+
+ return stockOrder;
+ }
+
+
+ // 거래 대기중인 매수 StockOrder 불러오기
+ public Queue getStockOrderQueue(long companyId, StockOrder.OrderStates orderStates) {
+ List stockOrderList = stockOrderRepository.findAllByCompanyCompanyIdAndOrderStates(companyId, orderStates);
+ Queue stockOrderQueue = new LinkedList<>(stockOrderList);
+
+ return stockOrderQueue;
+ }
+
+ // 멤버의 모든 주식 거래내역 삭제하기
+ public void deleteStockOrders(Member member) {
+ List stockOrders = stockOrderRepository.findAllByMember_MemberId(member.getMemberId());
+
+ stockOrderRepository.deleteAll(stockOrders);
+ }
+
+ // 멤버의 모든 StockOrders 불러오기
+ public List getMemberStockOrders(long memberId) {
+ List stockOrders = stockOrderRepository.findAllByMember_MemberIdOrderByModifiedAtDesc(memberId);
+ List stockOrderRepositories = stockOrders.stream()
+ .map(stockOrder -> stockMapper.stockOrderToStockOrderResponseDto(stockOrder)).collect(Collectors.toList());
+
+ return stockOrderRepositories;
+ }
+
+ // 미체결 stockOrder 취소하는 메소드
+ // 취소한 주식 수 만큼 보유주식으로 돌아오게 해야함
+ public void deleteStockOrder(Member member, long stockOrderId, int stockCount) {
+ Optional optionalStockOrder = stockOrderRepository.findById(stockOrderId);
+ StockOrder stockOrder = optionalStockOrder.orElseThrow(() -> new BusinessLogicException(ExceptionCode.STOCKORDER_NOT_FOUND));
+
+ if(stockOrder.getMember().getMemberId() != member.getMemberId()) {
+ throw new BusinessLogicException(ExceptionCode.STOCKORDER_PERMISSION_DENIED);
+ }
+ else if(!stockOrder.getOrderStates().equals(StockOrder.OrderStates.ORDER_WAIT))
+ throw new BusinessLogicException(ExceptionCode.STOCKORDER_ALREADY_FINISH);
+ // 수량 선택해서 취소 할 수 있게(취소한 만큼 보유 주식 돌아오게) 0이 되면 미체결 스톡 오더 삭제
+ else {
+ if(stockOrder.getStockCount() <= stockCount)
+ stockOrderRepository.delete(stockOrder);
+ else {
+ stockOrder.setStockCount(stockOrder.getStockCount() - stockCount);
+ }
+ if(StockOrder.OrderTypes.SELL.equals(stockOrder.getOrderTypes())) {
+ StockHold stockHold = stockHoldService.findStockHold(stockOrder.getCompany().getCompanyId(), stockOrder.getMember().getMemberId());
+ stockHold.setStockCount(stockHold.getStockCount() + stockCount);
+ stockHold.setReserveStockCount(stockHold.getReserveStockCount() - stockCount);
+ }
+
+ }
+ }
+ // 클라언트에 알림 보냄
+ public void sendStockOrder(List updateBuyStockOrders, List updateSellStockOrders) {
+ Map> buyStockOrdersByMemberId = updateBuyStockOrders.stream()
+ .collect(Collectors.groupingBy(stockOrder -> stockOrder.getMember().getMemberId()));
+ Map> sellStockOrdersByMemberId = updateSellStockOrders.stream()
+ .collect(Collectors.groupingBy(stockOrder -> stockOrder.getMember().getMemberId()));
+
+ buyStockOrdersByMemberId.forEach((memberId, orders) -> {
+ System.out.println("Member ID: " + memberId);
+ orders.forEach(order -> {
+ String destination = "/sub/" + memberId;
+ StockOrder stockOrder = order;
+ StockOrderResponseDto stockOrderResponseDto = stockMapper.stockOrderToStockOrderResponseDto(stockOrder);
+
+ messagingTemplate.convertAndSend(destination, stockOrderResponseDto);
+
+ });
+ });
+
+ sellStockOrdersByMemberId.forEach((memberId, orders) -> {
+ System.out.println("Member ID: " + memberId);
+ orders.forEach(order -> {
+ String destination = "/sub/" + memberId;
+ System.out.println(destination);
+ StockOrder stockOrder = order;
+ StockOrderResponseDto stockOrderResponseDto = stockMapper.stockOrderToStockOrderResponseDto(stockOrder);
+
+ messagingTemplate.convertAndSend(destination, stockOrderResponseDto);
+
+ });
+ });
+ }
+}
diff --git a/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/TokenService.java b/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/TokenService.java
new file mode 100644
index 00000000..0785e543
--- /dev/null
+++ b/server/008main_project/src/main/java/com/stockholm/main_project/stock/service/TokenService.java
@@ -0,0 +1,101 @@
+package com.stockholm.main_project.stock.service;
+
+import com.stockholm.main_project.stock.repository.TokenRepository;
+import com.stockholm.main_project.stock.entity.Token;
+import lombok.Getter;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import javax.transaction.Transactional;
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+@Service
+@Transactional
+public class TokenService {
+
+ public TokenService(TokenRepository tokenRepository) {
+ this.tokenRepository = tokenRepository;
+ }
+
+ private final TokenRepository tokenRepository;
+
+ @Getter
+ @Value("${token.app-key}")
+ private String APP_KEY;
+
+ @Getter
+ @Value("${token.app-secret}")
+ private String APP_SECRET;
+
+ @Getter
+ @Value("${stock-url.token}")
+ private String TOKEN_URL;
+
+ private RestTemplate restTemplate = new RestTemplate();
+
+ public String getAccessToken() {
+
+ // 만료 되지 않았을 경우
+ if(tokenVerification()) {
+ Optional token = tokenRepository.findById(1L);
+
+ return token.get().getToken();
+
+ }
+ // 만료 되었을 경우
+ else {
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+
+ Map body = new HashMap<>();
+ body.put("grant_type", "client_credentials");
+ body.put("appkey", APP_KEY);
+ body.put("appsecret", APP_SECRET);
+
+ HttpEntity