From db8ec433bd458931ba61fc75d41a0dad2cf5694c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=ED=98=B8=EC=9C=A4?= Date: Sun, 10 Dec 2023 20:46:30 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9D=B4=EB=A0=A5=EC=84=9C=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EC=9A=A9=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/gradle.yml | 2 +- build.gradle | 6 +- .../chwipoClova/resume/entity/QResume.java | 63 +++++++ .../chwipoClova/ChwipoClovaApplication.java | 6 + .../config/ApiDocsOperationCustomizer.java | 12 +- .../common/config/CommonConfig.java | 1 + .../common/config/SwaggerConfig.java | 10 ++ .../common/exception/ExceptionCode.java | 14 +- .../common/filter/RequestFilter.java | 24 ++- .../resume/controller/ResumeController.java | 70 ++++++++ .../com/chwipoClova/resume/entity/Resume.java | 65 +++++++ .../resume/repository/ResumeRepository.java | 16 ++ .../resume/request/ResumeDeleteOldReq.java | 11 ++ .../resume/request/ResumeDeleteReq.java | 15 ++ .../resume/request/ResumeUploadReq.java | 19 ++ .../resume/response/ResumeListRes.java | 22 +++ .../resume/response/ResumeUploadRes.java | 16 ++ .../resume/service/ResumeService.java | 168 ++++++++++++++++++ .../user/controller/UserController.java | 20 ++- .../com/chwipoClova/user/entity/User.java | 12 ++ .../user/repository/UserRepository.java | 2 + .../user/request/UserLoginReq.java | 9 + .../user/response/UserInfoRes.java | 27 +++ .../chwipoClova/user/service/UserService.java | 23 +++ .../ChwipoClovaApplicationTests.java | 8 + 25 files changed, 632 insertions(+), 9 deletions(-) create mode 100644 src/main/generated/com/chwipoClova/resume/entity/QResume.java create mode 100644 src/main/java/com/chwipoClova/resume/entity/Resume.java create mode 100644 src/main/java/com/chwipoClova/resume/repository/ResumeRepository.java create mode 100644 src/main/java/com/chwipoClova/resume/request/ResumeDeleteOldReq.java create mode 100644 src/main/java/com/chwipoClova/resume/request/ResumeDeleteReq.java create mode 100644 src/main/java/com/chwipoClova/resume/request/ResumeUploadReq.java create mode 100644 src/main/java/com/chwipoClova/resume/response/ResumeListRes.java create mode 100644 src/main/java/com/chwipoClova/resume/response/ResumeUploadRes.java create mode 100644 src/main/java/com/chwipoClova/resume/service/ResumeService.java create mode 100644 src/main/java/com/chwipoClova/user/response/UserInfoRes.java diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 1767df5..8c3921a 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -49,4 +49,4 @@ jobs: docker rm -f be-con || true docker rmi $(docker images -f "dangling=true" -q) || true docker pull ${{ secrets.DOCKER_REPO }}/chwipoclovadocker:latest - docker run -v /var/log/service:/var/log/service --name be-con -p 8080:8080 -d ${{ secrets.DOCKER_REPO }}/chwipoclovadocker:latest \ No newline at end of file + docker run -v /var/log/service:/var/log/service -v /var/upload:/var/upload --name be-con -p 8080:8080 -d ${{ secrets.DOCKER_REPO }}/chwipoclovadocker:latest \ No newline at end of file diff --git a/build.gradle b/build.gradle index 0c0ef81..6dfd81f 100644 --- a/build.gradle +++ b/build.gradle @@ -75,10 +75,12 @@ dependencies { implementation group: 'com.nimbusds', name: 'nimbus-jose-jwt', version: '3.10' //spring-doc - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.3.0' implementation group: 'commons-codec', name: 'commons-codec', version: '1.15' // 또는 최신 버전 - + // fileupload + implementation group: 'commons-io', name: 'commons-io', version: '2.15.1' + implementation group: 'commons-fileupload', name: 'commons-fileupload', version: '1.5' } diff --git a/src/main/generated/com/chwipoClova/resume/entity/QResume.java b/src/main/generated/com/chwipoClova/resume/entity/QResume.java new file mode 100644 index 0000000..1b8da58 --- /dev/null +++ b/src/main/generated/com/chwipoClova/resume/entity/QResume.java @@ -0,0 +1,63 @@ +package com.chwipoClova.resume.entity; + +import static com.querydsl.core.types.PathMetadataFactory.*; + +import com.querydsl.core.types.dsl.*; + +import com.querydsl.core.types.PathMetadata; +import javax.annotation.processing.Generated; +import com.querydsl.core.types.Path; +import com.querydsl.core.types.dsl.PathInits; + + +/** + * QResume is a Querydsl query type for Resume + */ +@Generated("com.querydsl.codegen.DefaultEntitySerializer") +public class QResume extends EntityPathBase { + + private static final long serialVersionUID = -1144291881L; + + private static final PathInits INITS = PathInits.DIRECT2; + + public static final QResume resume = new QResume("resume"); + + public final StringPath fileName = createString("fileName"); + + public final StringPath filePath = createString("filePath"); + + public final NumberPath fileSize = createNumber("fileSize", Long.class); + + public final StringPath orginalFileName = createString("orginalFileName"); + + public final DateTimePath regDate = createDateTime("regDate", java.util.Date.class); + + public final NumberPath resumeId = createNumber("resumeId", Long.class); + + public final StringPath summary = createString("summary"); + + public final com.chwipoClova.user.entity.QUser user; + + public QResume(String variable) { + this(Resume.class, forVariable(variable), INITS); + } + + public QResume(Path path) { + this(path.getType(), path.getMetadata(), PathInits.getFor(path.getMetadata(), INITS)); + } + + public QResume(PathMetadata metadata) { + this(metadata, PathInits.getFor(metadata, INITS)); + } + + public QResume(PathMetadata metadata, PathInits inits) { + this(Resume.class, metadata, inits); + } + + public QResume(Class type, PathMetadata metadata, PathInits inits) { + super(type, metadata, inits); + this.user = inits.isInitialized("user") ? new com.chwipoClova.user.entity.QUser(forProperty("user")) : null; + } + +} + diff --git a/src/main/java/com/chwipoClova/ChwipoClovaApplication.java b/src/main/java/com/chwipoClova/ChwipoClovaApplication.java index d47134d..e08bdee 100644 --- a/src/main/java/com/chwipoClova/ChwipoClovaApplication.java +++ b/src/main/java/com/chwipoClova/ChwipoClovaApplication.java @@ -1,7 +1,9 @@ package com.chwipoClova; +import jakarta.servlet.MultipartConfigElement; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; @SpringBootApplication public class ChwipoClovaApplication { @@ -10,4 +12,8 @@ public static void main(String[] args) { SpringApplication.run(ChwipoClovaApplication.class, args); } + @Bean + public MultipartConfigElement multipartConfigElement() { + return new MultipartConfigElement(""); + } } diff --git a/src/main/java/com/chwipoClova/common/config/ApiDocsOperationCustomizer.java b/src/main/java/com/chwipoClova/common/config/ApiDocsOperationCustomizer.java index 8722585..55a27c2 100644 --- a/src/main/java/com/chwipoClova/common/config/ApiDocsOperationCustomizer.java +++ b/src/main/java/com/chwipoClova/common/config/ApiDocsOperationCustomizer.java @@ -1,7 +1,9 @@ package com.chwipoClova.common.config; import com.chwipoClova.common.exception.ExceptionCode; +import com.chwipoClova.common.response.CommonMsgResponse; import com.chwipoClova.common.response.CommonResponse; +import com.chwipoClova.common.response.MessageCode; import io.swagger.v3.core.converter.ModelConverters; import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.media.Content; @@ -49,8 +51,14 @@ private Schema customizeSchema(String code, Schema objSchema) { responseMessage = HttpStatus.OK.getReasonPhrase(); } else { ExceptionCode exceptionCode = ExceptionCode.resolve(code); - responseCode = exceptionCode.getCode(); - responseMessage = exceptionCode.getMessage(); + if (exceptionCode != null) { + responseCode = exceptionCode.getCode(); + responseMessage = exceptionCode.getMessage(); + } else { + MessageCode messageCode = MessageCode.resolve(code); + responseCode = messageCode.getCode(); + responseMessage = messageCode.getMessage(); + } } wrapperSchema.addProperties("data", objSchema); wrapperSchema.addProperty("code", new StringSchema()._default(responseCode)); diff --git a/src/main/java/com/chwipoClova/common/config/CommonConfig.java b/src/main/java/com/chwipoClova/common/config/CommonConfig.java index ccc8c1d..d3b5b78 100644 --- a/src/main/java/com/chwipoClova/common/config/CommonConfig.java +++ b/src/main/java/com/chwipoClova/common/config/CommonConfig.java @@ -12,4 +12,5 @@ public RestTemplate restTemplate() { return new RestTemplate(); } + } diff --git a/src/main/java/com/chwipoClova/common/config/SwaggerConfig.java b/src/main/java/com/chwipoClova/common/config/SwaggerConfig.java index dd5c81b..d60c37e 100644 --- a/src/main/java/com/chwipoClova/common/config/SwaggerConfig.java +++ b/src/main/java/com/chwipoClova/common/config/SwaggerConfig.java @@ -6,8 +6,13 @@ import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; +import jakarta.servlet.http.HttpServletRequest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.web.multipart.MultipartException; +import org.springframework.web.multipart.MultipartHttpServletRequest; +import org.springframework.web.multipart.MultipartResolver; +import org.springframework.web.multipart.support.StandardServletMultipartResolver; @Configuration public class SwaggerConfig { @@ -43,4 +48,9 @@ private Info apiInfo() { .description("Springdoc을 사용한 Swagger UI") .version("1.0.0"); } + + @Bean + public MultipartResolver multipartResolver() { + return new StandardServletMultipartResolver(); + } } diff --git a/src/main/java/com/chwipoClova/common/exception/ExceptionCode.java b/src/main/java/com/chwipoClova/common/exception/ExceptionCode.java index 3735b38..c26da9e 100644 --- a/src/main/java/com/chwipoClova/common/exception/ExceptionCode.java +++ b/src/main/java/com/chwipoClova/common/exception/ExceptionCode.java @@ -16,8 +16,20 @@ public enum ExceptionCode { SERVER_ERROR(String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()), "내부 서버 오류입니다."), + + // Custom Exception - SECURITY("600", "로그인이 필요합니다"); + SECURITY("800", "로그인이 필요합니다"), + + USER_NULL("801", "유저 정보가 올바르지 않습니다."), + + FILE_EXT("850", "PDF 파일 형식이 아닙니다."), + + FILE_SIZE("851", "파일 업로드 최대 크기는 50M 입니다."), + + RESUME_NULL("860", "이력서 정보가 올바르지 않습니다.") + + ; private static final ExceptionCode[] VALUES; diff --git a/src/main/java/com/chwipoClova/common/filter/RequestFilter.java b/src/main/java/com/chwipoClova/common/filter/RequestFilter.java index 8493895..4446cb1 100644 --- a/src/main/java/com/chwipoClova/common/filter/RequestFilter.java +++ b/src/main/java/com/chwipoClova/common/filter/RequestFilter.java @@ -4,7 +4,11 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartHttpServletRequest; +import org.springframework.web.multipart.MultipartResolver; import org.springframework.web.util.ContentCachingRequestWrapper; import org.springframework.web.util.ContentCachingResponseWrapper; import org.springframework.web.util.WebUtils; @@ -20,11 +24,29 @@ @Component public class RequestFilter implements Filter { + @Autowired + private MultipartResolver multipartResolver; + + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - + //chain.doFilter(request, response); ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request); + + if (multipartResolver.isMultipart((HttpServletRequest) request)) { + // 멀티파트 요청으로부터 MultipartHttpServletRequest 획득 + MultipartHttpServletRequest multipartRequest = multipartResolver.resolveMultipart((HttpServletRequest) request); + + Map fileMap = multipartRequest.getFileMap(); + for (Map.Entry entry : fileMap.entrySet()) { + MultipartFile file = entry.getValue(); + // 파일 데이터를 다시 설정 + byte[] fileBytes = file.getBytes(); // 파일 데이터 읽기 + requestWrapper.setAttribute(entry.getKey(), fileBytes); // 새로운 요청(wrapper)에 파일 데이터 설정 + } + } + ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper((HttpServletResponse) response); CustomRequestWrapper customRequestWrapper = new CustomRequestWrapper(requestWrapper); diff --git a/src/main/java/com/chwipoClova/resume/controller/ResumeController.java b/src/main/java/com/chwipoClova/resume/controller/ResumeController.java index d08fee5..d0e9067 100644 --- a/src/main/java/com/chwipoClova/resume/controller/ResumeController.java +++ b/src/main/java/com/chwipoClova/resume/controller/ResumeController.java @@ -1,4 +1,74 @@ package com.chwipoClova.resume.controller; +import com.chwipoClova.common.response.CommonResponse; +import com.chwipoClova.resume.request.ResumeDeleteOldReq; +import com.chwipoClova.resume.request.ResumeDeleteReq; +import com.chwipoClova.resume.response.ResumeListRes; +import com.chwipoClova.resume.response.ResumeUploadRes; +import com.chwipoClova.resume.service.ResumeService; +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 io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.service.annotation.DeleteExchange; + +import java.util.List; + +@Slf4j +@RestController +@RequiredArgsConstructor +@Tag(name = "Resume", description = "이력서 API") +@RequestMapping("resume") public class ResumeController { + + private final ResumeService resumeService; + + @Operation(summary = "이력서 업로드", description = "이력서 업로드") + @PostMapping(path = "/resumeUpload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK") + } + ) + public ResumeUploadRes resumeUpload( + @Schema(description = "userId", example = "1", name = "userId") + @RequestParam(value = "userId") Long userId, + @RequestPart(value = "file") MultipartFile file + ) throws Exception { + return resumeService.resumeUpload(userId, file); + } + + @Operation(summary = "이력서 조회", description = "이력서 조회") + @GetMapping(path = "/getResumeList") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK") + } + ) + public List getResumeList(@Schema(description = "userId", example = "1", name = "userId") @RequestParam(name = "userId") Long userId) { + return resumeService.selectResumeList(userId); + } + + @Operation(summary = "이력서 삭제", description = "이력서 삭제") + @DeleteMapping(path = "/deleteResume") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class)))} + ) + public CommonResponse deleteResume(@RequestBody ResumeDeleteReq resumeDeleteReq) { + return resumeService.deleteResume(resumeDeleteReq); + } + + + @Operation(summary = "오래된 이력서 삭제", description = "오래된 이력서 삭제") + @DeleteMapping(path = "/deleteOldResume") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = String.class)))} + ) + public CommonResponse deleteOldResume(@RequestBody ResumeDeleteOldReq resumeDeleteOldReq) { + return resumeService.deleteOldResume(resumeDeleteOldReq); + } + } diff --git a/src/main/java/com/chwipoClova/resume/entity/Resume.java b/src/main/java/com/chwipoClova/resume/entity/Resume.java new file mode 100644 index 0000000..8dbd586 --- /dev/null +++ b/src/main/java/com/chwipoClova/resume/entity/Resume.java @@ -0,0 +1,65 @@ +package com.chwipoClova.resume.entity; + +import com.chwipoClova.user.entity.User; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.DynamicInsert; + +import java.util.Date; + +@Entity(name = "Resume") +@Table(name = "Resume") +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties() +@DynamicInsert +@Builder +@Getter +@ToString +@AllArgsConstructor +@NoArgsConstructor +@Schema(description = "이력서 정보 VO") +public class Resume { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "resumeId") + @Schema(description = "이력서ID") + private Long resumeId; + + @Column(name = "fileName") + @Schema(description = "파일이름") + private String fileName; + + @Column(name = "filePath") + @Schema(description = "파일경로") + private String filePath; + + @Column(name = "fileSize") + @Schema(description = "파일크기") + private Long fileSize; + + @Column(name = "orginalFileName") + @Schema(description = "원본파일이름") + private String orginalFileName; + + @Column(name = "summary") + @Schema(description = "요약") + private String summary; + + @Column(name = "regDate") + @Schema(description = "등록일") + private Date regDate; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "userId") + private User user; + + // @PrePersist 메서드 정의 (최초 등록시 호출) + @PrePersist + public void prePersist() { + this.regDate = new Date(); // 현재 날짜와 시간으로 등록일 설정 + } +} diff --git a/src/main/java/com/chwipoClova/resume/repository/ResumeRepository.java b/src/main/java/com/chwipoClova/resume/repository/ResumeRepository.java new file mode 100644 index 0000000..36302a6 --- /dev/null +++ b/src/main/java/com/chwipoClova/resume/repository/ResumeRepository.java @@ -0,0 +1,16 @@ +package com.chwipoClova.resume.repository; + +import com.chwipoClova.resume.entity.Resume; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface ResumeRepository extends JpaRepository { + + List findByUserUserIdOrderByRegDate(Long userId); + + Optional findByUserUserIdAndResumeId(Long userId, Long resumeId); + + Optional findTop1ByUserUserIdOrderByRegDate(Long userId); +} diff --git a/src/main/java/com/chwipoClova/resume/request/ResumeDeleteOldReq.java b/src/main/java/com/chwipoClova/resume/request/ResumeDeleteOldReq.java new file mode 100644 index 0000000..cfd55e5 --- /dev/null +++ b/src/main/java/com/chwipoClova/resume/request/ResumeDeleteOldReq.java @@ -0,0 +1,11 @@ +package com.chwipoClova.resume.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class ResumeDeleteOldReq { + + @Schema(description = "유저 ID", example = "1", name = "userId") + private Long userId; +} diff --git a/src/main/java/com/chwipoClova/resume/request/ResumeDeleteReq.java b/src/main/java/com/chwipoClova/resume/request/ResumeDeleteReq.java new file mode 100644 index 0000000..d7ac92a --- /dev/null +++ b/src/main/java/com/chwipoClova/resume/request/ResumeDeleteReq.java @@ -0,0 +1,15 @@ +package com.chwipoClova.resume.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class ResumeDeleteReq { + + @Schema(description = "이력서 ID", example = "1", name = "resumeId") + private Long resumeId; + + @Schema(description = "유저 ID", example = "1", name = "userId") + private Long userId; + +} diff --git a/src/main/java/com/chwipoClova/resume/request/ResumeUploadReq.java b/src/main/java/com/chwipoClova/resume/request/ResumeUploadReq.java new file mode 100644 index 0000000..1221f85 --- /dev/null +++ b/src/main/java/com/chwipoClova/resume/request/ResumeUploadReq.java @@ -0,0 +1,19 @@ +package com.chwipoClova.resume.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.multipart.MultipartFile; + +@Data +@Builder +public class ResumeUploadReq { + + @Schema(description = "이력서파일", example = "이력서.pdf", name = "file") + private MultipartFile file; + + @Schema(description = "아이디", example = "1", name = "userId") + private Long userId; + +} diff --git a/src/main/java/com/chwipoClova/resume/response/ResumeListRes.java b/src/main/java/com/chwipoClova/resume/response/ResumeListRes.java new file mode 100644 index 0000000..0c07ec3 --- /dev/null +++ b/src/main/java/com/chwipoClova/resume/response/ResumeListRes.java @@ -0,0 +1,22 @@ +package com.chwipoClova.resume.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.persistence.Column; +import lombok.Builder; +import lombok.Data; + +import java.util.Date; + +@Data +@Builder +public class ResumeListRes { + + @Schema(description = "이력서ID", example = "1", name = "resumeId") + private Long resumeId; + + @Schema(description = "파일이름", example = "이력서.pdf", name = "fileName") + private String fileName; + + @Schema(description = "등록일", example = "2023-12-09T10:13:17.838+00:00", name = "regDate") + private Date regDate; +} diff --git a/src/main/java/com/chwipoClova/resume/response/ResumeUploadRes.java b/src/main/java/com/chwipoClova/resume/response/ResumeUploadRes.java new file mode 100644 index 0000000..cdbc5c1 --- /dev/null +++ b/src/main/java/com/chwipoClova/resume/response/ResumeUploadRes.java @@ -0,0 +1,16 @@ +package com.chwipoClova.resume.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class ResumeUploadRes { + + @Schema(description = "아이디", example = "1", name = "userId") + private Long userId; + + @Schema(description = "이력서 번호", example = "1", name = "resumeId") + private Long resumeId; +} diff --git a/src/main/java/com/chwipoClova/resume/service/ResumeService.java b/src/main/java/com/chwipoClova/resume/service/ResumeService.java new file mode 100644 index 0000000..5d7bc56 --- /dev/null +++ b/src/main/java/com/chwipoClova/resume/service/ResumeService.java @@ -0,0 +1,168 @@ +package com.chwipoClova.resume.service; + +import com.chwipoClova.common.exception.CommonException; +import com.chwipoClova.common.exception.ExceptionCode; +import com.chwipoClova.common.response.CommonResponse; +import com.chwipoClova.common.response.MessageCode; +import com.chwipoClova.resume.entity.Resume; +import com.chwipoClova.resume.repository.ResumeRepository; +import com.chwipoClova.resume.request.ResumeDeleteOldReq; +import com.chwipoClova.resume.request.ResumeDeleteReq; +import com.chwipoClova.resume.response.ResumeListRes; +import com.chwipoClova.resume.response.ResumeUploadRes; +import com.chwipoClova.user.entity.User; +import com.chwipoClova.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@RequiredArgsConstructor +@Service +@Slf4j +public class ResumeService { + + @Value("${file.upload.path}") + private String uploadPath; + + @Value("${file.upload.max-size}") + private Long uploadMaxSize; + + @Value("${file.upload.type}") + private String uploadType; + + private final ResumeRepository resumeRepository; + + private final UserRepository userRepository; + + @Transactional + public ResumeUploadRes resumeUpload(Long userId, MultipartFile file) throws IOException { + User user = userRepository.findById(userId).orElseThrow(() -> new CommonException(ExceptionCode.USER_NULL.getMessage(), ExceptionCode.USER_NULL.getCode())); + + String contentType = file.getContentType(); + assert contentType != null; + + if (contentType.toLowerCase().indexOf(uploadType) == -1) { + throw new CommonException(ExceptionCode.FILE_EXT.getMessage(), ExceptionCode.FILE_EXT.getCode()); + } + + String orginalName = file.getOriginalFilename(); + assert orginalName != null; + + // 날짜 폴더 생성 + String folderPath = makeFolder(); + + // UUID + String uuid = UUID.randomUUID().toString(); + + long currentTimeMills = Timestamp.valueOf(LocalDateTime.now()).getTime(); + + String filePath = uploadPath + File.separator + folderPath + File.separator; + String fileName = uuid + "_" + currentTimeMills; + Long fileSize = file.getSize(); + + if (fileSize > uploadMaxSize) { + new CommonException(ExceptionCode.FILE_SIZE.getMessage(), ExceptionCode.FILE_SIZE.getCode()); + } + + // 저장할 파일 이름 중간에 "_"를 이용해서 구현 + String saveName = filePath + fileName; + Path savePath = Paths.get(saveName); + file.transferTo(savePath); + + // TODO 업로드 성공 후 요약 저장 + + // TODO 등록 전 개수 제한 필요 + + // 파일업로드 성공 후 DB 저장 + Resume resume = Resume.builder() + .fileName(fileName) + .filePath(filePath) + .fileSize(fileSize) + .orginalFileName(orginalName) + .user(user) + .build(); + + Resume resumeRst = resumeRepository.save(resume); + return ResumeUploadRes.builder().userId(userId).resumeId(resumeRst.getResumeId()).build(); + } + + /*날짜 폴더 생성*/ + private String makeFolder() { + + String str = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd")); + + String folderPath = str.replace("/", File.separator); + + // make folder -------- + File uploadPathFolder = new File(uploadPath, folderPath); + + if(!uploadPathFolder.exists()) { + boolean mkdirs = uploadPathFolder.mkdirs(); + log.info("-------------------makeFolder------------------"); + log.info("uploadPathFolder.exists() : {}", uploadPathFolder.exists()); + log.info("mkdirs : {}", mkdirs); + } + return folderPath; + + } + + public List selectResumeList(Long userId) { + + List resumeListResList = new ArrayList<>(); + + User user = userRepository.findById(userId).orElseThrow(() -> new CommonException(ExceptionCode.USER_NULL.getMessage(), ExceptionCode.USER_NULL.getCode())); + + List resumeList = resumeRepository.findByUserUserIdOrderByRegDate(user.getUserId()); + + resumeList.stream().forEach(resume -> { + ResumeListRes resumeListRes = ResumeListRes.builder() + .resumeId(resume.getResumeId()) + .fileName(resume.getOrginalFileName()) + .regDate(resume.getRegDate()) + .build(); + resumeListResList.add(resumeListRes); + }); + + return resumeListResList; + } + + + @Transactional + public CommonResponse deleteResume(ResumeDeleteReq resumeDeleteReq) { + Long resumeId = resumeDeleteReq.getResumeId(); + Long userId = resumeDeleteReq.getUserId(); + + userRepository.findById(userId).orElseThrow(() -> new CommonException(ExceptionCode.USER_NULL.getMessage(), ExceptionCode.USER_NULL.getCode())); + Resume resume = resumeRepository.findByUserUserIdAndResumeId(userId, resumeId).orElseThrow(() -> new CommonException(ExceptionCode.RESUME_NULL.getMessage(), ExceptionCode.RESUME_NULL.getCode())); + resumeRepository.delete(resume); + return new CommonResponse<>(MessageCode.SUCCESS_DELETE.getCode(), null, MessageCode.SUCCESS_DELETE.getMessage()); + } + + + @Transactional + public CommonResponse deleteOldResume(ResumeDeleteOldReq resumeDeleteOldReq) { + Long userId = resumeDeleteOldReq.getUserId(); + + userRepository.findById(userId).orElseThrow(() -> new CommonException(ExceptionCode.USER_NULL.getMessage(), ExceptionCode.USER_NULL.getCode())); + + Resume resume = resumeRepository.findTop1ByUserUserIdOrderByRegDate(userId).orElseThrow(() -> new CommonException(ExceptionCode.RESUME_NULL.getMessage(), ExceptionCode.RESUME_NULL.getCode())); + resumeRepository.delete(resume); + return new CommonResponse<>(MessageCode.SUCCESS_DELETE.getCode(), null, MessageCode.SUCCESS_DELETE.getMessage()); + } +} diff --git a/src/main/java/com/chwipoClova/user/controller/UserController.java b/src/main/java/com/chwipoClova/user/controller/UserController.java index 8ce8699..510d659 100644 --- a/src/main/java/com/chwipoClova/user/controller/UserController.java +++ b/src/main/java/com/chwipoClova/user/controller/UserController.java @@ -1,6 +1,9 @@ package com.chwipoClova.user.controller; import com.chwipoClova.common.response.CommonResponse; +import com.chwipoClova.common.response.MessageCode; +import com.chwipoClova.user.request.UserLoginReq; +import com.chwipoClova.user.response.UserInfoRes; import com.chwipoClova.user.response.UserLoginRes; import com.chwipoClova.user.response.UserSnsUrlRes; import com.chwipoClova.user.service.UserService; @@ -41,11 +44,24 @@ public UserSnsUrlRes getKakaoUrl() throws Exception { @Operation(summary = "카카오 로그인", description = "카카오 로그인") @GetMapping("/kakaoCallback") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK") + @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = UserLoginRes.class))), + @ApiResponse(responseCode = "700", description = "신규 가입되었습니다.", content = @Content(schema = @Schema(implementation = String.class))) } ) - public CommonResponse kakaoCallback(@RequestParam(name = "code") String code, HttpServletResponse response) throws Exception { + public CommonResponse kakaoCallback(@Schema(description = "로그인코드", example = "1", name = "code") @RequestParam(name = "code") String code, HttpServletResponse response) throws Exception { return userService.kakaoLogin(code, response); } + + @Operation(summary = "유저 정보 조회 (테스트용)", description = "유저 정보 조회 (테스트용)") + @GetMapping("/getUserInfo") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK") + } + ) + public UserInfoRes getUserInfo( + @Schema(description = "이메일", example = "test@naver.com", name = "email") @RequestParam(name = "email") String email + ) { + return userService.selectUserInfo(email); + } } diff --git a/src/main/java/com/chwipoClova/user/entity/User.java b/src/main/java/com/chwipoClova/user/entity/User.java index 813fe0f..e04f78e 100644 --- a/src/main/java/com/chwipoClova/user/entity/User.java +++ b/src/main/java/com/chwipoClova/user/entity/User.java @@ -52,6 +52,18 @@ public class User { @Schema(description = "소셜회원 ID") private Long snsId; + // @PrePersist 메서드 정의 (최초 등록시 호출) + @PrePersist + public void prePersist() { + this.regDate = new Date(); // 현재 날짜와 시간으로 등록일 설정 + } + + // @PreUpdate 메서드 정의 (업데이트 시 호출) + @PreUpdate + public void preUpdate() { + this.modifyDate = new Date(); // 현재 날짜와 시간으로 수정일 업데이트 + } + public UsersEditor.UsersEditorBuilder toEditor() { return UsersEditor.builder() .name(name) diff --git a/src/main/java/com/chwipoClova/user/repository/UserRepository.java b/src/main/java/com/chwipoClova/user/repository/UserRepository.java index c39b2ec..1f85dd2 100644 --- a/src/main/java/com/chwipoClova/user/repository/UserRepository.java +++ b/src/main/java/com/chwipoClova/user/repository/UserRepository.java @@ -7,4 +7,6 @@ public interface UserRepository extends JpaRepository { Optional findBySnsTypeAndSnsId(Integer SnsType, Long snsId); + + Optional findByEmailAndSnsType(String email, Integer SnsType); } diff --git a/src/main/java/com/chwipoClova/user/request/UserLoginReq.java b/src/main/java/com/chwipoClova/user/request/UserLoginReq.java index 5c55228..16c4d34 100644 --- a/src/main/java/com/chwipoClova/user/request/UserLoginReq.java +++ b/src/main/java/com/chwipoClova/user/request/UserLoginReq.java @@ -1,4 +1,13 @@ package com.chwipoClova.user.request; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder public class UserLoginReq { + + @Schema(description = "로그인코드", example = "1", name = "code") + private String code; } diff --git a/src/main/java/com/chwipoClova/user/response/UserInfoRes.java b/src/main/java/com/chwipoClova/user/response/UserInfoRes.java new file mode 100644 index 0000000..74d39c5 --- /dev/null +++ b/src/main/java/com/chwipoClova/user/response/UserInfoRes.java @@ -0,0 +1,27 @@ +package com.chwipoClova.user.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +import java.util.Date; + +@Data +@Builder +public class UserInfoRes { + + @Schema(description = "아이디", example = "1", name = "userId") + private Long userId; + + @Schema(description = "이름", example = "홍길동", name = "name") + private String name; + + @Schema(description = "이메일", example = "test@naver.com", name = "email") + private String email; + + @Schema(description = "가입일", example = "2023-12-09T10:13:17.838+00:00", name = "regDate") + private Date regDate; + + @Schema(description = "수정일", example = "2023-12-09T10:13:17.838+00:00", name = "modifyDate") + private Date modifyDate; +} diff --git a/src/main/java/com/chwipoClova/user/service/UserService.java b/src/main/java/com/chwipoClova/user/service/UserService.java index e429b58..5d0e346 100644 --- a/src/main/java/com/chwipoClova/user/service/UserService.java +++ b/src/main/java/com/chwipoClova/user/service/UserService.java @@ -2,6 +2,8 @@ import com.chwipoClova.common.dto.Token; import com.chwipoClova.common.dto.TokenDto; +import com.chwipoClova.common.exception.CommonException; +import com.chwipoClova.common.exception.ExceptionCode; import com.chwipoClova.common.repository.TokenRepository; import com.chwipoClova.common.response.CommonResponse; import com.chwipoClova.common.response.MessageCode; @@ -10,6 +12,8 @@ import com.chwipoClova.user.dto.KakaoUserInfo; import com.chwipoClova.user.entity.User; import com.chwipoClova.user.repository.UserRepository; +import com.chwipoClova.user.request.UserLoginReq; +import com.chwipoClova.user.response.UserInfoRes; import com.chwipoClova.user.response.UserLoginRes; import com.chwipoClova.user.response.UserSnsUrlRes; import jakarta.servlet.http.HttpServletResponse; @@ -21,6 +25,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; @@ -73,6 +78,7 @@ public UserSnsUrlRes getKakaoUrl() { return userSnsUrlRes; } + @Transactional public CommonResponse kakaoLogin(String code, HttpServletResponse response) { KakaoToken kakaoToken = requestAccessToken(code); KakaoUserInfo kakaoUserInfo = requestOauthInfo(kakaoToken); @@ -172,4 +178,21 @@ private void setHeader(HttpServletResponse response, TokenDto tokenDto) { response.addHeader(JwtUtil.ACCESS_TOKEN, tokenDto.getAccessToken()); response.addHeader(JwtUtil.REFRESH_TOKEN, tokenDto.getRefreshToken()); } + + public UserInfoRes selectUserInfo(String email) { + Optional usersInfo = userRepository.findByEmailAndSnsType(email, 1); + if (!usersInfo.isPresent()) { + throw new CommonException(ExceptionCode.USER_NULL.getMessage(), ExceptionCode.USER_NULL.getCode()); + } + + User user = usersInfo.get(); + + return UserInfoRes.builder() + .userId(user.getUserId()) + .email(user.getEmail()) + .name(user.getName()) + .regDate(user.getRegDate()) + .modifyDate(user.getModifyDate()) + .build(); + } } diff --git a/src/test/java/com/chwipoClova/ChwipoClovaApplicationTests.java b/src/test/java/com/chwipoClova/ChwipoClovaApplicationTests.java index df7d09c..be8538e 100644 --- a/src/test/java/com/chwipoClova/ChwipoClovaApplicationTests.java +++ b/src/test/java/com/chwipoClova/ChwipoClovaApplicationTests.java @@ -3,11 +3,19 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import java.util.Date; + @SpringBootTest class ChwipoClovaApplicationTests { @Test void contextLoads() { + + Date a = new Date(); + + System.out.println(a.getTime()); + Date b = new Date(a.getTime() + (30 * 60 * 1000L)); + System.out.println(b); } }