Commit 6ccbe723 authored by uuo00_n's avatar uuo00_n

feat: 添加Redis缓存支持并更新相关配置

为edu-service和llm-service添加Redis缓存支持
- 在edu-service中启用Spring缓存并添加学生今日摘要缓存
- 在llm-service中为敏感词过滤添加Redis缓存
- 更新docker-compose配置添加Redis服务
- 更新文档和架构图反映Redis集成
parent c2fb1149
...@@ -52,8 +52,8 @@ docker-compose logs -f ...@@ -52,8 +52,8 @@ docker-compose logs -f
首先确保数据库在运行。你可以只通过 Docker 启动 DB: 首先确保数据库在运行。你可以只通过 Docker 启动 DB:
```bash ```bash
# 启动 Postgres 和 Mongo # 启动 Postgres, Mongo 和 Redis
docker-compose up -d postgres mongo docker-compose up -d postgres mongo redis
``` ```
### 2. 启动 Auth Service (Go) ### 2. 启动 Auth Service (Go)
......
...@@ -100,7 +100,9 @@ graph TD ...@@ -100,7 +100,9 @@ graph TD
Gateway --> SecSvc[Security Service (Python/FastAPI)] Gateway --> SecSvc[Security Service (Python/FastAPI)]
AuthSvc --> PG[(PostgreSQL)] AuthSvc --> PG[(PostgreSQL)]
EduSvc --> PG EduSvc --> PG
EduSvc --> Redis[(Redis)]
LLMSvc --> Mongo[(MongoDB)] LLMSvc --> Mongo[(MongoDB)]
LLMSvc --> Redis
SecSvc --> Mongo SecSvc --> Mongo
LLMSvc --> LLM[Ollama / Dify] LLMSvc --> LLM[Ollama / Dify]
SecSvc --> LLM SecSvc --> LLM
......
...@@ -16,6 +16,17 @@ services: ...@@ -16,6 +16,17 @@ services:
networks: networks:
- llm-network - llm-network
# Redis 缓存服务
redis:
image: redis:7-alpine
container_name: llm-filter-redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- llm-network
# 身份认证服务 (Go) # 身份认证服务 (Go)
auth-service: auth-service:
build: build:
...@@ -51,8 +62,11 @@ services: ...@@ -51,8 +62,11 @@ services:
- DB_NAME=llm_filter_db - DB_NAME=llm_filter_db
- DB_PORT=5432 - DB_PORT=5432
- JWT_SECRET=llm_filter_secure_secret_key_2025_update_must_be_32_bytes - JWT_SECRET=llm_filter_secure_secret_key_2025_update_must_be_32_bytes
- SPRING_REDIS_HOST=redis
- SPRING_REDIS_PORT=6379
depends_on: depends_on:
- postgres - postgres
- redis
networks: networks:
- llm-network - llm-network
...@@ -80,8 +94,12 @@ services: ...@@ -80,8 +94,12 @@ services:
# Dify Configuration # Dify Configuration
- DIFY_API_URL=http://datacenter.dldzxx.cn:8089/v1 - DIFY_API_URL=http://datacenter.dldzxx.cn:8089/v1
- DIFY_API_KEY=app-lkK33EQOVXXrjD9x3SKbItr7 - DIFY_API_KEY=app-lkK33EQOVXXrjD9x3SKbItr7
# Redis Configuration
- REDIS_HOST=redis
- REDIS_PORT=6379
depends_on: depends_on:
- mongo - mongo
- redis
networks: networks:
- llm-network - llm-network
...@@ -141,3 +159,4 @@ networks: ...@@ -141,3 +159,4 @@ networks:
volumes: volumes:
postgres_data: postgres_data:
mongo_data: mongo_data:
redis_data:
...@@ -71,6 +71,16 @@ ...@@ -71,6 +71,16 @@
<version>0.11.5</version> <version>0.11.5</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<!-- Redis Cache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>
......
...@@ -6,9 +6,11 @@ import io.swagger.v3.oas.annotations.info.Info; ...@@ -6,9 +6,11 @@ import io.swagger.v3.oas.annotations.info.Info;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@SpringBootApplication @SpringBootApplication
@EnableCaching
@OpenAPIDefinition( @OpenAPIDefinition(
info = @Info( info = @Info(
title = "Edu Service API", title = "Edu Service API",
......
...@@ -9,6 +9,7 @@ import com.llmfilter.edu.security.UserContext; ...@@ -9,6 +9,7 @@ import com.llmfilter.edu.security.UserContext;
import com.llmfilter.edu.service.DashboardService; import com.llmfilter.edu.service.DashboardService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;
...@@ -58,6 +59,7 @@ public class DashboardServiceImpl implements DashboardService { ...@@ -58,6 +59,7 @@ public class DashboardServiceImpl implements DashboardService {
} }
@Override @Override
@Cacheable(value = "student_today_summary", key = "#user.personId + '_' + T(java.time.LocalDate).now().toString()", unless = "#result == null")
public StudentTodaySummary getStudentTodaySummary(UserContext user) { public StudentTodaySummary getStudentTodaySummary(UserContext user) {
String personId = user.getPersonId(); String personId = user.getPersonId();
if (personId == null) { if (personId == null) {
......
...@@ -16,3 +16,13 @@ spring.jpa.properties.hibernate.format_sql=true ...@@ -16,3 +16,13 @@ spring.jpa.properties.hibernate.format_sql=true
# Security # Security
jwt.secret=${JWT_SECRET:your_secret_key_here_must_be_very_long_to_be_secure_at_least_32_bytes} jwt.secret=${JWT_SECRET:your_secret_key_here_must_be_very_long_to_be_secure_at_least_32_bytes}
# Redis Configuration
spring.redis.host=${SPRING_REDIS_HOST:localhost}
spring.redis.port=${SPRING_REDIS_PORT:6379}
spring.redis.timeout=2000
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
server.port=8082
spring.application.name=edu-service
# Database Configuration
spring.datasource.url=jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5433}/${DB_NAME:llm_filter_db}
spring.datasource.username=${DB_USER:admin}
spring.datasource.password=${DB_PASSWORD:password}
spring.datasource.driver-class-name=org.postgresql.Driver
# JPA Configuration
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.format_sql=true
# Security
jwt.secret=${JWT_SECRET:your_secret_key_here_must_be_very_long_to_be_secure_at_least_32_bytes}
artifactId=edu-service
groupId=com.llmfilter
version=0.0.1-SNAPSHOT
com/llmfilter/edu/model/Student$StudentBuilder.class
com/llmfilter/edu/repository/ClassRepository.class
com/llmfilter/edu/repository/PersonRepository.class
com/llmfilter/edu/controller/PersonController.class
com/llmfilter/edu/controller/HealthController.class
com/llmfilter/edu/model/Person.class
com/llmfilter/edu/service/ScheduleService.class
com/llmfilter/edu/dto/HeadTeacherPayload.class
com/llmfilter/edu/dto/TeacherDto.class
com/llmfilter/edu/service/PersonService.class
com/llmfilter/edu/repository/ScheduleRepository.class
com/llmfilter/edu/dto/ClassDto.class
com/llmfilter/edu/service/ClassService.class
com/llmfilter/edu/controller/TeacherController.class
com/llmfilter/edu/dto/ScheduleDto.class
com/llmfilter/edu/model/Schedule$ScheduleBuilder.class
com/llmfilter/edu/model/Teacher.class
com/llmfilter/edu/dto/PersonDto.class
com/llmfilter/edu/dto/AssignTeacherPayload.class
com/llmfilter/edu/EduServiceApplication.class
com/llmfilter/edu/model/ClassEntity.class
com/llmfilter/edu/service/TeacherService.class
com/llmfilter/edu/service/ScheduleService$1.class
com/llmfilter/edu/model/Schedule.class
com/llmfilter/edu/model/Student.class
com/llmfilter/edu/controller/ClassController.class
com/llmfilter/edu/repository/TeacherRepository.class
com/llmfilter/edu/controller/ScheduleController.class
com/llmfilter/edu/model/Teacher$TeacherBuilder.class
com/llmfilter/edu/repository/StudentRepository.class
com/llmfilter/edu/model/Person$PersonBuilder.class
com/llmfilter/edu/model/ClassEntity$ClassEntityBuilder.class
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/model/Schedule.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/model/Student.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/service/ClassService.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/model/Person.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/service/TeacherService.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/controller/HealthController.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/controller/PersonController.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/dto/TeacherDto.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/controller/ClassController.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/dto/ScheduleDto.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/dto/ClassDto.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/model/Teacher.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/service/PersonService.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/dto/AssignTeacherPayload.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/controller/ScheduleController.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/model/ClassEntity.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/repository/ScheduleRepository.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/repository/ClassRepository.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/dto/HeadTeacherPayload.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/repository/TeacherRepository.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/dto/PersonDto.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/repository/StudentRepository.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/controller/TeacherController.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/EduServiceApplication.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/service/ScheduleService.java
/Users/uu/Desktop/dles_prj/llm-filter/microservices/edu-service/src/main/java/com/llmfilter/edu/repository/PersonRepository.java
...@@ -46,4 +46,10 @@ class Settings(BaseSettings): ...@@ -46,4 +46,10 @@ class Settings(BaseSettings):
# 学期配置 # 学期配置
TERM_START_DATE: str = os.getenv("TERM_START_DATE", "2025-09-01") # 默认开学日期 TERM_START_DATE: str = os.getenv("TERM_START_DATE", "2025-09-01") # 默认开学日期
# Redis 配置
REDIS_HOST: str = os.getenv("REDIS_HOST", "localhost")
REDIS_PORT: int = int(os.getenv("REDIS_PORT", "6379"))
REDIS_DB: int = int(os.getenv("REDIS_DB", "0"))
REDIS_PASSWORD: str = os.getenv("REDIS_PASSWORD", "")
settings = Settings() settings = Settings()
...@@ -35,7 +35,14 @@ async def add_sensitive_word( ...@@ -35,7 +35,14 @@ async def add_sensitive_word(
result = await db.db.sensitive_words.insert_one(sensitive_word.dict()) result = await db.db.sensitive_words.insert_one(sensitive_word.dict())
# 更新敏感词过滤器 # 清除 Redis 缓存并更新敏感词过滤器
try:
keys = await sensitive_word_filter.redis_client.keys("filter_cache:*")
if keys:
await sensitive_word_filter.redis_client.delete(*keys)
except Exception:
pass
await sensitive_word_filter.load_sensitive_words() await sensitive_word_filter.load_sensitive_words()
return str(result.inserted_id) return str(result.inserted_id)
...@@ -68,7 +75,14 @@ async def bulk_import_sensitive_words(words: List[SensitiveWordCreate]) -> int: ...@@ -68,7 +75,14 @@ async def bulk_import_sensitive_words(words: List[SensitiveWordCreate]) -> int:
# 批量插入数据库 # 批量插入数据库
result = await db.db.sensitive_words.insert_many(word_models) result = await db.db.sensitive_words.insert_many(word_models)
# 更新敏感词过滤器 # 清除 Redis 缓存并更新敏感词过滤器
try:
keys = await sensitive_word_filter.redis_client.keys("filter_cache:*")
if keys:
await sensitive_word_filter.redis_client.delete(*keys)
except Exception:
pass
await sensitive_word_filter.load_sensitive_words() await sensitive_word_filter.load_sensitive_words()
return len(result.inserted_ids) return len(result.inserted_ids)
...@@ -80,6 +94,16 @@ async def delete_sensitive_word(word_id: str) -> bool: ...@@ -80,6 +94,16 @@ async def delete_sensitive_word(word_id: str) -> bool:
# 更新敏感词过滤器 # 更新敏感词过滤器
if result.deleted_count > 0: if result.deleted_count > 0:
# 清除 Redis 中的所有过滤缓存
try:
# 注意:在生产环境中,SCAN 模式更安全,但 KEYS 在小数据量下更方便
# 这里简单处理,清除所有 filter_cache:* 键
keys = await sensitive_word_filter.redis_client.keys("filter_cache:*")
if keys:
await sensitive_word_filter.redis_client.delete(*keys)
except Exception:
pass
await sensitive_word_filter.load_sensitive_words() await sensitive_word_filter.load_sensitive_words()
return True return True
return False return False
......
...@@ -8,11 +8,25 @@ class TrieNode: ...@@ -8,11 +8,25 @@ class TrieNode:
self.is_end_of_word = False self.is_end_of_word = False
self.word_info = None # 存储敏感词的完整信息 self.word_info = None # 存储敏感词的完整信息
import hashlib
import json
import redis.asyncio as redis
from app.core.config import settings
class SensitiveWordFilter: class SensitiveWordFilter:
def __init__(self): def __init__(self):
self.root = TrieNode() self.root = TrieNode()
self.sensitive_words = {} # 改为字典,存储敏感词及其信息 self.sensitive_words = {} # 改为字典,存储敏感词及其信息
# 初始化 Redis 连接
self.redis_client = redis.Redis(
host=settings.REDIS_HOST,
port=settings.REDIS_PORT,
db=settings.REDIS_DB,
password=settings.REDIS_PASSWORD if settings.REDIS_PASSWORD else None,
decode_responses=True
)
async def load_sensitive_words(self): async def load_sensitive_words(self):
"""从数据库加载敏感词""" """从数据库加载敏感词"""
self.sensitive_words = {} self.sensitive_words = {}
...@@ -44,9 +58,9 @@ class SensitiveWordFilter: ...@@ -44,9 +58,9 @@ class SensitiveWordFilter:
node.is_end_of_word = True node.is_end_of_word = True
node.word_info = word_info node.word_info = word_info
def check_text(self, text: str) -> Dict[str, Any]: async def check_text(self, text: str) -> Dict[str, Any]:
""" """
检查文本是否包含敏感词 检查文本是否包含敏感词 (优先查询 Redis 缓存)
Args: Args:
text: 要检查的文本 text: 要检查的文本
...@@ -64,6 +78,16 @@ class SensitiveWordFilter: ...@@ -64,6 +78,16 @@ class SensitiveWordFilter:
"sensitive_words_found": [], "sensitive_words_found": [],
"highest_severity": 0 "highest_severity": 0
} }
# 1. 尝试从 Redis 获取缓存结果
cache_key = f"filter_cache:{hashlib.md5(text.encode('utf-8')).hexdigest()}"
try:
cached_result = await self.redis_client.get(cache_key)
if cached_result:
return json.loads(cached_result)
except Exception as e:
# Redis 异常不应阻塞业务,降级为直接计算
pass
found_words = [] found_words = []
highest_severity = 0 highest_severity = 0
...@@ -94,11 +118,19 @@ class SensitiveWordFilter: ...@@ -94,11 +118,19 @@ class SensitiveWordFilter:
break break
return { result = {
"contains_sensitive_words": len(found_words) > 0, "contains_sensitive_words": len(found_words) > 0,
"sensitive_words_found": found_words, "sensitive_words_found": found_words,
"highest_severity": highest_severity "highest_severity": highest_severity
} }
# 2. 将计算结果写入 Redis 缓存 (过期时间 1 小时)
try:
await self.redis_client.setex(cache_key, 3600, json.dumps(result))
except Exception:
pass
return result
# 创建全局敏感词过滤器实例 # 创建全局敏感词过滤器实例
sensitive_word_filter = SensitiveWordFilter() sensitive_word_filter = SensitiveWordFilter()
\ No newline at end of file
...@@ -9,3 +9,4 @@ httpx==0.25.0 ...@@ -9,3 +9,4 @@ httpx==0.25.0
python-dotenv==1.0.0 python-dotenv==1.0.0
PyJWT==2.8.0 PyJWT==2.8.0
psycopg2-binary==2.9.9 psycopg2-binary==2.9.9
redis==5.0.1
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment