From 3caa0f05e2e154c6326a0c3e52b1748699ee4881 Mon Sep 17 00:00:00 2001 From: hykim02 Date: Mon, 17 Mar 2025 00:30:52 +0900 Subject: [PATCH 1/2] Create README.md --- README.md | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..04f0c64 --- /dev/null +++ b/README.md @@ -0,0 +1,89 @@ +
+ logo +

+ 경상국립대 학우들을 위한 교내 정보 제공 카카오톡 챗봇 서비스 +

+ + [서비스 링크](https://pf.kakao.com/_bikxiG)    |    [API 명세서](https://github.com/GNU-connect/.github/wiki/API-%EB%AA%85%EC%84%B8%EC%84%9C)   |   [DB 스키마](https://github.com/GNU-connect/.github/wiki/DB-%EC%8A%A4%ED%82%A4%EB%A7%88)   |   [컨벤션](https://github.com/GNU-connect/.github/wiki/%EC%BB%A8%EB%B2%A4%EC%85%98) + +
2025.03.04 친구 수 2,000명 돌파 🎉 +
2025.02.11 친구 수 1,500명 돌파 🎉 +
2024.10.08 친구 수 1,000명 돌파 🎉 +
2024.08.15 친구 수 500명 돌파 🎉 +
2024.05.27 베타 테스트 시작 +
+

+ +# ⭐️ 프로젝트 기능 소개 +

+ +
+

학교 & 학과 공지사항 조회

+ notice +
+
+ +- 116개의 학과, 169개의 게시판에서 실시간으로 공지사항을 스크래핑하여 제공합니다. +- 사용자는 자신의 단과대학 및 학과를 선택하여 맞춤형 공지사항을 제공받을 수 있습니다. +- 공지사항을 클릭하면 원본 게시글로 이동할 수 있습니다. + +

+ +
+

학식 메뉴 조회

+ diet +
+
+ +- 4개 캠퍼스, 9개의 식당 학식 메뉴를 조회할 수 있습니다. +- 원하는 캠퍼스 및 식당을 선택하면 해당 날짜의 메뉴를 한눈에 확인할 수 있습니다. +- 매주 새로운 학식 메뉴가 자동으로 업데이트됩니다. + +
+

학사일정

+ calendar +
+
+ +- 매월 주요 학사 일정을 한눈에 확인할 수 있습니다. +- 개강, 중간·기말고사, 수강신청 등 중요한 일정을 놓치지 않도록 도와줍니다. +- 학사 일정이 업데이트되면 자동으로 반영됩니다. + +

+ +
+

열람실 좌석

+ library +
+
+ +- 실시간으로 도서관 열람실의 잔여 좌석을 확인할 수 있습니다. +- Selenium 스크린샷 메서드를 활용하여 5분 간격으로 열람실 좌석 정보를 캡쳐하여 Supabase Stroage에 저장합니다. + +

+ +# ⚙️ 프로젝트 구조 + +## 시스템 아키텍처 + +![alt text](https://raw.githubusercontent.com/GNU-connect/.github/refs/heads/main/profile/image/architecture.png) +

+ +## 기술 스택 + +| Category | Stack | +| :---: | --- | +| Frontend | ![](https://img.shields.io/badge/Kakao%20i%20Open%20Builder-FFCD00?logo=kakaotalk&logoColor=ffffff) | +| Backend | ![](https://img.shields.io/badge/python-3776AB?logo=python&logoColor=ffffff) ![](https://camo.githubusercontent.com/56177508a398e0e29196654b3d18d71e7afe4958e69dabfad6eae1ee05c282e7/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4e6f64652e6a732d3131343431313f6c6f676f3d6e6f64652e6a73) ![](https://camo.githubusercontent.com/c1a21d38ebe312ca67c4239b306a70ad5c45cc97d35a987ee483c5d4fffdffc7/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f547970655363726970742d3331373843363f6c6f676f3d74797065736372697074266c6f676f436f6c6f723d666666666666) ![](https://camo.githubusercontent.com/771c66c24efade30eb5003b9bd0c0b95934decdaabb0147e118819eaa794f7db/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4e6573744a532d4530323334453f6c6f676f3d6e6573746a73266c6f676f436f6c6f723d666666666666) ![](https://camo.githubusercontent.com/4e047329d99d0f2e66780a3a8f05a58374a7be61e1b2033ea3d8a43aa4a63644/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f547970654f524d2d4644463234413f6c6f676f3d747970656f726d266c6f676f436f6c6f723d666666666666) ![](https://img.shields.io/badge/pnpm-F69220?logo=pnpm&logoColor=ffffff)
![](https://img.shields.io/badge/Spring%20Boot-6DB33F?logo=springboot&logoColor=white) ![](https://img.shields.io/badge/Redis-DC382D?logo=Redis&logoColor=white) ![](https://img.shields.io/badge/JUnit5-25A162?&logo=JUnit5&logoColor=white) ![](https://img.shields.io/badge/Hibernate-59666C?&logo=Hibernate&logoColor=white) ![](https://camo.githubusercontent.com/8571b20aadb7cc940f1b5b0610725fd53ee0f964b891b01d247d19ecff99926e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f506f737467726553514c2d3431363945313f6c6f676f3d706f737467726573716c266c6f676f436f6c6f723d666666666666) | +| Deployment | ![](https://img.shields.io/badge/Google%20Cloud%20Platform-4285F4?logo=google-cloud&logoColor=ffffff) ![](https://camo.githubusercontent.com/ca093296b9d015edbfd5a950c38f335486f3be08b04022c70b2949aa4b97365a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6e67696e782d3031343533323f6c6f676f3d4e67696e78266c6f676f436f6c6f723d30303936333926) ![](https://camo.githubusercontent.com/00e52a40c165fca8664e331c61c4a9590d2c470a1b21532d57935c9672065159/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f47697448756220416374696f6e732d3230383846463f6c6f676f3d47697448756220416374696f6e73266c6f676f436f6c6f723d666666666666) ![](https://camo.githubusercontent.com/2f0cdd506c8a73c472da0bc4342401b8a3cdce3f12f1e4bdb92ca1f8627f667d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446f636b65722d3234393645443f6c6f676f3d646f636b6572266c6f676f436f6c6f723d666666666666) ![](https://img.shields.io/badge/sentry-362D59?logo=sentry&logoColor=ffffff) | +| Collaboration | ![](https://camo.githubusercontent.com/77eecc15d2bbf26e04ff2ce7f34025d72a0e11327fac97688a1c3fe0701853d5/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4e6f74696f6e2d3030303030303f6c6f676f3d4e6f74696f6e) ![](https://camo.githubusercontent.com/f0a2f97aebc746865ebb9c711364148593ae2e35b8b7360b9ebf3d99a6fb1919/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4669676d612d4632344531453f6c6f676f3d4669676d61266c6f676f436f6c6f723d666666666666) ![](https://camo.githubusercontent.com/8dfedc7e808ce3f0713da774a328e1f411774b13958e63df895bbaa4689e17b4/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f536c61636b2d3441313534423f6c6f676f3d536c61636b266c6f676f436f6c6f723d666666666666) | + +

+ +# 🐾 팀원 소개 + +| | | | | | +| :----------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------: | +| Dongho Jang
[@JangDongHo](https://github.com/JangDongHo) | hykim02
[@hykim02](https://github.com/hykim02) | hayeonkang
[@hayeonkang](https://github.com/hayeonkang) | [@brainVRG](https://github.com/brainVRG) | @minseob | + +[Table made by TIT](https://team-info-table.seondal.kr/) From 2c0aff0ec71256343f5ab58501fa30e645811cff Mon Sep 17 00:00:00 2001 From: hykim02 Date: Fri, 21 Mar 2025 14:46:36 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[Fix]cafeteria=20=EC=BA=90=EC=8B=B1=20?= =?UTF-8?q?=ED=82=A4=EA=B0=92=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/v2/cafeteria/CacheServiceV2.java | 1 - .../v2/cafeteria/CafeteriaServiceV2.java | 2 +- .../cafeteria/CafeteriaServiceV2Test.java | 46 ++++++++++++++++++- .../cafeteria/CampusServiceV2Test.java | 1 - 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/example/Jinus/service/v2/cafeteria/CacheServiceV2.java b/src/main/java/com/example/Jinus/service/v2/cafeteria/CacheServiceV2.java index 8d577c1..3cde52d 100644 --- a/src/main/java/com/example/Jinus/service/v2/cafeteria/CacheServiceV2.java +++ b/src/main/java/com/example/Jinus/service/v2/cafeteria/CacheServiceV2.java @@ -34,7 +34,6 @@ public List getDietList(HandleRequestDto parameters, int cafeteriaId) { // Redis에서 조회 (없으면 DB에서 가져옴) - // cache-warming 적용 @Cacheable(value = "cafeteriaList", key = "#campusId", cacheManager = "contentCacheManager") public List getCafeteriaList(int campusId) { return cafeteriaRepositoryV2.findCafeteriaListByCampusId(campusId); diff --git a/src/main/java/com/example/Jinus/service/v2/cafeteria/CafeteriaServiceV2.java b/src/main/java/com/example/Jinus/service/v2/cafeteria/CafeteriaServiceV2.java index 5e6f256..49293f9 100644 --- a/src/main/java/com/example/Jinus/service/v2/cafeteria/CafeteriaServiceV2.java +++ b/src/main/java/com/example/Jinus/service/v2/cafeteria/CafeteriaServiceV2.java @@ -85,7 +85,7 @@ public List mappingButtonDto() { } - @Cacheable(value = "cafeteriaId", key = "#cafeteriaName", + @Cacheable(value = "cafeteriaId", key = "#campusId + '::' + #cafeteriaName", unless = "#result == -1", cacheManager = "contentCacheManager") // 캠퍼스에 식당이 존재한다면 cafeteriaId 찾기 diff --git a/src/test/java/com/example/Jinus/service/cafeteria/CafeteriaServiceV2Test.java b/src/test/java/com/example/Jinus/service/cafeteria/CafeteriaServiceV2Test.java index e23146b..be6ba52 100644 --- a/src/test/java/com/example/Jinus/service/cafeteria/CafeteriaServiceV2Test.java +++ b/src/test/java/com/example/Jinus/service/cafeteria/CafeteriaServiceV2Test.java @@ -1,5 +1,6 @@ package com.example.Jinus.service.cafeteria; +import com.example.Jinus.config.RedisCacheManager; import com.example.Jinus.dto.data.CafeteriaDto; import com.example.Jinus.repository.v2.cafeteria.CafeteriaRepositoryV2; import com.example.Jinus.service.v2.cafeteria.CacheServiceV2; @@ -10,15 +11,18 @@ import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.test.context.ActiveProfiles; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.client.ExpectedCount.times; @ExtendWith(MockitoExtension.class) @SpringBootTest @@ -27,6 +31,8 @@ public class CafeteriaServiceV2Test { @Autowired private CafeteriaServiceV2 cafeteriaServiceV2; @Autowired + private RedisTemplate redisTemplate; + @Autowired private CacheServiceV2 cacheServiceV2; @MockBean private CafeteriaRepositoryV2 cafeteriaRepositoryV2; @@ -48,4 +54,40 @@ public void checkUserCafeteriaList() { // then assertThat(result).usingRecursiveComparison().isEqualTo(resultList); } + +// @Test +// @DisplayName("campusId, cafeteriaName을 키값으로 캐싱하여 cafeteriaId 값을 찾는다.") +// public void checkCacheData() { +// // given +// int campusId = 1; +// String cafeteriaName = "교직원식당"; +// int expectedCafeteriaId = 4; +// String expectedCacheKey = "cafeteridId::" + campusId + "::" + cafeteriaName; // 캐싱 키 예상값 +// +// // Mock 설정: DB 조회 시 특정 값 반환하도록 설정 +// Mockito.when(cafeteriaRepositoryV2.findCafeteriaId(cafeteriaName, campusId)).thenReturn(Optional.of(expectedCafeteriaId)); +// +// // when: 첫 번째 호출 (DB 조회 발생 후 Redis에 캐싱됨) +// int cafeteriaId = cafeteriaServiceV2.getCafeteriaId(cafeteriaName, campusId); +// +// // then: DB에서 가져온 값이 기대한 값과 같은지 확인 +// assertThat(cafeteriaId).isEqualTo(expectedCafeteriaId); +// +// // Redis에서 직접 키 조회하여 확인 +// ValueOperations valueOps = redisTemplate.opsForValue(); +// String cachedValue = valueOps.get(expectedCacheKey); +// +// assertThat(cachedValue).isNotNull(); // 캐시가 존재해야 함 +// assertThat(Integer.parseInt(cachedValue)).isEqualTo(expectedCafeteriaId); // 저장된 값이 예상한 값과 같은지 확인 +// +// // 캐시가 적용되었는지 검증: 두 번째 호출에서는 DB 조회가 발생하면 안 됨 +// int cachedCafeteriaId = cafeteriaServiceV2.getCafeteriaId(cafeteriaName, campusId); +// +// // 두 번째 호출은 캐시에서 가져와야 하므로 DB 호출이 한 번만 발생해야 함 +// verify(cafeteriaRepositoryV2, Mockito.times(1)).findCafeteriaId(cafeteriaName, campusId); +// +// // 캐시된 값과 두 번째 조회한 값이 같은지 확인 +// assertThat(cachedCafeteriaId).isEqualTo(expectedCafeteriaId); +// +// } } diff --git a/src/test/java/com/example/Jinus/service/cafeteria/CampusServiceV2Test.java b/src/test/java/com/example/Jinus/service/cafeteria/CampusServiceV2Test.java index 16765f7..698e134 100644 --- a/src/test/java/com/example/Jinus/service/cafeteria/CampusServiceV2Test.java +++ b/src/test/java/com/example/Jinus/service/cafeteria/CampusServiceV2Test.java @@ -41,7 +41,6 @@ public void checkUserCampusName() { // when Mockito.when(campusRepositoryV2.findCampusNameByCampusId(campusId)).thenReturn(campusName); // Mocking String result = campusServiceV2.getUserCampusName(campusId); - System.out.println("result:" + result); // then assertThat(result).isEqualTo(campusName);