feat: Embedded Redis 적용 — Docker 없이 Redis 자동 구동

- EmbeddedRedisConfig: 외부 Redis 없으면 Embedded Redis 자동 시작
- RedisCacheAutoConfig: Redis 연결 실패 시 인메모리 캐시 fallback
- install.bat/sh: Docker/Redis 필수 의존성 제거, 인메모리 전환 안내
- application yml: Redis 설정을 선택 사항으로 변경

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
이 Commit은 다음에 포함되어 있습니다:
2026-03-27 14:37:57 +09:00
부모 c44b1273b1
커밋 6b248c6a69
8개의 변경된 파일131개의 추가작업 그리고 44개의 파일을 삭제

파일 보기

@@ -34,6 +34,12 @@ dependencies {
compileOnly 'com.oracle.database.jdbc:ojdbc11:23.6.0.24.10'
compileOnly 'com.microsoft.sqlserver:mssql-jdbc:12.8.1.jre11'
// Embedded Redis (Docker 없이 Redis 자동 구동)
api('it.ozimov:embedded-redis:0.7.3') {
exclude group: 'org.slf4j'
exclude group: 'ch.qos.logback'
}
// Micrometer
runtimeOnly 'io.micrometer:micrometer-registry-prometheus'

파일 보기

@@ -85,7 +85,7 @@ where docker >nul 2>&1
if !ERRORLEVEL! equ 0 (
for /f "delims=" %%d in ('docker --version') do echo [OK] %%d
) else (
echo [WARN] Docker 미설치 — DB/Redis를 직접 설치해야 합니다
echo [INFO] Docker 미설치 — DB를 직접 설치해야 합니다
)
:: ---------- 4. Redis 확인 ----------
@@ -100,23 +100,7 @@ if !ERRORLEVEL! equ 0 (
)
)
if !REDIS_OK! equ 0 (
where docker >nul 2>&1
if !ERRORLEVEL! equ 0 (
docker ps --format "{{.Names}}" 2>nul | findstr /i "redis" >nul 2>&1
if !ERRORLEVEL! equ 0 (
echo [OK] Redis Docker 컨테이너 실행 중
) else (
echo [INFO] Redis 미실행 — Docker로 자동 시작...
docker run -d --name redis -p 6379:6379 redis:7-alpine >nul 2>&1
if !ERRORLEVEL! equ 0 (
echo [OK] Redis 컨테이너 시작 완료
) else (
echo [WARN] Redis 자동 시작 실패 — 수동으로 Redis를 실행하세요
)
)
) else (
echo [WARN] Redis 연결 불가 — Redis를 설치/실행하세요
)
echo [INFO] Redis 미실행 — 인메모리 캐시로 자동 전환됩니다 (운영 환경에서는 Redis 권장^)
)
:: ---------- 5. MySQL 연결 확인 ----------
@@ -170,8 +154,8 @@ if not exist "!PROJECT_ROOT!\.env" (
echo DB_USER=jsh
echo DB_PASS=jsh@
echo.
echo # --- Redis ---
echo SPRING_DATA_REDIS_HOST=localhost
echo # --- Redis (선택 — 미설정 시 인메모리 캐시 자동 사용) ---
echo # SPRING_DATA_REDIS_HOST=localhost
echo.
echo # --- CORS ---
echo CORS_ORIGINS=https://app.company.com

파일 보기

@@ -136,7 +136,7 @@ echo "3. Docker 확인 (선택)"
if command -v docker &>/dev/null; then
ok "$(docker --version | head -1)"
else
warn "Docker 미설치 — DB/Redis를 직접 설치해야 합니다"
info "Docker 미설치 — DB를 직접 설치해야 합니다"
fi
# ---------- 4. Redis 확인 ----------
@@ -145,19 +145,8 @@ REDIS_HOST="${SPRING_DATA_REDIS_HOST:-localhost}"
REDIS_PORT="${SPRING_DATA_REDIS_PORT:-6379}"
if command -v redis-cli &>/dev/null && redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" ping 2>/dev/null | grep -q PONG; then
ok "Redis 응답 OK ($REDIS_HOST:$REDIS_PORT)"
elif command -v docker &>/dev/null; then
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q redis; then
ok "Redis Docker 컨테이너 실행 중"
else
info "Redis 미실행 — Docker로 자동 시작..."
if docker run -d --name redis -p "${REDIS_PORT}:6379" redis:7-alpine &>/dev/null; then
ok "Redis 컨테이너 시작 완료"
else
warn "Redis 자동 시작 실패 — 수동으로 Redis를 실행하세요"
fi
fi
else
warn "Redis 연결 불가 ($REDIS_HOST:$REDIS_PORT) — Redis를 설치/실행하세요"
info "Redis 미실행 — 인메모리 캐시로 자동 전환됩니다 (운영 환경에서는 Redis 권장)"
fi
# ---------- 5. MySQL 연결 확인 ----------
@@ -213,8 +202,8 @@ DB_NAME=mos
DB_USER=jsh
DB_PASS=jsh@
# --- Redis ---
SPRING_DATA_REDIS_HOST=localhost
# --- Redis (선택 — 미설정 시 인메모리 캐시 자동 사용) ---
# SPRING_DATA_REDIS_HOST=localhost
# --- CORS ---
CORS_ORIGINS=https://app.company.com

파일 보기

@@ -0,0 +1,68 @@
package kr.co.accura.wbx.spring.config;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import redis.embedded.RedisServer;
import java.net.Socket;
/**
* Embedded Redis 자동 구성.
*
* 동작 순서:
* 1. 외부 Redis가 이미 실행 중이면 → 그대로 사용 (운영 환경)
* 2. 외부 Redis 없으면 → Embedded Redis 자동 시작 (개발 환경)
* 3. Embedded Redis도 실패하면 → RedisCacheAutoConfig에서 인메모리 캐시 전환
*
* Docker Desktop 없이 개발 PC에서 Redis가 자동 구동됩니다.
*/
@Configuration
public class EmbeddedRedisConfig {
private static final Logger log = LoggerFactory.getLogger(EmbeddedRedisConfig.class);
@Value("${spring.data.redis.port:6379}")
private int redisPort;
private RedisServer redisServer;
@PostConstruct
public void start() {
if (isRedisRunning()) {
log.info("[WBX] 외부 Redis 감지 — Embedded Redis 건너뜀 (port: {})", redisPort);
return;
}
try {
redisServer = RedisServer.builder()
.port(redisPort)
.setting("maxmemory 128mb")
.setting("maxmemory-policy allkeys-lru")
.build();
redisServer.start();
log.info("[WBX] Embedded Redis 시작 (port: {})", redisPort);
} catch (Exception e) {
log.warn("[WBX] Embedded Redis 시작 실패 — 인메모리 캐시로 전환됩니다 ({})", e.getMessage());
redisServer = null;
}
}
@PreDestroy
public void stop() {
if (redisServer != null && redisServer.isActive()) {
redisServer.stop();
log.info("[WBX] Embedded Redis 종료");
}
}
private boolean isRedisRunning() {
try (Socket socket = new Socket("localhost", redisPort)) {
return true;
} catch (Exception e) {
return false;
}
}
}

파일 보기

@@ -0,0 +1,42 @@
package kr.co.accura.wbx.spring.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
/**
* Redis/인메모리 캐시 자동 구성.
*
* 동작 방식:
* 1. Redis 연결 가능 → RedisCacheManager 사용
* 2. Redis 연결 불가 → ConcurrentMapCacheManager 자동 전환 (인메모리)
*
* 개발 PC에서 Docker/Redis 없이도 애플리케이션이 정상 구동됩니다.
*/
@Configuration
@AutoConfigureBefore(CacheAutoConfiguration.class)
public class RedisCacheAutoConfig {
private static final Logger log = LoggerFactory.getLogger(RedisCacheAutoConfig.class);
@Bean
@Primary
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
try {
redisConnectionFactory.getConnection().close();
log.info("[WBX] Redis 캐시 사용 (연결 성공)");
return RedisCacheManager.builder(redisConnectionFactory).build();
} catch (Exception e) {
log.warn("[WBX] Redis 연결 실패 — 인메모리 캐시로 전환 ({})", e.getMessage());
return new ConcurrentMapCacheManager("permissions", "deptScopes");
}
}
}

파일 보기

@@ -1,2 +1,4 @@
kr.co.accura.wbx.spring.config.EmbeddedRedisConfig
kr.co.accura.wbx.spring.config.RedisCacheAutoConfig
kr.co.accura.wbx.spring.config.WbxAutoConfiguration
kr.co.accura.wbx.spring.admin.AdminAutoConfiguration

파일 보기

@@ -26,10 +26,11 @@ spring:
minimum-idle: 5
connection-timeout: 30000
data:
redis:
host: localhost
port: 6379
# Redis (선택 — 미설정 시 인메모리 캐시 자동 사용)
# data:
# redis:
# host: localhost
# port: 6379
server:
port: 8080

파일 보기

@@ -16,11 +16,6 @@ spring:
flyway:
enabled: false
data:
redis:
host: localhost
port: 6379
wbx:
spring:
jwt: