diff --git a/data/.ideavimrc b/data/.ideavimrc new file mode 100644 index 0000000..e0c0002 --- /dev/null +++ b/data/.ideavimrc @@ -0,0 +1 @@ +inoremap jj diff --git a/data/testdb.mv.db b/data/testdb.mv.db new file mode 100644 index 0000000..56337c4 Binary files /dev/null and b/data/testdb.mv.db differ diff --git a/data/vim-shortkey-keyboard.png b/data/vim-shortkey-keyboard.png new file mode 100644 index 0000000..9e2aaee Binary files /dev/null and b/data/vim-shortkey-keyboard.png differ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7454180 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..05679dc --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/repo/simplecaptcha-1.2.1.jar b/repo/simplecaptcha-1.2.1.jar new file mode 100644 index 0000000..9bba7cb Binary files /dev/null and b/repo/simplecaptcha-1.2.1.jar differ diff --git a/src/main/java/com/xit/Application.java b/src/main/java/com/xit/Application.java new file mode 100644 index 0000000..1b9b947 --- /dev/null +++ b/src/main/java/com/xit/Application.java @@ -0,0 +1,81 @@ +package com.xit; + +//import com.xit.core.config.ignore.DataJdbcConfig; + +import com.xit.core.api.CustomBeanNameGenerator; +import com.xit.core.oauth2.config.properties.AppProperties; +import com.xit.core.oauth2.config.properties.CorsProperties; +import com.xit.core.util.Checks; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.web.servlet.ServletComponentScan; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import java.io.IOException; + +/** + * 설정에서 제외하는 경우 + * 자동설정에서 제외할 자동설정 클래스 제외 설정 : Security chain 비활성 + * SpringBootApplication(exclude = {SecurityAutoConfiguration.class, UserDetailsServiceAutoConfiguration.class}) + * excludeFilters 설정 : + * type = FilterType.ASSIGNABLE_TYPE 인 경우 value = {WebMvcConfig.class, ThymeleafWebViewResolverConfig.class} + * type = FilterType.REGEX 인 경우 pattern = {"com.xit.core.config.ignore.*"} 형식으로 + */ +@Slf4j +@SpringBootApplication //(exclude = {SecurityAutoConfiguration.class, UserDetailsServiceAutoConfiguration.class}) +@EnableTransactionManagement +// @WebFilter load +@ServletComponentScan +@EnableConfigurationProperties({ + CorsProperties.class, + AppProperties.class +}) +@ComponentScan( + basePackages = {"com.xit.biz", "com.xit.core"}, + excludeFilters = @ComponentScan.Filter( + type = FilterType.ASPECTJ, + pattern = { + "com.xit.._ignore..*" + //"com..support.auth.jwt..*" + } + ) +) +public class Application { //WebApplicationInitializer { + static final String BEAN_GEN_BASE_PACKAGE = "com.xit.**.controller"; + + /** + * WebFlux main application + * @param args String[] + * @throws IOException + */ + public static void main(String[] args) throws IOException { + log.info("xitApplication Application load start..."); + if(Checks.isEmpty(System.getProperty("spring.profiles.active"))) { + log.error("===================================================================="); + log.error(">>>>>>>>>>>>>> Undefined start option <<<<<<<<<<<<<<"); + log.error(">>>>>>>>>>>>>> -Dspring.profiles.active=local|dev|prd <<<<<<<<<<<<<<"); + log.error("============== xitApplication Application start fail ==============="); + log.error("===================================================================="); + System.exit(-1); + } + SpringApplicationBuilder applicationBuilder = new SpringApplicationBuilder(Application.class); + + // beanName Generator 등록 : API v1, v2 등으로 분류하는 경우 + // Bean 이름 식별시 풀패키지 명으로 식별 하도록 함 + CustomBeanNameGenerator beanNameGenerator = new CustomBeanNameGenerator(); + beanNameGenerator.addBasePackages(BEAN_GEN_BASE_PACKAGE); + applicationBuilder.beanNameGenerator(beanNameGenerator); + + //TODO : 이벤트 실행 시점이 Application context 실행 이전인 경우 리스너 추가 + //application.listeners(new xitCoreApplicationListner()); + applicationBuilder.build().run(args); + + log.info("========================================================================================="); + log.info("========== xitApplication Application load Complete :: active profiles - {} ==========", System.getProperty("spring.profiles.active")); + log.info("========================================================================================="); + } +} diff --git a/src/main/java/com/xit/ServletInitializer.java b/src/main/java/com/xit/ServletInitializer.java new file mode 100644 index 0000000..7b5368b --- /dev/null +++ b/src/main/java/com/xit/ServletInitializer.java @@ -0,0 +1,13 @@ +package com.xit; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +public class ServletInitializer extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(Application.class); + } + +} diff --git a/src/main/java/com/xit/biz/cmm/controller/CmmAddressMgtController.java b/src/main/java/com/xit/biz/cmm/controller/CmmAddressMgtController.java new file mode 100644 index 0000000..50b06bc --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/controller/CmmAddressMgtController.java @@ -0,0 +1,36 @@ +package com.xit.biz.cmm.controller; + +//import com.xit.biz.cmm.domain.CmmAddress; +//import com.xit.biz.cmm.service.impl.CmmAddressMgtService; +//import com.xit.core.api.ApiResponseDto; +//import io.swagger.annotations.Api; +//import io.swagger.annotations.ApiOperation; +//import lombok.RequiredArgsConstructor; +//import org.springframework.web.bind.annotation.*; +// +///** +// * @author Lim, Jong Uk (minuk926) +// * @since 2021-07-16 +// */ +// +//@RestController +//@RequiredArgsConstructor +//@RequestMapping("/api/biz/cmm/addr") +//@Api(tags = "CmmAddressMgtController") +//public class CmmAddressMgtController { +// +// private final CmmAddressMgtService cmmAddressMgtService; +// +// @Operation(description = "주소 등록") +// @PostMapping +// public ResponseEntity save(@RequestBody CmmAddress cmmAddress){ +// return ResponseEntity.ok().body(RestResult.of(cmmAddressMgtService.save(cmmAddress)); +// } +// +// @Operation(description = "주소 조회") +// @GetMapping +// public ResponseEntity findAll(){ +// return ResponseEntity.ok().body(RestResult.of(cmmAddressMgtService.findAll()); +// +// } +//} diff --git a/src/main/java/com/xit/biz/cmm/controller/CmmBoardMgtController.java b/src/main/java/com/xit/biz/cmm/controller/CmmBoardMgtController.java new file mode 100644 index 0000000..00cf096 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/controller/CmmBoardMgtController.java @@ -0,0 +1,108 @@ +package com.xit.biz.cmm.controller; + +import com.xit.biz.cmm.entity.CmmBoard; +import com.xit.biz.cmm.service.ICmmBoardService; +import com.xit.core.api.IRestResponse; +import com.xit.core.api.RestResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; + +/** + *
+ * ResponseEntity(HttpStatus status)
+ * ResponseEntity(MultiValueMap headers, HttpStatus status)
+ * ResponseEntity(@Nullable T body, @Nullable MultiValueMap headers, HttpStatus status)
+ *
+ * ResponseEntity 에 헤더값 set ::
+ * MultiValueMap header = new LinkedMultiValueMap<>();
+ * header.add("token", "xxxx");
+ * header.add("authcode", "xxxxx");
+ * return new ResponseEntity(header, HttpStatus.OK)
+ * 
+ */ + +@Tag(name = "CmmBoardMgtController", description = "게시판 관리") +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/biz/cmm/board") +public class CmmBoardMgtController { + + private final ICmmBoardService cmmBoardService; + + //private CmmBoardMapstruct mapstruct = Mappers.getMapper(CmmBoardMapstruct.class); + + @Operation(summary = "게시판 목록 조회" , description = "등록된 게시판 목록 전체 조회") + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity findAll() { + return RestResponse.of(cmmBoardService.findAll()); + } +/* + Page loadCharactersPage( + @PageableDefault(page = 0, size = 20) + @SortDefault.SortDefaults({ + @SortDefault(sort = "name", direction = Sort.Direction.DESC), + @SortDefault(sort = "id", direction = Sort.Direction.ASC) + }) Pageable pageable) +*/ + + /** + * + * @param pageable + * @return + */ + @Operation(summary = "게시판 목록 조회(페이징)" , description = "등록된 게시판 목록 전체 조회(페이징)") + //@@Parameters({ + // @Parameter(name = "pageNumber", value = "조회할 페이지", required = false), // dataType = "Pageable", paramType = "Integer", defaultValue = "0"), + // @Parameter(name = "pageSize", value = "페이지당 갯수", required = false) //, dataType = "Pageable", paramType = "Integer", defaultValue = "10") + //}) + @GetMapping(value = "/page", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity findAllPage(final Pageable pageable) { + + Page page = cmmBoardService.findAll(pageable); + //log.debug("Page : {}", page); + log.debug("Page number : {}", page.getNumber()); + log.debug("Page size : {}", page.getSize()); + log.debug("Total size : {}", page.getTotalPages()); + log.debug("Total count : {}", page.getTotalElements()); + log.debug("Prev : {}", page.hasPrevious()); + log.debug("Next : {}", page.hasNext()); + List list = page.getContent(); + return RestResponse.of(page); + } + + @Operation(summary = "게시물 상세 조회" , description = "등록된 게시물 상세") + @GetMapping(value = "/{boardId}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity findByBoardId(@PathVariable Long boardId) { + Optional board = cmmBoardService.findById(boardId); + return RestResponse.of(board.get()); + } + + @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity saveBoard(@RequestBody CmmBoard board) { + return RestResponse.of(cmmBoardService.save(board)); + } + + + @PutMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity updateBoard(@RequestBody CmmBoard board) { + return RestResponse.of(cmmBoardService.update(board)); + } + + @DeleteMapping(value="/{boardId}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity deleteBoard(@PathVariable Long boardId) { + cmmBoardService.deleteById(boardId); + return RestResponse.of(HttpStatus.NO_CONTENT); + } +} diff --git a/src/main/java/com/xit/biz/cmm/controller/CmmBoardWebController.java b/src/main/java/com/xit/biz/cmm/controller/CmmBoardWebController.java new file mode 100644 index 0000000..fe1445f --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/controller/CmmBoardWebController.java @@ -0,0 +1,158 @@ +package com.xit.biz.cmm.controller; + +//import com.xit.biz.adm.mapstruct.BoardJpaMapper; + +import com.xit.biz.cmm.entity.CmmBoard; +import com.xit.biz.cmm.service.ICmmBoardService; +import com.xit.core.api.IRestResponse; +import com.xit.core.api.RestResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Optional; + +/** + *
+ * ResponseEntity(HttpStatus status)
+ * ResponseEntity(MultiValueMap headers, HttpStatus status)
+ * ResponseEntity(@Nullable T body, @Nullable MultiValueMap headers, HttpStatus status)
+ *
+ * ResponseEntity 에 헤더값 set ::
+ * MultiValueMap header = new LinkedMultiValueMap<>();
+ * header.add("token", "xxxx");
+ * header.add("authcode", "xxxxx");
+ * return new ResponseEntity(header, HttpStatus.OK)
+ * 
+ */ +@Slf4j +@Controller +@RequiredArgsConstructor +@RequestMapping("/web/biz/cmm/board") +public class CmmBoardWebController { + + private final ICmmBoardService cmmBoardService; + + //private CmmBoardMapstruct mapstruct = Mappers.getMapper(CmmBoardMapstruct.class); + + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public String boardForm(Model model) { + return "/biz/adm/boardManagement"; + } +/* + Page loadCharactersPage( + @PageableDefault(page = 0, size = 20) + @SortDefault.SortDefaults({ + @SortDefault(sort = "name", direction = Sort.Direction.DESC), + @SortDefault(sort = "id", direction = Sort.Direction.ASC) + }) Pageable pageable) +*/ + + /** + * + * @param pageable Pageable + * @return String + */ + @GetMapping(value = "/page") //, produces = MediaType.APPLICATION_JSON_VALUE) + public String findAllPage(final Pageable pageable, Model model) { + + Page page = cmmBoardService.findAll(pageable); + //log.debug("Page : {}", page); + log.debug("Page number : {}", page.getNumber()); + log.debug("Page size : {}", page.getSize()); + log.debug("Total size : {}", page.getTotalPages()); + log.debug("Total count : {}", page.getTotalElements()); + log.debug("Prev : {}", page.hasPrevious()); + log.debug("Next : {}", page.hasNext()); + List list = page.getContent(); + model.addAttribute("data", list); + model.addAttribute("pagination", page.getPageable()); + model.addAttribute("title", "djdjjdjdjjd"); + return "biz/adm/boardManagement"; + } + +// @GetMapping(value = "/page", produces = MediaType.APPLICATION_JSON_VALUE) +// public ResponseEntity> findByBoardIdGreaterThan(PageRequest pageRequest) { +// Page page = boardService.findByBoardIdGreaterThan(dto, pageRequest); +// log.debug("Page : {}", page); +// log.debug("Page number : {}", page.getNumber()); +// log.debug("Page size : {}", page.getSize()); +// log.debug("Total size : {}", page.getTotalPages()); +// log.debug("Total count : {}", page.getTotalElements()); +// log.debug("Prev : {}", page.hasPrevious()); +// log.debug("Next : {}", page.hasNext()); +// return ResponseEntity.ok(page); +// } + + @GetMapping(value = "/{boardId}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity findByBoardId(@PathVariable final Long boardId) { + Optional board = cmmBoardService.findById(boardId); + return RestResponse.of(board.get()); + } + + @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity saveBoard(@RequestBody CmmBoard board) { + return RestResponse.of(cmmBoardService.save(board)); + } + + + @PutMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity updateBoard(@RequestBody CmmBoard board) { + return RestResponse.of(cmmBoardService.update(board)); + } + + @DeleteMapping(value="/{boardId}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity deleteBoard(@PathVariable final Long boardId) { + cmmBoardService.deleteById(boardId); + return RestResponse.of(HttpStatus.NO_CONTENT); + } + + + + + + @PostMapping(value = "/api", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity saveBoardApi(@RequestBody CmmBoard board) { + return RestResponse.of(cmmBoardService.save(board)); + //return ResponseEntity.ok(boardService.save(board)); + } + + @GetMapping(value = "/api", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity findAllApi() { + return RestResponse.of(cmmBoardService.findAll()); + //return ResponseEntity.ok(boardService.findAll(), HttpStatus.OK); + } + /* + Page loadCharactersPage( + @PageableDefault(page = 0, size = 20) + @SortDefault.SortDefaults({ + @SortDefault(sort = "name", direction = Sort.Direction.DESC), + @SortDefault(sort = "id", direction = Sort.Direction.ASC) + }) Pageable pageable) + */ + @GetMapping(value = "/api/page", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity findAllPageApi(final Pageable pageable) { + +// Page page = boardService.findAllPage(pageable); +// //log.debug("Page : {}", page); +// log.debug("Page number : {}", page.getNumber()); +// log.debug("Page size : {}", page.getSize()); +// log.debug("Total size : {}", page.getTotalPages()); +// log.debug("Total count : {}", page.getTotalElements()); +// log.debug("Prev : {}", page.hasPrevious()); +// log.debug("Next : {}", page.hasNext()); +// List list = page.getContent(); +// return ResponseEntity.ok(page); + + return RestResponse.of(cmmBoardService.findAll(pageable)); + } + +} diff --git a/src/main/java/com/xit/biz/cmm/controller/CmmCodeMgtController.java b/src/main/java/com/xit/biz/cmm/controller/CmmCodeMgtController.java new file mode 100644 index 0000000..1f5e335 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/controller/CmmCodeMgtController.java @@ -0,0 +1,115 @@ +package com.xit.biz.cmm.controller; + +import com.xit.biz.cmm.entity.*; +import com.xit.biz.cmm.entity.ids.CmmCodeSIds; +import com.xit.biz.cmm.dto.CmmCodeDto; +import com.xit.biz.cmm.service.ICmmCodeService; +import com.xit.core.api.IRestResponse; +import com.xit.core.api.RestResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Nonnull; + +@Tag(name = "CmmCodeMgtController", description = "코드 관리") +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/biz/cmm/code") +public class CmmCodeMgtController { + private final ICmmCodeService cmmCodeService; + + @Operation(summary = "콤보코드조회" , description = "콤보코드를 조회") + @GetMapping("/combo") + public ResponseEntity findComboCodes(@Nonnull final CmmCodeSIds searchKeyDto) { + Assert.notNull(searchKeyDto, "조회할 콤보코드 대상이 없습니다."); + Assert.notNull(searchKeyDto.getCodeGrpId(), "조회할 대분류 코드를 선택해 주세요."); + return RestResponse.of(cmmCodeService.findComboCodes(searchKeyDto)); + } + + @Operation(summary = "코드그룹 목록 조회") + @Parameters({ + @Parameter(in = ParameterIn.QUERY, name = "codeGrpId", description = "코드그룹ID", required = false, example = " "), + @Parameter(in = ParameterIn.QUERY, name = "codeNm", description = "코드그룹명", required = false, example = " "), + @Parameter(name = "codeCd", hidden = true), + @Parameter(name = "codeMeta1", hidden = true), + @Parameter(name = "codeMeta2", hidden = true), + @Parameter(name = "codeMeta3", hidden = true), + @Parameter(name = "codeMeta4", hidden = true), + @Parameter(name = "codeMeta5", hidden = true), + @Parameter(name = "createdBy", hidden = true), + @Parameter(name = "modifiedBy", hidden = true), + @Parameter(name = "createdDate", hidden = true), + @Parameter(name = "modifiedDate", hidden = true), + @Parameter(in = ParameterIn.QUERY, name = "page", description = "페이지", required = true, example = "0"), + @Parameter(in = ParameterIn.QUERY, name = "size", description = "페이지당갯수", required = true, example = "10") + //@Parameter(in = ParameterIn.QUERY, name = "sort", description = "정렬", required = true, example = "codeOrdr"), + }) + @GetMapping("/grp") + public ResponseEntity findCmmCodeGrps( + @Parameter(hidden = true) + final CmmCodeDto codeDto, + @Parameter(hidden = true) + @Nonnull final Pageable pageable) { + return RestResponse.of(cmmCodeService.findCmmCodeGrps(codeDto, pageable)); + } + + @Operation(summary = "코드 목록 조회") + @Parameters({ + @Parameter(in = ParameterIn.QUERY, name = "codeGrpId", description = "코드그룹ID", required = true, example = "G_CODE"), + @Parameter(in = ParameterIn.QUERY, name = "codeLcd", description = "대분류코드", required = false, example = " "), + @Parameter(in = ParameterIn.QUERY, name = "codeMcd", description = "중분류코드", required = false, example = " "), + @Parameter(in = ParameterIn.QUERY, name = "codeCd", description = "코드", required = false, example = " "), + @Parameter(in = ParameterIn.QUERY, name = "codeNm", description = "코드명", required = false, example = " "), + @Parameter(in = ParameterIn.QUERY, name = "codeMeta1", description = "코드메타1", required = false, example = " "), + @Parameter(in = ParameterIn.QUERY, name = "codeMeta2", description = "코드메타2", required = false, example = " "), + @Parameter(in = ParameterIn.QUERY, name = "codeMeta3", description = "코드메타3", required = false, example = " "), + @Parameter(in = ParameterIn.QUERY, name = "codeMeta4", description = "코드메타4", required = false, example = " "), + @Parameter(in = ParameterIn.QUERY, name = "codeMeta5", description = "코드메타5", required = false, example = " "), + @Parameter(in = ParameterIn.QUERY, name = "createdBy", hidden = true), + @Parameter(in = ParameterIn.QUERY, name = "modifiedBy", hidden = true), + @Parameter(in = ParameterIn.QUERY, name = "createdDate", hidden = true), + @Parameter(in = ParameterIn.QUERY, name = "modifiedDate", hidden = true) + }) + @GetMapping + public ResponseEntity findCmmCodes(@Parameter(hidden = true) @Nonnull final CmmCodeDto codeDto) { + return RestResponse.of(cmmCodeService.findCmmCodes(codeDto)); + } + + @Operation(summary = "코드그룹저장" , description = "코드그룹저장") + @PostMapping("/grp") + public ResponseEntity saveCmmCodeGrp(@RequestBody @Nonnull CmmCodeGrp cmmCodeGrp) { + return RestResponse.of(cmmCodeService.saveCmmCodeGrp(cmmCodeGrp)); + } + +// @Operation(summary = "대분류코드저장" , description = "대분류코드저장") +// @PostMapping("/lcode") +// public ResponseEntity saveCmmCodeL(@RequestBody @Nonnull CmmCodeL cmmCodeL) { +// return RestResult.of(cmmCodeService.saveCmmCodeL(cmmCodeL)); +// } +// +// @Operation(summary = "중분류코드저장" , description = "중분류코드저장") +// @PostMapping("/mcode") +// public ResponseEntity saveCmmCodeM(@RequestBody @Nonnull CmmCodeM cmmCodeM) { +// return RestResult.of(cmmCodeService.saveCmmCodeM(cmmCodeM)); +// } +// +// @Operation(summary = "소분류코드저장" , description = "소분류코드저장") +// @PostMapping("/scode") +// public ResponseEntity saveCmmCodeS(@RequestBody @Nonnull CmmCodeS cmmCodeS) { +// return RestResult.of(cmmCodeService.saveCmmCodeS(cmmCodeS)); +// } + + @Operation(summary = "코드 저장") + @PostMapping + public ResponseEntity saveCmmCode(@RequestBody @Nonnull CmmCodeDto cmmCodeDto) { + return RestResponse.of(cmmCodeService.saveCmmCode(cmmCodeDto)); + } +} diff --git a/src/main/java/com/xit/biz/cmm/controller/CmmFileMgtController.java b/src/main/java/com/xit/biz/cmm/controller/CmmFileMgtController.java new file mode 100644 index 0000000..6ddb7a5 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/controller/CmmFileMgtController.java @@ -0,0 +1,70 @@ +package com.xit.biz.cmm.controller; + +import com.xit.biz.cmm.entity.CmmFileMst; +import com.xit.biz.cmm.dto.CmmFileDto; +import com.xit.biz.cmm.service.ICmmFileService; +import com.xit.core.api.IRestResponse; +import com.xit.core.api.RestResponse; +import com.xit.core.util.AssertUtils; +import com.xit.core.util.Checks; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.lang.NonNull; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Nonnull; + +@Tag(name = "CmmFileMgtController", description = "파일 관리") +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/biz/cmm/file") +public class CmmFileMgtController { + private final ICmmFileService cmmFileService; + + @Operation(summary = "파일 조회" , description = "등록된 파일 조회") + @GetMapping("/{id}") + public ResponseEntity findFiles(@PathVariable("id") @NonNull final String fileMstId) { + AssertUtils.isTrue(!Checks.isEmpty(fileMstId), "대상 파일[fileMstId]을 선택해 주세요."); + return RestResponse.of(cmmFileService.findFiles(fileMstId)); + } + + //@Operation(summary = "파일 저장" , description = "파일 저장") + //@PostMapping() + public ResponseEntity saveFiles2(CmmFileMst cmmFileMst, @RequestParam("files") MultipartFile[] files) { + AssertUtils.isTrue(!Checks.isEmpty(cmmFileMst), "파일 정보가 존재하지 않습니다."); + AssertUtils.isTrue(!Checks.isEmpty(cmmFileMst.getFileCtgCd()), "파일 구분 코드[fileCtgCd] 정보가 존재하지 않습니다."); + AssertUtils.isTrue(!Checks.isEmpty(cmmFileMst.getFileBizId()), "파일 업무 ID[fileBizId] 정보가 존재하지 않습니다."); + AssertUtils.isTrue(!Checks.isEmpty(files), "대상 파일이 존재하지 않습니다."); + return RestResponse.of(cmmFileService.saveFiles(cmmFileMst, files)); + +// RedirectAttributes redirectAttributes +// redirectAttributes.addFlashAttribute("message", +// "You successfully uploaded " + file.getOriginalFilename() + "!"); +// +// return "redirect:/"; + } + + @Operation(summary = "파일 저장" , description = "파일 저장") + @PostMapping(consumes = "multipart/form-data") + public ResponseEntity saveFiles(@ModelAttribute @Nonnull CmmFileDto cmmFileDto) { + AssertUtils.isTrue(!Checks.isEmpty(cmmFileDto), "파일 정보가 존재하지 않습니다."); + AssertUtils.isTrue(!Checks.isEmpty(cmmFileDto.getFileCtgCd()), "파일 구분 코드[fileCtgCd] 정보가 존재하지 않습니다."); + AssertUtils.isTrue(!Checks.isEmpty(cmmFileDto.getFileBizId()), "파일 업무 ID[fileBizId] 정보가 존재하지 않습니다."); + AssertUtils.isTrue(!Checks.isEmpty(cmmFileDto.getFiles()), "대상 파일이 존재하지 않습니다."); + CmmFileMst cmmFileMst = CmmFileMst.builder() + .fileMstId(cmmFileDto.getFileMstId()) + .fileCtgCd(cmmFileDto.getFileCtgCd()) + .fileBizId(cmmFileDto.getFileBizId()) + .build(); + return RestResponse.of(cmmFileService.saveFiles(cmmFileMst, cmmFileDto.getFiles())); + +// RedirectAttributes redirectAttributes +// redirectAttributes.addFlashAttribute("message", +// "You successfully uploaded " + file.getOriginalFilename() + "!"); +// +// return "redirect:/"; + } +} diff --git a/src/main/java/com/xit/biz/cmm/controller/CmmRoleMenuMgtController.java b/src/main/java/com/xit/biz/cmm/controller/CmmRoleMenuMgtController.java new file mode 100644 index 0000000..c5dd931 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/controller/CmmRoleMenuMgtController.java @@ -0,0 +1,53 @@ +package com.xit.biz.cmm.controller; + +import com.xit.biz.cmm.service.ICmmRoleMenuService; +import com.xit.core.api.IRestResponse; +import com.xit.core.api.RestResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "CmmRoleMenuMgtController", description = "권한별 메뉴 관리") +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/biz/cmm/role/menu") +public class CmmRoleMenuMgtController { + private final ICmmRoleMenuService cmmRoleUserService; + + @Operation(description = "권한별 메뉴 목록") + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity findAll() { + + return RestResponse.of(cmmRoleUserService.findAll()); + + } + +// @Operation(description = "권한 메뉴 목록 조회") +// @GetMapping(value="/{roleId}", produces = MediaType.APPLICATION_JSON_VALUE) +// public ResponseEntity findByCmmUserId( +// @Parameter(name="roleId", description = "권한ID", example = "") +// @PathVariable final String cmmUserId) { +// return RestResult.of(cmmRoleUserService.findById(cmmUserId)); +// } +// +// @Operation(description = "권한 메뉴 추가") +// @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE) +// public ResponseEntity save(@RequestBody CmmRoleUer cmmRoleUer) { +// +// return RestResult.of(cmmRoleUserService.save(cmmRoleUer)); +// +// } +// +// @Operation(description = "권한 메뉴 삭제") +// @DeleteMapping(value="/{roleId}", produces = MediaType.APPLICATION_JSON_VALUE) +// public ResponseEntity deleteByCmmUserId(@PathVariable final String cmmUserId) { +// cmmRoleUserService.deleteById(cmmUserId); +// return RestResult.of(HttpStatus.OK); +// +// } +} diff --git a/src/main/java/com/xit/biz/cmm/controller/CmmRoleMgtController.java b/src/main/java/com/xit/biz/cmm/controller/CmmRoleMgtController.java new file mode 100644 index 0000000..24cbd1d --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/controller/CmmRoleMgtController.java @@ -0,0 +1,78 @@ +package com.xit.biz.cmm.controller; + +import com.xit.biz.cmm.entity.CmmRole; +import com.xit.biz.cmm.service.ICmmRoleService; +import com.xit.biz.cmm.service.ICmmRoleUserService; +import com.xit.core.api.IRestResponse; +import com.xit.core.api.RestResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "CmmRoleMgtController", description = "권한 관리") +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/biz/cmm/role") +public class CmmRoleMgtController { + private final ICmmRoleService cmmRoleService; + private final ICmmRoleUserService cmmRoleUserService; + + @Operation(summary = "권한 목록 조회") + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity findAll() { + + return RestResponse.of(cmmRoleService.findAll()); + + } + +// @Operation(summary = "권한 목록 조회") +// @GetMapping(value="/page", produces = MediaType.APPLICATION_JSON_VALUE) +// public ResponseEntity findAllPage(final Pageable pageable) { +// +// Page page = cmmRoleRepository.findAll(PageRequest.of(pageable.getPageNumber(), pageable.getPageSize())); +// return ResponseEntity.ok().body(RestResult.of(page); +// +// } + + @Operation(summary = "권한 정보 조회") + @GetMapping(value="/{roleId}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity findByRoleId(@PathVariable final String roleId) { + return RestResponse.of(cmmRoleService.findById(roleId)); + } + + @Operation(summary = "권한 추가") + @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity save(@RequestBody CmmRole cmmRole) { + + return RestResponse.of(cmmRoleService.save(cmmRole)); + + } + + @Operation(summary = "궘한 정보 변경") + @PutMapping(value="/{roleId}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity modify(@PathVariable final String roleId, @RequestBody CmmRole cmmRoleEntity) { + return RestResponse.of(cmmRoleService.save(cmmRoleEntity)); + } + + @Operation(summary = "권한 삭제") + @DeleteMapping(value="/{roleId}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity deleteByRoleId(@PathVariable final String roleId) { + cmmRoleService.deleteById(roleId); + return RestResponse.of(HttpStatus.OK); + + } + +// @Operation(summary = "권한그룹 사용자 목록 조회") +// @GetMapping(value="/user/{roleId}", produces = MediaType.APPLICATION_JSON_VALUE) +// public ResponseEntity findUserMstByroleId( +// @Parameter(name = "roleId", description = "권한그룹 ID", example = "1") +// @PathVariable final String roleId) { +// return RestResult.of(cmmRoleUserService.findById(roleId)); +// } +} diff --git a/src/main/java/com/xit/biz/cmm/controller/CmmRoleUserMgtController.java b/src/main/java/com/xit/biz/cmm/controller/CmmRoleUserMgtController.java new file mode 100644 index 0000000..e81c5b1 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/controller/CmmRoleUserMgtController.java @@ -0,0 +1,63 @@ +package com.xit.biz.cmm.controller; + +import com.xit.biz.cmm.entity.CmmRoleUer; +import com.xit.biz.cmm.service.ICmmRoleUserService; +import com.xit.core.api.IRestResponse; +import com.xit.core.api.RestResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "CmmRoleUserMgtController", description = "권한별 사용자 관리") +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/biz/cmm/role/user") +public class CmmRoleUserMgtController { + private final ICmmRoleUserService cmmRoleUserService; + + @Operation(description = "사용자 권한 목록 조회") + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity findAll() { + + return RestResponse.of(cmmRoleUserService.findAll()); + + } + +// @Operation(description = "사용자 권한 목록 조회") +// @GetMapping(value="/page", produces = MediaType.APPLICATION_JSON_VALUE) +// public ResponseEntity findAllPage(final Pageable pageable) { +// +// Page page = cmmUserRoleRepository.findAll(PageRequest.of(pageable.getPageNumber(), pageable.getPageSize())); +// return RestResult.of(page); +// +// } + +// @Operation(description = "사용자 권한 그룹 목록 조회") +// @GetMapping(value="/{cmmUserId}", produces = MediaType.APPLICATION_JSON_VALUE) +// public ResponseEntity findByCmmUserId( +// @Parameter(name="cmmUserId", description = "사용자 ID(PK)", example = "1") +// @PathVariable final String cmmUserId) { +// return RestResult.of(cmmRoleUserService.findById(cmmUserId)); +// } + + @Operation(description = "사용자 권한 추가") + @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity save(@RequestBody CmmRoleUer cmmRoleUer) { + + return RestResponse.of(cmmRoleUserService.save(cmmRoleUer)); + + } + +// @Operation(description = "사용자 권한 삭제") +// @DeleteMapping(value="/{roleId}", produces = MediaType.APPLICATION_JSON_VALUE) +// public ResponseEntity deleteByCmmUserId(@PathVariable final String cmmUserId) { +// cmmRoleUserService.deleteById(cmmUserId); +// return RestResult.of(HttpStatus.OK); +// +// } +} diff --git a/src/main/java/com/xit/biz/cmm/controller/CmmUserMgtController.java b/src/main/java/com/xit/biz/cmm/controller/CmmUserMgtController.java new file mode 100644 index 0000000..cd82e86 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/controller/CmmUserMgtController.java @@ -0,0 +1,120 @@ +package com.xit.biz.cmm.controller; + +import com.xit.biz.cmm.dto.struct.CmmUserMapstruct; +import com.xit.biz.cmm.entity.CmmUser; +import com.xit.biz.cmm.dto.CmmUserDto; +import com.xit.biz.cmm.service.ICmmRoleUserService; +import com.xit.biz.cmm.service.ICmmUserService; +import com.xit.core.api.IRestResponse; +import com.xit.core.api.RestResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.mapstruct.factory.Mappers; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; + +@Tag(name = "CmmUserMgtController", description = "사용자 관리") +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/biz/cmm/user") +public class CmmUserMgtController { + private final ICmmUserService cmmUserService; + private final ICmmRoleUserService cmmRoleUerService; + private final PasswordEncoder encoder; + + private CmmUserMapstruct cmmUserMapstruct = Mappers.getMapper(CmmUserMapstruct.class); + + @Operation(summary = "사용자 전체 목록 조회" , description = "등록된 사용자 전체 목록 조회") + @Parameters({ + @Parameter(in = ParameterIn.QUERY, name = "page", description = "페이지", required = true, example = "1"), + @Parameter(in = ParameterIn.QUERY, name = "size", description = "페이지당갯수", required = true, example = "10") + }) + @GetMapping(value="/page1", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity findAll(@Parameter(hidden = true) final Pageable pageable) { + //Page page = cmmUserRepository.findAll(PageRequest.of(paging.getPage(), paging.getSize())); + return RestResponse.of(cmmUserService.findAll(pageable)); + + } + + @Operation(summary = "사용자 목록 조회" , description = "등록된 사용자 목록 조회") +// @Parameter(description="CmmUserDto to add. Cannot null or empty.", +// required=true, schema=@Schema(implementation = CmmUserDto.class)) +// @Parameter(description="Pageable to add. Cannot null or empty.", +// required=true, schema=@Schema(implementation = Pageable.class)) + @Parameters({ + @Parameter(in = ParameterIn.QUERY, name = "userId", description = "사용자id", required = false, example = " "), + @Parameter(in = ParameterIn.QUERY, name = "userName", description = "사용자이름", required = false, example = " "), + @Parameter(in = ParameterIn.QUERY, name = "userMbl", description = "전화(핸드폰)번호", required = false, example = " "), + @Parameter(name = "userEmail", hidden = true), + @Parameter(name = "userPswd", hidden = true), + @Parameter(name = "authority", hidden = true), + @Parameter(in = ParameterIn.QUERY, name = "page", description = "페이지", required = true, example = "1"), + @Parameter(in = ParameterIn.QUERY, name = "size", description = "페이지당갯수", required = true, example = "10") + //@Parameter(in = ParameterIn.QUERY, name = "sort", description = "정렬", required = true, example = "codeOrdr"), + }) + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity findCmmUsers( + @Parameter(hidden = true) + @ModelAttribute("cmmUserDto") + final CmmUserDto cmmUserDto, + @Parameter(hidden = true) + final Pageable pageable) { + return RestResponse.of(cmmUserService.findCmmUsers(cmmUserMapstruct.toEntity(cmmUserDto), pageable)); + } + + @Operation(summary = "사용자 정보 조회" , description = "사용자 정보 조회") + @Parameter(in = ParameterIn.PATH, name = "cmmUserId", description = "사용자 ID(PK)", example = "1") + @GetMapping(value="/{cmmUserId}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity findByCmmUserId( + @PathVariable final String cmmUserId) { + return RestResponse.of(cmmUserService.findById(cmmUserId)); + } + + @Operation(summary = "사용자 등록" , description = "신규 사용자 등록") + @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity save(@RequestBody @Valid CmmUser cmmUser) { + cmmUser.setPassword(encoder.encode(cmmUser.getPassword())); + return RestResponse.of(cmmUserService.save(cmmUser)); + + } + + @Operation(summary = "사용자 정보 변경" , description = "사용자 정보 변경") + @PutMapping(value="/{cmmUserId}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity modify( + @Parameter(name = "cmmUserId", description = "사용자 ID(PK)", example = "1", required = true) + @PathVariable final Long cmmUserId, @RequestBody CmmUser cmmUser) { + return RestResponse.of(cmmUserService.save(cmmUser)); + } + + @Operation(summary = "사용자 삭제" , description = "사용자 제거") + @DeleteMapping(value="/{cmmUserId}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity deleteById( + @Parameter(name = "cmmUserId", description = "사용자 ID(PK)", example = "1") + @PathVariable final String cmmUserId) { + cmmUserService.deleteByCmmUserId(cmmUserId); + return RestResponse.of(HttpStatus.OK); + + } + + @Operation(summary = "사용자 권한 그룹 목록 조회") + @GetMapping(value="/role/{cmmUserId}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity findRoleGroupByCmmUserId( + @Parameter(name = "cmmUserId", description = "사용자 ID(PK)", example = "1") + @PathVariable final Long cmmUserId) { + return RestResponse.of(cmmRoleUerService.findById(cmmUserId)); + } + + +} diff --git a/src/main/java/com/xit/biz/cmm/controller/CmmUserWebController.java b/src/main/java/com/xit/biz/cmm/controller/CmmUserWebController.java new file mode 100644 index 0000000..840ba87 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/controller/CmmUserWebController.java @@ -0,0 +1,33 @@ +package com.xit.biz.cmm.controller; + +import com.xit.biz.cmm.service.ICmmUserService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@Slf4j +@Controller +@RequiredArgsConstructor +@RequestMapping("/web/biz/cmm/user") +public class CmmUserWebController { + + private final ICmmUserService cmmUserService; + + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public String userForm() { + return "/biz/cmm/userManagement"; + } + +// @Operation(summary = "사용자 정보 조회", description = "사용자 정보 조회") +// @GetMapping(value = "/{cmmUserId}", produces = MediaType.APPLICATION_JSON_VALUE) +// public Mono findByCmmUserId(@PathVariable final Long cmmUserId) { +// CmmUser cmmUser = cmmUserRepository.findByCmmUserId(cmmUserId); +// return Mono.just( +// Rendering.view("/biz/cmm/userManagement.html") +// .modelAttribute("data", cmmUserRepository.findByCmmUserId(cmmUserId)) +// .build()); +// } +} diff --git a/src/main/java/com/xit/biz/cmm/dto/CmmBoardDto.java b/src/main/java/com/xit/biz/cmm/dto/CmmBoardDto.java new file mode 100644 index 0000000..f282525 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/dto/CmmBoardDto.java @@ -0,0 +1,33 @@ +package com.xit.biz.cmm.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.springframework.data.domain.Sort; + +/** + * MapStruct 사용을 위해 아래 사항 반드시 지킬 것 + * 1. @NoArgsConstructor + * 2. @Builder 사용 + * --> @Setter이 필요하나 사용을 지양하는 추세이므로 Builder 방식으로 generate 되도록 한다 + */ +@Schema(name = "CmmBoardDto", description = "게시판 관리용 DTO") +@Getter +@NoArgsConstructor +@AllArgsConstructor // Builder 사용시 필요 +@Builder +public class CmmBoardDto { + + @Schema(name = "board ID") + private Long boardId; + private String ctgCd; + private String title; + private String content; + private String fileBizId; + private String fileId; + private String filePath; + + private int pageNum; + private int pageSize; + private Sort.Direction direction; + private String[] sortArr; +} diff --git a/src/main/java/com/xit/biz/cmm/dto/CmmCodeDto.java b/src/main/java/com/xit/biz/cmm/dto/CmmCodeDto.java new file mode 100644 index 0000000..fc1ac0a --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/dto/CmmCodeDto.java @@ -0,0 +1,72 @@ +package com.xit.biz.cmm.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.io.Serializable; + +/** + * MapStruct 사용을 위해 아래 사항 반드시 지킬 것 + * 1. @NoArgsConstructor + * 2. @Builder 사용 + * --> @Setter이 필요하나 사용을 지양하는 추세이므로 Builder 방식으로 generate 되도록 한다 + */ +@Schema(name = "CmmCodeDto", description = "공통코드 DTO") +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class CmmCodeDto implements Serializable { + private static final long SerialVersionUID = 1L; + + @Schema(title = "코드그룹ID", example = " ", description = "대분류코드조회시 필수") + private String codeGrpId; + + @Schema(title = "대분류코드", example = " ", description = "중분류코드조회시 필수") + private String codeLcd; + + @Schema(title = "중분류코드", example = " ", description = "소분류코드조회시 필수") + private String codeMcd; + + @Schema(title = "코드", example = " ") + private String codeCd; + + @Schema(title = "코드명", example = " ") + private String codeNm; + + @Schema(title = "코드설명", example = " ") + private String codeRemark; + + @Schema(title = "코드메타1", example = " ") + private String codeMeta1; + + @Schema(title = "코드메타2", example = " ") + private String codeMeta2; + + @Schema(title = "코드메타3", example = " ") + private String codeMeta3; + + @Schema(title = "코드메타4", example = " ") + private String codeMeta4; + + @Schema(title = "코드메타5", example = " ") + private String codeMeta5; + + @Schema(title = "코드정렬순서", example = "99") + private Integer codeOrdr; + + @Schema(title = "사용여부", example = "Y") + private String useYn; + + @Schema(title = "생성자", example = " ") + private String createdBy; + + @Schema(title = "생성일시", example = " ") + private String createdDate; + + @Schema(title = "변경자", example = " ") + private String modifiedBy; + + @Schema(title = "변경일시", example = " ") + private String modifiedDate; +} diff --git a/src/main/java/com/xit/biz/cmm/dto/CmmFileDto.java b/src/main/java/com/xit/biz/cmm/dto/CmmFileDto.java new file mode 100644 index 0000000..d1d8bdb --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/dto/CmmFileDto.java @@ -0,0 +1,34 @@ +package com.xit.biz.cmm.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.Serializable; + +/** + * MapStruct 사용을 위해 아래 사항 반드시 지킬 것 + * 1. @NoArgsConstructor + * 2. @Builder 사용 + * --> @Setter이 필요하나 사용을 지양하는 추세이므로 Builder 방식으로 generate 되도록 한다 + */ +@Schema(name = "CmmFileDto", description = "파일 관리용 DTO") +@Getter +@NoArgsConstructor +@AllArgsConstructor // Builder 사용시 필요 +@Builder +public class CmmFileDto implements Serializable { + private static final long SerialVersionUID = 1L; + + @Schema(title = "파일 마스터 ID", description = "변경시 nonNull") + private String fileMstId; + + @Schema(required = true, title = "파일구분코드", example = "BOARD", description = "파일분류구분코드") + private String fileCtgCd; + + @Schema(required = true, title = "파일업무ID", example = "BOARD001", description = "업무테이블 PK조합") + private String fileBizId; + + @Schema(required = true, title = "파일객체", example = "null", description = "업로드 파일 객체") + private MultipartFile[] files; +} diff --git a/src/main/java/com/xit/biz/cmm/dto/CmmUserDto.java b/src/main/java/com/xit/biz/cmm/dto/CmmUserDto.java new file mode 100644 index 0000000..9913b4d --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/dto/CmmUserDto.java @@ -0,0 +1,99 @@ +package com.xit.biz.cmm.dto; + +//import com.xit.core.annotation.EnumTypeValid; +import com.xit.biz.cmm.entity.CmmUser; +import com.xit.core.support.valid.EnumNamePattern; +import com.xit.core.support.valid.Enums; +import com.xit.core.oauth2.oauth.entity.ProviderType; +import com.xit.core.oauth2.oauth.entity.RoleType; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.springframework.security.crypto.password.PasswordEncoder; +//import org.hibernate.validator.constraints.Email; +//import org.hibernate.validator.constraints.NotEmpty; + +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.Pattern; +import java.io.Serializable; + +/** + * MapStruct 사용을 위해 아래 사항 반드시 지킬 것 + * 1. @NoArgsConstructor + * 2. @Builder 사용 + * --> @Setter이 필요하나 사용을 지양하는 추세이므로 Builder 방식으로 generate 되도록 한다 + */ +@Schema(name = "CmmUserDto", description = "사용자 Dto") //, parent = AuditEntity.class) +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class CmmUserDto implements Serializable { //extends RepresentationModel implements Serializable { + + @Schema(title = "사용자ID(PK)", example = " ", description = "실제 PK(unique)") + private String cmmUserId; + + @Schema(title = "사용자ID(실제ID)", example = "minuk926", description = "논리적인 PK(unique)") + @NotEmpty(message = "{userId.not.empty}") + private String userId; + + @Schema(title = "사용자 이름", example = "홍길동") + @NotEmpty(message = "{userName.not.empty}") + private String userName; + + @Schema(title = "사용자 비밀번호", example = "Minuk926!@") + @Pattern( + regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}", + message = "비밀번호는 영문 대소문자와 숫자, 특수기호가 적어도 1개 이상씩 포함된 8자 ~ 20자의 비밀번호여야 합니다." + ) + private String password; + + @Schema(title = "전화번호", example = "01012341234") + @Pattern(regexp = "[0-9]{11}", message = "{mobile.pattern}") + private String userMbl; + + @Schema(title = "사용자 이메일", example = "a@b.com", description = "unique") + @NotBlank(message = "메일 주소는 필수 입니다") + @Email(message = "{mail.pattern}") + private String email; + + @Schema(required = true, title = "권한", example = "USER", description = "권한 - ROLE_USER, ROLE_ADMIN") + @Enumerated(EnumType.STRING) + @EnumNamePattern(regexp = "ADMIN|USER|GUEST", message = "{auth.user.pattern.RoleType}") + private RoleType roleType; + + ////////////////////////// Socail 사용시 //////////////////////////////////////////////////////////////// + @Schema(required = true, title = "ProviderType Type", example = "LOCAL", description = "SNS TYPE") + @Enumerated(EnumType.STRING) + @Enums(enumClass = ProviderType.class, ignoreCase = false, message = "{auth.user.pattern.ProviderType}") + private ProviderType providerType; + + @Schema(title = "프로필 이미지 RL", example = "NO_IMAGE_URL", description = "SNS 사용시") + private String profileImageUrl; + + @Schema(title = "이메일 확인 여부", example = "N", description = "SNS 사용시sms 'Y'") + @NotBlank + @Builder.Default + String emailVerifiedYn = "N"; + + @Builder + public CmmUser toUser(PasswordEncoder passwordEncoder) { + return CmmUser.builder() + .providerType(providerType) + //.providerType(ProviderType.valueOf(providerType)) + .userId(userId) + .userName(userName) + .password(passwordEncoder.encode(password)) + .userMbl(userMbl) + .email(email) + .roleType(roleType) + //.roleType(RoleType.valueOf(roleType)) + .emailVerifiedYn(emailVerifiedYn) + .profileImageUrl(profileImageUrl) + .build(); + } + +} diff --git a/src/main/java/com/xit/biz/cmm/dto/ComboCodeDto.java b/src/main/java/com/xit/biz/cmm/dto/ComboCodeDto.java new file mode 100644 index 0000000..7654fab --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/dto/ComboCodeDto.java @@ -0,0 +1,9 @@ +package com.xit.biz.cmm.dto; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(name = "CmmCodeDto", description = "콤보공통코드 DTO") +public interface ComboCodeDto { + public String getCode(); + public String getValue(); +} diff --git a/src/main/java/com/xit/biz/cmm/dto/struct/CmmBoardMapstruct.java b/src/main/java/com/xit/biz/cmm/dto/struct/CmmBoardMapstruct.java new file mode 100644 index 0000000..26ccf94 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/dto/struct/CmmBoardMapstruct.java @@ -0,0 +1,12 @@ +package com.xit.biz.cmm.dto.struct; + +import com.xit.biz.cmm.entity.CmmBoard; +import com.xit.biz.cmm.dto.CmmBoardDto; +import com.xit.core.support.jpa.mapstruct.IMapstruct; +import com.xit.core.support.jpa.mapstruct.MapStructMapperConfig; +import org.mapstruct.Builder; +import org.mapstruct.Mapper; + +@Mapper(config = MapStructMapperConfig.class) +public interface CmmBoardMapstruct extends IMapstruct { +} \ No newline at end of file diff --git a/src/main/java/com/xit/biz/cmm/dto/struct/CmmCodeGrpMapstruct.java b/src/main/java/com/xit/biz/cmm/dto/struct/CmmCodeGrpMapstruct.java new file mode 100644 index 0000000..356b24a --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/dto/struct/CmmCodeGrpMapstruct.java @@ -0,0 +1,11 @@ +package com.xit.biz.cmm.dto.struct; + +import com.xit.biz.cmm.entity.CmmCodeGrp; +import com.xit.biz.cmm.dto.CmmCodeDto; +import com.xit.core.support.jpa.mapstruct.IMapstruct; +import com.xit.core.support.jpa.mapstruct.MapStructMapperConfig; +import org.mapstruct.Mapper; + +@Mapper(config = MapStructMapperConfig.class) +public interface CmmCodeGrpMapstruct extends IMapstruct { +} \ No newline at end of file diff --git a/src/main/java/com/xit/biz/cmm/dto/struct/CmmCodeLMapstruct.java b/src/main/java/com/xit/biz/cmm/dto/struct/CmmCodeLMapstruct.java new file mode 100644 index 0000000..f16d933 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/dto/struct/CmmCodeLMapstruct.java @@ -0,0 +1,11 @@ +package com.xit.biz.cmm.dto.struct; + +import com.xit.biz.cmm.entity.CmmCodeL; +import com.xit.biz.cmm.dto.CmmCodeDto; +import com.xit.core.support.jpa.mapstruct.IMapstruct; +import com.xit.core.support.jpa.mapstruct.MapStructMapperConfig; +import org.mapstruct.Mapper; + +@Mapper(config = MapStructMapperConfig.class) +public interface CmmCodeLMapstruct extends IMapstruct { +} \ No newline at end of file diff --git a/src/main/java/com/xit/biz/cmm/dto/struct/CmmCodeMMapstruct.java b/src/main/java/com/xit/biz/cmm/dto/struct/CmmCodeMMapstruct.java new file mode 100644 index 0000000..8616c3d --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/dto/struct/CmmCodeMMapstruct.java @@ -0,0 +1,11 @@ +package com.xit.biz.cmm.dto.struct; + +import com.xit.biz.cmm.entity.CmmCodeM; +import com.xit.biz.cmm.dto.CmmCodeDto; +import com.xit.core.support.jpa.mapstruct.IMapstruct; +import com.xit.core.support.jpa.mapstruct.MapStructMapperConfig; +import org.mapstruct.Mapper; + +@Mapper(config = MapStructMapperConfig.class) +public interface CmmCodeMMapstruct extends IMapstruct { +} \ No newline at end of file diff --git a/src/main/java/com/xit/biz/cmm/dto/struct/CmmCodeSMapstruct.java b/src/main/java/com/xit/biz/cmm/dto/struct/CmmCodeSMapstruct.java new file mode 100644 index 0000000..a734de6 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/dto/struct/CmmCodeSMapstruct.java @@ -0,0 +1,11 @@ +package com.xit.biz.cmm.dto.struct; + +import com.xit.biz.cmm.entity.CmmCodeS; +import com.xit.biz.cmm.dto.CmmCodeDto; +import com.xit.core.support.jpa.mapstruct.IMapstruct; +import com.xit.core.support.jpa.mapstruct.MapStructMapperConfig; +import org.mapstruct.Mapper; + +@Mapper(config = MapStructMapperConfig.class) +public interface CmmCodeSMapstruct extends IMapstruct { +} \ No newline at end of file diff --git a/src/main/java/com/xit/biz/cmm/dto/struct/CmmUserMapstruct.java b/src/main/java/com/xit/biz/cmm/dto/struct/CmmUserMapstruct.java new file mode 100644 index 0000000..9743b37 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/dto/struct/CmmUserMapstruct.java @@ -0,0 +1,11 @@ +package com.xit.biz.cmm.dto.struct; + +import com.xit.biz.cmm.entity.CmmUser; +import com.xit.biz.cmm.dto.CmmUserDto; +import com.xit.core.support.jpa.mapstruct.IMapstruct; +import com.xit.core.support.jpa.mapstruct.MapStructMapperConfig; +import org.mapstruct.Mapper; + +@Mapper(config = MapStructMapperConfig.class) +public interface CmmUserMapstruct extends IMapstruct { +} \ No newline at end of file diff --git a/src/main/java/com/xit/biz/cmm/entity/CmmAddress.java b/src/main/java/com/xit/biz/cmm/entity/CmmAddress.java new file mode 100644 index 0000000..858baae --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/CmmAddress.java @@ -0,0 +1,88 @@ +package com.xit.biz.cmm.entity; + +import com.xit.biz.cmm.entity.ids.CmmAddressIds; +import com.xit.core.constant.XitConstants; +import com.xit.core.support.jpa.AuditEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.hibernate.Hibernate; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Objects; + +@Schema(name = "CmmAddress", description = "주소") +@Table(name = "tb_cmm_address") +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +@IdClass(CmmAddressIds.class) +public class CmmAddress extends AuditEntity implements Serializable { + private static final long SerialVersionUID = 1L; + + @Id + @JoinColumn(name = "cmm_user_id") + private String cmmUserId; + + @Id + @Schema(required = true, title = "주소일련번호", example = "001", description = "사용자별 일련번호") + @Column(name = "addr_seq", nullable = false, length = 3) + private String addrSeq; + +// @MapsId("userId") +// @ManyToOne +// @JoinColumn(name = "user_id") +// private CmmUser cmmUser; +// +// @EmbeddedId +// private CmmAddressIds addrSeq; + + @Schema(required = true, title = "주소 구분", example = "STREET", description = "지번 / 도로명") + @Column(nullable = false, length = 10) + @Enumerated(EnumType.STRING) + private XitConstants.AddressType addressType; + + @Schema(required = true, title = "우편번호", example = "03001", description = "우편번호") + @Column(nullable = false, length = 6) + private String zipCode; + + @Schema(required = true, title = "기본 주소", example = "경기도 일산 동구 호수로 336", description = "기본 주소") + @Column(nullable = false, length = 100) + private String address1; + + @Schema(title = "상세 주소", example = "xxx아파트 xx동 xx호", description = "상세 주소") + @Column(nullable = false, length = 100) + private String address2; + + @Schema(title = "부가 주소", example = "null", description = "부가 주소 - 외국 주소인 경우 필요") + @Column(nullable = false, length = 100) + private String address3; + + @Schema(title = "기본주소 여부", example = "Y", description = "기본주소 여부") + //@Column(name = "default_yn", columnDefinition = "CHAR(1)", nullable = false, length = 1) + @Column(name = "default_yn", nullable = false, length = 1) + private String defaultYn; + +// public void setCmmUser(CmmUser cmmUser){ +// this.cmmUser = cmmUser; +// } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; + CmmAddress that = (CmmAddress) o; + + if (!Objects.equals(cmmUserId, that.cmmUserId)) return false; + return Objects.equals(addrSeq, that.addrSeq); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(cmmUserId); + result = 31 * result + (Objects.hashCode(addrSeq)); + return result; + } +} diff --git a/src/main/java/com/xit/biz/cmm/entity/CmmBoard.java b/src/main/java/com/xit/biz/cmm/entity/CmmBoard.java new file mode 100644 index 0000000..2d3d01d --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/CmmBoard.java @@ -0,0 +1,86 @@ +package com.xit.biz.cmm.entity; + +import com.xit.core.support.jpa.AuditEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.hibernate.Hibernate; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Objects; + +@Schema(name = "CmmBoard", description = "게시판") +@Table( + name = "tb_cmm_board", + indexes = { + @Index(name = "idx_tb_cmm_board_01", columnList ="ctg_cd,title") + } +) +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class CmmBoard extends AuditEntity implements Serializable{ + + private static final long SerialVersionUID = 1L; + + @Schema(required = true, title = "게시판 ID", example = " ", description = "자동부여") + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "board_id") + private Long boardId; + + @Schema(required = true, title = "카테고리 구분 코드", example = "ctg_1") + @Column(name = "ctg_cd", length = 20, nullable = false) + private String ctgCd; + + @Schema(required = true, title = "게시판 제목", example = "제목") + @Column(length = 128, nullable = false) + private String title; + + @Schema(required = true, title = "게시판 내용", example = "내용") + //@Column(columnDefinition = "TEXT", nullable = false) + @Column(nullable = false) + @Lob + private String content; + + + +/* + + @Transient + private int pageNum; + @Transient + private int pageSize; + @Transient + private int totalPage; + @Transient + private int totalCount; + + @Transient // DB에 저장도 조회도 하지 않는다 + private String firstName; + + @Transient + private String lastName; + + @Access(AccessType.PROPERTY) // FULLNAME 컬럼에 firstName+lastName의 결과가 저장됨 getter를 이용해서 접근한다. + public String getFullName(){ + return firstName + lastName; + } +*/ + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; + CmmBoard cmmBoard = (CmmBoard) o; + + return Objects.equals(boardId, cmmBoard.boardId); + } + + @Override + public int hashCode() { + return 1566954134; + } +} diff --git a/src/main/java/com/xit/biz/cmm/entity/CmmCodeGrp.java b/src/main/java/com/xit/biz/cmm/entity/CmmCodeGrp.java new file mode 100644 index 0000000..3cd20f4 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/CmmCodeGrp.java @@ -0,0 +1,111 @@ +package com.xit.biz.cmm.entity; + +import com.xit.core.support.jpa.AuditEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.hibernate.Hibernate; +import org.hibernate.annotations.ColumnDefault; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.io.Serializable; +import java.util.Objects; + +@Schema(name = "CmmCodeGrp", description = "코드그룹") //, parent = AuditEntity.class) +@Table(name = "tb_cmm_code_grp") +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class CmmCodeGrp extends AuditEntity implements Serializable { + + @Schema(required = true, title = "코드그룹ID", example = "G_CODE_SAM") + @Id + @Column(name = "code_grp_id", nullable = false, length = 20) + private String codeGrpId; + + @Schema(required = true, title = "코드그룹명", example = "코드그룹예제") + @Column(name = "code_nm", nullable = false, length = 20) + private String codeNm; + + @Schema(title = "코드그룹상세", example = "코드그룹상세") + @Column(name = "code_remark", length = 50) + private String codeRemark; + +// @Schema(title = "추가필드1", example = " ") +// @Column(name = "code_meta_1", length = 10) +// private String codeMeta1; +// +// @Schema(title = "추가필드2", example = " ") +// @Column(name = "code_meta_2", length = 10) +// private String codeMeta2; +// +// @Schema(title = "추가필드3", example = " ") +// @Column(name = "code_meta_3", length = 10) +// private String codeMeta3; +// +// @Schema(title = "추가필드4", example = " ") +// @Column(name = "code_meta_4", length = 10) +// private String codeMeta4; +// +// @Schema(title = "추가필드5", example = " ") +// @Column(name = "code_meta_5", length = 10) +// private String codeMeta5; + + @Schema(required = true, title = "코드정렬순서", example = "99") + @Column(name = "code_ordr", nullable = false, length = 3) // columnDefinition = "int(3)") + @ColumnDefault(value = "99") + private Integer codeOrdr; + + + /* + @Schema(required = true, title = "그룹코드명", example = "코드예제", description = "코드그룹명") + @Column(name = "code_nm", nullable = false, length = 20) + private String codeNm; + + @Schema(title = "그룹코드상세", example = "공통 코드 예제", description = "공통 코드 예제") + @Column(name = "code_remark", length = 50) + private String codeRemark; + + @Schema(title = "추가필드1") + @Column(name = "code_meta_1", length = 10) + private String codeMeta1; + + @Schema(title = "추가필드2") + @Column(name = "code_meta_2", length = 10) + private String codeMeta2; + + @Schema(title = "추가필드3") + @Column(name = "code_meta_3", length = 10) + private String codeMeta3; + + @Schema(title = "추가필드4") + @Column(name = "code_meta_4", length = 10) + private String codeMeta4; + + @Schema(title = "추가필드5") + @Column(name = "code_meta_5", length = 10) + private String codeMeta5; + + @Schema(required = true, title = "코드정렬순서", example = "99") + @Column(name = "code_ordr", nullable = false, length = 3) @ColumnDefault(value = "99") + private Integer codeOrdr; + */ + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; + CmmCodeGrp that = (CmmCodeGrp) o; + + return Objects.equals(codeGrpId, that.codeGrpId); + } + + @Override + public int hashCode() { + return 1946457019; + } +} diff --git a/src/main/java/com/xit/biz/cmm/entity/CmmCodeL.java b/src/main/java/com/xit/biz/cmm/entity/CmmCodeL.java new file mode 100644 index 0000000..20ec5b4 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/CmmCodeL.java @@ -0,0 +1,118 @@ +package com.xit.biz.cmm.entity; + +import com.xit.biz.cmm.entity.ids.CmmCodeLIds; +import com.xit.core.support.jpa.AuditEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.hibernate.Hibernate; +import org.hibernate.annotations.ColumnDefault; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Objects; + +@Schema(name = "CmmCodeL", description = "대분류코드") //, parent = AuditEntity.class) +@Table(name = "tb_cmm_code_l") +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +@IdClass(CmmCodeLIds.class) +public class CmmCodeL extends AuditEntity implements Serializable { + + @Schema(required = true, title = "코드그룹ID", example = "G_CODE_SAM", description = "공통코드그룹ID") + @Id + @Column(name = "code_grp_id", nullable = false, length = 20) + private String codeGrpId; + + @Schema(required = true, title = "대분류코드", example = "L0001", description = "대분류 코드값") + @Id + @Column(name = "code_cd", nullable = false, length = 10) + private String codeCd; + + @Schema(required = true, title = "대분류코드명", example = "대분류코드명") + @Column(name = "code_nm", nullable = false, length = 20) + private String codeNm; + + @Schema(title = "대분류코드상세", example = "대분류코드상세") + @Column(name = "code_remark", length = 50) + private String codeRemark; + + @Schema(title = "추가필드1", example = " ") + @Column(name = "code_meta_1", length = 10) + private String codeMeta1; + + @Schema(title = "추가필드2", example = " ") + @Column(name = "code_meta_2", length = 10) + private String codeMeta2; + + @Schema(title = "추가필드3", example = " ") + @Column(name = "code_meta_3", length = 10) + private String codeMeta3; + + @Schema(title = "추가필드4", example = " ") + @Column(name = "code_meta_4", length = 10) + private String codeMeta4; + + @Schema(title = "추가필드5", example = " ") + @Column(name = "code_meta_5", length = 10) + private String codeMeta5; + + @Schema(required = true, title = "코드정렬순서", example = "99") + @Column(name = "code_ordr", nullable = false, length = 3) //columnDefinition = "int(3)") + @ColumnDefault(value = "99") + private Integer codeOrdr; + + + /* + @Schema(required = true, title = "그룹코드명", example = "코드예제", description = "코드그룹명") + @Column(name = "code_nm", nullable = false, length = 20) + private String codeNm; + + @Schema(title = "그룹코드상세", example = "공통 코드 예제", description = "공통 코드 예제") + @Column(name = "code_remark", length = 50) + private String codeRemark; + + @Schema(title = "추가필드1") + @Column(name = "code_meta_1", length = 10) + private String codeMeta1; + + @Schema(title = "추가필드2") + @Column(name = "code_meta_2", length = 10) + private String codeMeta2; + + @Schema(title = "추가필드3") + @Column(name = "code_meta_3", length = 10) + private String codeMeta3; + + @Schema(title = "추가필드4") + @Column(name = "code_meta_4", length = 10) + private String codeMeta4; + + @Schema(title = "추가필드5") + @Column(name = "code_meta_5", length = 10) + private String codeMeta5; + + @Schema(required = true, title = "코드정렬순서", example = "99") + @Column(name = "code_ordr", nullable = false, length = 3) @ColumnDefault(value = "99") + private Integer codeOrdr; + */ + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; + CmmCodeL cmmCodeL = (CmmCodeL) o; + + if (!Objects.equals(codeGrpId, cmmCodeL.codeGrpId)) return false; + return Objects.equals(codeCd, cmmCodeL.codeCd); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(codeGrpId); + result = 31 * result + (Objects.hashCode(codeCd)); + return result; + } +} diff --git a/src/main/java/com/xit/biz/cmm/entity/CmmCodeM.java b/src/main/java/com/xit/biz/cmm/entity/CmmCodeM.java new file mode 100644 index 0000000..7b4a798 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/CmmCodeM.java @@ -0,0 +1,123 @@ +package com.xit.biz.cmm.entity; + +import com.xit.biz.cmm.entity.ids.CmmCodeMIds; +import com.xit.core.support.jpa.AuditEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.hibernate.Hibernate; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Objects; + +@Schema(name = "CmmCodeM", description = "중분류코드") //, parent = AuditEntity.class) +@Table(name = "tb_cmm_code_m") +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +@IdClass(CmmCodeMIds.class) +public class CmmCodeM extends AuditEntity implements Serializable { + + @Schema(required = true, title = "코드그룹ID", example = "G_CODE_SAM", description = "공통코드그룹ID") + @Id + @Column(name = "code_grp_id", nullable = false, length = 20) + private String codeGrpId; + + @Schema(required = true, title = "대분류코드", example = "L0001", description = "대분류코드값") + @Id + @Column(name = "code_lcd", nullable = false, length = 10) + private String codeLcd; + + @Schema(required = true, title = "중분류코드", example = "M0001", description = "중분류 코드값") + @Id + @Column(name = "code_cd", nullable = false, length = 10) + private String codeCd; + + @Schema(required = true, title = "중분류코드명", example = "중분류코드명") + @Column(name = "code_nm", nullable = false, length = 20) + private String codeNm; + + @Schema(title = "중분류코드상세", example = "중분류코드상세") + @Column(name = "code_remark", length = 50) + private String codeRemark; + + @Schema(title = "추가필드1", example = " ") + @Column(name = "code_meta_1", length = 10) + private String codeMeta1; + + @Schema(title = "추가필드2", example = " ") + @Column(name = "code_meta_2", length = 10) + private String codeMeta2; + + @Schema(title = "추가필드3", example = " ") + @Column(name = "code_meta_3", length = 10) + private String codeMeta3; + + @Schema(title = "추가필드4", example = " ") + @Column(name = "code_meta_4", length = 10) + private String codeMeta4; + + @Schema(title = "추가필드5", example = " ") + @Column(name = "code_meta_5", length = 10) + private String codeMeta5; + + @Schema(required = true, title = "코드정렬순서", example = "99") + @Column(name = "code_ordr", nullable = false, length = 3) //columnDefinition = "int(3)") + // @ColumnDefault(value = "99") + private Integer codeOrdr; + + /* + @Schema(required = true, title = "그룹코드명", example = "코드예제", description = "코드그룹명") + @Column(name = "code_nm", nullable = false, length = 20) + private String codeNm; + + @Schema(title = "그룹코드상세", example = "공통 코드 예제", description = "공통 코드 예제") + @Column(name = "code_remark", length = 50) + private String codeRemark; + + @Schema(title = "추가필드1") + @Column(name = "code_meta_1", length = 10) + private String codeMeta1; + + @Schema(title = "추가필드2") + @Column(name = "code_meta_2", length = 10) + private String codeMeta2; + + @Schema(title = "추가필드3") + @Column(name = "code_meta_3", length = 10) + private String codeMeta3; + + @Schema(title = "추가필드4") + @Column(name = "code_meta_4", length = 10) + private String codeMeta4; + + @Schema(title = "추가필드5") + @Column(name = "code_meta_5", length = 10) + private String codeMeta5; + + @Schema(required = true, title = "코드정렬순서", example = "99") + @Column(name = "code_ordr", nullable = false, length = 3) @ColumnDefault(value = "99") + private Integer codeOrdr; + */ + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; + CmmCodeM cmmCodeM = (CmmCodeM) o; + + if (!Objects.equals(codeGrpId, cmmCodeM.codeGrpId)) return false; + if (!Objects.equals(codeLcd, cmmCodeM.codeLcd)) return false; + return Objects.equals(codeCd, cmmCodeM.codeCd); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(codeGrpId); + result = 31 * result + (Objects.hashCode(codeLcd)); + result = 31 * result + (Objects.hashCode(codeCd)); + return result; + } +} diff --git a/src/main/java/com/xit/biz/cmm/entity/CmmCodeS.java b/src/main/java/com/xit/biz/cmm/entity/CmmCodeS.java new file mode 100644 index 0000000..563247b --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/CmmCodeS.java @@ -0,0 +1,131 @@ +package com.xit.biz.cmm.entity; + +import com.xit.biz.cmm.entity.ids.CmmCodeSIds; +import com.xit.core.support.jpa.AuditEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.hibernate.Hibernate; +import org.hibernate.annotations.ColumnDefault; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Objects; + +@Schema(name = "CmmCodeS", description = "소분류코드") //, parent = AuditEntity.class) +@Table(name = "tb_cmm_code_s") +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +@IdClass(CmmCodeSIds.class) +public class CmmCodeS extends AuditEntity implements Serializable { + + @Schema(required = true, title = "코드그룹ID", example = "G_CODE_SAM", description = "공통코드그룹ID") + @Id + @Column(name = "code_grp_id", nullable = false, length = 20) + private String codeGrpId; + + @Schema(required = true, title = "대분류코드", example = "L0001", description = "대분류코드값") + @Id + @Column(name = "code_lcd", nullable = false, length = 10) + private String codeLcd; + + @Schema(required = true, title = "중분류코드", example = "M0001", description = "중분류 코드값") + @Id + @Column(name = "code_mcd", nullable = false, length = 10) + private String codeMcd; + + @Schema(required = true, title = "소분류코드", example = "001", description = "소분류 코드값") + @Id + @Column(name = "code_cd", nullable = false, length = 10) + private String codeCd; + + @Schema(required = true, title = "소분류코드명", example = "소분류코드예시") + @Column(name = "code_nm", nullable = false, length = 20) + private String codeNm; + + @Schema(title = "소분류코드상세", example = "소분류코드상세예시") + @Column(name = "code_remark", length = 50) + private String codeRemark; + + @Schema(title = "추가필드1", example = " ") + @Column(name = "code_meta_1", length = 10) + private String codeMeta1; + + @Schema(title = "추가필드2", example = " ") + @Column(name = "code_meta_2", length = 10) + private String codeMeta2; + + @Schema(title = "추가필드3", example = " ") + @Column(name = "code_meta_3", length = 10) + private String codeMeta3; + + @Schema(title = "추가필드4", example = " ") + @Column(name = "code_meta_4", length = 10) + private String codeMeta4; + + @Schema(title = "추가필드5", example = " ") + @Column(name = "code_meta_5", length = 10) + private String codeMeta5; + + @Schema(required = true, title = "코드정렬순서", example = "99") + @Column(name = "code_ordr", nullable = false, length = 3) //columnDefinition = "int(3)") + @ColumnDefault(value = "99") + private Integer codeOrdr; + + /* + @Schema(required = true, title = "그룹코드명", example = "코드예제", description = "코드그룹명") + @Column(name = "code_nm", nullable = false, length = 20) + private String codeNm; + + @Schema(title = "그룹코드상세", example = "공통 코드 예제", description = "공통 코드 예제") + @Column(name = "code_remark", length = 50) + private String codeRemark; + + @Schema(title = "추가필드1") + @Column(name = "code_meta_1", length = 10) + private String codeMeta1; + + @Schema(title = "추가필드2") + @Column(name = "code_meta_2", length = 10) + private String codeMeta2; + + @Schema(title = "추가필드3") + @Column(name = "code_meta_3", length = 10) + private String codeMeta3; + + @Schema(title = "추가필드4") + @Column(name = "code_meta_4", length = 10) + private String codeMeta4; + + @Schema(title = "추가필드5") + @Column(name = "code_meta_5", length = 10) + private String codeMeta5; + + @Schema(required = true, title = "코드정렬순서", example = "99") + @Column(name = "code_ordr", nullable = false, length = 3) @ColumnDefault(value = "99") + private Integer codeOrdr; + */ + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; + CmmCodeS codeS = (CmmCodeS) o; + + if (!Objects.equals(codeGrpId, codeS.codeGrpId)) return false; + if (!Objects.equals(codeLcd, codeS.codeLcd)) return false; + if (!Objects.equals(codeMcd, codeS.codeMcd)) return false; + return Objects.equals(codeCd, codeS.codeCd); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(codeGrpId); + result = 31 * result + (Objects.hashCode(codeLcd)); + result = 31 * result + (Objects.hashCode(codeMcd)); + result = 31 * result + (Objects.hashCode(codeCd)); + return result; + } +} diff --git a/src/main/java/com/xit/biz/cmm/entity/CmmFileDtl.java b/src/main/java/com/xit/biz/cmm/entity/CmmFileDtl.java new file mode 100644 index 0000000..15e188a --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/CmmFileDtl.java @@ -0,0 +1,87 @@ +package com.xit.biz.cmm.entity; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.xit.biz.cmm.entity.ids.CmmFileDtlIds; +import com.xit.core.support.jpa.AuditEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.hibernate.Hibernate; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Objects; + +@Schema(name = "CmmFileDtl", description = "파일 상세") //, parent = AuditEntity.class) +@Table( + name = "tb_cmm_file_dtl" +) +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +@IdClass(CmmFileDtlIds.class) +public class CmmFileDtl extends AuditEntity implements Serializable { + private static final long serialVersionUID = -543086850590535341L; + +// @MapsId(value = "fileMstId") + @Id + @Schema(required = true, title = "파일MstID", example = " ", description = "FileMst ID") + @Column(name = "file_mst_id", nullable = false) + private String fileMstId; +// +// @EmbeddedId + @Id + @Schema(required = true, title = "파일ID", example = " ", description = "UUID로 자동 생성") + @Column(name = "file_id", nullable = false, length = 50) + private String fileId;// = UUID.randomUUID().toString().replaceAll("-", ""); + +// @EmbeddedId +// private CmmFileDtlIds cmmFileDtlIds; + + @Schema(required = true, title = "원본파일명", example = "sample.jpg", description = "서버에서 처리") + @Column(name = "org_file_nm", nullable = false, length = 100) + private String orgFileNm; + + @Schema(required = true, title = "파일콘텐츠타입", example = "image/jpeg", description = "서버에서 처리") + @Column(name = "content_type", nullable = false, length = 200) + private String contentType; + + @Schema(required = true, title = "파일확장자", example = "jpg", description = "서버에서 처리") + @Column(name = "file_ext", nullable = false, length = 50) + private String fileExt; + + @Schema(required = true, title = "파일용량", example = " ", description = "서버에서 처리") + @Column(name = "file_size", nullable = false) + private Long fileSize; + + @Schema(required = true, title = "파일경로", example = " ", description = "서버에서 처리") + @Column(name = "file_upld_path", nullable = false, length = 100) + private String fileUpldPath; + + @Transient + @JsonIgnore + @ManyToOne + private CmmFileMst cmmFileMst; + + public void setCmmFileMst(CmmFileMst cmmFileMst){ + this.cmmFileMst = cmmFileMst; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; + CmmFileDtl that = (CmmFileDtl) o; + + if (!Objects.equals(fileMstId, that.fileMstId)) return false; + return Objects.equals(fileId, that.fileId); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(fileMstId); + result = 31 * result + (Objects.hashCode(fileId)); + return result; + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/biz/cmm/entity/CmmFileMst.java b/src/main/java/com/xit/biz/cmm/entity/CmmFileMst.java new file mode 100644 index 0000000..45b9d0e --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/CmmFileMst.java @@ -0,0 +1,77 @@ +package com.xit.biz.cmm.entity; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.xit.core.support.jpa.AuditEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.hibernate.Hibernate; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +@Schema(name = "CmmFileMst", description = "파일 마스터") //, parent = AuditEntity.class) +@Table( + name = "tb_cmm_file_mst", + indexes = { + @Index(unique = true, name = "idx_tb_file_mst_01", columnList ="file_ctg_cd, file_biz_id") + } +) +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class CmmFileMst extends AuditEntity implements Serializable { + private static final long serialVersionUID = 5741919760452445573L; + + @Schema(required = true, title = "파일 마스터 ID", example = " ", description = "UUID로 생성") + @Id + @Setter + @Column(name = "file_mst_id", nullable = false, length = 40) + private String fileMstId; + + @Schema(required = true, title = "파일분류코드", example = "BOARD", description = "업무별분류코드") + @Column(name = "file_ctg_cd", nullable = false, length = 50) + private String fileCtgCd; + + @Schema(required = true, title = "파일업무ID", example = "1", description = "각업무테이블PK조합") + @Column(name = "file_biz_id", nullable = false, length = 50) + private String fileBizId; + + @Transient + @JsonIgnore + @OneToMany(targetEntity = CmmFileDtl.class, mappedBy = "cmmFileMst") + @Builder.Default + private Set cmmFileDtls = new HashSet<>(); + + public void setCmmFileDtls(CmmFileDtl cmmFileDtl){ + this.getCmmFileDtls().add(cmmFileDtl); + } + + public void addCmmFileDtls(CmmFileDtl cmmFileDtl){ + this.getCmmFileDtls().add(cmmFileDtl); + cmmFileDtl.setCmmFileMst(this); + } + + public void removeCmmFileDtls(CmmFileDtl cmmFileDtl){ + this.getCmmFileDtls().remove(cmmFileDtl); + cmmFileDtl.setCmmFileMst(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; + CmmFileMst that = (CmmFileMst) o; + + return Objects.equals(fileMstId, that.fileMstId); + } + + @Override + public int hashCode() { + return 761862093; + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/biz/cmm/entity/CmmMenu.java b/src/main/java/com/xit/biz/cmm/entity/CmmMenu.java new file mode 100644 index 0000000..6654262 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/CmmMenu.java @@ -0,0 +1,87 @@ +package com.xit.biz.cmm.entity; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.xit.core.support.jpa.AuditEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.hibernate.Hibernate; +import org.hibernate.annotations.ColumnDefault; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +@Schema(name = "CmmMenu", description = "메뉴") //, parent = AuditEntity.class) +@Table(name = "tb_cmm_menu") +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class CmmMenu extends AuditEntity implements Serializable { + + @Schema(required = true, title = "메뉴ID", example = "C000000000", description = "메뉴ID") + @Id + @Column(name = "menu_id", nullable = false, length = 10) + private String menuId; + + @Schema(required = true, title = "상위메뉴ID", example = "ROOT", description = "ROOT - 최상위메뉴는 null") + @Column(name = "upr_menu_id", length = 10) + private String uprMenuId; + + @Schema(required = true, title = "메뉴명", example = "파일관리") + @Column(name = "menu_nm", nullable = false, length = 20) + private String menuNm; + + @Schema(required = true, title = "메뉴상세", example = "파일관리메뉴") + @Column(name = "menu_remark", length = 20) + private String menuRemark; + + @Schema(required = true, title = "메뉴URL", example = "/api/biz/cmm/file") + @Column(name = "menu_url", length = 200) + private String menuUrl; + + @Schema(required = true, title = "노출여부", example = "Y") + @Column(name = "expo_yn", nullable = false, length = 1) //, columnDefinition = "char(1)") + @ColumnDefault(value = "'Y'") + @Builder.Default + private String expoYn = "Y"; + + @Schema(required = true, title = "메뉴정렬순서", example = "99") + @Column(name = "menuOrdr", nullable = false, length = 3) //columnDefinition = "int(3)") + @ColumnDefault(value = "99") + private Integer menuOrdr; + + @Schema(hidden = true) + @Transient + @JsonIgnore + @OneToMany(targetEntity = CmmRoleMenu.class, mappedBy = "cmmMenuGrp") + @Builder.Default + private Set cmmRoles = new HashSet<>(); + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; + CmmMenu cmmMenu = (CmmMenu) o; + + return Objects.equals(menuId, cmmMenu.menuId); + } + + @Override + public int hashCode() { + return 1856257695; + } +} + +/* + , BTN_GRP_ID + , BTN_GRP_NM + , BTN_CSS_NM + , EVNT_NM + , DEL_YN +*/ + + diff --git a/src/main/java/com/xit/biz/cmm/entity/CmmRole.java b/src/main/java/com/xit/biz/cmm/entity/CmmRole.java new file mode 100644 index 0000000..ab65d2b --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/CmmRole.java @@ -0,0 +1,60 @@ +package com.xit.biz.cmm.entity; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.xit.core.support.jpa.AuditEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.hibernate.Hibernate; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +@Schema(name = "CmmRole", description = "권한") +@Table(name = "tb_cmm_role") +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class CmmRole extends AuditEntity implements Serializable { + private static final long SerialVersionUID = 1L; + + @Schema(required = true, title = "권한ID", example = "ADMIN_01") + @Id + @Column(name = "role_id", length = 10, nullable = false) + private String roleId; + + @Schema(required = true, title = "권한 이름", example = "ROLE_ADMIN") + @Column(name = "role_name", length = 20, nullable = false) + private String roleName; + + @Schema(name = "권한 설명", example = "관리자") + @Column(name = "role_remark", length = 100, nullable = true) + private String roleRemark; + + @Transient + @JsonIgnore + @OneToMany(targetEntity = CmmUser.class, mappedBy = "cmmUserMst") + private final Set cmmUsers = new HashSet<>(); + + public void setCmmUsers(CmmUser cmmUser){ + this.getCmmUsers().add(cmmUser); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; + CmmRole cmmRole = (CmmRole) o; + + return Objects.equals(roleId, cmmRole.roleId); + } + + @Override + public int hashCode() { + return 904897982; + } +} diff --git a/src/main/java/com/xit/biz/cmm/entity/CmmRoleMenu.java b/src/main/java/com/xit/biz/cmm/entity/CmmRoleMenu.java new file mode 100644 index 0000000..193f6ff --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/CmmRoleMenu.java @@ -0,0 +1,77 @@ +package com.xit.biz.cmm.entity; + +import com.xit.core.support.jpa.AuditEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.hibernate.Hibernate; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Objects; + +@Schema(name = "CmmRoleMenu", description = "권한별메뉴") +@Table(name = "tb_cmm_role_menu") +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class CmmRoleMenu extends AuditEntity implements Serializable { + private static final long SerialVersionUID = 1L; + +// @Schema(required = true, title = "메뉴그룹ID", example = "G_MENU_1", description = "메뉴그룹ID") +// @Id +// @Column(name = "menu_grp_id", nullable = false, length = 10) +// private String menuGrpId; +// +// @Schema(required = true, title = "메뉴ID", example = "C000000000", description = "메뉴ID") +// @Id +// @Column(name = "menu_id", nullable = false, length = 10) +// private String menuId; + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Schema(required = true, title = "권한ID", example = "ADMIN_01") + @Column(name = "role_id", nullable = false, length = 10) + private String roleId; + + @Schema(required = true, title = "메뉴ID", example = "C000000000") + @Column(name = "menu_id", nullable = false, length = 10) + private String menuId; + + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "role_id", insertable = false, updatable = false) + private CmmRole cmmRole; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "menu_id", insertable = false, updatable = false) + private CmmMenu cmmMenu; + +// @Schema(hidden = true) +// //@Transient +// @ManyToOne(targetEntity = CmmRole.class, fetch = FetchType.LAZY) +// @JoinColumn(name="role_id", insertable = false, updatable = false) +// private CmmRole cmmRole; +// +// @Schema(hidden = true) +// //@Transient +// @ManyToOne(targetEntity = CmmMenu.class, fetch = FetchType.LAZY) +// @JoinColumn(name = "menu_id", insertable = false, updatable = false) +// private CmmMenu cmmMenu; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; + CmmRoleMenu that = (CmmRoleMenu) o; + + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return 1632561512; + } +} diff --git a/src/main/java/com/xit/biz/cmm/entity/CmmRoleUer.java b/src/main/java/com/xit/biz/cmm/entity/CmmRoleUer.java new file mode 100644 index 0000000..305066d --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/CmmRoleUer.java @@ -0,0 +1,66 @@ +package com.xit.biz.cmm.entity; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.hibernate.Hibernate; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Objects; + +@Schema(name = "CmmRoleUer", description = "권한별사용자") +@Table(name = "tb_cmm_role_user") +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class CmmRoleUer implements Serializable { + private static final long SerialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Schema(required = true, title = "권한ID", example = "ADMIN_01") + @Column(name = "role_id", nullable = false, length = 10) + private String roleId; + + @Schema(required = true, title = "시스템사용자ID", example = "W000000001") + @Column(name = "cmm_user_id", nullable = false, length = 10) + private String cmmUserId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "role_id", insertable = false, updatable = false) + private CmmRole cmmRole; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "cmm_user_id", insertable = false, updatable = false) + private CmmUser cmmUser; + +// @Schema(hidden = true) +// //@Transient +// @ManyToOne(targetEntity = CmmRole.class, fetch = FetchType.LAZY) +// @JoinColumn(name="role_id", insertable = false, updatable = false) +// private CmmRole cmmRole; +// +// @Schema(hidden = true) +// //@Transient +// @ManyToOne(targetEntity = CmmUser.class, fetch = FetchType.LAZY) +// @JoinColumn(name="cmm_user_id", insertable = false, updatable = false) +// private CmmUser cmmUser; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; + CmmRoleUer that = (CmmRoleUer) o; + + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return 2024097714; + } +} diff --git a/src/main/java/com/xit/biz/cmm/entity/CmmUser.java b/src/main/java/com/xit/biz/cmm/entity/CmmUser.java new file mode 100644 index 0000000..48e56cb --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/CmmUser.java @@ -0,0 +1,181 @@ +package com.xit.biz.cmm.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.xit.core.oauth2.oauth.entity.ProviderType; +import com.xit.core.oauth2.oauth.entity.RoleType; +import com.xit.core.support.jpa.AuditEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.hibernate.Hibernate; + +import javax.persistence.*; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import java.io.Serializable; +import java.util.Objects; + +@Schema(name = "CmmUserMst", description = "사용자 마스터") //, parent = AuditEntity.class) +@Table( + name = "tb_cmm_user_mst", + indexes = { + @Index(unique = true, name = "idx_tb_cmm_user_mst_01", columnList ="user_id") + //@Index(name = "idx_tb_cmm_user_mst_02", columnList ="user_id,user_name,user_email") + } +) +@Entity +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class CmmUser extends AuditEntity implements Serializable { //extends RepresentationModel implements Serializable { + private static final long SerialVersionUID = 1L; + + @Setter + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @Schema(required = true, title = "사용자ID(PK)", example = "W000000001") + @Id + @Column(name = "cmm_user_id", length = 64) + private String cmmUserId; + + @Setter + @Schema(required = true, title = "사용자ID(실제ID)", example = "minuk926", description = "논리적인 실제 PK(unique)") + @Column(name = "user_id", length = 64, nullable = false, unique = true) + //@NotNull + //@Size(max = 64) + private String userId; + + @Setter + @Schema(required = true, title = "사용자 이름", example = "홍길동") + @Column(name="user_name", length = 100, nullable = false) + //@NotNull + //@Size(max = 100) + private String userName; + + //TODO : ??? + @Setter + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + @Schema(required = true, title = "사용자 비밀번호", example = "minuk926") + @Column(name="password", length = 128, nullable = false) + //@NotNull + //@Size(max = 128) + private String password; + + @Setter + @Schema(title = "전화번호", example = "01012341234") + @Column(name="user_mbl", length = 100) + //@Size(max = 128) + private String userMbl; + + @Setter + @Schema(title = "사용자 이메일", example = "a@b.com") + @Column(name="email", length = 100, nullable = false) + //@Size(max = 100) + private String email; + + @Setter + @Column(name = "role_type", length = 20, nullable = false) + @Enumerated(EnumType.STRING) + //@NotNull + private RoleType roleType; + + /////////////////////////// Social login 관련 //////////////////////////////////// + + @Setter + @Column(name = "provider_type", length = 50, nullable = false) + @Enumerated(EnumType.STRING) + //@NotNull + private ProviderType providerType; + + @Setter + @Schema(title = "프로필이미지URL", example = " ", description = "SNS로 로그인시 필수") + @Column(name = "profile_image_url", length = 256) + //@NotNull + //@Size(max = 256) + private String profileImageUrl; + + @Builder.Default + @Setter + @Column(name = "email_verified_yn", length = 1, nullable = false) + //@NotNull + //@Size(min = 1, max = 1) + private String emailVerifiedYn = "N"; + + public CmmUser( + @NotNull @Size(max = 64) String userId, + @NotNull @Size(max = 100) String userName, + @NotNull @Size(max = 100) String email, + @NotNull @Size(max = 1) String emailVerifiedYn, + @NotNull @Size(max = 256) String profileImageUrl, + @NotNull ProviderType providerType, + @NotNull RoleType roleType + ) { + this.userId = userId; + this.userName = userName; + this.password = "NO_PASS"; + this.email = email != null ? email : "NO_EMAIL"; + this.emailVerifiedYn = emailVerifiedYn; + this.profileImageUrl = profileImageUrl != null ? profileImageUrl : ""; + this.providerType = providerType; + this.roleType = roleType; + } + + // @ManyToMany(fetch = FetchType.LAZY) +// @JoinTable( +// name = "tb_cmm_role_user", +// joinColumns = {@JoinColumn(name="cmm_user_id", referencedColumnName = "cmm_user_id")}, +// inverseJoinColumns = {@JoinColumn(name="role_id", referencedColumnName = "role_id")} +// ) +// private Set cmmRoles; + +// +// @Schema(hidden = true) +// @Transient +// //@JsonIgnore +// @OneToMany(targetEntity = CmmAddress.class, mappedBy = "cmmUserMst") +// private final Set cmmAddresses = new HashSet<>(); + + + +// public void setCmmRoles(CmmRole cmmRole){ +// this.getCmmRoles().add(cmmRole); +// } +// +// public void setCmmAddresses(CmmAddress cmmAddress){ +// this.getCmmAddresses().add(cmmAddress); +// } +// +// public void addCmmAddress(CmmAddress cmmAddress){ +// this.getCmmAddresses().add(cmmAddress); +// cmmAddress.setCmmUser(this); +// } +// +// public void removeCmmAddress(CmmAddress cmmAddress){ +// this.getCmmAddresses().remove(cmmAddress); +// cmmAddress.setCmmUser(this); +// } +// +// public void addRoleGrps(CmmRole cmmRole){ +// this.getCmmRoles().add(cmmRole); +// cmmRole.setCmmUsers(this); +// +// } +// public void removeRoleGrps(CmmRole cmmRole){ +// this.getCmmRoles().remove(cmmRole); +// cmmRole.setCmmUsers(this); +// +// } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; + CmmUser cmmUser = (CmmUser) o; + + return Objects.equals(cmmUserId, cmmUser.cmmUserId); + } + + @Override + public int hashCode() { + return 880966850; + } +} diff --git a/src/main/java/com/xit/biz/cmm/entity/ids/CmmAddressIds.java b/src/main/java/com/xit/biz/cmm/entity/ids/CmmAddressIds.java new file mode 100644 index 0000000..d2210b2 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/ids/CmmAddressIds.java @@ -0,0 +1,23 @@ +package com.xit.biz.cmm.entity.ids; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.persistence.Column; +import java.io.Serializable; + +//@Embeddable +@Getter +@Setter +@EqualsAndHashCode +@NoArgsConstructor +public class CmmAddressIds implements Serializable { + private String cmmUserId; + + @Schema(required = true, title = "주소일련번호", example = "001", description = "사용자별 일련번호") + @Column(name = "addr_seq", nullable = false, length = 3) + private String addrSeq; +} diff --git a/src/main/java/com/xit/biz/cmm/entity/ids/CmmCodeLIds.java b/src/main/java/com/xit/biz/cmm/entity/ids/CmmCodeLIds.java new file mode 100644 index 0000000..a677144 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/ids/CmmCodeLIds.java @@ -0,0 +1,25 @@ +package com.xit.biz.cmm.entity.ids; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.persistence.Column; +import java.io.Serializable; + +@Getter +@Setter +@EqualsAndHashCode +@NoArgsConstructor +public class CmmCodeLIds implements Serializable { + + @Schema(required = true, title = "코드그룹ID", example = "CODE_SAM", description = "공통코드그룹ID") + @Column(name = "code_grp_id", nullable = false, length = 20) + private String codeGrpId; + + @Schema(required = true, title = "공통코드 코드", example = "00001", description = "공통코드의 코드값") + @Column(name = "code_cd", nullable = false, length = 10) + private String codeCd; +} diff --git a/src/main/java/com/xit/biz/cmm/entity/ids/CmmCodeMIds.java b/src/main/java/com/xit/biz/cmm/entity/ids/CmmCodeMIds.java new file mode 100644 index 0000000..ce28412 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/ids/CmmCodeMIds.java @@ -0,0 +1,29 @@ +package com.xit.biz.cmm.entity.ids; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.persistence.Column; +import java.io.Serializable; + +@Getter +@Setter +@EqualsAndHashCode +@NoArgsConstructor +public class CmmCodeMIds implements Serializable { + + @Schema(required = true, title = "코드그룹ID", example = "CODE_SAM", description = "공통코드그룹ID") + @Column(name = "code_grp_id", nullable = false, length = 20) + private String codeGrpId; + + @Schema(required = true, title = "대분류코드", example = "00001", description = "대분류 코드값") + @Column(name = "code_lcd", nullable = false, length = 10) + private String codeLcd; + + @Schema(required = true, title = "공통코드 코드", example = "001", description = "공통코드의 코드값") + @Column(name = "code_cd", nullable = false, length = 10) + private String codeCd; +} diff --git a/src/main/java/com/xit/biz/cmm/entity/ids/CmmCodeSIds.java b/src/main/java/com/xit/biz/cmm/entity/ids/CmmCodeSIds.java new file mode 100644 index 0000000..6c1946a --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/ids/CmmCodeSIds.java @@ -0,0 +1,34 @@ +package com.xit.biz.cmm.entity.ids; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.persistence.Column; +import java.io.Serializable; + +@Schema(name = "CmmCodeSIds", description = "소분류코드 PK(ClassIds) && 콤보코드 조회시 파라메터(searchKeyDto)로 사용") +@Getter +@Setter +@EqualsAndHashCode +@NoArgsConstructor +public class CmmCodeSIds implements Serializable { + + @Schema(required = true, title = "코드그룹ID", example = "G_CODE_SAM", description = "공통코드그룹ID") + @Column(name = "code_grp_id", nullable = false, length = 20) + private String codeGrpId; + + @Schema(required = true, title = "대분류코드", example = "L0001", description = "대분류 코드값") + @Column(name = "code_lcd", nullable = false, length = 10) + private String codeLcd; + + @Schema(required = true, title = "중분류코드", example = "M0001", description = "중분류 코드값") + @Column(name = "code_mcd", nullable = false, length = 10) + private String codeMcd; + + @Schema(required = true, title = "공통코드 코드", example = "001", description = "공통코드의 코드값") + @Column(name = "code_cd", nullable = false, length = 10) + private String codeCd; +} diff --git a/src/main/java/com/xit/biz/cmm/entity/ids/CmmFileDtlIds.java b/src/main/java/com/xit/biz/cmm/entity/ids/CmmFileDtlIds.java new file mode 100644 index 0000000..1513040 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/ids/CmmFileDtlIds.java @@ -0,0 +1,25 @@ +package com.xit.biz.cmm.entity.ids; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + + +import javax.persistence.Column; +import java.io.Serializable; + +//@Embeddable +@Getter +@Setter +@EqualsAndHashCode +@NoArgsConstructor +public class CmmFileDtlIds implements Serializable { + + @Schema(required = true, title = "파일MstID", example = " ", description = "FileMst ID") + @Column(name = "file_mst_id", nullable = false) + private String fileMstId; + + @Schema(required = true, title = "파일ID", example = " ", description = "UUID로 생성") + @Column(name = "file_id", nullable = false, length = 50) + private String fileId; + +} diff --git a/src/main/java/com/xit/biz/cmm/entity/ids/CmmUserRolePK.java b/src/main/java/com/xit/biz/cmm/entity/ids/CmmUserRolePK.java new file mode 100644 index 0000000..852dd3b --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/ids/CmmUserRolePK.java @@ -0,0 +1,18 @@ +package com.xit.biz.cmm.entity.ids; + +import lombok.*; + +import java.io.Serializable; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode +public class CmmUserRolePK implements Serializable { + private static final long SerialVersionUID = 1L; + + private Long cmmUserId; + + private Long roleId; +} diff --git a/src/main/java/com/xit/biz/cmm/entity/ids/CodeCmmField.java b/src/main/java/com/xit/biz/cmm/entity/ids/CodeCmmField.java new file mode 100644 index 0000000..bf89a63 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/ids/CodeCmmField.java @@ -0,0 +1,50 @@ +//package com.xit.biz.cmm.domain.ids; +// +//import com.xit.core.support.jpa.AuditEntity; +//import io.swagger.v3.oas.annotations.media.Schema; +//import lombok.*; +//import org.hibernate.annotations.ColumnDefault; +// +//import javax.persistence.*; +//import java.io.Serializable; +// +//@Embeddable +//@Getter +//@Setter +//@NoArgsConstructor +//@EqualsAndHashCode(callSuper = false) +//public class CodeCmmField implements Serializable { +// +// @Schema(required = true, title = "코드명", example = "코드예제", description = "각 분류에 해당하는 코드명") +// @Column(name = "code_nm", nullable = false, length = 20) +// private String codeNm; +// +// @Schema(title = "코드상세", example = "공통 코드 상세 예제", description = "각 분류에 해당하는 코드 상세") +// @Column(name = "code_remark", length = 50) +// private String codeRemark; +// +// @Schema(title = "추가필드1") +// @Column(name = "code_meta_1", length = 10) +// private String codeMeta1; +// +// @Schema(title = "추가필드2") +// @Column(name = "code_meta_2", length = 10) +// private String codeMeta2; +// +// @Schema(title = "추가필드3") +// @Column(name = "code_meta_3", length = 10) +// private String codeMeta3; +// +// @Schema(title = "추가필드4") +// @Column(name = "code_meta_4", length = 10) +// private String codeMeta4; +// +// @Schema(title = "추가필드5") +// @Column(name = "code_meta_5", length = 10) +// private String codeMeta5; +// +// @Schema(required = true, title = "코드정렬순서", example = "99") +// @Column(name = "code_ordr", nullable = false, columnDefinition = "int(3)") @ColumnDefault(value = "99") +// private Integer codeOrdr; +// +//} diff --git a/src/main/java/com/xit/biz/cmm/entity/spec/CmmCodeSpecification.java b/src/main/java/com/xit/biz/cmm/entity/spec/CmmCodeSpecification.java new file mode 100644 index 0000000..4ea5b11 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/entity/spec/CmmCodeSpecification.java @@ -0,0 +1,65 @@ +package com.xit.biz.cmm.entity.spec; + +import com.xit.biz.cmm.dto.CmmCodeDto; +import org.springframework.data.jpa.domain.Specification; + +import java.time.LocalDateTime; + +/** + * @author Lim, Jong Uk (minuk926) + * @since 2021-08-10 + */ +public class CmmCodeSpecification { + + public static Specification eqCodeGrpId(String codeGrpId){ + return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("codeGrpId"), codeGrpId); + } + + public static Specification eqCodeLcd(String codeLcd){ + return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("codeLcd"), codeLcd); + } + + public static Specification eqCodeMcd(String codeMcd){ + return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("codeMcd"), codeMcd); + } + + public static Specification eqCodeCd(String codeCd){ + return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("codeCd"), codeCd); + } + + public static Specification eqCodeNm(String codeNm){ + return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("codeNm"), codeNm); + } + + public static Specification likeCodeRemark(String codeRemark){ + return (root, query, criteriaBuilder) -> criteriaBuilder.like(root.get("codeRemark"), codeRemark + "%"); + } + + public static Specification eqCodeMeta1(String codeMeta1){ + return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("codeMeta1"), codeMeta1); + } + + public static Specification eqCodeMeta2(String codeMeta2){ + return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("codeMeta2"), codeMeta2); + } + + public static Specification eqCodeMeta3(String codeMeta3){ + return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("codeMeta3"), codeMeta3); + } + + public static Specification eqCodeMeta4(String codeMeta4){ + return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("codeMeta4"), codeMeta4); + } + + public static Specification eqCodeMeta5(String codeMeta5){ + return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("codeMeta5"), codeMeta5); + } + + public static Specification eqUseYn(String useYn){ + return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("useYn"), useYn); + } + + public static Specification betweenCreatedBy(LocalDateTime startDatetime, LocalDateTime endDatetime){ + return (root, query, criteriaBuilder) -> criteriaBuilder.between(root.get("createdBy"), startDatetime, endDatetime); + } +} diff --git a/src/main/java/com/xit/biz/cmm/mapper/ICmmBoardMapper.java b/src/main/java/com/xit/biz/cmm/mapper/ICmmBoardMapper.java new file mode 100644 index 0000000..737c53a --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/mapper/ICmmBoardMapper.java @@ -0,0 +1,11 @@ +package com.xit.biz.cmm.mapper; + +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; +import java.util.Map; + +@Mapper +public interface ICmmBoardMapper { + List> selectBoardList(Map map); +} diff --git a/src/main/java/com/xit/biz/cmm/repository/ICmmAddressRepository.java b/src/main/java/com/xit/biz/cmm/repository/ICmmAddressRepository.java new file mode 100644 index 0000000..daba6c8 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/repository/ICmmAddressRepository.java @@ -0,0 +1,9 @@ +package com.xit.biz.cmm.repository; + +import com.xit.biz.cmm.entity.CmmAddress; +import com.xit.biz.cmm.entity.ids.CmmAddressIds; +import org.springframework.data.repository.CrudRepository; + +public interface ICmmAddressRepository extends CrudRepository { + +} diff --git a/src/main/java/com/xit/biz/cmm/repository/ICmmBoardRepository.java b/src/main/java/com/xit/biz/cmm/repository/ICmmBoardRepository.java new file mode 100644 index 0000000..bc21025 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/repository/ICmmBoardRepository.java @@ -0,0 +1,10 @@ +package com.xit.biz.cmm.repository; + +import com.xit.biz.cmm.entity.CmmBoard; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ICmmBoardRepository extends JpaRepository { +// Page findByBoardIdGreaterThan(final Long boardId, final Pageable paging); +// Optional findByBoardId(final Long boardId); +// void deleteByBoardId(Long boardId); +} diff --git a/src/main/java/com/xit/biz/cmm/repository/ICmmCodeGrpRepository.java b/src/main/java/com/xit/biz/cmm/repository/ICmmCodeGrpRepository.java new file mode 100644 index 0000000..b668bf6 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/repository/ICmmCodeGrpRepository.java @@ -0,0 +1,10 @@ +package com.xit.biz.cmm.repository; + +import com.xit.biz.cmm.entity.CmmCodeGrp; +import com.xit.biz.cmm.dto.CmmCodeDto; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface ICmmCodeGrpRepository extends JpaRepository, JpaSpecificationExecutor { + CmmCodeGrp findByCodeGrpId(String codeGrpId); +} diff --git a/src/main/java/com/xit/biz/cmm/repository/ICmmCodeLRepostory.java b/src/main/java/com/xit/biz/cmm/repository/ICmmCodeLRepostory.java new file mode 100644 index 0000000..62356ea --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/repository/ICmmCodeLRepostory.java @@ -0,0 +1,17 @@ +package com.xit.biz.cmm.repository; + +import com.xit.biz.cmm.entity.CmmCodeL; +import com.xit.biz.cmm.entity.ids.CmmCodeLIds; +import com.xit.biz.cmm.dto.CmmCodeDto; +import com.xit.biz.cmm.dto.ComboCodeDto; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ICmmCodeLRepostory extends JpaRepository, JpaSpecificationExecutor { + @Query(value = "select c.codeCd as code, c.codeNm as value from #{#entityName} c where c.codeGrpId = :codeGrpId and c.useYn = 'Y' order by c.codeOrdr asc") + List queryComboCode(@Param("codeGrpId") String codeGrpId); +} diff --git a/src/main/java/com/xit/biz/cmm/repository/ICmmCodeMRepository.java b/src/main/java/com/xit/biz/cmm/repository/ICmmCodeMRepository.java new file mode 100644 index 0000000..47ced9c --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/repository/ICmmCodeMRepository.java @@ -0,0 +1,17 @@ +package com.xit.biz.cmm.repository; + +import com.xit.biz.cmm.entity.CmmCodeM; +import com.xit.biz.cmm.entity.ids.CmmCodeMIds; +import com.xit.biz.cmm.dto.CmmCodeDto; +import com.xit.biz.cmm.dto.ComboCodeDto; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ICmmCodeMRepository extends JpaRepository, JpaSpecificationExecutor { + @Query(value = "select c.codeCd as code, c.codeNm as value from #{#entityName} c where c.codeGrpId = :codeGrpId and c.codeLcd = :codeLcd and c.useYn = 'Y' order by c.codeOrdr asc") + List queryComboCode(@Param("codeGrpId")String codeGrpId, @Param("codeLcd")String codeLcd); +} diff --git a/src/main/java/com/xit/biz/cmm/repository/ICmmCodeSRepository.java b/src/main/java/com/xit/biz/cmm/repository/ICmmCodeSRepository.java new file mode 100644 index 0000000..174379f --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/repository/ICmmCodeSRepository.java @@ -0,0 +1,17 @@ +package com.xit.biz.cmm.repository; + +import com.xit.biz.cmm.entity.CmmCodeS; +import com.xit.biz.cmm.entity.ids.CmmCodeSIds; +import com.xit.biz.cmm.dto.CmmCodeDto; +import com.xit.biz.cmm.dto.ComboCodeDto; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ICmmCodeSRepository extends JpaRepository, JpaSpecificationExecutor { + @Query(value = "select c.codeCd as code, c.codeNm as value from #{#entityName} c where c.codeGrpId = :codeGrpId and c.codeLcd = :codeLcd and c.codeMcd = :codeMcd and c.useYn = 'Y' order by c.codeOrdr asc") + List queryComboCode(@Param("codeGrpId")String codeGrpId, @Param("codeLcd")String codeLcd, @Param("codeMcd")String codeMcd); +} diff --git a/src/main/java/com/xit/biz/cmm/repository/ICmmFileDtlRepository.java b/src/main/java/com/xit/biz/cmm/repository/ICmmFileDtlRepository.java new file mode 100644 index 0000000..345665d --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/repository/ICmmFileDtlRepository.java @@ -0,0 +1,13 @@ +package com.xit.biz.cmm.repository; + +import com.xit.biz.cmm.entity.CmmFileDtl; +import com.xit.biz.cmm.entity.ids.CmmFileDtlIds; +import org.springframework.data.repository.CrudRepository; + +import java.util.List; + +public interface ICmmFileDtlRepository extends CrudRepository { + List findByFileMstId(String fileMstId); + CmmFileDtl findByFileMstIdAndOrgFileNmIgnoreCase(String fileMstId, String OrgFileNm); + //List findAllById(CmmFileDtlIds cmmFileDtlIds); +} diff --git a/src/main/java/com/xit/biz/cmm/repository/ICmmFileMstRepository.java b/src/main/java/com/xit/biz/cmm/repository/ICmmFileMstRepository.java new file mode 100644 index 0000000..12146ab --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/repository/ICmmFileMstRepository.java @@ -0,0 +1,7 @@ +package com.xit.biz.cmm.repository; + +import com.xit.biz.cmm.entity.CmmFileMst; +import org.springframework.data.repository.CrudRepository; + +public interface ICmmFileMstRepository extends CrudRepository { +} diff --git a/src/main/java/com/xit/biz/cmm/repository/ICmmRoleMenuRepository.java b/src/main/java/com/xit/biz/cmm/repository/ICmmRoleMenuRepository.java new file mode 100644 index 0000000..21bf840 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/repository/ICmmRoleMenuRepository.java @@ -0,0 +1,7 @@ +package com.xit.biz.cmm.repository; + +import com.xit.biz.cmm.entity.CmmRoleMenu; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ICmmRoleMenuRepository extends JpaRepository { +} diff --git a/src/main/java/com/xit/biz/cmm/repository/ICmmRoleRepository.java b/src/main/java/com/xit/biz/cmm/repository/ICmmRoleRepository.java new file mode 100644 index 0000000..5db8b06 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/repository/ICmmRoleRepository.java @@ -0,0 +1,7 @@ +package com.xit.biz.cmm.repository; + +import com.xit.biz.cmm.entity.CmmRole; +import org.springframework.data.repository.CrudRepository; + +public interface ICmmRoleRepository extends CrudRepository { +} diff --git a/src/main/java/com/xit/biz/cmm/repository/ICmmRoleUserRepository.java b/src/main/java/com/xit/biz/cmm/repository/ICmmRoleUserRepository.java new file mode 100644 index 0000000..28a716a --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/repository/ICmmRoleUserRepository.java @@ -0,0 +1,10 @@ +package com.xit.biz.cmm.repository; + +import com.xit.biz.cmm.entity.CmmRoleUer; +import org.springframework.data.repository.CrudRepository; + +public interface ICmmRoleUserRepository extends CrudRepository { +// List findByCmmUserId(Long cmmUserId); +// +// List findByRoleId(Long roleId); +} diff --git a/src/main/java/com/xit/biz/cmm/repository/ICmmUserRepository.java b/src/main/java/com/xit/biz/cmm/repository/ICmmUserRepository.java new file mode 100644 index 0000000..cc7d0f3 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/repository/ICmmUserRepository.java @@ -0,0 +1,15 @@ +package com.xit.biz.cmm.repository; + +import com.xit.biz.cmm.entity.CmmUser; +import org.springframework.data.jpa.repository.JpaRepository; + +import javax.validation.constraints.NotNull; +import java.util.Optional; + +public interface ICmmUserRepository extends JpaRepository { + CmmUser findByCmmUserId(@NotNull final String cmmUserId); + Optional findByUserId(@NotNull final String userId); + boolean existsByUserId(@NotNull final String userId); + + //Optional findOneWithAuthorities(String userId); +} diff --git a/src/main/java/com/xit/biz/cmm/service/ICmmAddressService.java b/src/main/java/com/xit/biz/cmm/service/ICmmAddressService.java new file mode 100644 index 0000000..ab06d0f --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/service/ICmmAddressService.java @@ -0,0 +1,12 @@ +package com.xit.biz.cmm.service; + +import com.xit.biz.cmm.entity.CmmAddress; +import com.xit.biz.cmm.entity.ids.CmmAddressIds; +import com.xit.core.support.jpa.IJpaOperation; + +/** + * @author Lim, Jong Uk (minuk926) + * @since 2021-07-16 + */ +public interface ICmmAddressService extends IJpaOperation { +} diff --git a/src/main/java/com/xit/biz/cmm/service/ICmmBoardService.java b/src/main/java/com/xit/biz/cmm/service/ICmmBoardService.java new file mode 100644 index 0000000..dd2fdbd --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/service/ICmmBoardService.java @@ -0,0 +1,16 @@ +package com.xit.biz.cmm.service; + +import com.xit.biz.cmm.entity.CmmBoard; +import com.xit.core.support.jpa.IJpaOperation; + +import java.util.List; +import java.util.Map; + +public interface ICmmBoardService extends IJpaOperation { + /** + * use mybatis mapper + * @param map Map + * @return List + */ + List> selectBoardList(Map map); +} diff --git a/src/main/java/com/xit/biz/cmm/service/ICmmCodeService.java b/src/main/java/com/xit/biz/cmm/service/ICmmCodeService.java new file mode 100644 index 0000000..dbc7475 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/service/ICmmCodeService.java @@ -0,0 +1,29 @@ +package com.xit.biz.cmm.service; + +import com.xit.biz.cmm.entity.CmmCodeGrp; +import com.xit.biz.cmm.entity.ids.CmmCodeSIds; +import com.xit.biz.cmm.dto.CmmCodeDto; +import com.xit.biz.cmm.dto.ComboCodeDto; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +/** + * @author Lim, Jong Uk (minuk926) + * @since 2021-08-02 + */ +public interface ICmmCodeService { + List findComboCodes(CmmCodeSIds searchKeyDto); + + CmmCodeGrp saveCmmCodeGrp(CmmCodeGrp cmmCodeGrp); +// CmmCodeL saveCmmCodeL(CmmCodeL cmmCodeL); +// CmmCodeM saveCmmCodeM(CmmCodeM cmmCodeM); +// CmmCodeS saveCmmCodeS(CmmCodeS cmmCodeS); + + Page findCmmCodeGrps(CmmCodeDto cmmCodeDto, Pageable pageable); + + List findCmmCodes(CmmCodeDto cmmCodeDto); + + Object saveCmmCode(CmmCodeDto cmmCodeDto); +} diff --git a/src/main/java/com/xit/biz/cmm/service/ICmmFileService.java b/src/main/java/com/xit/biz/cmm/service/ICmmFileService.java new file mode 100644 index 0000000..247f40c --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/service/ICmmFileService.java @@ -0,0 +1,14 @@ +package com.xit.biz.cmm.service; + +import com.xit.biz.cmm.entity.CmmFileMst; +import org.springframework.web.multipart.MultipartFile; + +/** + * @author Lim, Jong Uk (minuk926) + * @since 2021-07-16 + */ +public interface ICmmFileService { + CmmFileMst findFiles(String fileMstId); + + CmmFileMst saveFiles(CmmFileMst cmmFileMst, MultipartFile[] files); +} diff --git a/src/main/java/com/xit/biz/cmm/service/ICmmRoleMenuService.java b/src/main/java/com/xit/biz/cmm/service/ICmmRoleMenuService.java new file mode 100644 index 0000000..dff3c4e --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/service/ICmmRoleMenuService.java @@ -0,0 +1,7 @@ +package com.xit.biz.cmm.service; + +import com.xit.biz.cmm.entity.CmmRoleMenu; +import com.xit.core.support.jpa.IJpaOperation; + +public interface ICmmRoleMenuService extends IJpaOperation { +} diff --git a/src/main/java/com/xit/biz/cmm/service/ICmmRoleService.java b/src/main/java/com/xit/biz/cmm/service/ICmmRoleService.java new file mode 100644 index 0000000..f46deaf --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/service/ICmmRoleService.java @@ -0,0 +1,8 @@ +package com.xit.biz.cmm.service; + +import com.xit.biz.cmm.entity.CmmRole; +import com.xit.core.support.jpa.IJpaOperation; + +public interface ICmmRoleService extends IJpaOperation { + +} diff --git a/src/main/java/com/xit/biz/cmm/service/ICmmRoleUserService.java b/src/main/java/com/xit/biz/cmm/service/ICmmRoleUserService.java new file mode 100644 index 0000000..0b234ea --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/service/ICmmRoleUserService.java @@ -0,0 +1,8 @@ +package com.xit.biz.cmm.service; + +import com.xit.biz.cmm.entity.CmmRoleUer; +import com.xit.core.support.jpa.IJpaOperation; + +public interface ICmmRoleUserService extends IJpaOperation { + +} diff --git a/src/main/java/com/xit/biz/cmm/service/ICmmUserService.java b/src/main/java/com/xit/biz/cmm/service/ICmmUserService.java new file mode 100644 index 0000000..2c215eb --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/service/ICmmUserService.java @@ -0,0 +1,26 @@ +package com.xit.biz.cmm.service; + +import com.xit.biz.cmm.entity.CmmUser; +import com.xit.biz.cmm.dto.CmmUserDto; +import com.xit.core.support.jpa.IJpaOperation; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.Optional; + +//public interface ICmmUserService { +public interface ICmmUserService extends IJpaOperation { + Page findCmmUsers(CmmUser cmmUser, Pageable pageable); + Iterable findCmmUsers2(CmmUser cmmUser); + CmmUser findByCmmUserId(final String cmmUserId); + void deleteByCmmUserId(String cmmUserId); + + Optional findByUserId(String userId); +// +// CmmUserEntity save(CmmUserEntity cmmUserEntity); +// +// List findAll(); +// Page findAll(Pageable pageable); +// + +} diff --git a/src/main/java/com/xit/biz/cmm/service/impl/CmmAddressService.java b/src/main/java/com/xit/biz/cmm/service/impl/CmmAddressService.java new file mode 100644 index 0000000..8e419a7 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/service/impl/CmmAddressService.java @@ -0,0 +1,29 @@ +package com.xit.biz.cmm.service.impl; + +import com.xit.biz.cmm.entity.CmmAddress; +import com.xit.biz.cmm.entity.ids.CmmAddressIds; +import com.xit.biz.cmm.repository.ICmmAddressRepository; +import com.xit.biz.cmm.service.ICmmAddressService; +import com.xit.core.support.jpa.AbstractJpaCrudService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Service; + + +/** + * @author Lim, Jong Uk (minuk926) + * @since 2021-07-16 + */ +@Service +@RequiredArgsConstructor +@Slf4j +public class CmmAddressService extends AbstractJpaCrudService implements ICmmAddressService { + + private final ICmmAddressRepository cmmAddressRepository; + + @Override + protected CrudRepository getRepository() { + return cmmAddressRepository; + } +} diff --git a/src/main/java/com/xit/biz/cmm/service/impl/CmmBoardService.java b/src/main/java/com/xit/biz/cmm/service/impl/CmmBoardService.java new file mode 100644 index 0000000..e210a3d --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/service/impl/CmmBoardService.java @@ -0,0 +1,45 @@ +package com.xit.biz.cmm.service.impl; + +import com.xit.biz.cmm.entity.CmmBoard; +import com.xit.biz.cmm.repository.ICmmBoardRepository; +import com.xit.biz.cmm.service.ICmmBoardService; +import com.xit.biz.cmm.mapper.ICmmBoardMapper; +import com.xit.core.support.jpa.AbstractJpaService; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Service +public class CmmBoardService extends AbstractJpaService implements ICmmBoardService { + private final ICmmBoardRepository boardRepository; + private final ICmmBoardMapper cmmBoardMapper; + + public CmmBoardService(ICmmBoardRepository boardRepository, ICmmBoardMapper cmmBoardMapper) { + this.boardRepository = boardRepository; + this.cmmBoardMapper = cmmBoardMapper; + } + + @Override + protected PagingAndSortingRepository getRepository() { + return boardRepository; + } + + @Transactional(readOnly = true) + public Optional findByBoardId(Long boardId) { + return boardRepository.findById(boardId); //.orElseThrow(() -> new NotFoundException("Board not found : " + boardId)); + } + @Transactional + public void deleteByBoardId(Long boardId) { + boardRepository.deleteById(boardId); + } + + + @Override + public List> selectBoardList(Map map) { + return cmmBoardMapper.selectBoardList(map); + } +} diff --git a/src/main/java/com/xit/biz/cmm/service/impl/CmmCodeService.java b/src/main/java/com/xit/biz/cmm/service/impl/CmmCodeService.java new file mode 100644 index 0000000..883712f --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/service/impl/CmmCodeService.java @@ -0,0 +1,168 @@ +package com.xit.biz.cmm.service.impl; + +import com.xit.biz.cmm.dto.struct.CmmCodeGrpMapstruct; +import com.xit.biz.cmm.dto.struct.CmmCodeLMapstruct; +import com.xit.biz.cmm.dto.struct.CmmCodeMMapstruct; +import com.xit.biz.cmm.dto.struct.CmmCodeSMapstruct; +import com.xit.biz.cmm.entity.CmmCodeGrp; +import com.xit.biz.cmm.entity.CmmCodeL; +import com.xit.biz.cmm.entity.CmmCodeM; +import com.xit.biz.cmm.entity.CmmCodeS; +import com.xit.biz.cmm.entity.ids.CmmCodeSIds; +import com.xit.biz.cmm.dto.CmmCodeDto; +import com.xit.biz.cmm.dto.ComboCodeDto; +import com.xit.biz.cmm.repository.ICmmCodeGrpRepository; +import com.xit.biz.cmm.repository.ICmmCodeLRepostory; +import com.xit.biz.cmm.repository.ICmmCodeMRepository; +import com.xit.biz.cmm.repository.ICmmCodeSRepository; +import com.xit.biz.cmm.service.ICmmCodeService; +import com.xit.core.util.AssertUtils; +import com.xit.core.util.Checks; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.mapstruct.factory.Mappers; +import org.springframework.data.domain.*; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.util.List; + + +@Slf4j +@Service +@RequiredArgsConstructor +public class CmmCodeService implements ICmmCodeService { + + private final ICmmCodeGrpRepository cmmCodeGrpRepository; + private final ICmmCodeLRepostory cmmCodeLRepository; + private final ICmmCodeMRepository cmmCodeMRepository; + private final ICmmCodeSRepository cmmCodeSRepository; + + private CmmCodeGrpMapstruct codeGrpstruct = Mappers.getMapper(CmmCodeGrpMapstruct.class); + private CmmCodeLMapstruct codeLstruct = Mappers.getMapper(CmmCodeLMapstruct.class); + private CmmCodeMMapstruct codeMstruct = Mappers.getMapper(CmmCodeMMapstruct.class); + private CmmCodeSMapstruct codeSstruct = Mappers.getMapper(CmmCodeSMapstruct.class); + + @Override + public List findComboCodes(CmmCodeSIds searchKeyDto) { + AssertUtils.isTrue(!Checks.isEmpty(searchKeyDto.getCodeGrpId()), "조회할 코드그룹을 선택해 주세요."); + + // 소분류 코드 조회 + if(StringUtils.hasText(searchKeyDto.getCodeMcd())){ + AssertUtils.isTrue(!Checks.isEmpty(searchKeyDto.getCodeLcd()), "대분류 코드가 선택되지 않았습니다."); + return cmmCodeSRepository.queryComboCode(searchKeyDto.getCodeGrpId(), searchKeyDto.getCodeLcd(), searchKeyDto.getCodeMcd()); + } + + // 중분류 코드 조회 + if(StringUtils.hasText(searchKeyDto.getCodeLcd())){ + return cmmCodeMRepository.queryComboCode(searchKeyDto.getCodeGrpId(), searchKeyDto.getCodeLcd()); + } + + // 대분류 코드 조회 + return cmmCodeLRepository.queryComboCode(searchKeyDto.getCodeGrpId()); + } + + @Override + public Page findCmmCodeGrps(CmmCodeDto cmmCodeDto, Pageable pageable) { + Sort sort = Sort.by(Sort.Direction.ASC, "codeOrdr"); + pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), sort); + Example example = Example.of(codeGrpstruct.toEntity(cmmCodeDto), ExampleMatcher.matchingAny()); + return cmmCodeGrpRepository.findAll(example, pageable); + } + + @Override + public List findCmmCodes(CmmCodeDto cmmCodeDto) { + Sort sort = Sort.by(Sort.Direction.ASC, "codeOrdr"); + AssertUtils.isTrue(!Checks.isEmpty(cmmCodeDto.getCodeGrpId()), "조회할 코드그룹을 선택해 주세요."); + + // 소분류 코드 조회 + if(StringUtils.hasText(cmmCodeDto.getCodeMcd())){ + AssertUtils.isTrue(!Checks.isEmpty(cmmCodeDto.getCodeLcd()), "대분류 코드가 선택되지 않았습니다."); + Example example = Example.of(codeSstruct.toEntity(cmmCodeDto), ExampleMatcher.matchingAny()); + return cmmCodeSRepository.findAll(example, sort); + } + + // 중분류 코드 조회 + if(StringUtils.hasText(cmmCodeDto.getCodeLcd())){ + Example example = Example.of(codeMstruct.toEntity(cmmCodeDto), ExampleMatcher.matchingAny()); + return cmmCodeMRepository.findAll(example, sort); + } + + // 대분류 코드 조회 + Example example = Example.of(codeLstruct.toEntity(cmmCodeDto), ExampleMatcher.matchingAny()); + return cmmCodeLRepository.findAll(example, sort); + } + + @Override + @Transactional + public CmmCodeGrp saveCmmCodeGrp(CmmCodeGrp cmmCodeGrp) { + AssertUtils.isTrue(!Checks.isEmpty(cmmCodeGrp.getCodeGrpId()), "코드 그룹을 입력해 주세요."); + AssertUtils.isTrue(!Checks.isEmpty(cmmCodeGrp.getCodeNm()), "코드 그룹명 입력해 주세요."); + AssertUtils.isTrue(!Checks.isEmpty(cmmCodeGrp.getCodeOrdr()), "정렬 순서를 입력해 주세요."); + return cmmCodeGrpRepository.save(cmmCodeGrp); + } + +// @Override +// @Transactional +// public CmmCodeL saveCmmCodeL(CmmCodeL cmmCodeL) { +// AssertUtils.state(!Checks.isEmpty(cmmCodeL.getCodeGrpId()), "코드 그룹을 입력해 주세요."); +// AssertUtils.state(!Checks.isEmpty(cmmCodeL.getCodeCd()), "대분류 코드를 입력해 주세요."); +// AssertUtils.state(!Checks.isEmpty(cmmCodeL.getCodeNm()), "대분류 코드명 입력해 주세요."); +// AssertUtils.state(!Checks.isEmpty(cmmCodeL.getCodeOrdr()), "정렬 순서를 입력해 주세요."); +// return cmmCodeLRepository.save(cmmCodeL); +// } +// +// @Override +// @Transactional +// public CmmCodeM saveCmmCodeM(CmmCodeM cmmCodeM) { +// AssertUtils.state(!Checks.isEmpty(cmmCodeM.getCodeGrpId()), "코드 그룹을 입력해 주세요."); +// AssertUtils.state(!Checks.isEmpty(cmmCodeM.getCodeLcd()), "대분류 코드를 입력해 주세요."); +// AssertUtils.state(!Checks.isEmpty(cmmCodeM.getCodeCd()), "중분류 코드를 입력해 주세요."); +// AssertUtils.state(!Checks.isEmpty(cmmCodeM.getCodeNm()), "중분류 코드명 입력해 주세요."); +// AssertUtils.state(!Checks.isEmpty(cmmCodeM.getCodeOrdr()), "정렬 순서를 입력해 주세요."); +// return cmmCodeMRepository.save(cmmCodeM); +// } +// +// @Override +// @Transactional +// public CmmCodeS saveCmmCodeS(CmmCodeS cmmCodeS) { +// AssertUtils.state(!Checks.isEmpty(cmmCodeS.getCodeGrpId()), "코드그룹을 입력해 주세요."); +// AssertUtils.state(!Checks.isEmpty(cmmCodeS.getCodeLcd()), "대분류 코드를 입력해 주세요."); +// AssertUtils.state(!Checks.isEmpty(cmmCodeS.getCodeMcd()), "중분류 코드를 입력해 주세요."); +// AssertUtils.state(!Checks.isEmpty(cmmCodeS.getCodeCd()), "소분류 코드를 입력해 주세요."); +// AssertUtils.state(!Checks.isEmpty(cmmCodeS.getCodeNm()), "소분류 코드명 입력해 주세요."); +// AssertUtils.state(!Checks.isEmpty(cmmCodeS.getCodeOrdr()), "정렬 순서를 입력해 주세요."); +// return cmmCodeSRepository.save(cmmCodeS); +// } + +// private void validate(Class clz){ +// AssertUtils.state(!Checks.isEmpty(cmmCodeS.getCodeGrpId()), "코드그룹을 입력해 주세요."); +// AssertUtils.state(!Checks.isEmpty(cmmCodeS.getCodeCd()), "대분류코드를 입력해 주세요."); +// AssertUtils.state(!Checks.isEmpty(cmmCodeS.getCodeNm()), "대분류코드명 입력해 주세요."); +// AssertUtils.state(!Checks.isEmpty(cmmCodeS.getCodeOrdr()), "정렬순서를 입력해 주세요."); +// } + + @Transactional + @Override + public Object saveCmmCode(CmmCodeDto cmmCodeDto) { + AssertUtils.isTrue(!Checks.isEmpty(cmmCodeDto.getCodeGrpId()), "코드그룹을 입력해 주세요."); + AssertUtils.isTrue(!Checks.isEmpty(cmmCodeDto.getCodeCd()), "코드를 입력해 주세요."); + AssertUtils.isTrue(!Checks.isEmpty(cmmCodeDto.getCodeNm()), "코드명을 입력해 주세요."); + + // 소분류 코드 + if(StringUtils.hasText(cmmCodeDto.getCodeMcd())){ + AssertUtils.isTrue(!Checks.isEmpty(cmmCodeDto.getCodeLcd()), "대분류 코드를 입력해 주세요."); + return cmmCodeSRepository.save(codeSstruct.toEntity(cmmCodeDto)); + } + + // 중분류 코드 + if(StringUtils.hasText(cmmCodeDto.getCodeLcd())){ + return cmmCodeMRepository.save(codeMstruct.toEntity(cmmCodeDto)); + } + + // 대분류 코드 + return cmmCodeLRepository.save(codeLstruct.toEntity(cmmCodeDto)); + } + +} diff --git a/src/main/java/com/xit/biz/cmm/service/impl/CmmFileService.java b/src/main/java/com/xit/biz/cmm/service/impl/CmmFileService.java new file mode 100644 index 0000000..ec8d79b --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/service/impl/CmmFileService.java @@ -0,0 +1,167 @@ +package com.xit.biz.cmm.service.impl; + +import com.xit.biz.cmm.entity.CmmFileDtl; +import com.xit.biz.cmm.entity.CmmFileMst; +import com.xit.biz.cmm.entity.ids.CmmFileDtlIds; +import com.xit.biz.cmm.repository.ICmmFileDtlRepository; +import com.xit.biz.cmm.repository.ICmmFileMstRepository; +import com.xit.biz.cmm.service.ICmmFileService; + +import com.xit.core.util.AssertUtils; +import com.xit.core.util.DateUtil; +import io.jsonwebtoken.lang.Assert; +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.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +@Slf4j +@Service +@RequiredArgsConstructor +public class CmmFileService implements ICmmFileService { + + @Value("${file.cmm.upload.path:/data/file/upload}") + private String uploadPath; + + @Value("${file.cmm.upload.allow.ext:}") + private String allowExt; + + @Value("${file.cmm.upload.max.size:1024}") + private long maxSize; + + private final ICmmFileMstRepository cmmFileMstRepository; + private final ICmmFileDtlRepository cmmFileDtlRepository; + + @Override + public CmmFileMst findFiles(String fileMstId) { + Assert.notNull(fileMstId, "대상 파일[fileMstId]을 선택해 주세요."); + + CmmFileMst cmmFileMst = cmmFileMstRepository.findById(fileMstId).orElse(null); +// CmmFileDtlIds cmmFileDtlIds = new CmmFileDtlIds(); +// cmmFileDtlIds.setFileMstId(cmmFileMst.getFileMstId()); + + if(cmmFileMst != null) cmmFileMst.getCmmFileDtls().addAll(cmmFileDtlRepository.findByFileMstId(cmmFileMst.getFileMstId())); + return cmmFileMst; + } + + /** + * 파일 등록 + * 신규등록시는 CmmFileMst.fileMstId가 null, 변경시 not null + * 변경시 동일한 파일 이름이 존재하면 DB 및 해당 파일 삭제후 신규로 생성 + * + * @param cmmFileMst CmmFileMst + * @param files MultipartFile[] + * @return CmmFileMst + */ + @Override + @Transactional + public CmmFileMst saveFiles(CmmFileMst cmmFileMst, MultipartFile[] files) { + Assert.notNull(cmmFileMst, "파일 정보가 존재하지 않습니다."); + Assert.notNull(cmmFileMst.getFileCtgCd(), "파일 구분 코드[fileCtgCd] 정보가 존재하지 않습니다."); + Assert.notNull(cmmFileMst.getFileBizId(), "파일 업무 ID[fileBizId] 정보가 존재하지 않습니다."); + Assert.notNull(files, "대상 파일이 존재하지 않습니다."); + + String makePath = ""; + boolean isCheckExists = StringUtils.hasText(cmmFileMst.getFileMstId()); + String fileMstId = null; + + // file Master key set + // file Master 생성 + if(isCheckExists) fileMstId = cmmFileMst.getFileMstId(); + else fileMstId = UUID.randomUUID().toString().replaceAll("-", ""); + cmmFileMst.setFileMstId(fileMstId); + CmmFileMst savedCmmFileMst = cmmFileMstRepository.save(cmmFileMst); + + makePath = File.separator + DateUtil.getToday(""); + String fileUploadPath = this.uploadPath + makePath; + File file = new File(fileUploadPath); + if(!file.exists()) file.mkdirs(); + + List cmmFileDtls = new ArrayList<>(); + for(MultipartFile mf : files){ + if(!mf.isEmpty()) { + String orgFileName = ""; + try { + orgFileName = StringUtils.cleanPath(Objects.requireNonNull(mf.getOriginalFilename())); + CmmFileDtl cmmFileDtl = CmmFileDtl.builder() + .fileMstId(savedCmmFileMst.getFileMstId()) + .fileId(UUID.randomUUID().toString().replaceAll("-", "")) + .fileUpldPath(makePath) + .contentType(mf.getContentType()) + .orgFileNm(orgFileName) + .fileExt(orgFileName.substring(orgFileName.lastIndexOf(".") + 1).toLowerCase()) + .fileSize(mf.getSize()) + .build(); + + if (StringUtils.hasText(allowExt) && !allowExt.contains(cmmFileDtl.getFileExt())) { + log.error("Not support extention :: {}", orgFileName); + //TODO : 에러처리 + //return RestError.of(String.format("Not support extention :: %s", orgFileName)); + AssertUtils.isTrue(false, String.format("Not support extention :: %s", orgFileName)); + } + if (cmmFileDtl.getFileSize() > (maxSize * 1024)) { + log.error("Over size :: {}[{}]", orgFileName, cmmFileDtl.getFileSize()); + //TODO : 에러처리 + //return RestError.of(String.format("Over size :: %s[%l]", orgFileName, cmmFileDtl.getFileSize())); + AssertUtils.isTrue(false, String.format("Over size :: %s[%d]", orgFileName, cmmFileDtl.getFileSize())); + } + + //동일파일 삭제후 정보 저장 + if(isCheckExists) removeExistsUploadFile(fileMstId, orgFileName); + CmmFileDtl savedCmmFileDtl = cmmFileDtlRepository.save(cmmFileDtl); + + // 파일 전송 + mf.transferTo(new File(fileUploadPath + File.separator + savedCmmFileDtl.getFileId())); + cmmFileDtls.add(savedCmmFileDtl); + + // inputStream을 가져와서 + // copyOfLocation (저장위치)로 파일을 쓴다. + // copy의 옵션은 기존에 존재하면 REPLACE(대체한다), 오버라이딩 한다 + //Files.copy(multipartFile.getInputStream(), copyOfLocation, StandardCopyOption.REPLACE_EXISTING); + + }catch(IOException e){ + String errMsg = String.format("File Upload Error :: %s", orgFileName); + //TODO : 에러처리 + //return RestError.of(String.format("File Upload Error :: %s", orgFileName)); + AssertUtils.isTrue(false, String.format("File Upload Error :: %s", orgFileName)); + } + } + } + savedCmmFileMst.getCmmFileDtls().addAll(cmmFileDtls); + return savedCmmFileMst; + } + + @Transactional + public void removeExistsUploadFile(String fileMstId, String orgFileNm){ + CmmFileDtl cmmFileDtl = cmmFileDtlRepository.findByFileMstIdAndOrgFileNmIgnoreCase(fileMstId, orgFileNm); + if(cmmFileDtl != null){ + CmmFileDtlIds cmmFileDtlIds = new CmmFileDtlIds(); + cmmFileDtlIds.setFileMstId(fileMstId); + cmmFileDtlIds.setFileId(cmmFileDtl.getFileId()); + cmmFileDtlRepository.deleteById(cmmFileDtlIds); + new File(this.uploadPath + cmmFileDtl.getFileUpldPath() + File.separator + cmmFileDtl.getFileId()).delete(); + } + } +} + +/* + // File.seperator 는 OS종속적이다. + // Spring에서 제공하는 cleanPath()를 통해서 ../ 내부 점들에 대해서 사용을 억제한다 + Path copyOfLocation = Paths.get(uploadDir + File.separator + StringUtils.cleanPath(multipartFile.getOriginalFilename())); + try { + // inputStream을 가져와서 + // copyOfLocation (저장위치)로 파일을 쓴다. + // copy의 옵션은 기존에 존재하면 REPLACE(대체한다), 오버라이딩 한다 + Files.copy(multipartFile.getInputStream(), copyOfLocation, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + e.printStackTrace(); + throw new FileStorageException("Could not store file : " + multipartFile.getOriginalFilename()); + } +*/ diff --git a/src/main/java/com/xit/biz/cmm/service/impl/CmmRoleMenuService.java b/src/main/java/com/xit/biz/cmm/service/impl/CmmRoleMenuService.java new file mode 100644 index 0000000..426f9c9 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/service/impl/CmmRoleMenuService.java @@ -0,0 +1,23 @@ +package com.xit.biz.cmm.service.impl; + +import com.xit.biz.cmm.entity.CmmRoleMenu; +import com.xit.biz.cmm.repository.ICmmRoleMenuRepository; +import com.xit.biz.cmm.service.ICmmRoleMenuService; +import com.xit.core.support.jpa.AbstractJpaService; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Service; + +@Service +public class CmmRoleMenuService extends AbstractJpaService implements ICmmRoleMenuService { + + private final ICmmRoleMenuRepository cmmRoleMenuRepository; + + public CmmRoleMenuService(ICmmRoleMenuRepository cmmRoleMenuRepository) { + this.cmmRoleMenuRepository = cmmRoleMenuRepository; + } + + @Override + protected PagingAndSortingRepository getRepository() { + return cmmRoleMenuRepository; + } +} diff --git a/src/main/java/com/xit/biz/cmm/service/impl/CmmRoleService.java b/src/main/java/com/xit/biz/cmm/service/impl/CmmRoleService.java new file mode 100644 index 0000000..cc1af3c --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/service/impl/CmmRoleService.java @@ -0,0 +1,24 @@ +package com.xit.biz.cmm.service.impl; + +import com.xit.biz.cmm.entity.CmmRole; +import com.xit.biz.cmm.repository.ICmmRoleRepository; +import com.xit.biz.cmm.service.ICmmRoleService; +import com.xit.core.support.jpa.AbstractJpaCrudService; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Service; + +@Service +public class CmmRoleService extends AbstractJpaCrudService implements ICmmRoleService { + private final ICmmRoleRepository cmmRoleRepository; + + public CmmRoleService(ICmmRoleRepository cmmRoleRepository) { + this.cmmRoleRepository = cmmRoleRepository; + } + + @Override + protected CrudRepository getRepository() { + return cmmRoleRepository; + } + + +} diff --git a/src/main/java/com/xit/biz/cmm/service/impl/CmmRoleUserService.java b/src/main/java/com/xit/biz/cmm/service/impl/CmmRoleUserService.java new file mode 100644 index 0000000..4f6dde7 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/service/impl/CmmRoleUserService.java @@ -0,0 +1,24 @@ +package com.xit.biz.cmm.service.impl; + +import com.xit.biz.cmm.entity.CmmRoleUer; +import com.xit.biz.cmm.repository.ICmmRoleUserRepository; +import com.xit.biz.cmm.service.ICmmRoleUserService; +import com.xit.core.support.jpa.AbstractJpaCrudService; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Service; + +@Service +public class CmmRoleUserService extends AbstractJpaCrudService implements ICmmRoleUserService { + private final ICmmRoleUserRepository cmmRoleUserRepository; + + public CmmRoleUserService(ICmmRoleUserRepository cmmRoleUserRepository) { + this.cmmRoleUserRepository = cmmRoleUserRepository; + } + + @Override + protected CrudRepository getRepository() { + return cmmRoleUserRepository; + } + + +} diff --git a/src/main/java/com/xit/biz/cmm/service/impl/CmmUserService.java b/src/main/java/com/xit/biz/cmm/service/impl/CmmUserService.java new file mode 100644 index 0000000..ccec430 --- /dev/null +++ b/src/main/java/com/xit/biz/cmm/service/impl/CmmUserService.java @@ -0,0 +1,121 @@ +package com.xit.biz.cmm.service.impl; + +import com.xit.biz.cmm.entity.CmmUser; +import com.xit.biz.cmm.repository.ICmmUserRepository; +import com.xit.biz.cmm.service.ICmmUserService; +import com.xit.core.support.jpa.AbstractJpaService; +import com.xit.core.support.jpa.JpaUtil; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleMatcher; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +import static org.springframework.data.domain.ExampleMatcher.GenericPropertyMatchers.contains; + +@Service +public class CmmUserService extends AbstractJpaService implements ICmmUserService { + + private final ICmmUserRepository cmmUserRepository; + //private CmmUserMapstruct cmmUserMapstruct = Mappers.getMapper(CmmUserMapstruct.class); + + public CmmUserService(ICmmUserRepository cmmUserRepository) { + this.cmmUserRepository = cmmUserRepository; + } + + @Override + protected PagingAndSortingRepository getRepository() { + return cmmUserRepository; + } + + @Override + @Transactional(readOnly = true) + public Page findCmmUsers(CmmUser cmmUser, Pageable pageable) { + //Sort sort = Sort.by(Sort.Direction.ASC, "codeOrdr"); + pageable = JpaUtil.getPagingInfo(pageable); + ExampleMatcher exampleMatcher = ExampleMatcher.matchingAll() + .withMatcher("userId", contains()); + Example example = Example.of(cmmUser, exampleMatcher); + Page page = cmmUserRepository.findAll(example, pageable); + // List userList = page.getContent(); + return page; + } + + @Override + @Transactional(readOnly = true) + public Iterable findCmmUsers2(CmmUser cmmUser) { + //Sort sort = Sort.by(Sort.Direction.ASC, "codeOrdr"); + //pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize()); + Example example = Example.of(cmmUser, ExampleMatcher.matchingAny()); + return cmmUserRepository.findAll(example); + } + + @Override + @Transactional(readOnly = true) + public CmmUser findByCmmUserId(final String cmmUserId){ + return cmmUserRepository.findByCmmUserId(cmmUserId); + } + + @Override + @Transactional(readOnly = true) + public Optional findById(final String cmmUserId){ + return cmmUserRepository.findById(cmmUserId); + } + + @Override + @Transactional(readOnly = true) + public Optional findByUserId(final String userId){ + return cmmUserRepository.findByUserId(userId); + } + + @Override + @Transactional + public void deleteByCmmUserId(final String cmmUserId){ + cmmUserRepository.deleteById(cmmUserId); + } + +// +// public CmmUserService(CmmUserRepository repository) { +// this.repository = repository; +// } +// +// public List findAll(){ +// return repository.findAll(); +// } +// +// @Override +// public Page findAll(Pageable pageable) { +// return repository.findAll(pageable); +// } +// +// public CmmUserEntity findByCmmUserId(Long cmmUserId){ +// return repository.findByCmmUserId(cmmUserId); +// } +// +// public CmmUserEntity findByUserId(String userId){ +// return repository.findByUserId(userId); +// } +// +// public void saves(List cmmUsers){ +// cmmUsers.forEach(this::save); +// } +// +// @Transactional +// public CmmUserEntity save(CmmUserEntity cmmUserEntity){ +// return repository.save(cmmUserEntity); +// } +// +// @Transactional +// public CmmUserEntity update(CmmUserEntity cmmUserEntity){ +// return repository.save(cmmUserEntity); +// } +// +// @Transactional +// public void deleteByCmmUserId(Long cmmUserId){ +// repository.deleteById(cmmUserId); +// } +} diff --git a/src/main/java/com/xit/biz/sample/Greeting.java b/src/main/java/com/xit/biz/sample/Greeting.java new file mode 100644 index 0000000..757af51 --- /dev/null +++ b/src/main/java/com/xit/biz/sample/Greeting.java @@ -0,0 +1,20 @@ +package com.xit.biz.sample; + +public class Greeting { + + private final long id; + private final String content; + + public Greeting(long id, String content) { + this.id = id; + this.content = content; + } + + public long getId() { + return id; + } + + public String getContent() { + return content; + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/biz/sample/controller/RestSampleController.java b/src/main/java/com/xit/biz/sample/controller/RestSampleController.java new file mode 100644 index 0000000..7e55af7 --- /dev/null +++ b/src/main/java/com/xit/biz/sample/controller/RestSampleController.java @@ -0,0 +1,60 @@ +package com.xit.biz.sample.controller; + +import com.xit.biz.cmm.service.ICmmBoardService; +import com.xit.biz.sample.Greeting; +import com.xit.core.api.IRestResponse; +import com.xit.core.api.RestResponse; +import com.xit.core.constant.ErrorCode; +import com.xit.core.exception.InvalidRequestException; +import com.xit.core.util.AssertUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +@Tag(name = "RestSampleController", description = "Rest API Sample") +@RequiredArgsConstructor +@RestController +@RequestMapping("/api/sample") +public class RestSampleController { + + private static final String template = "Hello, %s!"; + private final AtomicLong counter = new AtomicLong(); + + private final ICmmBoardService cmmBoardService; + + @GetMapping("/greeting") + public Greeting greeting(@RequestParam(value = "name", required = false, defaultValue = "World") String name) { + return new Greeting(counter.incrementAndGet(), String.format(template, name)); + } + + @Operation(summary = "Mybatis 예제") + @Parameters({ + @Parameter(in = ParameterIn.QUERY, name = "boardId", description = "게시글Id", required = false, example = " "), + @Parameter(in = ParameterIn.QUERY, name = "title", description = "제목", required = false, example = " "), + @Parameter(in = ParameterIn.QUERY, name = "content", description = "내용", required = false, example = " ") + //@Parameter(in = ParameterIn.QUERY, name = "sort", description = "정렬", required = true, example = "codeOrdr"), + }) + @GetMapping("/mybatis") + public List> mybatis(@RequestParam @Parameter(hidden = true) Map paramMap) { + return cmmBoardService.selectBoardList(paramMap); + } + + @Operation(summary = "에러 예제 - rtnMsg") + @GetMapping("/error1") + public ResponseEntity test1() { + AssertUtils.isTrue("조회할 콤보코드 대상이 없습니다."); + return RestResponse.of("test1"); + } +} diff --git a/src/main/java/com/xit/biz/sample/controller/WebSampleController.java b/src/main/java/com/xit/biz/sample/controller/WebSampleController.java new file mode 100644 index 0000000..a467d36 --- /dev/null +++ b/src/main/java/com/xit/biz/sample/controller/WebSampleController.java @@ -0,0 +1,88 @@ +package com.xit.biz.sample.controller; + +import com.xit.core.util.PoiExcelView; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.servlet.View; + +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; + +@Controller +@RequestMapping("/web/sample") +public class WebSampleController { + + private static final String template = "Hello, %s!"; + private final AtomicLong counter = new AtomicLong(); + + @GetMapping("/sample-dark") + public String sampleDarkMode() { + return "thymeleaf/sample/sample-dark"; + } + + @GetMapping("/sample-admin") + public String sampleAdminMode() { + return "thymeleaf/sample/sample-admin"; + } + + @GetMapping("/greeting") + public String greeting(@RequestParam(value = "name", required = false, defaultValue = "World") String name, + Model model) { + model.addAttribute("name", String.format(template, name)); + return "thymeleaf/sample/greeting"; + } + + @GetMapping("/jsp") + public String jspTest(@RequestParam(value = "name", required = false, defaultValue = "World") String name, + Model model) { + model.addAttribute("name", String.format(template, name)); + return "Test"; + } + + @GetMapping("/exceltest") + public String exceltest() { + return "thymeleaf/sample/exceldown"; + } + + @PostMapping(value = "/exceldown")//, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) + public View excelDownTest(ModelMap modelMap) { + List titleList = Arrays.asList( "아이디", "이름", "생년월일", "핸드폰번호", "전화", "이메일"); + List fieldList = Arrays.asList( "ID", "MBR_NM", "BRTHDY", "HP_NO", "TEL_NO", "EMAIL"); + modelMap.addAttribute("titleList", titleList); + modelMap.addAttribute("fieldList", fieldList); + + List> listRes = new ArrayList<>(); + Map map = new HashMap<>(); + map.put("MBR_NM", "fsafas"); + map.put("BRTHDY", "12341212"); + map.put("HP_NO", "01011112222"); + map.put("TEL_NO", "0311231234"); + map.put("EMAIL", "a@b.com"); + for(int i=0; i<10; i++) { + map.put("ID", i); + listRes.add(map); + } + modelMap.addAttribute("contentList", listRes); + return new PoiExcelView("exceltest.xlsx"); + + //modelMap.addAttribute("titleList", paramMap.get("titleList")); + //modelMap.addAttribute("fieldList", paramMap.get("fieldList")); + //return new PoiExcelView(paramMap.get("fileName").toString()); + } + + +// @GetMapping("/greeting2") +// public Mono greeting(@RequestParam(value = "name", defaultValue = "World") String name) { +// return new Greeting(counter.incrementAndGet(), String.format(template, name)); +// } +// +// @GetMapping("/getGreetings") +// public Flux getAllMargins() { +// return null;//marginRepository.findAll(); +// } +} diff --git a/src/main/java/com/xit/biz/sample/dto/DefaultDto.java b/src/main/java/com/xit/biz/sample/dto/DefaultDto.java new file mode 100644 index 0000000..6c1e8a2 --- /dev/null +++ b/src/main/java/com/xit/biz/sample/dto/DefaultDto.java @@ -0,0 +1,18 @@ +package com.xit.biz.sample.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class DefaultDto { + private String msg; + private boolean result; + private List stringFlux; +} diff --git a/src/main/java/com/xit/biz/sample/dto/PersonDto.java b/src/main/java/com/xit/biz/sample/dto/PersonDto.java new file mode 100644 index 0000000..6be9a3b --- /dev/null +++ b/src/main/java/com/xit/biz/sample/dto/PersonDto.java @@ -0,0 +1,13 @@ +package com.xit.biz.sample.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class PersonDto { + String name; + int age; +} \ No newline at end of file diff --git a/src/main/java/com/xit/biz/sample/ignore/FluxCmmUserController.java b/src/main/java/com/xit/biz/sample/ignore/FluxCmmUserController.java new file mode 100644 index 0000000..ceb861c --- /dev/null +++ b/src/main/java/com/xit/biz/sample/ignore/FluxCmmUserController.java @@ -0,0 +1,92 @@ +//package com.xit.biz.sample.ignore; +// +//import com.xit.biz.cmm.domain.CmmUser; +//import com.xit.biz.cmm.service.ICmmUserMgtService; +//import io.swagger.v3.oas.annotations.Operation; +//import io.swagger.v3.oas.annotations.tags.Tag; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.data.domain.PageRequest; +//import org.springframework.http.MediaType; +//import org.springframework.web.bind.annotation.*; +//import reactor.core.publisher.Flux; +//import reactor.core.publisher.Mono; +// +//@Tag(name = "FluxCmmUserController", description = "사용자 관리(Web-flux)") +//@Slf4j +//@RestController +//@RequiredArgsConstructor +//@RequestMapping("/api/biz/cmm/flux") +//public class FluxCmmUserController { +// private final ICmmUserMgtService cmmUserMgtService; +// +// @Operation(summary = "사용자 목록 조회" , description = "등록된 사용자 전체 목록 조회") +// @GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE) +// public Flux findAll() { +// return Flux.fromIterable(cmmUserMgtService.findAll()); +// //return new ResponseEntity>(cmmUserRepository.findAll(), HttpStatus.OK); +// +// } +// +// @Operation(summary = "사용자 목록 조회" , description = "등록된 사용자 전체 목록 조회") +// @GetMapping(value="/page", produces = MediaType.APPLICATION_JSON_VALUE) +// public Mono findAllPage() { +// +// return Mono.just(cmmUserMgtService.findAll(PageRequest.of(0, 2))); +// +// //Page page = cmmUserRepository.findAll(PageRequest.of(pageable.getPageNumber(), pageable.getPageSize())); +// //return xitApiResult.of(new ResponseEntity>(page, HttpStatus.OK)); +// +// } +// +// @Operation(summary = "사용자 정보 조회" , description = "사용자 정보 조회") +// @GetMapping(value="/{cmmUserId}", produces = MediaType.APPLICATION_JSON_VALUE) +// public Mono findByCmmUserId(@PathVariable final Long cmmUserId) { +// return Mono.just(cmmUserMgtService.findById(cmmUserId)); +// } +// +//// @Operation(summary = "사용자 정보 조회" , description = "사용자 정보 조회") +//// @GetMapping(value="/{userId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE) +//// public Flux findByUserId(@PathVariable final String userId) { +//// return Flux.just(cmmUserRepository.findByUserId(userId)); +//// } +// +// @Operation(summary = "사용자 추가" , description = "사용자 등록") +// @PostMapping(produces = MediaType.APPLICATION_JSON_VALUE) +// public Mono save(@RequestBody CmmUser cmmUserEntity) { +// return Mono.just(cmmUserMgtService.save(cmmUserEntity)); +// } +// +// @Operation(summary = "사용자 정보 변경" , description = "사용자 정보 변경") +// @PutMapping(value="/{cmmUserId}", produces = MediaType.APPLICATION_JSON_VALUE) +// public Mono modify(@PathVariable final Long cmmUserId, @RequestBody CmmUser cmmUser) { +// return Mono.just(cmmUserMgtService.save(cmmUser)); +// } +// +//// @Operation(summary = "사용자 삭제" , description = "사용자 제거") +//// @DeleteMapping(value="/{cmmUserId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE) +//// public Mono deleteById(@PathVariable final Long cmmUserId) { +//// cmmUserRepository.deleteById(cmmUserId); +//// return Mono.empty(); +//// +//// } +// +///* +// @Bean +// public RouterFunction monoRouterFunction(UserHandler userHandler) { +// return route(GET("/users/{userId}").and(accept(APPLICATION_JSON)), userHandler::getUser); +// } +// +// public Mono getUser(ServerRequest request) { +// // ... +// // request 로부터 userId를 뽑았다고 가정 +// +// Mono mono = Mono.just(userService.findById(userId)); +// return ServerResponse +// .ok() +// .contentType(MediaType.APPLICATION_JSON) +// .body(mono, User.class); +// } +//*/ +// +//} diff --git a/src/main/java/com/xit/biz/sample/ignore/FluxSampleController.java b/src/main/java/com/xit/biz/sample/ignore/FluxSampleController.java new file mode 100644 index 0000000..dea0bc3 --- /dev/null +++ b/src/main/java/com/xit/biz/sample/ignore/FluxSampleController.java @@ -0,0 +1,34 @@ +package com.xit.biz.sample.ignore; + +import com.xit.biz.sample.Greeting; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.concurrent.atomic.AtomicLong; + +@RestController +@RequestMapping("/api/sample/flux") +@Tag(name = "FluxSampleController", description = "Flux Rest Api Sample") +public class FluxSampleController { + + private static final String template = "Hello, %s!"; + private final AtomicLong counter = new AtomicLong(); + + @GetMapping("/greeting") + public Greeting greeting(@RequestParam(value = "name", required = false, defaultValue = "World") String name) { + return new Greeting(counter.incrementAndGet(), String.format(template, name)); + } + +// @GetMapping("/greeting2") +// public Mono greeting(@RequestParam(value = "name", defaultValue = "World") String name) { +// return new Greeting(counter.incrementAndGet(), String.format(template, name)); +// } +// +// @GetMapping("/getGreetings") +// public Flux getAllMargins() { +// return null;//marginRepository.findAll(); +// } +} diff --git a/src/main/java/com/xit/core/_ignore/security/entity/BaseEntity.java b/src/main/java/com/xit/core/_ignore/security/entity/BaseEntity.java new file mode 100644 index 0000000..c7a9373 --- /dev/null +++ b/src/main/java/com/xit/core/_ignore/security/entity/BaseEntity.java @@ -0,0 +1,48 @@ +package com.xit.core._ignore.security.entity; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Getter; +import lombok.Setter; + +import javax.persistence.*; +import java.io.Serializable; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.Date; + +@Getter @Setter +//@MappedSuperclass +public abstract class BaseEntity implements Serializable { + + private static final long serialVersionUID = 1146360965411496820L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(updatable = false, nullable = false, columnDefinition = "INT(11)") + private Long id; + + @JsonIgnore + @Temporal(TemporalType.TIMESTAMP) + @Column(updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP") + private Date createTimestamp; + + @JsonIgnore + @Temporal(TemporalType.TIMESTAMP) + @Column(nullable = true, columnDefinition = "TIMESTAMP DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP") + private Date updateTimestamp; + + @JsonIgnore + @Column(nullable = false, columnDefinition = "TINYINT(1) DEFAULT 0") + private boolean del; + + @PrePersist + protected void onCreate() { + createTimestamp = Timestamp.valueOf(LocalDateTime.now()); + } + + @PreUpdate + protected void onUpdate() { + updateTimestamp = Timestamp.valueOf(LocalDateTime.now()); + } + +} diff --git a/src/main/java/com/xit/core/_ignore/security/entity/SecurityUser.java b/src/main/java/com/xit/core/_ignore/security/entity/SecurityUser.java new file mode 100644 index 0000000..a6fa2b0 --- /dev/null +++ b/src/main/java/com/xit/core/_ignore/security/entity/SecurityUser.java @@ -0,0 +1,68 @@ +package com.xit.core._ignore.security.entity; + +import com.xit.core.oauth2.oauth.entity.RoleType; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Set; +import java.util.stream.Collectors; + +public class SecurityUser extends User implements UserDetails { + + private static final long serialVersionUID = 8666468119299100306L; + + private final boolean accountNonExpired; + private final boolean accountNonLocked; + private final boolean credentialsNonExpired; + private final boolean enabled; + + public SecurityUser(User user) { + super(); + setId(user.getId()); + setEmail(user.getEmail()); + setName(user.getName()); + setPassword(user.getPassword()); + setDel(user.isDel()); + setUserRoles(user.getUserRoles()); + this.accountNonExpired = true; + this.accountNonLocked = true; + this.credentialsNonExpired = true; + this.enabled = true; + } + + public Set getRoleTypes() { + return getUserRoles().stream().map(f -> f.getRoleName()).collect(Collectors.toSet()); + } + + @Override + public Collection getAuthorities() { + return getUserRoles(); + } + + @Override + public String getUsername() { + return super.getEmail(); + } + + @Override + public boolean isAccountNonExpired() { + return this.accountNonExpired; + } + + @Override + public boolean isAccountNonLocked() { + return this.accountNonLocked; + } + + @Override + public boolean isCredentialsNonExpired() { + return this.credentialsNonExpired; + } + + @Override + public boolean isEnabled() { + return this.enabled; + } + +} diff --git a/src/main/java/com/xit/core/_ignore/security/entity/User.java b/src/main/java/com/xit/core/_ignore/security/entity/User.java new file mode 100644 index 0000000..39514ba --- /dev/null +++ b/src/main/java/com/xit/core/_ignore/security/entity/User.java @@ -0,0 +1,69 @@ +package com.xit.core._ignore.security.entity; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import lombok.*; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.Where; + +import javax.persistence.Column; +import javax.persistence.OneToMany; +import java.io.Serializable; +import java.util.Set; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Setter +//@Entity +//@Table(name = "user") +//@DynamicUpdate @DynamicInsert +public class User extends BaseEntity implements Serializable { + + private static final long serialVersionUID = -563329217866858622L; + + @ColumnDefault(value = "0") + @Column(nullable = false, length = 1, columnDefinition = "CHAR(1)") + private String type; + + @Column(nullable = false, unique = true, length = 100) + private String email; + + @Column(nullable = false, length = 50) + private String name; + + @ColumnDefault(value = "1") + @Column(nullable = false, length = 1, columnDefinition = "CHAR(1)") + private String sex; + + @Column(nullable = false, length = 6) + private String birthDate; + + @Column(nullable = false, length = 20) + private String phoneNumber; + + @JsonIgnore + @Column(nullable = false, length = 150) + private String password; + + @Column(length = 50) + private String picture; + + @Singular("userRoles") + @JsonManagedReference + @OneToMany(mappedBy="user") + @Where(clause = "del = false") + private Set userRoles; + + @Builder + public User(String type, String name, String email, String sex, String birthDate, String phoneNumber, String password, String picture) { + this.type = type; + this.name = name; + this.email = email; + this.sex = sex; + this.birthDate = birthDate; + this.phoneNumber = phoneNumber; + this.password = password; + this.picture = picture; + } + +} diff --git a/src/main/java/com/xit/core/_ignore/security/entity/UserRole.java b/src/main/java/com/xit/core/_ignore/security/entity/UserRole.java new file mode 100644 index 0000000..a09be87 --- /dev/null +++ b/src/main/java/com/xit/core/_ignore/security/entity/UserRole.java @@ -0,0 +1,43 @@ +package com.xit.core._ignore.security.entity; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.xit.core.oauth2.oauth.entity.RoleType; +import lombok.*; +import org.springframework.security.core.GrantedAuthority; + +import javax.persistence.*; +import java.io.Serializable; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Setter +//@Entity +//@Table(name = "user_role", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "role_name"})}) +//@DynamicUpdate +public class UserRole extends BaseEntity implements Serializable, GrantedAuthority { + + private static final long serialVersionUID = 7943607393308984161L; + + @JsonBackReference + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false, foreignKey = @ForeignKey(name = "FK_USER_ROLE_USER")) + private User user; + + @Column(name="role_name", nullable=false, length = 20) + @Enumerated(EnumType.STRING) + private RoleType roleName; + + @Builder + public UserRole(User user, RoleType roleName) { + this.user = user; + this.roleName = roleName; + } + + @JsonIgnore + @Override + public String getAuthority() { + return this.roleName.name(); + } + +} diff --git a/src/main/java/com/xit/core/_ignore/security/handler/WebAccessDeniedHandler.java b/src/main/java/com/xit/core/_ignore/security/handler/WebAccessDeniedHandler.java new file mode 100644 index 0000000..2c329b0 --- /dev/null +++ b/src/main/java/com/xit/core/_ignore/security/handler/WebAccessDeniedHandler.java @@ -0,0 +1,53 @@ +package com.xit.core._ignore.security.handler; + +import com.xit.core._ignore.security.entity.SecurityUser; +import com.xit.core._ignore.security.entity.UserRole; +import com.xit.core.oauth2.oauth.entity.RoleType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.access.AccessDeniedHandler; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Set; + +//@Component +public class WebAccessDeniedHandler implements AccessDeniedHandler { + + private static final Logger logger = LoggerFactory.getLogger(WebAccessDeniedHandler.class); + + @Override + public void handle(HttpServletRequest req, HttpServletResponse res, AccessDeniedException ade) + throws IOException, ServletException { + res.setStatus(HttpStatus.FORBIDDEN.value()); + + if(ade instanceof AccessDeniedException) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null) { + SecurityUser securityUser = (SecurityUser) authentication.getPrincipal(); + Set roleTypes = securityUser.getRoleTypes(); + if(!roleTypes.isEmpty()) { + req.setAttribute("msg", "접근권한 없는 사용자입니다."); + //if (roleTypes.contains(RoleType..ROLE_VIEW)) { + req.setAttribute("nextPage", "/v"); + //} + } else { + req.setAttribute("msg", "로그인 권한이 없는 아이디입니다."); + req.setAttribute("nextPage", "/login"); + res.setStatus(HttpStatus.UNAUTHORIZED.value()); + SecurityContextHolder.clearContext(); + } + } + } else { + logger.info(ade.getClass().getCanonicalName()); + } + req.getRequestDispatcher("/err/denied-page").forward(req, res); + } + +} diff --git a/src/main/java/com/xit/core/_ignore/security/session/CustomAccessDeniedHandler.java b/src/main/java/com/xit/core/_ignore/security/session/CustomAccessDeniedHandler.java new file mode 100644 index 0000000..ee7641a --- /dev/null +++ b/src/main/java/com/xit/core/_ignore/security/session/CustomAccessDeniedHandler.java @@ -0,0 +1,21 @@ +package com.xit.core._ignore.security.session; + +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +//@Component +public class CustomAccessDeniedHandler implements AccessDeniedHandler { + + @Override + public void handle(final HttpServletRequest request, final HttpServletResponse response, final AccessDeniedException ex) throws IOException, ServletException { + response.getOutputStream().print("Error Message Goes Here"); + response.setStatus(403); + // response.sendRedirect("/my-error-page"); + } + +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/_ignore/security/session/RestAuthenticationEntryPoint.java b/src/main/java/com/xit/core/_ignore/security/session/RestAuthenticationEntryPoint.java new file mode 100644 index 0000000..78ae7da --- /dev/null +++ b/src/main/java/com/xit/core/_ignore/security/session/RestAuthenticationEntryPoint.java @@ -0,0 +1,25 @@ +package com.xit.core._ignore.security.session; + +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * The Entry Point will not redirect to any sort of Login - it will return the 401 + */ +//@Component +public final class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Override + public void commence( + final HttpServletRequest request, + final HttpServletResponse response, + final AuthenticationException authException) throws IOException { + + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); + } + +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/_ignore/security/session/SavedRequestAwareAuthenticationSuccessHandler.java b/src/main/java/com/xit/core/_ignore/security/session/SavedRequestAwareAuthenticationSuccessHandler.java new file mode 100644 index 0000000..64912df --- /dev/null +++ b/src/main/java/com/xit/core/_ignore/security/session/SavedRequestAwareAuthenticationSuccessHandler.java @@ -0,0 +1,41 @@ +package com.xit.core._ignore.security.session; + +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.security.web.savedrequest.HttpSessionRequestCache; +import org.springframework.security.web.savedrequest.RequestCache; +import org.springframework.security.web.savedrequest.SavedRequest; +import org.springframework.util.StringUtils; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +//@Component +public class SavedRequestAwareAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + + private RequestCache requestCache = new HttpSessionRequestCache(); + + @Override + public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response, final Authentication authentication) throws ServletException, IOException { + final SavedRequest savedRequest = requestCache.getRequest(request, response); + + if (savedRequest == null) { + clearAuthenticationAttributes(request); + return; + } + final String targetUrlParameter = getTargetUrlParameter(); + if (isAlwaysUseDefaultTargetUrl() || (targetUrlParameter != null && StringUtils.hasText(request.getParameter(targetUrlParameter)))) { + requestCache.removeRequest(request, response); + clearAuthenticationAttributes(request); + return; + } + + clearAuthenticationAttributes(request); + } + + public void setRequestCache(final RequestCache requestCache) { + this.requestCache = requestCache; + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/_ignore/security/session/SecuritySessionJavaConfig.java b/src/main/java/com/xit/core/_ignore/security/session/SecuritySessionJavaConfig.java new file mode 100644 index 0000000..bab1475 --- /dev/null +++ b/src/main/java/com/xit/core/_ignore/security/session/SecuritySessionJavaConfig.java @@ -0,0 +1,150 @@ +package com.xit.core._ignore.security.session; + +import com.xit.core.exception.filter.ExceptionHandlerFilter; +import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.task.DelegatingSecurityContextAsyncTaskExecutor; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; + +//@Configuration +//@EnableWebSecurity +//@EnableGlobalMethodSecurity(prePostEnabled = true) +//@ComponentScan("com.xit.core.security") +public class SecuritySessionJavaConfig extends WebSecurityConfigurerAdapter { + private static final String[] AUTH_WHITELIST = { + "/h2-console/**", + "/v2/api-docs/**", + "/swagger-ui/**", + "/swagger-resources/**", + "/webjars/**", + "favicon.ico", + "/configuration/ui", + "/configuration/security", + "/resources/**" + }; + +// @Value("${jwt.secret}") +// private String secret; + + private final CustomAccessDeniedHandler accessDeniedHandler; + private final RestAuthenticationEntryPoint restAuthenticationEntryPoint; + private final SavedRequestAwareAuthenticationSuccessHandler mySuccessHandler; + private final ExceptionHandlerFilter exceptionHandlerFilter; + private SimpleUrlAuthenticationFailureHandler myFailureHandler = new SimpleUrlAuthenticationFailureHandler(); + + public SecuritySessionJavaConfig(CustomAccessDeniedHandler accessDeniedHandler, RestAuthenticationEntryPoint restAuthenticationEntryPoint, SavedRequestAwareAuthenticationSuccessHandler mySuccessHandler, ExceptionHandlerFilter exceptionHandlerFilter) { + this.accessDeniedHandler = accessDeniedHandler; + this.restAuthenticationEntryPoint = restAuthenticationEntryPoint; + this.mySuccessHandler = mySuccessHandler; + this.exceptionHandlerFilter = exceptionHandlerFilter; + } + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception{ + return super.authenticationManagerBean(); + } + + @Override + protected void configure(final AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication() + .withUser("admin").password(encoder().encode("adminPass")).roles("ADMIN") + .and() + .withUser("user").password(encoder().encode("userPass")).roles("USER"); + } + + @Override + protected void configure(final HttpSecurity http) throws Exception { + + http + .cors().disable() + .csrf() + .ignoringAntMatchers("/h2-console/**") + .disable() + .authorizeRequests() + + //OPTIONS 메소드 허락 - jwt 사용시 Authorization 요청 처리를 위해 //////////////////////////////// + .antMatchers(HttpMethod.OPTIONS, "/api/**").permitAll() + //////////////////////////////////////////////////////////////////////////////////////////// + + .and() + .exceptionHandling() + .accessDeniedHandler(accessDeniedHandler) + .authenticationEntryPoint(restAuthenticationEntryPoint) + + // 인증 사용 및 미사용 + .and() + .authorizeRequests() + .antMatchers("/api/**").permitAll() + .antMatchers("/h2-console/**").permitAll() + .antMatchers("/api/admin/**").hasRole("ADMIN") + //.anyRequest().authenticated() // 인증사용 + + .and() + .exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint) + .and() + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) + ///////////////////////////////////////////////////////////////////////// + .and() + .formLogin().loginPage("/login").loginProcessingUrl("/login.do") + .usernameParameter("_username").passwordParameter("_password") + .successHandler(mySuccessHandler) + .failureHandler(myFailureHandler) + .and() + .httpBasic() + .and() + .logout() + //TODO : swagger 인증시 +// .and() +// .authorizeRequests().antMatchers(AUTH_WHITELIST).authenticated() +// .and() +// .httpBasic().authenticationEntryPoint(swaggerAuthenticationEntryPoint()) + + + ; + + } + + @Override + public void configure(WebSecurity web) throws Exception { + web.ignoring().antMatchers(AUTH_WHITELIST); + + } + + //TODO : swagger 인증시 +// @Bean +// public BasicAuthenticationEntryPoint swaggerAuthenticationEntryPoint() { +// BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint(); +// entryPoint.setRealmName("Swagger Realm"); +// return entryPoint; +// } + + @Bean + public PasswordEncoder encoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public ThreadPoolTaskExecutor threadPoolTaskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(10); + executor.setMaxPoolSize(100); + executor.setQueueCapacity(50); + executor.setThreadNamePrefix("async-"); + return executor; + } + + @Bean + public DelegatingSecurityContextAsyncTaskExecutor taskExecutor(ThreadPoolTaskExecutor threadPoolTaskExecutor) { + return new DelegatingSecurityContextAsyncTaskExecutor(threadPoolTaskExecutor); + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/annotation/Permission.java b/src/main/java/com/xit/core/annotation/Permission.java new file mode 100644 index 0000000..0bdb297 --- /dev/null +++ b/src/main/java/com/xit/core/annotation/Permission.java @@ -0,0 +1,12 @@ +package com.xit.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(value = RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +public @interface Permission { + String[] types() default {}; +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/annotation/Secured.java b/src/main/java/com/xit/core/annotation/Secured.java new file mode 100644 index 0000000..0f2958f --- /dev/null +++ b/src/main/java/com/xit/core/annotation/Secured.java @@ -0,0 +1,12 @@ +package com.xit.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(value = RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +public @interface Secured { + SecurityPolicy policy() default SecurityPolicy.DEFAULT; +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/annotation/SecurityPolicy.java b/src/main/java/com/xit/core/annotation/SecurityPolicy.java new file mode 100644 index 0000000..61af712 --- /dev/null +++ b/src/main/java/com/xit/core/annotation/SecurityPolicy.java @@ -0,0 +1,28 @@ +package com.xit.core.annotation; + +public enum SecurityPolicy { + /** + * 리소스의 아이피, 권한 등 기본 검증 처리 + */ + DEFAULT, + + /** + * 접근한 리소스의 토큰 기반 사용자가 접근할 수 있는 정책 적용 + */ + TOKEN, + + /** + * 접근한 리소스에 세션 기반 인증 사용자가 접근할 수 있는 정책 적용 + */ + SESSION, + + /** + * 접근한 리소스에 쿠키 기반 인증 사용자가 접근할 수 있는 정책 적용 + */ + COOKIE, + + /** + * 보안 검사를 수행하지 않도록 정책 적용 + */ + NONE +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/api/CustomBeanNameGenerator.java b/src/main/java/com/xit/core/api/CustomBeanNameGenerator.java new file mode 100644 index 0000000..fc0bb2c --- /dev/null +++ b/src/main/java/com/xit/core/api/CustomBeanNameGenerator.java @@ -0,0 +1,52 @@ +package com.xit.core.api; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.context.annotation.AnnotationBeanNameGenerator; +import org.springframework.lang.NonNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Bean 이름 식별시 패키지를 포함하도록 지정 + * /v1/api, /v2/api 형태로 생성 가능하도록 + */ +public class CustomBeanNameGenerator implements BeanNameGenerator { + + /** + * basePackages 외에 scaning된 beanGenerator + */ + private static final BeanNameGenerator DELEGATE = new AnnotationBeanNameGenerator(); + + /** + * VersioningBeanNameGenerator 대상 package 경로 + */ + private final List basePackages = new ArrayList<>( + Arrays.asList("com.xit.biz", "com.xit.core") + ); + + @Override + public @NonNull String generateBeanName(@NonNull BeanDefinition definition, @NonNull BeanDefinitionRegistry registry) { + if(isTargetPackageBean(definition)) { + return getBeanName(definition); + } + + return DELEGATE.generateBeanName(definition, registry); + } + + private boolean isTargetPackageBean(BeanDefinition definition) { + String beanClassName = getBeanName(definition); + return basePackages.stream().anyMatch(beanClassName::startsWith); + } + + private String getBeanName(BeanDefinition definition) { + return definition.getBeanClassName(); + } + + public void addBasePackages(String path) { + this.basePackages.add(path); + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/api/IRestResponse.java b/src/main/java/com/xit/core/api/IRestResponse.java new file mode 100644 index 0000000..1f5a3cc --- /dev/null +++ b/src/main/java/com/xit/core/api/IRestResponse.java @@ -0,0 +1,7 @@ +package com.xit.core.api; + +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(required = true, name = "RestResponse", description = "Rest Api Response interface class") +public interface IRestResponse { +} diff --git a/src/main/java/com/xit/core/api/RestErrorResponse.java b/src/main/java/com/xit/core/api/RestErrorResponse.java new file mode 100644 index 0000000..65264c4 --- /dev/null +++ b/src/main/java/com/xit/core/api/RestErrorResponse.java @@ -0,0 +1,146 @@ +package com.xit.core.api; + +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import com.xit.core.constant.ErrorCode; +import com.xit.core.exception.CustomBaseException; +import com.xit.core.util.Checks; +import com.xit.core.util.json.ConvertHelper; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.*; + +import static java.util.stream.Collectors.toMap; + +@Schema(name = "RestError", description = "Restful API 에러", implementation = IRestResponse.class) +@JacksonXmlRootElement(localName = "result") +@XmlRootElement(name = "result") +@Slf4j +@Getter +@Builder +@ToString +public class RestErrorResponse implements IRestResponse, Serializable { + private static final long SerialVersionUID = 1L; + + @Schema(name = "true: 성공, false:실패", example = "false", required = true, description = "에러인 경우 false") + private final boolean success = false; + + @Schema(name = "에러 발생 시간", example = " ", description = "에러 발생 시간") + private final LocalDateTime timestamp = LocalDateTime.now(); + + @Schema(name = "HttpStatus 상태", example = " ", description = "HttpStatus 상태") + private final int status; + + @Schema(name = "HttpStatus name", example = " ", description = "HttpStatus name") + private final String error; + + @Schema(name = "코드(에러코드)", description = "코드(에러코드)") + private final String code; + + @Schema(name = "에러 메세지", example = " ", description = "에러 메세지") + @Setter + private String message; + + public static ResponseEntity of(ErrorCode errorCode) { + RestErrorResponse errorResponse = getErrorResponse(errorCode); + printErrorResponse(errorResponse); + + return ResponseEntity + .status(errorCode.getHttpStatus()) + .body(errorResponse); + } + + public static ResponseEntity of(CustomBaseException cbe) { + RestErrorResponse errorResponse = null; + + if (Checks.isNotEmpty(cbe.getErrorCode())) { + errorResponse = getErrorResponse(cbe.getErrorCode()); + return RestErrorResponse.of(cbe.getErrorCode()); + + } else { + errorResponse = RestErrorResponse.builder() + .status(cbe.getHttpStatus().value()) + .error(cbe.getHttpStatus().name()) + .code(Checks.isNotEmpty(cbe.getCode()) ? cbe.getCode() : StringUtils.EMPTY) + .message(Checks.isNotEmpty(cbe.getLocalizedMessage()) ? cbe.getLocalizedMessage() : cbe.getLocalizedMessage()) + .build(); + } + printErrorResponse(errorResponse); + + return ResponseEntity + .status(cbe.getHttpStatus()) + .body(errorResponse); + } + + public static ResponseEntity of(String code, String message) { + RestErrorResponse errorResponse = RestErrorResponse.builder() + .status(HttpStatus.BAD_REQUEST.value()) + .error(HttpStatus.BAD_REQUEST.name()) + .code(code) + .message(message) + .build(); + printErrorResponse(errorResponse); + + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(errorResponse); + } + + //@SuppressWarnings("ConstantConditions") +// public static ResponseEntity of(MethodArgumentNotValidException ex) { +// +// Map validErrorMap = new HashMap<>(); +// ex.getBindingResult().getFieldErrors() +// .forEach(e -> validErrorMap.put(e.getField(), e.getDefaultMessage())); +// +// log.error("##############################################################################################"); +// log.error("{}", validErrorMap); +// log.error("##############################################################################################"); +// +// Optional firstMessage = validErrorMap +// .values() +// .stream() +// .findFirst(); +// +// RestErrorResponse errorResponse = RestErrorResponse.builder() +// .status(HttpStatus.BAD_REQUEST.value()) +// .error(HttpStatus.BAD_REQUEST.name()) +// .code(HttpStatus.BAD_REQUEST.name()) +// .message(firstMessage.orElse("에러 메세지가 정의 되지 않았습니다")) +// .build(); +// printErrorResponse(errorResponse); +// +// return ResponseEntity +// .status(HttpStatus.BAD_REQUEST) +// .body(errorResponse); +// } + + public static RestErrorResponse getErrorResponse(ErrorCode errorCode) { + return RestErrorResponse.builder() + .status(errorCode.getHttpStatus().value()) + .error(errorCode.getHttpStatus().name()) + .code(errorCode.name()) + .message(errorCode.getMessage()) + .build(); + } + + public String convertToJson() { + return ConvertHelper.jsonToObject(this); + } + + + private static void printErrorResponse(RestErrorResponse errorResponse) { + log.error("##############################################################################################"); + log.error("{}", errorResponse); + log.error("##############################################################################################"); + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/api/RestMessage.java b/src/main/java/com/xit/core/api/RestMessage.java new file mode 100644 index 0000000..4d9650a --- /dev/null +++ b/src/main/java/com/xit/core/api/RestMessage.java @@ -0,0 +1,17 @@ +package com.xit.core.api; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +/** + * @author Lim, Jong Uk (minuk926) + * @since 2021-11-11 + */ +@Setter +@Getter +@AllArgsConstructor +public class RestMessage { + private String code; + private String message; +} diff --git a/src/main/java/com/xit/core/api/RestResponse.java b/src/main/java/com/xit/core/api/RestResponse.java new file mode 100644 index 0000000..d359d58 --- /dev/null +++ b/src/main/java/com/xit/core/api/RestResponse.java @@ -0,0 +1,126 @@ +package com.xit.core.api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import com.google.gson.GsonBuilder; +import com.xit.core.support.jpa.Paginator; +import com.xit.core.util.json.ConvertHelper; +import com.xit.core.util.json.JsonMapper; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.domain.Page; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +@Schema(name = "RestResult", description = "Restful API 결과", implementation = IRestResponse.class) +@Getter @Setter +@JacksonXmlRootElement(localName = "result") +@XmlRootElement(name = "result") +@SuppressWarnings("rawtypes") +public class RestResponse implements IRestResponse, Serializable { + private static final long SerialVersionUID = 1L; + + private final static String SUCCESS = "200"; + private final static String NOT_FOUND = "400"; + private final static String FAILED = "500"; + private final static String SUCCESS_MESSAGE = "SUCCESS"; + private final static String NOT_FOUND_MESSAGE = "NOT FOUND"; + private final static String FAILED_MESSAGE = "서버에서 오류가 발생하였습니다."; + + @Schema(name = "true: 성공, false:실패", example = "true", required = true, description = "에러인 경우 false") + private boolean success = true; + + @Schema(name = "데이타 수", description = "API 실행 결과 데이타 수") + private int count; + + @Schema(name = "메세지", description = "API 실행 결과 data") + private RestMessage message; + + @Schema(name = "결과 data", description = "API 실행 결과 data") + private T data; + + @Schema(name = "페이징 정보", description = "페이징 정보") + private Paginator paginator; + + private RestResponse() { + + } + + private RestResponse(T data) { + this.message = new RestMessage(SUCCESS, SUCCESS_MESSAGE); + this.data = data; + + // Pageing 처리 + if(Page.class.isAssignableFrom(data.getClass())){ + + Page page = (Page)data; + + //noinspection unchecked + this.data = (T) page.getContent(); + this.paginator = new Paginator(page.getNumber(), page.getSize(), Integer.parseInt(String.valueOf(page.getTotalElements())));// paginator.getTotalPages + + this.count = paginator.getTotalCount(); + + }else { + if (Collection.class.isAssignableFrom(data.getClass())) { + this.count = (((Collection) data).size()); + + } else { + this.count = 1; + } + } + } + + public static ResponseEntity of(T data){ + return ResponseEntity.ok().body(new RestResponse<>(data)); + } + + public static ResponseEntity of(String name, T data){ + Map map = new HashMap<>(); + map.put(name, data); + return ResponseEntity.ok().body(new RestResponse<>(map)); + } + + public static ResponseEntity of(HttpStatus httpStatus){ + RestResponse result = new RestResponse(); + result.message = new RestMessage(SUCCESS, SUCCESS_MESSAGE); + return new ResponseEntity<>(result, httpStatus); + } + + @Override + public String toString() { + GsonBuilder builder = new GsonBuilder().serializeNulls(); // value가 null값인 경우도 생성 + builder.disableHtmlEscaping(); + return builder.setPrettyPrinting().create().toJson(this); + } + + public String convertToJson() { + return ConvertHelper.jsonToObject(this); + } + + public String asToString(RestResponse t) { + GsonBuilder builder = new GsonBuilder().serializeNulls(); // value가 null값인 경우도 생성 + builder.disableHtmlEscaping(); + return builder.setPrettyPrinting().create().toJson(t); + } + + public Map toMap(ObjectMapper mapper){ + if(mapper == null) mapper = JsonMapper.getMapper(); + + if(mapper instanceof XmlMapper){ + Map xmlMap = new HashMap<>(); + xmlMap.put("result", this); + return xmlMap; + } + + return mapper.convertValue(this, Map.class); + } +} diff --git a/src/main/java/com/xit/core/config/AuthorizationAspect.java b/src/main/java/com/xit/core/config/AuthorizationAspect.java new file mode 100644 index 0000000..ad6fc0e --- /dev/null +++ b/src/main/java/com/xit/core/config/AuthorizationAspect.java @@ -0,0 +1,69 @@ +package com.xit.core.config; + +import com.xit.core.constant.ErrorCode; +import com.xit.core.exception.AuthorizationException; +import com.xit.core.exception.TokenAuthException; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.WeakKeyException; +import lombok.Setter; +import org.apache.commons.lang3.StringUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Before; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.crypto.SecretKey; +import javax.servlet.http.HttpServletRequest; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * TODO : 토큰 체크 Aspect + * 토큰 체크 Aspect + */ +//@ConfigurationProperties(prefix = "xit.token") +//@Aspect +//@Component +public class AuthorizationAspect { + + @Setter private String apiKey; + @Setter private String secretKey; + + @Before("execution(public * com.xit..controller.*Controller.*(..)) ") + public void insertAdminLog(JoinPoint joinPoint) throws WeakKeyException, UnsupportedEncodingException, TokenAuthException { + SecretKey key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); + String authorization = request.getHeader("Authorization"); + if(StringUtils.isBlank(authorization)){ + throw new AuthorizationException(ErrorCode.AUTH_HEADER_NOT_EXISTS); + } + if(Pattern.matches("^Bearer .*", authorization)) { + authorization = authorization.replaceAll("^Bearer( )*", ""); + Jws jwsClaims = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(authorization); + + if(jwsClaims.getBody() != null) { + Claims claims = jwsClaims.getBody(); + if(!claims.containsKey("apiKey") || !Objects.equals(this.apiKey, claims.get("apiKey").toString()) + || claims.getExpiration() == null) { + throw new TokenAuthException(ErrorCode.INVALID_TOKEN); + } + long exp = claims.getExpiration().getTime(); + if(exp < new Date().getTime()) { + throw new TokenAuthException(ErrorCode.EXPIRED_TOKEN); + } + } + } else { + throw new TokenAuthException(ErrorCode.INVALID_TOKEN); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/DataSourceConfig.java b/src/main/java/com/xit/core/config/DataSourceConfig.java new file mode 100644 index 0000000..65332fc --- /dev/null +++ b/src/main/java/com/xit/core/config/DataSourceConfig.java @@ -0,0 +1,205 @@ +package com.xit.core.config; + +import com.xit.core.support.CamelCaseLinkedMap; +import com.xit.core.support.CamelCaseMap; +import com.xit.core.support.ObjectTypeHandler; +import com.zaxxer.hikari.HikariDataSource; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.session.AutoMappingBehavior; +import org.apache.ibatis.session.ExecutorType; +import org.apache.ibatis.session.LocalCacheScope; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.type.DateTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.mybatis.spring.SqlSessionTemplate; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.sql.DataSource; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Arrays; +import java.util.HashSet; + +@Slf4j +@Configuration +@EnableJpaAuditing +@EnableTransactionManagement +@EnableJpaRepositories( + transactionManagerRef = "jpaTransactionManager", + basePackages = {"com.xit.biz.**.repository", "com.xit.core.**.repository"} +) +// Mybatis +@MapperScan( + basePackages = DataSourceConfig.PACKAGE, + sqlSessionFactoryRef = "sqlSessionFactory" +) +public class DataSourceConfig { + // JPA + //static final String[] REPO_PACKAGES = new String[]{"com.xit.biz.**.repository", "com.xit.core.**.repository"}; + static final String[] ENTITY_PACKAGES = new String[]{"com.xit.biz.**.entity", "com.xit.core.**.entity"}; + + // Mybatis + static final String PACKAGE = "com.xit.**.mapper"; + static final String MAPPER_LOCATION = "classpath:mybatis-mapper/**/*-mapper.xml"; + + private final ApplicationContext applicationContext; + + public DataSourceConfig(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + @Primary + @Bean(name = "apiDataSource") + @ConfigurationProperties("spring.datasource.hikari") + public DataSource apiDataSource() { + return DataSourceBuilder.create() + .type(HikariDataSource.class) + .build(); + } + + //////////////////////////////////////////////////////////////////////////////////////////// + // JPA + /////////////////////////////////////////////////////////////////////////////////////////// + @Primary + @Bean(name = "jpaProperties") + @ConfigurationProperties(prefix = "spring.jpa") + public JpaProperties jpaProperties() { + return new JpaProperties(); + } + + @Primary + @Bean(name = "entityManagerFactory") + public LocalContainerEntityManagerFactoryBean entityManagerFactory( + EntityManagerFactoryBuilder builder, + @Qualifier("apiDataSource") DataSource apiDataSource, + @Qualifier("jpaProperties") JpaProperties jpaProperties + ) { + return builder + .dataSource(apiDataSource) + .properties(jpaProperties.getProperties()) + .packages(ENTITY_PACKAGES) + .persistenceUnit("default") + .build(); + } + + @Primary + @Bean(name = "jpaTransactionManager") + public PlatformTransactionManager jpaTransactionManager( + @Qualifier(value = "entityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) { + JpaTransactionManager transactionManager = new JpaTransactionManager(); + transactionManager.setEntityManagerFactory(entityManagerFactory.getObject()); + transactionManager.setNestedTransactionAllowed(true); + return transactionManager; + } + + @Primary + @Bean + public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){ + return new PersistenceExceptionTranslationPostProcessor(); + } + ///////////////////////////////////////////////////////////////////////////////////// + + + + //////////////////////////////////////////////////////////////////////////////////////////// + // Mybatis + /////////////////////////////////////////////////////////////////////////////////////////// + @Bean(name="sqlSessionFactory") + public SqlSessionFactory sqlSessionFactory(@Qualifier("apiDataSource") DataSource apiDataSource) throws Exception{ + final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); + sessionFactory.setDataSource(apiDataSource); + //sessionFactory.setConfiguration(mybatisConfiguration()); + sessionFactory.setConfigLocation(applicationContext.getResource("classpath:/config/mybatis-config.xml")); + //sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION)); + sessionFactory.setMapperLocations(applicationContext.getResources(MAPPER_LOCATION)); + return sessionFactory.getObject(); + } + + @Bean(name="sqlSessionTemplate") + public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory hikariSqlSessionFactory) { + return new SqlSessionTemplate(hikariSqlSessionFactory); + } + + @Bean(name = "mybatisTransactionManager") + public PlatformTransactionManager mybatisTransactionManager() { + DataSourceTransactionManager dstm = new DataSourceTransactionManager(apiDataSource()); + dstm.setGlobalRollbackOnParticipationFailure(false); + return dstm; + } + + private org.apache.ibatis.session.Configuration mybatisConfiguration() { + org.apache.ibatis.session.Configuration conf = new org.apache.ibatis.session.Configuration(); + conf.setCacheEnabled(true); + conf.setAggressiveLazyLoading(false); + conf.setMultipleResultSetsEnabled(true); + conf.setUseColumnLabel(true); + conf.setUseGeneratedKeys(false); + conf.setAutoMappingBehavior(AutoMappingBehavior.PARTIAL); // NONE / PARTIAL / FULL + conf.setDefaultExecutorType(ExecutorType.REUSE); // SIMPLE / REUSE / BATCH + conf.setDefaultStatementTimeout(25); + conf.setSafeRowBoundsEnabled(false); + conf.setMapUnderscoreToCamelCase(true); + conf.setLocalCacheScope(LocalCacheScope.SESSION); // SESSION / STATEMENT + conf.setJdbcTypeForNull(JdbcType.OTHER); // NULL / VARCHAR / OTHER 파라메터에 null 값이 있는경우 처리 + conf.setLazyLoadTriggerMethods(new HashSet(Arrays.asList("equals","clone","hashCode","toString"))); + conf.setCallSettersOnNulls(true); // 조회시 null값 필드 set + conf.setAggressiveLazyLoading(true); + conf.setReturnInstanceForEmptyRow(true); + + + conf.getTypeAliasRegistry().registerAliases("camelCaseMap", CamelCaseMap.class); + conf.getTypeAliasRegistry().registerAliases("camelCaseLinkedMap", CamelCaseLinkedMap.class); + + conf.getTypeHandlerRegistry().register(Timestamp.class, DateTypeHandler.class); + conf.getTypeHandlerRegistry().register(Time.class, DateTypeHandler.class); + conf.getTypeHandlerRegistry().register(Date.class, DateTypeHandler.class); + conf.getTypeHandlerRegistry().register(Object.class, ObjectTypeHandler.class); + + // TODO plugins 등록 +/* + + + + + +*/ + return conf; + } + ////////////////////////////////////////////////////////////////////////////////////////// + +} + + +// @Bean(name = "mariaDataSourceProperties") +// @ConfigurationProperties("spring.datasource.maria") +// public DataSourceProperties mariaDataSourceProperties(){ +// return new DataSourceProperties(); +// } +// +// @Bean(name="mariaDataSource") +// @ConfigurationProperties(prefix="spring.datasource.dev") //appliction.properties 참고. +// public DataSource mariaDataSource() { +// return new DataSourceProperties() +// .initializeDataSourceBuilder() +// //.type(M.class) +// .build(); +// } \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/EncodingFilterConfig.java b/src/main/java/com/xit/core/config/EncodingFilterConfig.java new file mode 100644 index 0000000..8311be3 --- /dev/null +++ b/src/main/java/com/xit/core/config/EncodingFilterConfig.java @@ -0,0 +1,26 @@ +package com.xit.core.config; + +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter; +import org.springframework.context.annotation.Bean; +import org.springframework.web.filter.CharacterEncodingFilter; + +/** + * 기본 UTF-8인데, encoding이 EUC-KR로 들어오는 경우가 있는 경우(외부에서 들어오는 - 결재 등 -) 사용 + * spring.http.encoding.force = false 추가 + */ +//@Configuration +//TODO :: 기본 UTF-8인데, encoding이 EUC-KR로 들어오는 경우가 있는 경우(외부에서 들어오는 - 결재 등 -) 사용 +public class EncodingFilterConfig { + + @Bean + public FilterRegistrationBean encodingFilterBean() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter(); + filter.setForceEncoding(true); + filter.setEncoding("MS949"); + registrationBean.setFilter(filter); + registrationBean.addUrlPatterns("/ms949filterUrl/*"); + return registrationBean; + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/FilterChainProxyAdvice.java b/src/main/java/com/xit/core/config/FilterChainProxyAdvice.java new file mode 100644 index 0000000..58d8369 --- /dev/null +++ b/src/main/java/com/xit/core/config/FilterChainProxyAdvice.java @@ -0,0 +1,30 @@ +package com.xit.core.config; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.springframework.security.web.firewall.RequestRejectedException; + +import javax.servlet.http.HttpServletResponse; + +/** + * http://localhost:8080/api-docs/%27http://localhost:8080%27/api/auth/authenticate 형태의 request 에러 메세지 skip + * @author Lim, Jong Uk (minuk926) + * @since 2021-07-20 + */ +//@Aspect +//@Component +@Slf4j +public class FilterChainProxyAdvice { + + @Around("execution(public void org.springframework.security.web.FilterChainProxy.doFilter(..))") + public void handleRequestRejectedException (ProceedingJoinPoint pjp) throws Throwable { + try { + pjp.proceed(); + } catch (RequestRejectedException exception) { + log.error("===== Request url error::{} =====", exception.getLocalizedMessage()); + HttpServletResponse response = (HttpServletResponse) pjp.getArgs()[1]; + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/MvcWebConfig.java b/src/main/java/com/xit/core/config/MvcWebConfig.java new file mode 100644 index 0000000..6c0d364 --- /dev/null +++ b/src/main/java/com/xit/core/config/MvcWebConfig.java @@ -0,0 +1,30 @@ +package com.xit.core.config; + +import com.xit.core.support.hateoas.CustomPagedResourceAssembler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.web.HateoasPageableHandlerMethodArgumentResolver; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + + +/** + * Web MVC 에서만 사용 하는 설정 + * @see WebCommonConfig#getAsyncExecutor() : @EnableAsync + */ +@Configuration +// @DataJpaTest를 사용하려면 @EnableWebMvc 사용하면 안된다 +//@EnableWebMvc +@EnableAsync +public class MvcWebConfig implements WebMvcConfigurer { + + /** + * JPA Hateoas 적용 + * @see com.xit.core.support.hateoas.CustomPagedResourceAssembler + * @return CustomPagedResourceAssembler + */ + @Bean + public CustomPagedResourceAssembler customPagedResourceAssembler(){ + return new CustomPagedResourceAssembler<>(new HateoasPageableHandlerMethodArgumentResolver(), 10); + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/WebCommonConfig.java b/src/main/java/com/xit/core/config/WebCommonConfig.java new file mode 100644 index 0000000..46d95c5 --- /dev/null +++ b/src/main/java/com/xit/core/config/WebCommonConfig.java @@ -0,0 +1,314 @@ +package com.xit.core.config; + +import com.xit.core.constant.XitConstants; +import com.xit.core.oauth2.oauth.interceptor.AuthInterceptor; +import com.xit.core.support.RestTemplateLoggingRequestInterceptor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.ReloadableResourceBundleMessageSource; +import org.springframework.context.support.ResourceBundleMessageSource; +import org.springframework.data.web.PageableHandlerMethodArgumentResolver; +import org.springframework.data.web.config.EnableSpringDataWebSupport; +import org.springframework.http.CacheControl; +import org.springframework.http.client.BufferingClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.scheduling.annotation.AsyncConfigurerSupport; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.validation.Validator; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.i18n.CookieLocaleResolver; +import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; +import net.rakugakibox.util.YamlResourceBundle; + +import java.time.Duration; +import java.util.List; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +/** + * Web MVC 와 WebFlux에서 공통으로 사용 하는 설정 + * EnableWebMvc 는 Web MVC 설정에서 set + */ +@Slf4j +@Configuration +@EnableSpringDataWebSupport +public class WebCommonConfig extends AsyncConfigurerSupport implements WebMvcConfigurer { + private static final String[] EXCLUDE_LOCALE_CHANGE_LIST = { + "/resources/**", + "/api-docs", + "/h2-console/**", + "/api-docs/**", + "/v2/api-docs/**", + "/swagger-ui.html", + "/swagger-ui/**", + "/swagger-resources/**", + "/webjars/**" + }; + + @Value("${spring.messages.basename}") + private String basename; + @Value("${spring.messages.encoding}") + private String encoding; + + @Value("${xit.locale}") + private String locale; + + //--------------------------------------------------------------------------- + // Locale Resolver Setting : Cookie + //--------------------------------------------------------------------------- + /** + * Locale Resolver + * default - AcceptHeaderLocaleResolver + * 1. AcceptHeaderLocaleResolver : Http header의 Accept-Language use + * 2. CookieLocaleResolver : Cookie use + * 3. SessionLocaleResolver : Session use + * 4. FixedLocaleResolver : 요청과 관계없이 default locale 사용 + * + * @return LocaleResolver + */ + @Bean + public LocaleResolver localeResolver() { + CookieLocaleResolver localeResolver = new CookieLocaleResolver(); + localeResolver.setCookieName("lang"); + localeResolver.setDefaultLocale(new Locale(locale)); + localeResolver.setCookieHttpOnly(true); + //localeResolver.setCookieMaxAge(); + return localeResolver; + } + + /** + * Locale change interceptor + * + * @return LocaleChangeInterceptor + */ + @Bean + public LocaleChangeInterceptor localeChangeInterceptor() { + LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor(); + localeChangeInterceptor.setParamName("lang"); + return localeChangeInterceptor; + } + + /** + * iterceptor registed + * + * @param registry InterceptorRegistry + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(localeChangeInterceptor()) + //.order(1) + .excludePathPatterns(EXCLUDE_LOCALE_CHANGE_LIST); + registry.addInterceptor(new AuthInterceptor()) + .order(1) + .addPathPatterns("/api/**") + //.excludePathPatterns() + ; + } + //--------------------------------------------------------------------------- + // Locale Resolver Setting + //--------------------------------------------------------------------------- + + + //--------------------------------------------------------------------------- + // Properties type Message loading + //--------------------------------------------------------------------------- + /** + * Validation MessageSource 설정 + * + * @return Validator + */ + @Override + public Validator getValidator() { + ReloadableResourceBundleMessageSource ms = new ReloadableResourceBundleMessageSource(); + ms.setBasename(basename + "validation"); + ms.setDefaultEncoding(encoding); + ms.setCacheSeconds(60); + + LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean(); + localValidatorFactoryBean.setValidationMessageSource(ms); + return localValidatorFactoryBean; + } + + /** + * MessageSource 설정 + * + * @return MessageSource + */ + @Bean//("messageSource") + public MessageSource messageSource() { + ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); + messageSource.setBasenames(basename + "error"); + messageSource.setDefaultEncoding(encoding); + messageSource.setCacheSeconds(60); //60 * 60); + return messageSource; + } + //--------------------------------------------------------------------------- + // Properties type Message loading + //--------------------------------------------------------------------------- + + + //--------------------------------------------------------------------------- + // yaml type Message loading + //--------------------------------------------------------------------------- + /** + * Validation MessageSource 설정 + * @return Validator + */ +// @Override +// public Validator getValidator(){ +// +// YamlMessageSource ms = new YamlMessageSource(); +// ms.setBasename(basename + "validation"); +// ms.setDefaultEncoding(encoding); +// ms.setAlwaysUseMessageFormat(true); +// ms.setUseCodeAsDefaultMessage(true); +// ms.setFallbackToSystemLocale(true); +// ms.setDefaultLocale(new Locale(locale)); +// +// LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean(); +// localValidatorFactoryBean.setValidationMessageSource(ms); +// return localValidatorFactoryBean; +// } +// +// /** +// * MessageSource 설정 +// * @return MessageSource +// */ +// @Bean +// public MessageSource messageSource() { +// +// YamlMessageSource yms = new YamlMessageSource(); +// yms.setBasenames(basename + "error"); +// yms.setDefaultEncoding(encoding); +// yms.setAlwaysUseMessageFormat(true); +// yms.setUseCodeAsDefaultMessage(true); +// yms.setFallbackToSystemLocale(true); +// return yms; +// } + //--------------------------------------------------------------------------- + // yaml type Message loading + //--------------------------------------------------------------------------- + + + @Override + public void addResourceHandlers(final ResourceHandlerRegistry registry) { + registry.addResourceHandler("/**") + .addResourceLocations("classpath:resources/**") + .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS)) + .resourceChain(false); +// registry.addResourceHandler( "/swagger-ui/**") +// .addResourceLocations("classpath:META-INF/resources/webjars/springfox-swagger-ui/") +// .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS)) +// .resourceChain(false); + registry.addResourceHandler("/webjars/**") + .addResourceLocations("classpath:META-INF/resources/webjars/") + .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS)) + .resourceChain(false); + } + + /** + * Pageable 확장 + * + * @param argumentResolvers List + */ + @Override + public void addArgumentResolvers(List argumentResolvers) { + // Sorting에 대한 설정 + //SortHandlerMethodArgumentResolver sortArgumentResolver = new SortHandlerMethodArgumentResolver(); + //sortArgumentResolver.setSortParameter("sortBy"); //sort 요청 시 요청 파라미터 + //sortArgumentResolver.setPropertyDelimiter("-"); //정렬조건 값을 전달할 때 정렬조건필드 property와 정렬기준 property를 구분하는 구분자 + //PageableHandlerMethodArgumentResolver pageableArgumentResolver = new PageableHandlerMethodArgumentResolver(sortArgumentResolver); + + PageableHandlerMethodArgumentResolver pageableArgumentResolver = new PageableHandlerMethodArgumentResolver(); +// pageableArgumentResolver.setOneIndexedParameters(true); // page 기본값을 1로 설정 +// pageableArgumentResolver.setMaxPageSize(500); // 최대 요청 가능한 size +// // 페이지 요청이 없는 경우 기본값 설정 : default : 0, 20 +// pageableArgumentResolver.setFallbackPageable(PageRequest.of(0,10)); +// pageableArgumentResolver.setPageParameterName("page"); // page 파라미터명 +// pageableArgumentResolver.setSizeParameterName("size"); // size 파라미터명 + argumentResolvers.add(pageableArgumentResolver); + } + + /** + * 비동기 호출 Thread 설정 + * AsyncConfigurerSupport 상속 + * 서비스의 public 메소드에 @Async annotation 사용하여 적용(private 메소드는 불가) + * corePoolSize: 기본적으로 실행을 대기하고 있는 Thread의 갯수 + * MaxPoolSise: 동시 동작하는, 최대 Thread 갯수 + * QueueCapacity : MaxPoolSize 초과 요청시 해당 내용을 Queue에 저장하고, 여유가 발생하면 하나씩 꺼내져서 동작 + * ThreadNamePrefix: spring이 생성하는 쓰레드의 접두사 지정 + * org.springframework.util.concurrent.ListenableFuture 타입으로 받아 처리 + * + * @return Executor + */ + @Override + public Executor getAsyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(10); + executor.setMaxPoolSize(100); + executor.setQueueCapacity(50); + executor.setThreadNamePrefix("async-"); + executor.initialize(); + return executor; + } + + /** + * Restful logging + * + * @param restTemplateBuilder RestTemplateBuilder + * @return RestTemplate + * @see RestTemplateLoggingRequestInterceptor + * @see RestTemplateLoggingRequestInterceptor + */ + @Bean + //@Qualifier("restTemplate") + public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) { +// return restTemplateBuilder +// // 로깅 인터셉터에서 Stream을 소비하므로 BufferingClientHttpRequestFactory 을 꼭 써야한다. +// .requestFactory(() -> new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory())) +// // 타임아웃 설정 (ms 단위) +// .setConnectTimeout(Duration.ofSeconds(CoreConstants.CONNECT_TIMEOUT)) +// .setReadTimeout(Duration.ofSeconds(CoreConstants.READ_TIMEOUT)) +// // UTF-8 인코딩으로 메시지 컨버터 추가 +// .additionalMessageConverters(new StringHttpMessageConverter(CoreConstants.CHARSET)) +// // 로깅 인터셉터 설정 +// .additionalInterceptors(new RestTemplateLoggingRequestInterceptor()) +// .build(); + + return restTemplateBuilder + .setReadTimeout(Duration.ofSeconds(XitConstants.READ_TIMEOUT)) + .setConnectTimeout(Duration.ofSeconds(XitConstants.CONNECT_TIMEOUT)) + // UTF-8 인코딩으로 메시지 컨버터 추가 + .messageConverters(new StringHttpMessageConverter(XitConstants.CHARSET)) + // 로깅 인터셉터 설정 + .interceptors(new RestTemplateLoggingRequestInterceptor()) + // 로깅 인터셉터에서 Stream을 소비하므로 BufferingClientHttpRequestFactory 을 꼭 써야한다. + .customizers(restTemplate -> restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()))) + .build(); + } + + /** + * properties files yaml file로 대체하기 위한 모듈 + */ + static class YamlMessageSource extends ResourceBundleMessageSource { + @SuppressWarnings("NullableProblems") + @Override + protected ResourceBundle doGetBundle(String basename, Locale locale) throws MissingResourceException { + return ResourceBundle.getBundle(basename, locale, YamlResourceBundle.Control.INSTANCE); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/_ignore/FluxThymeleafConfig.java b/src/main/java/com/xit/core/config/_ignore/FluxThymeleafConfig.java new file mode 100644 index 0000000..74f63d1 --- /dev/null +++ b/src/main/java/com/xit/core/config/_ignore/FluxThymeleafConfig.java @@ -0,0 +1,64 @@ +//package com.xit.core.config.ignore;//package com.xit.core.config; +// +//import nz.net.ultraq.thymeleaf.LayoutDialect; +//import org.springframework.beans.BeansException; +//import org.springframework.beans.factory.annotation.Qualifier; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.context.ApplicationContext; +//import org.springframework.context.ApplicationContextAware; +//import org.springframework.context.MessageSource; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.lang.NonNull; +//import org.springframework.web.reactive.config.ViewResolverRegistry; +//import org.springframework.web.reactive.config.WebFluxConfigurer; +//import org.thymeleaf.extras.springsecurity5.dialect.SpringSecurityDialect; +//import org.thymeleaf.spring5.ISpringWebFluxTemplateEngine; +//import org.thymeleaf.spring5.SpringWebFluxTemplateEngine; +//import org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver; +//import org.thymeleaf.templateresolver.ITemplateResolver; +// +//import java.util.HashSet; +//import java.util.Set; +// +//@Configuration +//public class FluxThymeleafConfig { +// +// @Value("${spring.thymeleaf.view-names:[/th/*]}") +// private String[] viewNames; +// @Value("${spring.thymeleaf.template-resolver-order:0}") +// private int templateResolverOrder; +// +// @Bean +// public ThymeleafReactiveViewResolver fluxReactiveViewResolver(@Qualifier("webTemplateResolver") ITemplateResolver templateResolver, MessageSource messageSource) { +// ThymeleafReactiveViewResolver viewResolver = new ThymeleafReactiveViewResolver(); +// viewResolver.setTemplateEngine(getTemplateEngine(templateResolver, messageSource)); +// //viewResolver.setViewNames(viewNames); +// viewResolver.setOrder(templateResolverOrder); +// return viewResolver; +// } +// +// @Bean +// public LayoutDialect layoutDialect() { +// return new LayoutDialect(); +// } +// +// private ISpringWebFluxTemplateEngine getTemplateEngine(@Qualifier("webTemplateResolver") ITemplateResolver templateResolver, MessageSource messageSource) { +// SpringWebFluxTemplateEngine templateEngine = new SpringWebFluxTemplateEngine(); +// +// templateEngine.addDialect(new LayoutDialect()); +// templateEngine.setTemplateResolver(templateResolver); +// templateEngine.setTemplateEngineMessageSource(messageSource); +// //templateEngine.addDialect(new SpringSecurityDialect()); // security 사용 가능 set +// templateEngine.setEnableSpringELCompiler(true); // Spring EL 사용 +// return templateEngine; +// } +// +// +//// @Override +//// public void configureViewResolvers(ViewResolverRegistry registry) { +//// registry.viewResolver(fluxReactiveViewResolver()); +//// } +// +// +//} \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/_ignore/FluxWebConfig.java b/src/main/java/com/xit/core/config/_ignore/FluxWebConfig.java new file mode 100644 index 0000000..69f2e00 --- /dev/null +++ b/src/main/java/com/xit/core/config/_ignore/FluxWebConfig.java @@ -0,0 +1,18 @@ +//package com.xit.core.config.ignore; +// +//import org.springframework.context.annotation.Configuration; +//import org.springframework.http.MediaType; +//import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; +//import org.springframework.web.reactive.config.EnableWebFlux; +//import org.springframework.web.reactive.config.WebFluxConfigurer; +// +///** +// * WEB MVC와 함께 사용(EnableWebMvc)시는 EnableWebFlux 설정 사용 불가 +// * 또한 EnableWebFlux 설정은 springboot의 자동설정을 사용하지 않을때만 필요 +// * 즉, 직접 제어를 할때 사용 한다 +// */ +//@Configuration +////@EnableWebFlux //springboot의 webflux를 사용하지 않는 경우만 설정 +//public class WebFluxConfig implements WebFluxConfigurer { +// +//} \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/_ignore/FluxWebServerFactory.java b/src/main/java/com/xit/core/config/_ignore/FluxWebServerFactory.java new file mode 100644 index 0000000..1205d7f --- /dev/null +++ b/src/main/java/com/xit/core/config/_ignore/FluxWebServerFactory.java @@ -0,0 +1,48 @@ +package com.xit.core.config._ignore; + + +import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; +import org.springframework.boot.web.server.WebServer; +import org.springframework.boot.web.server.WebServerException; +import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.http.server.reactive.ServletHttpHandlerAdapter; + +import javax.servlet.ServletContext; +import javax.servlet.ServletRegistration; + +//@Component +public class FluxWebServerFactory implements ReactiveWebServerFactory { + private static final String DEFAULT_SERVLET_NAME = "http-handler-adapter"; + + private final ServletContext storedServletContext; + private final int port = 8080; + + public FluxWebServerFactory(ServletContext storedServletContext) { + this.storedServletContext = storedServletContext; + } + + @Override + public WebServer getWebServer(HttpHandler httpHandler) { + // create and register special servlet + ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter(httpHandler); + ServletRegistration.Dynamic registration = storedServletContext.addServlet(DEFAULT_SERVLET_NAME, servlet); + if (registration == null) { + throw new IllegalStateException("Failed to register servlet with name '" + DEFAULT_SERVLET_NAME + "'. " + + "Check if there is another servlet registered under the same name."); + } + + registration.setLoadOnStartup(1); + registration.addMapping("/"); + registration.setAsyncSupported(true); + + //we cannot control external server/tomcat, so the webserver does nothing + return new WebServer(){ + public void start() throws WebServerException {} + public void stop() throws WebServerException {} + public int getPort() { + return port; + } + }; + } + +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/_ignore/H2TcpServerConfig.java b/src/main/java/com/xit/core/config/_ignore/H2TcpServerConfig.java new file mode 100644 index 0000000..575c677 --- /dev/null +++ b/src/main/java/com/xit/core/config/_ignore/H2TcpServerConfig.java @@ -0,0 +1,67 @@ +//package com.xit.core.config.ignore; +// +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.context.annotation.*; +// +//import java.sql.SQLException; +// +//import org.h2.tools.Server; +// +////** +///* //TODO: H2 TCP인 경우 서버 먼저 start +///* DataSource 위에 @DependsOn(value = {"h2TcpServer"}) 로 먼저 start하도록 설정 +//*/ +//@Slf4j +//@Configuration +//public class H2TcpServerConfig { +// +// @Value("${spring.datasource.hikari.jdbc-url}") +// private String jdbcUrl; +// +// @Bean(name="h2TcpServer", destroyMethod = "stop") +// public Server h2TcpServerConfig() throws SQLException{ +// if(jdbcUrl.contains("jdbc:h2:tcp:")) { +// Server server = adviceRun(9092, "", "./src/main/resources/data/xitdb;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE", FilePath.absolute); +// //Server server = defaultRun(9092); +// if (server.isRunning(true)) { +// log.info("#############################################################"); +// log.info(" h2 server url = {}", server.getURL()); +// log.info("#############################################################"); +// } +// return server; +// }else{ +// return null; +// } +// +// } +// +// private Server adviceRun(int port, String externalDbName, String dbname, FilePath db_store) throws SQLException { +// return Server.createTcpServer( +// "-tcp", +// "-tcpAllowOthers", +// "-ifNotExists", +// //"-tcpPort", port+"", "-key", externalDbName, db_store.value2(dbname)).start(); +// "-tcpPort", port+"").start(); +// } +// +// private Server defaultRun(int port) throws SQLException { +// return Server.createTcpServer( +// "-tcp", +// "-tcpAllowOthers", +// "-ifNotExists", +// "-tcpPort", port+"").start(); +// } +// +// enum FilePath { +// absolute("~/"), +// relative("./"); +// String prefix; +// FilePath(String prefix){ +// this.prefix = prefix; +// } +// public String value2(String dbname){ +// return prefix + dbname; +// } +// } +//} \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/_ignore/MvcThymeleafConfig.java b/src/main/java/com/xit/core/config/_ignore/MvcThymeleafConfig.java new file mode 100644 index 0000000..a88375f --- /dev/null +++ b/src/main/java/com/xit/core/config/_ignore/MvcThymeleafConfig.java @@ -0,0 +1,83 @@ +package com.xit.core.config._ignore; + +//import lombok.extern.slf4j.Slf4j; +//import nz.net.ultraq.thymeleaf.LayoutDialect; +//import org.springframework.beans.factory.annotation.Qualifier; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.context.MessageSource; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.web.servlet.ViewResolver; +//import org.thymeleaf.spring5.SpringTemplateEngine; +//import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver; +//import org.thymeleaf.spring5.view.ThymeleafViewResolver; +//import org.thymeleaf.templateresolver.ITemplateResolver; +// +//import java.util.HashSet; +//import java.util.Set; +// +///** +// * JSP와 함께 사용시 사용하면 JSP 사용 불가하여 미상용으로 이동 +// * +// */ +//@Slf4j +//@Configuration +//public class MvcThymeleafConfig { +// @Value("${spring.thymeleaf.view-names:[/th/*]}") +// private String[] viewNames; +// @Value("${spring.thymeleaf.prefix:classpath:templates/}") +// private String prefix; +// @Value("${spring.thymeleaf.suffix:.html}") +// private String suffix; +// @Value("${spring.thymeleaf.mode:HTML}") +// private String mode; +// @Value("${spring.thymeleaf.encoding:UTF-8}") +// private String encoding; +// @Value("${spring.thymeleaf.cache:false}") +// private boolean isCache; +// @Value("${spring.thymeleaf.check-template:true}") +// private boolean isCheckTemplate; +// @Value("${spring.thymeleaf.template-resolver-order:0}") +// private int templateResolverOrder; +// +// @Bean +// public ITemplateResolver webTemplateResolver() { +// SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver(); +// //templateResolver.setApplicationContext(applicationContext); +// templateResolver.setPrefix(prefix); +// templateResolver.setCharacterEncoding(encoding); +// templateResolver.setSuffix(suffix); +// templateResolver.setTemplateMode(mode); +// templateResolver.setCacheable(isCache); +// templateResolver.setCheckExistence(isCheckTemplate); +// return templateResolver; +// } +// +// @Bean +// public ViewResolver viewResolver( ITemplateResolver webTemplateResolver, MessageSource messageSource) { +// ThymeleafViewResolver viewResolver = new ThymeleafViewResolver(); +// viewResolver.setTemplateEngine(getTemplateEngine(webTemplateResolver, messageSource)); +// viewResolver.setCharacterEncoding(encoding); +// //viewResolver.setViewNames(viewNames); +// viewResolver.setOrder(templateResolverOrder); +// return viewResolver; +// } +// +// @Bean +// public LayoutDialect layoutDialect() { +// return new LayoutDialect(); +// } +// +// private SpringTemplateEngine getTemplateEngine(@Qualifier("webTemplateResolver") ITemplateResolver templateResolver, MessageSource messageSource) { +// Set templatesResolvers = new HashSet<>(); +// templatesResolvers.add(templateResolver); +// +// SpringTemplateEngine templateEngine = new SpringTemplateEngine(); +// templateEngine.addDialect(new LayoutDialect()); +// templateEngine.setTemplateResolvers(templatesResolvers); +// templateEngine.setTemplateEngineMessageSource(messageSource); +// templateEngine.setEnableSpringELCompiler(true); // Spring EL 사용 +// //templateEngine.addDialect(new SpringSecurityDialect()); // security 사용 가능 set +// return templateEngine; +// } +//} \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/_ignore/TomcatServletServerConfig.java b/src/main/java/com/xit/core/config/_ignore/TomcatServletServerConfig.java new file mode 100644 index 0000000..63f0cfa --- /dev/null +++ b/src/main/java/com/xit/core/config/_ignore/TomcatServletServerConfig.java @@ -0,0 +1,38 @@ +package com.xit.core.config._ignore; + +import org.apache.catalina.connector.Connector; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.servlet.server.ServletWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class TomcatServletServerConfig { + + @Value("${server.port.http:8080}") + private int serverPortHttp; + + @Bean + public ServletWebServerFactory serverFactory(){ + TomcatServletWebServerFactory serverFactory = new TomcatServletWebServerFactory(); + serverFactory.addAdditionalTomcatConnectors(createStandardConnector()); + return serverFactory; + } + + private Connector createStandardConnector(){ + Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); + connector.setPort(serverPortHttp); + return connector; + } +} +/* + server.port : 8443 + server.port.http: 8080 + + #SSL + server.ssl.key-store: /data/xit/cert/tomcat/keystore + server.ssl.key-store-password: + server.ssl.key-password: + server.ssl.key-alias: 도메인인 + */ \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/aop/LoggerAspect.java b/src/main/java/com/xit/core/config/aop/LoggerAspect.java new file mode 100644 index 0000000..cf670e8 --- /dev/null +++ b/src/main/java/com/xit/core/config/aop/LoggerAspect.java @@ -0,0 +1,87 @@ +package com.xit.core.config.aop; + +import com.xit.core.util.Checks; +import com.xit.core.util.DateUtil; +import com.xit.core.util.LogUtil; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.json.simple.JSONObject; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +@Component +@Aspect +@Slf4j +public class LoggerAspect { + @Value("${api.reponse.logging}") + private boolean isResLogging; + + @Pointcut("execution(* com.xit..*Controller.*(..))") // 이런 패턴이 실행될 경우 수행 + public void loggerPointCut() { + } + + @Around("loggerPointCut()") + public Object methodLogger(ProceedingJoinPoint pjp) throws Throwable { + HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); // request 정보를 가져온다. + + String controllerName = pjp.getSignature().getDeclaringType().getSimpleName(); + String methodName = pjp.getSignature().getName(); + + Map params = new LinkedHashMap<>(); + + try { + params.put("locale", request.getLocale()); + params.put("request_uri", request.getRequestURI()); + params.put("http_method", request.getMethod()); + params.put("controller", controllerName); + params.put("method", methodName); + params.put("params", getParams(request)); + params.put("log_time", DateUtil.getTodayAndNowTime("-", false)); + } catch (Exception e) { + log.error("LoggerAspect error", e); + } + log.info("{}{}{}" + , "\n//################################ Paramters ###################################" + , LogUtil.toString(params) + , "\n################################################################################//" + ); + + Object result = pjp.proceed(); + + if(isResLogging){ + log.info("{}{}{}" + , "\n//################################ Result ###################################\n" + , Checks.isNotEmpty(result) ? result.toString() : null + , "\n#############################################################################//"); + } + return result; + + } + + /** + * request 에 담긴 정보를 JSONObject 형태로 반환한다. + * @param request + * @return + */ + private static JSONObject getParams(HttpServletRequest request) { + JSONObject jsonObject = new JSONObject(); + Enumeration params = request.getParameterNames(); + while (params.hasMoreElements()) { + String param = params.nextElement(); + String replaceParam = param.replaceAll("\\.", "-"); + jsonObject.put(replaceParam, request.getParameter(param)); + } + return jsonObject; + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/filter/log/CustomCommonsRequestLoggingFilter.java b/src/main/java/com/xit/core/config/filter/log/CustomCommonsRequestLoggingFilter.java new file mode 100644 index 0000000..466d906 --- /dev/null +++ b/src/main/java/com/xit/core/config/filter/log/CustomCommonsRequestLoggingFilter.java @@ -0,0 +1,107 @@ +package com.xit.core.config.filter.log; + +import com.xit.core.constant.XitConstants; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.web.filter.CommonsRequestLoggingFilter; +import org.springframework.web.util.ContentCachingRequestWrapper; + +import javax.servlet.ServletInputStream; +import javax.servlet.annotation.WebFilter; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.regex.Pattern; + +/** + * WebFilter로 등록 + * Application main에 @ServletComponentScan annotation 필요 + * logging.level.org.springframework.web.filter=debug 로 설정(로그 출력) + * --> 설정 불필요 :: 적용이 안돼고 있음 + * logback에 CustomCommonsRequestLoggingFilter 에 대한 logger level=debug or info로 설정 해야만 로그 출력 + * CommonsRequestLoggingFilter 확장 + * logging에서 제외할 url 패턴 지정 + */ +@WebFilter(urlPatterns = "/api/*") +@Order(Ordered.HIGHEST_PRECEDENCE) +public class CustomCommonsRequestLoggingFilter extends CommonsRequestLoggingFilter { + private static final Pattern ONLY_LOGGING_PATTERN = Pattern.compile(XitConstants.EXCLUDE_LOGGING_URL_PATTERN); + CatchLocation catchLocation = CatchLocation.AFTER; + + MessageMaker messageMaker; + + enum CatchLocation { + ALL, BEFORE, AFTER + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + //It can be either RequestURI(includes context path) + String loggingPattern = request.getRequestURI(); + //or ServletPath based on your requirement + //loggingPattern = request.getServletPath(); + + // If you want to log a particular endpoint then return true + // or if you don't want to log the endpoint then return false + return ONLY_LOGGING_PATTERN.matcher(loggingPattern).find(); + } + + @Override + protected boolean shouldLog(HttpServletRequest request) { + return logger.isInfoEnabled(); + } + + @Override + protected void beforeRequest(HttpServletRequest request, String message) { + if(catchLocation == CatchLocation.BEFORE || catchLocation == CatchLocation.ALL) { + logger.info(message); + } + } + + @Override + protected void afterRequest(HttpServletRequest request, String message) { + if(request instanceof ContentCachingRequestWrapper) { + try (ServletInputStream is = request.getInputStream()) { + if(is.available() >= 0) { + while(true) { + int read = is.read(new byte[8096]); + if (read < 8096) { + break; + } + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + if(catchLocation == CatchLocation.AFTER || catchLocation == CatchLocation.ALL) { + logger.info(message); + } + } + + @Override + protected String createMessage(HttpServletRequest request, String prefix, String suffix) { +// if(!logger.isWarnEnabled()) return null; + if(this.messageMaker != null) { + return this.messageMaker.make(request, prefix, suffix); + } else { + return super.createMessage(request, prefix, suffix); + } + } + + public interface MessageMaker { + String make(HttpServletRequest request, String prefix, String suffix); + } + + +// public CatchLocation getCatchLocation() { +// return catchLocation; +// } +// +// public void setCatchLocation(CatchLocation catchLocation) { +// this.catchLocation = catchLocation; +// } +// +// public void setMessageMaker(MessageMaker messageMaker) { +// this.messageMaker = messageMaker; +// } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/filter/log/ReadableRequestWrapperFilter.java b/src/main/java/com/xit/core/config/filter/log/ReadableRequestWrapperFilter.java new file mode 100644 index 0000000..1c4e8fb --- /dev/null +++ b/src/main/java/com/xit/core/config/filter/log/ReadableRequestWrapperFilter.java @@ -0,0 +1,160 @@ +package com.xit.core.config.filter.log; + +import com.xit.core.util.Checks; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; +import org.apache.http.entity.ContentType; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.springframework.util.StringUtils; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.*; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +//@WebFilter(urlPatterns = "/api/*") +//@Order(Ordered.HIGHEST_PRECEDENCE) +public class ReadableRequestWrapperFilter implements Filter { + @Override + public void init(FilterConfig filterConfig) { + // Do nothing + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + ReadableRequestWrapper wrapper = new ReadableRequestWrapper((HttpServletRequest)request); + chain.doFilter(wrapper, response); + } + + @Override + public void destroy() { + // Do nothing + } + + public class ReadableRequestWrapper extends HttpServletRequestWrapper { + private final Charset encoding; + private byte[] rawData; + private Map params = new HashMap<>(); + + public ReadableRequestWrapper(HttpServletRequest request) { + super(request); + this.params.putAll(request.getParameterMap()); // 원래의 파라미터를 저장 + + String charEncoding = request.getCharacterEncoding(); // 인코딩 설정 + this.encoding = !StringUtils.hasText(charEncoding) ? StandardCharsets.UTF_8 : Charset.forName(charEncoding); + + try { + InputStream is = request.getInputStream(); + this.rawData = IOUtils.toByteArray(is); // InputStream 을 별도로 저장한 다음 getReader() 에서 새 스트림으로 생성 + + // body 파싱 + String collect = this.getReader().lines().collect(Collectors.joining(System.lineSeparator())); + if (!StringUtils.hasText(collect)) { // body 가 없을경우 로깅 제외 + return; + } + if (request.getContentType() != null && request.getContentType().contains( + ContentType.MULTIPART_FORM_DATA.getMimeType())) { // 파일 업로드시 로깅제외 + return; + } + JSONParser jsonParser = new JSONParser(); + Object parse = jsonParser.parse(collect); + if (parse instanceof JSONArray) { + JSONArray jsonArray = (JSONArray)jsonParser.parse(collect); + setParameter("requestBody", jsonArray.toJSONString()); + } else { + JSONObject jsonObject = (JSONObject)jsonParser.parse(collect); + @SuppressWarnings("rawtypes") + Iterator iterator = jsonObject.keySet().iterator(); + if (iterator.hasNext()) { + do { + String key = (String) iterator.next(); + setParameter(key, Checks.isNotEmpty(jsonObject.get(key)) ? jsonObject.get(key).toString().replace("\"", "\\\"") : ""); + } while (iterator.hasNext()); + } + } + } catch (Exception e) { + log.error("ReadableRequestWrapper init error", e); + } + } + + @Override + public String getParameter(String name) { + String[] paramArray = getParameterValues(name); + if (paramArray != null && paramArray.length > 0) { + return paramArray[0]; + } else { + return null; + } + } + + @Override + public Map getParameterMap() { + return Collections.unmodifiableMap(params); + } + + @Override + public Enumeration getParameterNames() { + return Collections.enumeration(params.keySet()); + } + + @Override + public String[] getParameterValues(String name) { + String[] result = null; + String[] dummyParamValue = params.get(name); + + if (dummyParamValue != null) { + result = new String[dummyParamValue.length]; + System.arraycopy(dummyParamValue, 0, result, 0, dummyParamValue.length); + } + return result; + } + + public void setParameter(String name, String value) { + String[] param = {value}; + setParameter(name, param); + } + + public void setParameter(String name, String[] values) { + params.put(name, values); + } + + @Override + public ServletInputStream getInputStream() { + final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.rawData); + + return new ServletInputStream() { + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + // Do nothing + } + + public int read() { + return byteArrayInputStream.read(); + } + }; + } + + @Override + public BufferedReader getReader() { + return new BufferedReader(new InputStreamReader(this.getInputStream(), this.encoding)); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/filter/xss/RequestBodyLucyXssFilter.java b/src/main/java/com/xit/core/config/filter/xss/RequestBodyLucyXssFilter.java new file mode 100644 index 0000000..ca12a76 --- /dev/null +++ b/src/main/java/com/xit/core/config/filter/xss/RequestBodyLucyXssFilter.java @@ -0,0 +1,65 @@ +package com.xit.core.config.filter.xss; + +import com.xit.core.util.Checks; +import org.apache.commons.lang3.StringUtils; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; + +import javax.servlet.*; +import javax.servlet.annotation.WebFilter; +import javax.servlet.annotation.WebInitParam; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + + +/** + * Lucy-Xss 필터 사용을 위한 Wrapping Filter class + * json type parameter 처리를 위해 필요 + * + * @see RequestLucyXssWrapper + */ +@WebFilter( + initParams = {@WebInitParam(name = "extUrl", value = StringUtils.EMPTY)}, + urlPatterns = "/api/*" +) +@Order(Ordered.HIGHEST_PRECEDENCE) +public class RequestBodyLucyXssFilter implements Filter { + private List extUrl; + + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) + throws IOException, ServletException { + + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) res; + RequestLucyXssWrapper reqWrapper = null; + String path = ((HttpServletRequest) req).getServletPath(); + + try { + if (Checks.isEmpty(extUrl) || !extUrl.contains(path)) { + reqWrapper = new RequestLucyXssWrapper(request); + chain.doFilter(reqWrapper , response); + + } else { + chain.doFilter(request, response); + } + + } catch (Exception e) { + e.printStackTrace(); + } + + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + String excludePattern = filterConfig.getInitParameter("extUrls"); + if(Checks.isNotEmpty(excludePattern)) extUrl = Arrays.asList(excludePattern.split(",")); + } + + @Override + public void destroy() { + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/filter/xss/RequestLucyXssWrapper.java b/src/main/java/com/xit/core/config/filter/xss/RequestLucyXssWrapper.java new file mode 100644 index 0000000..95fcd85 --- /dev/null +++ b/src/main/java/com/xit/core/config/filter/xss/RequestLucyXssWrapper.java @@ -0,0 +1,94 @@ +package com.xit.core.config.filter.xss; + +import com.nhncorp.lucy.security.xss.XssFilter; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import java.io.*; +import java.nio.charset.StandardCharsets; + +/** + * Lucy-Xss 필터 사용을 위한 Wrapping class + * json type parameter 처리를 위해 필요 + * + * @see RequestBodyLucyXssFilter + */ +public class RequestLucyXssWrapper extends HttpServletRequestWrapper { + private final byte[] b; + + public RequestLucyXssWrapper(HttpServletRequest request) throws IOException { + super(request); + XssFilter filter = XssFilter.getInstance("lucy-xss-sax.xml"); + b = new String(filter.doFilter(getBody(request))).getBytes(StandardCharsets.UTF_8); + } + + public ServletInputStream getInputStream() throws IOException { + final ByteArrayInputStream bis = new ByteArrayInputStream(b); + return new ServletInputStreamImpl(bis); + } + + static class ServletInputStreamImpl extends ServletInputStream { + private InputStream is; + + public ServletInputStreamImpl(InputStream bis) { + is = bis; + } + + public int read() throws IOException { + return is.read(); + } + + public int read(byte[] b) throws IOException { + return is.read(b); + } + + @Override + public boolean isFinished() { + return false; + } + + @Override + public boolean isReady() { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) { + + } + } + + public static String getBody(HttpServletRequest request) throws IOException { + String body = null; + BufferedReader br= null; + StringBuilder sb= new StringBuilder(); + try { + InputStream inputStream = request.getInputStream(); + if (inputStream != null) { + br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + char[] charBuffer = new char[128]; + int bytesRead = -1; + while ((bytesRead = br.read(charBuffer)) > 0) { + sb.append(charBuffer, 0, bytesRead); + } + + } else { + sb.append(""); + } + } catch (IOException ex) { + throw ex; + } finally { + if (br!= null) { + try { + br.close(); + } catch (IOException ex) { + throw ex; + } + } + } + body = sb.toString(); + return body; + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/support/ApplicationContextProvider.java b/src/main/java/com/xit/core/config/support/ApplicationContextProvider.java new file mode 100644 index 0000000..53c1c8c --- /dev/null +++ b/src/main/java/com/xit/core/config/support/ApplicationContextProvider.java @@ -0,0 +1,29 @@ +package com.xit.core.config.support; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * ApplicationContext 를 이용하여 component 주입 + * Bean으로 등록되는 클래스 내에서만 @Autowired / @Resource 등이 동작 + * Filter / Interceptor 등에서 Bean 사용시 필요 + * + * @see com.xit.core.util.SpringUtils + */ +@Component +public class ApplicationContextProvider implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext ctx) throws BeansException { + applicationContext = ctx; + } + + public static ApplicationContext getApplicationContext() { + return applicationContext; + } + +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/support/LowercaseSnakePhysicalNamingStrategy.java b/src/main/java/com/xit/core/config/support/LowercaseSnakePhysicalNamingStrategy.java new file mode 100644 index 0000000..645be12 --- /dev/null +++ b/src/main/java/com/xit/core/config/support/LowercaseSnakePhysicalNamingStrategy.java @@ -0,0 +1,47 @@ +package com.xit.core.config.support; + +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.boot.model.naming.PhysicalNamingStrategy; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; + +public class LowercaseSnakePhysicalNamingStrategy implements PhysicalNamingStrategy { + @Override + public Identifier toPhysicalCatalogName(Identifier identifier, JdbcEnvironment jdbcEnvironment) { + if (identifier == null) { + return null; + } + return convertToSnakeUpperCase(identifier); + } + + @Override + public Identifier toPhysicalSchemaName(Identifier identifier, JdbcEnvironment jdbcEnvironment) { + if (identifier == null) { + return null; + } + return convertToSnakeUpperCase(identifier); + } + + @Override + public Identifier toPhysicalTableName(Identifier identifier, JdbcEnvironment jdbcEnvironment) { + return convertToSnakeUpperCase(identifier); + } + + @Override + public Identifier toPhysicalSequenceName(Identifier identifier, JdbcEnvironment jdbcEnvironment) { + return convertToSnakeUpperCase(identifier); + } + + @Override + public Identifier toPhysicalColumnName(Identifier identifier, JdbcEnvironment jdbcEnvironment) { + return convertToSnakeUpperCase(identifier); + } + + private Identifier convertToSnakeUpperCase(final Identifier identifier) { + final String regex = "([a-z])([A-Z])"; + final String replacement = "$1_$2"; + final String newName = identifier.getText() + .replaceAll(regex, replacement) + .toLowerCase(); + return Identifier.toIdentifier(newName); + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/support/P6spyLoggingConfig.java b/src/main/java/com/xit/core/config/support/P6spyLoggingConfig.java new file mode 100644 index 0000000..74254d6 --- /dev/null +++ b/src/main/java/com/xit/core/config/support/P6spyLoggingConfig.java @@ -0,0 +1,19 @@ +package com.xit.core.config.support; + +import com.p6spy.engine.spy.P6SpyOptions; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +@Configuration +public class P6spyLoggingConfig { + + /** + * JPA SQL Logging + * decorator.datasource.p6spy.enable-logging: true / false + */ + @PostConstruct + public void setLogMessageFormat() { + P6SpyOptions.getActiveInstance().setLogMessageFormat(P6spySqlFormatConfiguration.class.getName()); + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/support/P6spySqlFormatConfiguration.java b/src/main/java/com/xit/core/config/support/P6spySqlFormatConfiguration.java new file mode 100644 index 0000000..7602a06 --- /dev/null +++ b/src/main/java/com/xit/core/config/support/P6spySqlFormatConfiguration.java @@ -0,0 +1,66 @@ +package com.xit.core.config.support; + +import com.p6spy.engine.logging.Category; +import com.p6spy.engine.spy.appender.MessageFormattingStrategy; +import lombok.extern.slf4j.Slf4j; +import org.hibernate.engine.jdbc.internal.FormatStyle; + +import java.util.Locale; +import java.util.Objects; +import java.util.Stack; + +@Slf4j +public class P6spySqlFormatConfiguration implements MessageFormattingStrategy { + @Override + public String formatMessage(final int connectionId, final String now, final long elapsed, final String category, final String prepared, final String sql, final String url) { + Stack callStack = new Stack<>(); + StackTraceElement[] stackTrace = new Throwable().getStackTrace(); + + for (StackTraceElement stackTraceElement : stackTrace) { + String trace = stackTraceElement.toString(); + if (trace.startsWith("io.p6spy") && !trace.contains("P6spyPrettySqlFormatter")) { + callStack.push(trace); + } + } + + StringBuilder callStackBuilder = new StringBuilder(); + int order = 1; + while(callStack.size() != 0) { + callStackBuilder.append("\n\t\t").append(order++).append(". ").append(callStack.pop()); + } + + String message = new StringBuilder().append("\n\n\tConnection ID: ").append(connectionId) + .append("\n\tExecution Time: ").append(elapsed).append(" ms\n") + .append("\n\tCall Stack (number 1 is entry point): ").append(callStackBuilder).append("\n") + .toString(); + + return sqlFormat(sql, category, message); + } + + private String sqlFormat(String sql, String category, String message) { + if(sql.trim().isEmpty()) { + return ""; + } + + if(Objects.equals(Category.STATEMENT.getName(), category)) { + String s = sql.trim().toLowerCase(Locale.ROOT); + if(s.startsWith("create") || s.startsWith("alter") || s.startsWith("comment") || s.startsWith("drop")) { + sql = FormatStyle.DDL + .getFormatter() + .format(sql); + } + else { + sql = FormatStyle.BASIC + .getFormatter() + .format(sql); + } + } + + return new StringBuilder().append("\n") + .append("----------------------------------------------------------------------------------------------------") + .append(sql) //.append(sql.toUpperCase()) + .append(message) + .append("----------------------------------------------------------------------------------------------------") + .toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/support/PersistenceUnitInfoImpl.java b/src/main/java/com/xit/core/config/support/PersistenceUnitInfoImpl.java new file mode 100644 index 0000000..7b4e6cc --- /dev/null +++ b/src/main/java/com/xit/core/config/support/PersistenceUnitInfoImpl.java @@ -0,0 +1,128 @@ +package com.xit.core.config.support; + +import org.hibernate.jpa.HibernatePersistenceProvider; + +import javax.persistence.SharedCacheMode; +import javax.persistence.ValidationMode; +import javax.persistence.spi.ClassTransformer; +import javax.persistence.spi.PersistenceUnitInfo; +import javax.persistence.spi.PersistenceUnitTransactionType; +import javax.sql.DataSource; +import java.net.URL; +import java.util.*; + +public class PersistenceUnitInfoImpl implements PersistenceUnitInfo { + public final String jpaVersion; + private final String persistenceUnitName; + private final List managedClassNames; + private final Properties properties; + + private PersistenceUnitTransactionType transactionType = PersistenceUnitTransactionType.RESOURCE_LOCAL; + private final List mappingFileNames = new ArrayList<>(); + private DataSource jtaDataSource; + private DataSource nonJtaDataSource; + + public PersistenceUnitInfoImpl(String jpaVersion, String persistenceUnitName, List managedClassNames, Properties properties) { + this.jpaVersion = jpaVersion; + this.persistenceUnitName = persistenceUnitName; + this.managedClassNames = managedClassNames; + this.properties = properties; + } + + @Override + public String getPersistenceUnitName() { + return persistenceUnitName; + } + + @Override + public String getPersistenceProviderClassName() { + return HibernatePersistenceProvider.class.getName(); + } + + @Override + public PersistenceUnitTransactionType getTransactionType() { + return transactionType; + } + + @Override + public DataSource getJtaDataSource() { + return jtaDataSource; + } + + public PersistenceUnitInfoImpl setJtaDataSource(DataSource jtaDataSource) { + this.jtaDataSource = jtaDataSource; + this.nonJtaDataSource = null; + transactionType = PersistenceUnitTransactionType.JTA; + return this; + } + + @Override + public DataSource getNonJtaDataSource() { + return nonJtaDataSource; + } + + public PersistenceUnitInfoImpl setNonJtaDataSource(DataSource nonJtaDataSource) { + this.nonJtaDataSource = nonJtaDataSource; + this.jtaDataSource = null; + transactionType = PersistenceUnitTransactionType.RESOURCE_LOCAL; + return this; + } + + @Override + public List getMappingFileNames() { + return mappingFileNames; + } + + @Override + public List getJarFileUrls() { + return Collections.emptyList(); + } + + @Override + public URL getPersistenceUnitRootUrl() { + return null; + } + + @Override + public List getManagedClassNames() { + return managedClassNames; + } + + @Override + public boolean excludeUnlistedClasses() { + return false; + } + + @Override + public SharedCacheMode getSharedCacheMode() { + return SharedCacheMode.UNSPECIFIED; + } + + @Override + public ValidationMode getValidationMode() { + return ValidationMode.AUTO; + } + + public Properties getProperties() { + return properties; + } + + @Override + public String getPersistenceXMLSchemaVersion() { + return jpaVersion; + } + + @Override + public ClassLoader getClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } + + @Override + public void addTransformer(ClassTransformer transformer) { + } + + @Override + public ClassLoader getNewTempClassLoader() { + return null; + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/config/support/SpringDocApiConfig.java b/src/main/java/com/xit/core/config/support/SpringDocApiConfig.java new file mode 100644 index 0000000..1fd60aa --- /dev/null +++ b/src/main/java/com/xit/core/config/support/SpringDocApiConfig.java @@ -0,0 +1,181 @@ +package com.xit.core.config.support; + +import com.xit.core.constant.XitConstants; +import io.swagger.v3.core.converter.ModelConverters; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.media.Content; +import io.swagger.v3.oas.models.media.MediaType; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.responses.ApiResponse; +import io.swagger.v3.oas.models.responses.ApiResponses; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springdoc.core.GroupedOpenApi; +import org.springdoc.core.customizers.OpenApiCustomiser; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Collections; + +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +@Configuration +public class SpringDocApiConfig { + + @Bean + public OpenAPI openAPI(@Value("${xit.api.version}") String appVersion, + @Value("${xit.api.url}") String url, @Value("${spring.profiles.active}") String active) { + Info info = new Info().title("xit API - " + active).version(appVersion) + .description("Spring Boot를 이용한 xit 웹 애플리케이션 API입니다.") + .termsOfService("http://swagger.io/terms/") + .contact(new Contact().name("xit").url("https://www.lll.com/").email("xit@xit.com")) + .license(new License().name("Apache License Version 2.0").url("http://www.apache.org/licenses/LICENSE-2.0")); + + +//TODO : 등록시 url 뒤에 url에 ... (curl 실행시) +// List servers = Arrays.asList(new Server().url(url).description("xit (" + active +")")); + + // user / password +// SecurityScheme securityScheme = new SecurityScheme() +// .type(SecurityScheme.Type.HTTP).scheme("basic") +// .in(SecurityScheme.In.HEADER).name("Authorization"); +// SecurityRequirement schemaRequirement = new SecurityRequirement().addList("basicAuth"); + + // Token + SecurityScheme securityScheme = new SecurityScheme() + .type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT") + .in(SecurityScheme.In.HEADER).name(XitConstants.JwtToken.HEADER_NAME.getValue()); + SecurityRequirement schemaRequirement = new SecurityRequirement().addList("bearerAuth"); + + return new OpenAPI() + //.components(new Components().addSecuritySchemes("basicAuth", securityScheme)) + .components(new Components().addSecuritySchemes("bearerAuth", securityScheme)) + .addSecurityItem(schemaRequirement) + .security(Collections.singletonList(schemaRequirement)) + .info(info) +// .servers(servers) + + ; + } + + @Bean + public GroupedOpenApi authorizeApi() { + return GroupedOpenApi.builder() + .group("Authorize-API") + .pathsToMatch( + "/api/v1/auth/**", + "/api/v1/users/**" + ) + .build(); + } + + @Bean + public GroupedOpenApi commonApi() { + return GroupedOpenApi.builder() + .group("common-API") + .pathsToMatch( + "/api/biz/cmm/**" + ) + .pathsToExclude( + "/api/biz/cmm/flux/**" + ) + .build(); + } + + @Bean + public GroupedOpenApi sampleApi() { + return GroupedOpenApi.builder() + .group("Sample-API") + .pathsToMatch("/api/sample/**") + .build(); + } + +// @Bean +// public OpenApiCustomiser customerGlobalHeaderOpenApiCustomiser() { +// return openApi -> { +// openApi.getPaths().values().forEach(pathItem -> pathItem.readOperations().forEach(operation -> { +// ApiResponses apiResponses = operation.getResponses(); +// ApiResponse apiResponse = new ApiResponse().description("Custom Error") +// .content(new Content() +// .addMediaType(APPLICATION_JSON_VALUE, new MediaType())); +// apiResponses.addApiResponse("400", apiResponse); +// })); +// }; +// } + +// @Bean + public OpenApiCustomiser customOpenApiCustomiser() { + return openApi -> { + openApi.getPaths().values().forEach(pathItem -> pathItem.readOperations().forEach(operation -> { + + Schema sharedErrorSchema = ModelConverters.getInstance() + .read(Error.class) + .getOrDefault("Error", new Schema()); + + MediaType sharedMediaType = new MediaType().schema(sharedErrorSchema); + Content sharedContent = new Content() + .addMediaType(APPLICATION_JSON_VALUE, sharedMediaType); + + ApiResponses apiResponses = new ApiResponses(); //operation.getResponses(); + + ApiResponse response = new ApiResponse() + .description("Unhandled server error") + .content(sharedContent); + apiResponses.addApiResponse("500", response); + +// Schema errorResponseSchema = new Schema(); +// errorResponseSchema.setName("Error"); +// errorResponseSchema.set$ref("#/components/schemas/Error"); +// +// apiResponses.addApiResponse("400", createApiResponse("BadRequest", errorResponseSchema)); +// apiResponses.addApiResponse("403", createApiResponse("Forbidden", null)); +// apiResponses.addApiResponse("404", createApiResponse("Not Found", null)); +// apiResponses.addApiResponse("500", createApiResponse("Server Error", null)); + })); + }; + } + + private ApiResponse createApiResponse(String message, Schema schema) { + MediaType mediaType = new MediaType(); + mediaType.schema(schema); + return new ApiResponse().description(message) + .content(new Content().addMediaType(org.springframework.http.MediaType.APPLICATION_JSON_VALUE, mediaType)); + } +} + +//@Getter +//@Setter +//public static class Page { +// @ApiModelProperty(value = "페이지 번호(0..N)") +// private Integer page; +// +// @ApiModelProperty(value = "페이지 크기", allowableValues="range[0, 100]") +// private Integer size; +// +// @ApiModelProperty(value = "정렬(사용법: 컬럼명,ASC|DESC)") +// private List sort; +//} + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/xit/core/constant/ErrorCode.java b/src/main/java/com/xit/core/constant/ErrorCode.java new file mode 100644 index 0000000..618caf0 --- /dev/null +++ b/src/main/java/com/xit/core/constant/ErrorCode.java @@ -0,0 +1,92 @@ +package com.xit.core.constant; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +@JsonFormat(shape = JsonFormat.Shape.OBJECT) +public enum ErrorCode { + + /* +200 : OK, 요청 정상 처리 +201 : Created, 생성 요청 성공 +202 : Accepted, 비동기 요청 성공 +204 : No Content, 요청 정상 처리, 응답 데이터 없음. + +실패 +400 : Bad Request, 요청이 부적절 할 때, 유효성 검증 실패, 필수 값 누락 등. +401 : Unauthorized, 인증 실패, 로그인하지 않은 사용자 또는 권한 없는 사용자 처리 +402 : Payment Required +403 : Forbidden, 인증 성공 그러나 자원에 대한 권한 없음. 삭제, 수정시 권한 없음. +404 : Not Found, 요청한 URI에 대한 리소스 없을 때 사용. +405 : Method Not Allowed, 사용 불가능한 Method를 이용한 경우. +406 : Not Acceptable, 요청된 리소스의 미디어 타입을 제공하지 못할 때 사용. +408 : Request Timeout +409 : Conflict, 리소스 상태에 위반되는 행위 시 사용. +413 : Payload Too Large +423 : Locked +428 : Precondition Required +429 : Too Many Requests + +500 : 서버 에러 + + */ + + + BAD_REQUEST(HttpStatus.BAD_REQUEST, "요청 매개변수 오류 입니다."), + NOT_FOUND(HttpStatus.NOT_FOUND, "잘못된 요청 입니다"), + + /* 400 BAD_REQUEST : 잘못된 요청 */ + CANNOT_FOLLOW_MYSELF(HttpStatus.BAD_REQUEST, "자기 자신은 팔로우 할 수 없습니다"), + + /* 401 UNAUTHORIZED : 인증되지 않은 사용자 */ + //INVALID_AUTH_TOKEN(HttpStatus.UNAUTHORIZED, "권한 정보가 없는 토큰입니다"), + UN_AUTHORIZED_USER(HttpStatus.UNAUTHORIZED, "계정 정보가 존재하지 않습니다"), + AUTH_HEADER_NOT_EXISTS(HttpStatus.UNAUTHORIZED, "헤더에 인증 정보를 찾을 수 없습니다"), + LOGOUT_USER(HttpStatus.UNAUTHORIZED, "로그아웃된 사용자 입니다"), + NOT_EXISTS_SECURITY_AUTH(HttpStatus.UNAUTHORIZED, "Security Context 에 인증 정보가 없습니다"), + + NOT_EXISTS_TOKEN(HttpStatus.UNAUTHORIZED, "인증된 토큰이 없습니다"), + NOT_EXISTS_SAVED_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "저장된 인증 토큰이 없습니다"), + INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "유효한 토큰이 아닙니다"), + INVALID_ROLE_TOKEN(HttpStatus.UNAUTHORIZED, "사용 권한이 없는 토큰 입니다"), + EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "토큰 유효기간이 경과 되었습니다"), + INVALID_SIGN_TOKEN(HttpStatus.UNAUTHORIZED, "잘못된 서명의 토큰 입니다"), + INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "리프레시 토큰이 유효하지 않습니다"), + MISMATCH_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "리프레시 토큰의 유저 정보가 일치하지 않습니다"), + MISMATCH_REFRESH_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED, "발급된 refresh token의 정보가 일치하지 않습니다"), + NOT_EXPIRED_TOKEN_YET(HttpStatus.UNAUTHORIZED, "토큰 유효기간이 경과되지 않았습니다"), + + /* 404 NOT_FOUND : Resource 를 찾을 수 없음 */ + USER_NOT_FOUND(HttpStatus.NOT_FOUND, "사용자 정보를 찾을 수 없습니다"), + REFRESH_TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "로그아웃 된 사용자입니다"), + + /* 409 CONFLICT : Resource 의 현재 상태와 충돌. 보통 중복된 데이터 존재 */ + DUPLICATE_RESOURCE(HttpStatus.CONFLICT, "데이터가 이미 존재합니다"), + MEMBER_EXISTS(HttpStatus.CONFLICT, "가입되어 있는 회원 입니다"), + + FORBIDDEN(HttpStatus.FORBIDDEN, "FORBIDDEN"), + INVALID_CODE(HttpStatus.BAD_REQUEST, "유효하지 않은 HttpStatus 상태 코드 입니다"), + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "INTERNAL_SERVER_ERROR"), + ; + + + //USER_NOT_FOUND(null, "사용자(%s) 정보를 찾을 수 없습니다"); + + private HttpStatus httpStatus; + private String message; + //Integer code; + + ErrorCode(HttpStatus httpStatus, String message){ + this.httpStatus = httpStatus; + this.message = message; + } + + ErrorCode(String message){ + this.httpStatus = HttpStatus.BAD_REQUEST; + this.message = message; + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/constant/XitConstants.java b/src/main/java/com/xit/core/constant/XitConstants.java new file mode 100644 index 0000000..4fe039f --- /dev/null +++ b/src/main/java/com/xit/core/constant/XitConstants.java @@ -0,0 +1,349 @@ +package com.xit.core.constant; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +public class XitConstants { + + public static final String JPA_VERSION = "2.4"; + public static final String UNIT_NAME = "jpaPersistenceUnit"; + + // RestTemplate 상수 + public static final int CONNECT_TIMEOUT = 30 * 1000; // 30초 + public static final int READ_TIMEOUT = 60 * 1000 * 3; // 3분 + public static final Charset CHARSET = StandardCharsets.UTF_8; + + /** + * CustomCommonsRequestLoggingFilter에서 로깅을 제외할 url 패턴 정의 + */ + public static final String EXCLUDE_LOGGING_URL_PATTERN = "^(/webjars/|/swagger-ui/|/swagger-resources|/v2/api-docs|/h2-console)"; // "^(/webjars/|/sample/|/web/)" + + public enum ActiveProfile { + LOCAL("local"), + DEV("dev"), + STG("stg"), + PROD("prod"); + private final String value; + public String getValue() { + return this.value; + } + private ActiveProfile(String value) { + this.value = value; + } + } + + /** + * 인증정보 저장 방식 + * + */ + public enum AuthSaveType { + SECURITY("security"), // SessionCreationPolicy.STATELESS인 경우는 SecurityContext 사용불가 + SESSION("session"), // TOKEN도 사용 가능은 하지만... + HEADER("header"); // TOKEN + private final String value; + public String getValue() { + return this.value; + } + private AuthSaveType(String value) { + this.value = value; + } + } + + public enum JwtToken { + // 토큰헤더명, + HEADER_NAME("Authorization"), + GRANT_TYPE("Bearer"), + ACCESS_TOKEN_NAME("accessToken"), + REFRESH_TOKEN_NAME("refreshToken"), + AUTHORITIES_KEY("role") + ; + ; + private final String value; + public String getValue() { + return this.value; + } + private JwtToken(String value) { + this.value = value; + } + } + + /** + * JWT Token 통신 방식 지정 + * COOKIE : accessToken - header, refreshToken - cookie + * HEADER : accessToken - header, refreshToken - DTO + * DTO : accessToken - header, refreshToken - DTO + */ + public enum JwtTokenParamType { + COOKIE, + HEADER, + DTO + } + + public enum AddressType { + STREET("STREET"), // 도로명 + LOT("LOT"); // 지번 + private final String value; + public String getValue() { + return this.value; + } + private AddressType(String value) { + this.value = value; + } + + } + + + + + + + + + + + + + + + + + + + + + + + /** + * spring.profiles.active 에 따라 log / cache / db 결정 + * local / dev / qa / product + * local - jdbc, 그외는 jndi + */ + public static final String KEY_OP_MODE = "spring.profiles.active"; + public static final String OP_MODE_DEFAULT = "local"; + + public static final String DEFAULT_VIEW = ViewName.JSON_VIEW.getValue(); + public static final int BIZ_EXCEPTION_CODE = 999; + public static final String BIZ_EXCEPTION_CODE_STR = "999"; + public static final String SESSION_TIMEOUT_MSG_CODE = "cmm.info.session.timeout"; + public static final String PROP_LOCALE = "Config.locale"; + public static final String DEFAULT_ENCODING = "UTF-8"; + + /** + * login ID 저장 여부 파라메터 : 이 값에 저장여부가 넘어온다 + * html check field name을 이값으로 할것 + */ + public static final String COOKIE_SAVE_CHECK_FIELD = "isIdSave"; + /** + * login ID 저장 여부 체크시 true 값이 넘어 온다 : html 에서 넘어오는 값이라서 문자열로 + */ + public static final String COOKIE_SAVE_NAME_TRUE = "true"; + /** + * Cookie에 저장될 key 값 + */ + public static final String COOKIE_SAVE_USER_NAME = "SAVE_USER_NAME"; + /** + * 로그인 실패시 실패한 ID를 임시로 저장하기 위한 attribute key 값 + */ + public static final String TMP_USER_ID = "tmpUserId"; + + public static final String USER_ID = "j_username"; + + /** + * return list root attribute name + */ + public static final String RSLT_ATTR_NAME = "_item_"; + + public enum Cache { + Monitoring("Config.cache.monitoring"), + Statistics("Config.cache.statistics"), + Logging("Config.cache.logging"), + UpdateCheck("Config.cache.updateCheck"), + DynamicConfig("Config.cache.dynamicConfig"), + EvictionPolicy("Config.cache.evictionPolicy"), + CodeCacheName("Config.cache.codeCache.name"), + CodeCacheMaxEntries("Config.cache.codeCache.maxEntries"), + MenuCacheName("Config.cache.menuCache.name"), + MenuCacheMaxEntries("Config.cache.menuCache.maxEntries") + ; + + private String value; + private Cache(String value) { + this.value = value; + } + public String getValue() { + return this.value; + } + + } + + public enum LoginException{ + ACCOUNT_EXISTS("Account exists"), + PWD_MISMATCH("PasswordMismatchException"), + ACCOUNT_EXPIRED("UserAccountExpiredException"), + ACCOUNT_LOCK("UserAccountLockException"), + USER_NOT_FOUND("UserNotFoundException"), + LOGIN_DUPLICATED("principal exceeded") // Maximum sessions of 1 for this principal exceeded + ; + private String value; + private LoginException(String value) { + this.value = value; + } + public String getValue() { + return this.value; + } + } + + public enum ErrorMsgLayout{ + ROOT_ATTR("error"), + CODE_ATTR("code"), + MSG_ATTR("messages") + ; + private String value; + private ErrorMsgLayout(String value) { + this.value = value; + } + public String getValue() { + return this.value; + } + } + + public enum ViewName{ + JSON_VIEW("mappingJackson2JsonView") + ; + private String value; + private ViewName(String value) { + this.value = value; + } + public String getValue() { + return this.value; + } + } + + public enum ComboList{ + CODE_LIST("code_list"), + CODE("code"), + VALUE("value"); + private String value; + private ComboList(String value) { + this.value = value; + } + public String getValue() { + return this.value; + } + } + + public enum JspViewLayout{ + PREFIX("/WEB-INF/views/"), + SUFFIX(".jsp"); + private String value; + private JspViewLayout(String value) { + this.value = value; + } + public String getValue() { + return this.value; + } + } + + public enum Const { + VALID_ERROR_ROOT_ELEMENT("validError"), + MESSAGE_CODE_ATTR("code"), + MESSAGE_ATTR("messages"), + ERROR_ROOT_ELEMENT("error"), + VALID_ERROR_FIELD("field"), + CURRENT_LOCALE("currentLocale"), + DEFAULT_SUCCESS_MSG_CODE("cmm.info.success"), + DEFAULT_FAIL_MSG_CODE("cmm.info.success"), + VALID_ERROR_MSG("cmm.error.valid") + ; + private String value; + private Const(String value) { + this.value = value; + } + public String getValue() { + return this.value; + } + } + + public enum Session { + PAGE_INFO("_pageInfo_"), +// DATASET_NAME("gds_pageInfo"), + /** 세션에 저장되는 인증유저 객체 attribute name */ + AuthentSessionObject("AUTH_SS_OBJECT"), + /** 세션에 저장되는 인증유저 권한종류 attribute name */ + AuthentSessionAuthority("AUTH_SS_AUTHORITY"), + ; + + private String value; + private Session( String value ) { + this.value = value; + } + + public String getValue() { + return this.value; + } + } + + public enum LoginType { + SSO, + Login, + Unknown + } + + public enum CharsetType { + Default("default"), + Utf8( "UTF-8" ), + EucKr( "EUC-KR" ), + Iso8859("8859_1"); + + private String value; + private CharsetType( String value ) { + this.value = value; + } + public String getValue() { + return this.value; + } + + public Charset getCharset() { + if ( this == Default ) + return Charset.defaultCharset(); + + return Charset.forName( this.value ); + } + + public byte[] getBytes( String str ) { + return str.getBytes( this.getCharset() ); + } + + public String getString( byte[] bytes ) { + return new String( bytes, this.getCharset() ); + } + } + + /** + * String pad처리 시 추가문자 지정타입 + * @타입 : PadType + */ + public enum PadType { + /** 문자열 왼쪽 */ + Left, + /** 문자열 오른쪽 */ + Right + } + + public enum PageNavigator { + DIV_ID("pager"), + CLASS_NAME("pager"), + FIRST_SYMBOLIC(" ◀ "), + PREV_SYMBOLIC(" ◁ "), + NEXT_SYMBOLIC(" ▷ "), + LAST_SYMBOLIC(" ▶ "); + + private String value; + private PageNavigator(String value) { + this.value = value; + } + public String getValue() { + return this.value; + } + } +} diff --git a/src/main/java/com/xit/core/exception/AuthorizationException.java b/src/main/java/com/xit/core/exception/AuthorizationException.java new file mode 100644 index 0000000..9292996 --- /dev/null +++ b/src/main/java/com/xit/core/exception/AuthorizationException.java @@ -0,0 +1,35 @@ +package com.xit.core.exception; + +import com.xit.core.constant.ErrorCode; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Getter +@ResponseStatus(code = HttpStatus.UNAUTHORIZED, reason = "인증 에러") +public class AuthorizationException extends CustomBaseException{ + + public AuthorizationException() { + super(); + } + + public AuthorizationException(String msg) { + super(msg); + } + + public AuthorizationException(ErrorCode errorCode) { + super(errorCode); + } + + public AuthorizationException(Throwable e) { + super(e); + } + + public AuthorizationException(String msg, Throwable cause) { + super(msg, cause); + } + + public HttpStatus getHttpStatus(){ + return HttpStatus.CONFLICT; + } +} diff --git a/src/main/java/com/xit/core/exception/CustomBaseException.java b/src/main/java/com/xit/core/exception/CustomBaseException.java new file mode 100644 index 0000000..e07d5b5 --- /dev/null +++ b/src/main/java/com/xit/core/exception/CustomBaseException.java @@ -0,0 +1,61 @@ +package com.xit.core.exception; + +import com.xit.core.constant.ErrorCode; +import com.xit.core.util.SpringUtils; +import lombok.Getter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +import java.util.ResourceBundle; + +@Getter +@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "Invalid parameter") +public class CustomBaseException extends RuntimeException { + ResourceBundle message = ResourceBundle.getBundle("message.error"); + + private ErrorCode errorCode; + private String code; + + public CustomBaseException() { + super(); + } + + public CustomBaseException(String msg) { + super(msg); + } + + public CustomBaseException(ErrorCode errorCode) { + this.errorCode = errorCode; + } + + public CustomBaseException(String code, String msg) { + super(msg); + this.code = code; + } + + public CustomBaseException(Throwable e) { + super(e); + } + + public CustomBaseException(String msg, Throwable cause) { + super(msg, cause); + } + + public HttpStatus getHttpStatus(){ + return HttpStatus.BAD_REQUEST; + }; + + @Override + public String getMessage(){ + return super.getMessage(); + } + + @Override + public String getLocalizedMessage(){ + return message.getString(super.getMessage()); + //return messageSourceAccessor.get + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/exception/InvalidRequestException.java b/src/main/java/com/xit/core/exception/InvalidRequestException.java new file mode 100644 index 0000000..13bf1eb --- /dev/null +++ b/src/main/java/com/xit/core/exception/InvalidRequestException.java @@ -0,0 +1,35 @@ +package com.xit.core.exception; + +import com.xit.core.constant.ErrorCode; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Getter +@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "Invalid Request parameter") +public class InvalidRequestException extends CustomBaseException{ + + public InvalidRequestException() { + super(); + } + + public InvalidRequestException(String msg) { + super(msg); + } + + public InvalidRequestException(ErrorCode errorCode) { + super(errorCode); + } + + public InvalidRequestException(Throwable e) { + super(e); + } + + public InvalidRequestException(String msg, Throwable cause) { + super(msg, cause); + } + + public HttpStatus getHttpStatus(){ + return HttpStatus.BAD_REQUEST; + } +} diff --git a/src/main/java/com/xit/core/exception/JwtTokenExpiredException.java b/src/main/java/com/xit/core/exception/JwtTokenExpiredException.java new file mode 100644 index 0000000..4ac3559 --- /dev/null +++ b/src/main/java/com/xit/core/exception/JwtTokenExpiredException.java @@ -0,0 +1,35 @@ +package com.xit.core.exception; + +import com.xit.core.constant.ErrorCode; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Getter +@ResponseStatus(code = HttpStatus.UNAUTHORIZED, reason = "토큰 인증 에러") +public class JwtTokenExpiredException extends CustomBaseException{ + + public JwtTokenExpiredException() { + super(); + } + + public JwtTokenExpiredException(String msg) { + super(msg); + } + + public JwtTokenExpiredException(ErrorCode errorCode) { + super(errorCode); + } + + public JwtTokenExpiredException(Throwable e) { + super(e); + } + + public JwtTokenExpiredException(String msg, Throwable cause) { + super(msg, cause); + } + + public HttpStatus getHttpStatus(){ + return HttpStatus.CONFLICT; + } +} diff --git a/src/main/java/com/xit/core/exception/TokenAuthException.java b/src/main/java/com/xit/core/exception/TokenAuthException.java new file mode 100644 index 0000000..63dfef9 --- /dev/null +++ b/src/main/java/com/xit/core/exception/TokenAuthException.java @@ -0,0 +1,35 @@ +package com.xit.core.exception; + +import com.xit.core.constant.ErrorCode; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Getter +@ResponseStatus(code = HttpStatus.UNAUTHORIZED, reason = "토큰 인증 에러") +public class TokenAuthException extends CustomBaseException{ + + public TokenAuthException() { + super(); + } + + public TokenAuthException(String msg) { + super(msg); + } + + public TokenAuthException(ErrorCode errorCode) { + super(errorCode); + } + + public TokenAuthException(Throwable e) { + super(e); + } + + public TokenAuthException(String msg, Throwable cause) { + super(msg, cause); + } + + public HttpStatus getHttpStatus(){ + return HttpStatus.CONFLICT; + } +} diff --git a/src/main/java/com/xit/core/exception/UnknownException.java b/src/main/java/com/xit/core/exception/UnknownException.java new file mode 100644 index 0000000..378507e --- /dev/null +++ b/src/main/java/com/xit/core/exception/UnknownException.java @@ -0,0 +1,35 @@ +package com.xit.core.exception; + +import com.xit.core.constant.ErrorCode; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Getter +@ResponseStatus(code = HttpStatus.CONFLICT, reason = "Exists Member") +public class UnknownException extends CustomBaseException{ + + public UnknownException() { + super(); + } + + public UnknownException(String msg) { + super(msg); + } + + public UnknownException(ErrorCode errorCode) { + super(errorCode); + } + + public UnknownException(Throwable e) { + super(e); + } + + public UnknownException(String msg, Throwable cause) { + super(msg, cause); + } + + public HttpStatus getHttpStatus(){ + return HttpStatus.CONFLICT; + } +} diff --git a/src/main/java/com/xit/core/exception/UserAuthException.java b/src/main/java/com/xit/core/exception/UserAuthException.java new file mode 100644 index 0000000..a50dbce --- /dev/null +++ b/src/main/java/com/xit/core/exception/UserAuthException.java @@ -0,0 +1,35 @@ +package com.xit.core.exception; + +import com.xit.core.constant.ErrorCode; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Getter +@ResponseStatus(code = HttpStatus.UNAUTHORIZED, reason = "토큰 인증 에러") +public class UserAuthException extends CustomBaseException{ + + public UserAuthException() { + super(); + } + + public UserAuthException(String msg) { + super(msg); + } + + public UserAuthException(ErrorCode errorCode) { + super(errorCode); + } + + public UserAuthException(Throwable e) { + super(e); + } + + public UserAuthException(String msg, Throwable cause) { + super(msg, cause); + } + + public HttpStatus getHttpStatus(){ + return HttpStatus.CONFLICT; + } +} diff --git a/src/main/java/com/xit/core/exception/UserExistedException.java b/src/main/java/com/xit/core/exception/UserExistedException.java new file mode 100644 index 0000000..56d1994 --- /dev/null +++ b/src/main/java/com/xit/core/exception/UserExistedException.java @@ -0,0 +1,35 @@ +package com.xit.core.exception; + +import com.xit.core.constant.ErrorCode; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Getter +@ResponseStatus(code = HttpStatus.CONFLICT, reason = "Exists Member") +public class UserExistedException extends CustomBaseException{ + + public UserExistedException() { + super(); + } + + public UserExistedException(String msg) { + super(msg); + } + + public UserExistedException(ErrorCode errorCode) { + super(errorCode); + } + + public UserExistedException(Throwable e) { + super(e); + } + + public UserExistedException(String msg, Throwable cause) { + super(msg, cause); + } + + public HttpStatus getHttpStatus(){ + return HttpStatus.CONFLICT; + } +} diff --git a/src/main/java/com/xit/core/exception/UsernameFromTokenException.java b/src/main/java/com/xit/core/exception/UsernameFromTokenException.java new file mode 100644 index 0000000..f3ef179 --- /dev/null +++ b/src/main/java/com/xit/core/exception/UsernameFromTokenException.java @@ -0,0 +1,35 @@ +package com.xit.core.exception; + +import com.xit.core.constant.ErrorCode; +import lombok.Getter; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@Getter +@ResponseStatus(code = HttpStatus.UNAUTHORIZED, reason = "토큰 인증 에러") +public class UsernameFromTokenException extends CustomBaseException{ + + public UsernameFromTokenException() { + super(); + } + + public UsernameFromTokenException(String msg) { + super(msg); + } + + public UsernameFromTokenException(ErrorCode errorCode) { + super(errorCode); + } + + public UsernameFromTokenException(Throwable e) { + super(e); + } + + public UsernameFromTokenException(String msg, Throwable cause) { + super(msg, cause); + } + + public HttpStatus getHttpStatus(){ + return HttpStatus.CONFLICT; + } +} diff --git a/src/main/java/com/xit/core/exception/controller/ApiErrorController.java b/src/main/java/com/xit/core/exception/controller/ApiErrorController.java new file mode 100644 index 0000000..26eb6ff --- /dev/null +++ b/src/main/java/com/xit/core/exception/controller/ApiErrorController.java @@ -0,0 +1,79 @@ +package com.xit.core.exception.controller; + +import com.xit.core.api.RestErrorResponse; +import com.xit.core.constant.ErrorCode; +import com.xit.core.util.Checks; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.web.servlet.error.ErrorController; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestMapping; + +import javax.servlet.RequestDispatcher; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Slf4j +//@RestController +public class ApiErrorController implements ErrorController { //extends BasicErrorController { + + + private static final String PATH = "/error"; // configure 에서 Redirect 될 path + +// public ApiErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties) { +// super(errorAttributes, serverProperties.getError()); +// } + + @RequestMapping(value=PATH, produces = MediaType.APPLICATION_XML_VALUE) + public RestErrorResponse error(HttpServletRequest request, HttpServletResponse response) { + + Object ERROR_SERVLET_NAME = request.getAttribute(RequestDispatcher.ERROR_SERVLET_NAME); + Object ERROR_REQUEST_URI = request.getAttribute(RequestDispatcher.ERROR_REQUEST_URI); + Object ERROR_EXCEPTION = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION); + Object ERROR_STATUS_CODE = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); + Object ERROR_EXCEPTION_TYPE = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE); + Object ERROR_MESSAGE = request.getAttribute(RequestDispatcher.ERROR_MESSAGE); + + if(response.getStatus()!=404) { + log.error("============================================================="); + log.error("request Method:{}" ,request.getMethod()); + log.error("request URI:{}" ,request.getRequestURI()); + log.error("response Status:{}" ,response.getStatus()); + log.error("response ContentType:{}" ,response.getContentType()); + log.error("ERROR_SERVLET_NAME:{}" ,ERROR_SERVLET_NAME); + log.error("ERROR_REQUEST_URI:{}" ,ERROR_REQUEST_URI); + log.error("ERROR_STATUS_CODE:{}" ,ERROR_STATUS_CODE); + log.error("ERROR_EXCEPTION:{}" ,ERROR_EXCEPTION); + log.error("ERROR_EXCEPTION_TYPE:{}" ,ERROR_EXCEPTION_TYPE); + log.error("ERROR_MESSAGE:{}" ,ERROR_MESSAGE); + log.error("============================================================="); + } else { + log.error("404:{} {}" ,request.getMethod() ,request.getRequestURI()); + } + + ErrorCode errorCode = null; + if (ERROR_STATUS_CODE != null) { + + int statusCode = ((HttpStatus)ERROR_STATUS_CODE).value(); + + switch(statusCode){ + case 404 : //HttpStatus.NOT_FOUND.value() : + errorCode = ErrorCode.NOT_FOUND; + break; + case 403 : //HttpStatus.FORBIDDEN.value() : + errorCode = ErrorCode.FORBIDDEN; + break; + case 500 : //HttpStatus.INTERNAL_SERVER_ERROR.value() : + errorCode = ErrorCode.INTERNAL_SERVER_ERROR; + break; + default : + errorCode = ErrorCode.INVALID_CODE; + break; + } + } + + + return RestErrorResponse.getErrorResponse(Checks.isEmpty(errorCode)? ErrorCode.INVALID_CODE: errorCode); + } + +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/exception/controller/WebErrorController.java b/src/main/java/com/xit/core/exception/controller/WebErrorController.java new file mode 100644 index 0000000..f0da940 --- /dev/null +++ b/src/main/java/com/xit/core/exception/controller/WebErrorController.java @@ -0,0 +1,42 @@ +package com.xit.core.exception.controller; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.web.servlet.error.ErrorController; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.RequestMapping; + +import javax.servlet.RequestDispatcher; +import javax.servlet.http.HttpServletRequest; + +@Slf4j +//@Controller +public class WebErrorController implements ErrorController { + + private String VIEW_PATH = "/thymeleaf/error/"; + + @RequestMapping(value = "/error") + public String handleError(HttpServletRequest request) { + String rtnView = VIEW_PATH + "error"; + Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); + + if (status != null) { + int statusCode = ((HttpStatus)status).value(); + + + switch(statusCode){ + case 404 : //HttpStatus.NOT_FOUND.value() : + rtnView = "404"; + break; + case 403 : //HttpStatus.FORBIDDEN.value() : + rtnView = "403"; + break; + case 500 : //HttpStatus.INTERNAL_SERVER_ERROR.value() : + rtnView = "500"; + break; + default : + break; + } + } + return rtnView; + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/exception/filter/ExceptionHandlerFilter.java b/src/main/java/com/xit/core/exception/filter/ExceptionHandlerFilter.java new file mode 100644 index 0000000..e5c04fb --- /dev/null +++ b/src/main/java/com/xit/core/exception/filter/ExceptionHandlerFilter.java @@ -0,0 +1,73 @@ +package com.xit.core.exception.filter; + +import com.xit.core.api.RestErrorResponse; +import com.xit.core.constant.ErrorCode; + +import com.xit.core.exception.UsernameFromTokenException; +import com.xit.core.util.Checks; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.env.Environment; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.*; + +/** + * CustomExceptionHandler 와 함께 에러 처리 + * Filter에서 발생한 오류 처리 + */ +@Slf4j +@Component +public class ExceptionHandlerFilter extends OncePerRequestFilter { + + private final Environment env; + + public ExceptionHandlerFilter(Environment env) { + this.env = env; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + try{ + filterChain.doFilter(request,response); + } catch (UsernameFromTokenException ex){ + log.error("exception exception handler filter"); + setErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR,response,ex); + }catch (RuntimeException ex){ + log.error("runtime exception exception handler filter"); + setErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR,response,ex); + } + } + + private void setErrorResponse(HttpStatus status, HttpServletResponse response,Throwable ex){ + response.setStatus(status.value()); + response.setContentType("application/json"); + RestErrorResponse errorResponse = RestErrorResponse.getErrorResponse(ErrorCode.INTERNAL_SERVER_ERROR); + + // 운영 환경인 경우는 상세 정보 미출력 + if(Arrays.asList(env.getActiveProfiles()).contains("prod")) + errorResponse.setMessage(Checks.isNotEmpty(ex.getCause())? ex.getLocalizedMessage() : ex.getCause().getLocalizedMessage()); + else + errorResponse.setMessage(Checks.isNotEmpty(ex.getCause())? ex.getLocalizedMessage() : ex.getCause().toString()); + try{ + String json = errorResponse.convertToJson(); + log.error(json); + response.getWriter().write(json); + }catch (IOException e){ + e.printStackTrace(); + } + + } + +// @Override +// protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { +// +// } +} + diff --git a/src/main/java/com/xit/core/exception/handling/CustomErrorAttributes.java b/src/main/java/com/xit/core/exception/handling/CustomErrorAttributes.java new file mode 100644 index 0000000..a243e72 --- /dev/null +++ b/src/main/java/com/xit/core/exception/handling/CustomErrorAttributes.java @@ -0,0 +1,44 @@ +package com.xit.core.exception.handling; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.web.error.ErrorAttributeOptions; +import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.WebRequest; + +import java.util.Map; + +/** + * Catch 되지 않은 에러인 경우 처리 되는 class + * 개발자가 처리한 예외처리중 오류 및 framework에서 처리되지 않은 오류 발생시 + * 반드시 처리 필요 + * + *
+ * timestamp : String
+ * status : int
+ * error : String
+ * exception : String - Exception class
+ * trace
+ * message
+ * path : uri - call uri
+ * 
+ */
+@Slf4j
+@Component
+public class CustomErrorAttributes extends DefaultErrorAttributes {
+
+    @Override
+    public Map getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
+        Map result = super.getErrorAttributes(webRequest, options);
+        //result.remove("timestamp");
+        //result.put("trace", "AAAAAAAAAAAAAAAAAAAAAAA");
+        log.error("=================================================================================");
+        log.error("========================== 처리되지 않은 에러 발생 ==================================");
+        log.error("{}", result);
+        log.error("========================== 반드시 처리해 주세요 =====================================");
+        log.error("==================================================================================");
+        return result;
+
+        //return super.getErrorAttributes(webRequest, options);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/xit/core/exception/handling/CustomRestExceptionHandler.java b/src/main/java/com/xit/core/exception/handling/CustomRestExceptionHandler.java
new file mode 100644
index 0000000..c7499cf
--- /dev/null
+++ b/src/main/java/com/xit/core/exception/handling/CustomRestExceptionHandler.java
@@ -0,0 +1,200 @@
+package com.xit.core.exception.handling;
+
+import com.xit.core.api.RestErrorResponse;
+import com.xit.core.constant.ErrorCode;
+import com.xit.core.exception.CustomBaseException;
+import com.xit.core.util.Checks;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+import javax.validation.ConstraintViolationException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+
+/**
+ * 
+ * ExceptionHandlerFilter(Filter에서 발생한 에러 처리)와 함께 에러 처리
+ * ErrorCode 에서 해당 Exception 오류를 정의하여 사용
+ *
+ * spring boot의 기본 properties
+ * server.error:
+ *   include-exception: false # 응답에 exception의 내용을 포함할지 여부
+ *   include-stacktrace: never # 오류 응답에 stacktrace 내용을 포함할 지 여부
+ *   path: '/error' # 오류 응답을 처리할 Handler의 경로
+ *   whitelabel.enabled: true # 서버 오류 발생시 브라우저에 보여줄 기본 페이지 생성 여부
+ *   
+ */ +@Slf4j +@RestControllerAdvice +public class CustomRestExceptionHandler extends ResponseEntityExceptionHandler { + + /** + * CustomBaseException + * + * @param cbe CustomBaseException + * @return ErrorResponse + */ + @ExceptionHandler(value = {CustomBaseException.class}) + protected ResponseEntity handleCustomBaseException(CustomBaseException cbe) { + log.error("==================== handleCustomBaseException throw CustomBaseException : {} ====================", cbe.getClass().getCanonicalName()); + return RestErrorResponse.of(cbe); + } + + /** + * MethodArgumentNotValidException 에러 메세지 처리 + * Valid 체크 에러 메세지 처리를 위한 ResponseEntityExceptionHandler#handleMethodArgumentNotValid override + * + * Customize the response for MethodArgumentNotValidException. + *

This method delegates to {@link #handleExceptionInternal}. + * @param ex the exception + * @param headers the headers to be written to the response + * @param status the selected response status + * @param request the current request + * @return a {@code ResponseEntity} instance + */ + @SuppressWarnings("NullableProblems") + @Override + protected ResponseEntity handleMethodArgumentNotValid( + MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { + log.error("==================== Override handleMethodArgumentNotValid throw MethodArgumentNotValidException ===================="); + + Map validErrorMap = new HashMap<>(); + ex.getBindingResult().getFieldErrors() + .forEach(e -> validErrorMap.put(e.getField(), e.getDefaultMessage())); + + log.error("##############################################################################################"); + log.error("{}", validErrorMap); + log.error("##############################################################################################"); + + Optional firstMessage = validErrorMap + .values() + .stream() + .findFirst(); + + RestErrorResponse errorResponse = RestErrorResponse.builder() + .status(HttpStatus.BAD_REQUEST.value()) + .error(HttpStatus.BAD_REQUEST.name()) + .code(HttpStatus.BAD_REQUEST.name()) + .message(firstMessage.orElse("에러 메세지가 정의 되지 않았습니다")) + .build(); + + log.error("##############################################################################################"); + log.error("{}", errorResponse); + log.error("##############################################################################################"); + + + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(errorResponse); + } + + /** + * Customize the response for HttpMessageNotReadableException. + *

This method delegates to {@link #handleExceptionInternal}. + * @param ex the exception + * @param headers the headers to be written to the response + * @param status the selected response status + * @param request the current request + * @return a {@code ResponseEntity} instance + */ + @SuppressWarnings("NullableProblems") + @Override + protected ResponseEntity handleHttpMessageNotReadable( + HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { + log.error("==================== Override handleHttpMessageNotReadable throw HttpMessageNotReadableException ===================="); + + RestErrorResponse errorResponse = RestErrorResponse.builder() + .status(HttpStatus.BAD_REQUEST.value()) + .error(HttpStatus.BAD_REQUEST.name()) + .code(HttpStatus.BAD_REQUEST.name()) + .message(ex.getLocalizedMessage()) + .build(); + + log.error("##############################################################################################"); + log.error("{}", errorResponse); + log.error("##############################################################################################"); + + + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(errorResponse); + + } + + /** + * NoSuchElementException + * + * @return ErrorResponse + */ + @ExceptionHandler(value = {NoSuchElementException.class}) + @ResponseStatus(value = HttpStatus.CONFLICT) + protected ResponseEntity handleNoSuchElementException() { + log.error("==================== handleDataException throw ConstraintViolationException, DataIntegrityViolationException ===================="); + return RestErrorResponse.of(ErrorCode.DUPLICATE_RESOURCE); + } + + /** + * NoSuchElementException + * + * @return ErrorResponse + */ + @ExceptionHandler(value = {IllegalArgumentException.class}) + @ResponseStatus(value = HttpStatus.BAD_REQUEST) + protected ResponseEntity handleIllegalArgumentException(IllegalArgumentException iae) { + log.error("==================== handleIllegalArgumentException throw llegalArgumentException ===================="); + return RestErrorResponse.of(HttpStatus.BAD_REQUEST.toString(), iae.getLocalizedMessage()); + } + + /** + * Data 중복 + * + * @return ErrorResponse + */ + @ExceptionHandler(value = {ConstraintViolationException.class, DataIntegrityViolationException.class}) + @ResponseStatus(value = HttpStatus.CONFLICT) + protected ResponseEntity handleDataException() { + log.error("==================== handleDataException throw ConstraintViolationException, DataIntegrityViolationException ===================="); + return RestErrorResponse.of(ErrorCode.DUPLICATE_RESOURCE); + } + + /** + * RuntimeException + * + * @param e RuntimeException + * @return ErrorResponse + */ + @ExceptionHandler(value = {RuntimeException.class}) + @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) + protected ResponseEntity handleRuntimeException(RuntimeException e) { + String message = Checks.isNotNull(e) ? e.getLocalizedMessage() : StringUtils.EMPTY; + log.error("handleException RuntimeException : {}", Checks.isEmpty(message) ? StringUtils.EMPTY : e.getClass().getCanonicalName()); + return RestErrorResponse.of(HttpStatus.INTERNAL_SERVER_ERROR.toString(), message); + } + + /** + * Exception + * + * @param e Exception + * @return ErrorResponse + */ + @ExceptionHandler(value = {Exception.class}) + @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) + protected ResponseEntity handleException(Exception e) { + String message = Checks.isNotNull(e) ? e.getLocalizedMessage() : StringUtils.EMPTY; + log.error("handleException throw Exception : {}", Checks.isEmpty(message) ? StringUtils.EMPTY : e.getClass().getCanonicalName()); + return RestErrorResponse.of(HttpStatus.INTERNAL_SERVER_ERROR.toString(), message); + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/init/JpaRunner.java b/src/main/java/com/xit/core/init/JpaRunner.java new file mode 100644 index 0000000..5f1d4d2 --- /dev/null +++ b/src/main/java/com/xit/core/init/JpaRunner.java @@ -0,0 +1,123 @@ +package com.xit.core.init; + +import com.xit.biz.cmm.entity.CmmRole; +import com.xit.biz.cmm.entity.CmmUser; +import org.hibernate.Session; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.transaction.annotation.Transactional; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.ArrayList; +import java.util.List; + +//@Component +@Transactional +public class JpaRunner implements ApplicationRunner { + + @PersistenceContext + EntityManager entityManager; + + private final PasswordEncoder encoder; + + public JpaRunner(PasswordEncoder encoder) { + this.encoder = encoder; + } + + @Override + public void run(ApplicationArguments args) throws Exception { + List users = new ArrayList<>(); + + for(int idx = 0; idx<10; idx++){ + users.add( + CmmUser.builder() + .userId("minuk926-"+(idx+1)) + .userName("홍길동-"+(idx+1)) + .email("a"+(idx+1)+"@b.com") + .password(encoder.encode("minuk926")) + .build() + ); + } + Session session = entityManager.unwrap(Session.class); + + users.forEach(session::save); + + + List roles = new ArrayList<>(); + + for(int idx = 0; idx<10; idx++){ + roles.add( + CmmRole.builder() + .roleName("ROLE-0"+(idx+1)) + .roleRemark("권한-0"+(idx+1)) + .build() + ); + } + roles.forEach(session::save); + +// List userRoles = new ArrayList<>(); +// for(int idx = 0; idx<10; idx++){ +// userRoles.add( +// CmmRoleUer.builder() +// .cmmUserId(idx+1L) +// .roleId(idx+1L) +// .build() +// ); +// } +// userRoles.add( +// CmmRoleUer.builder() +// .cmmUserId(1L) +// .roleId(2L) +// .build() +// ); +// userRoles.add( +// CmmRoleUer.builder() +// .cmmUserId(1L) +// .roleId(3L) +// .build() +// ); +// userRoles.add( +// CmmRoleUer.builder() +// .cmmUserId(2L) +// .roleId(1L) +// .build() +// ); +// userRoles.add( +// CmmRoleUer.builder() +// .cmmUserId(3L) +// .roleId(1L) +// .build() +// ); +// +// userRoles.forEach(session::save); +// +// List addresses = new ArrayList<>(); +// addresses.add( +// CmmAddress.builder() +// .cmmUser(users.get(0)) +// .addressSeq("001") +// .addressType(XitCoreConstants.AddressType.STREET) +// .zipCode("12345") +// .address1("기본주소1") +// .address2("상세주소1") +// .address3("부가주소1") +// .defaultYn("Y") +// .build() +// ); +// addresses.add( +// CmmAddress.builder() +// .cmmUser(users.get(0)) +// .addressSeq("002") +// .addressType(XitCoreConstants.AddressType.STREET) +// .zipCode("12346") +// .address1("기본주소2") +// .address2("상세주소3") +// .address3("부가주소4") +// .defaultYn("N") +// .build() +// ); + //addresses.forEach(session::save); + } +} diff --git a/src/main/java/com/xit/core/init/PropertiesLoaderHelper.java b/src/main/java/com/xit/core/init/PropertiesLoaderHelper.java new file mode 100644 index 0000000..005663a --- /dev/null +++ b/src/main/java/com/xit/core/init/PropertiesLoaderHelper.java @@ -0,0 +1,91 @@ +package com.xit.core.init; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.lang.NonNull; + +import java.io.IOException; +import java.net.URL; +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Springboot 시작시 multi-properties overload 지원 + * application-{profiles}.yml + */ +@Slf4j +public class PropertiesLoaderHelper { + + // Profiles에서 제외할 설정 파일명 + private static final Set EXCLUDE_PROPERTY_SOURCES = new HashSet() {}; + + // 최상위 Properties 명 + private static final Set TOP_PRIORITY_PROPERTY_SOURCES = new HashSet() { + { + add("application.properties"); + } + }; + + /** + * Springboot 시작시 multi-properties overload 지원 + * @param application SpringApplicationBuilder + * @throws IOException + */ + public static void onLoad(SpringApplicationBuilder application)throws IOException { + List propertyNames = findPropertySourceNames(); + log.info("Properties source load :: {}", propertyNames); + + StringBuilder buildProperties = new StringBuilder(); + for (String propertyName : propertyNames) { + buildProperties.append("classpath:/").append(propertyName).append(","); + } + buildProperties.setLength(buildProperties.length() - 1); + application.properties("spring.config.location:" + buildProperties); + } + + private static List findPropertySourceNames() throws IOException { + ResourcePatternResolver resourceResolver = new SpecificResourcePatternResolver(); + Resource[] properties = resourceResolver.getResources("classpath:/*.yml"); + List names = new CopyOnWriteArrayList<>(); + + // 현재 활성 profile만 load + String profile = System.getProperty("spring.profiles.active", "undefind"); + for (Resource property : properties) { + // 설정에서 제외할 properties + if(!EXCLUDE_PROPERTY_SOURCES.contains(property.getFilename())) { + // 필수 properties와 활성 profile properties spring.config.location에 등록 + if (TOP_PRIORITY_PROPERTY_SOURCES.contains(property.getFilename()) + || Objects.requireNonNull(property.getFilename()).contains("-" + profile)) { + names.add(property.getFilename()); + } + } + } + + // properties overload + for (String name : names) { + if (TOP_PRIORITY_PROPERTY_SOURCES.contains(name)) { + names.remove(name); + names.add(0, name); + } + } + return names; + } + + static class SpecificResourcePatternResolver extends PathMatchingResourcePatternResolver { + @Override + protected @NonNull + Set doFindAllClassPathResources(@NonNull String path) throws IOException { + Set result = new LinkedHashSet<>(16); + ClassLoader cl = getClassLoader(); + Enumeration resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path)); + while (resourceUrls.hasMoreElements()) { + URL url = resourceUrls.nextElement(); + result.add(convertClassLoaderURL(url)); + } + return result; + } + } +} diff --git a/src/main/java/com/xit/core/init/XitFrameworkApplicationCommandLineRunner.java b/src/main/java/com/xit/core/init/XitFrameworkApplicationCommandLineRunner.java new file mode 100644 index 0000000..e1829bf --- /dev/null +++ b/src/main/java/com/xit/core/init/XitFrameworkApplicationCommandLineRunner.java @@ -0,0 +1,17 @@ +package com.xit.core.init; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; + +//@Order(1) +//@Component +@Slf4j +public class XitFrameworkApplicationCommandLineRunner implements CommandLineRunner { + + @Override + public void run(String... args) throws Exception { +// log.info("====================================================================================="); +// log.info("XitFrameworkApplicationCommandLineRunner Args: " + Arrays.toString(args)); +// log.info("====================================================================================="); + } +} diff --git a/src/main/java/com/xit/core/init/XitFrameworkApplicationListner.java b/src/main/java/com/xit/core/init/XitFrameworkApplicationListner.java new file mode 100644 index 0000000..76dccb2 --- /dev/null +++ b/src/main/java/com/xit/core/init/XitFrameworkApplicationListner.java @@ -0,0 +1,32 @@ +package com.xit.core.init; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.event.EventListener; + +@Slf4j +//@Component +//public class XitFrameworkApplicationListner implements ApplicationListener { +// +// @Override +// public void onApplicationEvent(ApplicationStartedEvent event) { +// ConfigurableApplicationContext applicationContext = event.getApplicationContext(); +//// log.info("====================================================================================="); +//// log.info("XitFrameworkApplicationListner"); +//// log.info("====================================================================================="); +// } +// +//} + +public class XitFrameworkApplicationListner { + + @EventListener + public void onApplicationEvent(ApplicationStartedEvent event) { + ConfigurableApplicationContext applicationContext = event.getApplicationContext(); +// log.info("====================================================================================="); +// log.info("XitFrameworkApplicationListner"); +// log.info("====================================================================================="); + } + +} diff --git a/src/main/java/com/xit/core/oauth2/api/controller/OAuth2LocalController.java b/src/main/java/com/xit/core/oauth2/api/controller/OAuth2LocalController.java new file mode 100644 index 0000000..48d9b75 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/api/controller/OAuth2LocalController.java @@ -0,0 +1,174 @@ +package com.xit.core.oauth2.api.controller; + +import com.xit.core.api.IRestResponse; +import com.xit.core.api.RestResponse; +import com.xit.core.oauth2.api.dto.LoginRequestDto; +import com.xit.core.oauth2.api.dto.TokenRequestDto; +import com.xit.core.oauth2.api.service.IAuthService; +import com.xit.core.oauth2.oauth.token.JwtTokenProvider; +import com.xit.core.support.jpa.EnumFindUtils; +import com.xit.core.util.Checks; +import com.xit.core.util.AssertUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; + +@Tag(name = "OAuth2LocalController", description = "인증 관리") +@RestController +@RequestMapping("/api/v1/auth") +@Validated +@RequiredArgsConstructor +public class OAuth2LocalController { + + + private final IAuthService authService; + + + /** + *
+     * 로그인 요청을 처리(Redis에 저장)하고 토큰(Access + Refresh) return
+     *
+     * 1. Login ID, PW 로 인증 정보 객체 UsernamePasswordAuthenticationToken 생성
+     * 2. AuthenticationManager에 authenticate 메소드의 파라미터로 넘겨, 검증 후 Authentication(사용자ID가 들어있다) return
+     *    AuthenticationManager --> 스프링 시큐리티의 실제 인증이 이루어지는 곳
+     *                              authenticate 메소드 하나만 정의되어 있는 인터페이스
+     *                              Builder 에서 UserDetails 의 유저 정보가 서로 일치하는지 검사
+     * 3. 인증정보로 JWT 토큰 생성
+     * 4. Refresh 토큰 저장 : : Redis에 저장
+     * 5. 토큰(Access + Refresh) return
+     * 
+ * @see AuthenticationManagerBuilder + * @see JwtTokenProvider + * + * @param loginRequestDto LoginRequestDto + * @param request HttpServletRequest + * @param response HttpServletResponse + * @param session Session + * @return ResponseEntity + */ + @Operation(summary = "login" , description = "login") +// @Parameters({ +// @Parameter(in = ParameterIn.QUERY, name = "providerType", description = "ProviderType", required = true, example = "GOOGLE"), +// @Parameter(in = ParameterIn.QUERY, name = "userId", description = "사용자ID", required = true, example = "minuk926926"), +// @Parameter(in = ParameterIn.QUERY, name = "password", description = "비밀번호", required = true, example = "minuk926926") +// }) + @PostMapping("/login") + public ResponseEntity login( + //@Validated + @Valid + @RequestBody + final LoginRequestDto loginRequestDto, + HttpServletRequest request, + HttpServletResponse response, + HttpSession session + //Errors errors + + ) { + + return RestResponse.of( + authService.login( + loginRequestDto, + request, + response, + session + ) + ); + + } + + /** + *
+     * JWT token 재발급
+     * access token - header, refresh - TokenRequestDto 에 담아 요청
+     *
+     * 1. access token의 유효기간이 남아 있는 경우 ErrorCode.NOT_EXPIRED_TOKEN_YET
+     * 2. refresh token의 유효기간이 재 발급 기준에 부합된 경우 refresh token도 재발급
+     * 3. 2번 항묵에 미 해당시 refresh token 값은 null로
+     * 
+ * + * @param tokenRequestDto TokenRequestDto + * @param request HttpServletRequest + * @return ResponseEntity IRestResponse + */ + @Operation(summary = "token 재발급 요청(header & DTO 사용)" , description = "token 재발급 :: accessToken - header, refreshToken - dto") + @Parameter(in = ParameterIn.QUERY, name = "refreshToken", description = "refresh token", required = true, example = " ") + @PostMapping("/jwt/reissue") + public ResponseEntity reissueFromHeader( + @Parameter(hidden = true) + @NotNull + @RequestBody + final TokenRequestDto tokenRequestDto, + HttpServletRequest request) { + return RestResponse.of(authService.reissue(tokenRequestDto, request, null)); + } + + /** + *
+     * JWT token 재발급
+     * access token - header, refresh - cookie 에 담아 요청
+     *
+     * 1. access token의 유효기간이 남아 있는 경우 ErrorCode.NOT_EXPIRED_TOKEN_YET
+     * 2. refresh token의 유효기간이 재 발급 기준에 부합된 경우 refresh token도 재발급
+     * 3. 2번 항묵에 미 해당시 refresh token 값은 null로
+     * 
+ * + * @param request HttpServletRequest + * @param response HttpServletResponse + * @return ResponseEntity IRestResponse + */ + @Operation(summary = "token 재발급(header & cookie 사용)" , description = "토큰 재발급 :: accessToken - header, refreshToken - cookie") + //@Parameter(in = ParameterIn.HEADER, name = "Authorization", description = "access token", required = true, example = " ") + //@Parameter(in = ParameterIn.COOKIE, name = "refreshToken", description = "refresh token", required = true, example = " ") + @PostMapping("/jwt/reissue/cookie") + public ResponseEntity reissueFromCookie(HttpServletRequest request, HttpServletResponse response) { + return RestResponse.of(authService.reissue(null, request, response)); + } + + + + /** + *
+     * JWT token 재발급
+     * access token - TokenRequestDto, refresh - TokenRequestDto 에 담아 요청
+     *
+     * 1. access token의 유효기간이 남아 있는 경우 ErrorCode.NOT_EXPIRED_TOKEN_YET
+     * 2. refresh token의 유효기간이 재 발급 기준에 부합된 경우 refresh token도 재발급
+     * 3. 2번 항묵에 미 해당시 refresh token 값은 null로
+     * 
+ * + * @param tokenRequestDto TokenRequestDto + * @return ResponseEntity IRestResponse + */ + @Operation(summary = "token 재발급(DTO 사용)" , description = "token 재발급 :: accessToken - dto, refreshToken - dto") + @PostMapping("/jwt/reissue/dto") + public ResponseEntity reissueFromParam( + @NotNull + @RequestBody + final TokenRequestDto tokenRequestDto) { + return RestResponse.of(authService.reissue(tokenRequestDto, null, null)); + } + + @Operation(summary = "토큰 체크" , description = "access token 체크") + @PostMapping("/jwt/validate") + public ResponseEntity validate(@RequestParam final String accessToken, @RequestParam boolean isExceptionThrow) { + return RestResponse.of(authService.validationToken(accessToken, isExceptionThrow)); + } + + @Operation(summary = "토큰 정보 확인" , description = "토큰 정보 확인") + @PostMapping("/jwt/info") + public ResponseEntity findAccessTokenInfo(@RequestParam final String accessToken) { + return RestResponse.of(authService.findAccessTokenInfo(accessToken)); + } +} diff --git a/src/main/java/com/xit/core/oauth2/api/controller/UserController.java b/src/main/java/com/xit/core/oauth2/api/controller/UserController.java new file mode 100644 index 0000000..1185d80 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/api/controller/UserController.java @@ -0,0 +1,109 @@ +package com.xit.core.oauth2.api.controller; + +import com.xit.biz.cmm.dto.CmmUserDto; +import com.xit.biz.cmm.dto.struct.CmmUserMapstruct; +import com.xit.core.annotation.Secured; +import com.xit.core.annotation.SecurityPolicy; +import com.xit.core.api.IRestResponse; +import com.xit.core.api.RestResponse; +import com.xit.core.oauth2.api.service.IAuthService; +import com.xit.core.oauth2.api.service.IUserService; +import com.xit.core.oauth2.oauth.entity.RoleType; +import com.xit.core.oauth2.oauth.token.JwtTokenProvider; +import com.xit.core.oauth2.utils.HeaderUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.mapstruct.factory.Mappers; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; +import java.util.Arrays; + +@Tag(name = "UserController", description = "사용자 관리") +@RestController +@RequestMapping("/api/v1/users") +@RequiredArgsConstructor +public class UserController { + + private final IUserService userService; + private final JwtTokenProvider tokenProvider; + + private final IAuthService authService; + private CmmUserMapstruct mapstruct = Mappers.getMapper(CmmUserMapstruct.class); + + @Operation(summary = "회원 가입" , description = "신규 회원 등록") + @PostMapping("/signup") // @Valid + public ResponseEntity signup(@RequestBody @Valid final CmmUserDto cmmUserDto) { + return RestResponse.of(userService.signup(mapstruct.toEntity(cmmUserDto))); + } + + + @GetMapping + public ResponseEntity getUser(HttpServletRequest request) { + return RestResponse.of(HeaderUtil.getUserId(request)); + } + + @Operation(summary = "사용자 정보 조회" , description = "사용자 정보 조회") + @GetMapping("/user") + @Secured(policy = SecurityPolicy.TOKEN) + @PreAuthorize("hasAnyRole('USER','ADMIN')") + public ResponseEntity getUserInfo() { + return RestResponse.of(authService.findMyUserWithoutAuthorities()); + } + + @Operation(summary = "사용자 정보 조회" , description = "사용자 정보 조회") + @Parameter(in = ParameterIn.PATH, name = "userId", description = "사용자ID", required = true, example = "minuk926") + @GetMapping("/user/{userId}") + @PreAuthorize("hasAnyRole('ADMIN')") + public ResponseEntity getUserInfo(@PathVariable String userId) { + return RestResponse.of(authService.findUserWithAuthorities(userId)); + } + + + + @Operation(summary = "사용자 정보 조회" , description = "등록된 사용자 정보 조회") + //@Secured(policy = SecurityPolicy.TOKEN) + @GetMapping("/me") + public ResponseEntity getMyMemberInfo() { + return RestResponse.of(userService.getMyInfo()); + } + + @Operation(summary = "세션의 사용자 정보 조회" , description = "세션 사용자 정보 조회") + @Parameter(in = ParameterIn.PATH, name = "userId", description = "사용자ID", required = true, example = "minuk926") + //@Secured(policy = SecurityPolicy.TOKEN) + @GetMapping("/{userId}") + public ResponseEntity getMemberInfo(@PathVariable String userId) { + return RestResponse.of(userService.getMemberInfo(userId)); + } + + private static RoleType findProviderType(RoleType roleType){ + return Arrays.stream(RoleType.values()) + .filter(pt -> pt.name().equals(roleType.getCode())) + .findAny() + .orElse(null); + } + + + // @GetMapping +// public ApiResponse getUser() { +// //org.springframework.security.core.userdetails.User principal = (org.springframework.security.core.userdetails.User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); +// +// //CmmUser user = userService.getUser(principal.getUsername()); +// +// Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); +// +// CmmUser user = userService.getUser(authentication.getName()); +// +// return ApiResponse.success("user", user); +// +// } + + + +} diff --git a/src/main/java/com/xit/core/oauth2/api/dto/LoginRequestDto.java b/src/main/java/com/xit/core/oauth2/api/dto/LoginRequestDto.java new file mode 100644 index 0000000..ef7ffbe --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/api/dto/LoginRequestDto.java @@ -0,0 +1,61 @@ +package com.xit.core.oauth2.api.dto; + +import com.xit.biz.cmm.entity.CmmUser; +import com.xit.core.oauth2.oauth.entity.ProviderType; +import com.xit.core.oauth2.oauth.entity.RoleType; +import com.xit.core.support.valid.Enums; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.crypto.password.PasswordEncoder; + +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; + +/** + * Validation (@NotBlank) class -> hibernate class 사용 + */ +@Schema(name = "LoginRequestDto", description = "로그인 parameter DTO") +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class LoginRequestDto { + + /** + * Enum type Validation -> controller에서 처리 + */ + @Schema(required = true, title = "Provider Type", example = "LOCAL", description = "Provider TYPE") + @Enumerated(EnumType.STRING) + @Enums(enumClass = ProviderType.class, ignoreCase = false, message = "{auth.user.pattern.ProviderType}") + private ProviderType providerType; + + @Schema(required = true, title = "사용자ID", example = "minuk926", description = "사용자 ID") + @Pattern(regexp = "[0-9a-zA-z]{6,20}", message = "{auth.user.pattern.id}") + @Size(min = 6, max = 20) + private String userId; + + @Schema(required = true, title = "비밀번호", example = "Minuk926!@", description = "비밀 번호") + @NotNull//(message = "비밀번호는 필수 입니다(6 ~ 20자리)") + @Pattern(regexp = "[0-9a-zA-z!@#$%^&*]{6,20}", message = "{auth.user.pattern.password}") + @Size(min = 6, max = 20) + private String password; + + @Builder + public CmmUser toUser(PasswordEncoder passwordEncoder) { + return CmmUser.builder() + .providerType(providerType) + //.providerType(ProviderType.from(providerType)) + .userId(userId) + .password(passwordEncoder.encode(password)) + .roleType(RoleType.USER) + .build(); + } + + public UsernamePasswordAuthenticationToken toAuthentication() { + return new UsernamePasswordAuthenticationToken(userId, password); + } +} diff --git a/src/main/java/com/xit/core/oauth2/api/dto/TokenDto.java b/src/main/java/com/xit/core/oauth2/api/dto/TokenDto.java new file mode 100644 index 0000000..2fd2be2 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/api/dto/TokenDto.java @@ -0,0 +1,26 @@ +package com.xit.core.oauth2.api.dto; + +import com.xit.core.constant.XitConstants; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import javax.persistence.EnumType; +import javax.persistence.Enumerated; + +@Schema(name = "TokenDto", description = "토큰 정보") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class TokenDto { + + @Schema(required = true, title = "토큰타입", example = "Bearer", description = "토큰 타입") + private String grantType; + + @Schema(required = true, title = "Access token", example = " ", description = "Access token") + private String accessToken; + + @Schema(required = true, title = "Refresh token", example = " ", description = "Refresh token") + private String refreshToken; +} diff --git a/src/main/java/com/xit/core/oauth2/api/dto/TokenRequestDto.java b/src/main/java/com/xit/core/oauth2/api/dto/TokenRequestDto.java new file mode 100644 index 0000000..60523bc --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/api/dto/TokenRequestDto.java @@ -0,0 +1,16 @@ +package com.xit.core.oauth2.api.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Schema(name = "TokenRequestDto", description = "토큰 재발급 요청 parameter") +@Getter +@NoArgsConstructor +public class TokenRequestDto { + @Schema(title = "accessToken", example = " ", description = "accessToken") + private String accessToken; + + @Schema(title = "refreshToken", example = " ", description = "refreshToken") + private String refreshToken; +} diff --git a/src/main/java/com/xit/core/oauth2/api/entity/AuthReqModel.java b/src/main/java/com/xit/core/oauth2/api/entity/AuthReqModel.java new file mode 100644 index 0000000..d41da4d --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/api/entity/AuthReqModel.java @@ -0,0 +1,10 @@ +package com.xit.core.oauth2.api.entity; + +//@Getter +//@Setter +//@NoArgsConstructor +//@AllArgsConstructor +//public class AuthReqModel { +// private String id; +// private String password; +//} diff --git a/src/main/java/com/xit/core/oauth2/api/entity/RefreshToken.java b/src/main/java/com/xit/core/oauth2/api/entity/RefreshToken.java new file mode 100644 index 0000000..ac29b5f --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/api/entity/RefreshToken.java @@ -0,0 +1,40 @@ +package com.xit.core.oauth2.api.entity; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + * 토큰 만료시 자동 삭제되도록 해야 함(redis 사용) + * RDB 사용시 배치로 만료된 토큰 삭제처리 + */ +@Schema(name = "RefreshToken", description = "Refresh Token 테이블") +@Table(name = "refresh_token") +@Entity +@Getter +@NoArgsConstructor +public class RefreshToken { + + @Schema(required = true, title = "사용자ID", example = " ", description = "사용자 ID") + @Id + private String key; + + @Schema(required = true, title = "refresh token", example = " ", description = "refresh token") + private String value; + + public RefreshToken updateValue(String token) { + this.value = token; + return this; + } + + @Builder + public RefreshToken(String key, String value) { + this.key = key; + this.value = value; + } +} diff --git a/src/main/java/com/xit/core/oauth2/api/entity/User.java b/src/main/java/com/xit/core/oauth2/api/entity/User.java new file mode 100644 index 0000000..fdaf50c --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/api/entity/User.java @@ -0,0 +1,100 @@ +//package com.xit.core.oauth2.api.entity.user; +// +//import com.fasterxml.jackson.annotation.JsonIgnore; +//import com.xit.core.oauth2.oauth.entity.ProviderType; +//import com.xit.core.oauth2.oauth.entity.RoleType; +//import io.swagger.v3.oas.annotations.media.Schema; +//import lombok.AllArgsConstructor; +//import lombok.Getter; +//import lombok.NoArgsConstructor; +//import lombok.Setter; +// +//import javax.persistence.*; +//import javax.validation.constraints.NotNull; +//import javax.validation.constraints.Size; +//import java.time.LocalDateTime; +// +//@Schema(name = "user", description = "user") +//@Table(name = "user") +//@Entity +//@Getter @Setter +//@NoArgsConstructor @AllArgsConstructor +//public class User { +// @JsonIgnore +// @Id +// @Column(name = "user_seq") +// @GeneratedValue(strategy = GenerationType.IDENTITY) +// private Long userSeq; +// +// @Column(name = "user_id", length = 64, unique = true) +// @NotNull +// @Size(max = 64) +// private String userId; +// +// @Column(name = "user_name", length = 100) +// @NotNull +// @Size(max = 100) +// private String userName; +// +// @JsonIgnore +// @Column(name = "password", length = 128) +// @NotNull +// @Size(max = 128) +// private String password; +// +// @Column(name = "email", length = 512, unique = true) +// @NotNull +// @Size(max = 512) +// private String email; +// +// @Column(name = "email_verified_yn", length = 1) +// @NotNull +// @Size(min = 1, max = 1) +// private String emailVerifiedYn; +// +// @Column(name = "profile_image_url", length = 512) +// @NotNull +// @Size(max = 512) +// private String profileImageUrl; +// +// @Column(name = "provider_type", length = 20) +// @Enumerated(EnumType.STRING) +// @NotNull +// private ProviderType providerType; +// +// @Column(name = "role_type", length = 20) +// @Enumerated(EnumType.STRING) +// @NotNull +// private RoleType roleType; +// +// @Column(name = "created_at") +// @NotNull +// private LocalDateTime createdAt; +// +// @Column(name = "modified_at") +// @NotNull +// private LocalDateTime modifiedAt; +// +// public User( +// @NotNull @Size(max = 64) String userId, +// @NotNull @Size(max = 100) String userName, +// @NotNull @Size(max = 512) String email, +// @NotNull @Size(max = 1) String emailVerifiedYn, +// @NotNull @Size(max = 512) String profileImageUrl, +// @NotNull ProviderType providerType, +// @NotNull RoleType roleType, +// @NotNull LocalDateTime createdAt, +// @NotNull LocalDateTime modifiedAt +// ) { +// this.userId = userId; +// this.userName = userName; +// this.password = "NO_PASS"; +// this.email = email != null ? email : "NO_EMAIL"; +// this.emailVerifiedYn = emailVerifiedYn; +// this.profileImageUrl = profileImageUrl != null ? profileImageUrl : ""; +// this.providerType = providerType; +// this.roleType = roleType; +// this.createdAt = createdAt; +// this.modifiedAt = modifiedAt; +// } +//} diff --git a/src/main/java/com/xit/core/oauth2/api/repository/RefreshTokenRepository.java b/src/main/java/com/xit/core/oauth2/api/repository/RefreshTokenRepository.java new file mode 100644 index 0000000..a948078 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/api/repository/RefreshTokenRepository.java @@ -0,0 +1,10 @@ +package com.xit.core.oauth2.api.repository; + +import com.xit.core.oauth2.api.entity.RefreshToken; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface RefreshTokenRepository extends JpaRepository { + public Optional findByKey(String key); +} diff --git a/src/main/java/com/xit/core/oauth2/api/service/IAuthService.java b/src/main/java/com/xit/core/oauth2/api/service/IAuthService.java new file mode 100644 index 0000000..4c279b3 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/api/service/IAuthService.java @@ -0,0 +1,31 @@ +package com.xit.core.oauth2.api.service; + +import com.xit.biz.cmm.entity.CmmUser; +import com.xit.core.oauth2.api.dto.LoginRequestDto; +import com.xit.core.oauth2.api.dto.TokenDto; +import com.xit.core.oauth2.api.dto.TokenRequestDto; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.util.Map; +import java.util.Optional; + +public interface IAuthService { + + //TokenDto login(final CmmUserRequestDto cmmUserRequestDto); + TokenDto login(final LoginRequestDto loginRequestDto, + HttpServletRequest request, + HttpServletResponse response, + HttpSession session); + + TokenDto reissue(final TokenRequestDto tokenRequestDto, HttpServletRequest request, HttpServletResponse response); + + boolean validationToken(final String accessToken, final boolean isExceptionThrow); + + Map findAccessTokenInfo(final String accessToken); + + Optional findMyUserWithoutAuthorities(); + + Optional findUserWithAuthorities(final String userId); +} diff --git a/src/main/java/com/xit/core/oauth2/api/service/IUserService.java b/src/main/java/com/xit/core/oauth2/api/service/IUserService.java new file mode 100644 index 0000000..913b29b --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/api/service/IUserService.java @@ -0,0 +1,11 @@ +package com.xit.core.oauth2.api.service; + +import com.xit.biz.cmm.entity.CmmUser; + +public interface IUserService { + public CmmUser signup(CmmUser cmmUser); + public CmmUser getUser(String userId); + + CmmUser getMemberInfo(String userId); + CmmUser getMyInfo(); +} diff --git a/src/main/java/com/xit/core/oauth2/api/service/impl/AuthService.java b/src/main/java/com/xit/core/oauth2/api/service/impl/AuthService.java new file mode 100644 index 0000000..93c5da1 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/api/service/impl/AuthService.java @@ -0,0 +1,311 @@ +package com.xit.core.oauth2.api.service.impl; + +import com.xit.core.oauth2.api.entity.RefreshToken; +import com.xit.core.oauth2.api.repository.RefreshTokenRepository; +import com.xit.biz.cmm.entity.CmmUser; +import com.xit.biz.cmm.repository.ICmmUserRepository; +import com.xit.core.constant.ErrorCode; +import com.xit.core.constant.XitConstants; +import com.xit.core.exception.TokenAuthException; +import com.xit.core.oauth2.api.dto.LoginRequestDto; +import com.xit.core.oauth2.api.dto.TokenDto; +import com.xit.core.oauth2.api.dto.TokenRequestDto; +import com.xit.core.oauth2.api.service.IAuthService; +import com.xit.core.oauth2.config.properties.AppProperties; +import com.xit.core.oauth2.oauth.token.JwtTokenProvider; +import com.xit.core.oauth2.utils.CookieUtil; +import com.xit.core.oauth2.utils.HeaderUtil; +import com.xit.core.util.Checks; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.util.*; + +@Service +@RequiredArgsConstructor +public class AuthService implements IAuthService { + @Value("${xit.auth.save.type:header}") + private String authSaveType; + + @Value("${jwt.refresh.save.type:HEADER}") + private String tokenParamType; + + private static final int EXPIRE_CONVERT_SECOND_FROM_DAY = 60 * 60 * 24; + private final static long THREE_DAYS_MILISECONDS = 3 * (1000 * 60 * 60 * 24); + + private final AppProperties appProperties; + private final AuthenticationManager authenticationManager; + private final ICmmUserRepository cmmUserRepository; + private final JwtTokenProvider jwtTokenProvider; + private final RefreshTokenRepository refreshTokenRepository; + + /** + *
+     * 로그인 요청을 처리(Redis에 저장)하고 토큰(Access + Refresh) return
+     *
+     * 1. Login ID, PW 로 인증 정보 객체 UsernamePasswordAuthenticationToken 생성
+     * 2. AuthenticationManager에 authenticate 메소드의 파라미터로 넘겨, 검증 후 Authentication(사용자ID가 들어있다) return
+     *    AuthenticationManager --> 스프링 시큐리티의 실제 인증이 이루어지는 곳
+     *                              authenticate 메소드 하나만 정의되어 있는 인터페이스
+     *                              Builder 에서 UserDetails 의 유저 정보가 서로 일치하는지 검사
+     * 3. 인증정보로 JWT 토큰 생성
+     * 4. Refresh 토큰 저장 : : Redis에 저장
+     * 5. 토큰(Access + Refresh) return
+     *
+     * @see AuthenticationManagerBuilder
+     * @see JwtTokenProvider
+     * 
+ * @param loginRequestDto LoginRequestDto + * @return TokenDto + */ + @Override + @Transactional + public TokenDto login(final LoginRequestDto loginRequestDto, + HttpServletRequest request, + HttpServletResponse response, + HttpSession session) { + TokenDto tokenDto = null; + Authentication authentication = null; + + // 1. Login ID/PW 를 기반으로 AuthenticationToken 생성 + UsernamePasswordAuthenticationToken authenticationToken = loginRequestDto.toAuthentication(); + + // 2. 실제로 검증 (사용자 비밀번호 체크) 이 이루어지는 부분 + authentication = authenticationManager.authenticate(authenticationToken); + + // Authentication 저장 + if(Objects.equals(authSaveType, XitConstants.AuthSaveType.SECURITY.getValue())){ + // TODO :: SessionCreationPolicy.STATELESS 인 경우 사용 불가 + SecurityContextHolder.getContext().setAuthentication(authentication); + + }else if(Objects.equals(authSaveType, XitConstants.AuthSaveType.SESSION.getValue())){ + session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext()); + } + + CmmUser cmmUser = cmmUserRepository.findByUserId(loginRequestDto.getUserId()).orElseThrow(() -> new UsernameNotFoundException(loginRequestDto.getUserId() + " -> 사용자를 찾을 수 없습니다.")); + Map infoMap = new HashMap<>(); + infoMap.put("userName", cmmUser.getUserName()); + infoMap.put("userEmail", cmmUser.getEmail()); + + RefreshToken savedRefreshToken = refreshTokenRepository.findByKey(authentication.getName()) + .orElse(null); + + // 저장된 refresh token not exists + if (Checks.isNull(savedRefreshToken)) { + // 없는 경우 새로 등록 + tokenDto = jwtTokenProvider.generateTokenDto(authentication, infoMap); + refreshTokenRepository.saveAndFlush( + RefreshToken.builder() + .key(loginRequestDto.getUserId()) + .value(tokenDto.getRefreshToken()) + .build() + + ); + + // 저장된 토큰이 있다 + } else { + + // 만료되지 않은 refresh token + if(!jwtTokenProvider.isExpiredToken(savedRefreshToken.getValue())) { + + // refresh token 잔여기간 기준 도달여부 check + Date now = new Date(); + long validTime = jwtTokenProvider.parseClaims(savedRefreshToken.getValue()).getExpiration().getTime() - now.getTime(); + + if (validTime <= THREE_DAYS_MILISECONDS) { + // 새로운 토큰 생성 : access token and refresh token + tokenDto = jwtTokenProvider.generateTokenDto(authentication, infoMap); + + // DB에 refresh 토큰 업데이트 + savedRefreshToken.updateValue(Objects.requireNonNull(tokenDto.getRefreshToken())); + + }else{ + tokenDto = TokenDto.builder() + .grantType(XitConstants.JwtToken.GRANT_TYPE.getValue()) + .accessToken(jwtTokenProvider.generateJwtAccessToken(authentication, infoMap)) + .refreshToken(null) + .build(); + + refreshTokenRepository.saveAndFlush( + RefreshToken.builder() + .key(loginRequestDto.getUserId()) + .value(tokenDto.getRefreshToken()) + .build() + ); + } + + // refresh token 만료 + }else{ + tokenDto = jwtTokenProvider.generateTokenDto(authentication, infoMap); + + refreshTokenRepository.saveAndFlush( + RefreshToken.builder() + .key(loginRequestDto.getUserId()) + .value(tokenDto.getRefreshToken()) + .build() + ); + } + } + + // COOKIE 타입의 요청인 경우 COOKIE set + if(Objects.equals(XitConstants.JwtTokenParamType.COOKIE.name(), tokenParamType)) { + CookieUtil.deleteCookie(request, response, XitConstants.JwtToken.REFRESH_TOKEN_NAME.getValue()); + CookieUtil.addCookie(response, XitConstants.JwtToken.REFRESH_TOKEN_NAME.getValue(), tokenDto.getRefreshToken(), appProperties.getAuth().getRefreshTokenExpiry() * EXPIRE_CONVERT_SECOND_FROM_DAY); + } + + // 5. 토큰 발급 + return tokenDto; + } + + /** + *
+     * 토큰 재발급 : Redis에 저장
+     * 1. Access 토큰 검증
+     * 2. Access Token 복호화 --> 유저 정보 (Member ID) GET
+     * 3. Refresh Token 만료 여부 검증
+     * 4. 저장소의 Refresh Token GET
+     * 5. 저장소의 Refresh Token == 전달받은 Refresh Token 의 일치 여부 확인
+     * 6. 토큰 생성
+     * 7. Refresh Token update & 저장소 저장
+     * 8. 토큰 return
+     *
+     * @see JwtTokenProvider
+     * 
+ * @param tokenRequestDto TokenRequestDto + * @return TokenDto + */ + @Override + @Transactional + public TokenDto reissue(final TokenRequestDto tokenRequestDto, HttpServletRequest request, HttpServletResponse response) { + TokenDto tokenDto = new TokenDto(); + String sAccessToken = null; + String sRefreshToken = null; + Authentication authentication = null; + + //------------------------------------------------------- + // access token 검증 + //------------------------------------------------------- + // Get access token ------------------------------------- + if(Objects.equals(XitConstants.JwtTokenParamType.DTO.name(), tokenParamType)) + sAccessToken = tokenRequestDto.getAccessToken(); + + else if( + Objects.nonNull(request) && + (Objects.equals(XitConstants.JwtTokenParamType.COOKIE.name(), tokenParamType) + || Objects.equals(XitConstants.JwtTokenParamType.HEADER.name(), tokenParamType)) + ) + sAccessToken = HeaderUtil.getAccessToken(request); + else + throw new TokenAuthException("Token 전달 형태가 미 정의 되었습니다."); + + // Access token check ------------------------------------- + // 오염된 토큰 인지만 체크 + jwtTokenProvider.validateTokenExcludeExpired(sAccessToken, false, true); + //if(!jwtTokenProvider.validateToken(sAccessToken, false, false)) throw new TokenAuthException(ErrorCode.INVALID_TOKEN); + // expired access token 여부 체크 + // TODO :: 유효기간 만료된 경우에만 재발급 - 테스트를 위해 반대로 발급되도록 함 + if(jwtTokenProvider.isExpiredToken(sAccessToken)) throw new TokenAuthException(ErrorCode.NOT_EXPIRED_TOKEN_YET); + // -------------------------------------------------------- + + authentication = jwtTokenProvider.getAuthentication(sAccessToken); + + //--------------------------------------------------------- + // refresh token 검증 + //--------------------------------------------------------- + // Get refresh token -------------------------------------- + if(Objects.equals(XitConstants.JwtTokenParamType.COOKIE.name(), tokenParamType)) + sRefreshToken = CookieUtil.getCookie(request, XitConstants.JwtToken.REFRESH_TOKEN_NAME.getValue()) + .map(Cookie::getValue) + .orElse((null)); + else if(Objects.equals(XitConstants.JwtTokenParamType.HEADER.name(), tokenParamType) + || Objects.equals(XitConstants.JwtTokenParamType.DTO.name(), tokenParamType)) + sRefreshToken = tokenRequestDto.getRefreshToken(); + + // Refresh token check ------------------------------------ + jwtTokenProvider.validateTokenExcludeExpired(sRefreshToken, true, true); + + // userId refresh token 으로 DB 확인 + RefreshToken savedRefreshToken = refreshTokenRepository.findByKey(authentication.getName()) + .orElse(null); + if (Checks.isEmpty(savedRefreshToken)) throw new TokenAuthException(ErrorCode.NOT_EXISTS_SAVED_REFRESH_TOKEN); + + if (!Objects.equals(Objects.requireNonNull(savedRefreshToken).getValue(), sRefreshToken)) { + throw new TokenAuthException(ErrorCode.MISMATCH_REFRESH_ACCESS_TOKEN); + } + + // 토큰 부가 정보 set + CmmUser cmmUser = cmmUserRepository.findByUserId(authentication.getName()).orElse(null); + if(Checks.isNull(cmmUser)) throw new TokenAuthException(ErrorCode.INVALID_ROLE_TOKEN); + Map infoMap = new HashMap<>(); + infoMap.put("userName", cmmUser.getUserName()); + infoMap.put("userEmail", cmmUser.getEmail()); + + // TODO : refresh토큰 발급 기준인 정의 필요 + // refresh 토큰 기간이 3일 이하로 남은 경우, refresh 토큰 갱신 + Date now = new Date(); + long validTime = jwtTokenProvider.parseClaims(sRefreshToken).getExpiration().getTime() - now.getTime(); + + if (validTime <= THREE_DAYS_MILISECONDS) { + // 토큰 생성 : access token and refresh token + tokenDto = jwtTokenProvider.generateTokenDto(authentication, infoMap); + + // TODO : DB 갱신 확인 필요 + // DB refresh 토큰 업데이트 + RefreshToken newRefreshToken = savedRefreshToken.updateValue(Objects.requireNonNull(tokenDto.getRefreshToken())); + //refreshTokenRepository.saveAndFlush(newRefreshToken); + + if(Objects.equals(XitConstants.JwtTokenParamType.COOKIE.name(), tokenParamType)) { + CookieUtil.deleteCookie(request, response, XitConstants.JwtToken.REFRESH_TOKEN_NAME.getValue()); + CookieUtil.addCookie(response, XitConstants.JwtToken.REFRESH_TOKEN_NAME.getValue(), newRefreshToken.getValue(), appProperties.getAuth().getRefreshTokenExpiry() * EXPIRE_CONVERT_SECOND_FROM_DAY); + } + + }else{ + tokenDto = TokenDto.builder() + .grantType(XitConstants.JwtToken.GRANT_TYPE.getValue()) + .accessToken(jwtTokenProvider.generateJwtAccessToken(authentication, infoMap)) + .refreshToken(null) + .build(); + } + return tokenDto; + } + + @Override + @Transactional(readOnly = true) + public boolean validationToken(final String accessToken, final boolean isExceptionThrow) { + return jwtTokenProvider.validateTokenExcludeExpired(accessToken, false, isExceptionThrow); + } + + // TODO :: 적용 필요 + @Override + @Transactional(readOnly = true) + public Map findAccessTokenInfo(String accessToken) { + return jwtTokenProvider.getAccessTokenInfo(accessToken); //authTokenProvider.getAccessTokenInfo(accessToken); + } + + + @Override + @Transactional(readOnly = true) + public Optional findMyUserWithoutAuthorities() { + //cmmUserRepos + return Optional.empty(); //cmmUserRepository.findOneWithAuthorities(SecurityUtil.getCurrentMemberId()); + } + + @Override + @Transactional(readOnly = true) + public Optional findUserWithAuthorities(final String userId) { + return Optional.empty(); //cmmUserRepository.findOneWithAuthorities(userId); + } +} diff --git a/src/main/java/com/xit/core/oauth2/api/service/impl/UserService.java b/src/main/java/com/xit/core/oauth2/api/service/impl/UserService.java new file mode 100644 index 0000000..0d51f71 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/api/service/impl/UserService.java @@ -0,0 +1,73 @@ +package com.xit.core.oauth2.api.service.impl; + +import com.xit.biz.cmm.entity.CmmUser; +import com.xit.biz.cmm.repository.ICmmUserRepository; +import com.xit.core.constant.ErrorCode; +import com.xit.core.exception.AuthorizationException; +import com.xit.core.exception.CustomBaseException; +import com.xit.core.exception.UserExistedException; +import com.xit.core.oauth2.api.service.IUserService; +import com.xit.core.oauth2.oauth.entity.ProviderType; +import com.xit.core.oauth2.oauth.entity.RoleType; +import com.xit.core.oauth2.utils.SecurityUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Objects; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class UserService implements IUserService { + @Value("${xit.auth.save.type:header}") + private String authSaveType; + + private final ICmmUserRepository cmmUserRepository; + private final PasswordEncoder passwordEncoder; + + /** + * 회원 정보 저장 + * + * @param cmmUser CmmUser + * @return CmmUser + */ + @Transactional + public CmmUser signup(CmmUser cmmUser) { + //if (cmmUserRepository.findByUserId(cmmUser.getUserId()).isPresent()) { + if (cmmUserRepository.existsByUserId(cmmUser.getUserId())) { + throw new UserExistedException(ErrorCode.MEMBER_EXISTS); + } + cmmUser.setCmmUserId(UUID.randomUUID().toString().replaceAll("-", "")); + cmmUser.setCreatedBy(cmmUser.getUserId()); + //cmmUser.setCreatedDate(LocalDateTime.now()); + cmmUser.setRoleType(RoleType.USER); + + if (Objects.equals(ProviderType.LOCAL, cmmUser.getProviderType())) { + cmmUser.setPassword(passwordEncoder.encode(cmmUser.getPassword())); + } else { + cmmUser.setPassword("NO_PASSWORD"); + //cmmUser.setProviderType(cmmUser.getUserId()); + } + return cmmUserRepository.save(cmmUser); + } + + public CmmUser getUser(String userId) { + return cmmUserRepository.findByUserId(userId).orElseThrow(() -> new CustomBaseException(ErrorCode.USER_NOT_FOUND)); + } + + + @Transactional(readOnly = true) + public CmmUser getMemberInfo(String userId) { + return cmmUserRepository.findByUserId(userId).orElseThrow(() -> new CustomBaseException(ErrorCode.USER_NOT_FOUND)); + } + + // 현재 SecurityContext 에 있는 유저 정보 가져오기 + @Transactional(readOnly = true) + public CmmUser getMyInfo() { + String userId = SecurityUtil.getCurrentUserId(authSaveType); + return cmmUserRepository.findByUserId(userId).orElseThrow(() -> new AuthorizationException(ErrorCode.USER_NOT_FOUND)); + } +} diff --git a/src/main/java/com/xit/core/oauth2/config/properties/AppProperties.java b/src/main/java/com/xit/core/oauth2/config/properties/AppProperties.java new file mode 100644 index 0000000..69a0e15 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/config/properties/AppProperties.java @@ -0,0 +1,47 @@ +package com.xit.core.oauth2.config.properties; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@ConfigurationProperties(prefix = "xit") +public class AppProperties { + + private final Auth auth = new Auth(); + private final OAuth2 oauth2 = new OAuth2(); + + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class Auth { + private String typ; + private String grant; + private String alg; + private String issure; + private String audience; + // minute + private int tokenExpiry; + // day + private int refreshTokenExpiry; + } + + public static final class OAuth2 { + private List authorizedRedirectUris = new ArrayList<>(); + + public List getAuthorizedRedirectUris() { + return authorizedRedirectUris; + } + + public OAuth2 authorizedRedirectUris(List authorizedRedirectUris) { + this.authorizedRedirectUris = authorizedRedirectUris; + return this; + } + } +} diff --git a/src/main/java/com/xit/core/oauth2/config/properties/CorsProperties.java b/src/main/java/com/xit/core/oauth2/config/properties/CorsProperties.java new file mode 100644 index 0000000..f37d3eb --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/config/properties/CorsProperties.java @@ -0,0 +1,15 @@ +package com.xit.core.oauth2.config.properties; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Getter +@Setter +@ConfigurationProperties(prefix = "cors") +public class CorsProperties { + private String allowedOrigins; + private String allowedMethods; + private String allowedHeaders; + private Long maxAge; +} diff --git a/src/main/java/com/xit/core/oauth2/config/security/JwtConfig.java b/src/main/java/com/xit/core/oauth2/config/security/JwtConfig.java new file mode 100644 index 0000000..6ca9625 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/config/security/JwtConfig.java @@ -0,0 +1,16 @@ +//package com.xit.core.oauth2.config.security; +// +//import com.xit.core.oauth2.oauth.token.JwtTokenProvider; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.context.annotation.Bean; +// +////@Configuration +//public class JwtConfig { +// @Value("${jwt.secret}") +// private String secret; +// +// @Bean +// public JwtTokenProvider jwtProvider() { +// return new JwtTokenProvider(secret); +// } +//} diff --git a/src/main/java/com/xit/core/oauth2/config/security/SecurityConfig.java b/src/main/java/com/xit/core/oauth2/config/security/SecurityConfig.java new file mode 100644 index 0000000..691accf --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/config/security/SecurityConfig.java @@ -0,0 +1,282 @@ +package com.xit.core.oauth2.config.security; + +import com.xit.core.oauth2.api.repository.RefreshTokenRepository; +import com.xit.core.oauth2.config.properties.AppProperties; +import com.xit.core.oauth2.config.properties.CorsProperties; +import com.xit.core.oauth2.oauth.entity.RoleType; +import com.xit.core.oauth2.oauth.exception.RestAuthenticationEntryPoint; +import com.xit.core.oauth2.oauth.filter.TokenAuthenticationFilter; +import com.xit.core.oauth2.oauth.handler.OAuth2AuthenticationFailureHandler; +import com.xit.core.oauth2.oauth.handler.OAuth2AuthenticationSuccessHandler; +import com.xit.core.oauth2.oauth.handler.TokenAccessDeniedHandler; +import com.xit.core.oauth2.oauth.repository.OAuth2AuthorizationRequestBasedOnCookieRepository; +import com.xit.core.oauth2.oauth.service.CustomOAuth2UserService; +import com.xit.core.oauth2.oauth.service.CustomUserDetailsService; +import com.xit.core.oauth2.oauth.token.JwtTokenProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.BeanIds; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.builders.WebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsUtils; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.Arrays; + +/** + * WebSecurityConfigurerAdapter 상속 Override + * + * 1. configure( WebSecurity) : 서비스 전체에 영향을 미치는 설정 + * - web.ignoring를 통한 security skip(resource file), debug 모드설정 등 + * + * 2. configure(AuthenticationManagerBuilder auth) + * - AuthenticationManager()를 획득하기 위해 AuthenticationManager()의 기본 구현에 사용 + * - AuthenticationManagerBuilder를 사용하여 AuthenticationManager를 지정 + * - AuthenticationManagerBuilder에 지정한 클래스가 UserDetailService 인터페이스를 구현한 겨우 + * 해당 클래스를 DaoAuthenticationConfigurer 클래스의 인자에 담아 DaoAuthenticationConfigurer 객체를 리턴 + * 사용자 클래스로 DaoAuthenticationConfigurer 인자로써 인증처리 + * - passwordEncoder()는 AbstractDaoAuthenticationConfigurer클래스의 메소드 + * DaoAuthenticationConfigurer에 의해 상속 + * abstract class AbstractDaoAuthenticationConfigurer + * , + * C extends AbstractDaoAuthenticationConfigurer, + * U extends UserDetailsService> + * + * 3. configure(HttpSecurity) : 특정 http 요청에 대한 웹 기반 보안을 구성 + * - FormLoginConfigurer : /login, /login?error 기본 제공 + * loginform() overload하여 명시적으로 변경 + * - CsrfConfigurer : CsrfFilter를 추가 requireCsrfProtectionMatcher에서 지정한 방법에 대한 CSRF 보호기능을 활성화 + * 생성자 : csrf() + * ignoringAntMatchers() 메소드의 인자로 등록한 url에 대하여서는 CSRF 보호 해지 + * - LogoutConfigurer : LogoutFilter를 추가하여 로그아웃 기능을 구현 + * 생선자 : logout() + * logoutRequestMatcher 클래스는 인자로 들어오는 RequestMatcher 객체로 로그아웃 진행 + * LogoutSuccessfulUrl() + * - ExceptionHandlingConfigurer : 에러에 대한 리다이렉트 url 을 설정 + * - OAuth2LoginConfigurer : 소셜 로그인 기능을 활성화 + */ +@Configuration +@RequiredArgsConstructor +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + private static final String[] AUTH_WHITELIST = { + "/favicon.ico", + "/static/**", + "/api-docs", + "/api-docs/**", + "/h2-console/**", + "/v2/api-docs/**", + "/swagger-ui.html", + "/swagger-ui/**", + "/swagger-resources/**", + "/webjars/**", + "favicon.ico", + "/configuration/ui", + "/configuration/security", + "/resources/**", + "/api/auth/**", + "/api/sample/**", + "/web/sample/**", + "/" + }; + + private final CorsProperties corsProperties; + private final AppProperties appProperties; + private final JwtTokenProvider authTokenProvider; + private final CustomUserDetailsService userDetailsService; + private final CustomOAuth2UserService customOAuth2UserService; + private final TokenAccessDeniedHandler tokenAccessDeniedHandler; + private final RefreshTokenRepository refreshTokenRepository; + + /** + * 1. configure( WebSecurity) : 서비스 전체에 영향을 미치는 설정 + * - web.ignoring를 통한 security skip(resource file), debug 모드설정 등 + * @param web WebSecurity + */ + @Override + public void configure(WebSecurity web) { + web.ignoring() + //.antMatchers("/h2-console/**", "/favicon.ico"); + .antMatchers(AUTH_WHITELIST); + } + + /** + * 2. configure(AuthenticationManagerBuilder auth) + * - AuthenticationManager()를 획득하기 위해 AuthenticationManager()의 기본 구현에 사용 + * - AuthenticationManagerBuilder를 사용하여 AuthenticationManager를 지정 + * - AuthenticationManagerBuilder에 지정한 클래스가 UserDetailService 인터페이스를 구현한 경우 + * 해당 클래스를 DaoAuthenticationConfigurer 클래스의 인자에 담아 DaoAuthenticationConfigurer 객체를 리턴 + * 사용자 클래스로 DaoAuthenticationConfigurer 인자로써 인증처리 + * - passwordEncoder()는 AbstractDaoAuthenticationConfigurer클래스의 메소드 + * abstract class AbstractDaoAuthenticationConfigurer + * , + * C extends AbstractDaoAuthenticationConfigurer, + * U extends UserDetailsService> + * + * @param auth AuthenticationManagerBuilder + * @throws Exception Exception + */ + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailsService) + .passwordEncoder(passwordEncoder()); + } + + /** + * 3. configure(HttpSecurity) : 특정 http 요청에 대한 웹 기반 보안을 구성 + * - FormLoginConfigurer : /login, /login?error 기본 제공 + * loginform() overload하여 명시적으로 변경 + * - CsrfConfigurer : CsrfFilter를 추가 requireCsrfProtectionMatcher에서 지정한 방법에 대한 CSRF 보호기능을 활성화 + * 생성자 : csrf() + * ignoringAntMatchers() 메소드의 인자로 등록한 url에 대하여서는 CSRF 보호 해지 + * - LogoutConfigurer : LogoutFilter를 추가하여 로그아웃 기능을 구현 + * 생선자 : logout() + * logoutRequestMatcher 클래스는 인자로 들어오는 RequestMatcher 객체로 로그아웃 진행 + * LogoutSuccessfulUrl() + * - ExceptionHandlingConfigurer : 에러에 대한 리다이렉트 url 을 설정 + * - OAuth2LoginConfigurer : 소셜 로그인 기능을 활성화 + * + * @param http HttpSecurity + * @throws Exception Exception + */ + @Override + protected void configure(HttpSecurity http) throws Exception { + http + // Rest API이므로 기본설정 안함 - 기본 설정은 비인증시 로그인 폼으로 direct + .httpBasic().disable() + // Rest API 이므로 csrf 보안 불필요 + .csrf().disable() + // jwt token 인증 - 세션은 필요 없어 생성 안함 + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + + .and() + .cors() + .and() + .authorizeRequests() + // GET, POST 요청시 : OPTIONS preflight 요청 - 실제 서버가 살아있는지를 사전에 확인하는 요청 + // Spring에서 OPTIONS에 대한 요청을 막고 있어 OPTIONS 요청이 왔을 때도 오류를 리턴하지 않도록 설정 + .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() + .antMatchers("/**/signup", "/**/login", "/**/swagger-ui.html").permitAll() + .antMatchers(HttpMethod.GET, "/**/users/**").permitAll() + .anyRequest().permitAll() //.hasRole(RoleType.USER.getCode()) + + .and() + .exceptionHandling() + .authenticationEntryPoint(new RestAuthenticationEntryPoint()) + .accessDeniedHandler(tokenAccessDeniedHandler) + +// .and() +// .logout() +// .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) +// .logoutSuccessUrl("/") +// .invalidateHttpSession(true) + + + .and() + .oauth2Login() + .authorizationEndpoint() + .baseUri("/oauth2/authorization") + .authorizationRequestRepository(oAuth2AuthorizationRequestBasedOnCookieRepository()) + + .and() + .userInfoEndpoint() + .userService(customOAuth2UserService) + + .and() + .redirectionEndpoint() + .baseUri("/*/oauth2/code/*") + + .and() + .successHandler(oAuth2AuthenticationSuccessHandler()) + .failureHandler(oAuth2AuthenticationFailureHandler()) + + .and() + // jwt token filter를 id / password 인증 필터 전에 넣는다 + .addFilterBefore(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); + + http.headers().frameOptions().disable(); + } + + /* + * auth 매니저 설정 + * */ + @Override + @Bean(BeanIds.AUTHENTICATION_MANAGER) + protected AuthenticationManager authenticationManager() throws Exception { + return super.authenticationManager(); + } + + /* + * security 설정 시, 사용할 인코더 설정 + * */ + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + /* + * 토큰 필터 설정 + * */ + @Bean + public TokenAuthenticationFilter tokenAuthenticationFilter() { + return new TokenAuthenticationFilter(authTokenProvider); + } + + /* + * 쿠키 기반 인가 Repository + * 인가 응답을 연계 하고 검증할 때 사용. + * */ + @Bean + public OAuth2AuthorizationRequestBasedOnCookieRepository oAuth2AuthorizationRequestBasedOnCookieRepository() { + return new OAuth2AuthorizationRequestBasedOnCookieRepository(); + } + + /* + * Oauth 인증 성공 핸들러 + * */ + @Bean + public OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler() { + return new OAuth2AuthenticationSuccessHandler( + authTokenProvider, + appProperties, + refreshTokenRepository, + oAuth2AuthorizationRequestBasedOnCookieRepository() + ); + } + + /* + * Oauth 인증 실패 핸들러 + * */ + @Bean + public OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler() { + return new OAuth2AuthenticationFailureHandler(oAuth2AuthorizationRequestBasedOnCookieRepository()); + } + + /* + * Cors 설정 + * */ + @Bean + public UrlBasedCorsConfigurationSource corsConfigurationSource() { + UrlBasedCorsConfigurationSource corsConfigSource = new UrlBasedCorsConfigurationSource(); + + CorsConfiguration corsConfig = new CorsConfiguration(); + corsConfig.setAllowedHeaders(Arrays.asList(corsProperties.getAllowedHeaders().split(","))); + corsConfig.setAllowedMethods(Arrays.asList(corsProperties.getAllowedMethods().split(","))); + corsConfig.setAllowedOrigins(Arrays.asList(corsProperties.getAllowedOrigins().split(","))); + corsConfig.setAllowCredentials(true); + corsConfig.setMaxAge(corsConfig.getMaxAge()); + + corsConfigSource.registerCorsConfiguration("/**", corsConfig); + return corsConfigSource; + } +} diff --git a/src/main/java/com/xit/core/oauth2/oauth/entity/ProviderType.java b/src/main/java/com/xit/core/oauth2/oauth/entity/ProviderType.java new file mode 100644 index 0000000..2f8323b --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/entity/ProviderType.java @@ -0,0 +1,26 @@ +package com.xit.core.oauth2.oauth.entity; + +import com.fasterxml.jackson.annotation.JsonCreator; + +public enum ProviderType { + GOOGLE, + FACEBOOK, + NAVER, + KAKAO, + LOCAL; + + /** + * Serialization을 위해 반드시 필요 + * 미정의시 미 매치값에 대해 IllegalArgumentException 에러 발생 - throw HttpMessageNotReadableException + * @param s String + * @return ProviderType + */ + @JsonCreator + public static ProviderType from(String s) { + try{ + return ProviderType.valueOf(s); + }catch (IllegalArgumentException iae){ + return null; + } + } +} diff --git a/src/main/java/com/xit/core/oauth2/oauth/entity/RoleType.java b/src/main/java/com/xit/core/oauth2/oauth/entity/RoleType.java new file mode 100644 index 0000000..3aa2954 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/entity/RoleType.java @@ -0,0 +1,49 @@ +package com.xit.core.oauth2.oauth.entity; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.AllArgsConstructor; + +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toMap; + +@AllArgsConstructor +public enum RoleType { + USER("USER", "일반 사용자 권한"), + ADMIN("ADMIN", "관리자 권한"), + GUEST("GUEST", "게스트 권한"); + + private final String code; + private final String displayName; + + /** + * Serialization을 위해 반드시 필요 + * 미정의시 JSON parser 에러 발생 - throw HttpMessageNotReadableException + * @param symbol String + * @return RoleType + */ + @JsonCreator + public static RoleType fromString(String symbol){ + return stringToEnum.get(symbol); + } + + /** + * Deserialization을 위해 반드시 필요 + * 미정의시 JSON parser 에러 발생 - throw HttpMessageNotReadableException + * @return String + */ + @JsonValue + public String getCode(){ + return code; + } + + public String getDisplayName(){ + return displayName; + } + + private static final Map stringToEnum = Stream.of(values()) + .collect(toMap(Objects::toString, e -> e)); +} diff --git a/src/main/java/com/xit/core/oauth2/oauth/entity/UserPrincipal.java b/src/main/java/com/xit/core/oauth2/oauth/entity/UserPrincipal.java new file mode 100644 index 0000000..7b47319 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/entity/UserPrincipal.java @@ -0,0 +1,103 @@ +package com.xit.core.oauth2.oauth.entity; + +import com.xit.biz.cmm.entity.CmmUser; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.oauth2.core.oidc.OidcIdToken; +import org.springframework.security.oauth2.core.oidc.OidcUserInfo; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.oauth2.core.user.OAuth2User; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +@Getter +@Setter +@AllArgsConstructor +@RequiredArgsConstructor +public class UserPrincipal implements OAuth2User, UserDetails, OidcUser { + private final String userId; + private final String password; + private final ProviderType providerType; + private final RoleType roleType; + private final Collection authorities; + private Map attributes; + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Collection getAuthorities() { + return authorities; + } + + @Override + public String getName() { + return userId; + } + + @Override + public String getUsername() { + return userId; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public Map getClaims() { + return null; + } + + @Override + public OidcUserInfo getUserInfo() { + return null; + } + + @Override + public OidcIdToken getIdToken() { + return null; + } + + public static UserPrincipal create(CmmUser user) { + return new UserPrincipal( + user.getUserId(), + user.getPassword(), + user.getProviderType(), + RoleType.USER, + Collections.singletonList(new SimpleGrantedAuthority(RoleType.USER.getCode())) + ); + } + + public static UserPrincipal create(CmmUser user, Map attributes) { + UserPrincipal userPrincipal = create(user); + userPrincipal.setAttributes(attributes); + + return userPrincipal; + } +} diff --git a/src/main/java/com/xit/core/oauth2/oauth/exception/OAuthProviderMissMatchException.java b/src/main/java/com/xit/core/oauth2/oauth/exception/OAuthProviderMissMatchException.java new file mode 100644 index 0000000..1bd4e4f --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/exception/OAuthProviderMissMatchException.java @@ -0,0 +1,8 @@ +package com.xit.core.oauth2.oauth.exception; + +public class OAuthProviderMissMatchException extends RuntimeException { + + public OAuthProviderMissMatchException(String message) { + super(message); + } +} diff --git a/src/main/java/com/xit/core/oauth2/oauth/exception/RestAuthenticationEntryPoint.java b/src/main/java/com/xit/core/oauth2/oauth/exception/RestAuthenticationEntryPoint.java new file mode 100644 index 0000000..d831bc0 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/exception/RestAuthenticationEntryPoint.java @@ -0,0 +1,27 @@ +package com.xit.core.oauth2.oauth.exception; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Slf4j +public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { + @Override + public void commence( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException authException + ) throws IOException, ServletException { + authException.printStackTrace(); + log.info("Responding with unauthorized error. Message := {}", authException.getLocalizedMessage()); + response.sendError( + HttpServletResponse.SC_UNAUTHORIZED, + authException.getLocalizedMessage() + ); + } +} diff --git a/src/main/java/com/xit/core/oauth2/oauth/exception/TokenValidFailedException.java b/src/main/java/com/xit/core/oauth2/oauth/exception/TokenValidFailedException.java new file mode 100644 index 0000000..084cb3d --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/exception/TokenValidFailedException.java @@ -0,0 +1,12 @@ +package com.xit.core.oauth2.oauth.exception; + +public class TokenValidFailedException extends RuntimeException { + + public TokenValidFailedException() { + super("Failed to generate Token."); + } + + private TokenValidFailedException(String message) { + super(message); + } +} diff --git a/src/main/java/com/xit/core/oauth2/oauth/filter/TokenAuthenticationFilter.java b/src/main/java/com/xit/core/oauth2/oauth/filter/TokenAuthenticationFilter.java new file mode 100644 index 0000000..bb76fa7 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/filter/TokenAuthenticationFilter.java @@ -0,0 +1,41 @@ +package com.xit.core.oauth2.oauth.filter; + +//import com.xit.core.oauth2.oauth.token.JwtToken; +import com.xit.core.oauth2.oauth.token.JwtTokenProvider; +import com.xit.core.oauth2.utils.HeaderUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Slf4j +@RequiredArgsConstructor +public class TokenAuthenticationFilter extends OncePerRequestFilter { + private final JwtTokenProvider tokenProvider; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + + String tokenStr = HeaderUtil.getAccessToken(request); + if(tokenStr != null) { + + //JwtToken token = tokenProvider.convertJwtToken(tokenStr); + + // Token check + if (tokenProvider.validateTokenExcludeExpired(tokenStr, false, false)) { + Authentication authentication = tokenProvider.getAuthentication(tokenStr); + // TODO ::: 여기에서 SecurityContextHoder에 인증정보 저장~~~~ + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } + filterChain.doFilter(request, response); + } + +} diff --git a/src/main/java/com/xit/core/oauth2/oauth/handler/OAuth2AuthenticationFailureHandler.java b/src/main/java/com/xit/core/oauth2/oauth/handler/OAuth2AuthenticationFailureHandler.java new file mode 100644 index 0000000..bb3dfcb --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/handler/OAuth2AuthenticationFailureHandler.java @@ -0,0 +1,42 @@ +package com.xit.core.oauth2.oauth.handler; + +import com.xit.core.oauth2.oauth.repository.OAuth2AuthorizationRequestBasedOnCookieRepository; +import com.xit.core.oauth2.utils.CookieUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +import javax.servlet.ServletException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +import static com.xit.core.oauth2.oauth.repository.OAuth2AuthorizationRequestBasedOnCookieRepository.REDIRECT_URI_PARAM_COOKIE_NAME; + + +@Component +@RequiredArgsConstructor +public class OAuth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { + + private final OAuth2AuthorizationRequestBasedOnCookieRepository authorizationRequestRepository; + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { + String targetUrl = CookieUtil.getCookie(request, REDIRECT_URI_PARAM_COOKIE_NAME) + .map(Cookie::getValue) + .orElse(("/")); + + exception.printStackTrace(); + + targetUrl = UriComponentsBuilder.fromUriString(targetUrl) + .queryParam("error", exception.getLocalizedMessage()) + .build().toUriString(); + + authorizationRequestRepository.removeAuthorizationRequestCookies(request, response); + + getRedirectStrategy().sendRedirect(request, response, targetUrl); + } +} diff --git a/src/main/java/com/xit/core/oauth2/oauth/handler/OAuth2AuthenticationSuccessHandler.java b/src/main/java/com/xit/core/oauth2/oauth/handler/OAuth2AuthenticationSuccessHandler.java new file mode 100644 index 0000000..a1c6bfa --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/handler/OAuth2AuthenticationSuccessHandler.java @@ -0,0 +1,136 @@ +package com.xit.core.oauth2.oauth.handler; + +import com.xit.core.oauth2.api.entity.RefreshToken; +import com.xit.core.oauth2.api.repository.RefreshTokenRepository; +import com.xit.core.constant.XitConstants; +import com.xit.core.oauth2.config.properties.AppProperties; +import com.xit.core.oauth2.oauth.entity.ProviderType; +import com.xit.core.oauth2.oauth.entity.RoleType; +import com.xit.core.oauth2.oauth.info.OAuth2UserInfo; +import com.xit.core.oauth2.oauth.info.OAuth2UserInfoFactory; +import com.xit.core.oauth2.oauth.repository.OAuth2AuthorizationRequestBasedOnCookieRepository; +import com.xit.core.oauth2.oauth.token.JwtTokenProvider; +import com.xit.core.oauth2.utils.CookieUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +import javax.servlet.ServletException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URI; +import java.util.Collection; +import java.util.Objects; +import java.util.Optional; + +import static com.xit.core.oauth2.oauth.repository.OAuth2AuthorizationRequestBasedOnCookieRepository.REDIRECT_URI_PARAM_COOKIE_NAME; + +@Component +@RequiredArgsConstructor +public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + private final JwtTokenProvider tokenProvider; + private final AppProperties appProperties; + private final RefreshTokenRepository refreshTokenRepository; + private final OAuth2AuthorizationRequestBasedOnCookieRepository authorizationRequestRepository; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { + String targetUrl = determineTargetUrl(request, response, authentication); + + if (response.isCommitted()) { + logger.debug("Response has already been committed. Unable to redirect to " + targetUrl); + return; + } + + clearAuthenticationAttributes(request, response); + getRedirectStrategy().sendRedirect(request, response, targetUrl); + } + + protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + Optional redirectUri = CookieUtil.getCookie(request, REDIRECT_URI_PARAM_COOKIE_NAME) + .map(Cookie::getValue); + + if(redirectUri.isPresent() && !isAuthorizedRedirectUri(redirectUri.get())) { + throw new IllegalArgumentException("Sorry! We've got an Unauthorized Redirect URI and can't proceed with the authentication"); + } + + String targetUrl = redirectUri.orElse(getDefaultTargetUrl()); + + OAuth2AuthenticationToken authToken = (OAuth2AuthenticationToken) authentication; + ProviderType providerType = ProviderType.valueOf(authToken.getAuthorizedClientRegistrationId().toUpperCase()); + + OidcUser user = ((OidcUser) authentication.getPrincipal()); + OAuth2UserInfo userInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(providerType, user.getAttributes()); + Collection authorities = ((OidcUser) authentication.getPrincipal()).getAuthorities(); + + RoleType roleType = hasAuthority(authorities, RoleType.ADMIN.getCode()) ? RoleType.ADMIN : RoleType.USER; + + String accessToken = tokenProvider.generateJwtAccessToken(userInfo.getId(), roleType.getCode()); + + // refresh 토큰 설정 + //long refreshTokenExpiry = appProperties.getAuth().getRefreshTokenExpiry(); + + String refreshToken = tokenProvider.generateJwtRefreshToken(userInfo.getId()); + + // DB 저장 + RefreshToken savedRefreshToken = refreshTokenRepository.findByKey(userInfo.getId()).orElse(null); + if (savedRefreshToken != null) { + //userRefreshToken(refreshToken); + savedRefreshToken.updateValue(Objects.requireNonNull(refreshToken)); + } else { + refreshTokenRepository.save( + RefreshToken.builder() + .key(userInfo.getId()) + .value(refreshToken) + .build() + ); + //userRefreshToken = new UserRefreshToken(userInfo.getId(), refreshToken); + //refreshTokenRepository.saveAndFlush(refreshToken); + } + CookieUtil.deleteCookie(request, response, XitConstants.JwtToken.REFRESH_TOKEN_NAME.getValue()); + CookieUtil.addCookie(response, XitConstants.JwtToken.REFRESH_TOKEN_NAME.getValue(), refreshToken, appProperties.getAuth().getRefreshTokenExpiry()); + + return UriComponentsBuilder.fromUriString(targetUrl) + .queryParam("token", accessToken) + .build().toUriString(); + } + + protected void clearAuthenticationAttributes(HttpServletRequest request, HttpServletResponse response) { + super.clearAuthenticationAttributes(request); + authorizationRequestRepository.removeAuthorizationRequestCookies(request, response); + } + + private boolean hasAuthority(Collection authorities, String authority) { + if (authorities == null) { + return false; + } + + for (GrantedAuthority grantedAuthority : authorities) { + if (Objects.equals(authority, grantedAuthority.getAuthority())) { + return true; + } + } + return false; + } + + private boolean isAuthorizedRedirectUri(String uri) { + URI clientRedirectUri = URI.create(uri); + + return appProperties.getOauth2().getAuthorizedRedirectUris() + .stream() + .anyMatch(authorizedRedirectUri -> { + // Only validate host and port. Let the clients use different paths if they want to + URI authorizedURI = URI.create(authorizedRedirectUri); + if (authorizedURI.getHost().equalsIgnoreCase(clientRedirectUri.getHost())) + return authorizedURI.getPort() == clientRedirectUri.getPort(); + return false; + }); + } +} diff --git a/src/main/java/com/xit/core/oauth2/oauth/handler/TokenAccessDeniedHandler.java b/src/main/java/com/xit/core/oauth2/oauth/handler/TokenAccessDeniedHandler.java new file mode 100644 index 0000000..8d33062 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/handler/TokenAccessDeniedHandler.java @@ -0,0 +1,24 @@ +package com.xit.core.oauth2.oauth.handler; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerExceptionResolver; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +@RequiredArgsConstructor +public class TokenAccessDeniedHandler implements AccessDeniedHandler { + + private final HandlerExceptionResolver handlerExceptionResolver; + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException { + //response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedExceptiongetLocalizedMessage()); + handlerExceptionResolver.resolveException(request, response, null, accessDeniedException); + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/oauth2/oauth/info/OAuth2UserInfo.java b/src/main/java/com/xit/core/oauth2/oauth/info/OAuth2UserInfo.java new file mode 100644 index 0000000..1ac0d39 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/info/OAuth2UserInfo.java @@ -0,0 +1,23 @@ +package com.xit.core.oauth2.oauth.info; + +import java.util.Map; + +public abstract class OAuth2UserInfo { + protected Map attributes; + + public OAuth2UserInfo(Map attributes) { + this.attributes = attributes; + } + + public Map getAttributes() { + return attributes; + } + + public abstract String getId(); + + public abstract String getName(); + + public abstract String getEmail(); + + public abstract String getImageUrl(); +} diff --git a/src/main/java/com/xit/core/oauth2/oauth/info/OAuth2UserInfoFactory.java b/src/main/java/com/xit/core/oauth2/oauth/info/OAuth2UserInfoFactory.java new file mode 100644 index 0000000..772e03b --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/info/OAuth2UserInfoFactory.java @@ -0,0 +1,21 @@ +package com.xit.core.oauth2.oauth.info; + +import com.xit.core.oauth2.oauth.entity.ProviderType; +import com.xit.core.oauth2.oauth.info.impl.FacebookOAuth2UserInfo; +import com.xit.core.oauth2.oauth.info.impl.GoogleOAuth2UserInfo; +import com.xit.core.oauth2.oauth.info.impl.KakaoOAuth2UserInfo; +import com.xit.core.oauth2.oauth.info.impl.NaverOAuth2UserInfo; + +import java.util.Map; + +public class OAuth2UserInfoFactory { + public static OAuth2UserInfo getOAuth2UserInfo(ProviderType providerType, Map attributes) { + switch (providerType) { + case GOOGLE: return new GoogleOAuth2UserInfo(attributes); + case FACEBOOK: return new FacebookOAuth2UserInfo(attributes); + case NAVER: return new NaverOAuth2UserInfo(attributes); + case KAKAO: return new KakaoOAuth2UserInfo(attributes); + default: throw new IllegalArgumentException("Invalid Provider Type."); + } + } +} diff --git a/src/main/java/com/xit/core/oauth2/oauth/info/impl/FacebookOAuth2UserInfo.java b/src/main/java/com/xit/core/oauth2/oauth/info/impl/FacebookOAuth2UserInfo.java new file mode 100644 index 0000000..19a5237 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/info/impl/FacebookOAuth2UserInfo.java @@ -0,0 +1,47 @@ +package com.xit.core.oauth2.oauth.info.impl; + +import com.xit.core.oauth2.oauth.info.OAuth2UserInfo; + +import java.util.Map; + +public class FacebookOAuth2UserInfo extends OAuth2UserInfo { + public FacebookOAuth2UserInfo(Map attributes) { + super(attributes); + } + + @Override + public String getId() { + return (String) attributes.get("id"); + } + + @Override + public String getName() { + return (String) attributes.get("name"); + } + + @Override + public String getEmail() { + return (String) attributes.get("email"); + } + + @Override + public String getImageUrl() { + return (String) attributes.get("imageUrl"); + } + + /* + @Override + public String getImageUrl() { + if(attributes.containsKey("picture")) { + Map pictureObj = (Map) attributes.get("picture"); + if(pictureObj.containsKey("data")) { + Map dataObj = (Map) pictureObj.get("data"); + if(dataObj.containsKey("url")) { + return (String) dataObj.get("url"); + } + } + } + return null; + } + */ +} diff --git a/src/main/java/com/xit/core/oauth2/oauth/info/impl/GoogleOAuth2UserInfo.java b/src/main/java/com/xit/core/oauth2/oauth/info/impl/GoogleOAuth2UserInfo.java new file mode 100644 index 0000000..f0849d4 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/info/impl/GoogleOAuth2UserInfo.java @@ -0,0 +1,32 @@ +package com.xit.core.oauth2.oauth.info.impl; + +import com.xit.core.oauth2.oauth.info.OAuth2UserInfo; + +import java.util.Map; + +public class GoogleOAuth2UserInfo extends OAuth2UserInfo { + + public GoogleOAuth2UserInfo(Map attributes) { + super(attributes); + } + + @Override + public String getId() { + return (String) attributes.get("sub"); + } + + @Override + public String getName() { + return (String) attributes.get("name"); + } + + @Override + public String getEmail() { + return (String) attributes.get("email"); + } + + @Override + public String getImageUrl() { + return (String) attributes.get("picture"); + } +} diff --git a/src/main/java/com/xit/core/oauth2/oauth/info/impl/KakaoOAuth2UserInfo.java b/src/main/java/com/xit/core/oauth2/oauth/info/impl/KakaoOAuth2UserInfo.java new file mode 100644 index 0000000..590808e --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/info/impl/KakaoOAuth2UserInfo.java @@ -0,0 +1,44 @@ +package com.xit.core.oauth2.oauth.info.impl; + +import com.xit.core.oauth2.oauth.info.OAuth2UserInfo; + +import java.util.Map; + +public class KakaoOAuth2UserInfo extends OAuth2UserInfo { + + public KakaoOAuth2UserInfo(Map attributes) { + super(attributes); + } + + @Override + public String getId() { + return attributes.get("id").toString(); + } + + @Override + public String getName() { + Map properties = (Map) attributes.get("properties"); + + if (properties == null) { + return null; + } + + return (String) properties.get("nickname"); + } + + @Override + public String getEmail() { + return (String) attributes.get("account_email"); + } + + @Override + public String getImageUrl() { + Map properties = (Map) attributes.get("properties"); + + if (properties == null) { + return null; + } + + return (String) properties.get("thumbnail_image"); + } +} diff --git a/src/main/java/com/xit/core/oauth2/oauth/info/impl/NaverOAuth2UserInfo.java b/src/main/java/com/xit/core/oauth2/oauth/info/impl/NaverOAuth2UserInfo.java new file mode 100644 index 0000000..e929fd0 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/info/impl/NaverOAuth2UserInfo.java @@ -0,0 +1,56 @@ +package com.xit.core.oauth2.oauth.info.impl; + +import com.xit.core.oauth2.oauth.info.OAuth2UserInfo; + +import java.util.Map; + +public class NaverOAuth2UserInfo extends OAuth2UserInfo { + + public NaverOAuth2UserInfo(Map attributes) { + super(attributes); + } + + @Override + public String getId() { + Map response = (Map) attributes.get("response"); + + if (response == null) { + return null; + } + + return (String) response.get("id"); + } + + @Override + public String getName() { + Map response = (Map) attributes.get("response"); + + if (response == null) { + return null; + } + + return (String) response.get("nickname"); + } + + @Override + public String getEmail() { + Map response = (Map) attributes.get("response"); + + if (response == null) { + return null; + } + + return (String) response.get("email"); + } + + @Override + public String getImageUrl() { + Map response = (Map) attributes.get("response"); + + if (response == null) { + return null; + } + + return (String) response.get("profile_image"); + } +} diff --git a/src/main/java/com/xit/core/oauth2/oauth/interceptor/AuthInterceptor.java b/src/main/java/com/xit/core/oauth2/oauth/interceptor/AuthInterceptor.java new file mode 100644 index 0000000..40acb5e --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/interceptor/AuthInterceptor.java @@ -0,0 +1,120 @@ +package com.xit.core.oauth2.oauth.interceptor; + +import com.xit.core.annotation.Secured; +import com.xit.core.annotation.SecurityPolicy; +import com.xit.core.constant.ErrorCode; +import com.xit.core.constant.XitConstants; +import com.xit.core.exception.CustomBaseException; +import com.xit.core.exception.TokenAuthException; +import com.xit.core.util.Checks; +import com.xit.core.util.SpringUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.AsyncHandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Objects; + +//TODO : 재개발 +@Slf4j +public class AuthInterceptor implements AsyncHandlerInterceptor {//AsyncHandlerInterceptor { //extends HandlerInterceptorAdapter{ // HandlerInterceptor{ + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{ + //log.debug("OpenApiTokenInterceptor start ==>> "); + + SecurityPolicy policy = null; + + if(handler instanceof HandlerMethod){ + policy = SecurityPolicy.DEFAULT; + + HandlerMethod method = (HandlerMethod)handler; + Secured secured = method.getMethod().getAnnotation(Secured.class); + + if(secured != null){ + policy = secured.policy(); + }else{ + secured = method.getBeanType().getAnnotation(Secured.class); + if(secured != null) + policy = secured.policy(); + } + } + + if(Checks.isNotEmpty(policy)){ + + // 토큰 인증 + if (Objects.equals(SecurityPolicy.TOKEN, policy)) { + log.debug("TOKEN 인증 start ==>> "); + + String tokenString = request.getHeader(XitConstants.JwtToken.HEADER_NAME.getValue()); + + if(Checks.isNotEmpty(tokenString)){ + + try{ + tokenString = tokenString.substring(XitConstants.JwtToken.GRANT_TYPE.getValue().length()+1); + if(SpringUtils.getJwtTokenProvider().validateTokenExcludeExpired(tokenString, false, true)){ + log.debug("<<==== 토큰인증성공"); + return true; + } + }catch(CustomBaseException cbe){ + //TODO Refresh토큰 사용시 자동 재발급하는 경우 주석 제거 + // access token expired >> refresh 토큰 확인후 access 토큰 재발급 +// if(Objects.equals(ErrorConst.TOKEN_EXPIRED, be.getErrorCode())){ +// //TODO refresh 토큰유효기간 확인 +// // 1.GET refresh 토큰 +// +// // 2.refresh 토큰 유효성 검사 +// if(tokenService.isValidToken("", tokenString)){ +// tokenService.getRefreshToken(tokenString, response); +// return true; +// +// }else{ +// throw new BaseException("swork.token.expired", new String[]{}, ErrorConst.TOKEN_EXPIRED); +// } +// } + log.error("====토큰인증실패 :: {} ====", cbe.getLocalizedMessage()); + throw cbe; + } + }else{ + log.error("==== 토큰인증실패 :: 요청 헤더에 토큰이 존재하지 않습니다. ===="); + throw new TokenAuthException(ErrorCode.AUTH_HEADER_NOT_EXISTS); + } + + // 세션 인증 (cookie 테스트를 위한 redirect 설정) + }else if (Objects.equals(SecurityPolicy.SESSION, policy)) { + + return true; + // 쿠키 인증 데이터 생성 + }else if (Objects.equals(SecurityPolicy.COOKIE, policy)) { + + return true; + } + }else{ + //log.debug("<<== OpenApiTokenInterceptor end"); + return true; + } + + // TODO : 인증이 필요없는 Page?? 로 처리할지 에러(401:인가되지 않음)로 표시할지 결정 + // 접근불가로 처리하려면 주석처리 + if(Objects.equals(SecurityPolicy.DEFAULT, policy)){ + //log.debug("===================== 인증이 필요없는 페이지 ==========================="); + return true; + } + // 401 : 인가되지 않음으로 에러 처리 + log.error("====인가되지 않음===="); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return false; + } + + @Override + public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception{ + + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) throws Exception{ + + } +} diff --git a/src/main/java/com/xit/core/oauth2/oauth/repository/OAuth2AuthorizationRequestBasedOnCookieRepository.java b/src/main/java/com/xit/core/oauth2/oauth/repository/OAuth2AuthorizationRequestBasedOnCookieRepository.java new file mode 100644 index 0000000..b77b902 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/repository/OAuth2AuthorizationRequestBasedOnCookieRepository.java @@ -0,0 +1,56 @@ +package com.xit.core.oauth2.oauth.repository; + +import com.xit.core.constant.XitConstants; +import com.xit.core.oauth2.utils.CookieUtil; +import com.nimbusds.oauth2.sdk.util.StringUtils; +import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class OAuth2AuthorizationRequestBasedOnCookieRepository implements AuthorizationRequestRepository { + + public final static String OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME = "oauth2_auth_request"; + public final static String REDIRECT_URI_PARAM_COOKIE_NAME = "redirect_uri"; + private final static int cookieExpireSeconds = 180; + + @Override + public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) { + return CookieUtil.getCookie(request, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME) + .map(cookie -> CookieUtil.deserialize(cookie, OAuth2AuthorizationRequest.class)) + .orElse(null); + } + + @Override + public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) { + if (authorizationRequest == null) { + CookieUtil.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME); + CookieUtil.deleteCookie(request, response, REDIRECT_URI_PARAM_COOKIE_NAME); + CookieUtil.deleteCookie(request, response, XitConstants.JwtToken.REFRESH_TOKEN_NAME.getValue()); + return; + } + + CookieUtil.addCookie(response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, CookieUtil.serialize(authorizationRequest), cookieExpireSeconds); + String redirectUriAfterLogin = request.getParameter(REDIRECT_URI_PARAM_COOKIE_NAME); + if (StringUtils.isNotBlank(redirectUriAfterLogin)) { + CookieUtil.addCookie(response, REDIRECT_URI_PARAM_COOKIE_NAME, redirectUriAfterLogin, cookieExpireSeconds); + } + } + + @Override + public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request) { + return this.loadAuthorizationRequest(request); + } + + @Override + public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request, HttpServletResponse response) { + return this.loadAuthorizationRequest(request); + } + + public void removeAuthorizationRequestCookies(HttpServletRequest request, HttpServletResponse response) { + CookieUtil.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME); + CookieUtil.deleteCookie(request, response, REDIRECT_URI_PARAM_COOKIE_NAME); + CookieUtil.deleteCookie(request, response, XitConstants.JwtToken.REFRESH_TOKEN_NAME.getValue()); + } +} diff --git a/src/main/java/com/xit/core/oauth2/oauth/service/CustomOAuth2UserService.java b/src/main/java/com/xit/core/oauth2/oauth/service/CustomOAuth2UserService.java new file mode 100644 index 0000000..048d8ef --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/service/CustomOAuth2UserService.java @@ -0,0 +1,111 @@ +package com.xit.core.oauth2.oauth.service; + +import com.xit.biz.cmm.entity.CmmUser; +import com.xit.biz.cmm.repository.ICmmUserRepository; +import com.xit.core.oauth2.oauth.entity.ProviderType; +import com.xit.core.oauth2.oauth.entity.RoleType; +import com.xit.core.oauth2.oauth.entity.UserPrincipal; +import com.xit.core.oauth2.oauth.exception.OAuthProviderMissMatchException; +import com.xit.core.oauth2.oauth.info.OAuth2UserInfo; +import com.xit.core.oauth2.oauth.info.OAuth2UserInfoFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.InternalAuthenticationServiceException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class CustomOAuth2UserService extends DefaultOAuth2UserService { + + private final ICmmUserRepository userRepository; + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2User user = super.loadUser(userRequest); + + try { + return this.process(userRequest, user); + } catch (AuthenticationException ex) { + throw ex; + } catch (Exception ex) { + ex.printStackTrace(); + throw new InternalAuthenticationServiceException(ex.getLocalizedMessage(), ex.getCause()); + } + } + + private OAuth2User process(OAuth2UserRequest userRequest, OAuth2User user) { + // TODO :: 적용 여부 파악 필요 + ProviderType providerType = ProviderType.valueOf(userRequest.getClientRegistration().getRegistrationId().toUpperCase()); + + OAuth2UserInfo userInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(providerType, user.getAttributes()); + CmmUser savedUser = null; + Optional optionalCmmUser = userRepository.findByUserId(userInfo.getId()); + + if(optionalCmmUser.isPresent()) { + savedUser = optionalCmmUser.get(); + if (Objects.equals(providerType, savedUser.getProviderType())) { + throw new OAuthProviderMissMatchException( + "Looks like you're signed up with " + providerType + + " account. Please use your " + savedUser.getProviderType() + " account to login." + ); + } + updateUser(savedUser, userInfo); + + }else{ + savedUser = createUser(userInfo, providerType); + } + +// if (optionalCmmUser.isEmpty()) { +// if (providerType != savedUser.getProviderType()) { +// throw new OAuthProviderMissMatchException( +// "Looks like you're signed up with " + providerType + +// " account. Please use your " + savedUser.getProviderType() + " account to login." +// ); +// } +// updateUser(savedUser, userInfo); +// } else { +// savedUser = createUser(userInfo, providerType); +// } + + return UserPrincipal.create(savedUser, user.getAttributes()); + } + + private CmmUser createUser(OAuth2UserInfo userInfo, ProviderType providerType) { + CmmUser user = new CmmUser( + userInfo.getId(), + userInfo.getName(), + userInfo.getEmail(), + "Y", + userInfo.getImageUrl(), + providerType, + RoleType.USER + ); + LocalDateTime localDateTime = LocalDateTime.now(); + user.setCmmUserId(UUID.randomUUID().toString().replaceAll("-", "")); + user.setCreatedBy(userInfo.getId()); + user.setModifiedBy(userInfo.getId()); + user.setCreatedDate(localDateTime); + user.setModifiedDate(localDateTime); + + return userRepository.saveAndFlush(user); + } + + private void updateUser(CmmUser user, OAuth2UserInfo userInfo) { + if (Objects.nonNull(userInfo.getName()) && !Objects.equals(userInfo.getName(), user.getUserName())) { + user.setUserName(userInfo.getName()); + } + + if (Objects.nonNull(userInfo.getImageUrl()) && !Objects.equals(userInfo.getImageUrl(), user.getProfileImageUrl())) { + user.setProfileImageUrl(userInfo.getImageUrl()); + } + } +} diff --git a/src/main/java/com/xit/core/oauth2/oauth/service/CustomUserDetailsService.java b/src/main/java/com/xit/core/oauth2/oauth/service/CustomUserDetailsService.java new file mode 100644 index 0000000..0922029 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/service/CustomUserDetailsService.java @@ -0,0 +1,25 @@ +package com.xit.core.oauth2.oauth.service; + +import com.xit.biz.cmm.entity.CmmUser; +import com.xit.biz.cmm.repository.ICmmUserRepository; +import com.xit.core.constant.ErrorCode; +import com.xit.core.exception.CustomBaseException; +import com.xit.core.oauth2.oauth.entity.UserPrincipal; +import lombok.RequiredArgsConstructor; +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.Service; + +@Service +@RequiredArgsConstructor +public class CustomUserDetailsService implements UserDetailsService { + + private final ICmmUserRepository cmmUserRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + CmmUser user = cmmUserRepository.findByUserId(username).orElseThrow(() -> new CustomBaseException(ErrorCode.USER_NOT_FOUND)); + return UserPrincipal.create(user); + } +} diff --git a/src/main/java/com/xit/core/oauth2/oauth/token/JwtToken.java b/src/main/java/com/xit/core/oauth2/oauth/token/JwtToken.java new file mode 100644 index 0000000..f59afe5 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/token/JwtToken.java @@ -0,0 +1,92 @@ +//package com.xit.core.oauth2.oauth.token; +// +//import com.xit.core.constant.XitConstants; +//import io.jsonwebtoken.*; +//import lombok.Getter; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +// +//import java.security.Key; +//import java.util.Date; +// +///** +// * Token +// * SignatureAlgorithm.HS256 +// * +// * +// * @see JwtTokenProvider +// */ +//@Slf4j +//@RequiredArgsConstructor +//public class JwtToken { +// +// @Getter +// private final String token; +// private final Key key; +// +// JwtToken(String id, Date expiry, Key key) { +// this.key = key; +// this.token = generateJwtRefreshToken(id, expiry); +// } +// +// JwtToken(String id, String role, Date expiry, Key key) { +// this.key = key; +// this.token = generateJwtAccessToken(id, role, expiry); +// } +// +// private String generateJwtRefreshToken(String id, Date expiry) { +// return Jwts.builder() +// .setSubject(id) +// .signWith(key, SignatureAlgorithm.HS256) +// .setExpiration(expiry) +// .compact(); +// } +// +// private String generateJwtAccessToken(String id, String role, Date expiry) { +// return Jwts.builder() +// .claim(XitConstants.JwtToken.AUTHORITIES_KEY.getValue(), role) +// .setSubject(id) +// .signWith(key, SignatureAlgorithm.HS256) +// .setExpiration(expiry) +// .compact(); +// } +// +// public boolean validate() { +// return this.getTokenClaims() != null; +// } +// +// public Claims getTokenClaims() { +// try { +// return Jwts.parserBuilder() +// .setSigningKey(key) +// .build() +// .parseClaimsJws(token) +// .getBody(); +// } catch (SecurityException e) { +// log.info("Invalid JWT signature."); +// } catch (MalformedJwtException e) { +// log.info("Invalid JWT token."); +// } catch (ExpiredJwtException e) { +// log.info("Expired JWT token."); +// } catch (UnsupportedJwtException e) { +// log.info("Unsupported JWT token."); +// } catch (IllegalArgumentException e) { +// log.info("JWT token compact of handler are invalid."); +// } +// return null; +// } +// +// public Claims getExpiredTokenClaims() { +// try { +// Jwts.parserBuilder() +// .setSigningKey(key) +// .build() +// .parseClaimsJws(token) +// .getBody(); +// } catch (ExpiredJwtException e) { +// log.info("Expired JWT token."); +// return e.getClaims(); +// } +// return null; +// } +//} diff --git a/src/main/java/com/xit/core/oauth2/oauth/token/JwtTokenProvider.java b/src/main/java/com/xit/core/oauth2/oauth/token/JwtTokenProvider.java new file mode 100644 index 0000000..c0b68ca --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/oauth/token/JwtTokenProvider.java @@ -0,0 +1,358 @@ +package com.xit.core.oauth2.oauth.token; + +import com.xit.core.constant.ErrorCode; +import com.xit.core.constant.XitConstants; +import com.xit.core.exception.TokenAuthException; +import com.xit.core.oauth2.api.dto.TokenDto; +import com.xit.core.oauth2.config.properties.AppProperties; +import com.xit.core.util.Checks; +import com.xit.core.util.DateUtil; +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Token Auth Provider + * + * + */ +@Slf4j +@Component +public class JwtTokenProvider { + + private final Key key; + private final AppProperties appProperties; + + private static final long EXPIRE_CONVERT_MIN_TIME = 1000 * 60; // 분 + private static final long EXPIRE_CONVERT_DAY_TIME = 1000 * 60 * 60 * 24; + + /** + * + * @param secret String + */ + public JwtTokenProvider(@Value("${jwt.secret}")String secret, AppProperties appProperties) { + this.key = Keys.hmacShaKeyFor(secret.getBytes()); + //byte[] keyBytes = Decoders.BASE64.decode(secret); + //this.key = Keys.hmacShaKeyFor(keyBytes); + this.appProperties = appProperties; + } + +// public JwtToken generateJwtRefreshToken(String id, Date expiry) { +// return new JwtToken(id, expiry, key); +// } +// +// /** +// * +// * @param id String +// * @param role String +// * @param expiry Date +// * @return AuthToken +// */ +// public JwtToken generateJwtAccessToken(String id, String role, Date expiry) { +// return new JwtToken(id, role, expiry, key); +// } + + + /** + * + * @param authentication Authentication + * @return String + */ + public String generateJwtAccessToken(Authentication authentication) { + return getJwtAccessToken(authentication, null); + } + + /** + * + * @param authentication Authentication + * @return String + */ + public String generateJwtAccessToken(Authentication authentication, Map infoMap) { + return getJwtAccessToken(authentication, infoMap); + } + + public String generateJwtAccessToken(String userId, String authorities) { + return getJwtAccessToken(userId, authorities, null); + } + + /** + * + * @param authentication Authentication + * @return String + */ + public String generateJwtRefreshToken(Authentication authentication) { + return getJwtRefreshToken(authentication.getName()); + } + + public String generateJwtRefreshToken(String userId) { + return getJwtRefreshToken(userId); + } + +// /** +// * +// * @param token String +// * @return AuthToken +// */ +// public JwtToken convertJwtToken(String token) { +// return new JwtToken(token, key); +// } + + +// public Authentication getAuthentication(JwtToken authToken) { +// +// if(authToken.validate()) { +// +// Claims claims = authToken.getTokenClaims(); +// Collection authorities = +// Arrays.stream(new String[]{claims.get(XitConstants.JwtToken.AUTHORITIES_KEY.getValue()).toString()}) +// .map(SimpleGrantedAuthority::new) +// .collect(Collectors.toList()); +// +// log.debug("claims subject := [{}]", claims.getSubject()); +// User principal = new User(claims.getSubject(), "", authorities); +// +// return new UsernamePasswordAuthenticationToken(principal, authToken, authorities); +// } else { +// throw new TokenAuthException(ErrorCode.INVALID_SIGN_TOKEN); //TokenValidFailedException(); +// } +// } + + /** + * token 정보 GET + * SecurityContext에 인증 정보를 저장하기위해 Authentication 객체로 return + * Access token만 사용자 정보를 담고 있어 Access token만 파라메터로 받는다 + *

+ * 1. JWT 토큰을 복호화 + * 3. Authentication GET + * 4. UsernamePasswordAuthenticationToken(Authentication) return + * + * @param accessToken String + * @return Authentication + */ + public Authentication getAuthentication(String accessToken) { + // 토큰 복호화 : 만료된 토큰이어도 정보를 꺼내기 위해 메소드 분리 + Claims claims = parseClaims(accessToken); + + if (Checks.isEmpty(claims.getSubject())) { + throw new TokenAuthException(ErrorCode.INVALID_TOKEN); + } + + if (claims.get(XitConstants.JwtToken.AUTHORITIES_KEY.getValue()) == null) { + throw new TokenAuthException(ErrorCode.INVALID_ROLE_TOKEN); + } + + // 클레임에서 권한 정보 가져오기 + Collection authorities = + Arrays.stream(claims.get(XitConstants.JwtToken.AUTHORITIES_KEY.getValue()).toString().split(",")) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + + // UserDetails 객체를 만들어서 Authentication 리턴 + UserDetails principal = new User(claims.getSubject(), "", authorities); + + return new UsernamePasswordAuthenticationToken(principal, "", authorities); + } + + /** + * 토큰 유효기간만 검증 + * validateTokenExcludeExpired 로 검증한 토큰에 한해서 체크 해야함 + * @param token + * @return + */ + public boolean isExpiredToken(String token) { + try { + Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); + } catch (ExpiredJwtException e) { + log.info("Expired JWT token."); + //return e.getClaims(); + return true; + //} catch (Exception e){ + // return false; + } + return false; + } + + + /** + * 토큰 정보 검증 - 토큰 만료일은 검증에서 제외 + * 에러발생시 Jwts 모듈이 알아서 Exception throws + * isExceptionThrow = false 인 경우 오염된 토큰인지 체크됨 + * + * @param token String + * @param isCheckRefresh refresh token인 경우 true + * @param isExceptionThrow 검증 오류시 Exception을 throw 할 경우 true + * @return boolean + */ + public boolean validateTokenExcludeExpired(String token, boolean isCheckRefresh, boolean isExceptionThrow) { + try { + Jws claimsJws = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); + if (!isCheckRefresh && Checks.isEmpty(claimsJws.getBody().getSubject())) { + if (isExceptionThrow) throw new TokenAuthException(ErrorCode.INVALID_TOKEN); + return false; + } + return true; + } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { + log.info("잘못된 JWT 서명입니다."); + if (isExceptionThrow) throw new TokenAuthException(ErrorCode.INVALID_SIGN_TOKEN); + } catch (ExpiredJwtException e) { + log.info("만료된 JWT 토큰입니다."); + return true; + //if (isExceptionThrow) throw new JwtTokenExpiredException(ErrorCode.EXPIRED_TOKEN); + } catch (UnsupportedJwtException e) { + log.info("지원되지 않는 JWT 토큰입니다."); + if (isExceptionThrow) throw new TokenAuthException(ErrorCode.INVALID_TOKEN); + } catch (IllegalArgumentException e) { + log.info("JWT 토큰이 잘못되었습니다."); + if (isExceptionThrow) throw new TokenAuthException(ErrorCode.INVALID_TOKEN); + } + return false; + } + + /** + * authentication 정보로 Access token과 Refresh token 생성 + * Access token에는 사용자와 권한 정보를 담고, Refresh token에는 만료일자만 담는다 + * + * @param authentication Authentication + * @return TokenDto + */ + public TokenDto generateTokenDto(Authentication authentication, Map info) { + // Access Token 생성 + String accessToken = getJwtAccessToken(authentication, info); + + // Refresh Token 생성 + String refreshToken = getJwtRefreshToken(authentication.getName()); + + return TokenDto.builder() + .grantType(XitConstants.JwtToken.GRANT_TYPE.getValue()) + .accessToken(accessToken) + .refreshToken(refreshToken) + .build(); + } + + public Map getAccessTokenInfo(String accessToken) { + Map map = new LinkedHashMap<>(); + + // 토큰 복호화 : 만료된 토큰이어도 정보를 꺼내기 위해 메소드 분리 + Claims claims = parseClaims(accessToken); + + Collection authorities = null; + + if (claims.get(XitConstants.JwtToken.AUTHORITIES_KEY.getValue()) != null) { + // 클레임에서 권한 정보 가져오기 + authorities = + Arrays.stream(claims.get(XitConstants.JwtToken.AUTHORITIES_KEY.getValue()).toString().split(",")) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + } + + if (authorities != null) { + //map.put("header", claims) + map.put("audience", claims.getAudience()); + map.put("userId", claims.getSubject()); + map.put("userName", claims.get("userName")); + map.put("userEmail", claims.get("userEmail")); + map.put(XitConstants.JwtToken.AUTHORITIES_KEY.getValue(), authorities); + map.put("Issuer", claims.getIssuer()); + map.put("IssuedAt", DateUtil.getFormatedDT(claims.getIssuedAt(), "yyyy-MM-dd HH:mm:ss")); + } else { + map.put("Refresh token", "Refresh token 입니다."); + } + map.put("expiration", DateUtil.getFormatedDT(claims.getExpiration(), "yyyy-MM-dd HH:mm:ss")); + + return map; + } + + + private String getJwtAccessToken(Authentication authentication, Map info) { + // 권한들 가져오기 + String authorities = authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .collect(Collectors.joining(",")); + + return getJwtAccessToken(authentication.getName(), authorities, info); + } + + private String getJwtAccessToken(String userId, String authorities, Map info) { + AppProperties.Auth auth = appProperties.getAuth(); + long now = (new Date()).getTime(); + Date accessTokenExpiresIn = new Date(now + (auth.getTokenExpiry() * EXPIRE_CONVERT_MIN_TIME)); + + Map header = new HashMap<>(); + header.put("typ", auth.getTyp()); + header.put("alg", auth.getAlg()); + + // Access Token 생성 + if(Checks.isNotEmpty(info)) { + return Jwts.builder() + .setHeader(header) + .setIssuer(auth.getIssure()) + .setAudience(auth.getAudience()) + .setSubject(userId) // payload "sub": "name" - ID + .claim(XitConstants.JwtToken.AUTHORITIES_KEY.getValue(), authorities) // payload "auth": "ROLE_USER" + .claim("userName", info.get("userName")) + .claim("userEmail", info.get("userEmail")) + .setIssuedAt(new Date()) + .signWith(key, Objects.equals("HS512", auth.getAlg())? SignatureAlgorithm.HS512 : SignatureAlgorithm.HS256) // header "alg": "HS512" + .setExpiration(accessTokenExpiresIn) // payload "exp": 1516239022 (예시) + .compact(); + }else{ + return Jwts.builder() + .setHeader(header) + .setIssuer(auth.getIssure()) + .setAudience(auth.getAudience()) + .setSubject(userId) // payload "sub": "name" - ID + .claim(XitConstants.JwtToken.AUTHORITIES_KEY.getValue(), authorities) // payload "auth": "ROLE_USER" + .setIssuedAt(new Date()) + .signWith(key, Objects.equals("HS512", auth.getAlg())? SignatureAlgorithm.HS512 : SignatureAlgorithm.HS256) // header "alg": "HS512" + .setExpiration(accessTokenExpiresIn) // payload "exp": 1516239022 (예시) + .compact(); + } + } + + private String getJwtRefreshToken(String userId) { + long now = (new Date()).getTime(); + + // Refresh Token 생성 + return Jwts.builder() + .setSubject(userId) + .signWith(key, Objects.equals("HS512", appProperties.getAuth().getAlg())? SignatureAlgorithm.HS512 : SignatureAlgorithm.HS256) + .setExpiration(new Date(now + (appProperties.getAuth().getRefreshTokenExpiry() * EXPIRE_CONVERT_DAY_TIME))) + .compact(); + } + +// public boolean validate(String accessToken) { +// return parseClaims(accessToken) != null; +// } + + public Claims parseClaims(String token) { + try { + return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody(); + } catch (ExpiredJwtException e) { + return e.getClaims(); + } + } + + public static void main(String[] args) { + + String tgt = "spring-boot-security-jwt-xit-core-javaframework-java-token-key"; + + String encodedString = Base64.getEncoder().withoutPadding().encodeToString(tgt.getBytes()); + log.debug("\ntarget bit={}\nOriginal={}\nBase64 Encoding={}\nBase64 Decoding={}", tgt.length()*8, tgt, encodedString, new String(Base64.getDecoder().decode(encodedString))); + } +} diff --git a/src/main/java/com/xit/core/oauth2/utils/CookieUtil.java b/src/main/java/com/xit/core/oauth2/utils/CookieUtil.java new file mode 100644 index 0000000..f0f9210 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/utils/CookieUtil.java @@ -0,0 +1,64 @@ +package com.xit.core.oauth2.utils; + +import org.springframework.util.SerializationUtils; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Base64; +import java.util.Objects; +import java.util.Optional; + +public class CookieUtil { + + public static Optional getCookie(HttpServletRequest request, String name) { + Cookie[] cookies = request.getCookies(); + + if (cookies != null && cookies.length > 0) { + for (Cookie cookie : cookies) { + if (Objects.equals(name, cookie.getName())) { + return Optional.of(cookie); + } + } + } + return Optional.empty(); + } + + public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) { + Cookie cookie = new Cookie(name, value); + cookie.setPath("/"); + cookie.setHttpOnly(true); + cookie.setMaxAge(maxAge); + + response.addCookie(cookie); + } + + public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String name) { + Cookie[] cookies = request.getCookies(); + + if (cookies != null && cookies.length > 0) { + for (Cookie cookie : cookies) { + if (Objects.equals(name, cookie.getName())) { + cookie.setValue(""); + cookie.setPath("/"); + cookie.setMaxAge(0); + response.addCookie(cookie); + } + } + } + } + + public static String serialize(Object obj) { + return Base64.getUrlEncoder() + .encodeToString(SerializationUtils.serialize(obj)); + } + + public static T deserialize(Cookie cookie, Class cls) { + return cls.cast( + SerializationUtils.deserialize( + Base64.getUrlDecoder().decode(cookie.getValue()) + ) + ); + } + +} diff --git a/src/main/java/com/xit/core/oauth2/utils/HeaderUtil.java b/src/main/java/com/xit/core/oauth2/utils/HeaderUtil.java new file mode 100644 index 0000000..0adf6f7 --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/utils/HeaderUtil.java @@ -0,0 +1,66 @@ +package com.xit.core.oauth2.utils; + +import com.xit.core.constant.XitConstants; +import com.xit.core.constant.ErrorCode; +import com.xit.core.exception.TokenAuthException; +//import com.xit.core.oauth2.oauth.token.JwtToken; +import com.xit.core.oauth2.oauth.token.JwtTokenProvider; +import com.xit.core.util.Checks; +import com.xit.core.util.SpringUtils; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; + +public class HeaderUtil { + + //private static String sAccessToken; + + public static String getAccessToken(){ + return getAccessToken(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()); + } + + public static String getAccessToken(HttpServletRequest request) { + String headerValue = request.getHeader(XitConstants.JwtToken.HEADER_NAME.getValue()); + + if (headerValue == null) { + return null; + } + + if (headerValue.startsWith(XitConstants.JwtToken.GRANT_TYPE.getValue())) { + return headerValue.substring(XitConstants.JwtToken.GRANT_TYPE.getValue().length()); + } + + return null; + } + + /** + * + * @param request + * @return String userId + */ + public static String getUserId(HttpServletRequest request){ + return getUserIdFromToken(getAccessToken(request)); + } + + + public static String getUserId(){ + return getUserIdFromToken(getAccessToken(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest())); + } + + private static String getUserIdFromToken(String accessToken){ + if(Checks.isEmpty(accessToken)) throw new TokenAuthException(ErrorCode.AUTH_HEADER_NOT_EXISTS); + +// JwtToken authToken = SpringUtils.getAuthTokenProvider().convertJwtToken(accessToken); +// if (!authToken.validate()) { +// return ApiResponse.invalidAccessToken(); +// } + + JwtTokenProvider jwtTokenProvider = SpringUtils.getJwtTokenProvider(); + + if(jwtTokenProvider.validateTokenExcludeExpired(accessToken, false, false)){ + return jwtTokenProvider.parseClaims(accessToken).getSubject(); + }; + throw new TokenAuthException(ErrorCode.INVALID_TOKEN); + } +} diff --git a/src/main/java/com/xit/core/oauth2/utils/SecurityUtil.java b/src/main/java/com/xit/core/oauth2/utils/SecurityUtil.java new file mode 100644 index 0000000..31f7f1e --- /dev/null +++ b/src/main/java/com/xit/core/oauth2/utils/SecurityUtil.java @@ -0,0 +1,70 @@ +package com.xit.core.oauth2.utils; + +import com.xit.core.constant.ErrorCode; +import com.xit.core.constant.XitConstants.AuthSaveType; +import com.xit.core.exception.UserAuthException; +import com.xit.core.oauth2.api.controller.OAuth2LocalController; +import com.xit.core.util.Checks; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextImpl; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import javax.validation.constraints.NotNull; +import java.util.Objects; + +/** + * JwtFilter에서 저장한 유저정보(토큰)에서 사용자 ID를 GET + * + * @see com.xit.biz.auth.jwt.JwtFilter + */ +@Slf4j +public class SecurityUtil { + + private SecurityUtil() { } + + /** + * @see OAuth2LocalController#login + * @return String + */ + // SecurityContext 에 유저 정보가 저장되는 시점 + // Request 가 들어올 때 JwtFilter 의 doFilter 에서 저장 + public static String getCurrentUserId(@NotNull final String authType) { + + // TODO :: SessionCreationPolicy.STATELESS 인 경우 사용 불가 + if(Objects.equals(authType, AuthSaveType.SECURITY.getValue())) { + final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null || authentication.getName() == null) { + throw new UserAuthException(ErrorCode.NOT_EXISTS_SECURITY_AUTH); + } + return authentication.getName(); + + // TODO :: Session에 저장한 경우 - Token도 가능은 하지만... + }else if(Objects.equals(authType, AuthSaveType.SESSION.getValue())) { + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); + HttpSession session = request.getSession(); + Object o = session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); + if(Checks.isNotNull(o)) { + SecurityContextImpl sci = (SecurityContextImpl) o; + Authentication authentication = sci.getAuthentication(); + + if (authentication == null || authentication.getName() == null) { + throw new UserAuthException(ErrorCode.NOT_EXISTS_SECURITY_AUTH); + } + return authentication.getName(); + }else { + throw new UserAuthException(ErrorCode.NOT_EXISTS_SECURITY_AUTH); + } + }else if(Objects.equals(authType, AuthSaveType.HEADER.getValue())) { + return HeaderUtil.getUserId(); + + }else{ + throw new UserAuthException(ErrorCode.UN_AUTHORIZED_USER); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/CamelCaseLinkedMap.java b/src/main/java/com/xit/core/support/CamelCaseLinkedMap.java new file mode 100644 index 0000000..113b240 --- /dev/null +++ b/src/main/java/com/xit/core/support/CamelCaseLinkedMap.java @@ -0,0 +1,14 @@ +package com.xit.core.support; + +import org.springframework.jdbc.support.JdbcUtils; + +import java.util.LinkedHashMap; + +public class CamelCaseLinkedMap extends LinkedHashMap{ + + @Override + public Object put(Object key, Object value){ + if(key != null && key.toString().contains("_")) return super.put(JdbcUtils.convertUnderscoreNameToPropertyName((String)key), value); + else return super.put(key, value); + } +} diff --git a/src/main/java/com/xit/core/support/CamelCaseMap.java b/src/main/java/com/xit/core/support/CamelCaseMap.java new file mode 100644 index 0000000..5af1113 --- /dev/null +++ b/src/main/java/com/xit/core/support/CamelCaseMap.java @@ -0,0 +1,14 @@ +package com.xit.core.support; + +import org.springframework.jdbc.support.JdbcUtils; + +import java.util.HashMap; + +public class CamelCaseMap extends HashMap{ + + @Override + public Object put(Object key, Object value){ + if(key != null && key.toString().contains("_")) return super.put(JdbcUtils.convertUnderscoreNameToPropertyName((String)key), value); + else return super.put(key, value); + } +} diff --git a/src/main/java/com/xit/core/support/CustomMultipartResolver.java b/src/main/java/com/xit/core/support/CustomMultipartResolver.java new file mode 100644 index 0000000..c3c9f62 --- /dev/null +++ b/src/main/java/com/xit/core/support/CustomMultipartResolver.java @@ -0,0 +1,90 @@ +package com.xit.core.support; + +import com.xit.core.util.Checks; +import lombok.Setter; +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.FileUpload; +import org.apache.commons.fileupload.FileUploadBase; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.multipart.MaxUploadSizeExceededException; +import org.springframework.web.multipart.MultipartException; +import org.springframework.web.multipart.commons.CommonsMultipartResolver; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +public class CustomMultipartResolver extends CommonsMultipartResolver { + + @Setter + private String allowUploadFileTypes; + + @Override + protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException { + String encoding = determineEncoding(request); + FileUpload fileUpload = prepareFileUpload(encoding); + try { + List fileItems = ((ServletFileUpload) fileUpload).parseRequest(request); + + // 정해진 확장자만 사용이 가능하도록 처리 + List notAllowFileTypes = new ArrayList(); + String [] allowFileTypes = allowUploadFileTypes.split("[|]"); + + Iterator it = fileItems.iterator(); + if (it.hasNext()) { + do { + FileItem fileItem = it.next(); + String fileName = fileItem.getName(); + + if (Checks.isNotEmpty(fileName)) { + String extension = ""; + int i = fileName.lastIndexOf('.'); + if (i >= 0) { + extension = fileName.substring(i + 1); + extension = extension.toLowerCase(); + } + + boolean isAllowFile = false; + for (String ext : allowFileTypes) { + if (Objects.equals(StringUtils.EMPTY, extension) || Objects.equals(extension, ext)) { + isAllowFile = true; + break; + } + } + + if (!isAllowFile) { + notAllowFileTypes.add(extension); + } + } + } while (it.hasNext()); + } + + if (notAllowFileTypes.size() > 0) { + throw new NotAllowContentTypeException(notAllowFileTypes); + } + + return parseFileItems(fileItems, encoding); + } + catch (FileUploadBase.SizeLimitExceededException ex) { + throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex); + } + catch (FileUploadException ex) { + throw new MultipartException("Could not parse multipart servlet request", ex); + } + } +} + +/* + + + + + + + + + */ \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/CustomSqlSessionTemplate.java b/src/main/java/com/xit/core/support/CustomSqlSessionTemplate.java new file mode 100644 index 0000000..b526152 --- /dev/null +++ b/src/main/java/com/xit/core/support/CustomSqlSessionTemplate.java @@ -0,0 +1,92 @@ +//package com.xit.core.ext; +// +//import java.util.Date; +//import java.util.List; +//import java.util.Map; +// +//import org.apache.ibatis.session.ExecutorType; +//import org.apache.ibatis.session.SqlSessionFactory; +//import org.mybatis.spring.SqlSessionTemplate; +//import org.springframework.dao.support.PersistenceExceptionTranslator; +//import org.springframework.security.core.Authentication; +//import org.springframework.security.core.context.SecurityContextHolder; +// +//public class CustomSqlSessionTemplate extends SqlSessionTemplate { +// +// public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { +// super(sqlSessionFactory); +// } +// +// public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) { +// super(sqlSessionFactory, executorType); +// } +// +// public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, +// PersistenceExceptionTranslator exceptionTranslator) { +// super(sqlSessionFactory, executorType, exceptionTranslator); +// } +// +// @Override +// public int insert(String statement, Object parameter) { +// setParameter(parameter, "insert"); +// return super.insert(statement, parameter); +// } +// +// @Override +// public int update(String statement, Object parameter) { +// setParameter(parameter, "update"); +// return super.update(statement, parameter); +// } +// +// @Override +// public List selectList(String statement, Object parameter) { +// setParameter(parameter, "selectList"); +// return super.selectList(statement, parameter); +// } +// +// @Override +// public T selectOne(String statement, Object parameter) { +// setParameter(parameter, "selectOne"); +// return super.selectOne(statement, parameter); +// } +// +// private void setParameter(Object parameter, String type) { +// if (parameter != null) { +// +// Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); +// +// String userId = ""; +// String userName = ""; +// Integer commonHostIdx = null; +// if (authentication == null) { +// userId = "anonymousUser"; +// userName = "anonymousUser"; +// } else if (authentication.getPrincipal().equals("anonymousUser")) { +// userId = "anonymousUser"; +// userName = "anonymousUser"; +// } else { +// CustomUser user = (CustomUser) authentication.getPrincipal(); +// userId = user.getUsername(); +// userName = user.getMemberName(); +// commonHostIdx = user.getHostIdx(); +// } +// if (parameter instanceof Map) { +// Map parameterMap = (Map) parameter; +// parameterMap.put("commonHostIdx", commonHostIdx); +// if (type.equals("insert")) { +// parameterMap.put("insId", userId); +// parameterMap.put("insName", userName); +// parameterMap.put("insDate", new Date()); +// parameterMap.put("uptId", userId); +// parameterMap.put("uptDate", new Date()); +// } else { +// parameterMap.put("uptId", userId); +// parameterMap.put("insName", userName); +// parameterMap.put("uptDate", new Date()); +// } +// } else if (parameter instanceof ...) { +// +// } +// } +// } +//} \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/NotAllowContentTypeException.java b/src/main/java/com/xit/core/support/NotAllowContentTypeException.java new file mode 100644 index 0000000..0bad40c --- /dev/null +++ b/src/main/java/com/xit/core/support/NotAllowContentTypeException.java @@ -0,0 +1,34 @@ +package com.xit.core.support; + +import org.springframework.web.multipart.MultipartException; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +@SuppressWarnings("serial") +public class NotAllowContentTypeException extends MultipartException { + + private final List notAllowFileTypeList; + + public NotAllowContentTypeException(List notAllowFileType) { + this(notAllowFileType, null); + } + + public NotAllowContentTypeException(List notAllowFileType, Throwable cause) { + super("허용되지 않은 파일 타입"+notAllowFileType.toString()+"이 존재합니다.", cause); + notAllowFileTypeList = notAllowFileType; + } + + public List getNotAllowFileTypeList () { + List list = null; + if (this.notAllowFileTypeList != null) { + list = new ArrayList(); + Iterator it = this.notAllowFileTypeList.iterator(); + while(it.hasNext()) { + list.add(it.next()); + } + } + return list; + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/ObjectTypeHandler.java b/src/main/java/com/xit/core/support/ObjectTypeHandler.java new file mode 100644 index 0000000..5e2e139 --- /dev/null +++ b/src/main/java/com/xit/core/support/ObjectTypeHandler.java @@ -0,0 +1,78 @@ +package com.xit.core.support; + +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; + +import java.sql.*; +import java.util.Date; + +public class ObjectTypeHandler extends BaseTypeHandler { + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) + throws SQLException { + ps.setObject(i, parameter); + } + + @Override + public Object getNullableResult(ResultSet rs, String columnName) throws SQLException { + Object value = ""; + Object object = rs.getObject(columnName); + value = object; + if(object instanceof Clob) { + + Clob clob = (Clob) object; + + if (clob != null) { + try { + int size = (int) clob.length(); + value = clob.getSubString(1, size); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + else if (object instanceof java.sql.Date ) { + Timestamp sqlTimestamp = rs.getTimestamp(columnName); + if (sqlTimestamp != null) { + value = new Date(sqlTimestamp.getTime()); + } + } + + return value; + } + + @Override + public Object getNullableResult(ResultSet rs, int columnIndex) + throws SQLException { + Object value = ""; + Object object = rs.getObject(columnIndex); + value = object; + if(object instanceof Clob) { + + Clob clob = (Clob) object; + + if (clob != null) { + try { + int size = (int) clob.length(); + value = clob.getSubString(1, size); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + else if (object instanceof java.sql.Date ) { + Timestamp sqlTimestamp = rs.getTimestamp(columnIndex); + if (sqlTimestamp != null) { + value = new Date(sqlTimestamp.getTime()); + } + } + return value; + } + + @Override + public Object getNullableResult(CallableStatement cs, int columnIndex) + throws SQLException { + return cs.getObject(columnIndex); + } +} diff --git a/src/main/java/com/xit/core/support/RefreshableSqlSessionFactoryBean.java b/src/main/java/com/xit/core/support/RefreshableSqlSessionFactoryBean.java new file mode 100644 index 0000000..73cec09 --- /dev/null +++ b/src/main/java/com/xit/core/support/RefreshableSqlSessionFactoryBean.java @@ -0,0 +1,171 @@ +package com.xit.core.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.core.io.Resource; + +import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.*; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * mybatis mapper 자동 감지 후 자동으로 서버 재시작이 필요 없이 반영 + */ +public class RefreshableSqlSessionFactoryBean extends SqlSessionFactoryBean implements DisposableBean { + private static final Log log = LogFactory.getLog(RefreshableSqlSessionFactoryBean.class); + private SqlSessionFactory proxy; + private int interval = 500; + private Timer timer; + private TimerTask task; + private Resource[] mapperLocations; + /** + * 파일 감시 쓰레드가 실행중인지 여부. + */ + private boolean running = false; + private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); + private final Lock r = rwl.readLock(); + private final Lock w = rwl.writeLock(); + + public void setMapperLocations(Resource[] mapperLocations) { + super.setMapperLocations(mapperLocations); + this.mapperLocations = mapperLocations; + } + + public void setInterval(int interval) { + this.interval = interval; + } + + /** + * @throws Exception + */ + public void refresh() throws Exception { + if (log.isInfoEnabled()) { + log.info("refreshing sqlMapClient."); + } + w.lock(); + try { + super.afterPropertiesSet(); + } finally { + w.unlock(); + } + } + + /** + * 싱글톤 멤버로 SqlMapClient 원본 대신 프록시로 설정하도록 오버라이드. + */ + public void afterPropertiesSet() throws Exception { + super.afterPropertiesSet(); + setRefreshable(); + } + + private void setRefreshable() { + proxy = (SqlSessionFactory) Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSessionFactory.class }, new InvocationHandler() { + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + // log.debug("method.getName() : " + method.getName()); + return method.invoke(getParentObject(), args); + } + }); + task = new TimerTask() { + private Map map = new HashMap(); + + public void run() { + if (isModified()) { + try { + refresh(); + } catch (Exception e) { + log.error("caught exception", e); + } + } + } + + private boolean isModified() { + boolean retVal = false; + if (mapperLocations != null) { + for (int i = 0; i < mapperLocations.length; i++) { + Resource mappingLocation = mapperLocations[i]; + retVal |= findModifiedResource(mappingLocation); + } + } + return retVal; + } + + private boolean findModifiedResource(Resource resource) { + boolean retVal = false; + List modifiedResources = new ArrayList(); + try { + long modified = resource.lastModified(); + if (map.containsKey(resource)) { + long lastModified = ((Long) map.get(resource)).longValue(); + if (lastModified != modified) { + map.put(resource, new Long(modified)); + modifiedResources.add(resource.getDescription()); + retVal = true; + } + } else { + map.put(resource, new Long(modified)); + } + } catch (IOException e) { + log.error("caught exception", e); + } + if (retVal) { + if (log.isInfoEnabled()) { + log.info("modified files : " + modifiedResources); + } + } + return retVal; + } + }; + timer = new Timer(true); + resetInterval(); + } + + private Object getParentObject() throws Exception { + r.lock(); + try { + return super.getObject(); + } finally { + r.unlock(); + } + } + + public SqlSessionFactory getObject() { + return this.proxy; + } + + public Class getObjectType() { + return (this.proxy != null ? this.proxy.getClass() : SqlSessionFactory.class); + } + + public boolean isSingleton() { + return true; + } + + public void setCheckInterval(int ms) { + interval = ms; + if (timer != null) { + resetInterval(); + } + } + + private void resetInterval() { + if (running) { + timer.cancel(); + running = false; + } + if (interval > 0) { + timer.schedule(task, 0, interval); + running = true; + } + } + + public void destroy() throws Exception { + timer.cancel(); + } +} diff --git a/src/main/java/com/xit/core/support/RestTemplateLoggingRequestInterceptor.java b/src/main/java/com/xit/core/support/RestTemplateLoggingRequestInterceptor.java new file mode 100644 index 0000000..539118d --- /dev/null +++ b/src/main/java/com/xit/core/support/RestTemplateLoggingRequestInterceptor.java @@ -0,0 +1,62 @@ +package com.xit.core.support; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.util.StreamUtils; + +import java.io.IOException; +import java.nio.charset.Charset; + +/** + * Restful Logging + * 반드시 RestTemplate의 Factory 변경 필요 + * + * @see com.xit.core.config.WebCommonConfig#restTemplate(RestTemplateBuilder) + */ +@Slf4j +public class RestTemplateLoggingRequestInterceptor implements ClientHttpRequestInterceptor { + + /** + * RestTemplate 로깅 Interceptor + * + * @param request HttpRequest + * @param body Request Body + * @param execution ClientHttpRequestExecution + * @return ClientHttpResponse + */ + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { + logRequest(request, body); + ClientHttpResponse response = execution.execute(request, body); + logResponse(response); + return response; + } + + private void logRequest(HttpRequest request, byte[] body) throws IOException { + log.debug( + "===========================request begin================================================"); + log.debug("URI : {}", request.getURI()); + log.debug("Method : {}", request.getMethod()); + log.debug("Headers : {}", request.getHeaders()); + log.debug("Request body: {}", new String(body, Charset.defaultCharset())); + log.debug( + "==========================request end================================================"); + } + + private void logResponse(ClientHttpResponse response) throws IOException { + log.debug( + "============================response begin=========================================="); + log.debug("Status code : {}", response.getStatusCode()); + log.debug("Status text : {}", response.getStatusText()); + log.debug("Headers : {}", response.getHeaders()); + log.debug("Response body: {}", + StreamUtils.copyToString(response.getBody(), Charset.defaultCharset())); + log.debug( + "=======================response end================================================="); + } +} + diff --git a/src/main/java/com/xit/core/support/captcha/SimpleCaptchaUtil.java b/src/main/java/com/xit/core/support/captcha/SimpleCaptchaUtil.java new file mode 100644 index 0000000..ac96ee8 --- /dev/null +++ b/src/main/java/com/xit/core/support/captcha/SimpleCaptchaUtil.java @@ -0,0 +1,229 @@ +package com.xit.core.support.captcha; + +import com.xit.core.util.Checks; +import nl.captcha.Captcha; +import nl.captcha.audio.AudioCaptcha; +import nl.captcha.backgrounds.GradiatedBackgroundProducer; +import nl.captcha.servlet.CaptchaServletUtil; +import nl.captcha.text.producer.TextProducer; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.awt.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * @author Lim, Jong Uk (minuk926) + * @since 2021-11-30 + */ +public class SimpleCaptchaUtil { + + private static final int WIDTH = 100; + private static final int HEIGHT = 30; + private static final int FONT_SIZE = 20; + + + /** + * + * @param req + * @param res + */ + public static void getImgCaptcha(final HttpServletRequest req, final HttpServletResponse res) { + int width = Objects.isNull(req.getParameter("width"))? WIDTH : Integer.parseInt(req.getParameter("width")); + int height = Objects.isNull(req.getParameter("height"))? HEIGHT : Integer.parseInt(req.getParameter("height")); + + List fonts = Arrays.asList( + new Font("", Font.HANGING_BASELINE, FONT_SIZE), + new Font("Courier", Font.ITALIC, FONT_SIZE), + new Font("", Font.PLAIN, FONT_SIZE) + ); + List colors = Collections.singletonList(Color.BLACK); + + Captcha captcha = + new Captcha.Builder(150, 50) + .addText() + //new Captcha.Builder(width, height) + //.addText(new NumbersAnswerProducer(5), new DefaultWordRenderer(colors, fonts)) + // 호출될때마다 라인 추가 + .addNoise() + .gimp() + // black border 생성 + .addBorder() + .addBackground(new GradiatedBackgroundProducer()) + .build(); +/* + res.setHeader("Cache-Control", "no-cache"); + res.setDateHeader("Expires", 0); + res.setHeader("Pragma", "no-cache"); + res.setDateHeader("Max-Age", 0); + res.setContentType("image/png"); + */ + CaptchaServletUtil.writeImage(res, captcha.getImage()); + req.getSession().setAttribute(Captcha.NAME, captcha); + //req.getSession().setAttribute("captcha", captcha.getAnswer());// 값 저장 + } + + /** + * + * @param req + * @param res + * @param answer + */ + public static void getAudioCaptcha(final HttpServletRequest req, final HttpServletResponse res, String answer) { + + if(Checks.isEmpty(answer)){ + Captcha captcha = (Captcha) req.getSession().getAttribute(Captcha.NAME); + answer = captcha.getAnswer(); + } + + AudioCaptcha audioCaptcha = new AudioCaptcha.Builder() + .addAnswer(new SetTextProducer(answer)) + .addNoise() + .build(); + + CaptchaServletUtil.writeAudio(res, audioCaptcha.getChallenge()); + } + + /** + * + * @param req + * @param res + * @param ans + * @return + */ + public static boolean isCorrect(final HttpServletRequest req, final HttpServletResponse res, String ans) { + + if(Checks.isEmpty(ans)) return false; + + Captcha captcha = (Captcha) req.getSession().getAttribute(Captcha.NAME); + if(captcha != null && captcha.isCorrect(ans)){ + req.getSession().removeAttribute(Captcha.NAME); + return true; + } + return false; +/* + String answer = (String)req.getSession().getAttribute("captcha"); + if(Objects.equals(ans, answer)) { + req.getSession().removeAttribute("captcha"); + return true; + } + return false; +*/ + + } + + public static class SetTextProducer implements TextProducer { + private final String str; + + public SetTextProducer(String answer){ + this.str = answer; + } + + @Override + public String getText(){ + return this.str; + } + } +} + + +/* + + + @RequestMapping(method = RequestMethod.GET, value = "/captcha/image") + public void getCaptchaImage(final HttpServletRequest req, final HttpServletResponse res) { + SimpleCaptchaUtil + .getImgCaptcha(req, res); + + + } + + @RequestMapping(method = RequestMethod.GET, value = "/captcha/audio") + public void getCaptchaAudio(final HttpServletRequest req, final HttpServletResponse res) { + SimpleCaptchaUtil.getAudioCaptcha(req, res, null); + + + } + + @RequestMapping(method = RequestMethod.POST, value = "/captcha/check") + @ResponseBody + public String isCorrect(final HttpServletRequest req, final HttpServletResponse res, String answer) { + //modelMap.addAttribute("OK", new SimpleCaptchaUtil().isCorrect(req, res, req.getParameter("answer"))); + + Gson gson = new GsonBuilder().create(); + return gson.toJson(SimpleCaptchaUtil.isCorrect(req, res, req.getParameter("answer"))); + } + + + + + 필수입력* + + 보안문자를 입력해 주세요. + + + + + + + + + + + this.captchaReload = function(){ + const rand = Math.random(); + const url = '${wwwHost}/support/captcha/image?rand='+rand; + document.querySelector('#captchaImg').setAttribute('src', url); + + } + + this.audio = function(){ + const rand = Math.random(); + const uAgent = navigator.userAgent; + + const url = '${wwwHost}/support/captcha/audio?rand='+rand; + + // IE + if(uAgent.indexOf('Trident') > -1 || uAgent.indexOf('MISE') > -1){ + document.querySelector('#ccaudio').innerHTML = ''; + + // Chrome + }else if(!!document.createElement('audio').canPlayType){ + try{ + new Audio(url).play() + }catch (e){ + document.querySelector('#ccaudio').innerHTML = ''; + } + }else{ + window.open(url, '', 'width=1,height=1'); + } + } + this.captchaConfirm = function(){ + const ans = document.querySelector('#captchaAns').value; + if(!ans){ + alert("보안문자를 입력해 주세요."); + return true; + } + + $http({ + method: 'POST', + url: '${wwwHost}/support/captcha/check', + data: {answer: ans} + }).success(function (data, status, headers, config) { + if(data){ + + }else{ + alert('보안문자가 일치하지 않습니다.\n다시 입력해 주세요'); + captchaReload(); + } + + }).error(function (data, status, headers, config) { + alert(data.message); + }); + + } + + */ \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/converter/HTMLCharacterEscapes.java b/src/main/java/com/xit/core/support/converter/HTMLCharacterEscapes.java new file mode 100644 index 0000000..8ee0de5 --- /dev/null +++ b/src/main/java/com/xit/core/support/converter/HTMLCharacterEscapes.java @@ -0,0 +1,85 @@ +package com.xit.core.support.converter; + +import com.fasterxml.jackson.core.SerializableString; +import com.fasterxml.jackson.core.io.CharacterEscapes; +import com.fasterxml.jackson.core.io.SerializedString; +import org.apache.commons.lang3.StringEscapeUtils; + +/** + * Xss 방어 + */ +public class HTMLCharacterEscapes extends CharacterEscapes { + + private final int[] asciiEscapes; + + //private final CharSequenceTranslator translator; + + public HTMLCharacterEscapes() { + + // 1. XSS 방지 처리할 특수 문자 지정 + asciiEscapes = CharacterEscapes.standardAsciiEscapesForJSON(); + asciiEscapes['<'] = CharacterEscapes.ESCAPE_CUSTOM; + asciiEscapes['>'] = CharacterEscapes.ESCAPE_CUSTOM; + asciiEscapes['&'] = CharacterEscapes.ESCAPE_CUSTOM; + asciiEscapes['\"'] = CharacterEscapes.ESCAPE_CUSTOM; + asciiEscapes['('] = CharacterEscapes.ESCAPE_CUSTOM; + asciiEscapes[')'] = CharacterEscapes.ESCAPE_CUSTOM; + asciiEscapes['#'] = CharacterEscapes.ESCAPE_CUSTOM; + asciiEscapes['\''] = CharacterEscapes.ESCAPE_CUSTOM; + + // 2. XSS 방지 처리 특수 문자 인코딩 값 지정 +// translator = new AggregateTranslator( +// new LookupTranslator(EntityArrays.BASIC_ESCAPE()), // <, >, &, " 는 여기에 포함됨 +// new LookupTranslator(EntityArrays.ISO8859_1_ESCAPE()), +// new LookupTranslator(EntityArrays.HTML40_EXTENDED_ESCAPE()), +// // 여기에서 커스터마이징 가능 +// new LookupTranslator( +// new String[][]{ +// {"(", "("}, +// {")", ")"}, +// {"#", "#"}, +// {"\'", "'"} +// } +// ) +// ); + } + + @Override + public int[] getEscapeCodesForAscii() { + return asciiEscapes; + } + + // 윈도우 이모지 관련하여 파싱 중에 에러 해결 + @Override + public SerializableString getEscapeSequence(int ch) { + SerializedString serializedString; + char charAt = (char) ch; + if (Character.isHighSurrogate(charAt) || Character.isLowSurrogate(charAt)) { + StringBuilder sb = new StringBuilder(); + sb.append("\\u"); + sb.append(String.format("%04x", ch)); + serializedString = new SerializedString(sb.toString()); + } else { + serializedString = new SerializedString(StringEscapeUtils.escapeHtml4(Character.toString(charAt))); + } + return serializedString; + } + +// @Override +// public SerializableString getEscapeSequence(int ch) { +// //return new SerializedString(translator.translate(Character.toString((char) ch))); +// +// // 참고 - 커스터마이징이 필요없다면 아래와 같이 Apache Commons Lang3에서 제공하는 메서드를 써도 된다. +// return new SerializedString(StringEscapeUtils.escapeHtml4(Character.toString((char) ch))); +// } +} + +/* + @Bean + public MappingJackson2HttpMessageConverter jsonEscapeConverter() { + // MappingJackson2HttpMessageConverter Default ObjectMapper 설정 및 ObjectMapper Config 설정 + ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build(); + objectMapper.getFactory().setCharacterEscapes(new HTMLCharacterEscapes()); + return new MappingJackson2HttpMessageConverter(objectMapper); + } + */ \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/hateoas/CustomPagedResourceAssembler.java b/src/main/java/com/xit/core/support/hateoas/CustomPagedResourceAssembler.java new file mode 100644 index 0000000..3bfdfac --- /dev/null +++ b/src/main/java/com/xit/core/support/hateoas/CustomPagedResourceAssembler.java @@ -0,0 +1,176 @@ +package com.xit.core.support.hateoas; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.HateoasPageableHandlerMethodArgumentResolver; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.hateoas.*; +import org.springframework.hateoas.server.RepresentationModelAssembler; +import org.springframework.util.Assert; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.springframework.web.util.UriComponentsBuilder.fromUri; + +/** + * https://cla9.tistory.com/ + * @see PagedModelUtil + * WebMvcConfigurer 구현체에 Bean 등록후 사용 + * + * @author Lim, Jong Uk (minuk926) + * @since 2021-07-30 + */ +public class CustomPagedResourceAssembler extends PagedResourcesAssembler { + private final HateoasPageableHandlerMethodArgumentResolver pageableResolver; + private final Optional baseUri; + private final int PAGE_SIZE; + private final int DEFAULT_PAGE_SIZE = 10; + + public CustomPagedResourceAssembler(HateoasPageableHandlerMethodArgumentResolver pageableResolver, @Nullable Integer pageSize) { + super(pageableResolver,null); + this.pageableResolver = pageableResolver; + baseUri = Optional.empty(); + this.PAGE_SIZE = pageSize == null ? DEFAULT_PAGE_SIZE : pageSize; + } + + @Override + public PagedModel> toModel(Page entity) { + return toModel(entity, EntityModel::of); + } + + @Override + public > PagedModel toModel(Page page, RepresentationModelAssembler assembler) { + Assert.notNull(page, "Page must not be null!"); + Assert.notNull(assembler, "ResourceAssembler must not be null!"); + + List resources = new ArrayList<>(page.getNumberOfElements()); + + for (T element : page) { + resources.add(assembler.toModel(element)); + } + + PagedModel resource = createPagedModel(resources, asPageMetadata(page), page); + return addPaginationLinks(resource, page, Optional.empty()); + } + + private PagedModel.PageMetadata asPageMetadata(Page page) { + + Assert.notNull(page, "Page must not be null!"); + int number = page.getNumber(); + return new PagedModel.PageMetadata(page.getSize(), number, page.getTotalElements(), page.getTotalPages()); + } + + private PagedModel addPaginationLinks(PagedModel resources, Page page, Optional link) { + + UriTemplate base = getUriTemplate(link); + boolean isNavigable = page.hasPrevious() || page.hasNext(); + + int currIndex = page.getNumber(); + int lastIndex = page.getTotalPages() == 0 ? 0 : page.getTotalPages() - 1; + int baseIndex = (currIndex / this.PAGE_SIZE) * this.PAGE_SIZE; + int prevIndex = baseIndex - this.PAGE_SIZE; + int nextIndex = baseIndex + this.PAGE_SIZE; + int size = page.getSize(); + Sort sort = page.getSort(); + + if(lastIndex < currIndex) + throw new IndexOutOfBoundsException("Page total size must not be less than page size"); + + if (isNavigable) { + resources.add(createLink(base, PageRequest.of(0, size, sort), IanaLinkRelations.FIRST)); + } + + Link selfLink = link.map(it -> it.withSelfRel()).orElseGet(() -> createLink(base, page.getPageable(), IanaLinkRelations.SELF)); + + resources.add(selfLink); + + if (prevIndex >= 0) { + resources.add(createLink(base, + PageRequest.of(prevIndex, size, sort), + IanaLinkRelations.PREV)); + } + + for(int i = 0; i < this.PAGE_SIZE; i++){ + if(baseIndex + i <= lastIndex){ + resources.add(createLink(base, PageRequest.of(baseIndex + i, size, sort), LinkRelation.of("pagination"))); + } + } + + if(nextIndex < lastIndex){ + resources.add(createLink(base, PageRequest.of(nextIndex, size, sort), IanaLinkRelations.NEXT)); + } + + if (isNavigable) { + resources.add(createLink(base, PageRequest.of(lastIndex, size, sort), IanaLinkRelations.LAST)); + } + + return resources; + } + + private UriTemplate getUriTemplate(Optional baseLink) { + return UriTemplate.of(baseLink.map(Link::getHref).orElseGet(this::baseUriOrCurrentRequest)); + } + + private String baseUriOrCurrentRequest() { + return baseUri.map(Object::toString).orElseGet(CustomPagedResourceAssembler::currentRequest); + } + + private static String currentRequest() { + return ServletUriComponentsBuilder.fromCurrentRequest().build().toString(); + } + + private Link createLink(UriTemplate base, Pageable pageable, LinkRelation relation) { + UriComponentsBuilder builder = fromUri(base.expand()); + pageableResolver.enhance(builder, getMethodParameter(), pageable); + return Link.of(UriTemplate.of(builder.build().toString()), relation); + } +} + + +/* + +@Configuration +public class AppConfig implements WebMvcConfigurer { + @Bean + public CustomPagedResourceAssembler customPagedResourceAssembler(){ + return new CustomPagedResourceAssembler<>(new HateoasPageableHandlerMethodArgumentResolver(), 10); + } +} + + +@RestController +@RequiredArgsConstructor +public class CustomerController { + private final CustomerService service; + private final CustomPagedResourceAssembler assembler; + + @GetMapping("/customers") + public ResponseEntity>> getCustomer(PageableDTO pageableDTO){ + Page customers = service.getCustomer(pageableDTO); + + PagedModel> entityModels = PagedModelUtil.getEntityModels(assembler, customers, + linkTo(methodOn(this.getClass()).getCustomer(null)), + CustomerDTO::getCustomerId); + return ResponseEntity.ok(entityModels); + } + + + @GetMapping("/posts") + public PagedModel> getHateoasPosts(Pageable pageable, PagedResourcesAssembler assembler){ + + return PagedModelUtil.getEntityModels(assembler, postRepository.findAll(pageable), + linkTo(methodOn(this.getClass()).getClass()), + Post::getId); + + //return assembler.toModel(postRepository.findAll(pageable)); + } +} + */ \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/hateoas/LinkResource.java b/src/main/java/com/xit/core/support/hateoas/LinkResource.java new file mode 100644 index 0000000..fe7acb6 --- /dev/null +++ b/src/main/java/com/xit/core/support/hateoas/LinkResource.java @@ -0,0 +1,22 @@ +package com.xit.core.support.hateoas; + +import org.springframework.hateoas.EntityModel; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; + +import java.util.function.Function; + +/** + * @author Lim, Jong Uk (minuk926) + * @since 2021-07-30 + */ +public class LinkResource extends EntityModel { + public static EntityModel of(WebMvcLinkBuilder builder, T model, Function resourceFunc){ + return EntityModel.of(model, getSelfLink(builder, model, resourceFunc)); + } + + private static Link getSelfLink(WebMvcLinkBuilder builder, T data, Function resourceFunc){ + WebMvcLinkBuilder slash = builder.slash(resourceFunc.apply(data)); + return slash.withSelfRel(); + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/hateoas/PagedModelUtil.java b/src/main/java/com/xit/core/support/hateoas/PagedModelUtil.java new file mode 100644 index 0000000..0715609 --- /dev/null +++ b/src/main/java/com/xit/core/support/hateoas/PagedModelUtil.java @@ -0,0 +1,79 @@ +package com.xit.core.support.hateoas; + +import org.springframework.data.domain.Page; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.hateoas.EntityModel; +import org.springframework.hateoas.PagedModel; +import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; + +import java.util.function.Function; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; + +/** + * https://cla9.tistory.com/ + * + * @author Lim, Jong Uk (minuk926) + * @since 2021-07-30 + */ +public class PagedModelUtil { + /** + * + * @param assembler PagedResourcesAssembler + * @param page Page + * @param clazz DTO or Domain :: linkTo(methodOn(this.getClass()).getClass()), + * @param selfLinkFunc paging key method :: Post::getId + * @return PagedModel + */ + public static PagedModel> getEntityModels(PagedResourcesAssembler assembler, + Page page, + Class clazz, + Function selfLinkFunc ){ + WebMvcLinkBuilder webMvcLinkBuilder = linkTo(clazz); + return assembler.toModel(page, model -> LinkResource.of(webMvcLinkBuilder, model, selfLinkFunc::apply)); + } + + /** + * + * @param assembler PagedResourcesAssembler + * @param page Page + * @param builder WebMvcLinkBuilder :: linkTo(methodOn(this.getClass()).getPost()), + * @param selfLinkFunc paging key method :: Post::getId + * @return PagedModel + */ + public static PagedModel> getEntityModels(PagedResourcesAssembler assembler, + Page page, + WebMvcLinkBuilder builder, + Function selfLinkFunc ){ + return assembler.toModel(page, model -> LinkResource.of(builder, model, selfLinkFunc::apply)); + } +} + +/* +@RestController +@RequiredArgsConstructor +public class CustomerController { + private final CustomerService service; + private final PagedResourcesAssembler assembler; + + @GetMapping("/customers") + public ResponseEntity>> getCustomer(PageableDTO pageableDTO){ + Page customers = service.getCustomer(pageableDTO); + + PagedModel> entityModels = PagedModelUtil.getEntityModels(assembler, customers, + linkTo(methodOn(this.getClass()).getCustomer(null)), + CustomerDTO::getCustomerId); + return ResponseEntity.ok(entityModels); + } + + @GetMapping("/posts") + public PagedModel> getHateoasPosts(Pageable pageable, PagedResourcesAssembler assembler){ + + return PagedModelUtil.getEntityModels(assembler, postRepository.findAll(pageable), + linkTo(methodOn(this.getClass()).getClass()), + Post::getId); + + //return assembler.toModel(postRepository.findAll(pageable)); + } +} + */ diff --git a/src/main/java/com/xit/core/support/jpa/AbstractJpaCrudService.java b/src/main/java/com/xit/core/support/jpa/AbstractJpaCrudService.java new file mode 100644 index 0000000..d5a9919 --- /dev/null +++ b/src/main/java/com/xit/core/support/jpa/AbstractJpaCrudService.java @@ -0,0 +1,71 @@ +package com.xit.core.support.jpa; + +import com.google.common.collect.Lists; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.CrudRepository; +import org.springframework.transaction.annotation.Transactional; + +import java.io.Serializable; +import java.util.List; +import java.util.Optional; + +//@Transactional(value = "jpaTransactionManager") +public abstract class AbstractJpaCrudService implements IJpaOperation { + + protected abstract CrudRepository getRepository(); + + @Override + @Transactional(readOnly = true) + public T findOne(final ID id) { + return getRepository().findById(id).orElse(null); + } + + @Override + @Transactional(readOnly = true) + public Optional findById(final ID id) { + return getRepository().findById(id); + } + + @Override + @Transactional(readOnly = true) + public List findAll() { + return Lists.newArrayList(getRepository().findAll()); + } + + @Override + @Transactional + public T save(final T entity) { + return getRepository().save(entity); + } + + @Override + @Transactional + public T update(final T entity) { + return getRepository().save(entity); + } + + @Override + @Transactional + public void delete(final T entity) { + getRepository().delete(entity); + } + + @Override + @Transactional + public void deleteById(final ID id) { + getRepository().deleteById(id); + } + + @Override + @Transactional(readOnly = true) + public Page findAll(final Pageable pageable) { + return null; + } + + @Override + @Transactional(readOnly = true) + public Page findPaginated(final int page, final int size) { + return null; + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/jpa/AbstractJpaService.java b/src/main/java/com/xit/core/support/jpa/AbstractJpaService.java new file mode 100644 index 0000000..f0ec578 --- /dev/null +++ b/src/main/java/com/xit/core/support/jpa/AbstractJpaService.java @@ -0,0 +1,78 @@ +package com.xit.core.support.jpa; + +import com.google.common.collect.Lists; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.transaction.annotation.Transactional; + +import java.io.Serializable; +import java.util.List; +import java.util.Optional; + +//@Transactional(value = "jpaTransactionManager") +public abstract class AbstractJpaService implements IJpaOperation { + + protected abstract PagingAndSortingRepository getRepository(); + + // Paging + @Transactional(readOnly = true) + public Page findAll(final Pageable pageable) { + return getRepository().findAll(pageable); + } + + // Paging + @Transactional(readOnly = true) + public Page findPaginated(final int page, final int size) { + return getRepository().findAll(PageRequest.of(page, size)); + } + + @Override + @Transactional(readOnly = true) + public T findOne(final ID id) { + return getRepository().findById(id).orElse(null); + } + + @Override + @Transactional(readOnly = true) + public Optional findById(final ID id) { + return getRepository().findById(id); + } + + @Override + @Transactional(readOnly = true) + public List findAll() { + return Lists.newArrayList(getRepository().findAll()); + } + + + // write + + @Override + @Transactional + public T save(final T entity) { + return getRepository().save(entity); + } + + @Override + @Transactional + public T update(final T entity) { + return getRepository().save(entity); + } + + @Override + @Transactional + public void delete(final T entity) { + getRepository().delete(entity); + } + + @Override + @Transactional + public void deleteById(final ID id) { + getRepository().deleteById(id); + } + + + +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/jpa/AuditEntity.java b/src/main/java/com/xit/core/support/jpa/AuditEntity.java new file mode 100644 index 0000000..3405c57 --- /dev/null +++ b/src/main/java/com/xit/core/support/jpa/AuditEntity.java @@ -0,0 +1,106 @@ +package com.xit.core.support.jpa; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.ColumnDefault; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.*; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 감사필드 값 생성 + * @see com.xit.core.support.jpa.AuditorAwareConfig + */ +@Schema(name = "AuditEntity", description = "감사 필드 정의") +@EntityListeners(AuditingEntityListener.class) +@Getter +// JPA Entity 클래스들이 해당 클래스를 상속할 경우 정의한 필드 적용 +@MappedSuperclass +public class AuditEntity implements Serializable { + private static final long serialVersionUID = 1146360965411496820L; + + @Setter + @Schema(description = "생성시 생성자 자동 할당", example = " ") + @CreatedBy + @Column(name = "created_by", updatable = false, length = 64) + private String createdBy; + + @Setter + @Schema(description = "변경시 변경자 자동 할당", example = " ") + @LastModifiedBy + @Column(name = "modified_by", length = 64, insertable = false) + private String modifiedBy; + + @Setter + @Schema(description = "생성시 생성일시 자동 할당", example = " ") + @CreatedDate + //@JsonIgnore + //@Temporal(TemporalType.TIMESTAMP) + //@Column(name = "created_date", updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP") + @Column(name = "created_date", updatable = false) + private LocalDateTime createdDate; + + @Setter + @Schema(description = "변경시 변경일시 자동 할당", example = " ") + @LastModifiedDate + //@JsonIgnore + //@Temporal(TemporalType.TIMESTAMP) + //@Column(name = "modified_date", insertable = false, columnDefinition = "TIMESTAMP DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP") + @Column(name = "modified_date", insertable = false) + private LocalDateTime modifiedDate; + + @Schema(example = "Y", required = true) + @Column(name = "use_yn", nullable = false, length = 1) + //@Column(name = "use_yn", nullable = false, length = 1) + @ColumnDefault(value = "'Y'") + private String useYn = "Y"; + + + // @PrePersist +// protected void onCreate() { +// createdDate = Timestamp.valueOf(LocalDateTime.now()); +// } +// +// @PreUpdate +// protected void onUpdate() { +// modifiedDate = Timestamp.valueOf(LocalDateTime.now()); +// } + + + // TODO : UserDetails 구현시 사용한 User와 join +// @CreatedBy +// @ManyToOne +// @JoinColumn(name = "ins_user_id", referencedColumnName = "user_id", updatable = false, nullable = false) +// private CmmUser createdBy; +// +// // org.springframework.data.domain.AuditorAware를 스프링 빈으로 등록 +// // 데이터 수정자 자동 저장 어노테이션 +// @LastModifiedBy +// @ManyToOne +// @JoinColumn(name = "upd_user_id", referencedColumnName = "user_id", nullable = false) +// private CmmUser modifiedBy; + + +} + + +/* + @PrePersist + public void prePersist() { + LocalDateTime now = LocalDateTime.now(); + created_at = now; + updated_at = now; + } + + @PreUpdate + public void preUpdate() { + updated_at = LocalDateTime.now(); + } + */ \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/jpa/AuditorAwareConfig.java b/src/main/java/com/xit/core/support/jpa/AuditorAwareConfig.java new file mode 100644 index 0000000..e772477 --- /dev/null +++ b/src/main/java/com/xit/core/support/jpa/AuditorAwareConfig.java @@ -0,0 +1,34 @@ +package com.xit.core.support.jpa; + +import org.springframework.data.domain.AuditorAware; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +/** + * 사용자 자동 생성 + * EntityListeners(AuditingEntityListener.class) 활성화 + * AuditFieldEntity 클래스의 사용자 set + * main에 설정시 Test시 오류 바생 + * Test 작성시 충돌을 피하기 위해 Application.class 대신 여기에 추가 + * + * @see com.xit.core.support.jpa.AuditEntity + */ +@Component +public class AuditorAwareConfig implements AuditorAware{ + + /** + * @return + */ + @Override + public Optional getCurrentAuditor() { + final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication == null || authentication.getName() == null) { + return null; + } + return Optional.of(authentication.getName()); + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/jpa/EnumFindUtils.java b/src/main/java/com/xit/core/support/jpa/EnumFindUtils.java new file mode 100644 index 0000000..236e959 --- /dev/null +++ b/src/main/java/com/xit/core/support/jpa/EnumFindUtils.java @@ -0,0 +1,16 @@ +package com.xit.core.support.jpa; + +import com.xit.core.oauth2.oauth.entity.ProviderType; + +import java.util.Arrays; +import java.util.Objects; + +public class EnumFindUtils { + + public static ProviderType fromProviderType(String code) { + return Arrays.stream(ProviderType.values()) + .filter(r -> Objects.equals(r.name(), code)) + .findAny() + .orElse(null); + } +} diff --git a/src/main/java/com/xit/core/support/jpa/IJpaOperation.java b/src/main/java/com/xit/core/support/jpa/IJpaOperation.java new file mode 100644 index 0000000..00be72f --- /dev/null +++ b/src/main/java/com/xit/core/support/jpa/IJpaOperation.java @@ -0,0 +1,32 @@ +package com.xit.core.support.jpa; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.io.Serializable; +import java.util.List; +import java.util.Optional; + +public interface IJpaOperation { + + T findOne(final ID id); + + Optional findById(final ID id); + + List findAll(); + + Page findAll(final Pageable pageable); + + Page findPaginated(int page, int size); + + // write + + T save(final T entity); + + T update(final T entity); + + void delete(final T entity); + + void deleteById(final ID id); + +} diff --git a/src/main/java/com/xit/core/support/jpa/IRepository.java b/src/main/java/com/xit/core/support/jpa/IRepository.java new file mode 100644 index 0000000..0da0961 --- /dev/null +++ b/src/main/java/com/xit/core/support/jpa/IRepository.java @@ -0,0 +1,50 @@ +package com.xit.core.support.jpa; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.data.repository.Repository; + +import java.io.Serializable; +import java.util.List; +import java.util.Optional; + +/** + * xit Repository 재정의 + * NoRepositoryBean annotaion 필수 + * + * @author Lim, Jong Uk (minuk926) + * @since 2021-07-22 + */ +@NoRepositoryBean +public interface IRepository extends Repository { + + T getById(ID var1); + Optional findById(ID var1); + List findAllById(Iterable var1); + boolean existsById(ID var1); + + List findAll(); + List findAll(Sort var1); + Page findAll(Pageable var1); + + long count(); + + S save(S var1); + List saveAll(Iterable var1); + + void deleteById(ID var1); + void delete(T var1); + void deleteAllById(Iterable var1); + void deleteAll(Iterable var1); + void deleteAll(); + + void deleteAllInBatch(Iterable var1); + void deleteAllByIdInBatch(Iterable var1); + void deleteAllInBatch(); + + S saveAndFlush(S var1); + List saveAllAndFlush(Iterable var1); + void flush(); +} diff --git a/src/main/java/com/xit/core/support/jpa/ISimpleRepository.java b/src/main/java/com/xit/core/support/jpa/ISimpleRepository.java new file mode 100644 index 0000000..c6cad1e --- /dev/null +++ b/src/main/java/com/xit/core/support/jpa/ISimpleRepository.java @@ -0,0 +1,18 @@ +package com.xit.core.support.jpa; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.NoRepositoryBean; + +import java.io.Serializable; + +/** + * Repository 재정의 + * NoRepositoryBean annotaion 필수 + * + * @author Lim, Jong Uk (minuk926) + * @since 2021-07-22 + */ +@NoRepositoryBean +public interface ISimpleRepository extends JpaRepository { //Repository { + boolean contains(T entity); +} diff --git a/src/main/java/com/xit/core/support/jpa/JpaUtil.java b/src/main/java/com/xit/core/support/jpa/JpaUtil.java new file mode 100644 index 0000000..7a45bd6 --- /dev/null +++ b/src/main/java/com/xit/core/support/jpa/JpaUtil.java @@ -0,0 +1,27 @@ +package com.xit.core.support.jpa; + +import com.xit.core.util.Checks; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +import java.util.Objects; + + +public class JpaUtil { + private static final int pageNumber = 0; + private static final int pageSize = 20; + + public static Pageable getPagingInfo(Pageable pageable){ + if(Checks.isEmpty(pageable)){ + return PageRequest.of(pageNumber, pageSize); + } + int page = Checks.isEmpty(pageable.getPageNumber())? pageNumber: pageable.getPageNumber() - 1; + int limit = Checks.isEmpty(pageable.getPageSize())? pageSize: pageable.getPageSize(); + Sort sort = Checks.isEmpty(pageable.getSort())? null: pageable.getSort(); + if(Checks.isEmpty(sort)) return PageRequest.of(page, limit); + else return PageRequest.of(page, limit, Objects.requireNonNull(sort)); +// if(Checks.isEmpty(sort)) return PageRequest.of(limit*(page-1), limit); +// else return PageRequest.of(limit*(page-1), limit, Objects.requireNonNull(sort)); + } +} diff --git a/src/main/java/com/xit/core/support/jpa/Paginator.java b/src/main/java/com/xit/core/support/jpa/Paginator.java new file mode 100644 index 0000000..ab02d04 --- /dev/null +++ b/src/main/java/com/xit/core/support/jpa/Paginator.java @@ -0,0 +1,144 @@ +package com.xit.core.support.jpa; + +import com.xit.core.constant.XitConstants; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import java.io.Serializable; + +public class Paginator implements Serializable { + private static final long serialVersionUID = 1L; + + private int limit; + private int page = 1; + private int totalCount; + + public Paginator(int page, int limit, int totalCount) { + super(); + this.limit = limit; + this.totalCount = totalCount; + this.page = computePageNo(page); + } + + public void setSessionPagination(Paginator paginator){ + RequestContextHolder.currentRequestAttributes().setAttribute(XitConstants.Session.PAGE_INFO.getValue(), paginator, RequestAttributes.SCOPE_REQUEST); + } + + public int getPage() { + return page; + } + + public void setPage(int page) { + this.page = page; + } + + public int getLimit() { + return limit; + } + + public int getTotalCount() { + return totalCount; + } + + public boolean isFirstPage() { + return page <= 1; + } + + public boolean isLastPage() { + return page >= getTotalPages(); + } + + public int getPrePage() { + if (isHasPrePage()) { + return page - 1; + } else { + return page; + } + } + + public int getNextPage() { + if (isHasNextPage()) { + return page + 1; + } else { + return page; + } + } + + public boolean isDisabledPage(int page) { + return ((page < 1) || (page > getTotalPages()) || (page == this.page)); + } + + public boolean isHasPrePage() { + return (page - 1 >= 1); + } + + public boolean isHasNextPage() { + return (page + 1 <= getTotalPages()); + } + + public int getStartRow() { + if (getLimit() <= 0 || totalCount <= 0) + return 0; + return page > 0 ? (page - 1) * getLimit() + 1 : 0; + } + + public int getEndRow() { + return page > 0 ? Math.min(limit * page, getTotalCount()) : 0; + } + + public int getOffset() { + return page > 0 ? (page - 1) * getLimit() : 0; + } + + public int getTotalPages() { + if (totalCount <= 0) { + return 0; + } + if (limit <= 0) { + return 0; + } + + int count = totalCount / limit; + if (totalCount % limit > 0) { + count++; + } + return count; + } + + protected int computePageNo(int page) { + return computePageNumber(page, limit, totalCount); + } + + private static int computeLastPageNumber(int totalItems, int pageSize) { + if (pageSize <= 0) + return 1; + int result = (int) (totalItems % pageSize == 0 ? totalItems / pageSize + : totalItems / pageSize + 1); + if (result <= 1) + result = 1; + return result; + } + + private static int computePageNumber(int page, int pageSize, int totalItems) { + if (page <= 1) { + return 1; + } + if (Integer.MAX_VALUE == page + || page > computeLastPageNumber(totalItems, pageSize)) { // last + // page + return computeLastPageNumber(totalItems, pageSize); + } + return page; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("Paginator"); + sb.append("{page=").append(page); + sb.append(", limit=").append(limit); + sb.append(", totalCount=").append(totalCount); + sb.append('}'); + return sb.toString(); + } +} diff --git a/src/main/java/com/xit/core/support/jpa/SimpleBaseRepository.java b/src/main/java/com/xit/core/support/jpa/SimpleBaseRepository.java new file mode 100644 index 0000000..8927b00 --- /dev/null +++ b/src/main/java/com/xit/core/support/jpa/SimpleBaseRepository.java @@ -0,0 +1,36 @@ +package com.xit.core.support.jpa; + +import org.springframework.data.jpa.repository.support.JpaEntityInformation; +import org.springframework.data.jpa.repository.support.SimpleJpaRepository; + +import javax.persistence.EntityManager; +import java.io.Serializable; + +/** + * 기본 구현체 확장 + * 사용하려면 @EnableJpaRepositories의 repositoryBaseClass로 지정하고 + * 각 Repository는 ISimpleRepository를 상속 받도록 한다 + * + * querydsl 사용시도 동일 + * ( SimpleJpaRepository가 아니라 QuerydslJpaRepository를 상속 받아야 했으나 + * 버전이 올라 가면서 deprecated 됨) + * + * @param + * @param + */ +//public class SimpleBaseRepository extends QuerydslJpaRepository implements ISimpleRepository{ +public class SimpleBaseRepository extends SimpleJpaRepository implements ISimpleRepository{ + + private final EntityManager em; + + public SimpleBaseRepository(JpaEntityInformation entityInformation, EntityManager em) { + super(entityInformation, em); + this.em = em; + } + + + @Override + public boolean contains(T entity) { + return em.contains(entity); + } +} diff --git a/src/main/java/com/xit/core/support/jpa/mapstruct/IMapstruct.java b/src/main/java/com/xit/core/support/jpa/mapstruct/IMapstruct.java new file mode 100644 index 0000000..99166b1 --- /dev/null +++ b/src/main/java/com/xit/core/support/jpa/mapstruct/IMapstruct.java @@ -0,0 +1,14 @@ +package com.xit.core.support.jpa.mapstruct; + +import org.mapstruct.BeanMapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; + +public interface IMapstruct { + D toDto(E e); + E toEntity(D d); + + //기존 생성되어있는 Entity를 업데이트하고 싶을 때 null이 아닌 값만 업데이트 + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + public void updateFromDto(D dto, @MappingTarget E entity); //MappingTarget : 변환하여 객체를 return하는 것이 아닌 인자로 받아 업데이트할 target을 설정 +} diff --git a/src/main/java/com/xit/core/support/jpa/mapstruct/MapStructMapperConfig.java b/src/main/java/com/xit/core/support/jpa/mapstruct/MapStructMapperConfig.java new file mode 100644 index 0000000..0b134e6 --- /dev/null +++ b/src/main/java/com/xit/core/support/jpa/mapstruct/MapStructMapperConfig.java @@ -0,0 +1,14 @@ +package com.xit.core.support.jpa.mapstruct; + +import org.mapstruct.Builder; +import org.mapstruct.MapperConfig; +import org.mapstruct.ReportingPolicy; + +/** + * 특정 타입이나 객체간 매핑을 MapStruct 스스로할 수 없거나 다른 Mapper를 이용해야 한다면 'uses'를 사용할 수 있다. + * 'uses = JsonMapper.class'로 지정하면 객체에서 String으로 변환이 필요할 때 JsonMapper를 사용한다. + * spring bean으로 등록하기 위해 componentModel = "spring" 속성 필수 + */ +@MapperConfig(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) //, builder = @Builder(disableBuilder = true)) //, uses = JsonMapper.class) //, unmappedTargetPolicy = ReportingPolicy.IGNORE, uses = JsonMapper.class) +public class MapStructMapperConfig { +} diff --git a/src/main/java/com/xit/core/support/listener/SessionListener.java b/src/main/java/com/xit/core/support/listener/SessionListener.java new file mode 100644 index 0000000..84cc73c --- /dev/null +++ b/src/main/java/com/xit/core/support/listener/SessionListener.java @@ -0,0 +1,181 @@ +package com.xit.core.support.listener; + +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; + +/** + * 동시 접속자 세션관리 HttpListener + * /WEB-INF/classes/session.properties 설정파일을 참조 + * 동시접속자 허용수를 setting 할 수 있도록 함. (제한접속자 수를 늘려야 할 경우 클래스 Compile없이 사용하도록) + * Site 접속시점 세션생성 + * Site 로그인시점 세션생성을 분리하여 접속자 수 Counting! + * + */ +public class SessionListener implements HttpSessionListener { + public static SessionListener sessionManager = null; + public static Hashtable sessionMonitor; + public static Hashtable loginSessionMonitor; + public static int maxSessionValidCount; + + public SessionListener() { + if (sessionMonitor == null) sessionMonitor = new Hashtable(); + if (loginSessionMonitor == null) loginSessionMonitor = new Hashtable(); + sessionManager = this; + + Properties prop = new Properties(); + try { + InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("session.properties"); + prop.load(inputStream); + } catch (Exception e) { + maxSessionValidCount = 300; + e.printStackTrace(); + } + + maxSessionValidCount = Integer.parseInt((String) prop.get("maxSessionValidCount")); +// System.out.println(" ########################### maxSessionValidCount : " + maxSessionValidCount); + } + + public static synchronized SessionListener getInstance() { + if (sessionManager == null) + sessionManager = new SessionListener(); + return sessionManager; + } + + /** 현재 활성화 된 session의 수를 반환한다. + * + * @return int int + */ + public int getActiveSessionCount() { + return sessionMonitor.size(); + } + + /** 현재 등록된 session의 id목록을 반환한다. + * @return Enumeration Enumeration + */ + public Enumeration getIds() { + return sessionMonitor.keys(); + } + + /** 전체 세션갯수를 측정하여 로그인(대기)상태 메세지 창 호출 + * @return boolean boolean + */ + public boolean isMaxLoginSessions() { + boolean retVal = false; + + if(maxSessionValidCount <= getActiveLoginSessionCount()) { + retVal = true; + } + + return retVal; + } + + /** 현재 활성화 된 session의 수를 반환한다. + * @return int int + */ + public int getActiveLoginSessionCount() { + return loginSessionMonitor.size(); + } + + /** + * 로그인한 Session Put + * @param session session + */ + public void setLoginSession(HttpSession session) { + synchronized (loginSessionMonitor) { + loginSessionMonitor.put(session.getId(), session); + + System.out.println(" ############################################################################### "); + System.out.println(" # 접속자 (로그인 허용인원수) : " + maxSessionValidCount + " 명#"); + System.out.println(" # 접속자 (사이트 접속자수) : " + getActiveSessionCount() + " 명#"); + System.out.println(" # 접속자 (로그인 사용자수) : " + getActiveLoginSessionCount() + " 명#"); + System.out.println(" ############################################################################### "); + } + } + + /** + * 로그아웃한 Session Remove + * @param session session + */ + public void setLogoutSession(HttpSession session) { + synchronized (loginSessionMonitor) { + loginSessionMonitor.remove(session.getId()); + } + } + + /** + * 현재 등록된 session중 현재 접속된 사용자 정보와 중복 여부 확인 후 중복 접속 이면 이전의 세션을 소멸 시킨다. + */ + /*public boolean checkDuplicationLogin(String sessionId, String userEeno) { + boolean ret = false; + Enumeration eNum = sessionMonitor.elements(); + System.out.println("session count : " + getActiveSessionCount()); + while (eNum.hasMoreElements()) { + HttpSession sh_session = null; + try { + sh_session = (HttpSession) eNum.nextElement(); + } catch (Exception e) { + continue; + } + UserModel baseModel = sh_session.getAttribute("UserInfo"); + if (baseModel != null) { + if (userEeno.equals(baseModel.getUserId_()) + && !sessionId.equals(sh_session.getId())) { + // 전달 받은 사번과(userEeno) 기존 세션값 중 사번이 중복 되면 + // 기존 세션을 소멸 시킨다. + // 사용자 로그아웃 이력(중복접속)을 저장한다. + try { + HashMap param = new HashMap(); + param.put("usrId", baseModel.getUserId_()); + param.put("ipAddr", baseModel.getRemoteIp_()); + param.put("logKind", "LOGOUT"); + param.put("logRsn", "DUPLICATE"); + // DB 처리 + xxxxxxxx.insertLoginLog(param); + + } catch (Exception e) { + e.printStackTrace(); + } + // 해당 세션 무효화 + sh_session.invalidate(); + ret = true; + break; + } + } + } + return ret; + }*/ + + /** 세션 생성시 이벤트 처리 **/ + public void sessionCreated(HttpSessionEvent event) { + HttpSession session = event.getSession(); + synchronized (sessionMonitor) { + sessionMonitor.put(session.getId(), session); + + System.out.println(" ############################################################################### "); + System.out.println(" # 접속자 (로그인 허용인원수) : " + maxSessionValidCount + " 명#"); + System.out.println(" # 접속자 (사이트 접속자수) : " + getActiveSessionCount() + " 명#"); + System.out.println(" # 접속자 (로그인 사용자수) : " + getActiveLoginSessionCount() + " 명#"); + System.out.println(" ############################################################################### "); + } + } + + /** 세션 소멸(종료)시 이벤트 처리 **/ + public void sessionDestroyed(HttpSessionEvent event) { + HttpSession session = event.getSession(); + synchronized (sessionMonitor) { + sessionMonitor.remove(session.getId()); + loginSessionMonitor.remove(session.getId()); + + System.out.println(" ############################################################################### "); + System.out.println(" # 접속자 (로그인 허용인원수) : " + maxSessionValidCount + " 명#"); + System.out.println(" # 접속자 (사이트 접속자수) : " + getActiveSessionCount() + " 명#"); + System.out.println(" # 접속자 (로그인 사용자수) : " + getActiveLoginSessionCount() + " 명#"); + System.out.println(" ############################################################################### "); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/listener/ThreadPoolContextLoaderListener.java b/src/main/java/com/xit/core/support/listener/ThreadPoolContextLoaderListener.java new file mode 100644 index 0000000..5a2bb1b --- /dev/null +++ b/src/main/java/com/xit/core/support/listener/ThreadPoolContextLoaderListener.java @@ -0,0 +1,134 @@ +package com.xit.core.support.listener; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * web.xml에 등록하여야 사용 할 수 있다 + * @author Administrator + * + */ +public class ThreadPoolContextLoaderListener implements ServletContextListener { + + private static final int NUMBER_OF_THREADS = 30; + public static final String THREADPOOL_ALIAS = "threadPoolAlias"; + + @Override + public void contextInitialized(ServletContextEvent event) { + final ExecutorService threadPool = Executors.newFixedThreadPool(NUMBER_OF_THREADS); // starts thread pool + final ServletContext servletContext = event.getServletContext(); + servletContext.setAttribute(THREADPOOL_ALIAS, threadPool); + } + + @Override + public void contextDestroyed(ServletContextEvent event) { + + final ExecutorService threadPool = (ExecutorService) event.getServletContext().getAttribute(THREADPOOL_ALIAS); + threadPool.shutdownNow(); + } +} + + +/* + +package com.gsshop.base.jfile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.PrintWriter; +import java.util.List; +import java.util.concurrent.ExecutorService; + +import jwork.web.modules.jfile.service.FileUploadCompletedEventListener; + +import org.apache.commons.net.PrintCommandListener; +import org.apache.commons.net.ftp.FTP; +import org.apache.commons.net.ftp.FTPClient; +import org.apache.commons.net.ftp.FTPReply; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; +import org.springframework.web.context.ContextLoader; + +import com.gsshop.base.listener.ThreadPoolContextLoaderListener; +import com.gsshop.common.Globals; + +public abstract class AbstractFTPUploadTemplate implements FileUploadCompletedEventListener { + + Logger logger = LoggerFactory.getLogger(getClass()); + + @Override + public void uploadCompleted(final String fileId, final String sourceRepositoryPath, + final String savedFileName, final String originalFileName, final String beanId, + final String etc01, final String etc02, final String etc03, final String etc04, final String etc05) { + + logger.debug("fileId : {}", fileId); + logger.debug("sourceRepositoryPath : {}", sourceRepositoryPath); + logger.debug("maskingFileName : {}", savedFileName); + logger.debug("originalFileName : {}", originalFileName); + logger.debug("beanId : {}", beanId); + + ExecutorService threadPool = (ExecutorService)ContextLoader.getCurrentWebApplicationContext().getServletContext().getAttribute(ThreadPoolContextLoaderListener.THREADPOOL_ALIAS); + + threadPool.execute(new Runnable() { + @Override + public void run() { + + FTPClient ftp = new FTPClient(); + ftp.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out))); + + try { + ftp.connect(Globals.PRD_FTP_WEBSERVER_IP); + int reply = ftp.getReplyCode(); + if (FTPReply.isPositiveCompletion(reply)) { + ftp.login(Globals.PRD_FTP_WEBSERVER_USER, Globals.PRD_FTP_WEBSERVER_PASSWORD); + if(logger.isDebugEnabled()) { + logger.debug(Globals.PRD_FTP_WEBSERVER_IP+" connect success !!! "); + } + ftp.setFileType(FTP.BINARY_FILE_TYPE); + + ftp.changeWorkingDirectory(Globals.PRD_FTP_WEBSERVER_UPLOAD_PATH); + + if(StringUtils.hasText(etc01)) { + String[] tokens = etc01.split("\\/"); + + String tempWorkingDirectory = Globals.PRD_FTP_WEBSERVER_UPLOAD_PATH; + for(String token : tokens) { + tempWorkingDirectory = tempWorkingDirectory+"/"+token; + ftp.makeDirectory(tempWorkingDirectory); + ftp.changeWorkingDirectory(tempWorkingDirectory); + } + } + + List list = getFTPUploadFileVO(fileId, sourceRepositoryPath,savedFileName, originalFileName, beanId,etc01, etc02, etc03, etc04, etc05); + if(list != null) { + for(FTPUploadFileVO vo : list) { + ftp.storeFile(vo.getRemoteFileName(), new FileInputStream(new File(sourceRepositoryPath+"/"+vo.getLocalFileName()))); + } + } + + if(logger.isDebugEnabled()) {logger.debug(" ftp transfer success !!! ");} + } + else { + ftp.disconnect(); + } + if(ftp.isConnected()) { + ftp.logout(); + ftp.disconnect(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + abstract public List getFTPUploadFileVO(String fileId, final String sourceRepositoryPath, + final String savedFileName, String originalFileName, String beanId, + String etc01, String etc02, String etc03, String etc04, String etc05); + +} +*/ \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/listener/UserDupCheckListener.java b/src/main/java/com/xit/core/support/listener/UserDupCheckListener.java new file mode 100644 index 0000000..45031bb --- /dev/null +++ b/src/main/java/com/xit/core/support/listener/UserDupCheckListener.java @@ -0,0 +1,68 @@ +package com.xit.core.support.listener; + +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Set; + +public class UserDupCheckListener implements HttpSessionListener { + private static UserDupCheckListener userDupCheckListener; + private static HashMap sessionMap; + + public UserDupCheckListener() { + if (sessionMap == null) sessionMap = new HashMap(); + userDupCheckListener = this; + } + + public static synchronized UserDupCheckListener getInstance() { + if (userDupCheckListener == null) userDupCheckListener = new UserDupCheckListener(); + return userDupCheckListener; + } + + public boolean isLogin(String sessionId, String userId) { +System.out.printf("Login Check :: userId:%s\n", userId); + if(sessionMap.containsValue(userId)){ +System.out.print("&&&&&&&&&&&&& Login User!!!\n"); + return true; + + }else{ + sessionMap.put(sessionId, userId); +System.out.printf("++++++++++++++Not Login User Session add :: %s=%s\n", sessionId, sessionMap.get(sessionId)); + return false; + } + } + + /** 세션 생성시 이벤트 처리 */ + public void sessionCreated(HttpSessionEvent event) { +System.out.printf("=====>>>>>sessionCreated : %s\n", event.getSession().getId()); + synchronized (sessionMap) { + sessionMap.put(event.getSession().getId(), "non-login"); +log(); + } + } + + /** 세션 소멸(logout, sessionTimeout)시 이벤트 처리 */ + public void sessionDestroyed(HttpSessionEvent event) { +System.out.printf("=====>>>>>sessionDestroyed:%s\n", event.getSession().getId()); + synchronized (sessionMap) { + sessionMap.remove(event.getSession().getId()); +log(); + } + } + + + + +private void log(){ + Set set = sessionMap.keySet(); + Iterator it = set.iterator(); +System.out.printf("================================================\n"); + while (it.hasNext()) { + String key = it.next(); + System.out.printf("%s=%s\n", key, sessionMap.get(key)); + } +System.out.printf("================================================\n"); +} + +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/mybatis/MybatisLanguageDriver.java b/src/main/java/com/xit/core/support/mybatis/MybatisLanguageDriver.java new file mode 100644 index 0000000..fc0554e --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/MybatisLanguageDriver.java @@ -0,0 +1,75 @@ +package com.xit.core.support.mybatis; + +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ParameterMapping; +import org.apache.ibatis.mapping.SqlSource; +import org.apache.ibatis.parsing.XNode; +import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver; +import org.apache.ibatis.session.Configuration; + +import java.lang.reflect.Field; +import java.util.List; + +/** + * Mybatis sql logging + * mybatis.configuration.default-scripting-language=com.xit.core.support.mybatis.MybatisLanguageDriver + */ +@Slf4j +public class MybatisLanguageDriver extends XMLLanguageDriver { + public MybatisLanguageDriver() { + } + + @Override + public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { + addDebuggingComment(boundSql); + return super.createParameterHandler(mappedStatement, parameterObject, boundSql); + } + + @Override + public SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType) { + return super.createSqlSource(configuration, script, parameterType); + } + + @Override + public SqlSource createSqlSource(Configuration configuration, String script, Class parameterType) { + return super.createSqlSource(configuration, script, parameterType); + } + + @SneakyThrows + private void addDebuggingComment(BoundSql boundSql) { + Field sqlField = BoundSql.class.getDeclaredField("sql"); + sqlField.setAccessible(true); + + String sql = (String) sqlField.get(boundSql); + List parameterMappings = boundSql.getParameterMappings(); + sql = addParameterComment(sql, parameterMappings); + + sqlField.set(boundSql, sql); + } + + private String addParameterComment(String sql, List parameters) { + StringBuilder sqlInternalStringBuilder = new StringBuilder(sql); + + int paramReverseIndex = parameters.size() - 1; + for (int idx = sql.length() - 1; idx > 0; idx--) { + char c = sql.charAt(idx); + if (c == '?') { + String commentedString = toCommentString(parameters.get(paramReverseIndex).getProperty()); + + sqlInternalStringBuilder.insert(idx + 1, commentedString); + paramReverseIndex = paramReverseIndex - 1; + } + } + + return sqlInternalStringBuilder.toString(); + } + + private String toCommentString(String comment) { + return " /*" + comment + "*/ "; + } + +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/mybatis/paging/CleanupMybatisPaginatorListener.java b/src/main/java/com/xit/core/support/mybatis/paging/CleanupMybatisPaginatorListener.java new file mode 100644 index 0000000..ad3df63 --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/CleanupMybatisPaginatorListener.java @@ -0,0 +1,14 @@ +package com.xit.core.support.mybatis.paging; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +public class CleanupMybatisPaginatorListener implements ServletContextListener { + public void contextInitialized(ServletContextEvent servletContextEvent) { + + } + + public void contextDestroyed(ServletContextEvent servletContextEvent) { + OffsetLimitInterceptor.Pool.shutdownNow(); + } +} diff --git a/src/main/java/com/xit/core/support/mybatis/paging/MybatisUtils.java b/src/main/java/com/xit/core/support/mybatis/paging/MybatisUtils.java new file mode 100644 index 0000000..f6e67a5 --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/MybatisUtils.java @@ -0,0 +1,41 @@ +package com.xit.core.support.mybatis.paging; + +import com.xit.core.support.mybatis.paging.domain.Order; +import com.xit.core.support.mybatis.paging.domain.PageBounds; +import com.xit.core.util.Checks; +import org.apache.ibatis.session.RowBounds; + +import java.util.Map; + +public class MybatisUtils{ + public static RowBounds getPagingInfo(Map map){ + if(Checks.isEmpty(map)){ + return new PageBounds(1, 10 , Order.formString("")); + } + int page = Checks.isEmpty(map.get("page"))? 1: Integer.parseInt(String.valueOf(map.get("page"))); + int limit = Checks.isEmpty(map.get("perPage"))? 20: Integer.parseInt(String.valueOf(map.get("perPage"))); + String sort = Checks.isEmpty(map.get("sidx"))? "": String.valueOf(map.get("sidx")); + return new PageBounds(page, limit , Order.formString(sort)); + } + +// public static RowBounds getPagingInfo(PagableVo bean){ +// if(bean == null){ +// return new PageBounds(1, 10 , Order.formString("")); +// } +// int page = Checks.isEmpty(bean.getPage()) ? 1: Integer.parseInt(bean.getPage()); +// int limit = Checks.isEmpty(bean.getPerPage())? 20: Integer.parseInt(String.valueOf(bean.getPerPage())); +// String sort = Checks.isEmpty(bean.getSidx())? "": String.valueOf(bean.getSidx()); +// return new PageBounds(page, limit , Order.formString(sort)); +// } + + public static RowBounds getPagingInfo(int page, int row, String sort){ + + return new PageBounds(page, row, Order.formString(sort)); + } + +// public static interface PagableVo { +// public String getPage(); +// public String getPerPage(); +// public String getSidx(); +// } +} diff --git a/src/main/java/com/xit/core/support/mybatis/paging/OffsetLimitInterceptor.java b/src/main/java/com/xit/core/support/mybatis/paging/OffsetLimitInterceptor.java new file mode 100644 index 0000000..3a778b8 --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/OffsetLimitInterceptor.java @@ -0,0 +1,222 @@ +package com.xit.core.support.mybatis.paging; + +import com.xit.core.constant.XitConstants; +import com.xit.core.support.mybatis.paging.dialect.Dialect; +import com.xit.core.support.mybatis.paging.dialect.LoggerHelper; +import com.xit.core.support.mybatis.paging.domain.PageBounds; +import com.xit.core.support.mybatis.paging.domain.Paginator; +import com.xit.core.support.mybatis.paging.support.PropertiesHelper; +import com.xit.core.support.mybatis.paging.support.SQLHelp; +import com.xit.core.util.Checks; +import org.apache.ibatis.cache.Cache; +import org.apache.ibatis.cache.CacheKey; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.MappedStatement.Builder; +import org.apache.ibatis.mapping.ParameterMapping; +import org.apache.ibatis.mapping.SqlSource; +import org.apache.ibatis.plugin.*; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; +import org.slf4j.Logger; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import java.lang.reflect.Constructor; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.*; + +/** + * Paging 정보 - Paginator(page, limit, totalcount)- 획득, 추가 + */ +@Intercepts({ @Signature(type = Executor.class, + method = "query", + args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }) }) +public class OffsetLimitInterceptor implements Interceptor { + private final Logger log = LoggerHelper.getLogger(); + static int MAPPED_STATEMENT_INDEX = 0; + static int PARAMETER_INDEX = 1; + static int ROWBOUNDS_INDEX = 2; + static int RESULT_HANDLER_INDEX = 3; + static ExecutorService Pool; + String dialectClass; + boolean asyncTotalCount = false; + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public Object intercept(final Invocation invocation) throws Throwable { + final Executor executor = (Executor) invocation.getTarget(); + final Object[] queryArgs = invocation.getArgs(); + final MappedStatement ms = (MappedStatement) queryArgs[MAPPED_STATEMENT_INDEX]; + final Object parameter = queryArgs[PARAMETER_INDEX]; + final RowBounds rowBounds = (RowBounds) queryArgs[ROWBOUNDS_INDEX]; + final PageBounds pageBounds = new PageBounds(rowBounds); + if (pageBounds.getOffset() == RowBounds.NO_ROW_OFFSET && pageBounds.getLimit() == RowBounds.NO_ROW_LIMIT && pageBounds.getOrders().isEmpty()) { + return invocation.proceed(); + } + + final Dialect dialect; + try { + Class clazz = Class.forName(dialectClass); + Constructor constructor = clazz.getConstructor(MappedStatement.class, Object.class, PageBounds.class); + dialect = (Dialect) constructor.newInstance(new Object[] { ms, parameter, pageBounds }); + } catch (Exception e) { + throw new ClassNotFoundException("Cannot create dialect instance: " + dialectClass, e); + } + + final BoundSql boundSql = ms.getBoundSql(parameter); + + queryArgs[MAPPED_STATEMENT_INDEX] = copyFromNewSql(ms, boundSql, dialect.getPageSQL(), dialect.getParameterMappings(), dialect.getParameterObject()); + queryArgs[PARAMETER_INDEX] = dialect.getParameterObject(); + queryArgs[ROWBOUNDS_INDEX] = new RowBounds(RowBounds.NO_ROW_OFFSET, RowBounds.NO_ROW_LIMIT); + + Boolean async = pageBounds.getAsyncTotalCount() == null ? asyncTotalCount : pageBounds.getAsyncTotalCount(); + Future listFuture = call(new Callable() { + public List call() throws Exception { + return (List) invocation.proceed(); + } + }, async); + + if (pageBounds.isContainsTotalCount()) { + Callable countTask = new Callable() { + public Object call() throws Exception { + Paginator paginator = null; + Integer count; + Cache cache = ms.getCache(); + if (cache != null && ms.isUseCache() && ms.getConfiguration().isCacheEnabled()) { + CacheKey cacheKey = executor.createCacheKey( + ms, + parameter, + new PageBounds(), + copyFromBoundSql(ms, boundSql, dialect.getCountSQL(), boundSql.getParameterMappings(), boundSql.getParameterObject())); + count = (Integer) cache.getObject(cacheKey); + if (count == null) { + count = SQLHelp.getCount(ms, parameter, boundSql, dialect); + cache.putObject(cacheKey, count); + } + } else { + count = SQLHelp.getCount(ms, parameter, boundSql, dialect); + } + paginator = new Paginator(pageBounds.getPage(), pageBounds.getLimit(), count); + return paginator; + } + }; + Future countFutrue = call(countTask, async); +// PageList pageList = new PageList(listFuture.get(), countFutrue.get()); + +// log.debug("###################################################################################"); +// log.debug("OffsetLimitInterceptor Page information ThreadLocal save::{} - {}", FEnum.Session.PAGE_INFO.getValue(), countFutrue.get()); +// log.debug("###################################################################################"); + RequestContextHolder.currentRequestAttributes().setAttribute(XitConstants.Session.PAGE_INFO.getValue(), countFutrue.get(), RequestAttributes.SCOPE_REQUEST); +// ContextThreadLocalHolder.setPageListThreadLocal(pageList); +// return pageList; return new PageList(listFuture.get(), countFutrue.get()); + } + return listFuture.get(); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private Future call(Callable callable, boolean async) { + if (async) { + return Pool.submit(callable); + } else { + FutureTask future = new FutureTask(callable); + future.run(); + return future; + } + } + + private MappedStatement copyFromNewSql(MappedStatement ms, BoundSql boundSql, String sql, List parameterMappings, Object parameter) { + BoundSql newBoundSql = copyFromBoundSql(ms, boundSql, sql, parameterMappings, parameter); + return copyFromMappedStatement(ms, new BoundSqlSqlSource(newBoundSql)); + } + + private BoundSql copyFromBoundSql(MappedStatement ms, BoundSql boundSql, String sql, List parameterMappings, Object parameter) { + BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql, parameterMappings, parameter); + for (ParameterMapping mapping : boundSql.getParameterMappings()) { + String prop = mapping.getProperty(); + if (boundSql.hasAdditionalParameter(prop)) { + newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop)); + } + } + return newBoundSql; + } + + // see: MapperBuilderAssistant + private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) { + Builder builder = new Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType()); + builder.resource(ms.getResource()); + builder.fetchSize(ms.getFetchSize()); + builder.statementType(ms.getStatementType()); + builder.keyGenerator(ms.getKeyGenerator()); + if (Checks.isNotEmpty(ms.getKeyProperties())) { + StringBuffer keyProperties = new StringBuffer(); + for (String keyProperty : ms.getKeyProperties()) { + keyProperties.append(keyProperty).append(","); + } + keyProperties.delete(keyProperties.length() - 1, keyProperties.length()); + builder.keyProperty(keyProperties.toString()); + } + + // setStatementTimeout() + builder.timeout(ms.getTimeout()); + + // setStatementResultMap() + builder.parameterMap(ms.getParameterMap()); + + // setStatementResultMap() + builder.resultMaps(ms.getResultMaps()); + builder.resultSetType(ms.getResultSetType()); + + // setStatementCache() + builder.cache(ms.getCache()); + builder.flushCacheRequired(ms.isFlushCacheRequired()); + builder.useCache(ms.isUseCache()); + + return builder.build(); + } + + public Object plugin(Object target) { + return Plugin.wrap(target, this); + } + + public void setProperties(Properties properties) { + PropertiesHelper propertiesHelper = new PropertiesHelper(properties); + String dialectClass = propertiesHelper.getRequiredString("dialectClass"); + setDialectClass(dialectClass); + setAsyncTotalCount(propertiesHelper.getBoolean("asyncTotalCount", false)); + setPoolMaxSize(propertiesHelper.getInt("poolMaxSize", 0)); + } + + public static class BoundSqlSqlSource implements SqlSource { + BoundSql boundSql; + + public BoundSqlSqlSource(BoundSql boundSql) { + this.boundSql = boundSql; + } + + public BoundSql getBoundSql(Object parameterObject) { + return boundSql; + } + } + + public void setDialectClass(String dialectClass) { + //logger.debug("dialectClass: {} ", dialectClass); + this.dialectClass = dialectClass; + } + + public void setAsyncTotalCount(boolean asyncTotalCount) { + //logger.debug("asyncTotalCount: {} ", asyncTotalCount); + this.asyncTotalCount = asyncTotalCount; + } + + public void setPoolMaxSize(int poolMaxSize) { + + if (poolMaxSize > 0) { + log.debug("poolMaxSize: {} ", poolMaxSize); + Pool = Executors.newFixedThreadPool(poolMaxSize); + } else { + Pool = Executors.newCachedThreadPool(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/mybatis/paging/dialect/DB2Dialect.java b/src/main/java/com/xit/core/support/mybatis/paging/dialect/DB2Dialect.java new file mode 100644 index 0000000..443468d --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/dialect/DB2Dialect.java @@ -0,0 +1,67 @@ +package com.xit.core.support.mybatis.paging.dialect; + +import com.xit.core.support.mybatis.paging.domain.PageBounds; +import org.apache.ibatis.mapping.MappedStatement; + +public class DB2Dialect extends Dialect { + + public DB2Dialect(MappedStatement mappedStatement, Object parameterObject, PageBounds pageBounds) { + super(mappedStatement, parameterObject, pageBounds); + } + + private static String getRowNumber(String sql) { + StringBuffer rownumber = new StringBuffer(50) + .append("rownumber() over("); + + int orderByIndex = sql.toLowerCase().indexOf("order by"); + + if (orderByIndex > 0 && !hasDistinct(sql)) { + rownumber.append(sql.substring(orderByIndex)); + } + + rownumber.append(") as rownumber_,"); + + return rownumber.toString(); + } + + private static boolean hasDistinct(String sql) { + return sql.toLowerCase().indexOf("select distinct") >= 0; + } + + protected String getLimitString(String sql, String offsetName, int offset, + String limitName, int limit) { + int startOfSelect = sql.toLowerCase().indexOf("select"); + + StringBuffer pagingSelect = new StringBuffer(sql.length() + 100) + .append(sql.substring(0, startOfSelect)) // add the comment + .append("select * from ( select ") // nest the main query in an + // outer select + .append(getRowNumber(sql)); // add the rownnumber bit into the + // outer query select list + + if (hasDistinct(sql)) { + pagingSelect.append(" row_.* from ( ") // add another (inner) nested + // select + .append(sql.substring(startOfSelect)) // add the main query + .append(" ) as row_"); // close off the inner nested select + } else { + pagingSelect.append(sql.substring(startOfSelect + 6)); // add the + // main + // query + } + + pagingSelect.append(" ) as temp_ where rownumber_ "); + + // add the restriction to the outer select + if (offset > 0) { + pagingSelect.append("between ?+1 and ?"); + setPageParameter(offsetName, offset, Integer.class); + setPageParameter("__offsetEnd", offset + limit, Integer.class); + } else { + pagingSelect.append("<= ?"); + setPageParameter(limitName, limit, Integer.class); + } + + return pagingSelect.toString(); + } +} diff --git a/src/main/java/com/xit/core/support/mybatis/paging/dialect/DerbyDialect.java b/src/main/java/com/xit/core/support/mybatis/paging/dialect/DerbyDialect.java new file mode 100644 index 0000000..86697de --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/dialect/DerbyDialect.java @@ -0,0 +1,32 @@ +package com.xit.core.support.mybatis.paging.dialect; + +import com.xit.core.support.mybatis.paging.domain.PageBounds; +import org.apache.ibatis.mapping.MappedStatement; + + +public class DerbyDialect extends Dialect { + + public DerbyDialect(MappedStatement mappedStatement, + Object parameterObject, PageBounds pageBounds) { + super(mappedStatement, parameterObject, pageBounds); + } + + public boolean supportsLimit() { + return true; + } + + public boolean supportsLimitOffset() { + return true; + } + + public String getLimitString(String sql, int offset, + String offsetPlaceholder, int limit, String limitPlaceholder) { + + return new StringBuffer(sql.length() + 30) + .append(sql) + .append(" offset " + offsetPlaceholder + " rows fetch next " + + limitPlaceholder + " rows only").toString(); + + } + +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/mybatis/paging/dialect/Dialect.java b/src/main/java/com/xit/core/support/mybatis/paging/dialect/Dialect.java new file mode 100644 index 0000000..505bb2c --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/dialect/Dialect.java @@ -0,0 +1,133 @@ +package com.xit.core.support.mybatis.paging.dialect; + +import com.xit.core.support.mybatis.paging.domain.Order; +import com.xit.core.support.mybatis.paging.domain.PageBounds; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ParameterMapping; +import org.apache.ibatis.session.RowBounds; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Dialect { + @SuppressWarnings("unused") + private static final Logger log = LoggerHelper.getLogger(); + + protected MappedStatement mappedStatement; + protected PageBounds pageBounds; + protected Object parameterObject; + protected BoundSql boundSql; + protected List parameterMappings; + protected Map pageParameters = new HashMap(); + + private String pageSQL; + private String countSQL; + + public Dialect(MappedStatement mappedStatement, Object parameterObject, PageBounds pageBounds) { + this.mappedStatement = mappedStatement; + this.parameterObject = parameterObject; + this.pageBounds = pageBounds; + init(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected void init() { + boundSql = mappedStatement.getBoundSql(parameterObject); + parameterMappings = new ArrayList(boundSql.getParameterMappings()); + if (parameterObject instanceof Map) { + pageParameters.putAll((Map) parameterObject); + } else { + for (ParameterMapping parameterMapping : parameterMappings) { + pageParameters.put(parameterMapping.getProperty(), parameterObject); + } + } + + StringBuffer bufferSql = new StringBuffer(boundSql.getSql().trim()); + if (bufferSql.lastIndexOf(";") == bufferSql.length() - 1) { + bufferSql.deleteCharAt(bufferSql.length() - 1); + } + String sql = bufferSql.toString(); + pageSQL = sql; + if (pageBounds.getOrders() != null && !pageBounds.getOrders().isEmpty()) { + pageSQL = getSortString(sql, pageBounds.getOrders()); + } +//log.debug("$$$$$$$$$$$$$$$ page::{}, offset::{}", pageBounds.getPage(), pageBounds.getOffset()); + if (pageBounds.getOffset() != RowBounds.NO_ROW_OFFSET || pageBounds.getLimit() != RowBounds.NO_ROW_LIMIT) { + pageSQL = getLimitString(pageSQL, "__offset", pageBounds.getOffset(), "__limit", pageBounds.getLimit()); + } + countSQL = getCountString(sql); + } + + public List getParameterMappings() { + return parameterMappings; + } + + public Object getParameterObject() { + return pageParameters; + } + + public String getPageSQL() { + return pageSQL; + } + + @SuppressWarnings("rawtypes") + protected void setPageParameter(String name, Object value, Class type) { + ParameterMapping parameterMapping = new ParameterMapping.Builder(mappedStatement.getConfiguration(), name, type).build(); + parameterMappings.add(parameterMapping); + pageParameters.put(name, value); + } + + public String getCountSQL() { + return countSQL; + } + + /** + * page query make + * + * @param sql sql + * @param offsetName offsetName + * @param offset offset + * @param limitName limitName + * @param limit limit + * @return String query + */ + protected String getLimitString(String sql, String offsetName, int offset, String limitName, int limit) { + throw new UnsupportedOperationException("paged queries not supported"); + } + + /** + * get total count SQL + * + * @param sql SQL + * @return String count sql + */ + protected String getCountString(String sql) { + return "select count(1) from (\n" + sql + "\n) tmp_count"; + } + + /** + * ordered sql + * + * @param sql SQL + * @param orders ordered field List + * @return 정렬구문까지 포함된 sql + */ + protected String getSortString(String sql, List orders) { + if (orders == null || orders.isEmpty()) { + return sql; + } + + StringBuffer buffer = new StringBuffer("select * from (\n").append(sql).append("\n) temp_order order by "); + for (Order order : orders) { + if (order != null) { + buffer.append(order.toString()).append(", "); + } + } + buffer.delete(buffer.length() - 2, buffer.length()); + return buffer.toString(); + } +} diff --git a/src/main/java/com/xit/core/support/mybatis/paging/dialect/H2Dialect.java b/src/main/java/com/xit/core/support/mybatis/paging/dialect/H2Dialect.java new file mode 100644 index 0000000..7738e59 --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/dialect/H2Dialect.java @@ -0,0 +1,21 @@ +package com.xit.core.support.mybatis.paging.dialect; + + +import com.xit.core.support.mybatis.paging.domain.PageBounds; +import org.apache.ibatis.mapping.MappedStatement; + +public class H2Dialect extends Dialect { + + public H2Dialect(MappedStatement mappedStatement, Object parameterObject, PageBounds pageBounds) { + super(mappedStatement, parameterObject, pageBounds); + } + + protected String getLimitString(String sql, String offsetName,int offset, String limitName, int limit) { + return new StringBuffer(sql.length() + 40). + append(sql). + append((offset > 0) ? " limit "+String.valueOf(limit)+" offset "+String.valueOf(offset) : " limit "+String.valueOf(limit)). + toString(); + } + + +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/mybatis/paging/dialect/HSQLDialect.java b/src/main/java/com/xit/core/support/mybatis/paging/dialect/HSQLDialect.java new file mode 100644 index 0000000..14b1312 --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/dialect/HSQLDialect.java @@ -0,0 +1,23 @@ +package com.xit.core.support.mybatis.paging.dialect; + +import com.xit.core.support.mybatis.paging.domain.PageBounds; +import org.apache.ibatis.mapping.MappedStatement; +import org.slf4j.Logger; + +public class HSQLDialect extends Dialect { + @SuppressWarnings("unused") + private static final Logger log = LoggerHelper.getLogger(); + + public HSQLDialect(MappedStatement mappedStatement, Object parameterObject, PageBounds pageBounds) { + super(mappedStatement, parameterObject, pageBounds); + } + + protected String getLimitString(String sql, String offsetName, int offset, String limitName, int limit) { +// log.debug("$$$$$$$$$$$$$$$ offset::{}, limit::{}", offset, limit); + boolean hasOffset = offset > 0; + return new StringBuffer(sql.length() + 10) + .append(sql) + .insert(sql.toLowerCase().indexOf("select") + 6, + hasOffset ? " limit " + String.valueOf(offset) + " " + String.valueOf(limit) : " top " + String.valueOf(limit)).toString(); + } +} diff --git a/src/main/java/com/xit/core/support/mybatis/paging/dialect/LoggerHelper.java b/src/main/java/com/xit/core/support/mybatis/paging/dialect/LoggerHelper.java new file mode 100644 index 0000000..919f4b6 --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/dialect/LoggerHelper.java @@ -0,0 +1,25 @@ +package com.xit.core.support.mybatis.paging.dialect; + +//import org.apache.log4j.Logger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LoggerHelper { + + /** + * Returns a org.slf4j.Logger logger configured as the calling class. This ensures + * copy-paste safe code to get a logger instance, an ensures the logger is + * always fetched in a consistent manner. + * usage: + * private final static Logger log = LoggerHelper.getLogger(); + * + * Since the logger is found by accessing the call stack it is important, + * that references are static. + * @return Logger Logger + */ + public static Logger getLogger() { + final Throwable t = new Throwable(); + t.fillInStackTrace(); + return LoggerFactory.getLogger(t.getStackTrace()[1].getClassName()); + } +} diff --git a/src/main/java/com/xit/core/support/mybatis/paging/dialect/MySQLDialect.java b/src/main/java/com/xit/core/support/mybatis/paging/dialect/MySQLDialect.java new file mode 100644 index 0000000..a2ea2ec --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/dialect/MySQLDialect.java @@ -0,0 +1,25 @@ +package com.xit.core.support.mybatis.paging.dialect; + +import com.xit.core.support.mybatis.paging.domain.PageBounds; +import org.apache.ibatis.mapping.MappedStatement; + +public class MySQLDialect extends Dialect { + + public MySQLDialect(MappedStatement mappedStatement, Object parameterObject, PageBounds pageBounds) { + super(mappedStatement, parameterObject, pageBounds); + } + + protected String getLimitString(String sql, String offsetName, int offset, String limitName, int limit) { + StringBuffer buffer = new StringBuffer(sql.length() + 20).append(sql); + if (offset > 0) { + buffer.append("\n limit ?, ?"); + setPageParameter(offsetName, offset, Integer.class); + setPageParameter(limitName, limit, Integer.class); + } else { + buffer.append("\n limit ?"); + setPageParameter(limitName, limit, Integer.class); + } + return buffer.toString(); + } + +} diff --git a/src/main/java/com/xit/core/support/mybatis/paging/dialect/OracleDialect.java b/src/main/java/com/xit/core/support/mybatis/paging/dialect/OracleDialect.java new file mode 100644 index 0000000..4036ed4 --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/dialect/OracleDialect.java @@ -0,0 +1,43 @@ +package com.xit.core.support.mybatis.paging.dialect; + +import com.xit.core.support.mybatis.paging.domain.PageBounds; +import org.apache.ibatis.mapping.MappedStatement; + +public class OracleDialect extends Dialect { + + public OracleDialect(MappedStatement mappedStatement, Object parameterObject, PageBounds pageBounds) { + super(mappedStatement, parameterObject, pageBounds); + } + + protected String getLimitString(String sql, String offsetName, int offset, String limitName, int limit) { + sql = sql.trim(); + boolean isForUpdate = false; + if (sql.toLowerCase().endsWith(" for update")) { + sql = sql.substring(0, sql.length() - 11); + isForUpdate = true; + } + + StringBuffer pagingSelect = new StringBuffer(sql.length() + 100); + if (offset > 0) { + pagingSelect.append("select * from ( select row_.*, rownum rownum_ from ( "); + } else { + pagingSelect.append("select * from ( "); + } + pagingSelect.append(sql); + if (offset > 0) { + pagingSelect.append(" ) row_ ) where rownum_ <= ? and rownum_ > ?"); + setPageParameter("__offsetEnd", offset + limit, Integer.class); + setPageParameter(offsetName, offset, Integer.class); + } else { + pagingSelect.append(" ) where rownum <= ?"); + setPageParameter(limitName, limit, Integer.class); + } + + if (isForUpdate) { + pagingSelect.append(" for update"); + } + + return pagingSelect.toString(); + } + +} diff --git a/src/main/java/com/xit/core/support/mybatis/paging/dialect/PostgreSQLDialect.java b/src/main/java/com/xit/core/support/mybatis/paging/dialect/PostgreSQLDialect.java new file mode 100644 index 0000000..d4c980b --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/dialect/PostgreSQLDialect.java @@ -0,0 +1,29 @@ +package com.xit.core.support.mybatis.paging.dialect; + +import com.xit.core.support.mybatis.paging.domain.PageBounds; +import org.apache.ibatis.mapping.MappedStatement; + +/** + * @author badqiu + * @author miemiedev + */ +public class PostgreSQLDialect extends Dialect{ + + public PostgreSQLDialect(MappedStatement mappedStatement, Object parameterObject, PageBounds pageBounds) { + super(mappedStatement, parameterObject, pageBounds); + } + + protected String getLimitString(String sql, String offsetName,int offset, String limitName, int limit) { + StringBuffer buffer = new StringBuffer( sql.length()+20 ).append(sql); + if(offset > 0){ + buffer.append(" limit ? offset ?"); + setPageParameter(limitName, limit, Integer.class); + setPageParameter(offsetName, offset, Integer.class); + + }else{ + buffer.append(" limit ?"); + setPageParameter(limitName, limit, Integer.class); + } + return buffer.toString(); + } +} diff --git a/src/main/java/com/xit/core/support/mybatis/paging/dialect/SQLServer2005Dialect.java b/src/main/java/com/xit/core/support/mybatis/paging/dialect/SQLServer2005Dialect.java new file mode 100644 index 0000000..19f042a --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/dialect/SQLServer2005Dialect.java @@ -0,0 +1,74 @@ +package com.xit.core.support.mybatis.paging.dialect; + +import com.xit.core.support.mybatis.paging.domain.PageBounds; +import org.apache.ibatis.mapping.MappedStatement; + +// Hibernate BUG: http://opensource.atlassian.com/projects/hibernate/browse/HHH-2655 +// SQLServer2005Dialect +public class SQLServer2005Dialect extends Dialect{ + + public SQLServer2005Dialect(MappedStatement mappedStatement, Object parameterObject, PageBounds pageBounds) { + super(mappedStatement, parameterObject, pageBounds); + } + + + /** + * Add a LIMIT clause to the given SQL SELECT + * The LIMIT SQL will look like: + * WITH query AS (SELECT TOP 100 percent ROW_NUMBER() OVER (ORDER BY CURRENT_TIMESTAMP) as __row_number__, * from table_name) + * SELECT * FROM query WHERE __row_number__ BETWEEN :offset and :lastRows ORDER BY __row_number__ + * + * @param sql The SQL statement to base the limit query off of. + * @param offset Offset of the first row to be returned by the query (zero-based) + * @param limit Maximum number of rows to be returned by the query + * @return A new SQL statement with the LIMIT clause applied. + */ + protected String getLimitString(String sql, String offsetName,int offset, String limitName, int limit) { + StringBuffer pagingBuilder = new StringBuffer(); + String orderby = getOrderByPart(sql); + String distinctStr = ""; + + String loweredString = sql.toLowerCase(); + String sqlPartString = sql; + if (loweredString.trim().startsWith("select")) { + int index = 6; + if (loweredString.startsWith("select distinct")) { + distinctStr = "DISTINCT "; + index = 15; + } + sqlPartString = sqlPartString.substring(index); + } + pagingBuilder.append(sqlPartString); + + // if no ORDER BY is specified use fake ORDER BY field to avoid errors + if (orderby == null || orderby.length() == 0) { + orderby = "ORDER BY CURRENT_TIMESTAMP"; + } + + StringBuffer result = new StringBuffer(); + result.append("WITH query AS (SELECT ") + .append(distinctStr) + .append("TOP 100 PERCENT ") + .append(" ROW_NUMBER() OVER (") + .append(orderby) + .append(") as __row_number__, ") + .append(pagingBuilder) + .append(") SELECT * FROM query WHERE __row_number__ > ? AND __row_number__ <= ?") + .append(" ORDER BY __row_number__"); + setPageParameter(offsetName,offset,Integer.class); + setPageParameter("__offsetEnd",offset+limit,Integer.class); + return result.toString(); + } + + static String getOrderByPart(String sql) { + String loweredString = sql.toLowerCase(); + int orderByIndex = loweredString.indexOf("order by"); + if (orderByIndex != -1) { + // if we find a new "order by" then we need to ignore + // the previous one since it was probably used for a subquery + return sql.substring(orderByIndex); + } else { + return ""; + } + } +} diff --git a/src/main/java/com/xit/core/support/mybatis/paging/dialect/SQLServerDialect.java b/src/main/java/com/xit/core/support/mybatis/paging/dialect/SQLServerDialect.java new file mode 100644 index 0000000..f4a1f25 --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/dialect/SQLServerDialect.java @@ -0,0 +1,27 @@ +package com.xit.core.support.mybatis.paging.dialect; + +import com.xit.core.support.mybatis.paging.domain.PageBounds; +import org.apache.ibatis.mapping.MappedStatement; + +public class SQLServerDialect extends Dialect{ + + public SQLServerDialect(MappedStatement mappedStatement, Object parameterObject, PageBounds pageBounds) { + super(mappedStatement, parameterObject, pageBounds); + } + + + static int getAfterSelectInsertPoint(String sql) { + int selectIndex = sql.toLowerCase().indexOf( "select" ); + final int selectDistinctIndex = sql.toLowerCase().indexOf( "select distinct" ); + return selectIndex + ( selectDistinctIndex == selectIndex ? 15 : 6 ); + } + + + protected String getLimitString(String sql, String offsetName,int offset, String limitName, int limit) { + if ( offset > 0 ) { + throw new UnsupportedOperationException( "sql server has no offset" ); + } + setPageParameter(limitName, limit, Integer.class); + return new StringBuffer( sql.length() + 8 ).append( sql ).insert( getAfterSelectInsertPoint( sql ), " top " + limit ).toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/mybatis/paging/dialect/SybaseDialect.java b/src/main/java/com/xit/core/support/mybatis/paging/dialect/SybaseDialect.java new file mode 100644 index 0000000..1328989 --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/dialect/SybaseDialect.java @@ -0,0 +1,17 @@ +package com.xit.core.support.mybatis.paging.dialect; + +import com.xit.core.support.mybatis.paging.domain.PageBounds; +import org.apache.ibatis.mapping.MappedStatement; + +public class SybaseDialect extends Dialect{ + + public SybaseDialect(MappedStatement mappedStatement, Object parameterObject, PageBounds pageBounds) { + super(mappedStatement, parameterObject, pageBounds); + } + + + protected String getLimitString(String sql, String offsetName,int offset, String limitName, int limit) { + throw new UnsupportedOperationException( "paged queries not supported" ); + } + +} diff --git a/src/main/java/com/xit/core/support/mybatis/paging/domain/Order.java b/src/main/java/com/xit/core/support/mybatis/paging/domain/Order.java new file mode 100644 index 0000000..3fc55f0 --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/domain/Order.java @@ -0,0 +1,154 @@ +package com.xit.core.support.mybatis.paging.domain; + +import org.apache.commons.lang3.StringUtils; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.regex.Pattern; + +public class Order implements Serializable { + private static final long serialVersionUID = 1L; + + private Direction direction; + private String property; + private String orderExpr; + + public Order(String property, Direction direction, String orderExpr) { + this.direction = direction; + this.property = property; + this.orderExpr = orderExpr; + } + + public Direction getDirection() { + return direction; + } + + public String getProperty() { + return property; + } + + public String getOrderExpr() { + return orderExpr; + } + + public void setDirection(Direction direction) { + this.direction = direction; + } + + public void setProperty(String property) { + this.property = property; + } + + public void setOrderExpr(String orderExpr) { + this.orderExpr = orderExpr; + } + + private static String INJECTION_REGEX = "[A-Za-z0-9\\_\\-\\+\\.]+"; + + public static boolean isSQLInjection(String str) { + return !Pattern.matches(INJECTION_REGEX, str); + } + + @Override + public String toString() { + if (isSQLInjection(property)) { + throw new IllegalArgumentException("SQLInjection property: " + property); + } + if (orderExpr != null && orderExpr.indexOf("?") != -1) { + String[] exprs = orderExpr.split("\\?"); + if (exprs.length == 2) { + return String.format(orderExpr.replaceAll("\\?", "%s"), property) + (direction == null ? "" : " " + direction.name()); + } + return String.format(orderExpr.replaceAll("\\?", "%s"), property, direction == null ? "" : " " + direction.name()); + } + return property + (direction == null ? "" : " " + direction.name()); + } + + public static List formString(String orderSegment) { + return formString(orderSegment, null); + } + + /** + * @param orderSegment + * ex: "id.asc,code.desc" or "code.desc" + * + * @param orderSegment orderSegment + * @param orderExpr orderExpr + * @return List List + */ + public static List formString(String orderSegment, String orderExpr) { + if (Objects.isNull(orderSegment) || Objects.equals(StringUtils.EMPTY, orderSegment.trim())) { + return new ArrayList(0); + } + + List results = new ArrayList(); + String[] orderSegments = orderSegment.trim().split(","); + for (int i = 0; i < orderSegments.length; i++) { + String sortSegment = orderSegments[i]; + Order order = _formString(sortSegment, orderExpr); + if (order != null) { + results.add(order); + } + } + return results; + } + + private static Order _formString(String orderSegment, String orderExpr) { + + if (Objects.isNull(orderSegment) || Objects.equals(StringUtils.EMPTY, orderSegment.trim()) + || orderSegment.startsWith("null.") + || orderSegment.startsWith(".")) { + return null; + } + + String[] array = orderSegment.trim().split("\\."); + if (array.length != 1 && array.length != 2) { + throw new IllegalArgumentException( + "orderSegment pattern must be {property}.{direction}, input is: " + orderSegment); + } + + return create(array[0], array.length == 2 ? array[1] : "asc", orderExpr); + } + + public static Order create(String property, String direction) { + return create(property, direction, null); + } + + /** + * + * @param property property + * @param direction direction + * @param orderExpr + * Oracle에서 query시에 특정 column의 대소문자 구분없이 정렬하는 방법 (case-insensitive sort) + * placeholder is "?", in oracle like: + * "nlssort( ? ,'NLS_SORT=SCHINESE_PINYIN_M')". Warning: you must + * prevent orderExpr SQL injection. + * Oracle session level에서 "nls_sort"의 값을 다음과 같이 설정함으로서 "nlssort"함수를 번번히 사용할 필요는 없습니다. + * SQL: alter session set nls_sort=generic_m_ai; + * Session altered. + * @return Order Order + */ + public static Order create(String property, String direction, String orderExpr) { + return new Order(property, Direction.fromString(direction), orderExpr); + } + + /** + * PropertyPath implements the pairing of an {@link Direction} and a + * property. It is used to provide input for + * + * @author Oliver Gierke + */ + public static enum Direction { + ASC, DESC; + public static Direction fromString(String value) { + try { + return Direction.valueOf(value.toUpperCase(Locale.US)); + } catch (Exception e) { + return ASC; + } + } + } +} diff --git a/src/main/java/com/xit/core/support/mybatis/paging/domain/PageBounds.java b/src/main/java/com/xit/core/support/mybatis/paging/domain/PageBounds.java new file mode 100644 index 0000000..7fb7de3 --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/domain/PageBounds.java @@ -0,0 +1,158 @@ +package com.xit.core.support.mybatis.paging.domain; + +import org.apache.ibatis.session.RowBounds; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * TODO 프로젝트에 맞춰 페이지 정보 내용 define + * new PageBounds(limit) + * new PageBounds(page, limit) + * new PageBounds("field, field") + * new PageBounds(page, limit, "field, field") + * new PageBounds(page, limit, "field.asc, filed.desc") + * @author minuk + * + */ +public class PageBounds extends RowBounds implements Serializable { + private static final long serialVersionUID = 1L; + + public final static int NO_PAGE = 1; + protected int page = NO_PAGE; + protected int limit = NO_ROW_LIMIT; + protected List orders = new ArrayList(); + protected boolean containsTotalCount; + protected Boolean asyncTotalCount; + + public PageBounds() { + containsTotalCount = false; + } + + public PageBounds(RowBounds rowBounds) { + if (rowBounds instanceof PageBounds) { + PageBounds pageBounds = (PageBounds) rowBounds; + this.page = pageBounds.page; + this.limit = pageBounds.limit; + this.orders = pageBounds.orders; + this.containsTotalCount = pageBounds.containsTotalCount; + this.asyncTotalCount = pageBounds.asyncTotalCount; + } else { + this.page = (rowBounds.getOffset() / rowBounds.getLimit()) + 1; + this.limit = rowBounds.getLimit(); + } + + } + + /** + * Query TOP N, default containsTotalCount = false + * @param limit limit + */ + public PageBounds(int limit) { + this.limit = limit; + this.containsTotalCount = false; + } + + public PageBounds(int page, int limit) { + this(page, limit, new ArrayList(), true); + } + + public PageBounds(int page, int limit, boolean containsTotalCount) { + this(page, limit, new ArrayList(), containsTotalCount); + } + + /** + * Just sorting, default containsTotalCount = false + * @param orders orders + */ + public PageBounds(List orders) { + this(NO_PAGE, NO_ROW_LIMIT, orders, false); + } + + /** + * Just sorting, default containsTotalCount = false + * @param order order + */ + public PageBounds(Order... order) { + this(NO_PAGE, NO_ROW_LIMIT, order); + this.containsTotalCount = false; + } + + public PageBounds(int page, int limit, Order... order) { + this(page, limit, Arrays.asList(order), true); + } + + public PageBounds(int page, int limit, List orders) { + this(page, limit, orders, true); + } + + public PageBounds(int page, int limit, List orders, + boolean containsTotalCount) { + this.page = page; + this.limit = limit; + this.orders = orders; + this.containsTotalCount = containsTotalCount; + } + + public int getPage() { + return page; + } + + public void setPage(int page) { + this.page = page; + } + + public int getLimit() { + return limit; + } + + public void setLimit(int limit) { + this.limit = limit; + } + + public boolean isContainsTotalCount() { + return containsTotalCount; + } + + public void setContainsTotalCount(boolean containsTotalCount) { + this.containsTotalCount = containsTotalCount; + } + + public List getOrders() { + return orders; + } + + public void setOrders(List orders) { + this.orders = orders; + } + + public Boolean getAsyncTotalCount() { + return asyncTotalCount; + } + + public void setAsyncTotalCount(Boolean asyncTotalCount) { + this.asyncTotalCount = asyncTotalCount; + } + + @Override + public int getOffset() { + if (page >= 1) { + return (page - 1) * limit; + } + return 0; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("PageBounds{"); + sb.append("page=").append(page); + sb.append(", limit=").append(limit); + sb.append(", orders=").append(orders); + sb.append(", containsTotalCount=").append(containsTotalCount); + sb.append(", asyncTotalCount=").append(asyncTotalCount); + sb.append('}'); + return sb.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/mybatis/paging/domain/PageList.java b/src/main/java/com/xit/core/support/mybatis/paging/domain/PageList.java new file mode 100644 index 0000000..87e38e5 --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/domain/PageList.java @@ -0,0 +1,31 @@ +package com.xit.core.support.mybatis.paging.domain; + +import java.util.ArrayList; +import java.util.Collection; + +public class PageList extends ArrayList { + private static final long serialVersionUID = 1412759446332294208L; + + private Paginator paginator; + + public PageList() { + } + + public PageList(Collection c) { + super(c); + } + + public PageList(Collection c, Paginator p) { + super(c); + this.paginator = p; + } + + public PageList(Paginator p) { + this.paginator = p; + } + + public Paginator getPaginator() { + return paginator; + } + +} diff --git a/src/main/java/com/xit/core/support/mybatis/paging/domain/Paginator.java b/src/main/java/com/xit/core/support/mybatis/paging/domain/Paginator.java new file mode 100644 index 0000000..b0c34ee --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/domain/Paginator.java @@ -0,0 +1,144 @@ +package com.xit.core.support.mybatis.paging.domain; + +import com.xit.core.constant.XitConstants; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +import java.io.Serializable; + +public class Paginator implements Serializable { + private static final long serialVersionUID = 1L; + + private int limit; + private int page = 1; + private int totalCount; + + public Paginator(int page, int limit, int totalCount) { + super(); + this.limit = limit; + this.totalCount = totalCount; + this.page = computePageNo(page); + } + + public void setSessionPagination(Paginator paginator){ + RequestContextHolder.currentRequestAttributes().setAttribute(XitConstants.Session.PAGE_INFO.getValue(), paginator, RequestAttributes.SCOPE_REQUEST); + } + + public int getPage() { + return page; + } + + public void setPage(int page) { + this.page = page; + } + + public int getLimit() { + return limit; + } + + public int getTotalCount() { + return totalCount; + } + + public boolean isFirstPage() { + return page <= 1; + } + + public boolean isLastPage() { + return page >= getTotalPages(); + } + + public int getPrePage() { + if (isHasPrePage()) { + return page - 1; + } else { + return page; + } + } + + public int getNextPage() { + if (isHasNextPage()) { + return page + 1; + } else { + return page; + } + } + + public boolean isDisabledPage(int page) { + return ((page < 1) || (page > getTotalPages()) || (page == this.page)); + } + + public boolean isHasPrePage() { + return (page - 1 >= 1); + } + + public boolean isHasNextPage() { + return (page + 1 <= getTotalPages()); + } + + public int getStartRow() { + if (getLimit() <= 0 || totalCount <= 0) + return 0; + return page > 0 ? (page - 1) * getLimit() + 1 : 0; + } + + public int getEndRow() { + return page > 0 ? Math.min(limit * page, getTotalCount()) : 0; + } + + public int getOffset() { + return page > 0 ? (page - 1) * getLimit() : 0; + } + + public int getTotalPages() { + if (totalCount <= 0) { + return 0; + } + if (limit <= 0) { + return 0; + } + + int count = totalCount / limit; + if (totalCount % limit > 0) { + count++; + } + return count; + } + + protected int computePageNo(int page) { + return computePageNumber(page, limit, totalCount); + } + + private static int computeLastPageNumber(int totalItems, int pageSize) { + if (pageSize <= 0) + return 1; + int result = (int) (totalItems % pageSize == 0 ? totalItems / pageSize + : totalItems / pageSize + 1); + if (result <= 1) + result = 1; + return result; + } + + private static int computePageNumber(int page, int pageSize, int totalItems) { + if (page <= 1) { + return 1; + } + if (Integer.MAX_VALUE == page + || page > computeLastPageNumber(totalItems, pageSize)) { // last + // page + return computeLastPageNumber(totalItems, pageSize); + } + return page; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("Paginator"); + sb.append("{page=").append(page); + sb.append(", limit=").append(limit); + sb.append(", totalCount=").append(totalCount); + sb.append('}'); + return sb.toString(); + } +} diff --git a/src/main/java/com/xit/core/support/mybatis/paging/jackson2/PageListJsonMapper.java b/src/main/java/com/xit/core/support/mybatis/paging/jackson2/PageListJsonMapper.java new file mode 100644 index 0000000..69bf878 --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/jackson2/PageListJsonMapper.java @@ -0,0 +1,30 @@ +package com.xit.core.support.mybatis.paging.jackson2; + +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.module.SimpleModule; + +import com.xit.core.support.mybatis.paging.domain.PageList; + +/** + * Converter의 ObjectMapper 확장 + * ResponseBody 사용시 호출 + * Converter 실행시 Paging 정보 추가 return + * page, limit(row/page), totalCount, offset(쿼리조회시작row), startRow, prePage, nextPage, endRow, totalPages + * boolean 값 : firstPage, lastPage, hasPrePage, hasNextPage + * @author minuk + * + */ +@SuppressWarnings("serial") +public class PageListJsonMapper extends ObjectMapper{ + + public PageListJsonMapper() { + //JSON data pretty 정렬 + enable(SerializationFeature.INDENT_OUTPUT); + SimpleModule module = new SimpleModule("PageListJSONModule", new Version(1, 0, 0, null, null, null)); + //module.addSerializer(PageList.class, new PageListJsonSerializer(this)); + module.addSerializer(PageList.class, new PageListJsonSerializer()); + registerModule(module); + } +} diff --git a/src/main/java/com/xit/core/support/mybatis/paging/jackson2/PageListJsonSerializer.java b/src/main/java/com/xit/core/support/mybatis/paging/jackson2/PageListJsonSerializer.java new file mode 100644 index 0000000..ad8496d --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/jackson2/PageListJsonSerializer.java @@ -0,0 +1,54 @@ +package com.xit.core.support.mybatis.paging.jackson2; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.xit.core.constant.XitConstants; +import com.xit.core.support.mybatis.paging.dialect.LoggerHelper; +import com.xit.core.support.mybatis.paging.domain.PageList; +import com.xit.core.support.mybatis.paging.domain.Paginator; +import org.slf4j.Logger; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +//TODO PageListJsonSerializer - Converting시 data attribute name 정의 - property에서 읽어 온다 +@SuppressWarnings("rawtypes") +public class PageListJsonSerializer extends JsonSerializer{ + private final Logger log = LoggerHelper.getLogger(); + + @SuppressWarnings("unchecked") + @Override + public void serialize(PageList value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { + Paginator paginator = value.getPaginator(); + + Map map = new HashMap(); + map.put("totalCount", paginator.getTotalCount()); + map.put("totalPages", paginator.getTotalPages()); + map.put("page", paginator.getPage()); + map.put("limit", paginator.getLimit()); + // Query Data + map.put(XitConstants.RSLT_ATTR_NAME, new ArrayList(value)); +// map.put(DEFAULT_RESPONSE_BODY_DATA_NAME, new ArrayList(value)); + map.put("startRow", paginator.getStartRow()); + map.put("endRow", paginator.getEndRow()); + map.put("offset", paginator.getOffset()); +// map.put("slider", paginator.getSlider()); + map.put("prePage", paginator.getPrePage()); + map.put("nextPage", paginator.getNextPage()); + map.put("firstPage", paginator.isFirstPage()); + map.put("hasNextPage", paginator.isHasNextPage()); + map.put("hasPrePage", paginator.isHasPrePage()); + map.put("lastPage", paginator.isLastPage()); + +// mapper.writeValue(jgen, map); + jgen.writeObject(map); + log.debug("#########################################Paging infomation##########################################################"); + log.debug("{}", map); + log.debug("###################################################################################################"); + + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/mybatis/paging/support/DefaultParameterHandler.java b/src/main/java/com/xit/core/support/mybatis/paging/support/DefaultParameterHandler.java new file mode 100644 index 0000000..6a7e4d0 --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/support/DefaultParameterHandler.java @@ -0,0 +1,92 @@ +/* + * Copyright 2009-2012 The MyBatis Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.xit.core.support.mybatis.paging.support; + +import org.apache.ibatis.executor.ErrorContext; +import org.apache.ibatis.executor.ExecutorException; +import org.apache.ibatis.executor.parameter.ParameterHandler; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ParameterMapping; +import org.apache.ibatis.mapping.ParameterMode; +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.TypeHandler; +import org.apache.ibatis.type.TypeHandlerRegistry; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; + +public class DefaultParameterHandler implements ParameterHandler { + + private final TypeHandlerRegistry typeHandlerRegistry; + + private final MappedStatement mappedStatement; + private final Object parameterObject; + private BoundSql boundSql; + private Configuration configuration; + + public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { + this.mappedStatement = mappedStatement; + this.configuration = mappedStatement.getConfiguration(); + this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry(); + this.parameterObject = parameterObject; + this.boundSql = boundSql; + } + + public Object getParameterObject() { + return parameterObject; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void setParameters(PreparedStatement ps) throws SQLException { + ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); + List parameterMappings = boundSql.getParameterMappings(); + if (parameterMappings != null) { + MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject); + for (int i = 0; i < parameterMappings.size(); i++) { + ParameterMapping parameterMapping = parameterMappings.get(i); + if (parameterMapping.getMode() != ParameterMode.OUT) { + Object value; + String propertyName = parameterMapping.getProperty(); + if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params + value = boundSql.getAdditionalParameter(propertyName); + } else if (parameterObject == null) { + value = null; + } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { + value = parameterObject; + } else { + value = metaObject == null ? null : metaObject.getValue(propertyName); + } + TypeHandler typeHandler = parameterMapping.getTypeHandler(); + if (typeHandler == null) { + throw new ExecutorException( + "There was no TypeHandler found for parameter " + + propertyName + " of statement " + + mappedStatement.getId()); + } + JdbcType jdbcType = parameterMapping.getJdbcType(); + if (value == null && jdbcType == null) + jdbcType = configuration.getJdbcTypeForNull(); + typeHandler.setParameter(ps, i + 1, value, jdbcType); + } + } + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/mybatis/paging/support/PropertiesHelper.java b/src/main/java/com/xit/core/support/mybatis/paging/support/PropertiesHelper.java new file mode 100644 index 0000000..be464c5 --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/support/PropertiesHelper.java @@ -0,0 +1,409 @@ +package com.xit.core.support.mybatis.paging.support; + +import com.xit.core.util.Checks; +import org.apache.commons.lang3.StringUtils; + +import java.io.*; +import java.util.*; +import java.util.Map.Entry; + +/** + *
+ * used :
+ * public class ConnectionUtils {
+ *     static Properties properties = new Properties(); 
+ *     // ... do load properties 
+ *     
+ *     // delegate to properties
+ * 	   static PropertiesHelper props = new PropertiesHelper(properties);
+ *     public static Connection getConnection() {
+ *     		// use getRequiredProperty() 
+ *     		DriverManager.getConnection(props.getRequiredString("jdbc.url"));
+ *     }
+ * }
+ * new PropertiesHelper(properties,PropertiesHelper.SYSTEM_PROPERTIES_MODE_OVERRIDE)
+ * 
+ */ +public class PropertiesHelper { + /** Never check system properties. */ + public static final int SYSTEM_PROPERTIES_MODE_NEVER = 0; + + /** + * Check system properties if not resolvable in the specified properties. + * This is the default. + */ + public static final int SYSTEM_PROPERTIES_MODE_FALLBACK = 1; + + /** + * Check system properties first, before trying the specified properties. + * This allows system properties to override any other property source. + */ + public static final int SYSTEM_PROPERTIES_MODE_OVERRIDE = 2; + + private int systemPropertiesMode = SYSTEM_PROPERTIES_MODE_NEVER; + private Properties p; + + public PropertiesHelper(Properties p) { + setProperties(p); + } + + public PropertiesHelper(Properties p, int systemPropertiesMode) { + setProperties(p); + if(systemPropertiesMode != SYSTEM_PROPERTIES_MODE_NEVER && systemPropertiesMode != SYSTEM_PROPERTIES_MODE_FALLBACK && systemPropertiesMode != SYSTEM_PROPERTIES_MODE_OVERRIDE) { + throw new IllegalArgumentException("error systemPropertiesMode mode:"+systemPropertiesMode); + } + this.systemPropertiesMode = systemPropertiesMode; + } + + public Properties getProperties() { + return p; + } + + public void setProperties(Properties props) { + if(props == null) throw new IllegalArgumentException("properties must be not null"); + this.p = props; + } + + public String getRequiredString(String key) { + String value = getProperty(key); + if(isBlankString(value)) { + throw new IllegalStateException("required property is blank by key="+key); + } + return value; + } + + public String getNullIfBlank(String key) { + String value = getProperty(key); + if(isBlankString(value)) { + return null; + } + return value; + } + + public String getNullIfEmpty(String key) { + String value = getProperty(key); + if(Checks.isEmpty(value)) { + return null; + } + return value; + } + + /** + * System.getProperty(key) System.getenv(key) + * @param key key + * @return String + */ + public String getAndTryFromSystem(String key) { + String value = getProperty(key); + if(isBlankString(value)) { + value = getSystemProperty(key); + } + return value; + } + + private String getSystemProperty(String key) { + String value; + value = System.getProperty(key); + if(isBlankString(value)) { + value = System.getenv(key); + } + return value; + } + + public Integer getInteger(String key) { + String v = getProperty(key); + if(v == null){ + return null; + } + return Integer.parseInt(v); + } + + public int getInt(String key,int defaultValue) { + if(getProperty(key) == null) { + return defaultValue; + } + return Integer.parseInt(getRequiredString(key)); + } + + public int getRequiredInt(String key) { + return Integer.parseInt(getRequiredString(key)); + } + + public Long getLong(String key) { + if(getProperty(key) == null) { + return null; + } + return Long.parseLong(getRequiredString(key)); + } + + public long getLong(String key,long defaultValue) { + if(getProperty(key) == null) { + return defaultValue; + } + return Long.parseLong(getRequiredString(key)); + } + + public Long getRequiredLong(String key) { + return Long.parseLong(getRequiredString(key)); + } + + public Boolean getBoolean(String key) { + if(getProperty(key) == null) { + return null; + } + return Boolean.parseBoolean(getRequiredString(key)); + } + + public boolean getBoolean(String key,boolean defaultValue) { + if(getProperty(key) == null) { + return defaultValue; + } + return Boolean.parseBoolean(getRequiredString(key)); + } + + public boolean getRequiredBoolean(String key) { + return Boolean.parseBoolean(getRequiredString(key)); + } + + public Float getFloat(String key) { + if(getProperty(key) == null) { + return null; + } + return Float.parseFloat(getRequiredString(key)); + } + + public float getFloat(String key,float defaultValue) { + if(getProperty(key) == null) { + return defaultValue; + } + return Float.parseFloat(getRequiredString(key)); + } + + public Float getRequiredFloat(String key) { + return Float.parseFloat(getRequiredString(key)); + } + + public Double getDouble(String key) { + if(getProperty(key) == null) { + return null; + } + return Double.parseDouble(getRequiredString(key)); + } + + public double getDouble(String key,double defaultValue) { + if(getProperty(key) == null) { + return defaultValue; + } + return Double.parseDouble(getRequiredString(key)); + } + + public Double getRequiredDouble(String key) { + return Double.parseDouble(getRequiredString(key)); + } + + //---------- setProperty(String key,int value) ... start ---------------// + public Object setProperty(String key,int value) { + return setProperty(key, String.valueOf(value)); + } + + public Object setProperty(String key,long value) { + return setProperty(key, String.valueOf(value)); + } + + public Object setProperty(String key,float value) { + return setProperty(key, String.valueOf(value)); + } + + public Object setProperty(String key,double value) { + return setProperty(key, String.valueOf(value)); + } + + public Object setProperty(String key,boolean value) { + return setProperty(key, String.valueOf(value)); + } + + public String[] getStringArray(String key) { + String v = getProperty(key); + if(v == null) { + return new String[0]; + }else { + return tokenizeToStringArray(v, ", \t\n\r\f"); + } + } + + public int[] getIntArray(String key) { + return toIntArray(getStringArray(key)); + } + + public Properties getStartsWithProperties(String prefix) { + if(prefix == null) throw new IllegalArgumentException("'prefix' must be not null"); + + Properties props = getProperties(); + Properties result = new Properties(); + for(Entry entry : props.entrySet()) { + String key = (String)entry.getKey(); + if(key != null && key.startsWith(prefix)) { + result.put(key.substring(prefix.length()), entry.getValue()); + } + } + return result; + } + + //--------- delegate method start ---// + public String getProperty(String key, String defaultValue) { + String value = getProperty(key); + if(isBlankString(value)) { + return defaultValue; + } + return value; + } + + public String getProperty(String key) { + String propVal = null; + if (systemPropertiesMode == SYSTEM_PROPERTIES_MODE_OVERRIDE) { + propVal = getSystemProperty(key); + } + if (propVal == null) { + propVal = p.getProperty(key); + } + if (propVal == null && systemPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) { + propVal = getSystemProperty(key); + } + return propVal; + } + + public Object setProperty(String key,String value) { + return p.setProperty(key, value); + } + + public void clear() { + p.clear(); + } + + public Set> entrySet() { + return p.entrySet(); + } + + public Enumeration propertyNames() { + return p.propertyNames(); + } + + public boolean contains(Object value) { + return p.contains(value); + } + + public boolean containsKey(Object key) { + return p.containsKey(key); + } + + public boolean containsValue(Object value) { + return p.containsValue(value); + } + + public Enumeration elements() { + return p.elements(); + } + + public Object get(Object key) { + return p.get(key); + } + + public boolean isEmpty() { + return p.isEmpty(); + } + + public Enumeration keys() { + return p.keys(); + } + + public Set keySet() { + return p.keySet(); + } + + public void list(PrintStream out) { + p.list(out); + } + + public void list(PrintWriter out) { + p.list(out); + } + + public void load(InputStream inStream) throws IOException { + p.load(inStream); + } + + public void loadFromXML(InputStream in) throws IOException, + InvalidPropertiesFormatException { + p.loadFromXML(in); + } + + public Object put(Object key, Object value) { + return p.put(key, value); + } + + public void putAll(Map t) { + p.putAll(t); + } + + public Object remove(Object key) { + return p.remove(key); + } + + /**@deprecated + * @param out out + * @param comments comments + */ + @Deprecated + public void save(OutputStream out, String comments) { + p.save(out, comments); + } + + public int size() { + return p.size(); + } + + public void store(OutputStream out, String comments) throws IOException { + p.store(out, comments); + } + + public void storeToXML(OutputStream os, String comment, String encoding) + throws IOException { + p.storeToXML(os, comment, encoding); + } + + public void storeToXML(OutputStream os, String comment) throws IOException { + p.storeToXML(os, comment); + } + + public Collection values() { + return p.values(); + } + + public String toString() { + return p.toString(); + } + + private static boolean isBlankString(String value) { + return Objects.isNull(value) || Objects.equals(StringUtils.EMPTY, value.trim()); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static String[] tokenizeToStringArray(String str, String sep) { + StringTokenizer st = new StringTokenizer(str, sep); + List result = new ArrayList(); + + while(st.hasMoreElements()) { + Object o = st.nextElement(); + result.add(o); + } + return (String[])result.toArray(new String[result.size()]); + } + + private static int[] toIntArray(String[] array) { + int[] result = new int[array.length]; + for(int i = 0; i < array.length; i++) { + result[i] = Integer.parseInt(array[i]); + } + return result; + } +} diff --git a/src/main/java/com/xit/core/support/mybatis/paging/support/SQLHelp.java b/src/main/java/com/xit/core/support/mybatis/paging/support/SQLHelp.java new file mode 100644 index 0000000..245a287 --- /dev/null +++ b/src/main/java/com/xit/core/support/mybatis/paging/support/SQLHelp.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2012-2013, Poplar Yfyang 杨友峰 (poplar1123@gmail.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.xit.core.support.mybatis.paging.support; + +import com.xit.core.support.mybatis.paging.dialect.Dialect; +import com.xit.core.support.mybatis.paging.dialect.LoggerHelper; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.slf4j.Logger; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +public class SQLHelp { + private static final Logger log = LoggerHelper.getLogger(); + + /** + * query record total count + * + * @param mappedStatement mapped + * @param parameterObject parameter + * @param boundSql boundSql + * @param dialect database dialect + * @return int + * @throws SQLException SQLException + */ + public static int getCount(final MappedStatement mappedStatement, final Object parameterObject, final BoundSql boundSql, Dialect dialect) throws SQLException { + final String count_sql = dialect.getCountSQL(); + + Connection connection = null; + PreparedStatement countStmt = null; + ResultSet rs = null; + try { + connection = mappedStatement.getConfiguration().getEnvironment().getDataSource().getConnection(); + countStmt = connection.prepareStatement(count_sql); + DefaultParameterHandler handler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql); + handler.setParameters(countStmt); + rs = countStmt.executeQuery(); + int count = 0; + + if (rs.next()) count = rs.getInt(1); +// log.debug("Total count:{}, SQL:{}, parameters:{}", count, count_sql, parameterObject); + return count; + + } finally { + try { + if (rs != null) rs.close(); + } finally { + try { + if (countStmt != null) countStmt.close(); + } finally { + if (connection != null && !connection.isClosed()) connection.close(); + } + } + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/valid/EnumNamePattern.java b/src/main/java/com/xit/core/support/valid/EnumNamePattern.java new file mode 100644 index 0000000..af1ca58 --- /dev/null +++ b/src/main/java/com/xit/core/support/valid/EnumNamePattern.java @@ -0,0 +1,90 @@ +package com.xit.core.support.valid; + +import com.xit.core.util.Checks; +import lombok.extern.slf4j.Slf4j; + +import javax.validation.Constraint; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import javax.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + *
+ * Enum validation check annotaion
+ * ADMIN("ROLE_ADMIN") or USER("ROLE_USER", "일잔사용자") 형태의 enum
+ * 해당 Enum 타입 클래스에 반드시 Serialized, Deserialized 메소드 구현 필요
+ *     @JsonCreator
+ *     public static RoleType fromString(String symbol){
+ *         return stringToEnum.get(symbol);
+ *     }
+ *
+ *     @JsonValue
+ *     public String getCode(){
+ *         return code;
+ *     }
+ *
+ *     private static final Map stringToEnum = Stream.of(values())
+ *             .collect(toMap(Objects::toString, e -> e));
+ *
+ * 사용 : @Enumerated(EnumType.STRING)
+ *       @EnumNamePattern(regexp = "ADMIN|USER|GUEST", message = "{auth.user.pattern.RoleType}")
+ *
+ * 
+ */ + +@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) +@Retention(RUNTIME) +@Documented +@Constraint(validatedBy = EnumNamePattern.EnumNamePatternValidator.class) +public @interface EnumNamePattern { + + /** + * @return the regular expression to match + */ + String regexp(); + + /** + * @return the error message template + */ + String message() default "must match \"{regexp}\""; + + /** + * @return the groups the constraint belongs to + */ + Class[] groups() default {}; + + /** + * @return the payload associated to the constraint + */ + Class[] payload() default {}; + + public class EnumNamePatternValidator implements ConstraintValidator> { + private Pattern pattern; + + @Override + public void initialize(EnumNamePattern constraintAnnotation) { + try { + pattern = Pattern.compile(constraintAnnotation.regexp()); + } catch (PatternSyntaxException e) { + throw new IllegalArgumentException("EnumNamePatternValidator.initialize Error :: ", e); + } + } + + @Override + public boolean isValid(Enum value, ConstraintValidatorContext context) { + if (Checks.isEmpty(value)) return false; + + Matcher m = pattern.matcher(value.name()); + return m.matches(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/valid/Enums.java b/src/main/java/com/xit/core/support/valid/Enums.java new file mode 100644 index 0000000..598ad57 --- /dev/null +++ b/src/main/java/com/xit/core/support/valid/Enums.java @@ -0,0 +1,73 @@ +package com.xit.core.support.valid; + +import com.xit.core.util.Checks; + +import javax.validation.Constraint; +import javax.validation.Payload; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *
+ * Enum validation check annotaion
+ * GOOGLE, KAKAO; 형태의 enum
+ * 해당 Enum 타입 클래스에 반드시 Serialized 메소드 구현 필요
+ *     @JsonCreator
+ *     public static ProviderType from(String s) {
+ *         try{
+ *             return ProviderType.valueOf(s);
+ *         }catch (IllegalArgumentException iae){
+ *             return null;
+ *         }
+ *     }
+ *
+ * 사용 : @Enumerated(EnumType.STRING)
+ *       @Enums(enumClass = ProviderType.class, ignoreCase = false, message = "{auth.user.pattern.ProviderType}")
+ * 
+ */ +/* 해당 annotation이 실행 할 ConstraintValidator 구현체를 `EnumValidator`로 지정합니다. */ +@Constraint(validatedBy = {Enums.EnumValidator.class}) +/* 해당 annotation은 메소드, 필드, 파라미터에 적용 할 수 있습니다. */ +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) +/* annotation을 Runtime까지 유지합니다. */ +@Retention(RetentionPolicy.RUNTIME) +public @interface Enums { + String message() default "Invalid value. This is not permitted."; + Class[] groups() default {}; + Class[] payload() default {}; + Class> enumClass(); + boolean ignoreCase() default false; + + public class EnumValidator implements ConstraintValidator { + + private Enums annotation; + + @Override + public void initialize(Enums constraintAnnotation) { + this.annotation = constraintAnnotation; + } + + @Override + public boolean isValid(Enum value, ConstraintValidatorContext context) { + if(Checks.isEmpty(value)) return false; + + boolean result = false; + Object[] enumValues = this.annotation.enumClass().getEnumConstants(); + if (enumValues != null) { + for (Object enumValue : enumValues) { + if (value.name().equals(enumValue.toString()) + || (this.annotation.ignoreCase() && value.name().equalsIgnoreCase(enumValue.toString()))) { + result = true; + break; + } + } + } + return result; + } + + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/support/valid/FromToTimeCheck.java b/src/main/java/com/xit/core/support/valid/FromToTimeCheck.java new file mode 100644 index 0000000..87eb59d --- /dev/null +++ b/src/main/java/com/xit/core/support/valid/FromToTimeCheck.java @@ -0,0 +1,84 @@ +package com.xit.core.support.valid; + +import javax.validation.Constraint; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import javax.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Field; +import java.time.LocalDateTime; + +/** + *
+ * 시작 ~ 종료시간 체크가 필요한 DTO 클래스에 annotation 정의
+ * startDate - 시작 필드 명, endDate - 종료 필드 명
+ * @FromToTimeCheck(startDate = "reservationStart", endDate = "reservationEnd")
+ * 
+ */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = FromToTimeCheck.FromToTimeCheckValidator.class) +public @interface FromToTimeCheck { + + String message() default "시작 시간이 종료 시간 보다 늦을 수 없습니다."; + + Class[] groups() default {}; + + Class[] payload() default {}; + + String startDate(); // 대상 객체의 시작시간 필드 이름 + + String endDate(); // 대상 객체의 종료시간 필드 이름 + + public class FromToTimeCheckValidator implements ConstraintValidator { + private String message; + private String startDate; + private String endDate; + + @Override + public void initialize(FromToTimeCheck constraintAnnotation) { + message = constraintAnnotation.message(); // 메세지 + startDate = constraintAnnotation.startDate(); // 시작시점 필드 + endDate = constraintAnnotation.endDate(); // 종료시점 필드 + } + + @Override + public boolean isValid(Object o, ConstraintValidatorContext context) { + try { + LocalDateTime reservationStart = (LocalDateTime) getFieldValue(o, startDate); + LocalDateTime reservationEnd = (LocalDateTime) getFieldValue(o, endDate); + + // 검증오류시 오류메세지와 대상 필드 set + if (reservationStart.isAfter(reservationEnd)) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(message) + .addPropertyNode(startDate) + .addConstraintViolation(); + return false; + } + return true; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + /** + * 정의한 필드명으로 해당 값 GET + * + * @param object Object + * @param fieldName String + * @return Object + * @throws Exception Exception + */ + private Object getFieldValue(Object object, String fieldName) throws Exception { + Class clazz = object.getClass(); // + Field dateField = clazz.getDeclaredField(fieldName); + dateField.setAccessible(true); + return dateField.get(object); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/util/AssertUtils.java b/src/main/java/com/xit/core/util/AssertUtils.java new file mode 100644 index 0000000..e72f2e7 --- /dev/null +++ b/src/main/java/com/xit/core/util/AssertUtils.java @@ -0,0 +1,255 @@ +package com.xit.core.util; + +import com.xit.core.exception.CustomBaseException; +import org.junit.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Map; + +public class AssertUtils extends Assert { + + private static final String NUMBER_ALPHABET_PATTERN = "^[a-zA-Z\\d]+$"; + private static final String NUMERIC_PATTERN = "^[\\+\\-]{0,1}[\\d]+$"; + private static final String FLOAT_PATTERN = "^[\\+\\-]{0,1}[\\d]+[\\.][0-9]+$"; + + + /** + * 에러메세지 return + * @param expression boolean + * @param message String + */ + public static void state(boolean expression, String message) { + if (!expression) { + throw new CustomBaseException(message); + } + } + + /** + * Assert a boolean expression, TRUE가 아닌 경우 사용자가 정의한 예외를 던진다. + * 사용자 정의 예외는 CustomBaseException.class를 상속 받아 구현해야 한다. + *
Assert.state(id == null, "The id property must not already be initialized", CustomBaseException.class);
+ * @param expression a boolean expression + * @param message the exception message to use if the assertion fails + * @param exceptionClass CustomBaseException + */ + public static void state(boolean expression, String message, final Class exceptionClass) { + if (!expression) { + throwException(message, exceptionClass); + } + } + + /** + * @param message the exception message to use if the assertion fails + */ + public static void isTrue(String message) { + throwException(message, CustomBaseException.class); + } + + /** + * Assert a boolean expression, TRUE가 아닌 경우 사용자가 정의한 예외를 던진다. + * @param expression a boolean expression + * @param message the exception message to use if the assertion fails + */ + public static void isTrue(boolean expression, String message) { + if (!expression) { + throwException(message, CustomBaseException.class); + } + } + + /** + * Assert that an object is {@code null}. 객체가 null이 아닌 경우 사용자가 정의한 예외를 던진다. + * 사용자 정의 예외는 CustomBaseException.class를 상속 받아 구현해야 한다. + *
Assert.isNull(user, "기존 사용자 정보가 존재합니다.", CustomBaseException.class);
+ * @param object CustomBaseException + * @param message the exception message to use if the assertion fails + * @param exceptionClass CustomBaseException + */ + public static void isNull(@Nullable Object object, String message, final Class exceptionClass) { + if (object != null) { + throwException(message, exceptionClass); + } + } + + /** + * null인 경우 사용자 정의 예외 발생 + * 사용자 정의 예외는 CustomBaseException.class를 상속 받아 구현해야 한다. + * @param object Object + * @param message String + * @param exceptionClass CustomBaseException + */ + public static void notNull(@Nullable Object object, String message, final Class exceptionClass) { + if (object == null) { + throwException(message, exceptionClass); + } + } + + /** + * 전달받은 값이 null이 거나 빈값인 경우 사용자 정의 예외 발생 + * 사용자 정의 예외는 CustomBaseException.class를 상속 받아 구현해야 한다. + *
Assert.hasLength(value, "전달 받은 값이 빈값입니다.", CustomBaseException.class);
+ * @param text String + * @param message String + * @param exceptionClass CustomBaseException + */ + public static void hasLength(@Nullable String text, String message, final Class exceptionClass) { + if (!StringUtils.hasLength(text)) { + throwException(message, exceptionClass); + } + } + + /** + * 전달받은 값이 null이 거나 빈값인 경우 사용자 정의 예외 발생 + * 사용자 정의 예외는 CustomBaseException.class를 상속 받아 구현해야 한다. + *
Assert.hasText(value, "전달 받은 값이 빈값입니다.", CustomBaseException.class);
+ * @param text String + * @param message String + * @param exceptionClass CustomBaseException + */ + public static void hasText(@Nullable String text, String message, final Class exceptionClass) { + if (!StringUtils.hasText(text)) { + throwException(message, exceptionClass); + } + } + + /** + * 전달받은 값이 null이 거나 빈값인 경우 사용자 정의 예외 발생 + * 사용자 정의 예외는 CustomBaseException.class를 상속 받아 구현해야 한다. + *
Assert.notEmpty(array, "전달 받은 값이 빈값입니다.", CustomBaseException.class);
+ * @param array Object[] + * @param message String + * @param exceptionClass CustomBaseException + */ + public static void notEmpty(@Nullable Object[] array, String message, final Class exceptionClass) { + if (ObjectUtils.isEmpty(array)) { + throwException(message, exceptionClass); + } + } + + /** + * 전달받은 값이 null이 거나 빈값인 경우 사용자 정의 예외 발생 + * 사용자 정의 예외는 CustomBaseException.class를 상속 받아 구현해야 한다. + *
Assert.notEmpty(collection, "전달 받은 값이 빈값입니다.", CustomBaseException.class);
+ * @param collection Collection + * @param message String + * @param exceptionClass CustomBaseException + */ + public static void notEmpty(@Nullable Collection collection, String message, final Class exceptionClass) { + if (CollectionUtils.isEmpty(collection)) { + throwException(message, exceptionClass); + } + } + + /** + * 전달받은 값이 null이 거나 빈값인 경우 사용자 정의 예외 발생 + * 사용자 정의 예외는 CustomBaseException.class를 상속 받아 구현해야 한다. + *
Assert.notEmpty(map, "전달 받은 값이 빈값입니다.", CustomBaseException.class);
+ * @param map Map + * @param message String + * @param exceptionClass CustomBaseException + */ + public static void notEmpty(@Nullable Map map, String message, final Class exceptionClass) { + if (CollectionUtils.isEmpty(map)) { + throwException(message, exceptionClass); + } + } + + private static void throwException(String message, final Class exceptionClass) { + try { + throw exceptionClass.getDeclaredConstructor( String.class ).newInstance( message ); + } catch (Exception e) { + e.printStackTrace(); + throw new CustomBaseException("예외 처리 중 오류가 발생했습니다. "+e.getLocalizedMessage()); + } + } + + /** + * 값이 영문 알파벳, 숫자가 아닌 경우 예외 발생 + * @param object + * @param message + */ + public static void isAlphaNumber(String object, String message) { + isMatched(object, NUMBER_ALPHABET_PATTERN, message, CustomBaseException.class); + } + + /** + * 값이 영문 알파벳, 숫자가 아닌 경우 사용자 정의 예외 발생 + * @param object + * @param message + * @param exceptionClass + */ + public static void isAlphaNumber(String object, String message, final Class exceptionClass) { + isMatched(object, NUMBER_ALPHABET_PATTERN, message, exceptionClass); + } + + /** + * 값이 숫자가 아닌 경우 예외 발생 + * @param object + * @param message + */ + public static void isNumeric(String object, String message) { + isMatched(object, NUMERIC_PATTERN, message, CustomBaseException.class); + } + + /** + * 값이 숫자가 아닌 경우 사용자 정의 예외 발생 + * @param object + * @param message + * @param exceptionClass + */ + public static void isNumeric(String object, String message, final Class exceptionClass) { + isMatched(object, NUMERIC_PATTERN, message, exceptionClass); + } + + /** + * 값이 float, double이 아닌 경우 예외 발생 + * @param object + * @param message + */ + public static void isFloat(String object, String message) { + isMatched(object, FLOAT_PATTERN, message, CustomBaseException.class); + } + + /** + * 값이 float, double이 아닌 경우 사용자 정의 예외 발생 + * @param object + * @param message + * @param exceptionClass + */ + public static void isFloat(String object, String message, final Class exceptionClass) { + isMatched(object, FLOAT_PATTERN, message, exceptionClass); + } + + /** + * 패턴 매치, 해당 패턴과 일치 하지 않는 경우 사용자 정의 예외 발생 + * @param object + * @param pattern + * @param message + * @param exceptionClass + */ + public static void isMatched(String object, String pattern, String message, final Class exceptionClass) { + if(object == null || "".equalsIgnoreCase(object)) return; + + if(!object.matches(pattern)) { + throwException(message, exceptionClass); + } + } + + /** + * 패턴 매치, 해당 패턴과 일치 하지 않는 경우 예외 발생 + * @param object + * @param pattern + * @param message + */ + public static void isMatched(String object, String pattern, String message) { + if(object == null || "".equalsIgnoreCase(object)) return; + + if(!object.matches(pattern)) { + throwException(message, CustomBaseException.class); + } + } + +} diff --git a/src/main/java/com/xit/core/util/AwsFileUtil.java b/src/main/java/com/xit/core/util/AwsFileUtil.java new file mode 100644 index 0000000..727c0de --- /dev/null +++ b/src/main/java/com/xit/core/util/AwsFileUtil.java @@ -0,0 +1,285 @@ +package com.xit.core.util; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.model.*; +import com.amazonaws.util.IOUtils; +import com.xit.core.constant.XitConstants; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.env.Environment; +import org.springframework.http.MediaType; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +/** + * + */ +@Slf4j +public class AwsFileUtil { + + private AWSCredentials awsCredentials = null; + private AmazonS3 amazonS3 = null; + private String bucketName = null; + + private final Environment env; + + public AwsFileUtil(Environment env) { + this.env = env; + } + + public AwsFileUtil(String accessKey, String secretKey, String bucketName, Environment env) { + this.awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + amazonS3 = new AmazonS3Client(this.awsCredentials); + this.bucketName = bucketName; + this.env = env; + } + + /** + * 단일 파일 업로드 + * + * @param file MultipartFile + * @param folder String + * @return Map + */ + public Map uploadSingleFile(MultipartFile file, String folder) { + + Map result = new HashMap(); + + if (file == null) { + result.put("fileName", ""); + result.put("key", ""); + result.put("url", ""); + } + + try { + assert file != null; + String key = createKey(Objects.requireNonNull(file.getOriginalFilename()), folder); + + ObjectMetadata metadata = new ObjectMetadata(); + long contentLength = (long) IOUtils.toByteArray(file.getInputStream()).length; + metadata.setContentType(file.getContentType()); + metadata.setContentLength(contentLength); + + amazonS3.putObject(new PutObjectRequest(bucketName, key, file.getInputStream(), metadata)); + + result.put("fileName", file.getOriginalFilename()); + result.put("key", key); + if (Objects.equals(XitConstants.ActiveProfile.PROD.getValue(), env.getActiveProfiles()[0])) { + result.put("url", "https://image.newbalancemynb.com/" + key); + } else { + result.put("url", amazonS3.getUrl(bucketName, key)); + } + } catch (Exception e) { + e.printStackTrace(); + log.error(e.getLocalizedMessage()); + } + + return result; + } + + /** + * 파일 데이터 + * + * @param fileId fileId + * @return InputStream + */ + public InputStream getFileStream(String fileId) { + GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, fileId); + S3Object s3Object = amazonS3.getObject(getObjectRequest); + return s3Object.getObjectContent(); + } + + /** + * 파일 다운로드 + * + * @param fileId String + * @param fileName String + * @param request HttpServletRequest + * @param response HttpServletResponse + * @return Map + */ + public Map downloadFile(String fileId, String fileName, HttpServletRequest request, + HttpServletResponse response) { + + Map result = new HashMap(); + InputStream inputStream = null; + OutputStream outputStream = null; + + AmazonS3 amazonS3 = new AmazonS3Client(awsCredentials); + + try { + GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, fileId); + S3Object s3Object = amazonS3.getObject(getObjectRequest); + S3ObjectInputStream objectContent = s3Object.getObjectContent(); + + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + response.setHeader("Content-Disposition", "attachment; filename=" + getFileName(request, fileName) + ";"); + response.setHeader("Content-Transfer-Encoding", "binary"); + + inputStream = objectContent; + outputStream = response.getOutputStream(); + + byte[] buffer = new byte[4096]; + int read = -1; + while ((read = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, read); + } + + result.put("code", "00"); + result.put("msg", "success"); + } catch (Exception e) { + result.put("code", "9999"); + result.put("msg", e.getLocalizedMessage()); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + return result; + } + + /** + * + * @param bucketName String + * @param folderName String + */ + public void createFolder(String bucketName, String folderName) { + amazonS3.putObject(bucketName, folderName + "/", new ByteArrayInputStream(new byte[0]), new ObjectMetadata()); + } + + /** + * + * @param path String + * @param fileId String + * @return Map + */ + public Map deleteFile(String path, String fileId) { + + Map result = new HashMap(); + + AmazonS3 amazonS3 = new AmazonS3Client(awsCredentials); + + try { + amazonS3.deleteObject(new DeleteObjectRequest(bucketName, path + fileId)); + + result.put("code", "00"); + result.put("msg", "success"); + } catch (Exception e) { + result.put("code", "9999"); + result.put("msg", e.getLocalizedMessage()); + } + return result; + } + + /** + * + * @param request HttpServletRequest + * @param fileName String + * @return String file name(한글명 포함) + * @throws UnsupportedEncodingException Exception + */ + public static String getFileName(HttpServletRequest request, String fileName) throws UnsupportedEncodingException { + + String userAgent = request.getHeader("User-Agent"); + + if (userAgent.contains("MSIE 5.5")) { + // MS IE 5.5 이하 + return URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "\\ "); + } else if (userAgent.contains("MSIE")) { + // MS IE (보통은 6.x 이상 가정) + return URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "\\ "); + } else if (userAgent.contains("Trident")) { + // MS IE 11 + return URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "\\ "); + } else { + // 모질라나 오페라 + return new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1); + } + } + + /** + * 파일 키 생성 UUID + currentTimeMillis + * + * @param fileName file name + * @param folder path + * @return String + */ + private String createKey(String fileName, String folder) { + String ext = fileName.substring(fileName.lastIndexOf("."), fileName.length()); + return folder + + ((folder.substring(folder.length() - 1)).equals("/") ? DateUtil.getToday("") + : ("/" + DateUtil.getToday("")) + "/") + + UUID.randomUUID().toString().replaceAll("-", "") + System.currentTimeMillis() + ext; + } + +// /** +// * 다중파일 업로드 +// * +// * @param files MultipartFile[] +// * @return List +// */ +// public List> uploadMultiFile(MultipartFile[] files) { +// +// List> result = new ArrayList>(); +// if (files == null || files.length <= 0) { +// Map map = new HashMap(); +// map.put("code", "9999"); +// map.put("msg", "file not exist"); +// result.add(map); +// } +// +// try { +// TransferManager transferManager = new TransferManager(awsCredentials); +// +// for (MultipartFile file : files) { +// // 파일 ID 생성 +// String fileId = createFileId(file.getOriginalFilename()); +// +// ObjectMetadata metadata = new ObjectMetadata(); +// long contentLength = (long) IOUtils.toByteArray(file.getInputStream()).length; +// metadata.setContentType(file.getContentType()); +// metadata.setContentLength(contentLength); +// Upload upload = transferManager.upload(bucketName, path + fileId, +// file.getInputStream(), metadata); +// +// upload.waitForCompletion(); +// +// Map map = new HashMap(); +// map.put("code", "00"); +// map.put("msg", "success"); +// map.put("fileId", fileId); +// map.put("fileName", file.getOriginalFilename()); +// map.put("fileSize", file.getSize()); +// map.put("fileContentType", file.getContentType()); +// result.add(map); +// } +// } catch (Exception e) { +// +// } +// +// return result; +// } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/util/Checks.java b/src/main/java/com/xit/core/util/Checks.java new file mode 100644 index 0000000..2039d9e --- /dev/null +++ b/src/main/java/com/xit/core/util/Checks.java @@ -0,0 +1,152 @@ +package com.xit.core.util; + +import java.util.Iterator; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Checks { + /** + * val이 null이 아니면 그대로 리턴, 아니면 defVal + * + * @param val + * @param defVal + * @return T + * @warning [Optional]함수의 제약사항이나 주의해야 할 점 + * @exception [Mandatory]throw하는 exception들에 대한 설명 + * @see [Optional]관련 정보(관련 함수, 관련 모듈) + */ + public static < T > T checkVal( T val, T defVal ) { + return ( isNotNull( val ) ? val : defVal ); + } + + /** + * val이 Empty가 아니면 그대로 리턴, 아니면 defVal (Empty는 배열이나 Map도 가능) + * + * @메소드 : checkEmptyVal + * + * @param val + * @param defVal + * @return + */ + public static < T > T checkEmptyVal( T val, T defVal ) { + return ( isNotEmpty( val ) ? val : defVal ); + } + + /** + * expression이 true이면 val, 아니면 defVal + * + * @param expression + * @param tVal + * @param fVal + * @return T + * @warning [Optional]함수의 제약사항이나 주의해야 할 점 + * @exception [Mandatory]throw하는 exception들에 대한 설명 + * @see [Optional]관련 정보(관련 함수, 관련 모듈) + */ + public static < T > T checkVal( boolean expression, T tVal, T fVal ) { + return ( expression ? tVal : fVal ); + } + + + public static boolean isNull( Object checkValue ) { + return !isNotNull(checkValue); + } + + public static boolean isNotNull( Object checkValue ) { + return checkValue != null; + } + + public static boolean isEmpty( Object checkValue ) { + return !isNotEmpty(checkValue); + } + + public static boolean isNotEmpty( Object checkValue ) { + if ( isNotNull( checkValue ) ) { + + if ( checkValue instanceof String ) { + return !( ( String ) checkValue ).isEmpty(); + } + else if ( checkValue instanceof Iterator< ? > ) { + return ( ( Iterator< ? > ) checkValue ).hasNext(); + } + else if ( checkValue instanceof Iterable< ? > ) { + return ( ( Iterable< ? > ) checkValue ).iterator().hasNext(); + } + else if ( checkValue instanceof Map< ?, ? > ) { + return !( ( Map< ?, ? > ) checkValue ).isEmpty(); + } +// else if ( checkValue instanceof DataSetMap ) { +// return !( ( DataSetMap ) checkValue ).isEmpty(); +// } + + if ( checkValue.getClass().isArray() ) { + return ( ( Object[] ) checkValue ).length != 0; + } + else { + // 체크하는 항목이 아니면서 not null 이면 not empty로 간주한다. + return true; + } + } + + return false; + } + + public static boolean isNumeric( String str ) + { + if ( Checks.isEmpty( str ) ) + return false; + + return str.matches( "-?\\d+(.\\d+)?" ); + } + + public static boolean isInstance( Object checkValue, Class< ? >... classes ) { + if ( isNotNull( checkValue ) ) { + for ( Class< ? > clazz : classes ) { + if ( clazz.isInstance( checkValue ) ) { + return true; + } + } + } + + return false; + } + + /** + * email형식 체크 + * + * @메소드 : isCheckEmail + * + * @param str + * @return + */ + public static boolean isCheckEmail( String str ) { + if ( Checks.isEmpty( str ) ) + return false; + + String regex = "^([\\w-]+(?:\\.[\\w-]+)*)@((?:[\\w-]+\\.)*\\w[\\w-]{0,66})\\.([a-z]{2,6}(?:\\.[a-z]{2})?)$"; + Pattern pattern = Pattern.compile( regex, Pattern.CASE_INSENSITIVE ); + + Matcher matcher = pattern.matcher( str ); + return matcher.find(); + } + + /** + * 전화형식 체크 + * + * @메소드 : isCheckTel + * + * @param str + * @return + */ + public static boolean isCheckTel( String str ) { + if ( Checks.isEmpty( str ) ) + return false; + + String regex = "^\\d[\\d-]+\\d$"; + Pattern pattern = Pattern.compile( regex, Pattern.MULTILINE ); + + Matcher matcher = pattern.matcher( str ); + return matcher.find(); + } +} diff --git a/src/main/java/com/xit/core/util/CommUtil.java b/src/main/java/com/xit/core/util/CommUtil.java new file mode 100644 index 0000000..c68c5ec --- /dev/null +++ b/src/main/java/com/xit/core/util/CommUtil.java @@ -0,0 +1,652 @@ +package com.xit.core.util; + +import com.rits.cloning.Cloner; +import eu.bitwalker.useragentutils.UserAgent; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.mozilla.universalchardet.UniversalDetector; +import org.springframework.http.ResponseCookie; +import org.springframework.mobile.device.Device; +import org.springframework.mobile.device.DeviceUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import java.io.StringReader; +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +@Slf4j +public class CommUtil { + +// /** +// * 클래스의 인스턴스 정보 생성 함수 +// * @param className String +// * @return Object +// * @throws IllegalAccessException +// * @throws ClassNotFoundException +// * @exception +// */ + + /** + * 클래스의 인스턴스 정보 생성 함수 + * + * @param className String + * @return Object + * @throws InstantiationException Exception + * @throws IllegalAccessException Exception + * @throws ClassNotFoundException Exception + */ + public static Object getInstance(String className) throws InstantiationException, IllegalAccessException, ClassNotFoundException { + + if(className == null) return null; + else return Class.forName(className).newInstance(); + } + + /** + * 현재 문자열의 Encoding Type return + * @param str String + * @return String + */ + public static String getEncodingType(String str){ + UniversalDetector detector = new UniversalDetector(null); + detector.handleData(str.getBytes(), 0, str.getBytes().length); + detector.dataEnd(); + return detector.getDetectedCharset(); + } + + /** + * Null 문자 처리 + * @param str String + * @return String + */ + public static String nvl(String str) { + return nvl(str, ""); + } + + /** + * Null 문자 처리 + * @param str String + * @param def String + * @return String + */ + public static String nvl(String str, String def) { + if(str == null) { + return def; + } else { + return str.trim(); + } + } + + /** + * Class Deep Copy + * @param object T + * @return T + */ + public static T deepCopy(T object) { + Cloner cloner = new Cloner(); + return cloner.deepClone(object); + } + + /** + * TypeObject String 변환 및 null 처리 + * @param object Object + * @return String + */ + public static String stringValueOf(Object object) { + if(object == null) { + return null; + } else { + return String.valueOf(object); + } + } + + /** + * Double 변환 및 null 처리 + * @param object String + * @return Integer + */ + public static Double parseDouble(String object) { + if(object == null) { + return 0.0; + } else { + return Double.parseDouble(object); + } + } + + /** + * Integer 변환 및 null 처리 + * @param object String + * @return Integer + */ + public static Integer parseInt(String object) { + if(object == null) { + return null; + } else { + return Integer.parseInt(object); + } + } + + /** + * redis 에서 반환된 문자열에서 controlId 추출 후 반환 + * @param redisString Strin + * @return String + */ + public static String getControlId(String redisString) { + //GetConfig:05.001.01.0000000000:none:CT5B8CE96D78b49624A400000D + String controlId = nvl(redisString); + + if(redisString != null) { + if(redisString.contains(":")) { + String[] elmt = redisString.split(":"); + if(elmt.length>=4) { + controlId = elmt[3]; + } else { + controlId = ""; + } + } + } + + return controlId; + } + + /** + * HP 포멧 변경 + * @param phoneNum String + * @return String + * @throws Exception Exception + */ + public static String toHpFmt(String phoneNum) throws Exception { + phoneNum = phoneNum.trim(); + if(phoneNum.length()==9) { + phoneNum = phoneNum.substring(0,2) + "-" + phoneNum.substring(2,5) + "-" + phoneNum.substring(5); + } else if(phoneNum.length()==10) { + phoneNum = phoneNum.substring(0,3) + "-" + phoneNum.substring(3,6) + "-" + phoneNum.substring(6); + } else if(phoneNum.length()==11) { + phoneNum = phoneNum.substring(0,3) + "-" + phoneNum.substring(3,7) + "-" + phoneNum.substring(7); + } + return phoneNum; + } + + public static String subStrByte(String str, int cutlen) { + if(!str.isEmpty()) { + str = str.trim(); + if(str.getBytes().length <= cutlen) { + return str; + } else { + StringBuilder sbStr = new StringBuilder(cutlen); + int nCnt = 0; + for(char ch : str.toCharArray()) { + nCnt += String.valueOf(ch).getBytes().length; + if(nCnt > cutlen) break; + sbStr.append(ch); + } + return sbStr.toString(); + } + } else { + return ""; + } + } + + public static String getDomainInUrl(HttpServletRequest request) { + return getDomainInUrl(request.getRequestURL().toString()); + } + + public static String getSiteUrl(HttpServletRequest request) { + String url = request.getRequestURL().toString(); + url = url.substring(0, url.indexOf(request.getRequestURI().toString())); + return url; + } + + public static void setSameSiteCookie(HttpServletRequest request, HttpServletResponse response) { + ResponseCookie cookie = ResponseCookie.from("sameSiteCookie", "sameSiteCookieValue") + .domain(getSiteUrl(request)) // 변동 Domain 이므로 여행사 도메인으로 적용 + .sameSite("None") + .secure(true) + .path("/") // ?????? + .build(); + + response.addHeader("Set-Cookie", cookie.toString()); + } + + public static String getUrlHost(HttpServletRequest request) throws Exception { + + String url = request.getRequestURL().toString(); + + if(!url.startsWith("http") && !url.startsWith("https")){ + url = "https://" + url; + } + + URL netUrl = new URL(url); + + String Host = netUrl.getHost(); + if(Host.startsWith("www")){ + Host = Host.substring("www".length()+1); + } + + return Host; + } + + public static String getDomainInUrl(String url) { + String domain = null; + if(url.length()>(url.indexOf("ags-aws.com")+11)) { + domain = url.substring(0,url.indexOf("ags-aws.com")+11); + } else { + domain = url; + } + return domain; + } + + public static T xmlToObject(String xml, Class resCls) throws Exception { + + JAXBContext jaxbContext = JAXBContext.newInstance(resCls); + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + StringReader reader = new StringReader(xml); + + return resCls.cast(unmarshaller.unmarshal(reader)); + } + + public static String ObjectToXml(T rltCls) throws Exception { + + StringWriter sw = new StringWriter(); + JAXBContext jaxbContext = JAXBContext.newInstance(rltCls.getClass()); + Marshaller marshaller = jaxbContext.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + marshaller.marshal(rltCls, sw); + + return sw.toString(); + } + + public static String tempRandomString(int digit){ + StringBuilder result = new StringBuilder(); + for(int i = 0; i < 10; i++) { + double dValue = Math.random(); + char cValue = (char)((dValue * 26) + 65); // 대문자 + result.append(String.valueOf(cValue)); + } + result.append(String.valueOf(System.currentTimeMillis())); + if(digit != 0) result = new StringBuilder(result.substring(0, digit)); + return result.toString(); + } + + public static String lpad(String str, char padChar, int padLen) { + StringBuilder strBuilder = new StringBuilder(str); + while (strBuilder.length() < padLen) { + strBuilder.insert(0, padChar); + } + str = strBuilder.toString(); + + return str; + } + + public static String rpad(String str, char padChar, int padLen) { + StringBuilder strBuilder = new StringBuilder(str); + while (strBuilder.length() < padLen) { + strBuilder.append(padChar); + } + str = strBuilder.toString(); + + return str; + } + + public static void clearObj(Map map) { + map.clear(); + map = null; + } + + public static void clearObj(List list) { + list.clear(); + list = null; + } + + /** + * 문자열에서 한글/숫자/영문 만 추출 + * @param str String + * @return String + */ + public static String getOnlyName(String str) { + String match = "[^\uAC00-\uD7A3xfe0-9a-zA-Z\\s]"; + str = nvl(str).replaceAll(match, ""); + return str; + } + + /** + * 문자열에서 숫자만 추출 + * @param str String + * @return String + */ + public static String getOnlyNo(String str) { + String match = "[^0-9]"; + str = nvl(str).replaceAll(match, ""); + return str; + } + + /** + * 문자열에서 숫자만 추출 + * @param length int + * @return String + */ + public static String getRandomCd(int length) { + Random rand = new Random(); + StringBuilder sb = new StringBuilder(); + for(int i=0; i key.length()) { + key = key + CommUtil.getRandomCd(len-key.length()); + } + + return key.toUpperCase(); + } + + public static String getElementName(String methodName) throws Exception { + + String reName = methodName; + + methodName = nvl(methodName); + + if(methodName.length()>3) { + reName = methodName.substring(3,4).toLowerCase() + methodName.substring(4); + } + + return reName; + } + + public static String setMethodName(String setMethodName) { + + String getMethodName = setMethodName; + getMethodName = nvl(getMethodName); + + if(getMethodName.indexOf("get")==0) { + getMethodName = "set" + getMethodName.substring(3); + } + + return getMethodName; + } + + /** + * Object 에 String 객체를 charSet 형태로 URL 인코딩 한다. + * @param obj Object + * @param charset String + * @throws Exception Exception + */ + public static void urlEncode(Object obj, String charset) throws Exception { + + Method[] methods = obj.getClass().getMethods(); + + for(Method method : methods) { + if( method.getName().indexOf("get")==0 ) { + + String emtNm = getElementName(method.getName()); + Object type = method.invoke(obj); + + if(type instanceof String && !"hashData".contentEquals(emtNm)) { + + Object sObj = method.invoke(obj); + String str = (String) sObj; + + if(!StringUtils.EMPTY.contentEquals(nvl(str))) { + String txt = URLEncoder.encode(str, charset); + Method setMethod = obj.getClass().getMethod(setMethodName(method.getName()), type.getClass()); + setMethod.invoke(obj, txt); + } +//obj.getClass().getMethods(); + } + + } + } + } + + /** + * Object 에 있는 String 객체를 파라미터 Map 으로 변환 한다. + * @param obj Object + * @return Map + * @throws Exception Exception + */ + public static Map getStrParamMap(Object obj) throws Exception { + + Map map = new HashMap(); + Method[] methods = obj.getClass().getMethods(); + + for(Method method : methods) { + if( method.getName().indexOf("get")==0 ) { + + String key = getElementName(method.getName()); + Object type = method.invoke(obj); + if(type instanceof String) { + + Object str = method.invoke(obj); + String value = (String) str; + + map.put(key, value); + } + } + } + + return map; + } + + public static String getDisposition(HttpServletRequest request, String filename) { + String browser = getBrowser(request.getHeader("User-Agent")); + String encodedFilename = ""; + + try { + String encodedFilename1 = "\"" + new String(filename.getBytes(StandardCharsets.UTF_8), "8859_1") + "\""; + + switch (browser) { + case "Firefox": + case "Opera": + encodedFilename = encodedFilename1; + break; + case "Chrome": + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < filename.length(); i++) { + char c = filename.charAt(i); + if (c > '~') { + sb.append(URLEncoder.encode("" + c, StandardCharsets.UTF_8)); + } else { + sb.append(c); + } + } + encodedFilename = sb.toString(); + break; + default: + encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8).replaceAll("\\+", "%20"); // MSIE 외 모든 브라우저 + + break; + } + } catch (Exception e) { + log.debug(e.getLocalizedMessage()); + } + + return encodedFilename; + } + + public static String getBrowser(String userAgent) { + String browser = ""; + + if(userAgent.contains("Trident")) { // IE + browser = "MSIE"; + } else if(userAgent.contains("Edge")) { // Edge + browser = "Edge"; + } else if(userAgent.contains("Whale")) { // Naver Whale + browser = "Whale"; + } else if(userAgent.contains("Opera") || userAgent.contains("OPR")) { // Opera + browser = "Opera"; + } else if(userAgent.contains("Firefox")) { // Firefox + browser = "Firefox"; + } else if(userAgent.contains("Safari") && !userAgent.contains("Chrome")) { // Safari + browser = "Safari"; + } else if(userAgent.contains("Chrome")) { // Chrome + browser = "Chrome"; + } + + return browser; + } + + public static void main(String ... args) throws Exception { + + for(int i=0; i<10; i++) { + System.out.println("####################################################################################################"); +// System.out.println(getHexTmMi()); + long tm = System.currentTimeMillis(); + String hex = getDecToHex(tm); + String dec = getHexToDec(hex); + System.out.println("timemile : "+tm); + System.out.println("hex : "+hex); + System.out.println("dec : "+dec); + System.out.println(CommUtil.lpad(""+i, '0', 2)+"."+hex+"/"+CommUtil.getRandomCd(16)); + + String chg36 = Long.toString(tm,36); + System.out.println("36 : "+chg36); + long chg10 = Long.parseLong(chg36,36); + System.out.println("10 : "+Long.toString(chg10)); + System.out.println("36 : "+Long.toString(chg10,36)); + + System.out.println(getRanStrKey(12)); + System.out.println(getRanStrKey(16)); + } + } +} + + diff --git a/src/main/java/com/xit/core/util/DateUtil.java b/src/main/java/com/xit/core/util/DateUtil.java new file mode 100644 index 0000000..b65d54c --- /dev/null +++ b/src/main/java/com/xit/core/util/DateUtil.java @@ -0,0 +1,502 @@ +package com.xit.core.util; + + + +import org.apache.commons.lang3.StringUtils; + +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.Date; + +/** + * 자바 8부터 java.time 패키지 활용 + * localDate : 날짜 정보만 필요할때 + * localDateTime : 날짜와 시간 모두 필요할때 + * localtime : 시간 정보만 필요할때 + */ +public class DateUtil { + + + /** + * LocalDate 객체 반환 + * + * @return LocalDate + */ + public static LocalDate localDate() { + return LocalDate.now(); + } + + /** + * LocalDate.parse 객체 반환 + * + * @param target yyyy-MM-dd + * @return LocalDate.parse(tartget); + */ + public static LocalDate parseLocalDate(String target) { + return LocalDate.parse(target); + } + + /** + * LocalDateTime 객체 반환 + * + * @return LocalDateTime + */ + public static LocalDateTime localDateTime() { + return LocalDateTime.now(); + } + + /** + * LocalDateTime.parse 객체 반환 + * + * @param target yyyy-MM-dd HH:mm:ss + * @return LocalDateTime.parse(tartget); + */ + public static LocalDateTime parseLocalDateTime(String target) { + return LocalDateTime.parse(target); + } + + /** + * 현재 년도 + * + * @return int + */ + public static int getCurrentYear() { + return localDate().getYear(); + } + + /** + * 현재 월 + * + * @return int + */ + public static int getCurrentMonth() { + return localDate().getMonthValue(); + } + + /** + * 현재 일 + * + * @return int + */ + public static int getCurrentDay() { + return localDate().getDayOfMonth(); + } + + /** + * 운년 여부 + * + * @return boolean + */ + public static boolean isLeapYear() { + return localDate().isLeapYear(); + } + + /** + * 오늘 날짜 : 년-월-일 + * BASIC_ISO_DATE : 20200621 리턴 + * + * @return LocalDate + */ + public static LocalDate getToday() { + return localDate(); + } + + /** + * 오늘 날짜 : 년-월-일 + * BASIC_ISO_DATE : 20200621 리턴 + * + * @param delimiter 년원일 사이 구분자 + * @return String + */ + public static String getToday(String delimiter) { + String result = ""; + String pattern = "yyyy" + delimiter + "MM" + delimiter + "dd"; + + try { + if (StringUtils.isEmpty(delimiter)) { + result = localDate().format(DateTimeFormatter.BASIC_ISO_DATE); + } else { + result = localDate().format(DateTimeFormatter.ofPattern(pattern)); + } + } catch (final Exception e) { + e.printStackTrace(); + } + + return result; + } + + /** + * 오늘 날짜 : 년-월-일 시분초 + * + * @return LocalDate + */ + public static LocalDateTime getTodayAndNowTime() { + return localDateTime(); + } + + /** + * 오늘 날짜 : 년-월-일 시분초 + * + * @param delimiter 년원일 사이 구분자 + * @param isMillisecond 밀리 세컨드 여부 + * @return String + */ + public static String getTodayAndNowTime(String delimiter, boolean isMillisecond) { + String pattern = ""; + + try { + pattern = "yyyy" + delimiter + "MM" + delimiter + "dd" + " HH:mm:ss" + (isMillisecond ? ".SSS" : ""); + } catch (Exception e) { + e.printStackTrace(); + } + + return localDateTime().format(DateTimeFormatter.ofPattern(pattern)); + } + + /** + * 오늘 날짜 : 년-월-일 시분초 + * + * @param userFormat 사용자가 원하는 포멧 + * @return String + */ + public static String getTodayAndNowTime(String userFormat) { + String pattern = ""; + + try { + pattern = "yyyy-MM-dd HH:mm:ss"; + if (StringUtils.isEmpty(userFormat)) { + pattern = userFormat; + } + } catch (Exception e) { + e.printStackTrace(); + } + + return localDateTime().format(DateTimeFormatter.ofPattern(pattern)); + } + + /** + * 년 월 일 계산 + * 날짜만 계산 년-월-일 형태로 넘겨줘야함(안그럼 예외) + * 해당월에 존재하지 않는 일을 넘겨도 예외 + * 빼기는 음수를 붙여서 넘기면 된다. + * ex : -1 = 빼기 1 + * + * @param targetDate 계산할 년-월-일 + * @param year 계산할 년 + * @param month 계산할 월 + * @param day 계산할 일 + * @return String + */ + public static String getCalculatorDate(String targetDate, int year, int month, int day) { + String result = ""; + + try { + LocalDate localDate = parseLocalDate(targetDate); + localDate = localDate.plusYears(year); + localDate = localDate.plusMonths(month); + localDate = localDate.plusDays(day); + result = localDate.toString(); + } catch (Exception e) { + e.printStackTrace(); + } + + return result; + } + + /** + * 년 월 계산 + * 년-월-일 형태로 넘겨줘야함(안그럼 예외) + * 해당월에 존재하지 않는 일을 넘겨도 예외 + * 빼기는 음수를 붙여서 넘기면 된다. + * ex : -1 = 빼기 1 + * + * @param targetDate 계산할 년-월-일 + * @param year 계산할 년 + * @param month 계산할 월 + * @return String + */ + public static String getCalculatorDate(String targetDate, int year, int month) { + return getCalculatorDate(targetDate, year, month, 0); + } + + /** + * 년 계산 + * 년-월-일 형태로 넘겨줘야함(안그럼 예외) + * 해당월에 존재하지 않는 일을 넘겨도 예외 + * 빼기는 음수를 붙여서 넘기면 된다. + * ex : -1 = 빼기 1 + * + * @param targetDate 계산할 년-월-일 + * @param year 계산할 년 + * @return String + */ + public static String getCalculatorDate(String targetDate, int year) { + return getCalculatorDate(targetDate, year, 0, 0); + } + + /** + * 날짜&시간 계산 년-월-일THH:mm:ss 형태(안그럼 예외) + * 2020-06-22T23:20:32 ISO 시간 표기법에 따라 'T'를 꼭 붙여야 함. + * 빼기는 음수를 붙여서 ex : -1 = 빼기 1 + * + * @param targetDate 계산할 년-월-일T시:분:초 + * @param year 계산할 년 + * @param month 계산할 월 + * @param day 계산할 일 + * @param hour 계산할 시간 + * @param minute 계산할 분 + * @param second 계산할 초 + * @return String + */ + public static String getCalculatorDateAndTime(String targetDate, int year, int month, int day, int hour, int minute, int second) { + String result = ""; + + try { + LocalDateTime localDateTime = parseLocalDateTime(targetDate); + localDateTime = localDateTime.plusYears(year); + localDateTime = localDateTime.plusMonths(month); + localDateTime = localDateTime.plusDays(day); + localDateTime = localDateTime.plusHours(hour); + localDateTime = localDateTime.plusMinutes(minute); + localDateTime = localDateTime.plusSeconds(second); + result = localDateTime.toString(); + } catch (Exception e) { + e.printStackTrace(); + } + + return result; + } + + /** + * 년 월 일 시 분 계산 + * 날짜&시간 계산 년-월-일THH:mm:ss 형태(안그럼 예외) + * 2020-06-22T23:20:32 ISO 시간 표기법에 따라 'T'를 꼭 붙여야 함. + * 빼기는 음수를 붙여서 ex : -1 = 빼기 1 + * + * @param targetDate 계산할 년-월-일T시:분:초 + * @param year 계산할 년 + * @param month 계산할 월 + * @param day 계산할 일 + * @param hour 계산할 시간 + * @param minute 계산할 분 + * @return String + */ + public static String getCalculatorDateAndTime(String targetDate, int year, int month, int day, int hour, int minute) { + return getCalculatorDateAndTime(targetDate, year, month, day, hour, minute, 0); + } + + /** + * 년 월 일 시 계산 + * 2020-06-22T23:20:32 ISO 시간 표기법에 따라 'T'를 꼭 붙여야 함. + * 빼기는 음수를 붙여서 ex : -1 = 빼기 1 + * + * @param targetDate 계산할 년-월-일T시:분:초 + * @param year 계산할 년 + * @param month 계산할 월 + * @param day 계산할 일 + * @param hour 계산할 시간 + * @return String + */ + public static String getCalculatorDateAndTime(String targetDate, int year, int month, int day, int hour) { + return getCalculatorDateAndTime(targetDate, year, month, day, hour, 0, 0); + } + + /** + * 년 월 일 계산 + * 2020-06-22T23:20:32 ISO 시간 표기법에 따라 'T'를 꼭 붙여야 함. + * 빼기는 음수를 붙여서 ex : -1 = 빼기 1 + * + * @param targetDate 계산할 년-월-일T시:분:초 + * @param year 계산할 년 + * @param month 계산할 월 + * @param day 계산할 일 + * @return String + */ + public static String getCalculatorDateAndTime(String targetDate, int year, int month, int day) { + return getCalculatorDateAndTime(targetDate, year, month, day, 0, 0, 0); + } + + /** + * 년 월 계산 + * 2020-06-22T23:20:32 ISO 시간 표기법에 따라 'T'를 꼭 붙여야 함. + * 빼기는 음수를 붙여서 ex : -1 = 빼기 1 + * + * @param targetDate 계산할 년-월-일T시:분:초 + * @param year 계산할 년 + * @param month 계산할 월 + * @return String + */ + public static String getCalculatorDateAndTime(String targetDate, int year, int month) { + return getCalculatorDateAndTime(targetDate, year, month, 0, 0, 0, 0); + } + + /** + * 년 계산 + * 2020-06-22T23:20:32 ISO 시간 표기법에 따라 'T'를 꼭 붙여야 함. + * 빼기는 음수를 붙여서 ex : -1 = 빼기 1 + * + * @param targetDate 계산할 년-월-일T시:분:초 + * @param year 계산할 년 + * @return String + */ + public static String getCalculatorDateAndTime(String targetDate, int year) { + return getCalculatorDateAndTime(targetDate, year, 0, 0, 0, 0, 0); + } + + /** + * 2020-06-22 + * 이전 날짜 확인 + * sourceDate < compareDate = true + * sourceDate > compareDate = false + * sourceDate == compareDate = false + * + * @param sourceDate 시작 년-월-일 + * @param compareDate 비교 년-월-일 + * @return boolean + */ + public static boolean isBeforeLocalDate(String sourceDate, String compareDate) { + boolean result = false; + + try { + LocalDate source = parseLocalDate(sourceDate); + result = source.isBefore(parseLocalDate(compareDate)); + } catch (Exception e) { + e.printStackTrace(); + } + + return result; + } + + /** + * 2020-06-22 + * 지난 날짜 확인 + * sourceDate > compareDate = true + * sourceDate < compareDate = false + * sourceDate == compareDate = false + * + * @param sourceDate 시작 년-월-일 + * @param compareDate 비교 년-월-일 + * @return boolean + */ + public static boolean isAfterLocalDate(String sourceDate, String compareDate) { + boolean result = false; + + try { + LocalDate source = parseLocalDate(sourceDate); + result = source.isAfter(parseLocalDate(compareDate)); + } catch (Exception e) { + e.printStackTrace(); + } + + return result; + } + + /** + * 2020-06-22T22:57:33 + * 이전 날짜&시간 확인 + * sourceDate < compareDate = true + * sourceDate > compareDate = false + * sourceDate == compareDate = false + * + * @param sourceDate 시작 년월일 + * @param compareDate 비교 년월일 + * @return boolean + */ + public static boolean isBeforeLocalDateTime(String sourceDate, String compareDate) { + boolean result = false; + + try { + LocalDateTime source = parseLocalDateTime(sourceDate); + result = source.isBefore(parseLocalDateTime(compareDate)); + } catch (final Exception e) { + e.printStackTrace(); + } + + return result; + } + + /** + * 2020-06-22T22:57:33 + * 지난 날짜&시간 확인 + * sourceDate > compareDate = true + * sourceDate < compareDate = false + * sourceDate == compareDate = false + * + * @param sourceDate 시작 년월일 + * @param compareDate 비교 년월일 + * @return boolean + */ + public static boolean isAfterLocalDateTime(String sourceDate, String compareDate) { + boolean result = false; + + try { + LocalDateTime source = parseLocalDateTime(sourceDate); + result = source.isAfter(parseLocalDateTime(compareDate)); + } catch (final Exception e) { + e.printStackTrace(); + } + + return result; + } + + /** + * 날짜 차이 계산 + * 2020-06-23 - 2020-06-22 = 1 + * 2020-06-23 - 2020-06-24 = -1 + * 앞에꺼 - 뒤에꺼 + * + * @param startDate String + * @param endDate String + * @return int + */ + public static int getDateDiffPeriodForLocalDate(String startDate, String endDate) { + int result = 0; + + try { + LocalDate source = parseLocalDate(endDate); + result = (int) ChronoUnit.DAYS.between(source, parseLocalDate(startDate)); + } catch (final Exception e) { + e.printStackTrace(); + } + + return result; + } + + /** + * 날짜 차이 계산(시분초까지 계산해줌) + * 2020-06-23T23:12:45 - 2020-06-22T23:12:45 = 1 + * 2020-07-23T23:59:59 - 2020-07-24T23:59:58 = 0 + * 2020-07-23T23:59:58 - 2020-07-24T23:59:59 = -1 + * 앞에꺼 - 뒤에꺼 + * + * @param startDate String + * @param endDate String + * @return int + */ + public static int getDateDiffPeriodForLocalDateTime(String startDate, String endDate) { + int result = 0; + + try { + LocalDateTime source = parseLocalDateTime(endDate); + result = (int) ChronoUnit.DAYS.between(source, parseLocalDateTime(startDate)); + } catch (final Exception e) { + e.printStackTrace(); + } + + return result; + } + + /** + * + * @param date + * @param sDateFormat + * @return + */ + public static String getFormatedDT(Date date, String sDateFormat) { + SimpleDateFormat sdf = new SimpleDateFormat(sDateFormat); + return sdf.format(date); + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/util/DateUtil2.java b/src/main/java/com/xit/core/util/DateUtil2.java new file mode 100644 index 0000000..03e5226 --- /dev/null +++ b/src/main/java/com/xit/core/util/DateUtil2.java @@ -0,0 +1,1071 @@ +package com.xit.core.util; + +import org.apache.commons.lang3.StringUtils; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +public class DateUtil2 { +// private static final int[] SOLAR_MONTH_ARRAY = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; +// private static final String[] TEN_KR_ARRAY = { "일", "이", "삼", "사", "오", "육", "칠", "팔", "구", "십" }; +// private static final String[] TEN_CH_ARRAY = { "一", "二", "三", "四", "五", "六", "七", "八", "九", "十" }; +// private static final String[] ZODIAC_KR_ARRAY = { "1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월" }; +// private static final String[] ZODIAC_CH_ARRAY = { "一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二" }; +// private static final String[] ZODIAC_ARRAY = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; +// private final static String[] week = {"sun", "mon", "tue", "wed", "thu", "fri", "sat"}; + + public static String getFormatedDT(String sDateFormat) { + SimpleDateFormat sdf = new SimpleDateFormat(sDateFormat); + return sdf.format(Calendar.getInstance().getTime()); + } + + public static String getFormatedDT(Date date, String sDateFormat) { + SimpleDateFormat sdf = new SimpleDateFormat(sDateFormat); + return sdf.format(date); + } + + public static Date getDateFromStr( String sDate, String sDateFormat ) { + Date resDate = null; + SimpleDateFormat sdf = new SimpleDateFormat(sDateFormat); + try { + resDate = sdf.parse(sDate); + } catch (ParseException e) { + e.printStackTrace(); + resDate = Calendar.getInstance().getTime(); + } + return resDate; + } + + public static Calendar getCalendarFromStr( String sDate, String sDateFormat ) { + Calendar calen = Calendar.getInstance(); + calen.setTime( getDateFromStr(sDate, sDateFormat) ); + return calen; + } + + public static Calendar plusTime( Calendar calen, int calendarField, int plusMinusNum ) { + calen.add(calendarField, plusMinusNum); + return calen; + } + + /** + * 현재 날짜 기준으로 몇일 전인지 후인지를 long 형식으로 리턴한다. + * 예를들어 오늘이 22일이면, targetDate가 23일 00시이면 1이 리턴되고, + * 오늘이 22일이고, targetDate가 21일 23시59분59초이면 -1이 리턴된다. + * 즉, 날짜차이가 발생하는 기준시간은 00시가 된다. + * @param targetDate 현재 날짜와 비교할 날짜 객체(Date객체) + * @return long 현재 날짜 기준으로 전날이면 날짜 차이만큼 음수가 리턴되고, 훗날이면 날짜 차이만큼 양수가 리턴된다. + */ + public static long diffDayFromCurr( Date targetDate ) { + Calendar curr = Calendar.getInstance(); + Calendar targ = Calendar.getInstance(); + targ.setTime(targetDate); + long currTime = curr.getTimeInMillis(); + currTime = (currTime / 1000 / 60 / 60 + 9) / 24; // 9를 더해주는 이유는 epoch가 1969-12-31일 15:00:00 이기 때문이다. 00시 기점으로 날짜가 변경되도록 하기 위해 9시간을 더해 줌. + long targTime = targ.getTimeInMillis(); + targTime = (targTime / 1000 / 60 / 60 + 9) / 24; + long diffDay = targTime - currTime; + return diffDay; + } + + /** + * 날짜 차이를 한글로 표현하는 함수 + * @param targetDate 현재 날짜와 비교할 날짜 객체(Date객체) + * @return String "X일 후", "X일 전", "오늘" + */ + public static String whatDaysAgoOrAfterToHangul( Date targetDate ) { + String sRes = ""; + long diffDay = diffDayFromCurr(targetDate); + if ( diffDay > 0 ) { + sRes = diffDay + "일 후"; + } else if( diffDay < 0 ) { + sRes = Math.abs(diffDay) + "일 전"; + } else { + sRes = "오늘"; + } + return sRes; + } + + + /** + *

현재 날짜와 시각을 yyyyMMdd 형태로 변환 후 return. + * + * @param cal Calendar + * @return yyyyMMdd + * + *

+	 *  - 사용 예
+	 * String date = getYyyymmdd()
+	 * 
+ */ + public static String getYyyymmdd(Calendar cal) { + String pattern = "yyyyMMdd"; + SimpleDateFormat formatter = new SimpleDateFormat(pattern, Locale.getDefault()); + return formatter.format(cal.getTime()); + } + + /** + *

GregorianCalendar 객체를 반환함. + * + * @param yyyymmdd 날짜 인수 + * @return GregorianCalendar + * @see Calendar + * @see GregorianCalendar + *

+	 *  - 사용 예
+	 * Calendar cal = getGregorianCalendar(getCurrentYyyymmdd())
+	 * 
+ */ + public static GregorianCalendar getGregorianCalendar(String yyyymmdd) { + int yyyy = Integer.parseInt(yyyymmdd.substring(0, 4)); + int mm = Integer.parseInt(yyyymmdd.substring(4, 6)); + int dd = Integer.parseInt(yyyymmdd.substring(6)); + GregorianCalendar calendar = new GregorianCalendar(yyyy, mm - 1, dd, 0, 0, 0); + return calendar; + } + + /** + *

현재 날짜와 시각을 yyyyMMddHHmmss 형태로 변환 후 return. + * + * @return String yyyyMMddHHmmss + * @see Date + * @see Locale + *

+	 *  - 사용 예
+	 * String date = getCurrentDateTime()
+	 * 
+ */ + public static String getCurrentDateTime() { + Date today = new Date(); + String pattern = "yyyyMMddHHmmss"; + SimpleDateFormat formatter = new SimpleDateFormat(pattern, Locale.getDefault()); + return formatter.format(today); + } + + /** + *

현재 날짜와 시각을 요청한 패턴(yyyyMMddHHmmss) 형태로 변환 후 return. + * + * @param pattern String + * @return yyyyMMddHHmmss + * @see Date + * @see Locale + *

+	 *  - 사용 예
+	 * String date = getCurrentDateTime()
+	 * 
+ */ + public static String getCurrentDateTime(String pattern) { + Date today = new Date(); + SimpleDateFormat formatter = new SimpleDateFormat(pattern, Locale.getDefault()); + return formatter.format(today); + } + + /** + *

현재 날짜와 시각을 yyyyMMddHHmmssSSS 형태로 변환 후 return. + * + * @return String yyyyMMddHHmmssSSS + * @see Date + * @see Locale + *

+	 *  - 사용 예
+	 * String date = getCurrentDateTime()
+	 * 
+ */ + public static String getCurrentDateTimeMile() { + Date today = new Date(); + String pattern = "yyyyMMddHHmmssSSS"; + SimpleDateFormat formatter = new SimpleDateFormat(pattern, Locale.getDefault()); + return formatter.format(today); + } + + /** + *

현재 날짜와 시각을 yyyy-MM-dd HH:mm:ss.SSS 형태로 변환 후 return. + * + * @return String yyyy-MM-dd HH:mm:ss.SSS + * @see Date + * @see Locale + *

+	 *  - 사용 예
+	 * String date = getCurrentDateTime()
+	 * 
+ */ + public static String getLogCurrentDateTimeMile() { + Date today = new Date(); + String pattern = "yyyy-MM-dd HH:mm:ss.SSS"; + SimpleDateFormat formatter = new SimpleDateFormat(pattern, Locale.getDefault()); + return formatter.format(today); + } + + /** + *

현재 시각을 HHmmss 형태로 변환 후 return. + * + * @return String HHmmss + * @see Date + * @see Locale + *

+	 *  - 사용 예
+	 *   String date = getCurrentDateTime()
+	 * 
+ */ + public static String getCurrentTime() { + Date today = new Date(); + String pattern = "HHmmss"; + SimpleDateFormat formatter = new SimpleDateFormat(pattern, Locale.getDefault()); + return formatter.format(today); + } + + /** + *

현재 날짜를 yyyyMMdd 형태로 변환 후 return. + * + * @return String yyyyMMdd + *

+	 *  - 사용 예
+	 * String date = getCurrentYyyymmdd()
+	 * 
+ */ + public static String getCurrentYyyymmdd() { + return getCurrentDateTime().substring(0, 8); + } + + /** + *

주로 일자를 구하는 메소드. + * + * @param yyyymm 년월 + * @param week 몇번째 주 + * @param pattern 리턴되는 날짜패턴 (ex:yyyyMMdd) + * @return 연산된 날짜 + * @see Calendar + *

+	 *  - 사용 예
+	 * String date = getWeekToDay("200801" , 1, "yyyyMMdd")
+	 * 
+ */ + public static String getWeekToDay(String yyyymm, int week, String pattern) { + Calendar cal = Calendar.getInstance(Locale.getDefault()); + + int new_yy = Integer.parseInt(yyyymm.substring(0,4)); + int new_mm = Integer.parseInt(yyyymm.substring(4,6)) -1; + int new_dd = 1; + + cal.set(new_yy,new_mm,new_dd); + // 일요일 ~ 토요일 + cal.add(Calendar.DATE, (week-1)*7+(cal.getFirstDayOfWeek()-cal.get(Calendar.DAY_OF_WEEK)-1)); + + SimpleDateFormat formatter = new SimpleDateFormat(pattern, Locale.getDefault()); + + return formatter.format(cal.getTime()); + } + + /** + *

지정된 플래그에 따라 연도 , 월 , 일자를 연산한다. + * + * @param field 연산 필드 + * @param amount 더할 수 + * @param date 연산 대상 날짜 + * @return 연산된 날짜 + * @see Calendar + *

+	 *  - 사용 예
+	 * String date = getOpDate(java.util.Calendar.DATE , 1, "20080101")
+	 * 
+ */ + public static String getOpDate(int field, int amount, String date) { + GregorianCalendar calDate = getGregorianCalendar(date); + + if (field == Calendar.YEAR) { + calDate.add(GregorianCalendar.YEAR, amount); + } else if (field == Calendar.MONTH) { + calDate.add(GregorianCalendar.MONTH, amount); + } else { + calDate.add(GregorianCalendar.DATE, amount); + } + + return getYyyymmdd(calDate); + } + + /** + *

입력된 일자를 더한 주를 구하여 return한다 + * + * @param yyyymmdd 년도별 + * @param addDay 추가일 + * @return 연산된 주 + * @see Calendar + *

+	 *  - 사용 예
+	 * int date = getWeek(getCurrentYyyymmdd() , 0)
+	 * 
+ */ + public static int getWeek(String yyyymmdd, int addDay){ + Calendar cal = Calendar.getInstance(Locale.getDefault()); + int new_yy = Integer.parseInt(yyyymmdd.substring(0,4)); + int new_mm = Integer.parseInt(yyyymmdd.substring(4,6)); + int new_dd = Integer.parseInt(yyyymmdd.substring(6,8)); + + cal.set(new_yy,new_mm-1,new_dd); + cal.add(Calendar.DATE, addDay); + + int week = cal.get(Calendar.DAY_OF_WEEK); + return week; + } + + /** + *

입력된 년월의 마지막 일수를 return 한다. + * + * @param year + * @param month + * @return 마지막 일수 + * @see Calendar + *

+	 *  - 사용 예
+	 * int date = getLastDayOfMon(2008 , 1)
+	 * 
+ */ + public static int getLastDayOfMon(int year, int month) { + Calendar cal = Calendar.getInstance(); + cal.set(year, month, 1); + return cal.getActualMaximum(Calendar.DAY_OF_MONTH); + + }//: + + /** + *

입력된 년월의 마지막 일수를 return한다 + * + * @param yyyymm String + * @return 마지막 일수 + *

+	 *  - 사용 예
+	 * int date = getLastDayOfMon("2008")
+	 * 
+ */ + public static int getLastDayOfMon(String yyyymm) { + Calendar cal = Calendar.getInstance(); + int yyyy = Integer.parseInt(yyyymm.substring(0, 4)); + int mm = Integer.parseInt(yyyymm.substring(4)) - 1; + + cal.set(yyyy, mm, 1); + return cal.getActualMaximum(Calendar.DAY_OF_MONTH); + } + + /** + *

입력된 날자가 올바른지 확인합니다. + * + * @param yyyymmdd + * @return boolean + *

+	 *  - 사용 예
+	 * boolean b = isCorrect("20080101")
+	 * 
+ */ + public static boolean isCorrect(String yyyymmdd) + { + if(yyyymmdd.length() < 8 ) return false; + boolean flag = false; + try + { + int yyyy = Integer.parseInt(yyyymmdd.substring(0, 4)); + int mm = Integer.parseInt(yyyymmdd.substring(4, 6)); + int dd = Integer.parseInt(yyyymmdd.substring(6)); + flag = isCorrect( yyyy, mm, dd); + } + catch(Exception ex) + { + return false; + } + return flag; + } + + /** + *

입력된 날자가 올바른 날자인지 확인합니다. + * + * @param yyyy int + * @param mm int + * @param dd int + * @return boolean + *

+	 *  - 사용 예
+	 * boolean b = isCorrect(2008,1,1)
+	 * 
+ */ + public static boolean isCorrect(int yyyy, int mm, int dd) + { + if(yyyy < 0 || mm < 0 || dd < 0) return false; + if(mm > 12 || dd > 31) return false; + + String year = "" + yyyy; + String month = "00" + mm; + String year_str = year + month.substring(month.length() - 2); + int endday = getLastDayOfMon(year_str); + + return dd <= endday; + } + + /** + *

현재 일자를 입력된 type의 날짜로 반환합니다. + * + * @param type String + * @return String + * @see java.text.DateFormat + *

+	 *  - 사용 예
+	 * String date = getThisDay("yyyymmddhhmmss")
+	 * 
+ */ + public static String getThisDay(String type) + { + Date date = new Date(); + SimpleDateFormat sdf = null; + + try{ + if(type.equalsIgnoreCase("yyyymmdd")) + { + sdf = new SimpleDateFormat("yyyyMMdd"); + return sdf.format(date); + } + + if(type.equalsIgnoreCase("yyyymmddhh")) + { + sdf = new SimpleDateFormat("yyyyMMddHH"); + return sdf.format(date); + } + + if(type.equalsIgnoreCase("yyyymmddhhmm")) + { + sdf = new SimpleDateFormat("yyyyMMddHHmm"); + return sdf.format(date); + } + + if(type.equalsIgnoreCase("yyyymmddhhmmss")) + { + sdf = new SimpleDateFormat("yyyyMMddHHmmss"); + return sdf.format(date); + } + + if(type.equalsIgnoreCase("yyyymmddhhmmssms")) + { + sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); + return sdf.format(date); + } + else + { + sdf = new SimpleDateFormat(type); + return sdf.format(date); + } + }catch(Exception e){ + return "[ ERROR ]: parameter must be 'YYYYMMDD', 'YYYYMMDDHH', 'YYYYMMDDHHSS'or 'YYYYMMDDHHSSMS'"; + } + } + + /** + *

입력된 일자를 '9999년 99월 99일' 형태로 변환하여 반환한다. + * + * @param yyyymmdd + * @return String + *

+	 *  - 사용 예
+	 * String date = changeDateFormat("20080101")
+	 * 
+ */ + public static String changeDateFormat(String yyyymmdd) + { + String rtnDate=null; + + String yyyy = yyyymmdd.substring(0, 4); + String mm = yyyymmdd.substring(4,6); + String dd = yyyymmdd.substring(6,8); + rtnDate=yyyy+" 년 "+mm + " 월 "+dd + " 일"; + + return rtnDate; + } + + /** + *

두 날짜간의 날짜수를 반환(윤년을 감안함) + * + * @param startDate 시작 날짜 + * @param endDate 끝 날짜 + * @return 날수 + * @see GregorianCalendar + *

+	 *  - 사용 예
+	 * long date = getDifferDays("20080101","20080202")
+	 * 
+ */ + public static long getDifferDays(String startDate, String endDate) { + GregorianCalendar StartDate = getGregorianCalendar(startDate); + GregorianCalendar EndDate = getGregorianCalendar(endDate); + long difer = (EndDate.getTime().getTime() - StartDate.getTime().getTime()) / 86400000; + return difer; + } + + /** + *

현재의 요일을 구한다. + * + * @return 요일 + * @see Calendar + *

+	 *  - 사용 예
+	 * int day = getDayOfWeek()
+	 *  SUNDAY    = 1
+	 *  MONDAY    = 2
+	 *  TUESDAY   = 3
+	 *  WEDNESDAY = 4
+	 *  THURSDAY  = 5
+	 *  FRIDAY    = 6
+	 * 
+ */ + public static int getDayOfWeek(){ + Calendar rightNow = Calendar.getInstance(); + return rightNow.get(Calendar.DAY_OF_WEEK); + } + + /** + *

현재주가 올해 전체의 몇째주에 해당되는지 계산한다. + * + * @return 요일 + * @see Calendar + *

+	 *  - 사용 예
+	 * int day = getWeekOfYear()
+	 * 
+ */ + public static int getWeekOfYear(){ + Calendar rightNow = Calendar.getInstance(Locale.getDefault()); + return rightNow.get(Calendar.WEEK_OF_YEAR); + } + + /** + *

현재주가 현재월에 몇째주에 해당되는지 계산한다. + * + * @return 요일 + * @see Calendar + *

+	 *  - 사용 예
+	 * int day = getWeekOfMonth()
+	 * 
+ */ + public static int getWeekOfMonth(){ + Calendar rightNow = Calendar.getInstance(Locale.getDefault()); + return rightNow.get(Calendar.WEEK_OF_MONTH); + } + + /** + *

현재주가 현재월에 몇째주에 해당되는지 계산한다. + * + * @param yyyyMMdd String + * @return 요일 + * @see Calendar + *

+	 *  - 사용 예
+	 * int day = getWeekOfMonth(yyyyMMdd)
+	 * 
+ */ + public static int getWeekOfMonth(String yyyyMMdd){ + Calendar cal = Calendar.getInstance(Locale.getDefault()); + if(yyyyMMdd.length()==8) { + int basicYear = Integer.parseInt(yyyyMMdd.substring(0,4)); + int basicMonth= Integer.parseInt(yyyyMMdd.substring(4,6))-1; + int basicDay = Integer.parseInt(yyyyMMdd.substring(6,8)); + cal.set(basicYear, basicMonth, basicDay); + } + + return cal.get(Calendar.WEEK_OF_MONTH); + } + + /** + *

현재주가 현재월에 몇째주에 해당되는지 계산한다. + * + * @param yyyyMMdd String + * @param addDate int + * @return 요일 + * @see Calendar + *

+	 *  - 사용 예
+	 * int day = getWeekOfMonth("20131201", 1)
+	 * 
+ */ + public static int getWeekOfMonth(String yyyyMMdd, int addDate){ + Calendar cal = Calendar.getInstance(Locale.getDefault()); + if(yyyyMMdd.length()==8) { + int basicYear = Integer.parseInt(yyyyMMdd.substring(0,4)); + int basicMonth= Integer.parseInt(yyyyMMdd.substring(4,6))-1; + int basicDay = Integer.parseInt(yyyyMMdd.substring(6,8)); + cal.set(basicYear, basicMonth, basicDay); + cal.add(Calendar.DATE, addDate); + } + + int week_of_month = cal.get(Calendar.WEEK_OF_MONTH); + return week_of_month; + } + + /** + *

해당 p_date날짜에 Calendar 객체를 반환함. + * + * @param p_date + * @return Calendar + * @see Calendar + *

+	 *  - 사용 예
+	 * Calendar cal = getCalendarInstance(getCurrentYyyymmdd())
+	 * 
+ */ + public static Calendar getCalendarInstance(String p_date){ + Calendar retCal = Calendar.getInstance(Locale.getDefault()); + + if(p_date != null && p_date.length() == 8){ + int year = Integer.parseInt(p_date.substring(0,4)); + int month = Integer.parseInt(p_date.substring(4,6))-1; + int date = Integer.parseInt(p_date.substring(6)); + + retCal.set(year, month, date); + } + return retCal; + } + + /** + * amount 차이만큼 날짜를 반환한다. + * @param basicDate String + * @param amount int + * @return String + */ + public static String getAddDate( String basicDate, int amount ) { + Calendar cal = Calendar.getInstance(Locale.getDefault()); + int basicYear = Integer.parseInt(basicDate.substring(0,4)); + int basicMonth= Integer.parseInt(basicDate.substring(4,6))-1; + int basicDay = Integer.parseInt(basicDate.substring(6,8)); + + cal.set(basicYear, basicMonth, basicDay) ; + cal.add(Calendar.DATE, amount); + + StringBuilder buf = new StringBuilder(); + buf.append(Integer.toString(cal.get(1))); + String month = Integer.toString(cal.get(2) + 1); + if(month.length() == 1) + month = "0" + month; + String day = Integer.toString(cal.get(5)); + if(day.length() == 1) + day = "0" + day; + buf.append(month); + buf.append(day); + return buf.toString(); + } + + public static String getWeekOfStartDate() { + return getWeekOfStartDate(getThisDay("yyyyMMdd")); + } + + public static String getWeekOfStartDate(String yyyyMMdd) { + return getWeekToDay(yyyyMMdd.substring(0,6), getWeekOfMonth(yyyyMMdd), "yyyyMMdd"); + } + + public static String getWeekOfEndDate() { + return getWeekOfEndDate(getThisDay("yyyyMMdd")); + } + + public static String getWeekOfEndDate(String yyyyMMdd) { + return getAddDate(getWeekToDay(yyyyMMdd.substring(0,6), getWeekOfMonth(yyyyMMdd), "yyyyMMdd"),6); + } + + public static String getLastMonth(String yyyyMm) { + int yyyy= Integer.parseInt(yyyyMm.substring(0,4)); + int mm = Integer.parseInt(yyyyMm.substring(4,6)) - 1; + if(mm<1) { + yyyy--; + mm = 12; + } + return Integer.toString(yyyy) + Integer.toString(mm); + } + + public static String getNextMonth(String yyyyMm) { + int yyyy= Integer.parseInt(yyyyMm.substring(0,4)); + int mm = Integer.parseInt(yyyyMm.substring(4,6)) + 1; + if(mm>12) { + yyyy++; + mm = 1; + } + return Integer.toString(yyyy) + (mm<10?"0"+mm:mm); + } + + /** + * amount 차이만큼 날짜를 반환한다. + * @param amount int + * @return String + */ + public static String getAddDate( int amount ) { + Calendar cal = Calendar.getInstance(Locale.getDefault()); + cal.add(Calendar.DATE, amount); + StringBuilder buf = new StringBuilder(); + buf.append(Integer.toString(cal.get(1))); + String month = Integer.toString(cal.get(2) + 1); + if(month.length() == 1) + month = "0" + month; + String day = Integer.toString(cal.get(5)); + if(day.length() == 1) + day = "0" + day; + buf.append(month); + buf.append(day); + return buf.toString(); + } + + /** + * 오늘 일자를 지정된 Format의 날짜 표현형식으로 돌려준다.

+ * + * 사용예) getToday("yyyy/MM/dd hh:mm a")
+ * 결 과 ) 2001/12/07 10:10 오후

+ * + * Format은 J2SE의 SimpleDateFormat의 Documentation을 참고한다. + * + * @return java.lang.String + * @param pOutformat String + */ + public static String getToday( String pOutformat) { + String rDateString = null; + Date vDate = new Date(); + + try + { + rDateString = getDateFormat( pOutformat, vDate); + + } catch( Exception e ) {} + + return rDateString; + } + + /** + * 전달받은 날짜(Date)를 지정된 Format의 날짜 표현형식으로 돌려준다.

+ * + * 사용예) getToday("yyyy/MM/dd hh:mm a")
+ * 결 과 ) 2001/12/07 10:10 오후

+ * + * Format은 J2SE의 SimpleDateFormat의 Documentation을 참고한다. + * + * @return java.lang.String + * @param pOutformat String + */ + public static String getDateFormat( String pOutformat, Date vDate) { + + SimpleDateFormat pOutformatter = new SimpleDateFormat (pOutformat, Locale.getDefault()); + + String rDateString = null; + + try + { + rDateString = pOutformatter.format(vDate); + + } catch( Exception e ) {} + + return rDateString; + } + + /** + * amount 차이만큼 날짜를 반환한다. + * @param amount int + * @param format String + * @return String + */ + public static String getAddDate(int amount, String format) { + Calendar cal = Calendar.getInstance(Locale.getDefault()); + cal.add(Calendar.DATE, amount); + if(Objects.equals(StringUtils.EMPTY, format)) format = "yyyy-MM-dd HH:mm:ss"; + SimpleDateFormat sdf = new SimpleDateFormat(format); + return sdf.format(cal.getTime()); + } + + public static String getMonthOfStartDate() { + return getAddDate(0,"yyyyMM") + "01"; + } + + public static String getMonthOfStartDate(String yyyyMm) { + return yyyyMm.substring(0,6) + "01"; + } + + public static String getMonthOfEndDate() { + return getAddDate(getNextMonth(getAddDate(0,"yyyyMM"))+"01",-1); + } + + public static String getMonthOfEndDate(String yyyyMm) { + return getAddDate(getNextMonth(yyyyMm.substring(0,6)+"01"),-1); + } + + // yyyyMMdd + public static String currentDateFormat(String fomat) { + long crrentTime = System.currentTimeMillis(); + SimpleDateFormat df = new SimpleDateFormat(fomat); + String dateFormat = df.format(crrentTime); + + return dateFormat; + } + + public static String getTimeZoneDtm() { + return getTimeZoneDtm("UTC", "yyyy-MM-dd HH:mm:ss"); + } + + public static String getTimeZoneDtm(String timeZone) { + return getTimeZoneDtm(timeZone, "yyyy-MM-dd HH:mm:ss"); + } + + public static String getTimeZoneDtm(String timeZone, String format) { + if(Objects.equals(StringUtils.EMPTY, CommUtil.nvl(timeZone))) { + timeZone = "UTC"; +// timeZone = "Asia/Seoul"; + } + + Date date = new Date(); + SimpleDateFormat sdf = new SimpleDateFormat(format); + sdf.setTimeZone(TimeZone.getTimeZone(timeZone)); + + return sdf.format(date); + } + + + + /** + * date 기준으로 amount만큼의 영업일 날짜를 반환한다. + * @param date {date:기준일, amount:날짜차이, 공휴일} + * @param amount int + * @param holidays List + * @return String + */ + public static String getBusinessDayDiff(String date, int amount, List holidays) { + + String diffDate = ""; + int cnt = 0; // 영업일 추가 카운트 + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd"); + + //List holidays = FileUtil.textFileRead("c:/dev/filetest/", "holidays"); + + if (amount > 0) { + + amount = amount+1; + + while(amount > 0){ + diffDate = getAddDate(date, cnt); + + Date nDate; + try { + nDate = dateFormat.parse(diffDate); + + Calendar cal = Calendar.getInstance(); + cal.setTime(nDate); + + int dayNum = cal.get(cal.DAY_OF_WEEK); + + boolean holiFlag = false; + if (dayNum != 1 && dayNum != 7) { + + for(String holiday : holidays) { + if (Objects.equals(diffDate, holiday)) { + holiFlag = true; + break; + } + } + + if(!holiFlag) { + amount--; + } + } + + cnt++; + + } catch (ParseException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + } else { + + amount = amount-1; + + while(amount < 0){ + diffDate = getAddDate(date, cnt); + + Date nDate; + try { + nDate = dateFormat.parse(diffDate); + + Calendar cal = Calendar.getInstance(); + cal.setTime(nDate); + + int dayNum = cal.get(cal.DAY_OF_WEEK); + + if (dayNum != 1 && dayNum != 7) { + + boolean holiFlag = false; + for(String holiday : holidays) { + if (Objects.equals(diffDate, holiday)) { + holiFlag = true; + break; + } + + } + + if(!holiFlag) { + amount++; + } + + } + + cnt--; + + } catch (ParseException e) { + e.printStackTrace(); + } + } + } + + return diffDate; + + } + + + /** + * 정산종료일을 알기위한 처리 + * @param date {date:정산시작일, 공휴일} + * @param holidays List + * @return String + */ + public static String getConectedLastHoliday(String date, List holidays) { + + String diffDate = date; + int cnt = 1; // 영업일 추가 카운트 + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd"); + + //List holidays = textFileRead("c:/dev/filetest/", "holidays"); + + boolean holiFlag = true; + while(holiFlag){ + diffDate = getAddDate(date, cnt); + Date nDate; + try { + nDate = dateFormat.parse(diffDate); + + Calendar cal = Calendar.getInstance(); + cal.setTime(nDate); + + int dayNum = cal.get(cal.DAY_OF_WEEK); + + if(dayNum == 1 || dayNum == 7) { + holiFlag = true; + }else { + + if(!holidays.contains(diffDate)){ + holiFlag = false; + //마지막 날짜 하루 줄임 + diffDate = getAddDate(diffDate,-1); + } + } + cnt++; + }catch (Exception e) { + e.printStackTrace(); + } + } + + return diffDate; + } + + /** + * 공휴일 여부 + * @param date {date:날짜, 공휴일} + * @param holidays List + * @return boolean + */ + public static boolean getHolidayFlag(String date, List holidays) { + boolean holiFlag = false; + + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd"); + + Date nDate; + try { + nDate = dateFormat.parse(date); + + Calendar cal = Calendar.getInstance(); + cal.setTime(nDate); + + int dayNum = cal.get(cal.DAY_OF_WEEK); + + if(dayNum == 1 || dayNum == 7) { + holiFlag = true; + }else if(holidays.contains(date)){ + holiFlag = true; + } + + }catch (Exception e) { + e.printStackTrace(); + } + return holiFlag; + } + + /** + * 현재 서버 시간을 Date 로 반환 + * @return Date + */ + public static Date getCurrDateTime() { + return calculateDateTime(0, 0, 0, 0); + } + + /** + * Date 계산 후 반환 + * @param sec int + * @param min int + * @param hour int + * @param date int + * @return Date + */ + public static Date calculateDateTime(int sec, int min, int hour, int date) { + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.SECOND ,sec); + cal.add(Calendar.MINUTE ,min); + cal.add(Calendar.HOUR ,hour); + cal.add(Calendar.DATE ,date); + + return cal.getTime(); + } + + + + + + + + + + + + + + + public static void main(String args[]) throws Exception { + String format = "yyyy-MM-dd HH:mm:ss"; + System.out.println("Current Local Time : " +getCurrentDateTime(format)); + System.out.println("UTC : " +getTimeZoneDtm(format)); + System.out.println("America/Los_Angeles : " +getTimeZoneDtm("America/Los_Angeles")); + System.out.println("Current Local Time : " +getCurrentDateTime(format)); + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/xit/core/util/DeviceInfo.java b/src/main/java/com/xit/core/util/DeviceInfo.java new file mode 100644 index 0000000..b640cd1 --- /dev/null +++ b/src/main/java/com/xit/core/util/DeviceInfo.java @@ -0,0 +1,28 @@ +package com.xit.core.util; + +import lombok.Data; + +/** + * Device 정보 Class + * com.golfzon.newgm.infra.config.common.domain.device DeviceInfo.java + * @author sjisbmoc + * @since + * @version 1.0 + * @see
+ * == 계정이력(Modification Infomation) ==
+ * 
+ * 수정일			수정자		수정내용
+ * ----------------------------------------
+ * 2021. 4. 6.	sjisbmoc	최초생성
+ * 
+ * 
+ */ +@Data +public class DeviceInfo { + + private String userAgent; // 사용자 Agent 정보 + private String browser; // 브라우져 + private String os; // Operating System + private String device; // 접속 장비 구분 [ Mobile, Tablet, PC ] + private String ip; // 접속 IP +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/util/JsonUtil.java b/src/main/java/com/xit/core/util/JsonUtil.java new file mode 100644 index 0000000..ba28a8f --- /dev/null +++ b/src/main/java/com/xit/core/util/JsonUtil.java @@ -0,0 +1,178 @@ +package com.xit.core.util; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * JSON Util Class + * com.golfzon.newgm.infra.config.common.util JsonUtil.java + * @author sjisbmoc + * @since + * @version 1.0 + * @see
+ * == 계정이력(Modification Infomation) ==
+ * 
+ * 수정일			수정자		수정내용
+ * ----------------------------------------
+ * 2021. 4. 6.	sjisbmoc	최초생성
+ * 
+ * 
+ */ +public class JsonUtil { + + /** + * Object 를 json string으로 변환 + * @return String + * @param obj + * @throws JsonProcessingException + */ + public static String toJson(Object obj) throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(Include.NON_NULL); +// mapper.setSerializationInclusion(Include.NON_EMPTY); + return obj != null ? mapper.writeValueAsString(obj) : null; + } + + /** + * Json string을 지정된 class로 변환 + * @return T + * @param str + * @param cls + * @throws IOException + */ + public static T toObject(String str, Class cls) throws IOException { + ObjectMapper om = new ObjectMapper(); + om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return str != null ? om.readValue(str, cls) : null; + } + + /** + * Json string을 지정된 class로 변환 + * @return T + * @param str + * @param cls + * @throws IOException + */ + public static T toObjByObj(Object obj, Class cls) throws IOException { + String str = toJson(obj); + ObjectMapper om = new ObjectMapper(); + om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return str != null ? om.readValue(str, cls) : null; + } + + /** + * Json string을 지정된 class list로 변환 + * @return T + * @param str + * @param cls + * @throws IOException + */ + public static List toObjectList(String str, Class cls) throws IOException { + ObjectMapper om = new ObjectMapper(); + om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + if(str != null){ + return om.readValue(str, om.getTypeFactory().constructCollectionType(List.class, cls)); + }else { + return null; + } + } + + /** + * JSON 문자열을 Map 구조체로 변환 + * @param String str + * @return + * @exception + */ + public static Map toMap(String str) throws IOException{ + Map map = null; + ObjectMapper om = new ObjectMapper(); + map = om.readValue(str, new TypeReference>(){}); + return map; + } + + /** + * Json 데이터 보기 좋게 변환. + * @param String json + * @return String + */ + public static String jsonEnterConvert(Object obj) { + String rltStr = ""; + + try { + rltStr = jsonEnterConvert((JsonUtil.toJson(obj)).toString()); + } catch(Exception e) { + rltStr = ""; + } + + return rltStr; + } + + /** + * Json 데이터 보기 좋게 변환. + * @param String json + * @return String + */ + private static String jsonEnterConvert(String json) { + + if( json == null || json.length() < 2 ) + return json; + + final int len = json.length(); + final StringBuilder sb = new StringBuilder(); + char c; + String tab = ""; + boolean beginEnd = true; + for( int i=0 ; i 0 ) + sb.insert(0, '\n'); + return sb.toString(); + } +} + diff --git a/src/main/java/com/xit/core/util/LogUtil.java b/src/main/java/com/xit/core/util/LogUtil.java new file mode 100644 index 0000000..ac2b10d --- /dev/null +++ b/src/main/java/com/xit/core/util/LogUtil.java @@ -0,0 +1,43 @@ +package com.xit.core.util; + +/** + * Log Util Class + * com.golfzon.newgm.infra.config.common.util LogUtil.java + * @author sjisbmoc + * @since + * @version 1.0 + * @see
+ * == 계정이력(Modification Infomation) ==
+ * 
+ * 수정일			수정자		수정내용
+ * ----------------------------------------
+ * 2021. 4. 6.	sjisbmoc	최초생성
+ * 
+ * 
+ */ +public class LogUtil { + + /** + * Json 데이터 보기 좋게 변환. + * @param Object obj + * @return String + */ + public static Object toString(Object obj){ + return JsonUtil.jsonEnterConvert(obj); + } + + public static String getClassNm(Throwable throwable) { + StackTraceElement[] stacks = throwable.getStackTrace(); + return stacks[0].getClassName(); + } + + public static String getMethodNm(Throwable throwable) { + StackTraceElement[] stacks = throwable.getStackTrace(); + return stacks[ 0 ].getMethodName(); + } + + public static String getMethodInfo(Throwable throwable) { + StackTraceElement[] stacks = throwable.getStackTrace(); + return stacks[0].getClassName() + "." + stacks[0].getMethodName(); + } +} diff --git a/src/main/java/com/xit/core/util/PoiExcelView.java b/src/main/java/com/xit/core/util/PoiExcelView.java new file mode 100644 index 0000000..058e34e --- /dev/null +++ b/src/main/java/com/xit/core/util/PoiExcelView.java @@ -0,0 +1,194 @@ +package com.xit.core.util; + +import org.apache.poi.hssf.usermodel.HSSFFont; +import org.apache.poi.ss.usermodel.*; +import org.springframework.web.servlet.view.document.AbstractXlsxView; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.*; + +public class PoiExcelView extends AbstractXlsxView { + + + String fileName = ""; + + public PoiExcelView(String fileName) { + this.fileName = fileName; + } + + private String getBrowser(HttpServletRequest request) { + String header = request.getHeader("User-Agent"); + if (header.contains("MSIE")) { + return "MSIE"; + } else if (header.contains("Trident")) { // IE11 문자열 깨짐 방지 + return "Trident"; + } else if (header.contains("Chrome")) { + return "Chrome"; + } else if (header.contains("Opera")) { + return "Opera"; + } + return "Firefox"; + } + + private void setDisposition(String filename, HttpServletRequest request, HttpServletResponse response) throws Exception { + String browser = getBrowser(request); + + String dispositionPrefix = "attachment; filename="; + String encodedFilename = null; + + byte[] BOM = new byte[4]; + BOM = filename.getBytes(); + + + String encodingType = CommUtil.getEncodingType(filename); + + // UTF-8 인 경우 + if (Objects.equals("UTF-8", CommUtil.getEncodingType(filename))){ + + encodedFilename = filename; + + }else if (Objects.equals("MSIE", browser) || Objects.equals("Trident", browser)) {// IE11 문자열 깨짐 방지 + encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8).replaceAll("\\+", "%20"); + + } else { + String encodedFilename1 = "\"" + new String(filename.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1) + "\""; + + if (Objects.equals("Firefox", browser)) { + encodedFilename = encodedFilename1; + } else if (Objects.equals("Opera", browser)) { + encodedFilename = encodedFilename1; + } else if (Objects.equals("Chrome", browser)) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < filename.length(); i++) { + char c = filename.charAt(i); + if (c > '~') { + sb.append(URLEncoder.encode("" + c, StandardCharsets.UTF_8)); + } else { + sb.append(c); + } + } + encodedFilename = sb.toString(); + } else { + throw new IOException("Not supported browser"); + } + } + + response.setHeader("fileName", encodedFilename); + response.setHeader("Content-Disposition", dispositionPrefix + encodedFilename); + + if (Objects.equals("Opera", browser)) { + response.setContentType("application/octet-stream;charset=UTF-8"); + } + } + + @SuppressWarnings("unchecked") + @Override + protected void buildExcelDocument(Map model, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception { + + String mimetype = "application/x-msdownload"; + + response.setContentType(mimetype); + setDisposition(fileName, request, response); + + Sheet sheet = workbook.createSheet(); + + List titleList = (List) model.get("titleList"); + List compareList = (List) model.get("fieldList"); + List> contentList = (List>) model.get("contentList"); + int titleIndex = 0; + + //본문 로우 처리 + int contentIndex = 0; + int keyIndex = 0; + Row row = null; + Cell cell = null; + String key = ""; + Object value = null; + + Font headeFont = workbook.createFont(); + headeFont.setFontName(HSSFFont.FONT_ARIAL); + headeFont.setFontHeightInPoints((short) 8); + headeFont.setBold(true); + + CellStyle headStyle = workbook.createCellStyle(); + headStyle.setAlignment(HorizontalAlignment.CENTER); + headStyle.setFont(headeFont); + headStyle.setFillBackgroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); + + Font defaultFont = workbook.createFont(); + defaultFont.setFontName(HSSFFont.FONT_ARIAL); + defaultFont.setFontHeightInPoints((short) 8); + + CellStyle defaultStyle = workbook.createCellStyle(); + defaultStyle.setAlignment(HorizontalAlignment.CENTER); + defaultStyle.setFont(defaultFont); + + // 오토 너비 사이즈 조절 + for (int i=0; i map : contentList) { + + int fieldIndex = 0; + + row = sheet.createRow(contentIndex + 1); + for (String s : map.keySet()) { + keyIndex = 0; + key = s; + value = map.get(key); + + if (compareList != null) { + if (compareList.contains(key)) { + keyIndex = compareList.indexOf(key); + } else { + continue; + } + } else { + keyIndex = fieldIndex++; + } + + cell = row.createCell(keyIndex); + cell.setCellValue(String.valueOf(value)); + cell.setCellStyle(defaultStyle); + } + + contentIndex++; + } + } + + public static void main(String[] args) { + List titleList = Arrays.asList( "이름", "생년월일", "핸드폰번호", "전화", "이메일"); + List fieldList = Arrays.asList( "MBR_NM", "BRTHDY", "HP_NO", "TEL_NO", "EMAIL"); + + List> listRes = new ArrayList<>(); + Map map = new HashMap<>(); + map.put("MBR_NM", "fsafas"); + map.put("BRTHDY", "12341212"); + map.put("HP_NO", "01011112222"); + map.put("TEL_NO", "0311231234"); + map.put("EMAIL", "a@b.com"); + listRes.add(map); + + //model.addAttribute("titleList", titleList); + //model.addAttribute("fieldList", fieldList); + //model.addAttribute("contentList", listRes); + + new PoiExcelView(new SimpleDateFormat("yyyyMMdd").format(new Date()) + "_회원목록.xlsx"); + + } +} diff --git a/src/main/java/com/xit/core/util/SpringUtils.java b/src/main/java/com/xit/core/util/SpringUtils.java new file mode 100644 index 0000000..130d424 --- /dev/null +++ b/src/main/java/com/xit/core/util/SpringUtils.java @@ -0,0 +1,62 @@ +package com.xit.core.util; + +import com.xit.core.config.support.ApplicationContextProvider; +import com.xit.core.oauth2.api.service.IAuthService; +import com.xit.core.oauth2.api.service.impl.AuthService; +import com.xit.core.oauth2.oauth.token.JwtTokenProvider; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.MessageSourceAccessor; + +/** + * Get Bean Object + * Filter / Interceptor 등에서 Bean 사용시 필요 + * (Bean으로 등록되는 클래스 내에서만 @Autowired / @Resource 등이 동작) + * + * @see ApplicationContextProvider + */ +public class SpringUtils { + + public static ApplicationContext getApplicationContext() { + return ApplicationContextProvider.getApplicationContext(); + } + + public static boolean containsBean(String beanName) { + return getApplicationContext().containsBean(beanName); + } + + public static Object getBean(String beanName) { + return getApplicationContext().getBean(beanName); + } + + public static Object getBean(Class clazz) { + return getApplicationContext().getBean(clazz); + } + + /** + * + * @return MessageSourceAccessor + */ + public static MessageSourceAccessor getMessageSourceAccessor(){ + return (MessageSourceAccessor)getBean(MessageSourceAccessor.class); + } + +// public static CacheService getCodeService(){ +// return (CacheService)getBean(CacheService.class); +// } + + /** + * + * @return IAuthService AuthService + */ + public static IAuthService getAuthService(){ + return (IAuthService)getBean(AuthService.class); + } + + /** + * + * @return AuthTokenProvider + */ + public static JwtTokenProvider getJwtTokenProvider(){ + return (JwtTokenProvider)getBean(JwtTokenProvider.class); + } +} diff --git a/src/main/java/com/xit/core/util/ValidationUtil.java b/src/main/java/com/xit/core/util/ValidationUtil.java new file mode 100644 index 0000000..e0b1744 --- /dev/null +++ b/src/main/java/com/xit/core/util/ValidationUtil.java @@ -0,0 +1,408 @@ +package com.xit.core.util; + +import java.util.Objects; +import java.util.regex.Pattern; + +public class ValidationUtil { + + private static final Pattern NUMERIC = Pattern.compile("^[0-9]+$"); + private static final Pattern INTEGER = Pattern.compile("^\\-?[0-9]+$"); + private static final Pattern DECIAML = Pattern.compile("^\\-?[0-9]*\\.?[0-9]+$"); + private static final Pattern EMAIL = Pattern.compile("^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))$"); + private static final Pattern ALPHA = Pattern.compile("^[A-Za-z]+$"); + private static final Pattern ALPHA_NUMERIC = Pattern.compile("^[A-Za-z0-9]+$"); + private static final Pattern ALPHA_DASH = Pattern.compile("^[A-Za-z\\-]+$"); + private static final Pattern NUMERIC_DASH = Pattern.compile("^[\\d\\-\\s]+$"); + private static final Pattern IPADDRESS = Pattern.compile("^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})$"); + private static final Pattern URL = Pattern.compile("^(https?|s?ftp):\\/\\/(((([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(%[\\da-f]{2})|[!\\$&'\\(\\)\\*\\+,;=]|:)*@)?(((\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.(\\d|[1-9]\\d|1\\d\\d|2[0-4]\\d|25[0-5]))|((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.?)(:\\d*)?)(\\/((([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(%[\\da-f]{2})|[!\\$&'\\(\\)\\*\\+,;=]|:|@)+(\\/(([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(%[\\da-f]{2})|[!\\$&'\\(\\)\\*\\+,;=]|:|@)*)*)?)?(\\?((([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(%[\\da-f]{2})|[!\\$&'\\(\\)\\*\\+,;=]|:|@)|[\\uE000-\\uF8FF]|\\/|\\?)*)?(#((([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(%[\\da-f]{2})|[!\\$&'\\(\\)\\*\\+,;=]|:|@)|\\/|\\?)*)?$"); + //private static final Pattern HANGUL = Pattern.compile("^[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]+$"); + private static final Pattern HANGUL = Pattern.compile(".*[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]+.*"); + private static final Pattern WHITESPACE = Pattern.compile("\\s+"); + private static final Pattern PHONE = Pattern.compile("^(01[016789]{1}|02|0[3-9]{1}[0-9]{1})-?[0-9]{3,4}-?[0-9]{4}$"); + private static final Pattern MOBILE_PHONE = Pattern.compile("^01([0|1|6|7|8|9]?)-?([0-9]{3,4})-?([0-9]{4})$"); + private static final Pattern ID = Pattern.compile("^[a-z]{1}[a-z0-9]+$"); + + + /** + * Numeric 체크 + * @param value 문자열데이터 + * @return boolean + */ + public static boolean isNumeric(String value) { + if(value == null){ + return false; + } + return NUMERIC.matcher(value).matches(); + } + + /** + * Integer 체크 + * @param value 문자열데이터 + * @return boolean + */ + public static boolean isInteger(String value) { + if(value == null){ + return false; + } + return INTEGER.matcher(value).matches(); + } + + /** + * Decimal 체크 + * @param value 문자열데이터 + * @return boolean + */ + public static boolean isDecimal(String value) { + if(value == null){ + return false; + } + return DECIAML.matcher(value).matches(); + } + + /** + * Email 체크 + * @param value 문자열데이터 + * @return boolean + */ + public static boolean isEmail(String value) { + if(value == null){ + return false; + } + return EMAIL.matcher(value).matches(); + } + + /** + * Alpha 체크 + * @param value 문자열데이터 + * @return boolean + */ + public static boolean isAlpha(String value) { + if(value == null){ + return false; + } + return ALPHA.matcher(value).matches(); + } + + /** + * Alpha or Numeric 체크 + * @param value 문자열데이터 + * @return boolean + */ + public static boolean isAlphaNumeric(String value) { + if(value == null){ + return false; + } + return ALPHA_NUMERIC.matcher(value).matches(); + } + + /** + * Alpha or Dash 체크 + * @param value 문자열데이터 + * @return boolean + */ + public static boolean isAlphaDash(String value) { + if(value == null){ + return false; + } + return ALPHA_DASH.matcher(value).matches(); + } + + /** + * Numeric or Dash 체크 + * @param value 문자열데이터 + * @return boolean + */ + public static boolean isNumericDash(String value) { + if(value == null){ + return false; + } + return NUMERIC_DASH.matcher(value).matches(); + } + + /** + * IP Address 체크 + * @param value 문자열데이터 + * @return boolean + */ + public static boolean isIpAddress(String value) { + if(value == null){ + return false; + } + return IPADDRESS.matcher(value).matches(); + } + + /** + * URL 체크 + * @param value 문자열데이터 + * @return boolean + */ + public static boolean isUrl(String value) { + if(value == null){ + return false; + } + return URL.matcher(value).matches(); + } + + /** + * 한글 체크 + * @param value 문자열데이터 + * @return boolean + */ + public static boolean isHangul(String value) { + if(value == null){ + return false; + } + return HANGUL.matcher(value).matches(); + } + + /** + * 공백문제 채크 + * @param value 문자열데이터 + * @return boolean + */ + public static boolean isWhitespace(String value) { + if(value == null) { + return false; + } + return WHITESPACE.matcher(value).matches(); + } + + /** + * Phone Number 체크 + * @param value 문자열데이터 + * @return boolean + */ + public static boolean isPhone(String value) { + if(value == null){ + return false; + } + return PHONE.matcher(value).matches(); + } + + /** + * MobilePhon Number 체크 + * @param value 문자열데이터 + * @return boolean + */ + public static boolean isMobilePhone(String value) { + if(value == null){ + return false; + } + return MOBILE_PHONE.matcher(value).matches(); + } + + /** + * 빈문자열 체크 + * @param value 문자열데이터 + * @return boolean + */ + public static boolean isEmpty(String value) { + if(value == null){ + return true; + } else if(value.trim().length() == 0){ + return true; + } + return false; + } + + /** + * 빈문자열 혹은 공백문제 체크 + * @param value 문자열데이터 + * @return boolean + */ + public static boolean isEmptyWhitespace(String value){ + return (isEmpty(value) || isWhitespace(value)); + } + + /** + * 문자열 길이 체크 + * @param value 문자열데이터 + * @param length 길이 + * @return boolean + */ + public static boolean isLength(String value, int length){ + if(value == null){ + return false; + } + return value.trim().length() == length; + } + + /** + * 문자열 길이 Min ~ Max 체크 + * @param value 문자열데이터 + * @param min 최소길이 + * @param max 최대길이 + * @return boolean + */ + public static boolean isLength(String value, int min, int max){ + if(value == null){ + return false; + } + if(value.length() < min){ + return false; + } + if(value.length() > max){ + return false; + } + + return true; + } + + /** + * Yes 혹은 No 체크 + * @param value 문자열데이터 + * @return boolean + */ + public static boolean isYesNo(String value){ + if(value == null){ + return false; + } + return Objects.equals("Y", value) || Objects.equals("N", value); + } + + /** + * 문자열 비교 + * @param value 문자열데이터 + * @param compare 비교문자 + * @return boolean + */ + public static boolean isEquals(String value, String compare) { + return compare.equals(value); + } + + /** + * 숫자 비교 + * @param value 숫자데이터 + * @param compare 비교숫자 + * @return boolean + */ + public static boolean isEquals(int value, int compare) { + return compare == value; + } + + /** + * 비교문자데이터중에서 해당 문자데이터가 있는지 여부 + * @param value 문자데이터 + * @param values 비교문자데이터 목록 + * @return boolean + */ + public static boolean selected(String value, String[] values) { + if(value == null || values == null || values.length == 0){ + return false; + } + for(String val : values){ + if(value.equals(val)){ + return true; + } + } + return false; + } + + /** + * 비교숫자데이터중에서 해당 숫자데이터가 있는지 여부 + * @param value 숫자데이터 + * @param values 비교숫자데이터 목록 + * @return boolean + */ + public static boolean selected(int value, int[] values) { + if(values.length == 0){ + return false; + } + for(int val : values){ + if(value == val){ + return true; + } + } + return false; + } + + /** + * @param little 작은값 + * @param lot 같거나 큰값 + * @return 비교여부 + */ + public static boolean sizeCompare(int little, int lot) { + return little <= lot; + } + + /** + * 문자열 길이(바이트 단위) + */ + public static int cstrlen(String s) { + return cstrlen(s, "KSC5601"); + } + + public static int cstrlen(String s, String enc) { + byte[] src = null; + try { + src = s.getBytes(enc); + return src.length; + } catch (java.io.UnsupportedEncodingException e) { + return 0; + } + } + + /** + * @param value 문자열데이터 + * @param min 최소길이 + * @param max 최대길이 + * @return byte 일치 여부 + */ + public static boolean isByte(String value, int min, int max){ + if(value == null){ + return false; + } + int byte_ = cstrlen(value); + if(byte_ < min){ + return false; + } + if(byte_ > max){ + return false; + } + return true; + } + + /** + * @param value 문자열데이터 + * @param min 최소길이 + * @return byte 일치 여부 + */ + public static boolean isMinByte(String value, int min){ + if(value == null){ + return false; + } + int byte_ = cstrlen(value); + if(byte_ < min){ + return false; + } + return true; + } + + /** + * @param value 문자열데이터 + * @param max 최대길이 + * @return boolean + */ + public static boolean isMaxByte(String value, int max){ + if(value == null){ + return true; + } + int byte_ = cstrlen(value); + if(byte_ > max){ + return false; + } + return true; + } + + /** + * ID 체크 + * @param value + * @return + */ + public static boolean isID(String value) { + if(value == null){ + return false; + } + return ID.matcher(value).matches(); + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/util/YamlPropertySourceFactory.java b/src/main/java/com/xit/core/util/YamlPropertySourceFactory.java new file mode 100644 index 0000000..c5cdb29 --- /dev/null +++ b/src/main/java/com/xit/core/util/YamlPropertySourceFactory.java @@ -0,0 +1,46 @@ +package com.xit.core.util; + +import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.support.EncodedResource; +import org.springframework.core.io.support.PropertySourceFactory; +import org.springframework.lang.Nullable; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Properties; + +public class YamlPropertySourceFactory implements PropertySourceFactory { + @Override + public PropertySource createPropertySource(@Nullable String name, EncodedResource resource) throws IOException { + Properties propertiesFromYaml = loadYamlIntoProperties(resource); + String sourceName = name != null ? name : resource.getResource().getFilename(); + return new PropertiesPropertySource(sourceName, propertiesFromYaml); + } + + private Properties loadYamlIntoProperties(EncodedResource resource) throws FileNotFoundException { + try { + YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); + factory.setResources(resource.getResource()); + factory.afterPropertiesSet(); + return factory.getObject(); + } catch (IllegalStateException e) { + Throwable cause = e.getCause(); + if (cause instanceof FileNotFoundException) + throw (FileNotFoundException) e.getCause(); + throw e; + } + } +} + +/* + +@PropertySource(value = "classpath:constants.yml", + factory = YamlPropertySourceFactory.class, ignoreResourceNotFound = true) +public class CustomConfig { + public class CumstomConfig(@Value("${info.code}") String code){ + //use code value + } +} + */ \ No newline at end of file diff --git a/src/main/java/com/xit/core/util/json/ConvertHelper.java b/src/main/java/com/xit/core/util/json/ConvertHelper.java new file mode 100644 index 0000000..1fd089a --- /dev/null +++ b/src/main/java/com/xit/core/util/json/ConvertHelper.java @@ -0,0 +1,52 @@ +package com.xit.core.util.json; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.joda.JodaModule; +import lombok.NoArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.StringWriter; + +@NoArgsConstructor +public class ConvertHelper{ + private static final Logger log = LoggerFactory.getLogger(ConvertHelper.class); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final JsonFactory JSON_FACTORY = new JsonFactory(); + + public static String jsonToObject(Object o){ + JsonGenerator generator = null; + StringWriter writer = new StringWriter(); + + try{ + OBJECT_MAPPER.registerModule(new JodaModule()); + generator = JSON_FACTORY.createGenerator(writer); + OBJECT_MAPPER.writeValue(generator, o); + generator.flush(); + return writer.toString(); + }catch(Exception e){ + e.printStackTrace(); + return null; + }finally{ + if(generator != null){ + try{ + generator.close(); + }catch(Exception e){ + log.error("InternalServerError: {}", e.getLocalizedMessage()); + } + } + } + } + + public static Object objectToJson(String json, Class clazz){ + try{ + OBJECT_MAPPER.registerModule(new JodaModule()); + return OBJECT_MAPPER.readValue(json, clazz); + }catch(Exception e){ + e.printStackTrace(); + return null; + } + } +} diff --git a/src/main/java/com/xit/core/util/json/JsonMapper.java b/src/main/java/com/xit/core/util/json/JsonMapper.java new file mode 100644 index 0000000..e4ffbb3 --- /dev/null +++ b/src/main/java/com/xit/core/util/json/JsonMapper.java @@ -0,0 +1,50 @@ +package com.xit.core.util.json; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.module.afterburner.AfterburnerModule; + + +public class JsonMapper{ + private static final ObjectMapper mapper = new ObjectMapper(); + + /** + * ex : Map map = getMapper().convertValue(Object o, Map.class); + * @return + */ + public static ObjectMapper getMapper(){ + return setDefaultOptionOnMapper(); + } + + public static ObjectReader getReader(Class T){ + return setDefaultOptionOnMapper().readerFor(T); + } + + public static ObjectReader getReader(JavaType T){ + return setDefaultOptionOnMapper().readerFor(T); + } + + public static ObjectReader getReader(TypeReference type){ + return setDefaultOptionOnMapper().readerFor(type); + } + + public static ObjectWriter getWriter(Class T){ + return setDefaultOptionOnMapper().writerFor(T); + } + + public static ObjectWriter getWriter(JavaType T){ + return setDefaultOptionOnMapper().writerFor(T); + } + + public static ObjectWriter getWriter(TypeReference T){ + return setDefaultOptionOnMapper().writerFor(T); + } + + + private static ObjectMapper setDefaultOptionOnMapper(){ + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + mapper.registerModule(new AfterburnerModule()); + return mapper; + } +} diff --git a/src/main/java/com/xit/core/util/security/Aes128.java b/src/main/java/com/xit/core/util/security/Aes128.java new file mode 100644 index 0000000..d036a04 --- /dev/null +++ b/src/main/java/com/xit/core/util/security/Aes128.java @@ -0,0 +1,96 @@ +package com.xit.core.util.security; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; + +public class Aes128 { + + // AES128 Key (16byte) + public static String AES128_KEY = "newgm2021golfzon"; + + // 값 체크용 + public static void main(String[] args) { + + Aes128 test = new Aes128(); + + String message = "20210517001501_newgmapi"; + + try { + String enc = test.encrypt(message); + System.out.println(message + " 값 암호화 : " + enc); + String dec = test.decrypt(enc); + System.out.println(enc + " 값 복호화 : " + dec); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static String encrypt(String message) throws Exception { + + if (message == null) { + return null; + } else { + SecretKeySpec secretKeySpec = new SecretKeySpec( + AES128_KEY.getBytes(), "AES"); + + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); + + byte[] encrypted = cipher.doFinal(message.getBytes()); + + return byteArrayToHex(encrypted); + } + } + + private static String byteArrayToHex(byte[] encrypted) { + + if (encrypted == null || encrypted.length == 0) { + return null; + } + + StringBuffer sb = new StringBuffer(encrypted.length * 2); + String hexNumber; + + for (int x = 0; x < encrypted.length; x++) { + hexNumber = "0" + Integer.toHexString(0xff & encrypted[x]); + sb.append(hexNumber.substring(hexNumber.length() - 2)); + } + + return sb.toString(); + } + + public static String decrypt(String encrypted) throws Exception { + + if (encrypted == null) { + return null; + } else { + SecretKeySpec secretKeySpec = new SecretKeySpec( + AES128_KEY.getBytes(), "AES"); + + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); + + byte[] original = cipher.doFinal(hexToByteArray(encrypted)); + + String originalStr = new String(original); + + return originalStr; + } + } + + private static byte[] hexToByteArray(String hex) { + + if (hex == null || hex.length() == 0) { + return null; + } + + // 16진수 문자열을 byte로 변환 + byte[] byteArray = new byte[hex.length() / 2]; + + for (int i = 0; i < byteArray.length; i++) { + byteArray[i] = (byte) Integer.parseInt( + hex.substring(2 * i, 2 * i + 2), 16); + } + return byteArray; + } +} \ No newline at end of file diff --git a/src/main/java/com/xit/core/util/security/HashingPasswordEncoder.java b/src/main/java/com/xit/core/util/security/HashingPasswordEncoder.java new file mode 100644 index 0000000..c49d75f --- /dev/null +++ b/src/main/java/com/xit/core/util/security/HashingPasswordEncoder.java @@ -0,0 +1,27 @@ +package com.xit.core.util.security; + +import org.springframework.security.crypto.password.PasswordEncoder; + +public class HashingPasswordEncoder implements PasswordEncoder { + + public String encode(CharSequence rawPassword) { + +//System.out.println("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); +//System.out.println("rawPassword : "+rawPassword.toString()); +//System.out.println(HashingPasswordUtil.createHash(rawPassword.toString())); +//System.out.println("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); + + return HashingPasswordUtil.createHash(rawPassword.toString()); + } + + public boolean matches(CharSequence rawPassword, String encodedPassword) { + +//System.out.println("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"); +//System.out.println("rawPassword : "+rawPassword.toString()); +//System.out.println("encodedPassword : "+encodedPassword); +//System.out.println("HashingPasswordUtil.validatePassword(rawPassword.toString(), encodedPassword) : "+HashingPasswordUtil.validatePassword(rawPassword.toString(), encodedPassword)); +//System.out.println("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"); + + return HashingPasswordUtil.validatePassword(rawPassword.toString(), encodedPassword); + } +} diff --git a/src/main/java/com/xit/core/util/security/HashingPasswordUtil.java b/src/main/java/com/xit/core/util/security/HashingPasswordUtil.java new file mode 100644 index 0000000..776a986 --- /dev/null +++ b/src/main/java/com/xit/core/util/security/HashingPasswordUtil.java @@ -0,0 +1,214 @@ +package com.xit.core.util.security; + +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.validation.constraints.NotNull; +import java.math.BigInteger; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; + +public class HashingPasswordUtil { + private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA1"; + // The following constants may be changed without breaking existing hashes. + private static final int SALT_BYTE_SIZE = 24; + private static final int HASH_BYTE_SIZE = 24; + private static final int PBKDF2_ITERATIONS = 1000; + private static final int ITERATION_INDEX = 0; + private static final int SALT_INDEX = 1; + private static final int PBKDF2_INDEX = 2; + + private HashingPasswordUtil() { + super(); + } + + /** + * 유효한 비밀번호인지 체크한다. + * + * @param password 평문 비밀번호 + * @param minLength 최소 길이 + * @return 유효한 경우 true, 그렇지 않다면 false + */ + public static boolean isValid(@NotNull String password, int minLength) { + if (password != null && minLength > 0 && password.length() >= minLength) { + boolean hasAlphabet = false, hasNumber = false; + + for (char c : password.toCharArray()) { + if (Character.isAlphabetic(c)) { + hasAlphabet = true; + } else if (Character.isDigit(c)) { + hasNumber = true; + } + + if (hasAlphabet && hasNumber) { + return true; + } + } + } + + return false; + } + + /** + * Returns a salted PBKDF2 hash of the password. + * + * @param password the password to hash + * @return a salted PBKDF2 hash of the password + */ + public static String createHash(String password) { + return createHash(password.toCharArray()); + } + + /** + * Returns a salted PBKDF2 hash of the password. + * + * @param password the password to hash + * @return a salted PBKDF2 hash of the password + */ + public static String createHash(char[] password) { + // Generate a random salt + SecureRandom random = new SecureRandom(); + byte[] salt = new byte[SALT_BYTE_SIZE]; + random.nextBytes(salt); + + // Hash the password + byte[] hash = pbkdf2(password, salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE); + // format iterations:salt:hash + return PBKDF2_ITERATIONS + ":" + toHex(salt) + ":" + toHex(hash); + } + + /** + * Validates a password using a hash. + * + * @param password the password to check + * @param correctHash the hash of the valid password + * @return true if the password is correct, false if not + */ + public static boolean validatePassword(String password, String correctHash) { + return validatePassword(password.toCharArray(), correctHash); + } + + /** + * Validates a password using a hash. + * + * @param password the password to check + * @param correctHash the hash of the valid password + * @return true if the password is correct, false if not + */ + public static boolean validatePassword(char[] password, String correctHash) { + // Decode the hash into its parameters + String[] params = correctHash.split(":"); + int iterations = Integer.parseInt(params[ITERATION_INDEX]); + byte[] salt = fromHex(params[SALT_INDEX]); + byte[] hash = fromHex(params[PBKDF2_INDEX]); + // Compute the hash of the provided password, using the same salt, + // iteration count, and hash length + byte[] testHash = pbkdf2(password, salt, iterations, hash.length); + // Compare the hashes in constant time. The password is correct if + // both hashes match. + return slowEquals(hash, testHash); + } + + /** + * Compares two byte arrays in length-constant time. This comparison method is used so that + * password hashes cannot be extracted from an on-line system using a timing attack and then + * attacked off-line. + * + * @param a the first byte array + * @param b the second byte array + * @return true if both byte arrays are the same, false if not + */ + private static boolean slowEquals(byte[] a, byte[] b) { + int diff = a.length ^ b.length; + for (int i = 0; i < a.length && i < b.length; i++) { + diff |= a[i] ^ b[i]; + } + return diff == 0; + } + + /** + * Computes the PBKDF2 hash of a password. + * + * @param password the password to hash. + * @param salt the salt + * @param iterations the iteration count (slowness factor) + * @param bytes the length of the hash to compute in bytes + * @return the PBDKF2 hash of the password + */ + private static byte[] pbkdf2(char[] password, byte[] salt, int iterations, int bytes) { + PBEKeySpec spec; + spec = new PBEKeySpec(password, salt, iterations, bytes * 8); + try { + SecretKeyFactory skf = SecretKeyFactory.getInstance(PBKDF2_ALGORITHM); + return skf.generateSecret(spec).getEncoded(); + } catch (InvalidKeySpecException e) { +// logger.debug("Key value is invalid"); + return null; + } catch (NoSuchAlgorithmException ne) { +// logger.debug("No such algorithm exception : " + PBKDF2_ALGORITHM); + return null; + } + } + + /** + * Converts a string of hexadecimal characters into a byte array. + * + * @param hex the hex string + * @return the hex string decoded into a byte array + */ + private static byte[] fromHex(String hex) { + byte[] binary = new byte[hex.length() / 2]; + for (int i = 0; i < binary.length; i++) { + binary[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16); + } + return binary; + } + + /** + * Converts a byte array into a hexadecimal string. + * + * @param array the byte array to convert + * @return a length*2 character string encoding the byte array + */ + private static String toHex(byte[] array) { + BigInteger bi = new BigInteger(1, array); + String hex = bi.toString(16); + int paddingLength = (array.length * 2) - hex.length(); + if (paddingLength > 0) { + return String.format("%0" + paddingLength + "d", 0) + hex; + } else { + return hex; + } + } + + public static void main(String[] args) { + + System.out.println(HashingPasswordUtil.createHash("123456789a")); + System.out.println(HashingPasswordUtil.createHash("qwer!2345")); + System.out.println(HashingPasswordUtil.createHash("dPtmdjqrmfpdlem")); + + String hash1 = HashingPasswordUtil.createHash("dPtmdjqrmfpdlem"); + String hash2 = HashingPasswordUtil.createHash("dPtmdjqrmfpdlem"); + String hash3 = HashingPasswordUtil.createHash("dPtmdjqrmfpdlem"); + String hash4 = HashingPasswordUtil.createHash("dPtmdjqrmfpdlem"); + + System.out.println(hash1); + System.out.println(hash2); + System.out.println(hash3); + System.out.println(hash4); + + if( HashingPasswordUtil.validatePassword("dPtmdjqrmfpdlem", "1000:2b9a77ba59bd6febe3a54ff201543d1a629c17bac9d7612d:0f17b581c2ff5f30fd17a8f9a9bc33e68408c74761eb60e0") ) { + System.out.println("Match Password"); + } else { + System.out.println("Not Match Password"); + } + + if( HashingPasswordUtil.validatePassword("1111", "1000:b15bfe3d0bad0d77f847d985776f36984fa265d63f2fd1a5:8a00ad2fad65e7ebaf5cb13282a855a9df01624364a5c28f") ) { + System.out.println("1111 Match Password"); + } else { + System.out.println("1111 Not Match Password"); + } + + System.out.println(HashingPasswordUtil.createHash("1111")); + } +} diff --git a/src/main/java/com/xit/study/FunctionalInterfaceExamples.java b/src/main/java/com/xit/study/FunctionalInterfaceExamples.java new file mode 100644 index 0000000..d94a407 --- /dev/null +++ b/src/main/java/com/xit/study/FunctionalInterfaceExamples.java @@ -0,0 +1,24 @@ +package com.xit.study; + +import java.util.function.Function; + +/** + * @author Lim, Jong Uk (minuk926) + * @since 2021-07-15 + */ +public class FunctionalInterfaceExamples { + + public static void main(String[] args) { + final Function toInt = s -> Integer.parseInt(s); + + final Integer apply = toInt.apply("100"); + System.out.println(apply); + + final Function identity = Function.identity(); + final Function identity2 = t -> t; + + + System.out.println(identity.apply(999)); + System.out.println(identity2.apply(999)); + } +} diff --git a/src/main/java/com/xit/study/ModernJava.java b/src/main/java/com/xit/study/ModernJava.java new file mode 100644 index 0000000..8c117cb --- /dev/null +++ b/src/main/java/com/xit/study/ModernJava.java @@ -0,0 +1,8 @@ +package com.xit.study; + +public class ModernJava { + + public static void main(String[] args) { + + } +} diff --git a/src/main/resources/config/application-dev.yml b/src/main/resources/config/application-dev.yml new file mode 100644 index 0000000..eefa2b8 --- /dev/null +++ b/src/main/resources/config/application-dev.yml @@ -0,0 +1,77 @@ +server: + port: 8090 + +spring: + config: + activate: + on-profile: dev + + sql: + init: + mode: always + platform: postgres + #schema-locations: classpath:/data/h2/schema.sql + #data-locations: classpath:/data/h2/data.sql + + # ================================================================================================================== + # database setting + # ================================================================================================================== + datasource: + driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://localhost:5432/xit_admin + username: admin + password: admin!@ + hikari: + driver-class-name: ${spring.datasource.driver-class-name} + jdbc-url: ${spring.datasource.url} + password: ${spring.datasource.password} + username: ${spring.datasource.username} + + # ================================================================================================================== + # JPA setting + # ================================================================================================================== + jpa: + database-platform: org.hibernate.dialect.PostgreSQLDialect + properties: + hibernate: + format_sql: true + use_sql_comments: false + hbm2ddl: + # create / create-drop / update / validate / none + auto: update + # hibernate sql log + #show-sql: false + +# h2: +# console: +# enabled: true +# path: /h2-console + + thymeleaf: + cache: false + +# ================================================================================================================== +# JPA logging lib setting +# ================================================================================================================== +decorator: + datasource: + p6spy: + enable-logging: true +logging: + level: + root: info + org: + hibernate: + # hibernate sql log 출력시 변수 바인딩 + #type: trace + springframework: + web: + #CommonsRequestLoggingFilter(RequestLoggingConfig) 를 이용한 로그출력을 위해 필수 debug + filter: error + +# ================================================================================================================== +# Spring-doc 활성 +# ================================================================================================================== +springdoc: + api-docs: + enabled: true diff --git a/src/main/resources/config/application-local.yml b/src/main/resources/config/application-local.yml new file mode 100644 index 0000000..b70e580 --- /dev/null +++ b/src/main/resources/config/application-local.yml @@ -0,0 +1,76 @@ +server: + port: 8090 + +spring: + config: + activate: + on-profile: local + sql: + init: + mode: always + platform: h2 + #schema-locations: classpath:/data/h2/schema.sql + #data-locations: classpath:/data/h2/data.sql + + # ================================================================================================================== + # database setting + # ================================================================================================================== + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:xitdb;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE + username: sa + password: '' + hikari: + driver-class-name: ${spring.datasource.driver-class-name} + jdbc-url: ${spring.datasource.url} + username: ${spring.datasource.username} + password: ${spring.datasource.password} + + # ================================================================================================================== + # JPA setting + # ================================================================================================================== + jpa: + database-platform: org.hibernate.dialect.H2Dialect + properties: + hibernate: + format_sql: true + use_sql_comments: false + hbm2ddl: + # create / create-drop / update / validate / none + auto: create-drop + # hibernate sql log + #show-sql: false + h2: + console: + enabled: true + path: /h2-console + + thymeleaf: + cache: false + + +# ================================================================================================================== +# JPA logging lib setting +# ================================================================================================================== +decorator: + datasource: + p6spy: + enable-logging: true +logging: + level: + root: debug + org: + hibernate: + # hibernate sql log 출력시 변수 바인딩 + #type: trace + springframework: + web: + #CommonsRequestLoggingFilter(RequestLoggingConfig) 를 이용한 로그출력을 위해 필수 debug + filter: error + +# ================================================================================================================== +# Spring-doc 활성 +# ================================================================================================================== +springdoc: + api-docs: + enabled: true diff --git a/src/main/resources/config/application-oauth.yml b/src/main/resources/config/application-oauth.yml new file mode 100644 index 0000000..587b273 --- /dev/null +++ b/src/main/resources/config/application-oauth.yml @@ -0,0 +1,93 @@ +spring: + security: + oauth2: + client: + registration: + google: + client-id: 259821403087-talgepagvnahurp0bfnk55pujrdaec7q + client-secret: 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 + scope: + - profile + - email + facebook: + clientId: 652436969254286 + clientSecret: 822069e9cd00654574d16a88cf3a4be9 + scope: + - email + - public_profile + kakao: + clientId: 1d0012ecc1e9d454a69b3a850c546879 + clientSecret: bfctm9QldllT81f2UK7L0Cc5QDcDfsYb + clientAuthenticationMethod: post + authorizationGrantType: authorization_code + redirectUri: http://localhost:8090/oauth2/code/kakao + scope: + - profile_nickname + - profile_image + - account_email + clientName: Kakao + naver: + clientId: '{네이버 client-id}' + clientSecret: '{네이버 client-secret}' + clientAuthenticationMethod: post + authorizationGrantType: authorization_code + redirectUri: http://localhost:8090/oauth2/code/naver + scope: + - nickname + - email + - profile_image + clientName: Naver + provider: + kakao: + authorizationUri: https://kauth.kakao.com/oauth/authorize + tokenUri: https://kauth.kakao.com/oauth/token + userInfoUri: https://kapi.kakao.com/v2/user/me + userNameAttribute: id + naver: + authorizationUri: https://nid.naver.com/oauth2.0/authorize + tokenUri: https://nid.naver.com/oauth2.0/token + userInfoUri: https://openapi.naver.com/v1/nid/me + userNameAttribute: response + +# Spring Security cors 설정 :: CorsConfiguration 설정 값 +cors: + allowed-origins: 'http://localhost:3000' + allowed-methods: GET,POST,PUT,DELETE,OPTIONS + allowed-headers: '*' + max-age: 3600 + + +# HS512 알고리즘을 사용할 것이기 때문에 512bit, 즉 64byte 이상의 secret key를 사용해야 한다. +# Base64.getEncoder().withoutPadding().encodeToString(orgString.getBytes()) +# new String(Base64.getDecoder().decode(encodedString)) +# com.xit.biz.auth.jwt.TokenProvider.main 참조 +# +# spring-boot-security-jwt-xit-core-javaframework-java-token-key +# --> c3ByaW5nLWJvb3Qtc2VjdXJpdHktand0LWlucGl4LWNvcmUtamF2YWZyYW1ld29yay1qYXZhLXRva2VuLWtleQ + +# 사용자 ID 정보를 어디에 저장할 것인지 설정 +# JWT 토큰을 사용하고 SessionCreationPolicy.STATELESS인 경우는 SecurityContext 사용불가 +# --> session에 저장하는 방식은 가능 +# security | session | header +# jwt secret key 설정 +jwt.secret: 8sknjlO3NPTBqo319DHLNqsQAfRJEdKsETOds +# 토큰 재발급시 토큰(refresh) 전달 방식 : COOKIE | HEADER | DTO +jwt.refresh.save.type: COOKIE +#jwt.secret: c3ByaW5nLWJvb3Qtc2VjdXJpdHktand0LWlucGl4LWNvcmUtamF2YWZyYW1ld29yay1qYXZhLXRva2VuLWtleQ + + +xit: + auth: + typ: JWT + grant: Bearer + alg: HS256 + #alg: HS512 + issure: xit + audience: xit-api + # minute + tokenExpiry: 1000 + # day + refreshTokenExpiry: 7 + oauth2: + authorizedRedirectUris: + - http://localhost:3000/oauth/redirect \ No newline at end of file diff --git a/src/main/resources/config/application-test.yml b/src/main/resources/config/application-test.yml new file mode 100644 index 0000000..af3ce35 --- /dev/null +++ b/src/main/resources/config/application-test.yml @@ -0,0 +1,54 @@ +server: + port: 18080 + +spring: + config: + activate: + on-profile: test + sql: + init: + mode: always + platform: h2 + + + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:xitdb;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE + username: sa + password: '' + hikari: + driver-class-name: ${spring.datasource.driver-class-name} + jdbc-url: ${spring.datasource.url} + username: ${spring.datasource.username} + password: ${spring.datasource.password} + +decorator: + datasource: + p6spy: + enable-logging: true +logging: + level: + org: + hibernate: + type: trace + springframework: + web: + filter: error + root: info + + jpa: + database-platform: org.hibernate.dialect.H2Dialect + properties: + hibernate: + format_sql: true + use_sql_comments: false + hbm2ddl: + auto: create-drop + show-sql: false + + thymeleaf: + cache: false + +springdoc: + api-docs: + enabled: false diff --git a/src/main/resources/config/application.yml b/src/main/resources/config/application.yml new file mode 100644 index 0000000..c2c8691 --- /dev/null +++ b/src/main/resources/config/application.yml @@ -0,0 +1,212 @@ +spring: + application: + name: xit-framework + config: + import: classpath:config/conf.yml + profiles: + active: local + include: oauth + # bean name 중복 허용 - junit 사용시 true?? + main: + allow-bean-definition-overriding: false + # WebSecurity debug + security: + debug: true + + # ================================================================================================================== + # spring message + # ================================================================================================================== + messages: + basename: classpath:/messages/i18n/ + encoding: UTF-8 + mvc: + # DispatcherServlet의 log 활성화 + log-request-details: true + log-resolved-exception: true + # JSP 등 템플릿 엔진 사용시 설정 +# static-path-pattern: /static/** +# view: +# prefix: /WEB-INF/jsp/ +# suffix: .jsp + + # ================================================================================================================== + # page setting + # ================================================================================================================== + web: + resources: + cache: + period: 0 + static-locations: classpath:/static/ + servlet: + multipart: + max-file-size: 1024MB + max-request-size: 1024MB + data: + rest: + default-page-size: 200 + max-page-size: 1000 + web: + pageable: + # 1 페이지 부터 시작 : default 0 + one-indexed-parameters: true + # 기본 페이지 size + default-page-size: 10 + # 페이지당 최대 size + max-page-size: 100 + page-parameter: page + size-parameter: size + sort: + sort-parameter: sort + + # ================================================================================================================== + # database setting + # ================================================================================================================== + sql: + init: + continue-on-error: false + # profiles 별로 설정 : + #mode: always #embeded / always / never + #platform: postgres + # hikari datasource + datasource: + type: com.zaxxer.hikari.HikariDataSource + hikari: + pool-name: jpa-hikari-pool + auto-commit: false + # 인프라의 적용된 connection time limit보다 작아야함 + max-lifetime: 1800000 + maximum-pool-size: 50 + minimum-idle: 50 + transaction-isolation: TRANSACTION_READ_UNCOMMITTED + data-source-properties: + rewriteBatchedStatements: true + + # ================================================================================================================== + # JPA setting + # ================================================================================================================== + jpa: + # 템플릿 view 화면의 렌더링이 끝날 때 까지 Lazy fetch 가 가능하도록 해주는 속성 + open-in-view: false + show-sql: true + properties: + order_inserts: true + order_updates: true + default_batch_fetch_size: ${chunkSize:100} + current_session_context_class: org.springframework.orm.hibernate5.SpringSessionContext + implicit_naming_strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + physical_naming_strategy: com.xit.core.config.support.LowercaseSnakePhysicalNamingStrategy + hibernate: + hbm2ddl: + import_files_sql_extractor: org.hibernate.tool.hbm2ddl.MultipleLinesSqlCommandExtractor + jdbc: + batch_size: 20 + lob: + # postgres 사용시 createLob() 미구현 경고 삭제 + non_contextual_creation: true + # Jdbc 환경구성을 하는 과정에서 Default Metadata를 사용할 지 여부 + temp: + use_jdbc_metadata_defaults: false + + # jackson time setting + jackson: + date-format: yyyy-MM-dd HH:mm:ss + serialization: + fail-on-empty-beans: false + time-zone: Asia/Seoul + + # ================================================================================================================== + # spring-doc setting + # ================================================================================================================== + #use-management-port: true +springdoc: + #show-actuator: true + #show-login-endpoint: true + writer-with-default-pretty-printer: true + model-and-view-allowed: true + api-docs: + path: /api-docs + swagger-ui: + path: swagger-ui.html + display-request-duration: true + operations-sorter: method + tags-sorter: alpha + disable-swagger-default-url: true + display-query-params-without-oauth2: true + csrf: + enabled: false + + # ================================================================================================================== + # view template engine setting + # ================================================================================================================== + #JSP와 같이 사용시 뷰 구분을 위해 컨트롤러가 뷰 이름을 반환시 thymeleaf/ 로 시작하면 타임리프로 처리하도록 view-names 지정 + thymeleaf: + check-template: true + # templates 디렉토리에 파일이 있는지 없는지 체크, 없으면 에러 + check-template-location: true + encoding: UTF-8 + mode: HTML + prefix: classpath:templates/ + suffix: .html + # profiles 별로 분리 + #cache: false + view-names: thymeleaf/* + + # devtools + devtools: + livereload: + enabled: true + +# ================================================================================================================== +# JPA logging lib setting +# ================================================================================================================== +decorator: + datasource: + p6spy: + # profiles 별로 정의 + #enable-logging: true + multiline: true + logging: slf4j + custom-appender-class: com.p6spy.engine.spy.appender.Slf4JLogger + log-format: com.xit.core.config.support.P6spySqlFormatConfiguration + tracing: + include-parameter-values: true + +# ================================================================================================================== +# xit-framework logging setting +# ================================================================================================================== +logging: + #config: classpath:logback-spring.xml + charset: + console: UTF-8 + file: UTF-8 + level: + root: warn + exception-conversion-word: '%wEx' + pattern: + console: '%d{yyyy-MM-dd HH:mm:ss.SSS} %clr(${LOG_LEVEL_PATTERN:-%5p}){green} %clr([%18thread]){magenta} %clr(%-40.40logger{39}%line){cyan} %clr(: %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}){faint}' + slack: + webhook-uri: https://hooks.slack.com/services/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 + +#log4jdbc 설정 +#log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator +#log4jdbc.dum.sql.maxlinelength=0 + +server: + # profiles 별로 정의 + #port: 8080 + servlet: + context-path: / + error: + # Response 에 Exception 을 표시할지 여부 + include-exception: true + # Response 에 Exception Message 를 표시할지 : ALWAYS, NEVER, ON_PARAM + include-message: ALWAYS + # Response 에 Stack Trace 를 표시할지 (never | always | on_param) on_trace_params 은 deprecated + include-stacktrace: ALWAYS + # 브라우저 요청에 대해 서버 오류시 기본으로 노출할 페이지를 사용할지 여부 + whitelabel: + enabled: true + tomcat: + basedir: . + + diff --git a/src/main/resources/config/conf.yml b/src/main/resources/config/conf.yml new file mode 100644 index 0000000..69ab8f3 --- /dev/null +++ b/src/main/resources/config/conf.yml @@ -0,0 +1,24 @@ +xit: + api: + url: 'http://localhost:${server.port}' + version: '@project.version@' + # Authentification 저장 위치 : header / cookie / token + auth: + save: + type: header + # locale : ko / en + locale: ko + +# api response logging 여부 +api: + reponse: + logging: true + +file: + cmm: + upload: + allow: + ext: + max: + size: 10000000 + path: d:/data/file/upload diff --git a/src/main/resources/config/mybatis-config.xml b/src/main/resources/config/mybatis-config.xml new file mode 100644 index 0000000..d562cbf --- /dev/null +++ b/src/main/resources/config/mybatis-config.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/data/docker.txt b/src/main/resources/data/docker.txt new file mode 100644 index 0000000..e24b907 --- /dev/null +++ b/src/main/resources/data/docker.txt @@ -0,0 +1,20 @@ +# docker pull postgres:latest + +# docker run -p 5432:5432 -e POSTGRES_PASSWORD=pass -e POSTGRES_USER=xit -e POSTGRES_DB=springdata --name postgres_boot -d postgres + +# docker exec -i -t postgres_boot bash + +# su - postgres + +docker images +docker ps -a +docker rm [컨테이너id] +docker rmi [이미지id] + +docker stop|start|restart [도커명] + + + + + + diff --git a/src/main/resources/data/ecr-ecs-deploy.yml b/src/main/resources/data/ecr-ecs-deploy.yml new file mode 100644 index 0000000..f8f857d --- /dev/null +++ b/src/main/resources/data/ecr-ecs-deploy.yml @@ -0,0 +1,109 @@ +name: Deploy to Amazon ECS(ECR -> ECS docker image) + +on: +# push: +# branches: +# - master +# pull_request: +# branches: +# - master + workflow_dispatch: + inputs: + logLevel: + description: 'Log level' + required: true + default: 'debug' + tags: + description: 'Run action tags' + required: true + default: 'Run action tags' + +#env: +# ECR_REGISTRY: xit-framework-bo +# # ECR리포지토리 이름 +# ECR_REPOSITORY: ${{ secrets.REPO_NAME }} +# IMAGE_TAG: ${{ github.sha }} + +jobs: + build: + name: CI with Gradle + runs-on: ubuntu-latest +# defaults: +# run: bash +# working-directory: BE + + steps: + - name: Check out Github-Action + uses: actions/checkout@v2 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ap-northeast-2 + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + + - name: Build, tag, and push the image to Amazon ECR + id: build-image + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + # ECR리포지토리 이름 + ECR_REPOSITORY: ${{ secrets.REPO_NAME }} + IMAGE_TAG: ${{ github.sha }} + run: | + # Build a docker container + docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . + # tagging + docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest + # Pushing image to ECR + docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest + + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ap-northeast-2 + run: docker pull $ECR_REGISTRY.dkr.ecr.ap-northeast-2.amazonaws.com/$ECR_REGISTRY/$ECR_REPOSITORY:latest +# docker pull 000000000000.dkr.ecr.ap-northeast-2.amazonaws.com/test:latest + +# deploy: +# needs: build +# name: Deploy +# runs-on: +# - self-hosted +# - label-development +# steps: +# - name: Login to ghcr +# uses: docker/login-action@v1 +# with: +# registry: ghcr.io +# username: ${{ github.actor }} +# password: hj719310! +# - name: Docker run +# run: | +# docker ps -q --filter "name=$ECR_REGISTRY/$ECR_REPOSITORY" | grep -q . && docker stop $ECR_REGISTRY/$ECR_REPOSITORY && docker rm -fv $ECR_REGISTRY/$ECR_REPOSITORY +# docker run -d -p 8080:8080 --name cicd --restart always ${{ env.DOCKER_IMAGE }}:${{ env.VERSION }} + + +# - name: Fill in the new image ID in the Amazon ECS task definition +# id: task-def +# uses: aws-actions/amazon-ecs-render-task-definition@v1 +# with: +# task-definition: task-definition.json +# container-name: 컨테이너이름 +# image: ${{ steps.build-image.outputs.image }} +# - name: Deploy Amazon ECS task definition +# uses: aws-actions/amazon-ecs-deploy-task-definition@v1 +# with: +# task-definition: ${{ steps.task-def.outputs.task-definition }} +# # 서비스이름 +# service: 서비스이름 +# # 클러스터이름 +# cluster: ecs-ec2-seoul +# wait-for-service-stability: true \ No newline at end of file diff --git a/src/main/resources/data/gradle.txt b/src/main/resources/data/gradle.txt new file mode 100644 index 0000000..39953e8 --- /dev/null +++ b/src/main/resources/data/gradle.txt @@ -0,0 +1,25 @@ +# buildEnvironment Task로 build 의존성을 확인 +gradlew buildEnvironment + + +# gradle.properties에 정의할 내용 + +org.gradle.java.home=d:\\tools\\java\\jdk8144 + +# Gradle이 데몬으로 떠서 실행되기 때문에 초기 로딩 시간이 줄어든다 +# 3시간동안 gradle 작업이 없으면 종료 +org.gradle.daemon=true +# memory allotted to JVM +org.gradle.jvmargs=-Xmx5120m +# configure on demand +org.gradle.configureondemand=true +# parallel builds +org.gradle.parallel=true +# build Cache +android.enableBuildCache=true +# gradle caching +org.gradle.caching=true + +# build.gradle에서 사용하는 인증정보 +mavenUser=admin +mavenPassword=admin123 \ No newline at end of file diff --git a/src/main/resources/data/h2/data.sql b/src/main/resources/data/h2/data.sql new file mode 100644 index 0000000..feae80f --- /dev/null +++ b/src/main/resources/data/h2/data.sql @@ -0,0 +1,21 @@ +INSERT + INTO `user` (`id`, `email`, `name`, `tel`) +VALUES (1, 'kim@naver.com', 'Kim', '010-0000-0001'), + (2, 'janny@naver.com', 'Janny', '010-0000-0002'), + (3, 'billie@naver.com', 'Billie Eilish', '010-0000-0003'), + (4, 'taylor@naver.com', 'Taylor Swift', '010-0000-0004'), + (5, 'jack@naver.com', 'Jack', '010-0000-0005'), + (6, 'hong@hanmail.net', '홍길동', '010-0000-0006'), + (7, 'kang@naver.com', '강감찬', '010-0000-1111'); +INSERT + INTO `product` (`no`, `name`) +VALUES ('10001', 'TV'), + ('10002', 'Notebook'), + ('10003', 'Phone'), + ('10004', 'Monitor'), + ('10005', 'Washer'), + ('10006', 'Car'), + ('10007', 'Table'), + ('10008', 'Beer'), + ('10009', 'Bike'), + ('10010', 'Boat'); \ No newline at end of file diff --git a/src/main/resources/data/h2/schema.sql b/src/main/resources/data/h2/schema.sql new file mode 100644 index 0000000..6f34d69 --- /dev/null +++ b/src/main/resources/data/h2/schema.sql @@ -0,0 +1,69 @@ +-- DROP TABLE IF EXISTS user; +-- CREATE TABLE user( +-- id INT PRIMARY KEY, +-- email VARCHAR(30), +-- name VARCHAR(30), +-- tel VARCHAR(30) +-- ); +-- DROP TABLE IF EXISTS product; +-- CREATE TABLE product ( +-- no VARCHAR(5), +-- name VARCHAR(50) +-- ); + +drop table if exists tb_cmm_board CASCADE; +drop table if exists tb_cmm_role CASCADE; +drop table if exists tb_cmm_user CASCADE; +drop table if exists tb_cmm_user_role CASCADE; + +create table tb_cmm_board +( + board_id bigint generated by default as identity, + content TEXT not null, + ins_dt_tm timestamp, + ins_user_id varchar(255), + title varchar(500) not null, + upd_dt_tm timestamp, + upd_user_id varchar(255), + primary key (board_id) +); + +create table tb_cmm_role +( + role_id bigint generated by default as identity, + role_name varchar(20) not null, + role_remark varchar(100), + primary key (role_id) +); + +create table tb_cmm_user +( + cmm_user_id bigint generated by default as identity, + ins_dt_tm timestamp, + ins_user_id varchar(255), + password varchar(256) not null, + upd_dt_tm timestamp, + upd_user_id varchar(255), + user_id varchar(20) not null, + user_name varchar(100) not null, + primary key (cmm_user_id) +); + +create table tb_cmm_user_role +( + role_id bigint not null, + cmm_user_id bigint not null, + primary key (role_id, cmm_user_id) +); +create index idx_tb_cmm_board_01 on tb_cmm_board (title); +create index idx_tb_cmm_user_01 on tb_cmm_user (user_id, user_name); + +alter table tb_cmm_user_role + add constraint FK_role_id + foreign key (role_id) + references tb_cmm_role; + +alter table tb_cmm_user_role + add constraint FK_cmm_user_id + foreign key (cmm_user_id) + references tb_cmm_user; diff --git a/src/main/resources/data/hikari_conf.txt b/src/main/resources/data/hikari_conf.txt new file mode 100644 index 0000000..21670c4 --- /dev/null +++ b/src/main/resources/data/hikari_conf.txt @@ -0,0 +1,71 @@ + hikari: + # HikariCP는 jdbcUrl을 참조하여 자동으로 driver를 설정하려고 시도함 + # 몇몇의 오래된 driver들은 driver-class-name을 명시화 해야함 + # 어떤 에러 메시지가 명백하게 표시 되지않는다면 생략해도 됨 + # default : none + #driver-class-name: none + + # 클라이언트가 pool에 connection을 요청하는데 기다리는 최대시간을 설정 : 250ms 이상 + # 설정한 시간을 초과하면 SQLException이 발생 + # default : 30000 + connection-timeout: 30000 + + # 유휴 및 사용중인 connection을 포함하여 풀에 보관가능한 최대 커넥션 개수 + # 사용할 수 있는 커넥션이 없다면 connectionTimeout 시간 만큼 대기하고 시간을 초과하면 SQLException이 발생 + # default : 10 + maximum-pool-size: 10 + + # connection pool에서 유지가능한 최소 커넥션 개수를 설정 + # HikariCP에서는 최고의 performance를 위해 maximum-pool-size와 minimum-idle값을 같은 값으로 지정해서 connection Pool의 크기를 fix하는 것을 강력하게 권장 + # default : maximum-pool-size + minimum-idle: 10 + + # connection의 최대 유지 시간 + # 강력하게 설정해야하는 설정 값으로 데이터베이스나 인프라의 적용된 connection time limit보다 작아야함 + # 0으로 설정하면 infinite lifetime이 적용됨 + # connection의 maxLifeTime 지났을 때, 사용중인 connection은 바로 폐기되지않고 작업이 완료되면 폐기 + # but, 유휴 커넥션은 바로 폐기 + max-lifetime: 1800000 + + # logging이나 JMX management console에 표시되는 이름 + pool-name: HikariCP + + # pool에서 커넥션을 초기화할 때 성공적으로 수행할 수 없을 경우 빠르게 실패하도록 해준다 + # default: 1 + initialization-fail-timeout: 1 + + # pool에서 얻은 connection이 기본적으로 readOnly인지 지정 + # 데이터베이스가 readOnly 속성을 지원할 경우에만 사용 가능 + # default : false + read-only: false + + # ========================================================================================================================= + # 데이터베이스 연결이 여전히 활성화되어있는지 확인하기 위해 pool에서 connection을 제공하기 전에 실행되는 쿼리 + # JDBC4를 지원안하는 드라이버를 위한 옵션임(Connection.isValid() API) - JDBC4 드라이버를 지원한다면 이 옵션은 설정하지 않는 것을 추천 + # JDBC4드라이버를 지원하지않는 환경에서 이 값을 설정하지 않는다면 error레벨 로그를 뱉어냄 + #connection-test-query: SELECT 1 + + # connection pool에서 유휴 상태로 유지시킬 최대 시간 설정 : 최소 10000ms + # minimumIdle이 maximumPoolSize보다 작은 경우에만 사용 + # pool에 있는 connection이 minimumIdle에 도달할 경우 이후에 반환되는 connection에 대해서 바로 반환하지 않고 idleTimeout 만큼 유휴 상태로 있다가 폐기 + # default : 600000 + #idle-timeout: 600000 + + # connection이 종료되거나 pool에 반환될 때, connection에 속해있는 transaction을 commit 할지를 결정 + # default : true + #auto-commit: false + + # Connection.TRANSACTION_NONE : transaction을 지원하지 않는다 + # Connection.TRANSACTION_READ_UNCOMMITTED : transaction이 끝나지 않았을 때, 다른 transaction에서 값을 읽는 경우 commit되지 않은 값(dirty value)를 읽는다 + # Connection.TRANSACTION_READ_COMMITTED : transaction이 끝나지 않았을 때, 다른 transaction에서 값을 읽는 경우 변경되지 않은 값을 읽는다 + # Connection.TRANSACTION_REPEATABLE_READ : 같은 transaction내에서 값을 또다시 읽을 때, 변경되기 전의 값을 읽는다 TRANSACTION_READ_UNCOMMITTED 와 같이 사용될 수 없다 + # Connection.TRANSACTION_SERIALIZABLE: dirty read를 지원하고, non-repeatable read를 지원한다 + # 기본값을 각 Driver vendor의 JDBCDriver에서 지원하는 Transaction Isoluation을 따라간다. (none으로 설정시) + # default : none + #transaction-isolation: none + + # 커넥션 누수 로그메시지가 나오기 전에 커넥션을 검사하여 pool에서 커넥션을 내보낼 수 있는 시간 + # 0으로 설정하면 leak detection을 이용하지않음 + # 최소값 2000ms (default: 0) + #leak-detection-threshold: 0 + # ============================================================================================================================= diff --git a/src/main/resources/data/inpix-core-requests.http b/src/main/resources/data/inpix-core-requests.http new file mode 100644 index 0000000..b92e348 --- /dev/null +++ b/src/main/resources/data/inpix-core-requests.http @@ -0,0 +1,55 @@ +### +POST http://localhost:8080/api/cmm/board +Content-Type: application/json;charset=UTF-8 + +{"boardId": 1, "title": "제목1", "content": "내용1", "insUserId": "작성자1", "updUserId": null, "insDtTm": null, "updDtTm": null} +//{"boardId": 2, "title": "제목2", "content": "내용2", "insUserId": "작성자2", "updUserId": null, "insDtTm": null, "updDtTm": null} +//{"boardId": 3, "title": "제목3", "content": "내용3", "insUserId": "작성자3", "updUserId": null, "insDtTm": null, "updDtTm": null} +//{"boardId": 4, "title": "제목4", "content": "내용4", "insUserId": "작성자4", "updUserId": null, "insDtTm": null, "updDtTm": null} +//{"boardId": 5, "title": "제목5", "content": "내용5", "insUserId": "작성자5", "updUserId": null, "insDtTm": null, "updDtTm": null} +//{"boardId": 6, "title": "제목6", "content": "내용6", "insUserId": "작성자6", "updUserId": null, "insDtTm": null, "updDtTm": null} +//{"boardId": 7, "title": "제목7", "content": "내용7", "insUserId": "작성자7", "updUserId": null, "insDtTm": null, "updDtTm": null} + +### +POST http://localhost:8080/api/cmm/board/api +Content-Type: application/json;charset=UTF-8 + +//{"boardId": 1, "title": "제목1", "content": "내용1", "insUserId": "작성자1", "updUserId": null, "insDtTm": null, "updDtTm": null} +//{"boardId": 2, "title": "제목2", "content": "내용2", "insUserId": "작성자2", "updUserId": null, "insDtTm": null, "updDtTm": null} +{"boardId": 3, "title": "제목3", "content": "내용3", "insUserId": "작성자3", "updUserId": null, "insDtTm": null, "updDtTm": null} +//{"boardId": 4, "title": "제목4", "content": "내용4", "insUserId": "작성자4", "updUserId": null, "insDtTm": null, "updDtTm": null} +//{"boardId": 5, "title": "제목5", "content": "내용5", "insUserId": "작성자5", "updUserId": null, "insDtTm": null, "updDtTm": null} +//{"boardId": 6, "title": "제목6", "content": "내용6", "insUserId": "작성자6", "updUserId": null, "insDtTm": null, "updDtTm": null} +//{"boardId": 7, "title": "제목7", "content": "내용7", "insUserId": "작성자7", "updUserId": null, "insDtTm": null, "updDtTm": null} + + +### +PUT http://localhost:8080/sample/board +Content-Type: application/json;charset=UTF-8 + +{"boardId": 1, "title": "제목1-1", "content": "내용1-1", "insUserId": "작성자1-1", "updUserId": null, "insDtTm": null, "updDtTm": null} + + +### +GET http://localhost:8080/adm/board + +### +GET http://localhost:8080/sample/board/api + +### +GET http://localhost:8080/adm/board/page?page=0&size=2 +//Content-Type: application/json;charset=UTF-8 + + +### +GET http://localhost:8080/adm/board/api/page?page=0&size=2&sort=boardId,desc&sort=title,asc + + +### +GET http://localhost:8080/sample/board/2 +Content-Type: application/json;charset=UTF-8 + + +### +DELETE http://localhost:8080/sample/board/1 +Content-Type: application/json;charset=UTF-8 \ No newline at end of file diff --git a/src/main/resources/data/inpixdb.mv.db b/src/main/resources/data/inpixdb.mv.db new file mode 100644 index 0000000..7357211 Binary files /dev/null and b/src/main/resources/data/inpixdb.mv.db differ diff --git a/src/main/resources/data/jpa_core.txt b/src/main/resources/data/jpa_core.txt new file mode 100644 index 0000000..fed5433 --- /dev/null +++ b/src/main/resources/data/jpa_core.txt @@ -0,0 +1,105 @@ +@RepositoryDefinition(domainClass = 에티티.class, idClass = Long.class) +public interface CommonRepository{ + +} + +@NoRepositoryBean +public interface MyRepository extends Repository{ + E save(E entity); + List findAll(); +} + + + +@Size(max=5, message="상품코드는 5자여야 합니다.") +@Pattern(regexp="^[0-9]{5}$", message="상품코드는 5자리 숫자여야 합니다.") +protected String goodCd; + +@NotNull(message="상품명은 필수 입력값입니다.") +@Size(max=100, message="상품명은 50자 이내여야 합니다.") +protected String goodNm; + +========================================================================================= +@Query("SELECT p FROM #{#entityName} p where p.title = :title") +List findByTitle(@Param("title") String title, Sort sort); + +@Query("SELECT p FROM #{#entityName} p where p.id = :id and p.title = :title") +List findByTitle2(@Param("id") Long id, @Param("title") String title); + +@Modifying(clearAutomatically = true) +@Query("update #{#entityName} p set p.title = :title where p.id = :id") +int updateTitle(@Param("id") Long id, @Param("title") String title); + +postRepository.findByTitle("jpa", Sort.by("title")); +//postRepository.findByTitle("jpa", Sort.by("LENGTH(title)")); +postRepository.findByTitle("jpa", JpaSort.unsafe("LENGTH(title)")); +postRepository.findByTitle2(1l,"jpa"); +=============================================================================================== + +===== EntityGraph ========================================================================== +@NamedEntityGraph( + name = "Comment.post", + attributeNodes = @NamedAttributeNode("post") +) +@Entity +@Getter +@Setter +public class Comment { + + +@EntityGraph(value = "Comment.post", type = EntityGraph.EntityGraphType.LOAD) +Optional queryById(Long id); +---------------------------------------------------------------------------------- + +@EntityGraph(attributePaths = {"post"}) +Optional queryById(Long id); +================================================================================================ + +===== Projection ========================================================================== + List findByPost_Id(Long id, Class type); + +commentRepository.findByPost_Id(savedPost.getId(), CommentSummary.class) + +public interface CommentSummary { + String getComment(); + int getUp(); + int getDown(); + +// @Value("#{target.up + ' ' + target.down}") +// String getVotes(); + + default String getVotes(){ + return getUp() + " " + getDown(); + } +} +============================================================================================== + +==== Query by Example =============================================================== +public Page getUsers(SearchForm searchForm, Character useYn, Pageable pageable) { + try { + User example = User.builder() //user 객체 생성 + .useYn(useYn=='Y'?useYn:null) + .id(searchForm.getId()) + .name(searchForm.getName()) + .serialNo(searchForm.getSerialNo()) + .phone(searchForm.getPhone()) + .organization(searchForm.getOrganization()) + .grade(searchForm.getGrade()) + .build(); + ExampleMatcher matcher = ExampleMatcher.matchingAll() + .withMatcher("id",contains().ignoreCase()) + .withMatcher("name", contains().ignoreCase()) + .withMatcher("serialNo", contains().ignoreCase()) + .withMatcher("phone", contains().ignoreCase()) + .withMatcher("organization", contains().ignoreCase()) + .withMatcher("grade", contains().ignoreCase()); + return userRepository.findAll(Example.of(example, matcher), pageable); + }catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "BAD REQUEST"); + } + } +===================================================================== + +querydsl BooleanExpression +BooleanExpression be = user.username.contains(text); +if(be) be.and(user.username.eq("Hi") \ No newline at end of file diff --git a/src/main/resources/data/junit.txt b/src/main/resources/data/junit.txt new file mode 100644 index 0000000..febadd1 --- /dev/null +++ b/src/main/resources/data/junit.txt @@ -0,0 +1,6 @@ +@ExtendWith(SpringExtension.class) //Junit4의 Runwith과 같은 기능을 하는 Junit5 어노테이션 +@SpringBootTest(classes = PmcwebApplication.class) // Junit5 기준 Application Context사용할 때 사용 +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) // Order를 붙일 때 사용 +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) // 진짜 데이터베이스에 테스트 + +출처: https://hirlawldo.tistory.com/39 [도비의 블로그] \ No newline at end of file diff --git a/src/main/resources/data/jwt.txt b/src/main/resources/data/jwt.txt new file mode 100644 index 0000000..0f13c6e --- /dev/null +++ b/src/main/resources/data/jwt.txt @@ -0,0 +1,55 @@ +TOKEN 구성 +Header : alg: Signature 를 해싱하기 위한 알고리즘 정보를 갖고 있음 + typ: 토큰의 타입을 나타내는데 없어도 됨. 보통 JWT 를 사용 +Payload : 서버와 클라이언트가 주고받는, 시스템에서 실제로 사용될 정보에 대한 내용을 담고 있음 + JWT 가 기본적으로 갖고 있는 키워드가 존재 + 추가 가능 + iss: 토큰 발급자 + sub: 토큰 제목 + aud: 토큰 대상 + exp: 토큰의 만료시간 + nbf: Not Before + iat: 토큰이 발급된 시간 + jti: JWT의 고유 식별자 +Signature : 서버에서 토큰이 유효한지 검증하기 위한 문자열 + Header + Payload + Secret Key 로 값을 생성하므로 데이터 변조 여부를 판단 가능 + Secret Key 는 노출되지 않도록 서버에서 잘 관리 필요 + +절차 +1. --> id/pwd 로그인 시도 +2. <-- access token + refresh token 발급 +3. --> API 요청시 access token을 헤더에 담아 요청 +4. <-- API 응답 또는 Access token 만료 응답 +5. --> 재발급 요청(Request Body에 Access token + refresh token) +6. <-- 토큰 검증후 새로운 Access Token + Refresh Token 발급 + +해당 모듈 +JWT 관련 : TokenProvider: 유저 정보로 JWT 토큰을 생성 / 검증 / 토큰을 바탕으로 유저 정보 GET + JwtFilter: Spring Request 앞단에 붙일 Custom Filter +Spring Security 관련 : JwtSecurityConfig: JWT Filter 등록 + JwtAccessDeniedHandler: 접근 권한 없을 때 403 에러 + JwtAuthenticationEntryPoint: 인증 정보 없을 때 401 에러 + SecurityConfig: 스프링 시큐리티에 필요한 설정 + SecurityUtil: SecurityContext 에서 전역으로 유저 정보를 제공하는 유틸 클래스 + +Spring security 비밀번호 검증 +(Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);) + +1. AuthService 에서 AuthenticationManagerBuilder 주입 받음 +2. AuthenticationManagerBuilder 에서 AuthenticationManager 를 구현한 ProviderManager 생성 +3. ProviderManager 는 AbstractUserDetailsAuthenticationProvider 의 자식 클래스인 DaoAuthenticationProvider 를 주입받아서 호출 +4. DaoAuthenticationProvider 의 authenticate 에서는 retrieveUser 로 DB 에 있는 사용자 정보를 가져오고 additionalAuthenticationChecks 로 비밀번호 비교 +5. retrieveUser 내부에서 UserDetailsService 인터페이스를 직접 구현한 CustomUserDetailsService 클래스의 오버라이드 메소드인 loadUserByUsername 가 호출됨 + +# Request +POST http://localhost:8080/api/auth/signup +Content-Type: application/json +{ + "email": "test@test.net", + "password": "1q2w3e4r" +} + +# Response +{ + "email": "test@test.net" +} diff --git a/src/main/resources/data/maven_setting.zip b/src/main/resources/data/maven_setting.zip new file mode 100644 index 0000000..10fe923 Binary files /dev/null and b/src/main/resources/data/maven_setting.zip differ diff --git a/src/main/resources/data/persistence.xml b/src/main/resources/data/persistence.xml new file mode 100644 index 0000000..c3558bf --- /dev/null +++ b/src/main/resources/data/persistence.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/data/s3-ec2-deploy.yml b/src/main/resources/data/s3-ec2-deploy.yml new file mode 100644 index 0000000..1e4fc2c --- /dev/null +++ b/src/main/resources/data/s3-ec2-deploy.yml @@ -0,0 +1,99 @@ +# Repository의 Actions 탭에 나타날 Workflow 이름으로 필수 옵션은 아닙니다. +name: Deploy to Amazon EC2(S3 -> EC2 war) + +# Workflow를 실행시키기 위한 Event 목록입니다. +on: + # 하단 코드에 따라 develop 브랜치에 Push 또는 Pull Request 이벤트가 발생한 경우에 Workflow가 실행됩니다. + # 만약 브랜치 구분 없이 이벤트를 지정하고 싶을 경우에는 단순히 아래와 같이 작성도 가능합니다. +# push: +# branches: +# - master +# pull_request: +# branches: +# - master + + # 해당 옵션을 통해 사용자가 직접 Actions 탭에서 Workflow를 실행시킬 수 있습니다. + workflow_dispatch: + inputs: + logLevel: + description: 'Log level' + required: true + default: 'debug' + tags: + description: 'Run action tags' + required: true + default: 'Run action tags' + +jobs: + build: + name: xit-framework-bo CI with Gradle + # Runner가 실행되는 환경을 정의하는 부분입니다. + runs-on: ubuntu-latest + + steps: + - name: Check out Github-Action + # uses 키워드를 통해 Action을 불러올 수 있습니다. + # 여기에서는 해당 레포지토리로 check-out하여 레포지토리에 접근할 수 있는 Action을 불러왔습니다. + uses: actions/checkout@v2 + +# - name: 자바 JDK 1.8 설치 +# uses: actions/setup-java@v1 +# with: +# java-version: 1.8 + + - name: gradlew 권한 부여 + run: chmod +x ./gradlew + + - name: Gradle 빌드 + run: ./gradlew clean bootWar + + # 하나의 커맨드가 아닌 여러 커맨드 실행 + - name: Zip build file and deploy sh + run: | + mv ./build/libs/core-0.0.1-SNAPSHOT.war ./ROOT.war + zip $GITHUB_SHA.zip ./ROOT.war ./appspec.yml ./deploy.sh + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ap-northeast-2 + + - name: S3 업로드 +# env: +# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_S3_UPLOAD }} +# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_S3_UPLOAD_KEY }} + run: aws s3 cp --region ap-northeast-2 $GITHUB_SHA.zip s3://xit/admin/ +# run: aws deploy push --application-name xit-admin-back-deploy --description "This is a revision for the application xit-admin-backoffice" --s3-location s3://xit/admin/ROOT.zip --source . + +# deploy: +# name: Deploy ec2 +# needs: build +# runs-on: self-hosted +# steps: + - name: EC2 deploy + run: | + aws deploy create-deployment \ + --application-name xit-adm-bo-github-action-deploy \ + --deployment-group-name xit-adm-bo-github-action-grp \ + --s3-location bucket=xit,key=admin/$GITHUB_SHA.zip,bundleType=zip +# --s3-location bucket=xit,key=admin/buildFile.zip,bundleType=zip > deploymentId.txt +# +# +# DEPLOYMENT_ID=$(cat deploymentId.txt | jq -r '.deploymentId') +# +# echo "Wait 60 seconds for deployment and test" +# sleep 60 +# +# IS_SUCCESS=$(aws deploy get-deployment --deployment-id $DEPLOYMENT_ID | jq -r '.deploymentInfo.status') +# +# echo $IS_SUCCESS; +# +# if [ "$IS_SUCCESS" = "Succeeded" ]; +# then +# echo "SUCCESS DEPLOY!" +# else +# echo "FAIL DEPLOY!" +# exit 1 +# fi \ No newline at end of file diff --git a/src/main/resources/data/springdoc.txt b/src/main/resources/data/springdoc.txt new file mode 100644 index 0000000..3a30f04 --- /dev/null +++ b/src/main/resources/data/springdoc.txt @@ -0,0 +1,10 @@ +@Api -> @Tag +@ApiIgnore -> @Parameter(hidden = true) or @Operation(hidden = true) or @Hidden +@ApiImplicitParam -> @Parameter +@ApiImplicitParams -> @Parameters +@ApiModel -> @Schema +@ApiModelProperty(hidden = true) -> @Schema(accessMode = READ_ONLY) +@ApiModelProperty -> @Schema +@ApiOperation(value = "foo", notes = "bar") -> @Operation(summary = "foo", description = "bar") +@ApiParam -> @Parameter +@ApiResponse(code = 404, message = "foo") -> @ApiResponse(responseCode = "404", description = "foo") \ No newline at end of file diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..5d76ef7 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + ${LOG_PATH}/xit-framework.log + + + [%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{35} - %msg%n + + + ${LOG_PATH}/backup/xit-framework_%d{yyyy-MM-dd}.%i.log.gz + + 30MB + + 50 + + + + + + + DEBUG + + + + + + + + + + ${LOG_PATH}/xit-framework-error.log + + + [%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{35} - %msg%n + + + ${LOG_PATH}/backup/xit-framework-error_%d{yyyy-MM-dd}.%i.log.gz + + 30MB + + 50 + + + + + + + DEBUG + + + + + + + + + + + [%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{35} - %msg%n + + ${SLACK_WEBHOOK_URI} + xit-framework + :stuck_out_tongue_winking_eye: + true + + + + + ERROR + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/lucy-xss-sax.xml b/src/main/resources/lucy-xss-sax.xml new file mode 100644 index 0000000..fb50fbd --- /dev/null +++ b/src/main/resources/lucy-xss-sax.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/lucy-xss-servlet-filter-rule.xml b/src/main/resources/lucy-xss-servlet-filter-rule.xml new file mode 100644 index 0000000..b28d5bd --- /dev/null +++ b/src/main/resources/lucy-xss-servlet-filter-rule.xml @@ -0,0 +1,60 @@ + + + + + xssPreventerDefender + com.navercorp.lucy.security.xss.servletfilter.defender.XssPreventerDefender + + + xssSaxFilterDefender + com.navercorp.lucy.security.xss.servletfilter.defender.XssSaxFilterDefender + + lucy-xss-sax.xml + false + + + + xssFilterDefender + com.navercorp.lucy.security.xss.servletfilter.defender.XssFilterDefender + + lucy-xss.xml + false + + + + + xssPreventerDefender + + + + + + + + + + + + + + + diff --git a/src/main/resources/messages/i18n/error.properties b/src/main/resources/messages/i18n/error.properties new file mode 100644 index 0000000..98f115c --- /dev/null +++ b/src/main/resources/messages/i18n/error.properties @@ -0,0 +1 @@ +AAA=\uD55C\uAE00\uC5D0\uB7EC \ No newline at end of file diff --git a/src/main/resources/messages/i18n/error.yml b/src/main/resources/messages/i18n/error.yml new file mode 100644 index 0000000..a587b05 --- /dev/null +++ b/src/main/resources/messages/i18n/error.yml @@ -0,0 +1,3 @@ +auth: + id: + not-empty: 아이디는 6 ~ 20자리 입니다 \ No newline at end of file diff --git a/src/main/resources/messages/i18n/error_en.properties b/src/main/resources/messages/i18n/error_en.properties new file mode 100644 index 0000000..f3002ff --- /dev/null +++ b/src/main/resources/messages/i18n/error_en.properties @@ -0,0 +1 @@ +AAA=\uC601\uBB38\uC5D0\uB7EC \ No newline at end of file diff --git a/src/main/resources/messages/i18n/error_ko.yml b/src/main/resources/messages/i18n/error_ko.yml new file mode 100644 index 0000000..a587b05 --- /dev/null +++ b/src/main/resources/messages/i18n/error_ko.yml @@ -0,0 +1,3 @@ +auth: + id: + not-empty: 아이디는 6 ~ 20자리 입니다 \ No newline at end of file diff --git a/src/main/resources/messages/i18n/validation.properties b/src/main/resources/messages/i18n/validation.properties new file mode 100644 index 0000000..2e54eb1 --- /dev/null +++ b/src/main/resources/messages/i18n/validation.properties @@ -0,0 +1,3 @@ +auth.user.pattern.ProviderType=\uC120\uD0DD\uD55C SNS \uD0C0\uC785\uC740 \uC9C0\uC6D0 \uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 +auth.user.pattern.RoleType=\uC9C0\uC815\uD55C \uAD8C\uD55C \uD0C0\uC785\uC740 \uC9C0\uC6D0 \uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 +auth.user.pattern.id=\uC0AC\uC6A9\uC790ID\uB294 \uC22B\uC790\uC640 \uC601\uBB38\uC790\uB9CC \uAC00\uB2A5\uD569\uB2C8\uB2E4 \ No newline at end of file diff --git a/src/main/resources/messages/i18n/validation.yml b/src/main/resources/messages/i18n/validation.yml new file mode 100644 index 0000000..6637ba1 --- /dev/null +++ b/src/main/resources/messages/i18n/validation.yml @@ -0,0 +1,9 @@ +auth: + user: + pattern: + id: "사용자ID는 숫자와 영문자만 가능합니다" + passwrod: "비밀번호는 숫자와 영문자만 가능합니다" + email: "이메일 주소가 잘못되었습니다" + mobile: "휴대폰 번호는 11자리의 숫자만 가능 합니다" + value: + user-name: "사용자 이름은 필수입니다" \ No newline at end of file diff --git a/src/main/resources/messages/i18n/validation_en.properties b/src/main/resources/messages/i18n/validation_en.properties new file mode 100644 index 0000000..1fdf150 --- /dev/null +++ b/src/main/resources/messages/i18n/validation_en.properties @@ -0,0 +1 @@ +auth.user.pattern.id=user id invalid \ No newline at end of file diff --git a/src/main/resources/messages/i18n/validation_ko.yml b/src/main/resources/messages/i18n/validation_ko.yml new file mode 100644 index 0000000..b97093f --- /dev/null +++ b/src/main/resources/messages/i18n/validation_ko.yml @@ -0,0 +1,9 @@ +auth: + user: + pattern: + id: 사용자ID는 숫자와 영문자만 가능합니다 + passwrod: 비밀번호는 숫자와 영문자만 가능합니다 + email: 이메일 주소가 잘못되었습니다 + mobile: 휴대폰 번호는 11자리의 숫자만 가능 합니다 + value: + user-name: 사용자 이름은 필수입니다 \ No newline at end of file diff --git a/src/main/resources/mybatis-mapper/biz/cmm/cmmBoard-mapper.xml b/src/main/resources/mybatis-mapper/biz/cmm/cmmBoard-mapper.xml new file mode 100644 index 0000000..8816dcc --- /dev/null +++ b/src/main/resources/mybatis-mapper/biz/cmm/cmmBoard-mapper.xml @@ -0,0 +1,30 @@ + + + + + + /* board-mapper|insertBoard|julim */ + INSERT + INTO tb_cmm_board ( + title + , content + , ins_user_id + , ins_dt_tm + ) values ( + #{title} + , #{content} + , #{insUserId} + , #{insDtTm} + ) + + + \ No newline at end of file diff --git a/src/main/resources/templates/csrAttracker.html b/src/main/resources/templates/csrAttracker.html new file mode 100644 index 0000000..f4a654f --- /dev/null +++ b/src/main/resources/templates/csrAttracker.html @@ -0,0 +1,21 @@ + + + + + + + Title + + +

CSRF Attacker

+ Show Kittens Pictures + + + +
+ + + +
+ + \ No newline at end of file diff --git a/src/main/resources/templates/thymeleaf/biz/cmm/boardManagement.html b/src/main/resources/templates/thymeleaf/biz/cmm/boardManagement.html new file mode 100644 index 0000000..6676da3 --- /dev/null +++ b/src/main/resources/templates/thymeleaf/biz/cmm/boardManagement.html @@ -0,0 +1,13 @@ + + + + + + +
+

+

+
diff --git a/src/main/resources/templates/thymeleaf/biz/cmm/userManagement.html b/src/main/resources/templates/thymeleaf/biz/cmm/userManagement.html new file mode 100644 index 0000000..6676da3 --- /dev/null +++ b/src/main/resources/templates/thymeleaf/biz/cmm/userManagement.html @@ -0,0 +1,13 @@ + + + + + + +
+

+

+
diff --git a/src/main/resources/templates/thymeleaf/error/404.html b/src/main/resources/templates/thymeleaf/error/404.html new file mode 100644 index 0000000..2ba4716 --- /dev/null +++ b/src/main/resources/templates/thymeleaf/error/404.html @@ -0,0 +1,19 @@ + + + + + Test Error Page! + + + +
404
+

+

+

+

+

+

+

+

+ + \ No newline at end of file diff --git a/src/main/resources/templates/thymeleaf/error/error.html b/src/main/resources/templates/thymeleaf/error/error.html new file mode 100644 index 0000000..5bcbe5c --- /dev/null +++ b/src/main/resources/templates/thymeleaf/error/error.html @@ -0,0 +1,19 @@ + + + + + Test Error Page! + + + +
Error
+

+

+

+

+

+

+

+

+ + \ No newline at end of file diff --git a/src/main/resources/templates/thymeleaf/fluxHtml.html b/src/main/resources/templates/thymeleaf/fluxHtml.html new file mode 100644 index 0000000..dc87b5a --- /dev/null +++ b/src/main/resources/templates/thymeleaf/fluxHtml.html @@ -0,0 +1,23 @@ + + + + + + +
+
+ + +
+
+ +
+
+
+ + + + \ No newline at end of file diff --git a/src/main/resources/templates/thymeleaf/fluxJson.html b/src/main/resources/templates/thymeleaf/fluxJson.html new file mode 100644 index 0000000..dc87b5a --- /dev/null +++ b/src/main/resources/templates/thymeleaf/fluxJson.html @@ -0,0 +1,23 @@ + + + + + + +
+
+ + +
+
+ +
+
+
+ + + + \ No newline at end of file diff --git a/src/main/resources/templates/thymeleaf/fluxSample.html b/src/main/resources/templates/thymeleaf/fluxSample.html new file mode 100644 index 0000000..7b55c18 --- /dev/null +++ b/src/main/resources/templates/thymeleaf/fluxSample.html @@ -0,0 +1,21 @@ + + + + + + +
+

WebFlux!!!!!!!!!!!!!!!!!

+

+

name :

+

age :

+

+
+
+ + + + \ No newline at end of file diff --git a/src/main/resources/templates/thymeleaf/fragments/admin/footer.html b/src/main/resources/templates/thymeleaf/fragments/admin/footer.html new file mode 100644 index 0000000..be90177 --- /dev/null +++ b/src/main/resources/templates/thymeleaf/fragments/admin/footer.html @@ -0,0 +1,5 @@ +
+
+ +
+
\ No newline at end of file diff --git a/src/main/resources/templates/thymeleaf/fragments/admin/header.html b/src/main/resources/templates/thymeleaf/fragments/admin/header.html new file mode 100644 index 0000000..b99c942 --- /dev/null +++ b/src/main/resources/templates/thymeleaf/fragments/admin/header.html @@ -0,0 +1,14 @@ + +
+ + + + + Tailwind Admin Starter Template : Tailwind Toolbox + + + +
+ + \ No newline at end of file diff --git a/src/main/resources/templates/thymeleaf/fragments/admin/menu.html b/src/main/resources/templates/thymeleaf/fragments/admin/menu.html new file mode 100644 index 0000000..589bcae --- /dev/null +++ b/src/main/resources/templates/thymeleaf/fragments/admin/menu.html @@ -0,0 +1,79 @@ + + + diff --git a/src/main/resources/templates/thymeleaf/fragments/admin/sidebar.html b/src/main/resources/templates/thymeleaf/fragments/admin/sidebar.html new file mode 100644 index 0000000..3aab866 --- /dev/null +++ b/src/main/resources/templates/thymeleaf/fragments/admin/sidebar.html @@ -0,0 +1,30 @@ + \ No newline at end of file diff --git a/src/main/resources/templates/thymeleaf/fragments/dark/footer.html b/src/main/resources/templates/thymeleaf/fragments/dark/footer.html new file mode 100644 index 0000000..9888dab --- /dev/null +++ b/src/main/resources/templates/thymeleaf/fragments/dark/footer.html @@ -0,0 +1,31 @@ +
+
+
+
+
+

About

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas vel mi ut felis tempus commodo nec id erat. Suspendisse consectetur dapibus velit ut lacinia. +

+
+
+ +
+
+

Social

+ +
+
+
+
+
\ No newline at end of file diff --git a/src/main/resources/templates/thymeleaf/fragments/dark/header.html b/src/main/resources/templates/thymeleaf/fragments/dark/header.html new file mode 100644 index 0000000..a65e806 --- /dev/null +++ b/src/main/resources/templates/thymeleaf/fragments/dark/header.html @@ -0,0 +1,29 @@ + +
+ + + + + Tailwind Starter Template - Night Admin Template: Tailwind Toolbox + + + + + + +
+ + + diff --git a/src/main/resources/templates/thymeleaf/fragments/dark/menu.html b/src/main/resources/templates/thymeleaf/fragments/dark/menu.html new file mode 100644 index 0000000..0fbf2e5 --- /dev/null +++ b/src/main/resources/templates/thymeleaf/fragments/dark/menu.html @@ -0,0 +1,142 @@ + + + + diff --git a/src/main/resources/templates/thymeleaf/fragments/dark/sidebar.html b/src/main/resources/templates/thymeleaf/fragments/dark/sidebar.html new file mode 100644 index 0000000..8a78404 --- /dev/null +++ b/src/main/resources/templates/thymeleaf/fragments/dark/sidebar.html @@ -0,0 +1,29 @@ + + + + diff --git a/src/main/resources/templates/thymeleaf/fragments/include-link.html b/src/main/resources/templates/thymeleaf/fragments/include-link.html new file mode 100644 index 0000000..76cc79b --- /dev/null +++ b/src/main/resources/templates/thymeleaf/fragments/include-link.html @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/src/main/resources/templates/thymeleaf/fragments/include-script.html b/src/main/resources/templates/thymeleaf/fragments/include-script.html new file mode 100644 index 0000000..30c84de --- /dev/null +++ b/src/main/resources/templates/thymeleaf/fragments/include-script.html @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/src/main/resources/templates/thymeleaf/index.html b/src/main/resources/templates/thymeleaf/index.html new file mode 100644 index 0000000..2e9a1ce --- /dev/null +++ b/src/main/resources/templates/thymeleaf/index.html @@ -0,0 +1,299 @@ + + + + + + + + + +
+ +
+
+
+
+
+
+
Total Revenue
+

$3249

+
+
+
+ +
+
+ +
+
+
+
+
+
+
Total Users
+

249

+
+
+
+ +
+
+ +
+
+
+
+
+
+
New Users
+

2

+
+
+
+ +
+
+ +
+
+
+
+
+
+
Server Uptime
+

152 days

+
+
+
+ +
+
+ +
+
+
+
+
+
+
To Do List
+

7 tasks

+
+
+
+ +
+
+ +
+
+
+
+
+
+
Issues
+

3

+
+
+
+ +
+ + + +
+ +
+ +
+ +
+
+
Graph
+
+
+ + +
+
+ +
+ +
+ +
+
+
Graph
+
+
+ + +
+
+ +
+ +
+ +
+
+
Graph
+
+
+ + +
+
+ +
+ +
+ +
+
+
Graph
+
+
+ +
+
+ +
+ +
+ +
+
+
Template
+
+
+ +
+
+ +
+ +
+ +
+
+
Table
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
NameSideRole
Obi Wan KenobiLightJedi
GreedoSouthScumbag
Darth VaderDarkSith
+ +

See More issues...

+ +
+
+ +
+
+
diff --git a/src/main/resources/templates/thymeleaf/layouts/index-admin.html b/src/main/resources/templates/thymeleaf/layouts/index-admin.html new file mode 100644 index 0000000..41ee70e --- /dev/null +++ b/src/main/resources/templates/thymeleaf/layouts/index-admin.html @@ -0,0 +1,446 @@ + + + + + + + + Tailwind Admin Starter Template : Tailwind Toolbox + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ +
+ +
+
+

Analytics

+
+
+ +
+
+ +
+
+
+
+
+
+
Total Revenue
+

$3249

+
+
+
+ +
+
+ +
+
+
+
+
+
+
Total Users
+

249

+
+
+
+ +
+
+ +
+
+
+
+
+
+
New Users
+

2

+
+
+
+ +
+
+ +
+
+
+
+
+
+
Server Uptime
+

152 days

+
+
+
+ +
+
+ +
+
+
+
+
+
+
To Do List
+

7 tasks

+
+
+
+ +
+
+ +
+
+
+
+
+
+
Issues
+

3

+
+
+
+ +
+
+ + +
+ +
+ +
+
+
Graph
+
+
+ + +
+
+ +
+ +
+ +
+
+
Graph
+
+
+ + +
+
+ +
+ +
+ +
+
+
Graph
+
+
+ + +
+
+ +
+ +
+ +
+
+
Graph
+
+
+ +
+
+ +
+ +
+ +
+
+
Graph
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
NameSideRole
Obi Wan KenobiLightJedi
GreedoSouthScumbag
Darth VaderDarkSith
+ +

See More issues...

+ +
+
+ +
+ +
+ +
+
+
Advert
+
+
+ + + + + +
+
+ +
+ + +
+
+
+ + + + + + + + + + diff --git a/src/main/resources/templates/thymeleaf/layouts/index-dark.html b/src/main/resources/templates/thymeleaf/layouts/index-dark.html new file mode 100644 index 0000000..5b000dc --- /dev/null +++ b/src/main/resources/templates/thymeleaf/layouts/index-dark.html @@ -0,0 +1,513 @@ + + + + + + + Tailwind Starter Template - Night Admin Template: Tailwind Toolbox + + + + + + + + + + + + + + + +
+ +
+ + + +
+
+ +
+
+
+
+
+
+
Total Revenue
+

$3249

+
+
+
+ +
+
+ +
+
+
+
+
+
+
Total Users
+

249

+
+
+
+ +
+
+ +
+
+
+
+
+
+
New Users
+

2

+
+
+
+ +
+
+ +
+
+
+
+
+
+
Server Uptime
+

152 days

+
+
+
+ +
+
+ +
+
+
+
+
+
+
To Do List
+

7 tasks

+
+
+
+ +
+
+ +
+
+
+
+
+
+
Issues
+

3

+
+
+
+ +
+
+ + +
+ +
+ +
+ +
+
+
Graph
+
+
+ + +
+
+ +
+ +
+ +
+
+
Graph
+
+
+ + +
+
+ +
+ +
+ +
+
+
Graph
+
+
+ + +
+
+ +
+ +
+ +
+
+
Graph
+
+
+ +
+
+ +
+ +
+ +
+
+
Template
+
+
+ +
+
+ +
+ +
+ +
+
+
Table
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
NameSideRole
Obi Wan KenobiLightJedi
GreedoSouthScumbag
Darth VaderDarkSith
+ +

See More issues...

+ +
+
+ +
+ + +
+ + + +
+ + +
+ + +
+
+ +
+
+
+

About

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas vel mi ut felis tempus commodo nec id erat. Suspendisse consectetur dapibus velit ut lacinia. +

+
+
+ +
+
+

Social

+ +
+
+
+ + + +
+
+ + + + + diff --git a/src/main/resources/templates/thymeleaf/layouts/layout-admin.html b/src/main/resources/templates/thymeleaf/layouts/layout-admin.html new file mode 100644 index 0000000..430d846 --- /dev/null +++ b/src/main/resources/templates/thymeleaf/layouts/layout-admin.html @@ -0,0 +1,33 @@ + + + + + + + + + + +
+ + +
+ +
+ + +
+ +
+
+ + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/thymeleaf/layouts/layout-dark.html b/src/main/resources/templates/thymeleaf/layouts/layout-dark.html new file mode 100644 index 0000000..69876d9 --- /dev/null +++ b/src/main/resources/templates/thymeleaf/layouts/layout-dark.html @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + +
+
+ +
+ + +
+
+ +
+ + + + + \ No newline at end of file diff --git a/src/main/resources/templates/thymeleaf/sample/exceldown.html b/src/main/resources/templates/thymeleaf/sample/exceldown.html new file mode 100644 index 0000000..0646303 --- /dev/null +++ b/src/main/resources/templates/thymeleaf/sample/exceldown.html @@ -0,0 +1,84 @@ + + + + + +
+ + + + + +
+ + +
+ + + + + + + diff --git a/src/main/resources/templates/thymeleaf/sample/greeting.html b/src/main/resources/templates/thymeleaf/sample/greeting.html new file mode 100644 index 0000000..de6f71b --- /dev/null +++ b/src/main/resources/templates/thymeleaf/sample/greeting.html @@ -0,0 +1,29 @@ + + + + + +
+

+

+ + + + + + + + + + + + + + + +
+ + \ No newline at end of file diff --git a/src/main/resources/templates/thymeleaf/sample/sample-admin.html b/src/main/resources/templates/thymeleaf/sample/sample-admin.html new file mode 100644 index 0000000..ef69576 --- /dev/null +++ b/src/main/resources/templates/thymeleaf/sample/sample-admin.html @@ -0,0 +1,266 @@ + + + + + + + + + + +
+
+

Analytics

+
+
+ +
+
+ +
+
+
+
+
+
+
Total Revenue
+

$3249

+
+
+
+ +
+
+ +
+
+
+
+
+
+
Total Users
+

249

+
+
+
+ +
+
+ +
+
+
+
+
+
+
New Users
+

2

+
+
+
+ +
+
+ + +
+ +
+ +
+
+
Graph
+
+
+ + +
+
+ +
+ +
+ +
+
+
Graph
+
+
+ + +
+
+ +
+ +
+ +
+
+
Graph
+
+
+ + +
+
+ +
+ +
+ +
+
+
Graph
+
+
+ +
+
+ +
+ +
+ +
+
+
Graph
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
NameSideRole
Obi Wan KenobiLightJedi
GreedoSouthScumbag
Darth VaderDarkSith
+ +

See More issues...

+ +
+
+ +
+ +
+ +
+
+
Advert
+
+
+ + + + + +
+
+ +
+ + +
+
diff --git a/src/main/resources/templates/thymeleaf/sample/sample-dark.html b/src/main/resources/templates/thymeleaf/sample/sample-dark.html new file mode 100644 index 0000000..7a9e4a9 --- /dev/null +++ b/src/main/resources/templates/thymeleaf/sample/sample-dark.html @@ -0,0 +1,253 @@ + + + + + + + + + +
+ +
+
+
+
+
+
+
Total Revenue
+

$3249

+
+
+
+ +
+
+ +
+
+
+
+
+
+
Total Users
+

249

+
+
+
+ +
+
+ +
+
+
+
+
+
+
New Users
+

2

+
+
+
+ +
+ + +
+ +
+ +
+ +
+
+
Graph
+
+
+ + +
+
+ +
+ +
+ +
+
+
Graph
+
+
+ + +
+
+ +
+ +
+ +
+
+
Graph
+
+
+ + +
+
+ +
+ +
+ +
+
+
Graph
+
+
+ +
+
+ +
+ +
+ +
+
+
Template
+
+
+ +
+
+ +
+ +
+ +
+
+
Table
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
NameSideRole
Obi Wan KenobiLightJedi
GreedoSouthScumbag
Darth VaderDarkSith
+ +

See More issues...

+ +
+
+ +
+
+
diff --git a/src/main/webapp/WEB-INF/jsp/Test.jsp b/src/main/webapp/WEB-INF/jsp/Test.jsp new file mode 100644 index 0000000..49d75f5 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/Test.jsp @@ -0,0 +1,47 @@ +<%-- + Created by IntelliJ IDEA. + User: Administrator + Date: 2021-08-09 + Time: 20:49 + To change this template use File | Settings | File Templates. +--%> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + Title + + + JSP Test 에서 엑셀다운로드 테스트 + + + + + diff --git a/src/test/java/com/xit/ApplicationTest.java b/src/test/java/com/xit/ApplicationTest.java new file mode 100644 index 0000000..29fd818 --- /dev/null +++ b/src/test/java/com/xit/ApplicationTest.java @@ -0,0 +1,17 @@ +package com.xit; + +import org.junit.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +//@ExtendWith(SpringExtension.class) +@SpringBootTest +//@TestPropertySource({"classpath:application-test.properties","classpath:config.properties"}) +//@ActiveProfiles({"local"}) +public class ApplicationTest extends SpringBootServletInitializer { + + @Test + public void contextLoads() { + } + +} diff --git a/src/test/java/com/xit/biz/cmm/BoardControllerMockMvcTest.java b/src/test/java/com/xit/biz/cmm/BoardControllerMockMvcTest.java new file mode 100644 index 0000000..ee71dd3 --- /dev/null +++ b/src/test/java/com/xit/biz/cmm/BoardControllerMockMvcTest.java @@ -0,0 +1,109 @@ +//package com.xit.biz.sample.controller; +// +//import com.fasterxml.jackson.databind.ObjectMapper; +//import com.xit.biz.sample.domain.Board; +//import com.xit.biz.sample.dto.BoardDto; +//import com.xit.biz.sample.service.BoardServiceImpl; +//import org.junit.jupiter.api.AfterEach; +//import org.junit.jupiter.api.BeforeEach; +//import org.junit.jupiter.api.Test; +// +//import org.mockito.Mockito; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +//import org.springframework.boot.test.mock.mockito.MockBean; +//import org.springframework.http.MediaType; +//import org.springframework.test.context.ActiveProfiles; +//import org.springframework.test.web.servlet.MockMvc; +//import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +// +//import static org.hamcrest.core.Is.is; +//import static org.mockito.ArgumentMatchers.any; +//import static org.mockito.BDDMockito.given; +//import static org.springframework.http.MediaType.APPLICATION_JSON; +//import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +// +// +//@WebMvcTest(BoardController.class) +//@ActiveProfiles({"local", "logging"}) +//class BoardControllerMockMvcTest { +// +// @MockBean +// private BoardServiceImpl boardService; +// +// @Autowired +// private MockMvc mockMvc; +// +// ObjectMapper mapper = new ObjectMapper(); +// +// final BoardDto boardDto = BoardDto.builder() +// .boardId(1L) +// .title("제목1") +// .content("내용1") +// .insUserId("작성자1") +// .updUserId(null) +// .insDtTm(null) +// .updDtTm(null) +// .build(); +// +// final Board board = Board.builder() +// .boardId(1L) +// .title("제목1") +// .content("내용1") +// .insUserId("작성자1") +// .updUserId(null) +// .insDtTm(null) +// .updDtTm(null) +// .build(); +// +// @Test +// void findByBoardId() throws Exception { +// Mockito.when(boardService.findByBoardId(boardDto.getBoardId())).thenReturn(boardDto); +// +// mockMvc.perform(MockMvcRequestBuilders +// .get("/sample/board/{boardId}", 1) +// //.param("id", String.valueOf(boardDto.getId())) +// .contentType(MediaType.APPLICATION_JSON)) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.boardId", is(boardDto.getBoardId().longValue()))) +// .andExpect(jsonPath("$.title", is(boardDto.getTitle()))) +// .andExpect(jsonPath("$.content", is(boardDto.getContent()))) +// .andExpect(jsonPath("$.insUserId", is(boardDto.getInsUserId()))) +// .andExpect(jsonPath("$.insDtTm", is(boardDto.getInsDtTm()))) +// .andExpect(jsonPath("$.updDtTm", is(boardDto.getUpdDtTm()))); +// //.andExpect(jsonPath("$.updDtTm").value(IsNull.nullValue())); +// } +// +// @Test +// void addBoard() throws Exception { +// given(boardService.add(any(BoardDto.class))).willReturn(boardDto); +// +// mockMvc.perform(MockMvcRequestBuilders +// .post("/sample/board") +// .content(mapper.writeValueAsString(boardDto)) +// .contentType(MediaType.APPLICATION_JSON) +// .accept(APPLICATION_JSON)) +// .andDo(print()) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$.boardId", is(boardDto.getBoardId().longValue()))) +// .andExpect(jsonPath("$.title", is(boardDto.getTitle()))) +// .andExpect(jsonPath("$.content", is(boardDto.getContent()))) +// .andExpect(jsonPath("$.insUserId", is(boardDto.getInsUserId()))) +// .andExpect(jsonPath("$.insDtTm", is(boardDto.getInsDtTm()))) +// .andExpect(jsonPath("$.updDtTm", is(boardDto.getUpdDtTm()))); +// //.andExpect(jsonPath("$.updDtTm").value(IsNull.nullValue())); +// } +// +// +// @BeforeEach +// void setUp() { +// } +// +// @AfterEach +// void tearDown() { +// } +// +// +//} \ No newline at end of file diff --git a/src/test/java/com/xit/biz/cmm/BoardControllerTest.java b/src/test/java/com/xit/biz/cmm/BoardControllerTest.java new file mode 100644 index 0000000..ae285a5 --- /dev/null +++ b/src/test/java/com/xit/biz/cmm/BoardControllerTest.java @@ -0,0 +1,60 @@ +//package com.xit.biz.sample.controller; +// +//import com.xit.biz.sample.dto.BoardDto; +//import com.xit.biz.sample.service.BoardServiceImpl; +//import com.xit.core.exception.NotFoundException; +//import org.junit.jupiter.api.AfterEach; +//import org.junit.jupiter.api.BeforeEach; +//import org.junit.jupiter.api.Test; +//import org.junit.jupiter.api.extension.ExtendWith; +//import org.mockito.InjectMocks; +//import org.mockito.Mock; +//import org.mockito.junit.jupiter.MockitoExtension; +//import org.springframework.http.HttpStatus; +//import org.springframework.http.ResponseEntity; +// +//import java.util.Objects; +// +//import static org.junit.jupiter.api.Assertions.*; +//import static org.mockito.ArgumentMatchers.*; +//import static org.mockito.BDDMockito.given; +//import static org.mockito.Mockito.*; +// +//@ExtendWith(MockitoExtension.class) +//class BoardControllerTest { +// +// @Mock +// private BoardServiceImpl boardService; +// +// @InjectMocks +// private BoardController boardController; +// +// @BeforeEach +// void setUp() { +// } +// +// @AfterEach +// void tearDown() { +// } +// +// @Test +// void findByBoardId() { +// final BoardDto boardDto = new BoardDto(1L, "제목1", "내용1", null,"작성자1", null, null, null); +// +// given(boardService.findByBoardId(boardDto.getBoardId())).willReturn(boardDto); +// ResponseEntity re = boardController.findByBoardId(boardDto.getBoardId()); +// assertNotNull(re); +// assertEquals(HttpStatus.OK, re.getStatusCode()); +// assertEquals(boardDto.getBoardId(), Objects.requireNonNull(re.getBody().getBoardId())); +// verify(boardService).findByBoardId(anyLong()); +// +// // 실패 +// given(boardService.findByBoardId(anyLong())).willThrow(NotFoundException.class); +// assertThrows(NotFoundException.class, ()->boardController.findByBoardId(anyLong())); +//// verify(boardService).findByBoardId(anyLong()); +// } +// +// @Test +// void addBoard() { +// } +//} \ No newline at end of file diff --git a/src/test/java/com/xit/biz/cmm/BoardRepositoryTest__.java b/src/test/java/com/xit/biz/cmm/BoardRepositoryTest__.java new file mode 100644 index 0000000..178eccd --- /dev/null +++ b/src/test/java/com/xit/biz/cmm/BoardRepositoryTest__.java @@ -0,0 +1,159 @@ +//package com.xit.biz.sample; +// +//import com.xit.biz.sample.domain.Board; +//import com.xit.biz.sample.domain.BoardRepository; +//import lombok.extern.slf4j.Slf4j; +//import org.aspectj.lang.annotation.After; +//import org.junit.jupiter.api.BeforeEach; +//import org.junit.jupiter.api.Test; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +//import org.springframework.boot.test.context.SpringBootTest; +//import org.springframework.boot.test.web.client.TestRestTemplate; +//import org.springframework.http.MediaType; +//import org.springframework.http.HttpStatus; +//import org.springframework.http.ResponseEntity; +// +// +//import org.springframework.test.context.ActiveProfiles; +//import org.springframework.test.web.servlet.MockMvc; +//import org.springframework.test.web.servlet.setup.MockMvcBuilders; +//import org.springframework.transaction.annotation.Transactional; +//import org.springframework.web.context.WebApplicationContext; +//import org.springframework.web.filter.CharacterEncodingFilter; +// +//import java.util.List; +// +//import static org.assertj.core.api.Assertions.assertThat; +//import static org.springframework.test.web.client.match.MockRestRequestMatchers.content; +//import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +// +//import static org.assertj.core.api.BDDAssertions.then; +//import static org.hamcrest.core.Is.is; +// +// +////Junit5 사용시에는 해당 어노테이션은 명시할 필요없다. +////@RunWith(SpringRunner.class) +// +//@SpringBootTest( +// //properties = { "propertyTest.value=propertyTest", "testValue=test" } +// //properties = {"spring.config.location=classpath:application-test.properties"} +// webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT +//) +//// Mock 테스트시 필요한 의존성을 제공 +//@AutoConfigureMockMvc +//@Transactional +//@ActiveProfiles(value = {"local","logging"}) +//@Slf4j +//public class BoardRepositoryTest { +// +// @Autowired +// BoardRepository boardRepository; +// +// /* +// @MockBean +// BoardRepository boardRepository; +// */ +// +// @Autowired +// MockMvc mvc; +// +// /* +// webEnvironment설정시(NONE을 설정시 사용 불가) 그에 맞춰서 자동으로 설정되어 빈이 생성되며, RestTemplate의 테스트를 처리가 가능 +// */ +// @Autowired private TestRestTemplate restTemplate; +// +// // Service로 등록하는 빈 +// //@Autowired +// //private MemberService memberService; +// +// @Autowired +// private WebApplicationContext ctx; +// +// +// //Junit4의 @Before +// @BeforeEach() +// public void setup() { +// this.mvc = MockMvcBuilders.webAppContextSetup(ctx) +// .addFilters(new CharacterEncodingFilter("UTF-8", true)) // 필터 추가 +// .alwaysDo(print()) +// .build(); +// } +// +// +// +// // @After +// public void cleanup() { +// boardRepository.deleteAll(); +// } +// +// @Test +// public void 게시글저장_블러오기() { +// //given +// String title = "테스트 게시글"; +// String content = "테스트 본문"; +// +// boardRepository.save(Board.builder() +// .title(title) +// .content(content) +// .insUserId("b088081@gmail.com") +// .build()); +// +// //when +// List boardList = boardRepository.findAll(); +// +// //then +// Board board = boardList.get(0); +// assertThat(board.getTitle()).isEqualTo(title); +// assertThat(board.getContent()).isEqualTo(content); +// } +// +// @Test +// void getMember() throws Exception { +// log.info("##### Properties 테스트 #####"); +// +// /******** START : MOC MVC test **********/ +// log.info("******** START : MOC MVC test **********"); +// mvc.perform(get("/memberTest/1")) +// .andExpect(status().isOk()) +// .andExpect(content().contentType(MediaType.APPLICATION_JSON)) +// .andExpect(jsonPath("$.boardId", is("goddaehee"))) +// .andDo(print()); +// log.info("******** END : MOC MVC test **********"); +// /******** END : MOC MVC test **********/ +// +// /******** START : TestRestTemplate test **********/ +// log.info("******** START : TestRestTemplate test **********"); +// ResponseEntity response = restTemplate.getForEntity("/memberTest/1", MemberVo.class); +// then(response.getStatusCode()).isEqualTo(HttpStatus.OK); +// then(response.getBody()).isNotNull(); log.info("******** END : TestRestTemplate test **********"); +// /******** END : TestRestTemplate test **********/ +// +// /******** START : MockBean test **********/ +// log.info("******** START : MockBean test **********"); +// /* +// MemberVo memberVo = MemberVo.builder() +// .id(testId) .name(testName) +// .build(); +// given(memberRepository.findByBoardId(1L)) +// .willReturn(Optional.of(memberVo)); +// */ +// +// Optional member = memberService.findByBoardId(1L); +// if (member.isPresent()) { +// // ※ Junit4 사용시 +// // assertThat(memberVo.getId()).isEqualTo(member.get().getId()); +// // assertThat(memberVo.getName()).isEqualTo(member.get().getName()); +// // Junit5 BDD 사용시 +// then("goddaehee").isEqualTo(member.get().getId()); +// then("갓대희").isEqualTo(member.get().getName()); +// } log.info("******** END : MockBean test **********"); +// /******** END : MockBean test **********/ +// } +// +// +// +//} diff --git a/src/test/java/com/xit/biz/cmm/BoardServiceTest.java b/src/test/java/com/xit/biz/cmm/BoardServiceTest.java new file mode 100644 index 0000000..6d2cb3d --- /dev/null +++ b/src/test/java/com/xit/biz/cmm/BoardServiceTest.java @@ -0,0 +1,106 @@ +//package com.xit.biz.sample.service; +// +//import com.xit.biz.sample.domain.Board; +//import com.xit.biz.sample.domain.BoardRepository; +//import com.xit.biz.sample.dto.BoardDto; +//import com.xit.biz.sample.mapper.BoardMapper; +//import com.xit.core.exception.NotFoundException; +//import org.junit.jupiter.api.AfterEach; +//import org.junit.jupiter.api.BeforeEach; +//import org.junit.jupiter.api.Test; +//import org.junit.jupiter.api.extension.ExtendWith; +//import org.mapstruct.factory.Mappers; +//import org.mockito.InjectMocks; +//import org.mockito.Mock; +//import org.mockito.junit.jupiter.MockitoExtension; +// +//import java.util.Optional; +// +//import static org.junit.jupiter.api.Assertions.*; +//import static org.mockito.ArgumentMatchers.*; +//import static org.mockito.BDDMockito.given; +//import static org.mockito.Mockito.*; +// +//@ExtendWith(MockitoExtension.class) +//class BoardServiceTest { +// +// @Mock +// private BoardRepository boardRepository; +// +// @InjectMocks +// private BoardService boardService = new BoardServiceImpl(this.boardRepository); +// +// private final BoardMapper mapper = Mappers.getMapper(BoardMapper.class); +// +// @BeforeEach +// void setUp() { +// } +// +// @AfterEach +// void tearDown() { +// } +// +// @Test +// void add() { +// final BoardDto boardDto = new BoardDto(1L, "제목1", "내용1", "작성자1", null, null, null); +// +// // 성공 +// // given()이 호출되면 willReturn() 이 반환 +// given(boardRepository.findByBoardId(boardDto.getId())).willReturn(Optional.empty()); +// given(boardRepository.save(mapper.toEntity(boardDto)).willReturn(Board.class); +// +// Board savedBoard = boardService.add(boardDto); +// assertNotNull(savedBoard); +// // verify() 가 findByBoardId를 호출 했는지 검증 +// verify(boardRepository).findByBoardId(anyLong()); +// verify(boardRepository).save(any(Board.class)); +// +// // 실패 +// given(boardRepository.findByBoardId(board.getId())).willReturn(Optional.of(board)); +// assertThrows(NotFoundException.class, ()->boardService.add(board)); +// verify(boardRepository, never()).save(any(Board.class)); +// } +// +// @Test +// void findByBoardId() { +// final BoardDto boardDto = new BoardDto(1L, "제목1", "내용1", "작성자1", null, null, null); +// +// // 성공 +// given(boardRepository.findByBoardId(boardDto.getId())).willReturn(Optional.of(boardDto)); +// BoardDto getBoard = boardService.findByBoardId(boardDto.getId()); +// assertEquals(boardDto, getBoard); +// verify(boardRepository).findByBoardId(anyLong()); +// +// // 실패 +// final Long id = 1L; +// given(boardRepository.findByBoardId(anyLong())).willReturn(Optional.empty()); +// assertThrows(NotFoundException.class, () -> boardService.findByBoardId(id)); +// verify(boardRepository).findByBoardId(any(Long.class)); +// } +// +// @Test +// void update() { +// final BoardDto boardDto = new BoardDto(1L, "제목1-1", "내용1-1", "작성자1", "변경자1", null, null); +// +// // 성공 +// given(boardRepository.findByBoardId(boardDto.getId())).willReturn(Optional.of(boardDto)); +// given(boardRepository.save(boardDto)).willReturn(board); +// +// Board updateBoard = boardService.update(board); +// assertNotNull(updateBoard); +// verify(boardRepository).save(any(Board.class)); +// +// // 실패 +// given(boardRepository.findByBoardId(anyLong())).willReturn(Optional.empty()); +// assertThrows(NotFoundException.class, () -> boardService.update(board)); +// verify(boardRepository).findByBoardId(anyLong()); +// } +// +// @Test +// void delete() { +// final Long id = 1L; +// boardService.delete(id); +// verify(boardRepository, times(1)).deleteById(anyLong()); +// verify(boardRepository, times(1)).findByBoardId(anyLong()); +// } +//} \ No newline at end of file diff --git a/src/test/java/com/xit/biz/cmm/controller/CmmUserControllerMockMvcTest.java b/src/test/java/com/xit/biz/cmm/controller/CmmUserControllerMockMvcTest.java new file mode 100644 index 0000000..0ac64d1 --- /dev/null +++ b/src/test/java/com/xit/biz/cmm/controller/CmmUserControllerMockMvcTest.java @@ -0,0 +1,122 @@ +//package com.xit.biz.cmm.controller; +// +//import com.xit.biz.cmm.domain.CmmUser; +//import com.xit.biz.cmm.domain.CmmUserRepository; +//import org.junit.jupiter.api.*; +//import org.mockito.Mockito; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +//import org.springframework.boot.test.context.SpringBootTest; +//import org.springframework.test.web.servlet.MockMvc; +//import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +// +//import static org.assertj.core.api.Assertions.assertThat; +//import static org.codehaus.groovy.runtime.DefaultGroovyMethods.any; +//import static org.mockito.BDDMockito.given; +//import static org.springframework.http.MediaType.APPLICATION_JSON; +//import static org.springframework.test.web.client.match.MockRestRequestMatchers.jsonPath; +//import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +// +//@SpringBootTest +//@AutoConfigureMockMvc +////@TestMethodOrder(value = MethodOrderer.OrderAnnotation.class) // @Order에 의한 순서 +//@TestMethodOrder(MethodOrderer.MethodName.class) +////@TestMethodOrder(value = MethodOrderer.DisplayName.class) +//class CmmUserControllerMockMvcTest { +// +//// @LocalServerPort +//// private int port; +// +// @Autowired +// private MockMvc mockMvc; +// +//// @Autowired +//// private TestRestTemplate restTemplate; +// +// @Autowired(required=true) +// private CmmUserRepository cmmUserRepository; +// +// final CmmUser cmmUser = CmmUser.builder() +// .cmmUserId(1L) +// .userId("minuk926") +// .userName("임종욱") +// .passWord("password") +// .insUserId("minuk926") +// .build(); +// +// @BeforeEach +// void setUp() { +// } +// +// @AfterEach +// void tearDown() { +// cmmUserRepository.deleteAll(); +// } +// +// @Test +// void T01_사용자_생성() throws Exception { +// given(cmmUserRepository.save(cmmUser)).willReturn(cmmUser); +// +// mockMvc.perform(MockMvcRequestBuilders +// .post("/web/cmm/user/save") +// //.content(mapper.writeValueAsString(boardDto)) +// .contentType(APPLICATION_JSON) +// .accept(APPLICATION_JSON)) +// .andDo(print()) +// .andExpect(status().isOk()); +//// .andExpect(jsonPath("$.boardId", is(boardDto.getBoardId().longValue()))) +//// .andExpect(jsonPath("$.title", is(boardDto.getTitle()))) +//// .andExpect(jsonPath("$.content", is(boardDto.getContent()))) +//// .andExpect(jsonPath("$.insUserId", is(boardDto.getInsUserId()))) +//// .andExpect(jsonPath("$.insDtTm", is(boardDto.getInsDtTm()))) +//// .andExpect(jsonPath("$.updDtTm", is(boardDto.getUpdDtTm()))); +// //.andExpect(jsonPath("$.updDtTm").value(IsNull.nullValue())); +// } +// +// @Test +// void T02_사용자_조회() throws Exception { +// Mockito.when(cmmUserRepository.findByCmmUserId(cmmUser.getCmmUserId())).thenReturn(cmmUser); +// +// this.mockMvc.perform(MockMvcRequestBuilders +// .get("/web/cmm/user/{cmmUserId}", 1) +// //.param("id", String.valueOf(boardDto.getId())) +// .contentType(APPLICATION_JSON)) +// .andExpect(status().isOk()); +//// .andExpect(content().json()) +//// .andExpect(jsonPath("$.title", is(boardDto.getTitle()))) +//// .andExpect(jsonPath("$.content", is(boardDto.getContent()))) +//// .andExpect(jsonPath("$.insUserId", is(boardDto.getInsUserId()))) +//// .andExpect(jsonPath("$.insDtTm", is(boardDto.getInsDtTm()))) +//// .andExpect(jsonPath("$.updDtTm", is(boardDto.getUpdDtTm()))); +// //.andExpect(jsonPath("$.updDtTm").value(IsNull.nullValue())); +// } +// +// +// +// +// @Test +// void userAll() { +// } +// +// @Test +// void userAllPage() { +// } +// +// @Test +// void findByCmmUserId() { +// } +// +// @Test +// void findByUserId() { +// } +// +// +// @Test +// void modifyUser() { +// } +// +// @Test +// void removeUserById() { +// } +//} \ No newline at end of file diff --git a/src/test/java/com/xit/biz/cmm/controller/CmmUserControllerMockitoTest.java b/src/test/java/com/xit/biz/cmm/controller/CmmUserControllerMockitoTest.java new file mode 100644 index 0000000..e41cb08 --- /dev/null +++ b/src/test/java/com/xit/biz/cmm/controller/CmmUserControllerMockitoTest.java @@ -0,0 +1,67 @@ +package com.xit.biz.cmm.controller; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(MockitoExtension.class) +class CmmUserControllerMockitoTest { + + @Mock + //private Service service; + + @InjectMocks + private CmmUserMgtController cmmUserMgtController; + + @InjectMocks + private CmmUserWebController cmmUserWebController; + + //@InjectMocks + //private CmmUserRepository cmmUserRepository; + + @BeforeEach + void setMockOutput() { + //when(helloService.getWelcomeMessage()).thenReturn("Hello Mockito Test"); + } + + @AfterEach + void tearDown() { + + } + + @Test + void T01_API_사용자등록() { + //CmmUser cmmUser = cmmUserController.findByCmmUserId(1L); + } + + @Test + void userAll() { + } + + @Test + void userAllPage() { + } + + @Test + void findByCmmUserId() { + } + + @Test + void findByUserId() { + } + + + @Test + void modifyUser() { + } + + @Test + void removeUserById() { + } +} \ No newline at end of file diff --git a/src/test/java/com/xit/biz/cmm/controller/CmmUserControllerRestTest.java b/src/test/java/com/xit/biz/cmm/controller/CmmUserControllerRestTest.java new file mode 100644 index 0000000..82fca1e --- /dev/null +++ b/src/test/java/com/xit/biz/cmm/controller/CmmUserControllerRestTest.java @@ -0,0 +1,89 @@ +//package com.xit.biz.cmm.controller; +// +//import com.xit.biz.cmm.domain.CmmUser; +//import com.xit.biz.cmm.domain.CmmUserRepository; +//import org.junit.jupiter.api.AfterEach; +//import org.junit.jupiter.api.BeforeEach; +//import org.junit.jupiter.api.Test; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.test.context.SpringBootTest; +//import org.springframework.boot.test.web.client.TestRestTemplate; +//import org.springframework.boot.web.server.LocalServerPort; +//import org.springframework.http.HttpStatus; +//import org.springframework.http.ResponseEntity; +// +//import java.util.List; +// +//import static org.assertj.core.api.Assertions.assertThat; +//import static org.junit.jupiter.api.Assertions.*; +// +//@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +//class CmmUserControllerRestTest { +// +// @LocalServerPort +// private int port; +// +// private String url; +// +// @Autowired +// private TestRestTemplate restTemplate; +// +// @BeforeEach +// void setUp() { +// url = String.format("http://localhost:%d", port); +// } +// +// @AfterEach +// void tearDown() { +// +// } +// +// @Test +// void T01_API_사용자등록() { +// String url = "http://localhost:" + port +"/api/cmm/user"; +// +// //given +// //CmmUser cmmUser = CmmUser.builder().cmmUserId().userId() +// CmmUser cmmUser = new CmmUser(null, "minuk926", "임종욱", "password", "minuk926", null, null, null); +// +// //when +// ResponseEntity responseEntity = this.restTemplate.postForEntity(url+"/api/cmm/user", cmmUser, CmmUser.class); +// +// //then +// assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK); +// +//// List list = cmmUserRepository.findAll(); +//// assertThat(list.get(0).getUserId()).isEqualTo("minuk926"); +//// assertThat(list.get(0).getUserName()).isEqualTo("임종욱"); +// } +// +// @Test +// void T01_WEB_사용자_조회() { +// CmmUser cmmUser = this.restTemplate.getForObject(url+"/api/cmm/user/{cmmUserId}", CmmUser.class, 1); +// } +// +// @Test +// void userAll() { +// } +// +// @Test +// void userAllPage() { +// } +// +// @Test +// void findByCmmUserId() { +// } +// +// @Test +// void findByUserId() { +// } +// +// +// @Test +// void modifyUser() { +// } +// +// @Test +// void removeUserById() { +// } +//} \ No newline at end of file diff --git a/src/test/java/com/xit/biz/cmm/controller/CmmUserControllerTest.java b/src/test/java/com/xit/biz/cmm/controller/CmmUserControllerTest.java new file mode 100644 index 0000000..df7dee0 --- /dev/null +++ b/src/test/java/com/xit/biz/cmm/controller/CmmUserControllerTest.java @@ -0,0 +1,93 @@ +package com.xit.biz.cmm.controller; + +import com.xit.biz.cmm.entity.CmmRole; +import com.xit.biz.cmm.entity.CmmUser; +import com.xit.biz.cmm.service.ICmmUserService; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.Arrays; +import java.util.List; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; + +@SpringBootTest +@AutoConfigureMockMvc +@Slf4j +@ActiveProfiles("test") +public class CmmUserControllerTest { + + @Autowired + MockMvc mockMvc; + + @MockBean + private ICmmUserService ICmmUserService; + + private CmmUser user; + private List roles; + + @BeforeEach + void setUp() { + // given + this.user = CmmUser.builder() + .userId("minuk926") + .userName("임종욱") + .password("minuk926") + //.insUserId("minuk926") + .build(); + this.roles = Arrays.asList( + CmmRole.builder() + .roleName("ROLE_1") + .roleRemark("ROLE_1_REMARK") + .build(), + CmmRole.builder() + .roleName("ROLE_2") + .roleRemark("ROLE_2_REMARK") + .build() + + ); + } + + @AfterEach + void tearDown() { + } + + @Test + public void test(){ + log.info("---------"); + } + + @Test + void findAll() { + } + + @Test + void findAllPage() { + } + + @Test + void findByCmmUserId() { + } + + @Test + void save() { +// mockMvc.perform(post("/api/cmm/user")) + + } + + @Test + void modify() { + } + + @Test + void deleteById() { + } +} diff --git a/src/test/java/com/xit/biz/cmm/repository/ICmmBoardRepositoryTest.java b/src/test/java/com/xit/biz/cmm/repository/ICmmBoardRepositoryTest.java new file mode 100644 index 0000000..4e58c61 --- /dev/null +++ b/src/test/java/com/xit/biz/cmm/repository/ICmmBoardRepositoryTest.java @@ -0,0 +1,65 @@ +package com.xit.biz.cmm.repository; + +import com.xit.biz.cmm.entity.CmmBoard; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@ExtendWith(SpringExtension.class) +@DataJpaTest +class ICmmBoardRepositoryTest { + + @Autowired + private ICmmBoardRepository boardRepository; + + private CmmBoard board; + + @BeforeEach + void setUp() { + board = board.builder() + .title("board title") + .content("board content") + .ctgCd("ctg1") + .build(); + + boardRepository.save(board); + } + + @AfterEach + void tearDown() { + } + + @Test + void idStrategyTest() { + CmmBoard board1 = new CmmBoard(null, "ctg_1", "제목1", "내용1"); + CmmBoard board2 = new CmmBoard(null, "ctg_2", "제목2", "내용2"); + CmmBoard savedBoard1 = boardRepository.save(board1); + CmmBoard savedBoard2 = boardRepository.save(board2); + + assertEquals(1, Math.abs(savedBoard2.getBoardId() - savedBoard1.getBoardId())); + + } + + @Test + void 게시글_작성(){ + CmmBoard board = new CmmBoard(null, "ctg_1", "제목1", "내용1"); + CmmBoard saveBoard = boardRepository.save(board); + assertEquals(board.getTitle(), saveBoard.getTitle(), "savedUserTest"); + assertEquals(board.getContent(), saveBoard.getContent()); + } + + @Test + void 게시글_조회(){ + Optional boardInfo = boardRepository.findById(1l); + boardInfo.ifPresent(v -> assertEquals(v.getTitle(), "제목1")); + + //Optional byId = boardRepository.findByBoardId(1l); + } +} \ No newline at end of file diff --git a/src/test/java/com/xit/biz/cmm/repository/ICmmCodeRepositoryTest.java b/src/test/java/com/xit/biz/cmm/repository/ICmmCodeRepositoryTest.java new file mode 100644 index 0000000..51717d8 --- /dev/null +++ b/src/test/java/com/xit/biz/cmm/repository/ICmmCodeRepositoryTest.java @@ -0,0 +1,143 @@ +package com.xit.biz.cmm.repository; + +import com.xit.biz.cmm.entity.*; +import com.xit.biz.cmm.entity.spec.CmmCodeSpecification; +import com.xit.biz.cmm.dto.CmmCodeDto; +import com.xit.biz.cmm.dto.ComboCodeDto; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.domain.Example; +import org.springframework.data.domain.ExampleMatcher; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.jpa.domain.Specification; + +import java.util.List; + +@DataJpaTest +class ICmmCodeRepositoryTest { + + @Autowired + private ICmmCodeGrpRepository cmmCodeGrpRepository; + + @Autowired + private ICmmCodeLRepostory cmmCodeLRepository; + + @Autowired + private ICmmCodeMRepository cmmCodeMRepository; + + @Autowired + private ICmmCodeSRepository cmmCodeSRepository; + + @BeforeEach + void setUp() { + } + + @AfterEach + void tearDown() { + } + + @Test + void load(){ + } + + @Test + void crud(){ + CmmCodeGrp cmmCodeGrp = CmmCodeGrp.builder() + .codeGrpId("G_CODE_SAM") + .codeNm("공통코드그룹예제") + .codeRemark("공통코드그룹설명") + .codeOrdr(99) + .build() + ; + CmmCodeGrp savedCmmCodeGrp = cmmCodeGrpRepository.save(cmmCodeGrp); + CmmCodeGrp qCmmCodeGrp = cmmCodeGrpRepository.findByCodeGrpId(savedCmmCodeGrp.getCodeGrpId()); + System.out.println(qCmmCodeGrp); + + + + CmmCodeL cmmCodeL = CmmCodeL.builder() + .codeGrpId("G_CODE_SAM") + .codeCd("L0001") + .codeNm("대분류코드예제") + .codeRemark("대분류코드설명") + .codeOrdr(99) + .build(); + CmmCodeL savedCmmCodeL = cmmCodeLRepository.save(cmmCodeL); + //List cmmCodeLList = cmmCodeLRepository.findByCodeGrpIdAndUseYnOrderByCodeOrdr(savedCmmCodeL.getCodeGrpId(), savedCmmCodeL.getUseYn()); + //System.out.println(cmmCodeLList); + + List comboCodeDtoList = cmmCodeLRepository.queryComboCode(savedCmmCodeL.getCodeGrpId()); + System.out.println(comboCodeDtoList); + + CmmCodeM cmmCodeM = CmmCodeM.builder() + .codeGrpId("G_CODE_SAM") + .codeLcd("L0001") + .codeCd("M001") + .codeNm("중분류코드예제") + .codeRemark("중분류코드설명") + .codeOrdr(1) + .build(); + CmmCodeM savedCmmCodeM = cmmCodeMRepository.save(cmmCodeM); + + CmmCodeS cmmCodeS = CmmCodeS.builder() + .codeGrpId("G_CODE_SAM") + .codeLcd("L0001") + .codeMcd("M001") + .codeCd("01") + .codeNm("소분류코드예제") + .codeRemark("소분류코드설명") + .codeOrdr(11) + .build(); + CmmCodeS savedCmmCodeS = cmmCodeSRepository.save(cmmCodeS); + + //List list = cmmCodeSRepository.findByCodeGrpIdAndCodeLcdAndCodeMcdAndUseYnOrderByCodeOrdr(savedCmmCodeS.getCodeGrpId(), savedCmmCodeS.getCodeLcd(), savedCmmCodeS.getCodeMcd(), savedCmmCodeS.getUseYn()); + //System.out.println(list); + + List comboCodeDtos = cmmCodeSRepository.queryComboCode(savedCmmCodeS.getCodeGrpId(), savedCmmCodeS.getCodeLcd(), savedCmmCodeS.getCodeMcd()); + System.out.println(comboCodeDtos); + +// cmmFileDtlIds.setFileMstId(savedCmmFileMst.getFileMstId()); +// //cmmFileDtlIds.setFileId(UUID.randomUUID().toString().replaceAll("-", "")); +// CmmFileDtl cmmFileDtl = CmmFileDtl.builder() +// .fileMstId(cmmFileDtlIds.getFileMstId()) +// .fileId(cmmFileDtlIds.getFileId()) +// .orgFileNm("aaa.jpg") +// .contentType("imeg/jpeg") +// .fileSize(200000l) +// .fileExt("jpg") +// .build(); +// +// CmmFileDtl savedCmmFileDtl = cmmFileDtlRepository.save(cmmFileDtl); +// +// cmmFileDtlRepository.findById(cmmFileDtlIds); + } + + @Test + void specs(){ + + Specification spec = Specification.where(CmmCodeSpecification.eqCodeGrpId("G_CODE_GRP")); + spec.and(CmmCodeSpecification.eqCodeLcd("00001")); +// +// if(CmmCodeDto) +// Specification.where(TodoSpecification.equalTodoId(todoId)); + cmmCodeSRepository.findAll(spec, PageRequest.of(0, 10)); + } + + @Test + void exam(){ + + CmmCodeS codeS = CmmCodeS.builder() + .codeGrpId("C_CODE") + .codeLcd("00001") + .build(); + + ExampleMatcher exampleMatcher = ExampleMatcher.matchingAny(); + //.withIgnorePaths("up", "down"); + + Example example = Example.of(codeS, exampleMatcher); + cmmCodeSRepository.findAll(example); + } +} \ No newline at end of file diff --git a/src/test/java/com/xit/biz/cmm/repository/ICmmFileRepositoryTest.java b/src/test/java/com/xit/biz/cmm/repository/ICmmFileRepositoryTest.java new file mode 100644 index 0000000..8ee4f2a --- /dev/null +++ b/src/test/java/com/xit/biz/cmm/repository/ICmmFileRepositoryTest.java @@ -0,0 +1,60 @@ +package com.xit.biz.cmm.repository; + +import com.xit.biz.cmm.entity.CmmFileDtl; +import com.xit.biz.cmm.entity.CmmFileMst; +import com.xit.biz.cmm.entity.ids.CmmFileDtlIds; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import java.util.UUID; + +@DataJpaTest +class ICmmFileRepositoryTest { + + @Autowired + private ICmmFileMstRepository cmmFileMstRepository; + + @Autowired + private ICmmFileDtlRepository cmmFileDtlRepository; + + @BeforeEach + void setUp() { + } + + @AfterEach + void tearDown() { + } + + @Test + void crud(){ + CmmFileMst cmmFileMst = CmmFileMst.builder() + .fileMstId(UUID.randomUUID().toString().replaceAll("-", "")) + .fileCtgCd("BOARD") + .fileBizId("BOARD1") + .build() + ; + + + + + CmmFileMst savedCmmFileMst = cmmFileMstRepository.save(cmmFileMst); + CmmFileDtlIds cmmFileDtlIds = new CmmFileDtlIds(); + cmmFileDtlIds.setFileMstId(savedCmmFileMst.getFileMstId()); + //cmmFileDtlIds.setFileId(UUID.randomUUID().toString().replaceAll("-", "")); + CmmFileDtl cmmFileDtl = CmmFileDtl.builder() + .fileMstId(cmmFileDtlIds.getFileMstId()) + .fileId(cmmFileDtlIds.getFileId()) + .orgFileNm("aaa.jpg") + .contentType("imeg/jpeg") + .fileSize(200000l) + .fileExt("jpg") + .build(); + + CmmFileDtl savedCmmFileDtl = cmmFileDtlRepository.save(cmmFileDtl); + + cmmFileDtlRepository.findById(cmmFileDtlIds); + } +} \ No newline at end of file diff --git a/src/test/java/com/xit/biz/cmm/repository/ICmmUserRepositoryTest.java b/src/test/java/com/xit/biz/cmm/repository/ICmmUserRepositoryTest.java new file mode 100644 index 0000000..b766437 --- /dev/null +++ b/src/test/java/com/xit/biz/cmm/repository/ICmmUserRepositoryTest.java @@ -0,0 +1,74 @@ +package com.xit.biz.cmm.repository; + +import com.xit.biz.cmm.entity.CmmRole; +import com.xit.biz.cmm.entity.CmmUser; +import com.xit.core.oauth2.oauth.entity.ProviderType; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@ExtendWith(SpringExtension.class) +@DataJpaTest +class ICmmUserRepositoryTest { + + @Autowired + ICmmUserRepository userRepository; + @Autowired + ICmmRoleRepository roleRepository; + + private CmmUser user; + private List roles; + + @BeforeEach + void setUp() { + // given + this.user = CmmUser.builder() + .providerType(ProviderType.LOCAL) + .userId("minuk926") + .userName("임종욱") + .password("minuk926") + .build(); + this.roles = Arrays.asList( + CmmRole.builder() + .roleName("ROLE_1") + .roleRemark("ROLE_1_REMARK") + .build(), + CmmRole.builder() + .roleName("ROLE_2") + .roleRemark("ROLE_2_REMARK") + .build() + + ); + + } + + @AfterEach + void tearDown() { + } + + @Test + void createTest(){ + // when + userRepository.save(user); + //List savedRols = roleRepository.saveAll(roles); + + // then + //assertEquals(user.getUserId(), savedRols.get(0).getRoleId()); + + } + + @Test + void findByCmmUserId() { + } + + @Test + void findByUserId() { + } +} \ No newline at end of file diff --git a/src/test/java/com/xit/biz/cmm/user/ApiTests.java b/src/test/java/com/xit/biz/cmm/user/ApiTests.java new file mode 100644 index 0000000..97bca8e --- /dev/null +++ b/src/test/java/com/xit/biz/cmm/user/ApiTests.java @@ -0,0 +1,42 @@ +//package com.xit.biz.cmm.user; +// +//import com.xit.biz.cmm.domain.CmmUser; +//import org.assertj.core.api.Assertions; +//import org.junit.jupiter.api.Test; +//import org.springframework.http.HttpStatus; +//import org.springframework.http.ResponseEntity; +//import org.springframework.web.client.HttpClientErrorException; +//import org.springframework.web.client.RestTemplate; +// +//public class ApiTests { +// +// @Test +// public void testCreateReadDelete() { +// RestTemplate restTemplate = new RestTemplate(); +// +// String url = "http://localhost:8080/api/cmm/user"; +// +// CmmUser cmmUser = new CmmUser(null, "minuk926", "임종욱", "password", "minuk926", null, null, null); +// ResponseEntity entity = restTemplate.postForEntity(url, cmmUser, CmmUser.class); +// +// CmmUser[] employees = restTemplate.getForObject(url, CmmUser[].class); +// Assertions.assertThat(employees).extracting(CmmUser::getUserName).containsOnly("임종욱"); +// +// restTemplate.delete(url + "/" + entity.getBody().getCmmUserId()); +// Assertions.assertThat(restTemplate.getForObject(url, CmmUser[].class)).isEmpty(); +// } +// +// @Test +// public void testErrorHandlingReturnsBadRequest() { +// +// RestTemplate restTemplate = new RestTemplate(); +// +// String url = "http://localhost:8080/wrong"; +// +// try { +// restTemplate.getForEntity(url, String.class); +// } catch (HttpClientErrorException e) { +// Assertions.assertThat(e.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); +// } +// } +//} diff --git a/src/test/java/com/xit/biz/cmm/user/IntegrationTests.java b/src/test/java/com/xit/biz/cmm/user/IntegrationTests.java new file mode 100644 index 0000000..0f91541 --- /dev/null +++ b/src/test/java/com/xit/biz/cmm/user/IntegrationTests.java @@ -0,0 +1,46 @@ +//package com.xit.biz.cmm.user; +// +//import com.xit.biz.cmm.controller.CmmUserController; +//import com.xit.biz.cmm.domain.CmmUser; +//import org.assertj.core.api.Assertions; +//import org.junit.jupiter.api.Test; +//import org.junit.jupiter.api.extension.ExtendWith; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.test.context.SpringBootTest; +//import org.springframework.test.context.junit.jupiter.SpringExtension; +// +//import javax.xml.bind.ValidationException; +// +//@ExtendWith(SpringExtension.class) +//@SpringBootTest +//public class IntegrationTests { +// +// @Autowired +// CmmUserController cmmUserController; +// +// final CmmUser cmmUser = CmmUser.builder() +// .cmmUserId(1L) +// .userId("minuk926") +// .userName("임종욱") +// .passWord("password") +// .insUserId("minuk926") +// .build(); +// +// @Test +// public void testCreateReadDelete() { +// CmmUser rsltUser = cmmUserController.save(cmmUser); +// +// Iterable cmmUsers = cmmUserController.findAll(); +// Assertions.assertThat(cmmUsers).first().hasFieldOrPropertyWithValue("userId", "minuk926"); +// +// cmmUserController.deleteById(rsltUser.getCmmUserId()); +// Assertions.assertThat(cmmUserController.findAll()).is.isEmpty(); +// } +// +// @Test +// public void errorHandlingValidationExceptionThrown() { +// +// Assertions.assertThatExceptionOfType(ValidationException.class) +// .isThrownBy(() -> cmmUserController.somethingIsWrong()); +// } +//} \ No newline at end of file diff --git a/src/test/java/com/xit/biz/cmm/user/RepositoryTests.java b/src/test/java/com/xit/biz/cmm/user/RepositoryTests.java new file mode 100644 index 0000000..f82aec0 --- /dev/null +++ b/src/test/java/com/xit/biz/cmm/user/RepositoryTests.java @@ -0,0 +1,35 @@ +//package com.xit.biz.cmm.user; +// +//import com.xit.biz.cmm.domain.CmmUser; +//import com.xit.biz.cmm.domain.CmmUserRepository; +//import org.assertj.core.api.Assertions; +//import org.junit.jupiter.api.Test; +//import org.junit.jupiter.api.extension.ExtendWith; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +//import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +//import org.springframework.test.context.junit.jupiter.SpringExtension; +// +//@ExtendWith(SpringExtension.class) +//@DataJpaTest +//@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +//public class RepositoryTests { +// +// @Autowired +// CmmUserRepository cmmUserRepository; +// +// @Test +// public void testCreateReadDelete() { +// CmmUser cmmUser = new CmmUser(null, "minuk926", "임종욱", "password", "minuk926", null, null, null); +// +// +// cmmUserRepository.save(cmmUser); +// +// Iterable employees = cmmUserRepository.findAll(); +// Assertions.assertThat(employees).extracting(CmmUser::getUserName).containsOnly("임종욱"); +// +// cmmUserRepository.deleteAll(); +// Assertions.assertThat(cmmUserRepository.findAll()).isEmpty(); +// } +//} +// diff --git a/src/test/java/com/xit/biz/cmm/user/ServiceTests.java b/src/test/java/com/xit/biz/cmm/user/ServiceTests.java new file mode 100644 index 0000000..a59ff50 --- /dev/null +++ b/src/test/java/com/xit/biz/cmm/user/ServiceTests.java @@ -0,0 +1,58 @@ +//package com.xit.biz.cmm.user; +// +//import static org.junit.jupiter.api.Assertions.assertEquals; +//import static org.mockito.Mockito.times; +//import static org.mockito.Mockito.verify; +//import static org.mockito.Mockito.when; +// +//import java.util.ArrayList; +//import java.util.List; +// +//import com.xit.biz.cmm.domain.CmmUser; +//import com.xit.biz.cmm.domain.CmmUserRepository; +//import org.junit.jupiter.api.Test; +//import org.junit.jupiter.api.extension.ExtendWith; +//import org.mockito.InjectMocks; +//import org.mockito.Mock; +//import org.mockito.junit.jupiter.MockitoExtension; +// +//@ExtendWith(MockitoExtension.class) +//public class ServiceTests { +// +// @InjectMocks +// CmmUserService service; +// +// @Mock +// CmmUserRepository dao; +// +// @Test +// public void testFindAllEmployees() +// { +// List list = new ArrayList(); +// CmmUser empOne = new CmmUser(null, "minuk9261", "임종욱1", "password1", "minuk9261", null, null, null); +// CmmUser empTwo = new CmmUser(null, "minuk9262", "임종욱2", "password2", "minuk9262", null, null, null); +// CmmUser empThree = new CmmUser(null, "minuk9263", "임종욱3", "password3", "minuk9263", null, null, null); +// +// list.add(empOne); +// list.add(empTwo); +// list.add(empThree); +// +// when(dao.findAll()).thenReturn(list); +// +// //test +// List empList = service.findAll(); +// +// assertEquals(3, empList.size()); +// verify(dao, times(1)).findAll(); +// } +// +// @Test +// public void testCreateOrSaveEmployee() +// { +// CmmUser cmmUser = new CmmUser(null, "minuk9261", "임종욱1", "password1", "minuk9261", null, null, null); +// +// service.save(cmmUser); +// +// verify(dao, times(1)).save(employee); +// } +//} diff --git a/src/test/java/com/xit/biz/cmm/user/StandaloneControllerTests.java b/src/test/java/com/xit/biz/cmm/user/StandaloneControllerTests.java new file mode 100644 index 0000000..053b1e7 --- /dev/null +++ b/src/test/java/com/xit/biz/cmm/user/StandaloneControllerTests.java @@ -0,0 +1,44 @@ +//package com.xit.biz.cmm.user; +// +//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +// +//import java.util.Arrays; +//import java.util.List; +// +//import com.xit.biz.cmm.controller.CmmUserController; +//import com.xit.biz.cmm.domain.CmmUser; +//import org.hamcrest.Matchers; +//import org.junit.jupiter.api.Test; +//import org.junit.jupiter.api.extension.ExtendWith; +//import org.mockito.Mockito; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +//import org.springframework.boot.test.mock.mockito.MockBean; +//import org.springframework.test.context.junit.jupiter.SpringExtension; +//import org.springframework.test.web.servlet.MockMvc; +// +//@ExtendWith(SpringExtension.class) +//@WebMvcTest(CmmUserController.class) +//public class StandaloneControllerTests { +// +// @MockBean +// CmmUserService cmmUserService; +// +// @Autowired +// MockMvc mockMvc; +// +// @Test +// public void testfindAll() throws Exception { +// CmmUser cmmUser = new CmmUser(null, "minuk9261", "임종욱1", "password1", "minuk9261", null, null, null); +// List cmmUsers = Arrays.asList(cmmUser); +// +// Mockito.when(cmmUserService.findAll()).thenReturn(cmmUsers); +// +// mockMvc.perform(get("/api/cmm/user")) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$", Matchers.hasSize(1))) +// .andExpect(jsonPath("$[0].userName", Matchers.is("임종욱1"))); +// } +//} diff --git a/src/test/java/com/xit/core/oauth2/api/controller/OAuth2LocalControllerTest.java b/src/test/java/com/xit/core/oauth2/api/controller/OAuth2LocalControllerTest.java new file mode 100644 index 0000000..8eb8a40 --- /dev/null +++ b/src/test/java/com/xit/core/oauth2/api/controller/OAuth2LocalControllerTest.java @@ -0,0 +1,188 @@ +package com.xit.core.oauth2.api.controller; + +import com.xit.biz.cmm.dto.CmmUserDto; +import com.xit.biz.cmm.dto.struct.CmmUserMapstruct; +import com.xit.biz.cmm.entity.CmmRole; +import com.xit.biz.cmm.entity.CmmUser; +import com.xit.biz.cmm.repository.ICmmRoleRepository; +import com.xit.biz.cmm.repository.ICmmUserRepository; +import com.xit.core.api.IRestResponse; +import com.xit.core.oauth2.api.dto.LoginRequestDto; +import com.xit.core.oauth2.api.dto.TokenDto; +import com.xit.core.oauth2.api.service.IAuthService; +import com.xit.core.oauth2.oauth.entity.ProviderType; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.runner.RunWith; +import org.mapstruct.factory.Mappers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.http.ResponseEntity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MockMvcBuilder; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.filter.CharacterEncodingFilter; + +import java.util.Arrays; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +//@RunWith(SpringRunner.class) +//@RunWith(MockitoJUnitRunner.class) +//@ExtendWith(SpringExtension.class) +//@DataJpaTest +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +class OAuth2LocalControllerTest { + + CmmUser user = CmmUser.builder() + .providerType(ProviderType.LOCAL) + .userId("minuk926") + .userName("임종욱") + .password("Minuk926!@") + .build(); + + + @Autowired + MockMvc mvc; + + //@InjectMocks + //private CmmUserMapstruct mapstruct = CmmUserMapstructImpl.INSTANCE; + @Autowired + ICmmUserRepository userRepository; + //@Autowired + //ICmmRoleRepository roleRepository; + + //private CmmUserMapstruct cmmUserMapstruct = Mappers.getMapper(CmmUserMapstruct.class); + + @BeforeEach + void setUp() { + CmmUser user = CmmUser.builder() + .providerType(ProviderType.LOCAL) + .userId("minuk926") + .userName("임종욱") + .password("Minuk926!@") + .build(); + user.setCmmUserId(UUID.randomUUID().toString().replaceAll("-", "")); + CmmUser cmmUser = userRepository.save(user); + + // userRepository.save(user); + } + + @AfterEach + void tearDown() { + } + + + @Test + void login() throws Exception { + +// CmmUserMapstruct mapper = Mappers.getMapper(CmmUserMapstruct.class); +// +// // we have some built other ArchUnit-checks that make sure we can rely on this filter +// final long mapperCount = new ClassFileImporter().importPackages("com.demo.service.mapper") +// .stream() +// .filter(javaClass -> javaClass.getSimpleName().endsWith("Mapper")) +// .count(); +// +// final List mappers = List.of(ACCOUNT_MAPPER, PERSON_MAPPER, INVOICE_MAPPER); +// +// assertThat(mappers) +// .withFailMessage("Scanned number of mappers doesn't match the provided amount") +// .hasSize(Long.valueOf(mapperCount).intValue()); +// +// for (Object mapper : mappers) { +// test(mapper, easyRandom); +// } + + //CmmUserDto cmmUser = cmmUserMapstruct.toDto(user); +// + //user.setCmmUserId(UUID.randomUUID().toString().replaceAll("-", "")); + //CmmUser cmmUser = userRepository.save(user); + + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("providerType", ProviderType.LOCAL.name()); + params.add("userId", "minuk926"); + params.add("password", "Minuk926!@"); + mvc.perform(post("/api/vi/auth/login").params(params)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.data").exists()) + //.andExpect(jsonPath("$.success").value(true)) + //.andExpect(jsonPath("$.success").value(true)) + ; + } + + @Test + void reissueFromHeader() { + } + + @Test + void reissueFromCookie() { + } + + @Test + void reissueFromParam() { + } + + @Test + void validate() { + } + + @Test + void findAccessTokenInfo() { + } + + @Test + void testLogin() { + } + + @Test + void testReissueFromHeader() { + } + + @Test + void testReissueFromCookie() { + } + + @Test + void testReissueFromParam() { + } + + @Test + void testValidate() { + } + + @Test + void testFindAccessTokenInfo() { + } +} \ No newline at end of file diff --git a/src/test/java/com/xit/core/oauth2/api/dto/LoginRequestDtoTest.java b/src/test/java/com/xit/core/oauth2/api/dto/LoginRequestDtoTest.java new file mode 100644 index 0000000..d4e53a2 --- /dev/null +++ b/src/test/java/com/xit/core/oauth2/api/dto/LoginRequestDtoTest.java @@ -0,0 +1,83 @@ +package com.xit.core.oauth2.api.dto; + +import com.xit.core.oauth2.oauth.entity.ProviderType; +import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.validation.ConstraintViolation; + +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +// test runner를 통해 Junit Framework 호출 +// SpringRunner는 SpringJunit4ClassRunner의 단축어 +// @Autowired @MockBean에 해당 하는 것들에만 application context loading +// 1. component가 주입 되어야 할 때 +// 2. 설정 데이타가 주입되어야 할 때 +// 3. Spring과 관련 없는 기능 테스트시 금지(SpringRunner는 overhead가 크다) +// @RunWith(SpringRunner.class) +// @RunWith(SpringRunner.class) : Junit4, @RunWith(MockitoJUnitRunner.class) : Junit 5 +// @Mock : Mock 객체 생성 - mapper에 사용 +// @InjectMocks : Mock 객체 주입 - service 에 사용 +//@RunWith(SpringRunner.class) +//@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) + +// 1. 메모리 데이타 베이스 설정 +// 2. @Entity class scan +// 3. Spring Data JAP Repository 설정 +// 4. 테스트 종료시 Rollback +//@DataJpaTest + +// Junit5 +// Spring Test & Spring Boot Test +// AssertJ +// Hamcrest +// Mockito +// JSONassert +// JsonPath +// @ExtendWith(SpringExtention.class)도 적용 되어있어 생략 가능 +// Junit4 사용시 @SpringBootTest는 SpringJUnit4ClassRunner를 상속받는 @RunWhith(SpringRynver.class)와 함께 사용 +//@SpringBootTest +public class LoginRequestDtoTest { + + @BeforeEach + void setUp() { + } + + @AfterEach + void tearDown() { + } + + @Test + public void whenAllInvalid_thenViolationsShouldBeReported() { + LoginRequestDto dto = new LoginRequestDto(); + //dto.setProviderType(ProviderType.GOOGLE.name()); + //customer.setCustomerTypeOfSubset(CustomerType.DEFAULT); + //customer.setCustomerTypeMatchesPattern(CustomerType.OLD); + +// Set violations = validator.validate(customer); + + // assertThat(violations.size()).isEqualTo(3); + +// assertThat(violations) +// .anyMatch(havingPropertyPath("customerTypeString") +// .and(havingMessage("must be any of enum class com.baeldung.javaxval.enums.demo.CustomerType"))); +// assertThat(violations) +// .anyMatch(havingPropertyPath("customerTypeOfSubset") +// .and(havingMessage("must be any of [NEW, OLD]"))); +// assertThat(violations) +// .anyMatch(havingPropertyPath("customerTypeMatchesPattern") +// .and(havingMessage("must match \"NEW|DEFAULT\""))); + } + +} \ No newline at end of file diff --git a/src/test/java/com/xit/core/resolver/TestActiveProfileResolver.java b/src/test/java/com/xit/core/resolver/TestActiveProfileResolver.java new file mode 100644 index 0000000..8a38321 --- /dev/null +++ b/src/test/java/com/xit/core/resolver/TestActiveProfileResolver.java @@ -0,0 +1,19 @@ +package com.xit.core.resolver; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.lang.NonNull; +import org.springframework.test.context.ActiveProfilesResolver; + +@Slf4j +public class TestActiveProfileResolver implements ActiveProfilesResolver { + + @Override + public @NonNull String[] resolve(@NonNull Class testClass) { + String profile = System.getProperty("profile"); + if (profile == null) { + profile = "local"; + log.warn("Default spring profiles active is null. use {} profile.", profile); + } + return new String[]{profile}; + } +} \ No newline at end of file