From 64e4b079537623b1bcb0ccc604aad2a3bfb89ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EC=84=9D=20=EC=B5=9C?= Date: Fri, 2 Feb 2024 16:50:54 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A7=8C=EB=A3=8C=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=9E=AC=EB=B0=9C=EA=B8=89=20=EC=A0=88=EC=B0=A8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95.=20=EA=B8=B0=EC=A4=80=EC=BD=94=EB=93=9C=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=A6=90=EA=B2=A8?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/api/egovFetch.js | 118 +++++++++--------- .../pages/standardCode/list/FavoriteIcon.jsx | 46 +++++++ .../standardCode/list/StandardCodeList.jsx | 5 +- .../dbnt/kcscbackend/auth/entity/LoginVO.java | 3 + .../config/jwt/JwtAuthenticationFilter.java | 1 + .../standardCode/StandardCodeController.java | 31 ++++- .../entity/TnDocumentCodeList.java | 2 + .../entity/TnDocumentFavorites.java | 40 ++++++ .../standardCode/entity/TnDocumentInfo.java | 2 + .../TnDocumentFavoritesRepository.java | 8 ++ .../service/StandardCodeService.java | 18 ++- .../mybatisMapper/StandardCodeMapper.xml | 11 +- 12 files changed, 218 insertions(+), 67 deletions(-) create mode 100644 egovframe-template-simple-react-contribution/src/pages/standardCode/list/FavoriteIcon.jsx create mode 100644 kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/entity/TnDocumentFavorites.java create mode 100644 kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/repository/TnDocumentFavoritesRepository.java diff --git a/egovframe-template-simple-react-contribution/src/api/egovFetch.js b/egovframe-template-simple-react-contribution/src/api/egovFetch.js index f634fb1..4ed6da9 100644 --- a/egovframe-template-simple-react-contribution/src/api/egovFetch.js +++ b/egovframe-template-simple-react-contribution/src/api/egovFetch.js @@ -29,65 +29,67 @@ export function requestFetch(url, requestOptions, handler, errorHandler) { const accessToken = getLocalItem('accessToken'); const sessionUser = parseJwt(accessToken); const sessionUserId = sessionUser?.id || null; - const refreshToken = getLocalItem('refreshToken'); - if(sessionUserId != null){ - if( !requestOptions['headers'] ) requestOptions['headers']={} - if( !requestOptions['headers']['Authorization'] ) requestOptions['headers']['Authorization']=null; - requestOptions['headers']['Authorization'] = accessToken; + + if(accessToken && new Date(sessionUser.exp*1000) < new Date()){ + //만료된 토큰 재발급 절차 진행. + accessTokenRefresh(url, requestOptions, handler, errorHandler); + }else{ + if(sessionUserId != null){ + if( !requestOptions['headers'] ) requestOptions['headers']={} + if( !requestOptions['headers']['Authorization'] ) requestOptions['headers']['Authorization']=null; + requestOptions['headers']['Authorization'] = accessToken; + } + + //CORS ISSUE 로 인한 조치 - origin 및 credentials 추가 + // origin 추가 + if (!requestOptions['origin']) { + requestOptions = { ...requestOptions, origin: SERVER_URL }; + } + // credentials 추가 + if (!requestOptions['credentials']) { + requestOptions = { ...requestOptions, credentials: 'include' }; + } + + fetch(SERVER_URL + url, requestOptions) + .then(response => {// response Stream. Not completion object + return response.json(); + }) + .then((resp) => { + if (Number(resp.resultCode) === Number(CODE.RCV_ERROR_AUTH)) { + alert("로그인이 해제되었습니다.") + window.location.href = "/login" + }else{ + return resp; + } + }) + .then((resp) => { + console.groupCollapsed("requestFetch.then()"); + console.log("requestFetch [response] ", resp); + if (typeof handler === 'function') { + handler(resp); + } else { + console.log('egov fetch handler not assigned!'); + } + console.groupEnd("requestFetch.then()"); + }) + .catch(error => { + console.error('There was an error!', error); + if (error === 'TypeError: Failed to fetch') { + alert("서버와의 연결이 원활하지 않습니다. 서버를 확인하세요."); + } + + if (typeof errorHandler === 'function') { + errorHandler(error); + } else { + console.error('egov error handler not assigned!'); + alert("ERR : " + error.message); + } + }) + .finally(() => { + console.log("requestFetch finally end"); + console.groupEnd("requestFetch"); + }); } - - //CORS ISSUE 로 인한 조치 - origin 및 credentials 추가 - // origin 추가 - if (!requestOptions['origin']) { - requestOptions = { ...requestOptions, origin: SERVER_URL }; - } - // credentials 추가 - if (!requestOptions['credentials']) { - requestOptions = { ...requestOptions, credentials: 'include' }; - } - - fetch(SERVER_URL + url, requestOptions) - .then(response => {// response Stream. Not completion object - //console.log("requestFetch [Response Stream] ", response); - return response.json(); - }) - .then((resp) => { - if (Number(resp.resultCode) === Number(CODE.RCV_ERROR_AUTH)) { - //accessToken 갱신 요청 - accessTokenRefresh(url, requestOptions, handler, errorHandler); - return resp; - } else { - return resp; - } - }) - .then((resp) => { - console.groupCollapsed("requestFetch.then()"); - console.log("requestFetch [response] ", resp); - if (typeof handler === 'function') { - handler(resp); - } else { - console.log('egov fetch handler not assigned!'); - } - console.groupEnd("requestFetch.then()"); - }) - .catch(error => { - console.error('There was an error!', error); - if (error === 'TypeError: Failed to fetch') { - alert("서버와의 연결이 원활하지 않습니다. 서버를 확인하세요."); - } - - if (typeof errorHandler === 'function') { - errorHandler(error); - } else { - console.error('egov error handler not assigned!'); - alert("ERR : " + error.message); - } - }) - .finally(() => { - console.log("requestFetch finally end"); - console.groupEnd("requestFetch"); - }); - } function accessTokenRefresh(url, requestOptions, handler, errorHandler){ diff --git a/egovframe-template-simple-react-contribution/src/pages/standardCode/list/FavoriteIcon.jsx b/egovframe-template-simple-react-contribution/src/pages/standardCode/list/FavoriteIcon.jsx new file mode 100644 index 0000000..4a59686 --- /dev/null +++ b/egovframe-template-simple-react-contribution/src/pages/standardCode/list/FavoriteIcon.jsx @@ -0,0 +1,46 @@ + +import React, {useState} from "react"; +import {AiFillStar} from "react-icons/ai"; +import {getLocalItem} from "utils/storage"; +import * as EgovNet from "../../../api/egovFetch"; + +function FavoriteIcon({item}){ + + const [favoriteChk, setFavoriteChk] = useState(item.favoriteChk); + + function favoriteStateChange(groupSeq, checked){ + EgovNet.requestFetch( + '/standardCode/document-favorite', + { + method: "POST", + headers: { + 'Content-type': 'application/json', + }, + body:JSON.stringify({groupSeq: groupSeq, active: checked}) + }, + (resp) => { + + }, + function (resp) { + console.log("err response : ", resp); + } + ); + } + + return ( +
{ + const accessToken = getLocalItem('accessToken') + if(accessToken) { + favoriteStateChange(item.groupSeq, !favoriteChk) + setFavoriteChk(!favoriteChk) + }else{ + alert("로그인 후 이용 가능한 서비스 입니다.") + } + }}> + +
+ ); +} + +export default FavoriteIcon; \ No newline at end of file diff --git a/egovframe-template-simple-react-contribution/src/pages/standardCode/list/StandardCodeList.jsx b/egovframe-template-simple-react-contribution/src/pages/standardCode/list/StandardCodeList.jsx index f59d217..7e1c1cf 100644 --- a/egovframe-template-simple-react-contribution/src/pages/standardCode/list/StandardCodeList.jsx +++ b/egovframe-template-simple-react-contribution/src/pages/standardCode/list/StandardCodeList.jsx @@ -1,8 +1,7 @@ import React from 'react'; - -import {AiFillStar} from "react-icons/ai"; import Col from "react-bootstrap/Col"; import Row from "react-bootstrap/Row"; +import FavoriteIcon from "./FavoriteIcon"; function StandardCodeList({listData, filterData}) { @@ -76,7 +75,7 @@ function StandardCodeList({listData, filterData}) { })} -
+ ) })} diff --git a/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/auth/entity/LoginVO.java b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/auth/entity/LoginVO.java index b3eb27c..52b7c3d 100644 --- a/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/auth/entity/LoginVO.java +++ b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/auth/entity/LoginVO.java @@ -34,6 +34,9 @@ public class LoginVO implements Serializable{ * */ private static final long serialVersionUID = -8274004534207618049L; + + @Schema(description = "사용자 번호") + private Integer userSeq; @Schema(description = "아이디") @Pattern(regexp = "^[a-zA-Z]{1}[a-zA-Z0-9_]{4,11}$") diff --git a/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/config/jwt/JwtAuthenticationFilter.java b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/config/jwt/JwtAuthenticationFilter.java index 0615647..00287f9 100644 --- a/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/config/jwt/JwtAuthenticationFilter.java +++ b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/config/jwt/JwtAuthenticationFilter.java @@ -67,6 +67,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { LoginVO loginVO = new LoginVO(); if( verificationFlag ){ logger.debug("jwtToken validated"); + loginVO.setUserSeq(Integer.parseInt(jwtTokenUtil.getUserSeqFromToken(jwtToken))); loginVO.setId(id); loginVO.setUserSe( jwtTokenUtil.getUserSeFromToken(jwtToken) ); // loginVO.setUniqId( jwtTokenUtil.getInfoFromToken("uniqId",jwtToken) ); diff --git a/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/StandardCodeController.java b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/StandardCodeController.java index 8bbb018..e1bf2bb 100644 --- a/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/StandardCodeController.java +++ b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/StandardCodeController.java @@ -6,6 +6,7 @@ import com.dbnt.kcscbackend.config.common.ResponseCode; import com.dbnt.kcscbackend.config.common.ResultVO; import com.dbnt.kcscbackend.standardCode.entity.TnDocumentCodeList; import com.dbnt.kcscbackend.standardCode.entity.TnDocumentContent; +import com.dbnt.kcscbackend.standardCode.entity.TnDocumentFavorites; import com.dbnt.kcscbackend.standardCode.entity.TnDocumentInfo; import com.dbnt.kcscbackend.standardCode.service.StandardCodeService; import com.dbnt.kcscbackend.standardCode.service.StandardCodeVO; @@ -152,9 +153,9 @@ public class StandardCodeController extends BaseController { Map resultMap = new HashMap<>(); tnDocumentInfo.makeListCode(); + tnDocumentInfo.setUserSeq(user.getUserSeq()); resultMap.put("resultList", standardCodeService.selectStandardCodeList(tnDocumentInfo)); resultMap.put("resultCnt", standardCodeService.selectStandardCodeListCnt(tnDocumentInfo)); - resultMap.put("user", user); resultVO.setResultCode(ResponseCode.SUCCESS.getCode()); resultVO.setResultMessage(ResponseCode.SUCCESS.getMessage()); @@ -184,6 +185,34 @@ public class StandardCodeController extends BaseController { return resultVO; } + @Operation( + summary = "건설기준코드 즐겨찾기 추가, 삭제", + description = "건설기준코드 즐겨찾기 추가, 삭제", + tags = {"StandardCodeController"} + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "수정 성공"), + @ApiResponse(responseCode = "403", description = "인가된 사용자가 아님") + }) + @PostMapping(value = "/document-favorite") + public ResultVO setDocumentFavorite(@RequestBody TnDocumentFavorites favorites, @AuthenticationPrincipal LoginVO loginUser) throws Exception { + ResultVO resultVO = new ResultVO(); + if(favorites.getActive()==null){ + resultVO.setResultCode(ResponseCode.SAVE_ERROR.getCode()); + resultVO.setResultMessage(ResponseCode.SAVE_ERROR.getMessage()); + return resultVO; + } + + if(favorites.getActive()){ + standardCodeService.saveFavorites(loginUser.getUserSeq(), favorites.getGroupSeq()); + }else{ + standardCodeService.deleteFavorites(loginUser.getUserSeq(), favorites.getGroupSeq()); + } + resultVO.setResultCode(ResponseCode.SUCCESS.getCode()); + resultVO.setResultMessage(ResponseCode.SUCCESS.getMessage()); + return resultVO; + } + @Operation( summary = "건설기준코드 개정이력 조회", description = "건설기준코드 개정이력 조회", diff --git a/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/entity/TnDocumentCodeList.java b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/entity/TnDocumentCodeList.java index 04e2bd9..e2974ab 100644 --- a/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/entity/TnDocumentCodeList.java +++ b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/entity/TnDocumentCodeList.java @@ -45,6 +45,8 @@ public class TnDocumentCodeList { @Transient private Integer remarkCnt; @Transient + private Boolean favoriteChk; + @Transient private List historyList; } diff --git a/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/entity/TnDocumentFavorites.java b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/entity/TnDocumentFavorites.java new file mode 100644 index 0000000..60deec4 --- /dev/null +++ b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/entity/TnDocumentFavorites.java @@ -0,0 +1,40 @@ +package com.dbnt.kcscbackend.standardCode.entity; + +import lombok.*; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; +import org.springframework.format.annotation.DateTimeFormat; + +import javax.persistence.*; +import java.io.Serializable; +import java.time.LocalDateTime; + +@Getter +@Setter +@Entity +@NoArgsConstructor +@DynamicInsert +@DynamicUpdate +@Table(name = "tn_document_favorites") +@IdClass(TnDocumentFavorites.TnDocumentFavoritesId.class) +public class TnDocumentFavorites { + @Id + @Column(name = "user_seq") + private Integer userSeq; + @Id + @Column(name = "group_seq") + private Integer groupSeq; + + @Transient + private Boolean active; + + + @Embeddable + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class TnDocumentFavoritesId implements Serializable { + private Integer userSeq; + private Integer groupSeq; + } +} diff --git a/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/entity/TnDocumentInfo.java b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/entity/TnDocumentInfo.java index bd15fc0..8acc2d3 100644 --- a/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/entity/TnDocumentInfo.java +++ b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/entity/TnDocumentInfo.java @@ -100,6 +100,8 @@ public class TnDocumentInfo { private String category2 = ""; @Transient private String category3 = ""; + @Transient + private Integer userSeq; @JsonIgnore public void makeListCode(){ diff --git a/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/repository/TnDocumentFavoritesRepository.java b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/repository/TnDocumentFavoritesRepository.java new file mode 100644 index 0000000..5f40eeb --- /dev/null +++ b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/repository/TnDocumentFavoritesRepository.java @@ -0,0 +1,8 @@ +package com.dbnt.kcscbackend.standardCode.repository; + +import com.dbnt.kcscbackend.standardCode.entity.TnDocumentFavorites; +import org.springframework.data.jpa.repository.JpaRepository; + + +public interface TnDocumentFavoritesRepository extends JpaRepository { +} diff --git a/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/service/StandardCodeService.java b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/service/StandardCodeService.java index 96ff80f..a0fc817 100644 --- a/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/service/StandardCodeService.java +++ b/kcsc-back-end/src/main/java/com/dbnt/kcscbackend/standardCode/service/StandardCodeService.java @@ -1,11 +1,9 @@ package com.dbnt.kcscbackend.standardCode.service; -import com.dbnt.kcscbackend.standardCode.entity.TnDocumentCodeList; -import com.dbnt.kcscbackend.standardCode.entity.TnDocumentContent; -import com.dbnt.kcscbackend.standardCode.entity.TnDocumentGroup; -import com.dbnt.kcscbackend.standardCode.entity.TnDocumentInfo; +import com.dbnt.kcscbackend.standardCode.entity.*; import com.dbnt.kcscbackend.standardCode.mapper.StandardCodeMapper; import com.dbnt.kcscbackend.standardCode.repository.TnDocumentContentRepository; +import com.dbnt.kcscbackend.standardCode.repository.TnDocumentFavoritesRepository; import com.dbnt.kcscbackend.standardCode.repository.TnDocumentGroupRepository; import com.dbnt.kcscbackend.standardCode.repository.TnDocumentInfoRepository; import lombok.RequiredArgsConstructor; @@ -23,6 +21,7 @@ public class StandardCodeService extends EgovAbstractServiceImpl { private final TnDocumentGroupRepository tnDocumentGroupRepository; private final TnDocumentContentRepository tnDocumentContentRepository; private final TnDocumentInfoRepository tnDocumentInfoRepository; + private final TnDocumentFavoritesRepository favoritesRepository; private final StandardCodeMapper standardCodeMapper; @Transactional @@ -74,4 +73,15 @@ public class StandardCodeService extends EgovAbstractServiceImpl { public List selectTnDocumentGroupToGroupFullCdLike(String groupFullCd) { return tnDocumentGroupRepository.findByGroupFullCdLike(groupFullCd+"__"); } + + public void saveFavorites(Integer userSeq, Integer groupSeq){ + TnDocumentFavorites favorites = new TnDocumentFavorites(); + favorites.setUserSeq(userSeq); + favorites.setGroupSeq(groupSeq); + favoritesRepository.save(favorites); + } + + public void deleteFavorites(Integer userSeq, Integer groupSeq){ + favoritesRepository.deleteById(new TnDocumentFavorites.TnDocumentFavoritesId(userSeq, groupSeq)); + } } diff --git a/kcsc-back-end/src/main/resources/mybatisMapper/StandardCodeMapper.xml b/kcsc-back-end/src/main/resources/mybatisMapper/StandardCodeMapper.xml index 994679d..7e8acab 100644 --- a/kcsc-back-end/src/main/resources/mybatisMapper/StandardCodeMapper.xml +++ b/kcsc-back-end/src/main/resources/mybatisMapper/StandardCodeMapper.xml @@ -35,11 +35,20 @@ c.rvsn_remark , c.kcsc_cd, tdi.doc_file_grp_id, - c.group_seq + c.group_seq, + tdf.user_seq is not null as favoriteChk from tn_document_group a inner join tn_document_group b on a.group_seq = b.parent_group_seq inner join tn_document_group c on b.group_seq = c.parent_group_seq left join tn_document_info tdi on c.group_seq = tdi.group_seq + left join tn_document_favorites tdf + on c.group_seq = tdf.group_seq + + and 1=0 + + + and tdf.user_seq = #{userSeq} + order by c.kcsc_cd