Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'com.google.genai:google-genai:1.6.0'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.kernellabs.kernellabs.application;

import org.springframework.stereotype.Service;

import com.kernellabs.kernellabs.global.util.RedisUtil;
import com.kernellabs.kernellabs.infrastructure.external.GeminiApiClient;
import com.kernellabs.kernellabs.infrastructure.external.GeminiSearchClient;
import com.kernellabs.kernellabs.presentation.dto.response.PolicyResponse;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class PolicyService {

private final String POLICY_REDIS_KEY = "policy";
private final RedisUtil redisUtil;
private final GeminiSearchClient geminiSearchClient;
private final String prompt = """
의성군으로 이주하려는 사람들을 위한 최신 혜택 정보를 알려줘.
주거, 교육, 귀농귀촌, 복지, 창업, 일자리 지원금, 정착금 등 모든 종류의 이주 및 정착 혜택을 포함해줘.
각 혜택별로 지원 조건, 신청 방법, 담당 부서 또는 관련 웹사이트 링크 같은 상세 정보도 알려줘.
정보를 대주제와 소주제로 나눠서 정리해줘.
각 소주제 아래에 자세한 설명을 추가하고, 관련 링크가 있다면 URL 주소를 명확히 포함해줘.
각 항목은 줄 바꿈(\n)을 사용해서 구분해줘.
다른 지역 정보나 일반적인 내용은 제외하고, 오직 의성군 관련 혜택만 다뤄줘.
""";

public PolicyResponse getCurrentPolicy() {
String result = "";
if(redisUtil.existData(POLICY_REDIS_KEY)){
result = redisUtil.getData(POLICY_REDIS_KEY);
}
else{
result = geminiSearchClient.generateAnswer(prompt);
redisUtil.setDataExpire(POLICY_REDIS_KEY, result, 10800);
}
return PolicyResponse.builder().policy(result).build();

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.kernellabs.kernellabs.global.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

@Value("${spring.data.redis.host}")
private String host;

@Value("${spring.data.redis.port}")
private int port;

@Value("${spring.data.redis.password}")
private String password;

@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(host, port);
redisStandaloneConfiguration.setHostName(host);
redisStandaloneConfiguration.setPort(port);
redisStandaloneConfiguration.setPassword(password);
return new LettuceConnectionFactory(redisStandaloneConfiguration);
}

@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
return template;
}
}
35 changes: 35 additions & 0 deletions src/main/java/com/kernellabs/kernellabs/global/util/RedisUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.kernellabs.kernellabs.global.util;

import java.time.Duration;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class RedisUtil {

private final StringRedisTemplate redisTemplate;

public String getData(String key) {
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
return valueOperations.get(key);
}

public boolean existData(String key) {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}

public void setDataExpire(String key, String value, long duration) {
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
Duration expireDuration = Duration.ofSeconds(duration);
valueOperations.set(key, value, expireDuration);
}

public void deleteData(String key) {
redisTemplate.delete(key);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.kernellabs.kernellabs.application;
package com.kernellabs.kernellabs.infrastructure.external;

import autovalue.shaded.com.google.common.collect.ImmutableList;
import com.google.genai.Client;
Expand All @@ -10,13 +10,13 @@
import org.springframework.stereotype.Service;

@Service
public class GeminiService {
public class GeminiSearchClient {

private final Client client;
private final Tool googleSearchTool;
private final String modelName;

public GeminiService(
public GeminiSearchClient(
@Value("${gemini.api.key}") String apiKey,
@Value("${gemini.model:gemini-2.5-flash}") String modelName
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.kernellabs.kernellabs.presentation.controller;

import com.kernellabs.kernellabs.application.GeminiService;
import com.kernellabs.kernellabs.infrastructure.external.GeminiSearchClient;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import lombok.Data;
Expand All @@ -15,11 +15,11 @@
@RequestMapping("/api/genie")
@AllArgsConstructor
public class GeminiController {
private final GeminiService geminiService;
private final GeminiSearchClient geminiSearchClient;

@PostMapping("/chat")
public ResponseEntity<ChatResponse> chat(@Valid @RequestBody ChatRequest req) {
String answer = geminiService.generateAnswer(req.getPrompt());
String answer = geminiSearchClient.generateAnswer(req.getPrompt());
return ResponseEntity.ok(new ChatResponse(answer));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.kernellabs.kernellabs.presentation.controller;

import java.util.List;

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.RestController;

import com.kernellabs.kernellabs.application.PolicyService;
import com.kernellabs.kernellabs.global.common.ApiResponse;
import com.kernellabs.kernellabs.presentation.dto.response.PolicyResponse;

import lombok.RequiredArgsConstructor;

@RestController
@RequestMapping("/policies")
@RequiredArgsConstructor
public class PolicyController {
private final PolicyService policyService;

@GetMapping("")
public ResponseEntity<ApiResponse<PolicyResponse>> getCurrentPolicy() {
PolicyResponse policyResponse = policyService.getCurrentPolicy();
return ResponseEntity.ok(ApiResponse.success(policyResponse));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.kernellabs.kernellabs.presentation.dto.response;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class PolicyResponse {
private String policy;
}
6 changes: 6 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ spring:
properties:
hibernate:
format_sql: true

data:
redis:
host: ${REDIS_HOST}
password: ${REDIS_PASSWORD}
port: 6379
logging:
level:
root: INFO # 전체 로그 최소 INFO 이상 기록 :contentReference[oaicite:0]{index=0}
Expand Down
Loading